跳到主要内容

rxdb-adapter-pglite

RxDB 适配器,使用 PGlite 在浏览器中运行 PostgreSQL。

功能特性​

  • 本地优先: 在浏览器中通过 WebAssembly 运行完整 PostgreSQL
  • 零服务器: 无需后端服务器,数据存储在本地
  • PostgreSQL 兼容: 支持标准 PostgreSQL 语法和功能
  • 响应式: 数据变化自动触发更新

何时使用​

  • 需要 PostgreSQL 特性(如 JSONB、tsvector 全文搜索、高级索引)
  • 计划未来迁移到 PostgreSQL 后端
  • 需要更强的 SQL 标准兼容性
  • 应用需要复杂查询和事务支持

与其他适配器对比​

特性PGlitewa-sqlitesqlite-wasm
数据库引擎PostgreSQLSQLiteSQLite
运行资产约 50.9 MB(未压缩 Worker)~500KB~800KB
全文搜索tsvectorFTS5FTS5
JSON 支持JSONBJSON1JSON1
生态兼容PostgreSQLSQLiteSQLite

体积按当前构建产物口径记录:浏览器 Worker 未压缩约 50.9 MB,npm tarball 压缩后约 16.9 MB。实际首次传输量取决于部署端压缩,后续加载取决于浏览器缓存策略。

安装​

npm install @aiao/rxdb-adapter-pglite
# 或
pnpm add @aiao/rxdb-adapter-pglite

使用​

import { RxDB, SyncType } from '@aiao/rxdb';
import { RxDBAdapterPGlite } from '@aiao/rxdb-adapter-pglite';

const rxdb = new RxDB({
dbName: 'demo',
entities: [],
sync: {
type: SyncType.None,
local: `{ adapter: 'pglite' }`
}
});

rxdb.adapter('pglite', database => new RxDBAdapterPGlite(database, { store: 'memory' }));
await rxdb.connect('pglite');

// 应用退出时释放 Worker 和数据库资源
await rxdb.disconnect('pglite');

事务 API(C2 已落地第二步)​

PGlite 适配器同 rxdb-adapter-sqlite-core 一致 —— transaction() / runInTransaction() 的回调收到 PGliteTransactionExecutor:

await adapter.transaction(async executor => {
const repo = executor.getRepository(Post);
await repo.create({ title: 'inside tx' });
await executor.mergeChanges(actions, localChanges, /* disableTriggers */ false);
});

PGliteTransactionExecutor 不直接导出 —— 它是 RxDBAdapterPGlite 的内部产物;状态自持,绝不从驱动的 tx.closed 派生(该标志在失败路径上不翻转,逃逸出去的 tx 会以 autocommit 继续写)。外部代码通过 @aiao/rxdb 的 TransactionExecutor 接口与之交互。

备份与恢复​

backup() 把整个数据库写成一个 .rxdb-backup 归档流;restorePGliteDatabase() 把归档恢复到一个空的、未连接的目标。两端都逐块流式处理,不会先把整个库读进内存。

外部文件不在备份范围内。 归档只包含数据库本身(scope: { database: 'included', externalFiles: 'excluded' })。rxdb-plugin-storage 等插件存放在数据库之外的文件需要另行备份。

import { restorePGliteDatabase, cleanupIncompletePGliteRestore } from '@aiao/rxdb-adapter-pglite';

// 备份:写入任意 WritableStream(例如 File System Access 的文件句柄)
const handle = await showSaveFilePicker({ suggestedName: 'demo.rxdb-backup' });
const result = await adapter.backup(await handle.createWritable());
console.log(result.sha256, result.bytes);

// 恢复到 IndexedDB:目标 RxDB 实例已注册 adapter,但尚未 connect
const file = await (await showOpenFilePicker())[0].getFile();
await restorePGliteDatabase(file.stream(), { rxdb: target, options: { store: 'idb' } });
await target.connect('pglite');

恢复到内存目标时,结果里的 database 句柄要交给 adapter 的 restoredDatabase 选项领取(只能领取一次,且只能由恢复时传入的那个 RxDB 实例、以同一组扩展领取,否则报 invalid_state):

