@scalar/json-magic 0.4.3 → 0.5.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 (106) hide show
  1. package/.turbo/turbo-build.log +4 -4
  2. package/CHANGELOG.md +10 -0
  3. package/dist/bundle/bundle.d.ts +0 -10
  4. package/dist/bundle/bundle.d.ts.map +1 -1
  5. package/dist/bundle/bundle.js +27 -29
  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 +2 -2
  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 +1 -1
  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 +1 -1
  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 +1 -1
  18. package/dist/bundle/plugins/read-files/index.js.map +2 -2
  19. package/dist/helpers/convert-to-local-ref.d.ts +10 -0
  20. package/dist/helpers/convert-to-local-ref.d.ts.map +1 -0
  21. package/dist/helpers/convert-to-local-ref.js +26 -0
  22. package/dist/helpers/convert-to-local-ref.js.map +7 -0
  23. package/dist/helpers/escape-json-pointer.d.ts.map +1 -0
  24. package/dist/{utils → helpers}/escape-json-pointer.js.map +1 -1
  25. package/dist/helpers/get-schemas.d.ts +21 -0
  26. package/dist/helpers/get-schemas.d.ts.map +1 -0
  27. package/dist/helpers/get-schemas.js +37 -0
  28. package/dist/helpers/get-schemas.js.map +7 -0
  29. package/dist/helpers/get-segments-from-path.d.ts.map +1 -0
  30. package/dist/{utils → helpers}/get-segments-from-path.js.map +1 -1
  31. package/dist/helpers/get-value-by-path.d.ts +24 -0
  32. package/dist/helpers/get-value-by-path.d.ts.map +1 -0
  33. package/dist/helpers/get-value-by-path.js +23 -0
  34. package/dist/helpers/get-value-by-path.js.map +7 -0
  35. package/dist/helpers/is-json-object.d.ts.map +1 -0
  36. package/dist/{utils → helpers}/is-json-object.js +1 -1
  37. package/dist/helpers/is-json-object.js.map +7 -0
  38. package/dist/helpers/is-object.d.ts.map +1 -0
  39. package/dist/{utils → helpers}/is-object.js.map +1 -1
  40. package/dist/helpers/is-yaml.d.ts.map +1 -0
  41. package/dist/{utils → helpers}/is-yaml.js.map +1 -1
  42. package/dist/{utils → helpers}/json-path-utils.d.ts +0 -11
  43. package/dist/helpers/json-path-utils.d.ts.map +1 -0
  44. package/dist/{utils → helpers}/json-path-utils.js +0 -9
  45. package/dist/helpers/json-path-utils.js.map +7 -0
  46. package/dist/helpers/normalize.d.ts.map +1 -0
  47. package/dist/{utils → helpers}/normalize.js.map +1 -1
  48. package/dist/helpers/unescape-json-pointer.d.ts.map +1 -0
  49. package/dist/{utils → helpers}/unescape-json-pointer.js.map +1 -1
  50. package/dist/magic-proxy/proxy.d.ts +35 -10
  51. package/dist/magic-proxy/proxy.d.ts.map +1 -1
  52. package/dist/magic-proxy/proxy.js +40 -20
  53. package/dist/magic-proxy/proxy.js.map +2 -2
  54. package/esbuild.ts +1 -0
  55. package/package.json +6 -1
  56. package/src/bundle/bundle.test.ts +462 -25
  57. package/src/bundle/bundle.ts +37 -36
  58. package/src/bundle/plugins/fetch-urls/index.ts +2 -2
  59. package/src/bundle/plugins/parse-json/index.ts +1 -1
  60. package/src/bundle/plugins/parse-yaml/index.ts +3 -2
  61. package/src/bundle/plugins/read-files/index.ts +1 -1
  62. package/src/helpers/convert-to-local-ref.test.ts +211 -0
  63. package/src/helpers/convert-to-local-ref.ts +43 -0
  64. package/src/helpers/get-schemas.test.ts +356 -0
  65. package/src/helpers/get-schemas.ts +80 -0
  66. package/src/helpers/get-value-by-path.test.ts +338 -0
  67. package/src/helpers/get-value-by-path.ts +44 -0
  68. package/src/{utils → helpers}/is-json-object.ts +1 -1
  69. package/src/{utils → helpers}/json-path-utils.ts +0 -19
  70. package/src/{utils → helpers}/normalize.test.ts +2 -1
  71. package/src/magic-proxy/proxy.test.ts +548 -0
  72. package/src/magic-proxy/proxy.ts +80 -31
  73. package/dist/utils/escape-json-pointer.d.ts.map +0 -1
  74. package/dist/utils/get-segments-from-path.d.ts.map +0 -1
  75. package/dist/utils/is-json-object.d.ts.map +0 -1
  76. package/dist/utils/is-json-object.js.map +0 -7
  77. package/dist/utils/is-object.d.ts.map +0 -1
  78. package/dist/utils/is-yaml.d.ts.map +0 -1
  79. package/dist/utils/json-path-utils.d.ts.map +0 -1
  80. package/dist/utils/json-path-utils.js.map +0 -7
  81. package/dist/utils/normalize.d.ts.map +0 -1
  82. package/dist/utils/unescape-json-pointer.d.ts.map +0 -1
  83. /package/dist/{utils → helpers}/escape-json-pointer.d.ts +0 -0
  84. /package/dist/{utils → helpers}/escape-json-pointer.js +0 -0
  85. /package/dist/{utils → helpers}/get-segments-from-path.d.ts +0 -0
  86. /package/dist/{utils → helpers}/get-segments-from-path.js +0 -0
  87. /package/dist/{utils → helpers}/is-json-object.d.ts +0 -0
  88. /package/dist/{utils → helpers}/is-object.d.ts +0 -0
  89. /package/dist/{utils → helpers}/is-object.js +0 -0
  90. /package/dist/{utils → helpers}/is-yaml.d.ts +0 -0
  91. /package/dist/{utils → helpers}/is-yaml.js +0 -0
  92. /package/dist/{utils → helpers}/normalize.d.ts +0 -0
  93. /package/dist/{utils → helpers}/normalize.js +0 -0
  94. /package/dist/{utils → helpers}/unescape-json-pointer.d.ts +0 -0
  95. /package/dist/{utils → helpers}/unescape-json-pointer.js +0 -0
  96. /package/src/{utils → helpers}/escape-json-pointer.test.ts +0 -0
  97. /package/src/{utils → helpers}/escape-json-pointer.ts +0 -0
  98. /package/src/{utils → helpers}/get-segments-from-path.test.ts +0 -0
  99. /package/src/{utils → helpers}/get-segments-from-path.ts +0 -0
  100. /package/src/{utils → helpers}/is-object.test.ts +0 -0
  101. /package/src/{utils → helpers}/is-object.ts +0 -0
  102. /package/src/{utils → helpers}/is-yaml.ts +0 -0
  103. /package/src/{utils → helpers}/json-path-utils.test.ts +0 -0
  104. /package/src/{utils → helpers}/normalize.ts +0 -0
  105. /package/src/{utils → helpers}/unescape-json-pointer.test.ts +0 -0
  106. /package/src/{utils → helpers}/unescape-json-pointer.ts +0 -0
