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/assignGingerly.ts CHANGED
@@ -1,11 +1,11 @@
1
1
 
2
2
 
3
- import { EnhancementConfig } from "./types/assign-gingerly/types";
3
+ import { EnhancementConfig, EnhKey } from "./types/assign-gingerly/types";
4
4
  import type { AssignFromOptions, FeatureConfigsMap, IAssignGingerlyOptions } from "./types/assign-gingerly/types";
5
- import type { AssignPermissions } from './types/assign-gingerly/types.js';
6
- import { buildRestrictedPropSet, checkRestrictedProp, redirectRestrictedProp } from './assignPermissions/restrictedProps.js';
7
- import type { RestrictedPropSettingsMap } from './assignPermissions/restrictedProps.js';
8
- import { normalizeAliasOptions } from './getValues.js';
5
+ import type { PermissionProcessor } from './types/assign-gingerly/types.js';
6
+ import { normalizeAliasOptions } from './resolve/getValues.js';
7
+
8
+ const DEFAULT_ENHANCEMENT_KEY = Symbol('assign-gingerly.default-enhancement-setup');
9
9
 
10
10
  /**
11
11
  * Constructor signature for ItemScope Manager classes
@@ -74,6 +74,7 @@ export class EnhancementRegisteredEvent extends Event {
74
74
  */
