doxum 0.1.40 → 0.1.42

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/README.md CHANGED
@@ -276,8 +276,8 @@ runtime.dispose();
276
276
 
277
277
  The stable vocabulary is `read` for a synchronous current value, `select` for a
278
278
  `Readable`, and `observe` for declaring a lazy source boundary. `ProjectionRuntime`
279
- exposes `read`, `select`, `items`, `update`, `batch`, `scope` and `dispose`.
280
- `ProjectionScope` mirrors `read`, `select`, `items`, `update`, `batch` and `dispose`, plus
279
+ exposes `read`, `select`, `items`, `update`, `batch`, `scope`, `onDispose` and `dispose`.
280
+ `ProjectionScope` mirrors `read`, `select`, `items`, `update`, `batch`, `onDispose` and `dispose`, plus
281
281
  `own(definitionOrTree)` for lifecycle ownership of lazy projection definitions.
282
282
 
283
283
  ### Commands inside a batch
@@ -638,8 +638,40 @@ values. Ordinary source resets preserve retained state when one exists. If a pro
638
638
  faults, the Runtime owns recovery, recreates declared state, and performs a reset
639
639
  evaluation. Stateless processors use the same recovery path without a state object.
640
640
 
641
+ ### Projection disposal
642
+
643
+ `runtime.dispose()`, `scope.dispose()` and Doxum unsubscribe functions are idempotent.
644
+ A parent Runtime ends its scopes, so later component cleanup can safely dispose them
645
+ again. Every subscription registration is independent, including registrations with
646
+ the same callback; an old unsubscribe cannot cancel a later subscription.
647
+
648
+ `runtime.onDispose(cleanup)` / `scope.onDispose(cleanup)` register synchronous,
649
+ cancellable callbacks. The returned function unregisters that callback without
650
+ executing it. Owner callbacks run in LIFO order; Runtime shutdown runs child scope
651
+ callbacks before its own while all projections remain readable. Cleanup may read
652
+ current values and unsubscribe, but cannot update, batch, subscribe or register new
653
+ resources. Perform domain writes before disposal. After callbacks, consumers and
654
+ producers are released, then source connections; failures do not prevent remaining
655
+ cleanup, and are thrown after disposal completes. Repeated disposal does not retry
656
+ failed callbacks. Reads, commands and new subscriptions still throw after disposal.
657
+
641
658
  ### React
642
659
 
