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