@orkestrel/timeout 0.0.8 → 0.0.10

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.
package/README.md CHANGED
@@ -1,13 +1,14 @@
1
1
  # @orkestrel/timeout
2
2
 
3
- A typed, **controllable** `setTimeout` wrapper — a deadline handle that
4
- exposes an `AbortSignal` which fires on expiry, for racing against work.
5
- Deliberately small: `start()` arms the deadline, `clear()` cancels it without
6
- firing, and re-`start()`ing a handle after expiry reuses it for a fresh
7
- deadline without re-construction. An optional parent `signal` links in without
8
- inheriting `AbortSignal.any` semantics — a parent abort during the timing
9
- window _clears_ the timeout (it never expires) rather than firing it. Part of
10
- the `@orkestrel` line.
3
+ > The time-bound half of the substrate's time-and-cancellation pair: a
4
+ > controllable `setTimeout` wrapper carrying a trace `id` and a deadline `ms`,
5
+ > whose native `AbortSignal` aborts on expiry.
6
+
7
+ Create a handle with the `createTimeout` function, call `start()` to arm the
8
+ deadline, and hand its signal to the fetch, stream, or queue task the deadline
9
+ bounds; call `clear()` when that work finishes first. Pass a parent signal
10
+ where an outer cancellation must retire the deadline rather than let it expire;
11
+ that link carries no `AbortSignal.any` semantics. Part of the `@orkestrel` line.
11
12
 
12
13
  ## Install
13
14
 
@@ -17,8 +18,8 @@ npm install @orkestrel/timeout
17
18
 
18
19
  ## Requirements
19
20
 
20
- - Node.js >= 24
21
- - ESM-only (no CommonJS build)
21
+ - Node.js >= 22.12.0, matching the `engines` field in `package.json`
22
+ - ESM (`import`) and CommonJS (`require`) through the `exports` field
22
23
 
23
24
  ## Usage
24
25
 
@@ -61,7 +62,7 @@ once-read helper that returns a fresh copy without absent optional keys.
61
62
  ## Guide
62
63
 
63
64
  For the full surface — the deadline lifecycle, parent-signal linking, and
64
- reuse semantics — see [`guides/src/timeout.md`](guides/src/timeout.md).
65
+ reuse semantics — see [`guides/timeout.md`](guides/timeout.md).
65
66
 
66
67
  ## Package
67
68
 
@@ -2,7 +2,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  let _orkestrel_contract = require("@orkestrel/contract");
3
3
  //#region src/core/constants.ts
4
4
  /**
5
- * Largest timeout duration accepted by the package, in milliseconds.
5
+ * Names the largest timeout duration the package accepts, `2_147_483_647` milliseconds.
6
6
  *
7
7
  * @example
8
8
  * ```ts
@@ -13,10 +13,11 @@ var MAX_TIMEOUT_MS = 2147483647;
13
13
  //#endregion
14
14
  //#region src/core/validators.ts
15
15
  /**
16
- * Determine whether a value is an accepted timeout duration.
16
+ * Determines whether a value is an integer in the inclusive range from `0` through
17
+ * `MAX_TIMEOUT_MS`, staying total for every input.
17
18
  *
18
19
  * @param value - Unknown duration candidate
19
- * @returns `true` only for an integer in the inclusive timeout range
20
+ * @returns True if the value is an integer in the inclusive timeout range; false otherwise
20
21
  *
21
22
  * @example
22
23
  * ```ts
@@ -25,18 +26,18 @@ var MAX_TIMEOUT_MS = 2147483647;
25
26
  * ```
26
27
  */
27
28
  function isTimeoutDuration(value) {
28
- return (0, _orkestrel_contract.isFiniteNumber)(value) && (0, _orkestrel_contract.isInteger)(value) && value >= 0 && value <= 2147483647;
29
+ return (0, _orkestrel_contract.isInteger)(value) && value >= 0 && value <= 2147483647;
29
30
  }
30
31
  /**
31
- * Determine whether a value is a genuine native `AbortSignal`.
32
+ * Determines whether a value is a genuine native `AbortSignal`, staying total for a
33
+ * structural spoof and for a hostile or revoked proxy.
32
34
  *
33
35
  * @remarks
34
- * The intrinsic `aborted` getter performs the native brand check. The boundary
35
- * contains revoked proxies and hostile receivers, returning `false` instead of
36
- * allowing their errors to escape.
36
+ * The intrinsic `aborted` getter performs the native brand check, and the boundary
37
+ * returns `false` rather than letting a receiver's error escape.
37
38
  *
38
39
  * @param value - Unknown signal candidate
39
- * @returns `true` only when the native `AbortSignal` getter accepts the value
40
+ * @returns True if the native `AbortSignal` getter accepts the value; false otherwise
40
41
  *
41
42
  * @example
42
43
  * ```ts
@@ -56,17 +57,17 @@ function isTimeoutSignal(value) {
56
57
  //#endregion
57
58
  //#region src/core/helpers.ts
58
59
  /**
59
- * Validate and normalize timeout construction options.
60
+ * Validates once-read timeout construction options and returns a fresh normalized copy
61
+ * omitting absent optional keys.
60
62
  *
61
63
  * @remarks
62
- * Each property is read exactly once before validation. The returned object is
63
- * a fresh copy and omits absent optional properties. No timer, controller, or
64
+ * Each property is read before any validation runs, and no timer, controller, or
64
65
  * listener lifecycle begins at this boundary.
65
66
  *
66
67
  * @param options - Potentially untrusted timeout options
67
68
  * @returns A fresh validated `TimeoutOptions` object
68
- * @throws {@link import('@orkestrel/contract').ContractError} When the input
69
- * does not satisfy `TimeoutOptions`
69
+ * @throws {@link import('@orkestrel/contract').ContractError} Thrown when the
70
+ * input does not satisfy `TimeoutOptions`
70
71
  *
71
72
  * @example
72
73
  * ```ts
@@ -142,7 +143,8 @@ function validateTimeoutOptions(options) {
142
143
  //#endregion
143
144
  //#region src/core/Timeout.ts
144
145
  /**
145
- * A controllable deadline whose native `AbortSignal` aborts when it expires.
146
+ * Implements `TimeoutInterface` exactly, as a controllable `setTimeout` wrapper over one
147
+ * owned `AbortController` whose signal aborts when the deadline expires.
146
148
  *
147
149
  * @remarks
148
150
  * `start()` arms or replaces the current deadline. Expiry sets `expired` and
@@ -167,21 +169,26 @@ function validateTimeoutOptions(options) {
167
169
  * ```
168
170
  */
169
171
  var Timeout = class {
170
- id;
171
- ms;
172
+ #id;
173
+ #ms;
172
174
  #parent;
173
175
  #listener;
174
176
  #controller;
175
177
  #handle;
176
- #linked = false;
177
178
  constructor(options) {
178
179
  const input = validateTimeoutOptions(options);
179
- this.id = input.id === void 0 ? crypto.randomUUID() : input.id;
180
- this.ms = input.ms;
180
+ this.#id = input.id === void 0 ? crypto.randomUUID() : input.id;
181
+ this.#ms = input.ms;
181
182
  this.#parent = input.signal;
182
183
  this.#controller = new AbortController();
183
184
  this.#listener = this.clear.bind(this);
184
185
  }
186
+ get id() {
187
+ return this.#id;
188
+ }
189
+ get ms() {
190
+ return this.#ms;
191
+ }
185
192
  get signal() {
186
193
  return this.#controller.signal;
187
194
  }
@@ -196,15 +203,12 @@ var Timeout = class {
196
203
  }
197
204
  this.#detach();
198
205
  if (this.#controller.signal.aborted) this.#controller = new AbortController();
199
- if (this.#parent !== void 0) {
200
- this.#parent.addEventListener("abort", this.#listener, { once: true });
201
- this.#linked = true;
202
- }
206
+ if (this.#parent !== void 0) this.#parent.addEventListener("abort", this.#listener, { once: true });
203
207
  this.#handle = setTimeout(() => {
204
208
  this.#handle = void 0;
205
209
  this.#detach();
206
210
  this.#controller.abort();
207
- }, this.ms);
211
+ }, this.#ms);
208
212
  }
