@jarenjs/validate 0.8.4 → 0.9.2

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/ARCHITECTURE.md +1067 -0
  2. package/LICENSE +21 -0
  3. package/README.md +339 -2
  4. package/dist/types/array.d.ts +2 -0
  5. package/dist/types/bigint.d.ts +1 -0
  6. package/dist/types/combine.d.ts +1 -0
  7. package/dist/types/condition.d.ts +1 -0
  8. package/dist/types/content.d.ts +3 -0
  9. package/dist/types/data.d.ts +7 -0
  10. package/dist/types/dollar-data.d.ts +20 -0
  11. package/dist/types/dynamic-ref.d.ts +44 -0
  12. package/dist/types/enum.d.ts +1 -0
  13. package/dist/types/format.d.ts +21 -0
  14. package/dist/types/index.d.ts +874 -0
  15. package/dist/types/number.d.ts +1 -0
  16. package/dist/types/object.d.ts +3 -0
  17. package/dist/types/query-keyword.d.ts +19 -0
  18. package/dist/types/query.d.ts +29 -0
  19. package/dist/types/schema.d.ts +1 -0
  20. package/dist/types/string.d.ts +1 -0
  21. package/dist/types/tools.d.ts +51 -0
  22. package/dist/types/traverse.d.ts +32 -0
  23. package/dist/types/unevaluated.d.ts +12 -0
  24. package/package.json +32 -7
  25. package/src/array.js +565 -0
  26. package/src/bigint.js +97 -0
  27. package/src/combine.js +226 -0
  28. package/src/condition.js +109 -0
  29. package/src/content.js +83 -0
  30. package/src/data.js +477 -0
  31. package/src/dollar-data.js +629 -0
  32. package/src/dynamic-ref.js +121 -0
  33. package/src/enum.js +148 -0
  34. package/src/format.js +66 -0
  35. package/src/index.js +1854 -0
  36. package/src/number.js +159 -0
  37. package/src/object.js +755 -0
  38. package/src/query-keyword.js +99 -0
  39. package/src/query.js +59 -0
  40. package/src/schema.js +645 -0
  41. package/src/string.js +152 -0
  42. package/src/tools.js +205 -0
  43. package/src/traverse.js +433 -0
  44. package/src/unevaluated.js +151 -0
  45. package/dist/index.js +0 -1998
  46. package/dist/index.js.map +0 -7
  47. package/dist/index.min.js +0 -2
  48. package/dist/index.min.js.map +0 -7
