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/dist/index.js ADDED
@@ -0,0 +1,40 @@
1
+ import { createMap, forwardMany, reverseMany, safeForward } from './chunk-3HB2CM5G.js';
2
+ export { MapHasNoResolversError, OneWayRuleError, ReverseKeyCollisionError, createMap, forwardMany, reverseMany, safeForward, splitWords, toCamelCase, toCamelKey, toConstantCase, toConstantKey, toKebabCase, toKebabKey, toPascalCase, toPascalKey, toSnakeCase, toSnakeKey } from './chunk-3HB2CM5G.js';
3
+ export { AsyncSchemaError, CodecError, deserialize, deserializeAsync, serialize } from './chunk-U3W6PGFE.js';
4
+ export { fromResolver, getPath, isDefaultRule, isFnRule, isFromResolverRule, isPairRule, isStringRule, setPath, withDefault } from './chunk-7DUCUUPF.js';
5
+ import { z } from 'zod';
6
+
7
+ function subObject(schema, keys) {
8
+ const shape = schema.shape;
9
+ const picked = {};
10
+ for (const key of keys) {
11
+ const field = shape[key];
12
+ if (field !== void 0) picked[key] = field;
13
+ }
14
+ return z.object(picked);
15
+ }
16
+ function pick(map, keys) {
17
+ return createMap(subObject(map.schema, keys));
18
+ }
19
+ function omit(map, keys) {
20
+ const drop = new Set(keys);
21
+ const remaining = Object.keys(map.schema.shape).filter((k) => !drop.has(k));
22
+ return createMap(subObject(map.schema, remaining));
23
+ }
24
+ function compose(a, b) {
25
+ const base = createMap(b.schema);
26
+ const composed = { ...base };
27
+ composed.forward = (source) => b.forward(a.forward(source));
28
+ composed.reverse = (dto) => a.reverse(b.reverse(dto));
29
+ composed.forwardMany = (sources) => forwardMany(composed, sources);
30
+ composed.reverseMany = (dtos) => reverseMany(composed, dtos);
31
+ composed.safeForward = (source) => safeForward(composed, source);
32
+ return composed;
33
+ }
34
+
35
+ // src/index.ts
36
+ var VERSION = "0.1.0";
37
+
38
+ export { VERSION, compose, omit, pick };
39
+ //# sourceMappingURL=index.js.map
40
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/map/map-views.ts","../src/index.ts"],"names":[],"mappings":";;;;;;AAeA,SAAS,SAAA,CAAU,QAA0B,IAAA,EAAkC;AAC7E,EAAA,MAAM,QAAQ,MAAA,CAAO,KAAA;AACrB,EAAA,MAAM,SAAoC,EAAC;AAC3C,EAAA,KAAA,MAAW,OAAO,IAAA,EAAM;AACtB,IAAA,MAAM,KAAA,GAAQ,MAAM,GAAG,CAAA;AACvB,IAAA,IAAI,KAAA,KAAU,MAAA,EAAW,MAAA,CAAO,GAAG,CAAA,GAAI,KAAA;AAAA,EACzC;AACA,EAAA,OAAO,CAAA,CAAE,OAAO,MAAM,CAAA;AACxB;AAGO,SAAS,IAAA,CAA0B,KAAQ,IAAA,EAA2B;AAC3E,EAAA,OAAO,SAAA,CAAU,SAAA,CAAU,GAAA,CAAI,MAAA,EAAQ,IAAI,CAAC,CAAA;AAC9C;AAGO,SAAS,IAAA,CAA0B,KAAQ,IAAA,EAA2B;AAC3E,EAAA,MAAM,IAAA,GAAO,IAAI,GAAA,CAAI,IAAI,CAAA;AACzB,EAAA,MAAM,SAAA,GAAY,MAAA,CAAO,IAAA,CAAK,GAAA,CAAI,OAAO,KAAe,CAAA,CAAE,MAAA,CAAO,CAAC,CAAA,KAAM,CAAC,IAAA,CAAK,GAAA,CAAI,CAAC,CAAC,CAAA;AACpF,EAAA,OAAO,SAAA,CAAU,SAAA,CAAU,GAAA,CAAI,MAAA,EAAQ,SAAS,CAAC,CAAA;AACnD;AAYO,SAAS,OAAA,CAAkD,GAAM,CAAA,EAAiB;AAGvF,EAAA,MAAM,IAAA,GAAO,SAAA,CAAU,CAAA,CAAE,MAAM,CAAA;AAC/B,EAAA,MAAM,QAAA,GAAW,EAAE,GAAG,IAAA,EAAK;AAC3B,EAAA,QAAA,CAAS,OAAA,GAAU,CAAC,MAAA,KAClB,CAAA,CAAE,QAAQ,CAAA,CAAE,OAAA,CAAQ,MAAM,CAA4B,CAAA;AACxD,EAAA,QAAA,CAAS,OAAA,GAAU,CAAC,GAAA,KAAiB,CAAA,CAAE,QAAQ,CAAA,CAAE,OAAA,CAAQ,GAAY,CAAU,CAAA;AAG/E,EAAA,QAAA,CAAS,WAAA,GAAc,CAAC,OAAA,KAAY,WAAA,CAAY,UAAU,OAAO,CAAA;AACjE,EAAA,QAAA,CAAS,WAAA,GAAc,CAAC,IAAA,KAAS,WAAA,CAAY,UAAU,IAAI,CAAA;AAC3D,EAAA,QAAA,CAAS,WAAA,GAAc,CAAC,MAAA,KAAW,WAAA,CAAY,UAAU,MAAM,CAAA;AAC/D,EAAA,OAAO,QAAA;AACT;;;AC5DO,IAAM,OAAA,GAAU","file":"index.js","sourcesContent":["/**\n * Map-deriving helpers that produce NEW {@link TypeMapper} instances: sub-DTO\n * views (`pick`/`omit`) and composition (`compose`). Kept separate from\n * `createMap.ts` so it can import `createMap` without a circular dependency.\n */\nimport { z } from \"zod\";\nimport { type TypeMapper, createMap } from \"./createMap.js\";\nimport { forwardMany, reverseMany, safeForward } from \"./map-ops.js\";\n\ntype AnyMapper = TypeMapper<z.ZodObject<any>, any>;\n\n/**\n * Build a fresh `z.object` from the chosen `keys` of `schema`'s raw `.shape`.\n * Uses the shape directly (never `.pick()`), so it is safe on refined schemas.\n */\nfunction subObject(schema: z.ZodObject<any>, keys: string[]): z.ZodObject<any> {\n const shape = schema.shape as Record<string, z.ZodType>;\n const picked: Record<string, z.ZodType> = {};\n for (const key of keys) {\n const field = shape[key];\n if (field !== undefined) picked[key] = field;\n }\n return z.object(picked);\n}\n\n/** A map over only `keys` of `map`'s DTO (identity rules; no transforms carried). */\nexport function pick<M extends AnyMapper>(map: M, keys: string[]): AnyMapper {\n return createMap(subObject(map.schema, keys));\n}\n\n/** A map over `map`'s DTO minus `keys`. */\nexport function omit<M extends AnyMapper>(map: M, keys: string[]): AnyMapper {\n const drop = new Set(keys);\n const remaining = Object.keys(map.schema.shape as object).filter((k) => !drop.has(k));\n return createMap(subObject(map.schema, remaining));\n}\n\n/**\n * Compose two maps into a DTO->DTO pipeline: `compose(a, b).forward(src)` runs\n * `b.forward(a.forward(src))`, validating against `b`'s schema. Reverse pipes\n * back through `a.reverse(b.reverse(dto))`.\n *\n * `forwardMany`/`safeForward`/`reverseMany` honor the composed pipeline. Reverse\n * only round-trips fields BOTH maps carry a reversible rule for (identity links\n * with no rule emit nothing on reverse). `toResolver` is unsupported on a\n * composed map (it declares no resolver graph) and throws.\n */\nexport function compose<A extends AnyMapper, B extends AnyMapper>(a: A, b: B): AnyMapper {\n // Base instance supplies schema/shape/case-helpers for b's DTO; forward and\n // reverse pipe through the ORIGINAL maps so their rules are honored.\n const base = createMap(b.schema);\n const composed = { ...base } as AnyMapper;\n composed.forward = (source: Record<string, unknown>) =>\n b.forward(a.forward(source) as Record<string, unknown>);\n composed.reverse = (dto: unknown) => a.reverse(b.reverse(dto as never) as never);\n // Re-wire derived ops over the COMPOSED forward/reverse — base's closures\n // would otherwise bypass the pipeline and silently skip a's transforms.\n composed.forwardMany = (sources) => forwardMany(composed, sources) as never;\n composed.reverseMany = (dtos) => reverseMany(composed, dtos);\n composed.safeForward = (source) => safeForward(composed, source) as never;\n return composed;\n}\n","// Root entry: the core sync mapper plus the serialize codec.\nexport const VERSION = \"0.1.0\";\n\nexport {\n createMap,\n OneWayRuleError,\n ReverseKeyCollisionError,\n MapHasNoResolversError,\n fromResolver,\n getPath,\n setPath,\n isStringRule,\n isFnRule,\n isPairRule,\n isFromResolverRule,\n isDefaultRule,\n withDefault,\n toSnakeCase,\n toCamelCase,\n toKebabCase,\n toPascalCase,\n toConstantCase,\n toSnakeKey,\n toCamelKey,\n toKebabKey,\n toPascalKey,\n toConstantKey,\n splitWords,\n type SnakeCased,\n type CamelCased,\n forwardMany,\n reverseMany,\n safeForward,\n pick,\n omit,\n compose,\n type TypeMapper,\n type RuleMap,\n type Rule,\n type FnRule,\n type PairRule,\n type FromResolver,\n type DefaultRule,\n type SafeResult,\n} from \"./map/index.js\";\n\nexport {\n serialize,\n deserialize,\n deserializeAsync,\n CodecError,\n AsyncSchemaError,\n} from \"./serialize/index.js\";\n"]}
@@ -0,0 +1,250 @@
1
+ 'use strict';
2
+
3
+ // src/map/rules.ts
4
+ var FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
5
+ function isForbidden(key) {
6
+ return FORBIDDEN_KEYS.has(key);
7
+ }
8
+ function splitPath(path) {
9
+ return path.split(".");
10
+ }
11
+ function getPath(source, path) {
12
+ let current = source;
13
+ for (const segment of splitPath(path)) {
14
+ if (isForbidden(segment)) return void 0;
15
+ if (current === null || current === void 0) return void 0;
16
+ if (typeof current !== "object") return void 0;
17
+ if (!Object.prototype.hasOwnProperty.call(current, segment)) return void 0;
18
+ current = current[segment];
19
+ }
20
+ return current;
21
+ }
22
+
23
+ // src/resolver/context.ts
24
+ var EMPTY_ANCESTORS = /* @__PURE__ */ new Set();
25
+ function defer() {
26
+ let resolve;
27
+ let reject;
28
+ const promise = new Promise((res, rej) => {
29
+ resolve = res;
30
+ reject = rej;
31
+ });
32
+ return { promise, resolve, reject };
33
+ }
34
+ var GraphContext = class {
35
+ adapter;
36
+ seed;
37
+ resolvers;
38
+ schemas;
39
+ onResolve;
40
+ /** Durable promise-cache: every attempted field lives here. */
41
+ cache = /* @__PURE__ */ new Map();
42
+ /** Synchronously-readable settled values, for `get`/`path` peeks. */
43
+ settled = /* @__PURE__ */ new Map();
44
+ /** Fields with an in-flight resolver; transient cycle guard. */
45
+ inProgress = /* @__PURE__ */ new Set();
46
+ /** Fields explicitly invalidated: prefer the resolver over seed on next resolve. */
47
+ invalidated = /* @__PURE__ */ new Set();
48
+ constructor(config) {
49
+ this.adapter = config.adapter;
50
+ this.seed = config.seed ?? {};
51
+ this.resolvers = config.resolvers ?? {};
52
+ this.schemas = config.schemas ?? {};
53
+ this.onResolve = config.onResolve;
54
+ }
55
+ /** Synchronous peek of seed first, then any settled resolved value. */
56
+ peek(field) {
57
+ const seeded = this.seed[field];
58
+ if (seeded !== void 0) return seeded;
59
+ return this.settled.get(field);
60
+ }
61
+ get(field) {
62
+ return this.peek(field);
63
+ }
64
+ path(field, keyPath) {
65
+ const base = this.peek(field);
66
+ if (base === void 0) return void 0;
67
+ if (keyPath.length === 0) return base;
68
+ return getPath(base, keyPath.join("."));
69
+ }
70
+ /** Public entry: a fresh resolution chain with no ancestors. */
71
+ resolve(field) {
72
+ return this.resolveWithin(field, EMPTY_ANCESTORS);
73
+ }
74
+ /**
75
+ * Resolve `field` within a chain whose currently-resolving fields are
76
+ * `ancestors`. If `field` is itself an ancestor, this is a dependency cycle
77
+ * (mutual A<->B, longer A->B->C->A, or self x->x): return `undefined` so the
78
+ * caller (e.g. a `strategies` candidate) falls through to its next route,
79
+ * instead of awaiting its own still-pending promise (which would deadlock).
80
+ *
81
+ * The ancestor check runs BEFORE the `cache.has` short-circuit, so a CONCURRENT
82
+ * caller (a parallel `Promise.all` chain — `field` is in-flight but NOT its
83
+ * ancestor) still receives the shared pending promise and dedups normally.
84
+ */
85
+ resolveWithin(field, ancestors) {
86
+ if (ancestors.has(field)) {
87
+ return Promise.resolve(void 0);
88
+ }
89
+ const cached = this.cache.get(field);
90
+ if (cached !== void 0) return cached;
91
+ const resolver = this.resolvers[field];
92
+ const preferResolver = this.invalidated.has(field) && resolver !== void 0;
93
+ const seeded = this.seed[field];
94
+ if (seeded !== void 0 && !preferResolver) {
95
+ const p2 = Promise.resolve(seeded);
96
+ this.cache.set(field, p2);
97
+ return p2;
98
+ }
99
+ if (resolver) {
100
+ this.invalidated.delete(field);
101
+ const deferred = defer();
102
+ this.cache.set(field, deferred.promise);
103
+ this.inProgress.add(field);
104
+ const childAncestors = new Set(ancestors).add(field);
105
+ this.runResolver(field, resolver, deferred, childAncestors);
106
+ return deferred.promise;
107
+ }
108
+ const p = Promise.resolve(void 0);
109
+ this.cache.set(field, p);
110
+ return p;
111
+ }
112
+ /**
113
+ * A context bound to `self` and the chain's `ancestors`: identical to the
114
+ * top-level context except `resolve`/`fallback` carry the chain so cycles
115
+ * break (see {@link resolveWithin}) and `fallback` retries the right field.
116
+ * Binding per invocation (not shared instance state) keeps concurrent
117
+ * resolutions from contaminating each other.
118
+ */
119
+ boundContext(self, ancestors) {
120
+ return {
121
+ adapter: this.adapter,
122
+ resolve: (field) => this.resolveWithin(field, ancestors),
123
+ resolveMany: (...fields) => this.resolveMany(...fields),
124
+ get: (field) => this.get(field),
125
+ path: (field, keyPath) => this.path(field, keyPath),
126
+ fallback: (deps) => this.fallbackFor(self, deps, ancestors),
127
+ invalidate: (...fields) => this.invalidate(...fields),
128
+ refresh: (field) => this.refresh(field)
129
+ };
130
+ }
131
+ invalidate(...fields) {
132
+ for (const field of fields) {
133
+ this.cache.delete(field);
134
+ this.settled.delete(field);
135
+ if (this.resolvers[field] !== void 0) this.invalidated.add(field);
136
+ }
137
+ return this;
138
+ }
139
+ refresh(field) {
140
+ this.invalidate(field);
141
+ return this.resolve(field);
142
+ }
143
+ /** Fire the best-effort `onResolve` observer; never let it break resolution. */
144
+ emitResolved(field, value) {
145
+ if (!this.onResolve) return;
146
+ try {
147
+ this.onResolve(field, value);
148
+ } catch {
149
+ }
150
+ }
151
+ runResolver(field, resolver, deferred, ancestors) {
152
+ const ctx = this.boundContext(field, ancestors);
153
+ Promise.resolve().then(() => resolver(ctx)).then((value) => this.validate(field, value)).then(
154
+ (value) => {
155
+ this.settled.set(field, value);
156
+ this.inProgress.delete(field);
157
+ this.emitResolved(field, value);
158
+ deferred.resolve(value);
159
+ },
160
+ (error) => {
161
+ this.cache.delete(field);
162
+ this.inProgress.delete(field);
163
+ deferred.reject(error);
164
+ }
165
+ );
166
+ }
167
+ /**
168
+ * Validate a resolver's output against its field schema (if any). A defined
169
+ * value is parsed (unknown keys stripped); a parse failure throws the ZodError,
170
+ * which the reject branch turns into an eviction. `undefined` is the forgiving
171
+ * absence terminal and is never validated.
172
+ */
173
+ async validate(field, value) {
174
+ const schema = this.schemas[field];
175
+ if (schema === void 0 || value === void 0) return value;
176
+ return await schema.parseAsync(value);
177
+ }
178
+ /** Resolve untouched `deps`, then retry `self`'s resolver if any produced a value. */
179
+ async fallbackFor(self, deps, ancestors) {
180
+ let resolvedAny = false;
181
+ for (const dep of deps) {
182
+ if (this.cache.has(dep) || this.inProgress.has(dep)) continue;
183
+ const value = await this.resolveWithin(dep, ancestors);
184
+ if (value) resolvedAny = true;
185
+ }
186
+ if (!resolvedAny) return void 0;
187
+ const resolver = this.resolvers[self];
188
+ return resolver(this.boundContext(self, ancestors));
189
+ }
190
+ /**
191
+ * Top-level `fallback` has no calling field, so there is nothing to retry.
192
+ * Resolvers always receive a field-bound context whose `fallback` does retry;
193
+ * this entry exists only to satisfy the public {@link ResolverContext} shape.
194
+ */
195
+ fallback(_deps) {
196
+ return Promise.resolve(void 0);
197
+ }
198
+ async resolveMany(...fields) {
199
+ const out = {};
200
+ for (const field of fields) {
201
+ const value = await this.resolve(field);
202
+ if (value !== void 0) out[field] = value;
203
+ }
204
+ return out;
205
+ }
206
+ };
207
+
208
+ // src/resolver/createResolver.ts
209
+ function createResolver(config) {
210
+ const schemas = config.maps ? buildSchemas(config.maps, config.fields) : config.schemas;
211
+ return new GraphContext({
212
+ adapter: config.adapter,
213
+ seed: config.seed,
214
+ resolvers: config.resolvers,
215
+ schemas,
216
+ onResolve: config.onResolve
217
+ });
218
+ }
219
+ function buildSchemas(maps, fields) {
220
+ const out = /* @__PURE__ */ Object.create(null);
221
+ for (const key of Object.keys(maps)) {
222
+ out[key] = maps[key].schema;
223
+ }
224
+ if (fields) {
225
+ const shape = fields.shape;
226
+ for (const key of Object.keys(shape)) {
227
+ out[key] = shape[key];
228
+ }
229
+ }
230
+ return out;
231
+ }
232
+
233
+ // src/resolver/strategies.ts
234
+ var SKIP = /* @__PURE__ */ Symbol("zodmapper.strategies.skip");
235
+ function strategies(...candidates) {
236
+ return async (ctx) => {
237
+ for (const candidate of candidates) {
238
+ const value = await candidate(ctx);
239
+ if (value !== void 0 && value !== SKIP) return value;
240
+ }
241
+ return void 0;
242
+ };
243
+ }
244
+
245
+ exports.GraphContext = GraphContext;
246
+ exports.SKIP = SKIP;
247
+ exports.createResolver = createResolver;
248
+ exports.strategies = strategies;
249
+ //# sourceMappingURL=index.cjs.map
250
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/map/rules.ts","../../src/resolver/context.ts","../../src/resolver/createResolver.ts","../../src/resolver/strategies.ts"],"names":["p"],"mappings":"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","file":"index.cjs","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","/**\n * `strategies` — declarative multi-path field resolution. Build a resolver fn\n * from an ordered list of candidate strategies; the first that yields a DEFINED\n * value wins (fastest/cheapest source first). A faithful generalization of the\n * `resolve_conversation`-style pattern (try reservationId, then conversationId,\n * then chatterRefId) without hand-writing the fallthrough each time.\n */\nimport type { ResolverContext, ResolverFn } from \"./context.js\";\n\n/**\n * Sentinel returned by a candidate to mean \"no value here — fall through to the\n * next strategy\", even when its natural result would be `null`. Use this when\n * `null` is a legitimate winning value for the field but a particular path has\n * nothing to offer.\n */\nexport const SKIP: unique symbol = Symbol(\"zodmapper.strategies.skip\");\nexport type Skip = typeof SKIP;\n\n/**\n * One candidate way to derive a field. Receives the resolver context (so it can\n * peek seed via `ctx.get`/`ctx.path`, resolve deps via `ctx.resolve`, or call\n * `ctx.adapter`).\n *\n * Return semantics (first DEFINED value wins):\n * - `undefined` → skip (fall through). Ergonomic for `return await ctx.resolve(dep)`,\n * which yields `undefined` on a cycle/miss and so falls through automatically.\n * - {@link SKIP} → skip (explicit), even if the value would otherwise be `null`.\n * - `null` → WINS — `null` is a defined value.\n * - any other value → WINS.\n * - a thrown error aborts and propagates (NOT a fallthrough).\n */\nexport type Strategy<Fields, TAdapter, V> = (\n ctx: ResolverContext<Fields, TAdapter>,\n) => V | undefined | Skip | Promise<V | undefined | Skip>;\n\n/**\n * Compose ordered strategies into a single resolver fn: each is tried in turn\n * and the first DEFINED result wins. A candidate that returns `undefined` or\n * {@link SKIP} is skipped; `null` (and any other value) is a winning result. If\n * every candidate skips, the field resolves to `undefined`.\n *\n * @example\n * import { strategies, SKIP } from \"zodmapper/resolver\";\n *\n * createResolver({\n * adapter,\n * resolvers: {\n * conversation: strategies(\n * (c) => c.adapter.byReservation(c.get(\"reservationId\")), // undefined -> skip\n * (c) => c.adapter.byConversationId(c.get(\"conversationId\")) ?? SKIP, // skip even if null\n * (c) => c.adapter.byChatterRef(c.get(\"chatterRefId\")),\n * ),\n * },\n * });\n */\nexport function strategies<Fields, TAdapter, K extends keyof Fields>(\n ...candidates: Array<Strategy<Fields, TAdapter, Fields[K]>>\n): ResolverFn<Fields, TAdapter, K> {\n return async (ctx) => {\n for (const candidate of candidates) {\n const value = await candidate(ctx);\n if (value !== undefined && value !== SKIP) return value as Fields[K];\n }\n return undefined;\n };\n}\n"]}
@@ -0,0 +1,101 @@
1
+ import { z } from 'zod';
2
+ import { a as ResolverRegistry, b as ResolverConfig, R as ResolverContext, c as ResolverFn } from '../context-B0f9mQWu.cjs';
3
+ export { G as GraphContext, d as ResolverSchemas } from '../context-B0f9mQWu.cjs';
4
+
5
+ /**
6
+ * `createResolver` — factory for a dependency-aware, memoizing, cycle-guarded
7
+ * resolver graph with an injected generic fetch adapter. See {@link GraphContext}
8
+ * for the resolution algorithm and its invariants.
9
+ *
10
+ * Two call styles:
11
+ * 1. **Hand-written fields** — `createResolver<Fields, Adapter>({ resolvers, ... })`.
12
+ * Fields are statically typed; resolver outputs are NOT validated at runtime.
13
+ * 2. **Schema-driven** — `createResolver({ maps, fields?, resolvers, ... })`.
14
+ * Resolvable fields and their Zod schemas are derived from `createMap`
15
+ * instances (`maps`) plus an optional scalar-dep `fields` object; each
16
+ * resolver's output is parsed (unknown keys stripped) before caching.
17
+ *
18
+ * Field names that collide with JavaScript object internals (`__proto__`,
19
+ * `constructor`, `prototype`) are not supported — the `resolvers`/`seed`
20
+ * registries are plain objects, so such keys do not register as own properties.
21
+ * Use ordinary identifier field names.
22
+ */
23
+
24
+ /** Minimal structural view of a `createMap` instance (avoids a hard dep cycle). */
25
+ interface SchemaSource<T = unknown> {
26
+ readonly schema: z.ZodType<T>;
27
+ }
28
+ /** Field set derived from a map of {@link SchemaSource}s (each map's inferred DTO). */
29
+ type FieldsFromMaps<M extends Record<string, SchemaSource>> = {
30
+ [K in keyof M]: M[K] extends SchemaSource<infer T> ? T : never;
31
+ };
32
+ /** Field set derived from an optional scalar `fields` object schema. */
33
+ type FieldsFromSchema<F extends z.ZodObject<any> | undefined> = F extends z.ZodObject<any> ? z.infer<F> : Record<never, never>;
34
+ /** Merged resolvable field set: mapped DTO fields + scalar dep fields. */
35
+ type SchemaResolverFields<M extends Record<string, SchemaSource>, F extends z.ZodObject<any> | undefined> = FieldsFromMaps<M> & FieldsFromSchema<F>;
36
+ /** Config for the schema-driven `createResolver` overload. */
37
+ interface SchemaResolverConfig<M extends Record<string, SchemaSource>, F extends z.ZodObject<any> | undefined, TAdapter> {
38
+ adapter: TAdapter;
39
+ /** `createMap` instances whose dest schemas define validated DTO fields. */
40
+ maps: M;
41
+ /** Optional object schema for scalar dependency fields (e.g. `orgId`). */
42
+ fields?: F;
43
+ seed?: Partial<SchemaResolverFields<M, F>>;
44
+ resolvers?: ResolverRegistry<SchemaResolverFields<M, F>, TAdapter>;
45
+ }
46
+ declare function createResolver<Fields, TAdapter>(config: ResolverConfig<Fields, TAdapter>): ResolverContext<Fields, TAdapter>;
47
+ declare function createResolver<M extends Record<string, SchemaSource>, TAdapter, F extends z.ZodObject<any> | undefined = undefined>(config: SchemaResolverConfig<M, F, TAdapter>): ResolverContext<SchemaResolverFields<M, F>, TAdapter>;
48
+
49
+ /**
50
+ * `strategies` — declarative multi-path field resolution. Build a resolver fn
51
+ * from an ordered list of candidate strategies; the first that yields a DEFINED
52
+ * value wins (fastest/cheapest source first). A faithful generalization of the
53
+ * `resolve_conversation`-style pattern (try reservationId, then conversationId,
54
+ * then chatterRefId) without hand-writing the fallthrough each time.
55
+ */
56
+
57
+ /**
58
+ * Sentinel returned by a candidate to mean "no value here — fall through to the
59
+ * next strategy", even when its natural result would be `null`. Use this when
60
+ * `null` is a legitimate winning value for the field but a particular path has
61
+ * nothing to offer.
62
+ */
63
+ declare const SKIP: unique symbol;
64
+ type Skip = typeof SKIP;
65
+ /**
66
+ * One candidate way to derive a field. Receives the resolver context (so it can
67
+ * peek seed via `ctx.get`/`ctx.path`, resolve deps via `ctx.resolve`, or call
68
+ * `ctx.adapter`).
69
+ *
70
+ * Return semantics (first DEFINED value wins):
71
+ * - `undefined` → skip (fall through). Ergonomic for `return await ctx.resolve(dep)`,
72
+ * which yields `undefined` on a cycle/miss and so falls through automatically.
73
+ * - {@link SKIP} → skip (explicit), even if the value would otherwise be `null`.
74
+ * - `null` → WINS — `null` is a defined value.
75
+ * - any other value → WINS.
76
+ * - a thrown error aborts and propagates (NOT a fallthrough).
77
+ */
78
+ type Strategy<Fields, TAdapter, V> = (ctx: ResolverContext<Fields, TAdapter>) => V | undefined | Skip | Promise<V | undefined | Skip>;
79
+ /**
80
+ * Compose ordered strategies into a single resolver fn: each is tried in turn
81
+ * and the first DEFINED result wins. A candidate that returns `undefined` or
82
+ * {@link SKIP} is skipped; `null` (and any other value) is a winning result. If
83
+ * every candidate skips, the field resolves to `undefined`.
84
+ *
85
+ * @example
86
+ * import { strategies, SKIP } from "zodmapper/resolver";
87
+ *
88
+ * createResolver({
89
+ * adapter,
90
+ * resolvers: {
91
+ * conversation: strategies(
92
+ * (c) => c.adapter.byReservation(c.get("reservationId")), // undefined -> skip
93
+ * (c) => c.adapter.byConversationId(c.get("conversationId")) ?? SKIP, // skip even if null
94
+ * (c) => c.adapter.byChatterRef(c.get("chatterRefId")),
95
+ * ),
96
+ * },
97
+ * });
98
+ */
99
+ declare function strategies<Fields, TAdapter, K extends keyof Fields>(...candidates: Array<Strategy<Fields, TAdapter, Fields[K]>>): ResolverFn<Fields, TAdapter, K>;
100
+
101
+ export { ResolverConfig, ResolverContext, ResolverFn, ResolverRegistry, SKIP, type SchemaResolverConfig, type SchemaResolverFields, type SchemaSource, type Skip, type Strategy, createResolver, strategies };
@@ -0,0 +1,101 @@
1
+ import { z } from 'zod';
2
+ import { a as ResolverRegistry, b as ResolverConfig, R as ResolverContext, c as ResolverFn } from '../context-B0f9mQWu.js';
3
+ export { G as GraphContext, d as ResolverSchemas } from '../context-B0f9mQWu.js';
4
+
5
+ /**
6
+ * `createResolver` — factory for a dependency-aware, memoizing, cycle-guarded
7
+ * resolver graph with an injected generic fetch adapter. See {@link GraphContext}
8
+ * for the resolution algorithm and its invariants.
9
+ *
10
+ * Two call styles:
11
+ * 1. **Hand-written fields** — `createResolver<Fields, Adapter>({ resolvers, ... })`.
12
+ * Fields are statically typed; resolver outputs are NOT validated at runtime.
13
+ * 2. **Schema-driven** — `createResolver({ maps, fields?, resolvers, ... })`.
14
+ * Resolvable fields and their Zod schemas are derived from `createMap`
15
+ * instances (`maps`) plus an optional scalar-dep `fields` object; each
16
+ * resolver's output is parsed (unknown keys stripped) before caching.
17
+ *
18
+ * Field names that collide with JavaScript object internals (`__proto__`,
19
+ * `constructor`, `prototype`) are not supported — the `resolvers`/`seed`
20
+ * registries are plain objects, so such keys do not register as own properties.
21
+ * Use ordinary identifier field names.
22
+ */
23
+
24
+ /** Minimal structural view of a `createMap` instance (avoids a hard dep cycle). */
25
+ interface SchemaSource<T = unknown> {
26
+ readonly schema: z.ZodType<T>;
27
+ }
28
+ /** Field set derived from a map of {@link SchemaSource}s (each map's inferred DTO). */
29
+ type FieldsFromMaps<M extends Record<string, SchemaSource>> = {
30
+ [K in keyof M]: M[K] extends SchemaSource<infer T> ? T : never;
31
+ };
32
+ /** Field set derived from an optional scalar `fields` object schema. */
33
+ type FieldsFromSchema<F extends z.ZodObject<any> | undefined> = F extends z.ZodObject<any> ? z.infer<F> : Record<never, never>;
34
+ /** Merged resolvable field set: mapped DTO fields + scalar dep fields. */
35
+ type SchemaResolverFields<M extends Record<string, SchemaSource>, F extends z.ZodObject<any> | undefined> = FieldsFromMaps<M> & FieldsFromSchema<F>;
36
+ /** Config for the schema-driven `createResolver` overload. */
37
+ interface SchemaResolverConfig<M extends Record<string, SchemaSource>, F extends z.ZodObject<any> | undefined, TAdapter> {
38
+ adapter: TAdapter;
39
+ /** `createMap` instances whose dest schemas define validated DTO fields. */
40
+ maps: M;
41
+ /** Optional object schema for scalar dependency fields (e.g. `orgId`). */
42
+ fields?: F;
43
+ seed?: Partial<SchemaResolverFields<M, F>>;
44
+ resolvers?: ResolverRegistry<SchemaResolverFields<M, F>, TAdapter>;
45
+ }
46
+ declare function createResolver<Fields, TAdapter>(config: ResolverConfig<Fields, TAdapter>): ResolverContext<Fields, TAdapter>;
47
+ declare function createResolver<M extends Record<string, SchemaSource>, TAdapter, F extends z.ZodObject<any> | undefined = undefined>(config: SchemaResolverConfig<M, F, TAdapter>): ResolverContext<SchemaResolverFields<M, F>, TAdapter>;
48
+
49
+ /**
50
+ * `strategies` — declarative multi-path field resolution. Build a resolver fn
51
+ * from an ordered list of candidate strategies; the first that yields a DEFINED
52
+ * value wins (fastest/cheapest source first). A faithful generalization of the
53
+ * `resolve_conversation`-style pattern (try reservationId, then conversationId,
54
+ * then chatterRefId) without hand-writing the fallthrough each time.
55
+ */
56
+
57
+ /**
58
+ * Sentinel returned by a candidate to mean "no value here — fall through to the
59
+ * next strategy", even when its natural result would be `null`. Use this when
60
+ * `null` is a legitimate winning value for the field but a particular path has
61
+ * nothing to offer.
62
+ */
63
+ declare const SKIP: unique symbol;
64
+ type Skip = typeof SKIP;
65
+ /**
66
+ * One candidate way to derive a field. Receives the resolver context (so it can
67
+ * peek seed via `ctx.get`/`ctx.path`, resolve deps via `ctx.resolve`, or call
68
+ * `ctx.adapter`).
69
+ *
70
+ * Return semantics (first DEFINED value wins):
71
+ * - `undefined` → skip (fall through). Ergonomic for `return await ctx.resolve(dep)`,
72
+ * which yields `undefined` on a cycle/miss and so falls through automatically.
73
+ * - {@link SKIP} → skip (explicit), even if the value would otherwise be `null`.
74
+ * - `null` → WINS — `null` is a defined value.
75
+ * - any other value → WINS.
76
+ * - a thrown error aborts and propagates (NOT a fallthrough).
77
+ */
78
+ type Strategy<Fields, TAdapter, V> = (ctx: ResolverContext<Fields, TAdapter>) => V | undefined | Skip | Promise<V | undefined | Skip>;
79
+ /**
80
+ * Compose ordered strategies into a single resolver fn: each is tried in turn
81
+ * and the first DEFINED result wins. A candidate that returns `undefined` or
82
+ * {@link SKIP} is skipped; `null` (and any other value) is a winning result. If
83
+ * every candidate skips, the field resolves to `undefined`.
84
+ *
85
+ * @example
86
+ * import { strategies, SKIP } from "zodmapper/resolver";
87
+ *
88
+ * createResolver({
89
+ * adapter,
90
+ * resolvers: {
91
+ * conversation: strategies(
92
+ * (c) => c.adapter.byReservation(c.get("reservationId")), // undefined -> skip
93
+ * (c) => c.adapter.byConversationId(c.get("conversationId")) ?? SKIP, // skip even if null
94
+ * (c) => c.adapter.byChatterRef(c.get("chatterRefId")),
95
+ * ),
96
+ * },
97
+ * });
98
+ */
99
+ declare function strategies<Fields, TAdapter, K extends keyof Fields>(...candidates: Array<Strategy<Fields, TAdapter, Fields[K]>>): ResolverFn<Fields, TAdapter, K>;
100
+
101
+ export { ResolverConfig, ResolverContext, ResolverFn, ResolverRegistry, SKIP, type SchemaResolverConfig, type SchemaResolverFields, type SchemaSource, type Skip, type Strategy, createResolver, strategies };
@@ -0,0 +1,17 @@
1
+ export { GraphContext, createResolver } from '../chunk-7DUCUUPF.js';
2
+
3
+ // src/resolver/strategies.ts
4
+ var SKIP = /* @__PURE__ */ Symbol("zodmapper.strategies.skip");
5
+ function strategies(...candidates) {
6
+ return async (ctx) => {
7
+ for (const candidate of candidates) {
8
+ const value = await candidate(ctx);
9
+ if (value !== void 0 && value !== SKIP) return value;
10
+ }
11
+ return void 0;
12
+ };
13
+ }
14
+
15
+ export { SKIP, strategies };
16
+ //# sourceMappingURL=index.js.map
17
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/resolver/strategies.ts"],"names":[],"mappings":";;;AAeO,IAAM,IAAA,0BAA6B,2BAA2B;AAwC9D,SAAS,cACX,UAAA,EAC8B;AACjC,EAAA,OAAO,OAAO,GAAA,KAAQ;AACpB,IAAA,KAAA,MAAW,aAAa,UAAA,EAAY;AAClC,MAAA,MAAM,KAAA,GAAQ,MAAM,SAAA,CAAU,GAAG,CAAA;AACjC,MAAA,IAAI,KAAA,KAAU,MAAA,IAAa,KAAA,KAAU,IAAA,EAAM,OAAO,KAAA;AAAA,IACpD;AACA,IAAA,OAAO,MAAA;AAAA,EACT,CAAA;AACF","file":"index.js","sourcesContent":["/**\n * `strategies` — declarative multi-path field resolution. Build a resolver fn\n * from an ordered list of candidate strategies; the first that yields a DEFINED\n * value wins (fastest/cheapest source first). A faithful generalization of the\n * `resolve_conversation`-style pattern (try reservationId, then conversationId,\n * then chatterRefId) without hand-writing the fallthrough each time.\n */\nimport type { ResolverContext, ResolverFn } from \"./context.js\";\n\n/**\n * Sentinel returned by a candidate to mean \"no value here — fall through to the\n * next strategy\", even when its natural result would be `null`. Use this when\n * `null` is a legitimate winning value for the field but a particular path has\n * nothing to offer.\n */\nexport const SKIP: unique symbol = Symbol(\"zodmapper.strategies.skip\");\nexport type Skip = typeof SKIP;\n\n/**\n * One candidate way to derive a field. Receives the resolver context (so it can\n * peek seed via `ctx.get`/`ctx.path`, resolve deps via `ctx.resolve`, or call\n * `ctx.adapter`).\n *\n * Return semantics (first DEFINED value wins):\n * - `undefined` → skip (fall through). Ergonomic for `return await ctx.resolve(dep)`,\n * which yields `undefined` on a cycle/miss and so falls through automatically.\n * - {@link SKIP} → skip (explicit), even if the value would otherwise be `null`.\n * - `null` → WINS — `null` is a defined value.\n * - any other value → WINS.\n * - a thrown error aborts and propagates (NOT a fallthrough).\n */\nexport type Strategy<Fields, TAdapter, V> = (\n ctx: ResolverContext<Fields, TAdapter>,\n) => V | undefined | Skip | Promise<V | undefined | Skip>;\n\n/**\n * Compose ordered strategies into a single resolver fn: each is tried in turn\n * and the first DEFINED result wins. A candidate that returns `undefined` or\n * {@link SKIP} is skipped; `null` (and any other value) is a winning result. If\n * every candidate skips, the field resolves to `undefined`.\n *\n * @example\n * import { strategies, SKIP } from \"zodmapper/resolver\";\n *\n * createResolver({\n * adapter,\n * resolvers: {\n * conversation: strategies(\n * (c) => c.adapter.byReservation(c.get(\"reservationId\")), // undefined -> skip\n * (c) => c.adapter.byConversationId(c.get(\"conversationId\")) ?? SKIP, // skip even if null\n * (c) => c.adapter.byChatterRef(c.get(\"chatterRefId\")),\n * ),\n * },\n * });\n */\nexport function strategies<Fields, TAdapter, K extends keyof Fields>(\n ...candidates: Array<Strategy<Fields, TAdapter, Fields[K]>>\n): ResolverFn<Fields, TAdapter, K> {\n return async (ctx) => {\n for (const candidate of candidates) {\n const value = await candidate(ctx);\n if (value !== undefined && value !== SKIP) return value as Fields[K];\n }\n return undefined;\n };\n}\n"]}