209
213
  clear() {
210
214
  if (this.#handle !== void 0) {
@@ -215,39 +219,43 @@ var Timeout = class {
215
219
  if (this.#controller.signal.aborted) this.#controller = new AbortController();
216
220
  }
217
221
  #detach() {
218
- if (this.#linked) {
219
- this.#parent?.removeEventListener("abort", this.#listener);
220
- this.#linked = false;
221
- }
222
+ this.#parent?.removeEventListener("abort", this.#listener);
222
223
  }
223
224
  };
224
225
  //#endregion
225
226
  //#region src/core/factories.ts
226
227
  /**
227
- * Create a controllable deadline whose native signal aborts on expiry.
228
+ * Creates a deadline handle from validated `TimeoutOptions` and returns it as a
229
+ * `TimeoutInterface`.
228
230
  *
229
231
  * @remarks
230
232
  * `options.ms` must be an integer from `0` through `2_147_483_647`, inclusive.
231
233
  * A parent `options.signal` clears an armed timeout without aborting the
232
- * timeout's own signal. Omitted `options.id` values generate a random UUID.
233
- * Malformed or unreadable options use contract code `bound`; invalid `id`,
234
- * `ms`, and `signal` values use `literal`, `range`, and `placement`.
234
+ * timeout's own signal. `start()` returns without arming when the parent
235
+ * `signal` supplied at construction has already aborted, so a handle whose
236
+ * parent aborted never expires again and its `signal` never fires. Omitted
237
+ * `options.id` values generate a random UUID. Malformed or unreadable options
238
+ * use contract code `bound`; invalid `id`, `ms`, and `signal` values use
239
+ * `literal`, `range`, and `placement`.
235
240
  *
236
241
  * @param options - Validated deadline, optional trace label, and optional native parent signal
237
242
  * @returns A reusable timeout handle
238
- * @throws {@link import('@orkestrel/contract').ContractError} When the
243
+ * @throws {@link import('@orkestrel/contract').ContractError} Thrown when the
239
244
  * JavaScript input does not satisfy `TimeoutOptions`
240
245
  *
241
- * @example
246
+ * @example Race work against a deadline
242
247
  * ```ts
243
248
  * import { createTimeout } from '@orkestrel/timeout'
244
249
  *
245
- * const timeout = createTimeout({ ms: 5_000 })
246
- * timeout.start()
247
- * try {
248
- * await fetch('/work', { signal: timeout.signal })
249
- * } finally {
250
- * timeout.clear()
250
+ * async function fetchWithDeadline(url: string, ms: number): Promise<Response> {
251
+ * const timeout = createTimeout({ ms })
252
+ * timeout.start()
253
+ *
254
+ * try {
255
+ * return await fetch(url, { signal: timeout.signal })
256
+ * } finally {
257
+ * timeout.clear() // cancels the still-armed deadline when the fetch won the race
258
+ * }
251
259
  * }
252
260
  * ```
253
261
  */
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["#parent","#listener","#controller","#handle","#detach","#linked"],"sources":["../../../src/core/constants.ts","../../../src/core/validators.ts","../../../src/core/helpers.ts","../../../src/core/Timeout.ts","../../../src/core/factories.ts"],"sourcesContent":["/**\n * Largest timeout duration accepted by the package, in milliseconds.\n *\n * @example\n * ```ts\n * createTimeout({ ms: MAX_TIMEOUT_MS })\n * ```\n */\nexport const MAX_TIMEOUT_MS = 2_147_483_647\n","import { isFiniteNumber, isInteger } from '@orkestrel/contract'\nimport { MAX_TIMEOUT_MS } from './constants.js'\n\n/**\n * Determine whether a value is an accepted timeout duration.\n *\n * @param value - Unknown duration candidate\n * @returns `true` only for an integer in the inclusive timeout range\n *\n * @example\n * ```ts\n * isTimeoutDuration(0) // true\n * isTimeoutDuration(1.5) // false\n * ```\n */\nexport function isTimeoutDuration(value: unknown): value is number {\n\treturn isFiniteNumber(value) && isInteger(value) && value >= 0 && value <= MAX_TIMEOUT_MS\n}\n\n/**\n * Determine whether a value is a genuine native `AbortSignal`.\n *\n * @remarks\n * The intrinsic `aborted` getter performs the native brand check. The boundary\n * contains revoked proxies and hostile receivers, returning `false` instead of\n * allowing their errors to escape.\n *\n * @param value - Unknown signal candidate\n * @returns `true` only when the native `AbortSignal` getter accepts the value\n *\n * @example\n * ```ts\n * isTimeoutSignal(new AbortController().signal) // true\n * isTimeoutSignal({ aborted: false }) // false\n * ```\n */\nexport function isTimeoutSignal(value: unknown): value is AbortSignal {\n\ttry {\n\t\tconst descriptor = Object.getOwnPropertyDescriptor(AbortSignal.prototype, 'aborted')\n\t\tif (descriptor?.get === undefined) return false\n\t\treturn typeof Reflect.apply(descriptor.get, value, []) === 'boolean'\n\t} catch {\n\t\treturn false\n\t}\n}\n","import type { TimeoutOptions } from './types.js'\nimport { ContractError, isRecord, isString, preview } from '@orkestrel/contract'\nimport { MAX_TIMEOUT_MS } from './constants.js'\nimport { isTimeoutDuration, isTimeoutSignal } from './validators.js'\n\n/**\n * Validate and normalize timeout construction options.\n *\n * @remarks\n * Each property is read exactly once before validation. The returned object is\n * a fresh copy and omits absent optional properties. No timer, controller, or\n * listener lifecycle begins at this boundary.\n *\n * @param options - Potentially untrusted timeout options\n * @returns A fresh validated `TimeoutOptions` object\n * @throws {@link import('@orkestrel/contract').ContractError} When the input\n * does not satisfy `TimeoutOptions`\n *\n * @example\n * ```ts\n * const options = validateTimeoutOptions({ ms: 5_000 })\n * ```\n */\nexport function validateTimeoutOptions(options: TimeoutOptions): TimeoutOptions {\n\tif (!isRecord(options)) {\n\t\tthrow new ContractError('Timeout: options must be a plain record', {\n\t\t\tcode: 'bound',\n\t\t\tcontext: {\n\t\t\t\tpath: ['options'],\n\t\t\t\tlimit: 'plain record',\n\t\t\t\treceived: preview(options),\n\t\t\t},\n\t\t})\n\t}\n\n\tlet id: TimeoutOptions['id']\n\tlet ms: TimeoutOptions['ms']\n\tlet signal: TimeoutOptions['signal']\n\ttry {\n\t\tid = options.id\n\t\tms = options.ms\n\t\tsignal = options.signal\n\t} catch (cause) {\n\t\tthrow new ContractError('Timeout: options could not be read', {\n\t\t\tcode: 'bound',\n\t\t\tcontext: {\n\t\t\t\tpath: ['options'],\n\t\t\t\tlimit: 'readable plain record',\n\t\t\t\treceived: preview(options),\n\t\t\t},\n\t\t\tcause,\n\t\t})\n\t}\n\n\tif (id !== undefined && !isString(id)) {\n\t\tthrow new ContractError('Timeout: id must be a string when defined', {\n\t\t\tcode: 'literal',\n\t\t\tcontext: {\n\t\t\t\tpath: ['options', 'id'],\n\t\t\t\tlimit: 'string or undefined',\n\t\t\t\treceived: preview(id),\n\t\t\t},\n\t\t})\n\t}\n\tif (!isTimeoutDuration(ms)) {\n\t\tthrow new ContractError('Timeout: ms must be an integer in the supported range', {\n\t\t\tcode: 'range',\n\t\t\tcontext: {\n\t\t\t\tpath: ['options', 'ms'],\n\t\t\t\tlimit: `integer in [0, ${MAX_TIMEOUT_MS}]`,\n\t\t\t\treceived: preview(ms),\n\t\t\t},\n\t\t})\n\t}\n\tif (signal !== undefined && !isTimeoutSignal(signal)) {\n\t\tthrow new ContractError('Timeout: signal must be a native AbortSignal when defined', {\n\t\t\tcode: 'placement',\n\t\t\tcontext: {\n\t\t\t\tpath: ['options', 'signal'],\n\t\t\t\tlimit: 'native AbortSignal or undefined',\n\t\t\t\treceived: preview(signal),\n\t\t\t},\n\t\t})\n\t}\n\n\tif (id !== undefined && signal !== undefined) return { id, ms, signal }\n\tif (id !== undefined) return { id, ms }\n\tif (signal !== undefined) return { ms, signal }\n\treturn { ms }\n}\n","import type { TimeoutInterface, TimeoutOptions } from './types.js'\nimport { validateTimeoutOptions } from './helpers.js'\n\n/**\n * A controllable deadline whose native `AbortSignal` aborts when it expires.\n *\n * @remarks\n * `start()` arms or replaces the current deadline. Expiry sets `expired` and\n * aborts the current signal once. `clear()` cancels without aborting and resets\n * `expired`. Starting or clearing after expiry installs a fresh signal. A parent\n * abort deliberately clears the timeout without aborting its signal; once the\n * parent has aborted, later starts are inert.\n *\n * Construction validates the complete JavaScript boundary before creating an\n * `AbortController`. A malformed or unreadable options record throws a\n * `bound`-coded {@link import('@orkestrel/contract').ContractError}; an invalid\n * `id`, `ms`, or `signal` uses `literal`, `range`, or `placement`, respectively.\n *\n * @example\n * ```ts\n * import { Timeout } from '@orkestrel/timeout'\n *\n * const timeout = new Timeout({ ms: 5_000 })\n * timeout.start()\n * timeout.signal.addEventListener('abort', () => giveUp(), { once: true })\n * timeout.clear()\n * ```\n */\nexport class Timeout implements TimeoutInterface {\n\treadonly id: string\n\treadonly ms: number\n\treadonly #parent: AbortSignal | undefined\n\treadonly #listener: () => void\n\t#controller: AbortController\n\t#handle: ReturnType<typeof setTimeout> | undefined\n\t#linked = false\n\n\tconstructor(options: TimeoutOptions) {\n\t\tconst input = validateTimeoutOptions(options)\n\t\tthis.id = input.id === undefined ? crypto.randomUUID() : input.id\n\t\tthis.ms = input.ms\n\t\tthis.#parent = input.signal\n\t\tthis.#controller = new AbortController()\n\t\tthis.#listener = this.clear.bind(this)\n\t}\n\n\tget signal(): AbortSignal {\n\t\treturn this.#controller.signal\n\t}\n\n\tget expired(): boolean {\n\t\treturn this.#controller.signal.aborted\n\t}\n\n\tstart(): void {\n\t\tif (this.#parent?.aborted === true) return\n\t\tif (this.#handle !== undefined) {\n\t\t\tclearTimeout(this.#handle)\n\t\t\tthis.#handle = undefined\n\t\t}\n\t\tthis.#detach()\n\t\tif (this.#controller.signal.aborted) this.#controller = new AbortController()\n\t\tif (this.#parent !== undefined) {\n\t\t\tthis.#parent.addEventListener('abort', this.#listener, { once: true })\n\t\t\tthis.#linked = true\n\t\t}\n\t\tthis.#handle = setTimeout(() => {\n\t\t\tthis.#handle = undefined\n\t\t\tthis.#detach()\n\t\t\tthis.#controller.abort()\n\t\t}, this.ms)\n\t}\n\n\tclear(): void {\n\t\tif (this.#handle !== undefined) {\n\t\t\tclearTimeout(this.#handle)\n\t\t\tthis.#handle = undefined\n\t\t}\n\t\tthis.#detach()\n\t\tif (this.#controller.signal.aborted) this.#controller = new AbortController()\n\t}\n\n\t#detach(): void {\n\t\tif (this.#linked) {\n\t\t\tthis.#parent?.removeEventListener('abort', this.#listener)\n\t\t\tthis.#linked = false\n\t\t}\n\t}\n}\n","import type { TimeoutInterface, TimeoutOptions } from './types.js'\nimport { Timeout } from './Timeout.js'\n\n/**\n * Create a controllable deadline whose native signal aborts on expiry.\n *\n * @remarks\n * `options.ms` must be an integer from `0` through `2_147_483_647`, inclusive.\n * A parent `options.signal` clears an armed timeout without aborting the\n * timeout's own signal. Omitted `options.id` values generate a random UUID.\n * Malformed or unreadable options use contract code `bound`; invalid `id`,\n * `ms`, and `signal` values use `literal`, `range`, and `placement`.\n *\n * @param options - Validated deadline, optional trace label, and optional native parent signal\n * @returns A reusable timeout handle\n * @throws {@link import('@orkestrel/contract').ContractError} When the\n * JavaScript input does not satisfy `TimeoutOptions`\n *\n * @example\n * ```ts\n * import { createTimeout } from '@orkestrel/timeout'\n *\n * const timeout = createTimeout({ ms: 5_000 })\n * timeout.start()\n * try {\n * \tawait fetch('/work', { signal: timeout.signal })\n * } finally {\n * \ttimeout.clear()\n * }\n * ```\n */\nexport function createTimeout(options: TimeoutOptions): TimeoutInterface {\n\treturn new Timeout(options)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":[],"sources":["../../../src/core/constants.ts","../../../src/core/validators.ts","../../../src/core/helpers.ts","../../../src/core/Timeout.ts","../../../src/core/factories.ts"],"sourcesContent":["/**\n * Names the largest timeout duration the package accepts, `2_147_483_647` milliseconds.\n *\n * @example\n * ```ts\n * createTimeout({ ms: MAX_TIMEOUT_MS })\n * ```\n */\nexport const MAX_TIMEOUT_MS = 2_147_483_647\n","import { isInteger } from '@orkestrel/contract'\nimport { MAX_TIMEOUT_MS } from './constants.js'\n\n/**\n * Determines whether a value is an integer in the inclusive range from `0` through\n * `MAX_TIMEOUT_MS`, staying total for every input.\n *\n * @param value - Unknown duration candidate\n * @returns True if the value is an integer in the inclusive timeout range; false otherwise\n *\n * @example\n * ```ts\n * isTimeoutDuration(0) // true\n * isTimeoutDuration(1.5) // false\n * ```\n */\nexport function isTimeoutDuration(value: unknown): value is number {\n\treturn isInteger(value) && value >= 0 && value <= MAX_TIMEOUT_MS\n}\n\n/**\n * Determines whether a value is a genuine native `AbortSignal`, staying total for a\n * structural spoof and for a hostile or revoked proxy.\n *\n * @remarks\n * The intrinsic `aborted` getter performs the native brand check, and the boundary\n * returns `false` rather than letting a receiver's error escape.\n *\n * @param value - Unknown signal candidate\n * @returns True if the native `AbortSignal` getter accepts the value; false otherwise\n *\n * @example\n * ```ts\n * isTimeoutSignal(new AbortController().signal) // true\n * isTimeoutSignal({ aborted: false }) // false\n * ```\n */\nexport function isTimeoutSignal(value: unknown): value is AbortSignal {\n\ttry {\n\t\tconst descriptor = Object.getOwnPropertyDescriptor(AbortSignal.prototype, 'aborted')\n\t\tif (descriptor?.get === undefined) return false\n\t\treturn typeof Reflect.apply(descriptor.get, value, []) === 'boolean'\n\t} catch {\n\t\treturn false\n\t}\n}\n","import type { TimeoutOptions } from './types.js'\nimport { ContractError, isRecord, isString, preview } from '@orkestrel/contract'\nimport { MAX_TIMEOUT_MS } from './constants.js'\nimport { isTimeoutDuration, isTimeoutSignal } from './validators.js'\n\n/**\n * Validates once-read timeout construction options and returns a fresh normalized copy\n * omitting absent optional keys.\n *\n * @remarks\n * Each property is read before any validation runs, and no timer, controller, or\n * listener lifecycle begins at this boundary.\n *\n * @param options - Potentially untrusted timeout options\n * @returns A fresh validated `TimeoutOptions` object\n * @throws {@link import('@orkestrel/contract').ContractError} Thrown when the\n * input does not satisfy `TimeoutOptions`\n *\n * @example\n * ```ts\n * const options = validateTimeoutOptions({ ms: 5_000 })\n * ```\n */\nexport function validateTimeoutOptions(options: TimeoutOptions): TimeoutOptions {\n\tif (!isRecord(options)) {\n\t\tthrow new ContractError('Timeout: options must be a plain record', {\n\t\t\tcode: 'bound',\n\t\t\tcontext: {\n\t\t\t\tpath: ['options'],\n\t\t\t\tlimit: 'plain record',\n\t\t\t\treceived: preview(options),\n\t\t\t},\n\t\t})\n\t}\n\n\tlet id: TimeoutOptions['id']\n\tlet ms: TimeoutOptions['ms']\n\tlet signal: TimeoutOptions['signal']\n\ttry {\n\t\tid = options.id\n\t\tms = options.ms\n\t\tsignal = options.signal\n\t} catch (cause) {\n\t\tthrow new ContractError('Timeout: options could not be read', {\n\t\t\tcode: 'bound',\n\t\t\tcontext: {\n\t\t\t\tpath: ['options'],\n\t\t\t\tlimit: 'readable plain record',\n\t\t\t\treceived: preview(options),\n\t\t\t},\n\t\t\tcause,\n\t\t})\n\t}\n\n\tif (id !== undefined && !isString(id)) {\n\t\tthrow new ContractError('Timeout: id must be a string when defined', {\n\t\t\tcode: 'literal',\n\t\t\tcontext: {\n\t\t\t\tpath: ['options', 'id'],\n\t\t\t\tlimit: 'string or undefined',\n\t\t\t\treceived: preview(id),\n\t\t\t},\n\t\t})\n\t}\n\tif (!isTimeoutDuration(ms)) {\n\t\tthrow new ContractError('Timeout: ms must be an integer in the supported range', {\n\t\t\tcode: 'range',\n\t\t\tcontext: {\n\t\t\t\tpath: ['options', 'ms'],\n\t\t\t\tlimit: `integer in [0, ${MAX_TIMEOUT_MS}]`,\n\t\t\t\treceived: preview(ms),\n\t\t\t},\n\t\t})\n\t}\n\tif (signal !== undefined && !isTimeoutSignal(signal)) {\n\t\tthrow new ContractError('Timeout: signal must be a native AbortSignal when defined', {\n\t\t\tcode: 'placement',\n\t\t\tcontext: {\n\t\t\t\tpath: ['options', 'signal'],\n\t\t\t\tlimit: 'native AbortSignal or undefined',\n\t\t\t\treceived: preview(signal),\n\t\t\t},\n\t\t})\n\t}\n\n\tif (id !== undefined && signal !== undefined) return { id, ms, signal }\n\tif (id !== undefined) return { id, ms }\n\tif (signal !== undefined) return { ms, signal }\n\treturn { ms }\n}\n","import type { TimeoutInterface, TimeoutOptions } from './types.js'\nimport { validateTimeoutOptions } from './helpers.js'\n\n/**\n * Implements `TimeoutInterface` exactly, as a controllable `setTimeout` wrapper over one\n * owned `AbortController` whose signal aborts when the deadline expires.\n *\n * @remarks\n * `start()` arms or replaces the current deadline. Expiry sets `expired` and\n * aborts the current signal once. `clear()` cancels without aborting and resets\n * `expired`. Starting or clearing after expiry installs a fresh signal. A parent\n * abort deliberately clears the timeout without aborting its signal; once the\n * parent has aborted, later starts are inert.\n *\n * Construction validates the complete JavaScript boundary before creating an\n * `AbortController`. A malformed or unreadable options record throws a\n * `bound`-coded {@link import('@orkestrel/contract').ContractError}; an invalid\n * `id`, `ms`, or `signal` uses `literal`, `range`, or `placement`, respectively.\n *\n * @example\n * ```ts\n * import { Timeout } from '@orkestrel/timeout'\n *\n * const timeout = new Timeout({ ms: 5_000 })\n * timeout.start()\n * timeout.signal.addEventListener('abort', () => giveUp(), { once: true })\n * timeout.clear()\n * ```\n */\nexport class Timeout implements TimeoutInterface {\n\treadonly #id: string\n\treadonly #ms: number\n\treadonly #parent: AbortSignal | undefined\n\treadonly #listener: () => void\n\t#controller: AbortController\n\t#handle: ReturnType<typeof setTimeout> | undefined\n\n\tconstructor(options: TimeoutOptions) {\n\t\tconst input = validateTimeoutOptions(options)\n\t\tthis.#id = input.id === undefined ? crypto.randomUUID() : input.id\n\t\tthis.#ms = input.ms\n\t\tthis.#parent = input.signal\n\t\tthis.#controller = new AbortController()\n\t\tthis.#listener = this.clear.bind(this)\n\t}\n\n\tget id(): string {\n\t\treturn this.#id\n\t}\n\n\tget ms(): number {\n\t\treturn this.#ms\n\t}\n\n\tget signal(): AbortSignal {\n\t\treturn this.#controller.signal\n\t}\n\n\tget expired(): boolean {\n\t\treturn this.#controller.signal.aborted\n\t}\n\n\tstart(): void {\n\t\tif (this.#parent?.aborted === true) return\n\t\tif (this.#handle !== undefined) {\n\t\t\tclearTimeout(this.#handle)\n\t\t\tthis.#handle = undefined\n\t\t}\n\t\tthis.#detach()\n\t\tif (this.#controller.signal.aborted) this.#controller = new AbortController()\n\t\tif (this.#parent !== undefined) {\n\t\t\tthis.#parent.addEventListener('abort', this.#listener, { once: true })\n\t\t}\n\t\tthis.#handle = setTimeout(() => {\n\t\t\tthis.#handle = undefined\n\t\t\tthis.#detach()\n\t\t\tthis.#controller.abort()\n\t\t}, this.#ms)\n\t}\n\n\tclear(): void {\n\t\tif (this.#handle !== undefined) {\n\t\t\tclearTimeout(this.#handle)\n\t\t\tthis.#handle = undefined\n\t\t}\n\t\tthis.#detach()\n\t\tif (this.#controller.signal.aborted) this.#controller = new AbortController()\n\t}\n\n\t#detach(): void {\n\t\tthis.#parent?.removeEventListener('abort', this.#listener)\n\t}\n}\n","import type { TimeoutInterface, TimeoutOptions } from './types.js'\nimport { Timeout } from './Timeout.js'\n\n/**\n * Creates a deadline handle from validated `TimeoutOptions` and returns it as a\n * `TimeoutInterface`.\n *\n * @remarks\n * `options.ms` must be an integer from `0` through `2_147_483_647`, inclusive.\n * A parent `options.signal` clears an armed timeout without aborting the\n * timeout's own signal. `start()` returns without arming when the parent\n * `signal` supplied at construction has already aborted, so a handle whose\n * parent aborted never expires again and its `signal` never fires. Omitted\n * `options.id` values generate a random UUID. Malformed or unreadable options\n * use contract code `bound`; invalid `id`, `ms`, and `signal` values use\n * `literal`, `range`, and `placement`.\n *\n * @param options - Validated deadline, optional trace label, and optional native parent signal\n * @returns A reusable timeout handle\n * @throws {@link import('@orkestrel/contract').ContractError} Thrown when the\n * JavaScript input does not satisfy `TimeoutOptions`\n *\n * @example Race work against a deadline\n * ```ts\n * import { createTimeout } from '@orkestrel/timeout'\n *\n * async function fetchWithDeadline(url: string, ms: number): Promise<Response> {\n * \tconst timeout = createTimeout({ ms })\n * \ttimeout.start()\n *\n * \ttry {\n * \t\treturn await fetch(url, { signal: timeout.signal })\n * \t} finally {\n * \t\ttimeout.clear() // cancels the still-armed deadline when the fetch won the race\n * \t}\n * }\n * ```\n */\nexport function createTimeout(options: TimeoutOptions): TimeoutInterface {\n\treturn new Timeout(options)\n}\n"],"mappings":"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"}
@@ -1,38 +1,46 @@
1
1
  /**
2
- * Create a controllable deadline whose native signal aborts on expiry.
2
+ * Creates a deadline handle from validated `TimeoutOptions` and returns it as a
3
+ * `TimeoutInterface`.
3
4
  *
4
5
  * @remarks
5
6
  * `options.ms` must be an integer from `0` through `2_147_483_647`, inclusive.
6
7
  * A parent `options.signal` clears an armed timeout without aborting the
7
- * timeout's own signal. Omitted `options.id` values generate a random UUID.
8
- * Malformed or unreadable options use contract code `bound`; invalid `id`,
9
- * `ms`, and `signal` values use `literal`, `range`, and `placement`.
8
+ * timeout's own signal. `start()` returns without arming when the parent
9
+ * `signal` supplied at construction has already aborted, so a handle whose
10
+ * parent aborted never expires again and its `signal` never fires. Omitted
11
+ * `options.id` values generate a random UUID. Malformed or unreadable options
12
+ * use contract code `bound`; invalid `id`, `ms`, and `signal` values use
13
+ * `literal`, `range`, and `placement`.
10
14
  *
11
15
  * @param options - Validated deadline, optional trace label, and optional native parent signal
12
16
  * @returns A reusable timeout handle
13
- * @throws {@link import('@orkestrel/contract').ContractError} When the
17
+ * @throws {@link import('@orkestrel/contract').ContractError} Thrown when the
14
18
  * JavaScript input does not satisfy `TimeoutOptions`
15
19
  *
16
- * @example
20
+ * @example Race work against a deadline
17
21
  * ```ts
18
22
  * import { createTimeout } from '@orkestrel/timeout'
19
23
  *
20
- * const timeout = createTimeout({ ms: 5_000 })
21
- * timeout.start()
22
- * try {
23
- * await fetch('/work', { signal: timeout.signal })
24
- * } finally {
25
- * timeout.clear()
24
+ * async function fetchWithDeadline(url: string, ms: number): Promise<Response> {
25
+ * const timeout = createTimeout({ ms })
26
+ * timeout.start()
27
+ *
28
+ * try {
29
+ * return await fetch(url, { signal: timeout.signal })
30
+ * } finally {
31
+ * timeout.clear() // cancels the still-armed deadline when the fetch won the race
32
+ * }
26
33
  * }
27
34
  * ```
28
35
  */
29
36
  export declare function createTimeout(options: TimeoutOptions): TimeoutInterface;
30
37
 
31
38
  /**
32
- * Determine whether a value is an accepted timeout duration.
39
+ * Determines whether a value is an integer in the inclusive range from `0` through
40
+ * `MAX_TIMEOUT_MS`, staying total for every input.
33
41
  *
34
42
  * @param value - Unknown duration candidate
35
- * @returns `true` only for an integer in the inclusive timeout range
43
+ * @returns True if the value is an integer in the inclusive timeout range; false otherwise
36
44
  *
37
45
  * @example
38
46
  * ```ts
@@ -43,15 +51,15 @@
43
51
  export declare function isTimeoutDuration(value: unknown): value is number;
44
52
 
45
53
  /**
46
- * Determine whether a value is a genuine native `AbortSignal`.
54
+ * Determines whether a value is a genuine native `AbortSignal`, staying total for a
55
+ * structural spoof and for a hostile or revoked proxy.
47
56
  *
48
57
  * @remarks
49
- * The intrinsic `aborted` getter performs the native brand check. The boundary
50
- * contains revoked proxies and hostile receivers, returning `false` instead of
51
- * allowing their errors to escape.
58
+ * The intrinsic `aborted` getter performs the native brand check, and the boundary
59
+ * returns `false` rather than letting a receiver's error escape.
52
60
  *
53
61
  * @param value - Unknown signal candidate
54
- * @returns `true` only when the native `AbortSignal` getter accepts the value
62
+ * @returns True if the native `AbortSignal` getter accepts the value; false otherwise
55
63
  *
56
64
  * @example
57
65
  * ```ts
@@ -62,7 +70,7 @@
62
70
  export declare function isTimeoutSignal(value: unknown): value is AbortSignal;
63
71
 
64
72
  /**
65
- * Largest timeout duration accepted by the package, in milliseconds.
73
+ * Names the largest timeout duration the package accepts, `2_147_483_647` milliseconds.
66
74
  *
67
75
  * @example
68
76
  * ```ts
@@ -72,7 +80,8 @@
72
80
  export declare const MAX_TIMEOUT_MS = 2147483647;
73
81
 
74
82
  /**
75
- * A controllable deadline whose native `AbortSignal` aborts when it expires.
83
+ * Implements `TimeoutInterface` exactly, as a controllable `setTimeout` wrapper over one
84
+ * owned `AbortController` whose signal aborts when the deadline expires.
76
85
  *
77
86
  * @remarks
78
87
  * `start()` arms or replaces the current deadline. Expiry sets `expired` and
@@ -98,9 +107,9 @@
98
107
  */
99
108
  export declare class Timeout implements TimeoutInterface {
100
109
  #private;
101
- readonly id: string;
102
- readonly ms: number;
103
110
  constructor(options: TimeoutOptions);
111
+ get id(): string;
112
+ get ms(): number;
104
113
  get signal(): AbortSignal;
105
114
  get expired(): boolean;
106
115
  start(): void;
@@ -108,7 +117,7 @@
108
117
  }
109
118
 
110
119
  /**
111
- * A controllable deadline exposing a native `AbortSignal` that aborts on expiry.
120
+ * Represents a controllable deadline exposing a native `AbortSignal` that aborts on expiry.
112
121
  *
113
122
  * @example
114
123
  * ```ts
@@ -118,22 +127,36 @@
118
127
  * ```
119
128
  */
120
129
  export declare interface TimeoutInterface {
121
- /** Trace label supplied at construction or generated as a random UUID. */
130
+ /** Holds the trace label supplied at construction or generated as a random UUID. */
122
131
  readonly id: string;
123
- /** Validated integer deadline in milliseconds. */
132
+ /** Holds the validated integer deadline in milliseconds. */
124
133
  readonly ms: number;
125
- /** Native signal that aborts once when the current deadline expires. */
134
+ /**
135
+ * Holds the native signal that aborts once when the current deadline
136
+ * expires. A `start()` or `clear()` call after an expiry installs a fresh
137
+ * signal, so a caller holding an earlier reference keeps the aborted one.
138
+ */
126
139
  readonly signal: AbortSignal;
127
- /** Whether the owned signal has aborted, derived directly from that signal. */
140
+ /** Reports whether the owned signal has aborted, derived directly from that signal. */
128
141
  readonly expired: boolean;
129
142
  /**
130
- * Arm or re-arm the deadline.
143
+ * Arms or re-arms the deadline for `ms`, installing a fresh `signal` when the
144
+ * current one has already aborted.
145
+ *
146
+ * @remarks
147
+ * Returns without arming when the parent `signal` supplied at construction
148
+ * has already aborted, so a handle whose parent aborted never expires again
149
+ * and its `signal` never fires.
131
150
  *
132
151
  * @returns Nothing
133
152
  */
134
153
  start(): void;
135
154
  /**
136
- * Cancel an armed deadline without aborting its signal and reset expiry state.
155
+ * Cancels an armed deadline without aborting its `signal`, and resets expiry by
156
+ * installing a fresh signal when the current one has already aborted.
157
+ *
158
+ * @remarks
159
+ * A signal that never fired keeps its identity.
137
160
  *
138
161
  * @returns Nothing
139
162
  */
@@ -141,7 +164,7 @@
141
164
  }
142
165
 
143
166
  /**
144
- * Options for constructing a timeout deadline.
167
+ * Represents the options `createTimeout` and the `Timeout` constructor accept.
145
168
  *
146
169
  * @remarks
147
170
  * `ms` is an integer from `0` through `2_147_483_647`, inclusive. `id`
@@ -155,26 +178,26 @@
155
178
  * ```
156
179
  */
157
180
  export declare interface TimeoutOptions {
158
- /** Trace label for the timeout; omission generates a random UUID. */
181
+ /** Holds the trace label for the timeout; omission generates a random UUID. */
159
182
  readonly id?: string;
160
- /** Integer deadline in milliseconds, inclusive from `0` through `2_147_483_647`. */
183
+ /** Holds the integer deadline in milliseconds, inclusive from `0` through `2_147_483_647`. */
161
184
  readonly ms: number;
162
- /** Native parent signal whose abort clears an armed timeout. */
185
+ /** Holds the native parent signal whose abort clears an armed timeout. */
163
186
  readonly signal?: AbortSignal;
164
187
  }
165
188
 
166
189
  /**
167
- * Validate and normalize timeout construction options.
190
+ * Validates once-read timeout construction options and returns a fresh normalized copy
191
+ * omitting absent optional keys.
168
192
  *
169
193
  * @remarks
170
- * Each property is read exactly once before validation. The returned object is
171
- * a fresh copy and omits absent optional properties. No timer, controller, or
194
+ * Each property is read before any validation runs, and no timer, controller, or
172
195
  * listener lifecycle begins at this boundary.
173
196
  *
174
197
  * @param options - Potentially untrusted timeout options
175
198
  * @returns A fresh validated `TimeoutOptions` object
176
- * @throws {@link import('@orkestrel/contract').ContractError} When the input
177
- * does not satisfy `TimeoutOptions`
199
+ * @throws {@link import('@orkestrel/contract').ContractError} Thrown when the
200
+ * input does not satisfy `TimeoutOptions`
178
201
  *
179
202
  * @example
180
203
  * ```ts
@@ -1,38 +1,46 @@
1
1
  /**
2
- * Create a controllable deadline whose native signal aborts on expiry.
2
+ * Creates a deadline handle from validated `TimeoutOptions` and returns it as a
3
+ * `TimeoutInterface`.
3
4
  *
4
5
  * @remarks
5
6
  * `options.ms` must be an integer from `0` through `2_147_483_647`, inclusive.
6
7
  * A parent `options.signal` clears an armed timeout without aborting the
7
- * timeout's own signal. Omitted `options.id` values generate a random UUID.
8
- * Malformed or unreadable options use contract code `bound`; invalid `id`,
9
- * `ms`, and `signal` values use `literal`, `range`, and `placement`.
8
+ * timeout's own signal. `start()` returns without arming when the parent
9
+ * `signal` supplied at construction has already aborted, so a handle whose
10
+ * parent aborted never expires again and its `signal` never fires. Omitted
11
+ * `options.id` values generate a random UUID. Malformed or unreadable options
12
+ * use contract code `bound`; invalid `id`, `ms`, and `signal` values use
13
+ * `literal`, `range`, and `placement`.
10
14
  *
11
15
  * @param options - Validated deadline, optional trace label, and optional native parent signal
12
16
  * @returns A reusable timeout handle
13
- * @throws {@link import('@orkestrel/contract').ContractError} When the
17
+ * @throws {@link import('@orkestrel/contract').ContractError} Thrown when the
14
18
  * JavaScript input does not satisfy `TimeoutOptions`
15
19
  *
16
- * @example
20
+ * @example Race work against a deadline
17
21
  * ```ts
18
22
  * import { createTimeout } from '@orkestrel/timeout'
19
23
  *
20
- * const timeout = createTimeout({ ms: 5_000 })
21
- * timeout.start()
22
- * try {
23
- * await fetch('/work', { signal: timeout.signal })
24
- * } finally {
25
- * timeout.clear()
24
+ * async function fetchWithDeadline(url: string, ms: number): Promise<Response> {
25
+ * const timeout = createTimeout({ ms })
26
+ * timeout.start()
27
+ *
28
+ * try {
29
+ * return await fetch(url, { signal: timeout.signal })
30
+ * } finally {
31
+ * timeout.clear() // cancels the still-armed deadline when the fetch won the race
32
+ * }
26
33
  * }
27
34
  * ```
28
35
  */
29
36
  export declare function createTimeout(options: TimeoutOptions): TimeoutInterface;
30
37
 
31
38
  /**
32
- * Determine whether a value is an accepted timeout duration.
39
+ * Determines whether a value is an integer in the inclusive range from `0` through
40
+ * `MAX_TIMEOUT_MS`, staying total for every input.
33
41
  *
34
42
  * @param value - Unknown duration candidate
35
- * @returns `true` only for an integer in the inclusive timeout range
43
+ * @returns True if the value is an integer in the inclusive timeout range; false otherwise
36
44
  *
37
45
  * @example
38
46
  * ```ts
@@ -43,15 +51,15 @@
43
51
  export declare function isTimeoutDuration(value: unknown): value is number;
44
52
 
45
53
  /**
46
- * Determine whether a value is a genuine native `AbortSignal`.
54
+ * Determines whether a value is a genuine native `AbortSignal`, staying total for a
55
+ * structural spoof and for a hostile or revoked proxy.
47
56
  *
48
57
  * @remarks
49
- * The intrinsic `aborted` getter performs the native brand check. The boundary
50
- * contains revoked proxies and hostile receivers, returning `false` instead of
51
- * allowing their errors to escape.
58
+ * The intrinsic `aborted` getter performs the native brand check, and the boundary
59
+ * returns `false` rather than letting a receiver's error escape.
52
60
  *
53
61
  * @param value - Unknown signal candidate
54
- * @returns `true` only when the native `AbortSignal` getter accepts the value
62
+ * @returns True if the native `AbortSignal` getter accepts the value; false otherwise
55
63
  *
56
64
  * @example
57
65
  * ```ts
@@ -62,7 +70,7 @@
62
70
  export declare function isTimeoutSignal(value: unknown): value is AbortSignal;
63
71
 
64
72
  /**
65
- * Largest timeout duration accepted by the package, in milliseconds.
73
+ * Names the largest timeout duration the package accepts, `2_147_483_647` milliseconds.
66
74
  *
67
75
  * @example
68
76
  * ```ts
@@ -72,7 +80,8 @@
72
80
  export declare const MAX_TIMEOUT_MS = 2147483647;
73
81
 
74
82
  /**
75
- * A controllable deadline whose native `AbortSignal` aborts when it expires.
83
+ * Implements `TimeoutInterface` exactly, as a controllable `setTimeout` wrapper over one
84
+ * owned `AbortController` whose signal aborts when the deadline expires.
76
85
  *
77
86
  * @remarks
78
87
  * `start()` arms or replaces the current deadline. Expiry sets `expired` and
@@ -98,9 +107,9 @@
98
107
  */
99
108
  export declare class Timeout implements TimeoutInterface {
100
109
  #private;
101
- readonly id: string;
102
- readonly ms: number;
103
110
  constructor(options: TimeoutOptions);
111
+ get id(): string;
112
+ get ms(): number;
104
113
  get signal(): AbortSignal;
105
114
  get expired(): boolean;
106
115
  start(): void;
@@ -108,7 +117,7 @@
108
117
  }
109
118
 
110
119
  /**
111
- * A controllable deadline exposing a native `AbortSignal` that aborts on expiry.
120
+ * Represents a controllable deadline exposing a native `AbortSignal` that aborts on expiry.
112
121
  *
113
122
  * @example
114
123
  * ```ts
@@ -118,22 +127,36 @@
118
127
  * ```
119
128
  */
120
129
  export declare interface TimeoutInterface {
121
- /** Trace label supplied at construction or generated as a random UUID. */
130
+ /** Holds the trace label supplied at construction or generated as a random UUID. */
122
131
  readonly id: string;
123
- /** Validated integer deadline in milliseconds. */
132
+ /** Holds the validated integer deadline in milliseconds. */
124
133
  readonly ms: number;
125
- /** Native signal that aborts once when the current deadline expires. */
134
+ /**
135
+ * Holds the native signal that aborts once when the current deadline
136
+ * expires. A `start()` or `clear()` call after an expiry installs a fresh
137
+ * signal, so a caller holding an earlier reference keeps the aborted one.
138
+ */
126
139
  readonly signal: AbortSignal;
127
- /** Whether the owned signal has aborted, derived directly from that signal. */
140
+ /** Reports whether the owned signal has aborted, derived directly from that signal. */
128
141
  readonly expired: boolean;
129
142
  /**
130
- * Arm or re-arm the deadline.
143
+ * Arms or re-arms the deadline for `ms`, installing a fresh `signal` when the
144
+ * current one has already aborted.
145
+ *
146
+ * @remarks
147
+ * Returns without arming when the parent `signal` supplied at construction
148
+ * has already aborted, so a handle whose parent aborted never expires again
149
+ * and its `signal` never fires.
131
150
  *
132
151
  * @returns Nothing
133
152
  */
134
153
  start(): void;
135
154
  /**
136
- * Cancel an armed deadline without aborting its signal and reset expiry state.
155
+ * Cancels an armed deadline without aborting its `signal`, and resets expiry by
156
+ * installing a fresh signal when the current one has already aborted.
157
+ *
158
+ * @remarks
159
+ * A signal that never fired keeps its identity.
137
160
  *
138
161
  * @returns Nothing
139
162
  */
@@ -141,7 +164,7 @@
141
164
  }
142
165
 
143
166
  /**
144
- * Options for constructing a timeout deadline.
167
+ * Represents the options `createTimeout` and the `Timeout` constructor accept.
145
168
  *
146
169
  * @remarks
147
170
  * `ms` is an integer from `0` through `2_147_483_647`, inclusive. `id`
@@ -155,26 +178,26 @@
155
178
  * ```
156
179
  */
157
180
  export declare interface TimeoutOptions {
158
- /** Trace label for the timeout; omission generates a random UUID. */
181
+ /** Holds the trace label for the timeout; omission generates a random UUID. */
159
182
  readonly id?: string;
160
- /** Integer deadline in milliseconds, inclusive from `0` through `2_147_483_647`. */
183
+ /** Holds the integer deadline in milliseconds, inclusive from `0` through `2_147_483_647`. */
161
184
  readonly ms: number;
162
- /** Native parent signal whose abort clears an armed timeout. */
185
+ /** Holds the native parent signal whose abort clears an armed timeout. */
163
186
  readonly signal?: AbortSignal;
164
187
  }
165
188
 
166
189
  /**
167
- * Validate and normalize timeout construction options.
190
+ * Validates once-read timeout construction options and returns a fresh normalized copy
191
+ * omitting absent optional keys.
168
192
  *
169
193
  * @remarks
170
- * Each property is read exactly once before validation. The returned object is
171
- * a fresh copy and omits absent optional properties. No timer, controller, or
194
+ * Each property is read before any validation runs, and no timer, controller, or
172
195
  * listener lifecycle begins at this boundary.
173
196
  *
174
197
  * @param options - Potentially untrusted timeout options
175
198
  * @returns A fresh validated `TimeoutOptions` object
176
- * @throws {@link import('@orkestrel/contract').ContractError} When the input
177
- * does not satisfy `TimeoutOptions`
199
+ * @throws {@link import('@orkestrel/contract').ContractError} Thrown when the
200
+ * input does not satisfy `TimeoutOptions`
178
201
  *
179
202
  * @example
180
203
  * ```ts
@@ -1,7 +1,7 @@
1
- import { ContractError, isFiniteNumber, isInteger, isRecord, isString, preview } from "@orkestrel/contract";
1
+ import { ContractError, isInteger, isRecord, isString, preview } from "@orkestrel/contract";
2
2
  //#region src/core/constants.ts
3
3
  /**
4
- * Largest timeout duration accepted by the package, in milliseconds.
4
+ * Names the largest timeout duration the package accepts, `2_147_483_647` milliseconds.
5
5
  *
6
6
  * @example
7
7
  * ```ts
@@ -12,10 +12,11 @@ var MAX_TIMEOUT_MS = 2147483647;
12
12
  //#endregion
13
13
  //#region src/core/validators.ts
14
14
  /**
15
- * Determine whether a value is an accepted timeout duration.
15
+ * Determines whether a value is an integer in the inclusive range from `0` through
16
+ * `MAX_TIMEOUT_MS`, staying total for every input.
16
17
  *
17
18
  * @param value - Unknown duration candidate
18
- * @returns `true` only for an integer in the inclusive timeout range
19
+ * @returns True if the value is an integer in the inclusive timeout range; false otherwise
19
20
  *
20
21
  * @example
21
22
  * ```ts
@@ -24,18 +25,18 @@ var MAX_TIMEOUT_MS = 2147483647;
24
25
  * ```
25
26
  */
26
27
  function isTimeoutDuration(value) {
27
- return isFiniteNumber(value) && isInteger(value) && value >= 0 && value <= 2147483647;
28
+ return isInteger(value) && value >= 0 && value <= 2147483647;
28
29
  }
29
30
  /**
30
- * Determine whether a value is a genuine native `AbortSignal`.
31
+ * Determines whether a value is a genuine native `AbortSignal`, staying total for a
32
+ * structural spoof and for a hostile or revoked proxy.
31
33
  *
32
34
  * @remarks
33
- * The intrinsic `aborted` getter performs the native brand check. The boundary
34
- * contains revoked proxies and hostile receivers, returning `false` instead of
35
- * allowing their errors to escape.
35
+ * The intrinsic `aborted` getter performs the native brand check, and the boundary
36
+ * returns `false` rather than letting a receiver's error escape.
36
37
  *
37
38
  * @param value - Unknown signal candidate
38
- * @returns `true` only when the native `AbortSignal` getter accepts the value
39
+ * @returns True if the native `AbortSignal` getter accepts the value; false otherwise
39
40
  *
40
41
  * @example
41
42
  * ```ts
@@ -55,17 +56,17 @@ function isTimeoutSignal(value) {
55
56
  //#endregion
56
57
  //#region src/core/helpers.ts
57
58
  /**
58
- * Validate and normalize timeout construction options.
59
+ * Validates once-read timeout construction options and returns a fresh normalized copy
60
+ * omitting absent optional keys.
59
61
  *
60
62
  * @remarks
61
- * Each property is read exactly once before validation. The returned object is
62
- * a fresh copy and omits absent optional properties. No timer, controller, or
63
+ * Each property is read before any validation runs, and no timer, controller, or
63
64
  * listener lifecycle begins at this boundary.
64
65
  *
65
66
  * @param options - Potentially untrusted timeout options
66
67
  * @returns A fresh validated `TimeoutOptions` object
67
- * @throws {@link import('@orkestrel/contract').ContractError} When the input
68
- * does not satisfy `TimeoutOptions`
68
+ * @throws {@link import('@orkestrel/contract').ContractError} Thrown when the
69
+ * input does not satisfy `TimeoutOptions`
69
70
  *
70
71
  * @example
71
72
  * ```ts
@@ -141,7 +142,8 @@ function validateTimeoutOptions(options) {
141
142
  //#endregion
142
143
  //#region src/core/Timeout.ts
143
144
  /**
144
- * A controllable deadline whose native `AbortSignal` aborts when it expires.
145
+ * Implements `TimeoutInterface` exactly, as a controllable `setTimeout` wrapper over one
146
+ * owned `AbortController` whose signal aborts when the deadline expires.
145
147
  *
146
148
  * @remarks
147
149
  * `start()` arms or replaces the current deadline. Expiry sets `expired` and
@@ -166,21 +168,26 @@ function validateTimeoutOptions(options) {
166
168
  * ```
167
169
  */
168
170
  var Timeout = class {
169
- id;
170
- ms;
171
+ #id;
172
+ #ms;
171
173
  #parent;
172
174
  #listener;
173
175
  #controller;
174
176
  #handle;
175
- #linked = false;
176
177
  constructor(options) {
177
178
  const input = validateTimeoutOptions(options);
178
- this.id = input.id === void 0 ? crypto.randomUUID() : input.id;
179
- this.ms = input.ms;
179
+ this.#id = input.id === void 0 ? crypto.randomUUID() : input.id;
180
+ this.#ms = input.ms;
180
181
  this.#parent = input.signal;
181
182
  this.#controller = new AbortController();
182
183
  this.#listener = this.clear.bind(this);
183
184
  }
185
+ get id() {
186
+ return this.#id;
187
+ }
188
+ get ms() {
189
+ return this.#ms;
190
+ }
184
191
  get signal() {
185
192
  return this.#controller.signal;
186
193
  }
@@ -195,15 +202,12 @@ var Timeout = class {
195
202
  }
196
203
  this.#detach();
197
204
  if (this.#controller.signal.aborted) this.#controller = new AbortController();
198
- if (this.#parent !== void 0) {
199
- this.#parent.addEventListener("abort", this.#listener, { once: true });
200
- this.#linked = true;
201
- }
205
+ if (this.#parent !== void 0) this.#parent.addEventListener("abort", this.#listener, { once: true });
202
206
  this.#handle = setTimeout(() => {
203
207
  this.#handle = void 0;
204
208
  this.#detach();
205
209
  this.#controller.abort();
206
- }, this.ms);
210
+ }, this.#ms);
207
211
  }
