convex-effect-workflows 0.1.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/README.md +107 -0
- package/dist/client/ConvexCtx.d.ts +12 -0
- package/dist/client/ConvexCtx.d.ts.map +1 -0
- package/dist/client/ConvexCtx.js +6 -0
- package/dist/client/ConvexCtx.js.map +1 -0
- package/dist/client/ConvexLogger.d.ts +7 -0
- package/dist/client/ConvexLogger.d.ts.map +1 -0
- package/dist/client/ConvexLogger.js +39 -0
- package/dist/client/ConvexLogger.js.map +1 -0
- package/dist/client/ConvexTracer.d.ts +7 -0
- package/dist/client/ConvexTracer.d.ts.map +1 -0
- package/dist/client/ConvexTracer.js +60 -0
- package/dist/client/ConvexTracer.js.map +1 -0
- package/dist/client/ConvexWorkflowEngine.d.ts +308 -0
- package/dist/client/ConvexWorkflowEngine.d.ts.map +1 -0
- package/dist/client/ConvexWorkflowEngine.js +88 -0
- package/dist/client/ConvexWorkflowEngine.js.map +1 -0
- package/dist/client/activityWorker.d.ts +23 -0
- package/dist/client/activityWorker.d.ts.map +1 -0
- package/dist/client/activityWorker.js +41 -0
- package/dist/client/activityWorker.js.map +1 -0
- package/dist/client/boundaries.d.ts +27 -0
- package/dist/client/boundaries.d.ts.map +1 -0
- package/dist/client/boundaries.js +17 -0
- package/dist/client/boundaries.js.map +1 -0
- package/dist/client/encoded.d.ts +22 -0
- package/dist/client/encoded.d.ts.map +1 -0
- package/dist/client/encoded.js +276 -0
- package/dist/client/encoded.js.map +1 -0
- package/dist/client/index.d.ts +13 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +11 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/registry.d.ts +17 -0
- package/dist/client/registry.d.ts.map +1 -0
- package/dist/client/registry.js +21 -0
- package/dist/client/registry.js.map +1 -0
- package/dist/client/runner.d.ts +27 -0
- package/dist/client/runner.d.ts.map +1 -0
- package/dist/client/runner.js +90 -0
- package/dist/client/runner.js.map +1 -0
- package/dist/client/runtime.d.ts +10 -0
- package/dist/client/runtime.d.ts.map +1 -0
- package/dist/client/runtime.js +15 -0
- package/dist/client/runtime.js.map +1 -0
- package/dist/component/_generated/api.d.ts +148 -0
- package/dist/component/_generated/api.d.ts.map +1 -0
- package/dist/component/_generated/api.js +31 -0
- package/dist/component/_generated/api.js.map +1 -0
- package/dist/component/_generated/component.d.ts +921 -0
- package/dist/component/_generated/component.d.ts.map +1 -0
- package/dist/component/_generated/component.js +11 -0
- package/dist/component/_generated/component.js.map +1 -0
- package/dist/component/_generated/dataModel.d.ts +46 -0
- package/dist/component/_generated/dataModel.d.ts.map +1 -0
- package/dist/component/_generated/dataModel.js +11 -0
- package/dist/component/_generated/dataModel.js.map +1 -0
- package/dist/component/_generated/server.d.ts +121 -0
- package/dist/component/_generated/server.d.ts.map +1 -0
- package/dist/component/_generated/server.js +78 -0
- package/dist/component/_generated/server.js.map +1 -0
- package/dist/component/activityCompletions.d.ts +27 -0
- package/dist/component/activityCompletions.d.ts.map +1 -0
- package/dist/component/activityCompletions.js +70 -0
- package/dist/component/activityCompletions.js.map +1 -0
- package/dist/component/boundaries.d.ts +20 -0
- package/dist/component/boundaries.d.ts.map +1 -0
- package/dist/component/boundaries.js +17 -0
- package/dist/component/boundaries.js.map +1 -0
- package/dist/component/cleanup.d.ts +11 -0
- package/dist/component/cleanup.d.ts.map +1 -0
- package/dist/component/cleanup.js +163 -0
- package/dist/component/cleanup.js.map +1 -0
- package/dist/component/clocks.d.ts +12 -0
- package/dist/component/clocks.d.ts.map +1 -0
- package/dist/component/clocks.js +26 -0
- package/dist/component/clocks.js.map +1 -0
- package/dist/component/config.d.ts +25 -0
- package/dist/component/config.d.ts.map +1 -0
- package/dist/component/config.js +110 -0
- package/dist/component/config.js.map +1 -0
- package/dist/component/convex.config.d.ts +3 -0
- package/dist/component/convex.config.d.ts.map +1 -0
- package/dist/component/convex.config.js +6 -0
- package/dist/component/convex.config.js.map +1 -0
- package/dist/component/dashboard.d.ts +268 -0
- package/dist/component/dashboard.d.ts.map +1 -0
- package/dist/component/dashboard.js +622 -0
- package/dist/component/dashboard.js.map +1 -0
- package/dist/component/deferreds.d.ts +31 -0
- package/dist/component/deferreds.d.ts.map +1 -0
- package/dist/component/deferreds.js +138 -0
- package/dist/component/deferreds.js.map +1 -0
- package/dist/component/executions.d.ts +77 -0
- package/dist/component/executions.d.ts.map +1 -0
- package/dist/component/executions.js +186 -0
- package/dist/component/executions.js.map +1 -0
- package/dist/component/journalSteps.d.ts +261 -0
- package/dist/component/journalSteps.d.ts.map +1 -0
- package/dist/component/journalSteps.js +203 -0
- package/dist/component/journalSteps.js.map +1 -0
- package/dist/component/logs.d.ts +68 -0
- package/dist/component/logs.d.ts.map +1 -0
- package/dist/component/logs.js +123 -0
- package/dist/component/logs.js.map +1 -0
- package/dist/component/onComplete.d.ts +31 -0
- package/dist/component/onComplete.d.ts.map +1 -0
- package/dist/component/onComplete.js +146 -0
- package/dist/component/onComplete.js.map +1 -0
- package/dist/component/payloads.d.ts +26 -0
- package/dist/component/payloads.d.ts.map +1 -0
- package/dist/component/payloads.js +57 -0
- package/dist/component/payloads.js.map +1 -0
- package/dist/component/queries.d.ts +2 -0
- package/dist/component/queries.d.ts.map +1 -0
- package/dist/component/queries.js +2 -0
- package/dist/component/queries.js.map +1 -0
- package/dist/component/runner.d.ts +31 -0
- package/dist/component/runner.d.ts.map +1 -0
- package/dist/component/runner.js +87 -0
- package/dist/component/runner.js.map +1 -0
- package/dist/component/schema.d.ts +282 -0
- package/dist/component/schema.d.ts.map +1 -0
- package/dist/component/schema.js +119 -0
- package/dist/component/schema.js.map +1 -0
- package/dist/component/spans.d.ts +105 -0
- package/dist/component/spans.d.ts.map +1 -0
- package/dist/component/spans.js +190 -0
- package/dist/component/spans.js.map +1 -0
- package/dist/component/utils.d.ts +15 -0
- package/dist/component/utils.d.ts.map +1 -0
- package/dist/component/utils.js +53 -0
- package/dist/component/utils.js.map +1 -0
- package/dist/shared/constants.d.ts +12 -0
- package/dist/shared/constants.d.ts.map +1 -0
- package/dist/shared/constants.js +12 -0
- package/dist/shared/constants.js.map +1 -0
- package/dist/shared/validators.d.ts +69 -0
- package/dist/shared/validators.d.ts.map +1 -0
- package/dist/shared/validators.js +30 -0
- package/dist/shared/validators.js.map +1 -0
- package/package.json +74 -0
- package/src/client/ConvexCtx.ts +21 -0
- package/src/client/ConvexLogger.ts +52 -0
- package/src/client/ConvexTracer.ts +75 -0
- package/src/client/ConvexWorkflowEngine.test.ts +124 -0
- package/src/client/ConvexWorkflowEngine.ts +209 -0
- package/src/client/activityWorker.ts +62 -0
- package/src/client/boundaries.test.ts +83 -0
- package/src/client/boundaries.ts +79 -0
- package/src/client/encoded.lifecycle.test.ts +336 -0
- package/src/client/encoded.test.ts +153 -0
- package/src/client/encoded.ts +484 -0
- package/src/client/index.ts +47 -0
- package/src/client/registry.ts +35 -0
- package/src/client/runner.ts +165 -0
- package/src/client/runtime.ts +30 -0
- package/src/component/_generated/api.ts +179 -0
- package/src/component/_generated/component.ts +1216 -0
- package/src/component/_generated/dataModel.ts +60 -0
- package/src/component/_generated/server.ts +156 -0
- package/src/component/activityCompletions.ts +73 -0
- package/src/component/boundaries.ts +55 -0
- package/src/component/cleanup.test.ts +219 -0
- package/src/component/cleanup.ts +218 -0
- package/src/component/clocks.ts +26 -0
- package/src/component/config.test.ts +159 -0
- package/src/component/config.ts +145 -0
- package/src/component/convex.config.ts +7 -0
- package/src/component/core.test.ts +829 -0
- package/src/component/dashboard.scaling.test.ts +268 -0
- package/src/component/dashboard.ts +743 -0
- package/src/component/deferreds.ts +162 -0
- package/src/component/executions.ts +225 -0
- package/src/component/journalSteps.ts +252 -0
- package/src/component/logs.ts +152 -0
- package/src/component/onComplete.ts +170 -0
- package/src/component/payloads.ts +83 -0
- package/src/component/queries.ts +8 -0
- package/src/component/runner.ts +122 -0
- package/src/component/schema.ts +155 -0
- package/src/component/setup.test.ts +15 -0
- package/src/component/spans.ts +241 -0
- package/src/component/utils.test.ts +32 -0
- package/src/component/utils.ts +73 -0
- package/src/shared/constants.test.ts +14 -0
- package/src/shared/constants.ts +15 -0
- package/src/shared/validators.ts +98 -0
- package/src/test.d.ts +8 -0
- package/src/test.ts +17 -0
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
|
2
|
+
|
|
3
|
+
import { describe, expect, test } from "vitest";
|
|
4
|
+
import * as Effect from "effect/Effect";
|
|
5
|
+
import * as Schema from "effect/Schema";
|
|
6
|
+
import * as Workflow from "@effect/workflow/Workflow";
|
|
7
|
+
import {
|
|
8
|
+
makeUnsafe as makeUnsafeWorkflowEngine,
|
|
9
|
+
WorkflowEngine,
|
|
10
|
+
WorkflowInstance,
|
|
11
|
+
} from "@effect/workflow/WorkflowEngine";
|
|
12
|
+
import { api } from "../component/_generated/api.js";
|
|
13
|
+
import { initConvexTest } from "../component/setup.test.js";
|
|
14
|
+
import { makeConvexEncoded } from "./encoded.js";
|
|
15
|
+
import { ConvexCtx } from "./ConvexCtx.js";
|
|
16
|
+
|
|
17
|
+
describe("encoded lifecycle", () => {
|
|
18
|
+
test("activity step suspends then replays to completion", async () => {
|
|
19
|
+
const t = initConvexTest();
|
|
20
|
+
|
|
21
|
+
const ctx = {
|
|
22
|
+
runMutation: async (fn: any, args: any) => t.mutation(fn, args),
|
|
23
|
+
runQuery: async (fn: any, args: any) => t.query(fn, args),
|
|
24
|
+
scheduler: {
|
|
25
|
+
runAfter: async () => "scheduled" as any,
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const enqueued: Array<{ stepNumber: number; attempt: number }> = [];
|
|
30
|
+
|
|
31
|
+
const encoded = makeConvexEncoded(api as any, {
|
|
32
|
+
enqueueRunner: async () => {},
|
|
33
|
+
enqueueActivity: async (_ctx, args) => {
|
|
34
|
+
enqueued.push({ stepNumber: args.stepNumber, attempt: args.attempt });
|
|
35
|
+
return `work-${args.stepNumber}`;
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
const engine = makeUnsafeWorkflowEngine(encoded);
|
|
39
|
+
|
|
40
|
+
const workflow = Workflow.make({
|
|
41
|
+
name: "encodedLifecycle",
|
|
42
|
+
payload: Schema.Struct({ id: Schema.String }),
|
|
43
|
+
success: Schema.String,
|
|
44
|
+
error: Schema.String,
|
|
45
|
+
idempotencyKey: (payload) => payload.id,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const activity = { name: "testActivity" } as any;
|
|
49
|
+
|
|
50
|
+
const execute = (_payload: object, _executionId: string) =>
|
|
51
|
+
Effect.gen(function*() {
|
|
52
|
+
const wfEngine = yield* WorkflowEngine;
|
|
53
|
+
const result = yield* wfEngine.activityExecute(activity, 1);
|
|
54
|
+
if (result._tag === "Suspended") {
|
|
55
|
+
const instance = yield* WorkflowInstance;
|
|
56
|
+
return yield* Workflow.suspend(instance);
|
|
57
|
+
}
|
|
58
|
+
return yield* result.exit;
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const first = await Effect.runPromise(
|
|
62
|
+
Effect.scoped(
|
|
63
|
+
Effect.provideService(
|
|
64
|
+
Effect.provideService(
|
|
65
|
+
Effect.gen(function*() {
|
|
66
|
+
yield* encoded.register(workflow as any, execute as any);
|
|
67
|
+
return yield* encoded.execute(workflow as any, {
|
|
68
|
+
executionId: "exec-encoded-lifecycle",
|
|
69
|
+
payload: { id: "1" },
|
|
70
|
+
discard: false,
|
|
71
|
+
parent: undefined,
|
|
72
|
+
});
|
|
73
|
+
}) as any,
|
|
74
|
+
WorkflowEngine,
|
|
75
|
+
engine,
|
|
76
|
+
),
|
|
77
|
+
ConvexCtx,
|
|
78
|
+
ctx as any,
|
|
79
|
+
),
|
|
80
|
+
),
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
expect((first as any)._tag).toBe("Suspended");
|
|
84
|
+
expect(enqueued).toEqual([{ stepNumber: 0, attempt: 1 }]);
|
|
85
|
+
|
|
86
|
+
const step = await t.query(api.journalSteps.getJournalStep, {
|
|
87
|
+
executionId: "exec-encoded-lifecycle",
|
|
88
|
+
stepNumber: 0,
|
|
89
|
+
});
|
|
90
|
+
expect(step).toBeTruthy();
|
|
91
|
+
expect(step!.state).toBe("suspended");
|
|
92
|
+
|
|
93
|
+
await t.mutation(api.journalSteps.completeJournalStep, {
|
|
94
|
+
executionId: "exec-encoded-lifecycle",
|
|
95
|
+
stepNumber: 0,
|
|
96
|
+
runResult: {
|
|
97
|
+
kind: "success",
|
|
98
|
+
valuePreview: "done",
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const second = await Effect.runPromise(
|
|
103
|
+
Effect.scoped(
|
|
104
|
+
Effect.provideService(
|
|
105
|
+
Effect.provideService(
|
|
106
|
+
encoded.execute(workflow as any, {
|
|
107
|
+
executionId: "exec-encoded-lifecycle",
|
|
108
|
+
payload: { id: "1" },
|
|
109
|
+
discard: false,
|
|
110
|
+
parent: undefined,
|
|
111
|
+
}) as any,
|
|
112
|
+
WorkflowEngine,
|
|
113
|
+
engine,
|
|
114
|
+
),
|
|
115
|
+
ConvexCtx,
|
|
116
|
+
ctx as any,
|
|
117
|
+
),
|
|
118
|
+
),
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
expect((second as any)._tag).toBe("Complete");
|
|
122
|
+
expect(enqueued).toEqual([{ stepNumber: 0, attempt: 1 }]);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("replay ignores activity attempt differences for completed steps", async () => {
|
|
126
|
+
const t = initConvexTest();
|
|
127
|
+
|
|
128
|
+
const ctx = {
|
|
129
|
+
runMutation: async (fn: any, args: any) => t.mutation(fn, args),
|
|
130
|
+
runQuery: async (fn: any, args: any) => t.query(fn, args),
|
|
131
|
+
scheduler: {
|
|
132
|
+
runAfter: async () => "scheduled" as any,
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const enqueued: Array<{ stepNumber: number; attempt: number }> = [];
|
|
137
|
+
|
|
138
|
+
const encoded = makeConvexEncoded(api as any, {
|
|
139
|
+
enqueueRunner: async () => {},
|
|
140
|
+
enqueueActivity: async (_ctx, args) => {
|
|
141
|
+
enqueued.push({ stepNumber: args.stepNumber, attempt: args.attempt });
|
|
142
|
+
return `work-${args.stepNumber}`;
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
const engine = makeUnsafeWorkflowEngine(encoded);
|
|
146
|
+
|
|
147
|
+
const workflow = Workflow.make({
|
|
148
|
+
name: "encodedAttemptReplay",
|
|
149
|
+
payload: Schema.Struct({ id: Schema.String }),
|
|
150
|
+
success: Schema.String,
|
|
151
|
+
error: Schema.String,
|
|
152
|
+
idempotencyKey: (payload) => payload.id,
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const activity = { name: "stableActivity" } as any;
|
|
156
|
+
|
|
157
|
+
const firstExecute = (_payload: object, _executionId: string) =>
|
|
158
|
+
Effect.gen(function*() {
|
|
159
|
+
const wfEngine = yield* WorkflowEngine;
|
|
160
|
+
const result = yield* wfEngine.activityExecute(activity, 1);
|
|
161
|
+
if (result._tag === "Suspended") {
|
|
162
|
+
const instance = yield* WorkflowInstance;
|
|
163
|
+
return yield* Workflow.suspend(instance);
|
|
164
|
+
}
|
|
165
|
+
return yield* result.exit;
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
await Effect.runPromise(
|
|
169
|
+
Effect.scoped(
|
|
170
|
+
Effect.provideService(
|
|
171
|
+
Effect.provideService(
|
|
172
|
+
Effect.gen(function*() {
|
|
173
|
+
yield* encoded.register(workflow as any, firstExecute as any);
|
|
174
|
+
return yield* encoded.execute(workflow as any, {
|
|
175
|
+
executionId: "exec-encoded-attempt-replay",
|
|
176
|
+
payload: { id: "1" },
|
|
177
|
+
discard: false,
|
|
178
|
+
parent: undefined,
|
|
179
|
+
});
|
|
180
|
+
}) as any,
|
|
181
|
+
WorkflowEngine,
|
|
182
|
+
engine,
|
|
183
|
+
),
|
|
184
|
+
ConvexCtx,
|
|
185
|
+
ctx as any,
|
|
186
|
+
),
|
|
187
|
+
),
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
await t.mutation(api.journalSteps.completeJournalStep, {
|
|
191
|
+
executionId: "exec-encoded-attempt-replay",
|
|
192
|
+
stepNumber: 0,
|
|
193
|
+
runResult: {
|
|
194
|
+
kind: "success",
|
|
195
|
+
valuePreview: "done",
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
const secondExecute = (_payload: object, _executionId: string) =>
|
|
200
|
+
Effect.gen(function*() {
|
|
201
|
+
const wfEngine = yield* WorkflowEngine;
|
|
202
|
+
// Attempt value changed from 1 -> 2; replay should still match.
|
|
203
|
+
const result = yield* wfEngine.activityExecute(activity, 2);
|
|
204
|
+
if (result._tag === "Suspended") {
|
|
205
|
+
const instance = yield* WorkflowInstance;
|
|
206
|
+
return yield* Workflow.suspend(instance);
|
|
207
|
+
}
|
|
208
|
+
return yield* result.exit;
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
const replay = await Effect.runPromise(
|
|
212
|
+
Effect.scoped(
|
|
213
|
+
Effect.provideService(
|
|
214
|
+
Effect.provideService(
|
|
215
|
+
Effect.gen(function*() {
|
|
216
|
+
yield* encoded.register(workflow as any, secondExecute as any);
|
|
217
|
+
return yield* encoded.execute(workflow as any, {
|
|
218
|
+
executionId: "exec-encoded-attempt-replay",
|
|
219
|
+
payload: { id: "1" },
|
|
220
|
+
discard: false,
|
|
221
|
+
parent: undefined,
|
|
222
|
+
});
|
|
223
|
+
}) as any,
|
|
224
|
+
WorkflowEngine,
|
|
225
|
+
engine,
|
|
226
|
+
),
|
|
227
|
+
ConvexCtx,
|
|
228
|
+
ctx as any,
|
|
229
|
+
),
|
|
230
|
+
),
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
expect((replay as any)._tag).toBe("Complete");
|
|
234
|
+
expect(JSON.stringify(replay)).not.toContain("Determinism mismatch");
|
|
235
|
+
expect(enqueued).toHaveLength(1);
|
|
236
|
+
expect(enqueued[0]).toEqual({ stepNumber: 0, attempt: 1 });
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
test("determinism mismatch returns terminal failure result", async () => {
|
|
240
|
+
const t = initConvexTest();
|
|
241
|
+
|
|
242
|
+
const ctx = {
|
|
243
|
+
runMutation: async (fn: any, args: any) => t.mutation(fn, args),
|
|
244
|
+
runQuery: async (fn: any, args: any) => t.query(fn, args),
|
|
245
|
+
scheduler: {
|
|
246
|
+
runAfter: async () => "scheduled" as any,
|
|
247
|
+
},
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
const encoded = makeConvexEncoded(api as any, {
|
|
251
|
+
enqueueRunner: async () => {},
|
|
252
|
+
enqueueActivity: async () => "work-0",
|
|
253
|
+
});
|
|
254
|
+
const engine = makeUnsafeWorkflowEngine(encoded);
|
|
255
|
+
|
|
256
|
+
const workflow = Workflow.make({
|
|
257
|
+
name: "encodedDeterminism",
|
|
258
|
+
payload: Schema.Struct({ id: Schema.String }),
|
|
259
|
+
success: Schema.String,
|
|
260
|
+
error: Schema.String,
|
|
261
|
+
idempotencyKey: (payload) => payload.id,
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
const activityA = { name: "activityA" } as any;
|
|
265
|
+
const activityB = { name: "activityB" } as any;
|
|
266
|
+
|
|
267
|
+
const firstExecute = (_payload: object, _executionId: string) =>
|
|
268
|
+
Effect.gen(function*() {
|
|
269
|
+
const wfEngine = yield* WorkflowEngine;
|
|
270
|
+
const result = yield* wfEngine.activityExecute(activityA, 1);
|
|
271
|
+
if (result._tag === "Suspended") {
|
|
272
|
+
const instance = yield* WorkflowInstance;
|
|
273
|
+
return yield* Workflow.suspend(instance);
|
|
274
|
+
}
|
|
275
|
+
return yield* result.exit;
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
await Effect.runPromise(
|
|
279
|
+
Effect.scoped(
|
|
280
|
+
Effect.provideService(
|
|
281
|
+
Effect.provideService(
|
|
282
|
+
Effect.gen(function*() {
|
|
283
|
+
yield* encoded.register(workflow as any, firstExecute as any);
|
|
284
|
+
return yield* encoded.execute(workflow as any, {
|
|
285
|
+
executionId: "exec-encoded-determinism",
|
|
286
|
+
payload: { id: "1" },
|
|
287
|
+
discard: false,
|
|
288
|
+
parent: undefined,
|
|
289
|
+
});
|
|
290
|
+
}) as any,
|
|
291
|
+
WorkflowEngine,
|
|
292
|
+
engine,
|
|
293
|
+
),
|
|
294
|
+
ConvexCtx,
|
|
295
|
+
ctx as any,
|
|
296
|
+
),
|
|
297
|
+
),
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
const mismatchedExecute = (_payload: object, _executionId: string) =>
|
|
301
|
+
Effect.gen(function*() {
|
|
302
|
+
const wfEngine = yield* WorkflowEngine;
|
|
303
|
+
const result = yield* wfEngine.activityExecute(activityB, 1);
|
|
304
|
+
if (result._tag === "Suspended") {
|
|
305
|
+
const instance = yield* WorkflowInstance;
|
|
306
|
+
return yield* Workflow.suspend(instance);
|
|
307
|
+
}
|
|
308
|
+
return yield* result.exit;
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
const mismatchResult = await Effect.runPromise(
|
|
312
|
+
Effect.scoped(
|
|
313
|
+
Effect.provideService(
|
|
314
|
+
Effect.provideService(
|
|
315
|
+
Effect.gen(function*() {
|
|
316
|
+
yield* encoded.register(workflow as any, mismatchedExecute as any);
|
|
317
|
+
return yield* encoded.execute(workflow as any, {
|
|
318
|
+
executionId: "exec-encoded-determinism",
|
|
319
|
+
payload: { id: "1" },
|
|
320
|
+
discard: false,
|
|
321
|
+
parent: undefined,
|
|
322
|
+
});
|
|
323
|
+
}) as any,
|
|
324
|
+
WorkflowEngine,
|
|
325
|
+
engine,
|
|
326
|
+
),
|
|
327
|
+
ConvexCtx,
|
|
328
|
+
ctx as any,
|
|
329
|
+
),
|
|
330
|
+
),
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
expect((mismatchResult as any)._tag).toBe("Complete");
|
|
334
|
+
expect((mismatchResult as any).exit._tag).toBe("Failure");
|
|
335
|
+
});
|
|
336
|
+
});
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import * as Workflow from "@effect/workflow/Workflow";
|
|
4
|
+
import * as Schema from "effect/Schema";
|
|
5
|
+
import * as DurableClock from "@effect/workflow/DurableClock";
|
|
6
|
+
import { makeConvexEncoded } from "./encoded.js";
|
|
7
|
+
import { ConvexCtx } from "./ConvexCtx.js";
|
|
8
|
+
|
|
9
|
+
describe("makeConvexEncoded scheduleClock", () => {
|
|
10
|
+
test("schedules deferred completion wake", async () => {
|
|
11
|
+
const deferredDoneRef = Symbol("deferredDone");
|
|
12
|
+
const getExecutionRef = Symbol("getExecution");
|
|
13
|
+
|
|
14
|
+
const scheduled: Array<{ delay: number; fn: unknown; args: unknown }> = [];
|
|
15
|
+
|
|
16
|
+
const ctx = {
|
|
17
|
+
runQuery: async (fn: unknown, args: any) => {
|
|
18
|
+
if (fn === getExecutionRef) {
|
|
19
|
+
return {
|
|
20
|
+
executionId: args.executionId,
|
|
21
|
+
generation: 3,
|
|
22
|
+
status: "running",
|
|
23
|
+
runnerHandle: "function://runner",
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
throw new Error("unexpected query");
|
|
27
|
+
},
|
|
28
|
+
runMutation: async () => {
|
|
29
|
+
throw new Error("unexpected mutation");
|
|
30
|
+
},
|
|
31
|
+
scheduler: {
|
|
32
|
+
runAfter: async (delay: number, fn: unknown, args: unknown) => {
|
|
33
|
+
scheduled.push({ delay, fn, args });
|
|
34
|
+
return "scheduled-id" as any;
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const component = {
|
|
40
|
+
executions: {
|
|
41
|
+
getExecution: getExecutionRef,
|
|
42
|
+
},
|
|
43
|
+
deferreds: {
|
|
44
|
+
deferredDone: deferredDoneRef,
|
|
45
|
+
},
|
|
46
|
+
} as any;
|
|
47
|
+
|
|
48
|
+
const encoded = makeConvexEncoded(component, {
|
|
49
|
+
enqueueRunner: async () => {},
|
|
50
|
+
enqueueActivity: async () => "work-id",
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const workflow = Workflow.make({
|
|
54
|
+
name: "clockWorkflow",
|
|
55
|
+
payload: Schema.Struct({ id: Schema.String }),
|
|
56
|
+
success: Schema.String,
|
|
57
|
+
error: Schema.String,
|
|
58
|
+
idempotencyKey: (p) => p.id,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const clock = DurableClock.make({
|
|
62
|
+
name: "wait",
|
|
63
|
+
duration: 1000,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
await Effect.runPromise(
|
|
67
|
+
Effect.provideService(
|
|
68
|
+
encoded.scheduleClock(workflow as any, {
|
|
69
|
+
executionId: "exec-1",
|
|
70
|
+
clock,
|
|
71
|
+
}) as any,
|
|
72
|
+
ConvexCtx,
|
|
73
|
+
ctx as any,
|
|
74
|
+
),
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
expect(scheduled).toHaveLength(1);
|
|
78
|
+
expect(scheduled[0]?.delay).toBe(1000);
|
|
79
|
+
expect(scheduled[0]?.fn).toBe(deferredDoneRef);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("schedules only deferred completion when no runnerHandle", async () => {
|
|
83
|
+
const deferredDoneRef = Symbol("deferredDone");
|
|
84
|
+
const getExecutionRef = Symbol("getExecution");
|
|
85
|
+
|
|
86
|
+
const scheduled: Array<{ delay: number; fn: unknown; args: unknown }> = [];
|
|
87
|
+
|
|
88
|
+
const ctx = {
|
|
89
|
+
runQuery: async (fn: unknown, args: any) => {
|
|
90
|
+
if (fn === getExecutionRef) {
|
|
91
|
+
return {
|
|
92
|
+
executionId: args.executionId,
|
|
93
|
+
generation: 1,
|
|
94
|
+
status: "running",
|
|
95
|
+
runnerHandle: undefined,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
throw new Error("unexpected query");
|
|
99
|
+
},
|
|
100
|
+
runMutation: async () => {
|
|
101
|
+
throw new Error("unexpected mutation");
|
|
102
|
+
},
|
|
103
|
+
scheduler: {
|
|
104
|
+
runAfter: async (delay: number, fn: unknown, args: unknown) => {
|
|
105
|
+
scheduled.push({ delay, fn, args });
|
|
106
|
+
return "scheduled-id" as any;
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const component = {
|
|
112
|
+
executions: {
|
|
113
|
+
getExecution: getExecutionRef,
|
|
114
|
+
},
|
|
115
|
+
deferreds: {
|
|
116
|
+
deferredDone: deferredDoneRef,
|
|
117
|
+
},
|
|
118
|
+
} as any;
|
|
119
|
+
|
|
120
|
+
const encoded = makeConvexEncoded(component, {
|
|
121
|
+
enqueueRunner: async () => {},
|
|
122
|
+
enqueueActivity: async () => "work-id",
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const workflow = Workflow.make({
|
|
126
|
+
name: "clockWorkflow",
|
|
127
|
+
payload: Schema.Struct({ id: Schema.String }),
|
|
128
|
+
success: Schema.String,
|
|
129
|
+
error: Schema.String,
|
|
130
|
+
idempotencyKey: (p) => p.id,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const clock = DurableClock.make({
|
|
134
|
+
name: "wait",
|
|
135
|
+
duration: 250,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
await Effect.runPromise(
|
|
139
|
+
Effect.provideService(
|
|
140
|
+
encoded.scheduleClock(workflow as any, {
|
|
141
|
+
executionId: "exec-2",
|
|
142
|
+
clock,
|
|
143
|
+
}) as any,
|
|
144
|
+
ConvexCtx,
|
|
145
|
+
ctx as any,
|
|
146
|
+
),
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
expect(scheduled).toHaveLength(1);
|
|
150
|
+
expect(scheduled[0]?.delay).toBe(250);
|
|
151
|
+
expect(scheduled[0]?.fn).toBe(deferredDoneRef);
|
|
152
|
+
});
|
|
153
|
+
});
|