@scalar/json-magic 0.12.3 → 0.12.4

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 (72) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/bundle/bundle.js +476 -250
  3. package/dist/bundle/index.js +1 -6
  4. package/dist/bundle/plugins/browser.js +4 -9
  5. package/dist/bundle/plugins/fetch-urls/index.js +76 -49
  6. package/dist/bundle/plugins/node.js +5 -11
  7. package/dist/bundle/plugins/parse-json/index.js +30 -23
  8. package/dist/bundle/plugins/parse-yaml/index.js +31 -24
  9. package/dist/bundle/plugins/read-files/index.js +50 -29
  10. package/dist/bundle/value-generator.js +118 -49
  11. package/dist/dereference/dereference.js +66 -36
  12. package/dist/dereference/index.js +1 -5
  13. package/dist/diff/apply.js +69 -36
  14. package/dist/diff/diff.js +68 -32
  15. package/dist/diff/index.js +4 -9
  16. package/dist/diff/merge.js +122 -60
  17. package/dist/diff/trie.js +100 -77
  18. package/dist/diff/utils.js +96 -41
  19. package/dist/helpers/convert-to-local-ref.js +30 -23
  20. package/dist/helpers/escape-json-pointer.js +7 -6
  21. package/dist/helpers/get-schemas.js +59 -32
  22. package/dist/helpers/get-segments-from-path.js +13 -9
  23. package/dist/helpers/get-value-by-path.js +38 -22
  24. package/dist/helpers/is-file-path.js +19 -9
  25. package/dist/helpers/is-http-url.js +20 -11
  26. package/dist/helpers/is-json-object.js +29 -15
  27. package/dist/helpers/is-yaml.js +17 -6
  28. package/dist/helpers/json-path-utils.js +30 -15
  29. package/dist/helpers/normalize.js +27 -26
  30. package/dist/helpers/resolve-reference-path.js +28 -16
  31. package/dist/helpers/set-value-at-path.js +72 -26
  32. package/dist/helpers/to-relative-path.js +40 -28
  33. package/dist/helpers/unescape-json-pointer.js +8 -6
  34. package/dist/magic-proxy/index.js +1 -6
  35. package/dist/magic-proxy/proxy.js +245 -186
  36. package/dist/types.js +1 -1
  37. package/package.json +5 -7
  38. package/dist/bundle/bundle.js.map +0 -7
  39. package/dist/bundle/index.js.map +0 -7
  40. package/dist/bundle/plugins/browser.js.map +0 -7
  41. package/dist/bundle/plugins/fetch-urls/index.js.map +0 -7
  42. package/dist/bundle/plugins/node.js.map +0 -7
  43. package/dist/bundle/plugins/parse-json/index.js.map +0 -7
  44. package/dist/bundle/plugins/parse-yaml/index.js.map +0 -7
  45. package/dist/bundle/plugins/read-files/index.js.map +0 -7
  46. package/dist/bundle/value-generator.js.map +0 -7
  47. package/dist/dereference/dereference.js.map +0 -7
  48. package/dist/dereference/index.js.map +0 -7
  49. package/dist/diff/apply.js.map +0 -7
  50. package/dist/diff/diff.js.map +0 -7
  51. package/dist/diff/index.js.map +0 -7
  52. package/dist/diff/merge.js.map +0 -7
  53. package/dist/diff/trie.js.map +0 -7
  54. package/dist/diff/utils.js.map +0 -7
  55. package/dist/helpers/convert-to-local-ref.js.map +0 -7
  56. package/dist/helpers/escape-json-pointer.js.map +0 -7
  57. package/dist/helpers/get-schemas.js.map +0 -7
  58. package/dist/helpers/get-segments-from-path.js.map +0 -7
  59. package/dist/helpers/get-value-by-path.js.map +0 -7
  60. package/dist/helpers/is-file-path.js.map +0 -7
  61. package/dist/helpers/is-http-url.js.map +0 -7
  62. package/dist/helpers/is-json-object.js.map +0 -7
  63. package/dist/helpers/is-yaml.js.map +0 -7
  64. package/dist/helpers/json-path-utils.js.map +0 -7
  65. package/dist/helpers/normalize.js.map +0 -7
  66. package/dist/helpers/resolve-reference-path.js.map +0 -7
  67. package/dist/helpers/set-value-at-path.js.map +0 -7
  68. package/dist/helpers/to-relative-path.js.map +0 -7
  69. package/dist/helpers/unescape-json-pointer.js.map +0 -7
  70. package/dist/magic-proxy/index.js.map +0 -7
  71. package/dist/magic-proxy/proxy.js.map +0 -7
  72. package/dist/types.js.map +0 -7
