@web-ts-toolkit/access-router 0.44.0 → 0.45.0
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 +77 -2
- package/advanced.d.mts +655 -19
- package/advanced.d.ts +655 -19
- package/advanced.js +322 -5
- package/advanced.mjs +322 -5
- package/index.d.mts +2 -2
- package/index.d.ts +2 -2
- package/index.js +1448 -197
- package/index.mjs +1323 -70
- package/llms.txt +16 -0
- package/package.json +3 -3
- package/{parsers-B5dbVmjm.d.mts → parsers-DppKW-Fd.d.mts} +79 -5
- package/{parsers-B5dbVmjm.d.ts → parsers-DppKW-Fd.d.ts} +79 -5
- package/processors.d.mts +151 -1
- package/processors.d.ts +151 -1
- package/processors.js +170 -6
- package/processors.mjs +165 -6
package/processors.d.ts
CHANGED
|
@@ -47,6 +47,97 @@ interface CopyAndDepopulateOptions {
|
|
|
47
47
|
*/
|
|
48
48
|
idField?: string;
|
|
49
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* A path copy operation consumed by {@link copyPaths} and the `COPY` task.
|
|
52
|
+
* Reuses the same `{ src, dest }` shape as {@link ProcessCopy}.
|
|
53
|
+
*/
|
|
54
|
+
type ProcessPathCopy = ProcessCopy;
|
|
55
|
+
/**
|
|
56
|
+
* A path move operation consumed by {@link movePaths} and the `MOVE` task.
|
|
57
|
+
* Reuses the same `{ src, dest }` shape as {@link ProcessCopy}.
|
|
58
|
+
*/
|
|
59
|
+
type ProcessPathMove = ProcessCopy;
|
|
60
|
+
/**
|
|
61
|
+
* A slice operation consumed by {@link sliceArrays} and the `SLICE` task.
|
|
62
|
+
*/
|
|
63
|
+
interface ProcessSlice {
|
|
64
|
+
/**
|
|
65
|
+
* Dotted source path of the array to slice. Intermediate segments fan out
|
|
66
|
+
* across arrays like {@link copyAndDepopulate}. Missing, null, or
|
|
67
|
+
* non-array leaves are safe no-ops.
|
|
68
|
+
*/
|
|
69
|
+
src: string;
|
|
70
|
+
/**
|
|
71
|
+
* Dotted destination path (relative to the leaf parent) where the sliced
|
|
72
|
+
* window is written. When omitted, the slice is applied in place at `src`.
|
|
73
|
+
* Empty-string destinations are safe no-ops.
|
|
74
|
+
*/
|
|
75
|
+
dest?: string;
|
|
76
|
+
/**
|
|
77
|
+
* Number of leading array members to skip. Defaults to `0`. Must be a
|
|
78
|
+
* non-negative safe integer when present.
|
|
79
|
+
*/
|
|
80
|
+
skip?: number;
|
|
81
|
+
/**
|
|
82
|
+
* Maximum number of array members to keep from `skip`. When omitted, the
|
|
83
|
+
* tail from `skip` to the end is kept. When present it is clamped to
|
|
84
|
+
* `maxSlice` (default `1000`). Must be a non-negative safe integer.
|
|
85
|
+
*/
|
|
86
|
+
limit?: number;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* A count operation consumed by {@link countPaths} and the `COUNT` task.
|
|
90
|
+
*/
|
|
91
|
+
interface ProcessCount {
|
|
92
|
+
/**
|
|
93
|
+
* Dotted source path whose size is measured. Arrays measure length, plain
|
|
94
|
+
* objects measure key count, present scalars count as `1`, and
|
|
95
|
+
* null/undefined leaves count as `0`. Missing leaves are safe no-ops.
|
|
96
|
+
*/
|
|
97
|
+
src: string;
|
|
98
|
+
/**
|
|
99
|
+
* Dotted destination path (relative to the leaf parent) where the numeric
|
|
100
|
+
* count is written. Empty destinations are safe no-ops.
|
|
101
|
+
*/
|
|
102
|
+
dest: string;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* A mask operation consumed by {@link maskPaths} and the `MASK` task.
|
|
106
|
+
*/
|
|
107
|
+
interface ProcessMask {
|
|
108
|
+
/**
|
|
109
|
+
* Dotted source path to redact. Each resolved leaf present on its parent
|
|
110
|
+
* is replaced with `replacement`. Missing leaves are safe no-ops.
|
|
111
|
+
*/
|
|
112
|
+
src: string;
|
|
113
|
+
/**
|
|
114
|
+
* Replacement value written at `src`. Defaults to `'***'` when the key is
|
|
115
|
+
* absent. An explicitly provided value (including `null`) is used as-is.
|
|
116
|
+
*/
|
|
117
|
+
replacement?: unknown;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Shared mutable/clone option for path processors.
|
|
121
|
+
*/
|
|
122
|
+
interface ProcessorOptions {
|
|
123
|
+
/**
|
|
124
|
+
* When `true` (default), `docObject` is mutated in place and returned.
|
|
125
|
+
* When `false`, a deep clone is produced via `cloneDeep` and the input is
|
|
126
|
+
* left untouched.
|
|
127
|
+
*/
|
|
128
|
+
mutable?: boolean;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Options for {@link sliceArrays}.
|
|
132
|
+
*/
|
|
133
|
+
interface SliceProcessorOptions extends ProcessorOptions {
|
|
134
|
+
/**
|
|
135
|
+
* Upper bound applied to a client-requested `limit`. Defaults to `1000`,
|
|
136
|
+
* matching the `listHardLimit` default. Must be a non-negative safe
|
|
137
|
+
* integer when present.
|
|
138
|
+
*/
|
|
139
|
+
maxSlice?: number;
|
|
140
|
+
}
|
|
50
141
|
/**
|
|
51
142
|
* Conservative default output for {@link copyAndDepopulate}.
|
|
52
143
|
*
|
|
@@ -90,5 +181,64 @@ type CopyAndDepopulateOutput = Record<string, unknown>;
|
|
|
90
181
|
* `constructor`, or when a populated record is missing the configured id field.
|
|
91
182
|
*/
|
|
92
183
|
declare const copyAndDepopulate: <Output extends object = CopyAndDepopulateOutput>(docObject: object, operations: ProcessCopy[], options?: CopyAndDepopulateOptions) => Output;
|
|
184
|
+
/**
|
|
185
|
+
* Conservative default output for path processors.
|
|
186
|
+
*/
|
|
187
|
+
type ProcessorOutput = Record<string, unknown>;
|
|
188
|
+
/**
|
|
189
|
+
* Copy the value at `src` to `dest` without modifying `src`.
|
|
190
|
+
*
|
|
191
|
+
* Semantics mirror {@link copyAndDepopulate} traversal: intermediate segments
|
|
192
|
+
* fan out across arrays, missing/null/scalar intermediates are safe no-ops,
|
|
193
|
+
* and unsafe (`__proto__`, `prototype`, `constructor`) or empty paths are
|
|
194
|
+
* handled the same way. The copied value is deep-cloned so later tasks in
|
|
195
|
+
* the chain cannot alias `src` through `dest`. Copying a path onto itself
|
|
196
|
+
* is a safe no-op.
|
|
197
|
+
*
|
|
198
|
+
* @throws Error when a path contains an unsafe segment.
|
|
199
|
+
*/
|
|
200
|
+
declare const copyPaths: <Output extends object = ProcessorOutput>(docObject: object, operations: ProcessCopy[], options?: ProcessorOptions) => Output;
|
|
201
|
+
/**
|
|
202
|
+
* Move the value at `src` to `dest` and delete `src`.
|
|
203
|
+
*
|
|
204
|
+
* Traversal and safety semantics match {@link copyPaths}. Moving a path onto
|
|
205
|
+
* itself is a safe no-op. A `dest` nested under the moved leaf (for example
|
|
206
|
+
* `src: 'a'`, `dest: 'a.b'`) copies without deleting `src` so the freshly
|
|
207
|
+
* written destination is not removed.
|
|
208
|
+
*
|
|
209
|
+
* @throws Error when a path contains an unsafe segment.
|
|
210
|
+
*/
|
|
211
|
+
declare const movePaths: <Output extends object = ProcessorOutput>(docObject: object, operations: ProcessCopy[], options?: ProcessorOptions) => Output;
|
|
212
|
+
/**
|
|
213
|
+
* Slice the array at `src` to `[skip, skip + limit)` and write the window to
|
|
214
|
+
* `dest` (default: in place at `src`).
|
|
215
|
+
*
|
|
216
|
+
* Missing, null, or non-array leaves are safe no-ops. `skip` defaults to `0`;
|
|
217
|
+
* an omitted `limit` keeps the tail from `skip`. A present `limit` is clamped
|
|
218
|
+
* to `maxSlice` (default `1000`) so client-requested windows stay bounded.
|
|
219
|
+
*
|
|
220
|
+
* @throws Error when a path contains an unsafe segment or when
|
|
221
|
+
* `skip`/`limit`/`maxSlice` is not a non-negative safe integer.
|
|
222
|
+
*/
|
|
223
|
+
declare const sliceArrays: <Output extends object = ProcessorOutput>(docObject: object, operations: ProcessSlice[], options?: SliceProcessorOptions) => Output;
|
|
224
|
+
/**
|
|
225
|
+
* Write the size of `src` to `dest`: array length, plain-object key count,
|
|
226
|
+
* `0` for null/undefined leaves, and `1` for other present scalars.
|
|
227
|
+
*
|
|
228
|
+
* Missing leaves are safe no-ops (no destination is created).
|
|
229
|
+
*
|
|
230
|
+
* @throws Error when a path contains an unsafe segment.
|
|
231
|
+
*/
|
|
232
|
+
declare const countPaths: <Output extends object = ProcessorOutput>(docObject: object, operations: ProcessCount[], options?: ProcessorOptions) => Output;
|
|
233
|
+
/**
|
|
234
|
+
* Replace the value at `src` with `replacement` (default `'***'`).
|
|
235
|
+
*
|
|
236
|
+
* Only existing leaves are redacted; missing paths are safe no-ops and no
|
|
237
|
+
* new keys are created. Intermediate segments fan out across arrays like
|
|
238
|
+
* {@link copyAndDepopulate}.
|
|
239
|
+
*
|
|
240
|
+
* @throws Error when a path contains an unsafe segment.
|
|
241
|
+
*/
|
|
242
|
+
declare const maskPaths: <Output extends object = ProcessorOutput>(docObject: object, operations: ProcessMask[], options?: ProcessorOptions) => Output;
|
|
93
243
|
|
|
94
|
-
export { type CopyAndDepopulateOptions, type CopyAndDepopulateOutput, type ProcessCopy, copyAndDepopulate };
|
|
244
|
+
export { type CopyAndDepopulateOptions, type CopyAndDepopulateOutput, type ProcessCopy, type ProcessCount, type ProcessMask, type ProcessPathCopy, type ProcessPathMove, type ProcessSlice, type ProcessorOptions, type ProcessorOutput, type SliceProcessorOptions, copyAndDepopulate, copyPaths, countPaths, maskPaths, movePaths, sliceArrays };
|
package/processors.js
CHANGED
|
@@ -19,28 +19,68 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
19
19
|
// src/processors.ts
|
|
20
20
|
var processors_exports = {};
|
|
21
21
|
__export(processors_exports, {
|
|
22
|
-
copyAndDepopulate: () => copyAndDepopulate
|
|
22
|
+
copyAndDepopulate: () => copyAndDepopulate,
|
|
23
|
+
copyPaths: () => copyPaths,
|
|
24
|
+
countPaths: () => countPaths,
|
|
25
|
+
maskPaths: () => maskPaths,
|
|
26
|
+
movePaths: () => movePaths,
|
|
27
|
+
sliceArrays: () => sliceArrays
|
|
23
28
|
});
|
|
24
29
|
module.exports = __toCommonJS(processors_exports);
|
|
25
30
|
var import_utils = require("@web-ts-toolkit/utils");
|
|
26
31
|
var UNSAFE_PATH_KEYS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
27
|
-
|
|
32
|
+
var DEFAULT_MAX_SLICE = 1e3;
|
|
33
|
+
var DEFAULT_MASK_REPLACEMENT = "***";
|
|
34
|
+
function failUnsafeSegment(path, fn = "copyAndDepopulate") {
|
|
28
35
|
throw new Error(
|
|
29
|
-
|
|
36
|
+
`${fn}: refusing path '${path}' because it contains an unsafe segment (__proto__, prototype, or constructor).`
|
|
30
37
|
);
|
|
31
38
|
}
|
|
32
39
|
function failMissingId(path) {
|
|
33
40
|
throw new Error(`copyAndDepopulate: path '${path}' resolved to a record missing the configured id field.`);
|
|
34
41
|
}
|
|
35
|
-
function
|
|
42
|
+
function failInvalidSlice(value, field, fn) {
|
|
43
|
+
throw new Error(`${fn}: '${field}' must be a non-negative safe integer, received ${String(value)}.`);
|
|
44
|
+
}
|
|
45
|
+
function assertSafePath(path, fn = "copyAndDepopulate") {
|
|
36
46
|
const segs = path.split(".");
|
|
37
47
|
for (const seg of segs) {
|
|
38
48
|
if (seg.length === 0) continue;
|
|
39
49
|
if (UNSAFE_PATH_KEYS.has(seg)) {
|
|
40
|
-
failUnsafeSegment(path);
|
|
50
|
+
failUnsafeSegment(path, fn);
|
|
41
51
|
}
|
|
42
52
|
}
|
|
43
53
|
}
|
|
54
|
+
function assertNonNegativeSafeInteger(value, field, fn) {
|
|
55
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
|
|
56
|
+
failInvalidSlice(value, field, fn);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function resolveLeafParents(obj, src) {
|
|
60
|
+
const segs = src.split(".");
|
|
61
|
+
let targets = [obj];
|
|
62
|
+
for (let ind = 0; ind < segs.length - 1; ind += 1) {
|
|
63
|
+
const seg = segs[ind];
|
|
64
|
+
targets = targets.reduce((ret, target) => {
|
|
65
|
+
if (!(0, import_utils.isPlainObject)(target)) return ret;
|
|
66
|
+
const targetObject = target;
|
|
67
|
+
const next = targetObject[seg];
|
|
68
|
+
if ((0, import_utils.isArray)(next)) {
|
|
69
|
+
for (const item of next) {
|
|
70
|
+
ret.push(item);
|
|
71
|
+
}
|
|
72
|
+
} else if (next !== null && next !== void 0) ret.push(next);
|
|
73
|
+
return ret;
|
|
74
|
+
}, []);
|
|
75
|
+
}
|
|
76
|
+
const leaf = segs[segs.length - 1];
|
|
77
|
+
const pairs = [];
|
|
78
|
+
(0, import_utils.forEach)(targets, (target) => {
|
|
79
|
+
if (!(0, import_utils.isPlainObject)(target)) return;
|
|
80
|
+
pairs.push({ parent: target, leaf });
|
|
81
|
+
});
|
|
82
|
+
return pairs;
|
|
83
|
+
}
|
|
44
84
|
var copyAndDepopulate = (docObject, operations, options = { mutable: true, idField: "_id" }) => {
|
|
45
85
|
const mutable = options.mutable !== false;
|
|
46
86
|
const idField = options.idField ?? "_id";
|
|
@@ -104,7 +144,131 @@ var copyAndDepopulate = (docObject, operations, options = { mutable: true, idFie
|
|
|
104
144
|
});
|
|
105
145
|
return obj;
|
|
106
146
|
};
|
|
147
|
+
var copyPaths = (docObject, operations, options = { mutable: true }) => {
|
|
148
|
+
const mutable = options.mutable !== false;
|
|
149
|
+
const obj = mutable ? docObject : (0, import_utils.cloneDeep)(docObject);
|
|
150
|
+
(0, import_utils.forEach)(Array.isArray(operations) ? operations : [], (op) => {
|
|
151
|
+
if (!op || typeof op.src !== "string" || typeof op.dest !== "string") return;
|
|
152
|
+
const src = op.src;
|
|
153
|
+
const dest = op.dest;
|
|
154
|
+
if (src.length === 0 || dest.length === 0) return;
|
|
155
|
+
assertSafePath(src, "copyPaths");
|
|
156
|
+
assertSafePath(dest, "copyPaths");
|
|
157
|
+
if (src === dest) return;
|
|
158
|
+
(0, import_utils.forEach)(resolveLeafParents(obj, src), ({ parent, leaf }) => {
|
|
159
|
+
if (!(0, import_utils.hasOwn)(parent, leaf)) return;
|
|
160
|
+
(0, import_utils.set)(parent, dest, (0, import_utils.cloneDeep)(parent[leaf]));
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
return obj;
|
|
164
|
+
};
|
|
165
|
+
var movePaths = (docObject, operations, options = { mutable: true }) => {
|
|
166
|
+
const mutable = options.mutable !== false;
|
|
167
|
+
const obj = mutable ? docObject : (0, import_utils.cloneDeep)(docObject);
|
|
168
|
+
(0, import_utils.forEach)(Array.isArray(operations) ? operations : [], (op) => {
|
|
169
|
+
if (!op || typeof op.src !== "string" || typeof op.dest !== "string") return;
|
|
170
|
+
const src = op.src;
|
|
171
|
+
const dest = op.dest;
|
|
172
|
+
if (src.length === 0 || dest.length === 0) return;
|
|
173
|
+
assertSafePath(src, "movePaths");
|
|
174
|
+
assertSafePath(dest, "movePaths");
|
|
175
|
+
if (src === dest) return;
|
|
176
|
+
(0, import_utils.forEach)(resolveLeafParents(obj, src), ({ parent, leaf }) => {
|
|
177
|
+
if (!(0, import_utils.hasOwn)(parent, leaf)) return;
|
|
178
|
+
const value = (0, import_utils.cloneDeep)(parent[leaf]);
|
|
179
|
+
(0, import_utils.set)(parent, dest, value);
|
|
180
|
+
if (dest === leaf || dest.startsWith(`${leaf}.`)) return;
|
|
181
|
+
delete parent[leaf];
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
return obj;
|
|
185
|
+
};
|
|
186
|
+
var sliceArrays = (docObject, operations, options = { mutable: true, maxSlice: DEFAULT_MAX_SLICE }) => {
|
|
187
|
+
const mutable = options.mutable !== false;
|
|
188
|
+
const maxSlice = options.maxSlice ?? DEFAULT_MAX_SLICE;
|
|
189
|
+
assertNonNegativeSafeInteger(maxSlice, "maxSlice", "sliceArrays");
|
|
190
|
+
const obj = mutable ? docObject : (0, import_utils.cloneDeep)(docObject);
|
|
191
|
+
(0, import_utils.forEach)(Array.isArray(operations) ? operations : [], (op) => {
|
|
192
|
+
if (!op || typeof op.src !== "string") return;
|
|
193
|
+
const src = op.src;
|
|
194
|
+
if (src.length === 0) return;
|
|
195
|
+
assertSafePath(src, "sliceArrays");
|
|
196
|
+
const rawDest = typeof op.dest === "undefined" ? void 0 : op.dest;
|
|
197
|
+
if (typeof rawDest !== "undefined") {
|
|
198
|
+
if (typeof rawDest !== "string" || rawDest.length === 0) return;
|
|
199
|
+
assertSafePath(rawDest, "sliceArrays");
|
|
200
|
+
}
|
|
201
|
+
const skip = op.skip ?? 0;
|
|
202
|
+
assertNonNegativeSafeInteger(skip, "skip", "sliceArrays");
|
|
203
|
+
let limit;
|
|
204
|
+
if (typeof op.limit !== "undefined") {
|
|
205
|
+
assertNonNegativeSafeInteger(op.limit, "limit", "sliceArrays");
|
|
206
|
+
limit = Math.min(op.limit, maxSlice);
|
|
207
|
+
}
|
|
208
|
+
(0, import_utils.forEach)(resolveLeafParents(obj, src), ({ parent, leaf }) => {
|
|
209
|
+
const leafValue = parent[leaf];
|
|
210
|
+
if (!(0, import_utils.isArray)(leafValue)) return;
|
|
211
|
+
const end = typeof limit === "undefined" ? void 0 : skip + limit;
|
|
212
|
+
const window = leafValue.slice(skip, end);
|
|
213
|
+
if (typeof rawDest === "undefined") {
|
|
214
|
+
(0, import_utils.set)(parent, leaf, window);
|
|
215
|
+
} else {
|
|
216
|
+
(0, import_utils.set)(parent, rawDest, window);
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
return obj;
|
|
221
|
+
};
|
|
222
|
+
var countPaths = (docObject, operations, options = { mutable: true }) => {
|
|
223
|
+
const mutable = options.mutable !== false;
|
|
224
|
+
const obj = mutable ? docObject : (0, import_utils.cloneDeep)(docObject);
|
|
225
|
+
(0, import_utils.forEach)(Array.isArray(operations) ? operations : [], (op) => {
|
|
226
|
+
if (!op || typeof op.src !== "string" || typeof op.dest !== "string") return;
|
|
227
|
+
const src = op.src;
|
|
228
|
+
const dest = op.dest;
|
|
229
|
+
if (src.length === 0 || dest.length === 0) return;
|
|
230
|
+
assertSafePath(src, "countPaths");
|
|
231
|
+
assertSafePath(dest, "countPaths");
|
|
232
|
+
(0, import_utils.forEach)(resolveLeafParents(obj, src), ({ parent, leaf }) => {
|
|
233
|
+
if (!(0, import_utils.hasOwn)(parent, leaf)) return;
|
|
234
|
+
const leafValue = parent[leaf];
|
|
235
|
+
let count;
|
|
236
|
+
if ((0, import_utils.isArray)(leafValue)) {
|
|
237
|
+
count = leafValue.length;
|
|
238
|
+
} else if ((0, import_utils.isPlainObject)(leafValue)) {
|
|
239
|
+
count = Object.keys(leafValue).length;
|
|
240
|
+
} else if (leafValue === null || typeof leafValue === "undefined") {
|
|
241
|
+
count = 0;
|
|
242
|
+
} else {
|
|
243
|
+
count = 1;
|
|
244
|
+
}
|
|
245
|
+
(0, import_utils.set)(parent, dest, count);
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
return obj;
|
|
249
|
+
};
|
|
250
|
+
var maskPaths = (docObject, operations, options = { mutable: true }) => {
|
|
251
|
+
const mutable = options.mutable !== false;
|
|
252
|
+
const obj = mutable ? docObject : (0, import_utils.cloneDeep)(docObject);
|
|
253
|
+
(0, import_utils.forEach)(Array.isArray(operations) ? operations : [], (op) => {
|
|
254
|
+
if (!op || typeof op.src !== "string") return;
|
|
255
|
+
const src = op.src;
|
|
256
|
+
if (src.length === 0) return;
|
|
257
|
+
assertSafePath(src, "maskPaths");
|
|
258
|
+
const replacement = Object.prototype.hasOwnProperty.call(op, "replacement") ? op.replacement : DEFAULT_MASK_REPLACEMENT;
|
|
259
|
+
(0, import_utils.forEach)(resolveLeafParents(obj, src), ({ parent, leaf }) => {
|
|
260
|
+
if (!(0, import_utils.hasOwn)(parent, leaf)) return;
|
|
261
|
+
(0, import_utils.set)(parent, leaf, replacement);
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
return obj;
|
|
265
|
+
};
|
|
107
266
|
// Annotate the CommonJS export names for ESM import in node:
|
|
108
267
|
0 && (module.exports = {
|
|
109
|
-
copyAndDepopulate
|
|
268
|
+
copyAndDepopulate,
|
|
269
|
+
copyPaths,
|
|
270
|
+
countPaths,
|
|
271
|
+
maskPaths,
|
|
272
|
+
movePaths,
|
|
273
|
+
sliceArrays
|
|
110
274
|
});
|
package/processors.mjs
CHANGED
|
@@ -1,23 +1,58 @@
|
|
|
1
1
|
// src/processors.ts
|
|
2
|
-
import { cloneDeep, forEach, get, isArray, isPlainObject, map, set } from "@web-ts-toolkit/utils";
|
|
2
|
+
import { cloneDeep, forEach, get, hasOwn, isArray, isPlainObject, map, set } from "@web-ts-toolkit/utils";
|
|
3
3
|
var UNSAFE_PATH_KEYS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
4
|
-
|
|
4
|
+
var DEFAULT_MAX_SLICE = 1e3;
|
|
5
|
+
var DEFAULT_MASK_REPLACEMENT = "***";
|
|
6
|
+
function failUnsafeSegment(path, fn = "copyAndDepopulate") {
|
|
5
7
|
throw new Error(
|
|
6
|
-
|
|
8
|
+
`${fn}: refusing path '${path}' because it contains an unsafe segment (__proto__, prototype, or constructor).`
|
|
7
9
|
);
|
|
8
10
|
}
|
|
9
11
|
function failMissingId(path) {
|
|
10
12
|
throw new Error(`copyAndDepopulate: path '${path}' resolved to a record missing the configured id field.`);
|
|
11
13
|
}
|
|
12
|
-
function
|
|
14
|
+
function failInvalidSlice(value, field, fn) {
|
|
15
|
+
throw new Error(`${fn}: '${field}' must be a non-negative safe integer, received ${String(value)}.`);
|
|
16
|
+
}
|
|
17
|
+
function assertSafePath(path, fn = "copyAndDepopulate") {
|
|
13
18
|
const segs = path.split(".");
|
|
14
19
|
for (const seg of segs) {
|
|
15
20
|
if (seg.length === 0) continue;
|
|
16
21
|
if (UNSAFE_PATH_KEYS.has(seg)) {
|
|
17
|
-
failUnsafeSegment(path);
|
|
22
|
+
failUnsafeSegment(path, fn);
|
|
18
23
|
}
|
|
19
24
|
}
|
|
20
25
|
}
|
|
26
|
+
function assertNonNegativeSafeInteger(value, field, fn) {
|
|
27
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
|
|
28
|
+
failInvalidSlice(value, field, fn);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function resolveLeafParents(obj, src) {
|
|
32
|
+
const segs = src.split(".");
|
|
33
|
+
let targets = [obj];
|
|
34
|
+
for (let ind = 0; ind < segs.length - 1; ind += 1) {
|
|
35
|
+
const seg = segs[ind];
|
|
36
|
+
targets = targets.reduce((ret, target) => {
|
|
37
|
+
if (!isPlainObject(target)) return ret;
|
|
38
|
+
const targetObject = target;
|
|
39
|
+
const next = targetObject[seg];
|
|
40
|
+
if (isArray(next)) {
|
|
41
|
+
for (const item of next) {
|
|
42
|
+
ret.push(item);
|
|
43
|
+
}
|
|
44
|
+
} else if (next !== null && next !== void 0) ret.push(next);
|
|
45
|
+
return ret;
|
|
46
|
+
}, []);
|
|
47
|
+
}
|
|
48
|
+
const leaf = segs[segs.length - 1];
|
|
49
|
+
const pairs = [];
|
|
50
|
+
forEach(targets, (target) => {
|
|
51
|
+
if (!isPlainObject(target)) return;
|
|
52
|
+
pairs.push({ parent: target, leaf });
|
|
53
|
+
});
|
|
54
|
+
return pairs;
|
|
55
|
+
}
|
|
21
56
|
var copyAndDepopulate = (docObject, operations, options = { mutable: true, idField: "_id" }) => {
|
|
22
57
|
const mutable = options.mutable !== false;
|
|
23
58
|
const idField = options.idField ?? "_id";
|
|
@@ -81,6 +116,130 @@ var copyAndDepopulate = (docObject, operations, options = { mutable: true, idFie
|
|
|
81
116
|
});
|
|
82
117
|
return obj;
|
|
83
118
|
};
|
|
119
|
+
var copyPaths = (docObject, operations, options = { mutable: true }) => {
|
|
120
|
+
const mutable = options.mutable !== false;
|
|
121
|
+
const obj = mutable ? docObject : cloneDeep(docObject);
|
|
122
|
+
forEach(Array.isArray(operations) ? operations : [], (op) => {
|
|
123
|
+
if (!op || typeof op.src !== "string" || typeof op.dest !== "string") return;
|
|
124
|
+
const src = op.src;
|
|
125
|
+
const dest = op.dest;
|
|
126
|
+
if (src.length === 0 || dest.length === 0) return;
|
|
127
|
+
assertSafePath(src, "copyPaths");
|
|
128
|
+
assertSafePath(dest, "copyPaths");
|
|
129
|
+
if (src === dest) return;
|
|
130
|
+
forEach(resolveLeafParents(obj, src), ({ parent, leaf }) => {
|
|
131
|
+
if (!hasOwn(parent, leaf)) return;
|
|
132
|
+
set(parent, dest, cloneDeep(parent[leaf]));
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
return obj;
|
|
136
|
+
};
|
|
137
|
+
var movePaths = (docObject, operations, options = { mutable: true }) => {
|
|
138
|
+
const mutable = options.mutable !== false;
|
|
139
|
+
const obj = mutable ? docObject : cloneDeep(docObject);
|
|
140
|
+
forEach(Array.isArray(operations) ? operations : [], (op) => {
|
|
141
|
+
if (!op || typeof op.src !== "string" || typeof op.dest !== "string") return;
|
|
142
|
+
const src = op.src;
|
|
143
|
+
const dest = op.dest;
|
|
144
|
+
if (src.length === 0 || dest.length === 0) return;
|
|
145
|
+
assertSafePath(src, "movePaths");
|
|
146
|
+
assertSafePath(dest, "movePaths");
|
|
147
|
+
if (src === dest) return;
|
|
148
|
+
forEach(resolveLeafParents(obj, src), ({ parent, leaf }) => {
|
|
149
|
+
if (!hasOwn(parent, leaf)) return;
|
|
150
|
+
const value = cloneDeep(parent[leaf]);
|
|
151
|
+
set(parent, dest, value);
|
|
152
|
+
if (dest === leaf || dest.startsWith(`${leaf}.`)) return;
|
|
153
|
+
delete parent[leaf];
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
return obj;
|
|
157
|
+
};
|
|
158
|
+
var sliceArrays = (docObject, operations, options = { mutable: true, maxSlice: DEFAULT_MAX_SLICE }) => {
|
|
159
|
+
const mutable = options.mutable !== false;
|
|
160
|
+
const maxSlice = options.maxSlice ?? DEFAULT_MAX_SLICE;
|
|
161
|
+
assertNonNegativeSafeInteger(maxSlice, "maxSlice", "sliceArrays");
|
|
162
|
+
const obj = mutable ? docObject : cloneDeep(docObject);
|
|
163
|
+
forEach(Array.isArray(operations) ? operations : [], (op) => {
|
|
164
|
+
if (!op || typeof op.src !== "string") return;
|
|
165
|
+
const src = op.src;
|
|
166
|
+
if (src.length === 0) return;
|
|
167
|
+
assertSafePath(src, "sliceArrays");
|
|
168
|
+
const rawDest = typeof op.dest === "undefined" ? void 0 : op.dest;
|
|
169
|
+
if (typeof rawDest !== "undefined") {
|
|
170
|
+
if (typeof rawDest !== "string" || rawDest.length === 0) return;
|
|
171
|
+
assertSafePath(rawDest, "sliceArrays");
|
|
172
|
+
}
|
|
173
|
+
const skip = op.skip ?? 0;
|
|
174
|
+
assertNonNegativeSafeInteger(skip, "skip", "sliceArrays");
|
|
175
|
+
let limit;
|
|
176
|
+
if (typeof op.limit !== "undefined") {
|
|
177
|
+
assertNonNegativeSafeInteger(op.limit, "limit", "sliceArrays");
|
|
178
|
+
limit = Math.min(op.limit, maxSlice);
|
|
179
|
+
}
|
|
180
|
+
forEach(resolveLeafParents(obj, src), ({ parent, leaf }) => {
|
|
181
|
+
const leafValue = parent[leaf];
|
|
182
|
+
if (!isArray(leafValue)) return;
|
|
183
|
+
const end = typeof limit === "undefined" ? void 0 : skip + limit;
|
|
184
|
+
const window = leafValue.slice(skip, end);
|
|
185
|
+
if (typeof rawDest === "undefined") {
|
|
186
|
+
set(parent, leaf, window);
|
|
187
|
+
} else {
|
|
188
|
+
set(parent, rawDest, window);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
return obj;
|
|
193
|
+
};
|
|
194
|
+
var countPaths = (docObject, operations, options = { mutable: true }) => {
|
|
195
|
+
const mutable = options.mutable !== false;
|
|
196
|
+
const obj = mutable ? docObject : cloneDeep(docObject);
|
|
197
|
+
forEach(Array.isArray(operations) ? operations : [], (op) => {
|
|
198
|
+
if (!op || typeof op.src !== "string" || typeof op.dest !== "string") return;
|
|
199
|
+
const src = op.src;
|
|
200
|
+
const dest = op.dest;
|
|
201
|
+
if (src.length === 0 || dest.length === 0) return;
|
|
202
|
+
assertSafePath(src, "countPaths");
|
|
203
|
+
assertSafePath(dest, "countPaths");
|
|
204
|
+
forEach(resolveLeafParents(obj, src), ({ parent, leaf }) => {
|
|
205
|
+
if (!hasOwn(parent, leaf)) return;
|
|
206
|
+
const leafValue = parent[leaf];
|
|
207
|
+
let count;
|
|
208
|
+
if (isArray(leafValue)) {
|
|
209
|
+
count = leafValue.length;
|
|
210
|
+
} else if (isPlainObject(leafValue)) {
|
|
211
|
+
count = Object.keys(leafValue).length;
|
|
212
|
+
} else if (leafValue === null || typeof leafValue === "undefined") {
|
|
213
|
+
count = 0;
|
|
214
|
+
} else {
|
|
215
|
+
count = 1;
|
|
216
|
+
}
|
|
217
|
+
set(parent, dest, count);
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
return obj;
|
|
221
|
+
};
|
|
222
|
+
var maskPaths = (docObject, operations, options = { mutable: true }) => {
|
|
223
|
+
const mutable = options.mutable !== false;
|
|
224
|
+
const obj = mutable ? docObject : cloneDeep(docObject);
|
|
225
|
+
forEach(Array.isArray(operations) ? operations : [], (op) => {
|
|
226
|
+
if (!op || typeof op.src !== "string") return;
|
|
227
|
+
const src = op.src;
|
|
228
|
+
if (src.length === 0) return;
|
|
229
|
+
assertSafePath(src, "maskPaths");
|
|
230
|
+
const replacement = Object.prototype.hasOwnProperty.call(op, "replacement") ? op.replacement : DEFAULT_MASK_REPLACEMENT;
|
|
231
|
+
forEach(resolveLeafParents(obj, src), ({ parent, leaf }) => {
|
|
232
|
+
if (!hasOwn(parent, leaf)) return;
|
|
233
|
+
set(parent, leaf, replacement);
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
return obj;
|
|
237
|
+
};
|
|
84
238
|
export {
|
|
85
|
-
copyAndDepopulate
|
|
239
|
+
copyAndDepopulate,
|
|
240
|
+
copyPaths,
|
|
241
|
+
countPaths,
|
|
242
|
+
maskPaths,
|
|
243
|
+
movePaths,
|
|
244
|
+
sliceArrays
|
|
86
245
|
};
|