@visiolab/effect-firebase 0.0.0-stage → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +81 -2
- package/dist/_virtual/_rolldown/runtime.mjs +12 -0
- package/dist/app/App.d.mts +27 -0
- package/dist/app/App.mjs +27 -0
- package/dist/app/index.d.mts +2 -0
- package/dist/app/index.mjs +1 -0
- package/dist/firestore/Collection.d.mts +423 -0
- package/dist/firestore/Collection.mjs +133 -0
- package/dist/firestore/CollectionGroup.d.mts +164 -0
- package/dist/firestore/CollectionGroup.mjs +84 -0
- package/dist/firestore/DocumentPath.d.mts +95 -0
- package/dist/firestore/DocumentPath.mjs +71 -0
- package/dist/firestore/DocumentSnapshot.d.mts +239 -0
- package/dist/firestore/DocumentSnapshot.mjs +134 -0
- package/dist/firestore/FieldValue.d.mts +332 -0
- package/dist/firestore/FieldValue.mjs +256 -0
- package/dist/firestore/Firestore.d.mts +1165 -0
- package/dist/firestore/Firestore.mjs +274 -0
- package/dist/firestore/FirestoreModel.d.mts +1232 -0
- package/dist/firestore/FirestoreModel.mjs +586 -0
- package/dist/firestore/FirestoreSchema.d.mts +567 -0
- package/dist/firestore/FirestoreSchema.mjs +419 -0
- package/dist/firestore/FirestoreValue.d.mts +145 -0
- package/dist/firestore/FirestoreValue.mjs +87 -0
- package/dist/firestore/GeoPoint.d.mts +90 -0
- package/dist/firestore/GeoPoint.mjs +75 -0
- package/dist/firestore/Patch.d.mts +73 -0
- package/dist/firestore/Patch.mjs +57 -0
- package/dist/firestore/QuerySnapshot.d.mts +107 -0
- package/dist/firestore/QuerySnapshot.mjs +43 -0
- package/dist/firestore/Timestamp.d.mts +273 -0
- package/dist/firestore/Timestamp.mjs +255 -0
- package/dist/firestore/index.d.mts +14 -0
- package/dist/firestore/index.mjs +14 -0
- package/dist/firestore/internal/collection.mjs +156 -0
- package/dist/firestore/internal/collectionGroup.mjs +64 -0
- package/dist/firestore/internal/documentPath.d.mts +4 -0
- package/dist/firestore/internal/documentPath.mjs +42 -0
- package/dist/firestore/internal/documentSnapshot.mjs +63 -0
- package/dist/firestore/internal/fieldValue.d.mts +11 -0
- package/dist/firestore/internal/fieldValue.mjs +88 -0
- package/dist/firestore/internal/firestore.mjs +145 -0
- package/dist/firestore/internal/firestoreError.d.mts +29 -0
- package/dist/firestore/internal/firestoreError.mjs +30 -0
- package/dist/firestore/internal/firestoreModel.mjs +719 -0
- package/dist/firestore/internal/firestoreSchema.mjs +123 -0
- package/dist/firestore/internal/firestoreValue.mjs +108 -0
- package/dist/firestore/internal/firestoreValueWalk.d.mts +58 -0
- package/dist/firestore/internal/firestoreValueWalk.mjs +223 -0
- package/dist/firestore/internal/geoPoint.d.mts +4 -0
- package/dist/firestore/internal/geoPoint.mjs +39 -0
- package/dist/firestore/internal/patch.mjs +19 -0
- package/dist/firestore/internal/querySnapshot.mjs +24 -0
- package/dist/firestore/internal/schemaAST.mjs +35 -0
- package/dist/firestore/internal/snapshotExtra.mjs +35 -0
- package/dist/firestore/internal/streamFromListener.mjs +12 -0
- package/dist/firestore/internal/timestamp.d.mts +4 -0
- package/dist/firestore/internal/timestamp.mjs +66 -0
- package/dist/firestore/internal/timestampSchema.mjs +31 -0
- package/dist/firestore/internal/transactionFromCallback.mjs +66 -0
- package/dist/functions/Callable.d.mts +654 -0
- package/dist/functions/Callable.mjs +450 -0
- package/dist/functions/CallableError.d.mts +297 -0
- package/dist/functions/CallableError.mjs +256 -0
- package/dist/functions/CallableMiddleware.d.mts +213 -0
- package/dist/functions/CallableMiddleware.mjs +52 -0
- package/dist/functions/CallableRequest.d.mts +166 -0
- package/dist/functions/CallableRequest.mjs +18 -0
- package/dist/functions/Caller.d.mts +103 -0
- package/dist/functions/Caller.mjs +115 -0
- package/dist/functions/DocumentTrigger.d.mts +795 -0
- package/dist/functions/DocumentTrigger.mjs +566 -0
- package/dist/functions/FirebaseFunction.d.mts +79 -0
- package/dist/functions/FirebaseFunction.mjs +4 -0
- package/dist/functions/FunctionGroup.d.mts +175 -0
- package/dist/functions/FunctionGroup.mjs +64 -0
- package/dist/functions/index.d.mts +9 -0
- package/dist/functions/index.mjs +9 -0
- package/dist/functions/internal/callable.mjs +167 -0
- package/dist/functions/internal/callableMiddleware.d.mts +19 -0
- package/dist/functions/internal/callableMiddleware.mjs +15 -0
- package/dist/functions/internal/callableRequest.mjs +6 -0
- package/dist/functions/internal/documentTrigger.d.mts +10 -0
- package/dist/functions/internal/documentTrigger.mjs +73 -0
- package/dist/functions/internal/firebaseFunction.d.mts +5 -0
- package/dist/functions/internal/firebaseFunction.mjs +24 -0
- package/dist/functions/internal/functionGroup.mjs +36 -0
- package/package.json +72 -3
- package/src/app/App.ts +24 -0
- package/src/app/index.ts +7 -0
- package/src/firestore/Collection.ts +520 -0
- package/src/firestore/CollectionGroup.ts +245 -0
- package/src/firestore/DocumentPath.ts +114 -0
- package/src/firestore/DocumentSnapshot.ts +308 -0
- package/src/firestore/FieldValue.ts +375 -0
- package/src/firestore/Firestore.ts +1432 -0
- package/src/firestore/FirestoreModel.ts +1884 -0
- package/src/firestore/FirestoreSchema.ts +643 -0
- package/src/firestore/FirestoreValue.ts +207 -0
- package/src/firestore/GeoPoint.ts +105 -0
- package/src/firestore/Patch.ts +117 -0
- package/src/firestore/QuerySnapshot.ts +150 -0
- package/src/firestore/Timestamp.ts +302 -0
- package/src/firestore/index.ts +67 -0
- package/src/firestore/internal/collection.ts +246 -0
- package/src/firestore/internal/collectionGroup.ts +81 -0
- package/src/firestore/internal/documentPath.ts +53 -0
- package/src/firestore/internal/documentSnapshot.ts +117 -0
- package/src/firestore/internal/fieldValue.ts +119 -0
- package/src/firestore/internal/firestore.ts +322 -0
- package/src/firestore/internal/firestoreError.ts +36 -0
- package/src/firestore/internal/firestoreModel.ts +1242 -0
- package/src/firestore/internal/firestoreSchema.ts +218 -0
- package/src/firestore/internal/firestoreValue.ts +202 -0
- package/src/firestore/internal/firestoreValueWalk.ts +349 -0
- package/src/firestore/internal/geoPoint.ts +59 -0
- package/src/firestore/internal/patch.ts +21 -0
- package/src/firestore/internal/querySnapshot.ts +39 -0
- package/src/firestore/internal/schemaAST.ts +83 -0
- package/src/firestore/internal/snapshotExtra.ts +47 -0
- package/src/firestore/internal/streamFromListener.ts +27 -0
- package/src/firestore/internal/timestamp.ts +119 -0
- package/src/firestore/internal/timestampSchema.ts +42 -0
- package/src/firestore/internal/transactionFromCallback.ts +106 -0
- package/src/functions/Callable.ts +855 -0
- package/src/functions/CallableError.ts +348 -0
- package/src/functions/CallableMiddleware.ts +279 -0
- package/src/functions/CallableRequest.ts +207 -0
- package/src/functions/Caller.ts +141 -0
- package/src/functions/DocumentTrigger.ts +927 -0
- package/src/functions/FirebaseFunction.ts +96 -0
- package/src/functions/FunctionGroup.ts +225 -0
- package/src/functions/index.ts +62 -0
- package/src/functions/internal/callable.ts +272 -0
- package/src/functions/internal/callableMiddleware.ts +54 -0
- package/src/functions/internal/callableRequest.ts +8 -0
- package/src/functions/internal/documentTrigger.ts +143 -0
- package/src/functions/internal/firebaseFunction.ts +58 -0
- package/src/functions/internal/functionGroup.ts +47 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Firestore timestamps, independent of either SDK.
|
|
3
|
+
*
|
|
4
|
+
* Firestore stores an instant as whole seconds since the Unix epoch plus a
|
|
5
|
+
* non-negative count of nanoseconds, and each SDK reads it back as its own
|
|
6
|
+
* `Timestamp` class. This module owns a portable version of that value, so a
|
|
7
|
+
* document schema works the same under the Admin SDK and the client SDK.
|
|
8
|
+
*
|
|
9
|
+
* Its schema is {@link FirestoreSchema.Timestamp}.
|
|
10
|
+
*
|
|
11
|
+
* @see {@link FirestoreSchema.Timestamp} for storing a `Timestamp` in a document
|
|
12
|
+
* @see {@link FirestoreModel.Timestamp} for a timestamp field the server may fill in
|
|
13
|
+
*
|
|
14
|
+
* @stability unstable
|
|
15
|
+
* @since 0.2.0
|
|
16
|
+
*/
|
|
17
|
+
import type { DateTime, Effect, Equal, Inspectable, Option, Order as order } from "effect";
|
|
18
|
+
import * as timestamp from "./internal/timestamp.ts";
|
|
19
|
+
|
|
20
|
+
const TypeId = timestamp.TypeId;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* An instant, stored the way Firestore stores it.
|
|
24
|
+
*
|
|
25
|
+
* **When to use**
|
|
26
|
+
*
|
|
27
|
+
* Use when a document is read, written back, and has to come out unchanged.
|
|
28
|
+
* Firestore keeps timestamps to the microsecond, which `DateTime.Utc` and
|
|
29
|
+
* `Date` cannot hold, so this is the only representation that round-trips
|
|
30
|
+
* exactly.
|
|
31
|
+
*
|
|
32
|
+
* **Details**
|
|
33
|
+
*
|
|
34
|
+
* Two timestamps are equal when their `seconds` and `nanoseconds` are.
|
|
35
|
+
* Firestore addresses instants from the start of year 1 to the end of year
|
|
36
|
+
* 9999, and every constructor here checks for that range.
|
|
37
|
+
*
|
|
38
|
+
* @see {@link FirestoreSchema.Timestamp} for the schema of this value
|
|
39
|
+
*
|
|
40
|
+
* @stability unstable
|
|
41
|
+
* @category models
|
|
42
|
+
* @since 0.2.0
|
|
43
|
+
*/
|
|
44
|
+
export interface Timestamp extends Equal.Equal, Inspectable.Inspectable {
|
|
45
|
+
readonly [TypeId]: typeof TypeId;
|
|
46
|
+
/** Seconds since the Unix epoch, negative before it. */
|
|
47
|
+
readonly seconds: number;
|
|
48
|
+
/** The fraction of that second, from 0 to 999,999,999. */
|
|
49
|
+
readonly nanoseconds: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Checks whether `u` is a {@link Timestamp} built by this module.
|
|
54
|
+
*
|
|
55
|
+
* An SDK's own `Timestamp` is not one.
|
|
56
|
+
*
|
|
57
|
+
* @stability unstable
|
|
58
|
+
* @category guards
|
|
59
|
+
* @since 0.2.0
|
|
60
|
+
*/
|
|
61
|
+
export const isTimestamp: (u: unknown) => u is Timestamp = timestamp.isTimestamp;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Builds a timestamp from seconds and nanoseconds, throwing on values
|
|
65
|
+
* Firestore cannot store.
|
|
66
|
+
*
|
|
67
|
+
* **When to use**
|
|
68
|
+
*
|
|
69
|
+
* Use for values known to be valid, such as constants. Use {@link make} for
|
|
70
|
+
* values you have not checked.
|
|
71
|
+
*
|
|
72
|
+
* **Details**
|
|
73
|
+
*
|
|
74
|
+
* Throws unless `seconds` is an integer from year 1 to year 9999, and
|
|
75
|
+
* `nanoseconds` is an integer from 0 to 999,999,999.
|
|
76
|
+
*
|
|
77
|
+
* @example Building a timestamp
|
|
78
|
+
* ```ts
|
|
79
|
+
* import { Timestamp } from "@visiolab/effect-firebase/firestore";
|
|
80
|
+
*
|
|
81
|
+
* const epoch = Timestamp.makeUnsafe(0, 0);
|
|
82
|
+
* ```
|
|
83
|
+
*
|
|
84
|
+
* @see {@link make} for the same check returning an `Option`
|
|
85
|
+
* @see {@link fromMillisUnsafe} for building one from milliseconds
|
|
86
|
+
*
|
|
87
|
+
* @stability unstable
|
|
88
|
+
* @category constructors
|
|
89
|
+
* @since 0.2.0
|
|
90
|
+
*/
|
|
91
|
+
export const makeUnsafe: (seconds: number, nanoseconds: number) => Timestamp = timestamp.makeUnsafe;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Builds a timestamp from seconds and nanoseconds, or `None` for values
|
|
95
|
+
* Firestore cannot store.
|
|
96
|
+
*
|
|
97
|
+
* **When to use**
|
|
98
|
+
*
|
|
99
|
+
* Use when the fields come from outside the program, such as a request body,
|
|
100
|
+
* and may be invalid.
|
|
101
|
+
*
|
|
102
|
+
* **Details**
|
|
103
|
+
*
|
|
104
|
+
* Returns `None` unless `seconds` is an integer from year 1 to year 9999, and
|
|
105
|
+
* `nanoseconds` is an integer from 0 to 999,999,999.
|
|
106
|
+
*
|
|
107
|
+
* @example Building a timestamp from untrusted input
|
|
108
|
+
* ```ts
|
|
109
|
+
* import { Option } from "effect";
|
|
110
|
+
* import { Timestamp } from "@visiolab/effect-firebase/firestore";
|
|
111
|
+
*
|
|
112
|
+
* const valid = Timestamp.make(1_700_000_000, 500); // Option.some(...)
|
|
113
|
+
* const invalid = Timestamp.make(0, -1); // Option.none()
|
|
114
|
+
*
|
|
115
|
+
* Option.isSome(valid); // true
|
|
116
|
+
* Option.isNone(invalid); // true
|
|
117
|
+
* ```
|
|
118
|
+
*
|
|
119
|
+
* @see {@link makeUnsafe} for the same check throwing instead
|
|
120
|
+
* @see {@link fromMillis} for building one from milliseconds
|
|
121
|
+
*
|
|
122
|
+
* @stability unstable
|
|
123
|
+
* @category constructors
|
|
124
|
+
* @since 0.2.0
|
|
125
|
+
*/
|
|
126
|
+
export const make: (seconds: number, nanoseconds: number) => Option.Option<Timestamp> =
|
|
127
|
+
timestamp.make;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Builds a timestamp from milliseconds since the Unix epoch, throwing outside
|
|
131
|
+
* the range Firestore stores.
|
|
132
|
+
*
|
|
133
|
+
* **When to use**
|
|
134
|
+
*
|
|
135
|
+
* Use with millisecond values known to be in range, such as `Date.now()`.
|
|
136
|
+
*
|
|
137
|
+
* @example Building a timestamp from milliseconds
|
|
138
|
+
* ```ts
|
|
139
|
+
* import { Timestamp } from "@visiolab/effect-firebase/firestore";
|
|
140
|
+
*
|
|
141
|
+
* const timestamp = Timestamp.fromMillisUnsafe(Date.now());
|
|
142
|
+
* ```
|
|
143
|
+
*
|
|
144
|
+
* @see {@link fromMillis} for the same check returning an `Option`
|
|
145
|
+
* @see {@link makeUnsafe} for building one from seconds and nanoseconds
|
|
146
|
+
*
|
|
147
|
+
* @stability unstable
|
|
148
|
+
* @category constructors
|
|
149
|
+
* @since 0.2.0
|
|
150
|
+
*/
|
|
151
|
+
export const fromMillisUnsafe: (millis: number) => Timestamp = timestamp.fromMillisUnsafe;
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Builds a timestamp from milliseconds since the Unix epoch, or `None` outside
|
|
155
|
+
* the range Firestore stores.
|
|
156
|
+
*
|
|
157
|
+
* **When to use**
|
|
158
|
+
*
|
|
159
|
+
* Use with millisecond values that may be out of range or not finite.
|
|
160
|
+
*
|
|
161
|
+
* @see {@link fromMillisUnsafe} for the same check throwing instead
|
|
162
|
+
* @see {@link toMillis} for the reverse conversion
|
|
163
|
+
*
|
|
164
|
+
* @stability unstable
|
|
165
|
+
* @category constructors
|
|
166
|
+
* @since 0.2.0
|
|
167
|
+
*/
|
|
168
|
+
export const fromMillis: (millis: number) => Option.Option<Timestamp> = timestamp.fromMillis;
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Builds a timestamp from a `DateTime.Utc`, throwing outside the range
|
|
172
|
+
* Firestore stores.
|
|
173
|
+
*
|
|
174
|
+
* **When to use**
|
|
175
|
+
*
|
|
176
|
+
* Use with a `DateTime` known to fall between year 1 and year 9999, which
|
|
177
|
+
* covers almost every real date.
|
|
178
|
+
*
|
|
179
|
+
* **Details**
|
|
180
|
+
*
|
|
181
|
+
* A `DateTime.Utc` only carries milliseconds, so the result has no finer
|
|
182
|
+
* digits.
|
|
183
|
+
*
|
|
184
|
+
* @example Converting a DateTime
|
|
185
|
+
* ```ts
|
|
186
|
+
* import { DateTime } from "effect";
|
|
187
|
+
* import { Timestamp } from "@visiolab/effect-firebase/firestore";
|
|
188
|
+
*
|
|
189
|
+
* const timestamp = Timestamp.fromDateTimeUnsafe(DateTime.makeUnsafe("2024-01-01T00:00:00Z"));
|
|
190
|
+
* ```
|
|
191
|
+
*
|
|
192
|
+
* @see {@link fromDateTime} for the same check returning an `Option`
|
|
193
|
+
* @see {@link toDateTime} for the reverse conversion
|
|
194
|
+
*
|
|
195
|
+
* @stability unstable
|
|
196
|
+
* @category constructors
|
|
197
|
+
* @since 0.2.0
|
|
198
|
+
*/
|
|
199
|
+
export const fromDateTimeUnsafe: (dateTime: DateTime.Utc) => Timestamp =
|
|
200
|
+
timestamp.fromDateTimeUnsafe;
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Builds a timestamp from a `DateTime.Utc`, or `None` outside the range
|
|
204
|
+
* Firestore stores.
|
|
205
|
+
*
|
|
206
|
+
* **When to use**
|
|
207
|
+
*
|
|
208
|
+
* Use with a `DateTime` that may fall outside year 1 to year 9999, such as one
|
|
209
|
+
* parsed from user input.
|
|
210
|
+
*
|
|
211
|
+
* **Details**
|
|
212
|
+
*
|
|
213
|
+
* A `DateTime.Utc` only carries milliseconds, so the result has no finer
|
|
214
|
+
* digits.
|
|
215
|
+
*
|
|
216
|
+
* @see {@link fromDateTimeUnsafe} for the same check throwing instead
|
|
217
|
+
* @see {@link toDateTime} for the reverse conversion
|
|
218
|
+
*
|
|
219
|
+
* @stability unstable
|
|
220
|
+
* @category constructors
|
|
221
|
+
* @since 0.2.0
|
|
222
|
+
*/
|
|
223
|
+
export const fromDateTime: (dateTime: DateTime.Utc) => Option.Option<Timestamp> =
|
|
224
|
+
timestamp.fromDateTime;
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* The current instant, read from Effect's `Clock`, to the millisecond.
|
|
228
|
+
*
|
|
229
|
+
* **When to use**
|
|
230
|
+
*
|
|
231
|
+
* Use when the time this program sees is the one to store, and when a test
|
|
232
|
+
* should control it through `TestClock`.
|
|
233
|
+
*
|
|
234
|
+
* **Details**
|
|
235
|
+
*
|
|
236
|
+
* This is the local clock, not the database's. Clients with skewed clocks
|
|
237
|
+
* write different times for the same moment.
|
|
238
|
+
*
|
|
239
|
+
* @example Reading the current time
|
|
240
|
+
* ```ts
|
|
241
|
+
* import { Effect } from "effect";
|
|
242
|
+
* import { Timestamp } from "@visiolab/effect-firebase/firestore";
|
|
243
|
+
*
|
|
244
|
+
* const program = Effect.gen(function* () {
|
|
245
|
+
* const now = yield* Timestamp.now;
|
|
246
|
+
* return Timestamp.toMillis(now);
|
|
247
|
+
* });
|
|
248
|
+
* ```
|
|
249
|
+
*
|
|
250
|
+
* @see {@link FieldValue.serverTimestamp} for the time the server applies the write
|
|
251
|
+
*
|
|
252
|
+
* @stability unstable
|
|
253
|
+
* @category constructors
|
|
254
|
+
* @since 0.2.0
|
|
255
|
+
*/
|
|
256
|
+
export const now: Effect.Effect<Timestamp> = timestamp.now;
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Converts a timestamp to milliseconds since the Unix epoch, dropping the
|
|
260
|
+
* finer digits.
|
|
261
|
+
*
|
|
262
|
+
* @see {@link fromMillis} for the reverse conversion
|
|
263
|
+
* @see {@link toDateTime} for converting to a `DateTime.Utc`
|
|
264
|
+
*
|
|
265
|
+
* @stability unstable
|
|
266
|
+
* @category conversions
|
|
267
|
+
* @since 0.2.0
|
|
268
|
+
*/
|
|
269
|
+
export const toMillis: (self: Timestamp) => number = timestamp.toMillis;
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Converts a timestamp to a `DateTime.Utc`, dropping the digits finer than a
|
|
273
|
+
* millisecond.
|
|
274
|
+
*
|
|
275
|
+
* @see {@link fromDateTime} for the reverse conversion
|
|
276
|
+
* @see {@link toMillis} for converting to milliseconds
|
|
277
|
+
*
|
|
278
|
+
* @stability unstable
|
|
279
|
+
* @category conversions
|
|
280
|
+
* @since 0.2.0
|
|
281
|
+
*/
|
|
282
|
+
export const toDateTime: (self: Timestamp) => DateTime.Utc = timestamp.toDateTime;
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Orders timestamps from earliest to latest, at full precision.
|
|
286
|
+
*
|
|
287
|
+
* @example Sorting timestamps
|
|
288
|
+
* ```ts
|
|
289
|
+
* import { Array } from "effect";
|
|
290
|
+
* import { Timestamp } from "@visiolab/effect-firebase/firestore";
|
|
291
|
+
*
|
|
292
|
+
* const sorted = Array.sort(
|
|
293
|
+
* [Timestamp.makeUnsafe(2, 0), Timestamp.makeUnsafe(1, 500)],
|
|
294
|
+
* Timestamp.Order,
|
|
295
|
+
* );
|
|
296
|
+
* ```
|
|
297
|
+
*
|
|
298
|
+
* @stability unstable
|
|
299
|
+
* @category ordering
|
|
300
|
+
* @since 0.2.0
|
|
301
|
+
*/
|
|
302
|
+
export const Order: order.Order<Timestamp> = timestamp.Order;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Firestore.
|
|
3
|
+
*
|
|
4
|
+
* @stability unstable
|
|
5
|
+
* @since 0.2.0
|
|
6
|
+
*/
|
|
7
|
+
export * as Collection from "./Collection.ts";
|
|
8
|
+
/**
|
|
9
|
+
* @stability unstable
|
|
10
|
+
* @since 0.2.0
|
|
11
|
+
*/
|
|
12
|
+
export * as CollectionGroup from "./CollectionGroup.ts";
|
|
13
|
+
/**
|
|
14
|
+
* @stability unstable
|
|
15
|
+
* @since 0.2.0
|
|
16
|
+
*/
|
|
17
|
+
export * as DocumentPath from "./DocumentPath.ts";
|
|
18
|
+
/**
|
|
19
|
+
* @stability unstable
|
|
20
|
+
* @since 0.2.0
|
|
21
|
+
*/
|
|
22
|
+
export * as DocumentSnapshot from "./DocumentSnapshot.ts";
|
|
23
|
+
/**
|
|
24
|
+
* @stability unstable
|
|
25
|
+
* @since 0.2.0
|
|
26
|
+
*/
|
|
27
|
+
export * as FieldValue from "./FieldValue.ts";
|
|
28
|
+
/**
|
|
29
|
+
* @stability unstable
|
|
30
|
+
* @since 0.2.0
|
|
31
|
+
*/
|
|
32
|
+
export * as Firestore from "./Firestore.ts";
|
|
33
|
+
/**
|
|
34
|
+
* @stability unstable
|
|
35
|
+
* @since 0.2.0
|
|
36
|
+
*/
|
|
37
|
+
export * as FirestoreModel from "./FirestoreModel.ts";
|
|
38
|
+
/**
|
|
39
|
+
* @stability unstable
|
|
40
|
+
* @since 0.2.0
|
|
41
|
+
*/
|
|
42
|
+
export * as FirestoreSchema from "./FirestoreSchema.ts";
|
|
43
|
+
/**
|
|
44
|
+
* @stability unstable
|
|
45
|
+
* @since 0.2.0
|
|
46
|
+
*/
|
|
47
|
+
export * as FirestoreValue from "./FirestoreValue.ts";
|
|
48
|
+
/**
|
|
49
|
+
* @stability unstable
|
|
50
|
+
* @since 0.2.0
|
|
51
|
+
*/
|
|
52
|
+
export * as GeoPoint from "./GeoPoint.ts";
|
|
53
|
+
/**
|
|
54
|
+
* @stability unstable
|
|
55
|
+
* @since 0.2.0
|
|
56
|
+
*/
|
|
57
|
+
export * as Patch from "./Patch.ts";
|
|
58
|
+
/**
|
|
59
|
+
* @stability unstable
|
|
60
|
+
* @since 0.2.0
|
|
61
|
+
*/
|
|
62
|
+
export * as QuerySnapshot from "./QuerySnapshot.ts";
|
|
63
|
+
/**
|
|
64
|
+
* @stability unstable
|
|
65
|
+
* @since 0.2.0
|
|
66
|
+
*/
|
|
67
|
+
export * as Timestamp from "./Timestamp.ts";
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { Equal, Hash, Inspectable, Option, Predicate, Schema } from "effect";
|
|
2
|
+
import type * as Collection from "../Collection.ts";
|
|
3
|
+
import type * as FirestoreModel from "../FirestoreModel.ts";
|
|
4
|
+
import * as documentSnapshot from "./documentSnapshot.ts";
|
|
5
|
+
|
|
6
|
+
/** @internal */
|
|
7
|
+
export const CollectionTypeId = "~@visiolab/effect-firebase/firestore/Collection";
|
|
8
|
+
|
|
9
|
+
/** @internal */
|
|
10
|
+
export const CollectionReferenceTypeId = "~@visiolab/effect-firebase/firestore/CollectionReference";
|
|
11
|
+
|
|
12
|
+
/** @internal */
|
|
13
|
+
export const DocumentReferenceTypeId = "~@visiolab/effect-firebase/firestore/DocumentReference";
|
|
14
|
+
|
|
15
|
+
/** @internal */
|
|
16
|
+
export const isCollection = (u: unknown): u is Collection.Any =>
|
|
17
|
+
Predicate.hasProperty(u, CollectionTypeId);
|
|
18
|
+
|
|
19
|
+
/** @internal */
|
|
20
|
+
export const isCollectionReference = (
|
|
21
|
+
u: unknown,
|
|
22
|
+
): u is Collection.CollectionReference<Collection.Any> =>
|
|
23
|
+
Predicate.hasProperty(u, CollectionReferenceTypeId);
|
|
24
|
+
|
|
25
|
+
/** @internal */
|
|
26
|
+
export const isDocumentReference = (
|
|
27
|
+
u: unknown,
|
|
28
|
+
): u is Collection.DocumentReference<Collection.Any> =>
|
|
29
|
+
Predicate.hasProperty(u, DocumentReferenceTypeId);
|
|
30
|
+
|
|
31
|
+
const encoder = new TextEncoder();
|
|
32
|
+
|
|
33
|
+
/** Rejects the segments Firestore cannot address, before they reach a path. */
|
|
34
|
+
const validateSegment = (segment: string): void => {
|
|
35
|
+
const reject = (reason: string): never => {
|
|
36
|
+
throw new Error(`Invalid Firestore collection id "${segment}": ${reason}`);
|
|
37
|
+
};
|
|
38
|
+
if (segment.length === 0) reject("it must not be empty");
|
|
39
|
+
if (segment.includes("/")) reject('it must not contain "/"');
|
|
40
|
+
if (segment === "." || segment === "..") reject('it must not be "." or ".."');
|
|
41
|
+
if (/^__.*__$/.test(segment)) reject('it must not match "__.*__"');
|
|
42
|
+
if (encoder.encode(segment).length > 1500) reject("it must be at most 1500 bytes");
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Brands an id schema with the path addressing it.
|
|
47
|
+
*
|
|
48
|
+
* The path is only known at runtime, and `Schema.brand` rejects an open `string`
|
|
49
|
+
* key. Narrowing the key to a placeholder literal keeps the real path on the
|
|
50
|
+
* annotation: the static brand is erased where `makeCollection` takes the schema
|
|
51
|
+
* as `Schema.Top`, and the public id types are built above it.
|
|
52
|
+
*/
|
|
53
|
+
const brandId = (path: ReadonlyArray<string>) =>
|
|
54
|
+
Schema.String.pipe(Schema.brand(path.join("/") as "~firestore/collection-path"));
|
|
55
|
+
|
|
56
|
+
/** Interleaves the segments with the ids addressing them: `a/1/b/2/c`. */
|
|
57
|
+
const joinPath = (segments: ReadonlyArray<string>, ancestors: ReadonlyArray<string>): string => {
|
|
58
|
+
let path = segments[0];
|
|
59
|
+
for (let i = 0; i < ancestors.length; i++) path += `/${ancestors[i]}/${segments[i + 1]}`;
|
|
60
|
+
return path;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The untyped shapes behind the exported ones.
|
|
65
|
+
*
|
|
66
|
+
* Everything below works on plain segments and ids. The public types are what
|
|
67
|
+
* make a path only buildable out of the ids that address it, so the runtime has
|
|
68
|
+
* no work left to do beyond joining strings.
|
|
69
|
+
*/
|
|
70
|
+
interface CollectionImpl {
|
|
71
|
+
readonly path: ReadonlyArray<string>;
|
|
72
|
+
readonly segment: string;
|
|
73
|
+
readonly schema: FirestoreModel.Any;
|
|
74
|
+
readonly id: Schema.Top;
|
|
75
|
+
contains(value: unknown): boolean;
|
|
76
|
+
subcollection(segment: string, schema: FirestoreModel.Any): CollectionImpl;
|
|
77
|
+
collection(...ancestors: ReadonlyArray<string>): CollectionReferenceImpl;
|
|
78
|
+
doc(...ids: ReadonlyArray<string>): DocumentReferenceImpl;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
interface CollectionReferenceImpl {
|
|
82
|
+
readonly collection: CollectionImpl;
|
|
83
|
+
readonly ancestors: ReadonlyArray<string>;
|
|
84
|
+
readonly id: string;
|
|
85
|
+
readonly path: string;
|
|
86
|
+
doc(id: string): DocumentReferenceImpl;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
interface DocumentReferenceImpl {
|
|
90
|
+
readonly collection: CollectionImpl;
|
|
91
|
+
readonly ancestors: ReadonlyArray<string>;
|
|
92
|
+
readonly id: string;
|
|
93
|
+
readonly path: string;
|
|
94
|
+
readonly parent: CollectionReferenceImpl;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const CollectionProto = {
|
|
98
|
+
...Inspectable.BaseProto,
|
|
99
|
+
[CollectionTypeId]: CollectionTypeId,
|
|
100
|
+
toJSON(this: CollectionImpl) {
|
|
101
|
+
return { _id: "Collection", path: this.path.join("/") };
|
|
102
|
+
},
|
|
103
|
+
subcollection(this: CollectionImpl, segment: string, schema: FirestoreModel.Any): CollectionImpl {
|
|
104
|
+
validateSegment(segment);
|
|
105
|
+
const path = [...this.path, segment];
|
|
106
|
+
return makeCollection(path, schema, brandId(path));
|
|
107
|
+
},
|
|
108
|
+
collection(this: CollectionImpl, ...ancestors: ReadonlyArray<string>): CollectionReferenceImpl {
|
|
109
|
+
return makeCollectionReference(this, ancestors);
|
|
110
|
+
},
|
|
111
|
+
doc(this: CollectionImpl, ...ids: ReadonlyArray<string>): DocumentReferenceImpl {
|
|
112
|
+
const parent = makeCollectionReference(this, ids.slice(0, -1));
|
|
113
|
+
return makeDocumentReference(parent, ids[ids.length - 1]);
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* The target is asserted as the prototype rather than as the finished shape, so
|
|
119
|
+
* that the result type is inferred from the fields below. The return annotation
|
|
120
|
+
* then catches a field that is missing, misspelled or of the wrong type -
|
|
121
|
+
* asserting the finished shape up front would check none of the three.
|
|
122
|
+
*/
|
|
123
|
+
const makeCollection = (
|
|
124
|
+
path: ReadonlyArray<string>,
|
|
125
|
+
schema: FirestoreModel.Any,
|
|
126
|
+
id: Schema.Top,
|
|
127
|
+
): CollectionImpl => {
|
|
128
|
+
const self: CollectionImpl = Object.assign(
|
|
129
|
+
Object.create(CollectionProto) as typeof CollectionProto,
|
|
130
|
+
{
|
|
131
|
+
path,
|
|
132
|
+
segment: path[path.length - 1],
|
|
133
|
+
schema,
|
|
134
|
+
id,
|
|
135
|
+
// An own closure rather than a method on the prototype, so that it can be
|
|
136
|
+
// passed on unbound, as in `docs.filter(Posts.contains)`. The definition
|
|
137
|
+
// is compared by reference, as references compare theirs in `Equal`.
|
|
138
|
+
contains: (value: unknown): boolean =>
|
|
139
|
+
isDocumentReference(value)
|
|
140
|
+
? value.collection === self
|
|
141
|
+
: documentSnapshot.isDocumentSnapshot(value) && value.ref.collection === self,
|
|
142
|
+
},
|
|
143
|
+
);
|
|
144
|
+
return self;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const CollectionReferenceProto = {
|
|
148
|
+
...Inspectable.BaseProto,
|
|
149
|
+
[CollectionReferenceTypeId]: CollectionReferenceTypeId,
|
|
150
|
+
toJSON(this: CollectionReferenceImpl) {
|
|
151
|
+
return { _id: "CollectionReference", path: this.path };
|
|
152
|
+
},
|
|
153
|
+
// The definition is compared by reference: it is written once and named, and
|
|
154
|
+
// `Equal.equals` would otherwise walk the whole schema behind it.
|
|
155
|
+
[Equal.symbol](this: CollectionReferenceImpl, that: unknown): boolean {
|
|
156
|
+
return (
|
|
157
|
+
isCollectionReference(that) && this.path === that.path && this.collection === that.collection
|
|
158
|
+
);
|
|
159
|
+
},
|
|
160
|
+
[Hash.symbol](this: CollectionReferenceImpl): number {
|
|
161
|
+
return Hash.combine(Hash.string(this.path), Hash.random(this.collection));
|
|
162
|
+
},
|
|
163
|
+
doc(this: CollectionReferenceImpl, id: string): DocumentReferenceImpl {
|
|
164
|
+
return makeDocumentReference(this, id);
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const makeCollectionReference = (
|
|
169
|
+
collection: CollectionImpl,
|
|
170
|
+
ancestors: ReadonlyArray<string>,
|
|
171
|
+
): CollectionReferenceImpl =>
|
|
172
|
+
Object.assign(Object.create(CollectionReferenceProto) as typeof CollectionReferenceProto, {
|
|
173
|
+
collection,
|
|
174
|
+
ancestors,
|
|
175
|
+
id: collection.segment,
|
|
176
|
+
path: joinPath(collection.path, ancestors),
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const DocumentReferenceProto = {
|
|
180
|
+
...Inspectable.BaseProto,
|
|
181
|
+
[DocumentReferenceTypeId]: DocumentReferenceTypeId,
|
|
182
|
+
toJSON(this: DocumentReferenceImpl) {
|
|
183
|
+
return { _id: "DocumentReference", path: this.path };
|
|
184
|
+
},
|
|
185
|
+
// The definition is compared by reference: it is written once and named, and
|
|
186
|
+
// `Equal.equals` would otherwise walk the whole schema behind it.
|
|
187
|
+
[Equal.symbol](this: DocumentReferenceImpl, that: unknown): boolean {
|
|
188
|
+
return (
|
|
189
|
+
isDocumentReference(that) && this.path === that.path && this.collection === that.collection
|
|
190
|
+
);
|
|
191
|
+
},
|
|
192
|
+
[Hash.symbol](this: DocumentReferenceImpl): number {
|
|
193
|
+
return Hash.combine(Hash.string(this.path), Hash.random(this.collection));
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
/** A document is always built from the collection holding it, which it keeps. */
|
|
198
|
+
const makeDocumentReference = (
|
|
199
|
+
parent: CollectionReferenceImpl,
|
|
200
|
+
id: string,
|
|
201
|
+
): DocumentReferenceImpl =>
|
|
202
|
+
Object.assign(Object.create(DocumentReferenceProto) as typeof DocumentReferenceProto, {
|
|
203
|
+
collection: parent.collection,
|
|
204
|
+
ancestors: parent.ancestors,
|
|
205
|
+
id,
|
|
206
|
+
path: `${parent.path}/${id}`,
|
|
207
|
+
parent,
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
/** @internal */
|
|
211
|
+
export const make = <const Segment extends string, S extends FirestoreModel.Any>(
|
|
212
|
+
segment: Segment,
|
|
213
|
+
schema: S,
|
|
214
|
+
): Collection.Collection<readonly [readonly [Segment, S]]> => {
|
|
215
|
+
validateSegment(segment);
|
|
216
|
+
const path = [segment];
|
|
217
|
+
return makeCollection(path, schema, brandId(path)) as unknown as Collection.Collection<
|
|
218
|
+
readonly [readonly [Segment, S]]
|
|
219
|
+
>;
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
/** @internal */
|
|
223
|
+
export const fromPath = <C extends Collection.Any>(
|
|
224
|
+
collection: C,
|
|
225
|
+
path: string,
|
|
226
|
+
): Option.Option<Collection.DocumentReference<C>> => {
|
|
227
|
+
const self = collection as unknown as CollectionImpl;
|
|
228
|
+
const parts = path.split("/");
|
|
229
|
+
if (parts.length !== self.path.length * 2) return Option.none();
|
|
230
|
+
const ids: Array<string> = [];
|
|
231
|
+
for (let i = 0; i < self.path.length; i++) {
|
|
232
|
+
const id = parts[2 * i + 1];
|
|
233
|
+
if (parts[2 * i] !== self.path[i] || id.length === 0) return Option.none();
|
|
234
|
+
ids.push(id);
|
|
235
|
+
}
|
|
236
|
+
return Option.some(self.doc(...ids) as unknown as Collection.DocumentReference<C>);
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* The path a document in `collection` is found at, with `{id}` standing in for
|
|
241
|
+
* each id: `users/{id}/posts/{id}`.
|
|
242
|
+
*
|
|
243
|
+
* @internal
|
|
244
|
+
*/
|
|
245
|
+
export const pattern = (collection: Collection.Any): string =>
|
|
246
|
+
(collection as unknown as CollectionImpl).path.map((segment) => `${segment}/{id}`).join("/");
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { Inspectable, Option, Predicate, Result } from "effect";
|
|
2
|
+
import type * as Collection from "../Collection.ts";
|
|
3
|
+
import type * as CollectionGroup from "../CollectionGroup.ts";
|
|
4
|
+
import { UndefinedCollectionError } from "./firestoreError.ts";
|
|
5
|
+
import * as collection from "./collection.ts";
|
|
6
|
+
|
|
7
|
+
/** @internal */
|
|
8
|
+
export const TypeId = "~@visiolab/effect-firebase/firestore/CollectionGroup";
|
|
9
|
+
|
|
10
|
+
/** @internal */
|
|
11
|
+
export const isCollectionGroup = (u: unknown): u is CollectionGroup.Any =>
|
|
12
|
+
Predicate.hasProperty(u, TypeId);
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Where `member`'s collections sit, with `{id}` standing in for each ancestor
|
|
16
|
+
* id: `users/{id}/posts`.
|
|
17
|
+
*/
|
|
18
|
+
const memberPattern = (member: Collection.Any): string =>
|
|
19
|
+
collection.pattern(member).slice(0, -"/{id}".length);
|
|
20
|
+
|
|
21
|
+
const CollectionGroupProto = {
|
|
22
|
+
...Inspectable.BaseProto,
|
|
23
|
+
[TypeId]: TypeId,
|
|
24
|
+
toJSON(this: CollectionGroup.Any) {
|
|
25
|
+
return { _id: "CollectionGroup", members: this.members.map(memberPattern) };
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Rejects the groups that would not mean one thing: one with nothing in it,
|
|
31
|
+
* one whose collections Firestore could not read under one collection id, and
|
|
32
|
+
* one with two collections at the same path, which no document could be told
|
|
33
|
+
* apart between.
|
|
34
|
+
*/
|
|
35
|
+
const validate = (members: ReadonlyArray<Collection.Any>): void => {
|
|
36
|
+
const reject = (reason: string): never => {
|
|
37
|
+
throw new Error(`Invalid collection group: ${reason}`);
|
|
38
|
+
};
|
|
39
|
+
if (members.length === 0) reject("it must hold at least one collection");
|
|
40
|
+
const [first] = members;
|
|
41
|
+
const seen = new Set<string>();
|
|
42
|
+
for (const member of members) {
|
|
43
|
+
if (member.segment !== first.segment) {
|
|
44
|
+
reject(
|
|
45
|
+
`"${memberPattern(first)}" and "${memberPattern(member)}" do not share their collection id`,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
const pattern = memberPattern(member);
|
|
49
|
+
if (seen.has(pattern)) reject(`it has more than one member at "${pattern}"`);
|
|
50
|
+
seen.add(pattern);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/** @internal */
|
|
55
|
+
export const make = <const Members extends readonly [Collection.Any, ...Array<Collection.Any>]>(
|
|
56
|
+
...valid: CollectionGroup.Valid<Members>
|
|
57
|
+
): CollectionGroup.CollectionGroup<Members[number]> => {
|
|
58
|
+
// The type checker has refused anything that is not one of `Members`, but
|
|
59
|
+
// a caller that got past it is checked again here.
|
|
60
|
+
const members = valid as ReadonlyArray<unknown> as Members;
|
|
61
|
+
validate(members);
|
|
62
|
+
return Object.assign(Object.create(CollectionGroupProto) as typeof CollectionGroupProto, {
|
|
63
|
+
members,
|
|
64
|
+
id: members[0].segment as string,
|
|
65
|
+
}) as CollectionGroup.CollectionGroup<Members[number]>;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/** @internal */
|
|
69
|
+
export const documentAt = <G extends CollectionGroup.Any>(
|
|
70
|
+
group: G,
|
|
71
|
+
path: string,
|
|
72
|
+
): Result.Result<CollectionGroup.DocumentReference<G>, UndefinedCollectionError> => {
|
|
73
|
+
// The members are read as `Collection.Any`, so the reference found in one is
|
|
74
|
+
// typed back by the member it was found in.
|
|
75
|
+
for (const member of group.members) {
|
|
76
|
+
const ref = collection.fromPath(member, path);
|
|
77
|
+
if (Option.isSome(ref))
|
|
78
|
+
return Result.succeed(ref.value as CollectionGroup.DocumentReference<G>);
|
|
79
|
+
}
|
|
80
|
+
return Result.fail(new UndefinedCollectionError({ path, collectionId: group.id }));
|
|
81
|
+
};
|