PGlite 适配器
@aiao/rxdb-adapter-pglite 提供了在浏览器中运行 PostgreSQL 数据库的能力,基于 PGlite 实现,让你在浏览器中使用完整的 PostgreSQL 功能。
安装
- npm
- Yarn
- pnpm
- Bun
npm install @aiao/rxdb @aiao/rxdb-adapter-pglite @electric-sql/pglite
yarn add @aiao/rxdb @aiao/rxdb-adapter-pglite @electric-sql/pglite
pnpm add @aiao/rxdb @aiao/rxdb-adapter-pglite @electric-sql/pglite
bun add @aiao/rxdb @aiao/rxdb-adapter-pglite @electric-sql/pglite
核心概念
PGlite 简介
PGlite 是一个轻量级的 PostgreSQL 实现,编译为 WebAssembly,可以在浏览器中运行。它提供:
- 完整的 PostgreSQL SQL 语法支持
- ACID 事务
- 复杂查询和连接
- JSON/JSONB 支持
- 全文搜索
- 扩展支持
存储方式
PGlite 支持多种存储方式:
- IndexedDB: 持久化存储,刷新页面后数据保留
- Memory: 内存存储,仅用于临时数据或测试
- OPFS: Origin Private File System,更好的性能(实验性)
基础使用
默认配置(IndexedDB)
import { RxDB, SyncType } from '@aiao/rxdb';
import { RxDBAdapterPGlite } from '@aiao/rxdb-adapter-pglite';
import { PGlite } from '@electric-sql/pglite';
const rxdb = new RxDB({
dbName: 'myapp',
entities: [Todo],
sync: {
local: { adapter: 'pglite' },
type: SyncType.None
}
});
// 注册 PGlite 适配器
rxdb.adapter('pglite', async db => {
const pg = await PGlite.create({
dataDir: `idb://rxdb-${db.dbName}`
});
return new RxDBAdapterPGlite(db, pg);
});
// 连接数据库
await rxdb.connect('pglite').toPromise();
内存模式
适用于测试或不需要持久化的场景:
import { PGlite } from '@electric-sql/pglite';
import { RxDBAdapterPGlite } from '@aiao/rxdb-adapter-pglite';
rxdb.adapter('pglite', async db => {
const pg = await PGlite.create(); // 不指定 dataDir 则使用内存
return new RxDBAdapterPGlite(db, pg);
});
OPFS 模式(实验性)
更好的性能,但需要浏览器支持:
import { PGlite } from '@electric-sql/pglite';
import { RxDBAdapterPGlite } from '@aiao/rxdb-adapter-pglite';
rxdb.adapter('pglite', async db => {
const pg = await PGlite.create({
dataDir: `opfs://rxdb-${db.dbName}`
});
return new RxDBAdapterPGlite(db, pg);
});
配置选项
PGlite 配置
interface PGliteOptions {
// 数据目录
// - 不指定:内存模式
// - 'idb://xxx':IndexedDB 持久化
// - 'opfs://xxx':OPFS 持久化(实验性)
dataDir?: string;
// 调试模式
debug?: boolean;
// 扩展
extensions?: any[];
}
PostgreSQL 特性
1. 完整的 SQL 支持
PGlite 支持标准 PostgreSQL SQL 语法:
@Entity({
name: 'Todo',
properties: [
{ name: 'title', type: PropertyType.string, required: true },
{ name: 'completed', type: PropertyType.boolean, default: false },
{ name: 'createdAt', type: PropertyType.date }
],
indexes: [
{ columns: ['title'], type: 'BTREE' },
{ columns: ['completed', 'createdAt'] }
]
})
export class Todo extends EntityBase {}
2. JSON/JSONB 支持
存储和查询 JSON 数据:
@Entity({
name: 'Settings',
properties: [
{
name: 'config',
type: PropertyType.JSON,
jsonType: {
theme: PropertyType.string,
language: PropertyType.string,
notifications: PropertyType.boolean
}
}
]
})
export class Settings extends EntityBase {}
// 查询 JSON 字段
const settings = await Settings.find({
where: {
combinator: 'and',
rules: [
{ field: 'config->theme', operator: '=', value: 'dark' }
]
}
}).toPromise();
3. 全文搜索
使用 PostgreSQL 的全文搜索功能:
```typescript
@Entity({
name: 'Article',
properties: [
{ name: 'title', type: PropertyType.string },
{ name: 'content', type: PropertyType.Text }
],
indexes: [
{
columns: ['title', 'content'],
type: 'GIN',
using: 'to_tsvector'
}
]
})
export class Article extends EntityBase {}
```// 全文搜索
const articles = await Article.find({
where: {
combinator: 'and',
rules: [
{
field: 'to_tsvector(title || \' \' || content)',
operator: '@@',
value: 'to_tsquery(\'search & terms\')'
}
]
}
}).toPromise();
4. 数组类型
PostgreSQL 原生支持数组:
```typescript
@Entity({
name: 'Post',
properties: [
{ name: 'title', type: PropertyType.string },
{ name: 'tags', type: PropertyType.array, itemType: PropertyType.string }
]
})
export class Post extends EntityBase {}
// 查询包含特定标签的文章
const posts = await Post.find({
where: {
combinator: 'and',
rules: [
{ field: 'tags', operator: '@>', value: ['typescript'] }
]
}
}).toPromise();
高级功能
事务支持
PGlite 支持完整的 ACID 事务:
await rxdb.transaction(async () => {
const todo1 = new Todo();
todo1.title = '任务 1';
await todo1.save();
const todo2 = new Todo();
todo2.title = '任务 2';
await todo2.save();
// 如果任何操作失败,所有操作都会回滚
});
触发器
PGlite 支持数据库触发器(通过 RxDB 的钩子机制):
@Entity({
name: 'Todo',
properties: [
{ name: 'title', type: PropertyType.string },
{ name: 'updatedAt', type: PropertyType.date }
]
})
export class Todo extends EntityBase {
// 保存前自动更新时间戳
beforeSave() {
this.updatedAt = new Date();
}
}
视图支持
创建数据库视图:
// 在适配器初始化后执行
await rxdb.connect('pglite').toPromise();
// 创建视图
await rxdb.execute(`
CREATE VIEW active_todos AS
SELECT * FROM todos
WHERE completed = false
`);
性能优化
1. 索引优化
合理使用索引提升查询性能:
@Entity({
indexes: [
// 单列索引
{ columns: ['createdAt'] },
// 复合索引
{ columns: ['completed', 'createdAt'] },
// GIN 索引(用于 JSON、数组、全文搜索)
{ columns: ['tags'], type: 'GIN' }
]
})
export class Todo extends EntityBase {
// ...
}
2. 查询优化
使用 EXPLAIN 分析查询:
const result = await rxdb.execute(`
EXPLAIN ANALYZE
SELECT * FROM todos
WHERE completed = false
ORDER BY created_at DESC
LIMIT 20
`);
console.log('查询计划:', result);
3. 批量操作
使用事务批量执行操作:
await rxdb.transaction(async () => {
for (const item of largeDataSet) {
const todo = new Todo();
todo.title = item.title;
await todo.save();
}
});
4. 连接池
PGlite 在浏览器中运行,不需要传统的连接池,但可以复用实例:
// 单例模式
let pgliteInstance: PGlite;
rxdb.adapter('pglite', async db => {
if (!pgliteInstance) {
pgliteInstance = await PGlite.create({
dataDir: `idb://rxdb-${db.dbName}`
});
}
return new RxDBAdapterPGlite(db, pgliteInstance);
});
浏览器兼容性
IndexedDB 模式
| 浏览器 | 版本 | 支持 |
|---|---|---|
| Chrome | 90+ | ✅ |
| Edge | 90+ | ✅ |
| Safari | 14+ | ✅ |
| Firefox | 88+ | ✅ |
OPFS 模式(实验性)
| 浏览器 | 版本 | 支持 |
|---|---|---|
| Chrome | 102+ | ✅ |
| Edge | 102+ | ✅ |
| Safari | 15.2+ | ⚠️ |
| Firefox | 111+ | ⚠️ |
对比 SQLite
PGlite 优势
- ✅ 完整的 PostgreSQL SQL 语法
- ✅ 原生 JSON/JSONB 支持
- ✅ 数组类型支持
- ✅ 全文搜索功能更强大
- ✅ 更多的数据类型
- ✅ 更好的扩展性
PGlite 劣势
- ⚠️ 文件大小较大(~3-4MB)
- ⚠️ 初始化时间稍长
- ⚠️ 浏览器兼容性测试不如 SQLite 成熟
选择建议
选择 PGlite 如果:
- 需要完整的 PostgreSQL 功能
- 使用 JSON/JSONB 数据
- 需要全文搜索
- 后端也使用 PostgreSQL(保持一致性)
选择 SQLite 如果:
- 追求最小体积
- 需要最佳兼容性
- 不需要高级 PostgreSQL 特性
故障排查
数据库初始化失败
确保正确等待 PGlite 初始化:
// ✅ 正确:等待创建完成
const pg = await PGlite.create({ dataDir: 'idb://mydb' });
// ❌ 错误:没有等待
const pg = PGlite.create({ dataDir: 'idb://mydb' });
存储空间不足
IndexedDB 有配额限制,可以请求更多空间:
if ('storage' in navigator && 'persist' in navigator.storage) {
const isPersisted = await navigator.storage.persist();
console.log(`持久化存储: ${isPersisted}`);
}
查询性能问题
- 检查是否创建了合适的索引
- 使用 EXPLAIN 分析查询计划
- 考虑添加复合索引
迁移指南
从 SQLite 迁移到 PGlite
// 1. 导出 SQLite 数据
const sqliteRxdb = /* 现有的 SQLite RxDB 实例 */;
const data = await sqliteRxdb.exportDatabase();
// 2. 创建 PGlite 实例
const pgliteRxdb = new RxDB({
dbName: 'myapp',
entities: [Todo],
sync: {
local: { adapter: 'pglite' },
type: SyncType.None
}
});
pgliteRxdb.adapter('pglite', async db => {
const pg = await PGlite.create({
dataDir: `idb://rxdb-${db.dbName}`
});
return new RxDBAdapterPGlite(db, pg);
});
await pgliteRxdb.connect('pglite').toPromise();
// 3. 导入数据
await pgliteRxdb.importDatabase(data);
完整示例
import { RxDB, Entity, EntityBase, PropertyType, SyncType } from '@aiao/rxdb';
import { RxDBAdapterPGlite } from '@aiao/rxdb-adapter-pglite';
import { PGlite } from '@electric-sql/pglite';
// 定义实体
@Entity({
name: 'Todo',
properties: [
{ name: 'title', type: PropertyType.string, required: true },
{ name: 'completed', type: PropertyType.boolean, default: false },
{ name: 'tags', type: PropertyType.array, itemType: PropertyType.string },
{
name: 'metadata',
type: PropertyType.JSON,
jsonType: {
priority: PropertyType.number,
category: PropertyType.string
}
}
],
indexes: [
{ columns: ['createdAt'] },
{ columns: ['completed', 'createdAt'] },
{ columns: ['tags'], type: 'GIN' }
]
})
export class Todo extends EntityBase {}
// 初始化数据库
async function initDatabase() {
const rxdb = new RxDB({
dbName: 'todo-app',
entities: [Todo],
sync: {
local: { adapter: 'pglite' },
type: SyncType.None
}
});
// 注册 PGlite 适配器
rxdb.adapter('pglite', async db => {
const pg = await PGlite.create({
dataDir: `idb://rxdb-${db.dbName}`,
debug: process.env.NODE_ENV === 'development'
});
return new RxDBAdapterPGlite(db, pg);
});
// 连接数据库
await rxdb.connect('pglite').toPromise();
return rxdb;
}
// 使用数据库
async function main() {
const rxdb = await initDatabase();
// 创建待办
const todo = new Todo();
todo.title = '学习 PostgreSQL';
todo.tags = ['database', 'postgresql'];
todo.metadata = { priority: 1, category: 'learning' };
await todo.save();
// 查询包含特定标签的待办
const todos = await Todo.find({
where: {
combinator: 'and',
rules: [
{ field: 'tags', operator: '@>', value: ['database'] }
]
},
orderBy: [{ field: 'createdAt', sort: 'desc' }]
}).toPromise();
console.log('包含 database 标签的待办:', todos);
// JSON 查询
const highPriority = await Todo.find({
where: {
combinator: 'and',
rules: [
{ field: 'metadata->priority', operator: '=', value: 1 }
]
}
}).toPromise();
console.log('高优先级待办:', highPriority);
}
main();