j-templates 8.0.27 → 8.1.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 +105 -29
- package/Store/Diff/diffSync.d.ts +1 -1
- package/Store/Diff/diffTree.d.ts +2 -69
- package/Store/Diff/diffTree.js +1 -2
- package/Store/Diff/diffTree.types.d.ts +105 -0
- package/Store/Diff/diffTree.types.js +12 -0
- package/Store/Diff/diffTreeWorker.d.ts +1 -16
- package/Store/Diff/diffTreeWriter.d.ts +103 -0
- package/Store/Diff/diffTreeWriter.js +188 -0
- package/Store/Diff/index.d.ts +1 -0
- package/Store/Diff/index.js +1 -0
- package/Store/Store/pushStore.d.ts +57 -0
- package/Store/Store/pushStore.js +96 -0
- package/Store/Store/store.d.ts +20 -10
- package/Store/Store/store.js +16 -6
- package/Store/Store/storeSync.d.ts +2 -2
- package/Store/Tree/observableScope.d.ts +1 -1
- package/Store/index.d.ts +2 -1
- package/Store/index.js +2 -1
- package/package.json +1 -1
package/SYNTAX_PRIMER.md
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# j-templates Syntax Primer
|
|
2
2
|
|
|
3
|
-
Oct
|
|
3
|
+
Oct 17, 2026 · @Jay Landrum
|
|
4
4
|
|
|
5
|
-
Reference for building apps with j-templates 8.0.
|
|
5
|
+
Reference for building apps with j-templates 8.1.0. Every behavioral claim was checked by running the library in jsdom; claims taken from type declarations or source alone are marked as such.
|
|
6
6
|
|
|
7
7
|
## How to use this document
|
|
8
8
|
|
|
9
9
|
Read Mental model, then Templates and State decorators before writing a component. Check the Traps checklist before shipping.
|
|
10
10
|
|
|
11
|
-
- **Version:** j-templates 8.0.
|
|
11
|
+
- **Version:** j-templates 8.1.0. The 8.0.19 claims were tested in jsdom with TypeScript (`experimentalDecorators`). 8.0.21 changed `peek`, how `@Computed` notifies readers, and emitter re-entry. 8.0.23 moved `Bound()` after the first render and added `Connected()`. 8.0.24 fixed dotted projection reads through keyed entities. 8.0.27 batches `StoreAsync` changes. 8.1.0 added `PushStore` and `DiffTreeWriter`, and moved the diff types and `PROJECTION_PREFIX` to `j-templates/Store/Diff`. Each of those sections was re-verified in jsdom against the version that changed it.
|
|
12
12
|
- **Not verified:** a real browser `Worker` (a structured-clone shim stood in), real focus behavior, and the `docs/`, `examples/` and `src/` folders the package does not ship.
|
|
13
13
|
- **Convention:** `this.x` in examples is a component field decorated with `@Value` or `@State` unless shown otherwise. Imports are omitted after the first example.
|
|
14
14
|
|
|
@@ -56,9 +56,10 @@ npm install j-templates
|
|
|
56
56
|
| `j-templates` | `Component`, `scope`, `gate`, `peek`, `mapped`; type `vNode` |
|
|
57
57
|
| `j-templates/DOM` | element functions (below), `text`, `fragment`, `CreateRootPropertyAssignment`, `CreateEventAssignment`, `PropertyAssignment`, `EventAssignment` |
|
|
58
58
|
| `j-templates/Utils` | `Value`, `State`, `Computed`, `ComputedAsync`, `Scope`, `Watch`, `Inject`, `Destroy`, `Bound`, `Animation`, `AnimationType`, `Injector`; type `IDestroyable` |
|
|
59
|
-
| `j-templates/Store` | `Store`, `StoreSync`, `StoreAsync`, `ObservableScope`, `ObservableNode` |
|
|
59
|
+
| `j-templates/Store` | `ReadStore`, `Store`, `StoreSync`, `StoreAsync`, `PushStore`, `ObservableScope`, `ObservableNode` |
|
|
60
|
+
| `j-templates/Store/Diff` | `PROJECTION_PREFIX`, `DIFF_TREE_EVENT`, `DIFF_TREE_ERROR`; types `DiffTreeProjectionMap`, `DiffTreeProjectionDefinition`, `DiffTreeProjectionFunc`, `IDiffPushEvent`, `DiffSpliceResult`, `IDiffTree` and the batch types. Types and constants only, so a worker bundle can import it. |
|
|
60
61
|
|
|
61
|
-
**Deep imports used below:** `j-templates/Store/Diff/diffTree` (`DiffTree
|
|
62
|
+
**Deep imports used below:** `j-templates/Store/Diff/diffTree` (`DiffTree`), `j-templates/Store/Diff/diffTreeWorker` (`ConnectWorkerToDiffTree`), `j-templates/Store/Diff/diffTreeWriter` (`DiffTreeWriter`), `j-templates/Node/vNode` (`vNode.Destroy`). `j-templates/Store/Diff/defaultDiffTreeWorker` runs worker code on import and throws outside a worker (`self is not defined`).
|
|
62
63
|
|
|
63
64
|
**Element functions** (all verified present):
|
|
64
65
|
|
|
@@ -102,20 +103,27 @@ Component.Attach(document.getElementById("app")!, counter({}));
|
|
|
102
103
|
**Members:**
|
|
103
104
|
|
|
104
105
|
- `Template(): vNode | vNode[]` — override to render. May return an array. Not overriding renders an empty host.
|
|
105
|
-
- `Bound()` / `Destroy()` — optional overrides. If you override
|
|
106
|
+
- `Bound()` / `Connected()` / `Destroy()` — optional overrides. If you override `Bound()` or `Destroy()`, call `super.Bound()` / `super.Destroy()`. `super.Bound()` starts `@Watch`; `super.Destroy()` tears down scopes and runs `@Destroy` properties. The base `Connected()` is empty.
|
|
106
107
|
- `Fire(event, payload?)` — sends a component event to the parent's `on:` handler. The payload is optional.
|
|
107
|
-
- `this.Data`, `this.Templates`, `this.Injector`, `this.VNode` (host vNode; `this.VNode.node` is the host element), `this.Destroyed`.
|
|
108
|
+
- `this.Data`, `this.Templates`, `this.Injector`, `this.VNode` (host vNode; `this.VNode.node` is the host element), `this.IsConnected`, `this.Destroyed`.
|
|
108
109
|
- Never override the constructor. Use field initializers and `Bound()`.
|
|
109
110
|
|
|
110
|
-
**Lifecycle (verified order):**
|
|
111
|
+
**Lifecycle (verified order, 8.0.23+):**
|
|
111
112
|
|
|
112
113
|
1. Field initializers run.
|
|
113
|
-
2. `
|
|
114
|
-
3. `
|
|
115
|
-
4.
|
|
116
|
-
5. The host is in the document.
|
|
114
|
+
2. `Template()` runs and its children functions build the DOM. Child components go through their own steps 1–3 here, so a child's `Bound()` runs before its parent's.
|
|
115
|
+
3. `Bound()` runs once the initial subtree is in the host: `querySelector` on the host finds rendered elements, including a child component's content. `@Watch` handlers fire inside `super.Bound()` with their initial values, so they can use those elements too. Code placed before `super.Bound()` runs before the first `@Watch` call.
|
|
116
|
+
4. `Connected()` runs once the host is first in the document, after `Bound()`. It runs parent first, then children. It fires once per component instance; a component that is removed and rendered again is a new instance and fires again.
|
|
117
117
|
|
|
118
|
-
|
|
118
|
+
By default steps 2–4 happen **after** `Attach` returns; with `SYNC_SCHEDULING=true`, before.
|
|
119
|
+
|
|
120
|
+
The host is in the document during `Bound()` only for the component passed to `Component.Attach(document.body, …)`. A child's host joins the document when its parent's children are placed, after the child's `Bound()`. So:
|
|
121
|
+
|
|
122
|
+
- Find and wire up the elements you render in `Bound()`.
|
|
123
|
+
- Put work that needs layout (measuring, sizes), focus, or the document in `Connected()`.
|
|
124
|
+
- An element that a later change renders isn't there at `Bound()` or `Connected()`. Reach it from the code that renders it, or watch for it.
|
|
125
|
+
|
|
126
|
+
`Connected()` only fires when j-templates itself puts a node in the document. A tree attached to a detached element never gets `Connected()`, even after you append that element to the document (verified). Attach native listeners to an element you render, not to the host.
|
|
119
127
|
|
|
120
128
|
**Host element.** The name passed to `ToFunction` is the host's tag. `Template()` output renders inside it:
|
|
121
129
|
|
|
@@ -352,7 +360,7 @@ Tracked array methods: `push`, `pop`, `shift`, `unshift`, `splice`, `sort`, `rev
|
|
|
352
360
|
syncFilter(value: FilterType) { this.localFilter = value; }
|
|
353
361
|
```
|
|
354
362
|
|
|
355
|
-
- Fires once in `super.Bound()` with the initial value, then on every change.
|
|
363
|
+
- Fires once in `super.Bound()` with the initial value, then on every change. Since `Bound()` runs after the first render, that first call can use rendered elements.
|
|
356
364
|
- Several synchronous writes in one tick produce one call with the latest value (`n = 1; n = 2; n = 3` → one call with `3`).
|
|
357
365
|
- Without `super.Bound()` it never fires.
|
|
358
366
|
- Use it for side effects and for copying parent `Data` into local `@Value` state. Don't use it to drive rendering; reads in templates are already reactive.
|
|
@@ -525,15 +533,15 @@ class Injector {
|
|
|
525
533
|
|
|
526
534
|
## Store
|
|
527
535
|
|
|
528
|
-
A store holds reactive data under string keys and diffs every write into existing objects in place. Use `StoreSync` unless diffing large data blocks the main thread.
|
|
536
|
+
A store holds reactive data under string keys and diffs every write into existing objects in place. Use `StoreSync` unless diffing large data blocks the main thread. Use `PushStore` when a worker produces the data itself, such as the result of a computation it runs.
|
|
529
537
|
|
|
530
|
-
| | `StoreSync` | `StoreAsync` |
|
|
531
|
-
| --- | --- | --- |
|
|
532
|
-
| Constructor | `new StoreSync(keyFunc?, projections?)` | `new StoreAsync(worker, keyFunc?)` |
|
|
533
|
-
| Diffing | main thread, synchronous | in the `Worker` you supply |
|
|
534
|
-
| Writes | `void`; readable immediately | `Promise`; readable only after `await` |
|
|
535
|
-
| `Destroy()` | none | required; terminates the worker |
|
|
536
|
-
| Backs | nothing (`@Computed` uses `ObservableNode` directly) | `@ComputedAsync` |
|
|
538
|
+
| | `StoreSync` | `StoreAsync` | `PushStore` |
|
|
539
|
+
| --- | --- | --- | --- |
|
|
540
|
+
| Constructor | `new StoreSync(keyFunc?, projections?)` | `new StoreAsync(worker, keyFunc?)` | `new PushStore(source, keyFunc?, cloneData?)` |
|
|
541
|
+
| Diffing | main thread, synchronous | in the `Worker` you supply | in the `DiffTreeWriter` that pushes to it |
|
|
542
|
+
| Writes | `void`; readable immediately | `Promise`; batched per tick, readable only after `await` | none; read-only. Changes arrive when the writer calls `Commit()` |
|
|
543
|
+
| `Destroy()` | none | required; terminates the worker | required; stops listening, leaves the worker running |
|
|
544
|
+
| Backs | nothing (`@Computed` uses `ObservableNode` directly) | `@ComputedAsync` | nothing |
|
|
537
545
|
|
|
538
546
|
### API
|
|
539
547
|
|
|
@@ -547,7 +555,7 @@ Push(key: string, ...items: unknown[]); // append to the array at key
|
|
|
547
555
|
Splice(key: string, start: number, deleteCount?: number, ...items: unknown[]); // returns removed items
|
|
548
556
|
```
|
|
549
557
|
|
|
550
|
-
`StoreAsync` has the same methods; the four writers return promises.
|
|
558
|
+
`StoreAsync` has the same methods; the four writers return promises. It also has `Flush()` (see below). `PushStore` has only `Get` and `Has`, plus `Error` (see PushStore and DiffTreeWriter).
|
|
551
559
|
|
|
552
560
|
### keyFunc and flattening
|
|
553
561
|
|
|
@@ -585,12 +593,19 @@ store.Destroy(); // when done
|
|
|
585
593
|
|
|
586
594
|
Data crosses the worker boundary by structured clone. Class instances arrive as plain objects without their methods; functions cannot be sent. Store plain data.
|
|
587
595
|
|
|
596
|
+
**Batching (8.0.27).** Every `Write`, `Patch`, `Push` and `Splice` made in the same tick goes to the worker as one message and is applied in call order. The worker runs projections once per batch, after the last change, not after each one. Verified: a `Write`, a `Push` and a `Patch` in one tick sent one message and ran a projection once.
|
|
597
|
+
|
|
598
|
+
- You don't have to await a change before making the next one. `Patch`, `Push` and `Splice` accept a key whose `Write` is still pending, and `Patch` merges in the worker, on top of everything before it.
|
|
599
|
+
- The pending-key check covers only the key passed to `Write`. Entities that `keyFunc` will flatten out of that write don't exist yet: `Patch("a", …)` right after an un-awaited `Write([{ id: "a" }], "todos")` rejects with `Key not found in store` (verified). Await the write first.
|
|
600
|
+
- `await store.Flush()` waits for every change made before the call. Changes made after it go in a later batch.
|
|
601
|
+
- **A failed change stops the store.** After any change fails in the worker (for example `Splice` on a value that isn't an array), every later `Write`, `Patch`, `Push`, `Splice` and `Flush` rejects with that same error (verified). Validate before writing; a store in this state has to be replaced.
|
|
602
|
+
|
|
588
603
|
### Projections
|
|
589
604
|
|
|
590
605
|
A projection is a derived value the store computes and stores under `$projection_<name>`. Pass them as `StoreSync`'s second argument (for `StoreAsync`, to the worker's `DiffTree`).
|
|
591
606
|
|
|
592
607
|
```typescript
|
|
593
|
-
import { PROJECTION_PREFIX } from "j-templates/Store/Diff
|
|
608
|
+
import { PROJECTION_PREFIX } from "j-templates/Store/Diff"; // "$projection_"
|
|
594
609
|
|
|
595
610
|
const store = new StoreSync((v: any) => v?.id, {
|
|
596
611
|
activeCount: { reads: ["todos"], projection: (keys, todos) => todos.filter((t) => !t.done).length },
|
|
@@ -605,6 +620,39 @@ store.Get<number>(`${PROJECTION_PREFIX}activeCount`);
|
|
|
605
620
|
- The last read may be a root wildcard such as `"task_*"`: each matching root's key is added to `keys` and its value appended (verified: `[["task_1","task_2"], v1, v2]`).
|
|
606
621
|
- A projection has no value until every read resolves. It re-runs when a read's value changes.
|
|
607
622
|
- A cycle or a read of an undefined projection throws at construction.
|
|
623
|
+
- A dotted read can pass through a keyed entity (`"doc.owner.name"`, where `owner` has an id). It reads the entity's own root, so a `Patch` to the entity reruns the projection with the new value (verified; before 8.0.24 such a read returned the stale embedded copy).
|
|
624
|
+
- `StoreSync` and the `StoreAsync` worker run projections for you, and a `DiffTreeWriter` runs them in `Commit()`. `DiffTree.DiffPath`, `PatchPath`, `PushPath` and `SplicePath` don't (8.0.27): code that drives a `DiffTree` directly, such as a test, must call `tree.RunProjections()` after its writes (verified).
|
|
625
|
+
|
|
626
|
+
### PushStore and DiffTreeWriter
|
|
627
|
+
|
|
628
|
+
`StoreAsync` only changes when the main thread writes. When a worker produces the data itself, the worker owns the tree instead: it writes into a `DiffTreeWriter` and calls `Commit()`, which runs projections and posts every change since the last commit as one message. A `PushStore` on the main thread applies each message as it arrives (8.1.0).
|
|
629
|
+
|
|
630
|
+
```typescript
|
|
631
|
+
// engine-worker.ts (bundled as its own worker entry)
|
|
632
|
+
import { DiffTreeWriter } from "j-templates/Store/Diff/diffTreeWriter";
|
|
633
|
+
const tree = new DiffTreeWriter(self, (v: any) => v?.id, projections);
|
|
634
|
+
self.onmessage = async (e) => {
|
|
635
|
+
const results = await compute(e.data); // the worker's own work
|
|
636
|
+
tree.DiffPath("results", results);
|
|
637
|
+
tree.DiffPath("meta", { finishedAt: Date.now() });
|
|
638
|
+
tree.Commit(); // one message: both writes and the projections
|
|
639
|
+
};
|
|
640
|
+
|
|
641
|
+
// main thread
|
|
642
|
+
const worker = new Worker(new URL("./engine-worker.ts", import.meta.url), { type: "module" });
|
|
643
|
+
const store = new PushStore(worker, (v: any) => v?.id); // before the first Commit()
|
|
644
|
+
store.Get("results"); // reactive; undefined until the first commit
|
|
645
|
+
store.Destroy(); // stops listening; terminate the worker yourself
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
- The writer is a `DiffTree`: write with `DiffPath`, `PatchPath`, `PushPath` and `SplicePath`. Nothing reaches the store until `Commit()`, and a `Commit()` with no changes posts nothing (verified).
|
|
649
|
+
- Projections run once per `Commit()`, after the batch's writes, and their results arrive in the same message, so readers never see a projection out of step with the writes it came from (verified). Calling `RunProjections()` on a writer throws (from source).
|
|
650
|
+
- Use the same `keyFunc` on both sides. Keyed entities resolve through their own roots, as in any store (verified).
|
|
651
|
+
- `source` is any `EventTarget` that delivers the writer's messages as `"message"` events: a `Worker`, a `MessagePort` (started for you), or a plain `EventTarget` on the same thread. The store ignores messages whose `type` isn't `DIFF_TREE_EVENT` or `DIFF_TREE_ERROR` (verified). It listens with `addEventListener` and never sets `onmessage` (from source). The worker can carry its own requests and replies on the same channel.
|
|
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
|
+
- **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
|
+
- `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).
|
|
608
656
|
|
|
609
657
|
## ObservableScope and ObservableNode
|
|
610
658
|
|
|
@@ -722,6 +770,23 @@ function fakeWorker(tree = new DiffTree()) {
|
|
|
722
770
|
const store = new StoreAsync(fakeWorker(new DiffTree((v: any) => v?.id)), (v: any) => v?.id);
|
|
723
771
|
```
|
|
724
772
|
|
|
773
|
+
4. For `PushStore`, use a plain `EventTarget` as the channel. Deliver copies a tick later, as a worker would, and await a timeout before reading:
|
|
774
|
+
|
|
775
|
+
```typescript
|
|
776
|
+
import { DiffTreeWriter } from "j-templates/Store/Diff/diffTreeWriter";
|
|
777
|
+
|
|
778
|
+
const target = new EventTarget();
|
|
779
|
+
const port = { postMessage: (d: unknown) => setTimeout(() =>
|
|
780
|
+
target.dispatchEvent(new MessageEvent("message", { data: structuredClone(d) }))) };
|
|
781
|
+
const writer = new DiffTreeWriter(port);
|
|
782
|
+
const store = new PushStore(target);
|
|
783
|
+
|
|
784
|
+
writer.DiffPath("a", { value: 1 });
|
|
785
|
+
writer.Commit();
|
|
786
|
+
await new Promise((r) => setTimeout(r));
|
|
787
|
+
store.Get<any>("a").value; // 1
|
|
788
|
+
```
|
|
789
|
+
|
|
725
790
|
jsdom has no `innerText`; use `textContent` in tests.
|
|
726
791
|
|
|
727
792
|
## Worked example: Smart Tasks
|
|
@@ -895,7 +960,7 @@ Component.Attach(document.getElementById("app")!, app({}));
|
|
|
895
960
|
9. **`@State` deep-tracks plain objects and arrays only**, not class instances, `Map` or `Set`.
|
|
896
961
|
10. **`@Scope` has no equality check**; consumers re-run on every change of its inputs. `gate()` it if it returns a primitive.
|
|
897
962
|
11. **`@Computed` returns a copy**, not the object the getter returned. Its identity changes only when the result's type changes or an object result loses a key.
|
|
898
|
-
12. **`Bound()` runs
|
|
963
|
+
12. **`Bound()` runs after the first render**, but a child's host isn't in the document yet. Do layout and focus work in `Connected()`. Forgetting `super.Bound()` silences `@Watch`.
|
|
899
964
|
13. **`@Watch` fires immediately** with the initial value, and coalesces synchronous changes into one call.
|
|
900
965
|
14. **`scope()`, `gate()` and `mapped()` outside a scope throw**, including in `on:` handlers and inside `peek()` callbacks. `peek()` works anywhere.
|
|
901
966
|
15. **Conditional `scope()`/`gate()` calls need ids**, or a later call takes a skipped call's scope. Callbacks are fixed at first run: don't close over locals.
|
|
@@ -904,12 +969,13 @@ Component.Attach(document.getElementById("app")!, app({}));
|
|
|
904
969
|
18. **Native DOM listeners belong on elements you render**, not the host.
|
|
905
970
|
19. **`svg()` is HTML-namespace.** Use namespaced components or `innerHTML` for SVG.
|
|
906
971
|
20. **`ObservableScope.Create` with no reactive reads is static** and ignores `Update()`. Use `Basic`.
|
|
907
|
-
21. **`StoreAsync` writes must be awaited
|
|
972
|
+
21. **`StoreAsync` writes must be awaited** before reading; data crosses by structured clone (class methods are lost). Changes in one tick are batched, and one failed change makes every later change reject.
|
|
908
973
|
22. **The package needs a bundler or TypeScript runner**; plain Node cannot import it.
|
|
909
974
|
|
|
910
975
|
23) **Equal-value writes still notify.** Assigning the current value again re-runs every reader. Guard repeated writes or read through `gate()`.
|
|
911
976
|
24) **`JSON.stringify(node)` and `ObservableNode.Snapshot(node)` don't subscribe.** Read properties directly, or `ObservableNode.Clone(node)` to track everything.
|
|
912
977
|
25) **Don't update a scope from its own emit.** Calling `ObservableScope.Update(s)` from a `Watch(s, …)` callback, or any write that re-emits the emitter currently notifying, throws `Emitter.Emit: re-entry during emit is not supported` (verified). Defer the write (`queueMicrotask`) or restructure.
|
|
978
|
+
26) **A `PushStore` sees only what's committed after it exists.** Create it before the writer's first `Commit()`, and call `Commit()` after every batch of writes.
|
|
913
979
|
|
|
914
980
|
## Debugging
|
|
915
981
|
|
|
@@ -927,7 +993,9 @@ Component.Attach(document.getElementById("app")!, app({}));
|
|
|
927
993
|
| `gate()` doesn't stop re-runs | gated value is a new object/array each time | gate a primitive, or use `@Computed` |
|
|
928
994
|
| Async value never resolves (shows a Promise) | callback lacks `async` | `async () => …` |
|
|
929
995
|
| Async value doesn't refresh | reactive read after `await` | read before the first `await` |
|
|
930
|
-
| `querySelector` in `Bound()` finds nothing |
|
|
996
|
+
| `querySelector` in `Bound()` finds nothing | the element is rendered later by a change, not by the first render | reach it where it's rendered, or watch for it |
|
|
997
|
+
| Measured size is 0, or `focus()` does nothing, in `Bound()` | the host isn't in the document yet | move the work to `Connected()` |
|
|
998
|
+
| `Connected()` never fires | the tree was attached to a detached element and appended later | attach to an element that's already in the document |
|
|
931
999
|
| Manual service scope never updates | `ObservableScope.Create` with no reactive reads | `ObservableScope.Basic` |
|
|
932
1000
|
| Test sees old DOM after a write | asynchronous rendering | `SYNC_SCHEDULING=true` or await a timeout |
|
|
933
1001
|
| `ReferenceError: self is not defined` | importing `defaultDiffTreeWorker` on the main thread | write your own worker entry file |
|
|
@@ -935,19 +1003,27 @@ Component.Attach(document.getElementById("app")!, app({}));
|
|
|
935
1003
|
| A watcher or scope over JSON.stringify/Snapshot never fires | snapshot reads register no dependencies | read properties directly, or JSON.stringify(ObservableNode.Clone(node)) |
|
|
936
1004
|
| `Emitter.Emit: re-entry during emit is not supported` | a `Watch`/`OnUpdated` callback updates the scope or value that is notifying it | defer the write, or move it out of the callback |
|
|
937
1005
|
| `scope() must be called within a watch context` inside `peek()` | tracking is off inside `peek` | call `scope()`/`gate()` outside the `peek` callback |
|
|
1006
|
+
| `Key not found in store` from `StoreAsync.Patch` right after a `Write` | the key is an entity id flattened out of a write that hasn't been applied yet | await the `Write` first |
|
|
1007
|
+
| Every `StoreAsync` change rejects with the same old error | an earlier change failed in the worker | fix that change; replace the store |
|
|
1008
|
+
| A test's projection never updates | the test drives a `DiffTree` directly | call `tree.RunProjections()` after each write |
|
|
1009
|
+
| `PushStore` never gets the writer's data | the worker wrote but didn't call `Commit()` | call `Commit()` after each batch |
|
|
1010
|
+
| `PushStore` holds partial or missing roots that the writer has | the store was created after the writer's first `Commit()` | create the store with the worker, before any commit |
|
|
1011
|
+
| Every `DiffTreeWriter` write throws the same old error | an earlier write in the batch failed | call `Commit()`: it sends the applied writes and the error, and the next batch starts clean |
|
|
1012
|
+
| `RunProjections` throws on a `DiffTreeWriter` | projections run in `Commit()` | call `Commit()` instead |
|
|
1013
|
+
| `PROJECTION_PREFIX` is `undefined`, projection keys read `"undefined<id>"` | imported from `j-templates/Store/Diff/diffTree`, its path before 8.1.0 | import it from `j-templates/Store/Diff` |
|
|
938
1014
|
|
|
939
1015
|
## Corrections from the v5 primer
|
|
940
1016
|
|
|
941
1017
|
For maintainers comparing against the previous `SYNTAX_PRIMER.md`. Builders can skip this section.
|
|
942
1018
|
|
|
943
|
-
| Previous claim | Observed
|
|
1019
|
+
| Previous claim | Observed (8.0.18 and 8.0.19 unless a version is named) |
|
|
944
1020
|
| --- | --- |
|
|
945
1021
|
| `Injector` is not exported from any public entry point | exported from `j-templates/Utils` |
|
|
946
1022
|
| `@Computed` returning `this.tasks` won't re-run on item property changes | it re-runs |
|
|
947
1023
|
| a second un-ID'd `scope()`/`gate()` call silently resolves to the first | unconditional duplicates are independent; conditional ones mis-pair |
|
|
948
1024
|
| exceptions in children functions/getters propagate uncaught | children-function and `Template()` exceptions are caught and logged; `props:`/`attrs:`/`data:` exceptions halt all rendering |
|
|
949
1025
|
| children functions can't return `null`/`undefined` | allowed as the sole return value; forbidden inside arrays |
|
|
950
|
-
| `Bound()` runs with the DOM attached, after the template renders |
|
|
1026
|
+
| `Bound()` runs with the DOM attached, after the template renders | before 8.0.23: ran before `Template()`, host detached and empty. Since 8.0.23: runs after the first render; the host is in the document only for the attached root, and `Connected()` marks entry into the document |
|
|
951
1027
|
| `gate()` and `@Scope` are incompatible | fine for primitive-returning getters |
|
|
952
1028
|
| `data: this.value` (non-function) in the granular-scopes example | throws `data is not a function` |
|
|
953
1029
|
| `StoreAsync` data must be JSON-serialisable (no `Date`, `Map`, `Set`) | transport is structured clone: `Date`, `Map`, `Set` survive; class methods and functions do not |
|
package/Store/Diff/diffSync.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { JsonDiffResult } from "../../Utils/json";
|
|
2
|
-
import { DiffSpliceResult, DiffTreeProjectionMap, IDiffTree } from "./diffTree";
|
|
2
|
+
import { DiffSpliceResult, DiffTreeProjectionMap, IDiffTree } from "./diffTree.types";
|
|
3
3
|
/**
|
|
4
4
|
* Synchronous diff implementation.
|
|
5
5
|
* Computes diffs immediately without queuing or workers.
|
package/Store/Diff/diffTree.d.ts
CHANGED
|
@@ -1,72 +1,5 @@
|
|
|
1
1
|
import { type JsonDiffResult } from "../../Utils/json";
|
|
2
|
-
|
|
3
|
-
* Interface for diff tree operations.
|
|
4
|
-
*/
|
|
5
|
-
export interface IDiffTree {
|
|
6
|
-
/**
|
|
7
|
-
* Computes the diff between a new value and the current value at a path.
|
|
8
|
-
* @param path - Dot-separated path to the value
|
|
9
|
-
* @param value - The new value to compare
|
|
10
|
-
* @returns Diff results showing changes
|
|
11
|
-
*/
|
|
12
|
-
DiffPath(path: string, value: any): JsonDiffResult;
|
|
13
|
-
PatchPath(path: string, patch: any): JsonDiffResult;
|
|
14
|
-
SplicePath(path: string, start: number, deleteCount: number | undefined, items: any[]): DiffSpliceResult;
|
|
15
|
-
PushPath(path: string, items: any[]): JsonDiffResult;
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Constructor interface for IDiffTree.
|
|
19
|
-
*/
|
|
20
|
-
export interface IDiffTreeConstructor {
|
|
21
|
-
/**
|
|
22
|
-
* Creates a new IDiffTree instance.
|
|
23
|
-
* @param keyFunc - Optional function to extract a key from a value
|
|
24
|
-
* @param projections - Optional map of derived-value projections, keyed by id
|
|
25
|
-
*/
|
|
26
|
-
new (keyFunc?: {
|
|
27
|
-
(val: any): string;
|
|
28
|
-
}, projections?: DiffTreeProjectionMap): IDiffTree;
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* A pure function computing a derived value from the current values at `reads`. Called with
|
|
32
|
-
* the resolved read paths (`keys`) first, then each value positionally in the same order as
|
|
33
|
-
* `reads`, so each value parameter can be typed at the call site instead of cast out of a
|
|
34
|
-
* generic read. A trailing wildcard read expands to one key/value pair per matched root key,
|
|
35
|
-
* so `keys` lets the function tell which root each trailing value came from.
|
|
36
|
-
*/
|
|
37
|
-
export type DiffTreeProjectionFunc = (keys: string[], ...values: any[]) => any;
|
|
38
|
-
/**
|
|
39
|
-
* A projection's declaration: the dot-separated paths it reads (a plain tree path, an
|
|
40
|
-
* entity's key, or another projection's `$projection_<id>` key for chaining) and the
|
|
41
|
-
* function computing its result from their current values, positionally. Declaring `reads`
|
|
42
|
-
* up front lets the tree topologically sort projections and reject a dependency cycle at
|
|
43
|
-
* construction instead of on first write.
|
|
44
|
-
*
|
|
45
|
-
* The final entry in `reads` may instead be a wildcard root key (e.g. `"user_*"`, no dot
|
|
46
|
-
* nesting) matching every root key with that prefix; each match is read and passed to the
|
|
47
|
-
* projection as its own trailing key/value pair. A wildcard read can't be chained off a
|
|
48
|
-
* projection's `$projection_<id>` key and can't appear anywhere but last.
|
|
49
|
-
*/
|
|
50
|
-
export type DiffTreeProjectionDefinition = {
|
|
51
|
-
reads: string[];
|
|
52
|
-
projection: DiffTreeProjectionFunc;
|
|
53
|
-
};
|
|
54
|
-
/** A map of projection id to its declaration, keyed by the id used in `$projection_<id>`. */
|
|
55
|
-
export type DiffTreeProjectionMap = {
|
|
56
|
-
[id: string]: DiffTreeProjectionDefinition;
|
|
57
|
-
};
|
|
58
|
-
/** Reserved key prefix a projection's result is stored under: `$projection_<id>`. */
|
|
59
|
-
export declare const PROJECTION_PREFIX = "$projection_";
|
|
60
|
-
export type DiffSpliceResult<T = unknown> = {
|
|
61
|
-
/** Array path segments (strings for object keys, numbers for array indices) */
|
|
62
|
-
path: (string | number)[];
|
|
63
|
-
/** Required by the splice operation **/
|
|
64
|
-
start: number;
|
|
65
|
-
deleteCount: number;
|
|
66
|
-
items: any[];
|
|
67
|
-
spliceResult: any[];
|
|
68
|
-
diffResult: JsonDiffResult;
|
|
69
|
-
};
|
|
2
|
+
import { DiffSpliceResult, DiffTreeProjectionMap } from "./diffTree.types";
|
|
70
3
|
/**
|
|
71
4
|
* Diff tree implementation. Maintains root state and computes diffs for path/value updates.
|
|
72
5
|
* Used directly by `DiffSync`, and is the building block for a `StoreAsync` worker entry
|
|
@@ -98,7 +31,7 @@ export declare class DiffTree {
|
|
|
98
31
|
DiffPath(path: string, value: any): JsonDiffResult;
|
|
99
32
|
PatchPath(path: string, patch: any): JsonDiffResult;
|
|
100
33
|
PushPath(path: string, items: unknown[]): JsonDiffResult;
|
|
101
|
-
SplicePath(path: string, start: number, deleteCount: number, items: any[]): DiffSpliceResult
|
|
34
|
+
SplicePath(path: string, start: number, deleteCount: number, items: any[]): DiffSpliceResult;
|
|
102
35
|
/**
|
|
103
36
|
* Retrieves the current value at a path.
|
|
104
37
|
* @param path - Dot-separated path to the value
|
package/Store/Diff/diffTree.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { ArraysEqual } from "../../Utils/array";
|
|
2
2
|
import { JsonDeepClone, JsonDiff, JsonMerge, JsonType } from "../../Utils/json";
|
|
3
|
-
|
|
4
|
-
export const PROJECTION_PREFIX = "$projection_";
|
|
3
|
+
import { PROJECTION_PREFIX } from "./diffTree.types";
|
|
5
4
|
/**
|
|
6
5
|
* Flattens nested objects/arrays, extracting keyed objects to root.
|
|
7
6
|
* @param root - Root object to store flattened values
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { JsonDiffResult } from "../../Utils/json";
|
|
2
|
+
/**
|
|
3
|
+
* `type` of the message a `DiffTreeWriter` posts on `Commit()`: the batch's diff results,
|
|
4
|
+
* which a `PushStore` applies.
|
|
5
|
+
*/
|
|
6
|
+
export declare const DIFF_TREE_EVENT = "DIFF_TREE_EVENT";
|
|
7
|
+
/**
|
|
8
|
+
* `type` of the message a `DiffTreeWriter` posts on `Commit()` when a change in the batch
|
|
9
|
+
* failed, after the `DIFF_TREE_EVENT` carrying the changes that were applied.
|
|
10
|
+
*/
|
|
11
|
+
export declare const DIFF_TREE_ERROR = "DIFF_TREE_ERROR";
|
|
12
|
+
/**
|
|
13
|
+
* Interface for diff tree operations.
|
|
14
|
+
*/
|
|
15
|
+
export interface IDiffTree {
|
|
16
|
+
/**
|
|
17
|
+
* Computes the diff between a new value and the current value at a path.
|
|
18
|
+
* @param path - Dot-separated path to the value
|
|
19
|
+
* @param value - The new value to compare
|
|
20
|
+
* @returns Diff results showing changes
|
|
21
|
+
*/
|
|
22
|
+
DiffPath(path: string, value: any): JsonDiffResult;
|
|
23
|
+
PatchPath(path: string, patch: any): JsonDiffResult;
|
|
24
|
+
SplicePath(path: string, start: number, deleteCount: number | undefined, items: any[]): DiffSpliceResult;
|
|
25
|
+
PushPath(path: string, items: any[]): JsonDiffResult;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Constructor interface for IDiffTree.
|
|
29
|
+
*/
|
|
30
|
+
export interface IDiffTreeConstructor {
|
|
31
|
+
/**
|
|
32
|
+
* Creates a new IDiffTree instance.
|
|
33
|
+
* @param keyFunc - Optional function to extract a key from a value
|
|
34
|
+
* @param projections - Optional map of derived-value projections, keyed by id
|
|
35
|
+
*/
|
|
36
|
+
new (keyFunc?: {
|
|
37
|
+
(val: any): string;
|
|
38
|
+
}, projections?: DiffTreeProjectionMap): IDiffTree;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* A pure function computing a derived value from the current values at `reads`. Called with
|
|
42
|
+
* the resolved read paths (`keys`) first, then each value positionally in the same order as
|
|
43
|
+
* `reads`, so each value parameter can be typed at the call site instead of cast out of a
|
|
44
|
+
* generic read. A trailing wildcard read expands to one key/value pair per matched root key,
|
|
45
|
+
* so `keys` lets the function tell which root each trailing value came from.
|
|
46
|
+
*/
|
|
47
|
+
export type DiffTreeProjectionFunc = (keys: string[], ...values: any[]) => any;
|
|
48
|
+
/**
|
|
49
|
+
* A projection's declaration: the dot-separated paths it reads (a plain tree path, an
|
|
50
|
+
* entity's key, or another projection's `$projection_<id>` key for chaining) and the
|
|
51
|
+
* function computing its result from their current values, positionally. Declaring `reads`
|
|
52
|
+
* up front lets the tree topologically sort projections and reject a dependency cycle at
|
|
53
|
+
* construction instead of on first write.
|
|
54
|
+
*
|
|
55
|
+
* The final entry in `reads` may instead be a wildcard root key (e.g. `"user_*"`, no dot
|
|
56
|
+
* nesting) matching every root key with that prefix; each match is read and passed to the
|
|
57
|
+
* projection as its own trailing key/value pair. A wildcard read can't be chained off a
|
|
58
|
+
* projection's `$projection_<id>` key and can't appear anywhere but last.
|
|
59
|
+
*/
|
|
60
|
+
export type DiffTreeProjectionDefinition = {
|
|
61
|
+
reads: string[];
|
|
62
|
+
projection: DiffTreeProjectionFunc;
|
|
63
|
+
};
|
|
64
|
+
/** A map of projection id to its declaration, keyed by the id used in `$projection_<id>`. */
|
|
65
|
+
export type DiffTreeProjectionMap = {
|
|
66
|
+
[id: string]: DiffTreeProjectionDefinition;
|
|
67
|
+
};
|
|
68
|
+
/** Reserved key prefix a projection's result is stored under: `$projection_<id>`. */
|
|
69
|
+
export declare const PROJECTION_PREFIX = "$projection_";
|
|
70
|
+
export type DiffSpliceResult<T = unknown> = {
|
|
71
|
+
/** Array path segments (strings for object keys, numbers for array indices) */
|
|
72
|
+
path: (string | number)[];
|
|
73
|
+
/** Required by the splice operation **/
|
|
74
|
+
start: number;
|
|
75
|
+
deleteCount: number;
|
|
76
|
+
items: T[];
|
|
77
|
+
spliceResult: T[];
|
|
78
|
+
diffResult: JsonDiffResult;
|
|
79
|
+
};
|
|
80
|
+
export type IDiffMethodBatch = IDiffMethod[];
|
|
81
|
+
export type IDiffMethodBatchResult = ({
|
|
82
|
+
type: "diff";
|
|
83
|
+
result: JsonDiffResult;
|
|
84
|
+
} | {
|
|
85
|
+
type: "splice";
|
|
86
|
+
result: DiffSpliceResult;
|
|
87
|
+
})[];
|
|
88
|
+
/**
|
|
89
|
+
* A message a `DiffTreeWriter` posts to its `PushStore`. Both arrive as ordinary
|
|
90
|
+
* `"message"` events, so the channel can carry other traffic too; listeners tell them apart
|
|
91
|
+
* by `type`.
|
|
92
|
+
*/
|
|
93
|
+
export type IDiffPushEvent = {
|
|
94
|
+
type: typeof DIFF_TREE_EVENT;
|
|
95
|
+
data: IDiffMethodBatchResult;
|
|
96
|
+
} | {
|
|
97
|
+
type: typeof DIFF_TREE_ERROR;
|
|
98
|
+
data: any;
|
|
99
|
+
};
|
|
100
|
+
export interface IDiffMethod {
|
|
101
|
+
/** The method to call */
|
|
102
|
+
method: "diffpath" | "splicepath" | "pushpath" | "patchpath";
|
|
103
|
+
/** Arguments for the method call */
|
|
104
|
+
arguments: Array<any>;
|
|
105
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `type` of the message a `DiffTreeWriter` posts on `Commit()`: the batch's diff results,
|
|
3
|
+
* which a `PushStore` applies.
|
|
4
|
+
*/
|
|
5
|
+
export const DIFF_TREE_EVENT = "DIFF_TREE_EVENT";
|
|
6
|
+
/**
|
|
7
|
+
* `type` of the message a `DiffTreeWriter` posts on `Commit()` when a change in the batch
|
|
8
|
+
* failed, after the `DIFF_TREE_EVENT` carrying the changes that were applied.
|
|
9
|
+
*/
|
|
10
|
+
export const DIFF_TREE_ERROR = "DIFF_TREE_ERROR";
|
|
11
|
+
/** Reserved key prefix a projection's result is stored under: `$projection_<id>`. */
|
|
12
|
+
export const PROJECTION_PREFIX = "$projection_";
|
|
@@ -1,17 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { DiffSpliceResult, DiffTree } from "./diffTree";
|
|
3
|
-
export type IDiffMethodBatch = IDiffMethod[];
|
|
4
|
-
export type IDiffMethodBatchResult = ({
|
|
5
|
-
type: "diff";
|
|
6
|
-
result: JsonDiffResult;
|
|
7
|
-
} | {
|
|
8
|
-
type: "splice";
|
|
9
|
-
result: DiffSpliceResult;
|
|
10
|
-
})[];
|
|
11
|
-
export interface IDiffMethod {
|
|
12
|
-
/** The method to call */
|
|
13
|
-
method: "diffpath" | "splicepath" | "pushpath" | "patchpath";
|
|
14
|
-
/** Arguments for the method call */
|
|
15
|
-
arguments: Array<any>;
|
|
16
|
-
}
|
|
1
|
+
import { DiffTree } from "./diffTree";
|
|
17
2
|
export declare function ConnectWorkerToDiffTree(diffTree: DiffTree, worker: Worker): void;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { JsonDiffResult } from "../../Utils/json";
|
|
2
|
+
import { DiffTree } from "./diffTree";
|
|
3
|
+
import { DiffSpliceResult, DiffTreeProjectionMap } from "./diffTree.types";
|
|
4
|
+
/**
|
|
5
|
+
* A `DiffTree` that pushes its changes to a `PushStore` instead of answering a store's
|
|
6
|
+
* requests. Use it inside a worker that produces data itself (for example, the result of a
|
|
7
|
+
* computation it runs): write with the usual `DiffTree` methods, then call `Commit()` to run
|
|
8
|
+
* projections and post everything since the last commit as one message.
|
|
9
|
+
*
|
|
10
|
+
* - Push only: don't pass a `DiffTreeWriter` to `ConnectWorkerToDiffTree`. It would record
|
|
11
|
+
* the request/response batches too and send them again on the next `Commit()`.
|
|
12
|
+
* - After a change fails, every later change before `Commit()` throws that same error. The
|
|
13
|
+
* changes made before the failure are already in the tree, so `Commit()` still sends them,
|
|
14
|
+
* followed by a `DIFF_TREE_ERROR` message. Projections aren't run for that batch; the next
|
|
15
|
+
* `Commit()` brings them up to date.
|
|
16
|
+
* - A `PushStore` only receives the changes committed after it starts listening, so
|
|
17
|
+
* create it before the writer's first `Commit()`.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* // engine-worker.ts
|
|
21
|
+
* const tree = new DiffTreeWriter(self, undefined, projections);
|
|
22
|
+
* tree.DiffPath("results", computeResults());
|
|
23
|
+
* tree.Commit();
|
|
24
|
+
*
|
|
25
|
+
* // main thread
|
|
26
|
+
* const store = new PushStore(worker);
|
|
27
|
+
*
|
|
28
|
+
* @see PushStore
|
|
29
|
+
*/
|
|
30
|
+
export declare class DiffTreeWriter extends DiffTree {
|
|
31
|
+
private worker;
|
|
32
|
+
private uncommittedError;
|
|
33
|
+
private uncommitted;
|
|
34
|
+
/**
|
|
35
|
+
* Creates a DiffTreeWriter.
|
|
36
|
+
* @param worker Where `Commit()` posts its messages: the worker's `self`, or a `MessagePort`.
|
|
37
|
+
* On the same thread as the store, anything whose `postMessage` dispatches a `"message"`
|
|
38
|
+
* event on the store's `EventTarget`; post copies, or construct the store with `cloneData`.
|
|
39
|
+
* @param keyFunc Optional function to extract a key from objects. Pass the same key logic to
|
|
40
|
+
* the `PushStore` that receives the changes.
|
|
41
|
+
* @param projections Optional map of derived-value projections, keyed by id. They run once
|
|
42
|
+
* per `Commit()`, after the batch's changes.
|
|
43
|
+
*/
|
|
44
|
+
constructor(worker: {
|
|
45
|
+
postMessage(message: unknown): void;
|
|
46
|
+
}, keyFunc?: {
|
|
47
|
+
(val: any): string;
|
|
48
|
+
}, projections?: DiffTreeProjectionMap);
|
|
49
|
+
/**
|
|
50
|
+
* Not available on a DiffTreeWriter: `Commit()` runs projections once per batch, after its
|
|
51
|
+
* changes. Running them earlier would push a projection result computed from part of a batch.
|
|
52
|
+
* @throws Always.
|
|
53
|
+
*/
|
|
54
|
+
RunProjections(): JsonDiffResult;
|
|
55
|
+
/**
|
|
56
|
+
* Computes the diff between a new value and the current value at a path, and records it
|
|
57
|
+
* for the next `Commit()`.
|
|
58
|
+
* @param path - Dot-separated path to the value
|
|
59
|
+
* @param value - The new value to compare
|
|
60
|
+
* @returns Diff results showing changes
|
|
61
|
+
* @throws The change's error, or an earlier uncommitted change's error.
|
|
62
|
+
*/
|
|
63
|
+
DiffPath(path: string, value: any): JsonDiffResult;
|
|
64
|
+
/**
|
|
65
|
+
* Merges a patch into the current value at a path, and records the diff for the next
|
|
66
|
+
* `Commit()`.
|
|
67
|
+
* @param path - Dot-separated path to the value
|
|
68
|
+
* @param patch - The value to merge into the current value
|
|
69
|
+
* @returns Diff results showing changes
|
|
70
|
+
* @throws The change's error, or an earlier uncommitted change's error.
|
|
71
|
+
*/
|
|
72
|
+
PatchPath(path: string, patch: any): JsonDiffResult;
|
|
73
|
+
/**
|
|
74
|
+
* Appends items to the array at a path, and records the diff for the next `Commit()`.
|
|
75
|
+
* @param path - Dot-separated path to the array
|
|
76
|
+
* @param items - The items to append
|
|
77
|
+
* @returns Diff results showing changes
|
|
78
|
+
* @throws The change's error, or an earlier uncommitted change's error.
|
|
79
|
+
*/
|
|
80
|
+
PushPath(path: string, items: unknown[]): JsonDiffResult;
|
|
81
|
+
/**
|
|
82
|
+
* Splices the array at a path, and records the splice for the next `Commit()`.
|
|
83
|
+
* @param path - Dot-separated path to the array
|
|
84
|
+
* @param start - The position at which to start changing the array
|
|
85
|
+
* @param deleteCount - The number of elements to delete
|
|
86
|
+
* @param items - The elements to insert
|
|
87
|
+
* @returns The splice and its diff results
|
|
88
|
+
* @throws The change's error, or an earlier uncommitted change's error.
|
|
89
|
+
*/
|
|
90
|
+
SplicePath(path: string, start: number, deleteCount: number, items: any[]): DiffSpliceResult<unknown>;
|
|
91
|
+
/**
|
|
92
|
+
* Runs projections and posts every change since the last commit as one `DIFF_TREE_EVENT`
|
|
93
|
+
* message, which a `PushStore` applies as a unit. Posts nothing when nothing changed.
|
|
94
|
+
*
|
|
95
|
+
* If a change in the batch failed, projections are skipped, the applied changes are still
|
|
96
|
+
* posted, and a `DIFF_TREE_ERROR` message follows with the error; `Commit()` itself doesn't
|
|
97
|
+
* throw, since the failed change already did. If a projection throws, the batch's changes
|
|
98
|
+
* and the error are posted the same way, then `Commit()` rethrows. Either way the next batch
|
|
99
|
+
* starts clean.
|
|
100
|
+
* @throws A projection's error.
|
|
101
|
+
*/
|
|
102
|
+
Commit(): void;
|
|
103
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { DiffTree } from "./diffTree";
|
|
2
|
+
import { DIFF_TREE_ERROR, DIFF_TREE_EVENT } from "./diffTree.types";
|
|
3
|
+
/**
|
|
4
|
+
* A `DiffTree` that pushes its changes to a `PushStore` instead of answering a store's
|
|
5
|
+
* requests. Use it inside a worker that produces data itself (for example, the result of a
|
|
6
|
+
* computation it runs): write with the usual `DiffTree` methods, then call `Commit()` to run
|
|
7
|
+
* projections and post everything since the last commit as one message.
|
|
8
|
+
*
|
|
9
|
+
* - Push only: don't pass a `DiffTreeWriter` to `ConnectWorkerToDiffTree`. It would record
|
|
10
|
+
* the request/response batches too and send them again on the next `Commit()`.
|
|
11
|
+
* - After a change fails, every later change before `Commit()` throws that same error. The
|
|
12
|
+
* changes made before the failure are already in the tree, so `Commit()` still sends them,
|
|
13
|
+
* followed by a `DIFF_TREE_ERROR` message. Projections aren't run for that batch; the next
|
|
14
|
+
* `Commit()` brings them up to date.
|
|
15
|
+
* - A `PushStore` only receives the changes committed after it starts listening, so
|
|
16
|
+
* create it before the writer's first `Commit()`.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* // engine-worker.ts
|
|
20
|
+
* const tree = new DiffTreeWriter(self, undefined, projections);
|
|
21
|
+
* tree.DiffPath("results", computeResults());
|
|
22
|
+
* tree.Commit();
|
|
23
|
+
*
|
|
24
|
+
* // main thread
|
|
25
|
+
* const store = new PushStore(worker);
|
|
26
|
+
*
|
|
27
|
+
* @see PushStore
|
|
28
|
+
*/
|
|
29
|
+
export class DiffTreeWriter extends DiffTree {
|
|
30
|
+
/**
|
|
31
|
+
* Creates a DiffTreeWriter.
|
|
32
|
+
* @param worker Where `Commit()` posts its messages: the worker's `self`, or a `MessagePort`.
|
|
33
|
+
* On the same thread as the store, anything whose `postMessage` dispatches a `"message"`
|
|
34
|
+
* event on the store's `EventTarget`; post copies, or construct the store with `cloneData`.
|
|
35
|
+
* @param keyFunc Optional function to extract a key from objects. Pass the same key logic to
|
|
36
|
+
* the `PushStore` that receives the changes.
|
|
37
|
+
* @param projections Optional map of derived-value projections, keyed by id. They run once
|
|
38
|
+
* per `Commit()`, after the batch's changes.
|
|
39
|
+
*/
|
|
40
|
+
constructor(worker, keyFunc, projections) {
|
|
41
|
+
super(keyFunc, projections);
|
|
42
|
+
this.worker = worker;
|
|
43
|
+
this.uncommittedError = null;
|
|
44
|
+
this.uncommitted = [];
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Not available on a DiffTreeWriter: `Commit()` runs projections once per batch, after its
|
|
48
|
+
* changes. Running them earlier would push a projection result computed from part of a batch.
|
|
49
|
+
* @throws Always.
|
|
50
|
+
*/
|
|
51
|
+
RunProjections() {
|
|
52
|
+
throw new Error("RunProjections is called during Commit()");
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Computes the diff between a new value and the current value at a path, and records it
|
|
56
|
+
* for the next `Commit()`.
|
|
57
|
+
* @param path - Dot-separated path to the value
|
|
58
|
+
* @param value - The new value to compare
|
|
59
|
+
* @returns Diff results showing changes
|
|
60
|
+
* @throws The change's error, or an earlier uncommitted change's error.
|
|
61
|
+
*/
|
|
62
|
+
DiffPath(path, value) {
|
|
63
|
+
if (this.uncommittedError)
|
|
64
|
+
throw this.uncommittedError;
|
|
65
|
+
try {
|
|
66
|
+
const result = super.DiffPath(path, value);
|
|
67
|
+
result.length > 0 && this.uncommitted.push({
|
|
68
|
+
type: "diff",
|
|
69
|
+
result
|
|
70
|
+
});
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
this.uncommittedError = err;
|
|
75
|
+
throw err;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Merges a patch into the current value at a path, and records the diff for the next
|
|
80
|
+
* `Commit()`.
|
|
81
|
+
* @param path - Dot-separated path to the value
|
|
82
|
+
* @param patch - The value to merge into the current value
|
|
83
|
+
* @returns Diff results showing changes
|
|
84
|
+
* @throws The change's error, or an earlier uncommitted change's error.
|
|
85
|
+
*/
|
|
86
|
+
PatchPath(path, patch) {
|
|
87
|
+
if (this.uncommittedError)
|
|
88
|
+
throw this.uncommittedError;
|
|
89
|
+
try {
|
|
90
|
+
const result = super.PatchPath(path, patch);
|
|
91
|
+
result.length > 0 && this.uncommitted.push({
|
|
92
|
+
type: "diff",
|
|
93
|
+
result
|
|
94
|
+
});
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
this.uncommittedError = err;
|
|
99
|
+
throw err;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Appends items to the array at a path, and records the diff for the next `Commit()`.
|
|
104
|
+
* @param path - Dot-separated path to the array
|
|
105
|
+
* @param items - The items to append
|
|
106
|
+
* @returns Diff results showing changes
|
|
107
|
+
* @throws The change's error, or an earlier uncommitted change's error.
|
|
108
|
+
*/
|
|
109
|
+
PushPath(path, items) {
|
|
110
|
+
if (this.uncommittedError)
|
|
111
|
+
throw this.uncommittedError;
|
|
112
|
+
try {
|
|
113
|
+
const result = super.PushPath(path, items);
|
|
114
|
+
result.length > 0 && this.uncommitted.push({
|
|
115
|
+
type: "diff",
|
|
116
|
+
result
|
|
117
|
+
});
|
|
118
|
+
return result;
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
this.uncommittedError = err;
|
|
122
|
+
throw err;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Splices the array at a path, and records the splice for the next `Commit()`.
|
|
127
|
+
* @param path - Dot-separated path to the array
|
|
128
|
+
* @param start - The position at which to start changing the array
|
|
129
|
+
* @param deleteCount - The number of elements to delete
|
|
130
|
+
* @param items - The elements to insert
|
|
131
|
+
* @returns The splice and its diff results
|
|
132
|
+
* @throws The change's error, or an earlier uncommitted change's error.
|
|
133
|
+
*/
|
|
134
|
+
SplicePath(path, start, deleteCount, items) {
|
|
135
|
+
if (this.uncommittedError)
|
|
136
|
+
throw this.uncommittedError;
|
|
137
|
+
try {
|
|
138
|
+
const result = super.SplicePath(path, start, deleteCount, items);
|
|
139
|
+
this.uncommitted.push({
|
|
140
|
+
type: "splice",
|
|
141
|
+
result
|
|
142
|
+
});
|
|
143
|
+
return result;
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
this.uncommittedError = err;
|
|
147
|
+
throw err;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Runs projections and posts every change since the last commit as one `DIFF_TREE_EVENT`
|
|
152
|
+
* message, which a `PushStore` applies as a unit. Posts nothing when nothing changed.
|
|
153
|
+
*
|
|
154
|
+
* If a change in the batch failed, projections are skipped, the applied changes are still
|
|
155
|
+
* posted, and a `DIFF_TREE_ERROR` message follows with the error; `Commit()` itself doesn't
|
|
156
|
+
* throw, since the failed change already did. If a projection throws, the batch's changes
|
|
157
|
+
* and the error are posted the same way, then `Commit()` rethrows. Either way the next batch
|
|
158
|
+
* starts clean.
|
|
159
|
+
* @throws A projection's error.
|
|
160
|
+
*/
|
|
161
|
+
Commit() {
|
|
162
|
+
try {
|
|
163
|
+
const result = !this.uncommittedError ? super.RunProjections() : [];
|
|
164
|
+
result.length > 0 && this.uncommitted.push({
|
|
165
|
+
type: "diff",
|
|
166
|
+
result
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
catch (err) {
|
|
170
|
+
this.uncommittedError = err;
|
|
171
|
+
throw err;
|
|
172
|
+
}
|
|
173
|
+
finally {
|
|
174
|
+
this.uncommitted.length > 0 && this.worker.postMessage({
|
|
175
|
+
type: DIFF_TREE_EVENT,
|
|
176
|
+
data: this.uncommitted
|
|
177
|
+
});
|
|
178
|
+
if (this.uncommittedError) {
|
|
179
|
+
this.worker.postMessage({
|
|
180
|
+
type: DIFF_TREE_ERROR,
|
|
181
|
+
data: this.uncommittedError
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
this.uncommitted = [];
|
|
185
|
+
this.uncommittedError = null;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./diffTree.types";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./diffTree.types";
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { ReadStore } from "./store";
|
|
2
|
+
/**
|
|
3
|
+
* A read-only store whose changes are pushed to it by a `DiffTreeWriter`. The writer decides
|
|
4
|
+
* when data changes and pushes it with `Commit()`; this store applies each pushed batch as it
|
|
5
|
+
* arrives, and readers update the same way they do for any other store.
|
|
6
|
+
*
|
|
7
|
+
* The source is any `EventTarget` that delivers the writer's messages as `"message"` events:
|
|
8
|
+
* usually a `Worker` or a `MessagePort`, or a plain `EventTarget` when the writer runs on the
|
|
9
|
+
* same thread. The store ignores any message that isn't a `DIFF_TREE_EVENT` or
|
|
10
|
+
* `DIFF_TREE_ERROR`, so the source can carry other traffic too. It never takes over the
|
|
11
|
+
* source's `onmessage` or terminates a worker; that stays with the source's owner. It doesn't
|
|
12
|
+
* listen for the source's `"error"` events either: the writer's errors arrive as
|
|
13
|
+
* `DIFF_TREE_ERROR` messages (see `Error`).
|
|
14
|
+
*
|
|
15
|
+
* Changes are diffs against the writer's tree, so create the store before the writer's
|
|
16
|
+
* first `Commit()`. A store created later misses the earlier changes.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* const worker = new Worker(new URL("./engine-worker.ts", import.meta.url), { type: "module" });
|
|
20
|
+
* const store = new PushStore(worker);
|
|
21
|
+
* store.Get<Results>("results"); // reactive; undefined until the first commit
|
|
22
|
+
* store.Destroy(); // stops listening; doesn't terminate the worker
|
|
23
|
+
*
|
|
24
|
+
* @see DiffTreeWriter
|
|
25
|
+
* @see StoreAsync
|
|
26
|
+
*/
|
|
27
|
+
export declare class PushStore extends ReadStore {
|
|
28
|
+
private eventTarget;
|
|
29
|
+
private error;
|
|
30
|
+
private errorScope;
|
|
31
|
+
/**
|
|
32
|
+
* The error the writer reported with its last commit, or null. Reactive. It's cleared when
|
|
33
|
+
* a later commit's changes arrive, so it reflects the most recent push, not a stopped store:
|
|
34
|
+
* the writer's later changes are still applied after an error.
|
|
35
|
+
*/
|
|
36
|
+
get Error(): any;
|
|
37
|
+
private messageHandler;
|
|
38
|
+
/**
|
|
39
|
+
* Creates a PushStore and starts listening to the source. A `MessagePort` is started for
|
|
40
|
+
* you, since `addEventListener` doesn't start a port the way assigning `onmessage` does.
|
|
41
|
+
* @param eventTarget The source of the `DiffTreeWriter`'s messages: a `Worker`, a
|
|
42
|
+
* `MessagePort`, or any `EventTarget` that dispatches them as `"message"` events.
|
|
43
|
+
* @param keyFunc Optional function to generate a key for a given data value, used to
|
|
44
|
+
* resolve root objects. Pass the same key logic the `DiffTreeWriter` was constructed
|
|
45
|
+
* with, if any.
|
|
46
|
+
* @param cloneData Whether to deep-clone the values the store adopts (false by default).
|
|
47
|
+
* A `Worker` or `MessagePort` already delivers a structured-clone copy of every message.
|
|
48
|
+
* A same-thread `EventTarget` delivers the writer's own objects, so either the writer
|
|
49
|
+
* posts copies or this is true; otherwise the store and the writer's tree share objects.
|
|
50
|
+
*/
|
|
51
|
+
constructor(eventTarget: EventTarget, keyFunc?: (value: any) => string | undefined, cloneData?: boolean);
|
|
52
|
+
/**
|
|
53
|
+
* Stops listening to the source and destroys the error scope. The source stays open (a
|
|
54
|
+
* worker keeps running); closing or terminating it is up to its owner.
|
|
55
|
+
*/
|
|
56
|
+
Destroy(): void;
|
|
57
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { DIFF_TREE_ERROR, DIFF_TREE_EVENT } from "../Diff/diffTree.types";
|
|
2
|
+
import { ObservableScope } from "../Tree/observableScope";
|
|
3
|
+
import { ReadStore } from "./store";
|
|
4
|
+
/**
|
|
5
|
+
* A read-only store whose changes are pushed to it by a `DiffTreeWriter`. The writer decides
|
|
6
|
+
* when data changes and pushes it with `Commit()`; this store applies each pushed batch as it
|
|
7
|
+
* arrives, and readers update the same way they do for any other store.
|
|
8
|
+
*
|
|
9
|
+
* The source is any `EventTarget` that delivers the writer's messages as `"message"` events:
|
|
10
|
+
* usually a `Worker` or a `MessagePort`, or a plain `EventTarget` when the writer runs on the
|
|
11
|
+
* same thread. The store ignores any message that isn't a `DIFF_TREE_EVENT` or
|
|
12
|
+
* `DIFF_TREE_ERROR`, so the source can carry other traffic too. It never takes over the
|
|
13
|
+
* source's `onmessage` or terminates a worker; that stays with the source's owner. It doesn't
|
|
14
|
+
* listen for the source's `"error"` events either: the writer's errors arrive as
|
|
15
|
+
* `DIFF_TREE_ERROR` messages (see `Error`).
|
|
16
|
+
*
|
|
17
|
+
* Changes are diffs against the writer's tree, so create the store before the writer's
|
|
18
|
+
* first `Commit()`. A store created later misses the earlier changes.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* const worker = new Worker(new URL("./engine-worker.ts", import.meta.url), { type: "module" });
|
|
22
|
+
* const store = new PushStore(worker);
|
|
23
|
+
* store.Get<Results>("results"); // reactive; undefined until the first commit
|
|
24
|
+
* store.Destroy(); // stops listening; doesn't terminate the worker
|
|
25
|
+
*
|
|
26
|
+
* @see DiffTreeWriter
|
|
27
|
+
* @see StoreAsync
|
|
28
|
+
*/
|
|
29
|
+
export class PushStore extends ReadStore {
|
|
30
|
+
/**
|
|
31
|
+
* The error the writer reported with its last commit, or null. Reactive. It's cleared when
|
|
32
|
+
* a later commit's changes arrive, so it reflects the most recent push, not a stopped store:
|
|
33
|
+
* the writer's later changes are still applied after an error.
|
|
34
|
+
*/
|
|
35
|
+
get Error() {
|
|
36
|
+
return ObservableScope.Value(this.errorScope);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Creates a PushStore and starts listening to the source. A `MessagePort` is started for
|
|
40
|
+
* you, since `addEventListener` doesn't start a port the way assigning `onmessage` does.
|
|
41
|
+
* @param eventTarget The source of the `DiffTreeWriter`'s messages: a `Worker`, a
|
|
42
|
+
* `MessagePort`, or any `EventTarget` that dispatches them as `"message"` events.
|
|
43
|
+
* @param keyFunc Optional function to generate a key for a given data value, used to
|
|
44
|
+
* resolve root objects. Pass the same key logic the `DiffTreeWriter` was constructed
|
|
45
|
+
* with, if any.
|
|
46
|
+
* @param cloneData Whether to deep-clone the values the store adopts (false by default).
|
|
47
|
+
* A `Worker` or `MessagePort` already delivers a structured-clone copy of every message.
|
|
48
|
+
* A same-thread `EventTarget` delivers the writer's own objects, so either the writer
|
|
49
|
+
* posts copies or this is true; otherwise the store and the writer's tree share objects.
|
|
50
|
+
*/
|
|
51
|
+
constructor(eventTarget, keyFunc, cloneData = false) {
|
|
52
|
+
super(keyFunc, cloneData);
|
|
53
|
+
this.eventTarget = eventTarget;
|
|
54
|
+
this.error = null;
|
|
55
|
+
this.errorScope = ObservableScope.Basic(() => this.error);
|
|
56
|
+
this.messageHandler = (event) => {
|
|
57
|
+
const messageData = event.data;
|
|
58
|
+
if (messageData && messageData.type === DIFF_TREE_EVENT) {
|
|
59
|
+
const data = messageData.data;
|
|
60
|
+
for (let x = 0; x < data.length; x++) {
|
|
61
|
+
const result = data[x];
|
|
62
|
+
switch (result.type) {
|
|
63
|
+
case "diff": {
|
|
64
|
+
this.UpdateRootMap(result.result);
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
case "splice": {
|
|
68
|
+
const spliceResult = result.result;
|
|
69
|
+
this.UpdateRootMap(spliceResult.diffResult);
|
|
70
|
+
this.SpliceRootObject(spliceResult.path[0], spliceResult.start, spliceResult.deleteCount, spliceResult.items);
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (this.error !== null) {
|
|
76
|
+
this.error = null;
|
|
77
|
+
ObservableScope.Update(this.errorScope);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
else if (messageData && messageData.type === DIFF_TREE_ERROR) {
|
|
81
|
+
this.error = messageData.data;
|
|
82
|
+
ObservableScope.Update(this.errorScope);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
eventTarget.addEventListener("message", this.messageHandler);
|
|
86
|
+
eventTarget.start?.();
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Stops listening to the source and destroys the error scope. The source stays open (a
|
|
90
|
+
* worker keeps running); closing or terminating it is up to its owner.
|
|
91
|
+
*/
|
|
92
|
+
Destroy() {
|
|
93
|
+
this.eventTarget.removeEventListener("message", this.messageHandler);
|
|
94
|
+
ObservableScope.Destroy(this.errorScope);
|
|
95
|
+
}
|
|
96
|
+
}
|
package/Store/Store/store.d.ts
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import { JsonDiffResult } from "../../Utils/json";
|
|
2
2
|
/**
|
|
3
|
-
* Base class for observable data
|
|
4
|
-
*
|
|
3
|
+
* Base class for reading observable data: holds root objects as ObservableNode instances
|
|
4
|
+
* and applies diff results to them. Subclasses decide where the diffs come from: `Store`
|
|
5
|
+
* adds the write methods, and `PushStore` takes them from a `DiffTreeWriter`.
|
|
5
6
|
*
|
|
6
|
-
* @see
|
|
7
|
-
* @see
|
|
7
|
+
* @see Store
|
|
8
|
+
* @see PushStore
|
|
8
9
|
*/
|
|
9
|
-
export declare abstract class
|
|
10
|
+
export declare abstract class ReadStore {
|
|
10
11
|
protected keyFunc?: (value: any) => string | undefined;
|
|
11
12
|
private cloneData;
|
|
12
13
|
private rootMap;
|
|
13
14
|
private createNode;
|
|
14
15
|
/**
|
|
15
|
-
* Creates an instance of
|
|
16
|
+
* Creates an instance of ReadStore.
|
|
16
17
|
* @param keyFunc Optional function to generate a key for a given data value.
|
|
17
18
|
* When provided, enables alias functionality where values can reference other root objects.
|
|
18
19
|
*/
|
|
@@ -33,10 +34,6 @@ export declare abstract class Store {
|
|
|
33
34
|
*/
|
|
34
35
|
Get<O>(id: string, defaultValue: O): O;
|
|
35
36
|
Has(id: string): boolean;
|
|
36
|
-
abstract Write(data: unknown, key?: string): Promise<void> | void;
|
|
37
|
-
abstract Patch(key: string, patch: unknown): Promise<void> | void;
|
|
38
|
-
abstract Push(key: string, ...data: unknown[]): Promise<void> | void;
|
|
39
|
-
abstract Splice(key: string, start: number, deleteCount?: number, ...items: unknown[]): Promise<any[]> | any[];
|
|
40
37
|
/**
|
|
41
38
|
* Updates the root map with diff results by grouping changes by root path.
|
|
42
39
|
* @param results - Array of diff results to apply
|
|
@@ -54,3 +51,16 @@ export declare abstract class Store {
|
|
|
54
51
|
*/
|
|
55
52
|
private UpdateRootObject;
|
|
56
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Base class for observable data store management.
|
|
56
|
+
* Stores root objects as ObservableNode instances and manages updates through diff operations.
|
|
57
|
+
*
|
|
58
|
+
* @see StoreSync
|
|
59
|
+
* @see StoreAsync
|
|
60
|
+
*/
|
|
61
|
+
export declare abstract class Store extends ReadStore {
|
|
62
|
+
abstract Write(data: unknown, key?: string): Promise<void> | void;
|
|
63
|
+
abstract Patch(key: string, patch: unknown): Promise<void> | void;
|
|
64
|
+
abstract Push(key: string, ...data: unknown[]): Promise<void> | void;
|
|
65
|
+
abstract Splice(key: string, start: number, deleteCount?: number, ...items: unknown[]): Promise<any[]> | any[];
|
|
66
|
+
}
|
package/Store/Store/store.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { JsonDeepClone } from "../../Utils/json";
|
|
2
2
|
import { ObservableNode } from "../Tree/observableNode";
|
|
3
3
|
/**
|
|
4
|
-
* Base class for observable data
|
|
5
|
-
*
|
|
4
|
+
* Base class for reading observable data: holds root objects as ObservableNode instances
|
|
5
|
+
* and applies diff results to them. Subclasses decide where the diffs come from: `Store`
|
|
6
|
+
* adds the write methods, and `PushStore` takes them from a `DiffTreeWriter`.
|
|
6
7
|
*
|
|
7
|
-
* @see
|
|
8
|
-
* @see
|
|
8
|
+
* @see Store
|
|
9
|
+
* @see PushStore
|
|
9
10
|
*/
|
|
10
|
-
export class
|
|
11
|
+
export class ReadStore {
|
|
11
12
|
/**
|
|
12
|
-
* Creates an instance of
|
|
13
|
+
* Creates an instance of ReadStore.
|
|
13
14
|
* @param keyFunc Optional function to generate a key for a given data value.
|
|
14
15
|
* When provided, enables alias functionality where values can reference other root objects.
|
|
15
16
|
*/
|
|
@@ -85,3 +86,12 @@ export class Store {
|
|
|
85
86
|
ObservableNode.ApplyDiff(rootObject, results);
|
|
86
87
|
}
|
|
87
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* Base class for observable data store management.
|
|
91
|
+
* Stores root objects as ObservableNode instances and manages updates through diff operations.
|
|
92
|
+
*
|
|
93
|
+
* @see StoreSync
|
|
94
|
+
* @see StoreAsync
|
|
95
|
+
*/
|
|
96
|
+
export class Store extends ReadStore {
|
|
97
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DiffTreeProjectionMap } from "../Diff/diffTree";
|
|
1
|
+
import { DiffTreeProjectionMap } from "../Diff/diffTree.types";
|
|
2
2
|
import { Store } from "./store";
|
|
3
3
|
/**
|
|
4
4
|
* StoreSync class extends the base Store class to provide synchronous data management operations.
|
|
@@ -70,5 +70,5 @@ export declare class StoreSync extends Store {
|
|
|
70
70
|
* @param items Optional elements to insert into the array.
|
|
71
71
|
* @returns The array of deleted elements.
|
|
72
72
|
*/
|
|
73
|
-
Splice(key: string, start: number, deleteCount?: number, ...items: unknown[]):
|
|
73
|
+
Splice(key: string, start: number, deleteCount?: number, ...items: unknown[]): unknown[];
|
|
74
74
|
}
|
|
@@ -188,7 +188,7 @@ export declare namespace ObservableScope {
|
|
|
188
188
|
* @param scope The scope to stop watching.
|
|
189
189
|
* @param callback The callback function to remove.
|
|
190
190
|
*/
|
|
191
|
-
function Unwatch<T>(scope: IObservableScope<T>, callback: (scope: IObservableScope<T>) => void): void;
|
|
191
|
+
function Unwatch<T>(scope: IObservableScope<T> | IBasicObservableScope<T>, callback: (scope: IObservableScope<T>) => void): void;
|
|
192
192
|
/**
|
|
193
193
|
* Registers a callback to be invoked when the scope's value is updated.
|
|
194
194
|
* Only applies to dynamic scopes; no-op for static scopes.
|
package/Store/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
export { Store } from "./Store/store";
|
|
1
|
+
export { ReadStore, Store } from "./Store/store";
|
|
2
2
|
export { StoreSync } from "./Store/storeSync";
|
|
3
3
|
export { StoreAsync } from "./Store/storeAsync";
|
|
4
|
+
export { PushStore } from "./Store/pushStore";
|
|
4
5
|
export { ObservableScope } from "./Tree/observableScope";
|
|
5
6
|
export { ObservableNode } from "./Tree/observableNode";
|
package/Store/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
export { Store } from "./Store/store";
|
|
1
|
+
export { ReadStore, Store } from "./Store/store";
|
|
2
2
|
export { StoreSync } from "./Store/storeSync";
|
|
3
3
|
export { StoreAsync } from "./Store/storeAsync";
|
|
4
|
+
export { PushStore } from "./Store/pushStore";
|
|
4
5
|
export { ObservableScope } from "./Tree/observableScope";
|
|
5
6
|
export { ObservableNode } from "./Tree/observableNode";
|