assign-gingerly 0.0.58 → 0.0.60
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 +511 -142
- package/assignFrom-extension.js +25 -0
- package/assignFrom-extension.ts +53 -0
- package/assignFrom.js +306 -133
- package/assignFrom.ts +318 -232
- package/assignFromAsync-extension.js +28 -0
- package/assignFromAsync-extension.ts +58 -0
- package/assignFromAsync.js +120 -0
- package/assignFromAsync.ts +256 -0
- package/assignGingerly.js +50 -0
- package/assignGingerly.ts +51 -0
- package/builtInEmoji.js +25 -0
- package/builtInEmoji.ts +33 -0
- package/getValues.js +223 -0
- package/getValues.ts +255 -0
- package/handlers/join.ts +1 -1
- package/handlers/lazyLoad.js +33 -2
- package/handlers/lazyLoad.ts +47 -2
- package/handlers/lazyLoadSwitch.ts +1 -1
- package/handlers/manageTemplateList.js +226 -0
- package/handlers/manageTemplateList.ts +263 -0
- package/handlers/microDataJoin.ts +1 -1
- package/index.js +1 -0
- package/index.ts +2 -0
- package/inferencer/inferencer.js +9 -21
- package/inferencer/inferencer.ts +10 -21
- package/inferredAssignments.js +35 -5
- package/inferredAssignments.ts +58 -8
- package/markerUtils.js +136 -127
- package/package.json +30 -1
- package/playwright.config.ts +3 -2
- package/processHandlerCommands.js +36 -4
- package/processHandlerCommands.ts +38 -8
- package/resolveIdRef.js +70 -19
- package/resolveIdRef.ts +73 -21
- package/resolveValues.js +41 -125
- package/resolveValues.ts +131 -255
- package/types/assign-gingerly/types.d.ts +61 -0
- package/waitForSettled.js +57 -0
- package/waitForSettled.ts +65 -0
- package/withIdsCorrector.js +47 -0
- package/withIdsCorrector.ts +59 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* assignFrom-extension.js — Adds assignFrom to Object.prototype.
|
|
3
|
+
*
|
|
4
|
+
* Import this module for the side effect of extending all objects with
|
|
5
|
+
* the assignFrom method, enabling fluent method chaining:
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* import 'assign-gingerly/assignFrom-extension.js';
|
|
9
|
+
*
|
|
10
|
+
* oElement
|
|
11
|
+
* .assignFrom({ '?.textContent': '?.greeting' }, { from: vm1 })
|
|
12
|
+
* .assignFrom({ '?.style Y=': { color: '?.themeColor' } }, { from: vm2 });
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { assignFrom } from './assignFrom.js';
|
|
16
|
+
|
|
17
|
+
Object.defineProperty(Object.prototype, 'assignFrom', {
|
|
18
|
+
value: function (pattern, options) {
|
|
19
|
+
assignFrom(this, pattern, options);
|
|
20
|
+
return this;
|
|
21
|
+
},
|
|
22
|
+
writable: true,
|
|
23
|
+
enumerable: false,
|
|
24
|
+
configurable: true,
|
|
25
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* assignFrom-extension.ts — Adds assignFrom to Object.prototype.
|
|
3
|
+
*
|
|
4
|
+
* Import this module for the side effect of extending all objects with
|
|
5
|
+
* the assignFrom method, enabling fluent method chaining:
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* import 'assign-gingerly/assignFrom-extension.js';
|
|
9
|
+
*
|
|
10
|
+
* oElement
|
|
11
|
+
* .assignFrom({ '?.textContent': '?.greeting' }, { from: vm1 })
|
|
12
|
+
* .assignFrom({ '?.style Y=': { color: '?.themeColor' } }, { from: vm2 });
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { assignFrom } from './assignFrom.js';
|
|
16
|
+
import type { AssignFromOptions } from './assignFromAsync.js';
|
|
17
|
+
|
|
18
|
+
declare global {
|
|
19
|
+
interface Object {
|
|
20
|
+
/**
|
|
21
|
+
* Resolve RHS path strings from a source object and assign into this object.
|
|
22
|
+
* Synchronous — handlers are fire-and-forget.
|
|
23
|
+
*
|
|
24
|
+
* @param pattern - Object with LHS paths as keys and RHS path strings (or literals) as values
|
|
25
|
+
* @param options - Configuration including `from` (source object), protocols, withMethods, etc.
|
|
26
|
+
* @returns This object after assignment
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* oElement.assignFrom({
|
|
30
|
+
* '?.textContent': '?.greeting',
|
|
31
|
+
* '?.style Y=': { width: '?.width' }
|
|
32
|
+
* }, { from: viewModel });
|
|
33
|
+
*/
|
|
34
|
+
assignFrom(
|
|
35
|
+
pattern: Record<string, any>,
|
|
36
|
+
options: AssignFromOptions
|
|
37
|
+
): this;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
Object.defineProperty(Object.prototype, 'assignFrom', {
|
|
42
|
+
value: function <T extends object>(
|
|
43
|
+
this: T,
|
|
44
|
+
pattern: Record<string, any>,
|
|
45
|
+
options: AssignFromOptions
|
|
46
|
+
): T {
|
|
47
|
+
assignFrom(this, pattern, options);
|
|
48
|
+
return this;
|
|
49
|
+
},
|
|
50
|
+
writable: true,
|
|
51
|
+
enumerable: false,
|
|
52
|
+
configurable: true,
|
|
53
|
+
});
|
package/assignFrom.js
CHANGED
|
@@ -1,45 +1,149 @@
|
|
|
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
|
-
import {
|
|
11
|
+
import { getValues, getValue } from './getValues.js';
|
|
23
12
|
import assignGingerly from './assignGingerly.js';
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
*/
|
|
27
|
-
function isHandlerCommand(key) {
|
|
28
|
-
return key.endsWith(' =>');
|
|
29
|
-
}
|
|
13
|
+
import { resolveIdVariable, parseIdRef } from './resolveIdRef.js';
|
|
14
|
+
import { processInferredAssignments } from './inferredAssignments.js';
|
|
30
15
|
/**
|
|
31
16
|
* Supported substitution variables and their option keys.
|
|
32
17
|
*/
|
|
33
|
-
const SUBSTITUTION_VARS = [
|
|
18
|
+
export const SUBSTITUTION_VARS = [
|
|
34
19
|
{ placeholder: '${x}', optionKey: 'where_x_in' },
|
|
35
20
|
{ placeholder: '${y}', optionKey: 'where_y_in' },
|
|
36
21
|
{ placeholder: '${z}', optionKey: 'where_z_in' },
|
|
37
22
|
];
|
|
23
|
+
/**
|
|
24
|
+
* Check if a key ends with the handler operator ' =>'.
|
|
25
|
+
*/
|
|
26
|
+
export function isHandlerCommand(key) {
|
|
27
|
+
return key.endsWith(' =>');
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Check if a key ends with the ternary operator ' ?='.
|
|
31
|
+
*/
|
|
32
|
+
export function isTernaryCommand(key) {
|
|
33
|
+
return key.endsWith(' ?=');
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Parse a ?= ternary command and extract the LHS path.
|
|
37
|
+
*/
|
|
38
|
+
export function parseTernaryCommand(key) {
|
|
39
|
+
if (!isTernaryCommand(key))
|
|
40
|
+
return null;
|
|
41
|
+
return key.substring(0, key.length - 3); // Remove ' ?=' suffix
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Resolve a single value — if it's a `?.` path string, resolve against source.
|
|
45
|
+
* If it's a protocol string, resolve via protocol. Otherwise pass through as literal.
|
|
46
|
+
*/
|
|
47
|
+
function resolveTernaryValue(value, source, options) {
|
|
48
|
+
if (typeof value === 'string' && value.startsWith('?.')) {
|
|
49
|
+
return getValue(value, source, {
|
|
50
|
+
withMethods: options.withMethods,
|
|
51
|
+
aka: options.aka,
|
|
52
|
+
protocols: options.protocols
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
if (typeof value === 'string' && value.includes('://') && options.protocols) {
|
|
56
|
+
// Check if it matches a known protocol
|
|
57
|
+
const protoEnd = value.indexOf('://');
|
|
58
|
+
const protocol = value.substring(0, protoEnd);
|
|
59
|
+
if (options.protocols[protocol]) {
|
|
60
|
+
return getValue(value, source, {
|
|
61
|
+
withMethods: options.withMethods,
|
|
62
|
+
aka: options.aka,
|
|
63
|
+
protocols: options.protocols
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return value;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Evaluate a ?= ternary expression.
|
|
71
|
+
*
|
|
72
|
+
* Supported forms:
|
|
73
|
+
* - [ifTruthy, thenResult] — guard (skip if falsy)
|
|
74
|
+
* - [ifTruthy, thenResult, elseResult] — ternary
|
|
75
|
+
* - [ifTrue, trueResult, falseResult, neither] — three-state (true/false/nullish)
|
|
76
|
+
* - [[lhs, rhs], ifEqual, ifNotEqual?] — equality comparison
|
|
77
|
+
* - [[lhs, rhs], ifEqual] — equality guard
|
|
78
|
+
*
|
|
79
|
+
* Returns undefined to signal "skip assignment" (guard forms when condition not met).
|
|
80
|
+
*/
|
|
81
|
+
const TERNARY_SKIP = Symbol('ternary-skip');
|
|
82
|
+
function evaluateTernary(arr, source, options) {
|
|
83
|
+
const condition = arr[0];
|
|
84
|
+
if (Array.isArray(condition)) {
|
|
85
|
+
// Comparison mode: [[lhs, rhs], ...] or [[lhs, op, rhs], ...]
|
|
86
|
+
const lhs = resolveTernaryValue(condition[0], source, options);
|
|
87
|
+
if (condition.length === 2) {
|
|
88
|
+
// Equality: [[lhs, rhs], result, elseResult?]
|
|
89
|
+
const rhs = resolveTernaryValue(condition[1], source, options);
|
|
90
|
+
if (lhs === rhs) {
|
|
91
|
+
return resolveTernaryValue(arr[1], source, options);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
return arr.length > 2 ? resolveTernaryValue(arr[2], source, options) : TERNARY_SKIP;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
// Operator: [[lhs, op, rhs], result, elseResult?]
|
|
99
|
+
const op = condition[1];
|
|
100
|
+
const rhs = resolveTernaryValue(condition[2], source, options);
|
|
101
|
+
const satisfied = compareWithOp(lhs, op, rhs);
|
|
102
|
+
if (satisfied) {
|
|
103
|
+
return resolveTernaryValue(arr[1], source, options);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
return arr.length > 2 ? resolveTernaryValue(arr[2], source, options) : TERNARY_SKIP;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
// Truthiness mode
|
|
112
|
+
const resolved = resolveTernaryValue(condition, source, options);
|
|
113
|
+
if (arr.length === 4) {
|
|
114
|
+
// [ifTrue, trueResult, falseResult, neitherResult]
|
|
115
|
+
if (resolved == null)
|
|
116
|
+
return resolveTernaryValue(arr[3], source, options);
|
|
117
|
+
return resolved ? resolveTernaryValue(arr[1], source, options) : resolveTernaryValue(arr[2], source, options);
|
|
118
|
+
}
|
|
119
|
+
else if (arr.length === 3) {
|
|
120
|
+
// [ifTruthy, thenResult, elseResult]
|
|
121
|
+
return resolved ? resolveTernaryValue(arr[1], source, options) : resolveTernaryValue(arr[2], source, options);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
// [ifTruthy, thenResult] — guard, skip if falsy
|
|
125
|
+
return resolved ? resolveTernaryValue(arr[1], source, options) : TERNARY_SKIP;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Compare two values with a given operator.
|
|
131
|
+
*/
|
|
132
|
+
function compareWithOp(lhs, op, rhs) {
|
|
133
|
+
switch (op) {
|
|
134
|
+
case '===': return lhs === rhs;
|
|
135
|
+
case '!==': return lhs !== rhs;
|
|
136
|
+
case '>': return lhs > rhs;
|
|
137
|
+
case '>=': return lhs >= rhs;
|
|
138
|
+
case '<': return lhs < rhs;
|
|
139
|
+
case '<=': return lhs <= rhs;
|
|
140
|
+
default: return lhs === rhs; // fallback to equality
|
|
141
|
+
}
|
|
142
|
+
}
|
|
38
143
|
/**
|
|
39
144
|
* Recursively substitute a placeholder in all string values of an object.
|
|
40
|
-
* Returns a new object (shallow clone at each level) with substitutions applied.
|
|
41
145
|
*/
|
|
42
|
-
function substituteInValue(value, placeholder, replacement) {
|
|
146
|
+
export function substituteInValue(value, placeholder, replacement) {
|
|
43
147
|
if (typeof value === 'string') {
|
|
44
148
|
return value.includes(placeholder) ? value.replaceAll(placeholder, replacement) : value;
|
|
45
149
|
}
|
|
@@ -61,7 +165,7 @@ function substituteInValue(value, placeholder, replacement) {
|
|
|
61
165
|
/**
|
|
62
166
|
* Check if a pattern entry (key + value) contains a given placeholder.
|
|
63
167
|
*/
|
|
64
|
-
function entryContainsPlaceholder(key, value, placeholder) {
|
|
168
|
+
export function entryContainsPlaceholder(key, value, placeholder) {
|
|
65
169
|
if (key.includes(placeholder))
|
|
66
170
|
return true;
|
|
67
171
|
return valueContainsPlaceholder(value, placeholder);
|
|
@@ -69,7 +173,7 @@ function entryContainsPlaceholder(key, value, placeholder) {
|
|
|
69
173
|
/**
|
|
70
174
|
* Check if a value (string, object, or array) contains a placeholder.
|
|
71
175
|
*/
|
|
72
|
-
function valueContainsPlaceholder(value, placeholder) {
|
|
176
|
+
export function valueContainsPlaceholder(value, placeholder) {
|
|
73
177
|
if (typeof value === 'string')
|
|
74
178
|
return value.includes(placeholder);
|
|
75
179
|
if (Array.isArray(value))
|
|
@@ -84,12 +188,8 @@ function valueContainsPlaceholder(value, placeholder) {
|
|
|
84
188
|
}
|
|
85
189
|
/**
|
|
86
190
|
* Expand looped substitution variables in a pattern.
|
|
87
|
-
* Applies cartesian expansion: x values are expanded first, then y, then z.
|
|
88
|
-
* Each variable multiplies the entries — result count = x.length × y.length × z.length.
|
|
89
|
-
*
|
|
90
|
-
* Returns the expanded pattern (or the original if no substitutions apply).
|
|
91
191
|
*/
|
|
92
|
-
function expandSubstitutions(pattern, options) {
|
|
192
|
+
export function expandSubstitutions(pattern, options) {
|
|
93
193
|
let entries = Object.entries(pattern);
|
|
94
194
|
for (const { placeholder, optionKey } of SUBSTITUTION_VARS) {
|
|
95
195
|
const values = options[optionKey];
|
|
@@ -98,7 +198,6 @@ function expandSubstitutions(pattern, options) {
|
|
|
98
198
|
const expanded = [];
|
|
99
199
|
for (const [key, value] of entries) {
|
|
100
200
|
if (entryContainsPlaceholder(key, value, placeholder)) {
|
|
101
|
-
// Expand this entry for each value in the variable array
|
|
102
201
|
for (const replacement of values) {
|
|
103
202
|
const newKey = key.includes(placeholder)
|
|
104
203
|
? key.replaceAll(placeholder, replacement)
|
|
@@ -108,7 +207,6 @@ function expandSubstitutions(pattern, options) {
|
|
|
108
207
|
}
|
|
109
208
|
}
|
|
110
209
|
else {
|
|
111
|
-
// No placeholder in this entry — pass through
|
|
112
210
|
expanded.push([key, value]);
|
|
113
211
|
}
|
|
114
212
|
}
|
|
@@ -118,14 +216,11 @@ function expandSubstitutions(pattern, options) {
|
|
|
118
216
|
}
|
|
119
217
|
/**
|
|
120
218
|
* Convert entries to an object, merging duplicate handler (` =>`) keys into arrays.
|
|
121
|
-
* For normal (non-handler) keys, later entries overwrite earlier ones (standard object behavior).
|
|
122
|
-
* For handler keys, duplicate entries are combined into an array (Multiple Handlers pattern).
|
|
123
219
|
*/
|
|
124
|
-
function mergeHandlerDuplicates(entries) {
|
|
220
|
+
export function mergeHandlerDuplicates(entries) {
|
|
125
221
|
const result = {};
|
|
126
222
|
for (const [key, value] of entries) {
|
|
127
223
|
if (key.endsWith(' =>') && key in result) {
|
|
128
|
-
// Duplicate handler key — merge into array
|
|
129
224
|
const existing = result[key];
|
|
130
225
|
if (Array.isArray(existing)) {
|
|
131
226
|
existing.push(value);
|
|
@@ -140,14 +235,36 @@ function mergeHandlerDuplicates(entries) {
|
|
|
140
235
|
}
|
|
141
236
|
return result;
|
|
142
237
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
238
|
+
/**
|
|
239
|
+
* Recursively walk an object and handle "..." spread keys.
|
|
240
|
+
*/
|
|
241
|
+
export function handleSpreads(obj) {
|
|
242
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
243
|
+
if (key !== '...' && typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
244
|
+
const proto = Object.getPrototypeOf(value);
|
|
245
|
+
if (proto === Object.prototype || proto === null) {
|
|
246
|
+
obj[key] = handleSpreads(value);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
if ('...' in obj) {
|
|
251
|
+
const spreadValue = obj['...'];
|
|
252
|
+
delete obj['...'];
|
|
253
|
+
if (spreadValue && typeof spreadValue === 'object') {
|
|
254
|
+
Object.assign(obj, spreadValue);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return obj;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Categorize pattern keys into handler keys, #[x] keys, and normal keys.
|
|
261
|
+
*/
|
|
262
|
+
export function categorizeKeys(expandedPattern) {
|
|
147
263
|
const handlerKeys = [];
|
|
148
264
|
const normalPattern = {};
|
|
149
265
|
const idRefNormalKeys = [];
|
|
150
266
|
const idRefHandlerKeys = [];
|
|
267
|
+
const ternaryKeys = [];
|
|
151
268
|
for (const key of Object.keys(expandedPattern)) {
|
|
152
269
|
if (isHandlerCommand(key)) {
|
|
153
270
|
if (key.startsWith('#[')) {
|
|
@@ -157,6 +274,9 @@ export async function assignFrom(target, pattern, options, permissions) {
|
|
|
157
274
|
handlerKeys.push(key);
|
|
158
275
|
}
|
|
159
276
|
}
|
|
277
|
+
else if (isTernaryCommand(key)) {
|
|
278
|
+
ternaryKeys.push(key);
|
|
279
|
+
}
|
|
160
280
|
else if (key.startsWith('#[')) {
|
|
161
281
|
idRefNormalKeys.push(key);
|
|
162
282
|
}
|
|
@@ -164,113 +284,166 @@ export async function assignFrom(target, pattern, options, permissions) {
|
|
|
164
284
|
normalPattern[key] = expandedPattern[key];
|
|
165
285
|
}
|
|
166
286
|
}
|
|
167
|
-
|
|
287
|
+
return { handlerKeys, normalPattern, idRefNormalKeys, idRefHandlerKeys, ternaryKeys };
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Merge withIds and at into a single lookup map for resolveIdVariable.
|
|
291
|
+
*/
|
|
292
|
+
function getEffectiveIds(options) {
|
|
293
|
+
if (!options.withIds && !options.at)
|
|
294
|
+
return undefined;
|
|
295
|
+
if (options.withIds && !options.at)
|
|
296
|
+
return options.withIds;
|
|
297
|
+
if (!options.withIds && options.at)
|
|
298
|
+
return options.at;
|
|
299
|
+
return { ...options.withIds, ...options.at };
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Process #[x] normal keys synchronously.
|
|
303
|
+
*/
|
|
304
|
+
function processIdRefNormalKeys(idRefNormalKeys, expandedPattern, target, options) {
|
|
305
|
+
const ids = getEffectiveIds(options);
|
|
306
|
+
if (!ids)
|
|
307
|
+
return;
|
|
308
|
+
for (const key of idRefNormalKeys) {
|
|
309
|
+
const parsed = parseIdRef(key);
|
|
310
|
+
if (!parsed)
|
|
311
|
+
continue;
|
|
312
|
+
const el = resolveIdVariable(parsed.varName, target, ids);
|
|
313
|
+
if (!el)
|
|
314
|
+
continue;
|
|
315
|
+
const value = expandedPattern[key];
|
|
316
|
+
if (parsed.remainingPath) {
|
|
317
|
+
const resolvedValue = getValues({ __v: value }, options.from, { withMethods: options.withMethods, aka: options.aka, protocols: options.protocols });
|
|
318
|
+
assignGingerly(el, { [parsed.remainingPath]: resolvedValue.__v }, options);
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
const resolvedValue = getValues(typeof value === 'object' && value !== null ? value : { __v: value }, options.from, { withMethods: options.withMethods, aka: options.aka, protocols: options.protocols });
|
|
322
|
+
if (!('__v' in resolvedValue)) {
|
|
323
|
+
assignGingerly(el, resolvedValue, options);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Synchronous assignFrom — resolves values, assigns to target, all without awaiting.
|
|
330
|
+
*
|
|
331
|
+
* Handler commands (` =>`), beVigilant, and enhance are fire-and-forget (async, non-blocking).
|
|
332
|
+
* For awaitable handler execution, use assignFromAsync.
|
|
333
|
+
*
|
|
334
|
+
* @param target - Object to merge resolved values into
|
|
335
|
+
* @param pattern - Object whose RHS values may contain `?.` path strings
|
|
336
|
+
* @param options - Options including `from` (source object)
|
|
337
|
+
* @param permissions - Optional security permissions
|
|
338
|
+
* @returns The target object after merging
|
|
339
|
+
*/
|
|
340
|
+
export function assignFrom(target, pattern, options, permissions) {
|
|
341
|
+
// Expand looped substitution variables
|
|
342
|
+
const expandedPattern = expandSubstitutions(pattern, options);
|
|
343
|
+
// Categorize keys
|
|
344
|
+
const { handlerKeys, normalPattern, idRefNormalKeys, idRefHandlerKeys, ternaryKeys } = categorizeKeys(expandedPattern);
|
|
345
|
+
// Process ?= ternary keys (sync)
|
|
346
|
+
if (ternaryKeys.length > 0) {
|
|
347
|
+
const ternaryResolved = {};
|
|
348
|
+
for (const key of ternaryKeys) {
|
|
349
|
+
const lhsPath = parseTernaryCommand(key);
|
|
350
|
+
if (!lhsPath)
|
|
351
|
+
continue;
|
|
352
|
+
const arr = expandedPattern[key];
|
|
353
|
+
if (!Array.isArray(arr) || arr.length < 2)
|
|
354
|
+
continue;
|
|
355
|
+
const result = evaluateTernary(arr, options.from, options);
|
|
356
|
+
if (result !== TERNARY_SKIP) {
|
|
357
|
+
ternaryResolved[lhsPath] = result;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
if (Object.keys(ternaryResolved).length > 0) {
|
|
361
|
+
assignGingerly(target, ternaryResolved, options);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
// Process normal keys via getValues (sync) + assignGingerly
|
|
168
365
|
if (Object.keys(normalPattern).length > 0) {
|
|
169
|
-
|
|
366
|
+
// Resolve #[x] references on RHS values before getValues
|
|
367
|
+
if (options.withIds || options.at) {
|
|
368
|
+
const ids = getEffectiveIds(options);
|
|
369
|
+
for (const key of Object.keys(normalPattern)) {
|
|
370
|
+
const value = normalPattern[key];
|
|
371
|
+
if (typeof value === 'string' && value.startsWith('#[')) {
|
|
372
|
+
const closeIdx = value.indexOf(']');
|
|
373
|
+
if (closeIdx !== -1) {
|
|
374
|
+
const varName = value.substring(2, closeIdx);
|
|
375
|
+
const el = resolveIdVariable(varName, target, ids);
|
|
376
|
+
if (el) {
|
|
377
|
+
const remainingPath = value.substring(closeIdx + 1);
|
|
378
|
+
if (remainingPath) {
|
|
379
|
+
normalPattern[key] = getValue(remainingPath, el, {
|
|
380
|
+
withMethods: options.withMethods,
|
|
381
|
+
aka: options.aka,
|
|
382
|
+
protocols: options.protocols
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
else {
|
|
386
|
+
normalPattern[key] = el.id; // bare #[x] → ID string
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
const resolved = getValues(normalPattern, options.from, {
|
|
170
394
|
withMethods: options.withMethods,
|
|
171
395
|
aka: options.aka,
|
|
172
396
|
protocols: options.protocols
|
|
173
397
|
});
|
|
174
|
-
// Recursively handle "..." spread keys at all nesting levels
|
|
175
398
|
handleSpreads(resolved);
|
|
176
399
|
assignGingerly(target, resolved, options);
|
|
177
400
|
}
|
|
178
|
-
// Process #[x] normal keys
|
|
179
|
-
if (idRefNormalKeys.length > 0
|
|
180
|
-
|
|
181
|
-
for (const key of idRefNormalKeys) {
|
|
182
|
-
const parsed = parseIdRef(key);
|
|
183
|
-
if (!parsed)
|
|
184
|
-
continue;
|
|
185
|
-
const el = resolveIdVariable(parsed.varName, target, options.withIds);
|
|
186
|
-
if (!el)
|
|
187
|
-
continue;
|
|
188
|
-
const value = expandedPattern[key];
|
|
189
|
-
if (parsed.remainingPath) {
|
|
190
|
-
// Resolve the RHS value
|
|
191
|
-
const resolvedValue = await resolveValues({ __v: value }, options.from, { withMethods: options.withMethods, aka: options.aka, protocols: options.protocols });
|
|
192
|
-
// Apply remaining path on the resolved element
|
|
193
|
-
assignGingerly(el, { [parsed.remainingPath]: resolvedValue.__v }, options);
|
|
194
|
-
}
|
|
195
|
-
else {
|
|
196
|
-
// No remaining path — resolve and assign directly to the element
|
|
197
|
-
const resolvedValue = await resolveValues(typeof value === 'object' && value !== null ? value : { __v: value }, options.from, { withMethods: options.withMethods, aka: options.aka, protocols: options.protocols });
|
|
198
|
-
if ('__v' in resolvedValue) {
|
|
199
|
-
// Single value — can't assign to element root without a path
|
|
200
|
-
}
|
|
201
|
-
else {
|
|
202
|
-
assignGingerly(el, resolvedValue, options);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
}
|
|
401
|
+
// Process #[x] normal keys (sync)
|
|
402
|
+
if (idRefNormalKeys.length > 0) {
|
|
403
|
+
processIdRefNormalKeys(idRefNormalKeys, expandedPattern, target, options);
|
|
206
404
|
}
|
|
207
|
-
// Process handler commands
|
|
405
|
+
// Process handler commands — fire-and-forget (async)
|
|
208
406
|
if (handlerKeys.length > 0) {
|
|
209
|
-
|
|
210
|
-
|
|
407
|
+
import('./processHandlerCommands.js').then(({ processHandlerCommands }) => {
|
|
408
|
+
processHandlerCommands(target, handlerKeys, expandedPattern, options, permissions);
|
|
409
|
+
});
|
|
211
410
|
}
|
|
212
|
-
// Process #[x] handler keys —
|
|
213
|
-
if (idRefHandlerKeys.length > 0 && options.withIds) {
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
const syntheticPattern = {
|
|
229
|
-
[syntheticKey]: expandedPattern[key]
|
|
230
|
-
};
|
|
231
|
-
await processHandlerCommands(el, [syntheticKey], syntheticPattern, options, permissions);
|
|
232
|
-
}
|
|
411
|
+
// Process #[x] handler keys — fire-and-forget (async)
|
|
412
|
+
if (idRefHandlerKeys.length > 0 && (options.withIds || options.at)) {
|
|
413
|
+
const ids = getEffectiveIds(options);
|
|
414
|
+
import('./processHandlerCommands.js').then(({ processHandlerCommands }) => {
|
|
415
|
+
for (const key of idRefHandlerKeys) {
|
|
416
|
+
const parsed = parseIdRef(key);
|
|
417
|
+
if (!parsed)
|
|
418
|
+
continue;
|
|
419
|
+
const el = resolveIdVariable(parsed.varName, target, ids);
|
|
420
|
+
if (!el)
|
|
421
|
+
continue;
|
|
422
|
+
const syntheticKey = parsed.remainingPath ? `${parsed.remainingPath} =>` : ' =>';
|
|
423
|
+
const syntheticPattern = { [syntheticKey]: expandedPattern[key] };
|
|
424
|
+
processHandlerCommands(el, [syntheticKey], syntheticPattern, options, permissions);
|
|
425
|
+
}
|
|
426
|
+
});
|
|
233
427
|
}
|
|
234
|
-
// Process inferred assignments
|
|
235
|
-
if (options.
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
if (options.inferredAssignments.beVigilant) {
|
|
428
|
+
// Process inferred assignments (sync)
|
|
429
|
+
if (options.infer) {
|
|
430
|
+
processInferredAssignments(target, options.from, options.infer);
|
|
431
|
+
// beVigilant — fire-and-forget (async)
|
|
432
|
+
if (options.infer.beVigilant) {
|
|
240
433
|
if (!options.signal) {
|
|
241
|
-
throw new Error('assignFrom:
|
|
434
|
+
throw new Error('assignFrom: infer.beVigilant requires options.signal (AbortSignal) for cleanup');
|
|
242
435
|
}
|
|
243
|
-
|
|
244
|
-
|
|
436
|
+
import('./beVigilant.js').then(({ setupVigilantObserver }) => {
|
|
437
|
+
setupVigilantObserver(target, options.from, options.infer, options.signal);
|
|
438
|
+
});
|
|
245
439
|
}
|
|
246
440
|
}
|
|
247
|
-
// Process bulk enhancements —
|
|
441
|
+
// Process bulk enhancements — fire-and-forget (async)
|
|
248
442
|
if (options.enhance && options.enhance.length > 0) {
|
|
249
|
-
|
|
250
|
-
|
|
443
|
+
import('./enhanceAll.js').then(({ enhanceAll }) => {
|
|
444
|
+
enhanceAll(target, options.enhance, permissions);
|
|
445
|
+
});
|
|
251
446
|
}
|
|
252
447
|
return target;
|
|
253
448
|
}
|
|
254
|
-
|
|
255
|
-
* Recursively walk an object and handle "..." spread keys.
|
|
256
|
-
* When a "..." key is found, its value (which should be an object after protocol resolution)
|
|
257
|
-
* is spread into the parent, replacing the "..." entry.
|
|
258
|
-
*/
|
|
259
|
-
function handleSpreads(obj) {
|
|
260
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
261
|
-
if (key !== '...' && typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
262
|
-
const proto = Object.getPrototypeOf(value);
|
|
263
|
-
if (proto === Object.prototype || proto === null) {
|
|
264
|
-
obj[key] = handleSpreads(value);
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
if ('...' in obj) {
|
|
269
|
-
const spreadValue = obj['...'];
|
|
270
|
-
delete obj['...'];
|
|
271
|
-
if (spreadValue && typeof spreadValue === 'object') {
|
|
272
|
-
Object.assign(obj, spreadValue);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
return obj;
|
|
276
|
-
}
|
|
449
|
+
export default assignFrom;
|