From 0ccbd0d7b516f468d1d0800d3c488f00cbd97fd1 Mon Sep 17 00:00:00 2001 From: emily-shen Date: Mon, 30 Mar 2026 20:04:00 +1100 Subject: [PATCH 1/8] bodge workflows into openapi config --- .../scripts/openapi-filter-config.ts | 761 ++++++++++++++++++ .../local-explorer/generated/types.gen.ts | 50 +- .../local-explorer/generated/zod.gen.ts | 32 +- .../workers/local-explorer/openapi.local.json | 57 +- 4 files changed, 830 insertions(+), 70 deletions(-) diff --git a/packages/miniflare/scripts/openapi-filter-config.ts b/packages/miniflare/scripts/openapi-filter-config.ts index e8628e39e6..89a18bc406 100644 --- a/packages/miniflare/scripts/openapi-filter-config.ts +++ b/packages/miniflare/scripts/openapi-filter-config.ts @@ -617,6 +617,639 @@ const config = { tags: ["Local Explorer"], }, }, + + // Workflows endpoints (local-only, not pulling from upstream API) + "/workflows": { + get: { + description: + "Returns the workflows configured for local development.", + operationId: "workflows-list-workflows", + parameters: [], + responses: { + "200": { + content: { + "application/json": { + schema: { + allOf: [ + { + $ref: "#/components/schemas/workers_api-response-common", + }, + { + properties: { + result: { + items: { + $ref: "#/components/schemas/workflows_workflow", + }, + type: "array", + }, + result_info: { + properties: { + count: { + type: "number", + }, + }, + type: "object", + }, + }, + type: "object", + }, + ], + }, + }, + }, + description: "List Workflows response.", + }, + "4XX": { + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/workers_api-response-common-failure", + }, + }, + }, + description: "List Workflows response failure.", + }, + }, + summary: "List Workflows", + tags: ["Workflows"], + }, + }, + "/workflows/{workflow_name}": { + get: { + description: + "Returns details of a specific workflow including instance status counts.", + operationId: "workflows-get-workflow-details", + parameters: [ + { + in: "path", + name: "workflow_name", + required: true, + schema: { + $ref: "#/components/schemas/workflows_workflow-name", + }, + }, + ], + responses: { + "200": { + content: { + "application/json": { + schema: { + allOf: [ + { + $ref: "#/components/schemas/workers_api-response-common", + }, + { + properties: { + result: { + $ref: "#/components/schemas/workflows_workflow-details", + }, + }, + type: "object", + }, + ], + }, + }, + }, + description: "Get Workflow Details response.", + }, + "4XX": { + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/workers_api-response-common-failure", + }, + }, + }, + description: "Get Workflow Details response failure.", + }, + }, + summary: "Get Workflow Details", + tags: ["Workflows"], + }, + delete: { + description: + "Deletes all instances of a workflow by removing their persistence files.", + operationId: "workflows-delete-workflow", + parameters: [ + { + in: "path", + name: "workflow_name", + required: true, + schema: { + $ref: "#/components/schemas/workflows_workflow-name", + }, + }, + ], + responses: { + "200": { + content: { + "application/json": { + schema: { + allOf: [ + { + $ref: "#/components/schemas/workers_api-response-common", + }, + { + properties: { + result: { + type: "object", + properties: { + status: { + type: "string", + }, + success: { + type: "boolean", + }, + }, + }, + }, + type: "object", + }, + ], + }, + }, + }, + description: "Delete Workflow response.", + }, + "4XX": { + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/workers_api-response-common-failure", + }, + }, + }, + description: "Delete Workflow response failure.", + }, + }, + summary: "Delete Workflow (all instances)", + tags: ["Workflows"], + }, + }, + "/workflows/{workflow_name}/instances": { + get: { + description: "Returns the instances of a workflow.", + operationId: "workflows-list-instances", + parameters: [ + { + in: "path", + name: "workflow_name", + required: true, + schema: { + $ref: "#/components/schemas/workflows_workflow-name", + }, + }, + { + in: "query", + name: "page", + schema: { + default: 1, + description: "Page number (1-indexed).", + minimum: 1, + type: "number", + }, + }, + { + in: "query", + name: "per_page", + schema: { + default: 25, + description: "Number of instances per page.", + maximum: 100, + minimum: 1, + type: "number", + }, + }, + { + in: "query", + name: "status", + schema: { + type: "string", + enum: [ + "queued", + "running", + "paused", + "errored", + "terminated", + "complete", + "waitingForPause", + "waiting", + ], + description: "Filter instances by status.", + }, + }, + ], + responses: { + "200": { + content: { + "application/json": { + schema: { + allOf: [ + { + $ref: "#/components/schemas/workers_api-response-common", + }, + { + properties: { + result: { + items: { + $ref: "#/components/schemas/workflows_instance", + }, + type: "array", + }, + result_info: { + properties: { + page: { + type: "number", + }, + per_page: { + type: "number", + }, + total_count: { + type: "number", + }, + total_pages: { + type: "number", + }, + }, + type: "object", + }, + }, + type: "object", + }, + ], + }, + }, + }, + description: "List Workflow Instances response.", + }, + "4XX": { + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/workers_api-response-common-failure", + }, + }, + }, + description: "List Workflow Instances response failure.", + }, + }, + summary: "List Workflow Instances", + tags: ["Workflows"], + }, + post: { + description: "Creates a new workflow instance.", + operationId: "workflows-create-instance", + parameters: [ + { + in: "path", + name: "workflow_name", + required: true, + schema: { + $ref: "#/components/schemas/workflows_workflow-name", + }, + }, + ], + requestBody: { + content: { + "application/json": { + schema: { + type: "object", + properties: { + id: { + type: "string", + description: + "Optional instance ID. If not provided, a UUID is generated.", + }, + params: { + description: + "Optional JSON payload to pass to the workflow.", + }, + }, + }, + }, + }, + }, + responses: { + "200": { + content: { + "application/json": { + schema: { + allOf: [ + { + $ref: "#/components/schemas/workers_api-response-common", + }, + { + properties: { + result: { + type: "object", + properties: { + id: { + type: "string", + description: + "The instance ID of the newly created workflow instance.", + }, + }, + required: ["id"], + }, + }, + type: "object", + }, + ], + }, + }, + }, + description: "Create Workflow Instance response.", + }, + "4XX": { + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/workers_api-response-common-failure", + }, + }, + }, + description: "Create Workflow Instance response failure.", + }, + }, + summary: "Create Workflow Instance", + tags: ["Workflows"], + }, + }, + "/workflows/{workflow_name}/instances/{instance_id}": { + get: { + description: "Returns the status details of a workflow instance.", + operationId: "workflows-get-instance-details", + parameters: [ + { + in: "path", + name: "workflow_name", + required: true, + schema: { + $ref: "#/components/schemas/workflows_workflow-name", + }, + }, + { + in: "path", + name: "instance_id", + required: true, + schema: { + $ref: "#/components/schemas/workflows_instance-id", + }, + }, + ], + responses: { + "200": { + content: { + "application/json": { + schema: { + allOf: [ + { + $ref: "#/components/schemas/workers_api-response-common", + }, + { + properties: { + result: { + $ref: "#/components/schemas/workflows_instance-details", + }, + }, + type: "object", + }, + ], + }, + }, + }, + description: "Get Workflow Instance Details response.", + }, + "4XX": { + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/workers_api-response-common-failure", + }, + }, + }, + description: "Get Workflow Instance Details response failure.", + }, + }, + summary: "Get Workflow Instance Details", + tags: ["Workflows"], + }, + delete: { + description: + "Deletes a workflow instance by removing its persistence files.", + operationId: "workflows-delete-instance", + parameters: [ + { + in: "path", + name: "workflow_name", + required: true, + schema: { + $ref: "#/components/schemas/workflows_workflow-name", + }, + }, + { + in: "path", + name: "instance_id", + required: true, + schema: { + $ref: "#/components/schemas/workflows_instance-id", + }, + }, + ], + responses: { + "200": { + content: { + "application/json": { + schema: { + allOf: [ + { + $ref: "#/components/schemas/workers_api-response-common", + }, + { + properties: { + result: { + type: "object", + properties: { + success: { + type: "boolean", + }, + }, + }, + }, + type: "object", + }, + ], + }, + }, + }, + description: "Delete Workflow Instance response.", + }, + "4XX": { + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/workers_api-response-common-failure", + }, + }, + }, + description: "Delete Workflow Instance response failure.", + }, + }, + summary: "Delete Workflow Instance", + tags: ["Workflows"], + }, + }, + "/workflows/{workflow_name}/instances/{instance_id}/status": { + patch: { + description: + "Changes the status of a workflow instance (pause, resume, restart, terminate).", + operationId: "workflows-change-instance-status", + parameters: [ + { + in: "path", + name: "workflow_name", + required: true, + schema: { + $ref: "#/components/schemas/workflows_workflow-name", + }, + }, + { + in: "path", + name: "instance_id", + required: true, + schema: { + $ref: "#/components/schemas/workflows_instance-id", + }, + }, + ], + requestBody: { + required: true, + content: { + "application/json": { + schema: { + type: "object", + required: ["action"], + properties: { + action: { + type: "string", + enum: ["pause", "resume", "restart", "terminate"], + description: + "The action to perform on the workflow instance.", + }, + }, + }, + }, + }, + }, + responses: { + "200": { + content: { + "application/json": { + schema: { + allOf: [ + { + $ref: "#/components/schemas/workers_api-response-common", + }, + { + properties: { + result: { + type: "object", + properties: { + success: { + type: "boolean", + }, + }, + }, + }, + type: "object", + }, + ], + }, + }, + }, + description: "Change Workflow Instance Status response.", + }, + "4XX": { + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/workers_api-response-common-failure", + }, + }, + }, + description: "Change Workflow Instance Status response failure.", + }, + }, + summary: "Change Workflow Instance Status", + tags: ["Workflows"], + }, + }, + "/workflows/{workflow_name}/instances/{instance_id}/events/{event_type}": { + post: { + description: "Sends an event to a workflow instance.", + operationId: "workflows-send-instance-event", + parameters: [ + { + in: "path", + name: "workflow_name", + required: true, + schema: { + $ref: "#/components/schemas/workflows_workflow-name", + }, + }, + { + in: "path", + name: "instance_id", + required: true, + schema: { + $ref: "#/components/schemas/workflows_instance-id", + }, + }, + { + in: "path", + name: "event_type", + required: true, + schema: { + type: "string", + description: "The event type to send.", + }, + }, + ], + requestBody: { + content: { + "application/json": { + schema: { + description: "Optional JSON payload for the event.", + }, + }, + }, + }, + responses: { + "200": { + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/workers_api-response-common", + }, + }, + }, + description: "Send Event response.", + }, + "4XX": { + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/workers_api-response-common-failure", + }, + }, + }, + description: "Send Event response failure.", + }, + }, + summary: "Send Event to Workflow Instance", + tags: ["Workflows"], + }, + }, }, schemas: { // R2 schemas - matches stratus dashboard API shapes @@ -827,6 +1460,134 @@ const config = { }, }, }, + + // Workflows schemas (local-only, not pulling from upstream API) + "workflows_workflow-name": { + description: "The name of the workflow.", + example: "my-workflow", + type: "string", + }, + "workflows_instance-id": { + description: "The unique identifier of a workflow instance.", + example: "my-instance-id", + type: "string", + }, + workflows_workflow: { + type: "object", + properties: { + name: { + type: "string", + description: "The name of the workflow.", + }, + class_name: { + type: "string", + description: "The entrypoint class name of the workflow.", + }, + script_name: { + type: "string", + description: "The script name containing the workflow.", + }, + }, + required: ["name"], + }, + "workflows_workflow-details": { + type: "object", + properties: { + name: { + type: "string", + description: "The name of the workflow.", + }, + class_name: { + type: "string", + description: "The entrypoint class name.", + }, + script_name: { + type: "string", + description: "The script containing the workflow.", + }, + instances: { + type: "object", + description: "Instance counts by status.", + properties: { + complete: { type: "number" }, + errored: { type: "number" }, + paused: { type: "number" }, + queued: { type: "number" }, + running: { type: "number" }, + terminated: { type: "number" }, + waiting: { type: "number" }, + waitingForPause: { type: "number" }, + }, + }, + }, + required: ["name", "class_name", "script_name", "instances"], + }, + workflows_instance: { + type: "object", + properties: { + id: { + type: "string", + description: "The unique identifier of the workflow instance.", + }, + status: { + type: "string", + enum: [ + "queued", + "running", + "paused", + "errored", + "terminated", + "complete", + "waitingForPause", + "waiting", + "unknown", + ], + description: "The current status of the instance.", + }, + created_on: { + type: "string", + description: + "ISO 8601 timestamp of when the instance was created.", + }, + }, + required: ["id"], + }, + "workflows_instance-details": { + type: "object", + properties: { + id: { + type: "string", + description: "The unique identifier of the workflow instance.", + }, + status: { + type: "string", + enum: [ + "queued", + "running", + "paused", + "errored", + "terminated", + "complete", + "waitingForPause", + "waiting", + "unknown", + ], + description: "The current status of the instance.", + }, + output: { + description: "Output value if the workflow completed successfully.", + }, + error: { + type: "object", + properties: { + name: { type: "string" }, + message: { type: "string" }, + }, + description: "Error details if the workflow errored.", + }, + }, + required: ["id", "status"], + }, }, }, } satisfies FilterConfig; diff --git a/packages/miniflare/src/workers/local-explorer/generated/types.gen.ts b/packages/miniflare/src/workers/local-explorer/generated/types.gen.ts index 12c788ef5f..5cf8dbdf51 100644 --- a/packages/miniflare/src/workers/local-explorer/generated/types.gen.ts +++ b/packages/miniflare/src/workers/local-explorer/generated/types.gen.ts @@ -590,6 +590,31 @@ export type LocalExplorerWorker = { protocol: string; }; +/** + * The name of the workflow. + */ +export type WorkflowsWorkflowName = string; + +/** + * The unique identifier of a workflow instance. + */ +export type WorkflowsInstanceId = string; + +export type WorkflowsWorkflow = { + /** + * The name of the workflow. + */ + name: string; + /** + * The entrypoint class name of the workflow. + */ + class_name?: string; + /** + * The script name containing the workflow. + */ + script_name?: string; +}; + export type WorkflowsWorkflowDetails = { /** * The name of the workflow. @@ -618,31 +643,6 @@ export type WorkflowsWorkflowDetails = { }; }; -/** - * The name of the workflow. - */ -export type WorkflowsWorkflowName = string; - -/** - * The unique identifier of a workflow instance. - */ -export type WorkflowsInstanceId = string; - -export type WorkflowsWorkflow = { - /** - * The name of the workflow. - */ - name: string; - /** - * The entrypoint class name of the workflow. - */ - class_name?: string; - /** - * The script name containing the workflow. - */ - script_name?: string; -}; - export type WorkflowsInstance = { /** * The unique identifier of the workflow instance. diff --git a/packages/miniflare/src/workers/local-explorer/generated/zod.gen.ts b/packages/miniflare/src/workers/local-explorer/generated/zod.gen.ts index dd5635df46..85a9edb4cc 100644 --- a/packages/miniflare/src/workers/local-explorer/generated/zod.gen.ts +++ b/packages/miniflare/src/workers/local-explorer/generated/zod.gen.ts @@ -409,22 +409,6 @@ export const zLocalExplorerWorker = z.object({ protocol: z.string(), }); -export const zWorkflowsWorkflowDetails = z.object({ - name: z.string(), - class_name: z.string(), - script_name: z.string(), - instances: z.object({ - complete: z.number().optional(), - errored: z.number().optional(), - paused: z.number().optional(), - queued: z.number().optional(), - running: z.number().optional(), - terminated: z.number().optional(), - waiting: z.number().optional(), - waitingForPause: z.number().optional(), - }), -}); - /** * The name of the workflow. */ @@ -441,6 +425,22 @@ export const zWorkflowsWorkflow = z.object({ script_name: z.string().optional(), }); +export const zWorkflowsWorkflowDetails = z.object({ + name: z.string(), + class_name: z.string(), + script_name: z.string(), + instances: z.object({ + complete: z.number().optional(), + errored: z.number().optional(), + paused: z.number().optional(), + queued: z.number().optional(), + running: z.number().optional(), + terminated: z.number().optional(), + waiting: z.number().optional(), + waitingForPause: z.number().optional(), + }), +}); + export const zWorkflowsInstance = z.object({ id: z.string(), status: z diff --git a/packages/miniflare/src/workers/local-explorer/openapi.local.json b/packages/miniflare/src/workers/local-explorer/openapi.local.json index a4fd1de84c..5485d4d6e7 100644 --- a/packages/miniflare/src/workers/local-explorer/openapi.local.json +++ b/packages/miniflare/src/workers/local-explorer/openapi.local.json @@ -1241,7 +1241,6 @@ "get": { "description": "List all workers in the local dev registry.", "operationId": "local-explorer-list-workers", - "parameters": [], "responses": { "200": { "content": { @@ -3010,6 +3009,34 @@ } } }, + "workflows_workflow-name": { + "description": "The name of the workflow.", + "example": "my-workflow", + "type": "string" + }, + "workflows_instance-id": { + "description": "The unique identifier of a workflow instance.", + "example": "my-instance-id", + "type": "string" + }, + "workflows_workflow": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the workflow." + }, + "class_name": { + "type": "string", + "description": "The entrypoint class name of the workflow." + }, + "script_name": { + "type": "string", + "description": "The script name containing the workflow." + } + }, + "required": ["name"] + }, "workflows_workflow-details": { "type": "object", "properties": { @@ -3058,34 +3085,6 @@ }, "required": ["name", "class_name", "script_name", "instances"] }, - "workflows_workflow-name": { - "description": "The name of the workflow.", - "example": "my-workflow", - "type": "string" - }, - "workflows_instance-id": { - "description": "The unique identifier of a workflow instance.", - "example": "my-instance-id", - "type": "string" - }, - "workflows_workflow": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "The name of the workflow." - }, - "class_name": { - "type": "string", - "description": "The entrypoint class name of the workflow." - }, - "script_name": { - "type": "string", - "description": "The script name containing the workflow." - } - }, - "required": ["name"] - }, "workflows_instance": { "type": "object", "properties": { From bb5f7821f786426d34ab9de16b1592dc86f0be0f Mon Sep 17 00:00:00 2001 From: emily-shen Date: Thu, 2 Apr 2026 16:30:31 +1100 Subject: [PATCH 2/8] update openapi description a little --- packages/miniflare/scripts/filter-openapi.ts | 2 +- .../miniflare/src/workers/local-explorer/openapi.local.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/miniflare/scripts/filter-openapi.ts b/packages/miniflare/scripts/filter-openapi.ts index d049fc26a0..cbe4e34bc6 100644 --- a/packages/miniflare/scripts/filter-openapi.ts +++ b/packages/miniflare/scripts/filter-openapi.ts @@ -32,7 +32,7 @@ const DEFAULT_OUTPUT_PATH = join( const LOCAL_EXPLORER_INFO = { title: "Local Explorer API", description: - "Local subset of Cloudflare API for exploring resources during local development.", + "A local subset of Cloudflare API for inspecting and modifying resource state during local development. Supports D1, R2, KV, Durable Objects and Workflows.", version: "0.0.1", }; diff --git a/packages/miniflare/src/workers/local-explorer/openapi.local.json b/packages/miniflare/src/workers/local-explorer/openapi.local.json index 5485d4d6e7..a3441afb9f 100644 --- a/packages/miniflare/src/workers/local-explorer/openapi.local.json +++ b/packages/miniflare/src/workers/local-explorer/openapi.local.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "Local Explorer API", - "description": "Local subset of Cloudflare API for exploring resources during local development.", + "description": "A local subset of Cloudflare API for inspecting and modifying resource state during local development. Supports D1, R2, KV, Durable Objects and Workflows.", "version": "0.0.1" }, "servers": [ From 5eca5f6f1bb2cf524de031e0dae6cee2bce8f4e3 Mon Sep 17 00:00:00 2001 From: emily-shen Date: Thu, 2 Apr 2026 16:40:41 +1100 Subject: [PATCH 3/8] serve api --- .../src/workers/local-explorer/explorer.worker.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/miniflare/src/workers/local-explorer/explorer.worker.ts b/packages/miniflare/src/workers/local-explorer/explorer.worker.ts index fe23288e9c..afbbc71550 100644 --- a/packages/miniflare/src/workers/local-explorer/explorer.worker.ts +++ b/packages/miniflare/src/workers/local-explorer/explorer.worker.ts @@ -4,6 +4,7 @@ import { Hono } from "hono/tiny"; import mime from "mime"; import { CoreBindings, CorePaths } from "../core"; +import openApiSpec from "./openapi.local.json"; import { errorResponse, validateQuery, validateRequestBody } from "./common"; import { wrapResponse } from "./common"; import { @@ -156,6 +157,12 @@ app.get("/*", async (c, next) => { return c.notFound(); }); +// ============================================================================ +// OpenAPI Spec Endpoint +// ============================================================================ + +app.get("/api", (c) => c.json(openApiSpec)); + // ============================================================================ // KV Endpoints // ============================================================================ From 45325d477983914db11c944cda95ef72d0d5ed1a Mon Sep 17 00:00:00 2001 From: emily-shen Date: Thu, 2 Apr 2026 16:48:04 +1100 Subject: [PATCH 4/8] changeset --- .changeset/better-rooms-fold.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/better-rooms-fold.md diff --git a/.changeset/better-rooms-fold.md b/.changeset/better-rooms-fold.md new file mode 100644 index 0000000000..d1490a8022 --- /dev/null +++ b/.changeset/better-rooms-fold.md @@ -0,0 +1,7 @@ +--- +"miniflare": minor +--- + +local explorer: serve the local explorer's OpenAPI spec at /cdn-cgi/explorer/api + +The local explorer is supported by a REST API served from the worker's local address. It can be accessed independently of the UI, (e.g. by an AI agent) and is thus documented at this endpoint. From 1a57d7fbd3c2111ea7c6404bb38a22cbd3c8dcff Mon Sep 17 00:00:00 2001 From: emily-shen Date: Thu, 2 Apr 2026 17:21:24 +1100 Subject: [PATCH 5/8] fixups and test --- .../scripts/openapi-filter-config.ts | 100 +++++++++--------- .../workers/local-explorer/explorer.worker.ts | 2 +- .../test/plugins/local-explorer/index.spec.ts | 14 +++ 3 files changed, 65 insertions(+), 51 deletions(-) diff --git a/packages/miniflare/scripts/openapi-filter-config.ts b/packages/miniflare/scripts/openapi-filter-config.ts index 89a18bc406..4edd2ce3fb 100644 --- a/packages/miniflare/scripts/openapi-filter-config.ts +++ b/packages/miniflare/scripts/openapi-filter-config.ts @@ -1184,72 +1184,73 @@ const config = { tags: ["Workflows"], }, }, - "/workflows/{workflow_name}/instances/{instance_id}/events/{event_type}": { - post: { - description: "Sends an event to a workflow instance.", - operationId: "workflows-send-instance-event", - parameters: [ - { - in: "path", - name: "workflow_name", - required: true, - schema: { - $ref: "#/components/schemas/workflows_workflow-name", - }, - }, - { - in: "path", - name: "instance_id", - required: true, - schema: { - $ref: "#/components/schemas/workflows_instance-id", + "/workflows/{workflow_name}/instances/{instance_id}/events/{event_type}": + { + post: { + description: "Sends an event to a workflow instance.", + operationId: "workflows-send-instance-event", + parameters: [ + { + in: "path", + name: "workflow_name", + required: true, + schema: { + $ref: "#/components/schemas/workflows_workflow-name", + }, }, - }, - { - in: "path", - name: "event_type", - required: true, - schema: { - type: "string", - description: "The event type to send.", + { + in: "path", + name: "instance_id", + required: true, + schema: { + $ref: "#/components/schemas/workflows_instance-id", + }, }, - }, - ], - requestBody: { - content: { - "application/json": { + { + in: "path", + name: "event_type", + required: true, schema: { - description: "Optional JSON payload for the event.", + type: "string", + description: "The event type to send.", }, }, - }, - }, - responses: { - "200": { + ], + requestBody: { content: { "application/json": { schema: { - $ref: "#/components/schemas/workers_api-response-common", + description: "Optional JSON payload for the event.", }, }, }, - description: "Send Event response.", }, - "4XX": { - content: { - "application/json": { - schema: { - $ref: "#/components/schemas/workers_api-response-common-failure", + responses: { + "200": { + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/workers_api-response-common", + }, + }, + }, + description: "Send Event response.", + }, + "4XX": { + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/workers_api-response-common-failure", + }, }, }, + description: "Send Event response failure.", }, - description: "Send Event response failure.", }, + summary: "Send Event to Workflow Instance", + tags: ["Workflows"], }, - summary: "Send Event to Workflow Instance", - tags: ["Workflows"], }, - }, }, schemas: { // R2 schemas - matches stratus dashboard API shapes @@ -1546,8 +1547,7 @@ const config = { }, created_on: { type: "string", - description: - "ISO 8601 timestamp of when the instance was created.", + description: "ISO 8601 timestamp of when the instance was created.", }, }, required: ["id"], diff --git a/packages/miniflare/src/workers/local-explorer/explorer.worker.ts b/packages/miniflare/src/workers/local-explorer/explorer.worker.ts index afbbc71550..d28464037b 100644 --- a/packages/miniflare/src/workers/local-explorer/explorer.worker.ts +++ b/packages/miniflare/src/workers/local-explorer/explorer.worker.ts @@ -4,7 +4,6 @@ import { Hono } from "hono/tiny"; import mime from "mime"; import { CoreBindings, CorePaths } from "../core"; -import openApiSpec from "./openapi.local.json"; import { errorResponse, validateQuery, validateRequestBody } from "./common"; import { wrapResponse } from "./common"; import { @@ -19,6 +18,7 @@ import { zWorkersKvNamespaceListNamespacesData, zWorkflowsListInstancesData, } from "./generated/zod.gen"; +import openApiSpec from "./openapi.local.json"; import { listD1Databases, rawD1Database } from "./resources/d1"; import { listDONamespaces, listDOObjects, queryDOSqlite } from "./resources/do"; import { diff --git a/packages/miniflare/test/plugins/local-explorer/index.spec.ts b/packages/miniflare/test/plugins/local-explorer/index.spec.ts index 17c32335bd..34989cb47c 100644 --- a/packages/miniflare/test/plugins/local-explorer/index.spec.ts +++ b/packages/miniflare/test/plugins/local-explorer/index.spec.ts @@ -255,6 +255,20 @@ describe("Local Explorer API validation", () => { }); describe("routing", () => { + test("serves OpenAPI spec at /cdn-cgi/explorer/api", async ({ expect }) => { + const res = await mf.dispatchFetch( + "http://localhost/cdn-cgi/explorer/api" + ); + expect(res.status).toBe(200); + expect(res.headers.get("Content-Type")).toContain("application/json"); + + const spec = await res.json(); + expect(spec).toMatchObject({ + openapi: "3.0.3", + info: { title: "Local Explorer API" }, + }); + }); + test("serves explorer UI at /cdn-cgi/explorer", async ({ expect }) => { const res = await mf.dispatchFetch("http://localhost/cdn-cgi/explorer"); expect(res.status).toBe(200); From 3b935732b832025464e52d6cb84b5dc8348f0076 Mon Sep 17 00:00:00 2001 From: emily-shen <69125074+emily-shen@users.noreply.github.com> Date: Tue, 7 Apr 2026 17:28:24 +1000 Subject: [PATCH 6/8] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Somhairle MacLeòid --- packages/miniflare/scripts/filter-openapi.ts | 2 +- .../miniflare/src/workers/local-explorer/openapi.local.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/miniflare/scripts/filter-openapi.ts b/packages/miniflare/scripts/filter-openapi.ts index cbe4e34bc6..15e7712c32 100644 --- a/packages/miniflare/scripts/filter-openapi.ts +++ b/packages/miniflare/scripts/filter-openapi.ts @@ -32,7 +32,7 @@ const DEFAULT_OUTPUT_PATH = join( const LOCAL_EXPLORER_INFO = { title: "Local Explorer API", description: - "A local subset of Cloudflare API for inspecting and modifying resource state during local development. Supports D1, R2, KV, Durable Objects and Workflows.", + "A local subset of the Cloudflare API for inspecting and modifying resource state during local development. Supports D1, R2, KV, Durable Objects and Workflows.", version: "0.0.1", }; diff --git a/packages/miniflare/src/workers/local-explorer/openapi.local.json b/packages/miniflare/src/workers/local-explorer/openapi.local.json index a3441afb9f..231beca5cb 100644 --- a/packages/miniflare/src/workers/local-explorer/openapi.local.json +++ b/packages/miniflare/src/workers/local-explorer/openapi.local.json @@ -2,7 +2,7 @@ "openapi": "3.0.3", "info": { "title": "Local Explorer API", - "description": "A local subset of Cloudflare API for inspecting and modifying resource state during local development. Supports D1, R2, KV, Durable Objects and Workflows.", + "description": "A local subset of the Cloudflare API for inspecting and modifying resource state during local development. Supports D1, R2, KV, Durable Objects and Workflows.", "version": "0.0.1" }, "servers": [ From c52cad9f5017004b1fc6363da546e6ca56206ca0 Mon Sep 17 00:00:00 2001 From: emily-shen Date: Tue, 7 Apr 2026 08:05:40 +0000 Subject: [PATCH 7/8] Add PATCH to CORS Access-Control-Allow-Methods for workflow status endpoint --- .../miniflare/src/workers/local-explorer/explorer.worker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/miniflare/src/workers/local-explorer/explorer.worker.ts b/packages/miniflare/src/workers/local-explorer/explorer.worker.ts index d28464037b..7329c4adaf 100644 --- a/packages/miniflare/src/workers/local-explorer/explorer.worker.ts +++ b/packages/miniflare/src/workers/local-explorer/explorer.worker.ts @@ -89,7 +89,7 @@ app.use("/api/*", async (c, next) => { status: 204, headers: { "Access-Control-Allow-Origin": origin ?? "*", - "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS", + "Access-Control-Allow-Methods": "GET, POST, PUT, PATCH, DELETE, OPTIONS", "Access-Control-Allow-Headers": "Content-Type, cf-metadata-only, cf-r2-custom-metadata", "Access-Control-Max-Age": "86400", From 63cbb6df14f4510ef66020afdfc4706959fbad65 Mon Sep 17 00:00:00 2001 From: emily-shen Date: Tue, 7 Apr 2026 21:42:25 +1000 Subject: [PATCH 8/8] lint previous patches --- .../miniflare/src/workers/local-explorer/explorer.worker.ts | 3 ++- packages/miniflare/test/plugins/local-explorer/index.spec.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/miniflare/src/workers/local-explorer/explorer.worker.ts b/packages/miniflare/src/workers/local-explorer/explorer.worker.ts index 7329c4adaf..83acd048c8 100644 --- a/packages/miniflare/src/workers/local-explorer/explorer.worker.ts +++ b/packages/miniflare/src/workers/local-explorer/explorer.worker.ts @@ -89,7 +89,8 @@ app.use("/api/*", async (c, next) => { status: 204, headers: { "Access-Control-Allow-Origin": origin ?? "*", - "Access-Control-Allow-Methods": "GET, POST, PUT, PATCH, DELETE, OPTIONS", + "Access-Control-Allow-Methods": + "GET, POST, PUT, PATCH, DELETE, OPTIONS", "Access-Control-Allow-Headers": "Content-Type, cf-metadata-only, cf-r2-custom-metadata", "Access-Control-Max-Age": "86400", diff --git a/packages/miniflare/test/plugins/local-explorer/index.spec.ts b/packages/miniflare/test/plugins/local-explorer/index.spec.ts index 34989cb47c..c8be4f7937 100644 --- a/packages/miniflare/test/plugins/local-explorer/index.spec.ts +++ b/packages/miniflare/test/plugins/local-explorer/index.spec.ts @@ -217,7 +217,7 @@ describe("Local Explorer API validation", () => { "http://localhost:5173" ); expect(res.headers.get("Access-Control-Allow-Methods")).toBe( - "GET, POST, PUT, DELETE, OPTIONS" + "GET, POST, PUT, PATCH, DELETE, OPTIONS" ); await res.arrayBuffer();