@foretag/tanstack-db-surrealdb 0.5.8 → 0.6.1
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 +313 -97
- package/dist/index.d.mts +135 -20
- package/dist/index.d.ts +135 -20
- package/dist/index.js +936 -417
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +933 -419
- package/dist/index.mjs.map +1 -1
- package/package.json +15 -11
package/dist/index.js
CHANGED
|
@@ -6,12 +6,80 @@ var surrealdb = require('surrealdb');
|
|
|
6
6
|
var db = require('@tanstack/db');
|
|
7
7
|
|
|
8
8
|
// src/index.ts
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
|
|
10
|
+
// src/crdt.ts
|
|
11
|
+
var toRecord = (value) => typeof value === "object" && value !== null ? value : {};
|
|
12
|
+
var materializeLoroJson = (doc, id) => {
|
|
13
|
+
const root = toRecord(doc.getMap("root").toJSON());
|
|
14
|
+
return {
|
|
15
|
+
id,
|
|
16
|
+
...root
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
var applyLoroJsonChange = (doc, change) => {
|
|
20
|
+
const root = doc.getMap("root");
|
|
21
|
+
if (change.type === "delete") {
|
|
22
|
+
root.set("deleted", true);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const value = toRecord(change.value);
|
|
26
|
+
for (const [key, fieldValue] of Object.entries(value)) {
|
|
27
|
+
if (key === "id") continue;
|
|
28
|
+
root.set(key, fieldValue);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
var materializeLoroRichtext = (doc, id) => {
|
|
32
|
+
const metadata = toRecord(doc.getMap("root").toJSON());
|
|
33
|
+
const content = doc.getText("content").toString();
|
|
34
|
+
return {
|
|
35
|
+
id,
|
|
36
|
+
content,
|
|
37
|
+
...metadata
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
var applyLoroRichtextChange = (doc, change) => {
|
|
41
|
+
const text = doc.getText("content");
|
|
42
|
+
const metadata = doc.getMap("root");
|
|
43
|
+
if (change.type === "delete") {
|
|
44
|
+
metadata.set("deleted", true);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const value = toRecord(change.value);
|
|
48
|
+
for (const [key, fieldValue] of Object.entries(value)) {
|
|
49
|
+
if (key === "id") continue;
|
|
50
|
+
if (key === "content") {
|
|
51
|
+
if (typeof fieldValue === "string") text.update(fieldValue);
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
metadata.set(key, fieldValue);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
var createLoroProfile = (profile) => {
|
|
58
|
+
if (profile === "richtext") {
|
|
59
|
+
return {
|
|
60
|
+
materialize: materializeLoroRichtext,
|
|
61
|
+
applyLocalChange: applyLoroRichtextChange
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
materialize: materializeLoroJson,
|
|
66
|
+
applyLocalChange: applyLoroJsonChange
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
var recordIdIdentityPool = /* @__PURE__ */ new Map();
|
|
70
|
+
var nativeRecordIdPool = /* @__PURE__ */ new Map();
|
|
71
|
+
var internRecordIdIdentity = (canonical, preferred) => {
|
|
72
|
+
const cached = recordIdIdentityPool.get(canonical);
|
|
73
|
+
if (cached !== void 0) return cached;
|
|
74
|
+
const created = preferred ?? canonicalToNativeRecordId(canonical);
|
|
75
|
+
recordIdIdentityPool.set(canonical, created);
|
|
76
|
+
return created;
|
|
77
|
+
};
|
|
78
|
+
var getNativeRecordId = (canonical) => {
|
|
79
|
+
const cached = nativeRecordIdPool.get(canonical);
|
|
12
80
|
if (cached) return cached;
|
|
13
81
|
const created = canonicalToNativeRecordId(canonical);
|
|
14
|
-
|
|
82
|
+
nativeRecordIdPool.set(canonical, created);
|
|
15
83
|
return created;
|
|
16
84
|
};
|
|
17
85
|
var canonicalToNativeRecordId = (canonical) => {
|
|
@@ -56,6 +124,26 @@ var isRecordIdString = (value) => {
|
|
|
56
124
|
return idx > 0 && idx < value.length - 1;
|
|
57
125
|
};
|
|
58
126
|
var looksLikeTableName = (value) => /^[A-Za-z_][A-Za-z0-9_-]*$/.test(value);
|
|
127
|
+
var getCtorName = (value) => {
|
|
128
|
+
if (!value || typeof value !== "object") return void 0;
|
|
129
|
+
const ctor = value.constructor;
|
|
130
|
+
return typeof ctor?.name === "string" ? ctor.name : void 0;
|
|
131
|
+
};
|
|
132
|
+
var isCrossRuntimeRecordIdObject = (value) => {
|
|
133
|
+
if (!value || typeof value !== "object" || value instanceof surrealdb.RecordId) {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
const obj = value;
|
|
137
|
+
if (typeof obj.toString !== "function") return false;
|
|
138
|
+
if (!("table" in obj) || !("id" in obj)) return false;
|
|
139
|
+
const ctorName = getCtorName(value);
|
|
140
|
+
if (!ctorName || !/^RecordId\d*$/.test(ctorName)) return false;
|
|
141
|
+
const tableValue = obj.table;
|
|
142
|
+
const table = typeof tableValue === "string" ? tableValue : tableValue != null ? String(tableValue) : "";
|
|
143
|
+
const idValue = obj.id;
|
|
144
|
+
const idIsPrimitive = typeof idValue === "string" || typeof idValue === "number" || typeof idValue === "bigint";
|
|
145
|
+
return idIsPrimitive && looksLikeTableName(stripOuterQuotes(table).trim());
|
|
146
|
+
};
|
|
59
147
|
var toCanonicalRecordIdString = (value) => {
|
|
60
148
|
const normalized = toRecordIdString(value);
|
|
61
149
|
if (!isRecordIdString(normalized)) return void 0;
|
|
@@ -72,6 +160,16 @@ var unwrapIdWrapper = (value) => {
|
|
|
72
160
|
if (keys.length !== 1 || keys[0] !== "id") return void 0;
|
|
73
161
|
return obj.id;
|
|
74
162
|
};
|
|
163
|
+
var asCanonicalRecordIdFromCrossRuntimeObject = (value) => {
|
|
164
|
+
if (!isCrossRuntimeRecordIdObject(value)) return void 0;
|
|
165
|
+
const obj = value;
|
|
166
|
+
const tableRaw = typeof obj.table === "string" ? obj.table : String(obj.table);
|
|
167
|
+
const table = stripOuterQuotes(tableRaw).trim();
|
|
168
|
+
const id = String(obj.id);
|
|
169
|
+
const fromFields = table && id && looksLikeTableName(table) ? `${table}:${id}` : void 0;
|
|
170
|
+
if (fromFields) return toCanonicalRecordIdString(fromFields);
|
|
171
|
+
return toCanonicalRecordIdString(String(obj.toString()));
|
|
172
|
+
};
|
|
75
173
|
var asCanonicalRecordIdString = (value) => {
|
|
76
174
|
if (typeof value === "string") {
|
|
77
175
|
return toCanonicalRecordIdString(value);
|
|
@@ -79,6 +177,8 @@ var asCanonicalRecordIdString = (value) => {
|
|
|
79
177
|
if (value instanceof surrealdb.RecordId) {
|
|
80
178
|
return toCanonicalRecordIdString(value.toString());
|
|
81
179
|
}
|
|
180
|
+
const crossRuntimeCanonical = asCanonicalRecordIdFromCrossRuntimeObject(value);
|
|
181
|
+
if (crossRuntimeCanonical) return crossRuntimeCanonical;
|
|
82
182
|
const wrappedId = unwrapIdWrapper(value);
|
|
83
183
|
if (wrappedId === void 0 || wrappedId === value) return void 0;
|
|
84
184
|
return asCanonicalRecordIdString(wrappedId);
|
|
@@ -87,52 +187,36 @@ var toNativeRecordIdLikeValue = (value) => {
|
|
|
87
187
|
if (value instanceof surrealdb.RecordId) {
|
|
88
188
|
const canonical2 = asCanonicalRecordIdString(value);
|
|
89
189
|
if (!canonical2) return value;
|
|
90
|
-
return
|
|
190
|
+
return getNativeRecordId(canonical2);
|
|
91
191
|
}
|
|
92
192
|
const canonical = asCanonicalRecordIdString(value);
|
|
93
193
|
if (!canonical) return value;
|
|
94
|
-
return
|
|
194
|
+
return getNativeRecordId(canonical);
|
|
95
195
|
};
|
|
96
|
-
var
|
|
97
|
-
const canonical = asCanonicalRecordIdString(value);
|
|
98
|
-
if (!canonical) return normalizeRecordIdLikeValue(value);
|
|
196
|
+
var normalizeRecordIdLikeValue = (value) => {
|
|
99
197
|
if (value instanceof surrealdb.RecordId) {
|
|
100
|
-
|
|
101
|
-
return value;
|
|
102
|
-
|
|
103
|
-
const wrappedId = unwrapIdWrapper(value);
|
|
104
|
-
if (wrappedId instanceof surrealdb.RecordId) {
|
|
105
|
-
recordIdInternPool.set(canonical, wrappedId);
|
|
106
|
-
return wrappedId;
|
|
107
|
-
}
|
|
108
|
-
return internRecordId(canonical);
|
|
109
|
-
};
|
|
110
|
-
var preferRecordIdLikeIdentityDeep = (value) => {
|
|
111
|
-
const preferred = preferRecordIdLikeIdentity(value);
|
|
112
|
-
if (Array.isArray(preferred)) {
|
|
113
|
-
return preferred.map(
|
|
114
|
-
(item) => preferRecordIdLikeIdentityDeep(item)
|
|
115
|
-
);
|
|
198
|
+
const canonical2 = asCanonicalRecordIdString(value);
|
|
199
|
+
if (!canonical2) return value;
|
|
200
|
+
return internRecordIdIdentity(canonical2, value);
|
|
116
201
|
}
|
|
117
|
-
if (
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
202
|
+
if (typeof value === "object" && value !== null) {
|
|
203
|
+
const canonical2 = asCanonicalRecordIdString(value);
|
|
204
|
+
if (!canonical2) return value;
|
|
205
|
+
if (isCrossRuntimeRecordIdObject(value)) {
|
|
206
|
+
return internRecordIdIdentity(canonical2, value);
|
|
121
207
|
}
|
|
122
|
-
|
|
208
|
+
const wrappedId = unwrapIdWrapper(value);
|
|
209
|
+
if (wrappedId instanceof surrealdb.RecordId || isCrossRuntimeRecordIdObject(wrappedId)) {
|
|
210
|
+
return internRecordIdIdentity(canonical2, wrappedId);
|
|
211
|
+
}
|
|
212
|
+
return internRecordIdIdentity(canonical2);
|
|
123
213
|
}
|
|
124
|
-
return preferred;
|
|
125
|
-
};
|
|
126
|
-
var normalizeRecordIdLikeValue = (value) => {
|
|
127
|
-
if (value instanceof surrealdb.RecordId) return toNativeRecordIdLikeValue(value);
|
|
128
|
-
if (typeof value === "object" && value !== null)
|
|
129
|
-
return toNativeRecordIdLikeValue(value);
|
|
130
214
|
if (typeof value !== "string") return value;
|
|
131
215
|
const trimmed = value.trim();
|
|
132
216
|
const unquoted = stripOuterQuotes(trimmed);
|
|
133
217
|
const canonical = toCanonicalRecordIdString(unquoted) ?? (unquoted === trimmed ? void 0 : toCanonicalRecordIdString(trimmed));
|
|
134
218
|
if (canonical) {
|
|
135
|
-
return
|
|
219
|
+
return internRecordIdIdentity(canonical);
|
|
136
220
|
}
|
|
137
221
|
return value;
|
|
138
222
|
};
|
|
@@ -169,126 +253,6 @@ var toRecordId = (tableName, id) => {
|
|
|
169
253
|
const key = normalized.startsWith(prefixed) ? normalized.slice(prefixed.length) : normalized;
|
|
170
254
|
return new surrealdb.RecordId(tableName, key);
|
|
171
255
|
};
|
|
172
|
-
var parseRecordIdLike = (value) => {
|
|
173
|
-
const normalized = normalizeRecordIdLikeValue(value);
|
|
174
|
-
if (normalized instanceof surrealdb.RecordId) {
|
|
175
|
-
return {
|
|
176
|
-
table: String(normalized.table),
|
|
177
|
-
id: String(normalized.id)
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
const str = toRecordIdString(String(normalized));
|
|
181
|
-
const idx = str.indexOf(":");
|
|
182
|
-
if (idx <= 0 || idx >= str.length - 1) {
|
|
183
|
-
throw new Error(
|
|
184
|
-
`Expected a record id in 'table:id' format, received '${String(value)}'.`
|
|
185
|
-
);
|
|
186
|
-
}
|
|
187
|
-
return {
|
|
188
|
-
table: str.slice(0, idx),
|
|
189
|
-
id: str.slice(idx + 1)
|
|
190
|
-
};
|
|
191
|
-
};
|
|
192
|
-
var eqRecordId = (field, value) => {
|
|
193
|
-
const parsed = parseRecordIdLike(value);
|
|
194
|
-
const fieldRef = field;
|
|
195
|
-
const canonical = `${parsed.table}:${parsed.id}`;
|
|
196
|
-
return db.or(
|
|
197
|
-
db.eq(fieldRef, canonical),
|
|
198
|
-
db.eq(fieldRef.id, parsed.id)
|
|
199
|
-
);
|
|
200
|
-
};
|
|
201
|
-
var serializeValue = (value) => {
|
|
202
|
-
const canonicalRecordId = asCanonicalRecordIdString(value);
|
|
203
|
-
if (canonicalRecordId) {
|
|
204
|
-
return {
|
|
205
|
-
__type: "recordid",
|
|
206
|
-
value: canonicalRecordId
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
const normalized = normalizeRecordIdLikeValue(value);
|
|
210
|
-
if (normalized instanceof surrealdb.RecordId) {
|
|
211
|
-
return {
|
|
212
|
-
__type: "recordid",
|
|
213
|
-
value: toRecordIdString(normalized)
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
|
-
if (normalized === void 0) {
|
|
217
|
-
return { __type: "undefined" };
|
|
218
|
-
}
|
|
219
|
-
if (typeof normalized === "number") {
|
|
220
|
-
if (Number.isNaN(normalized)) return { __type: "nan" };
|
|
221
|
-
if (normalized === Number.POSITIVE_INFINITY) {
|
|
222
|
-
return { __type: "infinity", sign: 1 };
|
|
223
|
-
}
|
|
224
|
-
if (normalized === Number.NEGATIVE_INFINITY) {
|
|
225
|
-
return { __type: "infinity", sign: -1 };
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
if (normalized === null || typeof normalized === "string" || typeof normalized === "number" || typeof normalized === "boolean") {
|
|
229
|
-
return normalized;
|
|
230
|
-
}
|
|
231
|
-
if (normalized instanceof Date) {
|
|
232
|
-
return { __type: "date", value: normalized.toJSON() };
|
|
233
|
-
}
|
|
234
|
-
if (Array.isArray(normalized)) {
|
|
235
|
-
return normalized.map((item) => serializeValue(item));
|
|
236
|
-
}
|
|
237
|
-
if (typeof normalized === "object") {
|
|
238
|
-
const entries = Object.entries(
|
|
239
|
-
normalized
|
|
240
|
-
).sort(([a], [b]) => a.localeCompare(b));
|
|
241
|
-
return Object.fromEntries(
|
|
242
|
-
entries.map(([key, item]) => [key, serializeValue(item)])
|
|
243
|
-
);
|
|
244
|
-
}
|
|
245
|
-
return normalized;
|
|
246
|
-
};
|
|
247
|
-
var serializeExpression = (expr) => {
|
|
248
|
-
if (!expr) return null;
|
|
249
|
-
switch (expr.type) {
|
|
250
|
-
case "val":
|
|
251
|
-
return {
|
|
252
|
-
type: "val",
|
|
253
|
-
value: serializeValue(expr.value)
|
|
254
|
-
};
|
|
255
|
-
case "ref":
|
|
256
|
-
return {
|
|
257
|
-
type: "ref",
|
|
258
|
-
path: [...expr.path]
|
|
259
|
-
};
|
|
260
|
-
case "func":
|
|
261
|
-
return {
|
|
262
|
-
type: "func",
|
|
263
|
-
name: expr.name,
|
|
264
|
-
args: expr.args.map(
|
|
265
|
-
(arg) => serializeExpression(arg)
|
|
266
|
-
)
|
|
267
|
-
};
|
|
268
|
-
default:
|
|
269
|
-
return null;
|
|
270
|
-
}
|
|
271
|
-
};
|
|
272
|
-
var serializeSurrealSubsetOptions = (options) => {
|
|
273
|
-
if (!options) return void 0;
|
|
274
|
-
const out = {};
|
|
275
|
-
if (options.where) out.where = serializeExpression(options.where);
|
|
276
|
-
if (options.orderBy?.length) {
|
|
277
|
-
out.orderBy = options.orderBy.map((clause) => ({
|
|
278
|
-
expression: serializeExpression(
|
|
279
|
-
clause.expression
|
|
280
|
-
),
|
|
281
|
-
direction: clause.compareOptions.direction,
|
|
282
|
-
nulls: clause.compareOptions.nulls,
|
|
283
|
-
stringSort: clause.compareOptions.stringSort,
|
|
284
|
-
locale: clause.compareOptions.locale,
|
|
285
|
-
localeOptions: clause.compareOptions.localeOptions
|
|
286
|
-
}));
|
|
287
|
-
}
|
|
288
|
-
if (options.limit !== void 0) out.limit = options.limit;
|
|
289
|
-
if (options.offset !== void 0) out.offset = options.offset;
|
|
290
|
-
return Object.keys(out).length ? JSON.stringify(out) : void 0;
|
|
291
|
-
};
|
|
292
256
|
var IDENTIFIER_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
293
257
|
var firstRow = (res) => {
|
|
294
258
|
if (!res) return void 0;
|
|
@@ -327,9 +291,10 @@ var mapRelationPath = (path, relation) => {
|
|
|
327
291
|
return path;
|
|
328
292
|
};
|
|
329
293
|
var normalizeFilterValue = (value) => {
|
|
330
|
-
if (Array.isArray(value))
|
|
294
|
+
if (Array.isArray(value))
|
|
331
295
|
return value.map((item) => normalizeFilterValue(item));
|
|
332
|
-
|
|
296
|
+
if (value && typeof value === "object")
|
|
297
|
+
return normalizeRecordIdLikeValue(value);
|
|
333
298
|
const native = toNativeRecordIdLikeValue(value);
|
|
334
299
|
if (native !== value) return native;
|
|
335
300
|
return normalizeRecordIdLikeValue(value);
|
|
@@ -350,7 +315,7 @@ var joinLogical = (op, args) => {
|
|
|
350
315
|
sql: parts.map((part) => `(${part})`).join(` ${op} `)
|
|
351
316
|
};
|
|
352
317
|
};
|
|
353
|
-
var buildSubsetQuery = (table,
|
|
318
|
+
var buildSubsetQuery = (table, subset) => {
|
|
354
319
|
let paramIdx = 0;
|
|
355
320
|
const params = { table: table.name };
|
|
356
321
|
const nextParam = (value) => {
|
|
@@ -371,7 +336,7 @@ var buildSubsetQuery = (table, useLoro, subset) => {
|
|
|
371
336
|
}
|
|
372
337
|
if (isReferencePathCandidate(value)) {
|
|
373
338
|
throw new Error(
|
|
374
|
-
"Got a field reference on the right side of a where comparison. Pass a concrete value
|
|
339
|
+
"Got a field reference on the right side of a where comparison. Pass a concrete value."
|
|
375
340
|
);
|
|
376
341
|
}
|
|
377
342
|
return {
|
|
@@ -439,7 +404,6 @@ var buildSubsetQuery = (table, useLoro, subset) => {
|
|
|
439
404
|
const cursorWhere = whereSqlFrom(subset.cursor.whereFrom);
|
|
440
405
|
if (cursorWhere) whereParts.push(cursorWhere);
|
|
441
406
|
}
|
|
442
|
-
if (useLoro) whereParts.push("sync_deleted = false");
|
|
443
407
|
const whereSql = whereParts.length ? ` WHERE ${whereParts.map((part) => `(${part})`).join(" AND ")}` : "";
|
|
444
408
|
const order = db.parseOrderByExpression(subset?.orderBy);
|
|
445
409
|
const orderSql = order.length ? ` ORDER BY ${order.map(
|
|
@@ -454,14 +418,14 @@ var buildSubsetQuery = (table, useLoro, subset) => {
|
|
|
454
418
|
params
|
|
455
419
|
};
|
|
456
420
|
};
|
|
457
|
-
function manageTable(db,
|
|
421
|
+
function manageTable(db, config) {
|
|
458
422
|
const { name } = config;
|
|
459
423
|
const table = new surrealdb.Table(name);
|
|
460
424
|
const listAll = async () => {
|
|
461
425
|
return loadSubset();
|
|
462
426
|
};
|
|
463
427
|
const loadSubset = async (subset) => {
|
|
464
|
-
const { sql, params } = buildSubsetQuery(config,
|
|
428
|
+
const { sql, params } = buildSubsetQuery(config, subset);
|
|
465
429
|
const [res] = await db.query(sql, params);
|
|
466
430
|
return res ?? [];
|
|
467
431
|
};
|
|
@@ -478,29 +442,11 @@ function manageTable(db, useLoro, config) {
|
|
|
478
442
|
};
|
|
479
443
|
const update = async (id, data) => {
|
|
480
444
|
const { id: _ignoredId, ...rest } = data;
|
|
481
|
-
|
|
482
|
-
await db.update(id).merge(rest);
|
|
483
|
-
return;
|
|
484
|
-
}
|
|
485
|
-
await db.update(id).merge({
|
|
486
|
-
...rest,
|
|
487
|
-
sync_deleted: false,
|
|
488
|
-
updated_at: Date.now()
|
|
489
|
-
});
|
|
445
|
+
await db.update(id).merge(rest);
|
|
490
446
|
};
|
|
491
447
|
const remove = async (id) => {
|
|
492
448
|
await db.delete(id);
|
|
493
449
|
};
|
|
494
|
-
const softDelete = async (id) => {
|
|
495
|
-
if (!useLoro) {
|
|
496
|
-
await db.delete(id);
|
|
497
|
-
return;
|
|
498
|
-
}
|
|
499
|
-
await db.upsert(id).merge({
|
|
500
|
-
sync_deleted: true,
|
|
501
|
-
updated_at: Date.now()
|
|
502
|
-
});
|
|
503
|
-
};
|
|
504
450
|
const subscribe = (cb) => {
|
|
505
451
|
let killed = false;
|
|
506
452
|
let live;
|
|
@@ -530,93 +476,205 @@ function manageTable(db, useLoro, config) {
|
|
|
530
476
|
create,
|
|
531
477
|
update,
|
|
532
478
|
remove,
|
|
533
|
-
softDelete,
|
|
479
|
+
softDelete: remove,
|
|
534
480
|
subscribe
|
|
535
481
|
};
|
|
536
482
|
}
|
|
537
483
|
|
|
538
|
-
// src/
|
|
539
|
-
var
|
|
540
|
-
var
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
if (
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
484
|
+
// src/util.ts
|
|
485
|
+
var encoder = new TextEncoder();
|
|
486
|
+
var decoder = new TextDecoder();
|
|
487
|
+
var hasBuffer = typeof Buffer !== "undefined";
|
|
488
|
+
function toBytes(value) {
|
|
489
|
+
if (typeof value === "string") return encoder.encode(value);
|
|
490
|
+
return encoder.encode(JSON.stringify(value));
|
|
491
|
+
}
|
|
492
|
+
function fromBytes(bytes, deserialize = false) {
|
|
493
|
+
if (deserialize) return JSON.parse(decoder.decode(bytes));
|
|
494
|
+
return decoder.decode(bytes);
|
|
495
|
+
}
|
|
496
|
+
function toBase64(bytes) {
|
|
497
|
+
if (hasBuffer) return Buffer.from(bytes).toString("base64");
|
|
498
|
+
return btoa(String.fromCharCode(...bytes));
|
|
499
|
+
}
|
|
500
|
+
function fromBase64(base64) {
|
|
501
|
+
if (hasBuffer) return new Uint8Array(Buffer.from(base64, "base64"));
|
|
502
|
+
const bin = atob(base64);
|
|
503
|
+
const out = new Uint8Array(bin.length);
|
|
504
|
+
for (let i = 0; i < bin.length; i++) {
|
|
505
|
+
out[i] = bin.charCodeAt(i);
|
|
506
|
+
}
|
|
507
|
+
return out;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// src/encryption/index.ts
|
|
511
|
+
var DEFAULT_ALG = "AES-256-GCM";
|
|
512
|
+
var DEFAULT_KID = "default";
|
|
513
|
+
var DEFAULT_VERSION = 1;
|
|
514
|
+
var resolveCrypto = () => {
|
|
515
|
+
if (typeof globalThis.crypto !== "undefined") return globalThis.crypto;
|
|
516
|
+
throw new Error("Web Crypto API is not available in this runtime.");
|
|
564
517
|
};
|
|
565
|
-
var
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
518
|
+
var toCryptoBytes = (value) => new Uint8Array(value);
|
|
519
|
+
var WebCryptoAESGCM = class _WebCryptoAESGCM {
|
|
520
|
+
alg;
|
|
521
|
+
version;
|
|
522
|
+
kid;
|
|
523
|
+
resolveKey;
|
|
524
|
+
constructor(key, options = {}) {
|
|
525
|
+
this.alg = options.alg ?? DEFAULT_ALG;
|
|
526
|
+
this.version = options.version ?? DEFAULT_VERSION;
|
|
527
|
+
this.kid = options.kid ?? DEFAULT_KID;
|
|
528
|
+
this.resolveKey = options.resolveKey ?? ((incomingKid) => {
|
|
529
|
+
if (incomingKid !== this.kid) {
|
|
530
|
+
throw new Error(`No key configured for kid '${incomingKid}'.`);
|
|
531
|
+
}
|
|
532
|
+
return key;
|
|
533
|
+
});
|
|
571
534
|
}
|
|
572
|
-
|
|
573
|
-
|
|
535
|
+
static async fromRawKey(rawKey, options = {}) {
|
|
536
|
+
const key = await resolveCrypto().subtle.importKey(
|
|
537
|
+
"raw",
|
|
538
|
+
toCryptoBytes(rawKey),
|
|
539
|
+
"AES-GCM",
|
|
540
|
+
false,
|
|
541
|
+
["encrypt", "decrypt"]
|
|
542
|
+
);
|
|
543
|
+
return new _WebCryptoAESGCM(key, options);
|
|
574
544
|
}
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
545
|
+
async keyFor(kid) {
|
|
546
|
+
return await this.resolveKey(kid);
|
|
547
|
+
}
|
|
548
|
+
async encrypt(input) {
|
|
549
|
+
const crypto = resolveCrypto();
|
|
550
|
+
const alg = input.alg ?? this.alg;
|
|
551
|
+
const kid = input.kid ?? this.kid;
|
|
552
|
+
const v = input.v ?? this.version;
|
|
553
|
+
const nonce = crypto.getRandomValues(new Uint8Array(12));
|
|
554
|
+
const key = await this.keyFor(kid);
|
|
555
|
+
const ct = new Uint8Array(
|
|
556
|
+
await crypto.subtle.encrypt(
|
|
557
|
+
{
|
|
558
|
+
name: "AES-GCM",
|
|
559
|
+
iv: nonce,
|
|
560
|
+
additionalData: input.aad ? toCryptoBytes(input.aad) : void 0
|
|
561
|
+
},
|
|
562
|
+
key,
|
|
563
|
+
toCryptoBytes(input.plaintext)
|
|
564
|
+
)
|
|
565
|
+
);
|
|
566
|
+
return {
|
|
567
|
+
v,
|
|
568
|
+
alg,
|
|
569
|
+
kid,
|
|
570
|
+
n: toBase64(nonce),
|
|
571
|
+
ct: toBase64(ct)
|
|
572
|
+
};
|
|
582
573
|
}
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
574
|
+
async decrypt({ envelope, aad }) {
|
|
575
|
+
if (envelope.alg !== this.alg) {
|
|
576
|
+
throw new Error(
|
|
577
|
+
`Unsupported envelope algorithm '${envelope.alg}'. Expected '${this.alg}'.`
|
|
578
|
+
);
|
|
579
|
+
}
|
|
580
|
+
const key = await this.keyFor(envelope.kid);
|
|
581
|
+
const crypto = resolveCrypto();
|
|
582
|
+
const plaintext = await crypto.subtle.decrypt(
|
|
583
|
+
{
|
|
584
|
+
name: "AES-GCM",
|
|
585
|
+
iv: toCryptoBytes(fromBase64(envelope.n)),
|
|
586
|
+
additionalData: aad ? toCryptoBytes(aad) : void 0
|
|
587
|
+
},
|
|
588
|
+
key,
|
|
589
|
+
toCryptoBytes(fromBase64(envelope.ct))
|
|
590
|
+
);
|
|
591
|
+
return new Uint8Array(plaintext);
|
|
586
592
|
}
|
|
587
593
|
};
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
594
|
+
|
|
595
|
+
// src/index.ts
|
|
596
|
+
var TEMP_ID_PREFIX = "__temp__";
|
|
597
|
+
var ENVELOPE_FIELDS = [
|
|
598
|
+
"version",
|
|
599
|
+
"algorithm",
|
|
600
|
+
"key_id",
|
|
601
|
+
"nonce",
|
|
602
|
+
"ciphertext"
|
|
603
|
+
];
|
|
604
|
+
var NOOP = () => {
|
|
605
|
+
};
|
|
606
|
+
var isTableObject = (value) => typeof value === "object" && value !== null && "name" in value && typeof value.name === "string";
|
|
607
|
+
var toTableOptions = (table) => {
|
|
608
|
+
if (typeof table === "string") return { name: table };
|
|
609
|
+
if (table instanceof surrealdb.Table) return { name: table.name };
|
|
610
|
+
if (isTableObject(table)) return table;
|
|
611
|
+
throw new Error("Expected table as string, Table, or { name }.");
|
|
612
|
+
};
|
|
613
|
+
var toTableResource = (table) => {
|
|
614
|
+
const normalized = toTableOptions(table);
|
|
615
|
+
return new surrealdb.Table(normalized.name);
|
|
616
|
+
};
|
|
617
|
+
var tableNameOf = (table) => toTableOptions(table).name;
|
|
618
|
+
var getWriteUtils = (utils) => typeof utils === "object" && utils !== null ? utils : {};
|
|
619
|
+
var firstRow2 = (result) => {
|
|
620
|
+
if (!result) return void 0;
|
|
621
|
+
if (Array.isArray(result)) return result[0];
|
|
622
|
+
return result;
|
|
593
623
|
};
|
|
624
|
+
var omitUndefined = (obj) => Object.fromEntries(
|
|
625
|
+
Object.entries(obj).filter(([, value]) => value !== void 0)
|
|
626
|
+
);
|
|
594
627
|
var createTempRecordId = (tableName) => {
|
|
595
628
|
const suffix = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
596
629
|
return new surrealdb.RecordId(tableName, `${TEMP_ID_PREFIX}${suffix}`);
|
|
597
630
|
};
|
|
598
631
|
var isTempId = (id, tableName) => {
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
return typeof recordKey === "string" && recordKey.startsWith(TEMP_ID_PREFIX);
|
|
602
|
-
}
|
|
603
|
-
const raw = toRecordIdString(id);
|
|
604
|
-
const key = raw.startsWith(`${tableName}:`) ? raw.slice(tableName.length + 1) : raw;
|
|
632
|
+
const normalized = toRecordIdString(id);
|
|
633
|
+
const key = normalized.startsWith(`${tableName}:`) ? normalized.slice(tableName.length + 1) : normalized;
|
|
605
634
|
return key.startsWith(TEMP_ID_PREFIX);
|
|
606
635
|
};
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
const
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
636
|
+
var toEnvelope = (value) => {
|
|
637
|
+
const version = value.version;
|
|
638
|
+
const algorithm = value.algorithm;
|
|
639
|
+
const keyId = value.key_id;
|
|
640
|
+
const nonce = value.nonce;
|
|
641
|
+
const ciphertext = value.ciphertext;
|
|
642
|
+
if (typeof version !== "number" || typeof algorithm !== "string" || typeof keyId !== "string" || typeof nonce !== "string" || typeof ciphertext !== "string") {
|
|
643
|
+
return null;
|
|
644
|
+
}
|
|
645
|
+
return {
|
|
646
|
+
v: version,
|
|
647
|
+
alg: algorithm,
|
|
648
|
+
kid: keyId,
|
|
649
|
+
n: nonce,
|
|
650
|
+
ct: ciphertext
|
|
651
|
+
};
|
|
652
|
+
};
|
|
653
|
+
var toStoredEnvelope = (envelope) => ({
|
|
654
|
+
version: envelope.v,
|
|
655
|
+
algorithm: envelope.alg,
|
|
656
|
+
key_id: envelope.kid,
|
|
657
|
+
nonce: envelope.n,
|
|
658
|
+
ciphertext: envelope.ct
|
|
659
|
+
});
|
|
660
|
+
var stripEnvelopeFields = (value) => {
|
|
661
|
+
const copy = { ...value };
|
|
662
|
+
for (const key of ENVELOPE_FIELDS) delete copy[key];
|
|
663
|
+
return copy;
|
|
664
|
+
};
|
|
665
|
+
var toRecordArray = (rows) => {
|
|
666
|
+
if (!rows) return [];
|
|
667
|
+
return Array.isArray(rows) ? rows : [rows];
|
|
668
|
+
};
|
|
669
|
+
async function queryRows(db, sql, bindings) {
|
|
670
|
+
const result = await db.query(sql, bindings ?? {});
|
|
671
|
+
if (Array.isArray(result)) {
|
|
672
|
+
const first = result[0];
|
|
673
|
+
if (Array.isArray(first)) return first;
|
|
674
|
+
return [];
|
|
675
|
+
}
|
|
676
|
+
return [];
|
|
615
677
|
}
|
|
616
|
-
var getWriteUtils = (utils) => typeof utils === "object" && utils !== null ? utils : {};
|
|
617
|
-
var omitUndefined = (obj) => Object.fromEntries(
|
|
618
|
-
Object.entries(obj).filter(([, value]) => value !== void 0)
|
|
619
|
-
);
|
|
620
678
|
function createInsertSchema(tableName) {
|
|
621
679
|
return {
|
|
622
680
|
"~standard": {
|
|
@@ -639,216 +697,669 @@ function createInsertSchema(tableName) {
|
|
|
639
697
|
}
|
|
640
698
|
};
|
|
641
699
|
}
|
|
642
|
-
function
|
|
643
|
-
id
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
const
|
|
663
|
-
|
|
700
|
+
function defaultAad(ctx) {
|
|
701
|
+
if (ctx.kind === "base") return toBytes(`${ctx.table}:${ctx.id}`);
|
|
702
|
+
const base = ctx.baseTable ?? ctx.table;
|
|
703
|
+
return toBytes(`${ctx.table}:${base}:${ctx.id}`);
|
|
704
|
+
}
|
|
705
|
+
var syncModeFrom = (syncMode) => syncMode ?? "eager";
|
|
706
|
+
var subsetCacheKey = (subset) => JSON.stringify(subset, (_key, value) => {
|
|
707
|
+
if (value instanceof Date) return value.toISOString();
|
|
708
|
+
if (value instanceof surrealdb.RecordId) return toRecordIdString(value);
|
|
709
|
+
if (typeof value === "function") return void 0;
|
|
710
|
+
if (value && typeof value === "object" && "table" in value && "id" in value) {
|
|
711
|
+
try {
|
|
712
|
+
return toRecordIdString(String(value));
|
|
713
|
+
} catch {
|
|
714
|
+
return value;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
return value;
|
|
718
|
+
});
|
|
719
|
+
function modernSurrealCollectionOptions(config) {
|
|
720
|
+
const {
|
|
721
|
+
db,
|
|
722
|
+
table,
|
|
723
|
+
queryClient,
|
|
724
|
+
queryKey,
|
|
725
|
+
onError,
|
|
726
|
+
e2ee,
|
|
727
|
+
crdt,
|
|
728
|
+
syncMode: inputSyncMode
|
|
729
|
+
} = config;
|
|
730
|
+
if (!queryClient || !queryKey) {
|
|
731
|
+
throw new Error("queryClient and queryKey are required.");
|
|
732
|
+
}
|
|
733
|
+
const syncMode = syncModeFrom(inputSyncMode);
|
|
734
|
+
const isOnDemandLike = syncMode === "on-demand" || syncMode === "progressive";
|
|
735
|
+
const isStrictOnDemand = syncMode === "on-demand";
|
|
736
|
+
const queryDrivenSyncMode = isOnDemandLike ? "on-demand" : "eager";
|
|
737
|
+
const tableOptions = toTableOptions(table);
|
|
738
|
+
const tableName = tableOptions.name;
|
|
739
|
+
const tableResource = toTableResource(table);
|
|
740
|
+
const subsetIds = /* @__PURE__ */ new Map();
|
|
741
|
+
const activeOnDemandIds = /* @__PURE__ */ new Set();
|
|
742
|
+
const e2eeEnabled = e2ee?.enabled === true;
|
|
743
|
+
const crdtEnabled = crdt?.enabled === true;
|
|
744
|
+
const defaultCrdtProfile = crdtEnabled ? createLoroProfile(crdt.profile) : void 0;
|
|
745
|
+
const materializeCrdt = crdt?.materialize ?? defaultCrdtProfile?.materialize;
|
|
746
|
+
const applyCrdtLocalChange = crdt?.applyLocalChange ?? defaultCrdtProfile?.applyLocalChange;
|
|
747
|
+
if (crdtEnabled && (!materializeCrdt || !applyCrdtLocalChange)) {
|
|
748
|
+
throw new Error(
|
|
749
|
+
"CRDT profile adapter is missing materialize/applyLocalChange handlers."
|
|
750
|
+
);
|
|
751
|
+
}
|
|
752
|
+
const tableAccess = manageTable(db, tableOptions);
|
|
753
|
+
const docs = /* @__PURE__ */ new Map();
|
|
754
|
+
const aadFor = (ctx) => (e2ee?.aad ?? defaultAad)(ctx);
|
|
755
|
+
const getKey = (row) => toRecordIdString(row.id);
|
|
756
|
+
const normalizeMutationId = (id) => toRecordId(tableName, id);
|
|
757
|
+
const normalizeRow = (row) => {
|
|
664
758
|
const normalized = normalizeRecordIdLikeValueDeep(row);
|
|
665
759
|
return {
|
|
666
760
|
...normalized,
|
|
667
761
|
id: normalizeMutationId(normalized.id)
|
|
668
762
|
};
|
|
669
763
|
};
|
|
670
|
-
const
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
764
|
+
const decodeBaseRow = async (row) => {
|
|
765
|
+
if (!e2eeEnabled) {
|
|
766
|
+
return normalizeRow(row);
|
|
767
|
+
}
|
|
768
|
+
const envelope = toEnvelope(row);
|
|
769
|
+
if (!envelope) return normalizeRow(row);
|
|
770
|
+
const id = toRecordKeyString(row.id);
|
|
771
|
+
const bytes = await e2ee.crypto.decrypt({
|
|
772
|
+
envelope,
|
|
773
|
+
aad: aadFor({ table: tableName, id, kind: "base" })
|
|
774
|
+
});
|
|
775
|
+
const payload = fromBytes(bytes, true);
|
|
776
|
+
const merged = {
|
|
777
|
+
...stripEnvelopeFields(row),
|
|
778
|
+
...payload,
|
|
779
|
+
id: normalizeMutationId(row.id)
|
|
780
|
+
};
|
|
781
|
+
return normalizeRow(merged);
|
|
782
|
+
};
|
|
783
|
+
const encodeBaseRow = async (row, id) => {
|
|
784
|
+
if (!e2eeEnabled) return row;
|
|
785
|
+
const envelope = await e2ee.crypto.encrypt({
|
|
786
|
+
plaintext: toBytes(row),
|
|
787
|
+
aad: aadFor({ table: tableName, id, kind: "base" })
|
|
788
|
+
});
|
|
789
|
+
return toStoredEnvelope(envelope);
|
|
675
790
|
};
|
|
676
|
-
const
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
791
|
+
const updatesTableName = crdtEnabled ? tableNameOf(crdt.updatesTable) : void 0;
|
|
792
|
+
const updatesTable = crdtEnabled ? toTableResource(crdt.updatesTable) : void 0;
|
|
793
|
+
const snapshotsTableName = crdtEnabled && crdt.snapshotsTable ? tableNameOf(crdt.snapshotsTable) : void 0;
|
|
794
|
+
crdtEnabled && crdt.snapshotsTable ? toTableResource(crdt.snapshotsTable) : void 0;
|
|
795
|
+
const getDoc = (id) => {
|
|
796
|
+
const existing = docs.get(id);
|
|
797
|
+
if (existing) return existing;
|
|
798
|
+
const doc = new loroCrdt.LoroDoc();
|
|
799
|
+
docs.set(id, doc);
|
|
800
|
+
return doc;
|
|
680
801
|
};
|
|
681
|
-
const
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
if (
|
|
802
|
+
const docRef = (id) => new surrealdb.RecordId(tableName, id);
|
|
803
|
+
const idFromDocRef = (doc) => toRecordKeyString(doc);
|
|
804
|
+
const resolveActor = (id, change) => {
|
|
805
|
+
if (!crdtEnabled) return void 0;
|
|
806
|
+
const candidate = crdt.actor ?? crdt.localActorId;
|
|
807
|
+
if (typeof candidate === "function") {
|
|
808
|
+
return candidate({ id, change });
|
|
809
|
+
}
|
|
810
|
+
return candidate;
|
|
685
811
|
};
|
|
686
|
-
const
|
|
687
|
-
if (!
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
const l = localJson[idStr];
|
|
693
|
-
if (!l) {
|
|
694
|
-
out.push(s);
|
|
695
|
-
continue;
|
|
812
|
+
const decodeUpdateBytes = async (row, kind) => {
|
|
813
|
+
if (!e2eeEnabled) {
|
|
814
|
+
if (kind === "snapshot") {
|
|
815
|
+
const snapshot = row.snapshot_bytes;
|
|
816
|
+
if (!snapshot) return new Uint8Array();
|
|
817
|
+
return fromBase64(snapshot);
|
|
696
818
|
}
|
|
697
|
-
|
|
698
|
-
|
|
819
|
+
const update = row.update_bytes;
|
|
820
|
+
if (!update) return new Uint8Array();
|
|
821
|
+
return fromBase64(update);
|
|
699
822
|
}
|
|
700
|
-
|
|
823
|
+
const envelope = toEnvelope(row);
|
|
824
|
+
if (!envelope) return new Uint8Array();
|
|
825
|
+
const id = idFromDocRef(row.doc);
|
|
826
|
+
return e2ee.crypto.decrypt({
|
|
827
|
+
envelope,
|
|
828
|
+
aad: aadFor({
|
|
829
|
+
table: kind === "snapshot" ? snapshotsTableName ?? tableName : updatesTableName ?? tableName,
|
|
830
|
+
baseTable: tableName,
|
|
831
|
+
id,
|
|
832
|
+
kind
|
|
833
|
+
})
|
|
834
|
+
});
|
|
835
|
+
};
|
|
836
|
+
const encodeUpdatePayload = async (bytes, id, kind) => {
|
|
837
|
+
if (!e2eeEnabled) {
|
|
838
|
+
return { update_bytes: toBase64(bytes) };
|
|
839
|
+
}
|
|
840
|
+
const targetTable = updatesTableName;
|
|
841
|
+
const envelope = await e2ee.crypto.encrypt({
|
|
842
|
+
plaintext: bytes,
|
|
843
|
+
aad: aadFor({
|
|
844
|
+
table: targetTable ?? tableName,
|
|
845
|
+
baseTable: tableName,
|
|
846
|
+
id,
|
|
847
|
+
kind
|
|
848
|
+
})
|
|
849
|
+
});
|
|
850
|
+
return toStoredEnvelope(envelope);
|
|
851
|
+
};
|
|
852
|
+
const persistCrdtUpdate = async (id, bytes, change) => {
|
|
853
|
+
if (!crdtEnabled || !updatesTable) return;
|
|
854
|
+
const payload = await encodeUpdatePayload(bytes, id, "update");
|
|
855
|
+
const actor = resolveActor(id, change);
|
|
856
|
+
const row = {
|
|
857
|
+
doc: docRef(id),
|
|
858
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
859
|
+
...actor ? { actor } : {},
|
|
860
|
+
...payload
|
|
861
|
+
};
|
|
862
|
+
await db.create(updatesTable).content(row);
|
|
863
|
+
};
|
|
864
|
+
const persistMaterialized = async (materialized) => {
|
|
865
|
+
if (!crdtEnabled || crdt.persistMaterializedView !== true) return;
|
|
866
|
+
const id = toRecordKeyString(materialized.id);
|
|
867
|
+
if (!e2eeEnabled) {
|
|
868
|
+
const { id: _ignoredId, ...rest } = materialized;
|
|
869
|
+
await db.upsert(normalizeMutationId(materialized.id)).merge(rest);
|
|
870
|
+
return;
|
|
871
|
+
}
|
|
872
|
+
const payload = await encodeBaseRow(
|
|
873
|
+
omitUndefined({
|
|
874
|
+
...materialized,
|
|
875
|
+
id: void 0
|
|
876
|
+
}),
|
|
877
|
+
id
|
|
878
|
+
);
|
|
879
|
+
await db.upsert(normalizeMutationId(materialized.id)).merge(payload);
|
|
880
|
+
};
|
|
881
|
+
const hydratePlainRows = async (rows, write, begin, commit, type = "insert") => {
|
|
882
|
+
if (!rows.length) return;
|
|
883
|
+
begin();
|
|
884
|
+
try {
|
|
885
|
+
for (const row of rows) {
|
|
886
|
+
const decoded = await decodeBaseRow(row);
|
|
887
|
+
write({ type, value: decoded });
|
|
888
|
+
}
|
|
889
|
+
} finally {
|
|
890
|
+
commit();
|
|
891
|
+
}
|
|
892
|
+
};
|
|
893
|
+
const hydrateCrdtDoc = async (id, write, begin, commit, writeType = "insert") => {
|
|
894
|
+
if (!crdtEnabled || !updatesTableName) return;
|
|
895
|
+
const doc = getDoc(id);
|
|
896
|
+
let since;
|
|
897
|
+
if (snapshotsTableName) {
|
|
898
|
+
const snapshots = await queryRows(
|
|
899
|
+
db,
|
|
900
|
+
`SELECT * FROM type::table($table) WHERE doc = $doc ORDER BY ts DESC LIMIT 1;`,
|
|
901
|
+
{
|
|
902
|
+
table: snapshotsTableName,
|
|
903
|
+
doc: docRef(id)
|
|
904
|
+
}
|
|
905
|
+
);
|
|
906
|
+
const snapshot = snapshots[0];
|
|
907
|
+
if (snapshot) {
|
|
908
|
+
const bytes = await decodeUpdateBytes(snapshot, "snapshot");
|
|
909
|
+
if (bytes.byteLength) doc.import(bytes);
|
|
910
|
+
since = typeof snapshot.ts === "string" ? snapshot.ts : snapshot.ts instanceof Date ? snapshot.ts.toISOString() : void 0;
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
const updates = await queryRows(
|
|
914
|
+
db,
|
|
915
|
+
since ? `SELECT * FROM type::table($table) WHERE doc = $doc AND ts > $since ORDER BY ts ASC;` : `SELECT * FROM type::table($table) WHERE doc = $doc ORDER BY ts ASC;`,
|
|
916
|
+
{
|
|
917
|
+
table: updatesTableName,
|
|
918
|
+
doc: docRef(id),
|
|
919
|
+
since
|
|
920
|
+
}
|
|
921
|
+
);
|
|
922
|
+
for (const update of updates) {
|
|
923
|
+
const bytes = await decodeUpdateBytes(update, "update");
|
|
924
|
+
if (!bytes.byteLength) continue;
|
|
925
|
+
doc.import(bytes);
|
|
926
|
+
}
|
|
927
|
+
const materialized = normalizeRow(materializeCrdt(doc, id));
|
|
928
|
+
begin();
|
|
929
|
+
try {
|
|
930
|
+
write({ type: writeType, value: materialized });
|
|
931
|
+
} finally {
|
|
932
|
+
commit();
|
|
933
|
+
}
|
|
934
|
+
};
|
|
935
|
+
const updateActiveOnDemandIds = () => {
|
|
936
|
+
activeOnDemandIds.clear();
|
|
937
|
+
for (const ids of subsetIds.values()) {
|
|
938
|
+
for (const id of ids) activeOnDemandIds.add(id);
|
|
939
|
+
}
|
|
940
|
+
};
|
|
941
|
+
const createSyncRuntime = (ctx) => {
|
|
942
|
+
let cleanupBaseLive = NOOP;
|
|
943
|
+
let cleanupUpdateLive = NOOP;
|
|
944
|
+
let killed = false;
|
|
945
|
+
const ensureBaseLive = async () => {
|
|
946
|
+
if (cleanupBaseLive !== NOOP) return;
|
|
947
|
+
if (!db.isFeatureSupported?.(surrealdb.Features.LiveQueries)) return;
|
|
948
|
+
const live = await db.live(
|
|
949
|
+
tableResource
|
|
950
|
+
);
|
|
951
|
+
if (killed) {
|
|
952
|
+
await live.kill();
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
live.subscribe(async (message) => {
|
|
956
|
+
if (message.action === "KILLED") return;
|
|
957
|
+
const row = message.value;
|
|
958
|
+
const id = toRecordKeyString(row.id);
|
|
959
|
+
const wasVisible = activeOnDemandIds.has(id);
|
|
960
|
+
if (isStrictOnDemand && !wasVisible && message.action !== "DELETE")
|
|
961
|
+
return;
|
|
962
|
+
if (message.action === "DELETE") {
|
|
963
|
+
for (const ids of subsetIds.values()) ids.delete(id);
|
|
964
|
+
updateActiveOnDemandIds();
|
|
965
|
+
if (isStrictOnDemand && !wasVisible) return;
|
|
966
|
+
ctx.begin();
|
|
967
|
+
try {
|
|
968
|
+
ctx.write({
|
|
969
|
+
type: "delete",
|
|
970
|
+
key: `${tableName}:${id}`
|
|
971
|
+
});
|
|
972
|
+
} finally {
|
|
973
|
+
ctx.commit();
|
|
974
|
+
}
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
const decoded = await decodeBaseRow(row);
|
|
978
|
+
ctx.begin();
|
|
979
|
+
try {
|
|
980
|
+
ctx.write({
|
|
981
|
+
type: message.action === "CREATE" ? "insert" : "update",
|
|
982
|
+
value: decoded
|
|
983
|
+
});
|
|
984
|
+
} finally {
|
|
985
|
+
ctx.commit();
|
|
986
|
+
}
|
|
987
|
+
});
|
|
988
|
+
cleanupBaseLive = () => {
|
|
989
|
+
void live.kill().catch(() => void 0);
|
|
990
|
+
cleanupBaseLive = NOOP;
|
|
991
|
+
};
|
|
992
|
+
};
|
|
993
|
+
const ensureUpdateLive = async () => {
|
|
994
|
+
if (!crdtEnabled || !updatesTable) return;
|
|
995
|
+
if (cleanupUpdateLive !== NOOP) return;
|
|
996
|
+
if (!db.isFeatureSupported?.(surrealdb.Features.LiveQueries)) return;
|
|
997
|
+
const live = await db.live(
|
|
998
|
+
updatesTable
|
|
999
|
+
);
|
|
1000
|
+
if (killed) {
|
|
1001
|
+
await live.kill();
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
1004
|
+
live.subscribe(async (message) => {
|
|
1005
|
+
if (message.action === "KILLED") return;
|
|
1006
|
+
if (message.action === "DELETE") return;
|
|
1007
|
+
const value = message.value;
|
|
1008
|
+
const id = idFromDocRef(value.doc);
|
|
1009
|
+
if (value.actor && value.actor === resolveActor(id)) return;
|
|
1010
|
+
if (isStrictOnDemand && !activeOnDemandIds.has(id)) return;
|
|
1011
|
+
const doc = getDoc(id);
|
|
1012
|
+
const bytes = await decodeUpdateBytes(value, "update");
|
|
1013
|
+
if (!bytes.byteLength) return;
|
|
1014
|
+
doc.import(bytes);
|
|
1015
|
+
const materialized = normalizeRow(materializeCrdt(doc, id));
|
|
1016
|
+
ctx.begin();
|
|
1017
|
+
try {
|
|
1018
|
+
ctx.write({ type: "update", value: materialized });
|
|
1019
|
+
} finally {
|
|
1020
|
+
ctx.commit();
|
|
1021
|
+
}
|
|
1022
|
+
});
|
|
1023
|
+
cleanupUpdateLive = () => {
|
|
1024
|
+
void live.kill().catch(() => void 0);
|
|
1025
|
+
cleanupUpdateLive = NOOP;
|
|
1026
|
+
};
|
|
1027
|
+
};
|
|
1028
|
+
const loadSubset = async (subset) => {
|
|
1029
|
+
if (!isOnDemandLike) return;
|
|
1030
|
+
const key = subsetCacheKey(subset);
|
|
1031
|
+
const rows = await tableAccess.loadSubset(subset);
|
|
1032
|
+
const ids = new Set(
|
|
1033
|
+
rows.map(
|
|
1034
|
+
(row) => toRecordKeyString(row.id)
|
|
1035
|
+
)
|
|
1036
|
+
);
|
|
1037
|
+
subsetIds.set(key, ids);
|
|
1038
|
+
updateActiveOnDemandIds();
|
|
1039
|
+
if (!crdtEnabled) {
|
|
1040
|
+
await hydratePlainRows(
|
|
1041
|
+
rows,
|
|
1042
|
+
ctx.write,
|
|
1043
|
+
ctx.begin,
|
|
1044
|
+
ctx.commit,
|
|
1045
|
+
"insert"
|
|
1046
|
+
);
|
|
1047
|
+
await ensureBaseLive();
|
|
1048
|
+
return;
|
|
1049
|
+
}
|
|
1050
|
+
for (const id of ids) {
|
|
1051
|
+
await hydrateCrdtDoc(
|
|
1052
|
+
id,
|
|
1053
|
+
ctx.write,
|
|
1054
|
+
ctx.begin,
|
|
1055
|
+
ctx.commit,
|
|
1056
|
+
"insert"
|
|
1057
|
+
);
|
|
1058
|
+
}
|
|
1059
|
+
await ensureUpdateLive();
|
|
1060
|
+
};
|
|
1061
|
+
const unloadSubset = (subset) => {
|
|
1062
|
+
if (!isOnDemandLike) return;
|
|
1063
|
+
const key = subsetCacheKey(subset);
|
|
1064
|
+
subsetIds.delete(key);
|
|
1065
|
+
updateActiveOnDemandIds();
|
|
1066
|
+
if (subsetIds.size === 0) {
|
|
1067
|
+
cleanupBaseLive();
|
|
1068
|
+
cleanupUpdateLive();
|
|
1069
|
+
}
|
|
1070
|
+
};
|
|
1071
|
+
const startRealtime = async () => {
|
|
1072
|
+
if (!crdtEnabled) {
|
|
1073
|
+
await ensureBaseLive();
|
|
1074
|
+
return;
|
|
1075
|
+
}
|
|
1076
|
+
await ensureUpdateLive();
|
|
1077
|
+
};
|
|
1078
|
+
const cleanup = () => {
|
|
1079
|
+
killed = true;
|
|
1080
|
+
subsetIds.clear();
|
|
1081
|
+
updateActiveOnDemandIds();
|
|
1082
|
+
cleanupBaseLive();
|
|
1083
|
+
cleanupUpdateLive();
|
|
1084
|
+
};
|
|
1085
|
+
return {
|
|
1086
|
+
startRealtime,
|
|
1087
|
+
cleanup,
|
|
1088
|
+
loadSubset,
|
|
1089
|
+
unloadSubset
|
|
1090
|
+
};
|
|
701
1091
|
};
|
|
702
1092
|
const base = queryDbCollection.queryCollectionOptions({
|
|
703
|
-
schema: createInsertSchema(
|
|
1093
|
+
schema: createInsertSchema(tableName),
|
|
704
1094
|
getKey,
|
|
705
|
-
queryKey
|
|
1095
|
+
queryKey,
|
|
706
1096
|
queryClient,
|
|
707
|
-
syncMode,
|
|
1097
|
+
syncMode: queryDrivenSyncMode,
|
|
708
1098
|
queryFn: async ({ meta }) => {
|
|
709
1099
|
try {
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
(
|
|
1100
|
+
if (isOnDemandLike && !meta?.loadSubsetOptions) {
|
|
1101
|
+
return [];
|
|
1102
|
+
}
|
|
1103
|
+
if (!crdtEnabled) {
|
|
1104
|
+
if (!isOnDemandLike) {
|
|
1105
|
+
const rows2 = await toRecordArray(
|
|
1106
|
+
await db.select(tableResource)
|
|
1107
|
+
);
|
|
1108
|
+
const decoded2 = await Promise.all(
|
|
1109
|
+
rows2.map(
|
|
1110
|
+
(row) => decodeBaseRow(row)
|
|
1111
|
+
)
|
|
1112
|
+
);
|
|
1113
|
+
return decoded2;
|
|
1114
|
+
}
|
|
1115
|
+
const rows = await tableAccess.loadSubset(
|
|
1116
|
+
meta?.loadSubsetOptions
|
|
1117
|
+
);
|
|
1118
|
+
const decoded = await Promise.all(
|
|
1119
|
+
rows.map(
|
|
1120
|
+
(row) => decodeBaseRow(row)
|
|
1121
|
+
)
|
|
1122
|
+
);
|
|
1123
|
+
return decoded;
|
|
1124
|
+
}
|
|
1125
|
+
if (isOnDemandLike) return [];
|
|
1126
|
+
if (!updatesTableName) return [];
|
|
1127
|
+
const updates = await queryRows(
|
|
1128
|
+
db,
|
|
1129
|
+
`SELECT * FROM type::table($table) ORDER BY ts ASC;`,
|
|
1130
|
+
{ table: updatesTableName }
|
|
1131
|
+
);
|
|
1132
|
+
for (const update of updates) {
|
|
1133
|
+
const id = idFromDocRef(update.doc);
|
|
1134
|
+
const doc = getDoc(id);
|
|
1135
|
+
const bytes = await decodeUpdateBytes(update, "update");
|
|
1136
|
+
if (!bytes.byteLength) continue;
|
|
1137
|
+
doc.import(bytes);
|
|
1138
|
+
}
|
|
1139
|
+
return [...docs.entries()].map(
|
|
1140
|
+
([id, doc]) => normalizeRow(materializeCrdt(doc, id))
|
|
715
1141
|
);
|
|
716
|
-
} catch (
|
|
717
|
-
onError?.(
|
|
1142
|
+
} catch (error) {
|
|
1143
|
+
onError?.(error);
|
|
718
1144
|
return [];
|
|
719
1145
|
}
|
|
720
1146
|
},
|
|
721
|
-
onInsert: (async (
|
|
722
|
-
const
|
|
723
|
-
const
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
if (useLoro && persisted?.id) {
|
|
748
|
-
loroRemove(tempKey, false);
|
|
749
|
-
loroPut(resolvedRow, false);
|
|
1147
|
+
onInsert: (async (params) => {
|
|
1148
|
+
const out = [];
|
|
1149
|
+
const writeUtils = getWriteUtils(params.collection.utils);
|
|
1150
|
+
for (const mutation of params.transaction.mutations) {
|
|
1151
|
+
if (mutation.type !== "insert") continue;
|
|
1152
|
+
const normalized = normalizeRow(mutation.modified);
|
|
1153
|
+
if (!crdtEnabled) {
|
|
1154
|
+
const idKey = toRecordKeyString(normalized.id);
|
|
1155
|
+
const payload = omitUndefined({
|
|
1156
|
+
...normalized,
|
|
1157
|
+
id: void 0
|
|
1158
|
+
});
|
|
1159
|
+
const recordPayload = await encodeBaseRow(payload, idKey);
|
|
1160
|
+
if (isTempId(normalized.id, tableName)) {
|
|
1161
|
+
const created = await db.create(tableResource).content(recordPayload);
|
|
1162
|
+
const createdRow = firstRow2(
|
|
1163
|
+
toRecordArray(created)
|
|
1164
|
+
);
|
|
1165
|
+
const createdId = createdRow && createdRow.id ? createdRow.id : normalized.id;
|
|
1166
|
+
const resolved = {
|
|
1167
|
+
...normalized,
|
|
1168
|
+
id: normalizeMutationId(createdId)
|
|
1169
|
+
};
|
|
1170
|
+
out.push(resolved);
|
|
1171
|
+
writeUtils.writeUpsert?.(resolved);
|
|
1172
|
+
continue;
|
|
750
1173
|
}
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
}) : normalizedRow
|
|
759
|
-
);
|
|
1174
|
+
await db.insert(tableResource, {
|
|
1175
|
+
id: normalizeMutationId(normalized.id),
|
|
1176
|
+
...recordPayload
|
|
1177
|
+
});
|
|
1178
|
+
out.push(normalized);
|
|
1179
|
+
writeUtils.writeUpsert?.(normalized);
|
|
1180
|
+
continue;
|
|
760
1181
|
}
|
|
1182
|
+
const id = toRecordKeyString(normalized.id);
|
|
1183
|
+
const doc = getDoc(id);
|
|
1184
|
+
const vv = doc.oplogVersion();
|
|
1185
|
+
const localChange = {
|
|
1186
|
+
type: "insert",
|
|
1187
|
+
value: normalized
|
|
1188
|
+
};
|
|
1189
|
+
applyCrdtLocalChange(doc, localChange);
|
|
1190
|
+
const bytes = doc.export({ mode: "update", from: vv });
|
|
1191
|
+
await persistCrdtUpdate(id, bytes, localChange);
|
|
1192
|
+
const materialized = normalizeRow(materializeCrdt(doc, id));
|
|
1193
|
+
await persistMaterialized(materialized);
|
|
1194
|
+
out.push(materialized);
|
|
1195
|
+
writeUtils.writeUpsert?.(materialized);
|
|
761
1196
|
}
|
|
762
|
-
|
|
763
|
-
return resultRows;
|
|
1197
|
+
return out;
|
|
764
1198
|
}),
|
|
765
|
-
onUpdate: (async (
|
|
766
|
-
const
|
|
767
|
-
const
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
1199
|
+
onUpdate: (async (params) => {
|
|
1200
|
+
const writeUtils = getWriteUtils(params.collection.utils);
|
|
1201
|
+
for (const mutation of params.transaction.mutations) {
|
|
1202
|
+
if (mutation.type !== "update") continue;
|
|
1203
|
+
const mutationId = normalizeMutationId(
|
|
1204
|
+
mutation.key
|
|
1205
|
+
);
|
|
772
1206
|
const normalizedModified = omitUndefined(
|
|
773
1207
|
normalizeRecordIdLikeFields({
|
|
774
|
-
...
|
|
1208
|
+
...mutation.modified
|
|
775
1209
|
})
|
|
776
1210
|
);
|
|
777
|
-
const
|
|
1211
|
+
const normalized = normalizeRow({
|
|
778
1212
|
...normalizedModified,
|
|
779
|
-
id:
|
|
780
|
-
};
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
1213
|
+
id: mutationId
|
|
1214
|
+
});
|
|
1215
|
+
if (!crdtEnabled) {
|
|
1216
|
+
if (!e2eeEnabled) {
|
|
1217
|
+
await db.update(mutationId).merge(normalizedModified);
|
|
1218
|
+
writeUtils.writeUpsert?.(normalized);
|
|
1219
|
+
continue;
|
|
1220
|
+
}
|
|
1221
|
+
const current = await db.select(mutationId);
|
|
1222
|
+
const currentRows = toRecordArray(
|
|
1223
|
+
current
|
|
1224
|
+
);
|
|
1225
|
+
const currentRow = currentRows[0];
|
|
1226
|
+
const decodedCurrent = currentRow ? await decodeBaseRow(currentRow) : { id: mutationId };
|
|
1227
|
+
const merged = {
|
|
1228
|
+
...decodedCurrent,
|
|
1229
|
+
...normalizedModified
|
|
1230
|
+
};
|
|
1231
|
+
delete merged.id;
|
|
1232
|
+
const encoded = await encodeBaseRow(
|
|
1233
|
+
omitUndefined(merged),
|
|
1234
|
+
toRecordKeyString(mutationId)
|
|
1235
|
+
);
|
|
1236
|
+
await db.update(mutationId).merge(encoded);
|
|
1237
|
+
writeUtils.writeUpsert?.(
|
|
1238
|
+
normalizeRow({ ...decodedCurrent, ...merged })
|
|
1239
|
+
);
|
|
1240
|
+
continue;
|
|
786
1241
|
}
|
|
787
|
-
|
|
788
|
-
|
|
1242
|
+
const id = toRecordKeyString(mutationId);
|
|
1243
|
+
const doc = getDoc(id);
|
|
1244
|
+
const vv = doc.oplogVersion();
|
|
1245
|
+
const localChange = {
|
|
1246
|
+
type: "update",
|
|
1247
|
+
value: normalized
|
|
1248
|
+
};
|
|
1249
|
+
applyCrdtLocalChange(doc, localChange);
|
|
1250
|
+
const bytes = doc.export({ mode: "update", from: vv });
|
|
1251
|
+
await persistCrdtUpdate(id, bytes, localChange);
|
|
1252
|
+
const materialized = normalizeRow(materializeCrdt(doc, id));
|
|
1253
|
+
await persistMaterialized(materialized);
|
|
1254
|
+
writeUtils.writeUpsert?.(materialized);
|
|
789
1255
|
}
|
|
790
|
-
if (shouldCommitLoro) commitLoro();
|
|
791
1256
|
return { refetch: false };
|
|
792
1257
|
}),
|
|
793
|
-
onDelete: (async (
|
|
794
|
-
const writeUtils = getWriteUtils(
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
1258
|
+
onDelete: (async (params) => {
|
|
1259
|
+
const writeUtils = getWriteUtils(params.collection.utils);
|
|
1260
|
+
for (const mutation of params.transaction.mutations) {
|
|
1261
|
+
if (mutation.type !== "delete") continue;
|
|
1262
|
+
const mutationId = normalizeMutationId(
|
|
1263
|
+
mutation.key
|
|
1264
|
+
);
|
|
1265
|
+
const id = toRecordKeyString(mutationId);
|
|
1266
|
+
if (!crdtEnabled) {
|
|
1267
|
+
await db.delete(mutationId);
|
|
1268
|
+
writeUtils.writeDelete?.(`${tableName}:${id}`);
|
|
1269
|
+
continue;
|
|
803
1270
|
}
|
|
804
|
-
|
|
805
|
-
|
|
1271
|
+
const doc = getDoc(id);
|
|
1272
|
+
const vv = doc.oplogVersion();
|
|
1273
|
+
const localChange = {
|
|
1274
|
+
type: "delete",
|
|
1275
|
+
value: { id: mutationId }
|
|
1276
|
+
};
|
|
1277
|
+
applyCrdtLocalChange(doc, localChange);
|
|
1278
|
+
const bytes = doc.export({ mode: "update", from: vv });
|
|
1279
|
+
await persistCrdtUpdate(id, bytes, localChange);
|
|
1280
|
+
writeUtils.writeDelete?.(`${tableName}:${id}`);
|
|
806
1281
|
}
|
|
807
|
-
if (shouldCommitLoro) commitLoro();
|
|
808
1282
|
return { refetch: false };
|
|
809
1283
|
})
|
|
810
1284
|
});
|
|
811
1285
|
const baseSync = base.sync?.sync;
|
|
812
1286
|
const sync = baseSync ? {
|
|
813
1287
|
sync: (ctx) => {
|
|
814
|
-
|
|
815
|
-
|
|
1288
|
+
const canRunBaseSync = typeof ctx.collection?.on === "function";
|
|
1289
|
+
const baseResult = canRunBaseSync ? baseSync(ctx) : void 0;
|
|
1290
|
+
const baseCleanup = typeof baseResult === "function" ? baseResult : typeof baseResult === "object" && baseResult && "cleanup" in baseResult && typeof baseResult.cleanup === "function" ? baseResult.cleanup : NOOP;
|
|
1291
|
+
const runtime = createSyncRuntime(
|
|
1292
|
+
ctx
|
|
816
1293
|
);
|
|
817
|
-
const
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
1294
|
+
const start = async () => {
|
|
1295
|
+
if (!isOnDemandLike) {
|
|
1296
|
+
if (!crdtEnabled) {
|
|
1297
|
+
const rows = toRecordArray(
|
|
1298
|
+
await db.select(tableResource)
|
|
1299
|
+
);
|
|
1300
|
+
await hydratePlainRows(
|
|
1301
|
+
rows,
|
|
1302
|
+
ctx.write,
|
|
1303
|
+
ctx.begin,
|
|
1304
|
+
ctx.commit,
|
|
1305
|
+
"insert"
|
|
1306
|
+
);
|
|
1307
|
+
await runtime.startRealtime();
|
|
1308
|
+
ctx.markReady();
|
|
1309
|
+
return;
|
|
828
1310
|
}
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
1311
|
+
if (updatesTableName) {
|
|
1312
|
+
const updates = await queryRows(
|
|
1313
|
+
db,
|
|
1314
|
+
`SELECT * FROM type::table($table) ORDER BY ts ASC;`,
|
|
1315
|
+
{ table: updatesTableName }
|
|
1316
|
+
);
|
|
1317
|
+
for (const update of updates) {
|
|
1318
|
+
const id = idFromDocRef(update.doc);
|
|
1319
|
+
const doc = getDoc(id);
|
|
1320
|
+
const bytes = await decodeUpdateBytes(
|
|
1321
|
+
update,
|
|
1322
|
+
"update"
|
|
1323
|
+
);
|
|
1324
|
+
if (!bytes.byteLength) continue;
|
|
1325
|
+
doc.import(bytes);
|
|
1326
|
+
}
|
|
1327
|
+
ctx.begin();
|
|
1328
|
+
try {
|
|
1329
|
+
for (const [id, doc] of docs.entries()) {
|
|
1330
|
+
ctx.write({
|
|
1331
|
+
type: "insert",
|
|
1332
|
+
value: normalizeRow(
|
|
1333
|
+
materializeCrdt(doc, id)
|
|
1334
|
+
)
|
|
1335
|
+
});
|
|
1336
|
+
}
|
|
1337
|
+
} finally {
|
|
1338
|
+
ctx.commit();
|
|
1339
|
+
}
|
|
1340
|
+
await runtime.startRealtime();
|
|
845
1341
|
}
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
1342
|
+
ctx.markReady();
|
|
1343
|
+
return;
|
|
1344
|
+
}
|
|
1345
|
+
ctx.markReady();
|
|
1346
|
+
if (syncMode === "progressive") {
|
|
1347
|
+
void runtime.loadSubset({}).catch((error) => onError?.(error));
|
|
1348
|
+
}
|
|
1349
|
+
};
|
|
1350
|
+
void start().catch((error) => onError?.(error));
|
|
1351
|
+
return {
|
|
1352
|
+
cleanup: () => {
|
|
1353
|
+
runtime.cleanup();
|
|
1354
|
+
baseCleanup();
|
|
1355
|
+
},
|
|
1356
|
+
loadSubset: async (subset) => {
|
|
1357
|
+
await runtime.loadSubset(subset);
|
|
1358
|
+
},
|
|
1359
|
+
unloadSubset: (subset) => {
|
|
1360
|
+
runtime.unloadSubset(subset);
|
|
1361
|
+
}
|
|
1362
|
+
};
|
|
852
1363
|
}
|
|
853
1364
|
} : void 0;
|
|
854
1365
|
return {
|
|
@@ -856,8 +1367,16 @@ function surrealCollectionOptions({
|
|
|
856
1367
|
sync: sync ?? base.sync
|
|
857
1368
|
};
|
|
858
1369
|
}
|
|
1370
|
+
function surrealCollectionOptions(config) {
|
|
1371
|
+
return modernSurrealCollectionOptions(config);
|
|
1372
|
+
}
|
|
859
1373
|
|
|
860
|
-
exports.
|
|
1374
|
+
exports.WebCryptoAESGCM = WebCryptoAESGCM;
|
|
1375
|
+
exports.applyLoroJsonChange = applyLoroJsonChange;
|
|
1376
|
+
exports.applyLoroRichtextChange = applyLoroRichtextChange;
|
|
1377
|
+
exports.createLoroProfile = createLoroProfile;
|
|
1378
|
+
exports.materializeLoroJson = materializeLoroJson;
|
|
1379
|
+
exports.materializeLoroRichtext = materializeLoroRichtext;
|
|
861
1380
|
exports.surrealCollectionOptions = surrealCollectionOptions;
|
|
862
1381
|
exports.toRecordKeyString = toRecordKeyString;
|
|
863
1382
|
//# sourceMappingURL=index.js.map
|