assign-gingerly 0.0.57 → 0.0.59
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +243 -131
- package/assignFrom.js +124 -129
- package/assignFrom.ts +146 -233
- package/assignFromAsync.js +118 -0
- package/assignFromAsync.ts +240 -0
- package/getValues.js +223 -0
- package/getValues.ts +255 -0
- package/handlers/join.ts +1 -1
- package/handlers/lazyLoad.js +2 -2
- package/handlers/lazyLoad.ts +3 -3
- package/handlers/lazyLoadSwitch.ts +1 -1
- package/handlers/manageTemplateList.js +203 -0
- package/handlers/manageTemplateList.ts +240 -0
- package/handlers/microDataJoin.ts +1 -1
- package/index.js +1 -0
- package/index.ts +2 -0
- package/inferredAssignments.js +1 -1
- package/inferredAssignments.ts +2 -2
- package/markerUtils.js +136 -127
- package/package.json +18 -1
- package/processHandlerCommands.js +30 -3
- package/processHandlerCommands.ts +31 -7
- package/resolveValues.js +41 -125
- package/resolveValues.ts +131 -255
- package/transitionHelper.js +11 -5
- package/transitionHelper.ts +11 -5
- package/types/assign-gingerly/types.d.ts +51 -0
- package/waitForSettled.js +57 -0
- package/waitForSettled.ts +65 -0
package/assignFrom.js
CHANGED
|
@@ -1,45 +1,35 @@
|
|
|
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 } 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
|
+
}
|
|
38
29
|
/**
|
|
39
30
|
* 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
31
|
*/
|
|
42
|
-
function substituteInValue(value, placeholder, replacement) {
|
|
32
|
+
export function substituteInValue(value, placeholder, replacement) {
|
|
43
33
|
if (typeof value === 'string') {
|
|
44
34
|
return value.includes(placeholder) ? value.replaceAll(placeholder, replacement) : value;
|
|
45
35
|
}
|
|
@@ -61,7 +51,7 @@ function substituteInValue(value, placeholder, replacement) {
|
|
|
61
51
|
/**
|
|
62
52
|
* Check if a pattern entry (key + value) contains a given placeholder.
|
|
63
53
|
*/
|
|
64
|
-
function entryContainsPlaceholder(key, value, placeholder) {
|
|
54
|
+
export function entryContainsPlaceholder(key, value, placeholder) {
|
|
65
55
|
if (key.includes(placeholder))
|
|
66
56
|
return true;
|
|
67
57
|
return valueContainsPlaceholder(value, placeholder);
|
|
@@ -69,7 +59,7 @@ function entryContainsPlaceholder(key, value, placeholder) {
|
|
|
69
59
|
/**
|
|
70
60
|
* Check if a value (string, object, or array) contains a placeholder.
|
|
71
61
|
*/
|
|
72
|
-
function valueContainsPlaceholder(value, placeholder) {
|
|
62
|
+
export function valueContainsPlaceholder(value, placeholder) {
|
|
73
63
|
if (typeof value === 'string')
|
|
74
64
|
return value.includes(placeholder);
|
|
75
65
|
if (Array.isArray(value))
|
|
@@ -84,12 +74,8 @@ function valueContainsPlaceholder(value, placeholder) {
|
|
|
84
74
|
}
|
|
85
75
|
/**
|
|
86
76
|
* 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
77
|
*/
|
|
92
|
-
function expandSubstitutions(pattern, options) {
|
|
78
|
+
export function expandSubstitutions(pattern, options) {
|
|
93
79
|
let entries = Object.entries(pattern);
|
|
94
80
|
for (const { placeholder, optionKey } of SUBSTITUTION_VARS) {
|
|
95
81
|
const values = options[optionKey];
|
|
@@ -98,7 +84,6 @@ function expandSubstitutions(pattern, options) {
|
|
|
98
84
|
const expanded = [];
|
|
99
85
|
for (const [key, value] of entries) {
|
|
100
86
|
if (entryContainsPlaceholder(key, value, placeholder)) {
|
|
101
|
-
// Expand this entry for each value in the variable array
|
|
102
87
|
for (const replacement of values) {
|
|
103
88
|
const newKey = key.includes(placeholder)
|
|
104
89
|
? key.replaceAll(placeholder, replacement)
|
|
@@ -108,7 +93,6 @@ function expandSubstitutions(pattern, options) {
|
|
|
108
93
|
}
|
|
109
94
|
}
|
|
110
95
|
else {
|
|
111
|
-
// No placeholder in this entry — pass through
|
|
112
96
|
expanded.push([key, value]);
|
|
113
97
|
}
|
|
114
98
|
}
|
|
@@ -118,14 +102,11 @@ function expandSubstitutions(pattern, options) {
|
|
|
118
102
|
}
|
|
119
103
|
/**
|
|
120
104
|
* 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
105
|
*/
|
|
124
|
-
function mergeHandlerDuplicates(entries) {
|
|
106
|
+
export function mergeHandlerDuplicates(entries) {
|
|
125
107
|
const result = {};
|
|
126
108
|
for (const [key, value] of entries) {
|
|
127
109
|
if (key.endsWith(' =>') && key in result) {
|
|
128
|
-
// Duplicate handler key — merge into array
|
|
129
110
|
const existing = result[key];
|
|
130
111
|
if (Array.isArray(existing)) {
|
|
131
112
|
existing.push(value);
|
|
@@ -140,10 +121,31 @@ function mergeHandlerDuplicates(entries) {
|
|
|
140
121
|
}
|
|
141
122
|
return result;
|
|
142
123
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
124
|
+
/**
|
|
125
|
+
* Recursively walk an object and handle "..." spread keys.
|
|
126
|
+
*/
|
|
127
|
+
export function handleSpreads(obj) {
|
|
128
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
129
|
+
if (key !== '...' && typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
130
|
+
const proto = Object.getPrototypeOf(value);
|
|
131
|
+
if (proto === Object.prototype || proto === null) {
|
|
132
|
+
obj[key] = handleSpreads(value);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if ('...' in obj) {
|
|
137
|
+
const spreadValue = obj['...'];
|
|
138
|
+
delete obj['...'];
|
|
139
|
+
if (spreadValue && typeof spreadValue === 'object') {
|
|
140
|
+
Object.assign(obj, spreadValue);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return obj;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Categorize pattern keys into handler keys, #[x] keys, and normal keys.
|
|
147
|
+
*/
|
|
148
|
+
export function categorizeKeys(expandedPattern) {
|
|
147
149
|
const handlerKeys = [];
|
|
148
150
|
const normalPattern = {};
|
|
149
151
|
const idRefNormalKeys = [];
|
|
@@ -164,113 +166,106 @@ export async function assignFrom(target, pattern, options, permissions) {
|
|
|
164
166
|
normalPattern[key] = expandedPattern[key];
|
|
165
167
|
}
|
|
166
168
|
}
|
|
167
|
-
|
|
169
|
+
return { handlerKeys, normalPattern, idRefNormalKeys, idRefHandlerKeys };
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Process #[x] normal keys synchronously.
|
|
173
|
+
*/
|
|
174
|
+
function processIdRefNormalKeys(idRefNormalKeys, expandedPattern, target, options) {
|
|
175
|
+
if (!options.withIds)
|
|
176
|
+
return;
|
|
177
|
+
for (const key of idRefNormalKeys) {
|
|
178
|
+
const parsed = parseIdRef(key);
|
|
179
|
+
if (!parsed)
|
|
180
|
+
continue;
|
|
181
|
+
const el = resolveIdVariable(parsed.varName, target, options.withIds);
|
|
182
|
+
if (!el)
|
|
183
|
+
continue;
|
|
184
|
+
const value = expandedPattern[key];
|
|
185
|
+
if (parsed.remainingPath) {
|
|
186
|
+
const resolvedValue = getValues({ __v: value }, options.from, { withMethods: options.withMethods, aka: options.aka, protocols: options.protocols });
|
|
187
|
+
assignGingerly(el, { [parsed.remainingPath]: resolvedValue.__v }, options);
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
const resolvedValue = getValues(typeof value === 'object' && value !== null ? value : { __v: value }, options.from, { withMethods: options.withMethods, aka: options.aka, protocols: options.protocols });
|
|
191
|
+
if (!('__v' in resolvedValue)) {
|
|
192
|
+
assignGingerly(el, resolvedValue, options);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Synchronous assignFrom — resolves values, assigns to target, all without awaiting.
|
|
199
|
+
*
|
|
200
|
+
* Handler commands (` =>`), beVigilant, and enhance are fire-and-forget (async, non-blocking).
|
|
201
|
+
* For awaitable handler execution, use assignFromAsync.
|
|
202
|
+
*
|
|
203
|
+
* @param target - Object to merge resolved values into
|
|
204
|
+
* @param pattern - Object whose RHS values may contain `?.` path strings
|
|
205
|
+
* @param options - Options including `from` (source object)
|
|
206
|
+
* @param permissions - Optional security permissions
|
|
207
|
+
* @returns The target object after merging
|
|
208
|
+
*/
|
|
209
|
+
export function assignFrom(target, pattern, options, permissions) {
|
|
210
|
+
// Expand looped substitution variables
|
|
211
|
+
const expandedPattern = expandSubstitutions(pattern, options);
|
|
212
|
+
// Categorize keys
|
|
213
|
+
const { handlerKeys, normalPattern, idRefNormalKeys, idRefHandlerKeys } = categorizeKeys(expandedPattern);
|
|
214
|
+
// Process normal keys via getValues (sync) + assignGingerly
|
|
168
215
|
if (Object.keys(normalPattern).length > 0) {
|
|
169
|
-
const resolved =
|
|
216
|
+
const resolved = getValues(normalPattern, options.from, {
|
|
170
217
|
withMethods: options.withMethods,
|
|
171
218
|
aka: options.aka,
|
|
172
219
|
protocols: options.protocols
|
|
173
220
|
});
|
|
174
|
-
// Recursively handle "..." spread keys at all nesting levels
|
|
175
221
|
handleSpreads(resolved);
|
|
176
222
|
assignGingerly(target, resolved, options);
|
|
177
223
|
}
|
|
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
|
-
}
|
|
224
|
+
// Process #[x] normal keys (sync)
|
|
225
|
+
if (idRefNormalKeys.length > 0) {
|
|
226
|
+
processIdRefNormalKeys(idRefNormalKeys, expandedPattern, target, options);
|
|
206
227
|
}
|
|
207
|
-
// Process handler commands
|
|
228
|
+
// Process handler commands — fire-and-forget (async)
|
|
208
229
|
if (handlerKeys.length > 0) {
|
|
209
|
-
|
|
210
|
-
|
|
230
|
+
import('./processHandlerCommands.js').then(({ processHandlerCommands }) => {
|
|
231
|
+
processHandlerCommands(target, handlerKeys, expandedPattern, options, permissions);
|
|
232
|
+
});
|
|
211
233
|
}
|
|
212
|
-
// Process #[x] handler keys —
|
|
234
|
+
// Process #[x] handler keys — fire-and-forget (async)
|
|
213
235
|
if (idRefHandlerKeys.length > 0 && options.withIds) {
|
|
214
|
-
|
|
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
|
-
}
|
|
236
|
+
import('./processHandlerCommands.js').then(({ processHandlerCommands }) => {
|
|
237
|
+
for (const key of idRefHandlerKeys) {
|
|
238
|
+
const parsed = parseIdRef(key);
|
|
239
|
+
if (!parsed)
|
|
240
|
+
continue;
|
|
241
|
+
const el = resolveIdVariable(parsed.varName, target, options.withIds);
|
|
242
|
+
if (!el)
|
|
243
|
+
continue;
|
|
244
|
+
const syntheticKey = parsed.remainingPath ? `${parsed.remainingPath} =>` : ' =>';
|
|
245
|
+
const syntheticPattern = { [syntheticKey]: expandedPattern[key] };
|
|
246
|
+
processHandlerCommands(el, [syntheticKey], syntheticPattern, options, permissions);
|
|
247
|
+
}
|
|
248
|
+
});
|
|
233
249
|
}
|
|
234
|
-
// Process inferred assignments
|
|
250
|
+
// Process inferred assignments (sync)
|
|
235
251
|
if (options.inferredAssignments) {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
// Set up MutationObserver for new matching elements if beVigilant
|
|
252
|
+
processInferredAssignments(target, options.from, options.inferredAssignments);
|
|
253
|
+
// beVigilant — fire-and-forget (async)
|
|
239
254
|
if (options.inferredAssignments.beVigilant) {
|
|
240
255
|
if (!options.signal) {
|
|
241
256
|
throw new Error('assignFrom: inferredAssignments.beVigilant requires options.signal (AbortSignal) for cleanup');
|
|
242
257
|
}
|
|
243
|
-
|
|
244
|
-
|
|
258
|
+
import('./beVigilant.js').then(({ setupVigilantObserver }) => {
|
|
259
|
+
setupVigilantObserver(target, options.from, options.inferredAssignments, options.signal);
|
|
260
|
+
});
|
|
245
261
|
}
|
|
246
262
|
}
|
|
247
|
-
// Process bulk enhancements —
|
|
263
|
+
// Process bulk enhancements — fire-and-forget (async)
|
|
248
264
|
if (options.enhance && options.enhance.length > 0) {
|
|
249
|
-
|
|
250
|
-
|
|
265
|
+
import('./enhanceAll.js').then(({ enhanceAll }) => {
|
|
266
|
+
enhanceAll(target, options.enhance, permissions);
|
|
267
|
+
});
|
|
251
268
|
}
|
|
252
269
|
return target;
|
|
253
270
|
}
|
|
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
|
-
}
|
|
271
|
+
export default assignFrom;
|