typebox 1.3.15 → 1.3.16

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.
@@ -4,7 +4,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
4
4
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
5
5
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
6
6
  };
7
- var _Stack_instances, _Stack_PushResourceAnchors, _Stack_PopResourceAnchors, _Stack_FromContext, _Stack_FromRef;
7
+ var _Stack_instances, _Stack_PushResourceAnchors, _Stack_PopResourceAnchors;
8
8
  import * as Schema from '../types/index.mjs';
9
9
  import { Guard as G } from '../../guard/index.mjs';
10
10
  import { Resolve } from '../resolve/index.mjs';
@@ -58,23 +58,29 @@ export class Stack {
58
58
  if (Schema.IsDynamicAnchor(schema))
59
59
  this.dynamicAnchors.pop();
60
60
  }
61
+ // ----------------------------------------------------------------
62
+ // Ref
63
+ // ----------------------------------------------------------------
61
64
  Ref(ref) {
62
- return __classPrivateFieldGet(this, _Stack_instances, "m", _Stack_FromContext).call(this, ref) ?? __classPrivateFieldGet(this, _Stack_instances, "m", _Stack_FromRef).call(this, ref);
65
+ const root = this.schema;
66
+ return !ref.$ref.startsWith('#')
67
+ ? Resolve.Ref(this.context, root, ref.$ref)
68
+ : Resolve.Ref(this.context, this.Base(), ref.$ref);
63
69
  }
64
70
  // ----------------------------------------------------------------
65
71
  // RecursiveRef
66
72
  // ----------------------------------------------------------------
67
73
  RecursiveRef(recursiveRef) {
68
74
  return Schema.IsRecursiveAnchorTrue(this.Base())
69
- ? Resolve.Ref(this.recursiveAnchors[0], recursiveRef.$recursiveRef)
70
- : Resolve.Ref(this.Base(), recursiveRef.$recursiveRef);
75
+ ? Resolve.Ref(this.context, this.recursiveAnchors[0], recursiveRef.$recursiveRef)
76
+ : Resolve.Ref(this.context, this.Base(), recursiveRef.$recursiveRef);
71
77
  }
72
78
  // ----------------------------------------------------------------
73
79
  // DynamicRef
74
80
  // ----------------------------------------------------------------
75
81
  DynamicRef(dynamicRef) {
76
82
  const root = this.schema;
77
- return Resolve.DynamicRef(root, this.Base(), dynamicRef, this.dynamicAnchors);
83
+ return Resolve.DynamicRef(this.context, root, this.Base(), dynamicRef, this.dynamicAnchors);
78
84
  }
79
85
  }
80
86
  _Stack_instances = new WeakSet(), _Stack_PushResourceAnchors = function _Stack_PushResourceAnchors(schema, isRoot = true) {
@@ -97,11 +103,4 @@ _Stack_instances = new WeakSet(), _Stack_PushResourceAnchors = function _Stack_P
97
103
  this.dynamicAnchors.pop();
98
104
  for (const key of G.Keys(current))
99
105
  __classPrivateFieldGet(this, _Stack_instances, "m", _Stack_PopResourceAnchors).call(this, current[key], false);
100
- }, _Stack_FromContext = function _Stack_FromContext(ref) {
101
- return G.HasPropertyKey(this.context, ref.$ref) ? this.context[ref.$ref] : undefined;
102
- }, _Stack_FromRef = function _Stack_FromRef(ref) {
103
- const root = this.schema;
104
- return !ref.$ref.startsWith('#')
105
- ? Resolve.Ref(root, ref.$ref)
106
- : Resolve.Ref(this.Base(), ref.$ref);
107
106
  };
