shelving 1.10.2 → 1.12.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/api/Resource.d.ts +1 -2
  2. package/db/Database.d.ts +1 -2
  3. package/db/Document.js +2 -2
  4. package/db/DocumentState.d.ts +11 -7
  5. package/db/DocumentState.js +28 -18
  6. package/db/Documents.js +2 -2
  7. package/db/DocumentsState.d.ts +12 -6
  8. package/db/DocumentsState.js +32 -16
  9. package/db/Pagination.js +11 -2
  10. package/db/Reference.d.ts +2 -2
  11. package/firestore-client/FirestoreClientProvider.d.ts +4 -4
  12. package/firestore-client/FirestoreClientProvider.js +36 -36
  13. package/firestore-lite/FirestoreLiteProvider.d.ts +23 -0
  14. package/firestore-lite/FirestoreLiteProvider.js +129 -0
  15. package/firestore-lite/index.d.ts +1 -0
  16. package/firestore-lite/index.js +1 -0
  17. package/firestore-server/FirestoreServerProvider.d.ts +1 -2
  18. package/firestore-server/FirestoreServerProvider.js +4 -5
  19. package/package.json +6 -6
  20. package/query/Filter.js +7 -0
  21. package/query/Filters.js +7 -0
  22. package/query/Sort.js +7 -1
  23. package/query/Sorts.js +7 -1
  24. package/react/useDocument.js +1 -1
  25. package/react/useDocuments.js +1 -1
  26. package/schema/ArraySchema.d.ts +1 -2
  27. package/schema/MapSchema.d.ts +1 -2
  28. package/schema/ObjectSchema.d.ts +1 -2
  29. package/schema/Schema.d.ts +1 -1
  30. package/schema/Schema.js +2 -1
  31. package/schema/StringSchema.d.ts +1 -1
  32. package/schema/index.d.ts +0 -1
  33. package/schema/index.js +0 -3
  34. package/stream/State.test.js +6 -5
  35. package/stream/Stream.js +6 -4
  36. package/tsconfig.build.tsbuildinfo +1 -1
  37. package/util/class.d.ts +4 -3
  38. package/util/class.js +1 -3
  39. package/util/clone.d.ts +2 -2
  40. package/util/date.d.ts +10 -0
  41. package/util/date.js +10 -0
  42. package/util/index.d.ts +1 -0
  43. package/util/index.js +1 -0
  44. package/util/observable.d.ts +4 -0
  45. package/util/observable.js +3 -0
  46. package/util/template.d.ts +3 -1
  47. package/{schema/Validator.d.ts → util/validate.d.ts} +1 -1
  48. package/{schema/Validator.js → util/validate.js} +1 -1
package/api/Resource.d.ts CHANGED
@@ -1,5 +1,4 @@
1
- import type { Arguments, AsyncFetcher } from "../util";
2
- import type { Validator } from "../schema";
1
+ import type { Arguments, AsyncFetcher, Validator } from "../util";
3
2
  /**
4
3
  * An abstract API resource definition, used to specify types for e.g. serverless functions..
5
4
  *
package/db/Database.d.ts CHANGED
@@ -1,5 +1,4 @@
1
- import type { Datas } from "../util/data";
2
- import type { Validators } from "../schema";
1
+ import type { Datas, Validators } from "../util";
3
2
  import type { Provider } from "./Provider";
4
3
  import { Document } from "./Document";
5
4
  import { Documents } from "./Documents";
package/db/Document.js CHANGED
@@ -1,4 +1,4 @@
1
- import { throwAsync, isAsync, } from "../util";
1
+ import { throwAsync, isAsync, createObserver, } from "../util";
2
2
  import { DocumentState } from "./DocumentState";
3
3
  import { ReferenceRequiredError } from "./errors";
4
4
  import { Reference } from "./Reference";
@@ -94,7 +94,7 @@ export class Document extends Reference {
94
94
  return DocumentState.get(this);
95
95
  }
96
96
  subscribe(next, error, complete) {
97
- return typeof next === "object" ? this.db.provider.onDocument(this, next) : this.db.provider.onDocument(this, { next, error, complete });
97
+ return this.db.provider.onDocument(this, createObserver(next, error, complete));
98
98
  }
99
99
  /**
100
100
  * Set the complete data of this document.
@@ -26,16 +26,20 @@ export declare class DocumentState<T extends Data = Data> extends State<Result<T
26
26
  */
27
27
  stop(): void;
28
28
  /**
29
- * Force refresh this state from the source provider.
29
+ * Refresh the state.
30
+ * @param maxAge How old the data can be before it's outdated.
31
+ * - If `maxAge` is undefined or falsy, always refresh the state.
32
+ * - If `maxAge` is a number this specifies how old (in milliseconds) the data can be before it's refreshed.
33
+ * - If `maxAge` is `true` a subscription to the data is started for 10 seconds.
30
34
  */
