@hey-api/json-schema-ref-parser 0.0.1

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 (68) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +138 -0
  3. package/dist/lib/bundle.d.ts +26 -0
  4. package/dist/lib/bundle.js +293 -0
  5. package/dist/lib/dereference.d.ts +11 -0
  6. package/dist/lib/dereference.js +224 -0
  7. package/dist/lib/index.d.ts +74 -0
  8. package/dist/lib/index.js +208 -0
  9. package/dist/lib/options.d.ts +61 -0
  10. package/dist/lib/options.js +45 -0
  11. package/dist/lib/parse.d.ts +13 -0
  12. package/dist/lib/parse.js +87 -0
  13. package/dist/lib/parsers/binary.d.ts +2 -0
  14. package/dist/lib/parsers/binary.js +12 -0
  15. package/dist/lib/parsers/json.d.ts +2 -0
  16. package/dist/lib/parsers/json.js +38 -0
  17. package/dist/lib/parsers/text.d.ts +2 -0
  18. package/dist/lib/parsers/text.js +18 -0
  19. package/dist/lib/parsers/yaml.d.ts +2 -0
  20. package/dist/lib/parsers/yaml.js +28 -0
  21. package/dist/lib/pointer.d.ts +88 -0
  22. package/dist/lib/pointer.js +293 -0
  23. package/dist/lib/ref.d.ts +180 -0
  24. package/dist/lib/ref.js +226 -0
  25. package/dist/lib/refs.d.ts +127 -0
  26. package/dist/lib/refs.js +232 -0
  27. package/dist/lib/resolve-external.d.ts +13 -0
  28. package/dist/lib/resolve-external.js +147 -0
  29. package/dist/lib/resolvers/file.d.ts +4 -0
  30. package/dist/lib/resolvers/file.js +61 -0
  31. package/dist/lib/resolvers/url.d.ts +11 -0
  32. package/dist/lib/resolvers/url.js +57 -0
  33. package/dist/lib/types/index.d.ts +43 -0
  34. package/dist/lib/types/index.js +2 -0
  35. package/dist/lib/util/convert-path-to-posix.d.ts +1 -0
  36. package/dist/lib/util/convert-path-to-posix.js +14 -0
  37. package/dist/lib/util/errors.d.ts +56 -0
  38. package/dist/lib/util/errors.js +112 -0
  39. package/dist/lib/util/is-windows.d.ts +1 -0
  40. package/dist/lib/util/is-windows.js +6 -0
  41. package/dist/lib/util/plugins.d.ts +16 -0
  42. package/dist/lib/util/plugins.js +45 -0
  43. package/dist/lib/util/url.d.ts +79 -0
  44. package/dist/lib/util/url.js +285 -0
  45. package/dist/vite.config.d.ts +2 -0
  46. package/dist/vite.config.js +18 -0
  47. package/lib/bundle.ts +299 -0
  48. package/lib/dereference.ts +286 -0
  49. package/lib/index.ts +209 -0
  50. package/lib/options.ts +108 -0
  51. package/lib/parse.ts +56 -0
  52. package/lib/parsers/binary.ts +13 -0
  53. package/lib/parsers/json.ts +39 -0
  54. package/lib/parsers/text.ts +21 -0
  55. package/lib/parsers/yaml.ts +26 -0
  56. package/lib/pointer.ts +327 -0
  57. package/lib/ref.ts +279 -0
  58. package/lib/refs.ts +239 -0
  59. package/lib/resolve-external.ts +141 -0
  60. package/lib/resolvers/file.ts +24 -0
  61. package/lib/resolvers/url.ts +78 -0
  62. package/lib/types/index.ts +51 -0
  63. package/lib/util/convert-path-to-posix.ts +11 -0
  64. package/lib/util/errors.ts +153 -0
  65. package/lib/util/is-windows.ts +2 -0
  66. package/lib/util/plugins.ts +56 -0
  67. package/lib/util/url.ts +266 -0
  68. package/package.json +96 -0
