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/assignFrom.js CHANGED
@@ -1,45 +1,35 @@
1
1
  /**
2
- * Resolve RHS path strings against a source object, then assign the
3
- * resolved values into a target using assignGingerly.
2
+ * assignFrom.ts Synchronous assign-from-source function.
4
3
  *
5
- * Combines resolveValues + assignGingerly into a single call.
6
- * Inherits all assignGingerly options (withMethods, aka, signal, etc.).
4
+ * Resolves RHS path strings synchronously via getValues, then assigns into the target.
5
+ * Supports looped substitution, #[x] refs, inferredAssignments, and handleSpreads — all sync.
7
6
  *
8
- * @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
7
+ * For async protocol handlers or awaitable handler execution, use assignFromAsync.
12
8
  *
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' }
9
+ * Handler commands (` =>`) are fire-and-forget (kicked off asynchronously, not awaited).
21
10
  */
22
- import { resolveValues } from './resolveValues.js';
11
+ import { getValues } from './getValues.js';
23
12
  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
- }
13
+ import { resolveIdVariable, parseIdRef } from './resolveIdRef.js';
14
+ import { processInferredAssignments } from './inferredAssignments.js';
30
15
  /**
31
16
  * Supported substitution variables and their option keys.
32
17
  */
33
- const SUBSTITUTION_VARS = [
18
+ export const SUBSTITUTION_VARS = [
34
19
  { placeholder: '${x}', optionKey: 'where_x_in' },
35
20
  { placeholder: '${y}', optionKey: 'where_y_in' },
36
21
  { placeholder: '${z}', optionKey: 'where_z_in' },
37
22
  ];
23
+ /**
24
+ * Check if a key ends with the handler operator ' =>'.
25
+ */
26
+ export function isHandlerCommand(key) {
27
+ return key.endsWith(' =>');
28
+ }
38
29
  /**
39
30
  * Recursively substitute a placeholder in all string values of an object.
40
- * Returns a new object (shallow clone at each level) with substitutions applied.
41
31
  */
42
- function substituteInValue(value, placeholder, replacement) {
32
+ export function substituteInValue(value, placeholder, replacement) {
43
33
  if (typeof value === 'string') {
44
34
  return value.includes(placeholder) ? value.replaceAll(placeholder, replacement) : value;
45
35
  }
@@ -61,7 +51,7 @@ function substituteInValue(value, placeholder, replacement) {
61
51
  /**
62
52
  * Check if a pattern entry (key + value) contains a given placeholder.
63
53
  */
64
- function entryContainsPlaceholder(key, value, placeholder) {
54
+ export function entryContainsPlaceholder(key, value, placeholder) {
65
55
  if (key.includes(placeholder))
66
56
  return true;
67
57
  return valueContainsPlaceholder(value, placeholder);
@@ -69,7 +59,7 @@ function entryContainsPlaceholder(key, value, placeholder) {
69
59
  /**
70
60
  * Check if a value (string, object, or array) contains a placeholder.
71
61
  */
72
- function valueContainsPlaceholder(value, placeholder) {
62
+ export function valueContainsPlaceholder(value, placeholder) {
73
63
  if (typeof value === 'string')
74
64
  return value.includes(placeholder);
75
65
  if (Array.isArray(value))
@@ -84,12 +74,8 @@ function valueContainsPlaceholder(value, placeholder) {
84
74
  }
85
75
  /**
86
76
  * Expand looped substitution variables in a pattern.
87
- * Applies cartesian expansion: x values are expanded first, then y, then z.
88
- * Each variable multiplies the entries — result count = x.length × y.length × z.length.
89
- *
90
- * Returns the expanded pattern (or the original if no substitutions apply).
91
77
  */
92
- function expandSubstitutions(pattern, options) {
78
+ export function expandSubstitutions(pattern, options) {
93
79
  let entries = Object.entries(pattern);
94
80
  for (const { placeholder, optionKey } of SUBSTITUTION_VARS) {
95
81
  const values = options[optionKey];
@@ -98,7 +84,6 @@ function expandSubstitutions(pattern, options) {
98
84
  const expanded = [];
99
85
  for (const [key, value] of entries) {
100
86
  if (entryContainsPlaceholder(key, value, placeholder)) {
101
- // Expand this entry for each value in the variable array
102
87
  for (const replacement of values) {
103
88
  const newKey = key.includes(placeholder)
104
89
  ? key.replaceAll(placeholder, replacement)
@@ -108,7 +93,6 @@ function expandSubstitutions(pattern, options) {
108
93
  }
109
94
  }
110
95
  else {
111
- // No placeholder in this entry — pass through
112
96
  expanded.push([key, value]);
113
97
  }
114
98
  }
@@ -118,14 +102,11 @@ function expandSubstitutions(pattern, options) {
118
102
  }
119
103
  /**
120
104
  * Convert entries to an object, merging duplicate handler (` =>`) keys into arrays.
121
- * For normal (non-handler) keys, later entries overwrite earlier ones (standard object behavior).
122
- * For handler keys, duplicate entries are combined into an array (Multiple Handlers pattern).
123
105
  */
124
- function mergeHandlerDuplicates(entries) {
106
+ export function mergeHandlerDuplicates(entries) {
125
107
  const result = {};
126
108
  for (const [key, value] of entries) {
127
109
  if (key.endsWith(' =>') && key in result) {
128
- // Duplicate handler key — merge into array
129
110
  const existing = result[key];
130
111
  if (Array.isArray(existing)) {
131
112
  existing.push(value);
@@ -140,10 +121,31 @@ function mergeHandlerDuplicates(entries) {
140
121
  }
141
122
  return result;
142
123
  }
143
- export async function assignFrom(target, pattern, options, permissions) {
144
- // First: expand looped substitution variables (${x}, ${y}, ${z})
145
- const expandedPattern = expandSubstitutions(pattern, options);
146
- // Separate handler commands ( =>), #[x] keys, and normal keys
124
+ /**
125
+ * Recursively walk an object and handle "..." spread keys.
126
+ */
127
+ export function handleSpreads(obj) {
128
+ for (const [key, value] of Object.entries(obj)) {
129
+ if (key !== '...' && typeof value === 'object' && value !== null && !Array.isArray(value)) {
130
+ const proto = Object.getPrototypeOf(value);
131
+ if (proto === Object.prototype || proto === null) {
132
+ obj[key] = handleSpreads(value);
133
+ }
134
+ }
135
+ }
136
+ if ('...' in obj) {
137
+ const spreadValue = obj['...'];
138
+ delete obj['...'];
139
+ if (spreadValue && typeof spreadValue === 'object') {
140
+ Object.assign(obj, spreadValue);
141
+ }
142
+ }
143
+ return obj;
144
+ }
145
+ /**
146
+ * Categorize pattern keys into handler keys, #[x] keys, and normal keys.
147
+ */
148
+ export function categorizeKeys(expandedPattern) {
147
149
  const handlerKeys = [];
148
150
  const normalPattern = {};
149
151
  const idRefNormalKeys = [];
@@ -164,113 +166,106 @@ export async function assignFrom(target, pattern, options, permissions) {
164
166
  normalPattern[key] = expandedPattern[key];
165
167
  }
166
168
  }
167
- // Process normal keys via resolveValues + assignGingerly
169
+ return { handlerKeys, normalPattern, idRefNormalKeys, idRefHandlerKeys };
170
+ }
171
+ /**
172
+ * Process #[x] normal keys synchronously.
173
+ */
174
+ function processIdRefNormalKeys(idRefNormalKeys, expandedPattern, target, options) {
175
+ if (!options.withIds)
176
+ return;
177
+ for (const key of idRefNormalKeys) {
178
+ const parsed = parseIdRef(key);
179
+ if (!parsed)
180
+ continue;
181
+ const el = resolveIdVariable(parsed.varName, target, options.withIds);
182
+ if (!el)
183
+ continue;
184
+ const value = expandedPattern[key];
185
+ if (parsed.remainingPath) {
186
+ const resolvedValue = getValues({ __v: value }, options.from, { withMethods: options.withMethods, aka: options.aka, protocols: options.protocols });
187
+ assignGingerly(el, { [parsed.remainingPath]: resolvedValue.__v }, options);
188
+ }
189
+ else {
190
+ const resolvedValue = getValues(typeof value === 'object' && value !== null ? value : { __v: value }, options.from, { withMethods: options.withMethods, aka: options.aka, protocols: options.protocols });
191
+ if (!('__v' in resolvedValue)) {
192
+ assignGingerly(el, resolvedValue, options);
193
+ }
194
+ }
195
+ }
196
+ }
197
+ /**
198
+ * Synchronous assignFrom — resolves values, assigns to target, all without awaiting.
199
+ *
200
+ * Handler commands (` =>`), beVigilant, and enhance are fire-and-forget (async, non-blocking).
201
+ * For awaitable handler execution, use assignFromAsync.
202
+ *
203
+ * @param target - Object to merge resolved values into
204
+ * @param pattern - Object whose RHS values may contain `?.` path strings
205
+ * @param options - Options including `from` (source object)
206
+ * @param permissions - Optional security permissions
207
+ * @returns The target object after merging
208
+ */
209
+ export function assignFrom(target, pattern, options, permissions) {
210
+ // Expand looped substitution variables
211
+ const expandedPattern = expandSubstitutions(pattern, options);
212
+ // Categorize keys
213
+ const { handlerKeys, normalPattern, idRefNormalKeys, idRefHandlerKeys } = categorizeKeys(expandedPattern);
214
+ // Process normal keys via getValues (sync) + assignGingerly
168
215
  if (Object.keys(normalPattern).length > 0) {
169
- const resolved = await resolveValues(normalPattern, options.from, {
216
+ const resolved = getValues(normalPattern, options.from, {
170
217
  withMethods: options.withMethods,
171
218
  aka: options.aka,
172
219
  protocols: options.protocols
173
220
  });
174
- // Recursively handle "..." spread keys at all nesting levels
175
221
  handleSpreads(resolved);
176
222
  assignGingerly(target, resolved, options);
177
223
  }
178
- // Process #[x] normal keys — 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)
184
- continue;
185
- const el = resolveIdVariable(parsed.varName, target, options.withIds);
186
- if (!el)
187
- continue;
188
- const value = expandedPattern[key];
189
- if (parsed.remainingPath) {
190
- // Resolve the RHS value
191
- const resolvedValue = await resolveValues({ __v: value }, options.from, { withMethods: options.withMethods, aka: options.aka, protocols: options.protocols });
192
- // Apply remaining path on the resolved element
193
- assignGingerly(el, { [parsed.remainingPath]: resolvedValue.__v }, options);
194
- }
195
- else {
196
- // No remaining path — resolve and assign directly to the element
197
- const resolvedValue = await resolveValues(typeof value === 'object' && value !== null ? value : { __v: value }, options.from, { withMethods: options.withMethods, aka: options.aka, protocols: options.protocols });
198
- if ('__v' in resolvedValue) {
199
- // Single value — can't assign to element root without a path
200
- }
201
- else {
202
- assignGingerly(el, resolvedValue, options);
203
- }
204
- }
205
- }
224
+ // Process #[x] normal keys (sync)
225
+ if (idRefNormalKeys.length > 0) {
226
+ processIdRefNormalKeys(idRefNormalKeys, expandedPattern, target, options);
206
227
  }
207
- // Process handler commands ( =>) dynamically imported only when needed
228
+ // Process handler commands — fire-and-forget (async)
208
229
  if (handlerKeys.length > 0) {
209
- const { processHandlerCommands } = await import('./processHandlerCommands.js');
210
- await processHandlerCommands(target, handlerKeys, expandedPattern, options, permissions);
230
+ import('./processHandlerCommands.js').then(({ processHandlerCommands }) => {
231
+ processHandlerCommands(target, handlerKeys, expandedPattern, options, permissions);
232
+ });
211
233
  }
212
- // Process #[x] handler keys — resolve element, then pass to handler processing
234
+ // Process #[x] handler keys — fire-and-forget (async)
213
235
  if (idRefHandlerKeys.length > 0 && options.withIds) {
214
- const { resolveIdVariable, parseIdRef } = await import('./resolveIdRef.js');
215
- const { processHandlerCommands } = await import('./processHandlerCommands.js');
216
- for (const key of idRefHandlerKeys) {
217
- const parsed = parseIdRef(key);
218
- if (!parsed)
219
- continue;
220
- const el = resolveIdVariable(parsed.varName, target, options.withIds);
221
- if (!el)
222
- continue;
223
- // Build a synthetic key for processHandlerCommands:
224
- // The resolved element becomes the target, remaining path is the LHS
225
- const syntheticKey = parsed.remainingPath
226
- ? `${parsed.remainingPath} =>`
227
- : ' =>';
228
- const syntheticPattern = {
229
- [syntheticKey]: expandedPattern[key]
230
- };
231
- await processHandlerCommands(el, [syntheticKey], syntheticPattern, options, permissions);
232
- }
236
+ import('./processHandlerCommands.js').then(({ processHandlerCommands }) => {
237
+ for (const key of idRefHandlerKeys) {
238
+ const parsed = parseIdRef(key);
239
+ if (!parsed)
240
+ continue;
241
+ const el = resolveIdVariable(parsed.varName, target, options.withIds);
242
+ if (!el)
243
+ continue;
244
+ const syntheticKey = parsed.remainingPath ? `${parsed.remainingPath} =>` : ' =>';
245
+ const syntheticPattern = { [syntheticKey]: expandedPattern[key] };
246
+ processHandlerCommands(el, [syntheticKey], syntheticPattern, options, permissions);
247
+ }
248
+ });
233
249
  }
234
- // Process inferred assignments — dynamically imported only when option is present
250
+ // Process inferred assignments (sync)
235
251
  if (options.inferredAssignments) {
236
- const { processInferredAssignments } = await import('./inferredAssignments.js');
237
- await processInferredAssignments(target, options.from, options.inferredAssignments);
238
- // Set up MutationObserver for new matching elements if beVigilant
252
+ processInferredAssignments(target, options.from, options.inferredAssignments);
253
+ // beVigilant fire-and-forget (async)
239
254
  if (options.inferredAssignments.beVigilant) {
240
255
  if (!options.signal) {
241
256
  throw new Error('assignFrom: inferredAssignments.beVigilant requires options.signal (AbortSignal) for cleanup');
242
257
  }
243
- const { setupVigilantObserver } = await import('./beVigilant.js');
244
- setupVigilantObserver(target, options.from, options.inferredAssignments, options.signal);
258
+ import('./beVigilant.js').then(({ setupVigilantObserver }) => {
259
+ setupVigilantObserver(target, options.from, options.inferredAssignments, options.signal);
260
+ });
245
261
  }
246
262
  }
247
- // Process bulk enhancements — dynamically imported only when option is present
263
+ // Process bulk enhancements — fire-and-forget (async)
248
264
  if (options.enhance && options.enhance.length > 0) {
249
- const { enhanceAll } = await import('./enhanceAll.js');
250
- await enhanceAll(target, options.enhance, permissions);
265
+ import('./enhanceAll.js').then(({ enhanceAll }) => {
266
+ enhanceAll(target, options.enhance, permissions);
267
+ });
251
268
  }
252
269
  return target;
253
270
  }
254
- /**
255
- * Recursively walk an object and handle "..." spread keys.
256
- * When a "..." key is found, its value (which should be an object after protocol resolution)
257
- * is spread into the parent, replacing the "..." entry.
258
- */
259
- function handleSpreads(obj) {
260
- for (const [key, value] of Object.entries(obj)) {
261
- if (key !== '...' && typeof value === 'object' && value !== null && !Array.isArray(value)) {
262
- const proto = Object.getPrototypeOf(value);
263
- if (proto === Object.prototype || proto === null) {
264
- obj[key] = handleSpreads(value);
265
- }
266
- }
267
- }
268
- if ('...' in obj) {
269
- const spreadValue = obj['...'];
270
- delete obj['...'];
271
- if (spreadValue && typeof spreadValue === 'object') {
272
- Object.assign(obj, spreadValue);
273
- }
274
- }
275
- return obj;
276
- }
271
+ export default assignFrom;