doxum 0.1.17 → 0.1.18

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/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { $ as OptionalNode, B as DocumentAddress, C as Draft, E as snapshot, F as MemberChange, G as DocumentTreeValue, H as DocumentListConfig, I as ValueTransition, J as Infer, K as FieldNode, N as Change, P as ChangeSet, Q as ObjectShape, S as DocumentImpact, T as replace, U as DocumentNode, V as DocumentAnchor, W as DocumentTreeNode, X as MapNode, Y as ListNode, Z as ObjectNode, _ as Unsubscribe, _t as Validator, a as DocumentDisposedError, at as VariantNode, b as MutationIssueCode, c as DocumentReentrancyError, ct as list, d as LocalHistory, dt as optional, et as PathValueOf, f as ObserverError, ft as table, g as TransactionResult, gt as ParseIssue, h as TransactionRejected, ht as ParseError, i as DocumentDiagnostic, it as TreeNode, l as DocumentRuntime, lt as map, mt as variant, n as CommitSource, nt as SchemaPath, o as DocumentProblem, ot as VariantShape, p as OperationResult, pt as tree, r as DocumentCommit, rt as TableNode, s as DocumentReadable, st as field, tt as ReadonlyValue, u as HistoryState, ut as object, v as Readable, vt as parse, w as Read, x as CollectionImpact, y as MutationIssue } from "./contract-XmKnroJq.cjs";
2
- import { a as observe, d as ExternalCollectionSource, f as ExternalValueEvent, h as ProjectionError, i as input, l as ExternalCollectionEvent, m as ProjectionDisposedError, n as Projection, o as CollectionChange, p as ExternalValueSource, r as derive, t as Input, u as ExternalCollectionRead } from "./definition-Bf8Z7xtX.cjs";
2
+ import { _ as ExternalValueSource, c as Projection, d as observe, f as CollectionChange, g as ExternalValueEvent, h as ExternalCollectionSource, l as derive, m as ExternalCollectionRead, o as incremental, p as ExternalCollectionEvent, s as Input, u as input, v as ProjectionDisposedError, y as ProjectionError } from "./advanced-GTwIWcn6.cjs";
3
3
 
4
4
  //#region core/src/runtime.d.ts
