@scalar/json-magic 0.6.0 → 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.
Files changed (46) hide show
  1. package/.turbo/turbo-build.log +4 -4
  2. package/CHANGELOG.md +22 -0
  3. package/dist/bundle/bundle.d.ts +1 -0
  4. package/dist/bundle/bundle.d.ts.map +1 -1
  5. package/dist/bundle/bundle.js +3 -3
  6. package/dist/bundle/bundle.js.map +2 -2
  7. package/dist/bundle/plugins/fetch-urls/index.d.ts.map +1 -1
  8. package/dist/bundle/plugins/fetch-urls/index.js +8 -1
  9. package/dist/bundle/plugins/fetch-urls/index.js.map +2 -2
  10. package/dist/bundle/plugins/parse-json/index.d.ts.map +1 -1
  11. package/dist/bundle/plugins/parse-json/index.js +7 -6
  12. package/dist/bundle/plugins/parse-json/index.js.map +2 -2
  13. package/dist/bundle/plugins/parse-yaml/index.d.ts.map +1 -1
  14. package/dist/bundle/plugins/parse-yaml/index.js +7 -6
  15. package/dist/bundle/plugins/parse-yaml/index.js.map +2 -2
  16. package/dist/bundle/plugins/read-files/index.d.ts.map +1 -1
  17. package/dist/bundle/plugins/read-files/index.js +2 -1
  18. package/dist/bundle/plugins/read-files/index.js.map +2 -2
  19. package/dist/diff/diff.d.ts.map +1 -1
  20. package/dist/diff/diff.js.map +2 -2
  21. package/dist/diff/utils.d.ts.map +1 -1
  22. package/dist/diff/utils.js.map +2 -2
  23. package/dist/magic-proxy/proxy.d.ts +7 -6
  24. package/dist/magic-proxy/proxy.d.ts.map +1 -1
  25. package/dist/magic-proxy/proxy.js +21 -11
  26. package/dist/magic-proxy/proxy.js.map +2 -2
  27. package/package.json +4 -4
  28. package/src/bundle/bundle.test.ts +278 -259
  29. package/src/bundle/bundle.ts +4 -4
  30. package/src/bundle/plugins/fetch-urls/index.test.ts +21 -24
  31. package/src/bundle/plugins/fetch-urls/index.ts +10 -0
  32. package/src/bundle/plugins/parse-json/index.test.ts +3 -1
  33. package/src/bundle/plugins/parse-json/index.ts +7 -6
  34. package/src/bundle/plugins/parse-yaml/index.test.ts +3 -1
  35. package/src/bundle/plugins/parse-yaml/index.ts +7 -6
  36. package/src/bundle/plugins/read-files/index.test.ts +4 -3
  37. package/src/bundle/plugins/read-files/index.ts +1 -0
  38. package/src/bundle/value-generator.test.ts +7 -8
  39. package/src/dereference/dereference.test.ts +6 -6
  40. package/src/diff/diff.ts +0 -1
  41. package/src/diff/utils.test.ts +2 -2
  42. package/src/diff/utils.ts +0 -2
  43. package/src/helpers/escape-json-pointer.test.ts +1 -1
  44. package/src/helpers/unescape-json-pointer.test.ts +1 -1
  45. package/src/magic-proxy/proxy.test.ts +108 -76
  46. package/src/magic-proxy/proxy.ts +34 -20
@@ -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
- async function resolveContents(value: string, plugins: LoaderPlugin[]): Promise<ResolveResult> {
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
- afterEach(async () => {
18
- await server.close()
19
- await setTimeout(100)
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 fn = vi.fn()
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
- fn(request.headers)
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(fn).toHaveBeenCalled()
91
- expect(fn.mock.calls[0][0]).toEqual({
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 fn = vi.fn()
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
- fn(request.headers)
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(fn).toHaveBeenCalled()
122
- expect(fn.mock.calls[0][0]).toEqual({
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 fn = vi.fn()
135
+ const customFetch = vi.fn().mockResolvedValue(new Response('{}', { status: 200 }))
135
136
 
136
137
  await fetchUrl('https://example.com', (fn) => fn(), {
137
- fetch: async (input, init) => {
138
- fn(input, init)
139
-
140
- return new Response('{}', { status: 200 })
141
- },
138
+ fetch: customFetch,
142
139
  })
143
140
 
144
- expect(fn).toHaveBeenCalled()
145
- expect(fn).toHaveBeenCalledWith('https://example.com', { headers: undefined })
141
+ expect(customFetch).toHaveBeenCalledOnce()
142
+ expect(customFetch).toHaveBeenCalledWith('https://example.com', { headers: undefined })
146
143
  })
147
144
  })
@@ -59,13 +59,23 @@ export async function fetchUrl(
59
59
  return {
60
60
  ok: true,
61
61
  data: normalize(body),
62
+ raw: body,
62
63
  }
63
64
  }
64
65
 
66
+ const contentType = result.headers.get('Content-Type') ?? ''
67
+
68
+ // Warn if the content type is HTML or XML as we only support JSON/YAML
69
+ if (['text/html', 'application/xml'].includes(contentType)) {
70
+ console.warn(`[WARN] We only support JSON/YAML formats, received ${contentType}`)
71
+ }
72
+
73
+ console.warn(`[WARN] Fetch failed with status ${result.status} ${result.statusText} for URL: ${url}`)
65
74
  return {
66
75
  ok: false,
67
76
  }
68
77
  } catch {
78
+ console.warn(`[WARN] Failed to parse JSON/YAML from URL: ${url}`)
69
79
  return {
70
80
  ok: false,
71
81
  }
@@ -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, ResolveResult } from '@/bundle'
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: async (value): Promise<ResolveResult> => {
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, ResolveResult } from '@/bundle'
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: async (value): Promise<ResolveResult> => {
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 { describe, expect, it } from 'vitest'
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 () => {
@@ -29,6 +29,7 @@ export async function readFile(path: string): Promise<ResolveResult> {
29
29
  return {
30
30
  ok: true,
31
31
  data: normalize(fileContents),
32
+ raw: fileContents,
32
33
  }
33
34
  } catch {
34
35
  return {
@@ -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 = async (value: string) => {
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 = async (value: string) => {
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 = async (value: string) => {
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 = async () => {
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', async () => {
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', async () => {
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
@@ -76,7 +76,6 @@ export const diff = <T extends Record<string, unknown>>(doc1: Record<string, unk
76
76
  const keys = new Set([...Object.keys(el1), ...Object.keys(el2)])
77
77
 
78
78
  for (const key of keys) {
79
- // @ts-ignore
80
79
  bfs(el1[key], el2[key], [...prefix, key])
81
80
  }
82
81
  return
@@ -148,7 +148,7 @@ describe('isArrayEqual', () => {
148
148
  [1, 2, 3],
149
149
  [1, 2, 3],
150
150
  ],
151
- // @ts-ignore
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-ignore
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', async () => {
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', async () => {
5
+ describe('unescapeJsonPointer', () => {
6
6
  it('unescapes a slash', () => {
7
7
  expect(unescapeJsonPointer('/foo~1bar~1baz')).toBe('/foo/bar/baz')
8
8
  })