@voyantjs/workflows-orchestrator-node 0.107.4 → 0.107.6

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
@@ -0,0 +1,150 @@
1
+ import type { ServerResponse } from "node:http"
2
+ import type { ChunkBus, ChunkEvent } from "./dashboard-chunks.js"
3
+ import type { SnapshotRunStore } from "./snapshot-run-store.js"
4
+ import type { StoreEvent, StoreStream } from "./store-stream.js"
5
+
6
+ function unrefTimer(timer: ReturnType<typeof setInterval>): void {
7
+ if (
8
+ typeof timer === "object" &&
9
+ timer !== null &&
10
+ "unref" in timer &&
11
+ typeof timer.unref === "function"
12
+ ) {
13
+ timer.unref()
14
+ }
15
+ }
16
+
17
+ export function handleSseStream(
18
+ res: ServerResponse,
19
+ stream: StoreStream,
20
+ chunkBus?: ChunkBus,
21
+ ): void {
22
+ res.writeHead(200, {
23
+ "content-type": "text/event-stream",
24
+ "cache-control": "no-store",
25
+ connection: "keep-alive",
26
+ "access-control-allow-origin": "*",
27
+ })
28
+ res.write("retry: 3000\n\n")
29
+
30
+ const writeEvent = (event: StoreEvent): void => {
31
+ try {
32
+ res.write(`event: ${event.kind}\ndata: ${JSON.stringify(event)}\n\n`)
33
+ } catch {
34
+ // Ignore write errors on closed sockets.
35
+ }
36
+ }
37
+
38
+ const writeChunk = (event: ChunkEvent): void => {
39
+ try {
40
+ res.write(`event: stream.chunk\ndata: ${JSON.stringify(event)}\n\n`)
41
+ } catch {
42
+ // Ignore write errors on closed sockets.
43
+ }
44
+ }
45
+
46
+ const unsubscribeStore = stream.subscribe(writeEvent)
47
+ const unsubscribeChunk = chunkBus ? chunkBus.subscribe(writeChunk) : () => {}
48
+ const ping = setInterval(() => {
49
+ try {
50
+ res.write(`: ping ${Date.now()}\n\n`)
51
+ } catch {
52
+ // Ignore write errors on closed sockets.
53
+ }
54
+ }, 25_000)
55
+ unrefTimer(ping)
56
+
57
+ res.on("close", () => {
58
+ clearInterval(ping)
59
+ unsubscribeStore()
60
+ unsubscribeChunk()
61
+ })
62
+ }
63
+
64
+ const TERMINAL_STATUSES = new Set([
65
+ "completed",
66
+ "failed",
67
+ "cancelled",
68
+ "compensated",
69
+ "compensation_failed",
70
+ ])
71
+
72
+ export function handleRunSseStream(
73
+ res: ServerResponse,
74
+ runId: string,
75
+ stream: StoreStream,
76
+ chunkBus: ChunkBus | undefined,
77
+ store: SnapshotRunStore,
78
+ ): void {
79
+ res.writeHead(200, {
80
+ "content-type": "text/event-stream",
81
+ "cache-control": "no-store",
82
+ connection: "keep-alive",
83
+ "access-control-allow-origin": "*",
84
+ })
85
+ res.write("retry: 3000\n\n")
86
+
87
+ let closed = false
88
+ const close = (): void => {
89
+ if (closed) return
90
+ closed = true
91
+ try {
92
+ res.end()
93
+ } catch {
94
+ // Ignore close failures.
95
+ }
96
+ }
97
+
98
+ const writeEvent = (kind: string, data: unknown): void => {
99
+ if (closed) return
100
+ try {
101
+ res.write(`event: ${kind}\ndata: ${JSON.stringify(data)}\n\n`)
102
+ } catch {
103
+ // Ignore write errors on closed sockets.
104
+ }
105
+ }
106
+
107
+ void store.get(runId).then((run) => {
108
+ if (run) {
109
+ writeEvent("hello", { run })
110
+ if (TERMINAL_STATUSES.has(run.status)) close()
111
+ } else {
112
+ writeEvent("hello", { run: null })
113
+ }
114
+ })
115
+
116
+ const unsubscribeStore = stream.subscribe((event) => {
117
+ if (event.kind === "added" || event.kind === "updated") {
118
+ if (event.run.id !== runId) return
119
+ writeEvent(event.kind, event)
120
+ if (TERMINAL_STATUSES.has(event.run.status)) close()
121
+ } else if (event.kind === "removed") {
122
+ if (event.runId !== runId) return
123
+ writeEvent(event.kind, event)
124
+ close()
125
+ }
126
+ })
127
+
128
+ const unsubscribeChunk = chunkBus
129
+ ? chunkBus.subscribe((event) => {
130
+ if (event.runId !== runId) return
131
+ writeEvent("stream.chunk", event)
132
+ })
133
+ : () => {}
134
+
135
+ const ping = setInterval(() => {
136
+ try {
137
+ res.write(`: ping ${Date.now()}\n\n`)
138
+ } catch {
139
+ // Ignore write errors on closed sockets.
140
+ }
141
+ }, 25_000)
142
+ unrefTimer(ping)
143
+
144
+ res.on("close", () => {
145
+ closed = true
146
+ clearInterval(ping)
147
+ unsubscribeStore()
148
+ unsubscribeChunk()
149
+ })
150
+ }
@@ -0,0 +1,88 @@
1
+ import { readFile, stat } from "node:fs/promises"
2
+ import { extname, join, resolve as resolvePath } from "node:path"
3
+
4
+ export function createStaticReader(rootDir: string): (path: string) => Promise<Uint8Array | null> {
5
+ const root = resolvePath(rootDir)
6
+ return async (path: string) => {
7
+ const absolute = resolvePath(root, path)
8
+ if (!absolute.startsWith(`${root}/`) && absolute !== root) return null
9
+ try {
10
+ return await readFile(absolute)
11
+ } catch {
12
+ return null
13
+ }
14
+ }
15
+ }
16
+
17
+ export async function findDashboardDir(startFrom: string): Promise<string | undefined> {
18
+ const candidates = [
19
+ join(startFrom, "apps/workflows-local-dashboard/dist"),
20
+ join(startFrom, "../local-dashboard/dist"),
21
+ join(startFrom, "../../apps/workflows-local-dashboard/dist"),
22
+ join(startFrom, "../../../apps/workflows-local-dashboard/dist"),
23
+ ]
24
+ for (const candidate of candidates) {
25
+ try {
26
+ const entry = await stat(join(candidate, "index.html"))
27
+ if (entry.isFile()) return candidate
28
+ } catch {
29
+ // Continue scanning candidate locations.
30
+ }
31
+ }
32
+ return undefined
33
+ }
34
+
35
+ export async function assertReadableFile(path: string, label: string): Promise<void> {
36
+ let info: Awaited<ReturnType<typeof stat>>
37
+ try {
38
+ info = await stat(path)
39
+ } catch (err) {
40
+ throw new Error(`voyant workflows selfhost: ${label} not found at "${path}"`, { cause: err })
41
+ }
42
+ if (!info.isFile()) {
43
+ throw new Error(`voyant workflows selfhost: ${label} must be a file (got "${path}")`)
44
+ }
45
+ }
46
+
47
+ export async function assertReadableDirectory(path: string, label: string): Promise<void> {
48
+ let info: Awaited<ReturnType<typeof stat>>
49
+ try {
50
+ info = await stat(path)
51
+ } catch (err) {
52
+ throw new Error(`voyant workflows selfhost: ${label} not found at "${path}"`, { cause: err })
53
+ }
54
+ if (!info.isDirectory()) {
55
+ throw new Error(`voyant workflows selfhost: ${label} must be a directory (got "${path}")`)
56
+ }
57
+ }
58
+
59
+ export function mimeFor(path: string): string {
60
+ const ext = extname(path).toLowerCase()
61
+ switch (ext) {
62
+ case ".html":
63
+ return "text/html; charset=utf-8"
64
+ case ".js":
65
+ case ".mjs":
66
+ return "application/javascript; charset=utf-8"
67
+ case ".css":
68
+ return "text/css; charset=utf-8"
69
+ case ".json":
70
+ return "application/json; charset=utf-8"
71
+ case ".svg":
72
+ return "image/svg+xml"
73
+ case ".png":
74
+ return "image/png"
75
+ case ".map":
76
+ return "application/json"
77
+ default:
78
+ return "application/octet-stream"
79
+ }
80
+ }
81
+
82
+ export function urlPath(raw: string): string {
83
+ try {
84
+ return new URL(raw, "http://local").pathname
85
+ } catch {
86
+ return raw
87
+ }
88
+ }
@@ -0,0 +1,106 @@
1
+ import type { createServer } from "node:http"
2
+ import type { ServiceResolver } from "@voyantjs/workflows/driver"
3
+ import type { WaitpointInjection } from "@voyantjs/workflows-orchestrator"
4
+ import type { ChunkBus } from "./dashboard-chunks.js"
5
+ import type { SchedulerHandle } from "./scheduler.js"
6
+ import type { ListFilter, SnapshotRunStore, StoredRun } from "./snapshot-run-store.js"
7
+
8
+ export interface ServeDeps {
9
+ store: SnapshotRunStore
10
+ createServer: typeof createServer
11
+ shutdown?: () => void | Promise<void>
12
+ healthCheck?: () => Promise<HealthReport> | HealthReport
13
+ readinessCheck?: () => Promise<HealthReport> | HealthReport
14
+ collectMetrics?: () => Promise<string> | string
15
+ staticDir?: string
16
+ readStatic?: (path: string) => Promise<Uint8Array | null>
17
+ triggerRun?: (args: {
18
+ workflowId: string
19
+ input: unknown
20
+ runId?: string
21
+ tags?: string[]
22
+ triggeredByUserId?: string | null
23
+ }) => Promise<{ ok: true; saved: StoredRun } | { ok: false; message: string; exitCode: number }>
24
+ resumeRun?: (args: {
25
+ parentRunId: string
26
+ workflowId?: string
27
+ input?: unknown
28
+ resumeFromStep?: string
29
+ seedResults?: Record<string, unknown>
30
+ runId?: string
31
+ tags?: string[]
32
+ triggeredByUserId?: string | null
33
+ }) => Promise<
34
+ | { ok: true; saved: StoredRun; parentRunId: string; resumeFromStep: string }
35
+ | { ok: false; message: string; exitCode: number }
36
+ >
37
+ listWorkflows?: () => { id: string; description?: string }[]
38
+ injectWaitpoint?: (args: {
39
+ runId: string
40
+ injection: WaitpointInjection
41
+ }) => Promise<{ ok: true; saved: StoredRun } | { ok: false; message: string; exitCode: number }>
42
+ scheduler?: SchedulerHandle
43
+ listSchedules?: () => { workflowId: string; name?: string; nextAt: number; done: boolean }[]
44
+ cancelRun?: (args: {
45
+ runId: string
46
+ }) => Promise<{ ok: true; saved: StoredRun } | { ok: false; message: string; exitCode: number }>
47
+ chunkBus?: ChunkBus
48
+ }
49
+
50
+ export interface RequestHandlerDeps {
51
+ store: SnapshotRunStore
52
+ healthCheck?: ServeDeps["healthCheck"]
53
+ readinessCheck?: ServeDeps["readinessCheck"]
54
+ collectMetrics?: ServeDeps["collectMetrics"]
55
+ readStatic?: (path: string) => Promise<Uint8Array | null>
56
+ hasStaticDashboard?: boolean
57
+ triggerRun?: ServeDeps["triggerRun"]
58
+ resumeRun?: ServeDeps["resumeRun"]
59
+ listWorkflows?: ServeDeps["listWorkflows"]
60
+ injectWaitpoint?: ServeDeps["injectWaitpoint"]
61
+ listSchedules?: ServeDeps["listSchedules"]
62
+ cancelRun?: ServeDeps["cancelRun"]
63
+ }
64
+
65
+ export interface HandlerResponse {
66
+ status: number
67
+ headers: Record<string, string>
68
+ body: string | Uint8Array
69
+ }
70
+
71
+ export interface HealthReport {
72
+ ok: boolean
73
+ service?: string
74
+ checks?: Record<string, "ok" | "error">
75
+ details?: Record<string, unknown>
76
+ }
77
+
78
+ export interface MetricsSnapshot {
79
+ workflowsRegistered: number
80
+ schedulesRegistered: number
81
+ runsTotal: number
82
+ wakeupsTotal: number
83
+ runsByStatus: Record<string, number>
84
+ generatedAtMs: number
85
+ }
86
+
87
+ export interface ServeHandle {
88
+ close: () => Promise<void>
89
+ url: string
90
+ }
91
+
92
+ export interface NodeSelfHostServerOptions {
93
+ entryFile: string
94
+ port?: number
95
+ host?: string
96
+ staticDir?: string
97
+ cacheBustEntry?: boolean
98
+ services?: ServiceResolver
99
+ store?: SnapshotRunStore
100
+ databaseUrl?: string
101
+ wakeupPollIntervalMs?: number
102
+ wakeupLeaseMs?: number
103
+ wakeupLeaseOwner?: string
104
+ }
105
+
106
+ export type DashboardListFilter = ListFilter
@@ -0,0 +1,9 @@
1
+ export const localTenantMeta = {
2
+ tenantId: "tnt_local",
3
+ projectId: "prj_local",
4
+ organizationId: "org_local",
5
+ }
6
+
7
+ export function createDefaultWakeupLeaseOwner(): string {
8
+ return `node-selfhost-${process.pid}-${Math.random().toString(36).slice(2, 8)}`
9
+ }