assign-gingerly 0.0.64 → 0.0.65

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 CHANGED
@@ -94,10 +94,24 @@ For most use cases — including `manageTemplateList`, reactive merge cycles, an
94
94
  assignFrom adds support for:
95
95
 
96
96
  1. Resolving RHS path strings against a source object (`from`).
97
- 2. Protocol resolution (`globalThis://`, `localStorage://`, custom sync protocols).
98
- 3. Handler plugins via the ` =>` operator for custom logic (fire-and-forget in sync mode, awaitable in async mode).
99
- 4. Looped substitution with `where_x_in` / `where_y_in` / `where_z_in` for expanding template patterns into multiple concrete assignments.
100
- 5. Spread merging via the `"..."` key.
97
+ 2. Target-relative root references via `$0` so paths can resolve from the first argument passed to `assignFrom`/`assignFromAsync` (the target object) instead of the `from` object.
98
+ 3. Protocol resolution (`globalThis://`, `localStorage://`, custom sync protocols).
99
+ 4. Handler plugins via the ` =>` operator for custom logic (fire-and-forget in sync mode, awaitable in async mode).
100
+ 5. Looped substitution with `where_x_in` / `where_y_in` / `where_z_in` for expanding template patterns into multiple concrete assignments.
101
+ 6. Spread merging via the `"..."` key.
102
+
103
+ Example:
104
+
105
+ ```TypeScript
106
+ const target = { value: 'target-value' };
107
+ const source = { value: 'from-value' };
108
+
109
+ assignFrom(target, {
110
+ resolved: '$0?.value'
111
+ }, { from: source });
112
+
113
+ console.log(target.resolved); // 'target-value'
114
+ ```
101
115
 
102
116
  All of assignGingerly's features (nested paths, `withMethods`, `aka`, `@each`, `@eachTime`, registry, etc.) are inherited.
103
117
 
