@orkestrel/workflow 0.0.16 → 0.0.17

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
@@ -33,13 +33,13 @@ attempt. Runtime pause gates are intentionally not persisted.
33
33
 
34
34
  Provider sessions, external processes, MCP projection, journals, leases, and
35
35
  distributed fencing remain integration concerns rather than core workflow
36
- behavior. See [guides/src/workflow.md](./guides/src/workflow.md) for the complete
37
- shipped contract. The proposed integration architecture now lives with the
36
+ behavior. See [guides/workflow.md](./guides/workflow.md) for the complete
37
+ shipped contract. The proposed integration architecture lives with the
38
38
  package that implements it, as `plan/PROPOSAL.md` in `@orkestrel/supervisor`.
39
39
 
40
40
  ## Package
41
41
 
42
- Published as three environment-scoped entry points: `.` provides the shared
42
+ Published as environment-scoped entry points: `.` provides the shared
43
43
  environment-agnostic core and default scheduler, `./browser` adds browser-native
44
44
  schedulers, and `./server` adds the Node-native scheduler. Core ships dual
45
45
  ESM+CJS builds; `./browser` is ESM-only.
@@ -3,7 +3,7 @@ import { SchedulerOptions } from '@orkestrel/workflow';
3
3
  import { SchedulerPriority } from '@orkestrel/workflow';
4
4
 
5
5
  /**
6
- * The browser {@link SchedulerInterface} — the browser-native cooperative-yield backend
6
+ * Implements the browser {@link SchedulerInterface} — the browser-native cooperative-yield backend
7
7
  * built on the Prioritized Task Scheduling API (`scheduler.postTask`), falling back to a
8
8
  * zero-delay macrotask where it is absent.
9
9
  *
@@ -13,9 +13,9 @@ import { SchedulerPriority } from '@orkestrel/workflow';
13
13
  * priority (`user` → `'user-blocking'`, `normal` → `'user-visible'`, `background` →
14
14
  * `'background'`), so the host genuinely regains control and the urgency hint is
15
15
  * honoured. The capability is feature-detected through guards (`isRecord` / `isFunction`),
16
- * never an `as` (AGENTS §14). Where the API is absent (Firefox today, older engines),
17
- * it **falls back** to a `setTimeout(0)` macrotask — still a real host-turn, just
18
- * without priority. `delay(ms)` is always a real `setTimeout`.
16
+ * never an `as`. Where the API is absent (Firefox today, older engines),
17
+ * it **falls back** to a `setTimeout(0)` macrotask — still a real host-turn, without
18
+ * priority. `delay(ms)` is always a real `setTimeout`.
19
19
  * - **Abort fidelity is verbatim.** The shared `scheduleHost` lifecycle links an owned
20
20
  * settlement composite before scheduling, preserving the exact caller reason without
21
21
  * invoking caller-owned signal methods. The caller signal is NOT handed to `postTask`;
@@ -39,26 +39,25 @@ import { SchedulerPriority } from '@orkestrel/workflow';
39
39
  export declare class BrowserScheduler implements SchedulerInterface {
40
40
  #private;
41
41
  /**
42
- * Yield control to the host via `scheduler.postTask` at the given priority (or a
43
- * `setTimeout(0)` macrotask where the API is absent), then resume; abort rejects with
42
+ * Yields control to the host through `scheduler.postTask` at the given priority (or a
43
+ * `setTimeout(0)` macrotask where the API is absent), then resumes; abort rejects with
44
44
  * `signal.reason`.
45
45
  */
46
46
  yield(options?: SchedulerOptions): Promise<void>;
47
47
  /**
48
- * Resume after at least `ms` milliseconds via `setTimeout`; abort rejects with
48
+ * Resumes after at least `ms` milliseconds through `setTimeout`; abort rejects with
49
49
  * `signal.reason`.
50
50
  *
51
51
  * @remarks
52
- * `ms` should be a non-negative finite number. The primitive does no validation: it
53
- * passes `ms` straight to the host `setTimeout`, which clamps a negative value or
54
- * `NaN` to ~0 — so an out-of-domain `ms` resolves on the next host turn rather than
55
- * throwing.
52
+ * Pass a non-negative finite `ms`. The primitive does no validation: it passes `ms`
53
+ * straight to the host `setTimeout`, which clamps a negative value or `NaN` to ~0 — so an
54
+ * out-of-domain `ms` resolves on the next host turn rather than throwing.
56
55
  */
57
56
  delay(ms: number, options?: SchedulerOptions): Promise<void>;
58
57
  }
59
58
 
60
59
  /**
61
- * Create the browser-native cooperative-yield {@link SchedulerInterface} — `yield()` uses
60
+ * Creates the browser-native cooperative-yield {@link SchedulerInterface} — `yield()` uses
62
61
  * the Prioritized Task Scheduling API (`scheduler.postTask`) at the requested priority
63
62
  * when present, falling back to a `setTimeout(0)` macrotask; `delay(ms)` is a real
64
63
  * `setTimeout`.
@@ -85,13 +84,13 @@ export declare class BrowserScheduler implements SchedulerInterface {
85
84
  export declare function createBrowserScheduler(): SchedulerInterface;
86
85
 
87
86
  /**
88
- * Create the frame-aligned cooperative-yield {@link SchedulerInterface} — `yield()` resumes
89
- * just before the next paint via `requestAnimationFrame`; `delay(ms)` is a real
87
+ * Creates the frame-aligned cooperative-yield {@link SchedulerInterface} — `yield()` resumes
88
+ * just before the next paint through `requestAnimationFrame`; `delay(ms)` is a real
90
89
  * `setTimeout`.
91
90
  *
92
91
  * @remarks
93
- * Use it for work that should batch per render frame (animation, incremental DOM updates)
94
- * and naturally pause while the tab is hidden. `yield` is abort-aware: pass `options.signal`
92
+ * Use it for work that batches per render frame (animation, incremental DOM updates) and
93
+ * naturally pauses while the tab is hidden. `yield` is abort-aware: pass `options.signal`
95
94
  * and a pending yield rejects with the signal's `reason` verbatim, cancelling the pending
96
95
  * frame request. `options.priority` is accepted but a no-op — a frame callback has no
97
96
  * priority dimension.
@@ -109,8 +108,8 @@ export declare function createBrowserScheduler(): SchedulerInterface;
109
108
  export declare function createFrameScheduler(): SchedulerInterface;
110
109
 
111
110
  /**
112
- * Create the idle-time cooperative-yield {@link SchedulerInterface} — `yield()` resumes when
113
- * the host is idle via `requestIdleCallback` when present, falling back to a `setTimeout(0)`
111
+ * Creates the idle-time cooperative-yield {@link SchedulerInterface} — `yield()` resumes when
112
+ * the host is idle through `requestIdleCallback` when present, falling back to a `setTimeout(0)`
114
113
  * macrotask; `delay(ms)` is a real `setTimeout`.
115
114
  *
116
115
  * @remarks
@@ -133,14 +132,14 @@ export declare function createFrameScheduler(): SchedulerInterface;
133
132
  export declare function createIdleScheduler(): SchedulerInterface;
134
133
 
135
134
  /**
136
- * The frame-aligned {@link SchedulerInterface} — a browser cooperative-yield backend
137
- * whose `yield` resumes just before the next paint via `requestAnimationFrame`.
135
+ * Implements the frame-aligned {@link SchedulerInterface} — a browser cooperative-yield backend
136
+ * whose `yield` resumes just before the next paint through `requestAnimationFrame`.
138
137
  *
139
138
  * @remarks
140
139
  * - **`yield` resumes before the next paint.** `yield()` waits on `requestAnimationFrame`,
141
140
  * so the resumption is aligned to the browser's render loop — ideal for work that
142
- * should batch per frame (animation, incremental DOM updates) and pause while the tab
143
- * is hidden (the host throttles rAF). `delay(ms)` is a real `setTimeout`, unaligned to
141
+ * batches per frame (animation, incremental DOM updates) and pauses while the tab is
142
+ * hidden (the host throttles rAF). `delay(ms)` is a real `setTimeout`, unaligned to
144
143
  * frames. `options.priority` is accepted for contract compliance but a no-op — a frame
145
144
  * callback has no priority dimension.
146
145
  * - **Abort fidelity is verbatim, with cleanup.** The shared `scheduleHost` lifecycle links
@@ -164,41 +163,40 @@ export declare function createIdleScheduler(): SchedulerInterface;
164
163
  export declare class FrameScheduler implements SchedulerInterface {
165
164
  #private;
166
165
  /**
167
- * Yield control to the host until just before the next paint via
168
- * `requestAnimationFrame`, then resume; abort rejects with `signal.reason`.
166
+ * Yields control to the host until just before the next paint through
167
+ * `requestAnimationFrame`, then resumes; abort rejects with `signal.reason`.
169
168
  */
