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/assignFrom.ts CHANGED
@@ -1,140 +1,44 @@
1
1
  /**
2
- * Resolve RHS path strings against a source object, then assign the
3
- * resolved values into a target using assignGingerly.
2
+ * assignFrom.ts Synchronous assign-from-source function.
4
3
  *
5
- * Combines resolveValues + assignGingerly into a single call.
6
- * Inherits all assignGingerly options (withMethods, aka, signal, etc.).
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
- * @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
7
+ * For async protocol handlers or awaitable handler execution, use assignFromAsync.
12
8
  *
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' }
9
+ * Handler commands (` =>`) are fire-and-forget (kicked off asynchronously, not awaited).
21
10
  */
22
- import { resolveValues, ResolveValuesOptions } from './resolveValues.js';
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 interface AssignFromOptions extends IAssignGingerlyOptions, ResolveValuesOptions {
27
- /** Source object to resolve RHS path strings against */
28
- from: any;
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
- * Interface for assignFrom handler classes.
107
- * Handlers are invoked when a LHS key ends with ' =>'.
23
+ * Supported substitution variables and their option keys.
108
24
  */
109
- export interface AssignFromHandler {
110
- assign(lhsTarget: any, resolvedParams: Record<string, any>, options: AssignFromOptions): Promise<void> | void;
111
- }
112
-
113
- export interface AssignFromHandlerConstructor {
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
- export async function assignFrom(
243
- target: any,
244
- pattern: Record<string, any>,
245
- options: AssignFromOptions,
246
- permissions?: AssignPermissions
247
- ): Promise<any> {
248
- // First: expand looped substitution variables (${x}, ${y}, ${z})
249
- const expandedPattern = expandSubstitutions(pattern, options);
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
- // Separate handler commands ( =>), #[x] keys, and normal keys
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
- // Process normal keys via resolveValues + assignGingerly
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 = await resolveValues(normalPattern, options.from, {
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 — resolve element, then apply remaining path + value
286
- if (idRefNormalKeys.length > 0 && options.withIds) {
287
- const { resolveIdVariable, parseIdRef } = await import('./resolveIdRef.js');
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 ( =>) dynamically imported only when needed
264
+ // Process handler commands — fire-and-forget (async)
321
265
  if (handlerKeys.length > 0) {
322
- const { processHandlerCommands } = await import('./processHandlerCommands.js');
323
- await processHandlerCommands(target, handlerKeys, expandedPattern, options, permissions);
266
+ import('./processHandlerCommands.js').then(({ processHandlerCommands }) => {
267
+ processHandlerCommands(target, handlerKeys, expandedPattern, options, permissions);
268
+ });
324
269
  }
325
270
 
326
- // Process #[x] handler keys — resolve element, then pass to handler processing
271
+ // Process #[x] handler keys — fire-and-forget (async)
327
272
  if (idRefHandlerKeys.length > 0 && options.withIds) {
328
- const { resolveIdVariable, parseIdRef } = await import('./resolveIdRef.js');
329
- const { processHandlerCommands } = await import('./processHandlerCommands.js');
330
-
331
- for (const key of idRefHandlerKeys) {
332
- const parsed = parseIdRef(key);
333
- if (!parsed) continue;
334
-
335
- const el = resolveIdVariable(parsed.varName, target, options.withIds);
336
- if (!el) continue;
337
-
338
- // Build a synthetic key for processHandlerCommands:
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 — dynamically imported only when option is present
286
+ // Process inferred assignments (sync)
353
287
  if (options.inferredAssignments) {
354
- const { processInferredAssignments } = await import('./inferredAssignments.js');
355
- await processInferredAssignments(target, options.from, options.inferredAssignments);
288
+ processInferredAssignments(target, options.from, options.inferredAssignments);
356
289
 
357
- // Set up MutationObserver for new matching elements if beVigilant
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
- const { setupVigilantObserver } = await import('./beVigilant.js');
363
- setupVigilantObserver(target, options.from, options.inferredAssignments, options.signal);
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 — dynamically imported only when option is present
301
+ // Process bulk enhancements — fire-and-forget (async)
368
302
  if (options.enhance && options.enhance.length > 0) {
369
- const { enhanceAll } = await import('./enhanceAll.js');
370
- await enhanceAll(target, options.enhance, permissions);
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
+ }