assign-gingerly 0.0.64 → 0.0.66
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 +85 -26
- package/assignFrom.js +21 -19
- package/assignFrom.ts +28 -25
- package/assignFromAsync.js +10 -7
- package/assignFromAsync.ts +15 -98
- package/assignGingerly.js +54 -26
- package/assignGingerly.ts +62 -27
- package/assignTentatively.js +8 -0
- package/assignTentatively.ts +6 -0
- package/{builtInEmoji.ts → emojis.js} +46 -34
- package/emojis.ts +52 -0
- package/getValues.js +80 -25
- package/getValues.ts +85 -26
- package/handlers/addEventListener.js +153 -0
- package/handlers/addEventListener.ts +178 -0
- package/handlers/arr.js +13 -0
- package/handlers/arr.ts +13 -0
- package/handlers/lazyLoad.js +3 -3
- package/handlers/lazyLoad.ts +3 -3
- package/handlers/manageTemplateList.js +48 -21
- package/handlers/manageTemplateList.ts +51 -21
- package/handlers/nudge.js +23 -0
- package/handlers/nudge.ts +23 -0
- package/index.js +2 -0
- package/index.ts +2 -0
- package/inferencer/types/assign-gingerly/types.d.ts +327 -1
- package/inferencer/types/mount-observer/types.d.ts +10 -2
- package/inferencer/types/roundabout/types.d.ts +1 -1
- package/inferencer/types/three-peat/types.d.ts +18 -0
- package/object-extension.js +1 -1
- package/package.json +12 -4
- package/paths.ts +1 -1
- package/{withIdsCorrector.js → pinCorrector.js} +5 -5
- package/{withIdsCorrector.ts → pinCorrector.ts} +5 -5
- package/processHandlerCommands.js +4 -2
- package/processHandlerCommands.ts +4 -2
- package/resolveIdRef.js +8 -8
- package/resolveIdRef.ts +9 -9
- package/resolveValues.js +20 -88
- package/resolveValues.ts +17 -91
- package/types/assign-gingerly/types.d.ts +159 -3
- package/builtInEmoji.js +0 -26
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.
|
|
98
|
-
3.
|
|
99
|
-
4.
|
|
100
|
-
5.
|
|
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
|
-
'
|
|
480
|
-
'
|
|
493
|
+
'?.🔍?.my-element?.🎨?.+': 'highlighted',
|
|
494
|
+
'?.🔍?.your-element?.🎨?.+': 'active'
|
|
481
495
|
}, {
|
|
482
496
|
withMethods: ['querySelector', 'add'],
|
|
483
|
-
aka: { '
|
|
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.
|
|
@@ -775,7 +816,7 @@ While we are in the business of passing values of object A into object B, we mig
|
|
|
775
816
|
|
|
776
817
|
| Operator | Name | Description | Example |
|
|
777
818
|
|----------|------|-------------|---------|
|
|
778
|
-
| ` +=` | Increment | Add to numeric
|
|
819
|
+
| ` +=` | Increment | Add to numeric, concat strings, append to arrays, or [bind events](docs/event-binding.md) | `'count +=': 5` |
|
|
779
820
|
| ` =!` | Toggle | Negate a boolean (or any value via `!`) | `'visible =!': '.'` |
|
|
780
821
|
| ` -=` | Delete | Remove properties from an object | `'?.data -=': 'key'` |
|
|
781
822
|
| ` Y=` | Merge | Recursively `assignGingerly` into a sub-object | `'style Y=': { width: '100px' }` |
|
|
@@ -816,6 +857,8 @@ The `+=` command syntax is `<path> +=` where the path uses the `?.` nested notat
|
|
|
816
857
|
| LHS type | RHS type | Result |
|
|
817
858
|
|----------|----------|--------|
|
|
818
859
|
| number | number | addition (`2 += 3` → `5`) |
|
|
860
|
+
| number | string (numeric) | parse + addition (`5 += '3'` → `8`) |
|
|
861
|
+
| number | string (non-numeric) | string concatenation (`5 += 'px'` → `'5px'`) |
|
|
819
862
|
| string | any | string concatenation (`"hello" += 3` → `"hello3"`) |
|
|
820
863
|
| array | array | array concatenation (`[1,2] += [3,4]` → `[1,2,3,4]`) |
|
|
821
864
|
| array | non-array | push single item (`[1,2] += 3` → `[1,2,3]`) |
|
|
@@ -835,6 +878,22 @@ assignGingerly(obj, {
|
|
|
835
878
|
assignGingerly(obj, { '?.tags +=': 'e' }); // ['a', 'b', 'c', 'd', 'e']
|
|
836
879
|
```
|
|
837
880
|
|
|
881
|
+
**Event binding with `+=`:**
|
|
882
|
+
|
|
883
|
+
When the LHS resolves to a DOM Element and the RHS is an object with an `on` property, `+=` attaches a declarative event listener:
|
|
884
|
+
|
|
885
|
+
```JavaScript
|
|
886
|
+
assignFrom(this.shadowRoot, {
|
|
887
|
+
'?.querySelector?.button +=': {
|
|
888
|
+
on: 'click',
|
|
889
|
+
'?.isHappy =!': '.', // toggle host property
|
|
890
|
+
fromLHS: { '?.age +=': '?.dataset.diff' } // read from button, assign to host
|
|
891
|
+
}
|
|
892
|
+
}, { from: this, withMethods: ['querySelector'] });
|
|
893
|
+
```
|
|
894
|
+
|
|
895
|
+
The handler is lazy-loaded on demand. For full details including assignment vectors, dedup, nudge, and custom event dispatch, see [docs/event-binding.md](docs/event-binding.md).
|
|
896
|
+
|
|
838
897
|
## Example 5 - Toggling boolean values and negating
|
|
839
898
|
|
|
840
899
|
The `=!` command allows us to toggle boolean values:
|
|
@@ -3859,7 +3918,7 @@ await assignFromAsync(container, {
|
|
|
3859
3918
|
if: '?.showPanel',
|
|
3860
3919
|
instantiate: 'globalThis://panelTemplate',
|
|
3861
3920
|
assign: {
|
|
3862
|
-
|
|
3921
|
+
toClone: {
|
|
3863
3922
|
'#[title]?.textContent': '?.panelTitle',
|
|
3864
3923
|
'#[body]?.textContent': '?.panelContent'
|
|
3865
3924
|
},
|
|
@@ -3872,7 +3931,7 @@ await assignFromAsync(container, {
|
|
|
3872
3931
|
}, { from: vm, withMethods: ['querySelector'], protocols: { globalThis: k => globalThis[k] } });
|
|
3873
3932
|
```
|
|
3874
3933
|
|
|
3875
|
-
The `assign.
|
|
3934
|
+
The `assign.toClone` paths resolve against `options.from` (the same source that drives the `if` condition). For multi-element templates, use `assign.configs` (same zip semantics as `manageTemplateList`).
|
|
3876
3935
|
|
|
3877
3936
|
### View Transitions
|
|
3878
3937
|
|
|
@@ -4189,7 +4248,7 @@ assignFrom(document.body, {
|
|
|
4189
4248
|
instantiate: 'globalThis://country-ranking',
|
|
4190
4249
|
},
|
|
4191
4250
|
fromEachItem: {
|
|
4192
|
-
|
|
4251
|
+
toClone: { '?.querySelector?.tr?.ish': '?.' },
|
|
4193
4252
|
withOptions: { withMethods: ['querySelector'], infer: true },
|
|
4194
4253
|
get: { key: '?.rank' }
|
|
4195
4254
|
}
|
|
@@ -4205,7 +4264,7 @@ assignFrom(document.body, {
|
|
|
4205
4264
|
|
|
4206
4265
|
1. Resolves `forEach` (iterable) and `instantiate` (template) from the `resolve` block
|
|
4207
4266
|
2. Clones the template once per item, buffering all clones into a `DocumentFragment`
|
|
4208
|
-
3. For each clone, calls `assignFrom(clone,
|
|
4267
|
+
3. For each clone, calls `assignFrom(clone, toClone, { from: item, ...withOptions })` — distributing the item's data
|
|
4209
4268
|
4. Inserts the fragment between comment markers in one DOM operation
|
|
4210
4269
|
5. On subsequent calls, reconciles by `key` — adds new items, removes missing ones, updates existing clones in place
|
|
4211
4270
|
|
|
@@ -4218,13 +4277,13 @@ The `key` field (in `fromEachItem.get`) identifies each item for stable identity
|
|
|
4218
4277
|
|
|
4219
4278
|
Without `key`, positional matching is used (item[i] → clone[i]).
|
|
4220
4279
|
|
|
4221
|
-
**Shared parent data (`
|
|
4280
|
+
**Shared parent data (`fromHost`):**
|
|
4222
4281
|
|
|
4223
4282
|
Pass data from the outer VM into each clone (e.g., aggregate totals):
|
|
4224
4283
|
|
|
4225
4284
|
```JavaScript
|
|
4226
|
-
|
|
4227
|
-
|
|
4285
|
+
fromHost: {
|
|
4286
|
+
toClone: {
|
|
4228
4287
|
'?.querySelector?.[part~="totalMedalCount"]?.textContent': '?.totalMedalCount'
|
|
4229
4288
|
},
|
|
4230
4289
|
withOptions: { withMethods: ['querySelector'] }
|
|
@@ -4387,7 +4446,7 @@ md`${$.firstName} ${{ prop: 'birthDate', val: $.birthDT, format: 'long' }}`
|
|
|
4387
4446
|
|
|
4388
4447
|
Both auto-detect path proxies (no `.path` needed inside template literals) and preserve nested arrays for optional segments.
|
|
4389
4448
|
|
|
4390
|
-
## Cached Element Resolution with `#[x]` and `
|
|
4449
|
+
## Cached Element Resolution with `#[x]` and `pin`
|
|
4391
4450
|
|
|
4392
4451
|
`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
4452
|
|
|
@@ -4402,7 +4461,7 @@ await assignFromAsync(document.body, {
|
|
|
4402
4461
|
}
|
|
4403
4462
|
}, {
|
|
4404
4463
|
from: viewModel,
|
|
4405
|
-
|
|
4464
|
+
pin: {
|
|
4406
4465
|
main: { qry: '.mainView' } // find by class, auto-assign ID, cache
|
|
4407
4466
|
}
|
|
4408
4467
|
});
|
|
@@ -4416,12 +4475,12 @@ await assignFromAsync(document.body, {
|
|
|
4416
4475
|
4. On subsequent calls, the cached `WeakRef.deref()` returns the element in ~10ns.
|
|
4417
4476
|
5. If the WeakRef is collected (element was GC'd), falls back to `getElementById` (~10-100ns).
|
|
4418
4477
|
|
|
4419
|
-
**`
|
|
4478
|
+
**`pin` — stable references with auto-assigned IDs:**
|
|
4420
4479
|
|
|
4421
4480
|
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
4481
|
|
|
4423
4482
|
```TypeScript
|
|
4424
|
-
|
|
4483
|
+
pin: {
|
|
4425
4484
|
x: { qry: '.mainView' }, // querySelector on target, auto-assign ID
|
|
4426
4485
|
y: 'existingId', // element already has an ID, cache via WeakRef
|
|
4427
4486
|
z: { path: [0, 1], expect: 'input', fallback: true }, // child index path + auto-ID + validation
|
|
@@ -4451,14 +4510,14 @@ at: {
|
|
|
4451
4510
|
- You want a clean DOM (no auto-generated `id` attributes)
|
|
4452
4511
|
- You're rendering many items (1,000 rows × 2 refs = 2,000 fewer DOM attributes)
|
|
4453
4512
|
|
|
4454
|
-
Both `
|
|
4513
|
+
Both `pin` and `at` use the same `#[x]` syntax on LHS and RHS keys.
|
|
4455
4514
|
|
|
4456
4515
|
**Validated paths with `expect` and `fallback`:**
|
|
4457
4516
|
|
|
4458
|
-
Both `
|
|
4517
|
+
Both `pin` and `at` support the `{ path, expect, fallback }` form for dev-time validation:
|
|
4459
4518
|
|
|
4460
4519
|
```TypeScript
|
|
4461
|
-
|
|
4520
|
+
pin: {
|
|
4462
4521
|
nameInput: { path: [1], expect: 'input' }, // warn if [1] isn't an <input>
|
|
4463
4522
|
label: { path: [0], expect: 'label', fallback: true } // warn + recover via querySelector
|
|
4464
4523
|
}
|
|
@@ -4481,7 +4540,7 @@ assignFrom(document.body, {
|
|
|
4481
4540
|
}, {
|
|
4482
4541
|
from: viewModel,
|
|
4483
4542
|
withMethods: ['querySelector'],
|
|
4484
|
-
|
|
4543
|
+
pin: {
|
|
4485
4544
|
form: { qry: '.registration-form' },
|
|
4486
4545
|
header: 'page-header'
|
|
4487
4546
|
}
|
|
@@ -4498,7 +4557,7 @@ await assignFromAsync(container, {
|
|
|
4498
4557
|
}
|
|
4499
4558
|
}, {
|
|
4500
4559
|
from: router,
|
|
4501
|
-
|
|
4560
|
+
pin: { outlet: { qry: '.router-outlet' } },
|
|
4502
4561
|
protocols: { globalThis: k => globalThis[k] }
|
|
4503
4562
|
});
|
|
4504
4563
|
```
|
|
@@ -4511,7 +4570,7 @@ await assignFromAsync(container, {
|
|
|
4511
4570
|
| `getElementById(id)` | 100ns | 20ns | 10ns |
|
|
4512
4571
|
| `Map<id, WeakRef>.deref()` | 12ns | 40ns | 10ns |
|
|
4513
4572
|
|
|
4514
|
-
The `#[x]` + `
|
|
4573
|
+
The `#[x]` + `pin` pattern gives you the Map+WeakRef speed tier automatically.
|
|
4515
4574
|
|
|
4516
4575
|
**RHS references (`#[x]` on the value side):**
|
|
4517
4576
|
|
|
@@ -4524,7 +4583,7 @@ assignFrom(form, {
|
|
|
4524
4583
|
'?.headerText': '#[info]?.dataset?.user', // → nested property access
|
|
4525
4584
|
}, {
|
|
4526
4585
|
from: {},
|
|
4527
|
-
|
|
4586
|
+
pin: { nameInput: { qry: 'input' }, info: { qry: '.info' } },
|
|
4528
4587
|
withMethods: ['querySelector']
|
|
4529
4588
|
});
|
|
4530
4589
|
```
|
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
|
|
292
|
+
* Merge pin and at into a single lookup map for resolveIdVariable.
|
|
291
293
|
*/
|
|
292
294
|
function getEffectiveIds(options) {
|
|
293
|
-
if (!options.
|
|
295
|
+
if (!options.pin && !options.at)
|
|
294
296
|
return undefined;
|
|
295
|
-
if (options.
|
|
296
|
-
return options.
|
|
297
|
-
if (!options.
|
|
297
|
+
if (options.pin && !options.at)
|
|
298
|
+
return options.pin;
|
|
299
|
+
if (!options.pin && options.at)
|
|
298
300
|
return options.at;
|
|
299
|
-
return { ...options.
|
|
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
|
+
const { withMethods, aka, akaMethods, protocols, from } = options;
|
|
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 },
|
|
320
|
+
const resolvedValue = getValues({ __v: value }, from, { withMethods, aka, akaMethods, 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 },
|
|
324
|
+
const resolvedValue = getValues(typeof value === 'object' && value !== null ? value : { __v: value }, from, { withMethods, aka, akaMethods, 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,
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
|
19
|
-
export type { AssignFromOptions, AssignFromHandler, AssignFromHandlerConstructor }
|
|
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
|
|
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.
|
|
306
|
-
if (options.
|
|
307
|
-
if (!options.
|
|
308
|
-
return { ...options.
|
|
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
|
/**
|
|
@@ -320,6 +322,7 @@ function processIdRefNormalKeys(
|
|
|
320
322
|
const ids = getEffectiveIds(options);
|
|
321
323
|
if (!ids) return;
|
|
322
324
|
|
|
325
|
+
const { withMethods, aka, akaMethods, protocols, from } = options;
|
|
323
326
|
for (const key of idRefNormalKeys) {
|
|
324
327
|
const parsed = parseIdRef(key);
|
|
325
328
|
if (!parsed) continue;
|
|
@@ -330,15 +333,15 @@ function processIdRefNormalKeys(
|
|
|
330
333
|
const value = expandedPattern[key];
|
|
331
334
|
if (parsed.remainingPath) {
|
|
332
335
|
const resolvedValue = getValues(
|
|
333
|
-
{ __v: value },
|
|
334
|
-
{ withMethods
|
|
336
|
+
{ __v: value }, from,
|
|
337
|
+
{ withMethods, aka, akaMethods, protocols, root: target }
|
|
335
338
|
);
|
|
336
339
|
assignGingerly(el, { [parsed.remainingPath]: resolvedValue.__v }, options);
|
|
337
340
|
} else {
|
|
338
341
|
const resolvedValue = getValues(
|
|
339
342
|
typeof value === 'object' && value !== null ? value : { __v: value },
|
|
340
|
-
|
|
341
|
-
{ withMethods
|
|
343
|
+
from,
|
|
344
|
+
{ withMethods, aka, akaMethods, protocols, root: target }
|
|
342
345
|
);
|
|
343
346
|
if (!('__v' in resolvedValue)) {
|
|
344
347
|
assignGingerly(el, resolvedValue, options);
|
|
@@ -371,6 +374,8 @@ export function assignFrom(
|
|
|
371
374
|
// Categorize keys
|
|
372
375
|
const { handlerKeys, normalPattern, idRefNormalKeys, idRefHandlerKeys, ternaryKeys } = categorizeKeys(expandedPattern);
|
|
373
376
|
|
|
377
|
+
const resolveOptions = { ...options, root: target } as any;
|
|
378
|
+
|
|
374
379
|
// Process ?= ternary keys (sync)
|
|
375
380
|
if (ternaryKeys.length > 0) {
|
|
376
381
|
const ternaryResolved: Record<string, any> = {};
|
|
@@ -379,7 +384,7 @@ export function assignFrom(
|
|
|
379
384
|
if (!lhsPath) continue;
|
|
380
385
|
const arr = expandedPattern[key];
|
|
381
386
|
if (!Array.isArray(arr) || arr.length < 2) continue;
|
|
382
|
-
const result = evaluateTernary(arr, options.from,
|
|
387
|
+
const result = evaluateTernary(arr, options.from, resolveOptions);
|
|
383
388
|
if (result !== TERNARY_SKIP) {
|
|
384
389
|
ternaryResolved[lhsPath] = result;
|
|
385
390
|
}
|
|
@@ -392,7 +397,7 @@ export function assignFrom(
|
|
|
392
397
|
// Process normal keys via getValues (sync) + assignGingerly
|
|
393
398
|
if (Object.keys(normalPattern).length > 0) {
|
|
394
399
|
// Resolve #[x] references on RHS values before getValues
|
|
395
|
-
if (options.
|
|
400
|
+
if (options.pin || options.at) {
|
|
396
401
|
const ids = getEffectiveIds(options)!;
|
|
397
402
|
for (const key of Object.keys(normalPattern)) {
|
|
398
403
|
const value = normalPattern[key];
|
|
@@ -407,7 +412,9 @@ export function assignFrom(
|
|
|
407
412
|
normalPattern[key] = getValue(remainingPath, el, {
|
|
408
413
|
withMethods: options.withMethods,
|
|
409
414
|
aka: options.aka,
|
|
410
|
-
|
|
415
|
+
akaMethods: options.akaMethods,
|
|
416
|
+
protocols: options.protocols,
|
|
417
|
+
root: target
|
|
411
418
|
});
|
|
412
419
|
} else {
|
|
413
420
|
normalPattern[key] = el.id; // bare #[x] → ID string
|
|
@@ -418,11 +425,7 @@ export function assignFrom(
|
|
|
418
425
|
}
|
|
419
426
|
}
|
|
420
427
|
|
|
421
|
-
const resolved = getValues(normalPattern, options.from,
|
|
422
|
-
withMethods: options.withMethods,
|
|
423
|
-
aka: options.aka,
|
|
424
|
-
protocols: options.protocols
|
|
425
|
-
});
|
|
428
|
+
const resolved = getValues(normalPattern, options.from, resolveOptions);
|
|
426
429
|
|
|
427
430
|
handleSpreads(resolved);
|
|
428
431
|
assignGingerly(target, resolved, options);
|
|
@@ -441,7 +444,7 @@ export function assignFrom(
|
|
|
441
444
|
}
|
|
442
445
|
|
|
443
446
|
// Process #[x] handler keys — fire-and-forget (async)
|
|
444
|
-
if (idRefHandlerKeys.length > 0 && (options.
|
|
447
|
+
if (idRefHandlerKeys.length > 0 && (options.pin || options.at)) {
|
|
445
448
|
const ids = getEffectiveIds(options)!;
|
|
446
449
|
import('./processHandlerCommands.js').then(({ processHandlerCommands }) => {
|
|
447
450
|
for (const key of idRefHandlerKeys) {
|
package/assignFromAsync.js
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
45
|
-
const ids = { ...options.
|
|
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
|
+
const { withMethods, aka, akaMethods, protocols, from } = options;
|
|
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 },
|
|
60
|
+
const resolvedValue = await resolveValues({ __v: value }, from, { withMethods, aka, akaMethods, 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 },
|
|
66
|
+
const resolvedValue = await resolveValues(typeof value === 'object' && value !== null ? value : { __v: value }, from, { withMethods, aka, akaMethods, 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.
|
|
80
|
-
const ids = { ...options.
|
|
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) {
|