@visiolab/effect-firebase-admin 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 +73 -2
- package/dist/_virtual/_rolldown/runtime.mjs +12 -0
- package/dist/app/App.d.mts +115 -0
- package/dist/app/App.mjs +145 -0
- package/dist/app/App.mjs.map +1 -0
- package/dist/app/index.d.mts +2 -0
- package/dist/app/index.mjs +1 -0
- package/dist/app/internal/app.mjs +20 -0
- package/dist/app/internal/app.mjs.map +1 -0
- package/dist/auth/Auth.d.mts +167 -0
- package/dist/auth/Auth.mjs +131 -0
- package/dist/auth/Auth.mjs.map +1 -0
- package/dist/auth/index.d.mts +2 -0
- package/dist/auth/index.mjs +1 -0
- package/dist/firestore/AdminFirestore.d.mts +241 -0
- package/dist/firestore/AdminFirestore.mjs +133 -0
- package/dist/firestore/AdminFirestore.mjs.map +1 -0
- package/dist/firestore/AdminFirestoreValue.d.mts +61 -0
- package/dist/firestore/AdminFirestoreValue.mjs +62 -0
- package/dist/firestore/AdminFirestoreValue.mjs.map +1 -0
- package/dist/firestore/index.d.mts +3 -0
- package/dist/firestore/index.mjs +3 -0
- package/dist/firestore/internal/firestore.mjs +135 -0
- package/dist/firestore/internal/firestore.mjs.map +1 -0
- package/dist/firestore/internal/firestoreValue.mjs +56 -0
- package/dist/firestore/internal/firestoreValue.mjs.map +1 -0
- package/package.json +61 -3
- package/src/app/App.ts +158 -0
- package/src/app/index.ts +7 -0
- package/src/app/internal/app.ts +35 -0
- package/src/auth/Auth.ts +258 -0
- package/src/auth/index.ts +7 -0
- package/src/firestore/AdminFirestore.ts +303 -0
- package/src/firestore/AdminFirestoreValue.ts +93 -0
- package/src/firestore/index.ts +14 -0
- package/src/firestore/internal/firestore.ts +238 -0
- package/src/firestore/internal/firestoreValue.ts +85 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { __exportAll } from "../_virtual/_rolldown/runtime.mjs";
|
|
2
|
+
import { fromAdmin as fromAdmin$1, toAdmin as toAdmin$1 } from "./internal/firestoreValue.mjs";
|
|
3
|
+
// src/firestore/AdminFirestoreValue.ts
|
|
4
|
+
var AdminFirestoreValue_exports = /* @__PURE__ */ __exportAll({
|
|
5
|
+
fromAdmin: () => fromAdmin,
|
|
6
|
+
toAdmin: () => toAdmin
|
|
7
|
+
});
|
|
8
|
+
/**
|
|
9
|
+
* Turns a Firestore Value into the value the Admin SDK writes into
|
|
10
|
+
* `firestore`.
|
|
11
|
+
*
|
|
12
|
+
* **When to use**
|
|
13
|
+
*
|
|
14
|
+
* Use to write what a model encodes to through the Admin SDK itself.
|
|
15
|
+
*
|
|
16
|
+
* **Details**
|
|
17
|
+
*
|
|
18
|
+
* The portable `Timestamp`, `GeoPoint`, bytes and sentinels become the Admin
|
|
19
|
+
* SDK's own. A `DocumentPath` becomes `firestore.doc(path)`, so it points into
|
|
20
|
+
* that database. Map keys are kept as they are, so the dotted field paths of an
|
|
21
|
+
* update stay field paths.
|
|
22
|
+
*
|
|
23
|
+
* It throws for anything that is not a Firestore Value, such as one of the
|
|
24
|
+
* Admin SDK's own values or a class instance cast past the type. What
|
|
25
|
+
* {@link FirestoreValue.toCodecFirestore} encodes is always a Firestore Value.
|
|
26
|
+
*
|
|
27
|
+
* @see {@link fromAdmin} for the other direction
|
|
28
|
+
*
|
|
29
|
+
* @stability unstable
|
|
30
|
+
* @category conversions
|
|
31
|
+
* @since 0.2.0
|
|
32
|
+
*/
|
|
33
|
+
const toAdmin = toAdmin$1;
|
|
34
|
+
/**
|
|
35
|
+
* Turns a value the Admin SDK read into a Firestore Value.
|
|
36
|
+
*
|
|
37
|
+
* **When to use**
|
|
38
|
+
*
|
|
39
|
+
* Use to decode what the Admin SDK itself read through a model, with
|
|
40
|
+
* {@link FirestoreValue.toCodecFirestore}.
|
|
41
|
+
*
|
|
42
|
+
* **Details**
|
|
43
|
+
*
|
|
44
|
+
* The Admin SDK's `Timestamp`, `GeoPoint`, bytes and sentinels become their
|
|
45
|
+
* portable forms. A `DocumentReference` becomes the `DocumentPath` it points
|
|
46
|
+
* at, since Firestore stores nothing else of it;
|
|
47
|
+
* {@link FirestoreSchema.DocumentReference} reads that back into the collection
|
|
48
|
+
* it names.
|
|
49
|
+
*
|
|
50
|
+
* Fails with a {@link Firestore.FirestoreError} naming the field for a value
|
|
51
|
+
* that is not a Firestore Value, such as a vector.
|
|
52
|
+
*
|
|
53
|
+
* @see {@link toAdmin} for the other direction
|
|
54
|
+
*
|
|
55
|
+
* @stability unstable
|
|
56
|
+
* @category conversions
|
|
57
|
+
* @since 0.2.0
|
|
58
|
+
*/
|
|
59
|
+
const fromAdmin = fromAdmin$1;
|
|
60
|
+
export { AdminFirestoreValue_exports, fromAdmin, toAdmin };
|
|
61
|
+
|
|
62
|
+
//# sourceMappingURL=AdminFirestoreValue.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AdminFirestoreValue.mjs","names":["internal.toAdmin","internal.fromAdmin"],"sources":["../../src/firestore/AdminFirestoreValue.ts"],"sourcesContent":["/**\n * Maps between Firestore Values, the portable values a model encodes to, and\n * the values the Admin SDK reads and writes.\n *\n * {@link AdminFirestore} applies it on every read and write. Use it directly to\n * read or write a model through the Admin SDK itself, such as through\n * {@link AdminFirestore.instance}.\n *\n * @example Writing a model through the Admin SDK\n * ```ts\n * import { Schema } from \"effect\";\n * import { FirestoreModel, FirestoreValue } from \"@visiolab/effect-firebase/firestore\";\n * import { AdminFirestoreValue } from \"@visiolab/effect-firebase-admin/firestore\";\n * import { type DocumentData, getFirestore } from \"firebase-admin/firestore\";\n *\n * class Post extends FirestoreModel.Class<Post>(\"Post\")({\n * title: Schema.String,\n * }) {}\n *\n * const firestore = getFirestore();\n * const encoded = Schema.encodeSync(FirestoreValue.toCodecFirestore(Post.create))({\n * title: \"Hello\",\n * });\n * await firestore\n * .doc(\"posts/hello\")\n * .set(AdminFirestoreValue.toAdmin(encoded, firestore) as DocumentData);\n * ```\n *\n * @see {@link ClientFirestoreValue} for the client SDK's mapping\n *\n * @stability unstable\n * @since 0.2.0\n */\nimport type { Result } from \"effect\";\nimport type { FirestoreError } from \"@visiolab/effect-firebase/firestore/Firestore\";\nimport type { FirestoreValue } from \"@visiolab/effect-firebase/firestore/FirestoreValue\";\nimport type { Firestore } from \"firebase-admin/firestore\";\nimport * as internal from \"./internal/firestoreValue.ts\";\n\n/**\n * Turns a Firestore Value into the value the Admin SDK writes into\n * `firestore`.\n *\n * **When to use**\n *\n * Use to write what a model encodes to through the Admin SDK itself.\n *\n * **Details**\n *\n * The portable `Timestamp`, `GeoPoint`, bytes and sentinels become the Admin\n * SDK's own. A `DocumentPath` becomes `firestore.doc(path)`, so it points into\n * that database. Map keys are kept as they are, so the dotted field paths of an\n * update stay field paths.\n *\n * It throws for anything that is not a Firestore Value, such as one of the\n * Admin SDK's own values or a class instance cast past the type. What\n * {@link FirestoreValue.toCodecFirestore} encodes is always a Firestore Value.\n *\n * @see {@link fromAdmin} for the other direction\n *\n * @stability unstable\n * @category conversions\n * @since 0.2.0\n */\nexport const toAdmin: (value: FirestoreValue, firestore: Firestore) => unknown = internal.toAdmin;\n\n/**\n * Turns a value the Admin SDK read into a Firestore Value.\n *\n * **When to use**\n *\n * Use to decode what the Admin SDK itself read through a model, with\n * {@link FirestoreValue.toCodecFirestore}.\n *\n * **Details**\n *\n * The Admin SDK's `Timestamp`, `GeoPoint`, bytes and sentinels become their\n * portable forms. A `DocumentReference` becomes the `DocumentPath` it points\n * at, since Firestore stores nothing else of it;\n * {@link FirestoreSchema.DocumentReference} reads that back into the collection\n * it names.\n *\n * Fails with a {@link Firestore.FirestoreError} naming the field for a value\n * that is not a Firestore Value, such as a vector.\n *\n * @see {@link toAdmin} for the other direction\n *\n * @stability unstable\n * @category conversions\n * @since 0.2.0\n */\nexport const fromAdmin: (value: unknown) => Result.Result<FirestoreValue, FirestoreError> =\n internal.fromAdmin;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgEA,MAAa,UAAoEA;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BjF,MAAa,YACXC"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { App } from "../../app/App.mjs";
|
|
2
|
+
import { readDocument, toAdmin } from "./firestoreValue.mjs";
|
|
3
|
+
import { Context, Effect, Result } from "effect";
|
|
4
|
+
import { FirebaseError } from "firebase-admin/app";
|
|
5
|
+
import * as Firestore$2 from "@visiolab/effect-firebase/firestore/Firestore";
|
|
6
|
+
import * as Admin from "firebase-admin/firestore";
|
|
7
|
+
// src/firestore/internal/firestore.ts
|
|
8
|
+
/** @internal */
|
|
9
|
+
const TypeId = "~@visiolab/effect-firebase-admin/firestore/AdminFirestore";
|
|
10
|
+
/** @internal */
|
|
11
|
+
const TransactionTypeId = "~@visiolab/effect-firebase-admin/firestore/AdminFirestore/AdminTransaction";
|
|
12
|
+
/**
|
|
13
|
+
* Opens the database the options name.
|
|
14
|
+
*
|
|
15
|
+
* Settings can only be handed over through `initializeFirestore`, which refuses
|
|
16
|
+
* a database already opened with different ones, so they are only passed along
|
|
17
|
+
* when there are some: asking for the default database twice keeps working, and
|
|
18
|
+
* asking for it twice with settings that disagree is the mistake it reports.
|
|
19
|
+
*/
|
|
20
|
+
const open = (app, options) => {
|
|
21
|
+
const databaseId = options?.databaseId;
|
|
22
|
+
if (options?.preferRest === void 0) return databaseId === void 0 ? Admin.getFirestore(app) : Admin.getFirestore(app, databaseId);
|
|
23
|
+
const settings = { preferRest: options.preferRest };
|
|
24
|
+
return databaseId === void 0 ? Admin.initializeFirestore(app, settings) : Admin.initializeFirestore(app, settings, databaseId);
|
|
25
|
+
};
|
|
26
|
+
const firestoreError = (message) => (cause) => new Firestore$2.FirestoreError({
|
|
27
|
+
message,
|
|
28
|
+
code: cause instanceof FirebaseError ? cause.code : void 0,
|
|
29
|
+
cause
|
|
30
|
+
});
|
|
31
|
+
/**
|
|
32
|
+
* Opens the database the options name on the {@link App.App} in context.
|
|
33
|
+
*
|
|
34
|
+
* The SDK memoizes a database on the app itself, so there is nothing to release
|
|
35
|
+
* here.
|
|
36
|
+
*
|
|
37
|
+
* @internal
|
|
38
|
+
*/
|
|
39
|
+
const openDatabase = Effect.fnUntraced(function* (options) {
|
|
40
|
+
const app = yield* App;
|
|
41
|
+
return open(app, options);
|
|
42
|
+
});
|
|
43
|
+
/**
|
|
44
|
+
* Calls the Admin SDK, failing with `message` for whatever it throws or
|
|
45
|
+
* rejects with. Writes into a transaction are queued rather than sent, so they
|
|
46
|
+
* answer straight away - or throw, where the transaction refuses them.
|
|
47
|
+
*/
|
|
48
|
+
const call = (message, f) => Effect.tryPromise({
|
|
49
|
+
try: () => Promise.resolve(f()),
|
|
50
|
+
catch: firestoreError(message)
|
|
51
|
+
});
|
|
52
|
+
/**
|
|
53
|
+
* A document's data as Firestore Values, or `undefined` for one that is not
|
|
54
|
+
* there. A document holding a value outside the Firestore Value union, such as
|
|
55
|
+
* a vector, fails to read.
|
|
56
|
+
*/
|
|
57
|
+
const dataOf = (path, data) => readDocument(path, data);
|
|
58
|
+
const documentReadOf = (path, snapshot) => Result.map(dataOf(path, snapshot.data()), (data) => ({ data }));
|
|
59
|
+
const queryReadOf = (query) => Result.map(Result.all(query.docs.map((snapshot) => Result.map(dataOf(snapshot.ref.path, snapshot.data()), (data) => ({
|
|
60
|
+
path: snapshot.ref.path,
|
|
61
|
+
data
|
|
62
|
+
})))), (docs) => ({ docs }));
|
|
63
|
+
/** The Admin SDK query `selector` names, and what it is called in a failure. */
|
|
64
|
+
const queryOf = (firestore, selector) => selector._tag === "CollectionGroup" ? {
|
|
65
|
+
query: () => firestore.collectionGroup(selector.id),
|
|
66
|
+
name: `the collection group "${selector.id}"`
|
|
67
|
+
} : {
|
|
68
|
+
query: () => firestore.collection(selector.path),
|
|
69
|
+
name: `the collection "${selector.path}"`
|
|
70
|
+
};
|
|
71
|
+
/** Reads many documents, through the database or a transaction. */
|
|
72
|
+
const getDocs = (firestore, get) => (selector) => {
|
|
73
|
+
const { query, name } = queryOf(firestore, selector);
|
|
74
|
+
return Effect.flatMap(call(`Failed to read ${name}`, () => get(query())), (snapshot) => Effect.fromResult(queryReadOf(snapshot)));
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* The reads and writes of one attempt at a transaction, which, unlike the
|
|
78
|
+
* client's, can read many documents.
|
|
79
|
+
*/
|
|
80
|
+
const transactionOperations = (firestore, tx) => ({
|
|
81
|
+
getDoc: (path) => Effect.flatMap(call(`Failed to read the document "${path}"`, () => tx.get(firestore.doc(path))), (snapshot) => Effect.fromResult(documentReadOf(path, snapshot))),
|
|
82
|
+
getDocs: getDocs(firestore, (query) => tx.get(query)),
|
|
83
|
+
setDoc: (path, data) => call(`Failed to write the document "${path}"`, () => {
|
|
84
|
+
tx.set(firestore.doc(path), toAdmin(data, firestore));
|
|
85
|
+
}),
|
|
86
|
+
updateDoc: (path, data) => call(`Failed to update the document "${path}"`, () => {
|
|
87
|
+
tx.update(firestore.doc(path), toAdmin(data, firestore));
|
|
88
|
+
})
|
|
89
|
+
});
|
|
90
|
+
/**
|
|
91
|
+
* The service over a database already opened.
|
|
92
|
+
*
|
|
93
|
+
* The key a transaction body finds its
|
|
94
|
+
* {@link AdminFirestore.AdminTransaction} under is handed in by the public
|
|
95
|
+
* module that defines it, so that this one only imports it as a type and the
|
|
96
|
+
* two do not import each other.
|
|
97
|
+
*
|
|
98
|
+
* @internal
|
|
99
|
+
*/
|
|
100
|
+
const fromDatabase = (firestore, AdminTransaction) => {
|
|
101
|
+
const toAdmin$1 = (data) => toAdmin(data, firestore);
|
|
102
|
+
const service = Firestore$2.make({
|
|
103
|
+
getDoc: (path) => Effect.flatMap(call(`Failed to read the document "${path}"`, () => firestore.doc(path).get()), (snapshot) => Effect.fromResult(documentReadOf(path, snapshot))),
|
|
104
|
+
getDocs: getDocs(firestore, (query) => query.get()),
|
|
105
|
+
setDoc: (path, data) => Effect.asVoid(call(`Failed to write the document "${path}"`, () => firestore.doc(path).set(toAdmin$1(data)))),
|
|
106
|
+
updateDoc: (path, data) => Effect.asVoid(call(`Failed to update the document "${path}"`, () => firestore.doc(path).update(toAdmin$1(data)))),
|
|
107
|
+
addDoc: (path, data) => Effect.map(call(`Failed to add a document to "${path}"`, () => firestore.collection(path).add(toAdmin$1(data))), (added) => added.id),
|
|
108
|
+
streamDoc: (path) => Firestore$2.streamFromListener({
|
|
109
|
+
subscribe: (onNext, onError) => firestore.doc(path).onSnapshot(onNext, onError),
|
|
110
|
+
onError: firestoreError(`Failed to listen to the document "${path}"`),
|
|
111
|
+
map: (snapshot) => documentReadOf(path, snapshot)
|
|
112
|
+
}),
|
|
113
|
+
streamDocs: (selector) => {
|
|
114
|
+
const { query, name } = queryOf(firestore, selector);
|
|
115
|
+
return Firestore$2.streamFromListener({
|
|
116
|
+
subscribe: (onNext, onError) => query().onSnapshot(onNext, onError),
|
|
117
|
+
onError: firestoreError(`Failed to listen to ${name}`),
|
|
118
|
+
map: queryReadOf
|
|
119
|
+
});
|
|
120
|
+
},
|
|
121
|
+
runTransaction: Firestore$2.transactionFromCallback({
|
|
122
|
+
run: (callback, options) => firestore.runTransaction(callback, options),
|
|
123
|
+
operations: (tx) => transactionOperations(firestore, tx),
|
|
124
|
+
onError: firestoreError("Failed to run a transaction"),
|
|
125
|
+
context: (transaction) => Context.make(AdminTransaction, Object.assign(transaction, { [TransactionTypeId]: TransactionTypeId }))
|
|
126
|
+
})
|
|
127
|
+
});
|
|
128
|
+
return Object.assign(service, {
|
|
129
|
+
[TypeId]: TypeId,
|
|
130
|
+
instance: firestore
|
|
131
|
+
});
|
|
132
|
+
};
|
|
133
|
+
export { TransactionTypeId, TypeId, fromDatabase, openDatabase };
|
|
134
|
+
|
|
135
|
+
//# sourceMappingURL=firestore.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"firestore.mjs","names":["Firestore","App.App","firestoreValue.readDocument","firestoreValue.toAdmin","toAdmin"],"sources":["../../../src/firestore/internal/firestore.ts"],"sourcesContent":["import { Context, Effect, Result } from \"effect\";\nimport * as Firestore from \"@visiolab/effect-firebase/firestore/Firestore\";\nimport { FirebaseError } from \"firebase-admin/app\";\nimport * as Admin from \"firebase-admin/firestore\";\nimport * as App from \"../../app/App.ts\";\nimport type * as AdminFirestore from \"../AdminFirestore.ts\";\nimport * as firestoreValue from \"./firestoreValue.ts\";\n\n/** @internal */\nexport const TypeId = \"~@visiolab/effect-firebase-admin/firestore/AdminFirestore\";\n\n/** @internal */\nexport const TransactionTypeId =\n \"~@visiolab/effect-firebase-admin/firestore/AdminFirestore/AdminTransaction\";\n\n/**\n * Opens the database the options name.\n *\n * Settings can only be handed over through `initializeFirestore`, which refuses\n * a database already opened with different ones, so they are only passed along\n * when there are some: asking for the default database twice keeps working, and\n * asking for it twice with settings that disagree is the mistake it reports.\n */\nconst open = (app: App.App, options: AdminFirestore.Options | undefined): Admin.Firestore => {\n const databaseId = options?.databaseId;\n if (options?.preferRest === undefined) {\n return databaseId === undefined ? Admin.getFirestore(app) : Admin.getFirestore(app, databaseId);\n }\n const settings = { preferRest: options.preferRest };\n return databaseId === undefined\n ? Admin.initializeFirestore(app, settings)\n : Admin.initializeFirestore(app, settings, databaseId);\n};\n\nconst firestoreError = (message: string) => (cause: unknown) =>\n new Firestore.FirestoreError({\n message,\n // Firestore answers over gRPC, whose statuses are numbered rather than\n // named, so a code only comes back for the failures the Admin SDK raises\n // itself.\n code: cause instanceof FirebaseError ? cause.code : undefined,\n cause,\n });\n\n/**\n * Opens the database the options name on the {@link App.App} in context.\n *\n * The SDK memoizes a database on the app itself, so there is nothing to release\n * here.\n *\n * @internal\n */\nexport const openDatabase = Effect.fnUntraced(function* (options?: AdminFirestore.Options) {\n const app = yield* App.App;\n return open(app, options);\n});\n\n/**\n * Calls the Admin SDK, failing with `message` for whatever it throws or\n * rejects with. Writes into a transaction are queued rather than sent, so they\n * answer straight away - or throw, where the transaction refuses them.\n */\nconst call = <A>(message: string, f: () => PromiseLike<A> | A) =>\n Effect.tryPromise({ try: () => Promise.resolve(f()), catch: firestoreError(message) });\n\n/**\n * A document's data as Firestore Values, or `undefined` for one that is not\n * there. A document holding a value outside the Firestore Value union, such as\n * a vector, fails to read.\n */\nconst dataOf = (\n path: string,\n data: Admin.DocumentData | undefined,\n): Result.Result<Firestore.DocumentData | undefined, Firestore.FirestoreError> =>\n firestoreValue.readDocument(path, data) as Result.Result<\n Firestore.DocumentData | undefined,\n Firestore.FirestoreError\n >;\n\nconst documentReadOf = (\n path: string,\n snapshot: Admin.DocumentSnapshot,\n): Result.Result<Firestore.DocumentRead, Firestore.FirestoreError> =>\n Result.map(dataOf(path, snapshot.data()), (data) => ({ data }));\n\nconst queryReadOf = (\n query: Admin.QuerySnapshot,\n): Result.Result<Firestore.QueryRead, Firestore.FirestoreError> =>\n Result.map(\n Result.all(\n query.docs.map((snapshot) =>\n // A document read as one of many is always there.\n Result.map(dataOf(snapshot.ref.path, snapshot.data()), (data) => ({\n path: snapshot.ref.path,\n data: data as Firestore.DocumentData,\n })),\n ),\n ),\n (docs) => ({ docs }),\n );\n\n/** The Admin SDK query `selector` names, and what it is called in a failure. */\nconst queryOf = (firestore: Admin.Firestore, selector: Firestore.CollectionSelector) =>\n selector._tag === \"CollectionGroup\"\n ? {\n query: (): Admin.Query => firestore.collectionGroup(selector.id),\n name: `the collection group \"${selector.id}\"`,\n }\n : {\n query: (): Admin.Query => firestore.collection(selector.path),\n name: `the collection \"${selector.path}\"`,\n };\n\n/** Reads many documents, through the database or a transaction. */\nconst getDocs =\n (firestore: Admin.Firestore, get: (query: Admin.Query) => Promise<Admin.QuerySnapshot>) =>\n (selector: Firestore.CollectionSelector) => {\n const { query, name } = queryOf(firestore, selector);\n return Effect.flatMap(\n call(`Failed to read ${name}`, () => get(query())),\n (snapshot) => Effect.fromResult(queryReadOf(snapshot)),\n );\n };\n\n/**\n * The reads and writes of one attempt at a transaction, which, unlike the\n * client's, can read many documents.\n */\nconst transactionOperations = (\n firestore: Admin.Firestore,\n tx: Admin.Transaction,\n): Required<Firestore.TransactionOperations> => ({\n getDoc: (path) =>\n Effect.flatMap(\n call(`Failed to read the document \"${path}\"`, () => tx.get(firestore.doc(path))),\n (snapshot) => Effect.fromResult(documentReadOf(path, snapshot)),\n ),\n getDocs: getDocs(firestore, (query) => tx.get(query)),\n setDoc: (path, data) =>\n call(`Failed to write the document \"${path}\"`, () => {\n tx.set(firestore.doc(path), firestoreValue.toAdmin(data, firestore) as Admin.DocumentData);\n }),\n updateDoc: (path, data) =>\n call(`Failed to update the document \"${path}\"`, () => {\n tx.update(\n firestore.doc(path),\n firestoreValue.toAdmin(data, firestore) as Admin.UpdateData<Admin.DocumentData>,\n );\n }),\n});\n\n/**\n * The service over a database already opened.\n *\n * The key a transaction body finds its\n * {@link AdminFirestore.AdminTransaction} under is handed in by the public\n * module that defines it, so that this one only imports it as a type and the\n * two do not import each other.\n *\n * @internal\n */\nexport const fromDatabase = (\n firestore: Admin.Firestore,\n AdminTransaction: typeof AdminFirestore.AdminTransaction,\n): AdminFirestore.AdminFirestore & Firestore.Firestore => {\n const toAdmin = (data: Firestore.DocumentData) => firestoreValue.toAdmin(data, firestore);\n\n const service = Firestore.make({\n getDoc: (path) =>\n Effect.flatMap(\n call(`Failed to read the document \"${path}\"`, () => firestore.doc(path).get()),\n (snapshot) => Effect.fromResult(documentReadOf(path, snapshot)),\n ),\n\n getDocs: getDocs(firestore, (query) => query.get()),\n\n setDoc: (path, data) =>\n Effect.asVoid(\n call(`Failed to write the document \"${path}\"`, () =>\n firestore.doc(path).set(toAdmin(data) as Admin.DocumentData),\n ),\n ),\n\n updateDoc: (path, data) =>\n Effect.asVoid(\n call(`Failed to update the document \"${path}\"`, () =>\n firestore.doc(path).update(toAdmin(data) as Admin.UpdateData<Admin.DocumentData>),\n ),\n ),\n\n addDoc: (path, data) =>\n Effect.map(\n call(`Failed to add a document to \"${path}\"`, () =>\n firestore.collection(path).add(toAdmin(data) as Admin.DocumentData),\n ),\n (added) => added.id,\n ),\n\n streamDoc: (path) =>\n Firestore.streamFromListener({\n subscribe: (onNext, onError) => firestore.doc(path).onSnapshot(onNext, onError),\n onError: firestoreError(`Failed to listen to the document \"${path}\"`),\n map: (snapshot: Admin.DocumentSnapshot) => documentReadOf(path, snapshot),\n }),\n\n streamDocs: (selector) => {\n const { query, name } = queryOf(firestore, selector);\n return Firestore.streamFromListener({\n subscribe: (onNext, onError) => query().onSnapshot(onNext, onError),\n onError: firestoreError(`Failed to listen to ${name}`),\n map: queryReadOf,\n });\n },\n\n runTransaction: Firestore.transactionFromCallback({\n run: (callback, options) => firestore.runTransaction(callback, options),\n operations: (tx: Admin.Transaction) => transactionOperations(firestore, tx),\n onError: firestoreError(\"Failed to run a transaction\"),\n // The operations read many documents, so the transaction core builds\n // has a `getDocs`. Branding it in place, rather than a copy, keeps one\n // object under both keys.\n context: (transaction) =>\n Context.make(\n AdminTransaction,\n Object.assign(transaction, { [TransactionTypeId]: TransactionTypeId } as const),\n ),\n }),\n });\n\n // The Admin interface is checked against what `make` built here, except for\n // the Admin Transaction its `context` offers: the compiler does not check\n // transaction services on assignment, so the AdminTransaction tests do.\n const admin: AdminFirestore.AdminFirestore & Firestore.Firestore = Object.assign(service, {\n [TypeId]: TypeId as typeof TypeId,\n instance: firestore,\n });\n return admin;\n};\n"],"mappings":";;;;;;;;AASA,MAAa,SAAS;;AAGtB,MAAa,oBACX;;;;;;;;;AAUF,MAAM,QAAQ,KAAc,YAAiE;CAC3F,MAAM,aAAa,SAAS;CAC5B,IAAI,SAAS,eAAe,KAAA,GAC1B,OAAO,eAAe,KAAA,IAAY,MAAM,aAAa,GAAG,IAAI,MAAM,aAAa,KAAK,UAAU;CAEhG,MAAM,WAAW,EAAE,YAAY,QAAQ,WAAW;CAClD,OAAO,eAAe,KAAA,IAClB,MAAM,oBAAoB,KAAK,QAAQ,IACvC,MAAM,oBAAoB,KAAK,UAAU,UAAU;AACzD;AAEA,MAAM,kBAAkB,aAAqB,UAC3C,IAAIA,YAAU,eAAe;CAC3B;CAIA,MAAM,iBAAiB,gBAAgB,MAAM,OAAO,KAAA;CACpD;AACF,CAAC;;;;;;;;;AAUH,MAAa,eAAe,OAAO,WAAW,WAAW,SAAkC;CACzF,MAAM,MAAM,OAAOC;CACnB,OAAO,KAAK,KAAK,OAAO;AAC1B,CAAC;;;;;;AAOD,MAAM,QAAW,SAAiB,MAChC,OAAO,WAAW;CAAE,WAAW,QAAQ,QAAQ,EAAE,CAAC;CAAG,OAAO,eAAe,OAAO;AAAE,CAAC;;;;;;AAOvF,MAAM,UACJ,MACA,SAEAC,aAA4B,MAAM,IAAI;AAKxC,MAAM,kBACJ,MACA,aAEA,OAAO,IAAI,OAAO,MAAM,SAAS,KAAK,CAAC,IAAI,UAAU,EAAE,KAAK,EAAE;AAEhE,MAAM,eACJ,UAEA,OAAO,IACL,OAAO,IACL,MAAM,KAAK,KAAK,aAEd,OAAO,IAAI,OAAO,SAAS,IAAI,MAAM,SAAS,KAAK,CAAC,IAAI,UAAU;CAChE,MAAM,SAAS,IAAI;CACb;AACR,EAAE,CACJ,CACF,IACC,UAAU,EAAE,KAAK,EACpB;;AAGF,MAAM,WAAW,WAA4B,aAC3C,SAAS,SAAS,oBACd;CACE,aAA0B,UAAU,gBAAgB,SAAS,EAAE;CAC/D,MAAM,yBAAyB,SAAS,GAAG;AAC7C,IACA;CACE,aAA0B,UAAU,WAAW,SAAS,IAAI;CAC5D,MAAM,mBAAmB,SAAS,KAAK;AACzC;;AAGN,MAAM,WACH,WAA4B,SAC5B,aAA2C;CAC1C,MAAM,EAAE,OAAO,SAAS,QAAQ,WAAW,QAAQ;CACnD,OAAO,OAAO,QACZ,KAAK,kBAAkB,cAAc,IAAI,MAAM,CAAC,CAAC,IAChD,aAAa,OAAO,WAAW,YAAY,QAAQ,CAAC,CACvD;AACF;;;;;AAMF,MAAM,yBACJ,WACA,QAC+C;CAC/C,SAAS,SACP,OAAO,QACL,KAAK,gCAAgC,KAAK,UAAU,GAAG,IAAI,UAAU,IAAI,IAAI,CAAC,CAAC,IAC9E,aAAa,OAAO,WAAW,eAAe,MAAM,QAAQ,CAAC,CAChE;CACF,SAAS,QAAQ,YAAY,UAAU,GAAG,IAAI,KAAK,CAAC;CACpD,SAAS,MAAM,SACb,KAAK,iCAAiC,KAAK,UAAU;EACnD,GAAG,IAAI,UAAU,IAAI,IAAI,GAAGC,QAAuB,MAAM,SAAS,CAAuB;CAC3F,CAAC;CACH,YAAY,MAAM,SAChB,KAAK,kCAAkC,KAAK,UAAU;EACpD,GAAG,OACD,UAAU,IAAI,IAAI,GAClBA,QAAuB,MAAM,SAAS,CACxC;CACF,CAAC;AACL;;;;;;;;;;;AAYA,MAAa,gBACX,WACA,qBACwD;CACxD,MAAMC,aAAW,SAAiCD,QAAuB,MAAM,SAAS;CAExF,MAAM,UAAUH,YAAU,KAAK;EAC7B,SAAS,SACP,OAAO,QACL,KAAK,gCAAgC,KAAK,UAAU,UAAU,IAAI,IAAI,CAAC,CAAC,IAAI,CAAC,IAC5E,aAAa,OAAO,WAAW,eAAe,MAAM,QAAQ,CAAC,CAChE;EAEF,SAAS,QAAQ,YAAY,UAAU,MAAM,IAAI,CAAC;EAElD,SAAS,MAAM,SACb,OAAO,OACL,KAAK,iCAAiC,KAAK,UACzC,UAAU,IAAI,IAAI,CAAC,CAAC,IAAII,UAAQ,IAAI,CAAuB,CAC7D,CACF;EAEF,YAAY,MAAM,SAChB,OAAO,OACL,KAAK,kCAAkC,KAAK,UAC1C,UAAU,IAAI,IAAI,CAAC,CAAC,OAAOA,UAAQ,IAAI,CAAyC,CAClF,CACF;EAEF,SAAS,MAAM,SACb,OAAO,IACL,KAAK,gCAAgC,KAAK,UACxC,UAAU,WAAW,IAAI,CAAC,CAAC,IAAIA,UAAQ,IAAI,CAAuB,CACpE,IACC,UAAU,MAAM,EACnB;EAEF,YAAY,SACVJ,YAAU,mBAAmB;GAC3B,YAAY,QAAQ,YAAY,UAAU,IAAI,IAAI,CAAC,CAAC,WAAW,QAAQ,OAAO;GAC9E,SAAS,eAAe,qCAAqC,KAAK,EAAE;GACpE,MAAM,aAAqC,eAAe,MAAM,QAAQ;EAC1E,CAAC;EAEH,aAAa,aAAa;GACxB,MAAM,EAAE,OAAO,SAAS,QAAQ,WAAW,QAAQ;GACnD,OAAOA,YAAU,mBAAmB;IAClC,YAAY,QAAQ,YAAY,MAAM,CAAC,CAAC,WAAW,QAAQ,OAAO;IAClE,SAAS,eAAe,uBAAuB,MAAM;IACrD,KAAK;GACP,CAAC;EACH;EAEA,gBAAgBA,YAAU,wBAAwB;GAChD,MAAM,UAAU,YAAY,UAAU,eAAe,UAAU,OAAO;GACtE,aAAa,OAA0B,sBAAsB,WAAW,EAAE;GAC1E,SAAS,eAAe,6BAA6B;GAIrD,UAAU,gBACR,QAAQ,KACN,kBACA,OAAO,OAAO,aAAa,GAAG,oBAAoB,kBAAkB,CAAU,CAChF;EACJ,CAAC;CACH,CAAC;CASD,OAJmE,OAAO,OAAO,SAAS;GACvF,SAAS;EACV,UAAU;CACZ,CACW;AACb"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Predicate } from "effect";
|
|
2
|
+
import * as Admin from "firebase-admin/firestore";
|
|
3
|
+
import * as DocumentPath from "@visiolab/effect-firebase/firestore/DocumentPath";
|
|
4
|
+
import * as FieldValue from "@visiolab/effect-firebase/firestore/FieldValue";
|
|
5
|
+
import * as FirestoreValue$1 from "@visiolab/effect-firebase/firestore/FirestoreValue";
|
|
6
|
+
import * as GeoPoint from "@visiolab/effect-firebase/firestore/GeoPoint";
|
|
7
|
+
import * as Timestamp from "@visiolab/effect-firebase/firestore/Timestamp";
|
|
8
|
+
// src/firestore/internal/firestoreValue.ts
|
|
9
|
+
/** How the Admin SDK writes each kind of Firestore Value into `firestore`. */
|
|
10
|
+
const writer = (firestore) => ({
|
|
11
|
+
bytes: (bytes) => bytes,
|
|
12
|
+
timestamp: (ts) => new Admin.Timestamp(ts.seconds, ts.nanoseconds),
|
|
13
|
+
geoPoint: (point) => new Admin.GeoPoint(point.latitude, point.longitude),
|
|
14
|
+
documentPath: (ref) => firestore.doc(ref.path),
|
|
15
|
+
serverTimestamp: () => Admin.FieldValue.serverTimestamp(),
|
|
16
|
+
increment: (increment) => Admin.FieldValue.increment(increment.by),
|
|
17
|
+
deleteField: () => Admin.FieldValue.delete(),
|
|
18
|
+
arrayUnion: (union, walk) => Admin.FieldValue.arrayUnion(...union.elements.map(walk)),
|
|
19
|
+
arrayRemove: (remove, walk) => Admin.FieldValue.arrayRemove(...remove.elements.map(walk))
|
|
20
|
+
});
|
|
21
|
+
/** @internal */
|
|
22
|
+
const toAdmin = (value, firestore) => FirestoreValue$1.toSdk(value, writer(firestore));
|
|
23
|
+
/** How the Admin SDK's own values read back as Firestore Values. */
|
|
24
|
+
const reader = (u, { walk, fail }) => {
|
|
25
|
+
if (u instanceof Uint8Array) return u.constructor === Uint8Array ? u : new Uint8Array(u.buffer, u.byteOffset, u.length);
|
|
26
|
+
if (u instanceof Admin.Timestamp) return Timestamp.makeUnsafe(u.seconds, u.nanoseconds);
|
|
27
|
+
if (u instanceof Admin.GeoPoint) return GeoPoint.makeUnsafe(u.latitude, u.longitude);
|
|
28
|
+
if (u instanceof Admin.DocumentReference) return DocumentPath.makeUnsafe(u.path);
|
|
29
|
+
if (u instanceof Admin.FieldValue) {
|
|
30
|
+
const sentinel = u;
|
|
31
|
+
switch (sentinel.methodName) {
|
|
32
|
+
case "FieldValue.serverTimestamp": return FieldValue.serverTimestamp;
|
|
33
|
+
case "FieldValue.delete": return FieldValue.deleteField;
|
|
34
|
+
case "FieldValue.increment":
|
|
35
|
+
if (Predicate.isNumber(sentinel.operand)) return FieldValue.increment(sentinel.operand);
|
|
36
|
+
break;
|
|
37
|
+
case "FieldValue.arrayUnion":
|
|
38
|
+
if (Array.isArray(sentinel.elements)) return FieldValue.arrayUnion(...sentinel.elements.map(walk));
|
|
39
|
+
break;
|
|
40
|
+
case "FieldValue.arrayRemove": if (Array.isArray(sentinel.elements)) return FieldValue.arrayRemove(...sentinel.elements.map(walk));
|
|
41
|
+
}
|
|
42
|
+
return fail(`the sentinel ${sentinel.methodName ?? u.constructor.name}`);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
/** @internal */
|
|
46
|
+
const fromAdmin = (value) => FirestoreValue$1.fromSdk(value, reader);
|
|
47
|
+
/**
|
|
48
|
+
* What the Admin SDK read for the document at `path`, as Firestore Values, or
|
|
49
|
+
* `undefined` for a document that is not there.
|
|
50
|
+
*
|
|
51
|
+
* @internal
|
|
52
|
+
*/
|
|
53
|
+
const readDocument = (path, data) => FirestoreValue$1.readDocument(path, data, reader);
|
|
54
|
+
export { fromAdmin, readDocument, toAdmin };
|
|
55
|
+
|
|
56
|
+
//# sourceMappingURL=firestoreValue.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"firestoreValue.mjs","names":["FirestoreValue"],"sources":["../../../src/firestore/internal/firestoreValue.ts"],"sourcesContent":["import { Predicate, type Result } from \"effect\";\nimport * as DocumentPath from \"@visiolab/effect-firebase/firestore/DocumentPath\";\nimport * as FieldValue from \"@visiolab/effect-firebase/firestore/FieldValue\";\nimport type { FirestoreError } from \"@visiolab/effect-firebase/firestore/Firestore\";\nimport * as FirestoreValue from \"@visiolab/effect-firebase/firestore/FirestoreValue\";\nimport * as GeoPoint from \"@visiolab/effect-firebase/firestore/GeoPoint\";\nimport * as Timestamp from \"@visiolab/effect-firebase/firestore/Timestamp\";\nimport * as Admin from \"firebase-admin/firestore\";\n\n/** How the Admin SDK writes each kind of Firestore Value into `firestore`. */\nconst writer = (firestore: Admin.Firestore): FirestoreValue.Writer => ({\n bytes: (bytes) => bytes,\n timestamp: (ts) => new Admin.Timestamp(ts.seconds, ts.nanoseconds),\n geoPoint: (point) => new Admin.GeoPoint(point.latitude, point.longitude),\n documentPath: (ref) => firestore.doc(ref.path),\n serverTimestamp: () => Admin.FieldValue.serverTimestamp(),\n increment: (increment) => Admin.FieldValue.increment(increment.by),\n deleteField: () => Admin.FieldValue.delete(),\n arrayUnion: (union, walk) => Admin.FieldValue.arrayUnion(...union.elements.map(walk)),\n arrayRemove: (remove, walk) => Admin.FieldValue.arrayRemove(...remove.elements.map(walk)),\n});\n\n/** @internal */\nexport const toAdmin = (\n value: FirestoreValue.FirestoreValue,\n firestore: Admin.Firestore,\n): unknown => FirestoreValue.toSdk(value, writer(firestore));\n\n/** The sentinels' names, as the Admin SDK keeps them. */\ninterface AdminSentinel {\n readonly methodName?: string;\n readonly operand?: number;\n readonly elements?: ReadonlyArray<unknown>;\n}\n\n/** How the Admin SDK's own values read back as Firestore Values. */\nconst reader: FirestoreValue.Reader = (u, { walk, fail }) => {\n if (u instanceof Uint8Array) {\n // The Admin SDK reads bytes as a Node `Buffer`.\n return u.constructor === Uint8Array ? u : new Uint8Array(u.buffer, u.byteOffset, u.length);\n }\n if (u instanceof Admin.Timestamp) return Timestamp.makeUnsafe(u.seconds, u.nanoseconds);\n if (u instanceof Admin.GeoPoint) return GeoPoint.makeUnsafe(u.latitude, u.longitude);\n if (u instanceof Admin.DocumentReference) return DocumentPath.makeUnsafe(u.path);\n if (u instanceof Admin.FieldValue) {\n const sentinel = u as AdminSentinel;\n switch (sentinel.methodName) {\n case \"FieldValue.serverTimestamp\":\n return FieldValue.serverTimestamp;\n case \"FieldValue.delete\":\n return FieldValue.deleteField;\n case \"FieldValue.increment\":\n if (Predicate.isNumber(sentinel.operand)) return FieldValue.increment(sentinel.operand);\n break;\n case \"FieldValue.arrayUnion\":\n if (Array.isArray(sentinel.elements))\n return FieldValue.arrayUnion(...sentinel.elements.map(walk));\n break;\n case \"FieldValue.arrayRemove\":\n if (Array.isArray(sentinel.elements))\n return FieldValue.arrayRemove(...sentinel.elements.map(walk));\n break;\n }\n return fail(`the sentinel ${sentinel.methodName ?? u.constructor.name}`);\n }\n return undefined;\n};\n\n/** @internal */\nexport const fromAdmin = (\n value: unknown,\n): Result.Result<FirestoreValue.FirestoreValue, FirestoreError> =>\n FirestoreValue.fromSdk(value, reader);\n\n/**\n * What the Admin SDK read for the document at `path`, as Firestore Values, or\n * `undefined` for a document that is not there.\n *\n * @internal\n */\nexport const readDocument = (\n path: string,\n data: Admin.DocumentData | undefined,\n): Result.Result<FirestoreValue.FirestoreValue | undefined, FirestoreError> =>\n FirestoreValue.readDocument(path, data, reader);\n"],"mappings":";;;;;;;;;AAUA,MAAM,UAAU,eAAuD;CACrE,QAAQ,UAAU;CAClB,YAAY,OAAO,IAAI,MAAM,UAAU,GAAG,SAAS,GAAG,WAAW;CACjE,WAAW,UAAU,IAAI,MAAM,SAAS,MAAM,UAAU,MAAM,SAAS;CACvE,eAAe,QAAQ,UAAU,IAAI,IAAI,IAAI;CAC7C,uBAAuB,MAAM,WAAW,gBAAgB;CACxD,YAAY,cAAc,MAAM,WAAW,UAAU,UAAU,EAAE;CACjE,mBAAmB,MAAM,WAAW,OAAO;CAC3C,aAAa,OAAO,SAAS,MAAM,WAAW,WAAW,GAAG,MAAM,SAAS,IAAI,IAAI,CAAC;CACpF,cAAc,QAAQ,SAAS,MAAM,WAAW,YAAY,GAAG,OAAO,SAAS,IAAI,IAAI,CAAC;AAC1F;;AAGA,MAAa,WACX,OACA,cACYA,iBAAe,MAAM,OAAO,OAAO,SAAS,CAAC;;AAU3D,MAAM,UAAiC,GAAG,EAAE,MAAM,WAAW;CAC3D,IAAI,aAAa,YAEf,OAAO,EAAE,gBAAgB,aAAa,IAAI,IAAI,WAAW,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM;CAE3F,IAAI,aAAa,MAAM,WAAW,OAAO,UAAU,WAAW,EAAE,SAAS,EAAE,WAAW;CACtF,IAAI,aAAa,MAAM,UAAU,OAAO,SAAS,WAAW,EAAE,UAAU,EAAE,SAAS;CACnF,IAAI,aAAa,MAAM,mBAAmB,OAAO,aAAa,WAAW,EAAE,IAAI;CAC/E,IAAI,aAAa,MAAM,YAAY;EACjC,MAAM,WAAW;EACjB,QAAQ,SAAS,YAAjB;GACE,KAAK,8BACH,OAAO,WAAW;GACpB,KAAK,qBACH,OAAO,WAAW;GACpB,KAAK;IACH,IAAI,UAAU,SAAS,SAAS,OAAO,GAAG,OAAO,WAAW,UAAU,SAAS,OAAO;IACtF;GACF,KAAK;IACH,IAAI,MAAM,QAAQ,SAAS,QAAQ,GACjC,OAAO,WAAW,WAAW,GAAG,SAAS,SAAS,IAAI,IAAI,CAAC;IAC7D;GACF,KAAK,0BACH,IAAI,MAAM,QAAQ,SAAS,QAAQ,GACjC,OAAO,WAAW,YAAY,GAAG,SAAS,SAAS,IAAI,IAAI,CAAC;EAElE;EACA,OAAO,KAAK,gBAAgB,SAAS,cAAc,EAAE,YAAY,MAAM;CACzE;AAEF;;AAGA,MAAa,aACX,UAEAA,iBAAe,QAAQ,OAAO,MAAM;;;;;;;AAQtC,MAAa,gBACX,MACA,SAEAA,iBAAe,aAAa,MAAM,MAAM,MAAM"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,64 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@visiolab/effect-firebase-admin",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Firebase Admin SDK implementation of the @visiolab/effect-firebase services.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"effect",
|
|
7
|
+
"firebase",
|
|
8
|
+
"firebase-admin",
|
|
9
|
+
"typescript"
|
|
10
|
+
],
|
|
11
|
+
"homepage": "https://github.com/VisioLab/effect-firebase",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/VisioLab/effect-firebase/issues"
|
|
14
|
+
},
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/VisioLab/effect-firebase.git",
|
|
19
|
+
"directory": "packages/admin"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"src/**/*.ts",
|
|
23
|
+
"dist/**/*.mjs",
|
|
24
|
+
"dist/**/*.mjs.map",
|
|
25
|
+
"dist/**/*.d.mts",
|
|
26
|
+
"dist/**/*.d.mts.map"
|
|
27
|
+
],
|
|
28
|
+
"type": "module",
|
|
29
|
+
"sideEffects": [],
|
|
30
|
+
"exports": {
|
|
31
|
+
"./app": "./dist/app/index.mjs",
|
|
32
|
+
"./app/App": "./dist/app/App.mjs",
|
|
33
|
+
"./auth": "./dist/auth/index.mjs",
|
|
34
|
+
"./auth/Auth": "./dist/auth/Auth.mjs",
|
|
35
|
+
"./firestore": "./dist/firestore/index.mjs",
|
|
36
|
+
"./firestore/AdminFirestore": "./dist/firestore/AdminFirestore.mjs",
|
|
37
|
+
"./firestore/AdminFirestoreValue": "./dist/firestore/AdminFirestoreValue.mjs",
|
|
38
|
+
"./package.json": "./package.json"
|
|
39
|
+
},
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@effect/vitest": "^4.1.0",
|
|
45
|
+
"effect": "4.1.0",
|
|
46
|
+
"firebase-admin": "^14.5.0",
|
|
47
|
+
"typescript": "^7.0.2",
|
|
48
|
+
"vite": "npm:@voidzero-dev/vite-plus-core@1.4.0",
|
|
49
|
+
"vite-plus": "1.4.0",
|
|
50
|
+
"vitest": "5.0.5",
|
|
51
|
+
"@visiolab/effect-firebase": "^0.3.0"
|
|
52
|
+
},
|
|
53
|
+
"peerDependencies": {
|
|
54
|
+
"effect": "~4.1.0",
|
|
55
|
+
"firebase-admin": "^14.5.0",
|
|
56
|
+
"@visiolab/effect-firebase": "^0.3.0"
|
|
57
|
+
},
|
|
58
|
+
"scripts": {
|
|
59
|
+
"build": "vp pack",
|
|
60
|
+
"dev": "vp pack --watch",
|
|
61
|
+
"test": "vp test",
|
|
62
|
+
"check": "vp check"
|
|
63
|
+
}
|
|
6
64
|
}
|
package/src/app/App.ts
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Firebase Admin SDK app as an Effect service.
|
|
3
|
+
*
|
|
4
|
+
* Every other service in this package, such as {@link Auth.Auth} and
|
|
5
|
+
* {@link AdminFirestore.AdminFirestore}, is built on the {@link App} in
|
|
6
|
+
* context. Provide it with {@link layer}, or hand it an app the host already
|
|
7
|
+
* initialized.
|
|
8
|
+
*
|
|
9
|
+
* @example Initializing the app for a program
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { Effect } from "effect";
|
|
12
|
+
* import { App } from "@visiolab/effect-firebase-admin/app";
|
|
13
|
+
*
|
|
14
|
+
* const program = Effect.gen(function* () {
|
|
15
|
+
* const app = yield* App.App;
|
|
16
|
+
* yield* Effect.log(`Running against ${app.name}`);
|
|
17
|
+
* });
|
|
18
|
+
*
|
|
19
|
+
* program.pipe(Effect.provide(App.layer()));
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* @see [Add the Firebase Admin SDK to your server](https://firebase.google.com/docs/admin/setup)
|
|
23
|
+
*
|
|
24
|
+
* @stability unstable
|
|
25
|
+
* @since 0.1.0
|
|
26
|
+
*/
|
|
27
|
+
import { Context, Effect, Layer, Schema } from "effect";
|
|
28
|
+
import type * as Admin from "firebase-admin/app";
|
|
29
|
+
// Only the module's documentation links to these.
|
|
30
|
+
// oxlint-disable-next-line no-unused-vars
|
|
31
|
+
import type * as Auth from "../auth/Auth.ts";
|
|
32
|
+
// oxlint-disable-next-line no-unused-vars
|
|
33
|
+
import type * as AdminFirestore from "../firestore/AdminFirestore.ts";
|
|
34
|
+
import * as internalApp from "./internal/app.ts";
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* A Firebase Admin SDK app: the SDK's own `App`.
|
|
38
|
+
*
|
|
39
|
+
* @stability unstable
|
|
40
|
+
* @category models
|
|
41
|
+
* @since 0.1.0
|
|
42
|
+
*/
|
|
43
|
+
export interface App extends Admin.App {}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Service key for the Firebase Admin SDK app.
|
|
47
|
+
*
|
|
48
|
+
* **When to use**
|
|
49
|
+
*
|
|
50
|
+
* Use to provide an app the host already initialized, such as one from
|
|
51
|
+
* `getApp()`, with `Layer.succeed`. Nothing deletes an app provided this way.
|
|
52
|
+
*
|
|
53
|
+
* @example Providing an app the host initialized
|
|
54
|
+
* ```ts
|
|
55
|
+
* import { Layer } from "effect";
|
|
56
|
+
* import { getApp } from "firebase-admin/app";
|
|
57
|
+
* import { App } from "@visiolab/effect-firebase-admin/app";
|
|
58
|
+
*
|
|
59
|
+
* const AppLive = Layer.succeed(App.App, getApp());
|
|
60
|
+
* ```
|
|
61
|
+
*
|
|
62
|
+
* @see {@link layer} for initializing the app for the lifetime of a layer
|
|
63
|
+
*
|
|
64
|
+
* @stability unstable
|
|
65
|
+
* @category services
|
|
66
|
+
* @since 0.1.0
|
|
67
|
+
*/
|
|
68
|
+
export const App = Context.Service<App>("@visiolab/effect-firebase-admin/app/App");
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Fails {@link make} and {@link layer} when the Admin SDK refuses to initialize
|
|
72
|
+
* the app, with what it threw as the `cause`.
|
|
73
|
+
*
|
|
74
|
+
* @stability unstable
|
|
75
|
+
* @category errors
|
|
76
|
+
* @since 0.1.0
|
|
77
|
+
*/
|
|
78
|
+
export class AppError extends Schema.TaggedError<AppError, { readonly brand: unique symbol }>()(
|
|
79
|
+
"AppError",
|
|
80
|
+
{
|
|
81
|
+
message: Schema.String,
|
|
82
|
+
cause: Schema.optional(Schema.Defect()),
|
|
83
|
+
},
|
|
84
|
+
) {}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Initializes a Firebase Admin SDK app, and deletes it again when the
|
|
88
|
+
* surrounding scope closes.
|
|
89
|
+
*
|
|
90
|
+
* **When to use**
|
|
91
|
+
*
|
|
92
|
+
* Use to build the app inside an effect of your own. Most programs provide it
|
|
93
|
+
* with {@link layer} instead.
|
|
94
|
+
*
|
|
95
|
+
* **Details**
|
|
96
|
+
*
|
|
97
|
+
* Takes the arguments of `initializeApp(options, name)`. With no options, the
|
|
98
|
+
* Admin SDK initializes the app from the `FIREBASE_CONFIG` environment variable
|
|
99
|
+
* and application default credentials.
|
|
100
|
+
*
|
|
101
|
+
* An app already registered under the same name is not deleted when the scope
|
|
102
|
+
* closes. The Admin SDK only hands back that app when the request matches how
|
|
103
|
+
* it was created: it rejects auto-init against explicit options, and rejects
|
|
104
|
+
* reuse outright when either side carries a credential or an `httpAgent`. To
|
|
105
|
+
* pick up an app the host initialized, provide `getApp()` as {@link App}.
|
|
106
|
+
*
|
|
107
|
+
* Fails with an {@link AppError} when the Admin SDK refuses to initialize the
|
|
108
|
+
* app. A failure to delete the app is logged as a warning, not raised.
|
|
109
|
+
*
|
|
110
|
+
* @see {@link layer} for the same as a layer
|
|
111
|
+
*
|
|
112
|
+
* @stability unstable
|
|
113
|
+
* @category constructors
|
|
114
|
+
* @since 0.1.0
|
|
115
|
+
*/
|
|
116
|
+
export const make = Effect.fn("App.make")(function* (options?: Admin.AppOptions, name?: string) {
|
|
117
|
+
const { app } = yield* Effect.acquireRelease(
|
|
118
|
+
Effect.try({
|
|
119
|
+
try: () => internalApp.initialize(options, name),
|
|
120
|
+
catch: (cause) =>
|
|
121
|
+
new AppError({ message: "Failed to initialize the Firebase Admin SDK app", cause }),
|
|
122
|
+
}),
|
|
123
|
+
internalApp.release,
|
|
124
|
+
);
|
|
125
|
+
return app;
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* A layer that initializes a Firebase Admin SDK app for the lifetime of the
|
|
130
|
+
* layer.
|
|
131
|
+
*
|
|
132
|
+
* **When to use**
|
|
133
|
+
*
|
|
134
|
+
* Use when this program owns the app. Where the host has already initialized
|
|
135
|
+
* one, provide that app as {@link App} instead.
|
|
136
|
+
*
|
|
137
|
+
* **Details**
|
|
138
|
+
*
|
|
139
|
+
* Takes the arguments of `initializeApp(options, name)`. With no options, the
|
|
140
|
+
* Admin SDK initializes the app from the `FIREBASE_CONFIG` environment variable
|
|
141
|
+
* and application default credentials.
|
|
142
|
+
*
|
|
143
|
+
* @example Initializing a named app with explicit options
|
|
144
|
+
* ```ts
|
|
145
|
+
* import { App } from "@visiolab/effect-firebase-admin/app";
|
|
146
|
+
*
|
|
147
|
+
* const AppLive = App.layer({ projectId: "my-project" }, "secondary");
|
|
148
|
+
* ```
|
|
149
|
+
*
|
|
150
|
+
* @see {@link make} for how the app is initialized and torn down
|
|
151
|
+
* @see {@link App} for providing an app the host initialized
|
|
152
|
+
*
|
|
153
|
+
* @stability unstable
|
|
154
|
+
* @category layers
|
|
155
|
+
* @since 0.1.0
|
|
156
|
+
*/
|
|
157
|
+
export const layer = (options?: Admin.AppOptions, name?: string): Layer.Layer<App, AppError> =>
|
|
158
|
+
Layer.effect(App, make(options, name));
|
package/src/app/index.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import { defaultAppName } from "@visiolab/effect-firebase/app/App";
|
|
3
|
+
import * as Admin from "firebase-admin/app";
|
|
4
|
+
import type * as App from "../App.ts";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The app together with whether we are the ones who initialized it.
|
|
8
|
+
*
|
|
9
|
+
* @internal
|
|
10
|
+
*/
|
|
11
|
+
export interface Initialized {
|
|
12
|
+
readonly app: App.App;
|
|
13
|
+
readonly owned: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** @internal */
|
|
17
|
+
export const initialize = (options?: Admin.AppOptions, name?: string): Initialized => {
|
|
18
|
+
// An app that is already registered under this name was initialized by
|
|
19
|
+
// somebody else - typically the hosting runtime - so ending its lifecycle is
|
|
20
|
+
// not ours to do. `initializeApp` still throws when that app was configured
|
|
21
|
+
// differently, so conflicting configuration is not swallowed here.
|
|
22
|
+
const owned = !Admin.getApps().some((app) => app.name === (name ?? defaultAppName));
|
|
23
|
+
return { app: Admin.initializeApp(options, name), owned };
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/** @internal */
|
|
27
|
+
export const release = ({ app, owned }: Initialized): Effect.Effect<void> =>
|
|
28
|
+
owned
|
|
29
|
+
? Effect.tryPromise(() => Admin.deleteApp(app)).pipe(
|
|
30
|
+
Effect.ignore({
|
|
31
|
+
log: "Warn",
|
|
32
|
+
message: `Failed to delete the Firebase Admin SDK app "${app.name}"`,
|
|
33
|
+
}),
|
|
34
|
+
)
|
|
35
|
+
: Effect.void;
|