75
75
  export class EnhancementRegistry extends EventTarget {
76
76
  #items: Set<EnhancementConfig> = new Set();
77
+ #pendingSetups = new Map<EnhKey, Promise<void>[]>();
77
78
 
78
79
  push(items: EnhancementConfig | EnhancementConfig[]): void {
79
80
  if (Array.isArray(items)) {
@@ -85,11 +86,12 @@ export class EnhancementRegistry extends EventTarget {
85
86
  // Dispatch event after adding items
86
87
  this.dispatchEvent(new EnhancementRegisteredEvent(items));
87
88
 
88
- // Process features if present (fire-and-forget, uses shared featuresRegistry)
89
+ // Process features if present (async, tracked so callers can await)
89
90
  const itemsArr = Array.isArray(items) ? items : [items];
90
91
  for (const item of itemsArr) {
91
92
  if (item.features) {
92
- this.#assignFeatures(item.spawn, item.features);
93
+ const promise = this.#assignFeatures(item.spawn, item.features);
94
+ this._trackSetup(item.enhKey ?? DEFAULT_ENHANCEMENT_KEY, promise);
93
95
  }
94
96
  }
95
97
  }
@@ -99,8 +101,42 @@ export class EnhancementRegistry extends EventTarget {
99
101
  const featuresRegistry = (this as any)._featuresRegistry
100
102
  ?? (typeof customElements !== 'undefined' ? (customElements as any).featuresRegistry : undefined);
101
103
  if (featuresRegistry) {
102
- assignFeatures(spawn, features, featuresRegistry);
104
+ await assignFeatures(spawn, features, featuresRegistry);
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Wait for all pending setups for a given enhancement key to complete.
110
+ * @param enhKey - Enhancement key to wait for
111
+ */
112
+ async whenDefined(enhKey: EnhKey): Promise<void> {
113
+ const pending = this.#pendingSetups.get(enhKey);
114
+ if (pending && pending.length > 0) {
115
+ await Promise.all(pending);
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Internal method to track a pending setup.
121
+ * @param name - Setup key
122
+ * @param promise - Promise representing the setup operation
123
+ */
124
+ _trackSetup(name: EnhKey, promise: Promise<void>): void {
125
+ if (!this.#pendingSetups.has(name)) {
126
+ this.#pendingSetups.set(name, []);
103
127
  }
128
+ this.#pendingSetups.get(name)!.push(promise);
129
+
130
+ // Clean up after completion
131
+ promise.finally(() => {
132
+ const pending = this.#pendingSetups.get(name);
133
+ if (pending) {
134
+ const index = pending.indexOf(promise);
135
+ if (index > -1) {
136
+ pending.splice(index, 1);
137
+ }
138
+ }
139
+ });
104
140
  }
105
141
 
106
142
  getItems(): EnhancementConfig[] {
@@ -153,9 +189,10 @@ export class ItemscopeRegistry extends EventTarget {
153
189
  this.#configs.set(name, config);
154
190
  this.dispatchEvent(new Event(name));
155
191
 
156
- // Process features if present (fire-and-forget, uses shared featuresRegistry)
192
+ // Process features if present (async, tracked so callers can await)
157
193
  if ((config as any).features) {
158
- this.#assignFeatures(config.manager, (config as any).features);
194
+ const promise = this.#assignFeatures(config.manager, (config as any).features);
195
+ this._trackSetup(name, promise);
159
196
  }
160
197
  }
161
198
 
@@ -164,7 +201,7 @@ export class ItemscopeRegistry extends EventTarget {
164
201
  const featuresRegistry = (this as any)._featuresRegistry
165
202
  ?? (typeof customElements !== 'undefined' ? (customElements as any).featuresRegistry : undefined);
166
203
  if (featuresRegistry) {
167
- assignFeatures(manager, features, featuresRegistry);
204
+ await assignFeatures(manager, features, featuresRegistry);
168
205
  }
169
206
  }
170
207
 
@@ -250,30 +287,30 @@ function isIncCommand(key: string): boolean {
250
287
  /**
251
288
  * Helper function to parse an += command and extract the path
252
289
  */
253
- function parseIncCommand(key: string): string | null {
254
- if (!isIncCommand(key)) {
255
- return null;
256
- }
257
- return key.substring(0, key.length - 3); // Remove ' +=' suffix
258
- }
259
-
260
- /**
261
- * Apply the scalar and array semantics of the += command.
262
- */
263
- function addValue(lhs: any, rhs: any): any {
264
- if (Array.isArray(lhs)) {
265
- return Array.isArray(rhs) ? [...lhs, ...rhs] : [...lhs, rhs];
266
- }
267
- if (typeof lhs === 'number' && typeof rhs === 'string') {
268
- const parsed = Number(rhs);
269
- return Number.isNaN(parsed) ? lhs + rhs : lhs + parsed;
270
- }
271
- if (typeof lhs === 'string' && typeof rhs === 'number') {
272
- const parsed = Number(lhs);
273
- return Number.isNaN(parsed) ? lhs + rhs : (parsed + rhs).toString();
274
- }
275
- return lhs + rhs;
276
- }
290
+ function parseIncCommand(key: string): string | null {
291
+ if (!isIncCommand(key)) {
292
+ return null;
293
+ }
294
+ return key.substring(0, key.length - 3); // Remove ' +=' suffix
295
+ }
296
+
297
+ /**
298
+ * Apply the scalar and array semantics of the += command.
299
+ */
300
+ function addValue(lhs: any, rhs: any): any {
301
+ if (Array.isArray(lhs)) {
302
+ return Array.isArray(rhs) ? [...lhs, ...rhs] : [...lhs, rhs];
303
+ }
304
+ if (typeof lhs === 'number' && typeof rhs === 'string') {
305
+ const parsed = Number(rhs);
306
+ return Number.isNaN(parsed) ? lhs + rhs : lhs + parsed;
307
+ }
308
+ if (typeof lhs === 'string' && typeof rhs === 'number') {
309
+ const parsed = Number(lhs);
310
+ return Number.isNaN(parsed) ? lhs + rhs : (parsed + rhs).toString();
311
+ }
312
+ return lhs + rhs;
313
+ }
277
314
 
278
315
  /**
279
316
  * Helper function to check if a key represents a =! command
@@ -444,6 +481,19 @@ export function isClassInstance(value: any): boolean {
444
481
  return proto !== Object.prototype && proto !== null;
445
482
  }
446
483
 
484
+ /**
485
+ * Check whether a method name is listed in withMethods and is not restricted
486
+ * by the permission processor. Restricted methods are treated as non-method
487
+ * property names so they fall through to normal access.
488
+ */
489
+ export function isAllowedMethod(
490
+ methodName: string,
491
+ withMethods: Set<string>,
492
+ permissionProcessor?: PermissionProcessor
493
+ ): boolean {
494
+ return withMethods.has(methodName) && !permissionProcessor?.checkRestrictedMethod(methodName);
495
+ }
496
+
447
497
  /**
448
498
  * Helper function to evaluate a nested path with method calls
449
499
  * Handles chained method calls where path segments can be methods
@@ -454,7 +504,8 @@ export function evaluatePathWithMethods(
454
504
  target: any,
455
505
  pathParts: string[],
456
506
  value: any,
457
- withMethods: Set<string>
507
+ withMethods: Set<string>,
508
+ permissionProcessor?: PermissionProcessor
458
509
  ): { target: any; lastKey: string; isMethod: boolean; isZeroArg: boolean } {
459
510
  let current = target;
460
511
  let i = 0;
@@ -466,11 +517,11 @@ export function evaluatePathWithMethods(
466
517
 
467
518
  // A trailing | marks a zero-argument method call: 'deref|' calls deref()
468
519
  // without consuming the next segment. Only applies to names in withMethods.
469
- const isZeroArgMethod = part.endsWith('|') && withMethods.has(part.slice(0, -1));
520
+ const isZeroArgMethod = part.endsWith('|') && isAllowedMethod(part.slice(0, -1), withMethods, permissionProcessor);
470
521
  const nextIsMethod = withMethods.has(nextPart)
471
522
  || (nextPart.endsWith('|') && withMethods.has(nextPart.slice(0, -1)));
472
523
 
473
- if (withMethods.has(part) || isZeroArgMethod) {
524
+ if (isAllowedMethod(part, withMethods, permissionProcessor) || isZeroArgMethod) {
474
525
  const methodName = isZeroArgMethod ? part.slice(0, -1) : part;
475
526
  const method = current[methodName];
476
527
  if (typeof method === 'function') {
@@ -503,12 +554,12 @@ export function evaluatePathWithMethods(
503
554
  // Strip a trailing | from the last segment only when it names a listed method;
504
555
  // otherwise it is a literal property name (e.g. an exotic key ending in |).
505
556
  const rawLastKey = pathParts[pathParts.length - 1];
506
- const isZeroArg = rawLastKey.endsWith('|') && withMethods.has(rawLastKey.slice(0, -1));
557
+ const isZeroArg = rawLastKey.endsWith('|') && isAllowedMethod(rawLastKey.slice(0, -1), withMethods, permissionProcessor);
507
558
  const lastKey = isZeroArg ? rawLastKey.slice(0, -1) : rawLastKey;
508
559
  return {
509
560
  target: current,
510
561
  lastKey,
511
- isMethod: withMethods.has(lastKey),
562
+ isMethod: isAllowedMethod(lastKey, withMethods, permissionProcessor),
512
563
  isZeroArg
513
564
  };
514
565
  }
@@ -568,8 +619,7 @@ function applyToEach(
568
619
  withMethods: Set<string>,
569
620
  aliasMap: Map<string, string>,
570
621
  options?: IAssignGingerlyOptions,
571
- permissions?: AssignPermissions,
572
- restrictedPropSet?: RestrictedPropSettingsMap
622
+ permissionProcessor?: PermissionProcessor
573
623
  ): void {
574
624
  // Convert to array for iteration
575
625
  const items = Array.isArray(iterable) ? iterable : Array.from(iterable);
@@ -594,8 +644,8 @@ function applyToEach(
594
644
  let current = item;
595
645
  for (const part of pathToForEach) {
596
646
  // A trailing | marks a zero-argument method call (only for names in withMethods)
597
- const isZeroArgMethod = part.endsWith('|') && withMethods.has(part.slice(0, -1));
598
- if (withMethods.has(part) || isZeroArgMethod) {
647
+ const isZeroArgMethod = part.endsWith('|') && isAllowedMethod(part.slice(0, -1), withMethods, permissionProcessor);
648
+ if (isAllowedMethod(part, withMethods, permissionProcessor) || isZeroArgMethod) {
599
649
  const methodName = isZeroArgMethod ? part.slice(0, -1) : part;
600
650
  const method = current[methodName];
601
651
  if (typeof method === 'function') {
@@ -627,11 +677,11 @@ function applyToEach(
627
677
 
628
678
  // Recursively apply to the nested iterable
629
679
  if (isIterable(current)) {
630
- applyToEach(current, pathAfterForEach, value, withMethods, aliasMap, options, permissions, restrictedPropSet);
680
+ applyToEach(current, pathAfterForEach, value, withMethods, aliasMap, options, permissionProcessor);
631
681
  }
632
682
  } else {
633
683
  // No nested @each, evaluate the remaining path normally
634
- const result = evaluatePathWithMethods(item, remainingPath, value, withMethods);
684
+ const result = evaluatePathWithMethods(item, remainingPath, value, withMethods, permissionProcessor);
635
685
 
636
686
  if (result.isMethod) {
637
687
  // Last segment is a method - call it
@@ -651,7 +701,7 @@ function applyToEach(
651
701
  const lastKey = result.lastKey;
652
702
  const parent = result.target;
653
703
 
654
- if (redirectRestrictedProp(restrictedPropSet, parent, lastKey, value)) {
704
+ if (permissionProcessor?.redirectRestrictedProp(parent, lastKey, value)) {
655
705
  // skip
656
706
  } else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
657
707
  if (lastKey in parent && isReadonlyProperty(parent, lastKey)) {
@@ -659,7 +709,7 @@ function applyToEach(
659
709
  if (typeof currentValue !== 'object' || currentValue === null) {
660
710
  throw new Error(`Cannot merge object into readonly primitive property '${String(lastKey)}'`);
661
711
  }
662
- assignGingerly(currentValue, value, options, permissions);
712
+ assignGingerly(currentValue, value, options, permissionProcessor);
663
713
  } else {
664
714
  parent[lastKey] = value;
665
715
  }
@@ -702,7 +752,7 @@ export function assignGingerly(
702
752
  target: any,
703
753
  source: Record<string | symbol, any>,
704
754
  options?: IAssignGingerlyOptions,
705
- permissions?: AssignPermissions
755
+ permissionProcessor?: PermissionProcessor
706
756
  ): any {
707
757
  if (!target || typeof target !== 'object') {
708
758
  return target;
@@ -710,9 +760,6 @@ export function assignGingerly(
710
760
 
711
761
  const { aliasMap, withMethods: withMethodsSet } = normalizeAliasOptions(options);
712
762
 
713
- // Normalize restrictedPropSettings once per top-level call
714
- const restrictedPropSet = buildRestrictedPropSet(permissions);
715
-
716
763
  // Convert withAsyncMethods array to Set for O(1) lookup
717
764
  const withAsyncMethodsSet = options?.withAsyncMethods
718
765
  ? options.withAsyncMethods instanceof Set
@@ -800,7 +847,7 @@ export function assignGingerly(
800
847
  let lhsParent: any;
801
848
  let lhsKey: string;
802
849
  if (withMethodsSet) {
803
- const result = evaluatePathWithMethods(target, pathParts, value, withMethodsSet);
850
+ const result = evaluatePathWithMethods(target, pathParts, value, withMethodsSet, permissionProcessor);
804
851
  lhsParent = result.target;
805
852
  lhsKey = result.lastKey;
806
853
  lhsValue = lhsParent[lhsKey];
@@ -811,49 +858,49 @@ export function assignGingerly(
811
858
  }
812
859
 
813
860
  //TODO: this logic seems to occur twice at least. Maybe make it a method?
814
- if (checkRestrictedProp(restrictedPropSet, lhsKey)) {
815
- continue;
816
- }
817
-
818
- // Event handler: Element LHS + object RHS with 'on' property
861
+ if (permissionProcessor?.checkRestrictedProp(lhsKey)) {
862
+ continue;
863
+ }
864
+
865
+ // Event handler: Element LHS + object RHS with 'on' property
819
866
  if (lhsValue instanceof Element && value && typeof value === 'object' && !Array.isArray(value) && 'on' in value) {
820
867
  const capturedLhs = lhsValue;
821
868
  const capturedValue = value;
822
869
  const capturedTarget = target;
823
- const capturedOptions = options as AssignFromOptions | undefined;
824
- import('./handlers/addEventListener.js').then(({ attachEventListener }) => {
825
- attachEventListener(capturedLhs, capturedValue, capturedTarget, capturedOptions?.from ?? capturedTarget, capturedOptions ?? {}, permissions);
870
+ const capturedOptions = options as AssignFromOptions | undefined;
871
+ import('./handlers/addEventListener.js').then(({ attachEventListener }) => {
872
+ attachEventListener(capturedLhs, capturedValue, capturedTarget, capturedOptions?.from ?? capturedTarget, capturedOptions ?? {}, permissionProcessor);
826
873
  });
827
874
  continue;
828
875
  }
829
876
 
830
- if (!(lhsKey in lhsParent)) {
831
- lhsParent[lhsKey] = value;
832
- } else {
833
- lhsParent[lhsKey] = addValue(lhsValue, value);
877
+ if (!(lhsKey in lhsParent)) {
878
+ lhsParent[lhsKey] = value;
879
+ } else {
880
+ lhsParent[lhsKey] = addValue(lhsValue, value);
834
881
  }
835
882
  } else {
836
- // Plain key - direct operation on target
837
- if (checkRestrictedProp(restrictedPropSet, path)) {
838
- continue;
839
- }
840
-
841
- // Event handler: Element LHS + object RHS with 'on' property
883
+ // Plain key - direct operation on target
884
+ if (permissionProcessor?.checkRestrictedProp(path)) {
885
+ continue;
886
+ }
887
+
888
+ // Event handler: Element LHS + object RHS with 'on' property
842
889
  if (target[path] instanceof Element && value && typeof value === 'object' && !Array.isArray(value) && 'on' in value) {
843
890
  const capturedLhs = target[path];
844
891
  const capturedValue = value;
845
892
  const capturedTarget = target;
846
- const capturedOptions = options as AssignFromOptions | undefined;
847
- import('./handlers/addEventListener.js').then(({ attachEventListener }) => {
848
- attachEventListener(capturedLhs, capturedValue, capturedTarget, capturedOptions?.from ?? capturedTarget, capturedOptions ?? {}, permissions);
893
+ const capturedOptions = options as AssignFromOptions | undefined;
894
+ import('./handlers/addEventListener.js').then(({ attachEventListener }) => {
895
+ attachEventListener(capturedLhs, capturedValue, capturedTarget, capturedOptions?.from ?? capturedTarget, capturedOptions ?? {}, permissionProcessor);
849
896
  });
850
897
  continue;
851
898
  }
852
899
 
853
- if (!(path in target)) {
854
- target[path] = value;
855
- } else {
856
- target[path] = addValue(target[path], value);
900
+ if (!(path in target)) {
901
+ target[path] = value;
902
+ } else {
903
+ target[path] = addValue(target[path], value);
857
904
  }
858
905
  }
859
906
  }
@@ -909,7 +956,7 @@ export function assignGingerly(
909
956
  }
910
957
 
911
958
  // Apply negation to LHS — check restriction first
912
- if (!checkRestrictedProp(restrictedPropSet, lhsLastKey)) {
959
+ if (!permissionProcessor?.checkRestrictedProp(lhsLastKey)) {
913
960
  lhsParent[lhsLastKey] = !valueToNegate;
914
961
  }
915
962
  }
@@ -961,19 +1008,19 @@ export function assignGingerly(
961
1008
  }
962
1009
 
963
1010
  // Handle Y= merge commands (recursive assignGingerly into sub-object)
964
- if (isMergeCommand(key)) {
965
- const path = parseMergeCommand(key);
966
- if (path) {
967
- const pathParts = isNestedPath(path) ? parsePath(path) : [path];
968
- const lastKey = pathParts[pathParts.length - 1];
969
- if (checkRestrictedProp(restrictedPropSet, lastKey)) {
970
- continue;
971
- }
972
- // Navigate to the target sub-object
973
- let mergeTarget: any;
1011
+ if (isMergeCommand(key)) {
1012
+ const path = parseMergeCommand(key);
1013
+ if (path) {
1014
+ const pathParts = isNestedPath(path) ? parsePath(path) : [path];
1015
+ const lastKey = pathParts[pathParts.length - 1];
1016
+ if (permissionProcessor?.checkRestrictedProp(lastKey)) {
1017
+ continue;
1018
+ }
1019
+ // Navigate to the target sub-object
1020
+ let mergeTarget: any;
974
1021
  if (isNestedPath(path)) {
975
1022
  if (withMethodsSet) {
976
- const result = evaluatePathWithMethods(target, parsePath(path), value, withMethodsSet);
1023
+ const result = evaluatePathWithMethods(target, parsePath(path), value, withMethodsSet, permissionProcessor);
977
1024
  mergeTarget = result.target[result.lastKey];
978
1025
  } else {
979
1026
  const pathParts = parsePath(path);
@@ -993,7 +1040,7 @@ export function assignGingerly(
993
1040
 
994
1041
  // Recursively merge if target is a valid object
995
1042
  if (mergeTarget && typeof mergeTarget === 'object') {
996
- assignGingerly(mergeTarget, value, options, permissions);
1043
+ assignGingerly(mergeTarget, value, options, permissionProcessor);
997
1044
  }
998
1045
  }
999
1046
  continue;
@@ -1021,11 +1068,10 @@ export function assignGingerly(
1021
1068
  pathParts,
1022
1069
  forEachIndex,
1023
1070
  value,
1024
- withMethodsSet,
1025
- aliasMap,
1026
- options,
1027
- permissions,
1028
- restrictedPropSet
1071
+ withMethodsSet,
1072
+ aliasMap,
1073
+ options,
1074
+ permissionProcessor
1029
1075
  );
1030
1076
  } catch (error) {
1031
1077
  console.error('Error in @eachTime:', error);
@@ -1042,7 +1088,7 @@ export function assignGingerly(
1042
1088
  let current = target;
1043
1089
  if (pathToForEach.length > 0) {
1044
1090
  if (withMethodsSet) {
1045
- const result = evaluatePathWithMethods(target, pathToForEach, value, withMethodsSet);
1091
+ const result = evaluatePathWithMethods(target, pathToForEach, value, withMethodsSet, permissionProcessor);
1046
1092
  // The result.target is the current position after evaluating the path
1047
1093
  // This is already the iterable we want
1048
1094
  current = result.target;
@@ -1055,7 +1101,7 @@ export function assignGingerly(
1055
1101
 
1056
1102
  // Apply to each item in the iterable
1057
1103
  if (isIterable(current)) {
1058
- applyToEach(current, pathAfterForEach, value, withMethodsSet || new Set(), aliasMap, options, permissions, restrictedPropSet);
1104
+ applyToEach(current, pathAfterForEach, value, withMethodsSet || new Set(), aliasMap, options, permissionProcessor);
1059
1105
  }
1060
1106
  // If not iterable, let JavaScript throw error naturally when trying to iterate
1061
1107
 
@@ -1069,15 +1115,15 @@ export function assignGingerly(
1069
1115
  const capturedTarget = target;
1070
1116
  const capturedPathParts = pathParts;
1071
1117
  const capturedValue = value;
1072
- const capturedWithMethodsSet = withMethodsSet || new Set<string>();
1073
- const capturedOptions = options;
1074
- const capturedPermissions = permissions;
1075
- const capturedRestrictedPropSet = restrictedPropSet;
1118
+ const capturedWithMethodsSet = withMethodsSet || new Set<string>();
1119
+ const capturedOptions = options;
1120
+ const capturedPermissionProcessor = permissionProcessor;
1076
1121
  (async () => {
1077
1122
  const { evaluatePathWithAsyncMethods } = await import('./evaluatePathWithAsyncMethods.js');
1078
1123
  const result = await evaluatePathWithAsyncMethods(
1079
1124
  capturedTarget, capturedPathParts, capturedValue,
1080
- capturedWithMethodsSet, withAsyncMethodsSet
1125
+ capturedWithMethodsSet, withAsyncMethodsSet,
1126
+ capturedPermissionProcessor
1081
1127
  );
1082
1128
 
1083
1129
  if (result.isMethod || result.isAsyncMethod) {
@@ -1096,16 +1142,16 @@ export function assignGingerly(
1096
1142
  } else {
1097
1143
  // Not a method — assign the value
1098
1144
  const lastKey = result.lastKey;
1099
- const parent = result.target;
1100
- if (redirectRestrictedProp(capturedRestrictedPropSet, parent, lastKey, capturedValue)) {
1101
- // skip
1102
- } else if (typeof capturedValue === 'object' && capturedValue !== null && !Array.isArray(capturedValue)) {
1145
+ const parent = result.target;
1146
+ if (capturedPermissionProcessor?.redirectRestrictedProp(parent, lastKey, capturedValue)) {
1147
+ // skip
1148
+ } else if (typeof capturedValue === 'object' && capturedValue !== null && !Array.isArray(capturedValue)) {
1103
1149
  if (lastKey in parent && isReadonlyProperty(parent, lastKey)) {
1104
1150
  const currentValue = parent[lastKey];
1105
1151
  if (typeof currentValue !== 'object' || currentValue === null) {
1106
1152
  throw new Error(`Cannot merge object into readonly primitive property '${String(lastKey)}'`);
1107
1153
  }
1108
- assignGingerly(currentValue, capturedValue, capturedOptions, capturedPermissions);
1154
+ assignGingerly(currentValue, capturedValue, capturedOptions, capturedPermissionProcessor);
1109
1155
  } else {
1110
1156
  parent[lastKey] = capturedValue;
1111
1157
  }
@@ -1119,7 +1165,7 @@ export function assignGingerly(
1119
1165
 
1120
1166
  // Check if we need to handle methods
1121
1167
  if (withMethodsSet) {
1122
- const result = evaluatePathWithMethods(target, pathParts, value, withMethodsSet);
1168
+ const result = evaluatePathWithMethods(target, pathParts, value, withMethodsSet, permissionProcessor);
1123
1169
 
1124
1170
  if (result.isMethod) {
1125
1171
  // Last segment is a method - call it
@@ -1142,19 +1188,19 @@ export function assignGingerly(
1142
1188
  const lastKey = result.lastKey;
1143
1189
  const parent = result.target;
1144
1190
 
1145
- if (redirectRestrictedProp(restrictedPropSet, parent, lastKey, value)) {
1146
- // skip
1147
- // Check for static assignTo protocol
1148
- } else if (lastKey in parent && tryAssignTo(parent[lastKey], value, parent, lastKey)) {
1149
- continue;
1150
- } else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
1191
+ if (permissionProcessor?.redirectRestrictedProp(parent, lastKey, value)) {
1192
+ // skip
1193
+ // Check for static assignTo protocol
1194
+ } else if (lastKey in parent && tryAssignTo(parent[lastKey], value, parent, lastKey)) {
1195
+ continue;
1196
+ } else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
1151
1197
  // Check if property exists and is readonly
1152
1198
  if (lastKey in parent && isReadonlyProperty(parent, lastKey)) {
1153
1199
  const currentValue = parent[lastKey];
1154
1200
  if (typeof currentValue !== 'object' || currentValue === null) {
1155
1201
  throw new Error(`Cannot merge object into readonly primitive property '${String(lastKey)}'`);
1156
1202
  }
1157
- assignGingerly(currentValue, value, options, permissions);
1203
+ assignGingerly(currentValue, value, options, permissionProcessor);
1158
1204
  } else {
1159
1205
  // Property is writable - replace it
1160
1206
  parent[lastKey] = value;
@@ -1167,12 +1213,12 @@ export function assignGingerly(
1167
1213
  const lastKey = pathParts[pathParts.length - 1];
1168
1214
  const parent = ensureNestedPath(target, pathParts);
1169
1215
 
1170
- if (redirectRestrictedProp(restrictedPropSet, parent, lastKey, value)) {
1171
- // skip
1172
- // Check for static assignTo protocol
1173
- } else if (lastKey in parent && tryAssignTo(parent[lastKey], value, parent, lastKey)) {
1174
- continue;
1175
- } else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
1216
+ if (permissionProcessor?.redirectRestrictedProp(parent, lastKey, value)) {
1217
+ // skip
1218
+ // Check for static assignTo protocol
1219
+ } else if (lastKey in parent && tryAssignTo(parent[lastKey], value, parent, lastKey)) {
1220
+ continue;
1221
+ } else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
1176
1222
  // Check if property exists and is readonly
1177
1223
  if (lastKey in parent && isReadonlyProperty(parent, lastKey)) {
1178
1224
  // Property is readonly - check if current value is an object
@@ -1181,7 +1227,7 @@ export function assignGingerly(
1181
1227
  throw new Error(`Cannot merge object into readonly primitive property '${String(lastKey)}'`);
1182
1228
  }
1183
1229
  // Recursively apply assignGingerly to the readonly object
1184
- assignGingerly(currentValue, value, options, permissions);
1230
+ assignGingerly(currentValue, value, options, permissionProcessor);
1185
1231
  } else {
1186
1232
  // Property is writable - replace it
1187
1233
  parent[lastKey] = value;
@@ -1212,12 +1258,12 @@ export function assignGingerly(
1212
1258
  continue;
1213
1259
  }
1214
1260
 
1215
- if (redirectRestrictedProp(restrictedPropSet, target, key, value)) {
1216
- // skip
1217
- // Check for static assignTo protocol
1218
- } else if (key in target && tryAssignTo(target[key], value, target, key)) {
1219
- continue;
1220
- } else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
1261
+ if (permissionProcessor?.redirectRestrictedProp(target, key, value)) {
1262
+ // skip
1263
+ // Check for static assignTo protocol
1264
+ } else if (key in target && tryAssignTo(target[key], value, target, key)) {
1265
+ continue;
1266
+ } else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
1221
1267
  // Check if property exists and is readonly
1222
1268
  if (key in target && isReadonlyProperty(target, key)) {
1223
1269
  // Property is readonly - check if current value is an object
@@ -1226,7 +1272,7 @@ export function assignGingerly(
1226
1272
  throw new Error(`Cannot merge object into readonly primitive property '${String(key)}'`);
1227
1273
  }
1228
1274
  // Recursively apply assignGingerly to the readonly object
1229
- assignGingerly(currentValue, value, options, permissions);
1275
+ assignGingerly(currentValue, value, options, permissionProcessor);
1230
1276
  } else {
1231
1277
  // Property is writable - replace it
1232
1278
  target[key] = value;
@@ -1370,7 +1416,7 @@ export function assignGingerly(
1370
1416
  // Fire-and-forget bulk enhancements (async, non-blocking)
1371
1417
  if (options?.enhance && options.enhance.length > 0 && typeof target === 'object' && target instanceof Element) {
1372
1418
  import('./enhanceAll.js').then(({ enhanceAll }) => {
1373
- enhanceAll(target, options!.enhance!, permissions);
1419
+ enhanceAll(target, options!.enhance!, permissionProcessor);
1374
1420
  });
1375
1421
  }
1376
1422