Skip to main content

本地数据库与迁移

本节介绍 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 线程。
  • 数据库迁移管理
    • 内置 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/core
    • rxjs
  • 数据库与 Web 实现相关:
    • @capacitor-community/sqlite
    • jeep-sqlite
    • kysely

在库的 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
5Web 端等待插件就绪与迁移在 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"
}
]
}

执行流程(简化):

  1. MigrationService.run() 确保 schema_migrations 表存在;
  2. 查询已执行的最大版本号;
  3. 读取 manifest.json,过滤出版本号更大的迁移,按版本升序执行;
  4. 对每个迁移:
    • 读取对应 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 中的实际示例,逐步把自己的终端项目迁移到这套统一的数据库底座上。