@effect-app/infra 4.0.0-beta.257 → 4.0.0-beta.259
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/dist/ClusterCosmos.d.ts +64 -0
- package/dist/ClusterCosmos.d.ts.map +1 -0
- package/dist/ClusterCosmos.js +487 -0
- package/dist/WorkflowEngineSqlite.d.ts +24 -0
- package/dist/WorkflowEngineSqlite.d.ts.map +1 -0
- package/dist/WorkflowEngineSqlite.js +550 -0
- package/docs/cluster-storage.md +26 -0
- package/docs/workflow-engine.md +262 -0
- package/package.json +7 -206
- package/run.sh +1 -0
- package/src/ClusterCosmos.ts +954 -0
- package/src/WorkflowEngineCosmos.ts +719 -0
- package/src/WorkflowEngineSqlite.ts +813 -0
- package/test/cluster-cosmos.test.ts +406 -0
- package/test/dist/cluster-cosmos.test.d.ts.map +1 -0
- package/test/dist/workflow-engine-sqlite.test.d.ts.map +1 -0
- package/test/workflow-engine-cosmos.test.ts +354 -0
- package/test/workflow-engine-sqlite.test.ts +299 -0
|
@@ -0,0 +1,550 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQLite backed {@link WorkflowEngine} implementation.
|
|
3
|
+
*
|
|
4
|
+
* Persists workflow state across four tables:
|
|
5
|
+
* - `workflow_exec` — one row per execution; tracks status, lease, etag
|
|
6
|
+
* - `workflow_activity` — recorded activity results keyed by (exec, name, attempt)
|
|
7
|
+
* - `workflow_deferred` — durable deferred completions keyed by (exec, name)
|
|
8
|
+
* - `workflow_clock` — scheduled clocks with `fire_at`
|
|
9
|
+
*
|
|
10
|
+
* Atomicity: multi-statement operations are wrapped in `sql.withTransaction`
|
|
11
|
+
* (BEGIN/COMMIT) so concurrent writers do not observe partial state.
|
|
12
|
+
*
|
|
13
|
+
* Optimistic concurrency:
|
|
14
|
+
* - exec state transitions use `UPDATE ... WHERE etag = ? RETURNING etag`;
|
|
15
|
+
* a zero-row result is an `OptimisticConcurrencyException`.
|
|
16
|
+
* - activity / deferred / clock inserts use `INSERT ... ON CONFLICT DO
|
|
17
|
+
* NOTHING RETURNING ...` for first-writer-wins semantics across drivers.
|
|
18
|
+
*
|
|
19
|
+
* Durability — everything that crosses the storage boundary is round-tripped
|
|
20
|
+
* through schema codecs (`S.fromJsonString(S.toCodecJson(...))`), exactly like
|
|
21
|
+
* the cluster engine:
|
|
22
|
+
*
|
|
23
|
+
* - The workflow payload and the top-level `Workflow.Result` are encoded with
|
|
24
|
+
* the workflow's own `payloadSchema` / `successSchema` / `errorSchema`, so
|
|
25
|
+
* typed values (dates, branded ids, schema classes) survive a restart.
|
|
26
|
+
* - Activity results flow through the engine already encoded, so they are
|
|
27
|
+
* persisted with an opaque `Workflow.Result({ success: AnyOrVoid, error:
|
|
28
|
+
* AnyOrVoid })` codec — same trick the cluster `ActivityRpc` uses.
|
|
29
|
+
* - Durable-deferred exits use an opaque `Exit` codec.
|
|
30
|
+
*
|
|
31
|
+
* Crash recovery: each driver holds a time-bound lease on the exec row,
|
|
32
|
+
* renewed by a heartbeat fiber. A scope-bound recovery poller re-drives any
|
|
33
|
+
* exec whose lease has lapsed. A clock poller fires due clocks even when
|
|
34
|
+
* the in-process timer is missing (e.g. after a restart).
|
|
35
|
+
*/
|
|
36
|
+
import * as Effect from "effect-app/Effect";
|
|
37
|
+
import * as Layer from "effect-app/Layer";
|
|
38
|
+
import * as Option from "effect-app/Option";
|
|
39
|
+
import * as S from "effect-app/Schema";
|
|
40
|
+
import * as Duration from "effect/Duration";
|
|
41
|
+
import * as Exit from "effect/Exit";
|
|
42
|
+
import * as Fiber from "effect/Fiber";
|
|
43
|
+
import * as FiberMap from "effect/FiberMap";
|
|
44
|
+
import * as Schedule from "effect/Schedule";
|
|
45
|
+
import { SqlClient } from "effect/unstable/sql";
|
|
46
|
+
import * as Workflow from "effect/unstable/workflow/Workflow";
|
|
47
|
+
import { makeUnsafe, WorkflowEngine, WorkflowInstance } from "effect/unstable/workflow/WorkflowEngine";
|
|
48
|
+
import { randomUUID } from "node:crypto";
|
|
49
|
+
import { OptimisticConcurrencyException } from "./errors.js";
|
|
50
|
+
import { annotateDb } from "./otel.js";
|
|
51
|
+
const parseExec = (row) => ({
|
|
52
|
+
executionId: row.execution_id,
|
|
53
|
+
workflowName: row.workflow_name,
|
|
54
|
+
payload: row.payload,
|
|
55
|
+
parent: row.parent ?? undefined,
|
|
56
|
+
status: row.status,
|
|
57
|
+
suspended: row.suspended !== 0,
|
|
58
|
+
interrupted: row.interrupted !== 0,
|
|
59
|
+
completedResult: row.completed_result ?? undefined,
|
|
60
|
+
worker: row.worker ?? undefined,
|
|
61
|
+
leaseExpiresAt: row.lease_expires_at ?? undefined,
|
|
62
|
+
etag: row.etag
|
|
63
|
+
});
|
|
64
|
+
// --- Storage codecs ---------------------------------------------------------
|
|
65
|
+
// Values flowing through the engine's activity / deferred boundary are already
|
|
66
|
+
// schema-encoded, so the structure is round-tripped while the payload stays
|
|
67
|
+
// opaque (mirrors the cluster engine's `AnyOrVoid` usage).
|
|
68
|
+
const AnyOrVoid = S.Union([S.Any, S.Void]);
|
|
69
|
+
const ActivityResultCodec = S.fromJsonString(S.toCodecJson(Workflow.Result({ success: AnyOrVoid, error: AnyOrVoid })));
|
|
70
|
+
const DeferredExitCodec = S.fromJsonString(S.toCodecJson(S.Exit(AnyOrVoid, AnyOrVoid, S.Defect)));
|
|
71
|
+
const encodeActivityResult = (r) => Effect.orDie(S.encodeEffect(ActivityResultCodec)(r));
|
|
72
|
+
const decodeActivityResult = (s) => Effect.orDie(S.decodeEffect(ActivityResultCodec)(s));
|
|
73
|
+
const encodeDeferredExit = (e) => Effect.orDie(S.encodeEffect(DeferredExitCodec)(e));
|
|
74
|
+
const decodeDeferredExit = (s) => Effect.orDie(S.decodeEffect(DeferredExitCodec)(s));
|
|
75
|
+
const makeSqliteWorkflowEngine = Effect.fnUntraced(function* (cfg) {
|
|
76
|
+
const sql = yield* SqlClient.SqlClient;
|
|
77
|
+
const scope = yield* Effect.scope;
|
|
78
|
+
const prefix = cfg.prefix ?? "";
|
|
79
|
+
const execTable = `${prefix}workflow_exec`;
|
|
80
|
+
const activityTable = `${prefix}workflow_activity`;
|
|
81
|
+
const deferredTable = `${prefix}workflow_deferred`;
|
|
82
|
+
const clockTable = `${prefix}workflow_clock`;
|
|
83
|
+
const workerId = cfg.workerId ?? randomUUID();
|
|
84
|
+
const leaseTtl = cfg.leaseTtl ?? Duration.seconds(30);
|
|
85
|
+
const heartbeatInterval = cfg.heartbeatInterval ?? Duration.seconds(10);
|
|
86
|
+
const recoveryInterval = cfg.recoveryInterval ?? Duration.seconds(15);
|
|
87
|
+
const clockPollInterval = cfg.clockPollInterval ?? Duration.seconds(5);
|
|
88
|
+
const annotate = (operation, executionId) => annotateDb({
|
|
89
|
+
operation,
|
|
90
|
+
system: "sqlite",
|
|
91
|
+
collection: execTable,
|
|
92
|
+
entity: "workflow",
|
|
93
|
+
extra: executionId !== undefined ? { "app.entity.id": executionId } : undefined
|
|
94
|
+
});
|
|
95
|
+
const exec = (query, params = []) => sql.unsafe(query, params).pipe(Effect.orDie);
|
|
96
|
+
// --- Schema -----------------------------------------------------------
|
|
97
|
+
yield* exec(`CREATE TABLE IF NOT EXISTS "${execTable}" (
|
|
98
|
+
execution_id TEXT PRIMARY KEY,
|
|
99
|
+
workflow_name TEXT NOT NULL,
|
|
100
|
+
payload TEXT NOT NULL,
|
|
101
|
+
parent TEXT,
|
|
102
|
+
status TEXT NOT NULL,
|
|
103
|
+
suspended INTEGER NOT NULL DEFAULT 0,
|
|
104
|
+
interrupted INTEGER NOT NULL DEFAULT 0,
|
|
105
|
+
completed_result TEXT,
|
|
106
|
+
worker TEXT,
|
|
107
|
+
lease_expires_at INTEGER,
|
|
108
|
+
etag TEXT NOT NULL
|
|
109
|
+
)`);
|
|
110
|
+
yield* exec(`CREATE INDEX IF NOT EXISTS "${execTable}_recovery" ON "${execTable}" (status, lease_expires_at)`);
|
|
111
|
+
yield* exec(`CREATE TABLE IF NOT EXISTS "${activityTable}" (
|
|
112
|
+
execution_id TEXT NOT NULL,
|
|
113
|
+
name TEXT NOT NULL,
|
|
114
|
+
attempt INTEGER NOT NULL,
|
|
115
|
+
result TEXT NOT NULL,
|
|
116
|
+
PRIMARY KEY (execution_id, name, attempt)
|
|
117
|
+
)`);
|
|
118
|
+
yield* exec(`CREATE TABLE IF NOT EXISTS "${deferredTable}" (
|
|
119
|
+
execution_id TEXT NOT NULL,
|
|
120
|
+
name TEXT NOT NULL,
|
|
121
|
+
exit TEXT NOT NULL,
|
|
122
|
+
PRIMARY KEY (execution_id, name)
|
|
123
|
+
)`);
|
|
124
|
+
yield* exec(`CREATE TABLE IF NOT EXISTS "${clockTable}" (
|
|
125
|
+
execution_id TEXT NOT NULL,
|
|
126
|
+
name TEXT NOT NULL,
|
|
127
|
+
workflow_name TEXT NOT NULL,
|
|
128
|
+
deferred_name TEXT NOT NULL,
|
|
129
|
+
fire_at INTEGER NOT NULL,
|
|
130
|
+
PRIMARY KEY (execution_id, name)
|
|
131
|
+
)`);
|
|
132
|
+
yield* exec(`CREATE INDEX IF NOT EXISTS "${clockTable}_due" ON "${clockTable}" (fire_at)`);
|
|
133
|
+
const workflows = new Map();
|
|
134
|
+
const locals = new Map();
|
|
135
|
+
const clocks = yield* FiberMap.make();
|
|
136
|
+
// Per-workflow codecs for the typed payload + top-level result. Cached by
|
|
137
|
+
// workflow name; derived from the workflow's own schemas so typed values
|
|
138
|
+
// (dates, branded ids, schema classes) survive the storage round-trip.
|
|
139
|
+
const makePayloadCodec = (workflow) => S.fromJsonString(S.toCodecJson(workflow.payloadSchema));
|
|
140
|
+
const payloadCodecCache = new Map();
|
|
141
|
+
const payloadCodecFor = (workflow) => {
|
|
142
|
+
let c = payloadCodecCache.get(workflow.name);
|
|
143
|
+
if (!c) {
|
|
144
|
+
c = makePayloadCodec(workflow);
|
|
145
|
+
payloadCodecCache.set(workflow.name, c);
|
|
146
|
+
}
|
|
147
|
+
return c;
|
|
148
|
+
};
|
|
149
|
+
const makeResultCodec = (workflow) => S.fromJsonString(S.toCodecJson(Workflow.Result({ success: workflow.successSchema, error: workflow.errorSchema })));
|
|
150
|
+
const resultCodecCache = new Map();
|
|
151
|
+
const resultCodecFor = (workflow) => {
|
|
152
|
+
let c = resultCodecCache.get(workflow.name);
|
|
153
|
+
if (!c) {
|
|
154
|
+
c = makeResultCodec(workflow);
|
|
155
|
+
resultCodecCache.set(workflow.name, c);
|
|
156
|
+
}
|
|
157
|
+
return c;
|
|
158
|
+
};
|
|
159
|
+
const encodePayload = (workflow, payload) => Effect.orDie(S.encodeEffect(payloadCodecFor(workflow))(payload));
|
|
160
|
+
const decodePayload = (workflow, s) => Effect.orDie(S.decodeEffect(payloadCodecFor(workflow))(s));
|
|
161
|
+
const encodeResult = (workflow, r) => Effect.orDie(S.encodeEffect(resultCodecFor(workflow))(r));
|
|
162
|
+
const decodeResult = (workflow, s) => Effect.orDie(S.decodeEffect(resultCodecFor(workflow))(s));
|
|
163
|
+
// --- Core SQL operations ----------------------------------------------
|
|
164
|
+
const readExec = (executionId) => exec(`SELECT * FROM "${execTable}" WHERE execution_id = ?`, [executionId])
|
|
165
|
+
.pipe(Effect.map((rows) => {
|
|
166
|
+
const r = rows[0];
|
|
167
|
+
return r ? Option.some(parseExec(r)) : Option.none();
|
|
168
|
+
}), annotate("readExec", executionId));
|
|
169
|
+
/**
|
|
170
|
+
* OCC-guarded write. Generates a fresh etag on success; returns
|
|
171
|
+
* `OptimisticConcurrencyException` when no row matches the prior etag.
|
|
172
|
+
*/
|
|
173
|
+
const replaceExec = (state, next) => Effect
|
|
174
|
+
.gen(function* () {
|
|
175
|
+
const newEtag = randomUUID();
|
|
176
|
+
const merged = { ...state, ...next, etag: newEtag };
|
|
177
|
+
const rows = yield* exec(`UPDATE "${execTable}"
|
|
178
|
+
SET status = ?,
|
|
179
|
+
suspended = ?,
|
|
180
|
+
interrupted = ?,
|
|
181
|
+
completed_result = ?,
|
|
182
|
+
worker = ?,
|
|
183
|
+
lease_expires_at = ?,
|
|
184
|
+
etag = ?
|
|
185
|
+
WHERE execution_id = ? AND etag = ?
|
|
186
|
+
RETURNING etag`, [
|
|
187
|
+
merged.status,
|
|
188
|
+
merged.suspended ? 1 : 0,
|
|
189
|
+
merged.interrupted ? 1 : 0,
|
|
190
|
+
merged.completedResult ?? null,
|
|
191
|
+
merged.worker ?? null,
|
|
192
|
+
merged.leaseExpiresAt ?? null,
|
|
193
|
+
newEtag,
|
|
194
|
+
state.executionId,
|
|
195
|
+
state.etag
|
|
196
|
+
]);
|
|
197
|
+
if (rows.length === 0) {
|
|
198
|
+
return yield* new OptimisticConcurrencyException({
|
|
199
|
+
type: "workflow.exec",
|
|
200
|
+
id: state.executionId,
|
|
201
|
+
code: 412
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
return merged;
|
|
205
|
+
})
|
|
206
|
+
.pipe(annotate("replaceExec", state.executionId));
|
|
207
|
+
const createExec = (initial) => exec(`INSERT INTO "${execTable}"
|
|
208
|
+
(execution_id, workflow_name, payload, parent, status, suspended, interrupted, etag)
|
|
209
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
210
|
+
ON CONFLICT DO NOTHING
|
|
211
|
+
RETURNING execution_id`, [
|
|
212
|
+
initial.executionId,
|
|
213
|
+
initial.workflowName,
|
|
214
|
+
initial.payload,
|
|
215
|
+
initial.parent ?? null,
|
|
216
|
+
initial.status,
|
|
217
|
+
initial.suspended ? 1 : 0,
|
|
218
|
+
initial.interrupted ? 1 : 0,
|
|
219
|
+
initial.etag
|
|
220
|
+
])
|
|
221
|
+
.pipe(Effect.map((rows) => rows.length > 0), annotate("createExec", initial.executionId));
|
|
222
|
+
// First-writer-wins persistence of an activity result; returns true if this
|
|
223
|
+
// call won, false if another writer beat us to the (exec, name, attempt) row.
|
|
224
|
+
const createActivity = (executionId, name, attempt, encoded) => exec(`INSERT INTO "${activityTable}" (execution_id, name, attempt, result)
|
|
225
|
+
VALUES (?, ?, ?, ?)
|
|
226
|
+
ON CONFLICT DO NOTHING
|
|
227
|
+
RETURNING execution_id`, [executionId, name, attempt, encoded])
|
|
228
|
+
.pipe(Effect.map((rows) => rows.length > 0));
|
|
229
|
+
// Overwrites a previously persisted *suspended* activity result so the next
|
|
230
|
+
// attempt can record its real outcome.
|
|
231
|
+
const upsertActivity = (executionId, name, attempt, encoded) => exec(`INSERT INTO "${activityTable}" (execution_id, name, attempt, result)
|
|
232
|
+
VALUES (?, ?, ?, ?)
|
|
233
|
+
ON CONFLICT(execution_id, name, attempt) DO UPDATE SET result = excluded.result`, [executionId, name, attempt, encoded])
|
|
234
|
+
.pipe(Effect.asVoid);
|
|
235
|
+
const readActivity = (executionId, name, attempt) => exec(`SELECT result FROM "${activityTable}" WHERE execution_id = ? AND name = ? AND attempt = ?`, [executionId, name, attempt])
|
|
236
|
+
.pipe(Effect.map((rows) => {
|
|
237
|
+
const r = rows[0];
|
|
238
|
+
return r ? Option.some(r.result) : Option.none();
|
|
239
|
+
}));
|
|
240
|
+
const createDeferred = (executionId, name, encoded) => exec(`INSERT INTO "${deferredTable}" (execution_id, name, exit)
|
|
241
|
+
VALUES (?, ?, ?)
|
|
242
|
+
ON CONFLICT DO NOTHING
|
|
243
|
+
RETURNING execution_id`, [executionId, name, encoded])
|
|
244
|
+
.pipe(Effect.map((rows) => rows.length > 0));
|
|
245
|
+
const readDeferred = (executionId, name) => exec(`SELECT exit FROM "${deferredTable}" WHERE execution_id = ? AND name = ?`, [executionId, name])
|
|
246
|
+
.pipe(Effect.map((rows) => {
|
|
247
|
+
const r = rows[0];
|
|
248
|
+
return r ? Option.some(r.exit) : Option.none();
|
|
249
|
+
}));
|
|
250
|
+
const insertClock = (executionId, name, workflowName, deferredName, fireAt) => exec(`INSERT INTO "${clockTable}" (execution_id, name, workflow_name, deferred_name, fire_at)
|
|
251
|
+
VALUES (?, ?, ?, ?, ?)
|
|
252
|
+
ON CONFLICT DO NOTHING
|
|
253
|
+
RETURNING execution_id`, [executionId, name, workflowName, deferredName, fireAt])
|
|
254
|
+
.pipe(Effect.map((rows) => rows.length > 0));
|
|
255
|
+
const deleteClock = (executionId, name) => exec(`DELETE FROM "${clockTable}" WHERE execution_id = ? AND name = ?`, [executionId, name]);
|
|
256
|
+
// --- Workflow result helpers ------------------------------------------
|
|
257
|
+
const completeResult = (workflow, state) => state.status === "complete" && state.completedResult
|
|
258
|
+
? Effect.map(decodeResult(workflow, state.completedResult), Option.some)
|
|
259
|
+
: Effect.succeedNone;
|
|
260
|
+
// --- Lease / claim ----------------------------------------------------
|
|
261
|
+
const leaseActive = (state, now) => state.worker !== undefined
|
|
262
|
+
&& state.worker !== workerId
|
|
263
|
+
&& state.leaseExpiresAt !== undefined
|
|
264
|
+
&& state.leaseExpiresAt > now;
|
|
265
|
+
const tryClaim = (state) => Effect.gen(function* () {
|
|
266
|
+
const now = Date.now();
|
|
267
|
+
if (leaseActive(state, now))
|
|
268
|
+
return Option.none();
|
|
269
|
+
return yield* replaceExec(state, {
|
|
270
|
+
worker: workerId,
|
|
271
|
+
leaseExpiresAt: now + Duration.toMillis(leaseTtl)
|
|
272
|
+
})
|
|
273
|
+
.pipe(Effect.map(Option.some), Effect.catchTag("OptimisticConcurrencyException", () => Effect.succeed(Option.none())));
|
|
274
|
+
});
|
|
275
|
+
const heartbeat = (executionId) => Effect.gen(function* () {
|
|
276
|
+
while (true) {
|
|
277
|
+
yield* Effect.sleep(heartbeatInterval);
|
|
278
|
+
const local = locals.get(executionId);
|
|
279
|
+
const polled = local?.fiber?.pollUnsafe();
|
|
280
|
+
if (!local?.fiber || polled)
|
|
281
|
+
return;
|
|
282
|
+
const cur = yield* readExec(executionId).pipe(Effect.catchCause(() => Effect.succeed(Option.none())));
|
|
283
|
+
if (Option.isNone(cur))
|
|
284
|
+
continue;
|
|
285
|
+
const state = cur.value;
|
|
286
|
+
if (state.status === "complete" || state.worker !== workerId)
|
|
287
|
+
return;
|
|
288
|
+
yield* replaceExec(state, {
|
|
289
|
+
leaseExpiresAt: Date.now() + Duration.toMillis(leaseTtl)
|
|
290
|
+
})
|
|
291
|
+
.pipe(Effect.catchTag("OptimisticConcurrencyException", () => Effect.void), Effect.catchCause(() => Effect.void));
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
// --- Drive logic ------------------------------------------------------
|
|
295
|
+
const drive = (executionId, payload, parent, entry) => Effect.gen(function* () {
|
|
296
|
+
let local = locals.get(executionId);
|
|
297
|
+
if (local?.fiber) {
|
|
298
|
+
const polled = local.fiber.pollUnsafe();
|
|
299
|
+
const stillRunning = !polled;
|
|
300
|
+
const completedNotResume = polled && polled._tag === "Success" && polled.value._tag === "Complete";
|
|
301
|
+
if (stillRunning || completedNotResume)
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
const stateOpt = yield* readExec(executionId);
|
|
305
|
+
if (Option.isNone(stateOpt) || stateOpt.value.status === "complete")
|
|
306
|
+
return;
|
|
307
|
+
const claimed = yield* tryClaim(stateOpt.value);
|
|
308
|
+
const state = Option.isSome(claimed) ? claimed.value : stateOpt.value;
|
|
309
|
+
const instance = WorkflowInstance.initial(entry.workflow, executionId);
|
|
310
|
+
instance.interrupted = state.interrupted;
|
|
311
|
+
if (!local) {
|
|
312
|
+
local = { instance, fiber: undefined, parent };
|
|
313
|
+
locals.set(executionId, local);
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
local.instance = instance;
|
|
317
|
+
}
|
|
318
|
+
const onComplete = Effect.fnUntraced(function* (result) {
|
|
319
|
+
const current = yield* readExec(executionId);
|
|
320
|
+
if (Option.isNone(current) || current.value.status === "complete")
|
|
321
|
+
return;
|
|
322
|
+
const isComplete = result._tag === "Complete";
|
|
323
|
+
const completedResult = isComplete ? yield* encodeResult(entry.workflow, result) : undefined;
|
|
324
|
+
yield* replaceExec(current.value, {
|
|
325
|
+
status: isComplete ? "complete" : current.value.status,
|
|
326
|
+
suspended: result._tag === "Suspended",
|
|
327
|
+
interrupted: instance.interrupted,
|
|
328
|
+
completedResult,
|
|
329
|
+
worker: isComplete ? undefined : current.value.worker,
|
|
330
|
+
leaseExpiresAt: isComplete ? undefined : current.value.leaseExpiresAt
|
|
331
|
+
})
|
|
332
|
+
.pipe(Effect.catchTag("OptimisticConcurrencyException", () => Effect.void));
|
|
333
|
+
if (parent && isComplete) {
|
|
334
|
+
yield* Effect.forkIn(driveById(parent), scope);
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
local.fiber = yield* entry.execute(payload, executionId).pipe(Effect.onExit(() => {
|
|
338
|
+
if (!instance.interrupted)
|
|
339
|
+
return Effect.void;
|
|
340
|
+
instance.suspended = false;
|
|
341
|
+
return Effect.withFiber((fiber) => Effect.interruptible(Fiber.interrupt(fiber)));
|
|
342
|
+
}), Workflow.intoResult, Effect.provideService(WorkflowInstance, instance), Effect.provideService(WorkflowEngine, engine), Effect.tap(onComplete), Effect.forkIn(entry.scope));
|
|
343
|
+
if (Option.isSome(claimed)) {
|
|
344
|
+
yield* Effect.forkIn(heartbeat(executionId), scope);
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
const driveById = (executionId) => Effect.gen(function* () {
|
|
348
|
+
const stateOpt = yield* readExec(executionId);
|
|
349
|
+
if (Option.isNone(stateOpt))
|
|
350
|
+
return;
|
|
351
|
+
const state = stateOpt.value;
|
|
352
|
+
const entry = workflows.get(state.workflowName);
|
|
353
|
+
if (!entry)
|
|
354
|
+
return;
|
|
355
|
+
const payload = yield* decodePayload(entry.workflow, state.payload);
|
|
356
|
+
yield* drive(executionId, payload, state.parent, entry);
|
|
357
|
+
});
|
|
358
|
+
// --- Clock firing -----------------------------------------------------
|
|
359
|
+
const fireClock = (executionId, name, deferredName) => Effect
|
|
360
|
+
.gen(function* () {
|
|
361
|
+
const encoded = yield* encodeDeferredExit(Exit.void);
|
|
362
|
+
const inserted = yield* sql
|
|
363
|
+
.withTransaction(Effect.gen(function* () {
|
|
364
|
+
const got = yield* createDeferred(executionId, deferredName, encoded);
|
|
365
|
+
yield* deleteClock(executionId, name);
|
|
366
|
+
return got;
|
|
367
|
+
}))
|
|
368
|
+
.pipe(Effect.orDie);
|
|
369
|
+
if (inserted)
|
|
370
|
+
yield* driveById(executionId);
|
|
371
|
+
})
|
|
372
|
+
.pipe(annotate("clockFire", executionId));
|
|
373
|
+
// --- Encoded engine ---------------------------------------------------
|
|
374
|
+
const encoded = {
|
|
375
|
+
register: Effect.fnUntraced(function* (workflow, execute) {
|
|
376
|
+
workflows.set(workflow.name, {
|
|
377
|
+
workflow,
|
|
378
|
+
execute,
|
|
379
|
+
scope: yield* Effect.scope
|
|
380
|
+
});
|
|
381
|
+
}),
|
|
382
|
+
execute: Effect.fnUntraced(function* (workflow, options) {
|
|
383
|
+
const entry = workflows.get(workflow.name);
|
|
384
|
+
if (!entry) {
|
|
385
|
+
return yield* Effect.orDie(Effect.fail(`Workflow ${workflow.name} is not registered`));
|
|
386
|
+
}
|
|
387
|
+
const initial = {
|
|
388
|
+
executionId: options.executionId,
|
|
389
|
+
workflowName: workflow.name,
|
|
390
|
+
payload: yield* encodePayload(workflow, options.payload),
|
|
391
|
+
parent: options.parent?.executionId,
|
|
392
|
+
status: "running",
|
|
393
|
+
suspended: false,
|
|
394
|
+
interrupted: false,
|
|
395
|
+
completedResult: undefined,
|
|
396
|
+
worker: undefined,
|
|
397
|
+
leaseExpiresAt: undefined,
|
|
398
|
+
etag: randomUUID()
|
|
399
|
+
};
|
|
400
|
+
yield* createExec(initial);
|
|
401
|
+
yield* drive(options.executionId, options.payload, options.parent?.executionId, entry);
|
|
402
|
+
if (options.discard)
|
|
403
|
+
return undefined;
|
|
404
|
+
const local = locals.get(options.executionId);
|
|
405
|
+
if (local?.fiber) {
|
|
406
|
+
return (yield* Fiber.join(local.fiber));
|
|
407
|
+
}
|
|
408
|
+
// Foreign-driver fallback: poll the persisted result until completion.
|
|
409
|
+
while (true) {
|
|
410
|
+
const cur = yield* readExec(options.executionId);
|
|
411
|
+
if (Option.isSome(cur)) {
|
|
412
|
+
const r = yield* completeResult(workflow, cur.value);
|
|
413
|
+
if (Option.isSome(r))
|
|
414
|
+
return r.value;
|
|
415
|
+
}
|
|
416
|
+
yield* Effect.sleep(Duration.millis(500));
|
|
417
|
+
}
|
|
418
|
+
}),
|
|
419
|
+
poll: (workflow, executionId) => Effect.gen(function* () {
|
|
420
|
+
const local = locals.get(executionId);
|
|
421
|
+
if (local?.fiber) {
|
|
422
|
+
const exitVal = local.fiber.pollUnsafe();
|
|
423
|
+
if (!exitVal)
|
|
424
|
+
return Option.none();
|
|
425
|
+
if (exitVal._tag !== "Success")
|
|
426
|
+
return yield* Effect.die(exitVal.cause);
|
|
427
|
+
return Option.some(exitVal.value);
|
|
428
|
+
}
|
|
429
|
+
const state = yield* readExec(executionId);
|
|
430
|
+
if (Option.isNone(state))
|
|
431
|
+
return Option.none();
|
|
432
|
+
return yield* completeResult(workflow, state.value);
|
|
433
|
+
}),
|
|
434
|
+
interrupt: Effect.fnUntraced(function* (_workflow, executionId) {
|
|
435
|
+
const local = locals.get(executionId);
|
|
436
|
+
if (local)
|
|
437
|
+
local.instance.interrupted = true;
|
|
438
|
+
const current = yield* readExec(executionId);
|
|
439
|
+
if (Option.isNone(current) || current.value.status === "complete")
|
|
440
|
+
return;
|
|
441
|
+
yield* replaceExec(current.value, { interrupted: true }).pipe(Effect.catchTag("OptimisticConcurrencyException", () => Effect.void));
|
|
442
|
+
yield* driveById(executionId);
|
|
443
|
+
}),
|
|
444
|
+
interruptUnsafe: Effect.fnUntraced(function* (_workflow, executionId) {
|
|
445
|
+
const local = locals.get(executionId);
|
|
446
|
+
if (local)
|
|
447
|
+
local.instance.interrupted = true;
|
|
448
|
+
const current = yield* readExec(executionId);
|
|
449
|
+
if (Option.isSome(current) && current.value.status !== "complete") {
|
|
450
|
+
yield* replaceExec(current.value, { interrupted: true }).pipe(Effect.catchTag("OptimisticConcurrencyException", () => Effect.void));
|
|
451
|
+
}
|
|
452
|
+
if (local?.fiber)
|
|
453
|
+
yield* Fiber.interrupt(local.fiber);
|
|
454
|
+
}),
|
|
455
|
+
resume: (_workflow, executionId) => driveById(executionId),
|
|
456
|
+
activityExecute: Effect.fnUntraced(function* (activity, attempt) {
|
|
457
|
+
const instance = yield* WorkflowInstance;
|
|
458
|
+
const existing = yield* readActivity(instance.executionId, activity.name, attempt);
|
|
459
|
+
if (Option.isSome(existing)) {
|
|
460
|
+
const prev = yield* decodeActivityResult(existing.value);
|
|
461
|
+
// A completed activity is replayed from its persisted result; a
|
|
462
|
+
// suspended one must re-run (it parked on a clock/deferred).
|
|
463
|
+
if (prev._tag === "Complete")
|
|
464
|
+
return prev;
|
|
465
|
+
}
|
|
466
|
+
const activityInstance = WorkflowInstance.initial(instance.workflow, instance.executionId);
|
|
467
|
+
activityInstance.interrupted = instance.interrupted;
|
|
468
|
+
const result = yield* activity.executeEncoded.pipe(Workflow.intoResult, Effect.provideService(WorkflowInstance, activityInstance));
|
|
469
|
+
const encodedResult = yield* encodeActivityResult(result);
|
|
470
|
+
if (Option.isSome(existing)) {
|
|
471
|
+
// Overwrite the previously persisted *suspended* result.
|
|
472
|
+
yield* upsertActivity(instance.executionId, activity.name, attempt, encodedResult);
|
|
473
|
+
return result;
|
|
474
|
+
}
|
|
475
|
+
// First-writer-wins: if persistence loses the race, use the persisted result.
|
|
476
|
+
const persisted = yield* createActivity(instance.executionId, activity.name, attempt, encodedResult);
|
|
477
|
+
if (persisted)
|
|
478
|
+
return result;
|
|
479
|
+
const winner = yield* readActivity(instance.executionId, activity.name, attempt);
|
|
480
|
+
if (Option.isSome(winner)) {
|
|
481
|
+
const w = yield* decodeActivityResult(winner.value);
|
|
482
|
+
if (w._tag === "Complete")
|
|
483
|
+
return w;
|
|
484
|
+
}
|
|
485
|
+
return result;
|
|
486
|
+
}),
|
|
487
|
+
deferredResult: Effect.fnUntraced(function* (deferred) {
|
|
488
|
+
const instance = yield* WorkflowInstance;
|
|
489
|
+
const got = yield* readDeferred(instance.executionId, deferred.name);
|
|
490
|
+
if (Option.isNone(got))
|
|
491
|
+
return Option.none();
|
|
492
|
+
return Option.some(yield* decodeDeferredExit(got.value));
|
|
493
|
+
}),
|
|
494
|
+
deferredDone: Effect.fnUntraced(function* (options) {
|
|
495
|
+
const encoded = yield* encodeDeferredExit(options.exit);
|
|
496
|
+
const inserted = yield* createDeferred(options.executionId, options.deferredName, encoded);
|
|
497
|
+
if (!inserted)
|
|
498
|
+
return;
|
|
499
|
+
yield* driveById(options.executionId);
|
|
500
|
+
}),
|
|
501
|
+
scheduleClock: (workflow, options) => {
|
|
502
|
+
const fireAt = Date.now() + Duration.toMillis(options.clock.duration);
|
|
503
|
+
return Effect.gen(function* () {
|
|
504
|
+
yield* insertClock(options.executionId, options.clock.name, workflow.name, options.clock.deferred.name, fireAt);
|
|
505
|
+
yield* fireClock(options.executionId, options.clock.name, options.clock.deferred.name).pipe(Effect.delay(options.clock.duration), FiberMap.run(clocks, `${options.executionId}/${options.clock.name}`, { onlyIfMissing: true }), Effect.asVoid);
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
const engine = makeUnsafe(encoded);
|
|
510
|
+
// --- Recovery poller --------------------------------------------------
|
|
511
|
+
if (Duration.toMillis(recoveryInterval) > 0) {
|
|
512
|
+
const recoverStep = Effect
|
|
513
|
+
.gen(function* () {
|
|
514
|
+
const rows = yield* exec(`SELECT execution_id, workflow_name FROM "${execTable}"
|
|
515
|
+
WHERE status = 'running' AND (lease_expires_at IS NULL OR lease_expires_at <= ?)
|
|
516
|
+
LIMIT 100`, [Date.now()]);
|
|
517
|
+
for (const row of rows) {
|
|
518
|
+
if (!workflows.has(row.workflow_name))
|
|
519
|
+
continue;
|
|
520
|
+
const local = locals.get(row.execution_id);
|
|
521
|
+
if (local?.fiber && !local.fiber.pollUnsafe())
|
|
522
|
+
continue;
|
|
523
|
+
yield* Effect.forkIn(driveById(row.execution_id), scope);
|
|
524
|
+
}
|
|
525
|
+
})
|
|
526
|
+
.pipe(annotate("recoveryScan"), Effect.catchCause(() => Effect.void));
|
|
527
|
+
yield* recoverStep.pipe(Effect.repeat(Schedule.spaced(recoveryInterval)), Effect.forkIn(scope));
|
|
528
|
+
}
|
|
529
|
+
// --- Clock poller -----------------------------------------------------
|
|
530
|
+
if (Duration.toMillis(clockPollInterval) > 0) {
|
|
531
|
+
const clockStep = Effect
|
|
532
|
+
.gen(function* () {
|
|
533
|
+
const rows = yield* exec(`SELECT execution_id, name, deferred_name FROM "${clockTable}"
|
|
534
|
+
WHERE fire_at <= ?
|
|
535
|
+
LIMIT 100`, [Date.now()]);
|
|
536
|
+
for (const row of rows) {
|
|
537
|
+
yield* Effect.forkIn(fireClock(row.execution_id, row.name, row.deferred_name), scope);
|
|
538
|
+
}
|
|
539
|
+
})
|
|
540
|
+
.pipe(annotate("clockScan"), Effect.catchCause(() => Effect.void));
|
|
541
|
+
yield* clockStep.pipe(Effect.repeat(Schedule.spaced(clockPollInterval)), Effect.forkIn(scope));
|
|
542
|
+
}
|
|
543
|
+
return engine;
|
|
544
|
+
});
|
|
545
|
+
/**
|
|
546
|
+
* SQLite backed `WorkflowEngine` layer. Requires an ambient `SqlClient`
|
|
547
|
+
* (`@effect/sql-sqlite-node` or a compatible client).
|
|
548
|
+
*/
|
|
549
|
+
export const layerSqlite = (cfg = {}) => Layer.effect(WorkflowEngine)(makeSqliteWorkflowEngine(cfg));
|
|
550
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiV29ya2Zsb3dFbmdpbmVTcWxpdGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvV29ya2Zsb3dFbmdpbmVTcWxpdGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FrQ0c7QUFDSCxPQUFPLEtBQUssTUFBTSxNQUFNLG1CQUFtQixDQUFBO0FBQzNDLE9BQU8sS0FBSyxLQUFLLE1BQU0sa0JBQWtCLENBQUE7QUFDekMsT0FBTyxLQUFLLE1BQU0sTUFBTSxtQkFBbUIsQ0FBQTtBQUMzQyxPQUFPLEtBQUssQ0FBQyxNQUFNLG1CQUFtQixDQUFBO0FBQ3RDLE9BQU8sS0FBSyxRQUFRLE1BQU0saUJBQWlCLENBQUE7QUFDM0MsT0FBTyxLQUFLLElBQUksTUFBTSxhQUFhLENBQUE7QUFDbkMsT0FBTyxLQUFLLEtBQUssTUFBTSxjQUFjLENBQUE7QUFDckMsT0FBTyxLQUFLLFFBQVEsTUFBTSxpQkFBaUIsQ0FBQTtBQUMzQyxPQUFPLEtBQUssUUFBUSxNQUFNLGlCQUFpQixDQUFBO0FBRTNDLE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSxxQkFBcUIsQ0FBQTtBQUMvQyxPQUFPLEtBQUssUUFBUSxNQUFNLG1DQUFtQyxDQUFBO0FBQzdELE9BQU8sRUFBZ0IsVUFBVSxFQUFFLGNBQWMsRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLHlDQUF5QyxDQUFBO0FBQ3BILE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxhQUFhLENBQUE7QUFDeEMsT0FBTyxFQUFFLDhCQUE4QixFQUFFLE1BQU0sYUFBYSxDQUFBO0FBQzVELE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxXQUFXLENBQUE7QUFpRHRDLE1BQU0sU0FBUyxHQUFHLENBQUMsR0FBWSxFQUFhLEVBQUUsQ0FBQyxDQUFDO0lBQzlDLFdBQVcsRUFBRSxHQUFHLENBQUMsWUFBWTtJQUM3QixZQUFZLEVBQUUsR0FBRyxDQUFDLGFBQWE7SUFD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
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Cluster storage backends (Cosmos vs SQL)
|
|
2
|
+
|
|
3
|
+
`@effect-app/infra` now ships Cosmos-backed cluster storage:
|
|
4
|
+
|
|
5
|
+
- [`ClusterCosmos.layerMessageStorage`](../src/ClusterCosmos.ts)
|
|
6
|
+
- [`ClusterCosmos.layerRunnerStorage`](../src/ClusterCosmos.ts)
|
|
7
|
+
- [`ClusterCosmos.layer`](../src/ClusterCosmos.ts)
|
|
8
|
+
- [`ClusterCosmos.layerCosmos`](../src/ClusterCosmos.ts)
|
|
9
|
+
|
|
10
|
+
The closest baseline in Effect is `SqlMessageStorage` + `SqlRunnerStorage`.
|
|
11
|
+
|
|
12
|
+
## Comparison
|
|
13
|
+
|
|
14
|
+
| Aspect | `ClusterCosmos` | `SqlMessageStorage` + `SqlRunnerStorage` |
|
|
15
|
+
| ------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
|
|
16
|
+
| Backend dependency | Azure Cosmos DB | SQL database via `SqlClient` |
|
|
17
|
+
| Schema management | Container-only document model (no SQL migrations) | Creates / migrates SQL tables (`messages`, `replies`, `runners`, `locks`, migrations table) |
|
|
18
|
+
| Message/reply model | JSON docs split by partition key (`message::shardId`, `reply::requestId`) | Relational rows with SQL indexes and joins |
|
|
19
|
+
| Locking strategy | Optimistic concurrency (`_etag`) on lock docs | Dialect-aware SQL locking (including advisory locks on pg/mysql when enabled) |
|
|
20
|
+
| Horizontal behavior | Throughput/cost depends on partitioning and cross-partition queries | Throughput/cost depends on SQL indexing, query plans, and connection limits |
|
|
21
|
+
| Operational fit | Best when Cosmos is already your system DB | Best when the cluster already runs on SQL infrastructure |
|
|
22
|
+
|
|
23
|
+
## Practical guidance
|
|
24
|
+
|
|
25
|
+
- Pick **`ClusterCosmos`** when your platform standard is Cosmos and you want to avoid introducing SQL just for cluster storage.
|
|
26
|
+
- Pick **SQL storage** when you already have strong SQL ops tooling and prefer table/migration based durability.
|