660
+ ```tsx
661
+ // runtime is owned by the application/service; this Provider owns only its scope.
662
+ <ProjectionProvider runtime={runtime} fallback={<span>Loading…</span>}>
663
+ <TaskList />
664
+ </ProjectionProvider>
665
+ ```
666
+
667
+ Managed Providers acquire a scope after commit and release it on cleanup. StrictMode
668
+ setup replay creates a fresh scope; runtime replacement also remounts the scoped subtree.
669
+ `useProjectionScope()` returns that scope for `scope.own(...)` and `scope.onDispose(...)`.
670
+ The initial render and SSR render `fallback` (default `null`). For synchronous SSR or
671
+ an externally managed lifetime, use `<ProjectionProvider value={runtimeOrScope}>`;
672
+ this mode only injects the owner and never disposes it. The two modes are mutually
673
+ exclusive. Do not create disposable scopes in `useMemo` or revive a disposed scope.
674
+
643
675
  ```ts
644
676
  const task = useProjection(tasks, tasks => tasks.get(taskId), equality);
645
677
  const [mode, setMode] = useInput(filter);
@@ -648,8 +680,8 @@ const [selectedRows, updateSelectedRows] = useInput(selection);
648
680
 
649
681
  `useProjection(projection, selector, equality?)` uses the same keyed selector semantics
650
682
  as Core. `useInput` handles both scalar `Input<T>` and `CollectionInput<K,V>`; collection
651
- updates receive a keyed draft callback. `ProjectionProvider` accepts either a
652
- `ProjectionRuntime` or `ProjectionScope`.
683
+ updates receive a keyed draft callback. Reading a root projection through a managed
684
+ scope does not transfer its ownership; use `scope.own(...)` for local definitions.
653
685
 
654
686
  See [projection contracts](docs/projections.md).
655
687
 
package/dist/advanced.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_transform = require("./transform-8xXf0OKB.cjs");
2
+ const require_transform = require("./transform-BPh55GWO.cjs");
3
3
  const require_record = require("./record-ChYUi8PP.cjs");
4
4
  //#region core/src/projection/advanced.ts
5
5
  const resetCollectionChange = Object.freeze({ kind: "reset" });
@@ -1,4 +1,4 @@
1
- import { c as ProjectionChange, d as CollectionRead, l as CollectionChange, n as KeyedDependencyValues, o as KeyedProjection, s as Projection, t as KeyedDependencies, u as CollectionDraft } from "./dependency-Cbf3C8eJ.cjs";
1
+ import { c as ProjectionChange, d as CollectionRead, l as CollectionChange, n as KeyedDependencyValues, o as KeyedProjection, s as Projection, t as KeyedDependencies, u as CollectionDraft } from "./dependency-D0bo_xe_.cjs";
2
2
 
3
3
  //#region core/src/projection/collection/change.d.ts
4
4
  declare const collectionChange: {
@@ -1,4 +1,4 @@
1
- import { c as ProjectionChange, d as CollectionRead, l as CollectionChange, n as KeyedDependencyValues, o as KeyedProjection, s as Projection, t as KeyedDependencies, u as CollectionDraft } from "./dependency-DzxXGYe1.js";
1
+ import { c as ProjectionChange, d as CollectionRead, l as CollectionChange, n as KeyedDependencyValues, o as KeyedProjection, s as Projection, t as KeyedDependencies, u as CollectionDraft } from "./dependency-BYiwTiBj.js";
2
2
 
3
3
  //#region core/src/projection/collection/change.d.ts
4
4
  declare const collectionChange: {
package/dist/advanced.js CHANGED
@@ -1,4 +1,4 @@
1
- import { D as defineProcessor, S as compileProjectionDependencies, _ as assertSynchronous, r as collectionChange, t as createKeyedTransform, w as collectionView } from "./transform--WCn6_v0.js";
1
+ import { D as defineProcessor, S as compileProjectionDependencies, _ as assertSynchronous, r as collectionChange, t as createKeyedTransform, w as collectionView } from "./transform-BvDYPUMm.js";
2
2
  import { n as isPlainObject } from "./record-B2yRhYsc.js";
3
3
  //#region core/src/projection/advanced.ts
4
4
  const resetCollectionChange = Object.freeze({ kind: "reset" });
@@ -509,10 +509,11 @@ type MutationIssue = {
509
509
  };
510
510
  //#endregion
511
511
  //#region core/src/readable.d.ts
512
+ /** Ends one registration at most once; safe after owner disposal. */
512
513
  type Unsubscribe = () => void;
513
514
  type Readable<TValue> = {
514
515
  current(): TValue;
515
- revision(): number;
516
+ revision(): number; /** Each call registers independently, even when the listener function is reused. */
516
517
  subscribe(listener: () => void): Unsubscribe;
517
518
  };
518
519
  //#endregion
@@ -509,10 +509,11 @@ type MutationIssue = {
509
509
  };
510
510
  //#endregion
511
511
  //#region core/src/readable.d.ts
512
+ /** Ends one registration at most once; safe after owner disposal. */
512
513
  type Unsubscribe = () => void;
513
514
  type Readable<TValue> = {
514
515
  current(): TValue;
515
- revision(): number;
516
+ revision(): number; /** Each call registers independently, even when the listener function is reused. */
516
517
  subscribe(listener: () => void): Unsubscribe;
517
518
  };
518
519
  //#endregion
@@ -1,4 +1,4 @@
1
- import { _ as Unsubscribe, b as CollectionImpact, p as Synchronous } from "./contract-COgxO5sv.js";
1
+ import { _ as Unsubscribe, b as CollectionImpact, p as Synchronous } from "./contract-PqdXNaP8.js";
2
2
 
3
3
  //#region core/src/projection/contract.d.ts
4
4
  /** The one processor-facing collection transition protocol. */
@@ -1,4 +1,4 @@
1
- import { _ as Unsubscribe, b as CollectionImpact, p as Synchronous } from "./contract-CNR6BS75.cjs";
1
+ import { _ as Unsubscribe, b as CollectionImpact, p as Synchronous } from "./contract-DovG1Yp-.cjs";
2
2
 
3
3
  //#region core/src/projection/contract.d.ts
4
4
  /** The one processor-facing collection transition protocol. */
@@ -208,6 +208,20 @@ var AddressIndex = class {
208
208
  }
209
209
  };
210
210
  //#endregion
211
+ //#region core/src/subscription.ts
212
+ /** Each registration owns its identity, independently of callback reuse. */
213
+ const addListener = (listeners, listener, removed) => {
214
+ const entry = (...args) => listener(...args);
215
+ listeners.add(entry);
216
+ let active = true;
217
+ return () => {
218
+ if (!active) return;
219
+ active = false;
220
+ listeners.delete(entry);
221
+ removed?.();
222
+ };
223
+ };
224
+ //#endregion
211
225
  //#region core/src/mutation/changes.ts
212
226
  const validated = /* @__PURE__ */ new WeakSet();
213
227
  const compareChanges = (a, b) => {
@@ -399,4 +413,4 @@ const installRuntimeWriteDriver = (runtime, driver) => {
399
413
  });
400
414
  };
401
415
  //#endregion
402
- export { changeCount as a, AddressIndex as c, invalidValue as d, children as f, validate as g, parent as h, contextOf as i, MutationRejected as l, is as m, installRuntimeWriteDriver as n, decodeChanges as o, contains as p, bindContext as r, sealChanges as s, assertRuntimeWritable as t, fail as u };
416
+ export { validate as _, changeCount as a, addListener as c, fail as d, invalidValue as f, parent as g, is as h, contextOf as i, AddressIndex as l, contains as m, installRuntimeWriteDriver as n, decodeChanges as o, children as p, bindContext as r, sealChanges as s, assertRuntimeWritable as t, MutationRejected as u };
@@ -208,6 +208,20 @@ var AddressIndex = class {
208
208
  }
209
209
  };
210
210
  //#endregion
211
+ //#region core/src/subscription.ts
212
+ /** Each registration owns its identity, independently of callback reuse. */
213
+ const addListener = (listeners, listener, removed) => {
214
+ const entry = (...args) => listener(...args);
215
+ listeners.add(entry);
216
+ let active = true;
217
+ return () => {
218
+ if (!active) return;
219
+ active = false;
220
+ listeners.delete(entry);
221
+ removed?.();
222
+ };
223
+ };
224
+ //#endregion
211
225
  //#region core/src/mutation/changes.ts
212
226
  const validated = /* @__PURE__ */ new WeakSet();
213
227
  const compareChanges = (a, b) => {
@@ -411,6 +425,12 @@ Object.defineProperty(exports, "MutationRejected", {
411
425
  return MutationRejected;
412
426
  }
413
427
  });
428
+ Object.defineProperty(exports, "addListener", {
429
+ enumerable: true,
430
+ get: function() {
431
+ return addListener;
432
+ }
433
+ });
414
434
  Object.defineProperty(exports, "assertRuntimeWritable", {
415
435
  enumerable: true,
416
436
  get: function() {