@schema-ts/core 0.1.3 → 0.1.5

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.ts CHANGED
@@ -88,7 +88,42 @@ declare function detectSchemaType(value: unknown): string;
88
88
  declare function parseJsonPointer(jsonPointer: string): string[];
89
89
  declare function getJsonPointer(obj: unknown, jsonPointer: string): unknown;
90
90
  declare function removeJsonPointer(obj: unknown, jsonPointer: string): boolean;
91
+ /**
92
+ * Sets a value at the specified JSON Pointer path within an object.
93
+ *
94
+ * This function modifies the object in place by setting a value at the location
95
+ * specified by the JSON Pointer. If intermediate paths don't exist, they will be
96
+ * automatically created as objects or arrays based on the next path segment
97
+ * (numeric segments create arrays, non-numeric create objects).
98
+ *
99
+ * @param obj - The object to modify. Must be a non-null object or array.
100
+ * @param jsonPointer - A JSON Pointer string (RFC 6901) indicating where to set the value.
101
+ * Examples: "/foo", "/foo/0", "/foo/bar/baz"
102
+ * @param value - The value to set at the specified location.
103
+ * @returns `true` if the value was successfully set, `false` otherwise.
104
+ *
105
+ * @remarks
106
+ * - Returns `false` if `jsonPointer` is an empty string (cannot replace root object).
107
+ * To replace the entire object, handle this case in the calling code.
108
+ * - Returns `false` if `obj` is null or undefined.
109
+ * - For array paths, automatically extends the array if the index is beyond current length.
110
+ * - Automatically initializes missing intermediate containers as arrays or objects.
111
+ *
112
+ * @example
113
+ * ```typescript
114
+ * const obj = { foo: { bar: 1 } };
115
+ * setJsonPointer(obj, "/foo/bar", 2); // true, obj.foo.bar is now 2
116
+ * setJsonPointer(obj, "/foo/baz", 3); // true, obj.foo.baz is now 3
117
+ * setJsonPointer(obj, "/new/path", 4); // true, obj.new.path is now 4
118
+ * setJsonPointer(obj, "", 5); // false, cannot replace root object
119
+ *
120
+ * const arr = [1, 2, 3];
121
+ * setJsonPointer(arr, "/0", 10); // true, arr[0] is now 10
122
+ * setJsonPointer(arr, "/5", 20); // true, arr is now [10, 2, 3, undefined, undefined, 20]
123
+ * ```
124
+ */
91
125
  declare function setJsonPointer(obj: unknown, jsonPointer: string, value: unknown): boolean;
126
+ declare function getJsonPointerParent(path: string): string;
92
127
  declare function get(obj: unknown, path: string[]): unknown;
93
128
  /**
94
129
  * Deep equality comparison for two values.
@@ -175,6 +210,103 @@ declare class Validator {
175
210
  */
176
211
  declare function validateSchema(schema: Schema | unknown, value: unknown, instancePath?: string, schemaPath?: string, fastFail?: boolean): Output;
177
212
 
