@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 +21 -0
- package/README.md +228 -2
- package/dist/Functions.d.mts +339 -0
- package/dist/Functions.mjs +213 -0
- package/dist/Functions.mjs.map +1 -0
- package/dist/_virtual/_rolldown/runtime.mjs +12 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.mjs +1 -0
- package/dist/internal/callable.mjs +104 -0
- package/dist/internal/callable.mjs.map +1 -0
- package/dist/internal/documentTrigger.mjs +110 -0
- package/dist/internal/documentTrigger.mjs.map +1 -0
- package/dist/internal/kinds.mjs +19 -0
- package/dist/internal/kinds.mjs.map +1 -0
- package/dist/internal/member.mjs +12 -0
- package/dist/internal/member.mjs.map +1 -0
- package/dist/internal/runtime.mjs +79 -0
- package/dist/internal/runtime.mjs.map +1 -0
- package/dist/internal/serverSpan.mjs +187 -0
- package/dist/internal/serverSpan.mjs.map +1 -0
- package/dist/logger/CloudLogging.d.mts +92 -0
- package/dist/logger/CloudLogging.mjs +146 -0
- package/dist/logger/CloudLogging.mjs.map +1 -0
- package/dist/logger/index.d.mts +2 -0
- package/dist/logger/index.mjs +1 -0
- package/package.json +64 -3
- package/src/Functions.ts +482 -0
- package/src/index.ts +8 -0
- package/src/internal/callable.ts +156 -0
- package/src/internal/documentTrigger.ts +188 -0
- package/src/internal/kinds.ts +36 -0
- package/src/internal/member.ts +21 -0
- package/src/internal/runtime.ts +110 -0
- package/src/internal/serverSpan.ts +203 -0
- package/src/logger/CloudLogging.ts +152 -0
- package/src/logger/index.ts +8 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { __exportAll } from "./_virtual/_rolldown/runtime.mjs";
|
|
2
|
+
import { deploy } from "./internal/kinds.mjs";
|
|
3
|
+
// src/Functions.ts
|
|
4
|
+
var Functions_exports = /* @__PURE__ */ __exportAll({
|
|
5
|
+
group: () => group,
|
|
6
|
+
make: () => make
|
|
7
|
+
});
|
|
8
|
+
/**
|
|
9
|
+
* Deploys a Function in no group, whatever its kind.
|
|
10
|
+
*
|
|
11
|
+
* **When to use**
|
|
12
|
+
*
|
|
13
|
+
* Use to export a Function in no group. Destructure the result into the
|
|
14
|
+
* module's exports, so the export's name is the Function's.
|
|
15
|
+
*
|
|
16
|
+
* **Details**
|
|
17
|
+
*
|
|
18
|
+
* `live` must provide what the Function's kind needs and leave nothing
|
|
19
|
+
* required. It may provide more. Give each export a Layer of its own where you
|
|
20
|
+
* can: a cold instance then builds only what its own function needs.
|
|
21
|
+
*
|
|
22
|
+
* `options` are typed by the Function's kind (see {@link Options}).
|
|
23
|
+
*
|
|
24
|
+
* For a Callable, `live` provides its handler, from `Callable.toLayer`, and
|
|
25
|
+
* the implementation of each of its middleware, such as
|
|
26
|
+
* `Caller.Authenticated.layer`. `Functions` provides no middleware itself.
|
|
27
|
+
* `options` is Firebase's `CallableOptions`, and the export is exactly what
|
|
28
|
+
* `onCall` returns, so its `__endpoint` and `run` work as for any `onCall`
|
|
29
|
+
* function.
|
|
30
|
+
*
|
|
31
|
+
* For a Document Trigger, `live` provides its handler, from
|
|
32
|
+
* `DocumentTrigger.toLayer`. `options` are {@link DocumentTriggerOptions}:
|
|
33
|
+
* the shared options plus `retry`. The export is what the provider of the
|
|
34
|
+
* trigger's change (`onDocumentCreated`, `onDocumentUpdated`,
|
|
35
|
+
* `onDocumentDeleted` or `onDocumentWritten`) returns for the `(default)`
|
|
36
|
+
* database and the path pattern of the trigger's collection. Each event is
|
|
37
|
+
* converted into the trigger's event, its data read into Firestore Values as
|
|
38
|
+
* a read does, a side that doesn't exist as a `Missing` snapshot, and run
|
|
39
|
+
* through `DocumentTrigger.serve`, in a `kind: "server"` span named
|
|
40
|
+
* `DocumentTrigger.<deployed name>` with `faas.name` and
|
|
41
|
+
* `faas.trigger: "datasource"`, logging through `CloudLogging.layer`. Any
|
|
42
|
+
* failure, whether a typed error, a defect, an interrupt or a Layer that
|
|
43
|
+
* fails to build, is logged at Error in that span and makes the function
|
|
44
|
+
* throw, so Firebase delivers the event again when `retry` is set. A Layer
|
|
45
|
+
* that failed to build is built again on the next event.
|
|
46
|
+
*
|
|
47
|
+
* Each call runs the Callable's middleware chain, the one `Callable.handler`
|
|
48
|
+
* runs, with Firebase's request provided as the portable `CallableRequest`.
|
|
49
|
+
* Its `rawRequest` is Firebase's Express request as Effect's
|
|
50
|
+
* `HttpServerRequest`, with the caller's IP address as `remoteAddress`. Its
|
|
51
|
+
* body streams Firebase's buffered bytes without copying them. The Payload is
|
|
52
|
+
* decoded inside the chain, after the middleware.
|
|
53
|
+
*
|
|
54
|
+
* A failed call answers with an `HttpsError`:
|
|
55
|
+
*
|
|
56
|
+
* - A declared error, or an error of a middleware, is sent with its schema's
|
|
57
|
+
* code, the error's `message` (else its `_tag`) and the error encoded with
|
|
58
|
+
* `Schema.toCodecJson` as `details`, so the client decodes it back into its
|
|
59
|
+
* class. A middleware that rejects the call answers before the Payload is
|
|
60
|
+
* decoded.
|
|
61
|
+
* - A Payload that doesn't decode is answered with `invalid-argument`, a
|
|
62
|
+
* generic message and no details. It is logged at Debug.
|
|
63
|
+
* - A defect, or a success or error value that doesn't encode, is answered
|
|
64
|
+
* with a bare `internal`. Its Cause is logged at Error, with the logger of
|
|
65
|
+
* `live` if it provides one.
|
|
66
|
+
* - A Layer that fails to build answers the call with a bare `internal` and
|
|
67
|
+
* is logged at Error. The next call builds it again.
|
|
68
|
+
* - An interrupt of the handler is answered with `unavailable`. When the
|
|
69
|
+
* client disconnects, the handler is interrupted and the call is answered
|
|
70
|
+
* with `cancelled`, as far as the platform reports the disconnect.
|
|
71
|
+
*
|
|
72
|
+
* @example Exporting a Callable
|
|
73
|
+
* ```ts
|
|
74
|
+
* import { Effect } from "effect";
|
|
75
|
+
* import { Callable } from "@visiolab/effect-firebase/functions";
|
|
76
|
+
* import { Functions } from "@visiolab/effect-firebase-functions";
|
|
77
|
+
*
|
|
78
|
+
* const Ping = Callable.make("ping");
|
|
79
|
+
*
|
|
80
|
+
* export const { ping } = Functions.make(Ping, Callable.toLayer(Ping, () => Effect.void));
|
|
81
|
+
* ```
|
|
82
|
+
*
|
|
83
|
+
* @example Exporting a Document Trigger
|
|
84
|
+
* ```ts
|
|
85
|
+
* import { Effect, Schema } from "effect";
|
|
86
|
+
* import { Collection, FirestoreModel } from "@visiolab/effect-firebase/firestore";
|
|
87
|
+
* import { DocumentTrigger } from "@visiolab/effect-firebase/functions";
|
|
88
|
+
* import { Functions } from "@visiolab/effect-firebase-functions";
|
|
89
|
+
*
|
|
90
|
+
* class Post extends FirestoreModel.Class<Post>("Post")({ title: Schema.String }) {}
|
|
91
|
+
*
|
|
92
|
+
* const OnPostCreated = DocumentTrigger.created("onPostCreated", Collection.make("posts", Post));
|
|
93
|
+
*
|
|
94
|
+
* export const { onPostCreated } = Functions.make(
|
|
95
|
+
* OnPostCreated,
|
|
96
|
+
* DocumentTrigger.toLayer(OnPostCreated, (event) =>
|
|
97
|
+
* Effect.flatMap(event.snapshot.data, (post) => Effect.log(`New post: ${post.title}`)),
|
|
98
|
+
* ),
|
|
99
|
+
* { retry: true },
|
|
100
|
+
* );
|
|
101
|
+
* ```
|
|
102
|
+
*
|
|
103
|
+
* @see {@link group} for deploying the members of a Function Group
|
|
104
|
+
* @see {@link Callable.serve} for serving a Callable from a transport of your own
|
|
105
|
+
* @see {@link Callable.handler} for running a Callable's handler in process
|
|
106
|
+
* @see {@link DocumentTrigger.serve} for running a Document Trigger's event in process
|
|
107
|
+
* @see {@link CallableClient.make} for calling a Callable from the client
|
|
108
|
+
*
|
|
109
|
+
* @stability unstable
|
|
110
|
+
* @category constructors
|
|
111
|
+
* @since 0.3.0
|
|
112
|
+
*/
|
|
113
|
+
const make = (fn, live, options) => ({ [fn.name]: deploy(void 0, fn, live, options) });
|
|
114
|
+
/**
|
|
115
|
+
* Deploys the members of a Function Group, each as a function of its own,
|
|
116
|
+
* whatever their kinds.
|
|
117
|
+
*
|
|
118
|
+
* **When to use**
|
|
119
|
+
*
|
|
120
|
+
* Use to export a Function Group. Destructure the result into the module's
|
|
121
|
+
* exports, so `export const { users } = Functions.group(Users, Live)` deploys
|
|
122
|
+
* `users-create`, `users-delete` and so on.
|
|
123
|
+
*
|
|
124
|
+
* **Details**
|
|
125
|
+
*
|
|
126
|
+
* `live` must provide what every member needs, by its kind, and leave nothing
|
|
127
|
+
* required. For a Callable member, that is its handler, from
|
|
128
|
+
* `Callable.toLayer(Users, name, handler)`, and the implementation of each of
|
|
129
|
+
* its middleware. A handler from `Callable.toLayer` of the Callable on its
|
|
130
|
+
* own doesn't serve it in the group. For a Document Trigger member, that is
|
|
131
|
+
* its handler, from `DocumentTrigger.toLayer(Posts, name, handler)`. `live`
|
|
132
|
+
* may provide more, such as the handlers of other groups.
|
|
133
|
+
*
|
|
134
|
+
* Each member is its own function, with a worker of its own. A worker builds
|
|
135
|
+
* `live` on the first call to its own function, and never for another
|
|
136
|
+
* member's. One `live` per group is fine. Split it when members need very
|
|
137
|
+
* different services, so a cold instance builds only what its function needs.
|
|
138
|
+
*
|
|
139
|
+
* `options` holds each member's {@link Options}, keyed by the member's name
|
|
140
|
+
* and typed by its kind. A Callable member's are Firebase's `CallableOptions`,
|
|
141
|
+
* passed to its `onCall` unchanged. A Document Trigger member's are
|
|
142
|
+
* {@link DocumentTriggerOptions}: the shared options plus `retry`. The group
|
|
143
|
+
* has no options of its own: set those once with `setGlobalOptions`.
|
|
144
|
+
*
|
|
145
|
+
* Each export is what the member's kind deploys, and fails as one from
|
|
146
|
+
* {@link make} does.
|
|
147
|
+
*
|
|
148
|
+
* @example Exporting a Function Group
|
|
149
|
+
* ```ts
|
|
150
|
+
* import { Effect, Layer, Schema } from "effect";
|
|
151
|
+
* import { Callable, FunctionGroup } from "@visiolab/effect-firebase/functions";
|
|
152
|
+
* import { Functions } from "@visiolab/effect-firebase-functions";
|
|
153
|
+
*
|
|
154
|
+
* const CreateUser = Callable.make("create", {
|
|
155
|
+
* payload: { name: Schema.String },
|
|
156
|
+
* success: Schema.String,
|
|
157
|
+
* });
|
|
158
|
+
* const DeleteUser = Callable.make("delete", { payload: { id: Schema.String } });
|
|
159
|
+
*
|
|
160
|
+
* class Users extends FunctionGroup.make("users").add(CreateUser, DeleteUser) {}
|
|
161
|
+
*
|
|
162
|
+
* const UsersLive = Layer.mergeAll(
|
|
163
|
+
* Callable.toLayer(Users, "create", ({ name }) => Effect.succeed(name)),
|
|
164
|
+
* Callable.toLayer(Users, "delete", () => Effect.void),
|
|
165
|
+
* );
|
|
166
|
+
*
|
|
167
|
+
* // Deploys `users-create` and `users-delete`.
|
|
168
|
+
* export const { users } = Functions.group(Users, UsersLive, {
|
|
169
|
+
* delete: { region: "europe-west1" },
|
|
170
|
+
* });
|
|
171
|
+
* ```
|
|
172
|
+
*
|
|
173
|
+
* @example Exporting a group of a Callable and a Document Trigger
|
|
174
|
+
* ```ts
|
|
175
|
+
* import { Effect, Layer, Schema } from "effect";
|
|
176
|
+
* import { Collection, FirestoreModel } from "@visiolab/effect-firebase/firestore";
|
|
177
|
+
* import { Callable, DocumentTrigger, FunctionGroup } from "@visiolab/effect-firebase/functions";
|
|
178
|
+
* import { Functions } from "@visiolab/effect-firebase-functions";
|
|
179
|
+
*
|
|
180
|
+
* class Post extends FirestoreModel.Class<Post>("Post")({ title: Schema.String }) {}
|
|
181
|
+
*
|
|
182
|
+
* const Publish = Callable.make("publish", { payload: { id: Schema.String } });
|
|
183
|
+
* const OnCreated = DocumentTrigger.created("onCreated", Collection.make("posts", Post));
|
|
184
|
+
*
|
|
185
|
+
* class Posts extends FunctionGroup.make("posts").add(Publish, OnCreated) {}
|
|
186
|
+
*
|
|
187
|
+
* // Deploys `posts-publish` and `posts-onCreated`.
|
|
188
|
+
* export const { posts } = Functions.group(
|
|
189
|
+
* Posts,
|
|
190
|
+
* Layer.mergeAll(
|
|
191
|
+
* Callable.toLayer(Posts, "publish", () => Effect.void),
|
|
192
|
+
* DocumentTrigger.toLayer(Posts, "onCreated", (event) => Effect.log(event.ref.id)),
|
|
193
|
+
* ),
|
|
194
|
+
* { publish: { cors: true }, onCreated: { retry: true } },
|
|
195
|
+
* );
|
|
196
|
+
* ```
|
|
197
|
+
*
|
|
198
|
+
* @see {@link make} for deploying a Function in no group
|
|
199
|
+
* @see {@link FunctionGroup.make} for declaring the group
|
|
200
|
+
* @see {@link CallableClient.make} for calling the group's Callables from the client
|
|
201
|
+
*
|
|
202
|
+
* @stability unstable
|
|
203
|
+
* @category constructors
|
|
204
|
+
* @since 0.3.0
|
|
205
|
+
*/
|
|
206
|
+
const group = (group, live, options) => {
|
|
207
|
+
const exports = {};
|
|
208
|
+
for (const [name, fn] of Object.entries(group.functions)) exports[name] = deploy(group, fn, live, options?.[name]);
|
|
209
|
+
return { [group.identifier]: exports };
|
|
210
|
+
};
|
|
211
|
+
export { Functions_exports, group, make };
|
|
212
|
+
|
|
213
|
+
//# sourceMappingURL=Functions.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Functions.mjs","names":["kinds.deploy"],"sources":["../src/Functions.ts"],"sourcesContent":["/**\n * Deploys Functions, of any kind, as Cloud Functions for Firebase, which plain\n * `firebase deploy` deploys. {@link make} deploys a Function in no group under\n * its name, and {@link group} deploys each member of a Function Group as\n * `<group>-<name>`. Each dispatches on the Function's kind: a Callable is\n * served with `onCall`, and a Document Trigger with the `onDocument*`\n * provider of its change, such as `onDocumentCreated`.\n *\n * Each export is built at once and synchronously, and builds nothing: the\n * Layer that implements it is built on the first call or event, so discovery\n * and deploy never run your setup. Every Layer is built through one memo map\n * per process, so a Layer shared by several exports is built once, and\n * concurrent first calls share one build.\n *\n * Where it differs from a plain `firebase-functions` function:\n *\n * - Finalizers of the Layer don't run when the instance shuts down. Release\n * what must be released within the call.\n * - `minInstances` keeps an instance alive, but its Layer is still built on the\n * first call.\n * - Call `setGlobalOptions` before any function is defined, as Firebase\n * requires.\n *\n * Every call or event logs through `CloudLogging.layer`, provided beneath the\n * Layer you serve: Effect logs land in Cloud Logging as structured entries\n * tied to the current span, so concurrent calls on one instance each log under\n * their own trace. A `Logger.layer` in the Layer replaces it. Logs of a first\n * call's Layer build are written once the build is done, when the spans they\n * were logged under have their final ids. Entries carry no timestamp, so\n * Cloud Logging stamps them with the time the build finished. Entries carry\n * no uid; add one in the handler with `Effect.annotateLogs`.\n *\n * Its span's parent is the trace context Firebase reads from the request or\n * event. With none, as in the emulator, the span is a root span. The span is\n * always on, and is exported only when the Layer provides a `Tracer`.\n *\n * **Callables**\n *\n * A Callable's Payload and success value cross the wire as\n * `Schema.toCodecJson` output, so a `Date` arrives as a `Date`.\n *\n * Every call runs in a `kind: \"server\"` span named `CallableFunction.<deployed\n * name>`, such as `CallableFunction.users-create`, with the attributes\n * `rpc.system: \"firebase_callable\"`, `rpc.method` (the Callable's name),\n * `rpc.service` (the group, for a member) and `faas.name` (the deployed name).\n * The span covers the Payload decoding, the handler and the encoding of the\n * answer, and the Layer build on the call that triggers it. Spans the handler\n * or the build open are its children. The trace context comes from the\n * `X-Cloud-Trace-Context` header, else `traceparent`. The client's span of\n * the same call is not linked to it, so the two land in separate traces. The\n * span carries no uid; add one in the handler with `Effect.annotateCurrentSpan`.\n *\n * **Document Triggers**\n *\n * A Document Trigger listens to the `(default)` database, at the path\n * pattern of its collection. Each event runs in a `kind: \"server\"` span named\n * `DocumentTrigger.<deployed name>`. Firebase delivers each event at least\n * once, so make the handler idempotent, keyed on the event's `id`.\n *\n * @example Deploying a Callable\n * ```ts\n * import { Effect, Schema } from \"effect\";\n * import { Callable } from \"@visiolab/effect-firebase/functions\";\n * import { Functions } from \"@visiolab/effect-firebase-functions\";\n *\n * const Greet = Callable.make(\"greet\", {\n * payload: { name: Schema.String },\n * success: Schema.String,\n * });\n *\n * const GreetLive = Callable.toLayer(Greet, ({ name }) => Effect.succeed(`Hello, ${name}!`));\n *\n * export const { greet } = Functions.make(Greet, GreetLive, { region: \"europe-west1\" });\n * ```\n *\n * @stability unstable\n * @since 0.3.0\n */\nimport type { Layer } from \"effect\";\nimport type * as Callable from \"@visiolab/effect-firebase/functions/Callable\";\nimport type * as DocumentTrigger from \"@visiolab/effect-firebase/functions/DocumentTrigger\";\nimport type * as FirebaseFunction from \"@visiolab/effect-firebase/functions/FirebaseFunction\";\nimport type * as FunctionGroup from \"@visiolab/effect-firebase/functions/FunctionGroup\";\nimport type { CloudFunction } from \"firebase-functions/v2\";\nimport type * as Firestore from \"firebase-functions/v2/firestore\";\nimport type * as Https from \"firebase-functions/v2/https\";\nimport * as kinds from \"./internal/kinds.ts\";\n\n/**\n * The value `onCall` returns for a Callable: what firebase-tools discovers and\n * deploys.\n *\n * @see {@link Export} for the export of a Function of any kind\n *\n * @stability unstable\n * @category models\n * @since 0.3.0\n */\nexport type CallableExport = Https.CallableFunction<unknown, Promise<unknown>>;\n\n/**\n * The value an `onDocument*` provider returns for a Document Trigger, by the\n * change it runs on: what firebase-tools discovers and deploys.\n *\n * **Details**\n *\n * A `created` trigger is exported with `onDocumentCreated`, a `deleted` one\n * with `onDocumentDeleted`, an `updated` one with `onDocumentUpdated` and a\n * `written` one with `onDocumentWritten`. The type is the `CloudFunction` of\n * the event that provider hands over.\n *\n * @see {@link Export} for the export of a Function of any kind\n *\n * @stability unstable\n * @category type-level\n * @since 0.3.0\n */\nexport type DocumentTriggerExport<T extends DocumentTrigger.Any> = CloudFunction<\n Firestore.FirestoreEvent<DocumentTriggerEventData[T[\"event\"]]>\n>;\n\n/**\n * The data of the event each `onDocument*` provider hands over, by the change\n * it runs on.\n */\ninterface DocumentTriggerEventData {\n readonly created: Firestore.QueryDocumentSnapshot | undefined;\n readonly deleted: Firestore.QueryDocumentSnapshot | undefined;\n readonly updated: Firestore.Change<Firestore.QueryDocumentSnapshot> | undefined;\n readonly written: Firestore.Change<Firestore.DocumentSnapshot> | undefined;\n}\n\n/**\n * The deploy options of a Document Trigger: Firebase's `DocumentOptions`\n * without what the library derives from the trigger. That leaves the options\n * every Function shares, such as `region`, `memory` and `timeoutSeconds`,\n * plus `retry`.\n *\n * **Details**\n *\n * The `document` path pattern comes from the trigger's collection, and the\n * database is always `(default)`, the one `Firestore` reads and writes, so\n * `document`, `database` and `namespace` are left out, as are the event\n * filter options.\n *\n * With `retry: true`, Firebase delivers a failed event again until it\n * succeeds or its retry window ends. Without it, a failed event is dropped.\n * The library adds no retry of its own.\n *\n * @stability unstable\n * @category models\n * @since 0.3.0\n */\nexport type DocumentTriggerOptions = Omit<\n Firestore.DocumentOptions,\n | \"document\"\n | \"database\"\n | \"namespace\"\n | \"eventType\"\n | \"eventFilters\"\n | \"eventFilterPathPatterns\"\n | \"channel\"\n>;\n\n/**\n * The export of a Function, by its kind: a {@link CallableExport} for a\n * Callable, a {@link DocumentTriggerExport} for a Document Trigger.\n *\n * @stability unstable\n * @category type-level\n * @since 0.3.0\n */\nexport type Export<F extends FirebaseFunction.Any> = F extends Callable.Any\n ? CallableExport\n : F extends DocumentTrigger.Any\n ? DocumentTriggerExport<F>\n : never;\n\n/**\n * The deploy options of a Function, by its kind: Firebase's `CallableOptions`\n * for a Callable, passed to `onCall` unchanged, and\n * {@link DocumentTriggerOptions} for a Document Trigger.\n *\n * @stability unstable\n * @category type-level\n * @since 0.3.0\n */\nexport type Options<F extends FirebaseFunction.Any> = F extends Callable.Any\n ? Https.CallableOptions\n : F extends DocumentTrigger.Any\n ? DocumentTriggerOptions\n : never;\n\n/**\n * What serving a Function under the handler key `H` requires, by its kind.\n * For a Callable: its handler, the implementation of each of its middleware,\n * and what its middleware chain still requires. For a Document Trigger: its\n * handler.\n *\n * @stability unstable\n * @category type-level\n * @since 0.3.0\n */\nexport type Services<F extends FirebaseFunction.Any, H> = F extends Callable.Any\n ? Callable.ChainServices<F, H>\n : F extends DocumentTrigger.Any\n ? H\n : never;\n\n/**\n * What serving every member of a {@link FunctionGroup.FunctionGroup}\n * requires: what each member requires, by its kind, under its handler key in\n * the group.\n *\n * @stability unstable\n * @category type-level\n * @since 0.3.0\n */\nexport type GroupServices<G extends FunctionGroup.Any> =\n FunctionGroup.Names<G> extends infer Name\n ? Name extends string\n ? Services<FunctionGroup.Member<G, Name>, FirebaseFunction.Handler<G[\"identifier\"], Name>>\n : never\n : never;\n\n/**\n * The exports of {@link make}: one export under the Function's name.\n *\n * @stability unstable\n * @category models\n * @since 0.3.0\n */\nexport type Exports<F extends FirebaseFunction.Any> = { readonly [K in F[\"name\"]]: Export<F> };\n\n/**\n * The exports of {@link group}: one export per member, keyed by the member's\n * name, under the group's name.\n *\n * @stability unstable\n * @category models\n * @since 0.3.0\n */\nexport type GroupExports<G extends FunctionGroup.Any> = {\n readonly [K in G[\"identifier\"]]: {\n readonly [N in FunctionGroup.Names<G>]: Export<FunctionGroup.Member<G, N>>;\n };\n};\n\n/**\n * The options of {@link group}: each member's {@link Options}, typed by its\n * kind and keyed by its name. Members left out get none.\n *\n * @stability unstable\n * @category models\n * @since 0.3.0\n */\nexport type GroupOptions<G extends FunctionGroup.Any> = {\n readonly [N in FunctionGroup.Names<G>]?: Options<FunctionGroup.Member<G, N>>;\n};\n\n/**\n * Deploys a Function in no group, whatever its kind.\n *\n * **When to use**\n *\n * Use to export a Function in no group. Destructure the result into the\n * module's exports, so the export's name is the Function's.\n *\n * **Details**\n *\n * `live` must provide what the Function's kind needs and leave nothing\n * required. It may provide more. Give each export a Layer of its own where you\n * can: a cold instance then builds only what its own function needs.\n *\n * `options` are typed by the Function's kind (see {@link Options}).\n *\n * For a Callable, `live` provides its handler, from `Callable.toLayer`, and\n * the implementation of each of its middleware, such as\n * `Caller.Authenticated.layer`. `Functions` provides no middleware itself.\n * `options` is Firebase's `CallableOptions`, and the export is exactly what\n * `onCall` returns, so its `__endpoint` and `run` work as for any `onCall`\n * function.\n *\n * For a Document Trigger, `live` provides its handler, from\n * `DocumentTrigger.toLayer`. `options` are {@link DocumentTriggerOptions}:\n * the shared options plus `retry`. The export is what the provider of the\n * trigger's change (`onDocumentCreated`, `onDocumentUpdated`,\n * `onDocumentDeleted` or `onDocumentWritten`) returns for the `(default)`\n * database and the path pattern of the trigger's collection. Each event is\n * converted into the trigger's event, its data read into Firestore Values as\n * a read does, a side that doesn't exist as a `Missing` snapshot, and run\n * through `DocumentTrigger.serve`, in a `kind: \"server\"` span named\n * `DocumentTrigger.<deployed name>` with `faas.name` and\n * `faas.trigger: \"datasource\"`, logging through `CloudLogging.layer`. Any\n * failure, whether a typed error, a defect, an interrupt or a Layer that\n * fails to build, is logged at Error in that span and makes the function\n * throw, so Firebase delivers the event again when `retry` is set. A Layer\n * that failed to build is built again on the next event.\n *\n * Each call runs the Callable's middleware chain, the one `Callable.handler`\n * runs, with Firebase's request provided as the portable `CallableRequest`.\n * Its `rawRequest` is Firebase's Express request as Effect's\n * `HttpServerRequest`, with the caller's IP address as `remoteAddress`. Its\n * body streams Firebase's buffered bytes without copying them. The Payload is\n * decoded inside the chain, after the middleware.\n *\n * A failed call answers with an `HttpsError`:\n *\n * - A declared error, or an error of a middleware, is sent with its schema's\n * code, the error's `message` (else its `_tag`) and the error encoded with\n * `Schema.toCodecJson` as `details`, so the client decodes it back into its\n * class. A middleware that rejects the call answers before the Payload is\n * decoded.\n * - A Payload that doesn't decode is answered with `invalid-argument`, a\n * generic message and no details. It is logged at Debug.\n * - A defect, or a success or error value that doesn't encode, is answered\n * with a bare `internal`. Its Cause is logged at Error, with the logger of\n * `live` if it provides one.\n * - A Layer that fails to build answers the call with a bare `internal` and\n * is logged at Error. The next call builds it again.\n * - An interrupt of the handler is answered with `unavailable`. When the\n * client disconnects, the handler is interrupted and the call is answered\n * with `cancelled`, as far as the platform reports the disconnect.\n *\n * @example Exporting a Callable\n * ```ts\n * import { Effect } from \"effect\";\n * import { Callable } from \"@visiolab/effect-firebase/functions\";\n * import { Functions } from \"@visiolab/effect-firebase-functions\";\n *\n * const Ping = Callable.make(\"ping\");\n *\n * export const { ping } = Functions.make(Ping, Callable.toLayer(Ping, () => Effect.void));\n * ```\n *\n * @example Exporting a Document Trigger\n * ```ts\n * import { Effect, Schema } from \"effect\";\n * import { Collection, FirestoreModel } from \"@visiolab/effect-firebase/firestore\";\n * import { DocumentTrigger } from \"@visiolab/effect-firebase/functions\";\n * import { Functions } from \"@visiolab/effect-firebase-functions\";\n *\n * class Post extends FirestoreModel.Class<Post>(\"Post\")({ title: Schema.String }) {}\n *\n * const OnPostCreated = DocumentTrigger.created(\"onPostCreated\", Collection.make(\"posts\", Post));\n *\n * export const { onPostCreated } = Functions.make(\n * OnPostCreated,\n * DocumentTrigger.toLayer(OnPostCreated, (event) =>\n * Effect.flatMap(event.snapshot.data, (post) => Effect.log(`New post: ${post.title}`)),\n * ),\n * { retry: true },\n * );\n * ```\n *\n * @see {@link group} for deploying the members of a Function Group\n * @see {@link Callable.serve} for serving a Callable from a transport of your own\n * @see {@link Callable.handler} for running a Callable's handler in process\n * @see {@link DocumentTrigger.serve} for running a Document Trigger's event in process\n * @see {@link CallableClient.make} for calling a Callable from the client\n *\n * @stability unstable\n * @category constructors\n * @since 0.3.0\n */\nexport const make = <F extends FirebaseFunction.Any, E>(\n fn: F,\n live: Layer.Layer<Services<F, FirebaseFunction.HandlerFor<F>>, E>,\n options?: Options<F>,\n): Exports<F> =>\n ({\n [fn.name]: kinds.deploy(undefined, fn, live, options),\n }) as Exports<F>;\n\n/**\n * Deploys the members of a Function Group, each as a function of its own,\n * whatever their kinds.\n *\n * **When to use**\n *\n * Use to export a Function Group. Destructure the result into the module's\n * exports, so `export const { users } = Functions.group(Users, Live)` deploys\n * `users-create`, `users-delete` and so on.\n *\n * **Details**\n *\n * `live` must provide what every member needs, by its kind, and leave nothing\n * required. For a Callable member, that is its handler, from\n * `Callable.toLayer(Users, name, handler)`, and the implementation of each of\n * its middleware. A handler from `Callable.toLayer` of the Callable on its\n * own doesn't serve it in the group. For a Document Trigger member, that is\n * its handler, from `DocumentTrigger.toLayer(Posts, name, handler)`. `live`\n * may provide more, such as the handlers of other groups.\n *\n * Each member is its own function, with a worker of its own. A worker builds\n * `live` on the first call to its own function, and never for another\n * member's. One `live` per group is fine. Split it when members need very\n * different services, so a cold instance builds only what its function needs.\n *\n * `options` holds each member's {@link Options}, keyed by the member's name\n * and typed by its kind. A Callable member's are Firebase's `CallableOptions`,\n * passed to its `onCall` unchanged. A Document Trigger member's are\n * {@link DocumentTriggerOptions}: the shared options plus `retry`. The group\n * has no options of its own: set those once with `setGlobalOptions`.\n *\n * Each export is what the member's kind deploys, and fails as one from\n * {@link make} does.\n *\n * @example Exporting a Function Group\n * ```ts\n * import { Effect, Layer, Schema } from \"effect\";\n * import { Callable, FunctionGroup } from \"@visiolab/effect-firebase/functions\";\n * import { Functions } from \"@visiolab/effect-firebase-functions\";\n *\n * const CreateUser = Callable.make(\"create\", {\n * payload: { name: Schema.String },\n * success: Schema.String,\n * });\n * const DeleteUser = Callable.make(\"delete\", { payload: { id: Schema.String } });\n *\n * class Users extends FunctionGroup.make(\"users\").add(CreateUser, DeleteUser) {}\n *\n * const UsersLive = Layer.mergeAll(\n * Callable.toLayer(Users, \"create\", ({ name }) => Effect.succeed(name)),\n * Callable.toLayer(Users, \"delete\", () => Effect.void),\n * );\n *\n * // Deploys `users-create` and `users-delete`.\n * export const { users } = Functions.group(Users, UsersLive, {\n * delete: { region: \"europe-west1\" },\n * });\n * ```\n *\n * @example Exporting a group of a Callable and a Document Trigger\n * ```ts\n * import { Effect, Layer, Schema } from \"effect\";\n * import { Collection, FirestoreModel } from \"@visiolab/effect-firebase/firestore\";\n * import { Callable, DocumentTrigger, FunctionGroup } from \"@visiolab/effect-firebase/functions\";\n * import { Functions } from \"@visiolab/effect-firebase-functions\";\n *\n * class Post extends FirestoreModel.Class<Post>(\"Post\")({ title: Schema.String }) {}\n *\n * const Publish = Callable.make(\"publish\", { payload: { id: Schema.String } });\n * const OnCreated = DocumentTrigger.created(\"onCreated\", Collection.make(\"posts\", Post));\n *\n * class Posts extends FunctionGroup.make(\"posts\").add(Publish, OnCreated) {}\n *\n * // Deploys `posts-publish` and `posts-onCreated`.\n * export const { posts } = Functions.group(\n * Posts,\n * Layer.mergeAll(\n * Callable.toLayer(Posts, \"publish\", () => Effect.void),\n * DocumentTrigger.toLayer(Posts, \"onCreated\", (event) => Effect.log(event.ref.id)),\n * ),\n * { publish: { cors: true }, onCreated: { retry: true } },\n * );\n * ```\n *\n * @see {@link make} for deploying a Function in no group\n * @see {@link FunctionGroup.make} for declaring the group\n * @see {@link CallableClient.make} for calling the group's Callables from the client\n *\n * @stability unstable\n * @category constructors\n * @since 0.3.0\n */\nexport const group = <G extends FunctionGroup.Any, E>(\n group: G,\n live: Layer.Layer<GroupServices<G>, E>,\n options?: GroupOptions<G>,\n): GroupExports<G> => {\n const exports: Record<string, unknown> = {};\n for (const [name, fn] of Object.entries(group.functions)) {\n exports[name] = kinds.deploy(\n group,\n fn,\n live,\n (options as Record<string, object | undefined> | undefined)?.[name],\n );\n }\n return { [group.identifier]: exports } as GroupExports<G>;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6WA,MAAa,QACX,IACA,MACA,aAEC,GACE,GAAG,OAAOA,OAAa,KAAA,GAAW,IAAI,MAAM,OAAO,EACtD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8FF,MAAa,SACX,OACA,MACA,YACoB;CACpB,MAAM,UAAmC,CAAC;CAC1C,KAAK,MAAM,CAAC,MAAM,OAAO,OAAO,QAAQ,MAAM,SAAS,GACrD,QAAQ,QAAQA,OACd,OACA,IACA,MACC,UAA6D,KAChE;CAEF,OAAO,GAAG,MAAM,aAAa,QAAQ;AACvC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// \0rolldown/runtime.js
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __exportAll = (all, no_symbols) => {
|
|
4
|
+
let target = {};
|
|
5
|
+
for (var name in all) __defProp(target, name, {
|
|
6
|
+
get: all[name],
|
|
7
|
+
enumerable: true
|
|
8
|
+
});
|
|
9
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
10
|
+
return target;
|
|
11
|
+
};
|
|
12
|
+
export { __exportAll };
|
package/dist/index.d.mts
ADDED
package/dist/index.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Functions_exports as Functions } from "./Functions.mjs";
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { call } from "./member.mjs";
|
|
2
|
+
import { make } from "./runtime.mjs";
|
|
3
|
+
import { Cause, Effect, Exit, Option } from "effect";
|
|
4
|
+
import * as HttpServerRequest from "effect/http/HttpServerRequest";
|
|
5
|
+
import * as Callable from "@visiolab/effect-firebase/functions/Callable";
|
|
6
|
+
import * as CallableError from "@visiolab/effect-firebase/functions/CallableError";
|
|
7
|
+
import * as Https from "firebase-functions/v2/https";
|
|
8
|
+
// src/internal/callable.ts
|
|
9
|
+
/**
|
|
10
|
+
* The Callable kind's runtime: serves one Callable as an `onCall` export.
|
|
11
|
+
*
|
|
12
|
+
* @internal
|
|
13
|
+
*/
|
|
14
|
+
const internalError = () => new Https.HttpsError("internal", "INTERNAL");
|
|
15
|
+
/**
|
|
16
|
+
* Firebase's Express request as Effect's `HttpServerRequest`: the same method,
|
|
17
|
+
* URL, headers and body, with the caller's address as `remoteAddress`. The
|
|
18
|
+
* body streams Firebase's bytes rather than copying them, as a `Buffer` body
|
|
19
|
+
* would.
|
|
20
|
+
*/
|
|
21
|
+
const toHttpServerRequest = (raw) => {
|
|
22
|
+
const headers = [];
|
|
23
|
+
for (const [name, value] of Object.entries(raw.headers)) {
|
|
24
|
+
if (value === void 0) continue;
|
|
25
|
+
headers.push([name, Array.isArray(value) ? value.join(", ") : value]);
|
|
26
|
+
}
|
|
27
|
+
const method = raw.method.toUpperCase();
|
|
28
|
+
const bytes = raw.rawBody;
|
|
29
|
+
const init = method === "GET" || method === "HEAD" ? {
|
|
30
|
+
method,
|
|
31
|
+
headers
|
|
32
|
+
} : {
|
|
33
|
+
method,
|
|
34
|
+
headers,
|
|
35
|
+
body: new ReadableStream({ start(controller) {
|
|
36
|
+
controller.enqueue(bytes);
|
|
37
|
+
controller.close();
|
|
38
|
+
} }),
|
|
39
|
+
duplex: "half"
|
|
40
|
+
};
|
|
41
|
+
return HttpServerRequest.fromWeb(new Request(`${raw.protocol}://${raw.get("host") ?? "localhost"}${raw.originalUrl}`, init)).modify({ remoteAddress: Option.fromNullishOr(raw.ip) });
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* The portable `CallableRequest` of a call, from what `firebase-functions`
|
|
45
|
+
* hands over.
|
|
46
|
+
*/
|
|
47
|
+
const toCallableRequest = (request) => ({
|
|
48
|
+
auth: request.auth,
|
|
49
|
+
app: request.app,
|
|
50
|
+
instanceIdToken: request.instanceIdToken,
|
|
51
|
+
data: request.data,
|
|
52
|
+
rawRequest: toHttpServerRequest(request.rawRequest)
|
|
53
|
+
});
|
|
54
|
+
/**
|
|
55
|
+
* The answer `Callable.serve` already chose for a failed call, if `cause`
|
|
56
|
+
* carries one.
|
|
57
|
+
*/
|
|
58
|
+
const answerOf = (cause) => {
|
|
59
|
+
const failure = cause.reasons.find(Cause.isFailReason)?.error;
|
|
60
|
+
return failure instanceof CallableError.CallableError && failure.reason._tag === "ResponseError" ? failure.reason : void 0;
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Logs the Cause of a call that failed unexpectedly: a defect, or a failure
|
|
64
|
+
* that isn't an answer. Answers and interrupts aren't logged.
|
|
65
|
+
*/
|
|
66
|
+
const logUnexpected = (message) => (cause) => answerOf(cause) !== void 0 || Cause.hasInterruptsOnly(cause) ? Effect.void : Effect.logError(message, cause);
|
|
67
|
+
/**
|
|
68
|
+
* The `HttpsError` a failed call answers with. A failure is already the
|
|
69
|
+
* answer `Callable.serve` chose. An interrupt is `cancelled` when the client
|
|
70
|
+
* went away and `unavailable` otherwise. Anything else, logged in the call's
|
|
71
|
+
* span by {@link logUnexpected}, is a bare `internal`.
|
|
72
|
+
*/
|
|
73
|
+
const toHttpsError = (cause, aborted) => {
|
|
74
|
+
const answer = answerOf(cause);
|
|
75
|
+
if (answer !== void 0) return new Https.HttpsError(answer.code, answer.message, answer.details);
|
|
76
|
+
if (Cause.hasInterruptsOnly(cause)) return aborted ? new Https.HttpsError("cancelled", "CANCELLED") : new Https.HttpsError("unavailable", "UNAVAILABLE");
|
|
77
|
+
return internalError();
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* The Callable Function of `callable` in `group`, or in no group: an `onCall`
|
|
81
|
+
* export that runs `Callable.serve` under the deployed name and span
|
|
82
|
+
* attributes of `Callable.deployment`. It builds `live` on its own first call.
|
|
83
|
+
*
|
|
84
|
+
* @internal
|
|
85
|
+
*/
|
|
86
|
+
const deploy = (group, callable, live, options = {}) => {
|
|
87
|
+
const deployment = call(group, callable, Callable.deployment);
|
|
88
|
+
const serve = call(group, callable, Callable.serve);
|
|
89
|
+
const run = make(live, {
|
|
90
|
+
span: `CallableFunction.${deployment.name}`,
|
|
91
|
+
attributes: deployment.attributes,
|
|
92
|
+
onBuildFailure: logUnexpected("The Layer of a Callable failed to build")
|
|
93
|
+
});
|
|
94
|
+
return Https.onCall(options, async (request, response) => {
|
|
95
|
+
const signal = response?.signal;
|
|
96
|
+
const callRequest = toCallableRequest(request);
|
|
97
|
+
const exit = await run(serve.pipe(Effect.flatMap((run) => run(callRequest)), Effect.onError(logUnexpected("A Callable failed"))), signal);
|
|
98
|
+
if (Exit.isSuccess(exit)) return exit.value;
|
|
99
|
+
throw toHttpsError(exit.cause, signal?.aborted === true);
|
|
100
|
+
});
|
|
101
|
+
};
|
|
102
|
+
export { deploy };
|
|
103
|
+
|
|
104
|
+
//# sourceMappingURL=callable.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"callable.mjs","names":["Member.call","Runtime.make"],"sources":["../../src/internal/callable.ts"],"sourcesContent":["/**\n * The Callable kind's runtime: serves one Callable as an `onCall` export.\n *\n * @internal\n */\nimport { Cause, Effect, Exit, type Layer, Option } from \"effect\";\nimport * as HttpServerRequest from \"effect/http/HttpServerRequest\";\nimport * as Callable from \"@visiolab/effect-firebase/functions/Callable\";\nimport * as CallableError from \"@visiolab/effect-firebase/functions/CallableError\";\nimport type * as CallableRequest from \"@visiolab/effect-firebase/functions/CallableRequest\";\nimport type * as FunctionGroup from \"@visiolab/effect-firebase/functions/FunctionGroup\";\nimport * as Https from \"firebase-functions/v2/https\";\nimport * as Member from \"./member.ts\";\nimport * as Runtime from \"./runtime.ts\";\n\nconst internalError = () => new Https.HttpsError(\"internal\", \"INTERNAL\");\n\n/**\n * Firebase's Express request as Effect's `HttpServerRequest`: the same method,\n * URL, headers and body, with the caller's address as `remoteAddress`. The\n * body streams Firebase's bytes rather than copying them, as a `Buffer` body\n * would.\n */\nconst toHttpServerRequest = (raw: Https.Request): HttpServerRequest.HttpServerRequest => {\n const headers: Array<[string, string]> = [];\n for (const [name, value] of Object.entries(raw.headers)) {\n if (value === undefined) continue;\n headers.push([name, Array.isArray(value) ? value.join(\", \") : value]);\n }\n const method = raw.method.toUpperCase();\n const bytes = raw.rawBody;\n const init: RequestInit =\n // A GET or HEAD request can't have a body. Callables are POSTs.\n method === \"GET\" || method === \"HEAD\"\n ? { method, headers }\n : {\n method,\n headers,\n body: new ReadableStream<Uint8Array>({\n start(controller) {\n controller.enqueue(bytes);\n controller.close();\n },\n }),\n // Required of a stream body.\n duplex: \"half\",\n };\n return HttpServerRequest.fromWeb(\n new Request(`${raw.protocol}://${raw.get(\"host\") ?? \"localhost\"}${raw.originalUrl}`, init),\n ).modify({ remoteAddress: Option.fromNullishOr(raw.ip) });\n};\n\n/**\n * The portable `CallableRequest` of a call, from what `firebase-functions`\n * hands over.\n */\nconst toCallableRequest = (request: Https.CallableRequest): CallableRequest.CallableRequest => ({\n auth: request.auth,\n app: request.app,\n instanceIdToken: request.instanceIdToken,\n data: request.data,\n rawRequest: toHttpServerRequest(request.rawRequest),\n});\n\n/**\n * The answer `Callable.serve` already chose for a failed call, if `cause`\n * carries one.\n */\nconst answerOf = <E>(cause: Cause.Cause<E>): CallableError.ResponseError | undefined => {\n const failure = cause.reasons.find(Cause.isFailReason)?.error;\n return failure instanceof CallableError.CallableError && failure.reason._tag === \"ResponseError\"\n ? failure.reason\n : undefined;\n};\n\n/**\n * Logs the Cause of a call that failed unexpectedly: a defect, or a failure\n * that isn't an answer. Answers and interrupts aren't logged.\n */\nconst logUnexpected =\n (message: string) =>\n <E>(cause: Cause.Cause<E>) =>\n answerOf(cause) !== undefined || Cause.hasInterruptsOnly(cause)\n ? Effect.void\n : Effect.logError(message, cause);\n\n/**\n * The `HttpsError` a failed call answers with. A failure is already the\n * answer `Callable.serve` chose. An interrupt is `cancelled` when the client\n * went away and `unavailable` otherwise. Anything else, logged in the call's\n * span by {@link logUnexpected}, is a bare `internal`.\n */\nconst toHttpsError = (cause: Cause.Cause<unknown>, aborted: boolean) => {\n const answer = answerOf(cause);\n if (answer !== undefined)\n return new Https.HttpsError(answer.code, answer.message, answer.details);\n if (Cause.hasInterruptsOnly(cause)) {\n return aborted\n ? new Https.HttpsError(\"cancelled\", \"CANCELLED\")\n : new Https.HttpsError(\"unavailable\", \"UNAVAILABLE\");\n }\n return internalError();\n};\n\n/**\n * `Callable.serve` of one Callable, with its requirements erased, since the\n * public signatures check that `live` meets them.\n */\ntype Serve = Effect.Effect<\n (request: CallableRequest.CallableRequest) => Effect.Effect<unknown, CallableError.CallableError>,\n never,\n unknown\n>;\n\n/**\n * What `onCall` returns for a Callable.\n *\n * @internal\n */\nexport type Export = Https.CallableFunction<unknown, Promise<unknown>>;\n\n/**\n * The Callable Function of `callable` in `group`, or in no group: an `onCall`\n * export that runs `Callable.serve` under the deployed name and span\n * attributes of `Callable.deployment`. It builds `live` on its own first call.\n *\n * @internal\n */\nexport const deploy = <R, E>(\n group: FunctionGroup.Any | undefined,\n callable: Callable.Any,\n live: Layer.Layer<R, E>,\n options: Https.CallableOptions = {},\n): Export => {\n const deployment = Member.call(group, callable, Callable.deployment);\n const serve = Member.call<Serve>(group, callable, Callable.serve);\n const run = Runtime.make(live, {\n span: `CallableFunction.${deployment.name}`,\n attributes: deployment.attributes,\n onBuildFailure: logUnexpected(\"The Layer of a Callable failed to build\"),\n });\n return Https.onCall<unknown, Promise<unknown>>(options, async (request, response) => {\n const signal = response?.signal;\n const callRequest = toCallableRequest(request);\n const exit = await run(\n serve.pipe(\n Effect.flatMap((run) => run(callRequest)),\n // Logged with the services of `live`, so a logger it provides is used.\n Effect.onError(logUnexpected(\"A Callable failed\")),\n ),\n signal,\n );\n if (Exit.isSuccess(exit)) return exit.value;\n throw toHttpsError(exit.cause, signal?.aborted === true);\n });\n};\n"],"mappings":";;;;;;;;;;;;;AAeA,MAAM,sBAAsB,IAAI,MAAM,WAAW,YAAY,UAAU;;;;;;;AAQvE,MAAM,uBAAuB,QAA4D;CACvF,MAAM,UAAmC,CAAC;CAC1C,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,IAAI,OAAO,GAAG;EACvD,IAAI,UAAU,KAAA,GAAW;EACzB,QAAQ,KAAK,CAAC,MAAM,MAAM,QAAQ,KAAK,IAAI,MAAM,KAAK,IAAI,IAAI,KAAK,CAAC;CACtE;CACA,MAAM,SAAS,IAAI,OAAO,YAAY;CACtC,MAAM,QAAQ,IAAI;CAClB,MAAM,OAEJ,WAAW,SAAS,WAAW,SAC3B;EAAE;EAAQ;CAAQ,IAClB;EACE;EACA;EACA,MAAM,IAAI,eAA2B,EACnC,MAAM,YAAY;GAChB,WAAW,QAAQ,KAAK;GACxB,WAAW,MAAM;EACnB,EACF,CAAC;EAED,QAAQ;CACV;CACN,OAAO,kBAAkB,QACvB,IAAI,QAAQ,GAAG,IAAI,SAAS,KAAK,IAAI,IAAI,MAAM,KAAK,cAAc,IAAI,eAAe,IAAI,CAC3F,CAAC,CAAC,OAAO,EAAE,eAAe,OAAO,cAAc,IAAI,EAAE,EAAE,CAAC;AAC1D;;;;;AAMA,MAAM,qBAAqB,aAAqE;CAC9F,MAAM,QAAQ;CACd,KAAK,QAAQ;CACb,iBAAiB,QAAQ;CACzB,MAAM,QAAQ;CACd,YAAY,oBAAoB,QAAQ,UAAU;AACpD;;;;;AAMA,MAAM,YAAe,UAAmE;CACtF,MAAM,UAAU,MAAM,QAAQ,KAAK,MAAM,YAAY,CAAC,EAAE;CACxD,OAAO,mBAAmB,cAAc,iBAAiB,QAAQ,OAAO,SAAS,kBAC7E,QAAQ,SACR,KAAA;AACN;;;;;AAMA,MAAM,iBACH,aACG,UACF,SAAS,KAAK,MAAM,KAAA,KAAa,MAAM,kBAAkB,KAAK,IAC1D,OAAO,OACP,OAAO,SAAS,SAAS,KAAK;;;;;;;AAQtC,MAAM,gBAAgB,OAA6B,YAAqB;CACtE,MAAM,SAAS,SAAS,KAAK;CAC7B,IAAI,WAAW,KAAA,GACb,OAAO,IAAI,MAAM,WAAW,OAAO,MAAM,OAAO,SAAS,OAAO,OAAO;CACzE,IAAI,MAAM,kBAAkB,KAAK,GAC/B,OAAO,UACH,IAAI,MAAM,WAAW,aAAa,WAAW,IAC7C,IAAI,MAAM,WAAW,eAAe,aAAa;CAEvD,OAAO,cAAc;AACvB;;;;;;;;AA0BA,MAAa,UACX,OACA,UACA,MACA,UAAiC,CAAC,MACvB;CACX,MAAM,aAAaA,KAAY,OAAO,UAAU,SAAS,UAAU;CACnE,MAAM,QAAQA,KAAmB,OAAO,UAAU,SAAS,KAAK;CAChE,MAAM,MAAMC,KAAa,MAAM;EAC7B,MAAM,oBAAoB,WAAW;EACrC,YAAY,WAAW;EACvB,gBAAgB,cAAc,yCAAyC;CACzE,CAAC;CACD,OAAO,MAAM,OAAkC,SAAS,OAAO,SAAS,aAAa;EACnF,MAAM,SAAS,UAAU;EACzB,MAAM,cAAc,kBAAkB,OAAO;EAC7C,MAAM,OAAO,MAAM,IACjB,MAAM,KACJ,OAAO,SAAS,QAAQ,IAAI,WAAW,CAAC,GAExC,OAAO,QAAQ,cAAc,mBAAmB,CAAC,CACnD,GACA,MACF;EACA,IAAI,KAAK,UAAU,IAAI,GAAG,OAAO,KAAK;EACtC,MAAM,aAAa,KAAK,OAAO,QAAQ,YAAY,IAAI;CACzD,CAAC;AACH"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { call } from "./member.mjs";
|
|
2
|
+
import { make } from "./runtime.mjs";
|
|
3
|
+
import { Cause, Effect, Exit, Option } from "effect";
|
|
4
|
+
import * as AdminFirestoreValue from "@visiolab/effect-firebase-admin/firestore/AdminFirestoreValue";
|
|
5
|
+
import * as Collection from "@visiolab/effect-firebase/firestore/Collection";
|
|
6
|
+
import * as DocumentSnapshot from "@visiolab/effect-firebase/firestore/DocumentSnapshot";
|
|
7
|
+
import * as DocumentTrigger from "@visiolab/effect-firebase/functions/DocumentTrigger";
|
|
8
|
+
import * as Firestore from "firebase-functions/v2/firestore";
|
|
9
|
+
// src/internal/documentTrigger.ts
|
|
10
|
+
/**
|
|
11
|
+
* The Document Trigger kind's runtime: serves one Document Trigger as an
|
|
12
|
+
* `onDocument*` export.
|
|
13
|
+
*
|
|
14
|
+
* @internal
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* The typed reference of the document an event is about, from its path
|
|
18
|
+
* relative to the database's `documents/`. A path outside the trigger's
|
|
19
|
+
* collection can't come from its own path pattern, so it is a defect.
|
|
20
|
+
*/
|
|
21
|
+
const refOf = (trigger, path) => Option.match(Collection.fromPath(trigger.collection, path), {
|
|
22
|
+
onNone: () => Effect.die(/* @__PURE__ */ new Error(`The document "${path}" is not in the collection of the trigger "${trigger.name}"`)),
|
|
23
|
+
onSome: Effect.succeed
|
|
24
|
+
});
|
|
25
|
+
/**
|
|
26
|
+
* An existing Document Snapshot at `ref` of the Admin SDK's snapshot, its
|
|
27
|
+
* data read into Firestore Values as a read does. Data that isn't a Firestore
|
|
28
|
+
* Value, such as a vector, is a defect.
|
|
29
|
+
*/
|
|
30
|
+
const existingOf = (ref, snapshot) => snapshot === void 0 ? Effect.die(/* @__PURE__ */ new Error(`The event of "${ref.path}" carries no document`)) : Effect.map(Effect.orDie(Effect.fromResult(AdminFirestoreValue.fromAdmin(snapshot.data()))), (raw) => DocumentSnapshot.existing(ref, raw));
|
|
31
|
+
/**
|
|
32
|
+
* The document of an event that hands over one, as an existing Document
|
|
33
|
+
* Snapshot: the created one, or the deleted one as it was just before.
|
|
34
|
+
*/
|
|
35
|
+
const snapshotOf = (ref, snapshot) => Effect.map(existingOf(ref, snapshot), (snapshot) => ({ snapshot }));
|
|
36
|
+
/**
|
|
37
|
+
* A Document Snapshot at `ref` of a side of a write: `Missing` for the side
|
|
38
|
+
* that doesn't exist, as the Admin SDK marks it with `exists`.
|
|
39
|
+
*/
|
|
40
|
+
const sideOf = (ref, snapshot) => snapshot.exists ? existingOf(ref, snapshot) : Effect.succeed(DocumentSnapshot.missing(ref));
|
|
41
|
+
/**
|
|
42
|
+
* Both sides of a change, each read with `side`: both existing for an update,
|
|
43
|
+
* and either one missing for a write. An event without its change is a defect.
|
|
44
|
+
*/
|
|
45
|
+
const changeOf = (side) => (ref, change) => change === void 0 ? Effect.die(/* @__PURE__ */ new Error(`The event of "${ref.path}" carries no change`)) : Effect.all({
|
|
46
|
+
before: side(ref, change.before),
|
|
47
|
+
after: side(ref, change.after)
|
|
48
|
+
});
|
|
49
|
+
/**
|
|
50
|
+
* The conversion of what an `onDocument*` provider hands over into the core
|
|
51
|
+
* event of a trigger: `read` reads the event's data at the document's typed
|
|
52
|
+
* reference, and `make` builds the core event from that and the CloudEvent's
|
|
53
|
+
* `id` and `time`.
|
|
54
|
+
*/
|
|
55
|
+
const conversion = (read, make) => (trigger, event) => Effect.flatMap(refOf(trigger, event.document), (ref) => Effect.map(read(ref, event.data), (fields) => make({
|
|
56
|
+
...fields,
|
|
57
|
+
id: event.id,
|
|
58
|
+
time: event.time
|
|
59
|
+
})));
|
|
60
|
+
const createdEvent = conversion(snapshotOf, DocumentTrigger.createdEvent);
|
|
61
|
+
const deletedEvent = conversion(snapshotOf, DocumentTrigger.deletedEvent);
|
|
62
|
+
const updatedEvent = conversion(changeOf(existingOf), DocumentTrigger.updatedEvent);
|
|
63
|
+
const writtenEvent = conversion(changeOf(sideOf), DocumentTrigger.writtenEvent);
|
|
64
|
+
/**
|
|
65
|
+
* Logs the Cause of a failed event or Layer build in the event's span.
|
|
66
|
+
*/
|
|
67
|
+
const logFailure = (message) => (cause) => Effect.logError(message, cause);
|
|
68
|
+
/**
|
|
69
|
+
* The export of `trigger` in `group`, or in no group: the `onDocument*`
|
|
70
|
+
* export of its change (`onDocumentCreated`, `onDocumentDeleted`,
|
|
71
|
+
* `onDocumentUpdated` or `onDocumentWritten`) on the `(default)` database, at
|
|
72
|
+
* the path pattern of the trigger's collection, that converts each event and
|
|
73
|
+
* runs it through `DocumentTrigger.serve`. It builds `live` on its own first
|
|
74
|
+
* event. A failed event, or a failed build, is logged and thrown, so Firebase
|
|
75
|
+
* delivers it again when `retry` is set.
|
|
76
|
+
*
|
|
77
|
+
* @internal
|
|
78
|
+
*/
|
|
79
|
+
const deploy = (group, trigger, live, options = {}) => {
|
|
80
|
+
const deployment = call(group, trigger, DocumentTrigger.deployment);
|
|
81
|
+
const serve = call(group, trigger, DocumentTrigger.serve);
|
|
82
|
+
const run = make(live, {
|
|
83
|
+
span: `DocumentTrigger.${deployment.name}`,
|
|
84
|
+
attributes: {
|
|
85
|
+
"faas.name": deployment.name,
|
|
86
|
+
"faas.trigger": "datasource"
|
|
87
|
+
},
|
|
88
|
+
onBuildFailure: logFailure("The Layer of a Document Trigger failed to build")
|
|
89
|
+
});
|
|
90
|
+
/** The raw handler that converts each event with `convert` and serves it. */
|
|
91
|
+
const handler = (convert) => async (event) => {
|
|
92
|
+
const exit = await run(serve.pipe(Effect.flatMap((handle) => Effect.flatMap(convert(trigger, event), (input) => handle(input))), Effect.onError(logFailure("A Document Trigger failed"))));
|
|
93
|
+
if (Exit.isFailure(exit)) throw Cause.squash(exit.cause);
|
|
94
|
+
};
|
|
95
|
+
const providerOptions = {
|
|
96
|
+
...options,
|
|
97
|
+
document: deployment.document,
|
|
98
|
+
database: "(default)",
|
|
99
|
+
namespace: "(default)"
|
|
100
|
+
};
|
|
101
|
+
switch (trigger.event) {
|
|
102
|
+
case "created": return Firestore.onDocumentCreated(providerOptions, handler(createdEvent));
|
|
103
|
+
case "deleted": return Firestore.onDocumentDeleted(providerOptions, handler(deletedEvent));
|
|
104
|
+
case "updated": return Firestore.onDocumentUpdated(providerOptions, handler(updatedEvent));
|
|
105
|
+
case "written": return Firestore.onDocumentWritten(providerOptions, handler(writtenEvent));
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
export { deploy };
|
|
109
|
+
|
|
110
|
+
//# sourceMappingURL=documentTrigger.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"documentTrigger.mjs","names":["Member.call","Runtime.make"],"sources":["../../src/internal/documentTrigger.ts"],"sourcesContent":["/**\n * The Document Trigger kind's runtime: serves one Document Trigger as an\n * `onDocument*` export.\n *\n * @internal\n */\nimport { Cause, Effect, Exit, type Layer, Option } from \"effect\";\nimport * as AdminFirestoreValue from \"@visiolab/effect-firebase-admin/firestore/AdminFirestoreValue\";\nimport * as Collection from \"@visiolab/effect-firebase/firestore/Collection\";\nimport * as DocumentSnapshot from \"@visiolab/effect-firebase/firestore/DocumentSnapshot\";\nimport * as DocumentTrigger from \"@visiolab/effect-firebase/functions/DocumentTrigger\";\nimport type * as FunctionGroup from \"@visiolab/effect-firebase/functions/FunctionGroup\";\nimport * as Firestore from \"firebase-functions/v2/firestore\";\nimport * as Member from \"./member.ts\";\nimport * as Runtime from \"./runtime.ts\";\n\n/**\n * The typed reference of the document an event is about, from its path\n * relative to the database's `documents/`. A path outside the trigger's\n * collection can't come from its own path pattern, so it is a defect.\n */\nconst refOf = (trigger: DocumentTrigger.Any, path: string) =>\n Option.match(Collection.fromPath(trigger.collection, path), {\n onNone: () =>\n Effect.die(\n new Error(\n `The document \"${path}\" is not in the collection of the trigger \"${trigger.name}\"`,\n ),\n ),\n onSome: Effect.succeed,\n });\n\n/**\n * An existing Document Snapshot at `ref` of the Admin SDK's snapshot, its\n * data read into Firestore Values as a read does. Data that isn't a Firestore\n * Value, such as a vector, is a defect.\n */\nconst existingOf = (\n ref: Collection.DocumentReference<Collection.Any>,\n snapshot: Firestore.DocumentSnapshot | undefined,\n): Effect.Effect<DocumentSnapshot.Existing<Collection.Any>> =>\n snapshot === undefined\n ? Effect.die(new Error(`The event of \"${ref.path}\" carries no document`))\n : Effect.map(\n Effect.orDie(Effect.fromResult(AdminFirestoreValue.fromAdmin(snapshot.data()))),\n (raw) => DocumentSnapshot.existing(ref, raw),\n );\n\n/**\n * The document of an event that hands over one, as an existing Document\n * Snapshot: the created one, or the deleted one as it was just before.\n */\nconst snapshotOf = (\n ref: Collection.DocumentReference<Collection.Any>,\n snapshot: Firestore.QueryDocumentSnapshot | undefined,\n) => Effect.map(existingOf(ref, snapshot), (snapshot) => ({ snapshot }));\n\n/**\n * A Document Snapshot at `ref` of a side of a write: `Missing` for the side\n * that doesn't exist, as the Admin SDK marks it with `exists`.\n */\nconst sideOf = (\n ref: Collection.DocumentReference<Collection.Any>,\n snapshot: Firestore.DocumentSnapshot,\n): Effect.Effect<DocumentSnapshot.DocumentSnapshot<Collection.Any>> =>\n snapshot.exists ? existingOf(ref, snapshot) : Effect.succeed(DocumentSnapshot.missing(ref));\n\n/**\n * Both sides of a change, each read with `side`: both existing for an update,\n * and either one missing for a write. An event without its change is a defect.\n */\nconst changeOf =\n <S, A>(\n side: (ref: Collection.DocumentReference<Collection.Any>, snapshot: S) => Effect.Effect<A>,\n ) =>\n (ref: Collection.DocumentReference<Collection.Any>, change: Firestore.Change<S> | undefined) =>\n change === undefined\n ? Effect.die(new Error(`The event of \"${ref.path}\" carries no change`))\n : Effect.all({ before: side(ref, change.before), after: side(ref, change.after) });\n\n/**\n * The conversion of what an `onDocument*` provider hands over into the core\n * event of a trigger: `read` reads the event's data at the document's typed\n * reference, and `make` builds the core event from that and the CloudEvent's\n * `id` and `time`.\n */\nconst conversion =\n <Data, Fields>(\n read: (ref: Collection.DocumentReference<Collection.Any>, data: Data) => Effect.Effect<Fields>,\n make: (input: Fields & { readonly id: string; readonly time: string }) => unknown,\n ) =>\n (trigger: DocumentTrigger.Any, event: Firestore.FirestoreEvent<Data>) =>\n Effect.flatMap(refOf(trigger, event.document), (ref) =>\n Effect.map(read(ref, event.data), (fields) =>\n make({ ...fields, id: event.id, time: event.time }),\n ),\n );\n\nconst createdEvent = conversion(snapshotOf, DocumentTrigger.createdEvent);\nconst deletedEvent = conversion(snapshotOf, DocumentTrigger.deletedEvent);\nconst updatedEvent = conversion(changeOf(existingOf), DocumentTrigger.updatedEvent);\nconst writtenEvent = conversion(changeOf(sideOf), DocumentTrigger.writtenEvent);\n\n/**\n * Logs the Cause of a failed event or Layer build in the event's span.\n */\nconst logFailure = (message: string) => (cause: Cause.Cause<unknown>) =>\n Effect.logError(message, cause);\n\n/**\n * `DocumentTrigger.serve` of one trigger, with its requirements erased, since\n * the public signatures check that `live` meets them.\n */\ntype Serve = Effect.Effect<(event: unknown) => Effect.Effect<void, unknown>, never, unknown>;\n\n/**\n * What an `onDocument*` provider returns, by the change it runs on.\n *\n * @internal\n */\nexport type Export =\n | ReturnType<typeof Firestore.onDocumentCreated>\n | ReturnType<typeof Firestore.onDocumentUpdated>\n | ReturnType<typeof Firestore.onDocumentWritten>;\n\n/**\n * The export of `trigger` in `group`, or in no group: the `onDocument*`\n * export of its change (`onDocumentCreated`, `onDocumentDeleted`,\n * `onDocumentUpdated` or `onDocumentWritten`) on the `(default)` database, at\n * the path pattern of the trigger's collection, that converts each event and\n * runs it through `DocumentTrigger.serve`. It builds `live` on its own first\n * event. A failed event, or a failed build, is logged and thrown, so Firebase\n * delivers it again when `retry` is set.\n *\n * @internal\n */\nexport const deploy = <R, E>(\n group: FunctionGroup.Any | undefined,\n trigger: DocumentTrigger.Any,\n live: Layer.Layer<R, E>,\n options: object = {},\n): Export => {\n const deployment = Member.call(group, trigger, DocumentTrigger.deployment);\n const serve = Member.call<Serve>(group, trigger, DocumentTrigger.serve);\n const run = Runtime.make(live, {\n span: `DocumentTrigger.${deployment.name}`,\n attributes: { \"faas.name\": deployment.name, \"faas.trigger\": \"datasource\" },\n onBuildFailure: logFailure(\"The Layer of a Document Trigger failed to build\"),\n });\n /** The raw handler that converts each event with `convert` and serves it. */\n const handler =\n <Data>(\n convert: (\n trigger: DocumentTrigger.Any,\n event: Firestore.FirestoreEvent<Data>,\n ) => Effect.Effect<unknown>,\n ) =>\n async (event: Firestore.FirestoreEvent<Data>) => {\n const exit = await run(\n serve.pipe(\n Effect.flatMap((handle) =>\n Effect.flatMap(convert(trigger, event), (input) => handle(input)),\n ),\n // Logged with the services of `live`, so a logger it provides is used.\n Effect.onError(logFailure(\"A Document Trigger failed\")),\n ),\n );\n if (Exit.isFailure(exit)) throw Cause.squash(exit.cause);\n };\n // After `options`, so a caller that passes event filters past the public\n // types can't move the trigger to another path, database or namespace.\n const providerOptions = {\n ...options,\n document: deployment.document,\n database: \"(default)\",\n namespace: \"(default)\",\n };\n switch (trigger.event) {\n case \"created\":\n return Firestore.onDocumentCreated(providerOptions, handler(createdEvent));\n case \"deleted\":\n return Firestore.onDocumentDeleted(providerOptions, handler(deletedEvent));\n case \"updated\":\n return Firestore.onDocumentUpdated(providerOptions, handler(updatedEvent));\n case \"written\":\n return Firestore.onDocumentWritten(providerOptions, handler(writtenEvent));\n }\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAqBA,MAAM,SAAS,SAA8B,SAC3C,OAAO,MAAM,WAAW,SAAS,QAAQ,YAAY,IAAI,GAAG;CAC1D,cACE,OAAO,oBACL,IAAI,MACF,iBAAiB,KAAK,6CAA6C,QAAQ,KAAK,EAClF,CACF;CACF,QAAQ,OAAO;AACjB,CAAC;;;;;;AAOH,MAAM,cACJ,KACA,aAEA,aAAa,KAAA,IACT,OAAO,oBAAI,IAAI,MAAM,iBAAiB,IAAI,KAAK,sBAAsB,CAAC,IACtE,OAAO,IACL,OAAO,MAAM,OAAO,WAAW,oBAAoB,UAAU,SAAS,KAAK,CAAC,CAAC,CAAC,IAC7E,QAAQ,iBAAiB,SAAS,KAAK,GAAG,CAC7C;;;;;AAMN,MAAM,cACJ,KACA,aACG,OAAO,IAAI,WAAW,KAAK,QAAQ,IAAI,cAAc,EAAE,SAAS,EAAE;;;;;AAMvE,MAAM,UACJ,KACA,aAEA,SAAS,SAAS,WAAW,KAAK,QAAQ,IAAI,OAAO,QAAQ,iBAAiB,QAAQ,GAAG,CAAC;;;;;AAM5F,MAAM,YAEF,UAED,KAAmD,WAClD,WAAW,KAAA,IACP,OAAO,oBAAI,IAAI,MAAM,iBAAiB,IAAI,KAAK,oBAAoB,CAAC,IACpE,OAAO,IAAI;CAAE,QAAQ,KAAK,KAAK,OAAO,MAAM;CAAG,OAAO,KAAK,KAAK,OAAO,KAAK;AAAE,CAAC;;;;;;;AAQvF,MAAM,cAEF,MACA,UAED,SAA8B,UAC7B,OAAO,QAAQ,MAAM,SAAS,MAAM,QAAQ,IAAI,QAC9C,OAAO,IAAI,KAAK,KAAK,MAAM,IAAI,IAAI,WACjC,KAAK;CAAE,GAAG;CAAQ,IAAI,MAAM;CAAI,MAAM,MAAM;AAAK,CAAC,CACpD,CACF;AAEJ,MAAM,eAAe,WAAW,YAAY,gBAAgB,YAAY;AACxE,MAAM,eAAe,WAAW,YAAY,gBAAgB,YAAY;AACxE,MAAM,eAAe,WAAW,SAAS,UAAU,GAAG,gBAAgB,YAAY;AAClF,MAAM,eAAe,WAAW,SAAS,MAAM,GAAG,gBAAgB,YAAY;;;;AAK9E,MAAM,cAAc,aAAqB,UACvC,OAAO,SAAS,SAAS,KAAK;;;;;;;;;;;;AA6BhC,MAAa,UACX,OACA,SACA,MACA,UAAkB,CAAC,MACR;CACX,MAAM,aAAaA,KAAY,OAAO,SAAS,gBAAgB,UAAU;CACzE,MAAM,QAAQA,KAAmB,OAAO,SAAS,gBAAgB,KAAK;CACtE,MAAM,MAAMC,KAAa,MAAM;EAC7B,MAAM,mBAAmB,WAAW;EACpC,YAAY;GAAE,aAAa,WAAW;GAAM,gBAAgB;EAAa;EACzE,gBAAgB,WAAW,iDAAiD;CAC9E,CAAC;;CAED,MAAM,WAEF,YAKF,OAAO,UAA0C;EAC/C,MAAM,OAAO,MAAM,IACjB,MAAM,KACJ,OAAO,SAAS,WACd,OAAO,QAAQ,QAAQ,SAAS,KAAK,IAAI,UAAU,OAAO,KAAK,CAAC,CAClE,GAEA,OAAO,QAAQ,WAAW,2BAA2B,CAAC,CACxD,CACF;EACA,IAAI,KAAK,UAAU,IAAI,GAAG,MAAM,MAAM,OAAO,KAAK,KAAK;CACzD;CAGF,MAAM,kBAAkB;EACtB,GAAG;EACH,UAAU,WAAW;EACrB,UAAU;EACV,WAAW;CACb;CACA,QAAQ,QAAQ,OAAhB;EACE,KAAK,WACH,OAAO,UAAU,kBAAkB,iBAAiB,QAAQ,YAAY,CAAC;EAC3E,KAAK,WACH,OAAO,UAAU,kBAAkB,iBAAiB,QAAQ,YAAY,CAAC;EAC3E,KAAK,WACH,OAAO,UAAU,kBAAkB,iBAAiB,QAAQ,YAAY,CAAC;EAC3E,KAAK,WACH,OAAO,UAAU,kBAAkB,iBAAiB,QAAQ,YAAY,CAAC;CAC7E;AACF"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { deploy as deploy$1 } from "./callable.mjs";
|
|
2
|
+
import { deploy as deploy$2 } from "./documentTrigger.mjs";
|
|
3
|
+
// src/internal/kinds.ts
|
|
4
|
+
/**
|
|
5
|
+
* The export of `fn` in `group`, or in no group, from its kind's runtime.
|
|
6
|
+
* `options` are the member's own, typed by kind in the public signatures.
|
|
7
|
+
*
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
const deploy = (group, fn, live, options) => {
|
|
11
|
+
switch (fn.kind) {
|
|
12
|
+
case "Callable": return deploy$1(group, fn, live, options);
|
|
13
|
+
case "DocumentTrigger": return deploy$2(group, fn, live, options);
|
|
14
|
+
default: throw new Error(`Functions can't deploy "${fn.name}": its kind "${fn.kind}" is unknown`);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
export { deploy };
|
|
18
|
+
|
|
19
|
+
//# sourceMappingURL=kinds.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kinds.mjs","names":["callable.deploy","documentTrigger.deploy"],"sources":["../../src/internal/kinds.ts"],"sourcesContent":["/**\n * Where `Functions.make` and `Functions.group` dispatch on a Function's kind.\n * A new kind adds its runtime's `deploy` here, and its types to the\n * kind-dispatched types of `Functions.ts` (`Export`, `Options`, `Services`).\n *\n * @internal\n */\nimport type { Layer } from \"effect\";\nimport type * as Callable from \"@visiolab/effect-firebase/functions/Callable\";\nimport type * as DocumentTrigger from \"@visiolab/effect-firebase/functions/DocumentTrigger\";\nimport type * as FirebaseFunction from \"@visiolab/effect-firebase/functions/FirebaseFunction\";\nimport type * as FunctionGroup from \"@visiolab/effect-firebase/functions/FunctionGroup\";\nimport * as callable from \"./callable.ts\";\nimport * as documentTrigger from \"./documentTrigger.ts\";\n\n/**\n * The export of `fn` in `group`, or in no group, from its kind's runtime.\n * `options` are the member's own, typed by kind in the public signatures.\n *\n * @internal\n */\nexport const deploy = <R, E>(\n group: FunctionGroup.Any | undefined,\n fn: FirebaseFunction.Any,\n live: Layer.Layer<R, E>,\n options: object | undefined,\n): unknown => {\n switch (fn.kind) {\n case \"Callable\":\n return callable.deploy(group, fn as Callable.Any, live, options);\n case \"DocumentTrigger\":\n return documentTrigger.deploy(group, fn as DocumentTrigger.Any, live, options);\n default:\n throw new Error(`Functions can't deploy \"${fn.name}\": its kind \"${fn.kind}\" is unknown`);\n }\n};\n"],"mappings":";;;;;;;;;AAqBA,MAAa,UACX,OACA,IACA,MACA,YACY;CACZ,QAAQ,GAAG,MAAX;EACE,KAAK,YACH,OAAOA,SAAgB,OAAO,IAAoB,MAAM,OAAO;EACjE,KAAK,mBACH,OAAOC,SAAuB,OAAO,IAA2B,MAAM,OAAO;EAC/E,SACE,MAAM,IAAI,MAAM,2BAA2B,GAAG,KAAK,eAAe,GAAG,KAAK,aAAa;CAC3F;AACF"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// src/internal/member.ts
|
|
2
|
+
/**
|
|
3
|
+
* Calls `api` with `fn` alone, or with `group` and the name of `fn` when it is
|
|
4
|
+
* a member of `group`. The kind's public signatures already checked that `fn`
|
|
5
|
+
* is of the kind `api` takes, so its arguments are erased.
|
|
6
|
+
*
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
const call = (group, fn, api) => group === void 0 ? api(fn) : api(group, fn.name);
|
|
10
|
+
export { call };
|
|
11
|
+
|
|
12
|
+
//# sourceMappingURL=member.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"member.mjs","names":[],"sources":["../../src/internal/member.ts"],"sourcesContent":["/**\n * How a kind's runtime calls the core functions that take a Function alone or\n * a Function Group and a member's name, such as `deployment` and `serve`.\n *\n * @internal\n */\nimport type * as FirebaseFunction from \"@visiolab/effect-firebase/functions/FirebaseFunction\";\nimport type * as FunctionGroup from \"@visiolab/effect-firebase/functions/FunctionGroup\";\n\n/**\n * Calls `api` with `fn` alone, or with `group` and the name of `fn` when it is\n * a member of `group`. The kind's public signatures already checked that `fn`\n * is of the kind `api` takes, so its arguments are erased.\n *\n * @internal\n */\nexport const call = <A>(\n group: FunctionGroup.Any | undefined,\n fn: FirebaseFunction.Any,\n api: (...args: ReadonlyArray<never>) => A,\n): A => (group === undefined ? api(fn as never) : api(group as never, fn.name as never));\n"],"mappings":";;;;;;;;AAgBA,MAAa,QACX,OACA,IACA,QACO,UAAU,KAAA,IAAY,IAAI,EAAW,IAAI,IAAI,OAAgB,GAAG,IAAa"}
|