@synnaxlabs/x 0.44.4 → 0.45.1

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 (150) hide show
  1. package/.turbo/turbo-build.log +38 -38
  2. package/dist/{base-BORMP3mH.js → base-DRybODwJ.js} +7 -6
  3. package/dist/base-KIBsp6TI.cjs +1 -0
  4. package/dist/binary.cjs +1 -1
  5. package/dist/binary.js +1 -1
  6. package/dist/{bounds-BXbqBINt.cjs → bounds-C2TKFgVk.cjs} +1 -1
  7. package/dist/{bounds-BQqppNFf.js → bounds-DeUXrllt.js} +11 -11
  8. package/dist/bounds.cjs +1 -1
  9. package/dist/bounds.js +1 -1
  10. package/dist/{box-DRH5SOaY.js → box-BYuq-Gjx.js} +4 -4
  11. package/dist/{box-qgxWXNhm.cjs → box-Blu-4d1n.cjs} +1 -1
  12. package/dist/box.cjs +1 -1
  13. package/dist/box.js +1 -1
  14. package/dist/caseconv.cjs +1 -1
  15. package/dist/caseconv.js +1 -1
  16. package/dist/compare.cjs +1 -1
  17. package/dist/compare.js +1 -1
  18. package/dist/deep.cjs +1 -1
  19. package/dist/deep.js +211 -99
  20. package/dist/{dimensions-qY12pyfC.cjs → dimensions-Cg5Owbwn.cjs} +1 -1
  21. package/dist/{dimensions-RaRkesPq.js → dimensions-DC0uLPwn.js} +1 -1
  22. package/dist/dimensions.cjs +1 -1
  23. package/dist/dimensions.js +1 -1
  24. package/dist/{direction-DKdfJwj7.js → direction-C_b4tfRN.js} +1 -1
  25. package/dist/{direction-XCdrc4is.cjs → direction-DqQB9M37.cjs} +1 -1
  26. package/dist/direction.cjs +1 -1
  27. package/dist/direction.js +1 -1
  28. package/dist/external-CtHGFcox.cjs +1 -0
  29. package/dist/{external-BM_NS5yM.js → external-tyaEMW4S.js} +1 -1
  30. package/dist/get-CXkBfLu1.js +82 -0
  31. package/dist/get-OP63N4c3.cjs +1 -0
  32. package/dist/{index-B5l_quQn.js → index-BHXRDFYj.js} +1 -1
  33. package/dist/index-Bxlv0uf_.js +57 -0
  34. package/dist/{index-CBMHFqs4.js → index-C452Pas0.js} +25 -22
  35. package/dist/{index-BQe8OIgm.cjs → index-CwGPVvbl.cjs} +1 -1
  36. package/dist/index-uDxeM-cl.cjs +1 -0
  37. package/dist/index-xaxa1hoa.cjs +1 -0
  38. package/dist/index.cjs +3 -3
  39. package/dist/index.js +442 -380
  40. package/dist/location-BPoXwOni.cjs +1 -0
  41. package/dist/{location-CGLioInQ.js → location-CVxysrHI.js} +31 -30
  42. package/dist/location.cjs +1 -1
  43. package/dist/location.js +1 -1
  44. package/dist/{scale-76Azh2EE.cjs → scale-BHs716im.cjs} +1 -1
  45. package/dist/{scale-BhIvACdB.js → scale-DjxC6ep2.js} +4 -4
  46. package/dist/scale.cjs +1 -1
  47. package/dist/scale.js +1 -1
  48. package/dist/series-C6ZwNf8i.cjs +6 -0
  49. package/dist/{series-kgnLXSDr.js → series-W5Aafjeu.js} +268 -251
  50. package/dist/{spatial-QY891r0E.js → spatial-DnsaOypA.js} +1 -1
  51. package/dist/{spatial-BsGadoUr.cjs → spatial-DrxzaD5U.cjs} +1 -1
  52. package/dist/spatial.cjs +1 -1
  53. package/dist/spatial.js +8 -8
  54. package/dist/src/caseconv/caseconv.d.ts +9 -0
  55. package/dist/src/caseconv/caseconv.d.ts.map +1 -1
  56. package/dist/src/color/color.d.ts +7 -0
  57. package/dist/src/color/color.d.ts.map +1 -1
  58. package/dist/src/deep/copy.spec.d.ts +2 -0
  59. package/dist/src/deep/copy.spec.d.ts.map +1 -0
  60. package/dist/src/deep/external.d.ts +3 -1
  61. package/dist/src/deep/external.d.ts.map +1 -1
  62. package/dist/src/deep/get.bench.d.ts +2 -0
  63. package/dist/src/deep/get.bench.d.ts.map +1 -0
  64. package/dist/src/deep/get.d.ts +16 -0
  65. package/dist/src/deep/get.d.ts.map +1 -0
  66. package/dist/src/deep/get.spec.d.ts +2 -0
  67. package/dist/src/deep/get.spec.d.ts.map +1 -0
  68. package/dist/src/deep/partial.spec.d.ts +2 -0
  69. package/dist/src/deep/partial.spec.d.ts.map +1 -0
  70. package/dist/src/deep/path.d.ts +4 -92
  71. package/dist/src/deep/path.d.ts.map +1 -1
  72. package/dist/src/deep/remove.bench.d.ts +2 -0
  73. package/dist/src/deep/remove.bench.d.ts.map +1 -0
  74. package/dist/src/deep/remove.d.ts +2 -0
  75. package/dist/src/deep/remove.d.ts.map +1 -0
  76. package/dist/src/deep/remove.spec.d.ts +2 -0
  77. package/dist/src/deep/remove.spec.d.ts.map +1 -0
  78. package/dist/src/deep/set.bench.d.ts +2 -0
  79. package/dist/src/deep/set.bench.d.ts.map +1 -0
  80. package/dist/src/deep/set.d.ts +2 -0
  81. package/dist/src/deep/set.d.ts.map +1 -0
  82. package/dist/src/deep/set.spec.d.ts +2 -0
  83. package/dist/src/deep/set.spec.d.ts.map +1 -0
  84. package/dist/src/index.d.ts +1 -0
  85. package/dist/src/index.d.ts.map +1 -1
  86. package/dist/src/primitive/primitive.d.ts +4 -0
  87. package/dist/src/primitive/primitive.d.ts.map +1 -1
  88. package/dist/src/spatial/location/location.d.ts +2 -2
  89. package/dist/src/spatial/location/location.d.ts.map +1 -1
  90. package/dist/src/status/status.d.ts +2 -1
  91. package/dist/src/status/status.d.ts.map +1 -1
  92. package/dist/src/telem/series.d.ts +1 -0
  93. package/dist/src/telem/series.d.ts.map +1 -1
  94. package/dist/src/telem/telem.d.ts +15 -6
  95. package/dist/src/telem/telem.d.ts.map +1 -1
  96. package/dist/src/undefined.d.ts +2 -0
  97. package/dist/src/undefined.d.ts.map +1 -0
  98. package/dist/telem.cjs +1 -1
  99. package/dist/telem.js +1 -1
  100. package/dist/{xy-BKIJiLu_.cjs → xy-DWwtHmgn.cjs} +1 -1
  101. package/dist/{xy-CBuhMaIo.js → xy-DYPw8-8C.js} +1 -1
  102. package/dist/xy.cjs +1 -1
  103. package/dist/xy.js +1 -1
  104. package/dist/zod.cjs +1 -1
  105. package/dist/zod.js +1 -1
  106. package/package.json +4 -4
  107. package/src/caseconv/caseconv.spec.ts +30 -0
  108. package/src/caseconv/caseconv.ts +46 -0
  109. package/src/color/color.spec.ts +40 -0
  110. package/src/color/color.ts +72 -0
  111. package/src/deep/copy.spec.ts +148 -0
  112. package/src/deep/external.ts +3 -1
  113. package/src/deep/get.bench.ts +170 -0
  114. package/src/deep/get.spec.ts +196 -0
  115. package/src/deep/get.ts +79 -0
  116. package/src/deep/partial.spec.ts +194 -0
  117. package/src/deep/path.spec.ts +92 -183
  118. package/src/deep/path.ts +27 -198
  119. package/src/deep/remove.bench.ts +238 -0
  120. package/src/deep/remove.spec.ts +219 -0
  121. package/src/deep/remove.ts +102 -0
  122. package/src/deep/set.bench.ts +208 -0
  123. package/src/deep/set.spec.ts +369 -0
  124. package/src/deep/set.ts +91 -0
  125. package/src/index.ts +1 -0
  126. package/src/primitive/primitive.spec.ts +9 -0
  127. package/src/primitive/primitive.ts +9 -0
  128. package/src/spatial/location/location.ts +2 -0
  129. package/src/status/status.spec.ts +52 -0
  130. package/src/status/status.ts +19 -6
  131. package/src/telem/series.ts +2 -0
  132. package/src/telem/telem.spec.ts +302 -0
  133. package/src/telem/telem.ts +60 -18
  134. package/src/undefined.ts +14 -0
  135. package/tsconfig.tsbuildinfo +1 -1
  136. package/dist/base-BLNViP3D.cjs +0 -1
  137. package/dist/external-E3ErJeeM.cjs +0 -1
  138. package/dist/index-D2xcvEO5.js +0 -46
  139. package/dist/index-DOJlZHqJ.cjs +0 -1
  140. package/dist/index-DdhM_E4k.cjs +0 -1
  141. package/dist/location-DJ_K4SlP.cjs +0 -1
  142. package/dist/path-Blh4wJuA.js +0 -110
  143. package/dist/path-CPSfCjde.cjs +0 -1
  144. package/dist/series-tAhThbnz.cjs +0 -6
  145. package/dist/src/deep/delete.d.ts +0 -3
  146. package/dist/src/deep/delete.d.ts.map +0 -1
  147. package/dist/src/deep/delete.spec.d.ts +0 -2
  148. package/dist/src/deep/delete.spec.d.ts.map +0 -1
  149. package/src/deep/delete.spec.ts +0 -73
  150. package/src/deep/delete.ts +0 -27
