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
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve RHS path strings against a source object, then assign the
|
|
3
|
+
* resolved values into a target using assignGingerly.
|
|
4
|
+
*
|
|
5
|
+
* Combines resolveValues + assignGingerly into a single call.
|
|
6
|
+
* Inherits all assignGingerly options (withMethods, aka, signal, etc.).
|
|
7
|
+
*
|
|
8
|
+
* @param target - Object to merge resolved values into
|
|
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
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
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' }
|
|
21
|
+
*/
|
|
22
|
+
import { resolveValues } from './resolveValues.js';
|
|
23
|
+
import assignGingerly, { IAssignGingerlyOptions } from './assignGingerly.js';
|
|
24
|
+
import type { AssignPermissions } from './isAllowedImportPath.js';
|
|
25
|
+
import {
|
|
26
|
+
expandSubstitutions, categorizeKeys, handleSpreads, isHandlerCommand
|
|
27
|
+
} from './assignFrom.js';
|
|
28
|
+
|
|
29
|
+
export interface AssignFromOptions extends IAssignGingerlyOptions {
|
|
30
|
+
/** Source object to resolve RHS path strings against */
|
|
31
|
+
from: any;
|
|
32
|
+
|
|
33
|
+
/** Protocol handlers (sync or async) */
|
|
34
|
+
protocols?: Record<string, (key: string) => any | Promise<any>>;
|
|
35
|
+
|
|
36
|
+
/** Loop variable bindings — expand pattern entries containing ${x} */
|
|
37
|
+
where_x_in?: string[];
|
|
38
|
+
/** Loop variable bindings — expand pattern entries containing ${y} */
|
|
39
|
+
where_y_in?: string[];
|
|
40
|
+
/** Loop variable bindings — expand pattern entries containing ${z} */
|
|
41
|
+
where_z_in?: string[];
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Cached element references by variable name.
|
|
45
|
+
* Used with `#[varName]` syntax in LHS keys for fast repeated element access.
|
|
46
|
+
*
|
|
47
|
+
* - String value: existing element ID (uses getElementById)
|
|
48
|
+
* - Object value: { qry: 'selector' } — finds element via querySelector on target, auto-assigns an ID
|
|
49
|
+
*
|
|
50
|
+
* Elements are cached via WeakRef with getElementById fallback on cache miss.
|
|
51
|
+
*/
|
|
52
|
+
withIds?: Record<string, string | { qry: string }>;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Handler implementations scoped to this call.
|
|
56
|
+
* Key: the `do` name referenced in handler configs.
|
|
57
|
+
* Value: a class constructor, or an import path to dynamically load one.
|
|
58
|
+
*
|
|
59
|
+
* Import paths must be local (relative, absolute, or bare specifier — no cross-domain URLs).
|
|
60
|
+
* The module's default export is checked first; otherwise the first exported class
|
|
61
|
+
* with an `assign` method on its prototype is used.
|
|
62
|
+
*
|
|
63
|
+
* Built-in handlers (builtIns.*) auto-load without needing to be listed here.
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* handlers: {
|
|
67
|
+
* 'my-list': MyListHandler, // class constructor
|
|
68
|
+
* 'my-chart': './handlers/chart.js', // dynamic import path
|
|
69
|
+
* 'vendor-widget': 'some-package/handler.js', // bare specifier (import map)
|
|
70
|
+
* }
|
|
71
|
+
*/
|
|
72
|
+
handlers?: Record<string, AssignFromHandlerConstructor | string>;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Inferred assignments — automatically distribute source values to matching
|
|
76
|
+
* DOM elements based on structural conventions (itemprop, name, etc.).
|
|
77
|
+
*
|
|
78
|
+
* Uses the inferencer submodule to determine the correct property for each
|
|
79
|
+
* matched element (textContent, value, checked, dateTime, ish, etc.).
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* inferredAssignments: {
|
|
83
|
+
* byItemprop: ['user', 'name', 'email'], // or true for all source keys
|
|
84
|
+
* beVigilant: true, // watch for new matching elements (requires signal)
|
|
85
|
+
* }
|
|
86
|
+
*/
|
|
87
|
+
inferredAssignments?: {
|
|
88
|
+
byItemprop?: string[] | true;
|
|
89
|
+
/** Watch for new matching elements via MutationObserver. Requires options.signal for cleanup. */
|
|
90
|
+
beVigilant?: boolean;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Bulk enhancement application via EMC JSON configs.
|
|
95
|
+
* Finds matching elements and spawns enhancements on them.
|
|
96
|
+
*
|
|
97
|
+
* Each entry specifies an EMC JSON path and optionally overrides the matching selector.
|
|
98
|
+
* Enhancements are auto-registered if not already present in the enhancement registry.
|
|
99
|
+
*
|
|
100
|
+
* No scope perimeter is applied — use mount-observer for reactive/scoped enhancement.
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* enhance: [
|
|
104
|
+
* { emc: 'be-bound/emc.json', matching: '[name]' },
|
|
105
|
+
* { emc: 'be-observant/emc.json', matching: '[itemprop]' },
|
|
106
|
+
* ]
|
|
107
|
+
*/
|
|
108
|
+
enhance?: Array<{ emc: string; matching?: string; parse?: boolean }>;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Interface for assignFrom handler classes.
|
|
113
|
+
* Handlers are invoked when a LHS key ends with ' =>'.
|
|
114
|
+
*/
|
|
115
|
+
export interface AssignFromHandler {
|
|
116
|
+
assign(lhsTarget: any, resolvedParams: Record<string, any>, options: AssignFromOptions): Promise<void> | void;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface AssignFromHandlerConstructor {
|
|
120
|
+
new (config: any): AssignFromHandler;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Module cache for processHandlerCommands — avoids await on dynamic import after first call
|
|
124
|
+
let _processHandlerCommands: any;
|
|
125
|
+
|
|
126
|
+
export async function assignFromAsync(
|
|
127
|
+
target: any,
|
|
128
|
+
pattern: Record<string, any>,
|
|
129
|
+
options: AssignFromOptions,
|
|
130
|
+
permissions?: AssignPermissions
|
|
131
|
+
): Promise<any> {
|
|
132
|
+
// First: expand looped substitution variables (${x}, ${y}, ${z})
|
|
133
|
+
const expandedPattern = expandSubstitutions(pattern, options);
|
|
134
|
+
|
|
135
|
+
// Categorize keys
|
|
136
|
+
const { handlerKeys, normalPattern, idRefNormalKeys, idRefHandlerKeys } = categorizeKeys(expandedPattern);
|
|
137
|
+
|
|
138
|
+
// Process normal keys via resolveValues + assignGingerly
|
|
139
|
+
if (Object.keys(normalPattern).length > 0) {
|
|
140
|
+
const resolved = await resolveValues(normalPattern, options.from, {
|
|
141
|
+
withMethods: options.withMethods,
|
|
142
|
+
aka: options.aka,
|
|
143
|
+
protocols: options.protocols
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Recursively handle "..." spread keys at all nesting levels
|
|
147
|
+
handleSpreads(resolved);
|
|
148
|
+
|
|
149
|
+
assignGingerly(target, resolved, options);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Process #[x] normal keys — resolve element, then apply remaining path + value
|
|
153
|
+
if (idRefNormalKeys.length > 0 && options.withIds) {
|
|
154
|
+
const { resolveIdVariable, parseIdRef } = await import('./resolveIdRef.js');
|
|
155
|
+
for (const key of idRefNormalKeys) {
|
|
156
|
+
const parsed = parseIdRef(key);
|
|
157
|
+
if (!parsed) continue;
|
|
158
|
+
|
|
159
|
+
const el = resolveIdVariable(parsed.varName, target, options.withIds);
|
|
160
|
+
if (!el) continue;
|
|
161
|
+
|
|
162
|
+
const value = expandedPattern[key];
|
|
163
|
+
if (parsed.remainingPath) {
|
|
164
|
+
// Resolve the RHS value
|
|
165
|
+
const resolvedValue = await resolveValues(
|
|
166
|
+
{ __v: value }, options.from,
|
|
167
|
+
{ withMethods: options.withMethods, aka: options.aka, protocols: options.protocols }
|
|
168
|
+
);
|
|
169
|
+
// Apply remaining path on the resolved element
|
|
170
|
+
assignGingerly(el, { [parsed.remainingPath]: resolvedValue.__v }, options);
|
|
171
|
+
} else {
|
|
172
|
+
// No remaining path — resolve and assign directly to the element
|
|
173
|
+
const resolvedValue = await resolveValues(
|
|
174
|
+
typeof value === 'object' && value !== null ? value : { __v: value },
|
|
175
|
+
options.from,
|
|
176
|
+
{ withMethods: options.withMethods, aka: options.aka, protocols: options.protocols }
|
|
177
|
+
);
|
|
178
|
+
if ('__v' in resolvedValue) {
|
|
179
|
+
// Single value — can't assign to element root without a path
|
|
180
|
+
} else {
|
|
181
|
+
assignGingerly(el, resolvedValue, options);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
// Process handler commands ( =>) — cached after first load to avoid await overhead
|
|
187
|
+
if (handlerKeys.length > 0) {
|
|
188
|
+
|
|
189
|
+
_processHandlerCommands ??= (await import('./processHandlerCommands.js')).processHandlerCommands;
|
|
190
|
+
await _processHandlerCommands(target, handlerKeys, expandedPattern, options, permissions);
|
|
191
|
+
}
|
|
192
|
+
// Process #[x] handler keys — resolve element, then pass to handler processing
|
|
193
|
+
if (idRefHandlerKeys.length > 0 && options.withIds) {
|
|
194
|
+
const { resolveIdVariable, parseIdRef } = await import('./resolveIdRef.js');
|
|
195
|
+
_processHandlerCommands ??= (await import('./processHandlerCommands.js')).processHandlerCommands;
|
|
196
|
+
|
|
197
|
+
for (const key of idRefHandlerKeys) {
|
|
198
|
+
const parsed = parseIdRef(key);
|
|
199
|
+
if (!parsed) continue;
|
|
200
|
+
|
|
201
|
+
const el = resolveIdVariable(parsed.varName, target, options.withIds);
|
|
202
|
+
if (!el) continue;
|
|
203
|
+
|
|
204
|
+
// Build a synthetic key for processHandlerCommands:
|
|
205
|
+
// The resolved element becomes the target, remaining path is the LHS
|
|
206
|
+
const syntheticKey = parsed.remainingPath
|
|
207
|
+
? `${parsed.remainingPath} =>`
|
|
208
|
+
: ' =>';
|
|
209
|
+
|
|
210
|
+
const syntheticPattern: Record<string, any> = {
|
|
211
|
+
[syntheticKey]: expandedPattern[key]
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
await _processHandlerCommands(el, [syntheticKey], syntheticPattern, options, permissions);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Process inferred assignments — dynamically imported only when option is present
|
|
219
|
+
if (options.inferredAssignments) {
|
|
220
|
+
const { processInferredAssignments } = await import('./inferredAssignments.js');
|
|
221
|
+
await processInferredAssignments(target, options.from, options.inferredAssignments);
|
|
222
|
+
|
|
223
|
+
// Set up MutationObserver for new matching elements if beVigilant
|
|
224
|
+
if (options.inferredAssignments.beVigilant) {
|
|
225
|
+
if (!options.signal) {
|
|
226
|
+
throw new Error('assignFrom: inferredAssignments.beVigilant requires options.signal (AbortSignal) for cleanup');
|
|
227
|
+
}
|
|
228
|
+
const { setupVigilantObserver } = await import('./beVigilant.js');
|
|
229
|
+
setupVigilantObserver(target, options.from, options.inferredAssignments, options.signal);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Process bulk enhancements — dynamically imported only when option is present
|
|
234
|
+
if (options.enhance && options.enhance.length > 0) {
|
|
235
|
+
const { enhanceAll } = await import('./enhanceAll.js');
|
|
236
|
+
await enhanceAll(target, options.enhance, permissions);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return target;
|
|
240
|
+
}
|
package/getValues.js
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* getValues.ts — Synchronous value resolution for path strings.
|
|
3
|
+
*
|
|
4
|
+
* The synchronous counterpart to resolveValues. Resolves `?.`-prefixed path
|
|
5
|
+
* strings against a source object, with support for withMethods, aka aliases,
|
|
6
|
+
* synchronous protocols, arrays, and nested plain objects.
|
|
7
|
+
*
|
|
8
|
+
* For async protocol handlers, use resolveValues instead.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* import { getValues, getValue } from 'assign-gingerly/getValues.js';
|
|
12
|
+
*
|
|
13
|
+
* const result = getValues({
|
|
14
|
+
* name: '?.user?.name',
|
|
15
|
+
* greeting: '?.messages?.hello',
|
|
16
|
+
* count: 42
|
|
17
|
+
* }, source, { withMethods: ['querySelector'], aka: { q: 'querySelector' } });
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Apply alias substitutions to a path string.
|
|
21
|
+
* Replaces complete tokens between `?.` delimiters with their aliased values.
|
|
22
|
+
*/
|
|
23
|
+
function applyAliases(path, aliasMap) {
|
|
24
|
+
if (aliasMap.size === 0)
|
|
25
|
+
return path;
|
|
26
|
+
const parts = path.split('?.');
|
|
27
|
+
const substituted = parts.map(part => aliasMap.get(part) ?? part);
|
|
28
|
+
return substituted.join('?.');
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Path cache for parsed path strings.
|
|
32
|
+
* Avoids re-splitting the same path on repeated calls.
|
|
33
|
+
*/
|
|
34
|
+
const pathCache = new Map();
|
|
35
|
+
/**
|
|
36
|
+
* Parse a `?.`-delimited path string into segments, with caching.
|
|
37
|
+
*/
|
|
38
|
+
function parseCachedPath(path) {
|
|
39
|
+
let parts = pathCache.get(path);
|
|
40
|
+
if (!parts) {
|
|
41
|
+
parts = path.split('?.').filter(p => p.length > 0);
|
|
42
|
+
pathCache.set(path, parts);
|
|
43
|
+
}
|
|
44
|
+
return parts;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Navigate a path against a source object, optionally calling methods.
|
|
48
|
+
* Returns the resolved value at the end of the path.
|
|
49
|
+
*/
|
|
50
|
+
function navigatePath(source, parts, withMethods) {
|
|
51
|
+
let current = source;
|
|
52
|
+
let i = 0;
|
|
53
|
+
while (i < parts.length) {
|
|
54
|
+
if (current == null)
|
|
55
|
+
return current;
|
|
56
|
+
const part = parts[i];
|
|
57
|
+
if (withMethods && withMethods.has(part)) {
|
|
58
|
+
const method = current[part];
|
|
59
|
+
if (typeof method === 'function') {
|
|
60
|
+
const nextPart = parts[i + 1];
|
|
61
|
+
if (nextPart !== undefined && !(withMethods.has(nextPart))) {
|
|
62
|
+
current = method.call(current, nextPart);
|
|
63
|
+
i += 2;
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
current = method.call(current);
|
|
67
|
+
i++;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
current = current[part];
|
|
72
|
+
i++;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
current = current[part];
|
|
77
|
+
i++;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return current;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Checks if a string value looks like a protocol reference.
|
|
84
|
+
*/
|
|
85
|
+
function hasProtocol(value) {
|
|
86
|
+
return value.includes('://');
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Resolve a protocol-prefixed value synchronously.
|
|
90
|
+
*/
|
|
91
|
+
function getProtocolValue(value, protocols, options) {
|
|
92
|
+
const protoEnd = value.indexOf('://');
|
|
93
|
+
const protocol = value.substring(0, protoEnd);
|
|
94
|
+
const handler = protocols[protocol];
|
|
95
|
+
if (!handler)
|
|
96
|
+
return value; // not a recognized protocol
|
|
97
|
+
const rest = value.substring(protoEnd + 3);
|
|
98
|
+
const pathStart = rest.indexOf('?.');
|
|
99
|
+
const key = pathStart === -1 ? rest : rest.substring(0, pathStart);
|
|
100
|
+
const path = pathStart === -1 ? null : rest.substring(pathStart);
|
|
101
|
+
const resolved = handler(key);
|
|
102
|
+
if (path) {
|
|
103
|
+
return getValue(path, resolved, options);
|
|
104
|
+
}
|
|
105
|
+
return resolved;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Resolve path strings and protocols within an array (synchronous).
|
|
109
|
+
* Recurses into nested arrays and plain objects.
|
|
110
|
+
*/
|
|
111
|
+
function getArray(arr, source, aliasMap, withMethods, protocols, options) {
|
|
112
|
+
const result = [];
|
|
113
|
+
for (const item of arr) {
|
|
114
|
+
if (typeof item === 'string' && item.startsWith('?.')) {
|
|
115
|
+
const aliased = applyAliases(item, aliasMap);
|
|
116
|
+
const parts = parseCachedPath(aliased);
|
|
117
|
+
result.push(parts.length === 0 ? source : navigatePath(source, parts, withMethods));
|
|
118
|
+
}
|
|
119
|
+
else if (typeof item === 'string' && protocols && hasProtocol(item)) {
|
|
120
|
+
result.push(getProtocolValue(item, protocols, options));
|
|
121
|
+
}
|
|
122
|
+
else if (Array.isArray(item)) {
|
|
123
|
+
result.push(getArray(item, source, aliasMap, withMethods, protocols, options));
|
|
124
|
+
}
|
|
125
|
+
else if (item && typeof item === 'object') {
|
|
126
|
+
const proto = Object.getPrototypeOf(item);
|
|
127
|
+
if (proto === Object.prototype || proto === null) {
|
|
128
|
+
result.push(getValues(item, source, options));
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
result.push(item);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
result.push(item);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return result;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Synchronously resolve RHS path strings in a pattern object against a source object.
|
|
142
|
+
*
|
|
143
|
+
* Any value that is a string starting with `?.` is treated as a path
|
|
144
|
+
* and resolved against the source object. Non-string values and strings
|
|
145
|
+
* not starting with `?.` pass through unchanged.
|
|
146
|
+
*
|
|
147
|
+
* @param pattern - Object whose RHS values may contain `?.` path strings
|
|
148
|
+
* @param source - Object to resolve paths against
|
|
149
|
+
* @param options - Optional withMethods, aka, and synchronous protocols
|
|
150
|
+
* @returns New object with path strings replaced by resolved values
|
|
151
|
+
*/
|
|
152
|
+
export function getValues(pattern, source, options) {
|
|
153
|
+
// Build alias map
|
|
154
|
+
const aliasMap = new Map();
|
|
155
|
+
if (options?.aka) {
|
|
156
|
+
for (const [alias, target] of Object.entries(options.aka)) {
|
|
157
|
+
aliasMap.set(alias, target);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// Build methods set
|
|
161
|
+
const withMethods = options?.withMethods
|
|
162
|
+
? options.withMethods instanceof Set
|
|
163
|
+
? options.withMethods
|
|
164
|
+
: new Set(options.withMethods)
|
|
165
|
+
: undefined;
|
|
166
|
+
const protocols = options?.protocols;
|
|
167
|
+
const result = {};
|
|
168
|
+
for (const [key, value] of Object.entries(pattern)) {
|
|
169
|
+
if (typeof value === 'string' && value.startsWith('?.')) {
|
|
170
|
+
const aliased = applyAliases(value, aliasMap);
|
|
171
|
+
const parts = parseCachedPath(aliased);
|
|
172
|
+
result[key] = parts.length === 0 ? source : navigatePath(source, parts, withMethods);
|
|
173
|
+
}
|
|
174
|
+
else if (typeof value === 'string' && protocols && hasProtocol(value)) {
|
|
175
|
+
result[key] = getProtocolValue(value, protocols, options);
|
|
176
|
+
}
|
|
177
|
+
else if (Array.isArray(value)) {
|
|
178
|
+
result[key] = getArray(value, source, aliasMap, withMethods, protocols, options);
|
|
179
|
+
}
|
|
180
|
+
else if (typeof value === 'object' && value !== null) {
|
|
181
|
+
const proto = Object.getPrototypeOf(value);
|
|
182
|
+
if (proto === Object.prototype || proto === null) {
|
|
183
|
+
result[key] = getValues(value, source, options);
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
result[key] = value;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
result[key] = value;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return result;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Synchronously resolve a single `?.`-delimited path string against a source object.
|
|
197
|
+
*
|
|
198
|
+
* @param path - A `?.`-delimited path string (e.g., '?.user?.name')
|
|
199
|
+
* @param source - Object to resolve the path against
|
|
200
|
+
* @param options - Optional withMethods and aka
|
|
201
|
+
* @returns The resolved value, or undefined if any segment is nullish
|
|
202
|
+
*/
|
|
203
|
+
export function getValue(path, source, options) {
|
|
204
|
+
if (!path.startsWith('?.'))
|
|
205
|
+
return path;
|
|
206
|
+
let aliased = path;
|
|
207
|
+
if (options?.aka) {
|
|
208
|
+
const aliasMap = new Map();
|
|
209
|
+
for (const [alias, target] of Object.entries(options.aka)) {
|
|
210
|
+
aliasMap.set(alias, target);
|
|
211
|
+
}
|
|
212
|
+
aliased = applyAliases(path, aliasMap);
|
|
213
|
+
}
|
|
214
|
+
const parts = parseCachedPath(aliased);
|
|
215
|
+
if (parts.length === 0)
|
|
216
|
+
return source;
|
|
217
|
+
const withMethods = options?.withMethods
|
|
218
|
+
? options.withMethods instanceof Set
|
|
219
|
+
? options.withMethods
|
|
220
|
+
: new Set(options.withMethods)
|
|
221
|
+
: undefined;
|
|
222
|
+
return navigatePath(source, parts, withMethods);
|
|
223
|
+
}
|