ata-validator 1.31.2 → 1.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.d.ts CHANGED
@@ -497,6 +497,16 @@ export interface Validator<T = unknown> {
497
497
  /** Parse JSON and validate against the schema. Returns the parsed value and the validation result. Works without the native addon. */
498
498
  validateAndParse(jsonString: string | Buffer): ValidateAndParseResult<T>;
499
499
 
500
+ /**
501
+ * Validate, then return a copy holding only the properties the schema declares, with defaults
502
+ * filled. The input is not modified. Throws an error named `AtaValidationError`, with `errors`,
503
+ * when the value is invalid, and a `TypeError` where the kept key set cannot be proven from the
504
+ * schema (a $ref it cannot inline, patternProperties, an object that allows unknown keys) or the
505
+ * validator rewrites input (`coerceTypes`, custom keywords). Same result as the `parse` export of
506
+ * a module built with `ata build`.
507
+ */
508
+ parse(data: unknown): T;
509
+
500
510
  /** Ultra-fast buffer validation via native addon */
501
511
  isValid(input: Buffer | Uint8Array | string): boolean;
502
512
 
package/index.js CHANGED
@@ -244,26 +244,36 @@ function collectRemovals(schema, actions, path) {
244
244
  // Emit the in-place strip for one schema node and everything under its
245
245
  // `properties`. Scope matches collectRemovals(): object properties only, so
246
246
  // the two paths keep the same answer.
247
+ // Each level's code, its own removal loop and its children's, sits inside the
248
+ // guard for that level. A child used to be guarded only on itself, so its
249
+ // access expression read through a parent that could be absent or null:
250
+ // `{ c: { properties: { d: { additionalProperties: false } } } }` threw a
251
+ // TypeError from validate({}) whenever the optional `c` was missing.
247
252
  function emitRemovals(node, access, lines, depth, seen) {
248
253
  if (!node || typeof node !== 'object' || !node.properties) return;
249
254
  if (seen.has(node)) return;
250
255
  seen.add(node);
251
256
  const keys = Object.keys(node.properties);
252
- if (node.additionalProperties === false && keys.length > 0) {
257
+ const body = [];
258
+ // With no declared property every key is additional, as the interpreted
259
+ // engine's remover has it; this pass used to skip such a node entirely.
260
+ if (node.additionalProperties === false) {
253
261
  const kv = '_k' + depth;
254
262
  const checks = keys.map((k) => `${kv}!==${JSON.stringify(k)}`).join('&&');
255
- const guard = depth === 0
256
- ? ''
257
- : `if(${access}!==null&&typeof ${access}==='object'&&!Array.isArray(${access}))`;
258
- lines.push(`${guard}for(var ${kv} in ${access})if(${checks})delete ${access}[${kv}]`);
263
+ body.push(keys.length > 0
264
+ ? `for(var ${kv} in ${access})if(${checks})delete ${access}[${kv}]`
265
+ : `for(var ${kv} in ${access})delete ${access}[${kv}]`);
259
266
  }
260
267
  for (const key of keys) {
261
268
  const prop = node.properties[key];
262
269
  if (prop && typeof prop === 'object' && prop.properties) {
263
- emitRemovals(prop, `${access}[${JSON.stringify(key)}]`, lines, depth + 1, seen);
270
+ emitRemovals(prop, `${access}[${JSON.stringify(key)}]`, body, depth + 1, seen);
264
271
  }
265
272
  }
266
273
  seen.delete(node);
274
+ if (body.length === 0) return;
275
+ if (depth === 0) lines.push(...body);
276
+ else lines.push(`if(${access}!==null&&typeof ${access}==='object'&&!Array.isArray(${access})){${body.join('\n')}}`);
267
277
  }
268
278
 
269
279
  // Generate a fast preprocess function via codegen instead of closure arrays
@@ -1306,6 +1316,23 @@ class Validator {
1306
1316
  this._mutatesInput = !!(preprocess || options.coerceTypes || options.removeAdditional);
1307
1317
  this._preprocess = preprocess;
1308
1318
 
1319
+ // removeAdditional alone, the common parse-and-strip use: a verdict function
1320
+ // that deletes unknown keys in the walk it already makes, where the pass
1321
+ // above walks every object a second time just to find them. It answers
1322
+ // the documents it accepts; anything it rejects takes the full path below,
1323
+ // which removes, validates and reports exactly as before, so a rejected
1324
+ // document is left as clean as it always was. The generator declines any
1325
+ // schema where deleting during the walk could change an answer.
1326
+ let fusedRemove = null;
1327
+ if (preprocess && options.removeAdditional && !options.coerceTypes && !this._interpretOnly &&
1328
+ !this._userFormats && !this._usesKeywords && !this._schemaStr.includes('"default"')) {
1329
+ try {
1330
+ fusedRemove = compileToJSCodegen(schemaObj, this._schemaMap.size > 0 ? this._schemaMap : null, null, { removeAdditional: true });
1331
+ } catch {
1332
+ fusedRemove = null;
1333
+ }
1334
+ }
1335
+
1309
1336
  // Detect if schema is "selective" -- doesn't recurse into arrays/deep objects.
1310
1337
  const hasArrayTraversal =
1311
1338
  schemaObj &&
@@ -1577,9 +1604,11 @@ class Validator {
1577
1604
  // The verdict methods answer validate()'s question without building the
1578
1605
  // error list, so they run the same preprocess pass. Skipping it made the
1579
1606
  // two disagree on input that coercion or a default would have fixed.
1580
- _bindVerdict(this, preprocess
1581
- ? (data) => { preprocess(data); return jsFn(data) }
1582
- : jsFn);
1607
+ _bindVerdict(this, fusedRemove
1608
+ ? (data) => fusedRemove(data) || (preprocess(data), jsFn(data))
1609
+ : preprocess
1610
+ ? (data) => { preprocess(data); return jsFn(data) }
1611
+ : jsFn);
1583
1612
  // Same preference as the object path: the combined function first, since
1584
1613
  // it validates and collects in one pass, and the error generator behind
1585
1614
  // it. `errPreferCombined` is that order, resolved on the first rejection
@@ -1794,7 +1823,12 @@ class Validator {
1794
1823
  : _validate;
1795
1824
  _bindVerdict(this, this._fastVerdict
1796
1825
  ? this._fastVerdict
1797
- : (data) => _validate(data).valid);
1826
+ // The rewrite runs first here too, as it does for validate() above; a
1827
+ // verdict without it disagreed with validate() on input that coercion,
1828
+ // a default or a removed key would have fixed.
1829
+ : preprocess
1830
+ ? (data) => { preprocess(data); return _validate(data).valid; }
1831
+ : (data) => _validate(data).valid);
1798
1832
  this.validateAndParse = (jsonStr) => this._compiled.validateAndParse(jsonStr);
1799
1833
  {
1800
1834
  const slot = this._fastSlot;
@@ -1927,12 +1961,22 @@ class Validator {
1927
1961
  // paths that only need a boolean.
1928
1962
  if (this.validate) {
1929
1963
  const _bare = this.validate;
1930
- this.validate = (data) => {
1931
- const r = _bare(data);
1932
- return (r.valid === true && r.data === undefined)
1933
- ? { valid: true, data, errors: r.errors }
1934
- : r;
1935
- };
1964
+ this.validate = fusedRemove
1965
+ // A document the removing verdict accepts is answered here, one call
1966
+ // deep; anything else takes the full path, see fusedRemove above.
1967
+ ? (data) => {
1968
+ if (fusedRemove(data)) return { valid: true, data, errors: VALID_RESULT.errors };
1969
+ const r = _bare(data);
1970
+ return (r.valid === true && r.data === undefined)
1971
+ ? { valid: true, data, errors: r.errors }
1972
+ : r;
1973
+ }
1974
+ : (data) => {
1975
+ const r = _bare(data);
1976
+ return (r.valid === true && r.data === undefined)
1977
+ ? { valid: true, data, errors: r.errors }
1978
+ : r;
1979
+ };
1936
1980
  }
1937
1981
 
1938
1982
  // Custom error messages: if any subschema declares an `errorMessage`
@@ -2851,6 +2895,59 @@ Validator.prototype._extendValidate = function (resolve) {
2851
2895
  // closure, where wrapping the five entry points cost a closure per entry point,
2852
2896
  // the wrappers themselves and an accessor, most of what building a wrapped
2853
2897
  // validator took.
2898
+ // parse(data): validate, then return a copy holding only what the schema
2899
+ // declares, the way the parse() export of an ahead-of-time module does, with
2900
+ // the same emitter behind both. Building the copy from the schema's own key
2901
+ // list costs less than finding and deleting unknown keys, and leaves the
2902
+ // caller's object alone. Where the key set cannot be proven (a $ref it cannot
2903
+ // inline, patternProperties, an open object) the method declines with an
2904
+ // error instead of guessing, as the module ships no parse() there; so it does
2905
+ // under options that rewrite input, whose answers a copy would not reproduce.
2906
+ Validator.prototype.parse = function (data) {
2907
+ let fn = this._parseFn;
2908
+ if (fn === undefined) {
2909
+ fn = _buildParse(this);
2910
+ Object.defineProperty(this, '_parseFn', { value: fn, writable: true, configurable: true, enumerable: false });
2911
+ }
2912
+ return fn(data);
2913
+ };
2914
+
2915
+ function _buildParse(self) {
2916
+ const decline = (why) => () => {
2917
+ throw new TypeError(`parse() is not available for this validator: ${why}. Use validate() with removeAdditional instead.`);
2918
+ };
2919
+ const o = self._options;
2920
+ if (o.coerceTypes || o.removeAdditional === 'all' || self._usesKeywords || self._verdictTail !== null || self._validateTail !== null) {
2921
+ return decline('its options or keywords rewrite or extend what the schema checks');
2922
+ }
2923
+ const { cloneExprFor } = require('./lib/clone-emit');
2924
+ const expr = cloneExprFor(self._schemaObj);
2925
+ if (!expr) return decline('the set of keys to keep cannot be proven from the schema');
2926
+ let copy;
2927
+ try {
2928
+ // eslint-disable-next-line no-new-func
2929
+ copy = new Function('data', 'return ' + expr);
2930
+ } catch {
2931
+ return decline('code generation is not allowed here');
2932
+ }
2933
+ self._ensureCompiled();
2934
+ const verdict = self._jsFn;
2935
+ if (typeof verdict !== 'function') return decline('the schema has no generated verdict function');
2936
+ return (data) => {
2937
+ if (!verdict(data)) {
2938
+ const e = new Error('validation failed');
2939
+ e.name = 'AtaValidationError';
2940
+ let target = data;
2941
+ if (self._mutatesInput) {
2942
+ try { target = structuredClone(data); } catch { target = data; }
2943
+ }
2944
+ e.errors = self.validate(target).errors;
2945
+ throw e;
2946
+ }
2947
+ return copy(data);
2948
+ };
2949
+ }
2950
+
2854
2951
  Validator.prototype._extendChecks = function (resolve) {
2855
2952
  if (typeof resolve !== 'function') throw new TypeError('_extendChecks expects a function');
2856
2953
  this._extendValidate(resolve);
package/lib/aot-impl.js CHANGED
@@ -189,277 +189,7 @@ function emitFormatDecls(closures, mode, declKW) {
189
189
  return { decls: out, exportsSetFormats: false };
190
190
  }
191
191
 
192
- // Emit an expression that rebuilds `access` from the properties the schema
193
- // declares, dropping everything else. This is the sanitising counterpart to
194
- // removeAdditional: instead of enumerating the input and deleting the keys
195
- // that do not belong, it reads only the keys that do. That is O(schema)
196
- // rather than O(input), it leaves the caller's object untouched, and it keeps
197
- // one hidden class instead of walking the object through a transition per
198
- // delete.
199
- //
200
- // Returns null when the clone cannot be proven exact, and the module then
201
- // simply has no parse(). Declining is recoverable; a sanitiser that quietly
202
- // drops a property the schema allows is not.
203
- // Resolve a local JSON pointer ('#', '#/$defs/x', draft-7 '#/definitions/x',
204
- // any '#/...' path) against the schema root. Returns null for anything else:
205
- // external documents, anchors, URNs. Those stay with the runtime engines.
206
- function resolveLocalPointer(root, ref) {
207
- if (ref === '#') return root;
208
- if (typeof ref !== 'string' || !ref.startsWith('#/')) return null;
209
- let cur = root;
210
- for (const raw of ref.slice(2).split('/')) {
211
- let seg = raw.replace(/~1/g, '/').replace(/~0/g, '~');
212
- if (cur === null || typeof cur !== 'object') return null;
213
- if (!Object.prototype.hasOwnProperty.call(cur, seg)) {
214
- try { seg = decodeURIComponent(seg); } catch (_) { return null; }
215
- if (!Object.prototype.hasOwnProperty.call(cur, seg)) return null;
216
- }
217
- cur = cur[seg];
218
- }
219
- return cur;
220
- }
221
-
222
- // Inline local, acyclic $refs before the clone proof runs, so a generated
223
- // schema ($defs + $ref, the shape every schema generator emits) can still get
224
- // a parse(). Only a node that is exactly a $ref plus annotations is replaced;
225
- // a $ref with a constraining sibling, a cycle, an unresolvable target or an
226
- // external reference is left in place, and emitClone declines it as before.
227
- // The walk never mutates its input and never descends into data positions
228
- // (default, const, enum, examples), where an object holding a "$ref" key is
229
- // a value, not a schema.
230
- const _INLINE_ANNOTATIONS = new Set(['$ref', 'title', 'description', '$comment', 'examples', 'deprecated', 'readOnly', 'writeOnly', 'default']);
231
- const _INLINE_DATA_KEYS = new Set(['default', 'const', 'enum', 'examples']);
232
- function inlineRefsForClone(root) {
233
- const state = { budget: 512 };
234
- // `inRef` marks content brought in from behind a reference. The runtime's
235
- // useDefaults fills a default written next to the $ref, and does not fill
236
- // defaults written inside the referenced definition, so the inlined copy
237
- // keeps the first and drops the second. parse() must stay exactly as
238
- // generous as validate(); a parse that fills more is a disagreement, not a
239
- // feature.
240
- const walk = (node, active, inRef) => {
241
- if (!node || typeof node !== 'object') return node;
242
- if (Array.isArray(node)) return node.map((n) => walk(n, active, inRef));
243
- let cur = node;
244
- let entered = false;
245
- let siblingDefault;
246
- while (cur && typeof cur === 'object' && !Array.isArray(cur) && typeof cur.$ref === 'string') {
247
- for (const k of Object.keys(cur)) if (!_INLINE_ANNOTATIONS.has(k)) return node;
248
- if (active.has(cur.$ref) || state.budget-- <= 0) return node;
249
- const target = resolveLocalPointer(root, cur.$ref);
250
- if (!target || typeof target !== 'object' || Array.isArray(target)) return node;
251
- if (!entered && !inRef && cur.default !== undefined) siblingDefault = cur.default;
252
- active = new Set(active);
253
- active.add(cur.$ref);
254
- cur = target;
255
- entered = true;
256
- }
257
- const strip = inRef || entered;
258
- const out = {};
259
- for (const k of Object.keys(cur)) {
260
- if (strip && k === 'default') continue;
261
- const v = cur[k];
262
- if (_INLINE_DATA_KEYS.has(k) || k === '$defs' || k === 'definitions') { out[k] = v; continue; }
263
- out[k] = walk(v, active, strip);
264
- }
265
- if (siblingDefault !== undefined) out.default = siblingDefault;
266
- return out;
267
- };
268
- return walk(root, new Set(), false);
269
- }
270
-
271
- function emitClone(node, access, depth) {
272
- if (!node || typeof node !== 'object') return null;
273
- if (depth > 12) return null;
274
- // Only plain object nodes are cloneable, and only when the schema names
275
- // every property it keeps. A $ref means the set of allowed keys is not
276
- // this node's to decide. In-place applicators (allOf, anyOf, oneOf,
277
- // if/then/else) are admitted under the same proof the unevaluated* error
278
- // generators use: every property name they contribute must already be
279
- // declared in this node's own `properties`, so they constrain values but
280
- // never widen the key set. `not` never widens it. `unevaluatedProperties`
281
- // is admitted only as `false` under that proof, where it is exactly
282
- // `additionalProperties: false`.
283
- if (node.$ref || node.patternProperties || node.additionalProperties === true) {
284
- return null;
285
- }
286
- // additionalProperties as a schema is the record shape (z.record and every
287
- // generator's map type): the key set is open by declaration, so keeping
288
- // every key is exact, and every undeclared value is rebuilt against that
289
- // one schema. That is a proof, not a guess, so it is admitted. What stays
290
- // out: applicators next to a record (a branch could constrain some keys'
291
- // values beyond the record schema, and the rebuild would ignore it), and
292
- // `additionalProperties: true`, where the value is unconstrained and this
293
- // pass only copies what it can name. An open object with no
294
- // `additionalProperties` at all also stays out, for the original reason:
295
- // validate() accepts its unknown keys, so a parse() that strips them would
296
- // silently drop allowed data, and one that keeps them raw would not be a
297
- // sanitiser.
298
- const apSchema = (node.additionalProperties && typeof node.additionalProperties === 'object')
299
- ? node.additionalProperties
300
- : null;
301
- if (apSchema) {
302
- if (node.unevaluatedProperties !== undefined) return null;
303
- if (node.allOf || node.anyOf || node.oneOf || node.if || node.then || node.else ||
304
- node.dependentSchemas !== undefined || node.dependencies !== undefined ||
305
- node.$dynamicRef !== undefined || node.$recursiveRef !== undefined) return null;
306
- }
307
- if (node.unevaluatedProperties !== undefined && node.unevaluatedProperties !== false) return null;
308
- if (node.type !== 'object') return null;
309
- if (!node.properties && !apSchema) return null;
310
- const keys = node.properties ? Object.keys(node.properties) : [];
311
- if (keys.length === 0 && !apSchema) return null;
312
- if (node.allOf || node.anyOf || node.oneOf || node.if || node.then || node.else ||
313
- node.unevaluatedProperties === false) {
314
- if (node.$dynamicRef !== undefined || node.$recursiveRef !== undefined ||
315
- node.dependentSchemas !== undefined || node.dependencies !== undefined) return null;
316
- const names = new Set();
317
- const state = { prefix: 0 };
318
- for (const k of ['allOf', 'anyOf', 'oneOf']) {
319
- if (node[k] !== undefined) {
320
- if (!Array.isArray(node[k])) return null;
321
- for (const b of node[k]) if (!unevalContributions(b, 'props', names, state, 0)) return null;
322
- }
323
- }
324
- for (const k of ['if', 'then', 'else']) {
325
- if (node[k] !== undefined && !unevalContributions(node[k], 'props', names, state, 0)) return null;
326
- }
327
- for (const n of names) if (!Object.prototype.hasOwnProperty.call(node.properties, n)) return null;
328
- }
329
- const required = new Set(Array.isArray(node.required) ? node.required : []);
330
-
331
- const fixed = [];
332
- const conditional = [];
333
- for (const key of keys) {
334
- const prop = node.properties[key];
335
- if (!prop || typeof prop !== 'object') return null;
336
- if (prop.$ref) return null;
337
- // A property that is required and carries a default is the one place the
338
- // runtime and a raw-input validation disagree: the runtime fills the
339
- // default before checking `required`, so `{}` validates there and would
340
- // throw here. Declining keeps parse() exactly as strict as validate().
341
- if (required.has(key) && prop.default !== undefined) return null;
342
- // A default the property's own schema rejects would make parse() hand
343
- // back a document validate() refuses: the runtime fills defaults before
344
- // validating, so it catches the bad default, and a raw-input check here
345
- // never sees it. Probe the default at emit time and decline when it does
346
- // not hold, or when the subschema cannot be compiled to check it.
347
- if (prop.default !== undefined) {
348
- let probe = null;
349
- try { probe = compileToJSCodegen(prop, null, null); } catch (_) { probe = null; }
350
- if (!probe || !probe(prop.default)) return null;
351
- }
352
- const read = `${access}[${JSON.stringify(key)}]`;
353
- const value = emitValueExpr(prop, read, depth);
354
- if (value === null) return null;
355
- // Computed key syntax: a plain `"__proto__":` in an object literal is
356
- // the prototype slot, not a property; `["__proto__"]:` is a property.
357
- if (required.has(key)) fixed.push(`[${JSON.stringify(key)}]: ${value}`);
358
- else conditional.push({ key, value, dflt: prop.default });
359
- }
360
- if (fixed.length === 0 && conditional.length === 0 && !apSchema) return null;
361
-
362
- const tmp = `_c${depth}`;
363
- // The record loop: every key of the input that is not a declared property
364
- // is kept, its value rebuilt against the additionalProperties schema. The
365
- // input is already validated, so every such value satisfies that schema;
366
- // the rebuild only strips inside it. Defaults under a record value are
367
- // dropped before emission because the runtime's useDefaults does not fill
368
- // them there, and parse() must equal validate().data, not improve on it.
369
- let recordLoop = '';
370
- if (apSchema) {
371
- const kVar = `_k${depth}`;
372
- const vVar = `_v${depth}`;
373
- const valueExpr = emitValueExpr(stripDefaultsDeep(apSchema), vVar, depth);
374
- if (valueExpr === null) return null;
375
- let skip = '';
376
- let declSet = '';
377
- if (keys.length > 8) {
378
- declSet = `const _d${depth} = new Set(${JSON.stringify(keys)}); `;
379
- skip = `if (_d${depth}.has(${kVar})) continue; `;
380
- } else if (keys.length > 0) {
381
- skip = `if (${keys.map((k) => `${kVar} === ${JSON.stringify(k)}`).join(' || ')}) continue; `;
382
- }
383
- // The key comes from the input, so it can be the string "__proto__" (an
384
- // own key only JSON.parse writes); assignment would hit the prototype
385
- // setter, so that one key goes through defineProperty.
386
- recordLoop = ` ${declSet}for (const ${kVar} of Object.keys(${access})) { ${skip}const ${vVar} = ${access}[${kVar}]; if (${kVar} === "__proto__") Object.defineProperty(${tmp}, ${kVar}, { value: ${valueExpr}, writable: true, enumerable: true, configurable: true }); else ${tmp}[${kVar}] = ${valueExpr}; }`;
387
- }
388
-
389
- const literal = `{ ${fixed.join(', ')} }`;
390
- if (conditional.length === 0 && !recordLoop) return literal;
391
- // Optional properties are added only when present, so the result never
392
- // gains a key the input did not have.
393
- const adds = conditional
394
- .map(({ key, value, dflt }) => {
395
- // Object.hasOwn, not `in`: `in` also answers for what the instance
396
- // inherits, so a property named `constructor` would copy the inherited
397
- // function into the output that validate().data does not carry. And a
398
- // key named `__proto__` cannot be written by assignment, which hits
399
- // the prototype setter; defineProperty writes an own key.
400
- const assign = key === '__proto__'
401
- ? `Object.defineProperty(${tmp}, ${JSON.stringify(key)}, { value: ${value}, writable: true, enumerable: true, configurable: true });`
402
- : `${tmp}[${JSON.stringify(key)}] = ${value};`;
403
- const set = `if (Object.hasOwn(${access}, ${JSON.stringify(key)})) ${assign}`;
404
- // The default is emitted as a literal inside the call, so an object or
405
- // array default is a fresh value on every parse, never shared state.
406
- if (dflt === undefined) return set;
407
- const setDflt = key === '__proto__'
408
- ? `Object.defineProperty(${tmp}, ${JSON.stringify(key)}, { value: ${JSON.stringify(dflt)}, writable: true, enumerable: true, configurable: true });`
409
- : `${tmp}[${JSON.stringify(key)}] = ${JSON.stringify(dflt)};`;
410
- return `${set} else ${setDflt}`;
411
- })
412
- .join(' ');
413
- return `(function(){ const ${tmp} = ${literal}; ${adds}${recordLoop} return ${tmp}; })()`;
414
- }
415
-
416
- // The value dispatch shared by declared properties and the record loop. A
417
- // value is copyable only when the emitter can name everything that may live
418
- // under it: a primitive holds nothing, an object with declared properties or
419
- // a record schema is rebuilt the same way, an array of primitives has
420
- // nowhere to hide a key. A $ref, a composition or an untyped node could all
421
- // carry keys this code cannot see, and copying the reference would smuggle
422
- // them into a value that claims to be sanitised, so the caller declines the
423
- // whole clone instead.
424
- // Drop every schema-position `default` in a subtree, leaving data positions
425
- // (const, enum, examples) untouched. Used on a record's value schema, where
426
- // the runtime's useDefaults fills nothing.
427
- function stripDefaultsDeep(node) {
428
- if (!node || typeof node !== 'object') return node;
429
- if (Array.isArray(node)) return node.map(stripDefaultsDeep);
430
- const out = {};
431
- for (const k of Object.keys(node)) {
432
- if (k === 'default') continue;
433
- out[k] = _INLINE_DATA_KEYS.has(k) ? node[k] : stripDefaultsDeep(node[k]);
434
- }
435
- return out;
436
- }
437
-
438
- const _CLONE_PRIMITIVE = new Set(['string', 'number', 'integer', 'boolean', 'null']);
439
- function emitValueExpr(prop, read, depth) {
440
- if (!prop || typeof prop !== 'object') return null;
441
- if (prop.$ref) return null;
442
- const types = Array.isArray(prop.type) ? prop.type : [prop.type];
443
- if (types.every((t) => _CLONE_PRIMITIVE.has(t))) return read;
444
- const isRecordable = (s) => s.properties ||
445
- (s.additionalProperties && typeof s.additionalProperties === 'object');
446
- if (prop.type === 'object' && isRecordable(prop)) {
447
- return emitClone(prop, read, depth + 1);
448
- }
449
- if (prop.type === 'array' && prop.items && typeof prop.items === 'object' &&
450
- !Array.isArray(prop.items)) {
451
- const it = prop.items;
452
- const itemTypes = Array.isArray(it.type) ? it.type : [it.type];
453
- if (itemTypes.every((t) => _CLONE_PRIMITIVE.has(t))) return read;
454
- if (it.type === 'object' && isRecordable(it)) {
455
- const el = '_e' + depth;
456
- const inner = emitClone(it, el, depth + 1);
457
- if (!inner) return null;
458
- return `${read}.map((${el}) => (${inner}))`;
459
- }
460
- }
461
- return null;
462
- }
192
+ const { emitClone, inlineRefsForClone } = require('./clone-emit');
463
193
 
464
194
  function toStandaloneModule(validator, opts) {
465
195
  assertEmittable(validator, 'toStandaloneModule');