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
@@ -0,0 +1,166 @@
1
+ import { isAllowedUrl } from './isAllowedUrl.js';
2
+ export class PermissionProcessor {
3
+ constructor(permissions) {
4
+ this.permissions = permissions;
5
+ const { props, attrs } = buildMaps(permissions);
6
+ this.props = props;
7
+ this.attrs = attrs;
8
+ this.methods = buildMethodSet(permissions);
9
+ this.warned = new Set();
10
+ this.warnedMethods = new Set();
11
+ }
12
+ get crossDomainImports() {
13
+ return !!this.permissions?.crossDomainImports;
14
+ }
15
+ get hasProps() {
16
+ return this.props.size > 0;
17
+ }
18
+ get hasAttrs() {
19
+ return this.attrs.size > 0;
20
+ }
21
+ checkRestrictedProp(key) {
22
+ if (!this.props.has(key))
23
+ return false;
24
+ this.warnRestricted(key);
25
+ return true;
26
+ }
27
+ checkRestrictedMethod(methodName) {
28
+ if (!this.methods.has(methodName))
29
+ return false;
30
+ this.warnRestrictedMethod(methodName);
31
+ return true;
32
+ }
33
+ redirectRestrictedProp(target, key, value) {
34
+ if (!this.props.has(key))
35
+ return false;
36
+ const setting = this.props.get(key);
37
+ const { allowCrossDomain, allowFromSameDomain } = setting || {};
38
+ if (allowCrossDomain) {
39
+ return this.applyAllowedSetting(setting, target, key, value);
40
+ }
41
+ if (allowFromSameDomain) {
42
+ if (isAllowedUrl(value)) {
43
+ return this.applyAllowedSetting(setting, target, key, value);
44
+ }
45
+ this.warnRestricted(key);
46
+ return true;
47
+ }
48
+ if (setting?.useMethod) {
49
+ const method = target?.[setting.useMethod];
50
+ if (typeof method === 'function') {
51
+ method.call(target, value);
52
+ return true;
53
+ }
54
+ }
55
+ this.warnRestricted(key);
56
+ return true;
57
+ }
58
+ checkRestrictedAttributeCall(methodName, args) {
59
+ if (methodName !== 'setAttribute' || args.length === 0 || this.attrs.size === 0) {
60
+ return { blocked: false };
61
+ }
62
+ const attrName = String(args[0]);
63
+ if (!this.attrs.has(attrName)) {
64
+ return { blocked: false };
65
+ }
66
+ const setting = this.attrs.get(attrName);
67
+ const value = args[1];
68
+ const { allowCrossDomain, allowFromSameDomain } = setting || {};
69
+ if (allowCrossDomain) {
70
+ return { blocked: false, attrName };
71
+ }
72
+ if (allowFromSameDomain) {
73
+ if (isAllowedUrl(value)) {
74
+ return { blocked: false, attrName };
75
+ }
76
+ this.warnRestricted(attrName);
77
+ return { blocked: true, attrName };
78
+ }
79
+ this.warnRestricted(attrName);
80
+ return { blocked: true, attrName };
81
+ }
82
+ applyAllowedSetting(setting, target, key, value) {
83
+ if (setting?.useMethod) {
84
+ const method = target?.[setting.useMethod];
85
+ if (typeof method === 'function') {
86
+ method.call(target, value);
87
+ return true;
88
+ }
89
+ this.warnRestricted(key);
90
+ return true;
91
+ }
92
+ return false;
93
+ }
94
+ warnRestricted(key) {
95
+ if (!this.warned.has(key)) {
96
+ this.warned.add(key);
97
+ console.warn(`assignGingerly: property '${key}' is in restrictedPropSettings — assignment skipped.`);
98
+ }
99
+ }
100
+ warnRestrictedMethod(methodName) {
101
+ if (!this.warnedMethods.has(methodName)) {
102
+ this.warnedMethods.add(methodName);
103
+ console.warn(`assignGingerly: method '${methodName}' is in restrictedMethodSettings — method call skipped.`);
104
+ }
105
+ }
106
+ }
107
+ function buildMaps(permissions) {
108
+ const settings = permissions?.restrictedPropSettings;
109
+ const props = new Map();
110
+ const attrs = new Map();
111
+ if (!settings || settings.length === 0) {
112
+ return { props, attrs };
113
+ }
114
+ for (const setting of settings) {
115
+ if (typeof setting === 'string') {
116
+ if (props.has(setting)) {
117
+ throw new Error(`assignGingerly: duplicate restrictedPropSettings entry for '${setting}'.`);
118
+ }
119
+ props.set(setting, undefined);
120
+ continue;
121
+ }
122
+ const propNames = normalizeStrings(setting.props);
123
+ if (propNames.length === 0) {
124
+ throw new Error('assignGingerly: restrictedPropSettings object entry must have a props value.');
125
+ }
126
+ for (const prop of propNames) {
127
+ if (props.has(prop)) {
128
+ throw new Error(`assignGingerly: duplicate restrictedPropSettings entry for '${prop}'.`);
129
+ }
130
+ props.set(prop, setting);
131
+ }
132
+ const attrNames = normalizeAttrNames(setting.attr, propNames);
133
+ for (const attr of attrNames) {
134
+ if (attrs.has(attr)) {
135
+ throw new Error(`assignGingerly: duplicate restrictedPropSettings attr entry for '${attr}'.`);
136
+ }
137
+ attrs.set(attr, setting);
138
+ }
139
+ }
140
+ return { props, attrs };
141
+ }
142
+ function normalizeStrings(value) {
143
+ if (value === undefined)
144
+ return [];
145
+ return Array.isArray(value) ? value : [value];
146
+ }
147
+ function normalizeAttrNames(attr, propNames) {
148
+ if (attr === undefined || attr === false)
149
+ return [];
150
+ if (attr === true)
151
+ return propNames;
152
+ return normalizeStrings(attr);
153
+ }
154
+ function buildMethodSet(permissions) {
155
+ const methodSettings = permissions?.restrictedMethodSettings;
156
+ const methods = new Set();
157
+ if (!methodSettings || methodSettings.length === 0) {
158
+ return methods;
159
+ }
160
+ for (const setting of methodSettings) {
161
+ if (typeof setting === 'string') {
162
+ methods.add(setting);
163
+ }
164
+ }
165
+ return methods;
166
+ }
@@ -0,0 +1,210 @@
1
+ import type { AssignPermissions, RestrictedPropSetting } from '../types/assign-gingerly/types.js';
2
+ import { isAllowedUrl } from './isAllowedUrl.js';
3
+
4
+ export interface RestrictedPropSettingsMap {
5
+ props: Map<string, RestrictedPropSetting | undefined>;
6
+ attrs: Map<string, RestrictedPropSetting | undefined>;
7
+ }
8
+
9
+ export class PermissionProcessor {
10
+ private readonly permissions: AssignPermissions | undefined;
11
+ private readonly props: Map<string, RestrictedPropSetting | undefined>;
12
+ private readonly attrs: Map<string, RestrictedPropSetting | undefined>;
13
+ private readonly methods: Set<string>;
14
+ private readonly warned = new Set<string>();
15
+ private readonly warnedMethods = new Set<string>();
16
+
17
+ constructor(permissions: AssignPermissions | undefined) {
18
+ this.permissions = permissions;
19
+ const { props, attrs } = buildMaps(permissions);
20
+ this.props = props;
21
+ this.attrs = attrs;
22
+ this.methods = buildMethodSet(permissions);
23
+ }
24
+
25
+ get crossDomainImports(): boolean {
26
+ return !!this.permissions?.crossDomainImports;
27
+ }
28
+
29
+ get hasProps(): boolean {
30
+ return this.props.size > 0;
31
+ }
32
+
33
+ get hasAttrs(): boolean {
34
+ return this.attrs.size > 0;
35
+ }
36
+
37
+ checkRestrictedProp(key: string): boolean {
38
+ if (!this.props.has(key)) return false;
39
+ this.warnRestricted(key);
40
+ return true;
41
+ }
42
+
43
+ checkRestrictedMethod(methodName: string): boolean {
44
+ if (!this.methods.has(methodName)) return false;
45
+ this.warnRestrictedMethod(methodName);
46
+ return true;
47
+ }
48
+
49
+ redirectRestrictedProp(target: any, key: string, value: any): boolean {
50
+ if (!this.props.has(key)) return false;
51
+ const setting = this.props.get(key);
52
+ const { allowCrossDomain, allowFromSameDomain } = setting || {};
53
+
54
+ if (allowCrossDomain) {
55
+ return this.applyAllowedSetting(setting, target, key, value);
56
+ }
57
+
58
+ if (allowFromSameDomain) {
59
+ if (isAllowedUrl(value)) {
60
+ return this.applyAllowedSetting(setting, target, key, value);
61
+ }
62
+ this.warnRestricted(key);
63
+ return true;
64
+ }
65
+
66
+ if (setting?.useMethod) {
67
+ const method = target?.[setting.useMethod];
68
+ if (typeof method === 'function') {
69
+ method.call(target, value);
70
+ return true;
71
+ }
72
+ }
73
+
74
+ this.warnRestricted(key);
75
+ return true;
76
+ }
77
+
78
+ checkRestrictedAttributeCall(
79
+ methodName: string,
80
+ args: any[]
81
+ ): { blocked: boolean; attrName?: string } {
82
+ if (methodName !== 'setAttribute' || args.length === 0 || this.attrs.size === 0) {
83
+ return { blocked: false };
84
+ }
85
+ const attrName = String(args[0]);
86
+ if (!this.attrs.has(attrName)) {
87
+ return { blocked: false };
88
+ }
89
+ const setting = this.attrs.get(attrName);
90
+ const value = args[1];
91
+ const { allowCrossDomain, allowFromSameDomain } = setting || {};
92
+
93
+ if (allowCrossDomain) {
94
+ return { blocked: false, attrName };
95
+ }
96
+
97
+ if (allowFromSameDomain) {
98
+ if (isAllowedUrl(value)) {
99
+ return { blocked: false, attrName };
100
+ }
101
+ this.warnRestricted(attrName);
102
+ return { blocked: true, attrName };
103
+ }
104
+
105
+ this.warnRestricted(attrName);
106
+ return { blocked: true, attrName };
107
+ }
108
+
109
+ private applyAllowedSetting(
110
+ setting: RestrictedPropSetting | undefined,
111
+ target: any,
112
+ key: string,
113
+ value: any
114
+ ): boolean {
115
+ if (setting?.useMethod) {
116
+ const method = target?.[setting.useMethod];
117
+ if (typeof method === 'function') {
118
+ method.call(target, value);
119
+ return true;
120
+ }
121
+ this.warnRestricted(key);
122
+ return true;
123
+ }
124
+ return false;
125
+ }
126
+
127
+ private warnRestricted(key: string): void {
128
+ if (!this.warned.has(key)) {
129
+ this.warned.add(key);
130
+ console.warn(`assignGingerly: property '${key}' is in restrictedPropSettings — assignment skipped.`);
131
+ }
132
+ }
133
+
134
+ private warnRestrictedMethod(methodName: string): void {
135
+ if (!this.warnedMethods.has(methodName)) {
136
+ this.warnedMethods.add(methodName);
137
+ console.warn(`assignGingerly: method '${methodName}' is in restrictedMethodSettings — method call skipped.`);
138
+ }
139
+ }
140
+ }
141
+
142
+ function buildMaps(permissions: AssignPermissions | undefined): RestrictedPropSettingsMap {
143
+ const settings = permissions?.restrictedPropSettings;
144
+ const props = new Map<string, RestrictedPropSetting | undefined>();
145
+ const attrs = new Map<string, RestrictedPropSetting | undefined>();
146
+ if (!settings || settings.length === 0) {
147
+ return { props, attrs };
148
+ }
149
+
150
+ for (const setting of settings) {
151
+ if (typeof setting === 'string') {
152
+ if (props.has(setting)) {
153
+ throw new Error(`assignGingerly: duplicate restrictedPropSettings entry for '${setting}'.`);
154
+ }
155
+ props.set(setting, undefined);
156
+ continue;
157
+ }
158
+
159
+ const propNames = normalizeStrings(setting.props);
160
+ if (propNames.length === 0) {
161
+ throw new Error('assignGingerly: restrictedPropSettings object entry must have a props value.');
162
+ }
163
+ for (const prop of propNames) {
164
+ if (props.has(prop)) {
165
+ throw new Error(`assignGingerly: duplicate restrictedPropSettings entry for '${prop}'.`);
166
+ }
167
+ props.set(prop, setting);
168
+ }
169
+
170
+ const attrNames = normalizeAttrNames(setting.attr, propNames);
171
+ for (const attr of attrNames) {
172
+ if (attrs.has(attr)) {
173
+ throw new Error(`assignGingerly: duplicate restrictedPropSettings attr entry for '${attr}'.`);
174
+ }
175
+ attrs.set(attr, setting);
176
+ }
177
+ }
178
+ return { props, attrs };
179
+ }
180
+
181
+ function normalizeStrings(value: string | string[] | undefined): string[] {
182
+ if (value === undefined) return [];
183
+ return Array.isArray(value) ? value : [value];
184
+ }
185
+
186
+ function normalizeAttrNames(
187
+ attr: boolean | string | string[] | undefined,
188
+ propNames: string[]
189
+ ): string[] {
190
+ if (attr === undefined || attr === false) return [];
191
+ if (attr === true) return propNames;
192
+ return normalizeStrings(attr);
193
+ }
194
+
195
+ function buildMethodSet(permissions: AssignPermissions | undefined): Set<string> {
196
+ const methodSettings = permissions?.restrictedMethodSettings;
197
+ const methods = new Set<string>();
198
+ if (!methodSettings || methodSettings.length === 0) {
199
+ return methods;
200
+ }
201
+
202
+ for (const setting of methodSettings) {
203
+ if (typeof setting === 'string') {
204
+ methods.add(setting);
205
+ }
206
+ // Phase II: object-form RestrictedMethodConfig entries are ignored for now.
207
+ }
208
+
209
+ return methods;
210
+ }
@@ -1,3 +1,4 @@
1
+ import { isAllowedUrl } from './isAllowedUrl.js';
1
2
  /**
2
3
  * Check whether an import specifier resolves to the current origin or is
3
4
  * covered by an import-map entry.
@@ -8,12 +9,7 @@ export function isAllowedImportPath(value) {
8
9
  if (!value || !(value.startsWith('./') || value.startsWith('../') || value.startsWith('/'))) {
9
10
  return isImportMapSpecifier(value);
10
11
  }
11
- try {
12
- return new URL(value, document.baseURI).origin === location.origin;
13
- }
14
- catch {
15
- return false;
16
- }
12
+ return isAllowedUrl(value);
17
13
  }
18
14
  function isImportMapSpecifier(value) {
19
15
  const importMaps = document.querySelectorAll('script[type="importmap"]');
@@ -1,3 +1,5 @@
1
+ import { isAllowedUrl } from './isAllowedUrl.js';
2
+
1
3
  /**
2
4
  * Check whether an import specifier resolves to the current origin or is
3
5
  * covered by an import-map entry.
@@ -8,11 +10,7 @@ export function isAllowedImportPath(value: string): boolean {
8
10
  return isImportMapSpecifier(value);
9
11
  }
10
12
 
11
- try {
12
- return new URL(value, document.baseURI).origin === location.origin;
13
- } catch {
14
- return false;
15
- }
13
+ return isAllowedUrl(value);
16
14
  }
17
15
 
18
16
  function isImportMapSpecifier(value: string): boolean {
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Check whether a URL string resolves to the current origin.
3
+ *
4
+ * Returns false for non-strings, empty strings, malformed URLs, or
5
+ * any URL whose origin differs from `location.origin`.
6
+ */
7
+ export function isAllowedUrl(value, base = document.baseURI) {
8
+ if (typeof document === 'undefined' || typeof location === 'undefined')
9
+ return false;
10
+ if (typeof value !== 'string' || value === '')
11
+ return false;
12
+ try {
13
+ return new URL(value, base).origin === location.origin;
14
+ }
15
+ catch {
16
+ return false;
17
+ }
18
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Check whether a URL string resolves to the current origin.
3
+ *
4
+ * Returns false for non-strings, empty strings, malformed URLs, or
5
+ * any URL whose origin differs from `location.origin`.
6
+ */
7
+ export function isAllowedUrl(value: string, base = document.baseURI): boolean {
8
+ if (typeof document === 'undefined' || typeof location === 'undefined') return false;
9
+ if (typeof value !== 'string' || value === '') return false;
10
+ try {
11
+ return new URL(value, base).origin === location.origin;
12
+ } catch {
13
+ return false;
14
+ }
15
+ }
@@ -1,4 +1,3 @@
1
- import { buildRestrictedPropSet, checkRestrictedProp } from './assignPermissions/restrictedProps.js';
2
1
  /**
3
2
  * Helper function to check if a string key represents an += command
4
3
  */
@@ -114,13 +113,12 @@ function getTopLevelKey(path) {
114
113
  /**
115
114
  * Main assignTentatively function with reversal support
116
115
  */
117
- export function assignTentatively(target, source, options, permissions) {
116
+ export function assignTentatively(target, source, options, permissionProcessor) {
118
117
  if (!target || typeof target !== 'object') {
119
118
  return target;
120
119
  }
121
120
  const reversal = options?.reversal || {};
122
121
  const trackedCreatedPaths = new Set();
123
- const restrictedPropSet = buildRestrictedPropSet(permissions);
124
122
  // Process all keys from source
125
123
  for (const key of Object.keys(source)) {
126
124
  const value = source[key];
@@ -132,7 +130,7 @@ export function assignTentatively(target, source, options, permissions) {
132
130
  const pathParts = parsePath(path);
133
131
  const topLevelKey = pathParts[0];
134
132
  const lastKey = pathParts[pathParts.length - 1];
135
- if (checkRestrictedProp(restrictedPropSet, lastKey)) {
133
+ if (permissionProcessor?.checkRestrictedProp(lastKey)) {
136
134
  continue;
137
135
  }
138
136
  // Track if we created a new top-level path (BEFORE calling ensureNestedPath)
@@ -155,7 +153,7 @@ export function assignTentatively(target, source, options, permissions) {
155
153
  }
156
154
  else {
157
155
  // Plain key - direct operation on target
158
- if (checkRestrictedProp(restrictedPropSet, path)) {
156
+ if (permissionProcessor?.checkRestrictedProp(path)) {
159
157
  continue;
160
158
  }
161
159
  if (path in target) {
@@ -183,7 +181,7 @@ export function assignTentatively(target, source, options, permissions) {
183
181
  lhsPathParts = parsePath(lhsPath);
184
182
  const topLevelKey = lhsPathParts[0];
185
183
  lhsLastKey = lhsPathParts[lhsPathParts.length - 1];
186
- if (checkRestrictedProp(restrictedPropSet, lhsLastKey)) {
184
+ if (permissionProcessor?.checkRestrictedProp(lhsLastKey)) {
187
185
  continue;
188
186
  }
189
187
  // Track if we created a new top-level path (BEFORE calling ensureNestedPath)
@@ -195,7 +193,7 @@ export function assignTentatively(target, source, options, permissions) {
195
193
  else {
196
194
  lhsPathParts = [lhsPath];
197
195
  lhsLastKey = lhsPath;
198
- if (checkRestrictedProp(restrictedPropSet, lhsLastKey)) {
196
+ if (permissionProcessor?.checkRestrictedProp(lhsLastKey)) {
199
197
  continue;
200
198
  }
201
199
  lhsParent = target;
@@ -301,7 +299,7 @@ export function assignTentatively(target, source, options, permissions) {
301
299
  const pathParts = parsePath(key);
302
300
  const topLevelKey = pathParts[0];
303
301
  const lastKey = pathParts[pathParts.length - 1];
304
- if (checkRestrictedProp(restrictedPropSet, lastKey)) {
302
+ if (permissionProcessor?.checkRestrictedProp(lastKey)) {
305
303
  continue;
306
304
  }
307
305
  // Track if we created a new top-level path (BEFORE calling ensureNestedPath)
@@ -323,7 +321,7 @@ export function assignTentatively(target, source, options, permissions) {
323
321
  }
324
322
  // For nested objects, recursively apply with nested reversal tracking
325
323
  const nestedReversal = {};
326
- assignTentatively(parent[lastKey], value, { reversal: nestedReversal }, permissions);
324
+ assignTentatively(parent[lastKey], value, { reversal: nestedReversal }, permissionProcessor);
327
325
  // Merge nested reversals
328
326
  for (const revKey of Object.keys(nestedReversal)) {
329
327
  if (!(revKey in reversal)) {
@@ -344,7 +342,7 @@ export function assignTentatively(target, source, options, permissions) {
344
342
  }
345
343
  else {
346
344
  // Non-nested key
347
- if (checkRestrictedProp(restrictedPropSet, key)) {
345
+ if (permissionProcessor?.checkRestrictedProp(key)) {
348
346
  continue;
349
347
  }
350
348
  if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
@@ -359,7 +357,7 @@ export function assignTentatively(target, source, options, permissions) {
359
357
  target[key] = {};
360
358
  }
361
359
  const nestedReversal = {};
362
- assignTentatively(target[key], value, { reversal: nestedReversal }, permissions);
360
+ assignTentatively(target[key], value, { reversal: nestedReversal }, permissionProcessor);
363
361
  // Merge nested reversals
364
362
  for (const revKey of Object.keys(nestedReversal)) {
365
363
  if (!(revKey in reversal)) {
@@ -1,9 +1,7 @@
1
1
  /**
2
2
  * assignTentatively — reversible assignment with change tracking.
3
3
  */
4
- import type { IAssignTentativelyOptions } from './types/assign-gingerly/types.js';
5
- import type { AssignPermissions } from './types/assign-gingerly/types.js';
6
- import { buildRestrictedPropSet, checkRestrictedProp } from './assignPermissions/restrictedProps.js';
4
+ import type { IAssignTentativelyOptions, PermissionProcessor } from './types/assign-gingerly/types.js';
7
5
  export type { IAssignTentativelyOptions };
8
6
 
9
7
  /**
@@ -137,7 +135,7 @@ export function assignTentatively(
137
135
  target: any,
138
136
  source: Record<string | symbol, any>,
139
137
  options?: IAssignTentativelyOptions,
140
- permissions?: AssignPermissions
138
+ permissionProcessor?: PermissionProcessor
141
139
  ): any {
142
140
  if (!target || typeof target !== 'object') {
143
141
  return target;
@@ -145,7 +143,7 @@ export function assignTentatively(
145
143
 
146
144
  const reversal = options?.reversal || {};
147
145
  const trackedCreatedPaths = new Set<string>();
148
- const restrictedPropSet = buildRestrictedPropSet(permissions);
146
+
149
147
 
150
148
  // Process all keys from source
151
149
  for (const key of Object.keys(source)) {
@@ -160,7 +158,7 @@ export function assignTentatively(
160
158
  const topLevelKey = pathParts[0];
161
159
  const lastKey = pathParts[pathParts.length - 1];
162
160
 
163
- if (checkRestrictedProp(restrictedPropSet, lastKey)) {
161
+ if (permissionProcessor?.checkRestrictedProp(lastKey)) {
164
162
  continue;
165
163
  }
166
164
 
@@ -184,7 +182,7 @@ export function assignTentatively(
184
182
  }
185
183
  } else {
186
184
  // Plain key - direct operation on target
187
- if (checkRestrictedProp(restrictedPropSet, path)) {
185
+ if (permissionProcessor?.checkRestrictedProp(path)) {
188
186
  continue;
189
187
  }
190
188
  if (path in target) {
@@ -215,7 +213,7 @@ export function assignTentatively(
215
213
  const topLevelKey = lhsPathParts[0];
216
214
  lhsLastKey = lhsPathParts[lhsPathParts.length - 1];
217
215
 
218
- if (checkRestrictedProp(restrictedPropSet, lhsLastKey)) {
216
+ if (permissionProcessor?.checkRestrictedProp(lhsLastKey)) {
219
217
  continue;
220
218
  }
221
219
 
@@ -228,7 +226,7 @@ export function assignTentatively(
228
226
  } else {
229
227
  lhsPathParts = [lhsPath];
230
228
  lhsLastKey = lhsPath;
231
- if (checkRestrictedProp(restrictedPropSet, lhsLastKey)) {
229
+ if (permissionProcessor?.checkRestrictedProp(lhsLastKey)) {
232
230
  continue;
233
231
  }
234
232
  lhsParent = target;
@@ -337,7 +335,7 @@ export function assignTentatively(
337
335
  const topLevelKey = pathParts[0];
338
336
  const lastKey = pathParts[pathParts.length - 1];
339
337
 
340
- if (checkRestrictedProp(restrictedPropSet, lastKey)) {
338
+ if (permissionProcessor?.checkRestrictedProp(lastKey)) {
341
339
  continue;
342
340
  }
343
341
 
@@ -362,7 +360,7 @@ export function assignTentatively(
362
360
  }
363
361
  // For nested objects, recursively apply with nested reversal tracking
364
362
  const nestedReversal: Record<string | symbol, any> = {};
365
- assignTentatively(parent[lastKey], value, { reversal: nestedReversal }, permissions);
363
+ assignTentatively(parent[lastKey], value, { reversal: nestedReversal }, permissionProcessor);
366
364
  // Merge nested reversals
367
365
  for (const revKey of Object.keys(nestedReversal)) {
368
366
  if (!(revKey in reversal)) {
@@ -381,7 +379,7 @@ export function assignTentatively(
381
379
  }
382
380
  } else {
383
381
  // Non-nested key
384
- if (checkRestrictedProp(restrictedPropSet, key)) {
382
+ if (permissionProcessor?.checkRestrictedProp(key)) {
385
383
  continue;
386
384
  }
387
385
  if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
@@ -396,7 +394,7 @@ export function assignTentatively(
396
394
  target[key] = {};
397
395
  }
398
396
  const nestedReversal: Record<string | symbol, any> = {};
399
- assignTentatively(target[key], value, { reversal: nestedReversal }, permissions);
397
+ assignTentatively(target[key], value, { reversal: nestedReversal }, permissionProcessor);
400
398
  // Merge nested reversals
401
399
  for (const revKey of Object.keys(nestedReversal)) {
402
400
  if (!(revKey in reversal)) {
package/beVigilant.js CHANGED
@@ -9,7 +9,7 @@
9
9
  */
10
10
  import { Infer } from './inferencer/inferencer.js';
11
11
  import { withScopePerimeter } from './inferencer/withScopePerimeter.js';
12
- import { registerInCache } from './resolveIdRef.js';
12
+ import { registerInCache } from './resolve/resolveIdRef.js';
13
13
  /**
14
14
  * Set up a MutationObserver that watches for new [itemprop] elements
15
15
  * and applies inferred assignments from the live `from` object.
package/beVigilant.ts CHANGED
@@ -10,7 +10,7 @@
10
10
 
11
11
  import { Infer } from './inferencer/inferencer.js';
12
12
  import { withScopePerimeter } from './inferencer/withScopePerimeter.js';
13
- import { registerInCache } from './resolveIdRef.js';
13
+ import { registerInCache } from './resolve/resolveIdRef.js';
14
14
 
15
15
  /**
16
16
  * Set up a MutationObserver that watches for new [itemprop] elements
package/buildCSSQuery.js CHANGED
@@ -1,4 +1,4 @@
1
- import { resolveTemplate } from './resolveTemplate.js';
1
+ import { resolveTemplate } from './resolve/resolveTemplate.js';
2
2
  /**
3
3
  * Extracts attribute names from withAttrs configuration
4
4
  * Resolves template variables and excludes underscore-prefixed config keys
package/buildCSSQuery.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { EnhancementConfig, AttrPatterns } from './types/assign-gingerly/types';
2
- import { resolveTemplate } from './resolveTemplate.js';
2
+ import { resolveTemplate } from './resolve/resolveTemplate.js';
3
3
 
4
4
  /**
5
5
  * Extracts attribute names from withAttrs configuration