shelving 1.101.0 → 1.102.1

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.
Files changed (48) hide show
  1. package/db/ItemState.d.ts +13 -4
  2. package/db/ItemState.js +51 -18
  3. package/db/QueryState.d.ts +13 -5
  4. package/db/QueryState.js +59 -25
  5. package/firestore/client/FirestoreClientProvider.js +3 -3
  6. package/firestore/server/FirestoreServerProvider.js +3 -3
  7. package/firestore/util/error.d.ts +2 -0
  8. package/firestore/util/error.js +19 -0
  9. package/iterate/ThroughGenerator.d.ts +1 -1
  10. package/iterate/ThroughGenerator.js +4 -4
  11. package/package.json +1 -1
  12. package/provider/DebugProvider.js +10 -10
  13. package/provider/MemoryProvider.d.ts +3 -1
  14. package/provider/MemoryProvider.js +6 -0
  15. package/react/createCache.js +3 -5
  16. package/react/useData.d.ts +5 -9
  17. package/react/useData.js +3 -8
  18. package/react/useState.d.ts +1 -12
  19. package/react/useState.js +6 -14
  20. package/sequence/AbstractSequence.d.ts +2 -2
  21. package/sequence/DeferredSequence.d.ts +2 -2
  22. package/sequence/InspectSequence.d.ts +1 -1
  23. package/sequence/LazyDeferredSequence.d.ts +14 -0
  24. package/sequence/LazyDeferredSequence.js +34 -0
  25. package/sequence/ThroughSequence.d.ts +3 -3
  26. package/sequence/ThroughSequence.js +4 -6
  27. package/sequence/index.d.ts +1 -2
  28. package/sequence/index.js +1 -2
  29. package/state/ArrayState.d.ts +1 -2
  30. package/state/ArrayState.js +2 -2
  31. package/state/BooleanState.d.ts +2 -2
  32. package/state/BooleanState.js +2 -2
  33. package/state/DictionaryState.d.ts +1 -2
  34. package/state/DictionaryState.js +2 -2
  35. package/state/State.d.ts +8 -16
  36. package/state/State.js +14 -21
  37. package/util/dispose.d.ts +39 -0
  38. package/util/dispose.js +61 -0
  39. package/util/index.d.ts +1 -1
  40. package/util/index.js +1 -1
  41. package/util/sequence.d.ts +3 -1
  42. package/util/sequence.js +27 -8
  43. package/sequence/SwitchingDeferredSequence.d.ts +0 -8
  44. package/sequence/SwitchingDeferredSequence.js +0 -13
  45. package/sequence/SwitchingSequence.d.ts +0 -10
  46. package/sequence/SwitchingSequence.js +0 -52
  47. package/util/switch.d.ts +0 -33
  48. package/util/switch.js +0 -49
package/db/ItemState.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { AsyncItemReference, ItemData, ItemReference, ItemValue } from "./ItemReference.js";
2
+ import type { AsyncProvider, Provider } from "../provider/Provider.js";
2
3
  import type { StopCallback } from "../util/callback.js";
3
4
  import type { Data } from "../util/data.js";
4
5
  import { BooleanState } from "../state/BooleanState.js";
@@ -15,10 +16,18 @@ export declare class ItemState<T extends Data = Data> extends State<ItemValue<T>
15
16
  get exists(): boolean;
16
17
  constructor(ref: ItemReference<T> | AsyncItemReference<T>);
17
18
  /** Refresh this state from the source provider. */
18
- readonly refresh: () => void;
19
+ refresh(provider?: Provider | AsyncProvider): void;
19
20
  private _refresh;
20
21
  /** Refresh this state if data in the cache is older than `maxAge` (in milliseconds). */
21
- refreshStale(maxAge: number): void;
22
- /** Subscribe this state to the source provider. */
23
- connectSource(): StopCallback;
22
+ refreshStale(maxAge: number, provider?: Provider | AsyncProvider): void;
23
+ /** Subscribe this state to a provider. */
24
+ connect(provider?: Provider | AsyncProvider): StopCallback;
25
+ [Symbol.asyncIterator](): AsyncGenerator<ItemValue<T>, void, void>;
26
+ private _iterating;
27
+ /** Start subscription to `MemoryProvider` if there is one. */
28
+ start(): void;
29
+ /** Stop subscription to `MemoryProvider` if there is one. */
30
+ stop(): void;
31
+ private _stop;
32
+ [Symbol.dispose](): void;
24
33
  }