@@ -10,164 +10,8 @@
10
10
  import { describe, expect, it } from "vitest";
11
11
 
12
12
  import { deep } from "@/deep";
13
- import { type record } from "@/record";
14
-
15
- interface TestRecord {
16
- a: number;
17
- b: {
18
- c?: number;
19
- d?: number;
20
- };
21
- c: number[];
22
- }
23
-
24
- describe("path", () => {
25
- describe("get", () => {
26
- it("should get a key", () => {
27
- const a: TestRecord = {
28
- a: 1,
29
- b: {
30
- c: 2,
31
- },
32
- c: [1],
33
- };
34
- expect(deep.get(a, "b.c")).toEqual(2);
35
- });
36
- it("should get an array index", () => {
37
- const a: TestRecord = {
38
- a: 1,
39
- b: {
40
- c: 2,
41
- },
42
- c: [1, 2, 3],
43
- };
44
- expect(deep.get(a, "c.1")).toEqual(2);
45
- });
46
- it("should return the object itself if the key is empty", () => {
47
- const a: TestRecord = {
48
- a: 1,
49
- b: {
50
- c: 2,
51
- },
52
- c: [1, 2, 3],
53
- };
54
- expect(deep.get(a, "")).toStrictEqual(a);
55
- });
56
- describe("custom getter function", () => {
57
- const v = {
58
- a: {
59
- value: () => ({
60
- c: 0,
61
- }),
62
- },
63
- };
64
- it("should use the custom getter function", () => {
65
- expect(
66
- deep.get(v, "a.value().c", {
67
- optional: false,
68
- getter: (obj, key) => {
69
- if (key === "value()")
70
- return (obj as { value: () => { c: number } }).value();
71
- return obj[key];
72
- },
73
- }),
74
- ).toEqual(0);
75
- });
76
-
77
- it("should get an array of keyed records", () => {
78
- interface TestKeyedRecord {
79
- values: record.KeyedNamed[];
80
- }
81
- const a: TestKeyedRecord = {
82
- values: [
83
- { key: "a", name: "a" },
84
- { key: "b", name: "b" },
85
- ],
86
- };
87
- expect(deep.get(a, "values.a.name")).toEqual("a");
88
- });
89
- });
90
- });
91
-
92
- describe("set", () => {
93
- it("should set a key", () => {
94
- const a: TestRecord = {
95
- a: 1,
96
- b: {
97
- c: 2,
98
- },
99
- c: [1],
100
- };
101
- const b: TestRecord = {
102
- a: 1,
103
- b: {
104
- c: 3,
105
- },
106
- c: [1],
107
- };
108
- deep.set(a, "b.c", 3);
109
- expect(a).toEqual(b);
110
- });
111
-
112
- it("should set an array index", () => {
113
- const a: TestRecord = {
114
- a: 1,
115
- b: {
116
- c: 2,
117
- },
118
- c: [1, 2, 3],
119
- };
120
- const b: TestRecord = {
121
- a: 1,
122
- b: {
123
- c: 2,
124
- },
125
- c: [1, 4, 3],
126
- };
127
- deep.set(a, "c.1", 4);
128
- expect(a).toEqual(b);
129
- });
130
-
131
- it("should interpret a leading number also containing letters as a key", () => {
132
- const data = {
133
- a: [
134
- {
135
- key: "1b",
136
- value: 1,
137
- },
138
- ],
139
- };
140
- deep.set(data, "a.1b.value", 2);
141
- expect(deep.get(data, "a.1b.value")).toEqual(2);
142
- });
143
-
144
- it("should set a value on a nested object in the array by key", () => {
145
- const data = {
146
- config: {
147
- channels: [{ key: "tMnAnJeQmn6", type: "ai_voltage" }],
148
- },
149
- };
150
- deep.set(data, "config.channels.tMnAnJeQmn6.type", "ai_force_bridge_table");
151
- expect(data.config.channels[0].type).toEqual("ai_force_bridge_table");
152
- });
153
-
154
- it("should set an entire item in the array by its key", () => {
155
- const data = {
156
- config: {
157
- channels: [{ key: "tMnAnJeQmn6", type: "ai_voltage" }],
158
- },
159
- };
160
- deep.set(data, "config.channels.tMnAnJeQmn6", {
161
- key: "tMnAnJeQmn6",
162
- type: "ai_force_bridge_table",
163
- });
164
- expect(data.config.channels[0]).toEqual({
165
- key: "tMnAnJeQmn6",
166
- type: "ai_force_bridge_table",
167
- });
168
- });
169
- });
170
13
 
14
+ describe("path utilities", () => {
171
15
  describe("transformPath", () => {
172
16
  it("should transform a path", () => {
173
17
  expect(deep.transformPath("a.b.c", (part) => part.toUpperCase())).toEqual(
@@ -184,7 +28,7 @@ describe("path", () => {
184
28
  });
185
29
  });
186
30
 
187
- describe("matches", () => {
31
+ describe("pathsMatch", () => {
188
32
  it("should return true if two paths are equal", () => {
189
33
  expect(deep.pathsMatch("a.b.c", "a.b.c")).toEqual(true);
190
34
  });
@@ -197,6 +41,12 @@ describe("path", () => {
197
41
  it("should return true for an empty pattern", () => {
198
42
  expect(deep.pathsMatch("a.b.c", "")).toEqual(true);
199
43
  });
44
+ it("should return false if pattern is longer than path", () => {
45
+ expect(deep.pathsMatch("a.b", "a.b.c")).toEqual(false);
46
+ });
47
+ it("should return false if paths don't match", () => {
48
+ expect(deep.pathsMatch("a.b.c", "a.b.d")).toEqual(false);
49
+ });
200
50
  });
201
51
 
202
52
  describe("resolvePath", () => {
@@ -225,33 +75,92 @@ describe("path", () => {
225
75
  });
226
76
  });
227
77
 
228
- describe("delete", () => {
229
- const a: TestRecord = {
230
- a: 1,
231
- b: {
232
- c: 2,
233
- },
234
- c: [1, 2, 3],
235
- };
236
- it("should delete a key", () => {
237
- const cpy = deep.copy(a);
238
- deep.remove(a, "b.c");
239
- expect(a).toEqual({ ...cpy, b: {} });
78
+ describe("element", () => {
79
+ it("should get element at index", () => {
80
+ expect(deep.element("a.b.c", 1)).toEqual("b");
81
+ });
82
+
83
+ it("should handle negative index", () => {
84
+ expect(deep.element("a.b.c", -1)).toEqual("c");
85
+ expect(deep.element("a.b.c", -2)).toEqual("b");
240
86
  });
241
- it("should delete an array index", () => {
242
- const cpy = deep.copy(a);
243
- deep.remove(a, "c.1");
244
- expect(a).toEqual({ ...cpy, c: [1, 3] });
87
+
88
+ it("should get first element", () => {
89
+ expect(deep.element("a.b.c", 0)).toEqual("a");
245
90
  });
246
- it("should not throw an error when the index is out of bounds", () => {
247
- const cpy = deep.copy(a);
248
- deep.remove(a, "c.100");
249
- expect(a).toEqual(cpy);
91
+ });
92
+
93
+ describe("getIndex", () => {
94
+ it("should return index for numeric string", () => {
95
+ expect(deep.getIndex("42")).toEqual(42);
250
96
  });
251
- it("should not throw an error when the key exists", () => {
252
- const cpy = deep.copy(a);
253
- deep.remove(a, "b.d");
254
- expect(a).toEqual(cpy);
97
+
98
+ it("should return null for non-numeric string", () => {
99
+ expect(deep.getIndex("abc")).toBeNull();
100
+ });
101
+
102
+ it("should return null for mixed string", () => {
103
+ expect(deep.getIndex("1a")).toBeNull();
104
+ });
105
+
106
+ it("should handle zero", () => {
107
+ expect(deep.getIndex("0")).toEqual(0);
108
+ });
109
+ });
110
+
111
+ describe("defaultGetter", () => {
112
+ it("should get property from object", () => {
113
+ const obj = { a: 1, b: 2 };
114
+ expect(deep.defaultGetter(obj, "a")).toEqual(1);
115
+ });
116
+
117
+ it("should get index from array", () => {
118
+ const arr = [1, 2, 3];
119
+ expect(deep.defaultGetter(arr as any, "1")).toEqual(2);
120
+ });
121
+
122
+ it("should find keyed item in array", () => {
123
+ const arr = [
124
+ { key: "item1", value: 1 },
125
+ { key: "item2", value: 2 },
126
+ ];
127
+ expect(deep.defaultGetter(arr as any, "item2")).toEqual({ key: "item2", value: 2 });
128
+ });
129
+
130
+ it("should return undefined for non-existent key", () => {
131
+ const obj = { a: 1 };
132
+ expect(deep.defaultGetter(obj, "b")).toBeUndefined();
133
+ });
134
+
135
+ it("should handle empty array", () => {
136
+ const arr: any[] = [];
137
+ expect(deep.defaultGetter(arr as any, "0")).toBeUndefined();
138
+ });
139
+ });
140
+
141
+ describe("findBestKey", () => {
142
+ it("should find single part key", () => {
143
+ const obj = { a: { b: 1 } };
144
+ const result = deep.findBestKey(obj, ["a", "b"]);
145
+ expect(result).toEqual(["a", 1]);
146
+ });
147
+
148
+ it("should find multi-part key with period", () => {
149
+ const obj = { "a.b": { c: 1 } };
150
+ const result = deep.findBestKey(obj, ["a.b", "c"]);
151
+ expect(result).toEqual(["a.b", 1]);
152
+ });
153
+
154
+ it("should return null if no key found", () => {
155
+ const obj = { a: 1 };
156
+ const result = deep.findBestKey(obj, ["b", "c"]);
157
+ expect(result).toBeNull();
158
+ });
159
+
160
+ it("should prefer shorter key", () => {
161
+ const obj = { a: { b: 1 }, "a.b": 2 };
162
+ const result = deep.findBestKey(obj, ["a", "b"]);
163
+ expect(result).toEqual(["a", 1]);
255
164
  });
256
165
  });
257
- });
166
+ });
package/src/deep/path.ts CHANGED
@@ -10,6 +10,8 @@
10
10
  import { type Join } from "@/join";
11
11
  import { type record } from "@/record";
12
12
 
13
+ export const SEPARATOR = ".";
14
+
13
15
  type Prev = [
14
16
  never,
15
17
  0,
@@ -36,10 +38,6 @@ type Prev = [
36
38
  ...0[],
37
39
  ];
38
40
 
39
- /**
40
- * A type that represents a deep key in an object.
41
- * @example type Key<T, 3> = "a" | "a.b" | "a.b.c"
42
- */
43
41
  export type Key<T, D extends number = 5> = [D] extends [never]
44
42
  ? never
45
43
  : T extends object
@@ -50,51 +48,6 @@ export type Key<T, D extends number = 5> = [D] extends [never]
50
48
  }[keyof T]
51
49
  : "";
52
50
 
53
- /** Options for the get function. */
54
- export interface GetOptions<O extends boolean | undefined = boolean | undefined> {
55
- optional: O;
56
- getter?: (obj: record.Unknown, key: string) => unknown;
57
- separator?: string;
58
- }
59
-
60
- /**
61
- * A function that gets the value at the given path on the object. If the path does not exist
62
- * and the optional flag is set to true, null will be returned. If the path does not exist and
63
- * the optional flag is set to false, an error will be thrown.
64
- * @param obj the object to get the value from.
65
- * @param path the path to get the value at.
66
- * @param options the options for getting the value.
67
- * @param options.optional whether the path is optional.
68
- * @param options.getter a custom getter function to use on each part of the path.
69
- * @returns the value at the given path on the object.
70
- */
71
- export interface Get {
72
- <V = record.Unknown, T = record.Unknown>(
73
- obj: T,
74
- path: string,
75
- options?: GetOptions<false>,
76
- ): V;
77
- <V = record.Unknown, T = record.Unknown>(
78
- obj: T,
79
- path: string,
80
- options?: GetOptions<boolean | undefined>,
81
- ): V | null;
82
- }
83
-
84
- /** A strongly typed version of the @link Get function. */
85
- export interface TypedGet<V = record.Unknown, T = record.Unknown> {
86
- (obj: T, path: string, options?: GetOptions<false>): V;
87
- (obj: T, path: string, options?: GetOptions<boolean | undefined>): V | null;
88
- }
89
-
90
- /**
91
- * Executes the given replacer function on each part of the path.
92
- * @param path the path to transform
93
- * @param replacer the function to execute on each part of the path. If multiple
94
- * parts are returned, they will be joined with a period. If null/undefined is returned,
95
- * the part will be removed from the path.
96
- * @returns the transformed path.
97
- */
98
51
  export const transformPath = (
99
52
  path: string,
100
53
  replacer: (
@@ -102,7 +55,7 @@ export const transformPath = (
102
55
  index: number,
103
56
  parts: string[],
104
57
  ) => string | string[] | undefined,
105
- separator = ".",
58
+ separator: string = SEPARATOR,
106
59
  ): string => {
107
60
  const parts = path.split(separator);
108
61
  const result = parts
@@ -116,7 +69,7 @@ export const transformPath = (
116
69
  return result.join(separator);
117
70
  };
118
71
 
119
- const defaultGetter = (obj: record.Unknown, key: string): unknown => {
72
+ export const defaultGetter = (obj: record.Unknown, key: string): unknown => {
120
73
  if (!Array.isArray(obj)) return obj[key];
121
74
  const res = obj[key];
122
75
  if (res != null || obj.length == 0) return res;
@@ -127,167 +80,25 @@ const defaultGetter = (obj: record.Unknown, key: string): unknown => {
127
80
  };
128
81
 
129
82
  export const resolvePath = <T = record.Unknown>(path: string, obj: T): string => {
130
- const parts = path.split(".");
83
+ const parts = path.split(SEPARATOR);
131
84
  parts.forEach((part, i) => {
132
85
  obj = defaultGetter(obj as record.Unknown, part) as T;
133
86
  if (obj != null && typeof obj === "object" && "key" in obj)
134
87
  parts[i] = obj.key as string;
135
88
  });
136
- return parts.join(".");
137
- };
138
-
139
- /**
140
- * Gets the value at the given path on the object. If the path does not exist
141
- * and the optional flag is set to true, null will be returned. If the path does
142
- * not exist and the optional flag is set to false, an error will be thrown.
143
- * @param obj the object to get the value from.
144
- * @param path the path to get the value at.
145
- * @param opts the options for getting the value.
146
- * @param opts.optional whether the path is optional.
147
- * @param opts.getter a custom getter function to use on each part of the path.
148
- * @returns the value at the given path on the object.
149
- */
150
- export const get = (<V = record.Unknown, T = record.Unknown>(
151
- obj: T,
152
- path: string,
153
- opts: GetOptions = { optional: false, separator: "." },
154
- ): V | null => {
155
- opts.separator ??= ".";
156
- const { optional, getter = defaultGetter } = opts;
157
- const parts = path.split(opts.separator);
158
- if (parts.length === 1 && parts[0] === "") return obj as record.Unknown as V;
159
- let result: record.Unknown = obj as record.Unknown;
160
- for (const part of parts) {
161
- const v = getter(result, part);
162
- if (v == null) {
163
- if (optional) return null;
164
- throw new Error(`Path ${path} does not exist. ${part} is null`);
165
- }
166
- result = v as record.Unknown;
167
- }
168
- return result as V;
169
- }) as Get;
170
-
171
- const getIndex = (part: string): number | null => {
172
- // in order to be considered an index, all characters must be numbers
173
- for (const char of part) if (isNaN(parseInt(char))) return null;
174
- return parseInt(part);
175
- };
176
-
177
- /**
178
- * Sets the value at the given path on the object. If the parents of the deep path
179
- * do not exist, new objects will be created.
180
- * @param obj the object to set the value on.
181
- * @param path the path to set the value at.
182
- * @param value the value to set.
183
- */
184
- export const set = <V>(obj: V, path: string, value: unknown): void => {
185
- const parts = path.split(".");
186
- let result: record.Unknown = obj as record.Unknown;
187
- for (let i = 0; i < parts.length - 1; i++) {
188
- const part = parts[i];
189
- const v = defaultGetter(result, part);
190
- if (v == null) throw new Error(`Path ${path} does not exist. ${part} is null`);
191
- result = v as record.Unknown;
192
- }
193
- try {
194
- if (!Array.isArray(result)) {
195
- result[parts[parts.length - 1]] = value;
196
- return;
197
- }
198
- if (result.length === 0) return;
199
- const index = getIndex(parts[parts.length - 1]);
200
- // If we can't parse an index, try to interpret it as an object key.
201
- if (index == null) {
202
- const first = result[0];
203
- if (typeof first === "object" && "key" in first) {
204
- const objIndex = result.findIndex((o) => o.key === parts[parts.length - 1]);
205
- if (objIndex !== -1) {
206
- result[objIndex] = value;
207
- return;
208
- }
209
- }
210
- return;
211
- }
212
- result[index] = value;
213
- } catch (e) {
214
- console.error("failed to set value", value, "at path", path, "on object", obj);
215
- throw e;
216
- }
89
+ return parts.join(SEPARATOR);
217
90
  };
218
91
 
219
- /**
220
- * Removes the value at the given path, modifying the object in place.
221
- * @param obj the object to remove the value from.
222
- * @param path the path to remove the value from.
223
- * @returns the object with the value removed.
224
- */
225
- export const remove = <V>(obj: V, path: string): void => {
226
- const parts = path.split(".");
227
- let result: record.Unknown = obj as record.Unknown;
228
- for (let i = 0; i < parts.length - 1; i++) {
229
- const part = parts[i];
230
- if (result[part] == null) return;
231
- result = result[part] as record.Unknown;
232
- }
233
- // if its an array, we need to splice it
234
- if (Array.isArray(result)) {
235
- const index = parseInt(parts[parts.length - 1]);
236
- if (isNaN(index)) return;
237
- result.splice(index, 1);
238
- return;
239
- }
240
- delete result[parts[parts.length - 1]];
241
- };
242
-
243
- /**
244
- * Returns the element at the given index in the path.
245
- * @param path the path to get the element from
246
- * @param index the index of the element to get
247
- * @returns the element at the given index in the path
248
- */
249
92
  export const element = (path: string, index: number): string => {
250
- const parts = path.split(".");
93
+ const parts = path.split(SEPARATOR);
251
94
  if (index < 0) return parts[parts.length + index];
252
95
  return parts[index];
253
96
  };
254
97
 
255
- /**
256
- * Checks if the path exists in the object.
257
- * @param obj the object to check
258
- * @param path the path to check
259
- * @returns whether the path exists in the object
260
- */
261
- export const has = <V = record.Unknown, T = record.Unknown>(
262
- obj: T,
263
- path: string,
264
- ): boolean => {
265
- try {
266
- get<V, T>(obj, path);
267
- return true;
268
- } catch {
269
- return false;
270
- }
271
- };
272
-
273
- /**
274
- * Checks if the path matches the given pattern.
275
- *
276
- * @param path The path to check.
277
- * @param pattern The pattern to match against. Only "*" is supported as a wildcard.
278
- * @returns Whether the path matches the pattern.
279
- *
280
- * * @example
281
- * pathsMatch("a.b.c", "a.b.c") // true
282
- * pathsMatch("a.b.c", "a.b") // true
283
- * pathsMatch("a.b", "a.b.c") // false
284
- * pathsMatch("a.b.c", "a.*") // true
285
- * pathsMatch("a.b.c", "a.*.c") // true
286
- */
287
98
  export const pathsMatch = (path: string, pattern: string): boolean => {
288
99
  if (pattern.length === 0) return true;
289
- const parts = path.split(".");
290
- const patterns = pattern.split(".");
100
+ const parts = path.split(SEPARATOR);
101
+ const patterns = pattern.split(SEPARATOR);
291
102
  if (patterns.length > parts.length) return false;
292
103
  for (let i = 0; i < patterns.length; i++) {
293
104
  const part = parts[i];
@@ -297,3 +108,21 @@ export const pathsMatch = (path: string, pattern: string): boolean => {
297
108
  }
298
109
  return true;
299
110
  };
111
+
112
+ export const getIndex = (part: string): number | null => {
113
+ const num = parseInt(part);
114
+ if (isNaN(num) || num < 0 || num.toString() !== part) return null;
115
+ return num;
116
+ };
117
+
118
+ export const findBestKey = (
119
+ obj: record.Unknown,
120
+ remainingParts: string[],
121
+ ): [string, number] | null => {
122
+ for (let i = 1; i <= remainingParts.length; i++) {
123
+ const candidateKey = remainingParts.slice(0, i).join(SEPARATOR);
124
+ const v = defaultGetter(obj, candidateKey);
125
+ if (v != null) return [candidateKey, i];
126
+ }
127
+ return null;
128
+ };