本地数据库与迁移
本节介绍 Capacitor 端的本地数据库组件库 @rydeen/database:
它为 Rydeen 应用提供统一的 SQLite 访问与数据库迁移能力,封装了 Web 与原生两种实现,并提供与 Angular 架构一致的一键接入方式。
这里所说的「数据库迁移能力」,可以理解为:
当你的应用版本不断升级、表结构不断变化时,有一套“可回放”的脚本与版本记录机制,确保所有终端都能从任意旧版本安全地迁移到最新结构,而不是靠“删库重建”或人工补救。
就像给数据库加上了「安装向导 + 升级向导」,每次发布新版本时只需附带新的迁移脚本,终端在启动时自动检查并按顺序执行缺失的迁移步骤。
可以把它理解为「终端侧的数据库底座」:接口风格尽量靠近后端 ORM / Repository 模式,但为前端与 Capacitor 环境做了优化。
1. 功能特性概览
- 统一数据库接口(
IDatabaseService)- 封装查询、插入、更新、删除、批量插入、事务(
beginTransaction/commit/rollback/transaction)、元数据查询(tableExists/getTableColumns)等操作; - 业务层只依赖接口,不关心底层是 Web 还是原生实现。
- 封装查询、插入、更新、删除、批量插入、事务(
- 跨平台实现
- Web 环境:使用
@capacitor-community/sqlite+jeep-sqlite,把 SQLite 持久化到 IndexedDB; - 原生环境:通过
CapacitorSQLite+ Web Worker 访问本地 SQLite,避免阻塞 UI 线程。
- Web 环境:使用
- 数据库迁移管理
- 内置
MigrationService,基于assets/db/migrations/manifest.json自动按版本顺序执行 SQL 脚本; - 维护
schema_migrations元数据表,保证数据库 Schema 的版本可追踪、可回滚。
- 内置
- Schema 扫描与管理
- 提供
discoverSchemas、getSchemaInfoFromDiscovered、createSchemaManagerFromModules等函数; - 基于应用传入的
import.meta.glob结果自动发现业务表 Schema,并生成统一管理入口(schemaManager、sqlBuilder等)。
- 提供
- 可配置化集成
- 通过
RydeenDatabaseOptions配置数据库名称、迁移清单路径、迁移脚本基础路径等; - 适配不同应用/环境的数据库需求。
- 通过
- Angular 一键接入
- 提供
provideRydeenDatabase(options),在app.config.ts中一行代码完成数据库服务注入,保持与整体 Angular 架构一致的 DI 风格。
- 提供
- Kysely 集成
- 内置
createSqliteKysely<DB>()与QueryBuilder,支持用类型安全的方式构建 SQL,减少手写字符串 SQL 的错误。
- 内置
2. 安装与依赖
npm install @rydeen/database kysely @capacitor-community/sqlite jeep-sqlite rxjs
@rydeen/database 假定你的应用是基于 Angular + Capacitor 的项目,以下依赖需要在最终应用中安装并维护版本:
- 框架与运行时:
@angular/core@capacitor/corerxjs
- 数据库与 Web 实现相关:
@capacitor-community/sqlitejeep-sqlitekysely
在库的 package.json 中,这些都被声明为 peerDependencies,即:库只声明“需要”,具体版本由你的应用决定并安装,避免多份 Angular / Capacitor / Kysely 版本冲突。
3. 接入步骤总览
建议按以下顺序接入数据库能力:
| 顺序 | 步骤 | 说明 |
|---|---|---|
| 1 | 在 app.config.ts 中注册 | 使用 provideRydeenDatabase(options) 注册数据库与迁移能力 |
| 2 | 配置 assets | 准备 manifest.json 与迁移 SQL 文件 |
| 3 | 建立项目内 Schema 胶水 | 使用 import.meta.glob + 插件提供的函数生成 schemaManager、sqlBuilder 等 |
| 4 | 定义应用级 DB 类型 | 合并各业务模块表的类型,与 BaseDB 组成完整 DB |
| 5 | Web 端等待插件就绪与迁移 | 在 root 组件中等待 jeep-sqlite ready 后执行迁移 |
| 6 | 在业务中使用 | 通过 IDatabaseService + Kysely/QueryBuilder 执行查询 |
下文将按此顺序逐步展开。
4. 在 Angular 应用中注册(app.config.ts)
在应用根配置中,通过 provideRydeenDatabase 注册数据库服务与迁移能力:
// app.config.ts
import type {ApplicationConfig} from '@angular/core';
import {provideRydeenDatabase} from '@rydeen/database';
export const appConfig: ApplicationConfig = {
providers: [
// ... 其它 providers
...provideRydeenDatabase({
dbName: 'app_db',
manifestUrl: '/assets/db/migrations/manifest.json',
migrationsBaseUrl: '/assets/db/migrations/',
}),
],
};
这样,在 DI 容器中就会注册好:
IDatabaseService(根据当前平台自动选择 Web / Native 实现);MigrationService(负责执行数据库迁移)。
5. 配置迁移脚本(assets/db/migrations)
数据库迁移采用「清单 + SQL 文件」的方式管理,典型结构如下(以 cross-platform-app 为例):
assets/
db/
migrations/
manifest.json
V1__init.sql
V2__create_users.sql
V3__add_user_index.sql
manifest.json:迁移清单,列出所有迁移版本与对应 SQL 文件;Vx__*.sql:具体 SQL 迁移文件,每个文件对应一次 Schema 变更或初始化。
manifest.json 示例:
{
"migrations": [
{
"version": 1,
"name": "init",
"file": "V1__init.sql",
"type": "sql"
},
{
"version": 2,
"name": "create_users",
"file": "V2__create_users.sql",
"type": "sql"
}
]
}
执行流程(简化):
MigrationService.run()确保schema_migrations表存在;- 查询已执行的最大版本号;
- 读取
manifest.json,过滤出版本号更大的迁移,按版本升序执行; - 对每个迁移:
- 读取对应 SQL 文件;
- 调用
IDatabaseService.executeRaw执行; - 在
schema_migrations记录一条已执行的版本信息。
开发者需要做的事情只有:维护好 manifest.json 和对应的 Vx__*.sql 文件,并保证版本号递增。
6. Schema 扫描与 DB 类型定义
在应用中,我们推荐将数据库上下文集中放在 core/db 目录,例如 core/db/db-context.ts,统一暴露:
- 自动扫描结果:
autoDiscoveredSchemas、getSchemaInfo(); schemaManager:用于 Schema 校验、表名与表结构查询;sqlBuilder:基于应用DB类型的 Kysely 实例。
示例(简化版):
/// <reference types="vite/client" />
import type {SchemaModuleMap, SchemaManager} from '@rydeen/database';
import {
discoverSchemas,
getSchemaInfoFromDiscovered,
createSchemaManagerFromModules,
createSqliteKysely,
} from '@rydeen/database';
import type {DB} from './db.types';
const schemaModules: SchemaModuleMap = import.meta.glob('../../features/**/schemas/*.schema.ts', {
eager: true,
});
const discovered = discoverSchemas(schemaModules);
export const autoDiscoveredSchemas = discovered;
export function getSchemaInfo() {
return getSchemaInfoFromDiscovered(discovered);
}
export const schemaManager: SchemaManager = createSchemaManagerFromModules(schemaModules);
export const sqlBuilder = createSqliteKysely<DB>();
db.types.ts 中再合并各业务模块的表类型:
import type {BaseDB} from '@rydeen/database';
import type {MenuDB} from '../../features/menu/schemas/menu.schema';
import type {UserDB} from '../../features/users/schemas/user.schema';
export interface AutoDB extends BaseDB, MenuDB, UserDB {}
export type DB = AutoDB;
7. 在业务代码中使用(示例)
完成上述接入后,业务代码通常不直接操作 SQL 字符串,而是通过 IDatabaseService + sqlBuilder/Kysely 进行访问,例如:
import {inject} from '@angular/core';
import type {IDatabaseService} from '@rydeen/database';
import {sqlBuilder} from 'src/app/core/db/db-context';
export class UserRepository {
private readonly db = inject<IDatabaseService>(IDatabaseService as any);
async listUsers(): Promise<User[]> {
const query = sqlBuilder.selectFrom('users').selectAll().orderBy('created_at', 'desc');
const {rows} = await this.db.executeKysely(query);
return rows;
}
}
8. 小结
@rydeen/database为前端提供了一套 统一、类型安全、可迁移的 SQLite 访问与管理方案,屏蔽了 Web / 原生平台差异。- 配合
MigrationService与 Schema 扫描能力,可以像后端一样管理数据库版本与结构演进。 - 与 Angular + Capacitor 架构深度集成(
provideRydeenDatabase+ DI),既适合人工开发,也非常适合后续由 AI Agent 根据规范自动生成和重构数据库相关代码。
在使用前,建议先通读本节,再结合 cross-platform-app 中的实际示例,逐步把自己的终端项目迁移到这套统一的数据库底座上。