@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,156 @@
|
|
|
1
|
+
import { isDocumentSnapshot } from "./documentSnapshot.mjs";
|
|
2
|
+
import { Equal, Hash, Inspectable, Option, Predicate, Schema } from "effect";
|
|
3
|
+
// src/firestore/internal/collection.ts
|
|
4
|
+
/** @internal */
|
|
5
|
+
const CollectionTypeId = "~@visiolab/effect-firebase/firestore/Collection";
|
|
6
|
+
/** @internal */
|
|
7
|
+
const CollectionReferenceTypeId = "~@visiolab/effect-firebase/firestore/CollectionReference";
|
|
8
|
+
/** @internal */
|
|
9
|
+
const DocumentReferenceTypeId = "~@visiolab/effect-firebase/firestore/DocumentReference";
|
|
10
|
+
/** @internal */
|
|
11
|
+
const isCollection = (u) => Predicate.hasProperty(u, CollectionTypeId);
|
|
12
|
+
/** @internal */
|
|
13
|
+
const isCollectionReference = (u) => Predicate.hasProperty(u, CollectionReferenceTypeId);
|
|
14
|
+
/** @internal */
|
|
15
|
+
const isDocumentReference = (u) => Predicate.hasProperty(u, DocumentReferenceTypeId);
|
|
16
|
+
const encoder = new TextEncoder();
|
|
17
|
+
/** Rejects the segments Firestore cannot address, before they reach a path. */
|
|
18
|
+
const validateSegment = (segment) => {
|
|
19
|
+
const reject = (reason) => {
|
|
20
|
+
throw new Error(`Invalid Firestore collection id "${segment}": ${reason}`);
|
|
21
|
+
};
|
|
22
|
+
if (segment.length === 0) reject("it must not be empty");
|
|
23
|
+
if (segment.includes("/")) reject("it must not contain \"/\"");
|
|
24
|
+
if (segment === "." || segment === "..") reject("it must not be \".\" or \"..\"");
|
|
25
|
+
if (/^__.*__$/.test(segment)) reject("it must not match \"__.*__\"");
|
|
26
|
+
if (encoder.encode(segment).length > 1500) reject("it must be at most 1500 bytes");
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Brands an id schema with the path addressing it.
|
|
30
|
+
*
|
|
31
|
+
* The path is only known at runtime, and `Schema.brand` rejects an open `string`
|
|
32
|
+
* key. Narrowing the key to a placeholder literal keeps the real path on the
|
|
33
|
+
* annotation: the static brand is erased where `makeCollection` takes the schema
|
|
34
|
+
* as `Schema.Top`, and the public id types are built above it.
|
|
35
|
+
*/
|
|
36
|
+
const brandId = (path) => Schema.String.pipe(Schema.brand(path.join("/")));
|
|
37
|
+
/** Interleaves the segments with the ids addressing them: `a/1/b/2/c`. */
|
|
38
|
+
const joinPath = (segments, ancestors) => {
|
|
39
|
+
let path = segments[0];
|
|
40
|
+
for (let i = 0; i < ancestors.length; i++) path += `/${ancestors[i]}/${segments[i + 1]}`;
|
|
41
|
+
return path;
|
|
42
|
+
};
|
|
43
|
+
const CollectionProto = {
|
|
44
|
+
...Inspectable.BaseProto,
|
|
45
|
+
[CollectionTypeId]: CollectionTypeId,
|
|
46
|
+
toJSON() {
|
|
47
|
+
return {
|
|
48
|
+
_id: "Collection",
|
|
49
|
+
path: this.path.join("/")
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
subcollection(segment, schema) {
|
|
53
|
+
validateSegment(segment);
|
|
54
|
+
const path = [...this.path, segment];
|
|
55
|
+
return makeCollection(path, schema, brandId(path));
|
|
56
|
+
},
|
|
57
|
+
collection(...ancestors) {
|
|
58
|
+
return makeCollectionReference(this, ancestors);
|
|
59
|
+
},
|
|
60
|
+
doc(...ids) {
|
|
61
|
+
const parent = makeCollectionReference(this, ids.slice(0, -1));
|
|
62
|
+
return makeDocumentReference(parent, ids[ids.length - 1]);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* The target is asserted as the prototype rather than as the finished shape, so
|
|
67
|
+
* that the result type is inferred from the fields below. The return annotation
|
|
68
|
+
* then catches a field that is missing, misspelled or of the wrong type -
|
|
69
|
+
* asserting the finished shape up front would check none of the three.
|
|
70
|
+
*/
|
|
71
|
+
const makeCollection = (path, schema, id) => {
|
|
72
|
+
const self = Object.assign(Object.create(CollectionProto), {
|
|
73
|
+
path,
|
|
74
|
+
segment: path[path.length - 1],
|
|
75
|
+
schema,
|
|
76
|
+
id,
|
|
77
|
+
contains: (value) => isDocumentReference(value) ? value.collection === self : isDocumentSnapshot(value) && value.ref.collection === self
|
|
78
|
+
});
|
|
79
|
+
return self;
|
|
80
|
+
};
|
|
81
|
+
const CollectionReferenceProto = {
|
|
82
|
+
...Inspectable.BaseProto,
|
|
83
|
+
[CollectionReferenceTypeId]: CollectionReferenceTypeId,
|
|
84
|
+
toJSON() {
|
|
85
|
+
return {
|
|
86
|
+
_id: "CollectionReference",
|
|
87
|
+
path: this.path
|
|
88
|
+
};
|
|
89
|
+
},
|
|
90
|
+
[Equal.symbol](that) {
|
|
91
|
+
return isCollectionReference(that) && this.path === that.path && this.collection === that.collection;
|
|
92
|
+
},
|
|
93
|
+
[Hash.symbol]() {
|
|
94
|
+
return Hash.combine(Hash.string(this.path), Hash.random(this.collection));
|
|
95
|
+
},
|
|
96
|
+
doc(id) {
|
|
97
|
+
return makeDocumentReference(this, id);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
const makeCollectionReference = (collection, ancestors) => Object.assign(Object.create(CollectionReferenceProto), {
|
|
101
|
+
collection,
|
|
102
|
+
ancestors,
|
|
103
|
+
id: collection.segment,
|
|
104
|
+
path: joinPath(collection.path, ancestors)
|
|
105
|
+
});
|
|
106
|
+
const DocumentReferenceProto = {
|
|
107
|
+
...Inspectable.BaseProto,
|
|
108
|
+
[DocumentReferenceTypeId]: DocumentReferenceTypeId,
|
|
109
|
+
toJSON() {
|
|
110
|
+
return {
|
|
111
|
+
_id: "DocumentReference",
|
|
112
|
+
path: this.path
|
|
113
|
+
};
|
|
114
|
+
},
|
|
115
|
+
[Equal.symbol](that) {
|
|
116
|
+
return isDocumentReference(that) && this.path === that.path && this.collection === that.collection;
|
|
117
|
+
},
|
|
118
|
+
[Hash.symbol]() {
|
|
119
|
+
return Hash.combine(Hash.string(this.path), Hash.random(this.collection));
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
/** A document is always built from the collection holding it, which it keeps. */
|
|
123
|
+
const makeDocumentReference = (parent, id) => Object.assign(Object.create(DocumentReferenceProto), {
|
|
124
|
+
collection: parent.collection,
|
|
125
|
+
ancestors: parent.ancestors,
|
|
126
|
+
id,
|
|
127
|
+
path: `${parent.path}/${id}`,
|
|
128
|
+
parent
|
|
129
|
+
});
|
|
130
|
+
/** @internal */
|
|
131
|
+
const make = (segment, schema) => {
|
|
132
|
+
validateSegment(segment);
|
|
133
|
+
const path = [segment];
|
|
134
|
+
return makeCollection(path, schema, brandId(path));
|
|
135
|
+
};
|
|
136
|
+
/** @internal */
|
|
137
|
+
const fromPath = (collection, path) => {
|
|
138
|
+
const self = collection;
|
|
139
|
+
const parts = path.split("/");
|
|
140
|
+
if (parts.length !== self.path.length * 2) return Option.none();
|
|
141
|
+
const ids = [];
|
|
142
|
+
for (let i = 0; i < self.path.length; i++) {
|
|
143
|
+
const id = parts[2 * i + 1];
|
|
144
|
+
if (parts[2 * i] !== self.path[i] || id.length === 0) return Option.none();
|
|
145
|
+
ids.push(id);
|
|
146
|
+
}
|
|
147
|
+
return Option.some(self.doc(...ids));
|
|
148
|
+
};
|
|
149
|
+
/**
|
|
150
|
+
* The path a document in `collection` is found at, with `{id}` standing in for
|
|
151
|
+
* each id: `users/{id}/posts/{id}`.
|
|
152
|
+
*
|
|
153
|
+
* @internal
|
|
154
|
+
*/
|
|
155
|
+
const pattern = (collection) => collection.path.map((segment) => `${segment}/{id}`).join("/");
|
|
156
|
+
export { CollectionReferenceTypeId, CollectionTypeId, DocumentReferenceTypeId, fromPath, isCollection, isCollectionReference, isDocumentReference, make, pattern };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { UndefinedCollectionError } from "./firestoreError.mjs";
|
|
2
|
+
import { fromPath, pattern } from "./collection.mjs";
|
|
3
|
+
import { Inspectable, Option, Predicate, Result } from "effect";
|
|
4
|
+
// src/firestore/internal/collectionGroup.ts
|
|
5
|
+
/** @internal */
|
|
6
|
+
const TypeId = "~@visiolab/effect-firebase/firestore/CollectionGroup";
|
|
7
|
+
/** @internal */
|
|
8
|
+
const isCollectionGroup = (u) => Predicate.hasProperty(u, TypeId);
|
|
9
|
+
/**
|
|
10
|
+
* Where `member`'s collections sit, with `{id}` standing in for each ancestor
|
|
11
|
+
* id: `users/{id}/posts`.
|
|
12
|
+
*/
|
|
13
|
+
const memberPattern = (member) => pattern(member).slice(0, -5);
|
|
14
|
+
const CollectionGroupProto = {
|
|
15
|
+
...Inspectable.BaseProto,
|
|
16
|
+
[TypeId]: TypeId,
|
|
17
|
+
toJSON() {
|
|
18
|
+
return {
|
|
19
|
+
_id: "CollectionGroup",
|
|
20
|
+
members: this.members.map(memberPattern)
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Rejects the groups that would not mean one thing: one with nothing in it,
|
|
26
|
+
* one whose collections Firestore could not read under one collection id, and
|
|
27
|
+
* one with two collections at the same path, which no document could be told
|
|
28
|
+
* apart between.
|
|
29
|
+
*/
|
|
30
|
+
const validate = (members) => {
|
|
31
|
+
const reject = (reason) => {
|
|
32
|
+
throw new Error(`Invalid collection group: ${reason}`);
|
|
33
|
+
};
|
|
34
|
+
if (members.length === 0) reject("it must hold at least one collection");
|
|
35
|
+
const [first] = members;
|
|
36
|
+
const seen = /* @__PURE__ */ new Set();
|
|
37
|
+
for (const member of members) {
|
|
38
|
+
if (member.segment !== first.segment) reject(`"${memberPattern(first)}" and "${memberPattern(member)}" do not share their collection id`);
|
|
39
|
+
const pattern = memberPattern(member);
|
|
40
|
+
if (seen.has(pattern)) reject(`it has more than one member at "${pattern}"`);
|
|
41
|
+
seen.add(pattern);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
/** @internal */
|
|
45
|
+
const make = (...valid) => {
|
|
46
|
+
const members = valid;
|
|
47
|
+
validate(members);
|
|
48
|
+
return Object.assign(Object.create(CollectionGroupProto), {
|
|
49
|
+
members,
|
|
50
|
+
id: members[0].segment
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
/** @internal */
|
|
54
|
+
const documentAt = (group, path) => {
|
|
55
|
+
for (const member of group.members) {
|
|
56
|
+
const ref = fromPath(member, path);
|
|
57
|
+
if (Option.isSome(ref)) return Result.succeed(ref.value);
|
|
58
|
+
}
|
|
59
|
+
return Result.fail(new UndefinedCollectionError({
|
|
60
|
+
path,
|
|
61
|
+
collectionId: group.id
|
|
62
|
+
}));
|
|
63
|
+
};
|
|
64
|
+
export { TypeId, documentAt, isCollectionGroup, make };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Equal, Hash, Inspectable, Option, Predicate } from "effect";
|
|
2
|
+
// src/firestore/internal/documentPath.ts
|
|
3
|
+
/** @internal */
|
|
4
|
+
const TypeId = "~@visiolab/effect-firebase/firestore/DocumentPath";
|
|
5
|
+
/** A collection id, then a document id, and so on: an even number of non-empty segments. */
|
|
6
|
+
const addressesDocument = (path) => {
|
|
7
|
+
const segments = path.split("/");
|
|
8
|
+
return segments.length % 2 === 0 && segments.every((segment) => segment.length > 0);
|
|
9
|
+
};
|
|
10
|
+
/** @internal */
|
|
11
|
+
const isDocumentPath = (u) => Predicate.hasProperty(u, "~@visiolab/effect-firebase/firestore/DocumentPath") && u["~@visiolab/effect-firebase/firestore/DocumentPath"] === "~@visiolab/effect-firebase/firestore/DocumentPath" && Predicate.hasProperty(u, "path") && Predicate.isString(u.path);
|
|
12
|
+
const Proto = {
|
|
13
|
+
...Inspectable.BaseProto,
|
|
14
|
+
[TypeId]: TypeId,
|
|
15
|
+
toJSON() {
|
|
16
|
+
return {
|
|
17
|
+
_id: "DocumentPath",
|
|
18
|
+
path: this.path
|
|
19
|
+
};
|
|
20
|
+
},
|
|
21
|
+
[Equal.symbol](that) {
|
|
22
|
+
return isDocumentPath(that) && this.path === that.path;
|
|
23
|
+
},
|
|
24
|
+
[Hash.symbol]() {
|
|
25
|
+
return Hash.string(this.path);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Builds the value without checking `path`, for one that already addresses a
|
|
30
|
+
* document, such as a reference's own.
|
|
31
|
+
*
|
|
32
|
+
* @internal
|
|
33
|
+
*/
|
|
34
|
+
const makeUnchecked = (path) => Object.assign(Object.create(Proto), { path });
|
|
35
|
+
/** @internal */
|
|
36
|
+
const make = (path) => addressesDocument(path) ? Option.some(makeUnchecked(path)) : Option.none();
|
|
37
|
+
/** @internal */
|
|
38
|
+
const makeUnsafe = (path) => {
|
|
39
|
+
if (!addressesDocument(path)) throw new Error(`Invalid Firestore document path "${path}": it must address a document`);
|
|
40
|
+
return makeUnchecked(path);
|
|
41
|
+
};
|
|
42
|
+
export { TypeId, isDocumentPath, make, makeUnchecked, makeUnsafe };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { toCodecFirestore } from "./firestoreValue.mjs";
|
|
2
|
+
import { attach, extraToJson } from "./snapshotExtra.mjs";
|
|
3
|
+
import { Effect, Inspectable, Predicate, Schema } from "effect";
|
|
4
|
+
// src/firestore/internal/documentSnapshot.ts
|
|
5
|
+
/** @internal */
|
|
6
|
+
const TypeId = "~@visiolab/effect-firebase/firestore/DocumentSnapshot";
|
|
7
|
+
/** @internal */
|
|
8
|
+
const isDocumentSnapshot = (u) => Predicate.hasProperty(u, TypeId);
|
|
9
|
+
/** @internal */
|
|
10
|
+
const isExisting = (self) => self._tag === "Existing";
|
|
11
|
+
/** @internal */
|
|
12
|
+
const isMissing = (self) => self._tag === "Missing";
|
|
13
|
+
const ExistingProto = {
|
|
14
|
+
...Inspectable.BaseProto,
|
|
15
|
+
[TypeId]: TypeId,
|
|
16
|
+
_tag: "Existing",
|
|
17
|
+
toJSON() {
|
|
18
|
+
return {
|
|
19
|
+
_id: "DocumentSnapshot",
|
|
20
|
+
_tag: "Existing",
|
|
21
|
+
ref: this.ref.path,
|
|
22
|
+
raw: Inspectable.toJson(this.raw),
|
|
23
|
+
...extraToJson(this)
|
|
24
|
+
};
|
|
25
|
+
},
|
|
26
|
+
get data() {
|
|
27
|
+
const self = this;
|
|
28
|
+
return Effect.suspend(() => Schema.decodeUnknownEffect(toCodecFirestore(self.ref.collection.schema))(self.raw));
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
const MissingProto = {
|
|
32
|
+
...Inspectable.BaseProto,
|
|
33
|
+
[TypeId]: TypeId,
|
|
34
|
+
_tag: "Missing",
|
|
35
|
+
toJSON() {
|
|
36
|
+
return {
|
|
37
|
+
_id: "DocumentSnapshot",
|
|
38
|
+
_tag: "Missing",
|
|
39
|
+
ref: this.ref.path,
|
|
40
|
+
...extraToJson(this)
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
/** @internal */
|
|
45
|
+
const existing = (ref, data, extra) => {
|
|
46
|
+
const self = Object.assign(Object.create(ExistingProto), {
|
|
47
|
+
ref,
|
|
48
|
+
id: ref.id,
|
|
49
|
+
raw: data
|
|
50
|
+
});
|
|
51
|
+
return attach(self, extra);
|
|
52
|
+
};
|
|
53
|
+
/** @internal */
|
|
54
|
+
const missing = (ref, extra) => {
|
|
55
|
+
const self = Object.assign(Object.create(MissingProto), {
|
|
56
|
+
ref,
|
|
57
|
+
id: ref.id
|
|
58
|
+
});
|
|
59
|
+
return attach(self, extra);
|
|
60
|
+
};
|
|
61
|
+
/** @internal */
|
|
62
|
+
const make = (ref, data, extra) => data === void 0 ? missing(ref, extra) : existing(ref, data, extra);
|
|
63
|
+
export { TypeId, existing, isDocumentSnapshot, isExisting, isMissing, make, missing };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ArrayRemove, ArrayUnion, DeleteField, Increment, ServerTimestamp } from "../FieldValue.mjs";
|
|
2
|
+
/** @internal */
|
|
3
|
+
export declare const isServerTimestamp: (u: unknown) => u is ServerTimestamp;
|
|
4
|
+
/** @internal */
|
|
5
|
+
export declare const isIncrement: (u: unknown) => u is Increment;
|
|
6
|
+
/** @internal */
|
|
7
|
+
export declare const isDeleteField: (u: unknown) => u is DeleteField;
|
|
8
|
+
/** @internal */
|
|
9
|
+
export declare const isArrayUnion: (u: unknown) => u is ArrayUnion<unknown>;
|
|
10
|
+
/** @internal */
|
|
11
|
+
export declare const isArrayRemove: (u: unknown) => u is ArrayRemove<unknown>;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { Equal, Hash, Inspectable, Predicate } from "effect";
|
|
2
|
+
// src/firestore/internal/fieldValue.ts
|
|
3
|
+
/** @internal */
|
|
4
|
+
const ServerTimestampTypeId = "~@visiolab/effect-firebase/firestore/FieldValue/ServerTimestamp";
|
|
5
|
+
const ServerTimestampProto = {
|
|
6
|
+
...Inspectable.BaseProto,
|
|
7
|
+
[ServerTimestampTypeId]: ServerTimestampTypeId,
|
|
8
|
+
toJSON() {
|
|
9
|
+
return { _id: "ServerTimestamp" };
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
/** @internal */
|
|
13
|
+
const serverTimestamp = Object.create(ServerTimestampProto);
|
|
14
|
+
/** @internal */
|
|
15
|
+
const isServerTimestamp = (u) => Predicate.hasProperty(u, ServerTimestampTypeId);
|
|
16
|
+
/** @internal */
|
|
17
|
+
const IncrementTypeId = "~@visiolab/effect-firebase/firestore/FieldValue/Increment";
|
|
18
|
+
/** @internal */
|
|
19
|
+
const isIncrement = (u) => Predicate.hasProperty(u, IncrementTypeId);
|
|
20
|
+
const IncrementProto = {
|
|
21
|
+
...Inspectable.BaseProto,
|
|
22
|
+
[IncrementTypeId]: IncrementTypeId,
|
|
23
|
+
toJSON() {
|
|
24
|
+
return {
|
|
25
|
+
_id: "Increment",
|
|
26
|
+
by: this.by
|
|
27
|
+
};
|
|
28
|
+
},
|
|
29
|
+
[Equal.symbol](that) {
|
|
30
|
+
return isIncrement(that) && this.by === that.by;
|
|
31
|
+
},
|
|
32
|
+
[Hash.symbol]() {
|
|
33
|
+
return Hash.combine(Hash.string(IncrementTypeId), Hash.number(this.by));
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
/** @internal */
|
|
37
|
+
const increment = (by) => {
|
|
38
|
+
if (!Number.isFinite(by)) throw new Error(`Invalid Firestore increment: by must be a finite number, got ${by}`);
|
|
39
|
+
return Object.assign(Object.create(IncrementProto), { by });
|
|
40
|
+
};
|
|
41
|
+
/** @internal */
|
|
42
|
+
const decrement = (by) => increment(-by);
|
|
43
|
+
/** @internal */
|
|
44
|
+
const DeleteFieldTypeId = "~@visiolab/effect-firebase/firestore/FieldValue/DeleteField";
|
|
45
|
+
const DeleteFieldProto = {
|
|
46
|
+
...Inspectable.BaseProto,
|
|
47
|
+
[DeleteFieldTypeId]: DeleteFieldTypeId,
|
|
48
|
+
toJSON() {
|
|
49
|
+
return { _id: "DeleteField" };
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
/** @internal */
|
|
53
|
+
const deleteField = Object.create(DeleteFieldProto);
|
|
54
|
+
/** @internal */
|
|
55
|
+
const isDeleteField = (u) => Predicate.hasProperty(u, DeleteFieldTypeId);
|
|
56
|
+
/** @internal */
|
|
57
|
+
const ArrayUnionTypeId = "~@visiolab/effect-firebase/firestore/FieldValue/ArrayUnion";
|
|
58
|
+
const ArrayUnionProto = {
|
|
59
|
+
...Inspectable.BaseProto,
|
|
60
|
+
[ArrayUnionTypeId]: ArrayUnionTypeId,
|
|
61
|
+
toJSON() {
|
|
62
|
+
return {
|
|
63
|
+
_id: "ArrayUnion",
|
|
64
|
+
elements: this.elements.map((element) => Inspectable.toJson(element))
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
/** @internal */
|
|
69
|
+
const isArrayUnion = (u) => Predicate.hasProperty(u, ArrayUnionTypeId);
|
|
70
|
+
/** @internal */
|
|
71
|
+
const arrayUnion = (...elements) => Object.assign(Object.create(ArrayUnionProto), { elements });
|
|
72
|
+
/** @internal */
|
|
73
|
+
const ArrayRemoveTypeId = "~@visiolab/effect-firebase/firestore/FieldValue/ArrayRemove";
|
|
74
|
+
const ArrayRemoveProto = {
|
|
75
|
+
...Inspectable.BaseProto,
|
|
76
|
+
[ArrayRemoveTypeId]: ArrayRemoveTypeId,
|
|
77
|
+
toJSON() {
|
|
78
|
+
return {
|
|
79
|
+
_id: "ArrayRemove",
|
|
80
|
+
elements: this.elements.map((element) => Inspectable.toJson(element))
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
/** @internal */
|
|
85
|
+
const isArrayRemove = (u) => Predicate.hasProperty(u, ArrayRemoveTypeId);
|
|
86
|
+
/** @internal */
|
|
87
|
+
const arrayRemove = (...elements) => Object.assign(Object.create(ArrayRemoveProto), { elements });
|
|
88
|
+
export { ArrayRemoveTypeId, ArrayUnionTypeId, DeleteFieldTypeId, IncrementTypeId, ServerTimestampTypeId, arrayRemove, arrayUnion, decrement, deleteField, increment, isArrayRemove, isArrayUnion, isDeleteField, isIncrement, isServerTimestamp, serverTimestamp };
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { checkDocument } from "./firestoreValueWalk.mjs";
|
|
2
|
+
import { toCodecFirestore } from "./firestoreValue.mjs";
|
|
3
|
+
import { existing, make as make$1 } from "./documentSnapshot.mjs";
|
|
4
|
+
import { documentAt, isCollectionGroup } from "./collectionGroup.mjs";
|
|
5
|
+
import { make as make$2 } from "./querySnapshot.mjs";
|
|
6
|
+
import { Context, Effect, Result, Schema, Stream } from "effect";
|
|
7
|
+
// src/firestore/internal/firestore.ts
|
|
8
|
+
/** @internal */
|
|
9
|
+
const TypeId = "~@visiolab/effect-firebase/firestore/Firestore";
|
|
10
|
+
/** @internal */
|
|
11
|
+
const TransactionTypeId = "~@visiolab/effect-firebase/firestore/Firestore/Transaction";
|
|
12
|
+
/** The last segment of a document's path: its id. */
|
|
13
|
+
const idOf = (path) => path.slice(path.lastIndexOf("/") + 1);
|
|
14
|
+
/**
|
|
15
|
+
* What a hook is handed for `source`, and how each document it read is
|
|
16
|
+
* addressed: in the collection read, by its id, or, for a group, matched by
|
|
17
|
+
* its path to the collection of the group it lives in.
|
|
18
|
+
*/
|
|
19
|
+
const resolve = (source) => isCollectionGroup(source) ? {
|
|
20
|
+
selector: {
|
|
21
|
+
_tag: "CollectionGroup",
|
|
22
|
+
id: source.id
|
|
23
|
+
},
|
|
24
|
+
refOf: (path) => documentAt(source, path)
|
|
25
|
+
} : {
|
|
26
|
+
selector: {
|
|
27
|
+
_tag: "Collection",
|
|
28
|
+
path: source.path
|
|
29
|
+
},
|
|
30
|
+
refOf: (path) => Result.succeed(source.doc(source.collection.id.make(idOf(path))))
|
|
31
|
+
};
|
|
32
|
+
/** A snapshot of the document at `ref`, as a hook read it, with its extra. */
|
|
33
|
+
const documentSnapshotOf = (ref, read) => Result.map(checkDocument(ref.path, read.data), (data) => make$1(ref, data, read.extra));
|
|
34
|
+
/**
|
|
35
|
+
* A snapshot of the documents a hook read, each addressed by `refOf`, with the
|
|
36
|
+
* extra of each and of the read as a whole.
|
|
37
|
+
*/
|
|
38
|
+
const querySnapshotOf = (refOf, read) => Result.map(Result.all(read.docs.map((doc) => Result.flatMap(refOf(doc.path), (ref) => Result.map(checkDocument(ref.path, doc.data), (data) => existing(ref, data, doc.extra))))), (docs) => make$2(docs, read.extra));
|
|
39
|
+
/**
|
|
40
|
+
* Turns what is written, in the shape `schema` takes, into Firestore Values,
|
|
41
|
+
* through the codec derived from `schema`. Whatever services the schema
|
|
42
|
+
* encodes through are left for the caller to provide.
|
|
43
|
+
*/
|
|
44
|
+
const encode = (schema, data) => Schema.encodeEffect(toCodecFirestore(schema))(data);
|
|
45
|
+
/** A document, in its model's `create` shape. */
|
|
46
|
+
const encodeCreate = (collection, data) => {
|
|
47
|
+
const schema = collection.schema.create;
|
|
48
|
+
return encode(schema, data);
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* A patch, in its model's update shape for `mode`, which encodes to a map from
|
|
52
|
+
* the field paths it writes at to what it writes there.
|
|
53
|
+
*/
|
|
54
|
+
const encodeUpdate = (collection, data, mode) => {
|
|
55
|
+
const schema = mode === "unchecked" ? collection.schema.updateUnchecked : collection.schema.update;
|
|
56
|
+
return encode(schema, data);
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* The reads and writes a {@link Firestore.Firestore} and a transaction share,
|
|
60
|
+
* made against `target`. Each runs in a span named after it, so traces read
|
|
61
|
+
* the same whichever backend answered.
|
|
62
|
+
*/
|
|
63
|
+
const operations = (target) => {
|
|
64
|
+
const span = (name) => Effect.withSpan(`${target.name}.${name}`);
|
|
65
|
+
const getDocs = target.getDocs;
|
|
66
|
+
return {
|
|
67
|
+
getDoc: (ref, options) => Effect.flatMap(target.getDoc(ref.path, options), (read) => Effect.fromResult(documentSnapshotOf(ref, read))).pipe(span("getDoc")),
|
|
68
|
+
getDocs: getDocs === void 0 ? void 0 : (source, options) => {
|
|
69
|
+
const { selector, refOf } = resolve(source);
|
|
70
|
+
return Effect.flatMap(getDocs(selector, options), (read) => Effect.fromResult(querySnapshotOf(refOf, read))).pipe(span("getDocs"));
|
|
71
|
+
},
|
|
72
|
+
setDoc: (ref, data) => Effect.flatMap(encodeCreate(ref.collection, data), (encoded) => target.setDoc(ref.path, encoded)).pipe(span("setDoc")),
|
|
73
|
+
updateDoc: (ref, data, options) => Effect.flatMap(encodeUpdate(ref.collection, data, options?.mode), (encoded) => target.updateDoc(ref.path, encoded)).pipe(span("updateDoc"))
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* The transaction a body reads and writes through, over the operations of one
|
|
78
|
+
* attempt.
|
|
79
|
+
*/
|
|
80
|
+
const transactionOf = (transaction) => {
|
|
81
|
+
const readMany = transaction.getDocs;
|
|
82
|
+
const { getDoc, getDocs, setDoc, updateDoc } = operations({
|
|
83
|
+
name: "Firestore.Transaction",
|
|
84
|
+
getDoc: (path) => transaction.getDoc(path),
|
|
85
|
+
getDocs: readMany === void 0 ? void 0 : (selector) => readMany(selector),
|
|
86
|
+
setDoc: transaction.setDoc,
|
|
87
|
+
updateDoc: transaction.updateDoc
|
|
88
|
+
});
|
|
89
|
+
const portable = {
|
|
90
|
+
[TransactionTypeId]: TransactionTypeId,
|
|
91
|
+
getDoc,
|
|
92
|
+
setDoc,
|
|
93
|
+
updateDoc
|
|
94
|
+
};
|
|
95
|
+
return getDocs === void 0 ? portable : {
|
|
96
|
+
...portable,
|
|
97
|
+
getDocs
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* The key a transaction body finds its {@link Firestore.Transaction} under is
|
|
102
|
+
* handed in by the public module that defines it, so that this one only
|
|
103
|
+
* imports it as a type and the two do not import each other.
|
|
104
|
+
*
|
|
105
|
+
* @internal
|
|
106
|
+
*/
|
|
107
|
+
const make = (options, Transaction) => {
|
|
108
|
+
const { getDoc, getDocs, setDoc, updateDoc } = operations({
|
|
109
|
+
name: "Firestore",
|
|
110
|
+
getDoc: options.getDoc,
|
|
111
|
+
getDocs: options.getDocs,
|
|
112
|
+
setDoc: options.setDoc,
|
|
113
|
+
updateDoc: options.updateDoc
|
|
114
|
+
});
|
|
115
|
+
const addDoc = (ref, data) => Effect.flatMap(encodeCreate(ref.collection, data), (encoded) => Effect.map(options.addDoc(ref.path, encoded), (id) => ref.doc(ref.collection.id.make(id)))).pipe(Effect.withSpan("Firestore.addDoc"));
|
|
116
|
+
const streamDoc = (ref, listenOptions) => Stream.mapEffect(options.streamDoc(ref.path, listenOptions), (read) => Effect.fromResult(documentSnapshotOf(ref, read))).pipe(Stream.withSpan("Firestore.streamDoc"));
|
|
117
|
+
const streamDocs = (source, listenOptions) => {
|
|
118
|
+
const { selector, refOf } = resolve(source);
|
|
119
|
+
return Stream.mapEffect(options.streamDocs(selector, listenOptions), (read) => Effect.fromResult(querySnapshotOf(refOf, read))).pipe(Stream.withSpan("Firestore.streamDocs"));
|
|
120
|
+
};
|
|
121
|
+
const service = {
|
|
122
|
+
getDoc,
|
|
123
|
+
getDocs,
|
|
124
|
+
setDoc,
|
|
125
|
+
updateDoc,
|
|
126
|
+
addDoc,
|
|
127
|
+
streamDoc,
|
|
128
|
+
streamDocs,
|
|
129
|
+
runTransaction: Effect.fn("Firestore.runTransaction")(function* (effect, transactionOptions) {
|
|
130
|
+
const context = yield* Effect.context();
|
|
131
|
+
const offer = options.runTransaction.context;
|
|
132
|
+
const maxAttempts = transactionOptions?.maxAttempts;
|
|
133
|
+
return yield* options.runTransaction.run((operations) => {
|
|
134
|
+
const transaction = transactionOf(operations);
|
|
135
|
+
const withTransaction = Context.add(context, Transaction, transaction);
|
|
136
|
+
return Effect.provideContext(effect, offer === void 0 ? withTransaction : Context.merge(withTransaction, offer(transaction)));
|
|
137
|
+
}, maxAttempts === void 0 ? {} : { maxAttempts });
|
|
138
|
+
})
|
|
139
|
+
};
|
|
140
|
+
return {
|
|
141
|
+
[TypeId]: TypeId,
|
|
142
|
+
...service
|
|
143
|
+
};
|
|
144
|
+
};
|
|
145
|
+
export { TransactionTypeId, TypeId, make };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
declare const FirestoreError_base: Schema.Class<FirestoreError, Schema.TaggedStruct<"FirestoreError", {
|
|
3
|
+
readonly message: Schema.String;
|
|
4
|
+
/**
|
|
5
|
+
* The code the SDK reported, where it reported one it can be named by.
|
|
6
|
+
*
|
|
7
|
+
* The client SDK names every failure, such as `permission-denied`. The Admin
|
|
8
|
+
* SDK answers for Firestore over gRPC, which numbers its statuses rather than
|
|
9
|
+
* naming them, so there this is often absent even where something went wrong
|
|
10
|
+
* at the database.
|
|
11
|
+
*/
|
|
12
|
+
readonly code: Schema.optional<Schema.String>;
|
|
13
|
+
readonly cause: Schema.optional<Schema.Defect>;
|
|
14
|
+
}>, import("effect/Cause").YieldableError & {
|
|
15
|
+
readonly brand: unique symbol;
|
|
16
|
+
}>;
|
|
17
|
+
/** @internal */
|
|
18
|
+
export declare class FirestoreError extends FirestoreError_base {}
|
|
19
|
+
declare const UndefinedCollectionError_base: Schema.Class<UndefinedCollectionError, Schema.TaggedStruct<"UndefinedCollectionError", {
|
|
20
|
+
/** The full path of the document no collection of the group defines. */
|
|
21
|
+
readonly path: Schema.String;
|
|
22
|
+
/** The collection id of the group that was read. */
|
|
23
|
+
readonly collectionId: Schema.String;
|
|
24
|
+
}>, import("effect/Cause").YieldableError & {
|
|
25
|
+
readonly brand: unique symbol;
|
|
26
|
+
}>;
|
|
27
|
+
/** @internal */
|
|
28
|
+
export declare class UndefinedCollectionError extends UndefinedCollectionError_base {}
|
|
29
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
// src/firestore/internal/firestoreError.ts
|
|
3
|
+
/**
|
|
4
|
+
* The errors of the Firestore module, defined apart from it so that the
|
|
5
|
+
* modules it builds on can raise them without importing it back: the public
|
|
6
|
+
* module imports its implementation, and an import cycle through it would
|
|
7
|
+
* leave these unset while a bundle evaluates.
|
|
8
|
+
*/
|
|
9
|
+
/** @internal */
|
|
10
|
+
var FirestoreError = class extends Schema.TaggedError()("FirestoreError", {
|
|
11
|
+
message: Schema.String,
|
|
12
|
+
/**
|
|
13
|
+
* The code the SDK reported, where it reported one it can be named by.
|
|
14
|
+
*
|
|
15
|
+
* The client SDK names every failure, such as `permission-denied`. The Admin
|
|
16
|
+
* SDK answers for Firestore over gRPC, which numbers its statuses rather than
|
|
17
|
+
* naming them, so there this is often absent even where something went wrong
|
|
18
|
+
* at the database.
|
|
19
|
+
*/
|
|
20
|
+
code: Schema.optional(Schema.String),
|
|
21
|
+
cause: Schema.optional(Schema.Defect())
|
|
22
|
+
}) {};
|
|
23
|
+
/** @internal */
|
|
24
|
+
var UndefinedCollectionError = class extends Schema.TaggedError()("UndefinedCollectionError", {
|
|
25
|
+
/** The full path of the document no collection of the group defines. */
|
|
26
|
+
path: Schema.String,
|
|
27
|
+
/** The collection id of the group that was read. */
|
|
28
|
+
collectionId: Schema.String
|
|
29
|
+
}) {};
|
|
30
|
+
export { FirestoreError, UndefinedCollectionError };
|