package/db/ItemState.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { CacheProvider } from "../provider/CacheProvider.js";
2
- import { SwitchingDeferredSequence } from "../sequence/SwitchingDeferredSequence.js";
3
2
  import { BooleanState } from "../state/BooleanState.js";
4
3
  import { State } from "../state/State.js";
4
+ import { call } from "../util/callback.js";
5
5
  import { getRequired } from "../util/null.js";
6
6
  import { getOptionalSource } from "../util/source.js";
7
7
  import { getItemData } from "./ItemReference.js";
@@ -17,31 +17,32 @@ export class ItemState extends State {
17
17
  }
18
18
  /** Does the item exist? */
19
19
  get exists() {
20
- return !this.loading && !!this.value;
20
+ return !!this.value;
21
21
  }
22
22
  constructor(ref) {
23
23
  var _a;
24
24
  const { provider, collection, id } = ref;
25
- const table = (_a = getOptionalSource(CacheProvider, provider)) === null || _a === void 0 ? void 0 : _a.memory.getTable(collection);
26
- const time = table ? table.getQueryTime(ref) : null;
27
- const next = table ? new SwitchingDeferredSequence(sequence => sequence.from(table.getCachedItemSequence(id))) : undefined;
28
- super(table && typeof time === "number" ? { value: table.getItem(id), time, next } : { next });
25
+ const memory = (_a = getOptionalSource(CacheProvider, provider)) === null || _a === void 0 ? void 0 : _a.memory;
26
+ const time = memory ? memory.getItemTime(collection, id) : undefined;
27
+ super(memory && typeof time === "number" ? memory.getItem(collection, id) : undefined, time);
29
28
  this.busy = new BooleanState();
30
- /** Refresh this state from the source provider. */
31
- this.refresh = () => {
32
- if (!this.busy.value)
33
- void this._refresh();
34
- };
29
+ this._iterating = 0;
30
+ this._stop = undefined;
35
31
  this.ref = ref;
36
32
  // Queue a request to refresh the value if it doesn't exist.
37
33
  if (this.loading)
38
34
  this.refresh();
39
35
  }
