哈勃萨克
背包是你们打包的包 到处都是.
每个新的 效果 项目从重建开始 同样的事情: 环绕Resend的电子邮件服务, 失败的特性标记 打开,一个有适当追踪跨度的条纹客户端,一个可持久性的限速器 对象, CPF 用真实的检查数字验证 。 哈勃萨克就是那个密码.. 从生产应用中提取,写成平庸的效果 从尾到尾, 然后装入一个包里 这样你就不会写两次了.
所有东西都是打字效果服务. 无松动函数,无
未类型错误, 没有全局配置 。 每个能力都是 Context.Service 与
标记错误、追踪跨度和a Layer 你像任何其他人一样作曲
包括配置、服务、测试层.
只导入您使用的 . 每个模块都是独立的子路径导出,
和重 SDK (Stripe, Resend, posthog- js,...) 是可选的对等点
依赖性:如果从未导入 /stripe你从不安装花脸.
bun add @ayronforge/haversack effect
Haversack是建立在效果4上(effect@^4.0.0) (中文(简体) ).
里面的东西
| 模块 | 它给你什么 |
|---|---|
/schema | Schema 构件: URL、 弹壳、 电话号码、 巴西文件( CPF、 CNPJ、 CEP) |
/email | 以响应- 电子邮件渲染和联系同步方式恢复后端的电子邮件服务 |
/posthog | 失败打开服务器解析和旗子, 加上官方 PostHog 提供者的反键旗政策 |
/stripe | 装有操作跟踪跨度和网签校验的SDK包装 |
/auth/workos, (中文(简体) ), /auth/clerk | Auth SDK 客户端层,带有已编辑的配置和标记出错 |
/cf | 工人原始:限速器,分布式锁,队列,分析引擎,R2,工作流适配器 |
/contracts, (中文(简体) ), /aws | 采用R2和S3的供应商中立布洛布存储端口 |
/testing | 测试助手:服务跑者、执行者、SDK分枝 |
/errors, (中文(简体) ), /utils | 标记错误原始、 错误正常化和纯助手 |
示马斯
可重复使用的效果Schema 构件,并带有犬类存储值和 单独的演示文稿格式.
import { Schema } from "effect";
import {
CnpjFromString,
CpfFromString,
EmailAddressFromString,
EndpointUrlFromString,
formatCnpj,
formatCpf,
PhoneNumberFromString,
SlugFromString,
UuidFromString,
} from "@ayronforge/haversack/schema";
const cpf = Schema.decodeUnknownSync(CpfFromString)("529.982.247-25");
// "52998224725" — canonical digits in both decoded and encoded forms
formatCpf(cpf); // "529.982.247-25"
const cnpj = Schema.decodeUnknownSync(CnpjFromString)("11.222.333/0001-81");
// "11222333000181" — canonical digits in both decoded and encoded forms
formatCnpj(cnpj); // "11.222.333/0001-81"
const phone = Schema.decodeUnknownSync(PhoneNumberFromString({ defaultCountry: "BR" }))(
"(11) 98765-4321",
);
// "+5511987654321" — canonical E.164
const email = Schema.decodeUnknownSync(EmailAddressFromString())("[email protected]");
// "[email protected]" — domain normalized, local part preserved by default
const host = Schema.decodeUnknownSync(EndpointUrlFromString)("https://api.example.com/");
// "https://api.example.com" — normalized, rejects query strings and fragments
const slug = Schema.decodeUnknownSync(SlugFromString)("Café com Leite!");
// "cafe-com-leite"
const uuid = Schema.decodeUnknownSync(UuidFromString)("0198A3FC-9DB1-7BD5-8A1E-2F6A1F4C9D10");
// "0198a3fc-9db1-7bd5-8a1e-2f6a1f4c9d10" — strict, canonical UUID
CepLookup.layerViaCep({ fetch }) 解析已解析 Cep 无
抓取全球I/O并区分未找到、无法找到和缺陷
回应. PhoneNumberFromString 描述国际或明确
通过 libphone number-js 进行国家范围输入.
电子邮件
带有Resend适配器的提供者端口. 发送预发的 HTML 或响应电子邮件 元素,并同步联系人到段落和主题.
import { Effect, Layer, Redacted } from "effect";
import { EmailConfig, EmailLive, EmailService } from "@ayronforge/haversack/email";
const config = EmailConfig.layer({
apiKey: Redacted.make(process.env.RESEND_API_KEY!),
from: "Acme <[email protected]>",
});
const program = Effect.gen(function* () {
const email = yield* EmailService;
yield* email.send({
to: "[email protected]",
subject: "Welcome",
html: "<p>Hello!</p>",
idempotencyKey: "welcome-user-1",
});
}).pipe(Effect.provide(EmailLive.pipe(Layer.provide(config))));
邮箱
服务器端抓取和特性旗在没有 SDK 的情况下运行并工作
工人们 服务器发送和评价失败打开:失败日志和倒计时
而不是打破一个请求。 运输是效应
FetchHttpClient.Fetch 引用( 默认为 globalThis.fetch;测试和
主机给自己注入 Effect.provideService(FetchHttpClient.Fetch, fn)。 。 。 .
import { Effect, Layer, Redacted } from "effect";
import { FeatureFlags, PostHogAnalytics, PostHogConfig } from "@ayronforge/haversack/posthog";
const config = PostHogConfig.layer({
projectToken: Redacted.make(process.env.POSTHOG_TOKEN!),
});
const program = Effect.gen(function* () {
const analytics = yield* PostHogAnalytics;
yield* analytics.track({ event: "signup", distinctId: "user_1" });
const flags = yield* FeatureFlags;
return yield* flags.isEnabled(
{ key: "new-checkout", fallback: false },
{ distinctId: "user_1" },
);
}).pipe(
Effect.provide(PostHogAnalytics.layer.pipe(Layer.provide(config))),
Effect.provide(FeatureFlags.layer.pipe(Layer.provide(config))),
);
反应地物旗政策在 @ayronforge/haversack/posthog/react 财务报告和审定财务报表
与官员直接作曲 @posthog/react 供养. useFeatureFlag
通过订阅 useSyncExternalStore· ; FeatureGate 添加已键入倒计时
在未拥有SDK初始化、捕获或身份的情况下进行渲染.
条纹
每个呼叫命名的 SDK 包装器: 操作成为追踪
间距( n)stripe.checkout.sessions.create)和错误元数据.
import { Effect, Layer, Redacted } from "effect";
import { StripeClient, StripeConfig } from "@ayronforge/haversack/stripe";
const config = StripeConfig.layer({
secretKey: Redacted.make(process.env.STRIPE_SECRET_KEY!),
webhookSecret: Redacted.make(process.env.STRIPE_WEBHOOK_SECRET!),
});
const program = Effect.gen(function* () {
const stripe = yield* StripeClient;
const session = yield* stripe.use("checkout.sessions.create", (sdk) =>
sdk.checkout.sessions.create({ mode: "subscription", line_items: [/* ... */] }),
);
return session.url;
}).pipe(Effect.provide(StripeClient.layer.pipe(Layer.provide(config))));
verifyWebhook(payload, signature) 用标签验证 webhook 签名
错误. makeStripeClientFromSdk 是测试中注射假SDK的接缝.
认证
WorkOS 的瘦客户端层( T)/auth/workos)和办事员(/auth/clerk, (中文(简体) ),
遵循相同的提供者模式: 配置已编辑的密钥, use 与
间距,标记错误。 办事员额外暴露 useWebhook 用于核实
网络事件.
云雾花
工人原始人,所有的约束导向——你通过明确的约束 图书馆从不读取一个全局性环境.
RequestRateLimiter——固定窗口和标志盒过关effect/persistence,与layerMemory用于试验和layerDurableObject(namespace)用于跨同位素限制。 命名空间点 执行RateLimiterRpc; 应用程序 拥有坚持,迁徙和清理. 无法打开商店故障 .DistributedLock编号 :withLock(key, effect)—— 说acquireUseRelease生命周期 执行打字租赁RPC合同.makeQueueClientService(queue)——队列制作人出版 Cloudflare's 百通批发.AnalyticsEngine——带有Schema-decode的严格分析引擎SQL API 作行相会. 未成功的答复仍然是输入的查询错误;库确实如此 而不是从响应文本中推断出没有记录的 Cloudflare 错误语义 。 请求通过FetchHttpClient.Fetch引用,所以没有捕获到全局获取 .makeR2BlobStorageLayer(bucket)编号 :R2BlobPresignerLive-布洛布存储器 与R2的合同(见下文合同).@ayronforge/haversack/cf/workflow—— 说makeCloudflareWorkflowEngineLayer({ workflow, step })适应effect/workflow工作流程:活动映射到step.do复试,耐用延后到sendEvent编号 :waitForEvent, (中文(简体) ), 持久时钟到step.sleep。 。 。 .
合同和AWS
@ayronforge/haversack/contracts 定义供应商中立端口——目前
BlobStorage (产出/物品/删除/存在/清单)和 BlobPresigner守则
端口; 选择每次运行的时间 :
import { Effect } from "effect";
import { BlobStorage } from "@ayronforge/haversack/contracts";
import { makeR2BlobStorageLayer } from "@ayronforge/haversack/cf";
import { S3BlobStorageLive, S3Config } from "@ayronforge/haversack/aws";
const program = Effect.gen(function* () {
const storage = yield* BlobStorage;
yield* storage.put("reports/q3.pdf", bytes, { contentType: "application/pdf" });
const report = yield* storage.get("reports/q3.pdf", { maxBytes: 10_000_000 });
});
// In a Worker: Effect.provide(program, makeR2BlobStorageLayer(env.BUCKET))
// Elsewhere: Effect.provide(program, S3BlobStorageLive) + S3Config.layer({ ... })
BlobPresigner.presignPut 签名 Content-Type 当你经过的时候
contentLength准确的体型 浏览器发送 Content-Length
自动,所以上传一个身体是其它大小的 被拒绝
在写入前存储 :
const url = yield* presigner.presignPut({
key: "uploads/avatar.png",
contentType: "image/png",
contentLength: file.size,
expiresInSeconds: 600,
});
测试
runWithService(tag, layer)- 以允诺为主的选手 在测试中不重复提供/运行锅炉板.testExecutionContext()- 一个假的"云花"ExecutionContext谁drainWaitUntil()等待着火和遗忘的工作.testStub<T>(partial)- 假冒SDK客户被制裁的逃生舱门 在测试。 永远不要在生产代码中使用它.applyTestEnvDefaults(record)- 补缺process.env键.
环境配置
Haversack故意不装任何舱道 使用
@ayronforge/envil 用于类型安全
环境验证并将解码值输入配置层.
捐款
bun install
bun run typecheck
bun test
bun run build # bundles with tsdown and smoke-tests every entrypoint
欢迎提出问题和拉动请求。 如果你要加入一个新的整合,
遵循房子的风格: Context.Service 带有配置
层、有标记的错误和追踪跨度 —— 和多供应商能力
端口进入 /contracts 每个供应商执行一次.
许可证
麻省理工学院