@voyantjs/workflows-orchestrator-node 0.107.5 → 0.107.7

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 (69) hide show
  1. package/dist/dashboard-chunks.d.ts +17 -0
  2. package/dist/dashboard-chunks.d.ts.map +1 -0
  3. package/dist/dashboard-chunks.js +19 -0
  4. package/dist/dashboard-http-server.d.ts +6 -0
  5. package/dist/dashboard-http-server.d.ts.map +1 -0
  6. package/dist/dashboard-http-server.js +99 -0
  7. package/dist/dashboard-metrics.d.ts +3 -0
  8. package/dist/dashboard-metrics.d.ts.map +1 -0
  9. package/dist/dashboard-metrics.js +26 -0
  10. package/dist/dashboard-request.d.ts +7 -0
  11. package/dist/dashboard-request.d.ts.map +1 -0
  12. package/dist/dashboard-request.js +436 -0
  13. package/dist/dashboard-server.d.ts +9 -171
  14. package/dist/dashboard-server.d.ts.map +1 -1
  15. package/dist/dashboard-server.js +7 -1229
  16. package/dist/dashboard-sse.d.ts +7 -0
  17. package/dist/dashboard-sse.d.ts.map +1 -0
  18. package/dist/dashboard-sse.js +134 -0
  19. package/dist/dashboard-static.d.ts +7 -0
  20. package/dist/dashboard-static.d.ts.map +1 -0
  21. package/dist/dashboard-static.js +89 -0
  22. package/dist/dashboard-types.d.ts +134 -0
  23. package/dist/dashboard-types.d.ts.map +1 -0
  24. package/dist/dashboard-types.js +1 -0
  25. package/dist/node-selfhost-defaults.d.ts +7 -0
  26. package/dist/node-selfhost-defaults.d.ts.map +1 -0
  27. package/dist/node-selfhost-defaults.js +8 -0
  28. package/dist/node-selfhost-deps.d.ts +4 -0
  29. package/dist/node-selfhost-deps.d.ts.map +1 -0
  30. package/dist/node-selfhost-deps.js +403 -0
  31. package/dist/node-selfhost-resume-input.d.ts +4 -0
  32. package/dist/node-selfhost-resume-input.d.ts.map +1 -0
  33. package/dist/node-selfhost-resume-input.js +20 -0
  34. package/dist/node-standalone-driver.d.ts.map +1 -1
  35. package/dist/node-standalone-driver.js +40 -3
  36. package/dist/node-step-runner.d.ts +3 -0
  37. package/dist/node-step-runner.d.ts.map +1 -0
  38. package/dist/node-step-runner.js +26 -0
  39. package/dist/postgres-manifest-store.d.ts.map +1 -1
  40. package/dist/postgres-manifest-store.js +6 -2
  41. package/dist/postgres-run-record-store.js +1 -1
  42. package/dist/postgres-schema.d.ts.map +1 -1
  43. package/dist/postgres-schema.js +2 -0
  44. package/dist/sleep-alarm-manager.d.ts.map +1 -1
  45. package/dist/sleep-alarm-manager.js +9 -1
  46. package/dist/store-stream.d.ts.map +1 -1
  47. package/dist/store-stream.js +9 -1
  48. package/dist/wakeup-poller.d.ts.map +1 -1
  49. package/dist/wakeup-poller.js +9 -1
  50. package/package.json +3 -3
  51. package/src/dashboard-chunks.ts +35 -0
  52. package/src/dashboard-http-server.ts +118 -0
  53. package/src/dashboard-metrics.ts +39 -0
  54. package/src/dashboard-request.ts +488 -0
  55. package/src/dashboard-server.ts +17 -1535
  56. package/src/dashboard-sse.ts +150 -0
  57. package/src/dashboard-static.ts +88 -0
  58. package/src/dashboard-types.ts +106 -0
  59. package/src/node-selfhost-defaults.ts +9 -0
  60. package/src/node-selfhost-deps.ts +495 -0
  61. package/src/node-selfhost-resume-input.ts +27 -0
  62. package/src/node-standalone-driver.ts +59 -3
  63. package/src/node-step-runner.ts +28 -0
  64. package/src/postgres-manifest-store.ts +2 -0
  65. package/src/postgres-run-record-store.ts +1 -1
  66. package/src/postgres-schema.ts +2 -0
  67. package/src/sleep-alarm-manager.ts +12 -1
  68. package/src/store-stream.ts +12 -1
  69. package/src/wakeup-poller.ts +12 -1
