Skip to content

Commit 7216835

Browse files
authored
Make workflow instance.status() return output equal to production workflows (#7575)
* feat: Make `instance.status()` return output equal to production Previously,in local dev, the `output` field would return the list of successful steps outputs in the workflow. This is not expected behaviour compared to production workflows (where the output is the actual return of the `run` function), probably just a implementation detail not set left-over from before beta. This commit makes it so that `output` is equal to production behaviour. For observability sake, I kept the old step output list in a different field `__LOCAL_DEV_STEP_OUTPUTS` - I think this is a good enough compromise right now since we want local dev to be correct against prod first. We can remove `__LOCAL_DEV_STEP_OUTPUTS` later, once we figure out on how to add custom stuff to the devtools page. * fix types * fix tests * chore: add better types for readLogs
1 parent 178fd01 commit 7216835

File tree

8 files changed

+85
-32
lines changed

8 files changed

+85
-32
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@cloudflare/workflows-shared": patch
3+
"miniflare": patch
4+
---
5+
6+
Make `Instance.status()` return type the same as production

fixtures/workflow-multiple/src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export class Demo extends WorkflowEntrypoint<{}, Params> {
2929
};
3030
});
3131

32-
return [result, result2, timestamp, payload, "workflow1"];
32+
return "i'm workflow1";
3333
}
3434
}
3535

@@ -53,7 +53,7 @@ export class Demo2 extends WorkflowEntrypoint<{}, Params> {
5353
};
5454
});
5555

56-
return [result, result2, timestamp, payload, "workflow2"];
56+
return "i'm workflow2";
5757
}
5858
}
5959

fixtures/workflow-multiple/tests/index.test.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ describe("Workflows", () => {
4848
id: "test",
4949
status: {
5050
status: "running",
51-
output: [],
51+
__LOCAL_DEV_STEP_OUTPUTS: [],
52+
output: null,
5253
},
5354
};
5455

@@ -65,7 +66,8 @@ describe("Workflows", () => {
6566
id: "test",
6667
status: {
6768
status: "running",
68-
output: [{ output: "First step result" }],
69+
__LOCAL_DEV_STEP_OUTPUTS: [{ output: "First step result" }],
70+
output: null,
6971
},
7072
};
7173
await Promise.all([
@@ -96,10 +98,11 @@ describe("Workflows", () => {
9698
id: "test",
9799
status: {
98100
status: "complete",
99-
output: [
101+
__LOCAL_DEV_STEP_OUTPUTS: [
100102
{ output: "First step result" },
101103
{ output: "workflow1" },
102104
],
105+
output: "i'm workflow1",
103106
},
104107
});
105108
},
@@ -113,10 +116,11 @@ describe("Workflows", () => {
113116
id: "test",
114117
status: {
115118
status: "complete",
116-
output: [
119+
__LOCAL_DEV_STEP_OUTPUTS: [
117120
{ output: "First step result" },
118121
{ output: "workflow2" },
119122
],
123+
output: "i'm workflow2",
120124
},
121125
});
122126
},

fixtures/workflow/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export class Demo extends WorkflowEntrypoint<{}, Params> {
2727
};
2828
});
2929

30-
return [result, result2, timestamp, payload];
30+
return "i'm a workflow output";
3131
}
3232
}
3333

