assign-gingerly 0.0.74 → 0.0.76

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.
Files changed (62) hide show
  1. package/DX/installForwarding.js +1 -1
  2. package/DX/installForwarding.ts +1 -1
  3. package/README.md +2 -0
  4. package/assignFrom.js +18 -17
  5. package/assignFrom.ts +23 -23
  6. package/assignFromAsync.js +14 -13
  7. package/assignFromAsync.ts +15 -15
  8. package/assignGingerly.js +46 -42
  9. package/assignGingerly.ts +131 -124
  10. package/assignPermissions/PermissionProcessor.js +166 -0
  11. package/assignPermissions/PermissionProcessor.ts +210 -0
  12. package/assignPermissions/isAllowedImportPath.js +2 -6
  13. package/assignPermissions/isAllowedImportPath.ts +3 -5
  14. package/assignPermissions/isAllowedUrl.js +18 -0
  15. package/assignPermissions/isAllowedUrl.ts +15 -0
  16. package/assignTentatively.js +9 -11
  17. package/assignTentatively.ts +11 -13
  18. package/beVigilant.js +1 -1
  19. package/beVigilant.ts +1 -1
  20. package/buildCSSQuery.js +1 -1
  21. package/buildCSSQuery.ts +1 -1
  22. package/eachTime.js +5 -6
  23. package/eachTime.ts +12 -15
  24. package/enhanceAll.js +3 -3
  25. package/enhanceAll.ts +4 -4
  26. package/evaluatePathWithAsyncMethods.js +12 -8
  27. package/evaluatePathWithAsyncMethods.ts +129 -117
  28. package/handlers/addEventListener.js +11 -11
  29. package/handlers/addEventListener.ts +12 -13
  30. package/handlers/lazyLoad.ts +7 -7
  31. package/handlers/lazyLoadSwitch.ts +3 -3
  32. package/handlers/manageTemplateList.js +10 -10
  33. package/handlers/manageTemplateList.ts +12 -12
  34. package/handlers/rangeSelector.ts +3 -3
  35. package/index.js +2 -3
  36. package/index.ts +2 -3
  37. package/inferencer/types/assign-gingerly/types.d.ts +37 -23
  38. package/inferencer/types/el-maker/types.d.ts +33 -0
  39. package/inferencer/types/scratch-box/types.d.ts +3 -0
  40. package/inferencer/types/truth-sourcer/types.d.ts +4 -0
  41. package/package.json +25 -23
  42. package/parseWithAttrs.js +1 -1
  43. package/parseWithAttrs.ts +1 -1
  44. package/processHandlerCommands.js +5 -7
  45. package/processHandlerCommands.ts +12 -15
  46. package/{getValues.js → resolve/getValues.js} +19 -8
  47. package/{getValues.ts → resolve/getValues.ts} +331 -314
  48. package/{resolveIdRef.js → resolve/resolveIdRef.js} +1 -1
  49. package/{resolveIdRef.ts → resolve/resolveIdRef.ts} +1 -1
  50. package/{resolveValues.ts → resolve/resolveValues.ts} +1 -1
  51. package/types/assign-gingerly/types.d.ts +37 -8
  52. package/utils/findClassPrototypeInPath.js +57 -0
  53. package/utils/findClassPrototypeInPath.ts +62 -0
  54. package/utils/isAsyncSpawn.js +20 -0
  55. package/utils/isAsyncSpawn.ts +17 -0
  56. package/assignPermissions/restrictedProps.js +0 -43
  57. package/assignPermissions/restrictedProps.ts +0 -53
  58. package/resolveAndAssignFeatures.js +0 -36
  59. package/resolveAndAssignFeatures.ts +0 -43
  60. /package/{resolveTemplate.js → resolve/resolveTemplate.js} +0 -0
  61. /package/{resolveTemplate.ts → resolve/resolveTemplate.ts} +0 -0
  62. /package/{resolveValues.js → resolve/resolveValues.js} +0 -0