170
169
  yield(options?: SchedulerOptions): Promise<void>;
171
170
  /**
172
- * Resume after at least `ms` milliseconds via `setTimeout`; abort rejects with
171
+ * Resumes after at least `ms` milliseconds through `setTimeout`; abort rejects with
173
172
  * `signal.reason`.
174
173
  *
175
174
  * @remarks
176
- * `ms` should be a non-negative finite number. The primitive does no validation: it
177
- * passes `ms` straight to the host `setTimeout`, which clamps a negative value or
178
- * `NaN` to ~0 — so an out-of-domain `ms` resolves on the next host turn rather than
179
- * throwing.
175
+ * Pass a non-negative finite `ms`. The primitive does no validation: it passes `ms`
176
+ * straight to the host `setTimeout`, which clamps a negative value or `NaN` to ~0 — so an
177
+ * out-of-domain `ms` resolves on the next host turn rather than throwing.
180
178
  */
181
179
  delay(ms: number, options?: SchedulerOptions): Promise<void>;
182
180
  }
183
181
 
184
182
  /**
185
- * The narrowed `requestIdleCallback` / `cancelIdleCallback` pair feature-detected off
183
+ * Declares the narrowed `requestIdleCallback` / `cancelIdleCallback` pair feature-detected off
186
184
  * `globalThis`.
187
185
  *
188
186
  * @remarks
189
187
  * A `request` taking a callback and returning a numeric handle, and a `cancel` taking that
190
188
  * handle. {@link IdleScheduler} feature-detects the pair through a guard (`isFunction`,
191
- * never an `as` — AGENTS §14) and resolves to `undefined` when the API is absent (Safari
189
+ * never an `as`) and resolves to `undefined` when the API is absent (Safari
192
190
  * today), so `yield` falls back to a macrotask.
193
191
  */
194
- export declare interface IdleAPI {
192
+ export declare interface IdleInterface {
195
193
  readonly request: (callback: () => void) => number;
196
194
  readonly cancel: (handle: number) => void;
197
195
  }
198
196
 
199
197
  /**
200
- * The idle-time {@link SchedulerInterface} — a browser cooperative-yield backend whose
201
- * `yield` resumes when the host is idle via `requestIdleCallback`, falling back to a
198
+ * Implements the idle-time {@link SchedulerInterface} — a browser cooperative-yield backend whose
199
+ * `yield` resumes when the host is idle through `requestIdleCallback`, falling back to a
202
200
  * zero-delay macrotask where it is absent.
203
201
  *
204
202
  * @remarks
@@ -206,8 +204,8 @@ export declare interface IdleAPI {
206
204
  * `yield()` waits on it, so the resumption happens when the browser has spare time after
207
205
  * rendering and input — ideal for low-priority background work that must not contend with
208
206
  * the user. The capability is feature-detected through a guard (`isFunction`), never an
209
- * `as` (AGENTS §14). Where the API is absent (Safari today), it **falls back** to a
210
- * `setTimeout(0)` macrotask — still a real host-turn, just not idle-gated. `delay(ms)` is
207
+ * `as`. Where the API is absent (Safari today), it **falls back** to a
208
+ * `setTimeout(0)` macrotask — still a real host-turn, not idle-gated. `delay(ms)` is
211
209
  * always a real `setTimeout`. `options.priority` is accepted for contract compliance but a
212
210
  * no-op — idle scheduling has no priority dimension.
213
211
  * - **Abort fidelity is verbatim, with cleanup.** The shared `scheduleHost` lifecycle links
@@ -231,26 +229,25 @@ export declare interface IdleAPI {
231
229
  export declare class IdleScheduler implements SchedulerInterface {
232
230
  #private;
233
231
  /**
234
- * Yield control to the host until it is idle via `requestIdleCallback` (or a
235
- * `setTimeout(0)` macrotask where the API is absent), then resume; abort rejects with
232
+ * Yields control to the host until it is idle through `requestIdleCallback` (or a
233
+ * `setTimeout(0)` macrotask where the API is absent), then resumes; abort rejects with
236
234
  * `signal.reason`.
237
235
  */
238
236
  yield(options?: SchedulerOptions): Promise<void>;
239
237
  /**
240
- * Resume after at least `ms` milliseconds via `setTimeout`; abort rejects with
238
+ * Resumes after at least `ms` milliseconds through `setTimeout`; abort rejects with
241
239
  * `signal.reason`.
242
240
  *
243
241
  * @remarks
244
- * `ms` should be a non-negative finite number. The primitive does no validation: it
245
- * passes `ms` straight to the host `setTimeout`, which clamps a negative value or
246
- * `NaN` to ~0 — so an out-of-domain `ms` resolves on the next host turn rather than
247
- * throwing.
242
+ * Pass a non-negative finite `ms`. The primitive does no validation: it passes `ms`
243
+ * straight to the host `setTimeout`, which clamps a negative value or `NaN` to ~0 — so an
244
+ * out-of-domain `ms` resolves on the next host turn rather than throwing.
248
245
  */
249
246
  delay(ms: number, options?: SchedulerOptions): Promise<void>;
250
247
  }
251
248
 
252
249
  /**
253
- * The browser-native `postTask` priority for each portable {@link SchedulerPriority} — the
250
+ * Maps each portable {@link SchedulerPriority} to the browser-native `postTask` priority — the
254
251
  * Prioritized Task Scheduling API's three levels.
255
252
  *
256
253
  * @remarks
@@ -1,8 +1,8 @@
1
- import { scheduleHost } from "../core/index.js";
1
+ import { delayHost, scheduleHost } from "../core/index.js";
2
2
  import { isFunction, isPromise, isRecord } from "@orkestrel/contract";
3
3
  //#region src/browser/constants.ts
4
4
  /**
5
- * The browser-native `postTask` priority for each portable {@link SchedulerPriority} — the
5
+ * Maps each portable {@link SchedulerPriority} to the browser-native `postTask` priority — the
6
6
  * Prioritized Task Scheduling API's three levels.
7
7
  *
8
8
  * @remarks
@@ -19,7 +19,7 @@ var POST_TASK_PRIORITY = Object.freeze({
19
19
  //#endregion
20
20
  //#region src/browser/BrowserScheduler.ts
21
21
  /**
22
- * The browser {@link SchedulerInterface} — the browser-native cooperative-yield backend
22
+ * Implements the browser {@link SchedulerInterface} — the browser-native cooperative-yield backend
23
23
  * built on the Prioritized Task Scheduling API (`scheduler.postTask`), falling back to a
24
24
  * zero-delay macrotask where it is absent.
25
25
  *
@@ -29,9 +29,9 @@ var POST_TASK_PRIORITY = Object.freeze({
29
29
  * priority (`user` → `'user-blocking'`, `normal` → `'user-visible'`, `background` →
30
30
  * `'background'`), so the host genuinely regains control and the urgency hint is
31
31
  * honoured. The capability is feature-detected through guards (`isRecord` / `isFunction`),
32
- * never an `as` (AGENTS §14). Where the API is absent (Firefox today, older engines),
33
- * it **falls back** to a `setTimeout(0)` macrotask — still a real host-turn, just
34
- * without priority. `delay(ms)` is always a real `setTimeout`.
32
+ * never an `as`. Where the API is absent (Firefox today, older engines),
33
+ * it **falls back** to a `setTimeout(0)` macrotask — still a real host-turn, without
34
+ * priority. `delay(ms)` is always a real `setTimeout`.
35
35
  * - **Abort fidelity is verbatim.** The shared `scheduleHost` lifecycle links an owned
36
36
  * settlement composite before scheduling, preserving the exact caller reason without
37
37
  * invoking caller-owned signal methods. The caller signal is NOT handed to `postTask`;
@@ -54,27 +54,26 @@ var POST_TASK_PRIORITY = Object.freeze({
54
54
  */