@@ -0,0 +1,1067 @@
1
+ # @jarenjs/validate Architecture
2
+
3
+ **IMPORTANT**: Update this document only when introducing architectural changes. If multiple architects are involved, ensure consensus on significant changes.
4
+
5
+ ## Overview
6
+
7
+ **@jarenjs/validate** is a high-performance JSON Schema validator for JavaScript/TypeScript. It implements a **compilation-based architecture** that transforms JSON Schemas into optimized validation functions at build time, rather than interpreting schemas during validation. This design choice delivers significant performance advantages over traditional interpretation-based validators.
8
+
9
+ ### Core Design Philosophy
10
+
11
+ ```
12
+ Schema → Compile → Optimized Validator Function → Execute against Data
13
+ ```
14
+
15
+ The validator embraces these architectural principles:
16
+
17
+ 1. **Compile-Time Optimization**: Schemas are parsed and compiled once, generating specialized validation functions
18
+ 2. **Fast Path Specialization**: Common schema patterns receive dedicated optimized code paths
19
+ 3. **Zero-Copy Validation**: Minimal object allocations during validation through careful memory management
20
+ 4. **Reference Pre-Resolution**: All `$ref` references are resolved and flattened at compile time
21
+ 5. **Lazy Error Generation**: Error objects are only created when explicitly requested
22
+
23
+ ### Key Files
24
+
25
+ | File | Purpose |
26
+ |------|---------|
27
+ | `packages/validate/src/index.js` | Main validator classes (`JarenValidator`, `ValidationRoot`, `ValidationObject`) |
28
+ | `packages/validate/src/traverse.js` | Schema traversal and ref resolution (`storeSchemaIdsInMap`, `restoreSchemaRefsInMap`) |
29
+ | `packages/validate/src/schema.js` | Schema compilation dispatcher (`compileSchemaObject`) |
30
+ | `packages/validate/src/array.js` | Array validation logic |
31
+ | `packages/validate/src/object.js` | Object validation logic |
32
+ | `packages/validate/src/string.js` | String validation logic |
33
+ | `packages/validate/src/number.js` | Number validation logic |
34
+
35
+ ---
36
+
37
+ ## Core Design Principles
38
+
39
+ ### 1. Compile-Time Optimization
40
+
41
+ The most important architectural insight: **all ref resolution must happen at compile time**. This means:
42
+
43
+ - `$ref` chains are flattened during schema loading
44
+ - Validation functions are pre-compiled before any data is validated
45
+ - No URI resolution happens during validation
46
+
47
+ ### 2. Lazy vs Eager Evaluation Trade-offs
48
+
49
+ Refs are resolved eagerly at compile time, never lazily on first validation. This keeps the first `validate(data)` call as fast as every subsequent one:
50
+
51
+ ```javascript
52
+ const validate = compile(schema); // All refs resolved here
53
+ validate(data); // Direct function call, no resolution
54
+ ```
55
+
56
+ ### 3. Function Inlining
57
+
58
+ To reduce call stack depth, simple schemas compile to inline validation functions rather than delegating to helper functions:
59
+
60
+ ```javascript
61
+ // Before: Multiple function calls
62
+ function validate(data) {
63
+ return validateType(data) && validateString(data);
64
+ }
65
+
66
+ // After: example, but use @jarenjs/core
67
+ function validate(data) {
68
+ if (typeof data !== 'string') return false;
69
+ if (data.length > maxLength) return false;
70
+ return true;
71
+ }
72
+ ```
73
+
74
+ ---
75
+
76
+ ## Four-Phase Architecture
77
+
78
+ The validator operates in four distinct phases that cleanly separate concerns:
79
+
80
+ ### Phase 1: Schema Registration
81
+
82
+ **Purpose**: Build a map of all reachable schemas by URI.
83
+
84
+ **Entry Point**: `JarenValidator.addSchema(schema, key)`
85
+
86
+ **Flow**:
87
+ ```
88
+ addSchema(schema, key)
89
+ ├── Store schema under key (and alt key with/without #)
90
+ └── #traverseAndStoreIds(baseUri, schema)
91
+ └── storeSchemaIdsInMap(schemasMap, baseUri, schema)
92
+ ├── Store root schema under baseUri
93
+ └── BFS traverse schema structure:
94
+ ├── On $id: Store subschema, update baseUri
95
+ ├── On $anchor: Store anchor
96
+ ├── On $ref: Store null placeholder (mark as ref)
97
+ └── On schema objects/arrays: Continue traversal
98
+ ```
99
+
100
+ **Key Data Structure**: `#schemas Map<string, schema|null>`
101
+
102
+ - Keys are normalized URIs (e.g., `http://example.com/schema#`, `#/$defs/foo`)
103
+ - Values are either the schema object or `null` (for refs that point elsewhere)
104
+ - Stored at `JarenValidator` instance level (survives multiple `compile()` calls)
105
+
106
+ **Important**: At this phase, refs are stored as `null` placeholders. The actual resolution happens later.
107
+
108
+ ### Phase 2: Schema Traversal
109
+
110
+ **Purpose**: Create a schemas map for a specific compilation, merging instance schemas with passed schemas.
111
+
112
+ **Entry Point**: `JarenValidator.compile(schema, schemas)`
113
+
114
+ **Flow**:
115
+ ```
116
+ compile(schema, schemas)
117
+ └── #traverseSchema(schema, schemas, instanceSchemas)
118
+ ├── Create new schemaMap from instanceSchemas
119
+ ├── storeSchemaIdsInMap(schemaMap, origin, schema)
120
+ ├── (Optional) storeSchemaIdsInMap for additional schemas
121
+ └── restoreSchemaRefsInMap(schemaMap)
122
+ └── For each null entry:
123
+ ├── Try resolveRefSchemaDeep (flatten chains)
124
+ └── On failure: resolveRefSchemaShallow (single hop)
125
+ ```
126
+
127
+ **Key Data Structure**: `schemaMap Map<string, schema>` (local to this compilation)
128
+
129
+ - Merges instance-level schemas (`this.#schemas`) with compile-time schemas
130
+ - After `restoreSchemaRefsInMap`, all ref values are resolved schemas (not null)
131
+ - **Ref chains are flattened**: `A → B → C` becomes `A → C`
132
+
133
+ **The Critical Optimization**: `restoreSchemaRefsInMap` now uses `resolveRefSchemaDeep` instead of `resolveRefSchemaShallow`. This follows the entire ref chain at load time and stores the final schema directly.
134
+
135
+ ### Phase 3: Compilation
136
+
137
+ **Purpose**: Create compiled validator functions from schemas.
138
+
139
+ **Entry Point**: `JarenValidator.#compileSchemaWithRoot(...)`
140
+
141
+ **Flow**:
142
+ ```
143
+ compile()
144
+ ├── Create ValidationRoot(origin, schemaMap, formats, options)
145
+ ├── #precompileRefs(root, schemaMap, origin)
146
+ │ └── For each ref in schemaMap:
147
+ │ ├── Skip if already compiled
148
+ │ └── root.createObject(id, schema, origin)
149
+ │ └── ValidationObject(root, id, schema, baseUri)
150
+ │ └── compileValidator() → returns validator function
151
+ └── Return jarenValidateSchema(data) function
152
+ ```
153
+
154
+ **Key Classes**:
155
+
156
+ #### ValidationRoot
157
+ - Manages the `Map<string, ValidationObject>` called `#objects`
158
+ - Entry point: `validate(data)` clears errors and calls `#firstSchema.validate(data, data)`
159
+ - Creates objects via `createObject()` and caches them in `#objects`
160
+ - **Pre-compilation**: Now creates ValidationObjects for ALL refs before returning
161
+
162
+ #### ValidationObject
163
+ - Represents a single schema location (identified by URI)
164
+ - **Constructor**: Immediately compiles validator via `compileValidator()`
165
+ - **Key property**: `#validator` - the compiled function that validates data
166
+ - For refs: `#validator` is the target validator function (direct reference)
167
+
168
+ **The Critical Optimization**: `#precompileRefs()` iterates through `schemaMap` and creates `ValidationObject` instances for every ref. This moves object creation from validation-time to compile-time.
169
+
170
+ ### Phase 4: Validation
171
+
172
+ **Purpose**: Validate data against compiled schema.
173
+
174
+ **Entry Point**: `jarenValidateSchema(data)`
175
+
176
+ **Flow**:
177
+ ```
178
+ jarenValidateSchema(data)
179
+ └── root.validate(data)
180
+ └── #firstSchema.validate(data, dataRoot)
181
+ └── this.#validator(data, dataRoot)
182
+ └── Either:
183
+ ├── compileSchemaObject() result (for non-ref schemas)
184
+ └── Target validator function (for refs, pre-compiled)
185
+ ```
186
+
187
+ **Key Insight**: For refs, `this.#validator` is **already** the target validator function (set during Phase 3 pre-compilation). No resolution happens at validation time.
188
+
189
+ ---
190
+
191
+ ## High-Level Architecture
192
+
193
+ ```mermaid
194
+ flowchart TB
195
+ subgraph "Public API Layer"
196
+ A[JarenValidator]
197
+ B[ValidationOptions]
198
+ C[ValidatorOptions]
199
+ end
200
+
201
+ subgraph "Compilation Layer"
202
+ D[ValidationRoot]
203
+ E[ValidationObject]
204
+ F[Schema Compilation]
205
+ end
206
+
207
+ subgraph "Schema Processing"
208
+ G[Schema Traversal]
209
+ H[Reference Resolution]
210
+ I[JSON Pointer Handling]
211
+ end
212
+
213
+ subgraph "Keyword Compilers"
214
+ J[Type Validators]
215
+ K[String Keywords]
216
+ L[Number Keywords]
217
+ M[Array Keywords]
218
+ N[Object Keywords]
219
+ O[Combine Keywords]
220
+ P[Conditional Keywords]
221
+ end
222
+
223
+ subgraph "Runtime Dependencies"
224
+ Q[@jarenjs/core]
225
+ end
226
+
227
+ A --> D
228
+ D --> E
229
+ E --> F
230
+ F --> J & K & L & M & N & O & P
231
+ G --> H
232
+ H --> I
233
+ D --> G
234
+ J & K & L & M & N & O & P --> Q
235
+ ```
236
+
237
+ ---
238
+
239
+ ## Module Organization
240
+
241
+ ```mermaid
242
+ flowchart LR
243
+ subgraph "Entry Point"
244
+ INDEX[index.js]
245
+ end
246
+
247
+ subgraph "Core Classes"
248
+ INDEX --> VR[ValidationRoot]
249
+ INDEX --> VO[ValidationObject]
250
+ INDEX --> JV[JarenValidator]
251
+ end
252
+
253
+ subgraph "Schema Compilation"
254
+ VO --> SCHEMA[schema.js]
255
+ SCHEMA --> TYPE[type validation]
256
+ SCHEMA --> COMPILE[compileSchemaObject]
257
+ end
258
+
259
+ subgraph "Keyword Modules"
260
+ SCHEMA --> STRING[string.js]
261
+ SCHEMA --> NUMBER[number.js]
262
+ SCHEMA --> BIGINT[bigint.js]
263
+ SCHEMA --> ARRAY[array.js]
264
+ SCHEMA --> OBJECT[object.js]
265
+ SCHEMA --> COMBINE[combine.js]
266
+ SCHEMA --> CONDITION[condition.js]
267
+ SCHEMA --> ENUM[enum.js]
268
+ SCHEMA --> FORMAT[format.js]
269
+ SCHEMA --> CONTENT[content.js]
270
+ SCHEMA --> DATA[data.js]
271
+ SCHEMA --> DDLR[dollar-data.js]
272
+ SCHEMA --> UNEVAL[unevaluated.js]
273
+ end
274
+
275
+ subgraph "Infrastructure"
276
+ INDEX --> TRAVERSE[traverse.js]
277
+ INDEX --> TOOLS[tools.js]
278
+ INDEX --> DYNREF[dynamic-ref.js]
279
+ end
280
+
281
+ subgraph "External Dependencies"
282
+ TOOLS --> CORE[@jarenjs/core]
283
+ end
284
+ ```
285
+
286
+ ---
287
+
288
+ ## Core Classes
289
+
290
+ ### JarenValidator
291
+
292
+ The primary entry point for consumers. Manages schema registration, format registration, and orchestrates the compilation process.
293
+
294
+ ```mermaid
295
+ classDiagram
296
+ class JarenValidator {
297
+ -Map #schemas
298
+ -Map #metaSchemas
299
+ -object #formats
300
+ -ValidatorOptions #options
301
+ +addSchema(schema, key)
302
+ +addFormat(name, compiler)
303
+ +addFormats(compilers)
304
+ +compile(schema)
305
+ +validateSchema(schema)
306
+ +addMetaSchema(schema, key)
307
+ +getSchema(key)
308
+ }
309
+
310
+ class ValidationRoot {
311
+ -string #rootOrigin
312
+ -Map #schemas
313
+ -object #formats
314
+ -ValidationOptions #options
315
+ -TraverseOptions #traverse
316
+ -Map #objects
317
+ -Array #errors
318
+ -ValidationObject #firstSchema
319
+ +validate(data)
320
+ +createObject(path, schema, baseUri)
321
+ +resolveObject(ref, path, schema)
322
+ +addError(error)
323
+ }
324
+
325
+ class ValidationObject {
326
+ -ValidationRoot #root
327
+ -string #path
328
+ -Array #members
329
+ -object #schema
330
+ -function #validator
331
+ -string #baseUri
332
+ -string #effectiveBaseUri
333
+ +createValidator(schema, key, index)
334
+ +createErrorHandler(expected, key)
335
+ +validate(data, dataPath, dataRoot)
336
+ }
337
+
338
+ JarenValidator --> ValidationRoot : creates
339
+ ValidationRoot --> ValidationObject : manages
340
+ ValidationObject --> ValidationObject : creates children
341
+ ```
342
+
343
+ ### ValidationRoot
344
+
345
+ The compilation context that:
346
+ - Maintains the schema registry (URI → Schema mapping)
347
+ - Manages format validators
348
+ - Houses all compiled ValidationObjects
349
+ - Collects validation errors
350
+ - Provides `$ref` resolution services
351
+
352
+ ### ValidationObject
353
+
354
+ Represents a single schema location with its compiled validator:
355
+ - **Identity**: URI path identifying this schema object
356
+ - **State**: The schema definition and compiled validator function
357
+ - **Relationships**: Parent root reference, child members
358
+ - **Behavior**: Creates child validators, handles error generation
359
+
360
+ ---
361
+
362
+ ## Compilation Pipeline
363
+
364
+ ```mermaid
365
+ sequenceDiagram
366
+ participant User
367
+ participant JV as JarenValidator
368
+ participant VR as ValidationRoot
369
+ participant VO as ValidationObject
370
+ participant SC as Schema Compilers
371
+ participant TD as Type-specific Compilers
372
+
373
+ User->>JV: compile(schema)
374
+ JV->>JV: #traverseSchema()
375
+ Note over JV: Build schema map with<br/>all $id and $ref locations
376
+
377
+ JV->>VR: new ValidationRoot(origin, schemas, formats, opts)
378
+ VR->>VR: Pre-compile all refs
379
+
380
+ VR->>VO: create root ValidationObject
381
+ VO->>VO: compileValidator()
382
+
383
+ alt Has $ref
384
+ VO->>VR: resolveObject(ref)
385
+ VR-->>VO: cached ValidationObject
386
+ else Complex Schema
387
+ VO->>SC: compileSchemaObject()
388
+ SC->>TD: compile keywords
389
+ TD-->>SC: validators[]
390
+ SC->>SC: compose validators
391
+ SC-->>VO: compiled function
392
+ end
393
+
394
+ VO-->>VR: ready
395
+ VR-->>JV: ValidationRoot ready
396
+ JV-->>User: validation function
397
+ ```
398
+
399
+ ---
400
+
401
+ ## Schema Compilation Flow
402
+
403
+ ```mermaid
404
+ flowchart TD
405
+ A[JSON Schema Input] --> B{Schema Type}
406
+
407
+ B -->|true| C[Return trueThat]
408
+ B -->|false| D[Return false validator]
409
+ B -->|object| E{Has $ref?}
410
+
411
+ E -->|Yes| F[Delegate to ref resolver]
412
+ E -->|No| G{Fast Path?}
413
+
414
+ G -->|Type only| H[compileTypeSimple]
415
+ G -->|Required only| I[compileRequired]
416
+ G -->|MinLength only| J[compileMinLengthFast]
417
+ G -->|Complex| K[Full Compilation]
418
+
419
+ K --> L[compileTypeBasic]
420
+ K --> M[compileEnumBasic]
421
+ K --> N[compileDollarDataSchema]
422
+ K --> O[compileNumberBasic]
423
+ K --> P[compileBigIntBasic]
424
+ K --> Q[compileStringBasic]
425
+ K --> R[compileFormatBasic]
426
+ K --> S[compileContentSchema]
427
+ K --> T[compileArraySchema]
428
+ K --> U[compileObjectSchema]
429
+ K --> V[compileCombineSchema]
430
+ K --> W[compileConditionSchema]
431
+
432
+ L & M & N & O & P & Q & R & S & T & U & V & W --> X[Compose validators]
433
+ X --> Y{Count}
434
+
435
+ Y -->|0| C
436
+ Y -->|1| Z[Return single]
437
+ Y -->|2| AA[Dual validator]
438
+ Y -->|3| AB[Triple validator]
439
+ Y -->|4+| AC[Loop validator]
440
+ ```
441
+
442
+ ---
443
+
444
+ ## Keyword Compiler Architecture
445
+
446
+ Each keyword module follows a consistent pattern:
447
+
448
+ ```mermaid
449
+ flowchart LR
450
+ A[Schema Object] --> B[Extract Keyword Value]
451
+ B --> C{Value Valid?}
452
+ C -->|No| D[Return undefined]
453
+ C -->|Yes| E[Create Error Handler]
454
+ E --> F[Compile Validator Function]
455
+ F --> G[Return Closure]
456
+
457
+ style D fill:#f99
458
+ style G fill:#9f9
459
+ ```
460
+
461
+ ### Example: String Keyword Compilation
462
+
463
+ ```mermaid
464
+ flowchart TD
465
+ A[compileStringBasic] --> B[compileStringIntern]
466
+
467
+ B --> C[compileMinLength]
468
+ B --> D[compileMaxLength]
469
+ B --> E[compilePattern]
470
+
471
+ C --> F{min > 0?}
472
+ D --> G{max >= 0?}
473
+ E --> H{pattern valid?}
474
+
475
+ F -->|No| I[undefined]
476
+ F -->|Yes| J[minLength validator]
477
+ G -->|No| K[undefined]
478
+ G -->|Yes| L[maxLength validator]
479
+ H -->|No| M[undefined]
480
+ H -->|Yes| N[pattern validator]
481
+
482
+ I & J & K & L & M & N --> O{Any validators?}
483
+
484
+ O -->|No| P[Return undefined]
485
+ O -->|Yes| Q{Fast path?}
486
+
487
+ Q -->|Max only| R[maxOnly validator]
488
+ Q -->|Min only| S[minOnly validator]
489
+ Q -->|Pattern only| T[patternOnly validator]
490
+ Q -->|Complex| U[full validator]
491
+
492
+ style P fill:#f99
493
+ style R fill:#9f9
494
+ style S fill:#9f9
495
+ style T fill:#9f9
496
+ style U fill:#9f9
497
+ ```
498
+
499
+ ---
500
+
501
+ ## Reference Resolution System
502
+
503
+ The `$ref` resolution is one of the most critical performance optimizations. Rather than resolving references at validation time, all references are pre-compiled:
504
+
505
+ ```mermaid
506
+ flowchart TD
507
+ A[Schema with $ref] --> B[Traverse Schema]
508
+ B --> C[Build Schema Map]
509
+ C --> D[Store all $id locations]
510
+ C --> E[Store null placeholders for $refs]
511
+
512
+ E --> F[restoreSchemaRefsInMap]
513
+ F --> G[resolveRefSchemaDeep]
514
+
515
+ G --> H{Chain resolution}
516
+ H --> I[Flatten a→b→c to a→c]
517
+
518
+ I --> J[Pre-compile ValidationObjects]
519
+ J --> K[Cache by URI]
520
+
521
+ K --> L[Validation time]
522
+ L --> M{Ref in cache?}
523
+ M -->|Yes| N[Direct lookup O1]
524
+ M -->|No| O[Lazy resolve fallback]
525
+
526
+ style N fill:#9f9
527
+ style O fill:#ff9
528
+ ```
529
+
530
+ ### JSON Pointer Handling
531
+
532
+ ```mermaid
533
+ classDiagram
534
+ class TraverseOptions {
535
+ +string origin
536
+ +boolean mergeSchemas
537
+ +boolean anchorsGlobal
538
+ +boolean anchorsAllowed
539
+ +boolean skipErrors
540
+ }
541
+
542
+ class JsonPointer {
543
+ +string id
544
+ +string search
545
+ +string leftUri
546
+ +string fragment
547
+ }
548
+
549
+ TraverseOptions --|> JsonPointerOptions
550
+ JsonPointerOptions --> JsonPointer : creates via createJsonPointer()
551
+ ```
552
+
553
+ ---
554
+
555
+ ## Data Reference Systems
556
+
557
+ The validator implements two complementary data reference mechanisms:
558
+
559
+ ```mermaid
560
+ flowchart TB
561
+ subgraph "$data Keyword Ajv-style"
562
+ A1[Relative JSON Pointer] --> A2[resolveRelativePointer]
563
+ A2 --> A3[Access parent/ancestor data]
564
+ A3 --> A4[Dynamic constraint values]
565
+ A4 --> A5[Example: maximum: {$data: '1/larger'}]
566
+ end
567
+
568
+ subgraph "data Keyword json-everything-style"
569
+ B1[JSON Pointer Path] --> B2[resolveDataRef]
570
+ B2 --> B3[Access from data root]
571
+ B3 --> B4[Cross-property validation]
572
+ B4 --> B5[Example: data: {minimum: '/A'}]
573
+ end
574
+
575
+ A5 --> C[Runtime value resolution]
576
+ B5 --> C
577
+ ```
578
+
579
+ ---
580
+
581
+ ## Error Handling Architecture
582
+
583
+ ```mermaid
584
+ flowchart LR
585
+ A[Validation Failure] --> B{skipErrors?}
586
+
587
+ B -->|true| C[Return false only]
588
+ B -->|false| D[Create InternalValidationError]
589
+
590
+ D --> E[Store in ValidationRoot.errors]
591
+ E --> F[Continue validation]
592
+
593
+ F --> G[All validators complete]
594
+ G --> H{collectErrors?}
595
+
596
+ H -->|true| I[Convert to ValidationError]
597
+ H -->|false| J[Return boolean only]
598
+
599
+ I --> K[Add human-readable messages]
600
+ K --> L[Return {valid, errors}]
601
+
602
+ style C fill:#9f9
603
+ style J fill:#9f9
604
+ style L fill:#9f9
605
+ ```
606
+
607
+ ### Error Handler Creation
608
+
609
+ ```mermaid
610
+ sequenceDiagram
611
+ participant Compiler
612
+ participant VO as ValidationObject
613
+ participant VR as ValidationRoot
614
+ participant User
615
+
616
+ Compiler->>VO: createErrorHandler(expected, keyword)
617
+ VO->>VR: check options.skipErrors
618
+
619
+ alt skipErrors = true
620
+ VR-->>VO: fast no-op handler
621
+ VO-->>Compiler: () => false
622
+ else
623
+ VR-->>VO: full error handler
624
+ VO-->>Compiler: (data, ...meta) => {...}
625
+ end
626
+
627
+ Compiler->>User: validation function with handlers
628
+ ```
629
+
630
+ ---
631
+
632
+ ## Performance Optimizations
633
+
634
+ ### 1. Fast Path Specialization
635
+
636
+ ```mermaid
637
+ flowchart TD
638
+ A[Schema Analysis] --> B{Common Pattern?}
639
+
640
+ B -->|type only| C[Inline type check]
641
+ B -->|required only| D[Inline required check]
642
+ B -->|minLength only| E[Direct length compare]
643
+ B -->|Complex| F[Full composition]
644
+
645
+ C --> G[No function call overhead]
646
+ D --> G
647
+ E --> G
648
+ F --> H[Minimal overhead]
649
+
650
+ G --> I[Maximum Performance]
651
+ H --> I
652
+ ```
653
+
654
+ ### 2. Validator Composition Strategies
655
+
656
+ Based on the number of validators, different composition strategies are used:
657
+
658
+ | Validator Count | Strategy | Implementation |
659
+ |-----------------|----------|----------------|
660
+ | 0 | No-op | `trueThat` |
661
+ | 1 | Direct | Return validator directly |
662
+ | 2 | Unrolled | `return v1(data) && v2(data)` |
663
+ | 3 | Unrolled | `return v1(data) && v2(data) && v3(data)` |
664
+ | 4+ | Loop | `for (let i = 0; i < n; i++)` |
665
+
666
+ ### 3. String Length Optimization
667
+
668
+ ```mermaid
669
+ flowchart TD
670
+ A[String validation] --> B{useGrapheme?}
671
+
672
+ B -->|false| C[data.length]
673
+ B -->|true| D{ASCII check}
674
+
675
+ D -->|All ASCII| E[data.length]
676
+ D -->|Non-ASCII| F{Cluster-forming chars?}
677
+
678
+ F -->|No| J[Code point count]
679
+ F -->|Yes| K[Segmenter iteration]
680
+
681
+ C --> G[Fast path]
682
+ E --> G
683
+ J --> G
684
+ K --> H[Slow path]
685
+
686
+ G --> I[Inline in hot loop]
687
+ H --> I
688
+ ```
689
+
690
+ ---
691
+
692
+ ## Module Dependencies
693
+
694
+ ```mermaid
695
+ flowchart TB
696
+ subgraph "@jarenjs/validate"
697
+ direction TB
698
+ INDEX[index.js]
699
+ SCHEMA[schema.js]
700
+
701
+ subgraph "Type Compilers"
702
+ STR[string.js]
703
+ NUM[number.js]
704
+ BINT[bigint.js]
705
+ ARR[array.js]
706
+ OBJ[object.js]
707
+ end
708
+
709
+ subgraph "Logic Compilers"
710
+ COMB[combine.js]
711
+ COND[condition.js]
712
+ ENUM[enum.js]
713
+ end
714
+
715
+ subgraph "Content Compilers"
716
+ FMT[format.js]
717
+ CONT[content.js]
718
+ end
719
+
720
+ subgraph "Data Reference"
721
+ DATA[data.js]
722
+ DDLR[dollar-data.js]
723
+ end
724
+
725
+ subgraph "Infrastructure"
726
+ TRAV[traverse.js]
727
+ TOOLS[tools.js]
728
+ end
729
+ end
730
+
731
+ subgraph "@jarenjs/core"
732
+ CORE[index.js]
733
+ CORE_NUM[number.js]
734
+ CORE_STR[string.js]
735
+ CORE_ARR[array.js]
736
+ CORE_OBJ[object.js]
737
+ CORE_FN[function.js]
738
+ CORE_TXT[text/index.js]
739
+ end
740
+
741
+ subgraph "@jarenjs/json"
742
+ CORE_JSON[index.js]
743
+ end
744
+
745
+ INDEX --> SCHEMA & TRAV & TOOLS & FMT
746
+ SCHEMA --> STR & NUM & BINT & ARR & OBJ & COMB & COND & ENUM & CONT & DATA & DDLR
747
+
748
+ INDEX --> CORE
749
+ SCHEMA --> CORE
750
+ STR --> CORE_STR & CORE_FN
751
+ NUM --> CORE_NUM & CORE_FN
752
+ BINT --> CORE_NUM & CORE_FN
753
+ ARR --> CORE_ARR & CORE_OBJ & CORE_FN
754
+ OBJ --> CORE_OBJ & CORE_ARR & CORE_FN
755
+ ENUM --> CORE_OBJ
756
+ CONT --> CORE_TXT & CORE_JSON
757
+ DATA --> CORE_JSON
758
+ DDLR --> CORE_JSON
759
+ TOOLS --> CORE & CORE_STR & CORE_ARR
760
+ TRAV --> CORE & CORE_NUM & CORE_STR & CORE_TXT
761
+ ```
762
+
763
+ ---
764
+
765
+ ## Extension Points
766
+
767
+ ### Format Registration
768
+
769
+ ```mermaid
770
+ flowchart LR
771
+ A[Custom Format] --> B[Format Compiler]
772
+ B --> C[Returns Validator Function]
773
+ C --> D[Register via addFormat]
774
+ D --> E[Available in schemas]
775
+
776
+ style A fill:#9cf
777
+ style E fill:#9f9
778
+ ```
779
+
780
+ ### Custom Keywords
781
+
782
+ To add custom keywords, you would extend the compilation pipeline in `schema.js`:
783
+
784
+ ```javascript
785
+ // In compileSchemaObject or a new module
786
+ function compileCustomKeyword(schemaObj, jsonSchema) {
787
+ if (jsonSchema.myKeyword === undefined) return undefined;
788
+
789
+ const addError = schemaObj.createErrorHandler(value, 'myKeyword');
790
+
791
+ return function validateMyKeyword(data, dataPath) {
792
+ // Custom validation logic
793
+ return isValid(data) || addError(data, dataPath);
794
+ };
795
+ }
796
+ ```
797
+
798
+ ---
799
+
800
+ ## Testing Strategy
801
+
802
+ The architecture supports comprehensive testing at multiple levels:
803
+
804
+ ```mermaid
805
+ flowchart TB
806
+ subgraph "Unit Tests"
807
+ A[Individual Compilers]
808
+ B[Utility Functions]
809
+ C[Type Checkers]
810
+ end
811
+
812
+ subgraph "Integration Tests"
813
+ D[Full Schema Compilation]
814
+ E[Reference Resolution]
815
+ F[Error Collection]
816
+ end
817
+
818
+ subgraph "Compliance Tests"
819
+ G[JSON Schema Test Suite]
820
+ H[Draft 6/7/2019-09/2020-12]
821
+ end
822
+
823
+ A & B & C --> I[Test Coverage]
824
+ D & E & F --> I
825
+ G & H --> I
826
+ ```
827
+
828
+ ---
829
+
830
+ ## Data Structures
831
+
832
+ ### schemasMap Structure
833
+
834
+ ```typescript
835
+ Map<string, schema | null> {
836
+ // Root schemas
837
+ "http://example.com/schema#" → { type: "object", properties: {...} }
838
+
839
+ // Internal refs (after Phase 2, these point to final schemas)
840
+ "#/$defs/foo" → { type: "string" } // Flattened: was A→B→C, now A→C
841
+ "#/$defs/bar" → { $ref: "#/$defs/foo" } // If not yet resolved
842
+
843
+ // Anchors
844
+ "http://example.com/schema#myAnchor" → { type: "number" }
845
+ "#myAnchor" → { type: "number" } // Global anchor
846
+ }
847
+ ```
848
+
849
+ ### ValidationRoot.#objects Structure
850
+
851
+ ```typescript
852
+ Map<string, ValidationObject> {
853
+ "http://example.com/schema#" → ValidationObject {
854
+ #path: "http://example.com/schema#",
855
+ #validator: function validateObject(data, root) {...},
856
+ #schema: { type: "object", ... },
857
+ #root: ValidationRoot
858
+ },
859
+
860
+ "#/$defs/foo" → ValidationObject {
861
+ #path: "#/$defs/foo",
862
+ #validator: function validateString(data, root) {...}, // Direct reference
863
+ #schema: { type: "string" },
864
+ #root: ValidationRoot
865
+ }
866
+ }
867
+ ```
868
+
869
+ ---
870
+
871
+ ## Key Functions Reference
872
+
873
+ | Function | File | Purpose |
874
+ |----------|------|---------|
875
+ | `storeSchemaIdsInMap` | traverse.js | BFS traversal storing all schema IDs and refs |
876
+ | `resolveRefSchemaShallow` | traverse.js | Resolve single ref hop (baseUri + fragment) |
877
+ | `resolveRefSchemaDeep` | traverse.js | Follow ref chain to final schema |
878
+ | `restoreSchemaRefsInMap` | traverse.js | Flatten all ref chains at load time |
879
+ | `createJsonPointer` | traverse.js | Parse URI into components (id, leftUri, fragment) |
880
+ | `JarenValidator.compile` | index.js | Main entry point for schema compilation, draft/vocabulary selection |
881
+ | `JarenValidator.#precompileRefs` | index.js | Pre-create ValidationObjects for all refs |
882
+ | `ValidationObject.compileValidator` | index.js | Compile validator, ref combination, dynamic-anchor registration |
883
+ | `ValidationRoot.resolveObject` | index.js | Resolve ref to ValidationObject (fallback only) |
884
+ | `wrapUnevaluated` | unevaluated.js | Final-stage unevaluatedProperties/unevaluatedItems check |
885
+ | `EvalLog` | tools.js | Annotation log with mark/rollback for unevaluated* |
886
+ | `collectDynamicAnchorsDeep` | dynamic-ref.js | Gather a resource's $dynamicAnchors for scope entry |
887
+
888
+ ---
889
+
890
+ ## Implementation Notes
891
+
892
+ ### How $ref Works
893
+
894
+ 1. **Draft 7 and earlier - $ref ignores siblings**: when a schema has `$ref`, all other keywords are ignored, the referenced schema completely replaces the current schema, and a sibling `$id` does not affect `$ref` resolution
895
+ 2. **Draft 2019-09 and later - $ref has siblings**: `$ref` is just another keyword; sibling keywords apply together with the referenced schema, and a sibling `$id` DOES establish the base URI the `$ref` resolves against
896
+ 3. **Location-independent identifiers**: Anchors like `#foo` should be resolvable within their document
897
+ 4. **Ref chain resolution happens at compile time**: pre-resolved via `restoreSchemaRefsInMap` and `#precompileRefs`
898
+
899
+ ### Base URI Resolution
900
+
901
+ When schemas have `$id` that changes the base URI:
902
+
903
+ ```json
904
+ {
905
+ "$id": "http://example.com/schema",
906
+ "$defs": {
907
+ "nested": {
908
+ "$id": "nested/",
909
+ "$defs": {
910
+ "deep": {
911
+ "$ref": "folder/file.json" // Resolves against http://example.com/nested/
912
+ }
913
+ }
914
+ }
915
+ }
916
+ }
917
+ ```
918
+
919
+ **Resolution Rules**:
920
+ 1. `$id` changes the base URI for itself and all children
921
+ 2. `$ref` is resolved against the current base URI
922
+ 3. Relative `$id` values resolve against the parent's base URI
923
+ 4. Trailing `#` is stripped for URL resolution
924
+
925
+ ### ECMAScript Regex in JSON Schema
926
+
927
+ 1. **Unicode flag required**: All regex patterns use the `u` flag for proper Unicode support
928
+ 2. **Unicode property escapes**: Patterns using `\p{...}` require the `u` flag
929
+ 3. **Surrogate pairs**: Non-BMP characters (like emojis) are represented as surrogate pairs in JavaScript
930
+
931
+ ---
932
+
933
+ ## Debugging Guide
934
+
935
+ ### "Can not resolve schema for 'X'"
936
+
937
+ **Cause**: The ref `X` is not in schemasMap
938
+
939
+ **Check**:
940
+ - Was the schema containing `X` added via `addSchema()`?
941
+ - Is the ref path correct in the schema?
942
+ - After `restoreSchemaRefsInMap`, all refs should have non-null values
943
+
944
+ ### Performance Degradation on First Validation
945
+
946
+ **Cause**: Refs being resolved at validation time instead of compile time
947
+
948
+ **Check**:
949
+ - Is `#precompileRefs` being called in `compile()`?
950
+ - Are ValidationObjects being created for all refs?
951
+ - Verify: `compileValidator` should find targets immediately without fallback
952
+
953
+ ### Test Interference (tests pass individually but fail together)
954
+
955
+ **Cause**: Global shared state between validators
956
+
957
+ **Check**:
958
+ - Are you using a global cache? (Don't)
959
+ - Solution: All state should be per-ValidationRoot or per-JarenValidator
960
+
961
+ ### Debugging Ref Resolution
962
+
963
+ Enable debug logging in traverse.js:
964
+
965
+ ```javascript
966
+ // In packages/validate/src/traverse.js
967
+ console.log('Resolving ref:', ref, 'against baseUri:', baseUri);
968
+ ```
969
+
970
+ ---
971
+
972
+ ## Related Documentation
973
+
974
+ For broader context on how this package fits into the JarenJS ecosystem:
975
+
976
+ - **Project-wide Architecture**: See `/ARCHITECTURE.md` at the project root
977
+ - **Developer Guide**: See `/HOWTO.md` at the project root
978
+ - **Core Package**: Depends on `@jarenjs/core` for fundamental utilities and `@jarenjs/json` for the JSON addressing standards
979
+
980
+ ---
981
+
982
+ ## Key Files Reference
983
+
984
+ | File | Purpose | Key Exports |
985
+ |------|---------|-------------|
986
+ | `index.js` | Public API | `JarenValidator`, `ValidationOptions`, `ValidatorOptions`, `ValidationError` |
987
+ | `schema.js` | Schema compilation | `compileSchemaObject` |
988
+ | `traverse.js` | Schema traversal | `TraverseOptions`, `storeSchemaIdsInMap`, `resolveRefSchemaDeep` |
989
+ | `tools.js` | Shared utilities | `isBoolOrObjectClass`, `hasSchemaRef`, `createIsSchemaTypeHandler` |
990
+ | `string.js` | String keywords | `compileStringBasic` |
991
+ | `number.js` | Number keywords | `compileNumberBasic` |
992
+ | `bigint.js` | BigInt keywords | `compileBigIntBasic` |
993
+ | `array.js` | Array keywords | `compileArraySchema` |
994
+ | `object.js` | Object keywords | `compileObjectSchema` |
995
+ | `combine.js` | Logic keywords | `compileCombineSchema` |
996
+ | `condition.js` | If/then/else | `compileConditionSchema` |
997
+ | `enum.js` | Enum/const | `compileEnumBasic` |
998
+ | `format.js` | Format registry | `registerFormatCompiler`, `compileFormatBasic` |
999
+ | `content.js` | Content encoding | `compileContentSchema` |
1000
+ | `data.js` | Data keyword | `compileDataSchema` |
1001
+ | `dollar-data.js` | $data keyword | `compileDollarDataSchema` |
1002
+ | `unevaluated.js` | unevaluated* keywords | `wrapUnevaluated` |
1003
+ | `dynamic-ref.js` | Dynamic scope helpers | `collectDynamicAnchorsDeep`, `hasRecursiveAnchor`, `getDynamicAnchorName` |
1004
+
1005
+ ---
1006
+
1007
+ ## Architectural Decisions
1008
+
1009
+ ### 1. Compilation over Interpretation
1010
+
1011
+ **Decision**: Compile schemas to functions rather than interpret them at runtime.
1012
+
1013
+ **Rationale**:
1014
+ - Eliminates schema traversal during validation
1015
+ - Enables V8 optimization of hot paths
1016
+ - Allows fast-path specialization
1017
+
1018
+ **Trade-off**: Higher memory usage for storing compiled functions.
1019
+
1020
+ ### 2. Pre-compiled References
1021
+
1022
+ **Decision**: Resolve all `$ref` at compile time, not validation time.
1023
+
1024
+ **Rationale**:
1025
+ - Eliminates recursive lookup overhead during validation
1026
+ - Flattens reference chains (a→b→c becomes a→c)
1027
+ - Enables circular reference detection at compile time
1028
+
1029
+ ### 3. Lazy Error Generation
1030
+
1031
+ **Decision**: Only create error objects when `skipErrors` is false.
1032
+
1033
+ **Rationale**:
1034
+ - Most production use cases only need boolean results
1035
+ - Error object creation is expensive
1036
+ - Reduces GC pressure during high-throughput validation
1037
+
1038
+ ### 4. Dual Data Reference Systems
1039
+
1040
+ **Decision**: Support both `$data` (Ajv-style) and `data` (json-everything-style) keywords.
1041
+
1042
+ **Rationale**:
1043
+ - Maximizes compatibility with existing schema ecosystems
1044
+ - Different use cases favor different reference styles
1045
+ - Minimal overhead when not used
1046
+
1047
+ ### 5. Annotation Tracking via a Shared Log
1048
+
1049
+ **Decision**: Implement `unevaluatedProperties`/`unevaluatedItems` with a per-root `EvalLog` of `(instance, key)` pairs and mark/rollback semantics, compiled in only when the schema set uses these keywords.
1050
+
1051
+ **Rationale**:
1052
+ - Instance-identity keying makes annotations flow correctly through `$ref` chains and recursion without threading context through every validator signature
1053
+ - mark/rollback gives failed applicator branches (anyOf/oneOf/not/if) exact annotation-discarding semantics
1054
+ - The compile-time feature scan keeps schemas without unevaluated* keywords completely free of tracking overhead
1055
+
1056
+ ### 6. Dynamic Scope as Per-Anchor Stacks
1057
+
1058
+ **Decision**: Track the dynamic scope for `$recursiveRef`/`$dynamicRef` as per-anchor-name validator stacks, pushed on resource entry (root validation and `$ref` crossings) and popped on exit.
1059
+
1060
+ **Rationale**:
1061
+ - Entering a resource registers ALL of its `$dynamicAnchor`s (collected once at compile time), matching the specification's resource-based dynamic scope
1062
+ - Outermost-first resolution is a bottom-of-stack read
1063
+ - Balanced push/pop via try/finally keeps the scope correct across validation failures
1064
+
1065
+ ---
1066
+
1067
+ *This document was generated for contributors to understand the architecture of @jarenjs/validate. For implementation details, refer to the source code and inline JSDoc comments.*