213
+ /**
214
+ * JSON Schema draft version definitions and detection utilities.
215
+ */
216
+ /**
217
+ * Supported JSON Schema draft versions.
218
+ */
219
+ type SchemaDraft = "draft-04" | "draft-07" | "draft-2019-09" | "draft-2020-12";
220
+ /**
221
+ * Standard $schema URI patterns for each draft version.
222
+ */
223
+ declare const DRAFT_URIS: Record<SchemaDraft, string[]>;
224
+ /**
225
+ * Detect the JSON Schema draft version from a schema.
226
+ * Detection is based on:
227
+ * 1. The $schema URI if present
228
+ * 2. Heuristics based on keywords used
229
+ *
230
+ * @param schema - The schema to detect the version of (can be object, boolean, or unknown)
231
+ * @returns The detected draft version, defaults to "draft-2020-12" if unknown
232
+ */
233
+ declare function detectSchemaDraft(schema: unknown): SchemaDraft;
234
+
235
+ /**
236
+ * Schema normalizer that transforms older JSON Schema drafts to draft-2020-12.
237
+ *
238
+ * This allows the validator to only implement draft-2020-12 semantics
239
+ * while still supporting schemas written for older drafts.
240
+ */
241
+
242
+ /**
243
+ * Schema normalizer interface for transforming various JSON Schema formats into a unified version.
244
+ */
245
+ interface Normalizer {
246
+ /**
247
+ * Normalizes the given schema.
248
+ * @param schema The raw schema input
249
+ * @returns A schema object following the latest draft specification
250
+ */
251
+ normalize(schema: unknown): Schema;
252
+ }
253
+ /**
254
+ * Base draft normalizer responsible for converting older JSON Schema keywords to newer versions (e.g., Draft-04 to Draft-2020-12).
255
+ */
256
+ declare class DraftNormalizer implements Normalizer {
257
+ normalize(schema: unknown): Schema;
258
+ }
259
+ interface DraftNormalizerOptions {
260
+ /**
261
+ * Source draft version. If not specified, will be auto-detected.
262
+ */
263
+ sourceDraft?: SchemaDraft;
264
+ }
265
+ /**
266
+ * Normalize a JSON Schema from any supported draft to draft-2020-12 format.
267
+ *
268
+ * Transformations performed:
269
+ * - `id` → `$id` (draft-04)
270
+ * - Boolean `exclusiveMaximum`/`exclusiveMinimum` → numeric values (draft-04)
271
+ * - Array `items` + `additionalItems` → `prefixItems` + `items` (draft-04/07/2019-09)
272
+ * - `dependencies` → `dependentRequired`/`dependentSchemas` (draft-04/07)
273
+ * - `$recursiveRef`/`$recursiveAnchor` → `$dynamicRef`/`$dynamicAnchor` (draft-2019-09)
274
+ *
275
+ * @param schema - The schema to normalize (can be object, boolean, or unknown)
276
+ * @param options - Optional configuration
277
+ * @returns A new schema object in draft-2020-12 format (always returns an object)
278
+ */
279
+ declare function normalizeSchema(schema: unknown, options?: DraftNormalizerOptions): Schema;
280
+ /**
281
+ * Enhanced normalizer that adds type inference, automatic required field identification,
282
+ * and support for OpenAPI/Kubernetes extension properties on top of base draft transformations.
283
+ */
284
+ declare class BetterNormalizer implements Normalizer {
285
+ /**
286
+ * Performs the normalization process:
287
+ * 1. Base draft normalization (DraftNormalizer)
288
+ * 2. Enhanced default value completion (Better logic)
289
+ */
290
+ normalize(schema: unknown): Schema;
291
+ /**
292
+ * Recursively processes all sub-nodes of the schema, applying default rules.
293
+ */
294
+ private default;
295
+ /**
296
+ * Batch processes Record-type sub-schemas.
297
+ */
298
+ private defaultSchemaMap;
299
+ /**
300
+ * Applies inference and heuristic rules to a single schema object.
301
+ * Includes:
302
+ * - `type` inference based on properties (object/array)
303
+ * - Automatically setting properties with `const`/`default`/`enum` as `required`
304
+ * - Handling `required` fields from OpenAPI `discriminator`
305
+ * - Handling `x-required` and `x-kubernetes-patch-merge-key`
306
+ */
307
+ private defaultSchema;
308
+ }
309
+
178
310
  /**
179
311
  * Represents a node in the schema tree.
180
312
  * Each node corresponds to a location in both the schema and the instance data.
@@ -187,10 +319,13 @@ interface FieldNode {
187
319
  children?: FieldNode[];
188
320
  instanceLocation: string;
189
321
  keywordLocation: string;
322
+ originKeywordLocation?: string;
190
323
  version: number;
191
324
  dependencies?: Set<string>;
192
325
  canRemove: boolean;
193
326
  canAdd: boolean;
327
+ required: boolean;
328
+ activated: boolean;
194
329
  }
195
330
  type SchemaChangeEvent = {
196
331
  type: "schema" | "value" | "error";
@@ -200,14 +335,26 @@ interface SchemaRuntimeOptions {
200
335
  /**
201
336
  * Control how default values are applied when schema changes.
202
337
  * - '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'
338
+ * - 'auto': Fill defaults by defaulting rules (required fields, const, default keyword etc.)
339
+ * @default 'auto'
340
+ */
341
+ fillDefaults?: "auto" | "never";
342
+ /**
343
+ * Control behavior when removing the last element from an array or object.
344
+ * - 'never': Always keep empty containers ([] or {})
345
+ * - 'auto': Remove empty container only following rules.
346
+ * @default 'auto'
347
+ */
348
+ removeEmptyContainers?: "never" | "auto";
349
+ /**
350
+ * Custom schema normalizer.
351
+ * If specified, will use this normalizer instead of the default one.
206
352
  */
