@voltro/workflow 0.86.0 → 0.87.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.
@@ -0,0 +1,58 @@
1
+ import { captureWorkflowShardGroups as e } from "./clusterShardGroups.js";
2
+ import { ShardId as t } from "effect/cluster";
3
+ //#region src/workflowShardRelocation.ts
4
+ var n = "Workflow/-/DurableClock", r = (r, i) => {
5
+ let a = /* @__PURE__ */ new Map();
6
+ for (let t of i) {
7
+ let n = t.entity === "workflow" ? "Workflow/" : t.entity === "cron" ? "ClusterCron/" : void 0;
8
+ if (!n || !t.name || t.name.length > 138 || t.name.startsWith("-/") || t.name === "*" && t.entity !== "cron" || a.has(n + t.name) || t.from === t.to) throw Error("Invalid journal relocation route");
9
+ e({
10
+ available: [t.from, t.to],
11
+ assigned: [t.to]
12
+ }, !1), a.set(n + t.name, { ...t });
13
+ }
14
+ let o = /* @__PURE__ */ new Map();
15
+ for (let e of r) {
16
+ if (!/^[1-9][0-9]*$/.test(e.id) || o.has(e.id) || ![
17
+ 0,
18
+ 1,
19
+ 2
20
+ ].includes(e.kind) || !e.entityId) throw Error("Invalid journal inventory identity");
21
+ o.set(e.id, e);
22
+ }
23
+ let s = (e) => {
24
+ if (e.entityType !== n) return e.entityType.startsWith("Workflow/") || e.entityType.startsWith("ClusterCron/") ? e.entityType : void 0;
25
+ let r = e.kind === 0 ? e : o.get(e.requestId ?? "");
26
+ if (!r || r.kind !== 0 || r.entityType !== n || r.entityId !== e.entityId) throw Error("Clock control message has no matching request in the fenced inventory");
27
+ if (r.payload === null) throw Error("Clock request has no workflow owner");
28
+ let i = JSON.parse(r.payload);
29
+ if (!i || typeof i != "object" || !("workflowName" in i) || typeof i.workflowName != "string" || !i.workflowName) throw Error("Invalid clock workflow owner");
30
+ if (r.shardId !== e.shardId) {
31
+ let n = a.get("Workflow/" + i.workflowName), o = t.fromString(r.shardId), s = t.fromString(e.shardId);
32
+ if (!n || o.id !== s.id || !Number.isSafeInteger(o.id) || o.id < 1 || o.toString() !== r.shardId || s.toString() !== e.shardId || ![n.from, n.to].includes(o.group) || ![n.from, n.to].includes(s.group)) throw Error("Clock control routing disagrees with its request");
33
+ }
34
+ return "Workflow/" + i.workflowName;
35
+ }, c = r.map((e) => [e, s(e)]), l = [];
36
+ for (let [e, n] of c) {
37
+ let r = n === void 0 ? void 0 : a.get(n) ?? (n.startsWith("ClusterCron/") ? a.get("ClusterCron/*") : void 0);
38
+ if (!r) continue;
39
+ let i = t.fromString(e.shardId);
40
+ if (!Number.isSafeInteger(i.id) || i.id < 1 || i.toString() !== e.shardId) throw Error("Invalid persisted shard address");
41
+ if (i.group !== r.to) {
42
+ if (i.group !== r.from) throw Error("Journal routing changed outside the relocation plan");
43
+ l.push(Object.freeze({
44
+ messageId: e.id,
45
+ entityType: e.entityType,
46
+ entityId: e.entityId,
47
+ expectedShardId: e.shardId,
48
+ nextShardId: t.make(r.to, i.id).toString()
49
+ }));
50
+ }
51
+ }
52
+ return Object.freeze(l.sort((e, t) => {
53
+ let n = BigInt(e.messageId), r = BigInt(t.messageId);
54
+ return n < r ? -1 : +(n > r);
55
+ }));
56
+ };
57
+ //#endregion
58
+ export { r as planJournalShardRelocation };
@@ -0,0 +1,72 @@
1
+ import { planJournalShardRelocation as e } from "./workflowShardRelocation.js";
2
+ import { Context as t, Effect as n } from "effect";
3
+ import { SqlClient as r } from "effect/sql";
4
+ //#region src/workflowShardRelocationSql.ts
5
+ var i = class extends t.Service()("@voltro/workflow/JournalRoutingMaintenance") {}, a = (t, a = 256) => n.gen(function* () {
6
+ if (!Number.isInteger(a) || a < 1 || a > 1e3) return yield* n.fail(/* @__PURE__ */ Error("Journal relocation page size must be between 1 and 1000"));
7
+ let o = t.map((e) => Object.freeze({ ...e }));
8
+ yield* n.sync(() => e([], o));
9
+ let s = yield* i, c = (yield* r.SqlClient).withoutTransforms();
10
+ return yield* s.exclusive(c.withTransaction(n.gen(function* () {
11
+ yield* c.onDialectOrElse({
12
+ sqlite: () => c`UPDATE cluster_messages SET shard_id = shard_id WHERE 1 = 0`.pipe(n.asVoid),
13
+ orElse: () => n.void
14
+ });
15
+ let t = "0", r = 0;
16
+ for (;;) {
17
+ let i = yield* c.onDialectOrElse({
18
+ mssql: () => c`SELECT TOP (${a}) id, request_id, kind, entity_type, entity_id, shard_id, payload
19
+ FROM cluster_messages WITH (UPDLOCK, HOLDLOCK) WHERE id > ${t} ORDER BY id`,
20
+ sqlite: () => c`SELECT id, request_id, kind, entity_type, entity_id, shard_id, payload
21
+ FROM cluster_messages WHERE id > ${t} ORDER BY id LIMIT ${a}`,
22
+ orElse: () => c`SELECT id, request_id, kind, entity_type, entity_id, shard_id, payload
23
+ FROM cluster_messages WHERE id > ${t} ORDER BY id LIMIT ${a} FOR UPDATE`
24
+ });
25
+ if (i.length === 0) break;
26
+ let s = new Map(i.map((e) => [String(e.id), e])), l = new Set(s.keys());
27
+ for (let e of i) {
28
+ if (e.entity_type !== "Workflow/-/DurableClock" || Number(e.kind) === 0 || e.request_id === null || s.has(String(e.request_id))) continue;
29
+ let t = yield* c.onDialectOrElse({
30
+ mssql: () => c`SELECT id, request_id, kind, entity_type, entity_id, shard_id, payload
31
+ FROM cluster_messages WITH (UPDLOCK, HOLDLOCK) WHERE id = ${String(e.request_id)}`,
32
+ sqlite: () => c`SELECT id, request_id, kind, entity_type, entity_id, shard_id, payload
33
+ FROM cluster_messages WHERE id = ${String(e.request_id)}`,
34
+ orElse: () => c`SELECT id, request_id, kind, entity_type, entity_id, shard_id, payload
35
+ FROM cluster_messages WHERE id = ${String(e.request_id)} FOR UPDATE`
36
+ });
37
+ for (let e of t) s.set(String(e.id), e);
38
+ }
39
+ let u = [];
40
+ for (let e of s.values()) {
41
+ let t = Number(e.kind);
42
+ if (t !== 0 && t !== 1 && t !== 2) return yield* n.fail(/* @__PURE__ */ Error("Unknown journal message kind"));
43
+ u.push({
44
+ id: String(e.id),
45
+ requestId: e.request_id === null ? null : String(e.request_id),
46
+ kind: t,
47
+ entityType: e.entity_type,
48
+ entityId: e.entity_id,
49
+ shardId: e.shard_id,
50
+ payload: e.payload
51
+ });
52
+ }
53
+ let d = yield* n.sync(() => e(u, o));
54
+ for (let e of d) {
55
+ if (!l.has(e.messageId)) continue;
56
+ yield* c`UPDATE cluster_messages SET shard_id = ${e.nextShardId}
57
+ WHERE id = ${e.messageId} AND entity_type = ${e.entityType}
58
+ AND entity_id = ${e.entityId} AND shard_id = ${e.expectedShardId}`;
59
+ let t = yield* c`
60
+ SELECT shard_id, entity_type, entity_id FROM cluster_messages WHERE id = ${e.messageId}`;
61
+ if (t.length !== 1 || t[0]?.shard_id !== e.nextShardId || t[0]?.entity_type !== e.entityType || t[0]?.entity_id !== e.entityId) return yield* n.fail(/* @__PURE__ */ Error("Journal relocation observation changed; transaction rolled back"));
62
+ r++;
63
+ }
64
+ let f = i[i.length - 1], p = String(f.id);
65
+ if (!/^[1-9][0-9]*$/.test(p) || BigInt(p) <= BigInt(t)) return yield* n.fail(/* @__PURE__ */ Error("Journal inventory cursor did not advance"));
66
+ t = p;
67
+ }
68
+ return { moved: r };
69
+ })));
70
+ }).pipe(n.provideService(r.SafeIntegers, !0));
71
+ //#endregion
72
+ export { i as JournalRoutingMaintenance, a as relocateWorkflowJournal };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voltro/workflow",
3
- "version": "0.86.0",
3
+ "version": "0.87.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",
@@ -48,9 +48,9 @@
48
48
  "node": ">=24.0.0"
49
49
  },
50
50
  "dependencies": {
51
- "@voltro/database": "0.86.0",
52
- "@voltro/logger": "0.86.0",
53
- "@voltro/protocol": "0.86.0"
51
+ "@voltro/database": "0.87.0",
52
+ "@voltro/logger": "0.87.0",
53
+ "@voltro/protocol": "0.87.0"
54
54
  },
55
55
  "peerDependencies": {
56
56
  "effect": "4.0.1",