shelving 1.181.2 → 1.182.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.
Files changed (67) hide show
  1. package/api/cache/APICache.d.ts +1 -0
  2. package/api/cache/APICache.js +8 -6
  3. package/api/cache/EndpointCache.d.ts +5 -6
  4. package/api/cache/EndpointCache.js +7 -13
  5. package/api/endpoint/Endpoint.d.ts +1 -1
  6. package/api/index.d.ts +0 -1
  7. package/api/index.js +0 -1
  8. package/api/provider/APIProvider.d.ts +39 -4
  9. package/api/provider/APIProvider.js +76 -0
  10. package/api/provider/MockAPIProvider.d.ts +5 -5
  11. package/api/provider/MockAPIProvider.js +2 -2
  12. package/api/provider/ThroughAPIProvider.d.ts +6 -0
  13. package/api/provider/ThroughAPIProvider.js +15 -0
  14. package/bun/BunPostgreSQLProvider.d.ts +3 -2
  15. package/cloudflare/CloudflareD1Provider.d.ts +3 -2
  16. package/cloudflare/CloudflareKVProvider.d.ts +16 -18
  17. package/cloudflare/CloudflareKVProvider.js +11 -13
  18. package/db/cache/CollectionCache.d.ts +37 -0
  19. package/db/cache/CollectionCache.js +62 -0
  20. package/db/cache/DBCache.d.ts +38 -0
  21. package/db/cache/DBCache.js +59 -0
  22. package/db/collection/Collection.d.ts +12 -10
  23. package/db/index.d.ts +2 -0
  24. package/db/index.js +2 -0
  25. package/db/provider/CacheDBProvider.d.ts +18 -18
  26. package/db/provider/ChangesDBProvider.d.ts +11 -11
  27. package/db/provider/DBProvider.d.ts +17 -17
  28. package/db/provider/DBProvider.js +2 -2
  29. package/db/provider/DebugDBProvider.d.ts +15 -15
  30. package/db/provider/MemoryDBProvider.d.ts +28 -26
  31. package/db/provider/MemoryDBProvider.js +10 -5
  32. package/db/provider/MockDBProvider.d.ts +17 -17
  33. package/db/provider/PostgreSQLProvider.d.ts +4 -2
  34. package/db/provider/PostgreSQLProvider.js +1 -1
  35. package/db/provider/SQLProvider.d.ts +23 -23
  36. package/db/provider/SQLProvider.js +24 -23
  37. package/db/provider/SQLiteProvider.d.ts +6 -2
  38. package/db/provider/SQLiteProvider.js +14 -1
  39. package/db/provider/ThroughDBProvider.d.ts +19 -19
  40. package/db/provider/ValidationDBProvider.d.ts +14 -14
  41. package/db/provider/ValidationDBProvider.js +4 -8
  42. package/db/store/QueryStore.d.ts +3 -3
  43. package/firestore/client/FirestoreClientProvider.d.ts +21 -15
  44. package/firestore/client/FirestoreClientProvider.js +40 -37
  45. package/firestore/lite/FirestoreLiteProvider.d.ts +21 -15
  46. package/firestore/lite/FirestoreLiteProvider.js +38 -29
  47. package/firestore/server/FirestoreServerProvider.d.ts +21 -15
  48. package/firestore/server/FirestoreServerProvider.js +67 -71
  49. package/package.json +1 -1
  50. package/react/createAPIContext.d.ts +4 -2
  51. package/react/createAPIContext.js +22 -15
  52. package/react/createDBContext.d.ts +31 -0
  53. package/react/createDBContext.js +35 -0
  54. package/react/index.d.ts +1 -2
  55. package/react/index.js +1 -2
  56. package/util/http.d.ts +3 -2
  57. package/util/http.js +6 -6
  58. package/util/item.d.ts +4 -4
  59. package/util/query.d.ts +1 -4
  60. package/util/uri.d.ts +9 -4
  61. package/util/uri.js +4 -0
  62. package/api/provider/ClientAPIProvider.d.ts +0 -37
  63. package/api/provider/ClientAPIProvider.js +0 -51
  64. package/react/createCacheContext.d.ts +0 -13
  65. package/react/createCacheContext.js +0 -22
  66. package/react/createDataContext.d.ts +0 -26
  67. package/react/createDataContext.js +0 -31
@@ -2,8 +2,8 @@ import type { Firestore } from "firebase/firestore/lite";
2
2
  import type { Collection } from "../../db/collection/Collection.js";
3
3
  import { DBProvider } from "../../db/provider/DBProvider.js";
4
4
  import type { Data } from "../../util/data.js";
5
- import type { Items, OptionalItem } from "../../util/item.js";
6
- import type { ItemQuery } from "../../util/query.js";
5
+ import type { Item, Items, OptionalItem } from "../../util/item.js";
6
+ import { type Query } from "../../util/query.js";
7
7
  import type { Updates } from "../../util/update.js";
8
8
  /**
9
9
  * Firestore Lite client database provider.
@@ -11,19 +11,25 @@ import type { Updates } from "../../util/update.js";
11
11
  * - Does not support offline mode.
12
12
  * - Does not support realtime subscriptions.
13
13
  */