40
- async _refresh() {
36
+ /** Refresh this state from the source provider. */
37
+ refresh(provider = this.ref.provider) {
38
+ if (!this.busy.value)
39
+ void this._refresh(provider);
40
+ }
41
+ async _refresh(provider) {
41
42
  this.busy.value = true;
42
43
  this.reason = undefined; // Optimistically clear the error.
43
44
  try {
44
- this.value = await this.ref.value;
45
+ this.value = (await provider.getItem(this.ref.collection, this.ref.id));
45
46
  }
46
47
  catch (thrown) {
47
48
  this.reason = thrown;
@@ -51,12 +52,44 @@ export class ItemState extends State {
51
52
  }
52
53
  }
53
54
  /** Refresh this state if data in the cache is older than `maxAge` (in milliseconds). */
54
- refreshStale(maxAge) {
55
+ refreshStale(maxAge, provider) {
55
56
  if (this.age > maxAge)
56
- this.refresh();
57
+ this.refresh(provider);
58
+ }
59
+ /** Subscribe this state to a provider. */
60
+ connect(provider = this.ref.provider) {
61
+ return this.from(provider.getItemSequence(this.ref.collection, this.ref.id));
62
+ }
63
+ // Override to subscribe to `MemoryProvider` while things are iterating over this state.
64
+ async *[Symbol.asyncIterator]() {
65
+ this.start();
66
+ this._iterating++;
67
+ try {
68
+ yield* super[Symbol.asyncIterator]();
69
+ }
70
+ finally {
71
+ this._iterating--;
72
+ if (this._iterating < 1)
73
+ this.stop();
74
+ }
75
+ }
76
+ /** Start subscription to `MemoryProvider` if there is one. */
77
+ start() {
78
+ var _a;
79
+ if (!this._stop) {
80
+ const { collection, id, provider } = this.ref;
81
+ const memory = (_a = getOptionalSource(CacheProvider, provider)) === null || _a === void 0 ? void 0 : _a.memory;
82
+ if (memory)
83
+ this._stop = this.from(memory.getCachedItemSequence(collection, id));
84
+ }
85
+ }
86
+ /** Stop subscription to `MemoryProvider` if there is one. */
87
+ stop() {
88
+ if (this._stop)
89
+ this._stop = void call(this._stop);
57
90
  }
58
- /** Subscribe this state to the source provider. */
59
- connectSource() {
60
- return this.from(this.ref);
91
+ // Implement `Disposable`
92
+ [Symbol.dispose]() {
93
+ this.stop();
61
94
  }
62
95
  }
@@ -1,5 +1,6 @@
1
1
  import type { ItemArray, ItemData, ItemValue } from "./ItemReference.js";
2
2
  import type { AsyncQueryReference, QueryReference } from "./QueryReference.js";
3
+ import type { AsyncProvider, Provider } from "../provider/Provider.js";
3
4
  import type { StopCallback } from "../util/callback.js";
4
5
  import type { Data } from "../util/data.js";
5
6
  import { BooleanState } from "../state/BooleanState.js";
@@ -26,18 +27,25 @@ export declare class QueryState<T extends Data = Data> extends State<ItemArray<T
26
27
  get count(): number;
27
28
  constructor(ref: QueryReference<T> | AsyncQueryReference<T>);
28
29
  /** Refresh this state from the source provider. */
29
- readonly refresh: () => void;
30
+ refresh(provider?: Provider | AsyncProvider): void;
30
31
  private _refresh;
31
32
  /** Refresh this state if data in the cache is older than `maxAge` (in milliseconds). */
32
33
  refreshStale(maxAge: number): void;
33
- /** Subscribe this state to the source provider. */
34
- connectSource(): StopCallback;
34
+ /** Subscribe this state to a provider. */
35
+ connect(provider?: Provider | AsyncProvider): StopCallback;
35
36
  /**
36
37
  * Load more items after the last once.
37
38
  * - Promise that needs to be handled.
38
39
  */
39
- readonly loadMore: () => void;
40
+ loadMore(): void;
40
41
  private _loadMore;
41
- /** Iterate over the items. */
42
+ [Symbol.asyncIterator](): AsyncGenerator<ItemArray<T>, void, void>;
43
+ private _iterating;
44
+ /** Start subscription to `MemoryProvider` if there is one. */
45
+ start(): void;
46
+ /** Stop subscription to `MemoryProvider` if there is one. */
47
+ stop(): void;
48
+ private _stop;
49
+ [Symbol.dispose](): void;
42
50
  [Symbol.iterator](): Iterator<ItemData<T>>;
43
51
  }
package/db/QueryState.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import { CacheProvider } from "../provider/CacheProvider.js";
2
- import { SwitchingDeferredSequence } from "../sequence/SwitchingDeferredSequence.js";
3
2
  import { BooleanState } from "../state/BooleanState.js";
4
3
  import { State } from "../state/State.js";
5
4
  import { getOptionalFirstItem, getOptionalLastItem } from "../util/array.js";
5
+ import { call } from "../util/callback.js";
6
+ import { NONE } from "../util/constants.js";
6
7
  import { getRequired } from "../util/null.js";
7
8
  import { getAfterQuery, getLimit } from "../util/query.js";
8
9
  import { getOptionalSource } from "../util/source.js";
@@ -30,7 +31,7 @@ export class QueryState extends State {
30
31
  }
31
32
  /** Does the document have at least one result. */
32
33
  get exists() {
33
- return !this.loading && !!this.value.length;
34
+ return !!this.value.length;
34
35
  }
35
36
  /** Get the number of items matching this query. */
36
37
  get count() {
@@ -39,36 +40,29 @@ export class QueryState extends State {
39
40
  constructor(ref) {
40
41
  var _a, _b;
41
42
  const { provider, collection, query } = ref;
42
- const table = (_a = getOptionalSource(CacheProvider, provider)) === null || _a === void 0 ? void 0 : _a.memory.getTable(collection);
43
- const time = table ? table.getQueryTime(ref) : null;
44
- const next = table ? new SwitchingDeferredSequence(sequence => sequence.from(table.getCachedQuerySequence(ref))) : undefined;
45
- super(table && typeof time === "number" ? { value: table.getQuery(ref), time, next } : { next });
43
+ const memory = (_a = getOptionalSource(CacheProvider, provider)) === null || _a === void 0 ? void 0 : _a.memory;
44
+ const time = memory ? memory.getQueryTime(collection, query) : undefined;
45
+ super(memory && typeof time === "number" ? memory.getQuery(collection, query) : NONE, time);
46
46
  this.busy = new BooleanState();
47
47
  this._hasMore = false;
48
- /** Refresh this state from the source provider. */
49
- this.refresh = () => {
50
- if (!this.busy.value)
51
- void this._refresh();
52
- };
53
- /**
54
- * Load more items after the last once.
55
- * - Promise that needs to be handled.
56
- */
57
- this.loadMore = () => {
58
- if (!this.busy.value)
59
- void this._loadMore();
60
- };
48
+ this._iterating = 0;
49
+ this._stop = undefined;
61
50
  this.ref = ref;
62
51
  this.limit = (_b = getLimit(query)) !== null && _b !== void 0 ? _b : Infinity;
63
52
  // Queue a request to refresh the value if it doesn't exist.
64
53
  if (this.loading)
65
54
  this.refresh();
66
55
  }
67
- async _refresh() {
56
+ /** Refresh this state from the source provider. */
57
+ refresh(provider = this.ref.provider) {
58
+ if (!this.busy.value)
59
+ void this._refresh(provider);
60
+ }
61
+ async _refresh(provider) {
68
62
  this.busy.value = true;
69
63
  this.reason = undefined; // Optimistically clear the error.
70
64
  try {
71
- const items = await this.ref.items;
65
+ const items = (await provider.getQuery(this.ref.collection, this.ref.query));
72
66
  this._hasMore = items.length >= this.limit; // If the query returned {limit} or more items, we can assume there are more items waiting to be queried.
73
67
  this.value = items;
74
68
  }
@@ -84,9 +78,17 @@ export class QueryState extends State {
84
78
  if (this.age > maxAge)
85
79
  this.refresh();
86
80
  }
87
- /** Subscribe this state to the source provider. */
88
- connectSource() {
89
- return this.from(this.ref);
81
+ /** Subscribe this state to a provider. */
82
+ connect(provider = this.ref.provider) {
83
+ return this.from(provider.getQuerySequence(this.ref.collection, this.ref.query));
84
+ }
85
+ /**
86
+ * Load more items after the last once.
87
+ * - Promise that needs to be handled.
88
+ */
89
+ loadMore() {
90
+ if (!this.busy.value)
91
+ void this._loadMore();
90
92
  }
91
93
  async _loadMore() {
92
94
  this.busy.value = true;
@@ -105,7 +107,39 @@ export class QueryState extends State {
105
107
  this.busy.value = false;
106
108
  }
107
109
  }
108
- /** Iterate over the items. */
110
+ // Override to subscribe to `MemoryProvider` while things are iterating over this state.
111
+ async *[Symbol.asyncIterator]() {
112
+ this.start();
113
+ this._iterating++;
114
+ try {
115
+ yield* super[Symbol.asyncIterator]();
116
+ }
117
+ finally {
118
+ this._iterating--;
119
+ if (this._iterating < 1)
120
+ this.stop();
121
+ }
122
+ }
123
+ /** Start subscription to `MemoryProvider` if there is one. */
124
+ start() {
125
+ var _a;
126
+ if (!this._stop) {
127
+ const { collection, query, provider } = this.ref;
128
+ const memory = (_a = getOptionalSource(CacheProvider, provider)) === null || _a === void 0 ? void 0 : _a.memory;
129
+ if (memory)
130
+ this._stop = this.from(memory.getCachedQuerySequence(collection, query));
131
+ }
132
+ }
133
+ /** Stop subscription to `MemoryProvider` if there is one. */
134
+ stop() {
135
+ if (this._stop)
136
+ this._stop = void call(this._stop);
137
+ }
138
+ // Implement `Disposable`
139
+ [Symbol.dispose]() {
140
+ this.stop();
141
+ }
142
+ // Implement `Iteratable`
109
143
  [Symbol.iterator]() {
110
144
  return this.value.values();
111
145
  }
@@ -1,6 +1,6 @@
1
1
  import { addDoc, collection, deleteDoc, doc, documentId, getDoc, getDocs, increment, limit, onSnapshot, orderBy, query, setDoc, updateDoc, where } from "firebase/firestore";
2
2
  import { getItemData } from "../../db/ItemReference.js";
3
- import { SwitchingDeferredSequence } from "../../sequence/SwitchingDeferredSequence.js";
3
+ import { LazyDeferredSequence } from "../../sequence/LazyDeferredSequence.js";
4
4
  import { getObject } from "../../util/object.js";
5
5
  import { getFilters, getLimit, getOrders } from "../../util/query.js";
6
6
  import { mapItems } from "../../util/transform.js";
@@ -61,7 +61,7 @@ export class FirestoreClientProvider {
61
61
  return _getItemValue(await getDoc(doc(this._firestore, c, id)));
62
62
  }
63
63
  getItemSequence(c, id) {
64
- return new SwitchingDeferredSequence(sequence => onSnapshot(doc(this._firestore, c, id), //
64
+ return new LazyDeferredSequence(sequence => onSnapshot(doc(this._firestore, c, id), //
65
65
  //
66
66
  snapshot => sequence.resolve(_getItemValue(snapshot)), reason => sequence.reject(reason)));
67
67
  }
@@ -82,7 +82,7 @@ export class FirestoreClientProvider {
82
82
  return _getItems(await getDocs(_getQuery(this._firestore, c, q)));
83
83
  }
84
84
  getQuerySequence(c, q) {
85
- return new SwitchingDeferredSequence(sequence => onSnapshot(_getQuery(this._firestore, c, q), //
85
+ return new LazyDeferredSequence(sequence => onSnapshot(_getQuery(this._firestore, c, q), //
86
86
  //
87
87
  snapshot => sequence.resolve(_getItems(snapshot)), reason => sequence.reject(reason)));
88
88
  }
@@ -1,6 +1,6 @@
1
1
  import { FieldPath, FieldValue, Firestore } from "@google-cloud/firestore";
2
2
  import { getItemData } from "../../db/ItemReference.js";
3
- import { SwitchingDeferredSequence } from "../../sequence/SwitchingDeferredSequence.js";
3
+ import { LazyDeferredSequence } from "../../sequence/LazyDeferredSequence.js";
4
4
  import { getObject } from "../../util/object.js";
5
5
  import { getFilters, getLimit, getOrders } from "../../util/query.js";
6
6
  import { mapItems } from "../../util/transform.js";
@@ -59,7 +59,7 @@ export class FirestoreServerProvider {
59
59
  }
60
60
  getItemSequence(c, id) {
61
61
  const ref = this._firestore.collection(c).doc(id);
62
- return new SwitchingDeferredSequence(sequence => ref.onSnapshot(snapshot => sequence.resolve(_getItemValue(snapshot)), //
62
+ return new LazyDeferredSequence(sequence => ref.onSnapshot(snapshot => sequence.resolve(_getItemValue(snapshot)), //
63
63
  sequence.reject));
64
64
  }
65
65
  async addItem(c, data) {
@@ -79,7 +79,7 @@ export class FirestoreServerProvider {
79
79
  }
80
80
  getQuerySequence(c, q) {
81
81
  const ref = _getQuery(this._firestore, c, q);
82
- return new SwitchingDeferredSequence(sequence => ref.onSnapshot(snapshot => sequence.resolve(_getItemArray(snapshot)), //
82
+ return new LazyDeferredSequence(sequence => ref.onSnapshot(snapshot => sequence.resolve(_getItemArray(snapshot)), //
83
83
  sequence.reject));
84
84
  }
85
85
  async setQuery(c, q, data) {
@@ -0,0 +1,2 @@
1
+ /** Convert a Firestore error (which use gRPC error codes) into a corresponding Shelving error. */
2
+ export declare function convertFirestoreError(thrown: unknown): never;
@@ -0,0 +1,19 @@
1
+ import { ConnectionError } from "../../error/ConnectionError.js";
2
+ import { PermissionError } from "../../error/PermissionError.js";
3
+ import { RequiredError } from "../../error/RequiredError.js";
4
+ import { isObject } from "../../util/object.js";
5
+ /** Convert a Firestore error (which use gRPC error codes) into a corresponding Shelving error. */
6
+ export function convertFirestoreError(thrown) {
7
+ if (isObject(thrown)) {
8
+ const code = thrown.code;
9
+ if (typeof code === "string") {
10
+ if (code === "unavailable")
11
+ throw new ConnectionError();
12
+ if (code === "not-found")
13
+ throw new RequiredError();
14
+ if (code === "permission-denied")
15
+ throw new PermissionError();
16
+ }
17
+ }
18
+ throw thrown;
19
+ }
@@ -1,7 +1,7 @@
1
1
  import { AbstractGenerator } from "./AbstractGenerator.js";
2
2
  /** Iterable that pulls values from a source iterable. */
3
3
  export declare class ThroughGenerator<T, R, N> extends AbstractGenerator<T, R, N> {
4
- private readonly _iterator;
4
+ private readonly _source;
5
5
  constructor(iterator: Iterator<T, R, N>);
6
6
  next(value: N): IteratorResult<T, R>;
7
7
  throw(thrown: Error | unknown): IteratorResult<T, R>;
@@ -3,16 +3,16 @@ import { AbstractGenerator } from "./AbstractGenerator.js";
3
3
  export class ThroughGenerator extends AbstractGenerator {
4
4
  constructor(iterator) {
5
5
  super();
6
- this._iterator = iterator;
6
+ this._source = iterator;
7
7
  }
8
8
  // Implement `AbstractGenerator`
9
9
  next(value) {
10
- return this._iterator.next(value);
10
+ return this._source.next(value);
11
11
  }
12
12
  throw(thrown) {
13
- return this._iterator.throw ? this._iterator.throw(thrown) : super.throw(thrown);
13
+ return this._source.throw ? this._source.throw(thrown) : super.throw(thrown);
14
14
  }
15
15
  return(value) {
16
- return this._iterator.return ? this._iterator.return(value) : super.return(value);
16
+ return this._source.return ? this._source.return(value) : super.return(value);
17
17
  }
18
18
  }
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "state-management",
12
12
  "query-builder"
13
13
  ],
14
- "version": "1.101.0",
14
+ "version": "1.102.1",
15
15
  "repository": "https://github.com/dhoulb/shelving",
16
16
  "author": "Dave Houlbrooke <dave@shax.com>",
17
17
  "license": "0BSD",
@@ -3,9 +3,9 @@
3
3
  class AbstractDebugProvider {
4
4
  async *getItemSequence(collection, id) {
5
5
  try {
6
- console.debug(" ITERATE", collection, id);
6
+ console.debug(" ITERATE", collection, id);
7
7
  for await (const item of this.source.getItemSequence(collection, id)) {
8
- console.debug(" ITERATE", collection, id, "GOT", item);
8
+ console.debug(" ITERATE", collection, id, item);
9
9
  yield item;
10
10
  }
11
11
  console.debug("✔ ITERATE", collection, id);
@@ -16,9 +16,9 @@ class AbstractDebugProvider {
16
16
  }
17
17
  async *getQuerySequence(collection, query) {
18
18
  try {
19
- console.debug(" ITERATE", collection, query);
19
+ console.debug(" ITERATE", collection, query);
20
20
  for await (const items of this.source.getQuerySequence(collection, query)) {
21
- console.debug(" ITERATE", collection, query, items);
21
+ console.debug(" ITERATE", collection, query, items);
22
22
  yield items;
23
23
  }
24
24
  console.debug("✔ ITERATE", collection, query);
@@ -37,7 +37,7 @@ export class DebugProvider extends AbstractDebugProvider {
37
37
  getItem(collection, id) {
38
38
  try {
39
39
  const item = this.source.getItem(collection, id);
40
- console.debug(" GET", collection, id, "ITEM", item);
40
+ console.debug(" GET", collection, id, item);
41
41
  return item;
42
42
  }
43
43
  catch (reason) {
@@ -48,7 +48,7 @@ export class DebugProvider extends AbstractDebugProvider {
48
48
  addItem(collection, data) {
49
49
  try {
50
50
  const id = this.source.addItem(collection, data);
51
- console.debug("✔ ADD", collection, data, "ID", id);
51
+ console.debug("✔ ADD", collection, data, id);
52
52
  return id;
53
53
  }
54
54
  catch (reason) {
@@ -68,8 +68,8 @@ export class DebugProvider extends AbstractDebugProvider {
68
68
  }
69
69
  updateItem(collection, id, updates) {
70
70
  try {
71
+ this.source.updateItem(collection, id, updates);
71
72
  console.debug("✔ UPDATE", collection, id, updates);
72
- return this.source.updateItem(collection, id, updates);
73
73
  }
74
74
  catch (reason) {
75
75
  console.error("✘ UPDATE", collection, id, updates, reason);
@@ -89,7 +89,7 @@ export class DebugProvider extends AbstractDebugProvider {
89
89
  getQuery(collection, query) {
90
90
  try {
91
91
  const items = this.source.getQuery(collection, query);
92
- console.debug("✔ GET", collection, query, "ITEMS", items);
92
+ console.debug("✔ GET", collection, query, items);
93
93
  return items;
94
94
  }
95
95
  catch (reason) {
@@ -141,7 +141,7 @@ export class AsyncDebugProvider extends AbstractDebugProvider {
141
141
  try {
142
142
  console.debug("⋯ GET", collection, id);
143
143
  const item = await this.source.getItem(collection, id);
144
- console.debug(" GET", collection, id, "ITEM", item);
144
+ console.debug(" GET", collection, id, item);
145
145
  return item;
146
146
  }
147
147
  catch (reason) {
@@ -198,7 +198,7 @@ export class AsyncDebugProvider extends AbstractDebugProvider {
198
198
  try {
199
199
  console.debug("⋯ GET", collection, query);
200
200
  const items = await this.source.getQuery(collection, query);
201
- console.debug("✔ GET", collection, query, "ITEMS", items);
201
+ console.debug("✔ GET", collection, query, items);
202
202
  return items;
203
203
  }
204
204
  catch (reason) {
@@ -16,6 +16,7 @@ export declare class MemoryProvider implements Provider {
16
16
  getItemTime(collection: string, id: string): number | undefined;
17
17
  getItem(collection: string, id: string): ItemValue;
18
18
  getItemSequence(collection: string, id: string): AsyncIterable<ItemValue>;
19
+ getCachedItemSequence(collection: string, id: string): AsyncIterable<ItemValue>;
19
20
  addItem(collection: string, data: Data): string;
20
21
  setItem(collection: string, id: string, data: Data): boolean;
21
22
  updateItem(collection: string, id: string, updates: Updates): boolean;
@@ -23,6 +24,7 @@ export declare class MemoryProvider implements Provider {
23
24
  getQueryTime(collection: string, query: ItemQuery): number | undefined;
24
25
  getQuery(collection: string, query: ItemQuery): ItemArray;
25
26
  getQuerySequence(collection: string, query: ItemQuery): AsyncIterable<ItemArray>;
27
+ getCachedQuerySequence(collection: string, query: ItemQuery): AsyncIterable<ItemArray>;
26
28
  setQuery(collection: string, query: ItemQuery, data: Data): number;
27
29
  updateQuery(collection: string, query: ItemQuery, updates: Updates): number;
28
30
  deleteQuery(collection: string, query: ItemQuery): number;
@@ -34,7 +36,7 @@ export declare class MemoryTable<T extends Data = Data> {
34
36
  /** Times data was last updated. */
35
37
  protected readonly _times: Map<string, number>;
36
38
  /** Deferred sequence of next values. */
37
- readonly _changed: DeferredSequence<void, void>;
39
+ readonly _changed: DeferredSequence<void>;
38
40
  getItemTime(id: string): number | undefined;
39
41
  getItem(id: string): ItemValue<T>;
40
42
  getItemSequence(id: string): AsyncIterable<ItemValue<T>>;
@@ -30,6 +30,9 @@ export class MemoryProvider {
30
30
  getItemSequence(collection, id) {
31
31
  return this.getTable(collection).getItemSequence(id);
32
32
  }
33
+ getCachedItemSequence(collection, id) {
34
+ return this.getTable(collection).getCachedItemSequence(id);
35
+ }
33
36
  addItem(collection, data) {
34
37
  return this.getTable(collection).addItem(data);
35
38
  }
@@ -51,6 +54,9 @@ export class MemoryProvider {
51
54
  getQuerySequence(collection, query) {
52
55
  return this.getTable(collection).getQuerySequence(query);
53
56
  }
57
+ getCachedQuerySequence(collection, query) {
58
+ return this.getTable(collection).getCachedQuerySequence(query);
59
+ }
54
60
  setQuery(collection, query, data) {
55
61
  return this.getTable(collection).setQuery(query, data);
56
62
  }
@@ -1,8 +1,5 @@
1
- import { createContext, createElement, useContext } from "react";
1
+ import { createContext, createElement, useContext, useRef } from "react";
2
2
  import { ConditionError } from "../error/ConditionError.js";
3
- import { useReduce } from "./useReduce.js";
4
- /** Reducer that gets an existing `Map` instance or creates a new one. */
5
- const _reduceMap = (previous) => previous || new Map();
6
3
  /** Create a cache. */
7
4
  export function createCache() {
8
5
  const context = createContext(undefined);
@@ -14,7 +11,8 @@ export function createCache() {
14
11
  return cache;
15
12
  },
16
13
  Cache: ({ children }) => {
17
- const cache = useReduce(_reduceMap);
14
+ var _a;
15
+ const cache = ((_a = useRef()).current || (_a.current = new Map()));
18
16
  return createElement(context.Provider, { children, value: cache });
19
17
  },
20
18
  };
@@ -1,18 +1,14 @@
1
1
  /// <reference types="react" />
2
2
  import type { AsyncQueryReference } from "../db/QueryReference.js";
3
- import type { ImmutableArray } from "../util/array.js";
4
- import type { Nullish } from "../util/null.js";
3
+ import type { Data } from "../util/data.js";
5
4
  import { AsyncItemReference } from "../db/ItemReference.js";
6
5
  import { ItemState } from "../db/ItemState.js";
7
6
  import { QueryState } from "../db/QueryState.js";
8
- type NullishReferenceState<T> = T extends undefined | null ? undefined : T extends AsyncItemReference<infer X> ? ItemState<X> : T extends AsyncQueryReference<infer X> ? QueryState<X> : never;
9
- /** Use one or more data items or queries. */
10
- export declare function useData<T extends Nullish<AsyncItemReference | AsyncQueryReference>>(ref: T): NullishReferenceState<T>;
11
- export declare function useData<T extends ImmutableArray<Nullish<AsyncItemReference | AsyncQueryReference>>>(...refs: T): {
12
- [K in keyof T]: NullishReferenceState<T[K]>;
13
- };
7
+ export declare function useData<T extends Data>(ref: AsyncItemReference<T>): ItemState<T>;
8
+ export declare function useData<T extends Data>(ref: AsyncItemReference<T> | undefined): ItemState<T> | undefined;
9
+ export declare function useData<T extends Data>(ref: AsyncQueryReference<T>): QueryState<T>;
10
+ export declare function useData<T extends Data>(ref: AsyncQueryReference<T> | undefined): QueryState<T> | undefined;
14
11
  /** Wrap components with `<DataProvider>` to allow the use of `useData()`. */
15
12
  export declare const DataProvider: ({ children }: {
16
13
  children: import("react").ReactNode;
17
14
  }) => import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>;
18
- export {};
package/react/useData.js CHANGED
@@ -2,20 +2,15 @@ import { AsyncItemReference } from "../db/ItemReference.js";
2
2
  import { ItemState } from "../db/ItemState.js";
3
3
  import { QueryState } from "../db/QueryState.js";
4
4
  import { setMapItem } from "../util/map.js";
5
- import { mapArray } from "../util/transform.js";
6
5
  import { createCache } from "./createCache.js";
7
6
  import { useState } from "./useState.js";
8
7
  /** Create a cache. */
9
8
  const { Cache, useCache } = createCache();
10
- export function useData(...refs) {
9
+ export function useData(ref) {
11
10
  const cache = useCache();
12
- const states = mapArray(refs, _getRefState, cache);
13
- return useState(...states);
14
- }
15
- /** Get the corresponding `ItemState` or `QueryState` instance from the cache for a given `Item` or `Query`. */
16
- function _getRefState(ref, cache) {
17
11
  const key = ref === null || ref === void 0 ? void 0 : ref.toString();
18
- return ref && key ? cache.get(key) || setMapItem(cache, key, ref instanceof AsyncItemReference ? new ItemState(ref) : new QueryState(ref)) : undefined;
12
+ const state = ref && key ? cache.get(key) || setMapItem(cache, key, ref instanceof AsyncItemReference ? new ItemState(ref) : new QueryState(ref)) : undefined;
13
+ return useState(state);
19
14
  }
20
15
  /** Wrap components with `<DataProvider>` to allow the use of `useData()`. */
21
16
  export const DataProvider = Cache;
@@ -1,14 +1,3 @@
1
1
  import type { AnyState } from "../state/State.js";
2
- import type { ImmutableArray } from "../util/array.js";
3
- import type { Nullish } from "../util/null.js";
4
- /**
5
- * Subscribe to one or more `State` instances.
6
- *
7
- * @param state `State` instance.
8
- * - Subscription is recreated every time the state instance changes.
9
- * - Memoise this value to persist the subscription for the lifetime of the component.
10
- * - If the value is a `State` instance
11
- */
12
2
  export declare function useState<T extends AnyState>(state: T): T;
13
- export declare function useState<T extends AnyState>(state?: Nullish<T>): Nullish<T>;
14
- export declare function useState<T extends ImmutableArray<Nullish<AnyState>>>(...states: T): T;
3
+ export declare function useState<T extends AnyState>(state?: T | undefined): T | undefined;