package/lib/pointer.ts ADDED
@@ -0,0 +1,327 @@
1
+ import type { ParserOptions } from "./options.js";
2
+
3
+ import $Ref from "./ref.js";
4
+ import * as url from "./util/url.js";
5
+ import { JSONParserError, InvalidPointerError, MissingPointerError, isHandledError } from "./util/errors.js";
6
+ import type { JSONSchema } from "./types";
7
+
8
+ const slashes = /\//g;
9
+ const tildes = /~/g;
10
+ const escapedSlash = /~1/g;
11
+ const escapedTilde = /~0/g;
12
+
13
+ const safeDecodeURIComponent = (encodedURIComponent: string): string => {
14
+ try {
15
+ return decodeURIComponent(encodedURIComponent);
16
+ } catch {
17
+ return encodedURIComponent;
18
+ }
19
+ };
20
+
21
+ /**
22
+ * This class represents a single JSON pointer and its resolved value.
23
+ *
24
+ * @param $ref
25
+ * @param path
26
+ * @param [friendlyPath] - The original user-specified path (used for error messages)
27
+ * @class
28
+ */
29
+ class Pointer<S extends object = JSONSchema> {
30
+ /**
31
+ * The {@link $Ref} object that contains this {@link Pointer} object.
32
+ */
33
+ $ref: $Ref<S>;
34
+
35
+ /**
36
+ * The file path or URL, containing the JSON pointer in the hash.
37
+ * This path is relative to the path of the main JSON schema file.
38
+ */
39
+ path: string;
40
+
41
+ /**
42
+ * The original path or URL, used for error messages.
43
+ */
44
+ originalPath: string;
45
+
46
+ /**
47
+ * The value of the JSON pointer.
48
+ * Can be any JSON type, not just objects. Unknown file types are represented as Buffers (byte arrays).
49
+ */
50
+
51
+ value: any;
52
+ /**
53
+ * Indicates whether the pointer references itself.
54
+ */
55
+ circular: boolean;
56
+ /**
57
+ * The number of indirect references that were traversed to resolve the value.
58
+ * Resolving a single pointer may require resolving multiple $Refs.
59
+ */
60
+ indirections: number;
61
+
62
+ constructor($ref: $Ref<S>, path: string, friendlyPath?: string) {
63
+ this.$ref = $ref;
64
+
65
+ this.path = path;
66
+
67
+ this.originalPath = friendlyPath || path;
68
+
69
+ this.value = undefined;
70
+
71
+ this.circular = false;
72
+
73
+ this.indirections = 0;
74
+ }
75
+
76
+ /**
77
+ * Resolves the value of a nested property within the given object.
78
+ *
79
+ * @param obj - The object that will be crawled
80
+ * @param options
81
+ * @param pathFromRoot - the path of place that initiated resolving
82
+ *
83
+ * @returns
84
+ * Returns a JSON pointer whose {@link Pointer#value} is the resolved value.
85
+ * If resolving this value required resolving other JSON references, then
86
+ * the {@link Pointer#$ref} and {@link Pointer#path} will reflect the resolution path
87
+ * of the resolved value.
88
+ */
89
+ resolve(obj: S, options?: ParserOptions, pathFromRoot?: string) {
90
+ const tokens = Pointer.parse(this.path, this.originalPath);
91
+
92
+ // Crawl the object, one token at a time
93
+ this.value = unwrapOrThrow(obj);
94
+
95
+ for (let i = 0; i < tokens.length; i++) {
96
+ if (resolveIf$Ref(this, options, pathFromRoot)) {
97
+ // The $ref path has changed, so append the remaining tokens to the path
98
+ this.path = Pointer.join(this.path, tokens.slice(i));
99
+ }
100
+
101
+ if (typeof this.value === "object" && this.value !== null && !isRootPath(pathFromRoot) && "$ref" in this.value) {
102
+ return this;
103
+ }
104
+
105
+ const token = tokens[i];
106
+ if (this.value[token] === undefined || (this.value[token] === null && i === tokens.length - 1)) {
107
+ // one final case is if the entry itself includes slashes, and was parsed out as a token - we can join the remaining tokens and try again
108
+ let didFindSubstringSlashMatch = false;
109
+ for (let j = tokens.length - 1; j > i; j--) {
110
+ const joinedToken = tokens.slice(i, j + 1).join("/");
111
+ if (this.value[joinedToken] !== undefined) {
112
+ this.value = this.value[joinedToken];
113
+ i = j;
114
+ didFindSubstringSlashMatch = true;
115
+ break;
116
+ }
117
+ }
118
+ if (didFindSubstringSlashMatch) {
119
+ continue;
120
+ }
121
+
122
+ this.value = null;
123
+ throw new MissingPointerError(token, decodeURI(this.originalPath));
124
+ } else {
125
+ this.value = this.value[token];
126
+ }
127
+ }
128
+
129
+ // Resolve the final value
130
+ if (!this.value || (this.value.$ref && url.resolve(this.path, this.value.$ref) !== pathFromRoot)) {
131
+ resolveIf$Ref(this, options, pathFromRoot);
132
+ }
133
+
134
+ return this;
135
+ }
136
+
137
+ /**
138
+ * Sets the value of a nested property within the given object.
139
+ *
140
+ * @param obj - The object that will be crawled
141
+ * @param value - the value to assign
142
+ * @param options
143
+ *
144
+ * @returns
145
+ * Returns the modified object, or an entirely new object if the entire object is overwritten.
146
+ */
147
+ set(obj: S, value: any, options?: ParserOptions) {
148
+ const tokens = Pointer.parse(this.path);
149
+ let token;
150
+
151
+ if (tokens.length === 0) {
152
+ // There are no tokens, replace the entire object with the new value
153
+ this.value = value;
154
+ return value;
155
+ }
156
+
157
+ // Crawl the object, one token at a time
158
+ this.value = unwrapOrThrow(obj);
159
+
160
+ for (let i = 0; i < tokens.length - 1; i++) {
161
+ resolveIf$Ref(this, options);
162
+
163
+ token = tokens[i];
164
+ if (this.value && this.value[token] !== undefined) {
165
+ // The token exists
166
+ this.value = this.value[token];
167
+ } else {
168
+ // The token doesn't exist, so create it
169
+ this.value = setValue(this, token, {});
170
+ }
171
+ }
172
+
173
+ // Set the value of the final token
174
+ resolveIf$Ref(this, options);
175
+ token = tokens[tokens.length - 1];
176
+ setValue(this, token, value);
177
+
178
+ // Return the updated object
179
+ return obj;
180
+ }
181
+
182
+ /**
183
+ * Parses a JSON pointer (or a path containing a JSON pointer in the hash)
184
+ * and returns an array of the pointer's tokens.
185
+ * (e.g. "schema.json#/definitions/person/name" => ["definitions", "person", "name"])
186
+ *
187
+ * The pointer is parsed according to RFC 6901
188
+ * {@link https://tools.ietf.org/html/rfc6901#section-3}
189
+ *
190
+ * @param path
191
+ * @param [originalPath]
192
+ * @returns
193
+ */
194
+ static parse(path: string, originalPath?: string): string[] {
195
+ // Get the JSON pointer from the path's hash
196
+ const pointer = url.getHash(path).substring(1);
197
+
198
+ // If there's no pointer, then there are no tokens,
199
+ // so return an empty array
200
+ if (!pointer) {
201
+ return [];
202
+ }
203
+
204
+ // Split into an array
205
+ const split = pointer.split("/");
206
+
207
+ // Decode each part, according to RFC 6901
208
+ for (let i = 0; i < split.length; i++) {
209
+ split[i] = safeDecodeURIComponent(split[i].replace(escapedSlash, "/").replace(escapedTilde, "~"));
210
+ }
211
+
212
+ if (split[0] !== "") {
213
+ throw new InvalidPointerError(pointer, originalPath === undefined ? path : originalPath);
214
+ }
215
+
216
+ return split.slice(1);
217
+ }
218
+
219
+ /**
220
+ * Creates a JSON pointer path, by joining one or more tokens to a base path.
221
+ *
222
+ * @param base - The base path (e.g. "schema.json#/definitions/person")
223
+ * @param tokens - The token(s) to append (e.g. ["name", "first"])
224
+ * @returns
225
+ */
226
+ static join(base: string, tokens: string | string[]) {
227
+ // Ensure that the base path contains a hash
228
+ if (base.indexOf("#") === -1) {
229
+ base += "#";
230
+ }
231
+
232
+ // Append each token to the base path
233
+ tokens = Array.isArray(tokens) ? tokens : [tokens];
234
+ for (let i = 0; i < tokens.length; i++) {
235
+ const token = tokens[i];
236
+ // Encode the token, according to RFC 6901
237
+ base += "/" + encodeURIComponent(token.replace(tildes, "~0").replace(slashes, "~1"));
238
+ }
239
+
240
+ return base;
241
+ }
242
+ }
243
+
244
+ /**
245
+ * If the given pointer's {@link Pointer#value} is a JSON reference,
246
+ * then the reference is resolved and {@link Pointer#value} is replaced with the resolved value.
247
+ * In addition, {@link Pointer#path} and {@link Pointer#$ref} are updated to reflect the
248
+ * resolution path of the new value.
249
+ *
250
+ * @param pointer
251
+ * @param options
252
+ * @param [pathFromRoot] - the path of place that initiated resolving
253
+ * @returns - Returns `true` if the resolution path changed
254
+ */
255
+ function resolveIf$Ref(pointer: any, options: any, pathFromRoot?: any) {
256
+ // Is the value a JSON reference? (and allowed?)
257
+
258
+ if ($Ref.isAllowed$Ref(pointer.value)) {
259
+ const $refPath = url.resolve(pointer.path, pointer.value.$ref);
260
+
261
+ if ($refPath === pointer.path && !isRootPath(pathFromRoot)) {
262
+ // The value is a reference to itself, so there's nothing to do.
263
+ pointer.circular = true;
264
+ } else {
265
+ const resolved = pointer.$ref.$refs._resolve($refPath, pointer.path, options);
266
+ if (resolved === null) {
267
+ return false;
268
+ }
269
+
270
+ pointer.indirections += resolved.indirections + 1;
271
+
272
+ if ($Ref.isExtended$Ref(pointer.value)) {
273
+ // This JSON reference "extends" the resolved value, rather than simply pointing to it.
274
+ // So the resolved path does NOT change. Just the value does.
275
+ pointer.value = $Ref.dereference(pointer.value, resolved.value);
276
+ return false;
277
+ } else {
278
+ // Resolve the reference
279
+ pointer.$ref = resolved.$ref;
280
+ pointer.path = resolved.path;
281
+ pointer.value = resolved.value;
282
+ }
283
+
284
+ return true;
285
+ }
286
+ }
287
+ return undefined;
288
+ }
289
+ export default Pointer;
290
+
291
+ /**
292
+ * Sets the specified token value of the {@link Pointer#value}.
293
+ *
294
+ * The token is evaluated according to RFC 6901.
295
+ * {@link https://tools.ietf.org/html/rfc6901#section-4}
296
+ *
297
+ * @param pointer - The JSON Pointer whose value will be modified
298
+ * @param token - A JSON Pointer token that indicates how to modify `obj`
299
+ * @param value - The value to assign
300
+ * @returns - Returns the assigned value
301
+ */
302
+ function setValue(pointer: any, token: any, value: any) {
303
+ if (pointer.value && typeof pointer.value === "object") {
304
+ if (token === "-" && Array.isArray(pointer.value)) {
305
+ pointer.value.push(value);
306
+ } else {
307
+ pointer.value[token] = value;
308
+ }
309
+ } else {
310
+ throw new JSONParserError(
311
+ `Error assigning $ref pointer "${pointer.path}". \nCannot set "${token}" of a non-object.`,
312
+ );
313
+ }
314
+ return value;
315
+ }
316
+
317
+ function unwrapOrThrow(value: any) {
318
+ if (isHandledError(value)) {
319
+ throw value;
320
+ }
321
+
322
+ return value;
323
+ }
324
+
325
+ function isRootPath(pathFromRoot: any): boolean {
326
+ return typeof pathFromRoot == "string" && Pointer.parse(pathFromRoot).length == 0;
327
+ }
package/lib/ref.ts ADDED
@@ -0,0 +1,279 @@
1
+ import Pointer from "./pointer.js";
2
+ import type { JSONParserError, MissingPointerError, ParserError, ResolverError } from "./util/errors.js";
3
+ import { normalizeError } from "./util/errors.js";
4
+ import type $Refs from "./refs.js";
5
+ import type { ParserOptions } from "./options.js";
6
+ import type { JSONSchema } from "./types";
7
+
8
+ export type $RefError = JSONParserError | ResolverError | ParserError | MissingPointerError;
9
+
10
+ /**
11
+ * This class represents a single JSON reference and its resolved value.
12
+ *
13
+ * @class
14
+ */
15
+ class $Ref<S extends object = JSONSchema> {
16
+ /**
17
+ * The file path or URL of the referenced file.
18
+ * This path is relative to the path of the main JSON schema file.
19
+ *
20
+ * This path does NOT contain document fragments (JSON pointers). It always references an ENTIRE file.
21
+ * Use methods such as {@link $Ref#get}, {@link $Ref#resolve}, and {@link $Ref#exists} to get
22
+ * specific JSON pointers within the file.
23
+ *
24
+ * @type {string}
25
+ */
26
+ path: undefined | string;
27
+
28
+ /**
29
+ * The resolved value of the JSON reference.
30
+ * Can be any JSON type, not just objects. Unknown file types are represented as Buffers (byte arrays).
31
+ *
32
+ * @type {?*}
33
+ */
34
+ value: any;
35
+
36
+ /**
37
+ * The {@link $Refs} object that contains this {@link $Ref} object.
38
+ *
39
+ * @type {$Refs}
40
+ */
41
+ $refs: $Refs<S>;
42
+
43
+ /**
44
+ * Indicates the type of {@link $Ref#path} (e.g. "file", "http", etc.)
45
+ */
46
+ pathType: string | unknown;
47
+
48
+ /**
49
+ * List of all errors. Undefined if no errors.
50
+ */
51
+ errors: Array<$RefError> = [];
52
+
53
+ constructor($refs: $Refs<S>) {
54
+ this.$refs = $refs;
55
+ }
56
+
57
+ /**
58
+ * Pushes an error to errors array.
59
+ *
60
+ * @param err - The error to be pushed
61
+ * @returns
62
+ */
63
+ addError(err: $RefError) {
64
+ if (this.errors === undefined) {
65
+ this.errors = [];
66
+ }
67
+
68
+ const existingErrors = this.errors.map(({ footprint }: any) => footprint);
69
+
70
+ // the path has been almost certainly set at this point,
71
+ // but just in case something went wrong, normalizeError injects path if necessary
72
+ // moreover, certain errors might point at the same spot, so filter them out to reduce noise
73
+ if ("errors" in err && Array.isArray(err.errors)) {
74
+ this.errors.push(
75
+ ...err.errors.map(normalizeError).filter(({ footprint }: any) => !existingErrors.includes(footprint)),
76
+ );
77
+ } else if (!("footprint" in err) || !existingErrors.includes(err.footprint)) {
78
+ this.errors.push(normalizeError(err));
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Determines whether the given JSON reference exists within this {@link $Ref#value}.
84
+ *
85
+ * @param path - The full path being resolved, optionally with a JSON pointer in the hash
86
+ * @param options
87
+ * @returns
88
+ */
89
+ exists(path: string, options?: ParserOptions) {
90
+ try {
91
+ this.resolve(path, options);
92
+ return true;
93
+ } catch {
94
+ return false;
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Resolves the given JSON reference within this {@link $Ref#value} and returns the resolved value.
100
+ *
101
+ * @param path - The full path being resolved, optionally with a JSON pointer in the hash
102
+ * @param options
103
+ * @returns - Returns the resolved value
104
+ */
105
+ get(path: string, options?: ParserOptions) {
106
+ return this.resolve(path, options)?.value;
107
+ }
108
+
109
+ /**
110
+ * Resolves the given JSON reference within this {@link $Ref#value}.
111
+ *
112
+ * @param path - The full path being resolved, optionally with a JSON pointer in the hash
113
+ * @param options
114
+ * @param friendlyPath - The original user-specified path (used for error messages)
115
+ * @param pathFromRoot - The path of `obj` from the schema root
116
+ * @returns
117
+ */
118
+ resolve(path: string, options?: ParserOptions, friendlyPath?: string, pathFromRoot?: string) {
119
+ const pointer = new Pointer<S>(this, path, friendlyPath);
120
+ return pointer.resolve(this.value, options, pathFromRoot);
121
+ }
122
+
123
+ /**
124
+ * Sets the value of a nested property within this {@link $Ref#value}.
125
+ * If the property, or any of its parents don't exist, they will be created.
126
+ *
127
+ * @param path - The full path of the property to set, optionally with a JSON pointer in the hash
128
+ * @param value - The value to assign
129
+ */
130
+ set(path: string, value: any) {
131
+ const pointer = new Pointer(this, path);
132
+ this.value = pointer.set(this.value, value);
133
+ }
134
+
135
+ /**
136
+ * Determines whether the given value is a JSON reference.
137
+ *
138
+ * @param value - The value to inspect
139
+ * @returns
140
+ */
141
+ static is$Ref(value: unknown): value is { $ref: string; length?: number } {
142
+ return (
143
+ Boolean(value) &&
144
+ typeof value === "object" &&
145
+ value !== null &&
146
+ "$ref" in value &&
147
+ typeof value.$ref === "string" &&
148
+ value.$ref.length > 0
149
+ );
150
+ }
151
+
152
+ /**
153
+ * Determines whether the given value is an external JSON reference.
154
+ *
155
+ * @param value - The value to inspect
156
+ * @returns
157
+ */
158
+ static isExternal$Ref(value: unknown): boolean {
159
+ return $Ref.is$Ref(value) && value.$ref![0] !== "#";
160
+ }
161
+
162
+ /**
163
+ * Determines whether the given value is a JSON reference, and whether it is allowed by the options.
164
+ *
165
+ * @param value - The value to inspect
166
+ * @param options
167
+ * @returns
168
+ */
169
+ static isAllowed$Ref(value: unknown) {
170
+ if (this.is$Ref(value)) {
171
+ if (value.$ref.substring(0, 2) === "#/" || value.$ref === "#") {
172
+ // It's a JSON Pointer reference, which is always allowed
173
+ return true;
174
+ } else if (value.$ref[0] !== "#") {
175
+ // It's an external reference, which is allowed by the options
176
+ return true;
177
+ }
178
+ }
179
+ return undefined;
180
+ }
181
+
182
+ /**
183
+ * Determines whether the given value is a JSON reference that "extends" its resolved value.
184
+ * That is, it has extra properties (in addition to "$ref"), so rather than simply pointing to
185
+ * an existing value, this $ref actually creates a NEW value that is a shallow copy of the resolved
186
+ * value, plus the extra properties.
187
+ *
188
+ * @example: {
189
+ person: {
190
+ properties: {
191
+ firstName: { type: string }
192
+ lastName: { type: string }
193
+ }
194
+ }
195
+ employee: {
196
+ properties: {
197
+ $ref: #/person/properties
198
+ salary: { type: number }
199
+ }
200
+ }
201
+ }
202
+ * In this example, "employee" is an extended $ref, since it extends "person" with an additional
203
+ * property (salary). The result is a NEW value that looks like this:
204
+ *
205
+ * {
206
+ * properties: {
207
+ * firstName: { type: string }
208
+ * lastName: { type: string }
209
+ * salary: { type: number }
210
+ * }
211
+ * }
212
+ *
213
+ * @param value - The value to inspect
214
+ * @returns
215
+ */
216
+ static isExtended$Ref(value: unknown) {
217
+ return $Ref.is$Ref(value) && Object.keys(value).length > 1;
218
+ }
219
+
220
+ /**
221
+ * Returns the resolved value of a JSON Reference.
222
+ * If necessary, the resolved value is merged with the JSON Reference to create a new object
223
+ *
224
+ * @example: {
225
+ person: {
226
+ properties: {
227
+ firstName: { type: string }
228
+ lastName: { type: string }
229
+ }
230
+ }
231
+ employee: {
232
+ properties: {
233
+ $ref: #/person/properties
234
+ salary: { type: number }
235
+ }
236
+ }
237
+ } When "person" and "employee" are merged, you end up with the following object:
238
+ *
239
+ * {
240
+ * properties: {
241
+ * firstName: { type: string }
242
+ * lastName: { type: string }
243
+ * salary: { type: number }
244
+ * }
245
+ * }
246
+ *
247
+ * @param $ref - The JSON reference object (the one with the "$ref" property)
248
+ * @param resolvedValue - The resolved value, which can be any type
249
+ * @returns - Returns the dereferenced value
250
+ */
251
+ static dereference<S extends object = JSONSchema>(
252
+ $ref: $Ref<S>,
253
+ resolvedValue: S,
254
+ ): S {
255
+ if (resolvedValue && typeof resolvedValue === "object" && $Ref.isExtended$Ref($ref)) {
256
+ const merged = {};
257
+ for (const key of Object.keys($ref)) {
258
+ if (key !== "$ref") {
259
+ // @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
260
+ merged[key] = $ref[key];
261
+ }
262
+ }
263
+
264
+ for (const key of Object.keys(resolvedValue)) {
265
+ if (!(key in merged)) {
266
+ // @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
267
+ merged[key] = resolvedValue[key];
268
+ }
269
+ }
270
+
271
+ return merged as S;
272
+ } else {
273
+ // Completely replace the original reference with the resolved value
274
+ return resolvedValue;
275
+ }
276
+ }
277
+ }
278
+
279
+ export default $Ref;