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.
@@ -4,6 +4,7 @@
4
4
  * Dynamically imported only when ` =>` keys are detected in the pattern.
5
5
  */
6
6
  import { resolveValues } from './resolveValues.js';
7
+ import { getValues } from './getValues.js';
7
8
  import { evaluatePathWithMethods } from './assignGingerly.js';
8
9
  import { isAllowedImportPath } from './isAllowedImportPath.js';
9
10
  /**
@@ -15,6 +16,7 @@ const BUILT_IN_MAP = {
15
16
  'builtIns.lazyLoadSwitch': './handlers/lazyLoadSwitch.js',
16
17
  'builtIns.join': './handlers/join.js',
17
18
  'builtIns.microDataJoin': './handlers/microDataJoin.js',
19
+ 'builtIns.manageTemplateList': './handlers/manageTemplateList.js',
18
20
  };
19
21
  /**
20
22
  * Find a handler class in a dynamically imported module.
@@ -35,16 +37,27 @@ function findHandlerInModule(module) {
35
37
  }
36
38
  return undefined;
37
39
  }
40
+ /**
41
+ * Cache for loaded built-in handler classes — avoids await on subsequent calls.
42
+ */
43
+ const handlerCache = new Map();
38
44
  /**
39
45
  * Dynamically load a built-in handler by name.
40
46
  * Returns the handler constructor, or undefined if the name isn't a recognized built-in.
47
+ * Cached after first load — subsequent calls are synchronous.
41
48
  */
42
49
  async function loadBuiltIn(name) {
50
+ const cached = handlerCache.get(name);
51
+ if (cached)
52
+ return cached;
43
53
  const path = BUILT_IN_MAP[name];
44
54
  if (!path)
45
55
  return undefined;
46
56
  const module = await import(path);
47
- return findHandlerInModule(module);
57
+ const cls = findHandlerInModule(module);
58
+ if (cls)
59
+ handlerCache.set(name, cls);
60
+ return cls;
48
61
  }
49
62
  /**
50
63
  * Resolve a handler from options.handlers (class constructor or import path).
@@ -157,27 +170,41 @@ export async function processHandlerCommands(target, handlerKeys, pattern, optio
157
170
  }
158
171
  // Execute handlers sequentially, sharing the same lhsTarget
159
172
  for (const config of configs) {
173
+ //return; //1.3ms
160
174
  // 1. Check options.handlers (local, per-call)
161
175
  let HandlerClass = await resolveFromHandlers(config.do, options.handlers, permissions);
176
+ //return; // 1.4
162
177
  // 2. Fallback to built-in auto-load
163
178
  if (!HandlerClass && config.do.startsWith('builtIns.')) {
164
179
  HandlerClass = await loadBuiltIn(config.do);
165
180
  }
181
+ //return; //1.4
166
182
  if (!HandlerClass) {
167
183
  throw new Error(`assignFrom: unknown handler "${config.do}". Provide it in options.handlers.`);
168
184
  }
169
- // Resolve 'resolve' map if present — uses full resolveValues (paths, protocols, literals)
185
+ // Resolve 'get' map synchronously (no thread yield)
170
186
  let resolvedParams = {};
187
+ if (config.get) {
188
+ resolvedParams = getValues(config.get, options.from, {
189
+ withMethods: options.withMethods,
190
+ aka: options.aka,
191
+ protocols: options.protocols
192
+ });
193
+ }
194
+ // Resolve 'resolve' map asynchronously (yields to microtask queue)
171
195
  if (config.resolve) {
172
- resolvedParams = await resolveValues(config.resolve, options.from, {
196
+ const asyncResolved = await resolveValues(config.resolve, options.from, {
173
197
  withMethods: options.withMethods,
174
198
  aka: options.aka,
175
199
  protocols: options.protocols
176
200
  });
201
+ Object.assign(resolvedParams, asyncResolved);
177
202
  }
178
203
  // Instantiate and invoke the handler
179
204
  const handler = new HandlerClass(config);
205
+ //return; //1.5ms
180
206
  const result = await handler.assign(lhsTarget, resolvedParams, options);
207
+ //return; //1.5ms
181
208
  // Return-value protocol: if handler returns a non-undefined value,
182
209
  // assign it back to the LHS path
183
210
  if (result !== undefined && lhsParent != null && lhsKey != null) {
@@ -5,10 +5,11 @@
5
5
  */
