@orkestrel/workflow 0.0.8 → 0.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/src/browser/index.d.ts +23 -38
- package/dist/src/browser/index.js +51 -122
- package/dist/src/browser/index.js.map +1 -1
- package/dist/src/core/index.cjs +595 -250
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +151 -65
- package/dist/src/core/index.d.ts +151 -65
- package/dist/src/core/index.js +595 -252
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +21 -43
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +12 -17
- package/dist/src/server/index.d.ts +12 -17
- package/dist/src/server/index.js +21 -43
- package/dist/src/server/index.js.map +1 -1
- package/package.json +8 -8
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
let _src_core = require("../core/index.cjs");
|
|
2
3
|
//#region src/server/NodeScheduler.ts
|
|
3
4
|
/**
|
|
4
5
|
* The Node {@link SchedulerInterface} — the server-native cooperative-yield backend.
|
|
@@ -10,17 +11,11 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
10
11
|
* resuming (unlike a microtask, which drains within the current task). `delay(ms)`
|
|
11
12
|
* waits on a real `setTimeout`.
|
|
12
13
|
* - **Abort fidelity is verbatim.** A pending `yield` / `delay` rejects with
|
|
13
|
-
* `signal.reason` exactly
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* cannot reach `reject`), and the abort path clears the timer before rejecting (so the
|
|
19
|
-
* macrotask cannot reach `resolve`). The promise settles exactly once, with no leaked
|
|
20
|
-
* timer and no leaked listener. It deliberately does NOT use `node:timers/promises`,
|
|
21
|
-
* whose `{ signal }` option rejects with a Node `AbortError` (`code: 'ABORT_ERR'`)
|
|
22
|
-
* rather than the caller's `signal.reason` — that would break reason fidelity, so the
|
|
23
|
-
* timer and listener are hand-rolled to match the contract.
|
|
14
|
+
* `signal.reason` exactly. The shared `scheduleHost` lifecycle links an owned composite
|
|
15
|
+
* before arming either Node handle, so caller signal method mutation is harmless and the
|
|
16
|
+
* first completion, abort, or setup failure owns settlement and cleanup. It deliberately
|
|
17
|
+
* does NOT use `node:timers/promises`, whose `{ signal }` option replaces the caller reason
|
|
18
|
+
* with a Node `AbortError` (`code: 'ABORT_ERR'`).
|
|
24
19
|
* - **Priority is accepted but a no-op.** Node has no priority primitive (no equivalent
|
|
25
20
|
* of the browser's `scheduler.postTask` priorities), so `options.priority` is accepted
|
|
26
21
|
* for contract compliance and ignored — every yield/delay is uniform.
|
|
@@ -28,8 +23,8 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
28
23
|
*
|
|
29
24
|
* @example
|
|
30
25
|
* ```ts
|
|
31
|
-
* import { createAbort } from '@
|
|
32
|
-
* import { NodeScheduler } from '@
|
|
26
|
+
* import { createAbort } from '@orkestrel/abort'
|
|
27
|
+
* import { NodeScheduler } from '@orkestrel/workflow/server'
|
|
33
28
|
*
|
|
34
29
|
* const abort = createAbort()
|
|
35
30
|
* const scheduler = new NodeScheduler()
|
|
@@ -61,34 +56,16 @@ var NodeScheduler = class {
|
|
|
61
56
|
return this.#sleep(ms, options?.signal);
|
|
62
57
|
}
|
|
63
58
|
#immediate(signal) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
resolve();
|
|
69
|
-
});
|
|
70
|
-
const onAbort = this.#abortImmediate.bind(this, handle, reject, signal);
|
|
71
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
72
|
-
});
|
|
59
|
+
return (0, _src_core.scheduleHost)((complete) => {
|
|
60
|
+
const handle = setImmediate(complete);
|
|
61
|
+
return () => clearImmediate(handle);
|
|
62
|
+
}, signal);
|
|
73
63
|
}
|
|
74
64
|
#sleep(ms, signal) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
resolve();
|
|
80
|
-
}, ms);
|
|
81
|
-
const onAbort = this.#abortTimeout.bind(this, handle, reject, signal);
|
|
82
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
#abortImmediate(handle, reject, signal) {
|
|
86
|
-
clearImmediate(handle);
|
|
87
|
-
reject(signal?.reason);
|
|
88
|
-
}
|
|
89
|
-
#abortTimeout(handle, reject, signal) {
|
|
90
|
-
clearTimeout(handle);
|
|
91
|
-
reject(signal?.reason);
|
|
65
|
+
return (0, _src_core.scheduleHost)((complete) => {
|
|
66
|
+
const handle = setTimeout(complete, ms);
|
|
67
|
+
return () => clearTimeout(handle);
|
|
68
|
+
}, signal);
|
|
92
69
|
}
|
|
93
70
|
};
|
|
94
71
|
//#endregion
|
|
@@ -102,16 +79,17 @@ var NodeScheduler = class {
|
|
|
102
79
|
* Use it on a server instead of the cross-environment `createScheduler` when a yield
|
|
103
80
|
* should hand the event loop a full turn (after pending I/O) via `setImmediate` rather
|
|
104
81
|
* than a zero-delay timer. Both methods are abort-aware: pass `options.signal` and a
|
|
105
|
-
* pending yield/delay rejects with the signal's `reason
|
|
106
|
-
*
|
|
82
|
+
* pending yield/delay rejects with the signal's exact `reason`; the shared owned-signal
|
|
83
|
+
* lifecycle clears the native handle without invoking caller listener methods.
|
|
84
|
+
* `options.priority` is accepted for contract compliance but a
|
|
107
85
|
* no-op — Node has no priority primitive.
|
|
108
86
|
*
|
|
109
87
|
* @returns A {@link SchedulerInterface} backed by Node's `setImmediate` / `setTimeout`
|
|
110
88
|
*
|
|
111
89
|
* @example
|
|
112
90
|
* ```ts
|
|
113
|
-
* import { createAbort } from '@
|
|
114
|
-
* import { createNodeScheduler } from '@
|
|
91
|
+
* import { createAbort } from '@orkestrel/abort'
|
|
92
|
+
* import { createNodeScheduler } from '@orkestrel/workflow/server'
|
|
115
93
|
*
|
|
116
94
|
* const abort = createAbort()
|
|
117
95
|
* const scheduler = createNodeScheduler()
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["#immediate","#sleep"
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["#immediate","#sleep"],"sources":["../../../src/server/NodeScheduler.ts","../../../src/server/factories.ts"],"sourcesContent":["import type { SchedulerInterface, SchedulerOptions } from '@src/core'\nimport { scheduleHost } from '@src/core'\n\n/**\n * The Node {@link SchedulerInterface} — the server-native cooperative-yield backend.\n *\n * @remarks\n * - **`yield` is a `setImmediate` host-turn.** `yield()` waits on `setImmediate`, the\n * canonical Node \"give the host a turn\" — it runs AFTER the current operation and\n * any pending I/O callbacks, so the event loop genuinely regains control before\n * resuming (unlike a microtask, which drains within the current task). `delay(ms)`\n * waits on a real `setTimeout`.\n * - **Abort fidelity is verbatim.** A pending `yield` / `delay` rejects with\n * `signal.reason` exactly. The shared `scheduleHost` lifecycle links an owned composite\n * before arming either Node handle, so caller signal method mutation is harmless and the\n * first completion, abort, or setup failure owns settlement and cleanup. It deliberately\n * does NOT use `node:timers/promises`, whose `{ signal }` option replaces the caller reason\n * with a Node `AbortError` (`code: 'ABORT_ERR'`).\n * - **Priority is accepted but a no-op.** Node has no priority primitive (no equivalent\n * of the browser's `scheduler.postTask` priorities), so `options.priority` is accepted\n * for contract compliance and ignored — every yield/delay is uniform.\n * - **Event-free.** A pure functional primitive — no Emitter, no events.\n *\n * @example\n * ```ts\n * import { createAbort } from '@orkestrel/abort'\n * import { NodeScheduler } from '@orkestrel/workflow/server'\n *\n * const abort = createAbort()\n * const scheduler = new NodeScheduler()\n * while (!abort.signal.aborted) {\n * \tdoSomeWork()\n * \tawait scheduler.yield({ signal: abort.signal }) // a setImmediate host-turn\n * }\n * ```\n */\nexport class NodeScheduler implements SchedulerInterface {\n\t/**\n\t * Yield control back to the event loop via `setImmediate` so pending I/O and timers\n\t * can run, then resume; abort rejects with `signal.reason`.\n\t */\n\tyield(options?: SchedulerOptions): Promise<void> {\n\t\treturn this.#immediate(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 Node-native immediate boundary; `scheduleHost` owns cancellation lifecycle.\n\t#immediate(signal?: AbortSignal): Promise<void> {\n\t\treturn scheduleHost((complete) => {\n\t\t\tconst handle = setImmediate(complete)\n\t\t\treturn () => clearImmediate(handle)\n\t\t}, signal)\n\t}\n\n\t// The Node 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 } from '@src/core'\nimport { NodeScheduler } from './NodeScheduler.js'\n\n/**\n * Create the Node-native cooperative-yield {@link SchedulerInterface} — `yield()` is a\n * `setImmediate` host-turn (the canonical Node \"give the event loop a turn\"), `delay(ms)`\n * a real `setTimeout`.\n *\n * @remarks\n * Use it on a server instead of the cross-environment `createScheduler` when a yield\n * should hand the event loop a full turn (after pending I/O) via `setImmediate` rather\n * than a zero-delay timer. Both methods are abort-aware: pass `options.signal` and a\n * pending yield/delay rejects with the signal's exact `reason`; the shared owned-signal\n * lifecycle clears the native handle without invoking caller listener methods.\n * `options.priority` is accepted for contract compliance but a\n * no-op — Node has no priority primitive.\n *\n * @returns A {@link SchedulerInterface} backed by Node's `setImmediate` / `setTimeout`\n *\n * @example\n * ```ts\n * import { createAbort } from '@orkestrel/abort'\n * import { createNodeScheduler } from '@orkestrel/workflow/server'\n *\n * const abort = createAbort()\n * const scheduler = createNodeScheduler()\n * while (!abort.signal.aborted) {\n * \tdoSomeWork()\n * \tawait scheduler.yield({ signal: abort.signal })\n * }\n * ```\n */\nexport function createNodeScheduler(): SchedulerInterface {\n\treturn new NodeScheduler()\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoCA,IAAa,gBAAb,MAAyD;;;;;CAKxD,MAAM,SAA2C;EAChD,OAAO,KAAKA,WAAW,SAAS,MAAM;CACvC;;;;;;;;;;;CAYA,MAAM,IAAY,SAA2C;EAC5D,OAAO,KAAKC,OAAO,IAAI,SAAS,MAAM;CACvC;CAKA,WAAW,QAAqC;EAC/C,QAAA,GAAO,UAAA,aAAA,EAAc,aAAa;GACjC,MAAM,SAAS,aAAa,QAAQ;GACpC,aAAa,eAAe,MAAM;EACnC,GAAG,MAAM;CACV;CAGA,OAAO,IAAY,QAAqC;EACvD,QAAA,GAAO,UAAA,aAAA,EAAc,aAAa;GACjC,MAAM,SAAS,WAAW,UAAU,EAAE;GACtC,aAAa,aAAa,MAAM;EACjC,GAAG,MAAM;CACV;AACD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC5CA,SAAgB,sBAA0C;CACzD,OAAO,IAAI,cAAc;AAC1B"}
|
|
@@ -10,16 +10,17 @@ import { SchedulerOptions } from '../core/index.ts';
|
|
|
10
10
|
* Use it on a server instead of the cross-environment `createScheduler` when a yield
|
|
11
11
|
* should hand the event loop a full turn (after pending I/O) via `setImmediate` rather
|
|
12
12
|
* than a zero-delay timer. Both methods are abort-aware: pass `options.signal` and a
|
|
13
|
-
* pending yield/delay rejects with the signal's `reason
|
|
14
|
-
*
|
|
13
|
+
* pending yield/delay rejects with the signal's exact `reason`; the shared owned-signal
|
|
14
|
+
* lifecycle clears the native handle without invoking caller listener methods.
|
|
15
|
+
* `options.priority` is accepted for contract compliance but a
|
|
15
16
|
* no-op — Node has no priority primitive.
|
|
16
17
|
*
|
|
17
18
|
* @returns A {@link SchedulerInterface} backed by Node's `setImmediate` / `setTimeout`
|
|
18
19
|
*
|
|
19
20
|
* @example
|
|
20
21
|
* ```ts
|
|
21
|
-
* import { createAbort } from '@
|
|
22
|
-
* import { createNodeScheduler } from '@
|
|
22
|
+
* import { createAbort } from '@orkestrel/abort'
|
|
23
|
+
* import { createNodeScheduler } from '@orkestrel/workflow/server'
|
|
23
24
|
*
|
|
24
25
|
* const abort = createAbort()
|
|
25
26
|
* const scheduler = createNodeScheduler()
|
|
@@ -41,17 +42,11 @@ export declare function createNodeScheduler(): SchedulerInterface;
|
|
|
41
42
|
* resuming (unlike a microtask, which drains within the current task). `delay(ms)`
|
|
42
43
|
* waits on a real `setTimeout`.
|
|
43
44
|
* - **Abort fidelity is verbatim.** A pending `yield` / `delay` rejects with
|
|
44
|
-
* `signal.reason` exactly
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* cannot reach `reject`), and the abort path clears the timer before rejecting (so the
|
|
50
|
-
* macrotask cannot reach `resolve`). The promise settles exactly once, with no leaked
|
|
51
|
-
* timer and no leaked listener. It deliberately does NOT use `node:timers/promises`,
|
|
52
|
-
* whose `{ signal }` option rejects with a Node `AbortError` (`code: 'ABORT_ERR'`)
|
|
53
|
-
* rather than the caller's `signal.reason` — that would break reason fidelity, so the
|
|
54
|
-
* timer and listener are hand-rolled to match the contract.
|
|
45
|
+
* `signal.reason` exactly. The shared `scheduleHost` lifecycle links an owned composite
|
|
46
|
+
* before arming either Node handle, so caller signal method mutation is harmless and the
|
|
47
|
+
* first completion, abort, or setup failure owns settlement and cleanup. It deliberately
|
|
48
|
+
* does NOT use `node:timers/promises`, whose `{ signal }` option replaces the caller reason
|
|
49
|
+
* with a Node `AbortError` (`code: 'ABORT_ERR'`).
|
|
55
50
|
* - **Priority is accepted but a no-op.** Node has no priority primitive (no equivalent
|
|
56
51
|
* of the browser's `scheduler.postTask` priorities), so `options.priority` is accepted
|
|
57
52
|
* for contract compliance and ignored — every yield/delay is uniform.
|
|
@@ -59,8 +54,8 @@ export declare function createNodeScheduler(): SchedulerInterface;
|
|
|
59
54
|
*
|
|
60
55
|
* @example
|
|
61
56
|
* ```ts
|
|
62
|
-
* import { createAbort } from '@
|
|
63
|
-
* import { NodeScheduler } from '@
|
|
57
|
+
* import { createAbort } from '@orkestrel/abort'
|
|
58
|
+
* import { NodeScheduler } from '@orkestrel/workflow/server'
|
|
64
59
|
*
|
|
65
60
|
* const abort = createAbort()
|
|
66
61
|
* const scheduler = new NodeScheduler()
|
|
@@ -10,16 +10,17 @@ import { SchedulerOptions } from '../core/index.ts';
|
|
|
10
10
|
* Use it on a server instead of the cross-environment `createScheduler` when a yield
|
|
11
11
|
* should hand the event loop a full turn (after pending I/O) via `setImmediate` rather
|
|
12
12
|
* than a zero-delay timer. Both methods are abort-aware: pass `options.signal` and a
|
|
13
|
-
* pending yield/delay rejects with the signal's `reason
|
|
14
|
-
*
|
|
13
|
+
* pending yield/delay rejects with the signal's exact `reason`; the shared owned-signal
|
|
14
|
+
* lifecycle clears the native handle without invoking caller listener methods.
|
|
15
|
+
* `options.priority` is accepted for contract compliance but a
|
|
15
16
|
* no-op — Node has no priority primitive.
|
|
16
17
|
*
|
|
17
18
|
* @returns A {@link SchedulerInterface} backed by Node's `setImmediate` / `setTimeout`
|
|
18
19
|
*
|
|
19
20
|
* @example
|
|
20
21
|
* ```ts
|
|
21
|
-
* import { createAbort } from '@
|
|
22
|
-
* import { createNodeScheduler } from '@
|
|
22
|
+
* import { createAbort } from '@orkestrel/abort'
|
|
23
|
+
* import { createNodeScheduler } from '@orkestrel/workflow/server'
|
|
23
24
|
*
|
|
24
25
|
* const abort = createAbort()
|
|
25
26
|
* const scheduler = createNodeScheduler()
|
|
@@ -41,17 +42,11 @@ export declare function createNodeScheduler(): SchedulerInterface;
|
|
|
41
42
|
* resuming (unlike a microtask, which drains within the current task). `delay(ms)`
|
|
42
43
|
* waits on a real `setTimeout`.
|
|
43
44
|
* - **Abort fidelity is verbatim.** A pending `yield` / `delay` rejects with
|
|
44
|
-
* `signal.reason` exactly
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* cannot reach `reject`), and the abort path clears the timer before rejecting (so the
|
|
50
|
-
* macrotask cannot reach `resolve`). The promise settles exactly once, with no leaked
|
|
51
|
-
* timer and no leaked listener. It deliberately does NOT use `node:timers/promises`,
|
|
52
|
-
* whose `{ signal }` option rejects with a Node `AbortError` (`code: 'ABORT_ERR'`)
|
|
53
|
-
* rather than the caller's `signal.reason` — that would break reason fidelity, so the
|
|
54
|
-
* timer and listener are hand-rolled to match the contract.
|
|
45
|
+
* `signal.reason` exactly. The shared `scheduleHost` lifecycle links an owned composite
|
|
46
|
+
* before arming either Node handle, so caller signal method mutation is harmless and the
|
|
47
|
+
* first completion, abort, or setup failure owns settlement and cleanup. It deliberately
|
|
48
|
+
* does NOT use `node:timers/promises`, whose `{ signal }` option replaces the caller reason
|
|
49
|
+
* with a Node `AbortError` (`code: 'ABORT_ERR'`).
|
|
55
50
|
* - **Priority is accepted but a no-op.** Node has no priority primitive (no equivalent
|
|
56
51
|
* of the browser's `scheduler.postTask` priorities), so `options.priority` is accepted
|
|
57
52
|
* for contract compliance and ignored — every yield/delay is uniform.
|
|
@@ -59,8 +54,8 @@ export declare function createNodeScheduler(): SchedulerInterface;
|
|
|
59
54
|
*
|
|
60
55
|
* @example
|
|
61
56
|
* ```ts
|
|
62
|
-
* import { createAbort } from '@
|
|
63
|
-
* import { NodeScheduler } from '@
|
|
57
|
+
* import { createAbort } from '@orkestrel/abort'
|
|
58
|
+
* import { NodeScheduler } from '@orkestrel/workflow/server'
|
|
64
59
|
*
|
|
65
60
|
* const abort = createAbort()
|
|
66
61
|
* const scheduler = new NodeScheduler()
|
package/dist/src/server/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { scheduleHost } from "../core/index.js";
|
|
1
2
|
//#region src/server/NodeScheduler.ts
|
|
2
3
|
/**
|
|
3
4
|
* The Node {@link SchedulerInterface} — the server-native cooperative-yield backend.
|
|
@@ -9,17 +10,11 @@
|
|
|
9
10
|
* resuming (unlike a microtask, which drains within the current task). `delay(ms)`
|
|
10
11
|
* waits on a real `setTimeout`.
|
|
11
12
|
* - **Abort fidelity is verbatim.** A pending `yield` / `delay` rejects with
|
|
12
|
-
* `signal.reason` exactly
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* cannot reach `reject`), and the abort path clears the timer before rejecting (so the
|
|
18
|
-
* macrotask cannot reach `resolve`). The promise settles exactly once, with no leaked
|
|
19
|
-
* timer and no leaked listener. It deliberately does NOT use `node:timers/promises`,
|
|
20
|
-
* whose `{ signal }` option rejects with a Node `AbortError` (`code: 'ABORT_ERR'`)
|
|
21
|
-
* rather than the caller's `signal.reason` — that would break reason fidelity, so the
|
|
22
|
-
* timer and listener are hand-rolled to match the contract.
|
|
13
|
+
* `signal.reason` exactly. The shared `scheduleHost` lifecycle links an owned composite
|
|
14
|
+
* before arming either Node handle, so caller signal method mutation is harmless and the
|
|
15
|
+
* first completion, abort, or setup failure owns settlement and cleanup. It deliberately
|
|
16
|
+
* does NOT use `node:timers/promises`, whose `{ signal }` option replaces the caller reason
|
|
17
|
+
* with a Node `AbortError` (`code: 'ABORT_ERR'`).
|
|
23
18
|
* - **Priority is accepted but a no-op.** Node has no priority primitive (no equivalent
|
|
24
19
|
* of the browser's `scheduler.postTask` priorities), so `options.priority` is accepted
|
|
25
20
|
* for contract compliance and ignored — every yield/delay is uniform.
|
|
@@ -27,8 +22,8 @@
|
|
|
27
22
|
*
|
|
28
23
|
* @example
|
|
29
24
|
* ```ts
|
|
30
|
-
* import { createAbort } from '@
|
|
31
|
-
* import { NodeScheduler } from '@
|
|
25
|
+
* import { createAbort } from '@orkestrel/abort'
|
|
26
|
+
* import { NodeScheduler } from '@orkestrel/workflow/server'
|
|
32
27
|
*
|
|
33
28
|
* const abort = createAbort()
|
|
34
29
|
* const scheduler = new NodeScheduler()
|
|
@@ -60,34 +55,16 @@ var NodeScheduler = class {
|
|
|
60
55
|
return this.#sleep(ms, options?.signal);
|
|
61
56
|
}
|
|
62
57
|
#immediate(signal) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
resolve();
|
|
68
|
-
});
|
|
69
|
-
const onAbort = this.#abortImmediate.bind(this, handle, reject, signal);
|
|
70
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
71
|
-
});
|
|
58
|
+
return scheduleHost((complete) => {
|
|
59
|
+
const handle = setImmediate(complete);
|
|
60
|
+
return () => clearImmediate(handle);
|
|
61
|
+
}, signal);
|
|
72
62
|
}
|
|
73
63
|
#sleep(ms, signal) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
resolve();
|
|
79
|
-
}, ms);
|
|
80
|
-
const onAbort = this.#abortTimeout.bind(this, handle, reject, signal);
|
|
81
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
#abortImmediate(handle, reject, signal) {
|
|
85
|
-
clearImmediate(handle);
|
|
86
|
-
reject(signal?.reason);
|
|
87
|
-
}
|
|
88
|
-
#abortTimeout(handle, reject, signal) {
|
|
89
|
-
clearTimeout(handle);
|
|
90
|
-
reject(signal?.reason);
|
|
64
|
+
return scheduleHost((complete) => {
|
|
65
|
+
const handle = setTimeout(complete, ms);
|
|
66
|
+
return () => clearTimeout(handle);
|
|
67
|
+
}, signal);
|
|
91
68
|
}
|
|
92
69
|
};
|
|
93
70
|
//#endregion
|
|
@@ -101,16 +78,17 @@ var NodeScheduler = class {
|
|
|
101
78
|
* Use it on a server instead of the cross-environment `createScheduler` when a yield
|
|
102
79
|
* should hand the event loop a full turn (after pending I/O) via `setImmediate` rather
|
|
103
80
|
* than a zero-delay timer. Both methods are abort-aware: pass `options.signal` and a
|
|
104
|
-
* pending yield/delay rejects with the signal's `reason
|
|
105
|
-
*
|
|
81
|
+
* pending yield/delay rejects with the signal's exact `reason`; the shared owned-signal
|
|
82
|
+
* lifecycle clears the native handle without invoking caller listener methods.
|
|
83
|
+
* `options.priority` is accepted for contract compliance but a
|
|
106
84
|
* no-op — Node has no priority primitive.
|
|
107
85
|
*
|
|
108
86
|
* @returns A {@link SchedulerInterface} backed by Node's `setImmediate` / `setTimeout`
|
|
109
87
|
*
|
|
110
88
|
* @example
|
|
111
89
|
* ```ts
|
|
112
|
-
* import { createAbort } from '@
|
|
113
|
-
* import { createNodeScheduler } from '@
|
|
90
|
+
* import { createAbort } from '@orkestrel/abort'
|
|
91
|
+
* import { createNodeScheduler } from '@orkestrel/workflow/server'
|
|
114
92
|
*
|
|
115
93
|
* const abort = createAbort()
|
|
116
94
|
* const scheduler = createNodeScheduler()
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["#immediate","#sleep"
|
|
1
|
+
{"version":3,"file":"index.js","names":["#immediate","#sleep"],"sources":["../../../src/server/NodeScheduler.ts","../../../src/server/factories.ts"],"sourcesContent":["import type { SchedulerInterface, SchedulerOptions } from '@src/core'\nimport { scheduleHost } from '@src/core'\n\n/**\n * The Node {@link SchedulerInterface} — the server-native cooperative-yield backend.\n *\n * @remarks\n * - **`yield` is a `setImmediate` host-turn.** `yield()` waits on `setImmediate`, the\n * canonical Node \"give the host a turn\" — it runs AFTER the current operation and\n * any pending I/O callbacks, so the event loop genuinely regains control before\n * resuming (unlike a microtask, which drains within the current task). `delay(ms)`\n * waits on a real `setTimeout`.\n * - **Abort fidelity is verbatim.** A pending `yield` / `delay` rejects with\n * `signal.reason` exactly. The shared `scheduleHost` lifecycle links an owned composite\n * before arming either Node handle, so caller signal method mutation is harmless and the\n * first completion, abort, or setup failure owns settlement and cleanup. It deliberately\n * does NOT use `node:timers/promises`, whose `{ signal }` option replaces the caller reason\n * with a Node `AbortError` (`code: 'ABORT_ERR'`).\n * - **Priority is accepted but a no-op.** Node has no priority primitive (no equivalent\n * of the browser's `scheduler.postTask` priorities), so `options.priority` is accepted\n * for contract compliance and ignored — every yield/delay is uniform.\n * - **Event-free.** A pure functional primitive — no Emitter, no events.\n *\n * @example\n * ```ts\n * import { createAbort } from '@orkestrel/abort'\n * import { NodeScheduler } from '@orkestrel/workflow/server'\n *\n * const abort = createAbort()\n * const scheduler = new NodeScheduler()\n * while (!abort.signal.aborted) {\n * \tdoSomeWork()\n * \tawait scheduler.yield({ signal: abort.signal }) // a setImmediate host-turn\n * }\n * ```\n */\nexport class NodeScheduler implements SchedulerInterface {\n\t/**\n\t * Yield control back to the event loop via `setImmediate` so pending I/O and timers\n\t * can run, then resume; abort rejects with `signal.reason`.\n\t */\n\tyield(options?: SchedulerOptions): Promise<void> {\n\t\treturn this.#immediate(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 Node-native immediate boundary; `scheduleHost` owns cancellation lifecycle.\n\t#immediate(signal?: AbortSignal): Promise<void> {\n\t\treturn scheduleHost((complete) => {\n\t\t\tconst handle = setImmediate(complete)\n\t\t\treturn () => clearImmediate(handle)\n\t\t}, signal)\n\t}\n\n\t// The Node 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 } from '@src/core'\nimport { NodeScheduler } from './NodeScheduler.js'\n\n/**\n * Create the Node-native cooperative-yield {@link SchedulerInterface} — `yield()` is a\n * `setImmediate` host-turn (the canonical Node \"give the event loop a turn\"), `delay(ms)`\n * a real `setTimeout`.\n *\n * @remarks\n * Use it on a server instead of the cross-environment `createScheduler` when a yield\n * should hand the event loop a full turn (after pending I/O) via `setImmediate` rather\n * than a zero-delay timer. Both methods are abort-aware: pass `options.signal` and a\n * pending yield/delay rejects with the signal's exact `reason`; the shared owned-signal\n * lifecycle clears the native handle without invoking caller listener methods.\n * `options.priority` is accepted for contract compliance but a\n * no-op — Node has no priority primitive.\n *\n * @returns A {@link SchedulerInterface} backed by Node's `setImmediate` / `setTimeout`\n *\n * @example\n * ```ts\n * import { createAbort } from '@orkestrel/abort'\n * import { createNodeScheduler } from '@orkestrel/workflow/server'\n *\n * const abort = createAbort()\n * const scheduler = createNodeScheduler()\n * while (!abort.signal.aborted) {\n * \tdoSomeWork()\n * \tawait scheduler.yield({ signal: abort.signal })\n * }\n * ```\n */\nexport function createNodeScheduler(): SchedulerInterface {\n\treturn new NodeScheduler()\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoCA,IAAa,gBAAb,MAAyD;;;;;CAKxD,MAAM,SAA2C;EAChD,OAAO,KAAKA,WAAW,SAAS,MAAM;CACvC;;;;;;;;;;;CAYA,MAAM,IAAY,SAA2C;EAC5D,OAAO,KAAKC,OAAO,IAAI,SAAS,MAAM;CACvC;CAKA,WAAW,QAAqC;EAC/C,OAAO,cAAc,aAAa;GACjC,MAAM,SAAS,aAAa,QAAQ;GACpC,aAAa,eAAe,MAAM;EACnC,GAAG,MAAM;CACV;CAGA,OAAO,IAAY,QAAqC;EACvD,OAAO,cAAc,aAAa;GACjC,MAAM,SAAS,WAAW,UAAU,EAAE;GACtC,aAAa,aAAa,MAAM;EACjC,GAAG,MAAM;CACV;AACD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC5CA,SAAgB,sBAA0C;CACzD,OAAO,IAAI,cAAc;AAC1B"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orkestrel/workflow",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.10",
|
|
4
4
|
"description": "A typed workflow engine for the @orkestrel line — a serializable Workflow → Phase → Task tree run by a composed runner on a cooperative scheduler. Part of the @orkestrel line.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"orchestration",
|
|
@@ -85,22 +85,22 @@
|
|
|
85
85
|
"prepublishOnly": "npm run format:check && npm run lint:check && npm run check && npm run build && npm test"
|
|
86
86
|
},
|
|
87
87
|
"dependencies": {
|
|
88
|
-
"@orkestrel/abort": "^0.0.
|
|
89
|
-
"@orkestrel/budget": "^0.0.
|
|
88
|
+
"@orkestrel/abort": "^0.0.5",
|
|
89
|
+
"@orkestrel/budget": "^0.0.5",
|
|
90
90
|
"@orkestrel/contract": "^0.0.9",
|
|
91
91
|
"@orkestrel/database": "^0.0.7",
|
|
92
92
|
"@orkestrel/emitter": "^0.0.5",
|
|
93
|
-
"@orkestrel/queue": "^0.0.
|
|
94
|
-
"@orkestrel/timeout": "^0.0.
|
|
93
|
+
"@orkestrel/queue": "^0.0.7",
|
|
94
|
+
"@orkestrel/timeout": "^0.0.5"
|
|
95
95
|
},
|
|
96
96
|
"devDependencies": {
|
|
97
97
|
"@microsoft/api-extractor": "^7.58.12",
|
|
98
98
|
"@orkestrel/guide": "^0.0.8",
|
|
99
|
-
"@orkestrel/scaffold": "^0.0.
|
|
99
|
+
"@orkestrel/scaffold": "^0.0.18",
|
|
100
100
|
"@types/node": "^26.1.2",
|
|
101
101
|
"@vitest/browser-playwright": "^4.1.10",
|
|
102
|
-
"oxfmt": "^0.
|
|
103
|
-
"oxlint": "^1.
|
|
102
|
+
"oxfmt": "^0.62.0",
|
|
103
|
+
"oxlint": "^1.77.0",
|
|
104
104
|
"playwright": "^1.62.1",
|
|
105
105
|
"typescript": "^6.0.3",
|
|
106
106
|
"vite": "^8.2.0",
|