@mokronos/wfkit 0.1.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 (108) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +57 -0
  3. package/dist/authoring.d.ts +7 -0
  4. package/dist/authoring.d.ts.map +1 -0
  5. package/dist/authoring.js +8 -0
  6. package/dist/authoring.js.map +1 -0
  7. package/dist/cli/integrations.d.ts +3 -0
  8. package/dist/cli/integrations.d.ts.map +1 -0
  9. package/dist/cli/integrations.js +40 -0
  10. package/dist/cli/integrations.js.map +1 -0
  11. package/dist/cli/main.d.ts +3 -0
  12. package/dist/cli/main.d.ts.map +1 -0
  13. package/dist/cli/main.js +591 -0
  14. package/dist/cli/main.js.map +1 -0
  15. package/dist/core.d.ts +198 -0
  16. package/dist/core.d.ts.map +1 -0
  17. package/dist/core.js +1061 -0
  18. package/dist/core.js.map +1 -0
  19. package/dist/errors.d.ts +3 -0
  20. package/dist/errors.d.ts.map +1 -0
  21. package/dist/errors.js +13 -0
  22. package/dist/errors.js.map +1 -0
  23. package/dist/events.d.ts +154 -0
  24. package/dist/events.d.ts.map +1 -0
  25. package/dist/events.js +24 -0
  26. package/dist/events.js.map +1 -0
  27. package/dist/index.d.ts +15 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +11 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/replay.d.ts +3 -0
  32. package/dist/replay.d.ts.map +1 -0
  33. package/dist/replay.js +28 -0
  34. package/dist/replay.js.map +1 -0
  35. package/dist/runtime.d.ts +70 -0
  36. package/dist/runtime.d.ts.map +1 -0
  37. package/dist/runtime.js +183 -0
  38. package/dist/runtime.js.map +1 -0
  39. package/dist/schema.d.ts +18 -0
  40. package/dist/schema.d.ts.map +1 -0
  41. package/dist/schema.js +19 -0
  42. package/dist/schema.js.map +1 -0
  43. package/dist/schemas.d.ts +1198 -0
  44. package/dist/schemas.d.ts.map +1 -0
  45. package/dist/schemas.js +383 -0
  46. package/dist/schemas.js.map +1 -0
  47. package/dist/sdk/artifact.d.ts +41 -0
  48. package/dist/sdk/artifact.d.ts.map +1 -0
  49. package/dist/sdk/artifact.js +94 -0
  50. package/dist/sdk/artifact.js.map +1 -0
  51. package/dist/sdk/graph.d.ts +15 -0
  52. package/dist/sdk/graph.d.ts.map +1 -0
  53. package/dist/sdk/graph.js +388 -0
  54. package/dist/sdk/graph.js.map +1 -0
  55. package/dist/sdk/index.d.ts +14 -0
  56. package/dist/sdk/index.d.ts.map +1 -0
  57. package/dist/sdk/index.js +8 -0
  58. package/dist/sdk/index.js.map +1 -0
  59. package/dist/sdk/integrations.d.ts +18 -0
  60. package/dist/sdk/integrations.d.ts.map +1 -0
  61. package/dist/sdk/integrations.js +20 -0
  62. package/dist/sdk/integrations.js.map +1 -0
  63. package/dist/sdk/json.d.ts +4 -0
  64. package/dist/sdk/json.d.ts.map +1 -0
  65. package/dist/sdk/json.js +31 -0
  66. package/dist/sdk/json.js.map +1 -0
  67. package/dist/sdk/loader.d.ts +10 -0
  68. package/dist/sdk/loader.d.ts.map +1 -0
  69. package/dist/sdk/loader.js +75 -0
  70. package/dist/sdk/loader.js.map +1 -0
  71. package/dist/sdk/sdk.d.ts +102 -0
  72. package/dist/sdk/sdk.d.ts.map +1 -0
  73. package/dist/sdk/sdk.js +669 -0
  74. package/dist/sdk/sdk.js.map +1 -0
  75. package/dist/sdk/sqlite.d.ts +8 -0
  76. package/dist/sdk/sqlite.d.ts.map +1 -0
  77. package/dist/sdk/sqlite.js +288 -0
  78. package/dist/sdk/sqlite.js.map +1 -0
  79. package/dist/signal.d.ts +17 -0
  80. package/dist/signal.d.ts.map +1 -0
  81. package/dist/signal.js +78 -0
  82. package/dist/signal.js.map +1 -0
  83. package/dist/testing/index.d.ts +35 -0
  84. package/dist/testing/index.d.ts.map +1 -0
  85. package/dist/testing/index.js +244 -0
  86. package/dist/testing/index.js.map +1 -0
  87. package/package.json +67 -0
  88. package/src/authoring.ts +32 -0
  89. package/src/cli/integrations.ts +75 -0
  90. package/src/cli/main.ts +793 -0
  91. package/src/core.ts +1515 -0
  92. package/src/errors.ts +19 -0
  93. package/src/events.ts +37 -0
  94. package/src/index.ts +109 -0
  95. package/src/replay.ts +29 -0
  96. package/src/runtime.ts +314 -0
  97. package/src/schema.ts +34 -0
  98. package/src/schemas.ts +473 -0
  99. package/src/sdk/artifact.ts +176 -0
  100. package/src/sdk/graph.ts +486 -0
  101. package/src/sdk/index.ts +64 -0
  102. package/src/sdk/integrations.ts +49 -0
  103. package/src/sdk/json.ts +40 -0
  104. package/src/sdk/loader.ts +99 -0
  105. package/src/sdk/sdk.ts +911 -0
  106. package/src/sdk/sqlite.ts +405 -0
  107. package/src/signal.ts +116 -0
  108. package/src/testing/index.ts +341 -0
