Skip to content

Commit 04e1f37

Browse files
authored
Fixed freezing async ZodReadonly results (#3457)
* docs: Corrected typo in readme * test: result freezing after async parse * fix: result freezing after async parse
1 parent 3fed6f2 commit 04e1f37

File tree

3 files changed

+60
-5
lines changed

3 files changed

+60
-5
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -2682,7 +2682,7 @@ Note that branded types do not affect the runtime result of `.parse`. It is a st
26822682
This method returns a `ZodReadonly` schema instance that parses the input using the base schema, then calls `Object.freeze()` on the result. The inferred type is also marked as `readonly`.
26832683

26842684
```ts
2685-
const schema = z.object({ name: string }).readonly();
2685+
const schema = z.object({ name: z.string() }).readonly();
26862686
type schema = z.infer<typeof schema>;
26872687
// Readonly<{name: string}>
26882688

src/__tests__/readonly.test.ts

+50
Original file line numberDiff line numberDiff line change
@@ -202,3 +202,53 @@ test("object freezing", () => {
202202
)
203203
).toBe(true);
204204
});
205+
206+
test("async object freezing", async () => {
207+
expect(
208+
Object.isFrozen(await z.array(z.string()).readonly().parseAsync(["a"]))
209+
).toBe(true);
210+
expect(
211+
Object.isFrozen(
212+
await z.tuple([z.string(), z.number()]).readonly().parseAsync(["a", 1])
213+
)
214+
).toBe(true);
215+
expect(
216+
Object.isFrozen(
217+
await z
218+
.map(z.string(), z.date())
219+
.readonly()
220+
.parseAsync(new Map([["a", new Date()]]))
221+
)
222+
).toBe(true);
223+
expect(
224+
Object.isFrozen(
225+
await z
226+
.set(z.promise(z.string()))
227+
.readonly()
228+
.parseAsync(new Set([Promise.resolve("a")]))
229+
)
230+
).toBe(true);
231+
expect(
232+
Object.isFrozen(
233+
await z.record(z.string()).readonly().parseAsync({ a: "b" })
234+
)
235+
).toBe(true);
236+
expect(
237+
Object.isFrozen(
238+
await z.record(z.string(), z.number()).readonly().parseAsync({ a: 1 })
239+
)
240+
).toBe(true);
241+
expect(
242+
Object.isFrozen(
243+
await z
244+
.object({ a: z.string(), 1: z.number() })
245+
.readonly()
246+
.parseAsync({ a: "b", 1: 2 })
247+
)
248+
).toBe(true);
249+
expect(
250+
Object.isFrozen(
251+
await z.promise(z.string()).readonly().parseAsync(Promise.resolve("a"))
252+
)
253+
).toBe(true);
254+
});

src/types.ts

+9-4
Original file line numberDiff line numberDiff line change
@@ -5041,10 +5041,15 @@ export class ZodReadonly<T extends ZodTypeAny> extends ZodType<
50415041
> {
50425042
_parse(input: ParseInput): ParseReturnType<this["_output"]> {
50435043
const result = this._def.innerType._parse(input);
5044-
if (isValid(result)) {
5045-
result.value = Object.freeze(result.value);
5046-
}
5047-
return result;
5044+
const freeze = (data: ParseReturnType<this["_output"]>) => {
5045+
if (isValid(data)) {
5046+
data.value = Object.freeze(data.value);
5047+
}
5048+
return data;
5049+
};
5050+
return isAsync(result)
5051+
? result.then((data) => freeze(data))
5052+
: freeze(result);
50485053
}
50495054

50505055
static create = <T extends ZodTypeAny>(

0 commit comments

Comments
 (0)