@voltro/workflow 0.85.0 → 0.86.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 (85) hide show
  1. package/CHANGELOG.md +666 -0
  2. package/THIRD-PARTY-NOTICES.md +5 -33
  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.js +8 -2
  16. package/dist/clusterCron.js +11 -0
  17. package/dist/clusterDiagnostics.js +20 -0
  18. package/dist/clusterDiagnosticsSql.js +145 -0
  19. package/dist/clusterLayer.js +110 -0
  20. package/dist/clusterMigrationGate.js +61 -0
  21. package/dist/clusterRunnerLayer.js +21 -0
  22. package/dist/clusterTestSuite.js +101 -101
  23. package/dist/controlMonitor.js +13 -0
  24. package/dist/controlRequest.js +367 -0
  25. package/dist/executionRefusal.js +162 -0
  26. package/dist/failureHandoff.js +183 -0
  27. package/dist/failureMessage.js +12 -0
  28. package/dist/failureRecovery.js +50 -0
  29. package/dist/failureReportStore.js +60 -0
  30. package/dist/flowControl.js +142 -0
  31. package/dist/forwardStartReceipts.js +41 -0
  32. package/dist/inMemoryLayer.js +7 -0
  33. package/dist/inMemoryRecorder.js +56 -0
  34. package/dist/index.d.ts +82 -8
  35. package/dist/index.js +49 -5
  36. package/dist/inspectWorkflow.js +101 -0
  37. package/dist/{journalEncryption-DTyhUzaS.js → journalEncryption.js} +30 -37
  38. package/dist/messages.js +92 -0
  39. package/dist/nondeterminism.js +51 -0
  40. package/dist/parentClosure.js +141 -0
  41. package/dist/parentClosureRecovery.js +54 -0
  42. package/dist/pendingSubmission.js +85 -0
  43. package/dist/prepareSubmissionDispatch.js +17 -0
  44. package/dist/preparedExecution.js +14 -0
  45. package/dist/primitives.js +225 -2
  46. package/dist/queuedCancellationRequest.js +364 -0
  47. package/dist/queuedControl.js +21 -0
  48. package/dist/recorder.js +11 -0
  49. package/dist/residentExecutionEvidence.js +64 -0
  50. package/dist/retryPolicy.js +57 -0
  51. package/dist/runEventsTable.js +17 -0
  52. package/dist/runGeneration.js +345 -0
  53. package/dist/runRecording.js +615 -0
  54. package/dist/runStepsTable.js +28 -0
  55. package/dist/runnerStorageGuard.js +29 -0
  56. package/dist/runsTable.js +42 -0
  57. package/dist/scheduleDeclaration.js +44 -0
  58. package/dist/signalHint.js +23 -0
  59. package/dist/signals.js +70 -0
  60. package/dist/signalsSuspending.js +57 -0
  61. package/dist/sqliteWorkflowOwnership.js +26 -0
  62. package/dist/stalenessSweep.js +191 -0
  63. package/dist/startContextsTable.js +20 -0
  64. package/dist/startReceipt.js +143 -0
  65. package/dist/submissionRecovery.js +122 -0
  66. package/dist/submissionRejection.js +10 -0
  67. package/dist/submissionRetry.js +71 -0
  68. package/dist/suspensionUnavailable.js +9 -0
  69. package/dist/updates.js +87 -0
  70. package/dist/watermarkTable.js +9 -0
  71. package/dist/webCrypto.js +17 -0
  72. package/dist/workflowDelivery.js +24 -0
  73. package/dist/workflowJournal.js +124 -0
  74. package/dist/workflowJournalReset.js +111 -0
  75. package/dist/workflowReadSelections.js +12 -0
  76. package/dist/workflowRestartDispatch.js +29 -0
  77. package/dist/workflowRestartObservation.js +60 -0
  78. package/dist/workflowRestartPreparation.js +109 -0
  79. package/dist/workflowRestartReservation.js +426 -0
  80. package/dist/workflowResumeDispatch.js +29 -0
  81. package/package.json +4 -4
  82. package/dist/cluster-F_bhqVP7.js +0 -679
  83. package/dist/primitives-DX04WTWh.js +0 -507
  84. package/dist/src-C2NEyUM8.js +0 -3925
  85. package/dist/workflowReadSelections-CZAUjP7f.js +0 -2096