208
212
  clear() {
209
213
  if (this.#handle !== void 0) {
@@ -214,39 +218,43 @@ var Timeout = class {
214
218
  if (this.#controller.signal.aborted) this.#controller = new AbortController();
215
219
  }
216
220
  #detach() {
217
- if (this.#linked) {
218
- this.#parent?.removeEventListener("abort", this.#listener);
219
- this.#linked = false;
220
- }
221
+ this.#parent?.removeEventListener("abort", this.#listener);
221
222
  }
222
223
  };
223
224
  //#endregion
224
225
  //#region src/core/factories.ts
225
226
  /**
226
- * Create a controllable deadline whose native signal aborts on expiry.
227
+ * Creates a deadline handle from validated `TimeoutOptions` and returns it as a
228
+ * `TimeoutInterface`.
227
229
  *
228
230
  * @remarks
229
231
  * `options.ms` must be an integer from `0` through `2_147_483_647`, inclusive.
230
232
  * A parent `options.signal` clears an armed timeout without aborting the
231
- * timeout's own signal. Omitted `options.id` values generate a random UUID.
232
- * Malformed or unreadable options use contract code `bound`; invalid `id`,
233
- * `ms`, and `signal` values use `literal`, `range`, and `placement`.
233
+ * timeout's own signal. `start()` returns without arming when the parent
234
+ * `signal` supplied at construction has already aborted, so a handle whose
235
+ * parent aborted never expires again and its `signal` never fires. Omitted
236
+ * `options.id` values generate a random UUID. Malformed or unreadable options
237
+ * use contract code `bound`; invalid `id`, `ms`, and `signal` values use
238
+ * `literal`, `range`, and `placement`.
234
239
  *
235
240
  * @param options - Validated deadline, optional trace label, and optional native parent signal
236
241
  * @returns A reusable timeout handle
237
- * @throws {@link import('@orkestrel/contract').ContractError} When the
242
+ * @throws {@link import('@orkestrel/contract').ContractError} Thrown when the
238
243
  * JavaScript input does not satisfy `TimeoutOptions`
239
244
  *
240
- * @example
245
+ * @example Race work against a deadline
241
246
  * ```ts
242
247
  * import { createTimeout } from '@orkestrel/timeout'
243
248
  *
244
- * const timeout = createTimeout({ ms: 5_000 })
245
- * timeout.start()
246
- * try {
247
- * await fetch('/work', { signal: timeout.signal })
248
- * } finally {
249
- * timeout.clear()
249
+ * async function fetchWithDeadline(url: string, ms: number): Promise<Response> {
250
+ * const timeout = createTimeout({ ms })
251
+ * timeout.start()
252
+ *
253
+ * try {
254
+ * return await fetch(url, { signal: timeout.signal })
255
+ * } finally {
256
+ * timeout.clear() // cancels the still-armed deadline when the fetch won the race
257
+ * }
250
258
  * }
251
259
  * ```
252
260
  */
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["#parent","#listener","#controller","#handle","#detach","#linked"],"sources":["../../../src/core/constants.ts","../../../src/core/validators.ts","../../../src/core/helpers.ts","../../../src/core/Timeout.ts","../../../src/core/factories.ts"],"sourcesContent":["/**\n * Largest timeout duration accepted by the package, in milliseconds.\n *\n * @example\n * ```ts\n * createTimeout({ ms: MAX_TIMEOUT_MS })\n * ```\n */\nexport const MAX_TIMEOUT_MS = 2_147_483_647\n","import { isFiniteNumber, isInteger } from '@orkestrel/contract'\nimport { MAX_TIMEOUT_MS } from './constants.js'\n\n/**\n * Determine whether a value is an accepted timeout duration.\n *\n * @param value - Unknown duration candidate\n * @returns `true` only for an integer in the inclusive timeout range\n *\n * @example\n * ```ts\n * isTimeoutDuration(0) // true\n * isTimeoutDuration(1.5) // false\n * ```\n */\nexport function isTimeoutDuration(value: unknown): value is number {\n\treturn isFiniteNumber(value) && isInteger(value) && value >= 0 && value <= MAX_TIMEOUT_MS\n}\n\n/**\n * Determine whether a value is a genuine native `AbortSignal`.\n *\n * @remarks\n * The intrinsic `aborted` getter performs the native brand check. The boundary\n * contains revoked proxies and hostile receivers, returning `false` instead of\n * allowing their errors to escape.\n *\n * @param value - Unknown signal candidate\n * @returns `true` only when the native `AbortSignal` getter accepts the value\n *\n * @example\n * ```ts\n * isTimeoutSignal(new AbortController().signal) // true\n * isTimeoutSignal({ aborted: false }) // false\n * ```\n */\nexport function isTimeoutSignal(value: unknown): value is AbortSignal {\n\ttry {\n\t\tconst descriptor = Object.getOwnPropertyDescriptor(AbortSignal.prototype, 'aborted')\n\t\tif (descriptor?.get === undefined) return false\n\t\treturn typeof Reflect.apply(descriptor.get, value, []) === 'boolean'\n\t} catch {\n\t\treturn false\n\t}\n}\n","import type { TimeoutOptions } from './types.js'\nimport { ContractError, isRecord, isString, preview } from '@orkestrel/contract'\nimport { MAX_TIMEOUT_MS } from './constants.js'\nimport { isTimeoutDuration, isTimeoutSignal } from './validators.js'\n\n/**\n * Validate and normalize timeout construction options.\n *\n * @remarks\n * Each property is read exactly once before validation. The returned object is\n * a fresh copy and omits absent optional properties. No timer, controller, or\n * listener lifecycle begins at this boundary.\n *\n * @param options - Potentially untrusted timeout options\n * @returns A fresh validated `TimeoutOptions` object\n * @throws {@link import('@orkestrel/contract').ContractError} When the input\n * does not satisfy `TimeoutOptions`\n *\n * @example\n * ```ts\n * const options = validateTimeoutOptions({ ms: 5_000 })\n * ```\n */\nexport function validateTimeoutOptions(options: TimeoutOptions): TimeoutOptions {\n\tif (!isRecord(options)) {\n\t\tthrow new ContractError('Timeout: options must be a plain record', {\n\t\t\tcode: 'bound',\n\t\t\tcontext: {\n\t\t\t\tpath: ['options'],\n\t\t\t\tlimit: 'plain record',\n\t\t\t\treceived: preview(options),\n\t\t\t},\n\t\t})\n\t}\n\n\tlet id: TimeoutOptions['id']\n\tlet ms: TimeoutOptions['ms']\n\tlet signal: TimeoutOptions['signal']\n\ttry {\n\t\tid = options.id\n\t\tms = options.ms\n\t\tsignal = options.signal\n\t} catch (cause) {\n\t\tthrow new ContractError('Timeout: options could not be read', {\n\t\t\tcode: 'bound',\n\t\t\tcontext: {\n\t\t\t\tpath: ['options'],\n\t\t\t\tlimit: 'readable plain record',\n\t\t\t\treceived: preview(options),\n\t\t\t},\n\t\t\tcause,\n\t\t})\n\t}\n\n\tif (id !== undefined && !isString(id)) {\n\t\tthrow new ContractError('Timeout: id must be a string when defined', {\n\t\t\tcode: 'literal',\n\t\t\tcontext: {\n\t\t\t\tpath: ['options', 'id'],\n\t\t\t\tlimit: 'string or undefined',\n\t\t\t\treceived: preview(id),\n\t\t\t},\n\t\t})\n\t}\n\tif (!isTimeoutDuration(ms)) {\n\t\tthrow new ContractError('Timeout: ms must be an integer in the supported range', {\n\t\t\tcode: 'range',\n\t\t\tcontext: {\n\t\t\t\tpath: ['options', 'ms'],\n\t\t\t\tlimit: `integer in [0, ${MAX_TIMEOUT_MS}]`,\n\t\t\t\treceived: preview(ms),\n\t\t\t},\n\t\t})\n\t}\n\tif (signal !== undefined && !isTimeoutSignal(signal)) {\n\t\tthrow new ContractError('Timeout: signal must be a native AbortSignal when defined', {\n\t\t\tcode: 'placement',\n\t\t\tcontext: {\n\t\t\t\tpath: ['options', 'signal'],\n\t\t\t\tlimit: 'native AbortSignal or undefined',\n\t\t\t\treceived: preview(signal),\n\t\t\t},\n\t\t})\n\t}\n\n\tif (id !== undefined && signal !== undefined) return { id, ms, signal }\n\tif (id !== undefined) return { id, ms }\n\tif (signal !== undefined) return { ms, signal }\n\treturn { ms }\n}\n","import type { TimeoutInterface, TimeoutOptions } from './types.js'\nimport { validateTimeoutOptions } from './helpers.js'\n\n/**\n * A controllable deadline whose native `AbortSignal` aborts when it expires.\n *\n * @remarks\n * `start()` arms or replaces the current deadline. Expiry sets `expired` and\n * aborts the current signal once. `clear()` cancels without aborting and resets\n * `expired`. Starting or clearing after expiry installs a fresh signal. A parent\n * abort deliberately clears the timeout without aborting its signal; once the\n * parent has aborted, later starts are inert.\n *\n * Construction validates the complete JavaScript boundary before creating an\n * `AbortController`. A malformed or unreadable options record throws a\n * `bound`-coded {@link import('@orkestrel/contract').ContractError}; an invalid\n * `id`, `ms`, or `signal` uses `literal`, `range`, or `placement`, respectively.\n *\n * @example\n * ```ts\n * import { Timeout } from '@orkestrel/timeout'\n *\n * const timeout = new Timeout({ ms: 5_000 })\n * timeout.start()\n * timeout.signal.addEventListener('abort', () => giveUp(), { once: true })\n * timeout.clear()\n * ```\n */\nexport class Timeout implements TimeoutInterface {\n\treadonly id: string\n\treadonly ms: number\n\treadonly #parent: AbortSignal | undefined\n\treadonly #listener: () => void\n\t#controller: AbortController\n\t#handle: ReturnType<typeof setTimeout> | undefined\n\t#linked = false\n\n\tconstructor(options: TimeoutOptions) {\n\t\tconst input = validateTimeoutOptions(options)\n\t\tthis.id = input.id === undefined ? crypto.randomUUID() : input.id\n\t\tthis.ms = input.ms\n\t\tthis.#parent = input.signal\n\t\tthis.#controller = new AbortController()\n\t\tthis.#listener = this.clear.bind(this)\n\t}\n\n\tget signal(): AbortSignal {\n\t\treturn this.#controller.signal\n\t}\n\n\tget expired(): boolean {\n\t\treturn this.#controller.signal.aborted\n\t}\n\n\tstart(): void {\n\t\tif (this.#parent?.aborted === true) return\n\t\tif (this.#handle !== undefined) {\n\t\t\tclearTimeout(this.#handle)\n\t\t\tthis.#handle = undefined\n\t\t}\n\t\tthis.#detach()\n\t\tif (this.#controller.signal.aborted) this.#controller = new AbortController()\n\t\tif (this.#parent !== undefined) {\n\t\t\tthis.#parent.addEventListener('abort', this.#listener, { once: true })\n\t\t\tthis.#linked = true\n\t\t}\n\t\tthis.#handle = setTimeout(() => {\n\t\t\tthis.#handle = undefined\n\t\t\tthis.#detach()\n\t\t\tthis.#controller.abort()\n\t\t}, this.ms)\n\t}\n\n\tclear(): void {\n\t\tif (this.#handle !== undefined) {\n\t\t\tclearTimeout(this.#handle)\n\t\t\tthis.#handle = undefined\n\t\t}\n\t\tthis.#detach()\n\t\tif (this.#controller.signal.aborted) this.#controller = new AbortController()\n\t}\n\n\t#detach(): void {\n\t\tif (this.#linked) {\n\t\t\tthis.#parent?.removeEventListener('abort', this.#listener)\n\t\t\tthis.#linked = false\n\t\t}\n\t}\n}\n","import type { TimeoutInterface, TimeoutOptions } from './types.js'\nimport { Timeout } from './Timeout.js'\n\n/**\n * Create a controllable deadline whose native signal aborts on expiry.\n *\n * @remarks\n * `options.ms` must be an integer from `0` through `2_147_483_647`, inclusive.\n * A parent `options.signal` clears an armed timeout without aborting the\n * timeout's own signal. Omitted `options.id` values generate a random UUID.\n * Malformed or unreadable options use contract code `bound`; invalid `id`,\n * `ms`, and `signal` values use `literal`, `range`, and `placement`.\n *\n * @param options - Validated deadline, optional trace label, and optional native parent signal\n * @returns A reusable timeout handle\n * @throws {@link import('@orkestrel/contract').ContractError} When the\n * JavaScript input does not satisfy `TimeoutOptions`\n *\n * @example\n * ```ts\n * import { createTimeout } from '@orkestrel/timeout'\n *\n * const timeout = createTimeout({ ms: 5_000 })\n * timeout.start()\n * try {\n * \tawait fetch('/work', { signal: timeout.signal })\n * } finally {\n * \ttimeout.clear()\n * }\n * ```\n */\nexport function createTimeout(options: TimeoutOptions): TimeoutInterface {\n\treturn new Timeout(options)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/core/constants.ts","../../../src/core/validators.ts","../../../src/core/helpers.ts","../../../src/core/Timeout.ts","../../../src/core/factories.ts"],"sourcesContent":["/**\n * Names the largest timeout duration the package accepts, `2_147_483_647` milliseconds.\n *\n * @example\n * ```ts\n * createTimeout({ ms: MAX_TIMEOUT_MS })\n * ```\n */\nexport const MAX_TIMEOUT_MS = 2_147_483_647\n","import { isInteger } from '@orkestrel/contract'\nimport { MAX_TIMEOUT_MS } from './constants.js'\n\n/**\n * Determines whether a value is an integer in the inclusive range from `0` through\n * `MAX_TIMEOUT_MS`, staying total for every input.\n *\n * @param value - Unknown duration candidate\n * @returns True if the value is an integer in the inclusive timeout range; false otherwise\n *\n * @example\n * ```ts\n * isTimeoutDuration(0) // true\n * isTimeoutDuration(1.5) // false\n * ```\n */\nexport function isTimeoutDuration(value: unknown): value is number {\n\treturn isInteger(value) && value >= 0 && value <= MAX_TIMEOUT_MS\n}\n\n/**\n * Determines whether a value is a genuine native `AbortSignal`, staying total for a\n * structural spoof and for a hostile or revoked proxy.\n *\n * @remarks\n * The intrinsic `aborted` getter performs the native brand check, and the boundary\n * returns `false` rather than letting a receiver's error escape.\n *\n * @param value - Unknown signal candidate\n * @returns True if the native `AbortSignal` getter accepts the value; false otherwise\n *\n * @example\n * ```ts\n * isTimeoutSignal(new AbortController().signal) // true\n * isTimeoutSignal({ aborted: false }) // false\n * ```\n */\nexport function isTimeoutSignal(value: unknown): value is AbortSignal {\n\ttry {\n\t\tconst descriptor = Object.getOwnPropertyDescriptor(AbortSignal.prototype, 'aborted')\n\t\tif (descriptor?.get === undefined) return false\n\t\treturn typeof Reflect.apply(descriptor.get, value, []) === 'boolean'\n\t} catch {\n\t\treturn false\n\t}\n}\n","import type { TimeoutOptions } from './types.js'\nimport { ContractError, isRecord, isString, preview } from '@orkestrel/contract'\nimport { MAX_TIMEOUT_MS } from './constants.js'\nimport { isTimeoutDuration, isTimeoutSignal } from './validators.js'\n\n/**\n * Validates once-read timeout construction options and returns a fresh normalized copy\n * omitting absent optional keys.\n *\n * @remarks\n * Each property is read before any validation runs, and no timer, controller, or\n * listener lifecycle begins at this boundary.\n *\n * @param options - Potentially untrusted timeout options\n * @returns A fresh validated `TimeoutOptions` object\n * @throws {@link import('@orkestrel/contract').ContractError} Thrown when the\n * input does not satisfy `TimeoutOptions`\n *\n * @example\n * ```ts\n * const options = validateTimeoutOptions({ ms: 5_000 })\n * ```\n */\nexport function validateTimeoutOptions(options: TimeoutOptions): TimeoutOptions {\n\tif (!isRecord(options)) {\n\t\tthrow new ContractError('Timeout: options must be a plain record', {\n\t\t\tcode: 'bound',\n\t\t\tcontext: {\n\t\t\t\tpath: ['options'],\n\t\t\t\tlimit: 'plain record',\n\t\t\t\treceived: preview(options),\n\t\t\t},\n\t\t})\n\t}\n\n\tlet id: TimeoutOptions['id']\n\tlet ms: TimeoutOptions['ms']\n\tlet signal: TimeoutOptions['signal']\n\ttry {\n\t\tid = options.id\n\t\tms = options.ms\n\t\tsignal = options.signal\n\t} catch (cause) {\n\t\tthrow new ContractError('Timeout: options could not be read', {\n\t\t\tcode: 'bound',\n\t\t\tcontext: {\n\t\t\t\tpath: ['options'],\n\t\t\t\tlimit: 'readable plain record',\n\t\t\t\treceived: preview(options),\n\t\t\t},\n\t\t\tcause,\n\t\t})\n\t}\n\n\tif (id !== undefined && !isString(id)) {\n\t\tthrow new ContractError('Timeout: id must be a string when defined', {\n\t\t\tcode: 'literal',\n\t\t\tcontext: {\n\t\t\t\tpath: ['options', 'id'],\n\t\t\t\tlimit: 'string or undefined',\n\t\t\t\treceived: preview(id),\n\t\t\t},\n\t\t})\n\t}\n\tif (!isTimeoutDuration(ms)) {\n\t\tthrow new ContractError('Timeout: ms must be an integer in the supported range', {\n\t\t\tcode: 'range',\n\t\t\tcontext: {\n\t\t\t\tpath: ['options', 'ms'],\n\t\t\t\tlimit: `integer in [0, ${MAX_TIMEOUT_MS}]`,\n\t\t\t\treceived: preview(ms),\n\t\t\t},\n\t\t})\n\t}\n\tif (signal !== undefined && !isTimeoutSignal(signal)) {\n\t\tthrow new ContractError('Timeout: signal must be a native AbortSignal when defined', {\n\t\t\tcode: 'placement',\n\t\t\tcontext: {\n\t\t\t\tpath: ['options', 'signal'],\n\t\t\t\tlimit: 'native AbortSignal or undefined',\n\t\t\t\treceived: preview(signal),\n\t\t\t},\n\t\t})\n\t}\n\n\tif (id !== undefined && signal !== undefined) return { id, ms, signal }\n\tif (id !== undefined) return { id, ms }\n\tif (signal !== undefined) return { ms, signal }\n\treturn { ms }\n}\n","import type { TimeoutInterface, TimeoutOptions } from './types.js'\nimport { validateTimeoutOptions } from './helpers.js'\n\n/**\n * Implements `TimeoutInterface` exactly, as a controllable `setTimeout` wrapper over one\n * owned `AbortController` whose signal aborts when the deadline expires.\n *\n * @remarks\n * `start()` arms or replaces the current deadline. Expiry sets `expired` and\n * aborts the current signal once. `clear()` cancels without aborting and resets\n * `expired`. Starting or clearing after expiry installs a fresh signal. A parent\n * abort deliberately clears the timeout without aborting its signal; once the\n * parent has aborted, later starts are inert.\n *\n * Construction validates the complete JavaScript boundary before creating an\n * `AbortController`. A malformed or unreadable options record throws a\n * `bound`-coded {@link import('@orkestrel/contract').ContractError}; an invalid\n * `id`, `ms`, or `signal` uses `literal`, `range`, or `placement`, respectively.\n *\n * @example\n * ```ts\n * import { Timeout } from '@orkestrel/timeout'\n *\n * const timeout = new Timeout({ ms: 5_000 })\n * timeout.start()\n * timeout.signal.addEventListener('abort', () => giveUp(), { once: true })\n * timeout.clear()\n * ```\n */\nexport class Timeout implements TimeoutInterface {\n\treadonly #id: string\n\treadonly #ms: number\n\treadonly #parent: AbortSignal | undefined\n\treadonly #listener: () => void\n\t#controller: AbortController\n\t#handle: ReturnType<typeof setTimeout> | undefined\n\n\tconstructor(options: TimeoutOptions) {\n\t\tconst input = validateTimeoutOptions(options)\n\t\tthis.#id = input.id === undefined ? crypto.randomUUID() : input.id\n\t\tthis.#ms = input.ms\n\t\tthis.#parent = input.signal\n\t\tthis.#controller = new AbortController()\n\t\tthis.#listener = this.clear.bind(this)\n\t}\n\n\tget id(): string {\n\t\treturn this.#id\n\t}\n\n\tget ms(): number {\n\t\treturn this.#ms\n\t}\n\n\tget signal(): AbortSignal {\n\t\treturn this.#controller.signal\n\t}\n\n\tget expired(): boolean {\n\t\treturn this.#controller.signal.aborted\n\t}\n\n\tstart(): void {\n\t\tif (this.#parent?.aborted === true) return\n\t\tif (this.#handle !== undefined) {\n\t\t\tclearTimeout(this.#handle)\n\t\t\tthis.#handle = undefined\n\t\t}\n\t\tthis.#detach()\n\t\tif (this.#controller.signal.aborted) this.#controller = new AbortController()\n\t\tif (this.#parent !== undefined) {\n\t\t\tthis.#parent.addEventListener('abort', this.#listener, { once: true })\n\t\t}\n\t\tthis.#handle = setTimeout(() => {\n\t\t\tthis.#handle = undefined\n\t\t\tthis.#detach()\n\t\t\tthis.#controller.abort()\n\t\t}, this.#ms)\n\t}\n\n\tclear(): void {\n\t\tif (this.#handle !== undefined) {\n\t\t\tclearTimeout(this.#handle)\n\t\t\tthis.#handle = undefined\n\t\t}\n\t\tthis.#detach()\n\t\tif (this.#controller.signal.aborted) this.#controller = new AbortController()\n\t}\n\n\t#detach(): void {\n\t\tthis.#parent?.removeEventListener('abort', this.#listener)\n\t}\n}\n","import type { TimeoutInterface, TimeoutOptions } from './types.js'\nimport { Timeout } from './Timeout.js'\n\n/**\n * Creates a deadline handle from validated `TimeoutOptions` and returns it as a\n * `TimeoutInterface`.\n *\n * @remarks\n * `options.ms` must be an integer from `0` through `2_147_483_647`, inclusive.\n * A parent `options.signal` clears an armed timeout without aborting the\n * timeout's own signal. `start()` returns without arming when the parent\n * `signal` supplied at construction has already aborted, so a handle whose\n * parent aborted never expires again and its `signal` never fires. Omitted\n * `options.id` values generate a random UUID. Malformed or unreadable options\n * use contract code `bound`; invalid `id`, `ms`, and `signal` values use\n * `literal`, `range`, and `placement`.\n *\n * @param options - Validated deadline, optional trace label, and optional native parent signal\n * @returns A reusable timeout handle\n * @throws {@link import('@orkestrel/contract').ContractError} Thrown when the\n * JavaScript input does not satisfy `TimeoutOptions`\n *\n * @example Race work against a deadline\n * ```ts\n * import { createTimeout } from '@orkestrel/timeout'\n *\n * async function fetchWithDeadline(url: string, ms: number): Promise<Response> {\n * \tconst timeout = createTimeout({ ms })\n * \ttimeout.start()\n *\n * \ttry {\n * \t\treturn await fetch(url, { signal: timeout.signal })\n * \t} finally {\n * \t\ttimeout.clear() // cancels the still-armed deadline when the fetch won the race\n * \t}\n * }\n * ```\n */\nexport function createTimeout(options: TimeoutOptions): TimeoutInterface {\n\treturn new Timeout(options)\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orkestrel/timeout",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
4
4
  "description": "A typed, controllable setTimeout wrapper — an AbortSignal deadline with start/clear lifecycle and parent-signal linking. Part of the @orkestrel line.",
5
5
  "keywords": [
6
6
  "abort-signal",
@@ -46,40 +46,43 @@
46
46
  "clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
47
47
  "copy": "node -e \"const fs=require('node:fs'),p=require('node:path'),a=process.argv[1],b=process.argv[2];fs.mkdirSync(p.dirname(b),{recursive:true});fs.cpSync(a,b,{force:true});console.log('Copied: '+a+' to '+b)\"",
48
48
  "scaffold": "scaffold",
49
- "lint": "oxlint --config .oxlintrc.json --fix --deny-warnings .",
49
+ "lint": "oxlint --config .oxlintrc.json --fix .",
50
50
  "check": "tsc --noEmit --project tsconfig.json && npm run check:src",
51
51
  "check:src": "npm run check:src:core",
52
52
  "check:src:core": "tsc --noEmit -p configs/src/tsconfig.core.json",
53
53
  "format": "oxfmt --config .oxfmtrc.json --write .",
54
54
  "format:check": "oxfmt --config .oxfmtrc.json --check .",
55
55
  "lint:check": "oxlint --config .oxlintrc.json --deny-warnings .",
56
- "test": "npm run test:src && npm run test:policy && npm run test:config && npm run test:guides",
56
+ "test": "npm run test:src && npm run test:policy && npm run test:config && npm run test:setup && npm run test:guides",
57
57
  "test:src": "vitest run --config vite.config.ts --no-cache --reporter=dot --project src:core",
58
58
  "test:src:core": "vitest run --config vite.config.ts --no-cache --reporter=dot --project src:core",
59
59
  "test:policy": "vitest run --config vite.config.ts --no-cache --reporter=dot --project policy",
60
60
  "test:config": "vitest run --config vite.config.ts --no-cache --reporter=dot --project config",
61
- "test:guides": "vitest run --config vite.config.ts --reporter=dot --project guides",
61
+ "test:guides": "node --experimental-strip-types tests/guides.test.ts",
62
62
  "build": "npm run clean && npm run build:src",
63
63
  "build:src": "npm run build:src:core",
64
64
  "build:src:core": "vite build --config configs/src/vite.core.config.ts && npm run copy dist/src/core/index.d.ts dist/src/core/index.d.cts",
65
- "prepublishOnly": "npm run format:check && npm run lint:check && npm run check && npm run build && npm test"
65
+ "prepublishOnly": "npm run format:check && npm run lint:check && npm run check && npm run build && npm test && npm run test:distribution -- --mode release",
66
+ "test:distribution": "vitest run --config vite.config.ts --no-cache --reporter=dot --project distribution",
67
+ "test:probe": "vitest run --config vite.config.ts --no-cache --reporter=verbose --project probe",
68
+ "test:bench": "vitest bench --config vite.config.ts --no-cache --project probe",
69
+ "prepack": "npm run build",
70
+ "test:setup": "vitest run --config vite.config.ts --no-cache --reporter=dot --project setup"
66
71
  },
67
72
  "dependencies": {
68
- "@orkestrel/contract": "^0.0.13"
73
+ "@orkestrel/contract": "^0.0.17"
69
74
  },
70
75
  "devDependencies": {
71
76
  "@microsoft/api-extractor": "^7.59.0",
72
- "@orkestrel/guide": "^0.0.12",
73
- "@orkestrel/probe": "^0.0.3",
74
- "@orkestrel/scaffold": "^0.0.49",
75
- "@orkestrel/test": "^0.0.11",
76
- "@types/node": "^26.2.0",
77
- "@vitest/browser-playwright": "^4.1.11",
78
- "oxfmt": "^0.64.0",
79
- "oxlint": "^1.79.0",
77
+ "@orkestrel/guide": "^0.0.17",
78
+ "@orkestrel/probe": "^0.0.12",
79
+ "@orkestrel/scaffold": "^0.0.63",
80
+ "@orkestrel/test": "^0.0.14",
81
+ "@types/node": "^26.4.1",
82
+ "oxfmt": "^0.66.0",
83
+ "oxlint": "^1.81.0",
80
84
  "typescript": "^6.0.3",
81
85
  "vite": "^8.2.2",
82
- "vite-plugin-dts": "^5.0.3",
83
86
  "vitest": "^4.1.11"
84
87
  },
85
88
  "engines": {