@appsoftwareltd/etherpk-mcp 0.15.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.js +386 -32
- package/dist/main.js.map +1 -1
- package/package.json +5 -5
package/dist/main.js
CHANGED
|
@@ -627,8 +627,8 @@ var ASSET_CHUNK_PLAINTEXT_BYTES = 4194304;
|
|
|
627
627
|
function assetChunkCount(size) {
|
|
628
628
|
return Math.max(1, Math.ceil(size / ASSET_CHUNK_PLAINTEXT_BYTES));
|
|
629
629
|
}
|
|
630
|
-
var DAY = 864e5;
|
|
631
|
-
365 * DAY, 30 * DAY, 7 * DAY;
|
|
630
|
+
var DAY$1 = 864e5;
|
|
631
|
+
365 * DAY$1, 30 * DAY$1, 7 * DAY$1;
|
|
632
632
|
/** Whether `text` is a short link a Sync Server opens: one of Google's share links. */
|
|
633
633
|
function isShortMapLink(text) {
|
|
634
634
|
let url;
|
|
@@ -717,7 +717,7 @@ function looksLikeAgentSetupCode(value) {
|
|
|
717
717
|
}
|
|
718
718
|
var package_default = {
|
|
719
719
|
name: "@appsoftwareltd/etherpk-mcp",
|
|
720
|
-
version: "0.
|
|
720
|
+
version: "0.16.0",
|
|
721
721
|
license: "Elastic-2.0",
|
|
722
722
|
description: "EtherPK Headless Client: an MCP server over a synced knowledge graph, run beside the agent on the user's own machine.",
|
|
723
723
|
type: "module",
|
|
@@ -863,7 +863,7 @@ function fromBase64Url(text) {
|
|
|
863
863
|
* is indistinguishable from random, so the server can tell two assets in *one* graph are
|
|
864
864
|
* identical and nothing more; the secret is per graph, so nothing compares across graphs.
|
|
865
865
|
*/
|
|
866
|
-
var INFO = "etherpk/asset-dedup/v1";
|
|
866
|
+
var INFO$1 = "etherpk/asset-dedup/v1";
|
|
867
867
|
/** The graph's dedup secret (32 bytes) for its newest epoch. Derive once per epoch. */
|
|
868
868
|
async function deriveAssetDedupSecret(keyring) {
|
|
869
869
|
const newest = keyring.epochs.at(-1);
|
|
@@ -873,7 +873,7 @@ async function deriveAssetDedupSecret(keyring) {
|
|
|
873
873
|
name: "HKDF",
|
|
874
874
|
hash: "SHA-256",
|
|
875
875
|
salt: utf8(keyring.graphId),
|
|
876
|
-
info: utf8(INFO)
|
|
876
|
+
info: utf8(INFO$1)
|
|
877
877
|
}, ikm, 256);
|
|
878
878
|
return new Uint8Array(bits);
|
|
879
879
|
}
|
|
@@ -1542,6 +1542,50 @@ async function openVault(envelope, key) {
|
|
|
1542
1542
|
throw new EnvelopeError(`unexpected vault envelope kind ${envelope[1] ?? "none"}`);
|
|
1543
1543
|
}
|
|
1544
1544
|
contextAad("settings-vault", "v1");
|
|
1545
|
+
//#endregion
|
|
1546
|
+
//#region ../client/src/lib/crypto/journal-doc-id.ts
|
|
1547
|
+
/**
|
|
1548
|
+
* The **journal document id** (ADR 0157): a [[Journal Entry]]'s document id, derived from its day,
|
|
1549
|
+
* so two devices that start the same day before they hear of each other write into one document
|
|
1550
|
+
* and the CRDT merges their text, instead of leaving two documents behind one name.
|
|
1551
|
+
*
|
|
1552
|
+
* secret = HKDF-SHA-256(ikm = epoch 1 key, salt = graph id, info = 'etherpk/journal-doc-id/v1')
|
|
1553
|
+
* id = HMAC-SHA-256(secret, 'YYYY-MM-DD'), first 128 bits, as a version 8 UUID
|
|
1554
|
+
*
|
|
1555
|
+
* The **epoch 1** key is the input, never the newest (unlike the asset dedup token): the id must
|
|
1556
|
+
* not change when the graph moves to a new Graph Key epoch (ADR 0127), or a day started before a
|
|
1557
|
+
* rotation and the same day started after it would get two documents. Every member holds every
|
|
1558
|
+
* epoch, so every member derives the same id. The server sees document ids already, and without
|
|
1559
|
+
* the key an id is indistinguishable from a random one, so it learns no day from it.
|
|
1560
|
+
*/
|
|
1561
|
+
var INFO = "etherpk/journal-doc-id/v1";
|
|
1562
|
+
var DAY = /^\d{4}-\d{2}-\d{2}$/;
|
|
1563
|
+
/** The graph's journal id secret (32 bytes). It never changes, so derive it once per session. */
|
|
1564
|
+
async function deriveJournalIdSecret(keyring) {
|
|
1565
|
+
const first = keyForEpoch(keyring, 1);
|
|
1566
|
+
if (!first) throw new Error("deriveJournalIdSecret: keyring has no epoch 1 key");
|
|
1567
|
+
const ikm = await crypto.subtle.importKey("raw", first, "HKDF", false, ["deriveBits"]);
|
|
1568
|
+
const bits = await crypto.subtle.deriveBits({
|
|
1569
|
+
name: "HKDF",
|
|
1570
|
+
hash: "SHA-256",
|
|
1571
|
+
salt: utf8(keyring.graphId),
|
|
1572
|
+
info: utf8(INFO)
|
|
1573
|
+
}, ikm, 256);
|
|
1574
|
+
return new Uint8Array(bits);
|
|
1575
|
+
}
|
|
1576
|
+
/** The document id of the journal entry for `day` (`YYYY-MM-DD`), under the graph's secret. */
|
|
1577
|
+
async function journalDocId(secret, day) {
|
|
1578
|
+
if (!DAY.test(day)) throw new Error(`journalDocId: "${day}" is not a calendar day`);
|
|
1579
|
+
const key = await crypto.subtle.importKey("raw", secret, {
|
|
1580
|
+
name: "HMAC",
|
|
1581
|
+
hash: "SHA-256"
|
|
1582
|
+
}, false, ["sign"]);
|
|
1583
|
+
const bytes = new Uint8Array(await crypto.subtle.sign("HMAC", key, utf8(day))).slice(0, 16);
|
|
1584
|
+
bytes[6] = bytes[6] & 15 | 128;
|
|
1585
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
1586
|
+
const hex = [...bytes].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
1587
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
1588
|
+
}
|
|
1545
1589
|
var PAD_TO = 64;
|
|
1546
1590
|
var LENGTH_PREFIX = 2;
|
|
1547
1591
|
function graphNameAad(graphId) {
|
|
@@ -7910,6 +7954,12 @@ var REMOTE = Symbol("etherpk-remote");
|
|
|
7910
7954
|
var CACHE_SEED = Symbol("etherpk-cache-seed");
|
|
7911
7955
|
/** Structural lifecycle changes which must never be mistaken for a user's edit. */
|
|
7912
7956
|
var SUPPRESSED = Symbol("etherpk-suppressed");
|
|
7957
|
+
/**
|
|
7958
|
+
* A created document's text written again as this device's own insert, so it can be sent whole
|
|
7959
|
+
* to a generation that never held the structs it was first written with (ADR 0157). Local, so it
|
|
7960
|
+
* is sent, but no undo manager tracks it.
|
|
7961
|
+
*/
|
|
7962
|
+
var RECREATED = Symbol("etherpk-recreated");
|
|
7913
7963
|
/** Health that says the bytes are not this document's, so neither text nor emptiness is real. */
|
|
7914
7964
|
function contentBlocked(health) {
|
|
7915
7965
|
return health === "key-unavailable" || health === "ciphertext-corrupt";
|
|
@@ -8002,6 +8052,14 @@ function createDocSync(deps) {
|
|
|
8002
8052
|
let pendingSnapshot;
|
|
8003
8053
|
/** The newest epoch an update needed that the keyring does not hold (ADR 0127). */
|
|
8004
8054
|
let missingEpoch;
|
|
8055
|
+
/** Set by `claimCreation` until the relay acknowledges this device's first text. */
|
|
8056
|
+
let createdHere = false;
|
|
8057
|
+
/**
|
|
8058
|
+
* Set when the relay answered `stale_generation` to text this device created or resurrected:
|
|
8059
|
+
* the next catch-up page tells which generation the relay holds, and that text is then
|
|
8060
|
+
* written again and sent whole into it rather than cleared.
|
|
8061
|
+
*/
|
|
8062
|
+
let recreating = false;
|
|
8005
8063
|
/** Outbox operations the relay refused as sealed under an older epoch: sealed again on `keysChanged`. */
|
|
8006
8064
|
const refusedAsStale = /* @__PURE__ */ new Set();
|
|
8007
8065
|
/** A read-back failed: the next idle uploads a fresh snapshot whatever the tail size. */
|
|
@@ -8031,6 +8089,44 @@ function createDocSync(deps) {
|
|
|
8031
8089
|
catchupActive = false;
|
|
8032
8090
|
catchupCompletion.resolve();
|
|
8033
8091
|
}
|
|
8092
|
+
/**
|
|
8093
|
+
* The relay holds this document at a generation that the text this device created was not
|
|
8094
|
+
* written in (ADR 0157). Move to that generation, and write the text again as one insert of
|
|
8095
|
+
* this device's own, so it depends on no struct the generation never held (a deterministic
|
|
8096
|
+
* Journal Template's among them). The next send carries the whole state: a `resurrect` into a
|
|
8097
|
+
* deleted generation, an append into one that another device started first.
|
|
8098
|
+
*/
|
|
8099
|
+
function recreateInto(nextGeneration, state) {
|
|
8100
|
+
recreating = false;
|
|
8101
|
+
documentGeneration = nextGeneration;
|
|
8102
|
+
lifecycleState = state;
|
|
8103
|
+
lastSeq = 0;
|
|
8104
|
+
syncHealth = "healthy";
|
|
8105
|
+
const content = doc.getText("content");
|
|
8106
|
+
const text = content.toString();
|
|
8107
|
+
if (text === "") return;
|
|
8108
|
+
doc.transact(() => {
|
|
8109
|
+
content.delete(0, text.length);
|
|
8110
|
+
content.insert(0, text);
|
|
8111
|
+
}, RECREATED);
|
|
8112
|
+
}
|
|
8113
|
+
/**
|
|
8114
|
+
* A catch-up has reached a newer active generation than this device's copy (ADR 0157): the
|
|
8115
|
+
* document was deleted, and started again, while this device was away. The copy holds the
|
|
8116
|
+
* deleted generation's text, so it is cleared, never merged into the new one. The new
|
|
8117
|
+
* generation never held the structs that remain, so the next send carries the whole state.
|
|
8118
|
+
*/
|
|
8119
|
+
function discardOlderGeneration() {
|
|
8120
|
+
const content = doc.getText("content");
|
|
8121
|
+
seeding = true;
|
|
8122
|
+
try {
|
|
8123
|
+
if (content.length > 0) doc.transact(() => content.delete(0, content.length), SUPPRESSED);
|
|
8124
|
+
} finally {
|
|
8125
|
+
seeding = false;
|
|
8126
|
+
}
|
|
8127
|
+
lastSyncedStateVector = Y.encodeStateVector(new Y.Doc());
|
|
8128
|
+
lastSyncedSnapshot = Y.snapshot(doc);
|
|
8129
|
+
}
|
|
8034
8130
|
function failCatchup(error) {
|
|
8035
8131
|
const reported = asError(error);
|
|
8036
8132
|
firstCatchupPage.reject(reported);
|
|
@@ -8138,7 +8234,8 @@ function createDocSync(deps) {
|
|
|
8138
8234
|
epochId: epoch.epochId,
|
|
8139
8235
|
envelope: toBase64Url(await encrypt(plaintext)),
|
|
8140
8236
|
stateVector: operationStateVector,
|
|
8141
|
-
createdAt: now()
|
|
8237
|
+
createdAt: now(),
|
|
8238
|
+
...createdHere ? { creates: true } : {}
|
|
8142
8239
|
};
|
|
8143
8240
|
const state = stateSnapshot();
|
|
8144
8241
|
await persistTask(() => persist.enqueue(operation, state, { settledDirtyTokens }));
|
|
@@ -8506,6 +8603,7 @@ function createDocSync(deps) {
|
|
|
8506
8603
|
}
|
|
8507
8604
|
lastSyncedStateVector = mergeStateVectors(lastSyncedStateVector, operation.stateVector);
|
|
8508
8605
|
lifecycleState = "active";
|
|
8606
|
+
createdHere = false;
|
|
8509
8607
|
updatesSinceSnapshot += 1;
|
|
8510
8608
|
bytesSinceSnapshot += fromBase64Url(operation.envelope).byteLength;
|
|
8511
8609
|
scheduleAutomaticCompaction();
|
|
@@ -8529,6 +8627,21 @@ function createDocSync(deps) {
|
|
|
8529
8627
|
try {
|
|
8530
8628
|
const beforeBatch = indexMutationVersion;
|
|
8531
8629
|
if (message.generation < documentGeneration) break;
|
|
8630
|
+
if (recreating) {
|
|
8631
|
+
recreateInto(message.generation, message.state);
|
|
8632
|
+
if (message.state === "deleted") {
|
|
8633
|
+
await saveCurrent({ indexContentChanged: true });
|
|
8634
|
+
futureUpdates.clear();
|
|
8635
|
+
completeCatchup();
|
|
8636
|
+
break;
|
|
8637
|
+
}
|
|
8638
|
+
} else if (message.state === "active" && message.generation > documentGeneration && lifecycleState === "active") discardOlderGeneration();
|
|
8639
|
+
if (message.state === "deleted" && message.generation === documentGeneration && (lifecycleState === "deleted" || lifecycleState === "resurrecting")) {
|
|
8640
|
+
lastSeq = 0;
|
|
8641
|
+
futureUpdates.clear();
|
|
8642
|
+
completeCatchup();
|
|
8643
|
+
break;
|
|
8644
|
+
}
|
|
8532
8645
|
if (message.state === "deleted") {
|
|
8533
8646
|
documentGeneration = message.generation;
|
|
8534
8647
|
lifecycleState = "deleted";
|
|
@@ -8635,10 +8748,21 @@ function createDocSync(deps) {
|
|
|
8635
8748
|
await transmit(durableQueue[0]);
|
|
8636
8749
|
},
|
|
8637
8750
|
lifecycle: () => lifecycleState,
|
|
8751
|
+
generation: () => documentGeneration,
|
|
8752
|
+
claimCreation() {
|
|
8753
|
+
createdHere = true;
|
|
8754
|
+
},
|
|
8638
8755
|
health: () => syncHealth,
|
|
8639
8756
|
async staleGeneration(currentGeneration) {
|
|
8640
8757
|
syncHealth = "generation-stale";
|
|
8641
|
-
|
|
8758
|
+
const discarded = durableQueue.splice(0);
|
|
8759
|
+
if (createdHere || discarded.some((operation) => operation.kind === "resurrect" || operation.kind === "append" && operation.creates)) {
|
|
8760
|
+
recreating = true;
|
|
8761
|
+
createdHere = true;
|
|
8762
|
+
lastSyncedStateVector = Y.encodeStateVector(new Y.Doc());
|
|
8763
|
+
lastSyncedSnapshot = void 0;
|
|
8764
|
+
} else if (currentGeneration > documentGeneration && lifecycleState === "active") discardOlderGeneration();
|
|
8765
|
+
for (const operation of discarded) await persist.discard(operation.outboxId);
|
|
8642
8766
|
queueChanged();
|
|
8643
8767
|
boundarySnapshots.clear();
|
|
8644
8768
|
documentGeneration = currentGeneration;
|
|
@@ -9720,6 +9844,14 @@ function createGraphSync(deps) {
|
|
|
9720
9844
|
const foregroundCatchups = [];
|
|
9721
9845
|
const backgroundCatchups = [];
|
|
9722
9846
|
let catchupInFlight;
|
|
9847
|
+
/**
|
|
9848
|
+
* The request the in-flight one's document asked for again while it was in flight. The relay
|
|
9849
|
+
* may have composed the in-flight answer before whatever prompted the second request: an ack
|
|
9850
|
+
* naming a sequence that another device's concurrent first write took, for one. Dropped as a
|
|
9851
|
+
* duplicate, that document stayed behind for good, so it runs once more if the answer leaves
|
|
9852
|
+
* the document with a gap.
|
|
9853
|
+
*/
|
|
9854
|
+
let catchupRepeat;
|
|
9723
9855
|
let resolveFirstOpen;
|
|
9724
9856
|
const firstOpen = new Promise((resolve) => {
|
|
9725
9857
|
resolveFirstOpen = resolve;
|
|
@@ -9812,7 +9944,10 @@ function createGraphSync(deps) {
|
|
|
9812
9944
|
}
|
|
9813
9945
|
function queueCatchup(message) {
|
|
9814
9946
|
if (!open) return;
|
|
9815
|
-
if (catchupInFlight?.docId === message.docId && catchupInFlight.generation === message.generation && catchupInFlight.afterSeq === message.afterSeq)
|
|
9947
|
+
if (catchupInFlight?.docId === message.docId && catchupInFlight.generation === message.generation && catchupInFlight.afterSeq === message.afterSeq) {
|
|
9948
|
+
catchupRepeat = message;
|
|
9949
|
+
return;
|
|
9950
|
+
}
|
|
9816
9951
|
const removeQueued = (queue) => {
|
|
9817
9952
|
const index = queue.findIndex((candidate) => candidate.docId === message.docId);
|
|
9818
9953
|
if (index >= 0) queue.splice(index, 1);
|
|
@@ -9826,6 +9961,9 @@ function createGraphSync(deps) {
|
|
|
9826
9961
|
if (!requestId || catchupInFlight?.requestId !== requestId) return;
|
|
9827
9962
|
const completedDocId = catchupInFlight.docId;
|
|
9828
9963
|
catchupInFlight = void 0;
|
|
9964
|
+
const repeat = catchupRepeat?.docId === completedDocId ? catchupRepeat : void 0;
|
|
9965
|
+
catchupRepeat = void 0;
|
|
9966
|
+
if (repeat && engines.get(completedDocId)?.health() === "sequence-gap") queueCatchup(repeat);
|
|
9829
9967
|
pumpCatchups();
|
|
9830
9968
|
retireEngineIfIdle(completedDocId);
|
|
9831
9969
|
}
|
|
@@ -10125,7 +10263,10 @@ function createGraphSync(deps) {
|
|
|
10125
10263
|
return;
|
|
10126
10264
|
}
|
|
10127
10265
|
if (message.code === "stale_generation" && message.docId && message.currentGeneration !== void 0) {
|
|
10128
|
-
if (catchupInFlight?.docId === message.docId)
|
|
10266
|
+
if (catchupInFlight?.docId === message.docId) {
|
|
10267
|
+
catchupInFlight = void 0;
|
|
10268
|
+
catchupRepeat = void 0;
|
|
10269
|
+
}
|
|
10129
10270
|
operationAnswered(message.docId);
|
|
10130
10271
|
engine(message.docId).staleGeneration(message.currentGeneration);
|
|
10131
10272
|
pumpCatchups();
|
|
@@ -10183,6 +10324,7 @@ function createGraphSync(deps) {
|
|
|
10183
10324
|
operationsInFlight.clear();
|
|
10184
10325
|
operationsWaiting.clear();
|
|
10185
10326
|
catchupInFlight = void 0;
|
|
10327
|
+
catchupRepeat = void 0;
|
|
10186
10328
|
foregroundCatchups.length = 0;
|
|
10187
10329
|
backgroundCatchups.length = 0;
|
|
10188
10330
|
for (const pending of watermarkRequests.values()) {
|
|
@@ -10311,6 +10453,7 @@ function createGraphSync(deps) {
|
|
|
10311
10453
|
});
|
|
10312
10454
|
}
|
|
10313
10455
|
connect();
|
|
10456
|
+
let journalIdSecret;
|
|
10314
10457
|
return {
|
|
10315
10458
|
rootDoc: root.doc,
|
|
10316
10459
|
docSync: (docId) => engine(docId),
|
|
@@ -10367,6 +10510,10 @@ function createGraphSync(deps) {
|
|
|
10367
10510
|
compactDoc: (docId) => deps.cache.docCache(docId).compact(),
|
|
10368
10511
|
uploadSnapshot: (docId) => engine(docId).compact(),
|
|
10369
10512
|
registry: () => registryMap,
|
|
10513
|
+
journalDocId(date) {
|
|
10514
|
+
journalIdSecret ??= deriveJournalIdSecret(keyring());
|
|
10515
|
+
return journalIdSecret.then((secret) => journalDocId(secret, date));
|
|
10516
|
+
},
|
|
10370
10517
|
getMeta() {
|
|
10371
10518
|
const name = metaMap.get("name");
|
|
10372
10519
|
const settings = metaMap.get("settings");
|
|
@@ -10623,6 +10770,7 @@ function createGraphSync(deps) {
|
|
|
10623
10770
|
foregroundCatchups.length = 0;
|
|
10624
10771
|
backgroundCatchups.length = 0;
|
|
10625
10772
|
catchupInFlight = void 0;
|
|
10773
|
+
catchupRepeat = void 0;
|
|
10626
10774
|
for (const pending of watermarkRequests.values()) {
|
|
10627
10775
|
clearTimeout(pending.timer);
|
|
10628
10776
|
pending.reject(/* @__PURE__ */ new Error("the graph sync session was disposed"));
|
|
@@ -14029,7 +14177,7 @@ async function publishedFence(asked, info, concept, pictures, warnings) {
|
|
|
14029
14177
|
function assetHrefOf(name) {
|
|
14030
14178
|
return `assets/${encodeURIComponent(name)}`;
|
|
14031
14179
|
}
|
|
14032
|
-
function bodyOf$
|
|
14180
|
+
function bodyOf$2(doc) {
|
|
14033
14181
|
return parseFrontmatter(doc.text).body;
|
|
14034
14182
|
}
|
|
14035
14183
|
/** Every `http(s)` resource a piece of HTML or CSS pulls in, for the report. */
|
|
@@ -14216,7 +14364,7 @@ async function publishPublication(source, publication, env, options = {}) {
|
|
|
14216
14364
|
});
|
|
14217
14365
|
const bodies = [...included, ...includeDocs.values()].map((d) => ({
|
|
14218
14366
|
doc: d,
|
|
14219
|
-
body: bodyOf$
|
|
14367
|
+
body: bodyOf$2(d)
|
|
14220
14368
|
}));
|
|
14221
14369
|
for (const { doc, body } of bodies) for (const fence of probe.fences(body)) {
|
|
14222
14370
|
const fenceKey = `${fence.lang}\n${fence.code}`;
|
|
@@ -14329,7 +14477,7 @@ async function publishPublication(source, publication, env, options = {}) {
|
|
|
14329
14477
|
progress("rendering", done++, included.length);
|
|
14330
14478
|
const slug = slugs.get(conceptKey$1(doc.concept));
|
|
14331
14479
|
scopeDiagrams(`mermaid_${slug}`);
|
|
14332
|
-
const rendered = renderer.render(bodyOf$
|
|
14480
|
+
const rendered = renderer.render(bodyOf$2(doc));
|
|
14333
14481
|
const isHome = doc === homeDoc;
|
|
14334
14482
|
const page = {
|
|
14335
14483
|
doc,
|
|
@@ -14346,7 +14494,7 @@ async function publishPublication(source, publication, env, options = {}) {
|
|
|
14346
14494
|
if (dated.date !== void 0) page.date = dated.date;
|
|
14347
14495
|
take(dated.issues);
|
|
14348
14496
|
}
|
|
14349
|
-
for (const link of linksOf(bodyOf$
|
|
14497
|
+
for (const link of linksOf(bodyOf$2(doc), resolver, doc)) report.missingLinks.push(link);
|
|
14350
14498
|
pages.push(page);
|
|
14351
14499
|
report.included.push({
|
|
14352
14500
|
concept: doc.concept,
|
|
@@ -14387,7 +14535,7 @@ async function publishPublication(source, publication, env, options = {}) {
|
|
|
14387
14535
|
});
|
|
14388
14536
|
continue;
|
|
14389
14537
|
}
|
|
14390
|
-
const body = bodyOf$
|
|
14538
|
+
const body = bodyOf$2(doc);
|
|
14391
14539
|
if (cssSlots.has(slot)) {
|
|
14392
14540
|
const fence = renderer.fences(body).find((f) => f.lang === "css");
|
|
14393
14541
|
if (!fence) {
|
|
@@ -14558,7 +14706,7 @@ async function publishPublication(source, publication, env, options = {}) {
|
|
|
14558
14706
|
})));
|
|
14559
14707
|
const wanted = /* @__PURE__ */ new Map();
|
|
14560
14708
|
for (const page of pages) for (const name of page.rendered.assets) if (!wanted.has(name)) wanted.set(name, page.doc.concept);
|
|
14561
|
-
for (const [, doc] of includeDocs) for (const name of renderer.render(bodyOf$
|
|
14709
|
+
for (const [, doc] of includeDocs) for (const name of renderer.render(bodyOf$2(doc)).assets) if (!wanted.has(name)) wanted.set(name, doc.concept);
|
|
14562
14710
|
let assetsDone = 0;
|
|
14563
14711
|
for (const [name, from] of wanted) {
|
|
14564
14712
|
progress("assets", assetsDone++, wanted.size);
|
|
@@ -23854,7 +24002,7 @@ function describeFilesystemSaveFailure(error, concept) {
|
|
|
23854
24002
|
//#endregion
|
|
23855
24003
|
//#region src/headless-documents.ts
|
|
23856
24004
|
/** The document's text after its frontmatter block, and where that block ends. */
|
|
23857
|
-
function bodyOf(text) {
|
|
24005
|
+
function bodyOf$1(text) {
|
|
23858
24006
|
const head = frontmatterSpan(text)?.end ?? 0;
|
|
23859
24007
|
return {
|
|
23860
24008
|
head,
|
|
@@ -23869,16 +24017,16 @@ function bodyOf(text) {
|
|
|
23869
24017
|
function bodyView(handle) {
|
|
23870
24018
|
return {
|
|
23871
24019
|
id: handle.id,
|
|
23872
|
-
getText: () => bodyOf(handle.getText()).body,
|
|
24020
|
+
getText: () => bodyOf$1(handle.getText()).body,
|
|
23873
24021
|
applyChange(change, origin) {
|
|
23874
|
-
const { head } = bodyOf(handle.getText());
|
|
24022
|
+
const { head } = bodyOf$1(handle.getText());
|
|
23875
24023
|
handle.applyChange({
|
|
23876
24024
|
from: change.from + head,
|
|
23877
24025
|
to: change.to + head,
|
|
23878
24026
|
insert: change.insert
|
|
23879
24027
|
}, origin);
|
|
23880
24028
|
},
|
|
23881
|
-
subscribe: (listener) => handle.subscribe((text) => listener(bodyOf(text).body))
|
|
24029
|
+
subscribe: (listener) => handle.subscribe((text) => listener(bodyOf$1(text).body))
|
|
23882
24030
|
};
|
|
23883
24031
|
}
|
|
23884
24032
|
//#endregion
|
|
@@ -24004,6 +24152,34 @@ function createBreather(now = () => performance.now()) {
|
|
|
24004
24152
|
};
|
|
24005
24153
|
}
|
|
24006
24154
|
//#endregion
|
|
24155
|
+
//#region ../client/src/lib/storage/server/duplicate-names.ts
|
|
24156
|
+
/** A document's text below its [[Frontmatter]] block, if it has one. */
|
|
24157
|
+
function bodyOf(text) {
|
|
24158
|
+
return text.slice(frontmatterSpan(text)?.end ?? 0);
|
|
24159
|
+
}
|
|
24160
|
+
/**
|
|
24161
|
+
* The text a Merge leaves in the first document: its own text whole, then each other document's
|
|
24162
|
+
* text below its block, after a blank line, in the order given. Every other document's identity is
|
|
24163
|
+
* already the first one's, and its aliases join in the registry, so its block adds nothing.
|
|
24164
|
+
*
|
|
24165
|
+
* Always the first text followed by an addition, so the store writes it as an append: an edit made
|
|
24166
|
+
* meanwhile on another device anywhere in the first document is kept.
|
|
24167
|
+
*/
|
|
24168
|
+
function mergedText(texts) {
|
|
24169
|
+
let merged = texts[0] ?? "";
|
|
24170
|
+
for (const other of texts.slice(1)) {
|
|
24171
|
+
const body = bodyOf(other).replace(/^\s+/, "");
|
|
24172
|
+
if (body === "") continue;
|
|
24173
|
+
if (merged.trim() === "") {
|
|
24174
|
+
merged += body;
|
|
24175
|
+
continue;
|
|
24176
|
+
}
|
|
24177
|
+
const separator = merged.endsWith("\n\n") ? "" : merged.endsWith("\n") ? "\n" : "\n\n";
|
|
24178
|
+
merged += separator + body;
|
|
24179
|
+
}
|
|
24180
|
+
return merged;
|
|
24181
|
+
}
|
|
24182
|
+
//#endregion
|
|
24007
24183
|
//#region ../client/src/lib/storage/server/server-document-store.ts
|
|
24008
24184
|
/**
|
|
24009
24185
|
* ServerDocumentStore (plan Phase 3 Task 6): the DocumentStore seam over the E2EE sync
|
|
@@ -24021,6 +24197,12 @@ function createServerDocumentStore(graph, options) {
|
|
|
24021
24197
|
const ENGINE_BATCH = 100;
|
|
24022
24198
|
/** Longest a cold-empty document holds its loading state waiting for first catchup. */
|
|
24023
24199
|
const COLD_CONTENT_TIMEOUT_MS = 15e3;
|
|
24200
|
+
/**
|
|
24201
|
+
* Longest a create waits, while connected, for the registry (and a journal's own document) to
|
|
24202
|
+
* catch up before it checks the name again (ADR 0157). Long enough for a reconnect's first
|
|
24203
|
+
* pages, short enough that a slow relay does not hold up a person who is typing.
|
|
24204
|
+
*/
|
|
24205
|
+
const CREATE_CONFIRM_MS = 5e3;
|
|
24024
24206
|
const breathe = createBreather();
|
|
24025
24207
|
const changeListeners = /* @__PURE__ */ new Set();
|
|
24026
24208
|
const docsChangedListeners = /* @__PURE__ */ new Set();
|
|
@@ -24029,6 +24211,16 @@ function createServerDocumentStore(graph, options) {
|
|
|
24029
24211
|
/** docId to the name it had when first seen renamed from elsewhere, and its latest name. */
|
|
24030
24212
|
const renamesFromElsewhere = /* @__PURE__ */ new Map();
|
|
24031
24213
|
const resurrectionClaims = /* @__PURE__ */ new Map();
|
|
24214
|
+
/**
|
|
24215
|
+
* Journal entries this device started before its registry had caught up (ADR 0157). A derived
|
|
24216
|
+
* id can already be a registry key that another device set and then deleted while this one was
|
|
24217
|
+
* away. Two concurrent sets of one Y.Map key keep one of them, and when the kept one is the
|
|
24218
|
+
* deleted one, this device's entry goes too: its text would be in the document but no name
|
|
24219
|
+
* would reach it. A claim sets the entry again when that happens, until the registry has
|
|
24220
|
+
* caught up once with this device connected, after which a delete is someone's decision.
|
|
24221
|
+
*/
|
|
24222
|
+
const creationClaims = /* @__PURE__ */ new Map();
|
|
24223
|
+
const stopClaimWatches = /* @__PURE__ */ new Set();
|
|
24032
24224
|
let disposed = false;
|
|
24033
24225
|
/** Whether the encrypted registry has been read to its terminal relay page this session. */
|
|
24034
24226
|
let registryConfirmed = false;
|
|
@@ -24114,10 +24306,21 @@ function createServerDocumentStore(graph, options) {
|
|
|
24114
24306
|
/**
|
|
24115
24307
|
* Every name a document answers to, to its docId. A page's own name outranks another page's
|
|
24116
24308
|
* alias of the same name, whatever order the registry lists them in, as the index resolves it.
|
|
24309
|
+
*
|
|
24310
|
+
* A name that more than one document answers to (a [[Duplicate Name]], ADR 0157) keeps the
|
|
24311
|
+
* document it opened before, while that document still answers to it. The registry lists a key
|
|
24312
|
+
* that arrived by sync last, so without this a duplicate arriving would move every open tab
|
|
24313
|
+
* and link of the name onto the other device's document, and two devices would each open the
|
|
24314
|
+
* other's. A name moves only when its document leaves it: deleted, merged or renamed away.
|
|
24117
24315
|
*/
|
|
24118
|
-
function currentDocIds() {
|
|
24316
|
+
function currentDocIds(previous) {
|
|
24119
24317
|
const out = /* @__PURE__ */ new Map();
|
|
24120
|
-
registry.forEach((entry, docId) =>
|
|
24318
|
+
registry.forEach((entry, docId) => {
|
|
24319
|
+
const key = conceptKey(conceptOf(entry));
|
|
24320
|
+
const held = out.get(key);
|
|
24321
|
+
if (held !== void 0 && previous?.get(key) === held) return;
|
|
24322
|
+
out.set(key, docId);
|
|
24323
|
+
});
|
|
24121
24324
|
registry.forEach((entry, docId) => {
|
|
24122
24325
|
for (const alias of entry.aliases ?? []) {
|
|
24123
24326
|
const key = conceptKey(alias);
|
|
@@ -24156,7 +24359,7 @@ function createServerDocumentStore(graph, options) {
|
|
|
24156
24359
|
const next = currentConcepts();
|
|
24157
24360
|
for (const [key, concept] of known) if (!next.has(key) && !renamedAway.has(key)) removedListeners.forEach((l) => l(concept));
|
|
24158
24361
|
known = next;
|
|
24159
|
-
docIdsByConcept = currentDocIds();
|
|
24362
|
+
docIdsByConcept = currentDocIds(docIdsByConcept);
|
|
24160
24363
|
knownRegistry = nextRegistry;
|
|
24161
24364
|
docsChangedListeners.forEach((l) => l());
|
|
24162
24365
|
if (!restored) {
|
|
@@ -24177,6 +24380,16 @@ function createServerDocumentStore(graph, options) {
|
|
|
24177
24380
|
}
|
|
24178
24381
|
for (const [docId, change] of event.changes.keys) {
|
|
24179
24382
|
if (change.action !== "delete") continue;
|
|
24383
|
+
const created = creationClaims.get(docId);
|
|
24384
|
+
if (created) queueMicrotask(() => {
|
|
24385
|
+
if (registry.has(docId) || !creationClaims.has(docId)) return;
|
|
24386
|
+
const engine = graph.docSync(docId);
|
|
24387
|
+
if (engine.lifecycle() === "deleting" || engine.doc.getText("content").length === 0) {
|
|
24388
|
+
creationClaims.delete(docId);
|
|
24389
|
+
return;
|
|
24390
|
+
}
|
|
24391
|
+
registry.set(docId, created);
|
|
24392
|
+
});
|
|
24180
24393
|
const claimed = resurrectionClaims.get(docId);
|
|
24181
24394
|
if (!claimed) continue;
|
|
24182
24395
|
queueMicrotask(() => {
|
|
@@ -24391,7 +24604,7 @@ function createServerDocumentStore(graph, options) {
|
|
|
24391
24604
|
registryConfirmed = true;
|
|
24392
24605
|
}
|
|
24393
24606
|
known = currentConcepts();
|
|
24394
|
-
docIdsByConcept = currentDocIds();
|
|
24607
|
+
docIdsByConcept = currentDocIds(docIdsByConcept);
|
|
24395
24608
|
knownRegistry = currentRegistryIdentities();
|
|
24396
24609
|
},
|
|
24397
24610
|
listDocuments() {
|
|
@@ -24415,6 +24628,9 @@ function createServerDocumentStore(graph, options) {
|
|
|
24415
24628
|
pages.sort((a, b) => a.concept.localeCompare(b.concept));
|
|
24416
24629
|
return [...journals, ...pages];
|
|
24417
24630
|
},
|
|
24631
|
+
documentIdOf(target) {
|
|
24632
|
+
return docIdFor(target);
|
|
24633
|
+
},
|
|
24418
24634
|
listIdentities() {
|
|
24419
24635
|
const out = [];
|
|
24420
24636
|
registry.forEach((entry, docId) => {
|
|
@@ -24685,26 +24901,83 @@ function createServerDocumentStore(graph, options) {
|
|
|
24685
24901
|
materialised.release();
|
|
24686
24902
|
}
|
|
24687
24903
|
},
|
|
24904
|
+
async mergeDuplicates(docIds, expected, mergeOptions) {
|
|
24905
|
+
const timeoutMs = mergeOptions?.timeoutMs ?? COLD_CONTENT_TIMEOUT_MS;
|
|
24906
|
+
const [first, ...others] = docIds;
|
|
24907
|
+
if (!first || others.length === 0 || !oneName(docIds)) return "gone";
|
|
24908
|
+
if (!graph.isConnected()) return "offline";
|
|
24909
|
+
if (!await confirmRegistry(timeoutMs)) return "unconfirmed";
|
|
24910
|
+
const materialised = await materialise(docIds, timeoutMs);
|
|
24911
|
+
try {
|
|
24912
|
+
if (!oneName(docIds)) return "gone";
|
|
24913
|
+
if (materialised.unconfirmed.length > 0) return "unconfirmed";
|
|
24914
|
+
const texts = docIds.map((id) => graph.docSync(id).doc.getText("content").toString());
|
|
24915
|
+
if (texts.some((text) => documentProtection(text).kind === "document")) return "protected";
|
|
24916
|
+
if (docIds.some((id, index) => texts[index] !== expected.get(id))) return "changed";
|
|
24917
|
+
const firstText = texts[0];
|
|
24918
|
+
const addition = mergedText(["", ...texts.slice(1)]);
|
|
24919
|
+
if (addition !== "" && !firstText.endsWith(addition)) {
|
|
24920
|
+
const merged = mergedText(texts);
|
|
24921
|
+
const ytext = graph.docSync(first).doc.getText("content");
|
|
24922
|
+
ytext.doc.transact(() => ytext.insert(firstText.length, merged.slice(firstText.length)), STORE);
|
|
24923
|
+
}
|
|
24924
|
+
const firstEntry = registry.get(first);
|
|
24925
|
+
const aliases = others.reduce((union, id) => mergeDocuments({
|
|
24926
|
+
body: "",
|
|
24927
|
+
aliases: union
|
|
24928
|
+
}, {
|
|
24929
|
+
body: "",
|
|
24930
|
+
aliases: registry.get(id)?.aliases ?? []
|
|
24931
|
+
}).aliases, firstEntry.aliases ?? []);
|
|
24932
|
+
if (aliases.length !== (firstEntry.aliases ?? []).length) writeAliases(first, firstEntry, aliases);
|
|
24933
|
+
if (!await acknowledged(first, timeoutMs)) return "not-acknowledged";
|
|
24934
|
+
if (others.filter((id, index) => graph.docSync(id).doc.getText("content").toString() !== texts[index + 1]).length > 0 || !oneName(docIds)) return "changed-after-append";
|
|
24935
|
+
for (const id of others) {
|
|
24936
|
+
creationClaims.delete(id);
|
|
24937
|
+
await graph.deleteDoc(id);
|
|
24938
|
+
registry.delete(id);
|
|
24939
|
+
}
|
|
24940
|
+
return "merged";
|
|
24941
|
+
} finally {
|
|
24942
|
+
materialised.release();
|
|
24943
|
+
}
|
|
24944
|
+
},
|
|
24688
24945
|
async reconcile() {},
|
|
24689
24946
|
async resolveConflict() {},
|
|
24690
|
-
async createJournal(date, body = "") {
|
|
24947
|
+
async createJournal(date, body = "", createOptions) {
|
|
24691
24948
|
const day = date.trim();
|
|
24692
24949
|
if (!isJournalConcept(day)) throw new Error(`"${date}" is not a calendar day.`);
|
|
24950
|
+
await confirmBeforeCreate();
|
|
24693
24951
|
if (docIdFor(day)) throw new Error(`A document for "${day}" already exists`);
|
|
24694
|
-
const docId =
|
|
24695
|
-
|
|
24952
|
+
const docId = await graph.journalDocId(day);
|
|
24953
|
+
const engine = graph.docSync(docId);
|
|
24954
|
+
await graph.whenReady(docId);
|
|
24955
|
+
const connected = graph.isConnected();
|
|
24956
|
+
const caughtUp = connected && await reachedWithin(graph.caughtUpDoc(docId), CREATE_CONFIRM_MS);
|
|
24957
|
+
if (docIdFor(day)) throw new Error(`A document for "${day}" already exists`);
|
|
24958
|
+
const entry = {
|
|
24696
24959
|
kind: "journal",
|
|
24697
24960
|
date: day
|
|
24698
|
-
}
|
|
24699
|
-
|
|
24700
|
-
|
|
24701
|
-
|
|
24702
|
-
|
|
24961
|
+
};
|
|
24962
|
+
registry.set(docId, entry);
|
|
24963
|
+
engine.claimCreation();
|
|
24964
|
+
if (!caughtUp) holdCreationClaim(docId, entry);
|
|
24965
|
+
const ytext = engine.doc.getText("content");
|
|
24966
|
+
if (!connected && ytext.length > 0) ytext.doc.transact(() => ytext.delete(0, ytext.length), STORE);
|
|
24967
|
+
else if (connected && !caughtUp && ytext.length > 0) console.warn(`[sync] "${day}" was started before its document caught up; its earlier text is kept`);
|
|
24968
|
+
const template = createOptions?.template ?? "";
|
|
24969
|
+
if (template !== "" && body.startsWith(template)) {
|
|
24970
|
+
await seedTemplate(docId, template);
|
|
24971
|
+
const rest = body.slice(template.length);
|
|
24972
|
+
if (rest !== "") ytext.doc.transact(() => ytext.insert(template.length, rest));
|
|
24973
|
+
} else if (body !== "") ytext.doc.transact(() => ytext.insert(0, body));
|
|
24703
24974
|
return day;
|
|
24704
24975
|
},
|
|
24705
24976
|
async createPage(title, body = "") {
|
|
24706
24977
|
if (isJournalConcept(title)) throw new Error(dayIsNotAPageName(title));
|
|
24707
24978
|
if (docIdFor(title)) throw new Error(`A page for "${title}" already exists`);
|
|
24979
|
+
await confirmBeforeCreate();
|
|
24980
|
+
if (docIdFor(title)) throw new Error(`A page for "${title}" already exists`);
|
|
24708
24981
|
const docId = crypto.randomUUID();
|
|
24709
24982
|
registry.set(docId, {
|
|
24710
24983
|
kind: "page",
|
|
@@ -24791,6 +25064,7 @@ function createServerDocumentStore(graph, options) {
|
|
|
24791
25064
|
async deleteDocument(concept) {
|
|
24792
25065
|
const docId = docIdFor(concept);
|
|
24793
25066
|
if (!docId) return;
|
|
25067
|
+
creationClaims.delete(docId);
|
|
24794
25068
|
await graph.deleteDoc(docId);
|
|
24795
25069
|
registry.delete(docId);
|
|
24796
25070
|
},
|
|
@@ -24800,10 +25074,90 @@ function createServerDocumentStore(graph, options) {
|
|
|
24800
25074
|
unobserveDocUpdates();
|
|
24801
25075
|
for (const stop of resurrectors.values()) stop();
|
|
24802
25076
|
resurrectors.clear();
|
|
25077
|
+
for (const stop of stopClaimWatches) stop();
|
|
25078
|
+
stopClaimWatches.clear();
|
|
24803
25079
|
await graph.flushAll();
|
|
24804
25080
|
graph.dispose();
|
|
24805
25081
|
}
|
|
24806
25082
|
};
|
|
25083
|
+
/**
|
|
25084
|
+
* Confirm before create (ADR 0157): while connected, wait (bounded) for the registry's current
|
|
25085
|
+
* catch-up, so a name another device created moments ago is seen before a second document is
|
|
25086
|
+
* made for it. A reconnect starts a new catch-up cycle a microtask after the socket opens,
|
|
25087
|
+
* which the yield lets begin. Offline nothing can arrive, and the create goes ahead at once.
|
|
25088
|
+
*/
|
|
25089
|
+
async function confirmBeforeCreate() {
|
|
25090
|
+
if (!graph.isConnected()) return;
|
|
25091
|
+
await Promise.resolve();
|
|
25092
|
+
await reachedWithin(graph.rootCaughtUp(), CREATE_CONFIRM_MS);
|
|
25093
|
+
}
|
|
25094
|
+
/**
|
|
25095
|
+
* Seed a [[Journal Template]] as one Yjs update whose client id is derived from the document,
|
|
25096
|
+
* its generation and the template's text (ADR 0157). Two devices that start the day with the
|
|
25097
|
+
* same template produce the same struct, which the CRDT keeps once. The generation keeps a day
|
|
25098
|
+
* started again after a delete from reusing the struct the deleted generation held. If the
|
|
25099
|
+
* struct is already here and deleted (a device's cached copy of that generation), it adds
|
|
25100
|
+
* nothing, and the template is inserted as an ordinary edit instead.
|
|
25101
|
+
*/
|
|
25102
|
+
async function seedTemplate(docId, template) {
|
|
25103
|
+
const engine = graph.docSync(docId);
|
|
25104
|
+
const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(`${docId}\n${engine.generation()}\n${template}`)));
|
|
25105
|
+
const seed = new Y.Doc();
|
|
25106
|
+
seed.clientID = new DataView(digest.buffer).getUint32(0);
|
|
25107
|
+
seed.getText("content").insert(0, template);
|
|
25108
|
+
Y.applyUpdate(engine.doc, Y.encodeStateAsUpdate(seed), STORE);
|
|
25109
|
+
seed.destroy();
|
|
25110
|
+
const ytext = engine.doc.getText("content");
|
|
25111
|
+
if (!ytext.toString().startsWith(template)) ytext.doc.transact(() => ytext.insert(0, template), STORE);
|
|
25112
|
+
}
|
|
25113
|
+
/** Whether every one of `docIds` is still in the registry under one name. */
|
|
25114
|
+
function oneName(docIds) {
|
|
25115
|
+
const keys = /* @__PURE__ */ new Set();
|
|
25116
|
+
for (const id of docIds) {
|
|
25117
|
+
const entry = registry.get(id);
|
|
25118
|
+
if (!entry) return false;
|
|
25119
|
+
keys.add(conceptKey(conceptOf(entry)));
|
|
25120
|
+
}
|
|
25121
|
+
return keys.size === 1;
|
|
25122
|
+
}
|
|
25123
|
+
/**
|
|
25124
|
+
* Send a document's pending edits and wait, bounded, until the relay has acknowledged every
|
|
25125
|
+
* one of them: what a Merge waits for before it deletes anything. False when it has not.
|
|
25126
|
+
*/
|
|
25127
|
+
async function acknowledged(docId, timeoutMs) {
|
|
25128
|
+
const engine = graph.docSync(docId);
|
|
25129
|
+
await engine.flush();
|
|
25130
|
+
const deadline = Date.now() + timeoutMs;
|
|
25131
|
+
while (engine.unsentOperations() > 0) {
|
|
25132
|
+
if (disposed || Date.now() >= deadline) return false;
|
|
25133
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
25134
|
+
}
|
|
25135
|
+
return true;
|
|
25136
|
+
}
|
|
25137
|
+
/**
|
|
25138
|
+
* Keep a creation claim until the registry has caught up once with this device connected
|
|
25139
|
+
* (`creationClaims`). Every concurrent set this device's entry could lose to is then here.
|
|
25140
|
+
*/
|
|
25141
|
+
function holdCreationClaim(docId, entry) {
|
|
25142
|
+
creationClaims.set(docId, entry);
|
|
25143
|
+
let released = false;
|
|
25144
|
+
const release = () => {
|
|
25145
|
+
if (released) return;
|
|
25146
|
+
released = true;
|
|
25147
|
+
stop();
|
|
25148
|
+
stopClaimWatches.delete(stop);
|
|
25149
|
+
(async () => {
|
|
25150
|
+
await Promise.resolve();
|
|
25151
|
+
await graph.rootCaughtUp();
|
|
25152
|
+
if (creationClaims.get(docId) === entry) creationClaims.delete(docId);
|
|
25153
|
+
})().catch(() => {});
|
|
25154
|
+
};
|
|
25155
|
+
const stop = graph.onActivity((activity) => {
|
|
25156
|
+
if (activity.connection === "open") release();
|
|
25157
|
+
});
|
|
25158
|
+
stopClaimWatches.add(stop);
|
|
25159
|
+
if (graph.isConnected()) release();
|
|
25160
|
+
}
|
|
24807
25161
|
async function confirmRegistry(timeoutMs = 5e3) {
|
|
24808
25162
|
if (registryConfirmed) return true;
|
|
24809
25163
|
if (!graph.isConnected()) return false;
|
|
@@ -25185,7 +25539,7 @@ async function openHeadlessGraph(deps) {
|
|
|
25185
25539
|
continue;
|
|
25186
25540
|
}
|
|
25187
25541
|
if (documentProtection(read.text).kind === "document") continue;
|
|
25188
|
-
bodies.set(identity.concept, bodyOf(read.text).body);
|
|
25542
|
+
bodies.set(identity.concept, bodyOf$1(read.text).body);
|
|
25189
25543
|
}
|
|
25190
25544
|
return {
|
|
25191
25545
|
bodies,
|