@orkestrel/test 0.0.7 → 0.0.8

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.
@@ -74,6 +74,25 @@ export declare function createRecorder<TArgs extends readonly unknown[]>(): Reco
74
74
  */
75
75
  export declare function createTeardown(): TeardownInterface;
76
76
 
77
+ /**
78
+ * Decodes newline-delimited JSON values.
79
+ *
80
+ * @param text - The JSON Lines text to decode.
81
+ * @returns The decoded values in physical-line order.
82
+ * @throws An `Error` naming the malformed physical line, with the native `SyntaxError` as its
83
+ * `cause`.
84
+ */
85
+ export declare function decodeJSONLines(text: string): readonly unknown[];
86
+
87
+ /**
88
+ * Subscribes a listener to one event source.
89
+ *
90
+ * @typeParam TArgs - The argument tuple the event delivers.
91
+ * @param listener - The listener that receives each delivery.
92
+ * @returns The cleanup that removes the listener, or `void` when the source needs none.
93
+ */
94
+ export declare type EventSubscriber<TArgs extends readonly unknown[]> = (listener: (...args: TArgs) => void) => (() => void) | void;
95
+
77
96
  /**
78
97
  * The JSON-safe projection of a type: every member JSON preserves, mapped to itself, and every
79
98
  * member it does not, mapped to `never`.
@@ -147,6 +166,29 @@ export declare function requireValue<T>(value: T | null | undefined, message?: s
147
166
  */
148
167
  export declare function resolveRoot(meta: ImportMeta): URL;
149
168
 
169
+ /** Configures a bounded retry. */
170
+ export declare interface RetryOptions extends WaitOptions {
171
+ /** The maximum number of producer calls. When omitted, only the time budget bounds the retry. */
172
+ readonly attempts?: number;
173
+ }
174
+
175
+ /**
176
+ * Repeats a producer until one produced value satisfies a predicate.
177
+ *
178
+ * @typeParam T - The produced value type.
179
+ * @param description - The operation described in an exhaustion error.
180
+ * @param produce - The synchronous or asynchronous operation to repeat.
181
+ * @param satisfied - The predicate that accepts a produced value.
182
+ * @param options - The time, attempt, and abort bounds.
183
+ * @returns The first produced value the predicate accepts.
184
+ * @throws The predicate's thrown value, the abort reason, or an `Error` when a bound is invalid or
185
+ * the retry exhausts its budget or attempts.
186
+ * @remarks A producer throw counts as an unsatisfied attempt. The last producer error becomes the
187
+ * exhaustion error's `cause`. Default budget: `1000` milliseconds. Default interval: `10`
188
+ * milliseconds.
189
+ */
190
+ export declare function retryUntil<T>(description: string, produce: () => T | Promise<T>, satisfied: (value: T) => boolean, options?: RetryOptions): Promise<T>;
191
+
150
192
  /**
151
193
  * Copies a JSON value through serialization and parsing.
152
194
  *
@@ -185,6 +227,20 @@ export declare interface TeardownInterface {
185
227
  destroy(): Promise<void>;
186
228
  }
187
229
 
230
+ /**
231
+ * Waits until a condition holds within an elapsed-time budget.
232
+ *
233
+ * @param description - The condition described in a timeout error.
234
+ * @param condition - The synchronous or asynchronous condition to read.
235
+ * @param options - The time bounds and abort signal.
236
+ * @returns A promise that resolves when the condition first returns `true`.
237
+ * @throws The condition's thrown value, the abort reason, or an `Error` when a bound is invalid or
238
+ * the condition does not hold within the budget.
239
+ * @remarks The first read is immediate. Default budget: `1000` milliseconds. Default interval: `10`
240
+ * milliseconds.
241
+ */
242
+ export declare function waitForCondition(description: string, condition: () => boolean | Promise<boolean>, options?: WaitOptions): Promise<void>;
243
+
188
244
  /**
189
245
  * Waits for a host timer to elapse.
190
246
  *
@@ -193,4 +249,35 @@ export declare interface TeardownInterface {
193
249
  */
194
250
  export declare function waitForDelay(ms?: number): Promise<void>;
195
251
 
252
+ /**
253
+ * Waits for the first delivery from an event subscription.
254
+ *
255
+ * @typeParam TArgs - The delivered argument tuple.
256
+ * @param subscribe - The function that installs the event listener and may return its cleanup.
257
+ * @param description - The event described in a timeout error.
258
+ * @param options - The time bounds and abort signal.
259
+ * @returns The first delivered argument tuple.
260
+ * @throws The subscription's thrown value, the abort reason, or an `Error` when a bound is invalid
261
+ * or the event is not delivered within the budget.
262
+ * @remarks Default budget: `1000` milliseconds. The interval is validated for consistency with the
263
+ * wait family but is not used because this helper parks on the event.
264
+ */
265
+ export declare function waitForEvent<TArgs extends readonly unknown[]>(subscribe: EventSubscriber<TArgs>, description: string, options?: WaitOptions): Promise<TArgs>;
266
+
267
+ /**
268
+ * Configures a bounded asynchronous wait.
269
+ *
270
+ * @remarks
271
+ * A default belongs to the function that reads these bounds rather than to the shape, because the
272
+ * consumers do not agree on one. Each states its own numbers in its `@remarks`.
273
+ */
274
+ export declare interface WaitOptions {
275
+ /** The elapsed-time limit in milliseconds. */
276
+ readonly budget?: number;
277
+ /** The delay between readings in milliseconds. */
278
+ readonly interval?: number;
279
+ /** The signal that aborts the wait. */
280
+ readonly signal?: AbortSignal;
281
+ }
282
+
196
283
  export { }