207
- autoFillDefaults?: "always" | "explicit" | "never";
353
+ schemaNormalizer?: Normalizer;
208
354
  }
209
355
  declare class SchemaRuntime {
210
356
  private validator;
357
+ private normalizer;
211
358
  private watchers;
212
359
  private globalWatchers;
213
360
  private dependentsMap;
@@ -231,6 +378,12 @@ declare class SchemaRuntime {
231
378
  * const runtime = new SchemaRuntime(validator, schema, { name: "Alice" });
232
379
  */
233
380
  constructor(validator: Validator, schema: Schema | unknown, value: unknown, options?: SchemaRuntimeOptions);
381
+ private resolveSchema;
382
+ /**
383
+ * Update the entire schema.
384
+ * This triggers a full rebuild of the node tree while preserving the current value.
385
+ */
386
+ setSchema(schema: Schema | unknown): void;
234
387
  /**
235
388
  * Register a node as dependent on a path
236
389
  */
@@ -293,11 +446,6 @@ declare class SchemaRuntime {
293
446
  * @param event - The change event containing type and path
294
447
  */
295
448
  notify(event: SchemaChangeEvent): void;
296
- /**
297
- * Update the entire schema.
298
- * This triggers a full rebuild of the node tree while preserving the current value.
299
- */
300
- setSchema(schema: Schema | unknown): void;
301
449
  /**
302
450
  * Get the value at a specific path.
303
451
  *
@@ -313,18 +461,42 @@ declare class SchemaRuntime {
313
461
  * Internal helper to set a value at a normalized path.
314
462
  * Handles both root and non-root paths.
315
463
  *
316
- * @param normalizedPath - The normalized JSON Pointer path
464
+ * @param path - The JSON Pointer path
317
465
  * @param value - The value to set
318
466
  * @returns true if successful, false if the path cannot be set
319
467
  */
320
- private setValueAtPath;
468
+ private setJsonPointer;
469
+ /**
470
+ * Internal method to remove a value at a path.
471
+ * Shared logic for removeValue and setValue(undefined).
472
+ * @param path - The normalized path to remove
473
+ * @param canRemove - Whether the removal is allowed (pre-checked by caller)
474
+ * @returns true if successful, false if removal failed
475
+ */
476
+ private removeValueInternal;
321
477
  /**
322
478
  * Remove a node at the specified path.
323
479
  * This deletes the value from the data structure (array splice or object delete).
480
+ * After removal, may also remove empty parent containers based on removeEmptyContainers option.
324
481
  * @param path - The path to remove
325
482
  * @returns true if successful, false if the path cannot be removed
326
483
  */
327
484
  removeValue(path: string): boolean;
485
+ /**
486
+ * Clean up empty parent containers after element removal.
487
+ * Recursively removes empty arrays/objects based on removeEmptyContainers option.
488
+ * @param path - The path to check for empty container
489
+ * @returns The topmost parent path changed
490
+ */
491
+ private cleanupEmptyContainers;
492
+ /**
493
+ * Get default value for a schema, respecting autoFillDefaults option.
494
+ * Falls back to 'always' strategy if configured strategy returns undefined.
495
+ * If still undefined (e.g., schema has no type), falls back to null as a valid JSON value.
496
+ */
497
+ private newDefaultValue;
498
+ private addObjectProperty;
499
+ private addArrayItem;
328
500
  /**
329
501
  * Add a new child to an array or object at the specified parent path.
330
502
  * For arrays, appends a new item with default value based on items schema.
@@ -334,34 +506,39 @@ declare class SchemaRuntime {
334
506
  * @param initialValue - Optional initial value to set. If not provided, uses default from schema.
335
507
  * @returns true if successful, false if cannot add
336
508
  */
337
- addValue(parentPath: string, key?: string, initialValue?: unknown): boolean;
509
+ addChild(parentPath: string, key?: string, initialValue?: unknown): boolean;
338
510
  /**
339
511
  * Set the value at a specific path.
340
512
  * Creates intermediate containers (objects/arrays) as needed.
341
513
  * Triggers reconciliation and notifies subscribers.
342
514
  *
515
+ * When value is undefined and the field is not required, the field will be
516
+ * removed from the parent container (similar to removeValue behavior).
517
+ *
343
518
  * @param path - The JSON Pointer path (e.g., "/user/name", "" for root)
344
- * @param value - The new value to set
519
+ * @param value - The new value to set. If undefined and field is optional, removes the field.
345
520
  * @returns true if successful, false if the path cannot be set
346
521
  *
347
522
  * @example
348
523
  * runtime.setValue("/name", "Bob"); // set name to "Bob"
349
524
  * runtime.setValue("", { name: "Alice" }); // replace entire root value
525
+ * runtime.setValue("/optional", undefined); // remove optional field
350
526
  */
351
527
  setValue(path: string, value: unknown): boolean;
352
528
  /**
353
- * Find the FieldNode at a specific path.
529
+ * Get the FieldNode at a specific path.
354
530
  * Returns the node tree representation that includes schema, type, error, and children.
355
531
  *
356
532
  * @param path - The JSON Pointer path (e.g., "/user/name", "" for root)
357
533
  * @returns The FieldNode at the path, or undefined if not found
358
534
  *
359
535
  * @example
360
- * const node = runtime.findNode("/name");
536
+ * const node = runtime.getNode("/name");
361
537
  * console.log(node?.schema, node?.type, node?.error);
362
538
  */
363
- findNode(path: string): FieldNode | undefined;
539
+ getNode(path: string): FieldNode | undefined;
364
540
  private findNearestExistingNode;
541
+ validate(path: string): void;
365
542
  /**
366
543
  * Update node dependencies when schema changes.
367
544
  * Unregisters old dependencies and registers new ones.
@@ -372,11 +549,22 @@ declare class SchemaRuntime {
372
549
  * This handles cases like if-then-else where new properties with defaults
373
550
  * may appear when conditions change.
374
551
  *
552
+ * Container initialization rules:
553
+ * - Required containers will be initialized if it has defaults or required properties
554
+ * - Nested containers are initialized only if they are in parent's required array
555
+ *
375
556
  * @param instanceLocation - The path to the node
376
557
  * @param newSchema - The new effective schema
377
558
  * @param type - The schema type
559
+ * @param required - Whether this node is required by its parent
378
560
  */
379
561
  private applySchemaDefaults;
562
+ /**
563
+ * Sort property entries by x-order.
564
+ * Properties with lower x-order values come first.
565
+ * Properties without x-order are placed at the end.
566
+ */
567
+ private sortPropertiesByOrder;
380
568
  /**
381
569
  * Build children for object and array nodes.
382
570
  * Reuses existing child nodes where possible.
@@ -386,61 +574,11 @@ declare class SchemaRuntime {
386
574
  * Build/update a FieldNode in place.
387
575
  * Updates the node's schema, type, error, and children based on the current value.
388
576
  * @param schema - Optional. If provided, updates node.originalSchema. Otherwise uses existing.
577
+ * @param isRequired - Whether this node is required by its parent schema.
389
578
  */
390
579
  private buildNode;
391
580
  }
392
581
 
393
- /**
394
- * JSON Schema draft version definitions and detection utilities.
395
- */
396
- /**
397
- * Supported JSON Schema draft versions.
398
- */
399
- type SchemaDraft = "draft-04" | "draft-07" | "draft-2019-09" | "draft-2020-12";
400
- /**
401
- * Standard $schema URI patterns for each draft version.
402
- */
403
- declare const DRAFT_URIS: Record<SchemaDraft, string[]>;
404
- /**
405
- * Detect the JSON Schema draft version from a schema.
406
- * Detection is based on:
407
- * 1. The $schema URI if present
408
- * 2. Heuristics based on keywords used
409
- *
410
- * @param schema - The schema to detect the version of (can be object, boolean, or unknown)
411
- * @returns The detected draft version, defaults to "draft-2020-12" if unknown
412
- */
413
- declare function detectSchemaDraft(schema: unknown): SchemaDraft;
414
-
415
- /**
416
- * Schema normalizer that transforms older JSON Schema drafts to draft-2020-12.
417
- *
418
- * This allows the validator to only implement draft-2020-12 semantics
419
- * while still supporting schemas written for older drafts.
420
- */
421
-
422
- interface NormalizerOptions {
423
- /**
424
- * Source draft version. If not specified, will be auto-detected.
425
- */
426
- sourceDraft?: SchemaDraft;
427
- }
428
- /**
429
- * Normalize a JSON Schema from any supported draft to draft-2020-12 format.
430
- *
431
- * Transformations performed:
432
- * - `id` → `$id` (draft-04)
433
- * - Boolean `exclusiveMaximum`/`exclusiveMinimum` → numeric values (draft-04)
434
- * - Array `items` + `additionalItems` → `prefixItems` + `items` (draft-04/07/2019-09)
435
- * - `dependencies` → `dependentRequired`/`dependentSchemas` (draft-04/07)
436
- * - `$recursiveRef`/`$recursiveAnchor` → `$dynamicRef`/`$dynamicAnchor` (draft-2019-09)
437
- *
438
- * @param schema - The schema to normalize (can be object, boolean, or unknown)
439
- * @param options - Optional configuration
440
- * @returns A new schema object in draft-2020-12 format (always returns an object)
441
- */
442
- declare function normalizeSchema(schema: unknown, options?: NormalizerOptions): Schema;
443
-
444
582
  /**
445
583
  * Collect all dependencies for a node's schema.
446
584
  *
@@ -460,6 +598,17 @@ declare function collectDependencies(schema: Schema, instanceLocation: string):
460
598
  */
461
599
  declare function extractReferencedPaths(conditionSchema: Schema, basePath?: string, depth?: number): string[];
462
600
 
601
+ interface GetDefaultValueOptions {
602
+ /** Optional existing value to fill defaults into */
603
+ value?: unknown;
604
+ /**
605
+ * Controls type-based default behavior:
606
+ * - 'always': Returns type-based defaults (e.g., "" for string, [] for array)
607
+ * - 'explicit' (default): Only returns explicitly declared schema.const or schema.default
608
+ */
609
+ strategy?: "always" | "explicit";
610
+ }
611
+ declare function typeNullable(schema: Schema): [string | undefined, boolean];
463
612
  /**
464
613
  * Generate a default value for a schema based on its type and constraints.
465
614
  *
@@ -474,24 +623,10 @@ declare function extractReferencedPaths(conditionSchema: Schema, basePath?: stri
474
623
  * If a value is provided, it will be used as the base and missing defaults will be filled in.
475
624
  *
476
625
  * @param schema - The JSON Schema to generate a default value for
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
626
+ * @param strategy - Controls type-based default behavior
481
627
  * @returns The generated default value, or undefined if type cannot be determined
482
- *
483
- * @example
484
- * getDefaultValue({ type: "string" }) // returns ""
485
- * getDefaultValue({ type: "string" }, undefined, 'explicit') // returns undefined
486
- * getDefaultValue({ type: "number", default: 42 }) // returns 42
487
- * getDefaultValue({ const: "fixed" }) // returns "fixed"
488
- * getDefaultValue({
489
- * type: "object",
490
- * properties: { name: { type: "string" } },
491
- * required: ["name"]
492
- * }) // returns { name: "" }
493
- * getDefaultValue({ type: "object", properties: { a: { default: 1 } } }, {}) // returns { a: 1 }
494
628
  */
495
- declare function getDefaultValue(schema: Schema, value?: unknown, strategy?: "always" | "explicit"): unknown;
629
+ declare function getDefaultValue(schema: Schema, required?: boolean): unknown;
630
+ declare function applyDefaults(type: string, value: unknown, schema: Schema, required?: boolean): [unknown, boolean];
496
631
 
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 };
632
+ export { BetterNormalizer, DRAFT_URIS, DraftNormalizer, type DraftNormalizerOptions, type ErrorFormatter, type ErrorMessage, type FieldNode, type GetDefaultValueOptions, MESSAGES, type Normalizer, type Output, type Schema, type SchemaChangeEvent, type SchemaDraft, SchemaRuntime, type SchemaRuntimeOptions, type SchemaType, StringFormatValidator, type StringFormatValidatorInterface, Validator, type ValidatorConfig, type ValidatorOptions, applyDefaults, collectDependencies, deepEqual, defaultErrorFormatter, detectSchemaDraft, detectSchemaType, extractReferencedPaths, get, getDefaultValue, getJsonPointer, getJsonPointerParent, jsonPointerEscape, jsonPointerJoin, jsonPointerUnescape, matchSchemaType, normalizeSchema, parseJsonPointer, removeJsonPointer, resolveAbsolutePath, safeRegexTest, setJsonPointer, stringFormatValidator, typeNullable, validateSchema };