datanotes 3.4.0 → 3.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/extension-columns.d.cts +64 -0
- package/dist/api/extension-columns.d.ts +64 -0
- package/dist/api/extension-plan.d.cts +1 -1
- package/dist/api/extension-plan.d.ts +1 -1
- package/dist/datanotes.cjs +2251 -1834
- package/dist/docs/administration/extensions.md +2 -1
- package/dist/docs/api/operations/identify_session.md +1 -0
- package/dist/docs/api/operations/record_tool_call.md +1 -0
- package/dist/docs/api.md +1 -1
- package/dist/docs/develop/write-an-extension.md +8 -2
- package/dist/docs/guides/data-quality/check-constraints.md +10 -10
- package/dist/docs/guides/data-quality/hand-edits.md +8 -8
- package/dist/docs/guides/database-objects/bases.md +11 -11
- package/dist/docs/guides/database-objects/catalogs-and-schemas.md +10 -10
- package/dist/docs/guides/database-objects/columns.md +39 -39
- package/dist/docs/guides/database-objects/relations.md +28 -28
- package/dist/docs/guides/database-objects/tables.md +26 -26
- package/dist/docs/guides/database-objects/views.md +19 -19
- package/dist/docs/guides/history/change-data-feed.md +34 -34
- package/dist/docs/guides/history/journal-and-undo.md +12 -12
- package/dist/docs/guides/history/restore.md +20 -20
- package/dist/docs/guides/history/table-history.md +31 -31
- package/dist/docs/guides/read-and-write/automatic-links.md +15 -15
- package/dist/docs/guides/read-and-write/import-notes.md +20 -20
- package/dist/docs/guides/read-and-write/write-rows.md +10 -10
- package/dist/docs/guides/search/text.md +1 -1
- package/dist/docs/guides/security/tokens.md +17 -4
- package/dist/docs/guides/security/who-did-what.md +8 -0
- package/dist/docs/reference/cli.md +1 -1
- package/dist/docs/release-notes.md +28 -0
- package/package.json +1 -1
package/dist/datanotes.cjs
CHANGED
|
@@ -26440,7 +26440,9 @@ var init_hints = __esm({
|
|
|
26440
26440
|
manageTokens: "/guides/security/tokens.md#manage-your-tokens",
|
|
26441
26441
|
tokenAutolink: "/guides/security/tokens.md#turn-automatic-links-off-for-a-sync-program",
|
|
26442
26442
|
tokenRead: "/guides/security/tokens.md#limit-what-a-token-can-read",
|
|
26443
|
+
tokenWrite: "/guides/security/tokens.md#a-write-into-another-table-needs-write-too",
|
|
26443
26444
|
reportAgents: "/guides/security/who-did-what.md#report-what-an-agent-does",
|
|
26445
|
+
sessionOwner: "/guides/security/who-did-what.md#a-session-belongs-to-one-token",
|
|
26444
26446
|
auth: "/develop/http-api.md#authenticate-your-requests",
|
|
26445
26447
|
routes: "/develop/http-api.md#call-the-engines-routes",
|
|
26446
26448
|
store: "/administration/run-the-service.md#where-the-engine-keeps-its-store",
|
|
@@ -29755,2004 +29757,2088 @@ var init_inverse = __esm({
|
|
|
29755
29757
|
}
|
|
29756
29758
|
});
|
|
29757
29759
|
|
|
29758
|
-
// src/core/
|
|
29759
|
-
function
|
|
29760
|
+
// src/core/changeset.ts
|
|
29761
|
+
function createdHash(text5) {
|
|
29760
29762
|
try {
|
|
29761
|
-
readOrderedFromMarkdown(text5);
|
|
29762
|
-
return
|
|
29763
|
-
} catch
|
|
29764
|
-
return
|
|
29765
|
-
}
|
|
29766
|
-
}
|
|
29767
|
-
function rawFrontmatterValue(text5, key2) {
|
|
29768
|
-
const split2 = splitFrontmatter(text5);
|
|
29769
|
-
if (!split2) return void 0;
|
|
29770
|
-
for (const line of split2.yamlText.split(/\r?\n/)) {
|
|
29771
|
-
const head = [`${key2}:`, `"${key2}":`, `'${key2}':`].find((h) => line.startsWith(h));
|
|
29772
|
-
if (!head) continue;
|
|
29773
|
-
const rest = line.slice(head.length).trim();
|
|
29774
|
-
try {
|
|
29775
|
-
return (0, import_yaml3.parse)(rest);
|
|
29776
|
-
} catch {
|
|
29777
|
-
return rest;
|
|
29778
|
-
}
|
|
29779
|
-
}
|
|
29780
|
-
return void 0;
|
|
29781
|
-
}
|
|
29782
|
-
function unreadableMessage(error2) {
|
|
29783
|
-
return `the front matter cannot be read, so the row is left out of every read: ${error2.split("\n")[0].trim()}`;
|
|
29784
|
-
}
|
|
29785
|
-
function excludedFolders(model, settings) {
|
|
29786
|
-
const out2 = /* @__PURE__ */ new Map();
|
|
29787
|
-
for (const { ref: ref2, entry } of catalogTables(model)) {
|
|
29788
|
-
try {
|
|
29789
|
-
const options = parseTableOptions(entryToOrdered(ref2, entry, settings).find(([k]) => k === TABLE_KEY)?.[1]);
|
|
29790
|
-
out2.set(ref2.full, [...options.exclude ?? [], catalogLayout(normalizeDatabaseFolder(settings.databaseFolder), ref2, options).archive]);
|
|
29791
|
-
} catch {
|
|
29792
|
-
}
|
|
29793
|
-
}
|
|
29794
|
-
return out2;
|
|
29795
|
-
}
|
|
29796
|
-
function unreadableRowOf(model, settings, path23, text5, excluded) {
|
|
29797
|
-
if (!text5.startsWith("---")) return null;
|
|
29798
|
-
const error2 = frontmatterError(text5);
|
|
29799
|
-
if (error2 === null) return null;
|
|
29800
|
-
const table = rowTableFull(model, rawFrontmatterValue(text5, settings.tableKey));
|
|
29801
|
-
if (!table) return null;
|
|
29802
|
-
const folders = (excluded ?? excludedFolders(model, settings)).get(table) ?? [];
|
|
29803
|
-
if (folders.some((dir) => path23.startsWith(`${dir}/`))) return null;
|
|
29804
|
-
return { path: path23, table, error: error2 };
|
|
29805
|
-
}
|
|
29806
|
-
async function unreadableRows(app, settings, model, table) {
|
|
29807
|
-
const excluded = excludedFolders(model, settings);
|
|
29808
|
-
const out2 = [];
|
|
29809
|
-
const idx = vaultIndex(app, settings);
|
|
29810
|
-
const notes = idx ? [...idx.allNotes()].filter((n) => n.bare).map((n) => n.file) : app.vault.getMarkdownFiles();
|
|
29811
|
-
for (const f of notes) {
|
|
29812
|
-
let cached2;
|
|
29813
|
-
try {
|
|
29814
|
-
cached2 = idx ? void 0 : app.metadataCache.getFileCache(f)?.frontmatter;
|
|
29815
|
-
} catch {
|
|
29816
|
-
cached2 = void 0;
|
|
29817
|
-
}
|
|
29818
|
-
if (cached2) continue;
|
|
29819
|
-
let text5;
|
|
29820
|
-
try {
|
|
29821
|
-
text5 = (await readHashed(app, f)).content;
|
|
29822
|
-
} catch {
|
|
29823
|
-
continue;
|
|
29824
|
-
}
|
|
29825
|
-
const u = unreadableRowOf(model, settings, f.path, text5, excluded);
|
|
29826
|
-
if (u && (table === void 0 || u.table === table)) out2.push(u);
|
|
29763
|
+
const { ordered, body } = readOrderedFromMarkdown(text5);
|
|
29764
|
+
return contentHash(JSON.stringify([ordered, body]));
|
|
29765
|
+
} catch {
|
|
29766
|
+
return contentHash(text5);
|
|
29827
29767
|
}
|
|
29828
|
-
return out2.sort((a, b) => a.path.localeCompare(b.path));
|
|
29829
29768
|
}
|
|
29830
|
-
|
|
29831
|
-
|
|
29832
|
-
"src/core/unreadable.ts"() {
|
|
29833
|
-
"use strict";
|
|
29834
|
-
init_file_cache();
|
|
29835
|
-
import_yaml3 = __toESM(require_dist(), 1);
|
|
29836
|
-
init_frontmatter();
|
|
29837
|
-
init_metastore();
|
|
29838
|
-
init_table_loader();
|
|
29839
|
-
init_types_table();
|
|
29840
|
-
init_message();
|
|
29841
|
-
init_db_paths();
|
|
29842
|
-
init_vault_index();
|
|
29843
|
-
}
|
|
29844
|
-
});
|
|
29845
|
-
|
|
29846
|
-
// src/core/log-files.ts
|
|
29847
|
-
function holdsVersions(path23) {
|
|
29848
|
-
return COMMIT_RE.test(path23) || SEGMENT_RE.test(path23);
|
|
29769
|
+
function bodyChanges(e) {
|
|
29770
|
+
return e.bodyAfter !== void 0 && e.bodyAfter !== e.bodyBefore;
|
|
29849
29771
|
}
|
|
29850
|
-
function
|
|
29851
|
-
|
|
29852
|
-
|
|
29853
|
-
return m;
|
|
29772
|
+
function bodyOf(text5) {
|
|
29773
|
+
const split2 = splitFrontmatter(text5);
|
|
29774
|
+
return split2 ? split2.body : text5;
|
|
29854
29775
|
}
|
|
29855
|
-
function
|
|
29856
|
-
|
|
29776
|
+
function replaceBody(text5, body) {
|
|
29777
|
+
const split2 = splitFrontmatter(text5);
|
|
29778
|
+
if (!split2) return body;
|
|
29779
|
+
return text5.slice(0, text5.length - split2.body.length) + body;
|
|
29857
29780
|
}
|
|
29858
|
-
function
|
|
29859
|
-
|
|
29860
|
-
|
|
29861
|
-
|
|
29862
|
-
|
|
29863
|
-
if (nl < 0) break;
|
|
29864
|
-
let head;
|
|
29865
|
-
try {
|
|
29866
|
-
head = JSON.parse(text5.slice(pos, nl)).commit;
|
|
29867
|
-
} catch {
|
|
29868
|
-
break;
|
|
29869
|
-
}
|
|
29870
|
-
if (!head || !Number.isInteger(head.version) || !Number.isInteger(head.lines) || head.lines < 1) break;
|
|
29871
|
-
let end = nl + 1;
|
|
29872
|
-
let whole = true;
|
|
29873
|
-
for (let i = 0; i < head.lines; i++) {
|
|
29874
|
-
const e = text5.indexOf("\n", end);
|
|
29875
|
-
if (e < 0) {
|
|
29876
|
-
whole = false;
|
|
29877
|
-
break;
|
|
29878
|
-
}
|
|
29879
|
-
end = e + 1;
|
|
29880
|
-
}
|
|
29881
|
-
if (!whole) break;
|
|
29882
|
-
if (typeof head.hash === "string" && contentHash(text5.slice(nl + 1, end)) !== head.hash) break;
|
|
29883
|
-
const prev = commits[commits.length - 1];
|
|
29884
|
-
if (prev && head.version !== prev.version + 1) break;
|
|
29885
|
-
commits.push({ version: head.version, start: nl + 1, end });
|
|
29886
|
-
pos = end;
|
|
29887
|
-
}
|
|
29888
|
-
return { commits, validEnd: pos };
|
|
29781
|
+
async function writeBody(app, file, expected, next) {
|
|
29782
|
+
await app.vault.process(file, (text5) => {
|
|
29783
|
+
if (bodyOf(text5) !== expected) throw new Error("note body changed since it was read");
|
|
29784
|
+
return replaceBody(text5, next);
|
|
29785
|
+
});
|
|
29889
29786
|
}
|
|
29890
|
-
function
|
|
29891
|
-
|
|
29892
|
-
|
|
29893
|
-
|
|
29787
|
+
function emptyChangeSet(op2, summary, schema) {
|
|
29788
|
+
return {
|
|
29789
|
+
op: op2,
|
|
29790
|
+
summary,
|
|
29791
|
+
...schema !== void 0 ? { schema } : {},
|
|
29792
|
+
edits: [],
|
|
29793
|
+
creates: [],
|
|
29794
|
+
moves: [],
|
|
29795
|
+
deletes: [],
|
|
29796
|
+
texts: []
|
|
29797
|
+
};
|
|
29894
29798
|
}
|
|
29895
|
-
|
|
29896
|
-
|
|
29897
|
-
if (!cache) SEGMENT_TEXTS.set(app, cache = /* @__PURE__ */ new Map());
|
|
29898
|
-
const key2 = `${seg.path}\0${seg.sealed ? "" : `${seg.chars}/${seg.last}`}\0${storeGeneration(app, seg.path)}`;
|
|
29899
|
-
const hit = cache.get(key2);
|
|
29900
|
-
if (hit) {
|
|
29901
|
-
cache.delete(key2);
|
|
29902
|
-
cache.set(key2, hit);
|
|
29903
|
-
return hit;
|
|
29904
|
-
}
|
|
29905
|
-
const text5 = await storeReadFile(app, seg.path);
|
|
29906
|
-
const index = /* @__PURE__ */ new Map();
|
|
29907
|
-
for (const c of parseSegment(text5).commits) index.set(c.version, [c.start, c.end]);
|
|
29908
|
-
const out2 = { text: text5, index };
|
|
29909
|
-
cache.set(key2, out2);
|
|
29910
|
-
for (const k of cache.keys()) {
|
|
29911
|
-
if (cache.size <= SEGMENT_TEXTS_KEPT) break;
|
|
29912
|
-
cache.delete(k);
|
|
29913
|
-
}
|
|
29914
|
-
return out2;
|
|
29799
|
+
function same(a, b) {
|
|
29800
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
29915
29801
|
}
|
|
29916
|
-
|
|
29917
|
-
const
|
|
29918
|
-
const
|
|
29919
|
-
const
|
|
29920
|
-
|
|
29921
|
-
|
|
29922
|
-
|
|
29923
|
-
|
|
29924
|
-
|
|
29925
|
-
|
|
29926
|
-
}
|
|
29927
|
-
|
|
29928
|
-
|
|
29929
|
-
|
|
29930
|
-
|
|
29931
|
-
|
|
29932
|
-
|
|
29933
|
-
|
|
29934
|
-
|
|
29935
|
-
|
|
29936
|
-
|
|
29937
|
-
|
|
29938
|
-
|
|
29939
|
-
|
|
29940
|
-
for (const seg of toc.segments) for (let v = seg.first; v <= seg.last; v++) versions.add(v);
|
|
29941
|
-
toc.versions = [...versions].sort((a, b) => a - b);
|
|
29942
|
-
const newest = latestOf(toc);
|
|
29943
|
-
const file = toc.files.get(newest);
|
|
29944
|
-
if (file !== void 0 && !inSegment(toc, newest)) {
|
|
29945
|
-
const text5 = await storeRead(app, file);
|
|
29946
|
-
if (text5 === null || text5.trim() === "" || !wholeVersion(text5)) {
|
|
29947
|
-
toc.torn = file;
|
|
29948
|
-
toc.files.delete(newest);
|
|
29949
|
-
toc.versions.pop();
|
|
29802
|
+
function diffOrdered(before, after) {
|
|
29803
|
+
const b = new Map(before);
|
|
29804
|
+
const a = new Map(after);
|
|
29805
|
+
const d = {
|
|
29806
|
+
set: [],
|
|
29807
|
+
remove: [],
|
|
29808
|
+
beforeValues: {},
|
|
29809
|
+
beforeAbsent: [],
|
|
29810
|
+
afterValues: {},
|
|
29811
|
+
afterAbsent: []
|
|
29812
|
+
};
|
|
29813
|
+
const keys = /* @__PURE__ */ new Set([...b.keys(), ...a.keys()]);
|
|
29814
|
+
for (const k of keys) {
|
|
29815
|
+
const inB = b.has(k);
|
|
29816
|
+
const inA = a.has(k);
|
|
29817
|
+
if (inB && inA && same(b.get(k), a.get(k))) continue;
|
|
29818
|
+
if (inB) d.beforeValues[k] = b.get(k);
|
|
29819
|
+
else d.beforeAbsent.push(k);
|
|
29820
|
+
if (inA) {
|
|
29821
|
+
d.afterValues[k] = a.get(k);
|
|
29822
|
+
d.set.push([k, a.get(k)]);
|
|
29823
|
+
} else {
|
|
29824
|
+
d.afterAbsent.push(k);
|
|
29825
|
+
d.remove.push(k);
|
|
29950
29826
|
}
|
|
29951
29827
|
}
|
|
29952
|
-
return
|
|
29953
|
-
}
|
|
29954
|
-
function wholeVersion(text5) {
|
|
29955
|
-
try {
|
|
29956
|
-
const lines = text5.split("\n").filter((l) => l.trim() !== "");
|
|
29957
|
-
if (lines.length === 0 || !JSON.parse(lines[0]).commitInfo) return false;
|
|
29958
|
-
for (const l of lines.slice(1)) JSON.parse(l);
|
|
29959
|
-
return true;
|
|
29960
|
-
} catch {
|
|
29961
|
-
return false;
|
|
29962
|
-
}
|
|
29963
|
-
}
|
|
29964
|
-
function inSegment(toc, v) {
|
|
29965
|
-
for (let i = toc.segments.length - 1; i >= 0; i--) {
|
|
29966
|
-
const s = toc.segments[i];
|
|
29967
|
-
if (v >= s.first && v <= s.last) return s;
|
|
29968
|
-
}
|
|
29969
|
-
return void 0;
|
|
29970
|
-
}
|
|
29971
|
-
async function logToc(app, dir) {
|
|
29972
|
-
const tocs = tocsOf(app);
|
|
29973
|
-
const kept2 = tocs.get(dir);
|
|
29974
|
-
if (kept2 && kept2.gen === storeGeneration(app, dir)) {
|
|
29975
|
-
const stat = await storeStat(app, dir);
|
|
29976
|
-
if (stat !== void 0 ? (stat ? stat.mtime : null) === kept2.mtime : await stillNewest(app, kept2)) return kept2;
|
|
29977
|
-
}
|
|
29978
|
-
const toc = await buildToc(app, dir);
|
|
29979
|
-
tocs.set(dir, toc);
|
|
29980
|
-
return toc;
|
|
29981
|
-
}
|
|
29982
|
-
async function stillNewest(app, toc) {
|
|
29983
|
-
const latest = latestOf(toc);
|
|
29984
|
-
const where = latest < 0 ? null : inSegment(toc, latest)?.path ?? toc.files.get(latest);
|
|
29985
|
-
if (where !== null && !await storeExists(app, where)) return false;
|
|
29986
|
-
const next = commitPath(toc.dir, latest + 1);
|
|
29987
|
-
if (next !== toc.torn && await storeExists(app, next)) return false;
|
|
29988
|
-
return !await storeExists(app, segmentPath(toc.dir, latest + 1));
|
|
29989
|
-
}
|
|
29990
|
-
async function settled(app, toc) {
|
|
29991
|
-
toc.gen = storeGeneration(app, toc.dir);
|
|
29992
|
-
const stat = await storeStat(app, toc.dir);
|
|
29993
|
-
toc.mtime = stat ? stat.mtime : null;
|
|
29828
|
+
return d;
|
|
29994
29829
|
}
|
|
29995
|
-
|
|
29996
|
-
const
|
|
29997
|
-
|
|
29998
|
-
|
|
29999
|
-
|
|
30000
|
-
if (at) return s.text.slice(at[0], at[1]);
|
|
30001
|
-
}
|
|
30002
|
-
const file = toc.files.get(v);
|
|
30003
|
-
if (file !== void 0) return storeReadFile(app, file);
|
|
30004
|
-
if (segments.length === 0) throw new HistoryError(`${toc.dir} has no version ${v}`);
|
|
30005
|
-
throw new HistoryError(
|
|
30006
|
-
`${segments[0].path} does not hold version ${v} whole`,
|
|
30007
|
-
guide(`A crash lost version ${v}: no file of the log holds it whole. Restore the store from its backup (datanotes store restore --from <backup url> --database <folder> --force), then run the command again.`, "restoreStore")
|
|
29830
|
+
function editIsNoop(e) {
|
|
29831
|
+
const d = diffOrdered(e.before, e.after);
|
|
29832
|
+
const orderChanged = !same(
|
|
29833
|
+
e.before.map(([k]) => k),
|
|
29834
|
+
e.after.map(([k]) => k)
|
|
30008
29835
|
);
|
|
29836
|
+
return d.set.length === 0 && d.remove.length === 0 && !orderChanged && !bodyChanges(e);
|
|
30009
29837
|
}
|
|
30010
|
-
|
|
30011
|
-
|
|
29838
|
+
function applyDiff(current2, set, remove3, order) {
|
|
29839
|
+
let out2 = current2;
|
|
29840
|
+
for (const k of remove3) out2 = fmDelete(out2, k);
|
|
29841
|
+
for (const [k, v] of set) out2 = fmSet(out2, k, v);
|
|
29842
|
+
return fmReorder(out2, order);
|
|
30012
29843
|
}
|
|
30013
|
-
function
|
|
30014
|
-
|
|
29844
|
+
function fileOrNull(app, path23) {
|
|
29845
|
+
const f = app.vault.getAbstractFileByPath(path23);
|
|
29846
|
+
return f != null && "extension" in f ? f : null;
|
|
30015
29847
|
}
|
|
30016
|
-
|
|
30017
|
-
const
|
|
30018
|
-
|
|
30019
|
-
for (let i = 0; i < versions.length; i++) {
|
|
30020
|
-
const v = versions[i];
|
|
30021
|
-
if (inSegment(toc, v)) out2[i] = await versionText(app, toc, v);
|
|
30022
|
-
else files.push(i);
|
|
30023
|
-
}
|
|
30024
|
-
for (let i = 0; i < files.length; i += READ_AHEAD) {
|
|
30025
|
-
const chunk = files.slice(i, i + READ_AHEAD);
|
|
30026
|
-
const texts = await Promise.all(chunk.map((j) => versionText(app, toc, versions[j])));
|
|
30027
|
-
texts.forEach((t, k) => out2[chunk[k]] = t);
|
|
30028
|
-
}
|
|
30029
|
-
return out2;
|
|
29848
|
+
function parentFolder(path23) {
|
|
29849
|
+
const i = path23.lastIndexOf("/");
|
|
29850
|
+
return i < 0 ? "" : path23.slice(0, i);
|
|
30030
29851
|
}
|
|
30031
|
-
async function
|
|
30032
|
-
|
|
30033
|
-
|
|
30034
|
-
|
|
29852
|
+
async function ensureFolder(app, folder) {
|
|
29853
|
+
if (folder === "") return;
|
|
29854
|
+
if (app.vault.getAbstractFileByPath(folder) != null) return;
|
|
29855
|
+
await ensureFolder(app, parentFolder(folder));
|
|
29856
|
+
await app.vault.createFolder(folder);
|
|
30035
29857
|
}
|
|
30036
|
-
async function
|
|
30037
|
-
|
|
30038
|
-
if (latestOf(toc) !== v - 1) {
|
|
30039
|
-
const file = toc.files.get(v);
|
|
30040
|
-
throw new HistoryError(file !== void 0 ? `log file exists: ${file}` : `log moved on: ${dir} is at version ${latestOf(toc)}, not ${v - 1}`);
|
|
30041
|
-
}
|
|
30042
|
-
const repaired = [];
|
|
30043
|
-
if (toc.torn) {
|
|
30044
|
-
repaired.push(`${toc.torn} set aside as ${await setAside(app, toc.torn)}`);
|
|
30045
|
-
toc.torn = null;
|
|
30046
|
-
}
|
|
30047
|
-
for (const seg of toc.segments) {
|
|
30048
|
-
if (seg.sealed || seg.tornAt === void 0 && seg.chars > 0) continue;
|
|
30049
|
-
const whole = (await storeReadFile(app, seg.path)).slice(0, seg.tornAt ?? 0);
|
|
30050
|
-
repaired.push(`${seg.path}: ${seg.chars - whole.length} characters of a torn version cut${whole === "" ? ", the file removed" : ""}`);
|
|
30051
|
-
if (whole === "") await storeRemove(app, seg.path);
|
|
30052
|
-
else await storeWrite(app, seg.path, whole);
|
|
30053
|
-
seg.chars = whole.length;
|
|
30054
|
-
delete seg.tornAt;
|
|
30055
|
-
const st = await storeStat(app, seg.path);
|
|
30056
|
-
if (st) seg.bytes = st.size;
|
|
30057
|
-
}
|
|
30058
|
-
toc.segments = toc.segments.filter((s) => s.sealed || s.chars > 0);
|
|
30059
|
-
if (format === "files") {
|
|
30060
|
-
const path23 = commitPath(dir, v);
|
|
30061
|
-
if (await storeExists(app, path23)) throw new HistoryError(`log file exists: ${path23}`);
|
|
30062
|
-
await storeWrite(app, path23, text5);
|
|
30063
|
-
toc.files.set(v, path23);
|
|
30064
|
-
} else await appendToSegment(app, toc, v, text5);
|
|
30065
|
-
toc.versions.push(v);
|
|
30066
|
-
await settled(app, toc);
|
|
30067
|
-
return repaired;
|
|
29858
|
+
async function readOrderedFor(app, file) {
|
|
29859
|
+
return readOrderedFromMarkdown(await app.vault.read(file)).ordered;
|
|
30068
29860
|
}
|
|
30069
|
-
async function
|
|
30070
|
-
|
|
30071
|
-
const open2 = toc.segments.filter((s) => !s.sealed);
|
|
30072
|
-
const active = open2[open2.length - 1];
|
|
30073
|
-
const fits = active && active.last === v - 1 && active.chars < SEGMENT_POLICY.maxBytes && active.last - active.first + 1 < SEGMENT_POLICY.maxVersions;
|
|
30074
|
-
for (const seg of open2) if (!(fits && seg === active)) await seal(app, toc, seg);
|
|
30075
|
-
if (fits) {
|
|
30076
|
-
const st2 = await storeStat(app, active.path);
|
|
30077
|
-
if (st2 && active.bytes !== void 0 && st2.size !== active.bytes) throw new HistoryError(`log moved on: ${active.path} changed`);
|
|
30078
|
-
await storeAppend(app, active.path, chunk);
|
|
30079
|
-
active.last = v;
|
|
30080
|
-
active.chars += chunk.length;
|
|
30081
|
-
const after = await storeStat(app, active.path);
|
|
30082
|
-
if (after) active.bytes = after.size;
|
|
30083
|
-
return;
|
|
30084
|
-
}
|
|
30085
|
-
const path23 = segmentPath(toc.dir, v);
|
|
30086
|
-
if (await storeExists(app, path23)) throw new HistoryError(`log file exists: ${path23}`);
|
|
30087
|
-
await storeWrite(app, path23, chunk);
|
|
30088
|
-
const st = await storeStat(app, path23);
|
|
30089
|
-
toc.segments.push({ path: path23, first: v, last: v, sealed: false, chars: chunk.length, ...st ? { bytes: st.size } : {} });
|
|
29861
|
+
async function fileExists(app, path23) {
|
|
29862
|
+
return inStore(path23) ? storeExists(app, path23) : fileOrNull(app, path23) != null;
|
|
30090
29863
|
}
|
|
30091
|
-
async function
|
|
30092
|
-
|
|
30093
|
-
await storeRename(app, seg.path, to);
|
|
30094
|
-
seg.path = to;
|
|
30095
|
-
seg.sealed = true;
|
|
30096
|
-
delete seg.bytes;
|
|
30097
|
-
delete seg.chars;
|
|
29864
|
+
async function occupied(app, path23) {
|
|
29865
|
+
return inStore(path23) ? storeExists(app, path23) : app.vault.getAbstractFileByPath(path23) != null;
|
|
30098
29866
|
}
|
|
30099
|
-
async function
|
|
29867
|
+
async function storeFilesSince(app, moves) {
|
|
29868
|
+
const folders = /* @__PURE__ */ new Map();
|
|
29869
|
+
for (const m of moves) if (inStore(m.from) && inStore(m.to)) folders.set(parentFolder(m.to), parentFolder(m.from));
|
|
29870
|
+
const moved = new Set(moves.map((m) => m.to));
|
|
30100
29871
|
const out2 = [];
|
|
30101
|
-
|
|
30102
|
-
|
|
30103
|
-
|
|
30104
|
-
|
|
30105
|
-
const path23 = segmentPath(dir, run2[0].version, run2[run2.length - 1].version);
|
|
30106
|
-
if (await holdsWhole(app, path23, run2.map((r) => r.version))) storeSyncNext(app, path23);
|
|
30107
|
-
else await storeWrite(app, path23, run2.map((r) => framed(r.version, r.text)).join(""));
|
|
30108
|
-
out2.push(path23);
|
|
30109
|
-
run2 = [];
|
|
30110
|
-
chars = 0;
|
|
30111
|
-
};
|
|
30112
|
-
for (const r of versions) {
|
|
30113
|
-
const last = run2[run2.length - 1];
|
|
30114
|
-
if (last && (r.version !== last.version + 1 || chars >= SEGMENT_POLICY.maxBytes || run2.length >= SEGMENT_POLICY.maxVersions)) await flush2();
|
|
30115
|
-
run2.push(r);
|
|
30116
|
-
chars += r.text.length;
|
|
29872
|
+
for (const [to, from] of folders) {
|
|
29873
|
+
for (const f of (await storeList(app, to)).files) {
|
|
29874
|
+
if (!moved.has(f)) out2.push({ from: `${from}/${f.slice(to.length + 1)}`, to: f });
|
|
29875
|
+
}
|
|
30117
29876
|
}
|
|
30118
|
-
await flush2();
|
|
30119
29877
|
return out2;
|
|
30120
29878
|
}
|
|
30121
|
-
|
|
30122
|
-
|
|
30123
|
-
if (text5 === null) return false;
|
|
30124
|
-
const { commits, validEnd } = parseSegment(text5);
|
|
30125
|
-
return validEnd === text5.length && commits.length === versions.length && commits.every((c, i) => c.version === versions[i]);
|
|
30126
|
-
}
|
|
30127
|
-
async function gzip(text5) {
|
|
30128
|
-
const stream = new Blob([text5]).stream().pipeThrough(new CompressionStream("gzip"));
|
|
30129
|
-
return new Response(stream).arrayBuffer();
|
|
30130
|
-
}
|
|
30131
|
-
async function gunzip(data) {
|
|
30132
|
-
const stream = new Blob([data]).stream().pipeThrough(new DecompressionStream("gzip"));
|
|
30133
|
-
return new Response(stream).text();
|
|
29879
|
+
function atOnceFor(count2) {
|
|
29880
|
+
return count2 >= WRITE_CONCURRENCY.from ? WRITE_CONCURRENCY.atOnce : 1;
|
|
30134
29881
|
}
|
|
30135
|
-
function
|
|
30136
|
-
const
|
|
30137
|
-
let
|
|
30138
|
-
let
|
|
30139
|
-
|
|
30140
|
-
|
|
30141
|
-
|
|
30142
|
-
|
|
30143
|
-
|
|
29882
|
+
async function inOrder(items3, pathOf, fn, limit = atOnceFor(items3.length)) {
|
|
29883
|
+
const results = new Array(items3.length);
|
|
29884
|
+
let next = 0;
|
|
29885
|
+
let stop = false;
|
|
29886
|
+
const worker = async () => {
|
|
29887
|
+
while (!stop && next < items3.length) {
|
|
29888
|
+
const i = next++;
|
|
29889
|
+
try {
|
|
29890
|
+
results[i] = { ok: true, written: await fn(items3[i]) };
|
|
29891
|
+
} catch (e) {
|
|
29892
|
+
results[i] = { ok: false, error: messageOf(e) };
|
|
29893
|
+
stop = true;
|
|
29894
|
+
}
|
|
29895
|
+
}
|
|
29896
|
+
};
|
|
29897
|
+
await Promise.all(Array.from({ length: Math.min(limit, items3.length) }, worker));
|
|
29898
|
+
const first2 = results.findIndex((r) => r !== void 0 && !r.ok);
|
|
29899
|
+
const kept2 = /* @__PURE__ */ new Set();
|
|
29900
|
+
if (first2 >= 0) {
|
|
29901
|
+
for (let i = results.length - 1; i > first2; i--) {
|
|
29902
|
+
const r = results[i];
|
|
29903
|
+
if (!r?.ok) continue;
|
|
29904
|
+
try {
|
|
29905
|
+
await r.written.undo();
|
|
29906
|
+
} catch {
|
|
29907
|
+
kept2.add(i);
|
|
29908
|
+
}
|
|
30144
29909
|
}
|
|
30145
|
-
cur.push(l);
|
|
30146
|
-
chars += l.length + 1;
|
|
30147
29910
|
}
|
|
30148
|
-
|
|
30149
|
-
|
|
29911
|
+
const done = [];
|
|
29912
|
+
results.forEach((r, i) => {
|
|
29913
|
+
if (r?.ok && (first2 < 0 || i < first2 || kept2.has(i))) done.push(r.written.value);
|
|
29914
|
+
});
|
|
29915
|
+
const f = first2 >= 0 ? results[first2] : null;
|
|
29916
|
+
return { done, failed: f ? { path: pathOf(items3[first2]), error: f.error } : null };
|
|
30150
29917
|
}
|
|
30151
|
-
async function
|
|
30152
|
-
|
|
30153
|
-
|
|
30154
|
-
|
|
30155
|
-
|
|
30156
|
-
|
|
30157
|
-
|
|
30158
|
-
const parts = chunked(textLines);
|
|
30159
|
-
let row = 0;
|
|
30160
|
-
const partNames = [];
|
|
30161
|
-
for (const [i, lines] of parts.entries()) {
|
|
30162
|
-
const name = `${pad2(head.version)}.part-${i}.jsonl${binary ? ".gz" : ""}`;
|
|
30163
|
-
const text5 = lines.length > 0 ? `${lines.join("\n")}
|
|
30164
|
-
` : "";
|
|
30165
|
-
if (binary) await storeWriteBinary(app, `${dir}/${name}`, await gzip(text5));
|
|
30166
|
-
else await storeWrite(app, `${dir}/${name}`, text5);
|
|
30167
|
-
written2.push(`${dir}/${name}`);
|
|
30168
|
-
partNames.push(name);
|
|
30169
|
-
for (let k = 0; k < lines.length; k++) partOf[row++] = i;
|
|
30170
|
-
}
|
|
30171
|
-
const entries = rows.map((r, i) => JSON.stringify([r.path, r.hash, r.content.length, partOf[i]]));
|
|
30172
|
-
const pieces = chunked(entries);
|
|
30173
|
-
const more = pieces.slice(1).map((_, i) => `${pad2(head.version)}.manifest-${i + 1}.jsonl`);
|
|
30174
|
-
for (const [i, lines] of pieces.slice(1).entries()) {
|
|
30175
|
-
await storeWrite(app, `${dir}/${more[i]}`, `${lines.join("\n")}
|
|
30176
|
-
`);
|
|
30177
|
-
written2.push(`${dir}/${more[i]}`);
|
|
30178
|
-
}
|
|
30179
|
-
const header2 = { format: 2, version: head.version, timestamp: head.timestamp, entry: head.entry, ...head.moves ? { moves: head.moves } : {}, rows: rows.length, parts: partNames, ...more.length > 0 ? { more } : {} };
|
|
30180
|
-
await storeWrite(app, main6, `${JSON.stringify({ checkpoint: header2 })}
|
|
30181
|
-
${pieces[0].length > 0 ? `${pieces[0].join("\n")}
|
|
30182
|
-
` : ""}`);
|
|
30183
|
-
written2.push(main6);
|
|
30184
|
-
return written2;
|
|
29918
|
+
async function moveFile(app, from, to) {
|
|
29919
|
+
if (inStore(from)) return storeRename(app, from, to);
|
|
29920
|
+
const f = fileOrNull(app, from);
|
|
29921
|
+
if (!f) throw new Error(`file not found: ${from}`);
|
|
29922
|
+
if (app.vault.getAbstractFileByPath(to) != null) throw new Error(`target exists: ${to}`);
|
|
29923
|
+
await ensureFolder(app, parentFolder(to));
|
|
29924
|
+
await app.fileManager.renameFile(f, to);
|
|
30185
29925
|
}
|
|
30186
|
-
async function
|
|
30187
|
-
|
|
30188
|
-
|
|
30189
|
-
|
|
30190
|
-
|
|
30191
|
-
|
|
30192
|
-
|
|
30193
|
-
|
|
30194
|
-
|
|
30195
|
-
|
|
30196
|
-
|
|
30197
|
-
|
|
29926
|
+
async function applyChangeSetImpl(app, settings, cs, now = /* @__PURE__ */ new Date()) {
|
|
29927
|
+
const rows = [];
|
|
29928
|
+
const creates = [];
|
|
29929
|
+
const moves = [];
|
|
29930
|
+
const references = [];
|
|
29931
|
+
const deleted = [];
|
|
29932
|
+
const failed2 = [];
|
|
29933
|
+
let referencesUpdated = 0;
|
|
29934
|
+
const fail2 = (path23, e) => {
|
|
29935
|
+
failed2.push({ path: path23, error: messageOf(e) });
|
|
29936
|
+
};
|
|
29937
|
+
run: {
|
|
29938
|
+
const folders = /* @__PURE__ */ new Map();
|
|
29939
|
+
const folderOnce = (folder) => {
|
|
29940
|
+
if (folder === "") return Promise.resolve();
|
|
29941
|
+
let p = folders.get(folder);
|
|
29942
|
+
if (!p) {
|
|
29943
|
+
p = folderOnce(parentFolder(folder)).then(async () => {
|
|
29944
|
+
if (app.vault.getAbstractFileByPath(folder) == null) await app.vault.createFolder(folder);
|
|
29945
|
+
});
|
|
29946
|
+
folders.set(folder, p);
|
|
29947
|
+
}
|
|
29948
|
+
return p;
|
|
29949
|
+
};
|
|
29950
|
+
const created = await inOrder(cs.creates, (c) => c.path, async (c) => {
|
|
29951
|
+
if (app.vault.getAbstractFileByPath(c.path) != null) throw new Error(`file exists: ${c.path}`);
|
|
29952
|
+
await folderOnce(parentFolder(c.path));
|
|
29953
|
+
const file = await app.vault.create(c.path, c.content);
|
|
29954
|
+
return { value: c.path, undo: () => app.vault.delete(file, true) };
|
|
29955
|
+
});
|
|
29956
|
+
creates.push(...created.done);
|
|
29957
|
+
if (created.failed) {
|
|
29958
|
+
failed2.push(created.failed);
|
|
29959
|
+
break run;
|
|
29960
|
+
}
|
|
29961
|
+
for (const t of cs.texts) {
|
|
29962
|
+
try {
|
|
29963
|
+
const existing = app.vault.getAbstractFileByPath(t.path);
|
|
29964
|
+
if (existing == null) {
|
|
29965
|
+
if (t.before !== "") throw new Error("file not found");
|
|
29966
|
+
if (t.path === metastorePath(settings)) {
|
|
29967
|
+
t.after = withNewDatabaseConfig(t.after);
|
|
29968
|
+
if (settings.databaseId) t.after = withMetastoreId(t.after, settings.databaseId);
|
|
29969
|
+
}
|
|
29970
|
+
await ensureFolder(app, parentFolder(t.path));
|
|
29971
|
+
await app.vault.create(t.path, t.after);
|
|
29972
|
+
} else {
|
|
29973
|
+
await app.vault.process(existing, (current2) => {
|
|
29974
|
+
if (current2 !== t.before) throw new Error("file changed since it was read");
|
|
29975
|
+
return t.after;
|
|
29976
|
+
});
|
|
29977
|
+
}
|
|
29978
|
+
references.push({ path: t.path, before_text: t.before, after_text: t.after });
|
|
29979
|
+
} catch (err) {
|
|
29980
|
+
fail2(t.path, err);
|
|
29981
|
+
break run;
|
|
29982
|
+
}
|
|
29983
|
+
}
|
|
29984
|
+
const edits = cs.edits.filter((e) => !editIsNoop(e));
|
|
29985
|
+
const distinct = new Set(edits.map((e) => e.path)).size === edits.length;
|
|
29986
|
+
const atOnce = distinct ? atOnceFor(edits.length) : 1;
|
|
29987
|
+
const edited = await inOrder(edits, (e) => e.path, async (e) => {
|
|
29988
|
+
const file = fileOrNull(app, e.path);
|
|
29989
|
+
if (!file) throw new Error("file not found");
|
|
29990
|
+
const was = atOnce > 1 ? (await readHashed(app, file)).content : "";
|
|
29991
|
+
const d = diffOrdered(e.before, e.after);
|
|
29992
|
+
const order = e.after.map(([k]) => k);
|
|
29993
|
+
const fmChanged = d.set.length > 0 || d.remove.length > 0 || !same(e.before.map(([k]) => k), order);
|
|
29994
|
+
if (fmChanged) {
|
|
29995
|
+
await writeFrontmatter(app, file, (fm) => applyDiff(fm, d.set, d.remove, order));
|
|
29996
|
+
}
|
|
29997
|
+
if (bodyChanges(e)) await writeBody(app, file, e.bodyBefore, e.bodyAfter);
|
|
29998
|
+
const row = {
|
|
29999
|
+
path: e.path,
|
|
30000
|
+
before: d.beforeValues,
|
|
30001
|
+
before_absent: d.beforeAbsent,
|
|
30002
|
+
before_order: e.before.map(([k]) => k),
|
|
30003
|
+
after: d.afterValues,
|
|
30004
|
+
after_absent: d.afterAbsent,
|
|
30005
|
+
...bodyChanges(e) ? { body_before: e.bodyBefore, body_after: e.bodyAfter } : {}
|
|
30006
|
+
};
|
|
30007
|
+
return { value: row, undo: async () => void await app.vault.process(file, () => was) };
|
|
30008
|
+
}, atOnce);
|
|
30009
|
+
rows.push(...edited.done);
|
|
30010
|
+
if (edited.failed) {
|
|
30011
|
+
failed2.push(edited.failed);
|
|
30012
|
+
break run;
|
|
30013
|
+
}
|
|
30014
|
+
for (const m of cs.moves) {
|
|
30015
|
+
try {
|
|
30016
|
+
await moveFile(app, m.from, m.to);
|
|
30017
|
+
moves.push(m);
|
|
30018
|
+
} catch (err) {
|
|
30019
|
+
fail2(m.from, err);
|
|
30020
|
+
break run;
|
|
30021
|
+
}
|
|
30022
|
+
}
|
|
30023
|
+
for (const p of cs.deletes) {
|
|
30024
|
+
const file = fileOrNull(app, p);
|
|
30025
|
+
try {
|
|
30026
|
+
if (!file) throw new Error("file not found");
|
|
30027
|
+
const content = await app.vault.read(file);
|
|
30028
|
+
await trash(app, file);
|
|
30029
|
+
deleted.push({ path: p, content });
|
|
30030
|
+
} catch (err) {
|
|
30031
|
+
fail2(p, err);
|
|
30032
|
+
break run;
|
|
30033
|
+
}
|
|
30198
30034
|
}
|
|
30199
|
-
if (rows.length !== head.rows) return null;
|
|
30200
|
-
return { head, rows };
|
|
30201
|
-
} catch {
|
|
30202
|
-
return null;
|
|
30203
30035
|
}
|
|
30204
|
-
|
|
30205
|
-
|
|
30206
|
-
const
|
|
30207
|
-
|
|
30208
|
-
|
|
30209
|
-
|
|
30210
|
-
}
|
|
30211
|
-
|
|
30212
|
-
try {
|
|
30213
|
-
let text5;
|
|
30214
|
-
if (name.endsWith(".gz")) {
|
|
30215
|
-
const data = await storeReadBinary(app, `${dir}/${name}`);
|
|
30216
|
-
text5 = data ? await gunzip(data) : null;
|
|
30217
|
-
} else text5 = await storeRead(app, `${dir}/${name}`);
|
|
30218
|
-
if (text5 == null) return null;
|
|
30219
|
-
const out2 = /* @__PURE__ */ new Map();
|
|
30220
|
-
for (const l of text5.split("\n")) {
|
|
30221
|
-
if (l === "") continue;
|
|
30222
|
-
const [path23, content] = JSON.parse(l);
|
|
30223
|
-
out2.set(path23, content);
|
|
30036
|
+
const createdHashes = [];
|
|
30037
|
+
const movedTo = new Map(moves.map((m) => [m.from, m.to]));
|
|
30038
|
+
for (const p of creates) {
|
|
30039
|
+
const f = fileOrNull(app, movedTo.get(p) ?? p);
|
|
30040
|
+
if (!f) continue;
|
|
30041
|
+
try {
|
|
30042
|
+
createdHashes.push({ path: p, hash: createdHash(await app.vault.read(f)) });
|
|
30043
|
+
} catch {
|
|
30224
30044
|
}
|
|
30225
|
-
return out2;
|
|
30226
|
-
} catch {
|
|
30227
|
-
return null;
|
|
30228
30045
|
}
|
|
30046
|
+
const journal = await writeJournal(
|
|
30047
|
+
app,
|
|
30048
|
+
settings,
|
|
30049
|
+
{
|
|
30050
|
+
op: cs.op,
|
|
30051
|
+
...cs.schema !== void 0 ? { schema: cs.schema } : {},
|
|
30052
|
+
...currentActor() ? { actor: currentActor() } : {},
|
|
30053
|
+
summary: cs.summary,
|
|
30054
|
+
executed_at: now.toISOString(),
|
|
30055
|
+
rows,
|
|
30056
|
+
creates,
|
|
30057
|
+
...createdHashes.length > 0 ? { created_hashes: createdHashes } : {},
|
|
30058
|
+
moves,
|
|
30059
|
+
references,
|
|
30060
|
+
deleted,
|
|
30061
|
+
failed: failed2
|
|
30062
|
+
},
|
|
30063
|
+
now
|
|
30064
|
+
);
|
|
30065
|
+
return {
|
|
30066
|
+
ok: failed2.length === 0,
|
|
30067
|
+
migrationId: journal.id,
|
|
30068
|
+
journalPath: journal.path,
|
|
30069
|
+
notesChanged: rows.length,
|
|
30070
|
+
filesCreated: creates.length,
|
|
30071
|
+
filesMoved: moves.length,
|
|
30072
|
+
filesDeleted: deleted.length,
|
|
30073
|
+
referencesUpdated,
|
|
30074
|
+
failed: failed2
|
|
30075
|
+
};
|
|
30229
30076
|
}
|
|
30230
|
-
function
|
|
30231
|
-
|
|
30077
|
+
async function trash(app, file) {
|
|
30078
|
+
const fm = app.fileManager;
|
|
30079
|
+
if (typeof fm.trashFile === "function") {
|
|
30080
|
+
await fm.trashFile(file);
|
|
30081
|
+
} else {
|
|
30082
|
+
await app.vault.trash(file, true);
|
|
30083
|
+
}
|
|
30232
30084
|
}
|
|
30233
|
-
|
|
30234
|
-
|
|
30235
|
-
|
|
30236
|
-
|
|
30237
|
-
|
|
30238
|
-
|
|
30239
|
-
|
|
30240
|
-
|
|
30241
|
-
|
|
30242
|
-
|
|
30243
|
-
|
|
30085
|
+
async function undoChangeSet(app, entries, summary = `undo ${entries.map((e) => e.migration_id).join(", ")}`) {
|
|
30086
|
+
const state = /* @__PURE__ */ new Map();
|
|
30087
|
+
const texts = /* @__PURE__ */ new Map();
|
|
30088
|
+
const origin = /* @__PURE__ */ new Map();
|
|
30089
|
+
const vaultNote = async (path23) => {
|
|
30090
|
+
const f = fileOrNull(app, path23);
|
|
30091
|
+
if (!f) return null;
|
|
30092
|
+
const text5 = await app.vault.read(f);
|
|
30093
|
+
return { ordered: readOrderedFromMarkdown(text5).ordered, body: bodyOf(text5) };
|
|
30094
|
+
};
|
|
30095
|
+
const read3 = async (path23) => state.has(path23) ? state.get(path23) : vaultNote(path23);
|
|
30096
|
+
for (const entry of entries) {
|
|
30097
|
+
const movedTo = new Map(entry.moves.map((m) => [m.from, m.to]));
|
|
30098
|
+
const currentPath = (p) => movedTo.get(p) ?? p;
|
|
30099
|
+
for (const ref2 of entry.references) {
|
|
30100
|
+
const path23 = currentPath(ref2.path);
|
|
30101
|
+
if (/\.md$/i.test(path23)) {
|
|
30102
|
+
const { ordered } = readOrderedFromMarkdown(ref2.before_text);
|
|
30103
|
+
state.set(path23, { ordered, body: bodyOf(ref2.before_text) });
|
|
30104
|
+
} else {
|
|
30105
|
+
texts.set(path23, { before: texts.get(path23)?.before ?? ref2.after_text, after: ref2.before_text });
|
|
30244
30106
|
}
|
|
30245
|
-
}
|
|
30246
|
-
|
|
30247
|
-
|
|
30248
|
-
|
|
30249
|
-
|
|
30250
|
-
|
|
30251
|
-
|
|
30252
|
-
|
|
30253
|
-
|
|
30254
|
-
|
|
30255
|
-
|
|
30256
|
-
|
|
30257
|
-
|
|
30258
|
-
|
|
30259
|
-
|
|
30260
|
-
READ_AHEAD = 16;
|
|
30261
|
-
}
|
|
30262
|
-
});
|
|
30263
|
-
|
|
30264
|
-
// src/core/transaction-log.ts
|
|
30265
|
-
async function unreadableNow(app, path23) {
|
|
30266
|
-
const f = app.vault.getAbstractFileByPath(path23);
|
|
30267
|
-
if (f == null || !("extension" in f)) return false;
|
|
30268
|
-
try {
|
|
30269
|
-
return frontmatterError((await readHashed(app, f)).content) !== null;
|
|
30270
|
-
} catch {
|
|
30271
|
-
return false;
|
|
30107
|
+
}
|
|
30108
|
+
for (const r of entry.rows) {
|
|
30109
|
+
const path23 = currentPath(r.path);
|
|
30110
|
+
const cur = await read3(path23);
|
|
30111
|
+
if (!cur) continue;
|
|
30112
|
+
state.set(path23, { ...cur, ordered: applyDiff(cur.ordered, Object.entries(r.before), r.before_absent, r.before_order) });
|
|
30113
|
+
}
|
|
30114
|
+
for (const m of [...entry.moves].reverse()) {
|
|
30115
|
+
state.set(m.from, await read3(m.to));
|
|
30116
|
+
state.set(m.to, null);
|
|
30117
|
+
origin.set(m.from, origin.get(m.to) ?? m.to);
|
|
30118
|
+
origin.delete(m.to);
|
|
30119
|
+
}
|
|
30120
|
+
for (const c of entry.creates) state.set(c, null);
|
|
30121
|
+
for (const d of entry.deleted) state.set(d.path, { ordered: readOrderedFromMarkdown(d.content).ordered, body: bodyOf(d.content) });
|
|
30272
30122
|
}
|
|
30273
|
-
|
|
30274
|
-
|
|
30275
|
-
|
|
30276
|
-
|
|
30277
|
-
|
|
30278
|
-
|
|
30279
|
-
}
|
|
30280
|
-
|
|
30281
|
-
if (entry == null) return "";
|
|
30282
|
-
let h = ENTRY_HASHES.get(entry);
|
|
30283
|
-
if (h === void 0) ENTRY_HASHES.set(entry, h = contentHash(JSON.stringify(entry)));
|
|
30284
|
-
return h;
|
|
30285
|
-
}
|
|
30286
|
-
async function loggedTables(app, settings, among) {
|
|
30287
|
-
const base = `${transactionLogRoot(settings)}/`;
|
|
30288
|
-
const out2 = [];
|
|
30289
|
-
for (const dir of (await storeList(app, transactionLogRoot(settings))).folders) {
|
|
30290
|
-
const full = dir.slice(base.length);
|
|
30291
|
-
if (among && !among(full)) continue;
|
|
30292
|
-
if ((await storeList(app, dir)).files.some(holdsVersions)) out2.push(full);
|
|
30123
|
+
const cs = emptyChangeSet("undo_migration", summary);
|
|
30124
|
+
const moved = /* @__PURE__ */ new Set();
|
|
30125
|
+
for (const [to, from] of origin) {
|
|
30126
|
+
const final = state.get(to);
|
|
30127
|
+
if (to === from || !final || !fileOrNull(app, from)) continue;
|
|
30128
|
+
moved.add(to).add(from);
|
|
30129
|
+
cs.moves.push({ from, to });
|
|
30130
|
+
cs.edits.push({ path: from, before: (await vaultNote(from)).ordered, after: final.ordered });
|
|
30293
30131
|
}
|
|
30294
|
-
|
|
30295
|
-
|
|
30296
|
-
|
|
30297
|
-
|
|
30298
|
-
|
|
30299
|
-
|
|
30300
|
-
return [...(await tocOf(app, settings, full)).versions];
|
|
30301
|
-
}
|
|
30302
|
-
function parseCommit(text5) {
|
|
30303
|
-
const commit = { commitInfo: void 0, add: [], remove: [] };
|
|
30304
|
-
for (const line of text5.split("\n")) {
|
|
30305
|
-
if (line.trim() === "") continue;
|
|
30306
|
-
const action = JSON.parse(line);
|
|
30307
|
-
if (action.commitInfo) commit.commitInfo = action.commitInfo;
|
|
30308
|
-
else if ("metaData" in action) commit.metaData = action.metaData;
|
|
30309
|
-
else if (action.add) commit.add.push(action.add);
|
|
30310
|
-
else if (action.remove) commit.remove.push(action.remove);
|
|
30132
|
+
for (const [path23, final] of state) {
|
|
30133
|
+
if (moved.has(path23)) continue;
|
|
30134
|
+
const now = fileOrNull(app, path23) ? await vaultNote(path23) : null;
|
|
30135
|
+
if (now && final) cs.edits.push({ path: path23, before: now.ordered, after: final.ordered });
|
|
30136
|
+
else if (now) cs.deletes.push(path23);
|
|
30137
|
+
else if (final) cs.creates.push({ path: path23, content: buildMarkdownWithFrontmatter(final.ordered, final.body) });
|
|
30311
30138
|
}
|
|
30312
|
-
if (
|
|
30313
|
-
return
|
|
30314
|
-
}
|
|
30315
|
-
function serializeCommit(c) {
|
|
30316
|
-
const lines = [JSON.stringify({ commitInfo: c.commitInfo })];
|
|
30317
|
-
if (c.metaData !== void 0) lines.push(JSON.stringify({ metaData: c.metaData }));
|
|
30318
|
-
for (const r of c.remove) lines.push(JSON.stringify({ remove: r }));
|
|
30319
|
-
for (const a of c.add) lines.push(JSON.stringify({ add: a }));
|
|
30320
|
-
return `${lines.join("\n")}
|
|
30321
|
-
`;
|
|
30322
|
-
}
|
|
30323
|
-
async function readCommit(app, settings, full, version2) {
|
|
30324
|
-
const toc = await tocOf(app, settings, full);
|
|
30325
|
-
if (!toc.versions.includes(version2)) throw new HistoryError(`${full} has no version ${version2}`);
|
|
30326
|
-
return movedCommit(parseCommit(await versionText(app, toc, version2)), await databaseMoves(app));
|
|
30139
|
+
for (const [path23, t] of texts) if (t.before !== t.after) cs.texts.push({ path: path23, ...t });
|
|
30140
|
+
return cs;
|
|
30327
30141
|
}
|
|
30328
|
-
function
|
|
30329
|
-
|
|
30330
|
-
|
|
30331
|
-
|
|
30332
|
-
|
|
30333
|
-
|
|
30334
|
-
|
|
30335
|
-
|
|
30336
|
-
|
|
30142
|
+
async function undoMigration(app, settings, migrationId, now = /* @__PURE__ */ new Date(), check2, op2 = "undo_migration") {
|
|
30143
|
+
const { entry, path: journalPath, undone } = await readJournal(app, settings, migrationId);
|
|
30144
|
+
const conflicts = [];
|
|
30145
|
+
const partial2 = entry.failed.length > 0;
|
|
30146
|
+
const back = /* @__PURE__ */ new Set();
|
|
30147
|
+
const why = RECORD_ONLY_OPS[entry.op];
|
|
30148
|
+
if (why) return { ok: false, undoes: migrationId, conflicts: [{ path: journalPath, message: why }], failed: [] };
|
|
30149
|
+
if (undone) {
|
|
30150
|
+
return {
|
|
30151
|
+
ok: false,
|
|
30152
|
+
undoes: migrationId,
|
|
30153
|
+
conflicts: [{ path: journalPath, message: "migration already undone" }],
|
|
30154
|
+
failed: []
|
|
30337
30155
|
};
|
|
30338
30156
|
}
|
|
30339
|
-
|
|
30340
|
-
|
|
30341
|
-
|
|
30342
|
-
|
|
30343
|
-
|
|
30344
|
-
|
|
30345
|
-
|
|
30157
|
+
const movedTo = new Map(entry.moves.map((m) => [m.from, m.to]));
|
|
30158
|
+
const currentPath = (p) => movedTo.get(p) ?? p;
|
|
30159
|
+
for (const r of entry.rows) {
|
|
30160
|
+
const f = fileOrNull(app, currentPath(r.path));
|
|
30161
|
+
if (!f) {
|
|
30162
|
+
conflicts.push({ path: r.path, message: "note no longer exists" });
|
|
30163
|
+
continue;
|
|
30164
|
+
}
|
|
30165
|
+
const cur = new Map(await readOrderedFor(app, f));
|
|
30166
|
+
if (partial2 && Object.entries(r.before).every(([k, v]) => cur.has(k) && same(cur.get(k), v)) && r.before_absent.every((k) => !cur.has(k)) && (r.body_before === void 0 || bodyOf(await app.vault.read(f)) === r.body_before)) {
|
|
30167
|
+
back.add(r);
|
|
30168
|
+
continue;
|
|
30169
|
+
}
|
|
30170
|
+
for (const [k, v] of Object.entries(r.after)) {
|
|
30171
|
+
if (!cur.has(k) || !same(cur.get(k), v)) {
|
|
30172
|
+
conflicts.push({ path: r.path, message: `"${k}" changed since the operation` });
|
|
30173
|
+
}
|
|
30174
|
+
}
|
|
30175
|
+
for (const k of r.after_absent) {
|
|
30176
|
+
if (cur.has(k)) conflicts.push({ path: r.path, message: `"${k}" was added since the operation` });
|
|
30177
|
+
}
|
|
30178
|
+
if (r.body_after !== void 0 && bodyOf(await app.vault.read(f)) !== r.body_after) {
|
|
30179
|
+
conflicts.push({ path: r.path, message: "body changed since the operation" });
|
|
30180
|
+
}
|
|
30346
30181
|
}
|
|
30347
|
-
|
|
30348
|
-
|
|
30349
|
-
|
|
30350
|
-
|
|
30351
|
-
|
|
30352
|
-
}
|
|
30353
|
-
|
|
30354
|
-
const files = state.files;
|
|
30355
|
-
const at = c.commitInfo.version;
|
|
30356
|
-
for (const r of c.remove) files.delete(r.path);
|
|
30357
|
-
for (const a of c.add) files.set(a.path, { hash: a.hash, content: a.content, at });
|
|
30358
|
-
return {
|
|
30359
|
-
full: state.full,
|
|
30360
|
-
version: c.commitInfo.version,
|
|
30361
|
-
timestamp: c.commitInfo.timestamp,
|
|
30362
|
-
entry: c.metaData !== void 0 ? c.metaData.entry : state.entry,
|
|
30363
|
-
files
|
|
30364
|
-
};
|
|
30365
|
-
}
|
|
30366
|
-
function applyLogged(state, c) {
|
|
30367
|
-
const at = c.commitInfo.version;
|
|
30368
|
-
for (const r of c.remove) {
|
|
30369
|
-
const old = state.files.get(r.path);
|
|
30370
|
-
if (old) state.bytes -= old.size;
|
|
30371
|
-
state.files.delete(r.path);
|
|
30182
|
+
for (const ref2 of entry.references) {
|
|
30183
|
+
const f = fileOrNull(app, currentPath(ref2.path));
|
|
30184
|
+
const text5 = f ? await app.vault.read(f) : null;
|
|
30185
|
+
if (partial2 && text5 === ref2.before_text) back.add(ref2);
|
|
30186
|
+
else if (text5 !== ref2.after_text) {
|
|
30187
|
+
conflicts.push({ path: ref2.path, message: "text changed since the operation" });
|
|
30188
|
+
}
|
|
30372
30189
|
}
|
|
30373
|
-
|
|
30374
|
-
|
|
30375
|
-
|
|
30376
|
-
|
|
30377
|
-
|
|
30378
|
-
|
|
30190
|
+
const since = await storeFilesSince(app, entry.moves);
|
|
30191
|
+
const vacuumed = /* @__PURE__ */ new Set();
|
|
30192
|
+
for (const m of entry.moves) {
|
|
30193
|
+
if (!await fileExists(app, m.to)) {
|
|
30194
|
+
if (inStore(m.to)) vacuumed.add(m.to);
|
|
30195
|
+
else if (partial2 && await fileExists(app, m.from)) back.add(m);
|
|
30196
|
+
else conflicts.push({ path: m.to, message: "moved file no longer exists" });
|
|
30197
|
+
} else if (await occupied(app, m.from)) {
|
|
30198
|
+
conflicts.push({ path: m.from, message: "original path is occupied" });
|
|
30199
|
+
}
|
|
30379
30200
|
}
|
|
30380
|
-
|
|
30381
|
-
|
|
30382
|
-
|
|
30383
|
-
|
|
30384
|
-
|
|
30201
|
+
for (const m of since) {
|
|
30202
|
+
if (await occupied(app, m.from)) conflicts.push({ path: m.from, message: "original path is occupied" });
|
|
30203
|
+
}
|
|
30204
|
+
const hashOfCreated = new Map((entry.created_hashes ?? []).map((h) => [h.path, h.hash]));
|
|
30205
|
+
for (const c of entry.creates) {
|
|
30206
|
+
const f = fileOrNull(app, currentPath(c));
|
|
30207
|
+
if (f) {
|
|
30208
|
+
const was = hashOfCreated.get(c);
|
|
30209
|
+
if (!partial2 && was !== void 0 && createdHash(await app.vault.read(f)) !== was) {
|
|
30210
|
+
conflicts.push({ path: currentPath(c), message: "created note changed since the operation" });
|
|
30211
|
+
}
|
|
30212
|
+
continue;
|
|
30213
|
+
}
|
|
30214
|
+
if (partial2) back.add(c);
|
|
30215
|
+
else conflicts.push({ path: c, message: "created file no longer exists" });
|
|
30216
|
+
}
|
|
30217
|
+
for (const d of entry.deleted) {
|
|
30218
|
+
const f = fileOrNull(app, d.path);
|
|
30219
|
+
if (partial2 && f && await app.vault.read(f) === d.content) back.add(d);
|
|
30220
|
+
else if (app.vault.getAbstractFileByPath(d.path) != null) {
|
|
30221
|
+
conflicts.push({ path: d.path, message: "deleted path is occupied" });
|
|
30222
|
+
}
|
|
30223
|
+
}
|
|
30224
|
+
if (conflicts.length > 0) return { ok: false, undoes: migrationId, conflicts, failed: [] };
|
|
30225
|
+
if (check2) {
|
|
30226
|
+
const refused2 = await check2(await undoChangeSet(app, [entry], `undo ${migrationId}`));
|
|
30227
|
+
if (refused2.length > 0) return { ok: false, undoes: migrationId, conflicts: refused2, failed: [] };
|
|
30228
|
+
}
|
|
30229
|
+
const failed2 = [];
|
|
30230
|
+
const undoRows = [];
|
|
30231
|
+
const undoMoves = [];
|
|
30232
|
+
const undoReferences = [];
|
|
30233
|
+
const undoCreates = [];
|
|
30234
|
+
const undoCreatedHashes = [];
|
|
30235
|
+
const undoDeleted = [];
|
|
30236
|
+
const steps = [];
|
|
30237
|
+
for (const ref2 of entry.references) {
|
|
30238
|
+
if (back.has(ref2)) continue;
|
|
30239
|
+
steps.push({
|
|
30240
|
+
path: currentPath(ref2.path),
|
|
30241
|
+
run: async () => {
|
|
30242
|
+
const f = fileOrNull(app, currentPath(ref2.path));
|
|
30243
|
+
await app.vault.process(f, () => ref2.before_text);
|
|
30244
|
+
undoReferences.push({ path: currentPath(ref2.path), before_text: ref2.after_text, after_text: ref2.before_text });
|
|
30245
|
+
}
|
|
30246
|
+
});
|
|
30247
|
+
}
|
|
30248
|
+
for (const r of entry.rows) {
|
|
30249
|
+
if (back.has(r)) continue;
|
|
30250
|
+
steps.push({
|
|
30251
|
+
path: currentPath(r.path),
|
|
30252
|
+
run: async () => {
|
|
30253
|
+
const f = fileOrNull(app, currentPath(r.path));
|
|
30254
|
+
const order = (await readOrderedFor(app, f)).map(([k]) => k);
|
|
30255
|
+
if (Object.keys(r.after).length > 0 || r.after_absent.length > 0 || Object.keys(r.before).length > 0) {
|
|
30256
|
+
await writeFrontmatter(
|
|
30257
|
+
app,
|
|
30258
|
+
f,
|
|
30259
|
+
(fm) => applyDiff(fm, Object.entries(r.before), r.before_absent, r.before_order)
|
|
30260
|
+
);
|
|
30261
|
+
}
|
|
30262
|
+
if (r.body_after !== void 0) await writeBody(app, f, r.body_after, r.body_before);
|
|
30263
|
+
undoRows.push({
|
|
30264
|
+
path: currentPath(r.path),
|
|
30265
|
+
before: r.after,
|
|
30266
|
+
before_absent: r.after_absent,
|
|
30267
|
+
before_order: order,
|
|
30268
|
+
after: r.before,
|
|
30269
|
+
after_absent: r.before_absent,
|
|
30270
|
+
...r.body_after !== void 0 ? { body_before: r.body_after, body_after: r.body_before } : {}
|
|
30271
|
+
});
|
|
30272
|
+
}
|
|
30273
|
+
});
|
|
30274
|
+
}
|
|
30275
|
+
for (const m of [...[...entry.moves].reverse().filter((x) => !vacuumed.has(x.to) && !back.has(x)), ...since]) {
|
|
30276
|
+
steps.push({
|
|
30277
|
+
path: m.to,
|
|
30278
|
+
run: async () => {
|
|
30279
|
+
await moveFile(app, m.to, m.from);
|
|
30280
|
+
undoMoves.push({ from: m.to, to: m.from });
|
|
30281
|
+
}
|
|
30282
|
+
});
|
|
30283
|
+
}
|
|
30284
|
+
for (const c of entry.creates) {
|
|
30285
|
+
if (back.has(c)) continue;
|
|
30286
|
+
steps.push({
|
|
30287
|
+
path: c,
|
|
30288
|
+
run: async () => {
|
|
30289
|
+
const f = fileOrNull(app, c);
|
|
30290
|
+
const content = await app.vault.read(f);
|
|
30291
|
+
await trash(app, f);
|
|
30292
|
+
undoDeleted.push({ path: c, content });
|
|
30293
|
+
}
|
|
30294
|
+
});
|
|
30295
|
+
}
|
|
30296
|
+
for (const d of entry.deleted) {
|
|
30297
|
+
if (back.has(d)) continue;
|
|
30298
|
+
steps.push({
|
|
30299
|
+
path: d.path,
|
|
30300
|
+
run: async () => {
|
|
30301
|
+
await ensureFolder(app, parentFolder(d.path));
|
|
30302
|
+
await app.vault.create(d.path, d.content);
|
|
30303
|
+
undoCreates.push(d.path);
|
|
30304
|
+
undoCreatedHashes.push({ path: d.path, hash: createdHash(d.content) });
|
|
30305
|
+
}
|
|
30306
|
+
});
|
|
30307
|
+
}
|
|
30308
|
+
let done = 0;
|
|
30385
30309
|
try {
|
|
30386
|
-
const
|
|
30387
|
-
|
|
30388
|
-
|
|
30389
|
-
|
|
30310
|
+
for (const s of steps) {
|
|
30311
|
+
await s.run();
|
|
30312
|
+
done++;
|
|
30313
|
+
}
|
|
30314
|
+
} catch (e) {
|
|
30315
|
+
failed2.push({ path: "undo", error: messageOf(e) });
|
|
30390
30316
|
}
|
|
30391
|
-
|
|
30392
|
-
|
|
30393
|
-
|
|
30394
|
-
|
|
30395
|
-
|
|
30396
|
-
|
|
30397
|
-
|
|
30398
|
-
|
|
30317
|
+
const journal = await writeJournal(
|
|
30318
|
+
app,
|
|
30319
|
+
settings,
|
|
30320
|
+
{
|
|
30321
|
+
op: op2,
|
|
30322
|
+
...entry.schema !== void 0 ? { schema: entry.schema } : {},
|
|
30323
|
+
...currentActor() ? { actor: currentActor() } : {},
|
|
30324
|
+
summary: `undo ${migrationId}`,
|
|
30325
|
+
executed_at: now.toISOString(),
|
|
30326
|
+
rows: undoRows,
|
|
30327
|
+
creates: undoCreates,
|
|
30328
|
+
...undoCreatedHashes.length > 0 ? { created_hashes: undoCreatedHashes } : {},
|
|
30329
|
+
moves: undoMoves,
|
|
30330
|
+
references: undoReferences,
|
|
30331
|
+
deleted: undoDeleted,
|
|
30332
|
+
failed: failed2,
|
|
30333
|
+
undoes: migrationId
|
|
30334
|
+
},
|
|
30335
|
+
now
|
|
30336
|
+
);
|
|
30337
|
+
if (failed2.length === 0) {
|
|
30338
|
+
await markUndone(app, settings, migrationId, journal.id, now);
|
|
30399
30339
|
}
|
|
30400
|
-
|
|
30401
|
-
|
|
30402
|
-
|
|
30403
|
-
|
|
30404
|
-
|
|
30405
|
-
|
|
30406
|
-
|
|
30340
|
+
return {
|
|
30341
|
+
ok: failed2.length === 0,
|
|
30342
|
+
undoes: migrationId,
|
|
30343
|
+
migration_id: journal.id,
|
|
30344
|
+
conflicts: [],
|
|
30345
|
+
failed: failed2,
|
|
30346
|
+
...failed2.length > 0 ? { left: [...new Set(steps.slice(done).map((s) => s.path))] } : {},
|
|
30347
|
+
touched: [
|
|
30348
|
+
...entry.rows.map((r) => r.path),
|
|
30349
|
+
...entry.creates,
|
|
30350
|
+
...[...entry.moves, ...since].flatMap((m) => [m.from, m.to]),
|
|
30351
|
+
...entry.references.map((r) => r.path),
|
|
30352
|
+
...entry.deleted.map((d) => d.path)
|
|
30353
|
+
]
|
|
30354
|
+
};
|
|
30355
|
+
}
|
|
30356
|
+
function applyChangeSet(...args) {
|
|
30357
|
+
const cs = args[2];
|
|
30358
|
+
return phase("engine.apply", async (span) => {
|
|
30359
|
+
const r = await applyChangeSetImpl(...args);
|
|
30360
|
+
span.setAttributes({ "datanotes.notes_changed": r.notesChanged, "datanotes.files_created": r.filesCreated, "datanotes.files_moved": r.filesMoved, "datanotes.files_deleted": r.filesDeleted, "datanotes.failed": r.failed.length });
|
|
30361
|
+
if (!r.ok) span.setAttribute("error.type", "write_failed").setStatus("error", "the write failed partway");
|
|
30362
|
+
return r;
|
|
30363
|
+
}, { "datanotes.edits": cs.edits.length, "datanotes.creates": cs.creates.length, "datanotes.moves": cs.moves.length, "datanotes.deletes": cs.deletes.length });
|
|
30364
|
+
}
|
|
30365
|
+
var inStore, WRITE_CONCURRENCY, RECORD_ONLY_OPS;
|
|
30366
|
+
var init_changeset = __esm({
|
|
30367
|
+
"src/core/changeset.ts"() {
|
|
30368
|
+
"use strict";
|
|
30369
|
+
init_frontmatter();
|
|
30370
|
+
init_actor();
|
|
30371
|
+
init_journal();
|
|
30372
|
+
init_store();
|
|
30373
|
+
init_metastore();
|
|
30374
|
+
init_database_id();
|
|
30375
|
+
init_message();
|
|
30376
|
+
init_file_cache();
|
|
30377
|
+
init_tracing();
|
|
30378
|
+
init_journal();
|
|
30379
|
+
inStore = isStorePath;
|
|
30380
|
+
WRITE_CONCURRENCY = { atOnce: 16, from: 32 };
|
|
30381
|
+
RECORD_ONLY_OPS = {
|
|
30382
|
+
move_database: "a move of the database folder cannot be undone: move it back with configure_database (root)",
|
|
30383
|
+
set_database_id: "the database's id cannot be taken back: the engine finds its store by it",
|
|
30384
|
+
rollback: "the rollback of a failed write cannot be undone: it put the database back as it was; run the write again instead"
|
|
30385
|
+
};
|
|
30407
30386
|
}
|
|
30408
|
-
|
|
30409
|
-
|
|
30410
|
-
|
|
30411
|
-
|
|
30412
|
-
|
|
30387
|
+
});
|
|
30388
|
+
|
|
30389
|
+
// src/core/write-scope.ts
|
|
30390
|
+
function runWithWriteScope(scope, fn) {
|
|
30391
|
+
if (!storage3) {
|
|
30392
|
+
if (scope) throw new Error("write permissions need Node's AsyncLocalStorage");
|
|
30393
|
+
return fn();
|
|
30413
30394
|
}
|
|
30414
|
-
|
|
30395
|
+
if (!scope && !storage3.getStore()) return fn();
|
|
30396
|
+
return storage3.run({ scope }, fn);
|
|
30415
30397
|
}
|
|
30416
|
-
|
|
30417
|
-
|
|
30418
|
-
if (known && known.version >= 0 && isLatest(known, asOf2) && await stillLatest(app, settings, known)) {
|
|
30419
|
-
const fromNotes = await stateFromLogged(app, settings, known);
|
|
30420
|
-
if (fromNotes) return fromNotes;
|
|
30421
|
-
}
|
|
30422
|
-
return replayState(app, settings, full, asOf2);
|
|
30398
|
+
function currentWriteScope() {
|
|
30399
|
+
return storage3?.getStore()?.scope ?? null;
|
|
30423
30400
|
}
|
|
30424
|
-
function
|
|
30425
|
-
|
|
30426
|
-
if (asOf2.timestamp === void 0) return true;
|
|
30427
|
-
const at = Date.parse(asOf2.timestamp);
|
|
30428
|
-
return !Number.isNaN(at) && at >= Date.parse(known.timestamp);
|
|
30401
|
+
function tableOfFrontmatter(model, settings, fm) {
|
|
30402
|
+
return rowTableFull(model, fmGet(fm, settings.tableKey));
|
|
30429
30403
|
}
|
|
30430
|
-
|
|
30431
|
-
const
|
|
30432
|
-
const
|
|
30433
|
-
|
|
30434
|
-
if (versions.length === 0) {
|
|
30435
|
-
if (asOf2.version !== void 0 || asOf2.timestamp !== void 0) {
|
|
30436
|
-
throw new HistoryError(
|
|
30437
|
-
`${full} has no history: time travel is off for it (turn it on with configure_database(history=true) or alter_table(history=true))`
|
|
30438
|
-
);
|
|
30439
|
-
}
|
|
30440
|
-
return emptyState(full);
|
|
30441
|
-
}
|
|
30442
|
-
let target = versions[versions.length - 1];
|
|
30443
|
-
if (asOf2.version !== void 0) {
|
|
30444
|
-
if (!versions.includes(asOf2.version)) {
|
|
30445
|
-
throw new HistoryError(`${full} has no version ${asOf2.version} (available: ${versions[0]}..${target})`);
|
|
30446
|
-
}
|
|
30447
|
-
target = asOf2.version;
|
|
30448
|
-
}
|
|
30449
|
-
const limitMs = asOf2.timestamp !== void 0 ? Date.parse(asOf2.timestamp) : void 0;
|
|
30450
|
-
if (limitMs !== void 0 && Number.isNaN(limitMs)) throw new HistoryError(`"timestamp" is not a date-time: ${asOf2.timestamp}`);
|
|
30451
|
-
const moves = await databaseMoves(app);
|
|
30452
|
-
let applied = 0;
|
|
30453
|
-
const moveUpTo = (timestamp) => {
|
|
30454
|
-
while (applied < moves.length && (timestamp === null || moves[applied].at <= timestamp)) moveState(state, moves[applied++]);
|
|
30404
|
+
function changeSetTables(app, settings, model, cs) {
|
|
30405
|
+
const out2 = /* @__PURE__ */ new Set();
|
|
30406
|
+
const add = (full) => {
|
|
30407
|
+
if (full !== null) out2.add(full);
|
|
30455
30408
|
};
|
|
30456
|
-
|
|
30457
|
-
for (const
|
|
30458
|
-
if (
|
|
30459
|
-
|
|
30460
|
-
|
|
30461
|
-
if (limitMs !== void 0 && Date.parse(c.timestamp) > limitMs) continue;
|
|
30462
|
-
state = { full, version: c.version, timestamp: c.timestamp, entry: c.entry, files: c.files };
|
|
30463
|
-
applied = Math.min(c.moves ?? moves.filter((m) => m.at <= c.timestamp).length, moves.length);
|
|
30464
|
-
break;
|
|
30465
|
-
}
|
|
30466
|
-
const since = { versions: 0, bytes: 0 };
|
|
30467
|
-
const replay = versions.filter((v) => v > state.version && v <= target);
|
|
30468
|
-
const texts = await versionTexts(app, toc, replay);
|
|
30469
|
-
for (const text5 of texts) {
|
|
30470
|
-
const c = parseCommit(text5);
|
|
30471
|
-
if (limitMs !== void 0 && Date.parse(c.commitInfo.timestamp) > limitMs) break;
|
|
30472
|
-
moveUpTo(c.commitInfo.timestamp);
|
|
30473
|
-
state = applyCommit(state, c);
|
|
30474
|
-
since.versions++;
|
|
30475
|
-
since.bytes += text5.length;
|
|
30409
|
+
const keep = /* @__PURE__ */ new Set([settings.tableKey]);
|
|
30410
|
+
for (const e of cs.edits) {
|
|
30411
|
+
if (editIsNoop(e)) continue;
|
|
30412
|
+
add(tableOfFrontmatter(model, settings, e.before) ?? tableOfPath(app, settings, e.path));
|
|
30413
|
+
add(tableOfFrontmatter(model, settings, e.after));
|
|
30476
30414
|
}
|
|
30477
|
-
|
|
30478
|
-
|
|
30479
|
-
|
|
30480
|
-
|
|
30481
|
-
return state;
|
|
30415
|
+
for (const c of cs.creates) add(tableOfFrontmatter(model, settings, readOrderedFromMarkdown(c.content, keep).ordered));
|
|
30416
|
+
for (const m of cs.moves) add(tableOfPath(app, settings, m.from));
|
|
30417
|
+
for (const d of cs.deletes) add(tableOfPath(app, settings, d));
|
|
30418
|
+
return out2;
|
|
30482
30419
|
}
|
|
30483
|
-
async function
|
|
30484
|
-
const
|
|
30485
|
-
|
|
30486
|
-
const
|
|
30487
|
-
if (
|
|
30488
|
-
const
|
|
30489
|
-
|
|
30490
|
-
|
|
30491
|
-
|
|
30492
|
-
|
|
30493
|
-
|
|
30494
|
-
|
|
30495
|
-
|
|
30496
|
-
|
|
30497
|
-
|
|
30498
|
-
|
|
30499
|
-
|
|
30500
|
-
|
|
30501
|
-
|
|
30502
|
-
|
|
30503
|
-
|
|
30504
|
-
const
|
|
30505
|
-
|
|
30506
|
-
|
|
30507
|
-
|
|
30508
|
-
for (const [path23, f] of c.files) {
|
|
30509
|
-
files.set(path23, { hash: f.hash, size: f.content.length, at: c.version, cp: true });
|
|
30510
|
-
bytes += f.content.length;
|
|
30511
|
-
}
|
|
30512
|
-
state = { full, version: c.version, timestamp: c.timestamp, entry: c.entry, files, sinceCheckpoint: { versions: 0, bytes: 0 }, bytes };
|
|
30513
|
-
applied = Math.min(c.moves ?? moves.filter((x) => x.at <= c.timestamp).length, moves.length);
|
|
30420
|
+
async function assertChangeSetWritable(app, settings, cs) {
|
|
30421
|
+
const scope = currentWriteScope();
|
|
30422
|
+
if (!scope || !ROW_WRITE_OPS.has(cs.op)) return;
|
|
30423
|
+
const model = (await loadMetastore(app, settings))?.model;
|
|
30424
|
+
if (!model) return;
|
|
30425
|
+
const denied = [...changeSetTables(app, settings, model, cs)].filter((t) => !matchesTablePattern(t, scope.patterns)).sort();
|
|
30426
|
+
if (denied.length === 0) return;
|
|
30427
|
+
const named = denied.find((t) => tableReadable(t));
|
|
30428
|
+
throw new WriteDeniedError(scope.token, named ?? null);
|
|
30429
|
+
}
|
|
30430
|
+
var storage3, ROW_WRITE_OPS, WriteDeniedError;
|
|
30431
|
+
var init_write_scope = __esm({
|
|
30432
|
+
"src/core/write-scope.ts"() {
|
|
30433
|
+
"use strict";
|
|
30434
|
+
init_changeset();
|
|
30435
|
+
init_frontmatter();
|
|
30436
|
+
init_hints();
|
|
30437
|
+
init_metastore();
|
|
30438
|
+
init_read_scope();
|
|
30439
|
+
storage3 = null;
|
|
30440
|
+
try {
|
|
30441
|
+
const { AsyncLocalStorage: AsyncLocalStorage2 } = require("node:async_hooks");
|
|
30442
|
+
storage3 = new AsyncLocalStorage2();
|
|
30443
|
+
} catch {
|
|
30444
|
+
storage3 = null;
|
|
30514
30445
|
}
|
|
30515
|
-
|
|
30446
|
+
ROW_WRITE_OPS = /* @__PURE__ */ new Set([
|
|
30447
|
+
"insert_row",
|
|
30448
|
+
"update_row",
|
|
30449
|
+
"update_rows",
|
|
30450
|
+
"delete_rows",
|
|
30451
|
+
"move_rows",
|
|
30452
|
+
"adopt_rows",
|
|
30453
|
+
"merge_rows",
|
|
30454
|
+
"recompute_columns",
|
|
30455
|
+
"align_relations"
|
|
30456
|
+
]);
|
|
30457
|
+
WriteDeniedError = class extends GuidedError {
|
|
30458
|
+
constructor(token, table) {
|
|
30459
|
+
super(
|
|
30460
|
+
table === null ? `token ${token} may not write a table it may not read: this write changes rows of it` : `token ${token} may not write ${table}: this write changes rows of it`,
|
|
30461
|
+
guide(
|
|
30462
|
+
table === null ? `Nothing was written. The write changes rows of a table this token may not read (set_null, an inverse link or align_relations). The owner can add that table to the token's "read" and "write" with tokens/update/. Or change the write so it leaves that table alone.` : `Nothing was written. The write changes rows of ${table} (set_null, an inverse link or align_relations), so the token needs write on it too: the owner adds "${table}" (or a wider pattern) to the token's "write" with tokens/update/. Or change the write so it leaves that table alone.`,
|
|
30463
|
+
"tokenWrite"
|
|
30464
|
+
)
|
|
30465
|
+
);
|
|
30466
|
+
this.name = "WriteDeniedError";
|
|
30467
|
+
this.table = table;
|
|
30468
|
+
}
|
|
30469
|
+
};
|
|
30516
30470
|
}
|
|
30517
|
-
|
|
30518
|
-
|
|
30519
|
-
|
|
30520
|
-
|
|
30521
|
-
|
|
30522
|
-
|
|
30471
|
+
});
|
|
30472
|
+
|
|
30473
|
+
// src/core/unreadable.ts
|
|
30474
|
+
function frontmatterError(text5) {
|
|
30475
|
+
try {
|
|
30476
|
+
readOrderedFromMarkdown(text5);
|
|
30477
|
+
return null;
|
|
30478
|
+
} catch (e) {
|
|
30479
|
+
return messageOf(e);
|
|
30523
30480
|
}
|
|
30524
|
-
if (moves.length > 0) state.movesApplied = applied;
|
|
30525
|
-
return state;
|
|
30526
|
-
}
|
|
30527
|
-
async function partsThere(app, dir, parts) {
|
|
30528
|
-
const { files } = await storeList(app, dir);
|
|
30529
|
-
const there = new Set(files);
|
|
30530
|
-
return parts.every((n) => there.has(`${dir}/${n}`));
|
|
30531
30481
|
}
|
|
30532
|
-
function
|
|
30533
|
-
const
|
|
30534
|
-
|
|
30535
|
-
for (const
|
|
30536
|
-
|
|
30537
|
-
|
|
30482
|
+
function rawFrontmatterValue(text5, key2) {
|
|
30483
|
+
const split2 = splitFrontmatter(text5);
|
|
30484
|
+
if (!split2) return void 0;
|
|
30485
|
+
for (const line of split2.yamlText.split(/\r?\n/)) {
|
|
30486
|
+
const head = [`${key2}:`, `"${key2}":`, `'${key2}':`].find((h) => line.startsWith(h));
|
|
30487
|
+
if (!head) continue;
|
|
30488
|
+
const rest = line.slice(head.length).trim();
|
|
30489
|
+
try {
|
|
30490
|
+
return (0, import_yaml3.parse)(rest);
|
|
30491
|
+
} catch {
|
|
30492
|
+
return rest;
|
|
30493
|
+
}
|
|
30538
30494
|
}
|
|
30539
|
-
return
|
|
30540
|
-
full: state.full,
|
|
30541
|
-
version: state.version,
|
|
30542
|
-
timestamp: state.timestamp,
|
|
30543
|
-
entry: state.entry,
|
|
30544
|
-
files,
|
|
30545
|
-
sinceCheckpoint: state.sinceCheckpoint,
|
|
30546
|
-
movesApplied: state.movesApplied,
|
|
30547
|
-
bytes
|
|
30548
|
-
};
|
|
30495
|
+
return void 0;
|
|
30549
30496
|
}
|
|
30550
|
-
|
|
30497
|
+
function unreadableMessage(error2) {
|
|
30498
|
+
return `the front matter cannot be read, so the row is left out of every read: ${error2.split("\n")[0].trim()}`;
|
|
30499
|
+
}
|
|
30500
|
+
function excludedFolders(model, settings) {
|
|
30551
30501
|
const out2 = /* @__PURE__ */ new Map();
|
|
30552
|
-
const
|
|
30553
|
-
|
|
30554
|
-
|
|
30555
|
-
|
|
30556
|
-
|
|
30557
|
-
if (note && "extension" in note) {
|
|
30558
|
-
try {
|
|
30559
|
-
const h = await readHashed(app, note);
|
|
30560
|
-
if (h.hash === f.hash) {
|
|
30561
|
-
out2.set(path23, h.content);
|
|
30562
|
-
continue;
|
|
30563
|
-
}
|
|
30564
|
-
} catch {
|
|
30565
|
-
}
|
|
30502
|
+
for (const { ref: ref2, entry } of catalogTables(model)) {
|
|
30503
|
+
try {
|
|
30504
|
+
const options = parseTableOptions(entryToOrdered(ref2, entry, settings).find(([k]) => k === TABLE_KEY)?.[1]);
|
|
30505
|
+
out2.set(ref2.full, [...options.exclude ?? [], catalogLayout(normalizeDatabaseFolder(settings.databaseFolder), ref2, options).archive]);
|
|
30506
|
+
} catch {
|
|
30566
30507
|
}
|
|
30567
|
-
const by = f.cp ? fromCheckpoint : fromCommit;
|
|
30568
|
-
const list4 = by.get(f.at);
|
|
30569
|
-
if (list4) list4.push(path23);
|
|
30570
|
-
else by.set(f.at, [path23]);
|
|
30571
30508
|
}
|
|
30572
|
-
|
|
30573
|
-
|
|
30509
|
+
return out2;
|
|
30510
|
+
}
|
|
30511
|
+
function unreadableRowOf(model, settings, path23, text5, excluded) {
|
|
30512
|
+
if (!text5.startsWith("---")) return null;
|
|
30513
|
+
const error2 = frontmatterError(text5);
|
|
30514
|
+
if (error2 === null) return null;
|
|
30515
|
+
const table = rowTableFull(model, rawFrontmatterValue(text5, settings.tableKey));
|
|
30516
|
+
if (!table) return null;
|
|
30517
|
+
const folders = (excluded ?? excludedFolders(model, settings)).get(table) ?? [];
|
|
30518
|
+
if (folders.some((dir) => path23.startsWith(`${dir}/`))) return null;
|
|
30519
|
+
return { path: path23, table, error: error2 };
|
|
30520
|
+
}
|
|
30521
|
+
async function unreadableRows(app, settings, model, table) {
|
|
30522
|
+
const excluded = excludedFolders(model, settings);
|
|
30523
|
+
const out2 = [];
|
|
30524
|
+
const idx = vaultIndex(app, settings);
|
|
30525
|
+
const notes = idx ? [...idx.allNotes()].filter((n) => n.bare).map((n) => n.file) : app.vault.getMarkdownFiles();
|
|
30526
|
+
for (const f of notes) {
|
|
30527
|
+
let cached2;
|
|
30574
30528
|
try {
|
|
30575
|
-
|
|
30529
|
+
cached2 = idx ? void 0 : app.metadataCache.getFileCache(f)?.frontmatter;
|
|
30576
30530
|
} catch {
|
|
30577
|
-
|
|
30531
|
+
cached2 = void 0;
|
|
30578
30532
|
}
|
|
30579
|
-
|
|
30580
|
-
|
|
30581
|
-
}
|
|
30582
|
-
for (const [v, list4] of fromCheckpoint) await textsFromCheckpoint(app, settings, state, v, list4, out2);
|
|
30583
|
-
const missing = [...fromCommit.values(), ...fromCheckpoint.values()].flat().filter((p) => !out2.has(p));
|
|
30584
|
-
if (missing.length > 0) {
|
|
30533
|
+
if (cached2) continue;
|
|
30534
|
+
let text5;
|
|
30585
30535
|
try {
|
|
30586
|
-
|
|
30587
|
-
for (const p of missing) {
|
|
30588
|
-
const x = whole.files.get(p);
|
|
30589
|
-
if (x && x.hash === state.files.get(p).hash) out2.set(p, x.content);
|
|
30590
|
-
}
|
|
30536
|
+
text5 = (await readHashed(app, f)).content;
|
|
30591
30537
|
} catch {
|
|
30538
|
+
continue;
|
|
30592
30539
|
}
|
|
30540
|
+
const u = unreadableRowOf(model, settings, f.path, text5, excluded);
|
|
30541
|
+
if (u && (table === void 0 || u.table === table)) out2.push(u);
|
|
30593
30542
|
}
|
|
30594
|
-
return out2;
|
|
30543
|
+
return out2.sort((a, b) => a.path.localeCompare(b.path));
|
|
30595
30544
|
}
|
|
30596
|
-
|
|
30597
|
-
|
|
30598
|
-
|
|
30599
|
-
|
|
30600
|
-
|
|
30601
|
-
|
|
30602
|
-
|
|
30603
|
-
|
|
30604
|
-
|
|
30605
|
-
|
|
30606
|
-
|
|
30607
|
-
|
|
30608
|
-
|
|
30609
|
-
}
|
|
30610
|
-
const m = await readManifest(app, dir, cp.path);
|
|
30611
|
-
if (!m) return;
|
|
30612
|
-
const partOf = new Map(m.rows.map(([path23, , , part]) => [path23, part]));
|
|
30613
|
-
const parts = new Set(paths.map((p) => partOf.get(p)).filter((x) => x !== void 0));
|
|
30614
|
-
for (const i of parts) {
|
|
30615
|
-
const part = await readPart(app, dir, m.head.parts[i]);
|
|
30616
|
-
for (const [path23, content] of part ?? []) keep(path23, content);
|
|
30545
|
+
var import_yaml3;
|
|
30546
|
+
var init_unreadable = __esm({
|
|
30547
|
+
"src/core/unreadable.ts"() {
|
|
30548
|
+
"use strict";
|
|
30549
|
+
init_file_cache();
|
|
30550
|
+
import_yaml3 = __toESM(require_dist(), 1);
|
|
30551
|
+
init_frontmatter();
|
|
30552
|
+
init_metastore();
|
|
30553
|
+
init_table_loader();
|
|
30554
|
+
init_types_table();
|
|
30555
|
+
init_message();
|
|
30556
|
+
init_db_paths();
|
|
30557
|
+
init_vault_index();
|
|
30617
30558
|
}
|
|
30559
|
+
});
|
|
30560
|
+
|
|
30561
|
+
// src/core/log-files.ts
|
|
30562
|
+
function holdsVersions(path23) {
|
|
30563
|
+
return COMMIT_RE.test(path23) || SEGMENT_RE.test(path23);
|
|
30618
30564
|
}
|
|
30619
|
-
|
|
30620
|
-
|
|
30621
|
-
if (
|
|
30622
|
-
|
|
30623
|
-
for (const [path23, f] of state.files) files.set(path23, { hash: f.hash, content: texts.get(path23), at: f.at, cp: f.cp });
|
|
30624
|
-
return {
|
|
30625
|
-
full: state.full,
|
|
30626
|
-
version: state.version,
|
|
30627
|
-
timestamp: state.timestamp,
|
|
30628
|
-
entry: state.entry,
|
|
30629
|
-
files,
|
|
30630
|
-
sinceCheckpoint: { ...state.sinceCheckpoint },
|
|
30631
|
-
movesApplied: state.movesApplied
|
|
30632
|
-
};
|
|
30633
|
-
}
|
|
30634
|
-
function infoOf(text5) {
|
|
30635
|
-
return JSON.parse(text5.slice(0, text5.indexOf("\n"))).commitInfo;
|
|
30565
|
+
function tocsOf(app) {
|
|
30566
|
+
let m = TOCS.get(app);
|
|
30567
|
+
if (!m) TOCS.set(app, m = /* @__PURE__ */ new Map());
|
|
30568
|
+
return m;
|
|
30636
30569
|
}
|
|
30637
|
-
|
|
30638
|
-
|
|
30639
|
-
return (await versionTexts(app, toc, toc.versions)).map(infoOf);
|
|
30570
|
+
function latestOf(toc) {
|
|
30571
|
+
return toc.versions.length > 0 ? toc.versions[toc.versions.length - 1] : -1;
|
|
30640
30572
|
}
|
|
30641
|
-
|
|
30642
|
-
const
|
|
30643
|
-
|
|
30644
|
-
|
|
30645
|
-
|
|
30646
|
-
|
|
30647
|
-
|
|
30648
|
-
|
|
30573
|
+
function parseSegment(text5) {
|
|
30574
|
+
const commits = [];
|
|
30575
|
+
let pos = 0;
|
|
30576
|
+
while (pos < text5.length) {
|
|
30577
|
+
const nl = text5.indexOf("\n", pos);
|
|
30578
|
+
if (nl < 0) break;
|
|
30579
|
+
let head;
|
|
30580
|
+
try {
|
|
30581
|
+
head = JSON.parse(text5.slice(pos, nl)).commit;
|
|
30582
|
+
} catch {
|
|
30583
|
+
break;
|
|
30584
|
+
}
|
|
30585
|
+
if (!head || !Number.isInteger(head.version) || !Number.isInteger(head.lines) || head.lines < 1) break;
|
|
30586
|
+
let end = nl + 1;
|
|
30587
|
+
let whole = true;
|
|
30588
|
+
for (let i = 0; i < head.lines; i++) {
|
|
30589
|
+
const e = text5.indexOf("\n", end);
|
|
30590
|
+
if (e < 0) {
|
|
30591
|
+
whole = false;
|
|
30592
|
+
break;
|
|
30593
|
+
}
|
|
30594
|
+
end = e + 1;
|
|
30595
|
+
}
|
|
30596
|
+
if (!whole) break;
|
|
30597
|
+
if (typeof head.hash === "string" && contentHash(text5.slice(nl + 1, end)) !== head.hash) break;
|
|
30598
|
+
const prev = commits[commits.length - 1];
|
|
30599
|
+
if (prev && head.version !== prev.version + 1) break;
|
|
30600
|
+
commits.push({ version: head.version, start: nl + 1, end });
|
|
30601
|
+
pos = end;
|
|
30649
30602
|
}
|
|
30650
|
-
return
|
|
30651
|
-
}
|
|
30652
|
-
async function history(app, settings, full, limit = 50) {
|
|
30653
|
-
const versions = (await listVersions(app, settings, full)).reverse().slice(0, Math.max(limit, 0));
|
|
30654
|
-
const out2 = [];
|
|
30655
|
-
for (const v of versions) out2.push((await readCommit(app, settings, full, v)).commitInfo);
|
|
30656
|
-
return out2;
|
|
30603
|
+
return { commits, validEnd: pos };
|
|
30657
30604
|
}
|
|
30658
|
-
function
|
|
30659
|
-
const
|
|
30660
|
-
|
|
30661
|
-
|
|
30662
|
-
const layout = catalogLayout(normalizeDatabaseFolder(settings.databaseFolder), ref2, options);
|
|
30663
|
-
return [...options.exclude ?? [], layout.archive, transactionLogRoot(settings)];
|
|
30605
|
+
function framed(version2, text5) {
|
|
30606
|
+
const lines = text5.split("\n").length - 1;
|
|
30607
|
+
return `${JSON.stringify({ commit: { version: version2, lines, hash: contentHash(text5) } })}
|
|
30608
|
+
${text5}`;
|
|
30664
30609
|
}
|
|
30665
|
-
async function
|
|
30666
|
-
|
|
30667
|
-
|
|
30668
|
-
|
|
30669
|
-
|
|
30670
|
-
|
|
30671
|
-
|
|
30672
|
-
|
|
30673
|
-
|
|
30674
|
-
const candidates = /* @__PURE__ */ new Map();
|
|
30675
|
-
for (const p of touched) if (p.endsWith(".md")) candidates.set(p, null);
|
|
30676
|
-
for (const f of app.vault.getMarkdownFiles()) {
|
|
30677
|
-
const fm = app.metadataCache.getFileCache(f)?.frontmatter;
|
|
30678
|
-
if (fm && fm[settings.tableKey] != null) candidates.set(f.path, f);
|
|
30610
|
+
async function segmentText(app, seg) {
|
|
30611
|
+
let cache = SEGMENT_TEXTS.get(app);
|
|
30612
|
+
if (!cache) SEGMENT_TEXTS.set(app, cache = /* @__PURE__ */ new Map());
|
|
30613
|
+
const key2 = `${seg.path}\0${seg.sealed ? "" : `${seg.chars}/${seg.last}`}\0${storeGeneration(app, seg.path)}`;
|
|
30614
|
+
const hit = cache.get(key2);
|
|
30615
|
+
if (hit) {
|
|
30616
|
+
cache.delete(key2);
|
|
30617
|
+
cache.set(key2, hit);
|
|
30618
|
+
return hit;
|
|
30679
30619
|
}
|
|
30680
|
-
const
|
|
30681
|
-
|
|
30682
|
-
|
|
30683
|
-
|
|
30620
|
+
const text5 = await storeReadFile(app, seg.path);
|
|
30621
|
+
const index = /* @__PURE__ */ new Map();
|
|
30622
|
+
for (const c of parseSegment(text5).commits) index.set(c.version, [c.start, c.end]);
|
|
30623
|
+
const out2 = { text: text5, index };
|
|
30624
|
+
cache.set(key2, out2);
|
|
30625
|
+
for (const k of cache.keys()) {
|
|
30626
|
+
if (cache.size <= SEGMENT_TEXTS_KEPT) break;
|
|
30627
|
+
cache.delete(k);
|
|
30684
30628
|
}
|
|
30685
30629
|
return out2;
|
|
30686
30630
|
}
|
|
30687
|
-
function
|
|
30688
|
-
const
|
|
30689
|
-
|
|
30690
|
-
|
|
30691
|
-
|
|
30692
|
-
let
|
|
30693
|
-
|
|
30694
|
-
|
|
30695
|
-
|
|
30696
|
-
|
|
30697
|
-
|
|
30698
|
-
|
|
30699
|
-
|
|
30700
|
-
|
|
30701
|
-
if (
|
|
30702
|
-
|
|
30703
|
-
|
|
30704
|
-
|
|
30705
|
-
|
|
30706
|
-
|
|
30707
|
-
|
|
30708
|
-
|
|
30709
|
-
|
|
30710
|
-
const
|
|
30711
|
-
|
|
30712
|
-
|
|
30713
|
-
const
|
|
30714
|
-
const
|
|
30715
|
-
|
|
30716
|
-
|
|
30717
|
-
|
|
30718
|
-
|
|
30719
|
-
|
|
30720
|
-
|
|
30721
|
-
excludes.set(full, catalogEntryOptions(full, t.entry, settings));
|
|
30722
|
-
for (const p of t.files.keys()) tableOf.set(p, full);
|
|
30723
|
-
}
|
|
30724
|
-
const touchedOnly = new Set([...tableOf.keys()].filter((p) => !(idx.note(p)?.tableValue != null)));
|
|
30725
|
-
live = { epoch: idx.epoch, cursor, model, only: kept2, tableKey: settings.tableKey, databaseFolder: settings.databaseFolder, tables, excludes, tableOf, touchedOnly };
|
|
30726
|
-
LIVE.set(app, live);
|
|
30727
|
-
} else {
|
|
30728
|
-
const named = new Set(touched.filter((p) => p.endsWith(".md")));
|
|
30729
|
-
const reader = rowStateReader(app, settings, model, live.tables, live.excludes);
|
|
30730
|
-
for (const path23 of /* @__PURE__ */ new Set([...changed2, ...named, ...live.touchedOnly])) {
|
|
30731
|
-
const was = live.tableOf.get(path23);
|
|
30732
|
-
if (was !== void 0) live.tables.get(was).files.delete(path23);
|
|
30733
|
-
live.tableOf.delete(path23);
|
|
30734
|
-
live.touchedOnly.delete(path23);
|
|
30735
|
-
const n = idx.note(path23);
|
|
30736
|
-
const listed = n !== void 0 && n.tableValue != null;
|
|
30737
|
-
if (!listed && !named.has(path23)) continue;
|
|
30738
|
-
const r = await reader(path23, n?.file ?? null);
|
|
30739
|
-
if (!r) continue;
|
|
30740
|
-
live.tables.get(r.table).files.set(path23, r.state);
|
|
30741
|
-
live.tableOf.set(path23, r.table);
|
|
30742
|
-
if (!listed) live.touchedOnly.add(path23);
|
|
30631
|
+
async function buildToc(app, dir) {
|
|
30632
|
+
const gen = storeGeneration(app, dir);
|
|
30633
|
+
const stat = await storeStat(app, dir);
|
|
30634
|
+
const toc = { dir, versions: [], files: /* @__PURE__ */ new Map(), segments: [], checkpoints: [], torn: null, gen, mtime: stat ? stat.mtime : null };
|
|
30635
|
+
for (const f of (await storeList(app, dir)).files) {
|
|
30636
|
+
let m;
|
|
30637
|
+
if (m = COMMIT_RE.exec(f)) toc.files.set(Number(m[1]), f);
|
|
30638
|
+
else if (m = CHECKPOINT_RE.exec(f)) toc.checkpoints.push({ version: Number(m[1]), kind: 1, path: f });
|
|
30639
|
+
else if (m = MANIFEST_RE.exec(f)) toc.checkpoints.push({ version: Number(m[1]), kind: 2, path: f });
|
|
30640
|
+
else if (m = SEGMENT_RE.exec(f)) toc.segments.push({ path: f, first: Number(m[1]), last: m[2] !== void 0 ? Number(m[2]) : Number(m[1]) - 1, sealed: m[2] !== void 0 });
|
|
30641
|
+
}
|
|
30642
|
+
toc.segments.sort((a, b) => a.first - b.first);
|
|
30643
|
+
toc.checkpoints.sort((a, b) => a.version - b.version || b.kind - a.kind);
|
|
30644
|
+
for (const seg of toc.segments) {
|
|
30645
|
+
if (seg.sealed) continue;
|
|
30646
|
+
const text5 = await storeReadFile(app, seg.path);
|
|
30647
|
+
const { commits, validEnd } = parseSegment(text5);
|
|
30648
|
+
seg.last = commits.length > 0 ? commits[commits.length - 1].version : seg.first - 1;
|
|
30649
|
+
seg.chars = text5.length;
|
|
30650
|
+
if (validEnd < text5.length) seg.tornAt = validEnd;
|
|
30651
|
+
const st = await storeStat(app, seg.path);
|
|
30652
|
+
if (st) seg.bytes = st.size;
|
|
30653
|
+
}
|
|
30654
|
+
const versions = new Set(toc.files.keys());
|
|
30655
|
+
for (const seg of toc.segments) for (let v = seg.first; v <= seg.last; v++) versions.add(v);
|
|
30656
|
+
toc.versions = [...versions].sort((a, b) => a - b);
|
|
30657
|
+
const newest = latestOf(toc);
|
|
30658
|
+
const file = toc.files.get(newest);
|
|
30659
|
+
if (file !== void 0 && !inSegment(toc, newest)) {
|
|
30660
|
+
const text5 = await storeRead(app, file);
|
|
30661
|
+
if (text5 === null || text5.trim() === "" || !wholeVersion(text5)) {
|
|
30662
|
+
toc.torn = file;
|
|
30663
|
+
toc.files.delete(newest);
|
|
30664
|
+
toc.versions.pop();
|
|
30743
30665
|
}
|
|
30744
|
-
live.cursor = cursor;
|
|
30745
30666
|
}
|
|
30746
|
-
return
|
|
30667
|
+
return toc;
|
|
30747
30668
|
}
|
|
30748
|
-
|
|
30749
|
-
|
|
30750
|
-
|
|
30751
|
-
|
|
30752
|
-
|
|
30753
|
-
|
|
30754
|
-
|
|
30755
|
-
|
|
30756
|
-
for (const [full, t] of now) {
|
|
30757
|
-
const a = kept2.get(full).files;
|
|
30758
|
-
const b = t.files;
|
|
30759
|
-
for (const p of /* @__PURE__ */ new Set([...a.keys(), ...b.keys()])) if (a.get(p)?.hash !== b.get(p)?.hash) out2.push(`${full}: ${p}`);
|
|
30669
|
+
function wholeVersion(text5) {
|
|
30670
|
+
try {
|
|
30671
|
+
const lines = text5.split("\n").filter((l) => l.trim() !== "");
|
|
30672
|
+
if (lines.length === 0 || !JSON.parse(lines[0]).commitInfo) return false;
|
|
30673
|
+
for (const l of lines.slice(1)) JSON.parse(l);
|
|
30674
|
+
return true;
|
|
30675
|
+
} catch {
|
|
30676
|
+
return false;
|
|
30760
30677
|
}
|
|
30761
|
-
if (out2.length > 0 && repair) forgetCurrentStates(app);
|
|
30762
|
-
return out2.sort();
|
|
30763
|
-
}
|
|
30764
|
-
function checkpointDue(state, format) {
|
|
30765
|
-
const since = state.sinceCheckpoint;
|
|
30766
|
-
if (since.bytes >= Math.max(CHECKPOINT_POLICY.bytesFactor * state.bytes, CHECKPOINT_POLICY.minBytes)) return true;
|
|
30767
|
-
if (format === "segments") return false;
|
|
30768
|
-
return since.versions >= Math.max(CHECKPOINT_POLICY.maxVersions, Math.ceil(state.bytes / CHECKPOINT_POLICY.versionBytes));
|
|
30769
|
-
}
|
|
30770
|
-
async function checkpointRows(app, settings, state) {
|
|
30771
|
-
const texts = await loggedTexts(app, settings, state, state.files.keys());
|
|
30772
|
-
if (texts.size !== state.files.size) return null;
|
|
30773
|
-
return [...state.files].sort(([a], [b]) => a < b ? -1 : 1).map(([path23, f]) => ({ path: path23, hash: f.hash, content: texts.get(path23) }));
|
|
30774
|
-
}
|
|
30775
|
-
async function writeDueCheckpoint(app, settings, full) {
|
|
30776
|
-
const state = LAST.get(app)?.get(full);
|
|
30777
|
-
if (!state || !checkpointDue(state, formatOf(settings))) return false;
|
|
30778
|
-
if (!await stillLatest(app, settings, state)) return false;
|
|
30779
|
-
const rows = await checkpointRows(app, settings, state);
|
|
30780
|
-
if (!rows) return false;
|
|
30781
|
-
await writeManifestCheckpoint(app, transactionLogDir(settings, full), { version: state.version, timestamp: state.timestamp, entry: state.entry, ...state.movesApplied ? { moves: state.movesApplied } : {} }, rows);
|
|
30782
|
-
state.sinceCheckpoint = { versions: 0, bytes: 0 };
|
|
30783
|
-
return true;
|
|
30784
|
-
}
|
|
30785
|
-
function formatOf(settings) {
|
|
30786
|
-
return settings.transactionLogFormat === "files" ? "files" : "segments";
|
|
30787
|
-
}
|
|
30788
|
-
function setCheckpointRunner(app, run2) {
|
|
30789
|
-
if (run2) CHECKPOINT_RUNNERS.set(app, run2);
|
|
30790
|
-
else CHECKPOINT_RUNNERS.delete(app);
|
|
30791
30678
|
}
|
|
30792
|
-
|
|
30793
|
-
let
|
|
30794
|
-
|
|
30795
|
-
|
|
30796
|
-
const next = due ? [...due][0] : void 0;
|
|
30797
|
-
if (!next || stopped()) break;
|
|
30798
|
-
const [full, settings] = next;
|
|
30799
|
-
due.delete(full);
|
|
30800
|
-
if (await withLogLock(app, () => writeDueCheckpoint(app, settings, full))) n++;
|
|
30679
|
+
function inSegment(toc, v) {
|
|
30680
|
+
for (let i = toc.segments.length - 1; i >= 0; i--) {
|
|
30681
|
+
const s = toc.segments[i];
|
|
30682
|
+
if (v >= s.first && v <= s.last) return s;
|
|
30801
30683
|
}
|
|
30802
|
-
return
|
|
30803
|
-
}
|
|
30804
|
-
async function wholeCheckpoint(app, dir, v) {
|
|
30805
|
-
return parseCheckpoint(await storeRead(app, checkpointPath(dir, v)) ?? "")?.version === v;
|
|
30684
|
+
return void 0;
|
|
30806
30685
|
}
|
|
30807
|
-
async function
|
|
30808
|
-
const
|
|
30809
|
-
|
|
30810
|
-
|
|
30811
|
-
|
|
30812
|
-
|
|
30813
|
-
entry: state.entry,
|
|
30814
|
-
files: files.map(([path23, f]) => ({ path: path23, hash: f.hash, content: f.content })),
|
|
30815
|
-
...state.movesApplied ? { moves: state.movesApplied } : {}
|
|
30816
|
-
};
|
|
30817
|
-
const text5 = JSON.stringify(cp);
|
|
30818
|
-
if (text5.length <= LEGACY_CHECKPOINT.maxChars) {
|
|
30819
|
-
const path23 = checkpointPath(dir, state.version);
|
|
30820
|
-
if (!await wholeCheckpoint(app, dir, state.version)) await storeWrite(app, path23, text5);
|
|
30821
|
-
return;
|
|
30822
|
-
}
|
|
30686
|
+
async function logToc(app, dir) {
|
|
30687
|
+
const tocs = tocsOf(app);
|
|
30688
|
+
const kept2 = tocs.get(dir);
|
|
30689
|
+
if (kept2 && kept2.gen === storeGeneration(app, dir)) {
|
|
30690
|
+
const stat = await storeStat(app, dir);
|
|
30691
|
+
if (stat !== void 0 ? (stat ? stat.mtime : null) === kept2.mtime : await stillNewest(app, kept2)) return kept2;
|
|
30823
30692
|
}
|
|
30824
|
-
await
|
|
30693
|
+
const toc = await buildToc(app, dir);
|
|
30694
|
+
tocs.set(dir, toc);
|
|
30695
|
+
return toc;
|
|
30825
30696
|
}
|
|
30826
|
-
function
|
|
30827
|
-
|
|
30697
|
+
async function stillNewest(app, toc) {
|
|
30698
|
+
const latest = latestOf(toc);
|
|
30699
|
+
const where = latest < 0 ? null : inSegment(toc, latest)?.path ?? toc.files.get(latest);
|
|
30700
|
+
if (where !== null && !await storeExists(app, where)) return false;
|
|
30701
|
+
const next = commitPath(toc.dir, latest + 1);
|
|
30702
|
+
if (next !== toc.torn && await storeExists(app, next)) return false;
|
|
30703
|
+
return !await storeExists(app, segmentPath(toc.dir, latest + 1));
|
|
30828
30704
|
}
|
|
30829
|
-
async function
|
|
30830
|
-
|
|
30705
|
+
async function settled(app, toc) {
|
|
30706
|
+
toc.gen = storeGeneration(app, toc.dir);
|
|
30707
|
+
const stat = await storeStat(app, toc.dir);
|
|
30708
|
+
toc.mtime = stat ? stat.mtime : null;
|
|
30831
30709
|
}
|
|
30832
|
-
async function
|
|
30833
|
-
|
|
30834
|
-
|
|
30835
|
-
|
|
30836
|
-
|
|
30837
|
-
|
|
30710
|
+
async function versionText(app, toc, v) {
|
|
30711
|
+
const segments = segmentsWith(toc, v);
|
|
30712
|
+
for (const seg of segments) {
|
|
30713
|
+
const s = await segmentText(app, seg);
|
|
30714
|
+
const at = s.index.get(v);
|
|
30715
|
+
if (at) return s.text.slice(at[0], at[1]);
|
|
30716
|
+
}
|
|
30717
|
+
const file = toc.files.get(v);
|
|
30718
|
+
if (file !== void 0) return storeReadFile(app, file);
|
|
30719
|
+
if (segments.length === 0) throw new HistoryError(`${toc.dir} has no version ${v}`);
|
|
30720
|
+
throw new HistoryError(
|
|
30721
|
+
`${segments[0].path} does not hold version ${v} whole`,
|
|
30722
|
+
guide(`A crash lost version ${v}: no file of the log holds it whole. Restore the store from its backup (datanotes store restore --from <backup url> --database <folder> --force), then run the command again.`, "restoreStore")
|
|
30723
|
+
);
|
|
30838
30724
|
}
|
|
30839
|
-
async function
|
|
30840
|
-
return
|
|
30725
|
+
async function wholeVersions(app, seg) {
|
|
30726
|
+
return new Set((await segmentText(app, seg)).index.keys());
|
|
30841
30727
|
}
|
|
30842
|
-
|
|
30843
|
-
|
|
30844
|
-
return latest >= 0 ? latest : null;
|
|
30728
|
+
function segmentsWith(toc, v) {
|
|
30729
|
+
return toc.segments.filter((s) => v >= s.first && v <= s.last).reverse();
|
|
30845
30730
|
}
|
|
30846
|
-
async function
|
|
30847
|
-
|
|
30848
|
-
|
|
30849
|
-
|
|
30850
|
-
|
|
30851
|
-
|
|
30852
|
-
|
|
30853
|
-
n++;
|
|
30854
|
-
});
|
|
30731
|
+
async function versionTexts(app, toc, versions) {
|
|
30732
|
+
const out2 = new Array(versions.length);
|
|
30733
|
+
const files = [];
|
|
30734
|
+
for (let i = 0; i < versions.length; i++) {
|
|
30735
|
+
const v = versions[i];
|
|
30736
|
+
if (inSegment(toc, v)) out2[i] = await versionText(app, toc, v);
|
|
30737
|
+
else files.push(i);
|
|
30855
30738
|
}
|
|
30856
|
-
|
|
30739
|
+
for (let i = 0; i < files.length; i += READ_AHEAD) {
|
|
30740
|
+
const chunk = files.slice(i, i + READ_AHEAD);
|
|
30741
|
+
const texts = await Promise.all(chunk.map((j) => versionText(app, toc, versions[j])));
|
|
30742
|
+
texts.forEach((t, k) => out2[chunk[k]] = t);
|
|
30743
|
+
}
|
|
30744
|
+
return out2;
|
|
30857
30745
|
}
|
|
30858
|
-
async function
|
|
30859
|
-
const
|
|
30860
|
-
|
|
30861
|
-
|
|
30862
|
-
|
|
30863
|
-
|
|
30864
|
-
|
|
30865
|
-
|
|
30866
|
-
|
|
30867
|
-
|
|
30868
|
-
}
|
|
30746
|
+
async function setAside(app, path23) {
|
|
30747
|
+
const to = `${path23}.torn-${Date.now()}`;
|
|
30748
|
+
await storeRename(app, path23, to);
|
|
30749
|
+
return to;
|
|
30750
|
+
}
|
|
30751
|
+
async function appendVersion(app, dir, v, text5, format) {
|
|
30752
|
+
const toc = await logToc(app, dir);
|
|
30753
|
+
if (latestOf(toc) !== v - 1) {
|
|
30754
|
+
const file = toc.files.get(v);
|
|
30755
|
+
throw new HistoryError(file !== void 0 ? `log file exists: ${file}` : `log moved on: ${dir} is at version ${latestOf(toc)}, not ${v - 1}`);
|
|
30869
30756
|
}
|
|
30870
|
-
|
|
30871
|
-
|
|
30872
|
-
|
|
30757
|
+
const repaired = [];
|
|
30758
|
+
if (toc.torn) {
|
|
30759
|
+
repaired.push(`${toc.torn} set aside as ${await setAside(app, toc.torn)}`);
|
|
30760
|
+
toc.torn = null;
|
|
30873
30761
|
}
|
|
30874
|
-
for (const
|
|
30875
|
-
|
|
30876
|
-
|
|
30762
|
+
for (const seg of toc.segments) {
|
|
30763
|
+
if (seg.sealed || seg.tornAt === void 0 && seg.chars > 0) continue;
|
|
30764
|
+
const whole = (await storeReadFile(app, seg.path)).slice(0, seg.tornAt ?? 0);
|
|
30765
|
+
repaired.push(`${seg.path}: ${seg.chars - whole.length} characters of a torn version cut${whole === "" ? ", the file removed" : ""}`);
|
|
30766
|
+
if (whole === "") await storeRemove(app, seg.path);
|
|
30767
|
+
else await storeWrite(app, seg.path, whole);
|
|
30768
|
+
seg.chars = whole.length;
|
|
30769
|
+
delete seg.tornAt;
|
|
30770
|
+
const st = await storeStat(app, seg.path);
|
|
30771
|
+
if (st) seg.bytes = st.size;
|
|
30877
30772
|
}
|
|
30878
|
-
|
|
30879
|
-
|
|
30880
|
-
|
|
30773
|
+
toc.segments = toc.segments.filter((s) => s.sealed || s.chars > 0);
|
|
30774
|
+
if (format === "files") {
|
|
30775
|
+
const path23 = commitPath(dir, v);
|
|
30776
|
+
if (await storeExists(app, path23)) throw new HistoryError(`log file exists: ${path23}`);
|
|
30777
|
+
await storeWrite(app, path23, text5);
|
|
30778
|
+
toc.files.set(v, path23);
|
|
30779
|
+
} else await appendToSegment(app, toc, v, text5);
|
|
30780
|
+
toc.versions.push(v);
|
|
30781
|
+
await settled(app, toc);
|
|
30782
|
+
return repaired;
|
|
30881
30783
|
}
|
|
30882
|
-
function
|
|
30883
|
-
const
|
|
30884
|
-
|
|
30784
|
+
async function appendToSegment(app, toc, v, text5) {
|
|
30785
|
+
const chunk = framed(v, text5);
|
|
30786
|
+
const open2 = toc.segments.filter((s) => !s.sealed);
|
|
30787
|
+
const active = open2[open2.length - 1];
|
|
30788
|
+
const fits = active && active.last === v - 1 && active.chars < SEGMENT_POLICY.maxBytes && active.last - active.first + 1 < SEGMENT_POLICY.maxVersions;
|
|
30789
|
+
for (const seg of open2) if (!(fits && seg === active)) await seal(app, toc, seg);
|
|
30790
|
+
if (fits) {
|
|
30791
|
+
const st2 = await storeStat(app, active.path);
|
|
30792
|
+
if (st2 && active.bytes !== void 0 && st2.size !== active.bytes) throw new HistoryError(`log moved on: ${active.path} changed`);
|
|
30793
|
+
await storeAppend(app, active.path, chunk);
|
|
30794
|
+
active.last = v;
|
|
30795
|
+
active.chars += chunk.length;
|
|
30796
|
+
const after = await storeStat(app, active.path);
|
|
30797
|
+
if (after) active.bytes = after.size;
|
|
30798
|
+
return;
|
|
30799
|
+
}
|
|
30800
|
+
const path23 = segmentPath(toc.dir, v);
|
|
30801
|
+
if (await storeExists(app, path23)) throw new HistoryError(`log file exists: ${path23}`);
|
|
30802
|
+
await storeWrite(app, path23, chunk);
|
|
30803
|
+
const st = await storeStat(app, path23);
|
|
30804
|
+
toc.segments.push({ path: path23, first: v, last: v, sealed: false, chars: chunk.length, ...st ? { bytes: st.size } : {} });
|
|
30885
30805
|
}
|
|
30886
|
-
async function
|
|
30887
|
-
const
|
|
30888
|
-
|
|
30889
|
-
|
|
30890
|
-
|
|
30891
|
-
|
|
30892
|
-
|
|
30893
|
-
|
|
30894
|
-
|
|
30895
|
-
const names2 = /* @__PURE__ */ new Set([...enabled, ...orphans]);
|
|
30806
|
+
async function seal(app, toc, seg) {
|
|
30807
|
+
const to = segmentPath(toc.dir, seg.first, seg.last);
|
|
30808
|
+
await storeRename(app, seg.path, to);
|
|
30809
|
+
seg.path = to;
|
|
30810
|
+
seg.sealed = true;
|
|
30811
|
+
delete seg.bytes;
|
|
30812
|
+
delete seg.chars;
|
|
30813
|
+
}
|
|
30814
|
+
async function writeSealedSegments(app, dir, versions) {
|
|
30896
30815
|
const out2 = [];
|
|
30897
|
-
|
|
30898
|
-
|
|
30899
|
-
const
|
|
30900
|
-
|
|
30901
|
-
|
|
30902
|
-
|
|
30903
|
-
|
|
30904
|
-
|
|
30905
|
-
|
|
30906
|
-
|
|
30907
|
-
|
|
30908
|
-
|
|
30909
|
-
|
|
30910
|
-
|
|
30911
|
-
|
|
30912
|
-
|
|
30913
|
-
const { entry, add, remove: remove3, metadataChanged } = changes;
|
|
30914
|
-
const operation = prev.version < 0 && options.operation === EXTERNAL_EDIT ? "CREATE TABLE" : options.operation;
|
|
30915
|
-
planned.push({ table: full, add, remove: remove3, metadataChanged, entry, prev, operation });
|
|
30916
|
-
}
|
|
30917
|
-
const external = planned.filter((p) => p.operation === EXTERNAL_EDIT);
|
|
30918
|
-
let counts;
|
|
30919
|
-
if (options.check && external.length > 0) {
|
|
30920
|
-
const added = new Set(external.flatMap((p) => p.add.map((a) => a.path)));
|
|
30921
|
-
const changes = [];
|
|
30922
|
-
for (const p of external) {
|
|
30923
|
-
const wanted = /* @__PURE__ */ new Set([...p.remove.map((r) => r.path), ...[...added].filter((x) => p.prev.files.has(x))]);
|
|
30924
|
-
const texts = await loggedTexts(app, settings, p.prev, wanted);
|
|
30925
|
-
const before = /* @__PURE__ */ new Map();
|
|
30926
|
-
for (const x of wanted) if (texts.has(x)) before.set(x, { hash: p.prev.files.get(x).hash, content: texts.get(x) });
|
|
30927
|
-
const { prev: _prev, operation: _op, ...change } = p;
|
|
30928
|
-
changes.push({ ...change, before });
|
|
30929
|
-
}
|
|
30930
|
-
counts = await options.check(changes);
|
|
30931
|
-
}
|
|
30932
|
-
for (const { table: full, add, remove: remove3, metadataChanged, entry, prev, operation } of planned) {
|
|
30933
|
-
const version2 = prev.version + 1;
|
|
30934
|
-
const violations = operation === EXTERNAL_EDIT ? counts?.get(full) : void 0;
|
|
30935
|
-
const commit = {
|
|
30936
|
-
commitInfo: {
|
|
30937
|
-
version: version2,
|
|
30938
|
-
timestamp: now.toISOString(),
|
|
30939
|
-
operation,
|
|
30940
|
-
...options.parameters && Object.keys(options.parameters).length > 0 ? { operationParameters: options.parameters } : {},
|
|
30941
|
-
...options.migrationId ? { migrationId: options.migrationId } : {},
|
|
30942
|
-
operationMetrics: { numAddedFiles: add.length, numRemovedFiles: remove3.length, metadataChanged, ...violations !== void 0 ? { violations } : {} },
|
|
30943
|
-
engineInfo: "datanotes",
|
|
30944
|
-
...options.actor ? { userName: actorName(options.actor), actor: options.actor } : {}
|
|
30945
|
-
},
|
|
30946
|
-
...metadataChanged ? { metaData: { entry } } : {},
|
|
30947
|
-
add: [...add].sort((a, b) => a.path.localeCompare(b.path)),
|
|
30948
|
-
remove: [...remove3].sort((a, b) => a.path.localeCompare(b.path))
|
|
30949
|
-
};
|
|
30950
|
-
const text5 = serializeCommit(commit);
|
|
30951
|
-
const repaired = await appendVersion(app, transactionLogDir(settings, full), version2, text5, format);
|
|
30952
|
-
for (const r of repaired) telemetry().log("WARN", "datanotes transaction log repaired a version torn by a crash", { table: full, repair: r });
|
|
30953
|
-
applyLogged(prev, commit);
|
|
30954
|
-
prev.sinceCheckpoint = { versions: prev.sinceCheckpoint.versions + 1, bytes: prev.sinceCheckpoint.bytes + text5.length };
|
|
30955
|
-
if (version2 > 0 && checkpointDue(prev, format)) {
|
|
30956
|
-
let due = DUE.get(app);
|
|
30957
|
-
if (!due) DUE.set(app, due = /* @__PURE__ */ new Map());
|
|
30958
|
-
due.set(full, settings);
|
|
30959
|
-
}
|
|
30960
|
-
out2.push({ table: full, version: version2, operation: commit.commitInfo.operation });
|
|
30961
|
-
}
|
|
30962
|
-
if (DUE.get(app)?.size) {
|
|
30963
|
-
const runner = CHECKPOINT_RUNNERS.get(app);
|
|
30964
|
-
if (runner) runner();
|
|
30965
|
-
else {
|
|
30966
|
-
const due = DUE.get(app);
|
|
30967
|
-
for (const [full, s] of [...due]) {
|
|
30968
|
-
due.delete(full);
|
|
30969
|
-
await writeDueCheckpoint(app, s, full);
|
|
30970
|
-
}
|
|
30971
|
-
}
|
|
30816
|
+
let run2 = [];
|
|
30817
|
+
let chars = 0;
|
|
30818
|
+
const flush2 = async () => {
|
|
30819
|
+
if (run2.length === 0) return;
|
|
30820
|
+
const path23 = segmentPath(dir, run2[0].version, run2[run2.length - 1].version);
|
|
30821
|
+
if (await holdsWhole(app, path23, run2.map((r) => r.version))) storeSyncNext(app, path23);
|
|
30822
|
+
else await storeWrite(app, path23, run2.map((r) => framed(r.version, r.text)).join(""));
|
|
30823
|
+
out2.push(path23);
|
|
30824
|
+
run2 = [];
|
|
30825
|
+
chars = 0;
|
|
30826
|
+
};
|
|
30827
|
+
for (const r of versions) {
|
|
30828
|
+
const last = run2[run2.length - 1];
|
|
30829
|
+
if (last && (r.version !== last.version + 1 || chars >= SEGMENT_POLICY.maxBytes || run2.length >= SEGMENT_POLICY.maxVersions)) await flush2();
|
|
30830
|
+
run2.push(r);
|
|
30831
|
+
chars += r.text.length;
|
|
30972
30832
|
}
|
|
30833
|
+
await flush2();
|
|
30973
30834
|
return out2;
|
|
30974
30835
|
}
|
|
30975
|
-
function
|
|
30976
|
-
|
|
30977
|
-
if (
|
|
30978
|
-
const
|
|
30979
|
-
|
|
30980
|
-
const prev = LOCKS.get(app) ?? Promise.resolve();
|
|
30981
|
-
const wait = startSpan("engine.lock.acquire", { attributes: { "datanotes.lock.queued": a.pending - 1 } });
|
|
30982
|
-
const held = () => {
|
|
30983
|
-
wait.end();
|
|
30984
|
-
return phase("engine.lock.hold", async () => {
|
|
30985
|
-
try {
|
|
30986
|
-
return await fn();
|
|
30987
|
-
} finally {
|
|
30988
|
-
await storeFlush(app).catch((e) => telemetry().log("WARN", "datanotes could not sync the store's files", { error: messageOf(e) }));
|
|
30989
|
-
}
|
|
30990
|
-
});
|
|
30991
|
-
};
|
|
30992
|
-
const run2 = prev.then(held, held);
|
|
30993
|
-
const settled2 = run2.then(
|
|
30994
|
-
() => void 0,
|
|
30995
|
-
() => void 0
|
|
30996
|
-
);
|
|
30997
|
-
LOCKS.set(app, settled2);
|
|
30998
|
-
void settled2.then(() => {
|
|
30999
|
-
a.pending--;
|
|
31000
|
-
a.lastEnd = Date.now();
|
|
31001
|
-
});
|
|
31002
|
-
return run2;
|
|
30836
|
+
async function holdsWhole(app, path23, versions) {
|
|
30837
|
+
const text5 = await storeRead(app, path23);
|
|
30838
|
+
if (text5 === null) return false;
|
|
30839
|
+
const { commits, validEnd } = parseSegment(text5);
|
|
30840
|
+
return validEnd === text5.length && commits.length === versions.length && commits.every((c, i) => c.version === versions[i]);
|
|
31003
30841
|
}
|
|
31004
|
-
function
|
|
31005
|
-
const
|
|
31006
|
-
return
|
|
30842
|
+
async function gzip(text5) {
|
|
30843
|
+
const stream = new Blob([text5]).stream().pipeThrough(new CompressionStream("gzip"));
|
|
30844
|
+
return new Response(stream).arrayBuffer();
|
|
31007
30845
|
}
|
|
31008
|
-
async function
|
|
31009
|
-
const
|
|
31010
|
-
|
|
31011
|
-
const files = cp.kind === 1 ? [cp.path] : manifestFiles(dir, cp.path, (await readManifest(app, dir, cp.path))?.head ?? null);
|
|
31012
|
-
out2.set(cp.version, [...out2.get(cp.version) ?? [], ...files]);
|
|
31013
|
-
}
|
|
31014
|
-
return out2;
|
|
30846
|
+
async function gunzip(data) {
|
|
30847
|
+
const stream = new Blob([data]).stream().pipeThrough(new DecompressionStream("gzip"));
|
|
30848
|
+
return new Response(stream).text();
|
|
31015
30849
|
}
|
|
31016
|
-
|
|
31017
|
-
const
|
|
31018
|
-
|
|
31019
|
-
|
|
31020
|
-
|
|
31021
|
-
|
|
31022
|
-
|
|
31023
|
-
|
|
31024
|
-
|
|
31025
|
-
const straddling = toc.segments.filter((s) => s.first < keep && s.last >= keep);
|
|
31026
|
-
for (const s of toc.segments) if (s.last < keep || straddling.includes(s)) doomed.push(s.path);
|
|
31027
|
-
for (const [v, files] of await checkpointFiles(app, dir, toc)) if (v < keep) doomed.push(...files);
|
|
31028
|
-
doomed.sort();
|
|
31029
|
-
if (!dryRun) {
|
|
31030
|
-
await writeCheckpoint(app, dir, await tableStateAt(app, settings, full, { version: keep }), formatOf(settings));
|
|
31031
|
-
for (const s of straddling) {
|
|
31032
|
-
const kept2 = [];
|
|
31033
|
-
for (let v = keep; v <= s.last; v++) kept2.push({ version: v, text: await versionText(app, toc, v) });
|
|
31034
|
-
await writeSealedSegments(app, dir, kept2);
|
|
30850
|
+
function chunked(lines) {
|
|
30851
|
+
const out2 = [];
|
|
30852
|
+
let cur = [];
|
|
30853
|
+
let chars = 0;
|
|
30854
|
+
for (const l of lines) {
|
|
30855
|
+
if (cur.length > 0 && chars + l.length + 1 > CHECKPOINT_PART.maxChars) {
|
|
30856
|
+
out2.push(cur);
|
|
30857
|
+
cur = [];
|
|
30858
|
+
chars = 0;
|
|
31035
30859
|
}
|
|
31036
|
-
|
|
31037
|
-
|
|
31038
|
-
forgetLoggedStates(app);
|
|
30860
|
+
cur.push(l);
|
|
30861
|
+
chars += l.length + 1;
|
|
31039
30862
|
}
|
|
31040
|
-
|
|
30863
|
+
if (cur.length > 0 || out2.length === 0) out2.push(cur);
|
|
30864
|
+
return out2;
|
|
31041
30865
|
}
|
|
31042
|
-
async function
|
|
31043
|
-
const
|
|
31044
|
-
|
|
31045
|
-
const
|
|
31046
|
-
|
|
31047
|
-
|
|
31048
|
-
|
|
31049
|
-
|
|
31050
|
-
|
|
31051
|
-
|
|
31052
|
-
|
|
31053
|
-
|
|
31054
|
-
|
|
31055
|
-
|
|
31056
|
-
}
|
|
31057
|
-
|
|
31058
|
-
|
|
31059
|
-
|
|
31060
|
-
|
|
31061
|
-
|
|
31062
|
-
|
|
31063
|
-
|
|
31064
|
-
|
|
31065
|
-
|
|
31066
|
-
|
|
31067
|
-
|
|
31068
|
-
|
|
31069
|
-
|
|
31070
|
-
|
|
31071
|
-
|
|
31072
|
-
|
|
31073
|
-
|
|
31074
|
-
|
|
31075
|
-
|
|
31076
|
-
|
|
31077
|
-
|
|
31078
|
-
|
|
31079
|
-
|
|
31080
|
-
|
|
31081
|
-
|
|
31082
|
-
|
|
31083
|
-
|
|
30866
|
+
async function writeManifestCheckpoint(app, dir, head, rows) {
|
|
30867
|
+
const main6 = manifestPath(dir, head.version);
|
|
30868
|
+
if (await wholeManifest(app, dir, main6)) return [];
|
|
30869
|
+
const binary = storeHoldsBinary(app);
|
|
30870
|
+
const written2 = [];
|
|
30871
|
+
const textLines = rows.map((r) => JSON.stringify([r.path, r.content]));
|
|
30872
|
+
const partOf = [];
|
|
30873
|
+
const parts = chunked(textLines);
|
|
30874
|
+
let row = 0;
|
|
30875
|
+
const partNames = [];
|
|
30876
|
+
for (const [i, lines] of parts.entries()) {
|
|
30877
|
+
const name = `${pad2(head.version)}.part-${i}.jsonl${binary ? ".gz" : ""}`;
|
|
30878
|
+
const text5 = lines.length > 0 ? `${lines.join("\n")}
|
|
30879
|
+
` : "";
|
|
30880
|
+
if (binary) await storeWriteBinary(app, `${dir}/${name}`, await gzip(text5));
|
|
30881
|
+
else await storeWrite(app, `${dir}/${name}`, text5);
|
|
30882
|
+
written2.push(`${dir}/${name}`);
|
|
30883
|
+
partNames.push(name);
|
|
30884
|
+
for (let k = 0; k < lines.length; k++) partOf[row++] = i;
|
|
30885
|
+
}
|
|
30886
|
+
const entries = rows.map((r, i) => JSON.stringify([r.path, r.hash, r.content.length, partOf[i]]));
|
|
30887
|
+
const pieces = chunked(entries);
|
|
30888
|
+
const more = pieces.slice(1).map((_, i) => `${pad2(head.version)}.manifest-${i + 1}.jsonl`);
|
|
30889
|
+
for (const [i, lines] of pieces.slice(1).entries()) {
|
|
30890
|
+
await storeWrite(app, `${dir}/${more[i]}`, `${lines.join("\n")}
|
|
30891
|
+
`);
|
|
30892
|
+
written2.push(`${dir}/${more[i]}`);
|
|
30893
|
+
}
|
|
30894
|
+
const header2 = { format: 2, version: head.version, timestamp: head.timestamp, entry: head.entry, ...head.moves ? { moves: head.moves } : {}, rows: rows.length, parts: partNames, ...more.length > 0 ? { more } : {} };
|
|
30895
|
+
await storeWrite(app, main6, `${JSON.stringify({ checkpoint: header2 })}
|
|
30896
|
+
${pieces[0].length > 0 ? `${pieces[0].join("\n")}
|
|
30897
|
+
` : ""}`);
|
|
30898
|
+
written2.push(main6);
|
|
30899
|
+
return written2;
|
|
30900
|
+
}
|
|
30901
|
+
async function readManifest(app, dir, path23) {
|
|
30902
|
+
try {
|
|
30903
|
+
const text5 = await storeRead(app, path23);
|
|
30904
|
+
if (text5 == null) return null;
|
|
30905
|
+
const lines = text5.split("\n").filter((l) => l !== "");
|
|
30906
|
+
const head = JSON.parse(lines[0] ?? "null")?.checkpoint;
|
|
30907
|
+
if (!head || head.format !== 2) return null;
|
|
30908
|
+
const rows = lines.slice(1).map((l) => JSON.parse(l));
|
|
30909
|
+
for (const name of head.more ?? []) {
|
|
30910
|
+
const more = await storeRead(app, `${dir}/${name}`);
|
|
30911
|
+
if (more == null) return null;
|
|
30912
|
+
for (const l of more.split("\n")) if (l !== "") rows.push(JSON.parse(l));
|
|
31084
30913
|
}
|
|
31085
|
-
|
|
31086
|
-
|
|
31087
|
-
|
|
31088
|
-
|
|
30914
|
+
if (rows.length !== head.rows) return null;
|
|
30915
|
+
return { head, rows };
|
|
30916
|
+
} catch {
|
|
30917
|
+
return null;
|
|
30918
|
+
}
|
|
30919
|
+
}
|
|
30920
|
+
async function wholeManifest(app, dir, path23) {
|
|
30921
|
+
const m = await readManifest(app, dir, path23);
|
|
30922
|
+
if (!m) return false;
|
|
30923
|
+
for (const name of m.head.parts) if (!await readPart(app, dir, name)) return false;
|
|
30924
|
+
return true;
|
|
30925
|
+
}
|
|
30926
|
+
async function readPart(app, dir, name) {
|
|
30927
|
+
try {
|
|
30928
|
+
let text5;
|
|
30929
|
+
if (name.endsWith(".gz")) {
|
|
30930
|
+
const data = await storeReadBinary(app, `${dir}/${name}`);
|
|
30931
|
+
text5 = data ? await gunzip(data) : null;
|
|
30932
|
+
} else text5 = await storeRead(app, `${dir}/${name}`);
|
|
30933
|
+
if (text5 == null) return null;
|
|
30934
|
+
const out2 = /* @__PURE__ */ new Map();
|
|
30935
|
+
for (const l of text5.split("\n")) {
|
|
30936
|
+
if (l === "") continue;
|
|
30937
|
+
const [path23, content] = JSON.parse(l);
|
|
30938
|
+
out2.set(path23, content);
|
|
31089
30939
|
}
|
|
30940
|
+
return out2;
|
|
30941
|
+
} catch {
|
|
30942
|
+
return null;
|
|
31090
30943
|
}
|
|
31091
|
-
forgetLoggedStates(app);
|
|
31092
|
-
return result;
|
|
31093
30944
|
}
|
|
31094
|
-
|
|
31095
|
-
|
|
31096
|
-
|
|
30945
|
+
function manifestFiles(dir, path23, head) {
|
|
30946
|
+
return [path23, ...(head?.more ?? []).map((n) => `${dir}/${n}`), ...(head?.parts ?? []).map((n) => `${dir}/${n}`)];
|
|
30947
|
+
}
|
|
30948
|
+
var HistoryError, SEGMENT_POLICY, CHECKPOINT_PART, pad2, commitPath, checkpointPath, manifestPath, segmentPath, COMMIT_RE, CHECKPOINT_RE, MANIFEST_RE, SEGMENT_RE, TOCS, SEGMENT_TEXTS, SEGMENT_TEXTS_KEPT, READ_AHEAD;
|
|
30949
|
+
var init_log_files = __esm({
|
|
30950
|
+
"src/core/log-files.ts"() {
|
|
31097
30951
|
"use strict";
|
|
30952
|
+
init_hints();
|
|
31098
30953
|
init_file_cache();
|
|
31099
|
-
init_frontmatter();
|
|
31100
|
-
init_metastore();
|
|
31101
|
-
init_actor();
|
|
31102
30954
|
init_store();
|
|
31103
|
-
|
|
31104
|
-
|
|
31105
|
-
|
|
31106
|
-
|
|
31107
|
-
|
|
31108
|
-
init_tracing();
|
|
31109
|
-
init_vault_index();
|
|
31110
|
-
init_telemetry();
|
|
31111
|
-
init_message();
|
|
31112
|
-
init_log_files();
|
|
31113
|
-
TRANSACTION_LOG_FOLDER = "transaction_log";
|
|
31114
|
-
CHECKPOINT_POLICY = { bytesFactor: 2, minBytes: 256 * 1024, maxVersions: 1e3, versionBytes: 16 * 1024 };
|
|
31115
|
-
LEGACY_CHECKPOINT = { maxChars: 32 * 1024 * 1024 };
|
|
31116
|
-
EXTERNAL_EDIT = "EXTERNAL_EDIT";
|
|
31117
|
-
AbsentTableError = class extends HistoryError {
|
|
30955
|
+
HistoryError = class extends GuidedError {
|
|
30956
|
+
constructor() {
|
|
30957
|
+
super(...arguments);
|
|
30958
|
+
this.name = "HistoryError";
|
|
30959
|
+
}
|
|
31118
30960
|
};
|
|
31119
|
-
|
|
31120
|
-
|
|
31121
|
-
|
|
31122
|
-
|
|
31123
|
-
|
|
31124
|
-
|
|
31125
|
-
|
|
31126
|
-
|
|
30961
|
+
SEGMENT_POLICY = { maxBytes: 8 * 1024 * 1024, maxVersions: 1e3 };
|
|
30962
|
+
CHECKPOINT_PART = { maxChars: 4 * 1024 * 1024 };
|
|
30963
|
+
pad2 = (v) => String(v).padStart(20, "0");
|
|
30964
|
+
commitPath = (dir, v) => `${dir}/${pad2(v)}.json`;
|
|
30965
|
+
checkpointPath = (dir, v) => `${dir}/${pad2(v)}.checkpoint.json`;
|
|
30966
|
+
manifestPath = (dir, v) => `${dir}/${pad2(v)}.manifest.jsonl`;
|
|
30967
|
+
segmentPath = (dir, first2, last) => `${dir}/${pad2(first2)}${last === void 0 ? "" : `-${pad2(last)}`}.segment.jsonl`;
|
|
30968
|
+
COMMIT_RE = /\/(\d{20})\.json$/;
|
|
30969
|
+
CHECKPOINT_RE = /\/(\d{20})\.checkpoint\.json$/;
|
|
30970
|
+
MANIFEST_RE = /\/(\d{20})\.manifest\.jsonl$/;
|
|
30971
|
+
SEGMENT_RE = /\/(\d{20})(?:-(\d{20}))?\.segment\.jsonl$/;
|
|
30972
|
+
TOCS = /* @__PURE__ */ new WeakMap();
|
|
30973
|
+
SEGMENT_TEXTS = /* @__PURE__ */ new WeakMap();
|
|
30974
|
+
SEGMENT_TEXTS_KEPT = 3;
|
|
30975
|
+
READ_AHEAD = 16;
|
|
31127
30976
|
}
|
|
31128
30977
|
});
|
|
31129
30978
|
|
|
31130
|
-
// src/core/
|
|
31131
|
-
function
|
|
30979
|
+
// src/core/transaction-log.ts
|
|
30980
|
+
async function unreadableNow(app, path23) {
|
|
30981
|
+
const f = app.vault.getAbstractFileByPath(path23);
|
|
30982
|
+
if (f == null || !("extension" in f)) return false;
|
|
31132
30983
|
try {
|
|
31133
|
-
|
|
31134
|
-
return contentHash(JSON.stringify([ordered, body]));
|
|
30984
|
+
return frontmatterError((await readHashed(app, f)).content) !== null;
|
|
31135
30985
|
} catch {
|
|
31136
|
-
return
|
|
30986
|
+
return false;
|
|
31137
30987
|
}
|
|
31138
30988
|
}
|
|
31139
|
-
function
|
|
31140
|
-
return
|
|
30989
|
+
function transactionLogRoot(_settings) {
|
|
30990
|
+
return storePath(TRANSACTION_LOG_FOLDER);
|
|
31141
30991
|
}
|
|
31142
|
-
function
|
|
31143
|
-
|
|
31144
|
-
return split2 ? split2.body : text5;
|
|
30992
|
+
function transactionLogDir(settings, full) {
|
|
30993
|
+
return `${transactionLogRoot(settings)}/${full}`;
|
|
31145
30994
|
}
|
|
31146
|
-
function
|
|
31147
|
-
|
|
31148
|
-
|
|
31149
|
-
|
|
30995
|
+
function entryHash(entry) {
|
|
30996
|
+
if (entry == null) return "";
|
|
30997
|
+
let h = ENTRY_HASHES.get(entry);
|
|
30998
|
+
if (h === void 0) ENTRY_HASHES.set(entry, h = contentHash(JSON.stringify(entry)));
|
|
30999
|
+
return h;
|
|
31150
31000
|
}
|
|
31151
|
-
async function
|
|
31152
|
-
|
|
31153
|
-
|
|
31154
|
-
|
|
31155
|
-
|
|
31001
|
+
async function loggedTables(app, settings, among) {
|
|
31002
|
+
const base = `${transactionLogRoot(settings)}/`;
|
|
31003
|
+
const out2 = [];
|
|
31004
|
+
for (const dir of (await storeList(app, transactionLogRoot(settings))).folders) {
|
|
31005
|
+
const full = dir.slice(base.length);
|
|
31006
|
+
if (among && !among(full)) continue;
|
|
31007
|
+
if ((await storeList(app, dir)).files.some(holdsVersions)) out2.push(full);
|
|
31008
|
+
}
|
|
31009
|
+
return out2.sort();
|
|
31156
31010
|
}
|
|
31157
|
-
function
|
|
31158
|
-
return
|
|
31159
|
-
op: op2,
|
|
31160
|
-
summary,
|
|
31161
|
-
...schema !== void 0 ? { schema } : {},
|
|
31162
|
-
edits: [],
|
|
31163
|
-
creates: [],
|
|
31164
|
-
moves: [],
|
|
31165
|
-
deletes: [],
|
|
31166
|
-
texts: []
|
|
31167
|
-
};
|
|
31011
|
+
function tocOf(app, settings, full) {
|
|
31012
|
+
return logToc(app, transactionLogDir(settings, full));
|
|
31168
31013
|
}
|
|
31169
|
-
function
|
|
31170
|
-
return
|
|
31014
|
+
async function listVersions(app, settings, full) {
|
|
31015
|
+
return [...(await tocOf(app, settings, full)).versions];
|
|
31171
31016
|
}
|
|
31172
|
-
function
|
|
31173
|
-
const
|
|
31174
|
-
const
|
|
31175
|
-
|
|
31176
|
-
|
|
31177
|
-
|
|
31178
|
-
|
|
31179
|
-
|
|
31180
|
-
|
|
31181
|
-
afterAbsent: []
|
|
31182
|
-
};
|
|
31183
|
-
const keys = /* @__PURE__ */ new Set([...b.keys(), ...a.keys()]);
|
|
31184
|
-
for (const k of keys) {
|
|
31185
|
-
const inB = b.has(k);
|
|
31186
|
-
const inA = a.has(k);
|
|
31187
|
-
if (inB && inA && same(b.get(k), a.get(k))) continue;
|
|
31188
|
-
if (inB) d.beforeValues[k] = b.get(k);
|
|
31189
|
-
else d.beforeAbsent.push(k);
|
|
31190
|
-
if (inA) {
|
|
31191
|
-
d.afterValues[k] = a.get(k);
|
|
31192
|
-
d.set.push([k, a.get(k)]);
|
|
31193
|
-
} else {
|
|
31194
|
-
d.afterAbsent.push(k);
|
|
31195
|
-
d.remove.push(k);
|
|
31196
|
-
}
|
|
31017
|
+
function parseCommit(text5) {
|
|
31018
|
+
const commit = { commitInfo: void 0, add: [], remove: [] };
|
|
31019
|
+
for (const line of text5.split("\n")) {
|
|
31020
|
+
if (line.trim() === "") continue;
|
|
31021
|
+
const action = JSON.parse(line);
|
|
31022
|
+
if (action.commitInfo) commit.commitInfo = action.commitInfo;
|
|
31023
|
+
else if ("metaData" in action) commit.metaData = action.metaData;
|
|
31024
|
+
else if (action.add) commit.add.push(action.add);
|
|
31025
|
+
else if (action.remove) commit.remove.push(action.remove);
|
|
31197
31026
|
}
|
|
31198
|
-
|
|
31027
|
+
if (!commit.commitInfo) throw new HistoryError("log commit without commitInfo");
|
|
31028
|
+
return commit;
|
|
31199
31029
|
}
|
|
31200
|
-
function
|
|
31201
|
-
const
|
|
31202
|
-
|
|
31203
|
-
|
|
31204
|
-
|
|
31205
|
-
)
|
|
31206
|
-
|
|
31030
|
+
function serializeCommit(c) {
|
|
31031
|
+
const lines = [JSON.stringify({ commitInfo: c.commitInfo })];
|
|
31032
|
+
if (c.metaData !== void 0) lines.push(JSON.stringify({ metaData: c.metaData }));
|
|
31033
|
+
for (const r of c.remove) lines.push(JSON.stringify({ remove: r }));
|
|
31034
|
+
for (const a of c.add) lines.push(JSON.stringify({ add: a }));
|
|
31035
|
+
return `${lines.join("\n")}
|
|
31036
|
+
`;
|
|
31207
31037
|
}
|
|
31208
|
-
function
|
|
31209
|
-
|
|
31210
|
-
|
|
31211
|
-
|
|
31212
|
-
return fmReorder(out2, order);
|
|
31038
|
+
async function readCommit(app, settings, full, version2) {
|
|
31039
|
+
const toc = await tocOf(app, settings, full);
|
|
31040
|
+
if (!toc.versions.includes(version2)) throw new HistoryError(`${full} has no version ${version2}`);
|
|
31041
|
+
return movedCommit(parseCommit(await versionText(app, toc, version2)), await databaseMoves(app));
|
|
31213
31042
|
}
|
|
31214
|
-
function
|
|
31215
|
-
|
|
31216
|
-
|
|
31043
|
+
function movedCommit(c, moves) {
|
|
31044
|
+
let out2 = c;
|
|
31045
|
+
for (const m of moves) {
|
|
31046
|
+
if (m.at <= c.commitInfo.timestamp) continue;
|
|
31047
|
+
out2 = {
|
|
31048
|
+
...out2,
|
|
31049
|
+
...out2.metaData !== void 0 ? { metaData: { entry: moveEntryPaths(out2.metaData.entry, m.from, m.to, m.entries) } } : {},
|
|
31050
|
+
add: out2.add.map((a) => ({ ...a, path: movePath(a.path, m.from, m.to, m.entries), content: moveLinks(a.content, m.from, m.to, m.entries) })),
|
|
31051
|
+
remove: out2.remove.map((r) => ({ ...r, path: movePath(r.path, m.from, m.to, m.entries) }))
|
|
31052
|
+
};
|
|
31053
|
+
}
|
|
31054
|
+
return out2;
|
|
31217
31055
|
}
|
|
31218
|
-
function
|
|
31219
|
-
const
|
|
31220
|
-
|
|
31056
|
+
function moveState(state, m) {
|
|
31057
|
+
const files = /* @__PURE__ */ new Map();
|
|
31058
|
+
for (const [path23, f] of state.files) {
|
|
31059
|
+
const content = moveLinks(f.content, m.from, m.to, m.entries);
|
|
31060
|
+
files.set(movePath(path23, m.from, m.to, m.entries), content === f.content ? f : { ...f, hash: contentHash(content), content });
|
|
31061
|
+
}
|
|
31062
|
+
state.files = files;
|
|
31063
|
+
state.entry = moveEntryPaths(state.entry, m.from, m.to, m.entries);
|
|
31221
31064
|
}
|
|
31222
|
-
|
|
31223
|
-
|
|
31224
|
-
if (app.vault.getAbstractFileByPath(folder) != null) return;
|
|
31225
|
-
await ensureFolder(app, parentFolder(folder));
|
|
31226
|
-
await app.vault.createFolder(folder);
|
|
31065
|
+
function emptyState(full) {
|
|
31066
|
+
return { full, version: -1, timestamp: "", entry: null, files: /* @__PURE__ */ new Map() };
|
|
31227
31067
|
}
|
|
31228
|
-
|
|
31229
|
-
|
|
31068
|
+
function applyCommit(state, c) {
|
|
31069
|
+
const files = state.files;
|
|
31070
|
+
const at = c.commitInfo.version;
|
|
31071
|
+
for (const r of c.remove) files.delete(r.path);
|
|
31072
|
+
for (const a of c.add) files.set(a.path, { hash: a.hash, content: a.content, at });
|
|
31073
|
+
return {
|
|
31074
|
+
full: state.full,
|
|
31075
|
+
version: c.commitInfo.version,
|
|
31076
|
+
timestamp: c.commitInfo.timestamp,
|
|
31077
|
+
entry: c.metaData !== void 0 ? c.metaData.entry : state.entry,
|
|
31078
|
+
files
|
|
31079
|
+
};
|
|
31230
31080
|
}
|
|
31231
|
-
|
|
31232
|
-
|
|
31081
|
+
function applyLogged(state, c) {
|
|
31082
|
+
const at = c.commitInfo.version;
|
|
31083
|
+
for (const r of c.remove) {
|
|
31084
|
+
const old = state.files.get(r.path);
|
|
31085
|
+
if (old) state.bytes -= old.size;
|
|
31086
|
+
state.files.delete(r.path);
|
|
31087
|
+
}
|
|
31088
|
+
for (const a of c.add) {
|
|
31089
|
+
const old = state.files.get(a.path);
|
|
31090
|
+
if (old) state.bytes -= old.size;
|
|
31091
|
+
const size = a.content.length;
|
|
31092
|
+
state.files.set(a.path, { hash: a.hash, size, at });
|
|
31093
|
+
state.bytes += size;
|
|
31094
|
+
}
|
|
31095
|
+
state.version = at;
|
|
31096
|
+
state.timestamp = c.commitInfo.timestamp;
|
|
31097
|
+
if (c.metaData !== void 0) state.entry = c.metaData.entry;
|
|
31233
31098
|
}
|
|
31234
|
-
|
|
31235
|
-
|
|
31099
|
+
function parseCheckpoint(text5) {
|
|
31100
|
+
try {
|
|
31101
|
+
const c = JSON.parse(text5);
|
|
31102
|
+
return c !== null && typeof c === "object" && Number.isInteger(c.version) && Array.isArray(c.files) ? c : null;
|
|
31103
|
+
} catch {
|
|
31104
|
+
return null;
|
|
31105
|
+
}
|
|
31236
31106
|
}
|
|
31237
|
-
async function
|
|
31238
|
-
|
|
31239
|
-
|
|
31240
|
-
|
|
31241
|
-
|
|
31242
|
-
|
|
31243
|
-
|
|
31244
|
-
if (!moved.has(f)) out2.push({ from: `${from}/${f.slice(to.length + 1)}`, to: f });
|
|
31245
|
-
}
|
|
31107
|
+
async function readCheckpoint(app, dir, cp) {
|
|
31108
|
+
if (cp.kind === 1) {
|
|
31109
|
+
const text5 = await storeRead(app, cp.path);
|
|
31110
|
+
if (text5 == null) return null;
|
|
31111
|
+
const c = parseCheckpoint(text5);
|
|
31112
|
+
if (!c) return null;
|
|
31113
|
+
return { version: c.version, timestamp: c.timestamp, entry: c.entry, ...c.moves !== void 0 ? { moves: c.moves } : {}, files: new Map(c.files.map((f) => [f.path, { hash: f.hash, content: f.content, at: c.version, cp: true }])) };
|
|
31246
31114
|
}
|
|
31247
|
-
|
|
31115
|
+
const m = await readManifest(app, dir, cp.path);
|
|
31116
|
+
if (!m) return null;
|
|
31117
|
+
const texts = /* @__PURE__ */ new Map();
|
|
31118
|
+
for (const name of m.head.parts) {
|
|
31119
|
+
const part = await readPart(app, dir, name);
|
|
31120
|
+
if (!part) return null;
|
|
31121
|
+
for (const [p, t] of part) texts.set(p, t);
|
|
31122
|
+
}
|
|
31123
|
+
const files = /* @__PURE__ */ new Map();
|
|
31124
|
+
for (const [path23, hash] of m.rows) {
|
|
31125
|
+
const content = texts.get(path23);
|
|
31126
|
+
if (content === void 0) return null;
|
|
31127
|
+
files.set(path23, { hash, content, at: m.head.version, cp: true });
|
|
31128
|
+
}
|
|
31129
|
+
return { version: m.head.version, timestamp: m.head.timestamp, entry: m.head.entry, ...m.head.moves !== void 0 ? { moves: m.head.moves } : {}, files };
|
|
31248
31130
|
}
|
|
31249
|
-
function
|
|
31250
|
-
|
|
31131
|
+
async function tableStateAt(app, settings, full, asOf2 = {}) {
|
|
31132
|
+
const known = LAST.get(app)?.get(full);
|
|
31133
|
+
if (known && known.version >= 0 && isLatest(known, asOf2) && await stillLatest(app, settings, known)) {
|
|
31134
|
+
const fromNotes = await stateFromLogged(app, settings, known);
|
|
31135
|
+
if (fromNotes) return fromNotes;
|
|
31136
|
+
}
|
|
31137
|
+
return replayState(app, settings, full, asOf2);
|
|
31251
31138
|
}
|
|
31252
|
-
|
|
31253
|
-
|
|
31254
|
-
|
|
31255
|
-
|
|
31256
|
-
|
|
31257
|
-
|
|
31258
|
-
|
|
31259
|
-
|
|
31260
|
-
|
|
31261
|
-
|
|
31262
|
-
|
|
31263
|
-
|
|
31264
|
-
|
|
31139
|
+
function isLatest(known, asOf2) {
|
|
31140
|
+
if (asOf2.version !== void 0) return asOf2.version === known.version && asOf2.timestamp === void 0;
|
|
31141
|
+
if (asOf2.timestamp === void 0) return true;
|
|
31142
|
+
const at = Date.parse(asOf2.timestamp);
|
|
31143
|
+
return !Number.isNaN(at) && at >= Date.parse(known.timestamp);
|
|
31144
|
+
}
|
|
31145
|
+
async function replayState(app, settings, full, asOf2 = {}) {
|
|
31146
|
+
const dir = transactionLogDir(settings, full);
|
|
31147
|
+
const toc = await logToc(app, dir);
|
|
31148
|
+
const versions = toc.versions;
|
|
31149
|
+
if (versions.length === 0) {
|
|
31150
|
+
if (asOf2.version !== void 0 || asOf2.timestamp !== void 0) {
|
|
31151
|
+
throw new HistoryError(
|
|
31152
|
+
`${full} has no history: time travel is off for it (turn it on with configure_database(history=true) or alter_table(history=true))`
|
|
31153
|
+
);
|
|
31265
31154
|
}
|
|
31266
|
-
|
|
31267
|
-
|
|
31268
|
-
|
|
31269
|
-
|
|
31270
|
-
|
|
31271
|
-
|
|
31272
|
-
const r = results[i];
|
|
31273
|
-
if (!r?.ok) continue;
|
|
31274
|
-
try {
|
|
31275
|
-
await r.written.undo();
|
|
31276
|
-
} catch {
|
|
31277
|
-
kept2.add(i);
|
|
31278
|
-
}
|
|
31155
|
+
return emptyState(full);
|
|
31156
|
+
}
|
|
31157
|
+
let target = versions[versions.length - 1];
|
|
31158
|
+
if (asOf2.version !== void 0) {
|
|
31159
|
+
if (!versions.includes(asOf2.version)) {
|
|
31160
|
+
throw new HistoryError(`${full} has no version ${asOf2.version} (available: ${versions[0]}..${target})`);
|
|
31279
31161
|
}
|
|
31162
|
+
target = asOf2.version;
|
|
31280
31163
|
}
|
|
31281
|
-
const
|
|
31282
|
-
|
|
31283
|
-
|
|
31284
|
-
|
|
31285
|
-
const
|
|
31286
|
-
|
|
31287
|
-
}
|
|
31288
|
-
async function moveFile(app, from, to) {
|
|
31289
|
-
if (inStore(from)) return storeRename(app, from, to);
|
|
31290
|
-
const f = fileOrNull(app, from);
|
|
31291
|
-
if (!f) throw new Error(`file not found: ${from}`);
|
|
31292
|
-
if (app.vault.getAbstractFileByPath(to) != null) throw new Error(`target exists: ${to}`);
|
|
31293
|
-
await ensureFolder(app, parentFolder(to));
|
|
31294
|
-
await app.fileManager.renameFile(f, to);
|
|
31295
|
-
}
|
|
31296
|
-
async function applyChangeSetImpl(app, settings, cs, now = /* @__PURE__ */ new Date()) {
|
|
31297
|
-
const rows = [];
|
|
31298
|
-
const creates = [];
|
|
31299
|
-
const moves = [];
|
|
31300
|
-
const references = [];
|
|
31301
|
-
const deleted = [];
|
|
31302
|
-
const failed2 = [];
|
|
31303
|
-
let referencesUpdated = 0;
|
|
31304
|
-
const fail2 = (path23, e) => {
|
|
31305
|
-
failed2.push({ path: path23, error: messageOf(e) });
|
|
31164
|
+
const limitMs = asOf2.timestamp !== void 0 ? Date.parse(asOf2.timestamp) : void 0;
|
|
31165
|
+
if (limitMs !== void 0 && Number.isNaN(limitMs)) throw new HistoryError(`"timestamp" is not a date-time: ${asOf2.timestamp}`);
|
|
31166
|
+
const moves = await databaseMoves(app);
|
|
31167
|
+
let applied = 0;
|
|
31168
|
+
const moveUpTo = (timestamp) => {
|
|
31169
|
+
while (applied < moves.length && (timestamp === null || moves[applied].at <= timestamp)) moveState(state, moves[applied++]);
|
|
31306
31170
|
};
|
|
31307
|
-
|
|
31308
|
-
|
|
31309
|
-
|
|
31310
|
-
|
|
31311
|
-
|
|
31312
|
-
|
|
31313
|
-
|
|
31314
|
-
|
|
31315
|
-
|
|
31316
|
-
|
|
31317
|
-
|
|
31318
|
-
|
|
31319
|
-
|
|
31320
|
-
|
|
31321
|
-
|
|
31322
|
-
|
|
31323
|
-
|
|
31324
|
-
|
|
31325
|
-
|
|
31326
|
-
|
|
31327
|
-
|
|
31328
|
-
|
|
31329
|
-
|
|
31330
|
-
|
|
31331
|
-
|
|
31332
|
-
|
|
31333
|
-
|
|
31334
|
-
|
|
31335
|
-
|
|
31336
|
-
|
|
31337
|
-
|
|
31338
|
-
|
|
31339
|
-
|
|
31340
|
-
|
|
31341
|
-
|
|
31342
|
-
|
|
31343
|
-
|
|
31344
|
-
|
|
31345
|
-
|
|
31346
|
-
|
|
31347
|
-
|
|
31348
|
-
|
|
31349
|
-
|
|
31350
|
-
|
|
31351
|
-
break run;
|
|
31171
|
+
let state = emptyState(full);
|
|
31172
|
+
for (const cp of [...toc.checkpoints].reverse()) {
|
|
31173
|
+
if (cp.version > target) continue;
|
|
31174
|
+
const c = await readCheckpoint(app, dir, cp);
|
|
31175
|
+
if (!c) continue;
|
|
31176
|
+
if (limitMs !== void 0 && Date.parse(c.timestamp) > limitMs) continue;
|
|
31177
|
+
state = { full, version: c.version, timestamp: c.timestamp, entry: c.entry, files: c.files };
|
|
31178
|
+
applied = Math.min(c.moves ?? moves.filter((m) => m.at <= c.timestamp).length, moves.length);
|
|
31179
|
+
break;
|
|
31180
|
+
}
|
|
31181
|
+
const since = { versions: 0, bytes: 0 };
|
|
31182
|
+
const replay = versions.filter((v) => v > state.version && v <= target);
|
|
31183
|
+
const texts = await versionTexts(app, toc, replay);
|
|
31184
|
+
for (const text5 of texts) {
|
|
31185
|
+
const c = parseCommit(text5);
|
|
31186
|
+
if (limitMs !== void 0 && Date.parse(c.commitInfo.timestamp) > limitMs) break;
|
|
31187
|
+
moveUpTo(c.commitInfo.timestamp);
|
|
31188
|
+
state = applyCommit(state, c);
|
|
31189
|
+
since.versions++;
|
|
31190
|
+
since.bytes += text5.length;
|
|
31191
|
+
}
|
|
31192
|
+
moveUpTo(null);
|
|
31193
|
+
state.sinceCheckpoint = since;
|
|
31194
|
+
state.movesApplied = applied;
|
|
31195
|
+
if (state.version < 0) throw new AbsentTableError(`${full} did not exist at ${asOf2.timestamp}`);
|
|
31196
|
+
return state;
|
|
31197
|
+
}
|
|
31198
|
+
async function replayLogged(app, settings, full) {
|
|
31199
|
+
const dir = transactionLogDir(settings, full);
|
|
31200
|
+
const toc = await logToc(app, dir);
|
|
31201
|
+
const empty = { full, version: -1, timestamp: "", entry: null, files: /* @__PURE__ */ new Map(), sinceCheckpoint: { versions: 0, bytes: 0 }, bytes: 0 };
|
|
31202
|
+
if (toc.versions.length === 0) return empty;
|
|
31203
|
+
const moves = await databaseMoves(app);
|
|
31204
|
+
let state = empty;
|
|
31205
|
+
let applied = 0;
|
|
31206
|
+
for (const cp of [...toc.checkpoints].reverse()) {
|
|
31207
|
+
if (cp.kind === 2) {
|
|
31208
|
+
const m = await readManifest(app, dir, cp.path);
|
|
31209
|
+
if (!m || !await partsThere(app, dir, m.head.parts)) continue;
|
|
31210
|
+
const files = /* @__PURE__ */ new Map();
|
|
31211
|
+
let bytes = 0;
|
|
31212
|
+
for (const [path23, hash, size] of m.rows) {
|
|
31213
|
+
files.set(path23, { hash, size, at: m.head.version, cp: true });
|
|
31214
|
+
bytes += size;
|
|
31352
31215
|
}
|
|
31353
|
-
|
|
31354
|
-
|
|
31355
|
-
|
|
31356
|
-
|
|
31357
|
-
|
|
31358
|
-
const
|
|
31359
|
-
|
|
31360
|
-
const
|
|
31361
|
-
|
|
31362
|
-
|
|
31363
|
-
const fmChanged = d.set.length > 0 || d.remove.length > 0 || !same(e.before.map(([k]) => k), order);
|
|
31364
|
-
if (fmChanged) {
|
|
31365
|
-
await writeFrontmatter(app, file, (fm) => applyDiff(fm, d.set, d.remove, order));
|
|
31216
|
+
state = { full, version: m.head.version, timestamp: m.head.timestamp, entry: m.head.entry, files, sinceCheckpoint: { versions: 0, bytes: 0 }, bytes };
|
|
31217
|
+
applied = Math.min(m.head.moves ?? moves.filter((x) => x.at <= m.head.timestamp).length, moves.length);
|
|
31218
|
+
} else {
|
|
31219
|
+
const c = await readCheckpoint(app, dir, cp);
|
|
31220
|
+
if (!c) continue;
|
|
31221
|
+
const files = /* @__PURE__ */ new Map();
|
|
31222
|
+
let bytes = 0;
|
|
31223
|
+
for (const [path23, f] of c.files) {
|
|
31224
|
+
files.set(path23, { hash: f.hash, size: f.content.length, at: c.version, cp: true });
|
|
31225
|
+
bytes += f.content.length;
|
|
31366
31226
|
}
|
|
31367
|
-
|
|
31368
|
-
|
|
31369
|
-
path: e.path,
|
|
31370
|
-
before: d.beforeValues,
|
|
31371
|
-
before_absent: d.beforeAbsent,
|
|
31372
|
-
before_order: e.before.map(([k]) => k),
|
|
31373
|
-
after: d.afterValues,
|
|
31374
|
-
after_absent: d.afterAbsent,
|
|
31375
|
-
...bodyChanges(e) ? { body_before: e.bodyBefore, body_after: e.bodyAfter } : {}
|
|
31376
|
-
};
|
|
31377
|
-
return { value: row, undo: async () => void await app.vault.process(file, () => was) };
|
|
31378
|
-
}, atOnce);
|
|
31379
|
-
rows.push(...edited.done);
|
|
31380
|
-
if (edited.failed) {
|
|
31381
|
-
failed2.push(edited.failed);
|
|
31382
|
-
break run;
|
|
31227
|
+
state = { full, version: c.version, timestamp: c.timestamp, entry: c.entry, files, sinceCheckpoint: { versions: 0, bytes: 0 }, bytes };
|
|
31228
|
+
applied = Math.min(c.moves ?? moves.filter((x) => x.at <= c.timestamp).length, moves.length);
|
|
31383
31229
|
}
|
|
31384
|
-
|
|
31230
|
+
break;
|
|
31231
|
+
}
|
|
31232
|
+
if (applied < moves.length) return loggedOf(await replayState(app, settings, full));
|
|
31233
|
+
const replay = toc.versions.filter((v) => v > state.version);
|
|
31234
|
+
for (const text5 of await versionTexts(app, toc, replay)) {
|
|
31235
|
+
applyLogged(state, parseCommit(text5));
|
|
31236
|
+
state.sinceCheckpoint.versions++;
|
|
31237
|
+
state.sinceCheckpoint.bytes += text5.length;
|
|
31238
|
+
}
|
|
31239
|
+
if (moves.length > 0) state.movesApplied = applied;
|
|
31240
|
+
return state;
|
|
31241
|
+
}
|
|
31242
|
+
async function partsThere(app, dir, parts) {
|
|
31243
|
+
const { files } = await storeList(app, dir);
|
|
31244
|
+
const there = new Set(files);
|
|
31245
|
+
return parts.every((n) => there.has(`${dir}/${n}`));
|
|
31246
|
+
}
|
|
31247
|
+
function loggedOf(state) {
|
|
31248
|
+
const files = /* @__PURE__ */ new Map();
|
|
31249
|
+
let bytes = 0;
|
|
31250
|
+
for (const [path23, f] of state.files) {
|
|
31251
|
+
files.set(path23, { hash: f.hash, size: f.content.length, at: f.at, cp: f.cp });
|
|
31252
|
+
bytes += f.content.length;
|
|
31253
|
+
}
|
|
31254
|
+
return {
|
|
31255
|
+
full: state.full,
|
|
31256
|
+
version: state.version,
|
|
31257
|
+
timestamp: state.timestamp,
|
|
31258
|
+
entry: state.entry,
|
|
31259
|
+
files,
|
|
31260
|
+
sinceCheckpoint: state.sinceCheckpoint,
|
|
31261
|
+
movesApplied: state.movesApplied,
|
|
31262
|
+
bytes
|
|
31263
|
+
};
|
|
31264
|
+
}
|
|
31265
|
+
async function loggedTexts(app, settings, state, paths) {
|
|
31266
|
+
const out2 = /* @__PURE__ */ new Map();
|
|
31267
|
+
const fromCommit = /* @__PURE__ */ new Map();
|
|
31268
|
+
const fromCheckpoint = /* @__PURE__ */ new Map();
|
|
31269
|
+
for (const path23 of paths) {
|
|
31270
|
+
const f = state.files.get(path23);
|
|
31271
|
+
const note = app.vault.getAbstractFileByPath(path23);
|
|
31272
|
+
if (note && "extension" in note) {
|
|
31385
31273
|
try {
|
|
31386
|
-
await
|
|
31387
|
-
|
|
31388
|
-
|
|
31389
|
-
|
|
31390
|
-
|
|
31274
|
+
const h = await readHashed(app, note);
|
|
31275
|
+
if (h.hash === f.hash) {
|
|
31276
|
+
out2.set(path23, h.content);
|
|
31277
|
+
continue;
|
|
31278
|
+
}
|
|
31279
|
+
} catch {
|
|
31391
31280
|
}
|
|
31392
31281
|
}
|
|
31393
|
-
|
|
31394
|
-
|
|
31395
|
-
|
|
31396
|
-
|
|
31397
|
-
|
|
31398
|
-
|
|
31399
|
-
|
|
31400
|
-
|
|
31401
|
-
|
|
31402
|
-
|
|
31403
|
-
|
|
31282
|
+
const by = f.cp ? fromCheckpoint : fromCommit;
|
|
31283
|
+
const list4 = by.get(f.at);
|
|
31284
|
+
if (list4) list4.push(path23);
|
|
31285
|
+
else by.set(f.at, [path23]);
|
|
31286
|
+
}
|
|
31287
|
+
for (const [v, list4] of fromCommit) {
|
|
31288
|
+
let c;
|
|
31289
|
+
try {
|
|
31290
|
+
c = await readCommit(app, settings, state.full, v);
|
|
31291
|
+
} catch {
|
|
31292
|
+
continue;
|
|
31404
31293
|
}
|
|
31294
|
+
const want = new Set(list4);
|
|
31295
|
+
for (const a of c.add) if (want.has(a.path) && contentHash(a.content) === state.files.get(a.path).hash) out2.set(a.path, a.content);
|
|
31405
31296
|
}
|
|
31406
|
-
const
|
|
31407
|
-
const
|
|
31408
|
-
|
|
31409
|
-
const f = fileOrNull(app, movedTo.get(p) ?? p);
|
|
31410
|
-
if (!f) continue;
|
|
31297
|
+
for (const [v, list4] of fromCheckpoint) await textsFromCheckpoint(app, settings, state, v, list4, out2);
|
|
31298
|
+
const missing = [...fromCommit.values(), ...fromCheckpoint.values()].flat().filter((p) => !out2.has(p));
|
|
31299
|
+
if (missing.length > 0) {
|
|
31411
31300
|
try {
|
|
31412
|
-
|
|
31301
|
+
const whole = await replayState(app, settings, state.full, { version: state.version });
|
|
31302
|
+
for (const p of missing) {
|
|
31303
|
+
const x = whole.files.get(p);
|
|
31304
|
+
if (x && x.hash === state.files.get(p).hash) out2.set(p, x.content);
|
|
31305
|
+
}
|
|
31413
31306
|
} catch {
|
|
31414
31307
|
}
|
|
31415
31308
|
}
|
|
31416
|
-
|
|
31417
|
-
|
|
31418
|
-
|
|
31419
|
-
|
|
31420
|
-
|
|
31421
|
-
|
|
31422
|
-
|
|
31423
|
-
|
|
31424
|
-
|
|
31425
|
-
|
|
31426
|
-
|
|
31427
|
-
|
|
31428
|
-
|
|
31429
|
-
|
|
31430
|
-
|
|
31431
|
-
|
|
31432
|
-
|
|
31433
|
-
|
|
31434
|
-
);
|
|
31309
|
+
return out2;
|
|
31310
|
+
}
|
|
31311
|
+
async function textsFromCheckpoint(app, settings, state, version2, paths, out2) {
|
|
31312
|
+
const dir = transactionLogDir(settings, state.full);
|
|
31313
|
+
const toc = await logToc(app, dir);
|
|
31314
|
+
const cp = [...toc.checkpoints].reverse().find((c) => c.version === version2);
|
|
31315
|
+
if (!cp) return;
|
|
31316
|
+
const want = new Set(paths);
|
|
31317
|
+
const keep = (path23, content) => {
|
|
31318
|
+
if (want.has(path23) && contentHash(content) === state.files.get(path23).hash) out2.set(path23, content);
|
|
31319
|
+
};
|
|
31320
|
+
if (cp.kind === 1) {
|
|
31321
|
+
const c = await readCheckpoint(app, dir, cp).catch(() => null);
|
|
31322
|
+
for (const [path23, f] of c?.files ?? []) keep(path23, f.content);
|
|
31323
|
+
return;
|
|
31324
|
+
}
|
|
31325
|
+
const m = await readManifest(app, dir, cp.path);
|
|
31326
|
+
if (!m) return;
|
|
31327
|
+
const partOf = new Map(m.rows.map(([path23, , , part]) => [path23, part]));
|
|
31328
|
+
const parts = new Set(paths.map((p) => partOf.get(p)).filter((x) => x !== void 0));
|
|
31329
|
+
for (const i of parts) {
|
|
31330
|
+
const part = await readPart(app, dir, m.head.parts[i]);
|
|
31331
|
+
for (const [path23, content] of part ?? []) keep(path23, content);
|
|
31332
|
+
}
|
|
31333
|
+
}
|
|
31334
|
+
async function stateFromLogged(app, settings, state) {
|
|
31335
|
+
const texts = await loggedTexts(app, settings, state, state.files.keys());
|
|
31336
|
+
if (texts.size !== state.files.size) return null;
|
|
31337
|
+
const files = /* @__PURE__ */ new Map();
|
|
31338
|
+
for (const [path23, f] of state.files) files.set(path23, { hash: f.hash, content: texts.get(path23), at: f.at, cp: f.cp });
|
|
31435
31339
|
return {
|
|
31436
|
-
|
|
31437
|
-
|
|
31438
|
-
|
|
31439
|
-
|
|
31440
|
-
|
|
31441
|
-
|
|
31442
|
-
|
|
31443
|
-
referencesUpdated,
|
|
31444
|
-
failed: failed2
|
|
31340
|
+
full: state.full,
|
|
31341
|
+
version: state.version,
|
|
31342
|
+
timestamp: state.timestamp,
|
|
31343
|
+
entry: state.entry,
|
|
31344
|
+
files,
|
|
31345
|
+
sinceCheckpoint: { ...state.sinceCheckpoint },
|
|
31346
|
+
movesApplied: state.movesApplied
|
|
31445
31347
|
};
|
|
31446
31348
|
}
|
|
31447
|
-
|
|
31448
|
-
|
|
31449
|
-
|
|
31450
|
-
|
|
31451
|
-
|
|
31452
|
-
|
|
31349
|
+
function infoOf(text5) {
|
|
31350
|
+
return JSON.parse(text5.slice(0, text5.indexOf("\n"))).commitInfo;
|
|
31351
|
+
}
|
|
31352
|
+
async function commitInfos(app, settings, full) {
|
|
31353
|
+
const toc = await tocOf(app, settings, full);
|
|
31354
|
+
return (await versionTexts(app, toc, toc.versions)).map(infoOf);
|
|
31355
|
+
}
|
|
31356
|
+
async function newestCommitInfos(app, settings, full, limit, from) {
|
|
31357
|
+
const toc = await tocOf(app, settings, full);
|
|
31358
|
+
const out2 = [];
|
|
31359
|
+
for (const v of [...toc.versions].reverse()) {
|
|
31360
|
+
if (out2.length >= limit) break;
|
|
31361
|
+
const info = infoOf(await versionText(app, toc, v));
|
|
31362
|
+
if (from !== void 0 && Date.parse(info.timestamp) < from) break;
|
|
31363
|
+
out2.push(info);
|
|
31453
31364
|
}
|
|
31365
|
+
return out2;
|
|
31454
31366
|
}
|
|
31455
|
-
async function
|
|
31456
|
-
const
|
|
31457
|
-
const
|
|
31458
|
-
const
|
|
31459
|
-
|
|
31460
|
-
|
|
31461
|
-
|
|
31462
|
-
|
|
31463
|
-
|
|
31464
|
-
|
|
31465
|
-
const
|
|
31466
|
-
|
|
31467
|
-
|
|
31468
|
-
|
|
31469
|
-
|
|
31470
|
-
|
|
31471
|
-
|
|
31472
|
-
|
|
31473
|
-
|
|
31474
|
-
|
|
31475
|
-
|
|
31476
|
-
|
|
31367
|
+
async function history(app, settings, full, limit = 50) {
|
|
31368
|
+
const versions = (await listVersions(app, settings, full)).reverse().slice(0, Math.max(limit, 0));
|
|
31369
|
+
const out2 = [];
|
|
31370
|
+
for (const v of versions) out2.push((await readCommit(app, settings, full, v)).commitInfo);
|
|
31371
|
+
return out2;
|
|
31372
|
+
}
|
|
31373
|
+
function catalogEntryOptions(full, entry, settings) {
|
|
31374
|
+
const [c, s, t] = full.split(".");
|
|
31375
|
+
const ref2 = refOf(c, s, t);
|
|
31376
|
+
const options = parseTableOptions(entryToOrdered(ref2, entry, settings).find(([k]) => k === TABLE_KEY)?.[1]);
|
|
31377
|
+
const layout = catalogLayout(normalizeDatabaseFolder(settings.databaseFolder), ref2, options);
|
|
31378
|
+
return [...options.exclude ?? [], layout.archive, transactionLogRoot(settings)];
|
|
31379
|
+
}
|
|
31380
|
+
async function readCurrentStates(app, settings, model, touched, only) {
|
|
31381
|
+
const out2 = /* @__PURE__ */ new Map();
|
|
31382
|
+
const excludes = /* @__PURE__ */ new Map();
|
|
31383
|
+
for (const { ref: ref2, entry } of catalogTables(model)) {
|
|
31384
|
+
if (only && !only.has(ref2.full)) continue;
|
|
31385
|
+
out2.set(ref2.full, { entry, files: /* @__PURE__ */ new Map() });
|
|
31386
|
+
excludes.set(ref2.full, catalogEntryOptions(ref2.full, entry, settings));
|
|
31387
|
+
}
|
|
31388
|
+
if (out2.size === 0) return out2;
|
|
31389
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
31390
|
+
for (const p of touched) if (p.endsWith(".md")) candidates.set(p, null);
|
|
31391
|
+
for (const f of app.vault.getMarkdownFiles()) {
|
|
31392
|
+
const fm = app.metadataCache.getFileCache(f)?.frontmatter;
|
|
31393
|
+
if (fm && fm[settings.tableKey] != null) candidates.set(f.path, f);
|
|
31394
|
+
}
|
|
31395
|
+
const reader = rowStateReader(app, settings, model, out2, excludes);
|
|
31396
|
+
for (const path23 of [...candidates.keys()].sort()) {
|
|
31397
|
+
const r = await reader(path23, candidates.get(path23) ?? null);
|
|
31398
|
+
if (r) out2.get(r.table).files.set(path23, r.state);
|
|
31399
|
+
}
|
|
31400
|
+
return out2;
|
|
31401
|
+
}
|
|
31402
|
+
function rowStateReader(app, settings, model, tables, excludes) {
|
|
31403
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
31404
|
+
return async (path23, given) => {
|
|
31405
|
+
const file = given ?? app.vault.getAbstractFileByPath(path23);
|
|
31406
|
+
if (file == null || !("extension" in file)) return null;
|
|
31407
|
+
let raw;
|
|
31408
|
+
try {
|
|
31409
|
+
raw = await readFrontmatterValue(app, file, NO_KEYS, (ordered) => fmGet(ordered, settings.tableKey));
|
|
31410
|
+
} catch {
|
|
31411
|
+
return null;
|
|
31477
31412
|
}
|
|
31478
|
-
|
|
31479
|
-
|
|
31480
|
-
|
|
31481
|
-
|
|
31482
|
-
|
|
31413
|
+
const rawKey = typeof raw === "string" ? raw : JSON.stringify(raw ?? null);
|
|
31414
|
+
if (!resolved.has(rawKey)) resolved.set(rawKey, rowTableFull(model, raw));
|
|
31415
|
+
const full = resolved.get(rawKey);
|
|
31416
|
+
if (full == null || !tables.has(full)) return null;
|
|
31417
|
+
if (excludes.get(full).some((dir) => path23.startsWith(`${dir}/`))) return null;
|
|
31418
|
+
return { table: full, state: await readHashed(app, file) };
|
|
31419
|
+
};
|
|
31420
|
+
}
|
|
31421
|
+
function forgetCurrentStates(app) {
|
|
31422
|
+
LIVE.delete(app);
|
|
31423
|
+
}
|
|
31424
|
+
async function currentStates(app, settings, model, touched, only) {
|
|
31425
|
+
const idx = vaultIndex(app, settings);
|
|
31426
|
+
if (!idx || idx.hasFailures) return readCurrentStates(app, settings, model, touched, only);
|
|
31427
|
+
let live = LIVE.get(app);
|
|
31428
|
+
const kept2 = JSON.stringify([...only].sort());
|
|
31429
|
+
const changed2 = live && live.model === model && live.only === kept2 && live.epoch === idx.epoch && live.tableKey === settings.tableKey && live.databaseFolder === settings.databaseFolder ? idx.changedSince(live.cursor, idx.epoch) : null;
|
|
31430
|
+
const cursor = idx.cursor;
|
|
31431
|
+
if (!live || !changed2) {
|
|
31432
|
+
const tables = await readCurrentStates(app, settings, model, touched, only);
|
|
31433
|
+
const excludes = /* @__PURE__ */ new Map();
|
|
31434
|
+
const tableOf = /* @__PURE__ */ new Map();
|
|
31435
|
+
for (const [full, t] of tables) {
|
|
31436
|
+
excludes.set(full, catalogEntryOptions(full, t.entry, settings));
|
|
31437
|
+
for (const p of t.files.keys()) tableOf.set(p, full);
|
|
31483
31438
|
}
|
|
31484
|
-
|
|
31485
|
-
|
|
31486
|
-
|
|
31487
|
-
|
|
31488
|
-
|
|
31439
|
+
const touchedOnly = new Set([...tableOf.keys()].filter((p) => !(idx.note(p)?.tableValue != null)));
|
|
31440
|
+
live = { epoch: idx.epoch, cursor, model, only: kept2, tableKey: settings.tableKey, databaseFolder: settings.databaseFolder, tables, excludes, tableOf, touchedOnly };
|
|
31441
|
+
LIVE.set(app, live);
|
|
31442
|
+
} else {
|
|
31443
|
+
const named = new Set(touched.filter((p) => p.endsWith(".md")));
|
|
31444
|
+
const reader = rowStateReader(app, settings, model, live.tables, live.excludes);
|
|
31445
|
+
for (const path23 of /* @__PURE__ */ new Set([...changed2, ...named, ...live.touchedOnly])) {
|
|
31446
|
+
const was = live.tableOf.get(path23);
|
|
31447
|
+
if (was !== void 0) live.tables.get(was).files.delete(path23);
|
|
31448
|
+
live.tableOf.delete(path23);
|
|
31449
|
+
live.touchedOnly.delete(path23);
|
|
31450
|
+
const n = idx.note(path23);
|
|
31451
|
+
const listed = n !== void 0 && n.tableValue != null;
|
|
31452
|
+
if (!listed && !named.has(path23)) continue;
|
|
31453
|
+
const r = await reader(path23, n?.file ?? null);
|
|
31454
|
+
if (!r) continue;
|
|
31455
|
+
live.tables.get(r.table).files.set(path23, r.state);
|
|
31456
|
+
live.tableOf.set(path23, r.table);
|
|
31457
|
+
if (!listed) live.touchedOnly.add(path23);
|
|
31489
31458
|
}
|
|
31490
|
-
|
|
31491
|
-
for (const d of entry.deleted) state.set(d.path, { ordered: readOrderedFromMarkdown(d.content).ordered, body: bodyOf(d.content) });
|
|
31459
|
+
live.cursor = cursor;
|
|
31492
31460
|
}
|
|
31493
|
-
|
|
31494
|
-
|
|
31495
|
-
|
|
31496
|
-
|
|
31497
|
-
|
|
31498
|
-
|
|
31499
|
-
|
|
31500
|
-
|
|
31461
|
+
return live.tables;
|
|
31462
|
+
}
|
|
31463
|
+
async function verifyCurrentStates(app, settings, repair = false) {
|
|
31464
|
+
const ms = await loadMetastore(app, settings);
|
|
31465
|
+
const live = LIVE.get(app);
|
|
31466
|
+
if (!ms || !live) return [];
|
|
31467
|
+
const only = new Set(JSON.parse(live.only));
|
|
31468
|
+
const kept2 = await currentStates(app, settings, ms.model, [], only);
|
|
31469
|
+
const now = await readCurrentStates(app, settings, ms.model, [], only);
|
|
31470
|
+
const out2 = [];
|
|
31471
|
+
for (const [full, t] of now) {
|
|
31472
|
+
const a = kept2.get(full).files;
|
|
31473
|
+
const b = t.files;
|
|
31474
|
+
for (const p of /* @__PURE__ */ new Set([...a.keys(), ...b.keys()])) if (a.get(p)?.hash !== b.get(p)?.hash) out2.push(`${full}: ${p}`);
|
|
31501
31475
|
}
|
|
31502
|
-
|
|
31503
|
-
|
|
31504
|
-
|
|
31505
|
-
|
|
31506
|
-
|
|
31507
|
-
|
|
31476
|
+
if (out2.length > 0 && repair) forgetCurrentStates(app);
|
|
31477
|
+
return out2.sort();
|
|
31478
|
+
}
|
|
31479
|
+
function checkpointDue(state, format) {
|
|
31480
|
+
const since = state.sinceCheckpoint;
|
|
31481
|
+
if (since.bytes >= Math.max(CHECKPOINT_POLICY.bytesFactor * state.bytes, CHECKPOINT_POLICY.minBytes)) return true;
|
|
31482
|
+
if (format === "segments") return false;
|
|
31483
|
+
return since.versions >= Math.max(CHECKPOINT_POLICY.maxVersions, Math.ceil(state.bytes / CHECKPOINT_POLICY.versionBytes));
|
|
31484
|
+
}
|
|
31485
|
+
async function checkpointRows(app, settings, state) {
|
|
31486
|
+
const texts = await loggedTexts(app, settings, state, state.files.keys());
|
|
31487
|
+
if (texts.size !== state.files.size) return null;
|
|
31488
|
+
return [...state.files].sort(([a], [b]) => a < b ? -1 : 1).map(([path23, f]) => ({ path: path23, hash: f.hash, content: texts.get(path23) }));
|
|
31489
|
+
}
|
|
31490
|
+
async function writeDueCheckpoint(app, settings, full) {
|
|
31491
|
+
const state = LAST.get(app)?.get(full);
|
|
31492
|
+
if (!state || !checkpointDue(state, formatOf(settings))) return false;
|
|
31493
|
+
if (!await stillLatest(app, settings, state)) return false;
|
|
31494
|
+
const rows = await checkpointRows(app, settings, state);
|
|
31495
|
+
if (!rows) return false;
|
|
31496
|
+
await writeManifestCheckpoint(app, transactionLogDir(settings, full), { version: state.version, timestamp: state.timestamp, entry: state.entry, ...state.movesApplied ? { moves: state.movesApplied } : {} }, rows);
|
|
31497
|
+
state.sinceCheckpoint = { versions: 0, bytes: 0 };
|
|
31498
|
+
return true;
|
|
31499
|
+
}
|
|
31500
|
+
function formatOf(settings) {
|
|
31501
|
+
return settings.transactionLogFormat === "files" ? "files" : "segments";
|
|
31502
|
+
}
|
|
31503
|
+
function setCheckpointRunner(app, run2) {
|
|
31504
|
+
if (run2) CHECKPOINT_RUNNERS.set(app, run2);
|
|
31505
|
+
else CHECKPOINT_RUNNERS.delete(app);
|
|
31506
|
+
}
|
|
31507
|
+
async function writeDueCheckpoints(app, stopped = () => false) {
|
|
31508
|
+
let n = 0;
|
|
31509
|
+
for (; ; ) {
|
|
31510
|
+
const due = DUE.get(app);
|
|
31511
|
+
const next = due ? [...due][0] : void 0;
|
|
31512
|
+
if (!next || stopped()) break;
|
|
31513
|
+
const [full, settings] = next;
|
|
31514
|
+
due.delete(full);
|
|
31515
|
+
if (await withLogLock(app, () => writeDueCheckpoint(app, settings, full))) n++;
|
|
31508
31516
|
}
|
|
31509
|
-
|
|
31510
|
-
return cs;
|
|
31517
|
+
return n;
|
|
31511
31518
|
}
|
|
31512
|
-
async function
|
|
31513
|
-
|
|
31514
|
-
|
|
31515
|
-
|
|
31516
|
-
const
|
|
31517
|
-
|
|
31518
|
-
|
|
31519
|
-
|
|
31520
|
-
|
|
31521
|
-
|
|
31522
|
-
|
|
31523
|
-
|
|
31524
|
-
failed: []
|
|
31519
|
+
async function wholeCheckpoint(app, dir, v) {
|
|
31520
|
+
return parseCheckpoint(await storeRead(app, checkpointPath(dir, v)) ?? "")?.version === v;
|
|
31521
|
+
}
|
|
31522
|
+
async function writeCheckpoint(app, dir, state, format) {
|
|
31523
|
+
const files = [...state.files.entries()].sort(([a], [b]) => a.localeCompare(b));
|
|
31524
|
+
if (format === "files") {
|
|
31525
|
+
const cp = {
|
|
31526
|
+
version: state.version,
|
|
31527
|
+
timestamp: state.timestamp,
|
|
31528
|
+
entry: state.entry,
|
|
31529
|
+
files: files.map(([path23, f]) => ({ path: path23, hash: f.hash, content: f.content })),
|
|
31530
|
+
...state.movesApplied ? { moves: state.movesApplied } : {}
|
|
31525
31531
|
};
|
|
31526
|
-
|
|
31527
|
-
|
|
31528
|
-
|
|
31529
|
-
|
|
31530
|
-
|
|
31531
|
-
if (!f) {
|
|
31532
|
-
conflicts.push({ path: r.path, message: "note no longer exists" });
|
|
31533
|
-
continue;
|
|
31534
|
-
}
|
|
31535
|
-
const cur = new Map(await readOrderedFor(app, f));
|
|
31536
|
-
if (partial2 && Object.entries(r.before).every(([k, v]) => cur.has(k) && same(cur.get(k), v)) && r.before_absent.every((k) => !cur.has(k)) && (r.body_before === void 0 || bodyOf(await app.vault.read(f)) === r.body_before)) {
|
|
31537
|
-
back.add(r);
|
|
31538
|
-
continue;
|
|
31539
|
-
}
|
|
31540
|
-
for (const [k, v] of Object.entries(r.after)) {
|
|
31541
|
-
if (!cur.has(k) || !same(cur.get(k), v)) {
|
|
31542
|
-
conflicts.push({ path: r.path, message: `"${k}" changed since the operation` });
|
|
31543
|
-
}
|
|
31544
|
-
}
|
|
31545
|
-
for (const k of r.after_absent) {
|
|
31546
|
-
if (cur.has(k)) conflicts.push({ path: r.path, message: `"${k}" was added since the operation` });
|
|
31547
|
-
}
|
|
31548
|
-
if (r.body_after !== void 0 && bodyOf(await app.vault.read(f)) !== r.body_after) {
|
|
31549
|
-
conflicts.push({ path: r.path, message: "body changed since the operation" });
|
|
31532
|
+
const text5 = JSON.stringify(cp);
|
|
31533
|
+
if (text5.length <= LEGACY_CHECKPOINT.maxChars) {
|
|
31534
|
+
const path23 = checkpointPath(dir, state.version);
|
|
31535
|
+
if (!await wholeCheckpoint(app, dir, state.version)) await storeWrite(app, path23, text5);
|
|
31536
|
+
return;
|
|
31550
31537
|
}
|
|
31551
31538
|
}
|
|
31552
|
-
|
|
31553
|
-
|
|
31554
|
-
|
|
31555
|
-
|
|
31556
|
-
|
|
31557
|
-
|
|
31558
|
-
|
|
31539
|
+
await writeManifestCheckpoint(app, dir, { version: state.version, timestamp: state.timestamp, entry: state.entry, ...state.movesApplied ? { moves: state.movesApplied } : {} }, files.map(([path23, f]) => ({ path: path23, hash: f.hash, content: f.content })));
|
|
31540
|
+
}
|
|
31541
|
+
function forgetLoggedStates(app) {
|
|
31542
|
+
LAST.delete(app);
|
|
31543
|
+
}
|
|
31544
|
+
async function knownState(app, settings, full) {
|
|
31545
|
+
return LAST.get(app)?.get(full) ?? readLastState(app, settings, full);
|
|
31546
|
+
}
|
|
31547
|
+
async function readLastState(app, settings, full) {
|
|
31548
|
+
let byTable = LAST.get(app);
|
|
31549
|
+
if (!byTable) LAST.set(app, byTable = /* @__PURE__ */ new Map());
|
|
31550
|
+
const state = await replayLogged(app, settings, full);
|
|
31551
|
+
byTable.set(full, state);
|
|
31552
|
+
return state;
|
|
31553
|
+
}
|
|
31554
|
+
async function stillLatest(app, settings, state) {
|
|
31555
|
+
return latestOf(await tocOf(app, settings, state.full)) === state.version;
|
|
31556
|
+
}
|
|
31557
|
+
async function latestVersion(app, settings, full) {
|
|
31558
|
+
const latest = latestOf(await tocOf(app, settings, full));
|
|
31559
|
+
return latest >= 0 ? latest : null;
|
|
31560
|
+
}
|
|
31561
|
+
async function warmLoggedStates(app, settings, stopped = () => false) {
|
|
31562
|
+
let n = 0;
|
|
31563
|
+
for (const full of await loggedTables(app, settings)) {
|
|
31564
|
+
if (stopped()) break;
|
|
31565
|
+
await withLogLock(app, async () => {
|
|
31566
|
+
if (LAST.get(app)?.has(full)) return;
|
|
31567
|
+
await readLastState(app, settings, full);
|
|
31568
|
+
n++;
|
|
31569
|
+
});
|
|
31559
31570
|
}
|
|
31560
|
-
|
|
31561
|
-
|
|
31562
|
-
|
|
31563
|
-
|
|
31564
|
-
|
|
31565
|
-
|
|
31566
|
-
|
|
31567
|
-
|
|
31568
|
-
|
|
31571
|
+
return n;
|
|
31572
|
+
}
|
|
31573
|
+
async function changesSince(app, prev, cur) {
|
|
31574
|
+
const entry = cur?.entry ?? null;
|
|
31575
|
+
let files = cur?.files ?? /* @__PURE__ */ new Map();
|
|
31576
|
+
const remove3 = [];
|
|
31577
|
+
const add = [];
|
|
31578
|
+
if (cur) {
|
|
31579
|
+
for (const [path23, f] of prev.files) {
|
|
31580
|
+
if (files.has(path23) || !await unreadableNow(app, path23)) continue;
|
|
31581
|
+
if (files === cur.files) files = new Map(files);
|
|
31582
|
+
files.set(path23, f);
|
|
31569
31583
|
}
|
|
31570
31584
|
}
|
|
31571
|
-
for (const
|
|
31572
|
-
|
|
31585
|
+
for (const [path23, f] of prev.files) {
|
|
31586
|
+
const now = files.get(path23);
|
|
31587
|
+
if (!now || now.hash !== f.hash) remove3.push({ path: path23, hash: f.hash });
|
|
31573
31588
|
}
|
|
31574
|
-
|
|
31575
|
-
|
|
31576
|
-
|
|
31577
|
-
|
|
31578
|
-
|
|
31579
|
-
|
|
31580
|
-
|
|
31581
|
-
|
|
31582
|
-
|
|
31589
|
+
for (const [path23, f] of files) {
|
|
31590
|
+
const was = prev.files.get(path23);
|
|
31591
|
+
if (!was || was.hash !== f.hash) add.push({ path: path23, hash: f.hash, size: f.content.length, content: f.content });
|
|
31592
|
+
}
|
|
31593
|
+
add.sort((a, b) => a.path < b.path ? -1 : 1);
|
|
31594
|
+
const metadataChanged = prev.version < 0 || entryHash(prev.entry) !== entryHash(entry);
|
|
31595
|
+
return { entry, add, remove: remove3, metadataChanged };
|
|
31596
|
+
}
|
|
31597
|
+
function historyEnabled(model, entry) {
|
|
31598
|
+
const own2 = entry.options?.history;
|
|
31599
|
+
return typeof own2 === "boolean" ? own2 : model.config.history === true;
|
|
31600
|
+
}
|
|
31601
|
+
async function syncTransactionLogs(app, settings, options) {
|
|
31602
|
+
const ms = await loadMetastore(app, settings);
|
|
31603
|
+
if (!ms) return [];
|
|
31604
|
+
const tables = catalogTables(ms.model);
|
|
31605
|
+
const enabled = new Set(tables.filter(({ entry }) => historyEnabled(ms.model, entry)).map(({ ref: ref2 }) => ref2.full));
|
|
31606
|
+
const defined = new Set(tables.map(({ ref: ref2 }) => ref2.full));
|
|
31607
|
+
const orphans = await loggedTables(app, settings, (full) => !defined.has(full));
|
|
31608
|
+
if (enabled.size === 0 && orphans.length === 0) return [];
|
|
31609
|
+
const current2 = await currentStates(app, settings, ms.model, options.touched ?? [], enabled);
|
|
31610
|
+
const names2 = /* @__PURE__ */ new Set([...enabled, ...orphans]);
|
|
31611
|
+
const out2 = [];
|
|
31612
|
+
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
31613
|
+
const format = formatOf(settings);
|
|
31614
|
+
const planned = [];
|
|
31615
|
+
for (const full of [...names2].sort()) {
|
|
31616
|
+
if (options.tables && !options.tables.includes(full)) continue;
|
|
31617
|
+
let prev = await knownState(app, settings, full);
|
|
31618
|
+
const cur = current2.get(full);
|
|
31619
|
+
if (prev.version < 0 && !cur) continue;
|
|
31620
|
+
const due = (c) => c.metadataChanged || c.add.length > 0 || c.remove.length > 0 || options.always === true && cur !== void 0;
|
|
31621
|
+
let changes = await changesSince(app, prev, cur);
|
|
31622
|
+
if (!due(changes)) continue;
|
|
31623
|
+
if (!await stillLatest(app, settings, prev)) {
|
|
31624
|
+
prev = await readLastState(app, settings, full);
|
|
31625
|
+
changes = await changesSince(app, prev, cur);
|
|
31626
|
+
if (!due(changes)) continue;
|
|
31583
31627
|
}
|
|
31584
|
-
|
|
31585
|
-
|
|
31628
|
+
const { entry, add, remove: remove3, metadataChanged } = changes;
|
|
31629
|
+
const operation = prev.version < 0 && options.operation === EXTERNAL_EDIT ? "CREATE TABLE" : options.operation;
|
|
31630
|
+
planned.push({ table: full, add, remove: remove3, metadataChanged, entry, prev, operation });
|
|
31586
31631
|
}
|
|
31587
|
-
|
|
31588
|
-
|
|
31589
|
-
|
|
31590
|
-
|
|
31591
|
-
|
|
31632
|
+
const external = planned.filter((p) => p.operation === EXTERNAL_EDIT);
|
|
31633
|
+
let counts;
|
|
31634
|
+
if (options.check && external.length > 0) {
|
|
31635
|
+
const added = new Set(external.flatMap((p) => p.add.map((a) => a.path)));
|
|
31636
|
+
const changes = [];
|
|
31637
|
+
for (const p of external) {
|
|
31638
|
+
const wanted = /* @__PURE__ */ new Set([...p.remove.map((r) => r.path), ...[...added].filter((x) => p.prev.files.has(x))]);
|
|
31639
|
+
const texts = await loggedTexts(app, settings, p.prev, wanted);
|
|
31640
|
+
const before = /* @__PURE__ */ new Map();
|
|
31641
|
+
for (const x of wanted) if (texts.has(x)) before.set(x, { hash: p.prev.files.get(x).hash, content: texts.get(x) });
|
|
31642
|
+
const { prev: _prev, operation: _op, ...change } = p;
|
|
31643
|
+
changes.push({ ...change, before });
|
|
31592
31644
|
}
|
|
31645
|
+
counts = await options.check(changes);
|
|
31593
31646
|
}
|
|
31594
|
-
|
|
31595
|
-
|
|
31596
|
-
const
|
|
31597
|
-
|
|
31647
|
+
for (const { table: full, add, remove: remove3, metadataChanged, entry, prev, operation } of planned) {
|
|
31648
|
+
const version2 = prev.version + 1;
|
|
31649
|
+
const violations = operation === EXTERNAL_EDIT ? counts?.get(full) : void 0;
|
|
31650
|
+
const commit = {
|
|
31651
|
+
commitInfo: {
|
|
31652
|
+
version: version2,
|
|
31653
|
+
timestamp: now.toISOString(),
|
|
31654
|
+
operation,
|
|
31655
|
+
...options.parameters && Object.keys(options.parameters).length > 0 ? { operationParameters: options.parameters } : {},
|
|
31656
|
+
...options.migrationId ? { migrationId: options.migrationId } : {},
|
|
31657
|
+
operationMetrics: { numAddedFiles: add.length, numRemovedFiles: remove3.length, metadataChanged, ...violations !== void 0 ? { violations } : {} },
|
|
31658
|
+
engineInfo: "datanotes",
|
|
31659
|
+
...options.actor ? { userName: actorName(options.actor), actor: options.actor } : {}
|
|
31660
|
+
},
|
|
31661
|
+
...metadataChanged ? { metaData: { entry } } : {},
|
|
31662
|
+
add: [...add].sort((a, b) => a.path.localeCompare(b.path)),
|
|
31663
|
+
remove: [...remove3].sort((a, b) => a.path.localeCompare(b.path))
|
|
31664
|
+
};
|
|
31665
|
+
const text5 = serializeCommit(commit);
|
|
31666
|
+
const repaired = await appendVersion(app, transactionLogDir(settings, full), version2, text5, format);
|
|
31667
|
+
for (const r of repaired) telemetry().log("WARN", "datanotes transaction log repaired a version torn by a crash", { table: full, repair: r });
|
|
31668
|
+
applyLogged(prev, commit);
|
|
31669
|
+
prev.sinceCheckpoint = { versions: prev.sinceCheckpoint.versions + 1, bytes: prev.sinceCheckpoint.bytes + text5.length };
|
|
31670
|
+
if (version2 > 0 && checkpointDue(prev, format)) {
|
|
31671
|
+
let due = DUE.get(app);
|
|
31672
|
+
if (!due) DUE.set(app, due = /* @__PURE__ */ new Map());
|
|
31673
|
+
due.set(full, settings);
|
|
31674
|
+
}
|
|
31675
|
+
out2.push({ table: full, version: version2, operation: commit.commitInfo.operation });
|
|
31598
31676
|
}
|
|
31599
|
-
|
|
31600
|
-
|
|
31601
|
-
|
|
31602
|
-
|
|
31603
|
-
|
|
31604
|
-
|
|
31605
|
-
|
|
31606
|
-
|
|
31607
|
-
for (const ref2 of entry.references) {
|
|
31608
|
-
if (back.has(ref2)) continue;
|
|
31609
|
-
steps.push({
|
|
31610
|
-
path: currentPath(ref2.path),
|
|
31611
|
-
run: async () => {
|
|
31612
|
-
const f = fileOrNull(app, currentPath(ref2.path));
|
|
31613
|
-
await app.vault.process(f, () => ref2.before_text);
|
|
31614
|
-
undoReferences.push({ path: currentPath(ref2.path), before_text: ref2.after_text, after_text: ref2.before_text });
|
|
31677
|
+
if (DUE.get(app)?.size) {
|
|
31678
|
+
const runner = CHECKPOINT_RUNNERS.get(app);
|
|
31679
|
+
if (runner) runner();
|
|
31680
|
+
else {
|
|
31681
|
+
const due = DUE.get(app);
|
|
31682
|
+
for (const [full, s] of [...due]) {
|
|
31683
|
+
due.delete(full);
|
|
31684
|
+
await writeDueCheckpoint(app, s, full);
|
|
31615
31685
|
}
|
|
31616
|
-
}
|
|
31686
|
+
}
|
|
31617
31687
|
}
|
|
31618
|
-
|
|
31619
|
-
|
|
31620
|
-
|
|
31621
|
-
|
|
31622
|
-
|
|
31623
|
-
|
|
31624
|
-
|
|
31625
|
-
|
|
31626
|
-
|
|
31627
|
-
|
|
31628
|
-
|
|
31629
|
-
|
|
31630
|
-
|
|
31631
|
-
|
|
31632
|
-
|
|
31633
|
-
|
|
31634
|
-
path: currentPath(r.path),
|
|
31635
|
-
before: r.after,
|
|
31636
|
-
before_absent: r.after_absent,
|
|
31637
|
-
before_order: order,
|
|
31638
|
-
after: r.before,
|
|
31639
|
-
after_absent: r.before_absent,
|
|
31640
|
-
...r.body_after !== void 0 ? { body_before: r.body_after, body_after: r.body_before } : {}
|
|
31641
|
-
});
|
|
31688
|
+
return out2;
|
|
31689
|
+
}
|
|
31690
|
+
function withLogLock(app, fn) {
|
|
31691
|
+
let activity = ACTIVITY.get(app);
|
|
31692
|
+
if (!activity) ACTIVITY.set(app, activity = { pending: 0, lastEnd: 0 });
|
|
31693
|
+
const a = activity;
|
|
31694
|
+
a.pending++;
|
|
31695
|
+
const prev = LOCKS.get(app) ?? Promise.resolve();
|
|
31696
|
+
const wait = startSpan("engine.lock.acquire", { attributes: { "datanotes.lock.queued": a.pending - 1 } });
|
|
31697
|
+
const held = () => {
|
|
31698
|
+
wait.end();
|
|
31699
|
+
return phase("engine.lock.hold", async () => {
|
|
31700
|
+
try {
|
|
31701
|
+
return await fn();
|
|
31702
|
+
} finally {
|
|
31703
|
+
await storeFlush(app).catch((e) => telemetry().log("WARN", "datanotes could not sync the store's files", { error: messageOf(e) }));
|
|
31642
31704
|
}
|
|
31643
31705
|
});
|
|
31706
|
+
};
|
|
31707
|
+
const run2 = prev.then(held, held);
|
|
31708
|
+
const settled2 = run2.then(
|
|
31709
|
+
() => void 0,
|
|
31710
|
+
() => void 0
|
|
31711
|
+
);
|
|
31712
|
+
LOCKS.set(app, settled2);
|
|
31713
|
+
void settled2.then(() => {
|
|
31714
|
+
a.pending--;
|
|
31715
|
+
a.lastEnd = Date.now();
|
|
31716
|
+
});
|
|
31717
|
+
return run2;
|
|
31718
|
+
}
|
|
31719
|
+
function logLockActivity(app) {
|
|
31720
|
+
const a = ACTIVITY.get(app);
|
|
31721
|
+
return { busy: (a?.pending ?? 0) > 0, lastEnd: a?.lastEnd ?? 0 };
|
|
31722
|
+
}
|
|
31723
|
+
async function checkpointFiles(app, dir, toc) {
|
|
31724
|
+
const out2 = /* @__PURE__ */ new Map();
|
|
31725
|
+
for (const cp of toc.checkpoints) {
|
|
31726
|
+
const files = cp.kind === 1 ? [cp.path] : manifestFiles(dir, cp.path, (await readManifest(app, dir, cp.path))?.head ?? null);
|
|
31727
|
+
out2.set(cp.version, [...out2.get(cp.version) ?? [], ...files]);
|
|
31644
31728
|
}
|
|
31645
|
-
|
|
31646
|
-
|
|
31647
|
-
|
|
31648
|
-
|
|
31649
|
-
|
|
31650
|
-
|
|
31651
|
-
|
|
31652
|
-
|
|
31729
|
+
return out2;
|
|
31730
|
+
}
|
|
31731
|
+
async function vacuum(app, settings, full, retainFrom, dryRun) {
|
|
31732
|
+
const dir = transactionLogDir(settings, full);
|
|
31733
|
+
const toc = await logToc(app, dir);
|
|
31734
|
+
const versions = toc.versions;
|
|
31735
|
+
if (versions.length === 0) throw new HistoryError(`${full} has no history`);
|
|
31736
|
+
const latest = versions[versions.length - 1];
|
|
31737
|
+
const keep = retainFrom ?? latest;
|
|
31738
|
+
if (!versions.includes(keep)) throw new HistoryError(`${full} has no version ${keep} (available: ${versions[0]}..${latest})`);
|
|
31739
|
+
const doomed = [...toc.files].filter(([v]) => v < keep).map(([, p]) => p);
|
|
31740
|
+
const straddling = toc.segments.filter((s) => s.first < keep && s.last >= keep);
|
|
31741
|
+
for (const s of toc.segments) if (s.last < keep || straddling.includes(s)) doomed.push(s.path);
|
|
31742
|
+
for (const [v, files] of await checkpointFiles(app, dir, toc)) if (v < keep) doomed.push(...files);
|
|
31743
|
+
doomed.sort();
|
|
31744
|
+
if (!dryRun) {
|
|
31745
|
+
await writeCheckpoint(app, dir, await tableStateAt(app, settings, full, { version: keep }), formatOf(settings));
|
|
31746
|
+
for (const s of straddling) {
|
|
31747
|
+
const kept2 = [];
|
|
31748
|
+
for (let v = keep; v <= s.last; v++) kept2.push({ version: v, text: await versionText(app, toc, v) });
|
|
31749
|
+
await writeSealedSegments(app, dir, kept2);
|
|
31750
|
+
}
|
|
31751
|
+
await storeFlush(app);
|
|
31752
|
+
for (const p of doomed) await storeRemove(app, p);
|
|
31753
|
+
forgetLoggedStates(app);
|
|
31653
31754
|
}
|
|
31654
|
-
|
|
31655
|
-
|
|
31656
|
-
|
|
31657
|
-
|
|
31658
|
-
|
|
31659
|
-
|
|
31660
|
-
|
|
31661
|
-
|
|
31662
|
-
|
|
31755
|
+
return { table: full, retained_from: keep, deleted: doomed, dry_run: dryRun };
|
|
31756
|
+
}
|
|
31757
|
+
async function convertLog(app, settings, full, to) {
|
|
31758
|
+
const dir = transactionLogDir(settings, full);
|
|
31759
|
+
const toc = await logToc(app, dir);
|
|
31760
|
+
const result = { table: full, versions: toc.versions.length, written: 0, removed: 0 };
|
|
31761
|
+
if (to === "segments") {
|
|
31762
|
+
for (const s of toc.segments) {
|
|
31763
|
+
if (!s.sealed || (await wholeVersions(app, s)).size === s.last - s.first + 1) {
|
|
31764
|
+
storeSyncNext(app, s.path);
|
|
31765
|
+
continue;
|
|
31663
31766
|
}
|
|
31664
|
-
|
|
31665
|
-
|
|
31666
|
-
|
|
31667
|
-
|
|
31668
|
-
|
|
31669
|
-
|
|
31670
|
-
|
|
31671
|
-
|
|
31672
|
-
|
|
31673
|
-
|
|
31674
|
-
|
|
31767
|
+
const range = Array.from({ length: s.last - s.first + 1 }, (_, i) => s.first + i);
|
|
31768
|
+
const texts2 = await versionTexts(app, toc, range);
|
|
31769
|
+
await storeWrite(app, s.path, range.map((v, i) => framed(v, texts2[i])).join(""));
|
|
31770
|
+
result.written++;
|
|
31771
|
+
}
|
|
31772
|
+
const loose = toc.versions.filter((v) => toc.files.has(v) && !toc.segments.some((s) => v >= s.first && v <= s.last));
|
|
31773
|
+
const texts = await versionTexts(app, toc, loose);
|
|
31774
|
+
const written2 = await writeSealedSegments(app, dir, loose.map((version2, i) => ({ version: version2, text: texts[i] })));
|
|
31775
|
+
result.written += written2.length;
|
|
31776
|
+
await storeFlush(app);
|
|
31777
|
+
for (const v of toc.files.keys()) {
|
|
31778
|
+
await storeRemove(app, toc.files.get(v));
|
|
31779
|
+
result.removed++;
|
|
31780
|
+
}
|
|
31781
|
+
} else {
|
|
31782
|
+
for (const seg of toc.segments) {
|
|
31783
|
+
for (let v = seg.first; v <= seg.last; v++) {
|
|
31784
|
+
const path23 = `${dir}/${pad2(v)}.json`;
|
|
31785
|
+
const file = toc.files.get(v);
|
|
31786
|
+
if (file === void 0) {
|
|
31787
|
+
await storeWrite(app, path23, await versionText(app, toc, v));
|
|
31788
|
+
result.written++;
|
|
31789
|
+
} else storeSyncNext(app, file);
|
|
31675
31790
|
}
|
|
31676
|
-
});
|
|
31677
|
-
}
|
|
31678
|
-
let done = 0;
|
|
31679
|
-
try {
|
|
31680
|
-
for (const s of steps) {
|
|
31681
|
-
await s.run();
|
|
31682
|
-
done++;
|
|
31683
31791
|
}
|
|
31684
|
-
|
|
31685
|
-
|
|
31686
|
-
|
|
31687
|
-
|
|
31688
|
-
|
|
31689
|
-
|
|
31690
|
-
|
|
31691
|
-
|
|
31692
|
-
|
|
31693
|
-
|
|
31694
|
-
|
|
31695
|
-
|
|
31696
|
-
|
|
31697
|
-
creates: undoCreates,
|
|
31698
|
-
...undoCreatedHashes.length > 0 ? { created_hashes: undoCreatedHashes } : {},
|
|
31699
|
-
moves: undoMoves,
|
|
31700
|
-
references: undoReferences,
|
|
31701
|
-
deleted: undoDeleted,
|
|
31702
|
-
failed: failed2,
|
|
31703
|
-
undoes: migrationId
|
|
31704
|
-
},
|
|
31705
|
-
now
|
|
31706
|
-
);
|
|
31707
|
-
if (failed2.length === 0) {
|
|
31708
|
-
await markUndone(app, settings, migrationId, journal.id, now);
|
|
31792
|
+
const oldest = toc.versions[0];
|
|
31793
|
+
if (oldest !== void 0 && oldest > 0) {
|
|
31794
|
+
if (await wholeCheckpoint(app, dir, oldest)) storeSyncNext(app, checkpointPath(dir, oldest));
|
|
31795
|
+
else {
|
|
31796
|
+
await writeCheckpoint(app, dir, await replayState(app, settings, full, { version: oldest }), "files");
|
|
31797
|
+
result.written++;
|
|
31798
|
+
}
|
|
31799
|
+
}
|
|
31800
|
+
await storeFlush(app);
|
|
31801
|
+
for (const seg of toc.segments) {
|
|
31802
|
+
await storeRemove(app, seg.path);
|
|
31803
|
+
result.removed++;
|
|
31804
|
+
}
|
|
31709
31805
|
}
|
|
31710
|
-
|
|
31711
|
-
|
|
31712
|
-
undoes: migrationId,
|
|
31713
|
-
migration_id: journal.id,
|
|
31714
|
-
conflicts: [],
|
|
31715
|
-
failed: failed2,
|
|
31716
|
-
...failed2.length > 0 ? { left: [...new Set(steps.slice(done).map((s) => s.path))] } : {},
|
|
31717
|
-
touched: [
|
|
31718
|
-
...entry.rows.map((r) => r.path),
|
|
31719
|
-
...entry.creates,
|
|
31720
|
-
...[...entry.moves, ...since].flatMap((m) => [m.from, m.to]),
|
|
31721
|
-
...entry.references.map((r) => r.path),
|
|
31722
|
-
...entry.deleted.map((d) => d.path)
|
|
31723
|
-
]
|
|
31724
|
-
};
|
|
31725
|
-
}
|
|
31726
|
-
function applyChangeSet(...args) {
|
|
31727
|
-
const cs = args[2];
|
|
31728
|
-
return phase("engine.apply", async (span) => {
|
|
31729
|
-
const r = await applyChangeSetImpl(...args);
|
|
31730
|
-
span.setAttributes({ "datanotes.notes_changed": r.notesChanged, "datanotes.files_created": r.filesCreated, "datanotes.files_moved": r.filesMoved, "datanotes.files_deleted": r.filesDeleted, "datanotes.failed": r.failed.length });
|
|
31731
|
-
if (!r.ok) span.setAttribute("error.type", "write_failed").setStatus("error", "the write failed partway");
|
|
31732
|
-
return r;
|
|
31733
|
-
}, { "datanotes.edits": cs.edits.length, "datanotes.creates": cs.creates.length, "datanotes.moves": cs.moves.length, "datanotes.deletes": cs.deletes.length });
|
|
31806
|
+
forgetLoggedStates(app);
|
|
31807
|
+
return result;
|
|
31734
31808
|
}
|
|
31735
|
-
var
|
|
31736
|
-
var
|
|
31737
|
-
"src/core/
|
|
31809
|
+
var TRANSACTION_LOG_FOLDER, CHECKPOINT_POLICY, LEGACY_CHECKPOINT, EXTERNAL_EDIT, AbsentTableError, ENTRY_HASHES, NO_KEYS, LIVE, DUE, CHECKPOINT_RUNNERS, LAST, LOCKS, ACTIVITY;
|
|
31810
|
+
var init_transaction_log = __esm({
|
|
31811
|
+
"src/core/transaction-log.ts"() {
|
|
31738
31812
|
"use strict";
|
|
31813
|
+
init_file_cache();
|
|
31739
31814
|
init_frontmatter();
|
|
31815
|
+
init_metastore();
|
|
31740
31816
|
init_actor();
|
|
31741
|
-
init_journal();
|
|
31742
31817
|
init_store();
|
|
31743
|
-
|
|
31744
|
-
|
|
31745
|
-
|
|
31746
|
-
|
|
31818
|
+
init_table_loader();
|
|
31819
|
+
init_types_table();
|
|
31820
|
+
init_database_move();
|
|
31821
|
+
init_db_paths();
|
|
31822
|
+
init_unreadable();
|
|
31747
31823
|
init_tracing();
|
|
31748
|
-
|
|
31749
|
-
|
|
31750
|
-
|
|
31751
|
-
|
|
31752
|
-
|
|
31753
|
-
|
|
31754
|
-
|
|
31824
|
+
init_vault_index();
|
|
31825
|
+
init_telemetry();
|
|
31826
|
+
init_message();
|
|
31827
|
+
init_log_files();
|
|
31828
|
+
TRANSACTION_LOG_FOLDER = "transaction_log";
|
|
31829
|
+
CHECKPOINT_POLICY = { bytesFactor: 2, minBytes: 256 * 1024, maxVersions: 1e3, versionBytes: 16 * 1024 };
|
|
31830
|
+
LEGACY_CHECKPOINT = { maxChars: 32 * 1024 * 1024 };
|
|
31831
|
+
EXTERNAL_EDIT = "EXTERNAL_EDIT";
|
|
31832
|
+
AbsentTableError = class extends HistoryError {
|
|
31755
31833
|
};
|
|
31834
|
+
ENTRY_HASHES = /* @__PURE__ */ new WeakMap();
|
|
31835
|
+
NO_KEYS = /* @__PURE__ */ new Set();
|
|
31836
|
+
LIVE = /* @__PURE__ */ new WeakMap();
|
|
31837
|
+
DUE = /* @__PURE__ */ new WeakMap();
|
|
31838
|
+
CHECKPOINT_RUNNERS = /* @__PURE__ */ new WeakMap();
|
|
31839
|
+
LAST = /* @__PURE__ */ new WeakMap();
|
|
31840
|
+
LOCKS = /* @__PURE__ */ new WeakMap();
|
|
31841
|
+
ACTIVITY = /* @__PURE__ */ new WeakMap();
|
|
31756
31842
|
}
|
|
31757
31843
|
});
|
|
31758
31844
|
|
|
@@ -32904,7 +32990,7 @@ var init_database = __esm({
|
|
|
32904
32990
|
|
|
32905
32991
|
// src/core/write-check.ts
|
|
32906
32992
|
function staleWrite(app) {
|
|
32907
|
-
const ctx =
|
|
32993
|
+
const ctx = storage4?.getStore();
|
|
32908
32994
|
if (ctx === void 0 || ctx.gen === generation(app)) return false;
|
|
32909
32995
|
ctx.stale = true;
|
|
32910
32996
|
return true;
|
|
@@ -32912,12 +32998,12 @@ function staleWrite(app) {
|
|
|
32912
32998
|
function wrote(app) {
|
|
32913
32999
|
const next = generation(app) + 1;
|
|
32914
33000
|
GENERATION.set(app, next);
|
|
32915
|
-
const ctx =
|
|
33001
|
+
const ctx = storage4?.getStore();
|
|
32916
33002
|
if (ctx) ctx.gen = next;
|
|
32917
33003
|
}
|
|
32918
33004
|
async function runWrite(app, fn, isStale) {
|
|
32919
|
-
if (!
|
|
32920
|
-
const s =
|
|
33005
|
+
if (!storage4) return fn();
|
|
33006
|
+
const s = storage4;
|
|
32921
33007
|
let ctx = { gen: 0, stale: false };
|
|
32922
33008
|
const attempt = () => s.run(ctx = { gen: generation(app), stale: false }, fn);
|
|
32923
33009
|
let result = await attempt();
|
|
@@ -32927,19 +33013,19 @@ async function runWrite(app, fn, isStale) {
|
|
|
32927
33013
|
function holdsStaleWrite(value) {
|
|
32928
33014
|
return JSON.stringify(value ?? null).includes(STALE_WRITE);
|
|
32929
33015
|
}
|
|
32930
|
-
var STALE_WRITE, WRITE_ATTEMPTS, GENERATION,
|
|
33016
|
+
var STALE_WRITE, WRITE_ATTEMPTS, GENERATION, storage4, generation;
|
|
32931
33017
|
var init_write_check = __esm({
|
|
32932
33018
|
"src/core/write-check.ts"() {
|
|
32933
33019
|
"use strict";
|
|
32934
33020
|
STALE_WRITE = "the database changed while this write was being checked";
|
|
32935
33021
|
WRITE_ATTEMPTS = 5;
|
|
32936
33022
|
GENERATION = /* @__PURE__ */ new WeakMap();
|
|
32937
|
-
|
|
33023
|
+
storage4 = null;
|
|
32938
33024
|
try {
|
|
32939
33025
|
const { AsyncLocalStorage: AsyncLocalStorage2 } = require("node:async_hooks");
|
|
32940
|
-
|
|
33026
|
+
storage4 = new AsyncLocalStorage2();
|
|
32941
33027
|
} catch {
|
|
32942
|
-
|
|
33028
|
+
storage4 = null;
|
|
32943
33029
|
}
|
|
32944
33030
|
generation = (app) => GENERATION.get(app) ?? 0;
|
|
32945
33031
|
}
|
|
@@ -33251,14 +33337,23 @@ function readableKeys(t, settings) {
|
|
|
33251
33337
|
function rowGetter(app, t, settings, tz = "") {
|
|
33252
33338
|
const real = knownKeys(t, settings);
|
|
33253
33339
|
let inlinks = null;
|
|
33340
|
+
const scope = currentReadScope();
|
|
33341
|
+
const seen = /* @__PURE__ */ new Map();
|
|
33342
|
+
const counts = (src) => {
|
|
33343
|
+
if (!scope) return true;
|
|
33344
|
+
let ok2 = seen.get(src);
|
|
33345
|
+
if (ok2 === void 0) seen.set(src, ok2 = pathReadable(app, settings, src, scope));
|
|
33346
|
+
return ok2;
|
|
33347
|
+
};
|
|
33254
33348
|
const backlinks = (path23) => {
|
|
33255
33349
|
if (!app) return null;
|
|
33256
33350
|
const kept2 = app.metadataCache.backlinksOf;
|
|
33257
|
-
if (kept2) return Object.keys(kept2.call(app.metadataCache, path23)).filter((src) => src !== path23).length;
|
|
33351
|
+
if (kept2) return Object.keys(kept2.call(app.metadataCache, path23)).filter((src) => src !== path23 && counts(src)).length;
|
|
33258
33352
|
if (!inlinks) {
|
|
33259
33353
|
inlinks = /* @__PURE__ */ new Map();
|
|
33260
33354
|
const resolved = app.metadataCache.resolvedLinks;
|
|
33261
33355
|
for (const [src, dests] of Object.entries(resolved)) {
|
|
33356
|
+
if (!counts(src)) continue;
|
|
33262
33357
|
for (const d of Object.keys(dests)) if (d !== src) inlinks.set(d, (inlinks.get(d) ?? 0) + 1);
|
|
33263
33358
|
}
|
|
33264
33359
|
}
|
|
@@ -35090,6 +35185,7 @@ async function finish(app, settings, cs, conflicts, violations, opts) {
|
|
|
35090
35185
|
base.conflicts = conflicts;
|
|
35091
35186
|
base.violations = violations.filter((v) => v.violations.length > 0);
|
|
35092
35187
|
if (base.conflicts.length > 0 || base.violations.length > 0) return base;
|
|
35188
|
+
await assertChangeSetWritable(app, settings, cs);
|
|
35093
35189
|
if (dryRun) return { ...base, ok: true };
|
|
35094
35190
|
const done = await whileWriting(app, () => withLogLock(app, async () => {
|
|
35095
35191
|
if (staleWrite(app)) return null;
|
|
@@ -36037,6 +36133,7 @@ var init_engine = __esm({
|
|
|
36037
36133
|
init_actor();
|
|
36038
36134
|
init_journal();
|
|
36039
36135
|
init_inverse();
|
|
36136
|
+
init_write_scope();
|
|
36040
36137
|
init_file_cache();
|
|
36041
36138
|
init_transaction_log();
|
|
36042
36139
|
init_changeset();
|
|
@@ -38840,11 +38937,223 @@ var init_extension_host = __esm({
|
|
|
38840
38937
|
}
|
|
38841
38938
|
});
|
|
38842
38939
|
|
|
38940
|
+
// src/api/extension-columns.ts
|
|
38941
|
+
function roleOf(v) {
|
|
38942
|
+
if (typeof v !== "string") return null;
|
|
38943
|
+
const m = ROLE_REF.exec(v);
|
|
38944
|
+
return m ? m[1] : null;
|
|
38945
|
+
}
|
|
38946
|
+
var isObject5, LOGICAL2, DATE_EXPR3, QUERY_KEYS, JOIN_KEYS, UNION_PART_KEYS, ColumnBinder;
|
|
38947
|
+
var init_extension_columns = __esm({
|
|
38948
|
+
"src/api/extension-columns.ts"() {
|
|
38949
|
+
"use strict";
|
|
38950
|
+
init_extension_manifest();
|
|
38951
|
+
isObject5 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
38952
|
+
LOGICAL2 = /* @__PURE__ */ new Set(["or", "and", "not"]);
|
|
38953
|
+
DATE_EXPR3 = /^date\((.+)\)([+-]\d+)?$/;
|
|
38954
|
+
QUERY_KEYS = /* @__PURE__ */ new Set(["table", "columns", "where", "order_by", "limit", "offset", "expand", "join", "group_by", "aggregates", "include_body", "union"]);
|
|
38955
|
+
JOIN_KEYS = /* @__PURE__ */ new Set(["table", "on", "as", "type", "columns", "where"]);
|
|
38956
|
+
UNION_PART_KEYS = /* @__PURE__ */ new Set(["table", "columns", "where"]);
|
|
38957
|
+
ColumnBinder = class {
|
|
38958
|
+
constructor(manifest, map) {
|
|
38959
|
+
this.manifest = manifest;
|
|
38960
|
+
this.issues = [];
|
|
38961
|
+
this.map = {};
|
|
38962
|
+
for (const [role, cols] of Object.entries(map)) {
|
|
38963
|
+
const differing = Object.entries(cols).filter(([from, to]) => from !== to);
|
|
38964
|
+
if (differing.length > 0) this.map[role] = Object.fromEntries(differing);
|
|
38965
|
+
}
|
|
38966
|
+
}
|
|
38967
|
+
/** True when no role has a column bound to another name: every rewrite is the identity. */
|
|
38968
|
+
get empty() {
|
|
38969
|
+
return Object.keys(this.map).length === 0;
|
|
38970
|
+
}
|
|
38971
|
+
/** The bound name of a role's manifest column. */
|
|
38972
|
+
name(role, column) {
|
|
38973
|
+
return role === null ? column : this.map[role]?.[column] ?? column;
|
|
38974
|
+
}
|
|
38975
|
+
/** The manifest columns of a role bound to another name. */
|
|
38976
|
+
mapped(role) {
|
|
38977
|
+
return Object.keys(this.map[role] ?? {});
|
|
38978
|
+
}
|
|
38979
|
+
/** The role a link column of `role` points to (`ref: "@x"`, or `self`), or null. */
|
|
38980
|
+
linkRole(role, column) {
|
|
38981
|
+
const c = typeof column === "string" ? this.manifest.tables[role]?.columns.find((x) => x.name === column) : column;
|
|
38982
|
+
if (!c || c.ref === void 0) return null;
|
|
38983
|
+
if (!(c.type === "link" || c.type === "list" && c.items === "link")) return null;
|
|
38984
|
+
return c.ref === "self" ? role : roleOf(c.ref);
|
|
38985
|
+
}
|
|
38986
|
+
/** A dotted column path read on `role`: each part on the role the previous link points to. */
|
|
38987
|
+
path(role, dotted) {
|
|
38988
|
+
if (role === null || this.empty) return dotted;
|
|
38989
|
+
const parts = dotted.split(".");
|
|
38990
|
+
const out2 = [];
|
|
38991
|
+
let cur = role;
|
|
38992
|
+
for (const part of parts) {
|
|
38993
|
+
out2.push(this.name(cur, part));
|
|
38994
|
+
cur = cur === null ? null : this.linkRole(cur, part);
|
|
38995
|
+
}
|
|
38996
|
+
return out2.join(".");
|
|
38997
|
+
}
|
|
38998
|
+
/** A group_by or join expression: `col`, `path`, `date(col)`, `date(col)+1`. */
|
|
38999
|
+
expr(role, e) {
|
|
39000
|
+
const d = DATE_EXPR3.exec(e);
|
|
39001
|
+
return d ? `date(${this.path(role, d[1])})${d[2] ?? ""}` : this.path(role, e);
|
|
39002
|
+
}
|
|
39003
|
+
/** A `where` filter (or a CHECK) on `role`: keys are column paths; `or` / `and` / `not` nest. */
|
|
39004
|
+
where(role, where) {
|
|
39005
|
+
if (!isObject5(where) || role === null || this.empty) return where;
|
|
39006
|
+
const out2 = {};
|
|
39007
|
+
for (const [k, v] of Object.entries(where)) {
|
|
39008
|
+
if (LOGICAL2.has(k)) out2[k] = Array.isArray(v) ? v.map((w) => this.where(role, w)) : this.where(role, v);
|
|
39009
|
+
else out2[this.path(role, k)] = v;
|
|
39010
|
+
}
|
|
39011
|
+
return out2;
|
|
39012
|
+
}
|
|
39013
|
+
/** A column spec of `role`: its link filter (`ref_where`), `ref_by`, `inverse`. */
|
|
39014
|
+
column(role, c) {
|
|
39015
|
+
if (this.empty) return c;
|
|
39016
|
+
let out2 = c;
|
|
39017
|
+
const target = this.linkRole(role, c);
|
|
39018
|
+
if (c.ref_where !== void 0) out2 = { ...out2, ref_where: this.where(target, c.ref_where) };
|
|
39019
|
+
if (isObject5(c.ref_by)) {
|
|
39020
|
+
const by = c.ref_by;
|
|
39021
|
+
const col = this.name(role, String(by.column));
|
|
39022
|
+
const tgt = this.name(target, String(by.target_column ?? by.column));
|
|
39023
|
+
const { target_column: _t, ...rest } = by;
|
|
39024
|
+
out2 = { ...out2, ref_by: { ...rest, column: col, ...tgt !== col ? { target_column: tgt } : {} } };
|
|
39025
|
+
}
|
|
39026
|
+
if (typeof c.inverse === "string") out2 = { ...out2, inverse: this.name(target, c.inverse) };
|
|
39027
|
+
return out2;
|
|
39028
|
+
}
|
|
39029
|
+
/** A view query: the tables it reads are roles (`@meal`); every column it names follows them. */
|
|
39030
|
+
query(view, q) {
|
|
39031
|
+
if (this.empty) return q;
|
|
39032
|
+
if (Array.isArray(q.union)) return this.union(view, q);
|
|
39033
|
+
const role = roleOf(q.table);
|
|
39034
|
+
const out2 = {};
|
|
39035
|
+
const aliases = new Set(isObject5(q.aggregates) ? Object.keys(q.aggregates) : []);
|
|
39036
|
+
const col = (v) => aliases.has(v) ? v : this.path(role, v);
|
|
39037
|
+
for (const [k, v] of Object.entries(q)) {
|
|
39038
|
+
switch (k) {
|
|
39039
|
+
case "columns":
|
|
39040
|
+
case "expand":
|
|
39041
|
+
out2[k] = Array.isArray(v) ? v.map((x) => typeof x === "string" ? col(x) : x) : v;
|
|
39042
|
+
break;
|
|
39043
|
+
case "order_by":
|
|
39044
|
+
out2[k] = Array.isArray(v) ? v.map((x) => this.order(x, col)) : this.order(v, col);
|
|
39045
|
+
break;
|
|
39046
|
+
case "where":
|
|
39047
|
+
out2[k] = this.where(role, v);
|
|
39048
|
+
break;
|
|
39049
|
+
case "group_by":
|
|
39050
|
+
out2[k] = Array.isArray(v) ? v.map((x) => typeof x === "string" ? this.expr(role, x) : x) : typeof v === "string" ? this.expr(role, v) : v;
|
|
39051
|
+
break;
|
|
39052
|
+
case "aggregates":
|
|
39053
|
+
out2[k] = isObject5(v) ? Object.fromEntries(Object.entries(v).map(([a, spec2]) => [a, this.aggregate(role, spec2)])) : v;
|
|
39054
|
+
break;
|
|
39055
|
+
case "join":
|
|
39056
|
+
out2[k] = isObject5(v) ? this.join(view, role, v) : v;
|
|
39057
|
+
break;
|
|
39058
|
+
default:
|
|
39059
|
+
if (!QUERY_KEYS.has(k)) this.unknown(view, [role], k, v);
|
|
39060
|
+
out2[k] = v;
|
|
39061
|
+
}
|
|
39062
|
+
}
|
|
39063
|
+
return out2;
|
|
39064
|
+
}
|
|
39065
|
+
order(v, col) {
|
|
39066
|
+
if (typeof v !== "string") return v;
|
|
39067
|
+
return v.startsWith("-") ? `-${col(v.slice(1))}` : col(v);
|
|
39068
|
+
}
|
|
39069
|
+
/** `count`, `count:col`, `sum:meal.kcal`. */
|
|
39070
|
+
aggregate(role, spec2) {
|
|
39071
|
+
if (typeof spec2 !== "string") return spec2;
|
|
39072
|
+
const i = spec2.indexOf(":");
|
|
39073
|
+
return i < 0 ? spec2 : `${spec2.slice(0, i)}:${this.path(role, spec2.slice(i + 1))}`;
|
|
39074
|
+
}
|
|
39075
|
+
/** `{table, on: {left expr: right expr}, as, type, columns, where}`: `columns` and `where` read the joined table. */
|
|
39076
|
+
join(view, left, j) {
|
|
39077
|
+
const right = roleOf(j.table);
|
|
39078
|
+
const out2 = {};
|
|
39079
|
+
for (const [k, v] of Object.entries(j)) {
|
|
39080
|
+
if (k === "on" && isObject5(v)) {
|
|
39081
|
+
out2[k] = Object.fromEntries(Object.entries(v).map(([a, b]) => [this.expr(left, a), typeof b === "string" ? this.expr(right, b) : b]));
|
|
39082
|
+
} else if (k === "columns" && Array.isArray(v)) out2[k] = v.map((x) => typeof x === "string" ? this.path(right, x) : x);
|
|
39083
|
+
else if (k === "where") out2[k] = this.where(right, v);
|
|
39084
|
+
else {
|
|
39085
|
+
if (!JOIN_KEYS.has(k)) this.unknown(view, [left, right], `join.${k}`, v);
|
|
39086
|
+
out2[k] = v;
|
|
39087
|
+
}
|
|
39088
|
+
}
|
|
39089
|
+
return out2;
|
|
39090
|
+
}
|
|
39091
|
+
/** `{union: [{table, columns, where}], …}`: each part on its role; the outer clauses read every part, so a mapped column there is an issue. */
|
|
39092
|
+
union(view, q) {
|
|
39093
|
+
const roles = [];
|
|
39094
|
+
const parts = q.union.map((p) => {
|
|
39095
|
+
if (!isObject5(p)) return p;
|
|
39096
|
+
const role = roleOf(p.table);
|
|
39097
|
+
roles.push(role);
|
|
39098
|
+
const out2 = {};
|
|
39099
|
+
for (const [k, v] of Object.entries(p)) {
|
|
39100
|
+
if (k === "columns" && Array.isArray(v)) out2[k] = v.map((x) => typeof x === "string" ? this.path(role, x) : x);
|
|
39101
|
+
else if (k === "where") out2[k] = this.where(role, v);
|
|
39102
|
+
else {
|
|
39103
|
+
if (!UNION_PART_KEYS.has(k)) this.unknown(view, [role], `union.${k}`, v);
|
|
39104
|
+
out2[k] = v;
|
|
39105
|
+
}
|
|
39106
|
+
}
|
|
39107
|
+
return out2;
|
|
39108
|
+
});
|
|
39109
|
+
for (const [k, v] of Object.entries(q)) if (k !== "union") this.unknown(view, roles, `union ${k}`, v);
|
|
39110
|
+
return { ...q, union: parts };
|
|
39111
|
+
}
|
|
39112
|
+
/** A clause the binder does not rewrite: an issue for each mapped column it names. */
|
|
39113
|
+
unknown(view, roles, key2, v) {
|
|
39114
|
+
for (const n of this.named(v, roles.filter((r) => r !== null), () => true)) this.issues.push({ ...n, where: `view ${view} (${key2})` });
|
|
39115
|
+
}
|
|
39116
|
+
/**
|
|
39117
|
+
* The mapped columns a value names, where nothing rewrites it: a word that is a mapped column of
|
|
39118
|
+
* one of `roles`, or a later part of a dotted path (`author.name`) that is a mapped column of any
|
|
39119
|
+
* role. `keys(key, inKeys)` tells whether the keys under `key` are names too.
|
|
39120
|
+
*/
|
|
39121
|
+
named(v, roles, keys) {
|
|
39122
|
+
const words = /* @__PURE__ */ new Set();
|
|
39123
|
+
const tails = /* @__PURE__ */ new Set();
|
|
39124
|
+
const text5 = (x) => {
|
|
39125
|
+
for (const w of x.split(/[^A-Za-z0-9_.]+/)) {
|
|
39126
|
+
const parts = w.split(".");
|
|
39127
|
+
if (parts.length === 1) words.add(w);
|
|
39128
|
+
else for (const p of parts.slice(1)) tails.add(p);
|
|
39129
|
+
}
|
|
39130
|
+
};
|
|
39131
|
+
const walk = (x, inKeys) => {
|
|
39132
|
+
if (typeof x === "string") text5(x);
|
|
39133
|
+
else if (Array.isArray(x)) x.forEach((y) => walk(y, inKeys));
|
|
39134
|
+
else if (isObject5(x)) {
|
|
39135
|
+
for (const [k, y] of Object.entries(x)) {
|
|
39136
|
+
if (inKeys) text5(k);
|
|
39137
|
+
walk(y, keys(k, inKeys));
|
|
39138
|
+
}
|
|
39139
|
+
}
|
|
39140
|
+
};
|
|
39141
|
+
walk(v, keys("", false));
|
|
39142
|
+
const out2 = [];
|
|
39143
|
+
for (const role of Object.keys(this.map)) {
|
|
39144
|
+
for (const column of this.mapped(role)) if (tails.has(column) || roles.includes(role) && words.has(column)) out2.push({ role, column });
|
|
39145
|
+
}
|
|
39146
|
+
return out2;
|
|
39147
|
+
}
|
|
39148
|
+
};
|
|
39149
|
+
}
|
|
39150
|
+
});
|
|
39151
|
+
|
|
38843
39152
|
// src/api/extension-plan.ts
|
|
38844
39153
|
function stable(v) {
|
|
38845
39154
|
const walk = (x) => {
|
|
38846
39155
|
if (Array.isArray(x)) return x.map(walk);
|
|
38847
|
-
if (
|
|
39156
|
+
if (isObject6(x)) {
|
|
38848
39157
|
const out2 = {};
|
|
38849
39158
|
for (const k of Object.keys(x).sort()) out2[k] = walk(x[k]);
|
|
38850
39159
|
return out2;
|
|
@@ -39018,6 +39327,10 @@ function planChanges(input) {
|
|
|
39018
39327
|
for (const role of m.roles) tables[role] = choiceOf.get(role)?.table ?? `@${role}`;
|
|
39019
39328
|
const bind = (v) => bindRoles(v, tables);
|
|
39020
39329
|
const existing = (c) => c.decision === "adopt" || c.decision === "match";
|
|
39330
|
+
const columnMap = {};
|
|
39331
|
+
for (const c of input.chosen) if (!c.owned && c.decision === "match") columnMap[c.role] = { ...recorded[c.role]?.columns ?? {}, ...choices.columns[c.role] ?? {} };
|
|
39332
|
+
const binder = new ColumnBinder(m, columnMap);
|
|
39333
|
+
const bindColumn = (role, c) => bind(spec(binder.column(role, c)));
|
|
39021
39334
|
const model = /* @__PURE__ */ new Map();
|
|
39022
39335
|
for (const c of input.chosen) if (existing(c)) model.set(c.table, modelTable(input.described[c.table] ?? { qualified: c.table, columns: [] }));
|
|
39023
39336
|
const views = new Set(input.listing.views.map((v) => v.qualified));
|
|
@@ -39045,6 +39358,19 @@ function planChanges(input) {
|
|
|
39045
39358
|
);
|
|
39046
39359
|
continue;
|
|
39047
39360
|
}
|
|
39361
|
+
const touched = mappedNamed(raw);
|
|
39362
|
+
if (touched !== null) {
|
|
39363
|
+
migrationProblems.push(
|
|
39364
|
+
problem(
|
|
39365
|
+
"migration_conflict",
|
|
39366
|
+
`migration ${g.version}: ${raw.op} names column ${touched.column} of ${touched.role}, which reads your column ${binder.name(touched.role, touched.column)}`,
|
|
39367
|
+
`Rename your column to ${touched.column} with rename_column and run the command again without --map ${touched.role}.${touched.column}, or ask the extension's author.`,
|
|
39368
|
+
EXT_DOCS.update,
|
|
39369
|
+
{ role: touched.role, column: touched.column }
|
|
39370
|
+
)
|
|
39371
|
+
);
|
|
39372
|
+
continue;
|
|
39373
|
+
}
|
|
39048
39374
|
const op2 = bind(raw);
|
|
39049
39375
|
const r = simulate(op2, g.version);
|
|
39050
39376
|
if (typeof r === "string") {
|
|
@@ -39056,6 +39382,9 @@ function planChanges(input) {
|
|
|
39056
39382
|
}
|
|
39057
39383
|
}
|
|
39058
39384
|
}
|
|
39385
|
+
function mappedNamed(raw) {
|
|
39386
|
+
return binder.named(raw, rolesNamed(raw), (k, inKeys) => inKeys || FILTER_KEYS.has(k))[0] ?? null;
|
|
39387
|
+
}
|
|
39059
39388
|
function qualify(op2) {
|
|
39060
39389
|
return `${String(op2.catalog ?? catalog)}.${String(op2.schema ?? viewSchema)}.${String(op2.name)}`;
|
|
39061
39390
|
}
|
|
@@ -39188,7 +39517,7 @@ function planChanges(input) {
|
|
|
39188
39517
|
schema,
|
|
39189
39518
|
name: tname,
|
|
39190
39519
|
description: def.description,
|
|
39191
|
-
columns:
|
|
39520
|
+
columns: def.columns.map((c) => bindColumn(role, c)),
|
|
39192
39521
|
...def.filename !== void 0 ? { filename: def.filename } : {},
|
|
39193
39522
|
...def.aliases !== void 0 ? { aliases: def.aliases } : {}
|
|
39194
39523
|
});
|
|
@@ -39197,8 +39526,8 @@ function planChanges(input) {
|
|
|
39197
39526
|
}
|
|
39198
39527
|
const actualCols = model.get(q).columns;
|
|
39199
39528
|
for (const c of def.columns) {
|
|
39200
|
-
const bound =
|
|
39201
|
-
const actualName =
|
|
39529
|
+
const bound = bindColumn(role, c);
|
|
39530
|
+
const actualName = binder.name(role, c.name);
|
|
39202
39531
|
const actual = actualCols.get(actualName);
|
|
39203
39532
|
if (actual) {
|
|
39204
39533
|
const diff = columnDifference(bound, actual, q, false);
|
|
@@ -39239,7 +39568,7 @@ function planChanges(input) {
|
|
|
39239
39568
|
const q = t.table;
|
|
39240
39569
|
const tags = { extension: name, extension_role: role };
|
|
39241
39570
|
const instructions = def.instructions === void 0 ? void 0 : bind(def.instructions);
|
|
39242
|
-
const constraint = (c) => ({ op: "add_constraint", table: q, name: c.name, check: bind(c.check), hint: c.hint });
|
|
39571
|
+
const constraint = (c) => ({ op: "add_constraint", table: q, name: c.name, check: bind(binder.where(role, c.check)), hint: c.hint });
|
|
39243
39572
|
if (t.decision === "create") {
|
|
39244
39573
|
const [cat, schema, tname] = q.split(".");
|
|
39245
39574
|
operations.push({
|
|
@@ -39248,7 +39577,7 @@ function planChanges(input) {
|
|
|
39248
39577
|
schema,
|
|
39249
39578
|
name: tname,
|
|
39250
39579
|
description: def.description,
|
|
39251
|
-
columns:
|
|
39580
|
+
columns: def.columns.map((c) => bindColumn(role, c)),
|
|
39252
39581
|
...def.filename !== void 0 ? { filename: def.filename } : {},
|
|
39253
39582
|
...def.aliases !== void 0 ? { aliases: def.aliases } : {},
|
|
39254
39583
|
...instructions !== void 0 ? { instructions } : {},
|
|
@@ -39261,7 +39590,7 @@ function planChanges(input) {
|
|
|
39261
39590
|
}
|
|
39262
39591
|
const mt = model.get(q);
|
|
39263
39592
|
for (const c of def.columns) {
|
|
39264
|
-
const bound =
|
|
39593
|
+
const bound = bindColumn(role, c);
|
|
39265
39594
|
const actual = mt.columns.get(c.name);
|
|
39266
39595
|
if (!actual) {
|
|
39267
39596
|
operations.push({ op: "add_column", table: q, column: bound });
|
|
@@ -39285,7 +39614,7 @@ function planChanges(input) {
|
|
|
39285
39614
|
if (!actual) {
|
|
39286
39615
|
operations.push(constraint(c));
|
|
39287
39616
|
t.changes.push(`add CHECK ${c.name}`);
|
|
39288
|
-
} else if (stable(actual.check) !== stable(bind(c.check)) || actual.hint !== c.hint) {
|
|
39617
|
+
} else if (stable(actual.check) !== stable(bind(binder.where(role, c.check))) || actual.hint !== c.hint) {
|
|
39289
39618
|
operations.push({ op: "drop_constraint", table: q, name: c.name }, constraint(c));
|
|
39290
39619
|
t.changes.push(`replace CHECK ${c.name}`);
|
|
39291
39620
|
}
|
|
@@ -39313,13 +39642,14 @@ function planChanges(input) {
|
|
|
39313
39642
|
for (const v of Object.keys(m.views)) {
|
|
39314
39643
|
const def = m.views[v];
|
|
39315
39644
|
const q = `${catalog}.${viewSchema}.${v}`;
|
|
39645
|
+
const query = binder.query(v, def.query);
|
|
39316
39646
|
const create = {
|
|
39317
39647
|
op: "create_view",
|
|
39318
39648
|
catalog,
|
|
39319
39649
|
schema: viewSchema,
|
|
39320
39650
|
name: v,
|
|
39321
39651
|
description: def.description,
|
|
39322
|
-
query: bind(
|
|
39652
|
+
query: bind(query),
|
|
39323
39653
|
...def.aliases !== void 0 ? { aliases: def.aliases } : {},
|
|
39324
39654
|
...def.labels !== void 0 ? { labels: def.labels } : {}
|
|
39325
39655
|
};
|
|
@@ -39328,7 +39658,7 @@ function planChanges(input) {
|
|
|
39328
39658
|
viewPlan.changes.push(`create view ${v}`);
|
|
39329
39659
|
} else if (action === "install") {
|
|
39330
39660
|
reconcileProblems.push(problem("view_exists", `${q} exists already`, "Drop or rename that view (drop_view), or install with --schema <another schema>.", EXT_DOCS.install, { table: q }));
|
|
39331
|
-
} else if (stable(oldManifest.views[v]) !== stable(def)) {
|
|
39661
|
+
} else if (stable(oldManifest.views[v]) !== stable(def) || stable(query) !== stable(def.query)) {
|
|
39332
39662
|
operations.push({ ...create, replace: true });
|
|
39333
39663
|
viewPlan.changes.push(`replace view ${v}`);
|
|
39334
39664
|
}
|
|
@@ -39344,6 +39674,17 @@ function planChanges(input) {
|
|
|
39344
39674
|
if (m.tables[role] === void 0) notes.push(`${role} (${rt.table}) is no longer used by the extension: it stays in the database`);
|
|
39345
39675
|
}
|
|
39346
39676
|
}
|
|
39677
|
+
for (const i of binder.issues) {
|
|
39678
|
+
reconcileProblems.push(
|
|
39679
|
+
problem(
|
|
39680
|
+
"column_map_unsupported",
|
|
39681
|
+
`${i.role}: ${i.where} names column ${i.column}, which reads your column ${binder.name(i.role, i.column)}: the installer cannot rewrite it there`,
|
|
39682
|
+
`Rename your column to ${i.column} with rename_column and leave --map ${i.role}.${i.column} out, or ask the extension's author.`,
|
|
39683
|
+
EXT_DOCS.tables,
|
|
39684
|
+
{ role: i.role, column: i.column }
|
|
39685
|
+
)
|
|
39686
|
+
);
|
|
39687
|
+
}
|
|
39347
39688
|
const toolProblems = [];
|
|
39348
39689
|
const others = input.others.filter((o) => o.name !== name);
|
|
39349
39690
|
const boundNames = (roles) => roles.map((r) => tables[r]);
|
|
@@ -39464,16 +39805,18 @@ function renderPlan(plan) {
|
|
|
39464
39805
|
lines.push("", plan.agent_message, plan.summary);
|
|
39465
39806
|
return lines.join("\n");
|
|
39466
39807
|
}
|
|
39467
|
-
var AGENT_MESSAGE, MAX_OPERATIONS,
|
|
39808
|
+
var AGENT_MESSAGE, FILTER_KEYS, MAX_OPERATIONS, isObject6, obj3, list2, tableName, isLink;
|
|
39468
39809
|
var init_extension_plan = __esm({
|
|
39469
39810
|
"src/api/extension-plan.ts"() {
|
|
39470
39811
|
"use strict";
|
|
39812
|
+
init_extension_columns();
|
|
39471
39813
|
init_extension_manifest();
|
|
39472
39814
|
init_guidance();
|
|
39473
39815
|
AGENT_MESSAGE = `For the agent installing this extension: show this plan to the user before anything is written. Where a table says "proposed", the installer chose it: ask the user to confirm it, to pick another table (--map <role>=<catalog.schema.table>, --map <role>.<column>=<column>), or to create it (--create <role>). Add a missing column only after the user agrees (--add <role>.<column>). Solve each problem the same way. Then run the same command again with the user's choices and --yes, only once the user approved the tables it creates or changes, the tools and the token's permissions.`;
|
|
39816
|
+
FILTER_KEYS = /* @__PURE__ */ new Set(["check", "where", "ref_where", "query", "on"]);
|
|
39474
39817
|
MAX_OPERATIONS = 200;
|
|
39475
|
-
|
|
39476
|
-
obj3 = (v) =>
|
|
39818
|
+
isObject6 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
39819
|
+
obj3 = (v) => isObject6(v) ? v : {};
|
|
39477
39820
|
list2 = (v) => Array.isArray(v) ? v : [];
|
|
39478
39821
|
tableName = (t, role) => t.name ?? role;
|
|
39479
39822
|
isLink = (c) => c.type === "link" || c.type === "list" && c.items === "link";
|
|
@@ -40600,7 +40943,7 @@ __export(util_exports, {
|
|
|
40600
40943
|
hexToUint8Array: () => hexToUint8Array,
|
|
40601
40944
|
hide: () => hide,
|
|
40602
40945
|
installLazyProp: () => installLazyProp,
|
|
40603
|
-
isObject: () =>
|
|
40946
|
+
isObject: () => isObject7,
|
|
40604
40947
|
isPlainObject: () => isPlainObject2,
|
|
40605
40948
|
issue: () => issue,
|
|
40606
40949
|
joinValues: () => joinValues,
|
|
@@ -40807,11 +41150,11 @@ function esc(str3) {
|
|
|
40807
41150
|
function slugify(input) {
|
|
40808
41151
|
return input.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[\s_-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
40809
41152
|
}
|
|
40810
|
-
function
|
|
41153
|
+
function isObject7(data) {
|
|
40811
41154
|
return typeof data === "object" && data !== null && !Array.isArray(data);
|
|
40812
41155
|
}
|
|
40813
41156
|
function isPlainObject2(o) {
|
|
40814
|
-
if (
|
|
41157
|
+
if (isObject7(o) === false)
|
|
40815
41158
|
return false;
|
|
40816
41159
|
const ctor = o.constructor;
|
|
40817
41160
|
if (ctor === void 0)
|
|
@@ -40819,7 +41162,7 @@ function isPlainObject2(o) {
|
|
|
40819
41162
|
if (typeof ctor !== "function")
|
|
40820
41163
|
return true;
|
|
40821
41164
|
const prot = ctor.prototype;
|
|
40822
|
-
if (
|
|
41165
|
+
if (isObject7(prot) === false)
|
|
40823
41166
|
return false;
|
|
40824
41167
|
if (Object.prototype.hasOwnProperty.call(prot, "isPrototypeOf") === false) {
|
|
40825
41168
|
return false;
|
|
@@ -43218,7 +43561,7 @@ var init_schemas = __esm({
|
|
|
43218
43561
|
}
|
|
43219
43562
|
return propValues;
|
|
43220
43563
|
});
|
|
43221
|
-
const
|
|
43564
|
+
const isObject8 = isObject7;
|
|
43222
43565
|
const catchall = def.catchall;
|
|
43223
43566
|
let value;
|
|
43224
43567
|
const memo2 = globalConfig.memoizer;
|
|
@@ -43226,7 +43569,7 @@ var init_schemas = __esm({
|
|
|
43226
43569
|
inst._zod.parse = (payload, ctx) => {
|
|
43227
43570
|
value ?? (value = _normalized.value);
|
|
43228
43571
|
const input = payload.value;
|
|
43229
|
-
if (!
|
|
43572
|
+
if (!isObject8(input)) {
|
|
43230
43573
|
payload.issues.push({
|
|
43231
43574
|
expected: "object",
|
|
43232
43575
|
code: "invalid_type",
|
|
@@ -43362,7 +43705,7 @@ var init_schemas = __esm({
|
|
|
43362
43705
|
return doc.compile();
|
|
43363
43706
|
};
|
|
43364
43707
|
let fastpass;
|
|
43365
|
-
const
|
|
43708
|
+
const isObject8 = isObject7;
|
|
43366
43709
|
const jit = !globalConfig.jitless;
|
|
43367
43710
|
const allowsEval2 = allowsEval;
|
|
43368
43711
|
const fastEnabled = jit && allowsEval2.value;
|
|
@@ -43371,7 +43714,7 @@ var init_schemas = __esm({
|
|
|
43371
43714
|
inst._zod.parse = (payload, ctx) => {
|
|
43372
43715
|
value ?? (value = _normalized.value);
|
|
43373
43716
|
const input = payload.value;
|
|
43374
|
-
if (!
|
|
43717
|
+
if (!isObject8(input)) {
|
|
43375
43718
|
payload.issues.push({
|
|
43376
43719
|
expected: "object",
|
|
43377
43720
|
code: "invalid_type",
|
|
@@ -43471,7 +43814,7 @@ var init_schemas = __esm({
|
|
|
43471
43814
|
const disc = cached(() => discriminatorMap(def));
|
|
43472
43815
|
inst._zod.parse = (payload, ctx) => {
|
|
43473
43816
|
const input = payload.value;
|
|
43474
|
-
if (!
|
|
43817
|
+
if (!isObject7(input)) {
|
|
43475
43818
|
payload.issues.push({
|
|
43476
43819
|
code: "invalid_type",
|
|
43477
43820
|
expected: "object",
|
|
@@ -66871,17 +67214,28 @@ var SemanticIndex = class {
|
|
|
66871
67214
|
if (this.queries.size > QUERY_VECTORS_MAX) this.queries.delete(this.queries.keys().next().value);
|
|
66872
67215
|
return v;
|
|
66873
67216
|
}
|
|
67217
|
+
/** The index's counts; under a read scope (read-scope.ts), of the notes the token may read only. */
|
|
66874
67218
|
status() {
|
|
66875
67219
|
const s = this.getSettings();
|
|
67220
|
+
const scope = currentReadScope();
|
|
67221
|
+
const readable = (path23) => !scope || pathReadable(this.app, s, path23, scope);
|
|
67222
|
+
let files = 0;
|
|
66876
67223
|
let passages = 0;
|
|
66877
|
-
for (const f of this.files
|
|
67224
|
+
for (const [path23, f] of this.files) {
|
|
67225
|
+
if (!readable(path23)) continue;
|
|
67226
|
+
files++;
|
|
67227
|
+
passages += f.passages.length;
|
|
67228
|
+
}
|
|
67229
|
+
let pending = 0;
|
|
67230
|
+
if (scope) for (const path23 of this.pending) pending += readable(path23) ? 1 : 0;
|
|
67231
|
+
else pending = this.pending.size;
|
|
66878
67232
|
return {
|
|
66879
67233
|
enabled: s.semanticEnabled,
|
|
66880
67234
|
provider: s.semanticProvider,
|
|
66881
67235
|
model: s.semanticModel,
|
|
66882
|
-
files
|
|
67236
|
+
files,
|
|
66883
67237
|
passages,
|
|
66884
|
-
pending
|
|
67238
|
+
pending,
|
|
66885
67239
|
running: this.running !== null,
|
|
66886
67240
|
...this.lastSync ? { last_sync: this.lastSync } : {},
|
|
66887
67241
|
...this.lastError ? { last_error: this.lastError } : {}
|
|
@@ -69078,6 +69432,7 @@ init_guidance();
|
|
|
69078
69432
|
init_requests();
|
|
69079
69433
|
init_actor();
|
|
69080
69434
|
init_read_scope();
|
|
69435
|
+
init_write_scope();
|
|
69081
69436
|
init_changeset_checks();
|
|
69082
69437
|
|
|
69083
69438
|
// src/server/token-routes.ts
|
|
@@ -69110,6 +69465,8 @@ var READ_ROUTES = /* @__PURE__ */ new Set(["/manual/search/", "/tokens/me/", "/t
|
|
|
69110
69465
|
var OWNER_ADMIN_ROUTES = /* @__PURE__ */ new Set(["/tokens/", "/tokens/create/", "/tokens/update/", "/tokens/delete/", "/tokens/rotate/", "/extensions/record/", "/extensions/delete/", "/status/", "/stop/"]);
|
|
69111
69466
|
var READ_SCOPE_UNSUPPORTED = "read permissions need Node's AsyncLocalStorage";
|
|
69112
69467
|
var READ_SCOPE_UNSUPPORTED_HINT = guide(`This token has "read" patterns, which this runtime cannot enforce: run the engine on Node (datanotes serve), or have the owner pass "read": null to tokens/update/ for this token.`, "tokenRead");
|
|
69468
|
+
var WRITE_SCOPE_UNSUPPORTED = "write permissions need Node's AsyncLocalStorage";
|
|
69469
|
+
var WRITE_SCOPE_UNSUPPORTED_HINT = guide(`This token's "write" patterns limit the tables its writes may change, which this runtime cannot enforce: run the engine on Node (datanotes serve), or write with a token whose "write" is ["*"].`, "tokenWrite");
|
|
69113
69470
|
|
|
69114
69471
|
// src/server/scheduler.ts
|
|
69115
69472
|
init_telemetry();
|
|
@@ -69358,7 +69715,7 @@ function errorResponse(e, op2, route) {
|
|
|
69358
69715
|
if (e instanceof MigrationNotFoundError) return { status: 404, json: body(guide("list_migrations lists the journal ids (migration_id) of the writes, newest first.", "undo")) };
|
|
69359
69716
|
if (e instanceof VaultPathNotFoundError) return { status: 404, json: body(guide("list_folder lists a folder, search_notes finds notes by their words.", "readNote")) };
|
|
69360
69717
|
if (e instanceof ReadDeniedError) return { status: 403, json: body(READ_DENIED_GUIDANCE) };
|
|
69361
|
-
if (e instanceof Forbidden) return { status: 403, json: body(FORBIDDEN_HINT) };
|
|
69718
|
+
if (e instanceof Forbidden || e instanceof WriteDeniedError) return { status: 403, json: body(FORBIDDEN_HINT) };
|
|
69362
69719
|
if (e instanceof AmbiguousCatalogRefError) {
|
|
69363
69720
|
return { status: 409, json: { ...body(guide(`Name the table in full (catalog.schema.table): ${e.candidates.join(" or ")}.`, "catalogs")), tables: e.candidates } };
|
|
69364
69721
|
}
|
|
@@ -69766,6 +70123,7 @@ function createHandlers(app, getSettings, saveSettings = async () => {
|
|
|
69766
70123
|
batch: async (b) => {
|
|
69767
70124
|
const operations = list(b, "operations");
|
|
69768
70125
|
if (operations.length === 0) throw new BadRequest(`"operations" must be a non-empty list`);
|
|
70126
|
+
const denied = /* @__PURE__ */ new Set();
|
|
69769
70127
|
const steps = operations.map((raw, i) => {
|
|
69770
70128
|
const { op: op2, ...args } = raw;
|
|
69771
70129
|
if (typeof op2 !== "string") throw new BadRequest(`operation ${i} needs "op"`);
|
|
@@ -69780,7 +70138,10 @@ function createHandlers(app, getSettings, saveSettings = async () => {
|
|
|
69780
70138
|
return {
|
|
69781
70139
|
op: op2,
|
|
69782
70140
|
run: async (dryRun) => {
|
|
69783
|
-
const res = await ops[op2]({ ...args, dry_run: dryRun }).catch((e) =>
|
|
70141
|
+
const res = await ops[op2]({ ...args, dry_run: dryRun }).catch((e) => {
|
|
70142
|
+
if (e instanceof WriteDeniedError) denied.add(i);
|
|
70143
|
+
return errorResponse(e, op2);
|
|
70144
|
+
});
|
|
69784
70145
|
if ("op" in res.json) return res.json;
|
|
69785
70146
|
throw stepFailure(res.json);
|
|
69786
70147
|
}
|
|
@@ -69791,8 +70152,13 @@ function createHandlers(app, getSettings, saveSettings = async () => {
|
|
|
69791
70152
|
...str(b, "summary") !== void 0 ? { summary: str(b, "summary") } : {}
|
|
69792
70153
|
});
|
|
69793
70154
|
const failedStep = r.failed_at !== void 0 && (r.steps[r.failed_at]?.result?.failed.length ?? 0) > 0;
|
|
69794
|
-
const
|
|
70155
|
+
const forbidden = r.failed_at !== void 0 ? denied.has(r.failed_at) ? r.failed_at : void 0 : [...denied].sort((a, b2) => a - b2)[0];
|
|
70156
|
+
const status = r.ok ? 200 : r.rolled_back === false ? 500 : forbidden !== void 0 ? 403 : writeStatus(failedStep, true);
|
|
69795
70157
|
const answer = redactBatchRollback(r, app, s());
|
|
70158
|
+
if (status === 403) {
|
|
70159
|
+
const step = answer.steps[forbidden];
|
|
70160
|
+
return { status, json: { ...answer, error: `operation ${forbidden}: ${step.error}`, hint: step.hint, docs: step.docs } };
|
|
70161
|
+
}
|
|
69796
70162
|
return { status, json: answer.ok ? answer : { ...answer, ...batchGuidance(answer) } };
|
|
69797
70163
|
},
|
|
69798
70164
|
// --- queries ---
|
|
@@ -70256,6 +70622,7 @@ var STATUS_TEXT = {
|
|
|
70256
70622
|
409: "Ambiguous table name: it matches several tables (`paths` or `tables` lists them)."
|
|
70257
70623
|
};
|
|
70258
70624
|
var READ_DENIED_TEXT = "A named token whose `read` patterns do not cover the table, view or note the request names (such as `token cook may not read personal.health.weight`). Sets it did not name (listings, bases, search, the graph, the change feed) are filtered instead.";
|
|
70625
|
+
var SESSION_TAKEN_TEXT = "The `session` belongs to another named token: the first token to report a session owns it (compared on its first 16 characters), and only that token and the owner may report it again. Nothing is stored.";
|
|
70259
70626
|
var ERRORS = {
|
|
70260
70627
|
400: { description: STATUS_TEXT[400], content: json(ref("Error")) },
|
|
70261
70628
|
401: { $ref: "#/components/responses/Unauthorized" },
|
|
@@ -70271,6 +70638,7 @@ function postRoute(d) {
|
|
|
70271
70638
|
const read3 = op2 !== void 0 ? READ_OPERATIONS.has(op2) : READ_ROUTES.has(d.route) && d.route !== "/tokens/update/";
|
|
70272
70639
|
if (!read3) responses["403"] = ERRORS[403];
|
|
70273
70640
|
else if (op2 !== void 0) responses["403"] = ERRORS.readDenied;
|
|
70641
|
+
if (d.forbidden) responses["403"] = { description: d.forbidden, content: json(ref("Error")) };
|
|
70274
70642
|
if (d.namesTable) responses["404"] = ERRORS[404];
|
|
70275
70643
|
const refused2 = op2 !== void 0 ? refusedStatus(op2) : void 0;
|
|
70276
70644
|
if (refused2 === 409) {
|
|
@@ -70609,6 +70977,7 @@ function openApiDocument(version2) {
|
|
|
70609
70977
|
route: "/sessions/identify/",
|
|
70610
70978
|
operationId: "identify_session",
|
|
70611
70979
|
summary: "Tell the engine the model and harness behind an agent session (what an MCP server reports): later requests carrying that session in `X-Datanotes-Actor` are journaled with it. Any token may call it.",
|
|
70980
|
+
forbidden: SESSION_TAKEN_TEXT,
|
|
70612
70981
|
body: { type: "object", required: ["session"], properties: { session: { type: "string" }, model: { type: "string" }, harness: { type: "string" } } },
|
|
70613
70982
|
bodyRequired: true,
|
|
70614
70983
|
result: ok
|
|
@@ -70617,6 +70986,7 @@ function openApiDocument(version2) {
|
|
|
70617
70986
|
route: "/tool-calls/record/",
|
|
70618
70987
|
operationId: "record_tool_call",
|
|
70619
70988
|
summary: "Record an agent's tool call in the change feed (`tool_call` events): what an MCP server reports after each call, its own tools included (`served_by`). Any token may call it.",
|
|
70989
|
+
forbidden: SESSION_TAKEN_TEXT,
|
|
70620
70990
|
body: {
|
|
70621
70991
|
type: "object",
|
|
70622
70992
|
required: ["tool"],
|
|
@@ -71352,6 +71722,8 @@ var MAX_IDENTITIES = 2e3;
|
|
|
71352
71722
|
var SessionIdentities = class {
|
|
71353
71723
|
constructor() {
|
|
71354
71724
|
this.map = /* @__PURE__ */ new Map();
|
|
71725
|
+
/** Who reported each session first: "owner" or a token's name, by the session's first 16 characters. */
|
|
71726
|
+
this.owners = /* @__PURE__ */ new Map();
|
|
71355
71727
|
}
|
|
71356
71728
|
set(key2, info) {
|
|
71357
71729
|
const clean2 = Object.fromEntries(Object.entries(info).filter(([, v]) => typeof v === "string" && v.trim() !== "").map(([k, v]) => [k, String(v).trim().slice(0, 200)]));
|
|
@@ -71363,6 +71735,21 @@ var SessionIdentities = class {
|
|
|
71363
71735
|
get(key2) {
|
|
71364
71736
|
return this.map.get(key2) ?? this.map.get(key2.slice(0, 16));
|
|
71365
71737
|
}
|
|
71738
|
+
/**
|
|
71739
|
+
* Claim a session for a credential ("owner" or a token's name): the first one to report it keeps
|
|
71740
|
+
* it. Answers the token that holds it when that is another named token, else null (claimed, or
|
|
71741
|
+
* the owner, who may report any session). Keyed by the first 16 characters, as lookups fall back
|
|
71742
|
+
* to them: a token cannot take over another's session with a longer or shorter id.
|
|
71743
|
+
*/
|
|
71744
|
+
claim(key2, who) {
|
|
71745
|
+
const k = key2.slice(0, 16);
|
|
71746
|
+
const held = this.owners.get(k);
|
|
71747
|
+
if (held !== void 0 && held !== who && who !== "owner") return held;
|
|
71748
|
+
this.owners.delete(k);
|
|
71749
|
+
if (this.owners.size >= MAX_IDENTITIES) this.owners.delete(this.owners.keys().next().value);
|
|
71750
|
+
this.owners.set(k, held ?? who);
|
|
71751
|
+
return null;
|
|
71752
|
+
}
|
|
71366
71753
|
};
|
|
71367
71754
|
|
|
71368
71755
|
// src/server/runtime.ts
|
|
@@ -71749,6 +72136,7 @@ async function engineStatus(i) {
|
|
|
71749
72136
|
// src/server/tokens.ts
|
|
71750
72137
|
init_actor();
|
|
71751
72138
|
init_read_scope();
|
|
72139
|
+
init_write_scope();
|
|
71752
72140
|
init_metastore();
|
|
71753
72141
|
init_store();
|
|
71754
72142
|
init_telemetry();
|
|
@@ -71768,6 +72156,13 @@ function tableAllowed(full, patterns) {
|
|
|
71768
72156
|
function scopeOf(t) {
|
|
71769
72157
|
return t.read === void 0 || t.read.includes("*") ? null : { token: t.name, patterns: t.read };
|
|
71770
72158
|
}
|
|
72159
|
+
function writeScopeOf(t) {
|
|
72160
|
+
return t.write.includes("*") ? null : { token: t.name, patterns: t.write };
|
|
72161
|
+
}
|
|
72162
|
+
function writesRows(route) {
|
|
72163
|
+
const op2 = operationOf(route);
|
|
72164
|
+
return op2 === "batch" || op2 !== void 0 && Object.prototype.hasOwnProperty.call(ROW_WRITES, op2);
|
|
72165
|
+
}
|
|
71771
72166
|
function readCovers(r, w) {
|
|
71772
72167
|
return r === "*" || r === w || r.endsWith(".*") && w.startsWith(r.slice(0, -1));
|
|
71773
72168
|
}
|
|
@@ -72010,7 +72405,12 @@ var TokenRegistry = class {
|
|
|
72010
72405
|
telemetry().log("WARN", "datanotes token refused", { token_name: name, "http.route": route, reason: READ_SCOPE_UNSUPPORTED });
|
|
72011
72406
|
return { status: 503, json: { error: READ_SCOPE_UNSUPPORTED, ...READ_SCOPE_UNSUPPORTED_HINT } };
|
|
72012
72407
|
}
|
|
72013
|
-
|
|
72408
|
+
const writeScope = writeScopeOf(token);
|
|
72409
|
+
if (writeScope && writesRows(route) && !(this.deps.readScopeSupported ?? READ_SCOPE_SUPPORTED)) {
|
|
72410
|
+
telemetry().log("WARN", "datanotes token refused", { token_name: name, "http.route": route, reason: WRITE_SCOPE_UNSUPPORTED });
|
|
72411
|
+
return { status: 503, json: { error: WRITE_SCOPE_UNSUPPORTED, ...WRITE_SCOPE_UNSUPPORTED_HINT } };
|
|
72412
|
+
}
|
|
72413
|
+
return runWithReadScope(scope, () => runWithWriteScope(writeScope, () => dispatch2(route, handler, body)));
|
|
72014
72414
|
};
|
|
72015
72415
|
}
|
|
72016
72416
|
// --- routes --------------------------------------------------------------------------------
|
|
@@ -72254,6 +72654,7 @@ var ExtensionRegistry = class {
|
|
|
72254
72654
|
init_metastore();
|
|
72255
72655
|
init_query_tables();
|
|
72256
72656
|
init_file_cache();
|
|
72657
|
+
init_read_scope();
|
|
72257
72658
|
init_vault_index();
|
|
72258
72659
|
var CACHED_READS = /* @__PURE__ */ new Set([
|
|
72259
72660
|
"select_rows",
|
|
@@ -72389,7 +72790,7 @@ var ReadCache = class {
|
|
|
72389
72790
|
if (this.maxBytes === 0 || !CACHED_READS.has(op2)) return null;
|
|
72390
72791
|
const settings = this.getSettings();
|
|
72391
72792
|
const model = (await loadMetastore(this.app, settings).catch(() => null))?.model ?? null;
|
|
72392
|
-
const deps = readDeps(op2, body, model);
|
|
72793
|
+
const deps = readDeps(op2, body, model, currentReadScope() !== null);
|
|
72393
72794
|
if (!deps) return null;
|
|
72394
72795
|
if (!this.looseKnown) this.indexLoose(settings);
|
|
72395
72796
|
if (deps.links && !this.linkPrints) this.printLinks();
|
|
@@ -72535,7 +72936,7 @@ var ReadCache = class {
|
|
|
72535
72936
|
function settingsKey(s) {
|
|
72536
72937
|
return JSON.stringify(s, (k, v) => SECRET_SETTINGS.has(k) ? void 0 : v);
|
|
72537
72938
|
}
|
|
72538
|
-
function readDeps(op2, body, model) {
|
|
72939
|
+
function readDeps(op2, body, model, restricted = false) {
|
|
72539
72940
|
if (!CACHED_READS.has(op2)) return null;
|
|
72540
72941
|
const text5 = JSON.stringify(body);
|
|
72541
72942
|
if (text5.includes("{now")) return null;
|
|
@@ -72546,7 +72947,10 @@ function readDeps(op2, body, model) {
|
|
|
72546
72947
|
const found = /* @__PURE__ */ new Set();
|
|
72547
72948
|
const r = queryTables(body, model, found, 0);
|
|
72548
72949
|
if (r.now || r.store) return null;
|
|
72549
|
-
if (!r.vault)
|
|
72950
|
+
if (!r.vault) {
|
|
72951
|
+
const tables = r.links && restricted ? catalogTables(model).map(({ ref: ref2 }) => ref2.full) : [...found];
|
|
72952
|
+
return { tables: tables.sort(), today: today || r.today, ...r.links ? { links: true } : {} };
|
|
72953
|
+
}
|
|
72550
72954
|
const views = viewTimes(model);
|
|
72551
72955
|
if (views.now) return null;
|
|
72552
72956
|
return { tables: "vault", today: today || r.today || views.today };
|
|
@@ -72575,6 +72979,7 @@ function viewTimes(model) {
|
|
|
72575
72979
|
// src/server/runtime.ts
|
|
72576
72980
|
init_message();
|
|
72577
72981
|
init_hints();
|
|
72982
|
+
var SESSION_TAKEN_HINT = guide("Use your own session id, such as a new UUID for each agent session. This session belongs to another token: only that token and the owner may report it.", "sessionOwner");
|
|
72578
72983
|
var REPORT_HINT = guide(`Send {"session", "model", "harness"} to sessions/identify/ and {"tool", "args", "ok", ...} to tool-calls/record/; DatanotesClient.recordToolCall does it.`, "reportAgents");
|
|
72579
72984
|
var EXTERNAL_EDIT_DELAY_MS = 3e3;
|
|
72580
72985
|
var LOG_CHECK_EVERY_MS = 10 * 6e4;
|
|
@@ -72709,6 +73114,8 @@ var EngineRuntime = class {
|
|
|
72709
73114
|
"/sessions/identify/": async (body) => {
|
|
72710
73115
|
const session = typeof body.session === "string" ? body.session.trim() : "";
|
|
72711
73116
|
if (!session) return { status: 400, json: { error: `"session" is required`, ...REPORT_HINT } };
|
|
73117
|
+
const taken = this.sessionTaken(session);
|
|
73118
|
+
if (taken) return taken;
|
|
72712
73119
|
this.identities.set(session, { model: body.model, harness: body.harness });
|
|
72713
73120
|
return { status: 200, json: { ok: true } };
|
|
72714
73121
|
},
|
|
@@ -72724,6 +73131,8 @@ var EngineRuntime = class {
|
|
|
72724
73131
|
// an MCP server reports the tool calls of its agents (the change feed's tool_call events)
|
|
72725
73132
|
"/tool-calls/record/": async (body) => {
|
|
72726
73133
|
if (typeof body.tool !== "string" || !body.tool) return { status: 400, json: { error: `"tool" is required`, ...REPORT_HINT } };
|
|
73134
|
+
const taken = typeof body.session === "string" && body.session.trim() ? this.sessionTaken(body.session.trim()) : null;
|
|
73135
|
+
if (taken) return taken;
|
|
72727
73136
|
const actor = currentActor();
|
|
72728
73137
|
await this.recordToolCall({
|
|
72729
73138
|
tool: body.tool,
|
|
@@ -72772,6 +73181,14 @@ var EngineRuntime = class {
|
|
|
72772
73181
|
} : {}
|
|
72773
73182
|
};
|
|
72774
73183
|
}
|
|
73184
|
+
/**
|
|
73185
|
+
* The 403 when the session belongs to another named token; else null, and the session is
|
|
73186
|
+
* claimed for the caller when nobody reported it before (the first reporter keeps it).
|
|
73187
|
+
*/
|
|
73188
|
+
sessionTaken(session) {
|
|
73189
|
+
if (this.identities.claim(session, tokenNameOf(currentActor()) ?? "owner") === null) return null;
|
|
73190
|
+
return { status: 403, json: { error: `session "${session}" belongs to another token`, ...SESSION_TAKEN_HINT } };
|
|
73191
|
+
}
|
|
72775
73192
|
/**
|
|
72776
73193
|
* An agent's tool call, as a change-feed event (secrets removed, long values cut); settled once
|
|
72777
73194
|
* it is recorded. The feed is kept in the store, which the store backup commits: no secret.
|
|
@@ -77216,7 +77633,7 @@ function setUp(o, vault, home, explicit, self) {
|
|
|
77216
77633
|
// src/node/service.ts
|
|
77217
77634
|
init_local();
|
|
77218
77635
|
init_hints();
|
|
77219
|
-
var VERSION = true ? "3.4.
|
|
77636
|
+
var VERSION = true ? "3.4.2" : "dev";
|
|
77220
77637
|
function parsePort(raw, from = "--port") {
|
|
77221
77638
|
const n = /^\s*\d+\s*$/.test(raw) ? Number(raw) : NaN;
|
|
77222
77639
|
if (n === 0 || Number.isInteger(n) && n >= 1024 && n <= 65535) return n;
|
|
@@ -78281,7 +78698,7 @@ Docs: https://datanotes.dev`;
|
|
|
78281
78698
|
|
|
78282
78699
|
// src/node/cli.ts
|
|
78283
78700
|
init_message();
|
|
78284
|
-
var VERSION2 = true ? "3.4.
|
|
78701
|
+
var VERSION2 = true ? "3.4.2" : "dev";
|
|
78285
78702
|
var consoleError = console.error;
|
|
78286
78703
|
async function main5(argv = process.argv.slice(2)) {
|
|
78287
78704
|
const [command, ...rest] = argv;
|