@eventvisor/sdk 0.0.2
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.
- package/LICENSE +21 -0
- package/README.md +9 -0
- package/dist/attributesManager.d.ts +36 -0
- package/dist/bucketer.d.ts +30 -0
- package/dist/compareVersions.d.ts +4 -0
- package/dist/conditions.d.ts +20 -0
- package/dist/datafileReader.d.ts +29 -0
- package/dist/effectsManager.d.ts +33 -0
- package/dist/emitter.d.ts +11 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2 -0
- package/dist/index.mjs.gz +0 -0
- package/dist/index.mjs.map +1 -0
- package/dist/instance.d.ts +67 -0
- package/dist/logger.d.ts +26 -0
- package/dist/modulesManager.d.ts +67 -0
- package/dist/murmurhash.d.ts +1 -0
- package/dist/persister.d.ts +40 -0
- package/dist/sourceResolver.d.ts +31 -0
- package/dist/transformer.d.ts +21 -0
- package/dist/validator.d.ts +28 -0
- package/jest.config.js +6 -0
- package/lib/attributesManager.d.ts +36 -0
- package/lib/bucketer.d.ts +30 -0
- package/lib/compareVersions.d.ts +4 -0
- package/lib/conditions.d.ts +20 -0
- package/lib/datafileReader.d.ts +29 -0
- package/lib/effectsManager.d.ts +33 -0
- package/lib/emitter.d.ts +11 -0
- package/lib/index.d.ts +12 -0
- package/lib/instance.d.ts +67 -0
- package/lib/logger.d.ts +26 -0
- package/lib/modulesManager.d.ts +67 -0
- package/lib/murmurhash.d.ts +1 -0
- package/lib/persister.d.ts +40 -0
- package/lib/sourceResolver.d.ts +31 -0
- package/lib/transformer.d.ts +21 -0
- package/lib/validator.d.ts +28 -0
- package/package.json +45 -0
- package/src/attributesManager.ts +181 -0
- package/src/bucketer.spec.ts +156 -0
- package/src/bucketer.ts +152 -0
- package/src/compareVersions.ts +93 -0
- package/src/conditions.ts +224 -0
- package/src/datafileReader.ts +133 -0
- package/src/effectsManager.ts +214 -0
- package/src/emitter.ts +64 -0
- package/src/index.spec.ts +5 -0
- package/src/index.ts +14 -0
- package/src/instance.spec.ts +184 -0
- package/src/instance.ts +608 -0
- package/src/logger.ts +90 -0
- package/src/modulesManager.ts +276 -0
- package/src/murmurhash.ts +71 -0
- package/src/persister.ts +162 -0
- package/src/sourceResolver.spec.ts +253 -0
- package/src/sourceResolver.ts +213 -0
- package/src/transformer.ts +316 -0
- package/src/transformer_static.spec.ts +377 -0
- package/src/transformer_types.spec.ts +820 -0
- package/src/validator.spec.ts +579 -0
- package/src/validator.ts +366 -0
- package/tsconfig.cjs.json +8 -0
- package/tsconfig.esm.json +8 -0
- package/webpack.config.js +80 -0
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import type { Value, Transform, Inputs } from "@eventvisor/types";
|
|
2
|
+
|
|
3
|
+
import type { Logger } from "./logger";
|
|
4
|
+
import type { ConditionsChecker } from "./conditions";
|
|
5
|
+
import type { SourceResolver } from "./sourceResolver";
|
|
6
|
+
|
|
7
|
+
export type GetTransformer = () => Transformer;
|
|
8
|
+
|
|
9
|
+
export interface TransformerOptions {
|
|
10
|
+
logger: Logger;
|
|
11
|
+
conditionsChecker: ConditionsChecker;
|
|
12
|
+
sourceResolver: SourceResolver;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class Transformer {
|
|
16
|
+
private logger: Logger;
|
|
17
|
+
private conditionsChecker: ConditionsChecker;
|
|
18
|
+
private sourceResolver: SourceResolver;
|
|
19
|
+
|
|
20
|
+
constructor(options: TransformerOptions) {
|
|
21
|
+
this.logger = options.logger;
|
|
22
|
+
this.conditionsChecker = options.conditionsChecker;
|
|
23
|
+
this.sourceResolver = options.sourceResolver;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async applyAll(value: Value, transforms: Transform[], inputs: Inputs = {}): Promise<Value> {
|
|
27
|
+
let result = value;
|
|
28
|
+
|
|
29
|
+
for (const transform of transforms) {
|
|
30
|
+
/**
|
|
31
|
+
* Conditions
|
|
32
|
+
*/
|
|
33
|
+
if (transform.conditions) {
|
|
34
|
+
const conditionMatched = await this.conditionsChecker.allAreMatched(
|
|
35
|
+
transform.conditions,
|
|
36
|
+
inputs,
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
if (!conditionMatched) {
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Source value
|
|
46
|
+
*/
|
|
47
|
+
let sourceValue = await this.sourceResolver.resolve(transform, inputs);
|
|
48
|
+
|
|
49
|
+
// when Transform has no source, but only target
|
|
50
|
+
if (sourceValue === null || sourceValue === undefined) {
|
|
51
|
+
if (transform.target) {
|
|
52
|
+
sourceValue = await this.sourceResolver.resolve(
|
|
53
|
+
{
|
|
54
|
+
payload: transform.target,
|
|
55
|
+
},
|
|
56
|
+
typeof inputs.payload === "undefined"
|
|
57
|
+
? {
|
|
58
|
+
...inputs,
|
|
59
|
+
payload: value,
|
|
60
|
+
}
|
|
61
|
+
: inputs,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Transform value
|
|
68
|
+
*/
|
|
69
|
+
// plain target
|
|
70
|
+
if (transform.target) {
|
|
71
|
+
// @TODO: target is always single string. tidy it up
|
|
72
|
+
const targets = Array.isArray(transform.target) ? transform.target : [transform.target];
|
|
73
|
+
|
|
74
|
+
for (const target of targets) {
|
|
75
|
+
// @TODO: use if/elseif below later
|
|
76
|
+
|
|
77
|
+
// string only
|
|
78
|
+
if (typeof sourceValue === "string") {
|
|
79
|
+
if (transform.type === "trim") {
|
|
80
|
+
result = Transformer.setValueAtPath(result, target, sourceValue.trim());
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// array only
|
|
85
|
+
if (Array.isArray(sourceValue)) {
|
|
86
|
+
if (transform.type === "concat") {
|
|
87
|
+
const separator = transform.separator || " ";
|
|
88
|
+
result = Transformer.setValueAtPath(result, target, sourceValue.join(separator));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// others
|
|
93
|
+
if (transform.type === "set") {
|
|
94
|
+
if ("value" in transform) {
|
|
95
|
+
result = Transformer.setValueAtPath(result, target, transform.value);
|
|
96
|
+
} else {
|
|
97
|
+
result = Transformer.setValueAtPath(result, target, sourceValue);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (transform.type === "remove") {
|
|
102
|
+
result = Transformer.removeValueAt(result, target);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// to other types
|
|
106
|
+
if (transform.type === "toInteger") {
|
|
107
|
+
result = Transformer.setValueAtPath(result, target, parseInt(String(sourceValue)));
|
|
108
|
+
} else if (transform.type === "toDouble") {
|
|
109
|
+
result = Transformer.setValueAtPath(result, target, parseFloat(String(sourceValue)));
|
|
110
|
+
} else if (transform.type === "toString") {
|
|
111
|
+
result = Transformer.setValueAtPath(result, target, String(sourceValue) || "");
|
|
112
|
+
} else if (transform.type === "toBoolean") {
|
|
113
|
+
const lowerCasedValue = String(sourceValue).toLowerCase();
|
|
114
|
+
|
|
115
|
+
result = Transformer.setValueAtPath(
|
|
116
|
+
result,
|
|
117
|
+
target,
|
|
118
|
+
["true", "1", "checked", "yes", "on", "y"].indexOf(lowerCasedValue) !== -1 ||
|
|
119
|
+
sourceValue === true,
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
// without target (meaning, self)
|
|
125
|
+
|
|
126
|
+
// set
|
|
127
|
+
if (transform.type === "set") {
|
|
128
|
+
if ("value" in transform) {
|
|
129
|
+
result = transform.value;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (transform.type === "spread") {
|
|
135
|
+
if (transform.target) {
|
|
136
|
+
const currentTargetValue = Transformer.getValueAtPath(result, transform.target);
|
|
137
|
+
result = Transformer.setValueAtPath(result, transform.target, {
|
|
138
|
+
...((currentTargetValue as object) || {}),
|
|
139
|
+
...((sourceValue as object) || {}),
|
|
140
|
+
});
|
|
141
|
+
} else {
|
|
142
|
+
result = {
|
|
143
|
+
...((result as object) || {}),
|
|
144
|
+
...((sourceValue as object) || {}),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// mathematical
|
|
150
|
+
if (transform.type === "increment") {
|
|
151
|
+
const by = typeof transform.value === "number" ? transform.value : 1;
|
|
152
|
+
|
|
153
|
+
if (transform.target) {
|
|
154
|
+
result = Transformer.setValueAtPath(result, transform.target, Number(sourceValue) + by);
|
|
155
|
+
} else {
|
|
156
|
+
result = (result as number) + by;
|
|
157
|
+
}
|
|
158
|
+
} else if (transform.type === "decrement") {
|
|
159
|
+
const by = typeof transform.value === "number" ? transform.value : 1;
|
|
160
|
+
|
|
161
|
+
if (transform.target) {
|
|
162
|
+
result = Transformer.setValueAtPath(result, transform.target, Number(sourceValue) - by);
|
|
163
|
+
} else {
|
|
164
|
+
result = (result as number) - by;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// target map
|
|
169
|
+
if (transform.targetMap) {
|
|
170
|
+
const targetMaps: Record<string, string>[] = [];
|
|
171
|
+
|
|
172
|
+
// @TODO: tidy it up
|
|
173
|
+
if (Array.isArray(transform.targetMap)) {
|
|
174
|
+
for (const targetMap of transform.targetMap) {
|
|
175
|
+
Object.entries(targetMap).forEach(([key, value]) => {
|
|
176
|
+
targetMaps.push({ [key]: value });
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
} else {
|
|
180
|
+
Object.entries(transform.targetMap).forEach(([key, value]) => {
|
|
181
|
+
targetMaps.push({ [key]: value });
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
for (const targetMap of targetMaps) {
|
|
186
|
+
// rename
|
|
187
|
+
if (transform.type === "rename") {
|
|
188
|
+
result = Transformer.renameValueAt(result, targetMap as Record<string, string>);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return result;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Helper function to rename value at path
|
|
198
|
+
public static renameValueAt(obj: any, target: Record<string, string>): any {
|
|
199
|
+
if (!obj || typeof obj !== "object") return obj;
|
|
200
|
+
if (!target || typeof target !== "object") return obj;
|
|
201
|
+
|
|
202
|
+
const entries = Object.entries(target);
|
|
203
|
+
if (entries.length === 0) return obj;
|
|
204
|
+
|
|
205
|
+
const [oldKey, newKey] = entries[0];
|
|
206
|
+
if (!oldKey || !newKey) return obj;
|
|
207
|
+
|
|
208
|
+
// Get the value at old path
|
|
209
|
+
const oldValue = Transformer.getValueAtPath(obj, oldKey);
|
|
210
|
+
if (oldValue === undefined) return obj;
|
|
211
|
+
|
|
212
|
+
// Create a copy to avoid mutating the original
|
|
213
|
+
const result = JSON.parse(JSON.stringify(obj));
|
|
214
|
+
|
|
215
|
+
// Remove old property
|
|
216
|
+
const oldKeys = oldKey.split(".");
|
|
217
|
+
let current = result;
|
|
218
|
+
|
|
219
|
+
// Navigate to parent of old property
|
|
220
|
+
for (let i = 0; i < oldKeys.length - 1; i++) {
|
|
221
|
+
const key = oldKeys[i];
|
|
222
|
+
if (current[key] === undefined) return result;
|
|
223
|
+
current = current[key];
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Remove old property
|
|
227
|
+
delete current[oldKeys[oldKeys.length - 1]];
|
|
228
|
+
|
|
229
|
+
// Set at new path
|
|
230
|
+
return Transformer.setValueAtPath(result, newKey, oldValue);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Helper function to get value at path
|
|
234
|
+
public static getValueAtPath(obj: any, path: string): any {
|
|
235
|
+
if (!path || typeof path !== "string") return undefined;
|
|
236
|
+
|
|
237
|
+
const keys = path.split(".");
|
|
238
|
+
let current = obj;
|
|
239
|
+
|
|
240
|
+
for (const key of keys) {
|
|
241
|
+
if (current === null || current === undefined) return undefined;
|
|
242
|
+
if (typeof current === "object" && !Array.isArray(current)) {
|
|
243
|
+
current = (current as any)[key];
|
|
244
|
+
} else if (Array.isArray(current) && /^\d+$/.test(key)) {
|
|
245
|
+
current = current[parseInt(key)];
|
|
246
|
+
} else {
|
|
247
|
+
return undefined;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return current;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Helper function to set value at path
|
|
255
|
+
public static setValueAtPath(obj: any, path: string, value: any): any {
|
|
256
|
+
if (!path || typeof path !== "string") return obj;
|
|
257
|
+
if (obj === null || obj === undefined) return obj;
|
|
258
|
+
|
|
259
|
+
// Create a copy to avoid mutating the original
|
|
260
|
+
const result = JSON.parse(JSON.stringify(obj));
|
|
261
|
+
const keys = path.split(".");
|
|
262
|
+
let current = result as any;
|
|
263
|
+
|
|
264
|
+
// Navigate to the parent of the target path
|
|
265
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
266
|
+
const key = keys[i];
|
|
267
|
+
if (current[key] === undefined) {
|
|
268
|
+
// Create nested object or array as needed
|
|
269
|
+
if (/^\d+$/.test(keys[i + 1])) {
|
|
270
|
+
current[key] = [];
|
|
271
|
+
} else {
|
|
272
|
+
current[key] = {};
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
current = current[key];
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Set the final value
|
|
279
|
+
const finalKey = keys[keys.length - 1];
|
|
280
|
+
current[finalKey] = value;
|
|
281
|
+
|
|
282
|
+
return result;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Helper function to remove value at path
|
|
286
|
+
public static removeValueAt(obj: any, path: string): any {
|
|
287
|
+
if (!path || typeof path !== "string") return obj;
|
|
288
|
+
if (obj === null || obj === undefined) return obj;
|
|
289
|
+
|
|
290
|
+
// Create a copy to avoid mutating the original
|
|
291
|
+
const result = JSON.parse(JSON.stringify(obj));
|
|
292
|
+
const keys = path.split(".");
|
|
293
|
+
let current = result as any;
|
|
294
|
+
|
|
295
|
+
// Navigate to the parent of the target path
|
|
296
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
297
|
+
const key = keys[i];
|
|
298
|
+
if (current[key] === undefined) {
|
|
299
|
+
return result; // Path doesn't exist, nothing to remove
|
|
300
|
+
}
|
|
301
|
+
current = current[key];
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Remove the property
|
|
305
|
+
const finalKey = keys[keys.length - 1];
|
|
306
|
+
if (Array.isArray(current)) {
|
|
307
|
+
if (/^\d+$/.test(finalKey)) {
|
|
308
|
+
current.splice(parseInt(finalKey), 1);
|
|
309
|
+
}
|
|
310
|
+
} else if (typeof current === "object" && current !== null) {
|
|
311
|
+
delete current[finalKey];
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return result;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
import { Transformer } from "./transformer";
|
|
2
|
+
|
|
3
|
+
describe("Transformer static methods", () => {
|
|
4
|
+
describe("getValueAtPath", () => {
|
|
5
|
+
const testObj = {
|
|
6
|
+
user: {
|
|
7
|
+
profile: {
|
|
8
|
+
name: "John Doe",
|
|
9
|
+
email: "john@example.com",
|
|
10
|
+
settings: {
|
|
11
|
+
theme: "dark",
|
|
12
|
+
notifications: true,
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
preferences: ["reading", "music"],
|
|
16
|
+
},
|
|
17
|
+
config: {
|
|
18
|
+
version: "1.0.0",
|
|
19
|
+
features: ["auth", "api"],
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
it("should get value at simple path", () => {
|
|
24
|
+
const result = Transformer.getValueAtPath(testObj, "user.profile.name");
|
|
25
|
+
expect(result).toBe("John Doe");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("should get value at nested path", () => {
|
|
29
|
+
const result = Transformer.getValueAtPath(testObj, "user.profile.settings.theme");
|
|
30
|
+
expect(result).toBe("dark");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("should get value at root level", () => {
|
|
34
|
+
const result = Transformer.getValueAtPath(testObj, "config");
|
|
35
|
+
expect(result).toEqual({
|
|
36
|
+
version: "1.0.0",
|
|
37
|
+
features: ["auth", "api"],
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("should get array value", () => {
|
|
42
|
+
const result = Transformer.getValueAtPath(testObj, "user.preferences");
|
|
43
|
+
expect(result).toEqual(["reading", "music"]);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("should get array element by index", () => {
|
|
47
|
+
const result = Transformer.getValueAtPath(testObj, "user.preferences.0");
|
|
48
|
+
expect(result).toBe("reading");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("should get array element by numeric string", () => {
|
|
52
|
+
const result = Transformer.getValueAtPath(testObj, "user.preferences.1");
|
|
53
|
+
expect(result).toBe("music");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("should return undefined for non-existent path", () => {
|
|
57
|
+
const result = Transformer.getValueAtPath(testObj, "user.profile.age");
|
|
58
|
+
expect(result).toBeUndefined();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("should return undefined for invalid path", () => {
|
|
62
|
+
const result = Transformer.getValueAtPath(testObj, "");
|
|
63
|
+
expect(result).toBeUndefined();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("should return undefined for null path", () => {
|
|
67
|
+
const result = Transformer.getValueAtPath(testObj, null as any);
|
|
68
|
+
expect(result).toBeUndefined();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("should return undefined for non-string path", () => {
|
|
72
|
+
const result = Transformer.getValueAtPath(testObj, 123 as any);
|
|
73
|
+
expect(result).toBeUndefined();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("should return undefined for non-existent array index", () => {
|
|
77
|
+
const result = Transformer.getValueAtPath(testObj, "user.preferences.5");
|
|
78
|
+
expect(result).toBeUndefined();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("should return undefined for invalid array index", () => {
|
|
82
|
+
const result = Transformer.getValueAtPath(testObj, "user.preferences.abc");
|
|
83
|
+
expect(result).toBeUndefined();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("should handle null object", () => {
|
|
87
|
+
const result = Transformer.getValueAtPath(null, "user.profile.name");
|
|
88
|
+
expect(result).toBeUndefined();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("should handle undefined object", () => {
|
|
92
|
+
const result = Transformer.getValueAtPath(undefined, "user.profile.name");
|
|
93
|
+
expect(result).toBeUndefined();
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe("setValueAtPath", () => {
|
|
98
|
+
let testObj: any;
|
|
99
|
+
|
|
100
|
+
beforeEach(() => {
|
|
101
|
+
testObj = {
|
|
102
|
+
user: {
|
|
103
|
+
profile: {
|
|
104
|
+
name: "John Doe",
|
|
105
|
+
email: "john@example.com",
|
|
106
|
+
},
|
|
107
|
+
preferences: ["reading", "music"],
|
|
108
|
+
},
|
|
109
|
+
config: {
|
|
110
|
+
version: "1.0.0",
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("should set value at simple path", () => {
|
|
116
|
+
const result = Transformer.setValueAtPath(testObj, "user.profile.age", 30);
|
|
117
|
+
expect(result.user.profile.age).toBe(30);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("should set value at nested path", () => {
|
|
121
|
+
const result = Transformer.setValueAtPath(testObj, "user.profile.settings.theme", "light");
|
|
122
|
+
expect(result.user.profile.settings.theme).toBe("light");
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("should set value at root level", () => {
|
|
126
|
+
const result = Transformer.setValueAtPath(testObj, "appName", "MyApp");
|
|
127
|
+
expect(result.appName).toBe("MyApp");
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("should set array element by index", () => {
|
|
131
|
+
const result = Transformer.setValueAtPath(testObj, "user.preferences.0", "gaming");
|
|
132
|
+
expect(result.user.preferences[0]).toBe("gaming");
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("should create nested objects when they don't exist", () => {
|
|
136
|
+
const result = Transformer.setValueAtPath(testObj, "user.profile.address.city", "New York");
|
|
137
|
+
expect(result.user.profile.address.city).toBe("New York");
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("should create arrays when numeric keys are expected", () => {
|
|
141
|
+
const result = Transformer.setValueAtPath(testObj, "user.scores.0", 100);
|
|
142
|
+
expect(Array.isArray(result.user.scores)).toBe(true);
|
|
143
|
+
expect(result.user.scores[0]).toBe(100);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("should handle empty path", () => {
|
|
147
|
+
const result = Transformer.setValueAtPath(testObj, "", "value");
|
|
148
|
+
expect(result).toBe(testObj);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("should handle null path", () => {
|
|
152
|
+
const result = Transformer.setValueAtPath(testObj, null as any, "value");
|
|
153
|
+
expect(result).toBe(testObj);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("should handle non-string path", () => {
|
|
157
|
+
const result = Transformer.setValueAtPath(testObj, 123 as any, "value");
|
|
158
|
+
expect(result).toBe(testObj);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("should handle null object", () => {
|
|
162
|
+
const result = Transformer.setValueAtPath(null, "user.profile.name", "Jane");
|
|
163
|
+
expect(result).toBe(null);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("should handle undefined object", () => {
|
|
167
|
+
const result = Transformer.setValueAtPath(undefined, "user.profile.name", "Jane");
|
|
168
|
+
expect(result).toBe(undefined);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("should not mutate original object", () => {
|
|
172
|
+
const original = JSON.parse(JSON.stringify(testObj));
|
|
173
|
+
Transformer.setValueAtPath(testObj, "user.profile.age", 30);
|
|
174
|
+
expect(testObj).toEqual(original);
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
describe("removeValueAt", () => {
|
|
179
|
+
let testObj: any;
|
|
180
|
+
|
|
181
|
+
beforeEach(() => {
|
|
182
|
+
testObj = {
|
|
183
|
+
user: {
|
|
184
|
+
profile: {
|
|
185
|
+
name: "John Doe",
|
|
186
|
+
email: "john@example.com",
|
|
187
|
+
settings: {
|
|
188
|
+
theme: "dark",
|
|
189
|
+
notifications: true,
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
preferences: ["reading", "music", "gaming"],
|
|
193
|
+
},
|
|
194
|
+
config: {
|
|
195
|
+
version: "1.0.0",
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it("should remove value at simple path", () => {
|
|
201
|
+
const result = Transformer.removeValueAt(testObj, "user.profile.email");
|
|
202
|
+
expect(result.user.profile.email).toBeUndefined();
|
|
203
|
+
expect(result.user.profile.name).toBe("John Doe"); // Other properties should remain
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("should remove value at nested path", () => {
|
|
207
|
+
const result = Transformer.removeValueAt(testObj, "user.profile.settings.theme");
|
|
208
|
+
expect(result.user.profile.settings.theme).toBeUndefined();
|
|
209
|
+
expect(result.user.profile.settings.notifications).toBe(true); // Other properties should remain
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it("should remove value at root level", () => {
|
|
213
|
+
const result = Transformer.removeValueAt(testObj, "config");
|
|
214
|
+
expect(result.config).toBeUndefined();
|
|
215
|
+
expect(result.user).toBeDefined(); // Other properties should remain
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it("should remove array element by index", () => {
|
|
219
|
+
const result = Transformer.removeValueAt(testObj, "user.preferences.1");
|
|
220
|
+
expect(result.user.preferences).toEqual(["reading", "gaming"]);
|
|
221
|
+
expect(result.user.preferences.length).toBe(2);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it("should handle non-existent path gracefully", () => {
|
|
225
|
+
const result = Transformer.removeValueAt(testObj, "user.profile.age");
|
|
226
|
+
expect(result).toEqual(testObj); // Should return original object unchanged
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it("should handle empty path", () => {
|
|
230
|
+
const result = Transformer.removeValueAt(testObj, "");
|
|
231
|
+
expect(result).toEqual(testObj);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it("should handle null path", () => {
|
|
235
|
+
const result = Transformer.removeValueAt(testObj, null as any);
|
|
236
|
+
expect(result).toEqual(testObj);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it("should handle non-string path", () => {
|
|
240
|
+
const result = Transformer.removeValueAt(testObj, 123 as any);
|
|
241
|
+
expect(result).toEqual(testObj);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it("should handle null object", () => {
|
|
245
|
+
const result = Transformer.removeValueAt(null, "user.profile.name");
|
|
246
|
+
expect(result).toBe(null);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it("should handle undefined object", () => {
|
|
250
|
+
const result = Transformer.removeValueAt(undefined, "user.profile.name");
|
|
251
|
+
expect(result).toBe(undefined);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it("should not mutate original object", () => {
|
|
255
|
+
const original = JSON.parse(JSON.stringify(testObj));
|
|
256
|
+
Transformer.removeValueAt(testObj, "user.profile.email");
|
|
257
|
+
expect(testObj).toEqual(original);
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
describe("renameValueAt", () => {
|
|
262
|
+
let testObj: any;
|
|
263
|
+
|
|
264
|
+
beforeEach(() => {
|
|
265
|
+
testObj = {
|
|
266
|
+
user: {
|
|
267
|
+
profile: {
|
|
268
|
+
name: "John Doe",
|
|
269
|
+
email: "john@example.com",
|
|
270
|
+
settings: {
|
|
271
|
+
theme: "dark",
|
|
272
|
+
notifications: true,
|
|
273
|
+
},
|
|
274
|
+
},
|
|
275
|
+
preferences: ["reading", "music"],
|
|
276
|
+
},
|
|
277
|
+
config: {
|
|
278
|
+
version: "1.0.0",
|
|
279
|
+
},
|
|
280
|
+
};
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it("should rename single property", () => {
|
|
284
|
+
const result = Transformer.renameValueAt(
|
|
285
|
+
{
|
|
286
|
+
name: "John Doe",
|
|
287
|
+
},
|
|
288
|
+
{ name: "fullName" },
|
|
289
|
+
);
|
|
290
|
+
expect(result.fullName).toBe("John Doe");
|
|
291
|
+
expect(result.name).toBeUndefined();
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it("should rename simple property", () => {
|
|
295
|
+
const result = Transformer.renameValueAt(testObj, {
|
|
296
|
+
"user.profile.name": "user.profile.fullName",
|
|
297
|
+
});
|
|
298
|
+
expect(result.user.profile.fullName).toBe("John Doe");
|
|
299
|
+
expect(result.user.profile.name).toBeUndefined();
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it("should rename nested property", () => {
|
|
303
|
+
const result = Transformer.renameValueAt(testObj, {
|
|
304
|
+
"user.profile.email": "user.profile.userEmail",
|
|
305
|
+
});
|
|
306
|
+
expect(result.user.profile.userEmail).toBe("john@example.com");
|
|
307
|
+
expect(result.user.profile.email).toBeUndefined();
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it("should rename to nested path", () => {
|
|
311
|
+
const result = Transformer.renameValueAt(testObj, {
|
|
312
|
+
"user.profile.name": "user.profile.fullName",
|
|
313
|
+
});
|
|
314
|
+
expect(result.user.profile.fullName).toBe("John Doe");
|
|
315
|
+
expect(result.user.profile.name).toBeUndefined();
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
it("should rename root property", () => {
|
|
319
|
+
const result = Transformer.renameValueAt(testObj, { config: "configuration" });
|
|
320
|
+
expect(result.configuration.version).toBe("1.0.0");
|
|
321
|
+
expect(result.config).toBeUndefined();
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
it("should handle non-existent source path gracefully", () => {
|
|
325
|
+
const result = Transformer.renameValueAt(testObj, { "user.profile.age": "userAge" });
|
|
326
|
+
expect(result).toEqual(testObj); // Should return original object unchanged
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it("should handle empty target object", () => {
|
|
330
|
+
const result = Transformer.renameValueAt(testObj, {});
|
|
331
|
+
expect(result).toEqual(testObj);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
it("should handle null target object", () => {
|
|
335
|
+
const result = Transformer.renameValueAt(testObj, null as any);
|
|
336
|
+
expect(result).toEqual(testObj);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
it("should handle null object", () => {
|
|
340
|
+
const result = Transformer.renameValueAt(null, { name: "fullName" });
|
|
341
|
+
expect(result).toBe(null);
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
it("should handle undefined object", () => {
|
|
345
|
+
const result = Transformer.renameValueAt(undefined, { name: "fullName" });
|
|
346
|
+
expect(result).toBe(undefined);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it("should handle non-object", () => {
|
|
350
|
+
const result = Transformer.renameValueAt("string", { name: "fullName" });
|
|
351
|
+
expect(result).toBe("string");
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
it("should not mutate original object", () => {
|
|
355
|
+
const original = JSON.parse(JSON.stringify(testObj));
|
|
356
|
+
Transformer.renameValueAt(testObj, { "user.profile.name": "user.profile.fullName" });
|
|
357
|
+
expect(testObj).toEqual(original);
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
it("should handle complex nested rename", () => {
|
|
361
|
+
const result = Transformer.renameValueAt(testObj, {
|
|
362
|
+
"user.profile.settings.theme": "user.profile.settings.colorScheme",
|
|
363
|
+
});
|
|
364
|
+
expect(result.user.profile.settings.colorScheme).toBe("dark");
|
|
365
|
+
expect(result.user.profile.settings.theme).toBeUndefined();
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
it("should handle rename with array path", () => {
|
|
369
|
+
const result = Transformer.renameValueAt(testObj, {
|
|
370
|
+
"user.preferences.0": "user.preferences.1",
|
|
371
|
+
});
|
|
372
|
+
expect(result.user.preferences[1]).toBe("reading");
|
|
373
|
+
expect(result.user.preferences.length).toBe(2); // Array length extends to accommodate index 1
|
|
374
|
+
// Note: Array manipulation during rename can be complex due to splice operations
|
|
375
|
+
});
|
|
376
|
+
});
|
|
377
|
+
});
|