@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.
- 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 +72 -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,1884 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Document schemas with one shape per operation.
|
|
3
|
+
*
|
|
4
|
+
* A Firestore document is read in one shape and written in two others: the id
|
|
5
|
+
* lives in the path rather than the data, a `createdAt` the server fills in is
|
|
6
|
+
* read as a timestamp but written as a sentinel, and an update is a map of the
|
|
7
|
+
* fields that change rather than a whole document. This module derives all
|
|
8
|
+
* three shapes from one field declaration, over `VariantSchema` - the
|
|
9
|
+
* machinery behind `effect/schema`'s `Model`, with the variants a document
|
|
10
|
+
* database needs.
|
|
11
|
+
*
|
|
12
|
+
* - {@link Class} defines a document, and {@link Union} a document or field
|
|
13
|
+
* holding one of several shapes.
|
|
14
|
+
* - {@link Timestamp}, {@link Number}, {@link Array}, {@link Record} and
|
|
15
|
+
* {@link Optional} declare fields whose writes take Firestore's sentinels.
|
|
16
|
+
* - {@link Immutable}, {@link Field}, {@link FieldOnly} and {@link FieldExcept}
|
|
17
|
+
* control which variants a field appears in.
|
|
18
|
+
*
|
|
19
|
+
* @example Defining a document
|
|
20
|
+
* ```ts
|
|
21
|
+
* import { Schema } from "effect";
|
|
22
|
+
* import { FirestoreModel, FirestoreSchema } from "@visiolab/effect-firebase/firestore";
|
|
23
|
+
*
|
|
24
|
+
* class User extends FirestoreModel.Class<User>("User")({
|
|
25
|
+
* displayName: Schema.String,
|
|
26
|
+
* signupSource: FirestoreModel.Immutable(Schema.String),
|
|
27
|
+
* createdAt: FirestoreModel.Timestamp(FirestoreSchema.Timestamp),
|
|
28
|
+
* }) {}
|
|
29
|
+
*
|
|
30
|
+
* User; // what a snapshot decodes into
|
|
31
|
+
* User.create; // what `setDoc` writes
|
|
32
|
+
* User.update; // what `updateDoc` writes, whatever the document holds
|
|
33
|
+
* User.updateUnchecked; // what `updateDoc` writes where the caller knows what it holds
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* @see {@link Collection.make} for addressing documents of a model
|
|
37
|
+
* @see {@link FirestoreSchema} for the schemas of Firestore's own values
|
|
38
|
+
*
|
|
39
|
+
* @stability unstable
|
|
40
|
+
* @since 0.2.0
|
|
41
|
+
*/
|
|
42
|
+
import type { Schema, SchemaAST, Struct as Struct_, Types } from "effect";
|
|
43
|
+
import type { VariantSchema } from "effect/schema";
|
|
44
|
+
import type * as FirestoreSchema from "./FirestoreSchema.ts";
|
|
45
|
+
import * as firestoreModel from "./internal/firestoreModel.ts";
|
|
46
|
+
import type * as Patch from "./Patch.ts";
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* The shapes a document is read and written in.
|
|
50
|
+
*
|
|
51
|
+
* **Details**
|
|
52
|
+
*
|
|
53
|
+
* - `read` is what a snapshot's data decodes into.
|
|
54
|
+
* - `create` is what `setDoc` writes: a whole document.
|
|
55
|
+
* - `update` is what `updateDoc` writes: the fields that change.
|
|
56
|
+
*
|
|
57
|
+
* `read` is the default variant, so a {@link Class} itself is the read shape
|
|
58
|
+
* and the write shapes hang off it.
|
|
59
|
+
*
|
|
60
|
+
* @stability unstable
|
|
61
|
+
* @category models
|
|
62
|
+
* @since 0.2.0
|
|
63
|
+
*/
|
|
64
|
+
export type Variant = "read" | "create" | "update";
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Declares a field with a different schema per variant.
|
|
68
|
+
*
|
|
69
|
+
* **When to use**
|
|
70
|
+
*
|
|
71
|
+
* Use when a field is read and written in shapes no ready-made field covers.
|
|
72
|
+
* Leave a variant out to keep the field out of that shape.
|
|
73
|
+
*
|
|
74
|
+
* @example A field written as a sentinel and read as a timestamp
|
|
75
|
+
* ```ts
|
|
76
|
+
* import { FirestoreModel, FirestoreSchema } from "@visiolab/effect-firebase/firestore";
|
|
77
|
+
*
|
|
78
|
+
* class Post extends FirestoreModel.Class<Post>("Post")({
|
|
79
|
+
* publishedAt: FirestoreModel.Field({
|
|
80
|
+
* read: FirestoreSchema.Timestamp,
|
|
81
|
+
* create: FirestoreSchema.ServerTimestamp,
|
|
82
|
+
* }),
|
|
83
|
+
* }) {}
|
|
84
|
+
* ```
|
|
85
|
+
*
|
|
86
|
+
* @see {@link FieldOnly} for the same schema in some variants
|
|
87
|
+
* @see {@link FieldExcept} for the same schema in all but some variants
|
|
88
|
+
* @see {@link Timestamp} for a timestamp the client or the server may set
|
|
89
|
+
*
|
|
90
|
+
* @stability unstable
|
|
91
|
+
* @category constructors
|
|
92
|
+
* @since 0.2.0
|
|
93
|
+
*/
|
|
94
|
+
export const Field = firestoreModel.Field;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Declares a field with one schema that appears only in the named variants.
|
|
98
|
+
*
|
|
99
|
+
* **When to use**
|
|
100
|
+
*
|
|
101
|
+
* Use for a field only some shapes carry, such as one only ever read, or one
|
|
102
|
+
* only ever written.
|
|
103
|
+
*
|
|
104
|
+
* @example A field only read
|
|
105
|
+
* ```ts
|
|
106
|
+
* import { Schema } from "effect";
|
|
107
|
+
* import { FirestoreModel } from "@visiolab/effect-firebase/firestore";
|
|
108
|
+
*
|
|
109
|
+
* class Account extends FirestoreModel.Class<Account>("Account")({
|
|
110
|
+
* plan: FirestoreModel.FieldOnly(["read"])(Schema.Literals(["free", "paid"])),
|
|
111
|
+
* }) {}
|
|
112
|
+
* ```
|
|
113
|
+
*
|
|
114
|
+
* @see {@link FieldExcept} for naming the variants to leave out instead
|
|
115
|
+
* @see {@link Field} for a different schema per variant
|
|
116
|
+
* @see {@link Immutable} for a field read and created, but never updated
|
|
117
|
+
*
|
|
118
|
+
* @stability unstable
|
|
119
|
+
* @category constructors
|
|
120
|
+
* @since 0.2.0
|
|
121
|
+
*/
|
|
122
|
+
export const FieldOnly = firestoreModel.FieldOnly;
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Declares a field with one schema that appears in every variant but the named
|
|
126
|
+
* ones.
|
|
127
|
+
*
|
|
128
|
+
* **When to use**
|
|
129
|
+
*
|
|
130
|
+
* Use when a field belongs in most shapes and only a few leave it out.
|
|
131
|
+
*
|
|
132
|
+
* @see {@link FieldOnly} for naming the variants to keep instead
|
|
133
|
+
* @see {@link Field} for a different schema per variant
|
|
134
|
+
*
|
|
135
|
+
* @stability unstable
|
|
136
|
+
* @category constructors
|
|
137
|
+
* @since 0.2.0
|
|
138
|
+
*/
|
|
139
|
+
export const FieldExcept = firestoreModel.FieldExcept;
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Declares a set of fields without building a class.
|
|
143
|
+
*
|
|
144
|
+
* **When to use**
|
|
145
|
+
*
|
|
146
|
+
* Use for a nested map inside a document, or for fields shared between several
|
|
147
|
+
* documents. An update reaches into a struct field by field, unlike a plain
|
|
148
|
+
* `Schema.Struct`, which is a value written whole.
|
|
149
|
+
*
|
|
150
|
+
* @example A nested address
|
|
151
|
+
* ```ts
|
|
152
|
+
* import { Schema } from "effect";
|
|
153
|
+
* import { FirestoreModel } from "@visiolab/effect-firebase/firestore";
|
|
154
|
+
*
|
|
155
|
+
* const Address = FirestoreModel.Struct({ city: Schema.String, zip: Schema.String });
|
|
156
|
+
*
|
|
157
|
+
* class Customer extends FirestoreModel.Class<Customer>("Customer")({
|
|
158
|
+
* address: Address,
|
|
159
|
+
* }) {}
|
|
160
|
+
*
|
|
161
|
+
* Customer.update.make({ address: { city: "Berlin" } }); // written at "address.city"
|
|
162
|
+
* ```
|
|
163
|
+
*
|
|
164
|
+
* @see {@link Class} for a whole document
|
|
165
|
+
* @see {@link Patch.replace} for writing a struct whole in an update
|
|
166
|
+
*
|
|
167
|
+
* @stability unstable
|
|
168
|
+
* @category constructors
|
|
169
|
+
* @since 0.2.0
|
|
170
|
+
*/
|
|
171
|
+
export const Struct = firestoreModel.Struct;
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Rewrites the schemas of a field, variant by variant.
|
|
175
|
+
*
|
|
176
|
+
* **When to use**
|
|
177
|
+
*
|
|
178
|
+
* Use to derive a field from another one, such as narrowing what one variant
|
|
179
|
+
* takes while keeping the others.
|
|
180
|
+
*
|
|
181
|
+
* @see {@link Field} for declaring a field per variant directly
|
|
182
|
+
*
|
|
183
|
+
* @stability unstable
|
|
184
|
+
* @category transforming
|
|
185
|
+
* @since 0.2.0
|
|
186
|
+
*/
|
|
187
|
+
export const fieldEvolve = firestoreModel.fieldEvolve;
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Pulls one variant out of a struct as a plain schema.
|
|
191
|
+
*
|
|
192
|
+
* **When to use**
|
|
193
|
+
*
|
|
194
|
+
* Use to get a struct's `read` or `create` shape as an ordinary schema, for
|
|
195
|
+
* example to validate data outside Firestore.
|
|
196
|
+
*
|
|
197
|
+
* **Details**
|
|
198
|
+
*
|
|
199
|
+
* The `update` variant it returns is the one the fields declare: it is neither
|
|
200
|
+
* made partial nor flattened into paths. Use {@link Class.update} for the
|
|
201
|
+
* update a document is written through.
|
|
202
|
+
*
|
|
203
|
+
* @see {@link fields} for the field declarations themselves
|
|
204
|
+
*
|
|
205
|
+
* @stability unstable
|
|
206
|
+
* @category converting
|
|
207
|
+
* @since 0.2.0
|
|
208
|
+
*/
|
|
209
|
+
export const extract = firestoreModel.extract;
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Returns the field declarations a {@link Class} or {@link Struct} was built
|
|
213
|
+
* from.
|
|
214
|
+
*
|
|
215
|
+
* **When to use**
|
|
216
|
+
*
|
|
217
|
+
* Use to build a new struct or class from the fields of an existing one.
|
|
218
|
+
*
|
|
219
|
+
* @see {@link extract} for one variant of a struct as a plain schema
|
|
220
|
+
*
|
|
221
|
+
* @stability unstable
|
|
222
|
+
* @category getters
|
|
223
|
+
* @since 0.2.0
|
|
224
|
+
*/
|
|
225
|
+
export const fields: <A extends VariantSchema.Struct<any>>(
|
|
226
|
+
self: A,
|
|
227
|
+
) => A[typeof VariantSchema.TypeId] = firestoreModel.fields;
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Any document schema: a {@link Class}, or a {@link Union} of classes.
|
|
231
|
+
*
|
|
232
|
+
* **Details**
|
|
233
|
+
*
|
|
234
|
+
* Unlike Effect's `Model.Any`, it asks for no `fields`: nothing that takes it
|
|
235
|
+
* reads them, and a union has none.
|
|
236
|
+
*
|
|
237
|
+
* @stability unstable
|
|
238
|
+
* @category models
|
|
239
|
+
* @since 0.2.0
|
|
240
|
+
*/
|
|
241
|
+
export type Any = Schema.Top & {
|
|
242
|
+
readonly create: Schema.Top;
|
|
243
|
+
readonly update: Schema.Top;
|
|
244
|
+
readonly updateUnchecked: Schema.Top;
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
const RecordTypeId = firestoreModel.RecordTypeId;
|
|
248
|
+
|
|
249
|
+
const UnionTypeId = firestoreModel.UnionTypeId;
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Anything a field may be declared as: a schema, a field, a struct or a
|
|
253
|
+
* {@link Record}.
|
|
254
|
+
*
|
|
255
|
+
* @stability unstable
|
|
256
|
+
* @category models
|
|
257
|
+
* @since 0.2.0
|
|
258
|
+
*/
|
|
259
|
+
export type Value = VariantSchema.Field<any> | VariantSchema.Struct<any> | Schema.Top;
|
|
260
|
+
|
|
261
|
+
/** The schema a field is written through in `V`, or `never` where it is not. */
|
|
262
|
+
type VariantSchemaOf<F, V extends "create" | "update"> =
|
|
263
|
+
F extends VariantSchema.Field<infer S>
|
|
264
|
+
? V extends keyof S
|
|
265
|
+
? S[V] extends Schema.Top
|
|
266
|
+
? S[V]
|
|
267
|
+
: never
|
|
268
|
+
: never
|
|
269
|
+
: F extends Schema.Top
|
|
270
|
+
? F
|
|
271
|
+
: never;
|
|
272
|
+
|
|
273
|
+
/** The schema a field is updated through, or `never` where it cannot be. */
|
|
274
|
+
type UpdateSchema<F> = VariantSchemaOf<F, "update">;
|
|
275
|
+
|
|
276
|
+
/** The schema `S` is made from, where it is made an optional key. */
|
|
277
|
+
type WithoutOptionalKey<S extends Schema.Top> =
|
|
278
|
+
S extends Schema.optionalKey<infer X extends Schema.Top> ? X : S;
|
|
279
|
+
|
|
280
|
+
/** The keys out of `K` that are known up front, which a Record holds every one of. */
|
|
281
|
+
type KnownKeys<K> = K extends PropertyKey
|
|
282
|
+
? {} extends { readonly [_ in K]: unknown }
|
|
283
|
+
? never
|
|
284
|
+
: K
|
|
285
|
+
: never;
|
|
286
|
+
|
|
287
|
+
/** Whether a Record keyed by `K` holds keys only the data names. */
|
|
288
|
+
type IsOpen<K extends Schema.Record.Key> = [Exclude<K["Type"], KnownKeys<K["Type"]>>] extends [
|
|
289
|
+
never,
|
|
290
|
+
]
|
|
291
|
+
? false
|
|
292
|
+
: true;
|
|
293
|
+
|
|
294
|
+
/** The schema `V` is declared as in `Variant`, or `never` where it has none. */
|
|
295
|
+
type ValueSchema<V, Variant extends "read" | "create"> =
|
|
296
|
+
V extends VariantSchema.Struct<any>
|
|
297
|
+
? VariantSchema.Extract<Variant, V>
|
|
298
|
+
: V extends VariantSchema.Field<infer S>
|
|
299
|
+
? Variant extends keyof S
|
|
300
|
+
? S[Variant] extends Schema.Top
|
|
301
|
+
? S[Variant]
|
|
302
|
+
: never
|
|
303
|
+
: never
|
|
304
|
+
: V extends Schema.Top
|
|
305
|
+
? V
|
|
306
|
+
: never;
|
|
307
|
+
|
|
308
|
+
/** A Record of `S` keyed by `K`, as a document holds it. */
|
|
309
|
+
type RecordSchema<K extends Schema.Record.Key, S extends Schema.Top> = Schema.$Record<
|
|
310
|
+
K,
|
|
311
|
+
IsOpen<K> extends true ? WithoutOptionalKey<S> : S
|
|
312
|
+
>;
|
|
313
|
+
|
|
314
|
+
/** Any field made {@link Optional}, whatever it holds. */
|
|
315
|
+
type AnyOptional = { readonly "~optionality": "optional"; readonly field: Value };
|
|
316
|
+
|
|
317
|
+
/** Whether a field was made {@link Optional}. */
|
|
318
|
+
type IsOptional<F> = F extends AnyOptional ? true : false;
|
|
319
|
+
|
|
320
|
+
/** What a field was made {@link Optional} of, or the field itself where it was not. */
|
|
321
|
+
type Unwrap<F> = F extends AnyOptional ? F["field"] : F;
|
|
322
|
+
|
|
323
|
+
/** The key and the value of a Record a field is, or `never` where it is none. */
|
|
324
|
+
type RecordOf<F> = RecordParts<Unwrap<F>>;
|
|
325
|
+
|
|
326
|
+
/** The key and the value a Record field carries, or `never` where it is none. */
|
|
327
|
+
type RecordParts<F> = F extends {
|
|
328
|
+
readonly [RecordTypeId]: {
|
|
329
|
+
readonly key: infer K extends Schema.Record.Key;
|
|
330
|
+
readonly value: infer V extends Value;
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
? { readonly key: K; readonly value: V }
|
|
334
|
+
: never;
|
|
335
|
+
|
|
336
|
+
/** Whether `V` is something an update reaches into: a struct or a Record. */
|
|
337
|
+
type IsNode<V> =
|
|
338
|
+
Unwrap<V> extends
|
|
339
|
+
| VariantSchema.Struct<any>
|
|
340
|
+
| { readonly [RecordTypeId]: any }
|
|
341
|
+
| { readonly [UnionTypeId]: any }
|
|
342
|
+
? true
|
|
343
|
+
: false;
|
|
344
|
+
|
|
345
|
+
/** Whether an update writes anything into a Record of `V`, at any depth. */
|
|
346
|
+
type IsWritable<V extends Value> = [RecordOf<V>] extends [never]
|
|
347
|
+
? [UnionOf<V>] extends [never]
|
|
348
|
+
? IsNode<V> extends true
|
|
349
|
+
? true
|
|
350
|
+
: [UpdateSchema<V>] extends [never]
|
|
351
|
+
? false
|
|
352
|
+
: true
|
|
353
|
+
: IsUnionWritable<UnionOf<V>, IsOptional<V>>
|
|
354
|
+
: IsWritable<RecordOf<V>["value"]>;
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* The fields of a Record at the keys out of `Keys` known up front, as a struct
|
|
358
|
+
* holds them: decoded for its patch, encoded for its paths.
|
|
359
|
+
*/
|
|
360
|
+
type KnownFields<Keys, V extends Value> = {
|
|
361
|
+
readonly [_ in KnownKeys<Keys> & string]: V;
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
/** What a key of a Record only the data names takes, which may be missing. */
|
|
365
|
+
type EntryUpdate<V extends Value, Mode extends UpdateMode> =
|
|
366
|
+
Unwrap<V> extends VariantSchema.Struct<infer G>
|
|
367
|
+
? StructTakes<G, true, Mode>
|
|
368
|
+
: [UnionOf<V>] extends [never]
|
|
369
|
+
? EntryValueUpdate<V, Mode>
|
|
370
|
+
: UnionTakes<UnionOf<V>, true, Mode>;
|
|
371
|
+
|
|
372
|
+
/** What a key of a Record only the data names takes, where it is no struct or union. */
|
|
373
|
+
type EntryValueUpdate<V extends Value, Mode extends UpdateMode> = [RecordOf<V>] extends [never]
|
|
374
|
+
? FirestoreSchema.DeleteFieldOr<WithoutOptionalKey<UpdateSchema<V>>>
|
|
375
|
+
: RecordTakes<RecordOf<V>["key"], RecordOf<V>["value"], true, Mode>;
|
|
376
|
+
|
|
377
|
+
/** The patch of a Record: key by key. */
|
|
378
|
+
type RecordPatch<K extends Schema.Record.Key, V extends Value, Mode extends UpdateMode> =
|
|
379
|
+
IsOpen<K> extends true
|
|
380
|
+
? Schema.$Record<Schema.toType<K>, EntryUpdate<V, Mode>>
|
|
381
|
+
: Schema.Struct<UpdateFields<KnownFields<K["Type"], V>, Mode>>;
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* What a Record takes in `Mode`. One keyed by keys only the data names is
|
|
385
|
+
* patched even where it may be missing, since every key of it may be missing
|
|
386
|
+
* as well.
|
|
387
|
+
*/
|
|
388
|
+
type RecordTakes<
|
|
389
|
+
K extends Schema.Record.Key,
|
|
390
|
+
V extends Value,
|
|
391
|
+
Optional extends boolean,
|
|
392
|
+
Mode extends UpdateMode,
|
|
393
|
+
> = Schema.Union<
|
|
394
|
+
readonly [
|
|
395
|
+
...(Optional extends true
|
|
396
|
+
? Mode extends "unchecked"
|
|
397
|
+
? [RecordPatch<K, V, Mode>]
|
|
398
|
+
: IsOpen<K> extends true
|
|
399
|
+
? [RecordPatch<K, V, Mode>]
|
|
400
|
+
: []
|
|
401
|
+
: [RecordPatch<K, V, Mode>]),
|
|
402
|
+
...(IsReplaceable<{ readonly _: V }> extends true
|
|
403
|
+
? [ReplaceSchema<RecordSchema<K, ValueSchema<V, "create">>>]
|
|
404
|
+
: []),
|
|
405
|
+
...(Optional extends true ? [FirestoreSchema.DeleteField] : []),
|
|
406
|
+
]
|
|
407
|
+
>;
|
|
408
|
+
|
|
409
|
+
/** The schemas written anywhere at or below a key of a Record only the data names. */
|
|
410
|
+
type EntryPaths<V extends Value, Mode extends UpdateMode> = WithoutOptionalKey<
|
|
411
|
+
Extract<
|
|
412
|
+
UpdatePaths<{ readonly _: AsEntry<V> }, Mode>[keyof UpdatePaths<
|
|
413
|
+
{ readonly _: AsEntry<V> },
|
|
414
|
+
Mode
|
|
415
|
+
>],
|
|
416
|
+
Schema.Top
|
|
417
|
+
>
|
|
418
|
+
>;
|
|
419
|
+
|
|
420
|
+
/** `V` as a key of a Record only the data names, which may be missing. */
|
|
421
|
+
type AsEntry<V extends Value> =
|
|
422
|
+
IsNode<V> extends true
|
|
423
|
+
? V extends AnyOptional
|
|
424
|
+
? V
|
|
425
|
+
: Optional<V>
|
|
426
|
+
: VariantSchema.Field<{
|
|
427
|
+
readonly update: FirestoreSchema.DeleteFieldOr<WithoutOptionalKey<UpdateSchema<V>>>;
|
|
428
|
+
}>;
|
|
429
|
+
|
|
430
|
+
/** The paths a Record at `Path` is written at. */
|
|
431
|
+
type RecordPaths<
|
|
432
|
+
K extends Schema.Record.Key,
|
|
433
|
+
V extends Value,
|
|
434
|
+
Optional extends boolean,
|
|
435
|
+
Mode extends UpdateMode,
|
|
436
|
+
Path extends string,
|
|
437
|
+
> =
|
|
438
|
+
IsWritable<V> extends false
|
|
439
|
+
? {}
|
|
440
|
+
: (Optional extends true
|
|
441
|
+
? {
|
|
442
|
+
readonly [_ in Path]: Schema.optionalKey<
|
|
443
|
+
IsReplaceable<{ readonly _: V }> extends true
|
|
444
|
+
? FirestoreSchema.DeleteFieldOr<RecordSchema<K, ValueSchema<V, "create">>>
|
|
445
|
+
: FirestoreSchema.DeleteField
|
|
446
|
+
>;
|
|
447
|
+
}
|
|
448
|
+
: IsReplaceable<{ readonly _: V }> extends true
|
|
449
|
+
? { readonly [_ in Path]: Schema.optionalKey<RecordSchema<K, ValueSchema<V, "create">>> }
|
|
450
|
+
: {}) &
|
|
451
|
+
(IsOpen<K> extends true
|
|
452
|
+
? { readonly [_ in `${Path}.${string}`]: EntryPaths<V, Mode> }
|
|
453
|
+
: UpdatePaths<KnownFields<K["Encoded"], V>, Mode, `${Path}.`>);
|
|
454
|
+
|
|
455
|
+
/** The paths a struct at `Path` is written at. */
|
|
456
|
+
type StructPaths<
|
|
457
|
+
Fields extends VariantSchema.Struct.Fields,
|
|
458
|
+
Optional extends boolean,
|
|
459
|
+
Mode extends UpdateMode,
|
|
460
|
+
Path extends string,
|
|
461
|
+
> = (Optional extends true
|
|
462
|
+
? {
|
|
463
|
+
readonly [_ in Path]: Schema.optionalKey<
|
|
464
|
+
IsReplaceable<Fields> extends true
|
|
465
|
+
? FirestoreSchema.DeleteFieldOr<WholeStruct<Fields>>
|
|
466
|
+
: FirestoreSchema.DeleteField
|
|
467
|
+
>;
|
|
468
|
+
}
|
|
469
|
+
: IsReplaceable<Fields> extends true
|
|
470
|
+
? { readonly [_ in Path]: Schema.optionalKey<WholeStruct<Fields>> }
|
|
471
|
+
: {}) &
|
|
472
|
+
UpdatePaths<Fields, Mode, `${Path}.`>;
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Which patches an update takes: `"checked"` or `"unchecked"`.
|
|
476
|
+
*
|
|
477
|
+
* **When to use**
|
|
478
|
+
*
|
|
479
|
+
* Use `"checked"`, the default, unless the caller knows what the document
|
|
480
|
+
* holds. Use `"unchecked"` to patch an {@link Optional} struct or union, or a
|
|
481
|
+
* key only some members of a {@link Union} hold, after an earlier read, in a
|
|
482
|
+
* transaction, or with a query that only matches such documents.
|
|
483
|
+
*
|
|
484
|
+
* **Details**
|
|
485
|
+
*
|
|
486
|
+
* Firestore applies an update to whatever the document holds without checking
|
|
487
|
+
* it, and a patch does not say which member of a union the document holds.
|
|
488
|
+
*
|
|
489
|
+
* - `checked` takes only patches that leave any document readable. An
|
|
490
|
+
* {@link Optional} struct is written whole with {@link Patch.replace} or
|
|
491
|
+
* removed, never patched: a patch written into a missing struct would create
|
|
492
|
+
* it holding only the patch. A {@link Union} is patched only at its Shared
|
|
493
|
+
* Fields, the fields every member holds, with a value every member takes and
|
|
494
|
+
* stores the same way. An {@link Optional} union is not patched at all.
|
|
495
|
+
* - `unchecked` also patches an {@link Optional} struct or union, and a key
|
|
496
|
+
* only some members of a union hold. That key is checked by the same rule as
|
|
497
|
+
* a Shared Field, over just the members that hold it: each must take the
|
|
498
|
+
* value and store it the same way.
|
|
499
|
+
*
|
|
500
|
+
* A patch is checked against every member of a union rather than the first it
|
|
501
|
+
* matches, since a patch one member takes could leave a document holding
|
|
502
|
+
* another unreadable - by exceeding its maximum length, say, or storing a
|
|
503
|
+
* `Date` as a timestamp where it holds a string.
|
|
504
|
+
*
|
|
505
|
+
* The modes differ only in which patches they take. A patch both take is
|
|
506
|
+
* written the same way, at the same paths.
|
|
507
|
+
*
|
|
508
|
+
* @see {@link Class.update} for the checked update of a document
|
|
509
|
+
* @see {@link Class.updateUnchecked} for the unchecked one
|
|
510
|
+
* @see {@link Firestore.UpdateOptions} for choosing the mode of `updateDoc`
|
|
511
|
+
* @see {@link Firestore.With.updateDoc} for writing an update
|
|
512
|
+
*
|
|
513
|
+
* @stability unstable
|
|
514
|
+
* @category models
|
|
515
|
+
* @since 0.2.0
|
|
516
|
+
*/
|
|
517
|
+
export type UpdateMode = "checked" | "unchecked";
|
|
518
|
+
|
|
519
|
+
/** The fields of the struct a field reaches into, or `never` if it is none. */
|
|
520
|
+
type StructOf<F> = Unwrap<F> extends VariantSchema.Struct<infer G> ? G : never;
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Whether a struct may be written whole by an update: only where every field it
|
|
524
|
+
* holds, at any depth, is written both by `set`, whose shape a replacement
|
|
525
|
+
* takes, and by an update.
|
|
526
|
+
*/
|
|
527
|
+
type IsReplaceable<Fields extends VariantSchema.Struct.Fields> = false extends {
|
|
528
|
+
[K in keyof Fields]: [UnionOf<Fields[K]>] extends [never]
|
|
529
|
+
? FieldIsReplaceable<Fields[K]>
|
|
530
|
+
: AreReplaceable<UnionOf<Fields[K]>>;
|
|
531
|
+
}[keyof Fields]
|
|
532
|
+
? false
|
|
533
|
+
: true;
|
|
534
|
+
|
|
535
|
+
/** Whether a field that is no union may be written whole by an update. */
|
|
536
|
+
type FieldIsReplaceable<F> = [RecordOf<F>] extends [never]
|
|
537
|
+
? [StructOf<F>] extends [never]
|
|
538
|
+
? [UpdateSchema<F>] extends [never]
|
|
539
|
+
? false
|
|
540
|
+
: [VariantSchemaOf<F, "create">] extends [never]
|
|
541
|
+
? false
|
|
542
|
+
: true
|
|
543
|
+
: IsReplaceable<StructOf<F>>
|
|
544
|
+
: IsWritable<RecordOf<F>["value"]> extends true
|
|
545
|
+
? IsReplaceable<{ readonly _: RecordOf<F>["value"] }>
|
|
546
|
+
: false;
|
|
547
|
+
|
|
548
|
+
/** The shape a struct is written whole in, as `set` would write it. */
|
|
549
|
+
type WholeStruct<Fields extends VariantSchema.Struct.Fields> = VariantSchema.Extract<
|
|
550
|
+
"create",
|
|
551
|
+
VariantSchema.Struct<Fields>
|
|
552
|
+
>;
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* The schema of a {@link Patch.replace} of a struct written whole as `S`. It is
|
|
556
|
+
* made from what `S` is made from, so the constructor defaults of the struct
|
|
557
|
+
* are filled in.
|
|
558
|
+
*/
|
|
559
|
+
interface ReplaceSchema<S extends Schema.Top> extends Schema.Bottom<
|
|
560
|
+
Patch.Replace<S["Type"]>,
|
|
561
|
+
Patch.Replace<S["Type"]>,
|
|
562
|
+
never,
|
|
563
|
+
never,
|
|
564
|
+
SchemaAST.Declaration,
|
|
565
|
+
ReplaceSchema<S>,
|
|
566
|
+
Patch.Replace<S["~type.make.in"]>
|
|
567
|
+
> {}
|
|
568
|
+
|
|
569
|
+
/** What a struct takes in `Mode`, as a field of the struct it sits in. */
|
|
570
|
+
type StructUpdate<
|
|
571
|
+
Fields extends VariantSchema.Struct.Fields,
|
|
572
|
+
Optional extends boolean,
|
|
573
|
+
Mode extends UpdateMode,
|
|
574
|
+
> = Schema.optionalKey<StructTakes<Fields, Optional, Mode>>;
|
|
575
|
+
|
|
576
|
+
/** What a struct takes in `Mode`. */
|
|
577
|
+
type StructTakes<
|
|
578
|
+
Fields extends VariantSchema.Struct.Fields,
|
|
579
|
+
Optional extends boolean,
|
|
580
|
+
Mode extends UpdateMode,
|
|
581
|
+
> = Schema.Union<
|
|
582
|
+
readonly [
|
|
583
|
+
...(Optional extends true
|
|
584
|
+
? Mode extends "unchecked"
|
|
585
|
+
? [Schema.Struct<UpdateFields<Fields, Mode>>]
|
|
586
|
+
: []
|
|
587
|
+
: [Schema.Struct<UpdateFields<Fields, Mode>>]),
|
|
588
|
+
...(IsReplaceable<Fields> extends true ? [ReplaceSchema<WholeStruct<Fields>>] : []),
|
|
589
|
+
...(Optional extends true ? [FirestoreSchema.DeleteField] : []),
|
|
590
|
+
]
|
|
591
|
+
>;
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* The fields of an update in `Mode`: every field that may change, each one
|
|
595
|
+
* optional.
|
|
596
|
+
*
|
|
597
|
+
* **Details**
|
|
598
|
+
*
|
|
599
|
+
* A struct every document holds takes a patch of its own. An {@link Optional}
|
|
600
|
+
* struct takes the removal, and a patch only under `unchecked` - see
|
|
601
|
+
* {@link UpdateMode}. Either takes {@link Patch.replace} where an update writes
|
|
602
|
+
* every field it holds.
|
|
603
|
+
*
|
|
604
|
+
* @see {@link UpdatePaths} for the paths these fields are written at
|
|
605
|
+
*
|
|
606
|
+
* @stability unstable
|
|
607
|
+
* @category models
|
|
608
|
+
* @since 0.2.0
|
|
609
|
+
*/
|
|
610
|
+
export type UpdateFields<
|
|
611
|
+
Fields extends VariantSchema.Struct.Fields,
|
|
612
|
+
Mode extends UpdateMode = "checked",
|
|
613
|
+
> = {
|
|
614
|
+
readonly [
|
|
615
|
+
K in keyof Fields as FieldUpdate<Fields[K], Mode> extends never ? never : K
|
|
616
|
+
]: FieldUpdate<Fields[K], Mode>;
|
|
617
|
+
};
|
|
618
|
+
|
|
619
|
+
/** What a field takes in an update in `Mode`, or `never` where it takes nothing. */
|
|
620
|
+
type FieldUpdate<F, Mode extends UpdateMode> =
|
|
621
|
+
Unwrap<F> extends VariantSchema.Struct<infer G>
|
|
622
|
+
? StructUpdate<G, IsOptional<F>, Mode>
|
|
623
|
+
: [UnionOf<F>] extends [never]
|
|
624
|
+
? ValueUpdate<F, Mode>
|
|
625
|
+
: UnionFieldUpdate<UnionOf<F>, IsOptional<F>, Mode>;
|
|
626
|
+
|
|
627
|
+
/** What a field that is no struct or union takes in an update in `Mode`. */
|
|
628
|
+
type ValueUpdate<F, Mode extends UpdateMode> = [RecordOf<F>] extends [never]
|
|
629
|
+
? [UpdateSchema<F>] extends [never]
|
|
630
|
+
? never
|
|
631
|
+
: Schema.optionalKey<UpdateSchema<F>>
|
|
632
|
+
: RecordUpdate<RecordOf<F>, IsOptional<F>, Mode>;
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* The services the key of each Record an update writes into takes to encode,
|
|
636
|
+
* which an update encodes every key it writes at with.
|
|
637
|
+
*/
|
|
638
|
+
type KeyServices<Fields extends VariantSchema.Struct.Fields> = {
|
|
639
|
+
[K in keyof Fields]: FieldKeyServices<Fields[K]>;
|
|
640
|
+
}[keyof Fields];
|
|
641
|
+
|
|
642
|
+
/** The services the keys of the Records a field holds take to encode. */
|
|
643
|
+
type FieldKeyServices<F> =
|
|
644
|
+
Unwrap<F> extends VariantSchema.Struct<infer G>
|
|
645
|
+
? KeyServices<G>
|
|
646
|
+
: [RecordOf<F>] extends [never]
|
|
647
|
+
? never
|
|
648
|
+
: IsWritable<RecordOf<F>["value"]> extends true
|
|
649
|
+
? RecordOf<F>["key"]["EncodingServices"] | FieldKeyServices<RecordOf<F>["value"]>
|
|
650
|
+
: never;
|
|
651
|
+
|
|
652
|
+
/** What a Record takes as a field of the struct it sits in. */
|
|
653
|
+
type RecordUpdate<
|
|
654
|
+
P extends { readonly key: Schema.Record.Key; readonly value: Value },
|
|
655
|
+
Optional extends boolean,
|
|
656
|
+
Mode extends UpdateMode,
|
|
657
|
+
> =
|
|
658
|
+
IsWritable<P["value"]> extends true
|
|
659
|
+
? Schema.optionalKey<RecordTakes<P["key"], P["value"], Optional, Mode>>
|
|
660
|
+
: never;
|
|
661
|
+
|
|
662
|
+
/**
|
|
663
|
+
* The fields an update is encoded as: one optional key per path it can write
|
|
664
|
+
* to, such as `"address.city"`.
|
|
665
|
+
*
|
|
666
|
+
* **Details**
|
|
667
|
+
*
|
|
668
|
+
* A struct's own key takes the struct written whole, where an update writes
|
|
669
|
+
* every field it holds, and the removal, where it is {@link Optional}. A
|
|
670
|
+
* struct that takes neither has no key of its own and is only written field by
|
|
671
|
+
* field.
|
|
672
|
+
*
|
|
673
|
+
* The paths are the same in either {@link UpdateMode}.
|
|
674
|
+
*
|
|
675
|
+
* @see {@link UpdateFields} for the nested patch these paths are encoded from
|
|
676
|
+
*
|
|
677
|
+
* @stability unstable
|
|
678
|
+
* @category models
|
|
679
|
+
* @since 0.2.0
|
|
680
|
+
*/
|
|
681
|
+
export type UpdatePaths<
|
|
682
|
+
Fields extends VariantSchema.Struct.Fields,
|
|
683
|
+
Mode extends UpdateMode = "checked",
|
|
684
|
+
Prefix extends string = "",
|
|
685
|
+
> = Struct_.Simplify<
|
|
686
|
+
Types.UnionToIntersection<
|
|
687
|
+
{
|
|
688
|
+
[K in keyof Fields & string]: Unwrap<Fields[K]> extends VariantSchema.Struct<infer G>
|
|
689
|
+
? StructPaths<G, IsOptional<Fields[K]>, Mode, `${Prefix}${K}`>
|
|
690
|
+
: [UnionOf<Fields[K]>] extends [never]
|
|
691
|
+
? ValuePaths<Fields[K], Mode, `${Prefix}${K}`>
|
|
692
|
+
: UnionPaths<UnionOf<Fields[K]>, IsOptional<Fields[K]>, `${Prefix}${K}`>;
|
|
693
|
+
}[keyof Fields & string]
|
|
694
|
+
> & {}
|
|
695
|
+
>;
|
|
696
|
+
|
|
697
|
+
/** The paths a field that is no struct or union is written at. */
|
|
698
|
+
type ValuePaths<F, Mode extends UpdateMode, Path extends string> = [RecordOf<F>] extends [never]
|
|
699
|
+
? [UpdateSchema<F>] extends [never]
|
|
700
|
+
? {}
|
|
701
|
+
: { readonly [_ in Path]: Schema.optionalKey<UpdateSchema<F>> }
|
|
702
|
+
: RecordPaths<RecordOf<F>["key"], RecordOf<F>["value"], IsOptional<F>, Mode, Path>;
|
|
703
|
+
|
|
704
|
+
/**
|
|
705
|
+
* The `update` variant of a set of fields, taking the patches `Mode` allows.
|
|
706
|
+
*
|
|
707
|
+
* **Details**
|
|
708
|
+
*
|
|
709
|
+
* It takes a nested partial - the fields that change, with a struct carrying
|
|
710
|
+
* only its own fields that change - and encodes each one at its path, so
|
|
711
|
+
* `{ address: { city } }` becomes `{ "address.city": city }`. That makes an
|
|
712
|
+
* update a merge: Firestore replaces a map it is handed as a value, but a path
|
|
713
|
+
* counts as the top level and leaves the rest of the map alone. It also lets a
|
|
714
|
+
* sentinel sit at any depth, which matters for removal: Firestore only takes
|
|
715
|
+
* `deleteField` at the top level of an update.
|
|
716
|
+
*
|
|
717
|
+
* - A {@link Struct} or class is reached into. Handed {@link Patch.replace},
|
|
718
|
+
* it is written whole at its path, as `setDoc` would write it, and one made
|
|
719
|
+
* {@link Optional} may also be removed.
|
|
720
|
+
* - Any other schema, a plain `Schema.Struct` included, is a value and is
|
|
721
|
+
* written whole. The patch decides which, not the key - see `Patch`.
|
|
722
|
+
* - A {@link Union} is replaced with one of its members, removed where it is
|
|
723
|
+
* {@link Optional}, and patched at its Shared Fields, each at its path.
|
|
724
|
+
* - A struct holding a field that `setDoc` or an update leaves out -
|
|
725
|
+
* {@link Immutable}, or a read-only field - is never replaced, since that
|
|
726
|
+
* would change or drop the field. It is patched where every document holds
|
|
727
|
+
* it, and a checked update only removes it where it is {@link Optional}.
|
|
728
|
+
*
|
|
729
|
+
* Under `checked`, a patch into an {@link Optional} struct or union is refused,
|
|
730
|
+
* by the type and by the encoding, since written into a missing struct it
|
|
731
|
+
* creates one holding only the patch. Under `unchecked` it is taken - see
|
|
732
|
+
* {@link UpdateMode}.
|
|
733
|
+
*
|
|
734
|
+
* A patch of a union's Shared Field is checked against every member: its type
|
|
735
|
+
* is what every member's update takes there, so the literal the members are
|
|
736
|
+
* told apart by is never shared, and its value is encoded through every member,
|
|
737
|
+
* each of which must store it as the same Firestore Value. Otherwise it fails
|
|
738
|
+
* with a schema issue. A sentinel is taken where every member takes it.
|
|
739
|
+
*
|
|
740
|
+
* The patch it takes is `Update.to`, and the paths it writes are `Update.from`.
|
|
741
|
+
* It is only ever written: decoding fails, since a document is never read in
|
|
742
|
+
* this shape.
|
|
743
|
+
*
|
|
744
|
+
* @see {@link UpdateFields} for the patch it takes
|
|
745
|
+
* @see {@link UpdatePaths} for the paths it writes
|
|
746
|
+
*
|
|
747
|
+
* @stability unstable
|
|
748
|
+
* @category models
|
|
749
|
+
* @since 0.2.0
|
|
750
|
+
*/
|
|
751
|
+
export interface Update<
|
|
752
|
+
// Declared rather than measured, which recursing through every struct for
|
|
753
|
+
// replaceability makes too deep for the compiler.
|
|
754
|
+
in out Fields extends VariantSchema.Struct.Fields,
|
|
755
|
+
in out Mode extends UpdateMode = "checked",
|
|
756
|
+
> extends Schema.decodeTo<
|
|
757
|
+
Schema.toType<Schema.Struct<UpdateFields<Fields, Mode>>>,
|
|
758
|
+
Schema.Struct<UpdatePaths<Fields, Mode> extends infer P extends Schema.Struct.Fields ? P : never>,
|
|
759
|
+
never,
|
|
760
|
+
KeyServices<Fields>
|
|
761
|
+
> {}
|
|
762
|
+
|
|
763
|
+
/**
|
|
764
|
+
* Type-level representation of {@link Class}: a document schema together with
|
|
765
|
+
* the shapes it is written in.
|
|
766
|
+
*
|
|
767
|
+
* @stability unstable
|
|
768
|
+
* @category models
|
|
769
|
+
* @since 0.2.0
|
|
770
|
+
*/
|
|
771
|
+
export interface Class<
|
|
772
|
+
Self,
|
|
773
|
+
Fields extends VariantSchema.Struct.Fields,
|
|
774
|
+
> extends VariantSchema.Class<
|
|
775
|
+
Self,
|
|
776
|
+
Fields,
|
|
777
|
+
Schema.Struct<VariantSchema.ExtractFields<"read", Fields, true>>
|
|
778
|
+
> {
|
|
779
|
+
/** The shape a snapshot decodes into, which is the class itself. */
|
|
780
|
+
readonly read: VariantSchema.Extract<"read", VariantSchema.Struct<Fields>>;
|
|
781
|
+
/**
|
|
782
|
+
* The shape `setDoc` and `addDoc` write: a whole document, taking the
|
|
783
|
+
* sentinels and constructor defaults its fields declare.
|
|
784
|
+
*/
|
|
785
|
+
readonly create: VariantSchema.Extract<"create", VariantSchema.Struct<Fields>>;
|
|
786
|
+
/**
|
|
787
|
+
* The shape `updateDoc` writes, taking only patches that leave any document
|
|
788
|
+
* readable.
|
|
789
|
+
*
|
|
790
|
+
* **When to use**
|
|
791
|
+
*
|
|
792
|
+
* Use for every update unless the caller knows what the document holds.
|
|
793
|
+
*
|
|
794
|
+
* **Details**
|
|
795
|
+
*
|
|
796
|
+
* An {@link Optional} struct is replaced with {@link Patch.replace} or
|
|
797
|
+
* removed, never patched, and a {@link Union} is only patched at its Shared
|
|
798
|
+
* Fields.
|
|
799
|
+
*
|
|
800
|
+
* @see {@link Class.updateUnchecked} for patching where the caller knows the
|
|
801
|
+
* document holds the target
|
|
802
|
+
*/
|
|
803
|
+
readonly update: Update<Fields, "checked">;
|
|
804
|
+
/**
|
|
805
|
+
* The shape `updateDoc` writes with `mode: "unchecked"`, also taking a patch
|
|
806
|
+
* into an {@link Optional} struct, and into a key only some members of a
|
|
807
|
+
* {@link Union} hold.
|
|
808
|
+
*
|
|
809
|
+
* **When to use**
|
|
810
|
+
*
|
|
811
|
+
* Use when the caller knows the struct is there, or which member of the
|
|
812
|
+
* union the document holds - from an earlier read, a transaction, or a
|
|
813
|
+
* query that only matches such documents.
|
|
814
|
+
*
|
|
815
|
+
* **Details**
|
|
816
|
+
*
|
|
817
|
+
* It takes every patch {@link Class.update} takes, and writes it the same
|
|
818
|
+
* way. Where the caller is wrong, Firestore writes the patch anyway and the
|
|
819
|
+
* document no longer reads.
|
|
820
|
+
*
|
|
821
|
+
* @see {@link Class.update} for the checked update
|
|
822
|
+
* @see {@link UpdateMode} for exactly what each mode takes
|
|
823
|
+
*/
|
|
824
|
+
readonly updateUnchecked: Update<Fields, "unchecked">;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
type MissingSelfGeneric =
|
|
828
|
+
`Missing \`Self\` generic - use \`class Self extends Class<Self>("Self")({ ... })\``;
|
|
829
|
+
|
|
830
|
+
/**
|
|
831
|
+
* Defines a document schema: one field declaration, with the shapes a document
|
|
832
|
+
* is read, created and updated in derived from it.
|
|
833
|
+
*
|
|
834
|
+
* **When to use**
|
|
835
|
+
*
|
|
836
|
+
* Use to define what the documents of a collection hold. Hand the class to
|
|
837
|
+
* {@link Collection.make}, and every read and write of that collection goes
|
|
838
|
+
* through it.
|
|
839
|
+
*
|
|
840
|
+
* **Details**
|
|
841
|
+
*
|
|
842
|
+
* The class is the `read` variant: what a snapshot decodes into, and what
|
|
843
|
+
* schema combinators take. The write shapes hang off it:
|
|
844
|
+
*
|
|
845
|
+
* - {@link Class.create} is what `setDoc` and `addDoc` write, a whole document,
|
|
846
|
+
* taking the sentinels and constructor defaults its fields declare.
|
|
847
|
+
* - {@link Class.update} is what `updateDoc` writes, a nested partial encoded at
|
|
848
|
+
* field paths, taking only patches that leave any document readable.
|
|
849
|
+
* - {@link Class.updateUnchecked} also takes patches that are only right where
|
|
850
|
+
* the caller knows what the document holds - see {@link UpdateMode}.
|
|
851
|
+
*
|
|
852
|
+
* The document's id is not a field: it lives in the path.
|
|
853
|
+
*
|
|
854
|
+
* @example Defining a document and its writes
|
|
855
|
+
* ```ts
|
|
856
|
+
* import { Schema } from "effect";
|
|
857
|
+
* import {
|
|
858
|
+
* FieldValue,
|
|
859
|
+
* FirestoreModel,
|
|
860
|
+
* FirestoreSchema,
|
|
861
|
+
* } from "@visiolab/effect-firebase/firestore";
|
|
862
|
+
*
|
|
863
|
+
* class User extends FirestoreModel.Class<User>("User")({
|
|
864
|
+
* displayName: Schema.String,
|
|
865
|
+
* signupSource: FirestoreModel.Immutable(Schema.String),
|
|
866
|
+
* createdAt: FirestoreModel.Timestamp(FirestoreSchema.Timestamp),
|
|
867
|
+
* visits: FirestoreModel.Number(Schema.Int),
|
|
868
|
+
* nickname: FirestoreModel.Optional(Schema.String),
|
|
869
|
+
* }) {}
|
|
870
|
+
*
|
|
871
|
+
* User.create.make({
|
|
872
|
+
* displayName: "Ada",
|
|
873
|
+
* signupSource: "web",
|
|
874
|
+
* createdAt: FieldValue.serverTimestamp,
|
|
875
|
+
* visits: 0,
|
|
876
|
+
* });
|
|
877
|
+
*
|
|
878
|
+
* User.update.make({ visits: FieldValue.increment(1), nickname: FieldValue.deleteField });
|
|
879
|
+
* ```
|
|
880
|
+
*
|
|
881
|
+
* @see {@link Union} for a document that holds one of several shapes
|
|
882
|
+
* @see {@link Struct} for a nested map, or fields shared between documents
|
|
883
|
+
* @see {@link Collection.make} for the collection a document lives in
|
|
884
|
+
*
|
|
885
|
+
* @stability unstable
|
|
886
|
+
* @category constructors
|
|
887
|
+
* @since 0.2.0
|
|
888
|
+
*/
|
|
889
|
+
export const Class: <Self = never>(
|
|
890
|
+
identifier: string,
|
|
891
|
+
) => <const Fields extends VariantSchema.Struct.Fields>(
|
|
892
|
+
fields: Fields & VariantSchema.Struct.Validate<Fields, Variant>,
|
|
893
|
+
annotations?: Schema.Annotations.Declaration<
|
|
894
|
+
Self,
|
|
895
|
+
readonly [Schema.Struct<VariantSchema.ExtractFields<"read", Fields, true>>]
|
|
896
|
+
>,
|
|
897
|
+
) => [Self] extends [never] ? MissingSelfGeneric : Class<Self, Fields> = firestoreModel.Class;
|
|
898
|
+
|
|
899
|
+
/** What a union is replaced with: the `create` shape of each member it may be replaced with. */
|
|
900
|
+
type ReplaceableCreates<Members extends ReadonlyArray<VariantSchema.Struct<any>>> =
|
|
901
|
+
Members extends readonly [
|
|
902
|
+
infer Head extends VariantSchema.Struct<any>,
|
|
903
|
+
...infer Tail extends ReadonlyArray<VariantSchema.Struct<any>>,
|
|
904
|
+
]
|
|
905
|
+
? IsReplaceable<Head[typeof VariantSchema.TypeId]> extends true
|
|
906
|
+
? [VariantSchema.Extract<"create", Head>, ...ReplaceableCreates<Tail>]
|
|
907
|
+
: ReplaceableCreates<Tail>
|
|
908
|
+
: [];
|
|
909
|
+
|
|
910
|
+
/** Whether every member of a union may be written whole by an update. */
|
|
911
|
+
type AreReplaceable<Members extends ReadonlyArray<VariantSchema.Struct<any>>> = false extends {
|
|
912
|
+
[K in keyof Members]: IsReplaceable<Members[K][typeof VariantSchema.TypeId]>;
|
|
913
|
+
}[number]
|
|
914
|
+
? false
|
|
915
|
+
: true;
|
|
916
|
+
|
|
917
|
+
/** The union of the `create` shapes of `Members`. */
|
|
918
|
+
type UnionCreate<Members extends ReadonlyArray<VariantSchema.Struct<any>>> = Schema.Union<{
|
|
919
|
+
readonly [K in keyof Members]: VariantSchema.Extract<"create", Members[K]>;
|
|
920
|
+
}>;
|
|
921
|
+
|
|
922
|
+
/** The fields each of `Members` holds. */
|
|
923
|
+
type MemberFields<Members extends ReadonlyArray<VariantSchema.Struct<any>>> = {
|
|
924
|
+
readonly [I in keyof Members]: Members[I][typeof VariantSchema.TypeId];
|
|
925
|
+
};
|
|
926
|
+
|
|
927
|
+
/** Whether every one of `Fs` holds a field at `K`. */
|
|
928
|
+
type HoldAll<
|
|
929
|
+
Fs extends ReadonlyArray<VariantSchema.Struct.Fields>,
|
|
930
|
+
K extends PropertyKey,
|
|
931
|
+
> = false extends { [I in keyof Fs]: K extends keyof Fs[I] ? true : false }[number] ? false : true;
|
|
932
|
+
|
|
933
|
+
/** The keys any of `Fs` holds. */
|
|
934
|
+
type AllKeys<Fs extends ReadonlyArray<VariantSchema.Struct.Fields>> = {
|
|
935
|
+
[I in keyof Fs]: keyof Fs[I];
|
|
936
|
+
}[number] &
|
|
937
|
+
string;
|
|
938
|
+
|
|
939
|
+
/** Those of `Fs` that hold a field at `K`. */
|
|
940
|
+
type HoldingMembers<
|
|
941
|
+
Fs extends ReadonlyArray<VariantSchema.Struct.Fields>,
|
|
942
|
+
K extends PropertyKey,
|
|
943
|
+
> = Fs extends readonly [
|
|
944
|
+
infer Head extends VariantSchema.Struct.Fields,
|
|
945
|
+
...infer Tail extends ReadonlyArray<VariantSchema.Struct.Fields>,
|
|
946
|
+
]
|
|
947
|
+
? K extends keyof Head
|
|
948
|
+
? [Head, ...HoldingMembers<Tail, K>]
|
|
949
|
+
: HoldingMembers<Tail, K>
|
|
950
|
+
: [];
|
|
951
|
+
|
|
952
|
+
/** The field each of `Fs` holds at `K`. */
|
|
953
|
+
type FieldsAt<Fs extends ReadonlyArray<VariantSchema.Struct.Fields>, K extends PropertyKey> = {
|
|
954
|
+
readonly [I in keyof Fs]: Fs[I][K & keyof Fs[I]];
|
|
955
|
+
};
|
|
956
|
+
|
|
957
|
+
/** Whether every one of `Vs` is a struct, {@link Optional} or not. */
|
|
958
|
+
type AllStructs<Vs extends ReadonlyArray<unknown>> = false extends {
|
|
959
|
+
[I in keyof Vs]: [StructOf<Vs[I]>] extends [never] ? false : true;
|
|
960
|
+
}[number]
|
|
961
|
+
? false
|
|
962
|
+
: true;
|
|
963
|
+
|
|
964
|
+
/** Whether every one of `Vs` is a value: no struct, Record or union. */
|
|
965
|
+
type AllValues<Vs extends ReadonlyArray<unknown>> = true extends {
|
|
966
|
+
[I in keyof Vs]: IsNode<Vs[I]>;
|
|
967
|
+
}[number]
|
|
968
|
+
? false
|
|
969
|
+
: true;
|
|
970
|
+
|
|
971
|
+
/** Whether every one of `Vs` is {@link Optional}, or none, or some. */
|
|
972
|
+
type Optionality<Vs extends ReadonlyArray<unknown>> = {
|
|
973
|
+
[I in keyof Vs]: IsOptional<Vs[I]>;
|
|
974
|
+
}[number] extends infer O
|
|
975
|
+
? [O] extends [true]
|
|
976
|
+
? "all"
|
|
977
|
+
: [O] extends [false]
|
|
978
|
+
? "none"
|
|
979
|
+
: "some"
|
|
980
|
+
: never;
|
|
981
|
+
|
|
982
|
+
/** The fields of the struct each of `Vs` is. */
|
|
983
|
+
type StructsOf<Vs extends ReadonlyArray<unknown>> = {
|
|
984
|
+
readonly [I in keyof Vs]: StructOf<Vs[I]>;
|
|
985
|
+
} extends infer Fs extends ReadonlyArray<VariantSchema.Struct.Fields>
|
|
986
|
+
? Fs
|
|
987
|
+
: never;
|
|
988
|
+
|
|
989
|
+
/** The values out of `A` and `B` that the other takes too. */
|
|
990
|
+
type Meet<A, B> = (A extends B ? A : never) | (B extends A ? B : never);
|
|
991
|
+
|
|
992
|
+
/** The values every one of `Ts` takes. */
|
|
993
|
+
type MeetAll<Ts extends ReadonlyArray<unknown>> = Ts extends readonly [infer H, ...infer T]
|
|
994
|
+
? T extends readonly []
|
|
995
|
+
? H
|
|
996
|
+
: Meet<H, MeetAll<T>>
|
|
997
|
+
: never;
|
|
998
|
+
|
|
999
|
+
/** What each of `Vs` takes in an update. */
|
|
1000
|
+
type UpdateTypes<Vs extends ReadonlyArray<unknown>> = {
|
|
1001
|
+
readonly [I in keyof Vs]: UpdateSchema<Vs[I]>["Type"];
|
|
1002
|
+
};
|
|
1003
|
+
|
|
1004
|
+
/**
|
|
1005
|
+
* The schema a Shared Field that is a value is patched through: it takes what
|
|
1006
|
+
* every member takes, and encodes as the first member does, which is how every
|
|
1007
|
+
* member would.
|
|
1008
|
+
*/
|
|
1009
|
+
interface SharedSchema<T, E> extends Schema.Bottom<
|
|
1010
|
+
T,
|
|
1011
|
+
E,
|
|
1012
|
+
never,
|
|
1013
|
+
never,
|
|
1014
|
+
SchemaAST.AST,
|
|
1015
|
+
SharedSchema<T, E>,
|
|
1016
|
+
T
|
|
1017
|
+
> {}
|
|
1018
|
+
|
|
1019
|
+
/** Whether every one of `Vs` is a {@link Union}, {@link Optional} or not. */
|
|
1020
|
+
type AllUnions<Vs extends ReadonlyArray<unknown>> = false extends {
|
|
1021
|
+
[I in keyof Vs]: [UnionOf<Vs[I]>] extends [never] ? false : true;
|
|
1022
|
+
}[number]
|
|
1023
|
+
? false
|
|
1024
|
+
: true;
|
|
1025
|
+
|
|
1026
|
+
/** The fields of every member of each of the unions `Vs`, in one list. */
|
|
1027
|
+
type UnionsMemberFields<Vs extends ReadonlyArray<unknown>> = Vs extends readonly [
|
|
1028
|
+
infer Head,
|
|
1029
|
+
...infer Tail,
|
|
1030
|
+
]
|
|
1031
|
+
? [...MemberFields<UnionOf<Head>>, ...UnionsMemberFields<Tail>]
|
|
1032
|
+
: [];
|
|
1033
|
+
|
|
1034
|
+
/** What each of the unions `Vs` is replaced with, or `never` where one is replaced with nothing. */
|
|
1035
|
+
type UnionsCreates<Vs extends ReadonlyArray<unknown>> = true extends {
|
|
1036
|
+
[I in keyof Vs]: ReplaceableCreates<UnionOf<Vs[I]>> extends readonly [] ? true : false;
|
|
1037
|
+
}[number]
|
|
1038
|
+
? never
|
|
1039
|
+
: { readonly [I in keyof Vs]: Schema.Union<ReplaceableCreates<UnionOf<Vs[I]>>> };
|
|
1040
|
+
|
|
1041
|
+
/**
|
|
1042
|
+
* The schema the unions the members of a union hold at one key are replaced
|
|
1043
|
+
* through: it takes what every one of them takes, made by every one.
|
|
1044
|
+
*/
|
|
1045
|
+
interface SharedReplaceSchema<T, MakeIn> extends Schema.Bottom<
|
|
1046
|
+
Patch.Replace<T>,
|
|
1047
|
+
Patch.Replace<T>,
|
|
1048
|
+
never,
|
|
1049
|
+
never,
|
|
1050
|
+
SchemaAST.Declaration,
|
|
1051
|
+
SharedReplaceSchema<T, MakeIn>,
|
|
1052
|
+
Patch.Replace<MakeIn>
|
|
1053
|
+
> {}
|
|
1054
|
+
|
|
1055
|
+
/** The value each of `Ss` holds, and what each is made from. */
|
|
1056
|
+
type CreateTypes<Ss extends ReadonlyArray<Schema.Top>> = {
|
|
1057
|
+
readonly [I in keyof Ss]: Ss[I]["Type"];
|
|
1058
|
+
};
|
|
1059
|
+
type CreateMakeIns<Ss extends ReadonlyArray<Schema.Top>> = {
|
|
1060
|
+
readonly [I in keyof Ss]: Ss[I]["~type.make.in"];
|
|
1061
|
+
};
|
|
1062
|
+
|
|
1063
|
+
/**
|
|
1064
|
+
* The replacement of the unions `Vs` takes, or `never` where there is none:
|
|
1065
|
+
* one every one of them is replaced with.
|
|
1066
|
+
*/
|
|
1067
|
+
type SharedReplacement<Vs extends ReadonlyArray<unknown>> =
|
|
1068
|
+
UnionsCreates<Vs> extends infer Ss extends ReadonlyArray<Schema.Top>
|
|
1069
|
+
? [MeetAll<CreateTypes<Ss>>] extends [never]
|
|
1070
|
+
? never
|
|
1071
|
+
: SharedReplaceSchema<MeetAll<CreateTypes<Ss>>, MeetAll<CreateMakeIns<Ss>>>
|
|
1072
|
+
: never;
|
|
1073
|
+
|
|
1074
|
+
/** The schema the unions `Vs` are written whole at their path through, or `never`. */
|
|
1075
|
+
type SharedWhole<Vs extends ReadonlyArray<unknown>> =
|
|
1076
|
+
UnionsCreates<Vs> extends infer Ss extends ReadonlyArray<Schema.Top>
|
|
1077
|
+
? [MeetAll<CreateTypes<Ss>>] extends [never]
|
|
1078
|
+
? never
|
|
1079
|
+
: SharedSchema<MeetAll<CreateTypes<Ss>>, Ss[0]["Encoded"]>
|
|
1080
|
+
: never;
|
|
1081
|
+
|
|
1082
|
+
/**
|
|
1083
|
+
* What the unions `Vs` the members of a union hold at one key take, by the
|
|
1084
|
+
* rule a union field is written by, held to every one of them: a replacement
|
|
1085
|
+
* every one takes, the removal where every one is {@link Optional}, and a
|
|
1086
|
+
* patch at the Shared Fields of all their members, unless one is
|
|
1087
|
+
* {@link Optional} and the update checked. `never` where they take none.
|
|
1088
|
+
*/
|
|
1089
|
+
type SharedUnionTakes<Vs extends ReadonlyArray<unknown>, Mode extends UpdateMode> = [
|
|
1090
|
+
...([SharedPatch<UnionsMemberFields<Vs>, Mode>] extends [never]
|
|
1091
|
+
? []
|
|
1092
|
+
: Optionality<Vs> extends "none"
|
|
1093
|
+
? [SharedPatch<UnionsMemberFields<Vs>, Mode>]
|
|
1094
|
+
: Mode extends "unchecked"
|
|
1095
|
+
? [SharedPatch<UnionsMemberFields<Vs>, Mode>]
|
|
1096
|
+
: []),
|
|
1097
|
+
...([SharedReplacement<Vs>] extends [never] ? [] : [SharedReplacement<Vs>]),
|
|
1098
|
+
...(Optionality<Vs> extends "all" ? [FirestoreSchema.DeleteField] : []),
|
|
1099
|
+
] extends infer Takes extends ReadonlyArray<Schema.Top>
|
|
1100
|
+
? Takes extends readonly []
|
|
1101
|
+
? never
|
|
1102
|
+
: Schema.Union<Takes>
|
|
1103
|
+
: never;
|
|
1104
|
+
|
|
1105
|
+
/** The path the unions `Vs` at `Path` are written whole or removed at. */
|
|
1106
|
+
type SharedUnionPath<Vs extends ReadonlyArray<unknown>, Path extends string> =
|
|
1107
|
+
Optionality<Vs> extends "all"
|
|
1108
|
+
? {
|
|
1109
|
+
readonly [_ in Path]: Schema.optionalKey<
|
|
1110
|
+
[SharedWhole<Vs>] extends [never]
|
|
1111
|
+
? FirestoreSchema.DeleteField
|
|
1112
|
+
: FirestoreSchema.DeleteFieldOr<SharedWhole<Vs>>
|
|
1113
|
+
>;
|
|
1114
|
+
}
|
|
1115
|
+
: [SharedWhole<Vs>] extends [never]
|
|
1116
|
+
? {}
|
|
1117
|
+
: { readonly [_ in Path]: Schema.optionalKey<SharedWhole<Vs>> };
|
|
1118
|
+
|
|
1119
|
+
/**
|
|
1120
|
+
* What the fields `Vs` the members of a union hold at one key take, where it
|
|
1121
|
+
* is a Shared Field, or `never` where it is none: a value, at what every
|
|
1122
|
+
* member takes there, or a struct, patched at its own Shared Fields, or removed
|
|
1123
|
+
* where every member may go without it, or a union - see
|
|
1124
|
+
* {@link SharedUnionTakes}.
|
|
1125
|
+
*/
|
|
1126
|
+
type SharedTakes<Vs extends ReadonlyArray<unknown>, Mode extends UpdateMode> =
|
|
1127
|
+
AllStructs<Vs> extends true
|
|
1128
|
+
? Optionality<Vs> extends "none"
|
|
1129
|
+
? SharedPatch<StructsOf<Vs>, Mode>
|
|
1130
|
+
: Optionality<Vs> extends "all"
|
|
1131
|
+
? FirestoreSchema.DeleteField
|
|
1132
|
+
: never
|
|
1133
|
+
: AllUnions<Vs> extends true
|
|
1134
|
+
? SharedUnionTakes<Vs, Mode>
|
|
1135
|
+
: AllValues<Vs> extends true
|
|
1136
|
+
? [MeetAll<UpdateTypes<Vs>>] extends [never]
|
|
1137
|
+
? never
|
|
1138
|
+
: SharedSchema<MeetAll<UpdateTypes<Vs>>, UpdateSchema<Vs[0]>["Encoded"]>
|
|
1139
|
+
: never;
|
|
1140
|
+
|
|
1141
|
+
/**
|
|
1142
|
+
* The keys members holding the fields `Fs` are patched at in `Mode`, as a
|
|
1143
|
+
* patch holds them: their Shared Fields, and under `unchecked` a key only some
|
|
1144
|
+
* of them hold as well, by the same rule over just those.
|
|
1145
|
+
*/
|
|
1146
|
+
type SharedUpdateFields<
|
|
1147
|
+
Fs extends ReadonlyArray<VariantSchema.Struct.Fields>,
|
|
1148
|
+
Mode extends UpdateMode,
|
|
1149
|
+
> = {
|
|
1150
|
+
readonly [
|
|
1151
|
+
K in AllKeys<Fs> as Mode extends "unchecked"
|
|
1152
|
+
? [SharedTakes<FieldsAt<HoldingMembers<Fs, K>, K>, Mode>] extends [never]
|
|
1153
|
+
? never
|
|
1154
|
+
: K
|
|
1155
|
+
: HoldAll<Fs, K> extends true
|
|
1156
|
+
? [SharedTakes<FieldsAt<Fs, K>, Mode>] extends [never]
|
|
1157
|
+
? never
|
|
1158
|
+
: K
|
|
1159
|
+
: never
|
|
1160
|
+
]: Schema.optionalKey<SharedTakes<FieldsAt<HoldingMembers<Fs, K>, K>, Mode>>;
|
|
1161
|
+
};
|
|
1162
|
+
|
|
1163
|
+
/** The patch of members holding `Fs` in `Mode`, or `never` where it takes no key. */
|
|
1164
|
+
type SharedPatch<
|
|
1165
|
+
Fs extends ReadonlyArray<VariantSchema.Struct.Fields>,
|
|
1166
|
+
Mode extends UpdateMode,
|
|
1167
|
+
> = keyof SharedUpdateFields<Fs, Mode> extends never
|
|
1168
|
+
? never
|
|
1169
|
+
: Schema.Struct<SharedUpdateFields<Fs, Mode>>;
|
|
1170
|
+
|
|
1171
|
+
/** Whether a union is patched at any key in `Mode`. */
|
|
1172
|
+
type HasSharedFields<
|
|
1173
|
+
Members extends ReadonlyArray<VariantSchema.Struct<any>>,
|
|
1174
|
+
Mode extends UpdateMode,
|
|
1175
|
+
> = [SharedPatch<MemberFields<Members>, Mode>] extends [never] ? false : true;
|
|
1176
|
+
|
|
1177
|
+
/**
|
|
1178
|
+
* The paths the keys of members holding `Fs` are written at: every key an
|
|
1179
|
+
* `unchecked` update patches, in either mode.
|
|
1180
|
+
*/
|
|
1181
|
+
type SharedPaths<
|
|
1182
|
+
Fs extends ReadonlyArray<VariantSchema.Struct.Fields>,
|
|
1183
|
+
Prefix extends string,
|
|
1184
|
+
> = Types.UnionToIntersection<
|
|
1185
|
+
{
|
|
1186
|
+
[K in keyof SharedUpdateFields<Fs, "unchecked"> & string]: SharedFieldPaths<
|
|
1187
|
+
FieldsAt<HoldingMembers<Fs, K>, K>,
|
|
1188
|
+
`${Prefix}${K}`
|
|
1189
|
+
>;
|
|
1190
|
+
}[keyof SharedUpdateFields<Fs, "unchecked"> & string]
|
|
1191
|
+
> & {};
|
|
1192
|
+
|
|
1193
|
+
/** The paths a Shared Field at `Path`, held as `Vs`, is written at. */
|
|
1194
|
+
type SharedFieldPaths<Vs extends ReadonlyArray<unknown>, Path extends string> =
|
|
1195
|
+
AllStructs<Vs> extends true
|
|
1196
|
+
? Optionality<Vs> extends "none"
|
|
1197
|
+
? SharedPaths<StructsOf<Vs>, `${Path}.`>
|
|
1198
|
+
: { readonly [_ in Path]: Schema.optionalKey<FirestoreSchema.DeleteField> }
|
|
1199
|
+
: AllUnions<Vs> extends true
|
|
1200
|
+
? SharedUnionPath<Vs, Path> & SharedPaths<UnionsMemberFields<Vs>, `${Path}.`>
|
|
1201
|
+
: { readonly [_ in Path]: Schema.optionalKey<SharedTakes<Vs, "unchecked">> };
|
|
1202
|
+
|
|
1203
|
+
/**
|
|
1204
|
+
* What a union takes in an update in `Mode`: a replacement with any member it
|
|
1205
|
+
* may be replaced with, the removal where it is {@link Optional}, and a patch
|
|
1206
|
+
* of its Shared Fields, unless it is {@link Optional} and the update checked.
|
|
1207
|
+
*/
|
|
1208
|
+
type UnionTakes<
|
|
1209
|
+
Members extends ReadonlyArray<VariantSchema.Struct<any>>,
|
|
1210
|
+
Optional extends boolean,
|
|
1211
|
+
Mode extends UpdateMode,
|
|
1212
|
+
> = Schema.Union<
|
|
1213
|
+
readonly [
|
|
1214
|
+
...(HasSharedFields<Members, Mode> extends true
|
|
1215
|
+
? Optional extends true
|
|
1216
|
+
? Mode extends "unchecked"
|
|
1217
|
+
? [SharedPatch<MemberFields<Members>, Mode>]
|
|
1218
|
+
: []
|
|
1219
|
+
: [SharedPatch<MemberFields<Members>, Mode>]
|
|
1220
|
+
: []),
|
|
1221
|
+
...(ReplaceableCreates<Members> extends readonly [] | readonly [never]
|
|
1222
|
+
? []
|
|
1223
|
+
: [ReplaceSchema<Schema.Union<ReplaceableCreates<Members>>>]),
|
|
1224
|
+
...(Optional extends true ? [FirestoreSchema.DeleteField] : []),
|
|
1225
|
+
]
|
|
1226
|
+
>;
|
|
1227
|
+
|
|
1228
|
+
/** Whether an update writes anything at all into a union. */
|
|
1229
|
+
type IsUnionWritable<
|
|
1230
|
+
Members extends ReadonlyArray<VariantSchema.Struct<any>>,
|
|
1231
|
+
Optional extends boolean,
|
|
1232
|
+
> = Optional extends true
|
|
1233
|
+
? true
|
|
1234
|
+
: ReplaceableCreates<Members> extends readonly []
|
|
1235
|
+
? HasSharedFields<Members, "unchecked">
|
|
1236
|
+
: true;
|
|
1237
|
+
|
|
1238
|
+
/** What a union takes as a field of the struct it sits in, or `never` where it takes nothing. */
|
|
1239
|
+
type UnionFieldUpdate<
|
|
1240
|
+
Members extends ReadonlyArray<VariantSchema.Struct<any>>,
|
|
1241
|
+
Optional extends boolean,
|
|
1242
|
+
Mode extends UpdateMode,
|
|
1243
|
+
> =
|
|
1244
|
+
IsUnionWritable<Members, Optional> extends true
|
|
1245
|
+
? Schema.optionalKey<UnionTakes<Members, Optional, Mode>>
|
|
1246
|
+
: never;
|
|
1247
|
+
|
|
1248
|
+
/**
|
|
1249
|
+
* The paths a union at `Path` is written at: its own, whole, and that of each
|
|
1250
|
+
* of its Shared Fields.
|
|
1251
|
+
*/
|
|
1252
|
+
type UnionPaths<
|
|
1253
|
+
Members extends ReadonlyArray<VariantSchema.Struct<any>>,
|
|
1254
|
+
Optional extends boolean,
|
|
1255
|
+
Path extends string,
|
|
1256
|
+
> = (Optional extends true
|
|
1257
|
+
? {
|
|
1258
|
+
readonly [_ in Path]: Schema.optionalKey<
|
|
1259
|
+
ReplaceableCreates<Members> extends readonly []
|
|
1260
|
+
? FirestoreSchema.DeleteField
|
|
1261
|
+
: FirestoreSchema.DeleteFieldOr<Schema.Union<ReplaceableCreates<Members>>>
|
|
1262
|
+
>;
|
|
1263
|
+
}
|
|
1264
|
+
: ReplaceableCreates<Members> extends readonly []
|
|
1265
|
+
? {}
|
|
1266
|
+
: { readonly [_ in Path]: Schema.optionalKey<Schema.Union<ReplaceableCreates<Members>>> }) &
|
|
1267
|
+
SharedPaths<MemberFields<Members>, `${Path}.`>;
|
|
1268
|
+
|
|
1269
|
+
/** The members of the union a field is, or `never` where it is none. */
|
|
1270
|
+
type UnionOf<F> =
|
|
1271
|
+
Unwrap<F> extends {
|
|
1272
|
+
readonly [UnionTypeId]: infer Members extends ReadonlyArray<VariantSchema.Struct<any>>;
|
|
1273
|
+
}
|
|
1274
|
+
? Members
|
|
1275
|
+
: never;
|
|
1276
|
+
|
|
1277
|
+
/** The schemas a union field is, variant by variant. */
|
|
1278
|
+
type UnionSchemas<Members extends ReadonlyArray<VariantSchema.Struct<any>>> =
|
|
1279
|
+
IsUnionWritable<Members, false> extends true
|
|
1280
|
+
? {
|
|
1281
|
+
readonly read: VariantSchema.Union<Members, "read">;
|
|
1282
|
+
readonly create: UnionCreate<Members>;
|
|
1283
|
+
readonly update: UnionFieldUpdate<Members, false, "unchecked">;
|
|
1284
|
+
}
|
|
1285
|
+
: {
|
|
1286
|
+
readonly read: VariantSchema.Union<Members, "read">;
|
|
1287
|
+
readonly create: UnionCreate<Members>;
|
|
1288
|
+
};
|
|
1289
|
+
|
|
1290
|
+
/**
|
|
1291
|
+
* Type-level representation of {@link Union}.
|
|
1292
|
+
*
|
|
1293
|
+
* It is the union of its members as they are read, and carries the members it
|
|
1294
|
+
* was made of, which a field holding it is written through.
|
|
1295
|
+
*
|
|
1296
|
+
* @stability unstable
|
|
1297
|
+
* @category models
|
|
1298
|
+
* @since 0.2.0
|
|
1299
|
+
*/
|
|
1300
|
+
export interface Union<in out Members extends ReadonlyArray<VariantSchema.Struct<any>>>
|
|
1301
|
+
extends VariantSchema.Union<Members, "read">, VariantSchema.Field<UnionSchemas<Members>> {
|
|
1302
|
+
/** The shape a snapshot decodes into: any member, as it is read. */
|
|
1303
|
+
readonly read: Schema.Union<{
|
|
1304
|
+
readonly [K in keyof Members]: VariantSchema.Extract<"read", Members[K]>;
|
|
1305
|
+
}>;
|
|
1306
|
+
/** The shape `setDoc` writes: any member, in its `create` shape. */
|
|
1307
|
+
readonly create: UnionCreate<Members>;
|
|
1308
|
+
/**
|
|
1309
|
+
* The shape `updateDoc` writes to a document that is the union: a patch of
|
|
1310
|
+
* its Shared Fields alone.
|
|
1311
|
+
*
|
|
1312
|
+
* A document is never written whole by an update: it changes member through
|
|
1313
|
+
* `setDoc`.
|
|
1314
|
+
*
|
|
1315
|
+
* @see {@link Union.updateUnchecked} for patching a key only some members
|
|
1316
|
+
* hold
|
|
1317
|
+
*/
|
|
1318
|
+
readonly update: UnionUpdate<Members, "checked">;
|
|
1319
|
+
/**
|
|
1320
|
+
* The shape `updateDoc` writes to a document that is the union with
|
|
1321
|
+
* `mode: "unchecked"`, also taking a patch of a key only some members hold.
|
|
1322
|
+
*
|
|
1323
|
+
* **When to use**
|
|
1324
|
+
*
|
|
1325
|
+
* Use when the caller knows which member the document holds.
|
|
1326
|
+
*
|
|
1327
|
+
* @see {@link Union.update} for the checked update
|
|
1328
|
+
* @see {@link Class.updateUnchecked} for the same rule on a class
|
|
1329
|
+
*/
|
|
1330
|
+
readonly updateUnchecked: UnionUpdate<Members, "unchecked">;
|
|
1331
|
+
readonly [UnionTypeId]: Members;
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
/** The patch of a document that is a union, or an empty one where it takes no key. */
|
|
1335
|
+
type UnionDocumentPatch<
|
|
1336
|
+
Members extends ReadonlyArray<VariantSchema.Struct<any>>,
|
|
1337
|
+
Mode extends UpdateMode,
|
|
1338
|
+
> = [SharedPatch<MemberFields<Members>, Mode>] extends [never]
|
|
1339
|
+
? Schema.Struct<{}>
|
|
1340
|
+
: SharedPatch<MemberFields<Members>, Mode>;
|
|
1341
|
+
|
|
1342
|
+
/**
|
|
1343
|
+
* The update of a document that is a {@link Union}, taking the patches `Mode`
|
|
1344
|
+
* allows.
|
|
1345
|
+
*
|
|
1346
|
+
* **Details**
|
|
1347
|
+
*
|
|
1348
|
+
* It takes a patch of the Shared Fields, and under `unchecked` of a key only
|
|
1349
|
+
* some members hold, by the rule a union field is patched by - see
|
|
1350
|
+
* {@link Update}. Each is written at its path.
|
|
1351
|
+
*
|
|
1352
|
+
* It takes no replacement or removal of the document: a document changes
|
|
1353
|
+
* member, such as to a new discriminator or version, through `setDoc`.
|
|
1354
|
+
*
|
|
1355
|
+
* @see {@link Union.update} and {@link Union.updateUnchecked}, which are this
|
|
1356
|
+
* in each mode
|
|
1357
|
+
*
|
|
1358
|
+
* @stability unstable
|
|
1359
|
+
* @category models
|
|
1360
|
+
* @since 0.2.0
|
|
1361
|
+
*/
|
|
1362
|
+
export interface UnionUpdate<
|
|
1363
|
+
in out Members extends ReadonlyArray<VariantSchema.Struct<any>>,
|
|
1364
|
+
in out Mode extends UpdateMode = "checked",
|
|
1365
|
+
> extends Schema.decodeTo<
|
|
1366
|
+
Schema.toType<UnionDocumentPatch<Members, Mode>>,
|
|
1367
|
+
Schema.Struct<
|
|
1368
|
+
Struct_.Simplify<SharedPaths<MemberFields<Members>, "">> extends infer P extends
|
|
1369
|
+
Schema.Struct.Fields
|
|
1370
|
+
? P
|
|
1371
|
+
: never
|
|
1372
|
+
>
|
|
1373
|
+
> {}
|
|
1374
|
+
|
|
1375
|
+
/**
|
|
1376
|
+
* Declares a document or field that holds one of several structs or classes.
|
|
1377
|
+
*
|
|
1378
|
+
* **When to use**
|
|
1379
|
+
*
|
|
1380
|
+
* Use when documents of one collection, or one field, come in several shapes,
|
|
1381
|
+
* such as the states of a job or the versions of a document, told apart by a
|
|
1382
|
+
* literal field.
|
|
1383
|
+
*
|
|
1384
|
+
* **Details**
|
|
1385
|
+
*
|
|
1386
|
+
* It is Effect's own union, with no tag of this library's. A snapshot decodes
|
|
1387
|
+
* into the first member that matches, so a literal field narrows it, and data
|
|
1388
|
+
* matching no member fails with a schema issue. A `create` takes any member
|
|
1389
|
+
* in the shape `setDoc` writes it. A value that fits several members, on
|
|
1390
|
+
* `create` or in a replacement, is taken as the first in declaration order, as
|
|
1391
|
+
* `Schema.Union` does.
|
|
1392
|
+
*
|
|
1393
|
+
* As a field, an update may:
|
|
1394
|
+
*
|
|
1395
|
+
* - Replace it with {@link Patch.replace} and any member, in its `create`
|
|
1396
|
+
* shape. A member holding a field that `setDoc` or an update leaves out -
|
|
1397
|
+
* {@link Immutable}, or a read-only field - is never replaced, as with a
|
|
1398
|
+
* struct.
|
|
1399
|
+
* - Remove it with {@link FieldValue.deleteField}, where it is
|
|
1400
|
+
* {@link Optional}.
|
|
1401
|
+
* - Patch its Shared Fields: keys every member holds, with a value every member
|
|
1402
|
+
* takes and stores the same way, so the patch is right whichever member the
|
|
1403
|
+
* document holds. A struct every member holds at a key is patched at its own
|
|
1404
|
+
* Shared Fields. A union every member holds at a key is written by this same
|
|
1405
|
+
* rule, across all of those unions. A Record is never shared.
|
|
1406
|
+
* - Under `unchecked`, also patch a key only some members hold, such as
|
|
1407
|
+
* `completedAt` on a job the caller knows is done. The value is checked
|
|
1408
|
+
* against just those members and written at its path. An {@link Optional}
|
|
1409
|
+
* union is only patched under `unchecked`.
|
|
1410
|
+
*
|
|
1411
|
+
* As a whole document, `setDoc` writes any member and changing member takes a
|
|
1412
|
+
* whole `setDoc`. An update patches it by the same rule - see
|
|
1413
|
+
* {@link UnionUpdate}.
|
|
1414
|
+
*
|
|
1415
|
+
* @example A field holding one of several states
|
|
1416
|
+
* ```ts
|
|
1417
|
+
* import { Schema } from "effect";
|
|
1418
|
+
* import {
|
|
1419
|
+
* FieldValue,
|
|
1420
|
+
* FirestoreModel,
|
|
1421
|
+
* FirestoreSchema,
|
|
1422
|
+
* Patch,
|
|
1423
|
+
* } from "@visiolab/effect-firebase/firestore";
|
|
1424
|
+
*
|
|
1425
|
+
* const Pending = FirestoreModel.Struct({ status: Schema.Literal("pending") });
|
|
1426
|
+
* const Done = FirestoreModel.Struct({
|
|
1427
|
+
* status: Schema.Literal("done"),
|
|
1428
|
+
* completedAt: FirestoreModel.Timestamp(FirestoreSchema.Timestamp),
|
|
1429
|
+
* });
|
|
1430
|
+
*
|
|
1431
|
+
* class Job extends FirestoreModel.Class<Job>("Job")({
|
|
1432
|
+
* state: FirestoreModel.Union([Pending, Done]),
|
|
1433
|
+
* }) {}
|
|
1434
|
+
*
|
|
1435
|
+
* Job.update.make({
|
|
1436
|
+
* state: Patch.replace({ status: "done", completedAt: FieldValue.serverTimestamp }),
|
|
1437
|
+
* });
|
|
1438
|
+
* // Where the job is known to be done: written at "state.completedAt".
|
|
1439
|
+
* Job.updateUnchecked.make({ state: { completedAt: FieldValue.serverTimestamp } });
|
|
1440
|
+
* ```
|
|
1441
|
+
*
|
|
1442
|
+
* @see {@link Class} for a document with a single shape
|
|
1443
|
+
* @see {@link UpdateMode} for what `checked` and `unchecked` patch
|
|
1444
|
+
*
|
|
1445
|
+
* @stability unstable
|
|
1446
|
+
* @category constructors
|
|
1447
|
+
* @since 0.2.0
|
|
1448
|
+
*/
|
|
1449
|
+
export const Union: <const Members extends ReadonlyArray<VariantSchema.Struct<any>>>(
|
|
1450
|
+
members: Members,
|
|
1451
|
+
) => Union<Members> = firestoreModel.Union;
|
|
1452
|
+
|
|
1453
|
+
/**
|
|
1454
|
+
* Type-level representation of {@link Immutable}.
|
|
1455
|
+
*
|
|
1456
|
+
* @stability unstable
|
|
1457
|
+
* @category models
|
|
1458
|
+
* @since 0.2.0
|
|
1459
|
+
*/
|
|
1460
|
+
export interface Immutable<S extends Schema.Top> extends VariantSchema.Field<{
|
|
1461
|
+
readonly read: S;
|
|
1462
|
+
readonly create: S;
|
|
1463
|
+
}> {}
|
|
1464
|
+
|
|
1465
|
+
/**
|
|
1466
|
+
* Declares a field written once, when the document is created.
|
|
1467
|
+
*
|
|
1468
|
+
* **When to use**
|
|
1469
|
+
*
|
|
1470
|
+
* Use for a field that must never change after creation, such as a sign-up
|
|
1471
|
+
* source or an owner id.
|
|
1472
|
+
*
|
|
1473
|
+
* **Details**
|
|
1474
|
+
*
|
|
1475
|
+
* It is read, and written by `setDoc`, but left out of `update`, so no update
|
|
1476
|
+
* can change it. A struct or union member holding it is never replaced by an
|
|
1477
|
+
* update, since that would rewrite it.
|
|
1478
|
+
*
|
|
1479
|
+
* @see {@link FieldOnly} for keeping a field to other variants
|
|
1480
|
+
* @see {@link Optional} for a field that may be missing
|
|
1481
|
+
*
|
|
1482
|
+
* @stability unstable
|
|
1483
|
+
* @category schemas
|
|
1484
|
+
* @since 0.2.0
|
|
1485
|
+
*/
|
|
1486
|
+
export const Immutable: <S extends Schema.Top>(schema: S) => Immutable<S> =
|
|
1487
|
+
firestoreModel.Immutable;
|
|
1488
|
+
|
|
1489
|
+
/**
|
|
1490
|
+
* Type-level representation of {@link Timestamp}.
|
|
1491
|
+
*
|
|
1492
|
+
* @stability unstable
|
|
1493
|
+
* @category models
|
|
1494
|
+
* @since 0.2.0
|
|
1495
|
+
*/
|
|
1496
|
+
export interface Timestamp<S extends FirestoreSchema.TimeSchema> extends VariantSchema.Field<{
|
|
1497
|
+
readonly read: S;
|
|
1498
|
+
readonly create: FirestoreSchema.ServerTimestampOr<S>;
|
|
1499
|
+
readonly update: FirestoreSchema.ServerTimestampOr<S>;
|
|
1500
|
+
}> {}
|
|
1501
|
+
|
|
1502
|
+
/**
|
|
1503
|
+
* Declares a timestamp field that the client or the server may set.
|
|
1504
|
+
*
|
|
1505
|
+
* **When to use**
|
|
1506
|
+
*
|
|
1507
|
+
* Use for a timestamp a write may leave to the server, such as `createdAt` or
|
|
1508
|
+
* `updatedAt`. Where the server must never set it, use the schema itself as
|
|
1509
|
+
* the field.
|
|
1510
|
+
*
|
|
1511
|
+
* **Details**
|
|
1512
|
+
*
|
|
1513
|
+
* `setDoc` and `updateDoc` take either a value of `schema` or
|
|
1514
|
+
* {@link FieldValue.serverTimestamp}. A read takes `schema` alone, since the
|
|
1515
|
+
* sentinel is resolved by the time a document comes back.
|
|
1516
|
+
*
|
|
1517
|
+
* `schema` decides what the instant is held as:
|
|
1518
|
+
* {@link FirestoreSchema.Timestamp} where precision finer than a millisecond
|
|
1519
|
+
* must survive a round trip, or `Schema.DateTimeUtc` or `Schema.Date` where one
|
|
1520
|
+
* of those is easier to work with. Firestore stores each as a timestamp.
|
|
1521
|
+
*
|
|
1522
|
+
* @example Timestamps held two ways
|
|
1523
|
+
* ```ts
|
|
1524
|
+
* import { Schema } from "effect";
|
|
1525
|
+
* import { FieldValue, FirestoreModel, FirestoreSchema } from "@visiolab/effect-firebase/firestore";
|
|
1526
|
+
*
|
|
1527
|
+
* class Session extends FirestoreModel.Class<Session>("Session")({
|
|
1528
|
+
* createdAt: FirestoreModel.Timestamp(FirestoreSchema.Timestamp),
|
|
1529
|
+
* seenAt: FirestoreModel.Timestamp(Schema.DateTimeUtc),
|
|
1530
|
+
* }) {}
|
|
1531
|
+
*
|
|
1532
|
+
* Session.update.make({ seenAt: FieldValue.serverTimestamp });
|
|
1533
|
+
* ```
|
|
1534
|
+
*
|
|
1535
|
+
* @see {@link FirestoreSchema.Timestamp} for the schema that keeps full precision
|
|
1536
|
+
* @see {@link FirestoreSchema.ServerTimestampOr} for the write schema this
|
|
1537
|
+
* declares
|
|
1538
|
+
* @see {@link FieldValue.serverTimestamp} for the sentinel a write hands it
|
|
1539
|
+
*
|
|
1540
|
+
* @stability unstable
|
|
1541
|
+
* @category schemas
|
|
1542
|
+
* @since 0.2.0
|
|
1543
|
+
*/
|
|
1544
|
+
export const Timestamp: <S extends FirestoreSchema.TimeSchema>(schema: S) => Timestamp<S> =
|
|
1545
|
+
firestoreModel.Timestamp;
|
|
1546
|
+
|
|
1547
|
+
/**
|
|
1548
|
+
* Type-level representation of {@link Number}.
|
|
1549
|
+
*
|
|
1550
|
+
* @stability unstable
|
|
1551
|
+
* @category models
|
|
1552
|
+
* @since 0.2.0
|
|
1553
|
+
*/
|
|
1554
|
+
export interface Number<
|
|
1555
|
+
S extends Schema.Codec<unknown, number, unknown, unknown>,
|
|
1556
|
+
> extends VariantSchema.Field<{
|
|
1557
|
+
readonly read: S;
|
|
1558
|
+
readonly create: FirestoreSchema.IncrementOr<S>;
|
|
1559
|
+
readonly update: FirestoreSchema.IncrementOr<S>;
|
|
1560
|
+
}> {}
|
|
1561
|
+
|
|
1562
|
+
/**
|
|
1563
|
+
* Declares a number field that a write may set or have the server increment.
|
|
1564
|
+
*
|
|
1565
|
+
* **When to use**
|
|
1566
|
+
*
|
|
1567
|
+
* Use for counters and totals more than one client may change at once: an
|
|
1568
|
+
* increment keeps the total right, where reading the number and writing the
|
|
1569
|
+
* sum back loses concurrent writes. Where it must never be incremented, use
|
|
1570
|
+
* the schema itself as the field.
|
|
1571
|
+
*
|
|
1572
|
+
* **Details**
|
|
1573
|
+
*
|
|
1574
|
+
* `setDoc` and `updateDoc` take either a value of `schema` or
|
|
1575
|
+
* {@link FieldValue.increment} / {@link FieldValue.decrement}. A read takes
|
|
1576
|
+
* `schema` alone, since the document holds the sum the server computed.
|
|
1577
|
+
*
|
|
1578
|
+
* `schema` decides what the number may be, on both reads and writes: hand it
|
|
1579
|
+
* `Schema.Int` for whole steps, or `Schema.Finite` for fractions. An increment
|
|
1580
|
+
* is not checked against it, since the server computes the sum and only the
|
|
1581
|
+
* next read sees it.
|
|
1582
|
+
*
|
|
1583
|
+
* @example A counter and a balance
|
|
1584
|
+
* ```ts
|
|
1585
|
+
* import { Schema } from "effect";
|
|
1586
|
+
* import { FieldValue, FirestoreModel } from "@visiolab/effect-firebase/firestore";
|
|
1587
|
+
*
|
|
1588
|
+
* class Account extends FirestoreModel.Class<Account>("Account")({
|
|
1589
|
+
* visits: FirestoreModel.Number(Schema.Int),
|
|
1590
|
+
* balance: FirestoreModel.Number(Schema.Finite),
|
|
1591
|
+
* }) {}
|
|
1592
|
+
*
|
|
1593
|
+
* Account.update.make({ visits: FieldValue.increment(1) });
|
|
1594
|
+
* ```
|
|
1595
|
+
*
|
|
1596
|
+
* @see {@link FieldValue.increment} for the sentinel a write hands it
|
|
1597
|
+
* @see {@link FirestoreSchema.IncrementOr} for the write schema this declares
|
|
1598
|
+
*
|
|
1599
|
+
* @stability unstable
|
|
1600
|
+
* @category schemas
|
|
1601
|
+
* @since 0.2.0
|
|
1602
|
+
*/
|
|
1603
|
+
export const Number: <S extends Schema.Codec<unknown, number, unknown, unknown>>(
|
|
1604
|
+
schema: S,
|
|
1605
|
+
) => Number<S> = firestoreModel.Number;
|
|
1606
|
+
|
|
1607
|
+
/**
|
|
1608
|
+
* Type-level representation of {@link Array}.
|
|
1609
|
+
*
|
|
1610
|
+
* @stability unstable
|
|
1611
|
+
* @category models
|
|
1612
|
+
* @since 0.2.0
|
|
1613
|
+
*/
|
|
1614
|
+
export interface Array<S extends Schema.Top> extends VariantSchema.Field<{
|
|
1615
|
+
readonly read: Schema.$Array<S>;
|
|
1616
|
+
readonly create: Schema.$Array<S>;
|
|
1617
|
+
readonly update: FirestoreSchema.ArrayOr<S>;
|
|
1618
|
+
}> {}
|
|
1619
|
+
|
|
1620
|
+
/**
|
|
1621
|
+
* Declares an array field that an update may write whole, or have the server
|
|
1622
|
+
* add elements to or remove elements from.
|
|
1623
|
+
*
|
|
1624
|
+
* **When to use**
|
|
1625
|
+
*
|
|
1626
|
+
* Use for an array more than one client may change at once, such as a list of
|
|
1627
|
+
* tags: {@link FieldValue.arrayUnion} and {@link FieldValue.arrayRemove} keep
|
|
1628
|
+
* an element another client added at the same time, where reading the array
|
|
1629
|
+
* and writing it back does not.
|
|
1630
|
+
*
|
|
1631
|
+
* **Details**
|
|
1632
|
+
*
|
|
1633
|
+
* An update takes the array, `arrayUnion` or `arrayRemove`. A read takes the
|
|
1634
|
+
* array alone. `setDoc` takes the array alone too: adding to a missing array
|
|
1635
|
+
* writes the elements as they are, and removing from one writes it empty,
|
|
1636
|
+
* which is not what was meant.
|
|
1637
|
+
*
|
|
1638
|
+
* Elements are matched by value, all the way down, so removing a map from the
|
|
1639
|
+
* array means handing over every key it holds - see
|
|
1640
|
+
* {@link FieldValue.ArrayRemove}. `item` is what each element is held as. It
|
|
1641
|
+
* cannot be an array, which Firestore does not nest, or a field, since an
|
|
1642
|
+
* element is always written whole.
|
|
1643
|
+
*
|
|
1644
|
+
* @example Adding a tag
|
|
1645
|
+
* ```ts
|
|
1646
|
+
* import { Schema } from "effect";
|
|
1647
|
+
* import { FieldValue, FirestoreModel } from "@visiolab/effect-firebase/firestore";
|
|
1648
|
+
*
|
|
1649
|
+
* class Article extends FirestoreModel.Class<Article>("Article")({
|
|
1650
|
+
* tags: FirestoreModel.Array(Schema.String),
|
|
1651
|
+
* }) {}
|
|
1652
|
+
*
|
|
1653
|
+
* Article.update.make({ tags: FieldValue.arrayUnion("news") });
|
|
1654
|
+
* ```
|
|
1655
|
+
*
|
|
1656
|
+
* @see {@link FieldValue.arrayUnion} and {@link FieldValue.arrayRemove} for the
|
|
1657
|
+
* sentinels an update hands it
|
|
1658
|
+
* @see {@link FirestoreSchema.ArrayOr} for the update schema this declares
|
|
1659
|
+
*
|
|
1660
|
+
* @stability unstable
|
|
1661
|
+
* @category schemas
|
|
1662
|
+
* @since 0.2.0
|
|
1663
|
+
*/
|
|
1664
|
+
export const Array: <S extends Schema.Top>(item: S) => Array<S> = firestoreModel.Array;
|
|
1665
|
+
|
|
1666
|
+
/**
|
|
1667
|
+
* Type-level representation of {@link Record}.
|
|
1668
|
+
*
|
|
1669
|
+
* It carries the key and value it was made of, which an update reaches into.
|
|
1670
|
+
*
|
|
1671
|
+
* @stability unstable
|
|
1672
|
+
* @category models
|
|
1673
|
+
* @since 0.2.0
|
|
1674
|
+
*/
|
|
1675
|
+
export interface Record<
|
|
1676
|
+
// Declared rather than measured, which recursing through the value makes
|
|
1677
|
+
// too deep for the compiler.
|
|
1678
|
+
in out K extends Schema.Record.Key,
|
|
1679
|
+
in out V extends Value,
|
|
1680
|
+
> extends VariantSchema.Field<
|
|
1681
|
+
IsWritable<V> extends true
|
|
1682
|
+
? {
|
|
1683
|
+
readonly read: RecordSchema<K, ValueSchema<V, "read">>;
|
|
1684
|
+
readonly create: RecordSchema<K, ValueSchema<V, "create">>;
|
|
1685
|
+
readonly update: Schema.optionalKey<RecordTakes<K, V, false, "unchecked">>;
|
|
1686
|
+
}
|
|
1687
|
+
: {
|
|
1688
|
+
readonly read: RecordSchema<K, ValueSchema<V, "read">>;
|
|
1689
|
+
readonly create: RecordSchema<K, ValueSchema<V, "create">>;
|
|
1690
|
+
}
|
|
1691
|
+
> {
|
|
1692
|
+
readonly [RecordTypeId]: { readonly key: K; readonly value: V };
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
/**
|
|
1696
|
+
* Declares a map field whose keys come from the data, which an update writes
|
|
1697
|
+
* key by key.
|
|
1698
|
+
*
|
|
1699
|
+
* **When to use**
|
|
1700
|
+
*
|
|
1701
|
+
* Use for a map keyed by data rather than by a fixed set of fields: a counter
|
|
1702
|
+
* per option, an entry per member, a lookup table. Writing key by key lets two
|
|
1703
|
+
* clients each change a key of the same map without reading it first.
|
|
1704
|
+
*
|
|
1705
|
+
* **Details**
|
|
1706
|
+
*
|
|
1707
|
+
* A read and `setDoc` hold it as `Schema.Record(key, value)`, with each value
|
|
1708
|
+
* in its read or create shape, so `setDoc` takes the sentinels `value` takes.
|
|
1709
|
+
* An update takes a patch, writing each key it carries at its own path, in the
|
|
1710
|
+
* update shape of `value`: a {@link Number} takes
|
|
1711
|
+
* {@link FieldValue.increment}, an {@link Array} {@link FieldValue.arrayUnion},
|
|
1712
|
+
* a {@link Timestamp} {@link FieldValue.serverTimestamp}.
|
|
1713
|
+
*
|
|
1714
|
+
* - Where `key` is a string, refined or not, any key may be missing, so each
|
|
1715
|
+
* takes {@link FieldValue.deleteField}. A value that is itself a struct or
|
|
1716
|
+
* Record is treated like an {@link Optional} struct: written whole with
|
|
1717
|
+
* {@link Patch.replace} or removed, and only patched by
|
|
1718
|
+
* {@link Class.updateUnchecked}.
|
|
1719
|
+
* - Where `key` is a union of literals, the Record holds every key, and an
|
|
1720
|
+
* update treats it as a struct with a field per key: no key is removed
|
|
1721
|
+
* unless `value` is {@link Optional}.
|
|
1722
|
+
*
|
|
1723
|
+
* Either way, it takes {@link Patch.replace} to be written whole, unless
|
|
1724
|
+
* `value` holds a field `setDoc` or an update leaves out.
|
|
1725
|
+
*
|
|
1726
|
+
* A key's path is the key as `key` encodes it, so the update requires any
|
|
1727
|
+
* service `key` needs to encode. A key no path can name - empty, or containing
|
|
1728
|
+
* a `.` - fails the update when it is encoded, or throws when the Record is
|
|
1729
|
+
* declared if it is a literal. A value no update writes, such as
|
|
1730
|
+
* {@link Immutable}, leaves the whole Record out of the update.
|
|
1731
|
+
*
|
|
1732
|
+
* @example Patching keys of a map
|
|
1733
|
+
* ```ts
|
|
1734
|
+
* import { Schema } from "effect";
|
|
1735
|
+
* import { FieldValue, FirestoreModel } from "@visiolab/effect-firebase/firestore";
|
|
1736
|
+
*
|
|
1737
|
+
* class Poll extends FirestoreModel.Class<Poll>("Poll")({
|
|
1738
|
+
* votes: FirestoreModel.Record(Schema.String, FirestoreModel.Number(Schema.Int)),
|
|
1739
|
+
* aliases: FirestoreModel.Record(Schema.String, Schema.String),
|
|
1740
|
+
* }) {}
|
|
1741
|
+
*
|
|
1742
|
+
* Poll.update.make({
|
|
1743
|
+
* votes: { pizza: FieldValue.increment(1) }, // "votes.pizza"
|
|
1744
|
+
* aliases: { old: FieldValue.deleteField }, // "aliases.old"
|
|
1745
|
+
* });
|
|
1746
|
+
* ```
|
|
1747
|
+
*
|
|
1748
|
+
* @see {@link Struct} for a map with a fixed set of fields
|
|
1749
|
+
*
|
|
1750
|
+
* @stability unstable
|
|
1751
|
+
* @category schemas
|
|
1752
|
+
* @since 0.2.0
|
|
1753
|
+
*/
|
|
1754
|
+
export const Record: <K extends Schema.Record.Key, V extends Value>(
|
|
1755
|
+
key: K,
|
|
1756
|
+
value: V,
|
|
1757
|
+
) => Record<K, V> = firestoreModel.Record;
|
|
1758
|
+
|
|
1759
|
+
/** Any {@link Record}, whatever it holds. */
|
|
1760
|
+
type AnyRecord = VariantSchema.Field<any> & { readonly [RecordTypeId]: any };
|
|
1761
|
+
|
|
1762
|
+
/** The variants of `F` made {@link Optional}. */
|
|
1763
|
+
type OptionalSchemas<F extends Value> =
|
|
1764
|
+
F extends VariantSchema.Struct<any>
|
|
1765
|
+
? {
|
|
1766
|
+
readonly read: Schema.optionalKey<VariantSchema.Extract<"read", F>>;
|
|
1767
|
+
readonly create: Schema.optionalKey<VariantSchema.Extract<"create", F>>;
|
|
1768
|
+
readonly update: StructUpdate<F[typeof VariantSchema.TypeId], true, "unchecked">;
|
|
1769
|
+
}
|
|
1770
|
+
: F extends Union<infer Members>
|
|
1771
|
+
? {
|
|
1772
|
+
readonly read: Schema.optionalKey<VariantSchema.Union<Members, "read">>;
|
|
1773
|
+
readonly create: Schema.optionalKey<UnionCreate<Members>>;
|
|
1774
|
+
readonly update: UnionFieldUpdate<Members, true, "unchecked">;
|
|
1775
|
+
}
|
|
1776
|
+
: F extends AnyRecord
|
|
1777
|
+
? IsWritable<RecordOf<F>["value"]> extends true
|
|
1778
|
+
? {
|
|
1779
|
+
readonly read: Schema.optionalKey<F["schemas"]["read"]>;
|
|
1780
|
+
readonly create: Schema.optionalKey<F["schemas"]["create"]>;
|
|
1781
|
+
readonly update: Schema.optionalKey<
|
|
1782
|
+
RecordTakes<RecordOf<F>["key"], RecordOf<F>["value"], true, "unchecked">
|
|
1783
|
+
>;
|
|
1784
|
+
}
|
|
1785
|
+
: {
|
|
1786
|
+
readonly read: Schema.optionalKey<F["schemas"]["read"]>;
|
|
1787
|
+
readonly create: Schema.optionalKey<F["schemas"]["create"]>;
|
|
1788
|
+
}
|
|
1789
|
+
: F extends Schema.Top
|
|
1790
|
+
? {
|
|
1791
|
+
readonly read: Schema.optionalKey<F>;
|
|
1792
|
+
readonly create: Schema.optionalKey<F>;
|
|
1793
|
+
readonly update: Schema.optionalKey<FirestoreSchema.DeleteFieldOr<F>>;
|
|
1794
|
+
}
|
|
1795
|
+
: F extends VariantSchema.Field<infer S>
|
|
1796
|
+
? {
|
|
1797
|
+
readonly [K in keyof S]: S[K] extends Schema.Top
|
|
1798
|
+
? K extends "update"
|
|
1799
|
+
? Schema.optionalKey<FirestoreSchema.DeleteFieldOr<S[K]>>
|
|
1800
|
+
: Schema.optionalKey<S[K]>
|
|
1801
|
+
: never;
|
|
1802
|
+
}
|
|
1803
|
+
: never;
|
|
1804
|
+
|
|
1805
|
+
/**
|
|
1806
|
+
* Type-level representation of {@link Optional}.
|
|
1807
|
+
*
|
|
1808
|
+
* It carries the field it was made of as `field`, which an update reaches into
|
|
1809
|
+
* where that is a struct or a {@link Record}.
|
|
1810
|
+
*
|
|
1811
|
+
* @stability unstable
|
|
1812
|
+
* @category models
|
|
1813
|
+
* @since 0.2.0
|
|
1814
|
+
*/
|
|
1815
|
+
export interface Optional<in out F extends Value> extends VariantSchema.Field<OptionalSchemas<F>> {
|
|
1816
|
+
readonly "~optionality": "optional";
|
|
1817
|
+
readonly field: F;
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
/**
|
|
1821
|
+
* Declares a field a document may go without, and an update may remove.
|
|
1822
|
+
*
|
|
1823
|
+
* **When to use**
|
|
1824
|
+
*
|
|
1825
|
+
* Use for a field that may be missing from a document. Wrap any schema, field,
|
|
1826
|
+
* struct, union or {@link Record} in it.
|
|
1827
|
+
*
|
|
1828
|
+
* **Details**
|
|
1829
|
+
*
|
|
1830
|
+
* A read and `setDoc` hold it as an optional key: a missing field is left out
|
|
1831
|
+
* rather than stored as `null`. An update also takes
|
|
1832
|
+
* {@link FieldValue.deleteField}, since leaving a field out of an update leaves
|
|
1833
|
+
* it as it is. Inside a struct, an optional field is removed the same way, at
|
|
1834
|
+
* its path.
|
|
1835
|
+
*
|
|
1836
|
+
* A field with no `update` variant, such as {@link Immutable}, becomes optional
|
|
1837
|
+
* where it is read and created, and stays out of the update. Making a field
|
|
1838
|
+
* optional twice is the same as once.
|
|
1839
|
+
*
|
|
1840
|
+
* An optional struct is where the stored document decides what a patch does: a
|
|
1841
|
+
* patch written into a missing struct creates it holding only the patch, and
|
|
1842
|
+
* the document no longer reads. So {@link Class.update} writes it whole with
|
|
1843
|
+
* {@link Patch.replace} or removes it, and only {@link Class.updateUnchecked}
|
|
1844
|
+
* patches it, for a caller who knows it is there.
|
|
1845
|
+
*
|
|
1846
|
+
* @example Optional fields
|
|
1847
|
+
* ```ts
|
|
1848
|
+
* import { Schema } from "effect";
|
|
1849
|
+
* import { FirestoreModel, FirestoreSchema } from "@visiolab/effect-firebase/firestore";
|
|
1850
|
+
*
|
|
1851
|
+
* const Address = FirestoreModel.Struct({ city: Schema.String });
|
|
1852
|
+
*
|
|
1853
|
+
* class User extends FirestoreModel.Class<User>("User")({
|
|
1854
|
+
* nickname: FirestoreModel.Optional(Schema.String),
|
|
1855
|
+
* lastSeenAt: FirestoreModel.Optional(FirestoreModel.Timestamp(FirestoreSchema.Timestamp)),
|
|
1856
|
+
* address: FirestoreModel.Optional(Address),
|
|
1857
|
+
* }) {}
|
|
1858
|
+
* ```
|
|
1859
|
+
*
|
|
1860
|
+
* @example Updating an optional struct
|
|
1861
|
+
* ```ts
|
|
1862
|
+
* import { Schema } from "effect";
|
|
1863
|
+
* import { FieldValue, FirestoreModel, Patch } from "@visiolab/effect-firebase/firestore";
|
|
1864
|
+
*
|
|
1865
|
+
* const Address = FirestoreModel.Struct({ city: Schema.String, zip: Schema.String });
|
|
1866
|
+
*
|
|
1867
|
+
* class Customer extends FirestoreModel.Class<Customer>("Customer")({
|
|
1868
|
+
* address: FirestoreModel.Optional(Address),
|
|
1869
|
+
* }) {}
|
|
1870
|
+
*
|
|
1871
|
+
* Customer.update.make({ address: Patch.replace({ city: "Berlin", zip: "10115" }) }); // written whole
|
|
1872
|
+
* Customer.update.make({ address: FieldValue.deleteField }); // removed
|
|
1873
|
+
* Customer.updateUnchecked.make({ address: { city: "Berlin" } }); // the caller knows it is there
|
|
1874
|
+
* ```
|
|
1875
|
+
*
|
|
1876
|
+
* @see {@link FieldValue.deleteField} for removing it in an update
|
|
1877
|
+
* @see {@link UpdateMode} for when an optional struct may be patched
|
|
1878
|
+
*
|
|
1879
|
+
* @stability unstable
|
|
1880
|
+
* @category schemas
|
|
1881
|
+
* @since 0.2.0
|
|
1882
|
+
*/
|
|
1883
|
+
export const Optional: <F extends Value>(self: F) => F extends AnyOptional ? F : Optional<F> =
|
|
1884
|
+
firestoreModel.Optional;
|