@aiao/rxdb-adapter-supabase 0.0.7 → 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +280 -58
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,91 +1,313 @@
|
|
|
1
|
-
# rxdb-adapter-supabase
|
|
1
|
+
# @aiao/rxdb-adapter-supabase
|
|
2
2
|
|
|
3
|
-
Supabase
|
|
3
|
+
RxDB Supabase 适配器 - 基于 PostgreSQL 的远程同步实现。
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## 特性
|
|
6
6
|
|
|
7
|
-
- ✅
|
|
8
|
-
- ✅
|
|
9
|
-
- ✅
|
|
10
|
-
- ✅
|
|
11
|
-
- ✅
|
|
12
|
-
- ✅
|
|
13
|
-
- ✅
|
|
7
|
+
- ✅ **完整的 CRUD 操作** - 通过 Repository 模式封装 Supabase API
|
|
8
|
+
- ✅ **批量操作** - `saveMany` / `removeMany` / `mutations` 高性能批处理
|
|
9
|
+
- ✅ **事务支持** - 基于 PostgreSQL RPC 的原子性操作
|
|
10
|
+
- ✅ **实时订阅** - Supabase Realtime 自动推送远程变更
|
|
11
|
+
- ✅ **变更追踪** - 自动记录所有数据变更到 `RxDBChange` 表
|
|
12
|
+
- ✅ **双向同步** - Push/Pull 机制确保本地与远程数据一致
|
|
13
|
+
- ✅ **树形结构** - `SupabaseTreeRepository` 支持树形数据操作
|
|
14
14
|
|
|
15
|
-
##
|
|
15
|
+
## 安装
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
- `>`, `>=`, `<`, `<=` - Greater / Less than
|
|
21
|
-
- `in`, `notIn` - Value in / not in array
|
|
22
|
-
- `between` - Value between range
|
|
23
|
-
- `startsWith`, `endsWith`, `contains` - String pattern matching
|
|
24
|
-
|
|
25
|
-
### Relationship Operators
|
|
17
|
+
```bash
|
|
18
|
+
pnpm add @aiao/rxdb-adapter-supabase @supabase/supabase-js
|
|
19
|
+
```
|
|
26
20
|
|
|
27
|
-
|
|
28
|
-
- `notExists` - Check if related record does not exist
|
|
21
|
+
## 快速开始
|
|
29
22
|
|
|
30
|
-
|
|
23
|
+
### 1. 配置 RxDB
|
|
31
24
|
|
|
32
25
|
```typescript
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
where: {
|
|
37
|
-
combinator: 'and',
|
|
38
|
-
rules: [{ field: 'orders', operator: 'exists' }]
|
|
39
|
-
}
|
|
40
|
-
});
|
|
26
|
+
import { RxDB } from '@aiao/rxdb';
|
|
27
|
+
import { RxDBAdapterSupabase } from '@aiao/rxdb-adapter-supabase';
|
|
28
|
+
import { RxDBAdapterSqlite } from '@aiao/rxdb-adapter-sqlite';
|
|
41
29
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
30
|
+
const rxdb = new RxDB({
|
|
31
|
+
dbName: 'my-app',
|
|
32
|
+
context: { userId: 'user-123' },
|
|
33
|
+
entities: [Todo, User],
|
|
34
|
+
sync: {
|
|
35
|
+
local: { adapter: 'sqlite' },
|
|
36
|
+
remote: { adapter: 'supabase' },
|
|
37
|
+
type: SyncType.PullPush
|
|
48
38
|
}
|
|
49
39
|
});
|
|
50
40
|
|
|
51
|
-
//
|
|
52
|
-
|
|
41
|
+
// 注册适配器
|
|
42
|
+
rxdb.adapter('sqlite', db => new RxDBAdapterSqlite(db, { vfs: 'MemoryAsyncVFS' }));
|
|
43
|
+
|
|
44
|
+
rxdb.adapter(
|
|
45
|
+
'supabase',
|
|
46
|
+
db =>
|
|
47
|
+
new RxDBAdapterSupabase(db, {
|
|
48
|
+
supabaseUrl: 'https://your-project.supabase.co',
|
|
49
|
+
supabaseKey: 'your-anon-key'
|
|
50
|
+
})
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
// 连接
|
|
54
|
+
await rxdb.connect('sqlite');
|
|
55
|
+
await rxdb.connect('supabase');
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 2. 数据操作
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
// 创建
|
|
62
|
+
const todo = rxdb.repository(Todo).create();
|
|
63
|
+
todo.title = 'Learn RxDB';
|
|
64
|
+
await todo.save();
|
|
65
|
+
|
|
66
|
+
// 查询
|
|
67
|
+
const todos = await rxdb.repository(Todo).find({
|
|
53
68
|
where: {
|
|
54
69
|
combinator: 'and',
|
|
55
|
-
rules: [{ field: '
|
|
70
|
+
rules: [{ field: 'completed', operator: '=', value: false }]
|
|
56
71
|
}
|
|
57
72
|
});
|
|
73
|
+
|
|
74
|
+
// 同步
|
|
75
|
+
await rxdb.versionManager.push(); // 推送本地变更到远程
|
|
76
|
+
await rxdb.versionManager.pull(); // 拉取远程变更到本地
|
|
77
|
+
await rxdb.versionManager.sync(); // 双向同步 (pull + push)
|
|
58
78
|
```
|
|
59
79
|
|
|
60
|
-
|
|
80
|
+
## 测试
|
|
61
81
|
|
|
62
|
-
|
|
82
|
+
### 前置条件
|
|
63
83
|
|
|
64
|
-
|
|
84
|
+
测试依赖本地 Supabase 环境,确保:
|
|
65
85
|
|
|
66
|
-
|
|
86
|
+
1. **Docker 已安装并运行**
|
|
87
|
+
2. **启动 Supabase 测试容器**:
|
|
67
88
|
|
|
68
|
-
|
|
89
|
+
```bash
|
|
90
|
+
cd docker
|
|
91
|
+
bash start.sh
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
这会启动并初始化:
|
|
95
|
+
- PostgreSQL (端口 5432)
|
|
96
|
+
- Kong API Gateway (端口 8000)
|
|
97
|
+
- 创建必要的表和函数
|
|
98
|
+
|
|
99
|
+
3. **环境变量配置**:
|
|
100
|
+
测试自动使用以下配置:
|
|
101
|
+
- `VITE_SUPABASE_URL=http://localhost:8000`
|
|
102
|
+
- `VITE_SUPABASE_KEY=<anon-key>` (见 `docker/.env`)
|
|
103
|
+
|
|
104
|
+
### 运行测试
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
# 确保 Supabase 已启动
|
|
108
|
+
cd docker && bash start.sh
|
|
109
|
+
|
|
110
|
+
# 运行测试
|
|
111
|
+
nx test rxdb-adapter-supabase
|
|
112
|
+
|
|
113
|
+
# 带覆盖率
|
|
114
|
+
nx test rxdb-adapter-supabase --coverage
|
|
115
|
+
|
|
116
|
+
# 停止 Supabase
|
|
117
|
+
cd docker && bash stop.sh
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
**常见问题**:
|
|
121
|
+
|
|
122
|
+
- ❌ `Could not find the table 'public.RxDBChange' in the schema cache`
|
|
123
|
+
- **原因**:Supabase 容器未启动或数据库未初始化
|
|
124
|
+
- **解决**:`cd docker && bash start.sh`
|
|
125
|
+
|
|
126
|
+
- ❌ `Connection refused at localhost:8000`
|
|
127
|
+
- **原因**:Kong API Gateway 未就绪
|
|
128
|
+
- **解决**:等待 `start.sh` 完成健康检查
|
|
129
|
+
|
|
130
|
+
## 数据库 Schema
|
|
131
|
+
|
|
132
|
+
Supabase 适配器需要以下表结构:
|
|
133
|
+
|
|
134
|
+
### RxDBChange (变更追踪表)
|
|
135
|
+
|
|
136
|
+
```sql
|
|
137
|
+
CREATE TABLE "RxDBChange" (
|
|
138
|
+
"id" BIGSERIAL PRIMARY KEY,
|
|
139
|
+
"branchId" TEXT NOT NULL,
|
|
140
|
+
"entityType" TEXT NOT NULL,
|
|
141
|
+
"entityId" TEXT NOT NULL,
|
|
142
|
+
"type" TEXT NOT NULL, -- INSERT/UPDATE/DELETE
|
|
143
|
+
"patch" JSONB,
|
|
144
|
+
"remoteId" BIGINT,
|
|
145
|
+
"revertChangeId" BIGINT,
|
|
146
|
+
"createdAt" TIMESTAMPTZ DEFAULT NOW(),
|
|
147
|
+
"updatedAt" TIMESTAMPTZ DEFAULT NOW()
|
|
148
|
+
);
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### RxDBBranch (分支元数据)
|
|
152
|
+
|
|
153
|
+
```sql
|
|
154
|
+
CREATE TABLE "RxDBBranch" (
|
|
155
|
+
"id" TEXT PRIMARY KEY,
|
|
156
|
+
"dbName" TEXT NOT NULL,
|
|
157
|
+
"context" JSONB,
|
|
158
|
+
"lastPushedChangeId" BIGINT,
|
|
159
|
+
"lastPullRemoteChangeId" BIGINT,
|
|
160
|
+
"createdAt" TIMESTAMPTZ DEFAULT NOW(),
|
|
161
|
+
"updatedAt" TIMESTAMPTZ DEFAULT NOW()
|
|
162
|
+
);
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### 业务表示例
|
|
166
|
+
|
|
167
|
+
```sql
|
|
168
|
+
CREATE TABLE "Todo" (
|
|
169
|
+
"id" TEXT PRIMARY KEY,
|
|
170
|
+
"title" TEXT NOT NULL,
|
|
171
|
+
"completed" BOOLEAN DEFAULT FALSE,
|
|
172
|
+
"createdAt" TIMESTAMPTZ DEFAULT NOW(),
|
|
173
|
+
"updatedAt" TIMESTAMPTZ DEFAULT NOW()
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
-- 自动创建变更记录的触发器
|
|
177
|
+
CREATE TRIGGER rxdb_sync_trigger
|
|
178
|
+
AFTER INSERT OR UPDATE OR DELETE ON "Todo"
|
|
179
|
+
FOR EACH ROW EXECUTE FUNCTION rxdb_sync_change();
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
完整 SQL 脚本见 `docker/sql/` 目录。
|
|
183
|
+
|
|
184
|
+
## API
|
|
185
|
+
|
|
186
|
+
### RxDBAdapterSupabase
|
|
69
187
|
|
|
70
188
|
```typescript
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
where: { rules: [{ field: 'idCard', operator: 'exists' }] }
|
|
74
|
-
});
|
|
189
|
+
class RxDBAdapterSupabase {
|
|
190
|
+
constructor(rxdb: RxDB, options: SupabaseAdapterOptions);
|
|
75
191
|
|
|
76
|
-
//
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
192
|
+
// 连接管理
|
|
193
|
+
connect(): Promise<IRxDBAdapter>;
|
|
194
|
+
disconnect(): Promise<void>;
|
|
195
|
+
|
|
196
|
+
// 同步操作
|
|
197
|
+
pullChanges(options: PullOptions): Promise<RemoteChange[]>;
|
|
198
|
+
pushChanges(changes: LocalChange[]): Promise<PushResult>;
|
|
199
|
+
|
|
200
|
+
// 批量操作
|
|
201
|
+
saveMany<T>(entities: T[]): Promise<T[]>;
|
|
202
|
+
removeMany<T>(entities: T[]): Promise<void>;
|
|
203
|
+
mutations(map: RxDBMutationsMap): Promise<void>;
|
|
204
|
+
}
|
|
80
205
|
```
|
|
81
206
|
|
|
82
|
-
|
|
207
|
+
### SupabaseRepository
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
class SupabaseRepository<T> {
|
|
211
|
+
// CRUD
|
|
212
|
+
create(): T;
|
|
213
|
+
find(options?: FindOptions): Promise<T[]>;
|
|
214
|
+
findOne(options?: FindOptions): Promise<T | null>;
|
|
215
|
+
count(options?: FindOptions): Promise<number>;
|
|
216
|
+
|
|
217
|
+
// 批量
|
|
218
|
+
saveMany(entities: T[]): Promise<T[]>;
|
|
219
|
+
removeMany(entities: T[]): Promise<void>;
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### SupabaseTreeRepository
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
class SupabaseTreeRepository<T> extends SupabaseRepository<T> {
|
|
227
|
+
// 树形操作
|
|
228
|
+
findDescendants(entity: T, depth?: number): Promise<T[]>;
|
|
229
|
+
findAncestors(entity: T): Promise<T[]>;
|
|
230
|
+
findRoots(): Promise<T[]>;
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## 架构
|
|
235
|
+
|
|
236
|
+
```
|
|
237
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
238
|
+
│ RxDB Core │
|
|
239
|
+
│ ┌────────────┐ ┌────────────┐ ┌──────────────────────┐ │
|
|
240
|
+
│ │ Repository │ │VersionMgr │ │ SchemaManager │ │
|
|
241
|
+
│ └─────┬──────┘ └─────┬──────┘ └──────────────────────┘ │
|
|
242
|
+
└────────┼───────────────┼─────────────────────────────────────┘
|
|
243
|
+
│ │
|
|
244
|
+
▼ ▼
|
|
245
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
246
|
+
│ RxDBAdapterSupabase (Remote) │
|
|
247
|
+
│ ┌──────────────────────────────────────────────────────┐ │
|
|
248
|
+
│ │ SupabaseRepository │ │
|
|
249
|
+
│ │ - CRUD 操作 │ │
|
|
250
|
+
│ │ - 查询构建 (apply_rule_group) │ │
|
|
251
|
+
│ │ - 批量操作 │ │
|
|
252
|
+
│ └──────────────────────────────────────────────────────┘ │
|
|
253
|
+
│ ┌──────────────────────────────────────────────────────┐ │
|
|
254
|
+
│ │ Sync Engine │ │
|
|
255
|
+
│ │ - pullChanges: 拉取远程变更 │ │
|
|
256
|
+
│ │ - pushChanges: 推送本地变更 (带压缩) │ │
|
|
257
|
+
│ │ - mergeChanges: 通过 RPC 原子性写入 │ │
|
|
258
|
+
│ └──────────────────────────────────────────────────────┘ │
|
|
259
|
+
│ ┌──────────────────────────────────────────────────────┐ │
|
|
260
|
+
│ │ Realtime Subscription │ │
|
|
261
|
+
│ │ - 监听 RxDBChange 表 INSERT 事件 │ │
|
|
262
|
+
│ │ - 自动触发 pull 同步 │ │
|
|
263
|
+
│ └──────────────────────────────────────────────────────┘ │
|
|
264
|
+
└─────────────┬───────────────────────────────────────────────┘
|
|
265
|
+
│
|
|
266
|
+
▼
|
|
267
|
+
┌─────────────────────┐
|
|
268
|
+
│ Supabase (PostgreSQL) │
|
|
269
|
+
│ - RxDBChange │
|
|
270
|
+
│ - RxDBBranch │
|
|
271
|
+
│ - Business Tables │
|
|
272
|
+
│ - Triggers/RPCs │
|
|
273
|
+
└─────────────────────┘
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
## 同步机制
|
|
277
|
+
|
|
278
|
+
### Push 流程
|
|
279
|
+
|
|
280
|
+
1. **收集本地变更**:从 `RxDBChange` 表查询 `remoteId = null` 的记录
|
|
281
|
+
2. **变更压缩**:
|
|
282
|
+
- `INSERT→UPDATE*` → 单个 `INSERT`
|
|
283
|
+
- `INSERT→DELETE` → 丢弃
|
|
284
|
+
- `UPDATE*→DELETE` → 单个 `DELETE`
|
|
285
|
+
3. **调用 RPC**:通过 `rxdb_mutations()` 原子性写入
|
|
286
|
+
4. **更新元数据**:记录 `lastPushedChangeId`
|
|
287
|
+
|
|
288
|
+
### Pull 流程
|
|
289
|
+
|
|
290
|
+
1. **查询远程变更**:`id > lastPullRemoteChangeId` 且 `branchId != current`
|
|
291
|
+
2. **应用到本地实体表**:
|
|
292
|
+
- `INSERT` → 插入
|
|
293
|
+
- `UPDATE` → 更新
|
|
294
|
+
- `DELETE` → 删除
|
|
295
|
+
3. **写入本地 RxDBChange**:标记 `remoteId` 避免重复 push
|
|
296
|
+
4. **更新元数据**:记录 `lastPullRemoteChangeId`
|
|
297
|
+
|
|
298
|
+
## 开发
|
|
83
299
|
|
|
84
300
|
```bash
|
|
85
|
-
|
|
301
|
+
# 构建
|
|
302
|
+
nx build rxdb-adapter-supabase
|
|
303
|
+
|
|
304
|
+
# 格式化
|
|
305
|
+
nx format:write --projects=rxdb-adapter-supabase
|
|
306
|
+
|
|
307
|
+
# Lint
|
|
308
|
+
nx lint rxdb-adapter-supabase
|
|
86
309
|
```
|
|
87
310
|
|
|
88
|
-
|
|
311
|
+
## License
|
|
89
312
|
|
|
90
|
-
|
|
91
|
-
- ⏭️ 1 skipped (bidirectional ONE_TO_ONE limitation)
|
|
313
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiao/rxdb-adapter-supabase",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -26,11 +26,11 @@
|
|
|
26
26
|
},
|
|
27
27
|
"peerDependencies": {
|
|
28
28
|
"@supabase/supabase-js": "^2.86.0",
|
|
29
|
-
"@aiao/rxdb": "0.0.
|
|
29
|
+
"@aiao/rxdb": "0.0.8"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"dotenv": "^17.2.3",
|
|
33
|
-
"@aiao/rxdb-test": "0.0.
|
|
34
|
-
"@aiao/rxdb-adapter-sqlite": "0.0.
|
|
33
|
+
"@aiao/rxdb-test": "0.0.8",
|
|
34
|
+
"@aiao/rxdb-adapter-sqlite": "0.0.8"
|
|
35
35
|
}
|
|
36
36
|
}
|