fixtures/workflow/tests/index.test.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ describe("Workflows", () => {
4646
fetchJson(`http://${ip}:${port}/create?workflowName=test`)
4747
).resolves.toEqual({
4848
status: "running",
49-
output: [],
49+
__LOCAL_DEV_STEP_OUTPUTS: [],
50+
output: null,
5051
});
5152

5253
await vi.waitFor(
@@ -55,7 +56,8 @@ describe("Workflows", () => {
5556
fetchJson(`http://${ip}:${port}/status?workflowName=test`)
5657
).resolves.toEqual({
5758
status: "running",
58-
output: [{ output: "First step result" }],
59+
__LOCAL_DEV_STEP_OUTPUTS: [{ output: "First step result" }],
60+
output: null,
5961
});
6062
},
6163
{ timeout: 5000 }
@@ -67,10 +69,11 @@ describe("Workflows", () => {
6769
fetchJson(`http://${ip}:${port}/status?workflowName=test`)
6870
).resolves.toEqual({
6971
status: "complete",
70-
output: [
72+
__LOCAL_DEV_STEP_OUTPUTS: [
7173
{ output: "First step result" },
7274
{ output: "Second step result" },
7375
],
76+
output: "i'm a workflow output",
7477
});
7578
},
7679
{ timeout: 5000 }
@@ -80,7 +83,8 @@ describe("Workflows", () => {
8083
it("creates a workflow without id", async ({ expect }) => {
8184
await expect(fetchJson(`http://${ip}:${port}/create`)).resolves.toEqual({
8285
status: "running",
83-
output: [],
86+
__LOCAL_DEV_STEP_OUTPUTS: [],
87+
output: null,
8488
});
8589
});
8690

packages/miniflare/test/plugins/workflows/index.spec.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,10 @@ test("persists Workflow data on file-system between runs", async (t) => {
4040
t.teardown(() => mf.dispose());
4141

4242
let res = await mf.dispatchFetch("http://localhost");
43-
t.is(await res.text(), '{"status":"running","output":[]}');
43+
t.is(
44+
await res.text(),
45+
'{"status":"running","__LOCAL_DEV_STEP_OUTPUTS":[],"output":null}'
46+
);
4447

4548
// there's no waitUntil in ava haha
4649
const begin = performance.now();
@@ -50,7 +53,10 @@ test("persists Workflow data on file-system between runs", async (t) => {
5053
const res = await mf.dispatchFetch("http://localhost");
5154
console.log(test);
5255
test = await res.text();
53-
if (test === '{"status":"complete","output":["yes you are"]}') {
56+
if (
57+
test ===
58+
'{"status":"complete","__LOCAL_DEV_STEP_OUTPUTS":["yes you are"],"output":"I\'m a output string"}'
59+
) {
5460
success = true;
5561
break;
5662
}
@@ -68,5 +74,8 @@ test("persists Workflow data on file-system between runs", async (t) => {
6874

6975
// state should be persisted now
7076
res = await mf.dispatchFetch("http://localhost");
71-
t.is(await res.text(), '{"status":"complete","output":["yes you are"]}');
77+
t.is(
78+
await res.text(),
79+
'{"status":"complete","__LOCAL_DEV_STEP_OUTPUTS":["yes you are"],"output":"I\'m a output string"}'
80+
);
7281
});

packages/workflows-shared/src/binding.ts

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type {
55
DatabaseVersion,
66
DatabaseWorkflow,
77
Engine,
8+
EngineLogs,
89
} from "./engine";
910

1011
type Env = {
@@ -93,16 +94,34 @@ export class WorkflowHandle extends RpcTarget implements WorkflowInstance {
9394
throw new Error("Not implemented yet");
9495
}
9596

96-
public async status(): Promise<InstanceStatus> {
97+
public async status(): Promise<
98+
InstanceStatus & { __LOCAL_DEV_STEP_OUTPUTS: unknown[] }
99+
> {
97100
const status = await this.stub.getStatus(0, this.id);
98-
const { logs } = await this.stub.readLogs();
99-
// @ts-expect-error TODO: Fix this
101+
102+
// NOTE(lduarte): for some reason, sync functions over RPC are typed as never instead of Promise<EngineLogs>
103+
const { logs } =
104+
await (this.stub.readLogs() as unknown as Promise<EngineLogs>);
105+
106+
const workflowSuccessEvent = logs
107+
.filter((log) => log.event === InstanceEvent.WORKFLOW_SUCCESS)
108+
.at(0);
109+
100110
const filteredLogs = logs.filter(
101-
// @ts-expect-error TODO: Fix this
102111
(log) => log.event === InstanceEvent.STEP_SUCCESS
103112
);
104-
// @ts-expect-error TODO: Fix this
105-
const output = filteredLogs.map((log) => log.metadata.result);
106-
return { status: instanceStatusName(status), output }; // output, error
113+
const stepOutputs = filteredLogs.map((log) => log.metadata.result);
114+
115+
const workflowOutput =
116+
workflowSuccessEvent !== undefined
117+
? workflowSuccessEvent.metadata.result
118+
: null;
119+
120+
return {
121+
status: instanceStatusName(status),
122+
__LOCAL_DEV_STEP_OUTPUTS: stepOutputs,
123+
// @ts-expect-error types are wrong, will remove this expect-error once I fix them
124+
output: workflowOutput,
125+
}; // output, error
107126
}
108127
}

packages/workflows-shared/src/engine.ts

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,7 @@ import {
1212
startGracePeriod,
1313
} from "./lib/gracePeriodSemaphore";
1414
import { TimePriorityQueue } from "./lib/timePriorityQueue";
15-
import type {
16-
InstanceLogsResponse,
17-
InstanceMetadata,
18-
RawInstanceLog,
19-
} from "./instance";
15+
import type { InstanceMetadata, RawInstanceLog } from "./instance";
2016
import type { WorkflowEntrypoint, WorkflowEvent } from "cloudflare:workers";
2117

2218
export interface Env {
@@ -53,6 +49,19 @@ export type DatabaseInstance = {
5349
ended_on: string | null;
5450
};
5551

52+
export type Log = {
53+
event: InstanceEvent;
54+
group: string | null;
55+
target: string | null;
56+
metadata: {
57+
result: unknown;
58+
};
59+
};
60+
61+
export type EngineLogs = {
62+
logs: Log[];
63+
};
64+
5665
const ENGINE_STATUS_KEY = "ENGINE_STATUS";
5766

5867
export class Engine extends DurableObject<Env> {
@@ -121,18 +130,20 @@ export class Engine extends DurableObject<Env> {
121130
return [];
122131
}
123132

124-
readLogs(): InstanceLogsResponse {
133+
readLogs(): EngineLogs {
125134
const logs = [
126-
...this.ctx.storage.sql.exec<Record<string, string | number>>(
127-
"SELECT event, groupKey, target, metadata FROM states"
128-
),
135+
...this.ctx.storage.sql.exec<{
136+
event: InstanceEvent;
137+
groupKey: string | null;
138+
target: string | null;
139+
metadata: string;
140+
}>("SELECT event, groupKey, target, metadata FROM states"),
129141
];
130142

131143
return {
132-
// @ts-expect-error TODO: Fix this
133144
logs: logs.map((log) => ({
134145
...log,
135-
metadata: JSON.parse(log.metadata as string),
146+
metadata: JSON.parse(log.metadata),
136147
group: log.groupKey,
137148
})),
138149
};

0 commit comments

Comments
 (0)