@voltro/workflow 0.85.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.
Files changed (103) hide show
  1. package/CHANGELOG.md +5987 -0
  2. package/THIRD-PARTY-NOTICES.md +1122 -31
  3. package/dist/admission.js +140 -0
  4. package/dist/admissionDrainer.js +705 -0
  5. package/dist/admissionGate.js +115 -0
  6. package/dist/admissionStore.js +347 -0
  7. package/dist/admissionSubmission.js +297 -0
  8. package/dist/admissionTables.js +137 -0
  9. package/dist/admissionTransaction.js +21 -0
  10. package/dist/awaitEvent.js +82 -0
  11. package/dist/budgetHold.js +136 -0
  12. package/dist/cancelOnPlan.js +75 -0
  13. package/dist/cancelOnSweep.js +188 -0
  14. package/dist/cancelPendingStart.js +50 -0
  15. package/dist/cluster.d.ts +111 -27
  16. package/dist/cluster.js +12 -2
  17. package/dist/clusterCron.js +19 -0
  18. package/dist/clusterDiagnostics.js +20 -0
  19. package/dist/clusterDiagnosticsSql.js +145 -0
  20. package/dist/clusterLayer.js +145 -0
  21. package/dist/clusterMigrationGate.js +61 -0
  22. package/dist/clusterRunnerLayer.js +31 -0
  23. package/dist/clusterShardGroups.js +33 -0
  24. package/dist/clusterTestSuite.d.ts +23 -0
  25. package/dist/clusterTestSuite.js +209 -179
  26. package/dist/controlMonitor.js +13 -0
  27. package/dist/controlRequest.js +367 -0
  28. package/dist/executionRefusal.js +162 -0
  29. package/dist/failureHandoff.js +183 -0
  30. package/dist/failureMessage.js +12 -0
  31. package/dist/failureRecovery.js +50 -0
  32. package/dist/failureReportStore.js +60 -0
  33. package/dist/flowControl.js +142 -0
  34. package/dist/forwardStartReceipts.js +41 -0
  35. package/dist/inMemoryLayer.js +7 -0
  36. package/dist/inMemoryRecorder.js +56 -0
  37. package/dist/index.d.ts +129 -30
  38. package/dist/index.js +50 -5
  39. package/dist/inspectWorkflow.js +101 -0
  40. package/dist/{journalEncryption-DTyhUzaS.js → journalEncryption.js} +30 -37
  41. package/dist/journalRoutingGuard.js +50 -0
  42. package/dist/journalRoutingLayout.js +47 -0
  43. package/dist/journalRoutingLock.js +39 -0
  44. package/dist/journalRoutingMaintenance.js +24 -0
  45. package/dist/journalRoutingMessageStorage.js +32 -0
  46. package/dist/journalRoutingNamespace.js +23 -0
  47. package/dist/journalRoutingRuntime.js +33 -0
  48. package/dist/journalRoutingSharding.js +28 -0
  49. package/dist/journalRoutingState.js +93 -0
  50. package/dist/journalRoutingTable.js +11 -0
  51. package/dist/journalRoutingTransaction.js +16 -0
  52. package/dist/messages.js +92 -0
  53. package/dist/nondeterminism.js +51 -0
  54. package/dist/parentClosure.js +141 -0
  55. package/dist/parentClosureRecovery.js +54 -0
  56. package/dist/pendingSubmission.js +85 -0
  57. package/dist/prepareSubmissionDispatch.js +17 -0
  58. package/dist/preparedExecution.js +14 -0
  59. package/dist/primitives.js +225 -2
  60. package/dist/queuedCancellationRequest.js +364 -0
  61. package/dist/queuedControl.js +21 -0
  62. package/dist/recorder.js +11 -0
  63. package/dist/residentExecutionEvidence.js +64 -0
  64. package/dist/retryPolicy.js +57 -0
  65. package/dist/runEventsTable.js +17 -0
  66. package/dist/runGeneration.js +345 -0
  67. package/dist/runRecording.js +615 -0
  68. package/dist/runStepsTable.js +28 -0
  69. package/dist/runnerStorageGuard.js +29 -0
  70. package/dist/runsTable.js +42 -0
  71. package/dist/scheduleDeclaration.js +44 -0
  72. package/dist/signalHint.js +23 -0
  73. package/dist/signals.js +70 -0
  74. package/dist/signalsSuspending.js +57 -0
  75. package/dist/sqliteWorkflowOwnership.js +26 -0
  76. package/dist/stalenessSweep.js +191 -0
  77. package/dist/startContextsTable.js +20 -0
  78. package/dist/startReceipt.js +143 -0
  79. package/dist/submissionRecovery.js +122 -0
  80. package/dist/submissionRejection.js +10 -0
  81. package/dist/submissionRetry.js +71 -0
  82. package/dist/suspensionUnavailable.js +9 -0
  83. package/dist/updates.js +87 -0
  84. package/dist/watermarkTable.js +9 -0
  85. package/dist/webCrypto.js +17 -0
  86. package/dist/workflowDelivery.js +24 -0
  87. package/dist/workflowExecutorRouting.js +33 -0
  88. package/dist/workflowJournal.js +124 -0
  89. package/dist/workflowJournalReset.js +111 -0
  90. package/dist/workflowReadSelections.js +12 -0
  91. package/dist/workflowRestartDispatch.js +35 -0
  92. package/dist/workflowRestartObservation.js +60 -0
  93. package/dist/workflowRestartPreparation.js +109 -0
  94. package/dist/workflowRestartReservation.js +427 -0
  95. package/dist/workflowResumeDispatch.js +30 -0
  96. package/dist/workflowRuntimeReadiness.js +12 -0
  97. package/dist/workflowShardRelocation.js +58 -0
  98. package/dist/workflowShardRelocationSql.js +72 -0
  99. package/package.json +4 -4
  100. package/dist/cluster-F_bhqVP7.js +0 -679
  101. package/dist/primitives-DX04WTWh.js +0 -507
  102. package/dist/src-C2NEyUM8.js +0 -3925
  103. package/dist/workflowReadSelections-CZAUjP7f.js +0 -2096