6
6
 
7
7
  import { resolveValues } from './resolveValues.js';
8
+ import { getValues } from './getValues.js';
8
9
  import { evaluatePathWithMethods } from './assignGingerly.js';
9
10
  import { isAllowedImportPath } from './isAllowedImportPath.js';
10
11
  import type { AssignPermissions } from './isAllowedImportPath.js';
11
- import type { AssignFromOptions, AssignFromHandlerConstructor } from './assignFrom.js';
12
+ import type { AssignFromOptions, AssignFromHandlerConstructor } from './assignFromAsync.js';
12
13
 
13
14
  /**
14
15
  * Map of built-in handler names to their module paths.
@@ -19,6 +20,7 @@ const BUILT_IN_MAP: Record<string, string> = {
19
20
  'builtIns.lazyLoadSwitch': './handlers/lazyLoadSwitch.js',
20
21
  'builtIns.join': './handlers/join.js',
21
22
  'builtIns.microDataJoin': './handlers/microDataJoin.js',
23
+ 'builtIns.manageTemplateList': './handlers/manageTemplateList.js',
22
24
  };
23
25
 
24
26
  /**
@@ -41,15 +43,25 @@ function findHandlerInModule(module: any): AssignFromHandlerConstructor | undefi
41
43
  return undefined;
42
44
  }
43
45
 
46
+ /**
47
+ * Cache for loaded built-in handler classes — avoids await on subsequent calls.
48
+ */
49
+ const handlerCache = new Map<string, AssignFromHandlerConstructor>();
50
+
44
51
  /**
45
52
  * Dynamically load a built-in handler by name.
46
53
  * Returns the handler constructor, or undefined if the name isn't a recognized built-in.
54
+ * Cached after first load — subsequent calls are synchronous.
47
55
  */
48
56
  async function loadBuiltIn(name: string): Promise<AssignFromHandlerConstructor | undefined> {
57
+ const cached = handlerCache.get(name);
58
+ if (cached) return cached;
49
59
  const path = BUILT_IN_MAP[name];
50
60
  if (!path) return undefined;
51
61
  const module = await import(path);
52
- return findHandlerInModule(module);
62
+ const cls = findHandlerInModule(module);
63
+ if (cls) handlerCache.set(name, cls);
64
+ return cls;
53
65
  }
54
66
 
