@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/ref.ts ADDED
@@ -0,0 +1,283 @@
1
+ import type { ParserOptions } from './options';
2
+ import Pointer from './pointer';
3
+ import type $Refs from './refs';
4
+ import type { JSONSchema } from './types';
5
+ import type {
6
+ JSONParserError,
7
+ MissingPointerError,
8
+ ParserError,
9
+ ResolverError,
10
+ } from './util/errors';
11
+ import { normalizeError } from './util/errors';
12
+
13
+ export type $RefError = JSONParserError | ResolverError | ParserError | MissingPointerError;
14
+
15
+ /**
16
+ * This class represents a single JSON reference and its resolved value.
17
+ *
18
+ * @class
19
+ */
20
+ class $Ref<S extends object = JSONSchema> {
21
+ /**
22
+ * The file path or URL of the referenced file.
23
+ * This path is relative to the path of the main JSON schema file.
24
+ *
25
+ * This path does NOT contain document fragments (JSON pointers). It always references an ENTIRE file.
26
+ * Use methods such as {@link $Ref#get}, {@link $Ref#resolve}, and {@link $Ref#exists} to get
27
+ * specific JSON pointers within the file.
28
+ *
29
+ * @type {string}
30
+ */
31
+ path: undefined | string;
32
+
33
+ /**
34
+ * The resolved value of the JSON reference.
35
+ * Can be any JSON type, not just objects. Unknown file types are represented as Buffers (byte arrays).
36
+ *
37
+ * @type {?*}
38
+ */
39
+ value: any;
40
+
41
+ /**
42
+ * The {@link $Refs} object that contains this {@link $Ref} object.
43
+ *
44
+ * @type {$Refs}
45
+ */
46
+ $refs: $Refs<S>;
47
+
48
+ /**
49
+ * Indicates the type of {@link $Ref#path} (e.g. "file", "http", etc.)
50
+ */
51
+ pathType: string | unknown;
52
+
53
+ /**
54
+ * List of all errors. Undefined if no errors.
55
+ */
56
+ errors: Array<$RefError> = [];
57
+
58
+ constructor($refs: $Refs<S>) {
59
+ this.$refs = $refs;
60
+ }
61
+
62
+ /**
63
+ * Pushes an error to errors array.
64
+ *
65
+ * @param err - The error to be pushed
66
+ * @returns
67
+ */
68
+ addError(err: $RefError) {
69
+ if (this.errors === undefined) {
70
+ this.errors = [];
71
+ }
72
+
73
+ const existingErrors = this.errors.map(({ footprint }: any) => footprint);
74
+
75
+ // the path has been almost certainly set at this point,
76
+ // but just in case something went wrong, normalizeError injects path if necessary
77
+ // moreover, certain errors might point at the same spot, so filter them out to reduce noise
78
+ if ('errors' in err && Array.isArray(err.errors)) {
79
+ this.errors.push(
80
+ ...err.errors
81
+ .map(normalizeError)
82
+ .filter(({ footprint }: any) => !existingErrors.includes(footprint)),
83
+ );
84
+ } else if (!('footprint' in err) || !existingErrors.includes(err.footprint)) {
85
+ this.errors.push(normalizeError(err));
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Determines whether the given JSON reference exists within this {@link $Ref#value}.
91
+ *
92
+ * @param path - The full path being resolved, optionally with a JSON pointer in the hash
93
+ * @param options
94
+ * @returns
95
+ */
96
+ exists(path: string, options?: ParserOptions) {
97
+ try {
98
+ this.resolve(path, options);
99
+ return true;
100
+ } catch {
101
+ return false;
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Resolves the given JSON reference within this {@link $Ref#value} and returns the resolved value.
107
+ *
108
+ * @param path - The full path being resolved, optionally with a JSON pointer in the hash
109
+ * @param options
110
+ * @returns - Returns the resolved value
111
+ */
112
+ get(path: string, options?: ParserOptions) {
113
+ return this.resolve(path, options)?.value;
114
+ }
115
+
116
+ /**
117
+ * Resolves the given JSON reference within this {@link $Ref#value}.
118
+ *
119
+ * @param path - The full path being resolved, optionally with a JSON pointer in the hash
120
+ * @param options
121
+ * @param friendlyPath - The original user-specified path (used for error messages)
122
+ * @param pathFromRoot - The path of `obj` from the schema root
123
+ * @returns
124
+ */
125
+ resolve(path: string, options?: ParserOptions, friendlyPath?: string, pathFromRoot?: string) {
126
+ const pointer = new Pointer<S>(this, path, friendlyPath);
127
+ return pointer.resolve(this.value, options, pathFromRoot);
128
+ }
129
+
130
+ /**
131
+ * Sets the value of a nested property within this {@link $Ref#value}.
132
+ * If the property, or any of its parents don't exist, they will be created.
133
+ *
134
+ * @param path - The full path of the property to set, optionally with a JSON pointer in the hash
135
+ * @param value - The value to assign
136
+ */
137
+ set(path: string, value: any) {
138
+ const pointer = new Pointer(this, path);
139
+ this.value = pointer.set(this.value, value);
140
+ }
141
+
142
+ /**
143
+ * Determines whether the given value is a JSON reference.
144
+ *
145
+ * @param value - The value to inspect
146
+ * @returns
147
+ */
148
+ static is$Ref(value: unknown): value is { $ref: string; length?: number } {
149
+ return (
150
+ Boolean(value) &&
151
+ typeof value === 'object' &&
152
+ value !== null &&
153
+ '$ref' in value &&
154
+ typeof value.$ref === 'string' &&
155
+ value.$ref.length > 0
156
+ );
157
+ }
158
+
159
+ /**
160
+ * Determines whether the given value is an external JSON reference.
161
+ *
162
+ * @param value - The value to inspect
163
+ * @returns
164
+ */
165
+ static isExternal$Ref(value: unknown): boolean {
166
+ return $Ref.is$Ref(value) && value.$ref![0] !== '#';
167
+ }
168
+
169
+ /**
170
+ * Determines whether the given value is a JSON reference, and whether it is allowed by the options.
171
+ *
172
+ * @param value - The value to inspect
173
+ * @param options
174
+ * @returns
175
+ */
176
+ static isAllowed$Ref(value: unknown) {
177
+ if (this.is$Ref(value)) {
178
+ if (value.$ref.substring(0, 2) === '#/' || value.$ref === '#') {
179
+ // It's a JSON Pointer reference, which is always allowed
180
+ return true;
181
+ } else if (value.$ref[0] !== '#') {
182
+ // It's an external reference, which is allowed by the options
183
+ return true;
184
+ }
185
+ }
186
+ return undefined;
187
+ }
188
+
189
+ /**
190
+ * Determines whether the given value is a JSON reference that "extends" its resolved value.
191
+ * That is, it has extra properties (in addition to "$ref"), so rather than simply pointing to
192
+ * an existing value, this $ref actually creates a NEW value that is a shallow copy of the resolved
193
+ * value, plus the extra properties.
194
+ *
195
+ * @example: {
196
+ person: {
197
+ properties: {
198
+ firstName: { type: string }
199
+ lastName: { type: string }
200
+ }
201
+ }
202
+ employee: {
203
+ properties: {
204
+ $ref: #/person/properties
205
+ salary: { type: number }
206
+ }
207
+ }
208
+ }
209
+ * In this example, "employee" is an extended $ref, since it extends "person" with an additional
210
+ * property (salary). The result is a NEW value that looks like this:
211
+ *
212
+ * {
213
+ * properties: {
214
+ * firstName: { type: string }
215
+ * lastName: { type: string }
216
+ * salary: { type: number }
217
+ * }
218
+ * }
219
+ *
220
+ * @param value - The value to inspect
221
+ * @returns
222
+ */
223
+ static isExtended$Ref(value: unknown) {
224
+ return $Ref.is$Ref(value) && Object.keys(value).length > 1;
225
+ }
226
+
227
+ /**
228
+ * Returns the resolved value of a JSON Reference.
229
+ * If necessary, the resolved value is merged with the JSON Reference to create a new object
230
+ *
231
+ * @example: {
232
+ person: {
233
+ properties: {
234
+ firstName: { type: string }
235
+ lastName: { type: string }
236
+ }
237
+ }
238
+ employee: {
239
+ properties: {
240
+ $ref: #/person/properties
241
+ salary: { type: number }
242
+ }
243
+ }
244
+ } When "person" and "employee" are merged, you end up with the following object:
245
+ *
246
+ * {
247
+ * properties: {
248
+ * firstName: { type: string }
249
+ * lastName: { type: string }
250
+ * salary: { type: number }
251
+ * }
252
+ * }
253
+ *
254
+ * @param $ref - The JSON reference object (the one with the "$ref" property)
255
+ * @param resolvedValue - The resolved value, which can be any type
256
+ * @returns - Returns the dereferenced value
257
+ */
258
+ static dereference<S extends object = JSONSchema>($ref: $Ref<S>, resolvedValue: S): S {
259
+ if (resolvedValue && typeof resolvedValue === 'object' && $Ref.isExtended$Ref($ref)) {
260
+ const merged = {};
261
+ for (const key of Object.keys($ref)) {
262
+ if (key !== '$ref') {
263
+ // @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
264
+ merged[key] = $ref[key];
265
+ }
266
+ }
267
+
268
+ for (const key of Object.keys(resolvedValue)) {
269
+ if (!(key in merged)) {
270
+ // @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
271
+ merged[key] = resolvedValue[key];
272
+ }
273
+ }
274
+
275
+ return merged as S;
276
+ } else {
277
+ // Completely replace the original reference with the resolved value
278
+ return resolvedValue;
279
+ }
280
+ }
281
+ }
282
+
283
+ export default $Ref;
package/src/refs.ts ADDED
@@ -0,0 +1,231 @@
1
+ import { ono } from '@jsdevtools/ono';
2
+ import type { JSONSchema4Type, JSONSchema6Type, JSONSchema7Type } from 'json-schema';
3
+
4
+ import type { ParserOptions } from './options';
5
+ import $Ref from './ref';
6
+ import type { JSONSchema } from './types';
7
+ import convertPathToPosix from './util/convert-path-to-posix';
8
+ import * as url from './util/url';
9
+
10
+ interface $RefsMap<S extends object = JSONSchema> {
11
+ [url: string]: $Ref<S>;
12
+ }
13
+ /**
14
+ * When you call the resolve method, the value that gets passed to the callback function (or Promise) is a $Refs object. This same object is accessible via the parser.$refs property of $RefParser objects.
15
+ *
16
+ * This object is a map of JSON References and their resolved values. It also has several convenient helper methods that make it easy for you to navigate and manipulate the JSON References.
17
+ *
18
+ * See https://apitools.dev/json-schema-ref-parser/docs/refs.html
19
+ */
20
+ export default class $Refs<S extends object = JSONSchema> {
21
+ /**
22
+ * This property is true if the schema contains any circular references. You may want to check this property before serializing the dereferenced schema as JSON, since JSON.stringify() does not support circular references by default.
23
+ *
24
+ * See https://apitools.dev/json-schema-ref-parser/docs/refs.html#circular
25
+ */
26
+ public circular: boolean;
27
+
28
+ /**
29
+ * Returns the paths/URLs of all the files in your schema (including the main schema file).
30
+ *
31
+ * See https://apitools.dev/json-schema-ref-parser/docs/refs.html#pathstypes
32
+ *
33
+ * @param types (optional) Optionally only return certain types of paths ("file", "http", etc.)
34
+ */
35
+ paths(...types: (string | string[])[]): string[] {
36
+ const paths = getPaths(this._$refs, types.flat());
37
+ return paths.map((path) => convertPathToPosix(path.decoded));
38
+ }
39
+
40
+ /**
41
+ * Returns a map of paths/URLs and their correspond values.
42
+ *
43
+ * See https://apitools.dev/json-schema-ref-parser/docs/refs.html#valuestypes
44
+ *
45
+ * @param types (optional) Optionally only return values from certain locations ("file", "http", etc.)
46
+ */
47
+ values(...types: (string | string[])[]): S {
48
+ const $refs = this._$refs;
49
+ const paths = getPaths($refs, types.flat());
50
+ return paths.reduce<Record<string, any>>((obj, path) => {
51
+ obj[convertPathToPosix(path.decoded)] = $refs[path.encoded]!.value;
52
+ return obj;
53
+ }, {}) as S;
54
+ }
55
+
56
+ /**
57
+ * Returns `true` if the given path exists in the schema; otherwise, returns `false`
58
+ *
59
+ * See https://apitools.dev/json-schema-ref-parser/docs/refs.html#existsref
60
+ *
61
+ * @param $ref The JSON Reference path, optionally with a JSON Pointer in the hash
62
+ */
63
+ /**
64
+ * Determines whether the given JSON reference exists.
65
+ *
66
+ * @param path - The path being resolved, optionally with a JSON pointer in the hash
67
+ * @param [options]
68
+ * @returns
69
+ */
70
+ exists(path: string, options: any) {
71
+ try {
72
+ this._resolve(path, '', options);
73
+ return true;
74
+ } catch {
75
+ return false;
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Resolves the given JSON reference and returns the resolved value.
81
+ *
82
+ * @param path - The path being resolved, with a JSON pointer in the hash
83
+ * @param [options]
84
+ * @returns - Returns the resolved value
85
+ */
86
+ get(path: string, options?: ParserOptions): JSONSchema4Type | JSONSchema6Type | JSONSchema7Type {
87
+ return this._resolve(path, '', options)!.value;
88
+ }
89
+
90
+ /**
91
+ * Sets the value at the given path in the schema. If the property, or any of its parents, don't exist, they will be created.
92
+ *
93
+ * @param path The JSON Reference path, optionally with a JSON Pointer in the hash
94
+ * @param value The value to assign. Can be anything (object, string, number, etc.)
95
+ */
96
+ set(path: string, value: JSONSchema4Type | JSONSchema6Type | JSONSchema7Type) {
97
+ const absPath = url.resolve(this._root$Ref.path!, path);
98
+ const withoutHash = url.stripHash(absPath);
99
+ const $ref = this._$refs[withoutHash];
100
+
101
+ if (!$ref) {
102
+ throw ono(`Error resolving $ref pointer "${path}". \n"${withoutHash}" not found.`);
103
+ }
104
+
105
+ $ref.set(absPath, value);
106
+ }
107
+ /**
108
+ * Returns the specified {@link $Ref} object, or undefined.
109
+ *
110
+ * @param path - The path being resolved, optionally with a JSON pointer in the hash
111
+ * @returns
112
+ * @protected
113
+ */
114
+ _get$Ref(path: string) {
115
+ path = url.resolve(this._root$Ref.path!, path);
116
+ const withoutHash = url.stripHash(path);
117
+ return this._$refs[withoutHash];
118
+ }
119
+
120
+ /**
121
+ * Creates a new {@link $Ref} object and adds it to this {@link $Refs} object.
122
+ *
123
+ * @param path - The file path or URL of the referenced file
124
+ */
125
+ _add(path: string) {
126
+ const withoutHash = url.stripHash(path);
127
+
128
+ const $ref = new $Ref<S>(this);
129
+ $ref.path = withoutHash;
130
+
131
+ this._$refs[withoutHash] = $ref;
132
+ this._root$Ref = this._root$Ref || $ref;
133
+
134
+ return $ref;
135
+ }
136
+
137
+ /**
138
+ * Resolves the given JSON reference.
139
+ *
140
+ * @param path - The path being resolved, optionally with a JSON pointer in the hash
141
+ * @param pathFromRoot - The path of `obj` from the schema root
142
+ * @param [options]
143
+ * @returns
144
+ * @protected
145
+ */
146
+ _resolve(path: string, pathFromRoot: string, options?: ParserOptions) {
147
+ const absPath = url.resolve(this._root$Ref.path!, path);
148
+ const withoutHash = url.stripHash(absPath);
149
+ const $ref = this._$refs[withoutHash];
150
+
151
+ if (!$ref) {
152
+ throw ono(`Error resolving $ref pointer "${path}". \n"${withoutHash}" not found.`);
153
+ }
154
+
155
+ return $ref.resolve(absPath, options, path, pathFromRoot);
156
+ }
157
+
158
+ /**
159
+ * A map of paths/urls to {@link $Ref} objects
160
+ *
161
+ * @type {object}
162
+ * @protected
163
+ */
164
+ _$refs: $RefsMap<S> = {};
165
+
166
+ /**
167
+ * The {@link $Ref} object that is the root of the JSON schema.
168
+ *
169
+ * @type {$Ref}
170
+ * @protected
171
+ */
172
+ _root$Ref: $Ref<S>;
173
+
174
+ constructor() {
175
+ /**
176
+ * Indicates whether the schema contains any circular references.
177
+ *
178
+ * @type {boolean}
179
+ */
180
+ this.circular = false;
181
+
182
+ this._$refs = {};
183
+
184
+ // @ts-ignore
185
+ this._root$Ref = null;
186
+ }
187
+
188
+ /**
189
+ * Returns the paths of all the files/URLs that are referenced by the JSON schema,
190
+ * including the schema itself.
191
+ *
192
+ * @param [types] - Only return paths of the given types ("file", "http", etc.)
193
+ * @returns
194
+ */
195
+ /**
196
+ * Returns the map of JSON references and their resolved values.
197
+ *
198
+ * @param [types] - Only return references of the given types ("file", "http", etc.)
199
+ * @returns
200
+ */
201
+
202
+ /**
203
+ * Returns a POJO (plain old JavaScript object) for serialization as JSON.
204
+ *
205
+ * @returns {object}
206
+ */
207
+ toJSON = this.values;
208
+ }
209
+
210
+ /**
211
+ * Returns the encoded and decoded paths keys of the given object.
212
+ *
213
+ * @param $refs - The object whose keys are URL-encoded paths
214
+ * @param [types] - Only return paths of the given types ("file", "http", etc.)
215
+ * @returns
216
+ */
217
+ function getPaths<S extends object = JSONSchema>($refs: $RefsMap<S>, types: string[]) {
218
+ let paths = Object.keys($refs);
219
+
220
+ // Filter the paths by type
221
+ types = Array.isArray(types[0]) ? types[0] : Array.prototype.slice.call(types);
222
+ if (types.length > 0 && types[0]) {
223
+ paths = paths.filter((key) => types.includes($refs[key]!.pathType as string));
224
+ }
225
+
226
+ // Decode local filesystem paths
227
+ return paths.map((path) => ({
228
+ decoded: $refs[path]!.pathType === 'file' ? url.toFileSystemPath(path, true) : path,
229
+ encoded: path,
230
+ }));
231
+ }
@@ -0,0 +1,142 @@
1
+ import type { $RefParser } from '.';
2
+ import { getResolvedInput } from '.';
3
+ import type { $RefParserOptions } from './options';
4
+ import { newFile, parseFile } from './parse';
5
+ import Pointer from './pointer';
6
+ import $Ref from './ref';
7
+ import type $Refs from './refs';
8
+ import { fileResolver } from './resolvers/file';
9
+ import { urlResolver } from './resolvers/url';
10
+ import type { JSONSchema } from './types';
11
+ import { isHandledError } from './util/errors';
12
+ import * as url from './util/url';
13
+
14
+ /**
15
+ * Crawls the JSON schema, finds all external JSON references, and resolves their values.
16
+ * This method does not mutate the JSON schema. The resolved values are added to {@link $RefParser#$refs}.
17
+ *
18
+ * NOTE: We only care about EXTERNAL references here. INTERNAL references are only relevant when dereferencing.
19
+ *
20
+ * @returns
21
+ * The promise resolves once all JSON references in the schema have been resolved,
22
+ * including nested references that are contained in externally-referenced files.
23
+ */
24
+ export function resolveExternal(parser: $RefParser, options: $RefParserOptions) {
25
+ try {
26
+ // console.log('Resolving $ref pointers in %s', parser.$refs._root$Ref.path);
27
+ const promises = crawl(parser.schema, parser.$refs._root$Ref.path + '#', parser.$refs, options);
28
+ return Promise.all(promises);
29
+ } catch (e) {
30
+ return Promise.reject(e);
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Recursively crawls the given value, and resolves any external JSON references.
36
+ *
37
+ * @param obj - The value to crawl. If it's not an object or array, it will be ignored.
38
+ * @param path - The full path of `obj`, possibly with a JSON Pointer in the hash
39
+ * @param {boolean} external - Whether `obj` was found in an external document.
40
+ * @param $refs
41
+ * @param options
42
+ * @param seen - Internal.
43
+ *
44
+ * @returns
45
+ * Returns an array of promises. There will be one promise for each JSON reference in `obj`.
46
+ * If `obj` does not contain any JSON references, then the array will be empty.
47
+ * If any of the JSON references point to files that contain additional JSON references,
48
+ * then the corresponding promise will internally reference an array of promises.
49
+ */
50
+ function crawl<S extends object = JSONSchema>(
51
+ obj: string | Buffer | S | undefined | null,
52
+ path: string,
53
+ $refs: $Refs<S>,
54
+ options: $RefParserOptions,
55
+ seen?: Set<any>,
56
+ external?: boolean,
57
+ ) {
58
+ seen ||= new Set();
59
+ let promises: any = [];
60
+
61
+ if (obj && typeof obj === 'object' && !ArrayBuffer.isView(obj) && !seen.has(obj)) {
62
+ seen.add(obj); // Track previously seen objects to avoid infinite recursion
63
+ if ($Ref.isExternal$Ref(obj)) {
64
+ promises.push(resolve$Ref<S>(obj, path, $refs, options));
65
+ }
66
+
67
+ const keys = Object.keys(obj) as string[];
68
+ for (const key of keys) {
69
+ const keyPath = Pointer.join(path, key);
70
+ const value = obj[key as keyof typeof obj] as string | JSONSchema | Buffer | undefined;
71
+ promises = promises.concat(crawl(value, keyPath, $refs, options, seen, external));
72
+ }
73
+ }
74
+
75
+ return promises;
76
+ }
77
+
78
+ /**
79
+ * Resolves the given JSON Reference, and then crawls the resulting value.
80
+ *
81
+ * @param $ref - The JSON Reference to resolve
82
+ * @param path - The full path of `$ref`, possibly with a JSON Pointer in the hash
83
+ * @param $refs
84
+ * @param options
85
+ *
86
+ * @returns
87
+ * The promise resolves once all JSON references in the object have been resolved,
88
+ * including nested references that are contained in externally-referenced files.
89
+ */
90
+ async function resolve$Ref<S extends object = JSONSchema>(
91
+ $ref: S,
92
+ path: string,
93
+ $refs: $Refs<S>,
94
+ options: $RefParserOptions,
95
+ ) {
96
+ const resolvedPath = url.resolve(path, ($ref as JSONSchema).$ref!);
97
+ const withoutHash = url.stripHash(resolvedPath);
98
+
99
+ // $ref.$ref = url.relative($refs._root$Ref.path, resolvedPath);
100
+
101
+ // If this ref points back to an input source we've already merged, avoid re-importing
102
+ // by checking if the path (without hash) matches a known source in parser and we can serve it internally later.
103
+ // We keep normal flow but ensure cache hit if already added.
104
+ // Do we already have this $ref?
105
+ const ref = $refs._$refs[withoutHash];
106
+ if (ref) {
107
+ // We've already parsed this $ref, so crawl it to resolve its own externals
108
+ const promises = crawl(ref.value as S, `${withoutHash}#`, $refs, options, new Set(), true);
109
+ return Promise.all(promises);
110
+ }
111
+
112
+ // Parse the $referenced file/url
113
+ const file = newFile(resolvedPath);
114
+
115
+ // Add a new $Ref for this file, even though we don't have the value yet.
116
+ // This ensures that we don't simultaneously read & parse the same file multiple times
117
+ const $refAdded = $refs._add(file.url);
118
+
119
+ try {
120
+ const resolvedInput = getResolvedInput({ pathOrUrlOrSchema: resolvedPath });
121
+
122
+ $refAdded.pathType = resolvedInput.type;
123
+
124
+ let promises: any = [];
125
+
126
+ if (resolvedInput.type !== 'json') {
127
+ const resolver = resolvedInput.type === 'file' ? fileResolver : urlResolver;
128
+ await resolver.handler({ file });
129
+ const parseResult = await parseFile(file, options);
130
+ $refAdded.value = parseResult.result;
131
+ promises = crawl(parseResult.result, `${withoutHash}#`, $refs, options, new Set(), true);
132
+ }
133
+
134
+ return Promise.all(promises);
135
+ } catch (err) {
136
+ if (isHandledError(err)) {
137
+ $refAdded.value = err;
138
+ }
139
+
140
+ throw err;
141
+ }
142
+ }
@@ -0,0 +1,25 @@
1
+ import { ono } from '@jsdevtools/ono';
2
+ import fs from 'fs';
3
+
4
+ import type { FileInfo } from '../types';
5
+ import { ResolverError } from '../util/errors';
6
+ import * as url from '../util/url';
7
+
8
+ export const fileResolver = {
9
+ handler: async ({ file }: { file: FileInfo }): Promise<void> => {
10
+ let path: string | undefined;
11
+
12
+ try {
13
+ path = url.toFileSystemPath(file.url);
14
+ } catch (error: any) {
15
+ throw new ResolverError(ono.uri(error, `Malformed URI: ${file.url}`), file.url);
16
+ }
17
+
18
+ try {
19
+ const data = await fs.promises.readFile(path);
20
+ file.data = data;
21
+ } catch (error: any) {
22
+ throw new ResolverError(ono(error, `Error opening file "${path}"`), path);
23
+ }
24
+ },
25
+ };