55
55
  var BrowserScheduler = class {
56
56
  /**
57
- * Yield control to the host via `scheduler.postTask` at the given priority (or a
58
- * `setTimeout(0)` macrotask where the API is absent), then resume; abort rejects with
57
+ * Yields control to the host through `scheduler.postTask` at the given priority (or a
58
+ * `setTimeout(0)` macrotask where the API is absent), then resumes; abort rejects with
59
59
  * `signal.reason`.
60
60
  */
61
61
  yield(options) {
62
62
  const post = this.#postTask();
63
- if (post === void 0) return this.#timer(0, options?.signal);
63
+ if (post === void 0) return delayHost(0, options?.signal);
64
64
  return this.#yieldVia(post, options?.priority ?? "normal", options?.signal);
65
65
  }
66
66
  /**
67
- * Resume after at least `ms` milliseconds via `setTimeout`; abort rejects with
67
+ * Resumes after at least `ms` milliseconds through `setTimeout`; abort rejects with
68
68
  * `signal.reason`.
69
69
  *
70
70
  * @remarks
71
- * `ms` should be a non-negative finite number. The primitive does no validation: it
72
- * passes `ms` straight to the host `setTimeout`, which clamps a negative value or
73
- * `NaN` to ~0 — so an out-of-domain `ms` resolves on the next host turn rather than
74
- * throwing.
71
+ * Pass a non-negative finite `ms`. The primitive does no validation: it passes `ms`
72
+ * straight to the host `setTimeout`, which clamps a negative value or `NaN` to ~0 — so an
73
+ * out-of-domain `ms` resolves on the next host turn rather than throwing.
75
74
  */
76
75
  delay(ms, options) {
77
- return this.#timer(ms, options?.signal);
76
+ return delayHost(ms, options?.signal);
78
77
  }
79
78
  #postTask() {
80
79
  const candidate = Reflect.get(globalThis, "scheduler");
@@ -94,24 +93,18 @@ var BrowserScheduler = class {
94
93
  return () => internal.abort();
95
94
  }, signal);
96
95
  }
97
- #timer(ms, signal) {
98
- return scheduleHost((complete) => {
99
- const handle = setTimeout(complete, ms);
100
- return () => clearTimeout(handle);
101
- }, signal);
102
- }
103
96
  };
104
97
  //#endregion
105
98
  //#region src/browser/FrameScheduler.ts
106
99
  /**
107
- * The frame-aligned {@link SchedulerInterface} — a browser cooperative-yield backend
108
- * whose `yield` resumes just before the next paint via `requestAnimationFrame`.
100
+ * Implements the frame-aligned {@link SchedulerInterface} — a browser cooperative-yield backend
101
+ * whose `yield` resumes just before the next paint through `requestAnimationFrame`.
109
102
  *
110
103
  * @remarks
111
104
  * - **`yield` resumes before the next paint.** `yield()` waits on `requestAnimationFrame`,
112
105
  * so the resumption is aligned to the browser's render loop — ideal for work that
113
- * should batch per frame (animation, incremental DOM updates) and pause while the tab
114
- * is hidden (the host throttles rAF). `delay(ms)` is a real `setTimeout`, unaligned to
106
+ * batches per frame (animation, incremental DOM updates) and pauses while the tab is
107
+ * hidden (the host throttles rAF). `delay(ms)` is a real `setTimeout`, unaligned to
115
108
  * frames. `options.priority` is accepted for contract compliance but a no-op — a frame
116
109
  * callback has no priority dimension.
117
110
  * - **Abort fidelity is verbatim, with cleanup.** The shared `scheduleHost` lifecycle links
@@ -134,24 +127,23 @@ var BrowserScheduler = class {
134
127
  */
135
128
  var FrameScheduler = class {
136
129
  /**
137
- * Yield control to the host until just before the next paint via
138
- * `requestAnimationFrame`, then resume; abort rejects with `signal.reason`.
130
+ * Yields control to the host until just before the next paint through
131
+ * `requestAnimationFrame`, then resumes; abort rejects with `signal.reason`.
139
132
  */
140
133
  yield(options) {
141
134
  return this.#frame(options?.signal);
142
135
  }
143
136
  /**
144
- * Resume after at least `ms` milliseconds via `setTimeout`; abort rejects with
137
+ * Resumes after at least `ms` milliseconds through `setTimeout`; abort rejects with
145
138
  * `signal.reason`.
146
139
  *
147
140
  * @remarks
148
- * `ms` should be a non-negative finite number. The primitive does no validation: it
149
- * passes `ms` straight to the host `setTimeout`, which clamps a negative value or
150
- * `NaN` to ~0 — so an out-of-domain `ms` resolves on the next host turn rather than
151
- * throwing.
141
+ * Pass a non-negative finite `ms`. The primitive does no validation: it passes `ms`
142
+ * straight to the host `setTimeout`, which clamps a negative value or `NaN` to ~0 — so an
143
+ * out-of-domain `ms` resolves on the next host turn rather than throwing.
152
144
  */
153
145
  delay(ms, options) {
154
- return this.#sleep(ms, options?.signal);
146
+ return delayHost(ms, options?.signal);
155
147
  }
156
148
  #frame(signal) {
157
149
  return scheduleHost((complete) => {
@@ -159,18 +151,12 @@ var FrameScheduler = class {
159
151
  return () => cancelAnimationFrame(handle);
160
152
  }, signal);
161
153
  }
162
- #sleep(ms, signal) {
163
- return scheduleHost((complete) => {
164
- const handle = setTimeout(complete, ms);
165
- return () => clearTimeout(handle);
166
- }, signal);
167
- }
168
154
  };
169
155
  //#endregion
170
156
  //#region src/browser/IdleScheduler.ts
171
157
  /**
172
- * The idle-time {@link SchedulerInterface} — a browser cooperative-yield backend whose
173
- * `yield` resumes when the host is idle via `requestIdleCallback`, falling back to a
158
+ * Implements the idle-time {@link SchedulerInterface} — a browser cooperative-yield backend whose
159
+ * `yield` resumes when the host is idle through `requestIdleCallback`, falling back to a
174
160
  * zero-delay macrotask where it is absent.
175
161
  *
176
162
  * @remarks
@@ -178,8 +164,8 @@ var FrameScheduler = class {
178
164
  * `yield()` waits on it, so the resumption happens when the browser has spare time after
179
165
  * rendering and input — ideal for low-priority background work that must not contend with
180
166
  * the user. The capability is feature-detected through a guard (`isFunction`), never an
181
- * `as` (AGENTS §14). Where the API is absent (Safari today), it **falls back** to a
182
- * `setTimeout(0)` macrotask — still a real host-turn, just not idle-gated. `delay(ms)` is
167
+ * `as`. Where the API is absent (Safari today), it **falls back** to a
168
+ * `setTimeout(0)` macrotask — still a real host-turn, not idle-gated. `delay(ms)` is
183
169
  * always a real `setTimeout`. `options.priority` is accepted for contract compliance but a
184
170
  * no-op — idle scheduling has no priority dimension.
185
171
  * - **Abort fidelity is verbatim, with cleanup.** The shared `scheduleHost` lifecycle links
@@ -202,29 +188,28 @@ var FrameScheduler = class {
202
188
  */
