@adonis-agora/durable 0.3.0 → 0.5.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 +24 -0
- package/README.md +47 -0
- package/dist/commands/main.d.ts +3 -2
- package/dist/commands/main.d.ts.map +1 -1
- package/dist/commands/main.js +2 -1
- package/dist/commands/main.js.map +1 -1
- package/dist/commands/make_workflow.d.ts +16 -0
- package/dist/commands/make_workflow.d.ts.map +1 -0
- package/dist/commands/make_workflow.js +30 -0
- package/dist/commands/make_workflow.js.map +1 -0
- package/dist/configure.d.ts +5 -2
- package/dist/configure.d.ts.map +1 -1
- package/dist/configure.js +7 -2
- package/dist/configure.js.map +1 -1
- package/dist/providers/durable_provider.d.ts +12 -0
- package/dist/providers/durable_provider.d.ts.map +1 -1
- package/dist/providers/durable_provider.js +50 -2
- package/dist/providers/durable_provider.js.map +1 -1
- package/dist/src/define_config.d.ts +11 -3
- package/dist/src/define_config.d.ts.map +1 -1
- package/dist/src/define_config.js.map +1 -1
- package/dist/src/hooks/workflows.d.ts +53 -0
- package/dist/src/hooks/workflows.d.ts.map +1 -0
- package/dist/src/hooks/workflows.js +55 -0
- package/dist/src/hooks/workflows.js.map +1 -0
- package/dist/src/index.d.ts +3 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/protocol.d.ts.map +1 -1
- package/dist/src/protocol.js +50 -18
- package/dist/src/protocol.js.map +1 -1
- package/dist/src/transports/event-emitter.d.ts +60 -0
- package/dist/src/transports/event-emitter.d.ts.map +1 -0
- package/dist/src/transports/event-emitter.js +103 -0
- package/dist/src/transports/event-emitter.js.map +1 -0
- package/dist/src/transports/factory.d.ts +23 -2
- package/dist/src/transports/factory.d.ts.map +1 -1
- package/dist/src/transports/factory.js +20 -1
- package/dist/src/transports/factory.js.map +1 -1
- package/dist/src/workflow-discovery.d.ts +45 -0
- package/dist/src/workflow-discovery.d.ts.map +1 -0
- package/dist/src/workflow-discovery.js +103 -0
- package/dist/src/workflow-discovery.js.map +1 -0
- package/dist/src/workflow-ref.d.ts +41 -5
- package/dist/src/workflow-ref.d.ts.map +1 -1
- package/dist/src/workflow-ref.js +37 -6
- package/dist/src/workflow-ref.js.map +1 -1
- package/dist/stubs/config/durable.stub +6 -1
- package/dist/stubs/make/workflow/main.stub +22 -0
- package/package.json +11 -2
package/dist/src/protocol.js
CHANGED
|
@@ -1,4 +1,30 @@
|
|
|
1
1
|
import { createStepLogger } from './step-logger.js';
|
|
2
|
+
/**
|
|
3
|
+
* The scoped-restore slot `@adonis-agora/context` exposes:
|
|
4
|
+
* `<T>(snapshot: Record<string, unknown> | undefined, fn: () => T) => T`. It runs `fn` INSIDE a
|
|
5
|
+
* freshly-activated context store seeded from `snapshot` (or just `fn()` when snapshot/slot is
|
|
6
|
+
* absent). Read structurally so a worker restores the originating request's userRef/tenant/traceId
|
|
7
|
+
* around a step handler with zero config when context is installed — and a clean no-op (`fn()`) when
|
|
8
|
+
* it is not. Wrapping (rather than the old `:set`, which only populates an already-active store) gives
|
|
9
|
+
* both correct propagation AND per-task isolation on a long-lived worker: each task runs in its own
|
|
10
|
+
* scope, so context never bleeds between tasks. The key is a global-registry symbol so it survives
|
|
11
|
+
* duplicate copies of either package in a dependency tree.
|
|
12
|
+
*/
|
|
13
|
+
const CONTEXT_SCOPE = Symbol.for('@agora/context:scope');
|
|
14
|
+
/**
|
|
15
|
+
* Run `fn` inside the originating request's context (userRef/tenant/traceId), restored from the
|
|
16
|
+
* task's snapshot (stamped at dispatch by the originating engine, see {@link RemoteTask.context}),
|
|
17
|
+
* so a step handler sees it with no manual code. When `@adonis-agora/context` is not installed (slot
|
|
18
|
+
* absent) the body runs directly — a clean no-op fallback. Never throws from the propagation path: a
|
|
19
|
+
* non-function slot is ignored; errors inside `fn` propagate as the step's own failure (the scope
|
|
20
|
+
* helper itself re-throws nothing of its own).
|
|
21
|
+
*/
|
|
22
|
+
function withRestoredContext(snapshot, fn) {
|
|
23
|
+
const scope = globalThis[CONTEXT_SCOPE];
|
|
24
|
+
if (typeof scope !== 'function')
|
|
25
|
+
return fn();
|
|
26
|
+
return scope(snapshot, fn);
|
|
27
|
+
}
|
|
2
28
|
/** Canonical step id — the stable identity of a step within a run, used for dedupe and
|
|
3
29
|
* correlation. The format is part of the cross-language wire contract (Python builds the same). */
|
|
4
30
|
export function stepId(runId, seq) {
|
|
@@ -25,23 +51,29 @@ export async function runStepHandler(task, handler) {
|
|
|
25
51
|
}
|
|
26
52
|
const events = [];
|
|
27
53
|
const withEvents = (result) => events.length > 0 ? { ...result, events } : result;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
...
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
54
|
+
// Run the handler INSIDE the originating request's context (userRef/tenant/traceId), restored from
|
|
55
|
+
// the task snapshot, so cross-process propagation is automatic — `ctx.call(remoteStep, input)`
|
|
56
|
+
// carries the caller's context with zero manual serialize/deserialize, and each task runs in its
|
|
57
|
+
// own scope (no cross-task bleed on a long-lived worker). No-op without `@adonis-agora/context`.
|
|
58
|
+
return withRestoredContext(task.context, async () => {
|
|
59
|
+
try {
|
|
60
|
+
const output = await handler(task.input, createStepLogger(events, Date.now));
|
|
61
|
+
return withEvents({ ...base, status: 'completed', output });
|
|
62
|
+
}
|
|
63
|
+
catch (err) {
|
|
64
|
+
// Carry `code`/`retryable` off the thrown error if present, so the engine's durable retry can
|
|
65
|
+
// honour a worker's "don't retry this" verdict (e.g. a declined card).
|
|
66
|
+
const e = err;
|
|
67
|
+
return withEvents({
|
|
68
|
+
...base,
|
|
69
|
+
status: 'failed',
|
|
70
|
+
error: {
|
|
71
|
+
message: err instanceof Error ? err.message : String(err),
|
|
72
|
+
...(typeof e?.code === 'string' ? { code: e.code } : {}),
|
|
73
|
+
...(typeof e?.retryable === 'boolean' ? { retryable: e.retryable } : {}),
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
});
|
|
46
78
|
}
|
|
47
79
|
//# sourceMappingURL=protocol.js.map
|
package/dist/src/protocol.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"protocol.js","sourceRoot":"","sources":["../../src/protocol.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEpD;oGACoG;AACpG,MAAM,UAAU,MAAM,CAAC,KAAa,EAAE,GAAW;IAC/C,OAAO,GAAG,KAAK,IAAI,GAAG,EAAE,CAAC;AAC3B,CAAC;AAED;6FAC6F;AAC7F,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,KAAa,EAAE,GAAW,EAAU,EAAE,CAAC,MAAM,KAAK,IAAI,GAAG,EAAE,CAAC;AAM5F;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,IAAgB,EAChB,OAAgC;IAEhC,+FAA+F;IAC/F,8FAA8F;IAC9F,MAAM,IAAI,GAAG,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;IAC9F,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO;YACL,GAAG,IAAI;YACP,MAAM,EAAE,QAAQ;YAChB,KAAK,EAAE,EAAE,OAAO,EAAE,kBAAkB,IAAI,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE;SACpE,CAAC;IACJ,CAAC;IACD,MAAM,MAAM,GAAgB,EAAE,CAAC;IAC/B,MAAM,UAAU,GAAG,CAAC,MAAkB,EAAc,EAAE,CACpD,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;IACrD,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"protocol.js","sourceRoot":"","sources":["../../src/protocol.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import { type ControlMessage, type ControlPlane, type Heartbeat, type RemoteTask, type StepResult, type Transport } from '../interfaces.js';
|
|
3
|
+
import { type StepHandler } from '../protocol.js';
|
|
4
|
+
/** Event names the transport multiplexes over a single in-process emitter. */
|
|
5
|
+
export declare const TASK_EVENT = "durable.task";
|
|
6
|
+
export declare const RESULT_EVENT = "durable.result";
|
|
7
|
+
export declare const HEARTBEAT_EVENT = "durable.heartbeat";
|
|
8
|
+
export declare const CONTROL_EVENT = "durable.control";
|
|
9
|
+
export interface EventEmitterTransportOptions {
|
|
10
|
+
/**
|
|
11
|
+
* The in-process emitter to multiplex over. Defaults to a fresh Node `EventEmitter` (listener cap
|
|
12
|
+
* lifted, since the engine wires several long-lived listeners). Pass `@adonisjs/core`'s `emitter`
|
|
13
|
+
* service if you'd rather ride the app's bus — it is API-compatible for `on`/`emit`.
|
|
14
|
+
*/
|
|
15
|
+
emitter?: EventEmitter;
|
|
16
|
+
/**
|
|
17
|
+
* The worker group this instance serves. Unused for routing (handlers are matched by step name in
|
|
18
|
+
* this same process) — accepted for parity with the broker transports, and stamped nowhere.
|
|
19
|
+
*/
|
|
20
|
+
group?: string;
|
|
21
|
+
/** Stable id for this process (stamped on control `from` when a publisher leaves it unset). Default random. */
|
|
22
|
+
instanceId?: string;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* A production **in-process** {@link Transport} (and {@link ControlPlane}) backed by a single Node
|
|
26
|
+
* `EventEmitter`. Zero external infrastructure (no DB, no Redis, no broker): step handlers run in the
|
|
27
|
+
* same process, fully decoupled from the workflow that dispatched them, so a single-process app runs
|
|
28
|
+
* real durable workflows with nothing else to deploy.
|
|
29
|
+
*
|
|
30
|
+
* Distinct from the test-only {@link import('../testing/in-memory-transport.js').InMemoryTransport}:
|
|
31
|
+
* that one drives `dispatch` straight into the handler (synchronous-ish, for deterministic tests);
|
|
32
|
+
* this one decouples both directions through the emitter's event loop, mirroring how a real broker
|
|
33
|
+
* fans dispatch → worker → result back. Both funnel every step through {@link runStepHandler}, so the
|
|
34
|
+
* scoped context restore (the `@agora/context:scope` slot) works identically here.
|
|
35
|
+
*
|
|
36
|
+
* Swap to {@link import('./db.js').DbTransport} / {@link import('./queue.js').QueueTransport} for
|
|
37
|
+
* true cross-process or cross-language steps. The {@link ControlPlane} here broadcasts locally (every
|
|
38
|
+
* subscriber in this process), correct for single-instance; it does NOT fan out across pods.
|
|
39
|
+
*
|
|
40
|
+
* Usually you don't construct this directly: `config/durable.ts` selects it via
|
|
41
|
+
* `transports.eventEmitter()` (alias `transports.memory()` from the factory points at the test
|
|
42
|
+
* transport) and the provider builds it for you.
|
|
43
|
+
*/
|
|
44
|
+
export declare class EventEmitterTransport implements Transport, ControlPlane {
|
|
45
|
+
#private;
|
|
46
|
+
constructor(options?: EventEmitterTransportOptions);
|
|
47
|
+
/** Stable id stamped on control `from` when a publisher leaves it unset. */
|
|
48
|
+
get instanceId(): string;
|
|
49
|
+
dispatch(task: RemoteTask): Promise<void>;
|
|
50
|
+
/** Register a step handler by name (the worker side, in this same process). */
|
|
51
|
+
handle(name: string, fn: StepHandler): void;
|
|
52
|
+
/** Worker side: a liveness heartbeat. In-process handlers run synchronously, so emit it straight
|
|
53
|
+
* through for symmetry — an engine that wired `onHeartbeat` still observes it. */
|
|
54
|
+
heartbeat(beat: Heartbeat): Promise<void>;
|
|
55
|
+
onResult(handler: (result: StepResult) => Promise<void>): void;
|
|
56
|
+
onHeartbeat(handler: (beat: Heartbeat) => Promise<void>): void;
|
|
57
|
+
publishControl(msg: ControlMessage): Promise<void>;
|
|
58
|
+
onControl(handler: (msg: ControlMessage) => void): void;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=event-emitter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-emitter.d.ts","sourceRoot":"","sources":["../../../src/transports/event-emitter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,SAAS,EACd,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,SAAS,EACf,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,KAAK,WAAW,EAAkB,MAAM,gBAAgB,CAAC;AAElE,8EAA8E;AAC9E,eAAO,MAAM,UAAU,iBAAiB,CAAC;AACzC,eAAO,MAAM,YAAY,mBAAmB,CAAC;AAC7C,eAAO,MAAM,eAAe,sBAAsB,CAAC;AACnD,eAAO,MAAM,aAAa,oBAAoB,CAAC;AAE/C,MAAM,WAAW,4BAA4B;IAC3C;;;;OAIG;IACH,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+GAA+G;IAC/G,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAMD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,qBAAsB,YAAW,SAAS,EAAE,YAAY;;gBAKvD,OAAO,GAAE,4BAAiC;IAatD,4EAA4E;IAC5E,IAAI,UAAU,IAAI,MAAM,CAEvB;IAMK,QAAQ,CAAC,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ/C,+EAA+E;IAC/E,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,WAAW,GAAG,IAAI;IAI3C;uFACmF;IAC7E,SAAS,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAmB/C,QAAQ,CAAC,OAAO,EAAE,CAAC,MAAM,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IAM9D,WAAW,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,SAAS,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IAUxD,cAAc,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAKxD,SAAS,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,IAAI,GAAG,IAAI;CAGxD"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { EventEmitter } from 'node:events';
|
|
3
|
+
import { runStepHandler } from '../protocol.js';
|
|
4
|
+
/** Event names the transport multiplexes over a single in-process emitter. */
|
|
5
|
+
export const TASK_EVENT = 'durable.task';
|
|
6
|
+
export const RESULT_EVENT = 'durable.result';
|
|
7
|
+
export const HEARTBEAT_EVENT = 'durable.heartbeat';
|
|
8
|
+
export const CONTROL_EVENT = 'durable.control';
|
|
9
|
+
/**
|
|
10
|
+
* A production **in-process** {@link Transport} (and {@link ControlPlane}) backed by a single Node
|
|
11
|
+
* `EventEmitter`. Zero external infrastructure (no DB, no Redis, no broker): step handlers run in the
|
|
12
|
+
* same process, fully decoupled from the workflow that dispatched them, so a single-process app runs
|
|
13
|
+
* real durable workflows with nothing else to deploy.
|
|
14
|
+
*
|
|
15
|
+
* Distinct from the test-only {@link import('../testing/in-memory-transport.js').InMemoryTransport}:
|
|
16
|
+
* that one drives `dispatch` straight into the handler (synchronous-ish, for deterministic tests);
|
|
17
|
+
* this one decouples both directions through the emitter's event loop, mirroring how a real broker
|
|
18
|
+
* fans dispatch → worker → result back. Both funnel every step through {@link runStepHandler}, so the
|
|
19
|
+
* scoped context restore (the `@agora/context:scope` slot) works identically here.
|
|
20
|
+
*
|
|
21
|
+
* Swap to {@link import('./db.js').DbTransport} / {@link import('./queue.js').QueueTransport} for
|
|
22
|
+
* true cross-process or cross-language steps. The {@link ControlPlane} here broadcasts locally (every
|
|
23
|
+
* subscriber in this process), correct for single-instance; it does NOT fan out across pods.
|
|
24
|
+
*
|
|
25
|
+
* Usually you don't construct this directly: `config/durable.ts` selects it via
|
|
26
|
+
* `transports.eventEmitter()` (alias `transports.memory()` from the factory points at the test
|
|
27
|
+
* transport) and the provider builds it for you.
|
|
28
|
+
*/
|
|
29
|
+
export class EventEmitterTransport {
|
|
30
|
+
#emitter;
|
|
31
|
+
#instanceId;
|
|
32
|
+
#handlers = new Map();
|
|
33
|
+
constructor(options = {}) {
|
|
34
|
+
// A long-lived single emitter carries several engine listeners; lift Node's default cap so the
|
|
35
|
+
// engine wiring (result + heartbeat + control + task) never trips a MaxListenersExceededWarning.
|
|
36
|
+
const emitter = options.emitter ?? new EventEmitter();
|
|
37
|
+
if (emitter instanceof EventEmitter)
|
|
38
|
+
emitter.setMaxListeners(0);
|
|
39
|
+
this.#emitter = emitter;
|
|
40
|
+
this.#instanceId = options.instanceId ?? randomUUID();
|
|
41
|
+
// The worker side listens for every dispatched task and runs it if it owns the step name.
|
|
42
|
+
this.#emitter.on(TASK_EVENT, (task) => {
|
|
43
|
+
void this.#process(task);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/** Stable id stamped on control `from` when a publisher leaves it unset. */
|
|
47
|
+
get instanceId() {
|
|
48
|
+
return this.#instanceId;
|
|
49
|
+
}
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
// engine → worker
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
async dispatch(task) {
|
|
54
|
+
this.#emitter.emit(TASK_EVENT, task);
|
|
55
|
+
}
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
// worker side — register a step handler, run it, emit the result back
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
/** Register a step handler by name (the worker side, in this same process). */
|
|
60
|
+
handle(name, fn) {
|
|
61
|
+
this.#handlers.set(name, fn);
|
|
62
|
+
}
|
|
63
|
+
/** Worker side: a liveness heartbeat. In-process handlers run synchronously, so emit it straight
|
|
64
|
+
* through for symmetry — an engine that wired `onHeartbeat` still observes it. */
|
|
65
|
+
async heartbeat(beat) {
|
|
66
|
+
this.#emitter.emit(HEARTBEAT_EVENT, beat);
|
|
67
|
+
}
|
|
68
|
+
async #process(task) {
|
|
69
|
+
const handler = this.#handlers.get(task.name);
|
|
70
|
+
// Another subscriber may own this step name — stay silent, don't synthesize a "no handler" failure.
|
|
71
|
+
if (!handler)
|
|
72
|
+
return;
|
|
73
|
+
const result = await runStepHandler(task, handler);
|
|
74
|
+
// Emit the result on a LATER tick: a durable `ctx.call` suspends the run right after dispatch, so
|
|
75
|
+
// the result must land AFTER that unwinds (else the resume re-enters mid-suspend). Real brokers
|
|
76
|
+
// deliver asynchronously; this mirrors them.
|
|
77
|
+
setImmediate(() => this.#emitter.emit(RESULT_EVENT, result));
|
|
78
|
+
}
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
// worker → engine — the engine consumes results + heartbeats
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
onResult(handler) {
|
|
83
|
+
this.#emitter.on(RESULT_EVENT, (result) => {
|
|
84
|
+
void handler(result);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
onHeartbeat(handler) {
|
|
88
|
+
this.#emitter.on(HEARTBEAT_EVENT, (beat) => {
|
|
89
|
+
void handler(beat);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
// control plane (broadcast within this process)
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
async publishControl(msg) {
|
|
96
|
+
const stamped = msg.from ? msg : { ...msg, from: this.#instanceId };
|
|
97
|
+
this.#emitter.emit(CONTROL_EVENT, stamped);
|
|
98
|
+
}
|
|
99
|
+
onControl(handler) {
|
|
100
|
+
this.#emitter.on(CONTROL_EVENT, (msg) => handler(msg));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=event-emitter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-emitter.js","sourceRoot":"","sources":["../../../src/transports/event-emitter.ts"],"names":[],"mappings":"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"}
|
|
@@ -24,9 +24,19 @@ export interface TransportContext {
|
|
|
24
24
|
* actually selected — keeping those packages optional.
|
|
25
25
|
*/
|
|
26
26
|
export type TransportFactory = (ctx: TransportContext) => Promise<Transport & Partial<ControlPlane>>;
|
|
27
|
-
/** Options for the in-memory transport (no peer dependency). */
|
|
27
|
+
/** Options for the in-memory (test-only) transport (no peer dependency). */
|
|
28
28
|
export interface MemoryTransportConfig {
|
|
29
29
|
}
|
|
30
|
+
/** Options for the production in-process EventEmitter transport (no peer dependency). */
|
|
31
|
+
export interface EventEmitterTransportConfig {
|
|
32
|
+
/**
|
|
33
|
+
* The worker group this instance serves. Accepted for parity with the broker transports; handlers
|
|
34
|
+
* are matched by step name in-process, so it does not affect routing.
|
|
35
|
+
*/
|
|
36
|
+
group?: string;
|
|
37
|
+
/** Stable id for this process (stamped on control `from` when a publisher leaves it unset). Default random. */
|
|
38
|
+
instanceId?: string;
|
|
39
|
+
}
|
|
30
40
|
/** Options for the `@adonisjs/queue` transport. */
|
|
31
41
|
export interface QueueTransportConfig {
|
|
32
42
|
/**
|
|
@@ -69,8 +79,19 @@ export interface DbTransportConfig {
|
|
|
69
79
|
instanceId?: string;
|
|
70
80
|
}
|
|
71
81
|
export declare const transports: {
|
|
72
|
-
/**
|
|
82
|
+
/**
|
|
83
|
+
* The test-only in-process transport + control plane (the engine's default when no `transport` is
|
|
84
|
+
* named). Drives `dispatch` straight into the handler for deterministic tests — for a real
|
|
85
|
+
* single-process production app, prefer {@link transports.eventEmitter}.
|
|
86
|
+
*/
|
|
73
87
|
memory(_config?: MemoryTransportConfig): TransportFactory;
|
|
88
|
+
/**
|
|
89
|
+
* Production **in-process** transport + control plane backed by a single Node `EventEmitter`. Zero
|
|
90
|
+
* external infrastructure (no DB, no Redis, no broker) — a single-process app runs real durable
|
|
91
|
+
* workflows with nothing else to deploy. Decouples dispatch → worker → result over the event loop
|
|
92
|
+
* (mirroring a real broker), unlike the test-only {@link transports.memory}.
|
|
93
|
+
*/
|
|
94
|
+
eventEmitter(config?: EventEmitterTransportConfig): TransportFactory;
|
|
74
95
|
/** Run remote steps cross-process over `@adonisjs/queue`, using a connection from `config/queue.ts`. */
|
|
75
96
|
queue(config?: QueueTransportConfig): TransportFactory;
|
|
76
97
|
/** Run remote steps cross-process over the app's database, using `@adonisjs/lucid` — no broker. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../../src/transports/factory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAGhE;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,wFAAwF;IACxF,GAAG,EAAE;QACH,SAAS,EAAE;YAAE,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;SAAE,CAAC;QACxD,MAAM,EAAE;YAAE,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,CAAC,GAAG,CAAC,CAAA;SAAE,CAAC;KACtD,CAAC;CACH;AAED;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAC7B,GAAG,EAAE,gBAAgB,KAClB,OAAO,CAAC,SAAS,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC;AAEhD,
|
|
1
|
+
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../../src/transports/factory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAGhE;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,wFAAwF;IACxF,GAAG,EAAE;QACH,SAAS,EAAE;YAAE,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;SAAE,CAAC;QACxD,MAAM,EAAE;YAAE,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,CAAC,GAAG,CAAC,CAAA;SAAE,CAAC;KACtD,CAAC;CACH;AAED;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAC7B,GAAG,EAAE,gBAAgB,KAClB,OAAO,CAAC,SAAS,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC;AAEhD,4EAA4E;AAC5E,MAAM,WAAW,qBAAqB;CAErC;AAED,yFAAyF;AACzF,MAAM,WAAW,2BAA2B;IAC1C;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+GAA+G;IAC/G,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,mDAAmD;AACnD,MAAM,WAAW,oBAAoB;IACnC;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,6FAA6F;IAC7F,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oGAAoG;IACpG,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qFAAqF;IACrF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gGAAgG;IAChG,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,qEAAqE;AACrE,MAAM,WAAW,iBAAiB;IAChC,6FAA6F;IAC7F,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mFAAmF;IACnF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qFAAqF;IACrF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,4GAA4G;IAC5G,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6CAA6C;IAC7C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yEAAyE;IACzE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,0GAA0G;IAC1G,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAyDD,eAAO,MAAM,UAAU;IACrB;;;;OAIG;qBACa,qBAAqB,GAAQ,gBAAgB;IAI7D;;;;;OAKG;0BACkB,2BAA2B,GAAQ,gBAAgB;IAUxE,wGAAwG;mBAC1F,oBAAoB,GAAQ,gBAAgB;IAc1D,mGAAmG;gBACxF,iBAAiB,GAAQ,gBAAgB;CAgBrD,CAAC"}
|
|
@@ -18,10 +18,29 @@ async function resolveQueueAdapter(ctx, connection) {
|
|
|
18
18
|
return typeof entry === 'function' ? entry : entry.resolver(ctx.app);
|
|
19
19
|
}
|
|
20
20
|
export const transports = {
|
|
21
|
-
/**
|
|
21
|
+
/**
|
|
22
|
+
* The test-only in-process transport + control plane (the engine's default when no `transport` is
|
|
23
|
+
* named). Drives `dispatch` straight into the handler for deterministic tests — for a real
|
|
24
|
+
* single-process production app, prefer {@link transports.eventEmitter}.
|
|
25
|
+
*/
|
|
22
26
|
memory(_config = {}) {
|
|
23
27
|
return async () => new InMemoryTransport();
|
|
24
28
|
},
|
|
29
|
+
/**
|
|
30
|
+
* Production **in-process** transport + control plane backed by a single Node `EventEmitter`. Zero
|
|
31
|
+
* external infrastructure (no DB, no Redis, no broker) — a single-process app runs real durable
|
|
32
|
+
* workflows with nothing else to deploy. Decouples dispatch → worker → result over the event loop
|
|
33
|
+
* (mirroring a real broker), unlike the test-only {@link transports.memory}.
|
|
34
|
+
*/
|
|
35
|
+
eventEmitter(config = {}) {
|
|
36
|
+
return async () => {
|
|
37
|
+
const { EventEmitterTransport } = await import('./event-emitter.js');
|
|
38
|
+
return new EventEmitterTransport({
|
|
39
|
+
...(config.group !== undefined ? { group: config.group } : {}),
|
|
40
|
+
...(config.instanceId !== undefined ? { instanceId: config.instanceId } : {}),
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
},
|
|
25
44
|
/** Run remote steps cross-process over `@adonisjs/queue`, using a connection from `config/queue.ts`. */
|
|
26
45
|
queue(config = {}) {
|
|
27
46
|
return async (ctx) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"factory.js","sourceRoot":"","sources":["../../../src/transports/factory.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,MAAM,mCAAmC,CAAC;
|
|
1
|
+
{"version":3,"file":"factory.js","sourceRoot":"","sources":["../../../src/transports/factory.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { WorkflowEngine } from './engine.js';
|
|
2
|
+
import { type WorkflowClass, type WorkflowMeta } from './workflow-ref.js';
|
|
3
|
+
/** A discovered, decorated workflow class plus the metadata its `@Workflow` decorator stamped. */
|
|
4
|
+
export interface DiscoveredWorkflow {
|
|
5
|
+
meta: WorkflowMeta;
|
|
6
|
+
cls: WorkflowClass;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Register a single `@Workflow`-decorated class on the engine: instantiate it once and bind its
|
|
10
|
+
* `run(ctx, input)` as the workflow body via `engine.register`. The low-level
|
|
11
|
+
* `engine.register(name, version, fn)` stays the escape hatch — this is the convenience the
|
|
12
|
+
* `app/workflows` convention builds on. No-op (returns `false`) for an undecorated class.
|
|
13
|
+
*/
|
|
14
|
+
export declare function registerWorkflowClass(engine: WorkflowEngine, cls: unknown): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Scan a directory RECURSIVELY for modules and collect every exported `@Workflow`-decorated class
|
|
17
|
+
* (the default export and any named export are considered) — so nested conventions like
|
|
18
|
+
* `app/workflows/billing/charge_workflow.ts` are found, matching `make:workflow`'s nested-path
|
|
19
|
+
* scaffolding. Only the environment-appropriate extension is imported (see {@link MODULE_EXT}), and
|
|
20
|
+
* each module path is visited once, so a built `.js` and a dev `.ts` of the same module never both
|
|
21
|
+
* register. Missing directory → empty list (the convention is opt-in: no `app/workflows`, nothing to
|
|
22
|
+
* register).
|
|
23
|
+
*/
|
|
24
|
+
export declare function discoverWorkflows(dir: string): Promise<DiscoveredWorkflow[]>;
|
|
25
|
+
/**
|
|
26
|
+
* Discover every `@Workflow` class under `dir` and register each on the engine. Returns the
|
|
27
|
+
* registered metadata so the caller can log what was wired. Best-effort over a missing directory.
|
|
28
|
+
*/
|
|
29
|
+
export declare function registerWorkflowsFromDir(engine: WorkflowEngine, dir: string): Promise<WorkflowMeta[]>;
|
|
30
|
+
/**
|
|
31
|
+
* The shape of the build-time barrel generated by the Assembler `init` hook
|
|
32
|
+
* (`@adonis-agora/durable/hooks/workflows`): a map of stable key → lazy module import, e.g.
|
|
33
|
+
* `{ Charge: () => import('#workflows/charge_workflow') }`. The exact key is irrelevant here — we
|
|
34
|
+
* register every `@Workflow`-decorated export of every module, identical to the runtime scan.
|
|
35
|
+
*/
|
|
36
|
+
export type WorkflowsBarrel = Record<string, () => Promise<Record<string, unknown>>>;
|
|
37
|
+
/**
|
|
38
|
+
* Register every `@Workflow` class reachable from a generated {@link WorkflowsBarrel}, by awaiting
|
|
39
|
+
* each lazy module import and registering each decorated export — the build-time equivalent of
|
|
40
|
+
* {@link registerWorkflowsFromDir}, with no runtime `readdir`. Each module is imported once and each
|
|
41
|
+
* decorated class registered once (deduped), so a class re-exported from several modules is safe.
|
|
42
|
+
* Returns the registered metadata so the caller can log what was wired.
|
|
43
|
+
*/
|
|
44
|
+
export declare function registerWorkflowsFromBarrel(engine: WorkflowEngine, barrel: WorkflowsBarrel): Promise<WorkflowMeta[]>;
|
|
45
|
+
//# sourceMappingURL=workflow-discovery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflow-discovery.d.ts","sourceRoot":"","sources":["../../src/workflow-discovery.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,KAAK,aAAa,EAAE,KAAK,YAAY,EAAgB,MAAM,mBAAmB,CAAC;AAExF,kGAAkG;AAClG,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,YAAY,CAAC;IACnB,GAAG,EAAE,aAAa,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,cAAc,EAAE,GAAG,EAAE,OAAO,GAAG,OAAO,CAgBnF;AAUD;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAuBlF;AAED;;;GAGG;AACH,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,cAAc,EACtB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,YAAY,EAAE,CAAC,CAIzB;AAED;;;;;GAKG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;AAErF;;;;;;GAMG;AACH,wBAAsB,2BAA2B,CAC/C,MAAM,EAAE,cAAc,EACtB,MAAM,EAAE,eAAe,GACtB,OAAO,CAAC,YAAY,EAAE,CAAC,CAezB"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { readdir } from 'node:fs/promises';
|
|
2
|
+
import { extname, join } from 'node:path';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
4
|
+
import { workflowMeta } from './workflow-ref.js';
|
|
5
|
+
/**
|
|
6
|
+
* Register a single `@Workflow`-decorated class on the engine: instantiate it once and bind its
|
|
7
|
+
* `run(ctx, input)` as the workflow body via `engine.register`. The low-level
|
|
8
|
+
* `engine.register(name, version, fn)` stays the escape hatch — this is the convenience the
|
|
9
|
+
* `app/workflows` convention builds on. No-op (returns `false`) for an undecorated class.
|
|
10
|
+
*/
|
|
11
|
+
export function registerWorkflowClass(engine, cls) {
|
|
12
|
+
const meta = workflowMeta(cls);
|
|
13
|
+
if (!meta)
|
|
14
|
+
return false;
|
|
15
|
+
const Ctor = cls;
|
|
16
|
+
const instance = new Ctor();
|
|
17
|
+
engine.register(meta.name, meta.version, (ctx, input) => Promise.resolve(instance.run(ctx, input)), {
|
|
18
|
+
...(meta.tags ? { tags: meta.tags } : {}),
|
|
19
|
+
...(meta.executionTimeout !== undefined ? { executionTimeout: meta.executionTimeout } : {}),
|
|
20
|
+
...(meta.onEvent ? { onEvent: meta.onEvent } : {}),
|
|
21
|
+
});
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Pick the module extension for the running environment so a built app (`.js`) and a dev/ts app
|
|
26
|
+
* (`.ts`, run under a loader) never double-register the same workflow. We import only files whose
|
|
27
|
+
* extension matches `import.meta.url`'s own (`.ts` when running from source, `.js` from `dist`),
|
|
28
|
+
* which keeps scanner and `make:workflow` (it scaffolds `.ts`) in agreement under both setups.
|
|
29
|
+
*/
|
|
30
|
+
const MODULE_EXT = extname(import.meta.url || '') === '.ts' ? '.ts' : '.js';
|
|
31
|
+
/**
|
|
32
|
+
* Scan a directory RECURSIVELY for modules and collect every exported `@Workflow`-decorated class
|
|
33
|
+
* (the default export and any named export are considered) — so nested conventions like
|
|
34
|
+
* `app/workflows/billing/charge_workflow.ts` are found, matching `make:workflow`'s nested-path
|
|
35
|
+
* scaffolding. Only the environment-appropriate extension is imported (see {@link MODULE_EXT}), and
|
|
36
|
+
* each module path is visited once, so a built `.js` and a dev `.ts` of the same module never both
|
|
37
|
+
* register. Missing directory → empty list (the convention is opt-in: no `app/workflows`, nothing to
|
|
38
|
+
* register).
|
|
39
|
+
*/
|
|
40
|
+
export async function discoverWorkflows(dir) {
|
|
41
|
+
let entries;
|
|
42
|
+
try {
|
|
43
|
+
entries = await readdir(dir, { recursive: true });
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
if (err.code === 'ENOENT')
|
|
47
|
+
return [];
|
|
48
|
+
throw err;
|
|
49
|
+
}
|
|
50
|
+
const found = [];
|
|
51
|
+
const seen = new Set();
|
|
52
|
+
for (const entry of entries.sort()) {
|
|
53
|
+
if (extname(entry) !== MODULE_EXT || entry.endsWith(`.d${MODULE_EXT}`))
|
|
54
|
+
continue;
|
|
55
|
+
const mod = (await import(pathToFileURL(join(dir, entry)).href));
|
|
56
|
+
for (const exported of Object.values(mod)) {
|
|
57
|
+
if (seen.has(exported))
|
|
58
|
+
continue;
|
|
59
|
+
const meta = workflowMeta(exported);
|
|
60
|
+
if (!meta)
|
|
61
|
+
continue;
|
|
62
|
+
seen.add(exported);
|
|
63
|
+
found.push({ meta, cls: exported });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return found;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Discover every `@Workflow` class under `dir` and register each on the engine. Returns the
|
|
70
|
+
* registered metadata so the caller can log what was wired. Best-effort over a missing directory.
|
|
71
|
+
*/
|
|
72
|
+
export async function registerWorkflowsFromDir(engine, dir) {
|
|
73
|
+
const discovered = await discoverWorkflows(dir);
|
|
74
|
+
for (const { cls } of discovered)
|
|
75
|
+
registerWorkflowClass(engine, cls);
|
|
76
|
+
return discovered.map((d) => d.meta);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Register every `@Workflow` class reachable from a generated {@link WorkflowsBarrel}, by awaiting
|
|
80
|
+
* each lazy module import and registering each decorated export — the build-time equivalent of
|
|
81
|
+
* {@link registerWorkflowsFromDir}, with no runtime `readdir`. Each module is imported once and each
|
|
82
|
+
* decorated class registered once (deduped), so a class re-exported from several modules is safe.
|
|
83
|
+
* Returns the registered metadata so the caller can log what was wired.
|
|
84
|
+
*/
|
|
85
|
+
export async function registerWorkflowsFromBarrel(engine, barrel) {
|
|
86
|
+
const registered = [];
|
|
87
|
+
const seen = new Set();
|
|
88
|
+
for (const load of Object.values(barrel)) {
|
|
89
|
+
const mod = await load();
|
|
90
|
+
for (const exported of Object.values(mod)) {
|
|
91
|
+
if (seen.has(exported))
|
|
92
|
+
continue;
|
|
93
|
+
const meta = workflowMeta(exported);
|
|
94
|
+
if (!meta)
|
|
95
|
+
continue;
|
|
96
|
+
seen.add(exported);
|
|
97
|
+
registerWorkflowClass(engine, exported);
|
|
98
|
+
registered.push(meta);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return registered;
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=workflow-discovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflow-discovery.js","sourceRoot":"","sources":["../../src/workflow-discovery.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,11 +6,47 @@
|
|
|
6
6
|
* string stays available for the cross-runtime case.
|
|
7
7
|
*/
|
|
8
8
|
/**
|
|
9
|
-
* The symbol the `@Workflow` decorator stamps
|
|
10
|
-
*
|
|
9
|
+
* The symbol the `@Workflow` decorator stamps the full options onto (name + version + tags …), so
|
|
10
|
+
* auto-discovery can register the class against the engine and a class ref can be resolved back to
|
|
11
|
+
* its name via {@link workflowName}. A global-registry symbol (`Symbol.for`) so it survives duplicate
|
|
11
12
|
* copies of this package in a dependency tree.
|
|
12
13
|
*/
|
|
13
|
-
export declare const
|
|
14
|
+
export declare const WORKFLOW_META_KEY: unique symbol;
|
|
15
|
+
/** Options passed to `@Workflow({ name, version, … })`. */
|
|
16
|
+
export interface WorkflowOptions {
|
|
17
|
+
/** The registered workflow name (the cross-runtime identity, e.g. `order`). */
|
|
18
|
+
name: string;
|
|
19
|
+
/** Workflow version. Defaults to `'1'`. Register a new version for a breaking change. */
|
|
20
|
+
version?: string;
|
|
21
|
+
/** Searchable labels merged onto every run of this workflow. */
|
|
22
|
+
tags?: string[];
|
|
23
|
+
/** Wall-clock budget for the whole run (a duration string like `'5m'`, or ms). */
|
|
24
|
+
executionTimeout?: string | number;
|
|
25
|
+
/** Event names that start a run of this workflow when published (see `onEvent`). */
|
|
26
|
+
onEvent?: string[];
|
|
27
|
+
}
|
|
28
|
+
/** The metadata the `@Workflow` decorator stamps onto a class for discovery + registration. */
|
|
29
|
+
export interface WorkflowMeta extends WorkflowOptions {
|
|
30
|
+
version: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Class decorator marking a class as a durable workflow. Stamps the full options (name + version +
|
|
34
|
+
* tags …) so a class ref resolves via {@link workflowName} and the provider's `app/workflows`
|
|
35
|
+
* auto-discovery can register it on the engine — no manual `engine.register(...)`. The class must
|
|
36
|
+
* expose `run(ctx, input)`; that method becomes the workflow body.
|
|
37
|
+
*
|
|
38
|
+
* ```ts
|
|
39
|
+
* @Workflow({ name: 'order', version: '1' })
|
|
40
|
+
* export default class OrderWorkflow {
|
|
41
|
+
* async run(ctx: WorkflowCtx, input: { id: string }) { ... }
|
|
42
|
+
* }
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export declare function Workflow(options: WorkflowOptions): <T extends abstract new (...args: never[]) => {
|
|
46
|
+
run(ctx: never, input: never): unknown;
|
|
47
|
+
}>(target: T) => T;
|
|
48
|
+
/** Read the {@link WorkflowMeta} a `@Workflow` decorator stamped on a class, or `undefined`. */
|
|
49
|
+
export declare function workflowMeta(target: unknown): WorkflowMeta | undefined;
|
|
14
50
|
/** Structural shape of a `@Workflow` class — its `run(ctx, input)` carries the input/output types. */
|
|
15
51
|
export type WorkflowClass<TInput = unknown, TOutput = unknown> = abstract new (...args: never[]) => {
|
|
16
52
|
run(ctx: never, input: TInput): Promise<TOutput> | TOutput;
|
|
@@ -27,8 +63,8 @@ export type WorkflowOutputOf<C> = C extends abstract new (...args: never[]) => {
|
|
|
27
63
|
} ? Awaited<R> : unknown;
|
|
28
64
|
/**
|
|
29
65
|
* Resolve a {@link WorkflowRef} to its registered workflow name: a string is returned as-is; a
|
|
30
|
-
* `@Workflow` class is resolved via the name the decorator stamped
|
|
31
|
-
* never decorated (so it carries no registered name).
|
|
66
|
+
* `@Workflow` class is resolved via the name the decorator stamped in its metadata. Throws if a
|
|
67
|
+
* class was never decorated (so it carries no registered name).
|
|
32
68
|
*/
|
|
33
69
|
export declare function workflowName(ref: WorkflowRef): string;
|
|
34
70
|
//# sourceMappingURL=workflow-ref.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workflow-ref.d.ts","sourceRoot":"","sources":["../../src/workflow-ref.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH
|
|
1
|
+
{"version":3,"file":"workflow-ref.d.ts","sourceRoot":"","sources":["../../src/workflow-ref.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,EAAE,OAAO,MAAmD,CAAC;AAE3F,2DAA2D;AAC3D,MAAM,WAAW,eAAe;IAC9B,+EAA+E;IAC/E,IAAI,EAAE,MAAM,CAAC;IACb,yFAAyF;IACzF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gEAAgE;IAChE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,kFAAkF;IAClF,gBAAgB,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACnC,oFAAoF;IACpF,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,+FAA+F;AAC/F,MAAM,WAAW,YAAa,SAAQ,eAAe;IACnD,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,eAAe,IACvC,CAAC,SAAS,QAAQ,MAAM,GAAG,IAAI,EAAE,KAAK,EAAE,KAAK;IAAE,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO,CAAA;CAAE,EAC7F,QAAQ,CAAC,KACR,CAAC,CASL;AAED,gGAAgG;AAChG,wBAAgB,YAAY,CAAC,MAAM,EAAE,OAAO,GAAG,YAAY,GAAG,SAAS,CAGtE;AAED,sGAAsG;AACtG,MAAM,MAAM,aAAa,CAAC,MAAM,GAAG,OAAO,EAAE,OAAO,GAAG,OAAO,IAAI,QAAQ,MACvE,GAAG,IAAI,EAAE,KAAK,EAAE,KACb;IACH,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;CAC5D,CAAC;AAEF,yGAAyG;AACzG,MAAM,MAAM,WAAW,CAAC,MAAM,GAAG,OAAO,EAAE,OAAO,GAAG,OAAO,IACvD,MAAM,GACN,aAAa,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEnC,uDAAuD;AACvD,MAAM,MAAM,eAAe,CAAC,CAAC,IAAI,CAAC,SAAS,QAAQ,MACjD,GAAG,IAAI,EAAE,KAAK,EAAE,KACb;IACH,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC;CAC1C,GACG,CAAC,GACD,OAAO,CAAC;AAEZ,gFAAgF;AAChF,MAAM,MAAM,gBAAgB,CAAC,CAAC,IAAI,CAAC,SAAS,QAAQ,MAClD,GAAG,IAAI,EAAE,KAAK,EAAE,KACb;IACH,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,GAAG,MAAM,CAAC,CAAC;CACxC,GACG,OAAO,CAAC,CAAC,CAAC,GACV,OAAO,CAAC;AAEZ;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,WAAW,GAAG,MAAM,CASrD"}
|