@voltro/workflow 0.80.0 → 0.82.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voltro/workflow",
3
- "version": "0.80.0",
3
+ "version": "0.82.0",
4
4
  "description": "Durable workflows for Voltro — the workflow() descriptor + step() / awaitSignal primitives over @effect/cluster, with a browser-safe define subpath.",
5
5
  "keywords": [
6
6
  "voltro",
@@ -51,9 +51,9 @@
51
51
  "@effect/cluster": "^0.60.2",
52
52
  "@effect/sql": "^0.52.1",
53
53
  "@effect/workflow": "^0.19.1",
54
- "@voltro/database": "0.80.0",
55
- "@voltro/logger": "0.80.0",
56
- "@voltro/protocol": "0.80.0"
54
+ "@voltro/database": "0.82.0",
55
+ "@voltro/logger": "0.82.0",
56
+ "@voltro/protocol": "0.82.0"
57
57
  },
58
58
  "peerDependencies": {
59
59
  "effect": "^3.22.2",
@@ -1,489 +0,0 @@
1
- import { a as e, l as t, n, o as r, t as i, u as a } from "./clusterDiagnostics-Cwt-WFec.js";
2
- import { Context as o, Duration as s, Effect as c, Layer as l, Option as u, Runtime as d, Schema as f } from "effect";
3
- import { SqlClient as p } from "@effect/sql";
4
- import { ClusterCron as m, ClusterSchema as h, ClusterWorkflowEngine as g, EntityAddress as _, EntityId as v, EntityType as ee, MessageStorage as y, RunnerAddress as b, ShardId as te, Sharding as x, ShardingConfig as ne, SingleRunner as re, SqlMessageStorage as ie, SqlRunnerStorage as ae } from "@effect/cluster";
5
- import { fnv1a64 as S, resolveMigrationLockSchema as C, sqlPoolCapacity as oe } from "@voltro/database/sql";
6
- import { createLogger as se } from "@voltro/logger";
7
- //#region src/workflowJournalReset.ts
8
- var w = f.parseJson(f.Struct({
9
- _tag: f.Literal("Success"),
10
- value: f.Struct({ _tag: f.Literal("Suspended") })
11
- })), ce = f.parseJson(f.Record({
12
- key: f.String,
13
- value: f.String
14
- })), le = (e) => c.gen(function* () {
15
- if (!/^[1-9]\d*$/.test(e.requestId) || e.workflowName.length === 0 || e.executionId.length === 0) return yield* c.die(/* @__PURE__ */ Error("Suspension observation requires an exact engine delivery owner"));
16
- let [t] = yield* (yield* p.SqlClient).withoutTransforms()`
17
- SELECT m.entity_type, m.entity_id, m.tag, m.headers, r.id AS reply_id, r.payload
18
- FROM cluster_messages m LEFT JOIN cluster_replies r
19
- ON r.id = m.last_reply_id AND r.request_id = m.id AND r.kind = 0
20
- WHERE m.id = ${e.requestId} AND m.kind = 0`;
21
- if (t === void 0) return;
22
- if (t.entity_type !== `Workflow/${e.workflowName}` || t.entity_id !== e.executionId || t.tag !== "run") return yield* c.die(/* @__PURE__ */ Error("Suspension observation belongs to another execution or procedure"));
23
- if (((yield* c.sync(() => f.decodeUnknownSync(ce)(t.headers)))["voltro-workflow-restart"] ?? null) !== e.restartToken || t.reply_id === null || f.decodeUnknownOption(w)(t.payload)._tag === "None") return;
24
- let n = String(t.reply_id);
25
- return /^[1-9]\d*$/.test(n) ? {
26
- ...e,
27
- replyId: n
28
- } : yield* c.die(/* @__PURE__ */ Error("Suspension reply has an invalid identity"));
29
- }).pipe(c.provideService(p.SafeIntegers, !0)), ue = (t, n) => c.gen(function* () {
30
- let { restart: r, suspension: i } = n, a = /* @__PURE__ */ new Map();
31
- for (let e of t) {
32
- if (!/^[1-9]\d*$/.test(e.requestId) || !/^[1-9]\d*$/.test(e.replyId)) return yield* c.die(/* @__PURE__ */ Error("Journal reset requires positive decimal request and reply identities"));
33
- let t = a.get(e.requestId);
34
- if (t !== void 0 && t !== e.replyId) return yield* c.die(/* @__PURE__ */ Error("Journal reset contains conflicting observations for one request"));
35
- a.set(e.requestId, e.replyId);
36
- }
37
- if (r !== void 0 && (!a.has(r.requestId) || r.token.length === 0 || r.token === r.previousToken || r.workflowName.length === 0 || r.executionId.length === 0)) return yield* c.die(/* @__PURE__ */ Error("Journal restart requires a selected request, new token and execution owner"));
38
- if (a.size === 0) return {
39
- kind: "reset",
40
- count: 0
41
- };
42
- let o = [...a].sort(([e], [t]) => e < t ? -1 : +(e > t)), s = (yield* p.SqlClient).withoutTransforms(), l = s("cluster_messages"), u = s("cluster_replies"), d = s.onDialectOrElse({
43
- pg: () => s.literal("FALSE"),
44
- orElse: () => s.literal("0")
45
- });
46
- return yield* c.gen(function* () {
47
- let t = /* @__PURE__ */ new Map();
48
- for (let [e] of o) {
49
- let n = yield* s.onDialectOrElse({
50
- mssql: () => s`
51
- SELECT last_reply_id FROM ${l} WITH (UPDLOCK, HOLDLOCK)
52
- WHERE id = ${e} AND kind = 0`,
53
- sqlite: () => s`
54
- SELECT last_reply_id FROM ${l} WHERE id = ${e} AND kind = 0`,
55
- orElse: () => s`
56
- SELECT last_reply_id FROM ${l} WHERE id = ${e} AND kind = 0 FOR UPDATE`
57
- });
58
- if (n.length !== 1) return {
59
- kind: "changed",
60
- requestId: e
61
- };
62
- if (t.set(e, String(n[0]?.last_reply_id)), r !== void 0) {
63
- let [t] = yield* s`
64
- SELECT entity_type, entity_id, tag FROM ${l} WHERE id = ${e}`;
65
- if (t?.entity_type !== `Workflow/${r.workflowName}` || t.entity_id !== r.executionId || t.tag !== (e === r.requestId ? "run" : "activity")) return yield* c.die(/* @__PURE__ */ Error("Journal restart selection belongs to another execution or procedure"));
66
- }
67
- }
68
- let n;
69
- if (i !== void 0) {
70
- let [e] = yield* s`
71
- SELECT entity_type, entity_id, tag, headers FROM ${l} WHERE id = ${i.requestId}`;
72
- if (e?.entity_type !== `Workflow/${i.workflowName}` || e.entity_id !== i.executionId || e.tag !== "run" || i.workflowName.length === 0 || i.executionId.length === 0) return yield* c.die(/* @__PURE__ */ Error("Journal resume selection belongs to another execution or procedure"));
73
- if (((yield* c.sync(() => f.decodeUnknownSync(f.parseJson(f.Record({
74
- key: f.String,
75
- value: f.String
76
- })))(e.headers)))["voltro-workflow-restart"] ?? null) !== i.restartToken) return {
77
- kind: "changed",
78
- requestId: i.requestId
79
- };
80
- }
81
- if (r !== void 0) {
82
- let [e] = yield* s`
83
- SELECT tag, entity_type, headers FROM ${l} WHERE id = ${r.requestId}`;
84
- if (e?.tag !== "run" || !e.entity_type.startsWith("Workflow/")) return yield* c.die(/* @__PURE__ */ Error("Journal restart marker can only target a workflow run"));
85
- if (n = yield* c.sync(() => f.decodeUnknownSync(f.parseJson(f.Record({
86
- key: f.String,
87
- value: f.String
88
- })))(e.headers)), n["voltro-workflow-restart"] === r.token) return {
89
- kind: "already-reset",
90
- count: o.length
91
- };
92
- if ((n["voltro-workflow-restart"] ?? null) !== r.previousToken) return {
93
- kind: "changed",
94
- requestId: r.requestId
95
- };
96
- }
97
- for (let [e, n] of o) {
98
- if (t.get(e) !== n) return {
99
- kind: "changed",
100
- requestId: e
101
- };
102
- let r = yield* s`SELECT payload FROM ${u}
103
- WHERE id = ${n} AND request_id = ${e} AND kind = 0`;
104
- if (r.length !== 1 || i !== void 0 && f.decodeUnknownOption(w)(r[0]?.payload)._tag === "None") return {
105
- kind: "changed",
106
- requestId: e
107
- };
108
- }
109
- r !== void 0 && (yield* s`UPDATE ${l} SET headers = ${JSON.stringify({
110
- ...n,
111
- [e]: r.token
112
- })}
113
- WHERE id = ${r.requestId}`);
114
- for (let [e] of o) yield* s`DELETE FROM ${u} WHERE request_id = ${e} AND kind = 0`, yield* s`DELETE FROM ${l} WHERE request_id = ${e} AND kind = 2`, yield* s`UPDATE ${l} SET processed = ${d}, last_reply_id = NULL,
115
- last_read = NULL WHERE request_id = ${e}`;
116
- return {
117
- kind: "reset",
118
- count: o.length
119
- };
120
- }).pipe(s.withTransaction, c.provideService(p.SafeIntegers, !0));
121
- }), de = (e) => ue([e], { suspension: e }), fe = (e) => c.gen(function* () {
122
- let n = yield* c.runtime(), r = yield* c.promise(() => e.transaction((t) => a(t, e.requestId, (e) => d.runPromise(n)(le(e)))));
123
- if (r.kind !== "bound") return r;
124
- let i = yield* c.promise(() => e.transaction((n) => t(n, e.requestId)));
125
- if (i.kind !== "dispatch") return i;
126
- let { request: o } = i;
127
- if (o.operation !== "resume" || o.resumeJournal === null) return yield* c.die(/* @__PURE__ */ Error("Resume dispatch requires its committed suspension binding"));
128
- let s = yield* de({
129
- workflowName: o.workflowName,
130
- executionId: o.executionId,
131
- ...o.resumeJournal
132
- });
133
- return yield* (yield* x.Sharding).pollStorage, {
134
- kind: "attempted",
135
- request: o,
136
- journal: s
137
- };
138
- }), T = class extends o.Tag("@voltro/workflow/WorkflowResumeDispatcher")() {}, pe = c.gen(function* () {
139
- let e = yield* p.SqlClient, t = yield* x.Sharding;
140
- return T.of({ dispatch: (n) => fe(n).pipe(c.provideService(p.SqlClient, e), c.provideService(x.Sharding, t)) });
141
- }), E = 6322741009312601n, D = () => {
142
- let e = C();
143
- return e === void 0 ? E : S(`voltro_cluster_migration_lock:${e}`) & 9223372036854775807n;
144
- }, O = () => {
145
- let e = `voltro_cluster_migration_${E.toString(36)}`, t = C();
146
- if (t === void 0) return e;
147
- let n = `${e}_${t}`;
148
- return /^[A-Za-z0-9_]+$/.test(t) && n.length <= 64 ? n : `${e}_h${S(`voltro_cluster_migration_lock:${t}`).toString(16)}`;
149
- }, k = () => {
150
- let e = Number(process.env.VOLTRO_CLUSTER_MIGRATION_LOCK_TIMEOUT_MS);
151
- return Number.isFinite(e) && e > 0 ? e : 6e4;
152
- }, A = 250, j = (e) => e[0]?.[0], M = (e, t) => e.onDialectOrElse({
153
- sqlite: () => c.void,
154
- mysql: () => t.executeValues(`SELECT GET_LOCK('${O()}', ${Math.ceil(k() / 1e3)})`, []).pipe(c.flatMap((e) => j(e) === 1 || j(e) === "1" ? c.void : c.die(/* @__PURE__ */ Error(`could not acquire cluster migration lock '${O()}' (GET_LOCK timed out)`)))),
155
- mssql: () => t.executeValues(`DECLARE @res int; EXEC @res = sp_getapplock @Resource = '${O()}', @LockMode = 'Exclusive', @LockOwner = 'Session', @LockTimeout = ${k()}; SELECT @res`, []).pipe(c.flatMap((e) => Number(j(e) ?? -999) >= 0 ? c.void : c.die(/* @__PURE__ */ Error(`could not acquire cluster migration lock '${O()}' (sp_getapplock ${String(j(e))})`)))),
156
- orElse: () => {
157
- let e = D().toString(), n = Date.now(), r = t.executeValues(`SELECT pg_try_advisory_lock(${e}::bigint)`, []).pipe(c.flatMap((e) => {
158
- let t = j(e);
159
- return t === !0 || t === "t" || t === 1 ? c.void : Date.now() - n >= k() ? c.die(/* @__PURE__ */ Error(`could not acquire the cluster migration advisory lock within ${Math.round(k() / 1e3)}s. Another runner is migrating the cluster schema, or a prior boot wedged while holding it. (Raise VOLTRO_CLUSTER_MIGRATION_LOCK_TIMEOUT_MS, or inspect pg_locks for the holder.)`)) : c.sleep(`${A} millis`).pipe(c.zipRight(r));
160
- }));
161
- return r;
162
- }
163
- }), N = (e, t) => e.onDialectOrElse({
164
- sqlite: () => c.void,
165
- mysql: () => c.asVoid(t.executeValues(`SELECT RELEASE_LOCK('${O()}')`, [])),
166
- mssql: () => c.asVoid(t.executeValues(`EXEC sp_releaseapplock @Resource = '${O()}', @LockOwner = 'Session'`, [])),
167
- orElse: () => c.asVoid(t.executeValues(`SELECT pg_advisory_unlock(${D().toString()}::bigint)`, []))
168
- }), P = (e, t) => e.onDialectOrElse({
169
- sqlite: () => !0,
170
- orElse: () => !1
171
- }) ? t : c.scoped(c.gen(function* () {
172
- let n = yield* e.reserve;
173
- return yield* c.acquireRelease(M(e, n), () => N(e, n).pipe(c.orDie)), yield* t;
174
- })), F = (e) => e === "sql" ? ["cluster_messages", "cluster_runners"] : ["cluster_messages"], I = (e, t) => e.onDialectOrElse({
175
- pg: () => e`SELECT (to_regclass(${t}) IS NOT NULL) AS present`,
176
- mysql: () => e`SELECT (COUNT(*) > 0) AS present FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = ${t}`,
177
- mssql: () => e`SELECT CASE WHEN OBJECT_ID(${t}, 'U') IS NULL THEN 0 ELSE 1 END AS present`,
178
- orElse: () => e`SELECT (COUNT(*) > 0) AS present FROM sqlite_master WHERE type = 'table' AND name = ${t}`
179
- }).pipe(c.map((e) => {
180
- let t = e[0]?.present;
181
- return t === !0 || t === 1 || t === "1" || t === "t" || typeof t == "bigint" && t === 1n;
182
- }), c.catchAll(() => c.succeed(!1))), L = (e, t) => c.forEach(F(t), (t) => I(e, t), { concurrency: 1 }).pipe(c.map((e) => e.every(Boolean))), R = (e, t) => c.scoped(c.asVoid(l.build(t.pipe(l.provide(ne.layerDefaults), l.provide(l.succeed(p.SqlClient, e)))))), z = (e, t) => R(e, ie.layer).pipe(c.zipRight(t === "sql" ? R(e, ae.layer) : c.void)), B = (e) => e === "sql" ? 3 : 2, me = (e) => l.effect(p.SqlClient, c.gen(function* () {
183
- let t = yield* p.SqlClient, n = B(e.runnerStorage), r = oe(t);
184
- return r !== void 0 && r < n ? yield* c.dieMessage(`the workflow runtime needs ${n} database connections at once and the pool has ${r}: ` + (e.runnerStorage === "sql" ? "on a first boot it holds its migration lock and its runner lock while the migration runs on a third, and once running it keeps the runner lock and needs one more to work. " : "on a first boot it holds its migration lock while the migration runs on a second. ") + `Set DB_MAX_CONNECTIONS (or PG_MAX_CONNECTIONS) to at least ${n}` + (e.runnerStorage === "sql" ? ", or VOLTRO_WORKFLOW_RUNNER_STORAGE=memory for a single-process runtime that needs 2 (no cross-pod shard handoff)." : ".")) : ((yield* L(t, e.runnerStorage)) || (yield* P(t, z(t, e.runnerStorage))), t);
185
- })), V = (e) => {
186
- if (e instanceof Date) return Number.isNaN(e.getTime()) ? null : e;
187
- if (typeof e == "string" || typeof e == "number") {
188
- let t = typeof e == "string" && /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(e) ? `${e.replace(" ", "T")}Z` : e, n = new Date(t);
189
- return Number.isNaN(n.getTime()) ? null : n;
190
- }
191
- return null;
192
- }, H = (e) => e === !0 || e === 1 || e === "1" || e instanceof Uint8Array && e[0] === 1, he = (e, t) => {
193
- let n = e.lastIndexOf(":");
194
- if (n < 0) return;
195
- let r = e.slice(0, n), i = Number(e.slice(n + 1)), a = t.indexOf(r);
196
- if (!(a < 0 || !Number.isInteger(i))) return (a + 1) * 1e6 + i;
197
- }, U = (e) => c.gen(function* () {
198
- let t = yield* p.SqlClient, n = e.shardGroups ?? ["default"], r = e.dialectId ?? "", a = r.startsWith("postgres") || r === "pg", o = (e) => e.pipe(c.catchAll(() => c.succeed([]))), s = t.onDialectOrElse({
199
- pg: () => t.literal("NOW()"),
200
- mysql: () => t.literal("NOW()"),
201
- mssql: () => t.literal("GETDATE()"),
202
- orElse: () => t.literal("CURRENT_TIMESTAMP")
203
- }), l = (e, t) => {
204
- let n = V(e.last_read), r = V(e.server_now), i = n !== null && r !== null ? Math.max(0, r.getTime() - n.getTime()) : null;
205
- return {
206
- tag: typeof e.tag == "string" ? e.tag : null,
207
- entityType: String(e.entity_type ?? ""),
208
- entityId: String(e.entity_id ?? ""),
209
- shardId: String(e.shard_id ?? ""),
210
- processed: H(e.processed),
211
- lastRead: i === null ? null : new Date(t - i)
212
- };
213
- }, u = (e) => t.onDialectOrElse({
214
- pg: () => t.literal(`NOW() - INTERVAL '${e} seconds'`),
215
- mysql: () => t.literal(`NOW() - INTERVAL ${e} SECOND`),
216
- mssql: () => t.literal(`DATEADD(SECOND, -${e}, GETDATE())`),
217
- orElse: () => t.literal(`DATETIME('now', '-${e} seconds')`)
218
- }), d = t.onDialectOrElse({
219
- pg: () => t.literal("FALSE"),
220
- orElse: () => t.literal("0")
221
- }), f = (e) => {
222
- let n = Date.now();
223
- return o(t`
224
- SELECT tag, entity_type, entity_id, shard_id, processed, last_read, ${s} AS server_now
225
- FROM cluster_messages
226
- WHERE entity_id = ${e}
227
- ORDER BY rowid ASC
228
- `).pipe(c.map((e) => e.map((e) => l(e, n))));
229
- }, m = (e = i) => {
230
- let n = Math.max(1, Math.ceil(e / 1e3)), r = Date.now();
231
- return o(t`
232
- SELECT tag, entity_type, entity_id, shard_id, processed, last_read, ${s} AS server_now
233
- FROM cluster_messages
234
- WHERE processed = ${d}
235
- AND last_read IS NOT NULL
236
- AND last_read < ${u(n)}
237
- AND (deliver_at IS NULL OR deliver_at <= ${r})
238
- ORDER BY last_read ASC
239
- `).pipe(c.map((e) => e.flatMap((e) => {
240
- let t = l(e, r);
241
- return t.lastRead === null ? [] : [{
242
- ...t,
243
- unreadForMs: Math.max(0, r - t.lastRead.getTime())
244
- }];
245
- })));
246
- }, h = (n) => o(t`
247
- SELECT shard_id, address, acquired_at FROM cluster_locks WHERE ${t.in("shard_id", n)}
248
- `).pipe(c.map((t) => t.map((t) => {
249
- let n = t, r = String(n.address ?? ""), i = String(n.shard_id ?? "");
250
- return {
251
- shardId: i,
252
- mode: "row",
253
- holder: r,
254
- own: r === e.ownAddress || (e.hasShard?.(i) ?? !1),
255
- since: V(n.acquired_at)
256
- };
257
- }))), g = (r) => {
258
- let i = /* @__PURE__ */ new Map();
259
- for (let e of r) {
260
- let t = he(e, n);
261
- t !== void 0 && i.set(t, e);
262
- }
263
- return i.size === 0 ? c.succeed([]) : c.gen(function* () {
264
- return (yield* o(t`
265
- SELECT l.objid, a.pid, a.client_addr, a.application_name, a.state, a.state_change, a.backend_start, a.wait_event_type
266
- FROM pg_locks l
267
- JOIN pg_stat_activity a ON a.pid = l.pid
268
- WHERE l.locktype = 'advisory' AND l.granted AND l.classid = 0 AND ${t.in("l.objid", [...i.keys()])}
269
- `)).flatMap((t) => {
270
- let n = t, r = i.get(Number(n.objid));
271
- if (r === void 0) return [];
272
- let a = Number(n.pid), o = n.client_addr === null || n.client_addr === void 0 ? "local socket" : String(n.client_addr), s = e.hasShard?.(r) ?? !1;
273
- return [{
274
- shardId: r,
275
- mode: "advisory",
276
- holder: s ? `this runner (pid ${a})` : `pid ${a} from ${o}`,
277
- own: s,
278
- since: V(n.state_change),
279
- detail: {
280
- pid: a,
281
- clientAddr: n.client_addr ?? null,
282
- applicationName: n.application_name ?? null,
283
- state: n.state ?? null,
284
- waitEventType: n.wait_event_type ?? null,
285
- backendStart: V(n.backend_start)?.toISOString() ?? null
286
- }
287
- }];
288
- });
289
- });
290
- };
291
- return {
292
- ownAddress: e.ownAddress,
293
- shardLockMode: e.shardLockMode,
294
- messagesFor: f,
295
- unreadMessages: m,
296
- shardLockHolders: (t) => t.length === 0 || e.runnerStorage !== "sql" ? c.succeed([]) : e.shardLockMode === "row" ? h(t) : e.shardLockMode === "advisory" && a ? g(t) : c.succeed([]),
297
- runners: () => e.runnerStorage === "sql" ? o(t`SELECT address, last_heartbeat, healthy FROM cluster_runners ORDER BY last_heartbeat DESC`).pipe(c.map((e) => e.map((e) => {
298
- let t = e;
299
- return {
300
- address: String(t.address ?? ""),
301
- lastHeartbeat: V(t.last_heartbeat),
302
- healthy: H(t.healthy)
303
- };
304
- }))) : c.succeed([]),
305
- discardMessages: (e) => {
306
- let n = e.unreadForMs === void 0 ? t.literal("") : t`AND last_read IS NOT NULL AND last_read < ${u(Math.max(1, Math.ceil(e.unreadForMs / 1e3)))}`;
307
- return c.gen(function* () {
308
- let r = (yield* t`
309
- SELECT id FROM cluster_messages
310
- WHERE entity_type = ${e.entityType} AND entity_id = ${e.entityId}
311
- AND processed = ${d}
312
- ${n}
313
- `).map((e) => e.id).filter((e) => e != null);
314
- return r.length === 0 ? 0 : (yield* t`DELETE FROM cluster_replies WHERE ${t.in("request_id", r)}`, yield* t`DELETE FROM cluster_messages WHERE ${t.in("id", r)}`, r.length);
315
- }).pipe(t.withTransaction, c.catchAll(() => c.succeed(0)));
316
- }
317
- };
318
- }), W = se({ scope: "voltro:workflow" }), ge = 34e3, G = (e) => {
319
- let t = e.runnerListenHost ?? process.env.POD_IP ?? process.env.VOLTRO_WORKFLOW_RUNNER_HOST ?? "localhost";
320
- return {
321
- host: t,
322
- port: e.runnerListenPort ?? 34e3,
323
- localhostRisk: e.runnerStorage === "sql" && (t === "localhost" || t === "127.0.0.1")
324
- };
325
- }, _e = (e) => {
326
- let t = e.sqliteFamily ? "memory" : "sql", n = e.override ?? process.env.VOLTRO_WORKFLOW_RUNNER_STORAGE;
327
- if (n === void 0 || n.trim() === "") return t;
328
- let r = n.trim().toLowerCase();
329
- if (r !== "memory" && r !== "sql") throw Error(`VOLTRO_WORKFLOW_RUNNER_STORAGE must be 'memory' or 'sql', got '${n}'.`);
330
- if (r === "sql" && e.sqliteFamily) throw Error("VOLTRO_WORKFLOW_RUNNER_STORAGE='sql' is unsupported on sqlite / turso — @effect/cluster has no sqlite advisory-lock branch. Use 'memory'.");
331
- return r;
332
- }, K = (e = process.env.VOLTRO_WORKFLOW_SHARD_LOCK) => {
333
- if (e === void 0 || e.trim() === "") return "auto";
334
- let t = e.trim().toLowerCase();
335
- if (t === "auto" || t === "row" || t === "advisory") return t;
336
- throw Error(`VOLTRO_WORKFLOW_SHARD_LOCK must be 'auto', 'row', or 'advisory', got '${e}'.`);
337
- }, q = c.gen(function* () {
338
- let e = yield* (yield* p.SqlClient)`SELECT @@wsrep_on AS wsrep`.pipe(c.catchAll(() => c.succeed([])));
339
- if (e.length === 0) return !1;
340
- let t = e[0].wsrep;
341
- return t === 1 || t === "1" || t === !0 || t === "ON" || t === "on";
342
- }), J = (e) => c.gen(function* () {
343
- return e.runnerStorage === "memory" ? !1 : e.mode === "row" ? !0 : e.mode === "advisory" || e.dialectId !== "mysql" && e.dialectId !== "mariadb" ? !1 : yield* q;
344
- }), ve = (e) => ({
345
- coordination: e.disableAdvisory ? "row-based" : "advisory",
346
- dialect: e.dialectId ?? "unknown",
347
- mode: e.mode,
348
- ...e.disableAdvisory && e.mode === "auto" ? { reason: "wsrep/Galera cluster detected" } : {}
349
- }), ye = () => {
350
- let e = (e) => {
351
- if (e === void 0) return;
352
- let t = Number(e);
353
- return Number.isFinite(t) && t > 0 ? t : void 0;
354
- }, t = e(process.env.VOLTRO_WORKFLOW_FAILOVER_LEASE), n = e(process.env.VOLTRO_WORKFLOW_FAILOVER_HEARTBEAT), r = e(process.env.VOLTRO_WORKFLOW_POLL_INTERVAL);
355
- return {
356
- ...t === void 0 ? {} : { failoverLeaseSeconds: t },
357
- ...n === void 0 ? {} : { failoverHeartbeatSeconds: n },
358
- ...r === void 0 ? {} : { messagePollSeconds: r }
359
- };
360
- }, be = c.flatMap(x.Sharding, (e) => e.pollStorage), Y = /* @__PURE__ */ new Set(), xe = (e) => {
361
- let t = G({
362
- runnerStorage: e.runnerStorage,
363
- runnerListenHost: e.runnerListenHost,
364
- runnerListenPort: e.runnerListenPort
365
- }), i = t.host, a = t.port;
366
- t.localhostRisk && !Y.has(i) && (Y.add(i), W.warn("runner host is not routable from other pods with SQL cluster storage — a workflow cannot resume on another pod after a reschedule. Inject POD_IP via the K8s downward API (fieldRef: status.podIP) or set runnerListenHost / VOLTRO_WORKFLOW_RUNNER_HOST.", { runnerHost: i }));
367
- let d = me({ runnerStorage: e.runnerStorage }).pipe(l.provide(e.sqlClientLayer)), f = e.shardLockMode ?? K(), p = l.unwrapEffect(J({
368
- dialectId: e.dialectId,
369
- runnerStorage: e.runnerStorage,
370
- mode: f
371
- }).pipe(c.map((t) => {
372
- e.runnerStorage === "sql" && W.info("shard-lock coordination", ve({
373
- disableAdvisory: t,
374
- dialectId: e.dialectId,
375
- mode: f
376
- }));
377
- let r = l.effect(n, c.flatMap(x.Sharding, (n) => U({
378
- ownAddress: b.make(i, a).toString(),
379
- shardLockMode: e.runnerStorage === "sql" ? t ? "row" : "advisory" : "none",
380
- runnerStorage: e.runnerStorage,
381
- dialectId: e.dialectId,
382
- hasShard: (e) => n.hasShardId(te.ShardId.fromString(e))
383
- }))), o = re.layer({
384
- runnerStorage: e.runnerStorage,
385
- shardingConfig: {
386
- runnerAddress: u.some(b.make(i, a)),
387
- runnerListenAddress: u.some(b.make(i, a)),
388
- ...t ? { shardLockDisableAdvisory: !0 } : {},
389
- ...e.failoverLeaseSeconds === void 0 ? {} : { shardLockExpiration: s.seconds(e.failoverLeaseSeconds) },
390
- ...e.failoverHeartbeatSeconds === void 0 ? {} : { shardLockRefreshInterval: s.seconds(e.failoverHeartbeatSeconds) },
391
- ...e.messagePollSeconds === void 0 ? {} : { entityMessagePollInterval: s.seconds(e.messagePollSeconds) }
392
- }
393
- });
394
- return l.merge(r, l.effect(T, pe)).pipe(l.provideMerge(o));
395
- }))).pipe(l.provide(d)), m = e.extraShardingLayers ?? [], h = m.length === 0 ? g.layer : l.mergeAll(g.layer, ...m), _ = p.pipe(l.map((e) => o.add(e, x.Sharding, r(o.get(e, x.Sharding)))));
396
- return h.pipe(l.provideMerge(_));
397
- }, Se = (e) => m.make({
398
- name: e.name,
399
- cron: e.cron,
400
- execute: c.promise(() => e.execute()),
401
- skipIfOlderThan: e.skipIfOlderThan ?? s.days(1)
402
- }), X = (e, t) => `${e}/${t}`, Ce = (e, t, n) => {
403
- let r = v.make(t), i = o.get(e.annotations, h.ShardGroup)(r);
404
- return new _.EntityAddress({
405
- entityType: ee.EntityType.make(`Workflow/${e.name}`),
406
- entityId: r,
407
- shardId: n.getShardId(r, i)
408
- });
409
- }, Z = f.decodeUnknownOption(f.Exit({
410
- success: f.Unknown,
411
- failure: f.Unknown,
412
- defect: f.Unknown
413
- })), we = (e) => {
414
- let t = (e) => e && typeof e == "object" && "_tag" in e && typeof e._tag == "string" ? e._tag : void 0, n = Z(e);
415
- if (u.isNone(n)) return "unrecognized-reply";
416
- if (n.value._tag === "Failure") return "failed";
417
- let r = n.value.value, i = t(r);
418
- if (i === "Suspended") return "suspended";
419
- if (i === "Complete") {
420
- let e = r.exit, t = Z(e);
421
- return u.isNone(t) ? "unrecognized-reply" : t.value._tag === "Success" ? "succeeded" : "failed";
422
- }
423
- return "unrecognized-reply";
424
- }, Q = (e) => $(e.workflow, e.executionId, e.failedActivities), Te = (e) => $(e.workflow, e.executionId, e.resetActivities), $ = (e, t, n) => c.gen(function* () {
425
- let r = yield* x.Sharding, i = yield* y.MessageStorage, a = Ce(e, t, r), o = yield* i.requestIdForPrimaryKey({
426
- address: a,
427
- tag: "run",
428
- id: ""
429
- }).pipe(c.orDie);
430
- if (u.isNone(o)) return {
431
- redriven: !1,
432
- runReplyCleared: !1,
433
- activitiesReset: 0,
434
- reason: "no-journal"
435
- };
436
- let s = (yield* i.repliesForUnfiltered([o.value]).pipe(c.orDie)).filter((e) => e._tag === "WithExit").at(-1);
437
- if (s === void 0) return {
438
- redriven: !1,
439
- runReplyCleared: !1,
440
- activitiesReset: 0,
441
- reason: "no-reply"
442
- };
443
- let l = we(s.exit);
444
- if (l !== "failed") return {
445
- redriven: !1,
446
- runReplyCleared: !1,
447
- activitiesReset: 0,
448
- reason: l
449
- };
450
- let d = /* @__PURE__ */ new Set();
451
- for (let e of n) {
452
- let t = yield* i.requestIdForPrimaryKey({
453
- address: a,
454
- tag: "activity",
455
- id: X(e.name, e.attempt)
456
- }).pipe(c.orDie);
457
- if (u.isNone(t)) return {
458
- redriven: !1,
459
- runReplyCleared: !1,
460
- activitiesReset: 0,
461
- reason: "activity-not-found"
462
- };
463
- d.add(t.value);
464
- }
465
- let f = 0;
466
- for (let e of d) {
467
- if (!(yield* r.reset(e))) return {
468
- redriven: !1,
469
- runReplyCleared: !1,
470
- activitiesReset: f,
471
- reason: "activity-reset-unconfirmed"
472
- };
473
- f++;
474
- }
475
- let p = yield* r.reset(o.value);
476
- return p ? (yield* r.pollStorage, {
477
- redriven: !0,
478
- runReplyCleared: p,
479
- activitiesReset: f,
480
- reason: null
481
- }) : {
482
- redriven: !1,
483
- runReplyCleared: !1,
484
- activitiesReset: f,
485
- reason: "run-reset-unconfirmed"
486
- };
487
- });
488
- //#endregion
489
- export { ge as a, be as c, _e as d, J as f, T as h, Se as i, ye as l, U as m, Q as n, q as o, xe as p, Te as r, K as s, X as t, G as u };