Alchemy 2.0.0-beta.59 命名对齐重构解析One Namespace、One Binding 与最小权限绑定的统一约定【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3codeAlchemyalchemy-run/alchemy仓库内源码位于 .repos/alchemy-effect在 v2.0.0-beta.59 中完成了一次影响面横跨约 900 个符号、约 1200 个文件的命名对齐重构每个 Cloudflare 服务成为真正的命名空间AWS 与 Cloudflare 的绑定统一为{Verb}{Resource}直接调用约定双层Binding.Policy设计被折叠进单个由运行时守卫__ALCHEMY_RUNTIME__控制的 Layer。阅读本文后你将掌握 beta.59 的全部破坏性变更清单、每类迁移的 diff 对照、*Binding与*Http两种传输层实现的选择逻辑以及部署期最小权限凭证IAM 语句 /AccountApiToken背后的源码级原理。版本定位beta.58 铺垫后的对齐发布beta.59 是 beta.58 所铺垫的对齐发布alignment release本质上几乎没有新行为而是一次协调一致的改名见 CHANGELOG 中 v2.0.0-beta.59 条目。其核心目的是让 API 在 AWS 与 Cloudflare 两侧读起来完全一致用户基本可以通过查找替换的方式完成迁移。官方发布说明明确给出了四条破坏性变更主线来源.repos/alchemy-effect/website/src/content/docs/blog/2026-06-27-beta-59.md变更维度旧写法新写法Cloudflare 服务命名空间化Cloudflare.KVNamespace、Cloudflare.WorkerCloudflare.KV.Namespace、Cloudflare.Workers.Worker常用符号在根路径保留 re-exportAiGateway/AiSearch/AiSecurity合并Cloudflare.AiGatewayCloudflare.AI.Gateway绑定可直接调用、命名空间化、最小权限R2Bucket.bind(b)、DynamoDB.GetItem.bind(t)Cloudflare.R2.ReadBucket(b)、AWS.DynamoDB.GetItem(t)Binding.Policy移除部署期 wiring 分散在独立 Policy Layer折叠进单个 Layer由if (!globalThis.__ALCHEMY_RUNTIME__)守卫Impl Layer 改名*Live*BindingCloudflare 原生 Worker 绑定或*HttpHTTP 客户端绑定AWS 全部 Cloudflare HTTP 实现事件源签名Queues.messages(q).subscribe(fn)Queues.consumeQueueMessages(q, fn)Worker-only 绑定改为字符串 idImages({ name: X })Images(X)原生层去掉*Live/*Layer后缀Artifacts 命名Artifacts.Store/ReadWriteStoreArtifacts.Namespace/ReadWriteNamespace另有Read*/Write*以下逐节展开每一类变更的迁移方法与底层实现。每个 Cloudflare 服务都是一个命名空间在 beta.59 之前Cloudflare/index.ts是扁平化的export *所有符号直接挂在Cloudflare.*下且每个名字都内嵌了冗余的服务前缀Cloudflare.KVNamespace、Cloudflare.R2Bucket、Cloudflare.D1Database。资源移入各自的服务命名空间新结构完全对齐 AWS 的标准组织方式——每个服务一个export * as Service并从符号名中剥掉冗余前缀。这一点在源码中有直接印证仓库内的 Cloudflare/index.ts 现在以export * as Access、export * as AI、export * as Account、export * as AnalyticsEngine等逐服务命名空间导出同时保留少量根级export *如CloudflareEnvironment、Fetcher、Providers。迁移 diff 如下- const kv yield* Cloudflare.KVNamespace(Sessions); - const bucket yield* Cloudflare.R2Bucket(Assets); - const db yield* Cloudflare.D1Database(App); const kv yield* Cloudflare.KV.Namespace(Sessions); const bucket yield* Cloudflare.R2.Bucket(Assets); const db yield* Cloudflare.D1.Database(App);Worker同样下移到Cloudflare.Workers下- export default Cloudflare.Worker(Worker, { /* ... */ }); export default Cloudflare.Workers.Worker(Worker, { /* ... */ });常用构建块保留在根路径为了不让日常开发路径变长常用构建块在根路径做了 re-export以下写法全部继续可用、无需命名空间前缀yield* Cloudflare.Worker(Worker, { /* ... */ }); yield* Cloudflare.Container(Sandbox, { /* ... */ }); yield* Cloudflare.DurableObject(Counter, { /* ... */ }); yield* Cloudflare.Workflow(Job, { /* ... */ }); yield* Cloudflare.RateLimit(Limiter, { /* ... */ }); Cloudflare.cron(0 * * * *, handler);其余能力一律通过各自命名空间访问。DurableObjectNamespace→DurableObjectDurable Object 基类去掉Namespace后缀现在就叫DurableObject- export class Counter extends Cloudflare.DurableObjectNamespaceCounter()( export class Counter extends Cloudflare.DurableObjectCounter()( Counter, /* ... */ ) {}目录调整与新的WebSocket类型为配合命名空间化若干目录被重塑Container/→Containers/、Queue→Queues工作流代码移入Workflows/源码目录可见 Cloudflare/Workflows。同时新增了Cloudflare.WebSocket类型用于为可休眠hibernatable的 WebSocket 处理器做类型标注。Ai*系列合并为单一AI命名空间原本分散在三个目录的 AI 产品面——AiGateway、AiSearch、AiSecurity——实际上是同一个产品表面beta.59 将它们合并为单一的Cloudflare.AI命名空间。仓库源码中 Cloudflare/AI 目录下已经可以直接看到Gateway.ts、Search.ts、Dataset.ts、SearchInstance.ts、CustomTopics.ts、Evaluation.ts等文件- const gateway yield* Cloudflare.AiGateway(Gateway); - const search yield* Cloudflare.AiSearch(Search); const gateway yield* Cloudflare.AI.Gateway(Gateway); const search yield* Cloudflare.AI.Search(Search);合并过程中符号同步去掉冗余的Ai前缀AiGatewayDataset→AI.Dataset、AiSearchInstance→AI.SearchInstance、AiSecuritySettings→AI.SecuritySettings。统一的绑定约定{Verb}{Resource}直接调用这是本次发布的核心。AWS 与 Cloudflare 的绑定现在读起来完全一致能力capability是名词由动词它授予的访问级别加前缀直接调用.bind方法彻底消失。- const get yield* AWS.DynamoDB.GetItem.bind(table); - const bucket yield* Cloudflare.R2Bucket.bind(Bucket); const get yield* AWS.DynamoDB.GetItem(table); const bucket yield* Cloudflare.R2.ReadWriteBucket(Bucket);在底层 API 区分访问级别的地方能力被拆成Read/Write/ReadWrite三种以便按需请求最小权限并且动词放在名字最前面yield* Cloudflare.KV.ReadNamespace(Sessions); // get / list yield* Cloudflare.KV.WriteNamespace(Sessions); // put / delete yield* Cloudflare.KV.ReadWriteNamespace(Sessions); // both每个能力都是契约 双实现 Layer从源码看绑定被拆成两半详见 binding.mdx。声明侧是一个Binding.Service——可调用的 Context 标签只命名能力、不承诺如何被满足。例如 Cloudflare/KV/ReadNamespace.ts 中export interface ReadNamespace extends Binding.Service ReadNamespace, Cloudflare.KV.ReadNamespace, (namespace: Namespace) Effect.EffectReadNamespaceClient {} export const ReadNamespace Binding.ServiceReadNamespace( Cloudflare.KV.ReadNamespace, );同一份接口下的ReadNamespaceClient暴露了raw、get支持 text/json/arrayBuffer/stream 与批量 key 重载、getWithMetadata、list等完整客户端方法。实现侧则是任意满足该契约的 LayerCloudflare 为每个能力提供两个可互换实现*Binding—— 原生Cloudflare Worker 绑定运行时从env读取如env.BUCKET*Http——受限 HTTP 客户端可在任何地方运行包括没有原生绑定的 Container 进程。// 原生 Worker 绑定 .pipe(Effect.provide(Cloudflare.R2.ReadWriteBucketBinding)) // 受限 HTTP token —— 同一客户端随处可跑 .pipe(Effect.provide(Cloudflare.R2.ReadWriteBucketHttp))两种传输在各自的世界里做的是同一件事客户端接口完全一致——通过原生绑定读桶的 Worker与通过 token 读同一桶的 Container代码一模一样AWS*Http在 Lambda 的 IAM role 上生成最小权限的IAM policy statement然后用它调用蒸馏distilled后的 HTTP APICloudflare*Http铸造一个只含该访问级别所需 permission groups 的受限AccountApiToken把 token 值作为 secret 绑定进环境再通过蒸馏层调用 Cloudflare REST API。AWS 侧*Live全部改名*Http由于 AWS 唯一的传输就是 HTTP所有 AWS impl layer 从*Live改名为*HttpDynamoDB.GetItemHttp、SQS.SendMessageHttp、S3.GetObjectHttp。AWS 运行时调用的是由 Lambda 的 IAM role 鉴权的蒸馏 HTTP APIBinding是 Cloudflare 原生 Worker 独有的概念——AWS 从来就没有原生绑定所以统一用*Http后缀。仓库 AWS/DynamoDB 目录下可以清晰看到GetItem.ts/GetItemHttp.ts、BatchGetItem.ts/BatchGetItemHttp.ts等成对文件。更多 Cloudflare*Http绑定同一套机制原生 Worker 绑定只存在于 Worker 内部。当你需要在没有原生绑定的地方使用同一能力时——Container 进程、Durable Object 访问外部资源、边缘之外运行的脚本——就必须走 HTTP 传输。beta.59 补齐了这一缺口R2、KV、Queues 原本已有*Http实现DNS 在本版本加入全部共享同一套机制和动词前置命名。仓库 Cloudflare/DNS 目录下的ReadDns.ts/WriteDns.ts/ReadWriteDns.ts与DnsHttp.ts即为此结构yield* Cloudflare.DNS.ReadDns(Zone); // list / get records yield* Cloudflare.DNS.WriteDns(Zone); // create / update / delete yield* Cloudflare.DNS.ReadWriteDns(Zone); // both原理部署期铸造受限AccountApiToken*HttpLayer 无法依赖env.MY_BINDING因此要自备凭证。它在部署期铸造一个仅含该访问级别所需 permission groups的AccountApiToken并将 token 值作为 secret 绑定到宿主环境——整个过程藏在__ALCHEMY_RUNTIME__守卫之后只在部署期执行const token yield* AccountApiToken(${self.LogicalId}Token); if (!globalThis.__ALCHEMY_RUNTIME__) { yield* token.bind${resource.LogicalId}({ policies: [ { effect: allow, permissionGroups, // e.g. [Workers KV Storage Read] for ReadNamespace resources: { [com.cloudflare.api.account.${accountId}]: * }, }, ], }); }permission groups 正是最小权限的体现ReadNamespace铸造只读 KV tokenWriteNamespace铸造只写 token。以 Cloudflare/KV/ReadNamespaceHttp.ts 为例其 Layer 实现明确传入了permissionGroups: [Workers KV Storage Read]再通过蒸馏 KV HTTP APIkv.getNamespaceValue、kv.bulkGetNamespaceKeys、kv.listNamespaceKeys等完成get/getWithMetadata/list。运行时客户端从 secrets 中读取 token 的value与accountId并调用 Cloudflare REST API——无需账户级 API key、无需手工 token 装配、每个能力一个独立的受限 token。Binding.Policy移除合并进单个运行时守卫的 Layer旧设计里绑定是两块运行时Binding.Service 独立的部署期Binding.Policy自带*PolicyLivelayer后者负责注册 IAM / 原生绑定。beta.59 把它们折叠为单个Binding.Service其 setup Effect 内联完成部署期 wiring并用守卫保证只在 plan 阶段运行if (!globalThis.__ALCHEMY_RUNTIME__) { // 仅部署期在 host 上注册 IAM / 原生绑定 / env yield* host.bind${resource}(/* … */); } // 始终执行返回类型化的运行时客户端这段运行时短路逻辑在源码中有直接实现核心 Binding.ts 中客户端解析的第一步就是if (globalThis.__ALCHEMY_RUNTIME__) return Effect.succeed(client);Binding.ts#L183。机制如下Plan 期守卫打开bind记录函数需要什么IAM 语句、原生绑定、env 配置运行时__ALCHEMY_RUNTIME__已设置整个分支被跳过同一调用只解析为轻量客户端运行时 bundle 保持精简。这正是 beta.58 中能力绑定capability bindings已经采用的模式——现在每一个绑定包括事件源都遵循它。Binding.Policy、Binding.ServiceClass以及Policy/PolicyLike类型从核心中删除。关于 plan 期与运行时两阶段模型的完整背景可参考 phases.mdx含__ALCHEMY_RUNTIME__守卫专节。Worker-only 绑定改为字符串 idImages、Browser、VersionMetadata、RateLimit这类没有底层资源的 Cloudflare 绑定现在第一个参数改为字符串 id如同资源 logical id不再接收{ name }对象- const images yield* Cloudflare.Images.Images({ name: PIPELINE }); const images yield* Cloudflare.Images.Images(PIPELINE);这一签名在源码中得到确认Cloudflare/Images/Images.ts 的示例代码即写作Cloudflare.Images.Images(PIPELINE)、Cloudflare.Images.Images(IMAGES)。它们的原生层同步去掉*Live/*Layer后缀以匹配*Binding约定- .pipe(Effect.provide(Cloudflare.Workers.BrowserBindingLive)) .pipe(Effect.provide(Cloudflare.Workers.BrowserBinding))Artifacts.Store→Artifacts.NamespaceArtifacts 命名空间从Store改名为Namespace访问方式仍然通过ReadNamespace/WriteNamespace/ReadWriteNamespace显式表达源码见 Cloudflare/Artifacts 目录下的Namespace.ts、ReadWriteNamespace.ts- const Repos Cloudflare.Artifacts.Store(Repos); - const repos yield* Cloudflare.Artifacts.ReadWriteStore(Repos); const Repos Cloudflare.Artifacts.Namespace(Repos); const repos yield* Cloudflare.Artifacts.ReadWriteNamespace(Repos);事件源consumeResourceEvent约定事件源本质上也是绑定因此获得同样的待遇。旧的X(resource, props?).subscribe(handler)形态被替换为单个动词前缀、以资源-事件命名、handler 作为最后一个参数的可调用函数- yield* Cloudflare.Queues.messages(inbound).subscribe((records) - records.pipe(Stream.runForEach(Console.log)), - ); yield* Cloudflare.Queues.consumeQueueMessages(inbound, (records) records.pipe(Stream.runForEach(Console.log)), );每个事件源现在读起来都一样——名字同时告诉你资源和它产出的元素之前之后SQS.messages(q).subscribeSQS.consumeQueueMessages(q, fn)S3.notifications(b).subscribeS3.consumeBucketEvents(b, fn)SNS.notifications(t).subscribeSNS.consumeTopicNotifications(t, fn)Kinesis.records(s).processKinesis.consumeStreamRecords(s, fn)DynamoDB.stream(t).processDynamoDB.consumeTableChanges(t, fn)GitHub.events(r).subscribeGitHub.consumeRepositoryEvents(r, fn)去掉.subscribe也顺带降低了 handler 在源边界的运行时要求显式的Body类型参数不再破坏类型推断。以 Cloudflare Queues 为例Cloudflare/Queues/EventSource.ts 中consumeQueueMessages(queue, props, handler)一次调用同时完成两半运行时注册queue事件监听器、把每批消息以Stream.Stream管道化部署期则自动产出Cloudflare.Queues.Consumer资源无需在alchemy.run.ts里手工装配 Consumer。其MessagesProps支持batchSize每批最大消息数、maxConcurrency最大并发调用、maxRetries死信前最大投递次数、maxWaitTime刷新部分批次前的等待时间转发时向上取整到整毫秒、retryDelay重试退避向上取整到整秒、deadLetterQueue可选死信队列名。完整的消费端用法可参考 queues.mdx。随附修复本次改名之外还带了两项修复deleteFirst在 replace 时被真正执行。替换默认采用先建后删先立起新世代再拆除旧世代但某些资源无法与自身旧版本共存固定物理名、单例。deleteFirst用于翻转这一顺序现在引擎确实做到了先删除上一世代、再创建替代物不再把旧链泄漏进下一阶段。源码 Apply.ts 的替换流程中对node.deleteFirst的处理如 Apply.ts#L1084-L1134 附近Deleting previous resource before creating its replacement (deleteFirst)的日志分支证实了这一行为。Worker diff 容忍旧版 custom-domain 状态。Alchemy ≤ beta.44 将每个 Worker 自定义域存为{ id, hostname, zoneId }对象beta.45 存为https://hostname字符串diff 路径会对每个条目调用字符串方法。旧状态现在会被强制转换回字符串Worker 部署不再抛出u.endsWith is not a function对应 issue #546。迁移路线图与进一步阅读本次发布的迁移总体是全局查找替换级别的工作将Cloudflare.KVNamespace换成Cloudflare.KV.Namespace、去掉所有.bind(...)调用、把*Live换成*Binding或*Http、将xxx.subscribe(fn)改写为consumeXxxYyy(resource, fn)。需要留意的是事件源 handler 参数的最终位置总是最后一个参数以及 Worker-only 绑定从对象参数到字符串 id 的变化。仓库内的官方文档提供了与本发布直接相关的延伸阅读Bindings 概念与最小权限绑定yield* ReadWriteBucket(Bucket)一行声明如何同时派生权限授予与环境配置Plantime 与 Runtime ›__ALCHEMY_RUNTIME__守卫解释 plan 期记录绑定、运行时轻量解析的两阶段模型Queue Consumer 完整实战consumeQueueMessages的端到端示例CHANGELOGv2.0.0-beta.59 的官方变更条目。若需对照源码验证本文所述结构可直接浏览 Cloudflare 包源码目录KV / R2 / DNS / AI / Queues / Artifacts / Images 均为新命名空间布局、Binding.ts__ALCHEMY_RUNTIME__运行时守卫与Binding.Service实现以及 Cloudflare/index.ts逐服务export * as导出。【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3code创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考