@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,5 +1 @@
1
- import { dereference } from "./dereference.js";
2
- export {
3
- dereference
4
- };
5
- //# sourceMappingURL=index.js.map
1
+ export { dereference } from './dereference.js';
@@ -1,40 +1,73 @@
1
- class InvalidChangesDetectedError extends Error {
2
- constructor(message) {
3
- super(message);
4
- this.name = "InvalidChangesDetectedError";
5
- }
6
- }
7
- const apply = (document, diff) => {
8
- const applyChange = (current, path, d, depth = 0) => {
9
- if (path[depth] === void 0) {
10
- throw new InvalidChangesDetectedError(
11
- `Process aborted. Path ${path.join(".")} at depth ${depth} is undefined, check diff object`
12
- );
1
+ export class InvalidChangesDetectedError extends Error {
2
+ constructor(message) {
3
+ super(message);
4
+ this.name = 'InvalidChangesDetectedError';
13
5
  }
14
- if (depth >= path.length - 1) {
15
- if (d.type === "add" || d.type === "update") {
16
- current[path[depth]] = d.changes;
17
- } else {
18
- if (Array.isArray(current)) {
19
- current.splice(Number.parseInt(path[depth]), 1);
20
- } else {
21
- delete current[path[depth]];
6
+ }
7
+ /**
8
+ * Applies a set of differences to a document object.
9
+ * The function traverses the document structure following the paths specified in the differences
10
+ * and applies the corresponding changes (add, update, or delete) at each location.
11
+ *
12
+ * @param document - The original document to apply changes to
13
+ * @param diff - Array of differences to apply, each containing a path and change type
14
+ * @returns The modified document with all changes applied
15
+ *
16
+ * @example
17
+ * const original = {
18
+ * paths: {
19
+ * '/users': {
20
+ * get: { responses: { '200': { description: 'OK' } } }
21
+ * }
22
+ * }
23
+ * }
24
+ *
25
+ * const changes = [
26
+ * {
27
+ * path: ['paths', '/users', 'get', 'responses', '200', 'content'],
28
+ * type: 'add',
29
+ * changes: { 'application/json': { schema: { type: 'object' } } }
30
+ * }
31
+ * ]
32
+ *
33
+ * const updated = apply(original, changes)
34
+ * // Result: original document with content added to the 200 response
35
+ */
36
+ export const apply = (document, diff) => {
37
+ // Traverse the object and apply the change
38
+ const applyChange = (current, path, d, depth = 0) => {
39
+ if (path[depth] === undefined) {
40
+ throw new InvalidChangesDetectedError(`Process aborted. Path ${path.join('.')} at depth ${depth} is undefined, check diff object`);
22
41
  }
23
- }
24
- return;
25
- }
26
- if (current[path[depth]] === void 0 || typeof current[path[depth]] !== "object") {
27
- throw new InvalidChangesDetectedError("Process aborted, check diff object");
42
+ // We reach where we want to be, now we can apply changes
43
+ if (depth >= path.length - 1) {
44
+ if (d.type === 'add' || d.type === 'update') {
45
+ current[path[depth]] = d.changes;
46
+ }
47
+ else {
48
+ // For arrays we don't use delete operator since it will leave blank spots and not actually remove the element
49
+ if (Array.isArray(current)) {
50
+ current.splice(Number.parseInt(path[depth]), 1);
51
+ }
52
+ else {
53
+ delete current[path[depth]];
54
+ }
55
+ }
56
+ return;
57
+ }
58
+ // Throw an error
59
+ // This scenario should not happen
60
+ // 1- if we are adding a new entry, the diff should only give us the higher level diff
61
+ // 2- if we are updating/deleting an entry, the path to that entry should exists
62
+ if (current[path[depth]] === undefined || typeof current[path[depth]] !== 'object') {
63
+ throw new InvalidChangesDetectedError('Process aborted, check diff object');
64
+ }
65
+ applyChange(current[path[depth]], path, d, depth + 1);
66
+ };
67
+ for (const d of diff) {
68
+ applyChange(document, d.path, d);
28
69
  }
29
- applyChange(current[path[depth]], path, d, depth + 1);
30
- };
31
- for (const d of diff) {
32
- applyChange(document, d.path, d);
33
- }
34
- return document;
35
- };
36
- export {
37
- InvalidChangesDetectedError,
38
- apply
70
+ // It is safe to cast here because this function mutates the input document
71
+ // to match the target type T as described by the diff changeset.
72
+ return document;
39
73
  };
40
- //# sourceMappingURL=apply.js.map
package/dist/diff/diff.js CHANGED
@@ -1,33 +1,69 @@
1
- const diff = (doc1, doc2) => {
2
- const diff2 = [];
3
- const bfs = (el1, el2, prefix = []) => {
4
- if (typeof el1 !== typeof el2) {
5
- if (typeof el1 === "undefined") {
6
- diff2.push({ path: prefix, changes: el2, type: "add" });
7
- return;
8
- }
9
- if (typeof el2 === "undefined") {
10
- diff2.push({ path: prefix, changes: el1, type: "delete" });
11
- return;
12
- }
13
- diff2.push({ path: prefix, changes: el2, type: "update" });
14
- return;
15
- }
16
- if (typeof el1 === "object" && typeof el2 === "object" && el1 !== null && el2 !== null) {
17
- const keys = /* @__PURE__ */ new Set([...Object.keys(el1), ...Object.keys(el2)]);
18
- for (const key of keys) {
19
- bfs(el1[key], el2[key], [...prefix, key]);
20
- }
21
- return;
22
- }
23
- if (el1 !== el2) {
24
- diff2.push({ path: prefix, changes: el2, type: "update" });
25
- }
26
- };
27
- bfs(doc1, doc2);
28
- return diff2;
1
+ /**
2
+ * Get the difference between two objects.
3
+ *
4
+ * This function performs a breadth-first comparison between two objects and returns
5
+ * a list of operations needed to transform the first object into the second.
6
+ *
7
+ * @param doc1 - The source object to compare from
8
+ * @param doc2 - The target object to compare to
9
+ * @returns A list of operations (add/update/delete) with their paths and changes
10
+ *
11
+ * @example
12
+ * // Compare two simple objects
13
+ * const original = { name: 'John', age: 30 }
14
+ * const updated = { name: 'John', age: 31, city: 'New York' }
15
+ * const differences = diff(original, updated)
16
+ * // Returns:
17
+ * // [
18
+ * // { path: ['age'], changes: 31, type: 'update' },
19
+ * // { path: ['city'], changes: 'New York', type: 'add' }
20
+ * // ]
21
+ *
22
+ * @example
23
+ * // Compare nested objects
24
+ * const original = {
25
+ * user: { name: 'John', settings: { theme: 'light' } }
26
+ * }
27
+ * const updated = {
28
+ * user: { name: 'John', settings: { theme: 'dark' } }
29
+ * }
30
+ * const differences = diff(original, updated)
31
+ * // Returns:
32
+ * // [
33
+ * // { path: ['user', 'settings', 'theme'], changes: 'dark', type: 'update' }
34
+ * // ]
35
+ */
36
+ export const diff = (doc1, doc2) => {
37
+ const diff = [];
38
+ const bfs = (el1, el2, prefix = []) => {
39
+ // If the types are different, we know that the property has been added, deleted or updated
40
+ if (typeof el1 !== typeof el2) {
41
+ if (typeof el1 === 'undefined') {
42
+ diff.push({ path: prefix, changes: el2, type: 'add' });
43
+ return;
44
+ }
45
+ if (typeof el2 === 'undefined') {
46
+ diff.push({ path: prefix, changes: el1, type: 'delete' });
47
+ return;
48
+ }
49
+ diff.push({ path: prefix, changes: el2, type: 'update' });
50
+ return;
51
+ }
52
+ // We now can assume that el1 and el2 are of the same type
53
+ // For nested objects, we need to recursively check the properties
54
+ if (typeof el1 === 'object' && typeof el2 === 'object' && el1 !== null && el2 !== null) {
55
+ const keys = new Set([...Object.keys(el1), ...Object.keys(el2)]);
56
+ for (const key of keys) {
57
+ bfs(el1[key], el2[key], [...prefix, key]);
58
+ }
59
+ return;
60
+ }
61
+ // For primitives, we can just compare the values
62
+ if (el1 !== el2) {
63
+ diff.push({ path: prefix, changes: el2, type: 'update' });
64
+ }
65
+ };
66
+ // Run breadth-first search
67
+ bfs(doc1, doc2);
68
+ return diff;
29
69
  };
30
- export {
31
- diff
32
- };
33
- //# sourceMappingURL=diff.js.map
@@ -1,9 +1,4 @@
1
- import { apply } from "../diff/apply.js";
2
- import { diff } from "../diff/diff.js";
3
- import { merge } from "../diff/merge.js";
4
- export {
5
- apply,
6
- diff,
7
- merge
8
- };
9
- //# sourceMappingURL=index.js.map
1
+ import { apply } from '../diff/apply.js';
2
+ import { diff } from '../diff/diff.js';
3
+ import { merge } from '../diff/merge.js';
4
+ export { diff, apply, merge };
@@ -1,61 +1,123 @@
1
- import { Trie } from "../diff/trie.js";
2
- import { isArrayEqual, isKeyCollisions, mergeObjects } from "../diff/utils.js";
3
- const merge = (diff1, diff2) => {
4
- const trie = new Trie();
5
- for (const [index, diff] of diff1.entries()) {
6
- trie.addPath(diff.path, { index, changes: diff });
7
- }
8
- const skipDiff1 = /* @__PURE__ */ new Set();
9
- const skipDiff2 = /* @__PURE__ */ new Set();
10
- const conflictsMap1 = /* @__PURE__ */ new Map();
11
- const conflictsMap2 = /* @__PURE__ */ new Map();
12
- for (const [index, diff] of diff2.entries()) {
13
- trie.findMatch(diff.path, (value) => {
14
- if (diff.type === "delete") {
15
- if (value.changes.type === "delete") {
16
- if (value.changes.path.length > diff.path.length) {
17
- skipDiff1.add(value.index);
18
- } else {
19
- skipDiff2.add(value.index);
20
- }
21
- } else {
22
- skipDiff1.add(value.index);
23
- skipDiff2.add(index);
24
- const conflictEntry = conflictsMap2.get(index);
25
- if (conflictEntry !== void 0) {
26
- conflictEntry[0].push(value.changes);
27
- } else {
28
- conflictsMap2.set(index, [[value.changes], [diff]]);
29
- }
30
- }
31
- }
32
- if (diff.type === "add" || diff.type === "update") {
33
- if (isArrayEqual(diff.path, value.changes.path) && value.changes.type !== "delete" && !isKeyCollisions(diff.changes, value.changes.changes)) {
34
- skipDiff1.add(value.index);
35
- if (typeof diff.changes === "object") {
36
- mergeObjects(diff.changes, value.changes.changes);
37
- }
38
- return;
39
- }
40
- skipDiff1.add(value.index);
41
- skipDiff2.add(index);
42
- const conflictEntry = conflictsMap1.get(value.index);
43
- if (conflictEntry !== void 0) {
44
- conflictEntry[1].push(diff);
45
- } else {
46
- conflictsMap1.set(value.index, [[value.changes], [diff]]);
47
- }
48
- }
49
- });
50
- }
51
- const conflicts = [...conflictsMap1.values(), ...conflictsMap2.values()];
52
- const diffs = [
53
- ...diff1.filter((_, index) => !skipDiff1.has(index)),
54
- ...diff2.filter((_, index) => !skipDiff2.has(index))
55
- ];
56
- return { diffs, conflicts };
1
+ import { Trie } from '../diff/trie.js';
2
+ import { isArrayEqual, isKeyCollisions, mergeObjects } from '../diff/utils.js';
3
+ /**
4
+ * Merges two sets of differences from the same document and resolves conflicts.
5
+ * This function combines changes from two diff lists while handling potential conflicts
6
+ * that arise when both diffs modify the same paths. It uses a trie data structure for
7
+ * efficient path matching and conflict detection.
8
+ *
9
+ * @param diff1 - First list of differences
10
+ * @param diff2 - Second list of differences
11
+ * @returns Object containing:
12
+ * - diffs: Combined list of non-conflicting differences
13
+ * - conflicts: Array of conflicting difference pairs that need manual resolution
14
+ *
15
+ * @example
16
+ * // Merge two sets of changes to a user profile
17
+ * const diff1 = [
18
+ * { path: ['name'], changes: 'John', type: 'update' },
19
+ * { path: ['age'], changes: 30, type: 'add' }
20
+ * ]
21
+ * const diff2 = [
22
+ * { path: ['name'], changes: 'Johnny', type: 'update' },
23
+ * { path: ['address'], changes: { city: 'NY' }, type: 'add' }
24
+ * ]
25
+ * const { diffs, conflicts } = merge(diff1, diff2)
26
+ * // Returns:
27
+ * // {
28
+ * // diffs: [
29
+ * // { path: ['age'], changes: 30, type: 'add' },
30
+ * // { path: ['address'], changes: { city: 'NY' }, type: 'add' }
31
+ * // ],
32
+ * // conflicts: [
33
+ * // [
34
+ * // [{ path: ['name'], changes: 'John', type: 'update' }],
35
+ * // [{ path: ['name'], changes: 'Johnny', type: 'update' }]
36
+ * // ]
37
+ * // ]
38
+ * // }
39
+ */
40
+ export const merge = (diff1, diff2) => {
41
+ // Here we need to use a trie to optimize searching for a prefix
42
+ // With the naive approach time complexity of the algorithm would be
43
+ // O(n * m)
44
+ // ^ ^
45
+ // n is the length off diff1 | | m length of diff2
46
+ //
47
+ // Assuming that the maximum depth of the nested objects would be constant lets say 0 <= D <= 100
48
+ // we try to optimize for that using the tire data structure.
49
+ // So the new time complexity would be O(n * D) where D is the maximum depth of the nested object
50
+ const trie = new Trie();
51
+ // Create the trie
52
+ for (const [index, diff] of diff1.entries()) {
53
+ trie.addPath(diff.path, { index, changes: diff });
54
+ }
55
+ const skipDiff1 = new Set();
56
+ const skipDiff2 = new Set();
57
+ // Keep related conflicts together for easy A, B pick conflict resolution
58
+ // map key is going to be conflicting index of first diff list where the diff will be
59
+ // a delete operation or an add/update operation with a one to many conflicts
60
+ const conflictsMap1 = new Map();
61
+ // map key will be the index from the second diff list where the diff will be
62
+ // a delete operation with one to many conflicts
63
+ const conflictsMap2 = new Map();
64
+ for (const [index, diff] of diff2.entries()) {
65
+ trie.findMatch(diff.path, (value) => {
66
+ if (diff.type === 'delete') {
67
+ if (value.changes.type === 'delete') {
68
+ // Keep the highest depth delete operation and skip the other
69
+ if (value.changes.path.length > diff.path.length) {
70
+ skipDiff1.add(value.index);
71
+ }
72
+ else {
73
+ skipDiff2.add(value.index);
74
+ }
75
+ }
76
+ else {
77
+ // Take care of updates/add on the same path (we are sure they will be on the
78
+ // same path since the change comes from the same document)
79
+ skipDiff1.add(value.index);
80
+ skipDiff2.add(index);
81
+ const conflictEntry = conflictsMap2.get(index);
82
+ if (conflictEntry !== undefined) {
83
+ conflictEntry[0].push(value.changes);
84
+ }
85
+ else {
86
+ conflictsMap2.set(index, [[value.changes], [diff]]);
87
+ }
88
+ }
89
+ }
90
+ if (diff.type === 'add' || diff.type === 'update') {
91
+ // For add -> add / update -> update operation we try to first see if we can merge this operations
92
+ if (isArrayEqual(diff.path, value.changes.path) &&
93
+ value.changes.type !== 'delete' &&
94
+ !isKeyCollisions(diff.changes, value.changes.changes)) {
95
+ skipDiff1.add(value.index);
96
+ // For non primitive values we merge object keys into diff2
97
+ if (typeof diff.changes === 'object') {
98
+ mergeObjects(diff.changes, value.changes.changes);
99
+ }
100
+ return;
101
+ }
102
+ // add/update -> delete operations always resolve in conflicts
103
+ skipDiff1.add(value.index);
104
+ skipDiff2.add(index);
105
+ const conflictEntry = conflictsMap1.get(value.index);
106
+ if (conflictEntry !== undefined) {
107
+ conflictEntry[1].push(diff);
108
+ }
109
+ else {
110
+ conflictsMap1.set(value.index, [[value.changes], [diff]]);
111
+ }
112
+ }
113
+ });
114
+ }
115
+ const conflicts = [...conflictsMap1.values(), ...conflictsMap2.values()];
116
+ // Filter all changes that should be skipped because of conflicts
117
+ // or auto conflict resolution
118
+ const diffs = [
119
+ ...diff1.filter((_, index) => !skipDiff1.has(index)),
120
+ ...diff2.filter((_, index) => !skipDiff2.has(index)),
121
+ ];
122
+ return { diffs, conflicts };
57
123
  };
58
- export {
59
- merge
60
- };
61
- //# sourceMappingURL=merge.js.map
package/dist/diff/trie.js CHANGED
@@ -1,82 +1,105 @@
1
- class TrieNode {
2
- constructor(value, children) {
3
- this.value = value;
4
- this.children = children;
5
- }
1
+ /**
2
+ * Trie data structure
3
+ *
4
+ * Read more: https://en.wikipedia.org/wiki/Trie
5
+ */
6
+ /**
7
+ * Represents a node in the trie data structure.
8
+ * Each node can store a value and has a map of child nodes.
9
+ *
10
+ * @template Value - The type of value that can be stored in the node
11
+ */
12
+ export class TrieNode {
13
+ value;
14
+ children;
15
+ constructor(value, children) {
16
+ this.value = value;
17
+ this.children = children;
18
+ }
6
19
  }
7
- class Trie {
8
- root;
9
- constructor() {
10
- this.root = new TrieNode(null, {});
11
- }
12
- /**
13
- * Adds a value to the trie at the specified path.
14
- * Creates new nodes as needed to build the path.
15
- *
16
- * @param path - Array of strings representing the path to store the value
17
- * @param value - The value to store at the end of the path
18
- *
19
- * @example
20
- * const trie = new Trie<number>()
21
- * trie.addPath(['users', 'john', 'age'], 30)
22
- */
23
- addPath(path, value) {
24
- let current = this.root;
25
- for (const dir of path) {
26
- if (current.children[dir]) {
27
- current = current.children[dir];
28
- } else {
29
- current.children[dir] = new TrieNode(null, {});
30
- current = current.children[dir];
31
- }
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 {
33
+ root;
34
+ constructor() {
35
+ this.root = new TrieNode(null, {});
32
36
  }
33
- current.value = value;
34
- }
35
- /**
36
- * Finds all matches along a given path in the trie.
37
- * This method traverses both the exact path and all deeper paths,
38
- * executing a callback for each matching value found.
39
- *
40
- * The search is performed in two phases:
41
- * 1. Traverse the exact path, checking for matches at each node
42
- * 2. Perform a depth-first search from the end of the path to find all deeper matches
43
- *
44
- * @param path - Array of strings representing the path to search
45
- * @param callback - Function to execute for each matching value found
46
- *
47
- * @example
48
- * const trie = new Trie<number>()
49
- * trie.addPath(['a', 'b', 'c'], 1)
50
- * trie.addPath(['a', 'b', 'd'], 2)
51
- * trie.findMatch(['a', 'b'], (value) => console.log(value)) // Logs: 1, 2
52
- */
53
- findMatch(path, callback) {
54
- let current = this.root;
55
- for (const dir of path) {
56
- if (current.value !== null) {
57
- callback(current.value);
58
- }
59
- const next = current.children[dir];
60
- if (!next) {
61
- return;
62
- }
63
- current = next;
37
+ /**
38
+ * Adds a value to the trie at the specified path.
39
+ * Creates new nodes as needed to build the path.
40
+ *
41
+ * @param path - Array of strings representing the path to store the value
42
+ * @param value - The value to store at the end of the path
43
+ *
44
+ * @example
45
+ * const trie = new Trie<number>()
46
+ * trie.addPath(['users', 'john', 'age'], 30)
47
+ */
48
+ addPath(path, value) {
49
+ let current = this.root;
50
+ for (const dir of path) {
51
+ if (current.children[dir]) {
52
+ current = current.children[dir];
53
+ }
54
+ else {
55
+ current.children[dir] = new TrieNode(null, {});
56
+ current = current.children[dir];
57
+ }
58
+ }
59
+ current.value = value;
64
60
  }
65
- const dfs = (current2) => {
66
- for (const child of Object.keys(current2?.children ?? {})) {
67
- if (current2 && Object.hasOwn(current2.children, child)) {
68
- dfs(current2?.children[child]);
61
+ /**
62
+ * Finds all matches along a given path in the trie.
63
+ * This method traverses both the exact path and all deeper paths,
64
+ * executing a callback for each matching value found.
65
+ *
66
+ * The search is performed in two phases:
67
+ * 1. Traverse the exact path, checking for matches at each node
68
+ * 2. Perform a depth-first search from the end of the path to find all deeper matches
69
+ *
70
+ * @param path - Array of strings representing the path to search
71
+ * @param callback - Function to execute for each matching value found
72
+ *
73
+ * @example
74
+ * const trie = new Trie<number>()
75
+ * trie.addPath(['a', 'b', 'c'], 1)
76
+ * trie.addPath(['a', 'b', 'd'], 2)
77
+ * trie.findMatch(['a', 'b'], (value) => console.log(value)) // Logs: 1, 2
78
+ */
79
+ findMatch(path, callback) {
80
+ let current = this.root;
81
+ for (const dir of path) {
82
+ // Note: the last callback wont fire here because it will fire on the dfs
83
+ if (current.value !== null) {
84
+ callback(current.value);
85
+ }
86
+ const next = current.children[dir];
87
+ if (!next) {
88
+ return;
89
+ }
90
+ current = next;
69
91
  }
70
- }
71
- if (current2?.value) {
72
- callback(current2.value);
73
- }
74
- };
75
- dfs(current);
76
- }
92
+ const dfs = (current) => {
93
+ for (const child of Object.keys(current?.children ?? {})) {
94
+ if (current && Object.hasOwn(current.children, child)) {
95
+ dfs(current?.children[child]);
96
+ }
97
+ }
98
+ if (current?.value) {
99
+ callback(current.value);
100
+ }
101
+ };
102
+ // Dfs for the rest of the path
103
+ dfs(current);
104
+ }
77
105
  }
78
- export {
79
- Trie,
80
- TrieNode
81
- };
82
- //# sourceMappingURL=trie.js.map