assign-gingerly 0.0.57 → 0.0.59
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/README.md +243 -131
- package/assignFrom.js +124 -129
- package/assignFrom.ts +146 -233
- package/assignFromAsync.js +118 -0
- package/assignFromAsync.ts +240 -0
- package/getValues.js +223 -0
- package/getValues.ts +255 -0
- package/handlers/join.ts +1 -1
- package/handlers/lazyLoad.js +2 -2
- package/handlers/lazyLoad.ts +3 -3
- package/handlers/lazyLoadSwitch.ts +1 -1
- package/handlers/manageTemplateList.js +203 -0
- package/handlers/manageTemplateList.ts +240 -0
- package/handlers/microDataJoin.ts +1 -1
- package/index.js +1 -0
- package/index.ts +2 -0
- package/inferredAssignments.js +1 -1
- package/inferredAssignments.ts +2 -2
- package/markerUtils.js +136 -127
- package/package.json +18 -1
- package/processHandlerCommands.js +30 -3
- package/processHandlerCommands.ts +31 -7
- package/resolveValues.js +41 -125
- package/resolveValues.ts +131 -255
- package/transitionHelper.js +11 -5
- package/transitionHelper.ts +11 -5
- package/types/assign-gingerly/types.d.ts +51 -0
- package/waitForSettled.js +57 -0
- package/waitForSettled.ts +65 -0
package/assignFrom.ts
CHANGED
|
@@ -1,140 +1,44 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* resolved values into a target using assignGingerly.
|
|
2
|
+
* assignFrom.ts — Synchronous assign-from-source function.
|
|
4
3
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Resolves RHS path strings synchronously via getValues, then assigns into the target.
|
|
5
|
+
* Supports looped substitution, #[x] refs, inferredAssignments, and handleSpreads — all sync.
|
|
7
6
|
*
|
|
8
|
-
*
|
|
9
|
-
* @param pattern - Object whose RHS values may contain `?.` path strings
|
|
10
|
-
* @param options - Options including `from` (source object) and any assignGingerly options
|
|
11
|
-
* @returns The target object after merging
|
|
7
|
+
* For async protocol handlers or awaitable handler execution, use assignFromAsync.
|
|
12
8
|
*
|
|
13
|
-
*
|
|
14
|
-
* const source = { theme: { color: 'red' }, label: 'Hello' };
|
|
15
|
-
* const target = { color: 'blue', text: '' };
|
|
16
|
-
* assignFrom(target, {
|
|
17
|
-
* color: '?.theme?.color',
|
|
18
|
-
* text: '?.label'
|
|
19
|
-
* }, { from: source });
|
|
20
|
-
* // target is now { color: 'red', text: 'Hello' }
|
|
9
|
+
* Handler commands (` =>`) are fire-and-forget (kicked off asynchronously, not awaited).
|
|
21
10
|
*/
|
|
22
|
-
|
|
11
|
+
|
|
12
|
+
import { getValues, getValue } from './getValues.js';
|
|
23
13
|
import assignGingerly, { IAssignGingerlyOptions } from './assignGingerly.js';
|
|
14
|
+
import { resolveIdVariable, parseIdRef } from './resolveIdRef.js';
|
|
15
|
+
import { processInferredAssignments } from './inferredAssignments.js';
|
|
24
16
|
import type { AssignPermissions } from './isAllowedImportPath.js';
|
|
25
17
|
|
|
26
|
-
export
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
/** Loop variable bindings — expand pattern entries containing ${x} */
|
|
31
|
-
where_x_in?: string[];
|
|
32
|
-
/** Loop variable bindings — expand pattern entries containing ${y} */
|
|
33
|
-
where_y_in?: string[];
|
|
34
|
-
/** Loop variable bindings — expand pattern entries containing ${z} */
|
|
35
|
-
where_z_in?: string[];
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Cached element references by variable name.
|
|
39
|
-
* Used with `#[varName]` syntax in LHS keys for fast repeated element access.
|
|
40
|
-
*
|
|
41
|
-
* - String value: existing element ID (uses getElementById)
|
|
42
|
-
* - Object value: { qry: 'selector' } — finds element via querySelector on target, auto-assigns an ID
|
|
43
|
-
*
|
|
44
|
-
* Elements are cached via WeakRef with getElementById fallback on cache miss.
|
|
45
|
-
*/
|
|
46
|
-
withIds?: Record<string, string | { qry: string }>;
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Handler implementations scoped to this call.
|
|
50
|
-
* Key: the `do` name referenced in handler configs.
|
|
51
|
-
* Value: a class constructor, or an import path to dynamically load one.
|
|
52
|
-
*
|
|
53
|
-
* Import paths must be local (relative, absolute, or bare specifier — no cross-domain URLs).
|
|
54
|
-
* The module's default export is checked first; otherwise the first exported class
|
|
55
|
-
* with an `assign` method on its prototype is used.
|
|
56
|
-
*
|
|
57
|
-
* Built-in handlers (builtIns.*) auto-load without needing to be listed here.
|
|
58
|
-
*
|
|
59
|
-
* @example
|
|
60
|
-
* handlers: {
|
|
61
|
-
* 'my-list': MyListHandler, // class constructor
|
|
62
|
-
* 'my-chart': './handlers/chart.js', // dynamic import path
|
|
63
|
-
* 'vendor-widget': 'some-package/handler.js', // bare specifier (import map)
|
|
64
|
-
* }
|
|
65
|
-
*/
|
|
66
|
-
handlers?: Record<string, AssignFromHandlerConstructor | string>;
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Inferred assignments — automatically distribute source values to matching
|
|
70
|
-
* DOM elements based on structural conventions (itemprop, name, etc.).
|
|
71
|
-
*
|
|
72
|
-
* Uses the inferencer submodule to determine the correct property for each
|
|
73
|
-
* matched element (textContent, value, checked, dateTime, ish, etc.).
|
|
74
|
-
*
|
|
75
|
-
* @example
|
|
76
|
-
* inferredAssignments: {
|
|
77
|
-
* byItemprop: ['user', 'name', 'email'], // or true for all source keys
|
|
78
|
-
* beVigilant: true, // watch for new matching elements (requires signal)
|
|
79
|
-
* }
|
|
80
|
-
*/
|
|
81
|
-
inferredAssignments?: {
|
|
82
|
-
byItemprop?: string[] | true;
|
|
83
|
-
/** Watch for new matching elements via MutationObserver. Requires options.signal for cleanup. */
|
|
84
|
-
beVigilant?: boolean;
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Bulk enhancement application via EMC JSON configs.
|
|
89
|
-
* Finds matching elements and spawns enhancements on them.
|
|
90
|
-
*
|
|
91
|
-
* Each entry specifies an EMC JSON path and optionally overrides the matching selector.
|
|
92
|
-
* Enhancements are auto-registered if not already present in the enhancement registry.
|
|
93
|
-
*
|
|
94
|
-
* No scope perimeter is applied — use mount-observer for reactive/scoped enhancement.
|
|
95
|
-
*
|
|
96
|
-
* @example
|
|
97
|
-
* enhance: [
|
|
98
|
-
* { emc: 'be-bound/emc.json', matching: '[name]' },
|
|
99
|
-
* { emc: 'be-observant/emc.json', matching: '[itemprop]' },
|
|
100
|
-
* ]
|
|
101
|
-
*/
|
|
102
|
-
enhance?: Array<{ emc: string; matching?: string; parse?: boolean }>;
|
|
103
|
-
}
|
|
18
|
+
// Re-export types and interfaces for consumers
|
|
19
|
+
export type { AssignFromOptions, AssignFromHandler, AssignFromHandlerConstructor } from './assignFromAsync.js';
|
|
20
|
+
import type { AssignFromOptions } from './assignFromAsync.js';
|
|
104
21
|
|
|
105
22
|
/**
|
|
106
|
-
*
|
|
107
|
-
* Handlers are invoked when a LHS key ends with ' =>'.
|
|
23
|
+
* Supported substitution variables and their option keys.
|
|
108
24
|
*/
|
|
109
|
-
export
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
new (config: any): AssignFromHandler;
|
|
115
|
-
}
|
|
25
|
+
export const SUBSTITUTION_VARS = [
|
|
26
|
+
{ placeholder: '${x}', optionKey: 'where_x_in' },
|
|
27
|
+
{ placeholder: '${y}', optionKey: 'where_y_in' },
|
|
28
|
+
{ placeholder: '${z}', optionKey: 'where_z_in' },
|
|
29
|
+
] as const;
|
|
116
30
|
|
|
117
31
|
/**
|
|
118
32
|
* Check if a key ends with the handler operator ' =>'.
|
|
119
33
|
*/
|
|
120
|
-
function isHandlerCommand(key: string): boolean {
|
|
34
|
+
export function isHandlerCommand(key: string): boolean {
|
|
121
35
|
return key.endsWith(' =>');
|
|
122
36
|
}
|
|
123
37
|
|
|
124
|
-
/**
|
|
125
|
-
* Supported substitution variables and their option keys.
|
|
126
|
-
*/
|
|
127
|
-
const SUBSTITUTION_VARS = [
|
|
128
|
-
{ placeholder: '${x}', optionKey: 'where_x_in' },
|
|
129
|
-
{ placeholder: '${y}', optionKey: 'where_y_in' },
|
|
130
|
-
{ placeholder: '${z}', optionKey: 'where_z_in' },
|
|
131
|
-
] as const;
|
|
132
|
-
|
|
133
38
|
/**
|
|
134
39
|
* Recursively substitute a placeholder in all string values of an object.
|
|
135
|
-
* Returns a new object (shallow clone at each level) with substitutions applied.
|
|
136
40
|
*/
|
|
137
|
-
function substituteInValue(value: any, placeholder: string, replacement: string): any {
|
|
41
|
+
export function substituteInValue(value: any, placeholder: string, replacement: string): any {
|
|
138
42
|
if (typeof value === 'string') {
|
|
139
43
|
return value.includes(placeholder) ? value.replaceAll(placeholder, replacement) : value;
|
|
140
44
|
}
|
|
@@ -157,7 +61,7 @@ function substituteInValue(value: any, placeholder: string, replacement: string)
|
|
|
157
61
|
/**
|
|
158
62
|
* Check if a pattern entry (key + value) contains a given placeholder.
|
|
159
63
|
*/
|
|
160
|
-
function entryContainsPlaceholder(key: string, value: any, placeholder: string): boolean {
|
|
64
|
+
export function entryContainsPlaceholder(key: string, value: any, placeholder: string): boolean {
|
|
161
65
|
if (key.includes(placeholder)) return true;
|
|
162
66
|
return valueContainsPlaceholder(value, placeholder);
|
|
163
67
|
}
|
|
@@ -165,7 +69,7 @@ function entryContainsPlaceholder(key: string, value: any, placeholder: string):
|
|
|
165
69
|
/**
|
|
166
70
|
* Check if a value (string, object, or array) contains a placeholder.
|
|
167
71
|
*/
|
|
168
|
-
function valueContainsPlaceholder(value: any, placeholder: string): boolean {
|
|
72
|
+
export function valueContainsPlaceholder(value: any, placeholder: string): boolean {
|
|
169
73
|
if (typeof value === 'string') return value.includes(placeholder);
|
|
170
74
|
if (Array.isArray(value)) return value.some(item => valueContainsPlaceholder(item, placeholder));
|
|
171
75
|
if (value && typeof value === 'object') {
|
|
@@ -179,12 +83,8 @@ function valueContainsPlaceholder(value: any, placeholder: string): boolean {
|
|
|
179
83
|
|
|
180
84
|
/**
|
|
181
85
|
* Expand looped substitution variables in a pattern.
|
|
182
|
-
* Applies cartesian expansion: x values are expanded first, then y, then z.
|
|
183
|
-
* Each variable multiplies the entries — result count = x.length × y.length × z.length.
|
|
184
|
-
*
|
|
185
|
-
* Returns the expanded pattern (or the original if no substitutions apply).
|
|
186
86
|
*/
|
|
187
|
-
function expandSubstitutions(
|
|
87
|
+
export function expandSubstitutions(
|
|
188
88
|
pattern: Record<string, any>,
|
|
189
89
|
options: AssignFromOptions
|
|
190
90
|
): Record<string, any> {
|
|
@@ -197,7 +97,6 @@ function expandSubstitutions(
|
|
|
197
97
|
const expanded: [string, any][] = [];
|
|
198
98
|
for (const [key, value] of entries) {
|
|
199
99
|
if (entryContainsPlaceholder(key, value, placeholder)) {
|
|
200
|
-
// Expand this entry for each value in the variable array
|
|
201
100
|
for (const replacement of values) {
|
|
202
101
|
const newKey = key.includes(placeholder)
|
|
203
102
|
? key.replaceAll(placeholder, replacement)
|
|
@@ -206,7 +105,6 @@ function expandSubstitutions(
|
|
|
206
105
|
expanded.push([newKey, newValue]);
|
|
207
106
|
}
|
|
208
107
|
} else {
|
|
209
|
-
// No placeholder in this entry — pass through
|
|
210
108
|
expanded.push([key, value]);
|
|
211
109
|
}
|
|
212
110
|
}
|
|
@@ -218,14 +116,11 @@ function expandSubstitutions(
|
|
|
218
116
|
|
|
219
117
|
/**
|
|
220
118
|
* Convert entries to an object, merging duplicate handler (` =>`) keys into arrays.
|
|
221
|
-
* For normal (non-handler) keys, later entries overwrite earlier ones (standard object behavior).
|
|
222
|
-
* For handler keys, duplicate entries are combined into an array (Multiple Handlers pattern).
|
|
223
119
|
*/
|
|
224
|
-
function mergeHandlerDuplicates(entries: [string, any][]): Record<string, any> {
|
|
120
|
+
export function mergeHandlerDuplicates(entries: [string, any][]): Record<string, any> {
|
|
225
121
|
const result: Record<string, any> = {};
|
|
226
122
|
for (const [key, value] of entries) {
|
|
227
123
|
if (key.endsWith(' =>') && key in result) {
|
|
228
|
-
// Duplicate handler key — merge into array
|
|
229
124
|
const existing = result[key];
|
|
230
125
|
if (Array.isArray(existing)) {
|
|
231
126
|
existing.push(value);
|
|
@@ -239,16 +134,32 @@ function mergeHandlerDuplicates(entries: [string, any][]): Record<string, any> {
|
|
|
239
134
|
return result;
|
|
240
135
|
}
|
|
241
136
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
)
|
|
248
|
-
|
|
249
|
-
|
|
137
|
+
/**
|
|
138
|
+
* Recursively walk an object and handle "..." spread keys.
|
|
139
|
+
*/
|
|
140
|
+
export function handleSpreads(obj: Record<string, any>): Record<string, any> {
|
|
141
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
142
|
+
if (key !== '...' && typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
143
|
+
const proto = Object.getPrototypeOf(value);
|
|
144
|
+
if (proto === Object.prototype || proto === null) {
|
|
145
|
+
obj[key] = handleSpreads(value);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if ('...' in obj) {
|
|
150
|
+
const spreadValue = obj['...'];
|
|
151
|
+
delete obj['...'];
|
|
152
|
+
if (spreadValue && typeof spreadValue === 'object') {
|
|
153
|
+
Object.assign(obj, spreadValue);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return obj;
|
|
157
|
+
}
|
|
250
158
|
|
|
251
|
-
|
|
159
|
+
/**
|
|
160
|
+
* Categorize pattern keys into handler keys, #[x] keys, and normal keys.
|
|
161
|
+
*/
|
|
162
|
+
export function categorizeKeys(expandedPattern: Record<string, any>) {
|
|
252
163
|
const handlerKeys: string[] = [];
|
|
253
164
|
const normalPattern: Record<string, any> = {};
|
|
254
165
|
const idRefNormalKeys: string[] = [];
|
|
@@ -268,131 +179,133 @@ export async function assignFrom(
|
|
|
268
179
|
}
|
|
269
180
|
}
|
|
270
181
|
|
|
271
|
-
|
|
182
|
+
return { handlerKeys, normalPattern, idRefNormalKeys, idRefHandlerKeys };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Process #[x] normal keys synchronously.
|
|
187
|
+
*/
|
|
188
|
+
function processIdRefNormalKeys(
|
|
189
|
+
idRefNormalKeys: string[],
|
|
190
|
+
expandedPattern: Record<string, any>,
|
|
191
|
+
target: any,
|
|
192
|
+
options: AssignFromOptions
|
|
193
|
+
): void {
|
|
194
|
+
if (!options.withIds) return;
|
|
195
|
+
|
|
196
|
+
for (const key of idRefNormalKeys) {
|
|
197
|
+
const parsed = parseIdRef(key);
|
|
198
|
+
if (!parsed) continue;
|
|
199
|
+
|
|
200
|
+
const el = resolveIdVariable(parsed.varName, target, options.withIds);
|
|
201
|
+
if (!el) continue;
|
|
202
|
+
|
|
203
|
+
const value = expandedPattern[key];
|
|
204
|
+
if (parsed.remainingPath) {
|
|
205
|
+
const resolvedValue = getValues(
|
|
206
|
+
{ __v: value }, options.from,
|
|
207
|
+
{ withMethods: options.withMethods, aka: options.aka, protocols: options.protocols }
|
|
208
|
+
);
|
|
209
|
+
assignGingerly(el, { [parsed.remainingPath]: resolvedValue.__v }, options);
|
|
210
|
+
} else {
|
|
211
|
+
const resolvedValue = getValues(
|
|
212
|
+
typeof value === 'object' && value !== null ? value : { __v: value },
|
|
213
|
+
options.from,
|
|
214
|
+
{ withMethods: options.withMethods, aka: options.aka, protocols: options.protocols }
|
|
215
|
+
);
|
|
216
|
+
if (!('__v' in resolvedValue)) {
|
|
217
|
+
assignGingerly(el, resolvedValue, options);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Synchronous assignFrom — resolves values, assigns to target, all without awaiting.
|
|
225
|
+
*
|
|
226
|
+
* Handler commands (` =>`), beVigilant, and enhance are fire-and-forget (async, non-blocking).
|
|
227
|
+
* For awaitable handler execution, use assignFromAsync.
|
|
228
|
+
*
|
|
229
|
+
* @param target - Object to merge resolved values into
|
|
230
|
+
* @param pattern - Object whose RHS values may contain `?.` path strings
|
|
231
|
+
* @param options - Options including `from` (source object)
|
|
232
|
+
* @param permissions - Optional security permissions
|
|
233
|
+
* @returns The target object after merging
|
|
234
|
+
*/
|
|
235
|
+
export function assignFrom(
|
|
236
|
+
target: any,
|
|
237
|
+
pattern: Record<string, any>,
|
|
238
|
+
options: AssignFromOptions,
|
|
239
|
+
permissions?: AssignPermissions
|
|
240
|
+
): any {
|
|
241
|
+
// Expand looped substitution variables
|
|
242
|
+
const expandedPattern = expandSubstitutions(pattern, options);
|
|
243
|
+
|
|
244
|
+
// Categorize keys
|
|
245
|
+
const { handlerKeys, normalPattern, idRefNormalKeys, idRefHandlerKeys } = categorizeKeys(expandedPattern);
|
|
246
|
+
|
|
247
|
+
// Process normal keys via getValues (sync) + assignGingerly
|
|
272
248
|
if (Object.keys(normalPattern).length > 0) {
|
|
273
|
-
const resolved =
|
|
249
|
+
const resolved = getValues(normalPattern, options.from, {
|
|
274
250
|
withMethods: options.withMethods,
|
|
275
251
|
aka: options.aka,
|
|
276
252
|
protocols: options.protocols
|
|
277
253
|
});
|
|
278
254
|
|
|
279
|
-
// Recursively handle "..." spread keys at all nesting levels
|
|
280
255
|
handleSpreads(resolved);
|
|
281
|
-
|
|
282
256
|
assignGingerly(target, resolved, options);
|
|
283
257
|
}
|
|
284
258
|
|
|
285
|
-
// Process #[x] normal keys
|
|
286
|
-
if (idRefNormalKeys.length > 0
|
|
287
|
-
|
|
288
|
-
for (const key of idRefNormalKeys) {
|
|
289
|
-
const parsed = parseIdRef(key);
|
|
290
|
-
if (!parsed) continue;
|
|
291
|
-
|
|
292
|
-
const el = resolveIdVariable(parsed.varName, target, options.withIds);
|
|
293
|
-
if (!el) continue;
|
|
294
|
-
|
|
295
|
-
const value = expandedPattern[key];
|
|
296
|
-
if (parsed.remainingPath) {
|
|
297
|
-
// Resolve the RHS value
|
|
298
|
-
const resolvedValue = await resolveValues(
|
|
299
|
-
{ __v: value }, options.from,
|
|
300
|
-
{ withMethods: options.withMethods, aka: options.aka, protocols: options.protocols }
|
|
301
|
-
);
|
|
302
|
-
// Apply remaining path on the resolved element
|
|
303
|
-
assignGingerly(el, { [parsed.remainingPath]: resolvedValue.__v }, options);
|
|
304
|
-
} else {
|
|
305
|
-
// No remaining path — resolve and assign directly to the element
|
|
306
|
-
const resolvedValue = await resolveValues(
|
|
307
|
-
typeof value === 'object' && value !== null ? value : { __v: value },
|
|
308
|
-
options.from,
|
|
309
|
-
{ withMethods: options.withMethods, aka: options.aka, protocols: options.protocols }
|
|
310
|
-
);
|
|
311
|
-
if ('__v' in resolvedValue) {
|
|
312
|
-
// Single value — can't assign to element root without a path
|
|
313
|
-
} else {
|
|
314
|
-
assignGingerly(el, resolvedValue, options);
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
}
|
|
259
|
+
// Process #[x] normal keys (sync)
|
|
260
|
+
if (idRefNormalKeys.length > 0) {
|
|
261
|
+
processIdRefNormalKeys(idRefNormalKeys, expandedPattern, target, options);
|
|
318
262
|
}
|
|
319
263
|
|
|
320
|
-
// Process handler commands
|
|
264
|
+
// Process handler commands — fire-and-forget (async)
|
|
321
265
|
if (handlerKeys.length > 0) {
|
|
322
|
-
|
|
323
|
-
|
|
266
|
+
import('./processHandlerCommands.js').then(({ processHandlerCommands }) => {
|
|
267
|
+
processHandlerCommands(target, handlerKeys, expandedPattern, options, permissions);
|
|
268
|
+
});
|
|
324
269
|
}
|
|
325
270
|
|
|
326
|
-
// Process #[x] handler keys —
|
|
271
|
+
// Process #[x] handler keys — fire-and-forget (async)
|
|
327
272
|
if (idRefHandlerKeys.length > 0 && options.withIds) {
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
// The resolved element becomes the target, remaining path is the LHS
|
|
340
|
-
const syntheticKey = parsed.remainingPath
|
|
341
|
-
? `${parsed.remainingPath} =>`
|
|
342
|
-
: ' =>';
|
|
343
|
-
|
|
344
|
-
const syntheticPattern: Record<string, any> = {
|
|
345
|
-
[syntheticKey]: expandedPattern[key]
|
|
346
|
-
};
|
|
347
|
-
|
|
348
|
-
await processHandlerCommands(el, [syntheticKey], syntheticPattern, options, permissions);
|
|
349
|
-
}
|
|
273
|
+
import('./processHandlerCommands.js').then(({ processHandlerCommands }) => {
|
|
274
|
+
for (const key of idRefHandlerKeys) {
|
|
275
|
+
const parsed = parseIdRef(key);
|
|
276
|
+
if (!parsed) continue;
|
|
277
|
+
const el = resolveIdVariable(parsed.varName, target, options.withIds!);
|
|
278
|
+
if (!el) continue;
|
|
279
|
+
const syntheticKey = parsed.remainingPath ? `${parsed.remainingPath} =>` : ' =>';
|
|
280
|
+
const syntheticPattern = { [syntheticKey]: expandedPattern[key] };
|
|
281
|
+
processHandlerCommands(el, [syntheticKey], syntheticPattern, options, permissions);
|
|
282
|
+
}
|
|
283
|
+
});
|
|
350
284
|
}
|
|
351
285
|
|
|
352
|
-
// Process inferred assignments
|
|
286
|
+
// Process inferred assignments (sync)
|
|
353
287
|
if (options.inferredAssignments) {
|
|
354
|
-
|
|
355
|
-
await processInferredAssignments(target, options.from, options.inferredAssignments);
|
|
288
|
+
processInferredAssignments(target, options.from, options.inferredAssignments);
|
|
356
289
|
|
|
357
|
-
//
|
|
290
|
+
// beVigilant — fire-and-forget (async)
|
|
358
291
|
if (options.inferredAssignments.beVigilant) {
|
|
359
292
|
if (!options.signal) {
|
|
360
293
|
throw new Error('assignFrom: inferredAssignments.beVigilant requires options.signal (AbortSignal) for cleanup');
|
|
361
294
|
}
|
|
362
|
-
|
|
363
|
-
|
|
295
|
+
import('./beVigilant.js').then(({ setupVigilantObserver }) => {
|
|
296
|
+
setupVigilantObserver(target, options.from, options.inferredAssignments!, options.signal!);
|
|
297
|
+
});
|
|
364
298
|
}
|
|
365
299
|
}
|
|
366
300
|
|
|
367
|
-
// Process bulk enhancements —
|
|
301
|
+
// Process bulk enhancements — fire-and-forget (async)
|
|
368
302
|
if (options.enhance && options.enhance.length > 0) {
|
|
369
|
-
|
|
370
|
-
|
|
303
|
+
import('./enhanceAll.js').then(({ enhanceAll }) => {
|
|
304
|
+
enhanceAll(target, options.enhance!, permissions);
|
|
305
|
+
});
|
|
371
306
|
}
|
|
372
307
|
|
|
373
308
|
return target;
|
|
374
309
|
}
|
|
375
310
|
|
|
376
|
-
|
|
377
|
-
* Recursively walk an object and handle "..." spread keys.
|
|
378
|
-
* When a "..." key is found, its value (which should be an object after protocol resolution)
|
|
379
|
-
* is spread into the parent, replacing the "..." entry.
|
|
380
|
-
*/
|
|
381
|
-
function handleSpreads(obj: Record<string, any>): Record<string, any> {
|
|
382
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
383
|
-
if (key !== '...' && typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
384
|
-
const proto = Object.getPrototypeOf(value);
|
|
385
|
-
if (proto === Object.prototype || proto === null) {
|
|
386
|
-
obj[key] = handleSpreads(value);
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
if ('...' in obj) {
|
|
391
|
-
const spreadValue = obj['...'];
|
|
392
|
-
delete obj['...'];
|
|
393
|
-
if (spreadValue && typeof spreadValue === 'object') {
|
|
394
|
-
Object.assign(obj, spreadValue);
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
return obj;
|
|
398
|
-
}
|
|
311
|
+
export default assignFrom;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve RHS path strings against a source object, then assign the
|
|
3
|
+
* resolved values into a target using assignGingerly.
|
|
4
|
+
*
|
|
5
|
+
* Combines resolveValues + assignGingerly into a single call.
|
|
6
|
+
* Inherits all assignGingerly options (withMethods, aka, signal, etc.).
|
|
7
|
+
*
|
|
8
|
+
* @param target - Object to merge resolved values into
|
|
9
|
+
* @param pattern - Object whose RHS values may contain `?.` path strings
|
|
10
|
+
* @param options - Options including `from` (source object) and any assignGingerly options
|
|
11
|
+
* @returns The target object after merging
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* const source = { theme: { color: 'red' }, label: 'Hello' };
|
|
15
|
+
* const target = { color: 'blue', text: '' };
|
|
16
|
+
* assignFrom(target, {
|
|
17
|
+
* color: '?.theme?.color',
|
|
18
|
+
* text: '?.label'
|
|
19
|
+
* }, { from: source });
|
|
20
|
+
* // target is now { color: 'red', text: 'Hello' }
|
|
21
|
+
*/
|
|
22
|
+
import { resolveValues } from './resolveValues.js';
|
|
23
|
+
import assignGingerly from './assignGingerly.js';
|
|
24
|
+
import { expandSubstitutions, categorizeKeys, handleSpreads } from './assignFrom.js';
|
|
25
|
+
// Module cache for processHandlerCommands — avoids await on dynamic import after first call
|
|
26
|
+
let _processHandlerCommands;
|
|
27
|
+
export async function assignFromAsync(target, pattern, options, permissions) {
|
|
28
|
+
// First: expand looped substitution variables (${x}, ${y}, ${z})
|
|
29
|
+
const expandedPattern = expandSubstitutions(pattern, options);
|
|
30
|
+
// Categorize keys
|
|
31
|
+
const { handlerKeys, normalPattern, idRefNormalKeys, idRefHandlerKeys } = categorizeKeys(expandedPattern);
|
|
32
|
+
// Process normal keys via resolveValues + assignGingerly
|
|
33
|
+
if (Object.keys(normalPattern).length > 0) {
|
|
34
|
+
const resolved = await resolveValues(normalPattern, options.from, {
|
|
35
|
+
withMethods: options.withMethods,
|
|
36
|
+
aka: options.aka,
|
|
37
|
+
protocols: options.protocols
|
|
38
|
+
});
|
|
39
|
+
// Recursively handle "..." spread keys at all nesting levels
|
|
40
|
+
handleSpreads(resolved);
|
|
41
|
+
assignGingerly(target, resolved, options);
|
|
42
|
+
}
|
|
43
|
+
// Process #[x] normal keys — resolve element, then apply remaining path + value
|
|
44
|
+
if (idRefNormalKeys.length > 0 && options.withIds) {
|
|
45
|
+
const { resolveIdVariable, parseIdRef } = await import('./resolveIdRef.js');
|
|
46
|
+
for (const key of idRefNormalKeys) {
|
|
47
|
+
const parsed = parseIdRef(key);
|
|
48
|
+
if (!parsed)
|
|
49
|
+
continue;
|
|
50
|
+
const el = resolveIdVariable(parsed.varName, target, options.withIds);
|
|
51
|
+
if (!el)
|
|
52
|
+
continue;
|
|
53
|
+
const value = expandedPattern[key];
|
|
54
|
+
if (parsed.remainingPath) {
|
|
55
|
+
// Resolve the RHS value
|
|
56
|
+
const resolvedValue = await resolveValues({ __v: value }, options.from, { withMethods: options.withMethods, aka: options.aka, protocols: options.protocols });
|
|
57
|
+
// Apply remaining path on the resolved element
|
|
58
|
+
assignGingerly(el, { [parsed.remainingPath]: resolvedValue.__v }, options);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
// No remaining path — resolve and assign directly to the element
|
|
62
|
+
const resolvedValue = await resolveValues(typeof value === 'object' && value !== null ? value : { __v: value }, options.from, { withMethods: options.withMethods, aka: options.aka, protocols: options.protocols });
|
|
63
|
+
if ('__v' in resolvedValue) {
|
|
64
|
+
// Single value — can't assign to element root without a path
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
assignGingerly(el, resolvedValue, options);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// Process handler commands ( =>) — cached after first load to avoid await overhead
|
|
73
|
+
if (handlerKeys.length > 0) {
|
|
74
|
+
_processHandlerCommands ??= (await import('./processHandlerCommands.js')).processHandlerCommands;
|
|
75
|
+
await _processHandlerCommands(target, handlerKeys, expandedPattern, options, permissions);
|
|
76
|
+
}
|
|
77
|
+
// Process #[x] handler keys — resolve element, then pass to handler processing
|
|
78
|
+
if (idRefHandlerKeys.length > 0 && options.withIds) {
|
|
79
|
+
const { resolveIdVariable, parseIdRef } = await import('./resolveIdRef.js');
|
|
80
|
+
_processHandlerCommands ??= (await import('./processHandlerCommands.js')).processHandlerCommands;
|
|
81
|
+
for (const key of idRefHandlerKeys) {
|
|
82
|
+
const parsed = parseIdRef(key);
|
|
83
|
+
if (!parsed)
|
|
84
|
+
continue;
|
|
85
|
+
const el = resolveIdVariable(parsed.varName, target, options.withIds);
|
|
86
|
+
if (!el)
|
|
87
|
+
continue;
|
|
88
|
+
// Build a synthetic key for processHandlerCommands:
|
|
89
|
+
// The resolved element becomes the target, remaining path is the LHS
|
|
90
|
+
const syntheticKey = parsed.remainingPath
|
|
91
|
+
? `${parsed.remainingPath} =>`
|
|
92
|
+
: ' =>';
|
|
93
|
+
const syntheticPattern = {
|
|
94
|
+
[syntheticKey]: expandedPattern[key]
|
|
95
|
+
};
|
|
96
|
+
await _processHandlerCommands(el, [syntheticKey], syntheticPattern, options, permissions);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// Process inferred assignments — dynamically imported only when option is present
|
|
100
|
+
if (options.inferredAssignments) {
|
|
101
|
+
const { processInferredAssignments } = await import('./inferredAssignments.js');
|
|
102
|
+
await processInferredAssignments(target, options.from, options.inferredAssignments);
|
|
103
|
+
// Set up MutationObserver for new matching elements if beVigilant
|
|
104
|
+
if (options.inferredAssignments.beVigilant) {
|
|
105
|
+
if (!options.signal) {
|
|
106
|
+
throw new Error('assignFrom: inferredAssignments.beVigilant requires options.signal (AbortSignal) for cleanup');
|
|
107
|
+
}
|
|
108
|
+
const { setupVigilantObserver } = await import('./beVigilant.js');
|
|
109
|
+
setupVigilantObserver(target, options.from, options.inferredAssignments, options.signal);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// Process bulk enhancements — dynamically imported only when option is present
|
|
113
|
+
if (options.enhance && options.enhance.length > 0) {
|
|
114
|
+
const { enhanceAll } = await import('./enhanceAll.js');
|
|
115
|
+
await enhanceAll(target, options.enhance, permissions);
|
|
116
|
+
}
|
|
117
|
+
return target;
|
|
118
|
+
}
|