assign-gingerly 0.0.71 → 0.0.72
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DX/emojis.js +16 -0
- package/DX/emojis.ts +16 -0
- package/README.md +30 -3
- package/assignFrom.js +6 -6
- package/assignFrom.ts +10 -9
- package/assignFromAsync.js +3 -3
- package/assignFromAsync.ts +3 -3
- package/assignGingerly.js +136 -69
- package/assignGingerly.ts +169 -103
- package/assignTentatively.js +45 -30
- package/assignTentatively.ts +85 -60
- package/defineWithFeatures.js +38 -32
- package/defineWithFeatures.ts +46 -39
- package/eachTime.js +12 -4
- package/eachTime.ts +16 -7
- package/evaluatePathWithAsyncMethods.js +29 -16
- package/evaluatePathWithAsyncMethods.ts +31 -16
- package/handlers/addEventListener.js +11 -11
- package/handlers/addEventListener.ts +18 -15
- package/handlers/lazyLoad.ts +10 -7
- package/handlers/lazyLoadSwitch.ts +4 -3
- package/handlers/manageTemplateList.js +9 -9
- package/handlers/manageTemplateList.ts +11 -10
- package/handlers/rangeSelector.ts +4 -3
- package/inferencer/types/assign-gingerly/types.d.ts +60 -3
- package/isAllowedImportPath.js +62 -0
- package/isAllowedImportPath.ts +71 -6
- package/package.json +2 -2
- package/processHandlerCommands.js +5 -3
- package/processHandlerCommands.ts +10 -8
- package/types/assign-gingerly/types.d.ts +61 -4
package/assignTentatively.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { buildRestrictedPropSet, checkRestrictedProp } from './isAllowedImportPath.js';
|
|
1
2
|
/**
|
|
2
3
|
* Helper function to check if a string key represents an += command
|
|
3
4
|
*/
|
|
@@ -13,6 +14,23 @@ function parseIncCommand(key) {
|
|
|
13
14
|
}
|
|
14
15
|
return key.substring(0, key.length - 3); // Remove ' +=' suffix
|
|
15
16
|
}
|
|
17
|
+
/**
|
|
18
|
+
* Apply the scalar and array semantics of the += command.
|
|
19
|
+
*/
|
|
20
|
+
function addValue(lhs, rhs) {
|
|
21
|
+
if (Array.isArray(lhs)) {
|
|
22
|
+
return Array.isArray(rhs) ? [...lhs, ...rhs] : [...lhs, rhs];
|
|
23
|
+
}
|
|
24
|
+
if (typeof lhs === 'number' && typeof rhs === 'string') {
|
|
25
|
+
const parsed = Number(rhs);
|
|
26
|
+
return Number.isNaN(parsed) ? lhs + rhs : lhs + parsed;
|
|
27
|
+
}
|
|
28
|
+
if (typeof lhs === 'string' && typeof rhs === 'number') {
|
|
29
|
+
const parsed = Number(lhs);
|
|
30
|
+
return Number.isNaN(parsed) ? lhs + rhs : (parsed + rhs).toString();
|
|
31
|
+
}
|
|
32
|
+
return lhs + rhs;
|
|
33
|
+
}
|
|
16
34
|
/**
|
|
17
35
|
* Helper function to check if a key represents a =! command
|
|
18
36
|
*/
|
|
@@ -96,12 +114,13 @@ function getTopLevelKey(path) {
|
|
|
96
114
|
/**
|
|
97
115
|
* Main assignTentatively function with reversal support
|
|
98
116
|
*/
|
|
99
|
-
export function assignTentatively(target, source, options) {
|
|
117
|
+
export function assignTentatively(target, source, options, permissions) {
|
|
100
118
|
if (!target || typeof target !== 'object') {
|
|
101
119
|
return target;
|
|
102
120
|
}
|
|
103
121
|
const reversal = options?.reversal || {};
|
|
104
122
|
const trackedCreatedPaths = new Set();
|
|
123
|
+
const restrictedPropSet = buildRestrictedPropSet(permissions);
|
|
105
124
|
// Process all keys from source
|
|
106
125
|
for (const key of Object.keys(source)) {
|
|
107
126
|
const value = source[key];
|
|
@@ -112,11 +131,14 @@ export function assignTentatively(target, source, options) {
|
|
|
112
131
|
if (isNestedPath(path)) {
|
|
113
132
|
const pathParts = parsePath(path);
|
|
114
133
|
const topLevelKey = pathParts[0];
|
|
134
|
+
const lastKey = pathParts[pathParts.length - 1];
|
|
135
|
+
if (checkRestrictedProp(restrictedPropSet, lastKey)) {
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
115
138
|
// Track if we created a new top-level path (BEFORE calling ensureNestedPath)
|
|
116
139
|
if (!(topLevelKey in target)) {
|
|
117
140
|
trackedCreatedPaths.add(topLevelKey);
|
|
118
141
|
}
|
|
119
|
-
const lastKey = pathParts[pathParts.length - 1];
|
|
120
142
|
const parent = ensureNestedPath(target, pathParts);
|
|
121
143
|
// If property already exists, store original value for reversal
|
|
122
144
|
if (lastKey in parent) {
|
|
@@ -124,18 +146,7 @@ export function assignTentatively(target, source, options) {
|
|
|
124
146
|
if (!(fullPath in reversal)) {
|
|
125
147
|
reversal[fullPath] = parent[lastKey];
|
|
126
148
|
}
|
|
127
|
-
|
|
128
|
-
parent[lastKey] = Array.isArray(value)
|
|
129
|
-
? [...parent[lastKey], ...value]
|
|
130
|
-
: [...parent[lastKey], value];
|
|
131
|
-
}
|
|
132
|
-
else if (typeof parent[lastKey] === 'number' && typeof value === 'string') {
|
|
133
|
-
const parsed = Number(value);
|
|
134
|
-
parent[lastKey] = isNaN(parsed) ? parent[lastKey] + value : parent[lastKey] + parsed;
|
|
135
|
-
}
|
|
136
|
-
else {
|
|
137
|
-
parent[lastKey] += value;
|
|
138
|
-
}
|
|
149
|
+
parent[lastKey] = addValue(parent[lastKey], value);
|
|
139
150
|
}
|
|
140
151
|
else {
|
|
141
152
|
// Property doesn't exist, create it with the value
|
|
@@ -144,22 +155,14 @@ export function assignTentatively(target, source, options) {
|
|
|
144
155
|
}
|
|
145
156
|
else {
|
|
146
157
|
// Plain key - direct operation on target
|
|
158
|
+
if (checkRestrictedProp(restrictedPropSet, path)) {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
147
161
|
if (path in target) {
|
|
148
162
|
if (!(path in reversal)) {
|
|
149
163
|
reversal[path] = target[path];
|
|
150
164
|
}
|
|
151
|
-
|
|
152
|
-
target[path] = Array.isArray(value)
|
|
153
|
-
? [...target[path], ...value]
|
|
154
|
-
: [...target[path], value];
|
|
155
|
-
}
|
|
156
|
-
else if (typeof target[path] === 'number' && typeof value === 'string') {
|
|
157
|
-
const parsed = Number(value);
|
|
158
|
-
target[path] = isNaN(parsed) ? target[path] + value : target[path] + parsed;
|
|
159
|
-
}
|
|
160
|
-
else {
|
|
161
|
-
target[path] += value;
|
|
162
|
-
}
|
|
165
|
+
target[path] = addValue(target[path], value);
|
|
163
166
|
}
|
|
164
167
|
else {
|
|
165
168
|
target[path] = value;
|
|
@@ -179,16 +182,22 @@ export function assignTentatively(target, source, options) {
|
|
|
179
182
|
if (isNestedPath(lhsPath)) {
|
|
180
183
|
lhsPathParts = parsePath(lhsPath);
|
|
181
184
|
const topLevelKey = lhsPathParts[0];
|
|
185
|
+
lhsLastKey = lhsPathParts[lhsPathParts.length - 1];
|
|
186
|
+
if (checkRestrictedProp(restrictedPropSet, lhsLastKey)) {
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
182
189
|
// Track if we created a new top-level path (BEFORE calling ensureNestedPath)
|
|
183
190
|
if (!(topLevelKey in target)) {
|
|
184
191
|
trackedCreatedPaths.add(topLevelKey);
|
|
185
192
|
}
|
|
186
|
-
lhsLastKey = lhsPathParts[lhsPathParts.length - 1];
|
|
187
193
|
lhsParent = ensureNestedPath(target, lhsPathParts);
|
|
188
194
|
}
|
|
189
195
|
else {
|
|
190
196
|
lhsPathParts = [lhsPath];
|
|
191
197
|
lhsLastKey = lhsPath;
|
|
198
|
+
if (checkRestrictedProp(restrictedPropSet, lhsLastKey)) {
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
192
201
|
lhsParent = target;
|
|
193
202
|
}
|
|
194
203
|
// Determine what to negate
|
|
@@ -291,11 +300,14 @@ export function assignTentatively(target, source, options) {
|
|
|
291
300
|
if (isNestedPath(key)) {
|
|
292
301
|
const pathParts = parsePath(key);
|
|
293
302
|
const topLevelKey = pathParts[0];
|
|
303
|
+
const lastKey = pathParts[pathParts.length - 1];
|
|
304
|
+
if (checkRestrictedProp(restrictedPropSet, lastKey)) {
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
294
307
|
// Track if we created a new top-level path (BEFORE calling ensureNestedPath)
|
|
295
308
|
if (!(topLevelKey in target)) {
|
|
296
309
|
trackedCreatedPaths.add(topLevelKey);
|
|
297
310
|
}
|
|
298
|
-
const lastKey = pathParts[pathParts.length - 1];
|
|
299
311
|
const parent = ensureNestedPath(target, pathParts);
|
|
300
312
|
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
301
313
|
// Recursively apply assignTentatively for nested objects
|
|
@@ -311,7 +323,7 @@ export function assignTentatively(target, source, options) {
|
|
|
311
323
|
}
|
|
312
324
|
// For nested objects, recursively apply with nested reversal tracking
|
|
313
325
|
const nestedReversal = {};
|
|
314
|
-
assignTentatively(parent[lastKey], value, { reversal: nestedReversal });
|
|
326
|
+
assignTentatively(parent[lastKey], value, { reversal: nestedReversal }, permissions);
|
|
315
327
|
// Merge nested reversals
|
|
316
328
|
for (const revKey of Object.keys(nestedReversal)) {
|
|
317
329
|
if (!(revKey in reversal)) {
|
|
@@ -332,6 +344,9 @@ export function assignTentatively(target, source, options) {
|
|
|
332
344
|
}
|
|
333
345
|
else {
|
|
334
346
|
// Non-nested key
|
|
347
|
+
if (checkRestrictedProp(restrictedPropSet, key)) {
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
335
350
|
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
336
351
|
// Recursively apply assignTentatively for nested objects
|
|
337
352
|
if (!(key in target) || typeof target[key] !== 'object') {
|
|
@@ -344,7 +359,7 @@ export function assignTentatively(target, source, options) {
|
|
|
344
359
|
target[key] = {};
|
|
345
360
|
}
|
|
346
361
|
const nestedReversal = {};
|
|
347
|
-
assignTentatively(target[key], value, { reversal: nestedReversal });
|
|
362
|
+
assignTentatively(target[key], value, { reversal: nestedReversal }, permissions);
|
|
348
363
|
// Merge nested reversals
|
|
349
364
|
for (const revKey of Object.keys(nestedReversal)) {
|
|
350
365
|
if (!(revKey in reversal)) {
|
package/assignTentatively.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* assignTentatively — reversible assignment with change tracking.
|
|
3
3
|
*/
|
|
4
|
-
import type { IAssignTentativelyOptions } from './types/assign-gingerly/types.js';
|
|
4
|
+
import type { IAssignTentativelyOptions } from './types/assign-gingerly/types.js';
|
|
5
|
+
import type { AssignPermissions } from './isAllowedImportPath.js';
|
|
6
|
+
import { buildRestrictedPropSet, checkRestrictedProp } from './isAllowedImportPath.js';
|
|
5
7
|
export type { IAssignTentativelyOptions };
|
|
6
8
|
|
|
7
9
|
/**
|
|
@@ -14,12 +16,30 @@ function isIncCommand(key: string): boolean {
|
|
|
14
16
|
/**
|
|
15
17
|
* Helper function to parse an += command and extract the path
|
|
16
18
|
*/
|
|
17
|
-
function parseIncCommand(key: string): string | null {
|
|
18
|
-
if (!isIncCommand(key)) {
|
|
19
|
-
return null;
|
|
20
|
-
}
|
|
21
|
-
return key.substring(0, key.length - 3); // Remove ' +=' suffix
|
|
22
|
-
}
|
|
19
|
+
function parseIncCommand(key: string): string | null {
|
|
20
|
+
if (!isIncCommand(key)) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
return key.substring(0, key.length - 3); // Remove ' +=' suffix
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Apply the scalar and array semantics of the += command.
|
|
28
|
+
*/
|
|
29
|
+
function addValue(lhs: any, rhs: any): any {
|
|
30
|
+
if (Array.isArray(lhs)) {
|
|
31
|
+
return Array.isArray(rhs) ? [...lhs, ...rhs] : [...lhs, rhs];
|
|
32
|
+
}
|
|
33
|
+
if (typeof lhs === 'number' && typeof rhs === 'string') {
|
|
34
|
+
const parsed = Number(rhs);
|
|
35
|
+
return Number.isNaN(parsed) ? lhs + rhs : lhs + parsed;
|
|
36
|
+
}
|
|
37
|
+
if (typeof lhs === 'string' && typeof rhs === 'number') {
|
|
38
|
+
const parsed = Number(lhs);
|
|
39
|
+
return Number.isNaN(parsed) ? lhs + rhs : (parsed + rhs).toString();
|
|
40
|
+
}
|
|
41
|
+
return lhs + rhs;
|
|
42
|
+
}
|
|
23
43
|
|
|
24
44
|
/**
|
|
25
45
|
* Helper function to check if a key represents a =! command
|
|
@@ -114,16 +134,18 @@ function getTopLevelKey(path: string): string | null {
|
|
|
114
134
|
* Main assignTentatively function with reversal support
|
|
115
135
|
*/
|
|
116
136
|
export function assignTentatively(
|
|
117
|
-
target: any,
|
|
118
|
-
source: Record<string | symbol, any>,
|
|
119
|
-
options?: IAssignTentativelyOptions
|
|
120
|
-
|
|
137
|
+
target: any,
|
|
138
|
+
source: Record<string | symbol, any>,
|
|
139
|
+
options?: IAssignTentativelyOptions,
|
|
140
|
+
permissions?: AssignPermissions
|
|
141
|
+
): any {
|
|
121
142
|
if (!target || typeof target !== 'object') {
|
|
122
143
|
return target;
|
|
123
144
|
}
|
|
124
145
|
|
|
125
|
-
const reversal = options?.reversal || {};
|
|
126
|
-
const trackedCreatedPaths = new Set<string>();
|
|
146
|
+
const reversal = options?.reversal || {};
|
|
147
|
+
const trackedCreatedPaths = new Set<string>();
|
|
148
|
+
const restrictedPropSet = buildRestrictedPropSet(permissions);
|
|
127
149
|
|
|
128
150
|
// Process all keys from source
|
|
129
151
|
for (const key of Object.keys(source)) {
|
|
@@ -133,17 +155,21 @@ export function assignTentatively(
|
|
|
133
155
|
if (isIncCommand(key)) {
|
|
134
156
|
const path = parseIncCommand(key);
|
|
135
157
|
if (path) {
|
|
136
|
-
if (isNestedPath(path)) {
|
|
137
|
-
const pathParts = parsePath(path);
|
|
138
|
-
const topLevelKey = pathParts[0];
|
|
158
|
+
if (isNestedPath(path)) {
|
|
159
|
+
const pathParts = parsePath(path);
|
|
160
|
+
const topLevelKey = pathParts[0];
|
|
161
|
+
const lastKey = pathParts[pathParts.length - 1];
|
|
162
|
+
|
|
163
|
+
if (checkRestrictedProp(restrictedPropSet, lastKey)) {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
139
166
|
|
|
140
167
|
// Track if we created a new top-level path (BEFORE calling ensureNestedPath)
|
|
141
168
|
if (!(topLevelKey in target)) {
|
|
142
169
|
trackedCreatedPaths.add(topLevelKey);
|
|
143
170
|
}
|
|
144
171
|
|
|
145
|
-
const
|
|
146
|
-
const parent = ensureNestedPath(target, pathParts);
|
|
172
|
+
const parent = ensureNestedPath(target, pathParts);
|
|
147
173
|
|
|
148
174
|
// If property already exists, store original value for reversal
|
|
149
175
|
if (lastKey in parent) {
|
|
@@ -151,36 +177,21 @@ export function assignTentatively(
|
|
|
151
177
|
if (!(fullPath in reversal)) {
|
|
152
178
|
reversal[fullPath] = parent[lastKey];
|
|
153
179
|
}
|
|
154
|
-
|
|
155
|
-
parent[lastKey] = Array.isArray(value)
|
|
156
|
-
? [...parent[lastKey], ...value]
|
|
157
|
-
: [...parent[lastKey], value];
|
|
158
|
-
} else if (typeof parent[lastKey] === 'number' && typeof value === 'string') {
|
|
159
|
-
const parsed = Number(value);
|
|
160
|
-
parent[lastKey] = isNaN(parsed) ? parent[lastKey] + value : parent[lastKey] + parsed;
|
|
161
|
-
} else {
|
|
162
|
-
parent[lastKey] += value;
|
|
163
|
-
}
|
|
180
|
+
parent[lastKey] = addValue(parent[lastKey], value);
|
|
164
181
|
} else {
|
|
165
182
|
// Property doesn't exist, create it with the value
|
|
166
183
|
parent[lastKey] = value;
|
|
167
184
|
}
|
|
168
|
-
} else {
|
|
169
|
-
// Plain key - direct operation on target
|
|
170
|
-
if (path
|
|
185
|
+
} else {
|
|
186
|
+
// Plain key - direct operation on target
|
|
187
|
+
if (checkRestrictedProp(restrictedPropSet, path)) {
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
if (path in target) {
|
|
171
191
|
if (!(path in reversal)) {
|
|
172
192
|
reversal[path] = target[path];
|
|
173
193
|
}
|
|
174
|
-
|
|
175
|
-
target[path] = Array.isArray(value)
|
|
176
|
-
? [...target[path], ...value]
|
|
177
|
-
: [...target[path], value];
|
|
178
|
-
} else if (typeof target[path] === 'number' && typeof value === 'string') {
|
|
179
|
-
const parsed = Number(value);
|
|
180
|
-
target[path] = isNaN(parsed) ? target[path] + value : target[path] + parsed;
|
|
181
|
-
} else {
|
|
182
|
-
target[path] += value;
|
|
183
|
-
}
|
|
194
|
+
target[path] = addValue(target[path], value);
|
|
184
195
|
} else {
|
|
185
196
|
target[path] = value;
|
|
186
197
|
}
|
|
@@ -199,21 +210,28 @@ export function assignTentatively(
|
|
|
199
210
|
let lhsLastKey: string;
|
|
200
211
|
let lhsPathParts: string[];
|
|
201
212
|
|
|
202
|
-
if (isNestedPath(lhsPath)) {
|
|
203
|
-
lhsPathParts = parsePath(lhsPath);
|
|
204
|
-
const topLevelKey = lhsPathParts[0];
|
|
213
|
+
if (isNestedPath(lhsPath)) {
|
|
214
|
+
lhsPathParts = parsePath(lhsPath);
|
|
215
|
+
const topLevelKey = lhsPathParts[0];
|
|
216
|
+
lhsLastKey = lhsPathParts[lhsPathParts.length - 1];
|
|
217
|
+
|
|
218
|
+
if (checkRestrictedProp(restrictedPropSet, lhsLastKey)) {
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
205
221
|
|
|
206
222
|
// Track if we created a new top-level path (BEFORE calling ensureNestedPath)
|
|
207
223
|
if (!(topLevelKey in target)) {
|
|
208
224
|
trackedCreatedPaths.add(topLevelKey);
|
|
209
225
|
}
|
|
210
226
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
lhsLastKey
|
|
216
|
-
|
|
227
|
+
lhsParent = ensureNestedPath(target, lhsPathParts);
|
|
228
|
+
} else {
|
|
229
|
+
lhsPathParts = [lhsPath];
|
|
230
|
+
lhsLastKey = lhsPath;
|
|
231
|
+
if (checkRestrictedProp(restrictedPropSet, lhsLastKey)) {
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
lhsParent = target;
|
|
217
235
|
}
|
|
218
236
|
|
|
219
237
|
// Determine what to negate
|
|
@@ -314,17 +332,21 @@ export function assignTentatively(
|
|
|
314
332
|
continue;
|
|
315
333
|
}
|
|
316
334
|
|
|
317
|
-
if (isNestedPath(key)) {
|
|
318
|
-
const pathParts = parsePath(key);
|
|
319
|
-
const topLevelKey = pathParts[0];
|
|
335
|
+
if (isNestedPath(key)) {
|
|
336
|
+
const pathParts = parsePath(key);
|
|
337
|
+
const topLevelKey = pathParts[0];
|
|
338
|
+
const lastKey = pathParts[pathParts.length - 1];
|
|
339
|
+
|
|
340
|
+
if (checkRestrictedProp(restrictedPropSet, lastKey)) {
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
320
343
|
|
|
321
344
|
// Track if we created a new top-level path (BEFORE calling ensureNestedPath)
|
|
322
345
|
if (!(topLevelKey in target)) {
|
|
323
346
|
trackedCreatedPaths.add(topLevelKey);
|
|
324
347
|
}
|
|
325
348
|
|
|
326
|
-
const
|
|
327
|
-
const parent = ensureNestedPath(target, pathParts);
|
|
349
|
+
const parent = ensureNestedPath(target, pathParts);
|
|
328
350
|
|
|
329
351
|
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
330
352
|
// Recursively apply assignTentatively for nested objects
|
|
@@ -340,7 +362,7 @@ export function assignTentatively(
|
|
|
340
362
|
}
|
|
341
363
|
// For nested objects, recursively apply with nested reversal tracking
|
|
342
364
|
const nestedReversal: Record<string | symbol, any> = {};
|
|
343
|
-
assignTentatively(parent[lastKey], value, { reversal: nestedReversal });
|
|
365
|
+
assignTentatively(parent[lastKey], value, { reversal: nestedReversal }, permissions);
|
|
344
366
|
// Merge nested reversals
|
|
345
367
|
for (const revKey of Object.keys(nestedReversal)) {
|
|
346
368
|
if (!(revKey in reversal)) {
|
|
@@ -357,9 +379,12 @@ export function assignTentatively(
|
|
|
357
379
|
}
|
|
358
380
|
parent[lastKey] = value;
|
|
359
381
|
}
|
|
360
|
-
} else {
|
|
361
|
-
// Non-nested key
|
|
362
|
-
if (
|
|
382
|
+
} else {
|
|
383
|
+
// Non-nested key
|
|
384
|
+
if (checkRestrictedProp(restrictedPropSet, key)) {
|
|
385
|
+
continue;
|
|
386
|
+
}
|
|
387
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
363
388
|
// Recursively apply assignTentatively for nested objects
|
|
364
389
|
if (!(key in target) || typeof target[key] !== 'object') {
|
|
365
390
|
// Store original value for reversal if it existed
|
|
@@ -371,7 +396,7 @@ export function assignTentatively(
|
|
|
371
396
|
target[key] = {};
|
|
372
397
|
}
|
|
373
398
|
const nestedReversal: Record<string | symbol, any> = {};
|
|
374
|
-
assignTentatively(target[key], value, { reversal: nestedReversal });
|
|
399
|
+
assignTentatively(target[key], value, { reversal: nestedReversal }, permissions);
|
|
375
400
|
// Merge nested reversals
|
|
376
401
|
for (const revKey of Object.keys(nestedReversal)) {
|
|
377
402
|
if (!(revKey in reversal)) {
|
package/defineWithFeatures.js
CHANGED
|
@@ -73,29 +73,33 @@ export async function defineWithFeatures(tagName, baseTagName, config, registry,
|
|
|
73
73
|
classCache = new Map();
|
|
74
74
|
resolvedSpawnCache.set(BaseClass, classCache);
|
|
75
75
|
}
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
spawn =
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
76
|
+
const { assignFeatures: af } = config;
|
|
77
|
+
let resolvedSpawns;
|
|
78
|
+
if (af) {
|
|
79
|
+
const featureKeys = Object.keys(af);
|
|
80
|
+
resolvedSpawns = new Map();
|
|
81
|
+
await Promise.all(featureKeys.map(async (key) => {
|
|
82
|
+
const optIn = supportedFeatures[key];
|
|
83
|
+
if (!optIn) {
|
|
84
|
+
throw new Error(`defineWithFeatures: feature "${key}" not found in ${baseTagName}.supportedFeatures`);
|
|
85
|
+
}
|
|
86
|
+
// Check cache first
|
|
87
|
+
if (classCache.has(key)) {
|
|
88
|
+
resolvedSpawns.set(key, classCache.get(key));
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
let spawn = optIn.fallbackSpawn;
|
|
92
|
+
if (spawn && isAsyncSpawn(spawn)) {
|
|
93
|
+
// Resolve the async spawner
|
|
94
|
+
spawn = await spawn();
|
|
95
|
+
}
|
|
96
|
+
// Cache the resolved spawn
|
|
97
|
+
if (spawn) {
|
|
98
|
+
classCache.set(key, spawn);
|
|
99
|
+
}
|
|
100
|
+
resolvedSpawns.set(key, spawn);
|
|
101
|
+
}));
|
|
102
|
+
}
|
|
99
103
|
// 3. Create subclass
|
|
100
104
|
const NewClass = class extends BaseClass {
|
|
101
105
|
};
|
|
@@ -103,16 +107,18 @@ export async function defineWithFeatures(tagName, baseTagName, config, registry,
|
|
|
103
107
|
if (options?.onSubclassCreated) {
|
|
104
108
|
options.onSubclassCreated(NewClass);
|
|
105
109
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
110
|
+
if (af) {
|
|
111
|
+
// 4. Build FeatureConfigsMap: resolved spawns + JSON config
|
|
112
|
+
const featuresMap = {};
|
|
113
|
+
for (const [key, jsonConfig] of Object.entries(af)) {
|
|
114
|
+
featuresMap[key] = {
|
|
115
|
+
spawn: resolvedSpawns.get(key),
|
|
116
|
+
...jsonConfig
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
// 5. assignFeatures (sequential onAssigned) + define
|
|
120
|
+
await assignFeatures(NewClass, featuresMap, reg.featuresRegistry);
|
|
113
121
|
}
|
|
114
|
-
// 5. assignFeatures (sequential onAssigned) + define
|
|
115
|
-
await assignFeatures(NewClass, featuresMap, reg.featuresRegistry);
|
|
116
122
|
reg.define(tagName, NewClass);
|
|
117
123
|
return NewClass;
|
|
118
124
|
}
|
package/defineWithFeatures.ts
CHANGED
|
@@ -104,56 +104,63 @@ export async function defineWithFeatures(
|
|
|
104
104
|
classCache = new Map();
|
|
105
105
|
resolvedSpawnCache.set(BaseClass, classCache);
|
|
106
106
|
}
|
|
107
|
+
const {assignFeatures: af} = config;
|
|
108
|
+
let resolvedSpawns: Map<string, any> | undefined;
|
|
109
|
+
if (af) {
|
|
110
|
+
const featureKeys = Object.keys(af);
|
|
111
|
+
resolvedSpawns = new Map<string, any>();
|
|
112
|
+
|
|
113
|
+
await Promise.all(featureKeys.map(async (key) => {
|
|
114
|
+
const optIn = supportedFeatures[key];
|
|
115
|
+
if (!optIn) {
|
|
116
|
+
throw new Error(
|
|
117
|
+
`defineWithFeatures: feature "${key}" not found in ${baseTagName}.supportedFeatures`
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Check cache first
|
|
122
|
+
if (classCache!.has(key)) {
|
|
123
|
+
resolvedSpawns!.set(key, classCache!.get(key));
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
let spawn = optIn.fallbackSpawn;
|
|
128
|
+
if (spawn && isAsyncSpawn(spawn)) {
|
|
129
|
+
// Resolve the async spawner
|
|
130
|
+
spawn = await (spawn as () => Promise<any>)();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Cache the resolved spawn
|
|
134
|
+
if (spawn) {
|
|
135
|
+
classCache!.set(key, spawn);
|
|
136
|
+
}
|
|
137
|
+
resolvedSpawns!.set(key, spawn);
|
|
138
|
+
}));
|
|
139
|
+
}
|
|
107
140
|
|
|
108
|
-
const featureKeys = Object.keys(config.assignFeatures);
|
|
109
|
-
const resolvedSpawns = new Map<string, any>();
|
|
110
|
-
|
|
111
|
-
await Promise.all(featureKeys.map(async (key) => {
|
|
112
|
-
const optIn = supportedFeatures[key];
|
|
113
|
-
if (!optIn) {
|
|
114
|
-
throw new Error(
|
|
115
|
-
`defineWithFeatures: feature "${key}" not found in ${baseTagName}.supportedFeatures`
|
|
116
|
-
);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Check cache first
|
|
120
|
-
if (classCache!.has(key)) {
|
|
121
|
-
resolvedSpawns.set(key, classCache!.get(key));
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
let spawn = optIn.fallbackSpawn;
|
|
126
|
-
if (spawn && isAsyncSpawn(spawn)) {
|
|
127
|
-
// Resolve the async spawner
|
|
128
|
-
spawn = await (spawn as () => Promise<any>)();
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Cache the resolved spawn
|
|
132
|
-
if (spawn) {
|
|
133
|
-
classCache!.set(key, spawn);
|
|
134
|
-
}
|
|
135
|
-
resolvedSpawns.set(key, spawn);
|
|
136
|
-
}));
|
|
137
141
|
|
|
138
142
|
// 3. Create subclass
|
|
139
|
-
const NewClass = class extends (BaseClass as any) {};
|
|
143
|
+
const NewClass = class extends (BaseClass as any) { };
|
|
140
144
|
|
|
141
145
|
// 3b. Call onSubclassCreated callback (before define, before features if needed)
|
|
142
146
|
if (options?.onSubclassCreated) {
|
|
143
147
|
options.onSubclassCreated(NewClass);
|
|
144
148
|
}
|
|
145
149
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
150
|
+
if(af){
|
|
151
|
+
// 4. Build FeatureConfigsMap: resolved spawns + JSON config
|
|
152
|
+
const featuresMap: FeatureConfigsMap = {};
|
|
153
|
+
for (const [key, jsonConfig] of Object.entries(af)) {
|
|
154
|
+
featuresMap[key] = {
|
|
155
|
+
spawn: resolvedSpawns!.get(key),
|
|
156
|
+
...jsonConfig
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// 5. assignFeatures (sequential onAssigned) + define
|
|
161
|
+
await assignFeatures(NewClass, featuresMap, (reg as any).featuresRegistry);
|
|
153
162
|
}
|
|
154
163
|
|
|
155
|
-
// 5. assignFeatures (sequential onAssigned) + define
|
|
156
|
-
await assignFeatures(NewClass, featuresMap, (reg as any).featuresRegistry);
|
|
157
164
|
(reg as any).define(tagName, NewClass);
|
|
158
165
|
|
|
159
166
|
return NewClass;
|
package/eachTime.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
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 './isAllowedImportPath.js';
|
|
6
7
|
/**
|
|
7
8
|
* Check if a value is an EventTarget
|
|
8
9
|
*/
|
|
@@ -20,7 +21,7 @@ function isEventTarget(value) {
|
|
|
20
21
|
* @param aliasMap - Map of aliases for token substitution
|
|
21
22
|
* @param options - Options including required AbortSignal
|
|
22
23
|
*/
|
|
23
|
-
export async function handleEachTime(target, pathParts, forEachIndex, value, withMethods, aliasMap, options) {
|
|
24
|
+
export async function handleEachTime(target, pathParts, forEachIndex, value, withMethods, aliasMap, options, permissions, restrictedPropSet) {
|
|
24
25
|
// Validate signal - required for cleanup
|
|
25
26
|
if (!options?.signal) {
|
|
26
27
|
throw new Error('@eachTime requires an AbortSignal in options.signal for cleanup');
|
|
@@ -65,7 +66,11 @@ export async function handleEachTime(target, pathParts, forEachIndex, value, wit
|
|
|
65
66
|
// Last segment is a method - call it
|
|
66
67
|
const method = result.target[result.lastKey];
|
|
67
68
|
if (typeof method === 'function') {
|
|
68
|
-
if (
|
|
69
|
+
if (result.isZeroArg) {
|
|
70
|
+
// Trailing | marker - call with no arguments, ignoring the value
|
|
71
|
+
method.call(result.target);
|
|
72
|
+
}
|
|
73
|
+
else if (Array.isArray(value)) {
|
|
69
74
|
method.apply(result.target, value);
|
|
70
75
|
}
|
|
71
76
|
else {
|
|
@@ -77,7 +82,10 @@ export async function handleEachTime(target, pathParts, forEachIndex, value, wit
|
|
|
77
82
|
// Normal assignment
|
|
78
83
|
const lastKey = result.lastKey;
|
|
79
84
|
const parent = result.target;
|
|
80
|
-
if (
|
|
85
|
+
if (redirectRestrictedProp(restrictedPropSet, parent, lastKey, value)) {
|
|
86
|
+
// skip
|
|
87
|
+
}
|
|
88
|
+
else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
81
89
|
// Check if property exists and is readonly
|
|
82
90
|
if (lastKey in parent && isReadonlyProperty(parent, lastKey)) {
|
|
83
91
|
const currentValue = parent[lastKey];
|
|
@@ -85,7 +93,7 @@ export async function handleEachTime(target, pathParts, forEachIndex, value, wit
|
|
|
85
93
|
throw new Error(`Cannot merge object into readonly primitive property '${String(lastKey)}'`);
|
|
86
94
|
}
|
|
87
95
|
// Recursively apply assignGingerly
|
|
88
|
-
assignGingerly(currentValue, value, options);
|
|
96
|
+
assignGingerly(currentValue, value, options, permissions);
|
|
89
97
|
}
|
|
90
98
|
else {
|
|
91
99
|
// Property is writable - replace it
|