55
67
  /**
@@ -107,6 +119,7 @@ export async function processHandlerCommands(
107
119
  options: AssignFromOptions,
108
120
  permissions?: AssignPermissions
109
121
  ): Promise<void> {
122
+
110
123
  for (const key of handlerKeys) {
111
124
  const lhsPath = key.substring(0, key.length - 3); // Remove ' =>'
112
125
  const rhs = pattern[key];
@@ -178,35 +191,46 @@ export async function processHandlerCommands(
178
191
  } else {
179
192
  lhsTarget = target;
180
193
  }
181
-
182
194
  // Execute handlers sequentially, sharing the same lhsTarget
183
195
  for (const config of configs) {
196
+ //return; //1.3ms
184
197
  // 1. Check options.handlers (local, per-call)
185
198
  let HandlerClass = await resolveFromHandlers(config.do, options.handlers, permissions);
186
-
199
+ //return; // 1.4
187
200
  // 2. Fallback to built-in auto-load
188
201
  if (!HandlerClass && config.do.startsWith('builtIns.')) {
189
202
  HandlerClass = await loadBuiltIn(config.do);
190
203
  }
204
+ //return; //1.4
191
205
 
192
206
  if (!HandlerClass) {
193
207
  throw new Error(`assignFrom: unknown handler "${config.do}". Provide it in options.handlers.`);
194
208
  }
195
209
 
196
- // Resolve 'resolve' map if present — uses full resolveValues (paths, protocols, literals)
210
+ // Resolve 'get' map synchronously (no thread yield)
197
211
  let resolvedParams: Record<string, any> = {};
212
+ if (config.get) {
213
+ resolvedParams = getValues(config.get, options.from, {
214
+ withMethods: options.withMethods,
215
+ aka: options.aka,
216
+ protocols: options.protocols
217
+ });
218
+ }
219
+ // Resolve 'resolve' map asynchronously (yields to microtask queue)
198
220
  if (config.resolve) {
199
- resolvedParams = await resolveValues(config.resolve, options.from, {
221
+ const asyncResolved = await resolveValues(config.resolve, options.from, {
200
222
  withMethods: options.withMethods,
201
223
  aka: options.aka,
202
224
  protocols: options.protocols
203
225
  });
226
+ Object.assign(resolvedParams, asyncResolved);
204
227
  }
205
228
 
206
229
  // Instantiate and invoke the handler
207
230
  const handler = new HandlerClass(config);
231
+ //return; //1.5ms
208
232
  const result = await handler.assign(lhsTarget, resolvedParams, options);
209
-
233
+ //return; //1.5ms
210
234
  // Return-value protocol: if handler returns a non-undefined value,
211
235
  // assign it back to the LHS path
212
236
  if (result !== undefined && lhsParent != null && lhsKey != null) {
package/resolveValues.js CHANGED
@@ -1,6 +1,22 @@
1
+ /**
2
+ * resolveValues.ts — Async value resolution for path strings.
3
+ *
4
+ * Thin async wrapper around getValues that adds support for async protocol handlers.
5
+ * For synchronous-only use cases, import getValues/getValue directly for better performance.
6
+ *
7
+ * Re-exports ResolveValuesOptions for backward compatibility.
8
+ */
9
+ import { getValue } from './getValues.js';
10
+ // Re-export getValue as resolveValue for backward compatibility
11
+ export { getValue as resolveValue };
12
+ /**
13
+ * Checks if a string value looks like a protocol reference.
14
+ */
15
+ function hasProtocol(value) {
16
+ return value.includes('://');
17
+ }
1
18
  /**
2
19
  * Apply alias substitutions to a path string.
3
- * Replaces complete tokens between `?.` delimiters with their aliased values.
4
20
  */
