@orkestrel/workflow 0.0.5 → 0.0.7
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/dist/src/browser/index.d.ts +6 -6
- package/dist/src/browser/index.js +40 -30
- package/dist/src/browser/index.js.map +1 -1
- package/dist/src/core/index.cjs +63 -67
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +15 -13
- package/dist/src/core/index.d.ts +15 -13
- package/dist/src/core/index.js +63 -67
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +10 -8
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +4 -4
- package/dist/src/server/index.d.ts +4 -4
- package/dist/src/server/index.js +10 -8
- package/dist/src/server/index.js.map +1 -1
- package/package.json +23 -20
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { SchedulerInterface } from '../core/index.
|
|
2
|
-
import { SchedulerOptions } from '../core/index.
|
|
3
|
-
import { SchedulerPriority } from '../core/index.
|
|
1
|
+
import { SchedulerInterface } from '../core/index.ts';
|
|
2
|
+
import { SchedulerOptions } from '../core/index.ts';
|
|
3
|
+
import { SchedulerPriority } from '../core/index.ts';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* The browser {@link SchedulerInterface} — the browser-native cooperative-yield backend
|
|
@@ -31,7 +31,7 @@ import { SchedulerPriority } from '../core/index.js';
|
|
|
31
31
|
*
|
|
32
32
|
* @example
|
|
33
33
|
* ```ts
|
|
34
|
-
* import { createAbort } from '
|
|
34
|
+
* import { createAbort } from '@src/core'
|
|
35
35
|
* import { BrowserScheduler } from '@src/browser'
|
|
36
36
|
*
|
|
37
37
|
* const abort = createAbort()
|
|
@@ -160,7 +160,7 @@ export declare function createIdleScheduler(): SchedulerInterface;
|
|
|
160
160
|
*
|
|
161
161
|
* @example
|
|
162
162
|
* ```ts
|
|
163
|
-
* import { createAbort } from '
|
|
163
|
+
* import { createAbort } from '@src/core'
|
|
164
164
|
* import { FrameScheduler } from '@src/browser'
|
|
165
165
|
*
|
|
166
166
|
* const abort = createAbort()
|
|
@@ -232,7 +232,7 @@ export declare interface IdleAPI {
|
|
|
232
232
|
*
|
|
233
233
|
* @example
|
|
234
234
|
* ```ts
|
|
235
|
-
* import { createAbort } from '
|
|
235
|
+
* import { createAbort } from '@src/core'
|
|
236
236
|
* import { IdleScheduler } from '@src/browser'
|
|
237
237
|
*
|
|
238
238
|
* const abort = createAbort()
|
|
@@ -10,11 +10,11 @@ import { isFunction, isRecord } from "@orkestrel/contract";
|
|
|
10
10
|
* map to translate the caller's portable priority into the value passed to
|
|
11
11
|
* `scheduler.postTask`, so the urgency hint is honoured by the host.
|
|
12
12
|
*/
|
|
13
|
-
var POST_TASK_PRIORITY = {
|
|
13
|
+
var POST_TASK_PRIORITY = Object.freeze({
|
|
14
14
|
user: "user-blocking",
|
|
15
15
|
normal: "user-visible",
|
|
16
16
|
background: "background"
|
|
17
|
-
};
|
|
17
|
+
});
|
|
18
18
|
//#endregion
|
|
19
19
|
//#region src/browser/BrowserScheduler.ts
|
|
20
20
|
/**
|
|
@@ -92,10 +92,6 @@ var BrowserScheduler = class {
|
|
|
92
92
|
if (signal?.aborted === true) return Promise.reject(signal.reason);
|
|
93
93
|
return new Promise((resolve, reject) => {
|
|
94
94
|
const internal = new AbortController();
|
|
95
|
-
const onAbort = () => {
|
|
96
|
-
internal.abort();
|
|
97
|
-
reject(signal?.reason);
|
|
98
|
-
};
|
|
99
95
|
const task = post(() => {
|
|
100
96
|
signal?.removeEventListener("abort", onAbort);
|
|
101
97
|
resolve();
|
|
@@ -103,6 +99,7 @@ var BrowserScheduler = class {
|
|
|
103
99
|
priority: POST_TASK_PRIORITY[priority],
|
|
104
100
|
signal: internal.signal
|
|
105
101
|
});
|
|
102
|
+
const onAbort = this.#abortTask.bind(this, internal, reject, signal);
|
|
106
103
|
if (task instanceof Promise) task.catch(() => {});
|
|
107
104
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
108
105
|
});
|
|
@@ -113,17 +110,22 @@ var BrowserScheduler = class {
|
|
|
113
110
|
#timer(ms, signal) {
|
|
114
111
|
if (signal?.aborted === true) return Promise.reject(signal.reason);
|
|
115
112
|
return new Promise((resolve, reject) => {
|
|
116
|
-
const onAbort = () => {
|
|
117
|
-
clearTimeout(handle);
|
|
118
|
-
reject(signal?.reason);
|
|
119
|
-
};
|
|
120
113
|
const handle = setTimeout(() => {
|
|
121
114
|
signal?.removeEventListener("abort", onAbort);
|
|
122
115
|
resolve();
|
|
123
116
|
}, ms);
|
|
117
|
+
const onAbort = this.#abortTimeout.bind(this, handle, reject, signal);
|
|
124
118
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
125
119
|
});
|
|
126
120
|
}
|
|
121
|
+
#abortTask(internal, reject, signal) {
|
|
122
|
+
internal.abort();
|
|
123
|
+
reject(signal?.reason);
|
|
124
|
+
}
|
|
125
|
+
#abortTimeout(handle, reject, signal) {
|
|
126
|
+
clearTimeout(handle);
|
|
127
|
+
reject(signal?.reason);
|
|
128
|
+
}
|
|
127
129
|
};
|
|
128
130
|
//#endregion
|
|
129
131
|
//#region src/browser/FrameScheduler.ts
|
|
@@ -185,31 +187,33 @@ var FrameScheduler = class {
|
|
|
185
187
|
#frame(signal) {
|
|
186
188
|
if (signal?.aborted === true) return Promise.reject(signal.reason);
|
|
187
189
|
return new Promise((resolve, reject) => {
|
|
188
|
-
const onAbort = () => {
|
|
189
|
-
cancelAnimationFrame(handle);
|
|
190
|
-
reject(signal?.reason);
|
|
191
|
-
};
|
|
192
190
|
const handle = requestAnimationFrame(() => {
|
|
193
191
|
signal?.removeEventListener("abort", onAbort);
|
|
194
192
|
resolve();
|
|
195
193
|
});
|
|
194
|
+
const onAbort = this.#abortFrame.bind(this, handle, reject, signal);
|
|
196
195
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
197
196
|
});
|
|
198
197
|
}
|
|
199
198
|
#sleep(ms, signal) {
|
|
200
199
|
if (signal?.aborted === true) return Promise.reject(signal.reason);
|
|
201
200
|
return new Promise((resolve, reject) => {
|
|
202
|
-
const onAbort = () => {
|
|
203
|
-
clearTimeout(handle);
|
|
204
|
-
reject(signal?.reason);
|
|
205
|
-
};
|
|
206
201
|
const handle = setTimeout(() => {
|
|
207
202
|
signal?.removeEventListener("abort", onAbort);
|
|
208
203
|
resolve();
|
|
209
204
|
}, ms);
|
|
205
|
+
const onAbort = this.#abortTimeout.bind(this, handle, reject, signal);
|
|
210
206
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
211
207
|
});
|
|
212
208
|
}
|
|
209
|
+
#abortFrame(handle, reject, signal) {
|
|
210
|
+
cancelAnimationFrame(handle);
|
|
211
|
+
reject(signal?.reason);
|
|
212
|
+
}
|
|
213
|
+
#abortTimeout(handle, reject, signal) {
|
|
214
|
+
clearTimeout(handle);
|
|
215
|
+
reject(signal?.reason);
|
|
216
|
+
}
|
|
213
217
|
};
|
|
214
218
|
//#endregion
|
|
215
219
|
//#region src/browser/IdleScheduler.ts
|
|
@@ -279,40 +283,46 @@ var IdleScheduler = class {
|
|
|
279
283
|
const cancel = Reflect.get(globalThis, "cancelIdleCallback");
|
|
280
284
|
if (!isFunction(request) || !isFunction(cancel)) return void 0;
|
|
281
285
|
return {
|
|
282
|
-
request:
|
|
283
|
-
cancel: (
|
|
284
|
-
Reflect.apply(cancel, globalThis, [handle]);
|
|
285
|
-
}
|
|
286
|
+
request: this.#request.bind(this, request),
|
|
287
|
+
cancel: this.#cancel.bind(this, cancel)
|
|
286
288
|
};
|
|
287
289
|
}
|
|
288
290
|
#idle(idle, signal) {
|
|
289
291
|
if (signal?.aborted === true) return Promise.reject(signal.reason);
|
|
290
292
|
return new Promise((resolve, reject) => {
|
|
291
|
-
const onAbort = () => {
|
|
292
|
-
idle.cancel(handle);
|
|
293
|
-
reject(signal?.reason);
|
|
294
|
-
};
|
|
295
293
|
const handle = idle.request(() => {
|
|
296
294
|
signal?.removeEventListener("abort", onAbort);
|
|
297
295
|
resolve();
|
|
298
296
|
});
|
|
297
|
+
const onAbort = this.#abortIdle.bind(this, idle, handle, reject, signal);
|
|
299
298
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
300
299
|
});
|
|
301
300
|
}
|
|
302
301
|
#sleep(ms, signal) {
|
|
303
302
|
if (signal?.aborted === true) return Promise.reject(signal.reason);
|
|
304
303
|
return new Promise((resolve, reject) => {
|
|
305
|
-
const onAbort = () => {
|
|
306
|
-
clearTimeout(handle);
|
|
307
|
-
reject(signal?.reason);
|
|
308
|
-
};
|
|
309
304
|
const handle = setTimeout(() => {
|
|
310
305
|
signal?.removeEventListener("abort", onAbort);
|
|
311
306
|
resolve();
|
|
312
307
|
}, ms);
|
|
308
|
+
const onAbort = this.#abortTimeout.bind(this, handle, reject, signal);
|
|
313
309
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
314
310
|
});
|
|
315
311
|
}
|
|
312
|
+
#request(request, callback) {
|
|
313
|
+
return Number(Reflect.apply(request, globalThis, [callback]));
|
|
314
|
+
}
|
|
315
|
+
#cancel(cancel, handle) {
|
|
316
|
+
Reflect.apply(cancel, globalThis, [handle]);
|
|
317
|
+
}
|
|
318
|
+
#abortIdle(idle, handle, reject, signal) {
|
|
319
|
+
idle.cancel(handle);
|
|
320
|
+
reject(signal?.reason);
|
|
321
|
+
}
|
|
322
|
+
#abortTimeout(handle, reject, signal) {
|
|
323
|
+
clearTimeout(handle);
|
|
324
|
+
reject(signal?.reason);
|
|
325
|
+
}
|
|
316
326
|
};
|
|
317
327
|
//#endregion
|
|
318
328
|
//#region src/browser/factories.ts
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["#postTask","#macrotask","#yieldVia","#timer","#frame","#sleep","#idleAPI","#sleep","#idle"],"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>> = {\n\tuser: 'user-blocking',\n\tnormal: 'user-visible',\n\tbackground: 'background',\n}\n","import type { SchedulerInterface, SchedulerOptions, SchedulerPriority } from '@src/core'\nimport { isFunction, 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.** A pending `yield` / `delay` rejects with `signal.reason`\n * exactly — the value the caller passed, never wrapped or replaced. The discipline\n * mirrors the cross-environment default's `#sleep`: an already-aborted signal rejects\n * immediately WITHOUT scheduling; otherwise the host-turn is scheduled and a\n * `{ once: true }` abort listener attached, and the two settle paths are mutually\n * exclusive — the turn path removes the listener before resolving, and the abort path\n * cancels the scheduled turn before rejecting. The promise settles exactly once, with no\n * leaked task/timer and no leaked listener. The caller's `signal` is NOT handed to\n * `postTask` (whose own abort would reject with a platform `AbortError`, not the\n * caller's `reason`); instead an internal controller cancels the posted task while this\n * scheduler rejects with the verbatim `signal.reason`.\n * - **Event-free.** A pure functional primitive — no Emitter, no events.\n *\n * @example\n * ```ts\n * import { createAbort } from '@src/core'\n * import { BrowserScheduler } from '@src/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.#macrotask(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` host-turn at the mapped priority. The caller's signal is NOT\n\t// passed to `postTask` (its abort rejects with a platform `AbortError`, not the\n\t// caller's `reason`); instead an internal controller cancels the posted task on abort\n\t// while this rejects with the verbatim `signal.reason`. Settle-once, no leak: the task\n\t// path removes the abort listener before resolving; the abort path aborts the internal\n\t// controller (cancelling the task) before rejecting.\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\tif (signal?.aborted === true) return Promise.reject(signal.reason)\n\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\tconst internal = new AbortController()\n\t\t\tconst onAbort = () => {\n\t\t\t\tinternal.abort()\n\t\t\t\treject(signal?.reason)\n\t\t\t}\n\t\t\tconst task = post(\n\t\t\t\t() => {\n\t\t\t\t\tsignal?.removeEventListener('abort', onAbort)\n\t\t\t\t\tresolve()\n\t\t\t\t},\n\t\t\t\t{ priority: POST_TASK_PRIORITY[priority], signal: internal.signal },\n\t\t\t)\n\t\t\t// `postTask` returns a promise that rejects when the internal controller aborts;\n\t\t\t// swallow that rejection (the abort path already rejected with the real reason).\n\t\t\tif (task instanceof Promise) task.catch(() => {})\n\t\t\tsignal?.addEventListener('abort', onAbort, { once: true })\n\t\t})\n\t}\n\n\t// The `setTimeout(0)` macrotask fallback for `yield` when `postTask` is absent. Same\n\t// settle-once discipline as the core `#sleep`: an already-aborted signal rejects\n\t// without arming; otherwise the timer path removes the listener before resolving and\n\t// the abort path clears the timer before rejecting with `signal.reason`.\n\t#macrotask(signal?: AbortSignal): Promise<void> {\n\t\treturn this.#timer(0, signal)\n\t}\n\n\t// The abort-aware `setTimeout` sleep shared by `delay` and the `yield` macrotask\n\t// fallback. Settle-once, no leak (the core `#sleep` discipline): already-aborted →\n\t// reject without arming; the timer path removes the listener before resolving; the\n\t// abort path clears the timer before rejecting with the verbatim `signal.reason`.\n\t#timer(ms: number, signal?: AbortSignal): Promise<void> {\n\t\tif (signal?.aborted === true) return Promise.reject(signal.reason)\n\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\tconst onAbort = () => {\n\t\t\t\tclearTimeout(handle)\n\t\t\t\treject(signal?.reason)\n\t\t\t}\n\t\t\tconst handle = setTimeout(() => {\n\t\t\t\tsignal?.removeEventListener('abort', onAbort) // load-bearing: prevents a post-resolve reject\n\t\t\t\tresolve()\n\t\t\t}, ms)\n\t\t\tsignal?.addEventListener('abort', onAbort, { once: true })\n\t\t})\n\t}\n}\n","import type { SchedulerInterface, SchedulerOptions } 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.** A pending `yield` / `delay` rejects with\n * `signal.reason` exactly. The discipline mirrors the cross-environment default's\n * `#sleep`: an already-aborted signal rejects immediately WITHOUT scheduling a frame;\n * otherwise the frame is requested and a `{ once: true }` abort listener attached, and\n * the two settle paths are mutually exclusive — the frame path removes the listener\n * before resolving, and the abort path `cancelAnimationFrame`s the pending handle before\n * rejecting. The promise settles exactly once, with no leaked frame request and no\n * leaked listener.\n * - **Event-free.** A pure functional primitive — no Emitter, no events.\n *\n * @example\n * ```ts\n * import { createAbort } from '@src/core'\n * import { FrameScheduler } from '@src/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 `requestAnimationFrame` host-turn for `yield`. Resolves in the next frame\n\t// callback (before paint); rejects with `signal.reason` if already aborted (no frame\n\t// requested) or aborted while pending. Settle-once, no leak: the frame path removes the\n\t// abort listener before resolving; the abort path cancels the frame before rejecting.\n\t#frame(signal?: AbortSignal): Promise<void> {\n\t\tif (signal?.aborted === true) return Promise.reject(signal.reason)\n\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\tconst onAbort = () => {\n\t\t\t\tcancelAnimationFrame(handle)\n\t\t\t\treject(signal?.reason)\n\t\t\t}\n\t\t\tconst handle = requestAnimationFrame(() => {\n\t\t\t\tsignal?.removeEventListener('abort', onAbort) // load-bearing: prevents a post-resolve reject\n\t\t\t\tresolve()\n\t\t\t})\n\t\t\tsignal?.addEventListener('abort', onAbort, { once: true })\n\t\t})\n\t}\n\n\t// The abort-aware `setTimeout` sleep for `delay`. Same settle-once discipline as the\n\t// core `#sleep`: already-aborted → reject without arming; the timer path removes the\n\t// listener before resolving; the abort path clears the timer before rejecting with the\n\t// verbatim `signal.reason`.\n\t#sleep(ms: number, signal?: AbortSignal): Promise<void> {\n\t\tif (signal?.aborted === true) return Promise.reject(signal.reason)\n\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\tconst onAbort = () => {\n\t\t\t\tclearTimeout(handle)\n\t\t\t\treject(signal?.reason)\n\t\t\t}\n\t\t\tconst handle = setTimeout(() => {\n\t\t\t\tsignal?.removeEventListener('abort', onAbort) // load-bearing: prevents a post-resolve reject\n\t\t\t\tresolve()\n\t\t\t}, ms)\n\t\t\tsignal?.addEventListener('abort', onAbort, { once: true })\n\t\t})\n\t}\n}\n","import type { SchedulerInterface, SchedulerOptions } from '@src/core'\nimport { isFunction } from '@orkestrel/contract'\nimport type { IdleAPI } from './types.js'\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.** A pending `yield` / `delay` rejects with\n * `signal.reason` exactly. The discipline mirrors the cross-environment default's\n * `#sleep`: an already-aborted signal rejects immediately WITHOUT scheduling; otherwise\n * the idle callback (or fallback timer) is requested and a `{ once: true }` abort listener\n * attached, and the two settle paths are mutually exclusive — the resume path removes the\n * listener before resolving, and the abort path `cancelIdleCallback`s (or `clearTimeout`s)\n * the pending handle before rejecting. The promise settles exactly once, with no leaked\n * callback/timer and no leaked listener.\n * - **Event-free.** A pure functional primitive — no Emitter, no events.\n *\n * @example\n * ```ts\n * import { createAbort } from '@src/core'\n * import { IdleScheduler } from '@src/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: (callback) => Number(Reflect.apply(request, globalThis, [callback])),\n\t\t\tcancel: (handle) => {\n\t\t\t\tReflect.apply(cancel, globalThis, [handle])\n\t\t\t},\n\t\t}\n\t}\n\n\t// The `requestIdleCallback` host-turn for `yield`. Resolves in the idle callback;\n\t// rejects with `signal.reason` if already aborted (nothing scheduled) or aborted while\n\t// pending. Settle-once, no leak: the resume path removes the abort listener before\n\t// resolving; the abort path cancels the idle callback before rejecting.\n\t#idle(idle: IdleAPI, signal?: AbortSignal): Promise<void> {\n\t\tif (signal?.aborted === true) return Promise.reject(signal.reason)\n\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\tconst onAbort = () => {\n\t\t\t\tidle.cancel(handle)\n\t\t\t\treject(signal?.reason)\n\t\t\t}\n\t\t\tconst handle = idle.request(() => {\n\t\t\t\tsignal?.removeEventListener('abort', onAbort) // load-bearing: prevents a post-resolve reject\n\t\t\t\tresolve()\n\t\t\t})\n\t\t\tsignal?.addEventListener('abort', onAbort, { once: true })\n\t\t})\n\t}\n\n\t// The abort-aware `setTimeout` sleep shared by `delay` and the `yield` macrotask\n\t// fallback. Same settle-once discipline as the core `#sleep`: already-aborted → reject\n\t// without arming; the timer path removes the listener before resolving; the abort path\n\t// clears the timer before rejecting with the verbatim `signal.reason`.\n\t#sleep(ms: number, signal?: AbortSignal): Promise<void> {\n\t\tif (signal?.aborted === true) return Promise.reject(signal.reason)\n\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\tconst onAbort = () => {\n\t\t\t\tclearTimeout(handle)\n\t\t\t\treject(signal?.reason)\n\t\t\t}\n\t\t\tconst handle = setTimeout(() => {\n\t\t\t\tsignal?.removeEventListener('abort', onAbort) // load-bearing: prevents a post-resolve reject\n\t\t\t\tresolve()\n\t\t\t}, ms)\n\t\t\tsignal?.addEventListener('abort', onAbort, { once: true })\n\t\t})\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 `reason` verbatim, with full task/timer/listener\n * cleanup. Prefer {@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 '@src/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 '@src/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 '@src/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":["#postTask","#macrotask","#yieldVia","#timer","#abortTask","#abortTimeout","#frame","#sleep","#abortFrame","#abortTimeout","#idleAPI","#sleep","#idle","#request","#cancel","#abortIdle","#abortTimeout"],"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 { isFunction, 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.** A pending `yield` / `delay` rejects with `signal.reason`\n * exactly — the value the caller passed, never wrapped or replaced. The discipline\n * mirrors the cross-environment default's `#sleep`: an already-aborted signal rejects\n * immediately WITHOUT scheduling; otherwise the host-turn is scheduled and a\n * `{ once: true }` abort listener attached, and the two settle paths are mutually\n * exclusive — the turn path removes the listener before resolving, and the abort path\n * cancels the scheduled turn before rejecting. The promise settles exactly once, with no\n * leaked task/timer and no leaked listener. The caller's `signal` is NOT handed to\n * `postTask` (whose own abort would reject with a platform `AbortError`, not the\n * caller's `reason`); instead an internal controller cancels the posted task while this\n * scheduler rejects with the verbatim `signal.reason`.\n * - **Event-free.** A pure functional primitive — no Emitter, no events.\n *\n * @example\n * ```ts\n * import { createAbort } from '@src/core'\n * import { BrowserScheduler } from '@src/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.#macrotask(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` host-turn at the mapped priority. The caller's signal is NOT\n\t// passed to `postTask` (its abort rejects with a platform `AbortError`, not the\n\t// caller's `reason`); instead an internal controller cancels the posted task on abort\n\t// while this rejects with the verbatim `signal.reason`. Settle-once, no leak: the task\n\t// path removes the abort listener before resolving; the abort path aborts the internal\n\t// controller (cancelling the task) before rejecting.\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\tif (signal?.aborted === true) return Promise.reject(signal.reason)\n\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\tconst internal = new AbortController()\n\t\t\tconst task = post(\n\t\t\t\t() => {\n\t\t\t\t\tsignal?.removeEventListener('abort', onAbort)\n\t\t\t\t\tresolve()\n\t\t\t\t},\n\t\t\t\t{ priority: POST_TASK_PRIORITY[priority], signal: internal.signal },\n\t\t\t)\n\t\t\tconst onAbort = this.#abortTask.bind(this, internal, reject, signal)\n\t\t\t// `postTask` returns a promise that rejects when the internal controller aborts;\n\t\t\t// swallow that rejection (the abort path already rejected with the real reason).\n\t\t\tif (task instanceof Promise) task.catch(() => {})\n\t\t\tsignal?.addEventListener('abort', onAbort, { once: true })\n\t\t})\n\t}\n\n\t// The `setTimeout(0)` macrotask fallback for `yield` when `postTask` is absent. Same\n\t// settle-once discipline as the core `#sleep`: an already-aborted signal rejects\n\t// without arming; otherwise the timer path removes the listener before resolving and\n\t// the abort path clears the timer before rejecting with `signal.reason`.\n\t#macrotask(signal?: AbortSignal): Promise<void> {\n\t\treturn this.#timer(0, signal)\n\t}\n\n\t// The abort-aware `setTimeout` sleep shared by `delay` and the `yield` macrotask\n\t// fallback. Settle-once, no leak (the core `#sleep` discipline): already-aborted →\n\t// reject without arming; the timer path removes the listener before resolving; the\n\t// abort path clears the timer before rejecting with the verbatim `signal.reason`.\n\t#timer(ms: number, signal?: AbortSignal): Promise<void> {\n\t\tif (signal?.aborted === true) return Promise.reject(signal.reason)\n\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\tconst handle = setTimeout(() => {\n\t\t\t\tsignal?.removeEventListener('abort', onAbort) // load-bearing: prevents a post-resolve reject\n\t\t\t\tresolve()\n\t\t\t}, ms)\n\t\t\tconst onAbort = this.#abortTimeout.bind(this, handle, reject, signal)\n\t\t\tsignal?.addEventListener('abort', onAbort, { once: true })\n\t\t})\n\t}\n\n\t#abortTask(\n\t\tinternal: AbortController,\n\t\treject: (reason?: unknown) => void,\n\t\tsignal?: AbortSignal,\n\t): void {\n\t\tinternal.abort()\n\t\treject(signal?.reason)\n\t}\n\n\t#abortTimeout(\n\t\thandle: ReturnType<typeof setTimeout>,\n\t\treject: (reason?: unknown) => void,\n\t\tsignal?: AbortSignal,\n\t): void {\n\t\tclearTimeout(handle)\n\t\treject(signal?.reason)\n\t}\n}\n","import type { SchedulerInterface, SchedulerOptions } 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.** A pending `yield` / `delay` rejects with\n * `signal.reason` exactly. The discipline mirrors the cross-environment default's\n * `#sleep`: an already-aborted signal rejects immediately WITHOUT scheduling a frame;\n * otherwise the frame is requested and a `{ once: true }` abort listener attached, and\n * the two settle paths are mutually exclusive — the frame path removes the listener\n * before resolving, and the abort path `cancelAnimationFrame`s the pending handle before\n * rejecting. The promise settles exactly once, with no leaked frame request and no\n * leaked listener.\n * - **Event-free.** A pure functional primitive — no Emitter, no events.\n *\n * @example\n * ```ts\n * import { createAbort } from '@src/core'\n * import { FrameScheduler } from '@src/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 `requestAnimationFrame` host-turn for `yield`. Resolves in the next frame\n\t// callback (before paint); rejects with `signal.reason` if already aborted (no frame\n\t// requested) or aborted while pending. Settle-once, no leak: the frame path removes the\n\t// abort listener before resolving; the abort path cancels the frame before rejecting.\n\t#frame(signal?: AbortSignal): Promise<void> {\n\t\tif (signal?.aborted === true) return Promise.reject(signal.reason)\n\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\tconst handle = requestAnimationFrame(() => {\n\t\t\t\tsignal?.removeEventListener('abort', onAbort) // load-bearing: prevents a post-resolve reject\n\t\t\t\tresolve()\n\t\t\t})\n\t\t\tconst onAbort = this.#abortFrame.bind(this, handle, reject, signal)\n\t\t\tsignal?.addEventListener('abort', onAbort, { once: true })\n\t\t})\n\t}\n\n\t// The abort-aware `setTimeout` sleep for `delay`. Same settle-once discipline as the\n\t// core `#sleep`: already-aborted → reject without arming; the timer path removes the\n\t// listener before resolving; the abort path clears the timer before rejecting with the\n\t// verbatim `signal.reason`.\n\t#sleep(ms: number, signal?: AbortSignal): Promise<void> {\n\t\tif (signal?.aborted === true) return Promise.reject(signal.reason)\n\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\tconst handle = setTimeout(() => {\n\t\t\t\tsignal?.removeEventListener('abort', onAbort) // load-bearing: prevents a post-resolve reject\n\t\t\t\tresolve()\n\t\t\t}, ms)\n\t\t\tconst onAbort = this.#abortTimeout.bind(this, handle, reject, signal)\n\t\t\tsignal?.addEventListener('abort', onAbort, { once: true })\n\t\t})\n\t}\n\n\t#abortFrame(handle: number, reject: (reason?: unknown) => void, signal?: AbortSignal): void {\n\t\tcancelAnimationFrame(handle)\n\t\treject(signal?.reason)\n\t}\n\n\t#abortTimeout(\n\t\thandle: ReturnType<typeof setTimeout>,\n\t\treject: (reason?: unknown) => void,\n\t\tsignal?: AbortSignal,\n\t): void {\n\t\tclearTimeout(handle)\n\t\treject(signal?.reason)\n\t}\n}\n","import type { SchedulerInterface, SchedulerOptions } 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.** A pending `yield` / `delay` rejects with\n * `signal.reason` exactly. The discipline mirrors the cross-environment default's\n * `#sleep`: an already-aborted signal rejects immediately WITHOUT scheduling; otherwise\n * the idle callback (or fallback timer) is requested and a `{ once: true }` abort listener\n * attached, and the two settle paths are mutually exclusive — the resume path removes the\n * listener before resolving, and the abort path `cancelIdleCallback`s (or `clearTimeout`s)\n * the pending handle before rejecting. The promise settles exactly once, with no leaked\n * callback/timer and no leaked listener.\n * - **Event-free.** A pure functional primitive — no Emitter, no events.\n *\n * @example\n * ```ts\n * import { createAbort } from '@src/core'\n * import { IdleScheduler } from '@src/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 `requestIdleCallback` host-turn for `yield`. Resolves in the idle callback;\n\t// rejects with `signal.reason` if already aborted (nothing scheduled) or aborted while\n\t// pending. Settle-once, no leak: the resume path removes the abort listener before\n\t// resolving; the abort path cancels the idle callback before rejecting.\n\t#idle(idle: IdleAPI, signal?: AbortSignal): Promise<void> {\n\t\tif (signal?.aborted === true) return Promise.reject(signal.reason)\n\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\tconst handle = idle.request(() => {\n\t\t\t\tsignal?.removeEventListener('abort', onAbort) // load-bearing: prevents a post-resolve reject\n\t\t\t\tresolve()\n\t\t\t})\n\t\t\tconst onAbort = this.#abortIdle.bind(this, idle, handle, reject, signal)\n\t\t\tsignal?.addEventListener('abort', onAbort, { once: true })\n\t\t})\n\t}\n\n\t// The abort-aware `setTimeout` sleep shared by `delay` and the `yield` macrotask\n\t// fallback. Same settle-once discipline as the core `#sleep`: already-aborted → reject\n\t// without arming; the timer path removes the listener before resolving; the abort path\n\t// clears the timer before rejecting with the verbatim `signal.reason`.\n\t#sleep(ms: number, signal?: AbortSignal): Promise<void> {\n\t\tif (signal?.aborted === true) return Promise.reject(signal.reason)\n\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\tconst handle = setTimeout(() => {\n\t\t\t\tsignal?.removeEventListener('abort', onAbort) // load-bearing: prevents a post-resolve reject\n\t\t\t\tresolve()\n\t\t\t}, ms)\n\t\t\tconst onAbort = this.#abortTimeout.bind(this, handle, reject, signal)\n\t\t\tsignal?.addEventListener('abort', onAbort, { once: true })\n\t\t})\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\t#abortIdle(\n\t\tidle: IdleAPI,\n\t\thandle: number,\n\t\treject: (reason?: unknown) => void,\n\t\tsignal?: AbortSignal,\n\t): void {\n\t\tidle.cancel(handle)\n\t\treject(signal?.reason)\n\t}\n\n\t#abortTimeout(\n\t\thandle: ReturnType<typeof setTimeout>,\n\t\treject: (reason?: unknown) => void,\n\t\tsignal?: AbortSignal,\n\t): void {\n\t\tclearTimeout(handle)\n\t\treject(signal?.reason)\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 `reason` verbatim, with full task/timer/listener\n * cleanup. Prefer {@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 '@src/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 '@src/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 '@src/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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"}
|
package/dist/src/core/index.cjs
CHANGED
|
@@ -65,17 +65,18 @@ var Scheduler = class {
|
|
|
65
65
|
#sleep(ms, signal) {
|
|
66
66
|
if (signal?.aborted === true) return Promise.reject(signal.reason);
|
|
67
67
|
return new Promise((resolve, reject) => {
|
|
68
|
-
const onAbort = () => {
|
|
69
|
-
clearTimeout(handle);
|
|
70
|
-
reject(signal?.reason);
|
|
71
|
-
};
|
|
72
68
|
const handle = setTimeout(() => {
|
|
73
69
|
signal?.removeEventListener("abort", onAbort);
|
|
74
70
|
resolve();
|
|
75
71
|
}, ms);
|
|
72
|
+
const onAbort = this.#abort.bind(this, handle, reject, signal);
|
|
76
73
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
77
74
|
});
|
|
78
75
|
}
|
|
76
|
+
#abort(handle, reject, signal) {
|
|
77
|
+
clearTimeout(handle);
|
|
78
|
+
reject(signal?.reason);
|
|
79
|
+
}
|
|
79
80
|
};
|
|
80
81
|
//#endregion
|
|
81
82
|
//#region src/core/constants.ts
|
|
@@ -201,7 +202,7 @@ var WorkflowError = class extends Error {
|
|
|
201
202
|
super(message);
|
|
202
203
|
this.name = "WorkflowError";
|
|
203
204
|
this.code = code;
|
|
204
|
-
this.context = context;
|
|
205
|
+
if (context !== void 0) this.context = context;
|
|
205
206
|
}
|
|
206
207
|
};
|
|
207
208
|
/**
|
|
@@ -661,16 +662,7 @@ function moveEntry(entries, key, index) {
|
|
|
661
662
|
* @returns A deferred `promise` plus its `resolve` / `reject`
|
|
662
663
|
*/
|
|
663
664
|
function createDeferred() {
|
|
664
|
-
|
|
665
|
-
let reject = () => {};
|
|
666
|
-
return {
|
|
667
|
-
promise: new Promise((res, rej) => {
|
|
668
|
-
resolve = res;
|
|
669
|
-
reject = rej;
|
|
670
|
-
}),
|
|
671
|
-
resolve,
|
|
672
|
-
reject
|
|
673
|
-
};
|
|
665
|
+
return Promise.withResolvers();
|
|
674
666
|
}
|
|
675
667
|
/**
|
|
676
668
|
* Park until `signal` aborts — a promise-parked wait (AGENTS §21), never a timer or
|
|
@@ -984,7 +976,6 @@ var Task = class {
|
|
|
984
976
|
#status;
|
|
985
977
|
#result;
|
|
986
978
|
#name;
|
|
987
|
-
#description;
|
|
988
979
|
#run;
|
|
989
980
|
#retries;
|
|
990
981
|
#timeout;
|
|
@@ -996,13 +987,16 @@ var Task = class {
|
|
|
996
987
|
this.#recompute = recompute;
|
|
997
988
|
this.#metadata = options?.metadata ?? {};
|
|
998
989
|
this.#emitter = new _orkestrel_emitter.Emitter({
|
|
999
|
-
on: options
|
|
1000
|
-
error: options
|
|
990
|
+
...options?.on === void 0 ? {} : { on: options.on },
|
|
991
|
+
...options?.error === void 0 ? {} : { error: options.error }
|
|
1001
992
|
});
|
|
1002
993
|
this.#status = status;
|
|
1003
994
|
this.#result = result;
|
|
1004
995
|
this.#name = context.name;
|
|
1005
|
-
|
|
996
|
+
if (context.description !== void 0) Object.defineProperty(this, "description", {
|
|
997
|
+
configurable: true,
|
|
998
|
+
value: context.description
|
|
999
|
+
});
|
|
1006
1000
|
this.#run = run;
|
|
1007
1001
|
this.#retries = retries;
|
|
1008
1002
|
this.#timeout = timeout;
|
|
@@ -1017,9 +1011,6 @@ var Task = class {
|
|
|
1017
1011
|
get name() {
|
|
1018
1012
|
return this.#name;
|
|
1019
1013
|
}
|
|
1020
|
-
get description() {
|
|
1021
|
-
return this.#description;
|
|
1022
|
-
}
|
|
1023
1014
|
get context() {
|
|
1024
1015
|
return this.#context;
|
|
1025
1016
|
}
|
|
@@ -1102,7 +1093,10 @@ var Task = class {
|
|
|
1102
1093
|
status: this.#status
|
|
1103
1094
|
});
|
|
1104
1095
|
if (value.name !== void 0) this.#name = value.name;
|
|
1105
|
-
if (value.description !== void 0) this
|
|
1096
|
+
if (value.description !== void 0) Object.defineProperty(this, "description", {
|
|
1097
|
+
configurable: true,
|
|
1098
|
+
value: value.description
|
|
1099
|
+
});
|
|
1106
1100
|
}
|
|
1107
1101
|
snapshot() {
|
|
1108
1102
|
return {
|
|
@@ -1275,7 +1269,6 @@ var TaskManager = class {
|
|
|
1275
1269
|
var Phase = class {
|
|
1276
1270
|
#id;
|
|
1277
1271
|
#name;
|
|
1278
|
-
#description;
|
|
1279
1272
|
#workflow;
|
|
1280
1273
|
#escalateUp;
|
|
1281
1274
|
#tasks = new TaskManager();
|
|
@@ -1290,15 +1283,18 @@ var Phase = class {
|
|
|
1290
1283
|
constructor(snapshot, workflow, escalate, options, bail, functions) {
|
|
1291
1284
|
this.#id = snapshot.id;
|
|
1292
1285
|
this.#name = snapshot.name;
|
|
1293
|
-
|
|
1286
|
+
if (snapshot.description !== void 0) Object.defineProperty(this, "description", {
|
|
1287
|
+
configurable: true,
|
|
1288
|
+
value: snapshot.description
|
|
1289
|
+
});
|
|
1294
1290
|
this.#workflow = workflow;
|
|
1295
1291
|
this.#escalateUp = escalate;
|
|
1296
1292
|
this.#functions = functions;
|
|
1297
1293
|
this.#bail = bail ?? snapshot.bail;
|
|
1298
1294
|
this.#concurrency = snapshot.concurrency;
|
|
1299
1295
|
this.#emitter = new _orkestrel_emitter.Emitter({
|
|
1300
|
-
on: options
|
|
1301
|
-
error: options
|
|
1296
|
+
...options?.on === void 0 ? {} : { on: options.on },
|
|
1297
|
+
...options?.error === void 0 ? {} : { error: options.error }
|
|
1302
1298
|
});
|
|
1303
1299
|
for (const task of snapshot.tasks) this.#append(task, options);
|
|
1304
1300
|
this.#override = snapshot.override;
|
|
@@ -1315,14 +1311,11 @@ var Phase = class {
|
|
|
1315
1311
|
get name() {
|
|
1316
1312
|
return this.#name;
|
|
1317
1313
|
}
|
|
1318
|
-
get description() {
|
|
1319
|
-
return this.#description;
|
|
1320
|
-
}
|
|
1321
1314
|
get context() {
|
|
1322
1315
|
return buildPhaseContext(this.#workflow.context, {
|
|
1323
1316
|
id: this.#id,
|
|
1324
1317
|
name: this.#name,
|
|
1325
|
-
...this
|
|
1318
|
+
...this.description === void 0 ? {} : { description: this.description }
|
|
1326
1319
|
});
|
|
1327
1320
|
}
|
|
1328
1321
|
get workflow() {
|
|
@@ -1424,7 +1417,10 @@ var Phase = class {
|
|
|
1424
1417
|
status: this.status
|
|
1425
1418
|
});
|
|
1426
1419
|
if (value.name !== void 0) this.#name = value.name;
|
|
1427
|
-
if (value.description !== void 0) this
|
|
1420
|
+
if (value.description !== void 0) Object.defineProperty(this, "description", {
|
|
1421
|
+
configurable: true,
|
|
1422
|
+
value: value.description
|
|
1423
|
+
});
|
|
1428
1424
|
if (value.concurrency !== void 0) this.#concurrency = value.concurrency;
|
|
1429
1425
|
if (value.bail !== void 0) this.#bail = value.bail;
|
|
1430
1426
|
}
|
|
@@ -1631,12 +1627,13 @@ var Workflow = class {
|
|
|
1631
1627
|
#destroyed;
|
|
1632
1628
|
constructor(snapshot, options) {
|
|
1633
1629
|
this.#context = buildWorkflowContext(snapshot);
|
|
1630
|
+
if (snapshot.description !== void 0) Object.defineProperty(this, "description", { value: snapshot.description });
|
|
1634
1631
|
this.#bail = options?.bail ?? snapshot.bail;
|
|
1635
1632
|
this.#bailOverride = options?.bail;
|
|
1636
1633
|
this.#functions = options?.functions;
|
|
1637
1634
|
this.#emitter = new _orkestrel_emitter.Emitter({
|
|
1638
|
-
on: options
|
|
1639
|
-
error: options
|
|
1635
|
+
...options?.on === void 0 ? {} : { on: options.on },
|
|
1636
|
+
...options?.error === void 0 ? {} : { error: options.error }
|
|
1640
1637
|
});
|
|
1641
1638
|
this.#created = snapshot.created;
|
|
1642
1639
|
this.#updated = snapshot.updated;
|
|
@@ -1657,9 +1654,6 @@ var Workflow = class {
|
|
|
1657
1654
|
get name() {
|
|
1658
1655
|
return this.#context.name;
|
|
1659
1656
|
}
|
|
1660
|
-
get description() {
|
|
1661
|
-
return this.#context.description;
|
|
1662
|
-
}
|
|
1663
1657
|
get context() {
|
|
1664
1658
|
return this.#context;
|
|
1665
1659
|
}
|
|
@@ -1896,7 +1890,7 @@ var WorkflowManager = class {
|
|
|
1896
1890
|
return [...this.#workflows.values()];
|
|
1897
1891
|
}
|
|
1898
1892
|
add(definition) {
|
|
1899
|
-
const workflow = createWorkflow(definition, { functions: this.#functions });
|
|
1893
|
+
const workflow = createWorkflow(definition, { ...this.#functions === void 0 ? {} : { functions: this.#functions } });
|
|
1900
1894
|
this.#workflows.set(workflow.id, workflow);
|
|
1901
1895
|
return workflow;
|
|
1902
1896
|
}
|
|
@@ -1906,7 +1900,7 @@ var WorkflowManager = class {
|
|
|
1906
1900
|
if (this.#store === void 0) return void 0;
|
|
1907
1901
|
const snapshot = await this.#store.get(id);
|
|
1908
1902
|
if (snapshot === void 0) return void 0;
|
|
1909
|
-
const workflow = restoreWorkflow(snapshot, { functions: this.#functions });
|
|
1903
|
+
const workflow = restoreWorkflow(snapshot, { ...this.#functions === void 0 ? {} : { functions: this.#functions } });
|
|
1910
1904
|
this.#workflows.set(workflow.id, workflow);
|
|
1911
1905
|
return workflow;
|
|
1912
1906
|
}
|
|
@@ -2056,14 +2050,14 @@ var Runner = class {
|
|
|
2056
2050
|
this.#handler = options.handler;
|
|
2057
2051
|
this.#entries = options.entries;
|
|
2058
2052
|
this.#emitter = new _orkestrel_emitter.Emitter({
|
|
2059
|
-
on: options
|
|
2060
|
-
error: options
|
|
2053
|
+
...options.on === void 0 ? {} : { on: options.on },
|
|
2054
|
+
...options.error === void 0 ? {} : { error: options.error }
|
|
2061
2055
|
});
|
|
2062
2056
|
this.#queue = (0, _orkestrel_queue.createQueue)({
|
|
2063
|
-
handler:
|
|
2064
|
-
concurrency: options.concurrency,
|
|
2065
|
-
retries: options.retries,
|
|
2066
|
-
timeout: options.timeout
|
|
2057
|
+
handler: this.#dispatch.bind(this),
|
|
2058
|
+
...options.concurrency === void 0 ? {} : { concurrency: options.concurrency },
|
|
2059
|
+
...options.retries === void 0 ? {} : { retries: options.retries },
|
|
2060
|
+
...options.timeout === void 0 ? {} : { timeout: options.timeout }
|
|
2067
2061
|
});
|
|
2068
2062
|
}
|
|
2069
2063
|
get emitter() {
|
|
@@ -2394,7 +2388,7 @@ var WorkflowRunner = class {
|
|
|
2394
2388
|
options?.budget?.start();
|
|
2395
2389
|
const runSignal = this.#fold(workflow, options, timeout);
|
|
2396
2390
|
const holder = { runner: void 0 };
|
|
2397
|
-
const onCancel = (
|
|
2391
|
+
const onCancel = this.#abortActive.bind(this, holder, runSignal);
|
|
2398
2392
|
if (runSignal.aborted) onCancel();
|
|
2399
2393
|
else runSignal.addEventListener("abort", onCancel, { once: true });
|
|
2400
2394
|
try {
|
|
@@ -2442,12 +2436,7 @@ var WorkflowRunner = class {
|
|
|
2442
2436
|
}
|
|
2443
2437
|
async #runPhase(workflow, phase, runSignal, holder) {
|
|
2444
2438
|
const launched = /* @__PURE__ */ new Set();
|
|
2445
|
-
|
|
2446
|
-
const onAdd = (task) => {
|
|
2447
|
-
if (launched.has(task.id)) return;
|
|
2448
|
-
launched.add(task.id);
|
|
2449
|
-
runner?.spawn(task);
|
|
2450
|
-
};
|
|
2439
|
+
const onAdd = this.#spawnAdded.bind(this, launched, holder);
|
|
2451
2440
|
phase.emitter.on("add", onAdd);
|
|
2452
2441
|
try {
|
|
2453
2442
|
const tasks = phase.tasks.tasks();
|
|
@@ -2458,13 +2447,9 @@ var WorkflowRunner = class {
|
|
|
2458
2447
|
const attempts = /* @__PURE__ */ new Map();
|
|
2459
2448
|
const created = new Runner({
|
|
2460
2449
|
concurrency,
|
|
2461
|
-
entries: (
|
|
2462
|
-
|
|
2463
|
-
timeout: task.timeout
|
|
2464
|
-
}),
|
|
2465
|
-
handler: (controller) => this.#runTask(workflow, controller.input, controller, runSignal, bail, attempts)
|
|
2450
|
+
entries: this.#entry.bind(this),
|
|
2451
|
+
handler: this.#runUnit.bind(this, workflow, runSignal, bail, attempts)
|
|
2466
2452
|
});
|
|
2467
|
-
runner = created;
|
|
2468
2453
|
holder.runner = created;
|
|
2469
2454
|
try {
|
|
2470
2455
|
await created.execute(tasks);
|
|
@@ -2481,6 +2466,23 @@ var WorkflowRunner = class {
|
|
|
2481
2466
|
for (const task of phase.tasks.tasks()) this.#skip(task);
|
|
2482
2467
|
}
|
|
2483
2468
|
}
|
|
2469
|
+
#abortActive(holder, runSignal) {
|
|
2470
|
+
holder.runner?.abort(runSignal.reason);
|
|
2471
|
+
}
|
|
2472
|
+
#spawnAdded(launched, holder, task) {
|
|
2473
|
+
if (launched.has(task.id)) return;
|
|
2474
|
+
launched.add(task.id);
|
|
2475
|
+
holder.runner?.spawn(task);
|
|
2476
|
+
}
|
|
2477
|
+
#entry(task) {
|
|
2478
|
+
return {
|
|
2479
|
+
...task.retries === void 0 ? {} : { retries: task.retries },
|
|
2480
|
+
...task.timeout === void 0 ? {} : { timeout: task.timeout }
|
|
2481
|
+
};
|
|
2482
|
+
}
|
|
2483
|
+
#runUnit(workflow, runSignal, bail, attempts, controller) {
|
|
2484
|
+
return this.#runTask(workflow, controller.input, controller, runSignal, bail, attempts);
|
|
2485
|
+
}
|
|
2484
2486
|
async #runTask(workflow, task, controller, runSignal, bail, attempts) {
|
|
2485
2487
|
const signal = this.#taskSignal(controller.signal, runSignal);
|
|
2486
2488
|
const attempt = (attempts.get(task.id) ?? 0) + 1;
|
|
@@ -2531,7 +2533,7 @@ var WorkflowRunner = class {
|
|
|
2531
2533
|
if (runSignal.aborted) return;
|
|
2532
2534
|
let onAbort;
|
|
2533
2535
|
const cancelled = new Promise((resolve) => {
|
|
2534
|
-
onAbort = (
|
|
2536
|
+
onAbort = resolve.bind(void 0, void 0);
|
|
2535
2537
|
runSignal.addEventListener("abort", onAbort, { once: true });
|
|
2536
2538
|
});
|
|
2537
2539
|
try {
|
|
@@ -2548,7 +2550,7 @@ var WorkflowRunner = class {
|
|
|
2548
2550
|
if (options?.signal !== void 0) signals.push(options.signal);
|
|
2549
2551
|
if (timeout !== void 0) signals.push(timeout.signal);
|
|
2550
2552
|
if (options?.budget !== void 0) signals.push(options.budget.signal);
|
|
2551
|
-
return signals.length === 1 ?
|
|
2553
|
+
return signals.length === 1 ? workflow.signal : AbortSignal.any(signals);
|
|
2552
2554
|
}
|
|
2553
2555
|
#haltFrom(phases, index, workflow, runSignal) {
|
|
2554
2556
|
if (this.#cancelled(runSignal) && this.#stoppable(workflow)) workflow.stop();
|
|
@@ -2619,13 +2621,7 @@ var WorkflowRunner = class {
|
|
|
2619
2621
|
* ```
|
|
2620
2622
|
*/
|
|
2621
2623
|
function createWorkflowContract() {
|
|
2622
|
-
|
|
2623
|
-
return {
|
|
2624
|
-
schema: contract.schema,
|
|
2625
|
-
is: contract.is,
|
|
2626
|
-
generate: (random) => contract.generate(random),
|
|
2627
|
-
parse: (value) => contract.parse(value)
|
|
2628
|
-
};
|
|
2624
|
+
return (0, _orkestrel_contract.createContract)(workflowShape);
|
|
2629
2625
|
}
|
|
2630
2626
|
/**
|
|
2631
2627
|
* Build the live W-b entity tree from a {@link WorkflowDefinition} — the whole
|