@@ -1,3 +1,11 @@
1
+ function unrefTimer(timer) {
2
+ if (typeof timer === "object" &&
3
+ timer !== null &&
4
+ "unref" in timer &&
5
+ typeof timer.unref === "function") {
6
+ timer.unref();
7
+ }
8
+ }
1
9
  export function diffSnapshots(prev, next) {
2
10
  const prevById = new Map(prev.map((run) => [run.id, run]));
3
11
  const nextById = new Map(next.map((run) => [run.id, run]));
@@ -57,7 +65,7 @@ export function createStoreStream(store, opts = {}) {
57
65
  const handle = setIntervalImpl(() => {
58
66
  void pollOnce();
59
67
  }, intervalMs);
60
- handle.unref?.();
68
+ unrefTimer(handle);
61
69
  return {
62
70
  subscribe(listener) {
63
71
  const dispatchSnapshot = async () => {
@@ -1 +1 @@
1
- {"version":3,"file":"wakeup-poller.d.ts","sourceRoot":"","sources":["../src/wakeup-poller.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,KAAK,SAAS,EACd,eAAe,EACf,KAAK,WAAW,EAChB,KAAK,WAAW,EACjB,MAAM,kCAAkC,CAAA;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAGpD,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,gBAAgB,CAAC,OAAO,SAAS,qBAAqB;IACrE,WAAW,EAAE,WAAW,CAAA;IACxB,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,CAAA;IACvD,OAAO,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IAC9C,QAAQ,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,SAAS,CAAA;IACxC,UAAU,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,CAAC,EAAE,OAAO,KAAK,OAAO,CAAA;IAC1D,OAAO,EAAE,WAAW,CAAA;IACpB,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;IAClB,WAAW,CAAC,EAAE,OAAO,WAAW,CAAA;IAChC,aAAa,CAAC,EAAE,OAAO,aAAa,CAAA;IACpC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,WAAW,CAAA;KAAE,KAAK,IAAI,CAAA;IACtE,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;IAC1E,cAAc,CAAC,EAAE,OAAO,sBAAsB,CAAA;IAC9C,mBAAmB,CAAC,EAAE,OAAO,eAAe,CAAA;CAC7C;AAED,MAAM,WAAW,YAAY,CAAC,OAAO,SAAS,qBAAqB;IACjE,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,IAAI,EAAE,MAAM,IAAI,CAAA;IAChB,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IACzB,aAAa,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAClD;AAED,wBAAgB,kBAAkB,CAAC,OAAO,SAAS,qBAAqB,EACtE,IAAI,EAAE,gBAAgB,CAAC,OAAO,CAAC,GAC9B,YAAY,CAAC,OAAO,CAAC,CAuFvB"}
1
+ {"version":3,"file":"wakeup-poller.d.ts","sourceRoot":"","sources":["../src/wakeup-poller.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,KAAK,SAAS,EACd,eAAe,EACf,KAAK,WAAW,EAChB,KAAK,WAAW,EACjB,MAAM,kCAAkC,CAAA;AACzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAGpD,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,gBAAgB,CAAC,OAAO,SAAS,qBAAqB;IACrE,WAAW,EAAE,WAAW,CAAA;IACxB,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC,CAAA;IACvD,OAAO,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;IAC9C,QAAQ,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,SAAS,CAAA;IACxC,UAAU,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,CAAC,EAAE,OAAO,KAAK,OAAO,CAAA;IAC1D,OAAO,EAAE,WAAW,CAAA;IACpB,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;IAClB,WAAW,CAAC,EAAE,OAAO,WAAW,CAAA;IAChC,aAAa,CAAC,EAAE,OAAO,aAAa,CAAA;IACpC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,WAAW,CAAA;KAAE,KAAK,IAAI,CAAA;IACtE,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;IAC1E,cAAc,CAAC,EAAE,OAAO,sBAAsB,CAAA;IAC9C,mBAAmB,CAAC,EAAE,OAAO,eAAe,CAAA;CAC7C;AAED,MAAM,WAAW,YAAY,CAAC,OAAO,SAAS,qBAAqB;IACjE,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,IAAI,EAAE,MAAM,IAAI,CAAA;IAChB,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IACzB,aAAa,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAClD;AAaD,wBAAgB,kBAAkB,CAAC,OAAO,SAAS,qBAAqB,EACtE,IAAI,EAAE,gBAAgB,CAAC,OAAO,CAAC,GAC9B,YAAY,CAAC,OAAO,CAAC,CAuFvB"}
@@ -1,5 +1,13 @@
1
1
  import { createInMemoryRunStore, resumeDueAlarms, } from "@voyantjs/workflows-orchestrator";
2
2
  import { syncWakeupFromRecord } from "./wakeup-store.js";
3
+ function unrefTimer(timer) {
4
+ if (typeof timer === "object" &&
5
+ timer !== null &&
6
+ "unref" in timer &&
7
+ typeof timer.unref === "function") {
8
+ timer.unref();
9
+ }
10
+ }
3
11
  export function createWakeupPoller(deps) {
4
12
  const intervalMs = deps.intervalMs ?? 1_000;
5
13
  const leaseMs = deps.leaseMs ?? Math.max(intervalMs * 4, 5_000);
@@ -71,7 +79,7 @@ export function createWakeupPoller(deps) {
71
79
  timer = setIntervalImpl(() => {
72
80
  void poll().catch(() => { });
73
81
  }, intervalMs);
74
- timer.unref?.();
82
+ unrefTimer(timer);
75
83
  },
76
84
  stop() {
77
85
  if (!timer)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voyantjs/workflows-orchestrator-node",
3
- "version": "0.107.5",
3
+ "version": "0.107.7",
4
4
  "description": "Node/Docker runtime primitives for @voyantjs/workflows-orchestrator, including a file-backed run store and local scheduler.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -29,8 +29,8 @@
29
29
  "dependencies": {
30
30
  "drizzle-orm": "^0.45.2",
31
31
  "pg": "^8.20.0",
32
- "@voyantjs/workflows-orchestrator": "^0.107.5",
33
- "@voyantjs/workflows": "^0.107.5"
32
+ "@voyantjs/workflows-orchestrator": "^0.107.7",
33
+ "@voyantjs/workflows": "^0.107.7"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@types/node": "^20.12.0",
@@ -0,0 +1,35 @@
1
+ export interface ChunkEvent {
2
+ runId: string
3
+ chunk: {
4
+ streamId: string
5
+ seq: number
6
+ encoding: "text" | "json" | "base64"
7
+ chunk: unknown
8
+ final: boolean
9
+ at: number
10
+ }
11
+ }
12
+
13
+ export interface ChunkBus {
14
+ publish(event: ChunkEvent): void
15
+ subscribe(fn: (event: ChunkEvent) => void): () => void
16
+ }
17
+
18
+ export function createChunkBus(): ChunkBus {
19
+ const subs = new Set<(event: ChunkEvent) => void>()
20
+ return {
21
+ publish(event) {
22
+ for (const fn of subs) {
23
+ try {
24
+ fn(event)
25
+ } catch {
26
+ // Ignore subscriber errors so streaming keeps going.
27
+ }
28
+ }
29
+ },
30
+ subscribe(fn) {
31
+ subs.add(fn)
32
+ return () => subs.delete(fn)
33
+ },
34
+ }
35
+ }
@@ -0,0 +1,118 @@
1
+ import type { IncomingMessage, Server, ServerResponse } from "node:http"
2
+ import { handleRequest } from "./dashboard-request.js"
3
+ import { handleRunSseStream, handleSseStream } from "./dashboard-sse.js"
4
+ import { createStaticReader, urlPath } from "./dashboard-static.js"
5
+ import type { ServeDeps, ServeHandle } from "./dashboard-types.js"
6
+ import { createStoreStream, type StoreStream } from "./store-stream.js"
7
+
8
+ function closeAllConnections(server: Server): void {
9
+ if ("closeAllConnections" in server && typeof server.closeAllConnections === "function") {
10
+ server.closeAllConnections()
11
+ }
12
+ }
13
+
14
+ export async function startServer(
15
+ options: { port: number; host: string },
16
+ deps: ServeDeps,
17
+ ): Promise<ServeHandle> {
18
+ const readStatic =
19
+ deps.readStatic ?? (deps.staticDir ? createStaticReader(deps.staticDir) : undefined)
20
+ const hasStaticDashboard = Boolean(readStatic)
21
+
22
+ let storeStream: StoreStream | undefined
23
+ const getStoreStream = (): StoreStream => {
24
+ if (!storeStream) storeStream = createStoreStream(deps.store)
25
+ return storeStream
26
+ }
27
+
28
+ const server: Server = deps.createServer(async (req: IncomingMessage, res: ServerResponse) => {
29
+ const method = (req.method ?? "GET").toUpperCase()
30
+ const url = req.url ?? "/"
31
+
32
+ if ((method === "GET" || method === "HEAD") && urlPath(url) === "/api/runs/stream") {
33
+ handleSseStream(res, getStoreStream(), deps.chunkBus)
34
+ return
35
+ }
36
+
37
+ const perRunMatch = urlPath(url).match(/^\/api\/runs\/([^/]+)\/stream$/)
38
+ if ((method === "GET" || method === "HEAD") && perRunMatch) {
39
+ const runId = decodeURIComponent(perRunMatch[1]!)
40
+ handleRunSseStream(res, runId, getStoreStream(), deps.chunkBus, deps.store)
41
+ return
42
+ }
43
+
44
+ try {
45
+ const body = method === "POST" ? await readRequestBody(req) : undefined
46
+ const response = await handleRequest(
47
+ { method, url, body },
48
+ {
49
+ store: deps.store,
50
+ healthCheck: deps.healthCheck,
51
+ readinessCheck: deps.readinessCheck,
52
+ collectMetrics: deps.collectMetrics,
53
+ readStatic,
54
+ hasStaticDashboard,
55
+ triggerRun: deps.triggerRun,
56
+ resumeRun: deps.resumeRun,
57
+ listWorkflows: deps.listWorkflows,
58
+ injectWaitpoint: deps.injectWaitpoint,
59
+ listSchedules: deps.listSchedules,
60
+ cancelRun: deps.cancelRun,
61
+ },
62
+ )
63
+ res.writeHead(response.status, response.headers)
64
+ res.end(response.body)
65
+ } catch (err) {
66
+ const message = err instanceof Error ? err.message : String(err)
67
+ res.writeHead(500, { "content-type": "application/json" })
68
+ res.end(JSON.stringify({ error: "internal_error", message }))
69
+ }
70
+ })
71
+
72
+ await new Promise<void>((resolve, reject) => {
73
+ server.once("error", reject)
74
+ server.listen(options.port, options.host, () => {
75
+ server.off("error", reject)
76
+ resolve()
77
+ })
78
+ })
79
+
80
+ deps.scheduler?.start()
81
+
82
+ return {
83
+ url: `http://${options.host}:${options.port}`,
84
+ close: () =>
85
+ new Promise<void>((resolve, reject) => {
86
+ deps.scheduler?.stop()
87
+ storeStream?.stop()
88
+ closeAllConnections(server)
89
+ server.close((err) => {
90
+ if (err) {
91
+ reject(err)
92
+ return
93
+ }
94
+ Promise.resolve(deps.shutdown?.()).then(() => resolve(), reject)
95
+ })
96
+ }),
97
+ }
98
+ }
99
+
100
+ async function readRequestBody(req: IncomingMessage): Promise<string> {
101
+ const maxBytes = 1_000_000
102
+ return new Promise((resolve, reject) => {
103
+ let total = 0
104
+ const chunks: Uint8Array[] = []
105
+ req.on("data", (chunk: Uint8Array) => {
106
+ total += chunk.length
107
+ if (total > maxBytes) {
108
+ req.destroy(new Error("request body exceeds 1MB"))
109
+ return
110
+ }
111
+ chunks.push(chunk)
112
+ })
113
+ req.on("end", () => {
114
+ resolve(Buffer.concat(chunks).toString("utf8"))
115
+ })
116
+ req.on("error", reject)
117
+ })
118
+ }
@@ -0,0 +1,39 @@
1
+ import type { MetricsSnapshot } from "./dashboard-types.js"
2
+
3
+ export function renderMetrics(snapshot: MetricsSnapshot): string {
4
+ const lines = [
5
+ "# HELP voyant_selfhost_up Self-host server availability.",
6
+ "# TYPE voyant_selfhost_up gauge",
7
+ "voyant_selfhost_up 1",
8
+ "# HELP voyant_selfhost_workflows_registered Registered workflow count.",
9
+ "# TYPE voyant_selfhost_workflows_registered gauge",
10
+ `voyant_selfhost_workflows_registered ${snapshot.workflowsRegistered}`,
11
+ "# HELP voyant_selfhost_schedules_registered Registered schedule count.",
12
+ "# TYPE voyant_selfhost_schedules_registered gauge",
13
+ `voyant_selfhost_schedules_registered ${snapshot.schedulesRegistered}`,
14
+ "# HELP voyant_selfhost_runs_total Persisted run count.",
15
+ "# TYPE voyant_selfhost_runs_total gauge",
16
+ `voyant_selfhost_runs_total ${snapshot.runsTotal}`,
17
+ "# HELP voyant_selfhost_runs_status Run count by status.",
18
+ "# TYPE voyant_selfhost_runs_status gauge",
19
+ ]
20
+ for (const [status, count] of Object.entries(snapshot.runsByStatus).sort(([a], [b]) =>
21
+ a.localeCompare(b),
22
+ )) {
23
+ lines.push(`voyant_selfhost_runs_status{status="${escapeMetricLabelValue(status)}"} ${count}`)
24
+ }
25
+ lines.push(
26
+ "# HELP voyant_selfhost_wakeups_total Persisted wakeup count.",
27
+ "# TYPE voyant_selfhost_wakeups_total gauge",
28
+ `voyant_selfhost_wakeups_total ${snapshot.wakeupsTotal}`,
29
+ "# HELP voyant_selfhost_metrics_generated_at_seconds Metrics generation timestamp.",
30
+ "# TYPE voyant_selfhost_metrics_generated_at_seconds gauge",
31
+ `voyant_selfhost_metrics_generated_at_seconds ${Math.floor(snapshot.generatedAtMs / 1000)}`,
32
+ "",
33
+ )
34
+ return lines.join("\n")
35
+ }
36
+
37
+ function escapeMetricLabelValue(value: string): string {
38
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n")
39
+ }