@scalar/json-magic 0.8.2 → 0.8.3
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/CHANGELOG.md +13 -0
- package/dist/bundle/index.d.ts +1 -0
- package/dist/bundle/index.d.ts.map +1 -1
- package/dist/bundle/index.js.map +1 -1
- package/dist/bundle/plugins/browser.js.map +1 -1
- package/dist/bundle/plugins/node.d.ts +1 -1
- package/dist/bundle/plugins/node.js +1 -1
- package/dist/bundle/plugins/node.js.map +1 -1
- package/dist/dereference/index.d.ts.map +1 -1
- package/dist/dereference/index.js.map +2 -2
- package/dist/diff/index.d.ts +1 -1
- package/dist/diff/index.d.ts.map +1 -1
- package/dist/diff/index.js +1 -1
- package/dist/diff/index.js.map +2 -2
- package/dist/helpers/escape-json-pointer.d.ts +1 -1
- package/dist/helpers/escape-json-pointer.js.map +1 -1
- package/dist/magic-proxy/index.d.ts.map +1 -1
- package/dist/magic-proxy/index.js.map +2 -2
- package/dist/magic-proxy/proxy.d.ts +0 -1
- package/dist/magic-proxy/proxy.d.ts.map +1 -1
- package/dist/magic-proxy/proxy.js +1 -2
- package/dist/magic-proxy/proxy.js.map +2 -2
- package/package.json +12 -13
- package/.turbo/turbo-build.log +0 -10
- package/esbuild.ts +0 -15
- package/src/bundle/bundle.test.ts +0 -2917
- package/src/bundle/bundle.ts +0 -916
- package/src/bundle/create-limiter.test.ts +0 -28
- package/src/bundle/create-limiter.ts +0 -52
- package/src/bundle/index.ts +0 -3
- package/src/bundle/plugins/browser.ts +0 -4
- package/src/bundle/plugins/fetch-urls/index.test.ts +0 -141
- package/src/bundle/plugins/fetch-urls/index.ts +0 -105
- package/src/bundle/plugins/node.ts +0 -5
- package/src/bundle/plugins/parse-json/index.test.ts +0 -24
- package/src/bundle/plugins/parse-json/index.ts +0 -32
- package/src/bundle/plugins/parse-yaml/index.test.ts +0 -26
- package/src/bundle/plugins/parse-yaml/index.ts +0 -34
- package/src/bundle/plugins/read-files/index.test.ts +0 -36
- package/src/bundle/plugins/read-files/index.ts +0 -58
- package/src/bundle/value-generator.test.ts +0 -165
- package/src/bundle/value-generator.ts +0 -143
- package/src/dereference/dereference.test.ts +0 -142
- package/src/dereference/dereference.ts +0 -84
- package/src/dereference/index.ts +0 -2
- package/src/diff/apply.test.ts +0 -262
- package/src/diff/apply.ts +0 -83
- package/src/diff/diff.test.ts +0 -328
- package/src/diff/diff.ts +0 -93
- package/src/diff/index.test.ts +0 -150
- package/src/diff/index.ts +0 -5
- package/src/diff/merge.test.ts +0 -1109
- package/src/diff/merge.ts +0 -136
- package/src/diff/trie.test.ts +0 -30
- package/src/diff/trie.ts +0 -113
- package/src/diff/utils.test.ts +0 -169
- package/src/diff/utils.ts +0 -111
- package/src/helpers/convert-to-local-ref.test.ts +0 -211
- package/src/helpers/convert-to-local-ref.ts +0 -43
- package/src/helpers/escape-json-pointer.test.ts +0 -13
- package/src/helpers/escape-json-pointer.ts +0 -8
- package/src/helpers/get-schemas.test.ts +0 -356
- package/src/helpers/get-schemas.ts +0 -80
- package/src/helpers/get-segments-from-path.test.ts +0 -17
- package/src/helpers/get-segments-from-path.ts +0 -17
- package/src/helpers/get-value-by-path.test.ts +0 -338
- package/src/helpers/get-value-by-path.ts +0 -44
- package/src/helpers/is-json-object.ts +0 -31
- package/src/helpers/is-object.test.ts +0 -27
- package/src/helpers/is-object.ts +0 -4
- package/src/helpers/is-yaml.ts +0 -18
- package/src/helpers/json-path-utils.test.ts +0 -57
- package/src/helpers/json-path-utils.ts +0 -50
- package/src/helpers/normalize.test.ts +0 -92
- package/src/helpers/normalize.ts +0 -35
- package/src/helpers/unescape-json-pointer.test.ts +0 -23
- package/src/helpers/unescape-json-pointer.ts +0 -9
- package/src/magic-proxy/index.ts +0 -2
- package/src/magic-proxy/proxy.test.ts +0 -1987
- package/src/magic-proxy/proxy.ts +0 -323
- package/src/types.ts +0 -1
- package/tsconfig.build.json +0 -12
- package/tsconfig.json +0 -16
- package/vite.config.ts +0 -8
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
|
|
3
|
-
import { generateUniqueValue, uniqueValueGeneratorFactory } from './value-generator'
|
|
4
|
-
|
|
5
|
-
describe('generateUniqueHash', () => {
|
|
6
|
-
it('should generate hash values from the function we pass in', async () => {
|
|
7
|
-
const hashFunction = (value: string) => {
|
|
8
|
-
if (value === 'a') {
|
|
9
|
-
return 'a'
|
|
10
|
-
}
|
|
11
|
-
throw 'Ops'
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const result = await generateUniqueValue(hashFunction, 'a', {})
|
|
15
|
-
|
|
16
|
-
expect(result).toBe('a')
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
it('should return same value for the same input', async () => {
|
|
20
|
-
const hashFunction = (value: string) => {
|
|
21
|
-
if (value === 'a') {
|
|
22
|
-
return value
|
|
23
|
-
}
|
|
24
|
-
throw 'Ops'
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const map = {}
|
|
28
|
-
|
|
29
|
-
const result1 = await generateUniqueValue(hashFunction, 'a', map)
|
|
30
|
-
expect(result1).toBe('a')
|
|
31
|
-
|
|
32
|
-
const result2 = await generateUniqueValue(hashFunction, 'a', map)
|
|
33
|
-
expect(result2).toBe('a')
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
it('should handle hash collisions', async () => {
|
|
37
|
-
const hashFunction = (value: string) => {
|
|
38
|
-
// Hash a, b and c will produce collisions
|
|
39
|
-
if (value === 'a' || value === 'b' || value === 'c') {
|
|
40
|
-
return 'd'
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Hashing the hashed result
|
|
44
|
-
if (value === 'd') {
|
|
45
|
-
return 'e'
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (value === 'e') {
|
|
49
|
-
return 'f'
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (value === 'f') {
|
|
53
|
-
return 'g'
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (value === 'g') {
|
|
57
|
-
return 'h'
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
throw 'Ops'
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const map = {}
|
|
64
|
-
|
|
65
|
-
const res1 = await generateUniqueValue(hashFunction, 'd', map)
|
|
66
|
-
expect(res1).toBe('e')
|
|
67
|
-
|
|
68
|
-
const res2 = await generateUniqueValue(hashFunction, 'b', map)
|
|
69
|
-
expect(res2).toBe('d')
|
|
70
|
-
|
|
71
|
-
// Takes the first available spot
|
|
72
|
-
const res3 = await generateUniqueValue(hashFunction, 'a', map)
|
|
73
|
-
expect(res3).toBe('f')
|
|
74
|
-
|
|
75
|
-
// Produces the same output for the same input
|
|
76
|
-
const res4 = await generateUniqueValue(hashFunction, 'a', map)
|
|
77
|
-
expect(res4).toBe('f')
|
|
78
|
-
|
|
79
|
-
const res5 = await generateUniqueValue(hashFunction, 'c', map)
|
|
80
|
-
expect(res5).toBe('g')
|
|
81
|
-
|
|
82
|
-
const res6 = await generateUniqueValue(hashFunction, 'g', map)
|
|
83
|
-
expect(res6).toBe('h')
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
it('should throw when it reaches max depth', async () => {
|
|
87
|
-
const hashFunction = () => 'a'
|
|
88
|
-
|
|
89
|
-
const map = {}
|
|
90
|
-
const result1 = await generateUniqueValue(hashFunction, 'a', map)
|
|
91
|
-
expect(result1).toBe('a')
|
|
92
|
-
|
|
93
|
-
const result2 = await generateUniqueValue(hashFunction, 'a', map)
|
|
94
|
-
expect(result2).toBe('a')
|
|
95
|
-
|
|
96
|
-
await expect(() => generateUniqueValue(hashFunction, 'b', map)).rejects.toThrowError()
|
|
97
|
-
})
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
describe('uniqueValueGeneratorFactory', () => {
|
|
101
|
-
it('should generate unique values while caching the previously generated values', async () => {
|
|
102
|
-
let calls = 0
|
|
103
|
-
const compress = (value: string) => {
|
|
104
|
-
// Ensure we return the same value only once for the input 'b'
|
|
105
|
-
if (value === 'b' && calls === 0) {
|
|
106
|
-
calls++
|
|
107
|
-
return 'another-value'
|
|
108
|
-
}
|
|
109
|
-
calls++
|
|
110
|
-
return 'should not be returned'
|
|
111
|
-
}
|
|
112
|
-
const { generate } = uniqueValueGeneratorFactory(compress, {
|
|
113
|
-
'b': 'a',
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
// Should use the value from the cache
|
|
117
|
-
expect(await generate('a')).toBe('b')
|
|
118
|
-
|
|
119
|
-
// should use the generator function to generate a unique value
|
|
120
|
-
expect(await generate('b')).toBe('another-value')
|
|
121
|
-
|
|
122
|
-
// should generate the same value when we call generate with the same input
|
|
123
|
-
expect(await generate('b')).toBe('another-value')
|
|
124
|
-
})
|
|
125
|
-
|
|
126
|
-
it('should handle conflicts', async () => {
|
|
127
|
-
const compress = (value: string) => {
|
|
128
|
-
if (value === 'a') {
|
|
129
|
-
return 'c'
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (value === 'b') {
|
|
133
|
-
return 'c'
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
if (value === 'c') {
|
|
137
|
-
return 'd'
|
|
138
|
-
}
|
|
139
|
-
return 'should not be returned'
|
|
140
|
-
}
|
|
141
|
-
const { generate } = uniqueValueGeneratorFactory(compress, {})
|
|
142
|
-
|
|
143
|
-
expect(await generate('a')).toBe('c')
|
|
144
|
-
expect(await generate('b')).toBe('d')
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
it('should handle numeric strings by prefixing them with letters', async () => {
|
|
148
|
-
const compress = (value: string) => {
|
|
149
|
-
if (!value) {
|
|
150
|
-
return '92819102'
|
|
151
|
-
}
|
|
152
|
-
return 'radom value'
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const { generate } = uniqueValueGeneratorFactory(compress, {})
|
|
156
|
-
|
|
157
|
-
// Prefix with a letter
|
|
158
|
-
expect(await generate('')).toBe('a92819102')
|
|
159
|
-
|
|
160
|
-
// Should return the same value for same input
|
|
161
|
-
expect(await generate('')).toBe('a92819102')
|
|
162
|
-
|
|
163
|
-
expect(await generate('abc')).toBe('radom value')
|
|
164
|
-
})
|
|
165
|
-
})
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
import { generateHash } from '@scalar/helpers/string/generate-hash'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Generates a short hash from a string value using xxhash.
|
|
5
|
-
*
|
|
6
|
-
* This function is used to create unique identifiers for external references
|
|
7
|
-
* while keeping the hash length manageable. It uses xxhash-wasm instead of
|
|
8
|
-
* crypto.subtle because crypto.subtle is only available in secure contexts (HTTPS) or on localhost.
|
|
9
|
-
* Returns the first 7 characters of the hash string.
|
|
10
|
-
* If the hash would be all numbers, it ensures at least one letter is included.
|
|
11
|
-
*
|
|
12
|
-
* @param value - The string to hash
|
|
13
|
-
* @returns A Promise that resolves to a 7-character hexadecimal hash with at least one letter
|
|
14
|
-
* @example
|
|
15
|
-
* // Returns "2ae91d7"
|
|
16
|
-
* getHash("https://example.com/schema.json")
|
|
17
|
-
*/
|
|
18
|
-
export function getHash(value: string): string {
|
|
19
|
-
// Hash the data using xxhash
|
|
20
|
-
const hashHex = generateHash(value)
|
|
21
|
-
|
|
22
|
-
// Return first 7 characters of the hash, ensuring at least one letter
|
|
23
|
-
const hash = hashHex.substring(0, 7)
|
|
24
|
-
return hash.match(/^\d+$/) ? 'a' + hash.substring(1) : hash
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Generates a unique compressed value for a string, handling collisions by recursively compressing
|
|
29
|
-
* until a unique value is found. This is used to create unique identifiers for external
|
|
30
|
-
* references in the bundled OpenAPI document.
|
|
31
|
-
*
|
|
32
|
-
* @param compress - Function that generates a compressed value from a string
|
|
33
|
-
* @param value - The original string value to compress
|
|
34
|
-
* @param compressedToValue - Object mapping compressed values to their original values
|
|
35
|
-
* @param prevCompressedValue - Optional previous compressed value to use as input for generating a new value
|
|
36
|
-
* @param depth - Current recursion depth to prevent infinite loops
|
|
37
|
-
* @returns A unique compressed value that doesn't conflict with existing values
|
|
38
|
-
*
|
|
39
|
-
* @example
|
|
40
|
-
* const valueMap = {}
|
|
41
|
-
* // First call generates compressed value for "example.com/schema.json"
|
|
42
|
-
* const value1 = await generateUniqueValue(compress, "example.com/schema.json", valueMap)
|
|
43
|
-
* // Returns something like "2ae91d7"
|
|
44
|
-
*
|
|
45
|
-
* // Second call with same value returns same compressed value
|
|
46
|
-
* const value2 = await generateUniqueValue(compress, "example.com/schema.json", valueMap)
|
|
47
|
-
* // Returns same value as value1
|
|
48
|
-
*
|
|
49
|
-
* // Call with different value generates new unique compressed value
|
|
50
|
-
* const value3 = await generateUniqueValue(compress, "example.com/other.json", valueMap)
|
|
51
|
-
* // Returns different value like "3bf82e9"
|
|
52
|
-
*/
|
|
53
|
-
export async function generateUniqueValue(
|
|
54
|
-
compress: (value: string) => Promise<string> | string,
|
|
55
|
-
value: string,
|
|
56
|
-
compressedToValue: Record<string, string>,
|
|
57
|
-
prevCompressedValue?: string,
|
|
58
|
-
depth = 0,
|
|
59
|
-
) {
|
|
60
|
-
// Prevent infinite recursion by limiting depth
|
|
61
|
-
const MAX_DEPTH = 100
|
|
62
|
-
|
|
63
|
-
if (depth >= MAX_DEPTH) {
|
|
64
|
-
throw 'Can not generate unique compressed values'
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Compress the value, using previous compressed value if provided
|
|
68
|
-
const compressedValue = await compress(prevCompressedValue ?? value)
|
|
69
|
-
|
|
70
|
-
// Handle collision by recursively trying with compressed value as input
|
|
71
|
-
if (compressedToValue[compressedValue] !== undefined && compressedToValue[compressedValue] !== value) {
|
|
72
|
-
return generateUniqueValue(compress, value, compressedToValue, compressedValue, depth + 1)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Store mapping and return unique compressed value
|
|
76
|
-
compressedToValue[compressedValue] = value
|
|
77
|
-
return compressedValue
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Factory function that creates a value generator with caching capabilities.
|
|
82
|
-
* The generator maintains a bidirectional mapping between original values and their compressed forms.
|
|
83
|
-
*
|
|
84
|
-
* @param compress - Function that generates a compressed value from a string
|
|
85
|
-
* @param compressedToValue - Initial mapping of compressed values to their original values
|
|
86
|
-
* @returns An object with a generate method that produces unique compressed values
|
|
87
|
-
*
|
|
88
|
-
* @example
|
|
89
|
-
* const compress = (value) => value.substring(0, 6) // Simple compression example
|
|
90
|
-
* const initialMap = { 'abc123': 'example.com/schema.json' }
|
|
91
|
-
* const generator = uniqueValueGeneratorFactory(compress, initialMap)
|
|
92
|
-
*
|
|
93
|
-
* // Generate compressed value for new string
|
|
94
|
-
* const compressed = await generator.generate('example.com/other.json')
|
|
95
|
-
* // Returns something like 'example'
|
|
96
|
-
*
|
|
97
|
-
* // Generate compressed value for existing string
|
|
98
|
-
* const cached = await generator.generate('example.com/schema.json')
|
|
99
|
-
* // Returns 'abc123' from cache
|
|
100
|
-
*/
|
|
101
|
-
export const uniqueValueGeneratorFactory = (
|
|
102
|
-
compress: (value: string) => Promise<string> | string,
|
|
103
|
-
compressedToValue: Record<string, string>,
|
|
104
|
-
) => {
|
|
105
|
-
// Create a reverse mapping from original values to their compressed forms
|
|
106
|
-
const valueToCompressed = Object.fromEntries(Object.entries(compressedToValue).map(([key, value]) => [value, key]))
|
|
107
|
-
|
|
108
|
-
return {
|
|
109
|
-
/**
|
|
110
|
-
* Generates a unique compressed value for the given input string.
|
|
111
|
-
* First checks if a compressed value already exists in the cache.
|
|
112
|
-
* If not, generates a new unique compressed value and stores it in the cache.
|
|
113
|
-
*
|
|
114
|
-
* @param value - The original string value to compress
|
|
115
|
-
* @returns A Promise that resolves to the compressed string value
|
|
116
|
-
*
|
|
117
|
-
* @example
|
|
118
|
-
* const generator = uniqueValueGeneratorFactory(compress, {})
|
|
119
|
-
* const compressed = await generator.generate('example.com/schema.json')
|
|
120
|
-
* // Returns a unique compressed value like 'example'
|
|
121
|
-
*/
|
|
122
|
-
generate: async (value: string) => {
|
|
123
|
-
// Check if we already have a compressed value for this input
|
|
124
|
-
const cache = valueToCompressed[value]
|
|
125
|
-
if (cache) {
|
|
126
|
-
return cache
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Generate a new unique compressed value
|
|
130
|
-
const generatedValue = await generateUniqueValue(compress, value, compressedToValue)
|
|
131
|
-
|
|
132
|
-
// Ensure the generated string contains at least one non-numeric character
|
|
133
|
-
// This prevents the `setValueAtPath` function from interpreting the value as an array index
|
|
134
|
-
// by forcing it to be treated as an object property name
|
|
135
|
-
const compressedValue = generatedValue.match(/^\d+$/) ? `a${generatedValue}` : generatedValue
|
|
136
|
-
|
|
137
|
-
// Store the new mapping in our cache
|
|
138
|
-
valueToCompressed[value] = compressedValue
|
|
139
|
-
|
|
140
|
-
return compressedValue
|
|
141
|
-
},
|
|
142
|
-
}
|
|
143
|
-
}
|
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
import { type FastifyInstance, fastify } from 'fastify'
|
|
2
|
-
import { beforeEach, describe, expect, it } from 'vitest'
|
|
3
|
-
|
|
4
|
-
import { dereference } from '@/dereference/dereference'
|
|
5
|
-
|
|
6
|
-
describe('dereference', () => {
|
|
7
|
-
describe('sync', () => {
|
|
8
|
-
it('should dereference JSON pointers', () => {
|
|
9
|
-
const data = {
|
|
10
|
-
users: {
|
|
11
|
-
name: 'John Doe',
|
|
12
|
-
age: 30,
|
|
13
|
-
address: {
|
|
14
|
-
city: 'New York',
|
|
15
|
-
street: '5th Avenue',
|
|
16
|
-
},
|
|
17
|
-
},
|
|
18
|
-
profile: {
|
|
19
|
-
$ref: '#/users',
|
|
20
|
-
},
|
|
21
|
-
address: {
|
|
22
|
-
$ref: '#/users/address',
|
|
23
|
-
},
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const result = dereference(data, { sync: true })
|
|
27
|
-
|
|
28
|
-
expect(result).toEqual({
|
|
29
|
-
success: true,
|
|
30
|
-
data: {
|
|
31
|
-
users: {
|
|
32
|
-
name: 'John Doe',
|
|
33
|
-
age: 30,
|
|
34
|
-
address: {
|
|
35
|
-
city: 'New York',
|
|
36
|
-
street: '5th Avenue',
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
profile: {
|
|
40
|
-
'$ref': '#/users',
|
|
41
|
-
'$ref-value': {
|
|
42
|
-
name: 'John Doe',
|
|
43
|
-
age: 30,
|
|
44
|
-
address: {
|
|
45
|
-
city: 'New York',
|
|
46
|
-
street: '5th Avenue',
|
|
47
|
-
},
|
|
48
|
-
},
|
|
49
|
-
},
|
|
50
|
-
address: {
|
|
51
|
-
'$ref': '#/users/address',
|
|
52
|
-
'$ref-value': {
|
|
53
|
-
city: 'New York',
|
|
54
|
-
street: '5th Avenue',
|
|
55
|
-
},
|
|
56
|
-
},
|
|
57
|
-
},
|
|
58
|
-
})
|
|
59
|
-
})
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
describe('async', () => {
|
|
63
|
-
let server: FastifyInstance
|
|
64
|
-
const port = 7299
|
|
65
|
-
const url = `http://localhost:${port}`
|
|
66
|
-
|
|
67
|
-
beforeEach(() => {
|
|
68
|
-
server = fastify({ logger: false })
|
|
69
|
-
|
|
70
|
-
return async () => {
|
|
71
|
-
await server.close()
|
|
72
|
-
}
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
it('should dereference JSON pointers asynchronously', async () => {
|
|
76
|
-
const userProfile = {
|
|
77
|
-
name: 'Jane Doe',
|
|
78
|
-
age: 25,
|
|
79
|
-
address: {
|
|
80
|
-
city: 'Los Angeles',
|
|
81
|
-
street: 'Sunset Boulevard',
|
|
82
|
-
},
|
|
83
|
-
}
|
|
84
|
-
server.get('/users', () => userProfile)
|
|
85
|
-
|
|
86
|
-
await server.listen({ port: port })
|
|
87
|
-
|
|
88
|
-
const data = {
|
|
89
|
-
profile: {
|
|
90
|
-
$ref: `${url}/users#`,
|
|
91
|
-
},
|
|
92
|
-
address: {
|
|
93
|
-
$ref: `${url}/users#/address`,
|
|
94
|
-
},
|
|
95
|
-
}
|
|
96
|
-
const result = await dereference(data, { sync: false })
|
|
97
|
-
expect(result).toEqual({
|
|
98
|
-
success: true,
|
|
99
|
-
data: {
|
|
100
|
-
profile: {
|
|
101
|
-
'$ref': '#/x-ext/f053c6d',
|
|
102
|
-
'$ref-value': {
|
|
103
|
-
name: 'Jane Doe',
|
|
104
|
-
age: 25,
|
|
105
|
-
address: {
|
|
106
|
-
city: 'Los Angeles',
|
|
107
|
-
street: 'Sunset Boulevard',
|
|
108
|
-
},
|
|
109
|
-
},
|
|
110
|
-
},
|
|
111
|
-
address: {
|
|
112
|
-
'$ref': '#/x-ext/f053c6d/address',
|
|
113
|
-
'$ref-value': {
|
|
114
|
-
city: 'Los Angeles',
|
|
115
|
-
street: 'Sunset Boulevard',
|
|
116
|
-
},
|
|
117
|
-
},
|
|
118
|
-
'x-ext': {
|
|
119
|
-
'f053c6d': userProfile,
|
|
120
|
-
},
|
|
121
|
-
'x-ext-urls': {
|
|
122
|
-
'f053c6d': `${url}/users`,
|
|
123
|
-
},
|
|
124
|
-
},
|
|
125
|
-
})
|
|
126
|
-
})
|
|
127
|
-
|
|
128
|
-
it('should handle errors when dereferencing remote refs', async () => {
|
|
129
|
-
const data = {
|
|
130
|
-
profile: {
|
|
131
|
-
$ref: `${url}/nonexistent`,
|
|
132
|
-
},
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const result = await dereference(data, { sync: false })
|
|
136
|
-
expect(result).toEqual({
|
|
137
|
-
success: false,
|
|
138
|
-
errors: [`Failed to resolve ${url}/nonexistent`],
|
|
139
|
-
})
|
|
140
|
-
})
|
|
141
|
-
})
|
|
142
|
-
})
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import { bundle } from '@/bundle'
|
|
2
|
-
import { fetchUrls } from '@/bundle/plugins/fetch-urls'
|
|
3
|
-
import { createMagicProxy } from '@/magic-proxy'
|
|
4
|
-
import type { UnknownObject } from '@/types'
|
|
5
|
-
|
|
6
|
-
type DereferenceResult =
|
|
7
|
-
| {
|
|
8
|
-
success: true
|
|
9
|
-
data: UnknownObject
|
|
10
|
-
}
|
|
11
|
-
| {
|
|
12
|
-
success: false
|
|
13
|
-
errors: string[]
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
type ReturnDereferenceResult<Opt extends { sync?: boolean }> = Opt['sync'] extends true
|
|
17
|
-
? DereferenceResult
|
|
18
|
-
: Promise<DereferenceResult>
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Dereferences a JSON object, resolving all $ref pointers.
|
|
22
|
-
*
|
|
23
|
-
* This function can operate synchronously (no remote refs, no async plugins) or asynchronously (with remote refs).
|
|
24
|
-
* If `options.sync` is true, it simply wraps the input in a magic proxy and returns it.
|
|
25
|
-
* Otherwise, it bundles the document, resolving all $refs (including remote ones), and returns a promise.
|
|
26
|
-
*
|
|
27
|
-
* @param input - JSON Schema object to dereference.
|
|
28
|
-
* @param options - Optional settings. If `sync` is true, dereferencing is synchronous.
|
|
29
|
-
* @returns A DereferenceResult (or Promise thereof) indicating success and the dereferenced data, or errors.
|
|
30
|
-
*
|
|
31
|
-
* @example
|
|
32
|
-
* // Synchronous dereference (no remote refs)
|
|
33
|
-
* const result = dereference({ openapi: '3.0.0', info: { title: 'My API', version: '1.0.0' } }, { sync: true });
|
|
34
|
-
* if (result.success) {
|
|
35
|
-
* console.log(result.data); // Magic proxy-wrapped document
|
|
36
|
-
* }
|
|
37
|
-
*
|
|
38
|
-
* @example
|
|
39
|
-
* // Asynchronous dereference (with remote refs)
|
|
40
|
-
* dereference({ $ref: 'https://example.com/api.yaml' })
|
|
41
|
-
* .then(result => {
|
|
42
|
-
* if (result.success) {
|
|
43
|
-
* console.log(result.data); // Fully dereferenced document
|
|
44
|
-
* } else {
|
|
45
|
-
* console.error(result.errors);
|
|
46
|
-
* }
|
|
47
|
-
* });
|
|
48
|
-
*/
|
|
49
|
-
export const dereference = <Opts extends { sync?: boolean }>(
|
|
50
|
-
input: UnknownObject,
|
|
51
|
-
options?: Opts,
|
|
52
|
-
): ReturnDereferenceResult<Opts> => {
|
|
53
|
-
if (options?.sync) {
|
|
54
|
-
return {
|
|
55
|
-
success: true,
|
|
56
|
-
data: createMagicProxy(input),
|
|
57
|
-
} as ReturnDereferenceResult<Opts>
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const errors: string[] = []
|
|
61
|
-
|
|
62
|
-
return bundle(input, {
|
|
63
|
-
plugins: [fetchUrls()],
|
|
64
|
-
treeShake: false,
|
|
65
|
-
urlMap: true,
|
|
66
|
-
hooks: {
|
|
67
|
-
onResolveError(node) {
|
|
68
|
-
errors.push(`Failed to resolve ${node.$ref}`)
|
|
69
|
-
},
|
|
70
|
-
},
|
|
71
|
-
}).then((result) => {
|
|
72
|
-
if (errors.length > 0) {
|
|
73
|
-
return {
|
|
74
|
-
success: false,
|
|
75
|
-
errors,
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return {
|
|
80
|
-
success: true,
|
|
81
|
-
data: createMagicProxy(result as UnknownObject),
|
|
82
|
-
}
|
|
83
|
-
}) as ReturnDereferenceResult<Opts>
|
|
84
|
-
}
|
package/src/dereference/index.ts
DELETED