@@ -74,6 +74,25 @@ export declare function createRecorder<TArgs extends readonly unknown[]>(): Reco
74
74
  */
75
75
  export declare function createTeardown(): TeardownInterface;
76
76
 
77
+ /**
78
+ * Decodes newline-delimited JSON values.
79
+ *
80
+ * @param text - The JSON Lines text to decode.
81
+ * @returns The decoded values in physical-line order.
82
+ * @throws An `Error` naming the malformed physical line, with the native `SyntaxError` as its
83
+ * `cause`.
84
+ */
85
+ export declare function decodeJSONLines(text: string): readonly unknown[];
86
+
87
+ /**
88
+ * Subscribes a listener to one event source.
89
+ *
90
+ * @typeParam TArgs - The argument tuple the event delivers.
91
+ * @param listener - The listener that receives each delivery.
92
+ * @returns The cleanup that removes the listener, or `void` when the source needs none.
93
+ */
94
+ export declare type EventSubscriber<TArgs extends readonly unknown[]> = (listener: (...args: TArgs) => void) => (() => void) | void;
95
+
77
96
  /**
78
97
  * The JSON-safe projection of a type: every member JSON preserves, mapped to itself, and every
79
98
  * member it does not, mapped to `never`.
@@ -147,6 +166,29 @@ export declare function requireValue<T>(value: T | null | undefined, message?: s
147
166
  */
148
167
  export declare function resolveRoot(meta: ImportMeta): URL;
149
168
 
169
+ /** Configures a bounded retry. */
170
+ export declare interface RetryOptions extends WaitOptions {
171
+ /** The maximum number of producer calls. When omitted, only the time budget bounds the retry. */
172
+ readonly attempts?: number;
173
+ }
174
+
175
+ /**
176
+ * Repeats a producer until one produced value satisfies a predicate.
177
+ *
178
+ * @typeParam T - The produced value type.
179
+ * @param description - The operation described in an exhaustion error.
180
+ * @param produce - The synchronous or asynchronous operation to repeat.
181
+ * @param satisfied - The predicate that accepts a produced value.
182
+ * @param options - The time, attempt, and abort bounds.
183
+ * @returns The first produced value the predicate accepts.
184
+ * @throws The predicate's thrown value, the abort reason, or an `Error` when a bound is invalid or
185
+ * the retry exhausts its budget or attempts.
186
+ * @remarks A producer throw counts as an unsatisfied attempt. The last producer error becomes the
187
+ * exhaustion error's `cause`. Default budget: `1000` milliseconds. Default interval: `10`
188
+ * milliseconds.
189
+ */
190
+ export declare function retryUntil<T>(description: string, produce: () => T | Promise<T>, satisfied: (value: T) => boolean, options?: RetryOptions): Promise<T>;
191
+
150
192
  /**
151
193
  * Copies a JSON value through serialization and parsing.
152
194
  *
@@ -185,6 +227,20 @@ export declare interface TeardownInterface {
185
227
  destroy(): Promise<void>;
186
228
  }
187
229
 
230
+ /**
231
+ * Waits until a condition holds within an elapsed-time budget.
232
+ *
233
+ * @param description - The condition described in a timeout error.
234
+ * @param condition - The synchronous or asynchronous condition to read.
235
+ * @param options - The time bounds and abort signal.
236
+ * @returns A promise that resolves when the condition first returns `true`.
237
+ * @throws The condition's thrown value, the abort reason, or an `Error` when a bound is invalid or
238
+ * the condition does not hold within the budget.
239
+ * @remarks The first read is immediate. Default budget: `1000` milliseconds. Default interval: `10`
240
+ * milliseconds.
241
+ */
242
+ export declare function waitForCondition(description: string, condition: () => boolean | Promise<boolean>, options?: WaitOptions): Promise<void>;
243
+
188
244
  /**
189
245
  * Waits for a host timer to elapse.
190
246
  *
@@ -193,4 +249,35 @@ export declare interface TeardownInterface {
193
249
  */
194
250
  export declare function waitForDelay(ms?: number): Promise<void>;
195
251
 
252
+ /**
253
+ * Waits for the first delivery from an event subscription.
254
+ *
255
+ * @typeParam TArgs - The delivered argument tuple.
256
+ * @param subscribe - The function that installs the event listener and may return its cleanup.
257
+ * @param description - The event described in a timeout error.
258
+ * @param options - The time bounds and abort signal.
259
+ * @returns The first delivered argument tuple.
260
+ * @throws The subscription's thrown value, the abort reason, or an `Error` when a bound is invalid
261
+ * or the event is not delivered within the budget.
262
+ * @remarks Default budget: `1000` milliseconds. The interval is validated for consistency with the
263
+ * wait family but is not used because this helper parks on the event.
264
+ */
265
+ export declare function waitForEvent<TArgs extends readonly unknown[]>(subscribe: EventSubscriber<TArgs>, description: string, options?: WaitOptions): Promise<TArgs>;
266
+
267
+ /**
268
+ * Configures a bounded asynchronous wait.
269
+ *
270
+ * @remarks
271
+ * A default belongs to the function that reads these bounds rather than to the shape, because the
272
+ * consumers do not agree on one. Each states its own numbers in its `@remarks`.
273
+ */
274
+ export declare interface WaitOptions {
275
+ /** The elapsed-time limit in milliseconds. */
276
+ readonly budget?: number;
277
+ /** The delay between readings in milliseconds. */
278
+ readonly interval?: number;
279
+ /** The signal that aborts the wait. */
280
+ readonly signal?: AbortSignal;
281
+ }
282
+
196
283
  export { }
@@ -9,6 +9,158 @@ function waitForDelay(ms = 0) {
9
9
  return new Promise((resolve) => setTimeout(resolve, ms));
10
10
  }
