@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.
Files changed (84) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/bundle/index.d.ts +1 -0
  3. package/dist/bundle/index.d.ts.map +1 -1
  4. package/dist/bundle/index.js.map +1 -1
  5. package/dist/bundle/plugins/browser.js.map +1 -1
  6. package/dist/bundle/plugins/node.d.ts +1 -1
  7. package/dist/bundle/plugins/node.js +1 -1
  8. package/dist/bundle/plugins/node.js.map +1 -1
  9. package/dist/dereference/index.d.ts.map +1 -1
  10. package/dist/dereference/index.js.map +2 -2
  11. package/dist/diff/index.d.ts +1 -1
  12. package/dist/diff/index.d.ts.map +1 -1
  13. package/dist/diff/index.js +1 -1
  14. package/dist/diff/index.js.map +2 -2
  15. package/dist/helpers/escape-json-pointer.d.ts +1 -1
  16. package/dist/helpers/escape-json-pointer.js.map +1 -1
  17. package/dist/magic-proxy/index.d.ts.map +1 -1
  18. package/dist/magic-proxy/index.js.map +2 -2
  19. package/dist/magic-proxy/proxy.d.ts +0 -1
  20. package/dist/magic-proxy/proxy.d.ts.map +1 -1
  21. package/dist/magic-proxy/proxy.js +1 -2
  22. package/dist/magic-proxy/proxy.js.map +2 -2
  23. package/package.json +12 -13
  24. package/.turbo/turbo-build.log +0 -10
  25. package/esbuild.ts +0 -15
  26. package/src/bundle/bundle.test.ts +0 -2917
  27. package/src/bundle/bundle.ts +0 -916
  28. package/src/bundle/create-limiter.test.ts +0 -28
  29. package/src/bundle/create-limiter.ts +0 -52
  30. package/src/bundle/index.ts +0 -3
  31. package/src/bundle/plugins/browser.ts +0 -4
  32. package/src/bundle/plugins/fetch-urls/index.test.ts +0 -141
  33. package/src/bundle/plugins/fetch-urls/index.ts +0 -105
  34. package/src/bundle/plugins/node.ts +0 -5
  35. package/src/bundle/plugins/parse-json/index.test.ts +0 -24
  36. package/src/bundle/plugins/parse-json/index.ts +0 -32
  37. package/src/bundle/plugins/parse-yaml/index.test.ts +0 -26
  38. package/src/bundle/plugins/parse-yaml/index.ts +0 -34
  39. package/src/bundle/plugins/read-files/index.test.ts +0 -36
  40. package/src/bundle/plugins/read-files/index.ts +0 -58
  41. package/src/bundle/value-generator.test.ts +0 -165
  42. package/src/bundle/value-generator.ts +0 -143
  43. package/src/dereference/dereference.test.ts +0 -142
  44. package/src/dereference/dereference.ts +0 -84
  45. package/src/dereference/index.ts +0 -2
  46. package/src/diff/apply.test.ts +0 -262
  47. package/src/diff/apply.ts +0 -83
  48. package/src/diff/diff.test.ts +0 -328
  49. package/src/diff/diff.ts +0 -93
  50. package/src/diff/index.test.ts +0 -150
  51. package/src/diff/index.ts +0 -5
  52. package/src/diff/merge.test.ts +0 -1109
  53. package/src/diff/merge.ts +0 -136
  54. package/src/diff/trie.test.ts +0 -30
  55. package/src/diff/trie.ts +0 -113
  56. package/src/diff/utils.test.ts +0 -169
  57. package/src/diff/utils.ts +0 -111
  58. package/src/helpers/convert-to-local-ref.test.ts +0 -211
  59. package/src/helpers/convert-to-local-ref.ts +0 -43
  60. package/src/helpers/escape-json-pointer.test.ts +0 -13
  61. package/src/helpers/escape-json-pointer.ts +0 -8
  62. package/src/helpers/get-schemas.test.ts +0 -356
  63. package/src/helpers/get-schemas.ts +0 -80
  64. package/src/helpers/get-segments-from-path.test.ts +0 -17
  65. package/src/helpers/get-segments-from-path.ts +0 -17
  66. package/src/helpers/get-value-by-path.test.ts +0 -338
  67. package/src/helpers/get-value-by-path.ts +0 -44
  68. package/src/helpers/is-json-object.ts +0 -31
  69. package/src/helpers/is-object.test.ts +0 -27
  70. package/src/helpers/is-object.ts +0 -4
  71. package/src/helpers/is-yaml.ts +0 -18
  72. package/src/helpers/json-path-utils.test.ts +0 -57
  73. package/src/helpers/json-path-utils.ts +0 -50
  74. package/src/helpers/normalize.test.ts +0 -92
  75. package/src/helpers/normalize.ts +0 -35
  76. package/src/helpers/unescape-json-pointer.test.ts +0 -23
  77. package/src/helpers/unescape-json-pointer.ts +0 -9
  78. package/src/magic-proxy/index.ts +0 -2
  79. package/src/magic-proxy/proxy.test.ts +0 -1987
  80. package/src/magic-proxy/proxy.ts +0 -323
  81. package/src/types.ts +0 -1
  82. package/tsconfig.build.json +0 -12
  83. package/tsconfig.json +0 -16
  84. package/vite.config.ts +0 -8
