@foretag/tanstack-db-surrealdb 0.1.13 → 0.1.15
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 +1 -1
- package/dist/index.js +105 -53
- package/dist/index.mjs +105 -53
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> Note: Please note this Software is Pre-release pending testing and not yet recommended for production use.
|
|
4
4
|
|
|
5
|
-
Add Offline / Local First Caching & Syncing to your SurrealDB app with TanstackDB and Loro (CRDTs). For SurrealDB v3 and SurrealDB JS SDK v2.
|
|
5
|
+
Add Offline / Local First Caching & Syncing to your SurrealDB app with TanstackDB and Loro (CRDTs). For SurrealDB v3.alpha-13 and SurrealDB JS SDK v2 (and above).
|
|
6
6
|
|
|
7
7
|
- Local / Offline first applications with TanstackDB and Loro
|
|
8
8
|
- High performance with Low resource consumption
|
package/dist/index.js
CHANGED
|
@@ -27,29 +27,43 @@ var import_surrealdb2 = require("surrealdb");
|
|
|
27
27
|
|
|
28
28
|
// src/table.ts
|
|
29
29
|
var import_surrealdb = require("surrealdb");
|
|
30
|
-
function manageTable(db, { name, ...args }) {
|
|
30
|
+
function manageTable(db, useLoro, { name, ...args }) {
|
|
31
31
|
const fields = args.fields ?? "*";
|
|
32
32
|
const listAll = async () => {
|
|
33
33
|
return await db.select(new import_surrealdb.Table(name)).where(args.where).fields(...fields);
|
|
34
34
|
};
|
|
35
35
|
const listActive = async () => {
|
|
36
|
+
if (!useLoro) return listAll();
|
|
36
37
|
return await db.select(new import_surrealdb.Table(name)).where((0, import_surrealdb.and)(args.where, (0, import_surrealdb.eq)("sync_deleted", false))).fields(...fields);
|
|
37
38
|
};
|
|
38
|
-
const
|
|
39
|
-
await db.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
39
|
+
const create = async (data) => {
|
|
40
|
+
await db.create(new import_surrealdb.Table(name)).content(data);
|
|
41
|
+
};
|
|
42
|
+
const update = async (id, data) => {
|
|
43
|
+
if (useLoro) {
|
|
44
|
+
await db.update(id).merge({
|
|
45
|
+
...data,
|
|
46
|
+
sync_deleted: false,
|
|
47
|
+
updated_at: Date.now()
|
|
48
|
+
});
|
|
49
|
+
} else {
|
|
50
|
+
await db.update(id).merge({
|
|
51
|
+
...data
|
|
52
|
+
});
|
|
53
|
+
}
|
|
44
54
|
};
|
|
45
55
|
const remove = async (id) => {
|
|
46
56
|
await db.delete(id);
|
|
47
57
|
};
|
|
48
58
|
const softDelete = async (id) => {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
59
|
+
if (useLoro) {
|
|
60
|
+
await db.update(id).merge({
|
|
61
|
+
sync_deleted: true,
|
|
62
|
+
updated_at: Date.now()
|
|
63
|
+
});
|
|
64
|
+
} else {
|
|
65
|
+
await db.delete(id);
|
|
66
|
+
}
|
|
53
67
|
};
|
|
54
68
|
const subscribe = (cb) => {
|
|
55
69
|
let killed = false;
|
|
@@ -79,7 +93,8 @@ function manageTable(db, { name, ...args }) {
|
|
|
79
93
|
return {
|
|
80
94
|
listAll,
|
|
81
95
|
listActive,
|
|
82
|
-
|
|
96
|
+
create,
|
|
97
|
+
update,
|
|
83
98
|
remove,
|
|
84
99
|
softDelete,
|
|
85
100
|
subscribe
|
|
@@ -115,32 +130,44 @@ function surrealCollectionOptions({
|
|
|
115
130
|
loroMap.delete(id2);
|
|
116
131
|
loro?.doc?.commit?.();
|
|
117
132
|
};
|
|
133
|
+
const pushQueue = [];
|
|
134
|
+
const enqueuePush = (op) => {
|
|
135
|
+
if (!useLoro) return;
|
|
136
|
+
pushQueue.push(op);
|
|
137
|
+
};
|
|
118
138
|
const flushPushQueue = async () => {
|
|
139
|
+
if (!useLoro) return;
|
|
119
140
|
const ops = pushQueue.splice(0, pushQueue.length);
|
|
120
141
|
for (const op of ops) {
|
|
121
|
-
if (op.kind === "
|
|
142
|
+
if (op.kind === "create") {
|
|
143
|
+
await table.create(op.row);
|
|
144
|
+
}
|
|
145
|
+
if (op.kind === "update") {
|
|
122
146
|
const rid = new import_surrealdb2.RecordId(
|
|
123
147
|
config.table.name,
|
|
124
148
|
op.row.id.toString()
|
|
125
149
|
);
|
|
126
|
-
await table.
|
|
127
|
-
}
|
|
150
|
+
await table.update(rid, op.row);
|
|
151
|
+
}
|
|
152
|
+
if (op.kind === "delete") {
|
|
128
153
|
const rid = new import_surrealdb2.RecordId(config.table.name, op.id.toString());
|
|
129
154
|
await table.softDelete(rid);
|
|
130
155
|
}
|
|
131
156
|
}
|
|
132
157
|
};
|
|
133
|
-
const pushQueue = [];
|
|
134
|
-
const enqueuePush = (op) => pushQueue.push(op);
|
|
135
158
|
const newer = (a, b) => (a?.getTime() ?? -1) > (b?.getTime() ?? -1);
|
|
136
159
|
const reconcileBoot = (serverRows, write) => {
|
|
137
|
-
|
|
160
|
+
if (!useLoro) {
|
|
161
|
+
diffAndEmit(serverRows, write);
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const localRows = loroToArray();
|
|
138
165
|
const serverById = new Map(serverRows.map((r) => [getKey(r), r]));
|
|
139
166
|
const localById = new Map(localRows.map((r) => [getKey(r), r]));
|
|
140
167
|
const ids = /* @__PURE__ */ new Set([...serverById.keys(), ...localById.keys()]);
|
|
141
168
|
const current = [];
|
|
142
169
|
const applyLocal = (row) => {
|
|
143
|
-
if (!
|
|
170
|
+
if (!row) return;
|
|
144
171
|
if (row.sync_deleted) loroRemove(getKey(row));
|
|
145
172
|
else loroPut(row);
|
|
146
173
|
};
|
|
@@ -148,18 +175,22 @@ function surrealCollectionOptions({
|
|
|
148
175
|
const s = serverById.get(id2);
|
|
149
176
|
const l = localById.get(id2);
|
|
150
177
|
if (s && l) {
|
|
151
|
-
|
|
178
|
+
const sDeleted = s.sync_deleted ?? false;
|
|
179
|
+
const lDeleted = l.sync_deleted ?? false;
|
|
180
|
+
const sUpdated = s.updated_at;
|
|
181
|
+
const lUpdated = l.updated_at;
|
|
182
|
+
if (sDeleted && lDeleted) {
|
|
152
183
|
applyLocal(s);
|
|
153
184
|
current.push(s);
|
|
154
|
-
} else if (
|
|
185
|
+
} else if (sDeleted && !lDeleted) {
|
|
155
186
|
applyLocal(s);
|
|
156
187
|
current.push(s);
|
|
157
|
-
} else if (!
|
|
158
|
-
if (newer(
|
|
188
|
+
} else if (!sDeleted && lDeleted) {
|
|
189
|
+
if (newer(lUpdated, sUpdated)) {
|
|
159
190
|
enqueuePush({
|
|
160
191
|
kind: "delete",
|
|
161
192
|
id: id2,
|
|
162
|
-
updated_at:
|
|
193
|
+
updated_at: lUpdated ?? /* @__PURE__ */ new Date()
|
|
163
194
|
});
|
|
164
195
|
applyLocal(l);
|
|
165
196
|
current.push(l);
|
|
@@ -168,8 +199,8 @@ function surrealCollectionOptions({
|
|
|
168
199
|
current.push(s);
|
|
169
200
|
}
|
|
170
201
|
} else {
|
|
171
|
-
if (newer(
|
|
172
|
-
enqueuePush({ kind: "
|
|
202
|
+
if (newer(lUpdated, sUpdated)) {
|
|
203
|
+
enqueuePush({ kind: "update", row: l });
|
|
173
204
|
applyLocal(l);
|
|
174
205
|
current.push(l);
|
|
175
206
|
} else {
|
|
@@ -181,16 +212,18 @@ function surrealCollectionOptions({
|
|
|
181
212
|
applyLocal(s);
|
|
182
213
|
current.push(s);
|
|
183
214
|
} else if (!s && l) {
|
|
184
|
-
|
|
215
|
+
const lDeleted = l.sync_deleted ?? false;
|
|
216
|
+
const lUpdated = l.updated_at;
|
|
217
|
+
if (lDeleted) {
|
|
185
218
|
enqueuePush({
|
|
186
219
|
kind: "delete",
|
|
187
220
|
id: id2,
|
|
188
|
-
updated_at:
|
|
221
|
+
updated_at: lUpdated ?? /* @__PURE__ */ new Date()
|
|
189
222
|
});
|
|
190
223
|
applyLocal(l);
|
|
191
224
|
current.push(l);
|
|
192
225
|
} else {
|
|
193
|
-
enqueuePush({ kind: "
|
|
226
|
+
enqueuePush({ kind: "create", row: l });
|
|
194
227
|
applyLocal(l);
|
|
195
228
|
current.push(l);
|
|
196
229
|
}
|
|
@@ -200,15 +233,24 @@ function surrealCollectionOptions({
|
|
|
200
233
|
};
|
|
201
234
|
let prevById = /* @__PURE__ */ new Map();
|
|
202
235
|
const buildMap = (rows) => new Map(rows.map((r) => [getKey(r), r]));
|
|
203
|
-
const same = (a, b) =>
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
236
|
+
const same = (a, b) => {
|
|
237
|
+
if (useLoro) {
|
|
238
|
+
const aUpdated = a.updated_at;
|
|
239
|
+
const bUpdated = b.updated_at;
|
|
240
|
+
const aDeleted = a.sync_deleted ?? false;
|
|
241
|
+
const bDeleted = b.sync_deleted ?? false;
|
|
242
|
+
return aDeleted === bDeleted && (aUpdated?.getTime() ?? 0) === (bUpdated?.getTime() ?? 0) && JSON.stringify({
|
|
243
|
+
...a,
|
|
244
|
+
updated_at: void 0,
|
|
245
|
+
sync_deleted: void 0
|
|
246
|
+
}) === JSON.stringify({
|
|
247
|
+
...b,
|
|
248
|
+
updated_at: void 0,
|
|
249
|
+
sync_deleted: void 0
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
253
|
+
};
|
|
212
254
|
const diffAndEmit = (currentRows, write) => {
|
|
213
255
|
const currById = buildMap(currentRows);
|
|
214
256
|
for (const [id2, row] of currById) {
|
|
@@ -226,7 +268,8 @@ function surrealCollectionOptions({
|
|
|
226
268
|
}
|
|
227
269
|
prevById = currById;
|
|
228
270
|
};
|
|
229
|
-
const table = manageTable(db, config.table);
|
|
271
|
+
const table = manageTable(db, useLoro, config.table);
|
|
272
|
+
const now = () => /* @__PURE__ */ new Date();
|
|
230
273
|
const sync = ({
|
|
231
274
|
begin,
|
|
232
275
|
write,
|
|
@@ -236,15 +279,18 @@ function surrealCollectionOptions({
|
|
|
236
279
|
let offLive = null;
|
|
237
280
|
const makeTombstone = (id2) => ({
|
|
238
281
|
id: new import_surrealdb2.RecordId(config.table.name, id2).toString(),
|
|
239
|
-
updated_at:
|
|
282
|
+
updated_at: now(),
|
|
240
283
|
sync_deleted: true
|
|
241
284
|
});
|
|
242
285
|
const start = async () => {
|
|
243
286
|
try {
|
|
244
287
|
const serverRows = await table.listAll();
|
|
245
288
|
begin();
|
|
246
|
-
if (useLoro)
|
|
247
|
-
|
|
289
|
+
if (useLoro) {
|
|
290
|
+
reconcileBoot(serverRows, write);
|
|
291
|
+
} else {
|
|
292
|
+
diffAndEmit(serverRows, write);
|
|
293
|
+
}
|
|
248
294
|
commit();
|
|
249
295
|
markReady();
|
|
250
296
|
await flushPushQueue();
|
|
@@ -253,7 +299,8 @@ function surrealCollectionOptions({
|
|
|
253
299
|
try {
|
|
254
300
|
if (evt.type === "insert" || evt.type === "update") {
|
|
255
301
|
const row = evt.row;
|
|
256
|
-
|
|
302
|
+
const deleted = useLoro ? row.sync_deleted ?? false : false;
|
|
303
|
+
if (deleted) {
|
|
257
304
|
if (useLoro) loroRemove(getKey(row));
|
|
258
305
|
const prev = prevById.get(getKey(row)) ?? makeTombstone(getKey(row));
|
|
259
306
|
write({ type: "delete", value: prev });
|
|
@@ -288,19 +335,18 @@ function surrealCollectionOptions({
|
|
|
288
335
|
if (offLive) offLive();
|
|
289
336
|
};
|
|
290
337
|
};
|
|
291
|
-
const now = () => /* @__PURE__ */ new Date();
|
|
292
338
|
const onInsert = async (p) => {
|
|
293
339
|
const resultRows = [];
|
|
294
340
|
for (const m of p.transaction.mutations) {
|
|
295
341
|
if (m.type !== "insert") continue;
|
|
296
|
-
const
|
|
297
|
-
|
|
342
|
+
const base = { ...m.modified };
|
|
343
|
+
const row = useLoro ? {
|
|
344
|
+
...base,
|
|
298
345
|
updated_at: now(),
|
|
299
|
-
|
|
300
|
-
};
|
|
346
|
+
sync_deleted: false
|
|
347
|
+
} : base;
|
|
301
348
|
if (useLoro) loroPut(row);
|
|
302
|
-
|
|
303
|
-
await table.upsert(rid, row);
|
|
349
|
+
await table.create(row);
|
|
304
350
|
resultRows.push(row);
|
|
305
351
|
}
|
|
306
352
|
return resultRows;
|
|
@@ -310,10 +356,14 @@ function surrealCollectionOptions({
|
|
|
310
356
|
for (const m of p.transaction.mutations) {
|
|
311
357
|
if (m.type !== "update") continue;
|
|
312
358
|
const id2 = m.key;
|
|
313
|
-
const
|
|
359
|
+
const base = { ...m.modified, id: id2 };
|
|
360
|
+
const merged = useLoro ? {
|
|
361
|
+
...base,
|
|
362
|
+
updated_at: now()
|
|
363
|
+
} : base;
|
|
314
364
|
if (useLoro) loroPut(merged);
|
|
315
365
|
const rid = new import_surrealdb2.RecordId(config.table.name, keyOf(id2));
|
|
316
|
-
await table.
|
|
366
|
+
await table.update(rid, merged);
|
|
317
367
|
resultRows.push(merged);
|
|
318
368
|
}
|
|
319
369
|
return resultRows;
|
|
@@ -323,7 +373,9 @@ function surrealCollectionOptions({
|
|
|
323
373
|
for (const m of p.transaction.mutations) {
|
|
324
374
|
if (m.type !== "delete") continue;
|
|
325
375
|
const id2 = m.key;
|
|
326
|
-
if (useLoro)
|
|
376
|
+
if (useLoro) {
|
|
377
|
+
loroRemove(keyOf(id2));
|
|
378
|
+
}
|
|
327
379
|
await table.softDelete(new import_surrealdb2.RecordId(config.table.name, keyOf(id2)));
|
|
328
380
|
}
|
|
329
381
|
return resultRows;
|
package/dist/index.mjs
CHANGED
|
@@ -9,29 +9,43 @@ import {
|
|
|
9
9
|
Features,
|
|
10
10
|
Table
|
|
11
11
|
} from "surrealdb";
|
|
12
|
-
function manageTable(db, { name, ...args }) {
|
|
12
|
+
function manageTable(db, useLoro, { name, ...args }) {
|
|
13
13
|
const fields = args.fields ?? "*";
|
|
14
14
|
const listAll = async () => {
|
|
15
15
|
return await db.select(new Table(name)).where(args.where).fields(...fields);
|
|
16
16
|
};
|
|
17
17
|
const listActive = async () => {
|
|
18
|
+
if (!useLoro) return listAll();
|
|
18
19
|
return await db.select(new Table(name)).where(and(args.where, eq("sync_deleted", false))).fields(...fields);
|
|
19
20
|
};
|
|
20
|
-
const
|
|
21
|
-
await db.
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
const create = async (data) => {
|
|
22
|
+
await db.create(new Table(name)).content(data);
|
|
23
|
+
};
|
|
24
|
+
const update = async (id, data) => {
|
|
25
|
+
if (useLoro) {
|
|
26
|
+
await db.update(id).merge({
|
|
27
|
+
...data,
|
|
28
|
+
sync_deleted: false,
|
|
29
|
+
updated_at: Date.now()
|
|
30
|
+
});
|
|
31
|
+
} else {
|
|
32
|
+
await db.update(id).merge({
|
|
33
|
+
...data
|
|
34
|
+
});
|
|
35
|
+
}
|
|
26
36
|
};
|
|
27
37
|
const remove = async (id) => {
|
|
28
38
|
await db.delete(id);
|
|
29
39
|
};
|
|
30
40
|
const softDelete = async (id) => {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
41
|
+
if (useLoro) {
|
|
42
|
+
await db.update(id).merge({
|
|
43
|
+
sync_deleted: true,
|
|
44
|
+
updated_at: Date.now()
|
|
45
|
+
});
|
|
46
|
+
} else {
|
|
47
|
+
await db.delete(id);
|
|
48
|
+
}
|
|
35
49
|
};
|
|
36
50
|
const subscribe = (cb) => {
|
|
37
51
|
let killed = false;
|
|
@@ -61,7 +75,8 @@ function manageTable(db, { name, ...args }) {
|
|
|
61
75
|
return {
|
|
62
76
|
listAll,
|
|
63
77
|
listActive,
|
|
64
|
-
|
|
78
|
+
create,
|
|
79
|
+
update,
|
|
65
80
|
remove,
|
|
66
81
|
softDelete,
|
|
67
82
|
subscribe
|
|
@@ -97,32 +112,44 @@ function surrealCollectionOptions({
|
|
|
97
112
|
loroMap.delete(id2);
|
|
98
113
|
loro?.doc?.commit?.();
|
|
99
114
|
};
|
|
115
|
+
const pushQueue = [];
|
|
116
|
+
const enqueuePush = (op) => {
|
|
117
|
+
if (!useLoro) return;
|
|
118
|
+
pushQueue.push(op);
|
|
119
|
+
};
|
|
100
120
|
const flushPushQueue = async () => {
|
|
121
|
+
if (!useLoro) return;
|
|
101
122
|
const ops = pushQueue.splice(0, pushQueue.length);
|
|
102
123
|
for (const op of ops) {
|
|
103
|
-
if (op.kind === "
|
|
124
|
+
if (op.kind === "create") {
|
|
125
|
+
await table.create(op.row);
|
|
126
|
+
}
|
|
127
|
+
if (op.kind === "update") {
|
|
104
128
|
const rid = new RecordId(
|
|
105
129
|
config.table.name,
|
|
106
130
|
op.row.id.toString()
|
|
107
131
|
);
|
|
108
|
-
await table.
|
|
109
|
-
}
|
|
132
|
+
await table.update(rid, op.row);
|
|
133
|
+
}
|
|
134
|
+
if (op.kind === "delete") {
|
|
110
135
|
const rid = new RecordId(config.table.name, op.id.toString());
|
|
111
136
|
await table.softDelete(rid);
|
|
112
137
|
}
|
|
113
138
|
}
|
|
114
139
|
};
|
|
115
|
-
const pushQueue = [];
|
|
116
|
-
const enqueuePush = (op) => pushQueue.push(op);
|
|
117
140
|
const newer = (a, b) => (a?.getTime() ?? -1) > (b?.getTime() ?? -1);
|
|
118
141
|
const reconcileBoot = (serverRows, write) => {
|
|
119
|
-
|
|
142
|
+
if (!useLoro) {
|
|
143
|
+
diffAndEmit(serverRows, write);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const localRows = loroToArray();
|
|
120
147
|
const serverById = new Map(serverRows.map((r) => [getKey(r), r]));
|
|
121
148
|
const localById = new Map(localRows.map((r) => [getKey(r), r]));
|
|
122
149
|
const ids = /* @__PURE__ */ new Set([...serverById.keys(), ...localById.keys()]);
|
|
123
150
|
const current = [];
|
|
124
151
|
const applyLocal = (row) => {
|
|
125
|
-
if (!
|
|
152
|
+
if (!row) return;
|
|
126
153
|
if (row.sync_deleted) loroRemove(getKey(row));
|
|
127
154
|
else loroPut(row);
|
|
128
155
|
};
|
|
@@ -130,18 +157,22 @@ function surrealCollectionOptions({
|
|
|
130
157
|
const s = serverById.get(id2);
|
|
131
158
|
const l = localById.get(id2);
|
|
132
159
|
if (s && l) {
|
|
133
|
-
|
|
160
|
+
const sDeleted = s.sync_deleted ?? false;
|
|
161
|
+
const lDeleted = l.sync_deleted ?? false;
|
|
162
|
+
const sUpdated = s.updated_at;
|
|
163
|
+
const lUpdated = l.updated_at;
|
|
164
|
+
if (sDeleted && lDeleted) {
|
|
134
165
|
applyLocal(s);
|
|
135
166
|
current.push(s);
|
|
136
|
-
} else if (
|
|
167
|
+
} else if (sDeleted && !lDeleted) {
|
|
137
168
|
applyLocal(s);
|
|
138
169
|
current.push(s);
|
|
139
|
-
} else if (!
|
|
140
|
-
if (newer(
|
|
170
|
+
} else if (!sDeleted && lDeleted) {
|
|
171
|
+
if (newer(lUpdated, sUpdated)) {
|
|
141
172
|
enqueuePush({
|
|
142
173
|
kind: "delete",
|
|
143
174
|
id: id2,
|
|
144
|
-
updated_at:
|
|
175
|
+
updated_at: lUpdated ?? /* @__PURE__ */ new Date()
|
|
145
176
|
});
|
|
146
177
|
applyLocal(l);
|
|
147
178
|
current.push(l);
|
|
@@ -150,8 +181,8 @@ function surrealCollectionOptions({
|
|
|
150
181
|
current.push(s);
|
|
151
182
|
}
|
|
152
183
|
} else {
|
|
153
|
-
if (newer(
|
|
154
|
-
enqueuePush({ kind: "
|
|
184
|
+
if (newer(lUpdated, sUpdated)) {
|
|
185
|
+
enqueuePush({ kind: "update", row: l });
|
|
155
186
|
applyLocal(l);
|
|
156
187
|
current.push(l);
|
|
157
188
|
} else {
|
|
@@ -163,16 +194,18 @@ function surrealCollectionOptions({
|
|
|
163
194
|
applyLocal(s);
|
|
164
195
|
current.push(s);
|
|
165
196
|
} else if (!s && l) {
|
|
166
|
-
|
|
197
|
+
const lDeleted = l.sync_deleted ?? false;
|
|
198
|
+
const lUpdated = l.updated_at;
|
|
199
|
+
if (lDeleted) {
|
|
167
200
|
enqueuePush({
|
|
168
201
|
kind: "delete",
|
|
169
202
|
id: id2,
|
|
170
|
-
updated_at:
|
|
203
|
+
updated_at: lUpdated ?? /* @__PURE__ */ new Date()
|
|
171
204
|
});
|
|
172
205
|
applyLocal(l);
|
|
173
206
|
current.push(l);
|
|
174
207
|
} else {
|
|
175
|
-
enqueuePush({ kind: "
|
|
208
|
+
enqueuePush({ kind: "create", row: l });
|
|
176
209
|
applyLocal(l);
|
|
177
210
|
current.push(l);
|
|
178
211
|
}
|
|
@@ -182,15 +215,24 @@ function surrealCollectionOptions({
|
|
|
182
215
|
};
|
|
183
216
|
let prevById = /* @__PURE__ */ new Map();
|
|
184
217
|
const buildMap = (rows) => new Map(rows.map((r) => [getKey(r), r]));
|
|
185
|
-
const same = (a, b) =>
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
218
|
+
const same = (a, b) => {
|
|
219
|
+
if (useLoro) {
|
|
220
|
+
const aUpdated = a.updated_at;
|
|
221
|
+
const bUpdated = b.updated_at;
|
|
222
|
+
const aDeleted = a.sync_deleted ?? false;
|
|
223
|
+
const bDeleted = b.sync_deleted ?? false;
|
|
224
|
+
return aDeleted === bDeleted && (aUpdated?.getTime() ?? 0) === (bUpdated?.getTime() ?? 0) && JSON.stringify({
|
|
225
|
+
...a,
|
|
226
|
+
updated_at: void 0,
|
|
227
|
+
sync_deleted: void 0
|
|
228
|
+
}) === JSON.stringify({
|
|
229
|
+
...b,
|
|
230
|
+
updated_at: void 0,
|
|
231
|
+
sync_deleted: void 0
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
235
|
+
};
|
|
194
236
|
const diffAndEmit = (currentRows, write) => {
|
|
195
237
|
const currById = buildMap(currentRows);
|
|
196
238
|
for (const [id2, row] of currById) {
|
|
@@ -208,7 +250,8 @@ function surrealCollectionOptions({
|
|
|
208
250
|
}
|
|
209
251
|
prevById = currById;
|
|
210
252
|
};
|
|
211
|
-
const table = manageTable(db, config.table);
|
|
253
|
+
const table = manageTable(db, useLoro, config.table);
|
|
254
|
+
const now = () => /* @__PURE__ */ new Date();
|
|
212
255
|
const sync = ({
|
|
213
256
|
begin,
|
|
214
257
|
write,
|
|
@@ -218,15 +261,18 @@ function surrealCollectionOptions({
|
|
|
218
261
|
let offLive = null;
|
|
219
262
|
const makeTombstone = (id2) => ({
|
|
220
263
|
id: new RecordId(config.table.name, id2).toString(),
|
|
221
|
-
updated_at:
|
|
264
|
+
updated_at: now(),
|
|
222
265
|
sync_deleted: true
|
|
223
266
|
});
|
|
224
267
|
const start = async () => {
|
|
225
268
|
try {
|
|
226
269
|
const serverRows = await table.listAll();
|
|
227
270
|
begin();
|
|
228
|
-
if (useLoro)
|
|
229
|
-
|
|
271
|
+
if (useLoro) {
|
|
272
|
+
reconcileBoot(serverRows, write);
|
|
273
|
+
} else {
|
|
274
|
+
diffAndEmit(serverRows, write);
|
|
275
|
+
}
|
|
230
276
|
commit();
|
|
231
277
|
markReady();
|
|
232
278
|
await flushPushQueue();
|
|
@@ -235,7 +281,8 @@ function surrealCollectionOptions({
|
|
|
235
281
|
try {
|
|
236
282
|
if (evt.type === "insert" || evt.type === "update") {
|
|
237
283
|
const row = evt.row;
|
|
238
|
-
|
|
284
|
+
const deleted = useLoro ? row.sync_deleted ?? false : false;
|
|
285
|
+
if (deleted) {
|
|
239
286
|
if (useLoro) loroRemove(getKey(row));
|
|
240
287
|
const prev = prevById.get(getKey(row)) ?? makeTombstone(getKey(row));
|
|
241
288
|
write({ type: "delete", value: prev });
|
|
@@ -270,19 +317,18 @@ function surrealCollectionOptions({
|
|
|
270
317
|
if (offLive) offLive();
|
|
271
318
|
};
|
|
272
319
|
};
|
|
273
|
-
const now = () => /* @__PURE__ */ new Date();
|
|
274
320
|
const onInsert = async (p) => {
|
|
275
321
|
const resultRows = [];
|
|
276
322
|
for (const m of p.transaction.mutations) {
|
|
277
323
|
if (m.type !== "insert") continue;
|
|
278
|
-
const
|
|
279
|
-
|
|
324
|
+
const base = { ...m.modified };
|
|
325
|
+
const row = useLoro ? {
|
|
326
|
+
...base,
|
|
280
327
|
updated_at: now(),
|
|
281
|
-
|
|
282
|
-
};
|
|
328
|
+
sync_deleted: false
|
|
329
|
+
} : base;
|
|
283
330
|
if (useLoro) loroPut(row);
|
|
284
|
-
|
|
285
|
-
await table.upsert(rid, row);
|
|
331
|
+
await table.create(row);
|
|
286
332
|
resultRows.push(row);
|
|
287
333
|
}
|
|
288
334
|
return resultRows;
|
|
@@ -292,10 +338,14 @@ function surrealCollectionOptions({
|
|
|
292
338
|
for (const m of p.transaction.mutations) {
|
|
293
339
|
if (m.type !== "update") continue;
|
|
294
340
|
const id2 = m.key;
|
|
295
|
-
const
|
|
341
|
+
const base = { ...m.modified, id: id2 };
|
|
342
|
+
const merged = useLoro ? {
|
|
343
|
+
...base,
|
|
344
|
+
updated_at: now()
|
|
345
|
+
} : base;
|
|
296
346
|
if (useLoro) loroPut(merged);
|
|
297
347
|
const rid = new RecordId(config.table.name, keyOf(id2));
|
|
298
|
-
await table.
|
|
348
|
+
await table.update(rid, merged);
|
|
299
349
|
resultRows.push(merged);
|
|
300
350
|
}
|
|
301
351
|
return resultRows;
|
|
@@ -305,7 +355,9 @@ function surrealCollectionOptions({
|
|
|
305
355
|
for (const m of p.transaction.mutations) {
|
|
306
356
|
if (m.type !== "delete") continue;
|
|
307
357
|
const id2 = m.key;
|
|
308
|
-
if (useLoro)
|
|
358
|
+
if (useLoro) {
|
|
359
|
+
loroRemove(keyOf(id2));
|
|
360
|
+
}
|
|
309
361
|
await table.softDelete(new RecordId(config.table.name, keyOf(id2)));
|
|
310
362
|
}
|
|
311
363
|
return resultRows;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@foretag/tanstack-db-surrealdb",
|
|
3
3
|
"description": "Add Offline / Local First Caching & Syncing to your SurrealDB app with TanstackDB and Loro (CRDTs)",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.15",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist"
|
|
7
7
|
],
|
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
"loro-crdt": "^1.9.0"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
|
-
"@tanstack/db": "
|
|
47
|
-
"@tanstack/query-db-collection": "
|
|
46
|
+
"@tanstack/db": "^0.5.0",
|
|
47
|
+
"@tanstack/query-db-collection": "^1.0.0",
|
|
48
48
|
"surrealdb": "2.0.0-alpha.13"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|