j-templates 8.0.24 → 8.0.26

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/Node/vNode.js CHANGED
@@ -212,7 +212,7 @@ function ToArray(result) {
212
212
  function MapNode(vnode) {
213
213
  switch (vnode.type) {
214
214
  case FRAGMENT_NODE:
215
- return vnode.children.flatMap(MapNode);
215
+ return vnode.children?.flatMap(MapNode) ?? null;
216
216
  default:
217
217
  return vnode.node;
218
218
  }
@@ -0,0 +1,135 @@
1
+ import { type JsonDiffResult } from "../../Utils/json";
2
+ /**
3
+ * Interface for diff tree operations.
4
+ */
5
+ export interface IDiffTree {
6
+ /**
7
+ * Computes diffs for a batch of path/value pairs.
8
+ * @param data - Array of objects with path and value properties
9
+ * @returns Combined diff results
10
+ */
11
+ DiffBatch(data: Array<{
12
+ path: string;
13
+ value: any;
14
+ }>): JsonDiffResult;
15
+ /**
16
+ * Computes the diff between a new value and the current value at a path.
17
+ * @param path - Dot-separated path to the value
18
+ * @param value - The new value to compare
19
+ * @returns Diff results showing changes
20
+ */
21
+ DiffPath(path: string, value: any, flatten?: boolean): JsonDiffResult;
22
+ SplicePath(path: string, start: number, deleteCount: number | undefined, items: any[], flatten?: boolean): DiffSpliceResult;
23
+ }
24
+ /**
25
+ * Constructor interface for IDiffTree.
26
+ */
27
+ export interface IDiffTreeConstructor {
28
+ /**
29
+ * Creates a new IDiffTree instance.
30
+ * @param keyFunc - Optional function to extract a key from a value
31
+ * @param projections - Optional map of derived-value projections, keyed by id
32
+ */
33
+ new (keyFunc?: {
34
+ (val: any): string;
35
+ }, projections?: DiffTreeProjectionMap): IDiffTree;
36
+ }
37
+ /**
38
+ * A pure function computing a derived value from the current values at `reads`. Called with
39
+ * the resolved read paths (`keys`) first, then each value positionally in the same order as
40
+ * `reads`, so each value parameter can be typed at the call site instead of cast out of a
41
+ * generic read. A trailing wildcard read expands to one key/value pair per matched root key,
42
+ * so `keys` lets the function tell which root each trailing value came from.
43
+ */
44
+ export type DiffTreeProjectionFunc = (keys: string[], ...values: any[]) => any;
45
+ /**
46
+ * A projection's declaration: the dot-separated paths it reads (a plain tree path, an
47
+ * entity's key, or another projection's `$projection_<id>` key for chaining) and the
48
+ * function computing its result from their current values, positionally. Declaring `reads`
49
+ * up front lets the tree topologically sort projections and reject a dependency cycle at
50
+ * construction instead of on first write.
51
+ *
52
+ * The final entry in `reads` may instead be a wildcard root key (e.g. `"user_*"`, no dot
53
+ * nesting) matching every root key with that prefix; each match is read and passed to the
54
+ * projection as its own trailing key/value pair. A wildcard read can't be chained off a
55
+ * projection's `$projection_<id>` key and can't appear anywhere but last.
56
+ */
57
+ export type DiffTreeProjectionDefinition = {
58
+ reads: string[];
59
+ projection: DiffTreeProjectionFunc;
60
+ };
61
+ /** A map of projection id to its declaration, keyed by the id used in `$projection_<id>`. */
62
+ export type DiffTreeProjectionMap = {
63
+ [id: string]: DiffTreeProjectionDefinition;
64
+ };
65
+ /** Reserved key prefix a projection's result is stored under: `$projection_<id>`. */
66
+ export declare const PROJECTION_PREFIX = "$projection_";
67
+ export type DiffSpliceResult<T = unknown> = {
68
+ /** Array path segments (strings for object keys, numbers for array indices) */
69
+ path: (string | number)[];
70
+ /** Required by the splice operation **/
71
+ start: number;
72
+ deleteCount: number;
73
+ spliceResult: any[];
74
+ diffResult: JsonDiffResult;
75
+ };
76
+ /**
77
+ * Diff tree implementation. Maintains root state and computes diffs for path/value updates.
78
+ * Used directly by `DiffSync`, and is the building block for a `DiffAsync` worker entry
79
+ * file: construct one with the desired `keyFunc`/`projections` and connect it to the worker
80
+ * thread with `ConnectWorkerToDiffTree` (see `defaultDiffTreeWorker.ts` for the no-argument
81
+ * case, or a custom entry file when either is needed).
82
+ */
83
+ export declare class DiffTree {
84
+ private keyFunc?;
85
+ private rootState;
86
+ private projectionOrder;
87
+ private projectionMemo;
88
+ /**
89
+ * Creates a DiffTree instance.
90
+ * @param keyFunc - Optional function to extract a key from objects
91
+ * @param projections - Optional map of derived-value projections, keyed by id. Cycles
92
+ * and reads of an unregistered projection id are rejected here, at construction.
93
+ */
94
+ constructor(keyFunc?: {
95
+ (val: any): string;
96
+ }, projections?: DiffTreeProjectionMap);
97
+ private RunProjections;
98
+ /**
99
+ * Computes diffs for a batch of path/value pairs.
100
+ * @param data - Array of objects with path and value properties
101
+ * @returns Combined diff results
102
+ */
103
+ DiffBatch(data: Array<{
104
+ path: string;
105
+ value: any;
106
+ }>): {
107
+ path: (string | number)[];
108
+ value: unknown;
109
+ }[];
110
+ /**
111
+ * Computes the diff between a new value and the current value at a path.
112
+ * @param path - Dot-separated path to the value
113
+ * @param value - The new value to compare
114
+ * @returns Diff results showing changes
115
+ */
116
+ DiffPath(path: string, value: any, flatten?: boolean): JsonDiffResult;
117
+ PatchPath(path: string, patch: any): JsonDiffResult;
118
+ PushPath(path: string, data: unknown[]): {
119
+ path: (string | number)[];
120
+ value: unknown;
121
+ }[];
122
+ SplicePath(path: string, start: number, deleteCount: number, items: any[], flatten?: boolean): DiffSpliceResult<unknown>;
123
+ /**
124
+ * Retrieves the current value at a path.
125
+ * @param path - Dot-separated path to the value
126
+ * @returns The value at the specified path
127
+ */
128
+ GetPath(path: string): any;
129
+ /**
130
+ * Returns an immutable, cached snapshot of the value at a path (empty string for the
131
+ * whole tree). Not part of `IDiffTree`; internal, for a future feature.
132
+ * @param path - Dot-separated path to the value
133
+ */
134
+ Snapshot(path: string): any;
135
+ }
@@ -0,0 +1,445 @@
1
+ import { ArraysEqual } from "../../Utils/array";
2
+ import { JsonDeepClone, JsonDiff, JsonMerge, JsonType } from "../../Utils/json";
3
+ /** Reserved key prefix a projection's result is stored under: `$projection_<id>`. */
4
+ export const PROJECTION_PREFIX = "$projection_";
5
+ /**
6
+ * Flattens nested objects/arrays, extracting keyed objects to root.
7
+ * @param root - Root object to store flattened values
8
+ * @param value - Value to flatten
9
+ * @param keyFunc - Function to extract key from objects
10
+ * @returns The root object with flattened values
11
+ */
12
+ function FlattenValue(root, value, keyFunc) {
13
+ const type = JsonType(value);
14
+ switch (type) {
15
+ case "array":
16
+ const typedArray = value;
17
+ for (let x = 0; x < typedArray.length; x++)
18
+ FlattenValue(root, typedArray[x], keyFunc);
19
+ break;
20
+ case "object":
21
+ const typedObject = value;
22
+ const key = keyFunc(typedObject);
23
+ if (key)
24
+ root[key] = typedObject;
25
+ const keys = Object.keys(typedObject);
26
+ for (let x = 0; x < keys.length; x++)
27
+ FlattenValue(root, typedObject[keys[x]], keyFunc);
28
+ }
29
+ return root;
30
+ }
31
+ /**
32
+ * Retrieves a value from a source object using a dot-separated path.
33
+ * @param source - The source object
34
+ * @param path - Dot-separated path to the value (empty string returns source)
35
+ * @returns The value at the specified path
36
+ */
37
+ function GetPathValue(source, path, keyFunc) {
38
+ if (path === "")
39
+ return [source, true];
40
+ const parts = path.split(".");
41
+ let curr = source;
42
+ let x = 0;
43
+ for (; curr && x < parts.length - 1; x++)
44
+ curr = ResolveKeyed(source, curr[parts[x]], keyFunc);
45
+ if (curr && Object.hasOwn(curr, parts[x])) {
46
+ curr = ResolveKeyed(source, curr[parts[x]], keyFunc);
47
+ x++;
48
+ }
49
+ else
50
+ curr = undefined;
51
+ return [curr, x === parts.length];
52
+ }
53
+ /**
54
+ * Sets a value in a source object at the specified path.
55
+ * @param source - The source object
56
+ * @param path - Array of path segments (strings for keys, numbers for indices)
57
+ * @param value - The value to set
58
+ */
59
+ function SetPathValue(source, path, value) {
60
+ if (path.length === 0)
61
+ return;
62
+ let curr = source;
63
+ let x = 0;
64
+ for (; x < path.length - 1; x++)
65
+ curr = curr[path[x]];
66
+ curr[path[x]] = value;
67
+ InvalidateSnapshotForValue(curr);
68
+ }
69
+ const valueSnapshotCache = new WeakMap();
70
+ const valueSnapshotHierarchy = new WeakMap();
71
+ function CreateSnapshotLink(child, parent) {
72
+ let parents = valueSnapshotHierarchy.get(child);
73
+ if (!parents)
74
+ valueSnapshotHierarchy.set(child, (parents = new Set()));
75
+ parents.add(parent);
76
+ }
77
+ /**
78
+ * Clears the cached snapshot for a raw value that was just written to, and climbs to
79
+ * every parent whose own cached snapshot embedded it, so a write to a deeply nested value
80
+ * invalidates every cached ancestor that still holds a snapshot. Mirrors ObservableNode's
81
+ * `InvalidateSnapshotForValue`.
82
+ */
83
+ function InvalidateSnapshotForValue(value) {
84
+ valueSnapshotCache.delete(value);
85
+ const parents = valueSnapshotHierarchy.get(value);
86
+ if (parents === undefined)
87
+ return;
88
+ valueSnapshotHierarchy.delete(value);
89
+ for (const parent of parents)
90
+ if (valueSnapshotCache.has(parent))
91
+ InvalidateSnapshotForValue(parent);
92
+ }
93
+ /**
94
+ * An immutable snapshot of a raw tree value (arrays and plain objects; other values are
95
+ * returned as-is), cached per raw value and reused until a write invalidates it. Mirrors
96
+ * ObservableNode's `CreateSnapshot`: a rebuild makes new containers but reuses the cached
97
+ * snapshots of unchanged children. A keyed child is read through the root entity `keyFunc`
98
+ * resolves it to, the parallel of ObservableNode's `alias`, so every reference to the same
99
+ * entity shares one snapshot and a write to the entity invalidates every holder.
100
+ */
101
+ function CreateSnapshot(source, keyFunc, value) {
102
+ if (JsonType(value) === "value")
103
+ return value;
104
+ const cached = valueSnapshotCache.get(value);
105
+ if (cached !== undefined)
106
+ return cached;
107
+ let snapshot;
108
+ if (Array.isArray(value)) {
109
+ snapshot = new Array(value.length);
110
+ for (let x = 0; x < value.length; x++)
111
+ snapshot[x] = ChildSnapshot(source, keyFunc, value, value[x]);
112
+ }
113
+ else {
114
+ snapshot = {};
115
+ const keys = Object.keys(value);
116
+ for (let x = 0; x < keys.length; x++)
117
+ snapshot[keys[x]] = ChildSnapshot(source, keyFunc, value, value[keys[x]]);
118
+ }
119
+ valueSnapshotCache.set(value, snapshot);
120
+ return snapshot;
121
+ }
122
+ /**
123
+ * A child's snapshot, linking the child to its parent so a write to the child invalidates
124
+ * the parent. A keyed child's snapshot comes from its root entity (`source[key]`), so the
125
+ * root is linked too; the parent's own embedded copy stays linked because a positional
126
+ * write (one that changes which entity a slot holds) mutates that copy in place.
127
+ */
128
+ function ChildSnapshot(source, keyFunc, parent, child) {
129
+ if (JsonType(child) === "value")
130
+ return child;
131
+ CreateSnapshotLink(child, parent);
132
+ if (keyFunc) {
133
+ const key = keyFunc(child);
134
+ if (key) {
135
+ const root = source[key];
136
+ if (root !== undefined && root !== child) {
137
+ CreateSnapshotLink(root, parent);
138
+ return CreateSnapshot(source, keyFunc, root);
139
+ }
140
+ }
141
+ }
142
+ return CreateSnapshot(source, keyFunc, child);
143
+ }
144
+ /**
145
+ * Topologically sorts projections by their declared `reads` into a single array, run
146
+ * sequentially: a read of another projection's `$projection_<id>` output is a dependency
147
+ * edge, so that projection is sorted earlier. Runs once, at construction, so a cycle (or a
148
+ * read of an id that isn't registered) is rejected immediately instead of on first write.
149
+ * Also validates each projection's wildcard reads here, at construction: a wildcard must be
150
+ * the final read, must not have dot nesting, and can't target a projection's
151
+ * `$projection_<id>` key.
152
+ */
153
+ function BuildProjectionOrder(projections) {
154
+ const order = [];
155
+ const state = new Map();
156
+ function Visit(id, stack) {
157
+ if (state.get(id) === "done")
158
+ return;
159
+ if (state.get(id) === "visiting")
160
+ throw new Error(`DiffTree projection cycle detected: ${stack.concat(id).join(" -> ")}`);
161
+ const def = projections[id];
162
+ if (!def)
163
+ throw new Error(`DiffTree projection "${id}" is not defined`);
164
+ state.set(id, "visiting");
165
+ for (let x = 0; x < def.reads.length; x++) {
166
+ const read = def.reads[x];
167
+ const root = read.split(".", 1)[0];
168
+ if (root.endsWith("*") && x !== def.reads.length - 1)
169
+ throw `Wildcard reads only supported as the final read: ${root}`;
170
+ if (root.endsWith("*") && root.includes("."))
171
+ throw `Wildcard reads do not support dot nesting: ${root}`;
172
+ if (root.startsWith(PROJECTION_PREFIX) && read.endsWith("*"))
173
+ throw `Projection keys do not support wildcard reads: ${root}`;
174
+ if (root.startsWith(PROJECTION_PREFIX))
175
+ Visit(root.slice(PROJECTION_PREFIX.length), stack.concat(id));
176
+ }
177
+ state.set(id, "done");
178
+ order.push({ id, ...def });
179
+ }
180
+ const ids = Object.keys(projections);
181
+ for (let x = 0; x < ids.length; x++)
182
+ Visit(ids[x], []);
183
+ return order;
184
+ }
185
+ /**
186
+ * Runs every registered projection, sequentially in the dependency order
187
+ * `BuildProjectionOrder` computed, skipping ones whose declared `reads` all still resolve
188
+ * to the same cached snapshot as last time (so nothing they read has changed), and returns
189
+ * the diff of every projection whose result changed. A projection's result is stored at
190
+ * `$projection_<id>` the same way a `DiffPath` write is: diffed against what was there
191
+ * before and applied path by path, so a change to one field of a projection's result
192
+ * produces one small diff entry rather than replacing the whole thing.
193
+ *
194
+ * Because `order` runs dependencies before dependents, a projection that reads another's
195
+ * `$projection_<id>` output (chaining) always sees that projection's up-to-date result
196
+ * within the same pass.
197
+ *
198
+ * A projection whose `reads` names a root key that doesn't exist yet (a plain path never
199
+ * written, an entity never seen, or another projection that hasn't produced a result) is
200
+ * not run at all, so a projection's author never has to guard every parameter against
201
+ * missing data. It's tried again on every later write until every read exists. This also
202
+ * makes a blocked dependency propagate for free: a projection that never runs never writes
203
+ * `$projection_<id>`, so anything chaining off it stays blocked in turn.
204
+ *
205
+ * A trailing wildcard read is expanded against the current root keys before the projection
206
+ * runs: every root key matching the prefix is read and appended as its own key/value pair,
207
+ * so the projection is called with `(keys, ...values)` where `keys` is every resolved read
208
+ * path (fixed reads first, then each matched wildcard key) positional with `values`.
209
+ *
210
+ * @param source - The root tree object
211
+ * @param keyFunc - Optional function to extract a key from objects, for reading through roots
212
+ * @param projections - Dependency-sorted projection entries, from `BuildProjectionOrder`
213
+ * @param memo - Per-tree, cross-call memo of each projection's last-read values, by id,
214
+ * positional with its `reads`
215
+ * @returns Diff entries for every projection whose result changed
216
+ */
217
+ function RunProjections(source, keyFunc, projections, memo) {
218
+ const diffResult = [];
219
+ for (let x = 0; x < projections.length; x++) {
220
+ const { id, reads, projection } = projections[x];
221
+ const values = [];
222
+ const keys = [];
223
+ let allPathsExist = true;
224
+ let y = 0;
225
+ for (; allPathsExist && y < reads.length && !reads[y].endsWith("*"); y++) {
226
+ const read = reads[y];
227
+ const [value, found] = GetPathValue(source, read, keyFunc);
228
+ allPathsExist = found;
229
+ keys.push(read);
230
+ values.push((found && CreateSnapshot(source, keyFunc, value)) || value);
231
+ }
232
+ if (y < reads.length && reads[y].endsWith("*")) {
233
+ const prefix = reads[y].slice(0, reads[y].length - 1);
234
+ const matchedKeys = Object.keys(source).filter(key => key.startsWith(prefix));
235
+ for (let z = 0; z < matchedKeys.length; z++) {
236
+ // No keyFunc passed because dot paths are not supported for wildcard paths
237
+ const [value, found] = GetPathValue(source, matchedKeys[z]);
238
+ allPathsExist = allPathsExist && found;
239
+ keys.push(matchedKeys[z]);
240
+ values.push((found && CreateSnapshot(source, keyFunc, value)) || value);
241
+ }
242
+ }
243
+ if (allPathsExist) {
244
+ const priorValues = memo.get(id);
245
+ if (!ArraysEqual(priorValues, values)) {
246
+ const result = projection(keys, ...values);
247
+ memo.set(id, values);
248
+ // The same diff-and-apply UpdateSource uses for any other write, so a keyed entity
249
+ // embedded in a projection's result is flattened to its own root like any other write,
250
+ // and the diff reported here is unfiltered, the same as DiffPath/DiffBatch return.
251
+ diffResult.push(...UpdateSource(source, PROJECTION_PREFIX + id, result, true, keyFunc));
252
+ }
253
+ }
254
+ }
255
+ return diffResult;
256
+ }
257
+ function ResolveKeyed(source, value, keyFunc) {
258
+ if (keyFunc === undefined || JsonType(value) === 'value')
259
+ return value;
260
+ const key = keyFunc(value);
261
+ return (key && source[key]) ?? value;
262
+ }
263
+ /**
264
+ * Resolves a path to its keyed root object if applicable.
265
+ * Searches up the path to find an object with a key from keyFunc.
266
+ * @param source - The source object
267
+ * @param path - Dot-separated path
268
+ * @param keyFunc - Function to extract key from objects
269
+ * @returns The resolved path (possibly shortened to root key)
270
+ */
271
+ function ResolveKeyPath(source, path, keyFunc) {
272
+ const parts = path.split(".");
273
+ const pathValues = new Array(parts.length - 1);
274
+ let curr = source;
275
+ for (let x = 0; x < parts.length - 1; x++) {
276
+ curr = curr[parts[x]];
277
+ pathValues[x] = curr;
278
+ }
279
+ let y = pathValues.length - 1;
280
+ for (; y >= 0 && !(JsonType(pathValues[y]) === "object" && keyFunc(pathValues[y])); y--) { }
281
+ if (y >= 0) {
282
+ const key = keyFunc(pathValues[y]);
283
+ parts.splice(0, y + 1, key);
284
+ return parts.join(".");
285
+ }
286
+ return path;
287
+ }
288
+ /**
289
+ * Updates the source with a new value at the specified path and computes diffs.
290
+ * Also updates any keyed objects that were nested in the value.
291
+ * @param source - The source object to update
292
+ * @param path - Dot-separated path to update
293
+ * @param value - The new value
294
+ * @param keyFunc - Optional function to extract keys from objects
295
+ * @returns Diff results showing all changes made
296
+ */
297
+ function UpdateSource(source, path, value, flatten, keyFunc) {
298
+ const diffResult = [];
299
+ if (keyFunc) {
300
+ const keyPath = ResolveKeyPath(source, path, keyFunc);
301
+ if (keyPath !== path) {
302
+ const keyDiffResult = UpdateSource(source, keyPath, value, flatten, keyFunc);
303
+ diffResult.push(...keyDiffResult);
304
+ }
305
+ }
306
+ const [sourceValue] = GetPathValue(source, path);
307
+ JsonDiff(value, sourceValue, path, diffResult);
308
+ if (flatten && keyFunc) {
309
+ let flattened = {};
310
+ flattened = FlattenValue(flattened, value, keyFunc);
311
+ flattened = JsonDeepClone(flattened);
312
+ const keys = Object.keys(flattened);
313
+ for (let x = 0; x < keys.length; x++)
314
+ JsonDiff(flattened[keys[x]], source[keys[x]], keys[x], diffResult);
315
+ }
316
+ const filteredDiffResult = diffResult.filter((diff) => diff.value !== undefined);
317
+ for (let x = 0; x < filteredDiffResult.length; x++) {
318
+ SetPathValue(source, filteredDiffResult[x].path, filteredDiffResult[x].value);
319
+ }
320
+ return diffResult;
321
+ }
322
+ function SpliceSource(source, path, start, deleteCount, items, flatten, keyFunc) {
323
+ const [sourceValue, found] = GetPathValue(source, path);
324
+ if (!found)
325
+ throw `Value not found at path ${path}`;
326
+ if (!Array.isArray(sourceValue))
327
+ throw `Value found at path ${path} is not an array`;
328
+ deleteCount ??= sourceValue.length - start;
329
+ const spliceResult = {
330
+ path: path.split("."),
331
+ start,
332
+ deleteCount,
333
+ spliceResult: null,
334
+ diffResult: [],
335
+ };
336
+ spliceResult.spliceResult = sourceValue.splice(start, deleteCount, ...items);
337
+ InvalidateSnapshotForValue(sourceValue);
338
+ if (flatten && keyFunc) {
339
+ // Register the keyed entities the new items carry, diffed against the roots
340
+ // already held: a new entity is added whole, one re-inserted unchanged (an undo)
341
+ // produces no diff, so its readers aren't woken.
342
+ const flattened = JsonDeepClone(FlattenValue({}, items, keyFunc));
343
+ const keys = Object.keys(flattened);
344
+ for (let x = 0; x < keys.length; x++)
345
+ JsonDiff(flattened[keys[x]], source[keys[x]], keys[x], spliceResult.diffResult);
346
+ }
347
+ const filteredDiffResult = spliceResult.diffResult.filter((diff) => diff.value !== undefined);
348
+ for (let x = 0; x < filteredDiffResult.length; x++) {
349
+ SetPathValue(source, filteredDiffResult[x].path, filteredDiffResult[x].value);
350
+ }
351
+ return spliceResult;
352
+ }
353
+ /**
354
+ * Diff tree implementation. Maintains root state and computes diffs for path/value updates.
355
+ * Used directly by `DiffSync`, and is the building block for a `DiffAsync` worker entry
356
+ * file: construct one with the desired `keyFunc`/`projections` and connect it to the worker
357
+ * thread with `ConnectWorkerToDiffTree` (see `defaultDiffTreeWorker.ts` for the no-argument
358
+ * case, or a custom entry file when either is needed).
359
+ */
360
+ export class DiffTree {
361
+ /**
362
+ * Creates a DiffTree instance.
363
+ * @param keyFunc - Optional function to extract a key from objects
364
+ * @param projections - Optional map of derived-value projections, keyed by id. Cycles
365
+ * and reads of an unregistered projection id are rejected here, at construction.
366
+ */
367
+ constructor(keyFunc, projections) {
368
+ this.keyFunc = keyFunc;
369
+ this.rootState = {};
370
+ this.projectionMemo = new Map();
371
+ this.projectionOrder = projections ? BuildProjectionOrder(projections) : [];
372
+ }
373
+ RunProjections() {
374
+ if (this.projectionOrder.length === 0)
375
+ return [];
376
+ return RunProjections(this.rootState, this.keyFunc, this.projectionOrder, this.projectionMemo);
377
+ }
378
+ /**
379
+ * Computes diffs for a batch of path/value pairs.
380
+ * @param data - Array of objects with path and value properties
381
+ * @returns Combined diff results
382
+ */
383
+ DiffBatch(data) {
384
+ const results = data
385
+ .map(({ path, value }) => UpdateSource(this.rootState, path, value, true, this.keyFunc))
386
+ .flat(1);
387
+ results.push(...this.RunProjections());
388
+ return results;
389
+ }
390
+ /**
391
+ * Computes the diff between a new value and the current value at a path.
392
+ * @param path - Dot-separated path to the value
393
+ * @param value - The new value to compare
394
+ * @returns Diff results showing changes
395
+ */
396
+ DiffPath(path, value, flatten = true) {
397
+ const results = UpdateSource(this.rootState, path, value, flatten, this.keyFunc);
398
+ results.push(...this.RunProjections());
399
+ return results;
400
+ }
401
+ PatchPath(path, patch) {
402
+ const value = this.Snapshot(path);
403
+ JsonMerge(value, patch);
404
+ const results = UpdateSource(this.rootState, path, value, true, this.keyFunc);
405
+ results.push(...this.RunProjections());
406
+ return results;
407
+ }
408
+ PushPath(path, data) {
409
+ const value = this.GetPath(path);
410
+ if (!Array.isArray(value))
411
+ throw new Error(`Value at path ${path} is not an array`);
412
+ const batch = data.map(function (d, i) {
413
+ return {
414
+ path: `${path}.${value.length + i}`,
415
+ value: d
416
+ };
417
+ });
418
+ return this.DiffBatch(batch);
419
+ }
420
+ SplicePath(path, start, deleteCount, items, flatten = true) {
421
+ const spliceResult = SpliceSource(this.rootState, path, start, deleteCount, items, flatten, this.keyFunc);
422
+ spliceResult.diffResult.push(...this.RunProjections());
423
+ return spliceResult;
424
+ }
425
+ /**
426
+ * Retrieves the current value at a path.
427
+ * @param path - Dot-separated path to the value
428
+ * @returns The value at the specified path
429
+ */
430
+ GetPath(path) {
431
+ const [value] = GetPathValue(this.rootState, path, this.keyFunc);
432
+ return value;
433
+ }
434
+ /**
435
+ * Returns an immutable, cached snapshot of the value at a path (empty string for the
436
+ * whole tree). Not part of `IDiffTree`; internal, for a future feature.
437
+ * @param path - Dot-separated path to the value
438
+ */
439
+ Snapshot(path) {
440
+ const [value, found] = GetPathValue(this.rootState, path, this.keyFunc);
441
+ if (!found)
442
+ throw `Unable to get snapshot for path ${path}`;
443
+ return CreateSnapshot(this.rootState, this.keyFunc, value);
444
+ }
445
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "j-templates",
3
- "version": "8.0.24",
3
+ "version": "8.0.26",
4
4
  "description": "j-templates",
5
5
  "license": "MIT",
6
6
  "repository": "https://github.com/TypesInCode/jTemplates",