j-templates 8.1.1 → 8.2.0

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/SYNTAX_PRIMER.md CHANGED
@@ -537,7 +537,7 @@ A store holds reactive data under string keys and diffs every write into existin
537
537
 
538
538
  | | `StoreSync` | `StoreAsync` | `PushStore` |
539
539
  | --- | --- | --- | --- |
540
- | Constructor | `new StoreSync(keyFunc?, projections?)` | `new StoreAsync(worker, keyFunc?)` | `new PushStore(source, keyFunc?, cloneData?)` |
540
+ | Constructor | `new StoreSync(keyFunc?, projections?)` | `new StoreAsync(worker, keyFunc?)` | `new PushStore(source, keyFunc?)` |
541
541
  | Diffing | main thread, synchronous | in the `Worker` you supply | in the `DiffTreeWriter` that pushes to it |
542
542
  | Writes | `void`; readable immediately | `Promise`; batched per tick, readable only after `await` | none; read-only. Changes arrive when the writer calls `Commit()` |
543
543
  | `Destroy()` | none | required; terminates the worker | required; stops listening, leaves the worker running |
@@ -652,7 +652,7 @@ store.Destroy(); // stops listening; terminate the worker yourself
652
652
  - **Create the store before the first `Commit()`.** Messages are diffs against the writer's tree, so a store created later misses the earlier changes and holds partial roots (from source). There is no catch-up message.
653
653
  - **A failed change doesn't stop either side.** After a write throws, every later write before `Commit()` throws the same error. `Commit()` still sends the writes made before the failure, skips projections, then posts `DIFF_TREE_ERROR`. The store applies the changes and sets `Error` (verified). The next commit brings projections up to date.
654
654
  - `store.Error` is reactive: the last commit's error, or `null`. It's cleared when a later commit's changes arrive (verified).
655
- - `cloneData` (default `false`): a `Worker` or `MessagePort` already delivers a structured-clone copy, so no copy is made. A same-thread `EventTarget` delivers the writer's own objects; post copies, or pass `true`. The flag only covers new roots and splices; changes to an existing root are always copied (from source).
655
+ - The store deep-clones every value it applies, so even a same-thread `EventTarget` that delivers the writer's own objects never leaves the store sharing them with the writer's tree (from source).
656
656
 
657
657
  ## ObservableScope and ObservableNode
658
658
 
@@ -707,13 +707,12 @@ ObservableNode.Unwrap(proxy) // raw underlying value
707
707
  ObservableNode.Apply(node, value) // merge a full value in place; node identity kept
708
708
  ObservableNode.Snapshot(node) // cached plain copy
709
709
  ObservableNode.Clone(value) // replaces proxies with plain data, mutating plain containers in place; returns value
710
- ObservableNode.Update(node, prop?) // force a change notification
711
710
  ObservableNode.CreateFactory(alias?) // proxy factory with aliasing (used by stores)
