@schema-ts/core 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -196,6 +196,16 @@ type SchemaChangeEvent = {
196
196
  type: "schema" | "value" | "error";
197
197
  path: string;
198
198
  };
199
+ interface SchemaRuntimeOptions {
200
+ /**
201
+ * Control how default values are applied when schema changes.
202
+ * - 'always': Fill all type-based defaults (e.g., [] for array, "" for string)
203
+ * - 'explicit': Only fill explicitly declared defaults (schema.default)
204
+ * - 'never': Never auto-fill defaults
205
+ * @default 'explicit'
206
+ */
207
+ autoFillDefaults?: "always" | "explicit" | "never";
208
+ }
199
209
  declare class SchemaRuntime {
200
210
  private validator;
201
211
  private watchers;
@@ -206,19 +216,21 @@ declare class SchemaRuntime {
206
216
  private value;
207
217
  private version;
208
218
  private rootSchema;
219
+ private options;
209
220
  /**
210
221
  * Create a new SchemaRuntime instance.
211
222
  *
212
223
  * @param validator - The validator instance for schema validation
213
224
  * @param schema - The JSON Schema definition (will be normalized and dereferenced)
214
225
  * @param value - The initial data value to manage
226
+ * @param options - Runtime configuration options
215
227
  *
216
228
  * @example
217
229
  * const validator = new Validator();
218
230
  * const schema = { type: "object", properties: { name: { type: "string" } } };
219
231
  * const runtime = new SchemaRuntime(validator, schema, { name: "Alice" });
220
232
  */
221
- constructor(validator: Validator, schema: Schema | unknown, value: unknown);
233
+ constructor(validator: Validator, schema: Schema | unknown, value: unknown, options?: SchemaRuntimeOptions);
222
234
  /**
223
235
  * Register a node as dependent on a path
224
236
  */
@@ -297,6 +309,15 @@ declare class SchemaRuntime {
297
309
  * runtime.getValue("/name"); // returns value at /name
298
310
  */
299
311
  getValue(path: string): unknown;
312
+ /**
313
+ * Internal helper to set a value at a normalized path.
314
+ * Handles both root and non-root paths.
315
+ *
316
+ * @param normalizedPath - The normalized JSON Pointer path
317
+ * @param value - The value to set
318
+ * @returns true if successful, false if the path cannot be set
319
+ */
320
+ private setValueAtPath;
300
321
  /**
301
322
  * Remove a node at the specified path.
302
323
  * This deletes the value from the data structure (array splice or object delete).
@@ -445,13 +466,7 @@ declare function extractReferencedPaths(conditionSchema: Schema, basePath?: stri
445
466
  * Priority order for determining the default value:
446
467
  * 1. const - if defined, returns the const value
447
468
  * 2. default - if defined, returns the default value
448
- * 3. Type-based defaults:
449
- * - string: ""
450
- * - number/integer: 0
451
- * - boolean: false
452
- * - null: null
453
- * - object: {} with required properties recursively initialized
454
- * - array: []
469
+ * 3. Type-based defaults (controlled by strategy):
455
470
  *
456
471
  * For objects, only required properties are initialized with their default values.
457
472
  * If no type is specified but properties exist, the schema is treated as an object.
@@ -460,10 +475,14 @@ declare function extractReferencedPaths(conditionSchema: Schema, basePath?: stri
460
475
  *
461
476
  * @param schema - The JSON Schema to generate a default value for
462
477
  * @param value - Optional existing value to fill defaults into
478
+ * @param strategy - Controls type-based default behavior:
479
+ * - 'always' (default): Returns type-based defaults (e.g., "" for string, [] for array)
480
+ * - 'explicit': Only returns explicitly declared schema.const or schema.default
463
481
  * @returns The generated default value, or undefined if type cannot be determined
464
482
  *
465
483
  * @example
466
484
  * getDefaultValue({ type: "string" }) // returns ""
485
+ * getDefaultValue({ type: "string" }, undefined, 'explicit') // returns undefined
467
486
  * getDefaultValue({ type: "number", default: 42 }) // returns 42
468
487
  * getDefaultValue({ const: "fixed" }) // returns "fixed"
469
488
  * getDefaultValue({
@@ -473,6 +492,6 @@ declare function extractReferencedPaths(conditionSchema: Schema, basePath?: stri
473
492
  * }) // returns { name: "" }
474
493
  * getDefaultValue({ type: "object", properties: { a: { default: 1 } } }, {}) // returns { a: 1 }
475
494
  */
476
- declare function getDefaultValue(schema: Schema, value?: unknown): unknown;
495
+ declare function getDefaultValue(schema: Schema, value?: unknown, strategy?: "always" | "explicit"): unknown;
477
496
 
478
- export { DRAFT_URIS, type ErrorFormatter, type ErrorMessage, type FieldNode, MESSAGES, type NormalizerOptions, type Output, type Schema, type SchemaChangeEvent, type SchemaDraft, SchemaRuntime, type SchemaType, StringFormatValidator, type StringFormatValidatorInterface, Validator, type ValidatorConfig, type ValidatorOptions, collectDependencies, deepEqual, defaultErrorFormatter, detectSchemaDraft, detectSchemaType, extractReferencedPaths, get, getDefaultValue, getJsonPointer, jsonPointerEscape, jsonPointerJoin, jsonPointerUnescape, matchSchemaType, normalizeSchema, parseJsonPointer, removeJsonPointer, resolveAbsolutePath, safeRegexTest, setJsonPointer, stringFormatValidator, validateSchema };
497
+ export { DRAFT_URIS, type ErrorFormatter, type ErrorMessage, type FieldNode, MESSAGES, type NormalizerOptions, type Output, type Schema, type SchemaChangeEvent, type SchemaDraft, SchemaRuntime, type SchemaRuntimeOptions, type SchemaType, StringFormatValidator, type StringFormatValidatorInterface, Validator, type ValidatorConfig, type ValidatorOptions, collectDependencies, deepEqual, defaultErrorFormatter, detectSchemaDraft, detectSchemaType, extractReferencedPaths, get, getDefaultValue, getJsonPointer, jsonPointerEscape, jsonPointerJoin, jsonPointerUnescape, matchSchemaType, normalizeSchema, parseJsonPointer, removeJsonPointer, resolveAbsolutePath, safeRegexTest, setJsonPointer, stringFormatValidator, validateSchema };
package/dist/index.d.ts CHANGED
@@ -196,6 +196,16 @@ type SchemaChangeEvent = {
196
196
  type: "schema" | "value" | "error";
197
197
  path: string;
198
198
  };
199
+ interface SchemaRuntimeOptions {
200
+ /**
201
+ * Control how default values are applied when schema changes.
202
+ * - 'always': Fill all type-based defaults (e.g., [] for array, "" for string)
203
+ * - 'explicit': Only fill explicitly declared defaults (schema.default)
204
+ * - 'never': Never auto-fill defaults
205
+ * @default 'explicit'
206
+ */
207
+ autoFillDefaults?: "always" | "explicit" | "never";
208
+ }
199
209
  declare class SchemaRuntime {
200
210
  private validator;
201
211
  private watchers;
@@ -206,19 +216,21 @@ declare class SchemaRuntime {
206
216
  private value;
207
217
  private version;
208
218
  private rootSchema;
219
+ private options;
209
220
  /**
210
221
  * Create a new SchemaRuntime instance.
211
222
  *
212
223
  * @param validator - The validator instance for schema validation
213
224
  * @param schema - The JSON Schema definition (will be normalized and dereferenced)
214
225
  * @param value - The initial data value to manage
226
+ * @param options - Runtime configuration options
215
227
  *
216
228
  * @example
217
229
  * const validator = new Validator();
218
230
  * const schema = { type: "object", properties: { name: { type: "string" } } };
219
231
  * const runtime = new SchemaRuntime(validator, schema, { name: "Alice" });
220
232
  */
221
- constructor(validator: Validator, schema: Schema | unknown, value: unknown);
233
+ constructor(validator: Validator, schema: Schema | unknown, value: unknown, options?: SchemaRuntimeOptions);
222
234
  /**
223
235
  * Register a node as dependent on a path
224
236
  */
@@ -297,6 +309,15 @@ declare class SchemaRuntime {
297
309
  * runtime.getValue("/name"); // returns value at /name
298
310
  */
299
311
  getValue(path: string): unknown;
312
+ /**
313
+ * Internal helper to set a value at a normalized path.
314
+ * Handles both root and non-root paths.
315
+ *
316
+ * @param normalizedPath - The normalized JSON Pointer path
317
+ * @param value - The value to set
318
+ * @returns true if successful, false if the path cannot be set
319
+ */
320
+ private setValueAtPath;
300
321
  /**
301
322
  * Remove a node at the specified path.
302
323
  * This deletes the value from the data structure (array splice or object delete).
@@ -445,13 +466,7 @@ declare function extractReferencedPaths(conditionSchema: Schema, basePath?: stri
445
466
  * Priority order for determining the default value:
446
467
  * 1. const - if defined, returns the const value
447
468
  * 2. default - if defined, returns the default value
448
- * 3. Type-based defaults:
449
- * - string: ""
450
- * - number/integer: 0
451
- * - boolean: false
452
- * - null: null
453
- * - object: {} with required properties recursively initialized
454
- * - array: []
469
+ * 3. Type-based defaults (controlled by strategy):
455
470
  *
456
471
  * For objects, only required properties are initialized with their default values.
457
472
  * If no type is specified but properties exist, the schema is treated as an object.
@@ -460,10 +475,14 @@ declare function extractReferencedPaths(conditionSchema: Schema, basePath?: stri
460
475
  *
461
476
  * @param schema - The JSON Schema to generate a default value for
462
477
  * @param value - Optional existing value to fill defaults into
478
+ * @param strategy - Controls type-based default behavior:
479
+ * - 'always' (default): Returns type-based defaults (e.g., "" for string, [] for array)
480
+ * - 'explicit': Only returns explicitly declared schema.const or schema.default
463
481
  * @returns The generated default value, or undefined if type cannot be determined
464
482
  *
465
483
  * @example
466
484
  * getDefaultValue({ type: "string" }) // returns ""
485
+ * getDefaultValue({ type: "string" }, undefined, 'explicit') // returns undefined
467
486
  * getDefaultValue({ type: "number", default: 42 }) // returns 42
468
487
  * getDefaultValue({ const: "fixed" }) // returns "fixed"
469
488
  * getDefaultValue({
@@ -473,6 +492,6 @@ declare function extractReferencedPaths(conditionSchema: Schema, basePath?: stri
473
492
  * }) // returns { name: "" }
474
493
  * getDefaultValue({ type: "object", properties: { a: { default: 1 } } }, {}) // returns { a: 1 }
475
494
  */
476
- declare function getDefaultValue(schema: Schema, value?: unknown): unknown;
495
+ declare function getDefaultValue(schema: Schema, value?: unknown, strategy?: "always" | "explicit"): unknown;
477
496
 
478
- export { DRAFT_URIS, type ErrorFormatter, type ErrorMessage, type FieldNode, MESSAGES, type NormalizerOptions, type Output, type Schema, type SchemaChangeEvent, type SchemaDraft, SchemaRuntime, type SchemaType, StringFormatValidator, type StringFormatValidatorInterface, Validator, type ValidatorConfig, type ValidatorOptions, collectDependencies, deepEqual, defaultErrorFormatter, detectSchemaDraft, detectSchemaType, extractReferencedPaths, get, getDefaultValue, getJsonPointer, jsonPointerEscape, jsonPointerJoin, jsonPointerUnescape, matchSchemaType, normalizeSchema, parseJsonPointer, removeJsonPointer, resolveAbsolutePath, safeRegexTest, setJsonPointer, stringFormatValidator, validateSchema };
497
+ export { DRAFT_URIS, type ErrorFormatter, type ErrorMessage, type FieldNode, MESSAGES, type NormalizerOptions, type Output, type Schema, type SchemaChangeEvent, type SchemaDraft, SchemaRuntime, type SchemaRuntimeOptions, type SchemaType, StringFormatValidator, type StringFormatValidatorInterface, Validator, type ValidatorConfig, type ValidatorOptions, collectDependencies, deepEqual, defaultErrorFormatter, detectSchemaDraft, detectSchemaType, extractReferencedPaths, get, getDefaultValue, getJsonPointer, jsonPointerEscape, jsonPointerJoin, jsonPointerUnescape, matchSchemaType, normalizeSchema, parseJsonPointer, removeJsonPointer, resolveAbsolutePath, safeRegexTest, setJsonPointer, stringFormatValidator, validateSchema };
package/dist/index.js CHANGED
@@ -1398,7 +1398,6 @@ function resolveEffectiveSchema(validator, schema, value, keywordLocation, insta
1398
1398
  instanceLocation
1399
1399
  );
1400
1400
  effective = mergeSchema(effective, res.effectiveSchema);
1401
- break;
1402
1401
  }
1403
1402
  }
1404
1403
  const { anyOf: _, ...rest } = effective;
@@ -1470,6 +1469,19 @@ function mergeSchemaArrays(a, b) {
1470
1469
  if (b === void 0) return a;
1471
1470
  return [...a, ...b];
1472
1471
  }
1472
+ function mergeSchemaMap(base, override) {
1473
+ if (base === void 0) return override;
1474
+ if (override === void 0) return base;
1475
+ const merged = { ...base };
1476
+ for (const [key, schema] of Object.entries(override)) {
1477
+ if (merged[key]) {
1478
+ merged[key] = mergeSchema(merged[key], schema);
1479
+ } else {
1480
+ merged[key] = schema;
1481
+ }
1482
+ }
1483
+ return merged;
1484
+ }
1473
1485
  function mergeSchema(base, override) {
1474
1486
  if (!override) return base;
1475
1487
  const merged = {
@@ -1496,17 +1508,16 @@ function mergeSchema(base, override) {
1496
1508
  ...override.dependentRequired
1497
1509
  };
1498
1510
  }
1499
- if (base.properties || override.properties) {
1500
- merged.properties = {
1501
- ...base.properties,
1502
- ...override.properties
1503
- };
1511
+ const mergedProperties = mergeSchemaMap(base.properties, override.properties);
1512
+ if (mergedProperties !== void 0) {
1513
+ merged.properties = mergedProperties;
1504
1514
  }
1505
- if (base.patternProperties || override.patternProperties) {
1506
- merged.patternProperties = {
1507
- ...base.patternProperties,
1508
- ...override.patternProperties
1509
- };
1515
+ const mergedPatternProperties = mergeSchemaMap(
1516
+ base.patternProperties,
1517
+ override.patternProperties
1518
+ );
1519
+ if (mergedPatternProperties !== void 0) {
1520
+ merged.patternProperties = mergedPatternProperties;
1510
1521
  }
1511
1522
  if (base.items && override.items) {
1512
1523
  merged.items = mergeSchema(base.items, override.items);
@@ -1541,11 +1552,12 @@ function mergeSchema(base, override) {
1541
1552
  merged[keyword] = mergedArray;
1542
1553
  }
1543
1554
  }
1544
- if (base.dependentSchemas || override.dependentSchemas) {
1545
- merged.dependentSchemas = {
1546
- ...base.dependentSchemas,
1547
- ...override.dependentSchemas
1548
- };
1555
+ const mergedDependentSchemas = mergeSchemaMap(
1556
+ base.dependentSchemas,
1557
+ override.dependentSchemas
1558
+ );
1559
+ if (mergedDependentSchemas !== void 0) {
1560
+ merged.dependentSchemas = mergedDependentSchemas;
1549
1561
  }
1550
1562
  return merged;
1551
1563
  }
@@ -1766,10 +1778,11 @@ function getSubSchema(schema, key) {
1766
1778
  }
1767
1779
 
1768
1780
  // src/default.ts
1769
- function getDefaultValue(schema, value) {
1781
+ function getDefaultValue(schema, value, strategy = "explicit") {
1770
1782
  if (value === void 0) {
1771
1783
  if (schema.const !== void 0) return schema.const;
1772
1784
  if (schema.default !== void 0) return schema.default;
1785
+ if (strategy === "explicit") return void 0;
1773
1786
  }
1774
1787
  const type = Array.isArray(schema.type) ? schema.type[0] : schema.type;
1775
1788
  if (type === "object" || !type && schema.properties) {
@@ -1784,9 +1797,9 @@ function getDefaultValue(schema, value) {
1784
1797
  if (schema.properties) {
1785
1798
  for (const [key, subschema] of Object.entries(schema.properties)) {
1786
1799
  if (obj[key] !== void 0) {
1787
- obj[key] = getDefaultValue(subschema, obj[key]);
1800
+ obj[key] = getDefaultValue(subschema, obj[key], strategy);
1788
1801
  } else if (schema.required?.includes(key)) {
1789
- obj[key] = getDefaultValue(subschema);
1802
+ obj[key] = getDefaultValue(subschema, void 0, strategy);
1790
1803
  }
1791
1804
  }
1792
1805
  }
@@ -1804,16 +1817,16 @@ function getDefaultValue(schema, value) {
1804
1817
  if (schema.prefixItems) {
1805
1818
  schema.prefixItems.forEach((subschema, index) => {
1806
1819
  if (index < arr.length) {
1807
- arr[index] = getDefaultValue(subschema, arr[index]);
1820
+ arr[index] = getDefaultValue(subschema, arr[index], strategy);
1808
1821
  } else if (value === void 0) {
1809
- arr.push(getDefaultValue(subschema));
1822
+ arr.push(getDefaultValue(subschema, void 0, strategy));
1810
1823
  }
1811
1824
  });
1812
1825
  }
1813
1826
  if (value !== void 0 && schema.items) {
1814
1827
  const startIndex = schema.prefixItems ? schema.prefixItems.length : 0;
1815
1828
  for (let i = startIndex; i < arr.length; i++) {
1816
- arr[i] = getDefaultValue(schema.items, arr[i]);
1829
+ arr[i] = getDefaultValue(schema.items, arr[i], strategy);
1817
1830
  }
1818
1831
  }
1819
1832
  return arr;
@@ -2027,21 +2040,24 @@ var SchemaRuntime = class {
2027
2040
  value;
2028
2041
  version = 0;
2029
2042
  rootSchema = {};
2043
+ options;
2030
2044
  /**
2031
2045
  * Create a new SchemaRuntime instance.
2032
2046
  *
2033
2047
  * @param validator - The validator instance for schema validation
2034
2048
  * @param schema - The JSON Schema definition (will be normalized and dereferenced)
2035
2049
  * @param value - The initial data value to manage
2050
+ * @param options - Runtime configuration options
2036
2051
  *
2037
2052
  * @example
2038
2053
  * const validator = new Validator();
2039
2054
  * const schema = { type: "object", properties: { name: { type: "string" } } };
2040
2055
  * const runtime = new SchemaRuntime(validator, schema, { name: "Alice" });
2041
2056
  */
2042
- constructor(validator, schema, value) {
2057
+ constructor(validator, schema, value, options = {}) {
2043
2058
  this.validator = validator;
2044
2059
  this.value = value;
2060
+ this.options = { autoFillDefaults: "explicit", ...options };
2045
2061
  const normalized = normalizeSchema(schema);
2046
2062
  this.rootSchema = dereferenceSchemaDeep(normalized, normalized);
2047
2063
  this.root = this.createEmptyNode("", "#");
@@ -2223,6 +2239,21 @@ var SchemaRuntime = class {
2223
2239
  if (normalizedPath === ROOT_PATH) return this.value;
2224
2240
  return getJsonPointer(this.value, normalizedPath);
2225
2241
  }
2242
+ /**
2243
+ * Internal helper to set a value at a normalized path.
2244
+ * Handles both root and non-root paths.
2245
+ *
2246
+ * @param normalizedPath - The normalized JSON Pointer path
2247
+ * @param value - The value to set
2248
+ * @returns true if successful, false if the path cannot be set
2249
+ */
2250
+ setValueAtPath(normalizedPath, value) {
2251
+ if (normalizedPath === ROOT_PATH) {
2252
+ this.value = value;
2253
+ return true;
2254
+ }
2255
+ return setJsonPointer(this.value, normalizedPath, value);
2256
+ }
2226
2257
  /**
2227
2258
  * Remove a node at the specified path.
2228
2259
  * This deletes the value from the data structure (array splice or object delete).
@@ -2263,8 +2294,23 @@ var SchemaRuntime = class {
2263
2294
  if (!parentNode || !parentNode.canAdd) {
2264
2295
  return false;
2265
2296
  }
2266
- const parentValue = this.getValue(normalizedPath);
2297
+ let parentValue = this.getValue(normalizedPath);
2267
2298
  const parentSchema = parentNode.schema;
2299
+ if (parentNode.type === "array") {
2300
+ if (Array.isArray(parentValue)) ; else if (parentValue === void 0 || parentValue === null) {
2301
+ parentValue = [];
2302
+ this.setValueAtPath(normalizedPath, parentValue);
2303
+ } else {
2304
+ return false;
2305
+ }
2306
+ } else if (parentNode.type === "object") {
2307
+ if (parentValue && typeof parentValue === "object") ; else if (parentValue === void 0 || parentValue === null) {
2308
+ parentValue = {};
2309
+ this.setValueAtPath(normalizedPath, parentValue);
2310
+ } else {
2311
+ return false;
2312
+ }
2313
+ }
2268
2314
  if (parentNode.type === "array" && Array.isArray(parentValue)) {
2269
2315
  const newIndex = parentValue.length;
2270
2316
  const { schema: subschema, keywordLocationToken } = getSubSchema(
@@ -2281,7 +2327,7 @@ var SchemaRuntime = class {
2281
2327
  this.reconcile(normalizedPath);
2282
2328
  this.notify({ type: "value", path: normalizedPath });
2283
2329
  return true;
2284
- } else if (parentNode.type === "object" && parentValue && typeof parentValue === "object") {
2330
+ } else if (parentNode.type === "object" && typeof parentValue === "object") {
2285
2331
  if (!key) {
2286
2332
  return false;
2287
2333
  }
@@ -2317,12 +2363,8 @@ var SchemaRuntime = class {
2317
2363
  */
2318
2364
  setValue(path, value) {
2319
2365
  const normalizedPath = normalizeRootPath(path);
2320
- if (normalizedPath === ROOT_PATH) {
2321
- this.value = value;
2322
- } else {
2323
- const success = setJsonPointer(this.value, normalizedPath, value);
2324
- if (!success) return false;
2325
- }
2366
+ const success = this.setValueAtPath(normalizedPath, value);
2367
+ if (!success) return false;
2326
2368
  this.reconcile(normalizedPath);
2327
2369
  this.notify({ type: "value", path: normalizedPath });
2328
2370
  return true;
@@ -2391,6 +2433,9 @@ var SchemaRuntime = class {
2391
2433
  * @param type - The schema type
2392
2434
  */
2393
2435
  applySchemaDefaults(instanceLocation, newSchema, type) {
2436
+ if (this.options.autoFillDefaults === "never") {
2437
+ return;
2438
+ }
2394
2439
  const value = this.getValue(instanceLocation);
2395
2440
  if (type === "object" && newSchema.properties) {
2396
2441
  const obj = value && typeof value === "object" ? value : null;
@@ -2398,7 +2443,11 @@ var SchemaRuntime = class {
2398
2443
  for (const [key, subschema] of Object.entries(newSchema.properties)) {
2399
2444
  const hasValue = obj[key] !== void 0;
2400
2445
  if (!hasValue) {
2401
- const defaultValue = getDefaultValue(subschema);
2446
+ const defaultValue = getDefaultValue(
2447
+ subschema,
2448
+ void 0,
2449
+ this.options.autoFillDefaults
2450
+ );
2402
2451
  if (defaultValue !== void 0) {
2403
2452
  const propertyPath = jsonPointerJoin(instanceLocation, key);
2404
2453
  setJsonPointer(this.value, propertyPath, defaultValue);
@@ -2412,7 +2461,12 @@ var SchemaRuntime = class {
2412
2461
  if (!arr) return;
2413
2462
  for (let i = 0; i < newSchema.prefixItems.length; i++) {
2414
2463
  if (arr[i] === void 0) {
2415
- const defaultValue = getDefaultValue(newSchema.prefixItems[i]);
2464
+ const itemSchema = newSchema.prefixItems[i];
2465
+ const defaultValue = getDefaultValue(
2466
+ itemSchema,
2467
+ void 0,
2468
+ this.options.autoFillDefaults
2469
+ );
2416
2470
  if (defaultValue !== void 0) {
2417
2471
  const itemPath = jsonPointerJoin(instanceLocation, String(i));
2418
2472
  setJsonPointer(this.value, itemPath, defaultValue);