203
189
  var IdleScheduler = class {
204
190
  /**
205
- * Yield control to the host until it is idle via `requestIdleCallback` (or a
206
- * `setTimeout(0)` macrotask where the API is absent), then resume; abort rejects with
191
+ * Yields control to the host until it is idle through `requestIdleCallback` (or a
192
+ * `setTimeout(0)` macrotask where the API is absent), then resumes; abort rejects with
207
193
  * `signal.reason`.
208
194
  */
209
195
  yield(options) {
210
- const idle = this.#idleAPI();
211
- if (idle === void 0) return this.#sleep(0, options?.signal);
196
+ const idle = this.#idleCallback();
197
+ if (idle === void 0) return delayHost(0, options?.signal);
212
198
  return this.#idle(idle, options?.signal);
213
199
  }
214
200
  /**
215
- * Resume after at least `ms` milliseconds via `setTimeout`; abort rejects with
201
+ * Resumes after at least `ms` milliseconds through `setTimeout`; abort rejects with
216
202
  * `signal.reason`.
217
203
  *
218
204
  * @remarks
219
- * `ms` should be a non-negative finite number. The primitive does no validation: it
220
- * passes `ms` straight to the host `setTimeout`, which clamps a negative value or
221
- * `NaN` to ~0 — so an out-of-domain `ms` resolves on the next host turn rather than
222
- * throwing.
205
+ * Pass a non-negative finite `ms`. The primitive does no validation: it passes `ms`
206
+ * straight to the host `setTimeout`, which clamps a negative value or `NaN` to ~0 — so an
207
+ * out-of-domain `ms` resolves on the next host turn rather than throwing.
223
208
  */
224
209
  delay(ms, options) {
225
- return this.#sleep(ms, options?.signal);
210
+ return delayHost(ms, options?.signal);
226
211
  }
227
- #idleAPI() {
212
+ #idleCallback() {
228
213
  const request = Reflect.get(globalThis, "requestIdleCallback");
229
214
  const cancel = Reflect.get(globalThis, "cancelIdleCallback");
230
215
  if (!isFunction(request) || !isFunction(cancel)) return void 0;
@@ -239,12 +224,6 @@ var IdleScheduler = class {
239
224
  return () => idle.cancel(handle);
240
225
  }, signal);
241
226
  }
242
- #sleep(ms, signal) {
243
- return scheduleHost((complete) => {
244
- const handle = setTimeout(complete, ms);
245
- return () => clearTimeout(handle);
246
- }, signal);
247
- }
248
227
  #request(request, callback) {
249
228
  return Number(Reflect.apply(request, globalThis, [callback]));
250
229
  }