@@ -0,0 +1,338 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import { getValueByPath } from './get-value-by-path'
4
+
5
+ describe('getValueByPath', () => {
6
+ it('should return the value at a simple path', () => {
7
+ const target = {
8
+ foo: 'bar',
9
+ }
10
+ const result = getValueByPath(target, ['foo'])
11
+
12
+ expect(result).toEqual({
13
+ context: '',
14
+ value: 'bar',
15
+ })
16
+ })
17
+
18
+ it('should return the value at a nested path', () => {
19
+ const target = {
20
+ foo: {
21
+ bar: {
22
+ baz: 42,
23
+ },
24
+ },
25
+ }
26
+ const result = getValueByPath(target, ['foo', 'bar', 'baz'])
27
+
28
+ expect(result).toEqual({
29
+ context: '',
30
+ value: 42,
31
+ })
32
+ })
33
+
34
+ it('should return context with $id when found', () => {
35
+ const target = {
36
+ foo: {
37
+ $id: 'https://example.com/schema',
38
+ bar: {
39
+ baz: 'value',
40
+ },
41
+ },
42
+ }
43
+ const result = getValueByPath(target, ['foo', 'bar', 'baz'])
44
+
45
+ expect(result).toEqual({
46
+ context: 'https://example.com/schema',
47
+ value: 'value',
48
+ })
49
+ })
50
+
51
+ it('should preserve context from parent when child has no $id', () => {
52
+ const target = {
53
+ foo: {
54
+ $id: 'https://example.com/parent',
55
+ bar: {
56
+ baz: 'value',
57
+ },
58
+ },
59
+ }
60
+ const result = getValueByPath(target, ['foo', 'bar', 'baz'])
61
+
62
+ expect(result).toEqual({
63
+ context: 'https://example.com/parent',
64
+ value: 'value',
65
+ })
66
+ })
67
+
68
+ it('should update context when child has $id', () => {
69
+ const target = {
70
+ foo: {
71
+ $id: 'https://example.com/parent',
72
+ bar: {
73
+ $id: 'https://example.com/child',
74
+ baz: 'value',
75
+ },
76
+ },
77
+ }
78
+ const result = getValueByPath(target, ['foo', 'bar', 'baz'])
79
+
80
+ expect(result).toEqual({
81
+ context: 'https://example.com/child',
82
+ value: 'value',
83
+ })
84
+ })
85
+
86
+ it('should return undefined value when path does not exist', () => {
87
+ const target = {
88
+ foo: 'bar',
89
+ }
90
+ const result = getValueByPath(target, ['nonexistent'])
91
+
92
+ expect(result).toEqual({
93
+ context: '',
94
+ value: undefined,
95
+ })
96
+ })
97
+
98
+ it('should return undefined value when path partially exists', () => {
99
+ const target = {
100
+ foo: {
101
+ bar: 'baz',
102
+ },
103
+ }
104
+ const result = getValueByPath(target, ['foo', 'nonexistent'])
105
+
106
+ expect(result).toEqual({
107
+ context: '',
108
+ value: undefined,
109
+ })
110
+ })
111
+
112
+ it('should return undefined value when traversing through null', () => {
113
+ const target = {
114
+ foo: null,
115
+ }
116
+ const result = getValueByPath(target, ['foo', 'bar'])
117
+
118
+ expect(result).toEqual({
119
+ context: '',
120
+ value: undefined,
121
+ })
122
+ })
123
+
124
+ it('should return undefined value when traversing through primitive', () => {
125
+ const target = {
126
+ foo: 'string',
127
+ }
128
+ const result = getValueByPath(target, ['foo', 'bar'])
129
+
130
+ expect(result).toEqual({
131
+ context: '',
132
+ value: undefined,
133
+ })
134
+ })
135
+
136
+ it('should handle empty segments array', () => {
137
+ const target = {
138
+ foo: 'bar',
139
+ }
140
+ const result = getValueByPath(target, [])
141
+
142
+ expect(result).toEqual({
143
+ context: '',
144
+ value: target,
145
+ })
146
+ })
147
+
148
+ it('should handle array indices in path', () => {
149
+ const target = {
150
+ items: [{ name: 'first' }, { name: 'second' }],
151
+ }
152
+ const result = getValueByPath(target, ['items', '0', 'name'])
153
+
154
+ expect(result).toEqual({
155
+ context: '',
156
+ value: 'first',
157
+ })
158
+ })
159
+
160
+ it('should handle objects with numeric keys', () => {
161
+ const target = {
162
+ '0': 'zero',
163
+ '1': 'one',
164
+ }
165
+ const result = getValueByPath(target, ['0'])
166
+
167
+ expect(result).toEqual({
168
+ context: '',
169
+ value: 'zero',
170
+ })
171
+ })
172
+
173
+ it('should handle deeply nested objects', () => {
174
+ const target = {
175
+ level1: {
176
+ level2: {
177
+ level3: {
178
+ level4: {
179
+ level5: {
180
+ value: 'deep',
181
+ },
182
+ },
183
+ },
184
+ },
185
+ },
186
+ }
187
+ const result = getValueByPath(target, ['level1', 'level2', 'level3', 'level4', 'level5', 'value'])
188
+
189
+ expect(result).toEqual({
190
+ context: '',
191
+ value: 'deep',
192
+ })
193
+ })
194
+
195
+ it('should handle mixed data types in path', () => {
196
+ const target = {
197
+ string: 'text',
198
+ number: 42,
199
+ boolean: true,
200
+ null: null,
201
+ array: [1, 2, 3],
202
+ object: { nested: 'value' },
203
+ }
204
+
205
+ expect(getValueByPath(target, ['string'])).toEqual({ context: '', value: 'text' })
206
+ expect(getValueByPath(target, ['number'])).toEqual({ context: '', value: 42 })
207
+ expect(getValueByPath(target, ['boolean'])).toEqual({ context: '', value: true })
208
+ expect(getValueByPath(target, ['null'])).toEqual({ context: '', value: null })
209
+ expect(getValueByPath(target, ['array'])).toEqual({ context: '', value: [1, 2, 3] })
210
+ expect(getValueByPath(target, ['object', 'nested'])).toEqual({ context: '', value: 'value' })
211
+ })
212
+
213
+ it('should handle $id with different formats', () => {
214
+ const target = {
215
+ schema1: {
216
+ $id: 'https://example.com/schema1',
217
+ value: 'test1',
218
+ },
219
+ schema2: {
220
+ $id: 'urn:uuid:123e4567-e89b-12d3-a456-426614174000',
221
+ value: 'test2',
222
+ },
223
+ schema3: {
224
+ $id: 'relative/path',
225
+ value: 'test3',
226
+ },
227
+ }
228
+
229
+ expect(getValueByPath(target, ['schema1', 'value'])).toEqual({
230
+ context: 'https://example.com/schema1',
231
+ value: 'test1',
232
+ })
233
+ expect(getValueByPath(target, ['schema2', 'value'])).toEqual({
234
+ context: 'urn:uuid:123e4567-e89b-12d3-a456-426614174000',
235
+ value: 'test2',
236
+ })
237
+ expect(getValueByPath(target, ['schema3', 'value'])).toEqual({
238
+ context: 'relative/path',
239
+ value: 'test3',
240
+ })
241
+ })
242
+
243
+ it('should ignore non-string $id values', () => {
244
+ const target = {
245
+ schema1: {
246
+ $id: 123, // number
247
+ value: 'test1',
248
+ },
249
+ schema2: {
250
+ $id: { nested: 'object' }, // object
251
+ value: 'test2',
252
+ },
253
+ schema3: {
254
+ $id: null, // null
255
+ value: 'test3',
256
+ },
257
+ }
258
+
259
+ expect(getValueByPath(target, ['schema1', 'value'])).toEqual({
260
+ context: '',
261
+ value: 'test1',
262
+ })
263
+ expect(getValueByPath(target, ['schema2', 'value'])).toEqual({
264
+ context: '',
265
+ value: 'test2',
266
+ })
267
+ expect(getValueByPath(target, ['schema3', 'value'])).toEqual({
268
+ context: '',
269
+ value: 'test3',
270
+ })
271
+ })
272
+
273
+ it('should handle context inheritance through multiple levels', () => {
274
+ const target = {
275
+ root: {
276
+ $id: 'https://example.com/root',
277
+ level1: {
278
+ level2: {
279
+ $id: 'https://example.com/level2',
280
+ level3: {
281
+ level4: {
282
+ value: 'nested',
283
+ },
284
+ },
285
+ },
286
+ },
287
+ },
288
+ }
289
+
290
+ // Should use root context when no intermediate $id
291
+ expect(getValueByPath(target, ['root', 'level1', 'level2', 'level3', 'level4', 'value'])).toEqual({
292
+ context: 'https://example.com/level2',
293
+ value: 'nested',
294
+ })
295
+ })
296
+
297
+ it('should handle edge case with empty string keys', () => {
298
+ const target = {
299
+ '': 'empty key',
300
+ normal: 'normal key',
301
+ }
302
+
303
+ expect(getValueByPath(target, [''])).toEqual({
304
+ context: '',
305
+ value: 'empty key',
306
+ })
307
+ expect(getValueByPath(target, ['normal'])).toEqual({
308
+ context: '',
309
+ value: 'normal key',
310
+ })
311
+ })
312
+
313
+ it('should handle special characters in keys', () => {
314
+ const target = {
315
+ 'key-with-dash': 'dash',
316
+ 'key_with_underscore': 'underscore',
317
+ 'key.with.dots': 'dots',
318
+ 'key with spaces': 'spaces',
319
+ }
320
+
321
+ expect(getValueByPath(target, ['key-with-dash'])).toEqual({
322
+ context: '',
323
+ value: 'dash',
324
+ })
325
+ expect(getValueByPath(target, ['key_with_underscore'])).toEqual({
326
+ context: '',
327
+ value: 'underscore',
328
+ })
329
+ expect(getValueByPath(target, ['key.with.dots'])).toEqual({
330
+ context: '',
331
+ value: 'dots',
332
+ })
333
+ expect(getValueByPath(target, ['key with spaces'])).toEqual({
334
+ context: '',
335
+ value: 'spaces',
336
+ })
337
+ })
338
+ })
@@ -0,0 +1,44 @@
1
+ import { getId } from '@/helpers/get-schemas'
2
+
3
+ /**
4
+ * Traverses an object using an array of string segments (path keys) and returns
5
+ * the value at the specified path along with its context (id if available).
6
+ *
7
+ * @param target - The root object to traverse.
8
+ * @param segments - An array of string keys representing the path to traverse.
9
+ * @returns An object containing the final context (id or previous context) and the value at the path.
10
+ *
11
+ * @example
12
+ * const obj = {
13
+ * foo: {
14
+ * bar: {
15
+ * baz: 42
16
+ * }
17
+ * }
18
+ * };
19
+ * // Returns: { context: '', value: 42 }
20
+ * getValueByPath(obj, ['foo', 'bar', 'baz']);
21
+ */
22
+ export function getValueByPath(target: unknown, segments: string[]): { context: string; value: any } {
23
+ return segments.reduce<{ context: string; value: unknown }>(
24
+ (acc, key) => {
25
+ // If the accumulator is undefined, the path does not exist
26
+ if (acc.value === undefined) {
27
+ return { context: '', value: undefined }
28
+ }
29
+ // If the accumulator is not an object or is null, stop traversal
30
+ if (typeof acc.value !== 'object' || acc.value === null) {
31
+ return { context: '', value: undefined }
32
+ }
33
+ // Attempt to get the id from the current value for context tracking
34
+ const id = getId(acc.value)
35
+
36
+ // Return the next context and value for the next iteration
37
+ return { context: id ?? acc.context, value: acc.value?.[key] }
38
+ },
39
+ {
40
+ context: '',
41
+ value: target,
42
+ },
43
+ )
44
+ }
@@ -1,4 +1,4 @@
1
- import { isObject } from '@/utils/is-object'
1
+ import { isObject } from '@/helpers/is-object'
2
2
 
