assign-gingerly 0.0.54 → 0.0.55
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 +143 -2
- package/assignFrom.js +37 -18
- package/assignFrom.ts +62 -3
- package/assignGingerly.js +34 -1
- package/assignGingerly.ts +54 -1
- package/beVigilant.js +73 -0
- package/beVigilant.ts +85 -0
- package/enhanceAll.js +106 -0
- package/enhanceAll.ts +138 -0
- package/handlers/join.js +74 -0
- package/handlers/join.ts +80 -0
- package/handlers/lazyLoad.js +212 -0
- package/handlers/lazyLoad.ts +307 -0
- package/handlers/lazyLoadSwitch.js +58 -0
- package/handlers/lazyLoadSwitch.ts +63 -0
- package/handlers/microDataJoin.js +184 -0
- package/handlers/microDataJoin.ts +270 -0
- package/inferencer/.gitmodules +3 -0
- package/inferencer/.vscode/settings.json +2 -0
- package/inferencer/InferencedPropagator.js +230 -0
- package/inferencer/InferencedPropagator.ts +269 -0
- package/inferencer/LICENSE +21 -0
- package/inferencer/README.md +524 -0
- package/inferencer/Requirements/SupportForPropagator.md +368 -0
- package/inferencer/imports.html +7 -0
- package/inferencer/inferencer.js +254 -0
- package/inferencer/inferencer.ts +292 -0
- package/inferencer/package-lock.json +129 -0
- package/inferencer/package.json +60 -0
- package/inferencer/playwright-report/data/507ad515125e13390ea07de92f22331c913fa068.md +55 -0
- package/inferencer/playwright-report/index.html +90 -0
- package/inferencer/playwright.config.ts +54 -0
- package/inferencer/test-results/.last-run.json +6 -0
- package/inferencer/test-results/inferencer-Inferencer-Enha-535bc-inferencer-tests-in-browser-chromium/error-context.md +55 -0
- package/inferencer/tests/inferencedPropagator.html +428 -0
- package/inferencer/tests/inferencedPropagator.spec.ts +18 -0
- package/inferencer/tests/inferencer.html +355 -0
- package/inferencer/tests/inferencer.spec.ts +19 -0
- package/inferencer/tsconfig.json +19 -0
- package/inferencer/types/.kiro/specs/conversion-template/README.md +128 -0
- package/inferencer/types/.kiro/specs/conversion-template/design.md +360 -0
- package/inferencer/types/.kiro/specs/conversion-template/requirements.md +191 -0
- package/inferencer/types/.kiro/specs/conversion-template/tasks.md +174 -0
- package/inferencer/types/.kiro/steering/coding-standards.md +53 -0
- package/inferencer/types/.kiro/steering/conversion-guide.md +108 -0
- package/inferencer/types/.kiro/steering/declarative-configuration.md +108 -0
- package/inferencer/types/.kiro/steering/emc-json-serializability.md +306 -0
- package/inferencer/types/EnhancementConversionInstructions.md +1854 -0
- package/inferencer/types/LICENSE +21 -0
- package/inferencer/types/NewCustomElement.md +388 -0
- package/inferencer/types/NewCustomElementFeature.md +683 -0
- package/inferencer/types/NewEnhancementInstructions.md +705 -0
- package/inferencer/types/README.md +2 -0
- package/inferencer/types/agrace/types.d.ts +11 -0
- package/inferencer/types/assign-gingerly/types.d.ts +572 -0
- package/inferencer/types/be-a-beacon/types.d.ts +17 -0
- package/inferencer/types/be-bound/types.d.ts +66 -0
- package/inferencer/types/be-buttoned-up/types.d.ts +19 -0
- package/inferencer/types/be-calculating/types.d.ts +54 -0
- package/inferencer/types/be-clonable/types.d.ts +38 -0
- package/inferencer/types/be-committed/types.d.ts +22 -0
- package/inferencer/types/be-consoling/types.d.ts +24 -0
- package/inferencer/types/be-decked-with/types.d.ts +26 -0
- package/inferencer/types/be-delible/types.d.ts +27 -0
- package/inferencer/types/be-dispatching/types.d.ts +34 -0
- package/inferencer/types/be-evanescent/types.d.ts +20 -0
- package/inferencer/types/be-flashy/types.d.ts +21 -0
- package/inferencer/types/be-gone/types.d.ts +25 -0
- package/inferencer/types/be-observing/types.d.ts +55 -0
- package/inferencer/types/be-reflective/types.d.ts +78 -0
- package/inferencer/types/be-reformable/types.d.ts +49 -0
- package/inferencer/types/be-render-neutral/types.d.ts +32 -0
- package/inferencer/types/be-switched/types.d.ts +146 -0
- package/inferencer/types/be-typed/types.d.ts +32 -0
- package/inferencer/types/be-valued/types.d.ts +22 -0
- package/inferencer/types/data-props/types.d.ts +34 -0
- package/inferencer/types/do-inc/types.d.ts +56 -0
- package/inferencer/types/do-invoke/types.d.ts +38 -0
- package/inferencer/types/do-merge/types.d.ts +28 -0
- package/inferencer/types/do-toggle/types.d.ts +31 -0
- package/inferencer/types/face-up/types.d.ts +100 -0
- package/inferencer/types/fetch-for/types.d.ts +36 -0
- package/inferencer/types/folder-picker/types.d.ts +21 -0
- package/inferencer/types/global.d.ts +29 -0
- package/inferencer/types/id-generation/types.d.ts +26 -0
- package/inferencer/types/inferencer/types.d.ts +46 -0
- package/inferencer/types/mount-observer/types.d.ts +363 -0
- package/inferencer/types/nested-regex-groups/types.d.ts +107 -0
- package/inferencer/types/pipe-in/types.d.ts +52 -0
- package/inferencer/types/roundabout/types.d.ts +268 -0
- package/inferencer/types/soak-up/types.d.ts +40 -0
- package/inferencer/types/templ-maker/types.d.ts +43 -0
- package/inferencer/types/time-ticker/types.d.ts +62 -0
- package/inferencer/types/truth-sourcer/types.d.ts +44 -0
- package/inferencer/upSearch.js +27 -0
- package/inferencer/upSearch.ts +26 -0
- package/inferencer/withScopePerimeter.js +27 -0
- package/inferencer/withScopePerimeter.ts +33 -0
- package/inferredAssignments.js +38 -0
- package/inferredAssignments.ts +65 -0
- package/isAllowedImportPath.js +42 -0
- package/isAllowedImportPath.ts +53 -0
- package/package.json +27 -1
- package/paths.js +49 -1
- package/paths.ts +80 -1
- package/processHandlerCommands.js +19 -19
- package/processHandlerCommands.ts +10 -13
- package/resolveIdRef.js +144 -125
- package/resolveIdRef.ts +30 -0
- package/types/assign-gingerly/types.d.ts +12 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* isAllowedImportPath.ts — Security utility for validating import paths.
|
|
3
|
+
*
|
|
4
|
+
* Checks that a path is local (relative, absolute, or bare specifier) and
|
|
5
|
+
* not a cross-domain URL. Used to prevent untrusted HTML attributes from
|
|
6
|
+
* triggering imports to arbitrary external domains.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* import { isAllowedImportPath } from 'assign-gingerly/isAllowedImportPath.js';
|
|
10
|
+
*
|
|
11
|
+
* isAllowedImportPath('./local.js'); // true
|
|
12
|
+
* isAllowedImportPath('../parent/file.js'); // true
|
|
13
|
+
* isAllowedImportPath('/absolute/path.js'); // true
|
|
14
|
+
* isAllowedImportPath('bare-specifier/mod.js'); // true
|
|
15
|
+
* isAllowedImportPath('https://evil.com/x.js'); // false
|
|
16
|
+
* isAllowedImportPath('//cdn.example.com/x.js'); // false
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Check if an import path is allowed (non-cross-domain).
|
|
20
|
+
*
|
|
21
|
+
* Allowed:
|
|
22
|
+
* - Relative paths: ./foo.js, ../bar.js
|
|
23
|
+
* - Absolute paths: /path/to/file.js
|
|
24
|
+
* - Bare specifiers: package-name/file.js, @scope/package/file.js
|
|
25
|
+
*
|
|
26
|
+
* Blocked:
|
|
27
|
+
* - Protocol URLs: https://..., http://..., data:..., etc.
|
|
28
|
+
* - Protocol-relative: //cdn.example.com/...
|
|
29
|
+
*
|
|
30
|
+
* @param path - The import path to validate
|
|
31
|
+
* @returns true if the path is local/safe, false if cross-domain
|
|
32
|
+
*/
|
|
33
|
+
export function isAllowedImportPath(path) {
|
|
34
|
+
if (path.startsWith('./') || path.startsWith('../') || path.startsWith('/')) {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
if (path.includes('://') || path.startsWith('//')) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
// Bare specifier (no protocol, no //) — allowed
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* isAllowedImportPath.ts — Security utility for validating import paths.
|
|
3
|
+
*
|
|
4
|
+
* Checks that a path is local (relative, absolute, or bare specifier) and
|
|
5
|
+
* not a cross-domain URL. Used to prevent untrusted HTML attributes from
|
|
6
|
+
* triggering imports to arbitrary external domains.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* import { isAllowedImportPath } from 'assign-gingerly/isAllowedImportPath.js';
|
|
10
|
+
*
|
|
11
|
+
* isAllowedImportPath('./local.js'); // true
|
|
12
|
+
* isAllowedImportPath('../parent/file.js'); // true
|
|
13
|
+
* isAllowedImportPath('/absolute/path.js'); // true
|
|
14
|
+
* isAllowedImportPath('bare-specifier/mod.js'); // true
|
|
15
|
+
* isAllowedImportPath('https://evil.com/x.js'); // false
|
|
16
|
+
* isAllowedImportPath('//cdn.example.com/x.js'); // false
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Permissions interface for controlling security-sensitive operations.
|
|
21
|
+
* Passed as the last parameter to assignGingerly, assignFrom, and enhanceAll.
|
|
22
|
+
* Only trusted script can set these — they are never parsed from HTML attributes.
|
|
23
|
+
*/
|
|
24
|
+
export interface AssignPermissions {
|
|
25
|
+
/** Allow imports from cross-domain URLs (default: false) */
|
|
26
|
+
crossDomainImports?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Check if an import path is allowed (non-cross-domain).
|
|
31
|
+
*
|
|
32
|
+
* Allowed:
|
|
33
|
+
* - Relative paths: ./foo.js, ../bar.js
|
|
34
|
+
* - Absolute paths: /path/to/file.js
|
|
35
|
+
* - Bare specifiers: package-name/file.js, @scope/package/file.js
|
|
36
|
+
*
|
|
37
|
+
* Blocked:
|
|
38
|
+
* - Protocol URLs: https://..., http://..., data:..., etc.
|
|
39
|
+
* - Protocol-relative: //cdn.example.com/...
|
|
40
|
+
*
|
|
41
|
+
* @param path - The import path to validate
|
|
42
|
+
* @returns true if the path is local/safe, false if cross-domain
|
|
43
|
+
*/
|
|
44
|
+
export function isAllowedImportPath(path: string): boolean {
|
|
45
|
+
if (path.startsWith('./') || path.startsWith('../') || path.startsWith('/')) {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
if (path.includes('://') || path.startsWith('//')) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
// Bare specifier (no protocol, no //) — allowed
|
|
52
|
+
return true;
|
|
53
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "assign-gingerly",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.55",
|
|
4
4
|
"description": "This package provides a utility function for carefully merging one object into another.",
|
|
5
5
|
"homepage": "https://github.com/bahrus/assign-gingerly#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
"files": [
|
|
18
18
|
"*.js",
|
|
19
19
|
"*.ts",
|
|
20
|
+
"handlers/**",
|
|
21
|
+
"inferencer/**",
|
|
20
22
|
"README.md",
|
|
21
23
|
"LICENSE",
|
|
22
24
|
"types/assign-gingerly/types.d.ts"
|
|
@@ -100,6 +102,30 @@
|
|
|
100
102
|
"default": "./paths.js",
|
|
101
103
|
"types": "./paths.ts"
|
|
102
104
|
},
|
|
105
|
+
"./enhanceAll.js": {
|
|
106
|
+
"default": "./enhanceAll.js",
|
|
107
|
+
"types": "./enhanceAll.ts"
|
|
108
|
+
},
|
|
109
|
+
"./isAllowedImportPath.js": {
|
|
110
|
+
"default": "./isAllowedImportPath.js",
|
|
111
|
+
"types": "./isAllowedImportPath.ts"
|
|
112
|
+
},
|
|
113
|
+
"./inferredAssignments.js": {
|
|
114
|
+
"default": "./inferredAssignments.js",
|
|
115
|
+
"types": "./inferredAssignments.ts"
|
|
116
|
+
},
|
|
117
|
+
"./beVigilant.js": {
|
|
118
|
+
"default": "./beVigilant.js",
|
|
119
|
+
"types": "./beVigilant.ts"
|
|
120
|
+
},
|
|
121
|
+
"./inferencer/inferencer.js": {
|
|
122
|
+
"default": "./inferencer/inferencer.js",
|
|
123
|
+
"types": "./inferencer/inferencer.ts"
|
|
124
|
+
},
|
|
125
|
+
"./inferencer/withScopePerimeter.js": {
|
|
126
|
+
"default": "./inferencer/withScopePerimeter.js",
|
|
127
|
+
"types": "./inferencer/withScopePerimeter.ts"
|
|
128
|
+
},
|
|
103
129
|
"./assignFrom.js": {
|
|
104
130
|
"default": "./assignFrom.js",
|
|
105
131
|
"types": "./assignFrom.ts"
|
package/paths.js
CHANGED
|
@@ -10,9 +10,41 @@
|
|
|
10
10
|
*/
|
|
11
11
|
const PATH_SYMBOL = Symbol('assign-gingerly-path');
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Create a proxy for id-ref paths (#[varName]).
|
|
15
|
+
*/
|
|
16
|
+
function createIdRefProxy(idRef, options) {
|
|
17
|
+
function handler() {}
|
|
18
|
+
return new Proxy(handler, {
|
|
19
|
+
get(_, prop) {
|
|
20
|
+
if (prop === 'path' || prop === PATH_SYMBOL) {
|
|
21
|
+
return idRef;
|
|
22
|
+
}
|
|
23
|
+
if (typeof prop === 'symbol') return undefined;
|
|
24
|
+
const chained = `${idRef}?.${String(prop)}`;
|
|
25
|
+
return createIdRefProxy(chained, options);
|
|
26
|
+
},
|
|
27
|
+
apply(_, __, args) {
|
|
28
|
+
if (args.length > 0) {
|
|
29
|
+
const arg = args[0];
|
|
30
|
+
let argStr;
|
|
31
|
+
if (arg === true) argStr = 'true';
|
|
32
|
+
else if (arg === false) argStr = 'false';
|
|
33
|
+
else if (arg && typeof arg === 'object' && PATH_SYMBOL in arg) {
|
|
34
|
+
const fullPath = arg[PATH_SYMBOL];
|
|
35
|
+
argStr = fullPath.startsWith('?.') ? fullPath.substring(2) : fullPath;
|
|
36
|
+
}
|
|
37
|
+
else argStr = String(arg);
|
|
38
|
+
const chained = `${idRef}?.${argStr}`;
|
|
39
|
+
return createIdRefProxy(chained, options);
|
|
40
|
+
}
|
|
41
|
+
return createIdRefProxy(idRef, options);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
13
46
|
/**
|
|
14
47
|
* Create a recursive proxy that records property access paths.
|
|
15
|
-
* Supports both property access and method call syntax (via apply trap on function target).
|
|
16
48
|
*/
|
|
17
49
|
function createPathProxy(prefix, options) {
|
|
18
50
|
const aliasMap = options?.aka;
|
|
@@ -27,6 +59,14 @@ function createPathProxy(prefix, options) {
|
|
|
27
59
|
if (typeof prop === 'symbol') return undefined;
|
|
28
60
|
|
|
29
61
|
let segment = String(prop);
|
|
62
|
+
|
|
63
|
+
// #-prefix: $['#firstName'] → '#[firstName]' (cached element ref)
|
|
64
|
+
if (segment.startsWith('#')) {
|
|
65
|
+
const varName = segment.substring(1);
|
|
66
|
+
const idRef = `#[${varName}]`;
|
|
67
|
+
return createIdRefProxy(idRef, options);
|
|
68
|
+
}
|
|
69
|
+
|
|
30
70
|
if (aliasMap) {
|
|
31
71
|
for (const [alias, target] of Object.entries(aliasMap)) {
|
|
32
72
|
if (target === segment) { segment = alias; break; }
|
|
@@ -111,6 +151,14 @@ export function doAssign(...pairs) {
|
|
|
111
151
|
return { assign: Object.assign({}, ...pairs) };
|
|
112
152
|
}
|
|
113
153
|
|
|
154
|
+
/**
|
|
155
|
+
* Compile-time loop expansion: generates one entry per key from a factory function.
|
|
156
|
+
*/
|
|
157
|
+
export function forEachKeyIn(keys, factory, options) {
|
|
158
|
+
const $ = paths(options);
|
|
159
|
+
return keys.map(key => factory(key, $));
|
|
160
|
+
}
|
|
161
|
+
|
|
114
162
|
/**
|
|
115
163
|
* Tagged template literal that splits a template into an array of parts.
|
|
116
164
|
* Path proxy objects are auto-detected and converted to path strings.
|
package/paths.ts
CHANGED
|
@@ -53,6 +53,44 @@ export interface PathsOptions {
|
|
|
53
53
|
withMethods?: string[] | Set<string>;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Create a proxy for id-ref paths (#[varName]).
|
|
58
|
+
* After the initial #[varName], further property access chains with ?. from the resolved element.
|
|
59
|
+
* .path returns the #[varName] prefix (optionally with further ?. path).
|
|
60
|
+
*/
|
|
61
|
+
function createIdRefProxy(idRef: string, options?: PathsOptions): any {
|
|
62
|
+
function handler() {}
|
|
63
|
+
return new Proxy(handler, {
|
|
64
|
+
get(_, prop: string | symbol) {
|
|
65
|
+
if (prop === 'path' || prop === PATH_SYMBOL) {
|
|
66
|
+
return idRef;
|
|
67
|
+
}
|
|
68
|
+
if (typeof prop === 'symbol') return undefined;
|
|
69
|
+
|
|
70
|
+
// Chain further path segments after the id ref
|
|
71
|
+
const chained = `${idRef}?.${String(prop)}`;
|
|
72
|
+
return createIdRefProxy(chained, options);
|
|
73
|
+
},
|
|
74
|
+
apply(_, __, args) {
|
|
75
|
+
if (args.length > 0) {
|
|
76
|
+
const arg = args[0];
|
|
77
|
+
let argStr: string;
|
|
78
|
+
if (arg === true) argStr = 'true';
|
|
79
|
+
else if (arg === false) argStr = 'false';
|
|
80
|
+
else if (arg && typeof arg === 'object' && PATH_SYMBOL in arg) {
|
|
81
|
+
const fullPath = arg[PATH_SYMBOL] as string;
|
|
82
|
+
argStr = fullPath.startsWith('?.') ? fullPath.substring(2) : fullPath;
|
|
83
|
+
}
|
|
84
|
+
else argStr = String(arg);
|
|
85
|
+
|
|
86
|
+
const chained = `${idRef}?.${argStr}`;
|
|
87
|
+
return createIdRefProxy(chained, options);
|
|
88
|
+
}
|
|
89
|
+
return createIdRefProxy(idRef, options);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
56
94
|
/**
|
|
57
95
|
* Create a recursive proxy that records property access paths.
|
|
58
96
|
* Supports both property access and method call syntax (via apply trap on function target).
|
|
@@ -74,8 +112,17 @@ function createPathProxy(prefix: string, options?: PathsOptions): any {
|
|
|
74
112
|
// Ignore symbol access (Symbol.iterator, Symbol.toPrimitive, etc.)
|
|
75
113
|
if (typeof prop === 'symbol') return undefined;
|
|
76
114
|
|
|
77
|
-
// Apply reverse alias: if prop matches an alias value, use the alias key
|
|
78
115
|
let segment = String(prop);
|
|
116
|
+
|
|
117
|
+
// #-prefix: $['#firstName'] → '#[firstName]' (cached element ref)
|
|
118
|
+
if (segment.startsWith('#')) {
|
|
119
|
+
const varName = segment.substring(1);
|
|
120
|
+
const idRef = `#[${varName}]`;
|
|
121
|
+
// Return a proxy that starts from this id ref (can chain further with ?.)
|
|
122
|
+
return createIdRefProxy(idRef, options);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Apply reverse alias: if prop matches an alias value, use the alias key
|
|
79
126
|
if (aliasMap) {
|
|
80
127
|
for (const [alias, target] of Object.entries(aliasMap)) {
|
|
81
128
|
if (target === segment) { segment = alias; break; }
|
|
@@ -222,6 +269,38 @@ export function smoothOver(value: any): any {
|
|
|
222
269
|
export function doAssign(...pairs: Record<string, any>[]): { assign: Record<string, any> } {
|
|
223
270
|
return { assign: Object.assign({}, ...pairs) };
|
|
224
271
|
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Compile-time loop expansion: generates one entry per key from a factory function.
|
|
275
|
+
* Creates a typed proxy internally — the factory receives both the key and the proxy.
|
|
276
|
+
*
|
|
277
|
+
* @param keys - Array of property names to iterate (type-checked against T)
|
|
278
|
+
* @param factory - Function that produces a config entry for each key
|
|
279
|
+
* @param options - Optional PathsOptions (aka, withMethods) for the internal proxy
|
|
280
|
+
* @returns Array of factory results (one per key) — spread into merges array
|
|
281
|
+
*
|
|
282
|
+
* @example
|
|
283
|
+
* import { forEachKeyIn, set, doAssign } from 'assign-gingerly/paths.js';
|
|
284
|
+
*
|
|
285
|
+
* interface Person extends HTMLElement { firstName: string; lastName: string; }
|
|
286
|
+
*
|
|
287
|
+
* const merges = [
|
|
288
|
+
* ...forEachKeyIn<Person>(['firstName', 'lastName'], (key, $) => ({
|
|
289
|
+
* ifKeyIn: [key],
|
|
290
|
+
* assignOptions: { withIds: { [key]: { qry: `[name="${key}"]` } } },
|
|
291
|
+
* ...doAssign(set($['#' + key]).to($[key]))
|
|
292
|
+
* })),
|
|
293
|
+
* ];
|
|
294
|
+
*/
|
|
295
|
+
export function forEachKeyIn<T>(
|
|
296
|
+
keys: (keyof T & string)[],
|
|
297
|
+
factory: (key: keyof T & string, proxy: PathProxy<T>) => any,
|
|
298
|
+
options?: PathsOptions
|
|
299
|
+
): any[] {
|
|
300
|
+
const $ = paths<T>(options);
|
|
301
|
+
return keys.map(key => factory(key, $));
|
|
302
|
+
}
|
|
303
|
+
|
|
225
304
|
/**
|
|
226
305
|
* Tagged template literal that splits a template into an array of parts.
|
|
227
306
|
* Interleaves static string segments with interpolated values.
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { resolveValues } from './resolveValues.js';
|
|
7
7
|
import { evaluatePathWithMethods } from './assignGingerly.js';
|
|
8
|
+
import { isAllowedImportPath } from './isAllowedImportPath.js';
|
|
8
9
|
/**
|
|
9
10
|
* Map of built-in handler names to their module paths.
|
|
10
11
|
* These are auto-loaded on demand — no explicit import required.
|
|
@@ -15,22 +16,17 @@ const BUILT_IN_MAP = {
|
|
|
15
16
|
'builtIns.join': './handlers/join.js',
|
|
16
17
|
'builtIns.microDataJoin': './handlers/microDataJoin.js',
|
|
17
18
|
};
|
|
18
|
-
/**
|
|
19
|
-
* Check if an import path is allowed (non-cross-domain).
|
|
20
|
-
*/
|
|
21
|
-
function isAllowedImportPath(path) {
|
|
22
|
-
return path.startsWith('./') || path.startsWith('../') || path.startsWith('/')
|
|
23
|
-
|| (!path.includes('://') && !path.startsWith('//'));
|
|
24
|
-
}
|
|
25
19
|
/**
|
|
26
20
|
* Find a handler class in a dynamically imported module.
|
|
27
21
|
* Checks default export first, then searches for the first class with `assign` on prototype.
|
|
28
22
|
*/
|
|
29
23
|
function findHandlerInModule(module) {
|
|
24
|
+
// Check default export first
|
|
30
25
|
if (module.default && typeof module.default === 'function'
|
|
31
26
|
&& module.default.prototype && 'assign' in module.default.prototype) {
|
|
32
27
|
return module.default;
|
|
33
28
|
}
|
|
29
|
+
// Search other exports
|
|
34
30
|
for (const key of Object.keys(module)) {
|
|
35
31
|
const exported = module[key];
|
|
36
32
|
if (typeof exported === 'function' && exported.prototype && 'assign' in exported.prototype) {
|
|
@@ -41,6 +37,7 @@ function findHandlerInModule(module) {
|
|
|
41
37
|
}
|
|
42
38
|
/**
|
|
43
39
|
* Dynamically load a built-in handler by name.
|
|
40
|
+
* Returns the handler constructor, or undefined if the name isn't a recognized built-in.
|
|
44
41
|
*/
|
|
45
42
|
async function loadBuiltIn(name) {
|
|
46
43
|
const path = BUILT_IN_MAP[name];
|
|
@@ -52,26 +49,25 @@ async function loadBuiltIn(name) {
|
|
|
52
49
|
/**
|
|
53
50
|
* Resolve a handler from options.handlers (class constructor or import path).
|
|
54
51
|
*/
|
|
55
|
-
async function resolveFromHandlers(name, handlers) {
|
|
52
|
+
async function resolveFromHandlers(name, handlers, permissions) {
|
|
56
53
|
if (!handlers || !(name in handlers))
|
|
57
54
|
return undefined;
|
|
58
55
|
const entry = handlers[name];
|
|
56
|
+
// Class constructor — use directly
|
|
59
57
|
if (typeof entry === 'function') {
|
|
60
58
|
return entry;
|
|
61
59
|
}
|
|
60
|
+
// Import path string — validate and dynamically import
|
|
62
61
|
if (typeof entry === 'string') {
|
|
63
|
-
if (!isAllowedImportPath(entry)) {
|
|
64
|
-
throw new Error(
|
|
65
|
-
`
|
|
66
|
-
`
|
|
67
|
-
);
|
|
62
|
+
if (!permissions?.crossDomainImports && !isAllowedImportPath(entry)) {
|
|
63
|
+
throw new Error(`assignFrom: handler "${name}" has an invalid import path "${entry}". ` +
|
|
64
|
+
`Only relative, absolute, or bare specifier paths are allowed (no cross-domain URLs). ` +
|
|
65
|
+
`Pass { crossDomainImports: true } in permissions to override.`);
|
|
68
66
|
}
|
|
69
67
|
const module = await import(entry);
|
|
70
68
|
const HandlerClass = findHandlerInModule(module);
|
|
71
69
|
if (!HandlerClass) {
|
|
72
|
-
throw new Error(
|
|
73
|
-
`assignFrom: handler "${name}" — module "${entry}" does not export a valid handler class.`
|
|
74
|
-
);
|
|
70
|
+
throw new Error(`assignFrom: handler "${name}" — module "${entry}" does not export a valid handler class.`);
|
|
75
71
|
}
|
|
76
72
|
return HandlerClass;
|
|
77
73
|
}
|
|
@@ -84,8 +80,9 @@ async function resolveFromHandlers(name, handlers) {
|
|
|
84
80
|
* @param handlerKeys - Array of keys ending with ' =>'
|
|
85
81
|
* @param pattern - The original pattern object
|
|
86
82
|
* @param options - The assignFrom options
|
|
83
|
+
* @param handlerRegistry - The registry of handler classes
|
|
87
84
|
*/
|
|
88
|
-
export async function processHandlerCommands(target, handlerKeys, pattern, options) {
|
|
85
|
+
export async function processHandlerCommands(target, handlerKeys, pattern, options, permissions) {
|
|
89
86
|
for (const key of handlerKeys) {
|
|
90
87
|
const lhsPath = key.substring(0, key.length - 3); // Remove ' =>'
|
|
91
88
|
const rhs = pattern[key];
|
|
@@ -104,6 +101,7 @@ export async function processHandlerCommands(target, handlerKeys, pattern, optio
|
|
|
104
101
|
if (configs.length === 0)
|
|
105
102
|
continue;
|
|
106
103
|
// Resolve the LHS path, preserving parent + key for return-value assignment.
|
|
104
|
+
// lhsParent[lhsKey] === lhsTarget (the current value at the path)
|
|
107
105
|
let lhsTarget;
|
|
108
106
|
let lhsParent = undefined;
|
|
109
107
|
let lhsKey = undefined;
|
|
@@ -119,13 +117,15 @@ export async function processHandlerCommands(target, handlerKeys, pattern, optio
|
|
|
119
117
|
lhsParent = result.target;
|
|
120
118
|
lhsKey = result.lastKey;
|
|
121
119
|
lhsTarget = result.target[result.lastKey];
|
|
120
|
+
// If last key is a method, call it to get the target
|
|
122
121
|
if (result.isMethod && typeof result.target[result.lastKey] === 'function') {
|
|
123
122
|
lhsTarget = result.target[result.lastKey].call(result.target);
|
|
124
|
-
lhsParent = undefined;
|
|
123
|
+
lhsParent = undefined; // Can't assign back to a method call result
|
|
125
124
|
lhsKey = undefined;
|
|
126
125
|
}
|
|
127
126
|
}
|
|
128
127
|
else {
|
|
128
|
+
// Simple path navigation — walk to parent, keep last key
|
|
129
129
|
if (pathParts.length === 0) {
|
|
130
130
|
lhsTarget = target;
|
|
131
131
|
}
|
|
@@ -158,7 +158,7 @@ export async function processHandlerCommands(target, handlerKeys, pattern, optio
|
|
|
158
158
|
// Execute handlers sequentially, sharing the same lhsTarget
|
|
159
159
|
for (const config of configs) {
|
|
160
160
|
// 1. Check options.handlers (local, per-call)
|
|
161
|
-
let HandlerClass = await resolveFromHandlers(config.do, options.handlers);
|
|
161
|
+
let HandlerClass = await resolveFromHandlers(config.do, options.handlers, permissions);
|
|
162
162
|
// 2. Fallback to built-in auto-load
|
|
163
163
|
if (!HandlerClass && config.do.startsWith('builtIns.')) {
|
|
164
164
|
HandlerClass = await loadBuiltIn(config.do);
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
import { resolveValues } from './resolveValues.js';
|
|
8
8
|
import { evaluatePathWithMethods } from './assignGingerly.js';
|
|
9
|
+
import { isAllowedImportPath } from './isAllowedImportPath.js';
|
|
10
|
+
import type { AssignPermissions } from './isAllowedImportPath.js';
|
|
9
11
|
import type { AssignFromOptions, AssignFromHandlerConstructor } from './assignFrom.js';
|
|
10
12
|
|
|
11
13
|
/**
|
|
@@ -19,14 +21,6 @@ const BUILT_IN_MAP: Record<string, string> = {
|
|
|
19
21
|
'builtIns.microDataJoin': './handlers/microDataJoin.js',
|
|
20
22
|
};
|
|
21
23
|
|
|
22
|
-
/**
|
|
23
|
-
* Check if an import path is allowed (non-cross-domain).
|
|
24
|
-
*/
|
|
25
|
-
function isAllowedImportPath(path: string): boolean {
|
|
26
|
-
return path.startsWith('./') || path.startsWith('../') || path.startsWith('/')
|
|
27
|
-
|| (!path.includes('://') && !path.startsWith('//'));
|
|
28
|
-
}
|
|
29
|
-
|
|
30
24
|
/**
|
|
31
25
|
* Find a handler class in a dynamically imported module.
|
|
32
26
|
* Checks default export first, then searches for the first class with `assign` on prototype.
|
|
@@ -63,7 +57,8 @@ async function loadBuiltIn(name: string): Promise<AssignFromHandlerConstructor |
|
|
|
63
57
|
*/
|
|
64
58
|
async function resolveFromHandlers(
|
|
65
59
|
name: string,
|
|
66
|
-
handlers: Record<string, AssignFromHandlerConstructor | string> | undefined
|
|
60
|
+
handlers: Record<string, AssignFromHandlerConstructor | string> | undefined,
|
|
61
|
+
permissions?: AssignPermissions
|
|
67
62
|
): Promise<AssignFromHandlerConstructor | undefined> {
|
|
68
63
|
if (!handlers || !(name in handlers)) return undefined;
|
|
69
64
|
|
|
@@ -76,10 +71,11 @@ async function resolveFromHandlers(
|
|
|
76
71
|
|
|
77
72
|
// Import path string — validate and dynamically import
|
|
78
73
|
if (typeof entry === 'string') {
|
|
79
|
-
if (!isAllowedImportPath(entry)) {
|
|
74
|
+
if (!permissions?.crossDomainImports && !isAllowedImportPath(entry)) {
|
|
80
75
|
throw new Error(
|
|
81
76
|
`assignFrom: handler "${name}" has an invalid import path "${entry}". ` +
|
|
82
|
-
`Only relative, absolute, or bare specifier paths are allowed (no cross-domain URLs)
|
|
77
|
+
`Only relative, absolute, or bare specifier paths are allowed (no cross-domain URLs). ` +
|
|
78
|
+
`Pass { crossDomainImports: true } in permissions to override.`
|
|
83
79
|
);
|
|
84
80
|
}
|
|
85
81
|
const module = await import(entry);
|
|
@@ -108,7 +104,8 @@ export async function processHandlerCommands(
|
|
|
108
104
|
target: any,
|
|
109
105
|
handlerKeys: string[],
|
|
110
106
|
pattern: Record<string, any>,
|
|
111
|
-
options: AssignFromOptions
|
|
107
|
+
options: AssignFromOptions,
|
|
108
|
+
permissions?: AssignPermissions
|
|
112
109
|
): Promise<void> {
|
|
113
110
|
for (const key of handlerKeys) {
|
|
114
111
|
const lhsPath = key.substring(0, key.length - 3); // Remove ' =>'
|
|
@@ -185,7 +182,7 @@ export async function processHandlerCommands(
|
|
|
185
182
|
// Execute handlers sequentially, sharing the same lhsTarget
|
|
186
183
|
for (const config of configs) {
|
|
187
184
|
// 1. Check options.handlers (local, per-call)
|
|
188
|
-
let HandlerClass = await resolveFromHandlers(config.do, options.handlers);
|
|
185
|
+
let HandlerClass = await resolveFromHandlers(config.do, options.handlers, permissions);
|
|
189
186
|
|
|
190
187
|
// 2. Fallback to built-in auto-load
|
|
191
188
|
if (!HandlerClass && config.do.startsWith('builtIns.')) {
|