Skip to content

Commit eab3b15

Browse files
authored
Reorganising plugin files (#2877)
Following #2869 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Enhanced Zod schemas with new utility methods: `example`, `deprecated`, and `label` for improved metadata handling. * Introduced a `brand` property for custom schema branding. * Added a `remap` method to object schemas for flexible key transformation. * **Refactor** * Reorganized and separated runtime augmentation logic to improve maintainability and clarity. * **Tests** * Updated test descriptions for clarity; test logic remains unchanged. * **Chores** * Updated internal setup to use the new runtime augmentation module. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 4128ab3 commit eab3b15

7 files changed

Lines changed: 115 additions & 111 deletions

File tree

zod-plugin/augmentation.ts

Lines changed: 2 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
import * as R from "ramda";
2-
import { globalRegistry, z } from "zod";
3-
import { name } from "./package.json";
4-
import { Intact, Remap } from "./remap";
1+
import type { z } from "zod";
2+
import type { Intact, Remap } from "./remap";
53

64
declare module "zod/v4/core" {
75
interface GlobalMeta {
@@ -55,107 +53,3 @@ declare module "zod" {
5553
): z.ZodPipe<z.ZodPipe<this, z.ZodTransform>, z.ZodObject<U>>; // internal type simplified
5654
}
5755
}
58-
59-
interface $EZBrandCheckDef extends z.core.$ZodCheckDef {
60-
check: "$EZBrandCheck";
61-
brand?: string | number | symbol;
62-
}
63-
64-
interface $EZBrandCheckInternals extends z.core.$ZodCheckInternals<unknown> {
65-
def: $EZBrandCheckDef;
66-
}
67-
68-
interface $EZBrandCheck extends z.core.$ZodCheck {
69-
_zod: $EZBrandCheckInternals;
70-
}
71-
72-
/**
73-
* This approach was suggested to me by Colin in a PM on Twitter.
74-
* Refrained from storing the brand in Metadata because it should withstand refinements.
75-
* */
76-
const $EZBrandCheck = z.core.$constructor<$EZBrandCheck>(
77-
"$EZBrandCheck",
78-
(inst, def) => {
79-
z.core.$ZodCheck.init(inst, def);
80-
inst._zod.onattach.push((schema) => (schema._zod.bag.brand = def.brand));
81-
inst._zod.check = () => {};
82-
},
83-
);
84-
85-
const exampleSetter = function (this: z.ZodType, value: z.output<typeof this>) {
86-
const examples = globalRegistry.get(this)?.examples?.slice() || [];
87-
examples.push(value);
88-
return this.meta({ examples });
89-
};
90-
91-
const deprecationSetter = function (this: z.ZodType) {
92-
return this.meta({ deprecated: true });
93-
};
94-
95-
const labelSetter = function (this: z.ZodDefault, defaultLabel: string) {
96-
return this.meta({ default: defaultLabel });
97-
};
98-
99-
const brandSetter = function (
100-
this: z.ZodType,
101-
brand?: string | number | symbol,
102-
) {
103-
return this.check(new $EZBrandCheck({ brand, check: "$EZBrandCheck" }));
104-
};
105-
106-
type _Mapper = <T extends Record<string, unknown>>(
107-
subject: T,
108-
) => { [P in string | keyof T]: T[keyof T] };
109-
110-
const objectMapper = function (
111-
this: z.ZodObject,
112-
tool: Record<string, string> | _Mapper,
113-
) {
114-
const transformer =
115-
typeof tool === "function" ? tool : R.renameKeys(R.reject(R.isNil, tool)); // rejecting undefined
116-
const nextShape = transformer(
117-
R.map(R.invoker(0, "clone"), this._zod.def.shape), // immutable, changed from R.clone due to failure
118-
);
119-
const hasPassThrough = this._zod.def.catchall instanceof z.ZodUnknown;
120-
const output = (hasPassThrough ? z.looseObject : z.object)(nextShape); // proxies unknown keys when set to "passthrough"
121-
return this.transform(transformer).pipe(output);
122-
};
123-
124-
const pluginFlag = Symbol.for(name);
125-
126-
if (!(pluginFlag in globalThis)) {
127-
(globalThis as Record<symbol, unknown>)[pluginFlag] = true;
128-
for (const entry of Object.keys(z)) {
129-
if (!entry.startsWith("Zod")) continue;
130-
if (/(Success|Error|Function)$/.test(entry)) continue;
131-
const Cls = z[entry as keyof typeof z];
132-
if (typeof Cls !== "function") continue;
133-
Object.defineProperties(Cls.prototype, {
134-
["example" satisfies keyof z.ZodType]: {
135-
value: exampleSetter,
136-
writable: false,
137-
},
138-
["deprecated" satisfies keyof z.ZodType]: {
139-
value: deprecationSetter,
140-
writable: false,
141-
},
142-
["brand" satisfies keyof z.ZodType]: {
143-
set() {}, // this is required to override the existing method
144-
get() {
145-
return brandSetter.bind(this) as z.ZodType["brand"];
146-
},
147-
},
148-
});
149-
}
150-
151-
Object.defineProperty(
152-
z.ZodDefault.prototype,
153-
"label" satisfies keyof z.ZodDefault,
154-
{ value: labelSetter, writable: false },
155-
);
156-
Object.defineProperty(
157-
z.ZodObject.prototype,
158-
"remap" satisfies keyof z.ZodObject,
159-
{ value: objectMapper, writable: false },
160-
);
161-
}

zod-plugin/brand-check.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { z } from "zod";
2+
3+
interface $EZBrandCheckDef extends z.core.$ZodCheckDef {
4+
check: "$EZBrandCheck";
5+
brand?: string | number | symbol;
6+
}
7+
8+
interface $EZBrandCheckInternals extends z.core.$ZodCheckInternals<unknown> {
9+
def: $EZBrandCheckDef;
10+
}
11+
12+
export interface $EZBrandCheck extends z.core.$ZodCheck {
13+
_zod: $EZBrandCheckInternals;
14+
}
15+
16+
/**
17+
* This approach was suggested to me by Colin in a PM on Twitter.
18+
* Refrained from storing the brand in Metadata because it should withstand refinements.
19+
* */
20+
export const $EZBrandCheck = z.core.$constructor<$EZBrandCheck>(
21+
"$EZBrandCheck",
22+
(inst, def) => {
23+
z.core.$ZodCheck.init(inst, def);
24+
inst._zod.onattach.push((schema) => (schema._zod.bag.brand = def.brand));
25+
inst._zod.check = () => {};
26+
},
27+
);

zod-plugin/index.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { z } from "zod";
22
import * as entrypoint from "./index";
33

44
describe("Entrypoint", () => {
5-
test("Extended Zod prototypes", () => {
5+
test("Augmentation", () => {
66
expectTypeOf<z.ZodAny>()
77
.toHaveProperty("example")
88
.toEqualTypeOf<(value: any) => z.ZodAny>();

zod-plugin/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1-
import "./augmentation"; // side effects here
1+
import "./augmentation";
2+
import "./runtime"; // side effects here
23
export { getBrand } from "./helpers";

zod-plugin/runtime.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import * as R from "ramda";
2+
import { globalRegistry, z } from "zod";
3+
import { $EZBrandCheck } from "./brand-check";
4+
import { name } from "./package.json";
5+
6+
const exampleSetter = function (this: z.ZodType, value: z.output<typeof this>) {
7+
const examples = globalRegistry.get(this)?.examples?.slice() || [];
8+
examples.push(value);
9+
return this.meta({ examples });
10+
};
11+
12+
const deprecationSetter = function (this: z.ZodType) {
13+
return this.meta({ deprecated: true });
14+
};
15+
16+
const labelSetter = function (this: z.ZodDefault, defaultLabel: string) {
17+
return this.meta({ default: defaultLabel });
18+
};
19+
20+
const brandSetter = function (
21+
this: z.ZodType,
22+
brand?: string | number | symbol,
23+
) {
24+
return this.check(new $EZBrandCheck({ brand, check: "$EZBrandCheck" }));
25+
};
26+
27+
type _Mapper = <T extends Record<string, unknown>>(
28+
subject: T,
29+
) => { [P in string | keyof T]: T[keyof T] };
30+
31+
const objectMapper = function (
32+
this: z.ZodObject,
33+
tool: Record<string, string> | _Mapper,
34+
) {
35+
const transformer =
36+
typeof tool === "function" ? tool : R.renameKeys(R.reject(R.isNil, tool)); // rejecting undefined
37+
const nextShape = transformer(
38+
R.map(R.invoker(0, "clone"), this._zod.def.shape), // immutable, changed from R.clone due to failure
39+
);
40+
const hasPassThrough = this._zod.def.catchall instanceof z.ZodUnknown;
41+
const output = (hasPassThrough ? z.looseObject : z.object)(nextShape); // proxies unknown keys when set to "passthrough"
42+
return this.transform(transformer).pipe(output);
43+
};
44+
45+
const pluginFlag = Symbol.for(name);
46+
47+
if (!(pluginFlag in globalThis)) {
48+
(globalThis as Record<symbol, unknown>)[pluginFlag] = true;
49+
for (const entry of Object.keys(z)) {
50+
if (!entry.startsWith("Zod")) continue;
51+
if (/(Success|Error|Function)$/.test(entry)) continue;
52+
const Cls = z[entry as keyof typeof z];
53+
if (typeof Cls !== "function") continue;
54+
Object.defineProperties(Cls.prototype, {
55+
["example" satisfies keyof z.ZodType]: {
56+
value: exampleSetter,
57+
writable: false,
58+
},
59+
["deprecated" satisfies keyof z.ZodType]: {
60+
value: deprecationSetter,
61+
writable: false,
62+
},
63+
["brand" satisfies keyof z.ZodType]: {
64+
set() {}, // this is required to override the existing method
65+
get() {
66+
return brandSetter.bind(this) as z.ZodType["brand"];
67+
},
68+
},
69+
});
70+
}
71+
72+
Object.defineProperty(
73+
z.ZodDefault.prototype,
74+
"label" satisfies keyof z.ZodDefault,
75+
{ value: labelSetter, writable: false },
76+
);
77+
Object.defineProperty(
78+
z.ZodObject.prototype,
79+
"remap" satisfies keyof z.ZodObject,
80+
{ value: objectMapper, writable: false },
81+
);
82+
}

zod-plugin/vitest.setup.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
import "./augmentation";
1+
import "./runtime";

0 commit comments

Comments
 (0)