@orkestrel/timeout 0.0.3 → 0.0.5

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.
@@ -1,106 +1,186 @@
1
1
  /**
2
- * Create a deadline handle — a controllable `setTimeout` wrapper that exposes an
3
- * `AbortSignal` which fires when the timeout expires, for racing against work.
2
+ * Create a controllable deadline whose native signal aborts on expiry.
4
3
  *
5
4
  * @remarks
6
- * Call `start()` to arm the deadline for `ms` milliseconds; on expiry the handle's
7
- * `signal` fires and `expired` flips `true`. Call `clear()` to cancel a pending
8
- * deadline without firing. When `options.signal` is given, a parent abort CLEARS
9
- * the timeout (it never expires) rather than firing it. Pass `options.id` to label
10
- * the handle for tracing, or let it default to a random UUID. `ms` must be a
11
- * non-negative finite number; the host `setTimeout` clamps a negative or `NaN`
12
- * value to roughly `0` (firing on the next macrotask) rather than throwing.
5
+ * `options.ms` must be an integer from `0` through `2_147_483_647`, inclusive.
6
+ * 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`.
13
10
  *
14
- * @param options - `ms` (the deadline in milliseconds, a non-negative finite
15
- * number), an optional `id` (a trace label; defaults to a random UUID), and an
16
- * optional parent `signal` whose abort clears the timeout
17
- * @returns A working {@link TimeoutInterface}
18
- *
19
- * @example
20
- * ```ts
21
- * import { createTimeout } from '@src/core'
22
- *
23
- * const timeout = createTimeout({ ms: 5_000 })
24
- * timeout.start()
25
- * const result = await Promise.race([
26
- * work(),
27
- * new Promise((_, reject) =>
28
- * timeout.signal.addEventListener('abort', () => reject(new Error('timed out')), {
29
- * once: true,
30
- * }),
31
- * ),
32
- * ])
33
- * timeout.clear() // work finished first — cancel the deadline
34
- * ```
35
- */
36
- export declare function createTimeout(options: TimeoutOptions): TimeoutInterface;
11
+ * @param options - Validated deadline, optional trace label, and optional native parent signal
12
+ * @returns A reusable timeout handle
13
+ * @throws {@link import('@orkestrel/contract').ContractError} When the
14
+ * JavaScript input does not satisfy `TimeoutOptions`
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * import { createTimeout } from '@orkestrel/timeout'
19
+ *
20
+ * const timeout = createTimeout({ ms: 5_000 })
21
+ * timeout.start()
22
+ * try {
23
+ * await fetch('/work', { signal: timeout.signal })
24
+ * } finally {
25
+ * timeout.clear()
26
+ * }
27
+ * ```
28
+ */
29
+ export declare function createTimeout(options: TimeoutOptions): TimeoutInterface;
37
30
 
38
- /**
39
- * A deadline handle — a controllable `setTimeout` wrapper that exposes an
40
- * `AbortSignal` which fires when the timeout expires, for racing against work.
41
- *
42
- * @remarks
43
- * - **Deadline signal.** `start()` arms a `setTimeout` for `ms`; on expiry it
44
- * flips `expired` and aborts the controller, firing `signal`. Race `signal`
45
- * against work to bound how long it may run.
46
- * - **Controllable.** `clear()` cancels a pending expiry WITHOUT firing `signal`,
47
- * and resets `expired` back to `false` (the §10 reset) — a cleared timeout
48
- * reports `expired === false` and a non-aborted `signal`.
49
- * - **Reusable.** Re-`start()` after an expiry swaps in a fresh controller and
50
- * resets `expired`, so `signal` / `expired` reflect the new run — the handle is
51
- * reusable across deadlines without re-construction.
52
- * - **Parent linking.** A parent `signal` does NOT expire this timeout — it
53
- * CLEARS it: the parent listener is attached only while a timer is armed (added
54
- * on `start()`, removed on expiry or `clear()`), so a parent abort during the
55
- * timing window cancels the pending expiry and a later `start()` is a no-op once
56
- * the parent has aborted. An idle handle holds no parent listener.
57
- * - **Event-free.** A pure functional primitive — no Emitter, no events.
58
- *
59
- * @example
60
- * ```ts
61
- * const timeout = new Timeout({ ms: 5_000 })
62
- * timeout.start()
63
- * timeout.signal.addEventListener('abort', () => giveUp(), { once: true })
64
- * timeout.clear() // cancels the deadline before it fires
65
- * ```
66
- */
67
- export declare class Timeout implements TimeoutInterface {
68
- #private;
69
- readonly id: string;
70
- readonly ms: number;
71
- constructor(options: TimeoutOptions);
72
- get signal(): AbortSignal;
73
- get expired(): boolean;
74
- start(): void;
75
- clear(): void;
76
- }
31
+ /**
32
+ * Determine whether a value is an accepted timeout duration.
33
+ *
34
+ * @param value - Unknown duration candidate
35
+ * @returns `true` only for an integer in the inclusive timeout range
36
+ *
37
+ * @example
38
+ * ```ts
39
+ * isTimeoutDuration(0) // true
40
+ * isTimeoutDuration(1.5) // false
41
+ * ```
42
+ */
43
+ export declare function isTimeoutDuration(value: unknown): value is number;
77
44
 
78
- /**
79
- * A deadline handle — a controllable `setTimeout` wrapper that exposes an
80
- * `AbortSignal` which fires when the timeout expires, for racing against work.
81
- */
82
- export declare interface TimeoutInterface {
83
- readonly id: string;
84
- readonly ms: number;
85
- readonly signal: AbortSignal;
86
- readonly expired: boolean;
87
- start(): void;
88
- clear(): void;
89
- }
45
+ /**
46
+ * Determine whether a value is a genuine native `AbortSignal`.
47
+ *
48
+ * @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.
52
+ *
53
+ * @param value - Unknown signal candidate
54
+ * @returns `true` only when the native `AbortSignal` getter accepts the value
55
+ *
56
+ * @example
57
+ * ```ts
58
+ * isTimeoutSignal(new AbortController().signal) // true
59
+ * isTimeoutSignal({ aborted: false }) // false
60
+ * ```
61
+ */
62
+ export declare function isTimeoutSignal(value: unknown): value is AbortSignal;
90
63
 
91
- /** Options for `createTimeout`. */
92
- export declare interface TimeoutOptions {
93
- readonly id?: string;
94
- /**
95
- * The deadline in milliseconds — must be a non-negative finite number.
96
- *
97
- * @remarks
98
- * Passed straight to the host `setTimeout`, which clamps a negative or `NaN`
99
- * value to roughly `0` (firing on the next macrotask) rather than throwing.
100
- */
101
- readonly ms: number;
102
- /** A parent signal — clears the timeout (so it never expires) if this aborts. */
103
- readonly signal?: AbortSignal;
104
- }
64
+ /**
65
+ * Largest timeout duration accepted by the package, in milliseconds.
66
+ *
67
+ * @example
68
+ * ```ts
69
+ * createTimeout({ ms: MAX_TIMEOUT_MS })
70
+ * ```
71
+ */
72
+ export declare const MAX_TIMEOUT_MS = 2147483647;
73
+
74
+ /**
75
+ * A controllable deadline whose native `AbortSignal` aborts when it expires.
76
+ *
77
+ * @remarks
78
+ * `start()` arms or replaces the current deadline. Expiry sets `expired` and
79
+ * aborts the current signal once. `clear()` cancels without aborting and resets
80
+ * `expired`. Starting or clearing after expiry installs a fresh signal. A parent
81
+ * abort deliberately clears the timeout without aborting its signal; once the
82
+ * parent has aborted, later starts are inert.
83
+ *
84
+ * Construction validates the complete JavaScript boundary before creating an
85
+ * `AbortController`. A malformed or unreadable options record throws a
86
+ * `bound`-coded {@link import('@orkestrel/contract').ContractError}; an invalid
87
+ * `id`, `ms`, or `signal` uses `literal`, `range`, or `placement`, respectively.
88
+ *
89
+ * @example
90
+ * ```ts
91
+ * import { Timeout } from '@orkestrel/timeout'
92
+ *
93
+ * const timeout = new Timeout({ ms: 5_000 })
94
+ * timeout.start()
95
+ * timeout.signal.addEventListener('abort', () => giveUp(), { once: true })
96
+ * timeout.clear()
97
+ * ```
98
+ */
99
+ export declare class Timeout implements TimeoutInterface {
100
+ #private;
101
+ readonly id: string;
102
+ readonly ms: number;
103
+ constructor(options: TimeoutOptions);
104
+ get signal(): AbortSignal;
105
+ get expired(): boolean;
106
+ start(): void;
107
+ clear(): void;
108
+ }
109
+
110
+ /**
111
+ * A controllable deadline exposing a native `AbortSignal` that aborts on expiry.
112
+ *
113
+ * @example
114
+ * ```ts
115
+ * const timeout: TimeoutInterface = createTimeout({ ms: 5_000 })
116
+ * timeout.start()
117
+ * timeout.clear()
118
+ * ```
119
+ */
120
+ export declare interface TimeoutInterface {
121
+ /** Trace label supplied at construction or generated as a random UUID. */
122
+ readonly id: string;
123
+ /** Validated integer deadline in milliseconds. */
124
+ readonly ms: number;
125
+ /** Native signal that aborts once when the current deadline expires. */
126
+ readonly signal: AbortSignal;
127
+ /** Whether the owned signal has aborted, derived directly from that signal. */
128
+ readonly expired: boolean;
129
+ /**
130
+ * Arm or re-arm the deadline.
131
+ *
132
+ * @returns Nothing
133
+ */
134
+ start(): void;
135
+ /**
136
+ * Cancel an armed deadline without aborting its signal and reset expiry state.
137
+ *
138
+ * @returns Nothing
139
+ */
140
+ clear(): void;
141
+ }
142
+
143
+ /**
144
+ * Options for constructing a timeout deadline.
145
+ *
146
+ * @remarks
147
+ * `ms` is an integer from `0` through `2_147_483_647`, inclusive. `id`
148
+ * defaults to a random UUID when omitted, and `signal` is an optional native
149
+ * parent signal whose abort clears an armed timeout without aborting the
150
+ * timeout's own signal.
151
+ *
152
+ * @example
153
+ * ```ts
154
+ * const options: TimeoutOptions = { id: 'request', ms: 5_000 }
155
+ * ```
156
+ */
157
+ export declare interface TimeoutOptions {
158
+ /** Trace label for the timeout; omission generates a random UUID. */
159
+ readonly id?: string;
160
+ /** Integer deadline in milliseconds, inclusive from `0` through `2_147_483_647`. */
161
+ readonly ms: number;
162
+ /** Native parent signal whose abort clears an armed timeout. */
163
+ readonly signal?: AbortSignal;
164
+ }
165
+
166
+ /**
167
+ * Validate and normalize timeout construction options.
168
+ *
169
+ * @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
172
+ * listener lifecycle begins at this boundary.
173
+ *
174
+ * @param options - Potentially untrusted timeout options
175
+ * @returns A fresh validated `TimeoutOptions` object
176
+ * @throws {@link import('@orkestrel/contract').ContractError} When the input
177
+ * does not satisfy `TimeoutOptions`
178
+ *
179
+ * @example
180
+ * ```ts
181
+ * const options = validateTimeoutOptions({ ms: 5_000 })
182
+ * ```
183
+ */
184
+ export declare function validateTimeoutOptions(options: TimeoutOptions): TimeoutOptions;
105
185
 
106
- export { }
186
+ export { }
@@ -1,106 +1,186 @@
1
1
  /**
2
- * Create a deadline handle — a controllable `setTimeout` wrapper that exposes an
3
- * `AbortSignal` which fires when the timeout expires, for racing against work.
2
+ * Create a controllable deadline whose native signal aborts on expiry.
4
3
  *
5
4
  * @remarks
6
- * Call `start()` to arm the deadline for `ms` milliseconds; on expiry the handle's
7
- * `signal` fires and `expired` flips `true`. Call `clear()` to cancel a pending
8
- * deadline without firing. When `options.signal` is given, a parent abort CLEARS
9
- * the timeout (it never expires) rather than firing it. Pass `options.id` to label
10
- * the handle for tracing, or let it default to a random UUID. `ms` must be a
11
- * non-negative finite number; the host `setTimeout` clamps a negative or `NaN`
12
- * value to roughly `0` (firing on the next macrotask) rather than throwing.
5
+ * `options.ms` must be an integer from `0` through `2_147_483_647`, inclusive.
6
+ * 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`.
13
10
  *
