zodbridge 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +361 -0
- package/dist/async/index.cjs +146 -0
- package/dist/async/index.cjs.map +1 -0
- package/dist/async/index.d.cts +49 -0
- package/dist/async/index.d.ts +49 -0
- package/dist/async/index.js +65 -0
- package/dist/async/index.js.map +1 -0
- package/dist/chunk-3HB2CM5G.js +213 -0
- package/dist/chunk-3HB2CM5G.js.map +1 -0
- package/dist/chunk-7DUCUUPF.js +274 -0
- package/dist/chunk-7DUCUUPF.js.map +1 -0
- package/dist/chunk-U3W6PGFE.js +241 -0
- package/dist/chunk-U3W6PGFE.js.map +1 -0
- package/dist/context-B0f9mQWu.d.cts +140 -0
- package/dist/context-B0f9mQWu.d.ts +140 -0
- package/dist/index.cjs +786 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +204 -0
- package/dist/index.d.ts +204 -0
- package/dist/index.js +40 -0
- package/dist/index.js.map +1 -0
- package/dist/resolver/index.cjs +250 -0
- package/dist/resolver/index.cjs.map +1 -0
- package/dist/resolver/index.d.cts +101 -0
- package/dist/resolver/index.d.ts +101 -0
- package/dist/resolver/index.js +17 -0
- package/dist/resolver/index.js.map +1 -0
- package/dist/rules-msHkZDR8.d.cts +59 -0
- package/dist/rules-msHkZDR8.d.ts +59 -0
- package/dist/serialize/index.cjs +258 -0
- package/dist/serialize/index.cjs.map +1 -0
- package/dist/serialize/index.d.cts +84 -0
- package/dist/serialize/index.d.ts +84 -0
- package/dist/serialize/index.js +3 -0
- package/dist/serialize/index.js.map +1 -0
- package/package.json +86 -0
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
// src/map/rules.ts
|
|
2
|
+
function fromResolver(field) {
|
|
3
|
+
return { __kind: "fromResolver", field };
|
|
4
|
+
}
|
|
5
|
+
function withDefault(value, source) {
|
|
6
|
+
return { __kind: "default", value, source };
|
|
7
|
+
}
|
|
8
|
+
function isStringRule(rule) {
|
|
9
|
+
return typeof rule === "string";
|
|
10
|
+
}
|
|
11
|
+
function isFromResolverRule(rule) {
|
|
12
|
+
return typeof rule === "object" && rule !== null && rule.__kind === "fromResolver";
|
|
13
|
+
}
|
|
14
|
+
function isPairRule(rule) {
|
|
15
|
+
return typeof rule === "object" && rule !== null && typeof rule.to === "function" && typeof rule.from === "function";
|
|
16
|
+
}
|
|
17
|
+
function isDefaultRule(rule) {
|
|
18
|
+
return typeof rule === "object" && rule !== null && rule.__kind === "default";
|
|
19
|
+
}
|
|
20
|
+
function isFnRule(rule) {
|
|
21
|
+
return typeof rule === "function";
|
|
22
|
+
}
|
|
23
|
+
var FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
24
|
+
function isForbidden(key) {
|
|
25
|
+
return FORBIDDEN_KEYS.has(key);
|
|
26
|
+
}
|
|
27
|
+
function splitPath(path) {
|
|
28
|
+
return path.split(".");
|
|
29
|
+
}
|
|
30
|
+
function getPath(source, path) {
|
|
31
|
+
let current = source;
|
|
32
|
+
for (const segment of splitPath(path)) {
|
|
33
|
+
if (isForbidden(segment)) return void 0;
|
|
34
|
+
if (current === null || current === void 0) return void 0;
|
|
35
|
+
if (typeof current !== "object") return void 0;
|
|
36
|
+
if (!Object.prototype.hasOwnProperty.call(current, segment)) return void 0;
|
|
37
|
+
current = current[segment];
|
|
38
|
+
}
|
|
39
|
+
return current;
|
|
40
|
+
}
|
|
41
|
+
function setPath(target, path, value) {
|
|
42
|
+
const segments = splitPath(path);
|
|
43
|
+
let current = target;
|
|
44
|
+
for (let i = 0; i < segments.length; i++) {
|
|
45
|
+
const segment = segments[i];
|
|
46
|
+
if (isForbidden(segment)) return;
|
|
47
|
+
if (i === segments.length - 1) {
|
|
48
|
+
current[segment] = value;
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const next = current[segment];
|
|
52
|
+
if (typeof next !== "object" || next === null) {
|
|
53
|
+
const fresh = {};
|
|
54
|
+
current[segment] = fresh;
|
|
55
|
+
current = fresh;
|
|
56
|
+
} else {
|
|
57
|
+
current = next;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// src/resolver/context.ts
|
|
63
|
+
var EMPTY_ANCESTORS = /* @__PURE__ */ new Set();
|
|
64
|
+
function defer() {
|
|
65
|
+
let resolve;
|
|
66
|
+
let reject;
|
|
67
|
+
const promise = new Promise((res, rej) => {
|
|
68
|
+
resolve = res;
|
|
69
|
+
reject = rej;
|
|
70
|
+
});
|
|
71
|
+
return { promise, resolve, reject };
|
|
72
|
+
}
|
|
73
|
+
var GraphContext = class {
|
|
74
|
+
adapter;
|
|
75
|
+
seed;
|
|
76
|
+
resolvers;
|
|
77
|
+
schemas;
|
|
78
|
+
onResolve;
|
|
79
|
+
/** Durable promise-cache: every attempted field lives here. */
|
|
80
|
+
cache = /* @__PURE__ */ new Map();
|
|
81
|
+
/** Synchronously-readable settled values, for `get`/`path` peeks. */
|
|
82
|
+
settled = /* @__PURE__ */ new Map();
|
|
83
|
+
/** Fields with an in-flight resolver; transient cycle guard. */
|
|
84
|
+
inProgress = /* @__PURE__ */ new Set();
|
|
85
|
+
/** Fields explicitly invalidated: prefer the resolver over seed on next resolve. */
|
|
86
|
+
invalidated = /* @__PURE__ */ new Set();
|
|
87
|
+
constructor(config) {
|
|
88
|
+
this.adapter = config.adapter;
|
|
89
|
+
this.seed = config.seed ?? {};
|
|
90
|
+
this.resolvers = config.resolvers ?? {};
|
|
91
|
+
this.schemas = config.schemas ?? {};
|
|
92
|
+
this.onResolve = config.onResolve;
|
|
93
|
+
}
|
|
94
|
+
/** Synchronous peek of seed first, then any settled resolved value. */
|
|
95
|
+
peek(field) {
|
|
96
|
+
const seeded = this.seed[field];
|
|
97
|
+
if (seeded !== void 0) return seeded;
|
|
98
|
+
return this.settled.get(field);
|
|
99
|
+
}
|
|
100
|
+
get(field) {
|
|
101
|
+
return this.peek(field);
|
|
102
|
+
}
|
|
103
|
+
path(field, keyPath) {
|
|
104
|
+
const base = this.peek(field);
|
|
105
|
+
if (base === void 0) return void 0;
|
|
106
|
+
if (keyPath.length === 0) return base;
|
|
107
|
+
return getPath(base, keyPath.join("."));
|
|
108
|
+
}
|
|
109
|
+
/** Public entry: a fresh resolution chain with no ancestors. */
|
|
110
|
+
resolve(field) {
|
|
111
|
+
return this.resolveWithin(field, EMPTY_ANCESTORS);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Resolve `field` within a chain whose currently-resolving fields are
|
|
115
|
+
* `ancestors`. If `field` is itself an ancestor, this is a dependency cycle
|
|
116
|
+
* (mutual A<->B, longer A->B->C->A, or self x->x): return `undefined` so the
|
|
117
|
+
* caller (e.g. a `strategies` candidate) falls through to its next route,
|
|
118
|
+
* instead of awaiting its own still-pending promise (which would deadlock).
|
|
119
|
+
*
|
|
120
|
+
* The ancestor check runs BEFORE the `cache.has` short-circuit, so a CONCURRENT
|
|
121
|
+
* caller (a parallel `Promise.all` chain — `field` is in-flight but NOT its
|
|
122
|
+
* ancestor) still receives the shared pending promise and dedups normally.
|
|
123
|
+
*/
|
|
124
|
+
resolveWithin(field, ancestors) {
|
|
125
|
+
if (ancestors.has(field)) {
|
|
126
|
+
return Promise.resolve(void 0);
|
|
127
|
+
}
|
|
128
|
+
const cached = this.cache.get(field);
|
|
129
|
+
if (cached !== void 0) return cached;
|
|
130
|
+
const resolver = this.resolvers[field];
|
|
131
|
+
const preferResolver = this.invalidated.has(field) && resolver !== void 0;
|
|
132
|
+
const seeded = this.seed[field];
|
|
133
|
+
if (seeded !== void 0 && !preferResolver) {
|
|
134
|
+
const p2 = Promise.resolve(seeded);
|
|
135
|
+
this.cache.set(field, p2);
|
|
136
|
+
return p2;
|
|
137
|
+
}
|
|
138
|
+
if (resolver) {
|
|
139
|
+
this.invalidated.delete(field);
|
|
140
|
+
const deferred = defer();
|
|
141
|
+
this.cache.set(field, deferred.promise);
|
|
142
|
+
this.inProgress.add(field);
|
|
143
|
+
const childAncestors = new Set(ancestors).add(field);
|
|
144
|
+
this.runResolver(field, resolver, deferred, childAncestors);
|
|
145
|
+
return deferred.promise;
|
|
146
|
+
}
|
|
147
|
+
const p = Promise.resolve(void 0);
|
|
148
|
+
this.cache.set(field, p);
|
|
149
|
+
return p;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* A context bound to `self` and the chain's `ancestors`: identical to the
|
|
153
|
+
* top-level context except `resolve`/`fallback` carry the chain so cycles
|
|
154
|
+
* break (see {@link resolveWithin}) and `fallback` retries the right field.
|
|
155
|
+
* Binding per invocation (not shared instance state) keeps concurrent
|
|
156
|
+
* resolutions from contaminating each other.
|
|
157
|
+
*/
|
|
158
|
+
boundContext(self, ancestors) {
|
|
159
|
+
return {
|
|
160
|
+
adapter: this.adapter,
|
|
161
|
+
resolve: (field) => this.resolveWithin(field, ancestors),
|
|
162
|
+
resolveMany: (...fields) => this.resolveMany(...fields),
|
|
163
|
+
get: (field) => this.get(field),
|
|
164
|
+
path: (field, keyPath) => this.path(field, keyPath),
|
|
165
|
+
fallback: (deps) => this.fallbackFor(self, deps, ancestors),
|
|
166
|
+
invalidate: (...fields) => this.invalidate(...fields),
|
|
167
|
+
refresh: (field) => this.refresh(field)
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
invalidate(...fields) {
|
|
171
|
+
for (const field of fields) {
|
|
172
|
+
this.cache.delete(field);
|
|
173
|
+
this.settled.delete(field);
|
|
174
|
+
if (this.resolvers[field] !== void 0) this.invalidated.add(field);
|
|
175
|
+
}
|
|
176
|
+
return this;
|
|
177
|
+
}
|
|
178
|
+
refresh(field) {
|
|
179
|
+
this.invalidate(field);
|
|
180
|
+
return this.resolve(field);
|
|
181
|
+
}
|
|
182
|
+
/** Fire the best-effort `onResolve` observer; never let it break resolution. */
|
|
183
|
+
emitResolved(field, value) {
|
|
184
|
+
if (!this.onResolve) return;
|
|
185
|
+
try {
|
|
186
|
+
this.onResolve(field, value);
|
|
187
|
+
} catch {
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
runResolver(field, resolver, deferred, ancestors) {
|
|
191
|
+
const ctx = this.boundContext(field, ancestors);
|
|
192
|
+
Promise.resolve().then(() => resolver(ctx)).then((value) => this.validate(field, value)).then(
|
|
193
|
+
(value) => {
|
|
194
|
+
this.settled.set(field, value);
|
|
195
|
+
this.inProgress.delete(field);
|
|
196
|
+
this.emitResolved(field, value);
|
|
197
|
+
deferred.resolve(value);
|
|
198
|
+
},
|
|
199
|
+
(error) => {
|
|
200
|
+
this.cache.delete(field);
|
|
201
|
+
this.inProgress.delete(field);
|
|
202
|
+
deferred.reject(error);
|
|
203
|
+
}
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Validate a resolver's output against its field schema (if any). A defined
|
|
208
|
+
* value is parsed (unknown keys stripped); a parse failure throws the ZodError,
|
|
209
|
+
* which the reject branch turns into an eviction. `undefined` is the forgiving
|
|
210
|
+
* absence terminal and is never validated.
|
|
211
|
+
*/
|
|
212
|
+
async validate(field, value) {
|
|
213
|
+
const schema = this.schemas[field];
|
|
214
|
+
if (schema === void 0 || value === void 0) return value;
|
|
215
|
+
return await schema.parseAsync(value);
|
|
216
|
+
}
|
|
217
|
+
/** Resolve untouched `deps`, then retry `self`'s resolver if any produced a value. */
|
|
218
|
+
async fallbackFor(self, deps, ancestors) {
|
|
219
|
+
let resolvedAny = false;
|
|
220
|
+
for (const dep of deps) {
|
|
221
|
+
if (this.cache.has(dep) || this.inProgress.has(dep)) continue;
|
|
222
|
+
const value = await this.resolveWithin(dep, ancestors);
|
|
223
|
+
if (value) resolvedAny = true;
|
|
224
|
+
}
|
|
225
|
+
if (!resolvedAny) return void 0;
|
|
226
|
+
const resolver = this.resolvers[self];
|
|
227
|
+
return resolver(this.boundContext(self, ancestors));
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Top-level `fallback` has no calling field, so there is nothing to retry.
|
|
231
|
+
* Resolvers always receive a field-bound context whose `fallback` does retry;
|
|
232
|
+
* this entry exists only to satisfy the public {@link ResolverContext} shape.
|
|
233
|
+
*/
|
|
234
|
+
fallback(_deps) {
|
|
235
|
+
return Promise.resolve(void 0);
|
|
236
|
+
}
|
|
237
|
+
async resolveMany(...fields) {
|
|
238
|
+
const out = {};
|
|
239
|
+
for (const field of fields) {
|
|
240
|
+
const value = await this.resolve(field);
|
|
241
|
+
if (value !== void 0) out[field] = value;
|
|
242
|
+
}
|
|
243
|
+
return out;
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
// src/resolver/createResolver.ts
|
|
248
|
+
function createResolver(config) {
|
|
249
|
+
const schemas = config.maps ? buildSchemas(config.maps, config.fields) : config.schemas;
|
|
250
|
+
return new GraphContext({
|
|
251
|
+
adapter: config.adapter,
|
|
252
|
+
seed: config.seed,
|
|
253
|
+
resolvers: config.resolvers,
|
|
254
|
+
schemas,
|
|
255
|
+
onResolve: config.onResolve
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
function buildSchemas(maps, fields) {
|
|
259
|
+
const out = /* @__PURE__ */ Object.create(null);
|
|
260
|
+
for (const key of Object.keys(maps)) {
|
|
261
|
+
out[key] = maps[key].schema;
|
|
262
|
+
}
|
|
263
|
+
if (fields) {
|
|
264
|
+
const shape = fields.shape;
|
|
265
|
+
for (const key of Object.keys(shape)) {
|
|
266
|
+
out[key] = shape[key];
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return out;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export { GraphContext, createResolver, fromResolver, getPath, isDefaultRule, isFnRule, isFromResolverRule, isPairRule, isStringRule, setPath, withDefault };
|
|
273
|
+
//# sourceMappingURL=chunk-7DUCUUPF.js.map
|
|
274
|
+
//# sourceMappingURL=chunk-7DUCUUPF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/map/rules.ts","../src/resolver/context.ts","../src/resolver/createResolver.ts"],"names":["p"],"mappings":"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","file":"chunk-7DUCUUPF.js","sourcesContent":["/**\n * Rule kinds for {@link createMap} and the dot-path get/set helpers they use.\n *\n * Rule kinds (by JS type):\n * - `string` — rename: copy `source[ruleValue]` to the dest key. Auto-reversible.\n * - `(source) => value` — computed function, one-way (no reverse unless paired).\n * - `{ to, from }` — explicit both-directions, reversible.\n * - `FromResolver` — value pulled from a resolver graph (async path, Phase 5).\n */\n\n/** A function rule: derive a dest value from the whole source object. */\nexport type FnRule<S, V> = (source: S) => V;\n\n/** A reversible rule with explicit forward (`to`) and inverse (`from`) maps. */\nexport interface PairRule<S, V> {\n to: (source: S) => V;\n from: (value: V) => unknown;\n}\n\n/** Marker for a dest field resolved from a resolver graph (consumed by Phase 5). */\nexport interface FromResolver {\n readonly __kind: \"fromResolver\";\n readonly field: string;\n}\n\n/**\n * Default-value rule: read `source` (a source key, defaults to the dest key)\n * and fall back to `value` when the source is `undefined`. Reverse-safe: the\n * default is dropped on reverse and only the read key round-trips.\n */\nexport interface DefaultRule<V = unknown> {\n readonly __kind: \"default\";\n readonly value: V;\n readonly source?: string;\n}\n\n/** Any rule a dest key may be configured with. */\nexport type Rule<S = any, V = any> =\n | string\n | FnRule<S, V>\n | PairRule<S, V>\n | FromResolver\n | DefaultRule<V>;\n\n/** Build a {@link FromResolver} marker for `field` in the resolver graph. */\nexport function fromResolver(field: string): FromResolver {\n return { __kind: \"fromResolver\", field };\n}\n\n/**\n * Build a {@link DefaultRule}: use `source` (or the dest key) from the entity,\n * falling back to `value` when absent.\n */\nexport function withDefault<V>(value: V, source?: string): DefaultRule<V> {\n return { __kind: \"default\", value, source };\n}\n\n// --- type guards ---\n\nexport function isStringRule(rule: Rule): rule is string {\n return typeof rule === \"string\";\n}\n\nexport function isFromResolverRule(rule: Rule): rule is FromResolver {\n return (\n typeof rule === \"object\" &&\n rule !== null &&\n (rule as FromResolver).__kind === \"fromResolver\"\n );\n}\n\nexport function isPairRule(rule: Rule): rule is PairRule<any, any> {\n return (\n typeof rule === \"object\" &&\n rule !== null &&\n typeof (rule as PairRule<any, any>).to === \"function\" &&\n typeof (rule as PairRule<any, any>).from === \"function\"\n );\n}\n\nexport function isDefaultRule(rule: Rule): rule is DefaultRule {\n return (\n typeof rule === \"object\" &&\n rule !== null &&\n (rule as DefaultRule).__kind === \"default\"\n );\n}\n\nexport function isFnRule(rule: Rule): rule is FnRule<any, any> {\n return typeof rule === \"function\";\n}\n\n// --- prototype-pollution-safe dot-path helpers ---\n\n/** Path segments that, if written, could poison `Object.prototype`. */\nconst FORBIDDEN_KEYS = new Set([\"__proto__\", \"prototype\", \"constructor\"]);\n\nfunction isForbidden(key: string): boolean {\n return FORBIDDEN_KEYS.has(key);\n}\n\n/** Split a dot-path into segments. A plain key (no dot) yields a single segment. */\nexport function splitPath(path: string): string[] {\n return path.split(\".\");\n}\n\n/**\n * Read a nested value by dot-path. Returns `undefined` if any segment is\n * missing or if a segment is a forbidden prototype key (read is skipped, not\n * served from the prototype chain).\n */\nexport function getPath(source: unknown, path: string): unknown {\n let current: unknown = source;\n for (const segment of splitPath(path)) {\n if (isForbidden(segment)) return undefined;\n if (current === null || current === undefined) return undefined;\n if (typeof current !== \"object\") return undefined;\n if (!Object.prototype.hasOwnProperty.call(current, segment)) return undefined;\n current = (current as Record<string, unknown>)[segment];\n }\n return current;\n}\n\n/**\n * Write a nested value by dot-path, auto-vivifying intermediate objects.\n * Forbidden prototype keys at any segment are skipped entirely, so a hostile\n * `__proto__.isAdmin` path can never mutate `Object.prototype`.\n */\nexport function setPath(target: Record<string, unknown>, path: string, value: unknown): void {\n const segments = splitPath(path);\n let current: Record<string, unknown> = target;\n for (let i = 0; i < segments.length; i++) {\n const segment = segments[i] as string;\n if (isForbidden(segment)) return;\n if (i === segments.length - 1) {\n current[segment] = value;\n return;\n }\n const next = current[segment];\n if (typeof next !== \"object\" || next === null) {\n const fresh: Record<string, unknown> = {};\n current[segment] = fresh;\n current = fresh;\n } else {\n current = next as Record<string, unknown>;\n }\n }\n}\n","/**\n * Resolver-graph context: dependency-aware, memoizing, cycle-guarded lazy field\n * resolution. A faithful generalization of a `ContextResolver`-style engine.\n *\n * Invariants (do not \"optimize\" away):\n * (a) The promise for a field is stored in `cache` BEFORE the first await, so\n * concurrent `Promise.all` over a shared dep fires its resolver once.\n * (b) The touched-check (`cache` ∪ `inProgress`) serializes a field's re-entry,\n * breaking cycles.\n * (c) A resolver that THROWS rejects and evicts its cache entry — errors are\n * never coerced to `undefined` and never cached. Only a RETURNED `undefined`\n * is cached as a terminal value.\n * (d) `fallback` retries the calling field by re-invoking its resolver FUNCTION\n * directly, bypassing the step-1 cache short-circuit, so a momentarily\n * `undefined` field can still resolve once a dependency becomes available.\n * (e) The calling field for `fallback` is bound per resolver invocation (a\n * field-bound context), NOT read from shared instance state — so two\n * fallback-using resolvers running concurrently never contaminate each\n * other's retry target.\n */\nimport type { z } from \"zod\";\nimport { getPath } from \"../map/rules.js\";\n\n/** A resolver function for one field; may use the context to derive its value. */\nexport type ResolverFn<Fields, TAdapter, K extends keyof Fields> = (\n ctx: ResolverContext<Fields, TAdapter>,\n) => Fields[K] | undefined | Promise<Fields[K] | undefined>;\n\n/** Registry of per-field resolvers (declarative; not class methods). */\nexport type ResolverRegistry<Fields, TAdapter> = {\n [K in keyof Fields]?: ResolverFn<Fields, TAdapter, K>;\n};\n\n/**\n * Per-field Zod schemas used to validate resolver outputs at runtime. Built by\n * the schema-driven `createResolver` overload from `maps` + `fields`; a field\n * with no entry here is not validated (e.g. hand-written-`Fields` callers).\n */\nexport type ResolverSchemas<Fields> = Partial<Record<keyof Fields, z.ZodType>>;\n\n/** Construction config for a resolver graph. */\nexport interface ResolverConfig<Fields, TAdapter> {\n adapter: TAdapter;\n seed?: Partial<Fields>;\n resolvers?: ResolverRegistry<Fields, TAdapter>;\n /**\n * Optional per-field schemas. When present for a field, its resolver's output\n * is parsed (and unknown keys stripped) before caching; a parse failure\n * rejects `resolve` and evicts the entry. Seed values are NOT validated.\n */\n schemas?: ResolverSchemas<Fields>;\n /**\n * Optional observer fired after a field's resolver settles successfully (post\n * validation), for tracing/debugging. Never fired for seed/terminal values.\n * Throwing here does not affect resolution (the hook is best-effort).\n */\n onResolve?: (field: keyof Fields, value: unknown) => void;\n}\n\n/** The context object handed to every resolver and returned to callers. */\nexport interface ResolverContext<Fields, TAdapter> {\n readonly adapter: TAdapter;\n /** Resolve one field: cache -> seed -> resolver -> `undefined`. Memoized. */\n resolve<K extends keyof Fields>(field: K): Promise<Fields[K] | undefined>;\n /** Resolve several fields; returns only the requested keys. */\n resolveMany<K extends keyof Fields>(\n ...fields: K[]\n ): Promise<Partial<Pick<Fields, K>>>;\n /** Cache peek: the resolved value if present, else `undefined`. No I/O. */\n get<K extends keyof Fields>(field: K): Fields[K] | undefined;\n /** Pure nested peek into seed + cache for `field`. Never resolves, never I/O. */\n path<K extends keyof Fields>(field: K, keyPath: string[]): unknown;\n /** Resolve untouched `deps`, then retry the calling field's resolver fn. */\n fallback(deps: Array<keyof Fields>): Promise<unknown>;\n /**\n * Forget a field's cached value (and any settled peek) so the next `resolve`\n * re-runs its resolver. Use after a write to force a fresh fetch from the\n * adapter. Seed values are also dropped for the field. Returns `this`.\n */\n invalidate(...fields: Array<keyof Fields>): ResolverContext<Fields, TAdapter>;\n /** Invalidate then re-resolve `field` — a forced sync re-fetch from the adapter. */\n refresh<K extends keyof Fields>(field: K): Promise<Fields[K] | undefined>;\n}\n\n/** Shared empty ancestor set for top-level resolution chains (never mutated). */\nconst EMPTY_ANCESTORS: ReadonlySet<never> = new Set<never>();\n\ninterface Deferred<T> {\n promise: Promise<T>;\n resolve: (value: T) => void;\n reject: (reason: unknown) => void;\n}\n\nfunction defer<T>(): Deferred<T> {\n let resolve!: (value: T) => void;\n let reject!: (reason: unknown) => void;\n const promise = new Promise<T>((res, rej) => {\n resolve = res;\n reject = rej;\n });\n return { promise, resolve, reject };\n}\n\nexport class GraphContext<Fields, TAdapter>\n implements ResolverContext<Fields, TAdapter>\n{\n readonly adapter: TAdapter;\n private readonly seed: Partial<Fields>;\n private readonly resolvers: ResolverRegistry<Fields, TAdapter>;\n private readonly schemas: ResolverSchemas<Fields>;\n private readonly onResolve?: (field: keyof Fields, value: unknown) => void;\n /** Durable promise-cache: every attempted field lives here. */\n private readonly cache = new Map<keyof Fields, Promise<unknown>>();\n /** Synchronously-readable settled values, for `get`/`path` peeks. */\n private readonly settled = new Map<keyof Fields, unknown>();\n /** Fields with an in-flight resolver; transient cycle guard. */\n private readonly inProgress = new Set<keyof Fields>();\n /** Fields explicitly invalidated: prefer the resolver over seed on next resolve. */\n private readonly invalidated = new Set<keyof Fields>();\n\n constructor(config: ResolverConfig<Fields, TAdapter>) {\n this.adapter = config.adapter;\n this.seed = config.seed ?? {};\n this.resolvers = config.resolvers ?? {};\n this.schemas = config.schemas ?? {};\n this.onResolve = config.onResolve;\n }\n\n /** Synchronous peek of seed first, then any settled resolved value. */\n private peek<K extends keyof Fields>(field: K): Fields[K] | undefined {\n const seeded = this.seed[field];\n if (seeded !== undefined) return seeded;\n return this.settled.get(field) as Fields[K] | undefined;\n }\n\n get<K extends keyof Fields>(field: K): Fields[K] | undefined {\n return this.peek(field);\n }\n\n path<K extends keyof Fields>(field: K, keyPath: string[]): unknown {\n const base = this.peek(field);\n if (base === undefined) return undefined;\n if (keyPath.length === 0) return base;\n return getPath(base, keyPath.join(\".\"));\n }\n\n /** Public entry: a fresh resolution chain with no ancestors. */\n resolve<K extends keyof Fields>(field: K): Promise<Fields[K] | undefined> {\n return this.resolveWithin(field, EMPTY_ANCESTORS);\n }\n\n /**\n * Resolve `field` within a chain whose currently-resolving fields are\n * `ancestors`. If `field` is itself an ancestor, this is a dependency cycle\n * (mutual A<->B, longer A->B->C->A, or self x->x): return `undefined` so the\n * caller (e.g. a `strategies` candidate) falls through to its next route,\n * instead of awaiting its own still-pending promise (which would deadlock).\n *\n * The ancestor check runs BEFORE the `cache.has` short-circuit, so a CONCURRENT\n * caller (a parallel `Promise.all` chain — `field` is in-flight but NOT its\n * ancestor) still receives the shared pending promise and dedups normally.\n */\n private resolveWithin<K extends keyof Fields>(\n field: K,\n ancestors: ReadonlySet<keyof Fields>,\n ): Promise<Fields[K] | undefined> {\n // (0) Cycle guard: `field` is being resolved above me in THIS chain.\n if (ancestors.has(field)) {\n return Promise.resolve(undefined) as Promise<Fields[K] | undefined>;\n }\n\n // (1) Already attempted -> return the (possibly pending) cached promise.\n const cached = this.cache.get(field);\n if (cached !== undefined) return cached as Promise<Fields[K] | undefined>;\n\n const resolver = this.resolvers[field];\n // After an explicit invalidate, prefer the resolver over seed so `refresh`\n // pulls fresh adapter data instead of the stale seed value.\n const preferResolver = this.invalidated.has(field) && resolver !== undefined;\n\n // (2) Seed yields a value -> cache a resolved promise.\n const seeded = this.seed[field];\n if (seeded !== undefined && !preferResolver) {\n const p = Promise.resolve(seeded) as Promise<Fields[K] | undefined>;\n this.cache.set(field, p);\n return p;\n }\n\n // (3) A resolver exists -> store an in-flight promise BEFORE awaiting.\n if (resolver) {\n this.invalidated.delete(field);\n const deferred = defer<Fields[K] | undefined>();\n this.cache.set(field, deferred.promise);\n this.inProgress.add(field);\n // Child chain = my ancestors + me, so a transitive re-entry of `field` trips (0).\n const childAncestors = new Set(ancestors).add(field);\n this.runResolver(field, resolver, deferred, childAncestors);\n return deferred.promise;\n }\n\n // (4) No seed, no resolver -> terminal `undefined`.\n const p = Promise.resolve(undefined) as Promise<Fields[K] | undefined>;\n this.cache.set(field, p);\n return p;\n }\n\n /**\n * A context bound to `self` and the chain's `ancestors`: identical to the\n * top-level context except `resolve`/`fallback` carry the chain so cycles\n * break (see {@link resolveWithin}) and `fallback` retries the right field.\n * Binding per invocation (not shared instance state) keeps concurrent\n * resolutions from contaminating each other.\n */\n private boundContext<K extends keyof Fields>(\n self: K,\n ancestors: ReadonlySet<keyof Fields>,\n ): ResolverContext<Fields, TAdapter> {\n return {\n adapter: this.adapter,\n resolve: (field) => this.resolveWithin(field, ancestors),\n resolveMany: (...fields) => this.resolveMany(...fields),\n get: (field) => this.get(field),\n path: (field, keyPath) => this.path(field, keyPath),\n fallback: (deps) => this.fallbackFor(self, deps, ancestors),\n invalidate: (...fields) => this.invalidate(...fields),\n refresh: (field) => this.refresh(field),\n };\n }\n\n invalidate(...fields: Array<keyof Fields>): ResolverContext<Fields, TAdapter> {\n for (const field of fields) {\n this.cache.delete(field);\n this.settled.delete(field);\n // Only flag fields that have a resolver to prefer — a seed-only field has\n // nothing to prefer over seed, so flagging it would just leak a dead key.\n if (this.resolvers[field] !== undefined) this.invalidated.add(field);\n }\n return this;\n }\n\n refresh<K extends keyof Fields>(field: K): Promise<Fields[K] | undefined> {\n this.invalidate(field);\n return this.resolve(field);\n }\n\n /** Fire the best-effort `onResolve` observer; never let it break resolution. */\n private emitResolved(field: keyof Fields, value: unknown): void {\n if (!this.onResolve) return;\n try {\n this.onResolve(field, value);\n } catch {\n // observer is diagnostic only — swallow.\n }\n }\n\n private runResolver<K extends keyof Fields>(\n field: K,\n resolver: ResolverFn<Fields, TAdapter, K>,\n deferred: Deferred<Fields[K] | undefined>,\n ancestors: ReadonlySet<keyof Fields>,\n ): void {\n const ctx = this.boundContext(field, ancestors);\n Promise.resolve()\n .then(() => resolver(ctx))\n .then((value) => this.validate(field, value))\n .then(\n (value) => {\n this.settled.set(field, value);\n // Settled fields are guarded by the durable cache; drop the transient flag.\n this.inProgress.delete(field);\n this.emitResolved(field, value);\n deferred.resolve(value);\n },\n (error) => {\n // (c) Throw/validation-failure -> evict so a later retry can re-run;\n // never cache as undefined, never serve unvalidated data.\n this.cache.delete(field);\n this.inProgress.delete(field);\n deferred.reject(error);\n },\n );\n }\n\n /**\n * Validate a resolver's output against its field schema (if any). A defined\n * value is parsed (unknown keys stripped); a parse failure throws the ZodError,\n * which the reject branch turns into an eviction. `undefined` is the forgiving\n * absence terminal and is never validated.\n */\n private async validate<K extends keyof Fields>(\n field: K,\n value: Fields[K] | undefined,\n ): Promise<Fields[K] | undefined> {\n const schema = this.schemas[field];\n if (schema === undefined || value === undefined) return value;\n return (await schema.parseAsync(value)) as Fields[K];\n }\n\n /** Resolve untouched `deps`, then retry `self`'s resolver if any produced a value. */\n private async fallbackFor<K extends keyof Fields>(\n self: K,\n deps: Array<keyof Fields>,\n ancestors: ReadonlySet<keyof Fields>,\n ): Promise<unknown> {\n let resolvedAny = false;\n for (const dep of deps) {\n // (b) touched-check: skip deps already cached or in progress. This is\n // `fallback`'s own cycle guard and is intentionally SEPARATE from\n // `resolveWithin`'s step-0 ancestor check (which guards direct `c.resolve`\n // re-entry) — both entry points need their own guard; do not merge them.\n if (this.cache.has(dep) || this.inProgress.has(dep)) continue;\n const value = await this.resolveWithin(dep, ancestors);\n if (value) resolvedAny = true;\n }\n if (!resolvedAny) return undefined;\n // (d) Retry the calling field by re-invoking its resolver fn directly with a\n // context still bound to `self` (so nested fallbacks retry the right field).\n // `fallbackFor` is only reachable from a field-bound context built in\n // runResolver, which requires a resolver — so `self`'s resolver exists.\n const resolver = this.resolvers[self] as ResolverFn<Fields, TAdapter, K>;\n return resolver(this.boundContext(self, ancestors));\n }\n\n /**\n * Top-level `fallback` has no calling field, so there is nothing to retry.\n * Resolvers always receive a field-bound context whose `fallback` does retry;\n * this entry exists only to satisfy the public {@link ResolverContext} shape.\n */\n fallback(_deps: Array<keyof Fields>): Promise<unknown> {\n return Promise.resolve(undefined);\n }\n\n async resolveMany<K extends keyof Fields>(\n ...fields: K[]\n ): Promise<Partial<Pick<Fields, K>>> {\n // No reset needed: each field clears its own inProgress flag when it settles,\n // and the durable cache is the real guard — safe under concurrent callers.\n const out: Partial<Pick<Fields, K>> = {};\n for (const field of fields) {\n const value = await this.resolve(field);\n if (value !== undefined) out[field] = value as Fields[K];\n }\n return out;\n }\n}\n","/**\n * `createResolver` — factory for a dependency-aware, memoizing, cycle-guarded\n * resolver graph with an injected generic fetch adapter. See {@link GraphContext}\n * for the resolution algorithm and its invariants.\n *\n * Two call styles:\n * 1. **Hand-written fields** — `createResolver<Fields, Adapter>({ resolvers, ... })`.\n * Fields are statically typed; resolver outputs are NOT validated at runtime.\n * 2. **Schema-driven** — `createResolver({ maps, fields?, resolvers, ... })`.\n * Resolvable fields and their Zod schemas are derived from `createMap`\n * instances (`maps`) plus an optional scalar-dep `fields` object; each\n * resolver's output is parsed (unknown keys stripped) before caching.\n *\n * Field names that collide with JavaScript object internals (`__proto__`,\n * `constructor`, `prototype`) are not supported — the `resolvers`/`seed`\n * registries are plain objects, so such keys do not register as own properties.\n * Use ordinary identifier field names.\n */\nimport type { z } from \"zod\";\nimport {\n GraphContext,\n type ResolverConfig,\n type ResolverContext,\n type ResolverRegistry,\n type ResolverSchemas,\n} from \"./context.js\";\n\n/** Minimal structural view of a `createMap` instance (avoids a hard dep cycle). */\nexport interface SchemaSource<T = unknown> {\n readonly schema: z.ZodType<T>;\n}\n\n/** Field set derived from a map of {@link SchemaSource}s (each map's inferred DTO). */\ntype FieldsFromMaps<M extends Record<string, SchemaSource>> = {\n [K in keyof M]: M[K] extends SchemaSource<infer T> ? T : never;\n};\n\n/** Field set derived from an optional scalar `fields` object schema. */\ntype FieldsFromSchema<F extends z.ZodObject<any> | undefined> = F extends z.ZodObject<any>\n ? z.infer<F>\n : Record<never, never>;\n\n/** Merged resolvable field set: mapped DTO fields + scalar dep fields. */\nexport type SchemaResolverFields<\n M extends Record<string, SchemaSource>,\n F extends z.ZodObject<any> | undefined,\n> = FieldsFromMaps<M> & FieldsFromSchema<F>;\n\n/** Config for the schema-driven `createResolver` overload. */\nexport interface SchemaResolverConfig<\n M extends Record<string, SchemaSource>,\n F extends z.ZodObject<any> | undefined,\n TAdapter,\n> {\n adapter: TAdapter;\n /** `createMap` instances whose dest schemas define validated DTO fields. */\n maps: M;\n /** Optional object schema for scalar dependency fields (e.g. `orgId`). */\n fields?: F;\n seed?: Partial<SchemaResolverFields<M, F>>;\n resolvers?: ResolverRegistry<SchemaResolverFields<M, F>, TAdapter>;\n}\n\n// Hand-written-fields overload (validation off unless schemas supplied).\nexport function createResolver<Fields, TAdapter>(\n config: ResolverConfig<Fields, TAdapter>,\n): ResolverContext<Fields, TAdapter>;\n\n// Schema-driven overload: fields inferred from maps + scalar fields; outputs validated.\nexport function createResolver<\n M extends Record<string, SchemaSource>,\n TAdapter,\n F extends z.ZodObject<any> | undefined = undefined,\n>(\n config: SchemaResolverConfig<M, F, TAdapter>,\n): ResolverContext<SchemaResolverFields<M, F>, TAdapter>;\n\nexport function createResolver(\n config: ResolverConfig<unknown, unknown> & {\n maps?: Record<string, SchemaSource>;\n fields?: z.ZodObject<any>;\n },\n): ResolverContext<unknown, unknown> {\n const schemas = config.maps ? buildSchemas(config.maps, config.fields) : config.schemas;\n return new GraphContext<unknown, unknown>({\n adapter: config.adapter,\n seed: config.seed,\n resolvers: config.resolvers,\n schemas,\n onResolve: config.onResolve,\n });\n}\n\n/** Build the runtime `field -> Zod schema` lookup from maps + scalar fields. */\nfunction buildSchemas(\n maps: Record<string, SchemaSource>,\n fields: z.ZodObject<any> | undefined,\n): ResolverSchemas<Record<string, unknown>> {\n // Null-prototype map so the schema lookup is a clean own-key store. (Field\n // names that collide with object internals like `__proto__` are not a\n // supported case anyway — see the createResolver docs — but a null proto\n // keeps this lookup itself free of prototype-chain surprises.)\n const out: Record<string, z.ZodType> = Object.create(null);\n for (const key of Object.keys(maps)) {\n out[key] = maps[key]!.schema;\n }\n if (fields) {\n const shape = fields.shape as Record<string, z.ZodType>;\n for (const key of Object.keys(shape)) {\n out[key] = shape[key]!;\n }\n }\n return out;\n}\n\nexport type { ResolverConfig, ResolverContext };\nexport type { ResolverFn, ResolverRegistry, ResolverSchemas } from \"./context.js\";\n"]}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
// src/serialize/json-types.ts
|
|
2
|
+
var CodecError = class extends Error {
|
|
3
|
+
/** Path to the offending value, e.g. `user.createdAt`. */
|
|
4
|
+
path;
|
|
5
|
+
constructor(message, path = []) {
|
|
6
|
+
const where = path.length ? ` (at ${path.join(".")})` : "";
|
|
7
|
+
super(`${message}${where}`);
|
|
8
|
+
this.name = "CodecError";
|
|
9
|
+
this.path = path;
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
var AsyncSchemaError = class extends Error {
|
|
13
|
+
constructor() {
|
|
14
|
+
super(
|
|
15
|
+
"deserialize is sync-only; this schema has async refinements. Remove async .refine()/.superRefine() or validate separately with parseAsync."
|
|
16
|
+
);
|
|
17
|
+
this.name = "AsyncSchemaError";
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
var LIMITS = {
|
|
21
|
+
/** Max characters in a BigInt wire string (sign + digits). */
|
|
22
|
+
bigintStringLength: 4096,
|
|
23
|
+
/** Max entries when rebuilding a Map from a wire array. */
|
|
24
|
+
mapEntries: 1e5
|
|
25
|
+
};
|
|
26
|
+
function dateToIso(value, path) {
|
|
27
|
+
if (!(value instanceof Date) || Number.isNaN(value.getTime())) {
|
|
28
|
+
throw new CodecError("expected a valid Date", path);
|
|
29
|
+
}
|
|
30
|
+
return value.toISOString();
|
|
31
|
+
}
|
|
32
|
+
function isoToDate(value, path) {
|
|
33
|
+
if (typeof value !== "string") {
|
|
34
|
+
throw new CodecError("expected an ISO date string", path);
|
|
35
|
+
}
|
|
36
|
+
const ms = Date.parse(value);
|
|
37
|
+
if (Number.isNaN(ms)) {
|
|
38
|
+
throw new CodecError("invalid ISO date string", path);
|
|
39
|
+
}
|
|
40
|
+
return new Date(ms);
|
|
41
|
+
}
|
|
42
|
+
function bigintToString(value, path) {
|
|
43
|
+
if (typeof value !== "bigint") {
|
|
44
|
+
throw new CodecError("expected a bigint", path);
|
|
45
|
+
}
|
|
46
|
+
return value.toString();
|
|
47
|
+
}
|
|
48
|
+
function stringToBigint(value, path) {
|
|
49
|
+
if (typeof value !== "string") {
|
|
50
|
+
throw new CodecError("expected a bigint string", path);
|
|
51
|
+
}
|
|
52
|
+
if (value.length > LIMITS.bigintStringLength) {
|
|
53
|
+
throw new CodecError(
|
|
54
|
+
`bigint string exceeds ${LIMITS.bigintStringLength} chars`,
|
|
55
|
+
path
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
if (!/^-?\d+$/.test(value)) {
|
|
59
|
+
throw new CodecError("malformed bigint string", path);
|
|
60
|
+
}
|
|
61
|
+
return BigInt(value);
|
|
62
|
+
}
|
|
63
|
+
function mapToEntries(value, path) {
|
|
64
|
+
if (!(value instanceof Map)) {
|
|
65
|
+
throw new CodecError("expected a Map", path);
|
|
66
|
+
}
|
|
67
|
+
return Array.from(value.entries());
|
|
68
|
+
}
|
|
69
|
+
function entriesToArray(value, path) {
|
|
70
|
+
if (!Array.isArray(value)) {
|
|
71
|
+
throw new CodecError("expected a Map entries array", path);
|
|
72
|
+
}
|
|
73
|
+
if (value.length > LIMITS.mapEntries) {
|
|
74
|
+
throw new CodecError(`Map entries exceed ${LIMITS.mapEntries}`, path);
|
|
75
|
+
}
|
|
76
|
+
for (const entry of value) {
|
|
77
|
+
if (!Array.isArray(entry) || entry.length !== 2) {
|
|
78
|
+
throw new CodecError("malformed Map entry (expected [key, value])", path);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return value;
|
|
82
|
+
}
|
|
83
|
+
function setToArray(value, path) {
|
|
84
|
+
if (!(value instanceof Set)) {
|
|
85
|
+
throw new CodecError("expected a Set", path);
|
|
86
|
+
}
|
|
87
|
+
return Array.from(value.values());
|
|
88
|
+
}
|
|
89
|
+
function arrayToSetItems(value, path) {
|
|
90
|
+
if (!Array.isArray(value)) {
|
|
91
|
+
throw new CodecError("expected a Set values array", path);
|
|
92
|
+
}
|
|
93
|
+
if (value.length > LIMITS.mapEntries) {
|
|
94
|
+
throw new CodecError(`Set values exceed ${LIMITS.mapEntries}`, path);
|
|
95
|
+
}
|
|
96
|
+
return value;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// src/serialize/codec.ts
|
|
100
|
+
function defOf(schema) {
|
|
101
|
+
return schema._zod.def;
|
|
102
|
+
}
|
|
103
|
+
function typeOf(schema) {
|
|
104
|
+
return defOf(schema).type;
|
|
105
|
+
}
|
|
106
|
+
function unwrap(schema) {
|
|
107
|
+
let current = schema;
|
|
108
|
+
for (; ; ) {
|
|
109
|
+
const def = defOf(current);
|
|
110
|
+
switch (def.type) {
|
|
111
|
+
case "optional":
|
|
112
|
+
case "nullable":
|
|
113
|
+
case "default":
|
|
114
|
+
case "nonoptional":
|
|
115
|
+
case "readonly":
|
|
116
|
+
case "catch":
|
|
117
|
+
current = def.innerType;
|
|
118
|
+
break;
|
|
119
|
+
case "pipe":
|
|
120
|
+
current = def.in;
|
|
121
|
+
break;
|
|
122
|
+
case "lazy":
|
|
123
|
+
current = def.getter();
|
|
124
|
+
break;
|
|
125
|
+
default:
|
|
126
|
+
return current;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function walk(schema, value, dir, path) {
|
|
131
|
+
if (value === null || value === void 0) return value;
|
|
132
|
+
const node = unwrap(schema);
|
|
133
|
+
const def = defOf(node);
|
|
134
|
+
switch (def.type) {
|
|
135
|
+
case "object": {
|
|
136
|
+
if (typeof value !== "object" || Array.isArray(value)) {
|
|
137
|
+
throw new CodecError("expected an object", path);
|
|
138
|
+
}
|
|
139
|
+
const shape = def.shape;
|
|
140
|
+
const src = value;
|
|
141
|
+
const out = {};
|
|
142
|
+
for (const key of Object.keys(shape)) {
|
|
143
|
+
if (Object.prototype.hasOwnProperty.call(src, key)) {
|
|
144
|
+
out[key] = walk(shape[key], src[key], dir, [...path, key]);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return out;
|
|
148
|
+
}
|
|
149
|
+
case "array": {
|
|
150
|
+
if (!Array.isArray(value)) throw new CodecError("expected an array", path);
|
|
151
|
+
const element = def.element;
|
|
152
|
+
return value.map((item, i) => walk(element, item, dir, [...path, i]));
|
|
153
|
+
}
|
|
154
|
+
case "map": {
|
|
155
|
+
const keyType = def.keyType;
|
|
156
|
+
const valueType = def.valueType;
|
|
157
|
+
if (dir === "serialize") {
|
|
158
|
+
const entries2 = mapToEntries(value, path);
|
|
159
|
+
return entries2.map(([k, v], i) => [
|
|
160
|
+
walk(keyType, k, dir, [...path, i, "key"]),
|
|
161
|
+
walk(valueType, v, dir, [...path, i, "value"])
|
|
162
|
+
]);
|
|
163
|
+
}
|
|
164
|
+
const entries = entriesToArray(value, path);
|
|
165
|
+
const out = /* @__PURE__ */ new Map();
|
|
166
|
+
entries.forEach(([k, v], i) => {
|
|
167
|
+
out.set(
|
|
168
|
+
walk(keyType, k, dir, [...path, i, "key"]),
|
|
169
|
+
walk(valueType, v, dir, [...path, i, "value"])
|
|
170
|
+
);
|
|
171
|
+
});
|
|
172
|
+
return out;
|
|
173
|
+
}
|
|
174
|
+
case "set": {
|
|
175
|
+
const valueType = def.valueType;
|
|
176
|
+
if (dir === "serialize") {
|
|
177
|
+
const items2 = setToArray(value, path);
|
|
178
|
+
return items2.map((item, i) => walk(valueType, item, dir, [...path, i]));
|
|
179
|
+
}
|
|
180
|
+
const items = arrayToSetItems(value, path);
|
|
181
|
+
return new Set(items.map((item, i) => walk(valueType, item, dir, [...path, i])));
|
|
182
|
+
}
|
|
183
|
+
case "union": {
|
|
184
|
+
const options = def.options;
|
|
185
|
+
let lastError = new CodecError("no union member matched", path);
|
|
186
|
+
for (const option of options) {
|
|
187
|
+
try {
|
|
188
|
+
return walk(option, value, dir, path);
|
|
189
|
+
} catch (err) {
|
|
190
|
+
if (!(err instanceof CodecError)) throw err;
|
|
191
|
+
lastError = err;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
throw lastError;
|
|
195
|
+
}
|
|
196
|
+
case "date":
|
|
197
|
+
return dir === "serialize" ? dateToIso(value, path) : isoToDate(value, path);
|
|
198
|
+
case "bigint":
|
|
199
|
+
return dir === "serialize" ? bigintToString(value, path) : stringToBigint(value, path);
|
|
200
|
+
// Scalars and explicit escape hatches pass through untouched.
|
|
201
|
+
case "string":
|
|
202
|
+
case "number":
|
|
203
|
+
case "boolean":
|
|
204
|
+
case "nan":
|
|
205
|
+
case "null":
|
|
206
|
+
case "undefined":
|
|
207
|
+
case "literal":
|
|
208
|
+
case "enum":
|
|
209
|
+
case "any":
|
|
210
|
+
case "unknown":
|
|
211
|
+
case "void":
|
|
212
|
+
case "custom":
|
|
213
|
+
return value;
|
|
214
|
+
default:
|
|
215
|
+
throw new CodecError(`unsupported schema node "${def.type}"`, path);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
function serialize(value, schema) {
|
|
219
|
+
return walk(schema, value, "serialize", []);
|
|
220
|
+
}
|
|
221
|
+
function isZodAsyncError(err) {
|
|
222
|
+
const name = err?.constructor?.name;
|
|
223
|
+
return name === "$ZodAsyncError";
|
|
224
|
+
}
|
|
225
|
+
function deserialize(json, schema) {
|
|
226
|
+
const rebuilt = walk(schema, json, "deserialize", []);
|
|
227
|
+
try {
|
|
228
|
+
return schema.parse(rebuilt);
|
|
229
|
+
} catch (err) {
|
|
230
|
+
if (isZodAsyncError(err)) throw new AsyncSchemaError();
|
|
231
|
+
throw err;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
async function deserializeAsync(json, schema) {
|
|
235
|
+
const rebuilt = walk(schema, json, "deserialize", []);
|
|
236
|
+
return await schema.parseAsync(rebuilt);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export { AsyncSchemaError, CodecError, LIMITS, arrayToSetItems, bigintToString, dateToIso, deserialize, deserializeAsync, entriesToArray, isoToDate, mapToEntries, serialize, setToArray, stringToBigint, typeOf, unwrap };
|
|
240
|
+
//# sourceMappingURL=chunk-U3W6PGFE.js.map
|
|
241
|
+
//# sourceMappingURL=chunk-U3W6PGFE.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/serialize/json-types.ts","../src/serialize/codec.ts"],"names":["entries","items"],"mappings":";AAQO,IAAM,UAAA,GAAN,cAAyB,KAAA,CAAM;AAAA;AAAA,EAE3B,IAAA;AAAA,EACT,WAAA,CAAY,OAAA,EAAiB,IAAA,GAAuC,EAAC,EAAG;AACtE,IAAA,MAAM,KAAA,GAAQ,KAAK,MAAA,GAAS,CAAA,KAAA,EAAQ,KAAK,IAAA,CAAK,GAAG,CAAC,CAAA,CAAA,CAAA,GAAM,EAAA;AACxD,IAAA,KAAA,CAAM,CAAA,EAAG,OAAO,CAAA,EAAG,KAAK,CAAA,CAAE,CAAA;AAC1B,IAAA,IAAA,CAAK,IAAA,GAAO,YAAA;AACZ,IAAA,IAAA,CAAK,IAAA,GAAO,IAAA;AAAA,EACd;AACF;AAGO,IAAM,gBAAA,GAAN,cAA+B,KAAA,CAAM;AAAA,EAC1C,WAAA,GAAc;AACZ,IAAA,KAAA;AAAA,MACE;AAAA,KAEF;AACA,IAAA,IAAA,CAAK,IAAA,GAAO,kBAAA;AAAA,EACd;AACF;AAOO,IAAM,MAAA,GAAS;AAAA;AAAA,EAEpB,kBAAA,EAAoB,IAAA;AAAA;AAAA,EAEpB,UAAA,EAAY;AACd;AAIO,SAAS,SAAA,CAAU,OAAa,IAAA,EAA8C;AACnF,EAAA,IAAI,EAAE,iBAAiB,IAAA,CAAA,IAAS,MAAA,CAAO,MAAM,KAAA,CAAM,OAAA,EAAS,CAAA,EAAG;AAC7D,IAAA,MAAM,IAAI,UAAA,CAAW,uBAAA,EAAyB,IAAI,CAAA;AAAA,EACpD;AACA,EAAA,OAAO,MAAM,WAAA,EAAY;AAC3B;AAEO,SAAS,SAAA,CAAU,OAAgB,IAAA,EAA4C;AACpF,EAAA,IAAI,OAAO,UAAU,QAAA,EAAU;AAC7B,IAAA,MAAM,IAAI,UAAA,CAAW,6BAAA,EAA+B,IAAI,CAAA;AAAA,EAC1D;AACA,EAAA,MAAM,EAAA,GAAK,IAAA,CAAK,KAAA,CAAM,KAAK,CAAA;AAC3B,EAAA,IAAI,MAAA,CAAO,KAAA,CAAM,EAAE,CAAA,EAAG;AACpB,IAAA,MAAM,IAAI,UAAA,CAAW,yBAAA,EAA2B,IAAI,CAAA;AAAA,EACtD;AACA,EAAA,OAAO,IAAI,KAAK,EAAE,CAAA;AACpB;AAIO,SAAS,cAAA,CAAe,OAAe,IAAA,EAA8C;AAC1F,EAAA,IAAI,OAAO,UAAU,QAAA,EAAU;AAC7B,IAAA,MAAM,IAAI,UAAA,CAAW,mBAAA,EAAqB,IAAI,CAAA;AAAA,EAChD;AACA,EAAA,OAAO,MAAM,QAAA,EAAS;AACxB;AAEO,SAAS,cAAA,CAAe,OAAgB,IAAA,EAA8C;AAC3F,EAAA,IAAI,OAAO,UAAU,QAAA,EAAU;AAC7B,IAAA,MAAM,IAAI,UAAA,CAAW,0BAAA,EAA4B,IAAI,CAAA;AAAA,EACvD;AACA,EAAA,IAAI,KAAA,CAAM,MAAA,GAAS,MAAA,CAAO,kBAAA,EAAoB;AAC5C,IAAA,MAAM,IAAI,UAAA;AAAA,MACR,CAAA,sBAAA,EAAyB,OAAO,kBAAkB,CAAA,MAAA,CAAA;AAAA,MAClD;AAAA,KACF;AAAA,EACF;AACA,EAAA,IAAI,CAAC,SAAA,CAAU,IAAA,CAAK,KAAK,CAAA,EAAG;AAC1B,IAAA,MAAM,IAAI,UAAA,CAAW,yBAAA,EAA2B,IAAI,CAAA;AAAA,EACtD;AACA,EAAA,OAAO,OAAO,KAAK,CAAA;AACrB;AAIO,SAAS,YAAA,CACd,OACA,IAAA,EAC2B;AAC3B,EAAA,IAAI,EAAE,iBAAiB,GAAA,CAAA,EAAM;AAC3B,IAAA,MAAM,IAAI,UAAA,CAAW,gBAAA,EAAkB,IAAI,CAAA;AAAA,EAC7C;AACA,EAAA,OAAO,KAAA,CAAM,IAAA,CAAK,KAAA,CAAM,OAAA,EAAS,CAAA;AACnC;AAEO,SAAS,cAAA,CACd,OACA,IAAA,EAC2B;AAC3B,EAAA,IAAI,CAAC,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,EAAG;AACzB,IAAA,MAAM,IAAI,UAAA,CAAW,8BAAA,EAAgC,IAAI,CAAA;AAAA,EAC3D;AACA,EAAA,IAAI,KAAA,CAAM,MAAA,GAAS,MAAA,CAAO,UAAA,EAAY;AACpC,IAAA,MAAM,IAAI,UAAA,CAAW,CAAA,mBAAA,EAAsB,MAAA,CAAO,UAAU,IAAI,IAAI,CAAA;AAAA,EACtE;AACA,EAAA,KAAA,MAAW,SAAS,KAAA,EAAO;AACzB,IAAA,IAAI,CAAC,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,IAAK,KAAA,CAAM,WAAW,CAAA,EAAG;AAC/C,MAAA,MAAM,IAAI,UAAA,CAAW,6CAAA,EAA+C,IAAI,CAAA;AAAA,IAC1E;AAAA,EACF;AACA,EAAA,OAAO,KAAA;AACT;AAIO,SAAS,UAAA,CACd,OACA,IAAA,EACW;AACX,EAAA,IAAI,EAAE,iBAAiB,GAAA,CAAA,EAAM;AAC3B,IAAA,MAAM,IAAI,UAAA,CAAW,gBAAA,EAAkB,IAAI,CAAA;AAAA,EAC7C;AACA,EAAA,OAAO,KAAA,CAAM,IAAA,CAAK,KAAA,CAAM,MAAA,EAAQ,CAAA;AAClC;AAEO,SAAS,eAAA,CACd,OACA,IAAA,EACW;AACX,EAAA,IAAI,CAAC,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,EAAG;AACzB,IAAA,MAAM,IAAI,UAAA,CAAW,6BAAA,EAA+B,IAAI,CAAA;AAAA,EAC1D;AACA,EAAA,IAAI,KAAA,CAAM,MAAA,GAAS,MAAA,CAAO,UAAA,EAAY;AACpC,IAAA,MAAM,IAAI,UAAA,CAAW,CAAA,kBAAA,EAAqB,MAAA,CAAO,UAAU,IAAI,IAAI,CAAA;AAAA,EACrE;AACA,EAAA,OAAO,KAAA;AACT;;;ACjGA,SAAS,MAAM,MAAA,EAAwB;AACrC,EAAA,OAAQ,OAAgD,IAAA,CAAK,GAAA;AAC/D;AAGO,SAAS,OAAO,MAAA,EAAwB;AAC7C,EAAA,OAAO,KAAA,CAAM,MAAM,CAAA,CAAE,IAAA;AACvB;AAQO,SAAS,OAAO,MAAA,EAAwB;AAC7C,EAAA,IAAI,OAAA,GAAU,MAAA;AAEd,EAAA,WAAS;AACP,IAAA,MAAM,GAAA,GAAM,MAAM,OAAO,CAAA;AACzB,IAAA,QAAQ,IAAI,IAAA;AAAM,MAChB,KAAK,UAAA;AAAA,MACL,KAAK,UAAA;AAAA,MACL,KAAK,SAAA;AAAA,MACL,KAAK,aAAA;AAAA,MACL,KAAK,UAAA;AAAA,MACL,KAAK,OAAA;AACH,QAAA,OAAA,GAAU,GAAA,CAAI,SAAA;AACd,QAAA;AAAA,MACF,KAAK,MAAA;AAGH,QAAA,OAAA,GAAU,GAAA,CAAI,EAAA;AACd,QAAA;AAAA,MACF,KAAK,MAAA;AACH,QAAA,OAAA,GAAW,IAAI,MAAA,EAAwB;AACvC,QAAA;AAAA,MACF;AACE,QAAA,OAAO,OAAA;AAAA;AACX,EACF;AACF;AAEA,SAAS,IAAA,CAAK,MAAA,EAAgB,KAAA,EAAgB,GAAA,EAAgB,IAAA,EAAqB;AACjF,EAAA,IAAI,KAAA,KAAU,IAAA,IAAQ,KAAA,KAAU,MAAA,EAAW,OAAO,KAAA;AAElD,EAAA,MAAM,IAAA,GAAO,OAAO,MAAM,CAAA;AAC1B,EAAA,MAAM,GAAA,GAAM,MAAM,IAAI,CAAA;AAEtB,EAAA,QAAQ,IAAI,IAAA;AAAM,IAChB,KAAK,QAAA,EAAU;AACb,MAAA,IAAI,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,EAAG;AACrD,QAAA,MAAM,IAAI,UAAA,CAAW,oBAAA,EAAsB,IAAI,CAAA;AAAA,MACjD;AACA,MAAA,MAAM,QAAQ,GAAA,CAAI,KAAA;AAClB,MAAA,MAAM,GAAA,GAAM,KAAA;AACZ,MAAA,MAAM,MAA+B,EAAC;AACtC,MAAA,KAAA,MAAW,GAAA,IAAO,MAAA,CAAO,IAAA,CAAK,KAAK,CAAA,EAAG;AACpC,QAAA,IAAI,OAAO,SAAA,CAAU,cAAA,CAAe,IAAA,CAAK,GAAA,EAAK,GAAG,CAAA,EAAG;AAClD,UAAA,GAAA,CAAI,GAAG,CAAA,GAAI,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA,EAAa,GAAA,CAAI,GAAG,CAAA,EAAG,GAAA,EAAK,CAAC,GAAG,IAAA,EAAM,GAAG,CAAC,CAAA;AAAA,QACrE;AAAA,MACF;AACA,MAAA,OAAO,GAAA;AAAA,IACT;AAAA,IAEA,KAAK,OAAA,EAAS;AACZ,MAAA,IAAI,CAAC,MAAM,OAAA,CAAQ,KAAK,GAAG,MAAM,IAAI,UAAA,CAAW,mBAAA,EAAqB,IAAI,CAAA;AACzE,MAAA,MAAM,UAAU,GAAA,CAAI,OAAA;AACpB,MAAA,OAAO,KAAA,CAAM,GAAA,CAAI,CAAC,IAAA,EAAM,MAAM,IAAA,CAAK,OAAA,EAAS,IAAA,EAAM,GAAA,EAAK,CAAC,GAAG,IAAA,EAAM,CAAC,CAAC,CAAC,CAAA;AAAA,IACtE;AAAA,IAEA,KAAK,KAAA,EAAO;AACV,MAAA,MAAM,UAAU,GAAA,CAAI,OAAA;AACpB,MAAA,MAAM,YAAY,GAAA,CAAI,SAAA;AACtB,MAAA,IAAI,QAAQ,WAAA,EAAa;AACvB,QAAA,MAAMA,QAAAA,GAAU,YAAA,CAAa,KAAA,EAAgC,IAAI,CAAA;AACjE,QAAA,OAAOA,SAAQ,GAAA,CAAI,CAAC,CAAC,CAAA,EAAG,CAAC,GAAG,CAAA,KAAM;AAAA,UAChC,IAAA,CAAK,SAAS,CAAA,EAAG,GAAA,EAAK,CAAC,GAAG,IAAA,EAAM,CAAA,EAAG,KAAK,CAAC,CAAA;AAAA,UACzC,IAAA,CAAK,WAAW,CAAA,EAAG,GAAA,EAAK,CAAC,GAAG,IAAA,EAAM,CAAA,EAAG,OAAO,CAAC;AAAA,SAC9C,CAAA;AAAA,MACH;AACA,MAAA,MAAM,OAAA,GAAU,cAAA,CAAe,KAAA,EAAO,IAAI,CAAA;AAC1C,MAAA,MAAM,GAAA,uBAAU,GAAA,EAAsB;AACtC,MAAA,OAAA,CAAQ,QAAQ,CAAC,CAAC,CAAA,EAAG,CAAC,GAAG,CAAA,KAAM;AAC7B,QAAA,GAAA,CAAI,GAAA;AAAA,UACF,IAAA,CAAK,SAAS,CAAA,EAAG,GAAA,EAAK,CAAC,GAAG,IAAA,EAAM,CAAA,EAAG,KAAK,CAAC,CAAA;AAAA,UACzC,IAAA,CAAK,WAAW,CAAA,EAAG,GAAA,EAAK,CAAC,GAAG,IAAA,EAAM,CAAA,EAAG,OAAO,CAAC;AAAA,SAC/C;AAAA,MACF,CAAC,CAAA;AACD,MAAA,OAAO,GAAA;AAAA,IACT;AAAA,IAEA,KAAK,KAAA,EAAO;AACV,MAAA,MAAM,YAAY,GAAA,CAAI,SAAA;AACtB,MAAA,IAAI,QAAQ,WAAA,EAAa;AACvB,QAAA,MAAMC,MAAAA,GAAQ,UAAA,CAAW,KAAA,EAAuB,IAAI,CAAA;AACpD,QAAA,OAAOA,MAAAA,CAAM,GAAA,CAAI,CAAC,IAAA,EAAM,MAAM,IAAA,CAAK,SAAA,EAAW,IAAA,EAAM,GAAA,EAAK,CAAC,GAAG,IAAA,EAAM,CAAC,CAAC,CAAC,CAAA;AAAA,MACxE;AACA,MAAA,MAAM,KAAA,GAAQ,eAAA,CAAgB,KAAA,EAAO,IAAI,CAAA;AACzC,MAAA,OAAO,IAAI,GAAA,CAAI,KAAA,CAAM,GAAA,CAAI,CAAC,MAAM,CAAA,KAAM,IAAA,CAAK,SAAA,EAAW,IAAA,EAAM,KAAK,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAA;AAAA,IACjF;AAAA,IAEA,KAAK,OAAA,EAAS;AAKZ,MAAA,MAAM,UAAU,GAAA,CAAI,OAAA;AACpB,MAAA,IAAI,SAAA,GAAwB,IAAI,UAAA,CAAW,yBAAA,EAA2B,IAAI,CAAA;AAC1E,MAAA,KAAA,MAAW,UAAU,OAAA,EAAS;AAC5B,QAAA,IAAI;AACF,UAAA,OAAO,IAAA,CAAK,MAAA,EAAQ,KAAA,EAAO,GAAA,EAAK,IAAI,CAAA;AAAA,QACtC,SAAS,GAAA,EAAK;AACZ,UAAA,IAAI,EAAE,GAAA,YAAe,UAAA,CAAA,EAAa,MAAM,GAAA;AACxC,UAAA,SAAA,GAAY,GAAA;AAAA,QACd;AAAA,MACF;AACA,MAAA,MAAM,SAAA;AAAA,IACR;AAAA,IAEA,KAAK,MAAA;AACH,MAAA,OAAO,GAAA,KAAQ,cACX,SAAA,CAAU,KAAA,EAAe,IAAI,CAAA,GAC7B,SAAA,CAAU,OAAO,IAAI,CAAA;AAAA,IAE3B,KAAK,QAAA;AACH,MAAA,OAAO,GAAA,KAAQ,cACX,cAAA,CAAe,KAAA,EAAiB,IAAI,CAAA,GACpC,cAAA,CAAe,OAAO,IAAI,CAAA;AAAA;AAAA,IAGhC,KAAK,QAAA;AAAA,IACL,KAAK,QAAA;AAAA,IACL,KAAK,SAAA;AAAA,IACL,KAAK,KAAA;AAAA,IACL,KAAK,MAAA;AAAA,IACL,KAAK,WAAA;AAAA,IACL,KAAK,SAAA;AAAA,IACL,KAAK,MAAA;AAAA,IACL,KAAK,KAAA;AAAA,IACL,KAAK,SAAA;AAAA,IACL,KAAK,MAAA;AAAA,IACL,KAAK,QAAA;AACH,MAAA,OAAO,KAAA;AAAA,IAET;AAGE,MAAA,MAAM,IAAI,UAAA,CAAW,CAAA,yBAAA,EAA4B,GAAA,CAAI,IAAI,KAAK,IAAI,CAAA;AAAA;AAExE;AAQO,SAAS,SAAA,CAA4B,OAAmB,MAAA,EAAoB;AACjF,EAAA,OAAO,IAAA,CAAK,MAAA,EAAQ,KAAA,EAAO,WAAA,EAAa,EAAE,CAAA;AAC5C;AAEA,SAAS,gBAAgB,GAAA,EAAuB;AAC9C,EAAA,MAAM,IAAA,GAAQ,KAAoD,WAAA,EAAa,IAAA;AAC/E,EAAA,OAAO,IAAA,KAAS,gBAAA;AAClB;AASO,SAAS,WAAA,CAA8B,MAAe,MAAA,EAAuB;AAClF,EAAA,MAAM,UAAU,IAAA,CAAK,MAAA,EAAQ,IAAA,EAAM,aAAA,EAAe,EAAE,CAAA;AACpD,EAAA,IAAI;AACF,IAAA,OAAO,MAAA,CAAO,MAAM,OAAO,CAAA;AAAA,EAC7B,SAAS,GAAA,EAAK;AACZ,IAAA,IAAI,eAAA,CAAgB,GAAG,CAAA,EAAG,MAAM,IAAI,gBAAA,EAAiB;AACrD,IAAA,MAAM,GAAA;AAAA,EACR;AACF;AAQA,eAAsB,gBAAA,CACpB,MACA,MAAA,EACqB;AACrB,EAAA,MAAM,UAAU,IAAA,CAAK,MAAA,EAAQ,IAAA,EAAM,aAAA,EAAe,EAAE,CAAA;AACpD,EAAA,OAAQ,MAAM,MAAA,CAAO,UAAA,CAAW,OAAO,CAAA;AACzC","file":"chunk-U3W6PGFE.js","sourcesContent":["/**\n * Leaf transforms between rich JS values and their JSON-safe wire forms.\n * Each inverse (wire -> JS) transform validates its input and throws a\n * {@link CodecError} on malformed data, so a hostile wire payload surfaces a\n * typed error instead of a raw `TypeError`/`SyntaxError` or an unbounded stall.\n */\n\n/** Thrown for malformed wire input or unsupported schema nodes during codec walk. */\nexport class CodecError extends Error {\n /** Path to the offending value, e.g. `user.createdAt`. */\n readonly path: ReadonlyArray<string | number>;\n constructor(message: string, path: ReadonlyArray<string | number> = []) {\n const where = path.length ? ` (at ${path.join(\".\")})` : \"\";\n super(`${message}${where}`);\n this.name = \"CodecError\";\n this.path = path;\n }\n}\n\n/** Thrown when `deserialize` is given a schema containing async refinements. */\nexport class AsyncSchemaError extends Error {\n constructor() {\n super(\n \"deserialize is sync-only; this schema has async refinements. \" +\n \"Remove async .refine()/.superRefine() or validate separately with parseAsync.\",\n );\n this.name = \"AsyncSchemaError\";\n }\n}\n\n/**\n * DoS guards on attacker-controlled wire input. A BigInt parsed from a very\n * long numeric string, or a Map rebuilt from a huge entries array, can stall\n * the event loop; these caps reject such input up front.\n */\nexport const LIMITS = {\n /** Max characters in a BigInt wire string (sign + digits). */\n bigintStringLength: 4096,\n /** Max entries when rebuilding a Map from a wire array. */\n mapEntries: 100_000,\n} as const;\n\n// --- Date <-> ISO string ---\n\nexport function dateToIso(value: Date, path: ReadonlyArray<string | number>): string {\n if (!(value instanceof Date) || Number.isNaN(value.getTime())) {\n throw new CodecError(\"expected a valid Date\", path);\n }\n return value.toISOString();\n}\n\nexport function isoToDate(value: unknown, path: ReadonlyArray<string | number>): Date {\n if (typeof value !== \"string\") {\n throw new CodecError(\"expected an ISO date string\", path);\n }\n const ms = Date.parse(value);\n if (Number.isNaN(ms)) {\n throw new CodecError(\"invalid ISO date string\", path);\n }\n return new Date(ms);\n}\n\n// --- BigInt <-> string ---\n\nexport function bigintToString(value: bigint, path: ReadonlyArray<string | number>): string {\n if (typeof value !== \"bigint\") {\n throw new CodecError(\"expected a bigint\", path);\n }\n return value.toString();\n}\n\nexport function stringToBigint(value: unknown, path: ReadonlyArray<string | number>): bigint {\n if (typeof value !== \"string\") {\n throw new CodecError(\"expected a bigint string\", path);\n }\n if (value.length > LIMITS.bigintStringLength) {\n throw new CodecError(\n `bigint string exceeds ${LIMITS.bigintStringLength} chars`,\n path,\n );\n }\n if (!/^-?\\d+$/.test(value)) {\n throw new CodecError(\"malformed bigint string\", path);\n }\n return BigInt(value);\n}\n\n// --- Map <-> entries array ---\n\nexport function mapToEntries(\n value: Map<unknown, unknown>,\n path: ReadonlyArray<string | number>,\n): Array<[unknown, unknown]> {\n if (!(value instanceof Map)) {\n throw new CodecError(\"expected a Map\", path);\n }\n return Array.from(value.entries());\n}\n\nexport function entriesToArray(\n value: unknown,\n path: ReadonlyArray<string | number>,\n): Array<[unknown, unknown]> {\n if (!Array.isArray(value)) {\n throw new CodecError(\"expected a Map entries array\", path);\n }\n if (value.length > LIMITS.mapEntries) {\n throw new CodecError(`Map entries exceed ${LIMITS.mapEntries}`, path);\n }\n for (const entry of value) {\n if (!Array.isArray(entry) || entry.length !== 2) {\n throw new CodecError(\"malformed Map entry (expected [key, value])\", path);\n }\n }\n return value as Array<[unknown, unknown]>;\n}\n\n// --- Set <-> array ---\n\nexport function setToArray(\n value: Set<unknown>,\n path: ReadonlyArray<string | number>,\n): unknown[] {\n if (!(value instanceof Set)) {\n throw new CodecError(\"expected a Set\", path);\n }\n return Array.from(value.values());\n}\n\nexport function arrayToSetItems(\n value: unknown,\n path: ReadonlyArray<string | number>,\n): unknown[] {\n if (!Array.isArray(value)) {\n throw new CodecError(\"expected a Set values array\", path);\n }\n if (value.length > LIMITS.mapEntries) {\n throw new CodecError(`Set values exceed ${LIMITS.mapEntries}`, path);\n }\n return value;\n}\n","/**\n * Schema-driven JSON-safe codec for Zod 4 schemas.\n *\n * `serialize` walks a value alongside its Zod schema and replaces rich runtime\n * types (Date, BigInt, Map) with JSON-safe representations; `deserialize` walks\n * the wire form back into rich values and then validates with Zod.\n *\n * Zod-4 only: all schema-shape reads go through {@link typeOf}/{@link unwrap},\n * which dispatch on the Zod 4 internal `_zod.def.type` (lowercase strings).\n * Zod 3's `_def.typeName` is intentionally unsupported.\n */\nimport type { z } from \"zod\";\nimport {\n AsyncSchemaError,\n CodecError,\n bigintToString,\n arrayToSetItems,\n dateToIso,\n entriesToArray,\n isoToDate,\n mapToEntries,\n setToArray,\n stringToBigint,\n} from \"./json-types.js\";\n\ntype AnyZod = z.ZodType;\ntype Path = ReadonlyArray<string | number>;\ntype Direction = \"serialize\" | \"deserialize\";\n\n// Minimal structural view of the Zod 4 internal def. Centralizes the one place\n// the library couples to `_zod.def`, so a Zod minor bump touches only this file.\ninterface ZodDef {\n type: string;\n innerType?: AnyZod;\n element?: AnyZod;\n shape?: Record<string, AnyZod>;\n keyType?: AnyZod;\n valueType?: AnyZod;\n options?: AnyZod[];\n getter?: () => AnyZod;\n in?: AnyZod;\n}\n\nfunction defOf(schema: AnyZod): ZodDef {\n return (schema as unknown as { _zod: { def: ZodDef } })._zod.def;\n}\n\n/** The Zod 4 node kind, e.g. `\"object\"`, `\"array\"`, `\"date\"`. */\nexport function typeOf(schema: AnyZod): string {\n return defOf(schema).type;\n}\n\n/**\n * Peel wrapper nodes that do not change the JSON-safe shape:\n * optional/nullable/default (innerType), pipe/transform (the `in` schema is the\n * wire-facing shape), and lazy (resolve the getter). Object `.refine()` in Zod\n * 4 stays a plain `object` node with its `shape` intact, so it needs no peeling.\n */\nexport function unwrap(schema: AnyZod): AnyZod {\n let current = schema;\n // Bounded by schema nesting depth; each step strictly descends.\n for (;;) {\n const def = defOf(current);\n switch (def.type) {\n case \"optional\":\n case \"nullable\":\n case \"default\":\n case \"nonoptional\":\n case \"readonly\":\n case \"catch\":\n current = def.innerType as AnyZod;\n break;\n case \"pipe\":\n // `.transform()` and `.pipe()` both produce a pipe; the `in` side is the\n // shape that wire data is validated against.\n current = def.in as AnyZod;\n break;\n case \"lazy\":\n current = (def.getter as () => AnyZod)();\n break;\n default:\n return current;\n }\n }\n}\n\nfunction walk(schema: AnyZod, value: unknown, dir: Direction, path: Path): unknown {\n if (value === null || value === undefined) return value;\n\n const node = unwrap(schema);\n const def = defOf(node);\n\n switch (def.type) {\n case \"object\": {\n if (typeof value !== \"object\" || Array.isArray(value)) {\n throw new CodecError(\"expected an object\", path);\n }\n const shape = def.shape as Record<string, AnyZod>;\n const src = value as Record<string, unknown>;\n const out: Record<string, unknown> = {};\n for (const key of Object.keys(shape)) {\n if (Object.prototype.hasOwnProperty.call(src, key)) {\n out[key] = walk(shape[key] as AnyZod, src[key], dir, [...path, key]);\n }\n }\n return out;\n }\n\n case \"array\": {\n if (!Array.isArray(value)) throw new CodecError(\"expected an array\", path);\n const element = def.element as AnyZod;\n return value.map((item, i) => walk(element, item, dir, [...path, i]));\n }\n\n case \"map\": {\n const keyType = def.keyType as AnyZod;\n const valueType = def.valueType as AnyZod;\n if (dir === \"serialize\") {\n const entries = mapToEntries(value as Map<unknown, unknown>, path);\n return entries.map(([k, v], i) => [\n walk(keyType, k, dir, [...path, i, \"key\"]),\n walk(valueType, v, dir, [...path, i, \"value\"]),\n ]);\n }\n const entries = entriesToArray(value, path);\n const out = new Map<unknown, unknown>();\n entries.forEach(([k, v], i) => {\n out.set(\n walk(keyType, k, dir, [...path, i, \"key\"]),\n walk(valueType, v, dir, [...path, i, \"value\"]),\n );\n });\n return out;\n }\n\n case \"set\": {\n const valueType = def.valueType as AnyZod;\n if (dir === \"serialize\") {\n const items = setToArray(value as Set<unknown>, path);\n return items.map((item, i) => walk(valueType, item, dir, [...path, i]));\n }\n const items = arrayToSetItems(value, path);\n return new Set(items.map((item, i) => walk(valueType, item, dir, [...path, i])));\n }\n\n case \"union\": {\n // Try each member; first that walks without a CodecError wins. Mirrors\n // Zod's own \"first match\" union semantics for the JSON-safe shape. A\n // non-CodecError (e.g. a throwing z.lazy getter) is a real fault and\n // propagates immediately rather than being treated as a failed match.\n const options = def.options as AnyZod[];\n let lastError: CodecError = new CodecError(\"no union member matched\", path);\n for (const option of options) {\n try {\n return walk(option, value, dir, path);\n } catch (err) {\n if (!(err instanceof CodecError)) throw err;\n lastError = err;\n }\n }\n throw lastError;\n }\n\n case \"date\":\n return dir === \"serialize\"\n ? dateToIso(value as Date, path)\n : isoToDate(value, path);\n\n case \"bigint\":\n return dir === \"serialize\"\n ? bigintToString(value as bigint, path)\n : stringToBigint(value, path);\n\n // Scalars and explicit escape hatches pass through untouched.\n case \"string\":\n case \"number\":\n case \"boolean\":\n case \"nan\":\n case \"null\":\n case \"undefined\":\n case \"literal\":\n case \"enum\":\n case \"any\":\n case \"unknown\":\n case \"void\":\n case \"custom\":\n return value;\n\n default:\n // Fail closed: an unhandled object-like node would otherwise silently\n // skip its inner schema. Use z.custom() for genuinely opaque values.\n throw new CodecError(`unsupported schema node \"${def.type}\"`, path);\n }\n}\n\n/**\n * Convert a rich value into a JSON-safe plain value per its Zod schema.\n * Date -> ISO string, BigInt -> string, Map -> `[key, value][]`, recursing\n * through objects, arrays, maps and unions. The result survives\n * `JSON.parse(JSON.stringify(...))` with no loss for supported types.\n */\nexport function serialize<S extends AnyZod>(value: z.infer<S>, schema: S): unknown {\n return walk(schema, value, \"serialize\", []);\n}\n\nfunction isZodAsyncError(err: unknown): boolean {\n const name = (err as { constructor?: { name?: string } } | null)?.constructor?.name;\n return name === \"$ZodAsyncError\";\n}\n\n/**\n * Rebuild rich values from their JSON-safe wire form per `schema`, then\n * validate with Zod. Leaf transforms validate their input and surface a typed\n * {@link CodecError} on malformed data, so untrusted JSON never escapes as a\n * raw throw or an unbounded stall. Sync-only: an async-refined schema throws\n * {@link AsyncSchemaError}.\n */\nexport function deserialize<S extends AnyZod>(json: unknown, schema: S): z.infer<S> {\n const rebuilt = walk(schema, json, \"deserialize\", []);\n try {\n return schema.parse(rebuilt) as z.infer<S>;\n } catch (err) {\n if (isZodAsyncError(err)) throw new AsyncSchemaError();\n throw err;\n }\n}\n\n/**\n * Async variant of {@link deserialize}: rebuilds rich values then validates with\n * `parseAsync`, so schemas containing async `.refine`/`.superRefine` are\n * supported (no {@link AsyncSchemaError}). The same leaf-transform safety and\n * DoS bounds apply during the rebuild.\n */\nexport async function deserializeAsync<S extends AnyZod>(\n json: unknown,\n schema: S,\n): Promise<z.infer<S>> {\n const rebuilt = walk(schema, json, \"deserialize\", []);\n return (await schema.parseAsync(rebuilt)) as z.infer<S>;\n}\n"]}
|