@@ -0,0 +1,405 @@
1
+ import { mkdirSync, readFileSync } from "node:fs"
2
+ import path from "node:path"
3
+ import { Schema } from "effect"
4
+ import {
5
+ WorkflowHistoryEvent as WorkflowHistoryEventSchema,
6
+ WorkflowRunEventRecord as WorkflowRunEventRecordSchema,
7
+ WorkflowRunRecord as WorkflowRunRecordSchema
8
+ } from "../schemas.ts"
9
+ import type {
10
+ WorkflowArtifact,
11
+ WorkflowRepository,
12
+ WorkflowRunEventRecord,
13
+ WorkflowRunRecord,
14
+ WorkflowRunStatus,
15
+ WorkflowStore
16
+ } from "./artifact.ts"
17
+ import { parseJsonText, toJsonText } from "./json.ts"
18
+
19
+ interface WorkflowRow {
20
+ readonly id: string
21
+ readonly name: string
22
+ readonly version: string
23
+ readonly source: string | null
24
+ readonly entrypoint?: string | null
25
+ readonly export_name: string | null
26
+ readonly created_at: string | null
27
+ }
28
+
29
+ interface WorkflowRunRow {
30
+ readonly id: string
31
+ readonly workflow_id: string
32
+ readonly workflow_version: string
33
+ readonly status: WorkflowRunStatus
34
+ readonly input_json: string
35
+ readonly result_json: string | null
36
+ readonly error_json: string | null
37
+ readonly started_at: string
38
+ readonly finished_at: string | null
39
+ }
40
+
41
+ interface WorkflowRunEventRow {
42
+ readonly id: number
43
+ readonly run_id: string
44
+ readonly sequence: number
45
+ readonly type: string
46
+ readonly event_json: string
47
+ readonly created_at: string
48
+ }
49
+
50
+ interface SqliteStatement<Row> {
51
+ get(...params: ReadonlyArray<unknown>): Row | null
52
+ all(...params: ReadonlyArray<unknown>): Row[]
53
+ run(...params: ReadonlyArray<unknown>): unknown
54
+ }
55
+
56
+ interface SqliteDatabase {
57
+ exec(sql: string): unknown
58
+ prepare<Row>(sql: string): SqliteStatement<Row>
59
+ }
60
+
61
+ export interface SqliteWorkflowRepositoryOptions {
62
+ readonly databasePath?: string
63
+ readonly rootDir?: string
64
+ }
65
+
66
+ const defaultDatabasePath = (rootDir: string) => path.join(rootDir, ".wf", "wf.sqlite")
67
+
68
+ const nowIso = () => new Date().toISOString()
69
+
70
+ const optionalStringField = <K extends string>(
71
+ key: K,
72
+ value: string | undefined
73
+ ): { readonly [P in K]?: string } => value === undefined ? {} : { [key]: value } as { readonly [P in K]?: string }
74
+
75
+ const toArtifact = (rootDir: string, row: WorkflowRow): WorkflowArtifact => ({
76
+ id: row.id,
77
+ name: row.name,
78
+ version: row.version,
79
+ source: row.source !== null && row.source.length > 0
80
+ ? row.source
81
+ : legacySourceFromEntrypoint(rootDir, row.entrypoint),
82
+ ...optionalStringField("exportName", row.export_name ?? undefined),
83
+ ...optionalStringField("createdAt", row.created_at ?? undefined)
84
+ })
85
+
86
+ const legacySourceFromEntrypoint = (rootDir: string, entrypoint: string | null | undefined): string => {
87
+ if (entrypoint === undefined || entrypoint === null || entrypoint.length === 0) {
88
+ throw new Error("Stored workflow row is missing source")
89
+ }
90
+
91
+ const sourcePath = path.isAbsolute(entrypoint)
92
+ ? entrypoint
93
+ : path.resolve(rootDir, entrypoint)
94
+
95
+ return readFileSync(sourcePath, "utf8")
96
+ }
97
+
98
+ const toRunRecord = (row: WorkflowRunRow): WorkflowRunRecord => Schema.decodeUnknownSync(WorkflowRunRecordSchema)({
99
+ id: row.id,
100
+ workflowId: row.workflow_id,
101
+ workflowVersion: row.workflow_version,
102
+ status: row.status,
103
+ input: JSON.parse(row.input_json),
104
+ result: parseJsonText(row.result_json),
105
+ error: parseJsonText(row.error_json),
106
+ startedAt: row.started_at,
107
+ ...optionalStringField("finishedAt", row.finished_at ?? undefined)
108
+ })
109
+
110
+ const toRunEventRecord = (row: WorkflowRunEventRow): WorkflowRunEventRecord => Schema.decodeUnknownSync(
111
+ WorkflowRunEventRecordSchema
112
+ )({
113
+ id: row.id,
114
+ runId: row.run_id,
115
+ sequence: row.sequence,
116
+ type: row.type,
117
+ event: Schema.decodeUnknownSync(WorkflowHistoryEventSchema)(JSON.parse(row.event_json)),
118
+ createdAt: row.created_at
119
+ })
120
+
121
+ const normalizeParams = (params: ReadonlyArray<unknown>) =>
122
+ params.map((param) => param === undefined ? null : param)
123
+
124
+ const createBunDatabase = async (databasePath: string): Promise<SqliteDatabase> => {
125
+ const { Database } = await import("bun:sqlite")
126
+ const db = new Database(databasePath, { create: true, readwrite: true })
127
+ return {
128
+ exec: (sql) => db.exec(sql),
129
+ prepare: <Row>(sql: string): SqliteStatement<Row> => {
130
+ const statement = db.query<Row, any>(sql)
131
+ return {
132
+ get: (...params) => statement.get(...params as any[]) ?? null,
133
+ all: (...params) => statement.all(...params as any[]),
134
+ run: (...params) => statement.run(...params as any[])
135
+ }
136
+ }
137
+ }
138
+ }
139
+
140
+ const createNodeDatabase = async (databasePath: string): Promise<SqliteDatabase> => {
141
+ const { DatabaseSync } = await import("node:sqlite")
142
+ const db = new DatabaseSync(databasePath)
143
+ return {
144
+ exec: (sql) => db.exec(sql),
145
+ prepare: <Row>(sql: string): SqliteStatement<Row> => {
146
+ const statement = db.prepare(sql)
147
+ return {
148
+ get: (...params) => statement.get(...normalizeParams(params) as any[]) as Row | undefined ?? null,
149
+ all: (...params) => statement.all(...normalizeParams(params) as any[]) as Row[],
150
+ run: (...params) => statement.run(...normalizeParams(params) as any[])
151
+ }
152
+ }
153
+ }
154
+ }
155
+
156
+ const createDatabase = async (databasePath: string): Promise<SqliteDatabase> =>
157
+ process.versions.bun === undefined
158
+ ? createNodeDatabase(databasePath)
159
+ : createBunDatabase(databasePath)
160
+
161
+ const migrate = (db: SqliteDatabase) => {
162
+ db.exec(`
163
+ PRAGMA journal_mode = WAL;
164
+ PRAGMA foreign_keys = ON;
165
+
166
+ CREATE TABLE IF NOT EXISTS workflows (
167
+ id TEXT NOT NULL,
168
+ name TEXT NOT NULL,
169
+ version TEXT NOT NULL,
170
+ source TEXT NOT NULL DEFAULT '',
171
+ entrypoint TEXT NOT NULL DEFAULT '',
172
+ export_name TEXT,
173
+ created_at TEXT NOT NULL,
174
+ PRIMARY KEY (name, version)
175
+ );
176
+
177
+ CREATE TABLE IF NOT EXISTS workflow_runs (
178
+ id TEXT PRIMARY KEY,
179
+ workflow_id TEXT NOT NULL,
180
+ workflow_version TEXT NOT NULL,
181
+ status TEXT NOT NULL CHECK (status IN ('running', 'completed', 'failed')),
182
+ input_json TEXT NOT NULL,
183
+ result_json TEXT,
184
+ error_json TEXT,
185
+ started_at TEXT NOT NULL,
186
+ finished_at TEXT
187
+ );
188
+
189
+ CREATE TABLE IF NOT EXISTS workflow_run_events (
190
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
191
+ run_id TEXT NOT NULL,
192
+ sequence INTEGER NOT NULL,
193
+ type TEXT NOT NULL,
194
+ event_json TEXT NOT NULL,
195
+ created_at TEXT NOT NULL,
196
+ FOREIGN KEY (run_id) REFERENCES workflow_runs(id),
197
+ UNIQUE (run_id, sequence)
198
+ );
199
+
200
+ CREATE INDEX IF NOT EXISTS workflow_runs_workflow_id_idx
201
+ ON workflow_runs(workflow_id);
202
+
203
+ CREATE INDEX IF NOT EXISTS workflow_run_events_run_id_idx
204
+ ON workflow_run_events(run_id, sequence);
205
+ `)
206
+
207
+ const columns = db.prepare<{ name: string }>("PRAGMA table_info(workflows)").all()
208
+ const columnNames = new Set(columns.map((column) => column.name))
209
+ if (!columnNames.has("source")) {
210
+ db.prepare("ALTER TABLE workflows ADD COLUMN source TEXT NOT NULL DEFAULT ''").run()
211
+ }
212
+
213
+ db.prepare("CREATE UNIQUE INDEX IF NOT EXISTS workflows_name_version_idx ON workflows(name, version)").run()
214
+ }
215
+
216
+ export const createSqliteWorkflowRepository = (
217
+ options: SqliteWorkflowRepositoryOptions = {}
218
+ ): WorkflowRepository => {
219
+ const rootDir = path.resolve(options.rootDir ?? process.cwd())
220
+ const databasePath = path.resolve(options.databasePath ?? defaultDatabasePath(rootDir))
221
+ mkdirSync(path.dirname(databasePath), { recursive: true })
222
+
223
+ const dbPromise = createDatabase(databasePath).then((db) => {
224
+ migrate(db)
225
+ return db
226
+ })
227
+
228
+ return {
229
+ async upsertWorkflow(workflow) {
230
+ const db = await dbPromise
231
+ const existing = db.prepare<WorkflowRow>(`
232
+ SELECT id, name, version, source, entrypoint, export_name, created_at
233
+ FROM workflows
234
+ WHERE name = ?
235
+ AND version = ?
236
+ `).get(workflow.name, workflow.version)
237
+
238
+ if (existing !== null) {
239
+ const existingSource = existing.source !== null && existing.source.length > 0
240
+ ? existing.source
241
+ : legacySourceFromEntrypoint(rootDir, existing.entrypoint)
242
+ if (existingSource !== workflow.source) {
243
+ throw new Error(
244
+ `Workflow ${workflow.name}@v${workflow.version} is already cataloged with different source; register a new version instead`
245
+ )
246
+ }
247
+ return
248
+ }
249
+
250
+ db.prepare<unknown>(`
251
+ INSERT INTO workflows (id, name, version, source, entrypoint, export_name, created_at)
252
+ VALUES (?, ?, ?, ?, ?, ?, ?)
253
+ `).run(
254
+ workflow.id,
255
+ workflow.name,
256
+ workflow.version,
257
+ workflow.source,
258
+ "",
259
+ workflow.exportName ?? null,
260
+ workflow.createdAt ?? nowIso()
261
+ )
262
+ },
263
+
264
+ async deleteWorkflow(id) {
265
+ const db = await dbPromise
266
+ db.prepare<unknown>("DELETE FROM workflows WHERE id = ?").run(id)
267
+ },
268
+
269
+ async list() {
270
+ const db = await dbPromise
271
+ const rows = db.prepare<WorkflowRow>(`
272
+ SELECT id, name, version, source, entrypoint, export_name, created_at
273
+ FROM workflows
274
+ ORDER BY id
275
+ `).all()
276
+ return rows.map((row) => toArtifact(rootDir, row))
277
+ },
278
+
279
+ async get(id) {
280
+ const db = await dbPromise
281
+ const row = db.prepare<WorkflowRow>(`
282
+ SELECT id, name, version, source, entrypoint, export_name, created_at
283
+ FROM workflows
284
+ WHERE id = ?
285
+ `).get(id)
286
+ return row === null ? undefined : toArtifact(rootDir, row)
287
+ },
288
+
289
+ async getRun(id) {
290
+ const db = await dbPromise
291
+ const row = db.prepare<WorkflowRunRow>(`
292
+ SELECT *
293
+ FROM workflow_runs
294
+ WHERE id = ?
295
+ `).get(id)
296
+ return row === null ? undefined : toRunRecord(row)
297
+ },
298
+
299
+ async startRun({ id, workflow, input }) {
300
+ const db = await dbPromise
301
+ const startedAt = nowIso()
302
+ db.prepare<unknown>(`
303
+ INSERT INTO workflow_runs (
304
+ id,
305
+ workflow_id,
306
+ workflow_version,
307
+ status,
308
+ input_json,
309
+ started_at
310
+ )
311
+ VALUES (?, ?, ?, 'running', ?, ?)
312
+ ON CONFLICT(id) DO UPDATE SET
313
+ status = 'running',
314
+ input_json = excluded.input_json,
315
+ result_json = NULL,
316
+ error_json = NULL,
317
+ started_at = excluded.started_at,
318
+ finished_at = NULL
319
+ `).run(id, workflow.id, workflow.version, toJsonText(input), startedAt)
320
+
321
+ const row = db.prepare<WorkflowRunRow>(`
322
+ SELECT *
323
+ FROM workflow_runs
324
+ WHERE id = ?
325
+ `).get(id)
326
+
327
+ if (row === null) {
328
+ throw new Error(`Failed to create workflow run ${id}`)
329
+ }
330
+
331
+ return toRunRecord(row)
332
+ },
333
+
334
+ async appendRunEvent({ runId, type, event }) {
335
+ const db = await dbPromise
336
+ const nextSequence = db.prepare<{ sequence: number }>(`
337
+ SELECT COALESCE(MAX(sequence), 0) + 1 AS sequence
338
+ FROM workflow_run_events
339
+ WHERE run_id = ?
340
+ `).get(runId)?.sequence ?? 1
341
+
342
+ db.prepare<unknown>(`
343
+ INSERT INTO workflow_run_events (run_id, sequence, type, event_json, created_at)
344
+ VALUES (?, ?, ?, ?, ?)
345
+ `).run(runId, nextSequence, type, toJsonText(event), nowIso())
346
+ },
347
+
348
+ async completeRun({ runId, result }) {
349
+ const db = await dbPromise
350
+ db.prepare<unknown>(`
351
+ UPDATE workflow_runs
352
+ SET status = 'completed',
353
+ result_json = ?,
354
+ error_json = NULL,
355
+ finished_at = ?
356
+ WHERE id = ?
357
+ `).run(toJsonText(result), nowIso(), runId)
358
+ },
359
+
360
+ async failRun({ runId, error }) {
361
+ const db = await dbPromise
362
+ db.prepare<unknown>(`
363
+ UPDATE workflow_runs
364
+ SET status = 'failed',
365
+ error_json = ?,
366
+ finished_at = ?
367
+ WHERE id = ?
368
+ `).run(toJsonText(error), nowIso(), runId)
369
+ },
370
+
371
+ async listRuns() {
372
+ const db = await dbPromise
373
+ const rows = db.prepare<WorkflowRunRow>(`
374
+ SELECT *
375
+ FROM workflow_runs
376
+ ORDER BY started_at DESC
377
+ `).all()
378
+ return rows.map(toRunRecord)
379
+ },
380
+
381
+ async listRunEvents(runId) {
382
+ const db = await dbPromise
383
+ const rows = db.prepare<WorkflowRunEventRow>(`
384
+ SELECT *
385
+ FROM workflow_run_events
386
+ WHERE run_id = ?
387
+ ORDER BY sequence
388
+ `).all(runId)
389
+ return rows.map(toRunEventRecord)
390
+ }
391
+ }
392
+ }
393
+
394
+ export const seedSqliteWorkflowRepository = async (
395
+ repository: WorkflowRepository,
396
+ seedStore: WorkflowStore
397
+ ) => {
398
+ if ((await repository.list()).length > 0) {
399
+ return
400
+ }
401
+
402
+ for (const workflow of await seedStore.list()) {
403
+ await repository.upsertWorkflow(workflow)
404
+ }
405
+ }
package/src/signal.ts ADDED
@@ -0,0 +1,116 @@
1
+ import { Schema } from "effect"
2
+
3
+ type AnySchema<A = any> = Schema.Codec<A, any, any, any>
4
+
5
+ export class SignalDeliveryError extends Error {
6
+ readonly _tag = "SignalDeliveryError"
7
+
8
+ constructor(message: string, options?: { readonly cause?: unknown }) {
9
+ super(message)
10
+ this.name = "SignalDeliveryError"
11
+ this.cause = options?.cause
12
+ }
13
+ }
14
+
15
+ interface SignalWaiter {
16
+ readonly schema: AnySchema
17
+ readonly resolve: (value: unknown) => void
18
+ readonly reject: (error: unknown) => void
19
+ }
20
+
21
+ const schemas = new Map<string, AnySchema>()
22
+ const buffers = new Map<string, unknown[]>()
23
+ const waiters = new Map<string, SignalWaiter[]>()
24
+
25
+ const keyOf = (executionId: string, name: string): string => `${executionId}\0${name}`
26
+
27
+ export const decodeSignal = <T>(schema: AnySchema<T>, value: unknown): T => {
28
+ try {
29
+ return Schema.decodeUnknownSync(schema as any)(value) as T
30
+ } catch (cause) {
31
+ throw new SignalDeliveryError("Signal payload failed schema validation", { cause })
32
+ }
33
+ }
34
+
35
+ export const getSignalSchema = (executionId: string, name: string): AnySchema | undefined =>
36
+ schemas.get(keyOf(executionId, name))
37
+
38
+ export const registerSignalSchema = <T>(
39
+ executionId: string,
40
+ name: string,
41
+ schema: AnySchema<T>
42
+ ) => {
43
+ schemas.set(keyOf(executionId, name), schema)
44
+ }
45
+
46
+ export const deliverSignal = async (
47
+ executionId: string,
48
+ name: string,
49
+ payload: unknown
50
+ ): Promise<void> => {
51
+ const key = keyOf(executionId, name)
52
+ const queuedWaiters = waiters.get(key)
53
+ const waiter = queuedWaiters?.[0]
54
+
55
+ if (waiter !== undefined) {
56
+ const decoded = decodeSignal(waiter.schema, payload)
57
+ queuedWaiters!.shift()
58
+ waiter.resolve(decoded)
59
+ return
60
+ }
61
+
62
+ const schema = schemas.get(key)
63
+ const value = schema === undefined ? payload : decodeSignal(schema, payload)
64
+ const queue = buffers.get(key) ?? []
65
+ queue.push(value)
66
+ buffers.set(key, queue)
67
+ }
68
+
69
+ export const takeBufferedSignal = <T>(
70
+ executionId: string,
71
+ name: string,
72
+ schema: AnySchema<T>
73
+ ): T | undefined => {
74
+ registerSignalSchema(executionId, name, schema)
75
+ const key = keyOf(executionId, name)
76
+ const queue = buffers.get(key)
77
+ if (queue === undefined || queue.length === 0) {
78
+ return undefined
79
+ }
80
+
81
+ const value = queue.shift()
82
+ if (queue.length === 0) {
83
+ buffers.delete(key)
84
+ }
85
+ return decodeSignal(schema, value)
86
+ }
87
+
88
+ export const awaitSignal = <T>(
89
+ executionId: string,
90
+ name: string,
91
+ schema: AnySchema<T>
92
+ ): Promise<T> => {
93
+ const buffered = takeBufferedSignal(executionId, name, schema)
94
+ if (buffered !== undefined) {
95
+ return Promise.resolve(buffered)
96
+ }
97
+
98
+ const key = keyOf(executionId, name)
99
+ return new Promise((resolve, reject) => {
100
+ const queue = waiters.get(key) ?? []
101
+ queue.push({ schema, resolve: resolve as (value: unknown) => void, reject })
102
+ waiters.set(key, queue)
103
+ })
104
+ }
105
+
106
+ export const cancelSignalWaits = (executionId: string, error: unknown) => {
107
+ for (const [key, queuedWaiters] of waiters) {
108
+ if (!key.startsWith(`${executionId}\0`)) {
109
+ continue
110
+ }
111
+ waiters.delete(key)
112
+ for (const waiter of queuedWaiters) {
113
+ waiter.reject(error)
114
+ }
115
+ }
116
+ }