@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.
- 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 +66 -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,349 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one walk over Firestore Values that the guard and both SDK mappings
|
|
3
|
+
* share: the path to each value, the scalars, arrays and maps, and how a
|
|
4
|
+
* value outside the union is reported. What differs per SDK is only how each
|
|
5
|
+
* kind of value in {@link kinds} becomes the SDK's own, and back.
|
|
6
|
+
*/
|
|
7
|
+
import { Equal, Result } from "effect";
|
|
8
|
+
import { FirestoreError } from "./firestoreError.ts";
|
|
9
|
+
import type * as FirestoreValue from "../FirestoreValue.ts";
|
|
10
|
+
import * as documentPath from "./documentPath.ts";
|
|
11
|
+
import * as fieldValue from "./fieldValue.ts";
|
|
12
|
+
import * as geoPoint from "./geoPoint.ts";
|
|
13
|
+
import * as timestamp from "./timestamp.ts";
|
|
14
|
+
|
|
15
|
+
/** @internal */
|
|
16
|
+
export type FieldPath = ReadonlyArray<string | number>;
|
|
17
|
+
|
|
18
|
+
/** @internal */
|
|
19
|
+
export const formatFieldPath = (path: FieldPath): string =>
|
|
20
|
+
path.length === 0
|
|
21
|
+
? "the document"
|
|
22
|
+
: `"${path
|
|
23
|
+
.map((key, i) => (typeof key === "number" ? `[${key}]` : i === 0 ? key : `.${key}`))
|
|
24
|
+
.join("")}"`;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The kinds of Firestore Value other than scalars, arrays and maps, each with
|
|
28
|
+
* the guard that recognises it. Adding a kind here makes every SDK's
|
|
29
|
+
* {@link Writer} name it too.
|
|
30
|
+
*/
|
|
31
|
+
const kinds = {
|
|
32
|
+
bytes: (u: unknown): u is Uint8Array => u instanceof Uint8Array,
|
|
33
|
+
timestamp: timestamp.isTimestamp,
|
|
34
|
+
geoPoint: geoPoint.isGeoPoint,
|
|
35
|
+
documentPath: documentPath.isDocumentPath,
|
|
36
|
+
serverTimestamp: fieldValue.isServerTimestamp,
|
|
37
|
+
increment: fieldValue.isIncrement,
|
|
38
|
+
deleteField: fieldValue.isDeleteField,
|
|
39
|
+
arrayUnion: fieldValue.isArrayUnion,
|
|
40
|
+
arrayRemove: fieldValue.isArrayRemove,
|
|
41
|
+
} as const;
|
|
42
|
+
|
|
43
|
+
type Guarded<F> = F extends ((u: unknown) => u is infer A) ? A : never;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Each kind of Firestore Value that is not a scalar, array or map, by name.
|
|
47
|
+
*
|
|
48
|
+
* @internal
|
|
49
|
+
*/
|
|
50
|
+
export type Kinds = { readonly [K in keyof typeof kinds]: Guarded<(typeof kinds)[K]> };
|
|
51
|
+
|
|
52
|
+
const kindNames = Object.keys(kinds) as ReadonlyArray<keyof Kinds>;
|
|
53
|
+
|
|
54
|
+
const kindOf = (u: object): keyof Kinds | undefined => kindNames.find((kind) => kinds[kind](u));
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Walks the value a kind holds, such as an array union's elements: `key` is
|
|
58
|
+
* where `child` sits within it.
|
|
59
|
+
*
|
|
60
|
+
* @internal
|
|
61
|
+
*/
|
|
62
|
+
export type Walk = (child: unknown, key: string | number) => unknown;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* How one SDK writes each kind of Firestore Value that is not a scalar, array
|
|
66
|
+
* or map.
|
|
67
|
+
*
|
|
68
|
+
* @internal
|
|
69
|
+
*/
|
|
70
|
+
export type Writer = {
|
|
71
|
+
readonly [K in keyof Kinds]: (value: Kinds[K], walk: Walk) => unknown;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* How one SDK's values read back as Firestore Values. It hands back
|
|
76
|
+
* `undefined` for a value it leaves to the walk - a scalar, array or map -
|
|
77
|
+
* and calls `fail` for one of its own it cannot read.
|
|
78
|
+
*
|
|
79
|
+
* @internal
|
|
80
|
+
*/
|
|
81
|
+
export type Reader = (
|
|
82
|
+
value: object,
|
|
83
|
+
context: {
|
|
84
|
+
readonly walk: (child: unknown, key: string | number) => FirestoreValue.FirestoreValue;
|
|
85
|
+
readonly fail: (kind: string) => never;
|
|
86
|
+
},
|
|
87
|
+
) => FirestoreValue.FirestoreValue | undefined;
|
|
88
|
+
|
|
89
|
+
/** A map: a plain object whose keys are all strings. */
|
|
90
|
+
const isMap = (u: object): u is Record<string, unknown> => {
|
|
91
|
+
const proto = Object.getPrototypeOf(u);
|
|
92
|
+
return (
|
|
93
|
+
(proto === Object.prototype || proto === null) && Object.getOwnPropertySymbols(u).length === 0
|
|
94
|
+
);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const describe = (u: object): string => {
|
|
98
|
+
const name = (u.constructor as { readonly name?: string } | undefined)?.name;
|
|
99
|
+
return name === undefined ? "an object with no prototype" : `a ${name}`;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/** A value outside the Firestore Value union, at `path`. */
|
|
103
|
+
class Outside {
|
|
104
|
+
readonly kind: string;
|
|
105
|
+
readonly value: unknown;
|
|
106
|
+
readonly path: FieldPath;
|
|
107
|
+
constructor(kind: string, value: unknown, path: FieldPath) {
|
|
108
|
+
this.kind = kind;
|
|
109
|
+
this.value = value;
|
|
110
|
+
this.path = path;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const unhandled: unique symbol = Symbol("unhandled");
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Walks `value`, handing every object to `visit` first and then walking
|
|
118
|
+
* arrays and maps itself. Throws {@link Outside} at the first value that is
|
|
119
|
+
* none of these, or that contains itself.
|
|
120
|
+
*/
|
|
121
|
+
const walk = (
|
|
122
|
+
value: unknown,
|
|
123
|
+
visit: (
|
|
124
|
+
u: object,
|
|
125
|
+
walk: (child: unknown, key: string | number) => unknown,
|
|
126
|
+
fail: (kind: string) => never,
|
|
127
|
+
) => unknown,
|
|
128
|
+
): unknown => {
|
|
129
|
+
const path: Array<string | number> = [];
|
|
130
|
+
const ancestors = new Set<object>();
|
|
131
|
+
const within = (child: unknown, key: string | number): unknown => {
|
|
132
|
+
path.push(key);
|
|
133
|
+
try {
|
|
134
|
+
return go(child);
|
|
135
|
+
} finally {
|
|
136
|
+
path.pop();
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
const go = (u: unknown): unknown => {
|
|
140
|
+
switch (typeof u) {
|
|
141
|
+
case "boolean":
|
|
142
|
+
case "number":
|
|
143
|
+
case "string":
|
|
144
|
+
return u;
|
|
145
|
+
case "object":
|
|
146
|
+
break;
|
|
147
|
+
default:
|
|
148
|
+
throw new Outside(`a value of type ${typeof u}`, u, [...path]);
|
|
149
|
+
}
|
|
150
|
+
if (u === null) return u;
|
|
151
|
+
const fail = (kind: string): never => {
|
|
152
|
+
throw new Outside(kind, u, [...path]);
|
|
153
|
+
};
|
|
154
|
+
if (ancestors.has(u)) return fail("a value that contains itself");
|
|
155
|
+
ancestors.add(u);
|
|
156
|
+
try {
|
|
157
|
+
const out = visit(u, within, fail);
|
|
158
|
+
if (out !== unhandled) return out;
|
|
159
|
+
if (Array.isArray(u)) return u.map((e, i) => within(e, i));
|
|
160
|
+
if (isMap(u)) {
|
|
161
|
+
const map: Record<string, unknown> = {};
|
|
162
|
+
for (const key of Object.keys(u)) map[key] = within(u[key], key);
|
|
163
|
+
return map;
|
|
164
|
+
}
|
|
165
|
+
return fail(describe(u));
|
|
166
|
+
} finally {
|
|
167
|
+
ancestors.delete(u);
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
return go(value);
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const write = (value: unknown, writer: Writer): unknown =>
|
|
174
|
+
walk(value, (u, within) => {
|
|
175
|
+
const kind = kindOf(u);
|
|
176
|
+
return kind === undefined ? unhandled : writer[kind](u as never, within);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const keep = <A>(value: A): A => value;
|
|
180
|
+
|
|
181
|
+
/** Builds nothing: walking with it only checks a value is a Firestore Value. */
|
|
182
|
+
const checker: Writer = {
|
|
183
|
+
bytes: keep,
|
|
184
|
+
timestamp: keep,
|
|
185
|
+
geoPoint: keep,
|
|
186
|
+
documentPath: keep,
|
|
187
|
+
serverTimestamp: keep,
|
|
188
|
+
increment: keep,
|
|
189
|
+
deleteField: keep,
|
|
190
|
+
arrayUnion: (u, within) => u.elements.forEach(within),
|
|
191
|
+
arrayRemove: (u, within) => u.elements.forEach(within),
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
/** @internal */
|
|
195
|
+
export const isFirestoreValue = (u: unknown): u is FirestoreValue.FirestoreValue => {
|
|
196
|
+
try {
|
|
197
|
+
write(u, checker);
|
|
198
|
+
return true;
|
|
199
|
+
} catch (e) {
|
|
200
|
+
if (e instanceof Outside) return false;
|
|
201
|
+
throw e;
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Whether two Firestore Values are the same, as Firestore would store them:
|
|
207
|
+
* scalars by value, arrays and maps element by element and key by key, bytes
|
|
208
|
+
* byte by byte, and every other kind as that kind tells its values apart.
|
|
209
|
+
*
|
|
210
|
+
* @internal
|
|
211
|
+
*/
|
|
212
|
+
export const equals = (self: unknown, that: unknown): boolean => {
|
|
213
|
+
if (Object.is(self, that)) {
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
if (typeof self !== "object" || typeof that !== "object" || self === null || that === null) {
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
if (Array.isArray(self) || Array.isArray(that)) {
|
|
220
|
+
return (
|
|
221
|
+
Array.isArray(self) &&
|
|
222
|
+
Array.isArray(that) &&
|
|
223
|
+
self.length === that.length &&
|
|
224
|
+
self.every((element, i) => equals(element, that[i]))
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
const kind = kindOf(self);
|
|
228
|
+
if (kind !== kindOf(that)) {
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
switch (kind) {
|
|
232
|
+
case undefined:
|
|
233
|
+
return (
|
|
234
|
+
isMap(self) &&
|
|
235
|
+
isMap(that) &&
|
|
236
|
+
Object.keys(self).length === Object.keys(that).length &&
|
|
237
|
+
Object.keys(self).every((key) => Object.hasOwn(that, key) && equals(self[key], that[key]))
|
|
238
|
+
);
|
|
239
|
+
case "bytes": {
|
|
240
|
+
const a = self as Uint8Array;
|
|
241
|
+
const b = that as Uint8Array;
|
|
242
|
+
return a.length === b.length && a.every((byte, i) => byte === b[i]);
|
|
243
|
+
}
|
|
244
|
+
case "arrayUnion":
|
|
245
|
+
case "arrayRemove":
|
|
246
|
+
return equals((self as Kinds["arrayUnion"]).elements, (that as Kinds["arrayUnion"]).elements);
|
|
247
|
+
default:
|
|
248
|
+
return Equal.equals(self, that);
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Turns a Firestore Value into an SDK's own value through `writer`. A value
|
|
254
|
+
* outside the union is a defect, since `toCodecFirestore` only ever encodes
|
|
255
|
+
* Firestore Values.
|
|
256
|
+
*
|
|
257
|
+
* @internal
|
|
258
|
+
*/
|
|
259
|
+
export const toSdk = (value: FirestoreValue.FirestoreValue, writer: Writer): unknown => {
|
|
260
|
+
try {
|
|
261
|
+
return write(value, writer);
|
|
262
|
+
} catch (e) {
|
|
263
|
+
if (!(e instanceof Outside)) throw e;
|
|
264
|
+
throw new globalThis.Error(
|
|
265
|
+
`Cannot write ${e.kind} at ${formatFieldPath(e.path)}, which is not a Firestore Value`,
|
|
266
|
+
{ cause: e.value },
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Turns what an SDK read into a Firestore Value through `reader`, failing on
|
|
273
|
+
* a value outside the union, such as a vector, and naming the field that
|
|
274
|
+
* holds it.
|
|
275
|
+
*
|
|
276
|
+
* @internal
|
|
277
|
+
*/
|
|
278
|
+
export const fromSdk = (
|
|
279
|
+
value: unknown,
|
|
280
|
+
reader: Reader,
|
|
281
|
+
): Result.Result<FirestoreValue.FirestoreValue, FirestoreError> => {
|
|
282
|
+
try {
|
|
283
|
+
return Result.succeed(
|
|
284
|
+
walk(value, (u, within, fail) => {
|
|
285
|
+
const out = reader(u, {
|
|
286
|
+
walk: within as (child: unknown, key: string | number) => FirestoreValue.FirestoreValue,
|
|
287
|
+
fail,
|
|
288
|
+
});
|
|
289
|
+
return out === undefined ? unhandled : out;
|
|
290
|
+
}) as FirestoreValue.FirestoreValue,
|
|
291
|
+
);
|
|
292
|
+
} catch (e) {
|
|
293
|
+
if (!(e instanceof Outside)) throw e;
|
|
294
|
+
return Result.fail(
|
|
295
|
+
new FirestoreError({
|
|
296
|
+
message: `Firestore holds ${e.kind} at ${formatFieldPath(e.path)}, which is not a Firestore Value`,
|
|
297
|
+
code: undefined,
|
|
298
|
+
cause: e.value,
|
|
299
|
+
}),
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Reads what an SDK holds for the document at `path` as Firestore Values
|
|
306
|
+
* through `reader`, keeping `undefined` - a document that is not there - as
|
|
307
|
+
* it is.
|
|
308
|
+
*
|
|
309
|
+
* @internal
|
|
310
|
+
*/
|
|
311
|
+
export const readDocument = (
|
|
312
|
+
path: string,
|
|
313
|
+
data: unknown,
|
|
314
|
+
reader: Reader,
|
|
315
|
+
): Result.Result<FirestoreValue.FirestoreValue | undefined, FirestoreError> =>
|
|
316
|
+
data === undefined
|
|
317
|
+
? Result.succeed(undefined)
|
|
318
|
+
: Result.mapError(
|
|
319
|
+
fromSdk(data, reader),
|
|
320
|
+
(error) =>
|
|
321
|
+
new FirestoreError({
|
|
322
|
+
message: `Failed to read the document "${path}": ${error.message}`,
|
|
323
|
+
code: undefined,
|
|
324
|
+
cause: error.cause,
|
|
325
|
+
}),
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Reads Firestore Values as they are, walking into the values a kind holds,
|
|
330
|
+
* so that only a value outside the union fails.
|
|
331
|
+
*/
|
|
332
|
+
const asIs: Reader = (u, { walk }) => {
|
|
333
|
+
const kind = kindOf(u);
|
|
334
|
+
if (kind === "arrayUnion" || kind === "arrayRemove") {
|
|
335
|
+
(u as Kinds["arrayUnion"]).elements.forEach((element, i) => walk(element, i));
|
|
336
|
+
}
|
|
337
|
+
return kind === undefined ? undefined : (u as FirestoreValue.FirestoreValue);
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Checks that what a backend read for the document at `path` is made of
|
|
342
|
+
* Firestore Values, failing as {@link readDocument} does on one that is not.
|
|
343
|
+
* It hands back `data` itself, so `undefined` - a document that is not there -
|
|
344
|
+
* stays as it is.
|
|
345
|
+
*
|
|
346
|
+
* @internal
|
|
347
|
+
*/
|
|
348
|
+
export const checkDocument = <A>(path: string, data: A): Result.Result<A, FirestoreError> =>
|
|
349
|
+
Result.map(readDocument(path, data, asIs), () => data);
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Equal, Hash, Inspectable, Option, Predicate } from "effect";
|
|
2
|
+
import type * as GeoPoint from "../GeoPoint.ts";
|
|
3
|
+
|
|
4
|
+
/** @internal */
|
|
5
|
+
export const TypeId = "~@visiolab/effect-firebase/firestore/GeoPoint";
|
|
6
|
+
|
|
7
|
+
/** Kept apart from the guards below, which narrow a `number` to `never`. */
|
|
8
|
+
const inLatitudeRange = (n: number): boolean => Number.isFinite(n) && n >= -90 && n <= 90;
|
|
9
|
+
|
|
10
|
+
const inLongitudeRange = (n: number): boolean => Number.isFinite(n) && n >= -180 && n <= 180;
|
|
11
|
+
|
|
12
|
+
/** @internal */
|
|
13
|
+
export const isGeoPoint = (u: unknown): u is GeoPoint.GeoPoint =>
|
|
14
|
+
Predicate.hasProperty(u, TypeId) &&
|
|
15
|
+
u[TypeId] === TypeId &&
|
|
16
|
+
Predicate.hasProperty(u, "latitude") &&
|
|
17
|
+
Predicate.hasProperty(u, "longitude") &&
|
|
18
|
+
Predicate.isNumber(u.latitude) &&
|
|
19
|
+
Predicate.isNumber(u.longitude) &&
|
|
20
|
+
inLatitudeRange(u.latitude) &&
|
|
21
|
+
inLongitudeRange(u.longitude);
|
|
22
|
+
|
|
23
|
+
const Proto: Omit<GeoPoint.GeoPoint, "latitude" | "longitude"> = {
|
|
24
|
+
...Inspectable.BaseProto,
|
|
25
|
+
[TypeId]: TypeId,
|
|
26
|
+
toJSON(this: GeoPoint.GeoPoint) {
|
|
27
|
+
return { _id: "GeoPoint", latitude: this.latitude, longitude: this.longitude };
|
|
28
|
+
},
|
|
29
|
+
[Equal.symbol](this: GeoPoint.GeoPoint, that: unknown): boolean {
|
|
30
|
+
return isGeoPoint(that) && this.latitude === that.latitude && this.longitude === that.longitude;
|
|
31
|
+
},
|
|
32
|
+
[Hash.symbol](this: GeoPoint.GeoPoint): number {
|
|
33
|
+
return Hash.combine(Hash.number(this.latitude), Hash.number(this.longitude));
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const makeUnchecked = (latitude: number, longitude: number): GeoPoint.GeoPoint =>
|
|
38
|
+
Object.assign(Object.create(Proto) as typeof Proto, { latitude, longitude });
|
|
39
|
+
|
|
40
|
+
/** @internal */
|
|
41
|
+
export const makeUnsafe = (latitude: number, longitude: number): GeoPoint.GeoPoint => {
|
|
42
|
+
if (!inLatitudeRange(latitude)) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
`Invalid Firestore geopoint: latitude must be a number between -90 and 90, got ${latitude}`,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
if (!inLongitudeRange(longitude)) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
`Invalid Firestore geopoint: longitude must be a number between -180 and 180, got ${longitude}`,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
return makeUnchecked(latitude, longitude);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/** @internal */
|
|
56
|
+
export const make = (latitude: number, longitude: number): Option.Option<GeoPoint.GeoPoint> =>
|
|
57
|
+
inLatitudeRange(latitude) && inLongitudeRange(longitude)
|
|
58
|
+
? Option.some(makeUnchecked(latitude, longitude))
|
|
59
|
+
: Option.none();
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Inspectable, Predicate } from "effect";
|
|
2
|
+
import type * as Patch from "../Patch.ts";
|
|
3
|
+
|
|
4
|
+
/** @internal */
|
|
5
|
+
export const ReplaceTypeId = "~@visiolab/effect-firebase/firestore/Patch/Replace";
|
|
6
|
+
|
|
7
|
+
const ReplaceProto: Omit<Patch.Replace<unknown>, "value"> = {
|
|
8
|
+
...Inspectable.BaseProto,
|
|
9
|
+
[ReplaceTypeId]: ReplaceTypeId,
|
|
10
|
+
toJSON(this: Patch.Replace<unknown>) {
|
|
11
|
+
return { _id: "Replace", value: Inspectable.toJson(this.value) };
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/** @internal */
|
|
16
|
+
export const isReplace = (u: unknown): u is Patch.Replace<unknown> =>
|
|
17
|
+
Predicate.hasProperty(u, ReplaceTypeId);
|
|
18
|
+
|
|
19
|
+
/** @internal */
|
|
20
|
+
export const replace = <const A>(value: A): Patch.Replace<A> =>
|
|
21
|
+
Object.assign(Object.create(ReplaceProto) as typeof ReplaceProto, { value });
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Inspectable, Predicate } from "effect";
|
|
2
|
+
import type * as Collection from "../Collection.ts";
|
|
3
|
+
import type * as DocumentSnapshot from "../DocumentSnapshot.ts";
|
|
4
|
+
import type * as QuerySnapshot from "../QuerySnapshot.ts";
|
|
5
|
+
import { attach, extraToJson, type WithExtra } from "./snapshotExtra.ts";
|
|
6
|
+
|
|
7
|
+
/** @internal */
|
|
8
|
+
export const TypeId = "~@visiolab/effect-firebase/firestore/QuerySnapshot";
|
|
9
|
+
|
|
10
|
+
/** @internal */
|
|
11
|
+
export const isQuerySnapshot = (u: unknown): u is QuerySnapshot.QuerySnapshot<Collection.Any> =>
|
|
12
|
+
Predicate.hasProperty(u, TypeId);
|
|
13
|
+
|
|
14
|
+
interface QuerySnapshotImpl extends WithExtra {
|
|
15
|
+
readonly docs: ReadonlyArray<DocumentSnapshot.Existing<Collection.Any>>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const QuerySnapshotProto = {
|
|
19
|
+
...Inspectable.BaseProto,
|
|
20
|
+
[TypeId]: TypeId,
|
|
21
|
+
toJSON(this: QuerySnapshotImpl) {
|
|
22
|
+
return {
|
|
23
|
+
_id: "QuerySnapshot",
|
|
24
|
+
docs: this.docs.map((doc) => doc.toJSON()),
|
|
25
|
+
...extraToJson(this),
|
|
26
|
+
};
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/** @internal */
|
|
31
|
+
export const make = <C extends Collection.Any>(
|
|
32
|
+
docs: ReadonlyArray<DocumentSnapshot.Existing<C>>,
|
|
33
|
+
extra?: object,
|
|
34
|
+
): QuerySnapshot.QuerySnapshot<C> => {
|
|
35
|
+
const self = Object.assign(Object.create(QuerySnapshotProto) as typeof QuerySnapshotProto, {
|
|
36
|
+
docs,
|
|
37
|
+
});
|
|
38
|
+
return attach(self, extra) as unknown as QuerySnapshot.QuerySnapshot<C>;
|
|
39
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one place this package reaches into Effect's `@internal` `SchemaAST`
|
|
3
|
+
* helpers.
|
|
4
|
+
*
|
|
5
|
+
* Effect has no public way to define a canonical format, so deriving
|
|
6
|
+
* `toCodecFirestore` the way `toCodecJson` is derived needs the same internal
|
|
7
|
+
* helpers Effect uses for it (ADR 0002). They exist at runtime but are stripped
|
|
8
|
+
* from Effect's type declarations, so each is typed here by hand, against the
|
|
9
|
+
* pinned `effect` version. Keeping them together means a future public API for
|
|
10
|
+
* canonical formats is a swap of this module only.
|
|
11
|
+
*/
|
|
12
|
+
import { SchemaAST } from "effect";
|
|
13
|
+
|
|
14
|
+
interface Internals {
|
|
15
|
+
readonly replaceEncoding: <A extends SchemaAST.AST>(
|
|
16
|
+
ast: A,
|
|
17
|
+
encoding: SchemaAST.Encoding | undefined,
|
|
18
|
+
) => A;
|
|
19
|
+
readonly mapLink: (
|
|
20
|
+
link: SchemaAST.Link,
|
|
21
|
+
f: (ast: SchemaAST.AST) => SchemaAST.AST,
|
|
22
|
+
) => SchemaAST.Link;
|
|
23
|
+
readonly replaceContextLastLink: <A extends SchemaAST.AST>(
|
|
24
|
+
ast: A,
|
|
25
|
+
context: SchemaAST.Context,
|
|
26
|
+
) => A;
|
|
27
|
+
readonly applyToSelfOrLastLinkEncodingIdempotent: (
|
|
28
|
+
f: (ast: SchemaAST.AST) => SchemaAST.AST,
|
|
29
|
+
) => (ast: SchemaAST.AST) => SchemaAST.AST;
|
|
30
|
+
readonly parameterFromString: (ast: SchemaAST.AST) => SchemaAST.AST;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const internals = SchemaAST as unknown as Internals;
|
|
34
|
+
|
|
35
|
+
/** Replaces the encoding chain of `ast`, leaving `ast` alone when it is the same. */
|
|
36
|
+
export const replaceEncoding = internals.replaceEncoding;
|
|
37
|
+
|
|
38
|
+
/** Maps the target of `link`, leaving `link` alone when the target is unchanged. */
|
|
39
|
+
export const mapLink = internals.mapLink;
|
|
40
|
+
|
|
41
|
+
/** Replaces the context of the last link's target. */
|
|
42
|
+
export const replaceContextLastLink = internals.replaceContextLastLink;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Applies `f` to the node itself when it has no encoding, or to the target of
|
|
46
|
+
* its last encoding link otherwise, so user transformations run first. The
|
|
47
|
+
* result is memoized per node.
|
|
48
|
+
*/
|
|
49
|
+
export const applyToSelfOrLastLinkEncodingIdempotent =
|
|
50
|
+
internals.applyToSelfOrLastLinkEncodingIdempotent;
|
|
51
|
+
|
|
52
|
+
/** How a canonical format reads index-signature and property keys back from strings. */
|
|
53
|
+
export const parameterFromString = internals.parameterFromString;
|
|
54
|
+
|
|
55
|
+
/** The keywords whose canonical JSON form Effect derives itself. */
|
|
56
|
+
type JsonKeyword =
|
|
57
|
+
| SchemaAST.Undefined
|
|
58
|
+
| SchemaAST.Void
|
|
59
|
+
| SchemaAST.Literal
|
|
60
|
+
| SchemaAST.Number
|
|
61
|
+
| SchemaAST.Symbol
|
|
62
|
+
| SchemaAST.UniqueSymbol
|
|
63
|
+
| SchemaAST.BigInt;
|
|
64
|
+
|
|
65
|
+
/** The keyword's own canonical JSON codec, as `toCodecJson` derives it. */
|
|
66
|
+
export const keywordToCodecJson = (ast: JsonKeyword): SchemaAST.AST =>
|
|
67
|
+
(ast as unknown as { toCodecJson(): SchemaAST.AST }).toCodecJson();
|
|
68
|
+
|
|
69
|
+
/** The keyword's own canonical StringTree codec, as `toCodecStringTree` derives it. */
|
|
70
|
+
export const keywordToCodecStringTree = (ast: JsonKeyword): SchemaAST.AST =>
|
|
71
|
+
(ast as unknown as { toCodecStringTree(): SchemaAST.AST }).toCodecStringTree();
|
|
72
|
+
|
|
73
|
+
/** Recurs into the members of a composite node. */
|
|
74
|
+
export const recur = <A extends SchemaAST.AST>(
|
|
75
|
+
ast: A,
|
|
76
|
+
f: (ast: SchemaAST.AST) => SchemaAST.AST,
|
|
77
|
+
onKey?: (ast: SchemaAST.AST) => SchemaAST.AST,
|
|
78
|
+
): A =>
|
|
79
|
+
(
|
|
80
|
+
ast as unknown as {
|
|
81
|
+
recur(f: (ast: SchemaAST.AST) => SchemaAST.AST, onKey?: unknown): A;
|
|
82
|
+
}
|
|
83
|
+
).recur(f, onKey);
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Inspectable } from "effect";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Where a snapshot keeps the extra an SDK built it with, so that it can show
|
|
5
|
+
* that extra when it prints without telling the extra's fields from its own.
|
|
6
|
+
*/
|
|
7
|
+
const ExtraKey = "~@visiolab/effect-firebase/firestore/SnapshotExtra";
|
|
8
|
+
|
|
9
|
+
/** @internal */
|
|
10
|
+
export interface WithExtra {
|
|
11
|
+
readonly [ExtraKey]?: object;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* `self` with the fields of `extra` on it, remembered as its extra so that
|
|
16
|
+
* {@link extraToJson} shows them.
|
|
17
|
+
*
|
|
18
|
+
* The extra itself is held on a key that is not enumerable, so that it does
|
|
19
|
+
* not show up twice: its fields are already on the snapshot.
|
|
20
|
+
*
|
|
21
|
+
* @internal
|
|
22
|
+
*/
|
|
23
|
+
export const attach = <A extends object>(self: A, extra: object | undefined): A => {
|
|
24
|
+
if (extra === undefined) {
|
|
25
|
+
return self;
|
|
26
|
+
}
|
|
27
|
+
Object.defineProperty(self, ExtraKey, { value: extra });
|
|
28
|
+
return Object.assign(self, extra);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* How the extra `self` was built with prints: each of its fields as it would
|
|
33
|
+
* print on its own, or nothing when it was built without one.
|
|
34
|
+
*
|
|
35
|
+
* @internal
|
|
36
|
+
*/
|
|
37
|
+
export const extraToJson = (self: WithExtra): Record<string, unknown> => {
|
|
38
|
+
const extra = self[ExtraKey];
|
|
39
|
+
if (extra === undefined) {
|
|
40
|
+
return {};
|
|
41
|
+
}
|
|
42
|
+
const json: Record<string, unknown> = {};
|
|
43
|
+
for (const [key, value] of Object.entries(extra)) {
|
|
44
|
+
json[key] = Inspectable.toJson(value);
|
|
45
|
+
}
|
|
46
|
+
return json;
|
|
47
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Cause, Effect, Queue, Result, Stream } from "effect";
|
|
2
|
+
import type * as Firestore from "../Firestore.ts";
|
|
3
|
+
import type { FirestoreError } from "./firestoreError.ts";
|
|
4
|
+
|
|
5
|
+
/** @internal */
|
|
6
|
+
export const streamFromListener = <S, A>(
|
|
7
|
+
options: Firestore.StreamFromListenerOptions<S, A>,
|
|
8
|
+
): Stream.Stream<A, FirestoreError> =>
|
|
9
|
+
Stream.callback<A, FirestoreError>(
|
|
10
|
+
(queue) =>
|
|
11
|
+
Effect.acquireRelease(
|
|
12
|
+
Effect.sync(() =>
|
|
13
|
+
options.subscribe(
|
|
14
|
+
(snapshot) => {
|
|
15
|
+
// Runs inside the SDK's listener, so a snapshot that does not map
|
|
16
|
+
// fails the stream rather than throwing back into the SDK.
|
|
17
|
+
const element = options.map(snapshot);
|
|
18
|
+
if (Result.isSuccess(element)) Queue.offerUnsafe(queue, element.success);
|
|
19
|
+
else Queue.failCauseUnsafe(queue, Cause.fail(element.failure));
|
|
20
|
+
},
|
|
21
|
+
(error) => Queue.failCauseUnsafe(queue, Cause.fail(options.onError(error))),
|
|
22
|
+
),
|
|
23
|
+
),
|
|
24
|
+
(unsubscribe) => Effect.sync(unsubscribe),
|
|
25
|
+
),
|
|
26
|
+
{ bufferSize: 1, strategy: "sliding" },
|
|
27
|
+
);
|