@@ -1,314 +1,331 @@
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
- import type { GetValuesOptions } from './types/assign-gingerly/types.js';
21
-
22
- export function normalizeAliasOptions(options?: {
23
- aka?: Record<string, string>;
24
- akaMethods?: Record<string, string>;
25
- withMethods?: string[] | Set<string>;
26
- }): { aliasMap: Map<string, string>; withMethods: Set<string> | undefined } {
27
- const aliasMap = new Map<string, string>();
28
-
29
- if (options?.aka) {
30
- for (const [alias, target] of Object.entries(options.aka)) {
31
- if (alias.includes(' ') || alias.includes('`')) {
32
- throw new Error(`Invalid alias '${alias}': aliases cannot contain space or backtick characters`);
33
- }
34
- aliasMap.set(alias, target);
35
- }
36
- }
37
-
38
- if (options?.akaMethods) {
39
- for (const [alias, target] of Object.entries(options.akaMethods)) {
40
- if (alias.includes(' ') || alias.includes('`')) {
41
- throw new Error(`Invalid alias '${alias}': aliases cannot contain space or backtick characters`);
42
- }
43
- aliasMap.set(alias, target);
44
- }
45
- }
46
-
47
- const withMethods = options?.withMethods
48
- ? options.withMethods instanceof Set
49
- ? new Set(options.withMethods)
50
- : new Set(options.withMethods)
51
- : options?.akaMethods
52
- ? new Set<string>()
53
- : undefined;
54
-
55
- if (options?.akaMethods) {
56
- for (const target of Object.values(options.akaMethods)) {
57
- withMethods?.add(target);
58
- }
59
- }
60
-
61
- return { aliasMap, withMethods };
62
- }
63
-
64
- /**
65
- * Apply alias substitutions to a path string.
66
- * Replaces complete tokens between `?.` delimiters with their aliased values.
67
- */
68
- function applyAliases(path: string, aliasMap: Map<string, string>): string {
69
- if (aliasMap.size === 0) return path;
70
- const parts = path.split('?.');
71
- const substituted = parts.map(part => aliasMap.get(part) ?? part);
72
- return substituted.join('?.');
73
- }
74
-
75
- /**
76
- * Resolve a special root-reference token at the start of a string.
77
- * '$0' refers to the first argument passed to assignFrom / resolveValues.
78
- */
79
- function resolveRootReference(path: string, source: any, root: any): { source: any; path: string } | null {
80
- if (path === '$0') return { source: root ?? source, path: '' };
81
- if (path.startsWith('$0?.')) return { source: root ?? source, path: path.substring(4) };
82
- return null;
83
- }
84
-
85
- /**
86
- * Path cache for parsed path strings.
87
- * Avoids re-splitting the same path on repeated calls.
88
- */
89
- const pathCache = new Map<string, string[]>();
90
-
91
- /**
92
- * Parse a `?.`-delimited path string into segments, with caching.
93
- */
94
- function parseCachedPath(path: string): string[] {
95
- let parts = pathCache.get(path);
96
- if (!parts) {
97
- parts = path.split('?.').filter(p => p.length > 0);
98
- pathCache.set(path, parts);
99
- }
100
- return parts;
101
- }
102
-
103
- /**
104
- * Navigate a path against a source object, optionally calling methods.
105
- * Returns the resolved value at the end of the path.
106
- */
107
- function navigatePath(
108
- source: any,
109
- parts: string[],
110
- withMethods: Set<string> | undefined
111
- ): any {
112
- let current = source;
113
- let i = 0;
114
-
115
- while (i < parts.length) {
116
- if (current == null) return current;
117
-
118
- const part = parts[i];
119
-
120
- if (withMethods && withMethods.has(part)) {
121
- const method = current[part];
122
- if (typeof method === 'function') {
123
- const nextPart = parts[i + 1];
124
- if (nextPart !== undefined && !(withMethods.has(nextPart))) {
125
- current = method.call(current, nextPart);
126
- i += 2;
127
- } else {
128
- current = method.call(current);
129
- i++;
130
- }
131
- } else {
132
- current = current[part];
133
- i++;
134
- }
135
- } else {
136
- current = current[part];
137
- i++;
138
- }
139
- }
140
-
141
- return current;
142
- }
143
-
144
- /**
145
- * Checks if a string value looks like a protocol reference.
146
- */
147
- function hasProtocol(value: string): boolean {
148
- return value.includes('://');
149
- }
150
-
151
- /**
152
- * Resolve a protocol-prefixed value synchronously.
153
- */
154
- function getProtocolValue(
155
- value: string,
156
- protocols: Record<string, (key: string) => any>,
157
- options?: GetValuesOptions
158
- ): any {
159
- const protoEnd = value.indexOf('://');
160
- const protocol = value.substring(0, protoEnd);
161
-
162
- const handler = protocols[protocol];
163
- if (!handler) return value; // not a recognized protocol
164
-
165
- const rest = value.substring(protoEnd + 3);
166
-
167
- const pathStart = rest.indexOf('?.');
168
- const key = pathStart === -1 ? rest : rest.substring(0, pathStart);
169
- const path = pathStart === -1 ? null : rest.substring(pathStart);
170
-
171
- const resolved = handler(key);
172
-
173
- if (path) {
174
- return getValue(path, resolved, options);
175
- }
176
- return resolved;
177
- }
178
-
179
- /**
180
- * Resolve path strings and protocols within an array (synchronous).
181
- * Recurses into nested arrays and plain objects.
182
- */
183
- function getArray(
184
- arr: any[],
185
- source: any,
186
- aliasMap: Map<string, string>,
187
- withMethods: Set<string> | undefined,
188
- protocols: Record<string, (key: string) => any> | undefined,
189
- options?: GetValuesOptions
190
- ): any[] {
191
- const result: any[] = [];
192
- for (const item of arr) {
193
- if (typeof item === 'string' && item.startsWith('?.')) {
194
- const aliased = applyAliases(item, aliasMap);
195
- const parts = parseCachedPath(aliased);
196
- result.push(parts.length === 0 ? source : navigatePath(source, parts, withMethods));
197
- } else if (typeof item === 'string' && item.startsWith('$0')) {
198
- const rootRef = resolveRootReference(item, source, options?.root);
199
- if (rootRef === null) {
200
- result.push(source);
201
- } else {
202
- const aliased = applyAliases(rootRef.path, aliasMap);
203
- const normalizedPath = aliased.startsWith('?.') ? aliased : (aliased ? `?.${aliased}` : '?.');
204
- const parts = parseCachedPath(normalizedPath);
205
- result.push(parts.length === 0 ? rootRef.source : navigatePath(rootRef.source, parts, withMethods));
206
- }
207
- } else if (typeof item === 'string' && protocols && hasProtocol(item)) {
208
- result.push(getProtocolValue(item, protocols, options));
209
- } else if (Array.isArray(item)) {
210
- result.push(getArray(item, source, aliasMap, withMethods, protocols, options));
211
- } else if (item && typeof item === 'object') {
212
- const proto = Object.getPrototypeOf(item);
213
- if (proto === Object.prototype || proto === null) {
214
- result.push(getValues(item, source, options));
215
- } else {
216
- result.push(item);
217
- }
218
- } else {
219
- result.push(item);
220
- }
221
- }
222
- return result;
223
- }
224
-
225
- /**
226
- * Synchronously resolve RHS path strings in a pattern object against a source object.
227
- *
228
- * Any value that is a string starting with `?.` is treated as a path
229
- * and resolved against the source object. Non-string values and strings
230
- * not starting with `?.` pass through unchanged.
231
- *
232
- * @param pattern - Object whose RHS values may contain `?.` path strings
233
- * @param source - Object to resolve paths against
234
- * @param options - Optional withMethods, aka, and synchronous protocols
235
- * @returns New object with path strings replaced by resolved values
236
- */
237
- export function getValues(
238
- pattern: Record<string, any>,
239
- source: any,
240
- options?: GetValuesOptions
241
- ): Record<string, any> {
242
- const { aliasMap, withMethods } = normalizeAliasOptions(options);
243
-
244
- const protocols = options?.protocols;
245
-
246
- const result: Record<string, any> = {};
247
- for (const [key, value] of Object.entries(pattern)) {
248
- if (typeof value === 'string' && value.startsWith('?.')) {
249
- const aliased = applyAliases(value, aliasMap);
250
- const parts = parseCachedPath(aliased);
251
- result[key] = parts.length === 0 ? source : navigatePath(source, parts, withMethods);
252
- } else if (typeof value === 'string' && value.startsWith('$0')) {
253
- const rootRef = resolveRootReference(value, source, options?.root);
254
- if (rootRef === null) {
255
- result[key] = source;
256
- } else {
257
- const aliased = applyAliases(rootRef.path, aliasMap);
258
- const normalizedPath = aliased.startsWith('?.') ? aliased : (aliased ? `?.${aliased}` : '?.');
259
- const parts = parseCachedPath(normalizedPath);
260
- result[key] = parts.length === 0 ? rootRef.source : navigatePath(rootRef.source, parts, withMethods);
261
- }
262
- } else if (typeof value === 'string' && protocols && hasProtocol(value)) {
263
- result[key] = getProtocolValue(value, protocols, options);
264
- } else if (Array.isArray(value)) {
265
- result[key] = getArray(value, source, aliasMap, withMethods, protocols, options);
266
- } else if (typeof value === 'object' && value !== null) {
267
- const proto = Object.getPrototypeOf(value);
268
- if (proto === Object.prototype || proto === null) {
269
- result[key] = getValues(value, source, options);
270
- } else {
271
- result[key] = value;
272
- }
273
- } else {
274
- result[key] = value;
275
- }
276
- }
277
- return result;
278
- }
279
-
280
- /**
281
- * Synchronously resolve a single `?.`-delimited path string against a source object.
282
- *
283
- * @param path - A `?.`-delimited path string (e.g., '?.user?.name')
284
- * @param source - Object to resolve the path against
285
- * @param options - Optional withMethods and aka
286
- * @returns The resolved value, or undefined if any segment is nullish
287
- */
288
- export function getValue(
289
- path: string,
290
- source: any,
291
- options?: GetValuesOptions
292
- ): any {
293
- const rootRef = resolveRootReference(path, source, options?.root);
294
- if (rootRef) {
295
- path = rootRef.path;
296
- source = rootRef.source;
297
- } else if (!path.startsWith('?.')) {
298
- return path;
299
- }
300
-
301
- let aliased = path;
302
- const { aliasMap } = normalizeAliasOptions(options);
303
- if (aliasMap.size > 0) {
304
- aliased = applyAliases(path, aliasMap);
305
- }
306
-
307
- const normalizedPath = aliased.startsWith('?.') ? aliased : (aliased ? `?.${aliased}` : '?.');
308
- const parts = parseCachedPath(normalizedPath);
309
- if (parts.length === 0) return source;
310
-
311
- const { withMethods } = normalizeAliasOptions(options);
312
-
313
- return navigatePath(source, parts, withMethods);
314
- }
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
+ import type { GetValuesOptions, PermissionProcessor } from '../types/assign-gingerly/types.js';
21
+
22
+ export function normalizeAliasOptions(options?: {
23
+ aka?: Record<string, string>;
24
+ akaMethods?: Record<string, string>;
25
+ withMethods?: string[] | Set<string>;
26
+ }): { aliasMap: Map<string, string>; withMethods: Set<string> | undefined } {
27
+ const aliasMap = new Map<string, string>();
28
+
29
+ if (options?.aka) {
30
+ for (const [alias, target] of Object.entries(options.aka)) {
31
+ if (alias.includes(' ') || alias.includes('`')) {
32
+ throw new Error(`Invalid alias '${alias}': aliases cannot contain space or backtick characters`);
33
+ }
34
+ aliasMap.set(alias, target);
35
+ }
36
+ }
37
+
38
+ if (options?.akaMethods) {
39
+ for (const [alias, target] of Object.entries(options.akaMethods)) {
40
+ if (alias.includes(' ') || alias.includes('`')) {
41
+ throw new Error(`Invalid alias '${alias}': aliases cannot contain space or backtick characters`);
42
+ }
43
+ aliasMap.set(alias, target);
44
+ }
45
+ }
46
+
47
+ const withMethods = options?.withMethods
48
+ ? options.withMethods instanceof Set
49
+ ? new Set(options.withMethods)
50
+ : new Set(options.withMethods)
51
+ : options?.akaMethods
52
+ ? new Set<string>()
53
+ : undefined;
54
+
55
+ if (options?.akaMethods) {
56
+ for (const target of Object.values(options.akaMethods)) {
57
+ withMethods?.add(target);
58
+ }
59
+ }
60
+
61
+ return { aliasMap, withMethods };
62
+ }
63
+
64
+ /**
65
+ * Check whether a method name is listed in withMethods and is not restricted
66
+ * by the permission processor. Restricted methods are treated as non-method
67
+ * property names so they fall through to normal access.
68
+ */
69
+ function isAllowedMethod(
70
+ methodName: string,
71
+ withMethods: Set<string> | undefined,
72
+ permissionProcessor?: PermissionProcessor
73
+ ): boolean {
74
+ return !!withMethods && withMethods.has(methodName) && !permissionProcessor?.checkRestrictedMethod(methodName);
75
+ }
76
+
77
+ /**
78
+ * Apply alias substitutions to a path string.
79
+ * Replaces complete tokens between `?.` delimiters with their aliased values.
80
+ */
81
+ function applyAliases(path: string, aliasMap: Map<string, string>): string {
82
+ if (aliasMap.size === 0) return path;
83
+ const parts = path.split('?.');
84
+ const substituted = parts.map(part => aliasMap.get(part) ?? part);
85
+ return substituted.join('?.');
86
+ }
87
+
88
+ /**
89
+ * Resolve a special root-reference token at the start of a string.
90
+ * '$0' refers to the first argument passed to assignFrom / resolveValues.
91
+ */
92
+ function resolveRootReference(path: string, source: any, root: any): { source: any; path: string } | null {
93
+ if (path === '$0') return { source: root ?? source, path: '' };
94
+ if (path.startsWith('$0?.')) return { source: root ?? source, path: path.substring(4) };
95
+ return null;
96
+ }
97
+
98
+ /**
99
+ * Path cache for parsed path strings.
100
+ * Avoids re-splitting the same path on repeated calls.
101
+ */
102
+ const pathCache = new Map<string, string[]>();
103
+
104
+ /**
105
+ * Parse a `?.`-delimited path string into segments, with caching.
106
+ */
107
+ function parseCachedPath(path: string): string[] {
108
+ let parts = pathCache.get(path);
109
+ if (!parts) {
110
+ parts = path.split('?.').filter(p => p.length > 0);
111
+ pathCache.set(path, parts);
112
+ }
113
+ return parts;
114
+ }
115
+
116
+ /**
117
+ * Navigate a path against a source object, optionally calling methods.
118
+ * Returns the resolved value at the end of the path.
119
+ */
120
+ function navigatePath(
121
+ source: any,
122
+ parts: string[],
123
+ withMethods: Set<string> | undefined,
124
+ permissionProcessor?: PermissionProcessor
125
+ ): any {
126
+ let current = source;
127
+ let i = 0;
128
+
129
+ while (i < parts.length) {
130
+ if (current == null) return current;
131
+
132
+ const part = parts[i];
133
+
134
+ if (isAllowedMethod(part, withMethods, permissionProcessor)) {
135
+ const method = current[part];
136
+ if (typeof method === 'function') {
137
+ const nextPart = parts[i + 1];
138
+ if (nextPart !== undefined && !isAllowedMethod(nextPart, withMethods, permissionProcessor)) {
139
+ current = method.call(current, nextPart);
140
+ i += 2;
141
+ } else {
142
+ current = method.call(current);
143
+ i++;
144
+ }
145
+ } else {
146
+ current = current[part];
147
+ i++;
148
+ }
149
+ } else {
150
+ current = current[part];
151
+ i++;
152
+ }
153
+ }
154
+
155
+ return current;
156
+ }
157
+
158
+ /**
159
+ * Checks if a string value looks like a protocol reference.
160
+ */
161
+ function hasProtocol(value: string): boolean {
162
+ return value.includes('://');
163
+ }
164
+
165
+ /**
166
+ * Resolve a protocol-prefixed value synchronously.
167
+ */
168
+ function getProtocolValue(
169
+ value: string,
170
+ protocols: Record<string, (key: string) => any>,
171
+ options?: GetValuesOptions
172
+ ): any {
173
+ const protoEnd = value.indexOf('://');
174
+ const protocol = value.substring(0, protoEnd);
175
+
176
+ const handler = protocols[protocol];
177
+ if (!handler) return value; // not a recognized protocol
178
+
179
+ const rest = value.substring(protoEnd + 3);
180
+
181
+ const pathStart = rest.indexOf('?.');
182
+ const key = pathStart === -1 ? rest : rest.substring(0, pathStart);
183
+ const path = pathStart === -1 ? null : rest.substring(pathStart);
184
+
185
+ const resolved = handler(key);
186
+
187
+ if (path) {
188
+ return getValue(path, resolved, options);
189
+ }
190
+ return resolved;
191
+ }
192
+
193
+ /**
194
+ * Resolve path strings and protocols within an array (synchronous).
195
+ * Recurses into nested arrays and plain objects.
196
+ */
197
+ function getArray(
198
+ arr: any[],
199
+ source: any,
200
+ aliasMap: Map<string, string>,
201
+ withMethods: Set<string> | undefined,
202
+ protocols: Record<string, (key: string) => any> | undefined,
203
+ options?: GetValuesOptions
204
+ ): any[] {
205
+ const permissionProcessor = options?.permissionProcessor;
206
+ const result: any[] = [];
207
+ for (const item of arr) {
208
+ if (typeof item === 'string' && item.startsWith('?.')) {
209
+ const aliased = applyAliases(item, aliasMap);
210
+ const parts = parseCachedPath(aliased);
211
+ result.push(parts.length === 0 ? source : navigatePath(source, parts, withMethods, permissionProcessor));
212
+ } else if (typeof item === 'string' && item.startsWith('$0')) {
213
+ const rootRef = resolveRootReference(item, source, options?.root);
214
+ if (rootRef === null) {
215
+ result.push(source);
216
+ } else {
217
+ const aliased = applyAliases(rootRef.path, aliasMap);
218
+ const normalizedPath = aliased.startsWith('?.') ? aliased : (aliased ? `?.${aliased}` : '?.');
219
+ const parts = parseCachedPath(normalizedPath);
220
+ result.push(parts.length === 0 ? rootRef.source : navigatePath(rootRef.source, parts, withMethods, permissionProcessor));
221
+ }
222
+ } else if (typeof item === 'string' && protocols && hasProtocol(item)) {
223
+ result.push(getProtocolValue(item, protocols, options));
224
+ } else if (Array.isArray(item)) {
225
+ result.push(getArray(item, source, aliasMap, withMethods, protocols, options));
226
+ } else if (item && typeof item === 'object') {
227
+ const proto = Object.getPrototypeOf(item);
228
+ if (proto === Object.prototype || proto === null) {
229
+ result.push(getValues(item, source, options));
230
+ } else {
231
+ result.push(item);
232
+ }
233
+ } else {
234
+ result.push(item);
235
+ }
236
+ }
237
+ return result;
238
+ }
239
+
240
+ /**
241
+ * Synchronously resolve RHS path strings in a pattern object against a source object.
242
+ *
243
+ * Any value that is a string starting with `?.` is treated as a path
244
+ * and resolved against the source object. Non-string values and strings
245
+ * not starting with `?.` pass through unchanged.
246
+ *
247
+ * @param pattern - Object whose RHS values may contain `?.` path strings
248
+ * @param source - Object to resolve paths against
249
+ * @param options - Optional withMethods, aka, and synchronous protocols
250
+ * @returns New object with path strings replaced by resolved values
251
+ */
252
+ export function getValues(
253
+ pattern: Record<string, any>,
254
+ source: any,
255
+ options?: GetValuesOptions
256
+ ): Record<string, any> {
257
+ const { aliasMap, withMethods } = normalizeAliasOptions(options);
258
+
259
+ const protocols = options?.protocols;
260
+ const permissionProcessor = options?.permissionProcessor;
261
+
262
+ const result: Record<string, any> = {};
263
+ for (const [key, value] of Object.entries(pattern)) {
264
+ if (typeof value === 'string' && value.startsWith('?.')) {
265
+ const aliased = applyAliases(value, aliasMap);
266
+ const parts = parseCachedPath(aliased);
267
+ result[key] = parts.length === 0 ? source : navigatePath(source, parts, withMethods, permissionProcessor);
268
+ } else if (typeof value === 'string' && value.startsWith('$0')) {
269
+ const rootRef = resolveRootReference(value, source, options?.root);
270
+ if (rootRef === null) {
271
+ result[key] = source;
272
+ } else {
273
+ const aliased = applyAliases(rootRef.path, aliasMap);
274
+ const normalizedPath = aliased.startsWith('?.') ? aliased : (aliased ? `?.${aliased}` : '?.');
275
+ const parts = parseCachedPath(normalizedPath);
276
+ result[key] = parts.length === 0 ? rootRef.source : navigatePath(rootRef.source, parts, withMethods, permissionProcessor);
277
+ }
278
+ } else if (typeof value === 'string' && protocols && hasProtocol(value)) {
279
+ result[key] = getProtocolValue(value, protocols, options);
280
+ } else if (Array.isArray(value)) {
281
+ result[key] = getArray(value, source, aliasMap, withMethods, protocols, options);
282
+ } else if (typeof value === 'object' && value !== null) {
283
+ const proto = Object.getPrototypeOf(value);
284
+ if (proto === Object.prototype || proto === null) {
285
+ result[key] = getValues(value, source, options);
286
+ } else {
287
+ result[key] = value;
288
+ }
289
+ } else {
290
+ result[key] = value;
291
+ }
292
+ }
293
+ return result;
294
+ }
295
+
296
+ /**
297
+ * Synchronously resolve a single `?.`-delimited path string against a source object.
298
+ *
299
+ * @param path - A `?.`-delimited path string (e.g., '?.user?.name')
300
+ * @param source - Object to resolve the path against
301
+ * @param options - Optional withMethods and aka
302
+ * @returns The resolved value, or undefined if any segment is nullish
303
+ */
304
+ export function getValue(
305
+ path: string,
306
+ source: any,
307
+ options?: GetValuesOptions
308
+ ): any {
309
+ const rootRef = resolveRootReference(path, source, options?.root);
310
+ if (rootRef) {
311
+ path = rootRef.path;
312
+ source = rootRef.source;
313
+ } else if (!path.startsWith('?.')) {
314
+ return path;
315
+ }
316
+
317
+ let aliased = path;
318
+ const { aliasMap } = normalizeAliasOptions(options);
319
+ if (aliasMap.size > 0) {
320
+ aliased = applyAliases(path, aliasMap);
321
+ }
322
+
323
+ const normalizedPath = aliased.startsWith('?.') ? aliased : (aliased ? `?.${aliased}` : '?.');
324
+ const parts = parseCachedPath(normalizedPath);
325
+ if (parts.length === 0) return source;
326
+
327
+ const { withMethods } = normalizeAliasOptions(options);
328
+ const permissionProcessor = options?.permissionProcessor;
329
+
330
+ return navigatePath(source, parts, withMethods, permissionProcessor);
331
+ }