@zipbul/baker 3.4.0 → 3.4.1

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.
Files changed (48) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/index.js +1 -10
  3. package/dist/src/collect.js +1 -26
  4. package/dist/src/configure.js +1 -43
  5. package/dist/src/create-rule.js +1 -41
  6. package/dist/src/decorators/field.js +1 -277
  7. package/dist/src/decorators/index.js +1 -2
  8. package/dist/src/decorators/recipe.js +1 -23
  9. package/dist/src/errors.js +1 -52
  10. package/dist/src/functions/check-call-options.js +1 -51
  11. package/dist/src/functions/deserialize.js +1 -57
  12. package/dist/src/functions/serialize.js +1 -52
  13. package/dist/src/functions/validate.js +1 -49
  14. package/dist/src/interfaces.js +0 -4
  15. package/dist/src/meta-access.js +1 -75
  16. package/dist/src/registry.js +1 -8
  17. package/dist/src/rule-metadata.js +1 -17
  18. package/dist/src/rule-plan.js +1 -117
  19. package/dist/src/rules/array.js +1 -96
  20. package/dist/src/rules/binary.js +3 -51
  21. package/dist/src/rules/combinators.js +1 -111
  22. package/dist/src/rules/common.js +1 -77
  23. package/dist/src/rules/date.js +1 -35
  24. package/dist/src/rules/index.js +1 -10
  25. package/dist/src/rules/locales.js +1 -249
  26. package/dist/src/rules/number.js +1 -79
  27. package/dist/src/rules/object.js +1 -49
  28. package/dist/src/rules/string.js +10 -2033
  29. package/dist/src/rules/typechecker.js +5 -171
  30. package/dist/src/seal/circular-analyzer.js +1 -63
  31. package/dist/src/seal/codegen-utils.js +1 -18
  32. package/dist/src/seal/deserialize-builder.js +265 -1564
  33. package/dist/src/seal/expose-validator.js +1 -65
  34. package/dist/src/seal/seal-state.js +1 -18
  35. package/dist/src/seal/seal.js +1 -431
  36. package/dist/src/seal/serialize-builder.js +66 -370
  37. package/dist/src/seal/validate-meta.js +1 -61
  38. package/dist/src/symbols.js +1 -13
  39. package/dist/src/transformers/collection.transformer.js +1 -25
  40. package/dist/src/transformers/date.transformer.js +1 -18
  41. package/dist/src/transformers/index.js +1 -6
  42. package/dist/src/transformers/luxon.transformer.js +1 -34
  43. package/dist/src/transformers/moment.transformer.js +1 -32
  44. package/dist/src/transformers/number.transformer.js +1 -8
  45. package/dist/src/transformers/string.transformer.js +1 -12
  46. package/dist/src/types.js +0 -1
  47. package/dist/src/utils.js +1 -10
  48. package/package.json +2 -2
