@workflow/world-testing 5.0.0-beta.6 → 5.0.0-beta.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.
@@ -32,10 +32,13 @@ const Invoke = z
32
32
  const flowInvocationCounts = new Map();
33
33
  const app = new Hono()
34
34
  .post('/.well-known/workflow/v1/flow', async (ctx) => {
35
- // Clone the request to read the body for tracking without consuming it
35
+ // Clone the request to read the body for tracking without consuming it.
36
+ // We must increment the invocation counter *before* awaiting flowPOST,
37
+ // otherwise the workflow may complete (and the test may observe the
38
+ // completed status) before the counter is bumped, producing a flaky
39
+ // `expected 0 to be 1` failure when the test immediately queries
40
+ // /_flow-invocations after seeing the run as completed.
36
41
  const cloned = ctx.req.raw.clone();
37
- const response = await flowPOST(ctx.req.raw);
38
- // Extract runId from the request body (queue message payload)
39
42
  try {
40
43
  const body = (await cloned.json());
41
44
  const runId = typeof body?.runId === 'string'
@@ -51,7 +54,7 @@ const app = new Hono()
51
54
  catch {
52
55
  // Health check or non-JSON messages — ignore
53
56
  }
54
- return response;
57
+ return flowPOST(ctx.req.raw);
55
58
  })
56
59
  .get('/_flow-invocations/:runId', (ctx) => {
57
60
  const count = flowInvocationCounts.get(ctx.req.param('runId')) ?? 0;
@@ -1 +1 @@
1
- {"version":3,"file":"server.mjs","sourceRoot":"","sources":["../../src/server.mts"],"names":[],"mappings":"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","sourcesContent":["import fs from 'node:fs';\nimport { serve } from '@hono/node-server';\nimport { Hono } from 'hono';\nimport { getHookByToken, getRun, resumeHook, start } from 'workflow/api';\nimport { getWorld } from 'workflow/runtime';\nimport * as z from 'zod';\nimport { POST as flowPOST } from '../.well-known/workflow/v1/flow.mjs';\nimport manifest from '../.well-known/workflow/v1/manifest.json' with {\n type: 'json',\n};\n\nif (!process.env.WORKFLOW_TARGET_WORLD) {\n console.error(\n 'Error: WORKFLOW_TARGET_WORLD environment variable is not set.'\n );\n process.exit(1);\n}\n\ntype Files = keyof typeof manifest.workflows;\ntype Workflows<F extends Files> = keyof (typeof manifest.workflows)[F];\ntype NonEmptyArray<T> = [T, ...T[]];\n\nconst Invoke = z\n .object({\n file: z.enum(Object.keys(manifest.workflows) as NonEmptyArray<Files>),\n workflow: z.string(),\n args: z.unknown().array().default([]),\n })\n .transform((obj) => {\n const file = obj.file as keyof typeof manifest.workflows;\n const workflow = z\n .enum(\n Object.keys(manifest.workflows[file]) as NonEmptyArray<\n Workflows<typeof file>\n >\n )\n .parse(obj.workflow);\n return {\n args: obj.args,\n workflow: manifest.workflows[file][workflow],\n };\n });\n\n// Track flow handler invocations per run for testing inline execution\nconst flowInvocationCounts = new Map<string, number>();\n\nconst app = new Hono()\n .post('/.well-known/workflow/v1/flow', async (ctx) => {\n // Clone the request to read the body for tracking without consuming it\n const cloned = ctx.req.raw.clone();\n const response = await flowPOST(ctx.req.raw);\n // Extract runId from the request body (queue message payload)\n try {\n const body = (await cloned.json()) as Record<string, unknown>;\n const runId =\n typeof body?.runId === 'string'\n ? body.runId\n : typeof (body.payload as Record<string, unknown> | undefined)\n ?.runId === 'string'\n ? ((body.payload as Record<string, unknown>).runId as string)\n : undefined;\n if (runId) {\n flowInvocationCounts.set(\n runId,\n (flowInvocationCounts.get(runId) ?? 0) + 1\n );\n }\n } catch {\n // Health check or non-JSON messages — ignore\n }\n return response;\n })\n .get('/_flow-invocations/:runId', (ctx) => {\n const count = flowInvocationCounts.get(ctx.req.param('runId')) ?? 0;\n return ctx.json({ count });\n })\n .get('/_manifest', (ctx) => ctx.json(manifest))\n .post('/invoke', async (ctx) => {\n const json = await ctx.req.json().then(Invoke.parse);\n const handler = await start(json.workflow, json.args);\n\n return ctx.json({ runId: handler.runId });\n })\n .post('/hooks/:token', async (ctx) => {\n const hook = await getHookByToken(ctx.req.param('token'));\n const { runId } = await resumeHook(hook.token, {\n ...(await ctx.req.json()),\n metadata: hook.metadata,\n });\n return ctx.json({ runId, hookId: hook.hookId });\n })\n .get('/runs/:runId', async (ctx) => {\n const world = await getWorld();\n const run = await world.runs.get(ctx.req.param('runId'));\n // Custom JSON serialization to handle Uint8Array as base64\n const json = JSON.stringify(run, (_key, value) => {\n if (value instanceof Uint8Array) {\n return {\n __type: 'Uint8Array',\n data: Buffer.from(value).toString('base64'),\n };\n }\n return value;\n });\n return new Response(json, {\n headers: { 'Content-Type': 'application/json' },\n });\n })\n .get('/runs/:runId/readable', async (ctx) => {\n const runId = ctx.req.param('runId');\n const run = getRun(runId);\n return new Response(run.getReadable());\n })\n .get('/runs/:runId/events', async (ctx) => {\n const runId = ctx.req.param('runId');\n const world = await getWorld();\n const allEvents: { eventType: string; correlationId?: string }[] = [];\n let cursor: string | undefined = undefined;\n while (true) {\n const page = await world.events.list({\n runId,\n pagination: { sortOrder: 'asc', cursor },\n });\n for (const e of page.data) {\n allEvents.push({\n eventType: e.eventType,\n correlationId: e.correlationId,\n });\n }\n if (!page.hasMore) break;\n cursor = page.cursor ?? undefined;\n if (!cursor) break;\n }\n return ctx.json({ events: allEvents });\n });\n\nserve(\n {\n fetch: app.fetch,\n port: Number(process.env.PORT) || 0,\n },\n async (info) => {\n console.log(`👂 listening on http://${info.address}:${info.port}`);\n console.log('');\n\n process.env.PORT = info.port.toString();\n\n for (const [filename, workflows] of Object.entries(manifest.workflows)) {\n for (const workflowName of Object.keys(\n workflows as Record<string, unknown>\n )) {\n console.log(\n `$ curl -X POST http://localhost:${info.port}/invoke -d '${JSON.stringify(\n {\n file: filename,\n workflow: workflowName,\n }\n )}'`\n );\n }\n }\n\n const world = await getWorld();\n if (world.start) {\n console.log(`starting background tasks...`);\n await world.start().then(\n () => console.log('background tasks started.'),\n (err) => console.error('❗ error starting background tasks:', err)\n );\n }\n\n if (process.env.CONTROL_FD === '3') {\n const control = fs.createWriteStream('', { fd: 3 });\n control.write(`${JSON.stringify({ state: 'listening', info })}\\n`);\n control.end();\n }\n }\n);\n"]}
1
+ {"version":3,"file":"server.mjs","sourceRoot":"","sources":["../../src/server.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC1C,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AACzE,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AACzB,OAAO,EAAE,IAAI,IAAI,QAAQ,EAAE,MAAM,qCAAqC,CAAC;AACvE,OAAO,QAAQ,MAAM,0CAA0C,CAAC;IAC9D,IAAI,EAAE,MAAM;CACb,CAAC;AAEF,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,qBAAqB,EAAE,CAAC;IACvC,OAAO,CAAC,KAAK,CACX,+DAA+D,CAChE,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAMD,MAAM,MAAM,GAAG,CAAC;KACb,MAAM,CAAC;IACN,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAyB,CAAC;IACrE,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;IACpB,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;CACtC,CAAC;KACD,SAAS,CAAC,CAAC,GAAG,EAAE,EAAE;IACjB,MAAM,IAAI,GAAG,GAAG,CAAC,IAAuC,CAAC;IACzD,MAAM,QAAQ,GAAG,CAAC;SACf,IAAI,CACH,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,CAEnC,CACF;SACA,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACvB,OAAO;QACL,IAAI,EAAE,GAAG,CAAC,IAAI;QACd,QAAQ,EAAE,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC;KAC7C,CAAC;AACJ,CAAC,CAAC,CAAC;AAEL,sEAAsE;AACtE,MAAM,oBAAoB,GAAG,IAAI,GAAG,EAAkB,CAAC;AAEvD,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE;KACnB,IAAI,CAAC,+BAA+B,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;IACnD,wEAAwE;IACxE,uEAAuE;IACvE,oEAAoE;IACpE,oEAAoE;IACpE,iEAAiE;IACjE,wDAAwD;IACxD,MAAM,MAAM,GAAG,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC;IACnC,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,CAAC,MAAM,MAAM,CAAC,IAAI,EAAE,CAA4B,CAAC;QAC9D,MAAM,KAAK,GACT,OAAO,IAAI,EAAE,KAAK,KAAK,QAAQ;YAC7B,CAAC,CAAC,IAAI,CAAC,KAAK;YACZ,CAAC,CAAC,OAAQ,IAAI,CAAC,OAA+C;gBACxD,EAAE,KAAK,KAAK,QAAQ;gBACxB,CAAC,CAAG,IAAI,CAAC,OAAmC,CAAC,KAAgB;gBAC7D,CAAC,CAAC,SAAS,CAAC;QAClB,IAAI,KAAK,EAAE,CAAC;YACV,oBAAoB,CAAC,GAAG,CACtB,KAAK,EACL,CAAC,oBAAoB,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAC3C,CAAC;QACJ,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,6CAA6C;IAC/C,CAAC;IACD,OAAO,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAC/B,CAAC,CAAC;KACD,GAAG,CAAC,2BAA2B,EAAE,CAAC,GAAG,EAAE,EAAE;IACxC,MAAM,KAAK,GAAG,oBAAoB,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC;IACpE,OAAO,GAAG,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;AAC7B,CAAC,CAAC;KACD,GAAG,CAAC,YAAY,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;KAC9C,IAAI,CAAC,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;IAC7B,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACrD,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IAEtD,OAAO,GAAG,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;AAC5C,CAAC,CAAC;KACD,IAAI,CAAC,eAAe,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;IACnC,MAAM,IAAI,GAAG,MAAM,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;IAC1D,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC,KAAK,EAAE;QAC7C,GAAG,CAAC,MAAM,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;QACzB,QAAQ,EAAE,IAAI,CAAC,QAAQ;KACxB,CAAC,CAAC;IACH,OAAO,GAAG,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;AAClD,CAAC,CAAC;KACD,GAAG,CAAC,cAAc,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;IACjC,MAAM,KAAK,GAAG,MAAM,QAAQ,EAAE,CAAC;IAC/B,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;IACzD,2DAA2D;IAC3D,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;QAC/C,IAAI,KAAK,YAAY,UAAU,EAAE,CAAC;YAChC,OAAO;gBACL,MAAM,EAAE,YAAY;gBACpB,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC;aAC5C,CAAC;QACJ,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC,CAAC,CAAC;IACH,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE;QACxB,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;KAChD,CAAC,CAAC;AACL,CAAC,CAAC;KACD,GAAG,CAAC,uBAAuB,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;IAC1C,MAAM,KAAK,GAAG,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACrC,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAC1B,OAAO,IAAI,QAAQ,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC;AACzC,CAAC,CAAC;KACD,GAAG,CAAC,qBAAqB,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;IACxC,MAAM,KAAK,GAAG,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACrC,MAAM,KAAK,GAAG,MAAM,QAAQ,EAAE,CAAC;IAC/B,MAAM,SAAS,GAAoD,EAAE,CAAC;IACtE,IAAI,MAAM,GAAuB,SAAS,CAAC;IAC3C,OAAO,IAAI,EAAE,CAAC;QACZ,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC;YACnC,KAAK;YACL,UAAU,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE;SACzC,CAAC,CAAC;QACH,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YAC1B,SAAS,CAAC,IAAI,CAAC;gBACb,SAAS,EAAE,CAAC,CAAC,SAAS;gBACtB,aAAa,EAAE,CAAC,CAAC,aAAa;aAC/B,CAAC,CAAC;QACL,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,MAAM;QACzB,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,SAAS,CAAC;QAClC,IAAI,CAAC,MAAM;YAAE,MAAM;IACrB,CAAC;IACD,OAAO,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;AACzC,CAAC,CAAC,CAAC;AAEL,KAAK,CACH;IACE,KAAK,EAAE,GAAG,CAAC,KAAK;IAChB,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;CACpC,EACD,KAAK,EAAE,IAAI,EAAE,EAAE;IACb,OAAO,CAAC,GAAG,CAAC,0BAA0B,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;IACnE,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAEhB,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;IAExC,KAAK,MAAM,CAAC,QAAQ,EAAE,SAAS,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;QACvE,KAAK,MAAM,YAAY,IAAI,MAAM,CAAC,IAAI,CACpC,SAAoC,CACrC,EAAE,CAAC;YACF,OAAO,CAAC,GAAG,CACT,mCAAmC,IAAI,CAAC,IAAI,eAAe,IAAI,CAAC,SAAS,CACvE;gBACE,IAAI,EAAE,QAAQ;gBACd,QAAQ,EAAE,YAAY;aACvB,CACF,GAAG,CACL,CAAC;QACJ,CAAC;IACH,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,QAAQ,EAAE,CAAC;IAC/B,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;QAChB,OAAO,CAAC,GAAG,CAAC,8BAA8B,CAAC,CAAC;QAC5C,MAAM,KAAK,CAAC,KAAK,EAAE,CAAC,IAAI,CACtB,GAAG,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,2BAA2B,CAAC,EAC9C,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,oCAAoC,EAAE,GAAG,CAAC,CAClE,CAAC;IACJ,CAAC;IAED,IAAI,OAAO,CAAC,GAAG,CAAC,UAAU,KAAK,GAAG,EAAE,CAAC;QACnC,MAAM,OAAO,GAAG,EAAE,CAAC,iBAAiB,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC;QACpD,OAAO,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,IAAI,CAAC,CAAC;QACnE,OAAO,CAAC,GAAG,EAAE,CAAC;IAChB,CAAC;AACH,CAAC,CACF,CAAC","sourcesContent":["import fs from 'node:fs';\nimport { serve } from '@hono/node-server';\nimport { Hono } from 'hono';\nimport { getHookByToken, getRun, resumeHook, start } from 'workflow/api';\nimport { getWorld } from 'workflow/runtime';\nimport * as z from 'zod';\nimport { POST as flowPOST } from '../.well-known/workflow/v1/flow.mjs';\nimport manifest from '../.well-known/workflow/v1/manifest.json' with {\n type: 'json',\n};\n\nif (!process.env.WORKFLOW_TARGET_WORLD) {\n console.error(\n 'Error: WORKFLOW_TARGET_WORLD environment variable is not set.'\n );\n process.exit(1);\n}\n\ntype Files = keyof typeof manifest.workflows;\ntype Workflows<F extends Files> = keyof (typeof manifest.workflows)[F];\ntype NonEmptyArray<T> = [T, ...T[]];\n\nconst Invoke = z\n .object({\n file: z.enum(Object.keys(manifest.workflows) as NonEmptyArray<Files>),\n workflow: z.string(),\n args: z.unknown().array().default([]),\n })\n .transform((obj) => {\n const file = obj.file as keyof typeof manifest.workflows;\n const workflow = z\n .enum(\n Object.keys(manifest.workflows[file]) as NonEmptyArray<\n Workflows<typeof file>\n >\n )\n .parse(obj.workflow);\n return {\n args: obj.args,\n workflow: manifest.workflows[file][workflow],\n };\n });\n\n// Track flow handler invocations per run for testing inline execution\nconst flowInvocationCounts = new Map<string, number>();\n\nconst app = new Hono()\n .post('/.well-known/workflow/v1/flow', async (ctx) => {\n // Clone the request to read the body for tracking without consuming it.\n // We must increment the invocation counter *before* awaiting flowPOST,\n // otherwise the workflow may complete (and the test may observe the\n // completed status) before the counter is bumped, producing a flaky\n // `expected 0 to be 1` failure when the test immediately queries\n // /_flow-invocations after seeing the run as completed.\n const cloned = ctx.req.raw.clone();\n try {\n const body = (await cloned.json()) as Record<string, unknown>;\n const runId =\n typeof body?.runId === 'string'\n ? body.runId\n : typeof (body.payload as Record<string, unknown> | undefined)\n ?.runId === 'string'\n ? ((body.payload as Record<string, unknown>).runId as string)\n : undefined;\n if (runId) {\n flowInvocationCounts.set(\n runId,\n (flowInvocationCounts.get(runId) ?? 0) + 1\n );\n }\n } catch {\n // Health check or non-JSON messages — ignore\n }\n return flowPOST(ctx.req.raw);\n })\n .get('/_flow-invocations/:runId', (ctx) => {\n const count = flowInvocationCounts.get(ctx.req.param('runId')) ?? 0;\n return ctx.json({ count });\n })\n .get('/_manifest', (ctx) => ctx.json(manifest))\n .post('/invoke', async (ctx) => {\n const json = await ctx.req.json().then(Invoke.parse);\n const handler = await start(json.workflow, json.args);\n\n return ctx.json({ runId: handler.runId });\n })\n .post('/hooks/:token', async (ctx) => {\n const hook = await getHookByToken(ctx.req.param('token'));\n const { runId } = await resumeHook(hook.token, {\n ...(await ctx.req.json()),\n metadata: hook.metadata,\n });\n return ctx.json({ runId, hookId: hook.hookId });\n })\n .get('/runs/:runId', async (ctx) => {\n const world = await getWorld();\n const run = await world.runs.get(ctx.req.param('runId'));\n // Custom JSON serialization to handle Uint8Array as base64\n const json = JSON.stringify(run, (_key, value) => {\n if (value instanceof Uint8Array) {\n return {\n __type: 'Uint8Array',\n data: Buffer.from(value).toString('base64'),\n };\n }\n return value;\n });\n return new Response(json, {\n headers: { 'Content-Type': 'application/json' },\n });\n })\n .get('/runs/:runId/readable', async (ctx) => {\n const runId = ctx.req.param('runId');\n const run = getRun(runId);\n return new Response(run.getReadable());\n })\n .get('/runs/:runId/events', async (ctx) => {\n const runId = ctx.req.param('runId');\n const world = await getWorld();\n const allEvents: { eventType: string; correlationId?: string }[] = [];\n let cursor: string | undefined = undefined;\n while (true) {\n const page = await world.events.list({\n runId,\n pagination: { sortOrder: 'asc', cursor },\n });\n for (const e of page.data) {\n allEvents.push({\n eventType: e.eventType,\n correlationId: e.correlationId,\n });\n }\n if (!page.hasMore) break;\n cursor = page.cursor ?? undefined;\n if (!cursor) break;\n }\n return ctx.json({ events: allEvents });\n });\n\nserve(\n {\n fetch: app.fetch,\n port: Number(process.env.PORT) || 0,\n },\n async (info) => {\n console.log(`👂 listening on http://${info.address}:${info.port}`);\n console.log('');\n\n process.env.PORT = info.port.toString();\n\n for (const [filename, workflows] of Object.entries(manifest.workflows)) {\n for (const workflowName of Object.keys(\n workflows as Record<string, unknown>\n )) {\n console.log(\n `$ curl -X POST http://localhost:${info.port}/invoke -d '${JSON.stringify(\n {\n file: filename,\n workflow: workflowName,\n }\n )}'`\n );\n }\n }\n\n const world = await getWorld();\n if (world.start) {\n console.log(`starting background tasks...`);\n await world.start().then(\n () => console.log('background tasks started.'),\n (err) => console.error('❗ error starting background tasks:', err)\n );\n }\n\n if (process.env.CONTROL_FD === '3') {\n const control = fs.createWriteStream('', { fd: 3 });\n control.write(`${JSON.stringify({ state: 'listening', info })}\\n`);\n control.end();\n }\n }\n);\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@workflow/world-testing",
3
- "version": "5.0.0-beta.6",
3
+ "version": "5.0.0-beta.7",
4
4
  "description": "Testing utilities and World implementation for Workflow SDK",
5
5
  "main": "dist/src/index.mjs",
6
6
  "files": [
@@ -21,10 +21,10 @@
21
21
  "hono": "4.12.3",
22
22
  "jsonlines": "0.1.1",
23
23
  "zod": "4.3.6",
24
- "@workflow/cli": "5.0.0-beta.6",
25
- "@workflow/core": "5.0.0-beta.6",
26
- "workflow": "5.0.0-beta.6",
27
- "@workflow/world": "5.0.0-beta.3"
24
+ "@workflow/cli": "5.0.0-beta.7",
25
+ "workflow": "5.0.0-beta.7",
26
+ "@workflow/core": "5.0.0-beta.7",
27
+ "@workflow/world": "5.0.0-beta.4"
28
28
  },
29
29
  "devDependencies": {
30
30
  "@types/node": "22.19.0",