@@ -255,7 +234,7 @@ var IdleScheduler = class {
255
234
  //#endregion
256
235
  //#region src/browser/factories.ts
257
236
  /**
258
- * Create the browser-native cooperative-yield {@link SchedulerInterface} — `yield()` uses
237
+ * Creates the browser-native cooperative-yield {@link SchedulerInterface} — `yield()` uses
259
238
  * the Prioritized Task Scheduling API (`scheduler.postTask`) at the requested priority
260
239
  * when present, falling back to a `setTimeout(0)` macrotask; `delay(ms)` is a real
261
240
  * `setTimeout`.
@@ -283,13 +262,13 @@ function createBrowserScheduler() {
283
262
  return new BrowserScheduler();
284
263
  }
285
264
  /**
286
- * Create the frame-aligned cooperative-yield {@link SchedulerInterface} — `yield()` resumes
287
- * just before the next paint via `requestAnimationFrame`; `delay(ms)` is a real
265
+ * Creates the frame-aligned cooperative-yield {@link SchedulerInterface} — `yield()` resumes
266
+ * just before the next paint through `requestAnimationFrame`; `delay(ms)` is a real
288
267
  * `setTimeout`.
289
268
  *
290
269
  * @remarks
291
- * Use it for work that should batch per render frame (animation, incremental DOM updates)
292
- * and naturally pause while the tab is hidden. `yield` is abort-aware: pass `options.signal`
270
+ * Use it for work that batches per render frame (animation, incremental DOM updates) and
271
+ * naturally pauses while the tab is hidden. `yield` is abort-aware: pass `options.signal`
293
272
  * and a pending yield rejects with the signal's `reason` verbatim, cancelling the pending
294
273
  * frame request. `options.priority` is accepted but a no-op — a frame callback has no
295
274
  * priority dimension.
@@ -308,8 +287,8 @@ function createFrameScheduler() {
308
287
  return new FrameScheduler();
309
288
  }
310
289
  /**
311
- * Create the idle-time cooperative-yield {@link SchedulerInterface} — `yield()` resumes when
312
- * the host is idle via `requestIdleCallback` when present, falling back to a `setTimeout(0)`
290
+ * Creates the idle-time cooperative-yield {@link SchedulerInterface} — `yield()` resumes when
291
+ * the host is idle through `requestIdleCallback` when present, falling back to a `setTimeout(0)`
313
292
  * macrotask; `delay(ms)` is a real `setTimeout`.
314
293
  *
315
294
  * @remarks
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/browser/constants.ts","../../../src/browser/BrowserScheduler.ts","../../../src/browser/FrameScheduler.ts","../../../src/browser/IdleScheduler.ts","../../../src/browser/factories.ts"],"sourcesContent":["import type { SchedulerPriority } from '@src/core'\n\n/**\n * The browser-native `postTask` priority for each portable {@link SchedulerPriority} — the\n * Prioritized Task Scheduling API's three levels.\n *\n * @remarks\n * A `user` hint maps to the most urgent `'user-blocking'`, `normal` to the default\n * `'user-visible'`, and `background` to `'background'`. {@link BrowserScheduler} reads this\n * map to translate the caller's portable priority into the value passed to\n * `scheduler.postTask`, so the urgency hint is honoured by the host.\n */\nexport const POST_TASK_PRIORITY: Readonly<Record<SchedulerPriority, string>> = Object.freeze({\n\tuser: 'user-blocking',\n\tnormal: 'user-visible',\n\tbackground: 'background',\n})\n","import type { SchedulerInterface, SchedulerOptions, SchedulerPriority } from '@src/core'\nimport { scheduleHost } from '@src/core'\nimport { isFunction, isPromise, isRecord } from '@orkestrel/contract'\nimport { POST_TASK_PRIORITY } from './constants.js'\n\n/**\n * The browser {@link SchedulerInterface} — the browser-native cooperative-yield backend\n * built on the Prioritized Task Scheduling API (`scheduler.postTask`), falling back to a\n * zero-delay macrotask where it is absent.\n *\n * @remarks\n * - **`yield` prefers `scheduler.postTask`, honouring priority.** When `globalThis`\n * exposes a `scheduler` with a `postTask` method, `yield()` posts a task at the mapped\n * priority (`user` → `'user-blocking'`, `normal` → `'user-visible'`, `background` →\n * `'background'`), so the host genuinely regains control and the urgency hint is\n * honoured. The capability is feature-detected through guards (`isRecord` / `isFunction`),\n * never an `as` (AGENTS §14). Where the API is absent (Firefox today, older engines),\n * it **falls back** to a `setTimeout(0)` macrotask — still a real host-turn, just\n * without priority. `delay(ms)` is always a real `setTimeout`.\n * - **Abort fidelity is verbatim.** The shared `scheduleHost` lifecycle links an owned\n * settlement composite before scheduling, preserving the exact caller reason without\n * invoking caller-owned signal methods. The caller signal is NOT handed to `postTask`;\n * an internal controller cancels that native task. An unexpected native promise rejection\n * is routed back as the exact host failure instead of being discarded.\n * - **Event-free.** A pure functional primitive — no Emitter, no events.\n *\n * @example\n * ```ts\n * import { createAbort } from '@orkestrel/abort'\n * import { BrowserScheduler } from '@orkestrel/workflow/browser'\n *\n * const abort = createAbort()\n * const scheduler = new BrowserScheduler()\n * while (!abort.signal.aborted) {\n * \tdoSomeWork()\n * \tawait scheduler.yield({ priority: 'background', signal: abort.signal })\n * }\n * ```\n */\nexport class BrowserScheduler implements SchedulerInterface {\n\t/**\n\t * Yield control to the host via `scheduler.postTask` at the given priority (or a\n\t * `setTimeout(0)` macrotask where the API is absent), then resume; abort rejects with\n\t * `signal.reason`.\n\t */\n\tyield(options?: SchedulerOptions): Promise<void> {\n\t\tconst post = this.#postTask()\n\t\tif (post === undefined) return this.#timer(0, options?.signal)\n\t\treturn this.#yieldVia(post, options?.priority ?? 'normal', options?.signal)\n\t}\n\n\t/**\n\t * Resume after at least `ms` milliseconds via `setTimeout`; abort rejects with\n\t * `signal.reason`.\n\t *\n\t * @remarks\n\t * `ms` should be a non-negative finite number. The primitive does no validation: it\n\t * passes `ms` straight to the host `setTimeout`, which clamps a negative value or\n\t * `NaN` to ~0 — so an out-of-domain `ms` resolves on the next host turn rather than\n\t * throwing.\n\t */\n\tdelay(ms: number, options?: SchedulerOptions): Promise<void> {\n\t\treturn this.#timer(ms, options?.signal)\n\t}\n\n\t// === Private\n\n\t// Feature-detect the Prioritized Task Scheduling API through guards (no `as`): the\n\t// global `scheduler` must be a record carrying a callable `postTask`. Returns the\n\t// narrowed `postTask` function, or `undefined` when the API is absent (the fallback).\n\t#postTask(): ((callback: () => void, options: Record<string, unknown>) => unknown) | undefined {\n\t\tconst candidate: unknown = Reflect.get(globalThis, 'scheduler')\n\t\tif (!isRecord(candidate)) return undefined\n\t\tconst post = candidate.postTask\n\t\tif (!isFunction(post)) return undefined\n\t\treturn (callback, options) => Reflect.apply(post, candidate, [callback, options])\n\t}\n\n\t// A `scheduler.postTask` boundary with an internal cancellation controller; `scheduleHost`\n\t// owns caller linking and first-settlement arbitration, including native promise failure.\n\t#yieldVia(\n\t\tpost: (callback: () => void, options: Record<string, unknown>) => unknown,\n\t\tpriority: SchedulerPriority,\n\t\tsignal?: AbortSignal,\n\t): Promise<void> {\n\t\treturn scheduleHost((complete, failure) => {\n\t\t\tconst internal = new AbortController()\n\t\t\tconst task = post(complete, {\n\t\t\t\tpriority: POST_TASK_PRIORITY[priority],\n\t\t\t\tsignal: internal.signal,\n\t\t\t})\n\t\t\tif (isPromise(task)) void task.catch(failure)\n\t\t\treturn () => internal.abort()\n\t\t}, signal)\n\t}\n\n\t// The browser timer boundary shared by `delay` and fallback `yield`.\n\t#timer(ms: number, signal?: AbortSignal): Promise<void> {\n\t\treturn scheduleHost((complete) => {\n\t\t\tconst handle = setTimeout(complete, ms)\n\t\t\treturn () => clearTimeout(handle)\n\t\t}, signal)\n\t}\n}\n","import type { SchedulerInterface, SchedulerOptions } from '@src/core'\nimport { scheduleHost } from '@src/core'\n\n/**\n * The frame-aligned {@link SchedulerInterface} — a browser cooperative-yield backend\n * whose `yield` resumes just before the next paint via `requestAnimationFrame`.\n *\n * @remarks\n * - **`yield` resumes before the next paint.** `yield()` waits on `requestAnimationFrame`,\n * so the resumption is aligned to the browser's render loop — ideal for work that\n * should batch per frame (animation, incremental DOM updates) and pause while the tab\n * is hidden (the host throttles rAF). `delay(ms)` is a real `setTimeout`, unaligned to\n * frames. `options.priority` is accepted for contract compliance but a no-op — a frame\n * callback has no priority dimension.\n * - **Abort fidelity is verbatim, with cleanup.** The shared `scheduleHost` lifecycle links\n * an owned settlement composite before requesting a frame, never invokes caller-owned\n * signal methods, and cancels the native handle when abort wins.\n * - **Event-free.** A pure functional primitive — no Emitter, no events.\n *\n * @example\n * ```ts\n * import { createAbort } from '@orkestrel/abort'\n * import { FrameScheduler } from '@orkestrel/workflow/browser'\n *\n * const abort = createAbort()\n * const scheduler = new FrameScheduler()\n * while (!abort.signal.aborted) {\n * \trenderOneFrameOfWork()\n * \tawait scheduler.yield({ signal: abort.signal }) // resume before the next paint\n * }\n * ```\n */\nexport class FrameScheduler implements SchedulerInterface {\n\t/**\n\t * Yield control to the host until just before the next paint via\n\t * `requestAnimationFrame`, then resume; abort rejects with `signal.reason`.\n\t */\n\tyield(options?: SchedulerOptions): Promise<void> {\n\t\treturn this.#frame(options?.signal)\n\t}\n\n\t/**\n\t * Resume after at least `ms` milliseconds via `setTimeout`; abort rejects with\n\t * `signal.reason`.\n\t *\n\t * @remarks\n\t * `ms` should be a non-negative finite number. The primitive does no validation: it\n\t * passes `ms` straight to the host `setTimeout`, which clamps a negative value or\n\t * `NaN` to ~0 — so an out-of-domain `ms` resolves on the next host turn rather than\n\t * throwing.\n\t */\n\tdelay(ms: number, options?: SchedulerOptions): Promise<void> {\n\t\treturn this.#sleep(ms, options?.signal)\n\t}\n\n\t// === Private\n\n\t// The frame request boundary; `scheduleHost` owns cancellation lifecycle.\n\t#frame(signal?: AbortSignal): Promise<void> {\n\t\treturn scheduleHost((complete) => {\n\t\t\tconst handle = requestAnimationFrame(complete)\n\t\t\treturn () => cancelAnimationFrame(handle)\n\t\t}, signal)\n\t}\n\n\t// The browser timer boundary; `scheduleHost` owns cancellation lifecycle.\n\t#sleep(ms: number, signal?: AbortSignal): Promise<void> {\n\t\treturn scheduleHost((complete) => {\n\t\t\tconst handle = setTimeout(complete, ms)\n\t\t\treturn () => clearTimeout(handle)\n\t\t}, signal)\n\t}\n}\n","import type { SchedulerInterface, SchedulerOptions } from '@src/core'\nimport { scheduleHost } from '@src/core'\nimport type { AnyFunction } from '@orkestrel/contract'\nimport type { IdleAPI } from './types.js'\nimport { isFunction } from '@orkestrel/contract'\n\n/**\n * The idle-time {@link SchedulerInterface} — a browser cooperative-yield backend whose\n * `yield` resumes when the host is idle via `requestIdleCallback`, falling back to a\n * zero-delay macrotask where it is absent.\n *\n * @remarks\n * - **`yield` resumes during idle time.** When `globalThis` exposes `requestIdleCallback`,\n * `yield()` waits on it, so the resumption happens when the browser has spare time after\n * rendering and input — ideal for low-priority background work that must not contend with\n * the user. The capability is feature-detected through a guard (`isFunction`), never an\n * `as` (AGENTS §14). Where the API is absent (Safari today), it **falls back** to a\n * `setTimeout(0)` macrotask — still a real host-turn, just not idle-gated. `delay(ms)` is\n * always a real `setTimeout`. `options.priority` is accepted for contract compliance but a\n * no-op — idle scheduling has no priority dimension.\n * - **Abort fidelity is verbatim, with cleanup.** The shared `scheduleHost` lifecycle links\n * an owned settlement composite before scheduling, never invokes caller-owned signal\n * methods, and cancels the idle callback or fallback timer when abort wins.\n * - **Event-free.** A pure functional primitive — no Emitter, no events.\n *\n * @example\n * ```ts\n * import { createAbort } from '@orkestrel/abort'\n * import { IdleScheduler } from '@orkestrel/workflow/browser'\n *\n * const abort = createAbort()\n * const scheduler = new IdleScheduler()\n * while (!abort.signal.aborted) {\n * \tdoLowPriorityWork()\n * \tawait scheduler.yield({ signal: abort.signal }) // resume when the host is idle\n * }\n * ```\n */\nexport class IdleScheduler implements SchedulerInterface {\n\t/**\n\t * Yield control to the host until it is idle via `requestIdleCallback` (or a\n\t * `setTimeout(0)` macrotask where the API is absent), then resume; abort rejects with\n\t * `signal.reason`.\n\t */\n\tyield(options?: SchedulerOptions): Promise<void> {\n\t\tconst idle = this.#idleAPI()\n\t\tif (idle === undefined) return this.#sleep(0, options?.signal)\n\t\treturn this.#idle(idle, options?.signal)\n\t}\n\n\t/**\n\t * Resume after at least `ms` milliseconds via `setTimeout`; abort rejects with\n\t * `signal.reason`.\n\t *\n\t * @remarks\n\t * `ms` should be a non-negative finite number. The primitive does no validation: it\n\t * passes `ms` straight to the host `setTimeout`, which clamps a negative value or\n\t * `NaN` to ~0 — so an out-of-domain `ms` resolves on the next host turn rather than\n\t * throwing.\n\t */\n\tdelay(ms: number, options?: SchedulerOptions): Promise<void> {\n\t\treturn this.#sleep(ms, options?.signal)\n\t}\n\n\t// === Private\n\n\t// Feature-detect `requestIdleCallback` / `cancelIdleCallback` off `globalThis` through\n\t// guards (no `as`): both must be callable. Returns the narrowed pair, or `undefined`\n\t// when the API is absent (the macrotask fallback).\n\t#idleAPI(): IdleAPI | undefined {\n\t\tconst request: unknown = Reflect.get(globalThis, 'requestIdleCallback')\n\t\tconst cancel: unknown = Reflect.get(globalThis, 'cancelIdleCallback')\n\t\tif (!isFunction(request) || !isFunction(cancel)) return undefined\n\t\treturn {\n\t\t\trequest: this.#request.bind(this, request),\n\t\t\tcancel: this.#cancel.bind(this, cancel),\n\t\t}\n\t}\n\n\t// The idle callback boundary; `scheduleHost` owns cancellation lifecycle.\n\t#idle(idle: IdleAPI, signal?: AbortSignal): Promise<void> {\n\t\treturn scheduleHost((complete) => {\n\t\t\tconst handle = idle.request(complete)\n\t\t\treturn () => idle.cancel(handle)\n\t\t}, signal)\n\t}\n\n\t// The browser timer boundary shared by `delay` and fallback `yield`.\n\t#sleep(ms: number, signal?: AbortSignal): Promise<void> {\n\t\treturn scheduleHost((complete) => {\n\t\t\tconst handle = setTimeout(complete, ms)\n\t\t\treturn () => clearTimeout(handle)\n\t\t}, signal)\n\t}\n\n\t#request(request: AnyFunction, callback: () => void): number {\n\t\treturn Number(Reflect.apply(request, globalThis, [callback]))\n\t}\n\n\t#cancel(cancel: AnyFunction, handle: number): void {\n\t\tReflect.apply(cancel, globalThis, [handle])\n\t}\n}\n","import type { SchedulerInterface } from '@src/core'\nimport { BrowserScheduler } from './BrowserScheduler.js'\nimport { FrameScheduler } from './FrameScheduler.js'\nimport { IdleScheduler } from './IdleScheduler.js'\n\n/**\n * Create the browser-native cooperative-yield {@link SchedulerInterface} — `yield()` uses\n * the Prioritized Task Scheduling API (`scheduler.postTask`) at the requested priority\n * when present, falling back to a `setTimeout(0)` macrotask; `delay(ms)` is a real\n * `setTimeout`.\n *\n * @remarks\n * The default browser scheduler: it honours `options.priority` (`user` /\n * `normal` / `background`) when `scheduler.postTask` is available and degrades to a plain\n * macrotask elsewhere. Both methods are abort-aware: pass `options.signal` and a pending\n * yield/delay rejects with the signal's exact `reason`; the shared owned-signal lifecycle\n * cancels the native handle without invoking caller listener methods. Prefer\n * {@link createFrameScheduler} for paint-aligned work or\n * {@link createIdleScheduler} for idle-time background work.\n *\n * @returns A {@link SchedulerInterface} backed by `scheduler.postTask` (or a macrotask)\n *\n * @example\n * ```ts\n * import { createBrowserScheduler } from '@orkestrel/workflow/browser'\n *\n * const scheduler = createBrowserScheduler()\n * await scheduler.yield({ priority: 'background' })\n * ```\n */\nexport function createBrowserScheduler(): SchedulerInterface {\n\treturn new BrowserScheduler()\n}\n\n/**\n * Create the frame-aligned cooperative-yield {@link SchedulerInterface} — `yield()` resumes\n * just before the next paint via `requestAnimationFrame`; `delay(ms)` is a real\n * `setTimeout`.\n *\n * @remarks\n * Use it for work that should batch per render frame (animation, incremental DOM updates)\n * and naturally pause while the tab is hidden. `yield` is abort-aware: pass `options.signal`\n * and a pending yield rejects with the signal's `reason` verbatim, cancelling the pending\n * frame request. `options.priority` is accepted but a no-op — a frame callback has no\n * priority dimension.\n *\n * @returns A {@link SchedulerInterface} backed by `requestAnimationFrame`\n *\n * @example\n * ```ts\n * import { createFrameScheduler } from '@orkestrel/workflow/browser'\n *\n * const scheduler = createFrameScheduler()\n * await scheduler.yield() // resumes before the next paint\n * ```\n */\nexport function createFrameScheduler(): SchedulerInterface {\n\treturn new FrameScheduler()\n}\n\n/**\n * Create the idle-time cooperative-yield {@link SchedulerInterface} — `yield()` resumes when\n * the host is idle via `requestIdleCallback` when present, falling back to a `setTimeout(0)`\n * macrotask; `delay(ms)` is a real `setTimeout`.\n *\n * @remarks\n * Use it for low-priority background work that must not contend with rendering or input.\n * Where `requestIdleCallback` is absent (Safari today) it degrades to a plain macrotask.\n * `yield` is abort-aware: pass `options.signal` and a pending yield rejects with the\n * signal's `reason` verbatim, cancelling the pending idle callback. `options.priority` is\n * accepted but a no-op — idle scheduling has no priority dimension.\n *\n * @returns A {@link SchedulerInterface} backed by `requestIdleCallback` (or a macrotask)\n *\n * @example\n * ```ts\n * import { createIdleScheduler } from '@orkestrel/workflow/browser'\n *\n * const scheduler = createIdleScheduler()\n * await scheduler.yield() // resumes when the host is idle\n * ```\n */\nexport function createIdleScheduler(): SchedulerInterface {\n\treturn new IdleScheduler()\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/browser/constants.ts","../../../src/browser/BrowserScheduler.ts","../../../src/browser/FrameScheduler.ts","../../../src/browser/IdleScheduler.ts","../../../src/browser/factories.ts"],"sourcesContent":["import type { SchedulerPriority } from '@src/core'\n\n/**\n * Maps each portable {@link SchedulerPriority} to the browser-native `postTask` priority — the\n * Prioritized Task Scheduling API's three levels.\n *\n * @remarks\n * A `user` hint maps to the most urgent `'user-blocking'`, `normal` to the default\n * `'user-visible'`, and `background` to `'background'`. {@link BrowserScheduler} reads this\n * map to translate the caller's portable priority into the value passed to\n * `scheduler.postTask`, so the urgency hint is honoured by the host.\n */\nexport const POST_TASK_PRIORITY: Readonly<Record<SchedulerPriority, string>> = Object.freeze({\n\tuser: 'user-blocking',\n\tnormal: 'user-visible',\n\tbackground: 'background',\n})\n","import type { SchedulerInterface, SchedulerOptions, SchedulerPriority } from '@src/core'\nimport { delayHost, scheduleHost } from '@src/core'\nimport { isFunction, isPromise, isRecord } from '@orkestrel/contract'\nimport { POST_TASK_PRIORITY } from './constants.js'\n\n/**\n * Implements the browser {@link SchedulerInterface} — the browser-native cooperative-yield backend\n * built on the Prioritized Task Scheduling API (`scheduler.postTask`), falling back to a\n * zero-delay macrotask where it is absent.\n *\n * @remarks\n * - **`yield` prefers `scheduler.postTask`, honouring priority.** When `globalThis`\n * exposes a `scheduler` with a `postTask` method, `yield()` posts a task at the mapped\n * priority (`user` → `'user-blocking'`, `normal` → `'user-visible'`, `background` →\n * `'background'`), so the host genuinely regains control and the urgency hint is\n * honoured. The capability is feature-detected through guards (`isRecord` / `isFunction`),\n * never an `as`. Where the API is absent (Firefox today, older engines),\n * it **falls back** to a `setTimeout(0)` macrotask — still a real host-turn, without\n * priority. `delay(ms)` is always a real `setTimeout`.\n * - **Abort fidelity is verbatim.** The shared `scheduleHost` lifecycle links an owned\n * settlement composite before scheduling, preserving the exact caller reason without\n * invoking caller-owned signal methods. The caller signal is NOT handed to `postTask`;\n * an internal controller cancels that native task. An unexpected native promise rejection\n * is routed back as the exact host failure instead of being discarded.\n * - **Event-free.** A pure functional primitive — no Emitter, no events.\n *\n * @example\n * ```ts\n * import { createAbort } from '@orkestrel/abort'\n * import { BrowserScheduler } from '@orkestrel/workflow/browser'\n *\n * const abort = createAbort()\n * const scheduler = new BrowserScheduler()\n * while (!abort.signal.aborted) {\n * \tdoSomeWork()\n * \tawait scheduler.yield({ priority: 'background', signal: abort.signal })\n * }\n * ```\n */\nexport class BrowserScheduler implements SchedulerInterface {\n\t/**\n\t * Yields control to the host through `scheduler.postTask` at the given priority (or a\n\t * `setTimeout(0)` macrotask where the API is absent), then resumes; abort rejects with\n\t * `signal.reason`.\n\t */\n\tyield(options?: SchedulerOptions): Promise<void> {\n\t\tconst post = this.#postTask()\n\t\tif (post === undefined) return delayHost(0, options?.signal)\n\t\treturn this.#yieldVia(post, options?.priority ?? 'normal', options?.signal)\n\t}\n\n\t/**\n\t * Resumes after at least `ms` milliseconds through `setTimeout`; abort rejects with\n\t * `signal.reason`.\n\t *\n\t * @remarks\n\t * Pass a non-negative finite `ms`. The primitive does no validation: it passes `ms`\n\t * straight to the host `setTimeout`, which clamps a negative value or `NaN` to ~0 — so an\n\t * out-of-domain `ms` resolves on the next host turn rather than throwing.\n\t */\n\tdelay(ms: number, options?: SchedulerOptions): Promise<void> {\n\t\treturn delayHost(ms, options?.signal)\n\t}\n\n\t// === Private\n\n\t// Feature-detect the Prioritized Task Scheduling API through guards (no `as`): the\n\t// global `scheduler` must be a record carrying a callable `postTask`. Returns the\n\t// narrowed `postTask` function, or `undefined` when the API is absent (the fallback).\n\t#postTask(): ((callback: () => void, options: Record<string, unknown>) => unknown) | undefined {\n\t\tconst candidate: unknown = Reflect.get(globalThis, 'scheduler')\n\t\tif (!isRecord(candidate)) return undefined\n\t\tconst post = candidate.postTask\n\t\tif (!isFunction(post)) return undefined\n\t\treturn (callback, options) => Reflect.apply(post, candidate, [callback, options])\n\t}\n\n\t// A `scheduler.postTask` boundary with an internal cancellation controller; `scheduleHost`\n\t// owns caller linking and first-settlement arbitration, including native promise failure.\n\t#yieldVia(\n\t\tpost: (callback: () => void, options: Record<string, unknown>) => unknown,\n\t\tpriority: SchedulerPriority,\n\t\tsignal?: AbortSignal,\n\t): Promise<void> {\n\t\treturn scheduleHost((complete, failure) => {\n\t\t\tconst internal = new AbortController()\n\t\t\tconst task = post(complete, {\n\t\t\t\tpriority: POST_TASK_PRIORITY[priority],\n\t\t\t\tsignal: internal.signal,\n\t\t\t})\n\t\t\tif (isPromise(task)) void task.catch(failure)\n\t\t\treturn () => internal.abort()\n\t\t}, signal)\n\t}\n}\n","import type { SchedulerInterface, SchedulerOptions } from '@src/core'\nimport { delayHost, scheduleHost } from '@src/core'\n\n/**\n * Implements the frame-aligned {@link SchedulerInterface} — a browser cooperative-yield backend\n * whose `yield` resumes just before the next paint through `requestAnimationFrame`.\n *\n * @remarks\n * - **`yield` resumes before the next paint.** `yield()` waits on `requestAnimationFrame`,\n * so the resumption is aligned to the browser's render loop — ideal for work that\n * batches per frame (animation, incremental DOM updates) and pauses while the tab is\n * hidden (the host throttles rAF). `delay(ms)` is a real `setTimeout`, unaligned to\n * frames. `options.priority` is accepted for contract compliance but a no-op — a frame\n * callback has no priority dimension.\n * - **Abort fidelity is verbatim, with cleanup.** The shared `scheduleHost` lifecycle links\n * an owned settlement composite before requesting a frame, never invokes caller-owned\n * signal methods, and cancels the native handle when abort wins.\n * - **Event-free.** A pure functional primitive — no Emitter, no events.\n *\n * @example\n * ```ts\n * import { createAbort } from '@orkestrel/abort'\n * import { FrameScheduler } from '@orkestrel/workflow/browser'\n *\n * const abort = createAbort()\n * const scheduler = new FrameScheduler()\n * while (!abort.signal.aborted) {\n * \trenderOneFrameOfWork()\n * \tawait scheduler.yield({ signal: abort.signal }) // resume before the next paint\n * }\n * ```\n */\nexport class FrameScheduler implements SchedulerInterface {\n\t/**\n\t * Yields control to the host until just before the next paint through\n\t * `requestAnimationFrame`, then resumes; abort rejects with `signal.reason`.\n\t */\n\tyield(options?: SchedulerOptions): Promise<void> {\n\t\treturn this.#frame(options?.signal)\n\t}\n\n\t/**\n\t * Resumes after at least `ms` milliseconds through `setTimeout`; abort rejects with\n\t * `signal.reason`.\n\t *\n\t * @remarks\n\t * Pass a non-negative finite `ms`. The primitive does no validation: it passes `ms`\n\t * straight to the host `setTimeout`, which clamps a negative value or `NaN` to ~0 — so an\n\t * out-of-domain `ms` resolves on the next host turn rather than throwing.\n\t */\n\tdelay(ms: number, options?: SchedulerOptions): Promise<void> {\n\t\treturn delayHost(ms, options?.signal)\n\t}\n\n\t// === Private\n\n\t// The frame request boundary; `scheduleHost` owns cancellation lifecycle.\n\t#frame(signal?: AbortSignal): Promise<void> {\n\t\treturn scheduleHost((complete) => {\n\t\t\tconst handle = requestAnimationFrame(complete)\n\t\t\treturn () => cancelAnimationFrame(handle)\n\t\t}, signal)\n\t}\n}\n","import type { SchedulerInterface, SchedulerOptions } from '@src/core'\nimport type { AnyFunction } from '@orkestrel/contract'\nimport type { IdleInterface } from './types.js'\nimport { delayHost, scheduleHost } from '@src/core'\nimport { isFunction } from '@orkestrel/contract'\n\n/**\n * Implements the idle-time {@link SchedulerInterface} — a browser cooperative-yield backend whose\n * `yield` resumes when the host is idle through `requestIdleCallback`, falling back to a\n * zero-delay macrotask where it is absent.\n *\n * @remarks\n * - **`yield` resumes during idle time.** When `globalThis` exposes `requestIdleCallback`,\n * `yield()` waits on it, so the resumption happens when the browser has spare time after\n * rendering and input — ideal for low-priority background work that must not contend with\n * the user. The capability is feature-detected through a guard (`isFunction`), never an\n * `as`. Where the API is absent (Safari today), it **falls back** to a\n * `setTimeout(0)` macrotask — still a real host-turn, not idle-gated. `delay(ms)` is\n * always a real `setTimeout`. `options.priority` is accepted for contract compliance but a\n * no-op — idle scheduling has no priority dimension.\n * - **Abort fidelity is verbatim, with cleanup.** The shared `scheduleHost` lifecycle links\n * an owned settlement composite before scheduling, never invokes caller-owned signal\n * methods, and cancels the idle callback or fallback timer when abort wins.\n * - **Event-free.** A pure functional primitive — no Emitter, no events.\n *\n * @example\n * ```ts\n * import { createAbort } from '@orkestrel/abort'\n * import { IdleScheduler } from '@orkestrel/workflow/browser'\n *\n * const abort = createAbort()\n * const scheduler = new IdleScheduler()\n * while (!abort.signal.aborted) {\n * \tdoLowPriorityWork()\n * \tawait scheduler.yield({ signal: abort.signal }) // resume when the host is idle\n * }\n * ```\n */\nexport class IdleScheduler implements SchedulerInterface {\n\t/**\n\t * Yields control to the host until it is idle through `requestIdleCallback` (or a\n\t * `setTimeout(0)` macrotask where the API is absent), then resumes; abort rejects with\n\t * `signal.reason`.\n\t */\n\tyield(options?: SchedulerOptions): Promise<void> {\n\t\tconst idle = this.#idleCallback()\n\t\tif (idle === undefined) return delayHost(0, options?.signal)\n\t\treturn this.#idle(idle, options?.signal)\n\t}\n\n\t/**\n\t * Resumes after at least `ms` milliseconds through `setTimeout`; abort rejects with\n\t * `signal.reason`.\n\t *\n\t * @remarks\n\t * Pass a non-negative finite `ms`. The primitive does no validation: it passes `ms`\n\t * straight to the host `setTimeout`, which clamps a negative value or `NaN` to ~0 — so an\n\t * out-of-domain `ms` resolves on the next host turn rather than throwing.\n\t */\n\tdelay(ms: number, options?: SchedulerOptions): Promise<void> {\n\t\treturn delayHost(ms, options?.signal)\n\t}\n\n\t// === Private\n\n\t// Feature-detect `requestIdleCallback` / `cancelIdleCallback` off `globalThis` through\n\t// guards (no `as`): both must be callable. Returns the narrowed pair, or `undefined`\n\t// when the API is absent (the macrotask fallback).\n\t#idleCallback(): IdleInterface | undefined {\n\t\tconst request: unknown = Reflect.get(globalThis, 'requestIdleCallback')\n\t\tconst cancel: unknown = Reflect.get(globalThis, 'cancelIdleCallback')\n\t\tif (!isFunction(request) || !isFunction(cancel)) return undefined\n\t\treturn {\n\t\t\trequest: this.#request.bind(this, request),\n\t\t\tcancel: this.#cancel.bind(this, cancel),\n\t\t}\n\t}\n\n\t// The idle yield boundary; `scheduleHost` owns cancellation lifecycle.\n\t#idle(idle: IdleInterface, signal?: AbortSignal): Promise<void> {\n\t\treturn scheduleHost((complete) => {\n\t\t\tconst handle = idle.request(complete)\n\t\t\treturn () => idle.cancel(handle)\n\t\t}, signal)\n\t}\n\n\t#request(request: AnyFunction, callback: () => void): number {\n\t\treturn Number(Reflect.apply(request, globalThis, [callback]))\n\t}\n\n\t#cancel(cancel: AnyFunction, handle: number): void {\n\t\tReflect.apply(cancel, globalThis, [handle])\n\t}\n}\n","import type { SchedulerInterface } from '@src/core'\nimport { BrowserScheduler } from './BrowserScheduler.js'\nimport { FrameScheduler } from './FrameScheduler.js'\nimport { IdleScheduler } from './IdleScheduler.js'\n\n/**\n * Creates the browser-native cooperative-yield {@link SchedulerInterface} — `yield()` uses\n * the Prioritized Task Scheduling API (`scheduler.postTask`) at the requested priority\n * when present, falling back to a `setTimeout(0)` macrotask; `delay(ms)` is a real\n * `setTimeout`.\n *\n * @remarks\n * The default browser scheduler: it honours `options.priority` (`user` /\n * `normal` / `background`) when `scheduler.postTask` is available and degrades to a plain\n * macrotask elsewhere. Both methods are abort-aware: pass `options.signal` and a pending\n * yield/delay rejects with the signal's exact `reason`; the shared owned-signal lifecycle\n * cancels the native handle without invoking caller listener methods. Prefer\n * {@link createFrameScheduler} for paint-aligned work or\n * {@link createIdleScheduler} for idle-time background work.\n *\n * @returns A {@link SchedulerInterface} backed by `scheduler.postTask` (or a macrotask)\n *\n * @example\n * ```ts\n * import { createBrowserScheduler } from '@orkestrel/workflow/browser'\n *\n * const scheduler = createBrowserScheduler()\n * await scheduler.yield({ priority: 'background' })\n * ```\n */\nexport function createBrowserScheduler(): SchedulerInterface {\n\treturn new BrowserScheduler()\n}\n\n/**\n * Creates the frame-aligned cooperative-yield {@link SchedulerInterface} — `yield()` resumes\n * just before the next paint through `requestAnimationFrame`; `delay(ms)` is a real\n * `setTimeout`.\n *\n * @remarks\n * Use it for work that batches per render frame (animation, incremental DOM updates) and\n * naturally pauses while the tab is hidden. `yield` is abort-aware: pass `options.signal`\n * and a pending yield rejects with the signal's `reason` verbatim, cancelling the pending\n * frame request. `options.priority` is accepted but a no-op — a frame callback has no\n * priority dimension.\n *\n * @returns A {@link SchedulerInterface} backed by `requestAnimationFrame`\n *\n * @example\n * ```ts\n * import { createFrameScheduler } from '@orkestrel/workflow/browser'\n *\n * const scheduler = createFrameScheduler()\n * await scheduler.yield() // resumes before the next paint\n * ```\n */\nexport function createFrameScheduler(): SchedulerInterface {\n\treturn new FrameScheduler()\n}\n\n/**\n * Creates the idle-time cooperative-yield {@link SchedulerInterface} — `yield()` resumes when\n * the host is idle through `requestIdleCallback` when present, falling back to a `setTimeout(0)`\n * macrotask; `delay(ms)` is a real `setTimeout`.\n *\n * @remarks\n * Use it for low-priority background work that must not contend with rendering or input.\n * Where `requestIdleCallback` is absent (Safari today) it degrades to a plain macrotask.\n * `yield` is abort-aware: pass `options.signal` and a pending yield rejects with the\n * signal's `reason` verbatim, cancelling the pending idle callback. `options.priority` is\n * accepted but a no-op — idle scheduling has no priority dimension.\n *\n * @returns A {@link SchedulerInterface} backed by `requestIdleCallback` (or a macrotask)\n *\n * @example\n * ```ts\n * import { createIdleScheduler } from '@orkestrel/workflow/browser'\n *\n * const scheduler = createIdleScheduler()\n * await scheduler.yield() // resumes when the host is idle\n * ```\n */\nexport function createIdleScheduler(): SchedulerInterface {\n\treturn new IdleScheduler()\n}\n"],"mappings":"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"}