31
- refresh(): void;
35
+ refresh(maxAge?: number | true): void;
32
36
  /**
33
- * Conditionally refresh a documents state if it's outdated.
34
- * @param maxAge
35
- * - If `maxAge` is a number this specifies how old (in milliseconds the data can be before a refresh will be issued).
36
- * - If `maxAge` is `true` this will start a realtime subscription to the data that lasts for ten seconds.
37
+ * Get the current value, or refetch the data again if it's outdated.
38
+ * @param maxAge How old the data can be before it's outdated.
39
+ * - If `maxAge` is a number this specifies how old (in milliseconds) the data can be before it's refreshed.
40
+ * - If `maxAge` is `true` a subscription to the data is started for 10 seconds.
37
41
  */
38
- refreshOutdated(maxAge: number | true): void;
42
+ get(maxAge?: number): Result<T> | Promise<Result<T>>;
39
43
  protected dispatchError(reason: Error | unknown): void;
40
44
  protected dispatchComplete(): void;
41
45
  }
@@ -67,15 +67,17 @@ export class DocumentState extends State {
67
67
  * - The realtime subscription will clean itself up when the last observer unsubscribes.
68
68
  */
69
69
  start(observer) {
70
+ var _b;
70
71
  if (this.closed)
71
72
  throw new StreamClosedError(this);
72
- __classPrivateFieldSet(this, _DocumentState_realtime, +__classPrivateFieldGet(this, _DocumentState_realtime, "f") + 1, "f");
73
+ __classPrivateFieldSet(this, _DocumentState_realtime, (_b = __classPrivateFieldGet(this, _DocumentState_realtime, "f"), _b++, _b), "f");
73
74
  if (!__classPrivateFieldGet(this, _DocumentState_stop, "f"))
74
75
  __classPrivateFieldSet(this, _DocumentState_stop, __classPrivateFieldGet(this, _DocumentState_ref, "f").subscribe(this), "f");
75
76
  if (observer)
76
77
  this.on(observer);
77
78
  return () => {
78
- __classPrivateFieldSet(this, _DocumentState_realtime, +__classPrivateFieldGet(this, _DocumentState_realtime, "f") - 1, "f");
79
+ var _b;
80
+ __classPrivateFieldSet(this, _DocumentState_realtime, (_b = __classPrivateFieldGet(this, _DocumentState_realtime, "f"), _b--, _b), "f");
79
81
  if (__classPrivateFieldGet(this, _DocumentState_realtime, "f") <= 0)
80
82
  this.stop();
81
83
  if (observer)
@@ -91,20 +93,13 @@ export class DocumentState extends State {
91
93
  __classPrivateFieldSet(this, _DocumentState_stop, void dispatch(__classPrivateFieldGet(this, _DocumentState_stop, "f")), "f");
92
94
  }
93
95
  /**
94
- * Force refresh this state from the source provider.
96
+ * Refresh the state.
97
+ * @param maxAge How old the data can be before it's outdated.
98
+ * - If `maxAge` is undefined or falsy, always refresh the state.
99
+ * - If `maxAge` is a number this specifies how old (in milliseconds) the data can be before it's refreshed.
100
+ * - If `maxAge` is `true` a subscription to the data is started for 10 seconds.
95
101
  */
96
- refresh() {
97
- if (this.closed)
98
- throw new StreamClosedError(this);
99
- this.next(__classPrivateFieldGet(this, _DocumentState_ref, "f").get());
100
- }
101
- /**
102
- * Conditionally refresh a documents state if it's outdated.
103
- * @param maxAge
104
- * - If `maxAge` is a number this specifies how old (in milliseconds the data can be before a refresh will be issued).
105
- * - If `maxAge` is `true` this will start a realtime subscription to the data that lasts for ten seconds.
106
- */
107
- refreshOutdated(maxAge) {
102
+ refresh(maxAge) {
108
103
  if (this.closed)
109
104
  throw new StreamClosedError(this);
110
105
  // No need to refresh if there's an active subscription.
@@ -114,12 +109,27 @@ export class DocumentState extends State {
114
109
  setTimeout(this.start(), 10000);
115
110
  }
116
111
  else {
117
- // Fetch the refeshed data once if there isn't an existing subscription, and a fetch isn't already pending, and the data is older than `maxAge`
118
- if (!this.pending && this.age < maxAge)
119
- this.refresh();
112
+ // Refresh the data if a fetch isn't already pending, and either `maxAge` is falsy or the existing state is older than `maxAge`
113
+ if (!this.pending && (!maxAge || this.age > maxAge))
114
+ this.next(__classPrivateFieldGet(this, _DocumentState_ref, "f").get());
120
115
  }
121
116
  }
122
117
  }
118
+ /**
119
+ * Get the current value, or refetch the data again if it's outdated.
120
+ * @param maxAge How old the data can be before it's outdated.
121
+ * - If `maxAge` is a number this specifies how old (in milliseconds) the data can be before it's refreshed.
122
+ * - If `maxAge` is `true` a subscription to the data is started for 10 seconds.
123
+ */
124
+ get(maxAge = 0) {
125
+ // Return the current state if its not loading and there's a subscription or the existing state is younger than `maxAge`
126
+ if (!this.loading && (__classPrivateFieldGet(this, _DocumentState_stop, "f") || this.age < maxAge))
127
+ return this.value;
128
+ // Refresh the state and return the next value after the refresh.
129
+ const next = __classPrivateFieldGet(this, _DocumentState_ref, "f").get();
130
+ this.next(next);
131
+ return next;
132
+ }
123
133
  // Override to stop any realtime subscription on error or complete.
124
134
  dispatchError(reason) {
125
135
  this.stop();
package/db/Documents.js CHANGED
@@ -1,4 +1,4 @@
1
- import { throwAsync, isAsync, countEntries, getFirstProp, getLastProp, } from "../util";
1
+ import { throwAsync, isAsync, countEntries, getFirstProp, getLastProp, createObserver, } from "../util";
2
2
  import { Query } from "../query";
3
3
  import { Document } from "./Document";
4
4
  import { DocumentsState } from "./DocumentsState";
@@ -94,7 +94,7 @@ export class Documents extends Reference {
94
94
  return isAsync(results) ? results.then(Object.keys) : Object.keys(results);
95
95
  }
96
96
  subscribe(next, error, complete) {
97
- return typeof next === "object" ? this.db.provider.onDocuments(this, next) : this.db.provider.onDocuments(this, { next, error, complete });
97
+ return this.db.provider.onDocuments(this, createObserver(next, error, complete));
98
98
  }
99
99
  /**
100
100
  * Get an entry for the first result in this set of documents (synchronously).
@@ -25,15 +25,21 @@ export declare class DocumentsState<T extends Data = Data> extends State<Results
25
25
  */
26
26
  stop(): void;
27
27
  /**
28
- * Force refresh this state from the source provider.
28
+ * Refresh the state.
29
+ * @param maxAge How old the data can be before it's outdated.
30
+ * - If `maxAge` is undefined or falsy, always refresh the state.
31
+ * - If `maxAge` is a number this specifies how old (in milliseconds) the data can be before it's refreshed.
32
+ * - If `maxAge` is `true` a subscription to the data is started for 10 seconds.
29
33
  */
30
- refresh(): void;
34
+ refresh(maxAge?: number | true): void;
31
35
  /**
32
- * Conditionally refresh a documents state if it's outdated.
33
- * @param maxAge
34
- * - If `maxAge` is a number this specifies how old (in milliseconds
36
+ * Get the current value, or refetch the data again if it's outdated.
37
+ * @param maxAge How old the data can be before it's outdated.
38
+ * - If `maxAge` is undefined or falsy, always return fresh state.
39
+ * - If `maxAge` is a number this specifies how old (in milliseconds) the data can be before it's refreshed.
40
+ * - If `maxAge` is `true` a subscription to the data is started for 10 seconds.
35
41
  */
36
- refreshOutdated(maxAge: number | true): void;
42
+ get(maxAge?: number): Results<T> | Promise<Results<T>>;
37
43
  protected dispatchNext(results: Results<T>): void;
38
44
  protected dispatchError(reason: Error | unknown): void;
39
45
  protected dispatchComplete(): void;
@@ -11,7 +11,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
11
11
  };
12
12
  var _a, _DocumentsState_states, _DocumentsState_ref, _DocumentsState_realtime, _DocumentsState_stop;
13
13
  import { AssertionError } from "../errors";
14
- import { State } from "../stream";
14
+ import { State, StreamClosedError } from "../stream";
15
15
  import { dispatch, LOADING } from "../util";
16
16
  import { DocumentState } from "./DocumentState";
17
17
  /**
@@ -63,15 +63,17 @@ export class DocumentsState extends State {
63
63
  * - The realtime subscription will clean itself up when the last observer unsubscribes.
64
64
  */
65
65
  start(observer) {
66
+ var _b;
66
67
  if (!this.closed)
67
68
  throw new AssertionError("State must not be closed", this.closed);
68
- __classPrivateFieldSet(this, _DocumentsState_realtime, +__classPrivateFieldGet(this, _DocumentsState_realtime, "f") + 1, "f");
69
+ __classPrivateFieldSet(this, _DocumentsState_realtime, (_b = __classPrivateFieldGet(this, _DocumentsState_realtime, "f"), _b++, _b), "f");
69
70
  if (!__classPrivateFieldGet(this, _DocumentsState_stop, "f"))
70
71
  __classPrivateFieldSet(this, _DocumentsState_stop, __classPrivateFieldGet(this, _DocumentsState_ref, "f").subscribe(this), "f");
71
72
  if (observer)
72
73
  this.on(observer);
73
74
  return () => {
74
- __classPrivateFieldSet(this, _DocumentsState_realtime, +__classPrivateFieldGet(this, _DocumentsState_realtime, "f") - 1, "f");
75
+ var _b;
76
+ __classPrivateFieldSet(this, _DocumentsState_realtime, (_b = __classPrivateFieldGet(this, _DocumentsState_realtime, "f"), _b--, _b), "f");
75
77
  if (__classPrivateFieldGet(this, _DocumentsState_realtime, "f") <= 0)
76
78
  this.stop();
77
79
  if (observer)
@@ -87,17 +89,15 @@ export class DocumentsState extends State {
87
89
  __classPrivateFieldSet(this, _DocumentsState_stop, void dispatch(__classPrivateFieldGet(this, _DocumentsState_stop, "f")), "f");
88
90
  }
89
91
  /**
90
- * Force refresh this state from the source provider.
91
- */
92
- refresh() {
93
- this.next(__classPrivateFieldGet(this, _DocumentsState_ref, "f").get());
94
- }
95
- /**
96
- * Conditionally refresh a documents state if it's outdated.
97
- * @param maxAge
98
- * - If `maxAge` is a number this specifies how old (in milliseconds
92
+ * Refresh the state.
93
+ * @param maxAge How old the data can be before it's outdated.
94
+ * - If `maxAge` is undefined or falsy, always refresh the state.
95
+ * - If `maxAge` is a number this specifies how old (in milliseconds) the data can be before it's refreshed.
96
+ * - If `maxAge` is `true` a subscription to the data is started for 10 seconds.
99
97
  */
100
- refreshOutdated(maxAge) {
98
+ refresh(maxAge) {
99
+ if (this.closed)
100
+ throw new StreamClosedError(this);
101
101
  // No need to refresh if there's an active subscription.
102
102
  if (!__classPrivateFieldGet(this, _DocumentsState_stop, "f")) {
103
103
  if (maxAge === true) {
@@ -105,12 +105,28 @@ export class DocumentsState extends State {
105
105
  setTimeout(__classPrivateFieldGet(this, _DocumentsState_ref, "f").subscribe({}), 10000);
106
106
  }
107
107
  else {
108
- // Fetch the refeshed data once if there isn't an existing subscription, and a fetch isn't already pending, and the data is older than `maxAge`
109
- if (!this.pending && this.age < maxAge)
110
- this.refresh();
108
+ // Refresh the data if a fetch isn't already pending, and either `maxAge` is falsy or the existing state is older than `maxAge`
109
+ if (!this.pending && (!maxAge || this.age > maxAge))
110
+ this.next(__classPrivateFieldGet(this, _DocumentsState_ref, "f").get());
111
111
  }
112
112
  }
113
113
  }
114
+ /**
115
+ * Get the current value, or refetch the data again if it's outdated.
116
+ * @param maxAge How old the data can be before it's outdated.
117
+ * - If `maxAge` is undefined or falsy, always return fresh state.
118
+ * - If `maxAge` is a number this specifies how old (in milliseconds) the data can be before it's refreshed.
119
+ * - If `maxAge` is `true` a subscription to the data is started for 10 seconds.
120
+ */
121
+ get(maxAge = 0) {
122
+ // Return the current state if its not loading and there's a subscription or the existing state is younger than `maxAge`
123
+ if (!this.loading && (__classPrivateFieldGet(this, _DocumentsState_stop, "f") || this.age < maxAge))
124
+ return this.value;
125
+ // Refresh the state and return the next value after the refresh.
126
+ const next = __classPrivateFieldGet(this, _DocumentsState_ref, "f").get();
127
+ this.next(next);
128
+ return next;
129
+ }
114
130
  // Override dispatchNext to set the state for every individual document too.
115
131
  dispatchNext(results) {
116
132
  super.dispatchNext(results);
package/db/Pagination.js CHANGED
@@ -4,6 +4,9 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
4
4
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
+ var __metadata = (this && this.__metadata) || function (k, v) {
8
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
9
+ };
7
10
  var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
8
11
  if (kind === "m") throw new TypeError("Private method is not writable");
9
12
  if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
@@ -91,8 +94,14 @@ async function _PaginationState_mergeReference(ref) {
91
94
  }
92
95
  };
93
96
  __decorate([
94
- bindMethod
97
+ bindMethod,
98
+ __metadata("design:type", Function),
99
+ __metadata("design:paramtypes", []),
100
+ __metadata("design:returntype", void 0)
95
101
  ], PaginationState.prototype, "backward", null);
96
102
  __decorate([
97
- bindMethod
103
+ bindMethod,
104
+ __metadata("design:type", Function),
105
+ __metadata("design:paramtypes", []),
106
+ __metadata("design:returntype", void 0)
98
107
  ], PaginationState.prototype, "forward", null);
package/db/Reference.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { ObjectSchema, Validator } from "../schema";
2
- import { Data } from "../util";
1
+ import { ObjectSchema } from "../schema";
2
+ import { Data, Validator } from "../util";
3
3
  import type { Database } from "./Database";
4
4
  /**
5
5
  * Reference: a location in a database
@@ -1,10 +1,10 @@
1
- import "firebase/firestore";
2
- import type { FirebaseFirestore as Firestore } from "@firebase/firestore-types";
1
+ import type { Firestore } from "firebase/firestore";
3
2
  import { Data, Results, Provider, Document, Documents, Result, Observer, Transforms } from "..";
4
3
  /**
5
4
  * Firestore client database provider.
6
- * - Works with the JS client SDK.
7
- * @todo Maybe find a way to generate a unique hash of
5
+ * - Works with the Firebase JS SDK.
6
+ * - Supports offline mode.
7
+ * - Supports realtime subscriptions.
8
8
  */
9
9
  export declare class FirestoreClientProvider implements Provider {
10
10
  readonly firestore: Firestore;
@@ -1,7 +1,5 @@
1
- import firebase from "firebase/app";
2
- import "firebase/firestore";
3
- import { dispatchNext, dispatchError } from "..";
4
- import { AddItemsTransform, AddEntriesTransform, IncrementTransform, isTransform, RemoveItemsTransform, RemoveEntriesTransform } from "../util";
1
+ import { getDoc, orderBy, where, limit, addDoc, increment, arrayUnion, arrayRemove, deleteField, collection as getFirestoreCollection, doc as getFirestoreDoc, query as getFirestoreQuery, onSnapshot, setDoc, updateDoc, deleteDoc, getDocs, } from "firebase/firestore";
2
+ import { dispatchNext, dispatchError, AddItemsTransform, AddEntriesTransform, IncrementTransform, isTransform, RemoveItemsTransform, RemoveEntriesTransform, } from "..";
5
3
  // Constants.
6
4
  // const ID = "__name__"; // DH: `__name__` is the entire path of the document. `__id__` is just ID.
7
5
  const ID = "__id__"; // Internal way Firestore Queries can reference the ID of the current document.
@@ -22,24 +20,24 @@ const DIRECTIONS = {
22
20
  DESC: "desc",
23
21
  };
24
22
  /** Get a Firestore DocumentReference for a given Shelving `Document` instance. */
25
- function getDocument(firestore, { path }) {
26
- return firestore.doc(path);
23
+ function getDocumentReference(firestore, { path }) {
24
+ return getFirestoreDoc(firestore, path);
27
25
  }
28
26
  /** Get a Firestore CollectionReference for a given Shelving `Document` instance. */
29
- function getCollection(firestore, { path }) {
30
- return firestore.collection(path);
27
+ function getCollectionReference(firestore, { path }) {
28
+ return getFirestoreCollection(firestore, path);
31
29
  }
32
30
  /** Create a corresponding `QueryReference` from a Query. */
33
- function getQuery(firestore, ref) {
31
+ function getQueryReference(firestore, ref) {
34
32
  const { sorts, filters, slice } = ref.query;
35
- let query = getCollection(firestore, ref);
33
+ const constraints = [];
36
34
  for (const { key, direction } of sorts)
37
- query = query.orderBy(key === "id" ? ID : key, DIRECTIONS[direction]);
35
+ constraints.push(orderBy(key === "id" ? ID : key, DIRECTIONS[direction]));
38
36
  for (const { operator, key, value } of filters)
39
- query = query.where(key === "id" ? ID : key, OPERATORS[operator], value);
37
+ constraints.push(where(key === "id" ? ID : key, OPERATORS[operator], value));
40
38
  if (slice.limit !== null)
41
- query = query.limit(slice.limit);
42
- return query;
39
+ constraints.push(limit(slice.limit));
40
+ return getFirestoreQuery(getCollectionReference(firestore, ref), ...constraints);
43
41
  }
44
42
  /** Create a set of results from a collection snapshot. */
45
43
  function getResults(snapshot) {
@@ -54,13 +52,13 @@ function convertTransforms(transforms) {
54
52
  for (const [key, transform] of Object.entries(transforms)) {
55
53
  if (isTransform(transform)) {
56
54
  if (transform instanceof IncrementTransform) {
57
- output[key] = firebase.firestore.FieldValue.increment(transform.amount);
55
+ output[key] = increment(transform.amount);
58
56
  }
59
57
  else if (transform instanceof AddItemsTransform) {
60
- output[key] = firebase.firestore.FieldValue.arrayUnion(...transform.items);
58
+ output[key] = arrayUnion(...transform.items);
61
59
  }
62
60
  else if (transform instanceof RemoveItemsTransform) {
63
- output[key] = firebase.firestore.FieldValue.arrayRemove(...transform.items);
61
+ output[key] = arrayRemove(...transform.items);
64
62
  }
65
63
  else if (transform instanceof AddEntriesTransform) {
66
64
  for (const [k, v] of Object.entries(transform.props))
@@ -68,62 +66,64 @@ function convertTransforms(transforms) {
68
66
  }
69
67
  else if (transform instanceof RemoveEntriesTransform) {
70
68
  for (const k of transform.props)
71
- output[`${key}.${k}`] = firebase.firestore.FieldValue.delete();
69
+ output[`${key}.${k}`] = deleteField();
72
70
  }
73
71
  else
74
72
  throw Error("Unsupported transform");
75
73
  }
76
- else
74
+ else if (transform !== undefined) {
77
75
  output[key] = transform;
76
+ }
78
77
  }
79
78
  return output;
80
79
  }
81
80
  /**
82
81
  * Firestore client database provider.
83
- * - Works with the JS client SDK.
84
- * @todo Maybe find a way to generate a unique hash of
82
+ * - Works with the Firebase JS SDK.
83
+ * - Supports offline mode.
84
+ * - Supports realtime subscriptions.
85
85
  */
86
86
  export class FirestoreClientProvider {
87
87
  constructor(firestore) {
88
88
  this.firestore = firestore;
89
89
  }
90
90
  async getDocument(ref) {
91
- const snapshot = await getDocument(this.firestore, ref).get();
91
+ const snapshot = await getDoc(getDocumentReference(this.firestore, ref));
92
92
  return snapshot.data();
93
93
  }
94
94
  onDocument(ref, observer) {
95
- return getDocument(this.firestore, ref).onSnapshot(snapshot => dispatchNext(observer, snapshot.data()), error => dispatchError(observer, error));
95
+ return onSnapshot(getDocumentReference(this.firestore, ref), snapshot => dispatchNext(observer, snapshot.data()), error => dispatchError(observer, error));
96
96
  }
97
97
  async addDocument(ref, data) {
98
- return (await getCollection(this.firestore, ref).add(data)).id;
98
+ const reference = await addDoc(getCollectionReference(this.firestore, ref), data); // eslint-disable-line @typescript-eslint/no-explicit-any
99
+ return reference.id;
99
100
  }
100
101
  async setDocument(ref, data) {
101
- await getDocument(this.firestore, ref).set(data);
102
+ await setDoc(getDocumentReference(this.firestore, ref), data); // eslint-disable-line @typescript-eslint/no-explicit-any
102
103
  }
103
104
  async updateDocument(ref, transforms) {
104
- const updates = convertTransforms(transforms);
105
- await getDocument(this.firestore, ref).update(updates);
105
+ await updateDoc(getDocumentReference(this.firestore, ref), convertTransforms(transforms)); // eslint-disable-line @typescript-eslint/no-explicit-any
106
106
  }
107
107
  async deleteDocument(ref) {
108
- await getDocument(this.firestore, ref).delete();
108
+ await deleteDoc(getDocumentReference(this.firestore, ref));
109
109
  }
110
110
  async getDocuments(ref) {
111
- return getResults(await getQuery(this.firestore, ref).get());
111
+ return getResults(await getDocs(getQueryReference(this.firestore, ref)));
112
112
  }
113
113
  onDocuments(ref, observer) {
114
- return getQuery(this.firestore, ref).onSnapshot(snapshot => dispatchNext(observer, getResults(snapshot)), error => dispatchError(observer, error));
114
+ return onSnapshot(getQueryReference(this.firestore, ref), snapshot => dispatchNext(observer, getResults(snapshot)), error => dispatchError(observer, error));
115
115
  }
116
116
  async setDocuments(ref, data) {
117
- const snapshot = await getQuery(this.firestore, ref).get();
118
- await Promise.all(snapshot.docs.map(s => s.ref.set(data)));
117
+ const snapshot = await getDocs(getQueryReference(this.firestore, ref));
118
+ await Promise.all(snapshot.docs.map(s => setDoc(s.ref, data))); // eslint-disable-line @typescript-eslint/no-explicit-any
119
119
  }
120
120
  async updateDocuments(ref, transforms) {
121
- const snapshot = await getQuery(this.firestore, ref).get();
121
+ const snapshot = await getDocs(getQueryReference(this.firestore, ref));
122
122
  const updates = convertTransforms(transforms);
123
- await Promise.all(snapshot.docs.map(s => s.ref.update(updates)));
123
+ await Promise.all(snapshot.docs.map(s => updateDoc(s.ref, updates))); // eslint-disable-line @typescript-eslint/no-explicit-any
124
124
  }
125
125
  async deleteDocuments(ref) {
126
- const snapshot = await getQuery(this.firestore, ref).get();
127
- await Promise.all(snapshot.docs.map(s => s.ref.delete()));
126
+ const snapshot = await getDocs(getQueryReference(this.firestore, ref));
127
+ await Promise.all(snapshot.docs.map(s => deleteDoc(s.ref)));
128
128
  }
129
129
  }
@@ -0,0 +1,23 @@
1
+ import type { Firestore } from "firebase/firestore/lite";
2
+ import { Data, Results, Provider, Document, Documents, Result, Transforms } from "..";
3
+ /**
4
+ * Firestore Lite client database provider.
5
+ * - Works with the Firebase JS SDK.
6
+ * - Does not support offline mode.
7
+ * - Does not support realtime subscriptions.
8
+ */
9
+ export declare class FirestoreClientProvider implements Provider {
10
+ readonly firestore: Firestore;
11
+ constructor(firestore: Firestore);
12
+ getDocument<X extends Data>(ref: Document<X>): Promise<Result<X>>;
13
+ onDocument(): () => void;
14
+ addDocument<X extends Data>(ref: Documents<X>, data: X): Promise<string>;
15
+ setDocument<X extends Data>(ref: Document<X>, data: X): Promise<void>;
16
+ updateDocument<X extends Data>(ref: Document<X>, transforms: Transforms<X>): Promise<void>;
17
+ deleteDocument<X extends Data>(ref: Document<X>): Promise<void>;
18
+ getDocuments<X extends Data>(ref: Documents<X>): Promise<Results<X>>;
19
+ onDocuments(): () => void;
20
+ setDocuments<X extends Data>(ref: Documents<X>, data: X): Promise<void>;
21
+ updateDocuments<X extends Data>(ref: Documents<X>, transforms: Transforms<X>): Promise<void>;
22
+ deleteDocuments<X extends Data>(ref: Documents<X>): Promise<void>;
23
+ }
@@ -0,0 +1,129 @@
1
+ import { getDoc, orderBy, where, limit, addDoc, increment, arrayUnion, arrayRemove, deleteField, collection as getFirestoreCollection, doc as getFirestoreDoc, query as getFirestoreQuery, setDoc, updateDoc, deleteDoc, getDocs, } from "firebase/firestore/lite";
2
+ import { AddItemsTransform, AddEntriesTransform, IncrementTransform, isTransform, RemoveItemsTransform, RemoveEntriesTransform, } from "..";
3
+ // Constants.
4
+ // const ID = "__name__"; // DH: `__name__` is the entire path of the document. `__id__` is just ID.
5
+ const ID = "__id__"; // Internal way Firestore Queries can reference the ID of the current document.
6
+ // Map `Filter.types` to `WhereFilterOp`
7
+ const OPERATORS = {
8
+ IS: "==",
9
+ NOT: "!=",
10
+ IN: "in",
11
+ GT: ">",
12
+ GTE: ">=",
13
+ LT: "<",
14
+ LTE: "<=",
15
+ CONTAINS: "array-contains",
16
+ };
17
+ // Map `Filter.types` to `OrderByDirection`
18
+ const DIRECTIONS = {
19
+ ASC: "asc",
20
+ DESC: "desc",
21
+ };
22
+ /** Get a Firestore DocumentReference for a given Shelving `Document` instance. */
23
+ function getDocumentReference(firestore, { path }) {
24
+ return getFirestoreDoc(firestore, path);
25
+ }
26
+ /** Get a Firestore CollectionReference for a given Shelving `Document` instance. */
27
+ function getCollectionReference(firestore, { path }) {
28
+ return getFirestoreCollection(firestore, path);
29
+ }
30
+ /** Create a corresponding `QueryReference` from a Query. */
31
+ function getQueryReference(firestore, ref) {
32
+ const { sorts, filters, slice } = ref.query;
33
+ const constraints = [];
34
+ for (const { key, direction } of sorts)
35
+ constraints.push(orderBy(key === "id" ? ID : key, DIRECTIONS[direction]));
36
+ for (const { operator, key, value } of filters)
37
+ constraints.push(where(key === "id" ? ID : key, OPERATORS[operator], value));
38
+ if (slice.limit !== null)
39
+ constraints.push(limit(slice.limit));
40
+ return getFirestoreQuery(getCollectionReference(firestore, ref), ...constraints);
41
+ }
42
+ /** Create a set of results from a collection snapshot. */
43
+ function getResults(snapshot) {
44
+ const results = {};
45
+ for (const s of snapshot.docs)
46
+ results[s.id] = s.data();
47
+ return results;
48
+ }
49
+ /** Convert a set of Shelving `Transform` instances into the corresponding Firestore `FieldValue` instances. */
50
+ function convertTransforms(transforms) {
51
+ const output = {};
52
+ for (const [key, transform] of Object.entries(transforms)) {
53
+ if (isTransform(transform)) {
54
+ if (transform instanceof IncrementTransform) {
55
+ output[key] = increment(transform.amount);
56
+ }
57
+ else if (transform instanceof AddItemsTransform) {
58
+ output[key] = arrayUnion(...transform.items);
59
+ }
60
+ else if (transform instanceof RemoveItemsTransform) {
61
+ output[key] = arrayRemove(...transform.items);
62
+ }
63
+ else if (transform instanceof AddEntriesTransform) {
64
+ for (const [k, v] of Object.entries(transform.props))
65
+ output[`${key}.${k}`] = v;
66
+ }
67
+ else if (transform instanceof RemoveEntriesTransform) {
68
+ for (const k of transform.props)
69
+ output[`${key}.${k}`] = deleteField();
70
+ }
71
+ else
72
+ throw Error("Unsupported transform");
73
+ }
74
+ else if (transform !== undefined) {
75
+ output[key] = transform;
76
+ }
77
+ }
78
+ return output;
79
+ }
80
+ /**
81
+ * Firestore Lite client database provider.
82
+ * - Works with the Firebase JS SDK.
83
+ * - Does not support offline mode.
84
+ * - Does not support realtime subscriptions.
85
+ */
86
+ export class FirestoreClientProvider {
87
+ constructor(firestore) {
88
+ this.firestore = firestore;
89
+ }
90
+ async getDocument(ref) {
91
+ const snapshot = await getDoc(getDocumentReference(this.firestore, ref));
92
+ return snapshot.data();
93
+ }
94
+ onDocument() {
95
+ throw new Error("FirestoreLiteProvider does not support realtime subscriptions");
96
+ }
97
+ async addDocument(ref, data) {
98
+ const reference = await addDoc(getCollectionReference(this.firestore, ref), data); // eslint-disable-line @typescript-eslint/no-explicit-any
99
+ return reference.id;
100
+ }
101
+ async setDocument(ref, data) {
102
+ await setDoc(getDocumentReference(this.firestore, ref), data); // eslint-disable-line @typescript-eslint/no-explicit-any
103
+ }
104
+ async updateDocument(ref, transforms) {
105
+ await updateDoc(getDocumentReference(this.firestore, ref), convertTransforms(transforms)); // eslint-disable-line @typescript-eslint/no-explicit-any
106
+ }
107
+ async deleteDocument(ref) {
108
+ await deleteDoc(getDocumentReference(this.firestore, ref));
109
+ }
110
+ async getDocuments(ref) {
111
+ return getResults(await getDocs(getQueryReference(this.firestore, ref)));
112
+ }
113
+ onDocuments() {
114
+ throw new Error("FirestoreLiteProvider does not support realtime subscriptions");
115
+ }
116
+ async setDocuments(ref, data) {
117
+ const snapshot = await getDocs(getQueryReference(this.firestore, ref));
118
+ await Promise.all(snapshot.docs.map(s => setDoc(s.ref, data))); // eslint-disable-line @typescript-eslint/no-explicit-any
119
+ }
120
+ async updateDocuments(ref, transforms) {
121
+ const snapshot = await getDocs(getQueryReference(this.firestore, ref));
122
+ const updates = convertTransforms(transforms);
123
+ await Promise.all(snapshot.docs.map(s => updateDoc(s.ref, updates))); // eslint-disable-line @typescript-eslint/no-explicit-any
124
+ }
125
+ async deleteDocuments(ref) {
126
+ const snapshot = await getDocs(getQueryReference(this.firestore, ref));
127
+ await Promise.all(snapshot.docs.map(s => deleteDoc(s.ref)));
128
+ }
129
+ }
@@ -0,0 +1 @@
1
+ export * from "./FirestoreLiteProvider";
@@ -0,0 +1 @@
1
+ export * from "./FirestoreLiteProvider";
@@ -2,8 +2,7 @@ import type { Firestore } from "@google-cloud/firestore";
2
2
  import { Data, Results, Provider, Document, Documents, Observer, Result, Transforms } from "..";
3
3
  /**
4
4
  * Firestore server database provider.
5
- * - Works with the Node.JS admin SDK.
6
- * - Equality hash is set on returned values so `deepEqual()` etc can use it to quickly compare results without needing to look deeply.
5
+ * - Works with the Firebase Admin SDK for Node.JS
7
6
  */
8
7
  export declare class FirestoreServerProvider implements Provider {
9
8
  readonly firestore: Firestore;