incur 0.3.22 → 0.3.23

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.
@@ -1 +1 @@
1
- {"version":3,"file":"Openapi.d.ts","sourceRoot":"","sources":["../src/Openapi.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAIvB,4HAA4H;AAC5H,MAAM,MAAM,WAAW,GAAG;IAAE,KAAK,CAAC,EAAE,EAAE,GAAG,SAAS,CAAA;CAAE,CAAA;AAyBpD,uBAAuB;AACvB,KAAK,YAAY,GAAG,CAAC,GAAG,EAAE,OAAO,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;AAElE,+EAA+E;AAC/E,KAAK,gBAAgB,GAAG;IACtB,IAAI,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,SAAS,CAAA;IACnC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAChC,OAAO,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,SAAS,CAAA;IACtC,GAAG,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,GAAG,CAAA;CAC3B,CAAA;AAED,0FAA0F;AAC1F,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,WAAW,EACjB,KAAK,EAAE,YAAY,EACnB,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAO,GAC9C,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAiExC"}
1
+ {"version":3,"file":"Openapi.d.ts","sourceRoot":"","sources":["../src/Openapi.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAKvB,4HAA4H;AAC5H,MAAM,MAAM,WAAW,GAAG;IAAE,KAAK,CAAC,EAAE,EAAE,GAAG,SAAS,CAAA;CAAE,CAAA;AAyBpD,uBAAuB;AACvB,KAAK,YAAY,GAAG,CAAC,GAAG,EAAE,OAAO,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;AAElE,+EAA+E;AAC/E,KAAK,gBAAgB,GAAG;IACtB,IAAI,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,SAAS,CAAA;IACnC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAChC,OAAO,CAAC,EAAE,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,SAAS,CAAA;IACtC,GAAG,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,GAAG,CAAA;CAC3B,CAAA;AAED,0FAA0F;AAC1F,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,WAAW,EACjB,KAAK,EAAE,YAAY,EACnB,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAO,GAC9C,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAiExC"}
package/dist/Openapi.js CHANGED
@@ -1,9 +1,9 @@
1
- import { dereference } from '@readme/openapi-parser';
2
1
  import { z } from 'zod';
3
2
  import * as Fetch from './Fetch.js';
3
+ import { dereference } from './internal/dereference.js';
4
4
  /** Generates incur command entries from an OpenAPI spec. Resolves all `$ref` pointers. */
5
5
  export async function generateCommands(spec, fetch, options = {}) {
6
- const resolved = (await dereference(structuredClone(spec)));
6
+ const resolved = dereference(structuredClone(spec));
7
7
  const commands = new Map();
8
8
  const paths = (resolved.paths ?? {});
9
9
  for (const [path, methods] of Object.entries(paths)) {
@@ -1 +1 @@
1
- {"version":3,"file":"Openapi.js","sourceRoot":"","sources":["../src/Openapi.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Openapi.js","sourceRoot":"","sources":["../src/Openapi.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Dereferences all local `$ref` pointers in a JSON object (e.g. `{"$ref": "#/components/schemas/User"}`),
3
+ * replacing them inline with the resolved values. Only handles local (`#/...`) references.
4
+ *
5
+ * Handles circular references by caching a mutable placeholder before recursing.
6
+ *
7
+ * Minimal reimplementation of the dereferencing behavior from `@apidevtools/json-schema-ref-parser`
8
+ * (https://github.com/APIDevTools/json-schema-ref-parser). Only supports in-memory, local-pointer
9
+ * resolution — no file/URL resolution, no `$id` scoping.
10
+ */
11
+ export declare function dereference<value>(root: value): value;
12
+ //# sourceMappingURL=dereference.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dereference.d.ts","sourceRoot":"","sources":["../../src/internal/dereference.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,GAAG,KAAK,CAGrD"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Dereferences all local `$ref` pointers in a JSON object (e.g. `{"$ref": "#/components/schemas/User"}`),
3
+ * replacing them inline with the resolved values. Only handles local (`#/...`) references.
4
+ *
5
+ * Handles circular references by caching a mutable placeholder before recursing.
6
+ *
7
+ * Minimal reimplementation of the dereferencing behavior from `@apidevtools/json-schema-ref-parser`
8
+ * (https://github.com/APIDevTools/json-schema-ref-parser). Only supports in-memory, local-pointer
9
+ * resolution — no file/URL resolution, no `$id` scoping.
10
+ */
11
+ export function dereference(root) {
12
+ const cache = new Map();
13
+ return walk(root, root, cache);
14
+ }
15
+ function walk(node, root, cache) {
16
+ if (Array.isArray(node))
17
+ return node.map((item) => walk(item, root, cache));
18
+ if (typeof node !== 'object' || node === null)
19
+ return node;
20
+ const obj = node;
21
+ // Resolve $ref pointer
22
+ if (typeof obj.$ref === 'string' && obj.$ref.startsWith('#')) {
23
+ const ref = obj.$ref;
24
+ if (cache.has(ref))
25
+ return cache.get(ref);
26
+ const resolved = resolvePointer(root, ref);
27
+ // Non-object targets (primitives, arrays) can't be circular — resolve directly
28
+ if (typeof resolved !== 'object' || resolved === null || Array.isArray(resolved)) {
29
+ const dereferenced = walk(resolved, root, cache);
30
+ cache.set(ref, dereferenced);
31
+ return dereferenced;
32
+ }
33
+ // Use a mutable placeholder so circular refs resolve to the same object.
34
+ // If the walked result is not a plain object (e.g. chained ref to primitive/array),
35
+ // skip the placeholder and cache directly.
36
+ const placeholder = {};
37
+ cache.set(ref, placeholder);
38
+ const dereferenced = walk(resolved, root, cache);
39
+ if (typeof dereferenced !== 'object' || dereferenced === null || Array.isArray(dereferenced)) {
40
+ cache.set(ref, dereferenced);
41
+ return dereferenced;
42
+ }
43
+ Object.assign(placeholder, dereferenced);
44
+ return placeholder;
45
+ }
46
+ const result = {};
47
+ for (const key of Object.keys(obj))
48
+ result[key] = walk(obj[key], root, cache);
49
+ return result;
50
+ }
51
+ /** Resolves a JSON Pointer (e.g. `#/components/schemas/User`) against a root object. */
52
+ function resolvePointer(root, pointer) {
53
+ // "#" or "#/" → root
54
+ const fragment = pointer.slice(1);
55
+ if (fragment === '' || fragment === '/')
56
+ return root;
57
+ const parts = fragment
58
+ .slice(1)
59
+ .split('/')
60
+ .map((p) => p.replace(/~1/g, '/').replace(/~0/g, '~'));
61
+ let current = root;
62
+ for (const part of parts) {
63
+ if (typeof current !== 'object' || current === null)
64
+ throw new Error(`Cannot resolve $ref "${pointer}": path segment "${part}" not found`);
65
+ current = current[part];
66
+ if (current === undefined)
67
+ throw new Error(`Cannot resolve $ref "${pointer}": "${part}" not found`);
68
+ }
69
+ return current;
70
+ }
71
+ //# sourceMappingURL=dereference.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dereference.js","sourceRoot":"","sources":["../../src/internal/dereference.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "incur",
3
3
  "type": "module",
4
- "version": "0.3.22",
4
+ "version": "0.3.23",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -16,7 +16,6 @@
16
16
  "dependencies": {
17
17
  "@cfworker/json-schema": "^4.1.1",
18
18
  "@modelcontextprotocol/server": "^2.0.0-alpha.2",
19
- "@readme/openapi-parser": "^6.0.0",
20
19
  "@toon-format/toon": "^2.1.0",
21
20
  "tokenx": "^1.3.0",
22
21
  "yaml": "^2.8.2",
package/src/Openapi.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { dereference } from '@readme/openapi-parser'
2
1
  import { z } from 'zod'
3
2
 
4
3
  import * as Fetch from './Fetch.js'
4
+ import { dereference } from './internal/dereference.js'
5
5
 
6
6
  /** A minimal OpenAPI 3.x spec shape. Accepts both hand-written specs and generated ones (e.g. from `@hono/zod-openapi`). */
7
7
  export type OpenAPISpec = { paths?: {} | undefined }
@@ -46,7 +46,7 @@ export async function generateCommands(
46
46
  fetch: FetchHandler,
47
47
  options: { basePath?: string | undefined } = {},
48
48
  ): Promise<Map<string, GeneratedCommand>> {
49
- const resolved = (await dereference(structuredClone(spec) as any)) as unknown as OpenAPISpec
49
+ const resolved = dereference(structuredClone(spec)) as OpenAPISpec
50
50
  const commands = new Map<string, GeneratedCommand>()
51
51
  const paths = (resolved.paths ?? {}) as Record<string, Record<string, unknown>>
52
52
 
@@ -0,0 +1,695 @@
1
+ import { describe, expect, test } from 'vitest'
2
+
3
+ import { dereference } from './dereference.js'
4
+
5
+ describe('dereference', () => {
6
+ test('resolves basic $ref', () => {
7
+ const spec = {
8
+ paths: {
9
+ '/users': {
10
+ get: {
11
+ responses: {
12
+ '200': {
13
+ content: {
14
+ 'application/json': {
15
+ schema: { $ref: '#/components/schemas/User' },
16
+ },
17
+ },
18
+ },
19
+ },
20
+ },
21
+ },
22
+ },
23
+ components: {
24
+ schemas: {
25
+ User: {
26
+ type: 'object',
27
+ properties: { name: { type: 'string' } },
28
+ },
29
+ },
30
+ },
31
+ }
32
+ const result = dereference(spec) as any
33
+ expect(result.paths['/users'].get.responses['200'].content['application/json'].schema).toEqual({
34
+ type: 'object',
35
+ properties: { name: { type: 'string' } },
36
+ })
37
+ })
38
+
39
+ test('resolves nested $ref (ref target contains another ref)', () => {
40
+ const spec = {
41
+ components: {
42
+ schemas: {
43
+ Name: { type: 'string' },
44
+ User: {
45
+ type: 'object',
46
+ properties: { name: { $ref: '#/components/schemas/Name' } },
47
+ },
48
+ },
49
+ },
50
+ root: { $ref: '#/components/schemas/User' },
51
+ }
52
+ const result = dereference(spec) as any
53
+ expect(result.root).toEqual({
54
+ type: 'object',
55
+ properties: { name: { type: 'string' } },
56
+ })
57
+ })
58
+
59
+ test('handles circular $ref without infinite loop', () => {
60
+ const spec = {
61
+ components: {
62
+ schemas: {
63
+ Node: {
64
+ type: 'object',
65
+ properties: {
66
+ value: { type: 'string' },
67
+ child: { $ref: '#/components/schemas/Node' },
68
+ },
69
+ },
70
+ },
71
+ },
72
+ root: { $ref: '#/components/schemas/Node' },
73
+ }
74
+ const result = dereference(spec) as any
75
+ // Should resolve without hanging
76
+ expect(result.root.type).toBe('object')
77
+ expect(result.root.properties.value).toEqual({ type: 'string' })
78
+ // Circular ref should point back to the same resolved object
79
+ expect(result.root.properties.child).toBe(result.root)
80
+ })
81
+
82
+ test('resolves multiple refs to same target (shares identity)', () => {
83
+ const spec = {
84
+ components: { schemas: { Id: { type: 'number' } } },
85
+ a: { $ref: '#/components/schemas/Id' },
86
+ b: { $ref: '#/components/schemas/Id' },
87
+ }
88
+ const result = dereference(spec) as any
89
+ expect(result.a).toEqual({ type: 'number' })
90
+ expect(result.a).toBe(result.b)
91
+ })
92
+
93
+ test('resolves $ref in arrays', () => {
94
+ const spec = {
95
+ components: { schemas: { Tag: { type: 'string' } } },
96
+ items: [{ $ref: '#/components/schemas/Tag' }, { $ref: '#/components/schemas/Tag' }],
97
+ }
98
+ const result = dereference(spec) as any
99
+ expect(result.items[0]).toEqual({ type: 'string' })
100
+ expect(result.items[1]).toEqual({ type: 'string' })
101
+ })
102
+
103
+ test('handles deeply nested path', () => {
104
+ const spec = {
105
+ a: { b: { c: { d: { value: 42 } } } },
106
+ ref: { $ref: '#/a/b/c/d' },
107
+ }
108
+ const result = dereference(spec) as any
109
+ expect(result.ref).toEqual({ value: 42 })
110
+ })
111
+
112
+ test('handles JSON Pointer escaping (~0 for ~, ~1 for /)', () => {
113
+ const spec = {
114
+ 'a/b': { 'c~d': { value: 'escaped' } },
115
+ ref: { $ref: '#/a~1b/c~0d' },
116
+ }
117
+ const result = dereference(spec) as any
118
+ expect(result.ref).toEqual({ value: 'escaped' })
119
+ })
120
+
121
+ test('throws on unresolvable $ref', () => {
122
+ const spec = { ref: { $ref: '#/does/not/exist' } }
123
+ expect(() => dereference(spec)).toThrow('Cannot resolve $ref')
124
+ })
125
+
126
+ test('passes through primitives unchanged', () => {
127
+ expect(dereference('hello')).toBe('hello')
128
+ expect(dereference(42)).toBe(42)
129
+ expect(dereference(null)).toBe(null)
130
+ expect(dereference(true)).toBe(true)
131
+ })
132
+
133
+ test('does not mutate original object', () => {
134
+ const spec = {
135
+ components: { schemas: { User: { type: 'object' } } },
136
+ ref: { $ref: '#/components/schemas/User' },
137
+ }
138
+ const original = JSON.stringify(spec)
139
+ dereference(spec)
140
+ expect(JSON.stringify(spec)).toBe(original)
141
+ })
142
+
143
+ test('resolves $ref: "#" to root', () => {
144
+ const spec = { type: 'object', self: { $ref: '#' } }
145
+ const result = dereference(spec) as any
146
+ expect(result.self.type).toBe('object')
147
+ })
148
+
149
+ test('realistic OpenAPI spec with shared parameter and request body refs', () => {
150
+ const spec = {
151
+ openapi: '3.0.0',
152
+ info: { title: 'Test', version: '1.0.0' },
153
+ paths: {
154
+ '/users/{id}': {
155
+ get: {
156
+ operationId: 'getUser',
157
+ parameters: [{ $ref: '#/components/parameters/UserId' }],
158
+ responses: {
159
+ '200': {
160
+ content: {
161
+ 'application/json': {
162
+ schema: { $ref: '#/components/schemas/User' },
163
+ },
164
+ },
165
+ },
166
+ },
167
+ },
168
+ put: {
169
+ operationId: 'updateUser',
170
+ parameters: [{ $ref: '#/components/parameters/UserId' }],
171
+ requestBody: {
172
+ content: {
173
+ 'application/json': {
174
+ schema: { $ref: '#/components/schemas/UserInput' },
175
+ },
176
+ },
177
+ },
178
+ },
179
+ },
180
+ },
181
+ components: {
182
+ parameters: {
183
+ UserId: {
184
+ name: 'id',
185
+ in: 'path',
186
+ required: true,
187
+ schema: { type: 'number' },
188
+ },
189
+ },
190
+ schemas: {
191
+ User: {
192
+ type: 'object',
193
+ properties: {
194
+ id: { type: 'number' },
195
+ name: { type: 'string' },
196
+ },
197
+ },
198
+ UserInput: {
199
+ type: 'object',
200
+ properties: {
201
+ name: { type: 'string' },
202
+ },
203
+ required: ['name'],
204
+ },
205
+ },
206
+ },
207
+ }
208
+ const result = dereference(spec) as any
209
+ const getParams = result.paths['/users/{id}'].get.parameters
210
+ expect(getParams[0].name).toBe('id')
211
+ expect(getParams[0].in).toBe('path')
212
+ // Both GET and PUT share the same resolved parameter
213
+ const putParams = result.paths['/users/{id}'].put.parameters
214
+ expect(putParams[0]).toBe(getParams[0])
215
+ // Request body schema resolved
216
+ const bodySchema =
217
+ result.paths['/users/{id}'].put.requestBody.content['application/json'].schema
218
+ expect(bodySchema.properties.name).toEqual({ type: 'string' })
219
+ expect(bodySchema.required).toEqual(['name'])
220
+ })
221
+
222
+ test('mutual circular refs', () => {
223
+ const spec = {
224
+ components: {
225
+ schemas: {
226
+ A: {
227
+ type: 'object',
228
+ properties: { b: { $ref: '#/components/schemas/B' } },
229
+ },
230
+ B: {
231
+ type: 'object',
232
+ properties: { a: { $ref: '#/components/schemas/A' } },
233
+ },
234
+ },
235
+ },
236
+ root: { $ref: '#/components/schemas/A' },
237
+ }
238
+ const result = dereference(spec) as any
239
+ expect(result.root.type).toBe('object')
240
+ expect(result.root.properties.b.type).toBe('object')
241
+ expect(result.root.properties.b.properties.a).toBe(result.root)
242
+ })
243
+
244
+ test('$ref target is a primitive (string)', () => {
245
+ const spec = {
246
+ components: { values: { name: 'Alice' } },
247
+ ref: { $ref: '#/components/values/name' },
248
+ }
249
+ const result = dereference(spec) as any
250
+ expect(result.ref).toBe('Alice')
251
+ })
252
+
253
+ test('$ref target is a primitive (number)', () => {
254
+ const spec = {
255
+ components: { values: { count: 42 } },
256
+ ref: { $ref: '#/components/values/count' },
257
+ }
258
+ const result = dereference(spec) as any
259
+ expect(result.ref).toBe(42)
260
+ })
261
+
262
+ test('$ref target is null', () => {
263
+ const spec = {
264
+ components: { values: { empty: null } },
265
+ ref: { $ref: '#/components/values/empty' },
266
+ }
267
+ const result = dereference(spec) as any
268
+ expect(result.ref).toBe(null)
269
+ })
270
+
271
+ test('$ref target is an array', () => {
272
+ const spec = {
273
+ components: { values: { tags: ['a', 'b', 'c'] } },
274
+ ref: { $ref: '#/components/values/tags' },
275
+ }
276
+ const result = dereference(spec) as any
277
+ expect(result.ref).toEqual(['a', 'b', 'c'])
278
+ })
279
+
280
+ test('$ref to array element by index', () => {
281
+ const spec = {
282
+ items: [{ name: 'first' }, { name: 'second' }],
283
+ ref: { $ref: '#/items/1' },
284
+ }
285
+ const result = dereference(spec) as any
286
+ expect(result.ref).toEqual({ name: 'second' })
287
+ })
288
+
289
+ test('chain of refs (A -> B -> C)', () => {
290
+ const spec = {
291
+ a: { $ref: '#/b' },
292
+ b: { $ref: '#/c' },
293
+ c: { value: 'end' },
294
+ }
295
+ const result = dereference(spec) as any
296
+ expect(result.a).toEqual({ value: 'end' })
297
+ expect(result.b).toEqual({ value: 'end' })
298
+ })
299
+
300
+ test('triple circular (A -> B -> C -> A)', () => {
301
+ const spec = {
302
+ components: {
303
+ schemas: {
304
+ A: { type: 'A', next: { $ref: '#/components/schemas/B' } },
305
+ B: { type: 'B', next: { $ref: '#/components/schemas/C' } },
306
+ C: { type: 'C', next: { $ref: '#/components/schemas/A' } },
307
+ },
308
+ },
309
+ root: { $ref: '#/components/schemas/A' },
310
+ }
311
+ const result = dereference(spec) as any
312
+ expect(result.root.type).toBe('A')
313
+ expect(result.root.next.type).toBe('B')
314
+ expect(result.root.next.next.type).toBe('C')
315
+ expect(result.root.next.next.next).toBe(result.root)
316
+ })
317
+
318
+ test('$ref with sibling properties (OpenAPI 3.1 style)', () => {
319
+ const spec = {
320
+ components: {
321
+ schemas: {
322
+ User: { type: 'object', properties: { name: { type: 'string' } } },
323
+ },
324
+ },
325
+ ref: {
326
+ $ref: '#/components/schemas/User',
327
+ description: 'A user object',
328
+ },
329
+ }
330
+ const result = dereference(spec) as any
331
+ // siblings are dropped (ref replaces the whole node)
332
+ expect(result.ref.type).toBe('object')
333
+ expect(result.ref.description).toBeUndefined()
334
+ })
335
+
336
+ test('root is an array', () => {
337
+ const root = [{ a: 1 }, { b: 2 }]
338
+ const result = dereference(root) as any
339
+ expect(result).toEqual([{ a: 1 }, { b: 2 }])
340
+ })
341
+
342
+ test('empty object', () => {
343
+ expect(dereference({})).toEqual({})
344
+ })
345
+
346
+ test('$ref "#/" resolves to root', () => {
347
+ const spec = { type: 'root', self: { $ref: '#/' } }
348
+ const result = dereference(spec) as any
349
+ expect(result.self.type).toBe('root')
350
+ })
351
+
352
+ test('falsy primitive targets (false, 0, empty string)', () => {
353
+ const spec = {
354
+ vals: { a: false, b: 0, c: '' },
355
+ refA: { $ref: '#/vals/a' },
356
+ refB: { $ref: '#/vals/b' },
357
+ refC: { $ref: '#/vals/c' },
358
+ }
359
+ const result = dereference(spec) as any
360
+ expect(result.refA).toBe(false)
361
+ expect(result.refB).toBe(0)
362
+ expect(result.refC).toBe('')
363
+ })
364
+
365
+ test('$ref target is an empty array', () => {
366
+ const spec = {
367
+ vals: { empty: [] as unknown[] },
368
+ ref: { $ref: '#/vals/empty' },
369
+ }
370
+ const result = dereference(spec) as any
371
+ expect(result.ref).toEqual([])
372
+ })
373
+
374
+ test('$ref target is an empty object', () => {
375
+ const spec = {
376
+ vals: { empty: {} },
377
+ ref: { $ref: '#/vals/empty' },
378
+ }
379
+ const result = dereference(spec) as any
380
+ expect(result.ref).toEqual({})
381
+ })
382
+
383
+ test('chained ref to primitive (A -> B -> string)', () => {
384
+ const spec = {
385
+ vals: { greeting: 'hello' },
386
+ b: { $ref: '#/vals/greeting' },
387
+ a: { $ref: '#/b' },
388
+ }
389
+ const result = dereference(spec) as any
390
+ expect(result.a).toBe('hello')
391
+ expect(result.b).toBe('hello')
392
+ })
393
+
394
+ test('$ref with non-string value is treated as normal object', () => {
395
+ const spec = { obj: { $ref: 123, other: 'value' } }
396
+ const result = dereference(spec) as any
397
+ expect(result.obj).toEqual({ $ref: 123, other: 'value' })
398
+ })
399
+
400
+ test('$ref that does not start with # is left as-is', () => {
401
+ const spec = { obj: { $ref: 'http://example.com/schema.json' } }
402
+ const result = dereference(spec) as any
403
+ expect(result.obj).toEqual({ $ref: 'http://example.com/schema.json' })
404
+ })
405
+
406
+ test('$ref inside array inside a $ref target', () => {
407
+ const spec = {
408
+ components: {
409
+ schemas: {
410
+ Tag: { type: 'string' },
411
+ User: {
412
+ type: 'object',
413
+ properties: {
414
+ tags: {
415
+ type: 'array',
416
+ items: { $ref: '#/components/schemas/Tag' },
417
+ },
418
+ },
419
+ },
420
+ },
421
+ },
422
+ root: { $ref: '#/components/schemas/User' },
423
+ }
424
+ const result = dereference(spec) as any
425
+ expect(result.root.properties.tags.items).toEqual({ type: 'string' })
426
+ })
427
+
428
+ test('allOf/oneOf/anyOf with $ref items', () => {
429
+ const spec = {
430
+ components: {
431
+ schemas: {
432
+ Name: { type: 'string' },
433
+ Age: { type: 'number' },
434
+ },
435
+ },
436
+ root: {
437
+ allOf: [
438
+ { $ref: '#/components/schemas/Name' },
439
+ { $ref: '#/components/schemas/Age' },
440
+ ],
441
+ },
442
+ }
443
+ const result = dereference(spec) as any
444
+ expect(result.root.allOf[0]).toEqual({ type: 'string' })
445
+ expect(result.root.allOf[1]).toEqual({ type: 'number' })
446
+ })
447
+
448
+ test('$ref inside deeply nested arrays', () => {
449
+ const spec = {
450
+ vals: { x: { value: 1 } },
451
+ nested: [[{ $ref: '#/vals/x' }]],
452
+ }
453
+ const result = dereference(spec) as any
454
+ expect(result.nested[0][0]).toEqual({ value: 1 })
455
+ })
456
+
457
+ test('circular ref inside an array (items ref self)', () => {
458
+ const spec = {
459
+ components: {
460
+ schemas: {
461
+ Tree: {
462
+ type: 'object',
463
+ properties: {
464
+ children: {
465
+ type: 'array',
466
+ items: { $ref: '#/components/schemas/Tree' },
467
+ },
468
+ },
469
+ },
470
+ },
471
+ },
472
+ root: { $ref: '#/components/schemas/Tree' },
473
+ }
474
+ const result = dereference(spec) as any
475
+ expect(result.root.type).toBe('object')
476
+ expect(result.root.properties.children.items).toBe(result.root)
477
+ })
478
+
479
+ test('$ref target is a boolean true', () => {
480
+ const spec = {
481
+ vals: { flag: true },
482
+ ref: { $ref: '#/vals/flag' },
483
+ }
484
+ const result = dereference(spec) as any
485
+ expect(result.ref).toBe(true)
486
+ })
487
+
488
+ test('same $ref used in different subtrees resolves identically', () => {
489
+ const spec = {
490
+ components: { schemas: { S: { type: 'object' } } },
491
+ tree: {
492
+ left: { schema: { $ref: '#/components/schemas/S' } },
493
+ right: { schema: { $ref: '#/components/schemas/S' } },
494
+ },
495
+ }
496
+ const result = dereference(spec) as any
497
+ expect(result.tree.left.schema).toBe(result.tree.right.schema)
498
+ })
499
+
500
+ test('ref target with array value containing refs', () => {
501
+ const spec = {
502
+ components: {
503
+ schemas: { Tag: { type: 'string' } },
504
+ lists: {
505
+ tags: [{ $ref: '#/components/schemas/Tag' }, { literal: true }],
506
+ },
507
+ },
508
+ ref: { $ref: '#/components/lists/tags' },
509
+ }
510
+ const result = dereference(spec) as any
511
+ expect(result.ref[0]).toEqual({ type: 'string' })
512
+ expect(result.ref[1]).toEqual({ literal: true })
513
+ })
514
+
515
+ test('root object is itself a $ref (self-referential)', () => {
516
+ const spec = { $ref: '#', type: 'object' }
517
+ const result = dereference(spec) as any
518
+ // $ref takes precedence, siblings (type) are dropped per OpenAPI 3.0.
519
+ // Circular self-ref resolves without infinite loop.
520
+ expect(result).toBeDefined()
521
+ expect(result.type).toBeUndefined()
522
+ })
523
+
524
+ test('non-local $ref is preserved (not resolved)', () => {
525
+ const spec = {
526
+ a: { $ref: 'https://example.com/schema.json#/Foo' },
527
+ b: { $ref: './other.yaml#/Bar' },
528
+ c: { $ref: 'relative.json' },
529
+ }
530
+ const result = dereference(spec) as any
531
+ expect(result.a.$ref).toBe('https://example.com/schema.json#/Foo')
532
+ expect(result.b.$ref).toBe('./other.yaml#/Bar')
533
+ expect(result.c.$ref).toBe('relative.json')
534
+ })
535
+
536
+ test('$ref target contains a non-local $ref (preserved after deref)', () => {
537
+ const spec = {
538
+ components: {
539
+ schemas: {
540
+ External: { type: 'object', nested: { $ref: 'https://example.com/other.json' } },
541
+ },
542
+ },
543
+ root: { $ref: '#/components/schemas/External' },
544
+ }
545
+ const result = dereference(spec) as any
546
+ expect(result.root.type).toBe('object')
547
+ expect(result.root.nested.$ref).toBe('https://example.com/other.json')
548
+ })
549
+
550
+ test('forward reference (A uses B, B defined after A)', () => {
551
+ const spec = {
552
+ components: {
553
+ schemas: {
554
+ A: { type: 'object', child: { $ref: '#/components/schemas/B' } },
555
+ B: { type: 'string' },
556
+ },
557
+ },
558
+ root: { $ref: '#/components/schemas/A' },
559
+ }
560
+ const result = dereference(spec) as any
561
+ expect(result.root.child).toEqual({ type: 'string' })
562
+ })
563
+
564
+ test('deep chain of refs (A -> B -> C -> D -> E -> value)', () => {
565
+ const spec = {
566
+ a: { $ref: '#/b' },
567
+ b: { $ref: '#/c' },
568
+ c: { $ref: '#/d' },
569
+ d: { $ref: '#/e' },
570
+ e: { value: 'deep' },
571
+ }
572
+ const result = dereference(spec) as any
573
+ expect(result.a).toEqual({ value: 'deep' })
574
+ })
575
+
576
+ test('deep chain of refs to array', () => {
577
+ const spec = {
578
+ a: { $ref: '#/b' },
579
+ b: { $ref: '#/c' },
580
+ c: [1, 2, 3],
581
+ }
582
+ const result = dereference(spec) as any
583
+ expect(result.a).toEqual([1, 2, 3])
584
+ })
585
+
586
+ test('combined ~0 and ~1 escaping in same pointer segment', () => {
587
+ const spec = {
588
+ 'a~/b': { value: 'complex' },
589
+ ref: { $ref: '#/a~0~1b' },
590
+ }
591
+ const result = dereference(spec) as any
592
+ expect(result.ref).toEqual({ value: 'complex' })
593
+ })
594
+
595
+ test('$ref to nested value inside a ref target', () => {
596
+ const spec = {
597
+ components: {
598
+ schemas: {
599
+ User: {
600
+ type: 'object',
601
+ properties: { name: { type: 'string', maxLength: 100 } },
602
+ },
603
+ },
604
+ },
605
+ nameSchema: { $ref: '#/components/schemas/User/properties/name' },
606
+ }
607
+ const result = dereference(spec) as any
608
+ expect(result.nameSchema).toEqual({ type: 'string', maxLength: 100 })
609
+ })
610
+
611
+ test('multiple independent circular cycles', () => {
612
+ const spec = {
613
+ components: {
614
+ schemas: {
615
+ X: { type: 'X', self: { $ref: '#/components/schemas/X' } },
616
+ Y: { type: 'Y', self: { $ref: '#/components/schemas/Y' } },
617
+ },
618
+ },
619
+ refX: { $ref: '#/components/schemas/X' },
620
+ refY: { $ref: '#/components/schemas/Y' },
621
+ }
622
+ const result = dereference(spec) as any
623
+ expect(result.refX.type).toBe('X')
624
+ expect(result.refX.self).toBe(result.refX)
625
+ expect(result.refY.type).toBe('Y')
626
+ expect(result.refY.self).toBe(result.refY)
627
+ // X and Y are distinct
628
+ expect(result.refX).not.toBe(result.refY)
629
+ })
630
+
631
+ test('object with constructor/toString keys (no prototype issues)', () => {
632
+ const spec = {
633
+ vals: { constructor: { value: 1 }, toString: { value: 2 } },
634
+ a: { $ref: '#/vals/constructor' },
635
+ b: { $ref: '#/vals/toString' },
636
+ }
637
+ const result = dereference(spec) as any
638
+ expect(result.a).toEqual({ value: 1 })
639
+ expect(result.b).toEqual({ value: 2 })
640
+ })
641
+
642
+ test('ref to boolean nested inside object', () => {
643
+ const spec = {
644
+ config: { features: { enabled: true, disabled: false } },
645
+ a: { $ref: '#/config/features/enabled' },
646
+ b: { $ref: '#/config/features/disabled' },
647
+ }
648
+ const result = dereference(spec) as any
649
+ expect(result.a).toBe(true)
650
+ expect(result.b).toBe(false)
651
+ })
652
+
653
+ test('array of $refs to different types', () => {
654
+ const spec = {
655
+ vals: { str: 'hello', num: 42, obj: { x: 1 }, arr: [1, 2] },
656
+ refs: [
657
+ { $ref: '#/vals/str' },
658
+ { $ref: '#/vals/num' },
659
+ { $ref: '#/vals/obj' },
660
+ { $ref: '#/vals/arr' },
661
+ ],
662
+ }
663
+ const result = dereference(spec) as any
664
+ expect(result.refs[0]).toBe('hello')
665
+ expect(result.refs[1]).toBe(42)
666
+ expect(result.refs[2]).toEqual({ x: 1 })
667
+ expect(result.refs[3]).toEqual([1, 2])
668
+ })
669
+
670
+ test('circular ref where first encounter is NOT via $ref', () => {
671
+ // Schema defines Node inline (not behind a $ref), but Node's child uses $ref
672
+ const spec = {
673
+ components: {
674
+ schemas: {
675
+ Node: {
676
+ type: 'object',
677
+ properties: {
678
+ child: { $ref: '#/components/schemas/Node' },
679
+ },
680
+ },
681
+ },
682
+ },
683
+ // Access Node directly through the tree walk, not via $ref
684
+ direct: {
685
+ schema: {
686
+ type: 'wrapper',
687
+ inner: { $ref: '#/components/schemas/Node' },
688
+ },
689
+ },
690
+ }
691
+ const result = dereference(spec) as any
692
+ expect(result.direct.schema.inner.type).toBe('object')
693
+ expect(result.direct.schema.inner.properties.child).toBe(result.direct.schema.inner)
694
+ })
695
+ })
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Dereferences all local `$ref` pointers in a JSON object (e.g. `{"$ref": "#/components/schemas/User"}`),
3
+ * replacing them inline with the resolved values. Only handles local (`#/...`) references.
4
+ *
5
+ * Handles circular references by caching a mutable placeholder before recursing.
6
+ *
7
+ * Minimal reimplementation of the dereferencing behavior from `@apidevtools/json-schema-ref-parser`
8
+ * (https://github.com/APIDevTools/json-schema-ref-parser). Only supports in-memory, local-pointer
9
+ * resolution — no file/URL resolution, no `$id` scoping.
10
+ */
11
+ export function dereference<value>(root: value): value {
12
+ const cache = new Map<string, unknown>()
13
+ return walk(root, root, cache) as value
14
+ }
15
+
16
+ function walk(node: unknown, root: unknown, cache: Map<string, unknown>): unknown {
17
+ if (Array.isArray(node)) return node.map((item) => walk(item, root, cache))
18
+
19
+ if (typeof node !== 'object' || node === null) return node
20
+
21
+ const obj = node as Record<string, unknown>
22
+
23
+ // Resolve $ref pointer
24
+ if (typeof obj.$ref === 'string' && obj.$ref.startsWith('#')) {
25
+ const ref = obj.$ref
26
+ if (cache.has(ref)) return cache.get(ref)
27
+
28
+ const resolved = resolvePointer(root, ref)
29
+
30
+ // Non-object targets (primitives, arrays) can't be circular — resolve directly
31
+ if (typeof resolved !== 'object' || resolved === null || Array.isArray(resolved)) {
32
+ const dereferenced = walk(resolved, root, cache)
33
+ cache.set(ref, dereferenced)
34
+ return dereferenced
35
+ }
36
+
37
+ // Use a mutable placeholder so circular refs resolve to the same object.
38
+ // If the walked result is not a plain object (e.g. chained ref to primitive/array),
39
+ // skip the placeholder and cache directly.
40
+ const placeholder: Record<string, unknown> = {}
41
+ cache.set(ref, placeholder)
42
+ const dereferenced = walk(resolved, root, cache)
43
+ if (typeof dereferenced !== 'object' || dereferenced === null || Array.isArray(dereferenced)) {
44
+ cache.set(ref, dereferenced)
45
+ return dereferenced
46
+ }
47
+ Object.assign(placeholder, dereferenced)
48
+ return placeholder
49
+ }
50
+
51
+ const result: Record<string, unknown> = {}
52
+ for (const key of Object.keys(obj)) result[key] = walk(obj[key], root, cache)
53
+ return result
54
+ }
55
+
56
+ /** Resolves a JSON Pointer (e.g. `#/components/schemas/User`) against a root object. */
57
+ function resolvePointer(root: unknown, pointer: string): unknown {
58
+ // "#" or "#/" → root
59
+ const fragment = pointer.slice(1)
60
+ if (fragment === '' || fragment === '/') return root
61
+
62
+ const parts = fragment
63
+ .slice(1)
64
+ .split('/')
65
+ .map((p) => p.replace(/~1/g, '/').replace(/~0/g, '~'))
66
+
67
+ let current: unknown = root
68
+ for (const part of parts) {
69
+ if (typeof current !== 'object' || current === null)
70
+ throw new Error(`Cannot resolve $ref "${pointer}": path segment "${part}" not found`)
71
+ current = (current as Record<string, unknown>)[part]
72
+ if (current === undefined) throw new Error(`Cannot resolve $ref "${pointer}": "${part}" not found`)
73
+ }
74
+ return current
75
+ }