assign-gingerly 0.0.75 → 0.0.77
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/DX/installForwarding.js +1 -1
- package/DX/installForwarding.ts +1 -1
- package/README.md +2 -0
- package/assignFeatures.js +10 -31
- package/assignFeatures.ts +15 -39
- package/assignFrom.js +18 -17
- package/assignFrom.ts +23 -23
- package/assignFromAsync.js +14 -13
- package/assignFromAsync.ts +15 -15
- package/assignGingerly.js +87 -48
- package/assignGingerly.ts +177 -131
- package/assignPermissions/PermissionProcessor.js +166 -0
- package/assignPermissions/PermissionProcessor.ts +210 -0
- package/assignPermissions/isAllowedImportPath.js +2 -6
- package/assignPermissions/isAllowedImportPath.ts +3 -5
- package/assignPermissions/isAllowedUrl.js +18 -0
- package/assignPermissions/isAllowedUrl.ts +15 -0
- package/assignTentatively.js +9 -11
- package/assignTentatively.ts +11 -13
- package/beVigilant.js +1 -1
- package/beVigilant.ts +1 -1
- package/buildCSSQuery.js +1 -1
- package/buildCSSQuery.ts +1 -1
- package/eachTime.js +5 -6
- package/eachTime.ts +12 -15
- package/enhanceAll.js +3 -3
- package/enhanceAll.ts +4 -4
- package/evaluatePathWithAsyncMethods.js +12 -8
- package/evaluatePathWithAsyncMethods.ts +129 -117
- package/handlers/addEventListener.js +11 -11
- package/handlers/addEventListener.ts +12 -13
- package/handlers/lazyLoad.ts +7 -7
- package/handlers/lazyLoadSwitch.ts +3 -3
- package/handlers/manageTemplateList.js +10 -10
- package/handlers/manageTemplateList.ts +12 -12
- package/handlers/rangeSelector.ts +3 -3
- package/index.js +2 -3
- package/index.ts +2 -3
- package/inferencer/types/assign-gingerly/types.d.ts +40 -9
- package/inferencer/types/el-maker/types.d.ts +33 -0
- package/inferencer/types/scratch-box/types.d.ts +3 -0
- package/inferencer/types/truth-sourcer/types.d.ts +4 -0
- package/inferencer/types/wc-info/SimpleWCInfo.d.ts +15 -0
- package/package.json +24 -23
- package/parseWithAttrs.js +1 -1
- package/parseWithAttrs.ts +1 -1
- package/processHandlerCommands.js +5 -7
- package/processHandlerCommands.ts +12 -15
- package/{getValues.js → resolve/getValues.js} +19 -8
- package/{getValues.ts → resolve/getValues.ts} +331 -314
- package/{resolveIdRef.js → resolve/resolveIdRef.js} +1 -1
- package/{resolveIdRef.ts → resolve/resolveIdRef.ts} +1 -1
- package/{resolveValues.ts → resolve/resolveValues.ts} +1 -1
- package/types/assign-gingerly/types.d.ts +40 -9
- package/assignPermissions/restrictedProps.js +0 -43
- package/assignPermissions/restrictedProps.ts +0 -53
- package/resolveAndAssignFeatures.js +0 -36
- package/resolveAndAssignFeatures.ts +0 -43
- /package/{resolveTemplate.js → resolve/resolveTemplate.js} +0 -0
- /package/{resolveTemplate.ts → resolve/resolveTemplate.ts} +0 -0
- /package/{resolveValues.js → resolve/resolveValues.js} +0 -0
package/eachTime.js
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
* This module is dynamically loaded only when @eachTime is encountered
|
|
4
4
|
* Provides event-driven iteration over elements as they mount
|
|
5
5
|
*/
|
|
6
|
-
import { redirectRestrictedProp } from './assignPermissions/restrictedProps.js';
|
|
7
6
|
/**
|
|
8
7
|
* Check if a value is an EventTarget
|
|
9
8
|
*/
|
|
@@ -21,7 +20,7 @@ function isEventTarget(value) {
|
|
|
21
20
|
* @param aliasMap - Map of aliases for token substitution
|
|
22
21
|
* @param options - Options including required AbortSignal
|
|
23
22
|
*/
|
|
24
|
-
export async function handleEachTime(target, pathParts, forEachIndex, value, withMethods, aliasMap, options,
|
|
23
|
+
export async function handleEachTime(target, pathParts, forEachIndex, value, withMethods, aliasMap, options, permissionProcessor) {
|
|
25
24
|
// Validate signal - required for cleanup
|
|
26
25
|
if (!options?.signal) {
|
|
27
26
|
throw new Error('@eachTime requires an AbortSignal in options.signal for cleanup');
|
|
@@ -35,7 +34,7 @@ export async function handleEachTime(target, pathParts, forEachIndex, value, wit
|
|
|
35
34
|
// Import evaluatePathWithMethods for navigation
|
|
36
35
|
const { evaluatePathWithMethods } = await import('./assignGingerly.js');
|
|
37
36
|
if (withMethods && withMethods.size > 0) {
|
|
38
|
-
const result = evaluatePathWithMethods(target, pathToEventSource, value, withMethods);
|
|
37
|
+
const result = evaluatePathWithMethods(target, pathToEventSource, value, withMethods, permissionProcessor);
|
|
39
38
|
eventSource = result.target;
|
|
40
39
|
}
|
|
41
40
|
else {
|
|
@@ -61,7 +60,7 @@ export async function handleEachTime(target, pathParts, forEachIndex, value, wit
|
|
|
61
60
|
// Import needed functions from assignGingerly
|
|
62
61
|
const { evaluatePathWithMethods, assignGingerly, isReadonlyProperty } = await import('./assignGingerly.js');
|
|
63
62
|
if (pathAfterForEach.length > 0) {
|
|
64
|
-
const result = evaluatePathWithMethods(mountedElement, pathAfterForEach, value, withMethods || new Set());
|
|
63
|
+
const result = evaluatePathWithMethods(mountedElement, pathAfterForEach, value, withMethods || new Set(), permissionProcessor);
|
|
65
64
|
if (result.isMethod) {
|
|
66
65
|
// Last segment is a method - call it
|
|
67
66
|
const method = result.target[result.lastKey];
|
|
@@ -82,7 +81,7 @@ export async function handleEachTime(target, pathParts, forEachIndex, value, wit
|
|
|
82
81
|
// Normal assignment
|
|
83
82
|
const lastKey = result.lastKey;
|
|
84
83
|
const parent = result.target;
|
|
85
|
-
if (redirectRestrictedProp(
|
|
84
|
+
if (permissionProcessor?.redirectRestrictedProp(parent, lastKey, value)) {
|
|
86
85
|
// skip
|
|
87
86
|
}
|
|
88
87
|
else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
@@ -93,7 +92,7 @@ export async function handleEachTime(target, pathParts, forEachIndex, value, wit
|
|
|
93
92
|
throw new Error(`Cannot merge object into readonly primitive property '${String(lastKey)}'`);
|
|
94
93
|
}
|
|
95
94
|
// Recursively apply assignGingerly
|
|
96
|
-
assignGingerly(currentValue, value, options,
|
|
95
|
+
assignGingerly(currentValue, value, options, permissionProcessor);
|
|
97
96
|
}
|
|
98
97
|
else {
|
|
99
98
|
// Property is writable - replace it
|
package/eachTime.ts
CHANGED
|
@@ -4,10 +4,7 @@
|
|
|
4
4
|
* Provides event-driven iteration over elements as they mount
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import type { IAssignGingerlyOptions } from './types/assign-gingerly/types.js';
|
|
8
|
-
import type { AssignPermissions } from './types/assign-gingerly/types.js';
|
|
9
|
-
import { redirectRestrictedProp } from './assignPermissions/restrictedProps.js';
|
|
10
|
-
import type { RestrictedPropSettingsMap } from './assignPermissions/restrictedProps.js';
|
|
7
|
+
import type { PermissionProcessor, IAssignGingerlyOptions } from './types/assign-gingerly/types.js';
|
|
11
8
|
|
|
12
9
|
/**
|
|
13
10
|
* Check if a value is an EventTarget
|
|
@@ -32,11 +29,10 @@ export async function handleEachTime(
|
|
|
32
29
|
pathParts: string[],
|
|
33
30
|
forEachIndex: number,
|
|
34
31
|
value: any,
|
|
35
|
-
withMethods: Set<string> | undefined,
|
|
36
|
-
aliasMap: Map<string, string>,
|
|
37
|
-
options?: IAssignGingerlyOptions,
|
|
38
|
-
|
|
39
|
-
restrictedPropSet?: RestrictedPropSettingsMap
|
|
32
|
+
withMethods: Set<string> | undefined,
|
|
33
|
+
aliasMap: Map<string, string>,
|
|
34
|
+
options?: IAssignGingerlyOptions,
|
|
35
|
+
permissionProcessor?: PermissionProcessor
|
|
40
36
|
): Promise<void> {
|
|
41
37
|
// Validate signal - required for cleanup
|
|
42
38
|
if (!options?.signal) {
|
|
@@ -54,7 +50,7 @@ export async function handleEachTime(
|
|
|
54
50
|
const { evaluatePathWithMethods } = await import('./assignGingerly.js');
|
|
55
51
|
|
|
56
52
|
if (withMethods && withMethods.size > 0) {
|
|
57
|
-
const result = evaluatePathWithMethods(target, pathToEventSource, value, withMethods);
|
|
53
|
+
const result = evaluatePathWithMethods(target, pathToEventSource, value, withMethods, permissionProcessor);
|
|
58
54
|
eventSource = result.target;
|
|
59
55
|
} else {
|
|
60
56
|
// Simple property navigation
|
|
@@ -90,7 +86,8 @@ export async function handleEachTime(
|
|
|
90
86
|
mountedElement,
|
|
91
87
|
pathAfterForEach,
|
|
92
88
|
value,
|
|
93
|
-
withMethods || new Set()
|
|
89
|
+
withMethods || new Set(),
|
|
90
|
+
permissionProcessor
|
|
94
91
|
);
|
|
95
92
|
|
|
96
93
|
if (result.isMethod) {
|
|
@@ -111,9 +108,9 @@ export async function handleEachTime(
|
|
|
111
108
|
const lastKey = result.lastKey;
|
|
112
109
|
const parent = result.target;
|
|
113
110
|
|
|
114
|
-
if (redirectRestrictedProp(
|
|
115
|
-
// skip
|
|
116
|
-
} else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
111
|
+
if (permissionProcessor?.redirectRestrictedProp(parent, lastKey, value)) {
|
|
112
|
+
// skip
|
|
113
|
+
} else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
117
114
|
// Check if property exists and is readonly
|
|
118
115
|
if (lastKey in parent && isReadonlyProperty(parent, lastKey)) {
|
|
119
116
|
const currentValue = parent[lastKey];
|
|
@@ -123,7 +120,7 @@ export async function handleEachTime(
|
|
|
123
120
|
);
|
|
124
121
|
}
|
|
125
122
|
// Recursively apply assignGingerly
|
|
126
|
-
assignGingerly(currentValue, value, options,
|
|
123
|
+
assignGingerly(currentValue, value, options, permissionProcessor);
|
|
127
124
|
} else {
|
|
128
125
|
// Property is writable - replace it
|
|
129
126
|
parent[lastKey] = value;
|
package/enhanceAll.js
CHANGED
|
@@ -33,13 +33,13 @@ import { findClassPrototypeInPath } from './utils/findClassPrototypeInPath.js';
|
|
|
33
33
|
* @param target - The DOM element to search within
|
|
34
34
|
* @param configs - Array of enhancement configurations
|
|
35
35
|
*/
|
|
36
|
-
export async function enhanceAll(target, configs,
|
|
36
|
+
export async function enhanceAll(target, configs, permissionProcessor) {
|
|
37
37
|
for (const config of configs) {
|
|
38
38
|
// Validate EMC path unless cross-domain imports are explicitly permitted
|
|
39
|
-
if (!
|
|
39
|
+
if (!permissionProcessor?.crossDomainImports && !isAllowedImportPath(config.emc)) {
|
|
40
40
|
throw new Error(`enhanceAll: EMC path "${config.emc}" is a cross-domain URL. ` +
|
|
41
41
|
`Only same-origin paths or import-map-covered specifiers are allowed by default. ` +
|
|
42
|
-
`Pass { crossDomainImports: true } in
|
|
42
|
+
`Pass { crossDomainImports: true } in permissionProcessor to override.`);
|
|
43
43
|
}
|
|
44
44
|
// 1. Import the EMC JSON
|
|
45
45
|
const emcModule = await import(config.emc, { with: { type: 'json' } });
|
package/enhanceAll.ts
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
import { isAllowedImportPath } from './assignPermissions/isAllowedImportPath.js';
|
|
20
20
|
import { findClassPrototypeInPath } from './utils/findClassPrototypeInPath.js';
|
|
21
|
-
import type {
|
|
21
|
+
import type { PermissionProcessor } from './types/assign-gingerly/types.js';
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* Configuration for a single enhancement to apply in bulk.
|
|
@@ -51,15 +51,15 @@ export interface EnhanceConfig {
|
|
|
51
51
|
export async function enhanceAll(
|
|
52
52
|
target: Element,
|
|
53
53
|
configs: EnhanceConfig[],
|
|
54
|
-
|
|
54
|
+
permissionProcessor?: PermissionProcessor
|
|
55
55
|
): Promise<void> {
|
|
56
56
|
for (const config of configs) {
|
|
57
57
|
// Validate EMC path unless cross-domain imports are explicitly permitted
|
|
58
|
-
if (!
|
|
58
|
+
if (!permissionProcessor?.crossDomainImports && !isAllowedImportPath(config.emc)) {
|
|
59
59
|
throw new Error(
|
|
60
60
|
`enhanceAll: EMC path "${config.emc}" is a cross-domain URL. ` +
|
|
61
61
|
`Only same-origin paths or import-map-covered specifiers are allowed by default. ` +
|
|
62
|
-
`Pass { crossDomainImports: true } in
|
|
62
|
+
`Pass { crossDomainImports: true } in permissionProcessor to override.`
|
|
63
63
|
);
|
|
64
64
|
}
|
|
65
65
|
|
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
* NOTE: Interaction with @each and @eachTime is not yet implemented.
|
|
11
11
|
* Deferred until a compelling use case presents itself.
|
|
12
12
|
*/
|
|
13
|
+
import { isAllowedMethod } from './assignGingerly.js';
|
|
14
|
+
function isAllowedAsyncMethod(methodName, withAsyncMethods, permissionProcessor) {
|
|
15
|
+
return withAsyncMethods.has(methodName) && !permissionProcessor?.checkRestrictedMethod(methodName);
|
|
16
|
+
}
|
|
13
17
|
/**
|
|
14
18
|
* Evaluates a path with support for both sync and async method calls.
|
|
15
19
|
* Awaits the return value of any method in the withAsyncMethods set.
|
|
@@ -21,7 +25,7 @@
|
|
|
21
25
|
* @param withAsyncMethods - Set of method names that are awaited
|
|
22
26
|
* @returns Promise resolving to the evaluation result
|
|
23
27
|
*/
|
|
24
|
-
export async function evaluatePathWithAsyncMethods(target, pathParts, value, withMethods, withAsyncMethods) {
|
|
28
|
+
export async function evaluatePathWithAsyncMethods(target, pathParts, value, withMethods, withAsyncMethods, permissionProcessor) {
|
|
25
29
|
let current = target;
|
|
26
30
|
let i = 0;
|
|
27
31
|
// Process all segments except the last one
|
|
@@ -30,12 +34,12 @@ export async function evaluatePathWithAsyncMethods(target, pathParts, value, wit
|
|
|
30
34
|
const nextPart = pathParts[i + 1];
|
|
31
35
|
// A trailing | marks a zero-argument method call: 'deref|' calls deref()
|
|
32
36
|
// without consuming the next segment. Only applies to listed method names.
|
|
33
|
-
const isZeroArgSync = part.endsWith('|') &&
|
|
34
|
-
const isZeroArgAsync = part.endsWith('|') &&
|
|
37
|
+
const isZeroArgSync = part.endsWith('|') && isAllowedMethod(part.slice(0, -1), withMethods, permissionProcessor);
|
|
38
|
+
const isZeroArgAsync = part.endsWith('|') && isAllowedAsyncMethod(part.slice(0, -1), withAsyncMethods, permissionProcessor);
|
|
35
39
|
const baseName = (isZeroArgSync || isZeroArgAsync) ? part.slice(0, -1) : part;
|
|
36
40
|
const nextIsMethod = withAsyncMethods.has(nextPart) || withMethods.has(nextPart)
|
|
37
41
|
|| (nextPart.endsWith('|') && (withAsyncMethods.has(nextPart.slice(0, -1)) || withMethods.has(nextPart.slice(0, -1))));
|
|
38
|
-
if (
|
|
42
|
+
if (isAllowedAsyncMethod(part, withAsyncMethods, permissionProcessor) || isZeroArgAsync) {
|
|
39
43
|
// Async method — call and await
|
|
40
44
|
const method = current[baseName];
|
|
41
45
|
if (typeof method === 'function') {
|
|
@@ -57,7 +61,7 @@ export async function evaluatePathWithAsyncMethods(target, pathParts, value, wit
|
|
|
57
61
|
current = current[baseName];
|
|
58
62
|
}
|
|
59
63
|
}
|
|
60
|
-
else if (
|
|
64
|
+
else if (isAllowedMethod(part, withMethods, permissionProcessor) || isZeroArgSync) {
|
|
61
65
|
// Sync method — same logic as evaluatePathWithMethods
|
|
62
66
|
const method = current[baseName];
|
|
63
67
|
if (typeof method === 'function') {
|
|
@@ -91,13 +95,13 @@ export async function evaluatePathWithAsyncMethods(target, pathParts, value, wit
|
|
|
91
95
|
// otherwise it is a literal property name (e.g. an exotic key ending in |).
|
|
92
96
|
const rawLastKey = pathParts[pathParts.length - 1];
|
|
93
97
|
const isZeroArg = rawLastKey.endsWith('|')
|
|
94
|
-
&& (
|
|
98
|
+
&& (isAllowedMethod(rawLastKey.slice(0, -1), withMethods, permissionProcessor) || isAllowedAsyncMethod(rawLastKey.slice(0, -1), withAsyncMethods, permissionProcessor));
|
|
95
99
|
const lastKey = isZeroArg ? rawLastKey.slice(0, -1) : rawLastKey;
|
|
96
100
|
return {
|
|
97
101
|
target: current,
|
|
98
102
|
lastKey,
|
|
99
|
-
isMethod:
|
|
100
|
-
isAsyncMethod:
|
|
103
|
+
isMethod: isAllowedMethod(lastKey, withMethods, permissionProcessor),
|
|
104
|
+
isAsyncMethod: isAllowedAsyncMethod(lastKey, withAsyncMethods, permissionProcessor),
|
|
101
105
|
isZeroArg
|
|
102
106
|
};
|
|
103
107
|
}
|
|
@@ -1,117 +1,129 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* evaluatePathWithAsyncMethods - Async variant of evaluatePathWithMethods.
|
|
3
|
-
*
|
|
4
|
-
* Walks a path of property accesses and method calls, awaiting any methods
|
|
5
|
-
* that are in the withAsyncMethods set before continuing the chain.
|
|
6
|
-
*
|
|
7
|
-
* This module is loaded dynamically (only when withAsyncMethods is used)
|
|
8
|
-
* to avoid adding async overhead to the synchronous path.
|
|
9
|
-
*
|
|
10
|
-
* NOTE: Interaction with @each and @eachTime is not yet implemented.
|
|
11
|
-
* Deferred until a compelling use case presents itself.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
1
|
+
/**
|
|
2
|
+
* evaluatePathWithAsyncMethods - Async variant of evaluatePathWithMethods.
|
|
3
|
+
*
|
|
4
|
+
* Walks a path of property accesses and method calls, awaiting any methods
|
|
5
|
+
* that are in the withAsyncMethods set before continuing the chain.
|
|
6
|
+
*
|
|
7
|
+
* This module is loaded dynamically (only when withAsyncMethods is used)
|
|
8
|
+
* to avoid adding async overhead to the synchronous path.
|
|
9
|
+
*
|
|
10
|
+
* NOTE: Interaction with @each and @eachTime is not yet implemented.
|
|
11
|
+
* Deferred until a compelling use case presents itself.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { PermissionProcessor } from './types/assign-gingerly/types.js';
|
|
15
|
+
import { isAllowedMethod } from './assignGingerly.js';
|
|
16
|
+
|
|
17
|
+
export interface AsyncPathResult {
|
|
18
|
+
target: any;
|
|
19
|
+
lastKey: string;
|
|
20
|
+
isMethod: boolean;
|
|
21
|
+
isAsyncMethod: boolean;
|
|
22
|
+
isZeroArg: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function isAllowedAsyncMethod(
|
|
26
|
+
methodName: string,
|
|
27
|
+
withAsyncMethods: Set<string>,
|
|
28
|
+
permissionProcessor?: PermissionProcessor
|
|
29
|
+
): boolean {
|
|
30
|
+
return withAsyncMethods.has(methodName) && !permissionProcessor?.checkRestrictedMethod(methodName);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Evaluates a path with support for both sync and async method calls.
|
|
35
|
+
* Awaits the return value of any method in the withAsyncMethods set.
|
|
36
|
+
*
|
|
37
|
+
* @param target - The root object to start path evaluation from
|
|
38
|
+
* @param pathParts - Array of path segments (split from '?.' notation)
|
|
39
|
+
* @param value - The value to assign or pass as argument at the end
|
|
40
|
+
* @param withMethods - Set of method names that are called synchronously
|
|
41
|
+
* @param withAsyncMethods - Set of method names that are awaited
|
|
42
|
+
* @returns Promise resolving to the evaluation result
|
|
43
|
+
*/
|
|
44
|
+
export async function evaluatePathWithAsyncMethods(
|
|
45
|
+
target: any,
|
|
46
|
+
pathParts: string[],
|
|
47
|
+
value: any,
|
|
48
|
+
withMethods: Set<string>,
|
|
49
|
+
withAsyncMethods: Set<string>,
|
|
50
|
+
permissionProcessor?: PermissionProcessor
|
|
51
|
+
): Promise<AsyncPathResult> {
|
|
52
|
+
let current = target;
|
|
53
|
+
let i = 0;
|
|
54
|
+
|
|
55
|
+
// Process all segments except the last one
|
|
56
|
+
while (i < pathParts.length - 1) {
|
|
57
|
+
const part = pathParts[i];
|
|
58
|
+
const nextPart = pathParts[i + 1];
|
|
59
|
+
|
|
60
|
+
// A trailing | marks a zero-argument method call: 'deref|' calls deref()
|
|
61
|
+
// without consuming the next segment. Only applies to listed method names.
|
|
62
|
+
const isZeroArgSync = part.endsWith('|') && isAllowedMethod(part.slice(0, -1), withMethods, permissionProcessor);
|
|
63
|
+
const isZeroArgAsync = part.endsWith('|') && isAllowedAsyncMethod(part.slice(0, -1), withAsyncMethods, permissionProcessor);
|
|
64
|
+
const baseName = (isZeroArgSync || isZeroArgAsync) ? part.slice(0, -1) : part;
|
|
65
|
+
const nextIsMethod = withAsyncMethods.has(nextPart) || withMethods.has(nextPart)
|
|
66
|
+
|| (nextPart.endsWith('|') && (withAsyncMethods.has(nextPart.slice(0, -1)) || withMethods.has(nextPart.slice(0, -1))));
|
|
67
|
+
|
|
68
|
+
if (isAllowedAsyncMethod(part, withAsyncMethods, permissionProcessor) || isZeroArgAsync) {
|
|
69
|
+
// Async method — call and await
|
|
70
|
+
const method = current[baseName];
|
|
71
|
+
if (typeof method === 'function') {
|
|
72
|
+
if (isZeroArgAsync || nextIsMethod) {
|
|
73
|
+
// Zero-arg call — next is either a method or explicitly not an argument
|
|
74
|
+
current = await method.call(current);
|
|
75
|
+
} else {
|
|
76
|
+
// Call with next part as string arg, then await
|
|
77
|
+
current = await method.call(current, nextPart);
|
|
78
|
+
i++; // Skip next part since we consumed it as argument
|
|
79
|
+
}
|
|
80
|
+
} else {
|
|
81
|
+
// Not a function — just access property
|
|
82
|
+
if (current[baseName] === undefined || current[baseName] === null) {
|
|
83
|
+
current[baseName] = {};
|
|
84
|
+
}
|
|
85
|
+
current = current[baseName];
|
|
86
|
+
}
|
|
87
|
+
} else if (isAllowedMethod(part, withMethods, permissionProcessor) || isZeroArgSync) {
|
|
88
|
+
// Sync method — same logic as evaluatePathWithMethods
|
|
89
|
+
const method = current[baseName];
|
|
90
|
+
if (typeof method === 'function') {
|
|
91
|
+
if (isZeroArgSync || nextIsMethod) {
|
|
92
|
+
// Zero-arg call — next is either a method or explicitly not an argument
|
|
93
|
+
current = method.call(current);
|
|
94
|
+
} else {
|
|
95
|
+
// Call with next part as string arg
|
|
96
|
+
current = method.call(current, nextPart);
|
|
97
|
+
i++; // Skip next part since we consumed it as argument
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
if (!(baseName in current) || typeof current[baseName] !== 'object' || current[baseName] === null) {
|
|
101
|
+
current[baseName] = {};
|
|
102
|
+
}
|
|
103
|
+
current = current[baseName];
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
// Not a method — normal property access
|
|
107
|
+
if (!(part in current) || typeof current[part] !== 'object' || current[part] === null) {
|
|
108
|
+
current[part] = {};
|
|
109
|
+
}
|
|
110
|
+
current = current[part];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
i++;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Strip a trailing | from the last segment only when it names a listed method;
|
|
117
|
+
// otherwise it is a literal property name (e.g. an exotic key ending in |).
|
|
118
|
+
const rawLastKey = pathParts[pathParts.length - 1];
|
|
119
|
+
const isZeroArg = rawLastKey.endsWith('|')
|
|
120
|
+
&& (isAllowedMethod(rawLastKey.slice(0, -1), withMethods, permissionProcessor) || isAllowedAsyncMethod(rawLastKey.slice(0, -1), withAsyncMethods, permissionProcessor));
|
|
121
|
+
const lastKey = isZeroArg ? rawLastKey.slice(0, -1) : rawLastKey;
|
|
122
|
+
return {
|
|
123
|
+
target: current,
|
|
124
|
+
lastKey,
|
|
125
|
+
isMethod: isAllowedMethod(lastKey, withMethods, permissionProcessor),
|
|
126
|
+
isAsyncMethod: isAllowedAsyncMethod(lastKey, withAsyncMethods, permissionProcessor),
|
|
127
|
+
isZeroArg
|
|
128
|
+
};
|
|
129
|
+
}
|
|
@@ -37,7 +37,7 @@ function extractShorthand(config) {
|
|
|
37
37
|
/**
|
|
38
38
|
* Process a single AssignDispatchVector — execute all toTarget/toHost/toLHS assignments.
|
|
39
39
|
*/
|
|
40
|
-
function processVector(vector, source, target, host, lhs, inheritedOptions, useAssignFrom,
|
|
40
|
+
function processVector(vector, source, target, host, lhs, inheritedOptions, useAssignFrom, permissionProcessor) {
|
|
41
41
|
const destinations = [
|
|
42
42
|
{ key: 'toTarget', dest: target },
|
|
43
43
|
{ key: 'toHost', dest: host },
|
|
@@ -50,11 +50,11 @@ function processVector(vector, source, target, host, lhs, inheritedOptions, useA
|
|
|
50
50
|
if (useAssignFrom && source != null) {
|
|
51
51
|
// Dynamic import assignFrom on demand (fire-and-forget context, already async)
|
|
52
52
|
import('../assignFrom.js').then(({ assignFrom }) => {
|
|
53
|
-
assignFrom(dest, pattern, { from: source, ...inheritedOptions, ...vector.withOptions },
|
|
53
|
+
assignFrom(dest, pattern, { from: source, ...inheritedOptions, ...vector.withOptions }, permissionProcessor);
|
|
54
54
|
});
|
|
55
55
|
}
|
|
56
56
|
else {
|
|
57
|
-
assignGingerly(dest, pattern, inheritedOptions,
|
|
57
|
+
assignGingerly(dest, pattern, inheritedOptions, permissionProcessor);
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
// Handle shorthand (implicit toHost)
|
|
@@ -62,11 +62,11 @@ function processVector(vector, source, target, host, lhs, inheritedOptions, useA
|
|
|
62
62
|
if (shorthand) {
|
|
63
63
|
if (useAssignFrom && source != null) {
|
|
64
64
|
import('../assignFrom.js').then(({ assignFrom }) => {
|
|
65
|
-
assignFrom(host, shorthand, { from: source, ...inheritedOptions, ...vector.withOptions },
|
|
65
|
+
assignFrom(host, shorthand, { from: source, ...inheritedOptions, ...vector.withOptions }, permissionProcessor);
|
|
66
66
|
});
|
|
67
67
|
}
|
|
68
68
|
else {
|
|
69
|
-
assignGingerly(host, shorthand, inheritedOptions,
|
|
69
|
+
assignGingerly(host, shorthand, inheritedOptions, permissionProcessor);
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
}
|
|
@@ -79,7 +79,7 @@ function processVector(vector, source, target, host, lhs, inheritedOptions, useA
|
|
|
79
79
|
* @param host - The options.from (source/view model)
|
|
80
80
|
* @param inheritedOptions - Parent assignFrom options (withMethods, aka, etc.)
|
|
81
81
|
*/
|
|
82
|
-
export function attachEventListener(lhs, config, target, host, inheritedOptions,
|
|
82
|
+
export function attachEventListener(lhs, config, target, host, inheritedOptions, permissionProcessor) {
|
|
83
83
|
const { on: eventName, get: getConfig, fromLHS, fromHost, fromTarget, fromEvent, dispatch } = config;
|
|
84
84
|
// Resolve get config values
|
|
85
85
|
const { abortController, key, nudge, options: listenerOptions, stopPropagation, preventDefault, dispatch: getDispatch } = getConfig ?? {};
|
|
@@ -112,22 +112,22 @@ export function attachEventListener(lhs, config, target, host, inheritedOptions,
|
|
|
112
112
|
if (preventDefault)
|
|
113
113
|
event.preventDefault();
|
|
114
114
|
// Static assignments (no from) — top-level toTarget/toHost/toLHS + shorthand
|
|
115
|
-
processVector(config, null, target, host, lhs, inheritedOptions, false,
|
|
115
|
+
processVector(config, null, target, host, lhs, inheritedOptions, false, permissionProcessor);
|
|
116
116
|
// fromLHS assignments
|
|
117
117
|
if (fromLHS) {
|
|
118
|
-
processVector(fromLHS, lhs, target, host, lhs, inheritedOptions, true,
|
|
118
|
+
processVector(fromLHS, lhs, target, host, lhs, inheritedOptions, true, permissionProcessor);
|
|
119
119
|
}
|
|
120
120
|
// fromHost assignments
|
|
121
121
|
if (fromHost) {
|
|
122
|
-
processVector(fromHost, host, target, host, lhs, inheritedOptions, true,
|
|
122
|
+
processVector(fromHost, host, target, host, lhs, inheritedOptions, true, permissionProcessor);
|
|
123
123
|
}
|
|
124
124
|
// fromTarget assignments
|
|
125
125
|
if (fromTarget) {
|
|
126
|
-
processVector(fromTarget, target, target, host, lhs, inheritedOptions, true,
|
|
126
|
+
processVector(fromTarget, target, target, host, lhs, inheritedOptions, true, permissionProcessor);
|
|
127
127
|
}
|
|
128
128
|
// fromEvent assignments
|
|
129
129
|
if (fromEvent) {
|
|
130
|
-
processVector(fromEvent, event, target, host, lhs, inheritedOptions, true,
|
|
130
|
+
processVector(fromEvent, event, target, host, lhs, inheritedOptions, true, permissionProcessor);
|
|
131
131
|
}
|
|
132
132
|
// Dispatch custom event if configured
|
|
133
133
|
const dispatchConfig = dispatch || getDispatch;
|
|
@@ -8,8 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import assignGingerly from '../assignGingerly.js';
|
|
11
|
-
import type { AddEventListenerConfig, AssignDispatchVector } from '../types/assign-gingerly/types.js';
|
|
12
|
-
import type { AssignPermissions } from '../types/assign-gingerly/types.js';
|
|
11
|
+
import type { AddEventListenerConfig, AssignDispatchVector, PermissionProcessor } from '../types/assign-gingerly/types.js';
|
|
13
12
|
|
|
14
13
|
/**
|
|
15
14
|
* WeakMap for dedup: Element → Map<key, AbortController>
|
|
@@ -52,7 +51,7 @@ function processVector(
|
|
|
52
51
|
lhs: Element,
|
|
53
52
|
inheritedOptions: any,
|
|
54
53
|
useAssignFrom: boolean,
|
|
55
|
-
|
|
54
|
+
permissionProcessor?: PermissionProcessor
|
|
56
55
|
): void {
|
|
57
56
|
const destinations = [
|
|
58
57
|
{ key: 'toTarget' as const, dest: target },
|
|
@@ -66,10 +65,10 @@ function processVector(
|
|
|
66
65
|
if (useAssignFrom && source != null) {
|
|
67
66
|
// Dynamic import assignFrom on demand (fire-and-forget context, already async)
|
|
68
67
|
import('../assignFrom.js').then(({ assignFrom }) => {
|
|
69
|
-
assignFrom(dest, pattern, { from: source, ...inheritedOptions, ...vector.withOptions },
|
|
68
|
+
assignFrom(dest, pattern, { from: source, ...inheritedOptions, ...vector.withOptions }, permissionProcessor);
|
|
70
69
|
});
|
|
71
70
|
} else {
|
|
72
|
-
assignGingerly(dest, pattern, inheritedOptions,
|
|
71
|
+
assignGingerly(dest, pattern, inheritedOptions, permissionProcessor);
|
|
73
72
|
}
|
|
74
73
|
}
|
|
75
74
|
|
|
@@ -78,10 +77,10 @@ function processVector(
|
|
|
78
77
|
if (shorthand) {
|
|
79
78
|
if (useAssignFrom && source != null) {
|
|
80
79
|
import('../assignFrom.js').then(({ assignFrom }) => {
|
|
81
|
-
assignFrom(host, shorthand, { from: source, ...inheritedOptions, ...vector.withOptions },
|
|
80
|
+
assignFrom(host, shorthand, { from: source, ...inheritedOptions, ...vector.withOptions }, permissionProcessor);
|
|
82
81
|
});
|
|
83
82
|
} else {
|
|
84
|
-
assignGingerly(host, shorthand, inheritedOptions,
|
|
83
|
+
assignGingerly(host, shorthand, inheritedOptions, permissionProcessor);
|
|
85
84
|
}
|
|
86
85
|
}
|
|
87
86
|
}
|
|
@@ -101,7 +100,7 @@ export function attachEventListener(
|
|
|
101
100
|
target: any,
|
|
102
101
|
host: any,
|
|
103
102
|
inheritedOptions: any,
|
|
104
|
-
|
|
103
|
+
permissionProcessor?: PermissionProcessor
|
|
105
104
|
): void {
|
|
106
105
|
const { on: eventName, get: getConfig, fromLHS, fromHost, fromTarget, fromEvent, dispatch } = config;
|
|
107
106
|
|
|
@@ -134,26 +133,26 @@ export function attachEventListener(
|
|
|
134
133
|
if (preventDefault) event.preventDefault();
|
|
135
134
|
|
|
136
135
|
// Static assignments (no from) — top-level toTarget/toHost/toLHS + shorthand
|
|
137
|
-
processVector(config, null, target, host, lhs, inheritedOptions, false,
|
|
136
|
+
processVector(config, null, target, host, lhs, inheritedOptions, false, permissionProcessor);
|
|
138
137
|
|
|
139
138
|
// fromLHS assignments
|
|
140
139
|
if (fromLHS) {
|
|
141
|
-
processVector(fromLHS, lhs, target, host, lhs, inheritedOptions, true,
|
|
140
|
+
processVector(fromLHS, lhs, target, host, lhs, inheritedOptions, true, permissionProcessor);
|
|
142
141
|
}
|
|
143
142
|
|
|
144
143
|
// fromHost assignments
|
|
145
144
|
if (fromHost) {
|
|
146
|
-
processVector(fromHost, host, target, host, lhs, inheritedOptions, true,
|
|
145
|
+
processVector(fromHost, host, target, host, lhs, inheritedOptions, true, permissionProcessor);
|
|
147
146
|
}
|
|
148
147
|
|
|
149
148
|
// fromTarget assignments
|
|
150
149
|
if (fromTarget) {
|
|
151
|
-
processVector(fromTarget, target, target, host, lhs, inheritedOptions, true,
|
|
150
|
+
processVector(fromTarget, target, target, host, lhs, inheritedOptions, true, permissionProcessor);
|
|
152
151
|
}
|
|
153
152
|
|
|
154
153
|
// fromEvent assignments
|
|
155
154
|
if (fromEvent) {
|
|
156
|
-
processVector(fromEvent, event, target, host, lhs, inheritedOptions, true,
|
|
155
|
+
processVector(fromEvent, event, target, host, lhs, inheritedOptions, true, permissionProcessor);
|
|
157
156
|
}
|
|
158
157
|
|
|
159
158
|
// Dispatch custom event if configured
|