@tensorix/metahub 0.1.10 → 0.1.11
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 +19 -6
- package/README.zh-CN.md +7 -2
- package/dist/cli.js +1601 -595
- package/dist/cli.js.map +43 -38
- package/dist/core/compact.d.ts +42 -0
- package/dist/core/compact.d.ts.map +1 -0
- package/dist/core/config.d.ts +3 -3
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/context.d.ts +4 -4
- package/dist/core/context.d.ts.map +1 -1
- package/dist/core/crdt.d.ts +30 -7
- package/dist/core/crdt.d.ts.map +1 -1
- package/dist/core/databases.d.ts +22 -8
- package/dist/core/databases.d.ts.map +1 -1
- package/dist/core/db.d.ts +1 -27
- package/dist/core/db.d.ts.map +1 -1
- package/dist/core/documents.d.ts +28 -15
- package/dist/core/documents.d.ts.map +1 -1
- package/dist/core/driver.d.ts +25 -0
- package/dist/core/driver.d.ts.map +1 -0
- package/dist/core/history.d.ts +99 -11
- package/dist/core/history.d.ts.map +1 -1
- package/dist/core/hlc.d.ts +3 -3
- package/dist/core/hlc.d.ts.map +1 -1
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/indexing.d.ts +4 -4
- package/dist/core/indexing.d.ts.map +1 -1
- package/dist/core/integrity.d.ts +1 -1
- package/dist/core/integrity.d.ts.map +1 -1
- package/dist/core/node.d.ts +2 -2
- package/dist/core/node.d.ts.map +1 -1
- package/dist/core/properties.d.ts +10 -9
- package/dist/core/properties.d.ts.map +1 -1
- package/dist/core/records.d.ts +13 -8
- package/dist/core/records.d.ts.map +1 -1
- package/dist/core/resolve.d.ts +4 -4
- package/dist/core/resolve.d.ts.map +1 -1
- package/dist/core/schema-init.d.ts +37 -0
- package/dist/core/schema-init.d.ts.map +1 -0
- package/dist/core/schema.d.ts +1 -1
- package/dist/core/schema.d.ts.map +1 -1
- package/dist/core/search.d.ts +3 -3
- package/dist/core/search.d.ts.map +1 -1
- package/dist/core/sites-core.d.ts +60 -0
- package/dist/core/sites-core.d.ts.map +1 -0
- package/dist/core/sites.d.ts +11 -52
- package/dist/core/sites.d.ts.map +1 -1
- package/dist/core/sqlcols.d.ts +4 -0
- package/dist/core/sqlcols.d.ts.map +1 -0
- package/dist/core/sync/auth.d.ts +5 -3
- package/dist/core/sync/auth.d.ts.map +1 -1
- package/dist/core/sync/client.d.ts +11 -3
- package/dist/core/sync/client.d.ts.map +1 -1
- package/dist/core/sync/peers.d.ts +10 -10
- package/dist/core/sync/peers.d.ts.map +1 -1
- package/dist/core/sync/protocol.d.ts +5 -0
- package/dist/core/sync/protocol.d.ts.map +1 -1
- package/dist/core/sync/routes.d.ts.map +1 -1
- package/dist/core/sync/server.d.ts +10 -0
- package/dist/core/sync/server.d.ts.map +1 -1
- package/dist/db-worker.js +268 -0
- package/dist/index.js +1061 -340
- package/dist/index.js.map +31 -28
- package/dist/metahub-sdk.js +1 -0
- package/dist/mh-runtime.js +1 -0
- package/dist/sqlite3.wasm +0 -0
- package/dist/sw.js +1 -0
- package/dist/webui/server/assets.d.ts +8 -1
- package/dist/webui/server/assets.d.ts.map +1 -1
- package/dist/webui/server/icons.d.ts +4 -0
- package/dist/webui/server/icons.d.ts.map +1 -0
- package/dist/webui/server/routes.d.ts.map +1 -1
- package/dist/webui.js +57 -57
- package/package.json +3 -1
package/dist/cli.js
CHANGED
|
@@ -1010,6 +1010,19 @@ function lcs(a2, b2) {
|
|
|
1010
1010
|
}
|
|
1011
1011
|
|
|
1012
1012
|
// src/core/crdt.ts
|
|
1013
|
+
function withChangeGroup(label, fn) {
|
|
1014
|
+
if (currentTxn !== null)
|
|
1015
|
+
return fn();
|
|
1016
|
+
currentTxn = (label ? label + ":" : "") + randomSuffix(8);
|
|
1017
|
+
try {
|
|
1018
|
+
return fn();
|
|
1019
|
+
} finally {
|
|
1020
|
+
currentTxn = null;
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
function grouped(fn, label = null) {
|
|
1024
|
+
return (...args) => withChangeGroup(label, () => fn(...args));
|
|
1025
|
+
}
|
|
1013
1026
|
function encodeScalar(val) {
|
|
1014
1027
|
if (val === null || val === undefined)
|
|
1015
1028
|
return null;
|
|
@@ -1063,7 +1076,7 @@ function materialize(db, dataset, rowId, col, valueJson) {
|
|
|
1063
1076
|
}
|
|
1064
1077
|
}
|
|
1065
1078
|
function applyChange(db, c3) {
|
|
1066
|
-
db.query("INSERT OR IGNORE INTO crdt_changes (hlc, node_id, dataset, row_id, col, value) VALUES (?, ?, ?, ?, ?, ?)").run(c3.hlc, c3.node_id, c3.dataset, c3.row_id, c3.col, c3.value);
|
|
1079
|
+
db.query("INSERT OR IGNORE INTO crdt_changes (hlc, node_id, dataset, row_id, col, value, txn) VALUES (?, ?, ?, ?, ?, ?, ?)").run(c3.hlc, c3.node_id, c3.dataset, c3.row_id, c3.col, c3.value, c3.txn ?? null);
|
|
1067
1080
|
const cur = db.query("SELECT MAX(hlc) AS h FROM crdt_changes WHERE dataset = ? AND row_id = ? AND col = ?").get(c3.dataset, c3.row_id, c3.col);
|
|
1068
1081
|
if (cur.h !== c3.hlc)
|
|
1069
1082
|
return false;
|
|
@@ -1078,7 +1091,8 @@ function emit(db, dataset, rowId, col, value) {
|
|
|
1078
1091
|
dataset,
|
|
1079
1092
|
row_id: rowId,
|
|
1080
1093
|
col,
|
|
1081
|
-
value: value === undefined ? null : JSON.stringify(value)
|
|
1094
|
+
value: value === undefined ? null : JSON.stringify(value),
|
|
1095
|
+
txn: currentTxn
|
|
1082
1096
|
};
|
|
1083
1097
|
applyChange(db, change);
|
|
1084
1098
|
return change;
|
|
@@ -1096,15 +1110,27 @@ function ingest(db, changes) {
|
|
|
1096
1110
|
tx(changes);
|
|
1097
1111
|
return applied;
|
|
1098
1112
|
}
|
|
1099
|
-
function changesAfterSeq(db, seq) {
|
|
1100
|
-
const
|
|
1101
|
-
const
|
|
1102
|
-
const
|
|
1113
|
+
function changesAfterSeq(db, seq, opts = {}) {
|
|
1114
|
+
const exclude = opts.excludeDatasets ?? [];
|
|
1115
|
+
const where = exclude.length ? `rowid > ? AND dataset NOT IN (${exclude.map(() => "?").join(", ")})` : "rowid > ?";
|
|
1116
|
+
const limitSql = opts.limit != null && opts.limit > 0 ? ` LIMIT ${Math.floor(opts.limit)}` : "";
|
|
1117
|
+
const rows = db.query(`SELECT rowid AS seq, ${CHANGE_SELECT} FROM crdt_changes WHERE ${where} ORDER BY rowid${limitSql}`).all(seq, ...exclude);
|
|
1118
|
+
const exhausted = opts.limit == null || opts.limit <= 0 || rows.length < opts.limit;
|
|
1119
|
+
let cursor;
|
|
1120
|
+
if (exhausted) {
|
|
1121
|
+
const top = db.query("SELECT MAX(rowid) AS m FROM crdt_changes").get();
|
|
1122
|
+
cursor = Math.max(seq, top.m ?? seq);
|
|
1123
|
+
} else {
|
|
1124
|
+
cursor = rows[rows.length - 1].seq;
|
|
1125
|
+
}
|
|
1103
1126
|
return { changes: rows.map(({ seq: _seq, ...c3 }) => c3), cursor };
|
|
1104
1127
|
}
|
|
1105
|
-
var DOMAIN, RECORD_META;
|
|
1128
|
+
var CHANGE_COLS, CHANGE_SELECT, currentTxn = null, DOMAIN, RECORD_META;
|
|
1106
1129
|
var init_crdt = __esm(() => {
|
|
1107
1130
|
init_node();
|
|
1131
|
+
init_ids();
|
|
1132
|
+
CHANGE_COLS = ["hlc", "node_id", "dataset", "row_id", "col", "value", "txn"];
|
|
1133
|
+
CHANGE_SELECT = CHANGE_COLS.join(", ");
|
|
1108
1134
|
DOMAIN = {
|
|
1109
1135
|
databases: {
|
|
1110
1136
|
table: "databases",
|
|
@@ -1183,7 +1209,7 @@ var init_cache = __esm(() => {
|
|
|
1183
1209
|
init_paths();
|
|
1184
1210
|
});
|
|
1185
1211
|
|
|
1186
|
-
// src/core/sites.ts
|
|
1212
|
+
// src/core/sites-core.ts
|
|
1187
1213
|
function inferContentType(path) {
|
|
1188
1214
|
const ext = path.slice(path.lastIndexOf(".") + 1).toLowerCase();
|
|
1189
1215
|
return MIME[ext] ?? "application/octet-stream";
|
|
@@ -1206,28 +1232,8 @@ function normalizeSitePath(raw) {
|
|
|
1206
1232
|
}
|
|
1207
1233
|
return segs.join("/");
|
|
1208
1234
|
}
|
|
1209
|
-
function isTextType(contentType) {
|
|
1210
|
-
const ct = contentType.toLowerCase();
|
|
1211
|
-
return ct.startsWith("text/") || ct.startsWith("application/json") || ct.startsWith("application/manifest+json") || ct.startsWith("application/xml") || ct.startsWith("text/javascript") || ct.startsWith("application/javascript") || ct.startsWith("image/svg+xml");
|
|
1212
|
-
}
|
|
1213
|
-
function toBytes(data) {
|
|
1214
|
-
if (typeof data === "string")
|
|
1215
|
-
return new TextEncoder().encode(data);
|
|
1216
|
-
return data instanceof Uint8Array ? data : new Uint8Array(data);
|
|
1217
|
-
}
|
|
1218
|
-
function createSite(db, opts) {
|
|
1219
|
-
const name = normalizeSiteName(opts.name);
|
|
1220
|
-
if (getSiteByName(db, name))
|
|
1221
|
-
throw new MhError("conflict", `site name already exists: ${name}`);
|
|
1222
|
-
const id = newId("site", name);
|
|
1223
|
-
const first = emit(db, "sites", id, "name", name);
|
|
1224
|
-
emit(db, "sites", id, "created_hlc", first.hlc);
|
|
1225
|
-
if (opts.title !== undefined)
|
|
1226
|
-
emit(db, "sites", id, "title", opts.title);
|
|
1227
|
-
return getSite(db, id);
|
|
1228
|
-
}
|
|
1229
1235
|
function getSite(db, id) {
|
|
1230
|
-
return db.query(
|
|
1236
|
+
return db.query(`SELECT ${SITE_SELECT} FROM sites WHERE id = ? AND __deleted = 0`).get(id);
|
|
1231
1237
|
}
|
|
1232
1238
|
function getSiteByName(db, name) {
|
|
1233
1239
|
let slug;
|
|
@@ -1236,10 +1242,10 @@ function getSiteByName(db, name) {
|
|
|
1236
1242
|
} catch {
|
|
1237
1243
|
return null;
|
|
1238
1244
|
}
|
|
1239
|
-
return db.query(
|
|
1245
|
+
return db.query(`SELECT ${SITE_SELECT} FROM sites WHERE name = ? AND __deleted = 0 ORDER BY created_hlc DESC LIMIT 1`).get(slug);
|
|
1240
1246
|
}
|
|
1241
1247
|
function listSites(db) {
|
|
1242
|
-
return db.query(
|
|
1248
|
+
return db.query(`SELECT ${SITE_SELECT} FROM sites WHERE __deleted = 0 ORDER BY created_hlc`).all();
|
|
1243
1249
|
}
|
|
1244
1250
|
function resolveSite(db, ref) {
|
|
1245
1251
|
const byId = ref.startsWith("site_") ? getSite(db, ref) : null;
|
|
@@ -1248,28 +1254,67 @@ function resolveSite(db, ref) {
|
|
|
1248
1254
|
throw new MhError("not_found", `no such site: ${ref}`);
|
|
1249
1255
|
return site;
|
|
1250
1256
|
}
|
|
1251
|
-
function
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
if (
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
if (dup && dup.id !== id)
|
|
1258
|
-
throw new MhError("conflict", `site name already exists: ${name}`);
|
|
1259
|
-
emit(db, "sites", id, "name", name);
|
|
1260
|
-
}
|
|
1261
|
-
if (opts.title !== undefined)
|
|
1262
|
-
emit(db, "sites", id, "title", opts.title);
|
|
1263
|
-
return getSite(db, id);
|
|
1257
|
+
function getFileRow(db, siteId, path) {
|
|
1258
|
+
const withIndex = path === "" || path.endsWith("/") ? `${path}index.html` : path;
|
|
1259
|
+
const p = normalizeSitePath(withIndex);
|
|
1260
|
+
if (!p)
|
|
1261
|
+
return null;
|
|
1262
|
+
return db.query("SELECT content_type, encoding, content FROM site_files WHERE site_id = ? AND path = ? AND __deleted = 0 ORDER BY created_hlc LIMIT 1").get(siteId, p);
|
|
1264
1263
|
}
|
|
1265
|
-
function
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1264
|
+
function listFiles(db, siteId) {
|
|
1265
|
+
return db.query("SELECT id, site_id, path, content_type, encoding FROM site_files WHERE site_id = ? AND __deleted = 0 ORDER BY path").all(siteId);
|
|
1266
|
+
}
|
|
1267
|
+
var SITE_COLS, SITE_SELECT, SITE_FILE_COLS, SITE_FILE_SELECT, MIME;
|
|
1268
|
+
var init_sites_core = __esm(() => {
|
|
1269
|
+
init_errors();
|
|
1270
|
+
SITE_COLS = ["id", "name", "title", "created_hlc"];
|
|
1271
|
+
SITE_SELECT = SITE_COLS.join(", ");
|
|
1272
|
+
SITE_FILE_COLS = [
|
|
1273
|
+
"id",
|
|
1274
|
+
"site_id",
|
|
1275
|
+
"path",
|
|
1276
|
+
"content_type",
|
|
1277
|
+
"encoding",
|
|
1278
|
+
"content"
|
|
1279
|
+
];
|
|
1280
|
+
SITE_FILE_SELECT = SITE_FILE_COLS.join(", ");
|
|
1281
|
+
MIME = {
|
|
1282
|
+
html: "text/html; charset=utf-8",
|
|
1283
|
+
htm: "text/html; charset=utf-8",
|
|
1284
|
+
css: "text/css; charset=utf-8",
|
|
1285
|
+
js: "text/javascript; charset=utf-8",
|
|
1286
|
+
mjs: "text/javascript; charset=utf-8",
|
|
1287
|
+
json: "application/json; charset=utf-8",
|
|
1288
|
+
svg: "image/svg+xml",
|
|
1289
|
+
txt: "text/plain; charset=utf-8",
|
|
1290
|
+
md: "text/markdown; charset=utf-8",
|
|
1291
|
+
xml: "application/xml; charset=utf-8",
|
|
1292
|
+
webmanifest: "application/manifest+json",
|
|
1293
|
+
ico: "image/x-icon",
|
|
1294
|
+
png: "image/png",
|
|
1295
|
+
jpg: "image/jpeg",
|
|
1296
|
+
jpeg: "image/jpeg",
|
|
1297
|
+
gif: "image/gif",
|
|
1298
|
+
webp: "image/webp",
|
|
1299
|
+
avif: "image/avif",
|
|
1300
|
+
woff: "font/woff",
|
|
1301
|
+
woff2: "font/woff2",
|
|
1302
|
+
ttf: "font/ttf",
|
|
1303
|
+
otf: "font/otf",
|
|
1304
|
+
pdf: "application/pdf",
|
|
1305
|
+
wasm: "application/wasm"
|
|
1306
|
+
};
|
|
1307
|
+
});
|
|
1308
|
+
|
|
1309
|
+
// src/core/sites.ts
|
|
1310
|
+
function isTextType(contentType) {
|
|
1311
|
+
const ct = contentType.toLowerCase();
|
|
1312
|
+
return ct.startsWith("text/") || ct.startsWith("application/json") || ct.startsWith("application/manifest+json") || ct.startsWith("application/xml") || ct.startsWith("text/javascript") || ct.startsWith("application/javascript") || ct.startsWith("image/svg+xml");
|
|
1313
|
+
}
|
|
1314
|
+
function toBytes(data) {
|
|
1315
|
+
if (typeof data === "string")
|
|
1316
|
+
return new TextEncoder().encode(data);
|
|
1317
|
+
return data instanceof Uint8Array ? data : new Uint8Array(data);
|
|
1273
1318
|
}
|
|
1274
1319
|
function fileIdFor(db, siteId, path) {
|
|
1275
1320
|
const row = db.query("SELECT id FROM site_files WHERE site_id = ? AND path = ? ORDER BY created_hlc LIMIT 1").get(siteId, path);
|
|
@@ -1295,36 +1340,21 @@ async function putFile(db, siteId, path, opts) {
|
|
|
1295
1340
|
}
|
|
1296
1341
|
const existing = fileIdFor(db, siteId, cleanPath);
|
|
1297
1342
|
const id = existing ?? newId("sf", cleanPath);
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
}
|
|
1309
|
-
|
|
1310
|
-
return db.query("SELECT id, site_id, path, content_type, encoding FROM site_files WHERE site_id = ? AND __deleted = 0 ORDER BY path").all(siteId);
|
|
1311
|
-
}
|
|
1312
|
-
function deleteFile(db, siteId, path) {
|
|
1313
|
-
const id = fileIdFor(db, siteId, normalizeSitePath(path));
|
|
1314
|
-
if (!id)
|
|
1315
|
-
return false;
|
|
1316
|
-
const live = db.query("SELECT __deleted AS d FROM site_files WHERE id = ?").get(id);
|
|
1317
|
-
if (!live || live.d)
|
|
1318
|
-
return false;
|
|
1319
|
-
emit(db, "site_files", id, "__deleted", 1);
|
|
1320
|
-
return true;
|
|
1343
|
+
withChangeGroup(null, () => {
|
|
1344
|
+
if (!existing) {
|
|
1345
|
+
const first = emit(db, "site_files", id, "site_id", siteId);
|
|
1346
|
+
emit(db, "site_files", id, "path", cleanPath);
|
|
1347
|
+
emit(db, "site_files", id, "created_hlc", first.hlc);
|
|
1348
|
+
}
|
|
1349
|
+
emit(db, "site_files", id, "content_type", contentType);
|
|
1350
|
+
emit(db, "site_files", id, "encoding", encoding);
|
|
1351
|
+
emit(db, "site_files", id, "content", content);
|
|
1352
|
+
emit(db, "site_files", id, "__deleted", 0);
|
|
1353
|
+
});
|
|
1354
|
+
return db.query(`SELECT ${SITE_FILE_SELECT} FROM site_files WHERE id = ?`).get(id);
|
|
1321
1355
|
}
|
|
1322
1356
|
async function getFileForServe(db, siteId, path) {
|
|
1323
|
-
const
|
|
1324
|
-
const p = normalizeSitePath(withIndex);
|
|
1325
|
-
if (!p)
|
|
1326
|
-
return null;
|
|
1327
|
-
const row = db.query("SELECT content_type, encoding, content FROM site_files WHERE site_id = ? AND path = ? AND __deleted = 0 ORDER BY created_hlc LIMIT 1").get(siteId, p);
|
|
1357
|
+
const row = getFileRow(db, siteId, path);
|
|
1328
1358
|
if (!row)
|
|
1329
1359
|
return null;
|
|
1330
1360
|
const content = row.content ?? "";
|
|
@@ -1339,39 +1369,59 @@ async function getFileForServe(db, siteId, path) {
|
|
|
1339
1369
|
return null;
|
|
1340
1370
|
return { contentType: row.content_type, bytes };
|
|
1341
1371
|
}
|
|
1342
|
-
var INLINE_LIMIT,
|
|
1372
|
+
var INLINE_LIMIT, createSite, updateSite, deleteSite, deleteFile;
|
|
1343
1373
|
var init_sites = __esm(() => {
|
|
1344
1374
|
init_ids();
|
|
1345
1375
|
init_crdt();
|
|
1346
1376
|
init_cache();
|
|
1347
1377
|
init_errors();
|
|
1378
|
+
init_sites_core();
|
|
1379
|
+
init_sites_core();
|
|
1348
1380
|
INLINE_LIMIT = 256 * 1024;
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1381
|
+
createSite = grouped(function createSite2(db, opts) {
|
|
1382
|
+
const name = normalizeSiteName(opts.name);
|
|
1383
|
+
if (getSiteByName(db, name))
|
|
1384
|
+
throw new MhError("conflict", `site name already exists: ${name}`);
|
|
1385
|
+
const id = newId("site", name);
|
|
1386
|
+
const first = emit(db, "sites", id, "name", name);
|
|
1387
|
+
emit(db, "sites", id, "created_hlc", first.hlc);
|
|
1388
|
+
if (opts.title !== undefined)
|
|
1389
|
+
emit(db, "sites", id, "title", opts.title);
|
|
1390
|
+
return getSite(db, id);
|
|
1391
|
+
});
|
|
1392
|
+
updateSite = grouped(function updateSite2(db, id, opts) {
|
|
1393
|
+
if (!getSite(db, id))
|
|
1394
|
+
throw new MhError("not_found", `no such site: ${id}`);
|
|
1395
|
+
if (opts.name !== undefined) {
|
|
1396
|
+
const name = normalizeSiteName(opts.name);
|
|
1397
|
+
const dup = getSiteByName(db, name);
|
|
1398
|
+
if (dup && dup.id !== id)
|
|
1399
|
+
throw new MhError("conflict", `site name already exists: ${name}`);
|
|
1400
|
+
emit(db, "sites", id, "name", name);
|
|
1401
|
+
}
|
|
1402
|
+
if (opts.title !== undefined)
|
|
1403
|
+
emit(db, "sites", id, "title", opts.title);
|
|
1404
|
+
return getSite(db, id);
|
|
1405
|
+
});
|
|
1406
|
+
deleteSite = grouped(function deleteSite2(db, id) {
|
|
1407
|
+
if (!getSite(db, id))
|
|
1408
|
+
return false;
|
|
1409
|
+
const files = db.query("SELECT id FROM site_files WHERE site_id = ? AND __deleted = 0").all(id);
|
|
1410
|
+
for (const f3 of files)
|
|
1411
|
+
emit(db, "site_files", f3.id, "__deleted", 1);
|
|
1412
|
+
emit(db, "sites", id, "__deleted", 1);
|
|
1413
|
+
return true;
|
|
1414
|
+
});
|
|
1415
|
+
deleteFile = grouped(function deleteFile2(db, siteId, path) {
|
|
1416
|
+
const id = fileIdFor(db, siteId, normalizeSitePath(path));
|
|
1417
|
+
if (!id)
|
|
1418
|
+
return false;
|
|
1419
|
+
const live = db.query("SELECT __deleted AS d FROM site_files WHERE id = ?").get(id);
|
|
1420
|
+
if (!live || live.d)
|
|
1421
|
+
return false;
|
|
1422
|
+
emit(db, "site_files", id, "__deleted", 1);
|
|
1423
|
+
return true;
|
|
1424
|
+
});
|
|
1375
1425
|
});
|
|
1376
1426
|
|
|
1377
1427
|
// node_modules/sisteransi/src/index.js
|
|
@@ -2863,7 +2913,7 @@ async function runMain(cmd, opts = {}) {
|
|
|
2863
2913
|
// package.json
|
|
2864
2914
|
var package_default = {
|
|
2865
2915
|
name: "@tensorix/metahub",
|
|
2866
|
-
version: "0.1.
|
|
2916
|
+
version: "0.1.11",
|
|
2867
2917
|
description: "Metahub core library and CLI",
|
|
2868
2918
|
license: "AGPL-3.0-only",
|
|
2869
2919
|
type: "module",
|
|
@@ -2897,6 +2947,7 @@ var package_default = {
|
|
|
2897
2947
|
},
|
|
2898
2948
|
dependencies: {
|
|
2899
2949
|
"@clack/prompts": "^1.5.0",
|
|
2950
|
+
"@sqlite.org/sqlite-wasm": "^3.53.0-build1",
|
|
2900
2951
|
citty: "^0.1.6",
|
|
2901
2952
|
"highlight.js": "^11.11.1",
|
|
2902
2953
|
preact: "^10.29.2",
|
|
@@ -2905,6 +2956,7 @@ var package_default = {
|
|
|
2905
2956
|
zod: "^4.4.3"
|
|
2906
2957
|
},
|
|
2907
2958
|
devDependencies: {
|
|
2959
|
+
"@happy-dom/global-registrator": "^20.10.2",
|
|
2908
2960
|
"@types/bun": "latest",
|
|
2909
2961
|
"@types/turndown": "^5.0.6",
|
|
2910
2962
|
typescript: "^5"
|
|
@@ -2924,6 +2976,8 @@ CREATE TABLE IF NOT EXISTS meta (
|
|
|
2924
2976
|
);
|
|
2925
2977
|
|
|
2926
2978
|
-- CRDT oplog: one row per field assignment. Source of truth for sync.
|
|
2979
|
+
-- txn groups the changes of one logical mutation for history rendering
|
|
2980
|
+
-- (nullable: legacy rows and changes from pre-txn peers have none).
|
|
2927
2981
|
CREATE TABLE IF NOT EXISTS crdt_changes (
|
|
2928
2982
|
hlc TEXT NOT NULL,
|
|
2929
2983
|
node_id TEXT NOT NULL,
|
|
@@ -2931,9 +2985,14 @@ CREATE TABLE IF NOT EXISTS crdt_changes (
|
|
|
2931
2985
|
row_id TEXT NOT NULL,
|
|
2932
2986
|
col TEXT NOT NULL,
|
|
2933
2987
|
value TEXT,
|
|
2988
|
+
txn TEXT,
|
|
2934
2989
|
PRIMARY KEY (dataset, row_id, col, hlc)
|
|
2935
2990
|
);
|
|
2936
2991
|
CREATE INDEX IF NOT EXISTS idx_changes_hlc ON crdt_changes(hlc);
|
|
2992
|
+
-- Serves history's "every block ever attached to this doc" lookup. Partial so
|
|
2993
|
+
-- it never indexes the large values other datasets carry (e.g. site file bodies).
|
|
2994
|
+
CREATE INDEX IF NOT EXISTS idx_changes_docref ON crdt_changes(value)
|
|
2995
|
+
WHERE dataset = 'doc_blocks' AND col = 'doc_id';
|
|
2937
2996
|
|
|
2938
2997
|
-- Per-peer replication cursors (SQLite rowids: monotonic in insertion order,
|
|
2939
2998
|
-- so no change is ever skipped regardless of HLC/clock skew). Outbound side of
|
|
@@ -3073,47 +3132,6 @@ init_crdt();
|
|
|
3073
3132
|
init_ids();
|
|
3074
3133
|
init_crdt();
|
|
3075
3134
|
init_errors();
|
|
3076
|
-
function createDatabase(db, opts) {
|
|
3077
|
-
const id = newId("db", opts.name);
|
|
3078
|
-
const first = emit(db, "databases", id, "name", opts.name);
|
|
3079
|
-
emit(db, "databases", id, "created_hlc", first.hlc);
|
|
3080
|
-
if (opts.icon !== undefined)
|
|
3081
|
-
emit(db, "databases", id, "icon", opts.icon);
|
|
3082
|
-
return getDatabase(db, id);
|
|
3083
|
-
}
|
|
3084
|
-
function updateDatabase(db, id, fields) {
|
|
3085
|
-
if (!getDatabase(db, id))
|
|
3086
|
-
throw new MhError("not_found", `no such database: ${id}`);
|
|
3087
|
-
if (fields.name !== undefined)
|
|
3088
|
-
emit(db, "databases", id, "name", fields.name);
|
|
3089
|
-
if (fields.icon !== undefined)
|
|
3090
|
-
emit(db, "databases", id, "icon", fields.icon);
|
|
3091
|
-
return getDatabase(db, id);
|
|
3092
|
-
}
|
|
3093
|
-
function getDatabase(db, id) {
|
|
3094
|
-
return db.query("SELECT id, name, icon, created_hlc FROM databases WHERE id = ? AND __deleted = 0").get(id);
|
|
3095
|
-
}
|
|
3096
|
-
function listDatabases(db) {
|
|
3097
|
-
return db.query("SELECT id, name, icon, created_hlc FROM databases WHERE __deleted = 0 ORDER BY created_hlc").all();
|
|
3098
|
-
}
|
|
3099
|
-
function deleteDatabase(db, id) {
|
|
3100
|
-
if (!getDatabase(db, id))
|
|
3101
|
-
return false;
|
|
3102
|
-
emit(db, "databases", id, "__deleted", 1);
|
|
3103
|
-
const tombstone = (table) => {
|
|
3104
|
-
for (const r3 of liveChildren(db, table, "database_id", id))
|
|
3105
|
-
emit(db, table, r3, "__deleted", 1);
|
|
3106
|
-
};
|
|
3107
|
-
tombstone("properties");
|
|
3108
|
-
tombstone("records");
|
|
3109
|
-
for (const d2 of liveChildren(db, "documents", "database_id", id))
|
|
3110
|
-
emit(db, "documents", d2, "database_id", null);
|
|
3111
|
-
db.query("DELETE FROM meta WHERE key = 'current_db' AND value = ?").run(id);
|
|
3112
|
-
return true;
|
|
3113
|
-
}
|
|
3114
|
-
function liveChildren(db, table, col, parentId) {
|
|
3115
|
-
return db.query(`SELECT id FROM ${table} WHERE ${col} = ? AND __deleted = 0`).all(parentId).map((r3) => r3.id);
|
|
3116
|
-
}
|
|
3117
3135
|
|
|
3118
3136
|
// src/core/properties.ts
|
|
3119
3137
|
init_ids();
|
|
@@ -3161,6 +3179,8 @@ var PROP_TYPES = new Set([
|
|
|
3161
3179
|
"relation",
|
|
3162
3180
|
"url"
|
|
3163
3181
|
]);
|
|
3182
|
+
var PROPERTY_COLS = ["id", "database_id", "name", "type", "config", "position"];
|
|
3183
|
+
var PROPERTY_SELECT = PROPERTY_COLS.join(", ");
|
|
3164
3184
|
function validateConfig(type, config) {
|
|
3165
3185
|
if (type === "select" || type === "multi_select") {
|
|
3166
3186
|
const opts = config?.options;
|
|
@@ -3176,7 +3196,7 @@ function nextPosition(db, databaseId) {
|
|
|
3176
3196
|
const row = db.query("SELECT MAX(position) AS m FROM properties WHERE database_id = ? AND __deleted = 0").get(databaseId);
|
|
3177
3197
|
return (row.m ?? 0) + 1;
|
|
3178
3198
|
}
|
|
3179
|
-
|
|
3199
|
+
var addProperty = grouped(function addProperty2(db, databaseId, opts) {
|
|
3180
3200
|
if (!getDatabase(db, databaseId))
|
|
3181
3201
|
throw new MhError("not_found", `no such database: ${databaseId}`);
|
|
3182
3202
|
if (!PROP_TYPES.has(opts.type))
|
|
@@ -3192,21 +3212,21 @@ function addProperty(db, databaseId, opts) {
|
|
|
3192
3212
|
if (opts.type === "relation" || opts.indexed === true)
|
|
3193
3213
|
ensurePropIndex(db, databaseId, id);
|
|
3194
3214
|
return getProperty(db, id);
|
|
3195
|
-
}
|
|
3215
|
+
});
|
|
3196
3216
|
function getProperty(db, id) {
|
|
3197
|
-
const row = db.query(
|
|
3217
|
+
const row = db.query(`SELECT ${PROPERTY_SELECT} FROM properties WHERE id = ? AND __deleted = 0`).get(id);
|
|
3198
3218
|
if (!row)
|
|
3199
3219
|
return null;
|
|
3200
3220
|
return { ...row, config: row.config ? JSON.parse(row.config) : null };
|
|
3201
3221
|
}
|
|
3202
3222
|
function listProperties(db, databaseId) {
|
|
3203
|
-
const rows = db.query(
|
|
3223
|
+
const rows = db.query(`SELECT ${PROPERTY_SELECT} FROM properties WHERE database_id = ? AND __deleted = 0 ORDER BY position`).all(databaseId);
|
|
3204
3224
|
return rows.map((r3) => ({
|
|
3205
3225
|
...r3,
|
|
3206
3226
|
config: r3.config ? JSON.parse(r3.config) : null
|
|
3207
3227
|
}));
|
|
3208
3228
|
}
|
|
3209
|
-
|
|
3229
|
+
var updateProperty = grouped(function updateProperty2(db, id, fields) {
|
|
3210
3230
|
const cur = getProperty(db, id);
|
|
3211
3231
|
if (!cur)
|
|
3212
3232
|
throw new MhError("not_found", `no such property: ${id}`);
|
|
@@ -3232,7 +3252,7 @@ function updateProperty(db, id, fields) {
|
|
|
3232
3252
|
emit(db, "records", r3.id, id, null);
|
|
3233
3253
|
}
|
|
3234
3254
|
return getProperty(db, id);
|
|
3235
|
-
}
|
|
3255
|
+
});
|
|
3236
3256
|
function setPropertyWidth(db, id, width) {
|
|
3237
3257
|
const cur = getProperty(db, id);
|
|
3238
3258
|
if (!cur)
|
|
@@ -3242,7 +3262,7 @@ function setPropertyWidth(db, id, width) {
|
|
|
3242
3262
|
const w2 = Math.max(80, Math.min(2000, Math.round(width)));
|
|
3243
3263
|
return updateProperty(db, id, { config: { ...cur.config ?? {}, width: w2 } });
|
|
3244
3264
|
}
|
|
3245
|
-
|
|
3265
|
+
var removeProperty = grouped(function removeProperty2(db, id) {
|
|
3246
3266
|
const prop = getProperty(db, id);
|
|
3247
3267
|
if (!prop)
|
|
3248
3268
|
return false;
|
|
@@ -3251,6 +3271,76 @@ function removeProperty(db, id) {
|
|
|
3251
3271
|
for (const r3 of rows)
|
|
3252
3272
|
emit(db, "records", r3.id, id, undefined);
|
|
3253
3273
|
return true;
|
|
3274
|
+
});
|
|
3275
|
+
|
|
3276
|
+
// src/core/databases.ts
|
|
3277
|
+
var DATABASE_COLS = ["id", "name", "icon", "created_hlc"];
|
|
3278
|
+
var DATABASE_SELECT = DATABASE_COLS.join(", ");
|
|
3279
|
+
var createDatabase = grouped(function createDatabase2(db, opts) {
|
|
3280
|
+
const id = newId("db", opts.name);
|
|
3281
|
+
const first = emit(db, "databases", id, "name", opts.name);
|
|
3282
|
+
emit(db, "databases", id, "created_hlc", first.hlc);
|
|
3283
|
+
if (opts.icon !== undefined)
|
|
3284
|
+
emit(db, "databases", id, "icon", opts.icon);
|
|
3285
|
+
return getDatabase(db, id);
|
|
3286
|
+
});
|
|
3287
|
+
var updateDatabase = grouped(function updateDatabase2(db, id, fields) {
|
|
3288
|
+
if (!getDatabase(db, id))
|
|
3289
|
+
throw new MhError("not_found", `no such database: ${id}`);
|
|
3290
|
+
if (fields.name !== undefined)
|
|
3291
|
+
emit(db, "databases", id, "name", fields.name);
|
|
3292
|
+
if (fields.icon !== undefined)
|
|
3293
|
+
emit(db, "databases", id, "icon", fields.icon);
|
|
3294
|
+
return getDatabase(db, id);
|
|
3295
|
+
});
|
|
3296
|
+
var duplicateDatabase = grouped(function duplicateDatabase2(db, id, opts = {}) {
|
|
3297
|
+
const src2 = getDatabase(db, id);
|
|
3298
|
+
if (!src2)
|
|
3299
|
+
throw new MhError("not_found", `no such database: ${id}`);
|
|
3300
|
+
const dup = createDatabase(db, {
|
|
3301
|
+
name: opts.name ?? src2.name,
|
|
3302
|
+
icon: opts.icon ?? src2.icon ?? undefined
|
|
3303
|
+
});
|
|
3304
|
+
const propIdMap = new Map;
|
|
3305
|
+
for (const p of listProperties(db, id)) {
|
|
3306
|
+
const config = p.config?.database === id ? { ...p.config, database: dup.id } : p.config ?? undefined;
|
|
3307
|
+
const np = addProperty(db, dup.id, { name: p.name, type: p.type, config, position: p.position });
|
|
3308
|
+
propIdMap.set(p.id, np.id);
|
|
3309
|
+
}
|
|
3310
|
+
for (const r3 of listRecords(db, id)) {
|
|
3311
|
+
const data = {};
|
|
3312
|
+
for (const [pid, v2] of Object.entries(r3.cells)) {
|
|
3313
|
+
const nid = propIdMap.get(pid);
|
|
3314
|
+
if (nid)
|
|
3315
|
+
data[nid] = v2;
|
|
3316
|
+
}
|
|
3317
|
+
createRecord(db, dup.id, data);
|
|
3318
|
+
}
|
|
3319
|
+
return getDatabase(db, dup.id);
|
|
3320
|
+
});
|
|
3321
|
+
function getDatabase(db, id) {
|
|
3322
|
+
return db.query(`SELECT ${DATABASE_SELECT} FROM databases WHERE id = ? AND __deleted = 0`).get(id);
|
|
3323
|
+
}
|
|
3324
|
+
function listDatabases(db) {
|
|
3325
|
+
return db.query(`SELECT ${DATABASE_SELECT} FROM databases WHERE __deleted = 0 ORDER BY created_hlc`).all();
|
|
3326
|
+
}
|
|
3327
|
+
var deleteDatabase = grouped(function deleteDatabase2(db, id) {
|
|
3328
|
+
if (!getDatabase(db, id))
|
|
3329
|
+
return false;
|
|
3330
|
+
emit(db, "databases", id, "__deleted", 1);
|
|
3331
|
+
const tombstone = (table) => {
|
|
3332
|
+
for (const r3 of liveChildren(db, table, "database_id", id))
|
|
3333
|
+
emit(db, table, r3, "__deleted", 1);
|
|
3334
|
+
};
|
|
3335
|
+
tombstone("properties");
|
|
3336
|
+
tombstone("records");
|
|
3337
|
+
for (const d2 of liveChildren(db, "documents", "database_id", id))
|
|
3338
|
+
emit(db, "documents", d2, "database_id", null);
|
|
3339
|
+
db.query("DELETE FROM meta WHERE key = 'current_db' AND value = ?").run(id);
|
|
3340
|
+
return true;
|
|
3341
|
+
});
|
|
3342
|
+
function liveChildren(db, table, col, parentId) {
|
|
3343
|
+
return db.query(`SELECT id FROM ${table} WHERE ${col} = ? AND __deleted = 0`).all(parentId).map((r3) => r3.id);
|
|
3254
3344
|
}
|
|
3255
3345
|
|
|
3256
3346
|
// src/core/resolve.ts
|
|
@@ -3468,12 +3558,30 @@ function coerce(db, prop, value) {
|
|
|
3468
3558
|
}
|
|
3469
3559
|
}
|
|
3470
3560
|
}
|
|
3561
|
+
function propsByName(props) {
|
|
3562
|
+
const byName = new Map;
|
|
3563
|
+
for (const p of props) {
|
|
3564
|
+
const k2 = p.name.toLowerCase();
|
|
3565
|
+
const list = byName.get(k2);
|
|
3566
|
+
if (list)
|
|
3567
|
+
list.push(p);
|
|
3568
|
+
else
|
|
3569
|
+
byName.set(k2, [p]);
|
|
3570
|
+
}
|
|
3571
|
+
return byName;
|
|
3572
|
+
}
|
|
3471
3573
|
function resolveData(props, data) {
|
|
3472
3574
|
const byId = new Map(props.map((p) => [p.id, p]));
|
|
3473
|
-
const byName =
|
|
3575
|
+
const byName = propsByName(props);
|
|
3474
3576
|
const out = [];
|
|
3475
3577
|
for (const [key, value] of Object.entries(data)) {
|
|
3476
|
-
|
|
3578
|
+
let prop = byId.get(key);
|
|
3579
|
+
if (!prop) {
|
|
3580
|
+
const matches = byName.get(key.toLowerCase()) ?? [];
|
|
3581
|
+
if (matches.length > 1)
|
|
3582
|
+
throw new MhError("ambiguous", `property name "${key}" matches ${matches.length} properties; use a property id`);
|
|
3583
|
+
prop = matches[0];
|
|
3584
|
+
}
|
|
3477
3585
|
if (!prop)
|
|
3478
3586
|
throw new MhError("not_found", `unknown property: ${key}`);
|
|
3479
3587
|
out.push({ prop, value });
|
|
@@ -3484,12 +3592,15 @@ function rowToRecord(row, props) {
|
|
|
3484
3592
|
const raw = JSON.parse(row.data || "{}");
|
|
3485
3593
|
const byId = new Map(props.map((p) => [p.id, p]));
|
|
3486
3594
|
const values = {};
|
|
3595
|
+
const cells = {};
|
|
3487
3596
|
for (const [propId, v2] of Object.entries(raw)) {
|
|
3488
3597
|
const p = byId.get(propId);
|
|
3489
|
-
if (p)
|
|
3490
|
-
|
|
3598
|
+
if (!p)
|
|
3599
|
+
continue;
|
|
3600
|
+
values[p.name] = v2;
|
|
3601
|
+
cells[p.id] = v2;
|
|
3491
3602
|
}
|
|
3492
|
-
return { id: row.id, database_id: row.database_id, values };
|
|
3603
|
+
return { id: row.id, database_id: row.database_id, values, cells };
|
|
3493
3604
|
}
|
|
3494
3605
|
function deriveTitle(props, resolved, fallbackBase) {
|
|
3495
3606
|
const text = resolved.find((r3) => r3.prop.type === "text" && typeof r3.value === "string" && r3.value);
|
|
@@ -3526,7 +3637,7 @@ function rebalanceRecordOrderKeys(db, databaseId) {
|
|
|
3526
3637
|
emit(db, "records", row.id, "order_key", keys[i2]);
|
|
3527
3638
|
});
|
|
3528
3639
|
}
|
|
3529
|
-
|
|
3640
|
+
var backfillRecordOrderKeys = grouped(function backfillRecordOrderKeys2(db, databaseId) {
|
|
3530
3641
|
const emitChange = canEmitOrderKeys(db);
|
|
3531
3642
|
const dbRows = databaseId ? [{ database_id: databaseId }] : db.query(`SELECT DISTINCT database_id FROM records
|
|
3532
3643
|
WHERE database_id IS NOT NULL AND __deleted = 0 AND order_key IS NULL`).all();
|
|
@@ -3540,8 +3651,8 @@ function backfillRecordOrderKeys(db, databaseId) {
|
|
|
3540
3651
|
const keys = keysBetween(start, null, missing.length);
|
|
3541
3652
|
missing.forEach((row, i2) => writeRecordOrderKey(db, row.id, keys[i2], emitChange));
|
|
3542
3653
|
}
|
|
3543
|
-
}
|
|
3544
|
-
|
|
3654
|
+
});
|
|
3655
|
+
var createRecord = grouped(function createRecord2(db, databaseId, data) {
|
|
3545
3656
|
const dbRow = getDatabase(db, databaseId);
|
|
3546
3657
|
if (!dbRow)
|
|
3547
3658
|
throw new MhError("not_found", `no such database: ${databaseId}`);
|
|
@@ -3555,7 +3666,7 @@ function createRecord(db, databaseId, data) {
|
|
|
3555
3666
|
for (const { prop, value } of resolved)
|
|
3556
3667
|
emit(db, "records", id, prop.id, coerce(db, prop, value));
|
|
3557
3668
|
return getRecord(db, id);
|
|
3558
|
-
}
|
|
3669
|
+
});
|
|
3559
3670
|
function getRecord(db, id) {
|
|
3560
3671
|
const row = db.query("SELECT id, database_id, data FROM records WHERE id = ? AND __deleted = 0").get(id);
|
|
3561
3672
|
if (!row)
|
|
@@ -3582,7 +3693,13 @@ function listRecords(db, databaseId, opts = {}) {
|
|
|
3582
3693
|
key = key.slice(1);
|
|
3583
3694
|
}
|
|
3584
3695
|
if (key !== "created" && key !== "created_hlc") {
|
|
3585
|
-
|
|
3696
|
+
let p = props.find((p2) => p2.id === key);
|
|
3697
|
+
if (!p) {
|
|
3698
|
+
const matches = props.filter((q2) => q2.name.toLowerCase() === key.toLowerCase());
|
|
3699
|
+
if (matches.length > 1)
|
|
3700
|
+
throw new MhError("ambiguous", `sort field "${key}" matches ${matches.length} properties; use a property id`);
|
|
3701
|
+
p = matches[0];
|
|
3702
|
+
}
|
|
3586
3703
|
if (!p)
|
|
3587
3704
|
throw new MhError("not_found", `unknown sort field: ${key}`);
|
|
3588
3705
|
sortProp = p;
|
|
@@ -3608,13 +3725,13 @@ function listRecords(db, databaseId, opts = {}) {
|
|
|
3608
3725
|
}
|
|
3609
3726
|
let rows = db.query(sql).all(...args).map((r3) => rowToRecord(r3, props));
|
|
3610
3727
|
if (jsFilters.length) {
|
|
3611
|
-
rows = rows.filter((r3) => jsFilters.every((f3) => JSON.stringify(r3.
|
|
3728
|
+
rows = rows.filter((r3) => jsFilters.every((f3) => JSON.stringify(r3.cells[f3.prop.id] ?? null) === JSON.stringify(f3.value)));
|
|
3612
3729
|
if (opts.limit != null)
|
|
3613
3730
|
rows = rows.slice(0, opts.limit);
|
|
3614
3731
|
}
|
|
3615
3732
|
return rows;
|
|
3616
3733
|
}
|
|
3617
|
-
|
|
3734
|
+
var moveRecord = grouped(function moveRecord2(db, id, targetId, where) {
|
|
3618
3735
|
if (where !== "before" && where !== "after")
|
|
3619
3736
|
throw new MhError("invalid_input", `unknown move position: ${where}`);
|
|
3620
3737
|
const src2 = db.query("SELECT id, database_id FROM records WHERE id = ? AND __deleted = 0").get(id);
|
|
@@ -3643,8 +3760,8 @@ function moveRecord(db, id, targetId, where) {
|
|
|
3643
3760
|
}
|
|
3644
3761
|
emit(db, "records", id, "order_key", keyBetween(left, right));
|
|
3645
3762
|
return getRecord(db, id);
|
|
3646
|
-
}
|
|
3647
|
-
|
|
3763
|
+
});
|
|
3764
|
+
var updateRecord = grouped(function updateRecord2(db, id, data) {
|
|
3648
3765
|
const rec = db.query("SELECT id, database_id FROM records WHERE id = ? AND __deleted = 0").get(id);
|
|
3649
3766
|
if (!rec)
|
|
3650
3767
|
throw new MhError("not_found", `no such record: ${id}`);
|
|
@@ -3652,19 +3769,38 @@ function updateRecord(db, id, data) {
|
|
|
3652
3769
|
for (const { prop, value } of resolved)
|
|
3653
3770
|
emit(db, "records", id, prop.id, coerce(db, prop, value));
|
|
3654
3771
|
return getRecord(db, id);
|
|
3655
|
-
}
|
|
3656
|
-
|
|
3772
|
+
});
|
|
3773
|
+
var deleteRecord = grouped(function deleteRecord2(db, id) {
|
|
3657
3774
|
const rec = db.query("SELECT id FROM records WHERE id = ? AND __deleted = 0").get(id);
|
|
3658
3775
|
if (!rec)
|
|
3659
3776
|
return false;
|
|
3660
3777
|
emit(db, "records", id, "__deleted", 1);
|
|
3661
3778
|
return true;
|
|
3662
|
-
}
|
|
3779
|
+
});
|
|
3663
3780
|
|
|
3664
3781
|
// src/core/documents.ts
|
|
3665
3782
|
init_ids();
|
|
3666
3783
|
init_crdt();
|
|
3667
3784
|
init_errors();
|
|
3785
|
+
var DOCUMENT_COLS = [
|
|
3786
|
+
"id",
|
|
3787
|
+
"title",
|
|
3788
|
+
"body",
|
|
3789
|
+
"database_id",
|
|
3790
|
+
"parent_id",
|
|
3791
|
+
"created_hlc",
|
|
3792
|
+
"order_key"
|
|
3793
|
+
];
|
|
3794
|
+
var DOCUMENT_SELECT = DOCUMENT_COLS.join(", ");
|
|
3795
|
+
var DOC_SUMMARY_COLS = [
|
|
3796
|
+
"id",
|
|
3797
|
+
"title",
|
|
3798
|
+
"database_id",
|
|
3799
|
+
"parent_id",
|
|
3800
|
+
"created_hlc",
|
|
3801
|
+
"order_key"
|
|
3802
|
+
];
|
|
3803
|
+
var DOC_SUMMARY_SELECT = DOC_SUMMARY_COLS.join(", ");
|
|
3668
3804
|
function liveBlocks(db, docId) {
|
|
3669
3805
|
return db.query("SELECT id, text, order_key, blank_after FROM doc_blocks WHERE doc_id = ? AND __deleted = 0 ORDER BY order_key, id").all(docId);
|
|
3670
3806
|
}
|
|
@@ -3725,7 +3861,7 @@ function documentVersion(db, id) {
|
|
|
3725
3861
|
OR (dataset = 'doc_blocks' AND row_id IN (SELECT id FROM doc_blocks WHERE doc_id = ?))`).get(id, id);
|
|
3726
3862
|
return row.h ?? "";
|
|
3727
3863
|
}
|
|
3728
|
-
|
|
3864
|
+
var createDocument = grouped(function createDocument2(db, opts) {
|
|
3729
3865
|
const id = newId("doc", opts.title);
|
|
3730
3866
|
const first = emit(db, "documents", id, "title", opts.title);
|
|
3731
3867
|
emit(db, "documents", id, "created_hlc", first.hlc);
|
|
@@ -3740,13 +3876,13 @@ function createDocument(db, opts) {
|
|
|
3740
3876
|
insertBlocks(db, id, blocks, null, null);
|
|
3741
3877
|
}
|
|
3742
3878
|
return getDocument(db, id);
|
|
3743
|
-
}
|
|
3879
|
+
});
|
|
3744
3880
|
function getDocument(db, id) {
|
|
3745
|
-
return db.query(
|
|
3881
|
+
return db.query(`SELECT ${DOCUMENT_SELECT} FROM documents WHERE id = ? AND __deleted = 0`).get(id);
|
|
3746
3882
|
}
|
|
3747
3883
|
var ORDER_BY = "ORDER BY order_key IS NULL, order_key, created_hlc, id";
|
|
3748
3884
|
function listDocuments(db, opts = {}) {
|
|
3749
|
-
const cols =
|
|
3885
|
+
const cols = `SELECT ${DOC_SUMMARY_SELECT} FROM documents WHERE __deleted = 0`;
|
|
3750
3886
|
if (opts.parent_id !== undefined)
|
|
3751
3887
|
return db.query(`${cols} AND parent_id = ? ${ORDER_BY}`).all(opts.parent_id);
|
|
3752
3888
|
if (opts.database_id)
|
|
@@ -3797,11 +3933,11 @@ function backfillSiblings(db, parentId) {
|
|
|
3797
3933
|
db.query("UPDATE documents SET order_key = ? WHERE id = ?").run(keys[i2], row.id);
|
|
3798
3934
|
});
|
|
3799
3935
|
}
|
|
3800
|
-
|
|
3936
|
+
var backfillDocumentOrderKeys = grouped(function backfillDocumentOrderKeys2(db) {
|
|
3801
3937
|
const parents = db.query("SELECT DISTINCT parent_id FROM documents WHERE __deleted = 0 AND order_key IS NULL").all();
|
|
3802
3938
|
for (const p of parents)
|
|
3803
3939
|
backfillSiblings(db, p.parent_id);
|
|
3804
|
-
}
|
|
3940
|
+
});
|
|
3805
3941
|
function placeInSiblings(db, id, parentId, anchor) {
|
|
3806
3942
|
let cur = parentId;
|
|
3807
3943
|
while (cur) {
|
|
@@ -3833,7 +3969,7 @@ function placeInSiblings(db, id, parentId, anchor) {
|
|
|
3833
3969
|
}
|
|
3834
3970
|
emit(db, "documents", id, "order_key", key);
|
|
3835
3971
|
}
|
|
3836
|
-
|
|
3972
|
+
var moveDocument = grouped(function moveDocument2(db, id, targetId, where) {
|
|
3837
3973
|
const src2 = getDocument(db, id);
|
|
3838
3974
|
if (!src2)
|
|
3839
3975
|
throw new MhError("not_found", `no such document: ${id}`);
|
|
@@ -3847,8 +3983,26 @@ function moveDocument(db, id, targetId, where) {
|
|
|
3847
3983
|
else
|
|
3848
3984
|
placeInSiblings(db, id, target.parent_id, { targetId, where });
|
|
3849
3985
|
return getDocument(db, id);
|
|
3850
|
-
}
|
|
3851
|
-
function
|
|
3986
|
+
});
|
|
3987
|
+
var duplicateDocument = grouped(function duplicateDocument2(db, id, opts = {}) {
|
|
3988
|
+
const src2 = getDocument(db, id);
|
|
3989
|
+
if (!src2)
|
|
3990
|
+
throw new MhError("not_found", `no such document: ${id}`);
|
|
3991
|
+
ensureBlocks(db, id);
|
|
3992
|
+
const blocks = liveBlocks(db, id);
|
|
3993
|
+
const parentId = opts.parentId !== undefined ? opts.parentId : src2.parent_id;
|
|
3994
|
+
const dup = createDocument(db, {
|
|
3995
|
+
title: opts.title ?? src2.title,
|
|
3996
|
+
database_id: src2.database_id ?? undefined,
|
|
3997
|
+
parent_id: parentId ?? undefined
|
|
3998
|
+
});
|
|
3999
|
+
if (blocks.length)
|
|
4000
|
+
insertBlocks(db, dup.id, blocks.map((b2) => ({ text: b2.text, blankAfter: b2.blank_after })), null, null);
|
|
4001
|
+
if (parentId === src2.parent_id)
|
|
4002
|
+
moveDocument(db, dup.id, src2.id, "after");
|
|
4003
|
+
return getDocument(db, dup.id);
|
|
4004
|
+
});
|
|
4005
|
+
var updateDocument = grouped(function updateDocument2(db, id, fields, opts = {}) {
|
|
3852
4006
|
if (!getDocument(db, id))
|
|
3853
4007
|
throw new MhError("not_found", `no such document: ${id}`);
|
|
3854
4008
|
if (opts.ifMatch !== undefined && documentVersion(db, id) !== opts.ifMatch)
|
|
@@ -3867,8 +4021,8 @@ function updateDocument(db, id, fields, opts = {}) {
|
|
|
3867
4021
|
reconcileBody(db, id, fields.body);
|
|
3868
4022
|
}
|
|
3869
4023
|
return getDocument(db, id);
|
|
3870
|
-
}
|
|
3871
|
-
|
|
4024
|
+
});
|
|
4025
|
+
var editDocument = grouped(function editDocument2(db, id, opts) {
|
|
3872
4026
|
if (!getDocument(db, id))
|
|
3873
4027
|
throw new MhError("not_found", `no such document: ${id}`);
|
|
3874
4028
|
if (opts.ifMatch !== undefined && documentVersion(db, id) !== opts.ifMatch)
|
|
@@ -3899,8 +4053,8 @@ function editDocument(db, id, opts) {
|
|
|
3899
4053
|
const newBody = opts.replaceAll ? body.replaceAll(opts.old, opts.new) : body.replace(opts.old, opts.new);
|
|
3900
4054
|
reconcileBody(db, id, newBody);
|
|
3901
4055
|
return { id, changed: newBody !== body, replaced: opts.replaceAll ? count : 1, version: documentVersion(db, id) };
|
|
3902
|
-
}
|
|
3903
|
-
|
|
4056
|
+
});
|
|
4057
|
+
var appendDocument = grouped(function appendDocument2(db, id, body) {
|
|
3904
4058
|
if (!getDocument(db, id))
|
|
3905
4059
|
throw new MhError("not_found", `no such document: ${id}`);
|
|
3906
4060
|
ensureBlocks(db, id);
|
|
@@ -3911,8 +4065,8 @@ function appendDocument(db, id, body) {
|
|
|
3911
4065
|
insertBlocks(db, id, next, last, null);
|
|
3912
4066
|
}
|
|
3913
4067
|
return { id, changed: next.length > 0, replaced: next.length, version: documentVersion(db, id) };
|
|
3914
|
-
}
|
|
3915
|
-
|
|
4068
|
+
});
|
|
4069
|
+
var prependDocument = grouped(function prependDocument2(db, id, body) {
|
|
3916
4070
|
if (!getDocument(db, id))
|
|
3917
4071
|
throw new MhError("not_found", `no such document: ${id}`);
|
|
3918
4072
|
ensureBlocks(db, id);
|
|
@@ -3923,8 +4077,8 @@ function prependDocument(db, id, body) {
|
|
|
3923
4077
|
insertBlocks(db, id, next, null, first);
|
|
3924
4078
|
}
|
|
3925
4079
|
return { id, changed: next.length > 0, replaced: next.length, version: documentVersion(db, id) };
|
|
3926
|
-
}
|
|
3927
|
-
|
|
4080
|
+
});
|
|
4081
|
+
var deleteDocument = grouped(function deleteDocument2(db, id) {
|
|
3928
4082
|
if (!getDocument(db, id))
|
|
3929
4083
|
return false;
|
|
3930
4084
|
emit(db, "documents", id, "__deleted", 1);
|
|
@@ -3933,7 +4087,7 @@ function deleteDocument(db, id) {
|
|
|
3933
4087
|
for (const c3 of liveChildIds(db, "documents", "parent_id", id))
|
|
3934
4088
|
emit(db, "documents", c3, "parent_id", null);
|
|
3935
4089
|
return true;
|
|
3936
|
-
}
|
|
4090
|
+
});
|
|
3937
4091
|
function liveChildIds(db, table, col, parentId) {
|
|
3938
4092
|
return db.query(`SELECT id FROM ${table} WHERE ${col} = ? AND __deleted = 0`).all(parentId).map((r3) => r3.id);
|
|
3939
4093
|
}
|
|
@@ -3949,11 +4103,7 @@ function countOccurrences(haystack, needle) {
|
|
|
3949
4103
|
return n2;
|
|
3950
4104
|
}
|
|
3951
4105
|
|
|
3952
|
-
// src/core/
|
|
3953
|
-
function ensureDirs() {
|
|
3954
|
-
mkdirSync(metahubHome(), { recursive: true });
|
|
3955
|
-
mkdirSync(cacheDir(), { recursive: true });
|
|
3956
|
-
}
|
|
4106
|
+
// src/core/schema-init.ts
|
|
3957
4107
|
function runSchema(db) {
|
|
3958
4108
|
db.exec(CORE_SCHEMA);
|
|
3959
4109
|
try {
|
|
@@ -4016,15 +4166,29 @@ function migrateDocuments(db) {
|
|
|
4016
4166
|
db.exec("ALTER TABLE documents ADD COLUMN order_key TEXT");
|
|
4017
4167
|
backfillDocumentOrderKeys(db);
|
|
4018
4168
|
}
|
|
4019
|
-
function
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4169
|
+
function migrateOplog(db) {
|
|
4170
|
+
if (!hasColumn(db, "crdt_changes", "txn"))
|
|
4171
|
+
db.exec("ALTER TABLE crdt_changes ADD COLUMN txn TEXT");
|
|
4172
|
+
}
|
|
4173
|
+
function initSchema(db) {
|
|
4023
4174
|
runSchema(db);
|
|
4175
|
+
migrateOplog(db);
|
|
4024
4176
|
migrateRecords(db);
|
|
4025
4177
|
migratePeers(db);
|
|
4026
4178
|
migrateDocBlocks(db);
|
|
4027
4179
|
migrateDocuments(db);
|
|
4180
|
+
}
|
|
4181
|
+
|
|
4182
|
+
// src/core/db.ts
|
|
4183
|
+
function ensureDirs() {
|
|
4184
|
+
mkdirSync(metahubHome(), { recursive: true });
|
|
4185
|
+
mkdirSync(cacheDir(), { recursive: true });
|
|
4186
|
+
}
|
|
4187
|
+
function openMetahub() {
|
|
4188
|
+
ensureDirs();
|
|
4189
|
+
const db = new Database(dbPath(), { create: true });
|
|
4190
|
+
db.exec("PRAGMA journal_mode = WAL;");
|
|
4191
|
+
initSchema(db);
|
|
4028
4192
|
return db;
|
|
4029
4193
|
}
|
|
4030
4194
|
|
|
@@ -4051,6 +4215,9 @@ function print(data, pretty) {
|
|
|
4051
4215
|
console.log(JSON.stringify(data, null, 2));
|
|
4052
4216
|
}
|
|
4053
4217
|
}
|
|
4218
|
+
function warn(message) {
|
|
4219
|
+
console.error(`warning: ${message}`);
|
|
4220
|
+
}
|
|
4054
4221
|
var EXIT_CODES = {
|
|
4055
4222
|
invalid_input: 2,
|
|
4056
4223
|
not_found: 3,
|
|
@@ -4110,236 +4277,6 @@ var init_default = defineCommand({
|
|
|
4110
4277
|
}
|
|
4111
4278
|
});
|
|
4112
4279
|
|
|
4113
|
-
// src/cli/commands/db.ts
|
|
4114
|
-
var create = defineCommand({
|
|
4115
|
-
meta: { name: "create", description: "Create a database" },
|
|
4116
|
-
args: {
|
|
4117
|
-
name: { type: "positional", required: true, description: "Database name" },
|
|
4118
|
-
icon: { type: "string", description: "Optional icon" }
|
|
4119
|
-
},
|
|
4120
|
-
run: guard((args) => {
|
|
4121
|
-
const row = createDatabase(openMetahub(), { name: args.name, icon: args.icon });
|
|
4122
|
-
print(row, () => `${row.id} ${row.name}`);
|
|
4123
|
-
})
|
|
4124
|
-
});
|
|
4125
|
-
var list = defineCommand({
|
|
4126
|
-
meta: { name: "list", description: "List databases" },
|
|
4127
|
-
run: guard(() => {
|
|
4128
|
-
const rows = listDatabases(openMetahub());
|
|
4129
|
-
print(rows, () => table(rows));
|
|
4130
|
-
})
|
|
4131
|
-
});
|
|
4132
|
-
var get = defineCommand({
|
|
4133
|
-
meta: { name: "get", description: "Show one database" },
|
|
4134
|
-
args: { id: { type: "positional", required: true, description: "Database ref (id/prefix/name)" } },
|
|
4135
|
-
run: guard((args) => {
|
|
4136
|
-
const db = openMetahub();
|
|
4137
|
-
print(getDatabase(db, resolveRef(db, args.id, { kind: "db" })));
|
|
4138
|
-
})
|
|
4139
|
-
});
|
|
4140
|
-
var del = defineCommand({
|
|
4141
|
-
meta: { name: "delete", description: "Delete a database" },
|
|
4142
|
-
args: { id: { type: "positional", required: true, description: "Database ref (id/prefix/name)" } },
|
|
4143
|
-
run: guard((args) => {
|
|
4144
|
-
const db = openMetahub();
|
|
4145
|
-
const id = resolveRef(db, args.id, { kind: "db" });
|
|
4146
|
-
deleteDatabase(db, id);
|
|
4147
|
-
print({ ok: true, deleted: id });
|
|
4148
|
-
})
|
|
4149
|
-
});
|
|
4150
|
-
var db_default = defineCommand({
|
|
4151
|
-
meta: { name: "db", description: "Manage databases (notion-like tables)" },
|
|
4152
|
-
subCommands: { create, list, get, delete: del }
|
|
4153
|
-
});
|
|
4154
|
-
|
|
4155
|
-
// src/core/context.ts
|
|
4156
|
-
var KEY = "current_db";
|
|
4157
|
-
function getCurrentDatabase(db) {
|
|
4158
|
-
const row = db.query("SELECT value FROM meta WHERE key = ?").get(KEY);
|
|
4159
|
-
if (!row)
|
|
4160
|
-
return null;
|
|
4161
|
-
const found = getDatabase(db, row.value);
|
|
4162
|
-
if (!found) {
|
|
4163
|
-
clearCurrentDatabase(db);
|
|
4164
|
-
return null;
|
|
4165
|
-
}
|
|
4166
|
-
return found;
|
|
4167
|
-
}
|
|
4168
|
-
function setCurrentDatabase(db, id) {
|
|
4169
|
-
db.query("INSERT INTO meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(KEY, id);
|
|
4170
|
-
}
|
|
4171
|
-
function clearCurrentDatabase(db) {
|
|
4172
|
-
db.query("DELETE FROM meta WHERE key = ?").run(KEY);
|
|
4173
|
-
}
|
|
4174
|
-
|
|
4175
|
-
// src/cli/commands/use.ts
|
|
4176
|
-
var use_default = defineCommand({
|
|
4177
|
-
meta: {
|
|
4178
|
-
name: "use",
|
|
4179
|
-
description: "Set/show the current database (record, doc, prop default to it)"
|
|
4180
|
-
},
|
|
4181
|
-
args: {
|
|
4182
|
-
database: {
|
|
4183
|
-
type: "positional",
|
|
4184
|
-
required: false,
|
|
4185
|
-
description: "Database ref (id/prefix/name); omit to show current"
|
|
4186
|
-
},
|
|
4187
|
-
clear: { type: "boolean", description: "Clear the current database" }
|
|
4188
|
-
},
|
|
4189
|
-
run: guard((args) => {
|
|
4190
|
-
const db = openMetahub();
|
|
4191
|
-
if (args.clear) {
|
|
4192
|
-
clearCurrentDatabase(db);
|
|
4193
|
-
print({ ok: true, current: null }, () => "\u2713 current database cleared");
|
|
4194
|
-
return;
|
|
4195
|
-
}
|
|
4196
|
-
if (args.database == null) {
|
|
4197
|
-
const cur = getCurrentDatabase(db);
|
|
4198
|
-
print({ current: cur }, () => cur ? `${cur.id} ${cur.name}` : "(no current database)");
|
|
4199
|
-
return;
|
|
4200
|
-
}
|
|
4201
|
-
const id = resolveRef(db, args.database, { kind: "db" });
|
|
4202
|
-
setCurrentDatabase(db, id);
|
|
4203
|
-
const row = getDatabase(db, id);
|
|
4204
|
-
print({ ok: true, current: row }, () => `\u2713 current database \u2192 ${row.name} (${row.id})`);
|
|
4205
|
-
})
|
|
4206
|
-
});
|
|
4207
|
-
|
|
4208
|
-
// src/cli/commands/get.ts
|
|
4209
|
-
var get_default = defineCommand({
|
|
4210
|
-
meta: { name: "get", description: "Look up any entity by ref (id/prefix/name), type auto-detected" },
|
|
4211
|
-
args: { ref: { type: "positional", required: true, description: "Ref (id/prefix/name)" } },
|
|
4212
|
-
run: guard((args) => {
|
|
4213
|
-
const db = openMetahub();
|
|
4214
|
-
const { kind, id } = resolveEntity(db, args.ref);
|
|
4215
|
-
switch (kind) {
|
|
4216
|
-
case "db": {
|
|
4217
|
-
const row = getDatabase(db, id);
|
|
4218
|
-
return print(row, () => `${row.id} ${row.name}`);
|
|
4219
|
-
}
|
|
4220
|
-
case "prop": {
|
|
4221
|
-
const row = getProperty(db, id);
|
|
4222
|
-
return print(row, () => table([{ id: row.id, name: row.name, type: row.type }]));
|
|
4223
|
-
}
|
|
4224
|
-
case "rec":
|
|
4225
|
-
return print(getRecord(db, id));
|
|
4226
|
-
case "doc": {
|
|
4227
|
-
const row = getDocument(db, id);
|
|
4228
|
-
return print(row, () => row.body ?? "");
|
|
4229
|
-
}
|
|
4230
|
-
}
|
|
4231
|
-
})
|
|
4232
|
-
});
|
|
4233
|
-
|
|
4234
|
-
// src/cli/input.ts
|
|
4235
|
-
async function resolveValue2(raw) {
|
|
4236
|
-
if (raw == null)
|
|
4237
|
-
return;
|
|
4238
|
-
if (raw === "@-")
|
|
4239
|
-
return await Bun.stdin.text();
|
|
4240
|
-
if (raw.startsWith("@"))
|
|
4241
|
-
return await Bun.file(raw.slice(1)).text();
|
|
4242
|
-
return raw;
|
|
4243
|
-
}
|
|
4244
|
-
async function resolveJson(raw) {
|
|
4245
|
-
const text = await resolveValue2(raw);
|
|
4246
|
-
if (text == null)
|
|
4247
|
-
return;
|
|
4248
|
-
return JSON.parse(text);
|
|
4249
|
-
}
|
|
4250
|
-
|
|
4251
|
-
// src/cli/refs.ts
|
|
4252
|
-
function resolveDb(db, ref) {
|
|
4253
|
-
if (ref != null)
|
|
4254
|
-
return resolveRef(db, ref, { kind: "db" });
|
|
4255
|
-
const cur = getCurrentDatabase(db);
|
|
4256
|
-
if (!cur)
|
|
4257
|
-
fail("no current database; pass <db> or run `mh use <db>`");
|
|
4258
|
-
return cur.id;
|
|
4259
|
-
}
|
|
4260
|
-
|
|
4261
|
-
// src/cli/commands/prop.ts
|
|
4262
|
-
function buildConfig(args, base) {
|
|
4263
|
-
const config = { ...base };
|
|
4264
|
-
if (typeof args.options === "string")
|
|
4265
|
-
config.options = args.options.split(",").map((s2) => s2.trim()).filter(Boolean);
|
|
4266
|
-
if (typeof args.target === "string")
|
|
4267
|
-
config.database = args.target;
|
|
4268
|
-
return Object.keys(config).length ? config : undefined;
|
|
4269
|
-
}
|
|
4270
|
-
var add = defineCommand({
|
|
4271
|
-
meta: { name: "add", description: "Add a property (column) to a database" },
|
|
4272
|
-
args: {
|
|
4273
|
-
name: { type: "positional", required: true, description: "Property name" },
|
|
4274
|
-
db: { type: "string", description: "Database ref (default: current)" },
|
|
4275
|
-
type: { type: "string", required: true, description: "text|number|checkbox|select|multi_select|date|relation|url" },
|
|
4276
|
-
options: { type: "string", description: "Comma list for select/multi_select" },
|
|
4277
|
-
target: { type: "string", description: "Target database ref for relation" },
|
|
4278
|
-
config: { type: "string", description: "Raw config JSON (@file/@- ok)" },
|
|
4279
|
-
position: { type: "string", description: "Sort position" }
|
|
4280
|
-
},
|
|
4281
|
-
run: guard(async (args) => {
|
|
4282
|
-
const db = openMetahub();
|
|
4283
|
-
const databaseId = resolveDb(db, args.db);
|
|
4284
|
-
const fromJson = await resolveJson(args.config);
|
|
4285
|
-
const target = args.target != null ? resolveRef(db, args.target, { kind: "db" }) : undefined;
|
|
4286
|
-
const config = buildConfig({ ...args, target }, fromJson);
|
|
4287
|
-
const row = addProperty(db, databaseId, {
|
|
4288
|
-
name: args.name,
|
|
4289
|
-
type: args.type,
|
|
4290
|
-
config,
|
|
4291
|
-
position: args.position != null ? Number(args.position) : undefined
|
|
4292
|
-
});
|
|
4293
|
-
print(row, () => `${row.id} ${row.name} ${row.type}`);
|
|
4294
|
-
})
|
|
4295
|
-
});
|
|
4296
|
-
var list2 = defineCommand({
|
|
4297
|
-
meta: { name: "list", description: "List properties of a database" },
|
|
4298
|
-
args: { database: { type: "positional", required: false, description: "Database ref (default: current)" } },
|
|
4299
|
-
run: guard((args) => {
|
|
4300
|
-
const db = openMetahub();
|
|
4301
|
-
const rows = listProperties(db, resolveDb(db, args.database));
|
|
4302
|
-
print(rows, () => table(rows.map((r3) => ({ id: r3.id, name: r3.name, type: r3.type, position: r3.position }))));
|
|
4303
|
-
})
|
|
4304
|
-
});
|
|
4305
|
-
var update = defineCommand({
|
|
4306
|
-
meta: { name: "update", description: "Update a property" },
|
|
4307
|
-
args: {
|
|
4308
|
-
id: { type: "positional", required: true, description: "Property ref (id/prefix/name)" },
|
|
4309
|
-
name: { type: "string" },
|
|
4310
|
-
options: { type: "string", description: "Comma list for select/multi_select" },
|
|
4311
|
-
target: { type: "string", description: "Target database ref for relation" },
|
|
4312
|
-
config: { type: "string", description: "Raw config JSON (@file/@- ok)" },
|
|
4313
|
-
position: { type: "string" }
|
|
4314
|
-
},
|
|
4315
|
-
run: guard(async (args) => {
|
|
4316
|
-
const db = openMetahub();
|
|
4317
|
-
const fromJson = await resolveJson(args.config);
|
|
4318
|
-
const target = args.target != null ? resolveRef(db, args.target, { kind: "db" }) : undefined;
|
|
4319
|
-
const config = buildConfig({ ...args, target }, fromJson);
|
|
4320
|
-
const row = updateProperty(db, resolveRef(db, args.id, { kind: "prop" }), {
|
|
4321
|
-
name: args.name,
|
|
4322
|
-
config,
|
|
4323
|
-
position: args.position != null ? Number(args.position) : undefined
|
|
4324
|
-
});
|
|
4325
|
-
print(row);
|
|
4326
|
-
})
|
|
4327
|
-
});
|
|
4328
|
-
var remove = defineCommand({
|
|
4329
|
-
meta: { name: "remove", description: "Remove a property" },
|
|
4330
|
-
args: { id: { type: "positional", required: true, description: "Property ref (id/prefix/name)" } },
|
|
4331
|
-
run: guard((args) => {
|
|
4332
|
-
const db = openMetahub();
|
|
4333
|
-
const id = resolveRef(db, args.id, { kind: "prop" });
|
|
4334
|
-
removeProperty(db, id);
|
|
4335
|
-
print({ ok: true, removed: id });
|
|
4336
|
-
})
|
|
4337
|
-
});
|
|
4338
|
-
var prop_default = defineCommand({
|
|
4339
|
-
meta: { name: "prop", description: "Manage database properties (columns)" },
|
|
4340
|
-
subCommands: { add, list: list2, update, remove }
|
|
4341
|
-
});
|
|
4342
|
-
|
|
4343
4280
|
// src/core/history.ts
|
|
4344
4281
|
init_crdt();
|
|
4345
4282
|
init_errors();
|
|
@@ -4350,12 +4287,20 @@ function hlcMillis(hlc) {
|
|
|
4350
4287
|
function hlcIso(hlc) {
|
|
4351
4288
|
return new Date(hlcMillis(hlc)).toISOString();
|
|
4352
4289
|
}
|
|
4290
|
+
function revisionKind(txn) {
|
|
4291
|
+
if (txn?.startsWith("repair:"))
|
|
4292
|
+
return "repair";
|
|
4293
|
+
if (txn?.startsWith("revert:"))
|
|
4294
|
+
return "revert";
|
|
4295
|
+
return "user";
|
|
4296
|
+
}
|
|
4353
4297
|
function clusterRevisions(changes) {
|
|
4354
4298
|
const groups = [];
|
|
4355
4299
|
let cur = [];
|
|
4356
4300
|
for (const c3 of changes) {
|
|
4357
4301
|
const prev = cur[cur.length - 1];
|
|
4358
|
-
|
|
4302
|
+
const boundary = prev && (prev.txn !== null && c3.txn !== null ? prev.txn !== c3.txn : prev.node_id !== c3.node_id || hlcMillis(c3.hlc) - hlcMillis(prev.hlc) > REVISION_GAP_MS);
|
|
4303
|
+
if (boundary) {
|
|
4359
4304
|
groups.push(cur);
|
|
4360
4305
|
cur = [];
|
|
4361
4306
|
}
|
|
@@ -4380,7 +4325,7 @@ function registersAt(changes, at) {
|
|
|
4380
4325
|
return out;
|
|
4381
4326
|
}
|
|
4382
4327
|
function rowChanges(db, dataset, rowId) {
|
|
4383
|
-
return db.query(
|
|
4328
|
+
return db.query(`SELECT ${CHANGE_SELECT} FROM crdt_changes WHERE dataset = ? AND row_id = ? ORDER BY hlc`).all(dataset, rowId);
|
|
4384
4329
|
}
|
|
4385
4330
|
function everBlockIds(db, docId) {
|
|
4386
4331
|
return db.query("SELECT DISTINCT row_id FROM crdt_changes WHERE dataset = 'doc_blocks' AND col = 'doc_id' AND value = ?").all(JSON.stringify(docId)).map((r3) => r3.row_id);
|
|
@@ -4389,7 +4334,7 @@ function docChanges(db, docId) {
|
|
|
4389
4334
|
const blocks = everBlockIds(db, docId);
|
|
4390
4335
|
const placeholders = blocks.map(() => "?").join(",");
|
|
4391
4336
|
const blockClause = blocks.length ? ` OR (dataset = 'doc_blocks' AND row_id IN (${placeholders}))` : "";
|
|
4392
|
-
return db.query(`SELECT
|
|
4337
|
+
return db.query(`SELECT ${CHANGE_SELECT} FROM crdt_changes
|
|
4393
4338
|
WHERE (dataset = 'documents' AND row_id = ?)${blockClause}
|
|
4394
4339
|
ORDER BY hlc`).all(docId, ...blocks);
|
|
4395
4340
|
}
|
|
@@ -4422,6 +4367,7 @@ function listDocumentRevisions(db, id) {
|
|
|
4422
4367
|
version: last.hlc,
|
|
4423
4368
|
at: hlcIso(last.hlc),
|
|
4424
4369
|
node_id: last.node_id,
|
|
4370
|
+
kind: revisionKind(last.txn),
|
|
4425
4371
|
changes: group.length,
|
|
4426
4372
|
created,
|
|
4427
4373
|
deleted,
|
|
@@ -4488,15 +4434,20 @@ function documentAtVersion(db, id, at) {
|
|
|
4488
4434
|
version: changes[changes.length - 1].hlc
|
|
4489
4435
|
};
|
|
4490
4436
|
}
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
if (!cur)
|
|
4437
|
+
var revertDocument = grouped(function revertDocument2(db, id, to, opts = {}) {
|
|
4438
|
+
let cur = getDocument(db, id);
|
|
4439
|
+
if (!cur && !db.query("SELECT 1 AS x FROM documents WHERE id = ?").get(id))
|
|
4494
4440
|
throw new MhError("not_found", `no such document: ${id}`);
|
|
4495
4441
|
if (opts.ifMatch !== undefined && documentVersion(db, id) !== opts.ifMatch)
|
|
4496
4442
|
throw new MhError("stale", `stale: document changed since ${opts.ifMatch}; re-read first`);
|
|
4497
4443
|
const past = documentAtVersion(db, id, to);
|
|
4498
4444
|
if (past.deleted)
|
|
4499
4445
|
throw new MhError("invalid_input", "target version is a deleted state; use doc delete instead");
|
|
4446
|
+
const undeleted = !cur;
|
|
4447
|
+
if (!cur) {
|
|
4448
|
+
emit(db, "documents", id, "__deleted", 0);
|
|
4449
|
+
cur = getDocument(db, id);
|
|
4450
|
+
}
|
|
4500
4451
|
const fields = {};
|
|
4501
4452
|
if (past.title !== cur.title)
|
|
4502
4453
|
fields.title = past.title;
|
|
@@ -4506,43 +4457,98 @@ function revertDocument(db, id, to, opts = {}) {
|
|
|
4506
4457
|
updateDocument(db, id, fields);
|
|
4507
4458
|
return {
|
|
4508
4459
|
id,
|
|
4509
|
-
changed: Object.keys(fields).length > 0,
|
|
4460
|
+
changed: Object.keys(fields).length > 0 || undeleted,
|
|
4510
4461
|
restored: past.version,
|
|
4511
|
-
version: documentVersion(db, id)
|
|
4462
|
+
version: documentVersion(db, id),
|
|
4463
|
+
undeleted
|
|
4512
4464
|
};
|
|
4513
|
-
}
|
|
4465
|
+
}, "revert");
|
|
4514
4466
|
var RECORD_META2 = new Set(["database_id", "created_hlc", "order_key", "__deleted"]);
|
|
4467
|
+
function recordRevisionOf(group) {
|
|
4468
|
+
const fields = new Set;
|
|
4469
|
+
let created = false;
|
|
4470
|
+
let deleted = false;
|
|
4471
|
+
let moved = false;
|
|
4472
|
+
for (const c3 of group) {
|
|
4473
|
+
if (c3.col === "created_hlc")
|
|
4474
|
+
created = true;
|
|
4475
|
+
else if (c3.col === "__deleted")
|
|
4476
|
+
deleted = flagSet(c3.value);
|
|
4477
|
+
else if (c3.col === "order_key")
|
|
4478
|
+
moved = true;
|
|
4479
|
+
else if (!RECORD_META2.has(c3.col))
|
|
4480
|
+
fields.add(c3.col);
|
|
4481
|
+
}
|
|
4482
|
+
const last = group[group.length - 1];
|
|
4483
|
+
return {
|
|
4484
|
+
version: last.hlc,
|
|
4485
|
+
at: hlcIso(last.hlc),
|
|
4486
|
+
node_id: last.node_id,
|
|
4487
|
+
kind: revisionKind(last.txn),
|
|
4488
|
+
changes: group.length,
|
|
4489
|
+
created,
|
|
4490
|
+
deleted,
|
|
4491
|
+
fields: [...fields],
|
|
4492
|
+
moved
|
|
4493
|
+
};
|
|
4494
|
+
}
|
|
4515
4495
|
function listRecordRevisions(db, id) {
|
|
4516
4496
|
const changes = rowChanges(db, "records", id);
|
|
4517
4497
|
if (!changes.length)
|
|
4518
4498
|
throw new MhError("not_found", `no such record: ${id}`);
|
|
4519
|
-
return clusterRevisions(changes).map((
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4499
|
+
return clusterRevisions(changes).map(recordRevisionOf).reverse();
|
|
4500
|
+
}
|
|
4501
|
+
function listDatabaseActivity(db, databaseId, opts = {}) {
|
|
4502
|
+
if (!db.query("SELECT 1 AS x FROM databases WHERE id = ?").get(databaseId))
|
|
4503
|
+
throw new MhError("not_found", `no such database: ${databaseId}`);
|
|
4504
|
+
const titleProp = db.query("SELECT id FROM properties WHERE database_id = ? AND type = 'text' AND __deleted = 0 ORDER BY position LIMIT 1").get(databaseId)?.id ?? null;
|
|
4505
|
+
const changes = db.query(`SELECT ${CHANGE_SELECT} FROM crdt_changes
|
|
4506
|
+
WHERE dataset = 'records' AND row_id IN (SELECT id FROM records WHERE database_id = ?)
|
|
4507
|
+
ORDER BY row_id, hlc`).all(databaseId);
|
|
4508
|
+
const out = [];
|
|
4509
|
+
let start = 0;
|
|
4510
|
+
for (let i2 = 1;i2 <= changes.length; i2++) {
|
|
4511
|
+
if (i2 !== changes.length && changes[i2].row_id === changes[start].row_id)
|
|
4512
|
+
continue;
|
|
4513
|
+
const rowId = changes[start].row_id;
|
|
4514
|
+
const state = new Map;
|
|
4515
|
+
for (const g3 of clusterRevisions(changes.slice(start, i2))) {
|
|
4516
|
+
const touched = new Set;
|
|
4517
|
+
for (const c3 of g3)
|
|
4518
|
+
if (!RECORD_META2.has(c3.col))
|
|
4519
|
+
touched.add(c3.col);
|
|
4520
|
+
const before = new Map;
|
|
4521
|
+
for (const col of touched)
|
|
4522
|
+
if (state.has(col))
|
|
4523
|
+
before.set(col, state.get(col));
|
|
4524
|
+
for (const c3 of g3) {
|
|
4525
|
+
if (RECORD_META2.has(c3.col))
|
|
4526
|
+
continue;
|
|
4527
|
+
if (c3.value === null)
|
|
4528
|
+
state.delete(c3.col);
|
|
4529
|
+
else
|
|
4530
|
+
state.set(c3.col, JSON.parse(c3.value));
|
|
4531
|
+
}
|
|
4532
|
+
const diffs = [];
|
|
4533
|
+
for (const col of touched) {
|
|
4534
|
+
const fc = { prop: col };
|
|
4535
|
+
if (before.has(col))
|
|
4536
|
+
fc.before = before.get(col);
|
|
4537
|
+
if (state.has(col))
|
|
4538
|
+
fc.after = state.get(col);
|
|
4539
|
+
diffs.push(fc);
|
|
4540
|
+
}
|
|
4541
|
+
out.push({
|
|
4542
|
+
...recordRevisionOf(g3),
|
|
4543
|
+
record_id: rowId,
|
|
4544
|
+
record_title: titleProp && state.has(titleProp) ? String(state.get(titleProp) ?? "") : null,
|
|
4545
|
+
diffs
|
|
4546
|
+
});
|
|
4533
4547
|
}
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
node_id: last.node_id,
|
|
4539
|
-
changes: group.length,
|
|
4540
|
-
created,
|
|
4541
|
-
deleted,
|
|
4542
|
-
fields: [...fields],
|
|
4543
|
-
moved
|
|
4544
|
-
};
|
|
4545
|
-
}).reverse();
|
|
4548
|
+
start = i2;
|
|
4549
|
+
}
|
|
4550
|
+
out.sort((a2, b2) => a2.version < b2.version ? 1 : -1);
|
|
4551
|
+
return out.slice(0, opts.limit ?? 100);
|
|
4546
4552
|
}
|
|
4547
4553
|
function recordAtVersion(db, id, at) {
|
|
4548
4554
|
if (!at)
|
|
@@ -4568,7 +4574,7 @@ function recordAtVersion(db, id, at) {
|
|
|
4568
4574
|
version: changes[changes.length - 1].hlc
|
|
4569
4575
|
};
|
|
4570
4576
|
}
|
|
4571
|
-
|
|
4577
|
+
var revertRecord = grouped(function revertRecord2(db, id, to) {
|
|
4572
4578
|
const cur = db.query("SELECT database_id, data, __deleted FROM records WHERE id = ?").get(id);
|
|
4573
4579
|
if (!cur)
|
|
4574
4580
|
throw new MhError("not_found", `no such record: ${id}`);
|
|
@@ -4593,7 +4599,127 @@ function revertRecord(db, id, to) {
|
|
|
4593
4599
|
if (undeleted)
|
|
4594
4600
|
emit(db, "records", id, "__deleted", 0);
|
|
4595
4601
|
return { id, changed: fields.length > 0 || undeleted, fields, undeleted, restored: past.version };
|
|
4602
|
+
}, "revert");
|
|
4603
|
+
var PROP_DEF_COLS = ["name", "type", "config", "position"];
|
|
4604
|
+
function listPropertyRevisions(db, id) {
|
|
4605
|
+
const changes = rowChanges(db, "properties", id);
|
|
4606
|
+
if (!changes.length)
|
|
4607
|
+
throw new MhError("not_found", `no such property: ${id}`);
|
|
4608
|
+
const clearedByTxn = new Map(db.query("SELECT txn, COUNT(*) AS n FROM crdt_changes WHERE dataset = 'records' AND col = ? AND txn IS NOT NULL GROUP BY txn").all(id).map((r3) => [r3.txn, r3.n]));
|
|
4609
|
+
return clusterRevisions(changes).map((group) => {
|
|
4610
|
+
const fields = new Set;
|
|
4611
|
+
let created = false;
|
|
4612
|
+
let deleted = false;
|
|
4613
|
+
for (const c3 of group) {
|
|
4614
|
+
if (c3.col === "database_id")
|
|
4615
|
+
created = true;
|
|
4616
|
+
else if (c3.col === "__deleted")
|
|
4617
|
+
deleted = flagSet(c3.value);
|
|
4618
|
+
else if (PROP_DEF_COLS.includes(c3.col))
|
|
4619
|
+
fields.add(c3.col);
|
|
4620
|
+
}
|
|
4621
|
+
const last = group[group.length - 1];
|
|
4622
|
+
return {
|
|
4623
|
+
version: last.hlc,
|
|
4624
|
+
at: hlcIso(last.hlc),
|
|
4625
|
+
node_id: last.node_id,
|
|
4626
|
+
kind: revisionKind(last.txn),
|
|
4627
|
+
changes: group.length,
|
|
4628
|
+
created,
|
|
4629
|
+
deleted,
|
|
4630
|
+
fields: [...fields],
|
|
4631
|
+
cells_cleared: last.txn ? clearedByTxn.get(last.txn) ?? 0 : 0
|
|
4632
|
+
};
|
|
4633
|
+
}).reverse();
|
|
4596
4634
|
}
|
|
4635
|
+
function propertyAtVersion(db, id, at) {
|
|
4636
|
+
if (!at)
|
|
4637
|
+
throw new MhError("invalid_input", "missing version cutoff");
|
|
4638
|
+
const changes = rowChanges(db, "properties", id).filter((c3) => c3.hlc <= at);
|
|
4639
|
+
if (!changes.length)
|
|
4640
|
+
throw new MhError("not_found", `no version of property ${id} at or before ${at}`);
|
|
4641
|
+
const regs = registersAt(changes, at);
|
|
4642
|
+
const reg = (col) => {
|
|
4643
|
+
const r3 = regs.get(col);
|
|
4644
|
+
return r3?.value == null ? undefined : JSON.parse(r3.value);
|
|
4645
|
+
};
|
|
4646
|
+
return {
|
|
4647
|
+
id,
|
|
4648
|
+
database_id: reg("database_id") ?? null,
|
|
4649
|
+
name: reg("name") ?? "",
|
|
4650
|
+
type: reg("type") ?? "text",
|
|
4651
|
+
config: reg("config") ?? null,
|
|
4652
|
+
position: reg("position") ?? null,
|
|
4653
|
+
deleted: regs.get("__deleted") ? flagSet(regs.get("__deleted").value) : false,
|
|
4654
|
+
version: changes[changes.length - 1].hlc
|
|
4655
|
+
};
|
|
4656
|
+
}
|
|
4657
|
+
var revertProperty = grouped(function revertProperty2(db, id, to) {
|
|
4658
|
+
const cur = db.query("SELECT database_id, name, type, config, position, __deleted FROM properties WHERE id = ?").get(id);
|
|
4659
|
+
if (!cur)
|
|
4660
|
+
throw new MhError("not_found", `no such property: ${id}`);
|
|
4661
|
+
const past = propertyAtVersion(db, id, to);
|
|
4662
|
+
if (past.deleted)
|
|
4663
|
+
throw new MhError("invalid_input", "target version is a deleted state; use prop remove instead");
|
|
4664
|
+
const curDef = {
|
|
4665
|
+
name: cur.name,
|
|
4666
|
+
type: cur.type,
|
|
4667
|
+
config: cur.config ? JSON.parse(cur.config) : null,
|
|
4668
|
+
position: cur.position
|
|
4669
|
+
};
|
|
4670
|
+
const pastDef = {
|
|
4671
|
+
name: past.name,
|
|
4672
|
+
type: past.type,
|
|
4673
|
+
config: past.config,
|
|
4674
|
+
position: past.position
|
|
4675
|
+
};
|
|
4676
|
+
const fields = [];
|
|
4677
|
+
for (const col of PROP_DEF_COLS) {
|
|
4678
|
+
if (JSON.stringify(pastDef[col] ?? null) !== JSON.stringify(curDef[col] ?? null)) {
|
|
4679
|
+
emit(db, "properties", id, col, pastDef[col]);
|
|
4680
|
+
fields.push(col);
|
|
4681
|
+
}
|
|
4682
|
+
}
|
|
4683
|
+
const undeleted = cur.__deleted !== 0;
|
|
4684
|
+
if (undeleted)
|
|
4685
|
+
emit(db, "properties", id, "__deleted", 0);
|
|
4686
|
+
const schemaTxns = new Set(db.query("SELECT DISTINCT txn FROM crdt_changes WHERE dataset = 'properties' AND row_id = ? AND hlc > ? AND txn IS NOT NULL").all(id, to).map((r3) => r3.txn));
|
|
4687
|
+
const cellChanges = db.query("SELECT row_id, hlc, value, txn FROM crdt_changes WHERE dataset = 'records' AND col = ? ORDER BY row_id, hlc").all(id);
|
|
4688
|
+
const isCascade = (txn) => txn !== null && (schemaTxns.has(txn) || txn.startsWith("repair:"));
|
|
4689
|
+
let restored = 0;
|
|
4690
|
+
let skipped = 0;
|
|
4691
|
+
let i2 = 0;
|
|
4692
|
+
while (i2 < cellChanges.length) {
|
|
4693
|
+
const rowId = cellChanges[i2].row_id;
|
|
4694
|
+
let winner = cellChanges[i2];
|
|
4695
|
+
let lastUser = null;
|
|
4696
|
+
for (;i2 < cellChanges.length && cellChanges[i2].row_id === rowId; i2++) {
|
|
4697
|
+
winner = cellChanges[i2];
|
|
4698
|
+
if (!isCascade(winner.txn))
|
|
4699
|
+
lastUser = winner;
|
|
4700
|
+
}
|
|
4701
|
+
if (winner.hlc <= to)
|
|
4702
|
+
continue;
|
|
4703
|
+
if (!isCascade(winner.txn)) {
|
|
4704
|
+
skipped++;
|
|
4705
|
+
continue;
|
|
4706
|
+
}
|
|
4707
|
+
const target = lastUser ? lastUser.value : null;
|
|
4708
|
+
if (target === winner.value)
|
|
4709
|
+
continue;
|
|
4710
|
+
emit(db, "records", rowId, id, target === null ? undefined : JSON.parse(target));
|
|
4711
|
+
restored++;
|
|
4712
|
+
}
|
|
4713
|
+
return {
|
|
4714
|
+
id,
|
|
4715
|
+
changed: fields.length > 0 || undeleted || restored > 0,
|
|
4716
|
+
fields,
|
|
4717
|
+
restored_cells: restored,
|
|
4718
|
+
skipped_cells: skipped,
|
|
4719
|
+
undeleted,
|
|
4720
|
+
restored: past.version
|
|
4721
|
+
};
|
|
4722
|
+
}, "revert");
|
|
4597
4723
|
function recordFieldHistory(db, id, propId) {
|
|
4598
4724
|
const rows = db.query("SELECT hlc, node_id, value FROM crdt_changes WHERE dataset = 'records' AND row_id = ? AND col = ? ORDER BY hlc DESC").all(id, propId);
|
|
4599
4725
|
return rows.map((r3) => ({
|
|
@@ -4605,6 +4731,356 @@ function recordFieldHistory(db, id, propId) {
|
|
|
4605
4731
|
}));
|
|
4606
4732
|
}
|
|
4607
4733
|
|
|
4734
|
+
// src/core/context.ts
|
|
4735
|
+
var KEY = "current_db";
|
|
4736
|
+
function getCurrentDatabase(db) {
|
|
4737
|
+
const row = db.query("SELECT value FROM meta WHERE key = ?").get(KEY);
|
|
4738
|
+
if (!row)
|
|
4739
|
+
return null;
|
|
4740
|
+
const found = getDatabase(db, row.value);
|
|
4741
|
+
if (!found) {
|
|
4742
|
+
clearCurrentDatabase(db);
|
|
4743
|
+
return null;
|
|
4744
|
+
}
|
|
4745
|
+
return found;
|
|
4746
|
+
}
|
|
4747
|
+
function setCurrentDatabase(db, id) {
|
|
4748
|
+
db.query("INSERT INTO meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(KEY, id);
|
|
4749
|
+
}
|
|
4750
|
+
function clearCurrentDatabase(db) {
|
|
4751
|
+
db.query("DELETE FROM meta WHERE key = ?").run(KEY);
|
|
4752
|
+
}
|
|
4753
|
+
|
|
4754
|
+
// src/cli/refs.ts
|
|
4755
|
+
function resolveDb(db, ref) {
|
|
4756
|
+
if (ref != null)
|
|
4757
|
+
return resolveRef(db, ref, { kind: "db" });
|
|
4758
|
+
const cur = getCurrentDatabase(db);
|
|
4759
|
+
if (!cur)
|
|
4760
|
+
fail("no current database; pass <db> or run `mh use <db>`");
|
|
4761
|
+
return cur.id;
|
|
4762
|
+
}
|
|
4763
|
+
|
|
4764
|
+
// src/cli/commands/db.ts
|
|
4765
|
+
var create = defineCommand({
|
|
4766
|
+
meta: { name: "create", description: "Create a database" },
|
|
4767
|
+
args: {
|
|
4768
|
+
name: { type: "positional", required: true, description: "Database name" },
|
|
4769
|
+
icon: { type: "string", description: "Optional icon" }
|
|
4770
|
+
},
|
|
4771
|
+
run: guard((args) => {
|
|
4772
|
+
const row = createDatabase(openMetahub(), { name: args.name, icon: args.icon });
|
|
4773
|
+
print(row, () => `${row.id} ${row.name}`);
|
|
4774
|
+
})
|
|
4775
|
+
});
|
|
4776
|
+
var list = defineCommand({
|
|
4777
|
+
meta: { name: "list", description: "List databases" },
|
|
4778
|
+
run: guard(() => {
|
|
4779
|
+
const rows = listDatabases(openMetahub());
|
|
4780
|
+
print(rows, () => table(rows));
|
|
4781
|
+
})
|
|
4782
|
+
});
|
|
4783
|
+
var get = defineCommand({
|
|
4784
|
+
meta: { name: "get", description: "Show one database" },
|
|
4785
|
+
args: { id: { type: "positional", required: true, description: "Database ref (id/prefix/name)" } },
|
|
4786
|
+
run: guard((args) => {
|
|
4787
|
+
const db = openMetahub();
|
|
4788
|
+
print(getDatabase(db, resolveRef(db, args.id, { kind: "db" })));
|
|
4789
|
+
})
|
|
4790
|
+
});
|
|
4791
|
+
var duplicate = defineCommand({
|
|
4792
|
+
meta: { name: "duplicate", description: "Copy a database whole \u2014 schema and all records" },
|
|
4793
|
+
args: {
|
|
4794
|
+
id: { type: "positional", required: true, description: "Database ref (id/prefix/name)" },
|
|
4795
|
+
name: { type: "string", description: "Name for the copy (defaults to the source name)" }
|
|
4796
|
+
},
|
|
4797
|
+
run: guard((args) => {
|
|
4798
|
+
const db = openMetahub();
|
|
4799
|
+
const row = duplicateDatabase(db, resolveRef(db, args.id, { kind: "db" }), { name: args.name });
|
|
4800
|
+
print(row, () => `${row.id} ${row.name}`);
|
|
4801
|
+
})
|
|
4802
|
+
});
|
|
4803
|
+
function fmtCell(v2) {
|
|
4804
|
+
const s2 = v2 === undefined || v2 === null || v2 === "" ? "(empty)" : typeof v2 === "string" ? v2.replaceAll(`
|
|
4805
|
+
`, "\u23CE") : JSON.stringify(v2);
|
|
4806
|
+
return s2.length > 40 ? s2.slice(0, 39) + "\u2026" : s2;
|
|
4807
|
+
}
|
|
4808
|
+
function activitySummary(e2, names) {
|
|
4809
|
+
const parts = [];
|
|
4810
|
+
if (e2.kind !== "user")
|
|
4811
|
+
parts.push(`[${e2.kind}]`);
|
|
4812
|
+
if (e2.deleted)
|
|
4813
|
+
parts.push("deleted");
|
|
4814
|
+
if (e2.created) {
|
|
4815
|
+
const vals = e2.diffs.filter((d2) => ("after" in d2)).map((d2) => `${names.get(d2.prop) ?? d2.prop}: ${fmtCell(d2.after)}`);
|
|
4816
|
+
parts.push("created" + (vals.length ? ` (${vals.join(", ")})` : ""));
|
|
4817
|
+
} else if (e2.diffs.length) {
|
|
4818
|
+
parts.push(e2.diffs.map((d2) => `${names.get(d2.prop) ?? d2.prop}: ${fmtCell(d2.before)} \u2192 ${fmtCell(d2.after)}`).join("; "));
|
|
4819
|
+
}
|
|
4820
|
+
if (e2.moved && !parts.length)
|
|
4821
|
+
parts.push("moved");
|
|
4822
|
+
return parts.join("; ") || "metadata";
|
|
4823
|
+
}
|
|
4824
|
+
var activity = defineCommand({
|
|
4825
|
+
meta: {
|
|
4826
|
+
name: "activity",
|
|
4827
|
+
description: "Recent changes across ALL records of a database, newest first"
|
|
4828
|
+
},
|
|
4829
|
+
args: {
|
|
4830
|
+
id: { type: "positional", required: false, description: "Database ref (default: current)" },
|
|
4831
|
+
limit: { type: "string", description: "Max entries (default 100)" }
|
|
4832
|
+
},
|
|
4833
|
+
run: guard((args) => {
|
|
4834
|
+
const db = openMetahub();
|
|
4835
|
+
const databaseId = resolveDb(db, args.id);
|
|
4836
|
+
const rows = listDatabaseActivity(db, databaseId, {
|
|
4837
|
+
limit: args.limit != null ? Number(args.limit) : undefined
|
|
4838
|
+
});
|
|
4839
|
+
const names = new Map(listProperties(db, databaseId).map((p) => [p.id, p.name]));
|
|
4840
|
+
print(rows, () => table(rows.map((e2) => ({
|
|
4841
|
+
version: e2.version,
|
|
4842
|
+
at: e2.at,
|
|
4843
|
+
node: e2.node_id,
|
|
4844
|
+
record: e2.record_title || e2.record_id,
|
|
4845
|
+
summary: activitySummary(e2, names)
|
|
4846
|
+
}))));
|
|
4847
|
+
})
|
|
4848
|
+
});
|
|
4849
|
+
var del = defineCommand({
|
|
4850
|
+
meta: { name: "delete", description: "Delete a database" },
|
|
4851
|
+
args: { id: { type: "positional", required: true, description: "Database ref (id/prefix/name)" } },
|
|
4852
|
+
run: guard((args) => {
|
|
4853
|
+
const db = openMetahub();
|
|
4854
|
+
const id = resolveRef(db, args.id, { kind: "db" });
|
|
4855
|
+
deleteDatabase(db, id);
|
|
4856
|
+
print({ ok: true, deleted: id });
|
|
4857
|
+
})
|
|
4858
|
+
});
|
|
4859
|
+
var db_default = defineCommand({
|
|
4860
|
+
meta: { name: "db", description: "Manage databases (notion-like tables)" },
|
|
4861
|
+
subCommands: { create, list, get, duplicate, activity, delete: del }
|
|
4862
|
+
});
|
|
4863
|
+
|
|
4864
|
+
// src/cli/commands/use.ts
|
|
4865
|
+
var use_default = defineCommand({
|
|
4866
|
+
meta: {
|
|
4867
|
+
name: "use",
|
|
4868
|
+
description: "Set/show the current database (record, doc, prop default to it)"
|
|
4869
|
+
},
|
|
4870
|
+
args: {
|
|
4871
|
+
database: {
|
|
4872
|
+
type: "positional",
|
|
4873
|
+
required: false,
|
|
4874
|
+
description: "Database ref (id/prefix/name); omit to show current"
|
|
4875
|
+
},
|
|
4876
|
+
clear: { type: "boolean", description: "Clear the current database" }
|
|
4877
|
+
},
|
|
4878
|
+
run: guard((args) => {
|
|
4879
|
+
const db = openMetahub();
|
|
4880
|
+
if (args.clear) {
|
|
4881
|
+
clearCurrentDatabase(db);
|
|
4882
|
+
print({ ok: true, current: null }, () => "\u2713 current database cleared");
|
|
4883
|
+
return;
|
|
4884
|
+
}
|
|
4885
|
+
if (args.database == null) {
|
|
4886
|
+
const cur = getCurrentDatabase(db);
|
|
4887
|
+
print({ current: cur }, () => cur ? `${cur.id} ${cur.name}` : "(no current database)");
|
|
4888
|
+
return;
|
|
4889
|
+
}
|
|
4890
|
+
const id = resolveRef(db, args.database, { kind: "db" });
|
|
4891
|
+
setCurrentDatabase(db, id);
|
|
4892
|
+
const row = getDatabase(db, id);
|
|
4893
|
+
print({ ok: true, current: row }, () => `\u2713 current database \u2192 ${row.name} (${row.id})`);
|
|
4894
|
+
})
|
|
4895
|
+
});
|
|
4896
|
+
|
|
4897
|
+
// src/cli/commands/get.ts
|
|
4898
|
+
var get_default = defineCommand({
|
|
4899
|
+
meta: { name: "get", description: "Look up any entity by ref (id/prefix/name), type auto-detected" },
|
|
4900
|
+
args: { ref: { type: "positional", required: true, description: "Ref (id/prefix/name)" } },
|
|
4901
|
+
run: guard((args) => {
|
|
4902
|
+
const db = openMetahub();
|
|
4903
|
+
const { kind, id } = resolveEntity(db, args.ref);
|
|
4904
|
+
switch (kind) {
|
|
4905
|
+
case "db": {
|
|
4906
|
+
const row = getDatabase(db, id);
|
|
4907
|
+
return print(row, () => `${row.id} ${row.name}`);
|
|
4908
|
+
}
|
|
4909
|
+
case "prop": {
|
|
4910
|
+
const row = getProperty(db, id);
|
|
4911
|
+
return print(row, () => table([{ id: row.id, name: row.name, type: row.type }]));
|
|
4912
|
+
}
|
|
4913
|
+
case "rec":
|
|
4914
|
+
return print(getRecord(db, id));
|
|
4915
|
+
case "doc": {
|
|
4916
|
+
const row = getDocument(db, id);
|
|
4917
|
+
return print(row, () => row.body ?? "");
|
|
4918
|
+
}
|
|
4919
|
+
}
|
|
4920
|
+
})
|
|
4921
|
+
});
|
|
4922
|
+
|
|
4923
|
+
// src/cli/input.ts
|
|
4924
|
+
async function resolveValue2(raw) {
|
|
4925
|
+
if (raw == null)
|
|
4926
|
+
return;
|
|
4927
|
+
if (raw === "@-")
|
|
4928
|
+
return await Bun.stdin.text();
|
|
4929
|
+
if (raw.startsWith("@"))
|
|
4930
|
+
return await Bun.file(raw.slice(1)).text();
|
|
4931
|
+
return raw;
|
|
4932
|
+
}
|
|
4933
|
+
async function resolveJson(raw) {
|
|
4934
|
+
const text = await resolveValue2(raw);
|
|
4935
|
+
if (text == null)
|
|
4936
|
+
return;
|
|
4937
|
+
return JSON.parse(text);
|
|
4938
|
+
}
|
|
4939
|
+
|
|
4940
|
+
// src/cli/commands/prop.ts
|
|
4941
|
+
init_errors();
|
|
4942
|
+
init_ids();
|
|
4943
|
+
function warnIfDuplicateName(db, databaseId, name) {
|
|
4944
|
+
const dups = listProperties(db, databaseId).filter((p) => p.name.toLowerCase() === name.toLowerCase());
|
|
4945
|
+
if (dups.length > 1)
|
|
4946
|
+
warn(`property name "${name}" now matches ${dups.length} properties in this database; ` + `name-keyed record reads/writes will fail as ambiguous \u2014 use property ids (${dups.map((p) => p.id).join(", ")})`);
|
|
4947
|
+
}
|
|
4948
|
+
function propIdMaybeDeleted(db, ref) {
|
|
4949
|
+
try {
|
|
4950
|
+
return resolveRef(db, ref, { kind: "prop" });
|
|
4951
|
+
} catch (e2) {
|
|
4952
|
+
if (errorCode(e2) === "not_found" && idKind(ref) === "prop")
|
|
4953
|
+
return ref;
|
|
4954
|
+
throw e2;
|
|
4955
|
+
}
|
|
4956
|
+
}
|
|
4957
|
+
function buildConfig(args, base) {
|
|
4958
|
+
const config = { ...base };
|
|
4959
|
+
if (typeof args.options === "string")
|
|
4960
|
+
config.options = args.options.split(",").map((s2) => s2.trim()).filter(Boolean);
|
|
4961
|
+
if (typeof args.target === "string")
|
|
4962
|
+
config.database = args.target;
|
|
4963
|
+
return Object.keys(config).length ? config : undefined;
|
|
4964
|
+
}
|
|
4965
|
+
var add = defineCommand({
|
|
4966
|
+
meta: { name: "add", description: "Add a property (column) to a database" },
|
|
4967
|
+
args: {
|
|
4968
|
+
name: { type: "positional", required: true, description: "Property name" },
|
|
4969
|
+
db: { type: "string", description: "Database ref (default: current)" },
|
|
4970
|
+
type: { type: "string", required: true, description: "text|number|checkbox|select|multi_select|date|relation|url" },
|
|
4971
|
+
options: { type: "string", description: "Comma list for select/multi_select" },
|
|
4972
|
+
target: { type: "string", description: "Target database ref for relation" },
|
|
4973
|
+
config: { type: "string", description: "Raw config JSON (@file/@- ok)" },
|
|
4974
|
+
position: { type: "string", description: "Sort position" }
|
|
4975
|
+
},
|
|
4976
|
+
run: guard(async (args) => {
|
|
4977
|
+
const db = openMetahub();
|
|
4978
|
+
const databaseId = resolveDb(db, args.db);
|
|
4979
|
+
const fromJson = await resolveJson(args.config);
|
|
4980
|
+
const target = args.target != null ? resolveRef(db, args.target, { kind: "db" }) : undefined;
|
|
4981
|
+
const config = buildConfig({ ...args, target }, fromJson);
|
|
4982
|
+
const row = addProperty(db, databaseId, {
|
|
4983
|
+
name: args.name,
|
|
4984
|
+
type: args.type,
|
|
4985
|
+
config,
|
|
4986
|
+
position: args.position != null ? Number(args.position) : undefined
|
|
4987
|
+
});
|
|
4988
|
+
warnIfDuplicateName(db, databaseId, row.name);
|
|
4989
|
+
print(row, () => `${row.id} ${row.name} ${row.type}`);
|
|
4990
|
+
})
|
|
4991
|
+
});
|
|
4992
|
+
var list2 = defineCommand({
|
|
4993
|
+
meta: { name: "list", description: "List properties of a database" },
|
|
4994
|
+
args: { database: { type: "positional", required: false, description: "Database ref (default: current)" } },
|
|
4995
|
+
run: guard((args) => {
|
|
4996
|
+
const db = openMetahub();
|
|
4997
|
+
const rows = listProperties(db, resolveDb(db, args.database));
|
|
4998
|
+
print(rows, () => table(rows.map((r3) => ({ id: r3.id, name: r3.name, type: r3.type, position: r3.position }))));
|
|
4999
|
+
})
|
|
5000
|
+
});
|
|
5001
|
+
var update = defineCommand({
|
|
5002
|
+
meta: { name: "update", description: "Update a property" },
|
|
5003
|
+
args: {
|
|
5004
|
+
id: { type: "positional", required: true, description: "Property ref (id/prefix/name)" },
|
|
5005
|
+
name: { type: "string" },
|
|
5006
|
+
options: { type: "string", description: "Comma list for select/multi_select" },
|
|
5007
|
+
target: { type: "string", description: "Target database ref for relation" },
|
|
5008
|
+
config: { type: "string", description: "Raw config JSON (@file/@- ok)" },
|
|
5009
|
+
position: { type: "string" }
|
|
5010
|
+
},
|
|
5011
|
+
run: guard(async (args) => {
|
|
5012
|
+
const db = openMetahub();
|
|
5013
|
+
const fromJson = await resolveJson(args.config);
|
|
5014
|
+
const target = args.target != null ? resolveRef(db, args.target, { kind: "db" }) : undefined;
|
|
5015
|
+
const config = buildConfig({ ...args, target }, fromJson);
|
|
5016
|
+
const row = updateProperty(db, resolveRef(db, args.id, { kind: "prop" }), {
|
|
5017
|
+
name: args.name,
|
|
5018
|
+
config,
|
|
5019
|
+
position: args.position != null ? Number(args.position) : undefined
|
|
5020
|
+
});
|
|
5021
|
+
if (args.name != null)
|
|
5022
|
+
warnIfDuplicateName(db, row.database_id, row.name);
|
|
5023
|
+
print(row);
|
|
5024
|
+
})
|
|
5025
|
+
});
|
|
5026
|
+
var remove = defineCommand({
|
|
5027
|
+
meta: { name: "remove", description: "Remove a property" },
|
|
5028
|
+
args: { id: { type: "positional", required: true, description: "Property ref (id/prefix/name)" } },
|
|
5029
|
+
run: guard((args) => {
|
|
5030
|
+
const db = openMetahub();
|
|
5031
|
+
const id = resolveRef(db, args.id, { kind: "prop" });
|
|
5032
|
+
removeProperty(db, id);
|
|
5033
|
+
print({ ok: true, removed: id });
|
|
5034
|
+
})
|
|
5035
|
+
});
|
|
5036
|
+
function propRevisionSummary(r3) {
|
|
5037
|
+
const parts = [];
|
|
5038
|
+
if (r3.kind !== "user")
|
|
5039
|
+
parts.push(`[${r3.kind}]`);
|
|
5040
|
+
if (r3.created)
|
|
5041
|
+
parts.push("created");
|
|
5042
|
+
if (r3.deleted)
|
|
5043
|
+
parts.push("removed");
|
|
5044
|
+
if (r3.fields.length)
|
|
5045
|
+
parts.push(r3.fields.join(", "));
|
|
5046
|
+
if (r3.cells_cleared)
|
|
5047
|
+
parts.push(`-${r3.cells_cleared} cell(s)`);
|
|
5048
|
+
return parts.join("; ") || "metadata";
|
|
5049
|
+
}
|
|
5050
|
+
var history = defineCommand({
|
|
5051
|
+
meta: { name: "history", description: "List a property's definition revisions (newest first)" },
|
|
5052
|
+
args: { id: { type: "positional", required: true, description: "Property ref (id/prefix/name)" } },
|
|
5053
|
+
run: guard((args) => {
|
|
5054
|
+
const db = openMetahub();
|
|
5055
|
+
const rows = listPropertyRevisions(db, propIdMaybeDeleted(db, args.id));
|
|
5056
|
+
print(rows, () => table(rows.map((r3) => ({
|
|
5057
|
+
version: r3.version,
|
|
5058
|
+
at: r3.at,
|
|
5059
|
+
node: r3.node_id,
|
|
5060
|
+
summary: propRevisionSummary(r3)
|
|
5061
|
+
}))));
|
|
5062
|
+
})
|
|
5063
|
+
});
|
|
5064
|
+
var revert = defineCommand({
|
|
5065
|
+
meta: {
|
|
5066
|
+
name: "revert",
|
|
5067
|
+
description: "Restore a property's definition AND the cells its type-change/removal cleared; user edits made since are kept"
|
|
5068
|
+
},
|
|
5069
|
+
args: {
|
|
5070
|
+
id: { type: "positional", required: true, description: "Property ref (id/prefix/name)" },
|
|
5071
|
+
to: { type: "string", required: true, description: "Version token from `prop history`" }
|
|
5072
|
+
},
|
|
5073
|
+
run: guard((args) => {
|
|
5074
|
+
const db = openMetahub();
|
|
5075
|
+
const r3 = revertProperty(db, propIdMaybeDeleted(db, args.id), args.to);
|
|
5076
|
+
print(r3, () => r3.changed ? `reverted ${r3.id} to ${r3.restored}` + (r3.fields.length ? ` (${r3.fields.join(", ")})` : "") + (r3.restored_cells ? ` +${r3.restored_cells} cell(s)` : "") + (r3.skipped_cells ? ` (${r3.skipped_cells} kept user edits)` : "") + (r3.undeleted ? " [undeleted]" : "") : `no change: ${r3.id} already matches ${r3.restored}`);
|
|
5077
|
+
})
|
|
5078
|
+
});
|
|
5079
|
+
var prop_default = defineCommand({
|
|
5080
|
+
meta: { name: "prop", description: "Manage database properties (columns)" },
|
|
5081
|
+
subCommands: { add, list: list2, update, history, revert, remove }
|
|
5082
|
+
});
|
|
5083
|
+
|
|
4608
5084
|
// src/cli/commands/record.ts
|
|
4609
5085
|
init_errors();
|
|
4610
5086
|
init_ids();
|
|
@@ -4687,6 +5163,8 @@ var update2 = defineCommand({
|
|
|
4687
5163
|
});
|
|
4688
5164
|
function recordRevisionSummary(r3, names) {
|
|
4689
5165
|
const parts = [];
|
|
5166
|
+
if (r3.kind !== "user")
|
|
5167
|
+
parts.push(`[${r3.kind}]`);
|
|
4690
5168
|
if (r3.created)
|
|
4691
5169
|
parts.push("created");
|
|
4692
5170
|
if (r3.deleted)
|
|
@@ -4697,7 +5175,7 @@ function recordRevisionSummary(r3, names) {
|
|
|
4697
5175
|
parts.push("moved");
|
|
4698
5176
|
return parts.join("; ") || "metadata";
|
|
4699
5177
|
}
|
|
4700
|
-
var
|
|
5178
|
+
var history2 = defineCommand({
|
|
4701
5179
|
meta: { name: "history", description: "List a record's revisions (newest first)" },
|
|
4702
5180
|
args: {
|
|
4703
5181
|
id: { type: "positional", required: true, description: "Record ref (id/prefix)" },
|
|
@@ -4729,7 +5207,7 @@ var history = defineCommand({
|
|
|
4729
5207
|
}))));
|
|
4730
5208
|
})
|
|
4731
5209
|
});
|
|
4732
|
-
var
|
|
5210
|
+
var revert2 = defineCommand({
|
|
4733
5211
|
meta: {
|
|
4734
5212
|
name: "revert",
|
|
4735
5213
|
description: "Restore a record's cells to a past version (recorded as a new revision)"
|
|
@@ -4758,13 +5236,24 @@ var del2 = defineCommand({
|
|
|
4758
5236
|
});
|
|
4759
5237
|
var record_default = defineCommand({
|
|
4760
5238
|
meta: { name: "record", description: "Manage records (rows)" },
|
|
4761
|
-
subCommands: { create: create2, list: list3, get: get2, update: update2, history, revert, delete: del2 }
|
|
5239
|
+
subCommands: { create: create2, list: list3, get: get2, update: update2, history: history2, revert: revert2, delete: del2 }
|
|
4762
5240
|
});
|
|
4763
5241
|
|
|
4764
5242
|
// src/cli/commands/doc.ts
|
|
5243
|
+
init_errors();
|
|
5244
|
+
init_ids();
|
|
4765
5245
|
function docId(db, ref) {
|
|
4766
5246
|
return resolveRef(db, ref, { kind: "doc" });
|
|
4767
5247
|
}
|
|
5248
|
+
function docIdMaybeDeleted(db, ref) {
|
|
5249
|
+
try {
|
|
5250
|
+
return docId(db, ref);
|
|
5251
|
+
} catch (e2) {
|
|
5252
|
+
if (errorCode(e2) === "not_found" && idKind(ref) === "doc")
|
|
5253
|
+
return ref;
|
|
5254
|
+
throw e2;
|
|
5255
|
+
}
|
|
5256
|
+
}
|
|
4768
5257
|
function docTree(rows) {
|
|
4769
5258
|
if (rows.length === 0)
|
|
4770
5259
|
return "(empty)";
|
|
@@ -4831,13 +5320,12 @@ var get3 = defineCommand({
|
|
|
4831
5320
|
},
|
|
4832
5321
|
run: guard((args) => {
|
|
4833
5322
|
const db = openMetahub();
|
|
4834
|
-
const id = docId(db, args.id);
|
|
4835
5323
|
if (args.at !== undefined) {
|
|
4836
|
-
const past = documentAtVersion(db, id, args.at);
|
|
5324
|
+
const past = documentAtVersion(db, docIdMaybeDeleted(db, args.id), args.at);
|
|
4837
5325
|
print(past, () => past.body);
|
|
4838
5326
|
return;
|
|
4839
5327
|
}
|
|
4840
|
-
const row = getDocument(db, id);
|
|
5328
|
+
const row = getDocument(db, docId(db, args.id));
|
|
4841
5329
|
print(row, () => row.body ?? "");
|
|
4842
5330
|
})
|
|
4843
5331
|
});
|
|
@@ -4935,6 +5423,8 @@ var prepend = defineCommand({
|
|
|
4935
5423
|
});
|
|
4936
5424
|
function docRevisionSummary(r3) {
|
|
4937
5425
|
const parts = [];
|
|
5426
|
+
if (r3.kind !== "user")
|
|
5427
|
+
parts.push(`[${r3.kind}]`);
|
|
4938
5428
|
if (r3.created)
|
|
4939
5429
|
parts.push("created");
|
|
4940
5430
|
if (r3.deleted)
|
|
@@ -4947,12 +5437,12 @@ function docRevisionSummary(r3) {
|
|
|
4947
5437
|
parts.push(`-${r3.blocks_deleted} block(s)`);
|
|
4948
5438
|
return parts.join(", ") || "metadata";
|
|
4949
5439
|
}
|
|
4950
|
-
var
|
|
5440
|
+
var history3 = defineCommand({
|
|
4951
5441
|
meta: { name: "history", description: "List a document's revisions (newest first)" },
|
|
4952
5442
|
args: { id: { type: "positional", required: true, description: "Document ref (id/prefix/title)" } },
|
|
4953
5443
|
run: guard((args) => {
|
|
4954
5444
|
const db = openMetahub();
|
|
4955
|
-
const rows = listDocumentRevisions(db,
|
|
5445
|
+
const rows = listDocumentRevisions(db, docIdMaybeDeleted(db, args.id));
|
|
4956
5446
|
print(rows, () => table(rows.map((r3) => ({
|
|
4957
5447
|
version: r3.version,
|
|
4958
5448
|
at: r3.at,
|
|
@@ -4961,7 +5451,7 @@ var history2 = defineCommand({
|
|
|
4961
5451
|
}))));
|
|
4962
5452
|
})
|
|
4963
5453
|
});
|
|
4964
|
-
var
|
|
5454
|
+
var revert3 = defineCommand({
|
|
4965
5455
|
meta: {
|
|
4966
5456
|
name: "revert",
|
|
4967
5457
|
description: "Restore title/body to a past version (recorded as a new revision)"
|
|
@@ -4973,8 +5463,22 @@ var revert2 = defineCommand({
|
|
|
4973
5463
|
},
|
|
4974
5464
|
run: guard((args) => {
|
|
4975
5465
|
const db = openMetahub();
|
|
4976
|
-
const r3 = revertDocument(db,
|
|
4977
|
-
|
|
5466
|
+
const r3 = revertDocument(db, docIdMaybeDeleted(db, args.id), args.to, {
|
|
5467
|
+
ifMatch: args["if-match"]
|
|
5468
|
+
});
|
|
5469
|
+
print(r3, () => r3.changed ? `reverted ${r3.id} to ${r3.restored}` + (r3.undeleted ? " [undeleted]" : "") : `no change: ${r3.id} already matches ${r3.restored}`);
|
|
5470
|
+
})
|
|
5471
|
+
});
|
|
5472
|
+
var duplicate2 = defineCommand({
|
|
5473
|
+
meta: { name: "duplicate", description: "Copy a document (title + all blocks) next to the source" },
|
|
5474
|
+
args: {
|
|
5475
|
+
id: { type: "positional", required: true, description: "Document ref (id/prefix/title)" },
|
|
5476
|
+
title: { type: "string", description: "Title for the copy (defaults to the source title)" }
|
|
5477
|
+
},
|
|
5478
|
+
run: guard((args) => {
|
|
5479
|
+
const db = openMetahub();
|
|
5480
|
+
const row = duplicateDocument(db, docId(db, args.id), { title: args.title });
|
|
5481
|
+
print({ id: row.id, title: row.title }, () => `${row.id} ${row.title}`);
|
|
4978
5482
|
})
|
|
4979
5483
|
});
|
|
4980
5484
|
var del3 = defineCommand({
|
|
@@ -4989,7 +5493,7 @@ var del3 = defineCommand({
|
|
|
4989
5493
|
});
|
|
4990
5494
|
var doc_default = defineCommand({
|
|
4991
5495
|
meta: { name: "doc", description: "Manage markdown documents" },
|
|
4992
|
-
subCommands: { create: create3, list: list4, get: get3, read, update: update3, edit, append, prepend, history:
|
|
5496
|
+
subCommands: { create: create3, list: list4, get: get3, read, update: update3, edit, append, prepend, history: history3, revert: revert3, duplicate: duplicate2, delete: del3 }
|
|
4993
5497
|
});
|
|
4994
5498
|
|
|
4995
5499
|
// src/cli/editor.ts
|
|
@@ -19896,12 +20400,15 @@ var ChangeSchema = exports_external.object({
|
|
|
19896
20400
|
dataset: exports_external.string().describe("CRDT dataset, e.g. databases / records / documents"),
|
|
19897
20401
|
row_id: exports_external.string().describe("Row identifier within the dataset"),
|
|
19898
20402
|
col: exports_external.string().describe("Column / property identifier"),
|
|
19899
|
-
value: exports_external.string().nullable().describe("JSON-encoded field value, or null")
|
|
20403
|
+
value: exports_external.string().nullable().describe("JSON-encoded field value, or null"),
|
|
20404
|
+
txn: exports_external.string().nullable().optional().describe("Change-group id (one logical mutation)")
|
|
19900
20405
|
});
|
|
19901
20406
|
var SyncRequestSchema = exports_external.object({
|
|
19902
20407
|
node_id: exports_external.string().describe("Calling client's node id"),
|
|
19903
20408
|
since: exports_external.number().describe("Server cursor the client last pulled"),
|
|
19904
|
-
changes: exports_external.array(ChangeSchema).describe("Client's changes to push")
|
|
20409
|
+
changes: exports_external.array(ChangeSchema).describe("Client's changes to push"),
|
|
20410
|
+
limit: exports_external.number().optional().describe("Max changes to return (pagination for hydration)"),
|
|
20411
|
+
exclude_datasets: exports_external.array(exports_external.string()).optional().describe("Datasets to omit from the pull (partial replica)")
|
|
19905
20412
|
});
|
|
19906
20413
|
var SyncResponseSchema = exports_external.object({
|
|
19907
20414
|
node_id: exports_external.string().describe("Server's node id"),
|
|
@@ -19936,10 +20443,11 @@ function getPeer(db, url2) {
|
|
|
19936
20443
|
function setPeer(db, url2, c3) {
|
|
19937
20444
|
db.query("INSERT INTO peers (url, pull_cursor, push_cursor) VALUES (?, ?, ?) ON CONFLICT(url) DO UPDATE SET pull_cursor = excluded.pull_cursor, push_cursor = excluded.push_cursor").run(url2, c3.pull_cursor, c3.push_cursor);
|
|
19938
20445
|
}
|
|
19939
|
-
async function syncWithPeer(db, url2,
|
|
20446
|
+
async function syncWithPeer(db, url2, opts = {}) {
|
|
20447
|
+
const o3 = typeof opts === "string" ? { token: opts } : opts;
|
|
19940
20448
|
const node = getNodeId(db);
|
|
19941
20449
|
const peer = getPeer(db, url2);
|
|
19942
|
-
const bearer = token ?? peer.token ?? undefined;
|
|
20450
|
+
const bearer = o3.token ?? peer.token ?? undefined;
|
|
19943
20451
|
const toPush = changesAfterSeq(db, peer.push_cursor);
|
|
19944
20452
|
const headers = { "content-type": "application/json" };
|
|
19945
20453
|
if (bearer)
|
|
@@ -19947,7 +20455,13 @@ async function syncWithPeer(db, url2, token) {
|
|
|
19947
20455
|
const res = await fetch(new URL(SYNC_PATH, url2), {
|
|
19948
20456
|
method: "POST",
|
|
19949
20457
|
headers,
|
|
19950
|
-
body: JSON.stringify({
|
|
20458
|
+
body: JSON.stringify({
|
|
20459
|
+
node_id: node,
|
|
20460
|
+
since: peer.pull_cursor,
|
|
20461
|
+
changes: toPush.changes,
|
|
20462
|
+
...o3.pullLimit != null ? { limit: o3.pullLimit } : {},
|
|
20463
|
+
...o3.excludeDatasets?.length ? { exclude_datasets: o3.excludeDatasets } : {}
|
|
20464
|
+
})
|
|
19951
20465
|
});
|
|
19952
20466
|
if (!res.ok)
|
|
19953
20467
|
throw new MhError(res.status === 401 || res.status === 403 ? "auth" : "network", `sync failed: ${res.status} ${await res.text()}`);
|
|
@@ -20455,7 +20969,7 @@ function repairPass(db) {
|
|
|
20455
20969
|
const applied = Object.values(fixed).reduce((a2, b2) => a2 + b2, 0);
|
|
20456
20970
|
return { applied, fixed };
|
|
20457
20971
|
}
|
|
20458
|
-
|
|
20972
|
+
var repairHub = grouped(function repairHub2(db) {
|
|
20459
20973
|
const fixed = {};
|
|
20460
20974
|
let applied = 0;
|
|
20461
20975
|
for (let i2 = 0;i2 < 20; i2++) {
|
|
@@ -20467,7 +20981,7 @@ function repairHub(db) {
|
|
|
20467
20981
|
fixed[cat] = (fixed[cat] ?? 0) + n2;
|
|
20468
20982
|
}
|
|
20469
20983
|
return { applied, fixed, remaining: validateHub(db) };
|
|
20470
|
-
}
|
|
20984
|
+
}, "repair");
|
|
20471
20985
|
|
|
20472
20986
|
// src/core/snapshot.ts
|
|
20473
20987
|
init_paths();
|
|
@@ -20643,6 +21157,113 @@ var restore_default = defineCommand({
|
|
|
20643
21157
|
})
|
|
20644
21158
|
});
|
|
20645
21159
|
|
|
21160
|
+
// src/core/compact.ts
|
|
21161
|
+
init_paths();
|
|
21162
|
+
init_errors();
|
|
21163
|
+
import { existsSync as existsSync2, readdirSync as readdirSync2, statSync, unlinkSync } from "fs";
|
|
21164
|
+
import { join as join6 } from "path";
|
|
21165
|
+
function dbBytes(db) {
|
|
21166
|
+
const pc = db.query("PRAGMA page_count").get();
|
|
21167
|
+
const ps = db.query("PRAGMA page_size").get();
|
|
21168
|
+
return pc.page_count * ps.page_size;
|
|
21169
|
+
}
|
|
21170
|
+
var COMPACTABLE = `
|
|
21171
|
+
hlc <= ?1
|
|
21172
|
+
AND rowid <> (SELECT MAX(rowid) FROM crdt_changes)
|
|
21173
|
+
AND EXISTS (
|
|
21174
|
+
SELECT 1 FROM crdt_changes k
|
|
21175
|
+
WHERE k.dataset = crdt_changes.dataset
|
|
21176
|
+
AND k.row_id = crdt_changes.row_id
|
|
21177
|
+
AND k.col = crdt_changes.col
|
|
21178
|
+
AND k.hlc > crdt_changes.hlc
|
|
21179
|
+
AND k.hlc <= ?1
|
|
21180
|
+
)`;
|
|
21181
|
+
function compactOplog(db, opts) {
|
|
21182
|
+
if (!Number.isFinite(opts.keepDays) || opts.keepDays < 0)
|
|
21183
|
+
throw new MhError("invalid_input", "keepDays must be a non-negative number");
|
|
21184
|
+
const now = opts.now ?? Date.now();
|
|
21185
|
+
const cutoff = formatHlc({
|
|
21186
|
+
millis: Math.max(0, now - opts.keepDays * 86400000),
|
|
21187
|
+
counter: 65535,
|
|
21188
|
+
node: "~"
|
|
21189
|
+
});
|
|
21190
|
+
const bytesBefore = dbBytes(db);
|
|
21191
|
+
const dryRun = opts.dryRun === true;
|
|
21192
|
+
let deleted;
|
|
21193
|
+
if (dryRun) {
|
|
21194
|
+
const row = db.query(`SELECT COUNT(*) AS n FROM crdt_changes WHERE ${COMPACTABLE}`).get(cutoff);
|
|
21195
|
+
deleted = row.n;
|
|
21196
|
+
} else {
|
|
21197
|
+
deleted = db.query(`DELETE FROM crdt_changes WHERE ${COMPACTABLE}`).run(cutoff).changes;
|
|
21198
|
+
}
|
|
21199
|
+
const kept = db.query("SELECT COUNT(*) AS n FROM crdt_changes").get().n - (dryRun ? deleted : 0);
|
|
21200
|
+
const blobs = gcBlobs(db, { dryRun });
|
|
21201
|
+
let bytesAfter = bytesBefore;
|
|
21202
|
+
if (!dryRun && opts.vacuum !== false) {
|
|
21203
|
+
try {
|
|
21204
|
+
db.exec("VACUUM");
|
|
21205
|
+
} catch {}
|
|
21206
|
+
bytesAfter = dbBytes(db);
|
|
21207
|
+
}
|
|
21208
|
+
return {
|
|
21209
|
+
cutoff,
|
|
21210
|
+
deleted_changes: deleted,
|
|
21211
|
+
kept_changes: kept,
|
|
21212
|
+
blobs_deleted: blobs.deleted,
|
|
21213
|
+
blob_bytes_freed: blobs.bytes,
|
|
21214
|
+
db_bytes_before: bytesBefore,
|
|
21215
|
+
db_bytes_after: bytesAfter,
|
|
21216
|
+
dry_run: dryRun
|
|
21217
|
+
};
|
|
21218
|
+
}
|
|
21219
|
+
function gcBlobs(db, opts = {}) {
|
|
21220
|
+
const dir = cacheDir();
|
|
21221
|
+
if (!existsSync2(dir))
|
|
21222
|
+
return { deleted: 0, bytes: 0 };
|
|
21223
|
+
const referenced = new Set;
|
|
21224
|
+
const collect = (rows) => {
|
|
21225
|
+
for (const r3 of rows) {
|
|
21226
|
+
if (r3.v == null)
|
|
21227
|
+
continue;
|
|
21228
|
+
try {
|
|
21229
|
+
const parsed = JSON.parse(r3.v);
|
|
21230
|
+
if (typeof parsed === "string")
|
|
21231
|
+
referenced.add(parsed);
|
|
21232
|
+
} catch {
|
|
21233
|
+
referenced.add(r3.v);
|
|
21234
|
+
}
|
|
21235
|
+
}
|
|
21236
|
+
};
|
|
21237
|
+
collect(db.query("SELECT DISTINCT value AS v FROM crdt_changes WHERE dataset = 'site_files' AND col = 'content'").all());
|
|
21238
|
+
collect(db.query("SELECT DISTINCT content AS v FROM site_files").all());
|
|
21239
|
+
let deleted = 0;
|
|
21240
|
+
let bytes = 0;
|
|
21241
|
+
for (const name of readdirSync2(dir)) {
|
|
21242
|
+
if (!/^[0-9a-f]{64}$/.test(name))
|
|
21243
|
+
continue;
|
|
21244
|
+
if (referenced.has(name))
|
|
21245
|
+
continue;
|
|
21246
|
+
const path = join6(dir, name);
|
|
21247
|
+
try {
|
|
21248
|
+
bytes += statSync(path).size;
|
|
21249
|
+
if (!opts.dryRun)
|
|
21250
|
+
unlinkSync(path);
|
|
21251
|
+
deleted++;
|
|
21252
|
+
} catch {}
|
|
21253
|
+
}
|
|
21254
|
+
return { deleted, bytes };
|
|
21255
|
+
}
|
|
21256
|
+
function compactEstimate(db, keepDays, now = Date.now()) {
|
|
21257
|
+
const cutoff = formatHlc({
|
|
21258
|
+
millis: Math.max(0, now - keepDays * 86400000),
|
|
21259
|
+
counter: 65535,
|
|
21260
|
+
node: "~"
|
|
21261
|
+
});
|
|
21262
|
+
const total = db.query("SELECT COUNT(*) AS n FROM crdt_changes").get().n;
|
|
21263
|
+
const compactable = db.query(`SELECT COUNT(*) AS n FROM crdt_changes WHERE ${COMPACTABLE}`).get(cutoff).n;
|
|
21264
|
+
return { total_changes: total, compactable_changes: compactable, db_bytes: dbBytes(db) };
|
|
21265
|
+
}
|
|
21266
|
+
|
|
20646
21267
|
// src/cli/commands/doctor.ts
|
|
20647
21268
|
var doctor_default = defineCommand({
|
|
20648
21269
|
meta: {
|
|
@@ -20650,10 +21271,15 @@ var doctor_default = defineCommand({
|
|
|
20650
21271
|
description: "Scan the hub for logical-integrity issues (read-only; run `mh repair` to fix)"
|
|
20651
21272
|
},
|
|
20652
21273
|
run: () => {
|
|
20653
|
-
const
|
|
20654
|
-
|
|
21274
|
+
const db = openMetahub();
|
|
21275
|
+
const report = validateHub(db);
|
|
21276
|
+
const storage = compactEstimate(db, 90);
|
|
21277
|
+
print({ ...report, storage }, () => {
|
|
21278
|
+
const mb = (storage.db_bytes / 1024 / 1024).toFixed(1);
|
|
21279
|
+
const storageLine = `storage: ${storage.total_changes} oplog change(s), ${mb} MB on disk` + (storage.compactable_changes ? `; ${storage.compactable_changes} prunable with \`mh compact\` (90d window)` : "");
|
|
20655
21280
|
if (report.ok)
|
|
20656
|
-
return
|
|
21281
|
+
return `No integrity issues found.
|
|
21282
|
+
${storageLine}`;
|
|
20657
21283
|
const head = `${report.total} issue(s): ${Object.entries(report.counts).map(([k2, v2]) => `${k2}=${v2}`).join(", ")}`;
|
|
20658
21284
|
const rows = report.issues.map((i2) => ({
|
|
20659
21285
|
category: i2.category,
|
|
@@ -20664,7 +21290,9 @@ var doctor_default = defineCommand({
|
|
|
20664
21290
|
}));
|
|
20665
21291
|
return `${head}
|
|
20666
21292
|
|
|
20667
|
-
${table(rows)}
|
|
21293
|
+
${table(rows)}
|
|
21294
|
+
|
|
21295
|
+
${storageLine}`;
|
|
20668
21296
|
});
|
|
20669
21297
|
}
|
|
20670
21298
|
});
|
|
@@ -20711,6 +21339,48 @@ ${left.length} report-only issue(s) need manual attention:
|
|
|
20711
21339
|
}
|
|
20712
21340
|
});
|
|
20713
21341
|
|
|
21342
|
+
// src/cli/commands/compact.ts
|
|
21343
|
+
function fmtBytes(n2) {
|
|
21344
|
+
if (n2 < 1024)
|
|
21345
|
+
return `${n2} B`;
|
|
21346
|
+
if (n2 < 1024 * 1024)
|
|
21347
|
+
return `${(n2 / 1024).toFixed(1)} KB`;
|
|
21348
|
+
return `${(n2 / 1024 / 1024).toFixed(1)} MB`;
|
|
21349
|
+
}
|
|
21350
|
+
var compact_default = defineCommand({
|
|
21351
|
+
meta: {
|
|
21352
|
+
name: "compact",
|
|
21353
|
+
description: "Prune oplog history older than the retention window and reclaim disk. " + "The current data is untouched; doc/record/prop history older than the window " + "collapses to a single baseline version (revert beyond it is no longer possible). " + "Local-only: peers keep their own history until they compact themselves."
|
|
21354
|
+
},
|
|
21355
|
+
args: {
|
|
21356
|
+
keep: {
|
|
21357
|
+
type: "string",
|
|
21358
|
+
description: "Days of full history to keep (default 90; 0 = head state only)"
|
|
21359
|
+
},
|
|
21360
|
+
"dry-run": { type: "boolean", description: "Report what would be removed, change nothing" },
|
|
21361
|
+
"no-vacuum": { type: "boolean", description: "Skip VACUUM (faster; space is reused, not returned)" }
|
|
21362
|
+
},
|
|
21363
|
+
run: guard((args) => {
|
|
21364
|
+
const db = openMetahub();
|
|
21365
|
+
const r3 = compactOplog(db, {
|
|
21366
|
+
keepDays: args.keep != null ? Number(args.keep) : 90,
|
|
21367
|
+
dryRun: args["dry-run"],
|
|
21368
|
+
vacuum: !args["no-vacuum"]
|
|
21369
|
+
});
|
|
21370
|
+
print(r3, () => {
|
|
21371
|
+
const lines = [
|
|
21372
|
+
`oplog: ${r3.deleted_changes} change(s) removed, ${r3.kept_changes} kept (cutoff ${r3.cutoff})`,
|
|
21373
|
+
`blobs: ${r3.blobs_deleted} file(s), ${fmtBytes(r3.blob_bytes_freed)}`,
|
|
21374
|
+
`db: ${fmtBytes(r3.db_bytes_before)} -> ${fmtBytes(r3.db_bytes_after)}`
|
|
21375
|
+
];
|
|
21376
|
+
if (r3.dry_run)
|
|
21377
|
+
lines.push("(dry run \u2014 nothing deleted)");
|
|
21378
|
+
return lines.join(`
|
|
21379
|
+
`);
|
|
21380
|
+
});
|
|
21381
|
+
})
|
|
21382
|
+
});
|
|
21383
|
+
|
|
20714
21384
|
// src/cli/commands/config.ts
|
|
20715
21385
|
init_node();
|
|
20716
21386
|
|
|
@@ -22817,7 +23487,10 @@ var syncRoutes = [
|
|
|
22817
23487
|
async handler(req, { db, node }) {
|
|
22818
23488
|
const body = await req.json();
|
|
22819
23489
|
ingest(db, body.changes ?? []);
|
|
22820
|
-
const batch = changesAfterSeq(db, body.since ?? 0
|
|
23490
|
+
const batch = changesAfterSeq(db, body.since ?? 0, {
|
|
23491
|
+
limit: body.limit,
|
|
23492
|
+
excludeDatasets: body.exclude_datasets
|
|
23493
|
+
});
|
|
22821
23494
|
return Response.json({ node_id: node, changes: batch.changes, cursor: batch.cursor });
|
|
22822
23495
|
}
|
|
22823
23496
|
},
|
|
@@ -22982,50 +23655,14 @@ function unlockPage() {
|
|
|
22982
23655
|
</script>
|
|
22983
23656
|
</body></html>`;
|
|
22984
23657
|
}
|
|
22985
|
-
var
|
|
22986
|
-
var KEY = "mh_token";
|
|
22987
|
-
function save(t){
|
|
22988
|
-
try { localStorage.setItem(KEY, t); } catch (e) {}
|
|
22989
|
-
document.cookie = KEY + "=" + encodeURIComponent(t) + "; path=/; SameSite=Strict; Max-Age=31536000";
|
|
22990
|
-
}
|
|
22991
|
-
var orig = window.fetch.bind(window);
|
|
22992
|
-
window.fetch = function(input, init){
|
|
22993
|
-
init = init || {};
|
|
22994
|
-
var t = null; try { t = localStorage.getItem(KEY); } catch (e) {}
|
|
22995
|
-
var url;
|
|
22996
|
-
try { url = new URL((typeof input === "string" ? input : input.url), location.href); } catch (e) { url = null; }
|
|
22997
|
-
var same = url && url.origin === location.origin;
|
|
22998
|
-
if (same && t) {
|
|
22999
|
-
var h = new Headers(init.headers || (typeof input !== "string" && input.headers) || {});
|
|
23000
|
-
if (!h.has("authorization")) h.set("authorization", "Bearer " + t);
|
|
23001
|
-
init.headers = h;
|
|
23002
|
-
}
|
|
23003
|
-
return orig(input, init).then(function(res){
|
|
23004
|
-
if (res.status !== 401 || !same || !t || init.__mhRetried) return res;
|
|
23005
|
-
return orig("${RENEW_PATH}", { headers: { authorization: "Bearer " + t } }).then(function(r){
|
|
23006
|
-
if (!r.ok) return res;
|
|
23007
|
-
return r.json().then(function(d){
|
|
23008
|
-
if (!d || !d.token) return res;
|
|
23009
|
-
save(d.token);
|
|
23010
|
-
var h2 = new Headers(init.headers || {});
|
|
23011
|
-
h2.set("authorization", "Bearer " + d.token);
|
|
23012
|
-
init.headers = h2;
|
|
23013
|
-
init.__mhRetried = true;
|
|
23014
|
-
return orig(input, init);
|
|
23015
|
-
});
|
|
23016
|
-
}).catch(function(){ return res; });
|
|
23017
|
-
});
|
|
23018
|
-
};
|
|
23019
|
-
})();</script>`;
|
|
23658
|
+
var RUNTIME_TAG = `<script src="/mh-runtime.js"></script>`;
|
|
23020
23659
|
function injectShim(html) {
|
|
23021
23660
|
const i2 = html.toLowerCase().indexOf("<head>");
|
|
23022
23661
|
if (i2 >= 0)
|
|
23023
|
-
return html.slice(0, i2 + 6) +
|
|
23024
|
-
return
|
|
23662
|
+
return html.slice(0, i2 + 6) + RUNTIME_TAG + html.slice(i2 + 6);
|
|
23663
|
+
return RUNTIME_TAG + html;
|
|
23025
23664
|
}
|
|
23026
|
-
async function withShim(res,
|
|
23027
|
-
if (!authActive(cfg))
|
|
23028
|
-
return res;
|
|
23665
|
+
async function withShim(res, _cfg) {
|
|
23029
23666
|
const ct3 = res.headers.get("content-type") ?? "";
|
|
23030
23667
|
if (!ct3.includes("text/html"))
|
|
23031
23668
|
return res;
|
|
@@ -23078,15 +23715,16 @@ function startServer(opts = {}) {
|
|
|
23078
23715
|
server = Bun.serve({
|
|
23079
23716
|
port,
|
|
23080
23717
|
hostname: host,
|
|
23718
|
+
tls: opts.tls ? { cert: Bun.file(opts.tls.certPath), key: Bun.file(opts.tls.keyPath) } : undefined,
|
|
23081
23719
|
async fetch(req) {
|
|
23082
23720
|
const url2 = new URL(req.url);
|
|
23083
23721
|
if (url2.pathname === SYNC_PATH) {
|
|
23084
23722
|
if (!acceptsSyncToken(req, url2, auth, db))
|
|
23085
23723
|
return unauthorized();
|
|
23086
23724
|
} else {
|
|
23087
|
-
const exempt = url2.pathname === HEALTH_PATH || url2.pathname === RENEW_PATH || url2.pathname === PAIR_PATH;
|
|
23725
|
+
const exempt = url2.pathname === HEALTH_PATH || url2.pathname === RENEW_PATH || url2.pathname === PAIR_PATH || url2.pathname === "/manifest.webmanifest" || url2.pathname === "/sw.js" || url2.pathname.startsWith("/icons/");
|
|
23088
23726
|
if (!exempt && !hasValidToken(req, url2, auth)) {
|
|
23089
|
-
return wantsHtml(req) ? new Response(unlockPage(), { headers: HTML_HEADERS }) : unauthorized();
|
|
23727
|
+
return wantsHtml(req) ? new Response(unlockPage(), { headers: { ...HTML_HEADERS, "x-mh-unlock": "1" } }) : unauthorized();
|
|
23090
23728
|
}
|
|
23091
23729
|
}
|
|
23092
23730
|
if (url2.pathname === RENEW_PATH) {
|
|
@@ -23143,6 +23781,7 @@ function startServer(opts = {}) {
|
|
|
23143
23781
|
token: active?.token ?? null,
|
|
23144
23782
|
exp: active?.exp ?? null,
|
|
23145
23783
|
authMode,
|
|
23784
|
+
tls: Boolean(opts.tls),
|
|
23146
23785
|
autoSync: Boolean(autoSync && syncIntervalMs > 0),
|
|
23147
23786
|
syncIntervalMs,
|
|
23148
23787
|
stop() {
|
|
@@ -23158,8 +23797,8 @@ init_errors();
|
|
|
23158
23797
|
|
|
23159
23798
|
// src/webui/server/assets.ts
|
|
23160
23799
|
import { fileURLToPath } from "url";
|
|
23161
|
-
import { readdirSync as
|
|
23162
|
-
import { join as
|
|
23800
|
+
import { readdirSync as readdirSync3, statSync as statSync2 } from "fs";
|
|
23801
|
+
import { join as join7, dirname } from "path";
|
|
23163
23802
|
|
|
23164
23803
|
// src/webui/styles.css
|
|
23165
23804
|
var styles_default = ` :root {
|
|
@@ -23244,7 +23883,26 @@ var styles_default = ` :root {
|
|
|
23244
23883
|
/* sidebar */
|
|
23245
23884
|
.sidebar { width:268px; flex:none; background:var(--sidebar); border-right:1px solid var(--line);
|
|
23246
23885
|
display:flex; flex-direction:column; position:relative; transition:margin-left .22s cubic-bezier(.4,0,.2,1); }
|
|
23247
|
-
|
|
23886
|
+
/* right:-4px (not -3): the 7px handle then straddles the 1px border line
|
|
23887
|
+
symmetrically (3.5px each side of the border's center), so the centered
|
|
23888
|
+
::before grip line lands exactly on the border instead of 1px left of it. */
|
|
23889
|
+
.sb-resizer { position:absolute; top:0; right:-4px; width:7px; height:100%; cursor:col-resize; z-index:5; }
|
|
23890
|
+
/* Unified resize-handle visual (sidebar + datasheet/doc-table column resizers):
|
|
23891
|
+
the handle stays an invisible hit area; a centered 3px round-capped line
|
|
23892
|
+
fades/expands in on hover and solidifies with a soft halo while dragging.
|
|
23893
|
+
--rz-guide-h (written by startColumnResize) stretches the dragging line to
|
|
23894
|
+
the full table height so the new column edge is visible across all rows. */
|
|
23895
|
+
.sb-resizer::before, .col-resizer::before, .doc-col-resizer::before {
|
|
23896
|
+
content:""; position:absolute; left:50%; top:0; height:100%; width:3px;
|
|
23897
|
+
transform:translateX(-50%) scaleX(.4); border-radius:999px;
|
|
23898
|
+
background:var(--accent); opacity:0; pointer-events:none;
|
|
23899
|
+
transition:opacity .15s cubic-bezier(.4,0,.2,1), transform .15s cubic-bezier(.4,0,.2,1), box-shadow .15s; }
|
|
23900
|
+
.sb-resizer:hover::before, .col-resizer:hover::before, .doc-col-resizer:hover::before {
|
|
23901
|
+
opacity:.6; transform:translateX(-50%) scaleX(1); }
|
|
23902
|
+
.sb-resizer.dragging::before, .col-resizer.dragging::before, .doc-col-resizer.dragging::before {
|
|
23903
|
+
opacity:1; transform:translateX(-50%) scaleX(1);
|
|
23904
|
+
box-shadow:0 0 0 3px color-mix(in srgb, var(--accent) 18%, transparent);
|
|
23905
|
+
height:var(--rz-guide-h, 100%); }
|
|
23248
23906
|
.sb-head { display:flex; align-items:center; gap:8px; padding:14px 14px 10px; }
|
|
23249
23907
|
.brand { display:flex; align-items:center; gap:8px; flex:1; font-weight:700; font-size:15px; letter-spacing:-.01em; }
|
|
23250
23908
|
.brand .mark { width:24px; height:24px; border-radius:7px;
|
|
@@ -23376,18 +24034,19 @@ var styles_default = ` :root {
|
|
|
23376
24034
|
.btn-primary:hover { filter:brightness(1.07); }
|
|
23377
24035
|
.btn-danger { background:var(--danger); color:#fff; } .btn-danger:hover { filter:brightness(1.05); }
|
|
23378
24036
|
|
|
23379
|
-
/* desktop
|
|
23380
|
-
strip
|
|
23381
|
-
|
|
24037
|
+
/* macOS desktop (hiddenInset frameless): drop the sidebar logo and reserve a
|
|
24038
|
+
draggable strip so the inset traffic lights never overlap our controls.
|
|
24039
|
+
Windows/Linux keep the native frame, so the logo stays. */
|
|
24040
|
+
body.desktop-mac .brand { display:none; }
|
|
23382
24041
|
/* logo gone \u2192 keep the collapse button on the right (where .brand's flex used to push it) */
|
|
23383
|
-
body.desktop .sb-head .iconbtn { margin-left:auto; }
|
|
24042
|
+
body.desktop-mac .sb-head .iconbtn { margin-left:auto; }
|
|
23384
24043
|
body.desktop-mac .sb-head { -webkit-app-region:drag; }
|
|
23385
24044
|
body.desktop-mac .sb-head .iconbtn { -webkit-app-region:no-drag; }
|
|
23386
24045
|
body.desktop-mac .topbar { -webkit-app-region:drag; }
|
|
23387
24046
|
body.desktop-mac .topbar .iconbtn, body.desktop-mac .topbar .btn { -webkit-app-region:no-drag; }
|
|
23388
24047
|
/* peek drawer header overlaps the topbar drag region \u2192 clear its buttons so they stay clickable */
|
|
23389
24048
|
body.desktop-mac .peek-head { -webkit-app-region:drag; }
|
|
23390
|
-
body.desktop-mac .peek-head .iconbtn, body.desktop-mac .peek-head .btn { -webkit-app-region:no-drag; }
|
|
24049
|
+
body.desktop-mac .peek-head .iconbtn, body.desktop-mac .peek-head .btn, body.desktop-mac .peek-head .hist-toggle { -webkit-app-region:no-drag; }
|
|
23391
24050
|
/* sidebar collapsed \u2192 traffic lights sit over the main header; clear the hamburger */
|
|
23392
24051
|
body.desktop-mac .sidebar.collapsed ~ .main .topbar { padding-left:80px; }
|
|
23393
24052
|
|
|
@@ -23552,8 +24211,7 @@ var styles_default = ` :root {
|
|
|
23552
24211
|
color:var(--muted); border-radius:4px; opacity:0; transition:opacity .12s,background .12s; }
|
|
23553
24212
|
.doc-th:hover .doc-col-menu { opacity:1; }
|
|
23554
24213
|
.doc-col-menu:hover { background:var(--hover-2); color:var(--fg); }
|
|
23555
|
-
.doc-col-resizer { position:absolute; top:0; right:-
|
|
23556
|
-
.doc-col-resizer:hover, .doc-col-resizer.dragging { background:var(--accent); opacity:.35; }
|
|
24214
|
+
.doc-col-resizer { position:absolute; top:0; right:-4px; width:7px; height:100%; cursor:col-resize; z-index:3; touch-action:none; }
|
|
23557
24215
|
/* per-row delete: small handle floating at the first cell on row hover */
|
|
23558
24216
|
.doc-row-del { position:absolute; top:3px; left:2px; width:18px; height:18px; display:grid; place-items:center;
|
|
23559
24217
|
color:var(--muted); background:var(--bg); border:1px solid var(--line); border-radius:5px; box-shadow:var(--shadow-sm);
|
|
@@ -23598,15 +24256,15 @@ var styles_default = ` :root {
|
|
|
23598
24256
|
.colhead:hover { background:var(--hover); }
|
|
23599
24257
|
.colhead .ti { color:var(--muted); } .colhead .ti svg { width:14px; height:14px; }
|
|
23600
24258
|
.colhead .nm { flex:1; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
23601
|
-
.col-resizer { position:absolute; top:0; right:-
|
|
23602
|
-
.col-resizer:hover, .col-resizer.dragging { background:var(--accent); opacity:.35; }
|
|
24259
|
+
.col-resizer { position:absolute; top:0; right:-4px; width:7px; height:100%; cursor:col-resize; z-index:2; touch-action:none; }
|
|
23603
24260
|
.addcol { min-width:46px; } .addcol .colhead { justify-content:flex-start; color:var(--muted); }
|
|
23604
24261
|
/* trailing filler column: empty canvas to the right of the data, not a real cell */
|
|
23605
24262
|
table.grid td.filler { border-right:0; border-bottom:0; background:transparent; }
|
|
23606
24263
|
body.col-resizing { user-select:none; cursor:col-resize; }
|
|
23607
24264
|
table.grid .selcell { width:38px; min-width:38px; max-width:38px; text-align:center; }
|
|
23608
24265
|
table.grid .gripcol, table.grid .rowgrip { width:26px; min-width:26px; max-width:26px; text-align:center; }
|
|
23609
|
-
|
|
24266
|
+
/* relative: anchors the .celledit overlay editor to the cell */
|
|
24267
|
+
td.cell-td { position:relative; vertical-align:top; }
|
|
23610
24268
|
.cell { padding:8px 11px; min-height:37px; cursor:text; word-break:break-word; display:flex; flex-wrap:wrap; gap:3px; align-items:center; }
|
|
23611
24269
|
/* hover/active tint goes on the <td> (always full row height) not the inner
|
|
23612
24270
|
.cell (only as tall as its content), so a taller sibling row never leaves a
|
|
@@ -23658,6 +24316,14 @@ var styles_default = ` :root {
|
|
|
23658
24316
|
.addrow:hover { background:var(--hover); color:var(--fg-soft); }
|
|
23659
24317
|
.gridfoot { display:flex; gap:18px; color:var(--muted); font-size:12px; padding:8px 2px; }
|
|
23660
24318
|
input.inlineedit { width:100%; border:0; outline:2px solid var(--accent); border-radius:4px; padding:7px 9px; background:var(--bg); color:var(--fg); font:inherit; }
|
|
24319
|
+
/* Overlay cell editor: floats over the td; the display content underneath
|
|
24320
|
+
stays in flow so the row height never changes while editing. Padding on the
|
|
24321
|
+
input matches .cell (8px 11px) so the text doesn't shift on entering edit. */
|
|
24322
|
+
.celledit { position:absolute; top:0; left:0; width:100%; min-height:100%; z-index:10;
|
|
24323
|
+
display:flex; align-items:stretch; background:var(--bg); border-radius:var(--radius-sm);
|
|
24324
|
+
box-shadow:0 0 0 2px var(--accent), var(--shadow-md); }
|
|
24325
|
+
.celledit input.inlineedit { width:100%; min-width:0; border:0; outline:0; border-radius:inherit;
|
|
24326
|
+
padding:8px 11px; background:transparent; }
|
|
23661
24327
|
|
|
23662
24328
|
/* ---- sites (\u7AD9\u70B9\u7BA1\u7406) ---- */
|
|
23663
24329
|
.sites-grid { display:grid; grid-template-columns:repeat(auto-fill, minmax(264px, 1fr)); gap:14px; margin-top:6px; }
|
|
@@ -23761,6 +24427,53 @@ var styles_default = ` :root {
|
|
|
23761
24427
|
.proprow .v:hover { background:var(--hover); }
|
|
23762
24428
|
.peek-divider { height:1px; background:var(--line); margin:22px 0 18px; }
|
|
23763
24429
|
|
|
24430
|
+
/* version history (doc drawer + record peek view) */
|
|
24431
|
+
.hist-peek { width:760px; }
|
|
24432
|
+
.hist-title { display:flex; align-items:center; gap:6px; font-weight:600; font-size:var(--fs-ui); }
|
|
24433
|
+
.hist-toggle { display:flex; align-items:center; gap:5px; font-size:var(--fs-sm); color:var(--muted); cursor:pointer; user-select:none; white-space:nowrap; }
|
|
24434
|
+
.hist-toggle input { accent-color:var(--accent); }
|
|
24435
|
+
.hist-split { display:flex; flex:1; min-height:0; }
|
|
24436
|
+
.hist-list { width:252px; flex:none; overflow:auto; border-right:1px solid var(--line); padding:8px; }
|
|
24437
|
+
.hist-item { padding:8px 10px; border-radius:var(--radius-sm); cursor:pointer; }
|
|
24438
|
+
.hist-item:hover { background:var(--hover); }
|
|
24439
|
+
.hist-item.sel { background:var(--accent-soft); }
|
|
24440
|
+
.hist-item.static { cursor:default; border-bottom:1px solid var(--line); border-radius:0; padding:10px 4px; }
|
|
24441
|
+
.hist-item.static:hover { background:none; }
|
|
24442
|
+
.hist-item .row1 { display:flex; align-items:center; gap:7px; }
|
|
24443
|
+
.hist-item .row1 .when { font-weight:600; font-size:var(--fs-ui); }
|
|
24444
|
+
.hist-item .row2 { display:flex; gap:8px; margin-top:2px; font-size:var(--fs-sm); color:var(--muted); min-width:0; }
|
|
24445
|
+
.hist-item .row2 .what { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
24446
|
+
.hist-kind { font-size:var(--fs-xs); padding:1px 6px; border-radius:99px; background:var(--surface-2); color:var(--muted); }
|
|
24447
|
+
.hist-kind.revert { background:var(--accent-soft); color:var(--accent); }
|
|
24448
|
+
.hist-kind.repair { background:var(--danger-soft); color:var(--danger); }
|
|
24449
|
+
.hist-now { font-size:var(--fs-xs); padding:1px 6px; border-radius:99px; background:var(--accent); color:var(--accent-fg); }
|
|
24450
|
+
.hist-preview { flex:1; overflow:auto; padding:20px 24px; }
|
|
24451
|
+
.hist-doc-title { font-size:21px; font-weight:700; letter-spacing:-.02em; margin-bottom:14px; }
|
|
24452
|
+
.hist-block { white-space:pre-wrap; font-size:var(--fs-body); line-height:1.65; padding:2px 6px; border-radius:4px; margin:0 -6px 12px; }
|
|
24453
|
+
/* line-level diff rows (git-like): pale full-line tint, darker tint on the
|
|
24454
|
+
intra-line changed segment; strikethrough only on the deleted segment. */
|
|
24455
|
+
.hist-line { white-space:pre-wrap; font-size:var(--fs-body); line-height:1.6; padding:1px 6px; margin:0 -6px; border-radius:3px; }
|
|
24456
|
+
.hist-line.add { background:color-mix(in srgb, var(--success) 13%, transparent); }
|
|
24457
|
+
.hist-line.del { background:var(--danger-soft); }
|
|
24458
|
+
.hist-line.add .seg { background:color-mix(in srgb, var(--success) 32%, transparent); border-radius:2px; }
|
|
24459
|
+
.hist-line.del .seg { background:color-mix(in srgb, var(--danger) 28%, transparent); border-radius:2px; text-decoration:line-through; }
|
|
24460
|
+
.hist-foot { display:flex; justify-content:flex-end; padding:12px 16px; border-top:1px solid var(--line); }
|
|
24461
|
+
.pad { padding:12px; }
|
|
24462
|
+
.hist-rec-head { display:flex; align-items:center; justify-content:space-between; margin-bottom:10px; }
|
|
24463
|
+
.hist-feed { padding:12px 24px; }
|
|
24464
|
+
.hist-recname { font-weight:600; font-size:var(--fs-sm); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
24465
|
+
.hist-status { font-size:var(--fs-xs); color:var(--muted); flex:none; }
|
|
24466
|
+
.hist-field .old, .hist-field .new { max-width:200px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
24467
|
+
.hist-more { border:0; background:none; color:var(--muted); font-size:var(--fs-sm); cursor:pointer; padding:2px 0; text-align:left; }
|
|
24468
|
+
.hist-more:hover { color:var(--fg); }
|
|
24469
|
+
.hist-fields { padding:8px 4px 4px; display:flex; flex-direction:column; gap:6px; }
|
|
24470
|
+
.hist-field { display:flex; gap:8px; font-size:var(--fs-sm); align-items:baseline; flex-wrap:wrap; }
|
|
24471
|
+
.hist-field .fname { color:var(--muted); width:120px; flex:none; }
|
|
24472
|
+
.hist-field .old { text-decoration:line-through; color:var(--muted); }
|
|
24473
|
+
.hist-field .arr { color:var(--muted); }
|
|
24474
|
+
.hist-field .new { font-weight:500; }
|
|
24475
|
+
.hist-restore { margin-top:8px; align-self:flex-start; }
|
|
24476
|
+
|
|
23764
24477
|
/* menu / popover */
|
|
23765
24478
|
.menu-layer { position:fixed; inset:0; z-index:90; }
|
|
23766
24479
|
.pop { position:fixed; background:var(--bg); border:1px solid var(--line); border-radius:var(--radius); box-shadow:var(--shadow-lg);
|
|
@@ -23889,12 +24602,51 @@ var styles_default = ` :root {
|
|
|
23889
24602
|
.kg-particle { opacity:0 !important; }
|
|
23890
24603
|
}
|
|
23891
24604
|
.error-bar { margin:12px 24px; padding:10px 14px; background:var(--danger-soft); color:var(--danger); border-radius:var(--radius-sm); font-size:13px; cursor:pointer; }
|
|
24605
|
+
.offline-bar { margin:12px 24px 0; padding:8px 14px; background:var(--surface-2); color:var(--fg-soft); border-radius:var(--radius-sm); font-size:12.5px; }
|
|
23892
24606
|
.search-hit { margin:0 0 8px; padding:10px 14px; background:var(--surface); border-radius:var(--radius-sm); font-size:13px; cursor:pointer; }
|
|
23893
24607
|
.search-hit:hover { background:var(--surface-2); }
|
|
23894
24608
|
.search-hit mark { background:transparent; color:var(--accent); font-weight:600; }
|
|
23895
24609
|
.view-placeholder { padding:40px; text-align:center; border:1px dashed var(--line-strong); border-radius:var(--radius); color:var(--muted); margin-top:8px; }
|
|
23896
24610
|
.ico.flip { transform:scaleX(-1); }
|
|
23897
24611
|
|
|
24612
|
+
/* ---- find in document (Ctrl-F) ---- */
|
|
24613
|
+
/* CSS Custom Highlight overlays \u2014 no DOM mutation, safe over contentEditable. */
|
|
24614
|
+
::highlight(mh-find-all) { background:color-mix(in srgb, var(--accent) 26%, transparent); border-radius:2px; }
|
|
24615
|
+
::highlight(mh-find-current) { background:var(--accent); color:var(--accent-fg); }
|
|
24616
|
+
.find-bar {
|
|
24617
|
+
position:fixed; top:58px; right:18px; z-index:40; /* below the ~50px topbar */
|
|
24618
|
+
display:flex; align-items:center; gap:4px;
|
|
24619
|
+
padding:5px 6px; background:var(--bg); border:1px solid var(--line-strong);
|
|
24620
|
+
border-radius:var(--radius); box-shadow:var(--shadow-lg);
|
|
24621
|
+
}
|
|
24622
|
+
.find-bar .find-ico { color:var(--muted); margin:0 2px; flex:none; }
|
|
24623
|
+
.find-bar input {
|
|
24624
|
+
width:200px; max-width:42vw; border:0; outline:0; background:transparent;
|
|
24625
|
+
color:var(--fg); font-size:13px; padding:3px 2px;
|
|
24626
|
+
}
|
|
24627
|
+
.find-bar input::placeholder { color:var(--muted); }
|
|
24628
|
+
.find-count { font-size:12px; color:var(--muted); min-width:48px; text-align:right; padding:0 2px; font-variant-numeric:tabular-nums; }
|
|
24629
|
+
.find-opt {
|
|
24630
|
+
border:1px solid var(--line-strong); background:transparent; color:var(--fg-soft);
|
|
24631
|
+
border-radius:var(--radius-sm); font-size:12px; line-height:1; padding:4px 6px; cursor:pointer;
|
|
24632
|
+
transition:background .12s, color .12s, border-color .12s;
|
|
24633
|
+
}
|
|
24634
|
+
.find-opt:hover { background:var(--surface-2); }
|
|
24635
|
+
.find-opt.on { background:var(--accent-soft); color:var(--accent); border-color:color-mix(in srgb, var(--accent) 45%, transparent); }
|
|
24636
|
+
.find-nav {
|
|
24637
|
+
display:grid; place-items:center; width:26px; height:26px; flex:none;
|
|
24638
|
+
border:0; background:transparent; color:var(--fg-soft); border-radius:var(--radius-sm); cursor:pointer;
|
|
24639
|
+
transition:background .12s, color .12s;
|
|
24640
|
+
}
|
|
24641
|
+
.find-nav:hover:not(:disabled) { background:var(--surface-2); color:var(--fg); }
|
|
24642
|
+
.find-nav:disabled { opacity:.4; cursor:default; }
|
|
24643
|
+
.find-nav .find-prev { transform:rotate(180deg); }
|
|
24644
|
+
.find-close { color:var(--muted); }
|
|
24645
|
+
@media (max-width:560px) {
|
|
24646
|
+
.find-bar { left:12px; right:12px; }
|
|
24647
|
+
.find-bar input { width:auto; flex:1; max-width:none; }
|
|
24648
|
+
}
|
|
24649
|
+
|
|
23898
24650
|
/* ---- board (kanban) view ---- */
|
|
23899
24651
|
.board-wrap { margin-top:4px; }
|
|
23900
24652
|
.board-bar { display:flex; align-items:center; gap:10px; padding:2px 0 12px; }
|
|
@@ -23970,9 +24722,16 @@ var styles_default = ` :root {
|
|
|
23970
24722
|
display:flex; align-items:center; gap:2px; padding:0 4px; cursor:grab; touch-action:none; box-shadow:var(--shadow-sm); overflow:hidden; }
|
|
23971
24723
|
.tl-item .tl-label { flex:1; font-size:12px; font-weight:500; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; pointer-events:none; }
|
|
23972
24724
|
.tl-row:hover .tl-item { box-shadow:0 0 0 1px var(--accent), var(--shadow-sm); }
|
|
23973
|
-
|
|
23974
|
-
|
|
23975
|
-
|
|
24725
|
+
/* Edge handles: a small centered round-capped grip inside the colored bar.
|
|
24726
|
+
--accent-fg (not white) keeps contrast right in both themes \u2014 dark grip on
|
|
24727
|
+
the light-purple dark-mode bar, white grip on the blue light-mode bar. */
|
|
24728
|
+
.tl-item .tl-h { position:relative; width:9px; align-self:stretch; flex:none; cursor:col-resize; opacity:0; transition:opacity .15s; touch-action:none; }
|
|
24729
|
+
.tl-item .tl-h::before { content:""; position:absolute; left:50%; top:50%; width:3px; height:10px;
|
|
24730
|
+
transform:translate(-50%,-50%); border-radius:999px;
|
|
24731
|
+
background:color-mix(in srgb, var(--accent-fg) 70%, transparent);
|
|
24732
|
+
transition:height .15s cubic-bezier(.4,0,.2,1), background .15s; }
|
|
24733
|
+
.tl-item:hover .tl-h, .tl-item .tl-h.dragging { opacity:1; }
|
|
24734
|
+
.tl-item .tl-h:hover::before, .tl-item .tl-h.dragging::before { height:14px; background:var(--accent-fg); }
|
|
23976
24735
|
.tl-milestone { position:absolute; top:9px; width:18px; height:18px; transform:rotate(45deg); background:var(--accent); border-radius:3px; cursor:grab; touch-action:none; box-shadow:var(--shadow-sm); margin-left:-9px; }
|
|
23977
24736
|
.tl-milestone:hover { box-shadow:0 0 0 2px var(--accent-soft), var(--shadow-sm); }
|
|
23978
24737
|
.tl-track-empty { position:absolute; top:0; right:0; height:100%; cursor:copy; }
|
|
@@ -24066,8 +24825,13 @@ var styles_default = ` :root {
|
|
|
24066
24825
|
/* Anchor/TOC jumps: clear the mobile topbar (49px min + notch inset);
|
|
24067
24826
|
the desktop 60px estimate leaves blocks half-hidden behind it. */
|
|
24068
24827
|
.block { scroll-margin-top:calc(49px + env(safe-area-inset-top) + 8px); }
|
|
24828
|
+
/* Find bar clears the taller mobile topbar (49px min + notch inset). */
|
|
24829
|
+
.find-bar { top:calc(49px + env(safe-area-inset-top) + 8px); }
|
|
24069
24830
|
.db { padding:16px 14px 90px; }
|
|
24070
24831
|
.peek { width:100%; }
|
|
24832
|
+
/* history drawer: the side-by-side list/preview doesn't fit a phone \u2014 stack. */
|
|
24833
|
+
.hist-split { flex-direction:column; }
|
|
24834
|
+
.hist-list { width:auto; max-height:38%; border-right:0; border-bottom:1px solid var(--line); }
|
|
24071
24835
|
|
|
24072
24836
|
/* Safe areas (viewport-fit=cover): keep chrome clear of the notch / home bar. */
|
|
24073
24837
|
.sb-head { padding-top:calc(14px + env(safe-area-inset-top)); }
|
|
@@ -24117,7 +24881,7 @@ var styles_default = ` :root {
|
|
|
24117
24881
|
table.doc-table .doc-row-del { opacity:1; }
|
|
24118
24882
|
.selcell input { opacity:1; }
|
|
24119
24883
|
.rowgrip { color:var(--muted); }
|
|
24120
|
-
.tl-item .tl-h { opacity:1;
|
|
24884
|
+
.tl-item .tl-h { opacity:1; }
|
|
24121
24885
|
/* Neutralize the stickiest tap-residue tints (a tap "hovers" until the
|
|
24122
24886
|
next tap elsewhere); transient menus/buttons are left alone. */
|
|
24123
24887
|
.navitem:hover { background:none; }
|
|
@@ -24133,15 +24897,20 @@ var styles_default = ` :root {
|
|
|
24133
24897
|
macOS vibrancy blur show through; a translucent panel keeps text readable.
|
|
24134
24898
|
The top bar is the window drag region; its buttons opt back out. */
|
|
24135
24899
|
body.quicknote { background:transparent; }
|
|
24900
|
+
/* html carries an opaque bg for the iOS 26 Safari toolbar tint; the vibrancy
|
|
24901
|
+
window needs the root transparent again or the blur is walled off. */
|
|
24902
|
+
html:has(body.quicknote) { background:transparent; }
|
|
24136
24903
|
/* The main app lays #app out as a flex row (sidebar + main); the quick-note
|
|
24137
24904
|
route has a single child, so override to a full-bleed block or the panel
|
|
24138
24905
|
shrinks to its content width and the window's vibrancy shows through. */
|
|
24139
24906
|
body.quicknote #app { display:block; width:100vw; height:100vh; height:100dvh; }
|
|
24140
24907
|
.qn { display:flex; flex-direction:column; width:100%; height:100vh; height:100dvh; overflow:hidden;
|
|
24141
24908
|
background:color-mix(in srgb, var(--bg) 20%, transparent); }
|
|
24142
|
-
.qn-bar { display:flex; align-items:center; gap:6px; padding:4px 10px
|
|
24909
|
+
.qn-bar { display:flex; align-items:center; gap:6px; padding:4px 10px;
|
|
24143
24910
|
user-select:none; -webkit-app-region:drag;
|
|
24144
24911
|
background:color-mix(in srgb, var(--surface) 30%, transparent); }
|
|
24912
|
+
/* macOS only: clear the inset traffic lights; Win/Linux frameless has none */
|
|
24913
|
+
body.desktop-mac .qn-bar { padding-left:76px; }
|
|
24145
24914
|
.qn-brand { flex:1; min-width:0; font-size:12px; font-weight:600; color:var(--fg-soft);
|
|
24146
24915
|
white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
|
24147
24916
|
.qn-actions { display:flex; gap:2px; -webkit-app-region:no-drag; }
|
|
@@ -24151,6 +24920,10 @@ var styles_default = ` :root {
|
|
|
24151
24920
|
.qn-body .doc { max-width:none; margin:0; padding:14px 22px 30vh; }
|
|
24152
24921
|
.qn-body .doc-title { font-size:24px; margin-bottom:2px; }
|
|
24153
24922
|
.qn-body .doc-meta { display:none; }
|
|
24923
|
+
/* The narrow-width gutter hide is now gated on (pointer:coarse), which this
|
|
24924
|
+
desktop window never matches \u2014 re-hide explicitly: quick notes offer no
|
|
24925
|
+
block insert/drag handles (Enter / slash commands cover insertion). */
|
|
24926
|
+
body.quicknote .block .gutter { display:none; }
|
|
24154
24927
|
/* A native checkbox paints an opaque white box when unchecked, which breaks
|
|
24155
24928
|
the window's vibrancy blur. Draw a custom box so the unchecked background
|
|
24156
24929
|
stays transparent; checked falls back to the accent fill + checkmark. */
|
|
@@ -24184,6 +24957,11 @@ var styles_default = ` :root {
|
|
|
24184
24957
|
.qn-shortcut.capturing { outline:2px solid var(--accent); color:var(--accent); }
|
|
24185
24958
|
`;
|
|
24186
24959
|
|
|
24960
|
+
// src/webui/server/icons.ts
|
|
24961
|
+
var ICON_192 = "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";
|
|
24962
|
+
var ICON_512 = "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";
|
|
24963
|
+
var ICON_180 = "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";
|
|
24964
|
+
|
|
24187
24965
|
// src/webui/server/assets.ts
|
|
24188
24966
|
var RUNNING_FROM_SOURCE = import.meta.url.includes("/src/webui/");
|
|
24189
24967
|
var HTML = `<!doctype html>
|
|
@@ -24211,6 +24989,8 @@ var HTML = `<!doctype html>
|
|
|
24211
24989
|
alone, so the dark palette exists exactly once \u2014 see styles.css. Dark also
|
|
24212
24990
|
pre-tints the status bar (hex mirrors --bg) so the first frame isn't white. -->
|
|
24213
24991
|
<script>try{var t=localStorage.getItem('mh-theme'),d=t==='dark'||(t!=='light'&&matchMedia('(prefers-color-scheme: dark)').matches);document.documentElement.dataset.resolved=d?'dark':'light';if(d)document.getElementById('theme-color-meta').content='#1a1a1c'}catch(e){}</script>
|
|
24992
|
+
<link rel="manifest" href="/manifest.webmanifest">
|
|
24993
|
+
<link rel="apple-touch-icon" href="/icons/icon-180.png">
|
|
24214
24994
|
<link rel="stylesheet" href="/webui.css">
|
|
24215
24995
|
</head>
|
|
24216
24996
|
<body>
|
|
@@ -24218,53 +24998,104 @@ var HTML = `<!doctype html>
|
|
|
24218
24998
|
<script type="module" src="/webui.js"></script>
|
|
24219
24999
|
</body>
|
|
24220
25000
|
</html>`;
|
|
24221
|
-
var
|
|
24222
|
-
|
|
24223
|
-
|
|
25001
|
+
var injected = {
|
|
25002
|
+
js: null,
|
|
25003
|
+
sw: null,
|
|
25004
|
+
dbWorker: null,
|
|
25005
|
+
runtime: null,
|
|
25006
|
+
sdk: null
|
|
25007
|
+
};
|
|
25008
|
+
var injectedWasm = null;
|
|
24224
25009
|
function newestSourceMtime(dir) {
|
|
24225
25010
|
let newest = 0;
|
|
24226
|
-
for (const e3 of
|
|
25011
|
+
for (const e3 of readdirSync3(dir, { withFileTypes: true })) {
|
|
24227
25012
|
if (e3.name === "server" || e3.name.startsWith("."))
|
|
24228
25013
|
continue;
|
|
24229
|
-
const p2 =
|
|
25014
|
+
const p2 = join7(dir, e3.name);
|
|
24230
25015
|
if (e3.isDirectory())
|
|
24231
25016
|
newest = Math.max(newest, newestSourceMtime(p2));
|
|
24232
25017
|
else if (/\.tsx?$/.test(e3.name))
|
|
24233
|
-
newest = Math.max(newest,
|
|
25018
|
+
newest = Math.max(newest, statSync2(p2).mtimeMs);
|
|
24234
25019
|
}
|
|
24235
25020
|
return newest;
|
|
24236
25021
|
}
|
|
24237
|
-
|
|
24238
|
-
|
|
24239
|
-
|
|
24240
|
-
|
|
24241
|
-
const
|
|
24242
|
-
|
|
24243
|
-
|
|
24244
|
-
|
|
24245
|
-
const
|
|
24246
|
-
|
|
24247
|
-
|
|
24248
|
-
|
|
24249
|
-
|
|
24250
|
-
|
|
24251
|
-
|
|
24252
|
-
|
|
24253
|
-
|
|
24254
|
-
|
|
24255
|
-
|
|
24256
|
-
|
|
24257
|
-
|
|
24258
|
-
|
|
24259
|
-
|
|
24260
|
-
|
|
24261
|
-
}
|
|
25022
|
+
function bundleGetter(opts) {
|
|
25023
|
+
let cached2 = null;
|
|
25024
|
+
let cachedMtime = 0;
|
|
25025
|
+
return async () => {
|
|
25026
|
+
const fromBinary = injected[opts.key];
|
|
25027
|
+
if (fromBinary != null)
|
|
25028
|
+
return fromBinary;
|
|
25029
|
+
if (RUNNING_FROM_SOURCE) {
|
|
25030
|
+
const srcDir = fileURLToPath(new URL("..", import.meta.url));
|
|
25031
|
+
let newest = newestSourceMtime(srcDir);
|
|
25032
|
+
for (const extra of opts.extraDirs ?? []) {
|
|
25033
|
+
newest = Math.max(newest, newestSourceMtime(fileURLToPath(new URL(extra, import.meta.url))));
|
|
25034
|
+
}
|
|
25035
|
+
if (cached2 == null || newest > cachedMtime) {
|
|
25036
|
+
const entry = fileURLToPath(new URL(opts.entry, import.meta.url));
|
|
25037
|
+
const res = await Bun.build({ entrypoints: [entry], target: "browser" });
|
|
25038
|
+
if (!res.success)
|
|
25039
|
+
throw new AggregateError(res.logs, `${opts.dist} build failed`);
|
|
25040
|
+
cached2 = await res.outputs[0].text();
|
|
25041
|
+
cachedMtime = newest;
|
|
25042
|
+
}
|
|
25043
|
+
return cached2;
|
|
25044
|
+
}
|
|
25045
|
+
if (cached2 == null) {
|
|
25046
|
+
const prebuilt = Bun.file(fileURLToPath(new URL(`./${opts.dist}`, import.meta.url)));
|
|
25047
|
+
if (!await prebuilt.exists()) {
|
|
25048
|
+
throw new Error(`${opts.dist} missing: run \`bun run build\``);
|
|
25049
|
+
}
|
|
25050
|
+
cached2 = await prebuilt.text();
|
|
25051
|
+
}
|
|
25052
|
+
return cached2;
|
|
25053
|
+
};
|
|
25054
|
+
}
|
|
25055
|
+
var getJs = bundleGetter({ key: "js", entry: "../app.tsx", dist: "webui.js" });
|
|
25056
|
+
var getSwRaw = bundleGetter({ key: "sw", entry: "../sw.ts", dist: "sw.js" });
|
|
25057
|
+
var getDbWorker = bundleGetter({ key: "dbWorker", entry: "../data/db-worker.ts", dist: "db-worker.js" });
|
|
25058
|
+
var getRuntime = bundleGetter({ key: "runtime", entry: "../runtime.ts", dist: "mh-runtime.js" });
|
|
25059
|
+
var getSdk = bundleGetter({ key: "sdk", entry: "../../sdk/client.ts", dist: "metahub-sdk.js", extraDirs: ["../../sdk"] });
|
|
24262
25060
|
async function getCss() {
|
|
24263
25061
|
if (RUNNING_FROM_SOURCE) {
|
|
24264
25062
|
return Bun.file(fileURLToPath(new URL("../styles.css", import.meta.url))).text();
|
|
24265
25063
|
}
|
|
24266
25064
|
return styles_default;
|
|
24267
25065
|
}
|
|
25066
|
+
async function getSw() {
|
|
25067
|
+
const [raw, js, css] = await Promise.all([getSwRaw(), getJs(), getCss()]);
|
|
25068
|
+
const version2 = new Bun.CryptoHasher("sha256").update(js).update(css).digest("hex").slice(0, 16);
|
|
25069
|
+
return raw.replaceAll("__MH_SW_VERSION__", version2);
|
|
25070
|
+
}
|
|
25071
|
+
async function getWasm() {
|
|
25072
|
+
if (injectedWasm != null)
|
|
25073
|
+
return injectedWasm;
|
|
25074
|
+
const path = RUNNING_FROM_SOURCE ? join7(dirname(Bun.resolveSync("@sqlite.org/sqlite-wasm", fileURLToPath(new URL(".", import.meta.url)))), "sqlite3.wasm") : fileURLToPath(new URL("./sqlite3.wasm", import.meta.url));
|
|
25075
|
+
const f4 = Bun.file(path);
|
|
25076
|
+
if (!await f4.exists()) {
|
|
25077
|
+
throw new Error(`sqlite3.wasm missing at ${path} \u2014 run \`bun install\` / \`bun run build\``);
|
|
25078
|
+
}
|
|
25079
|
+
return f4.arrayBuffer();
|
|
25080
|
+
}
|
|
25081
|
+
var MANIFEST = JSON.stringify({
|
|
25082
|
+
name: "Metahub",
|
|
25083
|
+
short_name: "Metahub",
|
|
25084
|
+
start_url: "/",
|
|
25085
|
+
scope: "/",
|
|
25086
|
+
display: "standalone",
|
|
25087
|
+
background_color: "#ffffff",
|
|
25088
|
+
theme_color: "#ffffff",
|
|
25089
|
+
icons: [
|
|
25090
|
+
{ src: "/icons/icon-192.png", sizes: "192x192", type: "image/png" },
|
|
25091
|
+
{ src: "/icons/icon-512.png", sizes: "512x512", type: "image/png" }
|
|
25092
|
+
]
|
|
25093
|
+
});
|
|
25094
|
+
var ICONS = {
|
|
25095
|
+
"/icons/icon-192.png": ICON_192,
|
|
25096
|
+
"/icons/icon-512.png": ICON_512,
|
|
25097
|
+
"/icons/icon-180.png": ICON_180
|
|
25098
|
+
};
|
|
24268
25099
|
async function warmWebui() {
|
|
24269
25100
|
try {
|
|
24270
25101
|
await getJs();
|
|
@@ -24283,11 +25114,37 @@ async function serveWebui(req) {
|
|
|
24283
25114
|
return asset(HTML, "text/html; charset=utf-8");
|
|
24284
25115
|
if (pathname === "/webui.css")
|
|
24285
25116
|
return asset(await getCss(), "text/css; charset=utf-8");
|
|
24286
|
-
if (pathname === "/
|
|
25117
|
+
if (pathname === "/manifest.webmanifest")
|
|
25118
|
+
return asset(MANIFEST, "application/manifest+json");
|
|
25119
|
+
const icon = ICONS[pathname];
|
|
25120
|
+
if (icon) {
|
|
25121
|
+
return new Response(Buffer.from(icon, "base64"), {
|
|
25122
|
+
headers: { "content-type": "image/png", "cache-control": "public, max-age=86400" }
|
|
25123
|
+
});
|
|
25124
|
+
}
|
|
25125
|
+
if (pathname === "/sqlite3.wasm") {
|
|
25126
|
+
try {
|
|
25127
|
+
return new Response(await getWasm(), {
|
|
25128
|
+
headers: { "content-type": "application/wasm", "cache-control": "public, max-age=86400" }
|
|
25129
|
+
});
|
|
25130
|
+
} catch (e3) {
|
|
25131
|
+
console.error("[webui] failed to serve /sqlite3.wasm \u2014", e3);
|
|
25132
|
+
return new Response(`sqlite3.wasm unavailable: ${e3}`, { status: 500 });
|
|
25133
|
+
}
|
|
25134
|
+
}
|
|
25135
|
+
const script = {
|
|
25136
|
+
"/webui.js": getJs,
|
|
25137
|
+
"/sw.js": getSw,
|
|
25138
|
+
"/db-worker.js": getDbWorker,
|
|
25139
|
+
"/mh-runtime.js": getRuntime,
|
|
25140
|
+
"/metahub-sdk.js": getSdk
|
|
25141
|
+
};
|
|
25142
|
+
const getter = script[pathname];
|
|
25143
|
+
if (getter) {
|
|
24287
25144
|
try {
|
|
24288
|
-
return asset(await
|
|
25145
|
+
return asset(await getter(), "text/javascript; charset=utf-8");
|
|
24289
25146
|
} catch (e3) {
|
|
24290
|
-
console.error(
|
|
25147
|
+
console.error(`[webui] failed to serve ${pathname} \u2014`, e3);
|
|
24291
25148
|
return new Response(`webui build failed: ${e3}`, { status: 500 });
|
|
24292
25149
|
}
|
|
24293
25150
|
}
|
|
@@ -24296,6 +25153,7 @@ async function serveWebui(req) {
|
|
|
24296
25153
|
|
|
24297
25154
|
// src/webui/server/routes.ts
|
|
24298
25155
|
init_errors();
|
|
25156
|
+
init_node();
|
|
24299
25157
|
var DatabaseSchema = exports_external.object({
|
|
24300
25158
|
id: exports_external.string(),
|
|
24301
25159
|
name: exports_external.string(),
|
|
@@ -24313,7 +25171,8 @@ var PropertySchema = exports_external.object({
|
|
|
24313
25171
|
var RecordSchema = exports_external.object({
|
|
24314
25172
|
id: exports_external.string(),
|
|
24315
25173
|
database_id: exports_external.string(),
|
|
24316
|
-
values: exports_external.record(exports_external.string(), exports_external.any()).describe('Field values keyed by property name (e.g. {"Amount":35}). Reads nest cells here; writes take a flat {column: value} map.')
|
|
25174
|
+
values: exports_external.record(exports_external.string(), exports_external.any()).describe('Field values keyed by property name (e.g. {"Amount":35}). Reads nest cells here; writes take a flat {column: value} map. Lossy when two properties share a name \u2014 prefer `cells` then.'),
|
|
25175
|
+
cells: exports_external.record(exports_external.string(), exports_external.any()).describe("Field values keyed by property id \u2014 lossless under duplicate property names. Writes accept property ids as keys too.")
|
|
24317
25176
|
});
|
|
24318
25177
|
var MoveRecordReq = exports_external.object({
|
|
24319
25178
|
target: exports_external.string(),
|
|
@@ -24348,6 +25207,10 @@ var UpdateDatabaseReq = exports_external.object({
|
|
|
24348
25207
|
name: exports_external.string().optional(),
|
|
24349
25208
|
icon: exports_external.string().nullable().optional()
|
|
24350
25209
|
});
|
|
25210
|
+
var DuplicateDatabaseReq = exports_external.object({
|
|
25211
|
+
name: exports_external.string().optional().describe("Name for the copy (defaults to the source name)"),
|
|
25212
|
+
icon: exports_external.string().optional()
|
|
25213
|
+
});
|
|
24351
25214
|
var CreatePropertyReq = exports_external.object({
|
|
24352
25215
|
db: exports_external.string(),
|
|
24353
25216
|
name: exports_external.string(),
|
|
@@ -24374,10 +25237,15 @@ var UpdateDocumentReq = exports_external.object({
|
|
|
24374
25237
|
parent_id: exports_external.string().nullable().optional(),
|
|
24375
25238
|
if_match: exports_external.string().optional()
|
|
24376
25239
|
});
|
|
25240
|
+
var DuplicateDocumentReq = exports_external.object({
|
|
25241
|
+
title: exports_external.string().optional().describe("Title for the copy (defaults to the source title)"),
|
|
25242
|
+
parent_id: exports_external.string().nullable().optional()
|
|
25243
|
+
});
|
|
24377
25244
|
var RevisionBase = exports_external.object({
|
|
24378
25245
|
version: exports_external.string(),
|
|
24379
25246
|
at: exports_external.string().describe("Wall-clock time of the revision (ISO 8601)"),
|
|
24380
25247
|
node_id: exports_external.string(),
|
|
25248
|
+
kind: exports_external.enum(["user", "repair", "revert"]).describe("Source of the revision"),
|
|
24381
25249
|
changes: exports_external.number(),
|
|
24382
25250
|
created: exports_external.boolean(),
|
|
24383
25251
|
deleted: exports_external.boolean()
|
|
@@ -24391,6 +25259,15 @@ var RecordRevisionSchema = RevisionBase.extend({
|
|
|
24391
25259
|
fields: exports_external.array(exports_external.string()).describe("Property ids of the cells written"),
|
|
24392
25260
|
moved: exports_external.boolean()
|
|
24393
25261
|
});
|
|
25262
|
+
var DatabaseActivityEntrySchema = RecordRevisionSchema.extend({
|
|
25263
|
+
record_id: exports_external.string(),
|
|
25264
|
+
record_title: exports_external.string().nullable().describe("Title-property value as of this revision (deleted records keep their last title)"),
|
|
25265
|
+
diffs: exports_external.array(exports_external.object({
|
|
25266
|
+
prop: exports_external.string(),
|
|
25267
|
+
before: exports_external.any().optional(),
|
|
25268
|
+
after: exports_external.any().optional()
|
|
25269
|
+
})).describe("Value-level cell changes; a missing side means the cell did not exist")
|
|
25270
|
+
});
|
|
24394
25271
|
var DocumentVersionStateSchema = exports_external.object({
|
|
24395
25272
|
id: exports_external.string(),
|
|
24396
25273
|
title: exports_external.string(),
|
|
@@ -24409,7 +25286,8 @@ var RevertDocResultSchema = exports_external.object({
|
|
|
24409
25286
|
id: exports_external.string(),
|
|
24410
25287
|
changed: exports_external.boolean(),
|
|
24411
25288
|
restored: exports_external.string(),
|
|
24412
|
-
version: exports_external.string()
|
|
25289
|
+
version: exports_external.string(),
|
|
25290
|
+
undeleted: exports_external.boolean()
|
|
24413
25291
|
});
|
|
24414
25292
|
var RevertRecordResultSchema = exports_external.object({
|
|
24415
25293
|
id: exports_external.string(),
|
|
@@ -24418,6 +25296,41 @@ var RevertRecordResultSchema = exports_external.object({
|
|
|
24418
25296
|
undeleted: exports_external.boolean(),
|
|
24419
25297
|
restored: exports_external.string()
|
|
24420
25298
|
});
|
|
25299
|
+
var PropertyRevisionSchema = RevisionBase.extend({
|
|
25300
|
+
fields: exports_external.array(exports_external.string()).describe("Definition columns touched (name/type/config/position)"),
|
|
25301
|
+
cells_cleared: exports_external.number()
|
|
25302
|
+
});
|
|
25303
|
+
var RevertPropertyReq = exports_external.object({
|
|
25304
|
+
to: exports_external.string().describe("Version token from /api/property/history")
|
|
25305
|
+
});
|
|
25306
|
+
var RevertPropertyResultSchema = exports_external.object({
|
|
25307
|
+
id: exports_external.string(),
|
|
25308
|
+
changed: exports_external.boolean(),
|
|
25309
|
+
fields: exports_external.array(exports_external.string()),
|
|
25310
|
+
restored_cells: exports_external.number(),
|
|
25311
|
+
skipped_cells: exports_external.number(),
|
|
25312
|
+
undeleted: exports_external.boolean(),
|
|
25313
|
+
restored: exports_external.string()
|
|
25314
|
+
});
|
|
25315
|
+
var RecordVersionStateSchema = exports_external.object({
|
|
25316
|
+
id: exports_external.string(),
|
|
25317
|
+
database_id: exports_external.string().nullable(),
|
|
25318
|
+
deleted: exports_external.boolean(),
|
|
25319
|
+
data: exports_external.record(exports_external.string(), exports_external.any()).describe("Cells at that version, keyed by property id"),
|
|
25320
|
+
version: exports_external.string()
|
|
25321
|
+
});
|
|
25322
|
+
var FieldHistoryEntrySchema = exports_external.object({
|
|
25323
|
+
version: exports_external.string(),
|
|
25324
|
+
at: exports_external.string(),
|
|
25325
|
+
node_id: exports_external.string(),
|
|
25326
|
+
value: exports_external.any().optional(),
|
|
25327
|
+
cleared: exports_external.boolean()
|
|
25328
|
+
});
|
|
25329
|
+
var NodeInfoSchema = exports_external.object({
|
|
25330
|
+
node_id: exports_external.string(),
|
|
25331
|
+
label: exports_external.string().nullable().describe("Peer label from pairing; null when unnamed"),
|
|
25332
|
+
self: exports_external.boolean().describe("True for the node serving this request")
|
|
25333
|
+
});
|
|
24421
25334
|
function need3(req, key) {
|
|
24422
25335
|
const v3 = new URL(req.url).searchParams.get(key);
|
|
24423
25336
|
if (!v3)
|
|
@@ -24477,6 +25390,17 @@ var webuiRoutes = [
|
|
|
24477
25390
|
return updateDatabase(db, need3(req, "id"), body);
|
|
24478
25391
|
})
|
|
24479
25392
|
},
|
|
25393
|
+
{
|
|
25394
|
+
method: "POST",
|
|
25395
|
+
path: "/api/database/duplicate",
|
|
25396
|
+
summary: "Copy a database whole \u2014 schema and all records. Query: ?id=<id>",
|
|
25397
|
+
request: DuplicateDatabaseReq,
|
|
25398
|
+
response: DatabaseSchema,
|
|
25399
|
+
handler: handle3(async (req, { db }) => {
|
|
25400
|
+
const body = await req.json().catch(() => ({}));
|
|
25401
|
+
return duplicateDatabase(db, need3(req, "id"), body);
|
|
25402
|
+
})
|
|
25403
|
+
},
|
|
24480
25404
|
{
|
|
24481
25405
|
method: "DELETE",
|
|
24482
25406
|
path: "/api/database",
|
|
@@ -24484,6 +25408,18 @@ var webuiRoutes = [
|
|
|
24484
25408
|
response: OkSchema2,
|
|
24485
25409
|
handler: handle3((req, { db }) => ({ ok: deleteDatabase(db, need3(req, "id")) }))
|
|
24486
25410
|
},
|
|
25411
|
+
{
|
|
25412
|
+
method: "GET",
|
|
25413
|
+
path: "/api/database/activity",
|
|
25414
|
+
summary: "Recent changes across all records of a database, newest first. Query: ?db=<id>&limit=<n>",
|
|
25415
|
+
response: exports_external.array(DatabaseActivityEntrySchema),
|
|
25416
|
+
handler: handle3((req, { db }) => {
|
|
25417
|
+
const limit = opt(req, "limit");
|
|
25418
|
+
return listDatabaseActivity(db, need3(req, "db"), {
|
|
25419
|
+
limit: limit ? Number(limit) : undefined
|
|
25420
|
+
});
|
|
25421
|
+
})
|
|
25422
|
+
},
|
|
24487
25423
|
{
|
|
24488
25424
|
method: "GET",
|
|
24489
25425
|
path: "/api/properties",
|
|
@@ -24531,6 +25467,24 @@ var webuiRoutes = [
|
|
|
24531
25467
|
response: OkSchema2,
|
|
24532
25468
|
handler: handle3((req, { db }) => ({ ok: removeProperty(db, need3(req, "id")) }))
|
|
24533
25469
|
},
|
|
25470
|
+
{
|
|
25471
|
+
method: "GET",
|
|
25472
|
+
path: "/api/property/history",
|
|
25473
|
+
summary: "List a property's definition revisions, newest first. Query: ?id=<id>",
|
|
25474
|
+
response: exports_external.array(PropertyRevisionSchema),
|
|
25475
|
+
handler: handle3((req, { db }) => listPropertyRevisions(db, need3(req, "id")))
|
|
25476
|
+
},
|
|
25477
|
+
{
|
|
25478
|
+
method: "POST",
|
|
25479
|
+
path: "/api/property/revert",
|
|
25480
|
+
summary: "Schema rollback: restore a property's definition and the cells its type-change/removal cleared (user edits since are kept). Query: ?id=<id>",
|
|
25481
|
+
request: RevertPropertyReq,
|
|
25482
|
+
response: RevertPropertyResultSchema,
|
|
25483
|
+
handler: handle3(async (req, { db }) => {
|
|
25484
|
+
const body = await req.json();
|
|
25485
|
+
return revertProperty(db, need3(req, "id"), body.to);
|
|
25486
|
+
})
|
|
25487
|
+
},
|
|
24534
25488
|
{
|
|
24535
25489
|
method: "GET",
|
|
24536
25490
|
path: "/api/records",
|
|
@@ -24597,6 +25551,20 @@ var webuiRoutes = [
|
|
|
24597
25551
|
response: exports_external.array(RecordRevisionSchema),
|
|
24598
25552
|
handler: handle3((req, { db }) => listRecordRevisions(db, need3(req, "id")))
|
|
24599
25553
|
},
|
|
25554
|
+
{
|
|
25555
|
+
method: "GET",
|
|
25556
|
+
path: "/api/record/at",
|
|
25557
|
+
summary: "A record's cells as of a past version. Query: ?id=<id>&version=<token>",
|
|
25558
|
+
response: RecordVersionStateSchema,
|
|
25559
|
+
handler: handle3((req, { db }) => recordAtVersion(db, need3(req, "id"), need3(req, "version")))
|
|
25560
|
+
},
|
|
25561
|
+
{
|
|
25562
|
+
method: "GET",
|
|
25563
|
+
path: "/api/record/field-history",
|
|
25564
|
+
summary: "The write trail of one record cell, newest first. Query: ?id=<id>&prop=<propertyId>",
|
|
25565
|
+
response: exports_external.array(FieldHistoryEntrySchema),
|
|
25566
|
+
handler: handle3((req, { db }) => recordFieldHistory(db, need3(req, "id"), need3(req, "prop")))
|
|
25567
|
+
},
|
|
24600
25568
|
{
|
|
24601
25569
|
method: "POST",
|
|
24602
25570
|
path: "/api/record/revert",
|
|
@@ -24659,6 +25627,21 @@ var webuiRoutes = [
|
|
|
24659
25627
|
return { ...doc2, version: documentVersion(db, doc2.id) };
|
|
24660
25628
|
})
|
|
24661
25629
|
},
|
|
25630
|
+
{
|
|
25631
|
+
method: "POST",
|
|
25632
|
+
path: "/api/document/duplicate",
|
|
25633
|
+
summary: "Copy a document \u2014 title and every block \u2014 next to the source. Query: ?id=<id>",
|
|
25634
|
+
request: DuplicateDocumentReq,
|
|
25635
|
+
response: DocumentSchema,
|
|
25636
|
+
handler: handle3(async (req, { db }) => {
|
|
25637
|
+
const body = await req.json().catch(() => ({}));
|
|
25638
|
+
const doc2 = duplicateDocument(db, need3(req, "id"), {
|
|
25639
|
+
title: body.title,
|
|
25640
|
+
parentId: body.parent_id
|
|
25641
|
+
});
|
|
25642
|
+
return { ...doc2, version: documentVersion(db, doc2.id) };
|
|
25643
|
+
})
|
|
25644
|
+
},
|
|
24662
25645
|
{
|
|
24663
25646
|
method: "PATCH",
|
|
24664
25647
|
path: "/api/document/move",
|
|
@@ -24702,6 +25685,20 @@ var webuiRoutes = [
|
|
|
24702
25685
|
response: OkSchema2,
|
|
24703
25686
|
handler: handle3((req, { db }) => ({ ok: deleteDocument(db, need3(req, "id")) }))
|
|
24704
25687
|
},
|
|
25688
|
+
{
|
|
25689
|
+
method: "GET",
|
|
25690
|
+
path: "/api/nodes",
|
|
25691
|
+
summary: "Known nodes for display: this device plus paired peers with their labels",
|
|
25692
|
+
response: exports_external.array(NodeInfoSchema),
|
|
25693
|
+
handler: handle3((_req, { db }) => {
|
|
25694
|
+
const self = getNodeId(db);
|
|
25695
|
+
const peers = db.query("SELECT node_id, label FROM peers WHERE node_id IS NOT NULL AND node_id <> '' GROUP BY node_id").all();
|
|
25696
|
+
return [
|
|
25697
|
+
{ node_id: self, label: null, self: true },
|
|
25698
|
+
...peers.filter((p2) => p2.node_id !== self).map((p2) => ({ ...p2, self: false }))
|
|
25699
|
+
];
|
|
25700
|
+
})
|
|
25701
|
+
},
|
|
24705
25702
|
{
|
|
24706
25703
|
method: "GET",
|
|
24707
25704
|
path: "/api/search",
|
|
@@ -24884,12 +25881,12 @@ function classifyV6(ip) {
|
|
|
24884
25881
|
function normFamily(family) {
|
|
24885
25882
|
return family === "IPv6" || family === 6 ? "IPv6" : "IPv4";
|
|
24886
25883
|
}
|
|
24887
|
-
function formatUrl(ip, family, port) {
|
|
25884
|
+
function formatUrl(ip, family, port, scheme = "http") {
|
|
24888
25885
|
if (family === "IPv6") {
|
|
24889
25886
|
const bare = ip.replace(/%.*$/, "");
|
|
24890
|
-
return
|
|
25887
|
+
return `${scheme}://[${bare}]:${port}`;
|
|
24891
25888
|
}
|
|
24892
|
-
return
|
|
25889
|
+
return `${scheme}://${ip}:${port}`;
|
|
24893
25890
|
}
|
|
24894
25891
|
function localAddresses(ifaces = os.networkInterfaces()) {
|
|
24895
25892
|
const groups = Array.isArray(ifaces) ? { _: ifaces } : ifaces;
|
|
@@ -24906,11 +25903,11 @@ function localAddresses(ifaces = os.networkInterfaces()) {
|
|
|
24906
25903
|
return out;
|
|
24907
25904
|
}
|
|
24908
25905
|
var SCOPE_ORDER = { loopback: 0, lan: 1, public: 2 };
|
|
24909
|
-
function resolveEndpoints(host, port, ifaces = os.networkInterfaces()) {
|
|
25906
|
+
function resolveEndpoints(host, port, ifaces = os.networkInterfaces(), scheme = "http") {
|
|
24910
25907
|
const localhost = {
|
|
24911
25908
|
scope: "loopback",
|
|
24912
25909
|
family: "IPv4",
|
|
24913
|
-
url:
|
|
25910
|
+
url: `${scheme}://localhost:${port}`
|
|
24914
25911
|
};
|
|
24915
25912
|
if (isLoopback2(host))
|
|
24916
25913
|
return [localhost];
|
|
@@ -24925,7 +25922,7 @@ function resolveEndpoints(host, port, ifaces = os.networkInterfaces()) {
|
|
|
24925
25922
|
if (seen.has(key))
|
|
24926
25923
|
continue;
|
|
24927
25924
|
seen.add(key);
|
|
24928
|
-
reps.push({ scope: a2.scope, family: a2.family, url: formatUrl(a2.ip, a2.family, port) });
|
|
25925
|
+
reps.push({ scope: a2.scope, family: a2.family, url: formatUrl(a2.ip, a2.family, port, scheme) });
|
|
24929
25926
|
}
|
|
24930
25927
|
reps.sort((x4, y3) => SCOPE_ORDER[x4.scope] - SCOPE_ORDER[y3.scope] || x4.family.localeCompare(y3.family));
|
|
24931
25928
|
return [localhost, ...reps];
|
|
@@ -25154,7 +26151,9 @@ var main = defineCommand({
|
|
|
25154
26151
|
debug: { type: "boolean", description: "Disable auth on the server (with --server)" },
|
|
25155
26152
|
token: { type: "string", description: "Server auth token; generated if omitted (with --server)" },
|
|
25156
26153
|
"sync-interval": { type: "string", description: "Auto-sync interval, e.g. 30s/5m (with --server)" },
|
|
25157
|
-
"no-auto-sync": { type: "boolean", description: "Disable the auto-sync timer (with --server)" }
|
|
26154
|
+
"no-auto-sync": { type: "boolean", description: "Disable the auto-sync timer (with --server)" },
|
|
26155
|
+
"tls-cert": { type: "string", description: "TLS certificate PEM path \u2014 serve https directly (with --server)" },
|
|
26156
|
+
"tls-key": { type: "string", description: "TLS private key PEM path (with --server)" }
|
|
25158
26157
|
},
|
|
25159
26158
|
subCommands: {
|
|
25160
26159
|
init: init_default,
|
|
@@ -25175,6 +26174,7 @@ var main = defineCommand({
|
|
|
25175
26174
|
restore: restore_default,
|
|
25176
26175
|
doctor: doctor_default,
|
|
25177
26176
|
repair: repair_default,
|
|
26177
|
+
compact: compact_default,
|
|
25178
26178
|
config: config_default
|
|
25179
26179
|
}
|
|
25180
26180
|
});
|
|
@@ -25189,6 +26189,11 @@ var argv2 = process.argv.slice(2);
|
|
|
25189
26189
|
if (argv2.includes("--server")) {
|
|
25190
26190
|
const portFlag = flagValue(argv2, "port");
|
|
25191
26191
|
const intervalFlag = flagValue(argv2, "sync-interval");
|
|
26192
|
+
const tlsCert = flagValue(argv2, "tls-cert");
|
|
26193
|
+
const tlsKey = flagValue(argv2, "tls-key");
|
|
26194
|
+
if (tlsCert == null !== (tlsKey == null)) {
|
|
26195
|
+
fail("--tls-cert and --tls-key must be provided together", 2);
|
|
26196
|
+
}
|
|
25192
26197
|
let s2;
|
|
25193
26198
|
try {
|
|
25194
26199
|
s2 = startServer({
|
|
@@ -25198,13 +26203,14 @@ if (argv2.includes("--server")) {
|
|
|
25198
26203
|
token: flagValue(argv2, "token") ?? process.env.METAHUB_TOKEN,
|
|
25199
26204
|
syncIntervalMs: intervalFlag != null ? parseDuration(intervalFlag, 30000) : undefined,
|
|
25200
26205
|
autoSync: argv2.includes("--no-auto-sync") ? false : undefined,
|
|
26206
|
+
tls: tlsCert != null && tlsKey != null ? { certPath: tlsCert, keyPath: tlsKey } : undefined,
|
|
25201
26207
|
ui: { serveAssets: serveWebui, routes: webuiRoutes }
|
|
25202
26208
|
});
|
|
25203
26209
|
warmWebui();
|
|
25204
26210
|
} catch (e3) {
|
|
25205
26211
|
fail(e3 instanceof Error ? e3.message : String(e3), errorCode(e3) ?? 1);
|
|
25206
26212
|
}
|
|
25207
|
-
const endpoints = resolveEndpoints(s2.host, s2.port);
|
|
26213
|
+
const endpoints = resolveEndpoints(s2.host, s2.port, undefined, s2.tls ? "https" : "http");
|
|
25208
26214
|
const webui = endpoints[0].url;
|
|
25209
26215
|
const docs = `${webui}/docs`;
|
|
25210
26216
|
print({
|
|
@@ -25237,4 +26243,4 @@ if (argv2.includes("--server")) {
|
|
|
25237
26243
|
runMain(main, { showUsage: showUsage2 });
|
|
25238
26244
|
}
|
|
25239
26245
|
|
|
25240
|
-
//# debugId=
|
|
26246
|
+
//# debugId=0ECC27D7190891F564756E2164756E21
|