@@ -0,0 +1,122 @@
1
+ import { loggableCause as e } from "./journalEncryption.js";
2
+ import { readResidentExecutionEvidence as t } from "./residentExecutionEvidence.js";
3
+ import { acknowledgeAdmissionSubmissionInTransaction as n, beginAdmissionSubmissionDispatchInTransaction as r, decodeAdmissionSubmission as i, noteAdmissionSubmissionFailure as a, readPendingAdmissionSubmissionRows as o, rejectAdmissionSubmissionInTransaction as s } from "./admissionSubmission.js";
4
+ import { decodePendingPayload as c, releaseLease as l, retryBackoffMs as u } from "./admissionStore.js";
5
+ import { prepareSubmissionDispatch as d } from "./prepareSubmissionDispatch.js";
6
+ import { Schema as f } from "effect";
7
+ //#region src/submissionRecovery.ts
8
+ var p = /* @__PURE__ */ new WeakMap(), m = async (m, h, g, _) => {
9
+ let v = p.get(m), y = await o(m.store, g, v?.afterId), b = 0, x = 0, S = 0, C = 0, w = v?.nextDueAt, T = (e) => {
10
+ w = Math.min(w ?? e, e);
11
+ };
12
+ for (let o of y) {
13
+ let p;
14
+ try {
15
+ p = i(o);
16
+ } catch {
17
+ b++, S++, T(h + u(1)), m.log?.warn?.("workflow submission metadata is invalid", { submissionId: typeof o.id == "string" ? o.id : null });
18
+ continue;
19
+ }
20
+ b++;
21
+ let g = p.dispatchAttempts === 0 ? 0 : void 0, v = p.retry.recovery?.attempts ?? 0;
22
+ try {
23
+ let e = p.preparedExecutionId, i = e === null || p.dispatchAttempts === 0 ? void 0 : await m.transaction(async (n) => {
24
+ let r = await t(n, p.workflowName, e);
25
+ return r === void 0 ? void 0 : {
26
+ executionId: e,
27
+ runId: r.runId
28
+ };
29
+ });
30
+ if (i === void 0 && p.retry.stoppedAt !== null) {
31
+ b--;
32
+ continue;
33
+ }
34
+ if (i === void 0 && p.retry.after > h) {
35
+ b--, T(p.retry.after);
36
+ continue;
37
+ }
38
+ if (i === void 0 && m.acquireSubmissionCapacity !== void 0 && !await m.acquireSubmissionCapacity(p)) {
39
+ T(h + 1e3);
40
+ continue;
41
+ }
42
+ let a = i ?? await (async () => {
43
+ let e = m.controls.get(p.workflowName)?.batch === void 0 ? c(m.store, p.workflowName, p.payload) : { items: f.decodeUnknownSync(f.Struct({ items: f.Array(f.Unknown) }))(p.payload).items.map((e) => c(m.store, p.workflowName, e)) }, t = await d(m, p, e), n = await m.transaction((e) => r(e, p.id, m.now?.() ?? Date.now()));
44
+ if (n === null) return;
45
+ v = n.retry.recovery?.attempts ?? 0, g = n.dispatchAttempts === 1 ? 1 : void 0;
46
+ let i = await m.startAdmitted(p.workflowName, e, p.callerContext, t);
47
+ if (g = void 0, i.executionId !== t) throw Error("Engine returned a different prepared execution identity");
48
+ return i;
49
+ })();
50
+ if (a === void 0) continue;
51
+ g = void 0, p.eviction !== null && (await m.cancelRun({
52
+ workflowName: p.workflowName,
53
+ ...p.eviction,
54
+ reason: "cancelled to make room for a newer start (singleton mode 'cancel')",
55
+ detail: {
56
+ source: "flow-control",
57
+ mode: "singleton"
58
+ }
59
+ }), await l(m.store, p.eviction.executionId, h)), await m.transaction((e) => n(e, p.id, {
60
+ executionId: a.executionId,
61
+ runId: a.runId ?? null
62
+ })), x++;
63
+ } catch (t) {
64
+ S++;
65
+ let n;
66
+ try {
67
+ if (g !== void 0 && m.isPermanentStartError?.(t) === !0) {
68
+ let e = g, n = await m.transaction((n) => s(n, p.id, e, {
69
+ handler: m.controls.get(p.workflowName)?.onFailure ?? null,
70
+ errorMessage: String(t),
71
+ now: h
72
+ }, v));
73
+ if (n !== void 0) {
74
+ C++, m.log?.warn?.("workflow submission rejected; failure report retained", {
75
+ workflow: p.workflowName,
76
+ submissionId: p.id,
77
+ failureId: n.id
78
+ });
79
+ continue;
80
+ }
81
+ }
82
+ n = await m.transaction((e) => a(e, p.id, String(t), h, _, v));
83
+ } catch (n) {
84
+ T(h), m.log?.warn?.("workflow submission retry state could not be recorded", {
85
+ workflow: p.workflowName,
86
+ submissionId: p.id,
87
+ cause: e(p.workflowName, t),
88
+ recordingCause: String(n)
89
+ });
90
+ continue;
91
+ }
92
+ n.retry.stoppedAt === null && T(n.retry.after), m.log?.warn?.("workflow submission remains unacknowledged", {
93
+ workflow: p.workflowName,
94
+ submissionId: p.id,
95
+ cause: e(p.workflowName, t),
96
+ retryStoppedAt: n.retry.stoppedAt,
97
+ acceptance: "unknown"
98
+ });
99
+ }
100
+ }
101
+ let E = y.at(-1);
102
+ return y.length === g && E !== void 0 && typeof E.id == "string" ? (p.set(m, {
103
+ afterId: E.id,
104
+ nextDueAt: w
105
+ }), {
106
+ scanned: b,
107
+ admitted: x,
108
+ failed: S,
109
+ rejected: C,
110
+ nextDueAt: h,
111
+ examined: !0
112
+ }) : (p.delete(m), {
113
+ scanned: b,
114
+ admitted: x,
115
+ failed: S,
116
+ rejected: C,
117
+ nextDueAt: w,
118
+ examined: y.length > 0
119
+ });
120
+ };
121
+ //#endregion
122
+ export { m as recoverAdmissionSubmissions };
@@ -0,0 +1,10 @@
1
+ import { readWorkflowFailureRecord as e } from "./failureReportStore.js";
2
+ //#region src/submissionRejection.ts
3
+ var t = async (t, n) => {
4
+ if (n.failureId === null) throw Error("Workflow submission has no rejection marker");
5
+ let r = await e(t, n.failureId);
6
+ if (n.executionId !== null || n.dispatchAttempts > 1 || r === void 0 || r.sourceKind !== "submission" || r.sourceId !== n.id || r.workflowName !== n.workflowName || r.tenantId !== n.tenantId || r.report.executionId !== null || r.report.runId !== null) throw Error("Workflow start rejection has contradictory ownership or execution evidence");
7
+ return r.id;
8
+ };
9
+ //#endregion
10
+ export { t as readProvenSubmissionRejection };
@@ -0,0 +1,71 @@
1
+ import { readAdmissionSubmission as e } from "./admissionSubmission.js";
2
+ import { ADMISSIONS_TABLE as t } from "./admissionStore.js";
3
+ import { eq as n } from "@voltro/database";
4
+ //#region src/submissionRetry.ts
5
+ var r = (e, t) => ({
6
+ kind: "refused",
7
+ submissionId: e,
8
+ reason: t
9
+ }), i = async (i, a) => {
10
+ let o = await e(i, a);
11
+ if (o === void 0) return r(a, "missing-submission");
12
+ if (!o.receiptsBound) return r(a, "incomplete-transfer");
13
+ if (o.executionId !== null || o.runId !== null) return r(a, "accepted");
14
+ if (o.failureId !== null) return r(a, "rejected");
15
+ if (o.dispatchAttempts !== 0) return r(a, "dispatch-attempted");
16
+ if (o.preparedExecutionId !== null) return r(a, "prepared-execution");
17
+ let [s] = await i.query({
18
+ table: t,
19
+ predicate: n("id", o.ledgerId),
20
+ order: [],
21
+ take: 1,
22
+ skip: void 0,
23
+ projection: void 0
24
+ });
25
+ return s === void 0 ? r(a, "missing-lease") : s.workflowName !== o.workflowName || s.tenantId !== o.tenantId || s.outcome !== "admitted" ? r(a, "foreign-lease") : s.releasedAt === null ? s.executionId !== null || s.runId !== null ? r(a, "bound-lease") : {
26
+ kind: "owner",
27
+ submission: o
28
+ } : r(a, "released-lease");
29
+ }, a = (e) => e.retry.stoppedAt === null ? void 0 : {
30
+ attempts: e.retry.attempts,
31
+ stoppedAt: e.retry.stoppedAt
32
+ }, o = async (e, t) => {
33
+ let n = await i(e, t);
34
+ if (n.kind === "refused") return n;
35
+ let o = a(n.submission);
36
+ return o === void 0 ? r(t, "not-stopped") : {
37
+ kind: "eligible",
38
+ submissionId: t,
39
+ expected: o
40
+ };
41
+ }, s = async (t, n, o, s) => {
42
+ if (!Number.isSafeInteger(o.attempts) || o.attempts < 1 || !Number.isFinite(o.stoppedAt) || !Number.isFinite(s)) throw Error("Submission recovery requires a valid stopped retry token and time");
43
+ let c = (await e(t, n))?.retry.recovery;
44
+ if (c?.attempts === o.attempts && c.stoppedAt === o.stoppedAt) return {
45
+ kind: "already-retried",
46
+ submissionId: n,
47
+ expected: o
48
+ };
49
+ let l = await i(t, n);
50
+ if (l.kind === "refused") return l;
51
+ let u = a(l.submission);
52
+ if (u === void 0) return r(n, "not-stopped");
53
+ if (u.attempts !== o.attempts || u.stoppedAt !== o.stoppedAt) return r(n, "changed-retry");
54
+ let d = {
55
+ ...l.submission.retry,
56
+ after: s,
57
+ stoppedAt: null,
58
+ recovery: {
59
+ ...u,
60
+ retriedAt: s
61
+ }
62
+ };
63
+ if (await t.update("_voltro_workflow_submissions", n, { retry: d }) === null) throw Error("Admission submission disappeared during stopped retry recovery");
64
+ return {
65
+ kind: "retried",
66
+ submissionId: n,
67
+ expected: o
68
+ };
69
+ };
70
+ //#endregion
71
+ export { o as inspectAdmissionSubmissionRetry, s as retryAdmissionSubmissionInTransaction };
@@ -0,0 +1,9 @@
1
+ import { Context as e } from "effect";
2
+ //#region src/suspensionUnavailable.ts
3
+ var t = e.Reference("@voltro/workflow/SuspensionUnavailable", { defaultValue: () => void 0 }), n = class extends Error {
4
+ constructor(e, t) {
5
+ super(`${e} cannot wait for a durable result here: ${t}`), this.name = "WorkflowCannotPark";
6
+ }
7
+ };
8
+ //#endregion
9
+ export { t as SuspensionUnavailable, n as WorkflowCannotPark };
@@ -0,0 +1,87 @@
1
+ import { failureMessage as e } from "./failureMessage.js";
2
+ import { encryptionAwareActivity as t, openEventPayload as n } from "./journalEncryption.js";
3
+ import { CurrentWorkflowRunId as r, WorkflowRunRecorder as i } from "./recorder.js";
4
+ import { terminalUpdates as a } from "./workflowReadSelections.js";
5
+ import { Cause as o, Effect as s, Schema as c } from "effect";
6
+ import { eq as l } from "@voltro/database";
7
+ //#region src/updates.ts
8
+ var u = async (e, t) => {
9
+ if (e) try {
10
+ await e.recordEvent(t);
11
+ } catch {}
12
+ }, d = (t) => {
13
+ let n = o.findErrorOption(t), r = t.reasons.filter(o.isDieReason).map((e) => e.defect), i = n._tag === "Some" ? n.value : r[0] ?? o.squash(t);
14
+ return {
15
+ errorTag: i && typeof i == "object" && "_tag" in i ? String(i._tag) : null,
16
+ errorMessage: e(i)
17
+ };
18
+ }, f = (e, o) => {
19
+ let f = s.gen(function* () {
20
+ let t = o.pollIntervalMs ?? 200, f = o.maxPollIntervalMs ?? 5e3, p = o.timeoutMs ?? 3e4, m = yield* r, h = yield* s.serviceOption(i), g = h._tag === "Some" ? h.value : void 0;
21
+ if (m === void 0) return yield* s.die(/* @__PURE__ */ Error("awaitUpdate: no active workflow run id in scope"));
22
+ let _ = m, v = Date.now(), y = t;
23
+ for (;;) {
24
+ let t = Date.now() - v;
25
+ if (t > p) return yield* s.die(/* @__PURE__ */ Error(`awaitUpdate('${o.name}') timed out after ${t}ms`));
26
+ let r = {
27
+ table: "_voltro_workflow_run_events",
28
+ order: [{
29
+ column: "occurredAt",
30
+ direction: "asc"
31
+ }],
32
+ predicate: l("runId", _),
33
+ projection: void 0,
34
+ skip: void 0,
35
+ take: 1e3
36
+ }, i = yield* s.tryPromise({
37
+ try: () => e.store.query(r),
38
+ catch: (e) => e
39
+ }), m = a(i), h = i.find((e) => e.eventType === "update-requested" && e.payload?.updateName === o.name && typeof e.payload.updateId == "string" && !m.has(e.payload.updateId));
40
+ if (h !== void 0) {
41
+ let e = h.payload.updateId;
42
+ yield* s.promise(() => u(g, {
43
+ runId: _,
44
+ eventType: "update-received",
45
+ payload: {
46
+ updateId: e,
47
+ updateName: o.name
48
+ }
49
+ }));
50
+ let t = yield* c.decodeUnknownEffect(o.schema)(n(h.payload)?.value).pipe(s.tapCause((t) => s.promise(() => u(g, {
51
+ runId: _,
52
+ eventType: "update-failed",
53
+ payload: {
54
+ updateId: e,
55
+ updateName: o.name,
56
+ ...d(t)
57
+ }
58
+ })))), r = yield* (o.handle ? o.handle(t) : s.succeed(t)).pipe(s.flatMap((e) => o.success ? c.decodeUnknownEffect(o.success)(e) : s.succeed(e))).pipe(s.tapCause((t) => s.promise(() => u(g, {
59
+ runId: _,
60
+ eventType: "update-failed",
61
+ payload: {
62
+ updateId: e,
63
+ updateName: o.name,
64
+ ...d(t)
65
+ }
66
+ }))));
67
+ return yield* s.promise(() => u(g, {
68
+ runId: _,
69
+ eventType: "update-completed",
70
+ payload: {
71
+ updateId: e,
72
+ updateName: o.name,
73
+ result: r
74
+ }
75
+ })), r;
76
+ }
77
+ yield* s.sleep(`${y} millis`), y = Math.min(y * 2, f);
78
+ }
79
+ });
80
+ return t({
81
+ name: `await-update:${o.name}`,
82
+ success: o.success ?? o.schema,
83
+ execute: f
84
+ });
85
+ };
86
+ //#endregion
87
+ export { f as awaitUpdate };
@@ -0,0 +1,9 @@
1
+ import { id as e, table as t, timestamp as n } from "@voltro/database";
2
+ //#region src/watermarkTable.ts
3
+ var r = t("_voltro_workflow_watermarks", {
4
+ id: e({ prefix: "wfwm" }),
5
+ positionAt: n(),
6
+ updatedAt: n().default("now")
7
+ });
8
+ //#endregion
9
+ export { r as _voltroWorkflowWatermarksTable };
@@ -0,0 +1,17 @@
1
+ import { Crypto as e, Effect as t, Layer as n, PlatformError as r } from "effect";
2
+ //#region src/webCrypto.ts
3
+ var i = e.make({
4
+ randomBytes: (e) => globalThis.crypto.getRandomValues(new Uint8Array(e)),
5
+ digest: (e, n) => t.tryPromise({
6
+ try: async () => new Uint8Array(await globalThis.crypto.subtle.digest(e, new Uint8Array(n).buffer)),
7
+ catch: (t) => r.systemError({
8
+ _tag: "Unknown",
9
+ module: "Crypto",
10
+ method: "digest",
11
+ description: `digest ${e} failed`,
12
+ cause: t
13
+ })
14
+ })
15
+ }), a = n.succeed(e.Crypto, i);
16
+ //#endregion
17
+ export { i as webCrypto, a as webCryptoLayer };
@@ -0,0 +1,24 @@
1
+ import { Context as e, Effect as t } from "effect";
2
+ //#region src/workflowDelivery.ts
3
+ var n = "voltro-workflow-restart", r = class extends e.Service()("voltro/CurrentWorkflowDelivery") {}, i = (e) => ({
4
+ ...e,
5
+ registerEntity: (n, i, a) => e.registerEntity(n, n.type.startsWith("Workflow/") ? i.pipe(t.map((e) => {
6
+ let n = e.run;
7
+ if (typeof n != "function") throw Error("Workflow entity is missing its run handler");
8
+ return {
9
+ ...e,
10
+ run: (e) => {
11
+ let i = {
12
+ requestId: String(e.requestId),
13
+ restartToken: e.headers["voltro-workflow-restart"] ?? null
14
+ };
15
+ return t.suspend(() => {
16
+ let r = n(e);
17
+ return t.isEffect(r) ? r : t.die(/* @__PURE__ */ Error("Workflow run handler must return an Effect"));
18
+ }).pipe(t.provideService(r, i));
19
+ }
20
+ };
21
+ })) : i, a)
22
+ });
23
+ //#endregion
24
+ export { r as CurrentWorkflowDelivery, n as WORKFLOW_RESTART_HEADER, i as withWorkflowDelivery };
@@ -0,0 +1,124 @@
1
+ import { latestExitReply as e } from "./workflowReadSelections.js";
2
+ import { Context as t, Effect as n, Option as r, Schema as i } from "effect";
3
+ import { ClusterSchema as a, EntityAddress as o, EntityId as s, EntityType as c, MessageStorage as l, Sharding as u } from "effect/cluster";
4
+ //#region src/workflowJournal.ts
5
+ var d = (e, t) => `${e}/${t}`, f = (e, n, r) => {
6
+ let i = s.make(n), l = t.get(e.annotations, a.ShardGroup)(i);
7
+ return new o.EntityAddress({
8
+ entityType: c.EntityType.make(`Workflow/${e._tag}`),
9
+ entityId: i,
10
+ shardId: r.getShardId(i, l)
11
+ });
12
+ }, p = (t, i) => n.gen(function* () {
13
+ let n = yield* u.Sharding, a = yield* l.MessageStorage, o = yield* a.requestIdForPrimaryKey({
14
+ address: f(t, i, n),
15
+ tag: "run",
16
+ id: ""
17
+ });
18
+ if (r.isNone(o)) return { kind: "not-found" };
19
+ let s = String(o.value), c = yield* a.repliesForUnfiltered([o.value]), d = e(c);
20
+ return d === void 0 ? {
21
+ kind: "pending",
22
+ requestId: s
23
+ } : {
24
+ kind: "reply",
25
+ requestId: s,
26
+ replyId: String(d.id),
27
+ exit: d.exit
28
+ };
29
+ }), m = i.Union([i.Struct({
30
+ _tag: i.Literal("Success"),
31
+ value: i.Unknown
32
+ }), i.Struct({
33
+ _tag: i.Literal("Failure"),
34
+ cause: i.Array(i.Union([
35
+ i.Struct({
36
+ _tag: i.Literal("Fail"),
37
+ error: i.Unknown
38
+ }),
39
+ i.Struct({
40
+ _tag: i.Literal("Die"),
41
+ defect: i.Unknown
42
+ }),
43
+ i.Struct({
44
+ _tag: i.Literal("Interrupt"),
45
+ fiberId: i.optional(i.NullOr(i.Number))
46
+ })
47
+ ]))
48
+ })]), h = i.decodeUnknownOption(m), g = (e) => {
49
+ let t = (e) => e && typeof e == "object" && "_tag" in e && typeof e._tag == "string" ? e._tag : void 0, n = h(e);
50
+ if (r.isNone(n)) return "unrecognized-reply";
51
+ if (n.value._tag === "Failure") return "failed";
52
+ let i = n.value.value, a = t(i);
53
+ if (a === "Suspended") return "suspended";
54
+ if (a === "Complete") {
55
+ let e = i.exit, t = h(e);
56
+ return r.isNone(t) ? "unrecognized-reply" : t.value._tag === "Success" ? "succeeded" : "failed";
57
+ }
58
+ return "unrecognized-reply";
59
+ }, _ = (e) => y(e.workflow, e.executionId, e.failedActivities), v = (e) => y(e.workflow, e.executionId, e.resetActivities), y = (t, i, a) => n.gen(function* () {
60
+ let o = yield* u.Sharding, s = yield* l.MessageStorage, c = f(t, i, o), p = yield* s.requestIdForPrimaryKey({
61
+ address: c,
62
+ tag: "run",
63
+ id: ""
64
+ }).pipe(n.orDie);
65
+ if (r.isNone(p)) return {
66
+ redriven: !1,
67
+ runReplyCleared: !1,
68
+ activitiesReset: 0,
69
+ reason: "no-journal"
70
+ };
71
+ let m = yield* s.repliesForUnfiltered([p.value]).pipe(n.orDie), h = e(m);
72
+ if (h === void 0) return {
73
+ redriven: !1,
74
+ runReplyCleared: !1,
75
+ activitiesReset: 0,
76
+ reason: "no-reply"
77
+ };
78
+ let _ = g(h.exit);
79
+ if (_ !== "failed") return {
80
+ redriven: !1,
81
+ runReplyCleared: !1,
82
+ activitiesReset: 0,
83
+ reason: _
84
+ };
85
+ let v = /* @__PURE__ */ new Set();
86
+ for (let e of a) {
87
+ let t = yield* s.requestIdForPrimaryKey({
88
+ address: c,
89
+ tag: "activity",
90
+ id: d(e.name, e.attempt)
91
+ }).pipe(n.orDie);
92
+ if (r.isNone(t)) return {
93
+ redriven: !1,
94
+ runReplyCleared: !1,
95
+ activitiesReset: 0,
96
+ reason: "activity-not-found"
97
+ };
98
+ v.add(t.value);
99
+ }
100
+ let y = 0;
101
+ for (let e of v) {
102
+ if (!(yield* o.reset(e))) return {
103
+ redriven: !1,
104
+ runReplyCleared: !1,
105
+ activitiesReset: y,
106
+ reason: "activity-reset-unconfirmed"
107
+ };
108
+ y++;
109
+ }
110
+ let b = yield* o.reset(p.value);
111
+ return b ? (yield* o.pollStorage, {
112
+ redriven: !0,
113
+ runReplyCleared: b,
114
+ activitiesReset: y,
115
+ reason: null
116
+ }) : {
117
+ redriven: !1,
118
+ runReplyCleared: !1,
119
+ activitiesReset: y,
120
+ reason: "run-reset-unconfirmed"
121
+ };
122
+ });
123
+ //#endregion
124
+ export { m as JournalExit, d as activityPrimaryKey, g as classifyRunReply, p as readWorkflowRunReply, _ as redriveFailedRun, v as resumeRunFromStep, f as runEntityAddress };
@@ -0,0 +1,111 @@
1
+ import { WORKFLOW_RESTART_HEADER as e } from "./workflowDelivery.js";
2
+ import { Effect as t, Schema as n } from "effect";
3
+ import { SqlClient as r } from "effect/sql";
4
+ //#region src/workflowJournalReset.ts
5
+ var i = n.fromJsonString(n.Struct({
6
+ _tag: n.Literal("Success"),
7
+ value: n.Struct({ _tag: n.Literal("Suspended") })
8
+ })), a = n.fromJsonString(n.Record(n.String, n.String)), o = (e) => t.gen(function* () {
9
+ if (!/^[1-9]\d*$/.test(e.requestId) || e.workflowName.length === 0 || e.executionId.length === 0) return yield* t.die(/* @__PURE__ */ Error("Suspension observation requires an exact engine delivery owner"));
10
+ let [o] = yield* (yield* r.SqlClient).withoutTransforms()`
11
+ SELECT m.entity_type, m.entity_id, m.tag, m.headers, r.id AS reply_id, r.payload
12
+ FROM cluster_messages m LEFT JOIN cluster_replies r
13
+ ON r.id = m.last_reply_id AND r.request_id = m.id AND r.kind = 0
14
+ WHERE m.id = ${e.requestId} AND m.kind = 0`;
15
+ if (o === void 0) return;
16
+ if (o.entity_type !== `Workflow/${e.workflowName}` || o.entity_id !== e.executionId || o.tag !== "run") return yield* t.die(/* @__PURE__ */ Error("Suspension observation belongs to another execution or procedure"));
17
+ if (((yield* t.sync(() => n.decodeUnknownSync(a)(o.headers)))["voltro-workflow-restart"] ?? null) !== e.restartToken || o.reply_id === null || n.decodeUnknownOption(i)(o.payload)._tag === "None") return;
18
+ let s = String(o.reply_id);
19
+ return /^[1-9]\d*$/.test(s) ? {
20
+ ...e,
21
+ replyId: s
22
+ } : yield* t.die(/* @__PURE__ */ Error("Suspension reply has an invalid identity"));
23
+ }).pipe(t.provideService(r.SafeIntegers, !0)), s = (a, o) => t.gen(function* () {
24
+ let { restart: s, suspension: c } = o, l = /* @__PURE__ */ new Map();
25
+ for (let e of a) {
26
+ if (!/^[1-9]\d*$/.test(e.requestId) || !/^[1-9]\d*$/.test(e.replyId)) return yield* t.die(/* @__PURE__ */ Error("Journal reset requires positive decimal request and reply identities"));
27
+ let n = l.get(e.requestId);
28
+ if (n !== void 0 && n !== e.replyId) return yield* t.die(/* @__PURE__ */ Error("Journal reset contains conflicting observations for one request"));
29
+ l.set(e.requestId, e.replyId);
30
+ }
31
+ if (s !== void 0 && (!l.has(s.requestId) || s.token.length === 0 || s.token === s.previousToken || s.workflowName.length === 0 || s.executionId.length === 0)) return yield* t.die(/* @__PURE__ */ Error("Journal restart requires a selected request, new token and execution owner"));
32
+ if (l.size === 0) return {
33
+ kind: "reset",
34
+ count: 0
35
+ };
36
+ let u = [...l].sort(([e], [t]) => e < t ? -1 : +(e > t)), d = (yield* r.SqlClient).withoutTransforms(), f = d("cluster_messages"), p = d("cluster_replies"), m = d.onDialectOrElse({
37
+ pg: () => d.literal("FALSE"),
38
+ orElse: () => d.literal("0")
39
+ });
40
+ return yield* t.gen(function* () {
41
+ let r = /* @__PURE__ */ new Map();
42
+ for (let [e] of u) {
43
+ let n = yield* d.onDialectOrElse({
44
+ mssql: () => d`
45
+ SELECT last_reply_id FROM ${f} WITH (UPDLOCK, HOLDLOCK)
46
+ WHERE id = ${e} AND kind = 0`,
47
+ sqlite: () => d`
48
+ SELECT last_reply_id FROM ${f} WHERE id = ${e} AND kind = 0`,
49
+ orElse: () => d`
50
+ SELECT last_reply_id FROM ${f} WHERE id = ${e} AND kind = 0 FOR UPDATE`
51
+ });
52
+ if (n.length !== 1) return {
53
+ kind: "changed",
54
+ requestId: e
55
+ };
56
+ if (r.set(e, String(n[0]?.last_reply_id)), s !== void 0) {
57
+ let [n] = yield* d`
58
+ SELECT entity_type, entity_id, tag FROM ${f} WHERE id = ${e}`;
59
+ if (n?.entity_type !== `Workflow/${s.workflowName}` || n.entity_id !== s.executionId || n.tag !== (e === s.requestId ? "run" : "activity")) return yield* t.die(/* @__PURE__ */ Error("Journal restart selection belongs to another execution or procedure"));
60
+ }
61
+ }
62
+ let a;
63
+ if (c !== void 0) {
64
+ let [e] = yield* d`
65
+ SELECT entity_type, entity_id, tag, headers FROM ${f} WHERE id = ${c.requestId}`;
66
+ if (e?.entity_type !== `Workflow/${c.workflowName}` || e.entity_id !== c.executionId || e.tag !== "run" || c.workflowName.length === 0 || c.executionId.length === 0) return yield* t.die(/* @__PURE__ */ Error("Journal resume selection belongs to another execution or procedure"));
67
+ if (((yield* t.sync(() => n.decodeUnknownSync(n.fromJsonString(n.Record(n.String, n.String)))(e.headers)))["voltro-workflow-restart"] ?? null) !== c.restartToken) return {
68
+ kind: "changed",
69
+ requestId: c.requestId
70
+ };
71
+ }
72
+ if (s !== void 0) {
73
+ let [e] = yield* d`
74
+ SELECT tag, entity_type, headers FROM ${f} WHERE id = ${s.requestId}`;
75
+ if (e?.tag !== "run" || !e.entity_type.startsWith("Workflow/")) return yield* t.die(/* @__PURE__ */ Error("Journal restart marker can only target a workflow run"));
76
+ if (a = yield* t.sync(() => n.decodeUnknownSync(n.fromJsonString(n.Record(n.String, n.String)))(e.headers)), a["voltro-workflow-restart"] === s.token) return {
77
+ kind: "already-reset",
78
+ count: u.length
79
+ };
80
+ if ((a["voltro-workflow-restart"] ?? null) !== s.previousToken) return {
81
+ kind: "changed",
82
+ requestId: s.requestId
83
+ };
84
+ }
85
+ for (let [e, t] of u) {
86
+ if (r.get(e) !== t) return {
87
+ kind: "changed",
88
+ requestId: e
89
+ };
90
+ let a = yield* d`SELECT payload FROM ${p}
91
+ WHERE id = ${t} AND request_id = ${e} AND kind = 0`;
92
+ if (a.length !== 1 || c !== void 0 && n.decodeUnknownOption(i)(a[0]?.payload)._tag === "None") return {
93
+ kind: "changed",
94
+ requestId: e
95
+ };
96
+ }
97
+ s !== void 0 && (yield* d`UPDATE ${f} SET headers = ${JSON.stringify({
98
+ ...a,
99
+ [e]: s.token
100
+ })}
101
+ WHERE id = ${s.requestId}`);
102
+ for (let [e] of u) yield* d`DELETE FROM ${p} WHERE request_id = ${e} AND kind = 0`, yield* d`DELETE FROM ${f} WHERE request_id = ${e} AND kind = 2`, yield* d`UPDATE ${f} SET processed = ${m}, last_reply_id = NULL,
103
+ last_read = NULL WHERE request_id = ${e}`;
104
+ return {
105
+ kind: "reset",
106
+ count: u.length
107
+ };
108
+ }).pipe(d.withTransaction, t.provideService(r.SafeIntegers, !0));
109
+ }), c = (e, t) => s(e, t === void 0 ? {} : { restart: t }), l = (e) => s([e], { suspension: e });
110
+ //#endregion
111
+ export { o as observeWorkflowSuspension, c as resetExpectedWorkflowReplies, l as resetExpectedWorkflowSuspension };
@@ -0,0 +1,12 @@
1
+ //#region src/workflowReadSelections.ts
2
+ var e = (e) => e.findLast((e, t, n) => t in n && e._tag === "WithExit"), t = (e) => {
3
+ let t = /* @__PURE__ */ new Set();
4
+ for (let n of e) {
5
+ if (n.eventType !== "update-completed" && n.eventType !== "update-failed") continue;
6
+ let e = n.payload?.updateId;
7
+ typeof e == "string" && e.length > 0 && t.add(e);
8
+ }
9
+ return t;
10
+ };
11
+ //#endregion
12
+ export { e as latestExitReply, t as terminalUpdates };
@@ -0,0 +1,29 @@
1
+ import { resetReservedWorkflowJournal as e } from "./workflowRestartReservation.js";
2
+ import { prepareWorkflowRestartCommand as t } from "./workflowRestartPreparation.js";
3
+ import { observeWorkflowRestartReplies as n } from "./workflowRestartObservation.js";
4
+ import { Context as r, Effect as i } from "effect";
5
+ import { SqlClient as a } from "effect/sql";
6
+ import { MessageStorage as o, Sharding as s } from "effect/cluster";
7
+ //#region src/workflowRestartDispatch.ts
8
+ var c = (t) => i.gen(function* () {
9
+ let n = yield* e(t);
10
+ return n.kind === "waiting-capacity" ? n : (yield* (yield* s.Sharding).pollStorage, {
11
+ kind: "attempted",
12
+ journal: n
13
+ });
14
+ }), l = class extends r.Service()("@voltro/workflow/WorkflowRestartDispatcher") {}, u = i.gen(function* () {
15
+ let e = yield* a.SqlClient, r = yield* s.Sharding, u = yield* o.MessageStorage, d = (t) => n(t).pipe(i.provideService(a.SqlClient, e), i.provideService(s.Sharding, r), i.provideService(o.MessageStorage, u));
16
+ return l.of({
17
+ prepare: (e) => i.tryPromise({
18
+ try: (n) => t({
19
+ ...e,
20
+ observe: (e) => i.runPromise(d(e), { signal: n })
21
+ }),
22
+ catch: (e) => e
23
+ }),
24
+ observe: d,
25
+ dispatch: (t) => c(t).pipe(i.provideService(a.SqlClient, e), i.provideService(s.Sharding, r))
26
+ });
27
+ });
28
+ //#endregion
29
+ export { l as WorkflowRestartDispatcher, c as dispatchWorkflowRestart, u as makeWorkflowRestartDispatcher };