@adonis-agora/durable 0.9.1 → 0.11.0
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/CHANGELOG.md +12 -0
- package/dist/assets/dashboard.html +116 -3
- package/dist/commands/durable_worker.d.ts +25 -0
- package/dist/commands/durable_worker.d.ts.map +1 -0
- package/dist/commands/durable_worker.js +113 -0
- package/dist/commands/durable_worker.js.map +1 -0
- package/dist/commands/main.d.ts +6 -5
- package/dist/commands/main.d.ts.map +1 -1
- package/dist/commands/main.js +11 -4
- package/dist/commands/main.js.map +1 -1
- package/dist/providers/dashboard_provider.d.ts +38 -0
- package/dist/providers/dashboard_provider.d.ts.map +1 -1
- package/dist/providers/dashboard_provider.js +103 -10
- package/dist/providers/dashboard_provider.js.map +1 -1
- package/dist/providers/durable_provider.d.ts +24 -16
- package/dist/providers/durable_provider.d.ts.map +1 -1
- package/dist/providers/durable_provider.js +195 -20
- package/dist/providers/durable_provider.js.map +1 -1
- package/dist/src/config_types.d.ts +92 -0
- package/dist/src/config_types.d.ts.map +1 -0
- package/dist/src/config_types.js +2 -0
- package/dist/src/config_types.js.map +1 -0
- package/dist/src/dashboard/compat-view.d.ts +13 -0
- package/dist/src/dashboard/compat-view.d.ts.map +1 -0
- package/dist/src/dashboard/compat-view.js +32 -0
- package/dist/src/dashboard/compat-view.js.map +1 -0
- package/dist/src/dashboard/compat.d.ts +76 -0
- package/dist/src/dashboard/compat.d.ts.map +1 -0
- package/dist/src/dashboard/compat.js +130 -0
- package/dist/src/dashboard/compat.js.map +1 -0
- package/dist/src/dashboard/diagnostics-recorder.d.ts +59 -0
- package/dist/src/dashboard/diagnostics-recorder.d.ts.map +1 -0
- package/dist/src/dashboard/diagnostics-recorder.js +75 -0
- package/dist/src/dashboard/diagnostics-recorder.js.map +1 -0
- package/dist/src/dashboard/gateway-adapter.d.ts +37 -0
- package/dist/src/dashboard/gateway-adapter.d.ts.map +1 -0
- package/dist/src/dashboard/gateway-adapter.js +49 -0
- package/dist/src/dashboard/gateway-adapter.js.map +1 -0
- package/dist/src/dashboard/handlers.d.ts +38 -8
- package/dist/src/dashboard/handlers.d.ts.map +1 -1
- package/dist/src/dashboard/handlers.js +17 -1
- package/dist/src/dashboard/handlers.js.map +1 -1
- package/dist/src/dashboard/index.d.ts +10 -3
- package/dist/src/dashboard/index.d.ts.map +1 -1
- package/dist/src/dashboard/index.js +8 -2
- package/dist/src/dashboard/index.js.map +1 -1
- package/dist/src/define_config.d.ts +31 -5
- package/dist/src/define_config.d.ts.map +1 -1
- package/dist/src/define_config.js +4 -3
- package/dist/src/define_config.js.map +1 -1
- package/dist/src/dispatch-routing.d.ts +105 -0
- package/dist/src/dispatch-routing.d.ts.map +1 -0
- package/dist/src/dispatch-routing.js +114 -0
- package/dist/src/dispatch-routing.js.map +1 -0
- package/dist/src/engine.d.ts +129 -3
- package/dist/src/engine.d.ts.map +1 -1
- package/dist/src/engine.js +354 -12
- package/dist/src/engine.js.map +1 -1
- package/dist/src/handshake/descriptor.d.ts +128 -0
- package/dist/src/handshake/descriptor.d.ts.map +1 -0
- package/dist/src/handshake/descriptor.js +133 -0
- package/dist/src/handshake/descriptor.js.map +1 -0
- package/dist/src/handshake/negotiate.d.ts +83 -0
- package/dist/src/handshake/negotiate.d.ts.map +1 -0
- package/dist/src/handshake/negotiate.js +108 -0
- package/dist/src/handshake/negotiate.js.map +1 -0
- package/dist/src/handshake/routing.d.ts +51 -0
- package/dist/src/handshake/routing.d.ts.map +1 -0
- package/dist/src/handshake/routing.js +57 -0
- package/dist/src/handshake/routing.js.map +1 -0
- package/dist/src/index.d.ts +14 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +12 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/interfaces.d.ts +184 -7
- package/dist/src/interfaces.d.ts.map +1 -1
- package/dist/src/remote-workflow-executor.d.ts +25 -1
- package/dist/src/remote-workflow-executor.d.ts.map +1 -1
- package/dist/src/remote-workflow-executor.js +41 -0
- package/dist/src/remote-workflow-executor.js.map +1 -1
- package/dist/src/role_bindings.d.ts +85 -0
- package/dist/src/role_bindings.d.ts.map +1 -0
- package/dist/src/role_bindings.js +73 -0
- package/dist/src/role_bindings.js.map +1 -0
- package/dist/src/run-gateway/interface.d.ts +89 -0
- package/dist/src/run-gateway/interface.d.ts.map +1 -0
- package/dist/src/run-gateway/interface.js +2 -0
- package/dist/src/run-gateway/interface.js.map +1 -0
- package/dist/src/run-gateway/proxy-run-gateway.d.ts +84 -0
- package/dist/src/run-gateway/proxy-run-gateway.d.ts.map +1 -0
- package/dist/src/run-gateway/proxy-run-gateway.js +142 -0
- package/dist/src/run-gateway/proxy-run-gateway.js.map +1 -0
- package/dist/src/run-gateway/run-request-responder.d.ts +67 -0
- package/dist/src/run-gateway/run-request-responder.d.ts.map +1 -0
- package/dist/src/run-gateway/run-request-responder.js +228 -0
- package/dist/src/run-gateway/run-request-responder.js.map +1 -0
- package/dist/src/run-gateway/store-run-gateway.d.ts +90 -0
- package/dist/src/run-gateway/store-run-gateway.d.ts.map +1 -0
- package/dist/src/run-gateway/store-run-gateway.js +80 -0
- package/dist/src/run-gateway/store-run-gateway.js.map +1 -0
- package/dist/src/run-gateway/tenant-auth.d.ts +19 -0
- package/dist/src/run-gateway/tenant-auth.d.ts.map +1 -0
- package/dist/src/run-gateway/tenant-auth.js +71 -0
- package/dist/src/run-gateway/tenant-auth.js.map +1 -0
- package/dist/src/services/main.d.ts +16 -3
- package/dist/src/services/main.d.ts.map +1 -1
- package/dist/src/services/main.js +23 -4
- package/dist/src/services/main.js.map +1 -1
- package/dist/src/step-name-symbol.d.ts +3 -0
- package/dist/src/step-name-symbol.d.ts.map +1 -1
- package/dist/src/step-name-symbol.js.map +1 -1
- package/dist/src/step-ref.d.ts +4 -0
- package/dist/src/step-ref.d.ts.map +1 -1
- package/dist/src/step-ref.js +2 -0
- package/dist/src/step-ref.js.map +1 -1
- package/dist/src/transports/bullmq/bullmq-transport.d.ts +130 -0
- package/dist/src/transports/bullmq/bullmq-transport.d.ts.map +1 -0
- package/dist/src/transports/bullmq/bullmq-transport.js +562 -0
- package/dist/src/transports/bullmq/bullmq-transport.js.map +1 -0
- package/dist/src/transports/bullmq/deps.d.ts +59 -0
- package/dist/src/transports/bullmq/deps.d.ts.map +1 -0
- package/dist/src/transports/bullmq/deps.js +41 -0
- package/dist/src/transports/bullmq/deps.js.map +1 -0
- package/dist/src/transports/bullmq/index.d.ts +5 -0
- package/dist/src/transports/bullmq/index.d.ts.map +1 -0
- package/dist/src/transports/bullmq/index.js +5 -0
- package/dist/src/transports/bullmq/index.js.map +1 -0
- package/dist/src/transports/bullmq/naming.d.ts +71 -0
- package/dist/src/transports/bullmq/naming.d.ts.map +1 -0
- package/dist/src/transports/bullmq/naming.js +101 -0
- package/dist/src/transports/bullmq/naming.js.map +1 -0
- package/dist/src/transports/bullmq/serialization.d.ts +56 -0
- package/dist/src/transports/bullmq/serialization.d.ts.map +1 -0
- package/dist/src/transports/bullmq/serialization.js +101 -0
- package/dist/src/transports/bullmq/serialization.js.map +1 -0
- package/dist/src/transports/factory.d.ts +31 -0
- package/dist/src/transports/factory.d.ts.map +1 -1
- package/dist/src/transports/factory.js +21 -0
- package/dist/src/transports/factory.js.map +1 -1
- package/dist/src/worker-runtime/index.d.ts +22 -0
- package/dist/src/worker-runtime/index.d.ts.map +1 -0
- package/dist/src/worker-runtime/index.js +27 -0
- package/dist/src/worker-runtime/index.js.map +1 -0
- package/dist/src/worker-runtime/naming.d.ts +24 -0
- package/dist/src/worker-runtime/naming.d.ts.map +1 -0
- package/dist/src/worker-runtime/naming.js +28 -0
- package/dist/src/worker-runtime/naming.js.map +1 -0
- package/dist/src/worker-runtime/registry.d.ts +81 -0
- package/dist/src/worker-runtime/registry.d.ts.map +1 -0
- package/dist/src/worker-runtime/registry.js +44 -0
- package/dist/src/worker-runtime/registry.js.map +1 -0
- package/dist/src/worker-runtime/worker-runtime.d.ts +155 -0
- package/dist/src/worker-runtime/worker-runtime.d.ts.map +1 -0
- package/dist/src/worker-runtime/worker-runtime.js +265 -0
- package/dist/src/worker-runtime/worker-runtime.js.map +1 -0
- package/dist/src/workflow-ctx.d.ts +9 -0
- package/dist/src/workflow-ctx.d.ts.map +1 -1
- package/dist/src/workflow-ctx.js +78 -27
- package/dist/src/workflow-ctx.js.map +1 -1
- package/dist/src/workflow-turn.d.ts +167 -0
- package/dist/src/workflow-turn.d.ts.map +1 -0
- package/dist/src/workflow-turn.js +372 -0
- package/dist/src/workflow-turn.js.map +1 -0
- package/package.json +7 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"naming.d.ts","sourceRoot":"","sources":["../../../src/worker-runtime/naming.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AAEnG;;;;;;;;;GASG;AACH,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,kBAAkB,EAAE,CAAC;AAE7D;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAEhG;AAED,qGAAqG;AACrG,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEnE"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { effectivePrefix, routingToken, workerHeartbeatKey } from '../transports/bullmq/naming.js';
|
|
2
|
+
/**
|
|
3
|
+
* Broker key naming for the store-less {@link import('./worker-runtime.js').WorkerRuntime}'s two-tier
|
|
4
|
+
* advertisement (design §6.2 / §7.2). Built ON TOP of the wave-1 aviary-compatible
|
|
5
|
+
* `transports/bullmq/naming` so the worker-liveness + descriptor keys stay byte-identical across the
|
|
6
|
+
* fleet (a Python worker / NestJS control-plane computes the same names). Pure string builders — no
|
|
7
|
+
* Redis, no bullmq, no Lucid — so the worker subpath stays lean and the names unit-test in isolation.
|
|
8
|
+
*
|
|
9
|
+
* Re-exported here (rather than re-derived) so a caller in the worker subpath never has to reach into
|
|
10
|
+
* the bullmq transport package for the shared sanitize/prefix/token rules.
|
|
11
|
+
*/
|
|
12
|
+
export { effectivePrefix, routingToken, workerHeartbeatKey };
|
|
13
|
+
/**
|
|
14
|
+
* `${P}-worker-descriptor:${token}:${instanceId}` — the full {@link WorkerDescriptor} key (design
|
|
15
|
+
* §6.2). One key per (routing token, instance): the worker publishes its whole descriptor under EACH
|
|
16
|
+
* routing token it serves, mirroring the per-token `${P}-worker-heartbeat:` liveness key so a
|
|
17
|
+
* control-plane scanning a token's keyspace finds both the compact heartbeat (with the ETag) and, when
|
|
18
|
+
* the ETag changed, the rich descriptor to re-read. Neither `token` nor `instanceId` carries a `:`, so
|
|
19
|
+
* the segments parse unambiguously — identical to {@link workerHeartbeatKey}'s layout.
|
|
20
|
+
*/
|
|
21
|
+
export function workerDescriptorKey(effPrefix, token, instanceId) {
|
|
22
|
+
return `${effPrefix}-worker-descriptor:${token}:${instanceId}`;
|
|
23
|
+
}
|
|
24
|
+
/** Common prefix of every worker-descriptor key (for a control-plane SCAN over live descriptors). */
|
|
25
|
+
export function workerDescriptorKeyPrefix(effPrefix) {
|
|
26
|
+
return `${effPrefix}-worker-descriptor:`;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=naming.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"naming.js","sourceRoot":"","sources":["../../../src/worker-runtime/naming.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AAEnG;;;;;;;;;GASG;AACH,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,kBAAkB,EAAE,CAAC;AAE7D;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CAAC,SAAiB,EAAE,KAAa,EAAE,UAAkB;IACtF,OAAO,GAAG,SAAS,sBAAsB,KAAK,IAAI,UAAU,EAAE,CAAC;AACjE,CAAC;AAED,qGAAqG;AACrG,MAAM,UAAU,yBAAyB,CAAC,SAAiB;IACzD,OAAO,GAAG,SAAS,qBAAqB,CAAC;AAC3C,CAAC"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { HeartbeatStatus, WorkerDescriptor } from '../handshake/descriptor.js';
|
|
2
|
+
/**
|
|
3
|
+
* The advertisement side of the store-less worker handshake (design §7.2): a thin writer the
|
|
4
|
+
* {@link import('./worker-runtime.js').WorkerRuntime} pushes its descriptor + compact heartbeat
|
|
5
|
+
* through. Kept behind this interface so the runtime is transport-AND-backend agnostic — a test proves
|
|
6
|
+
* advertisement against a fake, a real pod passes a {@link RedisWorkerRegistry}, and the runtime itself
|
|
7
|
+
* imports no Redis client (keeping the worker subpath lean).
|
|
8
|
+
*/
|
|
9
|
+
export interface WorkerRegistry {
|
|
10
|
+
/**
|
|
11
|
+
* Publish the FULL {@link WorkerDescriptor} for a routing token+instance (the `${P}-worker-descriptor`
|
|
12
|
+
* key). Re-called when the descriptor changes (new handler registered) and periodically to refresh
|
|
13
|
+
* its TTL. The `ttlSeconds` self-expires the key when the worker dies without a clean stop.
|
|
14
|
+
*/
|
|
15
|
+
advertiseDescriptor(ad: {
|
|
16
|
+
key: string;
|
|
17
|
+
descriptor: WorkerDescriptor;
|
|
18
|
+
ttlSeconds: number;
|
|
19
|
+
}): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* Beat the compact two-tier heartbeat `{ ts, status, descriptorHash }` for a routing token+instance
|
|
22
|
+
* (the `${P}-worker-heartbeat` key), TTL'd so its absence is the "worker gone" signal. The
|
|
23
|
+
* `descriptorHash` is the ETag a control-plane watches to know when to re-read the full descriptor.
|
|
24
|
+
*/
|
|
25
|
+
beat(beat: {
|
|
26
|
+
key: string;
|
|
27
|
+
status: HeartbeatStatus;
|
|
28
|
+
ttlSeconds: number;
|
|
29
|
+
}): Promise<void>;
|
|
30
|
+
/** Best-effort removal of this instance's keys on a clean stop (a graceful drain). Optional. */
|
|
31
|
+
remove?(keys: string[]): Promise<void>;
|
|
32
|
+
/** Release any owned connection on stop. Optional. */
|
|
33
|
+
close?(): Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* A registry that advertises nothing — the default when a worker runs without a Redis backend (e.g. an
|
|
37
|
+
* in-process test transport, or before the tenant provider wires a real one). The runtime still BUILDS
|
|
38
|
+
* its descriptor + heartbeat (observable via {@link import('./worker-runtime.js').WorkerRuntime.descriptor});
|
|
39
|
+
* they simply aren't published anywhere.
|
|
40
|
+
*/
|
|
41
|
+
export declare class NoopWorkerRegistry implements WorkerRegistry {
|
|
42
|
+
advertiseDescriptor(): Promise<void>;
|
|
43
|
+
beat(): Promise<void>;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The minimal Redis surface {@link RedisWorkerRegistry} needs — a structural subset satisfied by an
|
|
47
|
+
* `ioredis` client AND by the bullmq transport's `RedisLike`, so either can be handed in without a hard
|
|
48
|
+
* `ioredis` import at the worker subpath's module load. `set(...'EX', ttl)` is exactly the SET…EX the
|
|
49
|
+
* aviary liveness-key scheme uses; `del`/`disconnect` are optional cleanup.
|
|
50
|
+
*/
|
|
51
|
+
export interface DescriptorRedis {
|
|
52
|
+
set(key: string, value: string, mode: 'EX', ttl: number): Promise<unknown> | unknown;
|
|
53
|
+
del?(...keys: string[]): Promise<unknown> | unknown;
|
|
54
|
+
disconnect?(): void;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Publishes the descriptor + heartbeat to Redis with `SET key value EX ttl` — the byte-compatible
|
|
58
|
+
* two-tier advertisement (design §7.2) a control-plane (or a Python/NestJS peer) reads off the shared
|
|
59
|
+
* Redis. The client is injected (never constructed here) so this class stays pure and the worker
|
|
60
|
+
* subpath pulls in no `ioredis` statically; a caller builds the client once (e.g. from the same
|
|
61
|
+
* connection its bullmq transport uses) and passes it in.
|
|
62
|
+
*/
|
|
63
|
+
export declare class RedisWorkerRegistry implements WorkerRegistry {
|
|
64
|
+
#private;
|
|
65
|
+
constructor(redis: DescriptorRedis, opts?: {
|
|
66
|
+
ownsConnection?: boolean;
|
|
67
|
+
});
|
|
68
|
+
advertiseDescriptor(ad: {
|
|
69
|
+
key: string;
|
|
70
|
+
descriptor: WorkerDescriptor;
|
|
71
|
+
ttlSeconds: number;
|
|
72
|
+
}): Promise<void>;
|
|
73
|
+
beat(beat: {
|
|
74
|
+
key: string;
|
|
75
|
+
status: HeartbeatStatus;
|
|
76
|
+
ttlSeconds: number;
|
|
77
|
+
}): Promise<void>;
|
|
78
|
+
remove(keys: string[]): Promise<void>;
|
|
79
|
+
close(): Promise<void>;
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/worker-runtime/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAEpF;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC7B;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,EAAE;QACtB,GAAG,EAAE,MAAM,CAAC;QACZ,UAAU,EAAE,gBAAgB,CAAC;QAC7B,UAAU,EAAE,MAAM,CAAC;KACpB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClB;;;;OAIG;IACH,IAAI,CAAC,IAAI,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,eAAe,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxF,gGAAgG;IAChG,MAAM,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,sDAAsD;IACtD,KAAK,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACzB;AAED;;;;;GAKG;AACH,qBAAa,kBAAmB,YAAW,cAAc;IACjD,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC;IAIpC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAG5B;AAED;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IACrF,GAAG,CAAC,CAAC,GAAG,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;IACpD,UAAU,CAAC,IAAI,IAAI,CAAC;CACrB;AAED;;;;;;GAMG;AACH,qBAAa,mBAAoB,YAAW,cAAc;;gBAI5C,KAAK,EAAE,eAAe,EAAE,IAAI,GAAE;QAAE,cAAc,CAAC,EAAE,OAAO,CAAA;KAAO;IAKrE,mBAAmB,CAAC,EAAE,EAAE;QAC5B,GAAG,EAAE,MAAM,CAAC;QACZ,UAAU,EAAE,gBAAgB,CAAC;QAC7B,UAAU,EAAE,MAAM,CAAC;KACpB,GAAG,OAAO,CAAC,IAAI,CAAC;IAIX,IAAI,CAAC,IAAI,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,eAAe,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvF,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAIrC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7B"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A registry that advertises nothing — the default when a worker runs without a Redis backend (e.g. an
|
|
3
|
+
* in-process test transport, or before the tenant provider wires a real one). The runtime still BUILDS
|
|
4
|
+
* its descriptor + heartbeat (observable via {@link import('./worker-runtime.js').WorkerRuntime.descriptor});
|
|
5
|
+
* they simply aren't published anywhere.
|
|
6
|
+
*/
|
|
7
|
+
export class NoopWorkerRegistry {
|
|
8
|
+
async advertiseDescriptor() {
|
|
9
|
+
/* no backend — nothing to publish */
|
|
10
|
+
}
|
|
11
|
+
async beat() {
|
|
12
|
+
/* no backend — nothing to publish */
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Publishes the descriptor + heartbeat to Redis with `SET key value EX ttl` — the byte-compatible
|
|
17
|
+
* two-tier advertisement (design §7.2) a control-plane (or a Python/NestJS peer) reads off the shared
|
|
18
|
+
* Redis. The client is injected (never constructed here) so this class stays pure and the worker
|
|
19
|
+
* subpath pulls in no `ioredis` statically; a caller builds the client once (e.g. from the same
|
|
20
|
+
* connection its bullmq transport uses) and passes it in.
|
|
21
|
+
*/
|
|
22
|
+
export class RedisWorkerRegistry {
|
|
23
|
+
#redis;
|
|
24
|
+
#ownsConnection;
|
|
25
|
+
constructor(redis, opts = {}) {
|
|
26
|
+
this.#redis = redis;
|
|
27
|
+
this.#ownsConnection = opts.ownsConnection ?? false;
|
|
28
|
+
}
|
|
29
|
+
async advertiseDescriptor(ad) {
|
|
30
|
+
await this.#redis.set(ad.key, JSON.stringify(ad.descriptor), 'EX', ad.ttlSeconds);
|
|
31
|
+
}
|
|
32
|
+
async beat(beat) {
|
|
33
|
+
await this.#redis.set(beat.key, JSON.stringify(beat.status), 'EX', beat.ttlSeconds);
|
|
34
|
+
}
|
|
35
|
+
async remove(keys) {
|
|
36
|
+
if (keys.length > 0 && this.#redis.del)
|
|
37
|
+
await this.#redis.del(...keys);
|
|
38
|
+
}
|
|
39
|
+
async close() {
|
|
40
|
+
if (this.#ownsConnection)
|
|
41
|
+
this.#redis.disconnect?.();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../src/worker-runtime/registry.ts"],"names":[],"mappings":"AAgCA;;;;;GAKG;AACH,MAAM,OAAO,kBAAkB;IAC7B,KAAK,CAAC,mBAAmB;QACvB,qCAAqC;IACvC,CAAC;IAED,KAAK,CAAC,IAAI;QACR,qCAAqC;IACvC,CAAC;CACF;AAcD;;;;;;GAMG;AACH,MAAM,OAAO,mBAAmB;IACrB,MAAM,CAAkB;IACxB,eAAe,CAAU;IAElC,YAAY,KAAsB,EAAE,OAAqC,EAAE;QACzE,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;QACpB,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,cAAc,IAAI,KAAK,CAAC;IACtD,CAAC;IAED,KAAK,CAAC,mBAAmB,CAAC,EAIzB;QACC,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,UAAU,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC;IACpF,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,IAAkE;QAC3E,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;IACtF,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,IAAc;QACzB,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG;YAAE,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;IACzE,CAAC;IAED,KAAK,CAAC,KAAK;QACT,IAAI,IAAI,CAAC,eAAe;YAAE,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,EAAE,CAAC;IACvD,CAAC;CACF"}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { type WorkerDescriptor } from '../handshake/descriptor.js';
|
|
2
|
+
import type { WorkflowStepEvent } from '../interfaces.js';
|
|
3
|
+
import type { StepHandler } from '../protocol.js';
|
|
4
|
+
import { type WorkflowBody, type WorkflowTurnHandler } from '../workflow-turn.js';
|
|
5
|
+
import { type WorkerRegistry } from './registry.js';
|
|
6
|
+
/**
|
|
7
|
+
* The bare worker surface the {@link WorkerRuntime} consumes tasks over — exactly `handle(name, fn)`
|
|
8
|
+
* (the wave-1 BullMQTransport / any `StepServer` satisfies it) plus an optional `close`. Typed this
|
|
9
|
+
* narrowly (not the full `Transport`) so the store-less runtime depends only on what it uses: the
|
|
10
|
+
* transport owns the consume→`runStepHandler`→publish-result loop; the runtime wires handlers onto it
|
|
11
|
+
* and advertises what it serves.
|
|
12
|
+
*/
|
|
13
|
+
export interface WorkerTransport {
|
|
14
|
+
/** Register a step handler → the transport subscribes `${P}-tasks-<token>` for it and, on a task,
|
|
15
|
+
* runs it through the shared `protocol.ts runStepHandler` and publishes the {@link StepResult}. */
|
|
16
|
+
handle(name: string, fn: StepHandler): void;
|
|
17
|
+
/** Register a WORKFLOW turn consumer → the transport subscribes the workflow name's
|
|
18
|
+
* `${P}-tasks-<token>` queue and, on a `workflow`-shaped job (discriminated by SHAPE, spec §6.3),
|
|
19
|
+
* runs `turn(task)` (the replay → decision) and publishes the {@link WorkflowDecision} on
|
|
20
|
+
* `${P}-decisions`. OPTIONAL — a transport that can't carry workflow tasks omits it, and the runtime
|
|
21
|
+
* then only advertises the workflow name (routing/observability) without executing turns. */
|
|
22
|
+
handleWorkflow?(name: string, turn: WorkflowTurnHandler): void;
|
|
23
|
+
/** workflow worker → engine: stream a LOCAL step's lifecycle ({@link WorkflowStepEvent}) mid-turn so a
|
|
24
|
+
* long inline turn's steps show live. OPTIONAL — only broker transports carry it; absent = no live
|
|
25
|
+
* step streaming (the turn's final decision still records every step). */
|
|
26
|
+
dispatchStepEvent?(event: WorkflowStepEvent): Promise<void>;
|
|
27
|
+
/** Release the transport's broker workers/connections on stop. Optional (in-process transports omit it). */
|
|
28
|
+
close?(): Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
/** Where the runtime narrates its lifecycle. Defaults to a silent logger. */
|
|
31
|
+
export interface WorkerRuntimeLogger {
|
|
32
|
+
info(message: string): void;
|
|
33
|
+
error(message: string): void;
|
|
34
|
+
}
|
|
35
|
+
/** SDK identity stamped on the descriptor when the caller doesn't override it (observability only). */
|
|
36
|
+
export declare const WORKER_SDK: {
|
|
37
|
+
readonly name: "@adonis-agora/durable";
|
|
38
|
+
readonly version: "0.9.0";
|
|
39
|
+
};
|
|
40
|
+
export interface WorkerRuntimeOptions {
|
|
41
|
+
/** The broker the worker consumes tasks over (wave-1 `BullMQTransport`, or any `handle`-able server). */
|
|
42
|
+
transport: WorkerTransport;
|
|
43
|
+
/** Which tenant/partition this pod serves — suffixes every routing token (`<name>@<partition>`) and
|
|
44
|
+
* tags the descriptor. Required (a store-less worker always belongs to a partition). */
|
|
45
|
+
partition: string;
|
|
46
|
+
/** Deployment namespace segmenting every key/queue. Absent or `"default"` keeps names byte-identical. */
|
|
47
|
+
namespace?: string;
|
|
48
|
+
/** Key prefix namespacing the durable queues/keys. Default `durable`. */
|
|
49
|
+
prefix?: string;
|
|
50
|
+
/** Stable per-process id stamped on the liveness/descriptor keys. Default `ts-<host>-<pid>`. MUST
|
|
51
|
+
* match the transport's instance id so the two-tier keys correlate. */
|
|
52
|
+
instanceId?: string;
|
|
53
|
+
/** SDK identity advertised in the descriptor (observability only; never gates dispatch). */
|
|
54
|
+
sdk?: {
|
|
55
|
+
name: string;
|
|
56
|
+
version: string;
|
|
57
|
+
};
|
|
58
|
+
/** Extra advertised features beyond registered handler names (design §7.1 `capabilities`). */
|
|
59
|
+
capabilities?: string[];
|
|
60
|
+
/** Descriptor/heartbeat advertiser. Default {@link NoopWorkerRegistry} (descriptor still built +
|
|
61
|
+
* observable via {@link WorkerRuntime.descriptor}, just not published). Pass a `RedisWorkerRegistry`
|
|
62
|
+
* to advertise on the shared Redis. */
|
|
63
|
+
registry?: WorkerRegistry;
|
|
64
|
+
/** Worker-liveness beat cadence (ms). Default 10s (aviary `WORKER_HEARTBEAT_INTERVAL_MS`). */
|
|
65
|
+
heartbeatIntervalMs?: number;
|
|
66
|
+
/** TTL (s) on the descriptor + heartbeat keys. Default 35s (aviary `WORKER_HEARTBEAT_TTL_SECONDS`). */
|
|
67
|
+
ttlSeconds?: number;
|
|
68
|
+
/** Clock for the heartbeat `ts` (injectable for a deterministic test). Default `Date.now`. */
|
|
69
|
+
now?: () => number;
|
|
70
|
+
logger?: WorkerRuntimeLogger;
|
|
71
|
+
/** Where a background advertisement/beat failure is reported (best-effort — the key just expires). */
|
|
72
|
+
onError?: (err: unknown) => void;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* A **store-less** worker runtime (design §4): it executes step bodies pulled off the transport and
|
|
76
|
+
* advertises what it can serve — and has, by construction, NO store field and imports NO Lucid (proven
|
|
77
|
+
* by the `no-lucid` structural test). It is deliberately NOT a store-optional engine: isolation is a
|
|
78
|
+
* structural fact, not a runtime `if (this.store)`.
|
|
79
|
+
*
|
|
80
|
+
* Responsibilities:
|
|
81
|
+
* - **Register handlers** — {@link registerStep} (also `handle`, so `step-discovery`'s
|
|
82
|
+
* `registerStepsFromDir/Barrel` can target the runtime directly) forwards to `transport.handle`,
|
|
83
|
+
* which subscribes `${P}-tasks-<token>` and runs each task through the shared `runStepHandler`,
|
|
84
|
+
* publishing the result. Workflow NAMES ({@link registerWorkflowName}) populate the descriptor.
|
|
85
|
+
* - **Advertise** ({@link start}) — publishes the {@link WorkerDescriptor} to
|
|
86
|
+
* `${P}-worker-descriptor:<token>:<instance>` and beats the compact `{ ts, status, descriptorHash }`
|
|
87
|
+
* to `${P}-worker-heartbeat:<token>:<instance>` on a schedule (design §7.2 two-tier ETag scheme).
|
|
88
|
+
*
|
|
89
|
+
* It holds no store and reads none — every durable decision stays on the control-plane; this object
|
|
90
|
+
* only runs bodies and says who it is.
|
|
91
|
+
*/
|
|
92
|
+
export declare class WorkerRuntime {
|
|
93
|
+
#private;
|
|
94
|
+
constructor(options: WorkerRuntimeOptions);
|
|
95
|
+
/** Stable id stamped on this instance's liveness/descriptor keys. */
|
|
96
|
+
get instanceId(): string;
|
|
97
|
+
/** The step handler names this worker serves (sorted). */
|
|
98
|
+
get stepNames(): string[];
|
|
99
|
+
/** The workflow names this worker advertises (sorted). */
|
|
100
|
+
get workflowNames(): string[];
|
|
101
|
+
/**
|
|
102
|
+
* Register a step handler on the transport (which subscribes its `${P}-tasks-<token>` queue and runs
|
|
103
|
+
* each task through the shared `runStepHandler`) AND record its name for the descriptor. Named
|
|
104
|
+
* `handle` too so `step-discovery`'s `registerStep`/`registerStepsFromDir`/`registerStepsFromBarrel`
|
|
105
|
+
* can target the runtime directly (it satisfies their `StepServer`). Re-registering swaps the handler;
|
|
106
|
+
* if already {@link start}ed, the descriptor is re-advertised so the new name is routable.
|
|
107
|
+
*/
|
|
108
|
+
handle(name: string, fn: StepHandler): void;
|
|
109
|
+
/** Alias of {@link handle} reading as intent at a call site that isn't the discovery `StepServer`. */
|
|
110
|
+
registerStep(name: string, fn: StepHandler): void;
|
|
111
|
+
/**
|
|
112
|
+
* Register a runnable WORKFLOW TURN body (`(ctx, input) => output`, written against the worker-side
|
|
113
|
+
* {@link import('../workflow-turn.js').WorkflowTurnCtx}) AND advertise its name. This is what lets a
|
|
114
|
+
* store-less TS worker EXECUTE workflow turns for parity with a Python worker (design §4): the
|
|
115
|
+
* transport subscribes the workflow's `${P}-tasks-<token>` queue and, on a `workflow`-shaped job, the
|
|
116
|
+
* turn is replayed via the shared {@link runWorkflowTurn} (deterministic history → decision) and the
|
|
117
|
+
* {@link import('../interfaces.js').WorkflowDecision} published on `${P}-decisions`. The worker never
|
|
118
|
+
* touches a store — every durable decision stays on the control-plane; this only replays the body.
|
|
119
|
+
*
|
|
120
|
+
* NOTE: this is the polyglot TURN surface (the TS twin of the Python `@workflow`), NOT the
|
|
121
|
+
* store-backed `BaseWorkflow`/`WorkflowCtx` the engine runs in-process — those are a different
|
|
122
|
+
* authoring surface (they own the store). Re-registering swaps the body; if the transport can't carry
|
|
123
|
+
* workflow tasks (`handleWorkflow` absent) the name is still advertised, just not executed here.
|
|
124
|
+
*/
|
|
125
|
+
registerWorkflow(name: string, body: WorkflowBody): void;
|
|
126
|
+
/**
|
|
127
|
+
* Advertise a workflow NAME in the descriptor WITHOUT a runnable body (routing + capability
|
|
128
|
+
* negotiation only, design §7.1) — e.g. this pod routes/observes a workflow another worker executes.
|
|
129
|
+
* To actually EXECUTE turns here, use {@link registerWorkflow}. If already {@link start}ed,
|
|
130
|
+
* re-advertises so the name is visible.
|
|
131
|
+
*/
|
|
132
|
+
registerWorkflowName(name: string): void;
|
|
133
|
+
/** Advertise many workflow names at once (one re-advertise). */
|
|
134
|
+
registerWorkflowNames(names: Iterable<string>): void;
|
|
135
|
+
/**
|
|
136
|
+
* The worker's current {@link WorkerDescriptor} — the single source of truth for routing, compat and
|
|
137
|
+
* observability (design §7.1). `workflows`/`steps` are the registered handler names (sorted, a set);
|
|
138
|
+
* `capabilities` are the configured extra features; `protocol` is the current major with a `[1, N]`
|
|
139
|
+
* range. Rebuilt on each read so it always reflects what is registered right now.
|
|
140
|
+
*/
|
|
141
|
+
descriptor(): WorkerDescriptor;
|
|
142
|
+
/**
|
|
143
|
+
* Begin advertising: publish the descriptor + do the first heartbeat immediately (so a freshly
|
|
144
|
+
* started worker is visible without waiting a full interval), then refresh both on the heartbeat
|
|
145
|
+
* schedule. Idempotent. The interval is `unref`'d so it never keeps the process alive on its own.
|
|
146
|
+
*/
|
|
147
|
+
start(): Promise<void>;
|
|
148
|
+
/**
|
|
149
|
+
* Stop advertising and release resources: clears the beat interval, best-effort removes this
|
|
150
|
+
* instance's keys (a graceful drain — a watcher sees the worker leave immediately, not on TTL
|
|
151
|
+
* expiry), then closes the registry's owned connection and the transport. Idempotent.
|
|
152
|
+
*/
|
|
153
|
+
stop(): Promise<void>;
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=worker-runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-runtime.d.ts","sourceRoot":"","sources":["../../../src/worker-runtime/worker-runtime.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
import { CURRENT_PROTOCOL_VERSION, heartbeatStatus, } from '../handshake/descriptor.js';
|
|
2
|
+
import { WORKER_HEARTBEAT_INTERVAL_MS, WORKER_HEARTBEAT_TTL_SECONDS, buildInstanceId, } from '../transports/bullmq/serialization.js';
|
|
3
|
+
import { runWorkflowTurn, } from '../workflow-turn.js';
|
|
4
|
+
import { effectivePrefix, routingToken, workerDescriptorKey, workerHeartbeatKey, } from './naming.js';
|
|
5
|
+
import { NoopWorkerRegistry } from './registry.js';
|
|
6
|
+
/** SDK identity stamped on the descriptor when the caller doesn't override it (observability only). */
|
|
7
|
+
export const WORKER_SDK = { name: '@adonis-agora/durable', version: '0.9.0' };
|
|
8
|
+
const SILENT_LOGGER = { info: () => { }, error: () => { } };
|
|
9
|
+
/**
|
|
10
|
+
* A **store-less** worker runtime (design §4): it executes step bodies pulled off the transport and
|
|
11
|
+
* advertises what it can serve — and has, by construction, NO store field and imports NO Lucid (proven
|
|
12
|
+
* by the `no-lucid` structural test). It is deliberately NOT a store-optional engine: isolation is a
|
|
13
|
+
* structural fact, not a runtime `if (this.store)`.
|
|
14
|
+
*
|
|
15
|
+
* Responsibilities:
|
|
16
|
+
* - **Register handlers** — {@link registerStep} (also `handle`, so `step-discovery`'s
|
|
17
|
+
* `registerStepsFromDir/Barrel` can target the runtime directly) forwards to `transport.handle`,
|
|
18
|
+
* which subscribes `${P}-tasks-<token>` and runs each task through the shared `runStepHandler`,
|
|
19
|
+
* publishing the result. Workflow NAMES ({@link registerWorkflowName}) populate the descriptor.
|
|
20
|
+
* - **Advertise** ({@link start}) — publishes the {@link WorkerDescriptor} to
|
|
21
|
+
* `${P}-worker-descriptor:<token>:<instance>` and beats the compact `{ ts, status, descriptorHash }`
|
|
22
|
+
* to `${P}-worker-heartbeat:<token>:<instance>` on a schedule (design §7.2 two-tier ETag scheme).
|
|
23
|
+
*
|
|
24
|
+
* It holds no store and reads none — every durable decision stays on the control-plane; this object
|
|
25
|
+
* only runs bodies and says who it is.
|
|
26
|
+
*/
|
|
27
|
+
export class WorkerRuntime {
|
|
28
|
+
#transport;
|
|
29
|
+
#partition;
|
|
30
|
+
#namespace;
|
|
31
|
+
#prefix;
|
|
32
|
+
#instanceId;
|
|
33
|
+
#sdk;
|
|
34
|
+
#capabilities;
|
|
35
|
+
#registry;
|
|
36
|
+
#heartbeatIntervalMs;
|
|
37
|
+
#ttlSeconds;
|
|
38
|
+
#now;
|
|
39
|
+
#logger;
|
|
40
|
+
#onError;
|
|
41
|
+
#steps = new Set();
|
|
42
|
+
#workflows = new Set();
|
|
43
|
+
// Worker-side workflow turn bodies, keyed by name — the resolver `runWorkflowTurn` replays against.
|
|
44
|
+
#workflowBodies = new Map();
|
|
45
|
+
#startedAt;
|
|
46
|
+
#beatTimer;
|
|
47
|
+
#started = false;
|
|
48
|
+
constructor(options) {
|
|
49
|
+
this.#transport = options.transport;
|
|
50
|
+
this.#partition = options.partition;
|
|
51
|
+
this.#namespace = options.namespace;
|
|
52
|
+
this.#prefix = options.prefix ?? 'durable';
|
|
53
|
+
this.#instanceId = options.instanceId ?? buildInstanceId();
|
|
54
|
+
this.#sdk = options.sdk ?? { ...WORKER_SDK };
|
|
55
|
+
this.#capabilities = [...(options.capabilities ?? [])];
|
|
56
|
+
this.#registry = options.registry ?? new NoopWorkerRegistry();
|
|
57
|
+
this.#heartbeatIntervalMs = options.heartbeatIntervalMs ?? WORKER_HEARTBEAT_INTERVAL_MS;
|
|
58
|
+
this.#ttlSeconds = options.ttlSeconds ?? WORKER_HEARTBEAT_TTL_SECONDS;
|
|
59
|
+
this.#now = options.now ?? Date.now;
|
|
60
|
+
this.#logger = options.logger ?? SILENT_LOGGER;
|
|
61
|
+
this.#onError = options.onError ?? (() => { });
|
|
62
|
+
// Process start time — a restart changes it, so the descriptor hash (and thus the ETag) changes.
|
|
63
|
+
this.#startedAt = this.#now();
|
|
64
|
+
}
|
|
65
|
+
/** Stable id stamped on this instance's liveness/descriptor keys. */
|
|
66
|
+
get instanceId() {
|
|
67
|
+
return this.#instanceId;
|
|
68
|
+
}
|
|
69
|
+
/** The step handler names this worker serves (sorted). */
|
|
70
|
+
get stepNames() {
|
|
71
|
+
return [...this.#steps].sort();
|
|
72
|
+
}
|
|
73
|
+
/** The workflow names this worker advertises (sorted). */
|
|
74
|
+
get workflowNames() {
|
|
75
|
+
return [...this.#workflows].sort();
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Register a step handler on the transport (which subscribes its `${P}-tasks-<token>` queue and runs
|
|
79
|
+
* each task through the shared `runStepHandler`) AND record its name for the descriptor. Named
|
|
80
|
+
* `handle` too so `step-discovery`'s `registerStep`/`registerStepsFromDir`/`registerStepsFromBarrel`
|
|
81
|
+
* can target the runtime directly (it satisfies their `StepServer`). Re-registering swaps the handler;
|
|
82
|
+
* if already {@link start}ed, the descriptor is re-advertised so the new name is routable.
|
|
83
|
+
*/
|
|
84
|
+
handle(name, fn) {
|
|
85
|
+
this.#transport.handle(name, fn);
|
|
86
|
+
const isNew = !this.#steps.has(name);
|
|
87
|
+
this.#steps.add(name);
|
|
88
|
+
if (isNew && this.#started)
|
|
89
|
+
void this.#advertise();
|
|
90
|
+
}
|
|
91
|
+
/** Alias of {@link handle} reading as intent at a call site that isn't the discovery `StepServer`. */
|
|
92
|
+
registerStep(name, fn) {
|
|
93
|
+
this.handle(name, fn);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Register a runnable WORKFLOW TURN body (`(ctx, input) => output`, written against the worker-side
|
|
97
|
+
* {@link import('../workflow-turn.js').WorkflowTurnCtx}) AND advertise its name. This is what lets a
|
|
98
|
+
* store-less TS worker EXECUTE workflow turns for parity with a Python worker (design §4): the
|
|
99
|
+
* transport subscribes the workflow's `${P}-tasks-<token>` queue and, on a `workflow`-shaped job, the
|
|
100
|
+
* turn is replayed via the shared {@link runWorkflowTurn} (deterministic history → decision) and the
|
|
101
|
+
* {@link import('../interfaces.js').WorkflowDecision} published on `${P}-decisions`. The worker never
|
|
102
|
+
* touches a store — every durable decision stays on the control-plane; this only replays the body.
|
|
103
|
+
*
|
|
104
|
+
* NOTE: this is the polyglot TURN surface (the TS twin of the Python `@workflow`), NOT the
|
|
105
|
+
* store-backed `BaseWorkflow`/`WorkflowCtx` the engine runs in-process — those are a different
|
|
106
|
+
* authoring surface (they own the store). Re-registering swaps the body; if the transport can't carry
|
|
107
|
+
* workflow tasks (`handleWorkflow` absent) the name is still advertised, just not executed here.
|
|
108
|
+
*/
|
|
109
|
+
registerWorkflow(name, body) {
|
|
110
|
+
const firstBody = !this.#workflowBodies.has(name);
|
|
111
|
+
this.#workflowBodies.set(name, body);
|
|
112
|
+
// Wire the transport's turn consumer ONCE per name (the resolver reads the live #workflowBodies map,
|
|
113
|
+
// so a later re-register is picked up without re-wiring). No-op if the transport can't carry turns.
|
|
114
|
+
if (firstBody && this.#transport.handleWorkflow) {
|
|
115
|
+
this.#transport.handleWorkflow(name, (task) => runWorkflowTurn(this.#workflowBodies, task, {
|
|
116
|
+
partition: this.#partition,
|
|
117
|
+
...(this.#transport.dispatchStepEvent
|
|
118
|
+
? { onStep: (event) => void this.#transport.dispatchStepEvent?.(event) }
|
|
119
|
+
: {}),
|
|
120
|
+
}));
|
|
121
|
+
}
|
|
122
|
+
const isNew = !this.#workflows.has(name);
|
|
123
|
+
this.#workflows.add(name);
|
|
124
|
+
if (isNew && this.#started)
|
|
125
|
+
void this.#advertise();
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Advertise a workflow NAME in the descriptor WITHOUT a runnable body (routing + capability
|
|
129
|
+
* negotiation only, design §7.1) — e.g. this pod routes/observes a workflow another worker executes.
|
|
130
|
+
* To actually EXECUTE turns here, use {@link registerWorkflow}. If already {@link start}ed,
|
|
131
|
+
* re-advertises so the name is visible.
|
|
132
|
+
*/
|
|
133
|
+
registerWorkflowName(name) {
|
|
134
|
+
const isNew = !this.#workflows.has(name);
|
|
135
|
+
this.#workflows.add(name);
|
|
136
|
+
if (isNew && this.#started)
|
|
137
|
+
void this.#advertise();
|
|
138
|
+
}
|
|
139
|
+
/** Advertise many workflow names at once (one re-advertise). */
|
|
140
|
+
registerWorkflowNames(names) {
|
|
141
|
+
let added = false;
|
|
142
|
+
for (const name of names) {
|
|
143
|
+
if (!this.#workflows.has(name))
|
|
144
|
+
added = true;
|
|
145
|
+
this.#workflows.add(name);
|
|
146
|
+
}
|
|
147
|
+
if (added && this.#started)
|
|
148
|
+
void this.#advertise();
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* The worker's current {@link WorkerDescriptor} — the single source of truth for routing, compat and
|
|
152
|
+
* observability (design §7.1). `workflows`/`steps` are the registered handler names (sorted, a set);
|
|
153
|
+
* `capabilities` are the configured extra features; `protocol` is the current major with a `[1, N]`
|
|
154
|
+
* range. Rebuilt on each read so it always reflects what is registered right now.
|
|
155
|
+
*/
|
|
156
|
+
descriptor() {
|
|
157
|
+
return {
|
|
158
|
+
instanceId: this.#instanceId,
|
|
159
|
+
runtime: 'node',
|
|
160
|
+
sdk: { ...this.#sdk },
|
|
161
|
+
protocol: { version: CURRENT_PROTOCOL_VERSION, range: [1, CURRENT_PROTOCOL_VERSION] },
|
|
162
|
+
capabilities: [...this.#capabilities],
|
|
163
|
+
workflows: [...this.#workflows].sort(),
|
|
164
|
+
steps: [...this.#steps].sort(),
|
|
165
|
+
...(this.#partition ? { partition: this.#partition } : {}),
|
|
166
|
+
...(this.#namespace !== undefined ? { namespace: this.#namespace } : {}),
|
|
167
|
+
startedAt: this.#startedAt,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
/** The `${P}-worker-descriptor:<token>:<instance>` / `${P}-worker-heartbeat:<token>:<instance>` keys
|
|
171
|
+
* this worker owns — one per DISTINCT routing token across its registered steps + workflows. */
|
|
172
|
+
#tokens() {
|
|
173
|
+
const tokens = new Set();
|
|
174
|
+
for (const name of this.#steps)
|
|
175
|
+
tokens.add(routingToken(name, this.#partition));
|
|
176
|
+
for (const name of this.#workflows)
|
|
177
|
+
tokens.add(routingToken(name, this.#partition));
|
|
178
|
+
return [...tokens];
|
|
179
|
+
}
|
|
180
|
+
#effPrefix() {
|
|
181
|
+
return effectivePrefix(this.#prefix, this.#namespace);
|
|
182
|
+
}
|
|
183
|
+
/** Publish the full descriptor under every handled token's descriptor key (design §7.2). */
|
|
184
|
+
async #advertise() {
|
|
185
|
+
const descriptor = this.descriptor();
|
|
186
|
+
const effPrefix = this.#effPrefix();
|
|
187
|
+
try {
|
|
188
|
+
await Promise.all(this.#tokens().map((token) => this.#registry.advertiseDescriptor({
|
|
189
|
+
key: workerDescriptorKey(effPrefix, token, this.#instanceId),
|
|
190
|
+
descriptor,
|
|
191
|
+
ttlSeconds: this.#ttlSeconds,
|
|
192
|
+
})));
|
|
193
|
+
}
|
|
194
|
+
catch (err) {
|
|
195
|
+
this.#onError(err);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/** Beat the compact `{ ts, status, descriptorHash }` under every handled token's heartbeat key. */
|
|
199
|
+
async #beat(status) {
|
|
200
|
+
const descriptor = this.descriptor();
|
|
201
|
+
const beat = heartbeatStatus(descriptor, { ts: this.#now(), status });
|
|
202
|
+
const effPrefix = this.#effPrefix();
|
|
203
|
+
try {
|
|
204
|
+
await Promise.all(this.#tokens().map((token) => this.#registry.beat({
|
|
205
|
+
key: workerHeartbeatKey(effPrefix, token, this.#instanceId),
|
|
206
|
+
status: beat,
|
|
207
|
+
ttlSeconds: this.#ttlSeconds,
|
|
208
|
+
})));
|
|
209
|
+
}
|
|
210
|
+
catch (err) {
|
|
211
|
+
this.#onError(err);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Begin advertising: publish the descriptor + do the first heartbeat immediately (so a freshly
|
|
216
|
+
* started worker is visible without waiting a full interval), then refresh both on the heartbeat
|
|
217
|
+
* schedule. Idempotent. The interval is `unref`'d so it never keeps the process alive on its own.
|
|
218
|
+
*/
|
|
219
|
+
async start() {
|
|
220
|
+
if (this.#started)
|
|
221
|
+
return;
|
|
222
|
+
this.#started = true;
|
|
223
|
+
await this.#advertise();
|
|
224
|
+
await this.#beat('up');
|
|
225
|
+
this.#beatTimer = setInterval(() => {
|
|
226
|
+
// Re-advertise each cycle too: refreshes the descriptor key's TTL (cheap; the ETag is stable so a
|
|
227
|
+
// reader still skips the re-read) so it never expires under a live worker.
|
|
228
|
+
void this.#advertise();
|
|
229
|
+
void this.#beat('up');
|
|
230
|
+
}, this.#heartbeatIntervalMs);
|
|
231
|
+
this.#beatTimer.unref?.();
|
|
232
|
+
this.#logger.info(`worker-runtime started (instance ${this.#instanceId}, partition ${this.#partition}, ` +
|
|
233
|
+
`${this.#steps.size} steps, ${this.#workflows.size} workflows)`);
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Stop advertising and release resources: clears the beat interval, best-effort removes this
|
|
237
|
+
* instance's keys (a graceful drain — a watcher sees the worker leave immediately, not on TTL
|
|
238
|
+
* expiry), then closes the registry's owned connection and the transport. Idempotent.
|
|
239
|
+
*/
|
|
240
|
+
async stop() {
|
|
241
|
+
if (!this.#started)
|
|
242
|
+
return;
|
|
243
|
+
this.#started = false;
|
|
244
|
+
if (this.#beatTimer)
|
|
245
|
+
clearInterval(this.#beatTimer);
|
|
246
|
+
this.#beatTimer = undefined;
|
|
247
|
+
if (this.#registry.remove) {
|
|
248
|
+
const effPrefix = this.#effPrefix();
|
|
249
|
+
const keys = this.#tokens().flatMap((token) => [
|
|
250
|
+
workerDescriptorKey(effPrefix, token, this.#instanceId),
|
|
251
|
+
workerHeartbeatKey(effPrefix, token, this.#instanceId),
|
|
252
|
+
]);
|
|
253
|
+
try {
|
|
254
|
+
await this.#registry.remove(keys);
|
|
255
|
+
}
|
|
256
|
+
catch (err) {
|
|
257
|
+
this.#onError(err);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
await this.#registry.close?.();
|
|
261
|
+
await this.#transport.close?.();
|
|
262
|
+
this.#logger.info(`worker-runtime stopped (instance ${this.#instanceId})`);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
//# sourceMappingURL=worker-runtime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-runtime.js","sourceRoot":"","sources":["../../../src/worker-runtime/worker-runtime.ts"],"names":[],"mappings":"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"}
|
|
@@ -62,6 +62,15 @@ export interface CtxHost {
|
|
|
62
62
|
}): Promise<TOutput>;
|
|
63
63
|
/** Start a child run once, deferred so it can't reentrantly resume a still-running parent. */
|
|
64
64
|
startChild(workflow: string, input: unknown, id: string, priority?: number): void;
|
|
65
|
+
/**
|
|
66
|
+
* Best-effort cancel of a child run — deferred for the SAME reason as `startChild` (so ctx code
|
|
67
|
+
* can't reentrantly mutate engine state while the parent's own step is still executing
|
|
68
|
+
* synchronously). Plain cancel, no saga undo; used by `ctx.all`'s `failFast` mode to stop the
|
|
69
|
+
* surviving siblings once one has failed. Cancelling an already-terminal/cancelled run is a no-op
|
|
70
|
+
* (`engine.cancel`'s own idempotency guard), so a replay that re-issues the same calls is safe.
|
|
71
|
+
* Optional: hosts that never construct a `ctx.all` failFast call (e.g. test fakes) can omit it.
|
|
72
|
+
*/
|
|
73
|
+
cancelChild?(childId: string): void;
|
|
65
74
|
/** Shallow-merge `attrs` into the run's `searchAttributes` (see {@link WorkflowCtx.upsertSearchAttributes}). */
|
|
66
75
|
upsertSearchAttributes(runId: string, attrs: SearchAttributes): Promise<void>;
|
|
67
76
|
/** Deliver an op to a durable entity (deferred), optionally with a `reply` token for the result. */
|