@@ -1 +1,4 @@
1
- export * from './ref.mjs';
1
+ import * as Schema from '../types/index.mjs';
2
+ export declare const DefaultBase: URL;
3
+ export declare function Ref(context: Record<string, Schema.XSchema>, schema: Schema.XSchemaObject, ref: string): Schema.XSchema | undefined;
4
+ export declare function DynamicRef(context: Record<string, Schema.XSchema>, root: Schema.XSchemaObject, base: Schema.XSchemaObject, dynamicRef: Schema.XDynamicRef, dynamicAnchors: Schema.XDynamicAnchor[]): Schema.XSchema | undefined;
@@ -1 +1,202 @@
1
- export * from './ref.mjs';
1
+ // deno-fmt-ignore-file
2
+ import { Guard } from '../../guard/index.mjs';
3
+ import { Pointer } from '../pointer/index.mjs';
4
+ import * as Schema from '../types/index.mjs';
5
+ // ------------------------------------------------------------------
6
+ // DefaultBase
7
+ // ------------------------------------------------------------------
8
+ export const DefaultBase = new URL('https://json-schema.org');
9
+ // ------------------------------------------------------------------
10
+ // Match: Id
11
+ // ------------------------------------------------------------------
12
+ function MatchId(schema, base, ref) {
13
+ if (schema.$id === ref.hash)
14
+ return schema;
15
+ const absoluteId = new URL(schema.$id, base.href);
16
+ const absoluteRef = new URL(ref.href, base.href);
17
+ if (Guard.IsEqual(absoluteId.pathname, absoluteRef.pathname)) {
18
+ return ref.hash.startsWith('#') ? MatchHash(schema, base, ref) : schema;
19
+ }
20
+ return undefined;
21
+ }
22
+ // ------------------------------------------------------------------
23
+ // Match: Anchor
24
+ // ------------------------------------------------------------------
25
+ function MatchAnchor(schema, base, ref) {
26
+ const absoluteAnchor = new URL(`#${schema.$anchor}`, base.href);
27
+ const absoluteRef = new URL(ref.href, base.href);
28
+ return Guard.IsEqual(absoluteAnchor.href, absoluteRef.href) ? schema : undefined;
29
+ }
30
+ // ------------------------------------------------------------------
31
+ // Match: DynamicAnchor
32
+ // ------------------------------------------------------------------
33
+ function MatchDynamicAnchor(schema, base, ref) {
34
+ const absoluteAnchor = new URL(`#${schema.$dynamicAnchor}`, base.href);
35
+ const absoluteRef = new URL(ref.href, base.href);
36
+ return Guard.IsEqual(absoluteAnchor.href, absoluteRef.href) ? schema : undefined;
37
+ }
38
+ // ------------------------------------------------------------------
39
+ // Match: Hash
40
+ //
41
+ // Resolves JSON Pointer fragments only. Plain anchor-style fragments
42
+ // (no leading '/') are handled exclusively by MatchAnchor and
43
+ // MatchDynamicAnchor to prevent accidentally resolving an anchor name
44
+ // as a pointer into the schema tree.
45
+ //
46
+ // ------------------------------------------------------------------
47
+ function MatchHash(schema, _base, ref) {
48
+ if (ref.href.endsWith('#'))
49
+ return schema;
50
+ if (!ref.hash.startsWith('#'))
51
+ return undefined;
52
+ const fragment = decodeURIComponent(ref.hash.slice(1));
53
+ if (!fragment.startsWith('/'))
54
+ return undefined;
55
+ return Pointer.Get(schema, fragment);
56
+ }
57
+ // ------------------------------------------------------------------
58
+ // Match
59
+ // ------------------------------------------------------------------
60
+ function Match(schema, base, ref) {
61
+ if (Schema.IsId(schema)) {
62
+ const result = MatchId(schema, base, ref);
63
+ if (!Guard.IsUndefined(result))
64
+ return result;
65
+ }
66
+ if (Schema.IsAnchor(schema)) {
67
+ const result = MatchAnchor(schema, base, ref);
68
+ if (!Guard.IsUndefined(result))
69
+ return result;
70
+ }
71
+ if (Schema.IsDynamicAnchor(schema)) {
72
+ const result = MatchDynamicAnchor(schema, base, ref);
73
+ if (!Guard.IsUndefined(result))
74
+ return result;
75
+ }
76
+ return MatchHash(schema, base, ref);
77
+ }
78
+ // ------------------------------------------------------------------
79
+ // FromArray
80
+ // ------------------------------------------------------------------
81
+ function FromArray(schema, base, ref) {
82
+ return schema.reduce((result, item) => {
83
+ const match = FromValue(item, base, ref);
84
+ return !Guard.IsUndefined(match) ? match : result;
85
+ }, undefined);
86
+ }
87
+ // ------------------------------------------------------------------
88
+ // FromObject
89
+ // ------------------------------------------------------------------
90
+ function SkipProperty(key) {
91
+ // these are explicit data encoded in a schema, they should not
92
+ // be considered valid targets used for de-referencing.
93
+ return Guard.IsEqual(key, 'const') || Guard.IsEqual(key, 'enum');
94
+ }
95
+ function FromObject(schema, base, ref) {
96
+ return Guard.Keys(schema).reduce((result, key) => {
97
+ if (SkipProperty(key))
98
+ return result;
99
+ const match = FromValue(schema[key], base, ref);
100
+ return !Guard.IsUndefined(match) ? match : result;
101
+ }, undefined);
102
+ }
103
+ // ------------------------------------------------------------------
104
+ // FromValue
105
+ // ------------------------------------------------------------------
106
+ function FromValue(schema, base, ref) {
107
+ const nextBase = Schema.IsSchemaObject(schema) && Schema.IsId(schema)
108
+ ? new URL(schema.$id, base.href)
109
+ : base;
110
+ if (Schema.IsSchemaObject(schema)) {
111
+ const result = Match(schema, nextBase, ref);
112
+ if (!Guard.IsUndefined(result))
113
+ return result;
114
+ }
115
+ if (Guard.IsArray(schema))
116
+ return FromArray(schema, nextBase, ref);
117
+ if (Guard.IsObject(schema))
118
+ return FromObject(schema, nextBase, ref);
119
+ return undefined;
120
+ }
121
+ // ------------------------------------------------------------------
122
+ // CanonicalHref
123
+ //
124
+ // Returns the URL's href with its fragment removed, as a string.
125
+ // This is the canonical URI a document is identified and keyed by
126
+ // in context.
127
+ // ------------------------------------------------------------------
128
+ function CanonicalHref(url) {
129
+ return url.href.split('#')[0];
130
+ }
131
+ // ------------------------------------------------------------------
132
+ // RefContext: Phase 1
133
+ //
134
+ // A ContextRef targets a simple key name in the context and is
135
+ // used for fast resolution of inline referential TypeBox types.
136
+ // ------------------------------------------------------------------
137
+ function RefContext(context, ref) {
138
+ return Guard.HasPropertyKey(context, ref) ? context[ref] : undefined;
139
+ }
140
+ // ------------------------------------------------------------------
141
+ // RefLocal: Phase 2
142
+ //
143
+ // Resolves the ref's fragment within the current schema resource,
144
+ // either as a JSON Pointer or a plain-name fragment ($anchor,
145
+ // $dynamicAnchor), per the JSON Schema spec.
146
+ // ------------------------------------------------------------------
147
+ function RefLocal(schema, base, ref) {
148
+ return FromValue(schema, base, ref);
149
+ }
150
+ // ------------------------------------------------------------------
151
+ // RefRemote: Phase 3
152
+ //
153
+ // Locates the schema resource identified by the ref's base URI, then
154
+ // resolves the ref's fragment within that resource, either as a JSON
155
+ // Pointer or a plain-name fragment, per the JSON Schema spec.
156
+ // ------------------------------------------------------------------
157
+ function RefRemote(context, base, ref) {
158
+ const targetDocumentHref = CanonicalHref(ref);
159
+ if (Guard.IsEqual(targetDocumentHref, CanonicalHref(base)))
160
+ return undefined;
161
+ if (!Guard.HasPropertyKey(context, targetDocumentHref))
162
+ return undefined;
163
+ const remoteSchema = context[targetDocumentHref];
164
+ const remoteBase = (Schema.IsSchemaObject(remoteSchema) && Schema.IsId(remoteSchema)) ? new URL(remoteSchema.$id, DefaultBase.href) : new URL(targetDocumentHref);
165
+ return FromValue(remoteSchema, remoteBase, ref);
166
+ }
167
+ // ------------------------------------------------------------------
168
+ // Ref
169
+ // ------------------------------------------------------------------
170
+ export function Ref(context, schema, ref) {
171
+ const initialBase = Schema.IsId(schema) ? new URL(schema.$id, DefaultBase.href) : DefaultBase;
172
+ const initialRef = new URL(ref, initialBase.href);
173
+ return RefContext(context, ref) ?? RefLocal(schema, initialBase, initialRef) ?? RefRemote(context, initialBase, initialRef);
174
+ }
175
+ // ------------------------------------------------------------------
176
+ // DynamicRef
177
+ // ------------------------------------------------------------------
178
+ export function DynamicRef(context, root, base, dynamicRef, dynamicAnchors) {
179
+ // Resolve the static target using either the local base (for fragment‑only references)
180
+ // or the document root (for absolute URI references).
181
+ const fragmentTarget = dynamicRef.$dynamicRef.startsWith('#')
182
+ ? Ref(context, base, dynamicRef.$dynamicRef)
183
+ : Ref(context, root, dynamicRef.$dynamicRef);
184
+ if (Guard.IsUndefined(fragmentTarget))
185
+ return undefined;
186
+ // Dynamic override only applies if the resolved target itself declares a $dynamicAnchor.
187
+ // If it does not, return the static target unchanged.
188
+ if (!Schema.IsSchemaObject(fragmentTarget) || !Schema.IsDynamicAnchor(fragmentTarget))
189
+ return fragmentTarget;
190
+ // Extract the fragment portion of the reference. According to the test suite,
191
+ // only plain fragment names (e.g., "#foo") trigger the dynamic scope; JSON
192
+ // Pointer fragments (e.g., "#/definitions/foo") bypass dynamic resolution.
193
+ const fragment = new URL(dynamicRef.$dynamicRef, DefaultBase).hash;
194
+ if (fragment.startsWith('#/'))
195
+ return fragmentTarget;
196
+ // Search the live dynamic anchor stack for a schema whose $dynamicAnchor matches the
197
+ // target's $dynamicAnchor. The stack reflects the current evaluation path, and
198
+ // find() returns the outermost (first encountered) match, which is the correct
199
+ // lexical scope per the specification.
200
+ const anchorTarget = dynamicAnchors.find(anchor => anchor.$dynamicAnchor === fragmentTarget.$dynamicAnchor);
201
+ return anchorTarget ?? fragmentTarget;
202
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "typebox",
3
3
  "description": "Json Schema Type Builder with Static Type Resolution for TypeScript",
4
- "version": "1.3.15",
4
+ "version": "1.3.16",
5
5
  "keywords": [
6
6
  "typescript",
7
7
  "jsonschema"
package/readme.md CHANGED
@@ -231,7 +231,7 @@ const result = Vector.Parse({ x: 1, y: 0, z: 0 }) // const result: {
231
231
 
232
232
  [JSON Schema Test Suite](https://github.com/json-schema-org/JSON-Schema-Test-Suite) | [JSON Schema Compliance Suite](https://github.com/sinclairzx81/json-schema-compliance-suite)
233
233
 
234
- TypeBox supports all major JSON Schema draft versions and tracks compliance against the official JSON Schema Test Suite. It also maintains a separate JavaScript compliance suite to track ecosystem adoption as JSON Schema moves toward the Version 1 candidate. The following table shows TypeBox specification coverage.
234
+ TypeBox supports all major JSON Schema draft versions and tracks compliance against the official JSON Schema Test Suite. It also maintains a separate JavaScript compliance suite to track ecosystem adoption as JSON Schema moves toward the V1 candidate. The following table shows TypeBox specification coverage.
235
235
 
236
236
  | Spec | 3 | 4 | 6 | 7 | 2019-09 | 2020-12 | v1 |
237
237
  |:-----|:--|:--|:--|:--|:--|:--|:--|
@@ -248,7 +248,7 @@ TypeBox supports all major JSON Schema draft versions and tracks compliance agai
248
248
  | dependencies | 17/18 | ✅ | ✅ | ✅ | - | - | - |
249
249
  | dependentRequired | - | - | - | - | ✅ | ✅ | ✅ |
250
250
  | dependentSchemas | - | - | - | - | ✅ | ✅ | ✅ |
251
- | dynamicRef | - | - | - | - | - | 38/44 | 19/27 |
251
+ | dynamicRef | - | - | - | - | - | 40/44 | 21/27 |
252
252
  | enum | 14/16 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
253
253
  | exclusiveMaximum | - | - | ✅ | ✅ | ✅ | ✅ | ✅ |
254
254
  | exclusiveMinimum | - | - | ✅ | ✅ | ✅ | ✅ | ✅ |
@@ -274,7 +274,7 @@ TypeBox supports all major JSON Schema draft versions and tracks compliance agai
274
274
  | properties | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
275
275
  | propertyNames | - | - | ✅ | ✅ | ✅ | ✅ | ✅ |
276
276
  | recursiveRef | - | - | - | - | ✅ | - | - |
277
- | ref | 22/27 | 37/45 | 67/70 | 75/78 | 79/81 | 77/79 | 77/79 |
277
+ | ref | 22/27 | 37/45 | 67/70 | 75/78 | 79/81 | 77/79 | 78/79 |
278
278
  | required | 3/4 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
279
279
  | type | 73/80 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
280
280
  | unevaluatedItems | - | - | - | - | ✅ | ✅ | 70/71 |
@@ -283,40 +283,40 @@ TypeBox supports all major JSON Schema draft versions and tracks compliance agai
283
283
 
284
284
  ### Performance
285
285
 
286
- TypeBox tracks comparative performance against AJV8 as the de facto JSON Schema performance standard. For broader comparative benchmarks, refer to the community maintained projects below.
286
+ TypeBox tracks comparative performance against AJV8 as the de facto performance standard. For broader comparative benchmarks, refer to the community maintained projects below.
287
287
 
288
288
  [Runtime Benchmarks](https://moltar.github.io/typescript-runtime-type-benchmarks/) | [Schema Benchmarks](https://schemabenchmarks.dev/)
289
289
 
290
290
  The following table shows compilation performance for various JSON Schema structures. These benchmarks measure the time required to JIT compile schematics, with faster compilation resulting in faster application startup.
291
291
 
292
292
  ```python
293
- ┌──────────────────────┬─────────────┬─────────────┐
294
- │ Compile │ TB1X │ AJV8
295
- ├──────────────────────┼─────────────┼─────────────┤
296
- │ Boolean │ 39.7K ops/s │ 6.8K ops/s │
297
- │ Number │ 86.2K ops/s │ 7.5K ops/s │
298
- │ String │ 82.6K ops/s │ 8.2K ops/s │
299
- │ Null │ 62K ops/s │ 7.3K ops/s │
300
- │ Literal_String │ 72.1K ops/s │ 5.7K ops/s │
301
- │ Literal_Number │ 72.7K ops/s │ 6.9K ops/s │
302
- │ Literal_Boolean │ 80.9K ops/s │ 7K ops/s │
303
- │ Pattern │ 38.7K ops/s │ 5.7K ops/s │
304
- │ Object_Open │ 16.3K ops/s │ 1.3K ops/s │
305
- │ Object_Close │ 15.7K ops/s │ 952 ops/s │
306
- │ Object_Vector3 │ 40.2K ops/s │ 3.2K ops/s │
307
- │ Object_Basis3 │ 16.1K ops/s │ 834 ops/s │
308
- │ Intersect_And │ 45.7K ops/s │ 3.5K ops/s │
309
- │ Intersect_Structural │ 21K ops/s │ 1.6K ops/s │
310
- │ Union_Or │ 45.1K ops/s │ 3.4K ops/s │
311
- │ Union_Structural │ 27.6K ops/s │ 2K ops/s │
312
- │ Tuple_Values │ 14.7K ops/s │ 2K ops/s │
313
- │ Tuple_Objects │ 3.7K ops/s │ 380 ops/s │
314
- │ Array_Numbers_4 │ 73.6K ops/s │ 4.3K ops/s │
315
- │ Array_Numbers_8 │ 50.9K ops/s │ 3.8K ops/s │
316
- │ Array_Numbers_16 │ 85K ops/s │ 3.9K ops/s │
317
- │ Array_Objects_Open │ 18.9K ops/s │ 789 ops/s │
318
- │ Array_Objects_Close │ 17.9K ops/s │ 909 ops/s │
319
- └──────────────────────┴─────────────┴─────────────┘
293
+ ┌──────────────────────┬──────────────┬──────────────┐
294
+ │ Compile │ TB1X │ AJV8
295
+ ├──────────────────────┼──────────────┼──────────────┤
296
+ │ Boolean │ 50.4K ops/s │ 7.1K ops/s │
297
+ │ Number │ 129.9K ops/s │ 7.9K ops/s │
298
+ │ String │ 128.9K ops/s │ 9.1K ops/s │
299
+ │ Null │ 91.3K ops/s │ 8.4K ops/s │
300
+ │ Literal_String │ 63.1K ops/s │ 7.4K ops/s │
301
+ │ Literal_Number │ 74.5K ops/s │ 7.6K ops/s │
302
+ │ Literal_Boolean │ 143.3K ops/s │ 7.7K ops/s │
303
+ │ Pattern │ 94.2K ops/s │ 6.4K ops/s │
304
+ │ Object_Open │ 19.4K ops/s │ 1.3K ops/s │
305
+ │ Object_Close │ 17.6K ops/s │ 991 ops/s │
306
+ │ Object_Vector3 │ 46.1K ops/s │ 3.3K ops/s │
307
+ │ Object_Basis3 │ 16K ops/s │ 848 ops/s │
308
+ │ Intersect_And │ 41.3K ops/s │ 4.2K ops/s │
309
+ │ Intersect_Structural │ 25.3K ops/s │ 1.5K ops/s │
310
+ │ Union_Or │ 58.6K ops/s │ 2.4K ops/s │
311
+ │ Union_Structural │ 31.5K ops/s │ 1.8K ops/s │
312
+ │ Tuple_Values │ 18.2K ops/s │ 1.9K ops/s │
313
+ │ Tuple_Objects │ 3.9K ops/s │ 437 ops/s │
314
+ │ Array_Numbers_4 │ 114.5K ops/s │ 4.2K ops/s │
315
+ │ Array_Numbers_8 │ 128.3K ops/s │ 3.9K ops/s │
316
+ │ Array_Numbers_16 │ 128.4K ops/s │ 4K ops/s │
317
+ │ Array_Objects_Open │ 22K ops/s │ 780 ops/s │
318
+ │ Array_Objects_Close │ 22K ops/s │ 1K ops/s │
319
+ └──────────────────────┴──────────────┴──────────────┘
320
320
  ```
321
321
 
322
322
  The following tables shows validation performance for various JSON Schema structures. These benchmarks measure overall validation throughput for JIT compiled schematics.
@@ -1,4 +0,0 @@
1
- import * as Schema from '../types/index.mjs';
2
- export declare const DefaultBase: URL;
3
- export declare function Ref(schema: Schema.XSchemaObject, ref: string): Schema.XSchema | undefined;
4
- export declare function DynamicRef(root: Schema.XSchemaObject, base: Schema.XSchemaObject, dynamicRef: Schema.XDynamicRef, dynamicAnchors: Schema.XDynamicAnchor[]): Schema.XSchema | undefined;
@@ -1,156 +0,0 @@
1
- // deno-fmt-ignore-file
2
- import { Guard } from '../../guard/index.mjs';
3
- import { Pointer } from '../pointer/index.mjs';
4
- import * as Schema from '../types/index.mjs';
5
- // ------------------------------------------------------------------
6
- // DefaultBase
7
- // ------------------------------------------------------------------
8
- export const DefaultBase = new URL('https://json-schema.org');
9
- // ------------------------------------------------------------------
10
- // Match: Id
11
- // ------------------------------------------------------------------
12
- function MatchId(schema, base, ref) {
13
- if (schema.$id === ref.hash)
14
- return schema;
15
- const absoluteId = new URL(schema.$id, base.href);
16
- const absoluteRef = new URL(ref.href, base.href);
17
- if (Guard.IsEqual(absoluteId.pathname, absoluteRef.pathname)) {
18
- return ref.hash.startsWith('#') ? MatchHash(schema, base, ref) : schema;
19
- }
20
- return undefined;
21
- }
22
- // ------------------------------------------------------------------
23
- // Match: Anchor
24
- // ------------------------------------------------------------------
25
- function MatchAnchor(schema, base, ref) {
26
- const absoluteAnchor = new URL(`#${schema.$anchor}`, base.href);
27
- const absoluteRef = new URL(ref.href, base.href);
28
- return Guard.IsEqual(absoluteAnchor.href, absoluteRef.href) ? schema : undefined;
29
- }
30
- // ------------------------------------------------------------------
31
- // Match: DynamicAnchor
32
- // ------------------------------------------------------------------
33
- function MatchDynamicAnchor(schema, base, ref) {
34
- const absoluteAnchor = new URL(`#${schema.$dynamicAnchor}`, base.href);
35
- const absoluteRef = new URL(ref.href, base.href);
36
- return Guard.IsEqual(absoluteAnchor.href, absoluteRef.href) ? schema : undefined;
37
- }
38
- // ------------------------------------------------------------------
39
- // Match: Hash
40
- //
41
- // Resolves JSON Pointer fragments only. Plain anchor-style fragments
42
- // (no leading '/') are handled exclusively by MatchAnchor and
43
- // MatchDynamicAnchor to prevent accidentally resolving an anchor name
44
- // as a pointer into the schema tree.
45
- //
46
- // ------------------------------------------------------------------
47
- function MatchHash(schema, _base, ref) {
48
- if (ref.href.endsWith('#'))
49
- return schema;
50
- if (!ref.hash.startsWith('#'))
51
- return undefined;
52
- const fragment = decodeURIComponent(ref.hash.slice(1));
53
- if (!fragment.startsWith('/'))
54
- return undefined;
55
- return Pointer.Get(schema, fragment);
56
- }
57
- // ------------------------------------------------------------------
58
- // Match
59
- // ------------------------------------------------------------------
60
- function Match(schema, base, ref) {
61
- if (Schema.IsId(schema)) {
62
- const result = MatchId(schema, base, ref);
63
- if (!Guard.IsUndefined(result))
64
- return result;
65
- }
66
- if (Schema.IsAnchor(schema)) {
67
- const result = MatchAnchor(schema, base, ref);
68
- if (!Guard.IsUndefined(result))
69
- return result;
70
- }
71
- if (Schema.IsDynamicAnchor(schema)) {
72
- const result = MatchDynamicAnchor(schema, base, ref);
73
- if (!Guard.IsUndefined(result))
74
- return result;
75
- }
76
- return MatchHash(schema, base, ref);
77
- }
78
- // ------------------------------------------------------------------
79
- // FromArray
80
- // ------------------------------------------------------------------
81
- function FromArray(schema, base, ref) {
82
- return schema.reduce((result, item) => {
83
- const match = FromValue(item, base, ref);
84
- return !Guard.IsUndefined(match) ? match : result;
85
- }, undefined);
86
- }
87
- // ------------------------------------------------------------------
88
- // FromObject
89
- // ------------------------------------------------------------------
90
- function SkipProperty(key) {
91
- // these are explicit data encoded in a schema, they should not
92
- // be considered valid targets used for de-referencing.
93
- return Guard.IsEqual(key, 'const') || Guard.IsEqual(key, 'enum');
94
- }
95
- function FromObject(schema, base, ref) {
96
- return Guard.Keys(schema).reduce((result, key) => {
97
- if (SkipProperty(key))
98
- return result;
99
- const match = FromValue(schema[key], base, ref);
100
- return !Guard.IsUndefined(match) ? match : result;
101
- }, undefined);
102
- }
103
- // ------------------------------------------------------------------
104
- // FromValue
105
- // ------------------------------------------------------------------
106
- function FromValue(schema, base, ref) {
107
- const nextBase = Schema.IsSchemaObject(schema) && Schema.IsId(schema)
108
- ? new URL(schema.$id, base.href)
109
- : base;
110
- if (Schema.IsSchemaObject(schema)) {
111
- const result = Match(schema, nextBase, ref);
112
- if (!Guard.IsUndefined(result))
113
- return result;
114
- }
115
- if (Guard.IsArray(schema))
116
- return FromArray(schema, nextBase, ref);
117
- if (Guard.IsObject(schema))
118
- return FromObject(schema, nextBase, ref);
119
- return undefined;
120
- }
121
- // ------------------------------------------------------------------
122
- // Ref
123
- // ------------------------------------------------------------------
124
- export function Ref(schema, ref) {
125
- const initialBase = Schema.IsId(schema) ? new URL(schema.$id, DefaultBase.href) : DefaultBase;
126
- const initialRef = new URL(ref, initialBase.href);
127
- return FromValue(schema, initialBase, initialRef);
128
- }
129
- // ------------------------------------------------------------------
130
- // DynamicRef
131
- // ------------------------------------------------------------------
132
- export function DynamicRef(root, base, dynamicRef, dynamicAnchors) {
133
- // Resolve the static target using either the local base (for fragment‑only references)
134
- // or the document root (for absolute URI references).
135
- const fragmentTarget = dynamicRef.$dynamicRef.startsWith('#')
136
- ? Ref(base, dynamicRef.$dynamicRef)
137
- : Ref(root, dynamicRef.$dynamicRef);
138
- if (Guard.IsUndefined(fragmentTarget))
139
- return undefined;
140
- // Dynamic override only applies if the resolved target itself declares a $dynamicAnchor.
141
- // If it does not, return the static target unchanged.
142
- if (!Schema.IsSchemaObject(fragmentTarget) || !Schema.IsDynamicAnchor(fragmentTarget))
143
- return fragmentTarget;
144
- // Extract the fragment portion of the reference. According to the test suite,
145
- // only plain fragment names (e.g., "#foo") trigger the dynamic scope; JSON
146
- // Pointer fragments (e.g., "#/definitions/foo") bypass dynamic resolution.
147
- const fragment = new URL(dynamicRef.$dynamicRef, DefaultBase).hash;
148
- if (fragment.startsWith('#/'))
149
- return fragmentTarget;
150
- // Search the live dynamic anchor stack for a schema whose $dynamicAnchor matches the
151
- // target's $dynamicAnchor. The stack reflects the current evaluation path, and
152
- // find() returns the outermost (first encountered) match, which is the correct
153
- // lexical scope per the specification.
154
- const anchorTarget = dynamicAnchors.find(anchor => anchor.$dynamicAnchor === fragmentTarget.$dynamicAnchor);
155
- return anchorTarget ?? fragmentTarget;
156
- }