@avleon/core 0.0.26 → 0.0.28
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/README.md +601 -561
- package/package.json +38 -6
- package/src/application.ts +104 -125
- package/src/authentication.ts +16 -16
- package/src/cache.ts +91 -91
- package/src/collection.test.ts +71 -0
- package/src/collection.ts +344 -254
- package/src/config.test.ts +35 -0
- package/src/config.ts +85 -42
- package/src/constants.ts +1 -1
- package/src/container.ts +54 -54
- package/src/controller.ts +125 -127
- package/src/decorators.ts +27 -27
- package/src/environment-variables.ts +53 -46
- package/src/exceptions/http-exceptions.ts +86 -86
- package/src/exceptions/index.ts +1 -1
- package/src/exceptions/system-exception.ts +35 -34
- package/src/file-storage.ts +206 -206
- package/src/helpers.ts +324 -328
- package/src/icore.ts +66 -90
- package/src/index.ts +30 -30
- package/src/interfaces/avleon-application.ts +32 -40
- package/src/logger.ts +72 -72
- package/src/map-types.ts +159 -159
- package/src/middleware.ts +119 -98
- package/src/multipart.ts +116 -116
- package/src/openapi.ts +372 -372
- package/src/params.ts +111 -111
- package/src/queue.ts +126 -126
- package/src/response.ts +74 -74
- package/src/results.ts +30 -30
- package/src/route-methods.ts +186 -186
- package/src/swagger-schema.ts +213 -213
- package/src/testing.ts +220 -220
- package/src/types/app-builder.interface.ts +18 -19
- package/src/types/application.interface.ts +7 -9
- package/src/utils/hash.ts +8 -5
- package/src/utils/index.ts +2 -2
- package/src/utils/optional-require.ts +50 -50
- package/src/validation.ts +160 -156
- package/src/validator-extend.ts +25 -25
package/src/helpers.ts
CHANGED
|
@@ -1,328 +1,324 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @copyright 2024
|
|
3
|
-
* @author Tareq Hossain
|
|
4
|
-
* @email xtrinsic96@gmail.com
|
|
5
|
-
* @url https://github.com/xtareq
|
|
6
|
-
*/
|
|
7
|
-
import { instanceToPlain, plainToInstance } from "class-transformer";
|
|
8
|
-
import { InternalErrorException } from "./exceptions";
|
|
9
|
-
import fs from "fs";
|
|
10
|
-
import container from "./container";
|
|
11
|
-
import { SystemUseError } from "./exceptions/system-exception";
|
|
12
|
-
import crypto, { UUID } from "crypto";
|
|
13
|
-
import { getMetadataStorage, validate, validateSync } from "class-validator";
|
|
14
|
-
|
|
15
|
-
export const uuid = crypto.randomUUID();
|
|
16
|
-
|
|
17
|
-
export function inject<T>(cls: new (...args: any[]) => T): T {
|
|
18
|
-
try {
|
|
19
|
-
return container.get(cls);
|
|
20
|
-
} catch (error) {
|
|
21
|
-
throw new SystemUseError(
|
|
22
|
-
|
|
23
|
-
);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export type Constructor<T = any> = new (...args: any[]) => T;
|
|
28
|
-
|
|
29
|
-
export function isConstructor(func: any): boolean {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
false,
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
.
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
) {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
.
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
const {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
acc
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
):
|
|
264
|
-
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
acc
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
let
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
target
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
)
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
let
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
return clone;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @copyright 2024
|
|
3
|
+
* @author Tareq Hossain
|
|
4
|
+
* @email xtrinsic96@gmail.com
|
|
5
|
+
* @url https://github.com/xtareq
|
|
6
|
+
*/
|
|
7
|
+
import { instanceToPlain, plainToInstance } from "class-transformer";
|
|
8
|
+
import { InternalErrorException } from "./exceptions";
|
|
9
|
+
import fs from "fs";
|
|
10
|
+
import container from "./container";
|
|
11
|
+
import { SystemUseError } from "./exceptions/system-exception";
|
|
12
|
+
import crypto, { UUID } from "crypto";
|
|
13
|
+
import { getMetadataStorage, validate, validateSync } from "class-validator";
|
|
14
|
+
|
|
15
|
+
export const uuid = crypto.randomUUID();
|
|
16
|
+
|
|
17
|
+
export function inject<T>(cls: new (...args: any[]) => T): T {
|
|
18
|
+
try {
|
|
19
|
+
return container.get(cls);
|
|
20
|
+
} catch (error) {
|
|
21
|
+
throw new SystemUseError(
|
|
22
|
+
"Not a project class. Maybe you wanna register it first.",
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type Constructor<T = any> = new (...args: any[]) => T;
|
|
28
|
+
|
|
29
|
+
export function isConstructor(func: any): boolean {
|
|
30
|
+
if (typeof func !== "function") {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (func === Function.prototype.bind || func instanceof RegExp) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (func.prototype && typeof func.prototype === "object") {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
const instance = new (func as any)();
|
|
44
|
+
return typeof instance === "object";
|
|
45
|
+
} catch (e) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function formatUrl(path: string): string {
|
|
51
|
+
if (typeof path !== "string") {
|
|
52
|
+
throw new Error("The path must be a string");
|
|
53
|
+
}
|
|
54
|
+
path = path.trim();
|
|
55
|
+
|
|
56
|
+
if (!path.startsWith("/")) {
|
|
57
|
+
path = "/" + path;
|
|
58
|
+
}
|
|
59
|
+
path = path.replace(/\/\/+/g, "/");
|
|
60
|
+
if (path.endsWith("/")) {
|
|
61
|
+
path = path.slice(0, -1);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return path;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function parsedPath(ipath: string): string {
|
|
68
|
+
return !ipath.startsWith("/") ? "/" + ipath : ipath;
|
|
69
|
+
}
|
|
70
|
+
export const isClassValidator = (target: Constructor) => {
|
|
71
|
+
try {
|
|
72
|
+
const clsval = require("class-validator");
|
|
73
|
+
const result = getMetadataStorage().getTargetValidationMetadatas(
|
|
74
|
+
target,
|
|
75
|
+
"",
|
|
76
|
+
false,
|
|
77
|
+
false,
|
|
78
|
+
);
|
|
79
|
+
return result.length > 0;
|
|
80
|
+
} catch (err: any) {
|
|
81
|
+
console.log(err);
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export interface MatchLocation {
|
|
87
|
+
line: number;
|
|
88
|
+
column: number;
|
|
89
|
+
}
|
|
90
|
+
export const getLineNumber = (
|
|
91
|
+
filePath: string,
|
|
92
|
+
rpath: string | RegExp,
|
|
93
|
+
): MatchLocation[] | null => {
|
|
94
|
+
let numbers = [];
|
|
95
|
+
try {
|
|
96
|
+
const fileContent = fs.readFileSync(filePath, "utf8");
|
|
97
|
+
const lines = fileContent.split("\n");
|
|
98
|
+
for (let i = 0; i < lines.length; i++) {
|
|
99
|
+
const match = lines[i].match(rpath);
|
|
100
|
+
|
|
101
|
+
if (match) {
|
|
102
|
+
console.log(match);
|
|
103
|
+
numbers.push({
|
|
104
|
+
line: i + 1,
|
|
105
|
+
column: match.index ?? 0,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return numbers;
|
|
111
|
+
} catch (error) {
|
|
112
|
+
return numbers;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export function normalizePath(base: string = "/", subPath: string = "/") {
|
|
117
|
+
return `/${base}/${subPath}`.replace(/\/+/g, "/").replace(/\/$/, "");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function extrctParamFromUrl(url: string) {
|
|
121
|
+
const splitPart = url
|
|
122
|
+
.split("/")
|
|
123
|
+
.filter((x) => x.startsWith(":") || x.startsWith("?:"));
|
|
124
|
+
return splitPart.map((f) => ({
|
|
125
|
+
key: f.replace(/(\?|:)/g, ""),
|
|
126
|
+
required: !f.startsWith("?:"),
|
|
127
|
+
}));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function findDuplicates(arr: string[]): string[] {
|
|
131
|
+
const seen = new Set();
|
|
132
|
+
const duplicates = new Set();
|
|
133
|
+
|
|
134
|
+
for (const str of arr) {
|
|
135
|
+
if (seen.has(str)) {
|
|
136
|
+
duplicates.add(str);
|
|
137
|
+
} else {
|
|
138
|
+
seen.add(str);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return Array.from(duplicates) as string[];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function getDataType(expectedType: any) {
|
|
146
|
+
switch (expectedType.name) {
|
|
147
|
+
case "Object":
|
|
148
|
+
if (Array.isArray(expectedType)) {
|
|
149
|
+
return "array";
|
|
150
|
+
}
|
|
151
|
+
return "object";
|
|
152
|
+
case "String":
|
|
153
|
+
return "string";
|
|
154
|
+
case "Number":
|
|
155
|
+
return "number";
|
|
156
|
+
case "Boolean":
|
|
157
|
+
return "boolean";
|
|
158
|
+
default:
|
|
159
|
+
return expectedType;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
export function isValidType(value: any, expectedType: any): boolean {
|
|
163
|
+
if (value === undefined || value === null) return true;
|
|
164
|
+
|
|
165
|
+
switch (expectedType.name) {
|
|
166
|
+
case "String":
|
|
167
|
+
return typeof value === "string";
|
|
168
|
+
case "Number":
|
|
169
|
+
return typeof value === "number" || !isNaN(Number(value));
|
|
170
|
+
case "Boolean":
|
|
171
|
+
return typeof value === "boolean";
|
|
172
|
+
default:
|
|
173
|
+
return value instanceof expectedType;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function isValidJsonString(value: string): object | boolean {
|
|
178
|
+
try {
|
|
179
|
+
return JSON.parse(value);
|
|
180
|
+
} catch (err: any) {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function jsonToJs(value: string) {
|
|
186
|
+
try {
|
|
187
|
+
return JSON.parse(value);
|
|
188
|
+
} catch (err: any) {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function jsonToInstance(value: string, instance: Constructor) {
|
|
194
|
+
try {
|
|
195
|
+
const parsedValue = JSON.parse(value);
|
|
196
|
+
return plainToInstance(instance, parsedValue);
|
|
197
|
+
} catch (err: any) {
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function transformObjectByInstanceToObject(
|
|
203
|
+
instance: Constructor,
|
|
204
|
+
value: object,
|
|
205
|
+
) {
|
|
206
|
+
return instanceToPlain(plainToInstance(instance, value), {
|
|
207
|
+
excludeExtraneousValues: true,
|
|
208
|
+
exposeUnsetFields: true,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export const isClassValidatorClass = (target: Constructor) => {
|
|
213
|
+
try {
|
|
214
|
+
const clsval = require("class-validator");
|
|
215
|
+
const result = clsval
|
|
216
|
+
.getMetadataStorage()
|
|
217
|
+
.getTargetValidationMetadatas(target, undefined, false, false);
|
|
218
|
+
return result.length > 0;
|
|
219
|
+
} catch (err: any) {
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
export async function validateObjectByInstance(
|
|
225
|
+
target: Constructor,
|
|
226
|
+
value: object = {},
|
|
227
|
+
options: "object" | "array" = "array",
|
|
228
|
+
) {
|
|
229
|
+
try {
|
|
230
|
+
const { validateOrReject } = require("class-validator");
|
|
231
|
+
const { plainToInstance } = require("class-transformer");
|
|
232
|
+
await validateOrReject(plainToInstance(target, value));
|
|
233
|
+
} catch (error: any) {
|
|
234
|
+
if (typeof error == "object" && Array.isArray(error)) {
|
|
235
|
+
const errors =
|
|
236
|
+
options == "object"
|
|
237
|
+
? error.reduce((acc: any, x: any) => {
|
|
238
|
+
//acc[x.property] = Object.values(x.constraints);
|
|
239
|
+
acc[x.property] = x.constraints;
|
|
240
|
+
return acc;
|
|
241
|
+
}, {})
|
|
242
|
+
: error.map((x) => ({
|
|
243
|
+
path: x.property,
|
|
244
|
+
constraints: x.constraints,
|
|
245
|
+
}));
|
|
246
|
+
return errors;
|
|
247
|
+
} else {
|
|
248
|
+
throw new InternalErrorException("Can't validate object");
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
type ValidationError = {
|
|
254
|
+
count: number;
|
|
255
|
+
errors: any;
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
export function validateRequestBody(
|
|
259
|
+
target: Constructor,
|
|
260
|
+
value: object,
|
|
261
|
+
options: "object" | "array" = "array",
|
|
262
|
+
): ValidationError {
|
|
263
|
+
if (!isClassValidatorClass(target)) return { count: 0, errors: {} };
|
|
264
|
+
const error = validateSync(plainToInstance(target, value ? value : {}));
|
|
265
|
+
const errors =
|
|
266
|
+
options == "object"
|
|
267
|
+
? error.reduce((acc: any, x: any) => {
|
|
268
|
+
//acc[x.property] = Object.values(x.constraints);
|
|
269
|
+
acc[x.property] = x.constraints;
|
|
270
|
+
return acc;
|
|
271
|
+
}, {})
|
|
272
|
+
: error.map((x) => ({ path: x.property, constraints: x.constraints }));
|
|
273
|
+
return { count: error.length, errors } as ValidationError;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export function pick<T extends object>(obj: T, paths: string[]): Partial<T> {
|
|
277
|
+
const result: any = {};
|
|
278
|
+
|
|
279
|
+
for (const path of paths) {
|
|
280
|
+
const keys = path.split(".");
|
|
281
|
+
let source: any = obj;
|
|
282
|
+
let target: any = result;
|
|
283
|
+
|
|
284
|
+
for (let i = 0; i < keys.length; i++) {
|
|
285
|
+
const key = keys[i];
|
|
286
|
+
|
|
287
|
+
if (!(key in source)) break;
|
|
288
|
+
|
|
289
|
+
if (i === keys.length - 1) {
|
|
290
|
+
target[key] = source[key];
|
|
291
|
+
} else {
|
|
292
|
+
source = source[key];
|
|
293
|
+
target[key] = target[key] || {};
|
|
294
|
+
target = target[key];
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return result;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export function exclude<T extends object>(
|
|
303
|
+
obj: T | T[],
|
|
304
|
+
paths: string[],
|
|
305
|
+
): Partial<T> | Partial<T>[] {
|
|
306
|
+
if (Array.isArray(obj)) {
|
|
307
|
+
return obj.map((item) => exclude(item, paths) as Partial<T>);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const clone = structuredClone(obj); // Or use lodash.cloneDeep
|
|
311
|
+
for (const path of paths) {
|
|
312
|
+
const keys = path.split(".");
|
|
313
|
+
let target: any = clone;
|
|
314
|
+
|
|
315
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
316
|
+
if (!(keys[i] in target)) break;
|
|
317
|
+
target = target[keys[i]];
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
delete target?.[keys[keys.length - 1]];
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return clone;
|
|
324
|
+
}
|