@scalar/json-magic 0.6.1 → 0.7.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 +4 -4
- package/CHANGELOG.md +13 -0
- package/dist/bundle/bundle.d.ts +1 -0
- package/dist/bundle/bundle.d.ts.map +1 -1
- package/dist/bundle/bundle.js +3 -3
- package/dist/bundle/bundle.js.map +2 -2
- package/dist/bundle/plugins/fetch-urls/index.d.ts.map +1 -1
- package/dist/bundle/plugins/fetch-urls/index.js +2 -1
- package/dist/bundle/plugins/fetch-urls/index.js.map +2 -2
- package/dist/bundle/plugins/parse-json/index.d.ts.map +1 -1
- package/dist/bundle/plugins/parse-json/index.js +7 -6
- package/dist/bundle/plugins/parse-json/index.js.map +2 -2
- package/dist/bundle/plugins/parse-yaml/index.d.ts.map +1 -1
- package/dist/bundle/plugins/parse-yaml/index.js +7 -6
- package/dist/bundle/plugins/parse-yaml/index.js.map +2 -2
- package/dist/bundle/plugins/read-files/index.d.ts.map +1 -1
- package/dist/bundle/plugins/read-files/index.js +2 -1
- package/dist/bundle/plugins/read-files/index.js.map +2 -2
- package/dist/diff/diff.d.ts.map +1 -1
- package/dist/diff/diff.js.map +2 -2
- package/dist/diff/utils.d.ts.map +1 -1
- package/dist/diff/utils.js.map +2 -2
- package/dist/magic-proxy/proxy.d.ts +1 -0
- package/dist/magic-proxy/proxy.d.ts.map +1 -1
- package/dist/magic-proxy/proxy.js +11 -1
- package/dist/magic-proxy/proxy.js.map +2 -2
- package/package.json +4 -4
- package/src/bundle/bundle.test.ts +278 -259
- package/src/bundle/bundle.ts +4 -4
- package/src/bundle/plugins/fetch-urls/index.test.ts +21 -24
- package/src/bundle/plugins/fetch-urls/index.ts +1 -0
- package/src/bundle/plugins/parse-json/index.test.ts +3 -1
- package/src/bundle/plugins/parse-json/index.ts +7 -6
- package/src/bundle/plugins/parse-yaml/index.test.ts +3 -1
- package/src/bundle/plugins/parse-yaml/index.ts +7 -6
- package/src/bundle/plugins/read-files/index.test.ts +4 -3
- package/src/bundle/plugins/read-files/index.ts +1 -0
- package/src/bundle/value-generator.test.ts +7 -8
- package/src/dereference/dereference.test.ts +6 -6
- package/src/diff/diff.ts +0 -1
- package/src/diff/utils.test.ts +2 -2
- package/src/diff/utils.ts +0 -2
- package/src/helpers/escape-json-pointer.test.ts +1 -1
- package/src/helpers/unescape-json-pointer.test.ts +1 -1
- package/src/magic-proxy/proxy.ts +14 -0
package/src/bundle/bundle.ts
CHANGED
|
@@ -65,7 +65,7 @@ export function isLocalRef(value: string): boolean {
|
|
|
65
65
|
return value.startsWith('#')
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
export type ResolveResult = { ok: true; data: unknown } | { ok: false }
|
|
68
|
+
export type ResolveResult = { ok: true; data: unknown; raw: string } | { ok: false }
|
|
69
69
|
|
|
70
70
|
/**
|
|
71
71
|
* Resolves a string by finding and executing the appropriate plugin.
|
|
@@ -80,16 +80,16 @@ export type ResolveResult = { ok: true; data: unknown } | { ok: false }
|
|
|
80
80
|
* // No matching plugin returns { ok: false }
|
|
81
81
|
* await resolveContents('#/components/schemas/User', [urlPlugin, filePlugin])
|
|
82
82
|
*/
|
|
83
|
-
|
|
83
|
+
function resolveContents(value: string, plugins: LoaderPlugin[]): Promise<ResolveResult> {
|
|
84
84
|
const plugin = plugins.find((p) => p.validate(value))
|
|
85
85
|
|
|
86
86
|
if (plugin) {
|
|
87
87
|
return plugin.exec(value)
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
return {
|
|
90
|
+
return Promise.resolve({
|
|
91
91
|
ok: false,
|
|
92
|
-
}
|
|
92
|
+
})
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
/**
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { fastify, type FastifyInstance } from 'fastify'
|
|
2
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
3
|
-
import { fetchUrl } from '.'
|
|
4
|
-
import assert from 'node:assert'
|
|
5
1
|
import { setTimeout } from 'node:timers/promises'
|
|
6
2
|
|
|
3
|
+
import { type FastifyInstance, fastify } from 'fastify'
|
|
4
|
+
import { assert, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
5
|
+
|
|
6
|
+
import { fetchUrl } from '.'
|
|
7
|
+
|
|
7
8
|
describe('fetchUrl', () => {
|
|
8
9
|
const noLimit = <T>(fn: () => Promise<T>) => fn()
|
|
9
10
|
|
|
@@ -12,11 +13,11 @@ describe('fetchUrl', () => {
|
|
|
12
13
|
|
|
13
14
|
beforeEach(() => {
|
|
14
15
|
server = fastify({ logger: false })
|
|
15
|
-
})
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
return async () => {
|
|
18
|
+
await server.close()
|
|
19
|
+
await setTimeout(100)
|
|
20
|
+
}
|
|
20
21
|
})
|
|
21
22
|
|
|
22
23
|
it('reads json response', async () => {
|
|
@@ -71,14 +72,14 @@ describe('fetchUrl', () => {
|
|
|
71
72
|
|
|
72
73
|
it('send headers to the specified domain', async () => {
|
|
73
74
|
const url = `http://localhost:${PORT}`
|
|
74
|
-
const
|
|
75
|
+
const headersSpy = vi.fn()
|
|
75
76
|
|
|
76
77
|
const response = {
|
|
77
78
|
message: '200OK',
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
server.get('/', (request, reply) => {
|
|
81
|
-
|
|
82
|
+
headersSpy(request.headers)
|
|
82
83
|
reply.send(response)
|
|
83
84
|
})
|
|
84
85
|
|
|
@@ -87,8 +88,8 @@ describe('fetchUrl', () => {
|
|
|
87
88
|
headers: [{ headers: { 'Authorization': 'Bearer <TOKEN>' }, domains: [`localhost:${PORT}`] }],
|
|
88
89
|
})
|
|
89
90
|
|
|
90
|
-
expect(
|
|
91
|
-
expect(
|
|
91
|
+
expect(headersSpy).toHaveBeenCalledOnce()
|
|
92
|
+
expect(headersSpy).toHaveBeenCalledWith({
|
|
92
93
|
'accept': '*/*',
|
|
93
94
|
'accept-encoding': 'gzip, deflate',
|
|
94
95
|
'accept-language': '*',
|
|
@@ -102,14 +103,14 @@ describe('fetchUrl', () => {
|
|
|
102
103
|
|
|
103
104
|
it('does not send headers to other domains', async () => {
|
|
104
105
|
const url = `http://localhost:${PORT}`
|
|
105
|
-
const
|
|
106
|
+
const headersSpy = vi.fn()
|
|
106
107
|
|
|
107
108
|
const response = {
|
|
108
109
|
message: '200OK',
|
|
109
110
|
}
|
|
110
111
|
|
|
111
112
|
server.get('/', (request, reply) => {
|
|
112
|
-
|
|
113
|
+
headersSpy(request.headers)
|
|
113
114
|
reply.send(response)
|
|
114
115
|
})
|
|
115
116
|
|
|
@@ -118,8 +119,8 @@ describe('fetchUrl', () => {
|
|
|
118
119
|
headers: [{ headers: { 'Authorization': 'Bearer <TOKEN>' }, domains: ['localhost:9932', 'localhost'] }],
|
|
119
120
|
})
|
|
120
121
|
|
|
121
|
-
expect(
|
|
122
|
-
expect(
|
|
122
|
+
expect(headersSpy).toHaveBeenCalledOnce()
|
|
123
|
+
expect(headersSpy).toHaveBeenNthCalledWith(1, {
|
|
123
124
|
'accept': '*/*',
|
|
124
125
|
'accept-encoding': 'gzip, deflate',
|
|
125
126
|
'accept-language': '*',
|
|
@@ -131,17 +132,13 @@ describe('fetchUrl', () => {
|
|
|
131
132
|
})
|
|
132
133
|
|
|
133
134
|
it('runs custom fetcher', async () => {
|
|
134
|
-
const
|
|
135
|
+
const customFetch = vi.fn().mockResolvedValue(new Response('{}', { status: 200 }))
|
|
135
136
|
|
|
136
137
|
await fetchUrl('https://example.com', (fn) => fn(), {
|
|
137
|
-
fetch:
|
|
138
|
-
fn(input, init)
|
|
139
|
-
|
|
140
|
-
return new Response('{}', { status: 200 })
|
|
141
|
-
},
|
|
138
|
+
fetch: customFetch,
|
|
142
139
|
})
|
|
143
140
|
|
|
144
|
-
expect(
|
|
145
|
-
expect(
|
|
141
|
+
expect(customFetch).toHaveBeenCalledOnce()
|
|
142
|
+
expect(customFetch).toHaveBeenCalledWith('https://example.com', { headers: undefined })
|
|
146
143
|
})
|
|
147
144
|
})
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { parseJson } from '@/bundle/plugins/parse-json'
|
|
2
1
|
import { describe, expect, it } from 'vitest'
|
|
3
2
|
|
|
3
|
+
import { parseJson } from '@/bundle/plugins/parse-json'
|
|
4
|
+
|
|
4
5
|
describe('parse-json', () => {
|
|
5
6
|
it.each([
|
|
6
7
|
['{}', true],
|
|
@@ -17,6 +18,7 @@ describe('parse-json', () => {
|
|
|
17
18
|
expect(await parseJson().exec('{ "message": "Hello World" }')).toEqual({
|
|
18
19
|
ok: true,
|
|
19
20
|
data: { message: 'Hello World' },
|
|
21
|
+
'raw': '{ "message": "Hello World" }',
|
|
20
22
|
})
|
|
21
23
|
})
|
|
22
24
|
})
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { LoaderPlugin
|
|
1
|
+
import type { LoaderPlugin } from '@/bundle'
|
|
2
2
|
import { isJsonObject } from '@/helpers/is-json-object'
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -15,16 +15,17 @@ export function parseJson(): LoaderPlugin {
|
|
|
15
15
|
return {
|
|
16
16
|
type: 'loader',
|
|
17
17
|
validate: isJsonObject,
|
|
18
|
-
exec:
|
|
18
|
+
exec: (value) => {
|
|
19
19
|
try {
|
|
20
|
-
return {
|
|
20
|
+
return Promise.resolve({
|
|
21
21
|
ok: true,
|
|
22
22
|
data: JSON.parse(value),
|
|
23
|
-
|
|
23
|
+
raw: value,
|
|
24
|
+
})
|
|
24
25
|
} catch {
|
|
25
|
-
return {
|
|
26
|
+
return Promise.resolve({
|
|
26
27
|
ok: false,
|
|
27
|
-
}
|
|
28
|
+
})
|
|
28
29
|
}
|
|
29
30
|
},
|
|
30
31
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { parseYaml } from '@/bundle/plugins/parse-yaml'
|
|
2
1
|
import { describe, expect, it } from 'vitest'
|
|
3
2
|
|
|
3
|
+
import { parseYaml } from '@/bundle/plugins/parse-yaml'
|
|
4
|
+
|
|
4
5
|
describe('parse-yaml', () => {
|
|
5
6
|
it.each([
|
|
6
7
|
['hi: hello\n', true],
|
|
@@ -19,6 +20,7 @@ describe('parse-yaml', () => {
|
|
|
19
20
|
expect(await parseYaml().exec('{ "message": "Hello World" }')).toEqual({
|
|
20
21
|
ok: true,
|
|
21
22
|
data: { message: 'Hello World' },
|
|
23
|
+
'raw': '{ "message": "Hello World" }',
|
|
22
24
|
})
|
|
23
25
|
})
|
|
24
26
|
})
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import YAML from 'yaml'
|
|
2
2
|
|
|
3
|
-
import type { LoaderPlugin
|
|
3
|
+
import type { LoaderPlugin } from '@/bundle'
|
|
4
4
|
import { isYaml } from '@/helpers/is-yaml'
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -17,16 +17,17 @@ export function parseYaml(): LoaderPlugin {
|
|
|
17
17
|
return {
|
|
18
18
|
type: 'loader',
|
|
19
19
|
validate: isYaml,
|
|
20
|
-
exec:
|
|
20
|
+
exec: (value) => {
|
|
21
21
|
try {
|
|
22
|
-
return {
|
|
22
|
+
return Promise.resolve({
|
|
23
23
|
ok: true,
|
|
24
24
|
data: YAML.parse(value, { merge: true, maxAliasCount: 10000 }),
|
|
25
|
-
|
|
25
|
+
raw: value,
|
|
26
|
+
})
|
|
26
27
|
} catch {
|
|
27
|
-
return {
|
|
28
|
+
return Promise.resolve({
|
|
28
29
|
ok: false,
|
|
29
|
-
}
|
|
30
|
+
})
|
|
30
31
|
}
|
|
31
32
|
},
|
|
32
33
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import fs from 'node:fs/promises'
|
|
2
1
|
import { randomUUID } from 'node:crypto'
|
|
3
|
-
import
|
|
2
|
+
import fs from 'node:fs/promises'
|
|
3
|
+
|
|
4
|
+
import { assert, describe, expect, it } from 'vitest'
|
|
5
|
+
|
|
4
6
|
import { readFile } from '.'
|
|
5
|
-
import assert from 'node:assert'
|
|
6
7
|
|
|
7
8
|
describe('readFile', () => {
|
|
8
9
|
it('reads json contents of a file', async () => {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { generateUniqueValue, uniqueValueGeneratorFactory } from './value-generator'
|
|
2
1
|
import { describe, expect, it } from 'vitest'
|
|
3
2
|
|
|
3
|
+
import { generateUniqueValue, uniqueValueGeneratorFactory } from './value-generator'
|
|
4
|
+
|
|
4
5
|
describe('generateUniqueHash', () => {
|
|
5
6
|
it('should generate hash values from the function we pass in', async () => {
|
|
6
|
-
const hashFunction =
|
|
7
|
+
const hashFunction = (value: string) => {
|
|
7
8
|
if (value === 'a') {
|
|
8
9
|
return 'a'
|
|
9
10
|
}
|
|
@@ -16,7 +17,7 @@ describe('generateUniqueHash', () => {
|
|
|
16
17
|
})
|
|
17
18
|
|
|
18
19
|
it('should return same value for the same input', async () => {
|
|
19
|
-
const hashFunction =
|
|
20
|
+
const hashFunction = (value: string) => {
|
|
20
21
|
if (value === 'a') {
|
|
21
22
|
return value
|
|
22
23
|
}
|
|
@@ -33,7 +34,7 @@ describe('generateUniqueHash', () => {
|
|
|
33
34
|
})
|
|
34
35
|
|
|
35
36
|
it('should handle hash collisions', async () => {
|
|
36
|
-
const hashFunction =
|
|
37
|
+
const hashFunction = (value: string) => {
|
|
37
38
|
// Hash a, b and c will produce collisions
|
|
38
39
|
if (value === 'a' || value === 'b' || value === 'c') {
|
|
39
40
|
return 'd'
|
|
@@ -83,9 +84,7 @@ describe('generateUniqueHash', () => {
|
|
|
83
84
|
})
|
|
84
85
|
|
|
85
86
|
it('should throw when it reaches max depth', async () => {
|
|
86
|
-
const hashFunction =
|
|
87
|
-
return 'a'
|
|
88
|
-
}
|
|
87
|
+
const hashFunction = () => 'a'
|
|
89
88
|
|
|
90
89
|
const map = {}
|
|
91
90
|
const result1 = await generateUniqueValue(hashFunction, 'a', map)
|
|
@@ -94,7 +93,7 @@ describe('generateUniqueHash', () => {
|
|
|
94
93
|
const result2 = await generateUniqueValue(hashFunction, 'a', map)
|
|
95
94
|
expect(result2).toBe('a')
|
|
96
95
|
|
|
97
|
-
expect(() => generateUniqueValue(hashFunction, 'b', map)).rejects.toThrowError()
|
|
96
|
+
await expect(() => generateUniqueValue(hashFunction, 'b', map)).rejects.toThrowError()
|
|
98
97
|
})
|
|
99
98
|
})
|
|
100
99
|
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { dereference } from '@/dereference/dereference'
|
|
2
|
-
import { fastify, type FastifyInstance } from 'fastify'
|
|
3
1
|
import { setTimeout } from 'node:timers/promises'
|
|
2
|
+
|
|
3
|
+
import { type FastifyInstance, fastify } from 'fastify'
|
|
4
4
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
|
5
5
|
|
|
6
|
+
import { dereference } from '@/dereference/dereference'
|
|
7
|
+
|
|
6
8
|
describe('dereference', () => {
|
|
7
9
|
describe('sync', () => {
|
|
8
|
-
it('should dereference JSON pointers',
|
|
10
|
+
it('should dereference JSON pointers', () => {
|
|
9
11
|
const data = {
|
|
10
12
|
users: {
|
|
11
13
|
name: 'John Doe',
|
|
@@ -82,9 +84,7 @@ describe('dereference', () => {
|
|
|
82
84
|
street: 'Sunset Boulevard',
|
|
83
85
|
},
|
|
84
86
|
}
|
|
85
|
-
server.get('/users',
|
|
86
|
-
return userProfile
|
|
87
|
-
})
|
|
87
|
+
server.get('/users', () => userProfile)
|
|
88
88
|
|
|
89
89
|
await server.listen({ port: port })
|
|
90
90
|
|
package/src/diff/diff.ts
CHANGED
package/src/diff/utils.test.ts
CHANGED
|
@@ -148,7 +148,7 @@ describe('isArrayEqual', () => {
|
|
|
148
148
|
[1, 2, 3],
|
|
149
149
|
[1, 2, 3],
|
|
150
150
|
],
|
|
151
|
-
// @ts-
|
|
151
|
+
// @ts-expect-error
|
|
152
152
|
])('should return true', (a, b) => expect(isArrayEqual(a, b)).toEqual(true))
|
|
153
153
|
|
|
154
154
|
test.each([
|
|
@@ -164,6 +164,6 @@ describe('isArrayEqual', () => {
|
|
|
164
164
|
[2, 2, 4],
|
|
165
165
|
[1, 2, 3],
|
|
166
166
|
],
|
|
167
|
-
// @ts-
|
|
167
|
+
// @ts-expect-error
|
|
168
168
|
])('should return false', (a, b) => expect(isArrayEqual(a, b)).toEqual(false))
|
|
169
169
|
})
|
package/src/diff/utils.ts
CHANGED
|
@@ -28,9 +28,7 @@ export const isKeyCollisions = (a: unknown, b: unknown) => {
|
|
|
28
28
|
const keys = new Set([...Object.keys(a), ...Object.keys(b)])
|
|
29
29
|
|
|
30
30
|
for (const key of keys) {
|
|
31
|
-
// @ts-ignore
|
|
32
31
|
if (a[key] !== undefined && b[key] !== undefined) {
|
|
33
|
-
// @ts-ignore
|
|
34
32
|
if (isKeyCollisions(a[key], b[key])) {
|
|
35
33
|
return true
|
|
36
34
|
}
|
|
@@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest'
|
|
|
2
2
|
|
|
3
3
|
import { escapeJsonPointer } from './escape-json-pointer'
|
|
4
4
|
|
|
5
|
-
describe('escapeJsonPointer',
|
|
5
|
+
describe('escapeJsonPointer', () => {
|
|
6
6
|
it('should escape a slash', () => {
|
|
7
7
|
expect(escapeJsonPointer('application/json')).toBe('application~1json')
|
|
8
8
|
})
|
|
@@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest'
|
|
|
2
2
|
|
|
3
3
|
import { unescapeJsonPointer } from './unescape-json-pointer'
|
|
4
4
|
|
|
5
|
-
describe('unescapeJsonPointer',
|
|
5
|
+
describe('unescapeJsonPointer', () => {
|
|
6
6
|
it('unescapes a slash', () => {
|
|
7
7
|
expect(unescapeJsonPointer('/foo~1bar~1baz')).toBe('/foo/bar/baz')
|
|
8
8
|
})
|
package/src/magic-proxy/proxy.ts
CHANGED
|
@@ -140,6 +140,10 @@ export const createMagicProxy = <T extends Record<keyof T & symbol, unknown>, S
|
|
|
140
140
|
|
|
141
141
|
// Resolve the reference and create a new magic proxy
|
|
142
142
|
const resolvedValue = getValueByPath(args.root, parseJsonPointer(`#/${path}`))
|
|
143
|
+
// Return early if the value is already a magic proxy
|
|
144
|
+
if (isMagicProxyObject(resolvedValue.value)) {
|
|
145
|
+
return resolvedValue.value
|
|
146
|
+
}
|
|
143
147
|
const proxiedValue = createMagicProxy(resolvedValue.value, options, {
|
|
144
148
|
...args,
|
|
145
149
|
currentContext: resolvedValue.context,
|
|
@@ -152,6 +156,12 @@ export const createMagicProxy = <T extends Record<keyof T & symbol, unknown>, S
|
|
|
152
156
|
|
|
153
157
|
// For all other properties, recursively wrap the value in a magic proxy
|
|
154
158
|
const value = Reflect.get(target, prop, receiver)
|
|
159
|
+
|
|
160
|
+
// Return early if the value is already a magic proxy
|
|
161
|
+
if (isMagicProxyObject(value)) {
|
|
162
|
+
return value
|
|
163
|
+
}
|
|
164
|
+
|
|
155
165
|
return createMagicProxy(value as T, options, { ...args, currentContext: id ?? args.currentContext })
|
|
156
166
|
},
|
|
157
167
|
/**
|
|
@@ -286,6 +296,10 @@ export const createMagicProxy = <T extends Record<keyof T & symbol, unknown>, S
|
|
|
286
296
|
return proxied
|
|
287
297
|
}
|
|
288
298
|
|
|
299
|
+
export const isMagicProxyObject = (obj: unknown): boolean => {
|
|
300
|
+
return typeof obj === 'object' && obj !== null && (obj as { [isMagicProxy]: boolean })[isMagicProxy] === true
|
|
301
|
+
}
|
|
302
|
+
|
|
289
303
|
/**
|
|
290
304
|
* Gets the raw (non-proxied) version of an object created by createMagicProxy.
|
|
291
305
|
* This is useful when you need to access the original object without the magic proxy wrapper.
|