14
- export declare class FirestoreLiteProvider extends DBProvider<string> {
14
+ export declare class FirestoreLiteProvider<I extends string = string, T extends Data = Data> extends DBProvider<I, T> {
15
15
  private readonly _firestore;
16
16
  constructor(firestore: Firestore);
17
- getItem<T extends Data>({ name }: Collection<string, string, T>, id: string): Promise<OptionalItem<string, T>>;
18
- getItemSequence<T extends Data>(_c: Collection<string, string, T>, _id: string): AsyncIterableIterator<OptionalItem<string, T>>;
19
- addItem<T extends Data>({ name }: Collection<string, string, T>, data: T): Promise<string>;
20
- setItem<T extends Data>({ name }: Collection<string, string, T>, id: string, data: T): Promise<void>;
21
- updateItem<T extends Data>({ name }: Collection<string, string, T>, id: string, updates: Updates<T>): Promise<void>;
22
- deleteItem<T extends Data>({ name }: Collection<string, string, T>, id: string): Promise<void>;
23
- countQuery<T extends Data>({ name }: Collection<string, string, T>, q?: ItemQuery<string, T>): Promise<number>;
24
- getQuery<T extends Data>({ name }: Collection<string, string, T>, q?: ItemQuery<string, T>): Promise<Items<string, T>>;
25
- getQuerySequence<T extends Data>(_c: Collection<string, string, T>, _q?: ItemQuery<string, T>): AsyncIterableIterator<Items<string, T>>;
26
- setQuery<T extends Data>({ name }: Collection<string, string, T>, q: ItemQuery<string, T>, data: T): Promise<void>;
27
- updateQuery<T extends Data>({ name }: Collection<string, string, T>, q: ItemQuery<string, T>, updates: Updates<T>): Promise<void>;
28
- deleteQuery<T extends Data>({ name }: Collection<string, string, T>, q: ItemQuery<string, T>): Promise<void>;
17
+ /** Get a Firestore CollectionReference for a given collection. */
18
+ private _collection;
19
+ /** Get a Firestore DocumentReference for a given document. */
20
+ private _doc;
21
+ /** Get a Firestore QueryReference for a given query. */
22
+ private _query;
23
+ getItem<II extends I, TT extends T>(c: Collection<string, II, TT>, id: II): Promise<OptionalItem<II, TT>>;
24
+ getItemSequence<II extends I, TT extends T>(_c: Collection<string, II, TT>, _id: II): AsyncIterable<OptionalItem<II, TT>>;
25
+ addItem<II extends I, TT extends T>(c: Collection<string, II, TT>, data: TT): Promise<II>;
26
+ setItem<II extends I, TT extends T>(c: Collection<string, II, TT>, id: II, data: TT): Promise<void>;
27
+ updateItem<II extends I, TT extends T>(c: Collection<string, II, TT>, id: II, updates: Updates<Item<II, TT>>): Promise<void>;
28
+ deleteItem<II extends I, TT extends T>(c: Collection<string, II, TT>, id: II): Promise<void>;
29
+ countQuery<II extends I, TT extends T>(c: Collection<string, II, TT>, q?: Query<Item<II, TT>>): Promise<number>;
30
+ getQuery<II extends I, TT extends T>(c: Collection<string, II, TT>, q?: Query<Item<II, TT>>): Promise<Items<II, TT>>;
31
+ getQuerySequence<II extends I, TT extends T>(_c: Collection<string, II, TT>, _q?: Query<Item<II, TT>>): AsyncIterable<Items<II, TT>>;
32
+ setQuery<II extends I, TT extends T>(c: Collection<string, II, TT>, q: Query<Item<II, TT>>, data: TT): Promise<void>;
33
+ updateQuery<II extends I, TT extends T>(c: Collection<string, II, TT>, q: Query<Item<II, TT>>, updates: Updates<TT>): Promise<void>;
34
+ deleteQuery<II extends I, TT extends T>(c: Collection<string, II, TT>, q: Query<Item<II, TT>>): Promise<void>;
29
35
  }
@@ -21,10 +21,6 @@ const OPERATORS = {
21
21
  lt: "<",
22
22
  lte: "<=",
23
23
  };
24
- /** Create a corresponding `QueryReference` from a Query. */
25
- function _getQuery(firestore, c, q) {
26
- return q ? query(collection(firestore, c), ..._getConstraints(q)) : collection(firestore, c);
27
- }
28
24
  function* _getConstraints(q) {
29
25
  for (const { key, direction } of getQueryOrders(q)) {
30
26
  const k = joinDataKey(key);
@@ -38,14 +34,16 @@ function* _getConstraints(q) {
38
34
  if (typeof l === "number")
39
35
  yield limit(l);
40
36
  }
37
+ function _getItems(snapshot) {
38
+ return snapshot.docs.map(s => _getItem(s));
39
+ }
41
40
  function _getItem(snapshot) {
42
- const data = snapshot.data();
43
- return getItem(snapshot.id, data);
41
+ return getItem(snapshot.id, snapshot.data()); // `as II` needed: Firestore snapshot.id is always string, not II.
44
42
  }
45
43
  function _getOptionalItem(snapshot) {
46
44
  const data = snapshot.data();
47
45
  if (data)
48
- return getItem(snapshot.id, data);
46
+ return getItem(snapshot.id, data); // `as II` needed: Firestore snapshot.id is always string, not II.
49
47
  }
50
48
  /** Convert `Updates` object into corresponding Firestore `FieldValue` instances. */
51
49
  function _getFieldValues(updates) {
@@ -75,48 +73,59 @@ export class FirestoreLiteProvider extends DBProvider {
75
73
  super();
76
74
  this._firestore = firestore;
77
75
  }
78
- async getItem({ name }, id) {
79
- const snapshot = await getDoc(doc(this._firestore, name, id));
76
+ /** Get a Firestore CollectionReference for a given collection. */
77
+ _collection({ name }) {
78
+ return collection(this._firestore, name);
79
+ }
80
+ /** Get a Firestore DocumentReference for a given document. */
81
+ _doc(c, id) {
82
+ return doc(this._collection(c), id);
83
+ }
84
+ /** Get a Firestore QueryReference for a given query. */
85
+ _query(c, q) {
86
+ return q ? query(this._collection(c), ..._getConstraints(q)) : this._collection(c);
87
+ }
88
+ async getItem(c, id) {
89
+ const snapshot = await getDoc(this._doc(c, id));
80
90
  return _getOptionalItem(snapshot);
81
91
  }
82
92
  getItemSequence(_c, _id) {
83
93
  throw new UnimplementedError("FirestoreLiteProvider does not support realtime subscriptions");
84
94
  }
85
- async addItem({ name }, data) {
86
- const reference = await addDoc(collection(this._firestore, name), data);
87
- return reference.id;
95
+ async addItem(c, data) {
96
+ const reference = await addDoc(this._collection(c), data);
97
+ return reference.id; // `as II` needed: Firestore returns string, not II.
88
98
  }
89
- async setItem({ name }, id, data) {
90
- await setDoc(doc(this._firestore, name, id), data);
99
+ async setItem(c, id, data) {
100
+ await setDoc(this._doc(c, id), data);
91
101
  }
92
- async updateItem({ name }, id, updates) {
93
- await updateDoc(doc(this._firestore, name, id), _getFieldValues(updates));
102
+ async updateItem(c, id, updates) {
103
+ await updateDoc(this._doc(c, id), _getFieldValues(updates));
94
104
  }
95
- async deleteItem({ name }, id) {
96
- await deleteDoc(doc(this._firestore, name, id));
105
+ async deleteItem(c, id) {
106
+ await deleteDoc(this._doc(c, id));
97
107
  }
98
- async countQuery({ name }, q) {
99
- const snapshot = await getCount(_getQuery(this._firestore, name, q));
108
+ async countQuery(c, q) {
109
+ const snapshot = await getCount(this._query(c, q));
100
110
  return snapshot.data().count;
101
111
  }
102
- async getQuery({ name }, q) {
103
- const snapshot = await getDocs(_getQuery(this._firestore, name, q));
104
- return snapshot.docs.map(_getItem);
112
+ async getQuery(c, q) {
113
+ return _getItems(await getDocs(this._query(c, q)));
105
114
  }
106
115
  getQuerySequence(_c, _q) {
107
116
  throw new UnimplementedError("FirestoreLiteProvider does not support realtime subscriptions");
108
117
  }
109
- async setQuery({ name }, q, data) {
110
- const snapshot = await getDocs(_getQuery(this._firestore, name, q));
118
+ async setQuery(c, q, data) {
119
+ const snapshot = await getDocs(this._query(c, q));
111
120
  await Promise.all(snapshot.docs.map(s => setDoc(s.ref, data)));
112
121
  }
113
- async updateQuery({ name }, q, updates) {
114
- const snapshot = await getDocs(_getQuery(this._firestore, name, q));
122
+ async updateQuery(c, q, updates) {
123
+ const snapshot = await getDocs(this._query(c, q));
115
124
  const fieldValues = _getFieldValues(updates);
116
125
  await Promise.all(snapshot.docs.map(s => updateDoc(s.ref, fieldValues)));
117
126
  }
118
- async deleteQuery({ name }, q) {
119
- const snapshot = await getDocs(_getQuery(this._firestore, name, q));
127
+ async deleteQuery(c, q) {
128
+ const snapshot = await getDocs(this._query(c, q));
120
129
  await Promise.all(snapshot.docs.map(s => deleteDoc(s.ref)));
121
130
  }
122
131
  }
@@ -2,26 +2,32 @@ import { Firestore } from "@google-cloud/firestore";
2
2
  import type { Collection } from "../../db/collection/Collection.js";
3
3
  import { DBProvider } from "../../db/provider/DBProvider.js";
4
4
  import type { Data } from "../../util/data.js";
5
- import type { Items, OptionalItem } from "../../util/item.js";
6
- import type { ItemQuery } from "../../util/query.js";
5
+ import type { Item, Items, OptionalItem } from "../../util/item.js";
6
+ import { type Query } from "../../util/query.js";
7
7
  import type { Updates } from "../../util/update.js";
8
8
  /**
9
9
  * Firestore server database provider.
10
10
  * - Works with the Firebase Admin SDK for Node.JS
11
11
  */
12
- export declare class FirestoreServerProvider extends DBProvider<string> {
12
+ export declare class FirestoreServerProvider<I extends string = string, T extends Data = Data> extends DBProvider<I, T> {
13
13
  private readonly _firestore;
14
14
  constructor(firestore?: Firestore);
15
- getItem<T extends Data>({ name }: Collection<string, string, T>, id: string): Promise<OptionalItem<string, T>>;
16
- getItemSequence<T extends Data>({ name }: Collection<string, string, T>, id: string): AsyncIterable<OptionalItem<string, T>>;
17
- addItem<T extends Data>({ name }: Collection<string, string, T>, data: T): Promise<string>;
18
- setItem<T extends Data>({ name }: Collection<string, string, T>, id: string, data: T): Promise<void>;
19
- updateItem<T extends Data>({ name }: Collection<string, string, T>, id: string, updates: Updates<T>): Promise<void>;
20
- deleteItem<T extends Data>({ name }: Collection<string, string, T>, id: string): Promise<void>;
21
- countQuery<T extends Data>({ name }: Collection<string, string, T>, q?: ItemQuery<string, T>): Promise<number>;
22
- getQuery<T extends Data>({ name }: Collection<string, string, T>, q?: ItemQuery<string, T>): Promise<Items<string, T>>;
23
- getQuerySequence<T extends Data>({ name }: Collection<string, string, T>, q?: ItemQuery<string, T>): AsyncIterable<Items<string, T>>;
24
- setQuery<T extends Data>({ name }: Collection<string, string, T>, q: ItemQuery<string, T>, data: T): Promise<void>;
25
- updateQuery<T extends Data>({ name }: Collection<string, string, T>, q: ItemQuery<string, T>, updates: Updates<T>): Promise<void>;
26
- deleteQuery<T extends Data>({ name }: Collection<string, string, T>, q: ItemQuery<string, T>): Promise<void>;
15
+ /** Create a corresponding `FirestoreCollection` reference from a collection. */
16
+ private _getCollection;
17
+ /** Create a corresponding `FirestoreQuery` reference from a collection and query. */
18
+ private _getQuery;
19
+ /** Perform a bulk update on a set of documents using a `BulkWriter` */
20
+ private _bulkWrite;
21
+ getItem<II extends I, TT extends T>(collection: Collection<string, II, TT>, id: II): Promise<OptionalItem<II, TT>>;
22
+ getItemSequence<II extends I, TT extends T>(c: Collection<string, II, TT>, id: II): AsyncIterable<OptionalItem<II, TT>>;
23
+ addItem<II extends I, TT extends T>(c: Collection<string, II, TT>, data: TT): Promise<II>;
24
+ setItem<II extends I, TT extends T>(c: Collection<string, II, TT>, id: II, data: TT): Promise<void>;
25
+ updateItem<II extends I, TT extends T>(c: Collection<string, II, TT>, id: II, updates: Updates<Item<II, TT>>): Promise<void>;
26
+ deleteItem<II extends I, TT extends T>(c: Collection<string, II, TT>, id: II): Promise<void>;
27
+ countQuery<II extends I, TT extends T>(c: Collection<string, II, TT>, q?: Query<Item<II, TT>>): Promise<number>;
28
+ getQuery<II extends I, TT extends T>(c: Collection<string, II, TT>, q?: Query<Item<II, TT>>): Promise<Items<II, TT>>;
29
+ getQuerySequence<II extends I, TT extends T>(c: Collection<string, II, TT>, q?: Query<Item<II, TT>>): AsyncIterable<Items<II, TT>>;
30
+ setQuery<II extends I, TT extends T>(c: Collection<string, II, TT>, q: Query<Item<II, TT>>, data: TT): Promise<void>;
31
+ updateQuery<II extends I, TT extends T>(c: Collection<string, II, TT>, q: Query<Item<II, TT>>, updates: Updates<TT>): Promise<void>;
32
+ deleteQuery<II extends I, TT extends T>(c: Collection<string, II, TT>, q: Query<Item<II, TT>>): Promise<void>;
27
33
  }
@@ -9,6 +9,7 @@ import { mapItems } from "../../util/transform.js";
9
9
  import { getUpdates } from "../../util/update.js";
10
10
  // Constants.
11
11
  const ID = FieldPath.documentId();
12
+ const BATCH_SIZE = 1000;
12
13
  // Map `Filter.types` to `WhereFilterOp`
13
14
  const OPERATORS = {
14
15
  is: "==",
@@ -21,38 +22,16 @@ const OPERATORS = {
21
22
  lt: "<",
22
23
  lte: "<=",
23
24
  };
24
- function _getCollection(firestore, c) {
25
- return firestore.collection(c);
26
- }
27
- /** Create a corresponding `QueryReference` from a Query. */
28
- function _getQuery(firestore, c, q) {
29
- let ref = _getCollection(firestore, c);
30
- if (q) {
31
- for (const { key, direction } of getQueryOrders(q)) {
32
- const k = joinDataKey(key);
33
- ref = ref.orderBy(k === "id" ? ID : k, direction);
34
- }
35
- for (const { key, operator, value } of getQueryFilters(q)) {
36
- const k = joinDataKey(key);
37
- ref = ref.where(k === "id" ? ID : k, OPERATORS[operator], value);
38
- }
39
- const l = getQueryLimit(q);
40
- if (typeof l === "number")
41
- ref = ref.limit(l);
42
- }
43
- return ref;
44
- }
45
- function _getItemArray(snapshot) {
46
- return snapshot.docs.map(_getItem);
25
+ function _getItems(snapshot) {
26
+ return snapshot.docs.map(s => _getItem(s));
47
27
  }
48
28
  function _getItem(snapshot) {
49
- const data = snapshot.data();
50
- return getItem(snapshot.id, data);
29
+ return getItem(snapshot.id, snapshot.data()); // `as II` needed: Firestore snapshot.id is always string, not II.
51
30
  }
52
31
  function _getOptionalItem(snapshot) {
53
32
  const data = snapshot.data();
54
33
  if (data)
55
- return getItem(snapshot.id, data);
34
+ return getItem(snapshot.id, data); // `as II` needed: Firestore snapshot.id is always string, not II.
56
35
  }
57
36
  /** Convert `Update` instances into corresponding Firestore `FieldValue` instances. */
58
37
  function _getFieldValues(updates) {
@@ -80,63 +59,80 @@ export class FirestoreServerProvider extends DBProvider {
80
59
  super();
81
60
  this._firestore = firestore;
82
61
  }
83
- async getItem({ name }, id) {
84
- return _getOptionalItem(await _getCollection(this._firestore, name).doc(id).get());
62
+ /** Create a corresponding `FirestoreCollection` reference from a collection. */
63
+ _getCollection(collection) {
64
+ return this._firestore.collection(collection.name);
85
65
  }
86
- getItemSequence({ name }, id) {
87
- const ref = _getCollection(this._firestore, name).doc(id);
88
- return new LazyDeferredSequence(sequence => ref.onSnapshot(snapshot => sequence.resolve(_getOptionalItem(snapshot)), //
89
- //
90
- reason => sequence.reject(reason)));
66
+ /** Create a corresponding `FirestoreQuery` reference from a collection and query. */
67
+ _getQuery(c, q) {
68
+ let ref = this._getCollection(c);
69
+ if (q) {
70
+ for (const { key, direction } of getQueryOrders(q)) {
71
+ const k = joinDataKey(key);
72
+ ref = ref.orderBy(k === "id" ? ID : k, direction);
73
+ }
74
+ for (const { key, operator, value } of getQueryFilters(q)) {
75
+ const k = joinDataKey(key);
76
+ ref = ref.where(k === "id" ? ID : k, OPERATORS[operator], value);
77
+ }
78
+ const l = getQueryLimit(q);
79
+ if (typeof l === "number")
80
+ ref = ref.limit(l);
81
+ }
82
+ return ref;
83
+ }
84
+ /** Perform a bulk update on a set of documents using a `BulkWriter` */
85
+ async _bulkWrite(c, q, callback) {
86
+ const writer = this._firestore.bulkWriter();
87
+ const ref = this._getQuery(c, q).limit(BATCH_SIZE).select(); // `select()` turns the query into a field mask query (with no field masks) which saves data transfer and memory.
88
+ let current = ref;
89
+ while (current) {
90
+ const { docs, size } = await current.get();
91
+ for (const s of docs)
92
+ callback(writer, s);
93
+ current = size >= BATCH_SIZE && ref.startAfter(docs.pop()).select();
94
+ void writer.flush();
95
+ }
96
+ await writer.close();
97
+ }
98
+ async getItem(collection, id) {
99
+ return _getOptionalItem(await this._getCollection(collection).doc(id).get());
91
100
  }
92
- async addItem({ name }, data) {
93
- return (await _getCollection(this._firestore, name).add(data)).id;
101
+ getItemSequence(c, id) {
102
+ const ref = this._getCollection(c).doc(id);
103
+ return new LazyDeferredSequence(sequence => ref.onSnapshot(snapshot => sequence.resolve(_getOptionalItem(snapshot)), reason => sequence.reject(reason)));
94
104
  }
95
- async setItem({ name }, id, data) {
96
- await _getCollection(this._firestore, name).doc(id).set(data);
105
+ async addItem(c, data) {
106
+ return (await this._getCollection(c).add(data)).id; // `as II` needed: Firestore returns string, not II.
97
107
  }
98
- async updateItem({ name }, id, updates) {
99
- await _getCollection(this._firestore, name).doc(id).update(_getFieldValues(updates));
108
+ async setItem(c, id, data) {
109
+ await this._getCollection(c).doc(id).set(data);
100
110
  }
101
- async deleteItem({ name }, id) {
102
- await _getCollection(this._firestore, name).doc(id).delete();
111
+ async updateItem(c, id, updates) {
112
+ await this._getCollection(c).doc(id).update(_getFieldValues(updates));
103
113
  }
104
- async countQuery({ name }, q) {
105
- const snapshot = await _getQuery(this._firestore, name, q).count().get();
114
+ async deleteItem(c, id) {
115
+ await this._getCollection(c).doc(id).delete();
116
+ }
117
+ async countQuery(c, q) {
118
+ const snapshot = await this._getQuery(c, q).count().get();
106
119
  return snapshot.data().count;
107
120
  }
108
- async getQuery({ name }, q) {
109
- return _getItemArray(await _getQuery(this._firestore, name, q).get());
121
+ async getQuery(c, q) {
122
+ return _getItems(await this._getQuery(c, q).get());
110
123
  }
111
- getQuerySequence({ name }, q) {
112
- const ref = _getQuery(this._firestore, name, q);
113
- return new LazyDeferredSequence(sequence => ref.onSnapshot(snapshot => sequence.resolve(_getItemArray(snapshot)), //
114
- //
115
- reason => sequence.reject(reason)));
124
+ getQuerySequence(c, q) {
125
+ const ref = this._getQuery(c, q);
126
+ return new LazyDeferredSequence(sequence => ref.onSnapshot(snapshot => sequence.resolve(_getItems(snapshot)), reason => sequence.reject(reason)));
116
127
  }
117
- async setQuery({ name }, q, data) {
118
- return await bulkWrite(this._firestore, name, q, (w, s) => void w.set(s.ref, data));
128
+ async setQuery(c, q, data) {
129
+ return await this._bulkWrite(c, q, (w, s) => void w.set(s.ref, data));
119
130
  }
120
- async updateQuery({ name }, q, updates) {
131
+ async updateQuery(c, q, updates) {
121
132
  const fieldValues = _getFieldValues(updates);
122
- return await bulkWrite(this._firestore, name, q, (w, s) => void w.update(s.ref, fieldValues));
133
+ return await this._bulkWrite(c, q, (w, s) => void w.update(s.ref, fieldValues));
123
134
  }
124
- async deleteQuery({ name }, q) {
125
- return await bulkWrite(this._firestore, name, q, (w, s) => void w.delete(s.ref));
135
+ async deleteQuery(c, q) {
136
+ return await this._bulkWrite(c, q, (w, s) => void w.delete(s.ref));
126
137
  }
127
138
  }
128
- /** Perform a bulk update on a set of documents using a `BulkWriter` */
129
- async function bulkWrite(firestore, c, q, callback) {
130
- const writer = firestore.bulkWriter();
131
- const ref = _getQuery(firestore, c, q).limit(BATCH_SIZE).select(); // `select()` turns the query into a field mask query (with no field masks) which saves data transfer and memory.
132
- let current = ref;
133
- while (current) {
134
- const { docs, size } = await current.get();
135
- for (const s of docs)
136
- callback(writer, s);
137
- current = size >= BATCH_SIZE && ref.startAfter(docs.pop()).select();
138
- void writer.flush();
139
- }
140
- await writer.close();
141
- }
142
- const BATCH_SIZE = 1000;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shelving",
3
- "version": "1.181.2",
3
+ "version": "1.182.0",
4
4
  "author": "Dave Houlbrooke <dave@shax.com>",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,7 +1,7 @@
1
- import type { ReactElement, ReactNode } from "react";
1
+ import { type ReactElement, type ReactNode } from "react";
2
2
  import type { Endpoint } from "../api/endpoint/Endpoint.js";
3
3
  import type { APIProvider } from "../api/provider/APIProvider.js";
4
- import { EndpointStore } from "../api/store/EndpointStore.js";
4
+ import type { EndpointStore } from "../api/store/EndpointStore.js";
5
5
  import type { Nullish } from "../util/null.js";
6
6
  export interface APIContext {
7
7
  /** Get an `EndpointStore` for the specified endpoint/payload in the current `APIProvider` context. */
@@ -15,5 +15,7 @@ export interface APIContext {
15
15
  * Create an API context.
16
16
  * - Allows React elements to call `useAPI()` to access endpoint stores in an API provider.
17
17
  * - Each mounted `APIContext` gets its own in-memory store cache.
18
+ *
19
+ * @todo Use and integreate our `EndpointCache` functionality and use it in this.
18
20
  */
19
21
  export declare function createAPIContext(provider: APIProvider): APIContext;
@@ -1,25 +1,32 @@
1
- import { EndpointStore } from "../api/store/EndpointStore.js";
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createContext, use } from "react";
3
+ import { APICache } from "../api/index.js";
4
+ import { RequiredError } from "../error/RequiredError.js";
2
5
  import { MINUTE } from "../util/constants.js";
3
- import { setMapItem } from "../util/map.js";
4
- import { createCacheContext } from "./createCacheContext.js";
6
+ import { useInstance } from "./useInstance.js";
5
7
  import { useStore } from "./useStore.js";
6
8
  const DEFAULT_MAX_AGE = 5 * MINUTE;
7
9
  /**
8
10
  * Create an API context.
9
11
  * - Allows React elements to call `useAPI()` to access endpoint stores in an API provider.
10
12
  * - Each mounted `APIContext` gets its own in-memory store cache.
13
+ *
14
+ * @todo Use and integreate our `EndpointCache` functionality and use it in this.
11
15
  */
12
16
  export function createAPIContext(provider) {
13
- const { CacheContext, useCache } = createCacheContext();
14
- return {
15
- useAPI: (endpoint, payload, maxAge = DEFAULT_MAX_AGE) => {
16
- const cache = useCache();
17
- const key = endpoint && `${endpoint.toString()}?${JSON.stringify(payload)}`;
18
- const store = useStore(key && endpoint ? cache.get(key) || setMapItem(cache, key, new EndpointStore(endpoint, payload, provider)) : undefined);
19
- if (store)
20
- store.refreshStale(maxAge);
21
- return store;
22
- },
23
- APIContext: CacheContext,
24
- };
17
+ const CacheContext = createContext(undefined);
18
+ function useAPI(endpoint, payload, maxAge = DEFAULT_MAX_AGE) {
19
+ const cache = use(CacheContext);
20
+ if (!cache)
21
+ throw new RequiredError(`useAPI() can only be used inside <APIContext>`, { caller: useAPI });
22
+ const store = endpoint ? cache.get(endpoint).get(payload) : undefined;
23
+ if (store)
24
+ store.refreshStale(maxAge);
25
+ return useStore(store);
26
+ }
27
+ function APIContext({ children }) {
28
+ const cache = useInstance(APICache, provider);
29
+ return _jsx(CacheContext, { value: cache, children: children });
30
+ }
31
+ return { useAPI, APIContext };
25
32
  }
@@ -0,0 +1,31 @@
1
+ import { type ReactElement, type ReactNode } from "react";
2
+ import type { Collection } from "../db/collection/Collection.js";
3
+ import type { DBProvider } from "../db/provider/DBProvider.js";
4
+ import type { ItemStore } from "../db/store/ItemStore.js";
5
+ import type { QueryStore } from "../db/store/QueryStore.js";
6
+ import type { Data } from "../util/data.js";
7
+ import type { Identifier, Item } from "../util/item.js";
8
+ import type { Nullish } from "../util/null.js";
9
+ import type { Query } from "../util/query.js";
10
+ export interface DBContext<I extends Identifier, T extends Data> {
11
+ /** Get an `ItemStore` for the specified collection item in the current `DataProvider` context and subscribe to any changes in it. */
12
+ useItem<II extends I, TT extends T>(collection: Nullish<Collection<string, II, TT>>, //
13
+ id: Nullish<II>): ItemStore<II, TT> | undefined;
14
+ useItem<II extends I, TT extends T>(collection: Collection<string, II, TT>, //
15
+ id: II): ItemStore<II, TT>;
16
+ /** Get an `QueryStore` for the specified collection query in the current `DataProvider` context and subscribe to any changes in it. */
17
+ useQuery<II extends I, TT extends T>(collection: Nullish<Collection<string, II, TT>>, //
18
+ query: Nullish<Query<Item<II, TT>>>): QueryStore<II, TT> | undefined;
19
+ useQuery<II extends I, TT extends T>(collection: Collection<string, II, TT>, //
20
+ query: Query<Item<II, TT>>): QueryStore<II, TT>;
21
+ /** The `<DataContext>` wrapper to give your React components access to this data. */
22
+ readonly DBContext: ({ children }: {
23
+ children: ReactNode;
24
+ }) => ReactElement;
25
+ }
26
+ /**
27
+ * Create a data context
28
+ * - Allows React elements to call `useItem()` and `useQuery()` to access items/queries in a database provider.
29
+ * - If the database has a `CacheDBProvider` in its chain then in-memory data will be used in the returned stores.
30
+ */
31
+ export declare function createDBContext<I extends Identifier, T extends Data>(provider: DBProvider<I, T>): DBContext<I, T>;
@@ -0,0 +1,35 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createContext, use } from "react";
3
+ import { DBCache } from "../db/cache/DBCache.js";
4
+ import { RequiredError } from "../error/RequiredError.js";
5
+ import { useInstance } from "./useInstance.js";
6
+ import { useStore } from "./useStore.js";
7
+ /**
8
+ * Create a data context
9
+ * - Allows React elements to call `useItem()` and `useQuery()` to access items/queries in a database provider.
10
+ * - If the database has a `CacheDBProvider` in its chain then in-memory data will be used in the returned stores.
11
+ */
12
+ export function createDBContext(provider) {
13
+ const CacheContext = createContext(undefined);
14
+ function useItem(collection, //
15
+ id) {
16
+ const cache = use(CacheContext);
17
+ if (!cache)
18
+ throw new RequiredError("useItem() can only be used inside <DBContext>", { caller: useItem });
19
+ const store = collection && id ? cache.getItem(collection, id) : undefined;
20
+ return useStore(store);
21
+ }
22
+ function useQuery(collection, //
23
+ query) {
24
+ const cache = use(CacheContext);
25
+ if (!cache)
26
+ throw new RequiredError("useQuery() can only be used inside <DBContext>", { caller: useQuery });
27
+ const store = collection && query ? cache.getQuery(collection, query) : undefined;
28
+ return useStore(store);
29
+ }
30
+ function DBContext({ children }) {
31
+ const cache = useInstance(DBCache, provider);
32
+ return _jsx(CacheContext, { value: cache, children: children });
33
+ }
34
+ return { useItem, useQuery, DBContext };
35
+ }
package/react/index.d.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  export * from "./createAPIContext.js";
2
- export * from "./createCacheContext.js";
3
- export * from "./createDataContext.js";
2
+ export * from "./createDBContext.js";
4
3
  export * from "./useInstance.js";
5
4
  export * from "./useLazy.js";
6
5
  export * from "./useMap.js";
package/react/index.js CHANGED
@@ -1,6 +1,5 @@
1
1
  export * from "./createAPIContext.js";
2
- export * from "./createCacheContext.js";
3
- export * from "./createDataContext.js";
2
+ export * from "./createDBContext.js";
4
3
  export * from "./useInstance.js";
5
4
  export * from "./useLazy.js";
6
5
  export * from "./useMap.js";
package/util/http.d.ts CHANGED
@@ -3,6 +3,7 @@ import { ResponseError } from "../error/ResponseError.js";
3
3
  import { type Data } from "./data.js";
4
4
  import type { ImmutableDictionary } from "./dictionary.js";
5
5
  import type { AnyCaller, Arguments } from "./function.js";
6
+ import { type Nullish } from "./null.js";
6
7
  import { type PossibleURL } from "./url.js";
7
8
  /** A handler function takes a `Request` and optional extra arguments and returns a `Response` (possibly asynchronously). */
8
9
  export type RequestHandler<A extends Arguments = []> = (request: Request, ...args: A) => Response | Promise<Response>;
@@ -95,5 +96,5 @@ export declare function mergeRequestOptions({ headers: aHeaders, ...a }?: Reques
95
96
  * @throws {RequiredError} if this is a `HEAD` or `GET` request but `body` is not a data object.
96
97
  */
97
98
  export declare function getRequest(method: RequestMethod, url: PossibleURL, payload: unknown, options?: RequestOptions, caller?: AnyCaller): Request;
98
- /** Assert that the payload for a HEAD or GET method is a data object. */
99
- export declare function assertHeadMethodPayload(payload: unknown, method: RequestHeadMethod, caller?: AnyCaller): asserts payload is Data;
99
+ /** Assert that the payload for a HEAD or GET method is a data object, null, or undefined. */
100
+ export declare function assertHeadMethodPayload(payload: unknown, method: RequestHeadMethod, caller?: AnyCaller): asserts payload is Nullish<Data>;
package/util/http.js CHANGED
@@ -155,14 +155,14 @@ export function mergeRequestOptions({ headers: aHeaders, ...a } = {}, { headers:
155
155
  */
156
156
  export function getRequest(method, url, payload, options = {}, caller = getRequest) {
157
157
  url = requireURL(url, undefined, caller);
158
+ // `null` or `undefined` payloads send no body.
159
+ if (isNullish(payload))
160
+ return new Request(url, { ...options, method, body: null });
158
161
  // HEAD or GET have no body (but payload can only be data object).
159
162
  if (isArrayItem(HTTP_HEAD_METHODS, method)) {
160
163
  assertHeadMethodPayload(payload, method, caller);
161
164
  return new Request(withURIParams(url, payload), { ...options, method, body: null });
162
165
  }
163
- // `null` or `undefined` payloads send no body.
164
- if (isNullish(payload))
165
- return new Request(url, { ...options, method, body: null });
166
166
  // `FormData` instances in body pass through unaltered and will set their own `Content-Type` with complex boundary information
167
167
  if (payload instanceof FormData)
168
168
  return new Request(url, { ...options, method, body: payload });
@@ -172,8 +172,8 @@ export function getRequest(method, url, payload, options = {}, caller = getReque
172
172
  // JSON is the default.
173
173
  return new Request(url, { ...mergeRequestOptions(REQUEST_JSON_OPTIONS, options), method, body: JSON.stringify(payload) });
174
174
  }
175
- /** Assert that the payload for a HEAD or GET method is a data object. */
175
+ /** Assert that the payload for a HEAD or GET method is a data object, null, or undefined. */
176
176
  export function assertHeadMethodPayload(payload, method, caller = assertHeadMethodPayload) {
177
- if (!isData(payload))
178
- throw new RequiredError(`Payload for ${method} request must be data object`, { received: payload, caller });
177
+ if (!isData(payload) && !isNullish(payload))
178
+ throw new RequiredError(`Payload for ${method} request must be data object, null, or undefined`, { received: payload, caller });
179
179
  }
package/util/item.d.ts CHANGED
@@ -3,11 +3,13 @@ import type { Data } from "./data.js";
3
3
  /** Allowed types for the "id" property (identifier) for an item. */
4
4
  export type Identifier = string | number;
5
5
  /** An item object is a data object that includes an "id" identifier property that is either a string or number. */
6
- export type Item<I extends Identifier, T extends Data> = {
6
+ export type Item<I extends Identifier = Identifier, T extends Data = Data> = {
7
7
  id: I;
8
8
  } & T;
9
9
  /** Entity or `undefined` to indicate the item doesn't exist. */
10
- export type OptionalItem<I extends Identifier, T extends Data> = Item<I, T> | undefined;
10
+ export type OptionalItem<I extends Identifier = Identifier, T extends Data = Data> = Item<I, T> | undefined;
11
+ /** An array of item data. */
12
+ export type Items<I extends Identifier = Identifier, T extends Data = Data> = ImmutableArray<Item<I, T>>;
11
13
  /** Get the identifier from an item object. */
12
14
  export declare function getIdentifier<I extends Identifier, T extends Data>({ id }: Item<I, T>): I;
13
15
  /** Get the identifiers from an iterable set item objects. */
@@ -16,5 +18,3 @@ export declare function getIdentifiers<I extends Identifier, T extends Data>(ent
16
18
  export declare function hasIdentifier<I extends Identifier, T extends Data>(item: T | Item<I, T>, id: I): item is Item<I, T>;
17
19
  /** Merge an ID into a set of data to make an `ItemData` */
18
20
  export declare function getItem<I extends Identifier, T extends Data>(id: I, data: T | Item<I, T>): Item<I, T>;
19
- /** An array of item data. */
20
- export type Items<I extends Identifier, T extends Data> = ImmutableArray<Item<I, T>>;