@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.
- package/CHANGELOG.md +5321 -0
- package/THIRD-PARTY-NOTICES.md +1130 -11
- package/dist/cluster.d.ts +111 -27
- package/dist/cluster.js +12 -8
- package/dist/clusterCron.js +17 -9
- package/dist/clusterLayer.js +104 -69
- package/dist/clusterRunnerLayer.js +26 -16
- package/dist/clusterShardGroups.js +33 -0
- package/dist/clusterTestSuite.d.ts +23 -0
- package/dist/clusterTestSuite.js +142 -112
- package/dist/index.d.ts +47 -22
- package/dist/index.js +24 -23
- package/dist/journalRoutingGuard.js +50 -0
- package/dist/journalRoutingLayout.js +47 -0
- package/dist/journalRoutingLock.js +39 -0
- package/dist/journalRoutingMaintenance.js +24 -0
- package/dist/journalRoutingMessageStorage.js +32 -0
- package/dist/journalRoutingNamespace.js +23 -0
- package/dist/journalRoutingRuntime.js +33 -0
- package/dist/journalRoutingSharding.js +28 -0
- package/dist/journalRoutingState.js +93 -0
- package/dist/journalRoutingTable.js +11 -0
- package/dist/journalRoutingTransaction.js +16 -0
- package/dist/workflowExecutorRouting.js +33 -0
- package/dist/workflowRestartDispatch.js +29 -23
- package/dist/workflowRestartReservation.js +156 -155
- package/dist/workflowResumeDispatch.js +17 -16
- package/dist/workflowRuntimeReadiness.js +12 -0
- package/dist/workflowShardRelocation.js +58 -0
- package/dist/workflowShardRelocationSql.js +72 -0
- package/package.json +4 -4
|
@@ -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,33 @@
|
|
|
1
|
+
import { journalExecutorGroups as e } from "./journalRoutingLayout.js";
|
|
2
|
+
import { WorkflowEngine as t } from "effect/workflow";
|
|
3
|
+
import { Context as n, Effect as r, Layer as i, Option as a } from "effect";
|
|
4
|
+
//#region src/workflowExecutorRouting.ts
|
|
5
|
+
var o = n.Service("@voltro/workflow/WorkflowExecutorRouting"), s = (e, t, r, i, a) => n.add(e, o, Object.freeze({
|
|
6
|
+
engine: t,
|
|
7
|
+
mode: r,
|
|
8
|
+
state: i,
|
|
9
|
+
groups: a ?? Object.freeze({
|
|
10
|
+
available: Object.freeze(["default"]),
|
|
11
|
+
assigned: Object.freeze(r === "client" ? [] : ["default"])
|
|
12
|
+
})
|
|
13
|
+
})), c = (n, s) => {
|
|
14
|
+
let c = Object.freeze({
|
|
15
|
+
workflows: s.workflows,
|
|
16
|
+
schedules: s.schedules,
|
|
17
|
+
webhooks: s.webhooks
|
|
18
|
+
});
|
|
19
|
+
if (Object.values(c).some((e) => typeof e != "boolean")) throw Error("Explicit executor selection is required");
|
|
20
|
+
let l = Object.values(c).every(Boolean), u = Object.values(c).some(Boolean), d = r.gen(function* () {
|
|
21
|
+
let n = yield* r.serviceOption(t.WorkflowEngine);
|
|
22
|
+
if (a.isNone(n)) return yield* r.die(/* @__PURE__ */ Error("Executor registration requires a workflow engine"));
|
|
23
|
+
let i = yield* r.serviceOption(o);
|
|
24
|
+
if (a.isNone(i)) return l ? void 0 : yield* r.die(/* @__PURE__ */ Error("Selected executor roles require an engine routing contract"));
|
|
25
|
+
let s = i.value;
|
|
26
|
+
if (s.engine !== n.value || s.mode !== (u ? "worker" : "client")) return yield* r.die(/* @__PURE__ */ Error("Executor roles do not match the provided workflow engine"));
|
|
27
|
+
let d = yield* r.sync(() => e(s.state?.layout ?? "default", c)), f = (e, t) => e.length === t.length && e.every((e) => t.includes(e));
|
|
28
|
+
if (!f(s.groups.available, d.available) || !f(s.groups.assigned, d.assigned)) return yield* r.die(/* @__PURE__ */ Error("Executor roles do not match the engine shard ownership"));
|
|
29
|
+
});
|
|
30
|
+
return i.effectDiscard(d).pipe(i.provideMerge(n));
|
|
31
|
+
};
|
|
32
|
+
//#endregion
|
|
33
|
+
export { s as recordWorkflowExecutorRouting, c as withWorkflowExecutorSelection };
|
|
@@ -1,29 +1,35 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
1
|
+
import { JournalRoutingGuard as e, protectJournalRoutingEffect as t, tryOwnedJournalRoutingPromise as n } from "./journalRoutingGuard.js";
|
|
2
|
+
import { resetReservedWorkflowJournal as r } from "./workflowRestartReservation.js";
|
|
3
|
+
import { JournalRoutingLayout as i, routeJournalWorkflow as a } from "./journalRoutingRuntime.js";
|
|
4
|
+
import { prepareWorkflowRestartCommand as o } from "./workflowRestartPreparation.js";
|
|
5
|
+
import { observeWorkflowRestartReplies as s } from "./workflowRestartObservation.js";
|
|
6
|
+
import { Context as c, Effect as l, Option as u } from "effect";
|
|
7
|
+
import { SqlClient as d } from "effect/sql";
|
|
8
|
+
import { MessageStorage as f, Sharding as p } from "effect/cluster";
|
|
7
9
|
//#region src/workflowRestartDispatch.ts
|
|
8
|
-
var
|
|
9
|
-
let
|
|
10
|
-
return
|
|
10
|
+
var m = (e) => l.gen(function* () {
|
|
11
|
+
let t = yield* r(e);
|
|
12
|
+
return t.kind === "waiting-capacity" ? t : (yield* (yield* p.Sharding).pollStorage, {
|
|
11
13
|
kind: "attempted",
|
|
12
|
-
journal:
|
|
14
|
+
journal: t
|
|
13
15
|
});
|
|
14
|
-
}),
|
|
15
|
-
let e = yield*
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
16
|
+
}), h = class extends c.Service()("@voltro/workflow/WorkflowRestartDispatcher") {}, g = l.gen(function* () {
|
|
17
|
+
let r = u.getOrUndefined(yield* l.serviceOption(e)), c = u.getOrUndefined(yield* l.serviceOption(i)), g = yield* d.SqlClient, _ = yield* p.Sharding, v = yield* f.MessageStorage, y = (e) => t(r, s(c === void 0 ? e : {
|
|
18
|
+
...e,
|
|
19
|
+
workflow: a(e.workflow, c)
|
|
20
|
+
})).pipe(l.provideService(d.SqlClient, g), l.provideService(p.Sharding, _), l.provideService(f.MessageStorage, v));
|
|
21
|
+
return h.of({
|
|
22
|
+
prepare: (e) => t(r, n((t) => o({
|
|
23
|
+
...e,
|
|
24
|
+
transaction: (n) => {
|
|
25
|
+
if (t.throwIfAborted(), r?.isClosed()) throw Error("Journal routing admission is closed");
|
|
26
|
+
return e.transaction(n);
|
|
27
|
+
},
|
|
28
|
+
observe: (e) => l.runPromise(y(e), { signal: t })
|
|
29
|
+
}))),
|
|
30
|
+
observe: y,
|
|
31
|
+
dispatch: (e) => t(r, m(e)).pipe(l.provideService(d.SqlClient, g), l.provideService(p.Sharding, _))
|
|
26
32
|
});
|
|
27
33
|
});
|
|
28
34
|
//#endregion
|
|
29
|
-
export {
|
|
35
|
+
export { h as WorkflowRestartDispatcher, m as dispatchWorkflowRestart, g as makeWorkflowRestartDispatcher };
|