@visiolab/effect-firebase-functions 0.0.0-stage → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 VisioLab GmbH
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,229 @@
1
- # Temporary Holding Version
1
+ # @visiolab/effect-firebase-functions
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Deploys the Functions of [`@visiolab/effect-firebase`](https://github.com/VisioLab/effect-firebase/tree/main/packages/effect-firebase) as Cloud Functions for Firebase, with `firebase-functions`. A Function is a server function declared once by its name and what runs it. Callables are the kind of Function the client calls, with `CallableClient` from [`@visiolab/effect-firebase-client`](https://github.com/VisioLab/effect-firebase/tree/main/packages/client).
4
+
5
+ ```sh
6
+ pnpm add effect @visiolab/effect-firebase @visiolab/effect-firebase-admin @visiolab/effect-firebase-functions firebase-admin firebase-functions
7
+ ```
8
+
9
+ ## Callable Functions
10
+
11
+ Declare a Callable once, in code the server and the client share:
12
+
13
+ ```ts
14
+ import { Schema } from "effect";
15
+ import { Callable } from "@visiolab/effect-firebase/functions";
16
+
17
+ export const Greet = Callable.make("greet", {
18
+ payload: { name: Schema.String },
19
+ success: Schema.String,
20
+ });
21
+ ```
22
+
23
+ Implement it as a Layer, and export it with `Functions.make`:
24
+
25
+ ```ts
26
+ import { Effect } from "effect";
27
+ import { Callable } from "@visiolab/effect-firebase/functions";
28
+ import { Functions } from "@visiolab/effect-firebase-functions";
29
+ import { Greet } from "./contract.ts";
30
+
31
+ const GreetLive = Callable.toLayer(Greet, ({ name }) => Effect.succeed(`Hello, ${name}!`));
32
+
33
+ export const { greet } = Functions.make(Greet, GreetLive, { region: "europe-west1" });
34
+ ```
35
+
36
+ `Functions.make` dispatches on the Function's kind. For a Callable, the export is exactly what `onCall` returns, so plain `firebase deploy` deploys it, and the third argument is Firebase's `CallableOptions`, passed through unchanged.
37
+
38
+ - The Layer is built on the first call, not at deploy or when the module loads. Every Layer is built through one memo map per process, so concurrent first calls share one build.
39
+ - `minInstances` keeps an instance alive, but its Layer is still built on the first call.
40
+ - Finalizers of the Layer don't run when the instance shuts down.
41
+ - Give each export a Layer of its own where you can, so a cold instance builds only what its function needs.
42
+ - Call `setGlobalOptions` before any function is defined, as Firebase requires.
43
+
44
+ ## Document Triggers
45
+
46
+ A Document Trigger runs when a document of a collection changes. Declare it from a name and a `Collection` definition, implement it with `DocumentTrigger.toLayer`, and deploy it with `Functions.make`:
47
+
48
+ ```ts
49
+ import { Effect } from "effect";
50
+ import { DocumentTrigger } from "@visiolab/effect-firebase/functions";
51
+ import { Functions } from "@visiolab/effect-firebase-functions";
52
+ import { Posts } from "./collections.ts";
53
+
54
+ const OnPostCreated = DocumentTrigger.created("onPostCreated", Posts);
55
+
56
+ export const { onPostCreated } = Functions.make(
57
+ OnPostCreated,
58
+ DocumentTrigger.toLayer(OnPostCreated, (event) =>
59
+ Effect.gen(function* () {
60
+ const [authorId] = event.ref.ancestors;
61
+ const post = yield* event.snapshot.data;
62
+ yield* Effect.log(`${authorId} published ${post.title}`);
63
+ }),
64
+ ),
65
+ { retry: true },
66
+ );
67
+ ```
68
+
69
+ - There is one constructor per change: `created` and `deleted` hand over `event.snapshot` (on `deleted`, the document as it was just before), `updated` hands over `event.before` and `event.after`, both existing, and `written` runs on all three, with `before` missing on a create and `after` missing on a delete. Narrow a `written` side with `DocumentSnapshot.isExisting` or `isMissing`.
70
+ - The trigger listens to the `(default)` database, at a path pattern derived from the collection: one wildcard per parent document and one for the document. The ids are on `event.ref`, typed.
71
+ - `event.snapshot.data` decodes through the collection's model when it runs; `event.snapshot.raw` holds the stored Firestore Values. The same holds for each existing side of a change.
72
+ - The options are the shared `firebase-functions` options plus `retry`.
73
+ - Any failure fails the event: a typed error, a defect, an interrupt, or a Layer that fails to build. Its Cause is logged at Error in the event's span, and the function throws. Firebase then delivers the event again when `retry` is set, and drops it otherwise. The library adds no retry of its own. Events are delivered at least once: key idempotent work on `event.id`.
74
+ - A Layer that fails to build is built again on the next event.
75
+ - The Functions emulator never delivers a failed event again, even with `retry`, and replaces the worker whose function threw.
76
+ - Add a trigger to a Function Group next to Callables to deploy it as `<group>-<name>` (see [Function Groups](#function-groups)).
77
+ - Test a handler in process with `DocumentTrigger.serve`, and an event built with `DocumentTrigger.createdEvent`, `updatedEvent`, `deletedEvent` or `writtenEvent` from `DocumentSnapshot.existing` and `DocumentSnapshot.missing`.
78
+ - The package takes `@visiolab/effect-firebase-admin` and `firebase-admin` as peer dependencies, to read event data as the Admin SDK reads do.
79
+
80
+ ## Function Groups
81
+
82
+ Collect related Functions, of any kind, into a Function Group. Each member deploys under the group's name as a prefix:
83
+
84
+ ```ts
85
+ import { Effect, Layer, Schema } from "effect";
86
+ import { Callable, FunctionGroup } from "@visiolab/effect-firebase/functions";
87
+ import { Functions } from "@visiolab/effect-firebase-functions";
88
+
89
+ const CreateUser = Callable.make("create", {
90
+ payload: { name: Schema.String },
91
+ success: Schema.String,
92
+ });
93
+ const DeleteUser = Callable.make("delete", { payload: { id: Schema.String } });
94
+
95
+ class Users extends FunctionGroup.make("users").add(CreateUser, DeleteUser) {}
96
+
97
+ const UsersLive = Layer.mergeAll(
98
+ Callable.toLayer(Users, "create", ({ name }) => Effect.succeed(name)),
99
+ Callable.toLayer(Users, "delete", () => Effect.void),
100
+ );
101
+
102
+ // Deploys `users-create` and `users-delete`.
103
+ export const { users } = Functions.group(Users, UsersLive, {
104
+ delete: { region: "europe-west1" },
105
+ });
106
+ ```
107
+
108
+ A group can hold Callables and Document Triggers together, so you can group by domain rather than by kind:
109
+
110
+ ```ts
111
+ import { Effect, Layer, Schema } from "effect";
112
+ import { Collection, FirestoreModel } from "@visiolab/effect-firebase/firestore";
113
+ import { Callable, DocumentTrigger, FunctionGroup } from "@visiolab/effect-firebase/functions";
114
+ import { Functions } from "@visiolab/effect-firebase-functions";
115
+
116
+ class Post extends FirestoreModel.Class<Post>("Post")({ title: Schema.String }) {}
117
+
118
+ const PostsCollection = Collection.make("posts", Post);
119
+
120
+ const Publish = Callable.make("publish", { payload: { id: Schema.String } });
121
+ const OnCreated = DocumentTrigger.created("onCreated", PostsCollection);
122
+
123
+ class Posts extends FunctionGroup.make("posts").add(Publish, OnCreated) {}
124
+
125
+ // Deploys `posts-publish` and `posts-onCreated`.
126
+ export const { posts } = Functions.group(
127
+ Posts,
128
+ Layer.mergeAll(
129
+ Callable.toLayer(Posts, "publish", () => Effect.void),
130
+ DocumentTrigger.toLayer(Posts, "onCreated", (event) => Effect.log(event.ref.id)),
131
+ ),
132
+ { publish: { cors: true }, onCreated: { retry: true } },
133
+ );
134
+ ```
135
+
136
+ - Implement a member with `Callable.toLayer(Users, "create", handler)` or `DocumentTrigger.toLayer(Posts, "onCreated", handler)`. A Layer from the Function on its own doesn't serve it in the group.
137
+ - The options are a sparse map keyed by member name, each typed by the member's kind: `CallableOptions` for a Callable, and the shared options plus `retry` for a Document Trigger. The group has none of its own.
138
+ - A Function can be in more than one group. Each group serves and implements it separately.
139
+ - Names must be unique within a group. `.add` throws when a name is taken, so the clash fails when the module loads. The types don't catch it.
140
+ - Build the client from the group, `CallableClient.make(Users)`. It has a function for each of the group's Callables, and none for its Document Triggers. A client built from the member on its own calls the unprefixed name and gets `not-found`.
141
+
142
+ ## Middleware and the Caller
143
+
144
+ Requirements such as "the caller is signed in" are Callable Middleware on the contract. The built-in `Caller.Authenticated` provides the `Caller` and fails a signed-out call with `CallableError.Unauthenticated`, before the Payload is decoded:
145
+
146
+ ```ts
147
+ import { Effect, Layer, Schema } from "effect";
148
+ import { Callable, Caller } from "@visiolab/effect-firebase/functions";
149
+ import { Functions } from "@visiolab/effect-firebase-functions";
150
+
151
+ const WhoAmI = Callable.make("whoAmI", { success: Schema.String }).middleware(Caller.Authenticated);
152
+
153
+ const WhoAmILive = Callable.toLayer(WhoAmI, () =>
154
+ Effect.map(Effect.service(Caller.Caller), ({ uid }) => uid),
155
+ );
156
+
157
+ // `Live` provides the handler and every middleware implementation.
158
+ export const { whoAmI } = Functions.make(
159
+ WhoAmI,
160
+ Layer.merge(WhoAmILive, Caller.Authenticated.layer),
161
+ );
162
+ ```
163
+
164
+ - Declare a middleware of your own with `CallableMiddleware.Service`. Its implementation wraps the call and reads the portable `CallableRequest`: `auth`, `app`, the raw `data`, and `rawRequest`, the HTTP request as Effect's `HttpServerRequest`, with the caller's IP address as `remoteAddress`. Every call provides it, so the Layer you serve needn't, even when a handler or middleware reads it. Its body streams Firebase's buffered bytes without copying them.
165
+ - The Firebase web SDK's `httpsCallable` can't send custom headers. A header of your own reaches `rawRequest` only from callers that don't use it, such as another server. Headers like `user-agent` and `accept-language` are there for every caller.
166
+ - A middleware attached later wraps the ones attached before it.
167
+ - The Payload is decoded inside the chain, so a middleware that catches every failure also catches a Payload that doesn't decode. The caller then no longer gets `invalid-argument`. Catch only the errors you mean to.
168
+ - `Functions` provides no middleware itself, so a `Live` without an implementation is a type error.
169
+ - Run a handler in process, with its chain and without the emulator, through `Callable.handler(WhoAmI)`. Pass a `rawRequest` built with `HttpServerRequest.fromWeb` to test a middleware that reads headers or the address; left out, it is a JSON `POST` of the Payload to `http://localhost/<deployed name>`.
170
+
171
+ ### Middleware in a group
172
+
173
+ A Function Group carries no middleware. Attach a middleware to each member that needs it:
174
+
175
+ ```ts
176
+ import { Effect, Layer, Schema } from "effect";
177
+ import { Callable, FunctionGroup, Caller } from "@visiolab/effect-firebase/functions";
178
+ import { Functions } from "@visiolab/effect-firebase-functions";
179
+
180
+ const Me = Callable.make("me", { success: Schema.String }).middleware(Caller.Authenticated);
181
+
182
+ class Accounts extends FunctionGroup.make("accounts").add(Me) {}
183
+
184
+ export const { accounts } = Functions.group(
185
+ Accounts,
186
+ Layer.merge(
187
+ Callable.toLayer(Accounts, "me", () =>
188
+ Effect.map(Effect.service(Caller.Caller), ({ uid }) => uid),
189
+ ),
190
+ Caller.Authenticated.layer,
191
+ ),
192
+ );
193
+ ```
194
+
195
+ ## Tracing
196
+
197
+ Every call runs in a `kind: "server"` span named `CallableFunction.<deployed name>`, such as `CallableFunction.users-create`, with the attributes `rpc.system: "firebase_callable"`, `rpc.method`, `rpc.service` (the group, for a member) and `faas.name`. Spans are exported only when the Layer you export provides a `Tracer`, for example from `@effect/opentelemetry`:
198
+
199
+ ```ts
200
+ export const { users } = Functions.group(Users, Layer.mergeAll(UsersLive, TracingLive));
201
+ ```
202
+
203
+ - The span covers the middleware, decoding the Payload, the handler and encoding the answer. On the call that builds the Layer, it covers the build too, and spans opened during the build are its children.
204
+ - Its parent is the trace context Firebase reads from the request (`X-Cloud-Trace-Context`, else `traceparent`), so it joins the platform's trace. In the emulator it is a root span.
205
+ - `CallableClient` opens a `kind: "client"` span per call. It isn't linked to the server span: the two land in separate traces.
206
+ - Neither span carries a uid. Add one where you want it with `Effect.annotateCurrentSpan("enduser.id", uid)`.
207
+ - Every event of a Document Trigger runs in a `kind: "server"` span named `DocumentTrigger.<deployed name>`, such as `DocumentTrigger.onPostCreated`, with the attributes `faas.name` and `faas.trigger: "datasource"`. It covers converting the event, the handler and, on the event that builds the Layer, the build. Its parent is the trace context Firebase reads from the event's `traceparent`.
208
+
209
+ ## Logging
210
+
211
+ Every call and every Document Trigger event logs through `CloudLogging.layer`, which writes each Effect log as one Cloud Logging JSON line, in the format of Firebase's own `logger`:
212
+
213
+ - The trace, span id and sampled flag come from the current span, so concurrent calls and events on one instance each log under their own trace.
214
+ - Trace and Debug are `DEBUG`, Info `INFO`, Warn `WARNING`, Error `ERROR` and Fatal `CRITICAL`. Error and Fatal go to `console.error`, the rest to `console.log`.
215
+ - Log annotations become fields of the entry, other logged values go under `args` and log spans under `spans`.
216
+ - A Cause is appended to `message` with its stacks, so defects and failed Layer builds reach Error Reporting.
217
+ - Entries carry no uid. Add one where you want it with `Effect.annotateLogs({ uid })`.
218
+ - The project comes from `GCLOUD_PROJECT`, else `GOOGLE_CLOUD_PROJECT`. With neither, entries carry no trace fields.
219
+ - Logs written while the first call builds the Layer are held until the build is done, so they get the spans' final ids. Entries carry no timestamp of their own, so Cloud Logging stamps them with the time the build finished.
220
+ - Provide a `Logger.layer` in the Layer you export to log differently.
221
+
222
+ To log the same way outside a deployed Function, provide the layer yourself:
223
+
224
+ ```ts
225
+ import { Effect } from "effect";
226
+ import { CloudLogging } from "@visiolab/effect-firebase-functions/logger";
227
+
228
+ const job = Effect.log("Cleaning up").pipe(Effect.provide(CloudLogging.layer));
229
+ ```
@@ -0,0 +1,339 @@
1
+ import { Layer } from "effect";
2
+ import * as Callable from "@visiolab/effect-firebase/functions/Callable";
3
+ import * as Https from "firebase-functions/v2/https";
4
+ import { CloudFunction } from "firebase-functions/v2";
5
+ import * as DocumentTrigger from "@visiolab/effect-firebase/functions/DocumentTrigger";
6
+ import * as Firestore from "firebase-functions/v2/firestore";
7
+ import * as FirebaseFunction from "@visiolab/effect-firebase/functions/FirebaseFunction";
8
+ import * as FunctionGroup from "@visiolab/effect-firebase/functions/FunctionGroup";
9
+ declare namespace Functions_d_exports {
10
+ export { CallableExport, DocumentTriggerExport, DocumentTriggerOptions, Export, Exports, GroupExports, GroupOptions, GroupServices, Options, Services, group, make };
11
+ }
12
+ /**
13
+ * The value `onCall` returns for a Callable: what firebase-tools discovers and
14
+ * deploys.
15
+ *
16
+ * @see {@link Export} for the export of a Function of any kind
17
+ *
18
+ * @stability unstable
19
+ * @category models
20
+ * @since 0.3.0
21
+ */
22
+ export type CallableExport = Https.CallableFunction<unknown, Promise<unknown>>;
23
+ /**
24
+ * The value an `onDocument*` provider returns for a Document Trigger, by the
25
+ * change it runs on: what firebase-tools discovers and deploys.
26
+ *
27
+ * **Details**
28
+ *
29
+ * A `created` trigger is exported with `onDocumentCreated`, a `deleted` one
30
+ * with `onDocumentDeleted`, an `updated` one with `onDocumentUpdated` and a
31
+ * `written` one with `onDocumentWritten`. The type is the `CloudFunction` of
32
+ * the event that provider hands over.
33
+ *
34
+ * @see {@link Export} for the export of a Function of any kind
35
+ *
36
+ * @stability unstable
37
+ * @category type-level
38
+ * @since 0.3.0
39
+ */
40
+ export type DocumentTriggerExport<T extends DocumentTrigger.Any> = CloudFunction<Firestore.FirestoreEvent<DocumentTriggerEventData[T["event"]]>>;
41
+ /**
42
+ * The data of the event each `onDocument*` provider hands over, by the change
43
+ * it runs on.
44
+ */
45
+ interface DocumentTriggerEventData {
46
+ readonly created: Firestore.QueryDocumentSnapshot | undefined;
47
+ readonly deleted: Firestore.QueryDocumentSnapshot | undefined;
48
+ readonly updated: Firestore.Change<Firestore.QueryDocumentSnapshot> | undefined;
49
+ readonly written: Firestore.Change<Firestore.DocumentSnapshot> | undefined;
50
+ }
51
+ /**
52
+ * The deploy options of a Document Trigger: Firebase's `DocumentOptions`
53
+ * without what the library derives from the trigger. That leaves the options
54
+ * every Function shares, such as `region`, `memory` and `timeoutSeconds`,
55
+ * plus `retry`.
56
+ *
57
+ * **Details**
58
+ *
59
+ * The `document` path pattern comes from the trigger's collection, and the
60
+ * database is always `(default)`, the one `Firestore` reads and writes, so
61
+ * `document`, `database` and `namespace` are left out, as are the event
62
+ * filter options.
63
+ *
64
+ * With `retry: true`, Firebase delivers a failed event again until it
65
+ * succeeds or its retry window ends. Without it, a failed event is dropped.
66
+ * The library adds no retry of its own.
67
+ *
68
+ * @stability unstable
69
+ * @category models
70
+ * @since 0.3.0
71
+ */
72
+ export type DocumentTriggerOptions = Omit<Firestore.DocumentOptions, "document" | "database" | "namespace" | "eventType" | "eventFilters" | "eventFilterPathPatterns" | "channel">;
73
+ /**
74
+ * The export of a Function, by its kind: a {@link CallableExport} for a
75
+ * Callable, a {@link DocumentTriggerExport} for a Document Trigger.
76
+ *
77
+ * @stability unstable
78
+ * @category type-level
79
+ * @since 0.3.0
80
+ */
81
+ export type Export<F extends FirebaseFunction.Any> = F extends Callable.Any ? CallableExport : F extends DocumentTrigger.Any ? DocumentTriggerExport<F> : never;
82
+ /**
83
+ * The deploy options of a Function, by its kind: Firebase's `CallableOptions`
84
+ * for a Callable, passed to `onCall` unchanged, and
85
+ * {@link DocumentTriggerOptions} for a Document Trigger.
86
+ *
87
+ * @stability unstable
88
+ * @category type-level
89
+ * @since 0.3.0
90
+ */
91
+ export type Options<F extends FirebaseFunction.Any> = F extends Callable.Any ? Https.CallableOptions : F extends DocumentTrigger.Any ? DocumentTriggerOptions : never;
92
+ /**
93
+ * What serving a Function under the handler key `H` requires, by its kind.
94
+ * For a Callable: its handler, the implementation of each of its middleware,
95
+ * and what its middleware chain still requires. For a Document Trigger: its
96
+ * handler.
97
+ *
98
+ * @stability unstable
99
+ * @category type-level
100
+ * @since 0.3.0
101
+ */
102
+ export type Services<F extends FirebaseFunction.Any, H> = F extends Callable.Any ? Callable.ChainServices<F, H> : F extends DocumentTrigger.Any ? H : never;
103
+ /**
104
+ * What serving every member of a {@link FunctionGroup.FunctionGroup}
105
+ * requires: what each member requires, by its kind, under its handler key in
106
+ * the group.
107
+ *
108
+ * @stability unstable
109
+ * @category type-level
110
+ * @since 0.3.0
111
+ */
112
+ export type GroupServices<G extends FunctionGroup.Any> = FunctionGroup.Names<G> extends (infer Name) ? Name extends string ? Services<FunctionGroup.Member<G, Name>, FirebaseFunction.Handler<G["identifier"], Name>> : never : never;
113
+ /**
114
+ * The exports of {@link make}: one export under the Function's name.
115
+ *
116
+ * @stability unstable
117
+ * @category models
118
+ * @since 0.3.0
119
+ */
120
+ export type Exports<F extends FirebaseFunction.Any> = { readonly [K in F["name"]]: Export<F>; };
121
+ /**
122
+ * The exports of {@link group}: one export per member, keyed by the member's
123
+ * name, under the group's name.
124
+ *
125
+ * @stability unstable
126
+ * @category models
127
+ * @since 0.3.0
128
+ */
129
+ export type GroupExports<G extends FunctionGroup.Any> = { readonly [K in G["identifier"]]: { readonly [N in FunctionGroup.Names<G>]: Export<FunctionGroup.Member<G, N>>; }; };
130
+ /**
131
+ * The options of {@link group}: each member's {@link Options}, typed by its
132
+ * kind and keyed by its name. Members left out get none.
133
+ *
134
+ * @stability unstable
135
+ * @category models
136
+ * @since 0.3.0
137
+ */
138
+ export type GroupOptions<G extends FunctionGroup.Any> = { readonly [N in FunctionGroup.Names<G>]?: Options<FunctionGroup.Member<G, N>>; };
139
+ /**
140
+ * Deploys a Function in no group, whatever its kind.
141
+ *
142
+ * **When to use**
143
+ *
144
+ * Use to export a Function in no group. Destructure the result into the
145
+ * module's exports, so the export's name is the Function's.
146
+ *
147
+ * **Details**
148
+ *
149
+ * `live` must provide what the Function's kind needs and leave nothing
150
+ * required. It may provide more. Give each export a Layer of its own where you
151
+ * can: a cold instance then builds only what its own function needs.
152
+ *
153
+ * `options` are typed by the Function's kind (see {@link Options}).
154
+ *
155
+ * For a Callable, `live` provides its handler, from `Callable.toLayer`, and
156
+ * the implementation of each of its middleware, such as
157
+ * `Caller.Authenticated.layer`. `Functions` provides no middleware itself.
158
+ * `options` is Firebase's `CallableOptions`, and the export is exactly what
159
+ * `onCall` returns, so its `__endpoint` and `run` work as for any `onCall`
160
+ * function.
161
+ *
162
+ * For a Document Trigger, `live` provides its handler, from
163
+ * `DocumentTrigger.toLayer`. `options` are {@link DocumentTriggerOptions}:
164
+ * the shared options plus `retry`. The export is what the provider of the
165
+ * trigger's change (`onDocumentCreated`, `onDocumentUpdated`,
166
+ * `onDocumentDeleted` or `onDocumentWritten`) returns for the `(default)`
167
+ * database and the path pattern of the trigger's collection. Each event is
168
+ * converted into the trigger's event, its data read into Firestore Values as
169
+ * a read does, a side that doesn't exist as a `Missing` snapshot, and run
170
+ * through `DocumentTrigger.serve`, in a `kind: "server"` span named
171
+ * `DocumentTrigger.<deployed name>` with `faas.name` and
172
+ * `faas.trigger: "datasource"`, logging through `CloudLogging.layer`. Any
173
+ * failure, whether a typed error, a defect, an interrupt or a Layer that
174
+ * fails to build, is logged at Error in that span and makes the function
175
+ * throw, so Firebase delivers the event again when `retry` is set. A Layer
176
+ * that failed to build is built again on the next event.
177
+ *
178
+ * Each call runs the Callable's middleware chain, the one `Callable.handler`
179
+ * runs, with Firebase's request provided as the portable `CallableRequest`.
180
+ * Its `rawRequest` is Firebase's Express request as Effect's
181
+ * `HttpServerRequest`, with the caller's IP address as `remoteAddress`. Its
182
+ * body streams Firebase's buffered bytes without copying them. The Payload is
183
+ * decoded inside the chain, after the middleware.
184
+ *
185
+ * A failed call answers with an `HttpsError`:
186
+ *
187
+ * - A declared error, or an error of a middleware, is sent with its schema's
188
+ * code, the error's `message` (else its `_tag`) and the error encoded with
189
+ * `Schema.toCodecJson` as `details`, so the client decodes it back into its
190
+ * class. A middleware that rejects the call answers before the Payload is
191
+ * decoded.
192
+ * - A Payload that doesn't decode is answered with `invalid-argument`, a
193
+ * generic message and no details. It is logged at Debug.
194
+ * - A defect, or a success or error value that doesn't encode, is answered
195
+ * with a bare `internal`. Its Cause is logged at Error, with the logger of
196
+ * `live` if it provides one.
197
+ * - A Layer that fails to build answers the call with a bare `internal` and
198
+ * is logged at Error. The next call builds it again.
199
+ * - An interrupt of the handler is answered with `unavailable`. When the
200
+ * client disconnects, the handler is interrupted and the call is answered
201
+ * with `cancelled`, as far as the platform reports the disconnect.
202
+ *
203
+ * @example Exporting a Callable
204
+ * ```ts
205
+ * import { Effect } from "effect";
206
+ * import { Callable } from "@visiolab/effect-firebase/functions";
207
+ * import { Functions } from "@visiolab/effect-firebase-functions";
208
+ *
209
+ * const Ping = Callable.make("ping");
210
+ *
211
+ * export const { ping } = Functions.make(Ping, Callable.toLayer(Ping, () => Effect.void));
212
+ * ```
213
+ *
214
+ * @example Exporting a Document Trigger
215
+ * ```ts
216
+ * import { Effect, Schema } from "effect";
217
+ * import { Collection, FirestoreModel } from "@visiolab/effect-firebase/firestore";
218
+ * import { DocumentTrigger } from "@visiolab/effect-firebase/functions";
219
+ * import { Functions } from "@visiolab/effect-firebase-functions";
220
+ *
221
+ * class Post extends FirestoreModel.Class<Post>("Post")({ title: Schema.String }) {}
222
+ *
223
+ * const OnPostCreated = DocumentTrigger.created("onPostCreated", Collection.make("posts", Post));
224
+ *
225
+ * export const { onPostCreated } = Functions.make(
226
+ * OnPostCreated,
227
+ * DocumentTrigger.toLayer(OnPostCreated, (event) =>
228
+ * Effect.flatMap(event.snapshot.data, (post) => Effect.log(`New post: ${post.title}`)),
229
+ * ),
230
+ * { retry: true },
231
+ * );
232
+ * ```
233
+ *
234
+ * @see {@link group} for deploying the members of a Function Group
235
+ * @see {@link Callable.serve} for serving a Callable from a transport of your own
236
+ * @see {@link Callable.handler} for running a Callable's handler in process
237
+ * @see {@link DocumentTrigger.serve} for running a Document Trigger's event in process
238
+ * @see {@link CallableClient.make} for calling a Callable from the client
239
+ *
240
+ * @stability unstable
241
+ * @category constructors
242
+ * @since 0.3.0
243
+ */
244
+ export declare const make: <F extends FirebaseFunction.Any, E>(fn: F, live: Layer.Layer<Services<F, FirebaseFunction.HandlerFor<F>>, E>, options?: Options<F>) => Exports<F>;
245
+ /**
246
+ * Deploys the members of a Function Group, each as a function of its own,
247
+ * whatever their kinds.
248
+ *
249
+ * **When to use**
250
+ *
251
+ * Use to export a Function Group. Destructure the result into the module's
252
+ * exports, so `export const { users } = Functions.group(Users, Live)` deploys
253
+ * `users-create`, `users-delete` and so on.
254
+ *
255
+ * **Details**
256
+ *
257
+ * `live` must provide what every member needs, by its kind, and leave nothing
258
+ * required. For a Callable member, that is its handler, from
259
+ * `Callable.toLayer(Users, name, handler)`, and the implementation of each of
260
+ * its middleware. A handler from `Callable.toLayer` of the Callable on its
261
+ * own doesn't serve it in the group. For a Document Trigger member, that is
262
+ * its handler, from `DocumentTrigger.toLayer(Posts, name, handler)`. `live`
263
+ * may provide more, such as the handlers of other groups.
264
+ *
265
+ * Each member is its own function, with a worker of its own. A worker builds
266
+ * `live` on the first call to its own function, and never for another
267
+ * member's. One `live` per group is fine. Split it when members need very
268
+ * different services, so a cold instance builds only what its function needs.
269
+ *
270
+ * `options` holds each member's {@link Options}, keyed by the member's name
271
+ * and typed by its kind. A Callable member's are Firebase's `CallableOptions`,
272
+ * passed to its `onCall` unchanged. A Document Trigger member's are
273
+ * {@link DocumentTriggerOptions}: the shared options plus `retry`. The group
274
+ * has no options of its own: set those once with `setGlobalOptions`.
275
+ *
276
+ * Each export is what the member's kind deploys, and fails as one from
277
+ * {@link make} does.
278
+ *
279
+ * @example Exporting a Function Group
280
+ * ```ts
281
+ * import { Effect, Layer, Schema } from "effect";
282
+ * import { Callable, FunctionGroup } from "@visiolab/effect-firebase/functions";
283
+ * import { Functions } from "@visiolab/effect-firebase-functions";
284
+ *
285
+ * const CreateUser = Callable.make("create", {
286
+ * payload: { name: Schema.String },
287
+ * success: Schema.String,
288
+ * });
289
+ * const DeleteUser = Callable.make("delete", { payload: { id: Schema.String } });
290
+ *
291
+ * class Users extends FunctionGroup.make("users").add(CreateUser, DeleteUser) {}
292
+ *
293
+ * const UsersLive = Layer.mergeAll(
294
+ * Callable.toLayer(Users, "create", ({ name }) => Effect.succeed(name)),
295
+ * Callable.toLayer(Users, "delete", () => Effect.void),
296
+ * );
297
+ *
298
+ * // Deploys `users-create` and `users-delete`.
299
+ * export const { users } = Functions.group(Users, UsersLive, {
300
+ * delete: { region: "europe-west1" },
301
+ * });
302
+ * ```
303
+ *
304
+ * @example Exporting a group of a Callable and a Document Trigger
305
+ * ```ts
306
+ * import { Effect, Layer, Schema } from "effect";
307
+ * import { Collection, FirestoreModel } from "@visiolab/effect-firebase/firestore";
308
+ * import { Callable, DocumentTrigger, FunctionGroup } from "@visiolab/effect-firebase/functions";
309
+ * import { Functions } from "@visiolab/effect-firebase-functions";
310
+ *
311
+ * class Post extends FirestoreModel.Class<Post>("Post")({ title: Schema.String }) {}
312
+ *
313
+ * const Publish = Callable.make("publish", { payload: { id: Schema.String } });
314
+ * const OnCreated = DocumentTrigger.created("onCreated", Collection.make("posts", Post));
315
+ *
316
+ * class Posts extends FunctionGroup.make("posts").add(Publish, OnCreated) {}
317
+ *
318
+ * // Deploys `posts-publish` and `posts-onCreated`.
319
+ * export const { posts } = Functions.group(
320
+ * Posts,
321
+ * Layer.mergeAll(
322
+ * Callable.toLayer(Posts, "publish", () => Effect.void),
323
+ * DocumentTrigger.toLayer(Posts, "onCreated", (event) => Effect.log(event.ref.id)),
324
+ * ),
325
+ * { publish: { cors: true }, onCreated: { retry: true } },
326
+ * );
327
+ * ```
328
+ *
329
+ * @see {@link make} for deploying a Function in no group
330
+ * @see {@link FunctionGroup.make} for declaring the group
331
+ * @see {@link CallableClient.make} for calling the group's Callables from the client
332
+ *
333
+ * @stability unstable
334
+ * @category constructors
335
+ * @since 0.3.0
336
+ */
337
+ export declare const group: <G extends FunctionGroup.Any, E>(group: G, live: Layer.Layer<GroupServices<G>, E>, options?: GroupOptions<G>) => GroupExports<G>;
338
+ export { Functions_d_exports };
339
+ //# sourceMappingURL=Functions.d.mts.map