@nwire/forge 0.7.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/LICENSE +21 -0
- package/README.md +77 -0
- package/dist/__tests__/actor-methods.test.d.ts +9 -0
- package/dist/__tests__/actor-methods.test.d.ts.map +1 -0
- package/dist/__tests__/actor-methods.test.js +210 -0
- package/dist/__tests__/actor-methods.test.js.map +1 -0
- package/dist/__tests__/actor-schema-bound.test.d.ts +6 -0
- package/dist/__tests__/actor-schema-bound.test.d.ts.map +1 -0
- package/dist/__tests__/actor-schema-bound.test.js +112 -0
- package/dist/__tests__/actor-schema-bound.test.js.map +1 -0
- package/dist/__tests__/app-capabilities.test.d.ts +19 -0
- package/dist/__tests__/app-capabilities.test.d.ts.map +1 -0
- package/dist/__tests__/app-capabilities.test.js +57 -0
- package/dist/__tests__/app-capabilities.test.js.map +1 -0
- package/dist/__tests__/cli-runner.test.d.ts +6 -0
- package/dist/__tests__/cli-runner.test.d.ts.map +1 -0
- package/dist/__tests__/cli-runner.test.js +158 -0
- package/dist/__tests__/cli-runner.test.js.map +1 -0
- package/dist/__tests__/create-app.test.d.ts +18 -0
- package/dist/__tests__/create-app.test.d.ts.map +1 -0
- package/dist/__tests__/create-app.test.js +189 -0
- package/dist/__tests__/create-app.test.js.map +1 -0
- package/dist/__tests__/cross-service-bus.test.d.ts +8 -0
- package/dist/__tests__/cross-service-bus.test.d.ts.map +1 -0
- package/dist/__tests__/cross-service-bus.test.js +139 -0
- package/dist/__tests__/cross-service-bus.test.js.map +1 -0
- package/dist/__tests__/define-schema.test.d.ts +5 -0
- package/dist/__tests__/define-schema.test.d.ts.map +1 -0
- package/dist/__tests__/define-schema.test.js +83 -0
- package/dist/__tests__/define-schema.test.js.map +1 -0
- package/dist/__tests__/dev-logger.test.d.ts +9 -0
- package/dist/__tests__/dev-logger.test.d.ts.map +1 -0
- package/dist/__tests__/dev-logger.test.js +126 -0
- package/dist/__tests__/dev-logger.test.js.map +1 -0
- package/dist/__tests__/external-call.test.d.ts +14 -0
- package/dist/__tests__/external-call.test.d.ts.map +1 -0
- package/dist/__tests__/external-call.test.js +99 -0
- package/dist/__tests__/external-call.test.js.map +1 -0
- package/dist/__tests__/framework-events.test.d.ts +13 -0
- package/dist/__tests__/framework-events.test.d.ts.map +1 -0
- package/dist/__tests__/framework-events.test.js +204 -0
- package/dist/__tests__/framework-events.test.js.map +1 -0
- package/dist/__tests__/inline-handler.test.d.ts +8 -0
- package/dist/__tests__/inline-handler.test.d.ts.map +1 -0
- package/dist/__tests__/inline-handler.test.js +101 -0
- package/dist/__tests__/inline-handler.test.js.map +1 -0
- package/dist/__tests__/lifecycle-logging.test.d.ts +12 -0
- package/dist/__tests__/lifecycle-logging.test.d.ts.map +1 -0
- package/dist/__tests__/lifecycle-logging.test.js +112 -0
- package/dist/__tests__/lifecycle-logging.test.js.map +1 -0
- package/dist/__tests__/middleware.test.d.ts +7 -0
- package/dist/__tests__/middleware.test.d.ts.map +1 -0
- package/dist/__tests__/middleware.test.js +109 -0
- package/dist/__tests__/middleware.test.js.map +1 -0
- package/dist/__tests__/module-needs.test.d.ts +10 -0
- package/dist/__tests__/module-needs.test.d.ts.map +1 -0
- package/dist/__tests__/module-needs.test.js +77 -0
- package/dist/__tests__/module-needs.test.js.map +1 -0
- package/dist/__tests__/module-topo-sort.test.d.ts +15 -0
- package/dist/__tests__/module-topo-sort.test.d.ts.map +1 -0
- package/dist/__tests__/module-topo-sort.test.js +105 -0
- package/dist/__tests__/module-topo-sort.test.js.map +1 -0
- package/dist/__tests__/multi-tenancy.test.d.ts +10 -0
- package/dist/__tests__/multi-tenancy.test.d.ts.map +1 -0
- package/dist/__tests__/multi-tenancy.test.js +122 -0
- package/dist/__tests__/multi-tenancy.test.js.map +1 -0
- package/dist/__tests__/needs-topology.test.d.ts +11 -0
- package/dist/__tests__/needs-topology.test.d.ts.map +1 -0
- package/dist/__tests__/needs-topology.test.js +82 -0
- package/dist/__tests__/needs-topology.test.js.map +1 -0
- package/dist/__tests__/plugin-closure.test.d.ts +15 -0
- package/dist/__tests__/plugin-closure.test.d.ts.map +1 -0
- package/dist/__tests__/plugin-closure.test.js +140 -0
- package/dist/__tests__/plugin-closure.test.js.map +1 -0
- package/dist/__tests__/plugin.test.d.ts +10 -0
- package/dist/__tests__/plugin.test.d.ts.map +1 -0
- package/dist/__tests__/plugin.test.js +225 -0
- package/dist/__tests__/plugin.test.js.map +1 -0
- package/dist/__tests__/primitives.test.d.ts +9 -0
- package/dist/__tests__/primitives.test.d.ts.map +1 -0
- package/dist/__tests__/primitives.test.js +434 -0
- package/dist/__tests__/primitives.test.js.map +1 -0
- package/dist/__tests__/production-readiness.test.d.ts +22 -0
- package/dist/__tests__/production-readiness.test.d.ts.map +1 -0
- package/dist/__tests__/production-readiness.test.js +196 -0
- package/dist/__tests__/production-readiness.test.js.map +1 -0
- package/dist/__tests__/provider.test.d.ts +6 -0
- package/dist/__tests__/provider.test.d.ts.map +1 -0
- package/dist/__tests__/provider.test.js +122 -0
- package/dist/__tests__/provider.test.js.map +1 -0
- package/dist/__tests__/public-marker.test.d.ts +7 -0
- package/dist/__tests__/public-marker.test.d.ts.map +1 -0
- package/dist/__tests__/public-marker.test.js +54 -0
- package/dist/__tests__/public-marker.test.js.map +1 -0
- package/dist/__tests__/retry-dlq.test.d.ts +6 -0
- package/dist/__tests__/retry-dlq.test.d.ts.map +1 -0
- package/dist/__tests__/retry-dlq.test.js +68 -0
- package/dist/__tests__/retry-dlq.test.js.map +1 -0
- package/dist/__tests__/validate.test.d.ts +5 -0
- package/dist/__tests__/validate.test.d.ts.map +1 -0
- package/dist/__tests__/validate.test.js +53 -0
- package/dist/__tests__/validate.test.js.map +1 -0
- package/dist/__tests__/workflow-saga.test.d.ts +7 -0
- package/dist/__tests__/workflow-saga.test.d.ts.map +1 -0
- package/dist/__tests__/workflow-saga.test.js +239 -0
- package/dist/__tests__/workflow-saga.test.js.map +1 -0
- package/dist/actor-store.d.ts +83 -0
- package/dist/actor-store.d.ts.map +1 -0
- package/dist/actor-store.js +85 -0
- package/dist/actor-store.js.map +1 -0
- package/dist/cli-runner.d.ts +46 -0
- package/dist/cli-runner.d.ts.map +1 -0
- package/dist/cli-runner.js +164 -0
- package/dist/cli-runner.js.map +1 -0
- package/dist/create-app.d.ts +131 -0
- package/dist/create-app.d.ts.map +1 -0
- package/dist/create-app.js +593 -0
- package/dist/create-app.js.map +1 -0
- package/dist/define-action.d.ts +148 -0
- package/dist/define-action.d.ts.map +1 -0
- package/dist/define-action.js +52 -0
- package/dist/define-action.js.map +1 -0
- package/dist/define-actor.d.ts +302 -0
- package/dist/define-actor.d.ts.map +1 -0
- package/dist/define-actor.js +294 -0
- package/dist/define-actor.js.map +1 -0
- package/dist/define-app.d.ts +104 -0
- package/dist/define-app.d.ts.map +1 -0
- package/dist/define-app.js +49 -0
- package/dist/define-app.js.map +1 -0
- package/dist/define-cron.d.ts +50 -0
- package/dist/define-cron.d.ts.map +1 -0
- package/dist/define-cron.js +34 -0
- package/dist/define-cron.js.map +1 -0
- package/dist/define-error.d.ts +10 -0
- package/dist/define-error.d.ts.map +1 -0
- package/dist/define-error.js +10 -0
- package/dist/define-error.js.map +1 -0
- package/dist/define-external-call.d.ts +85 -0
- package/dist/define-external-call.d.ts.map +1 -0
- package/dist/define-external-call.js +38 -0
- package/dist/define-external-call.js.map +1 -0
- package/dist/define-handler.d.ts +98 -0
- package/dist/define-handler.d.ts.map +1 -0
- package/dist/define-handler.js +29 -0
- package/dist/define-handler.js.map +1 -0
- package/dist/define-inbound-webhook.d.ts +82 -0
- package/dist/define-inbound-webhook.d.ts.map +1 -0
- package/dist/define-inbound-webhook.js +42 -0
- package/dist/define-inbound-webhook.js.map +1 -0
- package/dist/define-inbox.d.ts +40 -0
- package/dist/define-inbox.d.ts.map +1 -0
- package/dist/define-inbox.js +31 -0
- package/dist/define-inbox.js.map +1 -0
- package/dist/define-initializer.d.ts +54 -0
- package/dist/define-initializer.d.ts.map +1 -0
- package/dist/define-initializer.js +38 -0
- package/dist/define-initializer.js.map +1 -0
- package/dist/define-middleware.d.ts +8 -0
- package/dist/define-middleware.d.ts.map +1 -0
- package/dist/define-middleware.js +8 -0
- package/dist/define-middleware.js.map +1 -0
- package/dist/define-model.d.ts +10 -0
- package/dist/define-model.d.ts.map +1 -0
- package/dist/define-model.js +13 -0
- package/dist/define-model.js.map +1 -0
- package/dist/define-module.d.ts +157 -0
- package/dist/define-module.d.ts.map +1 -0
- package/dist/define-module.js +60 -0
- package/dist/define-module.js.map +1 -0
- package/dist/define-outbox.d.ts +47 -0
- package/dist/define-outbox.d.ts.map +1 -0
- package/dist/define-outbox.js +36 -0
- package/dist/define-outbox.js.map +1 -0
- package/dist/define-plugin.d.ts +171 -0
- package/dist/define-plugin.d.ts.map +1 -0
- package/dist/define-plugin.js +134 -0
- package/dist/define-plugin.js.map +1 -0
- package/dist/define-projection.d.ts +56 -0
- package/dist/define-projection.d.ts.map +1 -0
- package/dist/define-projection.js +44 -0
- package/dist/define-projection.js.map +1 -0
- package/dist/define-provider.d.ts +49 -0
- package/dist/define-provider.d.ts.map +1 -0
- package/dist/define-provider.js +45 -0
- package/dist/define-provider.js.map +1 -0
- package/dist/define-query.d.ts +50 -0
- package/dist/define-query.d.ts.map +1 -0
- package/dist/define-query.js +33 -0
- package/dist/define-query.js.map +1 -0
- package/dist/define-resolver.d.ts +111 -0
- package/dist/define-resolver.d.ts.map +1 -0
- package/dist/define-resolver.js +146 -0
- package/dist/define-resolver.js.map +1 -0
- package/dist/define-schema.d.ts +88 -0
- package/dist/define-schema.d.ts.map +1 -0
- package/dist/define-schema.js +72 -0
- package/dist/define-schema.js.map +1 -0
- package/dist/define-workflow.d.ts +193 -0
- package/dist/define-workflow.d.ts.map +1 -0
- package/dist/define-workflow.js +345 -0
- package/dist/define-workflow.js.map +1 -0
- package/dist/dev-logger.d.ts +41 -0
- package/dist/dev-logger.d.ts.map +1 -0
- package/dist/dev-logger.js +135 -0
- package/dist/dev-logger.js.map +1 -0
- package/dist/event-message.d.ts +37 -0
- package/dist/event-message.d.ts.map +1 -0
- package/dist/event-message.js +51 -0
- package/dist/event-message.js.map +1 -0
- package/dist/foundation.d.ts +14 -0
- package/dist/foundation.d.ts.map +1 -0
- package/dist/foundation.js +14 -0
- package/dist/foundation.js.map +1 -0
- package/dist/framework-event-bus.d.ts +13 -0
- package/dist/framework-event-bus.d.ts.map +1 -0
- package/dist/framework-event-bus.js +13 -0
- package/dist/framework-event-bus.js.map +1 -0
- package/dist/framework-events.d.ts +121 -0
- package/dist/framework-events.d.ts.map +1 -0
- package/dist/framework-events.js +67 -0
- package/dist/framework-events.js.map +1 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +61 -0
- package/dist/index.js.map +1 -0
- package/dist/module-surface.d.ts +47 -0
- package/dist/module-surface.d.ts.map +1 -0
- package/dist/module-surface.js +65 -0
- package/dist/module-surface.js.map +1 -0
- package/dist/projection-store.d.ts +26 -0
- package/dist/projection-store.d.ts.map +1 -0
- package/dist/projection-store.js +28 -0
- package/dist/projection-store.js.map +1 -0
- package/dist/public-marker.d.ts +35 -0
- package/dist/public-marker.d.ts.map +1 -0
- package/dist/public-marker.js +45 -0
- package/dist/public-marker.js.map +1 -0
- package/dist/response.d.ts +8 -0
- package/dist/response.d.ts.map +1 -0
- package/dist/response.js +8 -0
- package/dist/response.js.map +1 -0
- package/dist/runtime.d.ts +497 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +1083 -0
- package/dist/runtime.js.map +1 -0
- package/dist/validate.d.ts +33 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +48 -0
- package/dist/validate.js.map +1 -0
- package/dist/when.d.ts +101 -0
- package/dist/when.d.ts.map +1 -0
- package/dist/when.js +57 -0
- package/dist/when.js.map +1 -0
- package/dist/workflow-timer-store.d.ts +78 -0
- package/dist/workflow-timer-store.d.ts.map +1 -0
- package/dist/workflow-timer-store.js +56 -0
- package/dist/workflow-timer-store.js.map +1 -0
- package/package.json +60 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stateful (saga) workflows — verify the closure-based state-machine
|
|
3
|
+
* runtime fires entry effects, transitions on handler return, persists
|
|
4
|
+
* data across events, and emits `complete` on final states.
|
|
5
|
+
*/
|
|
6
|
+
import { describe, it, expect } from "vitest";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import { defineEvent } from "@nwire/messages";
|
|
9
|
+
import { defineWorkflow } from "../define-workflow.js";
|
|
10
|
+
const PaymentRequested = defineEvent({
|
|
11
|
+
name: "billing.payment-requested",
|
|
12
|
+
schema: z.object({ subscriptionId: z.string(), amount: z.number() }),
|
|
13
|
+
});
|
|
14
|
+
const PaymentConfirmed = defineEvent({
|
|
15
|
+
name: "billing.payment-confirmed",
|
|
16
|
+
schema: z.object({ subscriptionId: z.string() }),
|
|
17
|
+
});
|
|
18
|
+
const PaymentFailed = defineEvent({
|
|
19
|
+
name: "billing.payment-failed",
|
|
20
|
+
schema: z.object({ subscriptionId: z.string() }),
|
|
21
|
+
});
|
|
22
|
+
/**
|
|
23
|
+
* Tiny in-test FireContext factory. Recorders capture every effect so
|
|
24
|
+
* tests can assert exact side-effect sequences.
|
|
25
|
+
*/
|
|
26
|
+
function makeFireCtx() {
|
|
27
|
+
const store = new Map();
|
|
28
|
+
const sent = [];
|
|
29
|
+
const published = [];
|
|
30
|
+
const scheduled = [];
|
|
31
|
+
return {
|
|
32
|
+
send: async (action, input) => {
|
|
33
|
+
sent.push({ action: action.name, input });
|
|
34
|
+
return undefined;
|
|
35
|
+
},
|
|
36
|
+
enqueue: async (action, input) => {
|
|
37
|
+
sent.push({ action: action.name, input });
|
|
38
|
+
},
|
|
39
|
+
publish: async (event) => {
|
|
40
|
+
published.push({ eventName: event.eventName });
|
|
41
|
+
},
|
|
42
|
+
load: (key) => store.get(key),
|
|
43
|
+
save: (key, instance) => store.set(key, { ...instance }),
|
|
44
|
+
drop: (key) => store.delete(key),
|
|
45
|
+
scheduleTimer: async (name, delay) => {
|
|
46
|
+
scheduled.push({ name, delay });
|
|
47
|
+
},
|
|
48
|
+
store,
|
|
49
|
+
sent,
|
|
50
|
+
published,
|
|
51
|
+
scheduled,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
describe("defineWorkflow — stateful", () => {
|
|
55
|
+
it("transitions on handler return + persists data with assign()", async () => {
|
|
56
|
+
const ctx = makeFireCtx();
|
|
57
|
+
const renewal = defineWorkflow("subscription.renewal", ({ on, assign, states }) => {
|
|
58
|
+
const { notified, paid } = states;
|
|
59
|
+
on(PaymentRequested, async (e) => {
|
|
60
|
+
await assign({
|
|
61
|
+
subscriptionId: e.subscriptionId,
|
|
62
|
+
amount: e.amount,
|
|
63
|
+
attempts: 0,
|
|
64
|
+
});
|
|
65
|
+
return notified.$name;
|
|
66
|
+
});
|
|
67
|
+
on(PaymentConfirmed, async () => {
|
|
68
|
+
return paid.$name;
|
|
69
|
+
});
|
|
70
|
+
// Reference states so they get registered in the spec.
|
|
71
|
+
void notified;
|
|
72
|
+
void paid;
|
|
73
|
+
}, {
|
|
74
|
+
data: z.object({
|
|
75
|
+
subscriptionId: z.string(),
|
|
76
|
+
amount: z.number(),
|
|
77
|
+
attempts: z.number().default(0),
|
|
78
|
+
}),
|
|
79
|
+
states: {
|
|
80
|
+
notified: {},
|
|
81
|
+
paid: { final: true },
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
expect(renewal.stateful).toBe(true);
|
|
85
|
+
expect(renewal.initialState).toBe("notified");
|
|
86
|
+
expect(renewal.finalStates.has("paid")).toBe(true);
|
|
87
|
+
await renewal._fire(PaymentRequested({ subscriptionId: "sub_1", amount: 99 }), ctx);
|
|
88
|
+
// The default-correlation key the runtime uses without explicit correlate.
|
|
89
|
+
const stored = [...ctx.store.values()][0];
|
|
90
|
+
expect(stored?.data).toMatchObject({ subscriptionId: "sub_1", amount: 99, attempts: 0 });
|
|
91
|
+
expect(stored?.state).toBe("notified");
|
|
92
|
+
});
|
|
93
|
+
it("transitioning to a final state fires complete and drops the instance", async () => {
|
|
94
|
+
const ctx = makeFireCtx();
|
|
95
|
+
const completed = [];
|
|
96
|
+
const flow = defineWorkflow("test.complete", ({ on, states, complete }) => {
|
|
97
|
+
const { open, closed } = states;
|
|
98
|
+
on(PaymentRequested, async () => closed.$name);
|
|
99
|
+
on(complete, async () => {
|
|
100
|
+
completed.push(1);
|
|
101
|
+
});
|
|
102
|
+
void open;
|
|
103
|
+
}, {
|
|
104
|
+
data: z.object({ x: z.number().default(0) }),
|
|
105
|
+
states: {
|
|
106
|
+
open: {},
|
|
107
|
+
closed: { final: true },
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
await flow._fire(PaymentRequested({ subscriptionId: "x", amount: 1 }), ctx);
|
|
111
|
+
expect(completed).toEqual([1]);
|
|
112
|
+
expect(ctx.store.size).toBe(0); // dropped after final
|
|
113
|
+
});
|
|
114
|
+
it("nested `state(() => on(...))` captures state-scoped subscriptions on entry", async () => {
|
|
115
|
+
const ctx = makeFireCtx();
|
|
116
|
+
const ran = [];
|
|
117
|
+
const flow = defineWorkflow("test.nested-state-body", ({ on, states }) => {
|
|
118
|
+
const { open, working, done } = states;
|
|
119
|
+
// Top-level: kicks off into `open`.
|
|
120
|
+
on(PaymentRequested, async () => open.$name);
|
|
121
|
+
// State body: registers a state-scoped handler for PaymentFailed
|
|
122
|
+
// that only fires while in `open`.
|
|
123
|
+
open(() => {
|
|
124
|
+
on(PaymentFailed, async () => {
|
|
125
|
+
ran.push("open.failed");
|
|
126
|
+
return working.$name;
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
working(() => {
|
|
130
|
+
on(PaymentFailed, async () => {
|
|
131
|
+
ran.push("working.failed");
|
|
132
|
+
return done.$name;
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
void done;
|
|
136
|
+
}, {
|
|
137
|
+
data: z.object({ k: z.string().default("") }),
|
|
138
|
+
states: { open: {}, working: {}, done: { final: true } },
|
|
139
|
+
});
|
|
140
|
+
// open: PaymentFailed → working (open's handler fires)
|
|
141
|
+
await flow._fire(PaymentRequested({ subscriptionId: "x", amount: 1 }), ctx);
|
|
142
|
+
await flow._fire(PaymentFailed({ subscriptionId: "x" }), ctx);
|
|
143
|
+
// working: PaymentFailed → done (working's handler fires)
|
|
144
|
+
await flow._fire(PaymentFailed({ subscriptionId: "x" }), ctx);
|
|
145
|
+
expect(ran).toEqual(["open.failed", "working.failed"]);
|
|
146
|
+
// done is final → instance dropped.
|
|
147
|
+
expect(ctx.store.size).toBe(0);
|
|
148
|
+
});
|
|
149
|
+
it("respects `opts.in` state-filter — handlers fire only in declared states", async () => {
|
|
150
|
+
const ctx = makeFireCtx();
|
|
151
|
+
const ran = [];
|
|
152
|
+
const flow = defineWorkflow("test.state-filter", ({ on, states }) => {
|
|
153
|
+
const { a, b } = states;
|
|
154
|
+
on(PaymentRequested, async () => b.$name); // any state → b
|
|
155
|
+
on(PaymentFailed, async () => {
|
|
156
|
+
ran.push("a-only");
|
|
157
|
+
}, { in: ["a"] });
|
|
158
|
+
on(PaymentFailed, async () => {
|
|
159
|
+
ran.push("b-only");
|
|
160
|
+
}, { in: ["b"] });
|
|
161
|
+
void a;
|
|
162
|
+
void b;
|
|
163
|
+
}, {
|
|
164
|
+
data: z.object({ k: z.string().default("") }),
|
|
165
|
+
states: { a: {}, b: {} },
|
|
166
|
+
});
|
|
167
|
+
// First event transitions from initial state "a" → "b".
|
|
168
|
+
await flow._fire(PaymentRequested({ subscriptionId: "x", amount: 1 }), ctx);
|
|
169
|
+
// Now in state "b": only b-only should fire.
|
|
170
|
+
await flow._fire(PaymentFailed({ subscriptionId: "x" }), ctx);
|
|
171
|
+
expect(ran).toEqual(["b-only"]);
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
describe("defineWorkflow — timers", () => {
|
|
175
|
+
it("schedule(timer) enqueues via FireContext.scheduleTimer with the right name+delay", async () => {
|
|
176
|
+
const ctx = makeFireCtx();
|
|
177
|
+
const flow = defineWorkflow("billing.renewal", ({ on, states, timeout, schedule }) => {
|
|
178
|
+
const { armed } = states;
|
|
179
|
+
const PaymentOverdue = timeout("payment-overdue", "7d");
|
|
180
|
+
on(PaymentRequested, async () => {
|
|
181
|
+
await schedule(PaymentOverdue);
|
|
182
|
+
return armed.$name;
|
|
183
|
+
});
|
|
184
|
+
// Suppress unused-state warning.
|
|
185
|
+
void PaymentOverdue;
|
|
186
|
+
}, {
|
|
187
|
+
data: z.object({ k: z.string().default("") }),
|
|
188
|
+
states: { armed: {} },
|
|
189
|
+
});
|
|
190
|
+
await flow._fire(PaymentRequested({ subscriptionId: "x", amount: 1 }), ctx);
|
|
191
|
+
expect(ctx.scheduled).toEqual([{ name: "payment-overdue", delay: "7d" }]);
|
|
192
|
+
});
|
|
193
|
+
it("timer fire routes via synthesized event name; on(timer, …) catches it", async () => {
|
|
194
|
+
const ctx = makeFireCtx();
|
|
195
|
+
const seen = [];
|
|
196
|
+
const flow = defineWorkflow("billing.renewal-2", ({ on, states, timeout }) => {
|
|
197
|
+
const { armed, expired } = states;
|
|
198
|
+
const Overdue = timeout("payment-overdue", "7d");
|
|
199
|
+
on(PaymentRequested, async () => armed.$name);
|
|
200
|
+
on(Overdue, async () => {
|
|
201
|
+
seen.push("overdue");
|
|
202
|
+
return expired.$name;
|
|
203
|
+
});
|
|
204
|
+
void armed;
|
|
205
|
+
void expired;
|
|
206
|
+
}, {
|
|
207
|
+
data: z.object({ k: z.string().default("") }),
|
|
208
|
+
states: { armed: {}, expired: { final: true } },
|
|
209
|
+
});
|
|
210
|
+
// Bootstrap the saga into `armed`.
|
|
211
|
+
await flow._fire(PaymentRequested({ subscriptionId: "x", amount: 1 }), ctx);
|
|
212
|
+
// Now simulate the timer fire: feed the synthesized event back in.
|
|
213
|
+
const timerEv = {
|
|
214
|
+
eventName: "__nwire/timer/billing.renewal-2/payment-overdue",
|
|
215
|
+
payload: undefined,
|
|
216
|
+
};
|
|
217
|
+
await flow._fire(timerEv, ctx);
|
|
218
|
+
expect(seen).toEqual(["overdue"]);
|
|
219
|
+
expect(ctx.store.size).toBe(0); // expired → final → dropped
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
describe("defineWorkflow — stateless still works", () => {
|
|
223
|
+
it("plain dispatch, no instance state", async () => {
|
|
224
|
+
const ctx = makeFireCtx();
|
|
225
|
+
const seen = [];
|
|
226
|
+
const flow = defineWorkflow("translate", ({ on, publish }) => {
|
|
227
|
+
on(PaymentRequested, async (e) => {
|
|
228
|
+
seen.push(e.subscriptionId);
|
|
229
|
+
await publish(PaymentConfirmed({ subscriptionId: e.subscriptionId }));
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
expect(flow.stateful).toBe(false);
|
|
233
|
+
await flow._fire(PaymentRequested({ subscriptionId: "s1", amount: 10 }), ctx);
|
|
234
|
+
expect(seen).toEqual(["s1"]);
|
|
235
|
+
expect(ctx.published.map((p) => p.eventName)).toEqual(["billing.payment-confirmed"]);
|
|
236
|
+
expect(ctx.store.size).toBe(0);
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
//# sourceMappingURL=workflow-saga.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflow-saga.test.js","sourceRoot":"","sources":["../../src/__tests__/workflow-saga.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ActorStore — persistence contract for actor state.
|
|
3
|
+
*
|
|
4
|
+
* The runtime reads and writes actor instances through this contract.
|
|
5
|
+
* Implementations:
|
|
6
|
+
* - InMemoryActorStore — process-local; default for tests and dev.
|
|
7
|
+
* - (future) MongoActorStore, PostgresActorStore — persisted, cluster-safe.
|
|
8
|
+
*
|
|
9
|
+
* The store is intentionally thin: load/save/exists keyed by (actorName, key).
|
|
10
|
+
* No queries, no joins, no streaming — those live in projections.
|
|
11
|
+
*/
|
|
12
|
+
import type { ActorDefinition } from "./define-actor.js";
|
|
13
|
+
export interface ActorInstance<TData = unknown> {
|
|
14
|
+
/** Logical actor type name (matches `ActorDefinition.name`). */
|
|
15
|
+
readonly actorName: string;
|
|
16
|
+
/** Stable identifier extracted from the actor's `key` field. */
|
|
17
|
+
readonly key: string;
|
|
18
|
+
/** Tenant scope. Empty string means the global / single-tenant scope. */
|
|
19
|
+
readonly tenant: string;
|
|
20
|
+
/** Current state name. */
|
|
21
|
+
readonly state: string;
|
|
22
|
+
/** Actor data. Validated against the actor's schema on save. */
|
|
23
|
+
readonly data: TData;
|
|
24
|
+
/**
|
|
25
|
+
* Active timers for the current state. Map of timer name → scheduled time
|
|
26
|
+
* (ms epoch) and the action that fires.
|
|
27
|
+
*/
|
|
28
|
+
readonly activeTimers: Readonly<Record<string, ActorTimerHandle>>;
|
|
29
|
+
}
|
|
30
|
+
export interface ActorTimerHandle {
|
|
31
|
+
readonly scheduledAt: number;
|
|
32
|
+
readonly fireAt: number;
|
|
33
|
+
readonly action: string;
|
|
34
|
+
readonly input: unknown;
|
|
35
|
+
}
|
|
36
|
+
export interface ActorStore {
|
|
37
|
+
/**
|
|
38
|
+
* Load an actor instance scoped to `tenant` (default `''` for the global
|
|
39
|
+
* / single-tenant scope). Two tenants writing the same key see entirely
|
|
40
|
+
* separate state — there is no cross-tenant leakage by construction.
|
|
41
|
+
*/
|
|
42
|
+
load(actorName: string, key: string, tenant?: string): Promise<ActorInstance | null>;
|
|
43
|
+
save(instance: ActorInstance): Promise<void>;
|
|
44
|
+
exists(actorName: string, key: string, tenant?: string): Promise<boolean>;
|
|
45
|
+
/**
|
|
46
|
+
* Enumerate stored instances. Used by the timer scheduler to walk every
|
|
47
|
+
* actor and find expired timers. With no `tenant`, returns instances
|
|
48
|
+
* across ALL tenants (the scheduler must dispatch each timer in its
|
|
49
|
+
* owning tenant's envelope). Implementations backed by databases should
|
|
50
|
+
* index `activeTimers.fireAt` for efficient querying.
|
|
51
|
+
*/
|
|
52
|
+
listInstances(actorName: string, tenant?: string): Promise<readonly ActorInstance[]>;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Process-local in-memory store. Each save deep-clones the instance to prevent
|
|
56
|
+
* the runtime from observing post-save mutations of the same reference.
|
|
57
|
+
*/
|
|
58
|
+
export declare class InMemoryActorStore implements ActorStore {
|
|
59
|
+
private readonly entries;
|
|
60
|
+
/**
|
|
61
|
+
* Storage key includes tenant first so prefix scans by `(actor, tenant)`
|
|
62
|
+
* are O(matching tenant) — and across-tenant scans (no tenant) are O(N)
|
|
63
|
+
* by intent.
|
|
64
|
+
*/
|
|
65
|
+
private cacheKey;
|
|
66
|
+
load(actorName: string, key: string, tenant?: string): Promise<ActorInstance | null>;
|
|
67
|
+
save(instance: ActorInstance): Promise<void>;
|
|
68
|
+
exists(actorName: string, key: string, tenant?: string): Promise<boolean>;
|
|
69
|
+
listInstances(actorName: string, tenant?: string): Promise<readonly ActorInstance[]>;
|
|
70
|
+
/** Test-only — returns the count of stored actor instances. */
|
|
71
|
+
size(): number;
|
|
72
|
+
/** Test-only — clears all stored instances. */
|
|
73
|
+
clear(): void;
|
|
74
|
+
private clone;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Build the initial instance for an actor whose key has never been seen in
|
|
78
|
+
* this tenant. Data is `{}` cast to the schema's output type — the first
|
|
79
|
+
* reaction's `assign` is responsible for populating it. Validation runs at
|
|
80
|
+
* save time.
|
|
81
|
+
*/
|
|
82
|
+
export declare function createInitialInstance(actor: ActorDefinition, key: string, tenant?: string): ActorInstance;
|
|
83
|
+
//# sourceMappingURL=actor-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"actor-store.d.ts","sourceRoot":"","sources":["../src/actor-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEtD,MAAM,WAAW,aAAa,CAAC,KAAK,GAAG,OAAO;IAC5C,gEAAgE;IAChE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,gEAAgE;IAChE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,yEAAyE;IACzE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,0BAA0B;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,gEAAgE;IAChE,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IACrB;;;OAGG;IACH,QAAQ,CAAC,YAAY,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAAC;CACnE;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,UAAU;IACzB;;;;OAIG;IACH,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC;IACrF,IAAI,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1E;;;;;;OAMG;IACH,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,aAAa,EAAE,CAAC,CAAC;CACtF;AAED;;;GAGG;AACH,qBAAa,kBAAmB,YAAW,UAAU;IACnD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoC;IAE5D;;;;OAIG;IACH,OAAO,CAAC,QAAQ;IAIV,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,GAAE,MAAW,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAKxF,IAAI,CAAC,QAAQ,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAO5C,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,GAAE,MAAW,GAAG,OAAO,CAAC,OAAO,CAAC;IAI7E,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,aAAa,EAAE,CAAC;IAW1F,+DAA+D;IAC/D,IAAI,IAAI,MAAM;IAId,+CAA+C;IAC/C,KAAK,IAAI,IAAI;IAIb,OAAO,CAAC,KAAK;CAYd;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,eAAe,EACtB,GAAG,EAAE,MAAM,EACX,MAAM,GAAE,MAAW,GAClB,aAAa,CAWf"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ActorStore — persistence contract for actor state.
|
|
3
|
+
*
|
|
4
|
+
* The runtime reads and writes actor instances through this contract.
|
|
5
|
+
* Implementations:
|
|
6
|
+
* - InMemoryActorStore — process-local; default for tests and dev.
|
|
7
|
+
* - (future) MongoActorStore, PostgresActorStore — persisted, cluster-safe.
|
|
8
|
+
*
|
|
9
|
+
* The store is intentionally thin: load/save/exists keyed by (actorName, key).
|
|
10
|
+
* No queries, no joins, no streaming — those live in projections.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Process-local in-memory store. Each save deep-clones the instance to prevent
|
|
14
|
+
* the runtime from observing post-save mutations of the same reference.
|
|
15
|
+
*/
|
|
16
|
+
export class InMemoryActorStore {
|
|
17
|
+
entries = new Map();
|
|
18
|
+
/**
|
|
19
|
+
* Storage key includes tenant first so prefix scans by `(actor, tenant)`
|
|
20
|
+
* are O(matching tenant) — and across-tenant scans (no tenant) are O(N)
|
|
21
|
+
* by intent.
|
|
22
|
+
*/
|
|
23
|
+
cacheKey(actorName, key, tenant) {
|
|
24
|
+
return `${tenant}::${actorName}::${key}`;
|
|
25
|
+
}
|
|
26
|
+
async load(actorName, key, tenant = "") {
|
|
27
|
+
const stored = this.entries.get(this.cacheKey(actorName, key, tenant));
|
|
28
|
+
return stored ? this.clone(stored) : null;
|
|
29
|
+
}
|
|
30
|
+
async save(instance) {
|
|
31
|
+
this.entries.set(this.cacheKey(instance.actorName, instance.key, instance.tenant), this.clone(instance));
|
|
32
|
+
}
|
|
33
|
+
async exists(actorName, key, tenant = "") {
|
|
34
|
+
return this.entries.has(this.cacheKey(actorName, key, tenant));
|
|
35
|
+
}
|
|
36
|
+
async listInstances(actorName, tenant) {
|
|
37
|
+
const result = [];
|
|
38
|
+
for (const [storeKey, value] of this.entries) {
|
|
39
|
+
if (value.actorName !== actorName)
|
|
40
|
+
continue;
|
|
41
|
+
if (tenant !== undefined && value.tenant !== tenant)
|
|
42
|
+
continue;
|
|
43
|
+
void storeKey;
|
|
44
|
+
result.push(this.clone(value));
|
|
45
|
+
}
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
/** Test-only — returns the count of stored actor instances. */
|
|
49
|
+
size() {
|
|
50
|
+
return this.entries.size;
|
|
51
|
+
}
|
|
52
|
+
/** Test-only — clears all stored instances. */
|
|
53
|
+
clear() {
|
|
54
|
+
this.entries.clear();
|
|
55
|
+
}
|
|
56
|
+
clone(instance) {
|
|
57
|
+
return {
|
|
58
|
+
actorName: instance.actorName,
|
|
59
|
+
key: instance.key,
|
|
60
|
+
tenant: instance.tenant,
|
|
61
|
+
state: instance.state,
|
|
62
|
+
data: structuredClone(instance.data),
|
|
63
|
+
activeTimers: structuredClone(instance.activeTimers),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Build the initial instance for an actor whose key has never been seen in
|
|
69
|
+
* this tenant. Data is `{}` cast to the schema's output type — the first
|
|
70
|
+
* reaction's `assign` is responsible for populating it. Validation runs at
|
|
71
|
+
* save time.
|
|
72
|
+
*/
|
|
73
|
+
export function createInitialInstance(actor, key, tenant = "") {
|
|
74
|
+
return {
|
|
75
|
+
actorName: actor.name,
|
|
76
|
+
key,
|
|
77
|
+
tenant,
|
|
78
|
+
state: actor.initial,
|
|
79
|
+
// Initial data is empty; the first reaction's `assign` fills it.
|
|
80
|
+
// Schema validation happens at save time, not here.
|
|
81
|
+
data: {},
|
|
82
|
+
activeTimers: {},
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=actor-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"actor-store.js","sourceRoot":"","sources":["../src/actor-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAgDH;;;GAGG;AACH,MAAM,OAAO,kBAAkB;IACZ,OAAO,GAAG,IAAI,GAAG,EAAyB,CAAC;IAE5D;;;;OAIG;IACK,QAAQ,CAAC,SAAiB,EAAE,GAAW,EAAE,MAAc;QAC7D,OAAO,GAAG,MAAM,KAAK,SAAS,KAAK,GAAG,EAAE,CAAC;IAC3C,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,SAAiB,EAAE,GAAW,EAAE,SAAiB,EAAE;QAC5D,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC;QACvE,OAAO,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC5C,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,QAAuB;QAChC,IAAI,CAAC,OAAO,CAAC,GAAG,CACd,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,EAChE,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CACrB,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,SAAiB,EAAE,GAAW,EAAE,SAAiB,EAAE;QAC9D,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC;IACjE,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,SAAiB,EAAE,MAAe;QACpD,MAAM,MAAM,GAAoB,EAAE,CAAC;QACnC,KAAK,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAC7C,IAAI,KAAK,CAAC,SAAS,KAAK,SAAS;gBAAE,SAAS;YAC5C,IAAI,MAAM,KAAK,SAAS,IAAI,KAAK,CAAC,MAAM,KAAK,MAAM;gBAAE,SAAS;YAC9D,KAAK,QAAQ,CAAC;YACd,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;QACjC,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,+DAA+D;IAC/D,IAAI;QACF,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;IAC3B,CAAC;IAED,+CAA+C;IAC/C,KAAK;QACH,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;IAEO,KAAK,CAAC,QAAuB;QACnC,OAAO;YACL,SAAS,EAAE,QAAQ,CAAC,SAAS;YAC7B,GAAG,EAAE,QAAQ,CAAC,GAAG;YACjB,MAAM,EAAE,QAAQ,CAAC,MAAM;YACvB,KAAK,EAAE,QAAQ,CAAC,KAAK;YACrB,IAAI,EAAE,eAAe,CAAC,QAAQ,CAAC,IAAI,CAAC;YACpC,YAAY,EAAE,eAAe,CAAC,QAAQ,CAAC,YAAY,CAElD;SACF,CAAC;IACJ,CAAC;CACF;AAED;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CACnC,KAAsB,EACtB,GAAW,EACX,SAAiB,EAAE;IAEnB,OAAO;QACL,SAAS,EAAE,KAAK,CAAC,IAAI;QACrB,GAAG;QACH,MAAM;QACN,KAAK,EAAE,KAAK,CAAC,OAAO;QACpB,iEAAiE;QACjE,oDAAoD;QACpD,IAAI,EAAE,EAAa;QACnB,YAAY,EAAE,EAAE;KACjB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `runCli` — command-line runner. Operators dispatch any registered action
|
|
3
|
+
* (or query) by name, with input parsed from CLI flags.
|
|
4
|
+
*
|
|
5
|
+
* amit submissions.flag-for-review --submissionId xyz --confidence 0.4
|
|
6
|
+
* amit lessons.start-attempt --lessonId hebrew-letters-1 \
|
|
7
|
+
* --studentId student-avi-9 \
|
|
8
|
+
* --exerciseIds exercise-A1 --exerciseIds exercise-A2
|
|
9
|
+
* amit submissions.by-student --studentId student-avi-9 --status under-review
|
|
10
|
+
*
|
|
11
|
+
* Wires create a small entry script that hands a fresh `App` to `runCli`:
|
|
12
|
+
*
|
|
13
|
+
* #!/usr/bin/env node
|
|
14
|
+
* import { runCli } from '@nwire/forge'
|
|
15
|
+
* import { boot } from './app/boot.js'
|
|
16
|
+
*
|
|
17
|
+
* const app = await boot() // returns { learnflow: App }
|
|
18
|
+
* const exitCode = await runCli(app.learnflow, process.argv.slice(2))
|
|
19
|
+
* process.exit(exitCode)
|
|
20
|
+
*
|
|
21
|
+
* Convention:
|
|
22
|
+
* - First arg is the action or query name (`submissions.flag-for-review`).
|
|
23
|
+
* - `--field value` sets a string field on the input.
|
|
24
|
+
* - `--field=value` is also accepted.
|
|
25
|
+
* - Repeating `--field a --field b` produces `field: ['a', 'b']`.
|
|
26
|
+
* - `--field` (no value) sets `field: true`.
|
|
27
|
+
* - `--tenant <id>` is special — sets `envelope.tenant` for the dispatch.
|
|
28
|
+
* - Numeric values are auto-coerced when the schema accepts a number.
|
|
29
|
+
*
|
|
30
|
+
* Dispatching against an action returns void; the runner prints `OK`. Against
|
|
31
|
+
* a query: prints the JSON result. Errors print to stderr and exit non-zero.
|
|
32
|
+
*
|
|
33
|
+
* Wire-mode invariance: same domain handlers run in HTTP, queue, and CLI.
|
|
34
|
+
* The CLI doesn't know about HTTP routes; it dispatches via the runtime.
|
|
35
|
+
*/
|
|
36
|
+
import type { App } from "./create-app.js";
|
|
37
|
+
export interface RunCliOptions {
|
|
38
|
+
/**
|
|
39
|
+
* Override stdout/stderr — useful for tests. Defaults to console.log /
|
|
40
|
+
* console.error.
|
|
41
|
+
*/
|
|
42
|
+
readonly stdout?: (line: string) => void;
|
|
43
|
+
readonly stderr?: (line: string) => void;
|
|
44
|
+
}
|
|
45
|
+
export declare function runCli(app: App, argv: readonly string[], options?: RunCliOptions): Promise<number>;
|
|
46
|
+
//# sourceMappingURL=cli-runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli-runner.d.ts","sourceRoot":"","sources":["../src/cli-runner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAGH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAExC,MAAM,WAAW,aAAa;IAC5B;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC1C;AAED,wBAAsB,MAAM,CAC1B,GAAG,EAAE,GAAG,EACR,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,MAAM,CAAC,CA2CjB"}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `runCli` — command-line runner. Operators dispatch any registered action
|
|
3
|
+
* (or query) by name, with input parsed from CLI flags.
|
|
4
|
+
*
|
|
5
|
+
* amit submissions.flag-for-review --submissionId xyz --confidence 0.4
|
|
6
|
+
* amit lessons.start-attempt --lessonId hebrew-letters-1 \
|
|
7
|
+
* --studentId student-avi-9 \
|
|
8
|
+
* --exerciseIds exercise-A1 --exerciseIds exercise-A2
|
|
9
|
+
* amit submissions.by-student --studentId student-avi-9 --status under-review
|
|
10
|
+
*
|
|
11
|
+
* Wires create a small entry script that hands a fresh `App` to `runCli`:
|
|
12
|
+
*
|
|
13
|
+
* #!/usr/bin/env node
|
|
14
|
+
* import { runCli } from '@nwire/forge'
|
|
15
|
+
* import { boot } from './app/boot.js'
|
|
16
|
+
*
|
|
17
|
+
* const app = await boot() // returns { learnflow: App }
|
|
18
|
+
* const exitCode = await runCli(app.learnflow, process.argv.slice(2))
|
|
19
|
+
* process.exit(exitCode)
|
|
20
|
+
*
|
|
21
|
+
* Convention:
|
|
22
|
+
* - First arg is the action or query name (`submissions.flag-for-review`).
|
|
23
|
+
* - `--field value` sets a string field on the input.
|
|
24
|
+
* - `--field=value` is also accepted.
|
|
25
|
+
* - Repeating `--field a --field b` produces `field: ['a', 'b']`.
|
|
26
|
+
* - `--field` (no value) sets `field: true`.
|
|
27
|
+
* - `--tenant <id>` is special — sets `envelope.tenant` for the dispatch.
|
|
28
|
+
* - Numeric values are auto-coerced when the schema accepts a number.
|
|
29
|
+
*
|
|
30
|
+
* Dispatching against an action returns void; the runner prints `OK`. Against
|
|
31
|
+
* a query: prints the JSON result. Errors print to stderr and exit non-zero.
|
|
32
|
+
*
|
|
33
|
+
* Wire-mode invariance: same domain handlers run in HTTP, queue, and CLI.
|
|
34
|
+
* The CLI doesn't know about HTTP routes; it dispatches via the runtime.
|
|
35
|
+
*/
|
|
36
|
+
import { seedEnvelope } from "@nwire/envelope";
|
|
37
|
+
export async function runCli(app, argv, options = {}) {
|
|
38
|
+
const stdout = options.stdout ?? ((line) => console.log(line));
|
|
39
|
+
const stderr = options.stderr ?? ((line) => console.error(line));
|
|
40
|
+
if (argv.length === 0 || argv[0] === "--help" || argv[0] === "-h") {
|
|
41
|
+
printHelp(app, stdout);
|
|
42
|
+
return 0;
|
|
43
|
+
}
|
|
44
|
+
const targetName = argv[0];
|
|
45
|
+
const rest = argv.slice(1);
|
|
46
|
+
// Try action first — actions write through the runtime's dispatch.
|
|
47
|
+
const action = findAction(app, targetName);
|
|
48
|
+
if (action) {
|
|
49
|
+
const { tenant, parsed } = parseFlags(rest);
|
|
50
|
+
try {
|
|
51
|
+
const validated = action.schema.parse(parsed);
|
|
52
|
+
const envelope = tenant ? seedEnvelope({ tenant }) : undefined;
|
|
53
|
+
await app.runtime.dispatch(action, validated, envelope);
|
|
54
|
+
stdout("OK");
|
|
55
|
+
return 0;
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
stderr(`error dispatching "${targetName}": ${err.message}`);
|
|
59
|
+
return 1;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// Fall back to query.
|
|
63
|
+
if (app.runtime.listQueries().includes(targetName)) {
|
|
64
|
+
const { tenant, parsed } = parseFlags(rest);
|
|
65
|
+
try {
|
|
66
|
+
const result = await app.runtime.query(targetName, parsed, tenant);
|
|
67
|
+
stdout(JSON.stringify(result, null, 2));
|
|
68
|
+
return 0;
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
stderr(`error querying "${targetName}": ${err.message}`);
|
|
72
|
+
return 1;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
stderr(`unknown action or query: "${targetName}". Run with --help for the list.`);
|
|
76
|
+
return 1;
|
|
77
|
+
}
|
|
78
|
+
function findAction(app, name) {
|
|
79
|
+
return toActionMap(app)[name];
|
|
80
|
+
}
|
|
81
|
+
function toActionMap(app) {
|
|
82
|
+
const map = {};
|
|
83
|
+
for (const module of app.modules) {
|
|
84
|
+
for (const action of module.manifest.actions ?? []) {
|
|
85
|
+
map[action.name] = action;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return map;
|
|
89
|
+
}
|
|
90
|
+
function printHelp(app, stdout) {
|
|
91
|
+
stdout("nwire — action CLI runner\n");
|
|
92
|
+
stdout("Usage: amit <action-or-query-name> [--field value ...] [--tenant <id>]\n");
|
|
93
|
+
stdout("Actions:");
|
|
94
|
+
for (const module of app.modules) {
|
|
95
|
+
for (const action of module.manifest.actions ?? []) {
|
|
96
|
+
stdout(` ${action.name}${action.description ? " — " + action.description.slice(0, 60) : ""}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
stdout("Queries:");
|
|
100
|
+
for (const module of app.modules) {
|
|
101
|
+
for (const query of module.manifest.queries ?? []) {
|
|
102
|
+
stdout(` ${query.name}${query.description ? " — " + query.description.slice(0, 60) : ""}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function parseFlags(argv) {
|
|
107
|
+
const out = {};
|
|
108
|
+
let tenant;
|
|
109
|
+
for (let i = 0; i < argv.length; i++) {
|
|
110
|
+
const arg = argv[i];
|
|
111
|
+
if (!arg.startsWith("--"))
|
|
112
|
+
continue;
|
|
113
|
+
// Support both --field=value and --field value.
|
|
114
|
+
const eq = arg.indexOf("=");
|
|
115
|
+
let key;
|
|
116
|
+
let value;
|
|
117
|
+
if (eq >= 0) {
|
|
118
|
+
key = arg.slice(2, eq);
|
|
119
|
+
value = arg.slice(eq + 1);
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
key = arg.slice(2);
|
|
123
|
+
const next = argv[i + 1];
|
|
124
|
+
if (next !== undefined && !next.startsWith("--")) {
|
|
125
|
+
value = next;
|
|
126
|
+
i++;
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
value = true;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (key === "tenant") {
|
|
133
|
+
if (typeof value === "string")
|
|
134
|
+
tenant = value;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const coerced = typeof value === "string" ? coerce(value) : value;
|
|
138
|
+
if (key in out) {
|
|
139
|
+
const existing = out[key];
|
|
140
|
+
if (Array.isArray(existing)) {
|
|
141
|
+
existing.push(coerced);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
out[key] = [existing, coerced];
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
out[key] = coerced;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return { tenant, parsed: out };
|
|
152
|
+
}
|
|
153
|
+
function coerce(value) {
|
|
154
|
+
if (value === "true")
|
|
155
|
+
return true;
|
|
156
|
+
if (value === "false")
|
|
157
|
+
return false;
|
|
158
|
+
if (/^-?\d+$/.test(value))
|
|
159
|
+
return Number(value);
|
|
160
|
+
if (/^-?\d+\.\d+$/.test(value))
|
|
161
|
+
return Number(value);
|
|
162
|
+
return value;
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=cli-runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli-runner.js","sourceRoot":"","sources":["../src/cli-runner.ts"],"names":[],"mappings":"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"}
|