712
711
  ```
713
712
 
714
713
  - `Snapshot` returns the same object on repeated calls until a write. After a write, unchanged nested parts are shared (`===`) with the previous snapshot. Snapshots are **not frozen**; treat them as read-only.
715
714
  - `JSON.stringify(node)` and `node.toJSON()` use the cached snapshot.
716
- - `ApplyDiff`, `ApplySplice`, `Assign`, `Touch` and `Read` also exist; they are internal to the stores.
715
+ - `ApplyDiff`, `ApplySplice` and `Touch` also exist; they are internal to the stores. `ApplyDiff` and `ApplySplice` deep-clone what they write, so the node never shares objects with the caller.
717
716
 
718
717
  **Snapshot reads do not subscribe.** `JSON.stringify(node)`, `node.toJSON()` and `ObservableNode.Snapshot(node)` register no dependencies, so a scope that reads only through them never updates (verified). Read properties directly, or use `ObservableNode.Clone(node)`, which tracks every nested field and leaves the source untouched:
719
718
 
@@ -1,47 +1,78 @@
1
- import { type JsonDiffResult } from "../../Utils/json";
2
- import { DiffSpliceResult, DiffTreeProjectionMap } from "./diffTree.types";
3
- /**
4
- * Diff tree implementation. Maintains root state and computes diffs for path/value updates.
5
- * Used directly by `DiffSync`, and is the building block for a `StoreAsync` worker entry
6
- * file: construct one with the desired `keyFunc`/`projections` and connect it to the worker
7
- * thread with `ConnectWorkerToDiffTree` (see `defaultDiffTreeWorker.ts` for the no-argument
8
- * case, or a custom entry file when either is needed).
9
- */
1
+ import { JsonDiffResult } from '../../Utils/json';
2
+ import { IKeyFunc, IRouteDefinitions } from './utils/diffTree.types';
10
3
  export declare class DiffTree {
11
4
  private keyFunc?;
5
+ private routeDefinitions;
12
6
  private rootState;
7
+ private rootRoute;
8
+ private commitedSnapshot;
9
+ private routeCache;
10
+ private routeKeyFunc;
11
+ private resolver;
12
+ private snapshot;
13
13
  private projectionOrder;
14
- private projectionMemo;
14
+ private routeVersion;
15
+ constructor(keyFunc?: IKeyFunc, routeDefinitions?: IRouteDefinitions);
16
+ WritePath(path: string, value: any): void;
17
+ PatchPath(path: string, patch: any): void;
18
+ PushPath(path: string, items: unknown[]): void;
19
+ SplicePath(path: string, start: number, deleteCount: number, items: any[]): void;
15
20
  /**
16
- * Creates a DiffTree instance.
17
- * @param keyFunc - Optional function to extract a key from objects
18
- * @param projections - Optional map of derived-value projections, keyed by id. Cycles
19
- * and reads of an unregistered projection id are rejected here, at construction.
21
+ * Deletes a route: its state, its child routes and projection outputs, and its route
22
+ * instances. The projection order is rebuilt on the next `RunProjections`, so deleting
23
+ * several routes in a row sorts once.
24
+ * @param routePath The route to delete, e.g. `/channel/1/`
25
+ * @throws When the path is the root route or has a property path, e.g. `/channel/1/name`
20
26
  */
21
- constructor(keyFunc?: {
22
- (val: any): string;
23
- }, projections?: DiffTreeProjectionMap);
24
- RunProjections(): JsonDiffResult;
27
+ DeleteRoute(routePath: string): void;
25
28
  /**
26
- * Computes the diff between a new value and the current value at a path.
27
- * @param path - Dot-separated path to the value
28
- * @param value - The new value to compare
29
- * @returns Diff results showing changes
29
+ * Reads the raw value at a path, root (`value.prop`) or routed (`/channel/1/value.prop`).
30
+ * @returns The value, or undefined when any segment of the path doesn't exist
30
31
  */
31
- DiffPath(path: string, value: any): JsonDiffResult;
32
- PatchPath(path: string, patch: any): JsonDiffResult;
33
- PushPath(path: string, items: unknown[]): JsonDiffResult;
34
- SplicePath(path: string, start: number, deleteCount: number, items: any[]): DiffSpliceResult;
32
+ ReadPath(path: string): any;
35
33
  /**
36
- * Retrieves the current value at a path.
37
- * @param path - Dot-separated path to the value
38
- * @returns The value at the specified path
34
+ * Runs every projection of every route instance, dependencies first. A projection is skipped
35
+ * when no route it reads through changed since its last run, when a read's parent is missing,
36
+ * or when every read value is the same as its last run. Outputs are written as-is to
37
+ * `$projection_<key>` in their route, without a diff.
38
+ * @throws When projections depend on each other in a cycle
39
39
  */
40
- GetPath(path: string): any;
40
+ RunProjections(): void;
41
+ Commit(): JsonDiffResult;
42
+ Snapshot(path: string): any;
41
43
  /**
42
- * Returns an immutable, cached snapshot of the value at a path (empty string for the
43
- * whole tree). Not part of `IDiffTree`; internal, for a future feature.
44
- * @param path - Dot-separated path to the value
44
+ * Finds the route instance for a route path (the portion of a path before the last `/`), creating it (and any missing
45
+ * parent instances) from the matching definition on first use.
46
+ * @returns The deepest route instance matched by a definition, or the root route when none matches
45
47
  */
46
- Snapshot(path: string): any;
48
+ private GetRouteInstance;
49
+ /**
50
+ * Whether every route a projection's reads resolved through on its last full read still has
51
+ * the snapshot it read: a write anywhere under a route clears its cached snapshot, so then no
52
+ * read can have changed. A wildcard read also needs the same route instances to exist.
53
+ */
54
+ private SourcesUnchanged;
55
+ /**
56
+ * Takes a new snapshot of every source in the route a projection just wrote its output to. The
57
+ * write cleared that route's snapshot, but nothing the projection read changed between its read
58
+ * and the write, so without this the next run would read everything again for nothing.
59
+ */
60
+ private RefreshSources;
61
+ /**
62
+ * Snapshots of the values at each path, read through the route's root object so keyed
63
+ * values resolve through their own copy.
64
+ * @param sources Filled with what each read resolved through; a read after a missing one is left undefined
65
+ * @param previous The sources of the last full read, so a wildcard read reuses its unchanged matches
66
+ * @returns The values, or undefined when any of them is missing
67
+ */
68
+ private ReadValues;
69
+ /** The value at a path, whether it was found, and the route it was read through, when there is one */
70
+ private ReadValue;
71
+ /**
72
+ * Reads every route a wildcard path matches. While no route instance was added or removed since
73
+ * the last read, the matches are the same, and a match whose route has the same snapshot keeps
74
+ * the value it read.
75
+ */
76
+ private ReadWildcard;
77
+ private ReadMatch;
47
78
  }