@@ -1,1564 +1,265 @@
1
- import { err as resultErr, isErr as resultIsErr } from '@zipbul/result';
2
- import { BakerError } from '../errors.js';
3
- import { getSealed } from '../meta-access.js';
4
- import { emitRulePlan } from '../rule-plan.js';
5
- import { sanitizeKey, buildGroupsHasExpr } from './codegen-utils.js';
6
- // ─────────────────────────────────────────────────────────────────────────────
7
- // Generated variable name prefixes — centralised to prevent typo-related bugs
8
- // ─────────────────────────────────────────────────────────────────────────────
9
- const GEN = {
10
- field: '__bk$f_',
11
- index: '__bk$i_',
12
- setIdx: '__bk$si_',
13
- setVal: '__bk$sv_',
14
- mapIdx: '__bk$mi_',
15
- mapVal: '__bk$mv_',
16
- mark: '__bk$mark_',
17
- skip: '__bk$skip_',
18
- result: '__bk$r_',
19
- errors: '__bk$re_',
20
- arr: '__bk$arr_',
21
- disc: '__bk$dt_',
22
- nestedIdx: '__bk$j_',
23
- out: '__bk$out',
24
- errList: '__bk$errors',
25
- groups: '__bk$groups',
26
- group0: '__bk$group0',
27
- groupsSet: '__bk$groupsSet',
28
- key: '__bk$k',
29
- };
30
- // ─────────────────────────────────────────────────────────────────────────────
31
- // Helpers — code generation utilities
32
- // ─────────────────────────────────────────────────────────────────────────────
33
- /** Generate nested error push code that propagates message/context fields */
34
- function nestedErrPush(errList, pathExpr, errItemExpr, tmpVar) {
35
- // Cache errItemExpr once avoids repeated property reads in the generated body
36
- const eVar = `${tmpVar}_e`;
37
- return (`var ${eVar}=${errItemExpr};\n` +
38
- ` if(${eVar}.message===undefined&&${eVar}.context===undefined){${errList}.push({path:${pathExpr},code:${eVar}.code});}\n` +
39
- ` else{var ${tmpVar}={path:${pathExpr},code:${eVar}.code};\n` +
40
- ` if(${eVar}.message!==undefined)${tmpVar}.message=${eVar}.message;\n` +
41
- ` if(${eVar}.context!==undefined)${tmpVar}.context=${eVar}.context;\n` +
42
- ` ${errList}.push(${tmpVar});}\n`);
43
- }
44
- /** Generate nested error return code that propagates message/context fields */
45
- function nestedErrReturn(pathExpr, errItemExpr, tmpVar, validateOnly) {
46
- const ret = (arr) => (validateOnly ? `return ${arr};\n` : `return err(${arr});\n`);
47
- return (`if(${errItemExpr}.message===undefined&&${errItemExpr}.context===undefined)${ret(`[{path:${pathExpr},code:${errItemExpr}.code}]`)}` +
48
- ` var ${tmpVar}={path:${pathExpr},code:${errItemExpr}.code};\n` +
49
- ` if(${errItemExpr}.message!==undefined)${tmpVar}.message=${errItemExpr}.message;\n` +
50
- ` if(${errItemExpr}.context!==undefined)${tmpVar}.context=${errItemExpr}.context;\n` +
51
- ` ${ret(`[${tmpVar}]`)}`);
52
- }
53
- /** Convert field name to a safe JS variable name (includes prefix to prevent internal variable collisions) */
54
- function toVarName(key, prefix) {
55
- return GEN.field + (prefix || '') + sanitizeKey(key);
56
- }
57
- /** Determine the extraction key for deserialization (§4.3 step 3) */
58
- function getDeserializeExtractKey(fieldKey, exposeStack) {
59
- // deserializeOnly @Expose with name use that name
60
- const desDef = exposeStack.find(e => e.deserializeOnly && e.name);
61
- if (desDef) {
62
- return desDef.name;
63
- }
64
- // Non-directional @Expose with name use for both directions
65
- const biDef = exposeStack.find(e => !e.deserializeOnly && !e.serializeOnly && e.name);
66
- if (biDef) {
67
- return biDef.name;
68
- }
69
- return fieldKey;
70
- }
71
- /** Determine field expose groups — returns undefined (no restriction) if any unconditional expose entry exists */
72
- function getDeserializeExposeGroups(exposeStack) {
73
- // Single-pass: scan once, bail out as soon as we see an unconditional entry,
74
- // lazily allocate the result Set.
75
- let all = null;
76
- for (const e of exposeStack) {
77
- if (e.serializeOnly) {
78
- continue;
79
- }
80
- if (!e.groups || e.groups.length === 0) {
81
- return undefined;
82
- }
83
- if (all === null) {
84
- all = new Set();
85
- }
86
- for (const g of e.groups) {
87
- all.add(g);
88
- }
89
- }
90
- return all === null ? undefined : [...all];
91
- }
92
- function buildDeserializeCode(Class, merged, options, needsCircularCheck, isAsync, validateOnly = false) {
93
- const stopAtFirstError = options?.stopAtFirstError ?? false;
94
- const collectErrors = !stopAtFirstError;
95
- const exposeDefaultValues = options?.exposeDefaultValues ?? false;
96
- // Reference arrays injected into new Function closure
97
- const regexes = [];
98
- const refs = [];
99
- const execs = [];
100
- // ── Code generation ────────────────────────────────────────────────────────
101
- // Helper: wrap error array return — validate mode returns raw array, deserialize mode wraps in Result.err
102
- const wrapErr = validateOnly ? (inner) => inner : (inner) => `err(${inner})`;
103
- let body = "'use strict';\n";
104
- // Create instance — skip in validate mode (no object creation needed)
105
- if (validateOnly) {
106
- if (exposeDefaultValues) {
107
- body += 'var __bk$defs = new _Cls();\n';
108
- }
109
- }
110
- else {
111
- body += exposeDefaultValues ? `var ${GEN.out} = new _Cls();\n` : `var ${GEN.out} = Object.create(_Cls.prototype);\n`;
112
- }
113
- // Error array (collectErrors mode)
114
- if (collectErrors) {
115
- body += `var ${GEN.errList} = [];\n`;
116
- }
117
- // preamble: input type guard (§4.9)
118
- body += `if (input == null || typeof input !== 'object' || Array.isArray(input)) return ${wrapErr("[{path:'',code:'invalidInput'}]")};\n`;
119
- // WeakSet guard (circular references) — N-3 fix: WeakSet lives per-call, threaded through
120
- // `opts` via a Symbol-keyed slot so nested DTOs in the same call share it. Symbol keys are
121
- // invisible to `Object.keys`/checkCallOptions, so this doesn't pollute the user's opts shape.
122
- // The previous shared-ref WeakSet caused concurrent async deserialize() to false-positive.
123
- if (needsCircularCheck) {
124
- // __SEEN_KEY is hoisted out of the per-call body and captured via the closure
125
- // arguments of `new Function(...)` below eliminates Symbol.for() lookup on every call.
126
- // Object literal spread is replaced with branched alloc — Bun/JSC optimizes literal-spread
127
- // better than Object.assign({}, ...) (audit H4/H5).
128
- body += `var __seen = (opts && opts[__SEEN_KEY]) || null;\n`;
129
- body += `if (__seen === null) { __seen = new WeakSet(); opts = opts ? { ...opts, [__SEEN_KEY]: __seen } : { [__SEEN_KEY]: __seen }; }\n`;
130
- body += `if (__seen.has(input)) return ${wrapErr("[{path:'',code:'circular'}]")};\n`;
131
- body += `__seen.add(input);\n`;
132
- body += `try {\n`;
133
- }
134
- // Whitelist check (§7.2) — reject undeclared fields
135
- if (options?.whitelist) {
136
- const allowedKeys = new Set();
137
- for (const [fieldKey, meta] of Object.entries(merged)) {
138
- const extractKey = getDeserializeExtractKey(fieldKey, meta.expose);
139
- allowedKeys.add(extractKey);
140
- }
141
- const allowedIdx = refs.length;
142
- refs.push(allowedKeys);
143
- // Indexed Object.keys loop empirically 2–30× faster than for-in + Object.hasOwn on
144
- // Bun/JSC. The keys array allocation is dominated by the per-iteration cost of for-in's
145
- // prototype walk + hasOwn function call.
146
- if (collectErrors) {
147
- body += `{var __wlk=Object.keys(input);for(var __wli=0;__wli<__wlk.length;__wli++){var ${GEN.key}=__wlk[__wli];if(!refs[${allowedIdx}].has(${GEN.key}))${GEN.errList}.push({path:${GEN.key},code:'whitelistViolation'});}}\n`;
148
- }
149
- else {
150
- body += `{var __wlk=Object.keys(input);for(var __wli=0;__wli<__wlk.length;__wli++){var ${GEN.key}=__wlk[__wli];if(!refs[${allowedIdx}].has(${GEN.key}))return ${wrapErr(`[{path:${GEN.key},code:'whitelistViolation'}]`)};}}\n`;
151
- }
152
- }
153
- // Groups variable — only when expose groups or validation rule groups exist (§4.9, §M4).
154
- // Single for-of with early break avoids Object.values alloc + closure allocations.
155
- let hasGroupsField = false;
156
- for (const fk in merged) {
157
- const meta = merged[fk];
158
- const exposeGroups = getDeserializeExposeGroups(meta.expose);
159
- if (exposeGroups && exposeGroups.length > 0) {
160
- hasGroupsField = true;
161
- break;
162
- }
163
- let ruleHasGroups = false;
164
- for (const rd of meta.validation) {
165
- if (rd.groups && rd.groups.length > 0) {
166
- ruleHasGroups = true;
167
- break;
168
- }
169
- }
170
- if (ruleHasGroups) {
171
- hasGroupsField = true;
172
- break;
173
- }
174
- }
175
- if (hasGroupsField) {
176
- body += `var ${GEN.groups} = opts && opts.groups;\n`;
177
- body += `var ${GEN.group0} = ${GEN.groups} && ${GEN.groups}.length === 1 ? ${GEN.groups}[0] : null;\n`;
178
- body += `var ${GEN.groupsSet} = ${GEN.groups} && ${GEN.groups}.length > 1 ? new Set(${GEN.groups}) : null;\n`;
179
- }
180
- // ── Per-field code generation ──────────────────────────────────────────────
181
- for (const [fieldKey, meta] of Object.entries(merged)) {
182
- const fieldCode = generateFieldCode(fieldKey, meta, {
183
- stopAtFirstError,
184
- collectErrors,
185
- exposeDefaultValues,
186
- isAsync,
187
- regexes,
188
- refs,
189
- execs,
190
- options,
191
- validateOnly,
192
- });
193
- body += fieldCode;
194
- }
195
- // ── epilogue ──────────────────────────────────────────────────────────────
196
- if (collectErrors) {
197
- body += `if (${GEN.errList}.length) return ${validateOnly ? GEN.errList : `err(${GEN.errList})`};\n`;
198
- }
199
- body += `return ${validateOnly ? 'null' : GEN.out};\n`;
200
- // Close try/finally for circular reference WeakSet cleanup
201
- if (needsCircularCheck) {
202
- body += `} finally { __seen.delete(input); }\n`;
203
- }
204
- // sourceURL (§4.9)
205
- // Sanitize class name so it cannot inject newlines / */ that would break out of the comment.
206
- const safeClsName = Class.name.replace(/[^\w$.-]/g, '_');
207
- body += `//# sourceURL=baker://${safeClsName}/${validateOnly ? 'validate' : 'deserialize'}\n`;
208
- // ── Execute new Function ───────────────────────────────────────────────────
209
- const fnKeyword = isAsync ? 'async function' : 'function';
210
- const seenKey = Symbol.for('baker:circular-seen');
211
- const executor = new Function('_Cls', 're', 'refs', 'execs', 'err', 'isErr', '__SEEN_KEY', `return ${fnKeyword}(input, opts) { ` + body + ' }')(Class, regexes, refs, execs, resultErr, resultIsErr, seenKey);
212
- return executor;
213
- }
214
- // ─────────────────────────────────────────────────────────────────────────────
215
- // buildValidateCode — validate-only executor (no Object.create, no assignments)
216
- // ─────────────────────────────────────────────────────────────────────────────
217
- function buildValidateCode(Class, merged, options, needsCircularCheck, isAsync) {
218
- return buildDeserializeCode(Class, merged, options, needsCircularCheck, isAsync, true);
219
- }
220
- function resolveGuardKey(isNullable, useOptionalGuard, isDefined) {
221
- if (isNullable && useOptionalGuard) {
222
- return 'nullable+optional';
223
- }
224
- if (isNullable) {
225
- return 'nullable';
226
- }
227
- if (isDefined) {
228
- return 'defined';
229
- }
230
- if (useOptionalGuard) {
231
- return 'optional';
232
- }
233
- return 'default';
234
- }
235
- const GUARD_STRATEGIES = {
236
- // Case 4: @IsNullable + @IsOptional — assign null, skip undefined
237
- 'nullable+optional'({ varName, assignNull, validationCode }) {
238
- let code = `if (${varName} === null) { ${assignNull}}\n`;
239
- code += `else if (${varName} !== undefined) {\n`;
240
- code += validationCode;
241
- code += '}\n';
242
- return code;
243
- },
244
- // Case 3: @IsNullable (+ optional @IsDefined same behavior)
245
- nullable({ varName, emitCtx, assignNull, validationCode }) {
246
- let code = `if (${varName} === undefined) ${emitCtx.fail('isDefined')};\n`;
247
- code += `else if (${varName} !== null) {\n`;
248
- code += validationCode;
249
- code += `} else { ${assignNull}}\n`;
250
- return code;
251
- },
252
- // @IsDefined — reject only undefined, null/""/0 etc. pass through to subsequent validation
253
- defined({ varName, emitCtx, validationCode }) {
254
- let code = `if (${varName} === undefined) ${emitCtx.fail('isDefined')};\n`;
255
- code += validationCode;
256
- return code;
257
- },
258
- // Case 2: @IsOptional — skip entirely on undefined/null
259
- optional({ varName, validationCode }) {
260
- let code = `if (${varName} !== undefined && ${varName} !== null) {\n`;
261
- code += validationCode;
262
- code += '}\n';
263
- return code;
264
- },
265
- // Case 1: No flags (default) reject undefined/null
266
- default({ varName, emitCtx, validationCode }) {
267
- let code = `if (${varName} === undefined || ${varName} === null) ${emitCtx.fail('isDefined')};\n`;
268
- code += `else {\n`;
269
- code += validationCode;
270
- code += '}\n';
271
- return code;
272
- },
273
- };
274
- function generateFieldCode(fieldKey, meta, ctx) {
275
- const { exposeDefaultValues } = ctx;
276
- // ⓪ Exclude deserializeOnly / bidirectional → skip
277
- if (meta.exclude) {
278
- if (!meta.exclude.serializeOnly) {
279
- if (ctx.options?.debug) {
280
- const reason = meta.exclude.deserializeOnly ? 'deserializeOnly' : 'bidirectional';
281
- return `// [baker] field ${JSON.stringify(fieldKey)} excluded (${reason} @Exclude)\n`;
282
- }
283
- return '';
284
- }
285
- }
286
- // Expose: check if this field is exposed to deserialize
287
- // If all @Expose entries are serializeOnly, skip field
288
- if (meta.expose.length > 0 && meta.expose.every(e => e.serializeOnly)) {
289
- if (ctx.options?.debug) {
290
- return `// [baker] field ${JSON.stringify(fieldKey)} excluded (all @Expose entries are serializeOnly)\n`;
291
- }
292
- return '';
293
- }
294
- const varName = toVarName(fieldKey, ctx.varPrefix);
295
- const extractKey = getDeserializeExtractKey(fieldKey, meta.expose);
296
- const exposeGroups = getDeserializeExposeGroups(meta.expose);
297
- const inputObj = ctx.inputExpr || 'input';
298
- // Create EmitContext — bake field-level message/context so EVERY field-own-path failure
299
- // (gate, required-missing, conversion, structural gates) carries them, not just rule bodies.
300
- const fieldExtras = computeFieldExtras(meta, fieldKey, varName, ctx);
301
- const emitCtx = makeEmitCtx(fieldKey, ctx, fieldExtras);
302
- let fieldCode = '';
303
- // ① @ValidateIf guard
304
- let validateIfIdx = null;
305
- if (meta.flags.validateIf) {
306
- validateIfIdx = ctx.refs.length;
307
- ctx.refs.push(meta.flags.validateIf);
308
- }
309
- // ③ Extract + exposeDefaultValues — W7 (N-4): use Object.hasOwn to block prototype-inherited values
310
- let extractCode;
311
- const extractKeyJson = JSON.stringify(extractKey);
312
- if (exposeDefaultValues && !meta.flags.isOptional) {
313
- // exposeDefaultValues still needs hasOwn — must distinguish "missing key" (use default)
314
- // from "explicit undefined" (no default). Prototype-only keys are treated as missing.
315
- const defaultsSource = ctx.validateOnly ? '__bk$defs' : GEN.out;
316
- extractCode = `var ${varName} = Object.hasOwn(${inputObj}, ${extractKeyJson}) ? ${inputObj}[${extractKeyJson}] : ${defaultsSource}[${JSON.stringify(fieldKey)}];\n`;
317
- }
318
- else {
319
- // Direct property access (own or inherited), matching the fast-validator norm (e.g. ajv).
320
- // A per-field `Object.hasOwn` guard would read own-only but cost ~10 ns per 5-field DTO
321
- // (Bun 1.3.13 / i7-13700K) — a ~30% regression on the hot path. The only case it would change
322
- // is an input whose prototype chain carries a declared field name, which requires a global
323
- // `Object.prototype` pollution introduced elsewhere (a separate, pre-existing app vulnerability
324
- // — baker's own input gate rejects `__proto__` payloads). Normal inputs (JSON.parse, framework
325
- // request bodies) are always own-keyed, so this never triggers in practice.
326
- extractCode = `var ${varName} = ${inputObj}[${extractKeyJson}];\n`;
327
- }
328
- // groups check wrap (§4.5)
329
- let fieldStart = '';
330
- let fieldEnd = '';
331
- if (exposeGroups && exposeGroups.length > 0) {
332
- fieldStart = `if ((${GEN.group0} !== null || ${GEN.groupsSet}) && (${buildGroupsHasExpr(GEN.group0, GEN.groupsSet, exposeGroups)})) {\n`;
333
- fieldEnd = '}\n';
334
- }
335
- // inner content (extract + optional guard + validation + assign)
336
- let innerCode = extractCode;
337
- // ② null/undefined guard — @IsOptional, @IsNullable, @IsDefined combinations (§4.3, Phase5)
338
- const useOptionalGuard = !!(meta.flags.isOptional && !meta.flags.isDefined);
339
- const isNullable = meta.flags.isNullable === true;
340
- const validationCode = generateValidationCode(fieldKey, varName, meta, ctx, emitCtx, exposeGroups);
341
- const assignNull = ctx.validateOnly ? '' : `${GEN.out}[${JSON.stringify(fieldKey)}] = null;\n`;
342
- const guardKey = resolveGuardKey(isNullable, useOptionalGuard, meta.flags.isDefined ?? false);
343
- innerCode += GUARD_STRATEGIES[guardKey]({ varName, emitCtx, assignNull, validationCode });
344
- // ① @ValidateIf outer wrap
345
- if (validateIfIdx !== null) {
346
- fieldCode += fieldStart + `if (refs[${validateIfIdx}](${inputObj})) {\n` + innerCode + '}\n' + fieldEnd;
347
- }
348
- else {
349
- fieldCode += fieldStart + innerCode + fieldEnd;
350
- }
351
- return fieldCode;
352
- }
353
- // ─────────────────────────────────────────────────────────────────────────────
354
- // Validation code generation — type guard + transform + validate + assign
355
- // ─────────────────────────────────────────────────────────────────────────────
356
- function generateValidationCode(fieldKey, varName, meta, ctx, emitCtx, fieldGroups) {
357
- const { collectErrors } = ctx;
358
- let code = '';
359
- // @Transform (deserialize direction) — before validation (§4.3 ⑤)
360
- const dsTransforms = meta.transform.filter(td => !td.options?.serializeOnly);
361
- if (dsTransforms.length > 0) {
362
- const fkJson = JSON.stringify(fieldKey);
363
- const objExpr = ctx.inputExpr || 'input';
364
- if (dsTransforms.length === 1) {
365
- const td = dsTransforms[0];
366
- const refIdx = ctx.refs.length;
367
- ctx.refs.push(td.fn);
368
- const callExpr = `refs[${refIdx}]({value:${varName},key:${fkJson},obj:${objExpr}})`;
369
- code += `${varName} = ${td.isAsync ? 'await ' : ''}${callExpr};\n`;
370
- }
371
- else if (dsTransforms.length === 2) {
372
- const td0 = dsTransforms[0];
373
- const td1 = dsTransforms[1];
374
- const refIdx0 = ctx.refs.length;
375
- ctx.refs.push(td0.fn);
376
- const refIdx1 = ctx.refs.length;
377
- ctx.refs.push(td1.fn);
378
- const call0 = `refs[${refIdx0}]({value:${varName},key:${fkJson},obj:${objExpr}})`;
379
- const expr0 = td0.isAsync ? `await ${call0}` : call0;
380
- const call1 = `refs[${refIdx1}]({value:${expr0},key:${fkJson},obj:${objExpr}})`;
381
- code += `${varName} = ${td1.isAsync ? 'await ' : ''}${call1};\n`;
382
- }
383
- else {
384
- for (const td of dsTransforms) {
385
- const refIdx = ctx.refs.length;
386
- ctx.refs.push(td.fn);
387
- const callExpr = `refs[${refIdx}]({value:${varName},key:${fkJson},obj:${objExpr}})`;
388
- code += `${varName} = ${td.isAsync ? 'await ' : ''}${callExpr};\n`;
389
- }
390
- }
391
- }
392
- // Collection (Map/Set) auto conversion
393
- if (meta.type?.collection) {
394
- code += ctx.validateOnly
395
- ? generateCollectionCodeValidateOnly(fieldKey, varName, meta, ctx, emitCtx)
396
- : generateCollectionCode(fieldKey, varName, meta, ctx, emitCtx);
397
- return code;
398
- }
399
- // @ValidateNested + @Type (§8.1)
400
- if (meta.flags.validateNested && meta.type?.fn) {
401
- code += ctx.validateOnly
402
- ? generateNestedCodeValidateOnly(fieldKey, varName, meta, ctx, emitCtx)
403
- : generateNestedCode(fieldKey, varName, meta, ctx, emitCtx);
404
- return code;
405
- }
406
- // No validation rules → direct assign (skip in validate mode)
407
- if (meta.validation.length === 0) {
408
- if (!ctx.validateOnly) {
409
- code += `${GEN.out}[${JSON.stringify(fieldKey)}] = ${varName};\n`;
410
- }
411
- return code;
412
- }
413
- // Build validation with type gate
414
- code += buildRulesCode(fieldKey, varName, meta.validation, collectErrors, emitCtx, ctx, meta, fieldGroups);
415
- return code;
416
- }
417
- // ─────────────────────────────────────────────────────────────────────────────
418
- // Helpers for computing message/context extra fields in generated issue objects
419
- // ─────────────────────────────────────────────────────────────────────────────
420
- /** Build the `,message:...,context:...` extras string for a generated issue object.
421
- * `getConstraintsArg` produces the JS expression for a message function's `constraints`
422
- * field; it runs AFTER the message ref is pushed, preserving ref-array order. */
423
- function buildIssueExtras(message, context, getConstraintsArg, fieldKey, varName, ctx) {
424
- let extra = '';
425
- if (typeof message === 'string') {
426
- extra += `,message:${JSON.stringify(message)}`;
427
- }
428
- else if (typeof message === 'function') {
429
- const msgIdx = ctx.refs.length;
430
- ctx.refs.push(message);
431
- const constraintsArg = getConstraintsArg();
432
- extra += `,message:refs[${msgIdx}]({property:${JSON.stringify(fieldKey)},value:${varName},constraints:${constraintsArg}})`;
433
- }
434
- if (context !== undefined) {
435
- const ctxIdx = ctx.refs.length;
436
- ctx.refs.push(context);
437
- extra += `,context:refs[${ctxIdx}]`;
438
- }
439
- return extra;
440
- }
441
- /** Per-rule extras — a message function receives the failing rule's `constraints`. */
442
- function computeRuleExtras(rd, fieldKey, varName, ctx) {
443
- return buildIssueExtras(rd.message, rd.context, () => {
444
- const constraintsIdx = ctx.refs.length;
445
- ctx.refs.push(rd.rule.constraints ?? {});
446
- return `refs[${constraintsIdx}]`;
447
- }, fieldKey, varName, ctx);
448
- }
449
- /** Field-level extras appended to EVERY failure of a field — including non-rule failures
450
- * (type gate, required-missing, conversion, structural gates) and type-only fields. No
451
- * specific rule applies, so a message function gets `constraints:{}`. */
452
- function computeFieldExtras(meta, fieldKey, varName, ctx) {
453
- return buildIssueExtras(meta.message, meta.context, () => '{}', fieldKey, varName, ctx);
454
- }
455
- /** Create per-rule EmitContext (with message/context overrides) */
456
- function makeRuleEmitCtx(baseEmitCtx, fieldKey, varName, rd, ctx) {
457
- const extra = computeRuleExtras(rd, fieldKey, varName, ctx);
458
- if (!extra) {
459
- return baseEmitCtx;
460
- }
461
- const pathExpr = baseEmitCtx.pathExpr ?? JSON.stringify(fieldKey);
462
- return {
463
- ...baseEmitCtx,
464
- fail(code) {
465
- if (baseEmitCtx.collectErrors) {
466
- return `${GEN.errList}.push({path:${pathExpr},code:${JSON.stringify(code)}${extra}})`;
467
- }
468
- else if (ctx.validateOnly) {
469
- return `return [{path:${pathExpr},code:${JSON.stringify(code)}${extra}}]`;
470
- }
471
- return `return err([{path:${pathExpr},code:${JSON.stringify(code)}${extra}}])`;
472
- },
473
- };
474
- }
475
- function emitRuleList(fieldKey, varName, rules, emitCtx, ctx, indent, fieldGroups, insideTypeGate) {
476
- let code = '';
477
- // Single-pass partition over rules, counting both cacheable categories without a filter[] alloc.
478
- let lengthCount = 0;
479
- let timeCount = 0;
480
- for (const rd of rules) {
481
- if (!sameGroups(rd.groups, fieldGroups)) {
482
- continue;
483
- }
484
- if (rd.rule.plan?.cacheKey === 'length') {
485
- lengthCount += 1;
486
- }
487
- else if (rd.rule.plan?.cacheKey === 'time') {
488
- timeCount += 1;
489
- }
490
- }
491
- const sk = sanitizeKey(fieldKey);
492
- const lengthVar = lengthCount > 1 ? `${GEN.arr}${sk}len` : null;
493
- const timeVar = timeCount > 1 ? `${GEN.arr}${sk}time` : null;
494
- if (lengthVar) {
495
- code += `${indent}var ${lengthVar} = ${varName}.length;\n`;
496
- }
497
- if (timeVar) {
498
- code += `${indent}var ${timeVar} = ${varName}.getTime();\n`;
499
- }
500
- for (const rd of rules) {
501
- const sg = sameGroups(rd.groups, fieldGroups); // cache once — was called 3× per rule
502
- const ruleEmitCtx = makeRuleEmitCtx(emitCtx, fieldKey, varName, rd, ctx);
503
- const gatedCtx = insideTypeGate ? { ...ruleEmitCtx, insideTypeGate: true } : ruleEmitCtx;
504
- let emitted;
505
- if (sg && rd.rule.plan && (lengthVar || timeVar)) {
506
- const cache = {};
507
- if (rd.rule.plan.cacheKey === 'length' && lengthVar) {
508
- cache.length = lengthVar;
509
- }
510
- if (rd.rule.plan.cacheKey === 'time' && timeVar) {
511
- cache.time = timeVar;
512
- }
513
- emitted = emitRulePlan(varName, gatedCtx, rd.rule.ruleName, rd.rule.plan, cache, insideTypeGate);
514
- }
515
- else {
516
- emitted = rd.rule.emit(varName, gatedCtx);
517
- }
518
- if (!emitted) {
519
- continue;
520
- } // empty emit (e.g., asserter fully subsumed by gate)
521
- const ruleCode = sg ? emitted : wrapGroupsGuard(rd, emitted);
522
- code += indent + ruleCode.replace(/\n/g, '\n' + indent) + '\n';
523
- }
524
- return code;
525
- }
526
- // ─────────────────────────────────────────────────────────────────────────────
527
- // wrapGroupsGuard — per-rule validation groups check wrapper (§M4)
528
- // ─────────────────────────────────────────────────────────────────────────────
529
- /**
530
- * When rd.groups is set, only execute code if there is an intersection with runtime __bk$groups.
531
- * Rules without groups always execute (preserves existing behavior).
532
- */
533
- function wrapGroupsGuard(rd, code) {
534
- if (!rd.groups || rd.groups.length === 0) {
535
- return code;
536
- }
537
- return `if ((${GEN.group0} === null && !${GEN.groupsSet}) || ${buildGroupsHasExpr(GEN.group0, GEN.groupsSet, rd.groups)}) {\n${code}\n}\n`;
538
- }
539
- function sameGroups(a, b) {
540
- if (!a || a.length === 0) {
541
- return !b || b.length === 0;
542
- }
543
- if (!b || a.length !== b.length) {
544
- return false;
545
- }
546
- for (let i = 0; i < a.length; i++) {
547
- if (a[i] !== b[i]) {
548
- return false;
549
- }
550
- }
551
- return true;
552
- }
553
- // ─────────────────────────────────────────────────────────────────────────────
554
- // generateConversionCode — enableImplicitConversion conversion code generation
555
- // ─────────────────────────────────────────────────────────────────────────────
556
- function generateConversionCode(targetType, varName, fieldKey, skipVar, // null = stopAtFirstError
557
- collectErrors, emitCtx) {
558
- const failCode = collectErrors
559
- ? `${emitCtx.fail('conversionFailed')}; ${skipVar} = true;`
560
- : emitCtx.fail('conversionFailed') + ';';
561
- switch (targetType) {
562
- case 'string':
563
- return ` ${varName} = String(${varName});\n`;
564
- case 'number':
565
- return ` ${varName} = Number(${varName});\n if (isNaN(${varName})) { ${failCode} }\n`;
566
- case 'boolean':
567
- return (` if (${varName} === 'true' || ${varName} === '1' || ${varName} === 1) ${varName} = true;\n` +
568
- ` else if (${varName} === 'false' || ${varName} === '0' || ${varName} === 0) ${varName} = false;\n` +
569
- ` else { ${failCode} }\n`);
570
- case 'date':
571
- return ` ${varName} = new Date(${varName});\n if (isNaN(${varName}.getTime())) { ${failCode} }\n`;
572
- default:
573
- throw new BakerError(`Unknown implicit conversion type: "${targetType}" for field "${fieldKey}"`);
574
- }
575
- }
576
- /** `@Type`() primitive builtin → target type mapping */
577
- const PRIMITIVE_TYPE_HINTS = {
578
- Number: 'number',
579
- Boolean: 'boolean',
580
- String: 'string',
581
- Date: 'date',
582
- };
583
- /** Asserter rule name → gate type mapping */
584
- const ASSERTER_TO_GATE = {
585
- isString: 'string',
586
- isNumber: 'number',
587
- isBoolean: 'boolean',
588
- isDate: 'date',
589
- isInt: 'number',
590
- isArray: 'array',
591
- isObject: 'object',
592
- };
593
- /** Asserters whose gate check fully subsumes the rule (skip emit inside gate) */
594
- const GATE_ONLY_ASSERTERS = new Set(['isString', 'isBoolean', 'isDate', 'isArray', 'isObject']);
595
- /** categorizeRules — separate each/nonEach rules, detect mixed gate conflicts */
596
- function categorizeRules(fieldKey, validation) {
597
- // Single-pass partition — was 9 separate .filter() passes over the same array, each allocating
598
- // a fresh intermediate. For a field with N rules, runs at seal time only but adds up across DTOs.
599
- const each = [];
600
- const generalRules = [];
601
- const typedBuckets = {
602
- string: [],
603
- number: [],
604
- boolean: [],
605
- date: [],
606
- array: [],
607
- object: [],
608
- };
609
- for (const rd of validation) {
610
- if (rd.each) {
611
- each.push(rd);
612
- continue;
613
- }
614
- const reqType = rd.rule.requiresType;
615
- if (reqType !== undefined) {
616
- typedBuckets[reqType].push(rd);
617
- }
618
- else {
619
- generalRules.push(rd);
620
- }
621
- }
622
- // Mixed gate conflict detection — at most one bucket should be non-empty
623
- let chosen = undefined;
624
- let activeTypes = null;
625
- for (const t of ['string', 'number', 'boolean', 'date', 'array', 'object']) {
626
- const deps = typedBuckets[t];
627
- if (deps.length === 0) {
628
- continue;
629
- }
630
- if (chosen) {
631
- // Late allocation: only build the array when we actually need to report a conflict
632
- if (activeTypes === null) {
633
- activeTypes = [chosen.type];
634
- }
635
- activeTypes.push(t);
636
- }
637
- else {
638
- chosen = { type: t, deps };
639
- }
640
- }
641
- if (activeTypes) {
642
- throw new BakerError(`Field "${fieldKey}" has conflicting requiresType: ${activeTypes.join(', ')}`);
643
- }
644
- return { each, generalRules, typedDeps: chosen };
645
- }
646
- /** resolveTypeGate — determine effective gate type from asserters/conversion/type hints */
647
- function resolveTypeGate(fieldKey, categorized, meta, ctx) {
648
- const { generalRules, typedDeps } = categorized;
649
- const hasTypedDeps = !!typedDeps;
650
- const gateType = typedDeps?.type ?? null;
651
- const gateDeps = typedDeps?.deps ?? [];
652
- // Find type asserter in generalRules matching gate type
653
- let typeAsserterIdx = -1;
654
- if (gateType) {
655
- typeAsserterIdx = generalRules.findIndex(rd => ASSERTER_TO_GATE[rd.rule.ruleName] === gateType);
656
- }
657
- // enableImplicitConversion check — skip if explicit @Transform for deserialize direction
658
- const enableConversion = !!ctx.options?.enableImplicitConversion && !meta?.transform.some(td => !td.options?.serializeOnly);
659
- // enableImplicitConversion: asserter-only gate inference — generate conversion gate even for standalone @IsNumber() usage
660
- let asserterInferredGate = null;
661
- if (!hasTypedDeps && enableConversion && typeAsserterIdx < 0) {
662
- for (let i = 0; i < generalRules.length; i++) {
663
- const gate = ASSERTER_TO_GATE[generalRules[i].rule.ruleName];
664
- if (gate) {
665
- typeAsserterIdx = i;
666
- asserterInferredGate = gate;
667
- break;
668
- }
669
- }
670
- }
671
- const typeAsserter = typeAsserterIdx >= 0 ? generalRules[typeAsserterIdx] : undefined;
672
- // @Type() primitive hint — infer conversion target when no typed deps exist
673
- let typeHintGate = null;
674
- if (!hasTypedDeps && !asserterInferredGate && enableConversion && meta?.type?.fn) {
675
- try {
676
- const raw = meta.type.fn();
677
- const typeCtor = Array.isArray(raw) ? raw[0] : raw;
678
- typeHintGate = typeCtor ? (PRIMITIVE_TYPE_HINTS[typeCtor.name] ?? null) : null;
679
- }
680
- catch (e) {
681
- throw new BakerError(`field "${fieldKey}": @Field type function threw: ${e.message}`, { cause: e });
682
- }
683
- }
684
- return {
685
- effectiveGateType: gateType ?? asserterInferredGate ?? typeHintGate,
686
- gateDeps,
687
- typeAsserterIdx,
688
- typeAsserter,
689
- enableConversion,
690
- asserterInferredGate,
691
- typeHintGate,
692
- };
693
- }
694
- /** emitTypedRules — generate type gate + inner validation code */
695
- function emitTypedRules(fieldKey, varName, collectErrors, emitCtx, ctx, config, fieldGroups) {
696
- let code = '';
697
- const sk = sanitizeKey(fieldKey); // cached — was called up to 4× in this function before
698
- const { effectiveGateType, gateCondition, gateErrorCode, gateEmitCtx, otherGeneral, gateDeps, typeAsserter, enableConversion } = config;
699
- // Helper: emit inner validation rules
700
- const emitInnerRules = (indent) => {
701
- const rules = [];
702
- // typeAsserter emit — skip GATE_ONLY_ASSERTERS (isString, isBoolean) as they fully overlap with the gate
703
- if (typeAsserter && !GATE_ONLY_ASSERTERS.has(typeAsserter.rule.ruleName)) {
704
- rules.push(typeAsserter);
705
- }
706
- rules.push(...otherGeneral, ...gateDeps);
707
- return emitRuleList(fieldKey, varName, rules, emitCtx, ctx, indent, fieldGroups, true);
708
- };
709
- if (collectErrors) {
710
- const canConvert = enableConversion &&
711
- (effectiveGateType === 'string' ||
712
- effectiveGateType === 'number' ||
713
- effectiveGateType === 'boolean' ||
714
- effectiveGateType === 'date');
715
- if (canConvert) {
716
- // Conversion mode: try convert on gate failure, skip field if conversion fails
717
- const skipVar = `${GEN.skip}${sk}`;
718
- code += `var ${skipVar} = false;\n`;
719
- code += `if (${gateCondition}) {\n`;
720
- code += generateConversionCode(effectiveGateType, varName, fieldKey, skipVar, true, emitCtx);
721
- code += `}\n`;
722
- code += `if (!${skipVar}) {\n`;
723
- if (ctx.validateOnly) {
724
- code += emitInnerRules(' ');
725
- }
726
- else {
727
- const markVar = `${GEN.mark}${sk}`;
728
- code += ` var ${markVar} = ${GEN.errList}.length;\n`;
729
- code += emitInnerRules(' ');
730
- code += ` if (${GEN.errList}.length === ${markVar}) ${GEN.out}[${JSON.stringify(fieldKey)}] = ${varName};\n`;
731
- }
732
- code += `}\n`;
733
- }
734
- else {
735
- code += `if (${gateCondition}) ${gateEmitCtx.fail(gateErrorCode)};\n`;
736
- code += `else {\n`;
737
- if (ctx.validateOnly) {
738
- code += emitInnerRules(' ');
739
- }
740
- else {
741
- const markVar = `${GEN.mark}${sk}`;
742
- code += ` var ${markVar} = ${GEN.errList}.length;\n`;
743
- code += emitInnerRules(' ');
744
- code += ` if (${GEN.errList}.length === ${markVar}) ${GEN.out}[${JSON.stringify(fieldKey)}] = ${varName};\n`;
745
- }
746
- code += `}\n`;
747
- }
748
- }
749
- else {
750
- const canConvert = enableConversion &&
751
- (effectiveGateType === 'string' ||
752
- effectiveGateType === 'number' ||
753
- effectiveGateType === 'boolean' ||
754
- effectiveGateType === 'date');
755
- if (canConvert) {
756
- code += `if (${gateCondition}) {\n`;
757
- code += generateConversionCode(effectiveGateType, varName, fieldKey, null, false, emitCtx);
758
- code += `}\n`;
759
- code += emitInnerRules('');
760
- if (!ctx.validateOnly) {
761
- code += `${GEN.out}[${JSON.stringify(fieldKey)}] = ${varName};\n`;
762
- }
763
- }
764
- else {
765
- code += `if (${gateCondition}) ${gateEmitCtx.fail(gateErrorCode)};\n`;
766
- code += emitInnerRules('');
767
- if (!ctx.validateOnly) {
768
- code += `${GEN.out}[${JSON.stringify(fieldKey)}] = ${varName};\n`;
769
- }
770
- }
771
- }
772
- return code;
773
- }
774
- /** emitGeneralRules — generate type-agnostic rule code */
775
- function emitGeneralRules(fieldKey, varName, generalRules, collectErrors, emitCtx, ctx, fieldGroups) {
776
- let code = '';
777
- if (collectErrors) {
778
- if (generalRules.length === 0) {
779
- if (!ctx.validateOnly) {
780
- code += `${GEN.out}[${JSON.stringify(fieldKey)}] = ${varName};\n`;
781
- }
782
- }
783
- else if (ctx.validateOnly) {
784
- code += emitRuleList(fieldKey, varName, generalRules, emitCtx, ctx, '', fieldGroups);
785
- }
786
- else {
787
- const markVar = `${GEN.mark}${sanitizeKey(fieldKey)}`;
788
- code += `var ${markVar} = ${GEN.errList}.length;\n`;
789
- code += emitRuleList(fieldKey, varName, generalRules, emitCtx, ctx, '', fieldGroups);
790
- code += `if (${GEN.errList}.length === ${markVar}) ${GEN.out}[${JSON.stringify(fieldKey)}] = ${varName};\n`;
791
- }
792
- }
793
- else {
794
- code += emitRuleList(fieldKey, varName, generalRules, emitCtx, ctx, '', fieldGroups);
795
- if (!ctx.validateOnly) {
796
- code += `${GEN.out}[${JSON.stringify(fieldKey)}] = ${varName};\n`;
797
- }
798
- }
799
- return code;
800
- }
801
- /** emitEachRules — generate Array/Set/Map each code */
802
- function emitEachRules(fieldKey, varName, eachRules, collectErrors, emitCtx, ctx, fieldGroups) {
803
- let code = '';
804
- for (const rd of eachRules) {
805
- // pathKey must honor ctx.pathPrefix so inlined nested DTOs report full path.
806
- // Without this, validate(Parent, ...) returned `tags[1]` while deserialize returned `nested.tags[1]`.
807
- const pathKey = ctx.pathPrefix ? `${ctx.pathPrefix}+${JSON.stringify(fieldKey)}` : JSON.stringify(fieldKey);
808
- const sk = sanitizeKey(fieldKey);
809
- const iVar = `${GEN.index}${sk}`;
810
- const siVar = `${GEN.setIdx}${sk}`;
811
- const svVar = `${GEN.setVal}${sk}`;
812
- const miVar = `${GEN.mapIdx}${sk}`;
813
- const mvVar = `${GEN.mapVal}${sk}`;
814
- const extra = computeRuleExtras(rd, fieldKey, varName, ctx);
815
- // Cache the groups-guard predicate once — was previously evaluated twice (open + close)
816
- const rdGroups = rd.groups && rd.groups.length > 0 && !sameGroups(rd.groups, fieldGroups) ? rd.groups : null;
817
- const eachGuardOpen = rdGroups
818
- ? `if ((${GEN.group0} === null && !${GEN.groupsSet}) || ${buildGroupsHasExpr(GEN.group0, GEN.groupsSet, rdGroups)}) {\n`
819
- : '';
820
- const eachGuardClose = rdGroups ? '}\n' : '';
821
- // Collection descriptors: [idxVar, elemExpr, loopHeader, counterDecl, counterInc]
822
- const collections = [
823
- {
824
- guard: `Array.isArray(${varName})`,
825
- idxVar: iVar,
826
- elemExpr: `${varName}[${iVar}]`,
827
- loopHeader: `for (var ${iVar}=0; ${iVar}<${varName}.length; ${iVar}++)`,
828
- counterDecl: '',
829
- counterInc: '',
830
- },
831
- {
832
- guard: `${varName} instanceof Set`,
833
- idxVar: siVar,
834
- elemExpr: svVar,
835
- loopHeader: `for (var ${svVar} of ${varName})`,
836
- counterDecl: `var ${siVar} = 0;\n`,
837
- counterInc: `${siVar}++;\n`,
838
- },
839
- {
840
- guard: `${varName} instanceof Map`,
841
- idxVar: miVar,
842
- elemExpr: mvVar,
843
- loopHeader: `for (var ${mvVar} of ${varName}.values())`,
844
- counterDecl: `var ${miVar} = 0;\n`,
845
- counterInc: `${miVar}++;\n`,
846
- },
847
- ];
848
- // Single prefix var shared across all collection branches — only one branch executes
849
- // at runtime, so reusing the same identifier avoids 3 hoisted-but-dead var declarations.
850
- const prefixVar = `__bk$ep_${sk}`;
851
- const emitCollectionBlock = (col) => {
852
- const failFn = (c) => collectErrors
853
- ? `${GEN.errList}.push({path:${prefixVar}+${col.idxVar}+']',code:${JSON.stringify(c)}${extra}})`
854
- : ctx.validateOnly
855
- ? `return [{path:${prefixVar}+${col.idxVar}+']',code:${JSON.stringify(c)}${extra}}]`
856
- : `return err([{path:${prefixVar}+${col.idxVar}+']',code:${JSON.stringify(c)}${extra}}])`;
857
- const colEmitCtx = { ...emitCtx, fail: failFn };
858
- let block = '';
859
- block += ` ${col.counterDecl}`;
860
- block += ` ${col.loopHeader} {\n`;
861
- block += ' ' + rd.rule.emit(col.elemExpr, colEmitCtx) + '\n';
862
- if (col.counterInc) {
863
- block += ` ${col.counterInc}`;
864
- }
865
- block += ` }\n`;
866
- return block;
867
- };
868
- // Compute collection kind once via integer dispatch — eliminates 2-3× repeated
869
- // Array.isArray / instanceof Set / instanceof Map evaluation.
870
- // prefixVar is declared once here and reused by all three branches.
871
- const kindVar = `__bk$ck${sk}`;
872
- code += eachGuardOpen;
873
- code += `var ${kindVar} = Array.isArray(${varName})?1:(${varName} instanceof Set?2:(${varName} instanceof Map?3:0));\n`;
874
- code += `var ${prefixVar} = ${pathKey}+'[';\n`;
875
- if (collectErrors) {
876
- code += `if (${kindVar} === 1) {\n`;
877
- code += emitCollectionBlock(collections[0]);
878
- code += `} else if (${kindVar} === 2) {\n`;
879
- code += emitCollectionBlock(collections[1]);
880
- code += `} else if (${kindVar} === 3) {\n`;
881
- code += emitCollectionBlock(collections[2]);
882
- code += `} else { ${emitCtx.fail('isArray')}; }\n`;
883
- }
884
- else {
885
- code += `if (${kindVar} === 0) ${emitCtx.fail('isArray')};\n`;
886
- code += `if (${kindVar} === 1) {\n`;
887
- code += emitCollectionBlock(collections[0]);
888
- code += `} else if (${kindVar} === 2) {\n`;
889
- code += emitCollectionBlock(collections[1]);
890
- code += `} else if (${kindVar} === 3) {\n`;
891
- code += emitCollectionBlock(collections[2]);
892
- code += `}\n`;
893
- }
894
- code += eachGuardClose;
895
- }
896
- return code;
897
- }
898
- /** buildRulesCode — orchestrator that composes categorize → resolve → emit phases */
899
- function buildRulesCode(fieldKey, varName, validation, collectErrors, emitCtx, ctx, meta, fieldGroups) {
900
- // Phase 1: Categorize rules
901
- const categorized = categorizeRules(fieldKey, validation);
902
- // Phase 2: Resolve type gate
903
- const resolved = resolveTypeGate(fieldKey, categorized, meta, ctx);
904
- let code = '';
905
- // Phase 3: Emit typed or general rules
906
- const hasTypedDeps = !!categorized.typedDeps;
907
- if (hasTypedDeps || resolved.asserterInferredGate || resolved.typeHintGate) {
908
- // Other general rules (excluding the type asserter)
909
- const otherGeneral = resolved.typeAsserter
910
- ? categorized.generalRules.filter((_, i) => i !== resolved.typeAsserterIdx)
911
- : categorized.generalRules;
912
- // Generate type gate condition — date uses instanceof, others use typeof
913
- let gateCondition;
914
- let gateErrorCode;
915
- if (resolved.typeAsserter) {
916
- gateErrorCode = resolved.typeAsserter.rule.ruleName;
917
- }
918
- else if (resolved.gateDeps.length > 0) {
919
- gateErrorCode = resolved.gateDeps[0].rule.ruleName;
920
- }
921
- else {
922
- gateErrorCode = 'conversionFailed'; // @Type hint only — no asserter or deps
923
- }
924
- if (resolved.effectiveGateType === 'date') {
925
- gateCondition = `!(${varName} instanceof Date) || isNaN(${varName}.getTime())`;
926
- }
927
- else if (resolved.effectiveGateType === 'array') {
928
- gateCondition = `!Array.isArray(${varName})`;
929
- }
930
- else if (resolved.effectiveGateType === 'object') {
931
- gateCondition = `typeof ${varName} !== 'object' || ${varName} === null || Array.isArray(${varName})`;
932
- }
933
- else if (resolved.effectiveGateType === 'number') {
934
- gateCondition = `typeof ${varName} !== 'number' || isNaN(${varName})`;
935
- }
936
- else {
937
- gateCondition = `typeof ${varName} !== '${resolved.effectiveGateType}'`;
938
- }
939
- // Type gate fail — reflect message/context if typeAsserter rd exists
940
- const gateEmitCtx = resolved.typeAsserter ? makeRuleEmitCtx(emitCtx, fieldKey, varName, resolved.typeAsserter, ctx) : emitCtx;
941
- code += emitTypedRules(fieldKey, varName, collectErrors, emitCtx, ctx, {
942
- effectiveGateType: resolved.effectiveGateType,
943
- gateCondition,
944
- gateErrorCode,
945
- gateEmitCtx,
946
- otherGeneral,
947
- gateDeps: resolved.gateDeps,
948
- typeAsserter: resolved.typeAsserter,
949
- enableConversion: resolved.enableConversion,
950
- }, fieldGroups);
951
- }
952
- else {
953
- code += emitGeneralRules(fieldKey, varName, categorized.generalRules, collectErrors, emitCtx, ctx, fieldGroups);
954
- }
955
- // Phase 4: Emit each rules
956
- code += emitEachRules(fieldKey, varName, categorized.each, collectErrors, emitCtx, ctx, fieldGroups);
957
- return code;
958
- }
959
- // ─────────────────────────────────────────────────────────────────────────────
960
- // generateCollectionCode — Map/Set auto conversion
961
- // ─────────────────────────────────────────────────────────────────────────────
962
- function generateCollectionCode(fieldKey, varName, meta, ctx, emitCtx) {
963
- const { collectErrors, execs } = ctx;
964
- const sk = sanitizeKey(fieldKey);
965
- const collection = meta.type.collection;
966
- const awaitKw = ctx.isAsync ? 'await ' : '';
967
- // nested DTO executor (if present)
968
- let execIdx = -1;
969
- if (meta.type.resolvedCollectionValue) {
970
- const nestedSealed = getSealed(meta.type.resolvedCollectionValue);
971
- execIdx = execs.length;
972
- execs.push(nestedSealed);
973
- }
974
- let code = '';
975
- if (collection === 'Set') {
976
- // input: array → Set
977
- code += `if (Array.isArray(${varName})) {\n`;
978
- // array-level validation rules (e.g. arrayMinSize)
979
- const nonEachRules = meta.validation.filter(rd => !rd.each);
980
- code += emitRuleList(fieldKey, varName, nonEachRules, emitCtx, ctx, ' ');
981
- if (execIdx >= 0) {
982
- // nested DTO Set
983
- const iVar = `${GEN.index}${sk}`;
984
- code += ` var ${GEN.arr}${sk} = new Set();\n`;
985
- code += ` for (var ${iVar}=0; ${iVar}<${varName}.length; ${iVar}++) {\n`;
986
- code += ` var ${GEN.result}${sk} = ${awaitKw}execs[${execIdx}].deserialize(${varName}[${iVar}], opts);\n`;
987
- code += ` if (isErr(${GEN.result}${sk})) {\n`;
988
- if (collectErrors) {
989
- code += ` var ${GEN.errors}${sk} = ${GEN.result}${sk}.data;\n`;
990
- code += ` var __bk$pp${sk} = ${JSON.stringify(fieldKey)}+'['+${iVar}+'].';\n`;
991
- code += ` for (var ${GEN.nestedIdx}${sk}=0; ${GEN.nestedIdx}${sk}<${GEN.errors}${sk}.length; ${GEN.nestedIdx}${sk}++) {\n`;
992
- code +=
993
- ` ` +
994
- nestedErrPush(GEN.errList, `__bk$pp${sk}+${GEN.errors}${sk}[${GEN.nestedIdx}${sk}].path`, `${GEN.errors}${sk}[${GEN.nestedIdx}${sk}]`, `__ne${sk}`);
995
- code += ` }\n`;
996
- }
997
- else {
998
- code += ` var ${GEN.errors}${sk} = ${GEN.result}${sk}.data;\n`;
999
- code += ` var __bk$pp${sk} = ${JSON.stringify(fieldKey)}+'['+${iVar}+'].';\n`;
1000
- code += ` ` + nestedErrReturn(`__bk$pp${sk}+${GEN.errors}${sk}[0].path`, `${GEN.errors}${sk}[0]`, `__ne${sk}`);
1001
- }
1002
- code += ` } else { ${GEN.arr}${sk}.add(${GEN.result}${sk}); }\n`;
1003
- code += ` }\n`;
1004
- code += ` ${GEN.out}[${JSON.stringify(fieldKey)}] = ${GEN.arr}${sk};\n`;
1005
- }
1006
- else {
1007
- // primitive Set
1008
- code += ` ${GEN.out}[${JSON.stringify(fieldKey)}] = new Set(${varName});\n`;
1009
- }
1010
- // each validation rules (per element)
1011
- const eachRules = meta.validation.filter(rd => rd.each);
1012
- if (eachRules.length > 0) {
1013
- const siVar = `${GEN.setIdx}${sk}`;
1014
- const svVar = `${GEN.setVal}${sk}`;
1015
- code += ` var ${siVar} = 0;\n`;
1016
- code += ` for (var ${svVar} of ${GEN.out}[${JSON.stringify(fieldKey)}]) {\n`;
1017
- for (const rd of eachRules) {
1018
- const extra = computeRuleExtras(rd, fieldKey, varName, ctx);
1019
- const failFn = (c) => collectErrors
1020
- ? `${GEN.errList}.push({path:${JSON.stringify(fieldKey)}+'['+${siVar}+']',code:${JSON.stringify(c)}${extra}})`
1021
- : `return err([{path:${JSON.stringify(fieldKey)}+'['+${siVar}+']',code:${JSON.stringify(c)}${extra}}])`;
1022
- const colEmitCtx = { ...emitCtx, fail: failFn };
1023
- code += ` ${rd.rule.emit(svVar, colEmitCtx)}\n`;
1024
- }
1025
- code += ` ${siVar}++;\n`;
1026
- code += ` }\n`;
1027
- }
1028
- code += `} else { ${emitCtx.fail('isArray')}; }\n`;
1029
- }
1030
- else {
1031
- // Map: input plain object → Map
1032
- code += `if (${varName} != null && typeof ${varName} === 'object' && !Array.isArray(${varName})) {\n`;
1033
- if (execIdx >= 0) {
1034
- // nested DTO Map — indexed Object.keys loop (measured 2-30× faster than for-in+hasOwn on Bun/JSC)
1035
- const kVar = `${GEN.key}${sk}`;
1036
- const ksVar = `__bk$mk${sk}`;
1037
- const iVarMap = `__bk$mi${sk}`;
1038
- code += ` var ${GEN.arr}${sk} = new Map();\n`;
1039
- code += ` var ${ksVar} = Object.keys(${varName});\n`;
1040
- code += ` for (var ${iVarMap}=0; ${iVarMap}<${ksVar}.length; ${iVarMap}++) {\n`;
1041
- code += ` var ${kVar} = ${ksVar}[${iVarMap}];\n`;
1042
- code += ` var ${GEN.result}${sk} = ${awaitKw}execs[${execIdx}].deserialize(${varName}[${kVar}], opts);\n`;
1043
- code += ` if (isErr(${GEN.result}${sk})) {\n`;
1044
- if (collectErrors) {
1045
- code += ` var ${GEN.errors}${sk} = ${GEN.result}${sk}.data;\n`;
1046
- code += ` var __bk$pp${sk} = ${JSON.stringify(fieldKey)}+'['+${kVar}+'].';\n`;
1047
- code += ` for (var ${GEN.nestedIdx}${sk}=0; ${GEN.nestedIdx}${sk}<${GEN.errors}${sk}.length; ${GEN.nestedIdx}${sk}++) {\n`;
1048
- code +=
1049
- ` ` +
1050
- nestedErrPush(GEN.errList, `__bk$pp${sk}+${GEN.errors}${sk}[${GEN.nestedIdx}${sk}].path`, `${GEN.errors}${sk}[${GEN.nestedIdx}${sk}]`, `__ne${sk}`);
1051
- code += ` }\n`;
1052
- }
1053
- else {
1054
- code += ` var ${GEN.errors}${sk} = ${GEN.result}${sk}.data;\n`;
1055
- code += ` var __bk$pp${sk} = ${JSON.stringify(fieldKey)}+'['+${kVar}+'].';\n`;
1056
- code += ` ` + nestedErrReturn(`__bk$pp${sk}+${GEN.errors}${sk}[0].path`, `${GEN.errors}${sk}[0]`, `__ne${sk}`);
1057
- }
1058
- code += ` } else { ${GEN.arr}${sk}.set(${kVar}, ${GEN.result}${sk}); }\n`;
1059
- code += ` }\n`;
1060
- code += ` ${GEN.out}[${JSON.stringify(fieldKey)}] = ${GEN.arr}${sk};\n`;
1061
- }
1062
- else {
1063
- // primitive Map — indexed Object.keys loop
1064
- const ksVar = `__bk$mk${sk}`;
1065
- const iVarMap = `__bk$mi${sk}`;
1066
- code += ` var ${GEN.arr}${sk} = new Map();\n`;
1067
- code += ` var ${ksVar} = Object.keys(${varName});\n`;
1068
- code += ` for (var ${iVarMap}=0; ${iVarMap}<${ksVar}.length; ${iVarMap}++) {\n`;
1069
- code += ` var ${GEN.key}${sk} = ${ksVar}[${iVarMap}];\n`;
1070
- code += ` ${GEN.arr}${sk}.set(${GEN.key}${sk}, ${varName}[${GEN.key}${sk}]);\n`;
1071
- code += ` }\n`;
1072
- code += ` ${GEN.out}[${JSON.stringify(fieldKey)}] = ${GEN.arr}${sk};\n`;
1073
- }
1074
- code += `} else { ${emitCtx.fail('isObject')}; }\n`;
1075
- }
1076
- return code;
1077
- }
1078
- // ─────────────────────────────────────────────────────────────────────────────
1079
- // generateNestedCode — @ValidateNested + @Type (§8.1, §8.2)
1080
- // ─────────────────────────────────────────────────────────────────────────────
1081
- function generateNestedCode(fieldKey, varName, meta, ctx, emitCtx) {
1082
- const { collectErrors, execs } = ctx;
1083
- if (!meta.type) {
1084
- return `${GEN.out}[${JSON.stringify(fieldKey)}] = ${varName};\n`;
1085
- }
1086
- let code = '';
1087
- const sk = sanitizeKey(fieldKey);
1088
- if (meta.type.discriminator) {
1089
- // §8.3 discriminator
1090
- const discProp = JSON.stringify(meta.type.discriminator.property);
1091
- code += `var ${GEN.disc}${sk} = ${varName} && ${varName}[${discProp}];\n`;
1092
- code += `switch (${GEN.disc}${sk}) {\n`;
1093
- for (const sub of meta.type.discriminator.subTypes) {
1094
- const nestedSealed = getSealed(sub.value);
1095
- const execIdx = execs.length;
1096
- execs.push(nestedSealed);
1097
- const awaitKwD = ctx.isAsync ? 'await ' : '';
1098
- code += ` case ${JSON.stringify(sub.name)}:\n`;
1099
- code += ` var ${GEN.result}${sk} = ${awaitKwD}execs[${execIdx}].deserialize(${varName}, opts);\n`;
1100
- code += generateNestedResultCode(fieldKey, `${GEN.result}${sk}`, collectErrors, ctx.pathPrefix);
1101
- code += ` break;\n`;
1102
- }
1103
- const validSubTypeNamesJson = JSON.stringify(meta.type.discriminator.subTypes.map(s => s.name));
1104
- const discPathExpr = emitCtx.pathExpr ?? JSON.stringify(fieldKey);
1105
- const discValueExpr = `${GEN.disc}${sk}`;
1106
- if (collectErrors) {
1107
- code += ` default: ${GEN.errList}.push({path:${discPathExpr},code:'invalidDiscriminator',context:{received:${discValueExpr},validSubTypes:${validSubTypeNamesJson}}});\n`;
1108
- }
1109
- else if (ctx.validateOnly) {
1110
- code += ` default: return [{path:${discPathExpr},code:'invalidDiscriminator',context:{received:${discValueExpr},validSubTypes:${validSubTypeNamesJson}}}];\n`;
1111
- }
1112
- else {
1113
- code += ` default: return err([{path:${discPathExpr},code:'invalidDiscriminator',context:{received:${discValueExpr},validSubTypes:${validSubTypeNamesJson}}}]);\n`;
1114
- }
1115
- code += `}\n`;
1116
- // keepDiscriminatorProperty: preserve discriminator property in result object (PB-3)
1117
- if (meta.type.keepDiscriminatorProperty) {
1118
- const fkJson = JSON.stringify(fieldKey);
1119
- code += `{var __dh=${GEN.out}[${fkJson}]; if(__dh!=null) __dh[${discProp}]=${GEN.disc}${sk};}\n`;
1120
- }
1121
- }
1122
- else {
1123
- // §8.1 simple nested or §8.2 each array
1124
- const nestedCls = meta.type.resolvedClass ?? meta.type.fn();
1125
- const nestedSealed = getSealed(nestedCls);
1126
- const execIdx = execs.length;
1127
- execs.push(nestedSealed);
1128
- // Check if validateNested each (array) — meta.type is already proven non-null above
1129
- const hasEach = meta.type.isArray || meta.flags.validateNestedEach || meta.validation.some(rd => rd.each);
1130
- if (hasEach) {
1131
- const iVar = `${GEN.index}${sk}`;
1132
- const awaitKwE = ctx.isAsync ? 'await ' : '';
1133
- code += `if (Array.isArray(${varName})) {\n`;
1134
- // Emit non-each array-level validation rules (e.g. @ArrayMinSize, @ArrayMaxSize)
1135
- const nonEachRules = meta.validation.filter(rd => !rd.each);
1136
- code += emitRuleList(fieldKey, varName, nonEachRules, emitCtx, ctx, ' ');
1137
- code += ` var ${GEN.arr}${sk} = [];\n`;
1138
- code += ` for (var ${iVar}=0; ${iVar}<${varName}.length; ${iVar}++) {\n`;
1139
- code += ` var ${GEN.result}${sk} = ${awaitKwE}execs[${execIdx}].deserialize(${varName}[${iVar}], opts);\n`;
1140
- code += ` if (isErr(${GEN.result}${sk})) {\n`;
1141
- if (collectErrors) {
1142
- code += ` var ${GEN.errors}${sk} = ${GEN.result}${sk}.data;\n`;
1143
- code += ` var __bk$pp${sk} = ${JSON.stringify(fieldKey)}+'['+${iVar}+'].';\n`;
1144
- code += ` for (var ${GEN.nestedIdx}${sk}=0; ${GEN.nestedIdx}${sk}<${GEN.errors}${sk}.length; ${GEN.nestedIdx}${sk}++) {\n`;
1145
- code +=
1146
- ` ` +
1147
- nestedErrPush(GEN.errList, `__bk$pp${sk}+${GEN.errors}${sk}[${GEN.nestedIdx}${sk}].path`, `${GEN.errors}${sk}[${GEN.nestedIdx}${sk}]`, `__ne${sk}`);
1148
- code += ` }\n`;
1149
- }
1150
- else {
1151
- code += ` var ${GEN.errors}${sk} = ${GEN.result}${sk}.data;\n`;
1152
- code += ` var __bk$pp${sk} = ${JSON.stringify(fieldKey)}+'['+${iVar}+'].';\n`;
1153
- code += ` ` + nestedErrReturn(`__bk$pp${sk}+${GEN.errors}${sk}[0].path`, `${GEN.errors}${sk}[0]`, `__ne${sk}`);
1154
- }
1155
- code += ` } else { ${GEN.arr}${sk}.push(${GEN.result}${sk}); }\n`;
1156
- code += ` }\n`;
1157
- code += ` ${GEN.out}[${JSON.stringify(fieldKey)}] = ${GEN.arr}${sk};\n`;
1158
- code += `} else { ${emitCtx.fail('isArray')}; }\n`;
1159
- }
1160
- else {
1161
- const awaitKwS = ctx.isAsync ? 'await ' : '';
1162
- code += `if (${varName} != null && typeof ${varName} === 'object' && !Array.isArray(${varName})) {\n`;
1163
- code += ` var ${GEN.result}${sk} = ${awaitKwS}execs[${execIdx}].deserialize(${varName}, opts);\n`;
1164
- code += generateNestedResultCode(fieldKey, `${GEN.result}${sk}`, collectErrors, ctx.pathPrefix);
1165
- code += `} else { ${emitCtx.fail('isObject')}; }\n`;
1166
- }
1167
- }
1168
- return code;
1169
- }
1170
- function generateNestedResultCode(fieldKey, resultVar, collectErrors, pathPrefix) {
1171
- const sk = sanitizeKey(fieldKey);
1172
- // Prepend the current scope's path prefix so an executor reached from inside an inlined block
1173
- // (e.g. a circular nested DTO) keeps the full path, not just `fieldKey.`.
1174
- const ppValue = pathPrefix ? `${pathPrefix}+${JSON.stringify(fieldKey + '.')}` : JSON.stringify(fieldKey + '.');
1175
- if (collectErrors) {
1176
- const errItem = `${GEN.errors}${sk}[${GEN.nestedIdx}${sk}]`;
1177
- return (` if (isErr(${resultVar})) {\n` +
1178
- ` var ${GEN.errors}${sk} = ${resultVar}.data;\n` +
1179
- ` var __bk$pp${sk} = ${ppValue};\n` +
1180
- ` for (var ${GEN.nestedIdx}${sk}=0; ${GEN.nestedIdx}${sk}<${GEN.errors}${sk}.length; ${GEN.nestedIdx}${sk}++) {\n` +
1181
- ` ` +
1182
- nestedErrPush(GEN.errList, `__bk$pp${sk}+${errItem}.path`, errItem, `__ne${sk}`) +
1183
- ` }\n` +
1184
- ` } else { ${GEN.out}[${JSON.stringify(fieldKey)}] = ${resultVar}; }\n`);
1185
- }
1186
- const errFirst = `${GEN.errors}${sk}[0]`;
1187
- return (` if (isErr(${resultVar})) {\n` +
1188
- ` var ${GEN.errors}${sk} = ${resultVar}.data;\n` +
1189
- ` var __bk$pp${sk} = ${ppValue};\n` +
1190
- ` ` +
1191
- nestedErrReturn(`__bk$pp${sk}+${errFirst}.path`, errFirst, `__ne${sk}`) +
1192
- ` } else { ${GEN.out}[${JSON.stringify(fieldKey)}] = ${resultVar}; }\n`);
1193
- }
1194
- // ─────────────────────────────────────────────────────────────────────────────
1195
- // generateNestedCodeValidateOnly — validate-only nested (inline when possible)
1196
- // ─────────────────────────────────────────────────────────────────────────────
1197
- // Inline-eligibility predicate: a nested DTO can be inlined unless it is already in the
1198
- // active inline-set (circular reference). Inlined directly at the three call sites below
1199
- // — no extra function call at seal time.
1200
- /**
1201
- * Emit inline validation code for all fields of a nested DTO.
1202
- * Reuses generateFieldCode with modified ctx (pathPrefix, varPrefix, inputExpr).
1203
- */
1204
- function emitInlineNestedBlock(nestedMerged, nestedClass, inputExpr, pathPrefixExpr, varPrefix, ctx) {
1205
- const inlinedSet = ctx.inlineNestedClasses;
1206
- inlinedSet.add(nestedClass);
1207
- const inlineCtx = {
1208
- ...ctx,
1209
- pathPrefix: pathPrefixExpr,
1210
- varPrefix,
1211
- inputExpr,
1212
- exposeDefaultValues: false, // inline nested doesn't use exposeDefaultValues
1213
- };
1214
- let code = '';
1215
- for (const [fieldKey, meta] of Object.entries(nestedMerged)) {
1216
- code += generateFieldCode(fieldKey, meta, inlineCtx);
1217
- }
1218
- inlinedSet.delete(nestedClass);
1219
- return code;
1220
- }
1221
- function generateNestedCodeValidateOnly(fieldKey, varName, meta, ctx, emitCtx) {
1222
- const { collectErrors, execs } = ctx;
1223
- if (!meta.type) {
1224
- return '';
1225
- }
1226
- const sk = (ctx.varPrefix || '') + sanitizeKey(fieldKey);
1227
- let code = '';
1228
- // Initialize inline tracking set if not present
1229
- if (!ctx.inlineNestedClasses) {
1230
- ctx.inlineNestedClasses = new Set();
1231
- }
1232
- if (meta.type.discriminator) {
1233
- // Discriminator — inline each subType's validation
1234
- const discProp = JSON.stringify(meta.type.discriminator.property);
1235
- code += `var ${GEN.disc}${sk} = ${varName} && ${varName}[${discProp}];\n`;
1236
- code += `switch (${GEN.disc}${sk}) {\n`;
1237
- for (const sub of meta.type.discriminator.subTypes) {
1238
- const subSealed = getSealed(sub.value);
1239
- const subMerged = subSealed.merged;
1240
- const canInline = subMerged && !ctx.inlineNestedClasses.has(sub.value);
1241
- code += ` case ${JSON.stringify(sub.name)}:\n`;
1242
- if (canInline) {
1243
- const ppExpr = ctx.pathPrefix ? `${ctx.pathPrefix}+${JSON.stringify(fieldKey + '.')}` : JSON.stringify(fieldKey + '.');
1244
- const vpPrefix = `${sk}_d${sanitizeKey(sub.name)}_`;
1245
- code += emitInlineNestedBlock(subMerged, sub.value, varName, ppExpr, vpPrefix, ctx);
1246
- }
1247
- else {
1248
- const execIdx = execs.length;
1249
- execs.push(subSealed);
1250
- const awaitKw = ctx.isAsync ? 'await ' : '';
1251
- code += ` var ${GEN.result}${sk} = ${awaitKw}execs[${execIdx}].validate(${varName}, opts);\n`;
1252
- code += generateValidateNestedResult(fieldKey, `${GEN.result}${sk}`, collectErrors, ctx.pathPrefix);
1253
- }
1254
- code += ` break;\n`;
1255
- }
1256
- const validSubTypeNamesJsonV = JSON.stringify(meta.type.discriminator.subTypes.map(s => s.name));
1257
- const discPathExprV = emitCtx.pathExpr ?? JSON.stringify(fieldKey);
1258
- const discValueExprV = `${GEN.disc}${sk}`;
1259
- if (collectErrors) {
1260
- code += ` default: ${GEN.errList}.push({path:${discPathExprV},code:'invalidDiscriminator',context:{received:${discValueExprV},validSubTypes:${validSubTypeNamesJsonV}}});\n`;
1261
- }
1262
- else {
1263
- code += ` default: return [{path:${discPathExprV},code:'invalidDiscriminator',context:{received:${discValueExprV},validSubTypes:${validSubTypeNamesJsonV}}}];\n`;
1264
- }
1265
- code += `}\n`;
1266
- }
1267
- else {
1268
- const nestedCls = meta.type.resolvedClass ?? meta.type.fn();
1269
- const nestedSealed = getSealed(nestedCls);
1270
- const nestedMerged = nestedSealed.merged;
1271
- const hasEach = meta.type.isArray || meta.flags.validateNestedEach || meta.validation.some(rd => rd.each);
1272
- // Decide: inline or function call
1273
- const useInline = nestedMerged && !ctx.inlineNestedClasses.has(nestedCls);
1274
- if (hasEach) {
1275
- const iVar = `${GEN.index}${sk}`;
1276
- code += `if (Array.isArray(${varName})) {\n`;
1277
- const nonEachRules = meta.validation.filter(rd => !rd.each);
1278
- code += emitRuleList(fieldKey, varName, nonEachRules, emitCtx, ctx, ' ');
1279
- code += ` for (var ${iVar}=0; ${iVar}<${varName}.length; ${iVar}++) {\n`;
1280
- if (useInline) {
1281
- // INLINE: generate validation code directly in the loop body.
1282
- // Emit the per-iteration path as a single local var — both the invalidInput error
1283
- // path and the nested block reference it, avoiding two identical 3-string concats.
1284
- const itemVar = `__il$${sk}item`;
1285
- const ppVar = `__bk$pp${sk}`;
1286
- const ppExpr = ppVar;
1287
- const ppInit = ctx.pathPrefix
1288
- ? `${ctx.pathPrefix}+${JSON.stringify(fieldKey)}+'['+${iVar}+'].'`
1289
- : `${JSON.stringify(fieldKey)}+'['+${iVar}+'].'`;
1290
- const vpPrefix = `${sk}i_`;
1291
- code += ` var ${itemVar} = ${varName}[${iVar}];\n`;
1292
- code += ` var ${ppVar} = ${ppInit};\n`;
1293
- // Input type guard for the item — uses the cached prefix
1294
- code += ` if (${itemVar} == null || typeof ${itemVar} !== 'object' || Array.isArray(${itemVar})) `;
1295
- if (collectErrors) {
1296
- code += `${GEN.errList}.push({path:${ppVar},code:'invalidInput'});\n`;
1297
- }
1298
- else {
1299
- code += `return [{path:${ppVar},code:'invalidInput'}];\n`;
1300
- }
1301
- code += ` else {\n`;
1302
- code += emitInlineNestedBlock(nestedMerged, nestedCls, itemVar, ppExpr, vpPrefix, ctx);
1303
- code += ` }\n`;
1304
- }
1305
- else {
1306
- // FALLBACK: function call to validate
1307
- const execIdx = execs.length;
1308
- execs.push(nestedSealed);
1309
- const awaitKw = ctx.isAsync ? 'await ' : '';
1310
- code += ` var ${GEN.result}${sk} = ${awaitKw}execs[${execIdx}].validate(${varName}[${iVar}], opts);\n`;
1311
- code += ` if (${GEN.result}${sk} !== null) {\n`;
1312
- const ppVar = `__bk$pp${sk}`;
1313
- const ppInit = ctx.pathPrefix
1314
- ? `${ctx.pathPrefix}+${JSON.stringify(fieldKey)}+'['+${iVar}+'].'`
1315
- : `${JSON.stringify(fieldKey)}+'['+${iVar}+'].'`;
1316
- code += ` var ${ppVar} = ${ppInit};\n`;
1317
- if (collectErrors) {
1318
- code += ` for (var ${GEN.nestedIdx}${sk}=0; ${GEN.nestedIdx}${sk}<${GEN.result}${sk}.length; ${GEN.nestedIdx}${sk}++) {\n`;
1319
- code +=
1320
- ` ` +
1321
- nestedErrPush(GEN.errList, `${ppVar}+${GEN.result}${sk}[${GEN.nestedIdx}${sk}].path`, `${GEN.result}${sk}[${GEN.nestedIdx}${sk}]`, `__ne${sk}`);
1322
- code += ` }\n`;
1323
- }
1324
- else {
1325
- code += ` ` + nestedErrReturn(`${ppVar}+${GEN.result}${sk}[0].path`, `${GEN.result}${sk}[0]`, `__ne${sk}`, true);
1326
- }
1327
- code += ` }\n`;
1328
- }
1329
- code += ` }\n`;
1330
- code += `} else { ${emitCtx.fail('isArray')}; }\n`;
1331
- }
1332
- else {
1333
- // Single nested object — arrays are objects by `typeof` but are not valid nested DTOs;
1334
- // reject them here (matching the deserialize path) instead of descending into their fields.
1335
- code += `if (${varName} != null && typeof ${varName} === 'object' && !Array.isArray(${varName})) {\n`;
1336
- if (useInline) {
1337
- const ppExpr = ctx.pathPrefix ? `${ctx.pathPrefix}+${JSON.stringify(fieldKey + '.')}` : JSON.stringify(fieldKey + '.');
1338
- const vpPrefix = `${sk}_`;
1339
- code += emitInlineNestedBlock(nestedMerged, nestedCls, varName, ppExpr, vpPrefix, ctx);
1340
- }
1341
- else {
1342
- const execIdx = execs.length;
1343
- execs.push(nestedSealed);
1344
- const awaitKw = ctx.isAsync ? 'await ' : '';
1345
- code += ` var ${GEN.result}${sk} = ${awaitKw}execs[${execIdx}].validate(${varName}, opts);\n`;
1346
- code += generateValidateNestedResult(fieldKey, `${GEN.result}${sk}`, collectErrors, ctx.pathPrefix);
1347
- }
1348
- code += `} else { ${emitCtx.fail('isObject')}; }\n`;
1349
- }
1350
- }
1351
- return code;
1352
- }
1353
- /** Generate validate-mode nested result handling (null check instead of isErr) */
1354
- function generateValidateNestedResult(fieldKey, resultVar, collectErrors, pathPrefix) {
1355
- const sk = sanitizeKey(fieldKey);
1356
- const ppVar = `__bk$pp${sk}`;
1357
- // Prepend the current scope's path prefix (see generateNestedResultCode).
1358
- const ppValue = pathPrefix ? `${pathPrefix}+${JSON.stringify(fieldKey + '.')}` : JSON.stringify(fieldKey + '.');
1359
- if (collectErrors) {
1360
- const errItem = `${resultVar}[${GEN.nestedIdx}${sk}]`;
1361
- return (` if (${resultVar} !== null) {\n` +
1362
- ` var ${ppVar} = ${ppValue};\n` +
1363
- ` for (var ${GEN.nestedIdx}${sk}=0; ${GEN.nestedIdx}${sk}<${resultVar}.length; ${GEN.nestedIdx}${sk}++) {\n` +
1364
- ` ` +
1365
- nestedErrPush(GEN.errList, `${ppVar}+${errItem}.path`, errItem, `__ne${sk}`) +
1366
- ` }\n` +
1367
- ` }\n`);
1368
- }
1369
- const errFirst = `${resultVar}[0]`;
1370
- return (` if (${resultVar} !== null) {\n` +
1371
- ` var ${ppVar} = ${ppValue};\n` +
1372
- ` ` +
1373
- nestedErrReturn(`${ppVar}+${errFirst}.path`, errFirst, `__ne${sk}`, true) +
1374
- ` }\n`);
1375
- }
1376
- // ─────────────────────────────────────────────────────────────────────────────
1377
- // generateCollectionCodeValidateOnly — validate-only collection (no Set/Map creation)
1378
- // ─────────────────────────────────────────────────────────────────────────────
1379
- function generateCollectionCodeValidateOnly(fieldKey, varName, meta, ctx, emitCtx) {
1380
- const { collectErrors, execs } = ctx;
1381
- const sk = (ctx.varPrefix || '') + sanitizeKey(fieldKey);
1382
- const collection = meta.type.collection;
1383
- const awaitKw = ctx.isAsync ? 'await ' : '';
1384
- if (!ctx.inlineNestedClasses) {
1385
- ctx.inlineNestedClasses = new Set();
1386
- }
1387
- // Resolve nested DTO for collection values
1388
- let nestedCls;
1389
- let nestedSealed;
1390
- let nestedMerged;
1391
- if (meta.type.resolvedCollectionValue) {
1392
- nestedCls = meta.type.resolvedCollectionValue;
1393
- nestedSealed = getSealed(nestedCls);
1394
- nestedMerged = nestedSealed.merged;
1395
- }
1396
- const useInline = nestedCls && nestedMerged && !ctx.inlineNestedClasses.has(nestedCls);
1397
- let code = '';
1398
- if (collection === 'Set') {
1399
- code += `if (Array.isArray(${varName})) {\n`;
1400
- const nonEachRules = meta.validation.filter(rd => !rd.each);
1401
- code += emitRuleList(fieldKey, varName, nonEachRules, emitCtx, ctx, ' ');
1402
- if (nestedSealed) {
1403
- const iVar = `${GEN.index}${sk}`;
1404
- code += ` for (var ${iVar}=0; ${iVar}<${varName}.length; ${iVar}++) {\n`;
1405
- if (useInline) {
1406
- // Cache per-iteration path prefix into a single local var — itemInvalidPathExpr was
1407
- // identical to ppExpr (two copies of the same 3-string concat in the emitted body).
1408
- const itemVar = `__il$${sk}ci`;
1409
- const ppVar = `__bk$pp${sk}`;
1410
- const ppInit = ctx.pathPrefix
1411
- ? `${ctx.pathPrefix}+${JSON.stringify(fieldKey)}+'['+${iVar}+'].'`
1412
- : `${JSON.stringify(fieldKey)}+'['+${iVar}+'].'`;
1413
- const vpPrefix = `${sk}c_`;
1414
- code += ` var ${itemVar} = ${varName}[${iVar}];\n`;
1415
- code += ` var ${ppVar} = ${ppInit};\n`;
1416
- code += ` if (${itemVar} == null || typeof ${itemVar} !== 'object' || Array.isArray(${itemVar})) `;
1417
- if (collectErrors) {
1418
- code += `${GEN.errList}.push({path:${ppVar},code:'invalidInput'});\n`;
1419
- }
1420
- else {
1421
- code += `return [{path:${ppVar},code:'invalidInput'}];\n`;
1422
- }
1423
- code += ` else {\n`;
1424
- code += emitInlineNestedBlock(nestedMerged, nestedCls, itemVar, ppVar, vpPrefix, ctx);
1425
- code += ` }\n`;
1426
- }
1427
- else {
1428
- const execIdx = execs.length;
1429
- execs.push(nestedSealed);
1430
- code += ` var ${GEN.result}${sk} = ${awaitKw}execs[${execIdx}].validate(${varName}[${iVar}], opts);\n`;
1431
- code += ` if (${GEN.result}${sk} !== null) {\n`;
1432
- const ppVar = `__bk$pp${sk}`;
1433
- const ppInit = ctx.pathPrefix
1434
- ? `${ctx.pathPrefix}+${JSON.stringify(fieldKey)}+'['+${iVar}+'].'`
1435
- : `${JSON.stringify(fieldKey)}+'['+${iVar}+'].'`;
1436
- code += ` var ${ppVar} = ${ppInit};\n`;
1437
- if (collectErrors) {
1438
- code += ` for (var ${GEN.nestedIdx}${sk}=0; ${GEN.nestedIdx}${sk}<${GEN.result}${sk}.length; ${GEN.nestedIdx}${sk}++) {\n`;
1439
- code +=
1440
- ` ` +
1441
- nestedErrPush(GEN.errList, `${ppVar}+${GEN.result}${sk}[${GEN.nestedIdx}${sk}].path`, `${GEN.result}${sk}[${GEN.nestedIdx}${sk}]`, `__ne${sk}`);
1442
- code += ` }\n`;
1443
- }
1444
- else {
1445
- code += ` ` + nestedErrReturn(`${ppVar}+${GEN.result}${sk}[0].path`, `${GEN.result}${sk}[0]`, `__ne${sk}`, true);
1446
- }
1447
- code += ` }\n`;
1448
- }
1449
- code += ` }\n`;
1450
- }
1451
- // each validation — iterate input array directly
1452
- const eachRules = meta.validation.filter(rd => rd.each);
1453
- if (eachRules.length > 0) {
1454
- const eiVar = `${GEN.index}${sk}e`;
1455
- code += ` for (var ${eiVar}=0; ${eiVar}<${varName}.length; ${eiVar}++) {\n`;
1456
- for (const rd of eachRules) {
1457
- const prefixVar = `__bk$ep_${sk}`;
1458
- const extra = computeRuleExtras(rd, fieldKey, varName, ctx);
1459
- const failFn = (c) => collectErrors
1460
- ? `${GEN.errList}.push({path:${prefixVar}+${eiVar}+']',code:${JSON.stringify(c)}${extra}})`
1461
- : `return [{path:${prefixVar}+${eiVar}+']',code:${JSON.stringify(c)}${extra}}]`;
1462
- const colEmitCtx = { ...emitCtx, fail: failFn };
1463
- if (!code.includes(`var ${prefixVar}`)) {
1464
- const prefixInit = ctx.pathPrefix
1465
- ? `${ctx.pathPrefix}+${JSON.stringify(fieldKey)}+'['`
1466
- : `${JSON.stringify(fieldKey)}+'['`;
1467
- code += ` var ${prefixVar} = ${prefixInit};\n`;
1468
- }
1469
- code += ` ${rd.rule.emit(`${varName}[${eiVar}]`, colEmitCtx)}\n`;
1470
- }
1471
- code += ` }\n`;
1472
- }
1473
- code += `} else { ${emitCtx.fail('isArray')}; }\n`;
1474
- }
1475
- else {
1476
- // Map: validate object values
1477
- code += `if (${varName} != null && typeof ${varName} === 'object' && !Array.isArray(${varName})) {\n`;
1478
- if (nestedSealed) {
1479
- const kVar = `${GEN.key}${sk}`;
1480
- const ksVar = `__bk$vk${sk}`;
1481
- const iVar = `__bk$vi${sk}`;
1482
- code += ` var ${ksVar} = Object.keys(${varName});\n`;
1483
- code += ` for (var ${iVar}=0; ${iVar}<${ksVar}.length; ${iVar}++) {\n`;
1484
- code += ` var ${kVar} = ${ksVar}[${iVar}];\n`;
1485
- if (useInline) {
1486
- const itemVar = `__il$${sk}mi`;
1487
- const ppExpr = ctx.pathPrefix
1488
- ? `${ctx.pathPrefix}+${JSON.stringify(fieldKey)}+'['+${kVar}+'].'`
1489
- : `${JSON.stringify(fieldKey)}+'['+${kVar}+'].'`;
1490
- const vpPrefix = `${sk}m_`;
1491
- const itemInvalidPathExpr = ppExpr;
1492
- code += ` var ${itemVar} = ${varName}[${kVar}];\n`;
1493
- code += ` if (${itemVar} == null || typeof ${itemVar} !== 'object' || Array.isArray(${itemVar})) `;
1494
- if (collectErrors) {
1495
- code += `${GEN.errList}.push({path:${itemInvalidPathExpr},code:'invalidInput'});\n`;
1496
- }
1497
- else {
1498
- code += `return [{path:${itemInvalidPathExpr},code:'invalidInput'}];\n`;
1499
- }
1500
- code += ` else {\n`;
1501
- code += emitInlineNestedBlock(nestedMerged, nestedCls, itemVar, ppExpr, vpPrefix, ctx);
1502
- code += ` }\n`;
1503
- }
1504
- else {
1505
- const execIdx = execs.length;
1506
- execs.push(nestedSealed);
1507
- code += ` var ${GEN.result}${sk} = ${awaitKw}execs[${execIdx}].validate(${varName}[${kVar}], opts);\n`;
1508
- code += ` if (${GEN.result}${sk} !== null) {\n`;
1509
- const ppVar = `__bk$pp${sk}`;
1510
- const ppInit = ctx.pathPrefix
1511
- ? `${ctx.pathPrefix}+${JSON.stringify(fieldKey)}+'['+${kVar}+'].'`
1512
- : `${JSON.stringify(fieldKey)}+'['+${kVar}+'].'`;
1513
- code += ` var ${ppVar} = ${ppInit};\n`;
1514
- if (collectErrors) {
1515
- code += ` for (var ${GEN.nestedIdx}${sk}=0; ${GEN.nestedIdx}${sk}<${GEN.result}${sk}.length; ${GEN.nestedIdx}${sk}++) {\n`;
1516
- code +=
1517
- ` ` +
1518
- nestedErrPush(GEN.errList, `${ppVar}+${GEN.result}${sk}[${GEN.nestedIdx}${sk}].path`, `${GEN.result}${sk}[${GEN.nestedIdx}${sk}]`, `__ne${sk}`);
1519
- code += ` }\n`;
1520
- }
1521
- else {
1522
- code += ` ` + nestedErrReturn(`${ppVar}+${GEN.result}${sk}[0].path`, `${GEN.result}${sk}[0]`, `__ne${sk}`, true);
1523
- }
1524
- code += ` }\n`;
1525
- }
1526
- code += ` }\n`;
1527
- }
1528
- code += `} else { ${emitCtx.fail('isObject')}; }\n`;
1529
- }
1530
- return code;
1531
- }
1532
- // ─────────────────────────────────────────────────────────────────────────────
1533
- // makeEmitCtx — create per-field EmitContext
1534
- // ─────────────────────────────────────────────────────────────────────────────
1535
- function makeEmitCtx(fieldKey, ctx, fieldExtras = '') {
1536
- const { collectErrors, regexes, refs, execs, validateOnly, pathPrefix } = ctx;
1537
- const pathExpr = pathPrefix ? `${pathPrefix}+${JSON.stringify(fieldKey)}` : JSON.stringify(fieldKey);
1538
- return {
1539
- addRegex(re) {
1540
- regexes.push(re);
1541
- return regexes.length - 1;
1542
- },
1543
- addRef(fn) {
1544
- refs.push(fn);
1545
- return refs.length - 1;
1546
- },
1547
- addExecutor(executor) {
1548
- execs.push(executor);
1549
- return execs.length - 1;
1550
- },
1551
- fail(code) {
1552
- if (collectErrors) {
1553
- return `${GEN.errList}.push({path:${pathExpr},code:${JSON.stringify(code)}${fieldExtras}})`;
1554
- }
1555
- else if (validateOnly) {
1556
- return `return [{path:${pathExpr},code:${JSON.stringify(code)}${fieldExtras}}]`;
1557
- }
1558
- return `return err([{path:${pathExpr},code:${JSON.stringify(code)}${fieldExtras}}])`;
1559
- },
1560
- collectErrors,
1561
- pathExpr: pathExpr,
1562
- };
1563
- }
1564
- export { buildDeserializeCode, buildValidateCode };
1
+ import{err as c,isErr as d}from"@zipbul/result";import{BakerError as N}from"../errors.js";import{getSealed as u}from"../meta-access.js";import{emitRulePlan as t}from"../rule-plan.js";import{sanitizeKey as G,buildGroupsHasExpr as n}from"./codegen-utils.js";const X={field:"__bk$f_",index:"__bk$i_",setIdx:"__bk$si_",setVal:"__bk$sv_",mapIdx:"__bk$mi_",mapVal:"__bk$mv_",mark:"__bk$mark_",skip:"__bk$skip_",result:"__bk$r_",errors:"__bk$re_",arr:"__bk$arr_",disc:"__bk$dt_",nestedIdx:"__bk$j_",out:"__bk$out",errList:"__bk$errors",groups:"__bk$groups",group0:"__bk$group0",groupsSet:"__bk$groupsSet",key:"__bk$k"};function I(Z,W,U,$){const _=`${$}_e`;return`var ${_}=${U};
2
+ if(${_}.message===undefined&&${_}.context===undefined){${Z}.push({path:${W},code:${_}.code});}
3
+ else{var ${$}={path:${W},code:${_}.code};
4
+ if(${_}.message!==undefined)${$}.message=${_}.message;
5
+ if(${_}.context!==undefined)${$}.context=${_}.context;
6
+ ${Z}.push(${$});}
7
+ `}function g(Z,W,U,$){const _=(j)=>$?`return ${j};
8
+ `:`return err(${j});
9
+ `;return`if(${W}.message===undefined&&${W}.context===undefined)${_(`[{path:${Z},code:${W}.code}]`)} var ${U}={path:${Z},code:${W}.code};
10
+ if(${W}.message!==undefined)${U}.message=${W}.message;
11
+ if(${W}.context!==undefined)${U}.context=${W}.context;
12
+ ${_(`[${U}]`)}`}function r(Z,W){return X.field+(W||"")+G(Z)}function x(Z,W){const U=W.find((_)=>_.deserializeOnly&&_.name);if(U)return U.name;const $=W.find((_)=>!_.deserializeOnly&&!_.serializeOnly&&_.name);if($)return $.name;return Z}function m(Z){let W=null;for(const U of Z){if(U.serializeOnly)continue;if(!U.groups||U.groups.length===0)return;if(W===null)W=new Set;for(const $ of U.groups)W.add($)}return W===null?void 0:[...W]}function buildDeserializeCode(Z,W,U,$,_,j=!1){const B=U?.stopAtFirstError??!1,Q=!B,q=U?.exposeDefaultValues??!1,z=[],F=[],H=[],D=j?(M)=>M:(M)=>`err(${M})`;let L=`'use strict';
13
+ `;if(j){if(q)L+=`var __bk$defs = new _Cls();
14
+ `}else L+=q?`var ${X.out} = new _Cls();
15
+ `:`var ${X.out} = Object.create(_Cls.prototype);
16
+ `;if(Q)L+=`var ${X.errList} = [];
17
+ `;L+=`if (input == null || typeof input !== 'object' || Array.isArray(input)) return ${D("[{path:'',code:'invalidInput'}]")};
18
+ `;if($){L+=`var __seen = (opts && opts[__SEEN_KEY]) || null;
19
+ `;L+=`if (__seen === null) { __seen = new WeakSet(); opts = opts ? { ...opts, [__SEEN_KEY]: __seen } : { [__SEEN_KEY]: __seen }; }
20
+ `;L+=`if (__seen.has(input)) return ${D("[{path:'',code:'circular'}]")};
21
+ `;L+=`__seen.add(input);
22
+ `;L+=`try {
23
+ `}if(U?.whitelist){const M=new Set;for(const[S,R]of Object.entries(W)){const T=x(S,R.expose);M.add(T)}const w=F.length;F.push(M);if(Q)L+=`{var __wlk=Object.keys(input);for(var __wli=0;__wli<__wlk.length;__wli++){var ${X.key}=__wlk[__wli];if(!refs[${w}].has(${X.key}))${X.errList}.push({path:${X.key},code:'whitelistViolation'});}}
24
+ `;else L+=`{var __wlk=Object.keys(input);for(var __wli=0;__wli<__wlk.length;__wli++){var ${X.key}=__wlk[__wli];if(!refs[${w}].has(${X.key}))return ${D(`[{path:${X.key},code:'whitelistViolation'}]`)};}}
25
+ `}let Y=!1;for(const M in W){const w=W[M],S=m(w.expose);if(S&&S.length>0){Y=!0;break}let R=!1;for(const T of w.validation)if(T.groups&&T.groups.length>0){R=!0;break}if(R){Y=!0;break}}if(Y){L+=`var ${X.groups} = opts && opts.groups;
26
+ `;L+=`var ${X.group0} = ${X.groups} && ${X.groups}.length === 1 ? ${X.groups}[0] : null;
27
+ `;L+=`var ${X.groupsSet} = ${X.groups} && ${X.groups}.length > 1 ? new Set(${X.groups}) : null;
28
+ `}for(const[M,w]of Object.entries(W)){const S=o(M,w,{stopAtFirstError:B,collectErrors:Q,exposeDefaultValues:q,isAsync:_,regexes:z,refs:F,execs:H,options:U,validateOnly:j});L+=S}if(Q)L+=`if (${X.errList}.length) return ${j?X.errList:`err(${X.errList})`};
29
+ `;L+=`return ${j?"null":X.out};
30
+ `;if($)L+=`} finally { __seen.delete(input); }
31
+ `;const A=Z.name.replace(/[^\w$.-]/g,"_");L+=`//# sourceURL=baker://${A}/${j?"validate":"deserialize"}
32
+ `;const O=_?"async function":"function",J=Symbol.for("baker:circular-seen");return Function("_Cls","re","refs","execs","err","isErr","__SEEN_KEY",`return ${O}(input, opts) { `+L+" }")(Z,z,F,H,c,d,J)}function buildValidateCode(Z,W,U,$,_){return buildDeserializeCode(Z,W,U,$,_,!0)}function a(Z,W,U){if(Z&&W)return"nullable+optional";if(Z)return"nullable";if(U)return"defined";if(W)return"optional";return"default"}const e={"nullable+optional"({varName:Z,assignNull:W,validationCode:U}){let $=`if (${Z} === null) { ${W}}
33
+ `;$+=`else if (${Z} !== undefined) {
34
+ `;$+=U;$+=`}
35
+ `;return $},nullable({varName:Z,emitCtx:W,assignNull:U,validationCode:$}){let _=`if (${Z} === undefined) ${W.fail("isDefined")};
36
+ `;_+=`else if (${Z} !== null) {
37
+ `;_+=$;_+=`} else { ${U}}
38
+ `;return _},defined({varName:Z,emitCtx:W,validationCode:U}){let $=`if (${Z} === undefined) ${W.fail("isDefined")};
39
+ `;$+=U;return $},optional({varName:Z,validationCode:W}){let U=`if (${Z} !== undefined && ${Z} !== null) {
40
+ `;U+=W;U+=`}
41
+ `;return U},default({varName:Z,emitCtx:W,validationCode:U}){let $=`if (${Z} === undefined || ${Z} === null) ${W.fail("isDefined")};
42
+ `;$+=`else {
43
+ `;$+=U;$+=`}
44
+ `;return $}};function o(Z,W,U){const{exposeDefaultValues:$}=U;if(W.exclude){if(!W.exclude.serializeOnly){if(U.options?.debug){const R=W.exclude.deserializeOnly?"deserializeOnly":"bidirectional";return`// [baker] field ${JSON.stringify(Z)} excluded (${R} @Exclude)
45
+ `}return""}}if(W.expose.length>0&&W.expose.every((R)=>R.serializeOnly)){if(U.options?.debug)return`// [baker] field ${JSON.stringify(Z)} excluded (all @Expose entries are serializeOnly)
46
+ `;return""}const _=r(Z,U.varPrefix),j=x(Z,W.expose),B=m(W.expose),Q=U.inputExpr||"input",q=WQ(W,Z,_,U),z=JQ(Z,U,q);let F="",H=null;if(W.flags.validateIf){H=U.refs.length;U.refs.push(W.flags.validateIf)}let D;const L=JSON.stringify(j);if($&&!W.flags.isOptional){const R=U.validateOnly?"__bk$defs":X.out;D=`var ${_} = Object.hasOwn(${Q}, ${L}) ? ${Q}[${L}] : ${R}[${JSON.stringify(Z)}];
47
+ `}else D=`var ${_} = ${Q}[${L}];
48
+ `;let Y="",A="";if(B&&B.length>0){Y=`if ((${X.group0} !== null || ${X.groupsSet}) && (${n(X.group0,X.groupsSet,B)})) {
49
+ `;A=`}
50
+ `}let O=D;const J=!!(W.flags.isOptional&&!W.flags.isDefined),P=W.flags.isNullable===!0,M=QQ(Z,_,W,U,z,B),w=U.validateOnly?"":`${X.out}[${JSON.stringify(Z)}] = null;
51
+ `,S=a(P,J,W.flags.isDefined??!1);O+=e[S]({varName:_,emitCtx:z,assignNull:w,validationCode:M});if(H!==null)F+=Y+`if (refs[${H}](${Q})) {
52
+ `+O+`}
53
+ `+A;else F+=Y+O+A;return F}function QQ(Z,W,U,$,_,j){const{collectErrors:B}=$;let Q="";const q=U.transform.filter((z)=>!z.options?.serializeOnly);if(q.length>0){const z=JSON.stringify(Z),F=$.inputExpr||"input";if(q.length===1){const H=q[0],D=$.refs.length;$.refs.push(H.fn);const L=`refs[${D}]({value:${W},key:${z},obj:${F}})`;Q+=`${W} = ${H.isAsync?"await ":""}${L};
54
+ `}else if(q.length===2){const H=q[0],D=q[1],L=$.refs.length;$.refs.push(H.fn);const Y=$.refs.length;$.refs.push(D.fn);const A=`refs[${L}]({value:${W},key:${z},obj:${F}})`,O=H.isAsync?`await ${A}`:A,J=`refs[${Y}]({value:${O},key:${z},obj:${F}})`;Q+=`${W} = ${D.isAsync?"await ":""}${J};
55
+ `}else for(const H of q){const D=$.refs.length;$.refs.push(H.fn);const L=`refs[${D}]({value:${W},key:${z},obj:${F}})`;Q+=`${W} = ${H.isAsync?"await ":""}${L};
56
+ `}}if(U.type?.collection){Q+=$.validateOnly?FQ(Z,W,U,$,_):jQ(Z,W,U,$,_);return Q}if(U.flags.validateNested&&U.type?.fn){Q+=$.validateOnly?BQ(Z,W,U,$,_):zQ(Z,W,U,$,_);return Q}if(U.validation.length===0){if(!$.validateOnly)Q+=`${X.out}[${JSON.stringify(Z)}] = ${W};
57
+ `;return Q}Q+=LQ(Z,W,U.validation,B,_,$,U,j);return Q}function l(Z,W,U,$,_,j){let B="";if(typeof Z==="string")B+=`,message:${JSON.stringify(Z)}`;else if(typeof Z==="function"){const Q=j.refs.length;j.refs.push(Z);const q=U();B+=`,message:refs[${Q}]({property:${JSON.stringify($)},value:${_},constraints:${q}})`}if(W!==void 0){const Q=j.refs.length;j.refs.push(W);B+=`,context:refs[${Q}]`}return B}function V(Z,W,U,$){return l(Z.message,Z.context,()=>{const _=$.refs.length;$.refs.push(Z.rule.constraints??{});return`refs[${_}]`},W,U,$)}function WQ(Z,W,U,$){return l(Z.message,Z.context,()=>"{}",W,U,$)}function s(Z,W,U,$,_){const j=V($,W,U,_);if(!j)return Z;const B=Z.pathExpr??JSON.stringify(W);return{...Z,fail(Q){if(Z.collectErrors)return`${X.errList}.push({path:${B},code:${JSON.stringify(Q)}${j}})`;else if(_.validateOnly)return`return [{path:${B},code:${JSON.stringify(Q)}${j}}]`;return`return err([{path:${B},code:${JSON.stringify(Q)}${j}}])`}}}function h(Z,W,U,$,_,j,B,Q){let q="",z=0,F=0;for(const Y of U){if(!k(Y.groups,B))continue;if(Y.rule.plan?.cacheKey==="length")z+=1;else if(Y.rule.plan?.cacheKey==="time")F+=1}const H=G(Z),D=z>1?`${X.arr}${H}len`:null,L=F>1?`${X.arr}${H}time`:null;if(D)q+=`${j}var ${D} = ${W}.length;
58
+ `;if(L)q+=`${j}var ${L} = ${W}.getTime();
59
+ `;for(const Y of U){const A=k(Y.groups,B),O=s($,Z,W,Y,_),J=Q?{...O,insideTypeGate:!0}:O;let P;if(A&&Y.rule.plan&&(D||L)){const w={};if(Y.rule.plan.cacheKey==="length"&&D)w.length=D;if(Y.rule.plan.cacheKey==="time"&&L)w.time=L;P=t(W,J,Y.rule.ruleName,Y.rule.plan,w,Q)}else P=Y.rule.emit(W,J);if(!P)continue;const M=A?P:XQ(Y,P);q+=j+M.replace(/\n/g,`
60
+ `+j)+`
61
+ `}return q}function XQ(Z,W){if(!Z.groups||Z.groups.length===0)return W;return`if ((${X.group0} === null && !${X.groupsSet}) || ${n(X.group0,X.groupsSet,Z.groups)}) {
62
+ ${W}
63
+ }
64
+ `}function k(Z,W){if(!Z||Z.length===0)return!W||W.length===0;if(!W||Z.length!==W.length)return!1;for(let U=0;U<Z.length;U++)if(Z[U]!==W[U])return!1;return!0}function y(Z,W,U,$,_,j){const B=_?`${j.fail("conversionFailed")}; ${$} = true;`:j.fail("conversionFailed")+";";switch(Z){case"string":return` ${W} = String(${W});
65
+ `;case"number":return` ${W} = Number(${W});
66
+ if (isNaN(${W})) { ${B} }
67
+ `;case"boolean":return` if (${W} === 'true' || ${W} === '1' || ${W} === 1) ${W} = true;
68
+ else if (${W} === 'false' || ${W} === '0' || ${W} === 0) ${W} = false;
69
+ else { ${B} }
70
+ `;case"date":return` ${W} = new Date(${W});
71
+ if (isNaN(${W}.getTime())) { ${B} }
72
+ `;default:throw new N(`Unknown implicit conversion type: "${Z}" for field "${U}"`)}}const ZQ={Number:"number",Boolean:"boolean",String:"string",Date:"date"};const p={isString:"string",isNumber:"number",isBoolean:"boolean",isDate:"date",isInt:"number",isArray:"array",isObject:"object"};const $Q=new Set(["isString","isBoolean","isDate","isArray","isObject"]);function UQ(Z,W){const U=[],$=[],_={string:[],number:[],boolean:[],date:[],array:[],object:[]};for(const Q of W){if(Q.each){U.push(Q);continue}const q=Q.rule.requiresType;if(q!==void 0)_[q].push(Q);else $.push(Q)}let j=void 0,B=null;for(const Q of["string","number","boolean","date","array","object"]){const q=_[Q];if(q.length===0)continue;if(j){if(B===null)B=[j.type];B.push(Q)}else j={type:Q,deps:q}}if(B)throw new N(`Field "${Z}" has conflicting requiresType: ${B.join(", ")}`);return{each:U,generalRules:$,typedDeps:j}}function YQ(Z,W,U,$){const{generalRules:_,typedDeps:j}=W,B=!!j,Q=j?.type??null,q=j?.deps??[];let z=-1;if(Q)z=_.findIndex((Y)=>p[Y.rule.ruleName]===Q);const F=!!$.options?.enableImplicitConversion&&!U?.transform.some((Y)=>!Y.options?.serializeOnly);let H=null;if(!B&&F&&z<0)for(let Y=0;Y<_.length;Y++){const A=p[_[Y].rule.ruleName];if(A){z=Y;H=A;break}}const D=z>=0?_[z]:void 0;let L=null;if(!B&&!H&&F&&U?.type?.fn)try{const Y=U.type.fn(),A=Array.isArray(Y)?Y[0]:Y;L=A?ZQ[A.name]??null:null}catch(Y){throw new N(`field "${Z}": @Field type function threw: ${Y.message}`,{cause:Y})}return{effectiveGateType:Q??H??L,gateDeps:q,typeAsserterIdx:z,typeAsserter:D,enableConversion:F,asserterInferredGate:H,typeHintGate:L}}function qQ(Z,W,U,$,_,j,B){let Q="";const q=G(Z),{effectiveGateType:z,gateCondition:F,gateErrorCode:H,gateEmitCtx:D,otherGeneral:L,gateDeps:Y,typeAsserter:A,enableConversion:O}=j,J=(P)=>{const M=[];if(A&&!$Q.has(A.rule.ruleName))M.push(A);M.push(...L,...Y);return h(Z,W,M,$,_,P,B,!0)};if(U)if(O&&(z==="string"||z==="number"||z==="boolean"||z==="date")){const M=`${X.skip}${q}`;Q+=`var ${M} = false;
73
+ `;Q+=`if (${F}) {
74
+ `;Q+=y(z,W,Z,M,!0,$);Q+=`}
75
+ `;Q+=`if (!${M}) {
76
+ `;if(_.validateOnly)Q+=J(" ");else{const w=`${X.mark}${q}`;Q+=` var ${w} = ${X.errList}.length;
77
+ `;Q+=J(" ");Q+=` if (${X.errList}.length === ${w}) ${X.out}[${JSON.stringify(Z)}] = ${W};
78
+ `}Q+=`}
79
+ `}else{Q+=`if (${F}) ${D.fail(H)};
80
+ `;Q+=`else {
81
+ `;if(_.validateOnly)Q+=J(" ");else{const M=`${X.mark}${q}`;Q+=` var ${M} = ${X.errList}.length;
82
+ `;Q+=J(" ");Q+=` if (${X.errList}.length === ${M}) ${X.out}[${JSON.stringify(Z)}] = ${W};
83
+ `}Q+=`}
84
+ `}else if(O&&(z==="string"||z==="number"||z==="boolean"||z==="date")){Q+=`if (${F}) {
85
+ `;Q+=y(z,W,Z,null,!1,$);Q+=`}
86
+ `;Q+=J("");if(!_.validateOnly)Q+=`${X.out}[${JSON.stringify(Z)}] = ${W};
87
+ `}else{Q+=`if (${F}) ${D.fail(H)};
88
+ `;Q+=J("");if(!_.validateOnly)Q+=`${X.out}[${JSON.stringify(Z)}] = ${W};
89
+ `}return Q}function _Q(Z,W,U,$,_,j,B){let Q="";if($)if(U.length===0){if(!j.validateOnly)Q+=`${X.out}[${JSON.stringify(Z)}] = ${W};
90
+ `}else if(j.validateOnly)Q+=h(Z,W,U,_,j,"",B);else{const q=`${X.mark}${G(Z)}`;Q+=`var ${q} = ${X.errList}.length;
91
+ `;Q+=h(Z,W,U,_,j,"",B);Q+=`if (${X.errList}.length === ${q}) ${X.out}[${JSON.stringify(Z)}] = ${W};
92
+ `}else{Q+=h(Z,W,U,_,j,"",B);if(!j.validateOnly)Q+=`${X.out}[${JSON.stringify(Z)}] = ${W};
93
+ `}return Q}function HQ(Z,W,U,$,_,j,B){let Q="";if(U.length===0)return Q;const q=j.pathPrefix?`${j.pathPrefix}+${JSON.stringify(Z)}`:JSON.stringify(Z),z=G(Z),F=`${X.index}${z}`,H=`${X.setIdx}${z}`,D=`${X.setVal}${z}`,L=`${X.mapIdx}${z}`,Y=`${X.mapVal}${z}`,A=`__bk$ep_${z}`,O=`__bk$ck${z}`;Q+=`var ${O} = Array.isArray(${W})?1:(${W} instanceof Set?2:(${W} instanceof Map?3:0));
94
+ `;Q+=`var ${A} = ${q}+'[';
95
+ `;Q+=`if (${O} === 0) ${_.fail("isArray")};
96
+ `;for(const J of U){const P=V(J,Z,W,j),M=J.groups&&J.groups.length>0&&!k(J.groups,B)?J.groups:null,w=M?`if ((${X.group0} === null && !${X.groupsSet}) || ${n(X.group0,X.groupsSet,M)}) {
97
+ `:"",S=M?`}
98
+ `:"",R=[{guard:`Array.isArray(${W})`,idxVar:F,elemExpr:`${W}[${F}]`,loopHeader:`for (var ${F}=0; ${F}<${W}.length; ${F}++)`,counterDecl:"",counterInc:""},{guard:`${W} instanceof Set`,idxVar:H,elemExpr:D,loopHeader:`for (var ${D} of ${W})`,counterDecl:`var ${H} = 0;
99
+ `,counterInc:`${H}++;
100
+ `},{guard:`${W} instanceof Map`,idxVar:L,elemExpr:Y,loopHeader:`for (var ${Y} of ${W}.values())`,counterDecl:`var ${L} = 0;
101
+ `,counterInc:`${L}++;
102
+ `}],T=(b)=>{const i={..._,fail:(K)=>$?`${X.errList}.push({path:${A}+${b.idxVar}+']',code:${JSON.stringify(K)}${P}})`:j.validateOnly?`return [{path:${A}+${b.idxVar}+']',code:${JSON.stringify(K)}${P}}]`:`return err([{path:${A}+${b.idxVar}+']',code:${JSON.stringify(K)}${P}}])`};let E="";E+=` ${b.counterDecl}`;E+=` ${b.loopHeader} {
103
+ `;E+=" "+J.rule.emit(b.elemExpr,i)+`
104
+ `;if(b.counterInc)E+=` ${b.counterInc}`;E+=` }
105
+ `;return E};Q+=w;Q+=`if (${O} === 1) {
106
+ `;Q+=T(R[0]);Q+=`} else if (${O} === 2) {
107
+ `;Q+=T(R[1]);Q+=`} else if (${O} === 3) {
108
+ `;Q+=T(R[2]);Q+=`}
109
+ `;Q+=S}return Q}function LQ(Z,W,U,$,_,j,B,Q){const q=UQ(Z,U),z=YQ(Z,q,B,j);let F="";if(!!q.typedDeps||z.asserterInferredGate||z.typeHintGate){const D=z.typeAsserter?q.generalRules.filter((O,J)=>J!==z.typeAsserterIdx):q.generalRules;let L,Y;if(z.typeAsserter)Y=z.typeAsserter.rule.ruleName;else if(z.gateDeps.length>0)Y=z.gateDeps[0].rule.ruleName;else Y="conversionFailed";if(z.effectiveGateType==="date")L=`!(${W} instanceof Date) || isNaN(${W}.getTime())`;else if(z.effectiveGateType==="array")L=`!Array.isArray(${W})`;else if(z.effectiveGateType==="object")L=`typeof ${W} !== 'object' || ${W} === null || Array.isArray(${W})`;else if(z.effectiveGateType==="number")L=`typeof ${W} !== 'number' || isNaN(${W})`;else L=`typeof ${W} !== '${z.effectiveGateType}'`;const A=z.typeAsserter?s(_,Z,W,z.typeAsserter,j):_;F+=qQ(Z,W,$,_,j,{effectiveGateType:z.effectiveGateType,gateCondition:L,gateErrorCode:Y,gateEmitCtx:A,otherGeneral:D,gateDeps:z.gateDeps,typeAsserter:z.typeAsserter,enableConversion:z.enableConversion},Q)}else F+=_Q(Z,W,q.generalRules,$,_,j,Q);F+=HQ(Z,W,q.each,$,_,j,Q);return F}function jQ(Z,W,U,$,_){const{collectErrors:j,execs:B}=$,Q=G(Z),q=U.type.collection,z=$.isAsync?"await ":"";let F=-1;if(U.type.resolvedCollectionValue){const D=u(U.type.resolvedCollectionValue);F=B.length;B.push(D)}let H="";if(q==="Set"){H+=`if (Array.isArray(${W})) {
110
+ `;const D=U.validation.filter((Y)=>!Y.each);H+=h(Z,W,D,_,$," ");if(F>=0){const Y=`${X.index}${Q}`;H+=` var ${X.arr}${Q} = new Set();
111
+ `;H+=` for (var ${Y}=0; ${Y}<${W}.length; ${Y}++) {
112
+ `;H+=` var ${X.result}${Q} = ${z}execs[${F}].deserialize(${W}[${Y}], opts);
113
+ `;H+=` if (isErr(${X.result}${Q})) {
114
+ `;if(j){H+=` var ${X.errors}${Q} = ${X.result}${Q}.data;
115
+ `;H+=` var __bk$pp${Q} = ${JSON.stringify(Z)}+'['+${Y}+'].';
116
+ `;H+=` for (var ${X.nestedIdx}${Q}=0; ${X.nestedIdx}${Q}<${X.errors}${Q}.length; ${X.nestedIdx}${Q}++) {
117
+ `;H+=" "+I(X.errList,`__bk$pp${Q}+${X.errors}${Q}[${X.nestedIdx}${Q}].path`,`${X.errors}${Q}[${X.nestedIdx}${Q}]`,`__ne${Q}`);H+=` }
118
+ `}else{H+=` var ${X.errors}${Q} = ${X.result}${Q}.data;
119
+ `;H+=` var __bk$pp${Q} = ${JSON.stringify(Z)}+'['+${Y}+'].';
120
+ `;H+=" "+g(`__bk$pp${Q}+${X.errors}${Q}[0].path`,`${X.errors}${Q}[0]`,`__ne${Q}`)}H+=` } else { ${X.arr}${Q}.add(${X.result}${Q}); }
121
+ `;H+=` }
122
+ `;H+=` ${X.out}[${JSON.stringify(Z)}] = ${X.arr}${Q};
123
+ `}else H+=` ${X.out}[${JSON.stringify(Z)}] = new Set(${W});
124
+ `;const L=U.validation.filter((Y)=>Y.each);if(L.length>0){const Y=`${X.setIdx}${Q}`,A=`${X.setVal}${Q}`;H+=` var ${Y} = 0;
125
+ `;H+=` for (var ${A} of ${X.out}[${JSON.stringify(Z)}]) {
126
+ `;for(const O of L){const J=V(O,Z,W,$),M={..._,fail:(w)=>j?`${X.errList}.push({path:${JSON.stringify(Z)}+'['+${Y}+']',code:${JSON.stringify(w)}${J}})`:`return err([{path:${JSON.stringify(Z)}+'['+${Y}+']',code:${JSON.stringify(w)}${J}}])`};H+=` ${O.rule.emit(A,M)}
127
+ `}H+=` ${Y}++;
128
+ `;H+=` }
129
+ `}H+=`} else { ${_.fail("isArray")}; }
130
+ `}else{H+=`if (${W} != null && typeof ${W} === 'object' && !Array.isArray(${W})) {
131
+ `;if(F>=0){const D=`${X.key}${Q}`,L=`__bk$mk${Q}`,Y=`__bk$mi${Q}`;H+=` var ${X.arr}${Q} = new Map();
132
+ `;H+=` var ${L} = Object.keys(${W});
133
+ `;H+=` for (var ${Y}=0; ${Y}<${L}.length; ${Y}++) {
134
+ `;H+=` var ${D} = ${L}[${Y}];
135
+ `;H+=` var ${X.result}${Q} = ${z}execs[${F}].deserialize(${W}[${D}], opts);
136
+ `;H+=` if (isErr(${X.result}${Q})) {
137
+ `;if(j){H+=` var ${X.errors}${Q} = ${X.result}${Q}.data;
138
+ `;H+=` var __bk$pp${Q} = ${JSON.stringify(Z)}+'['+${D}+'].';
139
+ `;H+=` for (var ${X.nestedIdx}${Q}=0; ${X.nestedIdx}${Q}<${X.errors}${Q}.length; ${X.nestedIdx}${Q}++) {
140
+ `;H+=" "+I(X.errList,`__bk$pp${Q}+${X.errors}${Q}[${X.nestedIdx}${Q}].path`,`${X.errors}${Q}[${X.nestedIdx}${Q}]`,`__ne${Q}`);H+=` }
141
+ `}else{H+=` var ${X.errors}${Q} = ${X.result}${Q}.data;
142
+ `;H+=` var __bk$pp${Q} = ${JSON.stringify(Z)}+'['+${D}+'].';
143
+ `;H+=" "+g(`__bk$pp${Q}+${X.errors}${Q}[0].path`,`${X.errors}${Q}[0]`,`__ne${Q}`)}H+=` } else { ${X.arr}${Q}.set(${D}, ${X.result}${Q}); }
144
+ `;H+=` }
145
+ `;H+=` ${X.out}[${JSON.stringify(Z)}] = ${X.arr}${Q};
146
+ `}else{const D=`__bk$mk${Q}`,L=`__bk$mi${Q}`;H+=` var ${X.arr}${Q} = new Map();
147
+ `;H+=` var ${D} = Object.keys(${W});
148
+ `;H+=` for (var ${L}=0; ${L}<${D}.length; ${L}++) {
149
+ `;H+=` var ${X.key}${Q} = ${D}[${L}];
150
+ `;H+=` ${X.arr}${Q}.set(${X.key}${Q}, ${W}[${X.key}${Q}]);
151
+ `;H+=` }
152
+ `;H+=` ${X.out}[${JSON.stringify(Z)}] = ${X.arr}${Q};
153
+ `}H+=`} else { ${_.fail("isObject")}; }
154
+ `}return H}function zQ(Z,W,U,$,_){const{collectErrors:j,execs:B}=$;if(!U.type)return`${X.out}[${JSON.stringify(Z)}] = ${W};
155
+ `;let Q="";const q=G(Z);if(U.type.discriminator){const z=JSON.stringify(U.type.discriminator.property);Q+=`var ${X.disc}${q} = ${W} && ${W}[${z}];
156
+ `;Q+=`switch (${X.disc}${q}) {
157
+ `;for(const L of U.type.discriminator.subTypes){const Y=u(L.value),A=B.length;B.push(Y);const O=$.isAsync?"await ":"";Q+=` case ${JSON.stringify(L.name)}:
158
+ `;Q+=` var ${X.result}${q} = ${O}execs[${A}].deserialize(${W}, opts);
159
+ `;Q+=f(Z,`${X.result}${q}`,j,$.pathPrefix);Q+=` break;
160
+ `}const F=JSON.stringify(U.type.discriminator.subTypes.map((L)=>L.name)),H=_.pathExpr??JSON.stringify(Z),D=`${X.disc}${q}`;if(j)Q+=` default: ${X.errList}.push({path:${H},code:'invalidDiscriminator',context:{received:${D},validSubTypes:${F}}});
161
+ `;else if($.validateOnly)Q+=` default: return [{path:${H},code:'invalidDiscriminator',context:{received:${D},validSubTypes:${F}}}];
162
+ `;else Q+=` default: return err([{path:${H},code:'invalidDiscriminator',context:{received:${D},validSubTypes:${F}}}]);
163
+ `;Q+=`}
164
+ `;if(U.type.keepDiscriminatorProperty){const L=JSON.stringify(Z);Q+=`{var __dh=${X.out}[${L}]; if(__dh!=null) __dh[${z}]=${X.disc}${q};}
165
+ `}}else{const z=U.type.resolvedClass??U.type.fn(),F=u(z),H=B.length;B.push(F);if(U.type.isArray||U.flags.validateNestedEach||U.validation.some((L)=>L.each)){const L=`${X.index}${q}`,Y=$.isAsync?"await ":"";Q+=`if (Array.isArray(${W})) {
166
+ `;const A=U.validation.filter((O)=>!O.each);Q+=h(Z,W,A,_,$," ");Q+=` var ${X.arr}${q} = [];
167
+ `;Q+=` for (var ${L}=0; ${L}<${W}.length; ${L}++) {
168
+ `;Q+=` var ${X.result}${q} = ${Y}execs[${H}].deserialize(${W}[${L}], opts);
169
+ `;Q+=` if (isErr(${X.result}${q})) {
170
+ `;if(j){Q+=` var ${X.errors}${q} = ${X.result}${q}.data;
171
+ `;Q+=` var __bk$pp${q} = ${JSON.stringify(Z)}+'['+${L}+'].';
172
+ `;Q+=` for (var ${X.nestedIdx}${q}=0; ${X.nestedIdx}${q}<${X.errors}${q}.length; ${X.nestedIdx}${q}++) {
173
+ `;Q+=" "+I(X.errList,`__bk$pp${q}+${X.errors}${q}[${X.nestedIdx}${q}].path`,`${X.errors}${q}[${X.nestedIdx}${q}]`,`__ne${q}`);Q+=` }
174
+ `}else{Q+=` var ${X.errors}${q} = ${X.result}${q}.data;
175
+ `;Q+=` var __bk$pp${q} = ${JSON.stringify(Z)}+'['+${L}+'].';
176
+ `;Q+=" "+g(`__bk$pp${q}+${X.errors}${q}[0].path`,`${X.errors}${q}[0]`,`__ne${q}`)}Q+=` } else { ${X.arr}${q}.push(${X.result}${q}); }
177
+ `;Q+=` }
178
+ `;Q+=` ${X.out}[${JSON.stringify(Z)}] = ${X.arr}${q};
179
+ `;Q+=`} else { ${_.fail("isArray")}; }
180
+ `}else{const L=$.isAsync?"await ":"";Q+=`if (${W} != null && typeof ${W} === 'object' && !Array.isArray(${W})) {
181
+ `;Q+=` var ${X.result}${q} = ${L}execs[${H}].deserialize(${W}, opts);
182
+ `;Q+=f(Z,`${X.result}${q}`,j,$.pathPrefix);Q+=`} else { ${_.fail("isObject")}; }
183
+ `}}return Q}function f(Z,W,U,$){const _=G(Z),j=$?`${$}+${JSON.stringify(Z+".")}`:JSON.stringify(Z+".");if(U){const Q=`${X.errors}${_}[${X.nestedIdx}${_}]`;return` if (isErr(${W})) {
184
+ var ${X.errors}${_} = ${W}.data;
185
+ var __bk$pp${_} = ${j};
186
+ for (var ${X.nestedIdx}${_}=0; ${X.nestedIdx}${_}<${X.errors}${_}.length; ${X.nestedIdx}${_}++) {
187
+ `+I(X.errList,`__bk$pp${_}+${Q}.path`,Q,`__ne${_}`)+` }
188
+ } else { ${X.out}[${JSON.stringify(Z)}] = ${W}; }
189
+ `}const B=`${X.errors}${_}[0]`;return` if (isErr(${W})) {
190
+ var ${X.errors}${_} = ${W}.data;
191
+ var __bk$pp${_} = ${j};
192
+ `+g(`__bk$pp${_}+${B}.path`,B,`__ne${_}`)+` } else { ${X.out}[${JSON.stringify(Z)}] = ${W}; }
193
+ `}function C(Z,W,U,$,_,j){const B=j.inlineNestedClasses;B.add(W);const Q={...j,pathPrefix:$,varPrefix:_,inputExpr:U,exposeDefaultValues:!1};let q="";for(const[z,F]of Object.entries(Z))q+=o(z,F,Q);B.delete(W);return q}function BQ(Z,W,U,$,_){const{collectErrors:j,execs:B}=$;if(!U.type)return"";const Q=($.varPrefix||"")+G(Z);let q="";if(!$.inlineNestedClasses)$.inlineNestedClasses=new Set;if(U.type.discriminator){const z=JSON.stringify(U.type.discriminator.property);q+=`var ${X.disc}${Q} = ${W} && ${W}[${z}];
194
+ `;q+=`switch (${X.disc}${Q}) {
195
+ `;for(const L of U.type.discriminator.subTypes){const Y=u(L.value),A=Y.merged,O=A&&!$.inlineNestedClasses.has(L.value);q+=` case ${JSON.stringify(L.name)}:
196
+ `;if(O){const J=$.pathPrefix?`${$.pathPrefix}+${JSON.stringify(Z+".")}`:JSON.stringify(Z+"."),P=`${Q}_d${G(L.name)}_`;q+=C(A,L.value,W,J,P,$)}else{const J=B.length;B.push(Y);const P=$.isAsync?"await ":"";q+=` var ${X.result}${Q} = ${P}execs[${J}].validate(${W}, opts);
197
+ `;q+=v(Z,`${X.result}${Q}`,j,$.pathPrefix)}q+=` break;
198
+ `}const F=JSON.stringify(U.type.discriminator.subTypes.map((L)=>L.name)),H=_.pathExpr??JSON.stringify(Z),D=`${X.disc}${Q}`;if(j)q+=` default: ${X.errList}.push({path:${H},code:'invalidDiscriminator',context:{received:${D},validSubTypes:${F}}});
199
+ `;else q+=` default: return [{path:${H},code:'invalidDiscriminator',context:{received:${D},validSubTypes:${F}}}];
200
+ `;q+=`}
201
+ `}else{const z=U.type.resolvedClass??U.type.fn(),F=u(z),H=F.merged,D=U.type.isArray||U.flags.validateNestedEach||U.validation.some((Y)=>Y.each),L=H&&!$.inlineNestedClasses.has(z);if(D){const Y=`${X.index}${Q}`;q+=`if (Array.isArray(${W})) {
202
+ `;const A=U.validation.filter((O)=>!O.each);q+=h(Z,W,A,_,$," ");q+=` for (var ${Y}=0; ${Y}<${W}.length; ${Y}++) {
203
+ `;if(L){const O=`__il$${Q}item`,J=`__bk$pp${Q}`,P=J,M=$.pathPrefix?`${$.pathPrefix}+${JSON.stringify(Z)}+'['+${Y}+'].'`:`${JSON.stringify(Z)}+'['+${Y}+'].'`,w=`${Q}i_`;q+=` var ${O} = ${W}[${Y}];
204
+ `;q+=` var ${J} = ${M};
205
+ `;q+=` if (${O} == null || typeof ${O} !== 'object' || Array.isArray(${O})) `;if(j)q+=`${X.errList}.push({path:${J},code:'invalidInput'});
206
+ `;else q+=`return [{path:${J},code:'invalidInput'}];
207
+ `;q+=` else {
208
+ `;q+=C(H,z,O,P,w,$);q+=` }
209
+ `}else{const O=B.length;B.push(F);const J=$.isAsync?"await ":"";q+=` var ${X.result}${Q} = ${J}execs[${O}].validate(${W}[${Y}], opts);
210
+ `;q+=` if (${X.result}${Q} !== null) {
211
+ `;const P=`__bk$pp${Q}`,M=$.pathPrefix?`${$.pathPrefix}+${JSON.stringify(Z)}+'['+${Y}+'].'`:`${JSON.stringify(Z)}+'['+${Y}+'].'`;q+=` var ${P} = ${M};
212
+ `;if(j){q+=` for (var ${X.nestedIdx}${Q}=0; ${X.nestedIdx}${Q}<${X.result}${Q}.length; ${X.nestedIdx}${Q}++) {
213
+ `;q+=" "+I(X.errList,`${P}+${X.result}${Q}[${X.nestedIdx}${Q}].path`,`${X.result}${Q}[${X.nestedIdx}${Q}]`,`__ne${Q}`);q+=` }
214
+ `}else q+=" "+g(`${P}+${X.result}${Q}[0].path`,`${X.result}${Q}[0]`,`__ne${Q}`,!0);q+=` }
215
+ `}q+=` }
216
+ `;q+=`} else { ${_.fail("isArray")}; }
217
+ `}else{q+=`if (${W} != null && typeof ${W} === 'object' && !Array.isArray(${W})) {
218
+ `;if(L){const Y=$.pathPrefix?`${$.pathPrefix}+${JSON.stringify(Z+".")}`:JSON.stringify(Z+"."),A=`${Q}_`;q+=C(H,z,W,Y,A,$)}else{const Y=B.length;B.push(F);const A=$.isAsync?"await ":"";q+=` var ${X.result}${Q} = ${A}execs[${Y}].validate(${W}, opts);
219
+ `;q+=v(Z,`${X.result}${Q}`,j,$.pathPrefix)}q+=`} else { ${_.fail("isObject")}; }
220
+ `}}return q}function v(Z,W,U,$){const _=G(Z),j=`__bk$pp${_}`,B=$?`${$}+${JSON.stringify(Z+".")}`:JSON.stringify(Z+".");if(U){const q=`${W}[${X.nestedIdx}${_}]`;return` if (${W} !== null) {
221
+ var ${j} = ${B};
222
+ for (var ${X.nestedIdx}${_}=0; ${X.nestedIdx}${_}<${W}.length; ${X.nestedIdx}${_}++) {
223
+ `+I(X.errList,`${j}+${q}.path`,q,`__ne${_}`)+` }
224
+ }
225
+ `}const Q=`${W}[0]`;return` if (${W} !== null) {
226
+ var ${j} = ${B};
227
+ `+g(`${j}+${Q}.path`,Q,`__ne${_}`,!0)+` }
228
+ `}function FQ(Z,W,U,$,_){const{collectErrors:j,execs:B}=$,Q=($.varPrefix||"")+G(Z),q=U.type.collection,z=$.isAsync?"await ":"";if(!$.inlineNestedClasses)$.inlineNestedClasses=new Set;let F,H,D;if(U.type.resolvedCollectionValue){F=U.type.resolvedCollectionValue;H=u(F);D=H.merged}const L=F&&D&&!$.inlineNestedClasses.has(F);let Y="";if(q==="Set"){Y+=`if (Array.isArray(${W})) {
229
+ `;const A=U.validation.filter((J)=>!J.each);Y+=h(Z,W,A,_,$," ");if(H){const J=`${X.index}${Q}`;Y+=` for (var ${J}=0; ${J}<${W}.length; ${J}++) {
230
+ `;if(L){const P=`__il$${Q}ci`,M=`__bk$pp${Q}`,w=$.pathPrefix?`${$.pathPrefix}+${JSON.stringify(Z)}+'['+${J}+'].'`:`${JSON.stringify(Z)}+'['+${J}+'].'`,S=`${Q}c_`;Y+=` var ${P} = ${W}[${J}];
231
+ `;Y+=` var ${M} = ${w};
232
+ `;Y+=` if (${P} == null || typeof ${P} !== 'object' || Array.isArray(${P})) `;if(j)Y+=`${X.errList}.push({path:${M},code:'invalidInput'});
233
+ `;else Y+=`return [{path:${M},code:'invalidInput'}];
234
+ `;Y+=` else {
235
+ `;Y+=C(D,F,P,M,S,$);Y+=` }
236
+ `}else{const P=B.length;B.push(H);Y+=` var ${X.result}${Q} = ${z}execs[${P}].validate(${W}[${J}], opts);
237
+ `;Y+=` if (${X.result}${Q} !== null) {
238
+ `;const M=`__bk$pp${Q}`,w=$.pathPrefix?`${$.pathPrefix}+${JSON.stringify(Z)}+'['+${J}+'].'`:`${JSON.stringify(Z)}+'['+${J}+'].'`;Y+=` var ${M} = ${w};
239
+ `;if(j){Y+=` for (var ${X.nestedIdx}${Q}=0; ${X.nestedIdx}${Q}<${X.result}${Q}.length; ${X.nestedIdx}${Q}++) {
240
+ `;Y+=" "+I(X.errList,`${M}+${X.result}${Q}[${X.nestedIdx}${Q}].path`,`${X.result}${Q}[${X.nestedIdx}${Q}]`,`__ne${Q}`);Y+=` }
241
+ `}else Y+=" "+g(`${M}+${X.result}${Q}[0].path`,`${X.result}${Q}[0]`,`__ne${Q}`,!0);Y+=` }
242
+ `}Y+=` }
243
+ `}const O=U.validation.filter((J)=>J.each);if(O.length>0){const J=`${X.index}${Q}e`;Y+=` for (var ${J}=0; ${J}<${W}.length; ${J}++) {
244
+ `;for(const P of O){const M=`__bk$ep_${Q}`,w=V(P,Z,W,$),R={..._,fail:(T)=>j?`${X.errList}.push({path:${M}+${J}+']',code:${JSON.stringify(T)}${w}})`:`return [{path:${M}+${J}+']',code:${JSON.stringify(T)}${w}}]`};if(!Y.includes(`var ${M}`)){const T=$.pathPrefix?`${$.pathPrefix}+${JSON.stringify(Z)}+'['`:`${JSON.stringify(Z)}+'['`;Y+=` var ${M} = ${T};
245
+ `}Y+=` ${P.rule.emit(`${W}[${J}]`,R)}
246
+ `}Y+=` }
247
+ `}Y+=`} else { ${_.fail("isArray")}; }
248
+ `}else{Y+=`if (${W} != null && typeof ${W} === 'object' && !Array.isArray(${W})) {
249
+ `;if(H){const A=`${X.key}${Q}`,O=`__bk$vk${Q}`,J=`__bk$vi${Q}`;Y+=` var ${O} = Object.keys(${W});
250
+ `;Y+=` for (var ${J}=0; ${J}<${O}.length; ${J}++) {
251
+ `;Y+=` var ${A} = ${O}[${J}];
252
+ `;if(L){const P=`__il$${Q}mi`,M=$.pathPrefix?`${$.pathPrefix}+${JSON.stringify(Z)}+'['+${A}+'].'`:`${JSON.stringify(Z)}+'['+${A}+'].'`,w=`${Q}m_`,S=M;Y+=` var ${P} = ${W}[${A}];
253
+ `;Y+=` if (${P} == null || typeof ${P} !== 'object' || Array.isArray(${P})) `;if(j)Y+=`${X.errList}.push({path:${S},code:'invalidInput'});
254
+ `;else Y+=`return [{path:${S},code:'invalidInput'}];
255
+ `;Y+=` else {
256
+ `;Y+=C(D,F,P,M,w,$);Y+=` }
257
+ `}else{const P=B.length;B.push(H);Y+=` var ${X.result}${Q} = ${z}execs[${P}].validate(${W}[${A}], opts);
258
+ `;Y+=` if (${X.result}${Q} !== null) {
259
+ `;const M=`__bk$pp${Q}`,w=$.pathPrefix?`${$.pathPrefix}+${JSON.stringify(Z)}+'['+${A}+'].'`:`${JSON.stringify(Z)}+'['+${A}+'].'`;Y+=` var ${M} = ${w};
260
+ `;if(j){Y+=` for (var ${X.nestedIdx}${Q}=0; ${X.nestedIdx}${Q}<${X.result}${Q}.length; ${X.nestedIdx}${Q}++) {
261
+ `;Y+=" "+I(X.errList,`${M}+${X.result}${Q}[${X.nestedIdx}${Q}].path`,`${X.result}${Q}[${X.nestedIdx}${Q}]`,`__ne${Q}`);Y+=` }
262
+ `}else Y+=" "+g(`${M}+${X.result}${Q}[0].path`,`${X.result}${Q}[0]`,`__ne${Q}`,!0);Y+=` }
263
+ `}Y+=` }
264
+ `}Y+=`} else { ${_.fail("isObject")}; }
265
+ `}return Y}function JQ(Z,W,U=""){const{collectErrors:$,regexes:_,refs:j,execs:B,validateOnly:Q,pathPrefix:q}=W,z=q?`${q}+${JSON.stringify(Z)}`:JSON.stringify(Z);return{addRegex(F){_.push(F);return _.length-1},addRef(F){j.push(F);return j.length-1},addExecutor(F){B.push(F);return B.length-1},fail(F){if($)return`${X.errList}.push({path:${z},code:${JSON.stringify(F)}${U}})`;else if(Q)return`return [{path:${z},code:${JSON.stringify(F)}${U}}]`;return`return err([{path:${z},code:${JSON.stringify(F)}${U}}])`},collectErrors:$,pathExpr:z}}export{buildDeserializeCode,buildValidateCode};