@schema-ts/core 0.1.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.
@@ -0,0 +1,412 @@
1
+ type Schema = {
2
+ $schema?: string;
3
+ $id?: string;
4
+ $anchor?: string;
5
+ $dynamicAnchor?: string;
6
+ $ref?: string;
7
+ $dynamicRef?: string;
8
+ $defs?: Record<string, Schema>;
9
+ $comment?: string;
10
+ title?: string;
11
+ description?: string;
12
+ default?: unknown;
13
+ deprecated?: boolean;
14
+ readOnly?: boolean;
15
+ writeOnly?: boolean;
16
+ examples?: unknown[];
17
+ type?: string | string[];
18
+ enum?: unknown[];
19
+ const?: unknown;
20
+ multipleOf?: number;
21
+ maximum?: number;
22
+ exclusiveMaximum?: number;
23
+ minimum?: number;
24
+ exclusiveMinimum?: number;
25
+ maxLength?: number;
26
+ minLength?: number;
27
+ pattern?: string;
28
+ format?: string;
29
+ maxItems?: number;
30
+ minItems?: number;
31
+ uniqueItems?: boolean;
32
+ maxContains?: number;
33
+ minContains?: number;
34
+ maxProperties?: number;
35
+ minProperties?: number;
36
+ required?: string[];
37
+ dependentRequired?: Record<string, string[]>;
38
+ allOf?: Schema[];
39
+ anyOf?: Schema[];
40
+ oneOf?: Schema[];
41
+ not?: Schema;
42
+ if?: Schema;
43
+ then?: Schema;
44
+ else?: Schema;
45
+ dependentSchemas?: Record<string, Schema>;
46
+ prefixItems?: Schema[];
47
+ items?: Schema;
48
+ contains?: Schema;
49
+ properties?: Record<string, Schema>;
50
+ patternProperties?: Record<string, Schema>;
51
+ additionalProperties?: boolean | Schema;
52
+ propertyNames?: Schema;
53
+ unevaluatedItems?: Schema;
54
+ unevaluatedProperties?: Schema;
55
+ contentEncoding?: string;
56
+ contentMediaType?: string;
57
+ contentSchema?: Schema;
58
+ [key: string]: unknown;
59
+ };
60
+ /**
61
+ * Result of schema validation
62
+ */
63
+ interface Output {
64
+ valid: boolean;
65
+ keywordLocation: string;
66
+ instanceLocation: string;
67
+ absoluteKeywordLocation?: string;
68
+ absoluteInstanceLocation?: string;
69
+ error?: string;
70
+ errors: Output[];
71
+ annotations?: Output[];
72
+ }
73
+ /**
74
+ * Structured error message for i18n support.
75
+ * Used internally by ErrorFormatter to produce localized error strings.
76
+ */
77
+ interface ErrorMessage {
78
+ key: string;
79
+ params?: Record<string, unknown>;
80
+ }
81
+ /**
82
+ * JSON Schema type values
83
+ */
84
+ type SchemaType = "string" | "number" | "integer" | "boolean" | "object" | "array" | "null" | "unknown";
85
+
86
+ declare function matchSchemaType(value: unknown, type: string): boolean;
87
+ declare function detectSchemaType(value: unknown): string;
88
+ declare function parseJsonPointer(jsonPointer: string): string[];
89
+ declare function getJsonPointer(obj: unknown, jsonPointer: string): unknown;
90
+ declare function removeJsonPointer(obj: unknown, jsonPointer: string): boolean;
91
+ declare function setJsonPointer(obj: unknown, jsonPointer: string, value: unknown): boolean;
92
+ declare function get(obj: unknown, path: string[]): unknown;
93
+ /**
94
+ * Deep equality comparison for two values.
95
+ * Supports primitives, arrays, plain objects, Date, Map, Set, and RegExp.
96
+ */
97
+ declare function deepEqual(a: unknown, b: unknown): boolean;
98
+ declare function jsonPointerEscape(str: string): string;
99
+ declare function jsonPointerUnescape(str: string): string;
100
+ declare function jsonPointerJoin(base: string, token: string): string;
101
+ /**
102
+ * Convert a relative path to an absolute path
103
+ * @param nodePath The node's jsonPointer, e.g. "/a/b"
104
+ * @param relativePath Relative path, e.g. "/c" means /a/b/c
105
+ */
106
+ declare function resolveAbsolutePath(nodePath: string, relativePath: string): string;
107
+ /**
108
+ * Safely test a regex pattern against a string.
109
+ * Returns false if the pattern is invalid instead of throwing.
110
+ * Uses caching to avoid creating RegExp objects repeatedly.
111
+ */
112
+ declare function safeRegexTest(pattern: string, value: string): boolean;
113
+
114
+ interface StringFormatValidatorInterface {
115
+ validate(format: string, value: string): boolean;
116
+ }
117
+ declare class StringFormatValidator implements StringFormatValidatorInterface {
118
+ validate(format: string, value: string): boolean;
119
+ private isDateTime;
120
+ private isDate;
121
+ private isEmail;
122
+ private isHostname;
123
+ private isIPv4;
124
+ private isIPv6;
125
+ private isUri;
126
+ private isUuid;
127
+ private isDuration;
128
+ }
129
+ declare const stringFormatValidator: StringFormatValidator;
130
+
131
+ /**
132
+ * Default error message templates for i18n support.
133
+ * Keys are translation keys, values are English templates with {param} placeholders.
134
+ */
135
+ declare const MESSAGES: Record<string, string>;
136
+ /**
137
+ * Formats ErrorMessage to string for Output.error.
138
+ * Provide a custom implementation for i18n support.
139
+ */
140
+ type ErrorFormatter = (msg: ErrorMessage) => string;
141
+ /**
142
+ * Default error formatter: looks up template from MESSAGES and interpolates params.
143
+ */
144
+ declare const defaultErrorFormatter: ErrorFormatter;
145
+
146
+ interface ValidatorOptions {
147
+ fastFail?: boolean;
148
+ shallow?: boolean;
149
+ }
150
+ interface ValidatorConfig {
151
+ formatValidator?: StringFormatValidatorInterface;
152
+ errorFormatter?: ErrorFormatter;
153
+ }
154
+ declare class Validator {
155
+ private formatValidator;
156
+ private errorFormatter;
157
+ constructor(config?: ValidatorConfig);
158
+ /**
159
+ * Format an ErrorMessage to a localized string.
160
+ */
161
+ formatError(msg: ErrorMessage): string;
162
+ validate(schema: Schema, value: unknown, keywordLocation?: string, instanceLocation?: string, options?: ValidatorOptions): Output;
163
+ private validateNumber;
164
+ private validateString;
165
+ private validateArray;
166
+ private validateObject;
167
+ private detectType;
168
+ private checkType;
169
+ private validateFormat;
170
+ }
171
+ /**
172
+ * Validates a value against a JSON Schema.
173
+ * Support multiple JSON Schema drafts (04, 07, 2019-09, 2020-12) by normalizing
174
+ * the schema to draft 2020-12 format before validation.
175
+ */
176
+ declare function validateSchema(schema: Schema | unknown, value: unknown, instancePath?: string, schemaPath?: string, fastFail?: boolean): Output;
177
+
178
+ /**
179
+ * Represents a node in the schema tree.
180
+ * Each node corresponds to a location in both the schema and the instance data.
181
+ */
182
+ interface FieldNode {
183
+ type: SchemaType;
184
+ schema: Schema;
185
+ originalSchema: Schema;
186
+ error?: Output;
187
+ children?: FieldNode[];
188
+ instanceLocation: string;
189
+ keywordLocation: string;
190
+ version: number;
191
+ dependencies?: Set<string>;
192
+ canRemove: boolean;
193
+ canAdd: boolean;
194
+ }
195
+ type SchemaChangeEvent = {
196
+ type: "schema" | "value" | "error";
197
+ path: string;
198
+ };
199
+ declare class SchemaRuntime {
200
+ private validator;
201
+ private watchers;
202
+ private globalWatchers;
203
+ private dependentsMap;
204
+ private updatingNodes;
205
+ root: FieldNode;
206
+ private value;
207
+ private version;
208
+ private rootSchema;
209
+ /**
210
+ * Create a new SchemaRuntime instance.
211
+ *
212
+ * @param validator - The validator instance for schema validation
213
+ * @param schema - The JSON Schema definition (will be normalized and dereferenced)
214
+ * @param value - The initial data value to manage
215
+ *
216
+ * @example
217
+ * const validator = new Validator();
218
+ * const schema = { type: "object", properties: { name: { type: "string" } } };
219
+ * const runtime = new SchemaRuntime(validator, schema, { name: "Alice" });
220
+ */
221
+ constructor(validator: Validator, schema: Schema | unknown, value: unknown);
222
+ /**
223
+ * Collect all dependencies for a node's schema.
224
+ *
225
+ * Key insight: We extract paths from condition keywords (if, oneOf, anyOf)
226
+ * using extractReferencedPaths, but for then/else/allOf keywords, we recursively
227
+ * call collectDependencies to ensure proper dependency isolation between
228
+ * parent and child nodes.
229
+ */
230
+ private collectDependencies;
231
+ /**
232
+ * Register a node as dependent on a path
233
+ */
234
+ private registerDependent;
235
+ /**
236
+ * Unregister a node from a dependency path
237
+ */
238
+ private unregisterDependent;
239
+ /**
240
+ * Unregister all dependencies for a node and its children.
241
+ * Note: Does NOT clean up external watchers - callers are responsible
242
+ * for calling unsubscribe() when they no longer need updates.
243
+ */
244
+ private unregisterNodeDependencies;
245
+ /**
246
+ * Create an empty FieldNode with default values.
247
+ */
248
+ private createEmptyNode;
249
+ /**
250
+ * Reconcile the tree starting from the specified path.
251
+ * Uses findNearestExistingNode to find the target node (or parent if path doesn't exist).
252
+ * Only rebuilds the affected subtree, not the entire tree.
253
+ */
254
+ private reconcile;
255
+ /**
256
+ * Get the current version number.
257
+ * The version increments on every notify call (value, schema, or error changes).
258
+ * Useful for detecting if the runtime state has changed.
259
+ *
260
+ * @returns The current version number
261
+ */
262
+ getVersion(): number;
263
+ /**
264
+ * Subscribe to changes at a specific path.
265
+ * The callback is invoked when the value, schema, or error at the path changes.
266
+ *
267
+ * @param path - The JSON Pointer path to watch (e.g., "/user/name", "" for root)
268
+ * @param cb - Callback function invoked with the change event
269
+ * @returns Unsubscribe function to remove the listener
270
+ *
271
+ * @example
272
+ * const unsubscribe = runtime.subscribe("/name", (event) => {
273
+ * console.log(`${event.type} changed at ${event.path}`);
274
+ * });
275
+ * // Later: unsubscribe();
276
+ */
277
+ subscribe(path: string, cb: (e: SchemaChangeEvent) => void): () => void;
278
+ /**
279
+ * Subscribe to all events in the runtime.
280
+ * The callback is invoked for any change at any path.
281
+ *
282
+ * @param cb - Callback function invoked with every change event
283
+ * @returns Unsubscribe function to remove the listener
284
+ */
285
+ subscribeAll(cb: (e: SchemaChangeEvent) => void): () => void;
286
+ /**
287
+ * Emit a change event to all relevant subscribers.
288
+ * Increments the version number and notifies both path-specific and global watchers.
289
+ *
290
+ * @param event - The change event containing type and path
291
+ */
292
+ notify(event: SchemaChangeEvent): void;
293
+ /**
294
+ * Update the entire schema.
295
+ * This triggers a full rebuild of the node tree while preserving the current value.
296
+ */
297
+ setSchema(schema: Schema | unknown): void;
298
+ /**
299
+ * Get the value at a specific path.
300
+ *
301
+ * @param path - The JSON Pointer path (e.g., "/user/name", "" for root)
302
+ * @returns The value at the path, or undefined if not found
303
+ *
304
+ * @example
305
+ * runtime.getValue(""); // returns entire root value
306
+ * runtime.getValue("/name"); // returns value at /name
307
+ */
308
+ getValue(path: string): unknown;
309
+ /**
310
+ * Remove a node at the specified path.
311
+ * This deletes the value from the data structure (array splice or object delete).
312
+ * @param path - The path to remove
313
+ * @returns true if successful, false if the path cannot be removed
314
+ */
315
+ removeValue(path: string): boolean;
316
+ /**
317
+ * Add a new child to an array or object at the specified parent path.
318
+ * For arrays, appends a new item with default value based on items schema.
319
+ * For objects, adds a new property with the given key and default value based on additionalProperties schema.
320
+ * @param parentPath - The path to the parent array or object
321
+ * @param key - For objects: the property key. For arrays: optional, ignored (appends to end)
322
+ * @param initialValue - Optional initial value to set. If not provided, uses default from schema.
323
+ * @returns true if successful, false if cannot add
324
+ */
325
+ addValue(parentPath: string, key?: string, initialValue?: unknown): boolean;
326
+ /**
327
+ * Set the value at a specific path.
328
+ * Creates intermediate containers (objects/arrays) as needed.
329
+ * Triggers reconciliation and notifies subscribers.
330
+ *
331
+ * @param path - The JSON Pointer path (e.g., "/user/name", "" for root)
332
+ * @param value - The new value to set
333
+ * @returns true if successful, false if the path cannot be set
334
+ *
335
+ * @example
336
+ * runtime.setValue("/name", "Bob"); // set name to "Bob"
337
+ * runtime.setValue("", { name: "Alice" }); // replace entire root value
338
+ */
339
+ setValue(path: string, value: unknown): boolean;
340
+ /**
341
+ * Find the FieldNode at a specific path.
342
+ * Returns the node tree representation that includes schema, type, error, and children.
343
+ *
344
+ * @param path - The JSON Pointer path (e.g., "/user/name", "" for root)
345
+ * @returns The FieldNode at the path, or undefined if not found
346
+ *
347
+ * @example
348
+ * const node = runtime.findNode("/name");
349
+ * console.log(node?.schema, node?.type, node?.error);
350
+ */
351
+ findNode(path: string): FieldNode | undefined;
352
+ private findNearestExistingNode;
353
+ /**
354
+ * Build/update a FieldNode in place.
355
+ * Updates the node's schema, type, error, and children based on the current value.
356
+ * @param schema - Optional. If provided, updates node.originalSchema. Otherwise uses existing.
357
+ */
358
+ private buildNode;
359
+ }
360
+
361
+ /**
362
+ * JSON Schema draft version definitions and detection utilities.
363
+ */
364
+ /**
365
+ * Supported JSON Schema draft versions.
366
+ */
367
+ type SchemaDraft = "draft-04" | "draft-07" | "draft-2019-09" | "draft-2020-12";
368
+ /**
369
+ * Standard $schema URI patterns for each draft version.
370
+ */
371
+ declare const DRAFT_URIS: Record<SchemaDraft, string[]>;
372
+ /**
373
+ * Detect the JSON Schema draft version from a schema.
374
+ * Detection is based on:
375
+ * 1. The $schema URI if present
376
+ * 2. Heuristics based on keywords used
377
+ *
378
+ * @param schema - The schema to detect the version of (can be object, boolean, or unknown)
379
+ * @returns The detected draft version, defaults to "draft-2020-12" if unknown
380
+ */
381
+ declare function detectSchemaDraft(schema: unknown): SchemaDraft;
382
+
383
+ /**
384
+ * Schema normalizer that transforms older JSON Schema drafts to draft-2020-12.
385
+ *
386
+ * This allows the validator to only implement draft-2020-12 semantics
387
+ * while still supporting schemas written for older drafts.
388
+ */
389
+
390
+ interface NormalizerOptions {
391
+ /**
392
+ * Source draft version. If not specified, will be auto-detected.
393
+ */
394
+ sourceDraft?: SchemaDraft;
395
+ }
396
+ /**
397
+ * Normalize a JSON Schema from any supported draft to draft-2020-12 format.
398
+ *
399
+ * Transformations performed:
400
+ * - `id` → `$id` (draft-04)
401
+ * - Boolean `exclusiveMaximum`/`exclusiveMinimum` → numeric values (draft-04)
402
+ * - Array `items` + `additionalItems` → `prefixItems` + `items` (draft-04/07/2019-09)
403
+ * - `dependencies` → `dependentRequired`/`dependentSchemas` (draft-04/07)
404
+ * - `$recursiveRef`/`$recursiveAnchor` → `$dynamicRef`/`$dynamicAnchor` (draft-2019-09)
405
+ *
406
+ * @param schema - The schema to normalize (can be object, boolean, or unknown)
407
+ * @param options - Optional configuration
408
+ * @returns A new schema object in draft-2020-12 format (always returns an object)
409
+ */
410
+ declare function normalizeSchema(schema: unknown, options?: NormalizerOptions): Schema;
411
+
412
+ 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, deepEqual, defaultErrorFormatter, detectSchemaDraft, detectSchemaType, get, getJsonPointer, jsonPointerEscape, jsonPointerJoin, jsonPointerUnescape, matchSchemaType, normalizeSchema, parseJsonPointer, removeJsonPointer, resolveAbsolutePath, safeRegexTest, setJsonPointer, stringFormatValidator, validateSchema };