@foretag/tanstack-db-surrealdb 0.1.14 → 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 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,32 +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
39
  const create = async (data) => {
39
40
  await db.create(new import_surrealdb.Table(name)).content(data);
40
41
  };
41
42
  const update = async (id, data) => {
42
- await db.update(id).merge({
43
- ...data,
44
- sync_deleted: false,
45
- updated_at: Date.now()
46
- });
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
+ }
47
54
  };
48
55
  const remove = async (id) => {
49
56
  await db.delete(id);
50
57
  };
51
58
  const softDelete = async (id) => {
52
- await db.upsert(id).merge({
53
- sync_deleted: true,
54
- updated_at: Date.now()
55
- });
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
+ }
56
67
  };
57
68
  const subscribe = (cb) => {
58
69
  let killed = false;
@@ -120,11 +131,17 @@ function surrealCollectionOptions({
120
131
  loro?.doc?.commit?.();
121
132
  };
122
133
  const pushQueue = [];
123
- const enqueuePush = (op) => pushQueue.push(op);
134
+ const enqueuePush = (op) => {
135
+ if (!useLoro) return;
136
+ pushQueue.push(op);
137
+ };
124
138
  const flushPushQueue = async () => {
139
+ if (!useLoro) return;
125
140
  const ops = pushQueue.splice(0, pushQueue.length);
126
141
  for (const op of ops) {
127
- if (op.kind === "create") await table.create(op.row);
142
+ if (op.kind === "create") {
143
+ await table.create(op.row);
144
+ }
128
145
  if (op.kind === "update") {
129
146
  const rid = new import_surrealdb2.RecordId(
130
147
  config.table.name,
@@ -140,13 +157,17 @@ function surrealCollectionOptions({
140
157
  };
141
158
  const newer = (a, b) => (a?.getTime() ?? -1) > (b?.getTime() ?? -1);
142
159
  const reconcileBoot = (serverRows, write) => {
143
- const localRows = useLoro ? loroToArray() : [];
160
+ if (!useLoro) {
161
+ diffAndEmit(serverRows, write);
162
+ return;
163
+ }
164
+ const localRows = loroToArray();
144
165
  const serverById = new Map(serverRows.map((r) => [getKey(r), r]));
145
166
  const localById = new Map(localRows.map((r) => [getKey(r), r]));
146
167
  const ids = /* @__PURE__ */ new Set([...serverById.keys(), ...localById.keys()]);
147
168
  const current = [];
148
169
  const applyLocal = (row) => {
149
- if (!useLoro || !row) return;
170
+ if (!row) return;
150
171
  if (row.sync_deleted) loroRemove(getKey(row));
151
172
  else loroPut(row);
152
173
  };
@@ -154,18 +175,22 @@ function surrealCollectionOptions({
154
175
  const s = serverById.get(id2);
155
176
  const l = localById.get(id2);
156
177
  if (s && l) {
157
- if (s.sync_deleted && l.sync_deleted) {
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) {
158
183
  applyLocal(s);
159
184
  current.push(s);
160
- } else if (s.sync_deleted && !l.sync_deleted) {
185
+ } else if (sDeleted && !lDeleted) {
161
186
  applyLocal(s);
162
187
  current.push(s);
163
- } else if (!s.sync_deleted && l.sync_deleted) {
164
- if (newer(l.updated_at, s.updated_at)) {
188
+ } else if (!sDeleted && lDeleted) {
189
+ if (newer(lUpdated, sUpdated)) {
165
190
  enqueuePush({
166
191
  kind: "delete",
167
192
  id: id2,
168
- updated_at: l.updated_at
193
+ updated_at: lUpdated ?? /* @__PURE__ */ new Date()
169
194
  });
170
195
  applyLocal(l);
171
196
  current.push(l);
@@ -174,7 +199,7 @@ function surrealCollectionOptions({
174
199
  current.push(s);
175
200
  }
176
201
  } else {
177
- if (newer(l.updated_at, s.updated_at)) {
202
+ if (newer(lUpdated, sUpdated)) {
178
203
  enqueuePush({ kind: "update", row: l });
179
204
  applyLocal(l);
180
205
  current.push(l);
@@ -187,11 +212,13 @@ function surrealCollectionOptions({
187
212
  applyLocal(s);
188
213
  current.push(s);
189
214
  } else if (!s && l) {
190
- if (l.sync_deleted) {
215
+ const lDeleted = l.sync_deleted ?? false;
216
+ const lUpdated = l.updated_at;
217
+ if (lDeleted) {
191
218
  enqueuePush({
192
219
  kind: "delete",
193
220
  id: id2,
194
- updated_at: l.updated_at
221
+ updated_at: lUpdated ?? /* @__PURE__ */ new Date()
195
222
  });
196
223
  applyLocal(l);
197
224
  current.push(l);
@@ -206,15 +233,24 @@ function surrealCollectionOptions({
206
233
  };
207
234
  let prevById = /* @__PURE__ */ new Map();
208
235
  const buildMap = (rows) => new Map(rows.map((r) => [getKey(r), r]));
209
- const same = (a, b) => a.sync_deleted === b.sync_deleted && a.updated_at.getTime() === b.updated_at.getTime() && JSON.stringify({
210
- ...a,
211
- updated_at: void 0,
212
- sync_deleted: void 0
213
- }) === JSON.stringify({
214
- ...b,
215
- updated_at: void 0,
216
- sync_deleted: void 0
217
- });
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
+ };
218
254
  const diffAndEmit = (currentRows, write) => {
219
255
  const currById = buildMap(currentRows);
220
256
  for (const [id2, row] of currById) {
@@ -232,7 +268,8 @@ function surrealCollectionOptions({
232
268
  }
233
269
  prevById = currById;
234
270
  };
235
- const table = manageTable(db, config.table);
271
+ const table = manageTable(db, useLoro, config.table);
272
+ const now = () => /* @__PURE__ */ new Date();
236
273
  const sync = ({
237
274
  begin,
238
275
  write,
@@ -242,15 +279,18 @@ function surrealCollectionOptions({
242
279
  let offLive = null;
243
280
  const makeTombstone = (id2) => ({
244
281
  id: new import_surrealdb2.RecordId(config.table.name, id2).toString(),
245
- updated_at: /* @__PURE__ */ new Date(),
282
+ updated_at: now(),
246
283
  sync_deleted: true
247
284
  });
248
285
  const start = async () => {
249
286
  try {
250
287
  const serverRows = await table.listAll();
251
288
  begin();
252
- if (useLoro) reconcileBoot(serverRows, write);
253
- else diffAndEmit(serverRows, write);
289
+ if (useLoro) {
290
+ reconcileBoot(serverRows, write);
291
+ } else {
292
+ diffAndEmit(serverRows, write);
293
+ }
254
294
  commit();
255
295
  markReady();
256
296
  await flushPushQueue();
@@ -259,7 +299,8 @@ function surrealCollectionOptions({
259
299
  try {
260
300
  if (evt.type === "insert" || evt.type === "update") {
261
301
  const row = evt.row;
262
- if (row.sync_deleted) {
302
+ const deleted = useLoro ? row.sync_deleted ?? false : false;
303
+ if (deleted) {
263
304
  if (useLoro) loroRemove(getKey(row));
264
305
  const prev = prevById.get(getKey(row)) ?? makeTombstone(getKey(row));
265
306
  write({ type: "delete", value: prev });
@@ -294,16 +335,16 @@ function surrealCollectionOptions({
294
335
  if (offLive) offLive();
295
336
  };
296
337
  };
297
- const now = () => /* @__PURE__ */ new Date();
298
338
  const onInsert = async (p) => {
299
339
  const resultRows = [];
300
340
  for (const m of p.transaction.mutations) {
301
341
  if (m.type !== "insert") continue;
302
- const row = {
303
- ...m.modified,
342
+ const base = { ...m.modified };
343
+ const row = useLoro ? {
344
+ ...base,
304
345
  updated_at: now(),
305
346
  sync_deleted: false
306
- };
347
+ } : base;
307
348
  if (useLoro) loroPut(row);
308
349
  await table.create(row);
309
350
  resultRows.push(row);
@@ -315,7 +356,11 @@ function surrealCollectionOptions({
315
356
  for (const m of p.transaction.mutations) {
316
357
  if (m.type !== "update") continue;
317
358
  const id2 = m.key;
318
- const merged = { ...m.modified, id: id2, updated_at: now() };
359
+ const base = { ...m.modified, id: id2 };
360
+ const merged = useLoro ? {
361
+ ...base,
362
+ updated_at: now()
363
+ } : base;
319
364
  if (useLoro) loroPut(merged);
320
365
  const rid = new import_surrealdb2.RecordId(config.table.name, keyOf(id2));
321
366
  await table.update(rid, merged);
@@ -328,7 +373,9 @@ function surrealCollectionOptions({
328
373
  for (const m of p.transaction.mutations) {
329
374
  if (m.type !== "delete") continue;
330
375
  const id2 = m.key;
331
- if (useLoro) loroRemove(keyOf(id2));
376
+ if (useLoro) {
377
+ loroRemove(keyOf(id2));
378
+ }
332
379
  await table.softDelete(new import_surrealdb2.RecordId(config.table.name, keyOf(id2)));
333
380
  }
334
381
  return resultRows;
package/dist/index.mjs CHANGED
@@ -9,32 +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
21
  const create = async (data) => {
21
22
  await db.create(new Table(name)).content(data);
22
23
  };
23
24
  const update = async (id, data) => {
24
- await db.update(id).merge({
25
- ...data,
26
- sync_deleted: false,
27
- updated_at: Date.now()
28
- });
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
+ }
29
36
  };
30
37
  const remove = async (id) => {
31
38
  await db.delete(id);
32
39
  };
33
40
  const softDelete = async (id) => {
34
- await db.upsert(id).merge({
35
- sync_deleted: true,
36
- updated_at: Date.now()
37
- });
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
+ }
38
49
  };
39
50
  const subscribe = (cb) => {
40
51
  let killed = false;
@@ -102,11 +113,17 @@ function surrealCollectionOptions({
102
113
  loro?.doc?.commit?.();
103
114
  };
104
115
  const pushQueue = [];
105
- const enqueuePush = (op) => pushQueue.push(op);
116
+ const enqueuePush = (op) => {
117
+ if (!useLoro) return;
118
+ pushQueue.push(op);
119
+ };
106
120
  const flushPushQueue = async () => {
121
+ if (!useLoro) return;
107
122
  const ops = pushQueue.splice(0, pushQueue.length);
108
123
  for (const op of ops) {
109
- if (op.kind === "create") await table.create(op.row);
124
+ if (op.kind === "create") {
125
+ await table.create(op.row);
126
+ }
110
127
  if (op.kind === "update") {
111
128
  const rid = new RecordId(
112
129
  config.table.name,
@@ -122,13 +139,17 @@ function surrealCollectionOptions({
122
139
  };
123
140
  const newer = (a, b) => (a?.getTime() ?? -1) > (b?.getTime() ?? -1);
124
141
  const reconcileBoot = (serverRows, write) => {
125
- const localRows = useLoro ? loroToArray() : [];
142
+ if (!useLoro) {
143
+ diffAndEmit(serverRows, write);
144
+ return;
145
+ }
146
+ const localRows = loroToArray();
126
147
  const serverById = new Map(serverRows.map((r) => [getKey(r), r]));
127
148
  const localById = new Map(localRows.map((r) => [getKey(r), r]));
128
149
  const ids = /* @__PURE__ */ new Set([...serverById.keys(), ...localById.keys()]);
129
150
  const current = [];
130
151
  const applyLocal = (row) => {
131
- if (!useLoro || !row) return;
152
+ if (!row) return;
132
153
  if (row.sync_deleted) loroRemove(getKey(row));
133
154
  else loroPut(row);
134
155
  };
@@ -136,18 +157,22 @@ function surrealCollectionOptions({
136
157
  const s = serverById.get(id2);
137
158
  const l = localById.get(id2);
138
159
  if (s && l) {
139
- if (s.sync_deleted && l.sync_deleted) {
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) {
140
165
  applyLocal(s);
141
166
  current.push(s);
142
- } else if (s.sync_deleted && !l.sync_deleted) {
167
+ } else if (sDeleted && !lDeleted) {
143
168
  applyLocal(s);
144
169
  current.push(s);
145
- } else if (!s.sync_deleted && l.sync_deleted) {
146
- if (newer(l.updated_at, s.updated_at)) {
170
+ } else if (!sDeleted && lDeleted) {
171
+ if (newer(lUpdated, sUpdated)) {
147
172
  enqueuePush({
148
173
  kind: "delete",
149
174
  id: id2,
150
- updated_at: l.updated_at
175
+ updated_at: lUpdated ?? /* @__PURE__ */ new Date()
151
176
  });
152
177
  applyLocal(l);
153
178
  current.push(l);
@@ -156,7 +181,7 @@ function surrealCollectionOptions({
156
181
  current.push(s);
157
182
  }
158
183
  } else {
159
- if (newer(l.updated_at, s.updated_at)) {
184
+ if (newer(lUpdated, sUpdated)) {
160
185
  enqueuePush({ kind: "update", row: l });
161
186
  applyLocal(l);
162
187
  current.push(l);
@@ -169,11 +194,13 @@ function surrealCollectionOptions({
169
194
  applyLocal(s);
170
195
  current.push(s);
171
196
  } else if (!s && l) {
172
- if (l.sync_deleted) {
197
+ const lDeleted = l.sync_deleted ?? false;
198
+ const lUpdated = l.updated_at;
199
+ if (lDeleted) {
173
200
  enqueuePush({
174
201
  kind: "delete",
175
202
  id: id2,
176
- updated_at: l.updated_at
203
+ updated_at: lUpdated ?? /* @__PURE__ */ new Date()
177
204
  });
178
205
  applyLocal(l);
179
206
  current.push(l);
@@ -188,15 +215,24 @@ function surrealCollectionOptions({
188
215
  };
189
216
  let prevById = /* @__PURE__ */ new Map();
190
217
  const buildMap = (rows) => new Map(rows.map((r) => [getKey(r), r]));
191
- const same = (a, b) => a.sync_deleted === b.sync_deleted && a.updated_at.getTime() === b.updated_at.getTime() && JSON.stringify({
192
- ...a,
193
- updated_at: void 0,
194
- sync_deleted: void 0
195
- }) === JSON.stringify({
196
- ...b,
197
- updated_at: void 0,
198
- sync_deleted: void 0
199
- });
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
+ };
200
236
  const diffAndEmit = (currentRows, write) => {
201
237
  const currById = buildMap(currentRows);
202
238
  for (const [id2, row] of currById) {
@@ -214,7 +250,8 @@ function surrealCollectionOptions({
214
250
  }
215
251
  prevById = currById;
216
252
  };
217
- const table = manageTable(db, config.table);
253
+ const table = manageTable(db, useLoro, config.table);
254
+ const now = () => /* @__PURE__ */ new Date();
218
255
  const sync = ({
219
256
  begin,
220
257
  write,
@@ -224,15 +261,18 @@ function surrealCollectionOptions({
224
261
  let offLive = null;
225
262
  const makeTombstone = (id2) => ({
226
263
  id: new RecordId(config.table.name, id2).toString(),
227
- updated_at: /* @__PURE__ */ new Date(),
264
+ updated_at: now(),
228
265
  sync_deleted: true
229
266
  });
230
267
  const start = async () => {
231
268
  try {
232
269
  const serverRows = await table.listAll();
233
270
  begin();
234
- if (useLoro) reconcileBoot(serverRows, write);
235
- else diffAndEmit(serverRows, write);
271
+ if (useLoro) {
272
+ reconcileBoot(serverRows, write);
273
+ } else {
274
+ diffAndEmit(serverRows, write);
275
+ }
236
276
  commit();
237
277
  markReady();
238
278
  await flushPushQueue();
@@ -241,7 +281,8 @@ function surrealCollectionOptions({
241
281
  try {
242
282
  if (evt.type === "insert" || evt.type === "update") {
243
283
  const row = evt.row;
244
- if (row.sync_deleted) {
284
+ const deleted = useLoro ? row.sync_deleted ?? false : false;
285
+ if (deleted) {
245
286
  if (useLoro) loroRemove(getKey(row));
246
287
  const prev = prevById.get(getKey(row)) ?? makeTombstone(getKey(row));
247
288
  write({ type: "delete", value: prev });
@@ -276,16 +317,16 @@ function surrealCollectionOptions({
276
317
  if (offLive) offLive();
277
318
  };
278
319
  };
279
- const now = () => /* @__PURE__ */ new Date();
280
320
  const onInsert = async (p) => {
281
321
  const resultRows = [];
282
322
  for (const m of p.transaction.mutations) {
283
323
  if (m.type !== "insert") continue;
284
- const row = {
285
- ...m.modified,
324
+ const base = { ...m.modified };
325
+ const row = useLoro ? {
326
+ ...base,
286
327
  updated_at: now(),
287
328
  sync_deleted: false
288
- };
329
+ } : base;
289
330
  if (useLoro) loroPut(row);
290
331
  await table.create(row);
291
332
  resultRows.push(row);
@@ -297,7 +338,11 @@ function surrealCollectionOptions({
297
338
  for (const m of p.transaction.mutations) {
298
339
  if (m.type !== "update") continue;
299
340
  const id2 = m.key;
300
- const merged = { ...m.modified, id: id2, updated_at: now() };
341
+ const base = { ...m.modified, id: id2 };
342
+ const merged = useLoro ? {
343
+ ...base,
344
+ updated_at: now()
345
+ } : base;
301
346
  if (useLoro) loroPut(merged);
302
347
  const rid = new RecordId(config.table.name, keyOf(id2));
303
348
  await table.update(rid, merged);
@@ -310,7 +355,9 @@ function surrealCollectionOptions({
310
355
  for (const m of p.transaction.mutations) {
311
356
  if (m.type !== "delete") continue;
312
357
  const id2 = m.key;
313
- if (useLoro) loroRemove(keyOf(id2));
358
+ if (useLoro) {
359
+ loroRemove(keyOf(id2));
360
+ }
314
361
  await table.softDelete(new RecordId(config.table.name, keyOf(id2)));
315
362
  }
316
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.14",
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": {