@@ -1,28 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
- import { createLimiter } from './create-limiter'
3
-
4
- describe('createLimiter', { timeout: 10000 }, () => {
5
- const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
6
-
7
- it('run in order and no more than the specified number of concurrent requests', async () => {
8
- const limiter = createLimiter(2)
9
-
10
- let active = 0
11
- const maxObserved: number[] = []
12
-
13
- const makeTask = (id: number) =>
14
- limiter(async () => {
15
- active++
16
- maxObserved.push(active)
17
- await sleep(100)
18
- active--
19
- return id
20
- })
21
-
22
- const tasks = [1, 2, 3, 4, 5].map(makeTask)
23
- const results = await Promise.all(tasks)
24
-
25
- expect(results).toEqual([1, 2, 3, 4, 5])
26
- expect(Math.max(...maxObserved)).toBeLessThanOrEqual(2)
27
- })
28
- })
@@ -1,52 +0,0 @@
1
- /**
2
- * Creates a function that limits the number of concurrent executions of async functions.
3
- *
4
- * @param maxConcurrent - Maximum number of concurrent executions allowed
5
- * @returns A function that wraps async functions to limit their concurrent execution
6
- *
7
- * @example
8
- * ```ts
9
- * const limiter = createLimiter(2) // Allow max 2 concurrent executions
10
- *
11
- * // These will run with max 2 at a time
12
- * const results = await Promise.all([
13
- * limiter(() => fetch('/api/1')),
14
- * limiter(() => fetch('/api/2')),
15
- * limiter(() => fetch('/api/3')),
16
- * limiter(() => fetch('/api/4'))
17
- * ])
18
- * ```
19
- */
20
- export function createLimiter(maxConcurrent: number) {
21
- let activeCount = 0
22
- const queue: (() => void)[] = []
23
-
24
- const next = () => {
25
- if (queue.length === 0 || activeCount >= maxConcurrent) {
26
- return
27
- }
28
-
29
- const resolve = queue.shift()
30
-
31
- if (resolve) {
32
- resolve()
33
- }
34
- }
35
-
36
- const run = async <T>(fn: () => Promise<T>): Promise<T> => {
37
- if (activeCount >= maxConcurrent) {
38
- await new Promise<void>((resolve) => queue.push(resolve))
39
- }
40
-
41
- activeCount++
42
- try {
43
- const result = await fn()
44
- return result
45
- } finally {
46
- activeCount--
47
- next()
48
- }
49
- }
50
-
51
- return run
52
- }
@@ -1,3 +0,0 @@
1
- export type { LifecyclePlugin, LoaderPlugin, Plugin, ResolveResult } from './bundle'
2
- // biome-ignore lint/performance/noBarrelFile: exporting bundle
3
- export { bundle, resolveAndCopyReferences } from './bundle'
@@ -1,4 +0,0 @@
1
- // biome-ignore lint/performance/noBarrelFile: <explanation>
2
- export { fetchUrls } from './fetch-urls'
3
- export { parseJson } from './parse-json'
4
- export { parseYaml } from './parse-yaml'
@@ -1,141 +0,0 @@
1
- import { type FastifyInstance, fastify } from 'fastify'
2
- import { assert, beforeEach, describe, expect, it, vi } from 'vitest'
3
-
4
- import { fetchUrl } from '.'
5
-
6
- describe('fetchUrl', () => {
7
- const noLimit = <T>(fn: () => Promise<T>) => fn()
8
-
9
- let server: FastifyInstance
10
- const PORT = 7291
11
-
12
- beforeEach(() => {
13
- server = fastify({ logger: false })
14
-
15
- return async () => {
16
- await server.close()
17
- }
18
- })
19
-
20
- it('reads json response', async () => {
21
- const url = `http://localhost:${PORT}`
22
-
23
- const response = {
24
- message: '200OK',
25
- }
26
-
27
- server.get('/', (_, reply) => {
28
- reply.send(response)
29
- })
30
-
31
- await server.listen({ port: PORT })
32
-
33
- const result = await fetchUrl(url, noLimit)
34
-
35
- expect(result.ok).toBe(true)
36
- assert(result.ok === true)
37
- expect(result.data).toEqual(response)
38
- })
39
-
40
- it('reads yaml response', async () => {
41
- const url = `http://localhost:${PORT}`
42
-
43
- server.get('/', (_, reply) => {
44
- reply.header('content-type', 'application/yml').send('a: a')
45
- })
46
-
47
- await server.listen({ port: PORT })
48
-
49
- const result = await fetchUrl(url, noLimit)
50
-
51
- expect(result.ok).toBe(true)
52
- assert(result.ok === true)
53
- expect(result.data).toEqual({ a: 'a' })
54
- })
55
-
56
- it('returns error on non-200 response', async () => {
57
- const url = `http://localhost:${PORT}`
58
-
59
- server.get('/', (_, reply) => {
60
- reply.status(404).send()
61
- })
62
-
63
- await server.listen({ port: PORT })
64
-
65
- const result = await fetchUrl(url, noLimit)
66
-
67
- expect(result.ok).toBe(false)
68
- })
69
-
70
- it('send headers to the specified domain', async () => {
71
- const url = `http://localhost:${PORT}`
72
- const headersSpy = vi.fn()
73
-
74
- const response = {
75
- message: '200OK',
76
- }
77
-
78
- server.get('/', (request, reply) => {
79
- headersSpy(request.headers)
80
- reply.send(response)
81
- })
82
-
83
- await server.listen({ port: PORT })
84
- await fetchUrl(url, noLimit, {
85
- headers: [{ headers: { 'Authorization': 'Bearer <TOKEN>' }, domains: [`localhost:${PORT}`] }],
86
- })
87
-
88
- expect(headersSpy).toHaveBeenCalledOnce()
89
- expect(headersSpy).toHaveBeenCalledWith({
90
- 'accept': '*/*',
91
- 'accept-encoding': 'gzip, deflate',
92
- 'accept-language': '*',
93
- 'authorization': 'Bearer <TOKEN>',
94
- 'connection': 'keep-alive',
95
- 'host': `localhost:${PORT}`,
96
- 'sec-fetch-mode': 'cors',
97
- 'user-agent': 'node',
98
- })
99
- })
100
-
101
- it('does not send headers to other domains', async () => {
102
- const url = `http://localhost:${PORT}`
103
- const headersSpy = vi.fn()
104
-
105
- const response = {
106
- message: '200OK',
107
- }
108
-
109
- server.get('/', (request, reply) => {
110
- headersSpy(request.headers)
111
- reply.send(response)
112
- })
113
-
114
- await server.listen({ port: PORT })
115
- await fetchUrl(url, noLimit, {
116
- headers: [{ headers: { 'Authorization': 'Bearer <TOKEN>' }, domains: ['localhost:9932', 'localhost'] }],
117
- })
118
-
119
- expect(headersSpy).toHaveBeenCalledOnce()
120
- expect(headersSpy).toHaveBeenNthCalledWith(1, {
121
- 'accept': '*/*',
122
- 'accept-encoding': 'gzip, deflate',
123
- 'accept-language': '*',
124
- 'connection': 'keep-alive',
125
- 'host': `localhost:${PORT}`,
126
- 'sec-fetch-mode': 'cors',
127
- 'user-agent': 'node',
128
- })
129
- })
130
-
131
- it('runs custom fetcher', async () => {
132
- const customFetch = vi.fn().mockResolvedValue(new Response('{}', { status: 200 }))
133
-
134
- await fetchUrl('https://example.com', (fn) => fn(), {
135
- fetch: customFetch,
136
- })
137
-
138
- expect(customFetch).toHaveBeenCalledOnce()
139
- expect(customFetch).toHaveBeenCalledWith('https://example.com', { headers: undefined })
140
- })
141
- })
@@ -1,105 +0,0 @@
1
- import type { LoaderPlugin, ResolveResult } from '@/bundle'
2
- import { isRemoteUrl } from '@/bundle/bundle'
3
- import { createLimiter } from '@/bundle/create-limiter'
4
- import { normalize } from '@/helpers/normalize'
5
-
6
- type FetchConfig = Partial<{
7
- headers: { headers: HeadersInit; domains: string[] }[]
8
- fetch: (input: string | URL | globalThis.Request, init?: RequestInit) => Promise<Response>
9
- }>
10
-
11
- /**
12
- * Safely checks for host from a URL
13
- * Needed because we cannot create a URL from a relative remote URL ex: examples/openapi.json
14
- */
15
- const getHost = (url: string): string | null => {
16
- try {
17
- return new URL(url).host
18
- } catch {
19
- return null
20
- }
21
- }
22
-
23
- /**
24
- * Fetches and normalizes data from a remote URL
25
- * @param url - The URL to fetch data from
26
- * @returns A promise that resolves to either the normalized data or an error result
27
- * @example
28
- * ```ts
29
- * const result = await fetchUrl('https://api.example.com/data.json')
30
- * if (result.ok) {
31
- * console.log(result.data) // The normalized data
32
- * } else {
33
- * console.log('Failed to fetch data')
34
- * }
35
- * ```
36
- */
37
- export async function fetchUrl(
38
- url: string,
39
- limiter: <T>(fn: () => Promise<T>) => Promise<T>,
40
- config?: FetchConfig,
41
- ): Promise<ResolveResult> {
42
- try {
43
- const host = getHost(url)
44
-
45
- // Get the headers that match the domain
46
- const headers = config?.headers?.find((a) => a.domains.find((d) => d === host) !== undefined)?.headers
47
-
48
- const exec = config?.fetch ?? fetch
49
-
50
- const result = await limiter(() =>
51
- exec(url, {
52
- headers,
53
- }),
54
- )
55
-
56
- if (result.ok) {
57
- const body = await result.text()
58
-
59
- return {
60
- ok: true,
61
- data: normalize(body),
62
- raw: body,
63
- }
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}`)
74
- return {
75
- ok: false,
76
- }
77
- } catch {
78
- console.warn(`[WARN] Failed to parse JSON/YAML from URL: ${url}`)
79
- return {
80
- ok: false,
81
- }
82
- }
83
- }
84
-
85
- /**
86
- * Creates a plugin for handling remote URL references.
87
- * This plugin validates and fetches data from HTTP/HTTPS URLs.
88
- *
89
- * @returns A plugin object with validate and exec functions
90
- * @example
91
- * const urlPlugin = fetchUrls()
92
- * if (urlPlugin.validate('https://example.com/schema.json')) {
93
- * const result = await urlPlugin.exec('https://example.com/schema.json')
94
- * }
95
- */
96
- export function fetchUrls(config?: FetchConfig & Partial<{ limit: number | null }>): LoaderPlugin {
97
- // If there is a limit specified we limit the number of concurrent calls
98
- const limiter = config?.limit ? createLimiter(config.limit) : <T>(fn: () => Promise<T>) => fn()
99
-
100
- return {
101
- type: 'loader',
102
- validate: isRemoteUrl,
103
- exec: (value) => fetchUrl(value, limiter, config),
104
- }
105
- }
@@ -1,5 +0,0 @@
1
- // biome-ignore lint/performance/noBarrelFile: <explanation>
2
- export { fetchUrls } from './fetch-urls'
3
- export { readFiles } from './read-files'
4
- export { parseJson } from './parse-json'
5
- export { parseYaml } from './parse-yaml'
@@ -1,24 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
-
3
- import { parseJson } from '@/bundle/plugins/parse-json'
4
-
5
- describe('parse-json', () => {
6
- it.each([
7
- ['{}', true],
8
- ['{ "a": "b" }', true],
9
- ['{ "a": 2 }', true],
10
- ["{ 'a': 2 }", false],
11
- ['{ some string', false],
12
- ['{ ', false],
13
- ])('should validate if strings are json valid format', (a, b) => {
14
- expect(parseJson().validate(a)).toBe(b)
15
- })
16
-
17
- it('should parse json string', async () => {
18
- expect(await parseJson().exec('{ "message": "Hello World" }')).toEqual({
19
- ok: true,
20
- data: { message: 'Hello World' },
21
- 'raw': '{ "message": "Hello World" }',
22
- })
23
- })
24
- })
@@ -1,32 +0,0 @@
1
- import type { LoaderPlugin } from '@/bundle'
2
- import { isJsonObject } from '@/helpers/is-json-object'
3
-
4
- /**
5
- * Creates a plugin that parses JSON strings into JavaScript objects.
6
- * @returns A plugin object with validate and exec functions
7
- * @example
8
- * ```ts
9
- * const jsonPlugin = parseJson()
10
- * const result = jsonPlugin.exec('{"name": "John", "age": 30}')
11
- * // result = { name: 'John', age: 30 }
12
- * ```
13
- */
14
- export function parseJson(): LoaderPlugin {
15
- return {
16
- type: 'loader',
17
- validate: isJsonObject,
18
- exec: (value) => {
19
- try {
20
- return Promise.resolve({
21
- ok: true,
22
- data: JSON.parse(value),
23
- raw: value,
24
- })
25
- } catch {
26
- return Promise.resolve({
27
- ok: false,
28
- })
29
- }
30
- },
31
- }
32
- }
@@ -1,26 +0,0 @@
1
- import { describe, expect, it } from 'vitest'
2
-
3
- import { parseYaml } from '@/bundle/plugins/parse-yaml'
4
-
5
- describe('parse-yaml', () => {
6
- it.each([
7
- ['hi: hello\n', true],
8
- ['- some: 1\n', true],
9
- ['valid: value\nhey: hi\n', true],
10
- ["{ 'a': 2 }", false],
11
- ['{ some string', false],
12
- ['{ ', false],
13
- ['{}', false],
14
- ['{ "json": "" }', false],
15
- ])('should validate if strings are yaml valid format', (a, b) => {
16
- expect(parseYaml().validate(a)).toBe(b)
17
- })
18
-
19
- it('should parse yaml string', async () => {
20
- expect(await parseYaml().exec('{ "message": "Hello World" }')).toEqual({
21
- ok: true,
22
- data: { message: 'Hello World' },
23
- 'raw': '{ "message": "Hello World" }',
24
- })
25
- })
26
- })
@@ -1,34 +0,0 @@
1
- import YAML from 'yaml'
2
-
3
- import type { LoaderPlugin } from '@/bundle'
4
- import { isYaml } from '@/helpers/is-yaml'
5
-
6
- /**
7
- * Creates a plugin that parses YAML strings into JavaScript objects.
8
- * @returns A plugin object with validate and exec functions
9
- * @example
10
- * ```ts
11
- * const yamlPlugin = parseYaml()
12
- * const result = yamlPlugin.exec('name: John\nage: 30')
13
- * // result = { name: 'John', age: 30 }
14
- * ```
15
- */
16
- export function parseYaml(): LoaderPlugin {
17
- return {
18
- type: 'loader',
19
- validate: isYaml,
20
- exec: (value) => {
21
- try {
22
- return Promise.resolve({
23
- ok: true,
24
- data: YAML.parse(value, { merge: true, maxAliasCount: 10000 }),
25
- raw: value,
26
- })
27
- } catch {
28
- return Promise.resolve({
29
- ok: false,
30
- })
31
- }
32
- },
33
- }
34
- }
@@ -1,36 +0,0 @@
1
- import { randomUUID } from 'node:crypto'
2
- import fs from 'node:fs/promises'
3
-
4
- import { assert, describe, expect, it } from 'vitest'
5
-
6
- import { readFile } from '.'
7
-
8
- describe('readFile', () => {
9
- it('reads json contents of a file', async () => {
10
- const contents = { message: 'ok' }
11
- const path = randomUUID()
12
- await fs.writeFile(path, JSON.stringify(contents))
13
-
14
- const result = await readFile(path)
15
- await fs.rm(path)
16
-
17
- expect(result.ok).toBe(true)
18
- assert(result.ok === true)
19
-
20
- expect(result.data).toEqual(contents)
21
- })
22
-
23
- it('reads yml contents of a file', async () => {
24
- const contents = 'a: a'
25
- const path = randomUUID()
26
- await fs.writeFile(path, contents)
27
-
28
- const result = await readFile(path)
29
- await fs.rm(path)
30
-
31
- expect(result.ok).toBe(true)
32
- assert(result.ok === true)
33
-
34
- expect(result.data).toEqual({ a: 'a' })
35
- })
36
- })
@@ -1,58 +0,0 @@
1
- import type { LoaderPlugin, ResolveResult } from '@/bundle'
2
- import { isFilePath } from '@/bundle/bundle'
3
- import { normalize } from '@/helpers/normalize'
4
-
5
- /**
6
- * Reads and normalizes data from a local file
7
- * @param path - The file path to read from
8
- * @returns A promise that resolves to either the normalized data or an error result
9
- * @example
10
- * ```ts
11
- * const result = await readFile('./schemas/user.json')
12
- * if (result.ok) {
13
- * console.log(result.data) // The normalized data
14
- * } else {
15
- * console.log('Failed to read file')
16
- * }
17
- * ```
18
- */
19
- export async function readFile(path: string): Promise<ResolveResult> {
20
- const fs = typeof window === 'undefined' ? await import('node:fs/promises') : undefined
21
-
22
- if (fs === undefined) {
23
- throw 'Can not use readFiles plugin outside of a node environment'
24
- }
25
-
26
- try {
27
- const fileContents = await fs.readFile(path, { encoding: 'utf-8' })
28
-
29
- return {
30
- ok: true,
31
- data: normalize(fileContents),
32
- raw: fileContents,
33
- }
34
- } catch {
35
- return {
36
- ok: false,
37
- }
38
- }
39
- }
40
-
41
- /**
42
- * Creates a plugin for handling local file references.
43
- * This plugin validates and reads data from local filesystem paths.
44
- *
45
- * @returns A plugin object with validate and exec functions
46
- * @example
47
- * const filePlugin = readFiles()
48
- * if (filePlugin.validate('./local-schema.json')) {
49
- * const result = await filePlugin.exec('./local-schema.json')
50
- * }
51
- */
52
- export function readFiles(): LoaderPlugin {
53
- return {
54
- type: 'loader',
55
- validate: isFilePath,
56
- exec: readFile,
57
- }
58
- }