14
- * @param options - `ms` (the deadline in milliseconds, a non-negative finite
15
- * number), an optional `id` (a trace label; defaults to a random UUID), and an
16
- * optional parent `signal` whose abort clears the timeout
17
- * @returns A working {@link TimeoutInterface}
18
- *
19
- * @example
20
- * ```ts
21
- * import { createTimeout } from '@src/core'
22
- *
23
- * const timeout = createTimeout({ ms: 5_000 })
24
- * timeout.start()
25
- * const result = await Promise.race([
26
- * work(),
27
- * new Promise((_, reject) =>
28
- * timeout.signal.addEventListener('abort', () => reject(new Error('timed out')), {
29
- * once: true,
30
- * }),
31
- * ),
32
- * ])
33
- * timeout.clear() // work finished first — cancel the deadline
34
- * ```
35
- */
36
- export declare function createTimeout(options: TimeoutOptions): TimeoutInterface;
11
+ * @param options - Validated deadline, optional trace label, and optional native parent signal
12
+ * @returns A reusable timeout handle
13
+ * @throws {@link import('@orkestrel/contract').ContractError} When the
14
+ * JavaScript input does not satisfy `TimeoutOptions`
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * import { createTimeout } from '@orkestrel/timeout'
19
+ *
20
+ * const timeout = createTimeout({ ms: 5_000 })
21
+ * timeout.start()
22
+ * try {
23
+ * await fetch('/work', { signal: timeout.signal })
24
+ * } finally {
25
+ * timeout.clear()
26
+ * }
27
+ * ```
28
+ */
29
+ export declare function createTimeout(options: TimeoutOptions): TimeoutInterface;
37
30
 
38
- /**
39
- * A deadline handle — a controllable `setTimeout` wrapper that exposes an
40
- * `AbortSignal` which fires when the timeout expires, for racing against work.
41
- *
42
- * @remarks
43
- * - **Deadline signal.** `start()` arms a `setTimeout` for `ms`; on expiry it
44
- * flips `expired` and aborts the controller, firing `signal`. Race `signal`
45
- * against work to bound how long it may run.
46
- * - **Controllable.** `clear()` cancels a pending expiry WITHOUT firing `signal`,
47
- * and resets `expired` back to `false` (the §10 reset) — a cleared timeout
48
- * reports `expired === false` and a non-aborted `signal`.
49
- * - **Reusable.** Re-`start()` after an expiry swaps in a fresh controller and
50
- * resets `expired`, so `signal` / `expired` reflect the new run — the handle is
51
- * reusable across deadlines without re-construction.
52
- * - **Parent linking.** A parent `signal` does NOT expire this timeout — it
53
- * CLEARS it: the parent listener is attached only while a timer is armed (added
54
- * on `start()`, removed on expiry or `clear()`), so a parent abort during the
55
- * timing window cancels the pending expiry and a later `start()` is a no-op once
56
- * the parent has aborted. An idle handle holds no parent listener.
57
- * - **Event-free.** A pure functional primitive — no Emitter, no events.
58
- *
59
- * @example
60
- * ```ts
61
- * const timeout = new Timeout({ ms: 5_000 })
62
- * timeout.start()
63
- * timeout.signal.addEventListener('abort', () => giveUp(), { once: true })
64
- * timeout.clear() // cancels the deadline before it fires
65
- * ```
66
- */
67
- export declare class Timeout implements TimeoutInterface {
68
- #private;
69
- readonly id: string;
70
- readonly ms: number;
71
- constructor(options: TimeoutOptions);
72
- get signal(): AbortSignal;
73
- get expired(): boolean;
74
- start(): void;
75
- clear(): void;
76
- }
31
+ /**
32
+ * Determine whether a value is an accepted timeout duration.
33
+ *
34
+ * @param value - Unknown duration candidate
35
+ * @returns `true` only for an integer in the inclusive timeout range
36
+ *
37
+ * @example
38
+ * ```ts
39
+ * isTimeoutDuration(0) // true
40
+ * isTimeoutDuration(1.5) // false
41
+ * ```
42
+ */
43
+ export declare function isTimeoutDuration(value: unknown): value is number;
77
44
 
78
- /**
79
- * A deadline handle — a controllable `setTimeout` wrapper that exposes an
80
- * `AbortSignal` which fires when the timeout expires, for racing against work.
81
- */
82
- export declare interface TimeoutInterface {
83
- readonly id: string;
84
- readonly ms: number;
85
- readonly signal: AbortSignal;
86
- readonly expired: boolean;
87
- start(): void;
88
- clear(): void;
89
- }
45
+ /**
46
+ * Determine whether a value is a genuine native `AbortSignal`.
47
+ *
48
+ * @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.
52
+ *
53
+ * @param value - Unknown signal candidate
54
+ * @returns `true` only when the native `AbortSignal` getter accepts the value
55
+ *
56
+ * @example
57
+ * ```ts
58
+ * isTimeoutSignal(new AbortController().signal) // true
59
+ * isTimeoutSignal({ aborted: false }) // false
60
+ * ```
61
+ */
62
+ export declare function isTimeoutSignal(value: unknown): value is AbortSignal;
90
63
 
91
- /** Options for `createTimeout`. */
92
- export declare interface TimeoutOptions {
93
- readonly id?: string;
94
- /**
95
- * The deadline in milliseconds — must be a non-negative finite number.
96
- *
97
- * @remarks
98
- * Passed straight to the host `setTimeout`, which clamps a negative or `NaN`
99
- * value to roughly `0` (firing on the next macrotask) rather than throwing.
100
- */
101
- readonly ms: number;
102
- /** A parent signal — clears the timeout (so it never expires) if this aborts. */
103
- readonly signal?: AbortSignal;
104
- }
64
+ /**
65
+ * Largest timeout duration accepted by the package, in milliseconds.
66
+ *
67
+ * @example
68
+ * ```ts
69
+ * createTimeout({ ms: MAX_TIMEOUT_MS })
70
+ * ```
71
+ */
72
+ export declare const MAX_TIMEOUT_MS = 2147483647;
73
+
74
+ /**
75
+ * A controllable deadline whose native `AbortSignal` aborts when it expires.
76
+ *
77
+ * @remarks
78
+ * `start()` arms or replaces the current deadline. Expiry sets `expired` and
79
+ * aborts the current signal once. `clear()` cancels without aborting and resets
80
+ * `expired`. Starting or clearing after expiry installs a fresh signal. A parent
81
+ * abort deliberately clears the timeout without aborting its signal; once the
82
+ * parent has aborted, later starts are inert.
83
+ *
84
+ * Construction validates the complete JavaScript boundary before creating an
85
+ * `AbortController`. A malformed or unreadable options record throws a
86
+ * `bound`-coded {@link import('@orkestrel/contract').ContractError}; an invalid
87
+ * `id`, `ms`, or `signal` uses `literal`, `range`, or `placement`, respectively.
88
+ *
89
+ * @example
90
+ * ```ts
91
+ * import { Timeout } from '@orkestrel/timeout'
92
+ *
93
+ * const timeout = new Timeout({ ms: 5_000 })
94
+ * timeout.start()
95
+ * timeout.signal.addEventListener('abort', () => giveUp(), { once: true })
96
+ * timeout.clear()
97
+ * ```
98
+ */
99
+ export declare class Timeout implements TimeoutInterface {
100
+ #private;
101
+ readonly id: string;
102
+ readonly ms: number;
103
+ constructor(options: TimeoutOptions);
104
+ get signal(): AbortSignal;
105
+ get expired(): boolean;
106
+ start(): void;
107
+ clear(): void;
108
+ }
109
+
110
+ /**
111
+ * A controllable deadline exposing a native `AbortSignal` that aborts on expiry.
112
+ *
113
+ * @example
114
+ * ```ts
115
+ * const timeout: TimeoutInterface = createTimeout({ ms: 5_000 })
116
+ * timeout.start()
117
+ * timeout.clear()
118
+ * ```
119
+ */
120
+ export declare interface TimeoutInterface {
121
+ /** Trace label supplied at construction or generated as a random UUID. */
122
+ readonly id: string;
123
+ /** Validated integer deadline in milliseconds. */
124
+ readonly ms: number;
125
+ /** Native signal that aborts once when the current deadline expires. */
126
+ readonly signal: AbortSignal;
127
+ /** Whether the owned signal has aborted, derived directly from that signal. */
128
+ readonly expired: boolean;
129
+ /**
130
+ * Arm or re-arm the deadline.
131
+ *
132
+ * @returns Nothing
133
+ */
134
+ start(): void;
135
+ /**
136
+ * Cancel an armed deadline without aborting its signal and reset expiry state.
137
+ *
138
+ * @returns Nothing
139
+ */
140
+ clear(): void;
141
+ }
142
+
143
+ /**
144
+ * Options for constructing a timeout deadline.
145
+ *
146
+ * @remarks
147
+ * `ms` is an integer from `0` through `2_147_483_647`, inclusive. `id`
148
+ * defaults to a random UUID when omitted, and `signal` is an optional native
149
+ * parent signal whose abort clears an armed timeout without aborting the
150
+ * timeout's own signal.
151
+ *
152
+ * @example
153
+ * ```ts
154
+ * const options: TimeoutOptions = { id: 'request', ms: 5_000 }
155
+ * ```
156
+ */
157
+ export declare interface TimeoutOptions {
158
+ /** Trace label for the timeout; omission generates a random UUID. */
159
+ readonly id?: string;
160
+ /** Integer deadline in milliseconds, inclusive from `0` through `2_147_483_647`. */
161
+ readonly ms: number;
162
+ /** Native parent signal whose abort clears an armed timeout. */
163
+ readonly signal?: AbortSignal;
164
+ }
165
+
166
+ /**
167
+ * Validate and normalize timeout construction options.
168
+ *
169
+ * @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
172
+ * listener lifecycle begins at this boundary.
173
+ *
174
+ * @param options - Potentially untrusted timeout options
175
+ * @returns A fresh validated `TimeoutOptions` object
176
+ * @throws {@link import('@orkestrel/contract').ContractError} When the input
177
+ * does not satisfy `TimeoutOptions`
178
+ *
179
+ * @example
180
+ * ```ts
181
+ * const options = validateTimeoutOptions({ ms: 5_000 })
182
+ * ```
183
+ */
184
+ export declare function validateTimeoutOptions(options: TimeoutOptions): TimeoutOptions;
105
185
 
106
- export { }
186
+ export { }