5
5
  declare const createDocument: <S extends ObjectNode>(input: {
@@ -18,20 +18,40 @@ type DocumentSelector<TSchema extends ObjectNode, TResult> = (read: Read<TSchema
18
18
  declare const read: <TSchema extends ObjectNode, TResult>(runtime: DocumentReadable<TSchema>, selector: DocumentSelector<TSchema, TResult>) => TResult;
19
19
  //#endregion
20
20
  //#region core/src/projection/runtime.d.ts
21
+ type KeyedDraft<K extends string, V> = {
22
+ get(key: K): V | undefined;
23
+ has(key: K): boolean;
24
+ set(key: K, value: V): void;
25
+ remove(key: K): void;
26
+ };
21
27
  type ProjectionRuntime = {
22
28
  get<T>(projection: Projection<T, unknown>): T;
23
29
  readable<T>(projection: Projection<T, unknown>): Readable<T>;
24
30
  readable<T, R>(projection: Projection<T, unknown>, selector: (value: T) => R, equality?: (previous: R, next: R) => boolean): Readable<R>;
25
31
  set<T>(input: Input<T>, value: T): void;
32
+ update<K extends string, V>(input: Input<ReadonlyMap<K, V>, CollectionChange<K, V>>, run: (draft: KeyedDraft<K, V>) => void): void;
26
33
  batch<T>(run: () => T): T;
27
34
  batch<T>(options: {
28
35
  readonly cause?: unknown;
29
36
  }, run: () => T): T;
37
+ scope(): ProjectionScope;
38
+ dispose(): void;
39
+ };
40
+ type ProjectionScope = {
41
+ readonly input: typeof input;
42
+ readonly derive: typeof derive;
43
+ readonly incremental: typeof incremental;
44
+ get<T>(projection: Projection<T, unknown>): T;
45
+ readable<T>(projection: Projection<T, unknown>): Readable<T>;
46
+ readable<T, R>(projection: Projection<T, unknown>, selector: (value: T) => R, equality?: (previous: R, next: R) => boolean): Readable<R>;
47
+ set<T>(input: Input<T>, value: T): void;
48
+ update<K extends string, V>(input: Input<ReadonlyMap<K, V>, CollectionChange<K, V>>, run: (draft: KeyedDraft<K, V>) => void): void;
49
+ batch: ProjectionRuntime['batch'];
30
50
  dispose(): void;
31
51
  };
32
52
  declare const createProjectionRuntime: (options?: {
33
53
  readonly onError?: (error: ProjectionError) => void;
34
54
  }) => ProjectionRuntime;
35
55
  //#endregion
36
- export { type Change, type ChangeSet, type CollectionChange, type CollectionImpact, type CommitSource, type DocumentAddress, type DocumentAnchor, type DocumentCommit, type DocumentDiagnostic, DocumentDisposedError, type DocumentImpact, type DocumentListConfig, type DocumentNode, type DocumentProblem, type DocumentReadable, DocumentReentrancyError, type DocumentRuntime, type DocumentSelector, type DocumentTreeNode, type DocumentTreeValue, type Draft, type ExternalCollectionEvent, type ExternalCollectionRead, type ExternalCollectionSource, type ExternalValueEvent, type ExternalValueSource, type FieldNode, type HistoryState, type Infer, type Input, type ListNode, type LocalHistory, type MapNode, type MemberChange, type MutationIssue, type MutationIssueCode, type ObjectNode, type ObjectShape, type ObserverError, type OperationResult, type OptionalNode, ParseError, type ParseIssue, type PathValueOf, type Projection, ProjectionDisposedError, ProjectionError, type ProjectionRuntime, type Read, type Readable, type ReadonlyValue, type SchemaPath, type TableNode, TransactionRejected, type TransactionResult, type TreeNode, type Unsubscribe, type Validator, type ValueTransition, type VariantNode, type VariantShape, asReadable, createDocument, createProjectionRuntime, derive, field, input, list, map, object, observe, optional, parse, read, replace, snapshot, table, tree, variant };
56
+ export { type Change, type ChangeSet, type CollectionChange, type CollectionImpact, type CommitSource, type DocumentAddress, type DocumentAnchor, type DocumentCommit, type DocumentDiagnostic, DocumentDisposedError, type DocumentImpact, type DocumentListConfig, type DocumentNode, type DocumentProblem, type DocumentReadable, DocumentReentrancyError, type DocumentRuntime, type DocumentSelector, type DocumentTreeNode, type DocumentTreeValue, type Draft, type ExternalCollectionEvent, type ExternalCollectionRead, type ExternalCollectionSource, type ExternalValueEvent, type ExternalValueSource, type FieldNode, type HistoryState, type Infer, type Input, type ListNode, type LocalHistory, type MapNode, type MemberChange, type MutationIssue, type MutationIssueCode, type ObjectNode, type ObjectShape, type ObserverError, type OperationResult, type OptionalNode, ParseError, type ParseIssue, type PathValueOf, type Projection, ProjectionDisposedError, ProjectionError, type ProjectionRuntime, type ProjectionScope, type Read, type Readable, type ReadonlyValue, type SchemaPath, type TableNode, TransactionRejected, type TransactionResult, type TreeNode, type Unsubscribe, type Validator, type ValueTransition, type VariantNode, type VariantShape, asReadable, createDocument, createProjectionRuntime, derive, field, input, list, map, object, observe, optional, parse, read, replace, snapshot, table, tree, variant };
37
57
  //# sourceMappingURL=index.d.cts.map
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { $ as OptionalNode, B as DocumentAddress, C as Draft, E as snapshot, F as MemberChange, G as DocumentTreeValue, H as DocumentListConfig, I as ValueTransition, J as Infer, K as FieldNode, N as Change, P as ChangeSet, Q as ObjectShape, S as DocumentImpact, T as replace, U as DocumentNode, V as DocumentAnchor, W as DocumentTreeNode, X as MapNode, Y as ListNode, Z as ObjectNode, _ as Unsubscribe, _t as Validator, a as DocumentDisposedError, at as VariantNode, b as MutationIssueCode, c as DocumentReentrancyError, ct as list, d as LocalHistory, dt as optional, et as PathValueOf, f as ObserverError, ft as table, g as TransactionResult, gt as ParseIssue, h as TransactionRejected, ht as ParseError, i as DocumentDiagnostic, it as TreeNode, l as DocumentRuntime, lt as map, mt as variant, n as CommitSource, nt as SchemaPath, o as DocumentProblem, ot as VariantShape, p as OperationResult, pt as tree, r as DocumentCommit, rt as TableNode, s as DocumentReadable, st as field, tt as ReadonlyValue, u as HistoryState, ut as object, v as Readable, vt as parse, w as Read, x as CollectionImpact, y as MutationIssue } from "./contract-CATCCVu5.js";
2
- import { a as observe, d as ExternalCollectionSource, f as ExternalValueEvent, h as ProjectionError, i as input, l as ExternalCollectionEvent, m as ProjectionDisposedError, n as Projection, o as CollectionChange, p as ExternalValueSource, r as derive, t as Input, u as ExternalCollectionRead } from "./definition-BdLGD8Rq.js";
2
+ import { _ as ExternalValueSource, c as Projection, d as observe, f as CollectionChange, g as ExternalValueEvent, h as ExternalCollectionSource, l as derive, m as ExternalCollectionRead, o as incremental, p as ExternalCollectionEvent, s as Input, u as input, v as ProjectionDisposedError, y as ProjectionError } from "./advanced-c0F3gpLG.js";
3
3
 
4
4
  //#region core/src/runtime.d.ts
5
5
  declare const createDocument: <S extends ObjectNode>(input: {
@@ -18,20 +18,40 @@ type DocumentSelector<TSchema extends ObjectNode, TResult> = (read: Read<TSchema
18
18
  declare const read: <TSchema extends ObjectNode, TResult>(runtime: DocumentReadable<TSchema>, selector: DocumentSelector<TSchema, TResult>) => TResult;
19
19
  //#endregion
20
20
  //#region core/src/projection/runtime.d.ts
21
+ type KeyedDraft<K extends string, V> = {
22
+ get(key: K): V | undefined;
23
+ has(key: K): boolean;
24
+ set(key: K, value: V): void;
25
+ remove(key: K): void;
26
+ };
21
27
  type ProjectionRuntime = {
22
28
  get<T>(projection: Projection<T, unknown>): T;
23
29
  readable<T>(projection: Projection<T, unknown>): Readable<T>;
24
30
  readable<T, R>(projection: Projection<T, unknown>, selector: (value: T) => R, equality?: (previous: R, next: R) => boolean): Readable<R>;
25
31
  set<T>(input: Input<T>, value: T): void;
32
+ update<K extends string, V>(input: Input<ReadonlyMap<K, V>, CollectionChange<K, V>>, run: (draft: KeyedDraft<K, V>) => void): void;
26
33
  batch<T>(run: () => T): T;
27
34
  batch<T>(options: {
28
35
  readonly cause?: unknown;
29
36
  }, run: () => T): T;
37
+ scope(): ProjectionScope;
38
+ dispose(): void;
39
+ };
40
+ type ProjectionScope = {
41
+ readonly input: typeof input;
42
+ readonly derive: typeof derive;
43
+ readonly incremental: typeof incremental;
44
+ get<T>(projection: Projection<T, unknown>): T;
45
+ readable<T>(projection: Projection<T, unknown>): Readable<T>;
46
+ readable<T, R>(projection: Projection<T, unknown>, selector: (value: T) => R, equality?: (previous: R, next: R) => boolean): Readable<R>;
47
+ set<T>(input: Input<T>, value: T): void;
48
+ update<K extends string, V>(input: Input<ReadonlyMap<K, V>, CollectionChange<K, V>>, run: (draft: KeyedDraft<K, V>) => void): void;
49
+ batch: ProjectionRuntime['batch'];
30
50
  dispose(): void;
31
51
  };
32
52
  declare const createProjectionRuntime: (options?: {
33
53
  readonly onError?: (error: ProjectionError) => void;
34
54
  }) => ProjectionRuntime;
35
55
  //#endregion
36
- export { type Change, type ChangeSet, type CollectionChange, type CollectionImpact, type CommitSource, type DocumentAddress, type DocumentAnchor, type DocumentCommit, type DocumentDiagnostic, DocumentDisposedError, type DocumentImpact, type DocumentListConfig, type DocumentNode, type DocumentProblem, type DocumentReadable, DocumentReentrancyError, type DocumentRuntime, type DocumentSelector, type DocumentTreeNode, type DocumentTreeValue, type Draft, type ExternalCollectionEvent, type ExternalCollectionRead, type ExternalCollectionSource, type ExternalValueEvent, type ExternalValueSource, type FieldNode, type HistoryState, type Infer, type Input, type ListNode, type LocalHistory, type MapNode, type MemberChange, type MutationIssue, type MutationIssueCode, type ObjectNode, type ObjectShape, type ObserverError, type OperationResult, type OptionalNode, ParseError, type ParseIssue, type PathValueOf, type Projection, ProjectionDisposedError, ProjectionError, type ProjectionRuntime, type Read, type Readable, type ReadonlyValue, type SchemaPath, type TableNode, TransactionRejected, type TransactionResult, type TreeNode, type Unsubscribe, type Validator, type ValueTransition, type VariantNode, type VariantShape, asReadable, createDocument, createProjectionRuntime, derive, field, input, list, map, object, observe, optional, parse, read, replace, snapshot, table, tree, variant };
56
+ export { type Change, type ChangeSet, type CollectionChange, type CollectionImpact, type CommitSource, type DocumentAddress, type DocumentAnchor, type DocumentCommit, type DocumentDiagnostic, DocumentDisposedError, type DocumentImpact, type DocumentListConfig, type DocumentNode, type DocumentProblem, type DocumentReadable, DocumentReentrancyError, type DocumentRuntime, type DocumentSelector, type DocumentTreeNode, type DocumentTreeValue, type Draft, type ExternalCollectionEvent, type ExternalCollectionRead, type ExternalCollectionSource, type ExternalValueEvent, type ExternalValueSource, type FieldNode, type HistoryState, type Infer, type Input, type ListNode, type LocalHistory, type MapNode, type MemberChange, type MutationIssue, type MutationIssueCode, type ObjectNode, type ObjectShape, type ObserverError, type OperationResult, type OptionalNode, ParseError, type ParseIssue, type PathValueOf, type Projection, ProjectionDisposedError, ProjectionError, type ProjectionRuntime, type ProjectionScope, type Read, type Readable, type ReadonlyValue, type SchemaPath, type TableNode, TransactionRejected, type TransactionResult, type TreeNode, type Unsubscribe, type Validator, type ValueTransition, type VariantNode, type VariantShape, asReadable, createDocument, createProjectionRuntime, derive, field, input, list, map, object, observe, optional, parse, read, replace, snapshot, table, tree, variant };
37
57
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ import { A as insert, D as equal, F as remove, L as profile, M as keys, N as mat
2
2
  import { a as ParseError, c as copyValue, i as snapshot, l as equalValue, n as createAccess, o as checkKey, r as replace, s as checkValue, t as collectionAccess, u as parse } from "./scope-d-L87NeC.js";
3
3
  import { _ as affected, a as documentReadableOwner, d as accessOf, f as bindRuntimeAccess, g as TransactionRejected, h as DocumentReentrancyError, i as disposeNotification, l as subscribeRoot, m as DocumentDisposedError, n as bindDocumentReadable, o as notify, p as readWith, r as createNotification, s as shareNotification, t as attachProjection, u as subscribeTargets, v as belongs, y as same } from "./notification-IQIJyH3Q.js";
4
4
  import { n as bindRuntimeDriver, o as decodeChanges, r as disposeRuntimeDriver, s as sealChanges, t as assertRuntimeWritable } from "./driver-CSnhMjbP.js";
5
- import { a as input, i as derive, o as observe, r as definitionOf } from "./definition-DUo1lfQ8.js";
5
+ import { a as observe, i as input, n as definitionOf, o as ownDefinition, r as derive, s as ownerOf, t as incremental } from "./advanced-DgRnyRAc.js";
6
6
  //#region core/src/schema.ts
7
7
  const node = (value) => Object.freeze(value);
8
8
  const field = (validator) => node({
@@ -1195,7 +1195,9 @@ const createScheduler = (onError) => {
1195
1195
  phase = "compute";
1196
1196
  lock(true);
1197
1197
  try {
1198
- pending.forEach((source) => source.consumers.forEach(enqueue));
1198
+ pending.forEach((source) => {
1199
+ if (source.shouldSettle?.() ?? true) source.consumers.forEach(enqueue);
1200
+ });
1199
1201
  while (heap.length) {
1200
1202
  const node = pop();
1201
1203
  if (node.disposed) continue;
@@ -1337,12 +1339,29 @@ const createScheduler = (onError) => {
1337
1339
  unregisterSource(handle) {
1338
1340
  const record = records.get(handle);
1339
1341
  if (record) {
1342
+ assertIdle();
1343
+ if (record.consumers.size) throw new Error("Projection source is still used by a node.");
1340
1344
  record.disposed = true;
1341
1345
  pending.delete(record);
1342
1346
  record.clear();
1343
1347
  records.delete(handle);
1344
1348
  }
1345
1349
  },
1350
+ releaseNode(handle) {
1351
+ assertIdle();
1352
+ const node = records.get(handle);
1353
+ if (!node || !nodes.has(node)) throw new Error("Unknown projection node.");
1354
+ if (node.consumers.size) throw new Error("Projection node is still used by another node.");
1355
+ node.disposed = true;
1356
+ node.sources.forEach((source) => source.consumers.delete(node));
1357
+ node.release();
1358
+ node.clear();
1359
+ nodes.delete(node);
1360
+ records.delete(handle);
1361
+ pending.delete(node);
1362
+ queued.delete(node);
1363
+ emissions.delete(node);
1364
+ },
1346
1365
  source(handle) {
1347
1366
  assertActive();
1348
1367
  const source = records.get(handle);
@@ -1684,6 +1703,149 @@ const createSources = (scheduler) => {
1684
1703
  }
1685
1704
  };
1686
1705
  };
1706
+ const collectionInput = (initial) => {
1707
+ const values = new Map(initial);
1708
+ const touched = /* @__PURE__ */ new Map();
1709
+ let beforeIds;
1710
+ let revision = 0;
1711
+ let cause;
1712
+ const handle = Object.freeze({ kind: "collection" });
1713
+ const change = () => {
1714
+ const added = [];
1715
+ const updated = [];
1716
+ const removed = [];
1717
+ for (const [key, before] of touched) {
1718
+ const afterPresent = values.has(key);
1719
+ if (!before.present && afterPresent) added.push({
1720
+ key,
1721
+ after: values.get(key)
1722
+ });
1723
+ else if (before.present && !afterPresent) removed.push({
1724
+ key,
1725
+ before: before.value
1726
+ });
1727
+ else if (before.present && afterPresent && !Object.is(before.value, values.get(key))) updated.push({
1728
+ key,
1729
+ before: before.value,
1730
+ after: values.get(key)
1731
+ });
1732
+ }
1733
+ let order;
1734
+ if (beforeIds) {
1735
+ const afterIds = [...values.keys()];
1736
+ const beforeKeys = new Set(beforeIds);
1737
+ const afterKeys = new Set(afterIds);
1738
+ const beforeCommon = beforeIds.filter((key) => afterKeys.has(key));
1739
+ const afterCommon = afterIds.filter((key) => beforeKeys.has(key));
1740
+ if (beforeCommon.some((key, index) => key !== afterCommon[index])) order = Object.freeze({
1741
+ before: beforeIds,
1742
+ after: Object.freeze(afterIds)
1743
+ });
1744
+ }
1745
+ if (!added.length && !updated.length && !removed.length && !order) return void 0;
1746
+ return Object.freeze({
1747
+ kind: "incremental",
1748
+ added: Object.freeze(added),
1749
+ updated: Object.freeze(updated),
1750
+ removed: Object.freeze(removed),
1751
+ ...order ? { order } : {}
1752
+ });
1753
+ };
1754
+ const record = {
1755
+ consumers: /* @__PURE__ */ new Set(),
1756
+ disposed: false,
1757
+ fault: void 0,
1758
+ revision: () => revision,
1759
+ reset: () => false,
1760
+ shouldSettle: () => change() !== void 0,
1761
+ clear: () => {
1762
+ touched.clear();
1763
+ beforeIds = void 0;
1764
+ cause = void 0;
1765
+ },
1766
+ context: (active) => {
1767
+ assertScope(active);
1768
+ const read = Object.freeze({
1769
+ get: (key) => {
1770
+ assertScope(active);
1771
+ return values.get(key);
1772
+ },
1773
+ has: (key) => {
1774
+ assertScope(active);
1775
+ return values.has(key);
1776
+ },
1777
+ ids: () => {
1778
+ assertScope(active);
1779
+ return Object.freeze([...values.keys()]);
1780
+ }
1781
+ });
1782
+ return Object.freeze({
1783
+ kind: "collection",
1784
+ read,
1785
+ change: change(),
1786
+ revision,
1787
+ cause
1788
+ });
1789
+ }
1790
+ };
1791
+ scheduler.register(handle, record);
1792
+ return {
1793
+ source: handle,
1794
+ update(run) {
1795
+ scheduler.assertIdle();
1796
+ const staged = /* @__PURE__ */ new Map();
1797
+ let active = true;
1798
+ const draft = Object.freeze({
1799
+ get: (key) => {
1800
+ assertScope(() => active);
1801
+ const entry = staged.get(key);
1802
+ return entry ? entry.present ? entry.value : void 0 : values.get(key);
1803
+ },
1804
+ has: (key) => {
1805
+ assertScope(() => active);
1806
+ const entry = staged.get(key);
1807
+ return entry ? entry.present : values.has(key);
1808
+ },
1809
+ set: (key, value) => {
1810
+ assertScope(() => active);
1811
+ if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
1812
+ staged.set(key, {
1813
+ present: true,
1814
+ value
1815
+ });
1816
+ },
1817
+ remove: (key) => {
1818
+ assertScope(() => active);
1819
+ if (typeof key !== "string") throw new TypeError("Projection keys must be strings.");
1820
+ staged.set(key, { present: false });
1821
+ }
1822
+ });
1823
+ try {
1824
+ assertSynchronous(run(draft));
1825
+ } finally {
1826
+ active = false;
1827
+ }
1828
+ let changed = false;
1829
+ for (const [key, entry] of staged) {
1830
+ const present = values.has(key);
1831
+ if (entry.present ? present && Object.is(values.get(key), entry.value) : !present) continue;
1832
+ if (!touched.has(key)) touched.set(key, {
1833
+ present,
1834
+ value: values.get(key)
1835
+ });
1836
+ if (!beforeIds && present !== entry.present) beforeIds = Object.freeze([...values.keys()]);
1837
+ if (entry.present) values.set(key, entry.value);
1838
+ else values.delete(key);
1839
+ changed = true;
1840
+ }
1841
+ if (!changed) return;
1842
+ revision++;
1843
+ cause = scheduler.batchContext()?.cause ?? cause;
1844
+ scheduler.capture(record);
1845
+ scheduler.run();
1846
+ }
1847
+ };
1848
+ };
1687
1849
  const externalValueSource = (port, equal) => {
1688
1850
  scheduler.assertIdle();
1689
1851
  const old = externalSources.get(port);
@@ -1876,6 +2038,7 @@ const createSources = (scheduler) => {
1876
2038
  set
1877
2039
  });
1878
2040
  },
2041
+ collectionInput,
1879
2042
  fromReadable: (readable, options) => {
1880
2043
  scheduler.assertIdle();
1881
2044
  if (projectionHandles.has(readable)) throw new Error("Projection nodes must be declared directly as sources.");
@@ -2496,11 +2659,15 @@ const createProjectionGraph = (options) => {
2496
2659
  fromSource: (source, options) => sources.fromSource(source, options),
2497
2660
  fromCollectionSource: (source) => sources.fromCollectionSource(source),
2498
2661
  input: sources.input,
2662
+ collectionInput: sources.collectionInput,
2499
2663
  fromReadable: sources.fromReadable,
2500
2664
  value,
2501
2665
  collection: (spec) => createCollection(scheduler, spec),
2502
2666
  map,
2503
2667
  batch: scheduler.batch,
2668
+ assertIdle: scheduler.assertIdle,
2669
+ releaseNode: scheduler.releaseNode,
2670
+ releaseInput: scheduler.unregisterSource,
2504
2671
  dispose: () => {
2505
2672
  try {
2506
2673
  scheduler.dispose();
@@ -2670,16 +2837,27 @@ const sourceOf = (instance) => instance;
2670
2837
  const createProjectionRuntime = (options) => {
2671
2838
  const runtime = createProjectionGraph({ onError: options?.onError ?? (() => void 0) });
2672
2839
  const materialized = /* @__PURE__ */ new WeakMap();
2673
- const materialize = (projection) => {
2840
+ let disposed = false;
2841
+ const scopes = /* @__PURE__ */ new Set();
2842
+ const materialize = (projection, requester) => {
2843
+ const owner = ownerOf(projection);
2844
+ if (owner) {
2845
+ if (owner !== requester) throw new TypeError("Projection belongs to another scope.");
2846
+ if (!requester.active) throw new ProjectionDisposedError();
2847
+ }
2674
2848
  const old = materialized.get(projection);
2675
2849
  if (old) return old.node;
2676
2850
  const definition = definitionOf(projection);
2677
2851
  let instance;
2678
- let setter;
2852
+ let write;
2679
2853
  switch (definition.kind) {
2680
2854
  case "input": {
2681
2855
  const input = runtime.input(definition.initial, { isEqual: definition.isEqual });
2682
- setter = input.set;
2856
+ write = {
2857
+ kind: "value-input",
2858
+ source: input.source,
2859
+ set: input.set
2860
+ };
2683
2861
  instance = runtime.value({
2684
2862
  sources: { input: input.source },
2685
2863
  build: (values) => ({
@@ -2692,6 +2870,16 @@ const createProjectionRuntime = (options) => {
2692
2870
  });
2693
2871
  break;
2694
2872
  }
2873
+ case "collection-input": {
2874
+ const source = runtime.collectionInput(definition.initial);
2875
+ write = {
2876
+ kind: "collection-input",
2877
+ source: source.source,
2878
+ update: source.update
2879
+ };
2880
+ instance = runtime.map(source.source, (_key, value) => value);
2881
+ break;
2882
+ }
2695
2883
  case "readable": {
2696
2884
  const source = runtime.fromReadable(definition.readable, { isEqual: definition.isEqual });
2697
2885
  instance = runtime.value({
@@ -2763,7 +2951,7 @@ const createProjectionRuntime = (options) => {
2763
2951
  break;
2764
2952
  }
2765
2953
  case "derive": {
2766
- const sources = Object.fromEntries(definition.dependencies.map((dependency, index) => [`d${index}`, sourceOf(materialize(dependency))]));
2954
+ const sources = Object.fromEntries(definition.dependencies.map((dependency, index) => [`d${index}`, sourceOf(materialize(dependency, owner))]));
2767
2955
  instance = runtime.value({
2768
2956
  sources,
2769
2957
  build: (values) => ({
@@ -2777,7 +2965,7 @@ const createProjectionRuntime = (options) => {
2777
2965
  break;
2778
2966
  }
2779
2967
  case "incremental-value": {
2780
- const sources = Object.fromEntries(Object.entries(definition.dependencies).map(([key, dependency]) => [key, sourceOf(materialize(dependency))]));
2968
+ const sources = Object.fromEntries(Object.entries(definition.dependencies).map(([key, dependency]) => [key, sourceOf(materialize(dependency, owner))]));
2781
2969
  instance = runtime.value({
2782
2970
  sources,
2783
2971
  build: definition.build
@@ -2785,7 +2973,7 @@ const createProjectionRuntime = (options) => {
2785
2973
  break;
2786
2974
  }
2787
2975
  case "incremental-collection": {
2788
- const sources = Object.fromEntries(Object.entries(definition.dependencies).map(([key, dependency]) => [key, sourceOf(materialize(dependency))]));
2976
+ const sources = Object.fromEntries(Object.entries(definition.dependencies).map(([key, dependency]) => [key, sourceOf(materialize(dependency, owner))]));
2789
2977
  instance = runtime.collection({
2790
2978
  sources,
2791
2979
  build: definition.build,
@@ -2795,14 +2983,19 @@ const createProjectionRuntime = (options) => {
2795
2983
  break;
2796
2984
  }
2797
2985
  }
2798
- materialized.set(projection, {
2986
+ const record = write ? {
2799
2987
  node: instance,
2800
- set: setter
2801
- });
2988
+ ...write
2989
+ } : {
2990
+ kind: "projection",
2991
+ node: instance
2992
+ };
2993
+ materialized.set(projection, record);
2994
+ if (owner) owner.materialized.push(projection);
2802
2995
  return instance;
2803
2996
  };
2804
- const get = (projection) => {
2805
- const instance = materialize(projection);
2997
+ const get = (projection, requester) => {
2998
+ const instance = materialize(projection, requester);
2806
2999
  const current = instance.current();
2807
3000
  if (instance.kind === "collection") {
2808
3001
  const record = materialized.get(projection) ?? materialized.get(instance);
@@ -2830,11 +3023,11 @@ const createProjectionRuntime = (options) => {
2830
3023
  }
2831
3024
  return current;
2832
3025
  };
2833
- function readable(projection, selector, equality = Object.is) {
3026
+ const makeReadable = (projection, selector, equality = Object.is, requester) => {
2834
3027
  if (!selector) return Object.freeze({
2835
- current: () => get(projection),
2836
- revision: () => materialize(projection).revision(),
2837
- subscribe: (listener) => materialize(projection).subscribe(listener)
3028
+ current: () => get(projection, requester),
3029
+ revision: () => materialize(projection, requester).revision(),
3030
+ subscribe: (listener) => materialize(projection, requester).subscribe(listener)
2838
3031
  });
2839
3032
  let initialized = false;
2840
3033
  let value;
@@ -2849,7 +3042,7 @@ const createProjectionRuntime = (options) => {
2849
3042
  let unsubscribeSource;
2850
3043
  const listeners = /* @__PURE__ */ new Set();
2851
3044
  const updateCollectionSnapshot = () => {
2852
- const current = get(projection);
3045
+ const current = get(projection, requester);
2853
3046
  previousCollection = isMapLike(current) ? current : void 0;
2854
3047
  };
2855
3048
  const evaluate = () => {
@@ -2863,7 +3056,7 @@ const createProjectionRuntime = (options) => {
2863
3056
  let source;
2864
3057
  let next;
2865
3058
  try {
2866
- source = get(projection);
3059
+ source = get(projection, requester);
2867
3060
  next = selector(source);
2868
3061
  } finally {
2869
3062
  activeTracker = previousTracker;
@@ -2880,30 +3073,30 @@ const createProjectionRuntime = (options) => {
2880
3073
  }
2881
3074
  selection = nextSelection;
2882
3075
  initialized = true;
2883
- sourceRevision = materialize(projection).revision();
3076
+ sourceRevision = materialize(projection, requester).revision();
2884
3077
  updateCollectionSnapshot();
2885
3078
  return changed;
2886
3079
  };
2887
3080
  const ensureCurrent = () => {
2888
- const revision = materialize(projection).revision();
3081
+ const revision = materialize(projection, requester).revision();
2889
3082
  if (!initialized || !unsubscribeSource && sourceRevision !== revision) evaluate();
2890
3083
  return value;
2891
3084
  };
2892
3085
  const onProjectionChange = (incoming) => {
2893
3086
  let change = incoming;
2894
3087
  if (!change && previousCollection) {
2895
- const current = get(projection);
3088
+ const current = get(projection, requester);
2896
3089
  if (isMapLike(current)) {
2897
3090
  change = mapChange(previousCollection, current);
2898
3091
  previousCollection = current;
2899
3092
  if (!change) {
2900
- sourceRevision = materialize(projection).revision();
3093
+ sourceRevision = materialize(projection, requester).revision();
2901
3094
  return;
2902
3095
  }
2903
3096
  } else previousCollection = void 0;
2904
3097
  }
2905
3098
  if (!selectionAffects(selection, change)) {
2906
- sourceRevision = materialize(projection).revision();
3099
+ sourceRevision = materialize(projection, requester).revision();
2907
3100
  updateCollectionSnapshot();
2908
3101
  return;
2909
3102
  }
@@ -2911,12 +3104,12 @@ const createProjectionRuntime = (options) => {
2911
3104
  const changed = evaluate();
2912
3105
  if (unsubscribeSource && !sameSelection(previousSelection, selection)) {
2913
3106
  unsubscribeSource();
2914
- unsubscribeSource = materialize(projection).subscribe(onProjectionChange);
3107
+ unsubscribeSource = materialize(projection, requester).subscribe(onProjectionChange);
2915
3108
  }
2916
3109
  if (changed) Array.from(listeners).forEach((listener) => listener());
2917
3110
  };
2918
3111
  const installSource = () => {
2919
- if (!unsubscribeSource) unsubscribeSource = materialize(projection).subscribe(onProjectionChange);
3112
+ if (!unsubscribeSource) unsubscribeSource = materialize(projection, requester).subscribe(onProjectionChange);
2920
3113
  };
2921
3114
  return Object.freeze({
2922
3115
  current: ensureCurrent,
@@ -2937,18 +3130,134 @@ const createProjectionRuntime = (options) => {
2937
3130
  };
2938
3131
  }
2939
3132
  });
3133
+ };
3134
+ function readable(projection, selector, equality) {
3135
+ return makeReadable(projection, selector, equality);
2940
3136
  }
3137
+ const setInput = (target, value, requester) => {
3138
+ materialize(target, requester);
3139
+ const record = materialized.get(target);
3140
+ if (record?.kind !== "value-input") throw new TypeError("Projection is not a value input.");
3141
+ record.set(value);
3142
+ };
3143
+ const updateInput = (target, run, requester) => {
3144
+ materialize(target, requester);
3145
+ const record = materialized.get(target);
3146
+ if (record?.kind !== "collection-input") throw new TypeError("Projection is not a collection input.");
3147
+ record.update(run);
3148
+ };
3149
+ const createScope = () => {
3150
+ runtime.assertIdle();
3151
+ const state = {
3152
+ active: true,
3153
+ materialized: [],
3154
+ subscriptions: /* @__PURE__ */ new Set()
3155
+ };
3156
+ scopes.add(state);
3157
+ const assertActive = () => {
3158
+ if (!state.active) throw new ProjectionDisposedError();
3159
+ };
3160
+ const own = (projection) => {
3161
+ assertActive();
3162
+ const definition = definitionOf(projection);
3163
+ const dependencies = definition.kind === "derive" ? definition.dependencies : definition.kind === "incremental-value" || definition.kind === "incremental-collection" ? Object.values(definition.dependencies) : [];
3164
+ for (const dependency of dependencies) {
3165
+ const dependencyOwner = ownerOf(dependency);
3166
+ if (dependencyOwner && dependencyOwner !== state) throw new TypeError("A scope cannot depend on another scope.");
3167
+ }
3168
+ ownDefinition(projection, state);
3169
+ return projection;
3170
+ };
3171
+ const scopedInput = Object.assign((initial, equality) => own(input(initial, equality)), { collection: (initial) => own(input.collection(initial)) });
3172
+ const scopedDerive = (dependencies, compute, equality) => own(derive(dependencies, compute, equality));
3173
+ const scopedIncremental = Object.assign((dependencies, processor) => own(incremental(dependencies, processor)), { collection: (dependencies, processor) => own(incremental.collection(dependencies, processor)) });
3174
+ function scopeReadable(projection, selector, equality) {
3175
+ assertActive();
3176
+ materialize(projection, state);
3177
+ const source = makeReadable(projection, selector, equality, state);
3178
+ return Object.freeze({
3179
+ current: () => {
3180
+ assertActive();
3181
+ return source.current();
3182
+ },
3183
+ revision: () => {
3184
+ assertActive();
3185
+ return source.revision();
3186
+ },
3187
+ subscribe: (listener) => {
3188
+ assertActive();
3189
+ const stop = source.subscribe(listener);
3190
+ let subscribed = true;
3191
+ const unsubscribe = () => {
3192
+ if (!subscribed) return;
3193
+ subscribed = false;
3194
+ state.subscriptions.delete(unsubscribe);
3195
+ stop();
3196
+ };
3197
+ state.subscriptions.add(unsubscribe);
3198
+ return unsubscribe;
3199
+ }
3200
+ });
3201
+ }
3202
+ function scopeBatch(optionsOrCallback, maybeCallback) {
3203
+ assertActive();
3204
+ return typeof optionsOrCallback === "function" ? runtime.batch(optionsOrCallback) : runtime.batch(optionsOrCallback, maybeCallback);
3205
+ }
3206
+ return Object.freeze({
3207
+ input: scopedInput,
3208
+ derive: scopedDerive,
3209
+ incremental: scopedIncremental,
3210
+ get: (projection) => {
3211
+ assertActive();
3212
+ return get(projection, state);
3213
+ },
3214
+ readable: scopeReadable,
3215
+ set: (target, value) => {
3216
+ assertActive();
3217
+ setInput(target, value, state);
3218
+ },
3219
+ update: (target, run) => {
3220
+ assertActive();
3221
+ updateInput(target, run, state);
3222
+ },
3223
+ batch: scopeBatch,
3224
+ dispose: () => {
3225
+ if (!state.active) return;
3226
+ runtime.assertIdle();
3227
+ state.active = false;
3228
+ state.subscriptions.forEach((unsubscribe) => unsubscribe());
3229
+ for (const definition of state.materialized.reverse()) {
3230
+ const record = materialized.get(definition);
3231
+ if (!record) continue;
3232
+ runtime.releaseNode(record.node);
3233
+ if (record.kind !== "projection") runtime.releaseInput(record.source);
3234
+ materialized.delete(definition);
3235
+ }
3236
+ state.materialized.length = 0;
3237
+ scopes.delete(state);
3238
+ }
3239
+ });
3240
+ };
2941
3241
  const store = {
2942
3242
  get,
2943
3243
  readable,
2944
- set: ((input, value) => {
2945
- materialize(input);
2946
- const target = materialized.get(input);
2947
- if (!target?.set) throw new TypeError("Projection is not an input.");
2948
- target.set(value);
2949
- }),
3244
+ set: setInput,
3245
+ update: updateInput,
2950
3246
  batch: runtime.batch,
2951
- dispose: runtime.dispose
3247
+ scope: createScope,
3248
+ dispose: () => {
3249
+ if (disposed) return;
3250
+ runtime.assertIdle();
3251
+ disposed = true;
3252
+ for (const scope of scopes) {
3253
+ scope.active = false;
3254
+ scope.subscriptions.forEach((unsubscribe) => unsubscribe());
3255
+ scope.subscriptions.clear();
3256
+ scope.materialized.length = 0;
3257
+ }
3258
+ scopes.clear();
3259
+ runtime.dispose();
3260
+ }
2952
3261
  };
2953
3262
  return Object.freeze(store);
2954
3263
  };