datanotes 3.4.0 → 3.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/datanotes.cjs +1641 -1470
- 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/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 +15 -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,418 +29757,1131 @@ 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
|
-
|
|
30159
|
-
|
|
30160
|
-
const
|
|
30161
|
-
|
|
30162
|
-
|
|
30163
|
-
|
|
30164
|
-
|
|
30165
|
-
|
|
30166
|
-
|
|
30167
|
-
|
|
30168
|
-
|
|
30169
|
-
|
|
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);
|
|
29925
|
+
}
|
|
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
|
+
}
|
|
30034
|
+
}
|
|
30035
|
+
}
|
|
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 {
|
|
30044
|
+
}
|
|
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
|
+
};
|
|
30076
|
+
}
|
|
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
|
+
}
|
|
30084
|
+
}
|
|
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 });
|
|
30106
|
+
}
|
|
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) });
|
|
30122
|
+
}
|
|
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 });
|
|
30131
|
+
}
|
|
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) });
|
|
30138
|
+
}
|
|
30139
|
+
for (const [path23, t] of texts) if (t.before !== t.after) cs.texts.push({ path: path23, ...t });
|
|
30140
|
+
return cs;
|
|
30141
|
+
}
|
|
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: []
|
|
30155
|
+
};
|
|
30156
|
+
}
|
|
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
|
+
}
|
|
30181
|
+
}
|
|
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
|
+
}
|
|
30189
|
+
}
|
|
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
|
+
}
|
|
30200
|
+
}
|
|
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;
|
|
30309
|
+
try {
|
|
30310
|
+
for (const s of steps) {
|
|
30311
|
+
await s.run();
|
|
30312
|
+
done++;
|
|
30313
|
+
}
|
|
30314
|
+
} catch (e) {
|
|
30315
|
+
failed2.push({ path: "undo", error: messageOf(e) });
|
|
30316
|
+
}
|
|
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);
|
|
30339
|
+
}
|
|
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
|
+
};
|
|
30386
|
+
}
|
|
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();
|
|
30394
|
+
}
|
|
30395
|
+
if (!scope && !storage3.getStore()) return fn();
|
|
30396
|
+
return storage3.run({ scope }, fn);
|
|
30397
|
+
}
|
|
30398
|
+
function currentWriteScope() {
|
|
30399
|
+
return storage3?.getStore()?.scope ?? null;
|
|
30400
|
+
}
|
|
30401
|
+
function tableOfFrontmatter(model, settings, fm) {
|
|
30402
|
+
return rowTableFull(model, fmGet(fm, settings.tableKey));
|
|
30403
|
+
}
|
|
30404
|
+
function changeSetTables(app, settings, model, cs) {
|
|
30405
|
+
const out2 = /* @__PURE__ */ new Set();
|
|
30406
|
+
const add = (full) => {
|
|
30407
|
+
if (full !== null) out2.add(full);
|
|
30408
|
+
};
|
|
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));
|
|
30414
|
+
}
|
|
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;
|
|
30419
|
+
}
|
|
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;
|
|
30445
|
+
}
|
|
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
|
+
};
|
|
30470
|
+
}
|
|
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);
|
|
30480
|
+
}
|
|
30481
|
+
}
|
|
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
|
+
}
|
|
30494
|
+
}
|
|
30495
|
+
return void 0;
|
|
30496
|
+
}
|
|
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) {
|
|
30501
|
+
const out2 = /* @__PURE__ */ new Map();
|
|
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 {
|
|
30507
|
+
}
|
|
30508
|
+
}
|
|
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;
|
|
30528
|
+
try {
|
|
30529
|
+
cached2 = idx ? void 0 : app.metadataCache.getFileCache(f)?.frontmatter;
|
|
30530
|
+
} catch {
|
|
30531
|
+
cached2 = void 0;
|
|
30532
|
+
}
|
|
30533
|
+
if (cached2) continue;
|
|
30534
|
+
let text5;
|
|
30535
|
+
try {
|
|
30536
|
+
text5 = (await readHashed(app, f)).content;
|
|
30537
|
+
} catch {
|
|
30538
|
+
continue;
|
|
30539
|
+
}
|
|
30540
|
+
const u = unreadableRowOf(model, settings, f.path, text5, excluded);
|
|
30541
|
+
if (u && (table === void 0 || u.table === table)) out2.push(u);
|
|
30542
|
+
}
|
|
30543
|
+
return out2.sort((a, b) => a.path.localeCompare(b.path));
|
|
30544
|
+
}
|
|
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();
|
|
30558
|
+
}
|
|
30559
|
+
});
|
|
30560
|
+
|
|
30561
|
+
// src/core/log-files.ts
|
|
30562
|
+
function holdsVersions(path23) {
|
|
30563
|
+
return COMMIT_RE.test(path23) || SEGMENT_RE.test(path23);
|
|
30564
|
+
}
|
|
30565
|
+
function tocsOf(app) {
|
|
30566
|
+
let m = TOCS.get(app);
|
|
30567
|
+
if (!m) TOCS.set(app, m = /* @__PURE__ */ new Map());
|
|
30568
|
+
return m;
|
|
30569
|
+
}
|
|
30570
|
+
function latestOf(toc) {
|
|
30571
|
+
return toc.versions.length > 0 ? toc.versions[toc.versions.length - 1] : -1;
|
|
30572
|
+
}
|
|
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;
|
|
30602
|
+
}
|
|
30603
|
+
return { commits, validEnd: pos };
|
|
30604
|
+
}
|
|
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}`;
|
|
30609
|
+
}
|
|
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;
|
|
30619
|
+
}
|
|
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);
|
|
30628
|
+
}
|
|
30629
|
+
return out2;
|
|
30630
|
+
}
|
|
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();
|
|
30665
|
+
}
|
|
30666
|
+
}
|
|
30667
|
+
return toc;
|
|
30668
|
+
}
|
|
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;
|
|
30677
|
+
}
|
|
30678
|
+
}
|
|
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;
|
|
30683
|
+
}
|
|
30684
|
+
return void 0;
|
|
30685
|
+
}
|
|
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;
|
|
30692
|
+
}
|
|
30693
|
+
const toc = await buildToc(app, dir);
|
|
30694
|
+
tocs.set(dir, toc);
|
|
30695
|
+
return toc;
|
|
30696
|
+
}
|
|
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));
|
|
30704
|
+
}
|
|
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;
|
|
30709
|
+
}
|
|
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
|
+
);
|
|
30724
|
+
}
|
|
30725
|
+
async function wholeVersions(app, seg) {
|
|
30726
|
+
return new Set((await segmentText(app, seg)).index.keys());
|
|
30727
|
+
}
|
|
30728
|
+
function segmentsWith(toc, v) {
|
|
30729
|
+
return toc.segments.filter((s) => v >= s.first && v <= s.last).reverse();
|
|
30730
|
+
}
|
|
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);
|
|
30738
|
+
}
|
|
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;
|
|
30745
|
+
}
|
|
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}`);
|
|
30756
|
+
}
|
|
30757
|
+
const repaired = [];
|
|
30758
|
+
if (toc.torn) {
|
|
30759
|
+
repaired.push(`${toc.torn} set aside as ${await setAside(app, toc.torn)}`);
|
|
30760
|
+
toc.torn = null;
|
|
30761
|
+
}
|
|
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;
|
|
30772
|
+
}
|
|
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;
|
|
30783
|
+
}
|
|
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 } : {} });
|
|
30805
|
+
}
|
|
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) {
|
|
30815
|
+
const out2 = [];
|
|
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;
|
|
30832
|
+
}
|
|
30833
|
+
await flush2();
|
|
30834
|
+
return out2;
|
|
30835
|
+
}
|
|
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]);
|
|
30841
|
+
}
|
|
30842
|
+
async function gzip(text5) {
|
|
30843
|
+
const stream = new Blob([text5]).stream().pipeThrough(new CompressionStream("gzip"));
|
|
30844
|
+
return new Response(stream).arrayBuffer();
|
|
30845
|
+
}
|
|
30846
|
+
async function gunzip(data) {
|
|
30847
|
+
const stream = new Blob([data]).stream().pipeThrough(new DecompressionStream("gzip"));
|
|
30848
|
+
return new Response(stream).text();
|
|
30849
|
+
}
|
|
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;
|
|
30859
|
+
}
|
|
30860
|
+
cur.push(l);
|
|
30861
|
+
chars += l.length + 1;
|
|
30862
|
+
}
|
|
30863
|
+
if (cur.length > 0 || out2.length === 0) out2.push(cur);
|
|
30864
|
+
return out2;
|
|
30865
|
+
}
|
|
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;
|
|
30170
30885
|
}
|
|
30171
30886
|
const entries = rows.map((r, i) => JSON.stringify([r.path, r.hash, r.content.length, partOf[i]]));
|
|
30172
30887
|
const pieces = chunked(entries);
|
|
@@ -30631,1128 +31346,499 @@ async function stateFromLogged(app, settings, state) {
|
|
|
30631
31346
|
movesApplied: state.movesApplied
|
|
30632
31347
|
};
|
|
30633
31348
|
}
|
|
30634
|
-
function infoOf(text5) {
|
|
30635
|
-
return JSON.parse(text5.slice(0, text5.indexOf("\n"))).commitInfo;
|
|
30636
|
-
}
|
|
30637
|
-
async function commitInfos(app, settings, full) {
|
|
30638
|
-
const toc = await tocOf(app, settings, full);
|
|
30639
|
-
return (await versionTexts(app, toc, toc.versions)).map(infoOf);
|
|
30640
|
-
}
|
|
30641
|
-
async function newestCommitInfos(app, settings, full, limit, from) {
|
|
30642
|
-
const toc = await tocOf(app, settings, full);
|
|
30643
|
-
const out2 = [];
|
|
30644
|
-
for (const v of [...toc.versions].reverse()) {
|
|
30645
|
-
if (out2.length >= limit) break;
|
|
30646
|
-
const info = infoOf(await versionText(app, toc, v));
|
|
30647
|
-
if (from !== void 0 && Date.parse(info.timestamp) < from) break;
|
|
30648
|
-
out2.push(info);
|
|
30649
|
-
}
|
|
30650
|
-
return out2;
|
|
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;
|
|
30657
|
-
}
|
|
30658
|
-
function catalogEntryOptions(full, entry, settings) {
|
|
30659
|
-
const [c, s, t] = full.split(".");
|
|
30660
|
-
const ref2 = refOf(c, s, t);
|
|
30661
|
-
const options = parseTableOptions(entryToOrdered(ref2, entry, settings).find(([k]) => k === TABLE_KEY)?.[1]);
|
|
30662
|
-
const layout = catalogLayout(normalizeDatabaseFolder(settings.databaseFolder), ref2, options);
|
|
30663
|
-
return [...options.exclude ?? [], layout.archive, transactionLogRoot(settings)];
|
|
30664
|
-
}
|
|
30665
|
-
async function readCurrentStates(app, settings, model, touched, only) {
|
|
30666
|
-
const out2 = /* @__PURE__ */ new Map();
|
|
30667
|
-
const excludes = /* @__PURE__ */ new Map();
|
|
30668
|
-
for (const { ref: ref2, entry } of catalogTables(model)) {
|
|
30669
|
-
if (only && !only.has(ref2.full)) continue;
|
|
30670
|
-
out2.set(ref2.full, { entry, files: /* @__PURE__ */ new Map() });
|
|
30671
|
-
excludes.set(ref2.full, catalogEntryOptions(ref2.full, entry, settings));
|
|
30672
|
-
}
|
|
30673
|
-
if (out2.size === 0) return out2;
|
|
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);
|
|
30679
|
-
}
|
|
30680
|
-
const reader = rowStateReader(app, settings, model, out2, excludes);
|
|
30681
|
-
for (const path23 of [...candidates.keys()].sort()) {
|
|
30682
|
-
const r = await reader(path23, candidates.get(path23) ?? null);
|
|
30683
|
-
if (r) out2.get(r.table).files.set(path23, r.state);
|
|
30684
|
-
}
|
|
30685
|
-
return out2;
|
|
30686
|
-
}
|
|
30687
|
-
function rowStateReader(app, settings, model, tables, excludes) {
|
|
30688
|
-
const resolved = /* @__PURE__ */ new Map();
|
|
30689
|
-
return async (path23, given) => {
|
|
30690
|
-
const file = given ?? app.vault.getAbstractFileByPath(path23);
|
|
30691
|
-
if (file == null || !("extension" in file)) return null;
|
|
30692
|
-
let raw;
|
|
30693
|
-
try {
|
|
30694
|
-
raw = await readFrontmatterValue(app, file, NO_KEYS, (ordered) => fmGet(ordered, settings.tableKey));
|
|
30695
|
-
} catch {
|
|
30696
|
-
return null;
|
|
30697
|
-
}
|
|
30698
|
-
const rawKey = typeof raw === "string" ? raw : JSON.stringify(raw ?? null);
|
|
30699
|
-
if (!resolved.has(rawKey)) resolved.set(rawKey, rowTableFull(model, raw));
|
|
30700
|
-
const full = resolved.get(rawKey);
|
|
30701
|
-
if (full == null || !tables.has(full)) return null;
|
|
30702
|
-
if (excludes.get(full).some((dir) => path23.startsWith(`${dir}/`))) return null;
|
|
30703
|
-
return { table: full, state: await readHashed(app, file) };
|
|
30704
|
-
};
|
|
30705
|
-
}
|
|
30706
|
-
function forgetCurrentStates(app) {
|
|
30707
|
-
LIVE.delete(app);
|
|
30708
|
-
}
|
|
30709
|
-
async function currentStates(app, settings, model, touched, only) {
|
|
30710
|
-
const idx = vaultIndex(app, settings);
|
|
30711
|
-
if (!idx || idx.hasFailures) return readCurrentStates(app, settings, model, touched, only);
|
|
30712
|
-
let live = LIVE.get(app);
|
|
30713
|
-
const kept2 = JSON.stringify([...only].sort());
|
|
30714
|
-
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;
|
|
30715
|
-
const cursor = idx.cursor;
|
|
30716
|
-
if (!live || !changed2) {
|
|
30717
|
-
const tables = await readCurrentStates(app, settings, model, touched, only);
|
|
30718
|
-
const excludes = /* @__PURE__ */ new Map();
|
|
30719
|
-
const tableOf = /* @__PURE__ */ new Map();
|
|
30720
|
-
for (const [full, t] of tables) {
|
|
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);
|
|
30743
|
-
}
|
|
30744
|
-
live.cursor = cursor;
|
|
30745
|
-
}
|
|
30746
|
-
return live.tables;
|
|
30747
|
-
}
|
|
30748
|
-
async function verifyCurrentStates(app, settings, repair = false) {
|
|
30749
|
-
const ms = await loadMetastore(app, settings);
|
|
30750
|
-
const live = LIVE.get(app);
|
|
30751
|
-
if (!ms || !live) return [];
|
|
30752
|
-
const only = new Set(JSON.parse(live.only));
|
|
30753
|
-
const kept2 = await currentStates(app, settings, ms.model, [], only);
|
|
30754
|
-
const now = await readCurrentStates(app, settings, ms.model, [], only);
|
|
30755
|
-
const out2 = [];
|
|
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}`);
|
|
30760
|
-
}
|
|
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
|
-
}
|
|
30792
|
-
async function writeDueCheckpoints(app, stopped = () => false) {
|
|
30793
|
-
let n = 0;
|
|
30794
|
-
for (; ; ) {
|
|
30795
|
-
const due = DUE.get(app);
|
|
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++;
|
|
30801
|
-
}
|
|
30802
|
-
return n;
|
|
30803
|
-
}
|
|
30804
|
-
async function wholeCheckpoint(app, dir, v) {
|
|
30805
|
-
return parseCheckpoint(await storeRead(app, checkpointPath(dir, v)) ?? "")?.version === v;
|
|
30806
|
-
}
|
|
30807
|
-
async function writeCheckpoint(app, dir, state, format) {
|
|
30808
|
-
const files = [...state.files.entries()].sort(([a], [b]) => a.localeCompare(b));
|
|
30809
|
-
if (format === "files") {
|
|
30810
|
-
const cp = {
|
|
30811
|
-
version: state.version,
|
|
30812
|
-
timestamp: state.timestamp,
|
|
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
|
-
}
|
|
30823
|
-
}
|
|
30824
|
-
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 })));
|
|
30825
|
-
}
|
|
30826
|
-
function forgetLoggedStates(app) {
|
|
30827
|
-
LAST.delete(app);
|
|
30828
|
-
}
|
|
30829
|
-
async function knownState(app, settings, full) {
|
|
30830
|
-
return LAST.get(app)?.get(full) ?? readLastState(app, settings, full);
|
|
30831
|
-
}
|
|
30832
|
-
async function readLastState(app, settings, full) {
|
|
30833
|
-
let byTable = LAST.get(app);
|
|
30834
|
-
if (!byTable) LAST.set(app, byTable = /* @__PURE__ */ new Map());
|
|
30835
|
-
const state = await replayLogged(app, settings, full);
|
|
30836
|
-
byTable.set(full, state);
|
|
30837
|
-
return state;
|
|
30838
|
-
}
|
|
30839
|
-
async function stillLatest(app, settings, state) {
|
|
30840
|
-
return latestOf(await tocOf(app, settings, state.full)) === state.version;
|
|
30841
|
-
}
|
|
30842
|
-
async function latestVersion(app, settings, full) {
|
|
30843
|
-
const latest = latestOf(await tocOf(app, settings, full));
|
|
30844
|
-
return latest >= 0 ? latest : null;
|
|
30845
|
-
}
|
|
30846
|
-
async function warmLoggedStates(app, settings, stopped = () => false) {
|
|
30847
|
-
let n = 0;
|
|
30848
|
-
for (const full of await loggedTables(app, settings)) {
|
|
30849
|
-
if (stopped()) break;
|
|
30850
|
-
await withLogLock(app, async () => {
|
|
30851
|
-
if (LAST.get(app)?.has(full)) return;
|
|
30852
|
-
await readLastState(app, settings, full);
|
|
30853
|
-
n++;
|
|
30854
|
-
});
|
|
30855
|
-
}
|
|
30856
|
-
return n;
|
|
30857
|
-
}
|
|
30858
|
-
async function changesSince(app, prev, cur) {
|
|
30859
|
-
const entry = cur?.entry ?? null;
|
|
30860
|
-
let files = cur?.files ?? /* @__PURE__ */ new Map();
|
|
30861
|
-
const remove3 = [];
|
|
30862
|
-
const add = [];
|
|
30863
|
-
if (cur) {
|
|
30864
|
-
for (const [path23, f] of prev.files) {
|
|
30865
|
-
if (files.has(path23) || !await unreadableNow(app, path23)) continue;
|
|
30866
|
-
if (files === cur.files) files = new Map(files);
|
|
30867
|
-
files.set(path23, f);
|
|
30868
|
-
}
|
|
30869
|
-
}
|
|
30870
|
-
for (const [path23, f] of prev.files) {
|
|
30871
|
-
const now = files.get(path23);
|
|
30872
|
-
if (!now || now.hash !== f.hash) remove3.push({ path: path23, hash: f.hash });
|
|
30873
|
-
}
|
|
30874
|
-
for (const [path23, f] of files) {
|
|
30875
|
-
const was = prev.files.get(path23);
|
|
30876
|
-
if (!was || was.hash !== f.hash) add.push({ path: path23, hash: f.hash, size: f.content.length, content: f.content });
|
|
30877
|
-
}
|
|
30878
|
-
add.sort((a, b) => a.path < b.path ? -1 : 1);
|
|
30879
|
-
const metadataChanged = prev.version < 0 || entryHash(prev.entry) !== entryHash(entry);
|
|
30880
|
-
return { entry, add, remove: remove3, metadataChanged };
|
|
30881
|
-
}
|
|
30882
|
-
function historyEnabled(model, entry) {
|
|
30883
|
-
const own2 = entry.options?.history;
|
|
30884
|
-
return typeof own2 === "boolean" ? own2 : model.config.history === true;
|
|
30885
|
-
}
|
|
30886
|
-
async function syncTransactionLogs(app, settings, options) {
|
|
30887
|
-
const ms = await loadMetastore(app, settings);
|
|
30888
|
-
if (!ms) return [];
|
|
30889
|
-
const tables = catalogTables(ms.model);
|
|
30890
|
-
const enabled = new Set(tables.filter(({ entry }) => historyEnabled(ms.model, entry)).map(({ ref: ref2 }) => ref2.full));
|
|
30891
|
-
const defined = new Set(tables.map(({ ref: ref2 }) => ref2.full));
|
|
30892
|
-
const orphans = await loggedTables(app, settings, (full) => !defined.has(full));
|
|
30893
|
-
if (enabled.size === 0 && orphans.length === 0) return [];
|
|
30894
|
-
const current2 = await currentStates(app, settings, ms.model, options.touched ?? [], enabled);
|
|
30895
|
-
const names2 = /* @__PURE__ */ new Set([...enabled, ...orphans]);
|
|
30896
|
-
const out2 = [];
|
|
30897
|
-
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
30898
|
-
const format = formatOf(settings);
|
|
30899
|
-
const planned = [];
|
|
30900
|
-
for (const full of [...names2].sort()) {
|
|
30901
|
-
if (options.tables && !options.tables.includes(full)) continue;
|
|
30902
|
-
let prev = await knownState(app, settings, full);
|
|
30903
|
-
const cur = current2.get(full);
|
|
30904
|
-
if (prev.version < 0 && !cur) continue;
|
|
30905
|
-
const due = (c) => c.metadataChanged || c.add.length > 0 || c.remove.length > 0 || options.always === true && cur !== void 0;
|
|
30906
|
-
let changes = await changesSince(app, prev, cur);
|
|
30907
|
-
if (!due(changes)) continue;
|
|
30908
|
-
if (!await stillLatest(app, settings, prev)) {
|
|
30909
|
-
prev = await readLastState(app, settings, full);
|
|
30910
|
-
changes = await changesSince(app, prev, cur);
|
|
30911
|
-
if (!due(changes)) continue;
|
|
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
|
-
}
|
|
30972
|
-
}
|
|
30973
|
-
return out2;
|
|
30974
|
-
}
|
|
30975
|
-
function withLogLock(app, fn) {
|
|
30976
|
-
let activity = ACTIVITY.get(app);
|
|
30977
|
-
if (!activity) ACTIVITY.set(app, activity = { pending: 0, lastEnd: 0 });
|
|
30978
|
-
const a = activity;
|
|
30979
|
-
a.pending++;
|
|
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;
|
|
31003
|
-
}
|
|
31004
|
-
function logLockActivity(app) {
|
|
31005
|
-
const a = ACTIVITY.get(app);
|
|
31006
|
-
return { busy: (a?.pending ?? 0) > 0, lastEnd: a?.lastEnd ?? 0 };
|
|
31007
|
-
}
|
|
31008
|
-
async function checkpointFiles(app, dir, toc) {
|
|
31009
|
-
const out2 = /* @__PURE__ */ new Map();
|
|
31010
|
-
for (const cp of toc.checkpoints) {
|
|
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;
|
|
31015
|
-
}
|
|
31016
|
-
async function vacuum(app, settings, full, retainFrom, dryRun) {
|
|
31017
|
-
const dir = transactionLogDir(settings, full);
|
|
31018
|
-
const toc = await logToc(app, dir);
|
|
31019
|
-
const versions = toc.versions;
|
|
31020
|
-
if (versions.length === 0) throw new HistoryError(`${full} has no history`);
|
|
31021
|
-
const latest = versions[versions.length - 1];
|
|
31022
|
-
const keep = retainFrom ?? latest;
|
|
31023
|
-
if (!versions.includes(keep)) throw new HistoryError(`${full} has no version ${keep} (available: ${versions[0]}..${latest})`);
|
|
31024
|
-
const doomed = [...toc.files].filter(([v]) => v < keep).map(([, p]) => p);
|
|
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);
|
|
31035
|
-
}
|
|
31036
|
-
await storeFlush(app);
|
|
31037
|
-
for (const p of doomed) await storeRemove(app, p);
|
|
31038
|
-
forgetLoggedStates(app);
|
|
31039
|
-
}
|
|
31040
|
-
return { table: full, retained_from: keep, deleted: doomed, dry_run: dryRun };
|
|
31041
|
-
}
|
|
31042
|
-
async function convertLog(app, settings, full, to) {
|
|
31043
|
-
const dir = transactionLogDir(settings, full);
|
|
31044
|
-
const toc = await logToc(app, dir);
|
|
31045
|
-
const result = { table: full, versions: toc.versions.length, written: 0, removed: 0 };
|
|
31046
|
-
if (to === "segments") {
|
|
31047
|
-
for (const s of toc.segments) {
|
|
31048
|
-
if (!s.sealed || (await wholeVersions(app, s)).size === s.last - s.first + 1) {
|
|
31049
|
-
storeSyncNext(app, s.path);
|
|
31050
|
-
continue;
|
|
31051
|
-
}
|
|
31052
|
-
const range = Array.from({ length: s.last - s.first + 1 }, (_, i) => s.first + i);
|
|
31053
|
-
const texts2 = await versionTexts(app, toc, range);
|
|
31054
|
-
await storeWrite(app, s.path, range.map((v, i) => framed(v, texts2[i])).join(""));
|
|
31055
|
-
result.written++;
|
|
31056
|
-
}
|
|
31057
|
-
const loose = toc.versions.filter((v) => toc.files.has(v) && !toc.segments.some((s) => v >= s.first && v <= s.last));
|
|
31058
|
-
const texts = await versionTexts(app, toc, loose);
|
|
31059
|
-
const written2 = await writeSealedSegments(app, dir, loose.map((version2, i) => ({ version: version2, text: texts[i] })));
|
|
31060
|
-
result.written += written2.length;
|
|
31061
|
-
await storeFlush(app);
|
|
31062
|
-
for (const v of toc.files.keys()) {
|
|
31063
|
-
await storeRemove(app, toc.files.get(v));
|
|
31064
|
-
result.removed++;
|
|
31065
|
-
}
|
|
31066
|
-
} else {
|
|
31067
|
-
for (const seg of toc.segments) {
|
|
31068
|
-
for (let v = seg.first; v <= seg.last; v++) {
|
|
31069
|
-
const path23 = `${dir}/${pad2(v)}.json`;
|
|
31070
|
-
const file = toc.files.get(v);
|
|
31071
|
-
if (file === void 0) {
|
|
31072
|
-
await storeWrite(app, path23, await versionText(app, toc, v));
|
|
31073
|
-
result.written++;
|
|
31074
|
-
} else storeSyncNext(app, file);
|
|
31075
|
-
}
|
|
31076
|
-
}
|
|
31077
|
-
const oldest = toc.versions[0];
|
|
31078
|
-
if (oldest !== void 0 && oldest > 0) {
|
|
31079
|
-
if (await wholeCheckpoint(app, dir, oldest)) storeSyncNext(app, checkpointPath(dir, oldest));
|
|
31080
|
-
else {
|
|
31081
|
-
await writeCheckpoint(app, dir, await replayState(app, settings, full, { version: oldest }), "files");
|
|
31082
|
-
result.written++;
|
|
31083
|
-
}
|
|
31084
|
-
}
|
|
31085
|
-
await storeFlush(app);
|
|
31086
|
-
for (const seg of toc.segments) {
|
|
31087
|
-
await storeRemove(app, seg.path);
|
|
31088
|
-
result.removed++;
|
|
31089
|
-
}
|
|
31090
|
-
}
|
|
31091
|
-
forgetLoggedStates(app);
|
|
31092
|
-
return result;
|
|
31093
|
-
}
|
|
31094
|
-
var TRANSACTION_LOG_FOLDER, CHECKPOINT_POLICY, LEGACY_CHECKPOINT, EXTERNAL_EDIT, AbsentTableError, ENTRY_HASHES, NO_KEYS, LIVE, DUE, CHECKPOINT_RUNNERS, LAST, LOCKS, ACTIVITY;
|
|
31095
|
-
var init_transaction_log = __esm({
|
|
31096
|
-
"src/core/transaction-log.ts"() {
|
|
31097
|
-
"use strict";
|
|
31098
|
-
init_file_cache();
|
|
31099
|
-
init_frontmatter();
|
|
31100
|
-
init_metastore();
|
|
31101
|
-
init_actor();
|
|
31102
|
-
init_store();
|
|
31103
|
-
init_table_loader();
|
|
31104
|
-
init_types_table();
|
|
31105
|
-
init_database_move();
|
|
31106
|
-
init_db_paths();
|
|
31107
|
-
init_unreadable();
|
|
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 {
|
|
31118
|
-
};
|
|
31119
|
-
ENTRY_HASHES = /* @__PURE__ */ new WeakMap();
|
|
31120
|
-
NO_KEYS = /* @__PURE__ */ new Set();
|
|
31121
|
-
LIVE = /* @__PURE__ */ new WeakMap();
|
|
31122
|
-
DUE = /* @__PURE__ */ new WeakMap();
|
|
31123
|
-
CHECKPOINT_RUNNERS = /* @__PURE__ */ new WeakMap();
|
|
31124
|
-
LAST = /* @__PURE__ */ new WeakMap();
|
|
31125
|
-
LOCKS = /* @__PURE__ */ new WeakMap();
|
|
31126
|
-
ACTIVITY = /* @__PURE__ */ new WeakMap();
|
|
31127
|
-
}
|
|
31128
|
-
});
|
|
31129
|
-
|
|
31130
|
-
// src/core/changeset.ts
|
|
31131
|
-
function createdHash(text5) {
|
|
31132
|
-
try {
|
|
31133
|
-
const { ordered, body } = readOrderedFromMarkdown(text5);
|
|
31134
|
-
return contentHash(JSON.stringify([ordered, body]));
|
|
31135
|
-
} catch {
|
|
31136
|
-
return contentHash(text5);
|
|
31137
|
-
}
|
|
31138
|
-
}
|
|
31139
|
-
function bodyChanges(e) {
|
|
31140
|
-
return e.bodyAfter !== void 0 && e.bodyAfter !== e.bodyBefore;
|
|
31349
|
+
function infoOf(text5) {
|
|
31350
|
+
return JSON.parse(text5.slice(0, text5.indexOf("\n"))).commitInfo;
|
|
31141
31351
|
}
|
|
31142
|
-
function
|
|
31143
|
-
const
|
|
31144
|
-
return
|
|
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);
|
|
31145
31355
|
}
|
|
31146
|
-
function
|
|
31147
|
-
const
|
|
31148
|
-
|
|
31149
|
-
|
|
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);
|
|
31364
|
+
}
|
|
31365
|
+
return out2;
|
|
31150
31366
|
}
|
|
31151
|
-
async function
|
|
31152
|
-
await app.
|
|
31153
|
-
|
|
31154
|
-
|
|
31155
|
-
|
|
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;
|
|
31156
31372
|
}
|
|
31157
|
-
function
|
|
31158
|
-
|
|
31159
|
-
|
|
31160
|
-
|
|
31161
|
-
|
|
31162
|
-
|
|
31163
|
-
creates: [],
|
|
31164
|
-
moves: [],
|
|
31165
|
-
deletes: [],
|
|
31166
|
-
texts: []
|
|
31167
|
-
};
|
|
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)];
|
|
31168
31379
|
}
|
|
31169
|
-
function
|
|
31170
|
-
|
|
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;
|
|
31171
31401
|
}
|
|
31172
|
-
function
|
|
31173
|
-
const
|
|
31174
|
-
|
|
31175
|
-
|
|
31176
|
-
|
|
31177
|
-
|
|
31178
|
-
|
|
31179
|
-
|
|
31180
|
-
|
|
31181
|
-
|
|
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;
|
|
31412
|
+
}
|
|
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) };
|
|
31182
31419
|
};
|
|
31183
|
-
|
|
31184
|
-
|
|
31185
|
-
|
|
31186
|
-
|
|
31187
|
-
|
|
31188
|
-
|
|
31189
|
-
|
|
31190
|
-
|
|
31191
|
-
|
|
31192
|
-
|
|
31193
|
-
|
|
31194
|
-
|
|
31195
|
-
|
|
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);
|
|
31438
|
+
}
|
|
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);
|
|
31196
31458
|
}
|
|
31459
|
+
live.cursor = cursor;
|
|
31197
31460
|
}
|
|
31198
|
-
return
|
|
31461
|
+
return live.tables;
|
|
31199
31462
|
}
|
|
31200
|
-
function
|
|
31201
|
-
const
|
|
31202
|
-
const
|
|
31203
|
-
|
|
31204
|
-
|
|
31205
|
-
);
|
|
31206
|
-
|
|
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}`);
|
|
31475
|
+
}
|
|
31476
|
+
if (out2.length > 0 && repair) forgetCurrentStates(app);
|
|
31477
|
+
return out2.sort();
|
|
31207
31478
|
}
|
|
31208
|
-
function
|
|
31209
|
-
|
|
31210
|
-
|
|
31211
|
-
|
|
31212
|
-
return
|
|
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));
|
|
31213
31484
|
}
|
|
31214
|
-
function
|
|
31215
|
-
const
|
|
31216
|
-
|
|
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) }));
|
|
31217
31489
|
}
|
|
31218
|
-
function
|
|
31219
|
-
const
|
|
31220
|
-
|
|
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;
|
|
31221
31499
|
}
|
|
31222
|
-
|
|
31223
|
-
|
|
31224
|
-
if (app.vault.getAbstractFileByPath(folder) != null) return;
|
|
31225
|
-
await ensureFolder(app, parentFolder(folder));
|
|
31226
|
-
await app.vault.createFolder(folder);
|
|
31500
|
+
function formatOf(settings) {
|
|
31501
|
+
return settings.transactionLogFormat === "files" ? "files" : "segments";
|
|
31227
31502
|
}
|
|
31228
|
-
|
|
31229
|
-
|
|
31503
|
+
function setCheckpointRunner(app, run2) {
|
|
31504
|
+
if (run2) CHECKPOINT_RUNNERS.set(app, run2);
|
|
31505
|
+
else CHECKPOINT_RUNNERS.delete(app);
|
|
31230
31506
|
}
|
|
31231
|
-
async function
|
|
31232
|
-
|
|
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++;
|
|
31516
|
+
}
|
|
31517
|
+
return n;
|
|
31233
31518
|
}
|
|
31234
|
-
async function
|
|
31235
|
-
return
|
|
31519
|
+
async function wholeCheckpoint(app, dir, v) {
|
|
31520
|
+
return parseCheckpoint(await storeRead(app, checkpointPath(dir, v)) ?? "")?.version === v;
|
|
31236
31521
|
}
|
|
31237
|
-
async function
|
|
31238
|
-
const
|
|
31239
|
-
|
|
31240
|
-
|
|
31241
|
-
|
|
31242
|
-
|
|
31243
|
-
|
|
31244
|
-
|
|
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 } : {}
|
|
31531
|
+
};
|
|
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;
|
|
31245
31537
|
}
|
|
31246
31538
|
}
|
|
31247
|
-
|
|
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 })));
|
|
31248
31540
|
}
|
|
31249
|
-
function
|
|
31250
|
-
|
|
31541
|
+
function forgetLoggedStates(app) {
|
|
31542
|
+
LAST.delete(app);
|
|
31251
31543
|
}
|
|
31252
|
-
async function
|
|
31253
|
-
|
|
31254
|
-
let next = 0;
|
|
31255
|
-
let stop = false;
|
|
31256
|
-
const worker = async () => {
|
|
31257
|
-
while (!stop && next < items3.length) {
|
|
31258
|
-
const i = next++;
|
|
31259
|
-
try {
|
|
31260
|
-
results[i] = { ok: true, written: await fn(items3[i]) };
|
|
31261
|
-
} catch (e) {
|
|
31262
|
-
results[i] = { ok: false, error: messageOf(e) };
|
|
31263
|
-
stop = true;
|
|
31264
|
-
}
|
|
31265
|
-
}
|
|
31266
|
-
};
|
|
31267
|
-
await Promise.all(Array.from({ length: Math.min(limit, items3.length) }, worker));
|
|
31268
|
-
const first2 = results.findIndex((r) => r !== void 0 && !r.ok);
|
|
31269
|
-
const kept2 = /* @__PURE__ */ new Set();
|
|
31270
|
-
if (first2 >= 0) {
|
|
31271
|
-
for (let i = results.length - 1; i > first2; i--) {
|
|
31272
|
-
const r = results[i];
|
|
31273
|
-
if (!r?.ok) continue;
|
|
31274
|
-
try {
|
|
31275
|
-
await r.written.undo();
|
|
31276
|
-
} catch {
|
|
31277
|
-
kept2.add(i);
|
|
31278
|
-
}
|
|
31279
|
-
}
|
|
31280
|
-
}
|
|
31281
|
-
const done = [];
|
|
31282
|
-
results.forEach((r, i) => {
|
|
31283
|
-
if (r?.ok && (first2 < 0 || i < first2 || kept2.has(i))) done.push(r.written.value);
|
|
31284
|
-
});
|
|
31285
|
-
const f = first2 >= 0 ? results[first2] : null;
|
|
31286
|
-
return { done, failed: f ? { path: pathOf(items3[first2]), error: f.error } : null };
|
|
31544
|
+
async function knownState(app, settings, full) {
|
|
31545
|
+
return LAST.get(app)?.get(full) ?? readLastState(app, settings, full);
|
|
31287
31546
|
}
|
|
31288
|
-
async function
|
|
31289
|
-
|
|
31290
|
-
|
|
31291
|
-
|
|
31292
|
-
|
|
31293
|
-
|
|
31294
|
-
await app.fileManager.renameFile(f, to);
|
|
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;
|
|
31295
31553
|
}
|
|
31296
|
-
async function
|
|
31297
|
-
|
|
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) });
|
|
31306
|
-
};
|
|
31307
|
-
run: {
|
|
31308
|
-
const folders = /* @__PURE__ */ new Map();
|
|
31309
|
-
const folderOnce = (folder) => {
|
|
31310
|
-
if (folder === "") return Promise.resolve();
|
|
31311
|
-
let p = folders.get(folder);
|
|
31312
|
-
if (!p) {
|
|
31313
|
-
p = folderOnce(parentFolder(folder)).then(async () => {
|
|
31314
|
-
if (app.vault.getAbstractFileByPath(folder) == null) await app.vault.createFolder(folder);
|
|
31315
|
-
});
|
|
31316
|
-
folders.set(folder, p);
|
|
31317
|
-
}
|
|
31318
|
-
return p;
|
|
31319
|
-
};
|
|
31320
|
-
const created = await inOrder(cs.creates, (c) => c.path, async (c) => {
|
|
31321
|
-
if (app.vault.getAbstractFileByPath(c.path) != null) throw new Error(`file exists: ${c.path}`);
|
|
31322
|
-
await folderOnce(parentFolder(c.path));
|
|
31323
|
-
const file = await app.vault.create(c.path, c.content);
|
|
31324
|
-
return { value: c.path, undo: () => app.vault.delete(file, true) };
|
|
31325
|
-
});
|
|
31326
|
-
creates.push(...created.done);
|
|
31327
|
-
if (created.failed) {
|
|
31328
|
-
failed2.push(created.failed);
|
|
31329
|
-
break run;
|
|
31330
|
-
}
|
|
31331
|
-
for (const t of cs.texts) {
|
|
31332
|
-
try {
|
|
31333
|
-
const existing = app.vault.getAbstractFileByPath(t.path);
|
|
31334
|
-
if (existing == null) {
|
|
31335
|
-
if (t.before !== "") throw new Error("file not found");
|
|
31336
|
-
if (t.path === metastorePath(settings)) {
|
|
31337
|
-
t.after = withNewDatabaseConfig(t.after);
|
|
31338
|
-
if (settings.databaseId) t.after = withMetastoreId(t.after, settings.databaseId);
|
|
31339
|
-
}
|
|
31340
|
-
await ensureFolder(app, parentFolder(t.path));
|
|
31341
|
-
await app.vault.create(t.path, t.after);
|
|
31342
|
-
} else {
|
|
31343
|
-
await app.vault.process(existing, (current2) => {
|
|
31344
|
-
if (current2 !== t.before) throw new Error("file changed since it was read");
|
|
31345
|
-
return t.after;
|
|
31346
|
-
});
|
|
31347
|
-
}
|
|
31348
|
-
references.push({ path: t.path, before_text: t.before, after_text: t.after });
|
|
31349
|
-
} catch (err) {
|
|
31350
|
-
fail2(t.path, err);
|
|
31351
|
-
break run;
|
|
31352
|
-
}
|
|
31353
|
-
}
|
|
31354
|
-
const edits = cs.edits.filter((e) => !editIsNoop(e));
|
|
31355
|
-
const distinct = new Set(edits.map((e) => e.path)).size === edits.length;
|
|
31356
|
-
const atOnce = distinct ? atOnceFor(edits.length) : 1;
|
|
31357
|
-
const edited = await inOrder(edits, (e) => e.path, async (e) => {
|
|
31358
|
-
const file = fileOrNull(app, e.path);
|
|
31359
|
-
if (!file) throw new Error("file not found");
|
|
31360
|
-
const was = atOnce > 1 ? (await readHashed(app, file)).content : "";
|
|
31361
|
-
const d = diffOrdered(e.before, e.after);
|
|
31362
|
-
const order = e.after.map(([k]) => k);
|
|
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));
|
|
31366
|
-
}
|
|
31367
|
-
if (bodyChanges(e)) await writeBody(app, file, e.bodyBefore, e.bodyAfter);
|
|
31368
|
-
const row = {
|
|
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;
|
|
31383
|
-
}
|
|
31384
|
-
for (const m of cs.moves) {
|
|
31385
|
-
try {
|
|
31386
|
-
await moveFile(app, m.from, m.to);
|
|
31387
|
-
moves.push(m);
|
|
31388
|
-
} catch (err) {
|
|
31389
|
-
fail2(m.from, err);
|
|
31390
|
-
break run;
|
|
31391
|
-
}
|
|
31392
|
-
}
|
|
31393
|
-
for (const p of cs.deletes) {
|
|
31394
|
-
const file = fileOrNull(app, p);
|
|
31395
|
-
try {
|
|
31396
|
-
if (!file) throw new Error("file not found");
|
|
31397
|
-
const content = await app.vault.read(file);
|
|
31398
|
-
await trash(app, file);
|
|
31399
|
-
deleted.push({ path: p, content });
|
|
31400
|
-
} catch (err) {
|
|
31401
|
-
fail2(p, err);
|
|
31402
|
-
break run;
|
|
31403
|
-
}
|
|
31404
|
-
}
|
|
31405
|
-
}
|
|
31406
|
-
const createdHashes = [];
|
|
31407
|
-
const movedTo = new Map(moves.map((m) => [m.from, m.to]));
|
|
31408
|
-
for (const p of creates) {
|
|
31409
|
-
const f = fileOrNull(app, movedTo.get(p) ?? p);
|
|
31410
|
-
if (!f) continue;
|
|
31411
|
-
try {
|
|
31412
|
-
createdHashes.push({ path: p, hash: createdHash(await app.vault.read(f)) });
|
|
31413
|
-
} catch {
|
|
31414
|
-
}
|
|
31415
|
-
}
|
|
31416
|
-
const journal = await writeJournal(
|
|
31417
|
-
app,
|
|
31418
|
-
settings,
|
|
31419
|
-
{
|
|
31420
|
-
op: cs.op,
|
|
31421
|
-
...cs.schema !== void 0 ? { schema: cs.schema } : {},
|
|
31422
|
-
...currentActor() ? { actor: currentActor() } : {},
|
|
31423
|
-
summary: cs.summary,
|
|
31424
|
-
executed_at: now.toISOString(),
|
|
31425
|
-
rows,
|
|
31426
|
-
creates,
|
|
31427
|
-
...createdHashes.length > 0 ? { created_hashes: createdHashes } : {},
|
|
31428
|
-
moves,
|
|
31429
|
-
references,
|
|
31430
|
-
deleted,
|
|
31431
|
-
failed: failed2
|
|
31432
|
-
},
|
|
31433
|
-
now
|
|
31434
|
-
);
|
|
31435
|
-
return {
|
|
31436
|
-
ok: failed2.length === 0,
|
|
31437
|
-
migrationId: journal.id,
|
|
31438
|
-
journalPath: journal.path,
|
|
31439
|
-
notesChanged: rows.length,
|
|
31440
|
-
filesCreated: creates.length,
|
|
31441
|
-
filesMoved: moves.length,
|
|
31442
|
-
filesDeleted: deleted.length,
|
|
31443
|
-
referencesUpdated,
|
|
31444
|
-
failed: failed2
|
|
31445
|
-
};
|
|
31554
|
+
async function stillLatest(app, settings, state) {
|
|
31555
|
+
return latestOf(await tocOf(app, settings, state.full)) === state.version;
|
|
31446
31556
|
}
|
|
31447
|
-
async function
|
|
31448
|
-
const
|
|
31449
|
-
|
|
31450
|
-
await fm.trashFile(file);
|
|
31451
|
-
} else {
|
|
31452
|
-
await app.vault.trash(file, true);
|
|
31453
|
-
}
|
|
31557
|
+
async function latestVersion(app, settings, full) {
|
|
31558
|
+
const latest = latestOf(await tocOf(app, settings, full));
|
|
31559
|
+
return latest >= 0 ? latest : null;
|
|
31454
31560
|
}
|
|
31455
|
-
async function
|
|
31456
|
-
|
|
31457
|
-
const
|
|
31458
|
-
|
|
31459
|
-
|
|
31460
|
-
|
|
31461
|
-
|
|
31462
|
-
|
|
31463
|
-
|
|
31464
|
-
}
|
|
31465
|
-
|
|
31466
|
-
|
|
31467
|
-
|
|
31468
|
-
|
|
31469
|
-
|
|
31470
|
-
|
|
31471
|
-
|
|
31472
|
-
|
|
31473
|
-
|
|
31474
|
-
|
|
31475
|
-
|
|
31476
|
-
|
|
31477
|
-
}
|
|
31478
|
-
for (const r of entry.rows) {
|
|
31479
|
-
const path23 = currentPath(r.path);
|
|
31480
|
-
const cur = await read3(path23);
|
|
31481
|
-
if (!cur) continue;
|
|
31482
|
-
state.set(path23, { ...cur, ordered: applyDiff(cur.ordered, Object.entries(r.before), r.before_absent, r.before_order) });
|
|
31483
|
-
}
|
|
31484
|
-
for (const m of [...entry.moves].reverse()) {
|
|
31485
|
-
state.set(m.from, await read3(m.to));
|
|
31486
|
-
state.set(m.to, null);
|
|
31487
|
-
origin.set(m.from, origin.get(m.to) ?? m.to);
|
|
31488
|
-
origin.delete(m.to);
|
|
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
|
+
});
|
|
31570
|
+
}
|
|
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);
|
|
31489
31583
|
}
|
|
31490
|
-
for (const c of entry.creates) state.set(c, null);
|
|
31491
|
-
for (const d of entry.deleted) state.set(d.path, { ordered: readOrderedFromMarkdown(d.content).ordered, body: bodyOf(d.content) });
|
|
31492
31584
|
}
|
|
31493
|
-
const
|
|
31494
|
-
|
|
31495
|
-
|
|
31496
|
-
const final = state.get(to);
|
|
31497
|
-
if (to === from || !final || !fileOrNull(app, from)) continue;
|
|
31498
|
-
moved.add(to).add(from);
|
|
31499
|
-
cs.moves.push({ from, to });
|
|
31500
|
-
cs.edits.push({ path: from, before: (await vaultNote(from)).ordered, after: final.ordered });
|
|
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 });
|
|
31501
31588
|
}
|
|
31502
|
-
for (const [path23,
|
|
31503
|
-
|
|
31504
|
-
|
|
31505
|
-
if (now && final) cs.edits.push({ path: path23, before: now.ordered, after: final.ordered });
|
|
31506
|
-
else if (now) cs.deletes.push(path23);
|
|
31507
|
-
else if (final) cs.creates.push({ path: path23, content: buildMarkdownWithFrontmatter(final.ordered, final.body) });
|
|
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 });
|
|
31508
31592
|
}
|
|
31509
|
-
|
|
31510
|
-
|
|
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 };
|
|
31511
31596
|
}
|
|
31512
|
-
|
|
31513
|
-
const
|
|
31514
|
-
|
|
31515
|
-
|
|
31516
|
-
|
|
31517
|
-
const
|
|
31518
|
-
if (
|
|
31519
|
-
|
|
31520
|
-
|
|
31521
|
-
|
|
31522
|
-
|
|
31523
|
-
|
|
31524
|
-
|
|
31525
|
-
|
|
31526
|
-
|
|
31527
|
-
const
|
|
31528
|
-
const
|
|
31529
|
-
|
|
31530
|
-
|
|
31531
|
-
if (!
|
|
31532
|
-
|
|
31533
|
-
|
|
31534
|
-
|
|
31535
|
-
const
|
|
31536
|
-
|
|
31537
|
-
|
|
31538
|
-
|
|
31539
|
-
|
|
31540
|
-
|
|
31541
|
-
if (!
|
|
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" });
|
|
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;
|
|
31550
31627
|
}
|
|
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 });
|
|
31551
31631
|
}
|
|
31552
|
-
|
|
31553
|
-
|
|
31554
|
-
|
|
31555
|
-
|
|
31556
|
-
|
|
31557
|
-
|
|
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 });
|
|
31558
31644
|
}
|
|
31645
|
+
counts = await options.check(changes);
|
|
31559
31646
|
}
|
|
31560
|
-
const
|
|
31561
|
-
|
|
31562
|
-
|
|
31563
|
-
|
|
31564
|
-
|
|
31565
|
-
|
|
31566
|
-
|
|
31567
|
-
|
|
31568
|
-
|
|
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);
|
|
31569
31674
|
}
|
|
31675
|
+
out2.push({ table: full, version: version2, operation: commit.commitInfo.operation });
|
|
31570
31676
|
}
|
|
31571
|
-
|
|
31572
|
-
|
|
31573
|
-
|
|
31574
|
-
|
|
31575
|
-
|
|
31576
|
-
|
|
31577
|
-
|
|
31578
|
-
|
|
31579
|
-
if (!partial2 && was !== void 0 && createdHash(await app.vault.read(f)) !== was) {
|
|
31580
|
-
conflicts.push({ path: currentPath(c), message: "created note changed since the operation" });
|
|
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);
|
|
31581
31685
|
}
|
|
31582
|
-
continue;
|
|
31583
|
-
}
|
|
31584
|
-
if (partial2) back.add(c);
|
|
31585
|
-
else conflicts.push({ path: c, message: "created file no longer exists" });
|
|
31586
|
-
}
|
|
31587
|
-
for (const d of entry.deleted) {
|
|
31588
|
-
const f = fileOrNull(app, d.path);
|
|
31589
|
-
if (partial2 && f && await app.vault.read(f) === d.content) back.add(d);
|
|
31590
|
-
else if (app.vault.getAbstractFileByPath(d.path) != null) {
|
|
31591
|
-
conflicts.push({ path: d.path, message: "deleted path is occupied" });
|
|
31592
31686
|
}
|
|
31593
31687
|
}
|
|
31594
|
-
|
|
31595
|
-
|
|
31596
|
-
|
|
31597
|
-
|
|
31598
|
-
}
|
|
31599
|
-
const
|
|
31600
|
-
|
|
31601
|
-
const
|
|
31602
|
-
const
|
|
31603
|
-
const
|
|
31604
|
-
|
|
31605
|
-
|
|
31606
|
-
|
|
31607
|
-
|
|
31608
|
-
|
|
31609
|
-
|
|
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 });
|
|
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) }));
|
|
31615
31704
|
}
|
|
31616
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]);
|
|
31617
31728
|
}
|
|
31618
|
-
|
|
31619
|
-
|
|
31620
|
-
|
|
31621
|
-
|
|
31622
|
-
|
|
31623
|
-
|
|
31624
|
-
|
|
31625
|
-
|
|
31626
|
-
|
|
31627
|
-
|
|
31628
|
-
|
|
31629
|
-
|
|
31630
|
-
|
|
31631
|
-
|
|
31632
|
-
|
|
31633
|
-
|
|
31634
|
-
|
|
31635
|
-
|
|
31636
|
-
|
|
31637
|
-
|
|
31638
|
-
|
|
31639
|
-
|
|
31640
|
-
|
|
31641
|
-
|
|
31642
|
-
|
|
31643
|
-
});
|
|
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);
|
|
31644
31754
|
}
|
|
31645
|
-
|
|
31646
|
-
|
|
31647
|
-
|
|
31648
|
-
|
|
31649
|
-
|
|
31650
|
-
|
|
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;
|
|
31651
31766
|
}
|
|
31652
|
-
|
|
31653
|
-
|
|
31654
|
-
|
|
31655
|
-
|
|
31656
|
-
|
|
31657
|
-
|
|
31658
|
-
|
|
31659
|
-
|
|
31660
|
-
|
|
31661
|
-
|
|
31662
|
-
|
|
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);
|
|
31663
31790
|
}
|
|
31664
|
-
}
|
|
31665
|
-
|
|
31666
|
-
|
|
31667
|
-
|
|
31668
|
-
|
|
31669
|
-
|
|
31670
|
-
|
|
31671
|
-
await ensureFolder(app, parentFolder(d.path));
|
|
31672
|
-
await app.vault.create(d.path, d.content);
|
|
31673
|
-
undoCreates.push(d.path);
|
|
31674
|
-
undoCreatedHashes.push({ path: d.path, hash: createdHash(d.content) });
|
|
31791
|
+
}
|
|
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++;
|
|
31675
31798
|
}
|
|
31676
|
-
});
|
|
31677
|
-
}
|
|
31678
|
-
let done = 0;
|
|
31679
|
-
try {
|
|
31680
|
-
for (const s of steps) {
|
|
31681
|
-
await s.run();
|
|
31682
|
-
done++;
|
|
31683
31799
|
}
|
|
31684
|
-
|
|
31685
|
-
|
|
31686
|
-
|
|
31687
|
-
|
|
31688
|
-
|
|
31689
|
-
settings,
|
|
31690
|
-
{
|
|
31691
|
-
op: op2,
|
|
31692
|
-
...entry.schema !== void 0 ? { schema: entry.schema } : {},
|
|
31693
|
-
...currentActor() ? { actor: currentActor() } : {},
|
|
31694
|
-
summary: `undo ${migrationId}`,
|
|
31695
|
-
executed_at: now.toISOString(),
|
|
31696
|
-
rows: undoRows,
|
|
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);
|
|
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();
|
|
@@ -66871,17 +66968,28 @@ var SemanticIndex = class {
|
|
|
66871
66968
|
if (this.queries.size > QUERY_VECTORS_MAX) this.queries.delete(this.queries.keys().next().value);
|
|
66872
66969
|
return v;
|
|
66873
66970
|
}
|
|
66971
|
+
/** The index's counts; under a read scope (read-scope.ts), of the notes the token may read only. */
|
|
66874
66972
|
status() {
|
|
66875
66973
|
const s = this.getSettings();
|
|
66974
|
+
const scope = currentReadScope();
|
|
66975
|
+
const readable = (path23) => !scope || pathReadable(this.app, s, path23, scope);
|
|
66976
|
+
let files = 0;
|
|
66876
66977
|
let passages = 0;
|
|
66877
|
-
for (const f of this.files
|
|
66978
|
+
for (const [path23, f] of this.files) {
|
|
66979
|
+
if (!readable(path23)) continue;
|
|
66980
|
+
files++;
|
|
66981
|
+
passages += f.passages.length;
|
|
66982
|
+
}
|
|
66983
|
+
let pending = 0;
|
|
66984
|
+
if (scope) for (const path23 of this.pending) pending += readable(path23) ? 1 : 0;
|
|
66985
|
+
else pending = this.pending.size;
|
|
66878
66986
|
return {
|
|
66879
66987
|
enabled: s.semanticEnabled,
|
|
66880
66988
|
provider: s.semanticProvider,
|
|
66881
66989
|
model: s.semanticModel,
|
|
66882
|
-
files
|
|
66990
|
+
files,
|
|
66883
66991
|
passages,
|
|
66884
|
-
pending
|
|
66992
|
+
pending,
|
|
66885
66993
|
running: this.running !== null,
|
|
66886
66994
|
...this.lastSync ? { last_sync: this.lastSync } : {},
|
|
66887
66995
|
...this.lastError ? { last_error: this.lastError } : {}
|
|
@@ -69078,6 +69186,7 @@ init_guidance();
|
|
|
69078
69186
|
init_requests();
|
|
69079
69187
|
init_actor();
|
|
69080
69188
|
init_read_scope();
|
|
69189
|
+
init_write_scope();
|
|
69081
69190
|
init_changeset_checks();
|
|
69082
69191
|
|
|
69083
69192
|
// src/server/token-routes.ts
|
|
@@ -69110,6 +69219,8 @@ var READ_ROUTES = /* @__PURE__ */ new Set(["/manual/search/", "/tokens/me/", "/t
|
|
|
69110
69219
|
var OWNER_ADMIN_ROUTES = /* @__PURE__ */ new Set(["/tokens/", "/tokens/create/", "/tokens/update/", "/tokens/delete/", "/tokens/rotate/", "/extensions/record/", "/extensions/delete/", "/status/", "/stop/"]);
|
|
69111
69220
|
var READ_SCOPE_UNSUPPORTED = "read permissions need Node's AsyncLocalStorage";
|
|
69112
69221
|
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");
|
|
69222
|
+
var WRITE_SCOPE_UNSUPPORTED = "write permissions need Node's AsyncLocalStorage";
|
|
69223
|
+
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
69224
|
|
|
69114
69225
|
// src/server/scheduler.ts
|
|
69115
69226
|
init_telemetry();
|
|
@@ -69358,7 +69469,7 @@ function errorResponse(e, op2, route) {
|
|
|
69358
69469
|
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
69470
|
if (e instanceof VaultPathNotFoundError) return { status: 404, json: body(guide("list_folder lists a folder, search_notes finds notes by their words.", "readNote")) };
|
|
69360
69471
|
if (e instanceof ReadDeniedError) return { status: 403, json: body(READ_DENIED_GUIDANCE) };
|
|
69361
|
-
if (e instanceof Forbidden) return { status: 403, json: body(FORBIDDEN_HINT) };
|
|
69472
|
+
if (e instanceof Forbidden || e instanceof WriteDeniedError) return { status: 403, json: body(FORBIDDEN_HINT) };
|
|
69362
69473
|
if (e instanceof AmbiguousCatalogRefError) {
|
|
69363
69474
|
return { status: 409, json: { ...body(guide(`Name the table in full (catalog.schema.table): ${e.candidates.join(" or ")}.`, "catalogs")), tables: e.candidates } };
|
|
69364
69475
|
}
|
|
@@ -69766,6 +69877,7 @@ function createHandlers(app, getSettings, saveSettings = async () => {
|
|
|
69766
69877
|
batch: async (b) => {
|
|
69767
69878
|
const operations = list(b, "operations");
|
|
69768
69879
|
if (operations.length === 0) throw new BadRequest(`"operations" must be a non-empty list`);
|
|
69880
|
+
const denied = /* @__PURE__ */ new Set();
|
|
69769
69881
|
const steps = operations.map((raw, i) => {
|
|
69770
69882
|
const { op: op2, ...args } = raw;
|
|
69771
69883
|
if (typeof op2 !== "string") throw new BadRequest(`operation ${i} needs "op"`);
|
|
@@ -69780,7 +69892,10 @@ function createHandlers(app, getSettings, saveSettings = async () => {
|
|
|
69780
69892
|
return {
|
|
69781
69893
|
op: op2,
|
|
69782
69894
|
run: async (dryRun) => {
|
|
69783
|
-
const res = await ops[op2]({ ...args, dry_run: dryRun }).catch((e) =>
|
|
69895
|
+
const res = await ops[op2]({ ...args, dry_run: dryRun }).catch((e) => {
|
|
69896
|
+
if (e instanceof WriteDeniedError) denied.add(i);
|
|
69897
|
+
return errorResponse(e, op2);
|
|
69898
|
+
});
|
|
69784
69899
|
if ("op" in res.json) return res.json;
|
|
69785
69900
|
throw stepFailure(res.json);
|
|
69786
69901
|
}
|
|
@@ -69791,8 +69906,13 @@ function createHandlers(app, getSettings, saveSettings = async () => {
|
|
|
69791
69906
|
...str(b, "summary") !== void 0 ? { summary: str(b, "summary") } : {}
|
|
69792
69907
|
});
|
|
69793
69908
|
const failedStep = r.failed_at !== void 0 && (r.steps[r.failed_at]?.result?.failed.length ?? 0) > 0;
|
|
69794
|
-
const
|
|
69909
|
+
const forbidden = r.failed_at !== void 0 ? denied.has(r.failed_at) ? r.failed_at : void 0 : [...denied].sort((a, b2) => a - b2)[0];
|
|
69910
|
+
const status = r.ok ? 200 : r.rolled_back === false ? 500 : forbidden !== void 0 ? 403 : writeStatus(failedStep, true);
|
|
69795
69911
|
const answer = redactBatchRollback(r, app, s());
|
|
69912
|
+
if (status === 403) {
|
|
69913
|
+
const step = answer.steps[forbidden];
|
|
69914
|
+
return { status, json: { ...answer, error: `operation ${forbidden}: ${step.error}`, hint: step.hint, docs: step.docs } };
|
|
69915
|
+
}
|
|
69796
69916
|
return { status, json: answer.ok ? answer : { ...answer, ...batchGuidance(answer) } };
|
|
69797
69917
|
},
|
|
69798
69918
|
// --- queries ---
|
|
@@ -70256,6 +70376,7 @@ var STATUS_TEXT = {
|
|
|
70256
70376
|
409: "Ambiguous table name: it matches several tables (`paths` or `tables` lists them)."
|
|
70257
70377
|
};
|
|
70258
70378
|
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.";
|
|
70379
|
+
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
70380
|
var ERRORS = {
|
|
70260
70381
|
400: { description: STATUS_TEXT[400], content: json(ref("Error")) },
|
|
70261
70382
|
401: { $ref: "#/components/responses/Unauthorized" },
|
|
@@ -70271,6 +70392,7 @@ function postRoute(d) {
|
|
|
70271
70392
|
const read3 = op2 !== void 0 ? READ_OPERATIONS.has(op2) : READ_ROUTES.has(d.route) && d.route !== "/tokens/update/";
|
|
70272
70393
|
if (!read3) responses["403"] = ERRORS[403];
|
|
70273
70394
|
else if (op2 !== void 0) responses["403"] = ERRORS.readDenied;
|
|
70395
|
+
if (d.forbidden) responses["403"] = { description: d.forbidden, content: json(ref("Error")) };
|
|
70274
70396
|
if (d.namesTable) responses["404"] = ERRORS[404];
|
|
70275
70397
|
const refused2 = op2 !== void 0 ? refusedStatus(op2) : void 0;
|
|
70276
70398
|
if (refused2 === 409) {
|
|
@@ -70609,6 +70731,7 @@ function openApiDocument(version2) {
|
|
|
70609
70731
|
route: "/sessions/identify/",
|
|
70610
70732
|
operationId: "identify_session",
|
|
70611
70733
|
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.",
|
|
70734
|
+
forbidden: SESSION_TAKEN_TEXT,
|
|
70612
70735
|
body: { type: "object", required: ["session"], properties: { session: { type: "string" }, model: { type: "string" }, harness: { type: "string" } } },
|
|
70613
70736
|
bodyRequired: true,
|
|
70614
70737
|
result: ok
|
|
@@ -70617,6 +70740,7 @@ function openApiDocument(version2) {
|
|
|
70617
70740
|
route: "/tool-calls/record/",
|
|
70618
70741
|
operationId: "record_tool_call",
|
|
70619
70742
|
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.",
|
|
70743
|
+
forbidden: SESSION_TAKEN_TEXT,
|
|
70620
70744
|
body: {
|
|
70621
70745
|
type: "object",
|
|
70622
70746
|
required: ["tool"],
|
|
@@ -71352,6 +71476,8 @@ var MAX_IDENTITIES = 2e3;
|
|
|
71352
71476
|
var SessionIdentities = class {
|
|
71353
71477
|
constructor() {
|
|
71354
71478
|
this.map = /* @__PURE__ */ new Map();
|
|
71479
|
+
/** Who reported each session first: "owner" or a token's name, by the session's first 16 characters. */
|
|
71480
|
+
this.owners = /* @__PURE__ */ new Map();
|
|
71355
71481
|
}
|
|
71356
71482
|
set(key2, info) {
|
|
71357
71483
|
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 +71489,21 @@ var SessionIdentities = class {
|
|
|
71363
71489
|
get(key2) {
|
|
71364
71490
|
return this.map.get(key2) ?? this.map.get(key2.slice(0, 16));
|
|
71365
71491
|
}
|
|
71492
|
+
/**
|
|
71493
|
+
* Claim a session for a credential ("owner" or a token's name): the first one to report it keeps
|
|
71494
|
+
* it. Answers the token that holds it when that is another named token, else null (claimed, or
|
|
71495
|
+
* the owner, who may report any session). Keyed by the first 16 characters, as lookups fall back
|
|
71496
|
+
* to them: a token cannot take over another's session with a longer or shorter id.
|
|
71497
|
+
*/
|
|
71498
|
+
claim(key2, who) {
|
|
71499
|
+
const k = key2.slice(0, 16);
|
|
71500
|
+
const held = this.owners.get(k);
|
|
71501
|
+
if (held !== void 0 && held !== who && who !== "owner") return held;
|
|
71502
|
+
this.owners.delete(k);
|
|
71503
|
+
if (this.owners.size >= MAX_IDENTITIES) this.owners.delete(this.owners.keys().next().value);
|
|
71504
|
+
this.owners.set(k, held ?? who);
|
|
71505
|
+
return null;
|
|
71506
|
+
}
|
|
71366
71507
|
};
|
|
71367
71508
|
|
|
71368
71509
|
// src/server/runtime.ts
|
|
@@ -71749,6 +71890,7 @@ async function engineStatus(i) {
|
|
|
71749
71890
|
// src/server/tokens.ts
|
|
71750
71891
|
init_actor();
|
|
71751
71892
|
init_read_scope();
|
|
71893
|
+
init_write_scope();
|
|
71752
71894
|
init_metastore();
|
|
71753
71895
|
init_store();
|
|
71754
71896
|
init_telemetry();
|
|
@@ -71768,6 +71910,13 @@ function tableAllowed(full, patterns) {
|
|
|
71768
71910
|
function scopeOf(t) {
|
|
71769
71911
|
return t.read === void 0 || t.read.includes("*") ? null : { token: t.name, patterns: t.read };
|
|
71770
71912
|
}
|
|
71913
|
+
function writeScopeOf(t) {
|
|
71914
|
+
return t.write.includes("*") ? null : { token: t.name, patterns: t.write };
|
|
71915
|
+
}
|
|
71916
|
+
function writesRows(route) {
|
|
71917
|
+
const op2 = operationOf(route);
|
|
71918
|
+
return op2 === "batch" || op2 !== void 0 && Object.prototype.hasOwnProperty.call(ROW_WRITES, op2);
|
|
71919
|
+
}
|
|
71771
71920
|
function readCovers(r, w) {
|
|
71772
71921
|
return r === "*" || r === w || r.endsWith(".*") && w.startsWith(r.slice(0, -1));
|
|
71773
71922
|
}
|
|
@@ -72010,7 +72159,12 @@ var TokenRegistry = class {
|
|
|
72010
72159
|
telemetry().log("WARN", "datanotes token refused", { token_name: name, "http.route": route, reason: READ_SCOPE_UNSUPPORTED });
|
|
72011
72160
|
return { status: 503, json: { error: READ_SCOPE_UNSUPPORTED, ...READ_SCOPE_UNSUPPORTED_HINT } };
|
|
72012
72161
|
}
|
|
72013
|
-
|
|
72162
|
+
const writeScope = writeScopeOf(token);
|
|
72163
|
+
if (writeScope && writesRows(route) && !(this.deps.readScopeSupported ?? READ_SCOPE_SUPPORTED)) {
|
|
72164
|
+
telemetry().log("WARN", "datanotes token refused", { token_name: name, "http.route": route, reason: WRITE_SCOPE_UNSUPPORTED });
|
|
72165
|
+
return { status: 503, json: { error: WRITE_SCOPE_UNSUPPORTED, ...WRITE_SCOPE_UNSUPPORTED_HINT } };
|
|
72166
|
+
}
|
|
72167
|
+
return runWithReadScope(scope, () => runWithWriteScope(writeScope, () => dispatch2(route, handler, body)));
|
|
72014
72168
|
};
|
|
72015
72169
|
}
|
|
72016
72170
|
// --- routes --------------------------------------------------------------------------------
|
|
@@ -72254,6 +72408,7 @@ var ExtensionRegistry = class {
|
|
|
72254
72408
|
init_metastore();
|
|
72255
72409
|
init_query_tables();
|
|
72256
72410
|
init_file_cache();
|
|
72411
|
+
init_read_scope();
|
|
72257
72412
|
init_vault_index();
|
|
72258
72413
|
var CACHED_READS = /* @__PURE__ */ new Set([
|
|
72259
72414
|
"select_rows",
|
|
@@ -72389,7 +72544,7 @@ var ReadCache = class {
|
|
|
72389
72544
|
if (this.maxBytes === 0 || !CACHED_READS.has(op2)) return null;
|
|
72390
72545
|
const settings = this.getSettings();
|
|
72391
72546
|
const model = (await loadMetastore(this.app, settings).catch(() => null))?.model ?? null;
|
|
72392
|
-
const deps = readDeps(op2, body, model);
|
|
72547
|
+
const deps = readDeps(op2, body, model, currentReadScope() !== null);
|
|
72393
72548
|
if (!deps) return null;
|
|
72394
72549
|
if (!this.looseKnown) this.indexLoose(settings);
|
|
72395
72550
|
if (deps.links && !this.linkPrints) this.printLinks();
|
|
@@ -72535,7 +72690,7 @@ var ReadCache = class {
|
|
|
72535
72690
|
function settingsKey(s) {
|
|
72536
72691
|
return JSON.stringify(s, (k, v) => SECRET_SETTINGS.has(k) ? void 0 : v);
|
|
72537
72692
|
}
|
|
72538
|
-
function readDeps(op2, body, model) {
|
|
72693
|
+
function readDeps(op2, body, model, restricted = false) {
|
|
72539
72694
|
if (!CACHED_READS.has(op2)) return null;
|
|
72540
72695
|
const text5 = JSON.stringify(body);
|
|
72541
72696
|
if (text5.includes("{now")) return null;
|
|
@@ -72546,7 +72701,10 @@ function readDeps(op2, body, model) {
|
|
|
72546
72701
|
const found = /* @__PURE__ */ new Set();
|
|
72547
72702
|
const r = queryTables(body, model, found, 0);
|
|
72548
72703
|
if (r.now || r.store) return null;
|
|
72549
|
-
if (!r.vault)
|
|
72704
|
+
if (!r.vault) {
|
|
72705
|
+
const tables = r.links && restricted ? catalogTables(model).map(({ ref: ref2 }) => ref2.full) : [...found];
|
|
72706
|
+
return { tables: tables.sort(), today: today || r.today, ...r.links ? { links: true } : {} };
|
|
72707
|
+
}
|
|
72550
72708
|
const views = viewTimes(model);
|
|
72551
72709
|
if (views.now) return null;
|
|
72552
72710
|
return { tables: "vault", today: today || r.today || views.today };
|
|
@@ -72575,6 +72733,7 @@ function viewTimes(model) {
|
|
|
72575
72733
|
// src/server/runtime.ts
|
|
72576
72734
|
init_message();
|
|
72577
72735
|
init_hints();
|
|
72736
|
+
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
72737
|
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
72738
|
var EXTERNAL_EDIT_DELAY_MS = 3e3;
|
|
72580
72739
|
var LOG_CHECK_EVERY_MS = 10 * 6e4;
|
|
@@ -72709,6 +72868,8 @@ var EngineRuntime = class {
|
|
|
72709
72868
|
"/sessions/identify/": async (body) => {
|
|
72710
72869
|
const session = typeof body.session === "string" ? body.session.trim() : "";
|
|
72711
72870
|
if (!session) return { status: 400, json: { error: `"session" is required`, ...REPORT_HINT } };
|
|
72871
|
+
const taken = this.sessionTaken(session);
|
|
72872
|
+
if (taken) return taken;
|
|
72712
72873
|
this.identities.set(session, { model: body.model, harness: body.harness });
|
|
72713
72874
|
return { status: 200, json: { ok: true } };
|
|
72714
72875
|
},
|
|
@@ -72724,6 +72885,8 @@ var EngineRuntime = class {
|
|
|
72724
72885
|
// an MCP server reports the tool calls of its agents (the change feed's tool_call events)
|
|
72725
72886
|
"/tool-calls/record/": async (body) => {
|
|
72726
72887
|
if (typeof body.tool !== "string" || !body.tool) return { status: 400, json: { error: `"tool" is required`, ...REPORT_HINT } };
|
|
72888
|
+
const taken = typeof body.session === "string" && body.session.trim() ? this.sessionTaken(body.session.trim()) : null;
|
|
72889
|
+
if (taken) return taken;
|
|
72727
72890
|
const actor = currentActor();
|
|
72728
72891
|
await this.recordToolCall({
|
|
72729
72892
|
tool: body.tool,
|
|
@@ -72772,6 +72935,14 @@ var EngineRuntime = class {
|
|
|
72772
72935
|
} : {}
|
|
72773
72936
|
};
|
|
72774
72937
|
}
|
|
72938
|
+
/**
|
|
72939
|
+
* The 403 when the session belongs to another named token; else null, and the session is
|
|
72940
|
+
* claimed for the caller when nobody reported it before (the first reporter keeps it).
|
|
72941
|
+
*/
|
|
72942
|
+
sessionTaken(session) {
|
|
72943
|
+
if (this.identities.claim(session, tokenNameOf(currentActor()) ?? "owner") === null) return null;
|
|
72944
|
+
return { status: 403, json: { error: `session "${session}" belongs to another token`, ...SESSION_TAKEN_HINT } };
|
|
72945
|
+
}
|
|
72775
72946
|
/**
|
|
72776
72947
|
* An agent's tool call, as a change-feed event (secrets removed, long values cut); settled once
|
|
72777
72948
|
* it is recorded. The feed is kept in the store, which the store backup commits: no secret.
|
|
@@ -77216,7 +77387,7 @@ function setUp(o, vault, home, explicit, self) {
|
|
|
77216
77387
|
// src/node/service.ts
|
|
77217
77388
|
init_local();
|
|
77218
77389
|
init_hints();
|
|
77219
|
-
var VERSION = true ? "3.4.
|
|
77390
|
+
var VERSION = true ? "3.4.1" : "dev";
|
|
77220
77391
|
function parsePort(raw, from = "--port") {
|
|
77221
77392
|
const n = /^\s*\d+\s*$/.test(raw) ? Number(raw) : NaN;
|
|
77222
77393
|
if (n === 0 || Number.isInteger(n) && n >= 1024 && n <= 65535) return n;
|
|
@@ -78281,7 +78452,7 @@ Docs: https://datanotes.dev`;
|
|
|
78281
78452
|
|
|
78282
78453
|
// src/node/cli.ts
|
|
78283
78454
|
init_message();
|
|
78284
|
-
var VERSION2 = true ? "3.4.
|
|
78455
|
+
var VERSION2 = true ? "3.4.1" : "dev";
|
|
78285
78456
|
var consoleError = console.error;
|
|
78286
78457
|
async function main5(argv = process.argv.slice(2)) {
|
|
78287
78458
|
const [command, ...rest] = argv;
|