@visiolab/effect-firebase 0.0.0-stage → 0.3.1

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