@hey-api/json-schema-ref-parser 0.0.0-next-20260212230650

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.ts ADDED
@@ -0,0 +1,599 @@
1
+ import { ono } from '@jsdevtools/ono';
2
+
3
+ import { bundle as _bundle } from './bundle';
4
+ import _dereference from './dereference';
5
+ import { getJsonSchemaRefParserDefaultOptions } from './options';
6
+ import { newFile, parseFile } from './parse';
7
+ import $Refs from './refs';
8
+ import { resolveExternal } from './resolve-external';
9
+ import { fileResolver } from './resolvers/file';
10
+ import { urlResolver } from './resolvers/url';
11
+ import type { JSONSchema } from './types';
12
+ import { isHandledError, JSONParserErrorGroup } from './util/errors';
13
+ import * as url from './util/url';
14
+
15
+ interface ResolvedInput {
16
+ path: string;
17
+ schema: string | JSONSchema | Buffer | Awaited<JSONSchema> | undefined;
18
+ type: 'file' | 'json' | 'url';
19
+ }
20
+
21
+ export const getResolvedInput = ({
22
+ pathOrUrlOrSchema,
23
+ }: {
24
+ pathOrUrlOrSchema: JSONSchema | string | unknown;
25
+ }): ResolvedInput => {
26
+ if (!pathOrUrlOrSchema) {
27
+ throw ono(`Expected a file path, URL, or object. Got ${pathOrUrlOrSchema}`);
28
+ }
29
+
30
+ const resolvedInput: ResolvedInput = {
31
+ path: typeof pathOrUrlOrSchema === 'string' ? pathOrUrlOrSchema : '',
32
+ schema: undefined,
33
+ type: 'url',
34
+ };
35
+
36
+ // If the path is a filesystem path, then convert it to a URL.
37
+ // NOTE: According to the JSON Reference spec, these should already be URLs,
38
+ // but, in practice, many people use local filesystem paths instead.
39
+ // So we're being generous here and doing the conversion automatically.
40
+ // This is not intended to be a 100% bulletproof solution.
41
+ // If it doesn't work for your use-case, then use a URL instead.
42
+ if (resolvedInput.path && url.isFileSystemPath(resolvedInput.path)) {
43
+ resolvedInput.path = url.fromFileSystemPath(resolvedInput.path);
44
+ resolvedInput.type = 'file';
45
+ } else if (!resolvedInput.path && pathOrUrlOrSchema && typeof pathOrUrlOrSchema === 'object') {
46
+ if ('$id' in pathOrUrlOrSchema && pathOrUrlOrSchema.$id) {
47
+ // when schema id has defined an URL should use that hostname to request the references,
48
+ // instead of using the current page URL
49
+ const { hostname, protocol } = new URL(pathOrUrlOrSchema.$id as string);
50
+ resolvedInput.path = `${protocol}//${hostname}:${protocol === 'https:' ? 443 : 80}`;
51
+ resolvedInput.type = 'url';
52
+ } else {
53
+ resolvedInput.schema = pathOrUrlOrSchema;
54
+ resolvedInput.type = 'json';
55
+ }
56
+ }
57
+
58
+ if (resolvedInput.type !== 'json') {
59
+ // resolve the absolute path of the schema
60
+ resolvedInput.path = url.resolve(url.cwd(), resolvedInput.path);
61
+ }
62
+
63
+ return resolvedInput;
64
+ };
65
+
66
+ // NOTE: previously used helper removed as unused
67
+
68
+ /**
69
+ * This class parses a JSON schema, builds a map of its JSON references and their resolved values,
70
+ * and provides methods for traversing, manipulating, and dereferencing those references.
71
+ */
72
+ export class $RefParser {
73
+ /**
74
+ * The resolved JSON references
75
+ *
76
+ * @type {$Refs}
77
+ * @readonly
78
+ */
79
+ $refs = new $Refs<JSONSchema>();
80
+ public options = getJsonSchemaRefParserDefaultOptions();
81
+ /**
82
+ * The parsed (and possibly dereferenced) JSON schema object
83
+ *
84
+ * @type {object}
85
+ * @readonly
86
+ */
87
+ public schema: JSONSchema | null = null;
88
+ public schemaMany: JSONSchema[] = [];
89
+ public schemaManySources: string[] = [];
90
+ public sourcePathToPrefix: Map<string, string> = new Map();
91
+
92
+ /**
93
+ * Bundles all referenced files/URLs into a single schema that only has internal `$ref` pointers. This lets you split-up your schema however you want while you're building it, but easily combine all those files together when it's time to package or distribute the schema to other people. The resulting schema size will be small, since it will still contain internal JSON references rather than being fully-dereferenced.
94
+ *
95
+ * This also eliminates the risk of circular references, so the schema can be safely serialized using `JSON.stringify()`.
96
+ *
97
+ * See https://apitools.dev/json-schema-ref-parser/docs/ref-parser.html#bundleschema-options-callback
98
+ *
99
+ * @param pathOrUrlOrSchema A JSON Schema object, or the file path or URL of a JSON Schema file.
100
+ */
101
+ public async bundle({
102
+ arrayBuffer,
103
+ fetch,
104
+ pathOrUrlOrSchema,
105
+ resolvedInput,
106
+ }: {
107
+ arrayBuffer?: ArrayBuffer;
108
+ fetch?: RequestInit;
109
+ pathOrUrlOrSchema: JSONSchema | string | unknown;
110
+ resolvedInput?: ResolvedInput;
111
+ }): Promise<JSONSchema> {
112
+ await this.parse({
113
+ arrayBuffer,
114
+ fetch,
115
+ pathOrUrlOrSchema,
116
+ resolvedInput,
117
+ });
118
+
119
+ await resolveExternal(this, this.options);
120
+ const errors = JSONParserErrorGroup.getParserErrors(this);
121
+ if (errors.length > 0) {
122
+ throw new JSONParserErrorGroup(this);
123
+ }
124
+ _bundle(this, this.options);
125
+ const errors2 = JSONParserErrorGroup.getParserErrors(this);
126
+ if (errors2.length > 0) {
127
+ throw new JSONParserErrorGroup(this);
128
+ }
129
+ return this.schema!;
130
+ }
131
+
132
+ /**
133
+ * Bundles multiple roots (files/URLs/objects) into a single schema by creating a synthetic root
134
+ * that references each input, resolving all externals, and then hoisting via the existing bundler.
135
+ */
136
+ public async bundleMany({
137
+ arrayBuffer,
138
+ fetch,
139
+ pathOrUrlOrSchemas,
140
+ resolvedInputs,
141
+ }: {
142
+ arrayBuffer?: ArrayBuffer[];
143
+ fetch?: RequestInit;
144
+ pathOrUrlOrSchemas: Array<JSONSchema | string | unknown>;
145
+ resolvedInputs?: ResolvedInput[];
146
+ }): Promise<JSONSchema> {
147
+ await this.parseMany({ arrayBuffer, fetch, pathOrUrlOrSchemas, resolvedInputs });
148
+ this.mergeMany();
149
+
150
+ await resolveExternal(this, this.options);
151
+ const errors = JSONParserErrorGroup.getParserErrors(this);
152
+ if (errors.length > 0) {
153
+ throw new JSONParserErrorGroup(this);
154
+ }
155
+ _bundle(this, this.options);
156
+ // Merged root is ready for bundling
157
+
158
+ const errors2 = JSONParserErrorGroup.getParserErrors(this);
159
+ if (errors2.length > 0) {
160
+ throw new JSONParserErrorGroup(this);
161
+ }
162
+ return this.schema!;
163
+ }
164
+
165
+ /**
166
+ * Dereferences all `$ref` pointers in the JSON Schema, replacing each reference with its resolved value. This results in a schema object that does not contain any `$ref` pointers. Instead, it's a normal JavaScript object tree that can easily be crawled and used just like any other JavaScript object. This is great for programmatic usage, especially when using tools that don't understand JSON references.
167
+ *
168
+ * The dereference method maintains object reference equality, meaning that all `$ref` pointers that point to the same object will be replaced with references to the same object. Again, this is great for programmatic usage, but it does introduce the risk of circular references, so be careful if you intend to serialize the schema using `JSON.stringify()`. Consider using the bundle method instead, which does not create circular references.
169
+ *
170
+ * See https://apitools.dev/json-schema-ref-parser/docs/ref-parser.html#dereferenceschema-options-callback
171
+ *
172
+ * @param pathOrUrlOrSchema A JSON Schema object, or the file path or URL of a JSON Schema file.
173
+ */
174
+ public async dereference({
175
+ fetch,
176
+ pathOrUrlOrSchema,
177
+ }: {
178
+ fetch?: RequestInit;
179
+ pathOrUrlOrSchema: JSONSchema | string | unknown;
180
+ }): Promise<JSONSchema> {
181
+ await this.parse({
182
+ fetch,
183
+ pathOrUrlOrSchema,
184
+ });
185
+ await resolveExternal(this, this.options);
186
+ const errors = JSONParserErrorGroup.getParserErrors(this);
187
+ if (errors.length > 0) {
188
+ throw new JSONParserErrorGroup(this);
189
+ }
190
+ _dereference(this, this.options);
191
+ const errors2 = JSONParserErrorGroup.getParserErrors(this);
192
+ if (errors2.length > 0) {
193
+ throw new JSONParserErrorGroup(this);
194
+ }
195
+ return this.schema!;
196
+ }
197
+
198
+ /**
199
+ * Parses the given JSON schema.
200
+ * This method does not resolve any JSON references.
201
+ * It just reads a single file in JSON or YAML format, and parse it as a JavaScript object.
202
+ *
203
+ * @param pathOrUrlOrSchema A JSON Schema object, or the file path or URL of a JSON Schema file.
204
+ * @returns - The returned promise resolves with the parsed JSON schema object.
205
+ */
206
+ public async parse({
207
+ arrayBuffer,
208
+ fetch,
209
+ pathOrUrlOrSchema,
210
+ resolvedInput: _resolvedInput,
211
+ }: {
212
+ arrayBuffer?: ArrayBuffer;
213
+ fetch?: RequestInit;
214
+ pathOrUrlOrSchema: JSONSchema | string | unknown;
215
+ resolvedInput?: ResolvedInput;
216
+ }): Promise<{ schema: JSONSchema }> {
217
+ const resolvedInput = _resolvedInput || getResolvedInput({ pathOrUrlOrSchema });
218
+ const { path, type } = resolvedInput;
219
+ let { schema } = resolvedInput;
220
+
221
+ // reset everything
222
+ this.schema = null;
223
+ this.$refs = new $Refs();
224
+
225
+ if (schema) {
226
+ // immediately add a new $Ref with the schema object as value
227
+ const $ref = this.$refs._add(path);
228
+ $ref.pathType = url.isFileSystemPath(path) ? 'file' : 'http';
229
+ $ref.value = schema;
230
+ } else if (type !== 'json') {
231
+ const file = newFile(path);
232
+
233
+ // Add a new $Ref for this file, even though we don't have the value yet.
234
+ // This ensures that we don't simultaneously read & parse the same file multiple times
235
+ const $refAdded = this.$refs._add(file.url);
236
+ $refAdded.pathType = type;
237
+ try {
238
+ const resolver = type === 'file' ? fileResolver : urlResolver;
239
+ await resolver.handler({
240
+ arrayBuffer,
241
+ fetch,
242
+ file,
243
+ });
244
+ const parseResult = await parseFile(file, this.options);
245
+ $refAdded.value = parseResult.result;
246
+ schema = parseResult.result;
247
+ } catch (err) {
248
+ if (isHandledError(err)) {
249
+ $refAdded.value = err;
250
+ }
251
+
252
+ throw err;
253
+ }
254
+ }
255
+
256
+ if (schema === null || typeof schema !== 'object' || Buffer.isBuffer(schema)) {
257
+ throw ono.syntax(`"${this.$refs._root$Ref.path || schema}" is not a valid JSON Schema`);
258
+ }
259
+
260
+ this.schema = schema;
261
+
262
+ return {
263
+ schema,
264
+ };
265
+ }
266
+
267
+ private async parseMany({
268
+ arrayBuffer,
269
+ fetch,
270
+ pathOrUrlOrSchemas,
271
+ resolvedInputs: _resolvedInputs,
272
+ }: {
273
+ arrayBuffer?: ArrayBuffer[];
274
+ fetch?: RequestInit;
275
+ pathOrUrlOrSchemas: Array<JSONSchema | string | unknown>;
276
+ resolvedInputs?: ResolvedInput[];
277
+ }): Promise<{ schemaMany: JSONSchema[] }> {
278
+ const resolvedInputs = [...(_resolvedInputs || [])];
279
+ resolvedInputs.push(
280
+ ...(pathOrUrlOrSchemas.map((schema) => getResolvedInput({ pathOrUrlOrSchema: schema })) ||
281
+ []),
282
+ );
283
+
284
+ this.schemaMany = [];
285
+ this.schemaManySources = [];
286
+ this.sourcePathToPrefix = new Map();
287
+
288
+ for (let i = 0; i < resolvedInputs.length; i++) {
289
+ const resolvedInput = resolvedInputs[i]!;
290
+ const { path, type } = resolvedInput;
291
+ let { schema } = resolvedInput;
292
+
293
+ if (schema) {
294
+ // keep schema as-is
295
+ } else if (type !== 'json') {
296
+ const file = newFile(path);
297
+
298
+ // Add a new $Ref for this file, even though we don't have the value yet.
299
+ // This ensures that we don't simultaneously read & parse the same file multiple times
300
+ const $refAdded = this.$refs._add(file.url);
301
+ $refAdded.pathType = type;
302
+ try {
303
+ const resolver = type === 'file' ? fileResolver : urlResolver;
304
+ await resolver.handler({
305
+ arrayBuffer: arrayBuffer?.[i],
306
+ fetch,
307
+ file,
308
+ });
309
+ const parseResult = await parseFile(file, this.options);
310
+ $refAdded.value = parseResult.result;
311
+ schema = parseResult.result;
312
+ } catch (err) {
313
+ if (isHandledError(err)) {
314
+ $refAdded.value = err;
315
+ }
316
+
317
+ throw err;
318
+ }
319
+ }
320
+
321
+ if (schema === null || typeof schema !== 'object' || Buffer.isBuffer(schema)) {
322
+ throw ono.syntax(`"${this.$refs._root$Ref.path || schema}" is not a valid JSON Schema`);
323
+ }
324
+
325
+ this.schemaMany.push(schema);
326
+ this.schemaManySources.push(path && path.length ? path : url.cwd());
327
+ }
328
+
329
+ return {
330
+ schemaMany: this.schemaMany,
331
+ };
332
+ }
333
+
334
+ public mergeMany(): JSONSchema {
335
+ const schemas = this.schemaMany || [];
336
+ if (schemas.length === 0) {
337
+ throw ono('mergeMany called with no schemas. Did you run parseMany?');
338
+ }
339
+
340
+ const merged: any = {};
341
+
342
+ // Determine spec version: prefer first occurrence of openapi, else swagger
343
+ let chosenOpenapi: string | undefined;
344
+ let chosenSwagger: string | undefined;
345
+ for (const s of schemas) {
346
+ if (!chosenOpenapi && s && typeof (s as any).openapi === 'string') {
347
+ chosenOpenapi = (s as any).openapi;
348
+ }
349
+ if (!chosenSwagger && s && typeof (s as any).swagger === 'string') {
350
+ chosenSwagger = (s as any).swagger;
351
+ }
352
+ if (chosenOpenapi && chosenSwagger) {
353
+ break;
354
+ }
355
+ }
356
+ if (typeof chosenOpenapi === 'string') {
357
+ merged.openapi = chosenOpenapi;
358
+ } else if (typeof chosenSwagger === 'string') {
359
+ merged.swagger = chosenSwagger;
360
+ }
361
+
362
+ // Merge info: take first non-empty per-field across inputs
363
+ const infoAccumulator: any = {};
364
+ for (const s of schemas) {
365
+ const info = (s as any)?.info;
366
+ if (info && typeof info === 'object') {
367
+ for (const [k, v] of Object.entries(info)) {
368
+ if (infoAccumulator[k] === undefined && v !== undefined) {
369
+ infoAccumulator[k] = JSON.parse(JSON.stringify(v));
370
+ }
371
+ }
372
+ }
373
+ }
374
+ if (Object.keys(infoAccumulator).length > 0) {
375
+ merged.info = infoAccumulator;
376
+ }
377
+
378
+ // Merge servers: union by url+description
379
+ const servers: any[] = [];
380
+ const seenServers = new Set<string>();
381
+ for (const s of schemas) {
382
+ const arr = (s as any)?.servers;
383
+ if (Array.isArray(arr)) {
384
+ for (const srv of arr) {
385
+ if (srv && typeof srv === 'object') {
386
+ const key = `${srv.url || ''}|${srv.description || ''}`;
387
+ if (!seenServers.has(key)) {
388
+ seenServers.add(key);
389
+ servers.push(JSON.parse(JSON.stringify(srv)));
390
+ }
391
+ }
392
+ }
393
+ }
394
+ }
395
+ if (servers.length > 0) {
396
+ merged.servers = servers;
397
+ }
398
+
399
+ merged.paths = {};
400
+ merged.components = {};
401
+
402
+ const componentSections = [
403
+ 'schemas',
404
+ 'parameters',
405
+ 'requestBodies',
406
+ 'responses',
407
+ 'headers',
408
+ 'securitySchemes',
409
+ 'examples',
410
+ 'links',
411
+ 'callbacks',
412
+ ];
413
+ for (const sec of componentSections) {
414
+ merged.components[sec] = {};
415
+ }
416
+
417
+ const tagNameSet = new Set<string>();
418
+ const tags: any[] = [];
419
+ const usedOpIds = new Set<string>();
420
+
421
+ const baseName = (p: string) => {
422
+ try {
423
+ const withoutHash = p.split('#')[0]!;
424
+ const parts = withoutHash.split('/');
425
+ const filename = parts[parts.length - 1] || 'schema';
426
+ const dot = filename.lastIndexOf('.');
427
+ const raw = dot > 0 ? filename.substring(0, dot) : filename;
428
+ return raw.replace(/[^A-Za-z0-9_-]/g, '_');
429
+ } catch {
430
+ return 'schema';
431
+ }
432
+ };
433
+ const unique = (set: Set<string>, proposed: string) => {
434
+ let name = proposed;
435
+ let i = 2;
436
+ while (set.has(name)) {
437
+ name = `${proposed}_${i++}`;
438
+ }
439
+ set.add(name);
440
+ return name;
441
+ };
442
+
443
+ const rewriteRef = (ref: string, refMap: Map<string, string>): string => {
444
+ // OAS3: #/components/{section}/{name}...
445
+ let m = ref.match(/^#\/components\/([^/]+)\/([^/]+)(.*)$/);
446
+ if (m) {
447
+ const base = `#/components/${m[1]}/${m[2]}`;
448
+ const mapped = refMap.get(base);
449
+ if (mapped) {
450
+ return mapped + (m[3] || '');
451
+ }
452
+ }
453
+ // OAS2: #/definitions/{name}...
454
+ m = ref.match(/^#\/definitions\/([^/]+)(.*)$/);
455
+ if (m) {
456
+ const base = `#/components/schemas/${m[1]}`;
457
+ const mapped = refMap.get(base);
458
+ if (mapped) {
459
+ // map definitions -> components/schemas
460
+ return mapped + (m[2] || '');
461
+ }
462
+ }
463
+ return ref;
464
+ };
465
+
466
+ const cloneAndRewrite = (
467
+ obj: any,
468
+ refMap: Map<string, string>,
469
+ tagMap: Map<string, string>,
470
+ opIdPrefix: string,
471
+ basePath: string,
472
+ ): any => {
473
+ if (obj === null || obj === undefined) {
474
+ return obj;
475
+ }
476
+ if (Array.isArray(obj)) {
477
+ return obj.map((v) => cloneAndRewrite(v, refMap, tagMap, opIdPrefix, basePath));
478
+ }
479
+ if (typeof obj !== 'object') {
480
+ return obj;
481
+ }
482
+
483
+ const out: any = {};
484
+ for (const [k, v] of Object.entries(obj)) {
485
+ if (k === '$ref' && typeof v === 'string') {
486
+ const s = v as string;
487
+ if (s.startsWith('#')) {
488
+ out[k] = rewriteRef(s, refMap);
489
+ } else {
490
+ const proto = url.getProtocol(s);
491
+ if (proto === undefined) {
492
+ // relative external ref -> absolutize against source base path
493
+ out[k] = url.resolve(basePath + '#', s);
494
+ } else {
495
+ out[k] = s;
496
+ }
497
+ }
498
+ } else if (k === 'tags' && Array.isArray(v) && v.every((x) => typeof x === 'string')) {
499
+ out[k] = v.map((t) => tagMap.get(t) || t);
500
+ } else if (k === 'operationId' && typeof v === 'string') {
501
+ out[k] = unique(usedOpIds, `${opIdPrefix}_${v}`);
502
+ } else {
503
+ out[k] = cloneAndRewrite(v as any, refMap, tagMap, opIdPrefix, basePath);
504
+ }
505
+ }
506
+ return out;
507
+ };
508
+
509
+ for (let i = 0; i < schemas.length; i++) {
510
+ const schema: any = schemas[i] || {};
511
+ const sourcePath = this.schemaManySources[i] || `multi://input/${i + 1}`;
512
+ const prefix = baseName(sourcePath);
513
+
514
+ // Track prefix for this source path (strip hash). Only map real file/http paths
515
+ const withoutHash = url.stripHash(sourcePath);
516
+ const protocol = url.getProtocol(withoutHash);
517
+ if (
518
+ protocol === undefined ||
519
+ protocol === 'file' ||
520
+ protocol === 'http' ||
521
+ protocol === 'https'
522
+ ) {
523
+ this.sourcePathToPrefix.set(withoutHash, prefix);
524
+ }
525
+
526
+ const refMap = new Map<string, string>();
527
+ const tagMap = new Map<string, string>();
528
+
529
+ const srcComponents = (schema.components || {}) as any;
530
+ for (const sec of componentSections) {
531
+ const group = srcComponents[sec] || {};
532
+ for (const [name] of Object.entries(group)) {
533
+ const newName = `${prefix}_${name}`;
534
+ refMap.set(`#/components/${sec}/${name}`, `#/components/${sec}/${newName}`);
535
+ }
536
+ }
537
+
538
+ const srcTags: any[] = Array.isArray(schema.tags) ? schema.tags : [];
539
+ for (const t of srcTags) {
540
+ if (!t || typeof t !== 'object' || typeof t.name !== 'string') {
541
+ continue;
542
+ }
543
+ const desired = t.name;
544
+ const finalName = tagNameSet.has(desired) ? `${prefix}_${desired}` : desired;
545
+ tagNameSet.add(finalName);
546
+ tagMap.set(desired, finalName);
547
+ if (!tags.find((x) => x && x.name === finalName)) {
548
+ tags.push({ ...t, name: finalName });
549
+ }
550
+ }
551
+
552
+ for (const sec of componentSections) {
553
+ const group = (schema.components && schema.components[sec]) || {};
554
+ for (const [name, val] of Object.entries(group)) {
555
+ const newName = `${prefix}_${name}`;
556
+ merged.components[sec][newName] = cloneAndRewrite(
557
+ val,
558
+ refMap,
559
+ tagMap,
560
+ prefix,
561
+ url.stripHash(sourcePath),
562
+ );
563
+ }
564
+ }
565
+
566
+ const srcPaths = (schema.paths || {}) as Record<string, any>;
567
+ for (const [p, item] of Object.entries(srcPaths)) {
568
+ let targetPath = p;
569
+ if (merged.paths[p]) {
570
+ const trimmed = p.startsWith('/') ? p.substring(1) : p;
571
+ targetPath = `/${prefix}/${trimmed}`;
572
+ }
573
+ merged.paths[targetPath] = cloneAndRewrite(
574
+ item,
575
+ refMap,
576
+ tagMap,
577
+ prefix,
578
+ url.stripHash(sourcePath),
579
+ );
580
+ }
581
+ }
582
+
583
+ if (tags.length > 0) {
584
+ merged.tags = tags;
585
+ }
586
+
587
+ // Rebuild $refs root using the first input's path to preserve external resolution semantics
588
+ const rootPath = this.schemaManySources[0] || url.cwd();
589
+ this.$refs = new $Refs();
590
+ const rootRef = this.$refs._add(rootPath);
591
+ rootRef.pathType = url.isFileSystemPath(rootPath) ? 'file' : 'http';
592
+ rootRef.value = merged;
593
+ this.schema = merged;
594
+ return merged as JSONSchema;
595
+ }
596
+ }
597
+
598
+ export { sendRequest } from './resolvers/url';
599
+ export type { JSONSchema } from './types';
package/src/options.ts ADDED
@@ -0,0 +1,112 @@
1
+ import { binaryParser } from './parsers/binary';
2
+ import { jsonParser } from './parsers/json';
3
+ import { textParser } from './parsers/text';
4
+ import { yamlParser } from './parsers/yaml';
5
+ import type { JSONSchemaObject, Plugin } from './types';
6
+
7
+ export interface DereferenceOptions {
8
+ /**
9
+ * Determines whether circular `$ref` pointers are handled.
10
+ *
11
+ * If set to `false`, then a `ReferenceError` will be thrown if the schema contains any circular references.
12
+ *
13
+ * If set to `"ignore"`, then circular references will simply be ignored. No error will be thrown, but the `$Refs.circular` property will still be set to `true`.
14
+ */
15
+ circular?: boolean | 'ignore';
16
+ /**
17
+ * A function, called for each path, which can return true to stop this path and all
18
+ * subpaths from being dereferenced further. This is useful in schemas where some
19
+ * subpaths contain literal $ref keys that should not be dereferenced.
20
+ */
21
+ excludedPathMatcher?(path: string): boolean;
22
+ /**
23
+ * Callback invoked during dereferencing.
24
+ *
25
+ * @argument {string} path - The path being dereferenced (ie. the `$ref` string)
26
+ * @argument {JSONSchemaObject} value - The JSON-Schema that the `$ref` resolved to
27
+ * @argument {JSONSchemaObject} parent - The parent of the dereferenced object
28
+ * @argument {string} parentPropName - The prop name of the parent object whose value was dereferenced
29
+ */
30
+ onDereference?(
31
+ path: string,
32
+ value: JSONSchemaObject,
33
+ parent?: JSONSchemaObject,
34
+ parentPropName?: string,
35
+ ): void;
36
+ }
37
+
38
+ /**
39
+ * Options that determine how JSON schemas are parsed, resolved, and dereferenced.
40
+ *
41
+ * @param [options] - Overridden options
42
+ * @class
43
+ */
44
+ export interface $RefParserOptions {
45
+ /**
46
+ * The `dereference` options control how JSON Schema `$Ref` Parser will dereference `$ref` pointers within the JSON schema.
47
+ */
48
+ dereference: DereferenceOptions;
49
+ /**
50
+ * The `parse` options determine how different types of files will be parsed.
51
+ *
52
+ * JSON Schema `$Ref` Parser comes with built-in JSON, YAML, plain-text, and binary parsers, any of which you can configure or disable. You can also add your own custom parsers if you want.
53
+ */
54
+ parse: {
55
+ binary: Plugin;
56
+ json: Plugin;
57
+ text: Plugin;
58
+ yaml: Plugin;
59
+ };
60
+ /**
61
+ * The maximum amount of time (in milliseconds) that JSON Schema $Ref Parser will spend dereferencing a single schema.
62
+ * It will throw a timeout error if the operation takes longer than this.
63
+ */
64
+ timeoutMs?: number;
65
+ }
66
+
67
+ export const getJsonSchemaRefParserDefaultOptions = (): $RefParserOptions => ({
68
+ /**
69
+ * Determines the types of JSON references that are allowed.
70
+ */
71
+ dereference: {
72
+ /**
73
+ * Dereference circular (recursive) JSON references?
74
+ * If false, then a {@link ReferenceError} will be thrown if a circular reference is found.
75
+ * If "ignore", then circular references will not be dereferenced.
76
+ *
77
+ * @type {boolean|string}
78
+ */
79
+ circular: true,
80
+ /**
81
+ * A function, called for each path, which can return true to stop this path and all
82
+ * subpaths from being dereferenced further. This is useful in schemas where some
83
+ * subpaths contain literal $ref keys that should not be dereferenced.
84
+ *
85
+ * @type {function}
86
+ */
87
+ excludedPathMatcher: () => false,
88
+ // @ts-expect-error
89
+ referenceResolution: 'relative',
90
+ },
91
+ /**
92
+ * Determines how different types of files will be parsed.
93
+ *
94
+ * You can add additional parsers of your own, replace an existing one with
95
+ * your own implementation, or disable any parser by setting it to false.
96
+ */
97
+ parse: {
98
+ binary: { ...binaryParser },
99
+ json: { ...jsonParser },
100
+ text: { ...textParser },
101
+ yaml: { ...yamlParser },
102
+ },
103
+ });
104
+
105
+ export type Options = $RefParserOptions;
106
+
107
+ type DeepPartial<T> = T extends object
108
+ ? {
109
+ [P in keyof T]?: DeepPartial<T[P]>;
110
+ }
111
+ : T;
112
+ export type ParserOptions = DeepPartial<$RefParserOptions>;