@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.
Files changed (38) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +73 -2
  3. package/dist/_virtual/_rolldown/runtime.mjs +12 -0
  4. package/dist/app/App.d.mts +115 -0
  5. package/dist/app/App.mjs +145 -0
  6. package/dist/app/App.mjs.map +1 -0
  7. package/dist/app/index.d.mts +2 -0
  8. package/dist/app/index.mjs +1 -0
  9. package/dist/app/internal/app.mjs +20 -0
  10. package/dist/app/internal/app.mjs.map +1 -0
  11. package/dist/auth/Auth.d.mts +167 -0
  12. package/dist/auth/Auth.mjs +131 -0
  13. package/dist/auth/Auth.mjs.map +1 -0
  14. package/dist/auth/index.d.mts +2 -0
  15. package/dist/auth/index.mjs +1 -0
  16. package/dist/firestore/AdminFirestore.d.mts +241 -0
  17. package/dist/firestore/AdminFirestore.mjs +133 -0
  18. package/dist/firestore/AdminFirestore.mjs.map +1 -0
  19. package/dist/firestore/AdminFirestoreValue.d.mts +61 -0
  20. package/dist/firestore/AdminFirestoreValue.mjs +62 -0
  21. package/dist/firestore/AdminFirestoreValue.mjs.map +1 -0
  22. package/dist/firestore/index.d.mts +3 -0
  23. package/dist/firestore/index.mjs +3 -0
  24. package/dist/firestore/internal/firestore.mjs +135 -0
  25. package/dist/firestore/internal/firestore.mjs.map +1 -0
  26. package/dist/firestore/internal/firestoreValue.mjs +56 -0
  27. package/dist/firestore/internal/firestoreValue.mjs.map +1 -0
  28. package/package.json +61 -3
  29. package/src/app/App.ts +158 -0
  30. package/src/app/index.ts +7 -0
  31. package/src/app/internal/app.ts +35 -0
  32. package/src/auth/Auth.ts +258 -0
  33. package/src/auth/index.ts +7 -0
  34. package/src/firestore/AdminFirestore.ts +303 -0
  35. package/src/firestore/AdminFirestoreValue.ts +93 -0
  36. package/src/firestore/index.ts +14 -0
  37. package/src/firestore/internal/firestore.ts +238 -0
  38. 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,3 @@
1
+ import { AdminFirestore_d_exports } from "./AdminFirestore.mjs";
2
+ import { AdminFirestoreValue_d_exports } from "./AdminFirestoreValue.mjs";
3
+ export { AdminFirestore_d_exports as AdminFirestore, AdminFirestoreValue_d_exports as AdminFirestoreValue };
@@ -0,0 +1,3 @@
1
+ import { AdminFirestore_exports } from "./AdminFirestore.mjs";
2
+ import { AdminFirestoreValue_exports } from "./AdminFirestoreValue.mjs";
3
+ export { AdminFirestore_exports as AdminFirestore, AdminFirestoreValue_exports as AdminFirestoreValue };
@@ -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.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
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));
@@ -0,0 +1,7 @@
1
+ /**
2
+ * The Firebase Admin SDK app as an Effect service.
3
+ *
4
+ * @stability unstable
5
+ * @since 0.1.0
6
+ */
7
+ export * as App from "./App.ts";
@@ -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;