@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,486 @@
1
+ import type {
2
+ DefinedWorkflow,
3
+ InMemoryDeterminismState,
4
+ OrchestrationCall,
5
+ SecretResolver,
6
+ Step
7
+ } from "../core.ts"
8
+ import { Schema } from "effect"
9
+ import { createInMemoryDeterminismState } from "../core.ts"
10
+ import type { WorkflowEvent } from "../events.ts"
11
+ import {
12
+ jsonSchemaOf,
13
+ type JsonSchema,
14
+ type WorkflowArtifactGraph,
15
+ type WorkflowGraph,
16
+ type WorkflowGraphEdge,
17
+ type WorkflowGraphNode,
18
+ type WorkflowGraphNodeKind,
19
+ type WorkflowGraphNodeMetadata,
20
+ type WorkflowGraphNodeSchemas,
21
+ type WorkflowGraphSchemas
22
+ } from "../schemas.ts"
23
+ import type { WorkflowArtifact } from "./artifact.ts"
24
+ import { loadWorkflowArtifact } from "./loader.ts"
25
+
26
+ interface SchemaAst {
27
+ readonly _tag?: string
28
+ readonly propertySignatures?: ReadonlyArray<{
29
+ readonly name: PropertyKey
30
+ readonly type?: SchemaAst
31
+ }>
32
+ readonly elements?: ReadonlyArray<SchemaAst>
33
+ readonly rest?: ReadonlyArray<SchemaAst>
34
+ readonly types?: ReadonlyArray<SchemaAst>
35
+ readonly literal?: unknown
36
+ }
37
+
38
+ export type {
39
+ WorkflowArtifactGraph,
40
+ WorkflowGraph,
41
+ WorkflowGraphEdge,
42
+ WorkflowGraphNode,
43
+ WorkflowGraphNodeKind,
44
+ WorkflowGraphNodeMetadata,
45
+ WorkflowGraphNodeSchemas,
46
+ WorkflowGraphSchemas
47
+ }
48
+
49
+ export interface WorkflowGraphOptions<I = unknown> {
50
+ readonly input?: I
51
+ readonly maxNodes?: number
52
+ }
53
+
54
+ const emptySecretResolver: SecretResolver = {
55
+ resolve: (name) => `__secret:${name}__`
56
+ }
57
+
58
+ const nodeId = (kind: WorkflowGraphNodeKind, name: string, invocation: number): string =>
59
+ `${kind}:${name}:${invocation}`
60
+
61
+ const callKey = (call: OrchestrationCall): string =>
62
+ `${call.kind}:${call.name}:${call.counter}`
63
+
64
+ const jsonSchemaFor = jsonSchemaOf
65
+
66
+ const objectWithOptionalSchemas = (schemas: WorkflowGraphNodeSchemas): WorkflowGraphNodeSchemas | undefined =>
67
+ schemas.input === undefined &&
68
+ schemas.output === undefined &&
69
+ schemas.errors === undefined &&
70
+ schemas.signal === undefined
71
+ ? undefined
72
+ : schemas
73
+
74
+ const workflowSchemas = (workflow: DefinedWorkflow): WorkflowGraphSchemas | undefined => {
75
+ const input = jsonSchemaFor(workflow.input)
76
+ const output = jsonSchemaFor(workflow.output)
77
+ const errors = jsonSchemaFor(workflow.errors)
78
+ return objectWithOptionalSchemas({
79
+ ...(input === undefined ? {} : { input }),
80
+ ...(output === undefined ? {} : { output }),
81
+ ...(errors === undefined ? {} : { errors })
82
+ })
83
+ }
84
+
85
+ const stepSchemas = (step: Step<any, any, any>): WorkflowGraphNodeSchemas | undefined => {
86
+ const input = jsonSchemaFor(step.input)
87
+ const output = jsonSchemaFor(step.output)
88
+ const errors = jsonSchemaFor(step.errors)
89
+ return objectWithOptionalSchemas({
90
+ ...(input === undefined ? {} : { input }),
91
+ ...(output === undefined ? {} : { output }),
92
+ ...(errors === undefined ? {} : { errors })
93
+ })
94
+ }
95
+
96
+ const describeStep = (step: Step<any, any, any>): WorkflowGraphNodeMetadata => ({
97
+ ...(step.retry === undefined ? {} : { retry: step.retry }),
98
+ ...(step.concurrency === undefined
99
+ ? {}
100
+ : {
101
+ concurrency: {
102
+ limit: step.concurrency.limit,
103
+ keyed: step.concurrency.key !== undefined
104
+ }
105
+ }),
106
+ compensates: step.compensate !== undefined
107
+ })
108
+
109
+ const schemaAst = (schema: unknown): SchemaAst | undefined =>
110
+ Schema.isSchema(schema) ? schema.ast as SchemaAst : undefined
111
+
112
+ export const sampleValueForSchema = (schema: unknown): unknown =>
113
+ sampleValueFromAst(schemaAst(schema), new Set())
114
+
115
+ /** Sample value for a JSON Schema document (e.g. PendingSignal.payloadSchema),
116
+ * mirroring the placeholders of sampleValueForSchema. */
117
+ export const sampleValueForJsonSchema = (schema: JsonSchema, depth = 0): unknown => {
118
+ if (depth > 8) {
119
+ return {}
120
+ }
121
+ if (schema.const !== undefined) {
122
+ return schema.const
123
+ }
124
+ if (schema.enum !== undefined && schema.enum.length > 0) {
125
+ return schema.enum[0]
126
+ }
127
+ const alternative = schema.anyOf?.[0] ?? schema.oneOf?.[0]
128
+ if (alternative !== undefined) {
129
+ return sampleValueForJsonSchema(alternative, depth + 1)
130
+ }
131
+ const type = Array.isArray(schema.type) ? schema.type[0] : schema.type
132
+ switch (type) {
133
+ case "string":
134
+ return "sample"
135
+ case "number":
136
+ case "integer":
137
+ return 1
138
+ case "boolean":
139
+ return true
140
+ case "null":
141
+ return null
142
+ case "array":
143
+ return schema.items === undefined ? [] : [sampleValueForJsonSchema(schema.items, depth + 1)]
144
+ case "object": {
145
+ const properties = schema.properties ?? {}
146
+ const required = schema.required
147
+ const entries = Object.entries(properties)
148
+ .filter(([key]) => required === undefined || required.includes(key))
149
+ .map(([key, property]) => [key, sampleValueForJsonSchema(property, depth + 1)])
150
+ return Object.fromEntries(entries)
151
+ }
152
+ default:
153
+ return {}
154
+ }
155
+ }
156
+
157
+ // `seen` holds the current recursion path only: schema AST nodes are shared
158
+ // (t.string is a singleton), so a persistent visited-set would misread the
159
+ // second reference to a node as a cycle.
160
+ const sampleValueFromAst = (ast: SchemaAst | undefined, seen: Set<SchemaAst>): unknown => {
161
+ if (ast === undefined || seen.has(ast)) {
162
+ return {}
163
+ }
164
+ seen.add(ast)
165
+ try {
166
+ return sampleValueFromAstUnguarded(ast, seen)
167
+ } finally {
168
+ seen.delete(ast)
169
+ }
170
+ }
171
+
172
+ const sampleValueFromAstUnguarded = (ast: SchemaAst, seen: Set<SchemaAst>): unknown => {
173
+ switch (ast._tag) {
174
+ case "String":
175
+ return "sample"
176
+ case "Number":
177
+ return 1
178
+ case "Boolean":
179
+ return true
180
+ case "Void":
181
+ case "Undefined":
182
+ return undefined
183
+ case "Null":
184
+ return null
185
+ case "Date":
186
+ return new Date(0)
187
+ case "Literal":
188
+ return ast.literal
189
+ case "Arrays": {
190
+ const element = ast.rest?.[0] ?? ast.elements?.[0]
191
+ return [sampleValueFromAst(element, seen)]
192
+ }
193
+ case "Union": {
194
+ const candidate = ast.types?.find((item) => item._tag !== "Undefined") ?? ast.types?.[0]
195
+ return sampleValueFromAst(candidate, seen)
196
+ }
197
+ case "Objects": {
198
+ const entries = ast.propertySignatures?.map((property) => [
199
+ property.name,
200
+ sampleValueFromAst(property.type, seen)
201
+ ]) ?? []
202
+ return Object.fromEntries(entries)
203
+ }
204
+ case "Unknown":
205
+ case "Any":
206
+ return {}
207
+ case "Never":
208
+ throw new Error("Cannot create a sample value for Schema.Never")
209
+ default:
210
+ return {}
211
+ }
212
+ }
213
+
214
+ const metadataFromEvents = (events: ReadonlyArray<WorkflowEvent>) => {
215
+ const metadata = new Map<string, WorkflowGraphNodeMetadata>()
216
+
217
+ for (const event of events) {
218
+ switch (event.type) {
219
+ case "step.started":
220
+ metadata.set(nodeId("step", event.stepName, event.invocation), {
221
+ input: event.input,
222
+ activityName: event.activityName
223
+ })
224
+ break
225
+ case "sleep.started":
226
+ metadata.set(nodeId("sleep", event.name, event.invocation), {
227
+ duration: event.duration,
228
+ activityName: event.activityName
229
+ })
230
+ break
231
+ case "signal.waiting":
232
+ metadata.set(nodeId("signal", event.name, event.invocation), {
233
+ timeout: event.timeout,
234
+ activityName: event.activityName
235
+ })
236
+ break
237
+ case "code.started":
238
+ metadata.set(nodeId("code", event.name, event.invocation), {
239
+ activityName: event.activityName,
240
+ ...(event.reason === undefined ? {} : { reason: event.reason })
241
+ })
242
+ break
243
+ case "all.started":
244
+ metadata.set(nodeId("all", event.name, event.invocation), {
245
+ activityName: event.activityName,
246
+ branches: event.branches
247
+ })
248
+ break
249
+ }
250
+ }
251
+
252
+ return metadata
253
+ }
254
+
255
+ const graphNodeForCall = (
256
+ call: OrchestrationCall,
257
+ options: {
258
+ readonly eventMetadata: ReadonlyMap<string, WorkflowGraphNodeMetadata>
259
+ readonly steps: ReadonlyMap<string, WorkflowGraphNodeMetadata>
260
+ readonly schemas: ReadonlyMap<string, WorkflowGraphNodeSchemas>
261
+ readonly nameCounts: ReadonlyMap<string, number>
262
+ }
263
+ ): WorkflowGraphNode => {
264
+ const id = nodeId(call.kind, call.name, call.counter)
265
+ const schemas = options.schemas.get(id)
266
+ const metadata: WorkflowGraphNodeMetadata = {
267
+ ...options.eventMetadata.get(id),
268
+ ...options.steps.get(id),
269
+ ...(call.branches === undefined ? {} : { branches: call.branches })
270
+ }
271
+ const description = typeof metadata.reason === "string"
272
+ ? metadata.reason
273
+ : call.counter > 1
274
+ ? `Invocation ${call.counter}`
275
+ : undefined
276
+ return {
277
+ id,
278
+ kind: call.kind,
279
+ label: call.name,
280
+ invocation: call.counter,
281
+ repeated: (options.nameCounts.get(`${call.kind}:${call.name}`) ?? 0) > 1,
282
+ ...(description === undefined ? {} : { description }),
283
+ ...(schemas === undefined ? {} : { schemas }),
284
+ metadata
285
+ }
286
+ }
287
+
288
+ const graphFromTrace = (options: {
289
+ readonly workflow: DefinedWorkflow
290
+ readonly determinism: InMemoryDeterminismState
291
+ readonly events: ReadonlyArray<WorkflowEvent>
292
+ readonly steps: ReadonlyMap<string, WorkflowGraphNodeMetadata>
293
+ readonly schemas: ReadonlyMap<string, WorkflowGraphNodeSchemas>
294
+ readonly diagnostics: ReadonlyArray<string>
295
+ readonly maxNodes: number
296
+ }): WorkflowGraph => {
297
+ const eventMetadata = metadataFromEvents(options.events)
298
+ const nameCounts = new Map<string, number>()
299
+ for (const call of options.determinism.calls) {
300
+ nameCounts.set(`${call.kind}:${call.name}`, (nameCounts.get(`${call.kind}:${call.name}`) ?? 0) + 1)
301
+ }
302
+
303
+ const calls = options.determinism.calls.slice(0, options.maxNodes)
304
+ const callKeys = new Set(calls.map(callKey))
305
+ const truncated = calls.length < options.determinism.calls.length
306
+ const blocks = options.determinism.blocks
307
+ .filter((block) => callKeys.has(callKey(block.call)))
308
+ .map((block) => ({
309
+ call: block.call,
310
+ branches: block.branches.map((branch) => branch.filter((call) => callKeys.has(callKey(call))))
311
+ }))
312
+ const blockByCall = new Map(blocks.map((block) => [callKey(block.call), block]))
313
+ const branchCallKeys = new Set<string>()
314
+ for (const block of blocks) {
315
+ for (const branch of block.branches) {
316
+ for (const call of branch) {
317
+ branchCallKeys.add(callKey(call))
318
+ }
319
+ }
320
+ }
321
+ const mainCalls = calls.filter((call) => !branchCallKeys.has(callKey(call)))
322
+ const start: WorkflowGraphNode = {
323
+ id: "start",
324
+ kind: "start",
325
+ label: "Start",
326
+ repeated: false,
327
+ metadata: {}
328
+ }
329
+ const end: WorkflowGraphNode = {
330
+ id: "end",
331
+ kind: "end",
332
+ label: truncated ? "Trace truncated" : "End",
333
+ repeated: false,
334
+ metadata: {}
335
+ }
336
+ const nodes: WorkflowGraphNode[] = [start]
337
+ const edges: WorkflowGraphEdge[] = []
338
+ let previousTails: WorkflowGraphNode[] = [start]
339
+ const addEdge = (source: WorkflowGraphNode, target: WorkflowGraphNode, label?: string) => {
340
+ edges.push({
341
+ id: `${source.id}->${target.id}${label === undefined ? "" : `:${label}`}`,
342
+ source: source.id,
343
+ target: target.id,
344
+ ...(label === undefined ? (target.repeated ? { label: "repeat" } : {}) : { label })
345
+ })
346
+ }
347
+ const appendAfterPrevious = (node: WorkflowGraphNode, label?: string) => {
348
+ for (const tail of previousTails) {
349
+ addEdge(tail, node, label)
350
+ }
351
+ }
352
+
353
+ for (const call of mainCalls) {
354
+ const node = graphNodeForCall(call, {
355
+ eventMetadata,
356
+ steps: options.steps,
357
+ schemas: options.schemas,
358
+ nameCounts
359
+ })
360
+ nodes.push(node)
361
+ appendAfterPrevious(node)
362
+
363
+ const block = blockByCall.get(callKey(call))
364
+ if (block === undefined) {
365
+ previousTails = [node]
366
+ continue
367
+ }
368
+
369
+ const branchTails: WorkflowGraphNode[] = []
370
+ block.branches.forEach((branch, branchIndex) => {
371
+ let branchPrevious = node
372
+ if (branch.length === 0) {
373
+ branchTails.push(node)
374
+ return
375
+ }
376
+ branch.forEach((branchCall, callIndex) => {
377
+ const branchNode = graphNodeForCall(branchCall, {
378
+ eventMetadata,
379
+ steps: options.steps,
380
+ schemas: options.schemas,
381
+ nameCounts
382
+ })
383
+ nodes.push(branchNode)
384
+ addEdge(branchPrevious, branchNode, callIndex === 0 ? `branch ${branchIndex + 1}` : undefined)
385
+ branchPrevious = branchNode
386
+ })
387
+ branchTails.push(branchPrevious)
388
+ })
389
+ previousTails = branchTails.length === 0 ? [node] : branchTails
390
+ }
391
+ nodes.push(end)
392
+ for (const tail of previousTails) {
393
+ addEdge(tail, end)
394
+ }
395
+ const schemas = workflowSchemas(options.workflow)
396
+
397
+ return {
398
+ workflowName: options.workflow.name,
399
+ version: options.workflow.version,
400
+ engineName: options.workflow.engineName,
401
+ sourceHash: options.workflow.sourceHash,
402
+ ...(schemas === undefined ? {} : { schemas }),
403
+ nodes,
404
+ edges,
405
+ calls,
406
+ diagnostics: truncated
407
+ ? [...options.diagnostics, `Trace stopped after ${options.maxNodes} nodes.`]
408
+ : options.diagnostics
409
+ }
410
+ }
411
+
412
+ export const workflowToGraph = async <I, O, E>(
413
+ workflow: DefinedWorkflow<I, O, E>,
414
+ options: WorkflowGraphOptions<I> = {}
415
+ ): Promise<WorkflowGraph> => {
416
+ const determinism = createInMemoryDeterminismState()
417
+ const events: WorkflowEvent[] = []
418
+ const steps = new Map<string, WorkflowGraphNodeMetadata>()
419
+ const schemas = new Map<string, WorkflowGraphNodeSchemas>()
420
+ const signalCounts = new Map<string, number>()
421
+ const diagnostics: string[] = []
422
+ const input = options.input ?? sampleValueForSchema(workflow.input) as I
423
+
424
+ try {
425
+ await workflow.executeInMemory(input, {
426
+ executionId: `graph-${workflow.name}-${workflow.version}`,
427
+ determinism,
428
+ onEvent: (event) => {
429
+ events.push(event)
430
+ },
431
+ stepExecutor: ({ step, invocation }) => {
432
+ const id = nodeId("step", step.name, invocation)
433
+ steps.set(id, describeStep(step))
434
+ const stepNodeSchemas = stepSchemas(step)
435
+ if (stepNodeSchemas !== undefined) {
436
+ schemas.set(id, stepNodeSchemas)
437
+ }
438
+ return sampleValueForSchema(step.output)
439
+ },
440
+ sleep: async () => undefined,
441
+ signalTimeout: async () => undefined,
442
+ signalValue: ({ name, schema }) => {
443
+ const invocation = (signalCounts.get(name) ?? 0) + 1
444
+ signalCounts.set(name, invocation)
445
+ const signalSchema = jsonSchemaFor(schema)
446
+ if (signalSchema !== undefined) {
447
+ schemas.set(nodeId("signal", name, invocation), { signal: signalSchema })
448
+ }
449
+ return sampleValueForSchema(schema)
450
+ },
451
+ secrets: emptySecretResolver
452
+ })
453
+ } catch (error) {
454
+ diagnostics.push(error instanceof Error ? error.message : String(error))
455
+ }
456
+
457
+ return graphFromTrace({
458
+ workflow,
459
+ determinism,
460
+ events,
461
+ steps,
462
+ schemas,
463
+ diagnostics,
464
+ maxNodes: options.maxNodes ?? 100
465
+ })
466
+ }
467
+
468
+ export const workflowArtifactToGraph = async (
469
+ artifact: WorkflowArtifact,
470
+ options: WorkflowGraphOptions = {}
471
+ ): Promise<WorkflowArtifactGraph> => {
472
+ try {
473
+ const loaded = await loadWorkflowArtifact(artifact)
474
+ return {
475
+ artifact,
476
+ exportName: loaded.exportName,
477
+ graph: await workflowToGraph(loaded.workflow, options),
478
+ diagnostics: []
479
+ }
480
+ } catch (error) {
481
+ return {
482
+ artifact,
483
+ diagnostics: [error instanceof Error ? error.message : String(error)]
484
+ }
485
+ }
486
+ }
@@ -0,0 +1,64 @@
1
+ export {
2
+ createFileWorkflowStore,
3
+ createMemoryWorkflowStore
4
+ } from "./artifact.ts"
5
+ export type {
6
+ FileWorkflowStoreOptions,
7
+ WorkflowArtifact,
8
+ WorkflowRepository,
9
+ WorkflowRunEventRecord,
10
+ WorkflowRunRecord,
11
+ WorkflowRunStatus,
12
+ WorkflowRunStore,
13
+ WorkflowStore
14
+ } from "./artifact.ts"
15
+ export {
16
+ createSqliteWorkflowRepository,
17
+ seedSqliteWorkflowRepository
18
+ } from "./sqlite.ts"
19
+ export type { SqliteWorkflowRepositoryOptions } from "./sqlite.ts"
20
+ export {
21
+ isDefinedWorkflow,
22
+ loadWorkflowArtifact
23
+ } from "./loader.ts"
24
+ export type { LoadedWorkflow } from "./loader.ts"
25
+ export {
26
+ sampleValueForJsonSchema,
27
+ sampleValueForSchema,
28
+ workflowArtifactToGraph,
29
+ workflowToGraph
30
+ } from "./graph.ts"
31
+ export type {
32
+ WorkflowArtifactGraph,
33
+ WorkflowGraph,
34
+ WorkflowGraphEdge,
35
+ WorkflowGraphNode,
36
+ WorkflowGraphNodeKind,
37
+ WorkflowGraphNodeMetadata,
38
+ WorkflowGraphNodeSchemas,
39
+ WorkflowGraphSchemas,
40
+ WorkflowGraphOptions
41
+ } from "./graph.ts"
42
+ export { parseJsonText, toJsonText } from "./json.ts"
43
+ export { discover } from "./integrations.ts"
44
+ export type {
45
+ DiscoverIntegrationsOptions,
46
+ DiscoverIntegrationsResult,
47
+ IntegrationKind,
48
+ IntegrationSearchResult
49
+ } from "./integrations.ts"
50
+ export { Cancelled, MissingWorkflowVersionError, createWorkflowClient, createWorkflowSdk } from "./sdk.ts"
51
+ export type {
52
+ WorkflowClient,
53
+ WorkflowExecutionHandle,
54
+ WorkflowExecutionStatus,
55
+ WorkflowHistoryEvent,
56
+ WorkflowHistoryRecord,
57
+ WorkflowListResult,
58
+ PendingSignal,
59
+ WorkflowResult,
60
+ RunWorkflowOptions,
61
+ WorkflowRunResult,
62
+ WorkflowSdk,
63
+ WorkflowSdkOptions
64
+ } from "./sdk.ts"
@@ -0,0 +1,49 @@
1
+ export type IntegrationKind = "mcp" | "openapi" | "graphql" | "cli"
2
+
3
+ export interface DiscoverIntegrationsOptions {
4
+ readonly kind?: IntegrationKind
5
+ readonly limit?: number
6
+ readonly baseUrl?: string
7
+ }
8
+
9
+ export interface IntegrationSearchResult {
10
+ readonly domain: string
11
+ readonly name: string
12
+ readonly description: string
13
+ readonly kinds: ReadonlyArray<IntegrationKind>
14
+ readonly url: string
15
+ }
16
+
17
+ export interface DiscoverIntegrationsResult {
18
+ readonly results: ReadonlyArray<IntegrationSearchResult>
19
+ }
20
+
21
+ export const discover = async (
22
+ searchTerm: string,
23
+ options: DiscoverIntegrationsOptions = {}
24
+ ): Promise<DiscoverIntegrationsResult> => {
25
+ const query = searchTerm.trim()
26
+
27
+ if (query.length === 0) {
28
+ throw new Error("discover requires a non-empty search term")
29
+ }
30
+
31
+ const url = new URL("/api/search", options.baseUrl ?? "https://integrations.sh")
32
+ url.searchParams.set("q", query)
33
+
34
+ if (options.kind !== undefined) {
35
+ url.searchParams.set("kind", options.kind)
36
+ }
37
+
38
+ if (options.limit !== undefined) {
39
+ url.searchParams.set("limit", String(options.limit))
40
+ }
41
+
42
+ const response = await fetch(url)
43
+
44
+ if (!response.ok) {
45
+ throw new Error(`integrations.sh discover failed: ${response.status} ${response.statusText}`)
46
+ }
47
+
48
+ return await response.json() as DiscoverIntegrationsResult
49
+ }
@@ -0,0 +1,40 @@
1
+ export const stableStringify = (value: unknown): string =>
2
+ JSON.stringify(normalizeJson(value))
3
+
4
+ const normalizeJson = (value: unknown): unknown => {
5
+ if (Array.isArray(value)) {
6
+ return value.map(normalizeJson)
7
+ }
8
+
9
+ if (typeof value !== "object" || value === null) {
10
+ return value
11
+ }
12
+
13
+ const input = value as Record<string, unknown>
14
+ const output: Record<string, unknown> = {}
15
+
16
+ for (const key of Object.keys(input).sort()) {
17
+ output[key] = normalizeJson(input[key])
18
+ }
19
+
20
+ return output
21
+ }
22
+
23
+ // Error has no enumerable own properties, so plain JSON.stringify turns a
24
+ // thrown Error into "{}" and history loses the failure message. Keep name,
25
+ // message, and any own enumerable fields (e.g. _tag on tagged errors).
26
+ const jsonReplacer = (_key: string, value: unknown): unknown =>
27
+ value instanceof Error
28
+ ? { ...value, name: value.name, message: value.message }
29
+ : value
30
+
31
+ export const toJsonText = (value: unknown): string => {
32
+ try {
33
+ return JSON.stringify(value, jsonReplacer) ?? JSON.stringify(String(value))
34
+ } catch {
35
+ return JSON.stringify(String(value))
36
+ }
37
+ }
38
+
39
+ export const parseJsonText = (value: string | null): unknown =>
40
+ value === null ? undefined : JSON.parse(value)