@@ -1,6 +1 @@
1
- import { bundle, resolveAndCopyReferences } from "./bundle.js";
2
- export {
3
- bundle,
4
- resolveAndCopyReferences
5
- };
6
- //# sourceMappingURL=index.js.map
1
+ export { bundle, resolveAndCopyReferences } from './bundle.js';
@@ -1,9 +1,4 @@
1
- import { fetchUrls } from "./fetch-urls/index.js";
2
- import { parseJson } from "./parse-json/index.js";
3
- import { parseYaml } from "./parse-yaml/index.js";
4
- export {
5
- fetchUrls,
6
- parseJson,
7
- parseYaml
8
- };
9
- //# sourceMappingURL=browser.js.map
1
+ // biome-ignore lint/performance/noBarrelFile: entrypoint
2
+ export { fetchUrls } from './fetch-urls/index.js';
3
+ export { parseJson } from './parse-json/index.js';
4
+ export { parseYaml } from './parse-yaml/index.js';
@@ -1,56 +1,83 @@
1
- import { createLimiter } from "@scalar/helpers/general/create-limiter";
2
- import { isHttpUrl } from "../../../helpers/is-http-url.js";
3
- import { normalize } from "../../../helpers/normalize.js";
1
+ import { createLimiter } from '@scalar/helpers/general/create-limiter';
2
+ import { isHttpUrl } from '../../../helpers/is-http-url.js';
3
+ import { normalize } from '../../../helpers/normalize.js';
4
+ /**
5
+ * Safely checks for host from a URL
6
+ * Needed because we cannot create a URL from a relative remote URL ex: examples/openapi.json
7
+ */
4
8
  const getHost = (url) => {
5
- try {
6
- return new URL(url).host;
7
- } catch {
8
- return null;
9
- }
9
+ try {
10
+ return new URL(url).host;
11
+ }
12
+ catch {
13
+ return null;
14
+ }
10
15
  };
