@scalar/json-magic 0.1.0
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/.turbo/turbo-build.log +9 -0
- package/CHANGELOG.md +7 -0
- package/LICENSE +21 -0
- package/README.md +356 -0
- package/dist/bundle/bundle.d.ts +292 -0
- package/dist/bundle/bundle.d.ts.map +1 -0
- package/dist/bundle/bundle.js +259 -0
- package/dist/bundle/bundle.js.map +7 -0
- package/dist/bundle/create-limiter.d.ts +21 -0
- package/dist/bundle/create-limiter.d.ts.map +1 -0
- package/dist/bundle/create-limiter.js +31 -0
- package/dist/bundle/create-limiter.js.map +7 -0
- package/dist/bundle/index.d.ts +2 -0
- package/dist/bundle/index.d.ts.map +1 -0
- package/dist/bundle/index.js +5 -0
- package/dist/bundle/index.js.map +7 -0
- package/dist/bundle/plugins/browser.d.ts +4 -0
- package/dist/bundle/plugins/browser.d.ts.map +1 -0
- package/dist/bundle/plugins/browser.js +9 -0
- package/dist/bundle/plugins/browser.js.map +7 -0
- package/dist/bundle/plugins/fetch-urls/index.d.ts +39 -0
- package/dist/bundle/plugins/fetch-urls/index.d.ts.map +1 -0
- package/dist/bundle/plugins/fetch-urls/index.js +48 -0
- package/dist/bundle/plugins/fetch-urls/index.js.map +7 -0
- package/dist/bundle/plugins/node.d.ts +5 -0
- package/dist/bundle/plugins/node.d.ts.map +1 -0
- package/dist/bundle/plugins/node.js +11 -0
- package/dist/bundle/plugins/node.js.map +7 -0
- package/dist/bundle/plugins/parse-json/index.d.ts +13 -0
- package/dist/bundle/plugins/parse-json/index.d.ts.map +1 -0
- package/dist/bundle/plugins/parse-json/index.js +22 -0
- package/dist/bundle/plugins/parse-json/index.js.map +7 -0
- package/dist/bundle/plugins/parse-yaml/index.d.ts +13 -0
- package/dist/bundle/plugins/parse-yaml/index.d.ts.map +1 -0
- package/dist/bundle/plugins/parse-yaml/index.js +23 -0
- package/dist/bundle/plugins/parse-yaml/index.js.map +7 -0
- package/dist/bundle/plugins/read-files/index.d.ts +29 -0
- package/dist/bundle/plugins/read-files/index.d.ts.map +1 -0
- package/dist/bundle/plugins/read-files/index.js +30 -0
- package/dist/bundle/plugins/read-files/index.js.map +7 -0
- package/dist/bundle/value-generator.d.ts +79 -0
- package/dist/bundle/value-generator.d.ts.map +1 -0
- package/dist/bundle/value-generator.js +55 -0
- package/dist/bundle/value-generator.js.map +7 -0
- package/dist/dereference/dereference.d.ts +45 -0
- package/dist/dereference/dereference.d.ts.map +1 -0
- package/dist/dereference/dereference.js +37 -0
- package/dist/dereference/dereference.js.map +7 -0
- package/dist/dereference/index.d.ts +2 -0
- package/dist/dereference/index.d.ts.map +1 -0
- package/dist/dereference/index.js +5 -0
- package/dist/dereference/index.js.map +7 -0
- package/dist/diff/apply.d.ts +35 -0
- package/dist/diff/apply.d.ts.map +1 -0
- package/dist/diff/apply.js +40 -0
- package/dist/diff/apply.js.map +7 -0
- package/dist/diff/diff.d.ts +56 -0
- package/dist/diff/diff.d.ts.map +1 -0
- package/dist/diff/diff.js +33 -0
- package/dist/diff/diff.js.map +7 -0
- package/dist/diff/index.d.ts +5 -0
- package/dist/diff/index.d.ts.map +1 -0
- package/dist/diff/index.js +9 -0
- package/dist/diff/index.js.map +7 -0
- package/dist/diff/merge.d.ts +43 -0
- package/dist/diff/merge.d.ts.map +1 -0
- package/dist/diff/merge.js +61 -0
- package/dist/diff/merge.js.map +7 -0
- package/dist/diff/trie.d.ts +64 -0
- package/dist/diff/trie.d.ts.map +1 -0
- package/dist/diff/trie.js +82 -0
- package/dist/diff/trie.js.map +7 -0
- package/dist/diff/utils.d.ts +63 -0
- package/dist/diff/utils.d.ts.map +1 -0
- package/dist/diff/utils.js +48 -0
- package/dist/diff/utils.js.map +7 -0
- package/dist/magic-proxy/index.d.ts +2 -0
- package/dist/magic-proxy/index.d.ts.map +1 -0
- package/dist/magic-proxy/index.js +6 -0
- package/dist/magic-proxy/index.js.map +7 -0
- package/dist/magic-proxy/proxy.d.ts +63 -0
- package/dist/magic-proxy/proxy.d.ts.map +1 -0
- package/dist/magic-proxy/proxy.js +108 -0
- package/dist/magic-proxy/proxy.js.map +7 -0
- package/dist/polyfills/index.d.ts +2 -0
- package/dist/polyfills/index.d.ts.map +1 -0
- package/dist/polyfills/index.js +25 -0
- package/dist/polyfills/index.js.map +7 -0
- package/dist/polyfills/path.d.ts +24 -0
- package/dist/polyfills/path.d.ts.map +1 -0
- package/dist/polyfills/path.js +174 -0
- package/dist/polyfills/path.js.map +7 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/dist/types.js.map +7 -0
- package/dist/utils/escape-json-pointer.d.ts +7 -0
- package/dist/utils/escape-json-pointer.d.ts.map +1 -0
- package/dist/utils/escape-json-pointer.js +7 -0
- package/dist/utils/escape-json-pointer.js.map +7 -0
- package/dist/utils/get-segments-from-path.d.ts +5 -0
- package/dist/utils/get-segments-from-path.d.ts.map +1 -0
- package/dist/utils/get-segments-from-path.js +11 -0
- package/dist/utils/get-segments-from-path.js.map +7 -0
- package/dist/utils/is-json-object.d.ts +18 -0
- package/dist/utils/is-json-object.d.ts.map +1 -0
- package/dist/utils/is-json-object.js +16 -0
- package/dist/utils/is-json-object.js.map +7 -0
- package/dist/utils/is-object.d.ts +5 -0
- package/dist/utils/is-object.d.ts.map +1 -0
- package/dist/utils/is-object.js +5 -0
- package/dist/utils/is-object.js.map +7 -0
- package/dist/utils/is-yaml.d.ts +17 -0
- package/dist/utils/is-yaml.d.ts.map +1 -0
- package/dist/utils/is-yaml.js +7 -0
- package/dist/utils/is-yaml.js.map +7 -0
- package/dist/utils/json-path-utils.d.ts +23 -0
- package/dist/utils/json-path-utils.d.ts.map +1 -0
- package/dist/utils/json-path-utils.js +16 -0
- package/dist/utils/json-path-utils.js.map +7 -0
- package/dist/utils/normalize.d.ts +5 -0
- package/dist/utils/normalize.d.ts.map +1 -0
- package/dist/utils/normalize.js +28 -0
- package/dist/utils/normalize.js.map +7 -0
- package/dist/utils/unescape-json-pointer.d.ts +8 -0
- package/dist/utils/unescape-json-pointer.d.ts.map +1 -0
- package/dist/utils/unescape-json-pointer.js +7 -0
- package/dist/utils/unescape-json-pointer.js.map +7 -0
- package/esbuild.ts +13 -0
- package/package.json +65 -0
- package/src/bundle/bundle.test.ts +1843 -0
- package/src/bundle/bundle.ts +758 -0
- package/src/bundle/create-limiter.test.ts +28 -0
- package/src/bundle/create-limiter.ts +52 -0
- package/src/bundle/index.ts +2 -0
- package/src/bundle/plugins/browser.ts +4 -0
- package/src/bundle/plugins/fetch-urls/index.test.ts +147 -0
- package/src/bundle/plugins/fetch-urls/index.ts +94 -0
- package/src/bundle/plugins/node.ts +5 -0
- package/src/bundle/plugins/parse-json/index.test.ts +22 -0
- package/src/bundle/plugins/parse-json/index.ts +30 -0
- package/src/bundle/plugins/parse-yaml/index.test.ts +24 -0
- package/src/bundle/plugins/parse-yaml/index.ts +31 -0
- package/src/bundle/plugins/read-files/index.test.ts +35 -0
- package/src/bundle/plugins/read-files/index.ts +55 -0
- package/src/bundle/value-generator.test.ts +166 -0
- package/src/bundle/value-generator.ts +147 -0
- package/src/dereference/dereference.test.ts +137 -0
- package/src/dereference/dereference.ts +84 -0
- package/src/dereference/index.ts +2 -0
- package/src/diff/apply.test.ts +262 -0
- package/src/diff/apply.ts +78 -0
- package/src/diff/diff.test.ts +328 -0
- package/src/diff/diff.ts +94 -0
- package/src/diff/index.test.ts +150 -0
- package/src/diff/index.ts +5 -0
- package/src/diff/merge.test.ts +1109 -0
- package/src/diff/merge.ts +136 -0
- package/src/diff/trie.test.ts +30 -0
- package/src/diff/trie.ts +113 -0
- package/src/diff/utils.test.ts +169 -0
- package/src/diff/utils.ts +113 -0
- package/src/magic-proxy/index.ts +2 -0
- package/src/magic-proxy/proxy.test.ts +145 -0
- package/src/magic-proxy/proxy.ts +225 -0
- package/src/polyfills/index.ts +12 -0
- package/src/polyfills/path.ts +248 -0
- package/src/types.ts +1 -0
- package/src/utils/escape-json-pointer.test.ts +13 -0
- package/src/utils/escape-json-pointer.ts +8 -0
- package/src/utils/get-segments-from-path.test.ts +17 -0
- package/src/utils/get-segments-from-path.ts +17 -0
- package/src/utils/is-json-object.ts +31 -0
- package/src/utils/is-object.test.ts +27 -0
- package/src/utils/is-object.ts +4 -0
- package/src/utils/is-yaml.ts +18 -0
- package/src/utils/json-path-utils.test.ts +13 -0
- package/src/utils/json-path-utils.ts +38 -0
- package/src/utils/normalize.test.ts +91 -0
- package/src/utils/normalize.ts +34 -0
- package/src/utils/unescape-json-pointer.test.ts +23 -0
- package/src/utils/unescape-json-pointer.ts +9 -0
- package/tsconfig.build.json +12 -0
- package/tsconfig.json +16 -0
- package/vite.config.ts +8 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { isReactive, toRaw } from 'vue'
|
|
2
|
+
import { isLocalRef } from '@/bundle/bundle'
|
|
3
|
+
import type { UnknownObject } from '@/types'
|
|
4
|
+
import { isObject } from '@/utils/is-object'
|
|
5
|
+
import { getValueByPath, parseJsonPointer } from '@/utils/json-path-utils'
|
|
6
|
+
|
|
7
|
+
const isMagicProxy = Symbol('isMagicProxy')
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Creates a proxy handler that automatically resolves JSON references ($ref) in an object.
|
|
11
|
+
* The handler intercepts property access, assignment, and property enumeration to automatically
|
|
12
|
+
* resolve any $ref references to their target values in the source document.
|
|
13
|
+
*
|
|
14
|
+
* @param sourceDocument - The source document containing the reference targets
|
|
15
|
+
* @param resolvedProxyCache - Optional cache to store resolved proxies and prevent duplicate proxies
|
|
16
|
+
* @returns A proxy handler that automatically resolves $ref references
|
|
17
|
+
*/
|
|
18
|
+
function createProxyHandler(
|
|
19
|
+
sourceDocument: UnknownObject | UnknownObject[],
|
|
20
|
+
resolvedProxyCache?: WeakMap<object, UnknownObject | UnknownObject[]>,
|
|
21
|
+
): ProxyHandler<UnknownObject | UnknownObject[]> {
|
|
22
|
+
return {
|
|
23
|
+
get(target, property, receiver) {
|
|
24
|
+
if (property === TARGET_SYMBOL) {
|
|
25
|
+
return target
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (property === isMagicProxy) {
|
|
29
|
+
return true
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const value = Reflect.get(target, property, receiver)
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Recursively resolves nested references in an object.
|
|
36
|
+
* If the value is not an object, returns it as is.
|
|
37
|
+
* If the value has a $ref property:
|
|
38
|
+
* - For local references: resolves the reference and continues resolving nested refs
|
|
39
|
+
* - For all other objects: creates a proxy for lazy resolution
|
|
40
|
+
*/
|
|
41
|
+
const deepResolveNestedRefs = (value: unknown, originalRef?: string) => {
|
|
42
|
+
if (!isObject(value) && !Array.isArray(value)) {
|
|
43
|
+
return value
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (typeof value === 'object' && '$ref' in value) {
|
|
47
|
+
const ref = value.$ref as string
|
|
48
|
+
|
|
49
|
+
if (isLocalRef(ref)) {
|
|
50
|
+
const referencePath = parseJsonPointer(ref)
|
|
51
|
+
const resolvedValue = getValueByPath(sourceDocument, referencePath)
|
|
52
|
+
|
|
53
|
+
// preserve the first $ref to maintain the original reference
|
|
54
|
+
return deepResolveNestedRefs(resolvedValue, originalRef ?? ref)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (originalRef && typeof value === 'object') {
|
|
59
|
+
return createMagicProxy({ ...value, 'x-original-ref': originalRef }, sourceDocument, resolvedProxyCache)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return createMagicProxy(value as UnknownObject, sourceDocument, resolvedProxyCache)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return deepResolveNestedRefs(value)
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
set(target: UnknownObject, property: string, newValue: unknown, receiver: UnknownObject) {
|
|
69
|
+
const rawTarget = isReactive(target) ? toRaw(target) : target
|
|
70
|
+
const currentValue = rawTarget[property]
|
|
71
|
+
|
|
72
|
+
if (
|
|
73
|
+
typeof currentValue === 'object' &&
|
|
74
|
+
isObject(currentValue) &&
|
|
75
|
+
'$ref' in currentValue &&
|
|
76
|
+
typeof currentValue.$ref === 'string' &&
|
|
77
|
+
isLocalRef(currentValue.$ref)
|
|
78
|
+
) {
|
|
79
|
+
const referencePath = parseJsonPointer(currentValue.$ref)
|
|
80
|
+
const targetObject = getValueByPath(sourceDocument, referencePath.slice(0, -1)) as UnknownObject
|
|
81
|
+
const lastPathSegment = referencePath[referencePath.length - 1]
|
|
82
|
+
|
|
83
|
+
if (targetObject && lastPathSegment) {
|
|
84
|
+
targetObject[lastPathSegment] = newValue
|
|
85
|
+
}
|
|
86
|
+
} else {
|
|
87
|
+
Reflect.set(rawTarget, property, newValue, receiver)
|
|
88
|
+
}
|
|
89
|
+
return true
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
has(target: UnknownObject, key: string) {
|
|
93
|
+
if (typeof key === 'string' && key !== '$ref' && typeof target.$ref === 'string' && isLocalRef(target.$ref)) {
|
|
94
|
+
const referencePath = parseJsonPointer(target['$ref'])
|
|
95
|
+
const resolvedValue = getValueByPath(sourceDocument, referencePath) as UnknownObject
|
|
96
|
+
|
|
97
|
+
return resolvedValue ? key in resolvedValue : false
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return key in target
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
ownKeys(target: UnknownObject) {
|
|
104
|
+
if ('$ref' in target && typeof target.$ref === 'string' && isLocalRef(target.$ref)) {
|
|
105
|
+
const referencePath = parseJsonPointer(target['$ref'])
|
|
106
|
+
const resolvedValue = getValueByPath<UnknownObject>(sourceDocument, referencePath)
|
|
107
|
+
|
|
108
|
+
return resolvedValue ? Reflect.ownKeys(resolvedValue) : []
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return Reflect.ownKeys(target)
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
getOwnPropertyDescriptor(target: UnknownObject, key: string) {
|
|
115
|
+
if ('$ref' in target && key !== '$ref' && typeof target.$ref === 'string' && isLocalRef(target.$ref)) {
|
|
116
|
+
const referencePath = parseJsonPointer(target['$ref'])
|
|
117
|
+
const resolvedValue = getValueByPath(sourceDocument, referencePath)
|
|
118
|
+
|
|
119
|
+
if (resolvedValue) {
|
|
120
|
+
return Object.getOwnPropertyDescriptor(resolvedValue, key)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return Object.getOwnPropertyDescriptor(target, key)
|
|
125
|
+
},
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Creates a proxy that automatically resolves JSON references ($ref) in an object.
|
|
131
|
+
* The proxy intercepts property access and automatically resolves any $ref references
|
|
132
|
+
* to their target values in the source document.
|
|
133
|
+
*
|
|
134
|
+
* @param targetObject - The object to create a proxy for
|
|
135
|
+
* @param sourceDocument - The source document containing the reference targets (defaults to targetObject)
|
|
136
|
+
* @param resolvedProxyCache - Optional cache to store resolved proxies and prevent duplicate proxies
|
|
137
|
+
* @returns A proxy that automatically resolves $ref references
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* // Basic usage with local references
|
|
141
|
+
* const doc = {
|
|
142
|
+
* components: {
|
|
143
|
+
* schemas: {
|
|
144
|
+
* User: { type: 'object', properties: { name: { type: 'string' } } }
|
|
145
|
+
* }
|
|
146
|
+
* },
|
|
147
|
+
* paths: {
|
|
148
|
+
* '/users': {
|
|
149
|
+
* get: {
|
|
150
|
+
* responses: {
|
|
151
|
+
* 200: {
|
|
152
|
+
* content: {
|
|
153
|
+
* 'application/json': {
|
|
154
|
+
* schema: { $ref: '#/components/schemas/User' }
|
|
155
|
+
* }
|
|
156
|
+
* }
|
|
157
|
+
* }
|
|
158
|
+
* }
|
|
159
|
+
* }
|
|
160
|
+
* }
|
|
161
|
+
* }
|
|
162
|
+
* }
|
|
163
|
+
*
|
|
164
|
+
* const proxy = createMagicProxy(doc)
|
|
165
|
+
* // Accessing the schema will automatically resolve the $ref
|
|
166
|
+
* console.log(proxy.paths['/users'].get.responses[200].content['application/json'].schema)
|
|
167
|
+
* // Output: { type: 'object', properties: { name: { type: 'string' } } }
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* // Using with a cache to prevent duplicate proxies
|
|
171
|
+
* const cache = new WeakMap()
|
|
172
|
+
* const proxy1 = createMagicProxy(doc, doc, cache)
|
|
173
|
+
* const proxy2 = createMagicProxy(doc, doc, cache)
|
|
174
|
+
* // proxy1 and proxy2 are the same instance due to caching
|
|
175
|
+
* console.log(proxy1 === proxy2) // true
|
|
176
|
+
*/
|
|
177
|
+
export function createMagicProxy<T extends UnknownObject | UnknownObject[]>(
|
|
178
|
+
targetObject: T,
|
|
179
|
+
sourceDocument: T = targetObject,
|
|
180
|
+
resolvedProxyCache?: WeakMap<object, T>,
|
|
181
|
+
): T {
|
|
182
|
+
if (!isObject(targetObject) && !Array.isArray(targetObject)) {
|
|
183
|
+
return targetObject
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const rawTarget = isReactive(targetObject) ? toRaw(targetObject) : targetObject
|
|
187
|
+
|
|
188
|
+
// check for cached results
|
|
189
|
+
if (resolvedProxyCache?.has(rawTarget)) {
|
|
190
|
+
const cachedValue = resolvedProxyCache.get(rawTarget)
|
|
191
|
+
|
|
192
|
+
if (cachedValue) {
|
|
193
|
+
return cachedValue
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Create a handler with the correct context
|
|
198
|
+
const handler = createProxyHandler(sourceDocument, resolvedProxyCache)
|
|
199
|
+
const proxy = new Proxy<T>(rawTarget, handler)
|
|
200
|
+
|
|
201
|
+
if (resolvedProxyCache) {
|
|
202
|
+
resolvedProxyCache.set(rawTarget, proxy)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return proxy
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export const TARGET_SYMBOL = Symbol('magicProxyTarget')
|
|
209
|
+
/**
|
|
210
|
+
* Gets the raw (non-proxied) version of an object created by createMagicProxy.
|
|
211
|
+
* This is useful when you need to access the original object without the magic proxy wrapper.
|
|
212
|
+
*
|
|
213
|
+
* @param obj - The magic proxy object to get the raw version of
|
|
214
|
+
* @returns The raw version of the object
|
|
215
|
+
* @example
|
|
216
|
+
* const proxy = createMagicProxy({ foo: { $ref: '#/bar' } })
|
|
217
|
+
* const raw = getRaw(proxy) // { foo: { $ref: '#/bar' } }
|
|
218
|
+
*/
|
|
219
|
+
export function getRaw<T extends UnknownObject>(obj: T): T {
|
|
220
|
+
if ((obj as T & { [isMagicProxy]: boolean | undefined })[isMagicProxy]) {
|
|
221
|
+
return (obj as T & { [TARGET_SYMBOL]: T })[TARGET_SYMBOL]
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return obj
|
|
225
|
+
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
|
|
3
|
+
// Copyright Joyent, Inc. and other Node contributors.
|
|
4
|
+
//
|
|
5
|
+
// Permission is hereby granted, free of charge, to any person obtaining a
|
|
6
|
+
// copy of this software and associated documentation files (the
|
|
7
|
+
// "Software"), to deal in the Software without restriction, including
|
|
8
|
+
// without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
// distribute, sublicense, and/or sell copies of the Software, and to permit
|
|
10
|
+
// persons to whom the Software is furnished to do so, subject to the
|
|
11
|
+
// following conditions:
|
|
12
|
+
//
|
|
13
|
+
// The above copyright notice and this permission notice shall be included
|
|
14
|
+
// in all copies or substantial portions of the Software.
|
|
15
|
+
//
|
|
16
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
17
|
+
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
|
19
|
+
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
20
|
+
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
21
|
+
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
|
22
|
+
// USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
23
|
+
|
|
24
|
+
// resolves . and .. elements in a path array with directory names there
|
|
25
|
+
// must be no slashes, empty elements, or device names (c:\) in the array
|
|
26
|
+
// (so also no leading and trailing slashes - it does not distinguish
|
|
27
|
+
// relative and absolute paths)
|
|
28
|
+
function normalizeArray(parts, allowAboveRoot) {
|
|
29
|
+
// if the path tries to go above the root, `up` ends up > 0
|
|
30
|
+
let up = 0
|
|
31
|
+
for (let i = parts.length - 1; i >= 0; i--) {
|
|
32
|
+
const last = parts[i]
|
|
33
|
+
if (last === '.') {
|
|
34
|
+
parts.splice(i, 1)
|
|
35
|
+
} else if (last === '..') {
|
|
36
|
+
parts.splice(i, 1)
|
|
37
|
+
up++
|
|
38
|
+
} else if (up) {
|
|
39
|
+
parts.splice(i, 1)
|
|
40
|
+
up--
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// if the path is allowed to go above the root, restore leading ..s
|
|
45
|
+
if (allowAboveRoot) {
|
|
46
|
+
for (; up--; up) {
|
|
47
|
+
parts.unshift('..')
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return parts
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Split a filename into [root, dir, basename, ext], unix version
|
|
55
|
+
// 'root' is just a slash, or nothing.
|
|
56
|
+
const splitPathRe = /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^/]+?|)(\.[^./]*|))(?:[/]*)$/
|
|
57
|
+
const splitPath = (filename) => splitPathRe.exec(filename).slice(1)
|
|
58
|
+
|
|
59
|
+
// path.resolve([from ...], to)
|
|
60
|
+
// posix version
|
|
61
|
+
export function resolve(...parameters) {
|
|
62
|
+
let resolvedPath = '',
|
|
63
|
+
resolvedAbsolute = false
|
|
64
|
+
|
|
65
|
+
for (let i = parameters.length - 1; i >= -1 && !resolvedAbsolute; i--) {
|
|
66
|
+
const path = i >= 0 ? parameters[i] : '/'
|
|
67
|
+
|
|
68
|
+
// Skip empty and invalid entries
|
|
69
|
+
if (typeof path !== 'string') {
|
|
70
|
+
throw new TypeError('Arguments to path.resolve must be strings')
|
|
71
|
+
}
|
|
72
|
+
if (!path) {
|
|
73
|
+
continue
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
resolvedPath = path + '/' + resolvedPath
|
|
77
|
+
resolvedAbsolute = path.charAt(0) === '/'
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// At this point the path should be resolved to a full absolute path, but
|
|
81
|
+
// handle relative paths to be safe (might happen when process.cwd() fails)
|
|
82
|
+
|
|
83
|
+
// Normalize the path
|
|
84
|
+
resolvedPath = normalizeArray(
|
|
85
|
+
filter(resolvedPath.split('/'), (p) => !!p),
|
|
86
|
+
!resolvedAbsolute,
|
|
87
|
+
).join('/')
|
|
88
|
+
|
|
89
|
+
return (resolvedAbsolute ? '/' : '') + resolvedPath || '.'
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// path.normalize(path)
|
|
93
|
+
// posix version
|
|
94
|
+
export function normalize(path) {
|
|
95
|
+
const isPathAbsolute = isAbsolute(path),
|
|
96
|
+
trailingSlash = substr(path, -1) === '/'
|
|
97
|
+
|
|
98
|
+
// Normalize the path
|
|
99
|
+
path = normalizeArray(
|
|
100
|
+
filter(path.split('/'), (p) => !!p),
|
|
101
|
+
!isPathAbsolute,
|
|
102
|
+
).join('/')
|
|
103
|
+
|
|
104
|
+
if (!path && !isPathAbsolute) {
|
|
105
|
+
path = '.'
|
|
106
|
+
}
|
|
107
|
+
if (path && trailingSlash) {
|
|
108
|
+
path += '/'
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return (isPathAbsolute ? '/' : '') + path
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// posix version
|
|
115
|
+
export function isAbsolute(path) {
|
|
116
|
+
return path.charAt(0) === '/'
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// posix version
|
|
120
|
+
export function join(...paths: string[]) {
|
|
121
|
+
return normalize(
|
|
122
|
+
filter(paths, (p, _index) => {
|
|
123
|
+
if (typeof p !== 'string') {
|
|
124
|
+
throw new TypeError('Arguments to path.join must be strings')
|
|
125
|
+
}
|
|
126
|
+
return p
|
|
127
|
+
}).join('/'),
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// path.relative(from, to)
|
|
132
|
+
// posix version
|
|
133
|
+
export function relative(from, to) {
|
|
134
|
+
from = resolve(from).substr(1)
|
|
135
|
+
to = resolve(to).substr(1)
|
|
136
|
+
|
|
137
|
+
function trim(arr) {
|
|
138
|
+
let start = 0
|
|
139
|
+
for (; start < arr.length; start++) {
|
|
140
|
+
if (arr[start] !== '') {
|
|
141
|
+
break
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
let end = arr.length - 1
|
|
146
|
+
for (; end >= 0; end--) {
|
|
147
|
+
if (arr[end] !== '') {
|
|
148
|
+
break
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (start > end) {
|
|
153
|
+
return []
|
|
154
|
+
}
|
|
155
|
+
return arr.slice(start, end - start + 1)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const fromParts = trim(from.split('/'))
|
|
159
|
+
const toParts = trim(to.split('/'))
|
|
160
|
+
|
|
161
|
+
const length = Math.min(fromParts.length, toParts.length)
|
|
162
|
+
let samePartsLength = length
|
|
163
|
+
for (let i = 0; i < length; i++) {
|
|
164
|
+
if (fromParts[i] !== toParts[i]) {
|
|
165
|
+
samePartsLength = i
|
|
166
|
+
break
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
let outputParts = []
|
|
171
|
+
for (let i = samePartsLength; i < fromParts.length; i++) {
|
|
172
|
+
outputParts.push('..')
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
outputParts = outputParts.concat(toParts.slice(samePartsLength))
|
|
176
|
+
|
|
177
|
+
return outputParts.join('/')
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export const sep = '/'
|
|
181
|
+
export const delimiter = ':'
|
|
182
|
+
|
|
183
|
+
export function dirname(path) {
|
|
184
|
+
const result = splitPath(path),
|
|
185
|
+
root = result[0]
|
|
186
|
+
|
|
187
|
+
let dir = result[1]
|
|
188
|
+
|
|
189
|
+
if (!root && !dir) {
|
|
190
|
+
// No dirname whatsoever
|
|
191
|
+
return '.'
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (dir) {
|
|
195
|
+
// It has a dirname, strip trailing slash
|
|
196
|
+
dir = dir.substr(0, dir.length - 1)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return root + dir
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function basename(path, ext) {
|
|
203
|
+
let f = splitPath(path)[2]
|
|
204
|
+
// TODO: make this comparison case-insensitive on windows?
|
|
205
|
+
if (ext && f.substr(-1 * ext.length) === ext) {
|
|
206
|
+
f = f.substr(0, f.length - ext.length)
|
|
207
|
+
}
|
|
208
|
+
return f
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function extname(path) {
|
|
212
|
+
return splitPath(path)[3]
|
|
213
|
+
}
|
|
214
|
+
export default {
|
|
215
|
+
extname: extname,
|
|
216
|
+
basename: basename,
|
|
217
|
+
dirname: dirname,
|
|
218
|
+
sep: sep,
|
|
219
|
+
delimiter: delimiter,
|
|
220
|
+
relative: relative,
|
|
221
|
+
join: join,
|
|
222
|
+
isAbsolute: isAbsolute,
|
|
223
|
+
normalize: normalize,
|
|
224
|
+
resolve: resolve,
|
|
225
|
+
}
|
|
226
|
+
function filter(xs, f) {
|
|
227
|
+
if (xs.filter) {
|
|
228
|
+
return xs.filter(f)
|
|
229
|
+
}
|
|
230
|
+
const res = []
|
|
231
|
+
for (let i = 0; i < xs.length; i++) {
|
|
232
|
+
if (f(xs[i], i, xs)) {
|
|
233
|
+
res.push(xs[i])
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return res
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// String.prototype.substr - negative index don't work in IE8
|
|
240
|
+
const substr =
|
|
241
|
+
'ab'.substr(-1) === 'b'
|
|
242
|
+
? (str, start, len) => str.substr(start, len)
|
|
243
|
+
: (str, start, len) => {
|
|
244
|
+
if (start < 0) {
|
|
245
|
+
start = str.length + start
|
|
246
|
+
}
|
|
247
|
+
return str.substr(start, len)
|
|
248
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type UnknownObject = Record<string, unknown>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { escapeJsonPointer } from './escape-json-pointer'
|
|
4
|
+
|
|
5
|
+
describe('escapeJsonPointer', async () => {
|
|
6
|
+
it('should escape a slash', () => {
|
|
7
|
+
expect(escapeJsonPointer('application/json')).toBe('application~1json')
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
it('should escape multiple slashes', () => {
|
|
11
|
+
expect(escapeJsonPointer('/api/users/{id}/reports')).toBe('~1api~1users~1{id}~1reports')
|
|
12
|
+
})
|
|
13
|
+
})
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { getSegmentsFromPath } from './get-segments-from-path'
|
|
4
|
+
|
|
5
|
+
describe('getSegmentsFromPath', () => {
|
|
6
|
+
it('returns path segments', () => {
|
|
7
|
+
const result = getSegmentsFromPath('/paths/test')
|
|
8
|
+
|
|
9
|
+
expect(result).toEqual(['paths', 'test'])
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it('unescaped slashes', () => {
|
|
13
|
+
const result = getSegmentsFromPath('/paths/~1test')
|
|
14
|
+
|
|
15
|
+
expect(result).toEqual(['paths', '/test'])
|
|
16
|
+
})
|
|
17
|
+
})
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { unescapeJsonPointer } from './unescape-json-pointer'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Translate `/paths/~1test` to `['paths', '/test']`
|
|
5
|
+
*/
|
|
6
|
+
export function getSegmentsFromPath(path: string) {
|
|
7
|
+
return (
|
|
8
|
+
// /paths/~1test
|
|
9
|
+
path
|
|
10
|
+
// ['', 'paths', '~1test']
|
|
11
|
+
.split('/')
|
|
12
|
+
// ['paths', '~test']
|
|
13
|
+
.slice(1)
|
|
14
|
+
// ['paths', '/test']
|
|
15
|
+
.map(unescapeJsonPointer)
|
|
16
|
+
)
|
|
17
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { isObject } from '@/utils/is-object'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Determines if a string represents a valid JSON object (i.e., a plain object, not an array, primitive, or null).
|
|
5
|
+
* The function first checks if the string appears to start with an opening curly brace (ignoring leading whitespace),
|
|
6
|
+
* which is a quick heuristic to rule out arrays, primitives, and most invalid JSON. If this check passes,
|
|
7
|
+
* it attempts to parse the string with JSON.parse. The result is then checked to ensure it is a plain object
|
|
8
|
+
* (not an array, null, or primitive) using the isObject utility.
|
|
9
|
+
*
|
|
10
|
+
* @param value - The string to evaluate
|
|
11
|
+
* @returns true if the string is valid JSON and parses to a plain object, false otherwise
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* isJsonObject('{"foo": "bar"}') // true
|
|
15
|
+
* isJsonObject('[1,2,3]') // false
|
|
16
|
+
* isJsonObject('not json') // false
|
|
17
|
+
* isJsonObject('42') // false
|
|
18
|
+
*/
|
|
19
|
+
export function isJsonObject(value: string) {
|
|
20
|
+
// Quickly rule out anything that doesn't start with an object brace
|
|
21
|
+
if (!/^\s*(\{)/.test(value.slice(0, 500))) {
|
|
22
|
+
return false
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const val = JSON.parse(value)
|
|
27
|
+
return isObject(val)
|
|
28
|
+
} catch {
|
|
29
|
+
return false
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { isObject } from './is-object'
|
|
4
|
+
|
|
5
|
+
describe('isObject', () => {
|
|
6
|
+
it('returns true for an object', () => {
|
|
7
|
+
const result = isObject({
|
|
8
|
+
foo: 'bar',
|
|
9
|
+
})
|
|
10
|
+
expect(result).toBe(true)
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
it('returns true for an empty object', () => {
|
|
14
|
+
const result = isObject({})
|
|
15
|
+
expect(result).toBe(true)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('returns false for a string', () => {
|
|
19
|
+
const result = isObject('foo')
|
|
20
|
+
expect(result).toBe(false)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('returns false for an array', () => {
|
|
24
|
+
const result = isObject([])
|
|
25
|
+
expect(result).toBe(false)
|
|
26
|
+
})
|
|
27
|
+
})
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks if a string appears to be YAML content.
|
|
3
|
+
* This function uses a simple heuristic: it looks for a line that starts with an optional dash,
|
|
4
|
+
* followed by a key (alphanumeric or dash), a colon, and a value, and then at least one more line.
|
|
5
|
+
* This is not a full YAML parser, but works for basic detection.
|
|
6
|
+
*
|
|
7
|
+
* @param value - The string to check
|
|
8
|
+
* @returns true if the string looks like YAML, false otherwise
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* isYaml('openapi: 3.0.0\ninfo:\n title: Example') // true
|
|
12
|
+
* isYaml('{"openapi": "3.0.0", "info": {"title": "Example"}}') // false
|
|
13
|
+
* isYaml('- name: value\n- name: value2') // true
|
|
14
|
+
* isYaml('type: object') // false (only one line)
|
|
15
|
+
*/
|
|
16
|
+
export function isYaml(value: string): boolean {
|
|
17
|
+
return /^\s*(?:-\s*)?[\w\-]+\s*:\s*.+\n.*/.test(value)
|
|
18
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { parseJsonPointer } from './json-path-utils'
|
|
2
|
+
import { describe, expect, test } from 'vitest'
|
|
3
|
+
|
|
4
|
+
describe('parseJsonPointer', () => {
|
|
5
|
+
test.each([
|
|
6
|
+
['#/users/name', ['users', 'name']],
|
|
7
|
+
['#/', []],
|
|
8
|
+
['', []],
|
|
9
|
+
['users/name', ['users', 'name']],
|
|
10
|
+
])('should correctly parse json pointers', (a, b) => {
|
|
11
|
+
expect(parseJsonPointer(a)).toEqual(b)
|
|
12
|
+
})
|
|
13
|
+
})
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parses a JSON Pointer string into an array of path segments
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```ts
|
|
6
|
+
* parseJsonPointer('#/components/schemas/User')
|
|
7
|
+
*
|
|
8
|
+
* ['components', 'schemas', 'User']
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
export function parseJsonPointer(pointer: string): string[] {
|
|
12
|
+
return (
|
|
13
|
+
pointer
|
|
14
|
+
// Split on '/'
|
|
15
|
+
.split('/')
|
|
16
|
+
// Remove the leading '#' if present
|
|
17
|
+
.filter((segment, index) => (index !== 0 || segment !== '#') && segment)
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Retrieves a nested value from the source document using a path array
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* getValueByPath(document, ['components', 'schemas', 'User'])
|
|
27
|
+
*
|
|
28
|
+
* { id: '123', name: 'John Doe' }
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export function getValueByPath<R = unknown>(obj: any, pointer: string[]): R {
|
|
32
|
+
return pointer.reduce((acc, part) => {
|
|
33
|
+
if (acc === undefined || acc === null) {
|
|
34
|
+
return undefined
|
|
35
|
+
}
|
|
36
|
+
return acc[part]
|
|
37
|
+
}, obj)
|
|
38
|
+
}
|