@visiolab/effect-firebase 0.0.0-stage → 0.3.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/LICENSE +21 -0
- package/README.md +81 -2
- package/dist/_virtual/_rolldown/runtime.mjs +12 -0
- package/dist/app/App.d.mts +27 -0
- package/dist/app/App.mjs +27 -0
- package/dist/app/index.d.mts +2 -0
- package/dist/app/index.mjs +1 -0
- package/dist/firestore/Collection.d.mts +423 -0
- package/dist/firestore/Collection.mjs +133 -0
- package/dist/firestore/CollectionGroup.d.mts +164 -0
- package/dist/firestore/CollectionGroup.mjs +84 -0
- package/dist/firestore/DocumentPath.d.mts +95 -0
- package/dist/firestore/DocumentPath.mjs +71 -0
- package/dist/firestore/DocumentSnapshot.d.mts +239 -0
- package/dist/firestore/DocumentSnapshot.mjs +134 -0
- package/dist/firestore/FieldValue.d.mts +332 -0
- package/dist/firestore/FieldValue.mjs +256 -0
- package/dist/firestore/Firestore.d.mts +1165 -0
- package/dist/firestore/Firestore.mjs +274 -0
- package/dist/firestore/FirestoreModel.d.mts +1232 -0
- package/dist/firestore/FirestoreModel.mjs +586 -0
- package/dist/firestore/FirestoreSchema.d.mts +567 -0
- package/dist/firestore/FirestoreSchema.mjs +419 -0
- package/dist/firestore/FirestoreValue.d.mts +145 -0
- package/dist/firestore/FirestoreValue.mjs +87 -0
- package/dist/firestore/GeoPoint.d.mts +90 -0
- package/dist/firestore/GeoPoint.mjs +75 -0
- package/dist/firestore/Patch.d.mts +73 -0
- package/dist/firestore/Patch.mjs +57 -0
- package/dist/firestore/QuerySnapshot.d.mts +107 -0
- package/dist/firestore/QuerySnapshot.mjs +43 -0
- package/dist/firestore/Timestamp.d.mts +273 -0
- package/dist/firestore/Timestamp.mjs +255 -0
- package/dist/firestore/index.d.mts +14 -0
- package/dist/firestore/index.mjs +14 -0
- package/dist/firestore/internal/collection.mjs +156 -0
- package/dist/firestore/internal/collectionGroup.mjs +64 -0
- package/dist/firestore/internal/documentPath.d.mts +4 -0
- package/dist/firestore/internal/documentPath.mjs +42 -0
- package/dist/firestore/internal/documentSnapshot.mjs +63 -0
- package/dist/firestore/internal/fieldValue.d.mts +11 -0
- package/dist/firestore/internal/fieldValue.mjs +88 -0
- package/dist/firestore/internal/firestore.mjs +145 -0
- package/dist/firestore/internal/firestoreError.d.mts +29 -0
- package/dist/firestore/internal/firestoreError.mjs +30 -0
- package/dist/firestore/internal/firestoreModel.mjs +719 -0
- package/dist/firestore/internal/firestoreSchema.mjs +123 -0
- package/dist/firestore/internal/firestoreValue.mjs +108 -0
- package/dist/firestore/internal/firestoreValueWalk.d.mts +58 -0
- package/dist/firestore/internal/firestoreValueWalk.mjs +223 -0
- package/dist/firestore/internal/geoPoint.d.mts +4 -0
- package/dist/firestore/internal/geoPoint.mjs +39 -0
- package/dist/firestore/internal/patch.mjs +19 -0
- package/dist/firestore/internal/querySnapshot.mjs +24 -0
- package/dist/firestore/internal/schemaAST.mjs +35 -0
- package/dist/firestore/internal/snapshotExtra.mjs +35 -0
- package/dist/firestore/internal/streamFromListener.mjs +12 -0
- package/dist/firestore/internal/timestamp.d.mts +4 -0
- package/dist/firestore/internal/timestamp.mjs +66 -0
- package/dist/firestore/internal/timestampSchema.mjs +31 -0
- package/dist/firestore/internal/transactionFromCallback.mjs +66 -0
- package/dist/functions/Callable.d.mts +654 -0
- package/dist/functions/Callable.mjs +450 -0
- package/dist/functions/CallableError.d.mts +297 -0
- package/dist/functions/CallableError.mjs +256 -0
- package/dist/functions/CallableMiddleware.d.mts +213 -0
- package/dist/functions/CallableMiddleware.mjs +52 -0
- package/dist/functions/CallableRequest.d.mts +166 -0
- package/dist/functions/CallableRequest.mjs +18 -0
- package/dist/functions/Caller.d.mts +103 -0
- package/dist/functions/Caller.mjs +115 -0
- package/dist/functions/DocumentTrigger.d.mts +795 -0
- package/dist/functions/DocumentTrigger.mjs +566 -0
- package/dist/functions/FirebaseFunction.d.mts +79 -0
- package/dist/functions/FirebaseFunction.mjs +4 -0
- package/dist/functions/FunctionGroup.d.mts +175 -0
- package/dist/functions/FunctionGroup.mjs +64 -0
- package/dist/functions/index.d.mts +9 -0
- package/dist/functions/index.mjs +9 -0
- package/dist/functions/internal/callable.mjs +167 -0
- package/dist/functions/internal/callableMiddleware.d.mts +19 -0
- package/dist/functions/internal/callableMiddleware.mjs +15 -0
- package/dist/functions/internal/callableRequest.mjs +6 -0
- package/dist/functions/internal/documentTrigger.d.mts +10 -0
- package/dist/functions/internal/documentTrigger.mjs +73 -0
- package/dist/functions/internal/firebaseFunction.d.mts +5 -0
- package/dist/functions/internal/firebaseFunction.mjs +24 -0
- package/dist/functions/internal/functionGroup.mjs +36 -0
- package/package.json +66 -3
- package/src/app/App.ts +24 -0
- package/src/app/index.ts +7 -0
- package/src/firestore/Collection.ts +520 -0
- package/src/firestore/CollectionGroup.ts +245 -0
- package/src/firestore/DocumentPath.ts +114 -0
- package/src/firestore/DocumentSnapshot.ts +308 -0
- package/src/firestore/FieldValue.ts +375 -0
- package/src/firestore/Firestore.ts +1432 -0
- package/src/firestore/FirestoreModel.ts +1884 -0
- package/src/firestore/FirestoreSchema.ts +643 -0
- package/src/firestore/FirestoreValue.ts +207 -0
- package/src/firestore/GeoPoint.ts +105 -0
- package/src/firestore/Patch.ts +117 -0
- package/src/firestore/QuerySnapshot.ts +150 -0
- package/src/firestore/Timestamp.ts +302 -0
- package/src/firestore/index.ts +67 -0
- package/src/firestore/internal/collection.ts +246 -0
- package/src/firestore/internal/collectionGroup.ts +81 -0
- package/src/firestore/internal/documentPath.ts +53 -0
- package/src/firestore/internal/documentSnapshot.ts +117 -0
- package/src/firestore/internal/fieldValue.ts +119 -0
- package/src/firestore/internal/firestore.ts +322 -0
- package/src/firestore/internal/firestoreError.ts +36 -0
- package/src/firestore/internal/firestoreModel.ts +1242 -0
- package/src/firestore/internal/firestoreSchema.ts +218 -0
- package/src/firestore/internal/firestoreValue.ts +202 -0
- package/src/firestore/internal/firestoreValueWalk.ts +349 -0
- package/src/firestore/internal/geoPoint.ts +59 -0
- package/src/firestore/internal/patch.ts +21 -0
- package/src/firestore/internal/querySnapshot.ts +39 -0
- package/src/firestore/internal/schemaAST.ts +83 -0
- package/src/firestore/internal/snapshotExtra.ts +47 -0
- package/src/firestore/internal/streamFromListener.ts +27 -0
- package/src/firestore/internal/timestamp.ts +119 -0
- package/src/firestore/internal/timestampSchema.ts +42 -0
- package/src/firestore/internal/transactionFromCallback.ts +106 -0
- package/src/functions/Callable.ts +855 -0
- package/src/functions/CallableError.ts +348 -0
- package/src/functions/CallableMiddleware.ts +279 -0
- package/src/functions/CallableRequest.ts +207 -0
- package/src/functions/Caller.ts +141 -0
- package/src/functions/DocumentTrigger.ts +927 -0
- package/src/functions/FirebaseFunction.ts +96 -0
- package/src/functions/FunctionGroup.ts +225 -0
- package/src/functions/index.ts +62 -0
- package/src/functions/internal/callable.ts +272 -0
- package/src/functions/internal/callableMiddleware.ts +54 -0
- package/src/functions/internal/callableRequest.ts +8 -0
- package/src/functions/internal/documentTrigger.ts +143 -0
- package/src/functions/internal/firebaseFunction.ts +58 -0
- package/src/functions/internal/functionGroup.ts +47 -0
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collection Groups: every collection with one collection id, wherever it
|
|
3
|
+
* sits, read together.
|
|
4
|
+
*
|
|
5
|
+
* A group is built from the {@link Collection.Collection} definitions it
|
|
6
|
+
* covers, not from a bare collection id. Each document a read of the group
|
|
7
|
+
* finds is matched to the collection it lives in, and comes back typed,
|
|
8
|
+
* addressed and decoded as that collection's.
|
|
9
|
+
*
|
|
10
|
+
* @example Reading every post of every user
|
|
11
|
+
* ```ts
|
|
12
|
+
* import {
|
|
13
|
+
* Collection,
|
|
14
|
+
* CollectionGroup,
|
|
15
|
+
* Firestore,
|
|
16
|
+
* FirestoreModel,
|
|
17
|
+
* } from "@visiolab/effect-firebase/firestore";
|
|
18
|
+
* import { Effect, Schema } from "effect";
|
|
19
|
+
*
|
|
20
|
+
* class User extends FirestoreModel.Class<User>("User")({ name: Schema.String }) {}
|
|
21
|
+
* class Post extends FirestoreModel.Class<Post>("Post")({ title: Schema.String }) {}
|
|
22
|
+
*
|
|
23
|
+
* const Users = Collection.make("users", User);
|
|
24
|
+
* const UserPosts = Users.subcollection("posts", Post);
|
|
25
|
+
* const AllPosts = CollectionGroup.make(UserPosts);
|
|
26
|
+
*
|
|
27
|
+
* const program = Effect.gen(function* () {
|
|
28
|
+
* const firestore = yield* Firestore.Firestore;
|
|
29
|
+
* const snapshot = yield* firestore.getDocs(AllPosts);
|
|
30
|
+
* return snapshot.docs.map((post) => post.ref.path);
|
|
31
|
+
* });
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* @see {@link Collection} for defining the collections a group is made of
|
|
35
|
+
*
|
|
36
|
+
* @stability unstable
|
|
37
|
+
* @since 0.2.0
|
|
38
|
+
*/
|
|
39
|
+
import type { Inspectable, Result } from "effect";
|
|
40
|
+
import type * as Collection from "./Collection.ts";
|
|
41
|
+
import type { UndefinedCollectionError } from "./Firestore.ts";
|
|
42
|
+
import * as collectionGroup from "./internal/collectionGroup.ts";
|
|
43
|
+
|
|
44
|
+
const TypeId = collectionGroup.TypeId;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The definition of a Collection Group: the collections in `Members`, which
|
|
48
|
+
* all share one collection id.
|
|
49
|
+
*
|
|
50
|
+
* **Details**
|
|
51
|
+
*
|
|
52
|
+
* Like a {@link Collection.Collection}, a group does not point at any data. It
|
|
53
|
+
* has no ids to fill in, so {@link Firestore.With.getDocs} and
|
|
54
|
+
* {@link Firestore.With.streamDocs} take it as it is. Build one with
|
|
55
|
+
* {@link make}.
|
|
56
|
+
*
|
|
57
|
+
* @see {@link Collection.CollectionReference} for reading one collection, under one parent
|
|
58
|
+
*
|
|
59
|
+
* @stability unstable
|
|
60
|
+
* @category models
|
|
61
|
+
* @since 0.2.0
|
|
62
|
+
*/
|
|
63
|
+
export interface CollectionGroup<Members extends Collection.Any> extends Inspectable.Inspectable {
|
|
64
|
+
readonly [TypeId]: typeof TypeId;
|
|
65
|
+
/** The collections it was defined from, in the order they were given. */
|
|
66
|
+
readonly members: ReadonlyArray<Members>;
|
|
67
|
+
/** The collection id its members share, which Firestore reads the group by. */
|
|
68
|
+
readonly id: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Any Collection Group, whatever collections it is made of. Use it as a type
|
|
73
|
+
* bound.
|
|
74
|
+
*
|
|
75
|
+
* @stability unstable
|
|
76
|
+
* @category models
|
|
77
|
+
* @since 0.2.0
|
|
78
|
+
*/
|
|
79
|
+
export type Any = CollectionGroup<any>;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* The collections `G` was defined from, as a union.
|
|
83
|
+
*
|
|
84
|
+
* @stability unstable
|
|
85
|
+
* @category models
|
|
86
|
+
* @since 0.2.0
|
|
87
|
+
*/
|
|
88
|
+
export type Members<G extends Any> = G extends CollectionGroup<infer M> ? M : never;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* A reference to a document in one of the collections of `G`, typed by the one
|
|
92
|
+
* it lives in: `DocumentReference<A> | DocumentReference<B>` rather than a
|
|
93
|
+
* reference to `A | B`.
|
|
94
|
+
*
|
|
95
|
+
* @see {@link Collection.DocumentReference} for a reference into one known collection
|
|
96
|
+
* @see {@link Collection.Collection.contains} for narrowing it to one member
|
|
97
|
+
*
|
|
98
|
+
* @stability unstable
|
|
99
|
+
* @category models
|
|
100
|
+
* @since 0.2.0
|
|
101
|
+
*/
|
|
102
|
+
export type DocumentReference<G extends Any> =
|
|
103
|
+
Members<G> extends infer C extends Collection.Any
|
|
104
|
+
? C extends Collection.Any
|
|
105
|
+
? Collection.DocumentReference<C>
|
|
106
|
+
: never
|
|
107
|
+
: never;
|
|
108
|
+
|
|
109
|
+
/** Whether `A` and `B` are the same type, rather than one fitting the other. */
|
|
110
|
+
type Same<A, B> =
|
|
111
|
+
(<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2 ? true : false;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Whether `A` and `B` are the same path. A path with a segment only known as
|
|
115
|
+
* `string` could be any path, so it is never taken for another here.
|
|
116
|
+
*/
|
|
117
|
+
type SamePath<A extends ReadonlyArray<string>, B extends ReadonlyArray<string>> = string extends
|
|
118
|
+
| A[number]
|
|
119
|
+
| B[number]
|
|
120
|
+
? false
|
|
121
|
+
: Same<A, B>;
|
|
122
|
+
|
|
123
|
+
/** Whether a member of `Members` other than the one at `K` sits at `C`'s path. */
|
|
124
|
+
type PathTakenElsewhere<
|
|
125
|
+
Members extends ReadonlyArray<Collection.Any>,
|
|
126
|
+
K,
|
|
127
|
+
C extends Collection.Any,
|
|
128
|
+
> = true extends {
|
|
129
|
+
[J in keyof Members]: J extends K ? false : SamePath<Members[J]["path"], C["path"]>;
|
|
130
|
+
}[number]
|
|
131
|
+
? true
|
|
132
|
+
: false;
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* The collections {@link make} accepts as `Members`: each one as it is, or, in
|
|
136
|
+
* place of one that breaks a rule, a message naming the rule. Every member must
|
|
137
|
+
* share the first one's collection id, and no two may sit at the same path.
|
|
138
|
+
*
|
|
139
|
+
* **Details**
|
|
140
|
+
*
|
|
141
|
+
* Members are told apart by their paths, so the same collection listed twice
|
|
142
|
+
* and two definitions built separately at the same path are both refused: a
|
|
143
|
+
* document at that path could belong to either. A path with a segment typed
|
|
144
|
+
* only as `string` is not checked here; {@link make} checks it at runtime.
|
|
145
|
+
*
|
|
146
|
+
* @stability unstable
|
|
147
|
+
* @category models
|
|
148
|
+
* @since 0.2.0
|
|
149
|
+
*/
|
|
150
|
+
export type Valid<Members extends ReadonlyArray<Collection.Any>> = {
|
|
151
|
+
readonly [K in keyof Members]: Members[K] extends Collection.Any
|
|
152
|
+
? Members[K]["segment"] extends Members[0]["segment"]
|
|
153
|
+
? PathTakenElsewhere<Members, K, Members[K]> extends true
|
|
154
|
+
? `Collection Group member '${Members[K]["segment"]}' has the same path as another member`
|
|
155
|
+
: Members[K]
|
|
156
|
+
: `Collection Group member '${Members[K]["segment"]}' does not share the collection id '${Members[0]["segment"]}'`
|
|
157
|
+
: never;
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Checks whether a value is a {@link CollectionGroup}.
|
|
162
|
+
*
|
|
163
|
+
* @see {@link Collection.isCollection} for a collection definition
|
|
164
|
+
* @see {@link Collection.isCollectionReference} for a reference to one collection
|
|
165
|
+
*
|
|
166
|
+
* @stability unstable
|
|
167
|
+
* @category guards
|
|
168
|
+
* @since 0.2.0
|
|
169
|
+
*/
|
|
170
|
+
export const isCollectionGroup: (u: unknown) => u is Any = collectionGroup.isCollectionGroup;
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Defines a Collection Group from the collections it covers.
|
|
174
|
+
*
|
|
175
|
+
* **When to use**
|
|
176
|
+
*
|
|
177
|
+
* Use to read or listen to documents across every parent at once, such as
|
|
178
|
+
* every user's posts. To read the collection under one parent, pass a
|
|
179
|
+
* {@link Collection.CollectionReference} to {@link Firestore.With.getDocs}
|
|
180
|
+
* instead.
|
|
181
|
+
*
|
|
182
|
+
* **Details**
|
|
183
|
+
*
|
|
184
|
+
* List every collection with the collection id you want to read. A read of
|
|
185
|
+
* the group fails with an `UndefinedCollectionError` when it finds a document
|
|
186
|
+
* under that collection id at a path none of the members define.
|
|
187
|
+
*
|
|
188
|
+
* The type checker refuses members that do not share the first one's
|
|
189
|
+
* collection id, and two members at the same path. The same checks run at
|
|
190
|
+
* runtime: it throws when given no collection, members with different
|
|
191
|
+
* collection ids, or two members at the same path, whether the same one twice
|
|
192
|
+
* or two built separately. An invalid definition is a programming error, as
|
|
193
|
+
* for {@link Collection.make}.
|
|
194
|
+
*
|
|
195
|
+
* @example Grouping collections under different parents
|
|
196
|
+
* ```ts
|
|
197
|
+
* import {
|
|
198
|
+
* Collection,
|
|
199
|
+
* CollectionGroup,
|
|
200
|
+
* Firestore,
|
|
201
|
+
* FirestoreModel,
|
|
202
|
+
* } from "@visiolab/effect-firebase/firestore";
|
|
203
|
+
* import { Effect, Schema } from "effect";
|
|
204
|
+
*
|
|
205
|
+
* class User extends FirestoreModel.Class<User>("User")({ name: Schema.String }) {}
|
|
206
|
+
* class Org extends FirestoreModel.Class<Org>("Org")({ name: Schema.String }) {}
|
|
207
|
+
* class Post extends FirestoreModel.Class<Post>("Post")({ title: Schema.String }) {}
|
|
208
|
+
*
|
|
209
|
+
* const UserPosts = Collection.make("users", User).subcollection("posts", Post);
|
|
210
|
+
* const OrgPosts = Collection.make("orgs", Org).subcollection("posts", Post);
|
|
211
|
+
* const AllPosts = CollectionGroup.make(UserPosts, OrgPosts);
|
|
212
|
+
*
|
|
213
|
+
* const program = Effect.gen(function* () {
|
|
214
|
+
* const firestore = yield* Firestore.Firestore;
|
|
215
|
+
* const snapshot = yield* firestore.getDocs(AllPosts);
|
|
216
|
+
* // Only the posts under a user.
|
|
217
|
+
* return snapshot.docs.filter(UserPosts.contains);
|
|
218
|
+
* });
|
|
219
|
+
* ```
|
|
220
|
+
*
|
|
221
|
+
* @see {@link Collection.make} for defining the member collections
|
|
222
|
+
*
|
|
223
|
+
* @stability unstable
|
|
224
|
+
* @category constructors
|
|
225
|
+
* @since 0.2.0
|
|
226
|
+
*/
|
|
227
|
+
export const make: <const Members extends readonly [Collection.Any, ...Array<Collection.Any>]>(
|
|
228
|
+
...members: Valid<Members>
|
|
229
|
+
) => CollectionGroup<Members[number]> = collectionGroup.make;
|
|
230
|
+
|
|
231
|
+
// How the SDK packages match each document a group read finds to its
|
|
232
|
+
// collection. Not API: they reach it here because a package can only import
|
|
233
|
+
// another's public modules.
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* References the document at `path` in whichever collection of `group` sits
|
|
237
|
+
* there, or fails with the {@link UndefinedCollectionError} a group read
|
|
238
|
+
* reports for a document none of them define.
|
|
239
|
+
*
|
|
240
|
+
* @internal
|
|
241
|
+
*/
|
|
242
|
+
export const documentAt: <G extends Any>(
|
|
243
|
+
group: G,
|
|
244
|
+
path: string,
|
|
245
|
+
) => Result.Result<DocumentReference<G>, UndefinedCollectionError> = collectionGroup.documentAt;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Untyped references to Firestore documents, independent of either SDK.
|
|
3
|
+
*
|
|
4
|
+
* Firestore stores a reference to a document as the document's path alone,
|
|
5
|
+
* and each SDK reads it back as its own `DocumentReference` class. This module
|
|
6
|
+
* owns a portable version of that value: the Firestore Value a reference is
|
|
7
|
+
* written and read as.
|
|
8
|
+
*
|
|
9
|
+
* A `DocumentPath` does not know which collection it points into, or what the
|
|
10
|
+
* document holds. A {@link Collection.DocumentReference} does.
|
|
11
|
+
*
|
|
12
|
+
* @see {@link FirestoreSchema.DocumentPath} for storing an untyped reference in a document
|
|
13
|
+
* @see {@link FirestoreSchema.DocumentReference} for storing a typed reference in a document
|
|
14
|
+
*
|
|
15
|
+
* @stability unstable
|
|
16
|
+
* @since 0.2.0
|
|
17
|
+
*/
|
|
18
|
+
import type { Equal, Inspectable, Option } from "effect";
|
|
19
|
+
import * as documentPath from "./internal/documentPath.ts";
|
|
20
|
+
|
|
21
|
+
const TypeId = documentPath.TypeId;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A reference to a document as Firestore stores it: the document's path, such
|
|
25
|
+
* as `users/alice/posts/hello`.
|
|
26
|
+
*
|
|
27
|
+
* **When to use**
|
|
28
|
+
*
|
|
29
|
+
* Use for a reference whose target collection the model does not know, or
|
|
30
|
+
* does not care about. Prefer {@link Collection.DocumentReference}, through
|
|
31
|
+
* {@link FirestoreSchema.DocumentReference}, when the field always points into
|
|
32
|
+
* a known collection: it keeps the target's ids and model typed.
|
|
33
|
+
*
|
|
34
|
+
* **Details**
|
|
35
|
+
*
|
|
36
|
+
* Two are equal when their paths are.
|
|
37
|
+
*
|
|
38
|
+
* @see {@link Collection.DocumentReference} for a reference that knows its collection and model
|
|
39
|
+
* @see {@link FirestoreSchema.DocumentPath} for the schema of this value
|
|
40
|
+
*
|
|
41
|
+
* @stability unstable
|
|
42
|
+
* @category models
|
|
43
|
+
* @since 0.2.0
|
|
44
|
+
*/
|
|
45
|
+
export interface DocumentPath extends Equal.Equal, Inspectable.Inspectable {
|
|
46
|
+
readonly [TypeId]: typeof TypeId;
|
|
47
|
+
/** The path of the document, from the root of the database. */
|
|
48
|
+
readonly path: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Checks whether `u` is a {@link DocumentPath} built by this module.
|
|
53
|
+
*
|
|
54
|
+
* An SDK's own `DocumentReference` is not one.
|
|
55
|
+
*
|
|
56
|
+
* @stability unstable
|
|
57
|
+
* @category guards
|
|
58
|
+
* @since 0.2.0
|
|
59
|
+
*/
|
|
60
|
+
export const isDocumentPath: (u: unknown) => u is DocumentPath = documentPath.isDocumentPath;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Builds a reference to the document at `path`, or `None` when `path` does not
|
|
64
|
+
* address a document.
|
|
65
|
+
*
|
|
66
|
+
* **When to use**
|
|
67
|
+
*
|
|
68
|
+
* Use when the path comes from outside the program and may be invalid.
|
|
69
|
+
*
|
|
70
|
+
* **Details**
|
|
71
|
+
*
|
|
72
|
+
* Returns `None` when `path` is empty, has an empty segment, or has an odd
|
|
73
|
+
* number of segments and so ends in a collection.
|
|
74
|
+
*
|
|
75
|
+
* @example Building a reference from untrusted input
|
|
76
|
+
* ```ts
|
|
77
|
+
* import { DocumentPath } from "@visiolab/effect-firebase/firestore";
|
|
78
|
+
*
|
|
79
|
+
* DocumentPath.make("users/alice"); // Option.some(...)
|
|
80
|
+
* DocumentPath.make("users"); // Option.none()
|
|
81
|
+
* ```
|
|
82
|
+
*
|
|
83
|
+
* @see {@link makeUnsafe} for the same check throwing instead
|
|
84
|
+
* @see {@link Collection.Collection} for typed references built from ids
|
|
85
|
+
*
|
|
86
|
+
* @stability unstable
|
|
87
|
+
* @category constructors
|
|
88
|
+
* @since 0.2.0
|
|
89
|
+
*/
|
|
90
|
+
export const make: (path: string) => Option.Option<DocumentPath> = documentPath.make;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Builds a reference to the document at `path`, throwing when `path` does not
|
|
94
|
+
* address a document.
|
|
95
|
+
*
|
|
96
|
+
* **When to use**
|
|
97
|
+
*
|
|
98
|
+
* Use for paths known to be valid, such as constants. Use {@link make} for
|
|
99
|
+
* paths you have not checked.
|
|
100
|
+
*
|
|
101
|
+
* @example Building a reference
|
|
102
|
+
* ```ts
|
|
103
|
+
* import { DocumentPath } from "@visiolab/effect-firebase/firestore";
|
|
104
|
+
*
|
|
105
|
+
* const post = DocumentPath.makeUnsafe("users/alice/posts/hello");
|
|
106
|
+
* ```
|
|
107
|
+
*
|
|
108
|
+
* @see {@link make} for the same check returning an `Option`
|
|
109
|
+
*
|
|
110
|
+
* @stability unstable
|
|
111
|
+
* @category constructors
|
|
112
|
+
* @since 0.2.0
|
|
113
|
+
*/
|
|
114
|
+
export const makeUnsafe: (path: string) => DocumentPath = documentPath.makeUnsafe;
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a read of one document returns.
|
|
3
|
+
*
|
|
4
|
+
* A document's id is in its path, not in its data, so the data alone cannot
|
|
5
|
+
* say which document it is. A snapshot pairs the two: the reference the
|
|
6
|
+
* document was read at, and what it held, or that it was not there.
|
|
7
|
+
*
|
|
8
|
+
* The data is kept as the SDK read it, and only decoded through the
|
|
9
|
+
* collection's model when you ask for it. Checking whether the document is
|
|
10
|
+
* there, or which one it is, never decodes, and a document that does not match
|
|
11
|
+
* the model only fails the code that reads its data.
|
|
12
|
+
*
|
|
13
|
+
* @example Reading a document that may be missing
|
|
14
|
+
* ```ts
|
|
15
|
+
* import {
|
|
16
|
+
* Collection,
|
|
17
|
+
* DocumentSnapshot,
|
|
18
|
+
* Firestore,
|
|
19
|
+
* FirestoreModel,
|
|
20
|
+
* } from "@visiolab/effect-firebase/firestore";
|
|
21
|
+
* import { Effect, Option, Schema } from "effect";
|
|
22
|
+
*
|
|
23
|
+
* class User extends FirestoreModel.Class<User>("User")({ name: Schema.String }) {}
|
|
24
|
+
*
|
|
25
|
+
* const Users = Collection.make("users", User);
|
|
26
|
+
*
|
|
27
|
+
* const program = Effect.gen(function* () {
|
|
28
|
+
* const firestore = yield* Firestore.Firestore;
|
|
29
|
+
* const snapshot = yield* firestore.getDoc(Users.doc(Users.id.make("alice")));
|
|
30
|
+
* if (DocumentSnapshot.isMissing(snapshot)) return Option.none();
|
|
31
|
+
* const user = yield* snapshot.data;
|
|
32
|
+
* return Option.some(user.name);
|
|
33
|
+
* });
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* @see {@link QuerySnapshot} for what a read of many documents returns
|
|
37
|
+
*
|
|
38
|
+
* @stability unstable
|
|
39
|
+
* @since 0.2.0
|
|
40
|
+
*/
|
|
41
|
+
import type { Effect, Inspectable, Schema } from "effect";
|
|
42
|
+
import type * as Collection from "./Collection.ts";
|
|
43
|
+
import * as documentSnapshot from "./internal/documentSnapshot.ts";
|
|
44
|
+
|
|
45
|
+
const TypeId = documentSnapshot.TypeId;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* What a document in `C` decodes into: the `read` variant of its model.
|
|
49
|
+
*
|
|
50
|
+
* @stability unstable
|
|
51
|
+
* @category models
|
|
52
|
+
* @since 0.2.0
|
|
53
|
+
*/
|
|
54
|
+
export type Data<C extends Collection.Any> = Collection.DocumentSchema<C>["Type"];
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The services decoding a document in `C` requires, which
|
|
58
|
+
* {@link Existing.data} asks for.
|
|
59
|
+
*
|
|
60
|
+
* @stability unstable
|
|
61
|
+
* @category models
|
|
62
|
+
* @since 0.2.0
|
|
63
|
+
*/
|
|
64
|
+
export type DecodingServices<C extends Collection.Any> =
|
|
65
|
+
Collection.DocumentSchema<C>["DecodingServices"];
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* A snapshot of a document that was there when it was read.
|
|
69
|
+
*
|
|
70
|
+
* **Details**
|
|
71
|
+
*
|
|
72
|
+
* Its tag is `"Existing"`. {@link Existing.data} decodes what it held through
|
|
73
|
+
* the collection's model. Every document of a {@link QuerySnapshot.QuerySnapshot}
|
|
74
|
+
* is one of these.
|
|
75
|
+
*
|
|
76
|
+
* @see {@link Missing} for a document that was not there
|
|
77
|
+
* @see {@link isExisting} for narrowing a {@link DocumentSnapshot} to this
|
|
78
|
+
*
|
|
79
|
+
* @stability unstable
|
|
80
|
+
* @category models
|
|
81
|
+
* @since 0.2.0
|
|
82
|
+
*/
|
|
83
|
+
export interface Existing<C extends Collection.Any> extends Inspectable.Inspectable {
|
|
84
|
+
readonly [TypeId]: typeof TypeId;
|
|
85
|
+
readonly _tag: "Existing";
|
|
86
|
+
/** The reference the document was read at. */
|
|
87
|
+
readonly ref: Collection.DocumentReference<C>;
|
|
88
|
+
/** The document's own id, the same as `ref.id`. */
|
|
89
|
+
readonly id: Collection.Id<C>;
|
|
90
|
+
/**
|
|
91
|
+
* The document's data as the SDK read it, before the collection's model has
|
|
92
|
+
* decoded it.
|
|
93
|
+
*
|
|
94
|
+
* **Details**
|
|
95
|
+
*
|
|
96
|
+
* It is what the document held at the time of the read. Do not change it: a
|
|
97
|
+
* snapshot is a record of that read, not a handle on the document.
|
|
98
|
+
*
|
|
99
|
+
* @see {@link Existing.data} for the decoded data
|
|
100
|
+
*/
|
|
101
|
+
readonly raw: unknown;
|
|
102
|
+
/**
|
|
103
|
+
* Decodes {@link Existing.raw} through the collection's model.
|
|
104
|
+
*
|
|
105
|
+
* **Details**
|
|
106
|
+
*
|
|
107
|
+
* Nothing is decoded until this runs, and it decodes again on each run.
|
|
108
|
+
* Fails with a `Schema.SchemaError` when the data does not match the model.
|
|
109
|
+
*/
|
|
110
|
+
readonly data: Effect.Effect<Data<C>, Schema.SchemaError, DecodingServices<C>>;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* A snapshot of a document that was not there when it was read.
|
|
115
|
+
*
|
|
116
|
+
* **Details**
|
|
117
|
+
*
|
|
118
|
+
* Its tag is `"Missing"`. It holds no data, so a missing document is never
|
|
119
|
+
* confused with one that failed to decode.
|
|
120
|
+
*
|
|
121
|
+
* @see {@link Existing} for a document that was there
|
|
122
|
+
* @see {@link isMissing} for narrowing a {@link DocumentSnapshot} to this
|
|
123
|
+
*
|
|
124
|
+
* @stability unstable
|
|
125
|
+
* @category models
|
|
126
|
+
* @since 0.2.0
|
|
127
|
+
*/
|
|
128
|
+
export interface Missing<C extends Collection.Any> extends Inspectable.Inspectable {
|
|
129
|
+
readonly [TypeId]: typeof TypeId;
|
|
130
|
+
readonly _tag: "Missing";
|
|
131
|
+
/** The reference the document was read at. */
|
|
132
|
+
readonly ref: Collection.DocumentReference<C>;
|
|
133
|
+
/** The document's own id, the same as `ref.id`. */
|
|
134
|
+
readonly id: Collection.Id<C>;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* A document in `C` as a read found it: {@link Existing} or {@link Missing}.
|
|
139
|
+
*
|
|
140
|
+
* **Details**
|
|
141
|
+
*
|
|
142
|
+
* {@link Firestore.With.getDoc} and {@link Firestore.With.streamDoc} return
|
|
143
|
+
* one. A missing document is a result, not a failure. Tell the two apart with
|
|
144
|
+
* {@link isExisting} or {@link isMissing}, or by `_tag`.
|
|
145
|
+
*
|
|
146
|
+
* @see {@link QuerySnapshot.QuerySnapshot} for the result of a read of many documents
|
|
147
|
+
*
|
|
148
|
+
* @stability unstable
|
|
149
|
+
* @category models
|
|
150
|
+
* @since 0.2.0
|
|
151
|
+
*/
|
|
152
|
+
export type DocumentSnapshot<C extends Collection.Any> = Existing<C> | Missing<C>;
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Checks whether a value is a {@link DocumentSnapshot}, existing or missing.
|
|
156
|
+
*
|
|
157
|
+
* @see {@link isExisting} and {@link isMissing} for telling the two kinds apart
|
|
158
|
+
* @see {@link QuerySnapshot.isQuerySnapshot} for a snapshot of many documents
|
|
159
|
+
*
|
|
160
|
+
* @stability unstable
|
|
161
|
+
* @category guards
|
|
162
|
+
* @since 0.2.0
|
|
163
|
+
*/
|
|
164
|
+
export const isDocumentSnapshot: (u: unknown) => u is DocumentSnapshot<Collection.Any> =
|
|
165
|
+
documentSnapshot.isDocumentSnapshot;
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Narrows a {@link DocumentSnapshot} to an {@link Existing} one.
|
|
169
|
+
*
|
|
170
|
+
* **When to use**
|
|
171
|
+
*
|
|
172
|
+
* Use after {@link Firestore.With.getDoc} to get at the data of a document
|
|
173
|
+
* that was there.
|
|
174
|
+
*
|
|
175
|
+
* @see {@link isMissing} for the opposite check
|
|
176
|
+
*
|
|
177
|
+
* @stability unstable
|
|
178
|
+
* @category guards
|
|
179
|
+
* @since 0.2.0
|
|
180
|
+
*/
|
|
181
|
+
export const isExisting: <C extends Collection.Any>(
|
|
182
|
+
self: DocumentSnapshot<C>,
|
|
183
|
+
) => self is Existing<C> = documentSnapshot.isExisting;
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Narrows a {@link DocumentSnapshot} to a {@link Missing} one.
|
|
187
|
+
*
|
|
188
|
+
* **When to use**
|
|
189
|
+
*
|
|
190
|
+
* Use after {@link Firestore.With.getDoc} to handle a document that was not
|
|
191
|
+
* there, such as returning early.
|
|
192
|
+
*
|
|
193
|
+
* @see {@link isExisting} for the opposite check
|
|
194
|
+
*
|
|
195
|
+
* @stability unstable
|
|
196
|
+
* @category guards
|
|
197
|
+
* @since 0.2.0
|
|
198
|
+
*/
|
|
199
|
+
export const isMissing: <C extends Collection.Any>(
|
|
200
|
+
self: DocumentSnapshot<C>,
|
|
201
|
+
) => self is Missing<C> = documentSnapshot.isMissing;
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Builds a snapshot of the document at `ref` from what an SDK's
|
|
205
|
+
* `snapshot.data()` returned: {@link Missing} for `undefined`, and
|
|
206
|
+
* {@link Existing} otherwise.
|
|
207
|
+
*
|
|
208
|
+
* **When to use**
|
|
209
|
+
*
|
|
210
|
+
* Use when implementing a backend or a test double, to turn an SDK snapshot
|
|
211
|
+
* into a portable one. Application code gets snapshots from
|
|
212
|
+
* {@link Firestore.With.getDoc} instead.
|
|
213
|
+
*
|
|
214
|
+
* **Details**
|
|
215
|
+
*
|
|
216
|
+
* Both SDKs return `undefined` from `data()` for a document that is not there.
|
|
217
|
+
* An SDK that knows more about the read, such as the client's `metadata`,
|
|
218
|
+
* passes it as `extra`: each of its fields is put on the snapshot and shown
|
|
219
|
+
* when it prints. Its fields must not have the same names as the snapshot's
|
|
220
|
+
* own.
|
|
221
|
+
*
|
|
222
|
+
* @example Building a snapshot with extra fields
|
|
223
|
+
* ```ts
|
|
224
|
+
* import {
|
|
225
|
+
* Collection,
|
|
226
|
+
* DocumentSnapshot,
|
|
227
|
+
* FirestoreModel,
|
|
228
|
+
* } from "@visiolab/effect-firebase/firestore";
|
|
229
|
+
* import { Schema } from "effect";
|
|
230
|
+
*
|
|
231
|
+
* class User extends FirestoreModel.Class<User>("User")({ name: Schema.String }) {}
|
|
232
|
+
*
|
|
233
|
+
* const Users = Collection.make("users", User);
|
|
234
|
+
* const ref = Users.doc(Users.id.make("alice"));
|
|
235
|
+
*
|
|
236
|
+
* const snapshot = DocumentSnapshot.make(ref, { name: "Alice" }, {
|
|
237
|
+
* metadata: { fromCache: false, hasPendingWrites: false },
|
|
238
|
+
* });
|
|
239
|
+
*
|
|
240
|
+
* snapshot.metadata.fromCache; // false
|
|
241
|
+
* ```
|
|
242
|
+
*
|
|
243
|
+
* @see {@link existing} for a document known to be there
|
|
244
|
+
* @see {@link missing} for a document known not to be there
|
|
245
|
+
*
|
|
246
|
+
* @stability unstable
|
|
247
|
+
* @category constructors
|
|
248
|
+
* @since 0.2.0
|
|
249
|
+
*/
|
|
250
|
+
export const make: {
|
|
251
|
+
<C extends Collection.Any>(
|
|
252
|
+
ref: Collection.DocumentReference<C>,
|
|
253
|
+
data: unknown,
|
|
254
|
+
): DocumentSnapshot<C>;
|
|
255
|
+
<C extends Collection.Any, E extends object>(
|
|
256
|
+
ref: Collection.DocumentReference<C>,
|
|
257
|
+
data: unknown,
|
|
258
|
+
extra: E,
|
|
259
|
+
): DocumentSnapshot<C> & E;
|
|
260
|
+
} = documentSnapshot.make;
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Builds an {@link Existing} snapshot of the document at `ref`, with the
|
|
264
|
+
* fields of `extra` when given, as {@link make} adds them.
|
|
265
|
+
*
|
|
266
|
+
* **When to use**
|
|
267
|
+
*
|
|
268
|
+
* Use in a backend for a document known to be there, such as each document a
|
|
269
|
+
* query returned.
|
|
270
|
+
*
|
|
271
|
+
* @see {@link make} for a document that may or may not be there
|
|
272
|
+
* @see {@link missing} for a document known not to be there
|
|
273
|
+
*
|
|
274
|
+
* @stability unstable
|
|
275
|
+
* @category constructors
|
|
276
|
+
* @since 0.2.0
|
|
277
|
+
*/
|
|
278
|
+
export const existing: {
|
|
279
|
+
<C extends Collection.Any>(ref: Collection.DocumentReference<C>, data: unknown): Existing<C>;
|
|
280
|
+
<C extends Collection.Any, E extends object>(
|
|
281
|
+
ref: Collection.DocumentReference<C>,
|
|
282
|
+
data: unknown,
|
|
283
|
+
extra: E,
|
|
284
|
+
): Existing<C> & E;
|
|
285
|
+
} = documentSnapshot.existing;
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Builds a {@link Missing} snapshot of the document at `ref`, with the fields
|
|
289
|
+
* of `extra` when given, as {@link make} adds them.
|
|
290
|
+
*
|
|
291
|
+
* **When to use**
|
|
292
|
+
*
|
|
293
|
+
* Use in a backend or a test double for a document known not to be there.
|
|
294
|
+
*
|
|
295
|
+
* @see {@link make} for a document that may or may not be there
|
|
296
|
+
* @see {@link existing} for a document known to be there
|
|
297
|
+
*
|
|
298
|
+
* @stability unstable
|
|
299
|
+
* @category constructors
|
|
300
|
+
* @since 0.2.0
|
|
301
|
+
*/
|
|
302
|
+
export const missing: {
|
|
303
|
+
<C extends Collection.Any>(ref: Collection.DocumentReference<C>): Missing<C>;
|
|
304
|
+
<C extends Collection.Any, E extends object>(
|
|
305
|
+
ref: Collection.DocumentReference<C>,
|
|
306
|
+
extra: E,
|
|
307
|
+
): Missing<C> & E;
|
|
308
|
+
} = documentSnapshot.missing;
|