@@ -1,17 +1,10 @@
1
- import { Activity as e } from "effect/workflow";
2
- import { Cause as t, Context as n, Effect as r, Effectable as i, Schema as a, SchemaAST as o, SchemaGetter as s, SchemaIssue as c } from "effect";
3
- //#region src/failureMessage.ts
4
- var l = (e) => {
5
- let t = e?.message;
6
- if (typeof t == "string" && t.length > 0) return t;
7
- if (typeof e == "object" && e && typeof e.toJSON == "function") {
8
- let { _tag: t, ...n } = e.toJSON(), r = typeof t == "string" ? t : String(e);
9
- return Object.keys(n).length === 0 ? r : `${r}: ${JSON.stringify(n)}`;
10
- }
11
- return String(e);
12
- }, u = () => void 0, d = (e) => {
1
+ import { failureMessage as e } from "./failureMessage.js";
2
+ import { Activity as t } from "effect/workflow";
3
+ import { Cause as n, Context as r, Effect as i, Effectable as a, Schema as o, SchemaAST as s, SchemaGetter as c, SchemaIssue as l } from "effect";
4
+ //#region src/journalEncryption.ts
5
+ var u = () => void 0, d = (e) => {
13
6
  u = typeof e == "function" ? e : () => e;
14
- }, f = () => u(), p = n.Reference("@voltro/workflow/CurrentWorkflowEncrypted", { defaultValue: () => !1 }), m = { column: "workflow-journal" }, h = /* @__PURE__ */ new Set(), g = (e) => {
7
+ }, f = () => u(), p = r.Reference("@voltro/workflow/CurrentWorkflowEncrypted", { defaultValue: () => !1 }), m = { column: "workflow-journal" }, h = /* @__PURE__ */ new Set(), g = (e) => {
15
8
  h.add(e);
16
9
  }, _ = /* @__PURE__ */ new Set(), v = (e) => {
17
10
  _.add(e);
@@ -25,21 +18,21 @@ var l = (e) => {
25
18
  let r = u();
26
19
  if (r === void 0) throw S(t);
27
20
  return JSON.parse(r.decrypt(e, n));
28
- }, T = (e) => o.isVoid(e) || o.isNever(e) || o.isUndefined(e), E = (e, t) => {
21
+ }, T = (e) => s.isVoid(e) || s.isNever(e) || s.isUndefined(e), E = (e, t) => {
29
22
  if (T(e.ast)) return e;
30
23
  let n = (e, t) => {
31
24
  try {
32
- return r.succeed(t());
25
+ return i.succeed(t());
33
26
  } catch (t) {
34
- return r.fail(new c.InvalidValue({ message: t.message }, e));
27
+ return i.fail(new l.InvalidValue({ message: t.message }, e));
35
28
  }
36
29
  };
37
- return (e.ast.context?.isOptional === !0 ? a.optionalKey(a.Unknown) : a.Unknown).pipe(a.decodeTo(e, {
38
- decode: s.transformEffect((e) => n(e, () => Array.isArray(e) ? e.map((e) => w(e, t)) : w(e, t))),
39
- encode: s.transformEffect((e) => n(e, () => e === void 0 ? void 0 : C(e, t)))
30
+ return (e.ast.context?.isOptional === !0 ? o.optionalKey(o.Unknown) : o.Unknown).pipe(o.decodeTo(e, {
31
+ decode: c.transformEffect((e) => n(e, () => Array.isArray(e) ? e.map((e) => w(e, t)) : w(e, t))),
32
+ encode: c.transformEffect((e) => n(e, () => e === void 0 ? void 0 : C(e, t)))
40
33
  }));
41
34
  }, D = (e, t) => {
42
- let n = a.isSchema(t) ? t.fields : t, r = `workflow '${e}' payload`, i = {};
35
+ let n = o.isSchema(t) ? t.fields : t, r = `workflow '${e}' payload`, i = {};
43
36
  for (let [e, t] of Object.entries(n)) i[e] = E(t, `${r}.${e}`);
44
37
  return i;
45
38
  }, O = (e) => typeof e == "object" && !!e && !Array.isArray(e), k = (e, t) => {
@@ -102,21 +95,21 @@ var l = (e) => {
102
95
  let t = { ...e };
103
96
  for (let e of z) x(t[e]) && (t[e] = L(t[e], `workflow event ${e}`));
104
97
  return t;
105
- }, H = (t) => {
106
- let n = e.make({
107
- ...t,
108
- error: a.Defect()
109
- }), o, s = `wait '${t.name}'`, c = () => o ??= e.make({
110
- ...t,
111
- success: E(t.success, `${s} value`),
112
- error: a.Defect(),
113
- execute: t.execute.pipe(r.catchDefect((e) => r.die(M(e, `${s} defect`))))
98
+ }, H = (e) => {
99
+ let n = t.make({
100
+ ...e,
101
+ error: o.Defect()
102
+ }), r, s = `wait '${e.name}'`, c = () => r ??= t.make({
103
+ ...e,
104
+ success: E(e.success, `${s} value`),
105
+ error: o.Defect(),
106
+ execute: e.execute.pipe(i.catchDefect((e) => i.die(M(e, `${s} defect`))))
114
107
  }), l = n;
115
108
  return {
116
109
  ...n,
117
- ...i.Prototype({
110
+ ...a.Prototype({
118
111
  label: "Activity",
119
- evaluate: () => r.flatMap(p, (e) => e ? c().pipe(r.catchDefect((e) => r.die(N(e, `${s} defect`)))) : l)
112
+ evaluate: () => i.flatMap(p, (e) => e ? c().pipe(i.catchDefect((e) => i.die(N(e, `${s} defect`)))) : l)
120
113
  })
121
114
  };
122
115
  }, U = { column: "_voltro_workflow_run_steps:payload" }, W = (e) => {
@@ -143,12 +136,12 @@ var l = (e) => {
143
136
  t.payload = k(t.payload, "workflow run payload");
144
137
  } catch {}
145
138
  return "output" in t && (t.output = L(t.output, "workflow run result")), typeof t.errorMessage == "string" && (t.errorMessage = R(t.errorMessage)), t;
146
- }, K = (e, n) => {
147
- if (e === void 0 || !b(e)) {
148
- let e = t.isCause(n) ? t.squash(n) : n;
149
- return e instanceof Error && e.message.length > 0 ? `${e.name}: ${e.message}` : l(e);
139
+ }, K = (t, r) => {
140
+ if (t === void 0 || !b(t)) {
141
+ let t = n.isCause(r) ? n.squash(r) : r;
142
+ return t instanceof Error && t.message.length > 0 ? `${t.name}: ${t.message}` : e(t);
150
143
  }
151
- return `${n instanceof Error ? n.name : typeof n}: [withheld — workflow '${e}' is declared encrypted; the full cause is recorded sealed on its row]`;
144
+ return `${r instanceof Error ? r.name : typeof r}: [withheld — workflow '${t}' is declared encrypted; the full cause is recorded sealed on its row]`;
152
145
  };
153
146
  //#endregion
154
- export { A as C, L as D, d as E, l as O, C as S, E as T, k as _, H as a, I as b, x as c, v as d, g as f, w as g, V as h, F as i, b as l, R as m, U as n, f as o, N as p, m as r, y as s, p as t, K as u, G as v, D as w, B as x, M as y };
147
+ export { p as CurrentWorkflowEncrypted, j as SEALED_DEFECT_NAME, U as STEP_JOURNAL_BINDING, m as WORKFLOW_JOURNAL_BINDING, F as anyWorkflowEncrypted, H as encryptionAwareActivity, f as getWorkflowJournalCipher, y as isQueueEncrypted, x as isSealedJournalValue, b as isWorkflowEncrypted, K as loggableCause, v as markQueueEncrypted, g as markWorkflowEncrypted, N as openDefect, R as openErrorText, V as openEventPayload, w as openJournalValue, k as openPayloadFields, G as openWorkflowRow, M as sealDefect, I as sealErrorText, B as sealEventPayload, C as sealJournalValue, A as sealPayloadFields, D as sealedPayloadFields, E as sealedSchema, d as setWorkflowJournalCipher, L as tryOpenJournalValue };
@@ -0,0 +1,50 @@
1
+ import { Context as e, Deferred as t, Effect as n, Option as r, Schedule as i, Semaphore as a } from "effect";
2
+ //#region src/journalRoutingGuard.ts
3
+ var o = (e) => n.gen(function* () {
4
+ let r = e.pollMs ?? 1e3, o = e.verify;
5
+ if (!Number.isSafeInteger(r) || r < 1 || r > 3e4) return yield* n.fail(/* @__PURE__ */ Error("Invalid journal routing probe interval"));
6
+ let s = yield* t.make(), c = /* @__PURE__ */ new Set(), l = yield* a.make(1), u = !1, d = (e) => n.gen(function* () {
7
+ u = !0, yield* t.fail(s, Error(e));
8
+ }), f = l.withPermits(1)(n.gen(function* () {
9
+ if (u) return yield* t.await(s);
10
+ let e = yield* n.interruptible(o).pipe(n.timeout(r), n.isSuccess);
11
+ if (u) return yield* t.await(s);
12
+ if (!e) return yield* d("Journal routing lock ownership was lost"), yield* t.await(s);
13
+ })), p = (e) => n.uninterruptibleMask((r) => n.gen(function* () {
14
+ if (u) return yield* t.await(s);
15
+ let i = yield* t.make();
16
+ return c.add(i), yield* r(f.pipe(n.andThen(n.suspend(() => u ? t.await(s) : n.raceFirst(e, n.interruptible(t.await(s))))))).pipe(n.ensuring(n.gen(function* () {
17
+ c.delete(i), yield* t.succeed(i, void 0);
18
+ })));
19
+ })), m = (e) => n.uninterruptibleMask((r) => n.gen(function* () {
20
+ if (u) return yield* t.await(s);
21
+ let i = yield* t.make();
22
+ c.add(i);
23
+ let a = new AbortController(), o = Promise.resolve().then(() => {
24
+ if (u) throw Error("Journal routing admission is closed");
25
+ return e(a.signal);
26
+ });
27
+ return yield* r(n.tryPromise({
28
+ try: () => o,
29
+ catch: (e) => e
30
+ })).pipe(n.ensuring(n.gen(function* () {
31
+ a.abort(), yield* n.promise(() => o.then(() => void 0, () => void 0)), c.delete(i), yield* t.succeed(i, void 0);
32
+ })));
33
+ })), h = n.gen(function* () {
34
+ yield* d("Journal routing admission is closed"), yield* n.forEach([...c], (e) => t.await(e), { discard: !0 });
35
+ });
36
+ return yield* n.addFinalizer(() => h), yield* f.pipe(n.ignore, n.repeat(i.spaced(r)), n.forkScoped), {
37
+ run: p,
38
+ ownPromise: m,
39
+ check: f,
40
+ close: h,
41
+ isClosed: () => u
42
+ };
43
+ }), s = e.Service("@voltro/workflow/JournalRoutingGuard"), c = (e, t) => e === void 0 ? t : n.gen(function* () {
44
+ return yield* yield* e.run(n.exit(t.pipe(n.provideService(s, e)))).pipe(n.orDie);
45
+ }), l = (e) => n.flatMap(n.serviceOption(s), (t) => r.isSome(t) ? t.value.ownPromise(e).pipe(n.orDie) : n.promise(e)), u = (e) => n.flatMap(n.serviceOption(s), (t) => r.isSome(t) ? t.value.ownPromise(e) : n.tryPromise({
46
+ try: e,
47
+ catch: (e) => e
48
+ }));
49
+ //#endregion
50
+ export { s as JournalRoutingGuard, o as makeJournalRoutingGuard, l as ownedJournalRoutingPromise, c as protectJournalRoutingEffect, u as tryOwnedJournalRoutingPromise };
@@ -0,0 +1,47 @@
1
+ //#region src/journalRoutingLayout.ts
2
+ var e = (e) => {
3
+ if (e !== "default" && e !== "api-workloads-v1") throw Error("Unknown journal routing layout");
4
+ let t = e === "api-workloads-v1";
5
+ return Object.freeze({
6
+ id: e,
7
+ workflowGroup: "default",
8
+ webhookGroup: t ? "voltro.webhooks" : "default",
9
+ cronGroup: t ? "voltro.schedules" : "default",
10
+ available: Object.freeze(t ? [
11
+ "default",
12
+ "voltro.webhooks",
13
+ "voltro.schedules"
14
+ ] : ["default"])
15
+ });
16
+ }, t = (t, n) => {
17
+ let r = e(t), i = e(n), a = [];
18
+ return r.webhookGroup !== i.webhookGroup && a.push({
19
+ entity: "workflow",
20
+ name: "voltro.deliverWebhook",
21
+ from: r.webhookGroup,
22
+ to: i.webhookGroup
23
+ }), r.cronGroup !== i.cronGroup && a.push({
24
+ entity: "cron",
25
+ name: "*",
26
+ from: r.cronGroup,
27
+ to: i.cronGroup
28
+ }), Object.freeze(a.map((e) => Object.freeze(e)));
29
+ }, n = (t, n) => {
30
+ let r = e(t);
31
+ return n === "voltro.deliverWebhook" ? r.webhookGroup : r.workflowGroup;
32
+ }, r = (t, n) => {
33
+ let r = e(t), i = [
34
+ n.workflows,
35
+ n.schedules,
36
+ n.webhooks
37
+ ];
38
+ if (i.some((e) => typeof e != "boolean")) throw Error("Explicit executor selection is required");
39
+ if (t === "default" && i.some(Boolean) && !i.every(Boolean)) throw Error("Separate executor roles require the api-workloads-v1 journal routing transition");
40
+ let a = /* @__PURE__ */ new Set();
41
+ return n.workflows && a.add(r.workflowGroup), n.schedules && a.add(r.cronGroup), n.webhooks && a.add(r.webhookGroup), Object.freeze({
42
+ available: r.available,
43
+ assigned: Object.freeze([...a])
44
+ });
45
+ };
46
+ //#endregion
47
+ export { r as journalExecutorGroups, e as journalRoutingLayout, t as journalRoutingTransitions, n as journalWorkflowGroup };
@@ -0,0 +1,39 @@
1
+ import { Effect as e } from "effect";
2
+ import { SqlClient as t } from "effect/sql";
3
+ import { fnv1a64 as n } from "@voltro/database/sql";
4
+ //#region src/journalRoutingLock.ts
5
+ var r = (r) => e.gen(function* () {
6
+ let i = r.namespace, a = r.mode, o = r.timeoutMs ?? 3e4;
7
+ if (!i || i.length > 256 || !["shared", "exclusive"].includes(a) || !Number.isSafeInteger(o) || o < 1 || o > 3e5) return yield* e.fail(/* @__PURE__ */ Error("Invalid journal routing lock options"));
8
+ let s = yield* t.SqlClient, c = s.onDialectOrElse({
9
+ pg: () => "pg",
10
+ mssql: () => "mssql",
11
+ orElse: () => "unsupported"
12
+ });
13
+ if (c === "unsupported") return yield* e.fail(/* @__PURE__ */ Error("Journal routing locks require PostgreSQL or SQL Server"));
14
+ let l = (n("voltro:journal-routing:" + i) & 9223372036854775807n).toString(), u = "voltro-journal-routing-" + l, d = yield* s.reserve, f = a === "shared" ? "_shared" : "", p = c === "pg" ? `SELECT pg_try_advisory_lock${f}(${l}::bigint)` : `DECLARE @res int; EXEC @res = sp_getapplock @Resource = '${u}', @LockMode = '${a === "shared" ? "Shared" : "Exclusive"}', @LockOwner = 'Session', @LockTimeout = 0; SELECT @res`, m = c === "pg" ? `SELECT pg_advisory_unlock${f}(${l}::bigint)` : `DECLARE @res int; EXEC @res = sp_releaseapplock @Resource = '${u}', @LockOwner = 'Session'; SELECT @res`, h = performance.now(), g = e.gen(function* () {
15
+ for (;;) {
16
+ let t = (yield* d.executeValues(p, []))[0]?.[0];
17
+ if (c === "pg" ? t === !0 || t === "t" || t === 1 : typeof t == "number" && t >= 0) return;
18
+ if (c === "mssql" && t !== -1) return yield* e.fail(/* @__PURE__ */ Error("Journal routing application lock failed"));
19
+ let n = o - (performance.now() - h);
20
+ if (n <= 0) return yield* e.fail(/* @__PURE__ */ Error("Journal routing lock acquisition timed out"));
21
+ yield* e.sleep(Math.min(n, 100));
22
+ }
23
+ });
24
+ yield* e.acquireRelease(g, () => d.executeValues(m, []).pipe(e.flatMap((t) => {
25
+ let n = t[0]?.[0];
26
+ return (c === "pg" ? n === !0 || n === "t" || n === 1 : typeof n == "number" && n >= 0) ? e.void : e.die(/* @__PURE__ */ Error("Journal routing lock was lost before release"));
27
+ }), e.orDie));
28
+ let _ = c === "pg" ? `SELECT EXISTS (SELECT 1 FROM pg_locks WHERE locktype = 'advisory' AND pid = pg_backend_pid() AND granted AND classid = (${l}::bigint >> 32)::oid AND objid = (${l}::bigint & 4294967295)::oid AND objsubid = 1 AND mode = '${a === "shared" ? "ShareLock" : "ExclusiveLock"}')` : `SELECT APPLOCK_MODE('public', '${u}', 'Session')`;
29
+ return {
30
+ connection: d,
31
+ dialect: c,
32
+ verify: d.executeValues(_, []).pipe(e.flatMap((t) => {
33
+ let n = t[0]?.[0];
34
+ return (c === "pg" ? n === !0 || n === "t" || n === 1 : n === (a === "shared" ? "Shared" : "Exclusive")) ? e.void : e.fail(/* @__PURE__ */ Error("Journal routing lock ownership was lost"));
35
+ }))
36
+ };
37
+ });
38
+ //#endregion
39
+ export { r as acquireJournalRoutingLock };
@@ -0,0 +1,24 @@
1
+ import { withJournalRoutingTransaction as e } from "./journalRoutingTransaction.js";
2
+ import { acquireJournalRoutingLock as t } from "./journalRoutingLock.js";
3
+ import { JournalRoutingMaintenance as n } from "./workflowShardRelocationSql.js";
4
+ import { Effect as r, Option as i } from "effect";
5
+ import { SqlClient as a } from "effect/sql";
6
+ //#region src/journalRoutingMaintenance.ts
7
+ var o = (o, s = 3e4) => r.gen(function* () {
8
+ let c = yield* a.SqlClient;
9
+ return c.onDialectOrElse({
10
+ pg: () => !0,
11
+ orElse: () => !1
12
+ }) ? n.of({ exclusive: (n) => r.scoped(r.gen(function* () {
13
+ let a = yield* r.serviceOption(c.transactionService);
14
+ if (i.isSome(a)) return yield* r.fail(/* @__PURE__ */ Error("Journal maintenance cannot enter an ambient transaction"));
15
+ let { connection: l } = yield* t({
16
+ namespace: o,
17
+ mode: "exclusive",
18
+ timeoutMs: s
19
+ });
20
+ return yield* e(c, l, n);
21
+ })).pipe(r.provideService(a.SqlClient, c)) }) : yield* r.fail(/* @__PURE__ */ Error("Pinned journal maintenance currently requires PostgreSQL"));
22
+ });
23
+ //#endregion
24
+ export { o as makePostgresJournalRoutingMaintenance };
@@ -0,0 +1,32 @@
1
+ import { protectJournalRoutingEffect as e } from "./journalRoutingGuard.js";
2
+ import { Effect as t } from "effect";
3
+ import { MessageStorage as n } from "effect/cluster";
4
+ import { PersistenceError as r } from "effect/cluster/ClusterError";
5
+ //#region src/journalRoutingMessageStorage.ts
6
+ var i = (i, a) => {
7
+ let o = (e) => t.gen(function* () {
8
+ return yield* yield* a.run(t.exit(e)).pipe(t.mapError((e) => new r({ cause: e })));
9
+ });
10
+ return n.MessageStorage.of({
11
+ saveRequest: (e) => o(t.suspend(() => i.saveRequest(e))),
12
+ saveEnvelope: (e) => o(t.suspend(() => i.saveEnvelope(e))),
13
+ saveReply: (e) => o(t.suspend(() => i.saveReply(e))),
14
+ clearReplies: (e) => o(t.suspend(() => i.clearReplies(e))),
15
+ repliesFor: (e) => o(t.suspend(() => i.repliesFor(e))),
16
+ repliesForUnfiltered: (e) => o(t.suspend(() => i.repliesForUnfiltered(e))),
17
+ requestIdForPrimaryKey: (e) => o(t.suspend(() => i.requestIdForPrimaryKey(e))),
18
+ unprocessedMessages: (e) => o(t.suspend(() => i.unprocessedMessages(e))),
19
+ unprocessedMessagesById: (e) => o(t.suspend(() => i.unprocessedMessagesById(e))),
20
+ resetShards: (e) => o(t.suspend(() => i.resetShards(e))),
21
+ resetRequests: (e) => o(t.suspend(() => i.resetRequests(e))),
22
+ resetAddresses: (e) => o(t.suspend(() => i.resetAddresses(e))),
23
+ withTransaction: (t) => e(a, i.withTransaction(t)),
24
+ resetAddress: (e) => o(t.suspend(() => i.resetAddress(e))),
25
+ clearAddress: (e) => o(t.suspend(() => i.clearAddress(e))),
26
+ registerReplyHandler: (e) => i.registerReplyHandler(e),
27
+ unregisterReplyHandler: (e) => i.unregisterReplyHandler(e),
28
+ unregisterShardReplyHandlers: (e) => i.unregisterShardReplyHandlers(e)
29
+ });
30
+ };
31
+ //#endregion
32
+ export { i as guardJournalMessageStorage };
@@ -0,0 +1,23 @@
1
+ import { workflowJournalRoutingTable as e } from "./journalRoutingTable.js";
2
+ import { Effect as t } from "effect";
3
+ import { SqlClient as n } from "effect/sql";
4
+ //#region src/journalRoutingNamespace.ts
5
+ var r = t.gen(function* () {
6
+ let r = (yield* n.SqlClient).withoutTransforms();
7
+ if (!r.onDialectOrElse({
8
+ pg: () => !0,
9
+ orElse: () => !1
10
+ })) return yield* t.fail(/* @__PURE__ */ Error("Journal namespace resolution requires PostgreSQL"));
11
+ let i = yield* r`
12
+ SELECT n.nspname AS schema_name, current_schema() AS active_schema, c.relkind AS kind,
13
+ format('%I.%I', n.nspname, ${e.tableName}::text) AS routing_table
14
+ FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace
15
+ WHERE c.oid = to_regclass('cluster_messages')`, a = i[0];
16
+ return i.length !== 1 || !a || a.kind !== "r" || a.schema_name !== a.active_schema ? yield* t.fail(/* @__PURE__ */ Error("Journal table must exist in the active PostgreSQL schema without search-path fallback")) : Object.freeze({
17
+ namespace: `pg:${a.schema_name}:cluster_messages`,
18
+ schema: a.schema_name,
19
+ routingTable: r.literal(a.routing_table)
20
+ });
21
+ }), i = r.pipe(t.map((e) => e.namespace));
22
+ //#endregion
23
+ export { r as resolvePostgresJournalLocation, i as resolvePostgresJournalNamespace };
@@ -0,0 +1,33 @@
1
+ import { journalRoutingLayout as e, journalWorkflowGroup as t } from "./journalRoutingLayout.js";
2
+ import { Context as n } from "effect";
3
+ import { ClusterSchema as r } from "effect/cluster";
4
+ //#region src/journalRoutingRuntime.ts
5
+ var i = class extends n.Service()("@voltro/workflow/JournalRoutingLayout") {}, a = (e, i) => {
6
+ let a = n.add(e.annotations, r.ShardGroup, () => t(i, e._tag));
7
+ return new Proxy(e, { get(e, t, n) {
8
+ return t === "annotations" ? a : Reflect.get(e, t, n);
9
+ } });
10
+ }, o = (t, n) => (e(n), {
11
+ ...t,
12
+ register: (e, r) => t.register(a(e, n), r),
13
+ execute: (e, r) => t.execute(a(e, n), r),
14
+ poll: (e, r) => t.poll(a(e, n), r),
15
+ interrupt: (e, r) => t.interrupt(a(e, n), r),
16
+ resume: (e, r) => t.resume(a(e, n), r),
17
+ scheduleClock: (e, r) => t.scheduleClock(a(e, n), r)
18
+ }), s = (n, i) => {
19
+ let a = e(i), o = (e) => e.startsWith("Workflow/") && !e.startsWith("Workflow/-/") ? t(i, e.slice(9)) : e.startsWith("ClusterCron/") ? a.cronGroup : void 0;
20
+ return {
21
+ ...n,
22
+ makeClient: (e) => {
23
+ let t = o(e.type);
24
+ return n.makeClient(t === void 0 ? e : e.annotate(r.ShardGroup, () => t));
25
+ },
26
+ registerEntity: (e, t, i) => {
27
+ let a = o(e.type);
28
+ return n.registerEntity(a === void 0 ? e : e.annotate(r.ShardGroup, () => a), t, i);
29
+ }
30
+ };
31
+ };
32
+ //#endregion
33
+ export { i as JournalRoutingLayout, a as routeJournalWorkflow, s as withJournalEntityRouting, o as withJournalWorkflowRouting };
@@ -0,0 +1,28 @@
1
+ import { protectJournalRoutingEffect as e } from "./journalRoutingGuard.js";
2
+ import { Effect as t, Stream as n } from "effect";
3
+ import { Rpc as r, RpcSchema as i } from "effect/rpc";
4
+ //#region src/journalRoutingSharding.ts
5
+ var a = (a, o) => {
6
+ let s = (i) => {
7
+ if (typeof i == "object" && i && r.isWrapper(i)) return {
8
+ ...i,
9
+ value: s(i.value)
10
+ };
11
+ if (t.isEffect(i)) return e(o, i);
12
+ if (n.isStream(i)) {
13
+ let r = i;
14
+ return n.unwrap(t.map(e(o, n.toPull(r)), (r) => n.fromPull(t.succeed(e(o, r)))));
15
+ }
16
+ throw Error("Cluster handler must return an Effect, Stream or RPC wrapper");
17
+ };
18
+ return {
19
+ ...a,
20
+ registerEntity: (r, c, l) => e(o, t.suspend(() => a.registerEntity(r, c.pipe(t.map((e) => new Proxy(e, { get(e, a, c) {
21
+ let l = Reflect.get(e, a, c), u = typeof a == "string" ? r.protocol.requests.get(a) : void 0;
22
+ return !u || typeof l != "function" ? l : (...r) => o.isClosed() ? i.isStreamSchema(u.successSchema) ? n.fromEffect(t.interrupt) : t.interrupt : s(Reflect.apply(l, e, r));
23
+ } }))), l))),
24
+ registerSingleton: (n, r, i) => e(o, t.suspend(() => a.registerSingleton(n, e(o, r), i)))
25
+ };
26
+ };
27
+ //#endregion
28
+ export { a as withJournalRoutingGuard };
@@ -0,0 +1,93 @@
1
+ import { makeJournalRoutingGuard as e } from "./journalRoutingGuard.js";
2
+ import { workflowJournalRoutingRowId as t, workflowJournalRoutingTable as n } from "./journalRoutingTable.js";
3
+ import { journalRoutingLayout as r, journalRoutingTransitions as i } from "./journalRoutingLayout.js";
4
+ import { withJournalRoutingTransaction as a } from "./journalRoutingTransaction.js";
5
+ import { resolvePostgresJournalLocation as o, resolvePostgresJournalNamespace as s } from "./journalRoutingNamespace.js";
6
+ import { acquireJournalRoutingLock as c } from "./journalRoutingLock.js";
7
+ import { JournalRoutingMaintenance as l, relocateWorkflowJournal as u } from "./workflowShardRelocationSql.js";
8
+ import { makePostgresJournalRoutingMaintenance as d } from "./journalRoutingMaintenance.js";
9
+ import { Effect as f, Option as p } from "effect";
10
+ import { SqlClient as m } from "effect/sql";
11
+ //#region src/journalRoutingState.ts
12
+ var h = (e, t) => f.gen(function* () {
13
+ let r = yield* e`SELECT c.relkind AS kind
14
+ FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace
15
+ WHERE n.nspname = ${t} AND c.relname = ${n.tableName}`;
16
+ return r.length === 0 ? !1 : r.length !== 1 || r[0]?.kind !== "r" ? yield* f.fail(/* @__PURE__ */ Error("Journal routing metadata must be a table in the journal schema")) : !0;
17
+ }), g = (e) => {
18
+ if (!Number.isSafeInteger(e.generation) || e.generation < 0 || e.generation >= 2 ** 53 - 1 || !/^[a-z][a-z0-9._-]{0,63}$/.test(e.layout)) throw Error("Invalid journal routing state");
19
+ return r(e.layout), Object.freeze({
20
+ generation: e.generation,
21
+ layout: e.layout
22
+ });
23
+ }, _ = (e) => f.gen(function* () {
24
+ let n = yield* f.sync(() => g(e.expected)), r = yield* f.sync(() => g({
25
+ generation: n.generation + 1,
26
+ layout: e.nextLayout
27
+ })), a = yield* f.sync(() => i(n.layout, r.layout)), c = yield* m.SqlClient, p = c.withoutTransforms(), _ = yield* s, v = yield* d(_), y = l.of({ exclusive: (e) => v.exclusive(f.gen(function* () {
28
+ let i = yield* o;
29
+ if (i.namespace !== _) return yield* f.fail(/* @__PURE__ */ Error("Journal namespace changed during maintenance acquisition"));
30
+ if (yield* p`SELECT pg_advisory_xact_lock(6322741009312602::bigint)`, !(yield* h(p, i.schema))) return yield* f.fail(/* @__PURE__ */ Error("Workflow journal routing table is missing; apply the app schema first"));
31
+ yield* p`INSERT INTO ${i.routingTable} (id, namespace, generation, layout, relocation)
32
+ VALUES (${t(_)}, ${_}, 0, 'default', '[]') ON CONFLICT (namespace) DO NOTHING`;
33
+ let [s] = yield* p`
34
+ SELECT generation, layout FROM ${i.routingTable} WHERE namespace = ${_} FOR UPDATE`;
35
+ if (!s || String(s.generation) !== String(n.generation) || s.layout !== n.layout) return yield* f.fail(/* @__PURE__ */ Error("Journal routing generation changed; transition refused"));
36
+ let c = yield* e;
37
+ return yield* p`UPDATE ${i.routingTable} SET generation = ${r.generation}, layout = ${r.layout},
38
+ relocation = ${JSON.stringify(a)} WHERE namespace = ${_}`, c;
39
+ }).pipe(f.provideService(m.SqlClient, c))) });
40
+ return {
41
+ ...yield* u(a).pipe(f.provideService(l, y)),
42
+ state: r
43
+ };
44
+ }), v = () => f.gen(function* () {
45
+ let e = yield* m.SqlClient;
46
+ if (p.isSome(yield* f.serviceOption(e.transactionService))) return yield* f.fail(/* @__PURE__ */ Error("Journal routing admission cannot enter an ambient transaction"));
47
+ if (!e.onDialectOrElse({
48
+ pg: () => !0,
49
+ orElse: () => !1
50
+ })) return yield* f.fail(/* @__PURE__ */ Error("Journal routing generation admission currently requires PostgreSQL"));
51
+ let t = yield* s, n = yield* c({
52
+ namespace: t,
53
+ mode: "shared"
54
+ }), r = yield* a(e, n.connection, f.gen(function* () {
55
+ let n = yield* o;
56
+ if (n.namespace !== t) return yield* f.fail(/* @__PURE__ */ Error("Journal namespace changed during runtime admission"));
57
+ if (!(yield* h(e, n.schema))) return {
58
+ generation: 0,
59
+ layout: "default"
60
+ };
61
+ let r = yield* e`
62
+ SELECT generation, layout FROM ${n.routingTable} WHERE namespace = ${t}`;
63
+ return r.length === 0 ? {
64
+ generation: 0,
65
+ layout: "default"
66
+ } : r.length === 1 ? yield* f.sync(() => g({
67
+ generation: Number(r[0]?.generation),
68
+ layout: String(r[0]?.layout)
69
+ })) : yield* f.fail(/* @__PURE__ */ Error("Journal routing metadata has duplicate namespace rows"));
70
+ }));
71
+ return {
72
+ ...n,
73
+ state: r,
74
+ namespace: t
75
+ };
76
+ }), y = f.gen(function* () {
77
+ let e = yield* v();
78
+ return {
79
+ namespace: e.namespace,
80
+ state: e.state
81
+ };
82
+ }), b = (e) => f.gen(function* () {
83
+ let t = yield* f.sync(() => g(e)), n = yield* v();
84
+ return n.state.generation !== t.generation || n.state.layout !== t.layout ? yield* f.fail(/* @__PURE__ */ Error("Journal routing generation does not match this runtime")) : n;
85
+ }), x = (t) => f.gen(function* () {
86
+ let n = yield* b(t), r = yield* e({ verify: n.verify });
87
+ return {
88
+ ...n,
89
+ ...r
90
+ };
91
+ });
92
+ //#endregion
93
+ export { x as acquireGuardedPostgresJournalRoutingAdmission, b as acquirePostgresJournalRoutingAdmission, g as captureJournalRoutingState, y as readPostgresJournalRoutingState, _ as transitionPostgresJournalRouting };
@@ -0,0 +1,11 @@
1
+ import { bigint as e, derivedRowId as t, id as n, table as r, text as i } from "@voltro/database";
2
+ //#region src/journalRoutingTable.ts
3
+ var a = r("_voltro_workflow_journal_routing", {
4
+ id: n({ prefix: "wfjr" }),
5
+ namespace: i(),
6
+ generation: e(),
7
+ layout: i(),
8
+ relocation: i()
9
+ }).unique(["namespace"]).nonReactive(), o = (e) => t("wfjr", e);
10
+ //#endregion
11
+ export { o as workflowJournalRoutingRowId, a as workflowJournalRoutingTable };
@@ -0,0 +1,16 @@
1
+ import { Effect as e } from "effect";
2
+ import { SqlClient as t } from "effect/sql";
3
+ //#region src/journalRoutingTransaction.ts
4
+ var n = (n, r, i) => t.makeWithTransaction({
5
+ transactionService: n.transactionService,
6
+ spanAttributes: [],
7
+ acquireConnection: e.succeed([void 0, r]),
8
+ begin: (t) => t.executeUnprepared("BEGIN", [], void 0).pipe(e.asVoid),
9
+ commit: (t) => t.executeUnprepared("COMMIT", [], void 0).pipe(e.asVoid),
10
+ rollback: (t) => t.executeUnprepared("ROLLBACK", [], void 0).pipe(e.asVoid),
11
+ savepoint: (t, n) => t.executeUnprepared(`SAVEPOINT voltro_routing_${n}`, [], void 0).pipe(e.asVoid),
12
+ rollbackSavepoint: (t, n) => t.executeUnprepared(`ROLLBACK TO SAVEPOINT voltro_routing_${n}`, [], void 0).pipe(e.asVoid),
13
+ releaseSavepoint: (t, n) => t.executeUnprepared(`RELEASE SAVEPOINT voltro_routing_${n}`, [], void 0).pipe(e.asVoid)
14
+ })(i);
15
+ //#endregion
16
+ export { n as withJournalRoutingTransaction };
@@ -0,0 +1,92 @@
1
+ import { openEventPayload as e } from "./journalEncryption.js";
2
+ import { eq as t } from "@voltro/database";
3
+ //#region src/messages.ts
4
+ var n = (e) => new Promise((t) => setTimeout(t, e)), r = () => {
5
+ let e = globalThis.crypto;
6
+ return typeof e?.randomUUID == "function" ? e.randomUUID() : `${Date.now().toString(36)}${Math.random().toString(36).slice(2)}`;
7
+ }, i = () => `wfu_${r()}`, a = async (e, n) => {
8
+ let r = n.id ?? n.executionId;
9
+ if (!r) throw Error("workflow message target requires target.id or target.executionId");
10
+ let i = await e.query({
11
+ table: "_voltro_workflow_runs",
12
+ order: [],
13
+ predicate: t("id", r),
14
+ projection: void 0,
15
+ skip: void 0,
16
+ take: 1
17
+ });
18
+ if (i[0]) return i[0];
19
+ let a = (await e.query({
20
+ table: "_voltro_workflow_runs",
21
+ order: [],
22
+ predicate: t("executionId", r),
23
+ projection: void 0,
24
+ skip: void 0,
25
+ take: 1
26
+ }))[0];
27
+ if (!a) throw Error(`workflow message target not found: ${r}`);
28
+ return a;
29
+ }, o = class extends Error {
30
+ runId;
31
+ eventType;
32
+ eventId;
33
+ _tag = "WorkflowMessageNotRecorded";
34
+ constructor(e, t, n) {
35
+ super(`workflow ${t} for run ${e} was NOT recorded (event ${n}): the run will not see it — the store refused the row, see the recorder's warning`), this.runId = e, this.eventType = t, this.eventId = n, this.name = "WorkflowMessageNotRecorded";
36
+ }
37
+ }, s = async (e) => {
38
+ let t = await a(e.store, e.target), n = await e.recorder.recordEvent({
39
+ runId: t.id,
40
+ eventType: "signal-sent",
41
+ payload: {
42
+ signalName: e.signalName,
43
+ value: e.payload
44
+ }
45
+ });
46
+ if (n.recorded === !1) throw new o(t.id, "signal-sent", n.id);
47
+ return { eventId: n.id };
48
+ }, c = async (r) => {
49
+ let s = await a(r.store, r.target), c = i(), l = await r.recorder.recordEvent({
50
+ runId: s.id,
51
+ eventType: "update-requested",
52
+ payload: {
53
+ updateId: c,
54
+ updateName: r.updateName,
55
+ value: r.payload
56
+ }
57
+ });
58
+ if (l.recorded === !1) throw new o(s.id, "update-requested", l.id);
59
+ let u = Date.now(), d = r.options?.timeoutMs ?? 3e4, f = r.options?.pollIntervalMs ?? 100;
60
+ for (;;) {
61
+ let i = {
62
+ table: "_voltro_workflow_run_events",
63
+ order: [{
64
+ column: "occurredAt",
65
+ direction: "asc"
66
+ }],
67
+ predicate: t("runId", s.id),
68
+ projection: void 0,
69
+ skip: void 0,
70
+ take: 1e3
71
+ }, a = await r.store.query(i), o = a.find((e) => e.eventType === "update-completed" && e.payload?.updateId === c);
72
+ if (o) return {
73
+ eventId: l.id,
74
+ updateId: c,
75
+ completedEventId: o.id,
76
+ result: e(o.payload)?.result
77
+ };
78
+ let p = a.find((e) => e.eventType === "update-failed" && e.payload?.updateId === c);
79
+ if (p) {
80
+ let t = Error(e(p.payload)?.errorMessage ?? `workflow update '${r.updateName}' failed`);
81
+ throw Object.assign(t, {
82
+ updateId: c,
83
+ eventId: l.id,
84
+ errorTag: p.payload?.errorTag ?? null
85
+ }), t;
86
+ }
87
+ if (Date.now() - u >= d) throw Error(`Timed out waiting for workflow update '${r.updateName}' (${c})`);
88
+ await n(f);
89
+ }
90
+ };
91
+ //#endregion
92
+ export { o as WorkflowMessageNotRecorded, i as makeWorkflowUpdateId, a as resolveWorkflowMessageRun, s as sendWorkflowSignal, c as sendWorkflowUpdate };