11
11
  /**
12
+ * Waits until a condition holds within an elapsed-time budget.
13
+ *
14
+ * @param description - The condition described in a timeout error.
15
+ * @param condition - The synchronous or asynchronous condition to read.
16
+ * @param options - The time bounds and abort signal.
17
+ * @returns A promise that resolves when the condition first returns `true`.
18
+ * @throws The condition's thrown value, the abort reason, or an `Error` when a bound is invalid or
19
+ * the condition does not hold within the budget.
20
+ * @remarks The first read is immediate. Default budget: `1000` milliseconds. Default interval: `10`
21
+ * milliseconds.
22
+ */
23
+ async function waitForCondition(description, condition, options) {
24
+ const budget = options?.budget ?? 1e3;
25
+ const interval = options?.interval ?? 10;
26
+ if (!Number.isFinite(budget) || budget < 0) throw new Error("Wait budget must be finite and non-negative");
27
+ if (!Number.isFinite(interval) || interval < 0) throw new Error("Wait interval must be finite and non-negative");
28
+ const start = performance.now();
29
+ while (true) {
30
+ options?.signal?.throwIfAborted();
31
+ const held = await condition();
32
+ options?.signal?.throwIfAborted();
33
+ if (held) return;
34
+ const elapsed = performance.now() - start;
35
+ if (elapsed >= budget) throw new Error(`Condition "${description}" did not hold within ${budget}ms (waited ${elapsed}ms)`);
36
+ await waitForDelay(interval);
37
+ }
38
+ }
39
+ /**
40
+ * Repeats a producer until one produced value satisfies a predicate.
41
+ *
42
+ * @typeParam T - The produced value type.
43
+ * @param description - The operation described in an exhaustion error.
44
+ * @param produce - The synchronous or asynchronous operation to repeat.
45
+ * @param satisfied - The predicate that accepts a produced value.
46
+ * @param options - The time, attempt, and abort bounds.
47
+ * @returns The first produced value the predicate accepts.
48
+ * @throws The predicate's thrown value, the abort reason, or an `Error` when a bound is invalid or
49
+ * the retry exhausts its budget or attempts.
50
+ * @remarks A producer throw counts as an unsatisfied attempt. The last producer error becomes the
51
+ * exhaustion error's `cause`. Default budget: `1000` milliseconds. Default interval: `10`
52
+ * milliseconds.
53
+ */
54
+ async function retryUntil(description, produce, satisfied, options) {
55
+ const budget = options?.budget ?? 1e3;
56
+ const interval = options?.interval ?? 10;
57
+ const attempts = options?.attempts;
58
+ if (!Number.isFinite(budget) || budget < 0) throw new Error("Retry budget must be finite and non-negative");
59
+ if (!Number.isFinite(interval) || interval < 0) throw new Error("Retry interval must be finite and non-negative");
60
+ if (attempts !== void 0 && (!Number.isInteger(attempts) || attempts < 1)) throw new Error("Retry attempts must be a positive integer");
61
+ const start = performance.now();
62
+ let count = 0;
63
+ let cause;
64
+ while (true) {
65
+ options?.signal?.throwIfAborted();
66
+ if (count > 0) {
67
+ const elapsed = performance.now() - start;
68
+ if (elapsed >= budget) throw new Error(`Retry "${description}" did not succeed within ${budget}ms (waited ${elapsed}ms)`, { cause });
69
+ }
70
+ let produced;
71
+ try {
72
+ produced = {
73
+ success: true,
74
+ value: await produce()
75
+ };
76
+ } catch (error) {
77
+ produced = {
78
+ success: false,
79
+ error
80
+ };
81
+ }
82
+ count += 1;
83
+ options?.signal?.throwIfAborted();
84
+ if (produced.success) {
85
+ if (satisfied(produced.value)) return produced.value;
86
+ } else cause = produced.error;
87
+ const elapsed = performance.now() - start;
88
+ if (elapsed >= budget) throw new Error(`Retry "${description}" did not succeed within ${budget}ms (waited ${elapsed}ms)`, { cause });
89
+ if (attempts !== void 0 && count >= attempts) throw new Error(`Retry "${description}" did not succeed within ${attempts} attempts`, { cause });
90
+ await waitForDelay(Math.min(interval, budget - elapsed));
91
+ }
92
+ }
93
+ /**
94
+ * Waits for the first delivery from an event subscription.
95
+ *
96
+ * @typeParam TArgs - The delivered argument tuple.
97
+ * @param subscribe - The function that installs the event listener and may return its cleanup.
98
+ * @param description - The event described in a timeout error.
99
+ * @param options - The time bounds and abort signal.
100
+ * @returns The first delivered argument tuple.
101
+ * @throws The subscription's thrown value, the abort reason, or an `Error` when a bound is invalid
102
+ * or the event is not delivered within the budget.
103
+ * @remarks Default budget: `1000` milliseconds. The interval is validated for consistency with the
104
+ * wait family but is not used because this helper parks on the event.
105
+ */
106
+ async function waitForEvent(subscribe, description, options) {
107
+ const budget = options?.budget ?? 1e3;
108
+ const interval = options?.interval ?? 10;
109
+ if (!Number.isFinite(budget) || budget < 0) throw new Error("Event budget must be finite and non-negative");
110
+ if (!Number.isFinite(interval) || interval < 0) throw new Error("Event interval must be finite and non-negative");
111
+ const signal = options?.signal;
112
+ signal?.throwIfAborted();
113
+ const delivery = Promise.withResolvers();
114
+ const controller = new AbortController();
115
+ let timeout;
116
+ const pending = [delivery.promise, new Promise((_resolve, reject) => {
117
+ timeout = setTimeout(() => {
118
+ reject(/* @__PURE__ */ new Error(`Event "${description}" was not delivered within ${budget}ms`));
119
+ }, budget);
120
+ })];
121
+ if (signal !== void 0) pending.push(new Promise((_resolve, reject) => {
122
+ AbortSignal.any([signal, controller.signal]).addEventListener("abort", () => {
123
+ if (signal.aborted) reject(signal.reason);
124
+ }, { once: true });
125
+ }));
126
+ const result = Promise.race(pending);
127
+ let cleanup = void 0;
128
+ try {
129
+ try {
130
+ cleanup = subscribe((...args) => delivery.resolve(args));
131
+ } catch (error) {
132
+ delivery.reject(error);
133
+ }
134
+ return await result;
135
+ } finally {
136
+ controller.abort();
137
+ if (timeout !== void 0) clearTimeout(timeout);
138
+ cleanup?.();
139
+ }
140
+ }
141
+ /**
142
+ * Decodes newline-delimited JSON values.
143
+ *
144
+ * @param text - The JSON Lines text to decode.
145
+ * @returns The decoded values in physical-line order.
146
+ * @throws An `Error` naming the malformed physical line, with the native `SyntaxError` as its
147
+ * `cause`.
148
+ */
149
+ function decodeJSONLines(text) {
150
+ const values = [];
151
+ for (const [index, physical] of text.split("\n").entries()) {
152
+ const line = physical.endsWith("\r") ? physical.slice(0, -1) : physical;
153
+ if (line.length === 0) continue;
154
+ try {
155
+ const value = JSON.parse(line);
156
+ values.push(value);
157
+ } catch (cause) {
158
+ throw new Error(`Invalid JSON on line ${index + 1}`, { cause });
159
+ }
160
+ }
161
+ return values;
162
+ }
163
+ /**
12
164
  * Captures the value thrown by a thunk.
13
165
  *
14
166
  * @param thunk - The work whose thrown value to capture.
@@ -206,6 +358,6 @@ function createTeardown() {
206
358
  };
207
359
  }
208
360
  //#endregion
209
- export { captureError, collect, collectStream, createHostileValues, createRecorder, createTeardown, requireValue, resolveRoot, roundTripJSON, waitForDelay };
361
+ export { captureError, collect, collectStream, createHostileValues, createRecorder, createTeardown, decodeJSONLines, requireValue, resolveRoot, retryUntil, roundTripJSON, waitForCondition, waitForDelay, waitForEvent };
210
362
 
211
363
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/core/helpers.ts","../../../src/core/factories.ts"],"sourcesContent":["import type { JSONSafe } from './types.js'\n\n/**\n * Waits for a host timer to elapse.\n *\n * @param ms - The delay in milliseconds.\n * @returns A promise that resolves after the timer fires.\n */\nexport function waitForDelay(ms = 0): Promise<void> {\n\treturn new Promise((resolve) => setTimeout(resolve, ms))\n}\n\n/**\n * Captures the value thrown by a thunk.\n *\n * @param thunk - The work whose thrown value to capture.\n * @returns The thrown value, or `undefined` when the thunk completes.\n */\nexport function captureError(thunk: () => unknown): unknown {\n\ttry {\n\t\tthunk()\n\t} catch (error) {\n\t\treturn error\n\t}\n\treturn undefined\n}\n\n/**\n * Requires a value to be present.\n *\n * @typeParam T - The required value type.\n * @param value - The value to check.\n * @param message - The error message used when the value is absent.\n * @returns The present value.\n */\nexport function requireValue<T>(value: T | null | undefined, message = 'Value is required'): T {\n\tif (value === null || value === undefined) throw new Error(message)\n\treturn value\n}\n\n/**\n * Collects every value from an async iterable.\n *\n * @typeParam T - The yielded value type.\n * @param source - The async iterable to drain.\n * @returns The yielded values in iteration order.\n */\nexport async function collect<T>(source: AsyncIterable<T>): Promise<readonly T[]> {\n\tconst values: T[] = []\n\tfor await (const value of source) values.push(value)\n\treturn values\n}\n\n/**\n * Collects every value from a readable stream.\n *\n * @typeParam T - The streamed value type.\n * @param stream - The readable stream to drain.\n * @returns The streamed values in read order.\n */\nexport async function collectStream<T>(stream: ReadableStream<T>): Promise<readonly T[]> {\n\tconst reader = stream.getReader()\n\tconst values: T[] = []\n\ttry {\n\t\twhile (true) {\n\t\t\tconst result = await reader.read()\n\t\t\tif (result.done) return values\n\t\t\tvalues.push(result.value)\n\t\t}\n\t} finally {\n\t\treader.releaseLock()\n\t}\n}\n\n/**\n * Copies a JSON value through serialization and parsing.\n *\n * @typeParam T - The copied value's type, which the copy keeps.\n * @param value - The value to copy, bounded by its own `JSONSafe` projection.\n * @returns The parsed JSON copy.\n * @remarks Non-finite numbers throw because JSON would replace them with `null`. Negative zero is\n * normalized to zero by JSON serialization. The bound intersects `JSONSafe<T>` rather than\n * constraining `T` to `JSONValue`, so an interface-typed value round-trips.\n */\nexport function roundTripJSON<T>(value: T & JSONSafe<T>): T {\n\tconst serialized = JSON.stringify(value, (_key, current) => {\n\t\tif (current === undefined || typeof current === 'function' || typeof current === 'symbol') {\n\t\t\tthrow new Error('JSON values must not contain undefined, functions, or symbols')\n\t\t}\n\t\tif (typeof current === 'number' && !Number.isFinite(current)) {\n\t\t\tthrow new Error('JSON values must contain finite numbers')\n\t\t}\n\t\treturn current\n\t})\n\tconst parsed: T = JSON.parse(serialized)\n\tconst pending: unknown[] = [parsed]\n\twhile (pending.length > 0) {\n\t\tconst current = pending.pop()\n\t\tif (typeof current === 'number' && !Number.isFinite(current)) {\n\t\t\tthrow new Error('JSON values must contain finite numbers')\n\t\t}\n\t\tif (Array.isArray(current)) {\n\t\t\tfor (const child of current) pending.push(child)\n\t\t} else if (typeof current === 'object' && current !== null) {\n\t\t\tfor (const child of Object.values(current)) pending.push(child)\n\t\t}\n\t}\n\treturn parsed\n}\n\n/**\n * Resolves the parent directory of a calling module, which is the workspace root when called from\n * the conventional `tests/setup.ts` location.\n *\n * @param meta - The calling module metadata.\n * @returns The root URL one directory above the calling file.\n */\nexport function resolveRoot(meta: ImportMeta): URL {\n\treturn new URL('../', meta.url)\n}\n","import type { RecorderInterface, TeardownHandler, TeardownInterface } from './types.js'\n\n/**\n * Creates values that make common object readers throw or violate their assumptions.\n *\n * @returns A frozen array whose six values are fresh on every call.\n * @remarks Every member makes a naive reader throw. A total guard survives every member without\n * throwing. Whether it accepts or refuses one is that guard's own contract. Membership may grow in\n * a release, so test the whole returned set in a loop and include the index in each failure.\n * @example\n * ```ts\n * import { expect } from 'vitest'\n * import { createHostileValues } from '@orkestrel/test'\n *\n * function isWireRecord(value: unknown): value is Readonly<Record<string, string>> {\n * \tif (typeof value !== 'object' || value === null) return false\n * \ttry {\n * \t\tif (Object.getPrototypeOf(value) !== Object.prototype) return false\n * \t\tReflect.get(value, 'value')\n * \t\tif (Reflect.ownKeys(value).length === 0) return false\n * \t\treturn Object.values(value).every((member) => typeof member === 'string')\n * \t} catch {\n * \t\treturn false\n * \t}\n * }\n *\n * for (const [index, value] of createHostileValues().entries()) {\n * \tlet accepted: boolean | undefined\n * \texpect(() => {\n * \t\taccepted = isWireRecord(value)\n * \t}, `hostile value ${index}`).not.toThrow()\n * \texpect(accepted, `hostile value ${index}`).toBe(false)\n * }\n * ```\n */\nexport function createHostileValues(): readonly unknown[] {\n\tconst cyclic: Record<string, unknown> = {}\n\tcyclic.self = cyclic\n\tconst revoked = Proxy.revocable({}, {})\n\trevoked.revoke()\n\n\treturn Object.freeze([\n\t\tcyclic,\n\t\trevoked.proxy,\n\t\tnew Proxy(\n\t\t\t{},\n\t\t\t{\n\t\t\t\tget() {\n\t\t\t\t\tthrow new Error('Hostile property read')\n\t\t\t\t},\n\t\t\t},\n\t\t),\n\t\tnew Proxy(\n\t\t\t{},\n\t\t\t{\n\t\t\t\townKeys() {\n\t\t\t\t\tthrow new Error('Hostile key enumeration')\n\t\t\t\t},\n\t\t\t},\n\t\t),\n\t\tnew Proxy(\n\t\t\t{},\n\t\t\t{\n\t\t\t\tgetPrototypeOf() {\n\t\t\t\t\tthrow new Error('Hostile prototype read')\n\t\t\t\t},\n\t\t\t},\n\t\t),\n\t\tObject.create(null),\n\t])\n}\n\n/**\n * Creates a recorder for callback arguments.\n *\n * @typeParam TArgs - The argument tuple to record.\n * @returns A recorder whose handler appends calls in order.\n */\nexport function createRecorder<TArgs extends readonly unknown[]>(): RecorderInterface<TArgs> {\n\tconst calls: TArgs[] = []\n\treturn {\n\t\tcalls,\n\t\tget count() {\n\t\t\treturn calls.length\n\t\t},\n\t\thandler(...args) {\n\t\t\tcalls.push(args)\n\t\t},\n\t\tclear() {\n\t\t\tcalls.length = 0\n\t\t},\n\t}\n}\n\n/**\n * Creates a teardown list that runs registered handlers newest-first.\n *\n * @returns A teardown list that awaits every handler and collects failures.\n */\nexport function createTeardown(): TeardownInterface {\n\tlet handlers: TeardownHandler[] = []\n\treturn {\n\t\tget count() {\n\t\t\treturn handlers.length\n\t\t},\n\t\tadd(handler) {\n\t\t\thandlers.push(handler)\n\t\t},\n\t\tasync destroy() {\n\t\t\tconst snapshot = handlers\n\t\t\thandlers = []\n\t\t\tconst failures: unknown[] = []\n\t\t\tfor (const handler of snapshot.reverse()) {\n\t\t\t\ttry {\n\t\t\t\t\tawait handler()\n\t\t\t\t} catch (error) {\n\t\t\t\t\tfailures.push(error)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (failures.length === 1) throw failures[0]\n\t\t\tif (failures.length > 1) throw new AggregateError(failures)\n\t\t},\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/core/helpers.ts","../../../src/core/factories.ts"],"sourcesContent":["import type { EventSubscriber, JSONSafe, RetryOptions, WaitOptions } from './types.js'\n\n/**\n * Waits for a host timer to elapse.\n *\n * @param ms - The delay in milliseconds.\n * @returns A promise that resolves after the timer fires.\n */\nexport function waitForDelay(ms = 0): Promise<void> {\n\treturn new Promise((resolve) => setTimeout(resolve, ms))\n}\n\n/**\n * Waits until a condition holds within an elapsed-time budget.\n *\n * @param description - The condition described in a timeout error.\n * @param condition - The synchronous or asynchronous condition to read.\n * @param options - The time bounds and abort signal.\n * @returns A promise that resolves when the condition first returns `true`.\n * @throws The condition's thrown value, the abort reason, or an `Error` when a bound is invalid or\n * the condition does not hold within the budget.\n * @remarks The first read is immediate. Default budget: `1000` milliseconds. Default interval: `10`\n * milliseconds.\n */\nexport async function waitForCondition(\n\tdescription: string,\n\tcondition: () => boolean | Promise<boolean>,\n\toptions?: WaitOptions,\n): Promise<void> {\n\tconst budget = options?.budget ?? 1000\n\tconst interval = options?.interval ?? 10\n\tif (!Number.isFinite(budget) || budget < 0) {\n\t\tthrow new Error('Wait budget must be finite and non-negative')\n\t}\n\tif (!Number.isFinite(interval) || interval < 0) {\n\t\tthrow new Error('Wait interval must be finite and non-negative')\n\t}\n\n\tconst start = performance.now()\n\twhile (true) {\n\t\toptions?.signal?.throwIfAborted()\n\t\tconst held = await condition()\n\t\toptions?.signal?.throwIfAborted()\n\t\tif (held) return\n\n\t\tconst elapsed = performance.now() - start\n\t\tif (elapsed >= budget) {\n\t\t\tthrow new Error(\n\t\t\t\t`Condition \"${description}\" did not hold within ${budget}ms (waited ${elapsed}ms)`,\n\t\t\t)\n\t\t}\n\t\tawait waitForDelay(interval)\n\t}\n}\n\n/**\n * Repeats a producer until one produced value satisfies a predicate.\n *\n * @typeParam T - The produced value type.\n * @param description - The operation described in an exhaustion error.\n * @param produce - The synchronous or asynchronous operation to repeat.\n * @param satisfied - The predicate that accepts a produced value.\n * @param options - The time, attempt, and abort bounds.\n * @returns The first produced value the predicate accepts.\n * @throws The predicate's thrown value, the abort reason, or an `Error` when a bound is invalid or\n * the retry exhausts its budget or attempts.\n * @remarks A producer throw counts as an unsatisfied attempt. The last producer error becomes the\n * exhaustion error's `cause`. Default budget: `1000` milliseconds. Default interval: `10`\n * milliseconds.\n */\nexport async function retryUntil<T>(\n\tdescription: string,\n\tproduce: () => T | Promise<T>,\n\tsatisfied: (value: T) => boolean,\n\toptions?: RetryOptions,\n): Promise<T> {\n\tconst budget = options?.budget ?? 1000\n\tconst interval = options?.interval ?? 10\n\tconst attempts = options?.attempts\n\tif (!Number.isFinite(budget) || budget < 0) {\n\t\tthrow new Error('Retry budget must be finite and non-negative')\n\t}\n\tif (!Number.isFinite(interval) || interval < 0) {\n\t\tthrow new Error('Retry interval must be finite and non-negative')\n\t}\n\tif (attempts !== undefined && (!Number.isInteger(attempts) || attempts < 1)) {\n\t\tthrow new Error('Retry attempts must be a positive integer')\n\t}\n\n\tconst start = performance.now()\n\tlet count = 0\n\tlet cause: unknown\n\twhile (true) {\n\t\toptions?.signal?.throwIfAborted()\n\t\tif (count > 0) {\n\t\t\tconst elapsed = performance.now() - start\n\t\t\tif (elapsed >= budget) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Retry \"${description}\" did not succeed within ${budget}ms (waited ${elapsed}ms)`,\n\t\t\t\t\t{ cause },\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t\tlet produced:\n\t\t\t| { readonly success: false; readonly error: unknown }\n\t\t\t| { readonly success: true; readonly value: T }\n\t\ttry {\n\t\t\tproduced = { success: true, value: await produce() }\n\t\t} catch (error) {\n\t\t\tproduced = { success: false, error }\n\t\t}\n\t\tcount += 1\n\t\toptions?.signal?.throwIfAborted()\n\n\t\tif (produced.success) {\n\t\t\tif (satisfied(produced.value)) return produced.value\n\t\t} else {\n\t\t\tcause = produced.error\n\t\t}\n\n\t\tconst elapsed = performance.now() - start\n\t\tif (elapsed >= budget) {\n\t\t\tthrow new Error(\n\t\t\t\t`Retry \"${description}\" did not succeed within ${budget}ms (waited ${elapsed}ms)`,\n\t\t\t\t{ cause },\n\t\t\t)\n\t\t}\n\t\tif (attempts !== undefined && count >= attempts) {\n\t\t\tthrow new Error(`Retry \"${description}\" did not succeed within ${attempts} attempts`, {\n\t\t\t\tcause,\n\t\t\t})\n\t\t}\n\t\tawait waitForDelay(Math.min(interval, budget - elapsed))\n\t}\n}\n\n/**\n * Waits for the first delivery from an event subscription.\n *\n * @typeParam TArgs - The delivered argument tuple.\n * @param subscribe - The function that installs the event listener and may return its cleanup.\n * @param description - The event described in a timeout error.\n * @param options - The time bounds and abort signal.\n * @returns The first delivered argument tuple.\n * @throws The subscription's thrown value, the abort reason, or an `Error` when a bound is invalid\n * or the event is not delivered within the budget.\n * @remarks Default budget: `1000` milliseconds. The interval is validated for consistency with the\n * wait family but is not used because this helper parks on the event.\n */\nexport async function waitForEvent<TArgs extends readonly unknown[]>(\n\tsubscribe: EventSubscriber<TArgs>,\n\tdescription: string,\n\toptions?: WaitOptions,\n): Promise<TArgs> {\n\tconst budget = options?.budget ?? 1000\n\tconst interval = options?.interval ?? 10\n\tif (!Number.isFinite(budget) || budget < 0) {\n\t\tthrow new Error('Event budget must be finite and non-negative')\n\t}\n\tif (!Number.isFinite(interval) || interval < 0) {\n\t\tthrow new Error('Event interval must be finite and non-negative')\n\t}\n\n\tconst signal = options?.signal\n\tsignal?.throwIfAborted()\n\tconst delivery = Promise.withResolvers<TArgs>()\n\tconst controller = new AbortController()\n\tlet timeout: ReturnType<typeof setTimeout> | undefined\n\tconst pending: Array<Promise<TArgs>> = [\n\t\tdelivery.promise,\n\t\tnew Promise((_resolve, reject) => {\n\t\t\ttimeout = setTimeout(() => {\n\t\t\t\treject(new Error(`Event \"${description}\" was not delivered within ${budget}ms`))\n\t\t\t}, budget)\n\t\t}),\n\t]\n\tif (signal !== undefined) {\n\t\tpending.push(\n\t\t\tnew Promise((_resolve, reject) => {\n\t\t\t\tconst combined = AbortSignal.any([signal, controller.signal])\n\t\t\t\tcombined.addEventListener(\n\t\t\t\t\t'abort',\n\t\t\t\t\t() => {\n\t\t\t\t\t\tif (signal.aborted) reject(signal.reason)\n\t\t\t\t\t},\n\t\t\t\t\t{ once: true },\n\t\t\t\t)\n\t\t\t}),\n\t\t)\n\t}\n\tconst result = Promise.race(pending)\n\tlet cleanup: (() => void) | void = undefined\n\ttry {\n\t\ttry {\n\t\t\tcleanup = subscribe((...args) => delivery.resolve(args))\n\t\t} catch (error) {\n\t\t\tdelivery.reject(error)\n\t\t}\n\t\treturn await result\n\t} finally {\n\t\tcontroller.abort()\n\t\tif (timeout !== undefined) clearTimeout(timeout)\n\t\tcleanup?.()\n\t}\n}\n\n/**\n * Decodes newline-delimited JSON values.\n *\n * @param text - The JSON Lines text to decode.\n * @returns The decoded values in physical-line order.\n * @throws An `Error` naming the malformed physical line, with the native `SyntaxError` as its\n * `cause`.\n */\nexport function decodeJSONLines(text: string): readonly unknown[] {\n\tconst values: unknown[] = []\n\tfor (const [index, physical] of text.split('\\n').entries()) {\n\t\tconst line = physical.endsWith('\\r') ? physical.slice(0, -1) : physical\n\t\tif (line.length === 0) continue\n\t\ttry {\n\t\t\tconst value: unknown = JSON.parse(line)\n\t\t\tvalues.push(value)\n\t\t} catch (cause) {\n\t\t\tthrow new Error(`Invalid JSON on line ${index + 1}`, { cause })\n\t\t}\n\t}\n\treturn values\n}\n\n/**\n * Captures the value thrown by a thunk.\n *\n * @param thunk - The work whose thrown value to capture.\n * @returns The thrown value, or `undefined` when the thunk completes.\n */\nexport function captureError(thunk: () => unknown): unknown {\n\ttry {\n\t\tthunk()\n\t} catch (error) {\n\t\treturn error\n\t}\n\treturn undefined\n}\n\n/**\n * Requires a value to be present.\n *\n * @typeParam T - The required value type.\n * @param value - The value to check.\n * @param message - The error message used when the value is absent.\n * @returns The present value.\n */\nexport function requireValue<T>(value: T | null | undefined, message = 'Value is required'): T {\n\tif (value === null || value === undefined) throw new Error(message)\n\treturn value\n}\n\n/**\n * Collects every value from an async iterable.\n *\n * @typeParam T - The yielded value type.\n * @param source - The async iterable to drain.\n * @returns The yielded values in iteration order.\n */\nexport async function collect<T>(source: AsyncIterable<T>): Promise<readonly T[]> {\n\tconst values: T[] = []\n\tfor await (const value of source) values.push(value)\n\treturn values\n}\n\n/**\n * Collects every value from a readable stream.\n *\n * @typeParam T - The streamed value type.\n * @param stream - The readable stream to drain.\n * @returns The streamed values in read order.\n */\nexport async function collectStream<T>(stream: ReadableStream<T>): Promise<readonly T[]> {\n\tconst reader = stream.getReader()\n\tconst values: T[] = []\n\ttry {\n\t\twhile (true) {\n\t\t\tconst result = await reader.read()\n\t\t\tif (result.done) return values\n\t\t\tvalues.push(result.value)\n\t\t}\n\t} finally {\n\t\treader.releaseLock()\n\t}\n}\n\n/**\n * Copies a JSON value through serialization and parsing.\n *\n * @typeParam T - The copied value's type, which the copy keeps.\n * @param value - The value to copy, bounded by its own `JSONSafe` projection.\n * @returns The parsed JSON copy.\n * @remarks Non-finite numbers throw because JSON would replace them with `null`. Negative zero is\n * normalized to zero by JSON serialization. The bound intersects `JSONSafe<T>` rather than\n * constraining `T` to `JSONValue`, so an interface-typed value round-trips.\n */\nexport function roundTripJSON<T>(value: T & JSONSafe<T>): T {\n\tconst serialized = JSON.stringify(value, (_key, current) => {\n\t\tif (current === undefined || typeof current === 'function' || typeof current === 'symbol') {\n\t\t\tthrow new Error('JSON values must not contain undefined, functions, or symbols')\n\t\t}\n\t\tif (typeof current === 'number' && !Number.isFinite(current)) {\n\t\t\tthrow new Error('JSON values must contain finite numbers')\n\t\t}\n\t\treturn current\n\t})\n\tconst parsed: T = JSON.parse(serialized)\n\tconst pending: unknown[] = [parsed]\n\twhile (pending.length > 0) {\n\t\tconst current = pending.pop()\n\t\tif (typeof current === 'number' && !Number.isFinite(current)) {\n\t\t\tthrow new Error('JSON values must contain finite numbers')\n\t\t}\n\t\tif (Array.isArray(current)) {\n\t\t\tfor (const child of current) pending.push(child)\n\t\t} else if (typeof current === 'object' && current !== null) {\n\t\t\tfor (const child of Object.values(current)) pending.push(child)\n\t\t}\n\t}\n\treturn parsed\n}\n\n/**\n * Resolves the parent directory of a calling module, which is the workspace root when called from\n * the conventional `tests/setup.ts` location.\n *\n * @param meta - The calling module metadata.\n * @returns The root URL one directory above the calling file.\n */\nexport function resolveRoot(meta: ImportMeta): URL {\n\treturn new URL('../', meta.url)\n}\n","import type { RecorderInterface, TeardownHandler, TeardownInterface } from './types.js'\n\n/**\n * Creates values that make common object readers throw or violate their assumptions.\n *\n * @returns A frozen array whose six values are fresh on every call.\n * @remarks Every member makes a naive reader throw. A total guard survives every member without\n * throwing. Whether it accepts or refuses one is that guard's own contract. Membership may grow in\n * a release, so test the whole returned set in a loop and include the index in each failure.\n * @example\n * ```ts\n * import { expect } from 'vitest'\n * import { createHostileValues } from '@orkestrel/test'\n *\n * function isWireRecord(value: unknown): value is Readonly<Record<string, string>> {\n * \tif (typeof value !== 'object' || value === null) return false\n * \ttry {\n * \t\tif (Object.getPrototypeOf(value) !== Object.prototype) return false\n * \t\tReflect.get(value, 'value')\n * \t\tif (Reflect.ownKeys(value).length === 0) return false\n * \t\treturn Object.values(value).every((member) => typeof member === 'string')\n * \t} catch {\n * \t\treturn false\n * \t}\n * }\n *\n * for (const [index, value] of createHostileValues().entries()) {\n * \tlet accepted: boolean | undefined\n * \texpect(() => {\n * \t\taccepted = isWireRecord(value)\n * \t}, `hostile value ${index}`).not.toThrow()\n * \texpect(accepted, `hostile value ${index}`).toBe(false)\n * }\n * ```\n */\nexport function createHostileValues(): readonly unknown[] {\n\tconst cyclic: Record<string, unknown> = {}\n\tcyclic.self = cyclic\n\tconst revoked = Proxy.revocable({}, {})\n\trevoked.revoke()\n\n\treturn Object.freeze([\n\t\tcyclic,\n\t\trevoked.proxy,\n\t\tnew Proxy(\n\t\t\t{},\n\t\t\t{\n\t\t\t\tget() {\n\t\t\t\t\tthrow new Error('Hostile property read')\n\t\t\t\t},\n\t\t\t},\n\t\t),\n\t\tnew Proxy(\n\t\t\t{},\n\t\t\t{\n\t\t\t\townKeys() {\n\t\t\t\t\tthrow new Error('Hostile key enumeration')\n\t\t\t\t},\n\t\t\t},\n\t\t),\n\t\tnew Proxy(\n\t\t\t{},\n\t\t\t{\n\t\t\t\tgetPrototypeOf() {\n\t\t\t\t\tthrow new Error('Hostile prototype read')\n\t\t\t\t},\n\t\t\t},\n\t\t),\n\t\tObject.create(null),\n\t])\n}\n\n/**\n * Creates a recorder for callback arguments.\n *\n * @typeParam TArgs - The argument tuple to record.\n * @returns A recorder whose handler appends calls in order.\n */\nexport function createRecorder<TArgs extends readonly unknown[]>(): RecorderInterface<TArgs> {\n\tconst calls: TArgs[] = []\n\treturn {\n\t\tcalls,\n\t\tget count() {\n\t\t\treturn calls.length\n\t\t},\n\t\thandler(...args) {\n\t\t\tcalls.push(args)\n\t\t},\n\t\tclear() {\n\t\t\tcalls.length = 0\n\t\t},\n\t}\n}\n\n/**\n * Creates a teardown list that runs registered handlers newest-first.\n *\n * @returns A teardown list that awaits every handler and collects failures.\n */\nexport function createTeardown(): TeardownInterface {\n\tlet handlers: TeardownHandler[] = []\n\treturn {\n\t\tget count() {\n\t\t\treturn handlers.length\n\t\t},\n\t\tadd(handler) {\n\t\t\thandlers.push(handler)\n\t\t},\n\t\tasync destroy() {\n\t\t\tconst snapshot = handlers\n\t\t\thandlers = []\n\t\t\tconst failures: unknown[] = []\n\t\t\tfor (const handler of snapshot.reverse()) {\n\t\t\t\ttry {\n\t\t\t\t\tawait handler()\n\t\t\t\t} catch (error) {\n\t\t\t\t\tfailures.push(error)\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (failures.length === 1) throw failures[0]\n\t\t\tif (failures.length > 1) throw new AggregateError(failures)\n\t\t},\n\t}\n}\n"],"mappings":"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"}