assign-gingerly 0.0.52 → 0.0.54
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 +705 -4
- package/assignFrom.js +229 -11
- package/assignFrom.ts +301 -13
- package/package.json +31 -3
- package/paths.js +183 -0
- package/paths.ts +334 -0
- package/processHandlerCommands.js +188 -0
- package/processHandlerCommands.ts +220 -0
- package/resolveIdRef.js +125 -0
- package/resolveIdRef.ts +140 -0
- package/resolveValues.js +49 -0
- package/resolveValues.ts +49 -0
- package/transitionHelper.js +109 -0
- package/transitionHelper.ts +132 -0
- package/types/assign-gingerly/types.d.ts +77 -0
package/assignFrom.js
CHANGED
|
@@ -21,19 +21,237 @@
|
|
|
21
21
|
*/
|
|
22
22
|
import { resolveValues } from './resolveValues.js';
|
|
23
23
|
import assignGingerly from './assignGingerly.js';
|
|
24
|
+
/**
|
|
25
|
+
* Check if a key ends with the handler operator ' =>'.
|
|
26
|
+
*/
|
|
27
|
+
function isHandlerCommand(key) {
|
|
28
|
+
return key.endsWith(' =>');
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Supported substitution variables and their option keys.
|
|
32
|
+
*/
|
|
33
|
+
const SUBSTITUTION_VARS = [
|
|
34
|
+
{ placeholder: '${x}', optionKey: 'where_x_in' },
|
|
35
|
+
{ placeholder: '${y}', optionKey: 'where_y_in' },
|
|
36
|
+
{ placeholder: '${z}', optionKey: 'where_z_in' },
|
|
37
|
+
];
|
|
38
|
+
/**
|
|
39
|
+
* 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
|
+
*/
|
|
42
|
+
function substituteInValue(value, placeholder, replacement) {
|
|
43
|
+
if (typeof value === 'string') {
|
|
44
|
+
return value.includes(placeholder) ? value.replaceAll(placeholder, replacement) : value;
|
|
45
|
+
}
|
|
46
|
+
if (Array.isArray(value)) {
|
|
47
|
+
return value.map(item => substituteInValue(item, placeholder, replacement));
|
|
48
|
+
}
|
|
49
|
+
if (value && typeof value === 'object') {
|
|
50
|
+
const proto = Object.getPrototypeOf(value);
|
|
51
|
+
if (proto === Object.prototype || proto === null) {
|
|
52
|
+
const result = {};
|
|
53
|
+
for (const [k, v] of Object.entries(value)) {
|
|
54
|
+
result[k] = substituteInValue(v, placeholder, replacement);
|
|
55
|
+
}
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return value;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Check if a pattern entry (key + value) contains a given placeholder.
|
|
63
|
+
*/
|
|
64
|
+
function entryContainsPlaceholder(key, value, placeholder) {
|
|
65
|
+
if (key.includes(placeholder))
|
|
66
|
+
return true;
|
|
67
|
+
return valueContainsPlaceholder(value, placeholder);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Check if a value (string, object, or array) contains a placeholder.
|
|
71
|
+
*/
|
|
72
|
+
function valueContainsPlaceholder(value, placeholder) {
|
|
73
|
+
if (typeof value === 'string')
|
|
74
|
+
return value.includes(placeholder);
|
|
75
|
+
if (Array.isArray(value))
|
|
76
|
+
return value.some(item => valueContainsPlaceholder(item, placeholder));
|
|
77
|
+
if (value && typeof value === 'object') {
|
|
78
|
+
const proto = Object.getPrototypeOf(value);
|
|
79
|
+
if (proto === Object.prototype || proto === null) {
|
|
80
|
+
return Object.values(value).some(v => valueContainsPlaceholder(v, placeholder));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* 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
|
+
*/
|
|
92
|
+
function expandSubstitutions(pattern, options) {
|
|
93
|
+
let entries = Object.entries(pattern);
|
|
94
|
+
for (const { placeholder, optionKey } of SUBSTITUTION_VARS) {
|
|
95
|
+
const values = options[optionKey];
|
|
96
|
+
if (!values || values.length === 0)
|
|
97
|
+
continue;
|
|
98
|
+
const expanded = [];
|
|
99
|
+
for (const [key, value] of entries) {
|
|
100
|
+
if (entryContainsPlaceholder(key, value, placeholder)) {
|
|
101
|
+
// Expand this entry for each value in the variable array
|
|
102
|
+
for (const replacement of values) {
|
|
103
|
+
const newKey = key.includes(placeholder)
|
|
104
|
+
? key.replaceAll(placeholder, replacement)
|
|
105
|
+
: key;
|
|
106
|
+
const newValue = substituteInValue(value, placeholder, replacement);
|
|
107
|
+
expanded.push([newKey, newValue]);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
// No placeholder in this entry — pass through
|
|
112
|
+
expanded.push([key, value]);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
entries = expanded;
|
|
116
|
+
}
|
|
117
|
+
return mergeHandlerDuplicates(entries);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* 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
|
+
*/
|
|
124
|
+
function mergeHandlerDuplicates(entries) {
|
|
125
|
+
const result = {};
|
|
126
|
+
for (const [key, value] of entries) {
|
|
127
|
+
if (key.endsWith(' =>') && key in result) {
|
|
128
|
+
// Duplicate handler key — merge into array
|
|
129
|
+
const existing = result[key];
|
|
130
|
+
if (Array.isArray(existing)) {
|
|
131
|
+
existing.push(value);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
result[key] = [existing, value];
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
result[key] = value;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return result;
|
|
142
|
+
}
|
|
24
143
|
export async function assignFrom(target, pattern, options) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
144
|
+
// First: expand looped substitution variables (${x}, ${y}, ${z})
|
|
145
|
+
const expandedPattern = expandSubstitutions(pattern, options);
|
|
146
|
+
// Separate handler commands ( =>), #[x] keys, and normal keys
|
|
147
|
+
const handlerKeys = [];
|
|
148
|
+
const normalPattern = {};
|
|
149
|
+
const idRefNormalKeys = [];
|
|
150
|
+
const idRefHandlerKeys = [];
|
|
151
|
+
for (const key of Object.keys(expandedPattern)) {
|
|
152
|
+
if (isHandlerCommand(key)) {
|
|
153
|
+
if (key.startsWith('#[')) {
|
|
154
|
+
idRefHandlerKeys.push(key);
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
handlerKeys.push(key);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
else if (key.startsWith('#[')) {
|
|
161
|
+
idRefNormalKeys.push(key);
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
normalPattern[key] = expandedPattern[key];
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// Process normal keys via resolveValues + assignGingerly
|
|
168
|
+
if (Object.keys(normalPattern).length > 0) {
|
|
169
|
+
const resolved = await resolveValues(normalPattern, options.from, {
|
|
170
|
+
withMethods: options.withMethods,
|
|
171
|
+
aka: options.aka,
|
|
172
|
+
protocols: options.protocols
|
|
173
|
+
});
|
|
174
|
+
// Recursively handle "..." spread keys at all nesting levels
|
|
175
|
+
handleSpreads(resolved);
|
|
176
|
+
assignGingerly(target, resolved, options);
|
|
177
|
+
}
|
|
178
|
+
// Process #[x] normal keys — resolve element, then apply remaining path + value
|
|
179
|
+
if (idRefNormalKeys.length > 0 && options.withIds) {
|
|
180
|
+
const { resolveIdVariable, parseIdRef } = await import('./resolveIdRef.js');
|
|
181
|
+
for (const key of idRefNormalKeys) {
|
|
182
|
+
const parsed = parseIdRef(key);
|
|
183
|
+
if (!parsed) continue;
|
|
184
|
+
const el = resolveIdVariable(parsed.varName, target, options.withIds);
|
|
185
|
+
if (!el) continue;
|
|
186
|
+
const value = expandedPattern[key];
|
|
187
|
+
if (parsed.remainingPath) {
|
|
188
|
+
// Resolve the RHS value
|
|
189
|
+
const resolvedValue = await resolveValues(
|
|
190
|
+
{ __v: value }, options.from,
|
|
191
|
+
{ withMethods: options.withMethods, aka: options.aka, protocols: options.protocols }
|
|
192
|
+
);
|
|
193
|
+
// Apply remaining path on the resolved element
|
|
194
|
+
assignGingerly(el, { [parsed.remainingPath]: resolvedValue.__v }, options);
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
// No remaining path — resolve and assign directly to the element
|
|
198
|
+
const resolvedValue = await resolveValues(
|
|
199
|
+
typeof value === 'object' && value !== null ? value : { __v: value },
|
|
200
|
+
options.from,
|
|
201
|
+
{ withMethods: options.withMethods, aka: options.aka, protocols: options.protocols }
|
|
202
|
+
);
|
|
203
|
+
if (!('__v' in resolvedValue)) {
|
|
204
|
+
assignGingerly(el, resolvedValue, options);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
// Process handler commands ( =>) — dynamically imported only when needed
|
|
210
|
+
if (handlerKeys.length > 0) {
|
|
211
|
+
const { processHandlerCommands } = await import('./processHandlerCommands.js');
|
|
212
|
+
await processHandlerCommands(target, handlerKeys, expandedPattern, options);
|
|
213
|
+
}
|
|
214
|
+
// Process #[x] handler keys — resolve element, then pass to handler processing
|
|
215
|
+
if (idRefHandlerKeys.length > 0 && options.withIds) {
|
|
216
|
+
const { resolveIdVariable, parseIdRef } = await import('./resolveIdRef.js');
|
|
217
|
+
const { processHandlerCommands } = await import('./processHandlerCommands.js');
|
|
218
|
+
for (const key of idRefHandlerKeys) {
|
|
219
|
+
const parsed = parseIdRef(key);
|
|
220
|
+
if (!parsed) continue;
|
|
221
|
+
const el = resolveIdVariable(parsed.varName, target, options.withIds);
|
|
222
|
+
if (!el) continue;
|
|
223
|
+
// Build a synthetic key for processHandlerCommands
|
|
224
|
+
const syntheticKey = parsed.remainingPath
|
|
225
|
+
? `${parsed.remainingPath} =>`
|
|
226
|
+
: ' =>';
|
|
227
|
+
const syntheticPattern = {
|
|
228
|
+
[syntheticKey]: expandedPattern[key]
|
|
229
|
+
};
|
|
230
|
+
await processHandlerCommands(el, [syntheticKey], syntheticPattern, options);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return target;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Recursively walk an object and handle "..." spread keys.
|
|
237
|
+
* When a "..." key is found, its value (which should be an object after protocol resolution)
|
|
238
|
+
* is spread into the parent, replacing the "..." entry.
|
|
239
|
+
*/
|
|
240
|
+
function handleSpreads(obj) {
|
|
241
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
242
|
+
if (key !== '...' && typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
243
|
+
const proto = Object.getPrototypeOf(value);
|
|
244
|
+
if (proto === Object.prototype || proto === null) {
|
|
245
|
+
obj[key] = handleSpreads(value);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if ('...' in obj) {
|
|
250
|
+
const spreadValue = obj['...'];
|
|
251
|
+
delete obj['...'];
|
|
33
252
|
if (spreadValue && typeof spreadValue === 'object') {
|
|
34
|
-
Object.assign(
|
|
253
|
+
Object.assign(obj, spreadValue);
|
|
35
254
|
}
|
|
36
|
-
delete resolved['...'];
|
|
37
255
|
}
|
|
38
|
-
return
|
|
256
|
+
return obj;
|
|
39
257
|
}
|
package/assignFrom.ts
CHANGED
|
@@ -25,6 +25,181 @@ import assignGingerly, { IAssignGingerlyOptions } from './assignGingerly.js';
|
|
|
25
25
|
export interface AssignFromOptions extends IAssignGingerlyOptions, ResolveValuesOptions {
|
|
26
26
|
/** Source object to resolve RHS path strings against */
|
|
27
27
|
from: any;
|
|
28
|
+
|
|
29
|
+
/** Loop variable bindings — expand pattern entries containing ${x} */
|
|
30
|
+
where_x_in?: string[];
|
|
31
|
+
/** Loop variable bindings — expand pattern entries containing ${y} */
|
|
32
|
+
where_y_in?: string[];
|
|
33
|
+
/** Loop variable bindings — expand pattern entries containing ${z} */
|
|
34
|
+
where_z_in?: string[];
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Cached element references by variable name.
|
|
38
|
+
* Used with `#[varName]` syntax in LHS keys for fast repeated element access.
|
|
39
|
+
*
|
|
40
|
+
* - String value: existing element ID (uses getElementById)
|
|
41
|
+
* - Object value: { qry: 'selector' } — finds element via querySelector on target, auto-assigns an ID
|
|
42
|
+
*
|
|
43
|
+
* Elements are cached via WeakRef with getElementById fallback on cache miss.
|
|
44
|
+
*/
|
|
45
|
+
withIds?: Record<string, string | { qry: string }>;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Handler implementations scoped to this call.
|
|
49
|
+
* Key: the `do` name referenced in handler configs.
|
|
50
|
+
* Value: a class constructor, or an import path to dynamically load one.
|
|
51
|
+
*
|
|
52
|
+
* Import paths must be local (relative, absolute, or bare specifier — no cross-domain URLs).
|
|
53
|
+
* The module's default export is checked first; otherwise the first exported class
|
|
54
|
+
* with an `assign` method on its prototype is used.
|
|
55
|
+
*
|
|
56
|
+
* Built-in handlers (builtIns.*) auto-load without needing to be listed here.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* handlers: {
|
|
60
|
+
* 'my-list': MyListHandler, // class constructor
|
|
61
|
+
* 'my-chart': './handlers/chart.js', // dynamic import path
|
|
62
|
+
* 'vendor-widget': 'some-package/handler.js', // bare specifier (import map)
|
|
63
|
+
* }
|
|
64
|
+
*/
|
|
65
|
+
handlers?: Record<string, AssignFromHandlerConstructor | string>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Interface for assignFrom handler classes.
|
|
70
|
+
* Handlers are invoked when a LHS key ends with ' =>'.
|
|
71
|
+
*/
|
|
72
|
+
export interface AssignFromHandler {
|
|
73
|
+
assign(lhsTarget: any, resolvedParams: Record<string, any>, options: AssignFromOptions): Promise<void> | void;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface AssignFromHandlerConstructor {
|
|
77
|
+
new (config: any): AssignFromHandler;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Check if a key ends with the handler operator ' =>'.
|
|
82
|
+
*/
|
|
83
|
+
function isHandlerCommand(key: string): boolean {
|
|
84
|
+
return key.endsWith(' =>');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Supported substitution variables and their option keys.
|
|
89
|
+
*/
|
|
90
|
+
const SUBSTITUTION_VARS = [
|
|
91
|
+
{ placeholder: '${x}', optionKey: 'where_x_in' },
|
|
92
|
+
{ placeholder: '${y}', optionKey: 'where_y_in' },
|
|
93
|
+
{ placeholder: '${z}', optionKey: 'where_z_in' },
|
|
94
|
+
] as const;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Recursively substitute a placeholder in all string values of an object.
|
|
98
|
+
* Returns a new object (shallow clone at each level) with substitutions applied.
|
|
99
|
+
*/
|
|
100
|
+
function substituteInValue(value: any, placeholder: string, replacement: string): any {
|
|
101
|
+
if (typeof value === 'string') {
|
|
102
|
+
return value.includes(placeholder) ? value.replaceAll(placeholder, replacement) : value;
|
|
103
|
+
}
|
|
104
|
+
if (Array.isArray(value)) {
|
|
105
|
+
return value.map(item => substituteInValue(item, placeholder, replacement));
|
|
106
|
+
}
|
|
107
|
+
if (value && typeof value === 'object') {
|
|
108
|
+
const proto = Object.getPrototypeOf(value);
|
|
109
|
+
if (proto === Object.prototype || proto === null) {
|
|
110
|
+
const result: Record<string, any> = {};
|
|
111
|
+
for (const [k, v] of Object.entries(value)) {
|
|
112
|
+
result[k] = substituteInValue(v, placeholder, replacement);
|
|
113
|
+
}
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return value;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Check if a pattern entry (key + value) contains a given placeholder.
|
|
122
|
+
*/
|
|
123
|
+
function entryContainsPlaceholder(key: string, value: any, placeholder: string): boolean {
|
|
124
|
+
if (key.includes(placeholder)) return true;
|
|
125
|
+
return valueContainsPlaceholder(value, placeholder);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Check if a value (string, object, or array) contains a placeholder.
|
|
130
|
+
*/
|
|
131
|
+
function valueContainsPlaceholder(value: any, placeholder: string): boolean {
|
|
132
|
+
if (typeof value === 'string') return value.includes(placeholder);
|
|
133
|
+
if (Array.isArray(value)) return value.some(item => valueContainsPlaceholder(item, placeholder));
|
|
134
|
+
if (value && typeof value === 'object') {
|
|
135
|
+
const proto = Object.getPrototypeOf(value);
|
|
136
|
+
if (proto === Object.prototype || proto === null) {
|
|
137
|
+
return Object.values(value).some(v => valueContainsPlaceholder(v, placeholder));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Expand looped substitution variables in a pattern.
|
|
145
|
+
* Applies cartesian expansion: x values are expanded first, then y, then z.
|
|
146
|
+
* Each variable multiplies the entries — result count = x.length × y.length × z.length.
|
|
147
|
+
*
|
|
148
|
+
* Returns the expanded pattern (or the original if no substitutions apply).
|
|
149
|
+
*/
|
|
150
|
+
function expandSubstitutions(
|
|
151
|
+
pattern: Record<string, any>,
|
|
152
|
+
options: AssignFromOptions
|
|
153
|
+
): Record<string, any> {
|
|
154
|
+
let entries = Object.entries(pattern);
|
|
155
|
+
|
|
156
|
+
for (const { placeholder, optionKey } of SUBSTITUTION_VARS) {
|
|
157
|
+
const values = options[optionKey as keyof AssignFromOptions] as string[] | undefined;
|
|
158
|
+
if (!values || values.length === 0) continue;
|
|
159
|
+
|
|
160
|
+
const expanded: [string, any][] = [];
|
|
161
|
+
for (const [key, value] of entries) {
|
|
162
|
+
if (entryContainsPlaceholder(key, value, placeholder)) {
|
|
163
|
+
// Expand this entry for each value in the variable array
|
|
164
|
+
for (const replacement of values) {
|
|
165
|
+
const newKey = key.includes(placeholder)
|
|
166
|
+
? key.replaceAll(placeholder, replacement)
|
|
167
|
+
: key;
|
|
168
|
+
const newValue = substituteInValue(value, placeholder, replacement);
|
|
169
|
+
expanded.push([newKey, newValue]);
|
|
170
|
+
}
|
|
171
|
+
} else {
|
|
172
|
+
// No placeholder in this entry — pass through
|
|
173
|
+
expanded.push([key, value]);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
entries = expanded;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return mergeHandlerDuplicates(entries);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Convert entries to an object, merging duplicate handler (` =>`) keys into arrays.
|
|
184
|
+
* For normal (non-handler) keys, later entries overwrite earlier ones (standard object behavior).
|
|
185
|
+
* For handler keys, duplicate entries are combined into an array (Multiple Handlers pattern).
|
|
186
|
+
*/
|
|
187
|
+
function mergeHandlerDuplicates(entries: [string, any][]): Record<string, any> {
|
|
188
|
+
const result: Record<string, any> = {};
|
|
189
|
+
for (const [key, value] of entries) {
|
|
190
|
+
if (key.endsWith(' =>') && key in result) {
|
|
191
|
+
// Duplicate handler key — merge into array
|
|
192
|
+
const existing = result[key];
|
|
193
|
+
if (Array.isArray(existing)) {
|
|
194
|
+
existing.push(value);
|
|
195
|
+
} else {
|
|
196
|
+
result[key] = [existing, value];
|
|
197
|
+
}
|
|
198
|
+
} else {
|
|
199
|
+
result[key] = value;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return result;
|
|
28
203
|
}
|
|
29
204
|
|
|
30
205
|
export async function assignFrom(
|
|
@@ -32,20 +207,133 @@ export async function assignFrom(
|
|
|
32
207
|
pattern: Record<string, any>,
|
|
33
208
|
options: AssignFromOptions
|
|
34
209
|
): Promise<any> {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
210
|
+
// First: expand looped substitution variables (${x}, ${y}, ${z})
|
|
211
|
+
const expandedPattern = expandSubstitutions(pattern, options);
|
|
212
|
+
|
|
213
|
+
// Separate handler commands ( =>), #[x] keys, and normal keys
|
|
214
|
+
const handlerKeys: string[] = [];
|
|
215
|
+
const normalPattern: Record<string, any> = {};
|
|
216
|
+
const idRefNormalKeys: string[] = [];
|
|
217
|
+
const idRefHandlerKeys: string[] = [];
|
|
218
|
+
|
|
219
|
+
for (const key of Object.keys(expandedPattern)) {
|
|
220
|
+
if (isHandlerCommand(key)) {
|
|
221
|
+
if (key.startsWith('#[')) {
|
|
222
|
+
idRefHandlerKeys.push(key);
|
|
223
|
+
} else {
|
|
224
|
+
handlerKeys.push(key);
|
|
225
|
+
}
|
|
226
|
+
} else if (key.startsWith('#[')) {
|
|
227
|
+
idRefNormalKeys.push(key);
|
|
228
|
+
} else {
|
|
229
|
+
normalPattern[key] = expandedPattern[key];
|
|
46
230
|
}
|
|
47
|
-
delete resolved['...'];
|
|
48
231
|
}
|
|
49
232
|
|
|
50
|
-
|
|
233
|
+
// Process normal keys via resolveValues + assignGingerly
|
|
234
|
+
if (Object.keys(normalPattern).length > 0) {
|
|
235
|
+
const resolved = await resolveValues(normalPattern, options.from, {
|
|
236
|
+
withMethods: options.withMethods,
|
|
237
|
+
aka: options.aka,
|
|
238
|
+
protocols: options.protocols
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// Recursively handle "..." spread keys at all nesting levels
|
|
242
|
+
handleSpreads(resolved);
|
|
243
|
+
|
|
244
|
+
assignGingerly(target, resolved, options);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Process #[x] normal keys — resolve element, then apply remaining path + value
|
|
248
|
+
if (idRefNormalKeys.length > 0 && options.withIds) {
|
|
249
|
+
const { resolveIdVariable, parseIdRef } = await import('./resolveIdRef.js');
|
|
250
|
+
for (const key of idRefNormalKeys) {
|
|
251
|
+
const parsed = parseIdRef(key);
|
|
252
|
+
if (!parsed) continue;
|
|
253
|
+
|
|
254
|
+
const el = resolveIdVariable(parsed.varName, target, options.withIds);
|
|
255
|
+
if (!el) continue;
|
|
256
|
+
|
|
257
|
+
const value = expandedPattern[key];
|
|
258
|
+
if (parsed.remainingPath) {
|
|
259
|
+
// Resolve the RHS value
|
|
260
|
+
const resolvedValue = await resolveValues(
|
|
261
|
+
{ __v: value }, options.from,
|
|
262
|
+
{ withMethods: options.withMethods, aka: options.aka, protocols: options.protocols }
|
|
263
|
+
);
|
|
264
|
+
// Apply remaining path on the resolved element
|
|
265
|
+
assignGingerly(el, { [parsed.remainingPath]: resolvedValue.__v }, options);
|
|
266
|
+
} else {
|
|
267
|
+
// No remaining path — resolve and assign directly to the element
|
|
268
|
+
const resolvedValue = await resolveValues(
|
|
269
|
+
typeof value === 'object' && value !== null ? value : { __v: value },
|
|
270
|
+
options.from,
|
|
271
|
+
{ withMethods: options.withMethods, aka: options.aka, protocols: options.protocols }
|
|
272
|
+
);
|
|
273
|
+
if ('__v' in resolvedValue) {
|
|
274
|
+
// Single value — can't assign to element root without a path
|
|
275
|
+
} else {
|
|
276
|
+
assignGingerly(el, resolvedValue, options);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Process handler commands ( =>) — dynamically imported only when needed
|
|
283
|
+
if (handlerKeys.length > 0) {
|
|
284
|
+
const { processHandlerCommands } = await import('./processHandlerCommands.js');
|
|
285
|
+
await processHandlerCommands(target, handlerKeys, expandedPattern, options);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Process #[x] handler keys — resolve element, then pass to handler processing
|
|
289
|
+
if (idRefHandlerKeys.length > 0 && options.withIds) {
|
|
290
|
+
const { resolveIdVariable, parseIdRef } = await import('./resolveIdRef.js');
|
|
291
|
+
const { processHandlerCommands } = await import('./processHandlerCommands.js');
|
|
292
|
+
|
|
293
|
+
for (const key of idRefHandlerKeys) {
|
|
294
|
+
const parsed = parseIdRef(key);
|
|
295
|
+
if (!parsed) continue;
|
|
296
|
+
|
|
297
|
+
const el = resolveIdVariable(parsed.varName, target, options.withIds);
|
|
298
|
+
if (!el) continue;
|
|
299
|
+
|
|
300
|
+
// Build a synthetic key for processHandlerCommands:
|
|
301
|
+
// The resolved element becomes the target, remaining path is the LHS
|
|
302
|
+
const syntheticKey = parsed.remainingPath
|
|
303
|
+
? `${parsed.remainingPath} =>`
|
|
304
|
+
: ' =>';
|
|
305
|
+
|
|
306
|
+
const syntheticPattern: Record<string, any> = {
|
|
307
|
+
[syntheticKey]: expandedPattern[key]
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
await processHandlerCommands(el, [syntheticKey], syntheticPattern, options);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return target;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Recursively walk an object and handle "..." spread keys.
|
|
319
|
+
* When a "..." key is found, its value (which should be an object after protocol resolution)
|
|
320
|
+
* is spread into the parent, replacing the "..." entry.
|
|
321
|
+
*/
|
|
322
|
+
function handleSpreads(obj: Record<string, any>): Record<string, any> {
|
|
323
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
324
|
+
if (key !== '...' && typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
325
|
+
const proto = Object.getPrototypeOf(value);
|
|
326
|
+
if (proto === Object.prototype || proto === null) {
|
|
327
|
+
obj[key] = handleSpreads(value);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
if ('...' in obj) {
|
|
332
|
+
const spreadValue = obj['...'];
|
|
333
|
+
delete obj['...'];
|
|
334
|
+
if (spreadValue && typeof spreadValue === 'object') {
|
|
335
|
+
Object.assign(obj, spreadValue);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return obj;
|
|
51
339
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "assign-gingerly",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.54",
|
|
4
4
|
"description": "This package provides a utility function for carefully merging one object into another.",
|
|
5
5
|
"homepage": "https://github.com/bahrus/assign-gingerly#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -60,6 +60,14 @@
|
|
|
60
60
|
"default": "./resolveValues.js",
|
|
61
61
|
"types": "./resolveValues.ts"
|
|
62
62
|
},
|
|
63
|
+
"./resolveIdRef.js": {
|
|
64
|
+
"default": "./resolveIdRef.js",
|
|
65
|
+
"types": "./resolveIdRef.ts"
|
|
66
|
+
},
|
|
67
|
+
"./transitionHelper.js": {
|
|
68
|
+
"default": "./transitionHelper.js",
|
|
69
|
+
"types": "./transitionHelper.ts"
|
|
70
|
+
},
|
|
63
71
|
"./installForwarding.js": {
|
|
64
72
|
"default": "./installForwarding.js",
|
|
65
73
|
"types": "./installForwarding.ts"
|
|
@@ -72,6 +80,26 @@
|
|
|
72
80
|
"default": "./resolveAndAssignFeatures.js",
|
|
73
81
|
"types": "./resolveAndAssignFeatures.ts"
|
|
74
82
|
},
|
|
83
|
+
"./handlers/lazyLoad.js": {
|
|
84
|
+
"default": "./handlers/lazyLoad.js",
|
|
85
|
+
"types": "./handlers/lazyLoad.ts"
|
|
86
|
+
},
|
|
87
|
+
"./handlers/lazyLoadSwitch.js": {
|
|
88
|
+
"default": "./handlers/lazyLoadSwitch.js",
|
|
89
|
+
"types": "./handlers/lazyLoadSwitch.ts"
|
|
90
|
+
},
|
|
91
|
+
"./handlers/join.js": {
|
|
92
|
+
"default": "./handlers/join.js",
|
|
93
|
+
"types": "./handlers/join.ts"
|
|
94
|
+
},
|
|
95
|
+
"./handlers/microDataJoin.js": {
|
|
96
|
+
"default": "./handlers/microDataJoin.js",
|
|
97
|
+
"types": "./handlers/microDataJoin.ts"
|
|
98
|
+
},
|
|
99
|
+
"./paths.js": {
|
|
100
|
+
"default": "./paths.js",
|
|
101
|
+
"types": "./paths.ts"
|
|
102
|
+
},
|
|
75
103
|
"./assignFrom.js": {
|
|
76
104
|
"default": "./assignFrom.js",
|
|
77
105
|
"types": "./assignFrom.ts"
|
|
@@ -95,9 +123,9 @@
|
|
|
95
123
|
"chrome": "npx playwright cr http://localhost:8000"
|
|
96
124
|
},
|
|
97
125
|
"devDependencies": {
|
|
98
|
-
"@playwright/test": "1.
|
|
126
|
+
"@playwright/test": "1.61.1",
|
|
99
127
|
"spa-ssi": "0.0.27",
|
|
100
|
-
"@types/node": "
|
|
128
|
+
"@types/node": "26.1.0",
|
|
101
129
|
"typescript": "6.0.3"
|
|
102
130
|
}
|
|
103
131
|
}
|