@scalar/json-magic 0.8.2 → 0.8.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 (84) hide show
  1. package/CHANGELOG.md +20 -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
package/src/diff/merge.ts DELETED
@@ -1,136 +0,0 @@
1
- import type { Difference } from '@/diff/diff'
2
- import { Trie } from '@/diff/trie'
3
- import { isArrayEqual, isKeyCollisions, mergeObjects } from '@/diff/utils'
4
-
5
- /**
6
- * Merges two sets of differences from the same document and resolves conflicts.
7
- * This function combines changes from two diff lists while handling potential conflicts
8
- * that arise when both diffs modify the same paths. It uses a trie data structure for
9
- * efficient path matching and conflict detection.
10
- *
11
- * @param diff1 - First list of differences
12
- * @param diff2 - Second list of differences
13
- * @returns Object containing:
14
- * - diffs: Combined list of non-conflicting differences
15
- * - conflicts: Array of conflicting difference pairs that need manual resolution
16
- *
17
- * @example
18
- * // Merge two sets of changes to a user profile
19
- * const diff1 = [
20
- * { path: ['name'], changes: 'John', type: 'update' },
21
- * { path: ['age'], changes: 30, type: 'add' }
22
- * ]
23
- * const diff2 = [
24
- * { path: ['name'], changes: 'Johnny', type: 'update' },
25
- * { path: ['address'], changes: { city: 'NY' }, type: 'add' }
26
- * ]
27
- * const { diffs, conflicts } = merge(diff1, diff2)
28
- * // Returns:
29
- * // {
30
- * // diffs: [
31
- * // { path: ['age'], changes: 30, type: 'add' },
32
- * // { path: ['address'], changes: { city: 'NY' }, type: 'add' }
33
- * // ],
34
- * // conflicts: [
35
- * // [
36
- * // [{ path: ['name'], changes: 'John', type: 'update' }],
37
- * // [{ path: ['name'], changes: 'Johnny', type: 'update' }]
38
- * // ]
39
- * // ]
40
- * // }
41
- */
42
- export const merge = <T>(diff1: Difference<T>[], diff2: Difference<T>[]) => {
43
- // Here we need to use a trie to optimize searching for a prefix
44
- // With the naive approach time complexity of the algorithm would be
45
- // O(n * m)
46
- // ^ ^
47
- // n is the length off diff1 | | m length of diff2
48
- //
49
- // Assuming that the maximum depth of the nested objects would be constant lets say 0 <= D <= 100
50
- // we try to optimize for that using the tire data structure.
51
- // So the new time complexity would be O(n * D) where D is the maximum depth of the nested object
52
- const trie = new Trie<{ index: number; changes: Difference<T> }>()
53
-
54
- // Create the trie
55
- for (const [index, diff] of diff1.entries()) {
56
- trie.addPath(diff.path, { index, changes: diff })
57
- }
58
-
59
- const skipDiff1 = new Set<number>()
60
- const skipDiff2 = new Set<number>()
61
-
62
- // Keep related conflicts together for easy A, B pick conflict resolution
63
- // map key is going to be conflicting index of first diff list where the diff will be
64
- // a delete operation or an add/update operation with a one to many conflicts
65
- const conflictsMap1 = new Map<number, [Difference<T>[], Difference<T>[]]>()
66
- // map key will be the index from the second diff list where the diff will be
67
- // a delete operation with one to many conflicts
68
- const conflictsMap2 = new Map<number, [Difference<T>[], Difference<T>[]]>()
69
-
70
- for (const [index, diff] of diff2.entries()) {
71
- trie.findMatch(diff.path, (value) => {
72
- if (diff.type === 'delete') {
73
- if (value.changes.type === 'delete') {
74
- // Keep the highest depth delete operation and skip the other
75
- if (value.changes.path.length > diff.path.length) {
76
- skipDiff1.add(value.index)
77
- } else {
78
- skipDiff2.add(value.index)
79
- }
80
- } else {
81
- // Take care of updates/add on the same path (we are sure they will be on the
82
- // same path since the change comes from the same document)
83
- skipDiff1.add(value.index)
84
- skipDiff2.add(index)
85
-
86
- const conflictEntry = conflictsMap2.get(index)
87
-
88
- if (conflictEntry !== undefined) {
89
- conflictEntry[0].push(value.changes)
90
- } else {
91
- conflictsMap2.set(index, [[value.changes], [diff]])
92
- }
93
- }
94
- }
95
-
96
- if (diff.type === 'add' || diff.type === 'update') {
97
- // For add -> add / update -> update operation we try to first see if we can merge this operations
98
- if (
99
- isArrayEqual(diff.path, value.changes.path) &&
100
- value.changes.type !== 'delete' &&
101
- !isKeyCollisions(diff.changes, value.changes.changes)
102
- ) {
103
- skipDiff1.add(value.index)
104
- // For non primitive values we merge object keys into diff2
105
- if (typeof diff.changes === 'object') {
106
- mergeObjects(diff.changes, value.changes.changes)
107
- }
108
- return
109
- }
110
-
111
- // add/update -> delete operations always resolve in conflicts
112
- skipDiff1.add(value.index)
113
- skipDiff2.add(index)
114
-
115
- const conflictEntry = conflictsMap1.get(value.index)
116
-
117
- if (conflictEntry !== undefined) {
118
- conflictEntry[1].push(diff)
119
- } else {
120
- conflictsMap1.set(value.index, [[value.changes], [diff]])
121
- }
122
- }
123
- })
124
- }
125
-
126
- const conflicts: [Difference<T>[], Difference<T>[]][] = [...conflictsMap1.values(), ...conflictsMap2.values()]
127
-
128
- // Filter all changes that should be skipped because of conflicts
129
- // or auto conflict resolution
130
- const diffs: Difference<T>[] = [
131
- ...diff1.filter((_, index) => !skipDiff1.has(index)),
132
- ...diff2.filter((_, index) => !skipDiff2.has(index)),
133
- ]
134
-
135
- return { diffs, conflicts }
136
- }
@@ -1,30 +0,0 @@
1
- import { Trie } from '@/diff/trie'
2
- import { describe, expect, test, vi } from 'vitest'
3
-
4
- describe('trie', () => {
5
- test('should correctly find matched', () => {
6
- const trie = new Trie<number>()
7
-
8
- trie.addPath(['a', 'b', 'c'], 1)
9
- trie.addPath(['a', 'b', 'd'], 2)
10
- trie.addPath(['a', 'b', 'c', 'd'], 3)
11
- trie.addPath(['a', 'b', 'c', 'd', 'e', 'f'], 4)
12
- trie.addPath(['a', 'b'], 5)
13
-
14
- /**
15
- * created trie:
16
- *
17
- * (a, null) -> (b, 5) -> (c, 1) -> (d, 3) -> (e, null) -> (f, 4)
18
- * \-> (d, 2)
19
- */
20
-
21
- const fn = vi.fn()
22
- trie.findMatch(['a', 'b', 'c'], fn)
23
-
24
- expect(fn).toHaveBeenCalledTimes(4)
25
- expect(fn).toHaveBeenNthCalledWith(1, 5)
26
- expect(fn).toHaveBeenNthCalledWith(2, 4)
27
- expect(fn).toHaveBeenNthCalledWith(3, 3)
28
- expect(fn).toHaveBeenNthCalledWith(4, 1)
29
- })
30
- })
package/src/diff/trie.ts DELETED
@@ -1,113 +0,0 @@
1
- /**
2
- * Trie data structure
3
- *
4
- * Read more: https://en.wikipedia.org/wiki/Trie
5
- */
6
-
7
- /**
8
- * Represents a node in the trie data structure.
9
- * Each node can store a value and has a map of child nodes.
10
- *
11
- * @template Value - The type of value that can be stored in the node
12
- */
13
- export class TrieNode<Value> {
14
- constructor(
15
- public value: Value | null,
16
- public children: Record<string, TrieNode<Value>>,
17
- ) {}
18
- }
19
-
20
- /**
21
- * A trie (prefix tree) data structure implementation.
22
- * This class provides efficient storage and retrieval of values associated with string paths.
23
- *
24
- * @template Value - The type of value to store at each node
25
- *
26
- * @example
27
- * const trie = new Trie<number>()
28
- * trie.addPath(['a', 'b', 'c'], 1)
29
- * trie.addPath(['a', 'b', 'd'], 2)
30
- * trie.findMatch(['a', 'b'], (value) => console.log(value)) // Logs: 1, 2
31
- */
32
- export class Trie<Value> {
33
- private root: TrieNode<Value>
34
- constructor() {
35
- this.root = new TrieNode<Value>(null, {})
36
- }
37
-
38
- /**
39
- * Adds a value to the trie at the specified path.
40
- * Creates new nodes as needed to build the path.
41
- *
42
- * @param path - Array of strings representing the path to store the value
43
- * @param value - The value to store at the end of the path
44
- *
45
- * @example
46
- * const trie = new Trie<number>()
47
- * trie.addPath(['users', 'john', 'age'], 30)
48
- */
49
- addPath(path: string[], value: Value) {
50
- let current = this.root
51
- for (const dir of path) {
52
- if (current.children[dir]) {
53
- current = current.children[dir]
54
- } else {
55
- current.children[dir] = new TrieNode<Value>(null, {})
56
- current = current.children[dir]
57
- }
58
- }
59
-
60
- current.value = value
61
- }
62
-
63
- /**
64
- * Finds all matches along a given path in the trie.
65
- * This method traverses both the exact path and all deeper paths,
66
- * executing a callback for each matching value found.
67
- *
68
- * The search is performed in two phases:
69
- * 1. Traverse the exact path, checking for matches at each node
70
- * 2. Perform a depth-first search from the end of the path to find all deeper matches
71
- *
72
- * @param path - Array of strings representing the path to search
73
- * @param callback - Function to execute for each matching value found
74
- *
75
- * @example
76
- * const trie = new Trie<number>()
77
- * trie.addPath(['a', 'b', 'c'], 1)
78
- * trie.addPath(['a', 'b', 'd'], 2)
79
- * trie.findMatch(['a', 'b'], (value) => console.log(value)) // Logs: 1, 2
80
- */
81
- findMatch(path: string[], callback: (value: Value) => void) {
82
- let current = this.root
83
-
84
- for (const dir of path) {
85
- // Note: the last callback wont fire here because it will fire on the dfs
86
- if (current.value !== null) {
87
- callback(current.value)
88
- }
89
-
90
- const next = current.children[dir]
91
- if (!next) {
92
- return
93
- }
94
-
95
- current = next
96
- }
97
-
98
- const dfs = (current: TrieNode<Value> | undefined) => {
99
- for (const child of Object.keys(current?.children ?? {})) {
100
- if (current && Object.hasOwn(current.children, child)) {
101
- dfs(current?.children[child])
102
- }
103
- }
104
-
105
- if (current?.value) {
106
- callback(current.value)
107
- }
108
- }
109
-
110
- // Dfs for the rest of the path
111
- dfs(current)
112
- }
113
- }
@@ -1,169 +0,0 @@
1
- import { isArrayEqual, isKeyCollisions, mergeObjects } from '@/diff/utils'
2
- import { describe, expect, test } from 'vitest'
3
-
4
- describe('isKeyCollisions', () => {
5
- test.each([
6
- [
7
- {
8
- a: 1,
9
- },
10
- {
11
- a: {
12
- hello: 1,
13
- },
14
- },
15
- ],
16
- ['hello', 'hi'],
17
- [{ a: { b: { c: 1 } } }, { a: { b: { c: 2 } }, c: 1 }],
18
- ])('should return true', (a, b) => {
19
- expect(isKeyCollisions(a, b)).toBe(true)
20
- })
21
-
22
- test.each([
23
- [
24
- {
25
- a: {
26
- b: 1,
27
- },
28
- },
29
- {
30
- a: {
31
- c: 1,
32
- },
33
- },
34
- ],
35
- [{ a: { b: { c: 1 } } }, { a: { b: { d: 1 } }, c: 1 }],
36
- ])('should return false', (a, b) => {
37
- expect(isKeyCollisions(a, b)).toBe(false)
38
- })
39
- })
40
-
41
- describe('mergeObjects', () => {
42
- test('should merge objects that does not have any conflicting keys', () => {
43
- const a = {
44
- a: 'Hello',
45
- }
46
-
47
- const b = {
48
- b: 'Hello',
49
- }
50
-
51
- expect(mergeObjects(a, b)).toEqual({
52
- a: a.a,
53
- b: b.b,
54
- })
55
- })
56
-
57
- test('should merge objects correctly even when they have the same key with the same value', () => {
58
- const a = {
59
- a: 'Hello',
60
- }
61
-
62
- const b = {
63
- a: 'Hello',
64
- }
65
-
66
- expect(mergeObjects(a, b)).toEqual({
67
- a: a.a,
68
- })
69
- })
70
-
71
- test('should deeply merge the objects', () => {
72
- const a = {
73
- a: {
74
- b: {
75
- c: {
76
- d: 1,
77
- },
78
- },
79
- },
80
- }
81
-
82
- const b = {
83
- a: {
84
- b: {
85
- d: {
86
- e: 1,
87
- },
88
- },
89
- },
90
- }
91
-
92
- expect(mergeObjects(a, b)).toEqual({
93
- a: {
94
- b: {
95
- c: {
96
- d: 1,
97
- },
98
- d: {
99
- e: 1,
100
- },
101
- },
102
- },
103
- })
104
- })
105
-
106
- test('should deeply merge the objects when there is same keys', () => {
107
- const a = {
108
- a: {
109
- b: {
110
- c: {
111
- d: 1,
112
- },
113
- },
114
- },
115
- }
116
-
117
- const b = {
118
- a: {
119
- b: {
120
- c: {
121
- d: 1,
122
- },
123
- },
124
- },
125
- b: 1,
126
- }
127
-
128
- expect(mergeObjects(a, b)).toEqual({
129
- a: {
130
- b: {
131
- c: {
132
- d: 1,
133
- },
134
- },
135
- },
136
- b: 1,
137
- })
138
- })
139
- })
140
-
141
- describe('isArrayEqual', () => {
142
- test.each([
143
- [
144
- ['a', 'b', 'c'],
145
- ['a', 'b', 'c'],
146
- ],
147
- [
148
- [1, 2, 3],
149
- [1, 2, 3],
150
- ],
151
- // @ts-expect-error
152
- ])('should return true', (a, b) => expect(isArrayEqual(a, b)).toEqual(true))
153
-
154
- test.each([
155
- [
156
- ['a', 'b', 'c'],
157
- ['a', 'b'],
158
- ],
159
- [
160
- [1, 2, 4],
161
- [1, 2, 3],
162
- ],
163
- [
164
- [2, 2, 4],
165
- [1, 2, 3],
166
- ],
167
- // @ts-expect-error
168
- ])('should return false', (a, b) => expect(isArrayEqual(a, b)).toEqual(false))
169
- })
package/src/diff/utils.ts DELETED
@@ -1,111 +0,0 @@
1
- /**
2
- * Deep check for objects for collisions
3
- * Check primitives if their values are different
4
- *
5
- * @param a - First value to compare
6
- * @param b - Second value to compare
7
- * @returns true if there is a collision, false otherwise
8
- *
9
- * @example
10
- * // Objects with different values for same key
11
- * isKeyCollisions({ a: 1 }, { a: 2 }) // true
12
- *
13
- * // Objects with different types
14
- * isKeyCollisions({ a: 1 }, { a: '1' }) // true
15
- *
16
- * // Objects with no collisions
17
- * isKeyCollisions({ a: 1 }, { b: 2 }) // false
18
- *
19
- * // Nested objects with collision
20
- * isKeyCollisions({ a: { b: 1 } }, { a: { b: 2 } }) // true
21
- */
22
- export const isKeyCollisions = (a: unknown, b: unknown) => {
23
- if (typeof a !== typeof b) {
24
- return true
25
- }
26
-
27
- if (typeof a === 'object' && typeof b === 'object' && a !== null && b !== null) {
28
- const keys = new Set([...Object.keys(a), ...Object.keys(b)])
29
-
30
- for (const key of keys) {
31
- if (a[key] !== undefined && b[key] !== undefined) {
32
- if (isKeyCollisions(a[key], b[key])) {
33
- return true
34
- }
35
- }
36
- }
37
- return false
38
- }
39
-
40
- // We handle all primitives here
41
- return a !== b
42
- }
43
-
44
- /**
45
- * Deep merges two objects, combining their properties recursively.
46
- *
47
- * ⚠️ Note: This operation assumes there are no key collisions between the objects.
48
- * Use isKeyCollisions() to check for collisions before merging.
49
- *
50
- * @param a - Target object to merge into
51
- * @param b - Source object to merge from
52
- * @returns The merged object (mutates and returns a)
53
- *
54
- * @example
55
- * // Simple merge
56
- * const a = { name: 'John' }
57
- * const b = { age: 30 }
58
- * mergeObjects(a, b) // { name: 'John', age: 30 }
59
- *
60
- * // Nested merge
61
- * const a = { user: { name: 'John' } }
62
- * const b = { user: { age: 30 } }
63
- * mergeObjects(a, b) // { user: { name: 'John', age: 30 } }
64
- */
65
- export const mergeObjects = (a: Record<string, unknown>, b: Record<string, unknown>): Record<string, unknown> => {
66
- for (const key in b) {
67
- if (!(key in a)) {
68
- a[key] = b[key]
69
- } else {
70
- const aValue = a[key]
71
- const bValue = b[key]
72
-
73
- if (typeof aValue === 'object' && aValue !== null && typeof bValue === 'object' && bValue !== null) {
74
- a[key] = mergeObjects(aValue as Record<string, unknown>, bValue as Record<string, unknown>)
75
- }
76
- }
77
- }
78
-
79
- return a
80
- }
81
-
82
- /**
83
- * Checks if two arrays have the same elements in the same order.
84
- *
85
- * @param a - First array to compare
86
- * @param b - Second array to compare
87
- * @returns True if arrays have same length and elements, false otherwise
88
- *
89
- * @example
90
- * // Arrays with same elements
91
- * isArrayEqual([1, 2, 3], [1, 2, 3]) // true
92
- *
93
- * // Arrays with different elements
94
- * isArrayEqual([1, 2, 3], [1, 2, 4]) // false
95
- *
96
- * // Arrays with different lengths
97
- * isArrayEqual([1, 2], [1, 2, 3]) // false
98
- */
99
- export const isArrayEqual = <T>(a: T[], b: T[]) => {
100
- if (a.length !== b.length) {
101
- return false
102
- }
103
-
104
- for (let i = 0; i <= a.length; ++i) {
105
- if (a[i] !== b[i]) {
106
- return false
107
- }
108
- }
109
-
110
- return true
111
- }