incur 0.3.21 → 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.
- package/dist/Cli.d.ts +1 -1
- package/dist/Cli.d.ts.map +1 -1
- package/dist/Cli.js +3 -3
- package/dist/Cli.js.map +1 -1
- package/dist/Mcp.d.ts +16 -4
- package/dist/Mcp.d.ts.map +1 -1
- package/dist/Mcp.js +7 -6
- package/dist/Mcp.js.map +1 -1
- package/dist/Openapi.d.ts.map +1 -1
- package/dist/Openapi.js +2 -2
- package/dist/Openapi.js.map +1 -1
- package/dist/internal/dereference.d.ts +12 -0
- package/dist/internal/dereference.d.ts.map +1 -0
- package/dist/internal/dereference.js +71 -0
- package/dist/internal/dereference.js.map +1 -0
- package/package.json +3 -3
- package/src/Cli.ts +4 -5
- package/src/Mcp.test.ts +16 -0
- package/src/Mcp.ts +15 -9
- package/src/Openapi.ts +2 -2
- package/src/e2e.test.ts +41 -0
- package/src/internal/dereference.test.ts +695 -0
- package/src/internal/dereference.ts +75 -0
|
@@ -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
|
+
}
|