5
21
  function applyAliases(path, aliasMap) {
6
22
  if (aliasMap.size === 0)
@@ -11,12 +27,8 @@ function applyAliases(path, aliasMap) {
11
27
  }
12
28
  /**
13
29
  * Path cache for parsed path strings.
14
- * Avoids re-splitting the same path on repeated calls.
15
30
  */
16
31
  const pathCache = new Map();
17
- /**
18
- * Parse a `?.`-delimited path string into segments, with caching.
19
- */
20
32
  function parseCachedPath(path) {
21
33
  let parts = pathCache.get(path);
22
34
  if (!parts) {
@@ -25,44 +37,8 @@ function parseCachedPath(path) {
25
37
  }
26
38
  return parts;
27
39
  }
28
- /**
29
- * Resolves a protocol-prefixed value (e.g., 'globalThis://key?.path').
30
- *
31
- * 1. Extracts the protocol name (before '://')
32
- * 2. If the protocol isn't in the protocols map, returns the value unchanged (false positive)
33
- * 3. Extracts the key (between '://' and first '?.' or end of string)
34
- * 4. Calls the protocol handler with the key
35
- * 5. If there's a remaining '?.' path, resolves it against the handler's result
36
- */
37
- async function resolveProtocolValue(value, protocols, options) {
38
- // Extract protocol name (before ://)
39
- const protoEnd = value.indexOf('://');
40
- const protocol = value.substring(0, protoEnd);
41
- // Resolve via protocol handler
42
- const handler = protocols[protocol];
43
- if (!handler)
44
- return value; // false flag — coincidentally looks like a protocol
45
- const rest = value.substring(protoEnd + 3);
46
- // Split at first ?. to separate key from path
47
- const pathStart = rest.indexOf('?.');
48
- const key = pathStart === -1 ? rest : rest.substring(0, pathStart);
49
- const path = pathStart === -1 ? null : rest.substring(pathStart);
50
- const resolved = await handler(key);
51
- // If there's a remaining path, resolve it against the result
52
- if (path) {
53
- return resolveValue(path, resolved, options);
54
- }
55
- return resolved;
56
- }
57
- /**
58
- * Checks if a string value looks like a protocol reference.
59
- */
60
- function hasProtocol(value) {
61
- return value.includes('://');
62
- }
63
40
  /**
64
41
  * Navigate a path against a source object, optionally calling methods.
65
- * Returns the resolved value at the end of the path.
66
42
  */
67
43
  function navigatePath(source, parts, withMethods) {
68
44
  let current = source;
@@ -76,12 +52,10 @@ function navigatePath(source, parts, withMethods) {
76
52
  if (typeof method === 'function') {
77
53
  const nextPart = parts[i + 1];
78
54
  if (nextPart !== undefined && !(withMethods.has(nextPart))) {
79
- // Call method with next segment as argument, consume it
80
55
  current = method.call(current, nextPart);
81
56
  i += 2;
82
57
  }
83
58
  else {
84
- // Consecutive methods or last segment — call with no args
85
59
  current = method.call(current);
86
60
  i++;
87
61
  }
@@ -99,9 +73,26 @@ function navigatePath(source, parts, withMethods) {
99
73
  return current;
100
74
  }
101
75
  /**
102
- * Resolve path strings and protocol references within an array.
103
- * Recurses into nested arrays and plain objects. Non-string elements,
104
- * class instances, and other non-plain objects pass through unchanged.
76
+ * Resolves a protocol-prefixed value asynchronously.
77
+ */
78
+ async function resolveProtocolValue(value, protocols, options) {
79
+ const protoEnd = value.indexOf('://');
80
+ const protocol = value.substring(0, protoEnd);
81
+ const handler = protocols[protocol];
82
+ if (!handler)
83
+ return value;
84
+ const rest = value.substring(protoEnd + 3);
85
+ const pathStart = rest.indexOf('?.');
86
+ const key = pathStart === -1 ? rest : rest.substring(0, pathStart);
87
+ const path = pathStart === -1 ? null : rest.substring(pathStart);
88
+ const resolved = await handler(key);
89
+ if (path) {
90
+ return getValue(path, resolved, options);
91
+ }
92
+ return resolved;
93
+ }
94
+ /**
95
+ * Resolve path strings and protocol references within an array (async).
105
96
  */
106
97
  async function resolveArray(arr, source, aliasMap, withMethods, protocols, options) {
107
98
  const result = [];
@@ -133,47 +124,23 @@ async function resolveArray(arr, source, aliasMap, withMethods, protocols, optio
133
124
  return result;
134
125
  }
135
126
  /**
136
- * Resolve RHS path strings in a pattern object against a source object.
127
+ * Async resolve RHS path strings in a pattern object against a source object.
137
128
  *
138
- * Any value that is a string starting with `?.` is treated as a path
139
- * and resolved against the source object using optional chaining semantics.
140
- * Non-string values and strings not starting with `?.` pass through unchanged.
141
- *
142
- * Supports `withMethods` for calling methods during resolution and `aka` for
143
- * alias substitution, consistent with assignGingerly's LHS path handling.
144
- *
145
- * Special case: `'?.'` (empty path) resolves to the source object itself.
129
+ * Supports async protocol handlers (e.g., fetch, IndexedDB).
130
+ * For synchronous-only patterns, use `getValues` from 'assign-gingerly/getValues.js' instead.
146
131
  *
147
132
  * @param pattern - Object whose RHS values may contain `?.` path strings
148
133
  * @param source - Object to resolve paths against
149
- * @param options - Optional withMethods and aka for method calls and aliases
134
+ * @param options - Optional withMethods, aka, and protocol handlers
150
135
  * @returns New object with path strings replaced by resolved values
151
- *
152
- * @example
153
- * const result = resolveValues({
154
- * hello: '?.myPropContainer?.stringProp',
155
- * foo: '?.myFooString',
156
- * literal: 42
157
- * }, source);
158
- *
159
- * @example
160
- * // With methods and aliases
161
- * const result = resolveValues({
162
- * text: '?.q?..username?.textContent'
163
- * }, source, {
164
- * withMethods: ['querySelector'],
165
- * aka: { 'q': 'querySelector' }
166
- * });
167
136
  */
168
137
  export async function resolveValues(pattern, source, options) {
169
- // Build alias map
170
138
  const aliasMap = new Map();
171
139
  if (options?.aka) {
172
140
  for (const [alias, target] of Object.entries(options.aka)) {
173
141
  aliasMap.set(alias, target);
174
142
  }
175
143
  }
176
- // Build methods set
177
144
  const withMethods = options?.withMethods
178
145
  ? options.withMethods instanceof Set
179
146
  ? options.withMethods
@@ -183,24 +150,17 @@ export async function resolveValues(pattern, source, options) {
183
150
  const result = {};
184
151
  for (const [key, value] of Object.entries(pattern)) {
185
152
  if (typeof value === 'string' && value.startsWith('?.')) {
186
- // Apply aliases to the RHS path
187
153
  const aliased = applyAliases(value, aliasMap);
188
- // Parse path with caching
189
154
  const parts = parseCachedPath(aliased);
190
- // Navigate with method support
191
155
  result[key] = parts.length === 0 ? source : navigatePath(source, parts, withMethods);
192
156
  }
193
157
  else if (typeof value === 'string' && protocols && hasProtocol(value)) {
194
- // Protocol-prefixed value — resolve asynchronously
195
158
  result[key] = await resolveProtocolValue(value, protocols, options);
196
159
  }
197
160
  else if (Array.isArray(value)) {
198
- // Resolve path strings and protocols within arrays (recursing into nested arrays)
199
161
  result[key] = await resolveArray(value, source, aliasMap, withMethods, protocols, options);
200
162
  }
201
163
  else if (typeof value === 'object' && value !== null) {
202
- // Recursively resolve nested plain objects (e.g., headers: { "...": "globalThis://key" })
203
- // Only recurse into plain objects — skip DOM elements, class instances, etc.
204
164
  const proto = Object.getPrototypeOf(value);
205
165
  if (proto === Object.prototype || proto === null) {
206
166
  result[key] = await resolveValues(value, source, options);
@@ -215,47 +175,3 @@ export async function resolveValues(pattern, source, options) {
215
175
  }
216
176
  return result;
217
177
  }
218
- /**
219
- * Resolve a single `?.`-delimited path string against a source object.
220
- *
221
- * This is a lighter-weight alternative to `resolveValues` when you only need
222
- * to resolve one path and don't want the overhead of creating wrapper objects.
223
- *
224
- * @param path - A `?.`-delimited path string (e.g., '?.behaviors?.command')
225
- * @param source - Object to resolve the path against
226
- * @param options - Optional withMethods and aka for method calls and aliases
227
- * @returns The resolved value, or undefined if any segment is nullish
228
- *
229
- * @example
230
- * const value = resolveValue('?.behaviors?.commandBehavior?.command', el);
231
- *
232
- * @example
233
- * const value = resolveValue('?.q?.myEl?.textContent', el, {
234
- * withMethods: ['querySelector'],
235
- * aka: { 'q': 'querySelector' }
236
- * });
237
- */
238
- export function resolveValue(path, source, options) {
239
- if (!path.startsWith('?.'))
240
- return path;
241
- // Build alias map
242
- let aliased = path;
243
- if (options?.aka) {
244
- const aliasMap = new Map();
245
- for (const [alias, target] of Object.entries(options.aka)) {
246
- aliasMap.set(alias, target);
247
- }
248
- aliased = applyAliases(path, aliasMap);
249
- }
250
- // Parse path with caching
251
- const parts = parseCachedPath(aliased);
252
- if (parts.length === 0)
253
- return source;
254
- // Build methods set
255
- const withMethods = options?.withMethods
256
- ? options.withMethods instanceof Set
257
- ? options.withMethods
258
- : new Set(options.withMethods)
259
- : undefined;
260
- return navigatePath(source, parts, withMethods);
261
- }