ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Kilo 仓库 Effect Drizzle SQLite 包:将 Drizzle SQLite 适配器 Effect 原生化的落地实践

Kilo 仓库 Effect Drizzle SQLite 包:将 Drizzle SQLite 适配器 Effect 原生化的落地实践 Kilo 仓库 Effect Drizzle SQLite 包将 Drizzle SQLite 适配器 Effect 原生化的落地实践【免费下载链接】kilocodeKilo is the all-in-one agentic engineering platform. Build, ship, and iterate faster with the most popular open source coding agent.项目地址: https://gitcode.com/GitHub_Trending/ki/kilocode导读本文围绕 specs/storage/effect-sqlite-package.md 展开讲解 Kilo 仓库中opencode-ai/effect-drizzle-sqlite这一本地 workspace 包的由来、公共 API 形状、源码级实现原理与落地迁移策略。它解决的核心问题是如何在不等待 Drizzle 上游发布effect-sqlite的情况下提前把 Drizzle 的 SQLite 适配器以 Effect 原生Effect-yieldable、可组合、可依赖注入的形态搬运进仓库供packages/opencode的存储层在事务、迁移与查询语义上统一复用。读完本文你将掌握该包的目录结构、make/makeWithDefaults/DefaultServices等公共面、事务与迁移的底层实现以及从Database.use回调式 API 平滑迁移到 Effect Drizzle 查询的八步路线。一、这个包要解决什么问题仓库 specs/storage/effect-sqlite-package.md 首先明确了包的定位它不是一个 opencode 存储抽象也不是新的存储服务 API而是一个vendor 化的 Drizzleeffect-sqlite适配器形状——将 Drizzle 的 Effect SQLite 实现移植进仓库使仓库可以在上游发布之前或独立于上游发布节奏使用它。该包应当保持通用性只关心Drizzle Effect SQLite三者的组合不允许出现 opencode 路径、迁移表、事务钩子、post-commit 行为或领域语言。packages/opencode会作为内部消费者使用它但 opencode 专属的通道/路径选择必须留在packages/opencode一侧。这一点与文档强调的边界完全一致适配器是通用的业务语义属于消费者。opencode的SyncEvent.run依赖事务可组合性与behavior: immediate来保证排序正确性这些语义由 opencode 包装层负责保留而非塞进适配器包。二、包结构与公共导出面2.1 目录布局规格要求新增一个风格上类似packages/http-recorder的包。当前仓库中该包已经按规格落地实际结构如下packages/effect-drizzle-sqlite/ ├── package.json ├── src/ │ ├── index.ts │ ├── effect-sqlite/ │ │ ├── index.ts │ │ ├── driver.ts │ │ ├── session.ts │ │ └── migrator.ts │ ├── sqlite-core/effect/ │ │ ├── index.ts │ │ ├── db.ts / query.ts / raw.ts / session.ts │ │ ├── select.ts / insert.ts / update.ts / delete.ts / count.ts │ ├── internal/drizzle-utils.ts │ └── up-migrations/ │ ├── effect-sqlite.ts / sqlite.ts / utils.ts ├── test/sqlite.test.ts └── examples/basic.ts这与规格中列出的文件骨架一一对应package.json、src/index.ts、src/effect-sqlite/*、src/sqlite-core/effect/*、test/sqlite.test.ts。2.2 包名与依赖package.json 中的包名为opencode-ai/effect-drizzle-sqlite仓库目录名为packages/effect-drizzle-sqlite类型为 ESMprivate: true。关键依赖依赖角色drizzle-ormcatalog 版本提供 SQLite core、effect-core、migrator 等基础能力effectcatalog 版本提供 Effect、Layer、Context、Scope 等 Effect 生态原语effect/sql-sqlite-bundevDependencies测试与示例中提供SqliteClientSQLite 客户端 layerexports暴露了四个入口.: ./src/index.ts, ./effect-sqlite: ./src/effect-sqlite/index.ts, ./effect-sqlite/migrator: ./src/effect-sqlite/migrator.ts, ./sqlite-core/effect: ./src/sqlite-core/effect/index.ts2.3 初始导出src/index.ts 与规格中的初始导出完全一致export { EffectLogger } from drizzle-orm/effect-core export * from ./effect-sqlite/driver export * from ./effect-sqlite/session export { migrate } from ./effect-sqlite/migrator export * as EffectDrizzleSqlite from .即从 Drizzle 的effect-core转出EffectLogger转出 driver 与 session 的全部内容提供migrate并以命名空间形式把整个包再暴露为EffectDrizzleSqlite方便import * as风格的使用。三、公共 APImake、makeWithDefaults 与 DefaultServices3.1 规格中的使用形态规格给出了两个核心 API 模式不发明InterfaceTDatabase之类的自定义抽象而是尽量镜像 Drizzle 的 Effect 适配器const db yield * EffectDrizzleSqlite.make({ relations }).pipe(Effect.provide(EffectDrizzleSqlite.DefaultServices)) yield * db.select().from(users) yield * db.transaction( (tx) Effect.gen(function* () { yield* tx.insert(users).values({ name: Ada }) }), { behavior: immediate }, )三个要点查询是 Effect 可 yield 的值yield* db.select().from(table)事务是 Effect 值yield* db.transaction((tx) Effect.gen(...), { behavior: immediate })SQLite 客户端来自 Effect layer如SqliteClient.layer({ filename })。3.2 源码实现在 driver.ts 中export const DefaultServices Layer.merge(EffectCache.Default, EffectLogger.Default) export const make Effect.fn(SQLiteDrizzle.make)(function* TRelations(config {}) { const client yield* SqlClient const cache yield* EffectCache const logger yield* EffectLogger const dialect new SQLiteAsyncDialect() const relations config.relations ?? ({} as TRelations) const session new EffectSQLiteSession(client, dialect, relations, { logger, cache, useJitMappers: jitCompatCheck(config.jit), }) const db new EffectSQLiteDatabase(dialect, session, relations) as EffectSQLiteDatabaseTRelations { $client: SqlClient } db.$client client db.$cache.invalidate cache.onMutate return db }) export const makeWithDefaults TRelations(config {}) make(config).pipe(Effect.provide(DefaultServices))几个值得展开的实现事实make是一个Effect.fn通过 Effect 的依赖注入依次取用三个服务通用SqlClient来自effect/unstable/sql/SqlClient、EffectCache来自drizzle-orm/cache/core/cache-effect、EffectLogger来自drizzle-orm/effect-core。Drizzle 只依赖通用的SqlClient实际 SQLite 客户端effect/sql-sqlite-bun、effect/sql-sqlite-node或其他暴露SqlClient的包由调用方自行安装并提供。内部构造了SQLiteAsyncDialect与EffectSQLiteSession其中useJitMappers: jitCompatCheck(config.jit)来自 internal/drizzle-utils.ts用于按 JIT 兼容性决定是否启用 JIT 行映射器。返回值是EffectSQLiteDatabase继承自SQLiteEffectDatabase并挂上$client与$cache.invalidate。makeWithDefaults等价于make(config).pipe(Effect.provide(DefaultServices))是开箱即用的便捷入口DefaultServices用Layer.merge合并了 Drizzle 默认的 logger 与 cache 服务与 Effect Postgres 适配器保持一致。文档注释给出了标准接线示例yield* SQLiteDrizzle.make({ relations }).pipe(Effect.provide(SQLiteDrizzle.DefaultServices), Effect.provide(SqliteClient.layer({ filename: sqlite.db })))。四、会话与事务的底层实现4.1 会话层effect-sqlite/session.ts 定义了EffectSQLiteQueryEffectHKT错误类型为EffectDrizzleQueryError、EffectSQLiteRunResult readonly never[]以及EffectSQLiteSessionOptions包含logger、cache、useJitMappers。EffectSQLiteSession继承自SQLiteEffectSession重写了prepareQuery与prepareRelationalQuery将查询通过this.client.unsafe(query.sql, params)交给 Effect SQLite 客户端执行private execute(query: Query, params: unknown[], method: SQLiteExecuteMethod | values) { const statement this.client.unsafe(query.sql, params) if (method values) return statement.values if (method get) return statement.withoutTransform.pipe(Effect.map((rows) rows[0])) return statement.withoutTransform }isInTransaction()通过Effect.serviceOption(this.client.transactionService)判断当前上下文是否已有事务服务这为后面事务的可组合性奠定了基础。4.2 事务实现begin / savepoint / commit / rollbackwithTransaction是事务语义的核心代码逻辑与 Drizzle Effect SQLite 分支保持一致使用Effect.uninterruptibleMask包裹保证事务生命周期内不可被中断打断通过Effect.withFiber拿到当前 fiber 的上下文services若上下文已存在transactionService则复用当前连接并递增事务 idid connectionOption.value[1] 1否则用Scope.make()this.client.reserve预留新连接id 0id 0时执行begin ${config?.behavior ?? deferred}否则执行savepoint effect_sql_${id}——也就是说嵌套事务在 SQLite 上通过命名 savepoint 实现成功提交id 0执行commit且对 deferred 约束提交失败后 SQLite 仍保持事务打开的情况做了rollback兜底否则release savepoint effect_sql_${id}失败回滚id 0执行rollback否则rollback to savepoint effect_sql_${id}再release。transaction方法将其包装进Effect.gen创建EffectSQLiteTransaction实例后调用用户回调transaction(tx)。EffectSQLiteTransaction覆写了transaction使嵌套调用继续走同一个 session 的事务逻辑参见 session.ts。4.3 行为参数 behavior事务支持SQLiteTransactionConfig的behavior参数可取值deferred默认/immediate/exclusive。immediate在事务开始时就获取写锁避免写者之间的锁升级死锁这也是文档中SyncEvent.run依赖behavior: immediate做排序正确性的原因——在并发写入场景下immediate保证事务按获取锁的顺序序列化执行。五、迁移机制一次且按序5.1 入口effect-sqlite/migrator.ts 提供migrateexport function migrateTRelations(db: EffectSQLiteDatabaseTRelations, config: MigrationConfig) { const migrations readMigrationFiles(config) return coreMigrate(migrations, db.session, config) }使用drizzle-orm/migrator的readMigrationFiles读取迁移目录再交给 sqlite-core/effect/session.ts 中的migrate一个Effect.fn执行。5.2 迁移表与版本升级核心迁移流程src/sqlite-core/effect/session.ts的migrate迁移表名默认为__drizzle_migrations可通过配置覆盖先通过upgradeIfNeeded定义于 up-migrations/effect-sqlite.ts检查迁移表是否存在、依据pragma_table_info判断迁移表版本并对旧版本迁移表执行升级当前实现提供upgradeFromV0在事务内把旧的id/hash/created_at行回填为新表结构新库时CREATE TABLE IF NOT EXISTS __drizzle_migrations (id INTEGER PRIMARY KEY, hash text NOT NULL, created_at numeric, name text, applied_at TEXT)读取已应用的迁移id, hash, created_at, name与本地迁移比对只执行尚未应用的部分实现运行一次且按序支持init模式要求数据库与本地都至多一条迁移用于从空库初始化。5.3 测试验证test/sqlite.test.ts 中runs migrations once and records migration metadata用例连续两次调用EffectDrizzleSqlite.migrate再插入数据并查询__drizzle_migrations结果只有一条记录20240101000000_create_migrated_users证明幂等与顺序保证。该测试还覆盖了从drizzle迁移表读取 hash 的兼容路径。六、适配器级保障测试规格要求适配器层面验证五类保证test/sqlite.test.ts 逐一给出了实现证据保障测试用例验证方式查询构建器是可 yield 的 Effect 值selects rows through Effect-yieldable query buildersyield* db.select().from(users)、.where(eq(...)).get()成功事务提交写入commits successful transactionstransaction(..., { behavior: immediate })后 select 可见失败事务回滚rolls back failed transactions/rolls back explicit transaction rollbackEffect 失败或tx.rollback()后表为空迁移只运行一次且按序runs migrations once and records migration metadata__drizzle_migrations仅一条close finalizer 关闭底层数据库测试统一经Effect.scoped运行SqliteClient.layer(...)Effect.scoped释放资源此外还包含两个仓库特有的增强用例preserves failed transaction begin errors在外部进程持有begin immediate写锁、busy_timeout 0时事务 begin 失败应原样抛出SqlErrorLockTimeoutErrorcause 含database is locked不得被事务 finalizer 吞掉。redacts bound values from query errors以kilocode_change标记查询错误绝不能暴露绑定的凭据值error.message与error.params中的参数一律替换为redacted。supports returning and rejects empty update sets验证insert/update/delete ... returning并断言空 update set 抛No values to set。测试的run辅助函数展示了标准的接线方式const run A, E(effect: Effect.EffectA, E, SqlClientService) Effect.runPromise( effect.pipe(Effect.provide(SqliteClient.layer({ filename: :memory:, disableWAL: true })), Effect.scoped), )即内存 SQLite:memory:、关闭 WAL通过Effect.scoped自动管理客户端生命周期。七、完整示例从 DB 到 Domain Storeexamples/basic.ts 演示了完整的依赖注入分层是先验证适配器、再构建领域存储的最短路径定义sqliteTableusers自增 id notNull nameconst makeDatabase EffectDrizzleSqlite.makeWithDefaults()并用type DatabaseShape Effect.Successtypeof makeDatabase推导类型用Context.Service把Database包成服务static layer Layer.effect(Database, makeDatabase).pipe(Layer.provide(sqliteLayer))定义UserStoreError extends Schema.TaggedErrorClass做领域错误建模UserStore服务聚合migrate / create / rename / list四个 Effect 方法migrate调用EffectDrizzleSqlite.migrate(db, { migrationsFolder })并mapErrorrename在db.transaction(Effect.fnUntraced(...), { behavior: immediate })内先 insert 再 update演示事务原子性所有错误统一mapError(mapStoreError(...))映射为领域错误组合program后Effect.runPromise(program.pipe(Effect.provide(UserStore.layer)))运行。这个例子完整呈现了适配器包保持通用、领域语义在上层服务的架构意图。八、Opencode 采用注意事项与迁移路线8.1 必须保留的两个语义规格明确指出opencode 当前 packages/opencode/src/storage/db.ts 有两个不明显的语义包装层必须保留事务内的嵌套Database.use看到的是当前事务tx而非根客户端Database.effect在事务内排队 post-commit 副作用事务外立即执行。opencode 包装层可以用 Effect context 替代LocalContext实现私有的事务上下文持有{ tx, afterCommit }withDb/db方法优先读取当前事务上下文否则用根 dbtransaction在 effect 周围安装事务上下文嵌套事务初期复用现有 tx与现状一致后续如需可再引入显式 savepoint。迁移期间不得删除该行为SyncEvent.run依赖事务可组合性与behavior: immediate保证排序正确性。8.2 八步迁移策略规格给出了明确的迁移顺序新增opencode-ai/effect-drizzle-sqlite配一个最小的内存/文件 SQLite 测试 schema即当前已完成的部分从 SQLite 分支移植 Drizzle Effect SQLite 适配器保留上游命名与 API 形状测试适配器级保障查询可 yield、immediate事务提交、失败回滚、迁移幂等按序、close finalizer将opencode-ai/effect-drizzle-sqlite加入packages/opencode依赖把packages/opencode/src/storage/db.ts移植为适配器 opencode 专属事务/post-commit 上下文的薄兼容层先让现有调用点保持可用Database.Client()、Database.use(...)、Database.transaction(...)、Database.effect(...)兼容稳定后将调用点从回调式Database.use迁移为直接 yield Effect Drizzle 查询最后才在 opencode 存储包装层之上构建 session/message/project 等领域存储。8.3 为何比直接从 SessionStorage 起步更干净SessionStorage是有用的领域接缝但没有回答核心适配器问题如何让 Drizzle SQLite 在本仓库 Effect 原生。先 vendor 适配器一次opencode 再在其上构建自己的存储包装SessionStorage、MessageStorage、事件存储与 projector 写入就能共享同一套事务与迁移模型。九、遗留问题与首个 PR 建议规格记录的开放问题包括首个包面向effect/sql-sqlite-bun、effect/sql-sqlite-node还是两者各自独立 layer从 Drizzle 分支拷贝多少源码 vs 从 catalog 的drizzle-orm内部导入上游发布effect-sqlite后的更新路径afterCommit在事件发布迁移前是否保持 opencode 专属默认答案是兼容包装层是否暂时保留同步返回类型CLI/admin 的裸 SQL 与 sqlite shell 是否留在packages/opencode。首个 PR 被明确定位为包专属且刻意无趣只加packages/effect-drizzle-sqlite、用极小的测试 schema 而非 opencode 领域表、证明 Effect Drizzle SQLite 的查询/事务/迁移、暂不迁移packages/opencode至多为了 typecheck 加依赖。这样可以在扰动 opencode 当前数据库运行时之前先用一个聚焦的地方验证 Effect SQLite 方案的可行性——当前仓库中的包、测试与示例正是这条路线第一阶段的产物。【免费下载链接】kilocodeKilo is the all-in-one agentic engineering platform. Build, ship, and iterate faster with the most popular open source coding agent.项目地址: https://gitcode.com/GitHub_Trending/ki/kilocode创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表