@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,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Production-readiness audit — L4 forge runtime properties teams must
|
|
3
|
+
* trust before adopting workflows + projections in prod.
|
|
4
|
+
*
|
|
5
|
+
* Each test pins a real-world invariant:
|
|
6
|
+
*
|
|
7
|
+
* 1. Workflow correlation respects envelope.tenant (no cross-tenant
|
|
8
|
+
* saga bleed).
|
|
9
|
+
* 2. Plugin shutdown error isolation — one bad plugin's shutdown error
|
|
10
|
+
* doesn't prevent other plugins from cleaning up.
|
|
11
|
+
* 3. Projection state is deterministic — replaying the same event log
|
|
12
|
+
* against a fresh projection store rebuilds an identical view.
|
|
13
|
+
* 4. Cascade integrity — when a workflow handler dispatches another
|
|
14
|
+
* action, downstream events emit exactly once (not duplicated by
|
|
15
|
+
* the parent dispatch's event-publish loop).
|
|
16
|
+
*
|
|
17
|
+
* Actor restart durability is verified at the store layer (see
|
|
18
|
+
* `nwire-store-mongo`, `nwire-store-file`); a round-trip integration
|
|
19
|
+
* test belongs there, not in this file.
|
|
20
|
+
*/
|
|
21
|
+
import { describe, it, expect } from "vitest";
|
|
22
|
+
import { z } from "zod";
|
|
23
|
+
import { defineEvent } from "@nwire/messages";
|
|
24
|
+
import * as forge from "../foundation.js";
|
|
25
|
+
import { seedEnvelope } from "../foundation.js";
|
|
26
|
+
const { defineAction, defineHandler, defineProjection, defineWorkflow, eventFactory, Runtime, InMemoryActorStore, InMemoryProjectionStore, createApp, definePlugin, } = forge;
|
|
27
|
+
// ─── Shared fixtures ─────────────────────────────────────────────────
|
|
28
|
+
const PaymentFailedDef = defineEvent({
|
|
29
|
+
name: "billing.payment-failed",
|
|
30
|
+
schema: z.object({ subscriptionId: z.string(), reason: z.string() }),
|
|
31
|
+
});
|
|
32
|
+
const PaymentFailed = eventFactory(PaymentFailedDef);
|
|
33
|
+
const ChargeAttemptedDef = defineEvent({
|
|
34
|
+
name: "billing.charge-attempted",
|
|
35
|
+
schema: z.object({ subscriptionId: z.string() }),
|
|
36
|
+
});
|
|
37
|
+
const ChargeAttempted = eventFactory(ChargeAttemptedDef);
|
|
38
|
+
const chargeSubscription = defineAction({
|
|
39
|
+
name: "billing.charge",
|
|
40
|
+
description: "Attempt to charge the subscription.",
|
|
41
|
+
schema: z.object({ subscriptionId: z.string(), willFail: z.boolean().default(false) }),
|
|
42
|
+
emits: [ChargeAttemptedDef, PaymentFailedDef],
|
|
43
|
+
});
|
|
44
|
+
const chargeHandler = defineHandler(chargeSubscription, async (input) => {
|
|
45
|
+
const out = [ChargeAttempted({ subscriptionId: input.subscriptionId })];
|
|
46
|
+
if (input.willFail) {
|
|
47
|
+
out.push(PaymentFailed({ subscriptionId: input.subscriptionId, reason: "card-declined" }));
|
|
48
|
+
}
|
|
49
|
+
return out;
|
|
50
|
+
});
|
|
51
|
+
// ─── 1. Workflow correlation respects tenant ─────────────────────────
|
|
52
|
+
describe("workflow correlation respects envelope.tenant", () => {
|
|
53
|
+
it("two tenants with the same subscriptionId have independent saga instances", async () => {
|
|
54
|
+
const attemptsObservedPerFire = [];
|
|
55
|
+
const renewal = defineWorkflow("renewal", ({ on, data, assign }) => {
|
|
56
|
+
on(PaymentFailedDef, async () => {
|
|
57
|
+
attemptsObservedPerFire.push(data.attempts);
|
|
58
|
+
await assign({ attempts: data.attempts + 1 });
|
|
59
|
+
});
|
|
60
|
+
}, {
|
|
61
|
+
data: z.object({ subscriptionId: z.string().default(""), attempts: z.number().default(0) }),
|
|
62
|
+
correlate: (map) => {
|
|
63
|
+
map(PaymentFailedDef, (e) => e.subscriptionId);
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
const runtime = new Runtime({
|
|
67
|
+
actorStore: new InMemoryActorStore(),
|
|
68
|
+
projectionStore: new InMemoryProjectionStore(),
|
|
69
|
+
});
|
|
70
|
+
runtime.registerHandler(chargeHandler);
|
|
71
|
+
runtime.registerWorkflow(renewal);
|
|
72
|
+
await runtime.dispatch(chargeSubscription, { subscriptionId: "sub-1", willFail: true }, seedEnvelope({ tenant: "tlv" }));
|
|
73
|
+
await runtime.dispatch(chargeSubscription, { subscriptionId: "sub-1", willFail: true }, seedEnvelope({ tenant: "jlm" }));
|
|
74
|
+
// Both tenants fired the workflow with `attempts === 0` — meaning each
|
|
75
|
+
// tenant got its OWN fresh saga instance. If they shared a key (sub-1)
|
|
76
|
+
// across tenants, the second fire would see attempts === 1.
|
|
77
|
+
//
|
|
78
|
+
// If this test FAILS, the runtime's workflow correlationKey derivation
|
|
79
|
+
// is missing the tenant axis — workflows with the same business key
|
|
80
|
+
// across tenants will collide.
|
|
81
|
+
expect(attemptsObservedPerFire).toEqual([0, 0]);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
// ─── 2. Plugin shutdown error isolation ──────────────────────────────
|
|
85
|
+
describe("plugin shutdown error isolation", () => {
|
|
86
|
+
it("a plugin that throws on shutdown does not block other plugins from shutting down", async () => {
|
|
87
|
+
const shutdownsAttempted = [];
|
|
88
|
+
const bad = definePlugin("bad-plugin", {
|
|
89
|
+
shutdown: async () => {
|
|
90
|
+
shutdownsAttempted.push("bad");
|
|
91
|
+
throw new Error("bad-plugin: shutdown failure");
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
const good = definePlugin("good-plugin", {
|
|
95
|
+
shutdown: async () => {
|
|
96
|
+
shutdownsAttempted.push("good");
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
const app = createApp({
|
|
100
|
+
modules: [],
|
|
101
|
+
plugins: [good, bad],
|
|
102
|
+
});
|
|
103
|
+
await app.start();
|
|
104
|
+
// app.stop() collects errors but still runs every shutdown step.
|
|
105
|
+
// It re-throws the first collected error AFTER the sweep — wires
|
|
106
|
+
// can detect the failure (existing contract from provider.test.ts)
|
|
107
|
+
// AND every plugin/provider still gets a chance to clean up.
|
|
108
|
+
await expect(app.stop()).rejects.toThrow(/bad-plugin: shutdown failure/);
|
|
109
|
+
// Both plugins' shutdown handlers got a chance to run.
|
|
110
|
+
expect(shutdownsAttempted).toContain("bad");
|
|
111
|
+
expect(shutdownsAttempted).toContain("good");
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
// ─── 3. Projection rebuild is deterministic ──────────────────────────
|
|
115
|
+
describe("projection rebuild is deterministic (replay = identity)", () => {
|
|
116
|
+
const BumpedDef = defineEvent({
|
|
117
|
+
name: "counter.bumped",
|
|
118
|
+
schema: z.object({ counterId: z.string(), delta: z.number() }),
|
|
119
|
+
});
|
|
120
|
+
const totalsProj = defineProjection("totals", {
|
|
121
|
+
description: "Per-counter totals folded from Bumped events.",
|
|
122
|
+
listens: [BumpedDef],
|
|
123
|
+
initial: () => ({ totals: {} }),
|
|
124
|
+
on: {
|
|
125
|
+
[BumpedDef.name]: (state, event) => {
|
|
126
|
+
const e = event;
|
|
127
|
+
return {
|
|
128
|
+
totals: { ...state.totals, [e.counterId]: (state.totals[e.counterId] ?? 0) + e.delta },
|
|
129
|
+
};
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
it("applying the same event sequence to two fresh projections yields identical state", async () => {
|
|
134
|
+
const store1 = new InMemoryProjectionStore();
|
|
135
|
+
const store2 = new InMemoryProjectionStore();
|
|
136
|
+
const runtime1 = new Runtime({ actorStore: new InMemoryActorStore(), projectionStore: store1 });
|
|
137
|
+
const runtime2 = new Runtime({ actorStore: new InMemoryActorStore(), projectionStore: store2 });
|
|
138
|
+
runtime1.registerProjection(totalsProj);
|
|
139
|
+
runtime2.registerProjection(totalsProj);
|
|
140
|
+
// Same event sequence to both runtimes.
|
|
141
|
+
const eventSequence = [
|
|
142
|
+
{ counterId: "a", delta: 1 },
|
|
143
|
+
{ counterId: "b", delta: 2 },
|
|
144
|
+
{ counterId: "a", delta: 5 },
|
|
145
|
+
{ counterId: "c", delta: 10 },
|
|
146
|
+
{ counterId: "a", delta: -3 },
|
|
147
|
+
];
|
|
148
|
+
for (const payload of eventSequence) {
|
|
149
|
+
await runtime1.publish([{ eventName: BumpedDef.name, payload }], seedEnvelope({}));
|
|
150
|
+
await runtime2.publish([{ eventName: BumpedDef.name, payload }], seedEnvelope({}));
|
|
151
|
+
}
|
|
152
|
+
const proj1 = await store1.load("totals", "");
|
|
153
|
+
const proj2 = await store2.load("totals", "");
|
|
154
|
+
expect(proj1).toEqual(proj2);
|
|
155
|
+
expect(proj1?.totals).toEqual({ a: 3, b: 2, c: 10 });
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
// ─── 4. Cascade integrity — no double-emit ───────────────────────────
|
|
159
|
+
describe("cascade integrity — handler-initiated dispatch emits each event exactly once", () => {
|
|
160
|
+
it("workflow sends → cascade fires → events emit once per dispatch (no duplicates)", async () => {
|
|
161
|
+
const eventCounts = {};
|
|
162
|
+
const cascade = defineWorkflow("cascade-probe", ({ on, send, data, assign }) => {
|
|
163
|
+
on(PaymentFailedDef, async (e) => {
|
|
164
|
+
const attempt = data.attempts;
|
|
165
|
+
await assign({ attempts: attempt + 1 });
|
|
166
|
+
if (attempt < 1) {
|
|
167
|
+
await send(chargeSubscription, { subscriptionId: e.subscriptionId, willFail: false });
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
}, {
|
|
171
|
+
data: z.object({ subscriptionId: z.string().default(""), attempts: z.number().default(0) }),
|
|
172
|
+
correlate: (map) => {
|
|
173
|
+
map(PaymentFailedDef, (e) => e.subscriptionId);
|
|
174
|
+
},
|
|
175
|
+
});
|
|
176
|
+
const runtime = new Runtime({
|
|
177
|
+
actorStore: new InMemoryActorStore(),
|
|
178
|
+
projectionStore: new InMemoryProjectionStore(),
|
|
179
|
+
});
|
|
180
|
+
runtime.registerHandler(chargeHandler);
|
|
181
|
+
runtime.registerWorkflow(cascade);
|
|
182
|
+
runtime.onTelemetry((rec) => {
|
|
183
|
+
if (rec.kind === "event.published") {
|
|
184
|
+
eventCounts[rec.event.eventName] = (eventCounts[rec.event.eventName] ?? 0) + 1;
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
// Dispatch a failing charge → workflow retries once with a success.
|
|
188
|
+
// first dispatch (willFail=true) → charge-attempted + payment-failed
|
|
189
|
+
// second dispatch (willFail=false) → charge-attempted only
|
|
190
|
+
// Total: charge-attempted ×2, payment-failed ×1.
|
|
191
|
+
await runtime.dispatch(chargeSubscription, { subscriptionId: "sub-cascade", willFail: true });
|
|
192
|
+
expect(eventCounts["billing.charge-attempted"]).toBe(2);
|
|
193
|
+
expect(eventCounts["billing.payment-failed"]).toBe(1);
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
//# sourceMappingURL=production-readiness.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"production-readiness.test.js","sourceRoot":"","sources":["../../src/__tests__/production-readiness.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/provider.test.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `defineProvider` — bootable dependencies registered on the container
|
|
3
|
+
* during `app.start()`, shut down in reverse order during `app.stop()`.
|
|
4
|
+
*/
|
|
5
|
+
import { describe, it, expect } from "vitest";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { defineProvider, defineAction, defineModule, createApp } from "../foundation.js";
|
|
8
|
+
describe("defineProvider", () => {
|
|
9
|
+
it("boots providers in declaration order and registers them on the container", async () => {
|
|
10
|
+
const bootOrder = [];
|
|
11
|
+
const dbProvider = defineProvider("database", {
|
|
12
|
+
boot: async () => {
|
|
13
|
+
bootOrder.push("database");
|
|
14
|
+
return { tag: "db-pool" };
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
const redisProvider = defineProvider("redis", {
|
|
18
|
+
boot: async () => {
|
|
19
|
+
bootOrder.push("redis");
|
|
20
|
+
return { tag: "redis-client" };
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
const app = createApp({
|
|
24
|
+
modules: [],
|
|
25
|
+
providers: [dbProvider, redisProvider],
|
|
26
|
+
});
|
|
27
|
+
await app.start();
|
|
28
|
+
expect(bootOrder).toEqual(["database", "redis"]);
|
|
29
|
+
const db = app.runtime.getContainer().resolve("database");
|
|
30
|
+
const redis = app.runtime.getContainer().resolve("redis");
|
|
31
|
+
expect(db.tag).toBe("db-pool");
|
|
32
|
+
expect(redis.tag).toBe("redis-client");
|
|
33
|
+
await app.stop();
|
|
34
|
+
});
|
|
35
|
+
it("shuts down providers in reverse boot order", async () => {
|
|
36
|
+
const shutdownOrder = [];
|
|
37
|
+
const a = defineProvider("a", {
|
|
38
|
+
boot: async () => "a-val",
|
|
39
|
+
shutdown: async () => {
|
|
40
|
+
shutdownOrder.push("a");
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
const b = defineProvider("b", {
|
|
44
|
+
boot: async () => "b-val",
|
|
45
|
+
shutdown: async () => {
|
|
46
|
+
shutdownOrder.push("b");
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
const c = defineProvider("c", {
|
|
50
|
+
boot: async () => "c-val",
|
|
51
|
+
shutdown: async () => {
|
|
52
|
+
shutdownOrder.push("c");
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
const app = createApp({ modules: [], providers: [a, b, c] });
|
|
56
|
+
await app.start();
|
|
57
|
+
await app.stop();
|
|
58
|
+
expect(shutdownOrder).toEqual(["c", "b", "a"]);
|
|
59
|
+
});
|
|
60
|
+
it("a handler can resolve a provider via ctx.resolve", async () => {
|
|
61
|
+
const counterProvider = defineProvider("counter", {
|
|
62
|
+
boot: async () => {
|
|
63
|
+
const counter = {
|
|
64
|
+
value: 0,
|
|
65
|
+
incr() {
|
|
66
|
+
counter.value++;
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
return counter;
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
let observed = -1;
|
|
73
|
+
const bump = defineAction({
|
|
74
|
+
name: "bump",
|
|
75
|
+
schema: z.object({}),
|
|
76
|
+
handler: async (_input, ctx) => {
|
|
77
|
+
const counter = ctx.resolve("counter");
|
|
78
|
+
counter.incr();
|
|
79
|
+
observed = counter.value;
|
|
80
|
+
return undefined;
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
const app = createApp({
|
|
84
|
+
modules: [defineModule("m", { actions: [bump] })],
|
|
85
|
+
providers: [counterProvider],
|
|
86
|
+
});
|
|
87
|
+
await app.start();
|
|
88
|
+
await app.runtime.dispatch(bump, {});
|
|
89
|
+
await app.runtime.dispatch(bump, {});
|
|
90
|
+
expect(observed).toBe(2);
|
|
91
|
+
await app.stop();
|
|
92
|
+
});
|
|
93
|
+
it("shutdown errors propagate (so wires can detect stuck shutdowns)", async () => {
|
|
94
|
+
const p = defineProvider("flaky", {
|
|
95
|
+
boot: async () => "ok",
|
|
96
|
+
shutdown: async () => {
|
|
97
|
+
throw new Error("shutdown failed");
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
const app = createApp({ modules: [], providers: [p] });
|
|
101
|
+
await app.start();
|
|
102
|
+
await expect(app.stop()).rejects.toThrow(/shutdown failed/);
|
|
103
|
+
});
|
|
104
|
+
it("healthCheck is a no-op invocation (smoke — the framework just stores the function)", async () => {
|
|
105
|
+
const checks = [];
|
|
106
|
+
const p = defineProvider("hc", {
|
|
107
|
+
boot: async () => "ok",
|
|
108
|
+
healthCheck: async () => {
|
|
109
|
+
checks.push("checked");
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
const app = createApp({ modules: [], providers: [p] });
|
|
113
|
+
await app.start();
|
|
114
|
+
// Health-check invocation API will land with the CLI inspector;
|
|
115
|
+
// for now confirm the provider def carries the function.
|
|
116
|
+
expect(typeof p.healthCheck).toBe("function");
|
|
117
|
+
await p.healthCheck("ok");
|
|
118
|
+
expect(checks).toEqual(["checked"]);
|
|
119
|
+
await app.stop();
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
//# sourceMappingURL=provider.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.test.js","sourceRoot":"","sources":["../../src/__tests__/provider.test.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAEtF,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;IAC9B,EAAE,CAAC,0EAA0E,EAAE,KAAK,IAAI,EAAE;QACxF,MAAM,SAAS,GAAa,EAAE,CAAC;QAC/B,MAAM,UAAU,GAAG,cAAc,CAAC,UAAU,EAAE;YAC5C,IAAI,EAAE,KAAK,IAAI,EAAE;gBACf,SAAS,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;gBAC3B,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC;YAC5B,CAAC;SACF,CAAC,CAAC;QACH,MAAM,aAAa,GAAG,cAAc,CAAC,OAAO,EAAE;YAC5C,IAAI,EAAE,KAAK,IAAI,EAAE;gBACf,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;gBACxB,OAAO,EAAE,GAAG,EAAE,cAAc,EAAE,CAAC;YACjC,CAAC;SACF,CAAC,CAAC;QAEH,MAAM,GAAG,GAAG,SAAS,CAAC;YACpB,OAAO,EAAE,EAAE;YACX,SAAS,EAAE,CAAC,UAAU,EAAE,aAAa,CAAC;SACvC,CAAC,CAAC;QACH,MAAM,GAAG,CAAC,KAAK,EAAE,CAAC;QAElB,MAAM,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC;QAEjD,MAAM,EAAE,GAAG,GAAG,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC,OAAO,CAAkB,UAAU,CAAC,CAAC;QAC3E,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC,OAAO,CAAkB,OAAO,CAAC,CAAC;QAC3E,MAAM,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC/B,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAEvC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;IACnB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4CAA4C,EAAE,KAAK,IAAI,EAAE;QAC1D,MAAM,aAAa,GAAa,EAAE,CAAC;QACnC,MAAM,CAAC,GAAG,cAAc,CAAC,GAAG,EAAE;YAC5B,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC,OAAO;YACzB,QAAQ,EAAE,KAAK,IAAI,EAAE;gBACnB,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC1B,CAAC;SACF,CAAC,CAAC;QACH,MAAM,CAAC,GAAG,cAAc,CAAC,GAAG,EAAE;YAC5B,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC,OAAO;YACzB,QAAQ,EAAE,KAAK,IAAI,EAAE;gBACnB,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC1B,CAAC;SACF,CAAC,CAAC;QACH,MAAM,CAAC,GAAG,cAAc,CAAC,GAAG,EAAE;YAC5B,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC,OAAO;YACzB,QAAQ,EAAE,KAAK,IAAI,EAAE;gBACnB,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC1B,CAAC;SACF,CAAC,CAAC;QAEH,MAAM,GAAG,GAAG,SAAS,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;QAC7D,MAAM,GAAG,CAAC,KAAK,EAAE,CAAC;QAClB,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QAEjB,MAAM,CAAC,aAAa,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kDAAkD,EAAE,KAAK,IAAI,EAAE;QAKhE,MAAM,eAAe,GAAG,cAAc,CAAU,SAAS,EAAE;YACzD,IAAI,EAAE,KAAK,IAAI,EAAE;gBACf,MAAM,OAAO,GAAY;oBACvB,KAAK,EAAE,CAAC;oBACR,IAAI;wBACF,OAAO,CAAC,KAAK,EAAE,CAAC;oBAClB,CAAC;iBACF,CAAC;gBACF,OAAO,OAAO,CAAC;YACjB,CAAC;SACF,CAAC,CAAC;QAEH,IAAI,QAAQ,GAAG,CAAC,CAAC,CAAC;QAClB,MAAM,IAAI,GAAG,YAAY,CAAC;YACxB,IAAI,EAAE,MAAM;YACZ,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;YACpB,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE;gBAC7B,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,CAAkC,SAAS,CAAC,CAAC;gBACxE,OAAO,CAAC,IAAI,EAAE,CAAC;gBACf,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC;gBACzB,OAAO,SAAS,CAAC;YACnB,CAAC;SACF,CAAC,CAAC;QAEH,MAAM,GAAG,GAAG,SAAS,CAAC;YACpB,OAAO,EAAE,CAAC,YAAY,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACjD,SAAS,EAAE,CAAC,eAAe,CAAC;SAC7B,CAAC,CAAC;QACH,MAAM,GAAG,CAAC,KAAK,EAAE,CAAC;QAClB,MAAM,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACrC,MAAM,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACrC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACzB,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;IACnB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iEAAiE,EAAE,KAAK,IAAI,EAAE;QAC/E,MAAM,CAAC,GAAG,cAAc,CAAC,OAAO,EAAE;YAChC,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC,IAAI;YACtB,QAAQ,EAAE,KAAK,IAAI,EAAE;gBACnB,MAAM,IAAI,KAAK,CAAC,iBAAiB,CAAC,CAAC;YACrC,CAAC;SACF,CAAC,CAAC;QACH,MAAM,GAAG,GAAG,SAAS,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACvD,MAAM,GAAG,CAAC,KAAK,EAAE,CAAC;QAClB,MAAM,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oFAAoF,EAAE,KAAK,IAAI,EAAE;QAClG,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,MAAM,CAAC,GAAG,cAAc,CAAC,IAAI,EAAE;YAC7B,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC,IAAI;YACtB,WAAW,EAAE,KAAK,IAAI,EAAE;gBACtB,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACzB,CAAC;SACF,CAAC,CAAC;QACH,MAAM,GAAG,GAAG,SAAS,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACvD,MAAM,GAAG,CAAC,KAAK,EAAE,CAAC;QAClB,gEAAgE;QAChE,yDAAyD;QACzD,MAAM,CAAC,OAAO,CAAC,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC9C,MAAM,CAAC,CAAC,WAAY,CAAC,IAAI,CAAC,CAAC;QAC3B,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;QACpC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;IACnB,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"public-marker.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/public-marker.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `.public()` marker — verify it's chainable, returns a clone (doesn't
|
|
3
|
+
* mutate the original), and the `$public` flag is non-enumerable so it
|
|
4
|
+
* doesn't pollute JSON output.
|
|
5
|
+
*/
|
|
6
|
+
import { describe, it, expect } from "vitest";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import { defineEvent } from "@nwire/messages";
|
|
9
|
+
import { defineAction } from "../define-action.js";
|
|
10
|
+
import { defineWorkflow } from "../define-workflow.js";
|
|
11
|
+
import { isPublic } from "../public-marker.js";
|
|
12
|
+
const CarWasWashed = defineEvent({
|
|
13
|
+
name: "stations.car-was-washed",
|
|
14
|
+
schema: z.object({ stationId: z.string() }),
|
|
15
|
+
});
|
|
16
|
+
const recordWash = defineAction({
|
|
17
|
+
name: "stations.record-wash",
|
|
18
|
+
schema: z.object({ stationId: z.string() }),
|
|
19
|
+
});
|
|
20
|
+
describe(".public() marker", () => {
|
|
21
|
+
it("action.public() returns a clone marked $public", () => {
|
|
22
|
+
const original = recordWash;
|
|
23
|
+
const exposed = original.public();
|
|
24
|
+
expect(isPublic(original)).toBe(false);
|
|
25
|
+
expect(isPublic(exposed)).toBe(true);
|
|
26
|
+
// Same routing identity — same name, same schema.
|
|
27
|
+
expect(exposed.name).toBe(original.name);
|
|
28
|
+
expect(exposed.schema).toBe(original.schema);
|
|
29
|
+
});
|
|
30
|
+
it("event.public() returns a callable clone with $public", () => {
|
|
31
|
+
const exposed = CarWasWashed.public();
|
|
32
|
+
expect(isPublic(CarWasWashed)).toBe(false);
|
|
33
|
+
expect(isPublic(exposed)).toBe(true);
|
|
34
|
+
// Still callable like the original.
|
|
35
|
+
const msg = exposed({ stationId: "s1" });
|
|
36
|
+
expect(msg.eventName).toBe("stations.car-was-washed");
|
|
37
|
+
expect(msg.payload).toEqual({ stationId: "s1" });
|
|
38
|
+
});
|
|
39
|
+
it("workflow.public() works for cross-module visibility", () => {
|
|
40
|
+
const wf = defineWorkflow("audit.on-wash", ({ on }) => {
|
|
41
|
+
on(CarWasWashed, async () => { });
|
|
42
|
+
});
|
|
43
|
+
const exposed = wf.public();
|
|
44
|
+
expect(isPublic(wf)).toBe(false);
|
|
45
|
+
expect(isPublic(exposed)).toBe(true);
|
|
46
|
+
expect(exposed.name).toBe("audit.on-wash");
|
|
47
|
+
});
|
|
48
|
+
it("$public is non-enumerable (JSON.stringify ignores it)", () => {
|
|
49
|
+
const exposed = recordWash.public();
|
|
50
|
+
const json = JSON.parse(JSON.stringify(exposed));
|
|
51
|
+
expect(json.$public).toBeUndefined();
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
//# sourceMappingURL=public-marker.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"public-marker.test.js","sourceRoot":"","sources":["../../src/__tests__/public-marker.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry-dlq.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/retry-dlq.test.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Retry / DLQ contract — handler throws are retried per `RetryPolicy`, then
|
|
3
|
+
* dead-lettered to the configured `DeadLetterSink` after `max + 1` attempts.
|
|
4
|
+
*/
|
|
5
|
+
import { describe, it, expect } from "vitest";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import * as forge from "../foundation.js";
|
|
8
|
+
const { defineAction, defineHandler, Runtime, InMemoryDeadLetterSink } = forge;
|
|
9
|
+
describe("retry + DLQ", () => {
|
|
10
|
+
it("a handler that throws is retried per RetryPolicy and succeeds on a later attempt", async () => {
|
|
11
|
+
const flakyAction = defineAction({
|
|
12
|
+
name: "flaky",
|
|
13
|
+
schema: z.object({}),
|
|
14
|
+
retry: { max: 2, backoff: "fixed", baseDelayMs: 1 },
|
|
15
|
+
});
|
|
16
|
+
let calls = 0;
|
|
17
|
+
const handler = defineHandler(flakyAction, async () => {
|
|
18
|
+
calls++;
|
|
19
|
+
if (calls < 3)
|
|
20
|
+
throw new Error(`flake ${calls}`);
|
|
21
|
+
return undefined;
|
|
22
|
+
});
|
|
23
|
+
const runtime = new Runtime();
|
|
24
|
+
runtime.registerHandler(handler);
|
|
25
|
+
await runtime.dispatch(flakyAction, {});
|
|
26
|
+
expect(calls).toBe(3);
|
|
27
|
+
const dlq = await runtime.getDeadLetterSink().list();
|
|
28
|
+
expect(dlq).toHaveLength(0);
|
|
29
|
+
});
|
|
30
|
+
it("a handler that throws on every attempt dead-letters and re-raises", async () => {
|
|
31
|
+
const dlq = new InMemoryDeadLetterSink();
|
|
32
|
+
const broken = defineAction({
|
|
33
|
+
name: "broken",
|
|
34
|
+
schema: z.object({ id: z.string() }),
|
|
35
|
+
retry: { max: 2, backoff: "fixed", baseDelayMs: 1 },
|
|
36
|
+
});
|
|
37
|
+
const handler = defineHandler(broken, async () => {
|
|
38
|
+
throw new Error("always fails");
|
|
39
|
+
});
|
|
40
|
+
const runtime = new Runtime({ deadLetterSink: dlq });
|
|
41
|
+
runtime.registerHandler(handler);
|
|
42
|
+
await expect(runtime.dispatch(broken, { id: "x-1" })).rejects.toThrow(/always fails/);
|
|
43
|
+
const entries = await dlq.list();
|
|
44
|
+
expect(entries).toHaveLength(1);
|
|
45
|
+
expect(entries[0]?.actionName).toBe("broken");
|
|
46
|
+
expect(entries[0]?.attempts).toBe(3);
|
|
47
|
+
expect(entries[0]?.lastError.message).toBe("always fails");
|
|
48
|
+
expect((entries[0]?.input).id).toBe("x-1");
|
|
49
|
+
});
|
|
50
|
+
it("an action with no retry policy fails immediately on first throw", async () => {
|
|
51
|
+
const dlq = new InMemoryDeadLetterSink();
|
|
52
|
+
const action = defineAction({
|
|
53
|
+
name: "noretry",
|
|
54
|
+
schema: z.object({}),
|
|
55
|
+
// no retry → maxAttempts = 1
|
|
56
|
+
});
|
|
57
|
+
const handler = defineHandler(action, async () => {
|
|
58
|
+
throw new Error("boom");
|
|
59
|
+
});
|
|
60
|
+
const runtime = new Runtime({ deadLetterSink: dlq });
|
|
61
|
+
runtime.registerHandler(handler);
|
|
62
|
+
await expect(runtime.dispatch(action, {})).rejects.toThrow(/boom/);
|
|
63
|
+
const entries = await dlq.list();
|
|
64
|
+
expect(entries).toHaveLength(1);
|
|
65
|
+
expect(entries[0]?.attempts).toBe(1);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
//# sourceMappingURL=retry-dlq.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry-dlq.test.js","sourceRoot":"","sources":["../../src/__tests__/retry-dlq.test.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,KAAK,MAAM,eAAe,CAAC;AAEvC,MAAM,EAAE,YAAY,EAAE,aAAa,EAAE,OAAO,EAAE,sBAAsB,EAAE,GAAG,KAAK,CAAC;AAE/E,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;IAC3B,EAAE,CAAC,kFAAkF,EAAE,KAAK,IAAI,EAAE;QAChG,MAAM,WAAW,GAAG,YAAY,CAAC;YAC/B,IAAI,EAAE,OAAO;YACb,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;YACpB,KAAK,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC,EAAE;SACpD,CAAC,CAAC;QAEH,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,MAAM,OAAO,GAAG,aAAa,CAAC,WAAW,EAAE,KAAK,IAAI,EAAE;YACpD,KAAK,EAAE,CAAC;YACR,IAAI,KAAK,GAAG,CAAC;gBAAE,MAAM,IAAI,KAAK,CAAC,SAAS,KAAK,EAAE,CAAC,CAAC;YACjD,OAAO,SAAS,CAAC;QACnB,CAAC,CAAC,CAAC;QAEH,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;QAC9B,OAAO,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;QAEjC,MAAM,OAAO,CAAC,QAAQ,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;QACxC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACtB,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,iBAAiB,EAAE,CAAC,IAAI,EAAE,CAAC;QACrD,MAAM,CAAC,GAAG,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAC9B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mEAAmE,EAAE,KAAK,IAAI,EAAE;QACjF,MAAM,GAAG,GAAG,IAAI,sBAAsB,EAAE,CAAC;QACzC,MAAM,MAAM,GAAG,YAAY,CAAC;YAC1B,IAAI,EAAE,QAAQ;YACd,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC;YACpC,KAAK,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC,EAAE;SACpD,CAAC,CAAC;QAEH,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,EAAE,KAAK,IAAI,EAAE;YAC/C,MAAM,IAAI,KAAK,CAAC,cAAc,CAAC,CAAC;QAClC,CAAC,CAAC,CAAC;QAEH,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,EAAE,cAAc,EAAE,GAAG,EAAE,CAAC,CAAC;QACrD,OAAO,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;QAEjC,MAAM,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;QAEtF,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QACjC,MAAM,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAChC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC9C,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACrC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAC3D,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,KAAwB,CAAA,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC/D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iEAAiE,EAAE,KAAK,IAAI,EAAE;QAC/E,MAAM,GAAG,GAAG,IAAI,sBAAsB,EAAE,CAAC;QACzC,MAAM,MAAM,GAAG,YAAY,CAAC;YAC1B,IAAI,EAAE,SAAS;YACf,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;YACpB,6BAA6B;SAC9B,CAAC,CAAC;QACH,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,EAAE,KAAK,IAAI,EAAE;YAC/C,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC;QAC1B,CAAC,CAAC,CAAC;QAEH,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,EAAE,cAAc,EAAE,GAAG,EAAE,CAAC,CAAC;QACrD,OAAO,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;QAEjC,MAAM,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACnE,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QACjC,MAAM,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAChC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/validate.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* validate(input, [preds]) — invariant helper.
|
|
3
|
+
*/
|
|
4
|
+
import { describe, it, expect } from "vitest";
|
|
5
|
+
import { InvariantError, validate, isInvariantError } from "../validate.js";
|
|
6
|
+
describe("validate", () => {
|
|
7
|
+
const hasName = (input) => (input.name?.length ?? 0) > 0 || "Name required";
|
|
8
|
+
const hasEmail = (input) => (input.email?.includes("@") ?? false) || "Email must contain @";
|
|
9
|
+
it("passes silently when all invariants hold", () => {
|
|
10
|
+
expect(() => validate({ name: "Alice", email: "alice@example.com" }, [hasName, hasEmail])).not.toThrow();
|
|
11
|
+
});
|
|
12
|
+
it("throws InvariantError with the failing message", () => {
|
|
13
|
+
try {
|
|
14
|
+
validate({ name: "", email: "alice@example.com" }, [hasName, hasEmail]);
|
|
15
|
+
expect.fail("should have thrown");
|
|
16
|
+
}
|
|
17
|
+
catch (e) {
|
|
18
|
+
expect(isInvariantError(e)).toBe(true);
|
|
19
|
+
expect(e.failures).toEqual(["Name required"]);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
it("aggregates every failure into one throw", () => {
|
|
23
|
+
try {
|
|
24
|
+
validate({ name: "", email: "not-an-email" }, [hasName, hasEmail]);
|
|
25
|
+
expect.fail("should have thrown");
|
|
26
|
+
}
|
|
27
|
+
catch (e) {
|
|
28
|
+
expect(isInvariantError(e)).toBe(true);
|
|
29
|
+
expect(e.failures).toEqual(["Name required", "Email must contain @"]);
|
|
30
|
+
expect(e.message).toContain("Name required");
|
|
31
|
+
expect(e.message).toContain("Email must contain @");
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
it("predicates can read actor state via the second arg", () => {
|
|
35
|
+
const isModerator = (_input, s) => s.isModerator || "Must be moderator";
|
|
36
|
+
expect(() => validate({}, [isModerator], { isModerator: true })).not.toThrow();
|
|
37
|
+
expect(() => validate({}, [isModerator], { isModerator: false })).toThrow(InvariantError);
|
|
38
|
+
});
|
|
39
|
+
it("supports an empty predicate list (no-op)", () => {
|
|
40
|
+
expect(() => validate({ foo: 1 }, [])).not.toThrow();
|
|
41
|
+
});
|
|
42
|
+
it("falls back to a generic message when a predicate returns non-string falsy", () => {
|
|
43
|
+
const badPred = () => false;
|
|
44
|
+
try {
|
|
45
|
+
validate({}, [badPred]);
|
|
46
|
+
expect.fail("should have thrown");
|
|
47
|
+
}
|
|
48
|
+
catch (e) {
|
|
49
|
+
expect(e.failures).toEqual(["invariant failed"]);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
//# sourceMappingURL=validate.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.test.js","sourceRoot":"","sources":["../../src/__tests__/validate.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
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
|
+
export {};
|
|
7
|
+
//# sourceMappingURL=workflow-saga.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflow-saga.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/workflow-saga.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
|