const { database } = await restorePGliteDatabase(stream, { rxdb: target, options: { store: 'memory' } });
target.adapter('pglite', db => new RxDBAdapterPGlite(db, { store: 'memory', restoredDatabase: database }));
await target.connect('pglite');
存储备份恢复
memory✅✅
idb✅✅
其他(opfs-ahp://、file:// 等)❌ unsupported_combination❌ unsupported_combination

要点:

  • 一致性:备份落在一个已提交事务边界上,备份期间的写入排在其后;锁等待上限由 lockTimeoutMs(默认 30s)控制,超时抛 lock_timeout。idb 源库的每个连接只在打开时从 IndexedDB 读入一份内存视图,之后看不到别的连接的提交,所以备份要求本连接从打开到现在一直独占这份存储: 其他连接(同页面另一个实例、其他标签页或 Worker)此刻还开着,或本连接打开之后有别的连接来过——哪怕已经关掉——都报 target_busy。 这时先关掉其他连接,再让备份所用的实例 disconnect() 后重新 connect(),然后备份。
  • 运行环境:idb 源库的备份靠 navigator.locks.query() 数出其他标签页 / Worker 里的连接,环境不提供时报 unsupported_combination(navigator.locks.query)。 一致快照用到 PGlite 未在类型声明里公开的运行时内部件(两把互斥锁与 Emscripten 文件系统);升级 PGlite 后缺了任何一个,备份报 unsupported_combination(pglite),不会做到一半才失败。
  • 先校验后写入:manifest 在第一块里读出,引擎版本、schema 指纹、扩展、加密认证域不兼容时在写入目标之前拒绝(incompatible_archive / auth_domain_mismatch);完整性(SHA-256)读到末尾才能确认,失败时已写的数据会被丢弃(corrupt_archive / truncated_archive)。
  • 加密库:归档里只有密文和 keyring 元数据,不含口令与密钥;恢复后的库保持锁定,用原口令 unlock()。
  • 中断:IndexedDB 目标在校验通过前不写 IndexedDB,并用持久标记记录「恢复进行中」。进程在恢复中途被杀后,连接与再次恢复都会报 restore_incomplete,调用 cleanupIncompletePGliteRestore(target) 清理后即可重新恢复;清理失败时报 cleanup_pending。
  • 独占:同一目标上并发的恢复只有一个能赢(target_busy),恢复期间的连接尝试报 restore_in_progress。

所有错误都是 RxDBBackupError,按 error.code 分支处理(isRxDBBackupError() 可做类型收窄)。

完整示例​

参考 dev-rxdb-angular 中的集成示例。

@aiao/rxdb-adapter-pglite — RxDB 适配器(PGlite / WebAssembly PostgreSQL 后端)。

公开三层 API:

  • 适配器与客户端:RxDBAdapterPGlite, PGliteClient
  • 数据访问:通过 RxDBAdapterPGlite 获取的 Repository
  • SQL 生成(纯函数):create_tables_sql, generate_trigger_sql, notify_function_sql,以及 PG 原生 FTS 模块(fts/)

测试工具单独从 @aiao/rxdb-adapter-pglite/testing 子路径导入,避免污染运行时包。

Enumerations​

EnumerationDescription
PGliteChangeTypePGlite 变更事件类型枚举 对应 PostgreSQL 的 TG_OP (trigger operation)

Classes​

ClassDescription
PGliteClientPGlite 客户端:封装 @electric-sql/pglite 实例,提供: - 统一的 query/exec/transaction API(对齐 IPGliteClient) - 系统表(rxdb_change/rxdb_branch/rxdb_migration)NOTIFY 监听 + 批量分发 (16ms trailing 防抖,另有 max-wait 与容量上限兜底,见 PGliteNotificationBatcher) - 安全的 disconnect(先 syncToFs 再 close,避免 IDBFS 关闭后回调抛错) - LiveQuery 支持(依赖 init 阶段注入的 live extension)
PGliteNotificationBatcher把裸 NOTIFY 聚合成 PGliteChangeEvent。
PGliteRestoredDatabase恢复到内存目标后得到的一次性数据库句柄。
PostgreSQLDialectPostgreSQL 方言实现
RxDBAdapterPGliteRxDB PGlite 适配器
RxdbAdapterPGliteErrorPGlite 适配器错误类
RxDBChangePipelineTimeoutErrorPGlite 变更管道在时限内未空闲。
RxDBQueryCacheRowContractErrorQueryCache 拉取落地时,远端行不满足本地表列契约。

Interfaces​

InterfaceDescription
AdapterEncryptionFacade暴露在 adapter.encryption 上的开发者门面,内部转发给 Keyring。 如果数据库中没有实体声明加密列,所有方法都会抛 EncryptedConfigurationError(code: 'no_encrypted_columns')。
EncryptionContext加密上下文,贯穿所有可能接触加密列的 PGlite 辅助函数。 当数据库没有加密列时 keyring 为 null,辅助函数走明文分支。 与 sqlite-core 的 EncryptionContext 结构保持一致。
FtsField-
FtsOptionsFTS DDL 生成选项。
IPGliteClientPGlite 客户端的最小公开契约。
ISqlDialectSQL 方言接口 定义数据库特定的 SQL 语法转换方法
PGliteChangeEventPGlite 变更事件 触发器通过 NOTIFY 发送的数据库变更事件
PGliteChangeEventSource变更事件源:能挂/摘 PGliteChangeType 监听的客户端。
PGliteClientEventsPGlite 客户端事件映射: 每个 PGliteChangeType 对应一个 PGliteChangeEvent, 用于 PGliteClient(基于 EventDispatcher)的 addEventListener 强类型推断。
PGliteClientOptionsPGlite 客户端配置选项 扩展自 PGlite 原生配置
PGliteEngineInfo引擎版本与数据格式兼容键。
PGliteNotificationBatcherOptionsPGliteNotificationBatcher 的入参。
PGliteNotifyPayloadPGlite 通知 payload 结构 从 NOTIFY 消息中解析的数据
PGliteQueryable能执行只读查询的最小接口:PGlite 实例与 IPGliteClient 都满足。
PGliteRestoreOptionsPGlite 恢复选项。
PGliteRestoreResultPGlite 恢复结果。
PGliteRestoreTarget恢复目标:一个尚未连接的 RxDB 实例,加上它将来连接时使用的 adapter 选项。
PgliteTableColumnPGlite 数据库表列信息接口 包含 PostgreSQL information_schema.columns 视图的所有字段
RxDBChangePipelineTimeoutDiagnostics变更管道超时诊断快照。

Type Aliases​

Type AliasDescription
ForeignKey外键约束信息类型 描述表之间的外键关系
FtsArrayKind数组列的物理存储形态。
PGliteBackupStorage数据目录所在的存储后端;只有这两种声明支持备份与恢复。
PGliteDataDirItem数据目录快照里的一项:目录或文件的条目头,或紧随文件条目头的一块内容。
PGliteRestoreStage恢复进行到的阶段,按顺序各触发一次。

Variables​

VariableDescription
ADAPTER_NAMEPGlite 适配器名称常量
DEFAULT_FTS_ARRAY_KIND数组列的默认物理形态,与适配器的建表映射保持一致。
DEFAULT_FTS_REGCONFIGFTS 默认 PostgreSQL regconfig,决定 tokenizer / stopwords / stemmer。
DEFAULT_NOTIFY_BATCH_TIMEOUT_MS默认的 trailing 防抖间隔(毫秒)。
DEFAULT_NOTIFY_MAX_BATCH_WAIT_MS一个批量窗口从开启到必须冲刷的上限(毫秒)。
DEFAULT_NOTIFY_MAX_PENDING_EVENTS单个窗口内累积事件的上限,超过即立即冲刷,形成背压。
deserializeFromEnvelope按持久化时的属性类型把已认证明文字节还原为业务值。
FTS_COLUMNFTS 表的物理列名(tsvector 类型)。固定加在原表上,避免与业务列冲突。
INVALID_QUERY_ERROR_CODE查询编译失败的错误码:where / orderBy 无法被翻译成 SQL。
normalizeCreateEntity规范化创建数据(过滤未赋值字段)。
normalizeUpdateEntity规范化更新数据并过滤 readonly 字段。
PG_MAX_PARAMSPostgreSQL 协议单次查询最大参数数量(int16)。 按列数将大批量数据切片,避免 INSERT ... VALUES (...) 超限。
pgDialect默认导出 PostgreSQL 方言实例
PGLITE_BACKUP_ENGINE归档里记录的引擎名。
PGLITE_BACKUP_LOCK_TIMEOUT_MSRxDBBackupOptions.lockTimeoutMs 的默认值。
PGLITE_EXCLUDED_FILES不进归档的文件名(任何深度)。
serializeForEnvelope按属性类型把单个非空业务值编码为信封明文字节。

Functions​

FunctionDescription
asPGliteChangeEventSource把客户端窄化为变更事件源;不具备该能力时返回 undefined。
assertQueryCacheRowContract落地前校验远端行的列集,不合契约就 fail-fast。
buildCreateFtsTableSql生成 PostgreSQL FTS 物理结构 DDL:在原表追加 _fts tsvector 列 + GIN 索引。
buildFtsTriggersSql生成 PostgreSQL FTS 同步 trigger(函数 + trigger)。
chunkByPgParamLimit按参数数量上限把批量数据分片。
cleanupIncompletePGliteRestore清理一次没做完的恢复(页面在恢复中途关闭、或恢复失败后清理本身也失败)。
create_tables_sql生成多张表的创建 SQL(多语句串)
create_tables_statements生成多张表的创建语句列表
generate_trigger_sql生成 PostgreSQL 触发器 SQL
generateNotifyFunctionSQL生成 NOTIFY 触发器函数 SQL
generateNotifyInfrastructureSQL生成完整的 NOTIFY 基础设施 SQL
generateNotifyTriggerSQL为指定表创建 NOTIFY 触发器
getEntityObjectFromResult从 PGlite 结果行获取实体对象数据 PGlite 返回行作为对象,主要用于类型转换
getMonotonicUpdatedAt保证 updatedAt 单调递增: 同一毫秒内多次 update 时递增 1ms,避免同步端按 updatedAt 比较失效。
getSqlValue-
getSqlWithParams将 PostgreSQL 参数占位符($1、$2 等)替换为实际值 用于批量操作中无法使用参数化查询的情况
getSwitchUpdatedAt计算分支切换(undo / redo)UPDATE 应写入的 updatedAt。
getTableColumnIndexName获取表列索引名称
getTableName拼出形如 "public"."users" 的完全限定表名(带双引号转义)。
getTableNameByMetadata根据 EntityMetadata 得到完全限定表名,等价于 getTableName(metadata.tableName, metadata.namespace)。
quoteIdentifier把标识符(schema 名、表名、列名)包成双引号形式。
quoteLiteral把字符串拼成 SQL 字符串字面量。
remove_all_triggers_sql生成删除所有实体触发器的 SQL
remove_trigger_sql生成删除单个实体触发器的 SQL
removeNotifyTriggerSQL移除表的 NOTIFY 触发器
requiredQueryCacheColumns算出「远端行必须自带」的列:本地表上 NOT NULL 且建表时拿不到默认值的那些。
resolvePGliteInitOptions把 PGliteClientOptions 规范化为 PGlite 构造函数能直接消费的形状。
restorePGliteDatabase把 RxDBAdapterPGlite.backup 产出的归档恢复成一个新的 PGlite 数据库。
rxDBColumnTypeToPGliteType将 RxDB 属性类型转换为 PGlite 数据类型
rxDBColumnTypeToPGliteTypeIndexName获取属性的索引操作符 http://www.postgres.cn/docs/current/indexes-opclass.html
shouldUsePGliteWorker判断是否需要为 PGlite 启用 Web Worker。
toPGliteEngineInfo由 SHOW server_version 的原文算出引擎信息。
transformEntityValuePGliteToJs将实体对象中的所有值从 PGlite 格式转换为 JS 类型 主要用于 RxDBChange 表的 patch/inversePatch 字段
transformEntityValueToSql将实体值转换为 SQL 兼容格式
transformValueJsToPGlite-
transformValuePGliteToJs-
verifyPGliteRestored校验恢复出来的库确实是 manifest 声明的那个库,而不只是一堆摘要正确的字节。