Skip to content

Commit ecb645e

Browse files
btraversclaude
andcommitted
fix(client): name the received runtime type in search-attribute errors
Copilot review on #354: typeof reports "object" for arrays, Dates, and null; a describeRuntimeType helper now spells those out. Also stops labeling zod a peer dependency in the README and tutorial — the packages require any Standard Schema validator. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 2c85d4c commit ecb645e

4 files changed

Lines changed: 45 additions & 3 deletions

File tree

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,11 @@ pnpm add @temporal-contract/contract@beta @temporal-contract/worker@beta \
142142
@temporal-contract/client@beta
143143

144144
# Peer dependencies (stable releases)
145-
pnpm add unthrown zod \
145+
pnpm add unthrown \
146146
@temporalio/client @temporalio/common @temporalio/worker @temporalio/workflow
147+
148+
# Plus one Standard Schema validator of your choice — zod, valibot, arktype, …
149+
pnpm add zod
147150
```
148151

149152
Requires **Node.js ≥ 22.19**, ESM (`"type": "module"`), and TypeScript `strict`.

docs/tutorial/your-first-workflow.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ npm install -D typescript @types/node tsx
4646
temporal-contract 8.0 — the API this tutorial teaches — is currently a
4747
prerelease published under the `beta` dist-tag. Without `@beta`, npm installs
4848
the 7.x line, and the code in this tutorial will not match. The peers
49-
(`unthrown`, `@temporalio/*`, `zod`) are stable releases.
49+
(`unthrown`, `@temporalio/*`) and `zod` — this tutorial's pick of
50+
[Standard Schema](https://standardschema.dev/) validator — are stable releases.
5051
:::
5152

5253
Create a `tsconfig.json`. The two settings that matter are `module: nodenext`

packages/client/src/client.spec.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2369,4 +2369,30 @@ describe("ContractClient — search attribute VALUE validation (runtime)", () =>
23692369
}
23702370
expect(mockWorkflow.start).not.toHaveBeenCalled();
23712371
});
2372+
2373+
it('names the received runtime type instead of typeof\'s blanket "object"', async () => {
2374+
const client = await bindContract(kindContract, {
2375+
workflow: mockWorkflow,
2376+
schedule: mockSchedule,
2377+
} as unknown as Client);
2378+
2379+
const cases: Array<{ value: unknown; reported: string }> = [
2380+
{ value: new Date("2026-01-01T00:00:00Z"), reported: "a Date" },
2381+
{ value: ["not", "an", "int"], reported: "an array" },
2382+
{ value: null, reported: "null" },
2383+
];
2384+
for (const { value, reported } of cases) {
2385+
const result = await client.startWorkflow("kinds", {
2386+
workflowId: "k-4",
2387+
args: { id: "a" },
2388+
searchAttributes: { priority: value as unknown as number },
2389+
});
2390+
2391+
expect(result).toBeDefect();
2392+
if (result.isDefect()) {
2393+
expect((result.cause as RuntimeClientError).message).toContain(`received ${reported}.`);
2394+
}
2395+
}
2396+
expect(mockWorkflow.start).not.toHaveBeenCalled();
2397+
});
23722398
});

packages/client/src/internal.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,18 @@ const searchAttributeValueChecks: Record<
7070
},
7171
};
7272

73+
/**
74+
* Name a value's runtime type for error messages. `typeof` alone reports
75+
* `"object"` for arrays, `Date`s, and `null` — the three shapes search
76+
* attributes actually trip over — so spell those out.
77+
*/
78+
function describeRuntimeType(value: unknown): string {
79+
if (value === null) return "null";
80+
if (Array.isArray(value)) return "an array";
81+
if (value instanceof Date) return "a Date";
82+
return `a ${typeof value}`;
83+
}
84+
7385
/**
7486
* Translate the contract's typed `searchAttributes` map (declared
7587
* name → value) into a Temporal `TypedSearchAttributes` instance, so the
@@ -121,7 +133,7 @@ export function toTypedSearchAttributes(
121133
"searchAttributes",
122134
new Error(
123135
`Search attribute "${name}" on workflow "${workflowName}" is declared as ` +
124-
`${def.kind} and must be ${expected}; received ${typeof value}.`,
136+
`${def.kind} and must be ${expected}; received ${describeRuntimeType(value)}.`,
125137
),
126138
);
127139
}

0 commit comments

Comments
 (0)