跳到主要内容

PGlite 适配器

@aiao/rxdb-adapter-pglite 提供了在浏览器中运行 PostgreSQL 数据库的能力,基于 PGlite 实现,让你在浏览器中使用完整的 PostgreSQL 功能。

安装

npm install @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 模式

浏览器版本支持
Chrome90+
Edge90+
Safari14+
Firefox88+

OPFS 模式(实验性)

浏览器版本支持
Chrome102+
Edge102+
Safari15.2+⚠️
Firefox111+⚠️

对比 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}`);
}

查询性能问题

  1. 检查是否创建了合适的索引
  2. 使用 EXPLAIN 分析查询计划
  3. 考虑添加复合索引

迁移指南

从 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();

参考