@@ -476,11 +490,11 @@ assignGingerly(div, {
476
490
 
477
491
  // With aliases (concise)
478
492
  assignGingerly(div, {
479
- '?.$?.my-element?.c?.+': 'highlighted',
480
- '?.$?.your-element?.c?.+': 'active'
493
+ '?.🔍?.my-element?.🎨?.+': 'highlighted',
494
+ '?.🔍?.your-element?.🎨?.+': 'active'
481
495
  }, {
482
496
  withMethods: ['querySelector', 'add'],
483
- aka: { '$': 'querySelector', 'c': 'classList', '+': 'add' }
497
+ aka: { '🔍': 'querySelector', '🎨': 'classList', '+': 'add' }
484
498
  });
485
499
  ```
486
500
 
@@ -536,6 +550,33 @@ assignGingerly(element, {
536
550
  - Improves readability when you have many similar operations
537
551
  - Works seamlessly with `withMethods`
538
552
 
553
+ **Shorthand for method aliases:**
554
+
555
+ If you frequently pair aliases with method calls, you can use `akaMethods` as a compact alternative to passing both `withMethods` and `aka` separately. Each entry in `akaMethods` maps an alias to the method name that should be treated as a callable method:
556
+
557
+ ```TypeScript
558
+ const div = document.createElement('div');
559
+
560
+ div.innerHTML = `
561
+ <my-element></my-element>
562
+ `;
563
+
564
+ assignGingerly(div, {
565
+ '?.🔍?.my-element?.🎨?.+': 'highlighted'
566
+ }, {
567
+ akaMethods: {
568
+ '🔍': 'querySelector',
569
+ '🎨': 'classList',
570
+ '+': 'add'
571
+ }
572
+ });
573
+
574
+ const myElement = div.querySelector('my-element');
575
+ console.log(myElement?.classList.contains('highlighted')); // true
576
+ ```
577
+
578
+ `akaMethods` is additive: it complements the existing `withMethods` and `aka` options rather than replacing them.
579
+
539
580
  ## Example 3e - ForEach with @each
540
581
 
541
582
  The `@each` symbol allows you to iterate over collections and apply operations to each item. This works with any iterable including Arrays, NodeList, HTMLCollection, and more.
@@ -4387,7 +4428,7 @@ md`${$.firstName} ${{ prop: 'birthDate', val: $.birthDT, format: 'long' }}`
4387
4428
 
4388
4429
  Both auto-detect path proxies (no `.path` needed inside template literals) and preserve nested arrays for optional segments.
4389
4430
 
4390
- ## Cached Element Resolution with `#[x]` and `withIds`
4431
+ ## Cached Element Resolution with `#[x]` and `pin`
4391
4432
 
4392
4433
  `assignFrom` supports cached element references via the `#[x]` syntax in LHS keys. This provides near-zero-cost repeated access to DOM elements (~10ns via WeakRef) instead of expensive `querySelector` calls (~3,000-17,000ns for class selectors at scale).
4393
4434
 
@@ -4402,7 +4443,7 @@ await assignFromAsync(document.body, {
4402
4443
  }
4403
4444
  }, {
4404
4445
  from: viewModel,
4405
- withIds: {
4446
+ pin: {
4406
4447
  main: { qry: '.mainView' } // find by class, auto-assign ID, cache
4407
4448
  }
4408
4449
  });
@@ -4416,12 +4457,12 @@ await assignFromAsync(document.body, {
4416
4457
  4. On subsequent calls, the cached `WeakRef.deref()` returns the element in ~10ns.
4417
4458
  5. If the WeakRef is collected (element was GC'd), falls back to `getElementById` (~10-100ns).
4418
4459
 
4419
- **`withIds` — stable references with auto-assigned IDs:**
4460
+ **`pin` — stable references with auto-assigned IDs:**
4420
4461
 
4421
4462
  All forms assign a unique ID to the resolved element (if it doesn't have one). This makes the reference resilient to future DOM mutations — once an element has an ID, it can be found regardless of structural changes.
4422
4463
 
4423
4464
  ```TypeScript
4424
- withIds: {
4465
+ pin: {
4425
4466
  x: { qry: '.mainView' }, // querySelector on target, auto-assign ID
4426
4467
  y: 'existingId', // element already has an ID, cache via WeakRef
4427
4468
  z: { path: [0, 1], expect: 'input', fallback: true }, // child index path + auto-ID + validation
@@ -4451,14 +4492,14 @@ at: {
4451
4492
  - You want a clean DOM (no auto-generated `id` attributes)
4452
4493
  - You're rendering many items (1,000 rows × 2 refs = 2,000 fewer DOM attributes)
4453
4494
 
4454
- Both `withIds` and `at` use the same `#[x]` syntax on LHS and RHS keys.
4495
+ Both `pin` and `at` use the same `#[x]` syntax on LHS and RHS keys.
4455
4496
 
4456
4497
  **Validated paths with `expect` and `fallback`:**
4457
4498
 
4458
- Both `withIds` and `at` support the `{ path, expect, fallback }` form for dev-time validation:
4499
+ Both `pin` and `at` support the `{ path, expect, fallback }` form for dev-time validation:
4459
4500
 
4460
4501
  ```TypeScript
4461
- withIds: {
4502
+ pin: {
4462
4503
  nameInput: { path: [1], expect: 'input' }, // warn if [1] isn't an <input>
4463
4504
  label: { path: [0], expect: 'label', fallback: true } // warn + recover via querySelector
4464
4505
  }
@@ -4481,7 +4522,7 @@ assignFrom(document.body, {
4481
4522
  }, {
4482
4523
  from: viewModel,
4483
4524
  withMethods: ['querySelector'],
4484
- withIds: {
4525
+ pin: {
4485
4526
  form: { qry: '.registration-form' },
4486
4527
  header: 'page-header'
4487
4528
  }
@@ -4498,7 +4539,7 @@ await assignFromAsync(container, {
4498
4539
  }
4499
4540
  }, {
4500
4541
  from: router,
4501
- withIds: { outlet: { qry: '.router-outlet' } },
4542
+ pin: { outlet: { qry: '.router-outlet' } },
4502
4543
  protocols: { globalThis: k => globalThis[k] }
4503
4544
  });
4504
4545
  ```
@@ -4511,7 +4552,7 @@ await assignFromAsync(container, {
4511
4552
  | `getElementById(id)` | 100ns | 20ns | 10ns |
4512
4553
  | `Map<id, WeakRef>.deref()` | 12ns | 40ns | 10ns |
4513
4554
 
4514
- The `#[x]` + `withIds` pattern gives you the Map+WeakRef speed tier automatically.
4555
+ The `#[x]` + `pin` pattern gives you the Map+WeakRef speed tier automatically.
4515
4556
 
4516
4557
  **RHS references (`#[x]` on the value side):**
4517
4558
 
@@ -4524,7 +4565,7 @@ assignFrom(form, {
4524
4565
  '?.headerText': '#[info]?.dataset?.user', // → nested property access
4525
4566
  }, {
4526
4567
  from: {},
4527
- withIds: { nameInput: { qry: 'input' }, info: { qry: '.info' } },
4568
+ pin: { nameInput: { qry: 'input' }, info: { qry: '.info' } },
4528
4569
  withMethods: ['querySelector']
4529
4570
  });
4530
4571
  ```
package/assignFrom.js CHANGED
@@ -49,7 +49,8 @@ function resolveTernaryValue(value, source, options) {
49
49
  return getValue(value, source, {
50
50
  withMethods: options.withMethods,
51
51
  aka: options.aka,
52
- protocols: options.protocols
52
+ protocols: options.protocols,
53
+ root: options.root
53
54
  });
54
55
  }
55
56
  if (typeof value === 'string' && value.includes('://') && options.protocols) {
@@ -60,7 +61,8 @@ function resolveTernaryValue(value, source, options) {
60
61
  return getValue(value, source, {
61
62
  withMethods: options.withMethods,
62
63
  aka: options.aka,
63
- protocols: options.protocols
64
+ protocols: options.protocols,
65
+ root: options.root
64
66
  });
65
67
  }
66
68
  }
@@ -287,16 +289,16 @@ export function categorizeKeys(expandedPattern) {
287
289
  return { handlerKeys, normalPattern, idRefNormalKeys, idRefHandlerKeys, ternaryKeys };
288
290
  }
289
291
  /**
290
- * Merge withIds and at into a single lookup map for resolveIdVariable.
292
+ * Merge pin and at into a single lookup map for resolveIdVariable.
291
293
  */
292
294
  function getEffectiveIds(options) {
293
- if (!options.withIds && !options.at)
295
+ if (!options.pin && !options.at)
294
296
  return undefined;
295
- if (options.withIds && !options.at)
296
- return options.withIds;
297
- if (!options.withIds && options.at)
297
+ if (options.pin && !options.at)
298
+ return options.pin;
299
+ if (!options.pin && options.at)
298
300
  return options.at;
299
- return { ...options.withIds, ...options.at };
301
+ return { ...options.pin, ...options.at };
300
302
  }
301
303
  /**
302
304
  * Process #[x] normal keys synchronously.
@@ -305,6 +307,7 @@ function processIdRefNormalKeys(idRefNormalKeys, expandedPattern, target, option
305
307
  const ids = getEffectiveIds(options);
306
308
  if (!ids)
307
309
  return;
310
+ //TODO
308
311
  for (const key of idRefNormalKeys) {
309
312
  const parsed = parseIdRef(key);
310
313
  if (!parsed)
@@ -314,11 +317,11 @@ function processIdRefNormalKeys(idRefNormalKeys, expandedPattern, target, option
314
317
  continue;
315
318
  const value = expandedPattern[key];
316
319
  if (parsed.remainingPath) {
317
- const resolvedValue = getValues({ __v: value }, options.from, { withMethods: options.withMethods, aka: options.aka, protocols: options.protocols });
320
+ const resolvedValue = getValues({ __v: value }, options.from, { withMethods: options.withMethods, aka: options.aka, akaMethods: options.akaMethods, protocols: options.protocols, root: target });
318
321
  assignGingerly(el, { [parsed.remainingPath]: resolvedValue.__v }, options);
319
322
  }
320
323
  else {
321
- const resolvedValue = getValues(typeof value === 'object' && value !== null ? value : { __v: value }, options.from, { withMethods: options.withMethods, aka: options.aka, protocols: options.protocols });
324
+ const resolvedValue = getValues(typeof value === 'object' && value !== null ? value : { __v: value }, options.from, { withMethods: options.withMethods, aka: options.aka, akaMethods: options.akaMethods, protocols: options.protocols, root: target });
322
325
  if (!('__v' in resolvedValue)) {
323
326
  assignGingerly(el, resolvedValue, options);
324
327
  }
@@ -342,6 +345,7 @@ export function assignFrom(target, pattern, options, permissions) {
342
345
  const expandedPattern = expandSubstitutions(pattern, options);
343
346
  // Categorize keys
344
347
  const { handlerKeys, normalPattern, idRefNormalKeys, idRefHandlerKeys, ternaryKeys } = categorizeKeys(expandedPattern);
348
+ const resolveOptions = { ...options, root: target };
345
349
  // Process ?= ternary keys (sync)
346
350
  if (ternaryKeys.length > 0) {
347
351
  const ternaryResolved = {};
@@ -352,7 +356,7 @@ export function assignFrom(target, pattern, options, permissions) {
352
356
  const arr = expandedPattern[key];
353
357
  if (!Array.isArray(arr) || arr.length < 2)
354
358
  continue;
355
- const result = evaluateTernary(arr, options.from, options);
359
+ const result = evaluateTernary(arr, options.from, resolveOptions);
356
360
  if (result !== TERNARY_SKIP) {
357
361
  ternaryResolved[lhsPath] = result;
358
362
  }
@@ -364,7 +368,7 @@ export function assignFrom(target, pattern, options, permissions) {
364
368
  // Process normal keys via getValues (sync) + assignGingerly
365
369
  if (Object.keys(normalPattern).length > 0) {
366
370
  // Resolve #[x] references on RHS values before getValues
367
- if (options.withIds || options.at) {
371
+ if (options.pin || options.at) {
368
372
  const ids = getEffectiveIds(options);
369
373
  for (const key of Object.keys(normalPattern)) {
370
374
  const value = normalPattern[key];
@@ -379,7 +383,9 @@ export function assignFrom(target, pattern, options, permissions) {
379
383
  normalPattern[key] = getValue(remainingPath, el, {
380
384
  withMethods: options.withMethods,
381
385
  aka: options.aka,
382
- protocols: options.protocols
386
+ akaMethods: options.akaMethods,
387
+ protocols: options.protocols,
388
+ root: target
383
389
  });
384
390
  }
385
391
  else {
@@ -390,11 +396,7 @@ export function assignFrom(target, pattern, options, permissions) {
390
396
  }
391
397
  }
392
398
  }
393
- const resolved = getValues(normalPattern, options.from, {
394
- withMethods: options.withMethods,
395
- aka: options.aka,
396
- protocols: options.protocols
397
- });
399
+ const resolved = getValues(normalPattern, options.from, resolveOptions);
398
400
  handleSpreads(resolved);
399
401
  assignGingerly(target, resolved, options);
400
402
  }
@@ -409,7 +411,7 @@ export function assignFrom(target, pattern, options, permissions) {
409
411
  });
410
412
  }
411
413
  // Process #[x] handler keys — fire-and-forget (async)
412
- if (idRefHandlerKeys.length > 0 && (options.withIds || options.at)) {
414
+ if (idRefHandlerKeys.length > 0 && (options.pin || options.at)) {
413
415
  const ids = getEffectiveIds(options);
414
416
  import('./processHandlerCommands.js').then(({ processHandlerCommands }) => {
415
417
  for (const key of idRefHandlerKeys) {
package/assignFrom.ts CHANGED
@@ -10,14 +10,14 @@
10
10
  */
11
11
 
12
12
  import { getValues, getValue } from './getValues.js';
13
- import assignGingerly, { IAssignGingerlyOptions } from './assignGingerly.js';
13
+ import assignGingerly from './assignGingerly.js';
14
14
  import { resolveIdVariable, parseIdRef } from './resolveIdRef.js';
15
15
  import { processInferredAssignments } from './inferredAssignments.js';
16
16
  import type { AssignPermissions } from './isAllowedImportPath.js';
17
+ import type { AssignFromOptions, AssignFromHandler, AssignFromHandlerConstructor } from './types/assign-gingerly/types.js';
17
18
 
18
- // Re-export types and interfaces for consumers
19
- export type { AssignFromOptions, AssignFromHandler, AssignFromHandlerConstructor } from './assignFromAsync.js';
20
- import type { AssignFromOptions } from './assignFromAsync.js';
19
+ // Re-export types for consumers
20
+ export type { AssignFromOptions, AssignFromHandler, AssignFromHandlerConstructor };
21
21
 
22
22
  /**
23
23
  * Supported substitution variables and their option keys.
@@ -54,12 +54,13 @@ export function parseTernaryCommand(key: string): string | null {
54
54
  * Resolve a single value — if it's a `?.` path string, resolve against source.
55
55
  * If it's a protocol string, resolve via protocol. Otherwise pass through as literal.
56
56
  */
57
- function resolveTernaryValue(value: any, source: any, options: AssignFromOptions): any {
57
+ function resolveTernaryValue(value: any, source: any, options: AssignFromOptions | any): any {
58
58
  if (typeof value === 'string' && value.startsWith('?.')) {
59
59
  return getValue(value, source, {
60
60
  withMethods: options.withMethods,
61
61
  aka: options.aka,
62
- protocols: options.protocols
62
+ protocols: options.protocols,
63
+ root: options.root
63
64
  });
64
65
  }
65
66
  if (typeof value === 'string' && value.includes('://') && options.protocols) {
@@ -70,7 +71,8 @@ function resolveTernaryValue(value: any, source: any, options: AssignFromOptions
70
71
  return getValue(value, source, {
71
72
  withMethods: options.withMethods,
72
73
  aka: options.aka,
73
- protocols: options.protocols
74
+ protocols: options.protocols,
75
+ root: options.root
74
76
  });
75
77
  }
76
78
  }
@@ -299,13 +301,13 @@ export function categorizeKeys(expandedPattern: Record<string, any>) {
299
301
  }
300
302
 
301
303
  /**
302
- * Merge withIds and at into a single lookup map for resolveIdVariable.
304
+ * Merge pin and at into a single lookup map for resolveIdVariable.
303
305
  */
304
306
  function getEffectiveIds(options: AssignFromOptions): Record<string, any> | undefined {
305
- if (!options.withIds && !options.at) return undefined;
306
- if (options.withIds && !options.at) return options.withIds;
307
- if (!options.withIds && options.at) return options.at;
308
- return { ...options.withIds, ...options.at };
307
+ if (!options.pin && !options.at) return undefined;
308
+ if (options.pin && !options.at) return options.pin;
309
+ if (!options.pin && options.at) return options.at;
310
+ return { ...options.pin, ...options.at };
309
311
  }
310
312
 
311
313
  /**
@@ -319,7 +321,7 @@ function processIdRefNormalKeys(
319
321
  ): void {
320
322
  const ids = getEffectiveIds(options);
321
323
  if (!ids) return;
322
-
324
+ //TODO
323
325
  for (const key of idRefNormalKeys) {
324
326
  const parsed = parseIdRef(key);
325
327
  if (!parsed) continue;
@@ -331,14 +333,14 @@ function processIdRefNormalKeys(
331
333
  if (parsed.remainingPath) {
332
334
  const resolvedValue = getValues(
333
335
  { __v: value }, options.from,
334
- { withMethods: options.withMethods, aka: options.aka, protocols: options.protocols }
336
+ { withMethods: options.withMethods, aka: options.aka, akaMethods: options.akaMethods, protocols: options.protocols, root: target }
335
337
  );
336
338
  assignGingerly(el, { [parsed.remainingPath]: resolvedValue.__v }, options);
337
339
  } else {
338
340
  const resolvedValue = getValues(
339
341
  typeof value === 'object' && value !== null ? value : { __v: value },
340
342
  options.from,
341
- { withMethods: options.withMethods, aka: options.aka, protocols: options.protocols }
343
+ { withMethods: options.withMethods, aka: options.aka, akaMethods: options.akaMethods, protocols: options.protocols, root: target }
342
344
  );
343
345
  if (!('__v' in resolvedValue)) {
344
346
  assignGingerly(el, resolvedValue, options);
@@ -371,6 +373,8 @@ export function assignFrom(
371
373
  // Categorize keys
372
374
  const { handlerKeys, normalPattern, idRefNormalKeys, idRefHandlerKeys, ternaryKeys } = categorizeKeys(expandedPattern);
373
375
 
376
+ const resolveOptions = { ...options, root: target } as any;
377
+
374
378
  // Process ?= ternary keys (sync)
375
379
  if (ternaryKeys.length > 0) {
376
380
  const ternaryResolved: Record<string, any> = {};
@@ -379,7 +383,7 @@ export function assignFrom(
379
383
  if (!lhsPath) continue;
380
384
  const arr = expandedPattern[key];
381
385
  if (!Array.isArray(arr) || arr.length < 2) continue;
382
- const result = evaluateTernary(arr, options.from, options);
386
+ const result = evaluateTernary(arr, options.from, resolveOptions);
383
387
  if (result !== TERNARY_SKIP) {
384
388
  ternaryResolved[lhsPath] = result;
385
389
  }
@@ -392,7 +396,7 @@ export function assignFrom(
392
396
  // Process normal keys via getValues (sync) + assignGingerly
393
397
  if (Object.keys(normalPattern).length > 0) {
394
398
  // Resolve #[x] references on RHS values before getValues
395
- if (options.withIds || options.at) {
399
+ if (options.pin || options.at) {
396
400
  const ids = getEffectiveIds(options)!;
397
401
  for (const key of Object.keys(normalPattern)) {
398
402
  const value = normalPattern[key];
@@ -407,7 +411,9 @@ export function assignFrom(
407
411
  normalPattern[key] = getValue(remainingPath, el, {
408
412
  withMethods: options.withMethods,
409
413
  aka: options.aka,
410
- protocols: options.protocols
414
+ akaMethods: options.akaMethods,
415
+ protocols: options.protocols,
416
+ root: target
411
417
  });
412
418
  } else {
413
419
  normalPattern[key] = el.id; // bare #[x] → ID string
@@ -418,11 +424,7 @@ export function assignFrom(
418
424
  }
419
425
  }
420
426
 
421
- const resolved = getValues(normalPattern, options.from, {
422
- withMethods: options.withMethods,
423
- aka: options.aka,
424
- protocols: options.protocols
425
- });
427
+ const resolved = getValues(normalPattern, options.from, resolveOptions);
426
428
 
427
429
  handleSpreads(resolved);
428
430
  assignGingerly(target, resolved, options);
@@ -441,7 +443,7 @@ export function assignFrom(
441
443
  }
442
444
 
443
445
  // Process #[x] handler keys — fire-and-forget (async)
444
- if (idRefHandlerKeys.length > 0 && (options.withIds || options.at)) {
446
+ if (idRefHandlerKeys.length > 0 && (options.pin || options.at)) {
445
447
  const ids = getEffectiveIds(options)!;
446
448
  import('./processHandlerCommands.js').then(({ processHandlerCommands }) => {
447
449
  for (const key of idRefHandlerKeys) {
@@ -34,16 +34,19 @@ export async function assignFromAsync(target, pattern, options, permissions) {
34
34
  const resolved = await resolveValues(normalPattern, options.from, {
35
35
  withMethods: options.withMethods,
36
36
  aka: options.aka,
37
- protocols: options.protocols
37
+ akaMethods: options.akaMethods,
38
+ protocols: options.protocols,
39
+ root: target
38
40
  });
39
41
  // Recursively handle "..." spread keys at all nesting levels
40
42
  handleSpreads(resolved);
41
43
  assignGingerly(target, resolved, options);
42
44
  }
43
45
  // Process #[x] normal keys — resolve element, then apply remaining path + value
44
- if (idRefNormalKeys.length > 0 && (options.withIds || options.at)) {
45
- const ids = { ...options.withIds, ...options.at };
46
+ if (idRefNormalKeys.length > 0 && (options.pin || options.at)) {
47
+ const ids = { ...options.pin, ...options.at };
46
48
  const { resolveIdVariable, parseIdRef } = await import('./resolveIdRef.js');
49
+ //TODO
47
50
  for (const key of idRefNormalKeys) {
48
51
  const parsed = parseIdRef(key);
49
52
  if (!parsed)
@@ -54,13 +57,13 @@ export async function assignFromAsync(target, pattern, options, permissions) {
54
57
  const value = expandedPattern[key];
55
58
  if (parsed.remainingPath) {
56
59
  // Resolve the RHS value
57
- const resolvedValue = await resolveValues({ __v: value }, options.from, { withMethods: options.withMethods, aka: options.aka, protocols: options.protocols });
60
+ const resolvedValue = await resolveValues({ __v: value }, options.from, { withMethods: options.withMethods, aka: options.aka, akaMethods: options.akaMethods, protocols: options.protocols, root: el });
58
61
  // Apply remaining path on the resolved element
59
62
  assignGingerly(el, { [parsed.remainingPath]: resolvedValue.__v }, options);
60
63
  }
61
64
  else {
62
65
  // No remaining path — resolve and assign directly to the element
63
- const resolvedValue = await resolveValues(typeof value === 'object' && value !== null ? value : { __v: value }, options.from, { withMethods: options.withMethods, aka: options.aka, protocols: options.protocols });
66
+ const resolvedValue = await resolveValues(typeof value === 'object' && value !== null ? value : { __v: value }, options.from, { withMethods: options.withMethods, aka: options.aka, akaMethods: options.akaMethods, protocols: options.protocols, root: el });
64
67
  if ('__v' in resolvedValue) {
65
68
  // Single value — can't assign to element root without a path
66
69
  }
@@ -76,8 +79,8 @@ export async function assignFromAsync(target, pattern, options, permissions) {
76
79
  await _processHandlerCommands(target, handlerKeys, expandedPattern, options, permissions);
77
80
  }
78
81
  // Process #[x] handler keys — resolve element, then pass to handler processing
79
- if (idRefHandlerKeys.length > 0 && (options.withIds || options.at)) {
80
- const ids = { ...options.withIds, ...options.at };
82
+ if (idRefHandlerKeys.length > 0 && (options.pin || options.at)) {
83
+ const ids = { ...options.pin, ...options.at };
81
84
  const { resolveIdVariable, parseIdRef } = await import('./resolveIdRef.js');
82
85
  _processHandlerCommands ??= (await import('./processHandlerCommands.js')).processHandlerCommands;
83
86
  for (const key of idRefHandlerKeys) {
@@ -22,117 +22,31 @@
22
22
  import { resolveValues } from './resolveValues.js';
23
23
  import assignGingerly, { IAssignGingerlyOptions } from './assignGingerly.js';
24
24
  import type { AssignPermissions } from './isAllowedImportPath.js';
25
+ import type { AssignFromHandler, AssignFromHandlerConstructor } from './types/assign-gingerly/types.js';
25
26
  import {
26
27
  expandSubstitutions, categorizeKeys, handleSpreads, isHandlerCommand
27
28
  } from './assignFrom.js';
28
29
 
29
30
  export interface AssignFromOptions extends IAssignGingerlyOptions {
30
- /** Source object to resolve RHS path strings against */
31
31
  from: any;
32
-
33
- /** Protocol handlers (sync or async) */
34
32
  protocols?: Record<string, (key: string) => any | Promise<any>>;
35
-
36
- /** Loop variable bindings — expand pattern entries containing ${x} */
37
33
  where_x_in?: string[];
38
- /** Loop variable bindings — expand pattern entries containing ${y} */
39
34
  where_y_in?: string[];
40
- /** Loop variable bindings — expand pattern entries containing ${z} */
41
35
  where_z_in?: string[];
42
-
43
- /**
44
- * Cached element references by variable name.
45
- * Used with `#[varName]` syntax in LHS keys for fast repeated element access.
46
- *
47
- * - String value: existing element ID (uses getElementById)
48
- * - Object value: { qry: 'selector' } — finds element via querySelector on target, auto-assigns an ID
49
- *
50
- * Elements are cached via WeakRef with getElementById fallback on cache miss.
51
- */
52
- withIds?: Record<string, string | { qry: string }>;
53
-
54
- /**
55
- * Positional element references for use with `#[varName]` syntax.
56
- * Resolves elements by child index path — no IDs assigned, no caching.
57
- *
58
- * - Array value: child index path (e.g., [0, 1] = target.children[0].children[1])
59
- * - Object value: { path: [...], expect?: 'selector', fallback?: true }
60
- * expect: validates via element.matches(), logs correction if wrong
61
- * fallback: on mismatch, recovers via querySelector(expect)
62
- */
36
+ pin?: Record<string, string | { qry: string } | { path: number[]; expect?: string; fallback?: boolean }>;
63
37
  at?: Record<string, number[] | { path: number[]; expect?: string; fallback?: boolean }>;
64
-
65
- /**
66
- * Handler implementations scoped to this call.
67
- * Key: the `do` name referenced in handler configs.
68
- * Value: a class constructor, or an import path to dynamically load one.
69
- *
70
- * Import paths must be local (relative, absolute, or bare specifier — no cross-domain URLs).
71
- * The module's default export is checked first; otherwise the first exported class
72
- * with an `assign` method on its prototype is used.
73
- *
74
- * Built-in handlers (builtIns.*) auto-load without needing to be listed here.
75
- *
76
- * @example
77
- * handlers: {
78
- * 'my-list': MyListHandler, // class constructor
79
- * 'my-chart': './handlers/chart.js', // dynamic import path
80
- * 'vendor-widget': 'some-package/handler.js', // bare specifier (import map)
81
- * }
82
- */
83
38
  handlers?: Record<string, AssignFromHandlerConstructor | string>;
84
-
85
- /**
86
- * Inferred assignments — automatically distribute source values to matching
87
- * DOM elements based on structural conventions (itemprop, name, etc.).
88
- *
89
- * Uses the inferencer submodule to determine the correct property for each
90
- * matched element (textContent, value, checked, dateTime, ish, etc.).
91
- *
92
- * @example
93
- * infer: {
94
- * byItemprop: ['user', 'name', 'email'], // or true for all source keys
95
- * beVigilant: true, // watch for new matching elements (requires signal)
96
- * }
97
- */
98
39
  infer?: {
99
40
  byItemprop?: string[] | true;
100
41
  '|'?: string[] | true;
101
42
  byName?: string[] | true | { props: string[] | true; outside: string };
102
43
  '@'?: string[] | true | { props: string[] | true; outside: string };
103
- /** Watch for new matching elements via MutationObserver. Requires options.signal for cleanup. */
104
44
  beVigilant?: boolean;
105
45
  };
106
-
107
- /**
108
- * Bulk enhancement application via EMC JSON configs.
109
- * Finds matching elements and spawns enhancements on them.
110
- *
111
- * Each entry specifies an EMC JSON path and optionally overrides the matching selector.
112
- * Enhancements are auto-registered if not already present in the enhancement registry.
113
- *
114
- * No scope perimeter is applied — use mount-observer for reactive/scoped enhancement.
115
- *
116
- * @example
117
- * enhance: [
118
- * { emc: 'be-bound/emc.json', matching: '[name]' },
119
- * { emc: 'be-observant/emc.json', matching: '[itemprop]' },
120
- * ]
121
- */
122
46
  enhance?: Array<{ emc: string; matching?: string; parse?: boolean }>;
123
47
  }
124
48
 
125
- /**
126
- * Interface for assignFrom handler classes.
127
- * Handlers are invoked when a LHS key ends with ' =>'.
128
- */
129
- export interface AssignFromHandler {
130
- assign(lhsTarget: any, resolvedParams: Record<string, any>, options: AssignFromOptions): Promise<void> | void;
131
- }
132
-
133
- export interface AssignFromHandlerConstructor {
134
- new (config: any): AssignFromHandler;
135
- }
49
+ export type { AssignFromHandler, AssignFromHandlerConstructor };
136
50
 
137
51
  // Module cache for processHandlerCommands — avoids await on dynamic import after first call
138
52
  let _processHandlerCommands: any;
@@ -154,7 +68,9 @@ export async function assignFromAsync(
154
68
  const resolved = await resolveValues(normalPattern, options.from, {
155
69
  withMethods: options.withMethods,
156
70
  aka: options.aka,
157
- protocols: options.protocols
71
+ akaMethods: options.akaMethods,
72
+ protocols: options.protocols,
73
+ root: target
158
74
  });
159
75
 
160
76
  // Recursively handle "..." spread keys at all nesting levels
@@ -164,9 +80,10 @@ export async function assignFromAsync(
164
80
  }
165
81
 
166
82
  // Process #[x] normal keys — resolve element, then apply remaining path + value
167
- if (idRefNormalKeys.length > 0 && (options.withIds || options.at)) {
168
- const ids = { ...options.withIds, ...options.at };
83
+ if (idRefNormalKeys.length > 0 && (options.pin || options.at)) {
84
+ const ids = { ...options.pin, ...options.at };
169
85
  const { resolveIdVariable, parseIdRef } = await import('./resolveIdRef.js');
86
+ //TODO
170
87
  for (const key of idRefNormalKeys) {
171
88
  const parsed = parseIdRef(key);
172
89
  if (!parsed) continue;
@@ -179,7 +96,7 @@ export async function assignFromAsync(
179
96
  // Resolve the RHS value
180
97
  const resolvedValue = await resolveValues(
181
98
  { __v: value }, options.from,
182
- { withMethods: options.withMethods, aka: options.aka, protocols: options.protocols }
99
+ { withMethods: options.withMethods, aka: options.aka, akaMethods: options.akaMethods, protocols: options.protocols, root: el }
183
100
  );
184
101
  // Apply remaining path on the resolved element
185
102
  assignGingerly(el, { [parsed.remainingPath]: resolvedValue.__v }, options);
@@ -188,7 +105,7 @@ export async function assignFromAsync(
188
105
  const resolvedValue = await resolveValues(
189
106
  typeof value === 'object' && value !== null ? value : { __v: value },
190
107
  options.from,
191
- { withMethods: options.withMethods, aka: options.aka, protocols: options.protocols }
108
+ { withMethods: options.withMethods, aka: options.aka, akaMethods: options.akaMethods, protocols: options.protocols, root: el }
192
109
  );
193
110
  if ('__v' in resolvedValue) {
194
111
  // Single value — can't assign to element root without a path
@@ -205,8 +122,8 @@ export async function assignFromAsync(
205
122
  await _processHandlerCommands(target, handlerKeys, expandedPattern, options, permissions);
206
123
  }
207
124
  // Process #[x] handler keys — resolve element, then pass to handler processing
208
- if (idRefHandlerKeys.length > 0 && (options.withIds || options.at)) {
209
- const ids = { ...options.withIds, ...options.at };
125
+ if (idRefHandlerKeys.length > 0 && (options.pin || options.at)) {
126
+ const ids = { ...options.pin, ...options.at };
210
127
  const { resolveIdVariable, parseIdRef } = await import('./resolveIdRef.js');
211
128
  _processHandlerCommands ??= (await import('./processHandlerCommands.js')).processHandlerCommands;
212
129