11
- async function fetchUrl(url, limiter, config) {
12
- try {
13
- const host = getHost(url);
14
- const headers = config?.headers?.find((a) => a.domains.find((d) => d === host) !== void 0)?.headers;
15
- const exec = config?.fetch ?? fetch;
16
- const result = await limiter(
17
- () => exec(url, {
18
- headers
19
- })
20
- );
21
- if (result.ok) {
22
- const body = await result.text();
23
- return {
24
- ok: true,
25
- data: normalize(body),
26
- raw: body
27
- };
16
+ /**
17
+ * Fetches and normalizes data from a remote URL
18
+ * @param url - The URL to fetch data from
19
+ * @returns A promise that resolves to either the normalized data or an error result
20
+ * @example
21
+ * ```ts
22
+ * const result = await fetchUrl('https://api.example.com/data.json')
23
+ * if (result.ok) {
24
+ * console.log(result.data) // The normalized data
25
+ * } else {
26
+ * console.log('Failed to fetch data')
27
+ * }
28
+ * ```
29
+ */
30
+ export async function fetchUrl(url, limiter, config) {
31
+ try {
32
+ const host = getHost(url);
33
+ // Get the headers that match the domain
34
+ const headers = config?.headers?.find((a) => a.domains.find((d) => d === host) !== undefined)?.headers;
35
+ const exec = config?.fetch ?? fetch;
36
+ const result = await limiter(() => exec(url, {
37
+ headers,
38
+ }));
39
+ if (result.ok) {
40
+ const body = await result.text();
41
+ return {
42
+ ok: true,
43
+ data: normalize(body),
44
+ raw: body,
45
+ };
46
+ }
47
+ const contentType = result.headers.get('Content-Type') ?? '';
48
+ // Warn if the content type is HTML or XML as we only support JSON/YAML
49
+ if (['text/html', 'application/xml'].includes(contentType)) {
50
+ console.warn(`[WARN] We only support JSON/YAML formats, received ${contentType}`);
51
+ }
52
+ console.warn(`[WARN] Fetch failed with status ${result.status} ${result.statusText} for URL: ${url}`);
53
+ return {
54
+ ok: false,
55
+ };
28
56
  }
29
- const contentType = result.headers.get("Content-Type") ?? "";
30
- if (["text/html", "application/xml"].includes(contentType)) {
31
- console.warn(`[WARN] We only support JSON/YAML formats, received ${contentType}`);
57
+ catch {
58
+ console.warn(`[WARN] Failed to parse JSON/YAML from URL: ${url}`);
59
+ return {
60
+ ok: false,
61
+ };
32
62
  }
33
- console.warn(`[WARN] Fetch failed with status ${result.status} ${result.statusText} for URL: ${url}`);
34
- return {
35
- ok: false
36
- };
37
- } catch {
38
- console.warn(`[WARN] Failed to parse JSON/YAML from URL: ${url}`);
63
+ }
64
+ /**
65
+ * Creates a plugin for handling remote URL references.
66
+ * This plugin validates and fetches data from HTTP/HTTPS URLs.
67
+ *
68
+ * @returns A plugin object with validate and exec functions
69
+ * @example
70
+ * const urlPlugin = fetchUrls()
71
+ * if (urlPlugin.validate('https://example.com/schema.json')) {
72
+ * const result = await urlPlugin.exec('https://example.com/schema.json')
73
+ * }
74
+ */
75
+ export function fetchUrls(config) {
76
+ // If there is a limit specified we limit the number of concurrent calls
77
+ const limiter = config?.limit ? createLimiter(config.limit) : (fn) => fn();
39
78
  return {
40
- ok: false
79
+ type: 'loader',
80
+ validate: isHttpUrl,
81
+ exec: (value) => fetchUrl(value, limiter, config),
41
82
  };
42
- }
43
- }
44
- function fetchUrls(config) {
45
- const limiter = config?.limit ? createLimiter(config.limit) : (fn) => fn();
46
- return {
47
- type: "loader",
48
- validate: isHttpUrl,
49
- exec: (value) => fetchUrl(value, limiter, config)
50
- };
51
83
  }
52
- export {
53
- fetchUrl,
54
- fetchUrls
55
- };
56
- //# sourceMappingURL=index.js.map
@@ -1,11 +1,5 @@
1
- import { fetchUrls } from "./fetch-urls/index.js";
2
- import { parseJson } from "./parse-json/index.js";
3
- import { parseYaml } from "./parse-yaml/index.js";
4
- import { readFiles } from "./read-files/index.js";
5
- export {
6
- fetchUrls,
7
- parseJson,
8
- parseYaml,
9
- readFiles
10
- };
11
- //# sourceMappingURL=node.js.map
1
+ // biome-ignore lint/performance/noBarrelFile: entrypoint
2
+ export { fetchUrls } from './fetch-urls/index.js';
3
+ export { parseJson } from './parse-json/index.js';
4
+ export { parseYaml } from './parse-yaml/index.js';
5
+ export { readFiles } from './read-files/index.js';
@@ -1,24 +1,31 @@
1
- import { isJsonObject } from "../../../helpers/is-json-object.js";
2
- function parseJson() {
3
- return {
4
- type: "loader",
5
- validate: isJsonObject,
6
- exec: (value) => {
7
- try {
8
- return Promise.resolve({
9
- ok: true,
10
- data: JSON.parse(value),
11
- raw: value
12
- });
13
- } catch {
14
- return Promise.resolve({
15
- ok: false
16
- });
17
- }
18
- }
19
- };
1
+ import { isJsonObject } from '../../../helpers/is-json-object.js';
2
+ /**
3
+ * Creates a plugin that parses JSON strings into JavaScript objects.
4
+ * @returns A plugin object with validate and exec functions
5
+ * @example
6
+ * ```ts
7
+ * const jsonPlugin = parseJson()
8
+ * const result = jsonPlugin.exec('{"name": "John", "age": 30}')
9
+ * // result = { name: 'John', age: 30 }
10
+ * ```
11
+ */
12
+ export function parseJson() {
13
+ return {
14
+ type: 'loader',
15
+ validate: isJsonObject,
16
+ exec: (value) => {
17
+ try {
18
+ return Promise.resolve({
19
+ ok: true,
20
+ data: JSON.parse(value),
21
+ raw: value,
22
+ });
23
+ }
24
+ catch {
25
+ return Promise.resolve({
26
+ ok: false,
27
+ });
28
+ }
29
+ },
30
+ };
20
31
  }
21
- export {
22
- parseJson
23
- };
24
- //# sourceMappingURL=index.js.map
@@ -1,25 +1,32 @@
1
- import YAML from "yaml";
2
- import { isYaml } from "../../../helpers/is-yaml.js";
3
- function parseYaml() {
4
- return {
5
- type: "loader",
6
- validate: isYaml,
7
- exec: (value) => {
8
- try {
9
- return Promise.resolve({
10
- ok: true,
11
- data: YAML.parse(value, { merge: true, maxAliasCount: 1e4 }),
12
- raw: value
13
- });
14
- } catch {
15
- return Promise.resolve({
16
- ok: false
17
- });
18
- }
19
- }
20
- };
1
+ import YAML from 'yaml';
2
+ import { isYaml } from '../../../helpers/is-yaml.js';
3
+ /**
4
+ * Creates a plugin that parses YAML strings into JavaScript objects.
5
+ * @returns A plugin object with validate and exec functions
6
+ * @example
7
+ * ```ts
8
+ * const yamlPlugin = parseYaml()
9
+ * const result = yamlPlugin.exec('name: John\nage: 30')
10
+ * // result = { name: 'John', age: 30 }
11
+ * ```
12
+ */
13
+ export function parseYaml() {
14
+ return {
15
+ type: 'loader',
16
+ validate: isYaml,
17
+ exec: (value) => {
18
+ try {
19
+ return Promise.resolve({
20
+ ok: true,
21
+ data: YAML.parse(value, { merge: true, maxAliasCount: 10000 }),
22
+ raw: value,
23
+ });
24
+ }
25
+ catch {
26
+ return Promise.resolve({
27
+ ok: false,
28
+ });
29
+ }
30
+ },
31
+ };
21
32
  }
22
- export {
23
- parseYaml
24
- };
25
- //# sourceMappingURL=index.js.map
@@ -1,32 +1,53 @@
1
- import { isFilePath } from "../../../helpers/is-file-path.js";
2
- import { normalize } from "../../../helpers/normalize.js";
3
- async function readFile(path) {
4
- const fs = typeof window === "undefined" ? await import("node:fs/promises") : void 0;
5
- if (fs === void 0) {
6
- throw "Can not use readFiles plugin outside of a node environment";
7
- }
8
- try {
9
- const fileContents = await fs.readFile(path, { encoding: "utf-8" });
10
- return {
11
- ok: true,
12
- data: normalize(fileContents),
13
- raw: fileContents
14
- };
15
- } catch {
1
+ import { isFilePath } from '../../../helpers/is-file-path.js';
2
+ import { normalize } from '../../../helpers/normalize.js';
3
+ /**
4
+ * Reads and normalizes data from a local file
5
+ * @param path - The file path to read from
6
+ * @returns A promise that resolves to either the normalized data or an error result
7
+ * @example
8
+ * ```ts
9
+ * const result = await readFile('./schemas/user.json')
10
+ * if (result.ok) {
11
+ * console.log(result.data) // The normalized data
12
+ * } else {
13
+ * console.log('Failed to read file')
14
+ * }
15
+ * ```
16
+ */
17
+ export async function readFile(path) {
18
+ const fs = typeof window === 'undefined' ? await import('node:fs/promises') : undefined;
19
+ if (fs === undefined) {
20
+ throw 'Can not use readFiles plugin outside of a node environment';
21
+ }
22
+ try {
23
+ const fileContents = await fs.readFile(path, { encoding: 'utf-8' });
24
+ return {
25
+ ok: true,
26
+ data: normalize(fileContents),
27
+ raw: fileContents,
28
+ };
29
+ }
30
+ catch {
31
+ return {
32
+ ok: false,
33
+ };
34
+ }
35
+ }
36
+ /**
37
+ * Creates a plugin for handling local file references.
38
+ * This plugin validates and reads data from local filesystem paths.
39
+ *
40
+ * @returns A plugin object with validate and exec functions
41
+ * @example
42
+ * const filePlugin = readFiles()
43
+ * if (filePlugin.validate('./local-schema.json')) {
44
+ * const result = await filePlugin.exec('./local-schema.json')
45
+ * }
46
+ */
47
+ export function readFiles() {
16
48
  return {
17
- ok: false
49
+ type: 'loader',
50
+ validate: isFilePath,
51
+ exec: readFile,
18
52
  };
19
- }
20
- }
21
- function readFiles() {
22
- return {
23
- type: "loader",
24
- validate: isFilePath,
25
- exec: readFile
26
- };
27
53
  }
28
- export {
29
- readFile,
30
- readFiles
31
- };
32
- //# sourceMappingURL=index.js.map
@@ -1,52 +1,121 @@
1
- import { generateHash } from "@scalar/helpers/string/generate-hash";
2
- function getHash(value) {
3
- const hashHex = generateHash(value);
4
- const hash = hashHex.substring(0, 7);
5
- return hash.match(/^\d+$/) ? "a" + hash.substring(1) : hash;
1
+ import { generateHash } from '@scalar/helpers/string/generate-hash';
2
+ /**
3
+ * Generates a short hash from a string value using xxhash.
4
+ *
5
+ * This function is used to create unique identifiers for external references
6
+ * while keeping the hash length manageable. It uses xxhash-wasm instead of
7
+ * crypto.subtle because crypto.subtle is only available in secure contexts (HTTPS) or on localhost.
8
+ * Returns the first 7 characters of the hash string.
9
+ * If the hash would be all numbers, it ensures at least one letter is included.
10
+ *
11
+ * @param value - The string to hash
12
+ * @returns A Promise that resolves to a 7-character hexadecimal hash with at least one letter
13
+ * @example
14
+ * // Returns "2ae91d7"
15
+ * getHash("https://example.com/schema.json")
16
+ */
17
+ export function getHash(value) {
18
+ // Hash the data using xxhash
19
+ const hashHex = generateHash(value);
20
+ // Return first 7 characters of the hash, ensuring at least one letter
21
+ const hash = hashHex.substring(0, 7);
22
+ return hash.match(/^\d+$/) ? 'a' + hash.substring(1) : hash;
6
23
  }
7
- async function generateUniqueValue(compress, value, compressedToValue, prevCompressedValue, depth = 0) {
8
- const MAX_DEPTH = 100;
9
- if (depth >= MAX_DEPTH) {
10
- throw "Can not generate unique compressed values";
11
- }
12
- const compressedValue = await compress(prevCompressedValue ?? value);
13
- if (compressedToValue[compressedValue] !== void 0 && compressedToValue[compressedValue] !== value) {
14
- return generateUniqueValue(compress, value, compressedToValue, compressedValue, depth + 1);
15
- }
16
- compressedToValue[compressedValue] = value;
17
- return compressedValue;
18
- }
19
- const uniqueValueGeneratorFactory = (compress, compressedToValue) => {
20
- const valueToCompressed = Object.fromEntries(Object.entries(compressedToValue).map(([key, value]) => [value, key]));
21
- return {
22
- /**
23
- * Generates a unique compressed value for the given input string.
24
- * First checks if a compressed value already exists in the cache.
25
- * If not, generates a new unique compressed value and stores it in the cache.
26
- *
27
- * @param value - The original string value to compress
28
- * @returns A Promise that resolves to the compressed string value
29
- *
30
- * @example
31
- * const generator = uniqueValueGeneratorFactory(compress, {})
32
- * const compressed = await generator.generate('example.com/schema.json')
33
- * // Returns a unique compressed value like 'example'
34
- */
35
- generate: async (value) => {
36
- const cache = valueToCompressed[value];
37
- if (cache) {
38
- return cache;
39
- }
40
- const generatedValue = await generateUniqueValue(compress, value, compressedToValue);
41
- const compressedValue = generatedValue.match(/^\d+$/) ? `a${generatedValue}` : generatedValue;
42
- valueToCompressed[value] = compressedValue;
43
- return compressedValue;
24
+ /**
25
+ * Generates a unique compressed value for a string, handling collisions by recursively compressing
26
+ * until a unique value is found. This is used to create unique identifiers for external
27
+ * references in the bundled OpenAPI document.
28
+ *
29
+ * @param compress - Function that generates a compressed value from a string
30
+ * @param value - The original string value to compress
31
+ * @param compressedToValue - Object mapping compressed values to their original values
32
+ * @param prevCompressedValue - Optional previous compressed value to use as input for generating a new value
33
+ * @param depth - Current recursion depth to prevent infinite loops
34
+ * @returns A unique compressed value that doesn't conflict with existing values
35
+ *
36
+ * @example
37
+ * const valueMap = {}
38
+ * // First call generates compressed value for "example.com/schema.json"
39
+ * const value1 = await generateUniqueValue(compress, "example.com/schema.json", valueMap)
40
+ * // Returns something like "2ae91d7"
41
+ *
42
+ * // Second call with same value returns same compressed value
43
+ * const value2 = await generateUniqueValue(compress, "example.com/schema.json", valueMap)
44
+ * // Returns same value as value1
45
+ *
46
+ * // Call with different value generates new unique compressed value
47
+ * const value3 = await generateUniqueValue(compress, "example.com/other.json", valueMap)
48
+ * // Returns different value like "3bf82e9"
49
+ */
50
+ export async function generateUniqueValue(compress, value, compressedToValue, prevCompressedValue, depth = 0) {
51
+ // Prevent infinite recursion by limiting depth
52
+ const MAX_DEPTH = 100;
53
+ if (depth >= MAX_DEPTH) {
54
+ throw 'Can not generate unique compressed values';
44
55
  }
45
- };
46
- };
47
- export {
48
- generateUniqueValue,
49
- getHash,
50
- uniqueValueGeneratorFactory
56
+ // Compress the value, using previous compressed value if provided
57
+ const compressedValue = await compress(prevCompressedValue ?? value);
58
+ // Handle collision by recursively trying with compressed value as input
59
+ if (compressedToValue[compressedValue] !== undefined && compressedToValue[compressedValue] !== value) {
60
+ return generateUniqueValue(compress, value, compressedToValue, compressedValue, depth + 1);
61
+ }
62
+ // Store mapping and return unique compressed value
63
+ compressedToValue[compressedValue] = value;
64
+ return compressedValue;
65
+ }
66
+ /**
67
+ * Factory function that creates a value generator with caching capabilities.
68
+ * The generator maintains a bidirectional mapping between original values and their compressed forms.
69
+ *
70
+ * @param compress - Function that generates a compressed value from a string
71
+ * @param compressedToValue - Initial mapping of compressed values to their original values
72
+ * @returns An object with a generate method that produces unique compressed values
73
+ *
74
+ * @example
75
+ * const compress = (value) => value.substring(0, 6) // Simple compression example
76
+ * const initialMap = { 'abc123': 'example.com/schema.json' }
77
+ * const generator = uniqueValueGeneratorFactory(compress, initialMap)
78
+ *
79
+ * // Generate compressed value for new string
80
+ * const compressed = await generator.generate('example.com/other.json')
81
+ * // Returns something like 'example'
82
+ *
83
+ * // Generate compressed value for existing string
84
+ * const cached = await generator.generate('example.com/schema.json')
85
+ * // Returns 'abc123' from cache
86
+ */
87
+ export const uniqueValueGeneratorFactory = (compress, compressedToValue) => {
88
+ // Create a reverse mapping from original values to their compressed forms
89
+ const valueToCompressed = Object.fromEntries(Object.entries(compressedToValue).map(([key, value]) => [value, key]));
90
+ return {
91
+ /**
92
+ * Generates a unique compressed value for the given input string.
93
+ * First checks if a compressed value already exists in the cache.
94
+ * If not, generates a new unique compressed value and stores it in the cache.
95
+ *
96
+ * @param value - The original string value to compress
97
+ * @returns A Promise that resolves to the compressed string value
98
+ *
99
+ * @example
100
+ * const generator = uniqueValueGeneratorFactory(compress, {})
101
+ * const compressed = await generator.generate('example.com/schema.json')
102
+ * // Returns a unique compressed value like 'example'
103
+ */
104
+ generate: async (value) => {
105
+ // Check if we already have a compressed value for this input
106
+ const cache = valueToCompressed[value];
107
+ if (cache) {
108
+ return cache;
109
+ }
110
+ // Generate a new unique compressed value
111
+ const generatedValue = await generateUniqueValue(compress, value, compressedToValue);
112
+ // Ensure the generated string contains at least one non-numeric character
113
+ // This prevents the `setValueAtPath` function from interpreting the value as an array index
114
+ // by forcing it to be treated as an object property name
115
+ const compressedValue = generatedValue.match(/^\d+$/) ? `a${generatedValue}` : generatedValue;
116
+ // Store the new mapping in our cache
117
+ valueToCompressed[value] = compressedValue;
118
+ return compressedValue;
119
+ },
120
+ };
51
121
  };
52
- //# sourceMappingURL=value-generator.js.map
@@ -1,38 +1,68 @@
1
- import { bundle } from "../bundle/index.js";
2
- import { fetchUrls } from "../bundle/plugins/fetch-urls/index.js";
3
- import { createMagicProxy } from "../magic-proxy/index.js";
4
- const dereference = (input, options) => {
5
- if (options?.sync) {
6
- return {
7
- success: true,
8
- data: createMagicProxy(input)
9
- };
10
- }
11
- const errors = [];
12
- const plugins = options?.plugins || [fetchUrls()];
13
- return bundle(input, {
14
- plugins,
15
- treeShake: false,
16
- urlMap: true,
17
- hooks: {
18
- onResolveError(node) {
19
- errors.push(`Failed to resolve ${node.$ref}`);
20
- }
1
+ import { bundle } from '../bundle/index.js';
2
+ import { fetchUrls } from '../bundle/plugins/fetch-urls/index.js';
3
+ import { createMagicProxy } from '../magic-proxy/index.js';
4
+ /**
5
+ * Dereferences a JSON object, resolving all $ref pointers.
6
+ *
7
+ * This function can operate synchronously (no remote refs, no async plugins) or asynchronously (with remote refs).
8
+ * If `options.sync` is true, it simply wraps the input in a magic proxy and returns it.
9
+ * Otherwise, it bundles the document, resolving all $refs (including remote ones), and returns a promise.
10
+ *
11
+ * @param input - JSON Schema object to dereference.
12
+ * @param options - Optional settings. If `sync` is true, dereferencing is synchronous. If `plugins` is provided, those plugins are used for resolution instead of the default `fetchUrls()`.
13
+ * @returns A DereferenceResult (or Promise thereof) indicating success and the dereferenced data, or errors.
14
+ *
15
+ * @example
16
+ * // Synchronous dereference (no remote refs)
17
+ * const result = dereference({ openapi: '3.0.0', info: { title: 'My API', version: '1.0.0' } }, { sync: true });
18
+ * if (result.success) {
19
+ * console.log(result.data); // Magic proxy-wrapped document
20
+ * }
21
+ *
22
+ * @example
23
+ * // Asynchronous dereference (with remote refs)
24
+ * dereference({ $ref: 'https://example.com/api.yaml' })
25
+ * .then(result => {
26
+ * if (result.success) {
27
+ * console.log(result.data); // Fully dereferenced document
28
+ * } else {
29
+ * console.error(result.errors);
30
+ * }
31
+ * });
32
+ *
33
+ * @example
34
+ * // Asynchronous dereference (with custom loader plugin)
35
+ * const plugin = { type: 'loader', validate: (v) => v.startsWith('workspace:'), exec: (v) => resolve(v) };
36
+ * const result = await dereference({ $ref: 'workspace:my-schema' }, { plugins: [plugin] });
37
+ */
38
+ export const dereference = (input, options) => {
39
+ if (options?.sync) {
40
+ return {
41
+ success: true,
42
+ data: createMagicProxy(input),
43
+ };
21
44
  }
22
- }).then((result) => {
23
- if (errors.length > 0) {
24
- return {
25
- success: false,
26
- errors
27
- };
28
- }
29
- return {
30
- success: true,
31
- data: createMagicProxy(result)
32
- };
33
- });
34
- };
35
- export {
36
- dereference
45
+ const errors = [];
46
+ const plugins = options?.plugins || [fetchUrls()];
47
+ return bundle(input, {
48
+ plugins,
49
+ treeShake: false,
50
+ urlMap: true,
51
+ hooks: {
52
+ onResolveError(node) {
53
+ errors.push(`Failed to resolve ${node.$ref}`);
54
+ },
55
+ },
56
+ }).then((result) => {
57
+ if (errors.length > 0) {
58
+ return {
59
+ success: false,
60
+ errors,
61
+ };
62
+ }
63
+ return {
64
+ success: true,
65
+ data: createMagicProxy(result),
66
+ };
67
+ });
37
68
  };
38
- //# sourceMappingURL=dereference.js.map