@ls-stack/utils 3.9.1 → 3.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -21,23 +21,12 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var asyncQueue_exports = {};
22
22
  __export(asyncQueue_exports, {
23
23
  createAsyncQueue: () => createAsyncQueue,
24
- createAsyncQueueWithId: () => createAsyncQueueWithId
24
+ createAsyncQueueWithMeta: () => createAsyncQueueWithMeta
25
25
  });
26
26
  module.exports = __toCommonJS(asyncQueue_exports);
27
27
  var import_evtmitter = require("evtmitter");
28
28
  var import_t_result = require("t-result");
29
29
 
30
- // src/assertions.ts
31
- function isObject(value) {
32
- return typeof value === "object" && value !== null && !Array.isArray(value);
33
- }
34
- function isFunction(value) {
35
- return typeof value === "function";
36
- }
37
- function isPromise(value) {
38
- return isObject(value) && "then" in value && isFunction(value.then);
39
- }
40
-
41
30
  // src/promiseUtils.ts
42
31
  function defer() {
43
32
  let resolve;
@@ -74,38 +63,48 @@ var AsyncQueue = class {
74
63
  this.#queue.push(task);
75
64
  this.#size++;
76
65
  }
77
- add(fn, options) {
66
+ async add(fn, options) {
67
+ if (this.#signal?.aborted) {
68
+ return import_t_result.Result.err(
69
+ this.#signal.reason instanceof Error ? this.#signal.reason : new DOMException("Queue aborted", "AbortError")
70
+ );
71
+ }
78
72
  const deferred = defer();
79
73
  const taskTimeout = this.#taskTimeout ?? options?.timeout;
80
74
  const task = {
81
75
  run: async (ctx) => {
82
- if (isPromise(fn)) {
83
- return fn;
84
- }
85
- return await fn(ctx);
76
+ return fn(ctx);
86
77
  },
87
78
  resolve: deferred.resolve,
88
79
  reject: deferred.reject,
89
80
  signal: options?.signal,
90
- id: options?.id,
81
+ meta: options?.meta,
91
82
  timeout: taskTimeout
92
83
  };
93
84
  this.#enqueue(task);
94
85
  this.#processQueue();
95
- return deferred.promise;
86
+ const r = await deferred.promise;
87
+ if (options?.onComplete) {
88
+ r.onOk(options.onComplete);
89
+ }
90
+ if (options?.onError) {
91
+ r.onErr(options.onError);
92
+ }
93
+ return r;
96
94
  }
97
95
  resultifyAdd(fn, options) {
98
96
  return this.add(
99
97
  (ctx) => (0, import_t_result.resultify)(async () => {
100
- if (isPromise(fn)) {
101
- return await fn;
102
- }
103
98
  return fn(ctx);
104
99
  }),
105
100
  options
106
101
  );
107
102
  }
108
103
  async #processQueue() {
104
+ if (this.#signal?.aborted) {
105
+ this.clear();
106
+ return;
107
+ }
109
108
  if (this.#pending >= this.#concurrency || this.#queue.length === 0) {
110
109
  return;
111
110
  }
@@ -129,43 +128,52 @@ var AsyncQueue = class {
129
128
  let abortListener;
130
129
  try {
131
130
  if (signal?.aborted) {
132
- const error = signal.reason instanceof Error ? signal.reason : new DOMException("Aborted", "AbortError");
131
+ const error = signal.reason instanceof Error ? signal.reason : new DOMException("This operation was aborted", "AbortError");
133
132
  throw error;
134
133
  }
135
134
  const signalAbortPromise = new Promise((_, reject) => {
136
135
  if (signal) {
137
- const error = signal.reason instanceof Error ? signal.reason : new DOMException("Aborted", "AbortError");
136
+ const error = signal.reason instanceof Error ? signal.reason : new DOMException("This operation was aborted", "AbortError");
138
137
  abortListener = () => {
139
138
  reject(error);
140
139
  };
141
140
  signal.addEventListener("abort", abortListener, { once: true });
142
141
  }
143
142
  });
144
- const taskRunPromise = task.run({ signal, id: task.id });
145
- this.events.emit("start", { id: task.id });
143
+ const taskRunPromise = task.run({ signal, meta: task.meta });
144
+ this.events.emit("start", { meta: task.meta });
146
145
  const result = await Promise.race([taskRunPromise, signalAbortPromise]);
147
146
  if ((0, import_t_result.isResult)(result)) {
148
147
  task.resolve(result);
149
148
  if (result.error) {
150
149
  this.#failed++;
151
- this.events.emit("error", { id: task.id, error: result.error });
150
+ this.events.emit("error", {
151
+ meta: task.meta,
152
+ error: result.error
153
+ });
152
154
  } else {
153
155
  this.#completed++;
154
- this.events.emit("complete", { id: task.id, value: result.value });
156
+ this.events.emit("complete", {
157
+ meta: task.meta,
158
+ value: result.value
159
+ });
155
160
  }
156
161
  } else {
157
162
  const error = new Error("Response not a Result");
158
163
  task.resolve(import_t_result.Result.err(error));
159
164
  this.#failed++;
160
165
  this.events.emit("error", {
161
- id: task.id,
166
+ meta: task.meta,
162
167
  error
163
168
  });
164
169
  }
165
170
  } catch (error) {
166
171
  task.resolve(import_t_result.Result.err(error));
167
172
  this.#failed++;
168
- this.events.emit("error", { id: task.id, error: (0, import_t_result.unknownToError)(error) });
173
+ this.events.emit("error", {
174
+ meta: task.meta,
175
+ error: (0, import_t_result.unknownToError)(error)
176
+ });
169
177
  } finally {
170
178
  if (signal && abortListener) {
171
179
  signal.removeEventListener("abort", abortListener);
@@ -193,13 +201,9 @@ var AsyncQueue = class {
193
201
  this.#idleResolvers.push(resolve);
194
202
  });
195
203
  }
196
- clear({ resetCounters = true } = {}) {
204
+ clear() {
197
205
  this.#queue = [];
198
206
  this.#size = 0;
199
- if (resetCounters) {
200
- this.#completed = 0;
201
- this.#failed = 0;
202
- }
203
207
  if (this.#pending === 0) {
204
208
  this.#resolveIdleWaiters();
205
209
  }
@@ -217,7 +221,7 @@ var AsyncQueue = class {
217
221
  return this.#size;
218
222
  }
219
223
  };
220
- var AsyncQueueWithId = class extends AsyncQueue {
224
+ var AsyncQueueWithMeta = class extends AsyncQueue {
221
225
  constructor(options) {
222
226
  super(options);
223
227
  }
@@ -231,11 +235,11 @@ var AsyncQueueWithId = class extends AsyncQueue {
231
235
  function createAsyncQueue(options) {
232
236
  return new AsyncQueue(options);
233
237
  }
234
- function createAsyncQueueWithId(options) {
235
- return new AsyncQueueWithId(options);
238
+ function createAsyncQueueWithMeta(options) {
239
+ return new AsyncQueueWithMeta(options);
236
240
  }
237
241
  // Annotate the CommonJS export names for ESM import in node:
238
242
  0 && (module.exports = {
239
243
  createAsyncQueue,
240
- createAsyncQueueWithId
244
+ createAsyncQueueWithMeta
241
245
  });
@@ -6,51 +6,51 @@ type AsyncQueueOptions = {
6
6
  signal?: AbortSignal;
7
7
  timeout?: number;
8
8
  };
9
- type AddOptions<I> = {
9
+ type AddOptions<I, T, E extends ResultValidErrors> = {
10
10
  signal?: AbortSignal;
11
11
  timeout?: number;
12
- id?: I;
12
+ meta?: I;
13
+ onComplete?: (value: T) => void;
14
+ onError?: (error: E | Error) => void;
13
15
  };
14
16
  type RunCtx<I> = {
15
17
  signal?: AbortSignal;
16
- id?: I;
18
+ meta?: I;
17
19
  };
18
- declare class AsyncQueue<T, E extends ResultValidErrors = Error, I = undefined> {
20
+ declare class AsyncQueue<T, E extends ResultValidErrors = Error, I = unknown> {
19
21
  #private;
20
22
  events: evtmitter.Emitter<{
21
23
  error: {
22
- id: I;
24
+ meta: I;
23
25
  error: E | Error;
24
26
  };
25
27
  complete: {
26
- id: I;
28
+ meta: I;
27
29
  value: T;
28
30
  };
29
31
  start: {
30
- id: I;
32
+ meta: I;
31
33
  };
32
34
  }>;
33
35
  constructor({ concurrency, signal, timeout: taskTimeout, }?: AsyncQueueOptions);
34
- add(fn: ((ctx: RunCtx<I>) => Promise<Result<T, E>> | Result<T, E>) | Promise<Result<T, E>>, options?: AddOptions<I>): Promise<Result<T, E | Error>>;
35
- resultifyAdd(fn: ((ctx: RunCtx<I>) => Promise<T> | T) | Promise<T>, options?: AddOptions<I>): Promise<Result<T, E | Error>>;
36
+ add(fn: (ctx: RunCtx<I>) => Promise<Result<T, E>> | Result<T, E>, options?: AddOptions<I, T, E>): Promise<Result<T, E | Error>>;
37
+ resultifyAdd(fn: (ctx: RunCtx<I>) => Promise<T> | T, options?: AddOptions<I, T, E>): Promise<Result<T, E | Error>>;
36
38
  onIdle(): Promise<void>;
37
- clear({ resetCounters }?: {
38
- resetCounters?: boolean;
39
- }): void;
39
+ clear(): void;
40
40
  get completed(): number;
41
41
  get failed(): number;
42
42
  get pending(): number;
43
43
  get size(): number;
44
44
  }
45
- type AddOptionsWithId<I> = Omit<AddOptions<I>, 'id'> & {
46
- id: I;
45
+ type AddOptionsWithId<I, T, E extends ResultValidErrors> = Omit<AddOptions<I, T, E>, 'meta'> & {
46
+ meta: I;
47
47
  };
48
- declare class AsyncQueueWithId<T, I, E extends ResultValidErrors = Error> extends AsyncQueue<T, E, I> {
48
+ declare class AsyncQueueWithMeta<T, I, E extends ResultValidErrors = Error> extends AsyncQueue<T, E, I> {
49
49
  constructor(options?: AsyncQueueOptions);
50
- add(fn: ((ctx: RunCtx<I>) => Promise<Result<T, E>> | Result<T, E>) | Promise<Result<T, E>>, options?: AddOptionsWithId<I>): Promise<Result<T, E | Error>>;
51
- resultifyAdd(fn: ((ctx: RunCtx<I>) => Promise<T> | T) | Promise<T>, options?: AddOptionsWithId<I>): Promise<Result<T, E | Error>>;
50
+ add(fn: (ctx: RunCtx<I>) => Promise<Result<T, E>> | Result<T, E>, options?: AddOptionsWithId<I, T, E>): Promise<Result<T, E | Error>>;
51
+ resultifyAdd(fn: (ctx: RunCtx<I>) => Promise<T> | T, options?: AddOptionsWithId<I, T, E>): Promise<Result<T, E | Error>>;
52
52
  }
53
53
  declare function createAsyncQueue<T, E extends ResultValidErrors = Error>(options?: AsyncQueueOptions): AsyncQueue<T, E>;
54
- declare function createAsyncQueueWithId<T, I, E extends ResultValidErrors = Error>(options?: AsyncQueueOptions): AsyncQueueWithId<T, I, E>;
54
+ declare function createAsyncQueueWithMeta<T, I, E extends ResultValidErrors = Error>(options?: AsyncQueueOptions): AsyncQueueWithMeta<T, I, E>;
55
55
 
56
- export { createAsyncQueue, createAsyncQueueWithId };
56
+ export { createAsyncQueue, createAsyncQueueWithMeta };
@@ -6,51 +6,51 @@ type AsyncQueueOptions = {
6
6
  signal?: AbortSignal;
7
7
  timeout?: number;
8
8
  };
9
- type AddOptions<I> = {
9
+ type AddOptions<I, T, E extends ResultValidErrors> = {
10
10
  signal?: AbortSignal;
11
11
  timeout?: number;
12
- id?: I;
12
+ meta?: I;
13
+ onComplete?: (value: T) => void;
14
+ onError?: (error: E | Error) => void;
13
15
  };
14
16
  type RunCtx<I> = {
15
17
  signal?: AbortSignal;
16
- id?: I;
18
+ meta?: I;
17
19
  };
18
- declare class AsyncQueue<T, E extends ResultValidErrors = Error, I = undefined> {
20
+ declare class AsyncQueue<T, E extends ResultValidErrors = Error, I = unknown> {
19
21
  #private;
20
22
  events: evtmitter.Emitter<{
21
23
  error: {
22
- id: I;
24
+ meta: I;
23
25
  error: E | Error;
24
26
  };
25
27
  complete: {
26
- id: I;
28
+ meta: I;
27
29
  value: T;
28
30
  };
29
31
  start: {
30
- id: I;
32
+ meta: I;
31
33
  };
32
34
  }>;
33
35
  constructor({ concurrency, signal, timeout: taskTimeout, }?: AsyncQueueOptions);
34
- add(fn: ((ctx: RunCtx<I>) => Promise<Result<T, E>> | Result<T, E>) | Promise<Result<T, E>>, options?: AddOptions<I>): Promise<Result<T, E | Error>>;
35
- resultifyAdd(fn: ((ctx: RunCtx<I>) => Promise<T> | T) | Promise<T>, options?: AddOptions<I>): Promise<Result<T, E | Error>>;
36
+ add(fn: (ctx: RunCtx<I>) => Promise<Result<T, E>> | Result<T, E>, options?: AddOptions<I, T, E>): Promise<Result<T, E | Error>>;
37
+ resultifyAdd(fn: (ctx: RunCtx<I>) => Promise<T> | T, options?: AddOptions<I, T, E>): Promise<Result<T, E | Error>>;
36
38
  onIdle(): Promise<void>;
37
- clear({ resetCounters }?: {
38
- resetCounters?: boolean;
39
- }): void;
39
+ clear(): void;
40
40
  get completed(): number;
41
41
  get failed(): number;
42
42
  get pending(): number;
43
43
  get size(): number;
44
44
  }
45
- type AddOptionsWithId<I> = Omit<AddOptions<I>, 'id'> & {
46
- id: I;
45
+ type AddOptionsWithId<I, T, E extends ResultValidErrors> = Omit<AddOptions<I, T, E>, 'meta'> & {
46
+ meta: I;
47
47
  };
48
- declare class AsyncQueueWithId<T, I, E extends ResultValidErrors = Error> extends AsyncQueue<T, E, I> {
48
+ declare class AsyncQueueWithMeta<T, I, E extends ResultValidErrors = Error> extends AsyncQueue<T, E, I> {
49
49
  constructor(options?: AsyncQueueOptions);
50
- add(fn: ((ctx: RunCtx<I>) => Promise<Result<T, E>> | Result<T, E>) | Promise<Result<T, E>>, options?: AddOptionsWithId<I>): Promise<Result<T, E | Error>>;
51
- resultifyAdd(fn: ((ctx: RunCtx<I>) => Promise<T> | T) | Promise<T>, options?: AddOptionsWithId<I>): Promise<Result<T, E | Error>>;
50
+ add(fn: (ctx: RunCtx<I>) => Promise<Result<T, E>> | Result<T, E>, options?: AddOptionsWithId<I, T, E>): Promise<Result<T, E | Error>>;
51
+ resultifyAdd(fn: (ctx: RunCtx<I>) => Promise<T> | T, options?: AddOptionsWithId<I, T, E>): Promise<Result<T, E | Error>>;
52
52
  }
53
53
  declare function createAsyncQueue<T, E extends ResultValidErrors = Error>(options?: AsyncQueueOptions): AsyncQueue<T, E>;
54
- declare function createAsyncQueueWithId<T, I, E extends ResultValidErrors = Error>(options?: AsyncQueueOptions): AsyncQueueWithId<T, I, E>;
54
+ declare function createAsyncQueueWithMeta<T, I, E extends ResultValidErrors = Error>(options?: AsyncQueueOptions): AsyncQueueWithMeta<T, I, E>;
55
55
 
56
- export { createAsyncQueue, createAsyncQueueWithId };
56
+ export { createAsyncQueue, createAsyncQueueWithMeta };
package/lib/asyncQueue.js CHANGED
@@ -1,9 +1,6 @@
1
1
  import {
2
2
  defer
3
3
  } from "./chunk-DFXNVEH6.js";
4
- import {
5
- isPromise
6
- } from "./chunk-3XCS7FVO.js";
7
4
 
8
5
  // src/asyncQueue.ts
9
6
  import { evtmitter } from "evtmitter";
@@ -37,38 +34,48 @@ var AsyncQueue = class {
37
34
  this.#queue.push(task);
38
35
  this.#size++;
39
36
  }
40
- add(fn, options) {
37
+ async add(fn, options) {
38
+ if (this.#signal?.aborted) {
39
+ return Result.err(
40
+ this.#signal.reason instanceof Error ? this.#signal.reason : new DOMException("Queue aborted", "AbortError")
41
+ );
42
+ }
41
43
  const deferred = defer();
42
44
  const taskTimeout = this.#taskTimeout ?? options?.timeout;
43
45
  const task = {
44
46
  run: async (ctx) => {
45
- if (isPromise(fn)) {
46
- return fn;
47
- }
48
- return await fn(ctx);
47
+ return fn(ctx);
49
48
  },
50
49
  resolve: deferred.resolve,
51
50
  reject: deferred.reject,
52
51
  signal: options?.signal,
53
- id: options?.id,
52
+ meta: options?.meta,
54
53
  timeout: taskTimeout
55
54
  };
56
55
  this.#enqueue(task);
57
56
  this.#processQueue();
58
- return deferred.promise;
57
+ const r = await deferred.promise;
58
+ if (options?.onComplete) {
59
+ r.onOk(options.onComplete);
60
+ }
61
+ if (options?.onError) {
62
+ r.onErr(options.onError);
63
+ }
64
+ return r;
59
65
  }
60
66
  resultifyAdd(fn, options) {
61
67
  return this.add(
62
68
  (ctx) => resultify(async () => {
63
- if (isPromise(fn)) {
64
- return await fn;
65
- }
66
69
  return fn(ctx);
67
70
  }),
68
71
  options
69
72
  );
70
73
  }
71
74
  async #processQueue() {
75
+ if (this.#signal?.aborted) {
76
+ this.clear();
77
+ return;
78
+ }
72
79
  if (this.#pending >= this.#concurrency || this.#queue.length === 0) {
73
80
  return;
74
81
  }
@@ -92,43 +99,52 @@ var AsyncQueue = class {
92
99
  let abortListener;
93
100
  try {
94
101
  if (signal?.aborted) {
95
- const error = signal.reason instanceof Error ? signal.reason : new DOMException("Aborted", "AbortError");
102
+ const error = signal.reason instanceof Error ? signal.reason : new DOMException("This operation was aborted", "AbortError");
96
103
  throw error;
97
104
  }
98
105
  const signalAbortPromise = new Promise((_, reject) => {
99
106
  if (signal) {
100
- const error = signal.reason instanceof Error ? signal.reason : new DOMException("Aborted", "AbortError");
107
+ const error = signal.reason instanceof Error ? signal.reason : new DOMException("This operation was aborted", "AbortError");
101
108
  abortListener = () => {
102
109
  reject(error);
103
110
  };
104
111
  signal.addEventListener("abort", abortListener, { once: true });
105
112
  }
106
113
  });
107
- const taskRunPromise = task.run({ signal, id: task.id });
108
- this.events.emit("start", { id: task.id });
114
+ const taskRunPromise = task.run({ signal, meta: task.meta });
115
+ this.events.emit("start", { meta: task.meta });
109
116
  const result = await Promise.race([taskRunPromise, signalAbortPromise]);
110
117
  if (isResult(result)) {
111
118
  task.resolve(result);
112
119
  if (result.error) {
113
120
  this.#failed++;
114
- this.events.emit("error", { id: task.id, error: result.error });
121
+ this.events.emit("error", {
122
+ meta: task.meta,
123
+ error: result.error
124
+ });
115
125
  } else {
116
126
  this.#completed++;
117
- this.events.emit("complete", { id: task.id, value: result.value });
127
+ this.events.emit("complete", {
128
+ meta: task.meta,
129
+ value: result.value
130
+ });
118
131
  }
119
132
  } else {
120
133
  const error = new Error("Response not a Result");
121
134
  task.resolve(Result.err(error));
122
135
  this.#failed++;
123
136
  this.events.emit("error", {
124
- id: task.id,
137
+ meta: task.meta,
125
138
  error
126
139
  });
127
140
  }
128
141
  } catch (error) {
129
142
  task.resolve(Result.err(error));
130
143
  this.#failed++;
131
- this.events.emit("error", { id: task.id, error: unknownToError(error) });
144
+ this.events.emit("error", {
145
+ meta: task.meta,
146
+ error: unknownToError(error)
147
+ });
132
148
  } finally {
133
149
  if (signal && abortListener) {
134
150
  signal.removeEventListener("abort", abortListener);
@@ -156,13 +172,9 @@ var AsyncQueue = class {
156
172
  this.#idleResolvers.push(resolve);
157
173
  });
158
174
  }
159
- clear({ resetCounters = true } = {}) {
175
+ clear() {
160
176
  this.#queue = [];
161
177
  this.#size = 0;
162
- if (resetCounters) {
163
- this.#completed = 0;
164
- this.#failed = 0;
165
- }
166
178
  if (this.#pending === 0) {
167
179
  this.#resolveIdleWaiters();
168
180
  }
@@ -180,7 +192,7 @@ var AsyncQueue = class {
180
192
  return this.#size;
181
193
  }
182
194
  };
183
- var AsyncQueueWithId = class extends AsyncQueue {
195
+ var AsyncQueueWithMeta = class extends AsyncQueue {
184
196
  constructor(options) {
185
197
  super(options);
186
198
  }
@@ -194,10 +206,10 @@ var AsyncQueueWithId = class extends AsyncQueue {
194
206
  function createAsyncQueue(options) {
195
207
  return new AsyncQueue(options);
196
208
  }
197
- function createAsyncQueueWithId(options) {
198
- return new AsyncQueueWithId(options);
209
+ function createAsyncQueueWithMeta(options) {
210
+ return new AsyncQueueWithMeta(options);
199
211
  }
200
212
  export {
201
213
  createAsyncQueue,
202
- createAsyncQueueWithId
214
+ createAsyncQueueWithMeta
203
215
  };
package/lib/testUtils.cjs CHANGED
@@ -21,7 +21,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var testUtils_exports = {};
22
22
  __export(testUtils_exports, {
23
23
  createLoggerStore: () => createLoggerStore,
24
- getResultFn: () => getResultFn
24
+ getResultFn: () => getResultFn,
25
+ waitController: () => waitController
25
26
  });
26
27
  module.exports = __toCommonJS(testUtils_exports);
27
28
 
@@ -141,6 +142,17 @@ function omit(obj, keys) {
141
142
  return result;
142
143
  }
143
144
 
145
+ // src/promiseUtils.ts
146
+ function defer() {
147
+ let resolve;
148
+ let reject;
149
+ const promise = new Promise((_resolve, _reject) => {
150
+ resolve = _resolve;
151
+ reject = _reject;
152
+ });
153
+ return { resolve, reject, promise };
154
+ }
155
+
144
156
  // src/testUtils.ts
145
157
  function createLoggerStore({
146
158
  filterKeys: defaultFilterKeys,
@@ -353,8 +365,23 @@ function getResultFn(fnGetter, wrapper) {
353
365
  }
354
366
  };
355
367
  }
368
+ function waitController() {
369
+ const { promise, resolve } = defer();
370
+ return {
371
+ wait: promise,
372
+ stopWaiting: () => {
373
+ resolve();
374
+ },
375
+ stopWaitingAfter: (ms) => {
376
+ setTimeout(() => {
377
+ resolve();
378
+ }, ms);
379
+ }
380
+ };
381
+ }
356
382
  // Annotate the CommonJS export names for ESM import in node:
357
383
  0 && (module.exports = {
358
384
  createLoggerStore,
359
- getResultFn
385
+ getResultFn,
386
+ waitController
360
387
  });
@@ -34,5 +34,10 @@ declare function createLoggerStore({ filterKeys: defaultFilterKeys, rejectKeys:
34
34
  addMark: (label: string) => void;
35
35
  };
36
36
  declare function getResultFn<T extends (...args: any[]) => any>(fnGetter: () => T, wrapper?: (...args: any[]) => any): T;
37
+ declare function waitController(): {
38
+ wait: Promise<void>;
39
+ stopWaiting: () => void;
40
+ stopWaitingAfter: (ms: number) => void;
41
+ };
37
42
 
38
- export { createLoggerStore, getResultFn };
43
+ export { createLoggerStore, getResultFn, waitController };
@@ -34,5 +34,10 @@ declare function createLoggerStore({ filterKeys: defaultFilterKeys, rejectKeys:
34
34
  addMark: (label: string) => void;
35
35
  };
36
36
  declare function getResultFn<T extends (...args: any[]) => any>(fnGetter: () => T, wrapper?: (...args: any[]) => any): T;
37
+ declare function waitController(): {
38
+ wait: Promise<void>;
39
+ stopWaiting: () => void;
40
+ stopWaitingAfter: (ms: number) => void;
41
+ };
37
42
 
38
- export { createLoggerStore, getResultFn };
43
+ export { createLoggerStore, getResultFn, waitController };
package/lib/testUtils.js CHANGED
@@ -5,6 +5,9 @@ import {
5
5
  import {
6
6
  deepEqual
7
7
  } from "./chunk-JQFUKJU5.js";
8
+ import {
9
+ defer
10
+ } from "./chunk-DFXNVEH6.js";
8
11
  import {
9
12
  clampMin
10
13
  } from "./chunk-HTCYUMDR.js";
@@ -228,7 +231,22 @@ function getResultFn(fnGetter, wrapper) {
228
231
  }
229
232
  };
230
233
  }
234
+ function waitController() {
235
+ const { promise, resolve } = defer();
236
+ return {
237
+ wait: promise,
238
+ stopWaiting: () => {
239
+ resolve();
240
+ },
241
+ stopWaitingAfter: (ms) => {
242
+ setTimeout(() => {
243
+ resolve();
244
+ }, ms);
245
+ }
246
+ };
247
+ }
231
248
  export {
232
249
  createLoggerStore,
233
- getResultFn
250
+ getResultFn,
251
+ waitController
234
252
  };
@@ -0,0 +1,128 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/xmlSerializer.ts
21
+ var xmlSerializer_exports = {};
22
+ __export(xmlSerializer_exports, {
23
+ serializeXML: () => serializeXML
24
+ });
25
+ module.exports = __toCommonJS(xmlSerializer_exports);
26
+
27
+ // src/arrayUtils.ts
28
+ function filterAndMap(array, mapFilter) {
29
+ const result = [];
30
+ let i = -1;
31
+ for (const item of array) {
32
+ i += 1;
33
+ const filterResult = mapFilter(item, i);
34
+ if (filterResult !== false) {
35
+ result.push(filterResult);
36
+ }
37
+ }
38
+ return result;
39
+ }
40
+
41
+ // src/xmlSerializer.ts
42
+ var XML_TAG_NAME_REGEX = /^[a-zA-Z_][a-zA-Z0-9._-]*$/;
43
+ var XML_PREFIX_REGEX = /^xml/i;
44
+ var XML_ESCAPE_MAP = {
45
+ "&": "&amp;",
46
+ "<": "&lt;",
47
+ ">": "&gt;",
48
+ '"': "&quot;",
49
+ "'": "&#39;"
50
+ };
51
+ var XML_ESCAPE_REGEX = /[&<>"']/g;
52
+ function serializeXML(node, options) {
53
+ return serializeWithLevel(node, options, 0);
54
+ }
55
+ function serializeWithLevel(node, options = {}, level) {
56
+ const { name, attributes = {}, children, escapeText: nodeEscapeText } = node;
57
+ const {
58
+ indent,
59
+ escapeText: globalEscapeText = true,
60
+ validateTagName = "throw"
61
+ } = options;
62
+ const shouldEscapeText = nodeEscapeText !== void 0 ? nodeEscapeText : globalEscapeText;
63
+ const indentStr = indent ? getIndentString(indent, level) : "";
64
+ const newline = indent ? "\n" : "";
65
+ if (validateTagName) {
66
+ if (!isValidXmlTagName(name)) {
67
+ if (validateTagName === "throw") {
68
+ throw new Error(`Invalid XML tag name: "${name}"`);
69
+ }
70
+ return "";
71
+ }
72
+ }
73
+ const attributesStr = filterAndMap(
74
+ Object.entries(attributes),
75
+ ([key, value]) => value !== null && value !== void 0 ? `${key}="${escapeXml(String(value))}"` : false
76
+ ).join(" ");
77
+ const attributesPart = attributesStr ? ` ${attributesStr}` : "";
78
+ if (children === void 0) {
79
+ return `${indentStr}<${name}${attributesPart} />`;
80
+ }
81
+ if (typeof children === "string") {
82
+ const processedText = shouldEscapeText ? escapeXml(children) : children;
83
+ if (processedText.includes("\n") && indent) {
84
+ const lines = processedText.split("\n");
85
+ const contentIndent = getIndentString(indent, level + 1);
86
+ const indentedLines = lines.map((line, index) => {
87
+ if (line.trim() === "") return "";
88
+ return index === 0 ? line : contentIndent + line;
89
+ });
90
+ const indentedText = indentedLines.join("\n");
91
+ return `${indentStr}<${name}${attributesPart}>
92
+ ${contentIndent}${indentedText}
93
+ ${indentStr}</${name}>`;
94
+ }
95
+ return `${indentStr}<${name}${attributesPart}>${processedText}</${name}>`;
96
+ }
97
+ if (children.length === 0) {
98
+ return `${indentStr}<${name}${attributesPart}></${name}>`;
99
+ }
100
+ const serializedChildren = filterAndMap(children, (child) => {
101
+ if (!child) return false;
102
+ const serializedChild = serializeWithLevel(child, options, level + 1);
103
+ return serializedChild || false;
104
+ });
105
+ if (serializedChildren.length === 0) {
106
+ return `${indentStr}<${name}${attributesPart}></${name}>`;
107
+ }
108
+ const childrenStr = serializedChildren.join(newline);
109
+ return `${indentStr}<${name}${attributesPart}>${newline}${childrenStr}${newline}${indentStr}</${name}>`;
110
+ }
111
+ function getIndentString(indent, level) {
112
+ if (typeof indent === "string") {
113
+ return indent.repeat(level);
114
+ }
115
+ return " ".repeat(indent * level);
116
+ }
117
+ function isValidXmlTagName(name) {
118
+ if (!name) return false;
119
+ if (XML_PREFIX_REGEX.test(name)) return false;
120
+ return XML_TAG_NAME_REGEX.test(name);
121
+ }
122
+ function escapeXml(text) {
123
+ return text.replace(XML_ESCAPE_REGEX, (char) => XML_ESCAPE_MAP[char]);
124
+ }
125
+ // Annotate the CommonJS export names for ESM import in node:
126
+ 0 && (module.exports = {
127
+ serializeXML
128
+ });
@@ -0,0 +1,14 @@
1
+ type XMLNode = {
2
+ name: string;
3
+ attributes?: Record<string, string | number | boolean | null | undefined>;
4
+ children?: (XMLNode | null | undefined | false)[] | string;
5
+ escapeText?: boolean;
6
+ };
7
+ type SerializeOptions = {
8
+ indent?: number | string;
9
+ escapeText?: boolean;
10
+ validateTagName?: 'throw' | 'reject' | false;
11
+ };
12
+ declare function serializeXML(node: XMLNode, options?: SerializeOptions): string;
13
+
14
+ export { type SerializeOptions, type XMLNode, serializeXML };
@@ -0,0 +1,14 @@
1
+ type XMLNode = {
2
+ name: string;
3
+ attributes?: Record<string, string | number | boolean | null | undefined>;
4
+ children?: (XMLNode | null | undefined | false)[] | string;
5
+ escapeText?: boolean;
6
+ };
7
+ type SerializeOptions = {
8
+ indent?: number | string;
9
+ escapeText?: boolean;
10
+ validateTagName?: 'throw' | 'reject' | false;
11
+ };
12
+ declare function serializeXML(node: XMLNode, options?: SerializeOptions): string;
13
+
14
+ export { type SerializeOptions, type XMLNode, serializeXML };
@@ -0,0 +1,92 @@
1
+ import {
2
+ filterAndMap
3
+ } from "./chunk-UTFE4P3P.js";
4
+ import "./chunk-3XCS7FVO.js";
5
+
6
+ // src/xmlSerializer.ts
7
+ var XML_TAG_NAME_REGEX = /^[a-zA-Z_][a-zA-Z0-9._-]*$/;
8
+ var XML_PREFIX_REGEX = /^xml/i;
9
+ var XML_ESCAPE_MAP = {
10
+ "&": "&amp;",
11
+ "<": "&lt;",
12
+ ">": "&gt;",
13
+ '"': "&quot;",
14
+ "'": "&#39;"
15
+ };
16
+ var XML_ESCAPE_REGEX = /[&<>"']/g;
17
+ function serializeXML(node, options) {
18
+ return serializeWithLevel(node, options, 0);
19
+ }
20
+ function serializeWithLevel(node, options = {}, level) {
21
+ const { name, attributes = {}, children, escapeText: nodeEscapeText } = node;
22
+ const {
23
+ indent,
24
+ escapeText: globalEscapeText = true,
25
+ validateTagName = "throw"
26
+ } = options;
27
+ const shouldEscapeText = nodeEscapeText !== void 0 ? nodeEscapeText : globalEscapeText;
28
+ const indentStr = indent ? getIndentString(indent, level) : "";
29
+ const newline = indent ? "\n" : "";
30
+ if (validateTagName) {
31
+ if (!isValidXmlTagName(name)) {
32
+ if (validateTagName === "throw") {
33
+ throw new Error(`Invalid XML tag name: "${name}"`);
34
+ }
35
+ return "";
36
+ }
37
+ }
38
+ const attributesStr = filterAndMap(
39
+ Object.entries(attributes),
40
+ ([key, value]) => value !== null && value !== void 0 ? `${key}="${escapeXml(String(value))}"` : false
41
+ ).join(" ");
42
+ const attributesPart = attributesStr ? ` ${attributesStr}` : "";
43
+ if (children === void 0) {
44
+ return `${indentStr}<${name}${attributesPart} />`;
45
+ }
46
+ if (typeof children === "string") {
47
+ const processedText = shouldEscapeText ? escapeXml(children) : children;
48
+ if (processedText.includes("\n") && indent) {
49
+ const lines = processedText.split("\n");
50
+ const contentIndent = getIndentString(indent, level + 1);
51
+ const indentedLines = lines.map((line, index) => {
52
+ if (line.trim() === "") return "";
53
+ return index === 0 ? line : contentIndent + line;
54
+ });
55
+ const indentedText = indentedLines.join("\n");
56
+ return `${indentStr}<${name}${attributesPart}>
57
+ ${contentIndent}${indentedText}
58
+ ${indentStr}</${name}>`;
59
+ }
60
+ return `${indentStr}<${name}${attributesPart}>${processedText}</${name}>`;
61
+ }
62
+ if (children.length === 0) {
63
+ return `${indentStr}<${name}${attributesPart}></${name}>`;
64
+ }
65
+ const serializedChildren = filterAndMap(children, (child) => {
66
+ if (!child) return false;
67
+ const serializedChild = serializeWithLevel(child, options, level + 1);
68
+ return serializedChild || false;
69
+ });
70
+ if (serializedChildren.length === 0) {
71
+ return `${indentStr}<${name}${attributesPart}></${name}>`;
72
+ }
73
+ const childrenStr = serializedChildren.join(newline);
74
+ return `${indentStr}<${name}${attributesPart}>${newline}${childrenStr}${newline}${indentStr}</${name}>`;
75
+ }
76
+ function getIndentString(indent, level) {
77
+ if (typeof indent === "string") {
78
+ return indent.repeat(level);
79
+ }
80
+ return " ".repeat(indent * level);
81
+ }
82
+ function isValidXmlTagName(name) {
83
+ if (!name) return false;
84
+ if (XML_PREFIX_REGEX.test(name)) return false;
85
+ return XML_TAG_NAME_REGEX.test(name);
86
+ }
87
+ function escapeXml(text) {
88
+ return text.replace(XML_ESCAPE_REGEX, (char) => XML_ESCAPE_MAP[char]);
89
+ }
90
+ export {
91
+ serializeXML
92
+ };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ls-stack/utils",
3
3
  "description": "Typescript utils",
4
- "version": "3.9.1",
4
+ "version": "3.11.0",
5
5
  "license": "MIT",
6
6
  "files": [
7
7
  "lib"
@@ -154,6 +154,10 @@
154
154
  "import": "./lib/typingUtils.js",
155
155
  "require": "./lib/typingUtils.cjs"
156
156
  },
157
+ "./xmlSerializer": {
158
+ "import": "./lib/xmlSerializer.js",
159
+ "require": "./lib/xmlSerializer.cjs"
160
+ },
157
161
  "./yamlStringify": {
158
162
  "import": "./lib/yamlStringify.js",
159
163
  "require": "./lib/yamlStringify.cjs"
@@ -198,7 +202,7 @@
198
202
  "tsc": "tsc -p tsconfig.prod.json",
199
203
  "tsc:watch": "tsc -p tsconfig.prod.json --watch",
200
204
  "eslint": "CI=true eslint src/ scripts/ --color --max-warnings=0",
201
- "build": "pnpm test && pnpm lint && pnpm build:no-test && pnpm docs && pnpm build:update-exports",
205
+ "build": "pnpm test && pnpm lint && pnpm build:no-test && pnpm run docs && pnpm run docs:commit && pnpm build:update-exports",
202
206
  "build:no-test": "tsup",
203
207
  "build:update-exports": "tsm --no-warnings scripts/updatePackageExports.ts",
204
208
  "build-test": "tsup --config tsup.test.config.ts",
@@ -206,6 +210,7 @@
206
210
  "test:console-fmt": "tsm --no-warnings scripts/testConsoleFmt.ts",
207
211
  "bench:deepEqual": "tsm --no-warnings benchmarks/deepEqual.ts",
208
212
  "docs": "typedoc",
209
- "docs:watch": "typedoc --watch"
213
+ "docs:watch": "typedoc --watch",
214
+ "docs:commit": "git add docs && git diff --cached --quiet docs || git commit -m 'docs: update docs'"
210
215
  }
211
216
  }