@foretag/tanstack-db-surrealdb 0.1.8 → 0.1.10
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.d.mts +9 -9
- package/dist/index.d.ts +9 -9
- package/dist/index.js +28 -32
- package/dist/index.mjs +30 -34
- package/package.json +3 -2
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).
|
|
5
|
+
Add Offline / Local First Caching & Syncing to your SurrealDB app with TanstackDB and Loro (CRDTs). For SurrealDB v3 and SurrealDB JS SDK v2.
|
|
6
6
|
|
|
7
7
|
- Local / Offline first applications with TanstackDB and Loro
|
|
8
8
|
- High performance with Low resource consumption
|
package/dist/index.d.mts
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
import { CollectionConfig, UtilsRecord } from '@tanstack/db';
|
|
2
2
|
import { Container } from 'loro-crdt';
|
|
3
|
-
import { Surreal, Expr } from 'surrealdb';
|
|
3
|
+
import { RecordId, Surreal, Expr } from 'surrealdb';
|
|
4
4
|
|
|
5
|
-
type
|
|
6
|
-
|
|
7
|
-
id: Id;
|
|
5
|
+
type WithId<T> = T & {
|
|
6
|
+
id: string | RecordId;
|
|
8
7
|
};
|
|
9
|
-
type
|
|
10
|
-
type SyncedRow = SurrealObject<{
|
|
8
|
+
type SyncedTable<T> = WithId<T & {
|
|
11
9
|
sync_deleted?: boolean;
|
|
12
10
|
updated_at?: Date;
|
|
13
11
|
}>;
|
|
14
12
|
type TableOptions<T> = {
|
|
15
13
|
name: string;
|
|
16
14
|
where?: Expr;
|
|
17
|
-
fields?:
|
|
15
|
+
fields?: (keyof T)[];
|
|
18
16
|
};
|
|
19
|
-
type SurrealCollectionConfig<T extends
|
|
17
|
+
type SurrealCollectionConfig<T extends {
|
|
18
|
+
id: string | RecordId;
|
|
19
|
+
}> = {
|
|
20
20
|
id?: string;
|
|
21
21
|
db: Surreal;
|
|
22
22
|
table: TableOptions<T>;
|
|
@@ -24,7 +24,7 @@ type SurrealCollectionConfig<T extends SurrealObject<object>> = {
|
|
|
24
24
|
onError?: (e: unknown) => void;
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
-
declare function surrealCollectionOptions<T extends
|
|
27
|
+
declare function surrealCollectionOptions<T extends SyncedTable<object>, S extends Record<string, Container> = {
|
|
28
28
|
[k: string]: never;
|
|
29
29
|
}>({ id, useLoro, onError, db, ...config }: SurrealCollectionConfig<T>): CollectionConfig<T, string | number, never, UtilsRecord>;
|
|
30
30
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
import { CollectionConfig, UtilsRecord } from '@tanstack/db';
|
|
2
2
|
import { Container } from 'loro-crdt';
|
|
3
|
-
import { Surreal, Expr } from 'surrealdb';
|
|
3
|
+
import { RecordId, Surreal, Expr } from 'surrealdb';
|
|
4
4
|
|
|
5
|
-
type
|
|
6
|
-
|
|
7
|
-
id: Id;
|
|
5
|
+
type WithId<T> = T & {
|
|
6
|
+
id: string | RecordId;
|
|
8
7
|
};
|
|
9
|
-
type
|
|
10
|
-
type SyncedRow = SurrealObject<{
|
|
8
|
+
type SyncedTable<T> = WithId<T & {
|
|
11
9
|
sync_deleted?: boolean;
|
|
12
10
|
updated_at?: Date;
|
|
13
11
|
}>;
|
|
14
12
|
type TableOptions<T> = {
|
|
15
13
|
name: string;
|
|
16
14
|
where?: Expr;
|
|
17
|
-
fields?:
|
|
15
|
+
fields?: (keyof T)[];
|
|
18
16
|
};
|
|
19
|
-
type SurrealCollectionConfig<T extends
|
|
17
|
+
type SurrealCollectionConfig<T extends {
|
|
18
|
+
id: string | RecordId;
|
|
19
|
+
}> = {
|
|
20
20
|
id?: string;
|
|
21
21
|
db: Surreal;
|
|
22
22
|
table: TableOptions<T>;
|
|
@@ -24,7 +24,7 @@ type SurrealCollectionConfig<T extends SurrealObject<object>> = {
|
|
|
24
24
|
onError?: (e: unknown) => void;
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
-
declare function surrealCollectionOptions<T extends
|
|
27
|
+
declare function surrealCollectionOptions<T extends SyncedTable<object>, S extends Record<string, Container> = {
|
|
28
28
|
[k: string]: never;
|
|
29
29
|
}>({ id, useLoro, onError, db, ...config }: SurrealCollectionConfig<T>): CollectionConfig<T, string | number, never, UtilsRecord>;
|
|
30
30
|
|
package/dist/index.js
CHANGED
|
@@ -63,19 +63,9 @@ function manageTable(db, { name, ...args }) {
|
|
|
63
63
|
cb({ type: "delete", row: { id: value.id } });
|
|
64
64
|
};
|
|
65
65
|
const start = async () => {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
live.
|
|
69
|
-
} else {
|
|
70
|
-
const ctx = {
|
|
71
|
-
def() {
|
|
72
|
-
return "";
|
|
73
|
-
}
|
|
74
|
-
};
|
|
75
|
-
const [id] = await db.query(
|
|
76
|
-
`LIVE SELECT * FROM ${name} WHERE ${args.where.toSQL(ctx)}`
|
|
77
|
-
).collect();
|
|
78
|
-
live = await db.liveOf(new import_surrealdb.Uuid(id));
|
|
66
|
+
const isLiveSupported = db.isFeatureSupported(import_surrealdb.Features.LiveQueries);
|
|
67
|
+
if (isLiveSupported) {
|
|
68
|
+
live = await db.live(new import_surrealdb.Table(name)).where(args.where);
|
|
79
69
|
live.subscribe(on);
|
|
80
70
|
}
|
|
81
71
|
};
|
|
@@ -106,7 +96,8 @@ function surrealCollectionOptions({
|
|
|
106
96
|
}) {
|
|
107
97
|
let loro;
|
|
108
98
|
if (useLoro) loro = { doc: new import_loro_crdt.LoroDoc(), key: id };
|
|
109
|
-
const
|
|
99
|
+
const keyOf = (id2) => typeof id2 === "string" ? id2 : id2.toString();
|
|
100
|
+
const getKey = (row) => keyOf(row.id);
|
|
110
101
|
const loroKey = loro?.key ?? id ?? "surreal";
|
|
111
102
|
const loroMap = useLoro ? loro?.doc?.getMap?.(loroKey) ?? null : null;
|
|
112
103
|
const loroToArray = () => {
|
|
@@ -116,22 +107,25 @@ function surrealCollectionOptions({
|
|
|
116
107
|
};
|
|
117
108
|
const loroPut = (row) => {
|
|
118
109
|
if (!loroMap) return;
|
|
119
|
-
loroMap.set(
|
|
110
|
+
loroMap.set(getKey(row), row);
|
|
120
111
|
loro?.doc?.commit?.();
|
|
121
112
|
};
|
|
122
113
|
const loroRemove = (id2) => {
|
|
123
114
|
if (!loroMap) return;
|
|
124
|
-
loroMap.delete(
|
|
115
|
+
loroMap.delete(id2);
|
|
125
116
|
loro?.doc?.commit?.();
|
|
126
117
|
};
|
|
127
118
|
const flushPushQueue = async () => {
|
|
128
119
|
const ops = pushQueue.splice(0, pushQueue.length);
|
|
129
120
|
for (const op of ops) {
|
|
130
121
|
if (op.kind === "upsert") {
|
|
131
|
-
const rid = new import_surrealdb2.RecordId(
|
|
122
|
+
const rid = new import_surrealdb2.RecordId(
|
|
123
|
+
config.table.name,
|
|
124
|
+
op.row.id.toString()
|
|
125
|
+
);
|
|
132
126
|
await table.upsert(rid, op.row);
|
|
133
127
|
} else {
|
|
134
|
-
const rid = new import_surrealdb2.RecordId(config.table.name, op.id);
|
|
128
|
+
const rid = new import_surrealdb2.RecordId(config.table.name, op.id.toString());
|
|
135
129
|
await table.softDelete(rid);
|
|
136
130
|
}
|
|
137
131
|
}
|
|
@@ -141,15 +135,13 @@ function surrealCollectionOptions({
|
|
|
141
135
|
const newer = (a, b) => (a?.getTime() ?? -1) > (b?.getTime() ?? -1);
|
|
142
136
|
const reconcileBoot = (serverRows, write) => {
|
|
143
137
|
const localRows = useLoro ? loroToArray() : [];
|
|
144
|
-
const serverById = new Map(
|
|
145
|
-
serverRows.map((r) => [getKey(r), r])
|
|
146
|
-
);
|
|
138
|
+
const serverById = new Map(serverRows.map((r) => [getKey(r), r]));
|
|
147
139
|
const localById = new Map(localRows.map((r) => [getKey(r), r]));
|
|
148
140
|
const ids = /* @__PURE__ */ new Set([...serverById.keys(), ...localById.keys()]);
|
|
149
141
|
const current = [];
|
|
150
142
|
const applyLocal = (row) => {
|
|
151
143
|
if (!useLoro || !row) return;
|
|
152
|
-
if (row.sync_deleted) loroRemove(row
|
|
144
|
+
if (row.sync_deleted) loroRemove(getKey(row));
|
|
153
145
|
else loroPut(row);
|
|
154
146
|
};
|
|
155
147
|
for (const id2 of ids) {
|
|
@@ -242,7 +234,11 @@ function surrealCollectionOptions({
|
|
|
242
234
|
markReady
|
|
243
235
|
}) => {
|
|
244
236
|
let offLive = null;
|
|
245
|
-
const makeTombstone = (id2) => ({
|
|
237
|
+
const makeTombstone = (id2) => ({
|
|
238
|
+
id: new import_surrealdb2.RecordId(config.table.name, id2).toString(),
|
|
239
|
+
updated_at: /* @__PURE__ */ new Date(),
|
|
240
|
+
sync_deleted: true
|
|
241
|
+
});
|
|
246
242
|
const start = async () => {
|
|
247
243
|
try {
|
|
248
244
|
const serverRows = await table.listAll();
|
|
@@ -258,18 +254,18 @@ function surrealCollectionOptions({
|
|
|
258
254
|
if (evt.type === "insert" || evt.type === "update") {
|
|
259
255
|
const row = evt.row;
|
|
260
256
|
if (row.sync_deleted) {
|
|
261
|
-
if (useLoro) loroRemove(row
|
|
262
|
-
const prev = prevById.get(row
|
|
257
|
+
if (useLoro) loroRemove(getKey(row));
|
|
258
|
+
const prev = prevById.get(getKey(row)) ?? makeTombstone(getKey(row));
|
|
263
259
|
write({ type: "delete", value: prev });
|
|
264
|
-
prevById.delete(row
|
|
260
|
+
prevById.delete(getKey(row));
|
|
265
261
|
} else {
|
|
266
262
|
if (useLoro) loroPut(row);
|
|
267
|
-
const had = prevById.has(row
|
|
263
|
+
const had = prevById.has(getKey(row));
|
|
268
264
|
write({
|
|
269
265
|
type: had ? "update" : "insert",
|
|
270
266
|
value: row
|
|
271
267
|
});
|
|
272
|
-
prevById.set(row
|
|
268
|
+
prevById.set(getKey(row), row);
|
|
273
269
|
}
|
|
274
270
|
} else if (evt.type === "delete") {
|
|
275
271
|
const id2 = getKey(evt.row);
|
|
@@ -303,7 +299,7 @@ function surrealCollectionOptions({
|
|
|
303
299
|
deleted: false
|
|
304
300
|
};
|
|
305
301
|
if (useLoro) loroPut(row);
|
|
306
|
-
const rid = new import_surrealdb2.RecordId(config.table.name, row
|
|
302
|
+
const rid = new import_surrealdb2.RecordId(config.table.name, getKey(row));
|
|
307
303
|
await table.upsert(rid, row);
|
|
308
304
|
resultRows.push(row);
|
|
309
305
|
}
|
|
@@ -316,7 +312,7 @@ function surrealCollectionOptions({
|
|
|
316
312
|
const id2 = m.key;
|
|
317
313
|
const merged = { ...m.modified, id: id2, updated_at: now() };
|
|
318
314
|
if (useLoro) loroPut(merged);
|
|
319
|
-
const rid = new import_surrealdb2.RecordId(config.table.name, id2);
|
|
315
|
+
const rid = new import_surrealdb2.RecordId(config.table.name, keyOf(id2));
|
|
320
316
|
await table.upsert(rid, merged);
|
|
321
317
|
resultRows.push(merged);
|
|
322
318
|
}
|
|
@@ -327,8 +323,8 @@ function surrealCollectionOptions({
|
|
|
327
323
|
for (const m of p.transaction.mutations) {
|
|
328
324
|
if (m.type !== "delete") continue;
|
|
329
325
|
const id2 = m.key;
|
|
330
|
-
if (useLoro) loroRemove(id2);
|
|
331
|
-
await table.softDelete(new import_surrealdb2.RecordId(config.table.name, id2));
|
|
326
|
+
if (useLoro) loroRemove(keyOf(id2));
|
|
327
|
+
await table.softDelete(new import_surrealdb2.RecordId(config.table.name, keyOf(id2)));
|
|
332
328
|
}
|
|
333
329
|
return resultRows;
|
|
334
330
|
};
|
package/dist/index.mjs
CHANGED
|
@@ -6,8 +6,8 @@ import { RecordId } from "surrealdb";
|
|
|
6
6
|
import {
|
|
7
7
|
and,
|
|
8
8
|
eq,
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
Features,
|
|
10
|
+
Table
|
|
11
11
|
} from "surrealdb";
|
|
12
12
|
function manageTable(db, { name, ...args }) {
|
|
13
13
|
const fields = args.fields?.join(", ") ?? "*";
|
|
@@ -45,19 +45,9 @@ function manageTable(db, { name, ...args }) {
|
|
|
45
45
|
cb({ type: "delete", row: { id: value.id } });
|
|
46
46
|
};
|
|
47
47
|
const start = async () => {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
live.
|
|
51
|
-
} else {
|
|
52
|
-
const ctx = {
|
|
53
|
-
def() {
|
|
54
|
-
return "";
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
|
-
const [id] = await db.query(
|
|
58
|
-
`LIVE SELECT * FROM ${name} WHERE ${args.where.toSQL(ctx)}`
|
|
59
|
-
).collect();
|
|
60
|
-
live = await db.liveOf(new Uuid(id));
|
|
48
|
+
const isLiveSupported = db.isFeatureSupported(Features.LiveQueries);
|
|
49
|
+
if (isLiveSupported) {
|
|
50
|
+
live = await db.live(new Table(name)).where(args.where);
|
|
61
51
|
live.subscribe(on);
|
|
62
52
|
}
|
|
63
53
|
};
|
|
@@ -88,7 +78,8 @@ function surrealCollectionOptions({
|
|
|
88
78
|
}) {
|
|
89
79
|
let loro;
|
|
90
80
|
if (useLoro) loro = { doc: new LoroDoc(), key: id };
|
|
91
|
-
const
|
|
81
|
+
const keyOf = (id2) => typeof id2 === "string" ? id2 : id2.toString();
|
|
82
|
+
const getKey = (row) => keyOf(row.id);
|
|
92
83
|
const loroKey = loro?.key ?? id ?? "surreal";
|
|
93
84
|
const loroMap = useLoro ? loro?.doc?.getMap?.(loroKey) ?? null : null;
|
|
94
85
|
const loroToArray = () => {
|
|
@@ -98,22 +89,25 @@ function surrealCollectionOptions({
|
|
|
98
89
|
};
|
|
99
90
|
const loroPut = (row) => {
|
|
100
91
|
if (!loroMap) return;
|
|
101
|
-
loroMap.set(
|
|
92
|
+
loroMap.set(getKey(row), row);
|
|
102
93
|
loro?.doc?.commit?.();
|
|
103
94
|
};
|
|
104
95
|
const loroRemove = (id2) => {
|
|
105
96
|
if (!loroMap) return;
|
|
106
|
-
loroMap.delete(
|
|
97
|
+
loroMap.delete(id2);
|
|
107
98
|
loro?.doc?.commit?.();
|
|
108
99
|
};
|
|
109
100
|
const flushPushQueue = async () => {
|
|
110
101
|
const ops = pushQueue.splice(0, pushQueue.length);
|
|
111
102
|
for (const op of ops) {
|
|
112
103
|
if (op.kind === "upsert") {
|
|
113
|
-
const rid = new RecordId(
|
|
104
|
+
const rid = new RecordId(
|
|
105
|
+
config.table.name,
|
|
106
|
+
op.row.id.toString()
|
|
107
|
+
);
|
|
114
108
|
await table.upsert(rid, op.row);
|
|
115
109
|
} else {
|
|
116
|
-
const rid = new RecordId(config.table.name, op.id);
|
|
110
|
+
const rid = new RecordId(config.table.name, op.id.toString());
|
|
117
111
|
await table.softDelete(rid);
|
|
118
112
|
}
|
|
119
113
|
}
|
|
@@ -123,15 +117,13 @@ function surrealCollectionOptions({
|
|
|
123
117
|
const newer = (a, b) => (a?.getTime() ?? -1) > (b?.getTime() ?? -1);
|
|
124
118
|
const reconcileBoot = (serverRows, write) => {
|
|
125
119
|
const localRows = useLoro ? loroToArray() : [];
|
|
126
|
-
const serverById = new Map(
|
|
127
|
-
serverRows.map((r) => [getKey(r), r])
|
|
128
|
-
);
|
|
120
|
+
const serverById = new Map(serverRows.map((r) => [getKey(r), r]));
|
|
129
121
|
const localById = new Map(localRows.map((r) => [getKey(r), r]));
|
|
130
122
|
const ids = /* @__PURE__ */ new Set([...serverById.keys(), ...localById.keys()]);
|
|
131
123
|
const current = [];
|
|
132
124
|
const applyLocal = (row) => {
|
|
133
125
|
if (!useLoro || !row) return;
|
|
134
|
-
if (row.sync_deleted) loroRemove(row
|
|
126
|
+
if (row.sync_deleted) loroRemove(getKey(row));
|
|
135
127
|
else loroPut(row);
|
|
136
128
|
};
|
|
137
129
|
for (const id2 of ids) {
|
|
@@ -224,7 +216,11 @@ function surrealCollectionOptions({
|
|
|
224
216
|
markReady
|
|
225
217
|
}) => {
|
|
226
218
|
let offLive = null;
|
|
227
|
-
const makeTombstone = (id2) => ({
|
|
219
|
+
const makeTombstone = (id2) => ({
|
|
220
|
+
id: new RecordId(config.table.name, id2).toString(),
|
|
221
|
+
updated_at: /* @__PURE__ */ new Date(),
|
|
222
|
+
sync_deleted: true
|
|
223
|
+
});
|
|
228
224
|
const start = async () => {
|
|
229
225
|
try {
|
|
230
226
|
const serverRows = await table.listAll();
|
|
@@ -240,18 +236,18 @@ function surrealCollectionOptions({
|
|
|
240
236
|
if (evt.type === "insert" || evt.type === "update") {
|
|
241
237
|
const row = evt.row;
|
|
242
238
|
if (row.sync_deleted) {
|
|
243
|
-
if (useLoro) loroRemove(row
|
|
244
|
-
const prev = prevById.get(row
|
|
239
|
+
if (useLoro) loroRemove(getKey(row));
|
|
240
|
+
const prev = prevById.get(getKey(row)) ?? makeTombstone(getKey(row));
|
|
245
241
|
write({ type: "delete", value: prev });
|
|
246
|
-
prevById.delete(row
|
|
242
|
+
prevById.delete(getKey(row));
|
|
247
243
|
} else {
|
|
248
244
|
if (useLoro) loroPut(row);
|
|
249
|
-
const had = prevById.has(row
|
|
245
|
+
const had = prevById.has(getKey(row));
|
|
250
246
|
write({
|
|
251
247
|
type: had ? "update" : "insert",
|
|
252
248
|
value: row
|
|
253
249
|
});
|
|
254
|
-
prevById.set(row
|
|
250
|
+
prevById.set(getKey(row), row);
|
|
255
251
|
}
|
|
256
252
|
} else if (evt.type === "delete") {
|
|
257
253
|
const id2 = getKey(evt.row);
|
|
@@ -285,7 +281,7 @@ function surrealCollectionOptions({
|
|
|
285
281
|
deleted: false
|
|
286
282
|
};
|
|
287
283
|
if (useLoro) loroPut(row);
|
|
288
|
-
const rid = new RecordId(config.table.name, row
|
|
284
|
+
const rid = new RecordId(config.table.name, getKey(row));
|
|
289
285
|
await table.upsert(rid, row);
|
|
290
286
|
resultRows.push(row);
|
|
291
287
|
}
|
|
@@ -298,7 +294,7 @@ function surrealCollectionOptions({
|
|
|
298
294
|
const id2 = m.key;
|
|
299
295
|
const merged = { ...m.modified, id: id2, updated_at: now() };
|
|
300
296
|
if (useLoro) loroPut(merged);
|
|
301
|
-
const rid = new RecordId(config.table.name, id2);
|
|
297
|
+
const rid = new RecordId(config.table.name, keyOf(id2));
|
|
302
298
|
await table.upsert(rid, merged);
|
|
303
299
|
resultRows.push(merged);
|
|
304
300
|
}
|
|
@@ -309,8 +305,8 @@ function surrealCollectionOptions({
|
|
|
309
305
|
for (const m of p.transaction.mutations) {
|
|
310
306
|
if (m.type !== "delete") continue;
|
|
311
307
|
const id2 = m.key;
|
|
312
|
-
if (useLoro) loroRemove(id2);
|
|
313
|
-
await table.softDelete(new RecordId(config.table.name, id2));
|
|
308
|
+
if (useLoro) loroRemove(keyOf(id2));
|
|
309
|
+
await table.softDelete(new RecordId(config.table.name, keyOf(id2)));
|
|
314
310
|
}
|
|
315
311
|
return resultRows;
|
|
316
312
|
};
|
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.10",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist"
|
|
7
7
|
],
|
|
@@ -40,7 +40,8 @@
|
|
|
40
40
|
"build": "tsup src/index.ts --dts --format esm,cjs"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@tanstack/db": "^0.4.
|
|
43
|
+
"@tanstack/db": "^0.4.20",
|
|
44
|
+
"@tanstack/query-db-collection": "^0.3.0",
|
|
44
45
|
"loro-crdt": "^1.8.9"
|
|
45
46
|
},
|
|
46
47
|
"peerDependencies": {
|