@visiolab/effect-firebase 0.0.0-stage → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +66 -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,207 @@
1
+ /**
2
+ * Firestore Values: values as Firestore stores them, independent of either
3
+ * SDK.
4
+ *
5
+ * {@link toCodecFirestore} derives a codec from any Effect schema to Firestore
6
+ * Values, the way `Schema.toCodecJson` derives one to JSON. That keeps a model
7
+ * a plain Effect schema that needs no service to encode. Each SDK package then
8
+ * maps Firestore Values to and from its own SDK's values.
9
+ *
10
+ * You rarely call this module yourself: {@link Firestore} encodes and decodes
11
+ * documents through it.
12
+ *
13
+ * @stability unstable
14
+ * @since 0.2.0
15
+ */
16
+ import type { Result, Schema, SchemaAST } from "effect";
17
+ import type * as DocumentPath from "./DocumentPath.ts";
18
+ import type * as FieldValue from "./FieldValue.ts";
19
+ import type { FirestoreError } from "./Firestore.ts";
20
+ import type * as GeoPoint from "./GeoPoint.ts";
21
+ import * as firestoreValue from "./internal/firestoreValue.ts";
22
+ import * as firestoreValueWalk from "./internal/firestoreValueWalk.ts";
23
+ import type * as Timestamp from "./Timestamp.ts";
24
+
25
+ declare module "effect/Schema" {
26
+ namespace Annotations {
27
+ // The augmentation has to repeat Effect's type parameters, `T` included.
28
+ // oxlint-disable-next-line no-unused-vars
29
+ interface Declaration<T, TypeParameters extends ReadonlyArray<Constraint> = readonly []> {
30
+ /**
31
+ * Returns the link used to derive the declaration's Firestore
32
+ * representation, or `undefined` when it already holds a Firestore Value.
33
+ *
34
+ * {@link toCodecFirestore} prefers it to `toCodecJson` and `toCodec`.
35
+ */
36
+ readonly toCodecFirestore?:
37
+ | ((typeParameters: TypeParameters.Encoded<TypeParameters>) => SchemaAST.Link | undefined)
38
+ | undefined;
39
+ }
40
+ }
41
+ }
42
+
43
+ /**
44
+ * A value as Firestore stores it, independent of either SDK.
45
+ *
46
+ * **Details**
47
+ *
48
+ * One of: `null`, a boolean, a number, a string, a {@link Timestamp.Timestamp},
49
+ * a {@link GeoPoint.GeoPoint}, a {@link DocumentPath.DocumentPath}, bytes as a
50
+ * `Uint8Array`, a {@link FieldValue} sentinel, or an array or plain-object map
51
+ * of Firestore Values.
52
+ *
53
+ * A typed {@link Collection.DocumentReference} is not one: Firestore stores
54
+ * only its path, as a {@link DocumentPath.DocumentPath}.
55
+ *
56
+ * @stability unstable
57
+ * @category models
58
+ * @since 0.2.0
59
+ */
60
+ export type FirestoreValue =
61
+ | null
62
+ | boolean
63
+ | number
64
+ | string
65
+ | Timestamp.Timestamp
66
+ | GeoPoint.GeoPoint
67
+ | DocumentPath.DocumentPath
68
+ | Uint8Array
69
+ | FieldValue.ServerTimestamp
70
+ | FieldValue.Increment
71
+ | FieldValue.DeleteField
72
+ | FieldValue.ArrayUnion<FirestoreValue>
73
+ | FieldValue.ArrayRemove<FirestoreValue>
74
+ | ReadonlyArray<FirestoreValue>
75
+ | { readonly [key: string]: FirestoreValue };
76
+
77
+ /**
78
+ * Checks whether `u` is a {@link FirestoreValue}, all the way down.
79
+ *
80
+ * **Details**
81
+ *
82
+ * Maps must be plain objects. An instance of any other class is not a
83
+ * Firestore Value, since Firestore would store it as a map and read it back as
84
+ * a plain object.
85
+ *
86
+ * @stability unstable
87
+ * @category guards
88
+ * @since 0.2.0
89
+ */
90
+ export const isFirestoreValue: (u: unknown) => u is FirestoreValue =
91
+ firestoreValue.isFirestoreValue;
92
+
93
+ // The walk the SDK packages map Firestore Values with. Not API: they reach it
94
+ // here because a package can only import another's public modules.
95
+
96
+ /** @internal */
97
+ export type FieldPath = firestoreValueWalk.FieldPath;
98
+
99
+ /** @internal */
100
+ export type Kinds = firestoreValueWalk.Kinds;
101
+
102
+ /** @internal */
103
+ export type Writer = firestoreValueWalk.Writer;
104
+
105
+ /** @internal */
106
+ export type Reader = firestoreValueWalk.Reader;
107
+
108
+ /** @internal */
109
+ export const formatFieldPath: (path: FieldPath) => string = firestoreValueWalk.formatFieldPath;
110
+
111
+ /** @internal */
112
+ export const toSdk: (value: FirestoreValue, writer: Writer) => unknown = firestoreValueWalk.toSdk;
113
+
114
+ /** @internal */
115
+ export const fromSdk: (
116
+ value: unknown,
117
+ reader: Reader,
118
+ ) => Result.Result<FirestoreValue, FirestoreError> = firestoreValueWalk.fromSdk;
119
+
120
+ /** @internal */
121
+ export const readDocument: (
122
+ path: string,
123
+ data: unknown,
124
+ reader: Reader,
125
+ ) => Result.Result<FirestoreValue | undefined, FirestoreError> = firestoreValueWalk.readDocument;
126
+
127
+ /**
128
+ * Type-level representation of the codec {@link toCodecFirestore} returns: the
129
+ * schema's `Type`, encoded as a {@link FirestoreValue}.
130
+ *
131
+ * @stability unstable
132
+ * @category models
133
+ * @since 0.2.0
134
+ */
135
+ export interface toCodecFirestore<S extends Schema.Constraint> extends Schema.BottomLazy<
136
+ S["ast"],
137
+ toCodecFirestore<S>,
138
+ S["~type.parameters"],
139
+ S["~type.mutability"],
140
+ S["~type.optionality"],
141
+ S["~type.constructor.default"],
142
+ S["~encoded.mutability"],
143
+ S["~encoded.optionality"]
144
+ > {
145
+ readonly Type: S["Type"];
146
+ readonly Encoded: FirestoreValue;
147
+ readonly DecodingServices: S["DecodingServices"];
148
+ readonly EncodingServices: S["EncodingServices"];
149
+ readonly "~type.make.in": S["~type.make.in"];
150
+ readonly "~type.make": S["~type.make"];
151
+ readonly Iso: S["Iso"];
152
+ readonly schema: S;
153
+ }
154
+
155
+ /**
156
+ * Derives a codec from a schema to Firestore Values.
157
+ *
158
+ * **When to use**
159
+ *
160
+ * Use to encode or decode a value the way {@link Firestore} stores it outside
161
+ * a read or write, such as in a test or a custom backend. Reads and writes
162
+ * through {@link Firestore} already use it.
163
+ *
164
+ * **Details**
165
+ *
166
+ * It works like `Schema.toCodecJson`: the schema's own transformations run
167
+ * first, then structs, tuples, arrays, unions and `Schema.suspend` are walked.
168
+ * A declaration is resolved in this order:
169
+ *
170
+ * 1. `Schema.Date` and `Schema.DateTimeUtc` become a portable `Timestamp`, and
171
+ * `Schema.Uint8Array` is stored as bytes. Reading a `Date` or `DateTime.Utc`
172
+ * back keeps milliseconds only.
173
+ * 2. A `toCodecFirestore` annotation.
174
+ * 3. A `toCodecJson` annotation, since every JSON value is a Firestore Value.
175
+ * 4. A `toCodec` annotation.
176
+ * 5. Otherwise the value must already be a Firestore Value, and encoding
177
+ * anything else fails with a schema issue at its path.
178
+ *
179
+ * Keywords follow `Schema.toCodecJson`: a `bigint` or `symbol` is written as a
180
+ * string, and `undefined` as `null`. Unlike there, `Schema.Any` is no escape
181
+ * hatch: like `Schema.Unknown` it takes Firestore Values only, since the SDK
182
+ * mappings take nothing else. Derivation never fails and is memoized per
183
+ * schema.
184
+ *
185
+ * @example Encoding a value to Firestore Values
186
+ * ```ts
187
+ * import { DateTime, Effect, Schema } from "effect";
188
+ * import { FirestoreValue } from "@visiolab/effect-firebase/firestore";
189
+ *
190
+ * const Event = Schema.Struct({ name: Schema.String, at: Schema.DateTimeUtc });
191
+ * const codec = FirestoreValue.toCodecFirestore(Event);
192
+ *
193
+ * // `at` is encoded as a portable `Timestamp`.
194
+ * const encoded = Schema.encodeEffect(codec)({
195
+ * name: "launch",
196
+ * at: DateTime.makeUnsafe("2024-01-01T00:00:00Z"),
197
+ * });
198
+ * ```
199
+ *
200
+ * @see {@link FirestoreSchema} for schemas of the values Effect has none for
201
+ *
202
+ * @stability unstable
203
+ * @category converting
204
+ * @since 0.2.0
205
+ */
206
+ export const toCodecFirestore: <S extends Schema.Constraint>(schema: S) => toCodecFirestore<S> =
207
+ firestoreValue.toCodecFirestore;
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Firestore geopoints, independent of either SDK.
3
+ *
4
+ * Firestore stores a place on the globe as a latitude and a longitude, and
5
+ * each SDK reads it back as its own `GeoPoint` class. This module owns a
6
+ * portable version of that value, so a document schema works the same under
7
+ * the Admin SDK and the client SDK.
8
+ *
9
+ * @see {@link FirestoreSchema.GeoPoint} for storing a `GeoPoint` in a document
10
+ *
11
+ * @stability unstable
12
+ * @since 0.2.0
13
+ */
14
+ import type { Equal, Inspectable, Option } from "effect";
15
+ import * as geoPoint from "./internal/geoPoint.ts";
16
+
17
+ const TypeId = geoPoint.TypeId;
18
+
19
+ /**
20
+ * A place on the globe, stored the way Firestore stores it.
21
+ *
22
+ * Two geopoints are equal when their latitudes and longitudes are.
23
+ *
24
+ * @see {@link FirestoreSchema.GeoPoint} for the schema of this value
25
+ *
26
+ * @stability unstable
27
+ * @category models
28
+ * @since 0.2.0
29
+ */
30
+ export interface GeoPoint extends Equal.Equal, Inspectable.Inspectable {
31
+ readonly [TypeId]: typeof TypeId;
32
+ /** Degrees north of the equator, between -90 and 90. */
33
+ readonly latitude: number;
34
+ /** Degrees east of the prime meridian, between -180 and 180. */
35
+ readonly longitude: number;
36
+ }
37
+
38
+ /**
39
+ * Checks whether `u` is a {@link GeoPoint} built by this module.
40
+ *
41
+ * An SDK's own `GeoPoint` is not one.
42
+ *
43
+ * @stability unstable
44
+ * @category guards
45
+ * @since 0.2.0
46
+ */
47
+ export const isGeoPoint: (u: unknown) => u is GeoPoint = geoPoint.isGeoPoint;
48
+
49
+ /**
50
+ * Builds a geopoint from a latitude and a longitude, throwing when either is
51
+ * out of range.
52
+ *
53
+ * **When to use**
54
+ *
55
+ * Use for coordinates known to be valid, such as constants. Use {@link make}
56
+ * for coordinates you have not checked.
57
+ *
58
+ * **Details**
59
+ *
60
+ * Throws unless `latitude` is a number from -90 to 90 and `longitude` a number
61
+ * from -180 to 180.
62
+ *
63
+ * @example Building a geopoint
64
+ * ```ts
65
+ * import { GeoPoint } from "@visiolab/effect-firebase/firestore";
66
+ *
67
+ * const berlin = GeoPoint.makeUnsafe(52.52, 13.405);
68
+ * ```
69
+ *
70
+ * @see {@link make} for the same check returning an `Option`
71
+ *
72
+ * @stability unstable
73
+ * @category constructors
74
+ * @since 0.2.0
75
+ */
76
+ export const makeUnsafe: (latitude: number, longitude: number) => GeoPoint = geoPoint.makeUnsafe;
77
+
78
+ /**
79
+ * Builds a geopoint from a latitude and a longitude, or `None` when either is
80
+ * out of range.
81
+ *
82
+ * **When to use**
83
+ *
84
+ * Use when the coordinates come from outside the program and may be invalid.
85
+ *
86
+ * **Details**
87
+ *
88
+ * Returns `None` unless `latitude` is a number from -90 to 90 and `longitude`
89
+ * a number from -180 to 180.
90
+ *
91
+ * @example Building a geopoint from untrusted input
92
+ * ```ts
93
+ * import { GeoPoint } from "@visiolab/effect-firebase/firestore";
94
+ *
95
+ * GeoPoint.make(52.52, 13.405); // Option.some(...)
96
+ * GeoPoint.make(91, 0); // Option.none()
97
+ * ```
98
+ *
99
+ * @see {@link makeUnsafe} for the same check throwing instead
100
+ *
101
+ * @stability unstable
102
+ * @category constructors
103
+ * @since 0.2.0
104
+ */
105
+ export const make: (latitude: number, longitude: number) => Option.Option<GeoPoint> = geoPoint.make;
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Patches: what an update writes, and how.
3
+ *
4
+ * A patch has the shape of the document it changes. Each part of it says how
5
+ * that part is written:
6
+ *
7
+ * - A plain object handed to a {@link FirestoreModel.Struct} field is a nested
8
+ * patch, written field by field: `{ address: { city } }` writes
9
+ * `"address.city"` and leaves the rest of `address` as it is.
10
+ * - {@link replace} handed to a {@link FirestoreModel.Struct} field writes the
11
+ * struct whole, dropping any field the value leaves out.
12
+ * - Any other value is written whole where it stands. A sentinel from
13
+ * {@link FieldValue} may stand at any depth.
14
+ *
15
+ * Firestore itself tells a merge from a replace by the key: a dotted path
16
+ * merges, a map handed to a field replaces it. A patch tells them apart by the
17
+ * value instead, so the same patch means the same thing wherever it is
18
+ * written, and replacing a map is asked for by name.
19
+ *
20
+ * Which patches a document accepts is up to its model.
21
+ *
22
+ * @example Replacing one struct and deleting a field
23
+ * ```ts
24
+ * import { Schema } from "effect";
25
+ * import { FieldValue, FirestoreModel, Patch } from "@visiolab/effect-firebase/firestore";
26
+ *
27
+ * class User extends FirestoreModel.Class<User>("User")({
28
+ * address: FirestoreModel.Struct({ city: Schema.String, zip: Schema.String }),
29
+ * nickname: FirestoreModel.Optional(Schema.String),
30
+ * }) {}
31
+ *
32
+ * const patch = User.update.make({
33
+ * address: Patch.replace({ city: "Berlin", zip: "10115" }),
34
+ * nickname: FieldValue.deleteField,
35
+ * });
36
+ * ```
37
+ *
38
+ * @see {@link Firestore.With.updateDoc} for writing a patch
39
+ * @see {@link FirestoreModel.Update} for which patches a model accepts
40
+ *
41
+ * @stability unstable
42
+ * @since 0.2.0
43
+ */
44
+ import type { Inspectable } from "effect";
45
+ import * as patch from "./internal/patch.ts";
46
+
47
+ const ReplaceTypeId = patch.ReplaceTypeId;
48
+
49
+ /**
50
+ * A struct written whole, as the map `value`. Built by {@link replace}.
51
+ *
52
+ * **Details**
53
+ *
54
+ * Unlike the sentinels in {@link FieldValue}, it never reaches Firestore. It
55
+ * only tells the update how to write the struct, and is the plain map by the
56
+ * time the update is encoded.
57
+ *
58
+ * @stability unstable
59
+ * @category models
60
+ * @since 0.2.0
61
+ */
62
+ export interface Replace<A> extends Inspectable.Inspectable {
63
+ readonly [ReplaceTypeId]: typeof ReplaceTypeId;
64
+ /** What the struct holds once the update lands. */
65
+ readonly value: A;
66
+ }
67
+
68
+ /**
69
+ * Checks whether `u` is a {@link Replace}.
70
+ *
71
+ * @stability unstable
72
+ * @category guards
73
+ * @since 0.2.0
74
+ */
75
+ export const isReplace: (u: unknown) => u is Replace<unknown> = patch.isReplace;
76
+
77
+ /**
78
+ * Marks a {@link FirestoreModel.Struct} field in a patch to be written whole,
79
+ * rather than field by field.
80
+ *
81
+ * **When to use**
82
+ *
83
+ * Use to replace a struct field outright, dropping any keys the new value leaves
84
+ * out. It is also the safe choice when the struct may not be in the document
85
+ * yet: a nested patch writes only some of its fields there, while `replace`
86
+ * writes all of them.
87
+ *
88
+ * **Details**
89
+ *
90
+ * `value` is the whole struct, in the shape `setDoc` writes it in. A plain
91
+ * object in the same place is a nested patch that only touches the fields it
92
+ * names. A field declared with a plain `Schema.Struct` is a value, always
93
+ * written whole, and takes neither.
94
+ *
95
+ * @example Replacing a struct
96
+ * ```ts
97
+ * import { Schema } from "effect";
98
+ * import { FirestoreModel, Patch } from "@visiolab/effect-firebase/firestore";
99
+ *
100
+ * class User extends FirestoreModel.Class<User>("User")({
101
+ * address: FirestoreModel.Struct({ city: Schema.String, zip: Schema.String }),
102
+ * }) {}
103
+ *
104
+ * // Writes `address` as `{ city: "Berlin", zip: "10115" }`, whatever it held.
105
+ * User.update.make({ address: Patch.replace({ city: "Berlin", zip: "10115" }) });
106
+ *
107
+ * // Writes `address.city` only, and leaves `address.zip` as it is.
108
+ * User.update.make({ address: { city: "Berlin" } });
109
+ * ```
110
+ *
111
+ * @see {@link Firestore.With.updateDoc} for writing the patch
112
+ *
113
+ * @stability unstable
114
+ * @category constructors
115
+ * @since 0.2.0
116
+ */
117
+ export const replace: <const A>(value: A) => Replace<A> = patch.replace;
@@ -0,0 +1,150 @@
1
+ /**
2
+ * What a read of many documents returns.
3
+ *
4
+ * A read of a collection or a Collection Group finds any number of documents.
5
+ * A query snapshot holds them as one result rather than a bare array, which
6
+ * leaves room for what a backend knows about the read as a whole, such as the
7
+ * client's `metadata`.
8
+ *
9
+ * @example Reading every document in a collection
10
+ * ```ts
11
+ * import { Collection, Firestore, FirestoreModel } from "@visiolab/effect-firebase/firestore";
12
+ * import { Effect, Schema } from "effect";
13
+ *
14
+ * class User extends FirestoreModel.Class<User>("User")({ name: Schema.String }) {}
15
+ *
16
+ * const Users = Collection.make("users", User);
17
+ *
18
+ * const program = Effect.gen(function* () {
19
+ * const firestore = yield* Firestore.Firestore;
20
+ * const snapshot = yield* firestore.getDocs(Users.collection());
21
+ * return yield* Effect.forEach(snapshot.docs, (user) => user.data);
22
+ * });
23
+ * ```
24
+ *
25
+ * @see {@link DocumentSnapshot} for what a read of one document returns
26
+ *
27
+ * @stability unstable
28
+ * @since 0.2.0
29
+ */
30
+ import type { Inspectable } from "effect";
31
+ import type * as Collection from "./Collection.ts";
32
+ import type * as DocumentSnapshot from "./DocumentSnapshot.ts";
33
+ import * as querySnapshot from "./internal/querySnapshot.ts";
34
+
35
+ const TypeId = querySnapshot.TypeId;
36
+
37
+ /**
38
+ * A {@link DocumentSnapshot.Existing} snapshot in whichever of the collections
39
+ * `C` it is in.
40
+ *
41
+ * **Details**
42
+ *
43
+ * For a union `C` it is `Existing<A> | Existing<B>` rather than
44
+ * `Existing<A | B>`, so a document's id, ancestors, reference and data all
45
+ * belong to the same collection.
46
+ *
47
+ * @stability unstable
48
+ * @category models
49
+ * @since 0.2.0
50
+ */
51
+ export type Existing<C extends Collection.Any> = C extends Collection.Any
52
+ ? DocumentSnapshot.Existing<C>
53
+ : never;
54
+
55
+ /**
56
+ * The documents of `C` that a read of many documents found.
57
+ *
58
+ * **Details**
59
+ *
60
+ * {@link Firestore.With.getDocs} returns one, and
61
+ * {@link Firestore.With.streamDocs} emits one per change. `C` is a union when
62
+ * the read was of a Collection Group with several members, and each document
63
+ * is then in one of them.
64
+ *
65
+ * `DocumentExtra` is what a backend adds to each document beyond the portable
66
+ * snapshot, such as the client's `metadata`. What it adds to the read as a
67
+ * whole is not part of this type: it is intersected where the snapshot is
68
+ * used, as `QuerySnapshot<C, E> & E`.
69
+ *
70
+ * @see {@link DocumentSnapshot.DocumentSnapshot} for the result of a read of one document
71
+ *
72
+ * @stability unstable
73
+ * @category models
74
+ * @since 0.2.0
75
+ */
76
+ export interface QuerySnapshot<C extends Collection.Any, DocumentExtra extends object = {}>
77
+ extends Inspectable.Inspectable {
78
+ readonly [TypeId]: typeof TypeId;
79
+ /**
80
+ * Every document the read found, in the order Firestore returned them.
81
+ * Empty when there were none.
82
+ *
83
+ * **Details**
84
+ *
85
+ * Each was there when it was read, so each is a
86
+ * {@link DocumentSnapshot.Existing} in the collection of `C` it lives in,
87
+ * with the `DocumentExtra` of the read.
88
+ *
89
+ * @see {@link Collection.Collection.contains} for keeping the documents of one collection
90
+ */
91
+ readonly docs: ReadonlyArray<Existing<C> & DocumentExtra>;
92
+ }
93
+
94
+ /**
95
+ * The fields a document snapshot `D` has beyond the portable
96
+ * {@link DocumentSnapshot.Existing}, such as the client's `metadata`.
97
+ *
98
+ * @stability unstable
99
+ * @category models
100
+ * @since 0.3.0
101
+ */
102
+ export type ExtraOf<D extends DocumentSnapshot.Existing<Collection.Any>> = {
103
+ [K in keyof D as K extends keyof DocumentSnapshot.Existing<Collection.Any> ? never : K]: D[K];
104
+ };
105
+
106
+ /**
107
+ * Checks whether a value is a {@link QuerySnapshot}.
108
+ *
109
+ * @see {@link DocumentSnapshot.isDocumentSnapshot} for a snapshot of one document
110
+ *
111
+ * @stability unstable
112
+ * @category guards
113
+ * @since 0.2.0
114
+ */
115
+ export const isQuerySnapshot: (u: unknown) => u is QuerySnapshot<Collection.Any> =
116
+ querySnapshot.isQuerySnapshot;
117
+
118
+ /**
119
+ * Builds a {@link QuerySnapshot} from the documents a read found.
120
+ *
121
+ * **When to use**
122
+ *
123
+ * Use when implementing a backend or a test double. Application code gets
124
+ * query snapshots from {@link Firestore.With.getDocs} instead.
125
+ *
126
+ * **Details**
127
+ *
128
+ * An SDK that knows more about the read as a whole, such as the client's
129
+ * `metadata`, passes it as `extra`: each of its fields is put on the snapshot
130
+ * and shown when it prints, as {@link DocumentSnapshot.make} does for one
131
+ * document. Its fields must not have the same names as the snapshot's own.
132
+ *
133
+ * The documents are kept as given, so any extra fields on them stay, and stay
134
+ * in the snapshot's type.
135
+ *
136
+ * @see {@link DocumentSnapshot.existing} for building each document
137
+ *
138
+ * @stability unstable
139
+ * @category constructors
140
+ * @since 0.2.0
141
+ */
142
+ export const make: {
143
+ <C extends Collection.Any, D extends DocumentSnapshot.Existing<C> = DocumentSnapshot.Existing<C>>(
144
+ docs: ReadonlyArray<D> & ReadonlyArray<DocumentSnapshot.Existing<C>>,
145
+ ): QuerySnapshot<C, ExtraOf<D>>;
146
+ <C extends Collection.Any, D extends DocumentSnapshot.Existing<C>, E extends object>(
147
+ docs: ReadonlyArray<D> & ReadonlyArray<DocumentSnapshot.Existing<C>>,
148
+ extra: E,
149
+ ): QuerySnapshot<C, ExtraOf<D>> & E;
150
+ } = querySnapshot.make;