@web-ts-toolkit/access-router 0.32.0 → 0.34.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 +101 -37
- package/advanced.d.mts +12 -1444
- package/advanced.d.ts +12 -1444
- package/advanced.js +1167 -269
- package/advanced.mjs +1178 -269
- package/index.d.mts +93 -1264
- package/index.d.ts +93 -1264
- package/index.js +1465 -489
- package/index.mjs +1354 -388
- package/llms.txt +34 -11
- package/package.json +10 -5
- package/parsers-D9B9dS0p.d.mts +1702 -0
- package/parsers-D9B9dS0p.d.ts +1702 -0
- package/processors.d.mts +72 -1
- package/processors.d.ts +72 -1
- package/processors.js +61 -13
- package/processors.mjs +62 -14
package/processors.d.mts
CHANGED
|
@@ -1,11 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A depopulate copy operation consumed by {@link copyAndDepopulate}.
|
|
3
|
+
*/
|
|
1
4
|
interface ProcessCopy {
|
|
5
|
+
/**
|
|
6
|
+
* Dotted source path on `docObject` whose populated value should be
|
|
7
|
+
* relocated to {@link ProcessCopy.dest}. {@link copyAndDepopulate} walks
|
|
8
|
+
* intermediate segments and, at the leaf, copies the original objects to
|
|
9
|
+
* {@link ProcessCopy.dest} and replaces the leaf with the `idField`
|
|
10
|
+
* value(s) of those objects.
|
|
11
|
+
*
|
|
12
|
+
* Examples:
|
|
13
|
+
* `src: 'items'` -> depopulate `docObject.items`
|
|
14
|
+
* `src: 'pear.items'` -> depopulate `docObject.pear.items`
|
|
15
|
+
*
|
|
16
|
+
* Empty strings are treated as safe no-ops. Unsafe segments
|
|
17
|
+
* (`__proto__`, `prototype`, `constructor`) throw a descriptive
|
|
18
|
+
* `ProcessorPathError`.
|
|
19
|
+
*/
|
|
2
20
|
src: string;
|
|
21
|
+
/**
|
|
22
|
+
* Dotted destination path on `docObject` where the moved objects will be
|
|
23
|
+
* written. Unsafe segments (`__proto__`, `prototype`, `constructor`)
|
|
24
|
+
* throw a descriptive `ProcessorPathError`. Empty and missing
|
|
25
|
+
* destinations are safe no-ops.
|
|
26
|
+
*/
|
|
3
27
|
dest: string;
|
|
4
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Options for {@link copyAndDepopulate}.
|
|
31
|
+
*/
|
|
5
32
|
interface CopyAndDepopulateOptions {
|
|
33
|
+
/**
|
|
34
|
+
* When `true` (default), `docObject` is mutated in place and returned.
|
|
35
|
+
* When `false`, a deep clone is produced via `cloneDeep` and the input is
|
|
36
|
+
* left untouched. Both modes produce value-identical output; only object
|
|
37
|
+
* identity differs.
|
|
38
|
+
*/
|
|
6
39
|
mutable?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Identifier field pulled from each relocated record to replace the
|
|
42
|
+
* original populated value. Defaults to `'_id'`.
|
|
43
|
+
*
|
|
44
|
+
* For nested populated arrays, every leaf record must carry this field.
|
|
45
|
+
* Records missing the id field throw a descriptive `ProcessorPathError`.
|
|
46
|
+
* Primitive array members (non-record scalars) cannot be depopulated and
|
|
47
|
+
* are left in place as a safe no-op.
|
|
48
|
+
*/
|
|
7
49
|
idField?: string;
|
|
8
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Hardened processor that relocates populated documents to a destination
|
|
53
|
+
* path and replaces the original location with their proposed
|
|
54
|
+
* "depopulated" form: a single id for plain-object values, or an array of
|
|
55
|
+
* ids for populated arrays.
|
|
56
|
+
*
|
|
57
|
+
* Key semantics:
|
|
58
|
+
* - Operations run sequentially in the order supplied. Each operation
|
|
59
|
+
* sees the state produced by the previous one. Two operations that
|
|
60
|
+
* reference the same `src` therefore intentionally chain - the second
|
|
61
|
+
* operation will find the id value/type left by the first and treat it
|
|
62
|
+
* as a safe no-op if it can no longer be depopulated (e.g. it is now an
|
|
63
|
+
* array of primitive ids rather than records).
|
|
64
|
+
* - Missing, null, or scalar intermediate segments in `src` are safe
|
|
65
|
+
* no-ops: nothing is copied, nothing is replaced, and no error is
|
|
66
|
+
* thrown. The object identity of intermediates is unchanged.
|
|
67
|
+
* - An array leaf whose members are all plain records is depopulated to
|
|
68
|
+
* an array of ids. Records missing the id field throw a descriptive
|
|
69
|
+
* error; primitive/scalar array members prevent depopulation and the
|
|
70
|
+
* leaf is left in place as a safe no-op.
|
|
71
|
+
* - A plain-object leaf is depopulated to a single id value; a missing id
|
|
72
|
+
* field throws a descriptive error.
|
|
73
|
+
* - `__proto__`, `prototype`, and `constructor` segments in either `src`
|
|
74
|
+
* or `dest` throw a descriptive error. No operation can mutate
|
|
75
|
+
* `Object.prototype` or any object's prototype.
|
|
76
|
+
* - Empty `src` or `dest` strings are safe no-ops.
|
|
77
|
+
*
|
|
78
|
+
* @typeParam T - Object shape produced by the depopulation.
|
|
79
|
+
*/
|
|
9
80
|
declare const copyAndDepopulate: <T extends object>(docObject: T, operations: ProcessCopy[], options?: CopyAndDepopulateOptions) => T;
|
|
10
81
|
|
|
11
|
-
export { copyAndDepopulate };
|
|
82
|
+
export { type CopyAndDepopulateOptions, type ProcessCopy, copyAndDepopulate };
|
package/processors.d.ts
CHANGED
|
@@ -1,11 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A depopulate copy operation consumed by {@link copyAndDepopulate}.
|
|
3
|
+
*/
|
|
1
4
|
interface ProcessCopy {
|
|
5
|
+
/**
|
|
6
|
+
* Dotted source path on `docObject` whose populated value should be
|
|
7
|
+
* relocated to {@link ProcessCopy.dest}. {@link copyAndDepopulate} walks
|
|
8
|
+
* intermediate segments and, at the leaf, copies the original objects to
|
|
9
|
+
* {@link ProcessCopy.dest} and replaces the leaf with the `idField`
|
|
10
|
+
* value(s) of those objects.
|
|
11
|
+
*
|
|
12
|
+
* Examples:
|
|
13
|
+
* `src: 'items'` -> depopulate `docObject.items`
|
|
14
|
+
* `src: 'pear.items'` -> depopulate `docObject.pear.items`
|
|
15
|
+
*
|
|
16
|
+
* Empty strings are treated as safe no-ops. Unsafe segments
|
|
17
|
+
* (`__proto__`, `prototype`, `constructor`) throw a descriptive
|
|
18
|
+
* `ProcessorPathError`.
|
|
19
|
+
*/
|
|
2
20
|
src: string;
|
|
21
|
+
/**
|
|
22
|
+
* Dotted destination path on `docObject` where the moved objects will be
|
|
23
|
+
* written. Unsafe segments (`__proto__`, `prototype`, `constructor`)
|
|
24
|
+
* throw a descriptive `ProcessorPathError`. Empty and missing
|
|
25
|
+
* destinations are safe no-ops.
|
|
26
|
+
*/
|
|
3
27
|
dest: string;
|
|
4
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Options for {@link copyAndDepopulate}.
|
|
31
|
+
*/
|
|
5
32
|
interface CopyAndDepopulateOptions {
|
|
33
|
+
/**
|
|
34
|
+
* When `true` (default), `docObject` is mutated in place and returned.
|
|
35
|
+
* When `false`, a deep clone is produced via `cloneDeep` and the input is
|
|
36
|
+
* left untouched. Both modes produce value-identical output; only object
|
|
37
|
+
* identity differs.
|
|
38
|
+
*/
|
|
6
39
|
mutable?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Identifier field pulled from each relocated record to replace the
|
|
42
|
+
* original populated value. Defaults to `'_id'`.
|
|
43
|
+
*
|
|
44
|
+
* For nested populated arrays, every leaf record must carry this field.
|
|
45
|
+
* Records missing the id field throw a descriptive `ProcessorPathError`.
|
|
46
|
+
* Primitive array members (non-record scalars) cannot be depopulated and
|
|
47
|
+
* are left in place as a safe no-op.
|
|
48
|
+
*/
|
|
7
49
|
idField?: string;
|
|
8
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Hardened processor that relocates populated documents to a destination
|
|
53
|
+
* path and replaces the original location with their proposed
|
|
54
|
+
* "depopulated" form: a single id for plain-object values, or an array of
|
|
55
|
+
* ids for populated arrays.
|
|
56
|
+
*
|
|
57
|
+
* Key semantics:
|
|
58
|
+
* - Operations run sequentially in the order supplied. Each operation
|
|
59
|
+
* sees the state produced by the previous one. Two operations that
|
|
60
|
+
* reference the same `src` therefore intentionally chain - the second
|
|
61
|
+
* operation will find the id value/type left by the first and treat it
|
|
62
|
+
* as a safe no-op if it can no longer be depopulated (e.g. it is now an
|
|
63
|
+
* array of primitive ids rather than records).
|
|
64
|
+
* - Missing, null, or scalar intermediate segments in `src` are safe
|
|
65
|
+
* no-ops: nothing is copied, nothing is replaced, and no error is
|
|
66
|
+
* thrown. The object identity of intermediates is unchanged.
|
|
67
|
+
* - An array leaf whose members are all plain records is depopulated to
|
|
68
|
+
* an array of ids. Records missing the id field throw a descriptive
|
|
69
|
+
* error; primitive/scalar array members prevent depopulation and the
|
|
70
|
+
* leaf is left in place as a safe no-op.
|
|
71
|
+
* - A plain-object leaf is depopulated to a single id value; a missing id
|
|
72
|
+
* field throws a descriptive error.
|
|
73
|
+
* - `__proto__`, `prototype`, and `constructor` segments in either `src`
|
|
74
|
+
* or `dest` throw a descriptive error. No operation can mutate
|
|
75
|
+
* `Object.prototype` or any object's prototype.
|
|
76
|
+
* - Empty `src` or `dest` strings are safe no-ops.
|
|
77
|
+
*
|
|
78
|
+
* @typeParam T - Object shape produced by the depopulation.
|
|
79
|
+
*/
|
|
9
80
|
declare const copyAndDepopulate: <T extends object>(docObject: T, operations: ProcessCopy[], options?: CopyAndDepopulateOptions) => T;
|
|
10
81
|
|
|
11
|
-
export { copyAndDepopulate };
|
|
82
|
+
export { type CopyAndDepopulateOptions, type ProcessCopy, copyAndDepopulate };
|
package/processors.js
CHANGED
|
@@ -23,29 +23,77 @@ __export(processors_exports, {
|
|
|
23
23
|
});
|
|
24
24
|
module.exports = __toCommonJS(processors_exports);
|
|
25
25
|
var import_utils = require("@web-ts-toolkit/utils");
|
|
26
|
+
var UNSAFE_PATH_KEYS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
27
|
+
function failUnsafeSegment(path) {
|
|
28
|
+
throw new Error(
|
|
29
|
+
`copyAndDepopulate: refusing path '${path}' because it contains an unsafe segment (__proto__, prototype, or constructor).`
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
function failMissingId(path) {
|
|
33
|
+
throw new Error(`copyAndDepopulate: path '${path}' resolved to a record missing the configured id field.`);
|
|
34
|
+
}
|
|
35
|
+
function assertSafePath(path) {
|
|
36
|
+
const segs = path.split(".");
|
|
37
|
+
for (const seg of segs) {
|
|
38
|
+
if (seg.length === 0) continue;
|
|
39
|
+
if (UNSAFE_PATH_KEYS.has(seg)) {
|
|
40
|
+
failUnsafeSegment(path);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
26
44
|
var copyAndDepopulate = (docObject, operations, options = { mutable: true, idField: "_id" }) => {
|
|
27
|
-
const
|
|
28
|
-
const idField =
|
|
29
|
-
|
|
30
|
-
|
|
45
|
+
const mutable = options.mutable !== false;
|
|
46
|
+
const idField = options.idField ?? "_id";
|
|
47
|
+
const obj = mutable ? docObject : (0, import_utils.cloneDeep)(docObject);
|
|
48
|
+
(0, import_utils.forEach)(Array.isArray(operations) ? operations : [], (op) => {
|
|
49
|
+
if (!op || typeof op.src !== "string" || typeof op.dest !== "string") return;
|
|
50
|
+
const src = op.src;
|
|
51
|
+
const dest = op.dest;
|
|
52
|
+
if (src.length === 0 || dest.length === 0) return;
|
|
53
|
+
assertSafePath(src);
|
|
54
|
+
assertSafePath(dest);
|
|
31
55
|
let targets = [obj];
|
|
32
|
-
const segs =
|
|
56
|
+
const segs = src.split(".");
|
|
33
57
|
(0, import_utils.forEach)(segs, (seg, ind) => {
|
|
34
58
|
if (segs.length === ind + 1) {
|
|
35
59
|
(0, import_utils.forEach)(targets, (target) => {
|
|
60
|
+
if (!(0, import_utils.isPlainObject)(target)) return;
|
|
36
61
|
const targetObject = target;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
62
|
+
const leaf = targetObject[seg];
|
|
63
|
+
const sourceForDest = leaf;
|
|
64
|
+
let depopulated;
|
|
65
|
+
if ((0, import_utils.isArray)(leaf)) {
|
|
66
|
+
if (leaf.length === 0) {
|
|
67
|
+
depopulated = [];
|
|
68
|
+
} else if (leaf.every((item) => (0, import_utils.isPlainObject)(item))) {
|
|
69
|
+
depopulated = (0, import_utils.map)(leaf, idField).map((id, i) => {
|
|
70
|
+
if (id === void 0) {
|
|
71
|
+
failMissingId(`${src}[${i}]`);
|
|
72
|
+
}
|
|
73
|
+
return id;
|
|
74
|
+
});
|
|
75
|
+
} else {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
} else if ((0, import_utils.isPlainObject)(leaf)) {
|
|
79
|
+
const id = (0, import_utils.get)(leaf, idField);
|
|
80
|
+
if (id === void 0) {
|
|
81
|
+
failMissingId(src);
|
|
82
|
+
}
|
|
83
|
+
depopulated = id;
|
|
84
|
+
} else {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
(0, import_utils.set)(targetObject, dest, sourceForDest);
|
|
88
|
+
(0, import_utils.set)(targetObject, seg, depopulated);
|
|
43
89
|
});
|
|
44
90
|
} else {
|
|
45
91
|
targets = targets.reduce((ret, target) => {
|
|
92
|
+
if (!(0, import_utils.isPlainObject)(target)) return ret;
|
|
46
93
|
const targetObject = target;
|
|
47
|
-
|
|
48
|
-
|
|
94
|
+
const next = targetObject[seg];
|
|
95
|
+
if ((0, import_utils.isArray)(next)) ret.push(...next);
|
|
96
|
+
else if (next !== null && next !== void 0) ret.push(next);
|
|
49
97
|
return ret;
|
|
50
98
|
}, []);
|
|
51
99
|
}
|
package/processors.mjs
CHANGED
|
@@ -1,28 +1,76 @@
|
|
|
1
1
|
// src/processors.ts
|
|
2
|
-
import {
|
|
2
|
+
import { cloneDeep, forEach, get, isArray, isPlainObject, map, set } from "@web-ts-toolkit/utils";
|
|
3
|
+
var UNSAFE_PATH_KEYS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
4
|
+
function failUnsafeSegment(path) {
|
|
5
|
+
throw new Error(
|
|
6
|
+
`copyAndDepopulate: refusing path '${path}' because it contains an unsafe segment (__proto__, prototype, or constructor).`
|
|
7
|
+
);
|
|
8
|
+
}
|
|
9
|
+
function failMissingId(path) {
|
|
10
|
+
throw new Error(`copyAndDepopulate: path '${path}' resolved to a record missing the configured id field.`);
|
|
11
|
+
}
|
|
12
|
+
function assertSafePath(path) {
|
|
13
|
+
const segs = path.split(".");
|
|
14
|
+
for (const seg of segs) {
|
|
15
|
+
if (seg.length === 0) continue;
|
|
16
|
+
if (UNSAFE_PATH_KEYS.has(seg)) {
|
|
17
|
+
failUnsafeSegment(path);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
3
21
|
var copyAndDepopulate = (docObject, operations, options = { mutable: true, idField: "_id" }) => {
|
|
4
|
-
const
|
|
5
|
-
const idField =
|
|
6
|
-
|
|
7
|
-
|
|
22
|
+
const mutable = options.mutable !== false;
|
|
23
|
+
const idField = options.idField ?? "_id";
|
|
24
|
+
const obj = mutable ? docObject : cloneDeep(docObject);
|
|
25
|
+
forEach(Array.isArray(operations) ? operations : [], (op) => {
|
|
26
|
+
if (!op || typeof op.src !== "string" || typeof op.dest !== "string") return;
|
|
27
|
+
const src = op.src;
|
|
28
|
+
const dest = op.dest;
|
|
29
|
+
if (src.length === 0 || dest.length === 0) return;
|
|
30
|
+
assertSafePath(src);
|
|
31
|
+
assertSafePath(dest);
|
|
8
32
|
let targets = [obj];
|
|
9
|
-
const segs =
|
|
33
|
+
const segs = src.split(".");
|
|
10
34
|
forEach(segs, (seg, ind) => {
|
|
11
35
|
if (segs.length === ind + 1) {
|
|
12
36
|
forEach(targets, (target) => {
|
|
37
|
+
if (!isPlainObject(target)) return;
|
|
13
38
|
const targetObject = target;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
39
|
+
const leaf = targetObject[seg];
|
|
40
|
+
const sourceForDest = leaf;
|
|
41
|
+
let depopulated;
|
|
42
|
+
if (isArray(leaf)) {
|
|
43
|
+
if (leaf.length === 0) {
|
|
44
|
+
depopulated = [];
|
|
45
|
+
} else if (leaf.every((item) => isPlainObject(item))) {
|
|
46
|
+
depopulated = map(leaf, idField).map((id, i) => {
|
|
47
|
+
if (id === void 0) {
|
|
48
|
+
failMissingId(`${src}[${i}]`);
|
|
49
|
+
}
|
|
50
|
+
return id;
|
|
51
|
+
});
|
|
52
|
+
} else {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
} else if (isPlainObject(leaf)) {
|
|
56
|
+
const id = get(leaf, idField);
|
|
57
|
+
if (id === void 0) {
|
|
58
|
+
failMissingId(src);
|
|
59
|
+
}
|
|
60
|
+
depopulated = id;
|
|
61
|
+
} else {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
set(targetObject, dest, sourceForDest);
|
|
65
|
+
set(targetObject, seg, depopulated);
|
|
20
66
|
});
|
|
21
67
|
} else {
|
|
22
68
|
targets = targets.reduce((ret, target) => {
|
|
69
|
+
if (!isPlainObject(target)) return ret;
|
|
23
70
|
const targetObject = target;
|
|
24
|
-
|
|
25
|
-
|
|
71
|
+
const next = targetObject[seg];
|
|
72
|
+
if (isArray(next)) ret.push(...next);
|
|
73
|
+
else if (next !== null && next !== void 0) ret.push(next);
|
|
26
74
|
return ret;
|
|
27
75
|
}, []);
|
|
28
76
|
}
|