@ effect- firebase/ admin (英语)
Firebase Admin SDK集成,用于效应Firebase. 规定 a FirestoreService 执行和云函数触发效果包.
安装
npm install @effect-firebase/admin effect-firebase effect
npm install firebase-admin firebase-functions express
设置
从一层创建运行时间并将其传递给函数处理器 :
import { initializeApp } from 'firebase-admin/app';
import { Admin, FunctionsRuntime } from '@effect-firebase/admin';
const runtime = FunctionsRuntime.make(Admin.layer({ app: initializeApp() }));
Admin.layer 接受 { app }, (中文(简体) ), { firestore },或者没有参数(使用/初始化默认应用). 它规定 FirestoreService 并接通了"云寻". FunctionsRuntime.Default(app?) 是一个缩写 make(Admin.layer(...))。 。 。 . make 处理运行时间 SIGINT编号 :SIGTERM。 。 。 .
存储器只是需要的效果 FirestoreService这样 yield* PostRepository 在操作员内部工作,却不提供其他东西.
云函数
HTTP (英语:onRequest(中文(简体) )
import { onRequestEffect } from '@effect-firebase/admin';
import { Query } from 'effect-firebase';
export const myFunction = onRequestEffect({ runtime }, (request, response) =>
Effect.gen(function* () {
const repo = yield* PostRepository;
response.json({ posts: yield* repo.query(Query.limit(20)) });
}),
);
可调用( N)onCall(中文(简体) )
import { onCallEffect } from '@effect-firebase/admin';
import { Schema } from 'effect';
const Input = Schema.Struct({ title: Schema.String, content: Schema.String });
const Output = Schema.Struct({ postId: Schema.String });
export const createPost = onCallEffect(
{ runtime, inputSchema: Input, outputSchema: Output },
(input) =>
Effect.gen(function* () {
const repo = yield* PostRepository;
const postId = yield* repo.add({ ...input, status: 'draft' });
return { postId };
}),
);
何时 inputSchema 财务报告和审定财务报表 outputSchema 自动处理解码和编码.
何时 inputSchema 设置, 处理器收到 (input, context)· ; context 曝光情况 auth, (中文(简体) ), app, (中文(简体) ), rawRequest, (中文(简体) ), instanceIdToken, (中文(简体) ), acceptsStreaming 当
客户端已调用 httpsCallable(...).stream(),则 response 手动对象 sendChunk 打电话 无 inputSchema,处理器接收原始 (request, response)。 。 。 .
onRequestEffect 采取同样做法: bodySchema 处理器收到 (body, request, response); 与 responseSchema 它返回编码和发送的值(状态) successStatus默认值 200) (中文(简体) ).
可流调( N)onCall + 键 sendChunk(中文(简体) )
onCallStreamEffect 需要一个处理器返回 a Stream。每个元素的编码为:
chunkSchema 并发送到客户端 response.sendChunk 因为它是生产的。 当
溪流完成, 收集的块作为可调用的最后块返回 data,所以一个
调用此函数的客户端 .stream() 仍然得到全部结果。 溪流是
当客户端断开时中断,包括飞行中拉出.
import { onCallStreamEffect } from '@effect-firebase/admin';
import { Schema, Stream } from 'effect';
import { LanguageModel } from 'effect/ai';
export const chat = onCallStreamEffect(
{
runtime,
timeoutSeconds: 540,
inputSchema: Schema.Struct({ prompt: Schema.String }),
chunkSchema: Schema.Struct({ delta: Schema.String }),
},
(input) =>
LanguageModel.streamText({ prompt: input.prompt }).pipe(
Stream.filter((part) => part.type === 'text-delta'),
Stream.map((part) => ({ delta: part.delta })),
Stream.provide(AnthropicModel), // any Effect AI provider layer
),
);
客户端 :
const { stream, data } = await httpsCallable(functions, 'chat').stream({
prompt,
});
for await (const chunk of stream) append(chunk.delta);
const allChunks = await data; // ReadonlyArray<{ delta: string }>
流式调用需要 2 gen 云函数; set timeoutSeconds 足够高
历代相传.
测试可调用设备
CallableFunction.stream() 火场的功能还是根茎,所以 @effect-firebase/admin
以假充名的船舶 CallableResponse数字 :
import {
runCallable,
streamCallable,
makeCallableRequest,
} from '@effect-firebase/admin';
const { stream, data, abort } = streamCallable(chat, { prompt: 'hi' });
for await (const chunk of stream) chunks.push(chunk); // what the client would see
expect(await data).toEqual(chunks); // final `data`
abort(); // simulate a client disconnect
await runCallable(chat, { prompt: 'hi' }); // non-streaming client, final `data` only
// full control over auth/app/rawRequest
streamCallable(
chat,
makeCallableRequest(
{ prompt: 'hi' },
{ auth: { uid: 'u1', token, rawToken: 'test-token' } },
),
);
火炉触发器
import {
onDocumentCreatedEffect,
onDocumentUpdatedEffect,
onDocumentDeletedEffect,
onDocumentWrittenEffect,
} from '@effect-firebase/admin';
export const onPostCreated = onDocumentCreatedEffect(
{ runtime, document: 'posts/{postId}', schema: PostModel, idField: 'id' },
(post) => Effect.log(`Created: ${post.id}`),
);
Firestore 触发处理器必须返回 Effect<void, never, R>: 自己处理或记录每个故障. onDocumentUpdatedEffect 通过 a TypedChange<A> (单位:千美元){ before, after }, (中文(简体) ), onDocumentWrittenEffect (单位:千美元) TypedWrittenChange<A> (单位:千美元){ before: Option<A>, after: Option<A> }) (中文(简体) ). 每个触发器也有一个 ...WithAuthContextEffect 变体(例如:. onDocumentCreatedWithAuthContextEffect(其操作者收到) (event, data)。 。 。 .
Pub/Sub (中文(简体) )onMessagePublished(中文(简体) )
import { onMessagePublishedEffect } from '@effect-firebase/admin';
const MessageSchema = Schema.Struct({ userId: Schema.String });
export const onMessage = onMessagePublishedEffect(
{ runtime, topic: 'my-topic', messageSchema: MessageSchema },
(message) => Effect.log(`Received for user: ${message.userId}`),
);
云层任务( O)onTaskDispatched(中文(简体) )
import { onTaskDispatchedEffect } from '@effect-firebase/admin';
const TaskSchema = Schema.Struct({ email: Schema.String });
export const processEmail = onTaskDispatchedEffect(
{ runtime, retryConfig: { maxAttempts: 5 }, schema: TaskSchema },
(task) => Effect.log(`Sending to: ${task.email}`),
);
计划onSchedule(中文(简体) )
import { onScheduleEffect } from '@effect-firebase/admin';
export const cleanup = onScheduleEffect(
{ runtime, schedule: 'every 24 hours' },
(event) => Effect.log(`Running cleanup job: ${event.jobName}`),
);
设置出错( E)onSetupError(中文(简体) )
每个包装器解码输入的数据,并用你的计划编码输出的数据
声明 当失败时——调用者发送的数据不匹配 inputSchema页:1
文档不匹配 schema - 包装器升起一个 FunctionSetupError 携带
页:1 phase 失败( e)decode-input, (中文(简体) ), encode-output, (中文(简体) ), decode-body, (中文(简体) ),
encode-response, (中文(简体) ), decode-document, (中文(简体) ), decode-message, (中文(简体) ), decode-task)和
基础失败, 如 cause (通常为a) SchemaError; 平原 Error 当一个pub/Sub时
有效有效载荷为JSON). FunctionSetupError 财务报告和审定财务报表 isFunctionSetupError 导出.
过 onSetupError 以回收代替默认:
import { onCallEffect } from '@effect-firebase/admin';
import { HttpsError } from 'firebase-functions/https';
export const createPost = onCallEffect(
{
runtime,
inputSchema: Input,
outputSchema: Output,
onSetupError: (error, request) =>
Effect.fail(new HttpsError('invalid-argument', error.cause.message)),
},
(input, context) => handle(input, context),
);
钩子接收包装器的本地参数, 所以一个 HTTP 处理器可以自己写 反应和触发器可以检查事件:
export const onPostCreated = onDocumentCreatedEffect(
{
runtime,
document: 'posts/{postId}',
schema: PostModel,
onSetupError: (error, event) =>
Effect.logWarning(`Skipping malformed post ${event.params.postId}`),
},
(post) => handle(post),
);
默认时间 onSetupError 省略:
| 包装 | 无效的数据 | 编码失败 |
|---|---|---|
onCallEffect | HttpsError('invalid-argument', ...) | HttpsError('internal') |
onCallStreamEffect | HttpsError('invalid-argument', ...) | HttpsError('internal') |
onRequestEffect | 400 { error: 'Invalid request body' } | 500 |
| 火炉 / Pub/Sub | 已登录缺陷( 无重试) | —— 说 |
| 任务 | 已登记缺陷,重推(重审) | —— 说 |
onCallStreamEffect 不暴露一个 onSetupError 选项,因此其上行是无条件的;其他包装则允许 onSetupError 覆盖这些默认值 .
onCallEffect 财务报告和审定财务报表 onCallStreamEffect 还宣传任何 HttpsError 处理器本身失败到
客户端的代码和信件完整,所以 Effect.catchTag(...) 链条结束于
Effect.fail(new HttpsError(...)) 以书面形式工作.
回收只覆盖边界 。 您的处理器升起的错误从未经过
onSetupError 包括: FunctionSetupError 你养活自己 所以两个留下来
可以区分.
预期拒绝和缺陷记录
单位 onCallEffect, (中文(简体) ), onCallStreamEffect 财务报告和审定财务报表 onRequestEffect,一个可以逃出
处理器被记录为缺陷,除非它是预期的拒绝: HttpsError,或任何
带有 Effect 注释的错误 ErrorReporter.ignore (《公约》)
HttpApiError.BadRequest 并使用朋友. onRequestEffect 仍然响应 500; 仅
日志行被跳过 。 触发包装符( Firestore、 Pub/ Sub、 任务、 时间表) 日志
逃出错误 。 任务和计划处理器在日志后重播, 所以重试
配置适用。 对于可调用和 HTTP 处理器,请注释您自己的错误以保存它们
出自缺陷日志, 但仍无法通过调用 :
import { Data, ErrorReporter } from 'effect';
class RejectedError extends Data.TaggedError('RejectedError')<{
readonly reason: string;
}> {
readonly [ErrorReporter.ignore] = true;
}
云记录
Admin.layer 自动将默认效果日志替换为将结构化日志写入 Cloud 日志的日志 :
Effect.gen(function* () {
yield* Effect.log('info message');
yield* Effect.logError('error message');
}).pipe(Effect.provide(Admin.layer({ app: initializeApp() })));
跟踪的平地物体成为条目 jsonPayload,如同 firebase-functions自己的记录器。 日志说明、日志长度和原因合并为同一有效载荷:
Effect.logError('Could not add order', cause, { partnerId }).pipe(
Effect.annotateLogs({ orderId }),
Effect.withLogSpan('checkout'),
);
// jsonPayload: { orderId, partnerId, logSpans: { checkout: 12 }, cause: '<Cause.pretty>' }
- 在密钥相撞时,明确的后导对象胜过说明.
- 当原因倒塌到一个
Error,其堆栈被附加到message,因此错误 报告分组 真实错误 而不是内部合成的堆栈firebase-functions其它原因都印得漂亮. - 有效载荷值为 JSON 安全, 并带有效果
Formatter.formatJson(大写,错误,循环引用,Redacted); 仍然无法序列化的值被替换为'[Unserializable]'。 。 。 .
伐木机也可以单独使用,作为 Logger.cloudConsole 层.
解决问题
Failed to initialize Google Cloud Firestore client with the available credentials 通常由多个安装的复制件造成 firebase-admin或加以去除(pnpm why firebase-admin)或直接通过 Firestore 实例:
import { getFirestore } from 'firebase-admin/firestore';
const db = getFirestore(initializeApp());
const runtime = FunctionsRuntime.make(Admin.layer({ firestore: db }));
许可证
麻省理工学院