@tensorix/metahub 0.5.0 → 0.5.2
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/dist/cli.js +5743 -14958
- package/dist/cli.js.map +20 -74
- package/dist/core/audit.d.ts.map +1 -1
- package/dist/core/blobs-core.d.ts.map +1 -1
- package/dist/core/crdt.d.ts.map +1 -1
- package/dist/core/db.d.ts +1 -1
- package/dist/core/db.d.ts.map +1 -1
- package/dist/core/integrity.d.ts +5 -0
- package/dist/core/integrity.d.ts.map +1 -1
- package/dist/core/node.d.ts +52 -6
- package/dist/core/node.d.ts.map +1 -1
- package/dist/core/schema-init.d.ts +3 -0
- package/dist/core/schema-init.d.ts.map +1 -1
- package/dist/core/schema.d.ts +1 -1
- package/dist/core/schema.d.ts.map +1 -1
- package/dist/core/snapshot.d.ts.map +1 -1
- package/dist/core/sync/devices.d.ts +6 -0
- package/dist/core/sync/devices.d.ts.map +1 -1
- package/dist/core/sync/peers-routes.d.ts.map +1 -1
- package/dist/core/sync/peers.d.ts +2 -0
- package/dist/core/sync/peers.d.ts.map +1 -1
- package/dist/core/sync/server.d.ts.map +1 -1
- package/dist/core/sync/storage.d.ts +4 -0
- package/dist/core/sync/storage.d.ts.map +1 -1
- package/dist/core/tables.d.ts +16 -0
- package/dist/core/tables.d.ts.map +1 -0
- package/dist/db-worker.js +112 -100
- package/dist/edge-worker.js +96 -68
- package/dist/index.js +2797 -12021
- package/dist/index.js.map +17 -71
- package/dist/metahub-sdk.js +5 -5
- package/dist/sw.js +5 -5
- package/dist/webui/server/routes.d.ts.map +1 -1
- package/dist/webui-fmt-clang.js +1 -1
- package/dist/webui-fmt-sh.js +5 -5
- package/dist/webui-fmt.js +113 -113
- package/dist/webui.js +138 -137
- package/package.json +1 -1
package/dist/edge-worker.js
CHANGED
|
@@ -279,9 +279,9 @@ function lcs(a, b) {
|
|
|
279
279
|
const m = a.length;
|
|
280
280
|
const n = b.length;
|
|
281
281
|
const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
|
|
282
|
-
for (let
|
|
283
|
-
for (let
|
|
284
|
-
dp[
|
|
282
|
+
for (let i = m - 1;i >= 0; i--)
|
|
283
|
+
for (let j = n - 1;j >= 0; j--)
|
|
284
|
+
dp[i][j] = a[i] === b[j] ? dp[i + 1][j + 1] + 1 : Math.max(dp[i + 1][j], dp[i][j + 1]);
|
|
285
285
|
const pairs = [];
|
|
286
286
|
let i = 0;
|
|
287
287
|
let j = 0;
|
|
@@ -411,6 +411,10 @@ var DOMAIN = {
|
|
|
411
411
|
blob_policy: {
|
|
412
412
|
table: "blob_policy",
|
|
413
413
|
cols: new Set(["full_nodes", "redundancy", "__deleted"])
|
|
414
|
+
},
|
|
415
|
+
nodes: {
|
|
416
|
+
table: "nodes",
|
|
417
|
+
cols: new Set(["label", "platform", "form", "app", "first_seen", "__deleted"])
|
|
414
418
|
}
|
|
415
419
|
};
|
|
416
420
|
var RECORD_META = new Set(["database_id", "created_hlc", "order_key", "__deleted"]);
|
|
@@ -428,7 +432,8 @@ var NOT_NULL_ZERO_COLS = new Set([
|
|
|
428
432
|
"sites:spa",
|
|
429
433
|
"site_channels:__deleted",
|
|
430
434
|
"site_files:__deleted",
|
|
431
|
-
"blob_policy:__deleted"
|
|
435
|
+
"blob_policy:__deleted",
|
|
436
|
+
"nodes:__deleted"
|
|
432
437
|
]);
|
|
433
438
|
function decodeMaterializeValue(dataset, col, valueJson) {
|
|
434
439
|
if (valueJson !== null)
|
|
@@ -463,8 +468,8 @@ function materialize(db, dataset, rowId, col, valueJson) {
|
|
|
463
468
|
if (dataset === "records") {
|
|
464
469
|
if (RECORD_META.has(col)) {
|
|
465
470
|
ensureRow(db, "records", rowId);
|
|
466
|
-
const
|
|
467
|
-
db.query(`UPDATE records SET "${col}" = ? WHERE id = ?`).run(encodeScalar(
|
|
471
|
+
const v = decodeMaterializeValue("records", col, valueJson);
|
|
472
|
+
db.query(`UPDATE records SET "${col}" = ? WHERE id = ?`).run(encodeScalar(v), rowId);
|
|
468
473
|
} else {
|
|
469
474
|
ensureRow(db, "records", rowId);
|
|
470
475
|
if (valueJson === null) {
|
|
@@ -603,7 +608,7 @@ function nextPosition(db, databaseId) {
|
|
|
603
608
|
const row = db.query("SELECT MAX(position) AS m FROM properties WHERE database_id = ? AND __deleted = 0").get(databaseId);
|
|
604
609
|
return (row.m ?? 0) + 1;
|
|
605
610
|
}
|
|
606
|
-
var addProperty = grouped(function
|
|
611
|
+
var addProperty = grouped(function addProperty(db, databaseId, opts) {
|
|
607
612
|
if (!getDatabase(db, databaseId))
|
|
608
613
|
throw new MhError("not_found", `no such database: ${databaseId}`);
|
|
609
614
|
if (!PROP_TYPES.has(opts.type))
|
|
@@ -633,7 +638,7 @@ function listProperties(db, databaseId) {
|
|
|
633
638
|
config: r.config ? JSON.parse(r.config) : null
|
|
634
639
|
}));
|
|
635
640
|
}
|
|
636
|
-
var updateProperty = grouped(function
|
|
641
|
+
var updateProperty = grouped(function updateProperty(db, id, fields) {
|
|
637
642
|
const cur = getProperty(db, id);
|
|
638
643
|
if (!cur)
|
|
639
644
|
throw new MhError("not_found", `no such property: ${id}`);
|
|
@@ -677,7 +682,7 @@ function getSelectProperty(db, id) {
|
|
|
677
682
|
throw new MhError("invalid_input", `${prop.type} property has no options`);
|
|
678
683
|
return { prop, options: prop.config?.options ?? [] };
|
|
679
684
|
}
|
|
680
|
-
var renameSelectOption = grouped(function
|
|
685
|
+
var renameSelectOption = grouped(function renameSelectOption(db, id, from, to) {
|
|
681
686
|
const { prop, options } = getSelectProperty(db, id);
|
|
682
687
|
to = to.trim();
|
|
683
688
|
if (!to)
|
|
@@ -710,7 +715,7 @@ var renameSelectOption = grouped(function renameSelectOption2(db, id, from, to)
|
|
|
710
715
|
}
|
|
711
716
|
return { property: getProperty(db, id), renamed };
|
|
712
717
|
});
|
|
713
|
-
var removeSelectOption = grouped(function
|
|
718
|
+
var removeSelectOption = grouped(function removeSelectOption(db, id, name) {
|
|
714
719
|
const { prop, options } = getSelectProperty(db, id);
|
|
715
720
|
if (!options.includes(name))
|
|
716
721
|
throw new MhError("not_found", `no such option: ${name}`);
|
|
@@ -738,7 +743,7 @@ var removeSelectOption = grouped(function removeSelectOption2(db, id, name) {
|
|
|
738
743
|
}
|
|
739
744
|
return { property: getProperty(db, id), cleared };
|
|
740
745
|
});
|
|
741
|
-
var removeProperty = grouped(function
|
|
746
|
+
var removeProperty = grouped(function removeProperty(db, id) {
|
|
742
747
|
const prop = getProperty(db, id);
|
|
743
748
|
if (!prop)
|
|
744
749
|
return false;
|
|
@@ -1041,7 +1046,7 @@ function rebalanceRecordOrderKeys(db, databaseId) {
|
|
|
1041
1046
|
emit(db, "records", row.id, "order_key", keys[i]);
|
|
1042
1047
|
});
|
|
1043
1048
|
}
|
|
1044
|
-
var backfillRecordOrderKeys = grouped(function
|
|
1049
|
+
var backfillRecordOrderKeys = grouped(function backfillRecordOrderKeys(db, databaseId) {
|
|
1045
1050
|
const emitChange = canEmitOrderKeys(db);
|
|
1046
1051
|
const dbRows = databaseId ? [{ database_id: databaseId }] : db.query(`SELECT DISTINCT database_id FROM records
|
|
1047
1052
|
WHERE database_id IS NOT NULL AND __deleted = 0 AND order_key IS NULL`).all();
|
|
@@ -1056,7 +1061,7 @@ var backfillRecordOrderKeys = grouped(function backfillRecordOrderKeys2(db, data
|
|
|
1056
1061
|
missing.forEach((row, i) => writeRecordOrderKey(db, row.id, keys[i], emitChange));
|
|
1057
1062
|
}
|
|
1058
1063
|
});
|
|
1059
|
-
var createRecordPrepared = grouped(function
|
|
1064
|
+
var createRecordPrepared = grouped(function createRecordPrepared(db, database, cells) {
|
|
1060
1065
|
const id = deriveTitle(cells, slugify(database.name, "rec"));
|
|
1061
1066
|
const orderKey = keyBetween(lastRecordOrderKey(db, database.id), null);
|
|
1062
1067
|
const first = emit(db, "records", id, "database_id", database.id);
|
|
@@ -1066,7 +1071,7 @@ var createRecordPrepared = grouped(function createRecordPrepared2(db, database,
|
|
|
1066
1071
|
emit(db, "records", id, prop.id, value);
|
|
1067
1072
|
return getRecord(db, id);
|
|
1068
1073
|
});
|
|
1069
|
-
var createRecord = grouped(function
|
|
1074
|
+
var createRecord = grouped(function createRecord(db, databaseId, data) {
|
|
1070
1075
|
const database = getDatabase(db, databaseId);
|
|
1071
1076
|
if (!database)
|
|
1072
1077
|
throw new MhError("not_found", `no such database: ${databaseId}`);
|
|
@@ -1098,7 +1103,7 @@ function listRecords(db, databaseId, opts = {}) {
|
|
|
1098
1103
|
key = key.slice(1);
|
|
1099
1104
|
}
|
|
1100
1105
|
if (key !== "created" && key !== "created_hlc") {
|
|
1101
|
-
let p = props.find((
|
|
1106
|
+
let p = props.find((p) => p.id === key);
|
|
1102
1107
|
if (!p) {
|
|
1103
1108
|
const matches = props.filter((q) => q.name.toLowerCase() === key.toLowerCase());
|
|
1104
1109
|
if (matches.length > 1)
|
|
@@ -1136,7 +1141,7 @@ function listRecords(db, databaseId, opts = {}) {
|
|
|
1136
1141
|
}
|
|
1137
1142
|
return rows;
|
|
1138
1143
|
}
|
|
1139
|
-
var moveRecord = grouped(function
|
|
1144
|
+
var moveRecord = grouped(function moveRecord(db, id, targetId, where) {
|
|
1140
1145
|
if (where !== "before" && where !== "after")
|
|
1141
1146
|
throw new MhError("invalid_input", `unknown move position: ${where}`);
|
|
1142
1147
|
const src = db.query("SELECT id, database_id FROM records WHERE id = ? AND __deleted = 0").get(id);
|
|
@@ -1166,7 +1171,7 @@ var moveRecord = grouped(function moveRecord2(db, id, targetId, where) {
|
|
|
1166
1171
|
emit(db, "records", id, "order_key", keyBetween(left, right));
|
|
1167
1172
|
return getRecord(db, id);
|
|
1168
1173
|
});
|
|
1169
|
-
var updateRecordPrepared = grouped(function
|
|
1174
|
+
var updateRecordPrepared = grouped(function updateRecordPrepared(db, id, cells) {
|
|
1170
1175
|
const rec = db.query("SELECT id, database_id, data FROM records WHERE id = ? AND __deleted = 0").get(id);
|
|
1171
1176
|
if (!rec)
|
|
1172
1177
|
throw new MhError("not_found", `no such record: ${id}`);
|
|
@@ -1179,13 +1184,13 @@ var updateRecordPrepared = grouped(function updateRecordPrepared2(db, id, cells)
|
|
|
1179
1184
|
}
|
|
1180
1185
|
return getRecord(db, id);
|
|
1181
1186
|
});
|
|
1182
|
-
var updateRecord = grouped(function
|
|
1187
|
+
var updateRecord = grouped(function updateRecord(db, id, data) {
|
|
1183
1188
|
const rec = db.query("SELECT id, database_id FROM records WHERE id = ? AND __deleted = 0").get(id);
|
|
1184
1189
|
if (!rec)
|
|
1185
1190
|
throw new MhError("not_found", `no such record: ${id}`);
|
|
1186
1191
|
return updateRecordPrepared(db, id, prepareRecordCells(db, rec.database_id, data));
|
|
1187
1192
|
});
|
|
1188
|
-
var deleteRecord = grouped(function
|
|
1193
|
+
var deleteRecord = grouped(function deleteRecord(db, id) {
|
|
1189
1194
|
const rec = db.query("SELECT id FROM records WHERE id = ? AND __deleted = 0").get(id);
|
|
1190
1195
|
if (!rec)
|
|
1191
1196
|
return false;
|
|
@@ -1201,7 +1206,7 @@ function rowOut(r) {
|
|
|
1201
1206
|
return null;
|
|
1202
1207
|
return { ...r, meta: r.meta === null ? null : JSON.parse(r.meta) };
|
|
1203
1208
|
}
|
|
1204
|
-
var createDatabase = grouped(function
|
|
1209
|
+
var createDatabase = grouped(function createDatabase(db, opts) {
|
|
1205
1210
|
const id = newId("db", opts.name);
|
|
1206
1211
|
const first = emit(db, "databases", id, "name", opts.name);
|
|
1207
1212
|
emit(db, "databases", id, "created_hlc", first.hlc);
|
|
@@ -1209,7 +1214,7 @@ var createDatabase = grouped(function createDatabase2(db, opts) {
|
|
|
1209
1214
|
emit(db, "databases", id, "icon", opts.icon);
|
|
1210
1215
|
return getDatabase(db, id);
|
|
1211
1216
|
});
|
|
1212
|
-
var updateDatabase = grouped(function
|
|
1217
|
+
var updateDatabase = grouped(function updateDatabase(db, id, fields) {
|
|
1213
1218
|
if (!getDatabase(db, id))
|
|
1214
1219
|
throw new MhError("not_found", `no such database: ${id}`);
|
|
1215
1220
|
if (fields.name !== undefined)
|
|
@@ -1223,7 +1228,7 @@ var updateDatabase = grouped(function updateDatabase2(db, id, fields) {
|
|
|
1223
1228
|
}
|
|
1224
1229
|
return getDatabase(db, id);
|
|
1225
1230
|
});
|
|
1226
|
-
var duplicateDatabase = grouped(function
|
|
1231
|
+
var duplicateDatabase = grouped(function duplicateDatabase(db, id, opts = {}) {
|
|
1227
1232
|
const src = getDatabase(db, id);
|
|
1228
1233
|
if (!src)
|
|
1229
1234
|
throw new MhError("not_found", `no such database: ${id}`);
|
|
@@ -1275,7 +1280,7 @@ var duplicateDatabase = grouped(function duplicateDatabase2(db, id, opts = {}) {
|
|
|
1275
1280
|
function getDatabase(db, id) {
|
|
1276
1281
|
return rowOut(db.query(`SELECT ${DATABASE_SELECT} FROM databases WHERE id = ? AND __deleted = 0`).get(id));
|
|
1277
1282
|
}
|
|
1278
|
-
var deleteDatabase = grouped(function
|
|
1283
|
+
var deleteDatabase = grouped(function deleteDatabase(db, id) {
|
|
1279
1284
|
if (!getDatabase(db, id))
|
|
1280
1285
|
return false;
|
|
1281
1286
|
emit(db, "databases", id, "__deleted", 1);
|
|
@@ -1599,14 +1604,14 @@ function prepareIntent(db, policy, principal, intent, clock) {
|
|
|
1599
1604
|
if (intent.action === "createRecord") {
|
|
1600
1605
|
if (!intent.table || typeof intent.table !== "string")
|
|
1601
1606
|
throw new MhError("invalid_input", "createRecord intent needs a table");
|
|
1602
|
-
const
|
|
1603
|
-
const
|
|
1607
|
+
const database = authorizeDbRef(db, policy.grants, intent.table, "create");
|
|
1608
|
+
const cells = assertGuestPayload(db, policy.grants, principal, database, intent.payload, { limits: policy.limits });
|
|
1604
1609
|
return {
|
|
1605
1610
|
action: intent.action,
|
|
1606
|
-
database
|
|
1607
|
-
cells
|
|
1611
|
+
database,
|
|
1612
|
+
cells,
|
|
1608
1613
|
targetLive: true,
|
|
1609
|
-
fingerprint: fingerprintIntent(intent.action,
|
|
1614
|
+
fingerprint: fingerprintIntent(intent.action, database.id, clock === "submitted" ? intent.recordId ?? null : null, cells)
|
|
1610
1615
|
};
|
|
1611
1616
|
}
|
|
1612
1617
|
if (intent.action !== "updateRecord")
|
|
@@ -1752,10 +1757,10 @@ function isRecordRow(value) {
|
|
|
1752
1757
|
return typeof row.id === "string" && typeof row.database_id === "string" && !!row.values && typeof row.values === "object" && !Array.isArray(row.values) && !!row.cells && typeof row.cells === "object" && !Array.isArray(row.cells);
|
|
1753
1758
|
}
|
|
1754
1759
|
function clampMillis(submittedAt, now) {
|
|
1755
|
-
const
|
|
1756
|
-
const ceil =
|
|
1760
|
+
const safeNow = Math.max(0, Math.trunc(Number.isFinite(now) ? now : Date.now()));
|
|
1761
|
+
const ceil = safeNow + HLC_SKEW_MS;
|
|
1757
1762
|
if (!Number.isFinite(submittedAt))
|
|
1758
|
-
return
|
|
1763
|
+
return safeNow;
|
|
1759
1764
|
return Math.max(0, Math.min(Math.trunc(submittedAt), ceil));
|
|
1760
1765
|
}
|
|
1761
1766
|
function seedCounter(db, node, millis) {
|
|
@@ -1795,12 +1800,12 @@ function stableStringify(value) {
|
|
|
1795
1800
|
|
|
1796
1801
|
// src/core/sync/anti-abuse.ts
|
|
1797
1802
|
function timingSafeEq(a, b) {
|
|
1798
|
-
const
|
|
1803
|
+
const ab = new TextEncoder().encode(a);
|
|
1799
1804
|
const bb = new TextEncoder().encode(b);
|
|
1800
|
-
let diff =
|
|
1801
|
-
const n = Math.max(
|
|
1805
|
+
let diff = ab.length ^ bb.length;
|
|
1806
|
+
const n = Math.max(ab.length, bb.length);
|
|
1802
1807
|
for (let i = 0;i < n; i++)
|
|
1803
|
-
diff |= (
|
|
1808
|
+
diff |= (ab[i] ?? 0) ^ (bb[i] ?? 0);
|
|
1804
1809
|
return diff === 0;
|
|
1805
1810
|
}
|
|
1806
1811
|
var siteverify = async (secret, token, ip) => {
|
|
@@ -2081,7 +2086,7 @@ function assertVisibility(v) {
|
|
|
2081
2086
|
if (v !== "public" && v !== "private")
|
|
2082
2087
|
throw new MhError("invalid_input", `visibility must be "public" or "private", got ${JSON.stringify(v)}`);
|
|
2083
2088
|
}
|
|
2084
|
-
var createSite = grouped(function
|
|
2089
|
+
var createSite = grouped(function createSite(db, opts) {
|
|
2085
2090
|
const name = normalizeSiteName(opts.name);
|
|
2086
2091
|
if (getSiteByName(db, name))
|
|
2087
2092
|
throw new MhError("conflict", `site name already exists: ${name}`);
|
|
@@ -2096,7 +2101,7 @@ var createSite = grouped(function createSite2(db, opts) {
|
|
|
2096
2101
|
emit(db, "sites", id, "visibility", opts.visibility);
|
|
2097
2102
|
return getSite(db, id);
|
|
2098
2103
|
});
|
|
2099
|
-
var updateSite = grouped(function
|
|
2104
|
+
var updateSite = grouped(function updateSite(db, id, opts) {
|
|
2100
2105
|
if (!getSite(db, id))
|
|
2101
2106
|
throw new MhError("not_found", `no such site: ${id}`);
|
|
2102
2107
|
if (opts.visibility !== undefined)
|
|
@@ -2116,14 +2121,14 @@ var updateSite = grouped(function updateSite2(db, id, opts) {
|
|
|
2116
2121
|
emit(db, "sites", id, "spa", opts.spa ? 1 : 0);
|
|
2117
2122
|
return getSite(db, id);
|
|
2118
2123
|
});
|
|
2119
|
-
var setSitePublicGrants = grouped(function
|
|
2124
|
+
var setSitePublicGrants = grouped(function setSitePublicGrants(db, siteId, grants) {
|
|
2120
2125
|
if (!getSite(db, siteId))
|
|
2121
2126
|
throw new MhError("not_found", `no such site: ${siteId}`);
|
|
2122
2127
|
const value = grants == null ? null : serializeGrantSet(validateGrantSetInput(grants));
|
|
2123
2128
|
emit(db, "sites", siteId, "public_grants", value);
|
|
2124
2129
|
return getSite(db, siteId);
|
|
2125
2130
|
});
|
|
2126
|
-
var deleteSite = grouped(function
|
|
2131
|
+
var deleteSite = grouped(function deleteSite(db, id) {
|
|
2127
2132
|
if (!getSite(db, id))
|
|
2128
2133
|
return false;
|
|
2129
2134
|
const files = db.query("SELECT id FROM site_files WHERE site_id = ? AND __deleted = 0").all(id);
|
|
@@ -2136,7 +2141,7 @@ function fileIdFor(db, siteId, path) {
|
|
|
2136
2141
|
const row = db.query("SELECT id FROM site_files WHERE site_id = ? AND path = ? ORDER BY created_hlc LIMIT 1").get(siteId, path);
|
|
2137
2142
|
return row?.id ?? null;
|
|
2138
2143
|
}
|
|
2139
|
-
var deleteFile = grouped(function
|
|
2144
|
+
var deleteFile = grouped(function deleteFile(db, siteId, path) {
|
|
2140
2145
|
const id = fileIdFor(db, siteId, normalizeSitePath(path));
|
|
2141
2146
|
if (!id)
|
|
2142
2147
|
return false;
|
|
@@ -2509,6 +2514,22 @@ CREATE TABLE IF NOT EXISTS blob_policy (
|
|
|
2509
2514
|
redundancy TEXT, -- 'all' | 'any'
|
|
2510
2515
|
__deleted INTEGER NOT NULL DEFAULT 0
|
|
2511
2516
|
);
|
|
2517
|
+
|
|
2518
|
+
-- Synced device roster (system tier, see tables.ts): one row per node id, written
|
|
2519
|
+
-- by the device itself on open (describeSelf) and by anyone who renames it.
|
|
2520
|
+
-- Replicates so every device shows the same name and icon for a peer — the only
|
|
2521
|
+
-- record a purely bucket-joined device leaves besides its oplog stream. Each
|
|
2522
|
+
-- column is its own LWW register, so a rename and a platform refresh never
|
|
2523
|
+
-- clobber each other. meta.node_label survives as a node-local read fallback.
|
|
2524
|
+
CREATE TABLE IF NOT EXISTS nodes (
|
|
2525
|
+
id TEXT PRIMARY KEY, -- node_id
|
|
2526
|
+
label TEXT, -- shared display name; any device may set it
|
|
2527
|
+
platform TEXT, -- macos | windows | linux | ios | android | web
|
|
2528
|
+
form TEXT, -- laptop | desktop | phone | server | browser
|
|
2529
|
+
app TEXT, -- cli | server | desktop | web
|
|
2530
|
+
first_seen INTEGER, -- epoch ms of the device's first self-description
|
|
2531
|
+
__deleted INTEGER NOT NULL DEFAULT 0
|
|
2532
|
+
);
|
|
2512
2533
|
`;
|
|
2513
2534
|
var FTS_SCHEMA = `
|
|
2514
2535
|
CREATE VIRTUAL TABLE IF NOT EXISTS search_fts USING fts5(
|
|
@@ -2600,7 +2621,7 @@ function documentVersion(db, id) {
|
|
|
2600
2621
|
OR (dataset = 'doc_blocks' AND row_id IN (SELECT id FROM doc_blocks WHERE doc_id = ?))`).get(id, id);
|
|
2601
2622
|
return row.h ?? "";
|
|
2602
2623
|
}
|
|
2603
|
-
var createDocument = grouped(function
|
|
2624
|
+
var createDocument = grouped(function createDocument(db, opts) {
|
|
2604
2625
|
const id = newId("doc", opts.title);
|
|
2605
2626
|
const first = emit(db, "documents", id, "title", opts.title);
|
|
2606
2627
|
emit(db, "documents", id, "created_hlc", first.hlc);
|
|
@@ -2664,7 +2685,7 @@ function backfillSiblings(db, parentId) {
|
|
|
2664
2685
|
db.query("UPDATE documents SET order_key = ? WHERE id = ?").run(keys[i], row.id);
|
|
2665
2686
|
});
|
|
2666
2687
|
}
|
|
2667
|
-
var backfillDocumentOrderKeys = grouped(function
|
|
2688
|
+
var backfillDocumentOrderKeys = grouped(function backfillDocumentOrderKeys(db) {
|
|
2668
2689
|
const parents = db.query("SELECT DISTINCT parent_id FROM documents WHERE __deleted = 0 AND order_key IS NULL").all();
|
|
2669
2690
|
for (const p of parents)
|
|
2670
2691
|
backfillSiblings(db, p.parent_id);
|
|
@@ -2700,7 +2721,7 @@ function placeInSiblings(db, id, parentId, anchor) {
|
|
|
2700
2721
|
}
|
|
2701
2722
|
emit(db, "documents", id, "order_key", key);
|
|
2702
2723
|
}
|
|
2703
|
-
var moveDocument = grouped(function
|
|
2724
|
+
var moveDocument = grouped(function moveDocument(db, id, targetId, where) {
|
|
2704
2725
|
const src = getDocument(db, id);
|
|
2705
2726
|
if (!src)
|
|
2706
2727
|
throw new MhError("not_found", `no such document: ${id}`);
|
|
@@ -2715,7 +2736,7 @@ var moveDocument = grouped(function moveDocument2(db, id, targetId, where) {
|
|
|
2715
2736
|
placeInSiblings(db, id, target.parent_id, { targetId, where });
|
|
2716
2737
|
return getDocument(db, id);
|
|
2717
2738
|
});
|
|
2718
|
-
var duplicateDocument = grouped(function
|
|
2739
|
+
var duplicateDocument = grouped(function duplicateDocument(db, id, opts = {}) {
|
|
2719
2740
|
const src = getDocument(db, id);
|
|
2720
2741
|
if (!src)
|
|
2721
2742
|
throw new MhError("not_found", `no such document: ${id}`);
|
|
@@ -2733,7 +2754,7 @@ var duplicateDocument = grouped(function duplicateDocument2(db, id, opts = {}) {
|
|
|
2733
2754
|
moveDocument(db, dup.id, src.id, "after");
|
|
2734
2755
|
return getDocument(db, dup.id);
|
|
2735
2756
|
});
|
|
2736
|
-
var updateDocument = grouped(function
|
|
2757
|
+
var updateDocument = grouped(function updateDocument(db, id, fields, opts = {}) {
|
|
2737
2758
|
const doc = getDocument(db, id);
|
|
2738
2759
|
if (!doc)
|
|
2739
2760
|
throw new MhError("not_found", `no such document: ${id}`);
|
|
@@ -2754,7 +2775,7 @@ var updateDocument = grouped(function updateDocument2(db, id, fields, opts = {})
|
|
|
2754
2775
|
}
|
|
2755
2776
|
return getDocument(db, id);
|
|
2756
2777
|
});
|
|
2757
|
-
var editDocument = grouped(function
|
|
2778
|
+
var editDocument = grouped(function editDocument(db, id, opts) {
|
|
2758
2779
|
if (!getDocument(db, id))
|
|
2759
2780
|
throw new MhError("not_found", `no such document: ${id}`);
|
|
2760
2781
|
if (opts.ifMatch !== undefined && documentVersion(db, id) !== opts.ifMatch)
|
|
@@ -2792,7 +2813,7 @@ var editDocument = grouped(function editDocument2(db, id, opts) {
|
|
|
2792
2813
|
reconcileBody(db, id, newBody);
|
|
2793
2814
|
return { id, changed: newBody !== body, replaced: opts.replaceAll ? count : 1, version: documentVersion(db, id) };
|
|
2794
2815
|
});
|
|
2795
|
-
var editDocumentBatch = grouped(function
|
|
2816
|
+
var editDocumentBatch = grouped(function editDocumentBatch(db, id, opts) {
|
|
2796
2817
|
if (!getDocument(db, id))
|
|
2797
2818
|
throw new MhError("not_found", `no such document: ${id}`);
|
|
2798
2819
|
if (opts.edits.length === 0)
|
|
@@ -2819,7 +2840,7 @@ var editDocumentBatch = grouped(function editDocumentBatch2(db, id, opts) {
|
|
|
2819
2840
|
reconcileBody(db, id, body);
|
|
2820
2841
|
return { id, changed: body !== orig, replaced: total, version: documentVersion(db, id) };
|
|
2821
2842
|
});
|
|
2822
|
-
var appendDocument = grouped(function
|
|
2843
|
+
var appendDocument = grouped(function appendDocument(db, id, body) {
|
|
2823
2844
|
if (!getDocument(db, id))
|
|
2824
2845
|
throw new MhError("not_found", `no such document: ${id}`);
|
|
2825
2846
|
ensureBlocks(db, id);
|
|
@@ -2831,7 +2852,7 @@ var appendDocument = grouped(function appendDocument2(db, id, body) {
|
|
|
2831
2852
|
}
|
|
2832
2853
|
return { id, changed: next.length > 0, replaced: next.length, version: documentVersion(db, id) };
|
|
2833
2854
|
});
|
|
2834
|
-
var prependDocument = grouped(function
|
|
2855
|
+
var prependDocument = grouped(function prependDocument(db, id, body) {
|
|
2835
2856
|
if (!getDocument(db, id))
|
|
2836
2857
|
throw new MhError("not_found", `no such document: ${id}`);
|
|
2837
2858
|
ensureBlocks(db, id);
|
|
@@ -2843,7 +2864,7 @@ var prependDocument = grouped(function prependDocument2(db, id, body) {
|
|
|
2843
2864
|
}
|
|
2844
2865
|
return { id, changed: next.length > 0, replaced: next.length, version: documentVersion(db, id) };
|
|
2845
2866
|
});
|
|
2846
|
-
var deleteDocument = grouped(function
|
|
2867
|
+
var deleteDocument = grouped(function deleteDocument(db, id) {
|
|
2847
2868
|
if (!getDocument(db, id))
|
|
2848
2869
|
return false;
|
|
2849
2870
|
emit(db, "documents", id, "__deleted", 1);
|
|
@@ -3125,22 +3146,28 @@ function migrateSitesAccess(db) {
|
|
|
3125
3146
|
backfill("public_grants", "NULL");
|
|
3126
3147
|
}
|
|
3127
3148
|
}
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
if (!tableExists3(db, "site_channels"))
|
|
3149
|
+
function replayDataset(db, dataset) {
|
|
3150
|
+
if (!tableExists3(db, dataset))
|
|
3131
3151
|
return;
|
|
3132
|
-
const
|
|
3152
|
+
const key = `${dataset}_replay_seq`;
|
|
3153
|
+
const wm = db.query("SELECT value FROM meta WHERE key = ?").get(key);
|
|
3133
3154
|
const since = wm ? Number(wm.value) : 0;
|
|
3134
3155
|
const changes = db.query(`SELECT seq, ${CHANGE_SELECT} FROM crdt_changes
|
|
3135
|
-
WHERE dataset =
|
|
3156
|
+
WHERE dataset = ? AND seq > ? ORDER BY seq`).all(dataset, since);
|
|
3136
3157
|
if (changes.length === 0)
|
|
3137
3158
|
return;
|
|
3138
3159
|
db.transaction((rows) => {
|
|
3139
3160
|
for (const change of rows)
|
|
3140
3161
|
applyChange(db, change);
|
|
3141
|
-
db.query("INSERT INTO meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(
|
|
3162
|
+
db.query("INSERT INTO meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(key, String(rows[rows.length - 1].seq));
|
|
3142
3163
|
})(changes);
|
|
3143
3164
|
}
|
|
3165
|
+
function migrateSiteChannels(db) {
|
|
3166
|
+
replayDataset(db, "site_channels");
|
|
3167
|
+
}
|
|
3168
|
+
function migrateNodes(db) {
|
|
3169
|
+
replayDataset(db, "nodes");
|
|
3170
|
+
}
|
|
3144
3171
|
function initSchema(db) {
|
|
3145
3172
|
runSchema(db);
|
|
3146
3173
|
migrateOplog(db);
|
|
@@ -3155,6 +3182,7 @@ function initSchema(db) {
|
|
|
3155
3182
|
migrateShares(db);
|
|
3156
3183
|
migrateSitesAccess(db);
|
|
3157
3184
|
migrateSiteChannels(db);
|
|
3185
|
+
migrateNodes(db);
|
|
3158
3186
|
}
|
|
3159
3187
|
|
|
3160
3188
|
// src/core/sync/partition.ts
|
|
@@ -3543,7 +3571,7 @@ function createRoomFetch(deps) {
|
|
|
3543
3571
|
if (!body || typeof body.guestBase !== "string" || body.guestBase === "")
|
|
3544
3572
|
return json({ error: "provision needs a guestBase", code: "invalid_input" }, 400);
|
|
3545
3573
|
const str = (v) => typeof v === "string" && v !== "" ? v : null;
|
|
3546
|
-
const
|
|
3574
|
+
const cfg = initRoomDb(db, {
|
|
3547
3575
|
slug: typeof body.slug === "string" && body.slug !== "" ? body.slug : pathSlug,
|
|
3548
3576
|
guestBase: body.guestBase,
|
|
3549
3577
|
grants: str(body.grants) ?? "",
|
|
@@ -3551,11 +3579,11 @@ function createRoomFetch(deps) {
|
|
|
3551
3579
|
pwSalt: str(body.pwSalt),
|
|
3552
3580
|
expiresAt: typeof body.expiresAt === "number" ? body.expiresAt : null
|
|
3553
3581
|
});
|
|
3554
|
-
await deps.setAlarm?.(
|
|
3555
|
-
return json({ slug:
|
|
3582
|
+
await deps.setAlarm?.(cfg.expiresAt ?? null);
|
|
3583
|
+
return json({ slug: cfg.slug, room_node: cfg.roomNode, provisioned: true });
|
|
3556
3584
|
}
|
|
3557
|
-
const
|
|
3558
|
-
if (!
|
|
3585
|
+
const cfg = tryConfig(db);
|
|
3586
|
+
if (!cfg)
|
|
3559
3587
|
return json({ error: "room is not provisioned", code: "not_found" }, 404);
|
|
3560
3588
|
if (sub === "owner/sync") {
|
|
3561
3589
|
const body = await req.json().catch(() => null);
|
|
@@ -3563,7 +3591,7 @@ function createRoomFetch(deps) {
|
|
|
3563
3591
|
return json({ error: "malformed sync request", code: "invalid_input" }, 400);
|
|
3564
3592
|
await hlcHeadroom(db, deps);
|
|
3565
3593
|
try {
|
|
3566
|
-
const resp = handleOwnerSync(db, body,
|
|
3594
|
+
const resp = handleOwnerSync(db, body, cfg);
|
|
3567
3595
|
if (resp.share_state === "active" && body.changes.length > 0)
|
|
3568
3596
|
deps.poke?.(maxSeq(db));
|
|
3569
3597
|
return json(resp);
|
|
@@ -3662,10 +3690,10 @@ function serveRoomFile(db, cfg, path) {
|
|
|
3662
3690
|
if (row.encoding === "utf8")
|
|
3663
3691
|
return new Response(row.content ?? "", { status, headers });
|
|
3664
3692
|
if (row.encoding === "base64") {
|
|
3665
|
-
const
|
|
3666
|
-
const
|
|
3667
|
-
|
|
3668
|
-
return new Response(
|
|
3693
|
+
const bytes = base64ToBytes(row.content ?? "");
|
|
3694
|
+
const body = new Uint8Array(bytes.byteLength);
|
|
3695
|
+
body.set(bytes);
|
|
3696
|
+
return new Response(body.buffer, { status, headers });
|
|
3669
3697
|
}
|
|
3670
3698
|
const bytes = row.content ? roomBlobBytes(db, row.content) : null;
|
|
3671
3699
|
if (!bytes)
|
|
@@ -4066,8 +4094,8 @@ var edge_worker_default = {
|
|
|
4066
4094
|
if (slug === null)
|
|
4067
4095
|
return new Response("bad request", { status: 400 });
|
|
4068
4096
|
const id = env.ROOM.idFromName(slug);
|
|
4069
|
-
const
|
|
4070
|
-
return cors ? corsResponse(
|
|
4097
|
+
const res = await env.ROOM.get(id).fetch(req);
|
|
4098
|
+
return cors ? corsResponse(res) : res;
|
|
4071
4099
|
}
|
|
4072
4100
|
const res = await createInboxFetch({ sql: d1Sql(env.DB), ownerToken: env.OWNER_TOKEN })(req);
|
|
4073
4101
|
return cors ? corsResponse(res) : res;
|