@visiolab/effect-firebase 0.0.0-stage → 0.3.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 (140) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +81 -2
  3. package/dist/_virtual/_rolldown/runtime.mjs +12 -0
  4. package/dist/app/App.d.mts +27 -0
  5. package/dist/app/App.mjs +27 -0
  6. package/dist/app/index.d.mts +2 -0
  7. package/dist/app/index.mjs +1 -0
  8. package/dist/firestore/Collection.d.mts +423 -0
  9. package/dist/firestore/Collection.mjs +133 -0
  10. package/dist/firestore/CollectionGroup.d.mts +164 -0
  11. package/dist/firestore/CollectionGroup.mjs +84 -0
  12. package/dist/firestore/DocumentPath.d.mts +95 -0
  13. package/dist/firestore/DocumentPath.mjs +71 -0
  14. package/dist/firestore/DocumentSnapshot.d.mts +239 -0
  15. package/dist/firestore/DocumentSnapshot.mjs +134 -0
  16. package/dist/firestore/FieldValue.d.mts +332 -0
  17. package/dist/firestore/FieldValue.mjs +256 -0
  18. package/dist/firestore/Firestore.d.mts +1165 -0
  19. package/dist/firestore/Firestore.mjs +274 -0
  20. package/dist/firestore/FirestoreModel.d.mts +1232 -0
  21. package/dist/firestore/FirestoreModel.mjs +586 -0
  22. package/dist/firestore/FirestoreSchema.d.mts +567 -0
  23. package/dist/firestore/FirestoreSchema.mjs +419 -0
  24. package/dist/firestore/FirestoreValue.d.mts +145 -0
  25. package/dist/firestore/FirestoreValue.mjs +87 -0
  26. package/dist/firestore/GeoPoint.d.mts +90 -0
  27. package/dist/firestore/GeoPoint.mjs +75 -0
  28. package/dist/firestore/Patch.d.mts +73 -0
  29. package/dist/firestore/Patch.mjs +57 -0
  30. package/dist/firestore/QuerySnapshot.d.mts +107 -0
  31. package/dist/firestore/QuerySnapshot.mjs +43 -0
  32. package/dist/firestore/Timestamp.d.mts +273 -0
  33. package/dist/firestore/Timestamp.mjs +255 -0
  34. package/dist/firestore/index.d.mts +14 -0
  35. package/dist/firestore/index.mjs +14 -0
  36. package/dist/firestore/internal/collection.mjs +156 -0
  37. package/dist/firestore/internal/collectionGroup.mjs +64 -0
  38. package/dist/firestore/internal/documentPath.d.mts +4 -0
  39. package/dist/firestore/internal/documentPath.mjs +42 -0
  40. package/dist/firestore/internal/documentSnapshot.mjs +63 -0
  41. package/dist/firestore/internal/fieldValue.d.mts +11 -0
  42. package/dist/firestore/internal/fieldValue.mjs +88 -0
  43. package/dist/firestore/internal/firestore.mjs +145 -0
  44. package/dist/firestore/internal/firestoreError.d.mts +29 -0
  45. package/dist/firestore/internal/firestoreError.mjs +30 -0
  46. package/dist/firestore/internal/firestoreModel.mjs +719 -0
  47. package/dist/firestore/internal/firestoreSchema.mjs +123 -0
  48. package/dist/firestore/internal/firestoreValue.mjs +108 -0
  49. package/dist/firestore/internal/firestoreValueWalk.d.mts +58 -0
  50. package/dist/firestore/internal/firestoreValueWalk.mjs +223 -0
  51. package/dist/firestore/internal/geoPoint.d.mts +4 -0
  52. package/dist/firestore/internal/geoPoint.mjs +39 -0
  53. package/dist/firestore/internal/patch.mjs +19 -0
  54. package/dist/firestore/internal/querySnapshot.mjs +24 -0
  55. package/dist/firestore/internal/schemaAST.mjs +35 -0
  56. package/dist/firestore/internal/snapshotExtra.mjs +35 -0
  57. package/dist/firestore/internal/streamFromListener.mjs +12 -0
  58. package/dist/firestore/internal/timestamp.d.mts +4 -0
  59. package/dist/firestore/internal/timestamp.mjs +66 -0
  60. package/dist/firestore/internal/timestampSchema.mjs +31 -0
  61. package/dist/firestore/internal/transactionFromCallback.mjs +66 -0
  62. package/dist/functions/Callable.d.mts +654 -0
  63. package/dist/functions/Callable.mjs +450 -0
  64. package/dist/functions/CallableError.d.mts +297 -0
  65. package/dist/functions/CallableError.mjs +256 -0
  66. package/dist/functions/CallableMiddleware.d.mts +213 -0
  67. package/dist/functions/CallableMiddleware.mjs +52 -0
  68. package/dist/functions/CallableRequest.d.mts +166 -0
  69. package/dist/functions/CallableRequest.mjs +18 -0
  70. package/dist/functions/Caller.d.mts +103 -0
  71. package/dist/functions/Caller.mjs +115 -0
  72. package/dist/functions/DocumentTrigger.d.mts +795 -0
  73. package/dist/functions/DocumentTrigger.mjs +566 -0
  74. package/dist/functions/FirebaseFunction.d.mts +79 -0
  75. package/dist/functions/FirebaseFunction.mjs +4 -0
  76. package/dist/functions/FunctionGroup.d.mts +175 -0
  77. package/dist/functions/FunctionGroup.mjs +64 -0
  78. package/dist/functions/index.d.mts +9 -0
  79. package/dist/functions/index.mjs +9 -0
  80. package/dist/functions/internal/callable.mjs +167 -0
  81. package/dist/functions/internal/callableMiddleware.d.mts +19 -0
  82. package/dist/functions/internal/callableMiddleware.mjs +15 -0
  83. package/dist/functions/internal/callableRequest.mjs +6 -0
  84. package/dist/functions/internal/documentTrigger.d.mts +10 -0
  85. package/dist/functions/internal/documentTrigger.mjs +73 -0
  86. package/dist/functions/internal/firebaseFunction.d.mts +5 -0
  87. package/dist/functions/internal/firebaseFunction.mjs +24 -0
  88. package/dist/functions/internal/functionGroup.mjs +36 -0
  89. package/package.json +72 -3
  90. package/src/app/App.ts +24 -0
  91. package/src/app/index.ts +7 -0
  92. package/src/firestore/Collection.ts +520 -0
  93. package/src/firestore/CollectionGroup.ts +245 -0
  94. package/src/firestore/DocumentPath.ts +114 -0
  95. package/src/firestore/DocumentSnapshot.ts +308 -0
  96. package/src/firestore/FieldValue.ts +375 -0
  97. package/src/firestore/Firestore.ts +1432 -0
  98. package/src/firestore/FirestoreModel.ts +1884 -0
  99. package/src/firestore/FirestoreSchema.ts +643 -0
  100. package/src/firestore/FirestoreValue.ts +207 -0
  101. package/src/firestore/GeoPoint.ts +105 -0
  102. package/src/firestore/Patch.ts +117 -0
  103. package/src/firestore/QuerySnapshot.ts +150 -0
  104. package/src/firestore/Timestamp.ts +302 -0
  105. package/src/firestore/index.ts +67 -0
  106. package/src/firestore/internal/collection.ts +246 -0
  107. package/src/firestore/internal/collectionGroup.ts +81 -0
  108. package/src/firestore/internal/documentPath.ts +53 -0
  109. package/src/firestore/internal/documentSnapshot.ts +117 -0
  110. package/src/firestore/internal/fieldValue.ts +119 -0
  111. package/src/firestore/internal/firestore.ts +322 -0
  112. package/src/firestore/internal/firestoreError.ts +36 -0
  113. package/src/firestore/internal/firestoreModel.ts +1242 -0
  114. package/src/firestore/internal/firestoreSchema.ts +218 -0
  115. package/src/firestore/internal/firestoreValue.ts +202 -0
  116. package/src/firestore/internal/firestoreValueWalk.ts +349 -0
  117. package/src/firestore/internal/geoPoint.ts +59 -0
  118. package/src/firestore/internal/patch.ts +21 -0
  119. package/src/firestore/internal/querySnapshot.ts +39 -0
  120. package/src/firestore/internal/schemaAST.ts +83 -0
  121. package/src/firestore/internal/snapshotExtra.ts +47 -0
  122. package/src/firestore/internal/streamFromListener.ts +27 -0
  123. package/src/firestore/internal/timestamp.ts +119 -0
  124. package/src/firestore/internal/timestampSchema.ts +42 -0
  125. package/src/firestore/internal/transactionFromCallback.ts +106 -0
  126. package/src/functions/Callable.ts +855 -0
  127. package/src/functions/CallableError.ts +348 -0
  128. package/src/functions/CallableMiddleware.ts +279 -0
  129. package/src/functions/CallableRequest.ts +207 -0
  130. package/src/functions/Caller.ts +141 -0
  131. package/src/functions/DocumentTrigger.ts +927 -0
  132. package/src/functions/FirebaseFunction.ts +96 -0
  133. package/src/functions/FunctionGroup.ts +225 -0
  134. package/src/functions/index.ts +62 -0
  135. package/src/functions/internal/callable.ts +272 -0
  136. package/src/functions/internal/callableMiddleware.ts +54 -0
  137. package/src/functions/internal/callableRequest.ts +8 -0
  138. package/src/functions/internal/documentTrigger.ts +143 -0
  139. package/src/functions/internal/firebaseFunction.ts +58 -0
  140. 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;