3
3
  /**
4
4
  * Determines if a string represents a valid JSON object (i.e., a plain object, not an array, primitive, or null).
@@ -18,25 +18,6 @@ export function parseJsonPointer(pointer: string): string[] {
18
18
  )
19
19
  }
20
20
 
21
- /**
22
- * Retrieves a nested value from the source document using a path array
23
- *
24
- * @example
25
- * ```ts
26
- * getValueByPath(document, ['components', 'schemas', 'User'])
27
- *
28
- * { id: '123', name: 'John Doe' }
29
- * ```
30
- */
31
- export function getValueByPath<R = unknown>(obj: any, pointer: string[]): R {
32
- return pointer.reduce((acc, part) => {
33
- if (acc === undefined || acc === null) {
34
- return undefined
35
- }
36
- return acc[part]
37
- }, obj)
38
- }
39
-
40
21
  /**
41
22
  * Creates a nested path in an object from an array of path segments.
42
23
  * Only creates intermediate objects/arrays if they don't already exist.
@@ -1,6 +1,7 @@
1
- import { normalize } from '@/utils/normalize'
2
1
  import { describe, expect, it } from 'vitest'
3
2
 
3
+ import { normalize } from '@/helpers/normalize'
4
+
4
5
  describe('normalize', () => {
5
6
  it('returns undefined if the document is null', () => {
6
7
  expect(normalize(null)).toEqual(undefined)