@voltro/workflow 0.69.0 → 0.70.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.69.0",
3
+ "version": "0.70.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.69.0",
55
- "@voltro/logger": "0.69.0",
56
- "@voltro/protocol": "0.69.0"
54
+ "@voltro/database": "0.70.0",
55
+ "@voltro/logger": "0.70.0",
56
+ "@voltro/protocol": "0.70.0"
57
57
  },
58
58
  "peerDependencies": {
59
59
  "effect": "^3.22.1",
@@ -1,177 +0,0 @@
1
- import { Context as e, Duration as t, Effect as n, Layer as r, Option as i } from "effect";
2
- import { ClusterCron as a, ClusterSchema as o, ClusterWorkflowEngine as s, EntityAddress as c, EntityId as l, EntityType as u, MessageStorage as d, RunnerAddress as f, Sharding as p, ShardingConfig as m, SingleRunner as h, SqlMessageStorage as g, SqlRunnerStorage as _ } from "@effect/cluster";
3
- import { SqlClient as v } from "@effect/sql";
4
- import { fnv1a64 as y, resolveMigrationLockSchema as b } from "@voltro/database/sql";
5
- import { createLogger as x } from "@voltro/logger";
6
- //#region src/clusterMigrationGate.ts
7
- var S = 6322741009312601n, C = () => {
8
- let e = b();
9
- return e === void 0 ? S : y(`voltro_cluster_migration_lock:${e}`) & 9223372036854775807n;
10
- }, w = () => {
11
- let e = `voltro_cluster_migration_${S.toString(36)}`, t = b();
12
- if (t === void 0) return e;
13
- let n = `${e}_${t}`;
14
- return /^[A-Za-z0-9_]+$/.test(t) && n.length <= 64 ? n : `${e}_h${y(`voltro_cluster_migration_lock:${t}`).toString(16)}`;
15
- }, T = () => {
16
- let e = Number(process.env.VOLTRO_CLUSTER_MIGRATION_LOCK_TIMEOUT_MS);
17
- return Number.isFinite(e) && e > 0 ? e : 6e4;
18
- }, E = 250, D = (e) => e[0]?.[0], O = (e, t) => e.onDialectOrElse({
19
- sqlite: () => n.void,
20
- mysql: () => t.executeValues(`SELECT GET_LOCK('${w()}', ${Math.ceil(T() / 1e3)})`, []).pipe(n.flatMap((e) => D(e) === 1 || D(e) === "1" ? n.void : n.die(/* @__PURE__ */ Error(`could not acquire cluster migration lock '${w()}' (GET_LOCK timed out)`)))),
21
- mssql: () => t.executeValues(`DECLARE @res int; EXEC @res = sp_getapplock @Resource = '${w()}', @LockMode = 'Exclusive', @LockOwner = 'Session', @LockTimeout = ${T()}; SELECT @res`, []).pipe(n.flatMap((e) => Number(D(e) ?? -999) >= 0 ? n.void : n.die(/* @__PURE__ */ Error(`could not acquire cluster migration lock '${w()}' (sp_getapplock ${String(D(e))})`)))),
22
- orElse: () => {
23
- let e = C().toString(), r = Date.now(), i = t.executeValues(`SELECT pg_try_advisory_lock(${e}::bigint)`, []).pipe(n.flatMap((e) => {
24
- let t = D(e);
25
- return t === !0 || t === "t" || t === 1 ? n.void : Date.now() - r >= T() ? n.die(/* @__PURE__ */ Error(`could not acquire the cluster migration advisory lock within ${Math.round(T() / 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.)`)) : n.sleep(`${E} millis`).pipe(n.zipRight(i));
26
- }));
27
- return i;
28
- }
29
- }), k = (e, t) => e.onDialectOrElse({
30
- sqlite: () => n.void,
31
- mysql: () => n.asVoid(t.executeValues(`SELECT RELEASE_LOCK('${w()}')`, [])),
32
- mssql: () => n.asVoid(t.executeValues(`EXEC sp_releaseapplock @Resource = '${w()}', @LockOwner = 'Session'`, [])),
33
- orElse: () => n.asVoid(t.executeValues(`SELECT pg_advisory_unlock(${C().toString()}::bigint)`, []))
34
- }), A = (e, t) => e.onDialectOrElse({
35
- sqlite: () => !0,
36
- orElse: () => !1
37
- }) ? t : n.scoped(n.gen(function* () {
38
- let r = yield* e.reserve;
39
- return yield* n.acquireRelease(O(e, r), () => k(e, r).pipe(n.orDie)), yield* t;
40
- })), ee = (e) => e === "sql" ? ["cluster_messages", "cluster_runners"] : ["cluster_messages"], j = (e, t) => n.forEach(ee(t), (t) => e`SELECT 1 FROM ${e(t)}`.pipe(n.as(!0), n.catchAll(() => n.succeed(!1))), { concurrency: 1 }).pipe(n.map((e) => e.every(Boolean))), M = (e, t) => n.scoped(n.asVoid(r.build(t.pipe(r.provide(m.layerDefaults), r.provide(r.succeed(v.SqlClient, e)))))), N = (e, t) => M(e, g.layer).pipe(n.zipRight(t === "sql" ? M(e, _.layer) : n.void)), P = (e) => r.effect(v.SqlClient, n.gen(function* () {
41
- let t = yield* v.SqlClient;
42
- return (yield* j(t, e.runnerStorage)) || (yield* A(t, N(t, e.runnerStorage))), t;
43
- })), F = x({ scope: "voltro:workflow" }), I = 34e3, L = (e) => {
44
- let t = e.runnerListenHost ?? process.env.POD_IP ?? process.env.VOLTRO_WORKFLOW_RUNNER_HOST ?? "localhost";
45
- return {
46
- host: t,
47
- port: e.runnerListenPort ?? 34e3,
48
- localhostRisk: e.runnerStorage === "sql" && (t === "localhost" || t === "127.0.0.1")
49
- };
50
- }, R = (e) => {
51
- let t = e.sqliteFamily ? "memory" : "sql", n = e.override ?? process.env.VOLTRO_WORKFLOW_RUNNER_STORAGE;
52
- if (n === void 0 || n.trim() === "") return t;
53
- let r = n.trim().toLowerCase();
54
- if (r !== "memory" && r !== "sql") throw Error(`VOLTRO_WORKFLOW_RUNNER_STORAGE must be 'memory' or 'sql', got '${n}'.`);
55
- 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'.");
56
- return r;
57
- }, z = (e = process.env.VOLTRO_WORKFLOW_SHARD_LOCK) => {
58
- if (e === void 0 || e.trim() === "") return "auto";
59
- let t = e.trim().toLowerCase();
60
- if (t === "auto" || t === "row" || t === "advisory") return t;
61
- throw Error(`VOLTRO_WORKFLOW_SHARD_LOCK must be 'auto', 'row', or 'advisory', got '${e}'.`);
62
- }, B = n.gen(function* () {
63
- let e = yield* (yield* v.SqlClient)`SELECT @@wsrep_on AS wsrep`.pipe(n.catchAll(() => n.succeed([])));
64
- if (e.length === 0) return !1;
65
- let t = e[0].wsrep;
66
- return t === 1 || t === "1" || t === !0 || t === "ON" || t === "on";
67
- }), V = (e) => n.gen(function* () {
68
- return e.runnerStorage === "memory" ? !1 : e.mode === "row" ? !0 : e.mode === "advisory" || e.dialectId !== "mysql" && e.dialectId !== "mariadb" ? !1 : yield* B;
69
- }), H = (e) => ({
70
- coordination: e.disableAdvisory ? "row-based" : "advisory",
71
- dialect: e.dialectId ?? "unknown",
72
- mode: e.mode,
73
- ...e.disableAdvisory && e.mode === "auto" ? { reason: "wsrep/Galera cluster detected" } : {}
74
- }), U = () => {
75
- let e = (e) => {
76
- if (e === void 0) return;
77
- let t = Number(e);
78
- return Number.isFinite(t) && t > 0 ? t : void 0;
79
- }, t = e(process.env.VOLTRO_WORKFLOW_FAILOVER_LEASE), n = e(process.env.VOLTRO_WORKFLOW_FAILOVER_HEARTBEAT), r = e(process.env.VOLTRO_WORKFLOW_POLL_INTERVAL);
80
- return {
81
- ...t === void 0 ? {} : { failoverLeaseSeconds: t },
82
- ...n === void 0 ? {} : { failoverHeartbeatSeconds: n },
83
- ...r === void 0 ? {} : { messagePollSeconds: r }
84
- };
85
- }, W = n.flatMap(p.Sharding, (e) => e.pollStorage), G = /* @__PURE__ */ new Set(), K = (e) => {
86
- let a = L({
87
- runnerStorage: e.runnerStorage,
88
- runnerListenHost: e.runnerListenHost,
89
- runnerListenPort: e.runnerListenPort
90
- }), o = a.host, c = a.port;
91
- a.localhostRisk && !G.has(o) && (G.add(o), F.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: o }));
92
- let l = P({ runnerStorage: e.runnerStorage }).pipe(r.provide(e.sqlClientLayer)), u = e.shardLockMode ?? z(), d = r.unwrapEffect(V({
93
- dialectId: e.dialectId,
94
- runnerStorage: e.runnerStorage,
95
- mode: u
96
- }).pipe(n.map((n) => (e.runnerStorage === "sql" && F.info("shard-lock coordination", H({
97
- disableAdvisory: n,
98
- dialectId: e.dialectId,
99
- mode: u
100
- })), h.layer({
101
- runnerStorage: e.runnerStorage,
102
- shardingConfig: {
103
- runnerAddress: i.some(f.make(o, c)),
104
- runnerListenAddress: i.some(f.make(o, c)),
105
- ...n ? { shardLockDisableAdvisory: !0 } : {},
106
- ...e.failoverLeaseSeconds === void 0 ? {} : { shardLockExpiration: t.seconds(e.failoverLeaseSeconds) },
107
- ...e.failoverHeartbeatSeconds === void 0 ? {} : { shardLockRefreshInterval: t.seconds(e.failoverHeartbeatSeconds) },
108
- ...e.messagePollSeconds === void 0 ? {} : { entityMessagePollInterval: t.seconds(e.messagePollSeconds) }
109
- }
110
- }))))).pipe(r.provide(l)), p = e.extraShardingLayers ?? [];
111
- return (p.length === 0 ? s.layer : r.mergeAll(s.layer, ...p)).pipe(r.provideMerge(d));
112
- }, q = (e) => a.make({
113
- name: e.name,
114
- cron: e.cron,
115
- execute: n.promise(() => e.execute()),
116
- skipIfOlderThan: e.skipIfOlderThan ?? t.days(1)
117
- }), J = (e, t) => `${e}/${t}`, Y = (t, n, r) => {
118
- let i = l.make(n), a = e.get(t.annotations, o.ShardGroup)(i);
119
- return new c.EntityAddress({
120
- entityType: u.EntityType.make(`Workflow/${t.name}`),
121
- entityId: i,
122
- shardId: r.getShardId(i, a)
123
- });
124
- }, X = (e) => {
125
- let t = (e) => e && typeof e == "object" && "_tag" in e ? String(e._tag) : void 0, n = t(e);
126
- if (n === "Failure" || n !== "Success") return "failed";
127
- let r = e.value, i = t(r);
128
- if (i === "Suspended") return "suspended";
129
- if (i === "Complete") {
130
- let e = r.exit;
131
- return t(e) === "Success" ? "succeeded" : "failed";
132
- }
133
- return "failed";
134
- }, Z = (e) => $(e.workflow, e.executionId, e.failedActivities), Q = (e) => $(e.workflow, e.executionId, e.resetActivities), $ = (e, t, r) => n.gen(function* () {
135
- let a = yield* p.Sharding, o = yield* d.MessageStorage, s = Y(e, t, a), c = yield* o.requestIdForPrimaryKey({
136
- address: s,
137
- tag: "run",
138
- id: ""
139
- }).pipe(n.orElseSucceed(() => i.none()));
140
- if (i.isNone(c)) return {
141
- redriven: !1,
142
- runReplyCleared: !1,
143
- activitiesReset: 0,
144
- reason: "no-journal"
145
- };
146
- let l = (yield* o.repliesForUnfiltered([c.value]).pipe(n.orElseSucceed(() => []))).filter((e) => e._tag === "WithExit").at(-1);
147
- if (l === void 0) return {
148
- redriven: !1,
149
- runReplyCleared: !1,
150
- activitiesReset: 0,
151
- reason: "no-reply"
152
- };
153
- if (X(l.exit) === "succeeded") return {
154
- redriven: !1,
155
- runReplyCleared: !1,
156
- activitiesReset: 0,
157
- reason: "succeeded"
158
- };
159
- let u = 0;
160
- for (let e of r) {
161
- let t = yield* o.requestIdForPrimaryKey({
162
- address: s,
163
- tag: "activity",
164
- id: J(e.name, e.attempt)
165
- }).pipe(n.orElseSucceed(() => i.none()));
166
- i.isSome(t) && (yield* a.reset(t.value)) && (u += 1);
167
- }
168
- let f = yield* a.reset(c.value);
169
- return yield* a.pollStorage, {
170
- redriven: !0,
171
- runReplyCleared: f,
172
- activitiesReset: u,
173
- reason: null
174
- };
175
- });
176
- //#endregion
177
- export { I as a, W as c, R as d, V as f, q as i, U as l, Z as n, B as o, K as p, Q as r, z as s, J as t, L as u };