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.
- package/api/cache/APICache.d.ts +1 -0
- package/api/cache/APICache.js +8 -6
- package/api/cache/EndpointCache.d.ts +5 -6
- package/api/cache/EndpointCache.js +7 -13
- package/api/endpoint/Endpoint.d.ts +1 -1
- package/api/index.d.ts +0 -1
- package/api/index.js +0 -1
- package/api/provider/APIProvider.d.ts +39 -4
- package/api/provider/APIProvider.js +76 -0
- package/api/provider/MockAPIProvider.d.ts +5 -5
- package/api/provider/MockAPIProvider.js +2 -2
- package/api/provider/ThroughAPIProvider.d.ts +6 -0
- package/api/provider/ThroughAPIProvider.js +15 -0
- package/bun/BunPostgreSQLProvider.d.ts +3 -2
- package/cloudflare/CloudflareD1Provider.d.ts +3 -2
- package/cloudflare/CloudflareKVProvider.d.ts +16 -18
- package/cloudflare/CloudflareKVProvider.js +11 -13
- package/db/cache/CollectionCache.d.ts +37 -0
- package/db/cache/CollectionCache.js +62 -0
- package/db/cache/DBCache.d.ts +38 -0
- package/db/cache/DBCache.js +59 -0
- package/db/collection/Collection.d.ts +12 -10
- package/db/index.d.ts +2 -0
- package/db/index.js +2 -0
- package/db/provider/CacheDBProvider.d.ts +18 -18
- package/db/provider/ChangesDBProvider.d.ts +11 -11
- package/db/provider/DBProvider.d.ts +17 -17
- package/db/provider/DBProvider.js +2 -2
- package/db/provider/DebugDBProvider.d.ts +15 -15
- package/db/provider/MemoryDBProvider.d.ts +28 -26
- package/db/provider/MemoryDBProvider.js +10 -5
- package/db/provider/MockDBProvider.d.ts +17 -17
- package/db/provider/PostgreSQLProvider.d.ts +4 -2
- package/db/provider/PostgreSQLProvider.js +1 -1
- package/db/provider/SQLProvider.d.ts +23 -23
- package/db/provider/SQLProvider.js +24 -23
- package/db/provider/SQLiteProvider.d.ts +6 -2
- package/db/provider/SQLiteProvider.js +14 -1
- package/db/provider/ThroughDBProvider.d.ts +19 -19
- package/db/provider/ValidationDBProvider.d.ts +14 -14
- package/db/provider/ValidationDBProvider.js +4 -8
- package/db/store/QueryStore.d.ts +3 -3
- package/firestore/client/FirestoreClientProvider.d.ts +21 -15
- package/firestore/client/FirestoreClientProvider.js +40 -37
- package/firestore/lite/FirestoreLiteProvider.d.ts +21 -15
- package/firestore/lite/FirestoreLiteProvider.js +38 -29
- package/firestore/server/FirestoreServerProvider.d.ts +21 -15
- package/firestore/server/FirestoreServerProvider.js +67 -71
- package/package.json +1 -1
- package/react/createAPIContext.d.ts +4 -2
- package/react/createAPIContext.js +22 -15
- package/react/createDBContext.d.ts +31 -0
- package/react/createDBContext.js +35 -0
- package/react/index.d.ts +1 -2
- package/react/index.js +1 -2
- package/util/http.d.ts +3 -2
- package/util/http.js +6 -6
- package/util/item.d.ts +4 -4
- package/util/query.d.ts +1 -4
- package/util/uri.d.ts +9 -4
- package/util/uri.js +4 -0
- package/api/provider/ClientAPIProvider.d.ts +0 -37
- package/api/provider/ClientAPIProvider.js +0 -51
- package/react/createCacheContext.d.ts +0 -13
- package/react/createCacheContext.js +0 -22
- package/react/createDataContext.d.ts +0 -26
- 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
|
|
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<
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
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
|
-
|
|
79
|
-
|
|
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(
|
|
86
|
-
const reference = await addDoc(
|
|
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(
|
|
90
|
-
await setDoc(
|
|
99
|
+
async setItem(c, id, data) {
|
|
100
|
+
await setDoc(this._doc(c, id), data);
|
|
91
101
|
}
|
|
92
|
-
async updateItem(
|
|
93
|
-
await updateDoc(
|
|
102
|
+
async updateItem(c, id, updates) {
|
|
103
|
+
await updateDoc(this._doc(c, id), _getFieldValues(updates));
|
|
94
104
|
}
|
|
95
|
-
async deleteItem(
|
|
96
|
-
await deleteDoc(
|
|
105
|
+
async deleteItem(c, id) {
|
|
106
|
+
await deleteDoc(this._doc(c, id));
|
|
97
107
|
}
|
|
98
|
-
async countQuery(
|
|
99
|
-
const snapshot = await getCount(
|
|
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(
|
|
103
|
-
|
|
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(
|
|
110
|
-
const snapshot = await getDocs(
|
|
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(
|
|
114
|
-
const snapshot = await getDocs(
|
|
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(
|
|
119
|
-
const snapshot = await getDocs(
|
|
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
|
|
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<
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
|
25
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
84
|
-
|
|
62
|
+
/** Create a corresponding `FirestoreCollection` reference from a collection. */
|
|
63
|
+
_getCollection(collection) {
|
|
64
|
+
return this._firestore.collection(collection.name);
|
|
85
65
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
93
|
-
|
|
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
|
|
96
|
-
await
|
|
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
|
|
99
|
-
await _getCollection(
|
|
108
|
+
async setItem(c, id, data) {
|
|
109
|
+
await this._getCollection(c).doc(id).set(data);
|
|
100
110
|
}
|
|
101
|
-
async
|
|
102
|
-
await _getCollection(
|
|
111
|
+
async updateItem(c, id, updates) {
|
|
112
|
+
await this._getCollection(c).doc(id).update(_getFieldValues(updates));
|
|
103
113
|
}
|
|
104
|
-
async
|
|
105
|
-
|
|
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(
|
|
109
|
-
return
|
|
121
|
+
async getQuery(c, q) {
|
|
122
|
+
return _getItems(await this._getQuery(c, q).get());
|
|
110
123
|
}
|
|
111
|
-
getQuerySequence(
|
|
112
|
-
const ref = _getQuery(
|
|
113
|
-
return new LazyDeferredSequence(sequence => ref.onSnapshot(snapshot => sequence.resolve(
|
|
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(
|
|
118
|
-
return await
|
|
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(
|
|
131
|
+
async updateQuery(c, q, updates) {
|
|
121
132
|
const fieldValues = _getFieldValues(updates);
|
|
122
|
-
return await
|
|
133
|
+
return await this._bulkWrite(c, q, (w, s) => void w.update(s.ref, fieldValues));
|
|
123
134
|
}
|
|
124
|
-
async deleteQuery(
|
|
125
|
-
return await
|
|
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,7 +1,7 @@
|
|
|
1
|
-
import type
|
|
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 {
|
|
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 {
|
|
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
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
package/react/index.js
CHANGED
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>>;
|