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.
Files changed (61) hide show
  1. package/DX/installForwarding.js +1 -1
  2. package/DX/installForwarding.ts +1 -1
  3. package/README.md +2 -0
  4. package/assignFeatures.js +10 -31
  5. package/assignFeatures.ts +15 -39
  6. package/assignFrom.js +18 -17
  7. package/assignFrom.ts +23 -23
  8. package/assignFromAsync.js +14 -13
  9. package/assignFromAsync.ts +15 -15
  10. package/assignGingerly.js +87 -48
  11. package/assignGingerly.ts +177 -131
  12. package/assignPermissions/PermissionProcessor.js +166 -0
  13. package/assignPermissions/PermissionProcessor.ts +210 -0
  14. package/assignPermissions/isAllowedImportPath.js +2 -6
  15. package/assignPermissions/isAllowedImportPath.ts +3 -5
  16. package/assignPermissions/isAllowedUrl.js +18 -0
  17. package/assignPermissions/isAllowedUrl.ts +15 -0
  18. package/assignTentatively.js +9 -11
  19. package/assignTentatively.ts +11 -13
  20. package/beVigilant.js +1 -1
  21. package/beVigilant.ts +1 -1
  22. package/buildCSSQuery.js +1 -1
  23. package/buildCSSQuery.ts +1 -1
  24. package/eachTime.js +5 -6
  25. package/eachTime.ts +12 -15
  26. package/enhanceAll.js +3 -3
  27. package/enhanceAll.ts +4 -4
  28. package/evaluatePathWithAsyncMethods.js +12 -8
  29. package/evaluatePathWithAsyncMethods.ts +129 -117
  30. package/handlers/addEventListener.js +11 -11
  31. package/handlers/addEventListener.ts +12 -13
  32. package/handlers/lazyLoad.ts +7 -7
  33. package/handlers/lazyLoadSwitch.ts +3 -3
  34. package/handlers/manageTemplateList.js +10 -10
  35. package/handlers/manageTemplateList.ts +12 -12
  36. package/handlers/rangeSelector.ts +3 -3
  37. package/index.js +2 -3
  38. package/index.ts +2 -3
  39. package/inferencer/types/assign-gingerly/types.d.ts +40 -9
  40. package/inferencer/types/el-maker/types.d.ts +33 -0
  41. package/inferencer/types/scratch-box/types.d.ts +3 -0
  42. package/inferencer/types/truth-sourcer/types.d.ts +4 -0
  43. package/inferencer/types/wc-info/SimpleWCInfo.d.ts +15 -0
  44. package/package.json +24 -23
  45. package/parseWithAttrs.js +1 -1
  46. package/parseWithAttrs.ts +1 -1
  47. package/processHandlerCommands.js +5 -7
  48. package/processHandlerCommands.ts +12 -15
  49. package/{getValues.js → resolve/getValues.js} +19 -8
  50. package/{getValues.ts → resolve/getValues.ts} +331 -314
  51. package/{resolveIdRef.js → resolve/resolveIdRef.js} +1 -1
  52. package/{resolveIdRef.ts → resolve/resolveIdRef.ts} +1 -1
  53. package/{resolveValues.ts → resolve/resolveValues.ts} +1 -1
  54. package/types/assign-gingerly/types.d.ts +40 -9
  55. package/assignPermissions/restrictedProps.js +0 -43
  56. package/assignPermissions/restrictedProps.ts +0 -53
  57. package/resolveAndAssignFeatures.js +0 -36
  58. package/resolveAndAssignFeatures.ts +0 -43
  59. /package/{resolveTemplate.js → resolve/resolveTemplate.js} +0 -0
  60. /package/{resolveTemplate.ts → resolve/resolveTemplate.ts} +0 -0
  61. /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, permissions, restrictedPropSet) {
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(restrictedPropSet, parent, lastKey, value)) {
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, permissions);
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
- permissions?: AssignPermissions,
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(restrictedPropSet, parent, lastKey, value)) {
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, permissions);
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, permissions) {
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 (!permissions?.crossDomainImports && !isAllowedImportPath(config.emc)) {
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 permissions to override.`);
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 { AssignPermissions } from './types/assign-gingerly/types.js';
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
- permissions?: AssignPermissions
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 (!permissions?.crossDomainImports && !isAllowedImportPath(config.emc)) {
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 permissions to override.`
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('|') && withMethods.has(part.slice(0, -1));
34
- const isZeroArgAsync = part.endsWith('|') && withAsyncMethods.has(part.slice(0, -1));
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 (withAsyncMethods.has(part) || isZeroArgAsync) {
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 (withMethods.has(part) || isZeroArgSync) {
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
- && (withMethods.has(rawLastKey.slice(0, -1)) || withAsyncMethods.has(rawLastKey.slice(0, -1)));
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: withMethods.has(lastKey),
100
- isAsyncMethod: withAsyncMethods.has(lastKey),
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
- export interface AsyncPathResult {
15
- target: any;
16
- lastKey: string;
17
- isMethod: boolean;
18
- isAsyncMethod: boolean;
19
- isZeroArg: boolean;
20
- }
21
-
22
- /**
23
- * Evaluates a path with support for both sync and async method calls.
24
- * Awaits the return value of any method in the withAsyncMethods set.
25
- *
26
- * @param target - The root object to start path evaluation from
27
- * @param pathParts - Array of path segments (split from '?.' notation)
28
- * @param value - The value to assign or pass as argument at the end
29
- * @param withMethods - Set of method names that are called synchronously
30
- * @param withAsyncMethods - Set of method names that are awaited
31
- * @returns Promise resolving to the evaluation result
32
- */
33
- export async function evaluatePathWithAsyncMethods(
34
- target: any,
35
- pathParts: string[],
36
- value: any,
37
- withMethods: Set<string>,
38
- withAsyncMethods: Set<string>
39
- ): Promise<AsyncPathResult> {
40
- let current = target;
41
- let i = 0;
42
-
43
- // Process all segments except the last one
44
- while (i < pathParts.length - 1) {
45
- const part = pathParts[i];
46
- const nextPart = pathParts[i + 1];
47
-
48
- // A trailing | marks a zero-argument method call: 'deref|' calls deref()
49
- // without consuming the next segment. Only applies to listed method names.
50
- const isZeroArgSync = part.endsWith('|') && withMethods.has(part.slice(0, -1));
51
- const isZeroArgAsync = part.endsWith('|') && withAsyncMethods.has(part.slice(0, -1));
52
- const baseName = (isZeroArgSync || isZeroArgAsync) ? part.slice(0, -1) : part;
53
- const nextIsMethod = withAsyncMethods.has(nextPart) || withMethods.has(nextPart)
54
- || (nextPart.endsWith('|') && (withAsyncMethods.has(nextPart.slice(0, -1)) || withMethods.has(nextPart.slice(0, -1))));
55
-
56
- if (withAsyncMethods.has(part) || isZeroArgAsync) {
57
- // Async method — call and await
58
- const method = current[baseName];
59
- if (typeof method === 'function') {
60
- if (isZeroArgAsync || nextIsMethod) {
61
- // Zero-arg call next is either a method or explicitly not an argument
62
- current = await method.call(current);
63
- } else {
64
- // Call with next part as string arg, then await
65
- current = await method.call(current, nextPart);
66
- i++; // Skip next part since we consumed it as argument
67
- }
68
- } else {
69
- // Not a function just access property
70
- if (current[baseName] === undefined || current[baseName] === null) {
71
- current[baseName] = {};
72
- }
73
- current = current[baseName];
74
- }
75
- } else if (withMethods.has(part) || isZeroArgSync) {
76
- // Sync method same logic as evaluatePathWithMethods
77
- const method = current[baseName];
78
- if (typeof method === 'function') {
79
- if (isZeroArgSync || nextIsMethod) {
80
- // Zero-arg call — next is either a method or explicitly not an argument
81
- current = method.call(current);
82
- } else {
83
- // Call with next part as string arg
84
- current = method.call(current, nextPart);
85
- i++; // Skip next part since we consumed it as argument
86
- }
87
- } else {
88
- if (!(baseName in current) || typeof current[baseName] !== 'object' || current[baseName] === null) {
89
- current[baseName] = {};
90
- }
91
- current = current[baseName];
92
- }
93
- } else {
94
- // Not a method — normal property access
95
- if (!(part in current) || typeof current[part] !== 'object' || current[part] === null) {
96
- current[part] = {};
97
- }
98
- current = current[part];
99
- }
100
-
101
- i++;
102
- }
103
-
104
- // Strip a trailing | from the last segment only when it names a listed method;
105
- // otherwise it is a literal property name (e.g. an exotic key ending in |).
106
- const rawLastKey = pathParts[pathParts.length - 1];
107
- const isZeroArg = rawLastKey.endsWith('|')
108
- && (withMethods.has(rawLastKey.slice(0, -1)) || withAsyncMethods.has(rawLastKey.slice(0, -1)));
109
- const lastKey = isZeroArg ? rawLastKey.slice(0, -1) : rawLastKey;
110
- return {
111
- target: current,
112
- lastKey,
113
- isMethod: withMethods.has(lastKey),
114
- isAsyncMethod: withAsyncMethods.has(lastKey),
115
- isZeroArg
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 methodcall 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, permissions) {
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 }, permissions);
53
+ assignFrom(dest, pattern, { from: source, ...inheritedOptions, ...vector.withOptions }, permissionProcessor);
54
54
  });
55
55
  }
56
56
  else {
57
- assignGingerly(dest, pattern, inheritedOptions, permissions);
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 }, permissions);
65
+ assignFrom(host, shorthand, { from: source, ...inheritedOptions, ...vector.withOptions }, permissionProcessor);
66
66
  });
67
67
  }
68
68
  else {
69
- assignGingerly(host, shorthand, inheritedOptions, permissions);
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, permissions) {
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, permissions);
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, permissions);
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, permissions);
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, permissions);
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, permissions);
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
- permissions?: AssignPermissions
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 }, permissions);
68
+ assignFrom(dest, pattern, { from: source, ...inheritedOptions, ...vector.withOptions }, permissionProcessor);
70
69
  });
71
70
  } else {
72
- assignGingerly(dest, pattern, inheritedOptions, permissions);
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 }, permissions);
80
+ assignFrom(host, shorthand, { from: source, ...inheritedOptions, ...vector.withOptions }, permissionProcessor);
82
81
  });
83
82
  } else {
84
- assignGingerly(host, shorthand, inheritedOptions, permissions);
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
- permissions?: AssignPermissions
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, permissions);
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, permissions);
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, permissions);
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, permissions);
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, permissions);
155
+ processVector(fromEvent, event, target, host, lhs, inheritedOptions, true, permissionProcessor);
157
156
  }
158
157
 
159
158
  // Dispatch custom event if configured