@bendyline/docblocks 2.6.2 → 2.7.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/THIRD_PARTY_NOTICES.txt +33 -33
- package/dist/capabilities-CdQAc745.d.ts +1233 -0
- package/dist/catalog-BQK05tne.d.ts +122 -0
- package/dist/chunk-245W57QP.js +31 -0
- package/dist/{chunk-2WDJ27EH.js → chunk-46AGYHDY.js} +8 -0
- package/dist/{chunk-4437YWLE.js → chunk-6UPO2GE6.js} +4 -2
- package/dist/{chunk-UMWCTEXW.js → chunk-7SN7YQL4.js} +211 -40
- package/dist/{chunk-OQCPBOQA.js → chunk-DAPFSEKY.js} +35 -0
- package/dist/{chunk-M62ZGJ5I.js → chunk-HABG2JM3.js} +0 -17
- package/dist/{chunk-YM7O3PWO.js → chunk-HKOOHM67.js} +20 -12
- package/dist/chunk-IDYE73TU.js +77 -0
- package/dist/{chunk-KLQKU74Q.js → chunk-IGB2VSRE.js} +124 -330
- package/dist/{chunk-5MJC4KOE.js → chunk-L6PRMR7T.js} +53 -101
- package/dist/chunk-LPIJ6HKL.js +19 -0
- package/dist/{chunk-SJBB5OD7.js → chunk-QS5WTLUZ.js} +10 -8
- package/dist/chunk-R2JX5N3F.js +382 -0
- package/dist/{chunk-ZO7G53QP.js → chunk-UDEWLHQI.js} +4 -2
- package/dist/chunk-UK7R3RC7.js +514 -0
- package/dist/chunk-XTNWCNSK.js +464 -0
- package/dist/chunk-YHSFRPBS.js +42 -0
- package/dist/chunk-YNUQG4GF.js +1173 -0
- package/dist/document/index.d.ts +31 -1
- package/dist/document/index.js +7 -6
- package/dist/filesystem/electron.d.ts +8 -35
- package/dist/filesystem/electron.js +7 -4
- package/dist/filesystem/host.d.ts +28 -0
- package/dist/filesystem/host.js +103 -0
- package/dist/filesystem/index.d.ts +52 -6
- package/dist/filesystem/index.js +36 -15
- package/dist/filesystem/indexeddb.d.ts +8 -1
- package/dist/filesystem/indexeddb.js +7 -6
- package/dist/filesystem/memory.d.ts +2 -1
- package/dist/filesystem/memory.js +3 -2
- package/dist/filesystem/native.d.ts +2 -1
- package/dist/filesystem/native.js +3 -2
- package/dist/filesystem-v2-C8AdSXUN.d.ts +74 -0
- package/dist/host/index.d.ts +127 -5
- package/dist/host/index.js +98 -4
- package/dist/host/mobile.d.ts +389 -0
- package/dist/host/mobile.js +315 -0
- package/dist/host-provider-v2-CyLTRKJF.d.ts +51 -0
- package/dist/index.d.ts +8 -5
- package/dist/index.js +151 -34
- package/dist/mcp/index.d.ts +2 -2
- package/dist/mcp/zod.d.ts +23 -1
- package/dist/mcp/zod.js +1 -0
- package/dist/settings-HrrCikVX.d.ts +170 -0
- package/dist/types-BvDnr3YU.d.ts +88 -0
- package/dist/{types-Oy9U5FST.d.ts → types-d0yvHUJz.d.ts} +6 -0
- package/dist/{types-Bb3yccTH.d.ts → v2-BVnZZyd4.d.ts} +1 -86
- package/dist/vscode/index.d.ts +43 -1
- package/dist/vscode/index.js +67 -1
- package/dist/workspace/index.d.ts +10 -6
- package/dist/workspace/index.js +4 -2
- package/dist/workspace-index/index.d.ts +240 -0
- package/dist/workspace-index/index.js +1287 -0
- package/dist/workspace-settings/index.d.ts +301 -0
- package/dist/workspace-settings/index.js +820 -0
- package/package.json +22 -2
- package/dist/chunk-KFWPPIJH.js +0 -45
- package/dist/chunk-QI6YKTQV.js +0 -110
- package/dist/types-DhLZZNEn.d.ts +0 -577
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
RawIndexedDBFileSystemStore,
|
|
3
|
+
indexedDBWorkspaceDatabaseName
|
|
4
|
+
} from "./chunk-DAPFSEKY.js";
|
|
1
5
|
import {
|
|
2
6
|
withFileCommitLock
|
|
3
7
|
} from "./chunk-DZB666V7.js";
|
|
@@ -11,9 +15,11 @@ import {
|
|
|
11
15
|
bytesEqual,
|
|
12
16
|
compareSnapshotsByName,
|
|
13
17
|
compareText,
|
|
14
|
-
copyBytes
|
|
18
|
+
copyBytes
|
|
19
|
+
} from "./chunk-HABG2JM3.js";
|
|
20
|
+
import {
|
|
15
21
|
parseFileSystemVersion
|
|
16
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-LPIJ6HKL.js";
|
|
17
23
|
import {
|
|
18
24
|
decodeUtf8Text
|
|
19
25
|
} from "./chunk-M5ZNQGR5.js";
|
|
@@ -26,10 +32,6 @@ import {
|
|
|
26
32
|
workspacePathContains,
|
|
27
33
|
workspacePathDirname
|
|
28
34
|
} from "./chunk-AYWKIZLD.js";
|
|
29
|
-
import {
|
|
30
|
-
RawIndexedDBFileSystemStore,
|
|
31
|
-
indexedDBWorkspaceDatabaseName
|
|
32
|
-
} from "./chunk-OQCPBOQA.js";
|
|
33
35
|
|
|
34
36
|
// src/filesystem/indexeddb-provider-v2.ts
|
|
35
37
|
var ENTRY_SCHEMA_VERSION = 1;
|
|
@@ -132,13 +134,10 @@ var IndexedDBFileSystemProviderV2 = class {
|
|
|
132
134
|
"Expected a directory but found a file."
|
|
133
135
|
);
|
|
134
136
|
}
|
|
135
|
-
const paths = await listEntryPaths(tx);
|
|
136
137
|
const children = [];
|
|
137
|
-
for (const candidate of
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
children.push(toEntrySnapshot(entry));
|
|
141
|
-
}
|
|
138
|
+
for (const candidate of await listChildPaths(tx, canonical)) {
|
|
139
|
+
const entry = await readRequiredEntry(tx, candidate);
|
|
140
|
+
children.push(toEntrySnapshot(entry));
|
|
142
141
|
}
|
|
143
142
|
children.sort(compareSnapshotsByName);
|
|
144
143
|
return Object.freeze(children);
|
|
@@ -960,6 +959,15 @@ function entryPathsFromKeys(keys) {
|
|
|
960
959
|
async function listEntryPaths(tx) {
|
|
961
960
|
return entryPathsFromKeys(await tx.keys());
|
|
962
961
|
}
|
|
962
|
+
async function listChildPaths(tx, directory) {
|
|
963
|
+
if (!tx.childKeys) {
|
|
964
|
+
return (await listEntryPaths(tx)).filter(
|
|
965
|
+
(candidate) => candidate !== "" && workspacePathDirname(candidate) === directory
|
|
966
|
+
);
|
|
967
|
+
}
|
|
968
|
+
const prefix = directory ? `${entryKey(directory)}/` : ENTRY_PREFIX;
|
|
969
|
+
return entryPathsFromKeys(await tx.childKeys(prefix));
|
|
970
|
+
}
|
|
963
971
|
async function readEntry(tx, path) {
|
|
964
972
|
const raw = await tx.get(entryKey(path));
|
|
965
973
|
return raw === null ? null : validateEntry(raw, path);
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import {
|
|
2
|
+
HostFileSystemProviderV2
|
|
3
|
+
} from "./chunk-L6PRMR7T.js";
|
|
4
|
+
import {
|
|
5
|
+
ELECTRON_FILE_SYSTEM_V2_CAPABILITIES,
|
|
6
|
+
maybeGetDocBlocksHost
|
|
7
|
+
} from "./chunk-YNUQG4GF.js";
|
|
8
|
+
|
|
9
|
+
// src/filesystem/electron-provider-v2.ts
|
|
10
|
+
var ElectronFileSystemProviderV2 = class extends HostFileSystemProviderV2 {
|
|
11
|
+
constructor(id, label, rootPath) {
|
|
12
|
+
super(id, label, rootPath, { capabilities: ELECTRON_FILE_SYSTEM_V2_CAPABILITIES });
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// src/filesystem/electron-provider.ts
|
|
17
|
+
function getHostFs() {
|
|
18
|
+
const host = maybeGetDocBlocksHost();
|
|
19
|
+
if (!host?.fs) {
|
|
20
|
+
throw new Error("ElectronFileSystemProvider: the host does not expose the legacy fs bridge.");
|
|
21
|
+
}
|
|
22
|
+
return host.fs;
|
|
23
|
+
}
|
|
24
|
+
var ElectronFileSystemProvider = class {
|
|
25
|
+
constructor(id, label, rootPath) {
|
|
26
|
+
this.id = id;
|
|
27
|
+
this.label = label;
|
|
28
|
+
this.rootPath = rootPath;
|
|
29
|
+
this.v2 = new ElectronFileSystemProviderV2(id, label, rootPath);
|
|
30
|
+
}
|
|
31
|
+
/** Absolute path this provider is rooted at. */
|
|
32
|
+
getRootPath() {
|
|
33
|
+
return this.rootPath;
|
|
34
|
+
}
|
|
35
|
+
readFile(path) {
|
|
36
|
+
return getHostFs().readFile(this.id, path);
|
|
37
|
+
}
|
|
38
|
+
writeFile(path, content) {
|
|
39
|
+
return getHostFs().writeFile(this.id, path, content);
|
|
40
|
+
}
|
|
41
|
+
commitFile(path, content, expectedContent) {
|
|
42
|
+
return getHostFs().commitFile(this.id, path, content, expectedContent);
|
|
43
|
+
}
|
|
44
|
+
delete(path) {
|
|
45
|
+
return getHostFs().delete(this.id, path);
|
|
46
|
+
}
|
|
47
|
+
rename(oldPath, newPath) {
|
|
48
|
+
return getHostFs().rename(this.id, oldPath, newPath);
|
|
49
|
+
}
|
|
50
|
+
readDirectory(path) {
|
|
51
|
+
return getHostFs().readDirectory(this.id, path);
|
|
52
|
+
}
|
|
53
|
+
exists(path) {
|
|
54
|
+
return getHostFs().exists(this.id, path);
|
|
55
|
+
}
|
|
56
|
+
createDirectory(path) {
|
|
57
|
+
return getHostFs().createDirectory(this.id, path);
|
|
58
|
+
}
|
|
59
|
+
stat(path) {
|
|
60
|
+
return getHostFs().stat(this.id, path);
|
|
61
|
+
}
|
|
62
|
+
readBinary(path) {
|
|
63
|
+
return getHostFs().readBinary(this.id, path);
|
|
64
|
+
}
|
|
65
|
+
writeBinary(path, data) {
|
|
66
|
+
return getHostFs().writeBinary(this.id, path, data);
|
|
67
|
+
}
|
|
68
|
+
/** Subscribe to external change notifications under this root. */
|
|
69
|
+
watch(onChange) {
|
|
70
|
+
return getHostFs().watch(this.id, onChange);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export {
|
|
75
|
+
ElectronFileSystemProviderV2,
|
|
76
|
+
ElectronFileSystemProvider
|
|
77
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
} from "./chunk-
|
|
2
|
+
getFileSystemProviderV2
|
|
3
|
+
} from "./chunk-XAMILHQP.js";
|
|
4
4
|
import {
|
|
5
5
|
decodeUtf8Text
|
|
6
6
|
} from "./chunk-M5ZNQGR5.js";
|
|
@@ -8,9 +8,100 @@ import {
|
|
|
8
8
|
FsError,
|
|
9
9
|
parseWorkspacePath
|
|
10
10
|
} from "./chunk-AYWKIZLD.js";
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
|
|
12
|
+
// src/filesystem/move-error.ts
|
|
13
|
+
function locationFromPresence(source, destination) {
|
|
14
|
+
if (source === "unknown" || destination === "unknown") return "unknown";
|
|
15
|
+
if (source === "present" && destination === "present") return "both";
|
|
16
|
+
if (source === "present") return "source";
|
|
17
|
+
if (destination === "present") return "destination";
|
|
18
|
+
return "neither";
|
|
19
|
+
}
|
|
20
|
+
var FileSystemMoveRecoveryError = class extends Error {
|
|
21
|
+
constructor(sourcePath, destinationPath, state, moveError) {
|
|
22
|
+
super(
|
|
23
|
+
`The move did not restore one authoritative path. Entry location: ${locationFromPresence(
|
|
24
|
+
state.source,
|
|
25
|
+
state.destination
|
|
26
|
+
)}.`
|
|
27
|
+
);
|
|
28
|
+
this.sourcePath = sourcePath;
|
|
29
|
+
this.destinationPath = destinationPath;
|
|
30
|
+
this.state = state;
|
|
31
|
+
this.moveError = moveError;
|
|
32
|
+
this.code = "partial-move";
|
|
33
|
+
this.name = "FileSystemMoveRecoveryError";
|
|
34
|
+
}
|
|
35
|
+
get location() {
|
|
36
|
+
return locationFromPresence(this.state.source, this.state.destination);
|
|
37
|
+
}
|
|
38
|
+
/** Alias used by DocumentSession's document-retarget recovery protocol. */
|
|
39
|
+
get documentLocation() {
|
|
40
|
+
return this.location;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
var FileSystemPartialMoveError = class extends Error {
|
|
44
|
+
constructor(sourcePath, destinationPath, companionSourcePath, companionDestinationPath, state, moveError, rollbackError) {
|
|
45
|
+
super(
|
|
46
|
+
`The document move could not be rolled back. Document location: ${locationFromPresence(
|
|
47
|
+
state.source,
|
|
48
|
+
state.destination
|
|
49
|
+
)}; companion location: ${locationFromPresence(
|
|
50
|
+
state.companionSource,
|
|
51
|
+
state.companionDestination
|
|
52
|
+
)}.`
|
|
53
|
+
);
|
|
54
|
+
this.sourcePath = sourcePath;
|
|
55
|
+
this.destinationPath = destinationPath;
|
|
56
|
+
this.companionSourcePath = companionSourcePath;
|
|
57
|
+
this.companionDestinationPath = companionDestinationPath;
|
|
58
|
+
this.state = state;
|
|
59
|
+
this.moveError = moveError;
|
|
60
|
+
this.rollbackError = rollbackError;
|
|
61
|
+
this.code = "partial-move";
|
|
62
|
+
this.name = "FileSystemPartialMoveError";
|
|
63
|
+
}
|
|
64
|
+
get documentLocation() {
|
|
65
|
+
return locationFromPresence(this.state.source, this.state.destination);
|
|
66
|
+
}
|
|
67
|
+
get companionLocation() {
|
|
68
|
+
return locationFromPresence(this.state.companionSource, this.state.companionDestination);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
function isFileSystemPartialMoveError(error) {
|
|
72
|
+
return error instanceof FileSystemPartialMoveError;
|
|
73
|
+
}
|
|
74
|
+
function isFileSystemMoveRecoveryError(error) {
|
|
75
|
+
return error instanceof FileSystemMoveRecoveryError;
|
|
76
|
+
}
|
|
77
|
+
function isFileSystemMoveStateError(error) {
|
|
78
|
+
return isFileSystemMoveRecoveryError(error) || isFileSystemPartialMoveError(error);
|
|
79
|
+
}
|
|
80
|
+
async function probePath(provider, path) {
|
|
81
|
+
try {
|
|
82
|
+
const providerV2 = getFileSystemProviderV2(provider);
|
|
83
|
+
const present = providerV2 ? await providerV2.stat(parseWorkspacePath(path)) !== null : await provider.exists(path);
|
|
84
|
+
return present ? "present" : "missing";
|
|
85
|
+
} catch {
|
|
86
|
+
return "unknown";
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async function describePartialMove(provider, sourcePath, destinationPath, companionSourcePath, companionDestinationPath) {
|
|
90
|
+
const [source, destination, companionSource, companionDestination] = await Promise.all([
|
|
91
|
+
probePath(provider, sourcePath),
|
|
92
|
+
probePath(provider, destinationPath),
|
|
93
|
+
probePath(provider, companionSourcePath),
|
|
94
|
+
probePath(provider, companionDestinationPath)
|
|
95
|
+
]);
|
|
96
|
+
return { source, destination, companionSource, companionDestination };
|
|
97
|
+
}
|
|
98
|
+
async function describeEntryMove(provider, sourcePath, destinationPath) {
|
|
99
|
+
const [source, destination] = await Promise.all([
|
|
100
|
+
probePath(provider, sourcePath),
|
|
101
|
+
probePath(provider, destinationPath)
|
|
102
|
+
]);
|
|
103
|
+
return { source, destination };
|
|
104
|
+
}
|
|
14
105
|
|
|
15
106
|
// src/document/document-session.ts
|
|
16
107
|
var DEFAULT_AUTO_SAVE_RETRY_DELAYS_MS = [1e3, 3e3, 1e4];
|
|
@@ -31,6 +122,7 @@ var DocumentSessionConflictError = class extends Error {
|
|
|
31
122
|
};
|
|
32
123
|
var DocumentSession = class {
|
|
33
124
|
constructor(options = {}) {
|
|
125
|
+
this.recoveredRecords = [];
|
|
34
126
|
this.listeners = /* @__PURE__ */ new Set();
|
|
35
127
|
this.target = null;
|
|
36
128
|
this.content = "";
|
|
@@ -60,7 +152,7 @@ var DocumentSession = class {
|
|
|
60
152
|
this.autoSaveRetryDelaysMs = normalizeRetryDelays(
|
|
61
153
|
options.autoSaveRetryDelaysMs ?? DEFAULT_AUTO_SAVE_RETRY_DELAYS_MS
|
|
62
154
|
);
|
|
63
|
-
this.recoveryJournal = options.recoveryJournal ?? null;
|
|
155
|
+
this.recoveryJournal = options.recoveryJournal?.forOwner(crypto.randomUUID()) ?? null;
|
|
64
156
|
this.snapshot = this.createSnapshot();
|
|
65
157
|
}
|
|
66
158
|
/** Enable or disable automatic persistence without weakening explicit or lifecycle flushes. */
|
|
@@ -281,6 +373,7 @@ var DocumentSession = class {
|
|
|
281
373
|
this.clearAutoSaveTimer();
|
|
282
374
|
this.haltDrain = true;
|
|
283
375
|
this.conflict = {
|
|
376
|
+
...this.conflict,
|
|
284
377
|
targetKey: change.targetKey,
|
|
285
378
|
localContent: this.content,
|
|
286
379
|
localRevision: this.revision,
|
|
@@ -541,6 +634,7 @@ var DocumentSession = class {
|
|
|
541
634
|
generation,
|
|
542
635
|
persistedRevision: this.persistedRevision
|
|
543
636
|
});
|
|
637
|
+
this.acknowledgeRecoveredRecords();
|
|
544
638
|
this.emit();
|
|
545
639
|
}
|
|
546
640
|
}
|
|
@@ -558,15 +652,16 @@ var DocumentSession = class {
|
|
|
558
652
|
* baseline becomes an explicit conflict and is never auto-overwritten.
|
|
559
653
|
*/
|
|
560
654
|
restoreRecoverySnapshot() {
|
|
655
|
+
this.recoveredRecords = [];
|
|
561
656
|
if (!this.target || !this.recoveryJournal) return "none";
|
|
562
|
-
const
|
|
657
|
+
const records = this.recoveryJournal.list(this.target.key);
|
|
658
|
+
const recovered = records[0];
|
|
563
659
|
if (!recovered) return "none";
|
|
660
|
+
this.recoveredRecords = records.filter(
|
|
661
|
+
(record) => record.content === recovered.content && record.persistedContent === recovered.persistedContent
|
|
662
|
+
);
|
|
564
663
|
if (recovered.content === this.content) {
|
|
565
|
-
this.
|
|
566
|
-
targetKey: recovered.targetKey,
|
|
567
|
-
generation: recovered.generation,
|
|
568
|
-
persistedRevision: recovered.revision
|
|
569
|
-
});
|
|
664
|
+
this.acknowledgeRecoveredRecords();
|
|
570
665
|
return "none";
|
|
571
666
|
}
|
|
572
667
|
const externalContent = this.content;
|
|
@@ -580,6 +675,10 @@ var DocumentSession = class {
|
|
|
580
675
|
this.persistedContent = recovered.persistedContent;
|
|
581
676
|
this.haltDrain = true;
|
|
582
677
|
this.conflict = {
|
|
678
|
+
recoveredDraft: true,
|
|
679
|
+
recoveredDraftCapturedAt: Math.min(
|
|
680
|
+
...this.recoveredRecords.map((record) => record.updatedAt)
|
|
681
|
+
),
|
|
583
682
|
targetKey: this.target.key,
|
|
584
683
|
localContent: recovered.content,
|
|
585
684
|
localRevision: this.revision,
|
|
@@ -603,6 +702,11 @@ var DocumentSession = class {
|
|
|
603
702
|
discardRecoverySnapshot() {
|
|
604
703
|
if (!this.target) return;
|
|
605
704
|
this.recoveryJournal?.discard(this.target.key);
|
|
705
|
+
this.acknowledgeRecoveredRecords();
|
|
706
|
+
}
|
|
707
|
+
acknowledgeRecoveredRecords() {
|
|
708
|
+
for (const record of this.recoveredRecords) this.recoveryJournal?.acknowledgeRecord(record);
|
|
709
|
+
this.recoveredRecords = [];
|
|
606
710
|
}
|
|
607
711
|
detach() {
|
|
608
712
|
this.clearAutoSaveTimer();
|
|
@@ -758,326 +862,16 @@ function createFileSystemDocumentTarget(provider, path, options = {}) {
|
|
|
758
862
|
};
|
|
759
863
|
}
|
|
760
864
|
|
|
761
|
-
// src/document/recovery-journal.ts
|
|
762
|
-
var DOCUMENT_RECOVERY_JOURNAL_STORAGE_KEY = "docblocks:document-recovery:v1";
|
|
763
|
-
var DOCUMENT_RECOVERY_JOURNAL_SCHEMA_VERSION = 1;
|
|
764
|
-
var DEFAULT_MAX_ENTRIES = 20;
|
|
765
|
-
var DEFAULT_MAX_STORAGE_BYTES = 2e6;
|
|
766
|
-
var DEFAULT_MAX_RECORD_BYTES = 1e6;
|
|
767
|
-
var DEFAULT_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
768
|
-
var DEFAULT_MAX_FUTURE_SKEW_MS = 5 * 60 * 1e3;
|
|
769
|
-
var MAX_TARGET_KEY_LENGTH = 4096;
|
|
770
|
-
var DocumentRecoveryJournal = class {
|
|
771
|
-
constructor(storage, options = {}) {
|
|
772
|
-
this.storage = storage;
|
|
773
|
-
this.storageKey = options.storageKey?.trim() || DOCUMENT_RECOVERY_JOURNAL_STORAGE_KEY;
|
|
774
|
-
this.maxEntries = positiveInteger(options.maxEntries, DEFAULT_MAX_ENTRIES);
|
|
775
|
-
this.maxStorageBytes = positiveInteger(options.maxStorageBytes, DEFAULT_MAX_STORAGE_BYTES);
|
|
776
|
-
this.maxRecordBytes = Math.min(
|
|
777
|
-
positiveInteger(options.maxRecordBytes, DEFAULT_MAX_RECORD_BYTES),
|
|
778
|
-
this.maxStorageBytes
|
|
779
|
-
);
|
|
780
|
-
this.maxAgeMs = nonNegativeFinite(options.maxAgeMs, DEFAULT_MAX_AGE_MS);
|
|
781
|
-
this.maxFutureSkewMs = nonNegativeFinite(options.maxFutureSkewMs, DEFAULT_MAX_FUTURE_SKEW_MS);
|
|
782
|
-
this.now = options.now ?? Date.now;
|
|
783
|
-
}
|
|
784
|
-
/**
|
|
785
|
-
* Synchronously persist the latest dirty snapshot for a target.
|
|
786
|
-
* Storage/quota failures are returned, never thrown into an editor onChange.
|
|
787
|
-
*/
|
|
788
|
-
write(input) {
|
|
789
|
-
const timestamp = this.now();
|
|
790
|
-
if (!isValidWrite(input) || !Number.isFinite(timestamp) || timestamp < 0) {
|
|
791
|
-
return { status: "rejected", reason: "invalid-record", evicted: 0 };
|
|
792
|
-
}
|
|
793
|
-
if (!this.storage) {
|
|
794
|
-
return { status: "rejected", reason: "storage-unavailable", evicted: 0 };
|
|
795
|
-
}
|
|
796
|
-
if (estimatedBytes(input.content) > this.maxRecordBytes || input.persistedContent !== null && estimatedBytes(input.persistedContent) > this.maxRecordBytes) {
|
|
797
|
-
return { status: "rejected", reason: "record-too-large", evicted: 0 };
|
|
798
|
-
}
|
|
799
|
-
const loaded = this.load(timestamp);
|
|
800
|
-
const existing = loaded.records.find((record) => record.targetKey === input.targetKey);
|
|
801
|
-
if (existing) {
|
|
802
|
-
const ordering = compareGenerationRevision(input, existing);
|
|
803
|
-
if (ordering < 0) return { status: "ignored-stale", evicted: 0 };
|
|
804
|
-
if (ordering === 0) {
|
|
805
|
-
if (existing.content === input.content && existing.persistedContent === input.persistedContent) {
|
|
806
|
-
return { status: "unchanged", evicted: 0 };
|
|
807
|
-
}
|
|
808
|
-
return { status: "rejected", reason: "invalid-record", evicted: 0 };
|
|
809
|
-
}
|
|
810
|
-
}
|
|
811
|
-
const candidate = {
|
|
812
|
-
targetKey: input.targetKey,
|
|
813
|
-
generation: input.generation,
|
|
814
|
-
revision: input.revision,
|
|
815
|
-
content: input.content,
|
|
816
|
-
persistedContent: input.persistedContent,
|
|
817
|
-
createdAt: existing?.createdAt ?? timestamp,
|
|
818
|
-
updatedAt: timestamp
|
|
819
|
-
};
|
|
820
|
-
if (estimatedBytes(JSON.stringify(candidate)) > this.maxRecordBytes) {
|
|
821
|
-
return { status: "rejected", reason: "record-too-large", evicted: 0 };
|
|
822
|
-
}
|
|
823
|
-
if (estimatedBytes(serializeEnvelope([candidate])) > this.maxStorageBytes) {
|
|
824
|
-
return { status: "rejected", reason: "record-too-large", evicted: 0 };
|
|
825
|
-
}
|
|
826
|
-
const next = loaded.records.filter((record) => record.targetKey !== input.targetKey);
|
|
827
|
-
next.push(candidate);
|
|
828
|
-
const persisted = this.persistBounded(next, input.targetKey);
|
|
829
|
-
if (!persisted.stored) {
|
|
830
|
-
return {
|
|
831
|
-
status: "rejected",
|
|
832
|
-
reason: persisted.reason ?? "quota-exceeded",
|
|
833
|
-
evicted: persisted.evicted
|
|
834
|
-
};
|
|
835
|
-
}
|
|
836
|
-
return { status: "stored", evicted: persisted.evicted };
|
|
837
|
-
}
|
|
838
|
-
/** Return the recoverable snapshot for one target, if present and valid. */
|
|
839
|
-
lookup(targetKey, generation) {
|
|
840
|
-
if (!isValidTargetKey(targetKey) || !isOptionalGeneration(generation)) return null;
|
|
841
|
-
const record = this.load(this.now()).records.find(
|
|
842
|
-
(candidate) => candidate.targetKey === targetKey && (generation === void 0 || candidate.generation === generation)
|
|
843
|
-
);
|
|
844
|
-
return record ? cloneRecord(record) : null;
|
|
845
|
-
}
|
|
846
|
-
/** List valid records, newest first. Optionally restrict to one target. */
|
|
847
|
-
list(targetKey) {
|
|
848
|
-
if (targetKey !== void 0 && !isValidTargetKey(targetKey)) return [];
|
|
849
|
-
return this.load(this.now()).records.filter((record) => targetKey === void 0 || record.targetKey === targetKey).sort(compareNewestFirst).map(cloneRecord);
|
|
850
|
-
}
|
|
851
|
-
/**
|
|
852
|
-
* Clear a record only when the same generation has durably acknowledged at
|
|
853
|
-
* least the journaled revision. A stale acknowledgement cannot erase a newer
|
|
854
|
-
* crash-recovery snapshot.
|
|
855
|
-
*/
|
|
856
|
-
acknowledge(input) {
|
|
857
|
-
if (!isValidAcknowledgement(input) || !this.storage) return false;
|
|
858
|
-
const records = this.load(this.now()).records;
|
|
859
|
-
const record = records.find((candidate) => candidate.targetKey === input.targetKey);
|
|
860
|
-
if (!record || record.generation !== input.generation || record.revision > input.persistedRevision) {
|
|
861
|
-
return false;
|
|
862
|
-
}
|
|
863
|
-
return this.persistExact(records.filter((candidate) => candidate !== record));
|
|
864
|
-
}
|
|
865
|
-
/** Explicitly discard recovery data after a user or lifecycle decision. */
|
|
866
|
-
discard(targetKey, generation) {
|
|
867
|
-
if (!isValidTargetKey(targetKey) || !isOptionalGeneration(generation) || !this.storage) {
|
|
868
|
-
return false;
|
|
869
|
-
}
|
|
870
|
-
const records = this.load(this.now()).records;
|
|
871
|
-
const next = records.filter(
|
|
872
|
-
(record) => record.targetKey !== targetKey || generation !== void 0 && record.generation !== generation
|
|
873
|
-
);
|
|
874
|
-
if (next.length === records.length) return false;
|
|
875
|
-
return this.persistExact(next);
|
|
876
|
-
}
|
|
877
|
-
load(timestamp) {
|
|
878
|
-
if (!this.storage) return { records: [], needsRepair: false };
|
|
879
|
-
let raw;
|
|
880
|
-
try {
|
|
881
|
-
raw = this.storage.getItem(this.storageKey);
|
|
882
|
-
} catch {
|
|
883
|
-
return { records: [], needsRepair: false };
|
|
884
|
-
}
|
|
885
|
-
if (raw === null) return { records: [], needsRepair: false };
|
|
886
|
-
let parsed;
|
|
887
|
-
try {
|
|
888
|
-
parsed = JSON.parse(raw);
|
|
889
|
-
} catch {
|
|
890
|
-
this.tryRemove();
|
|
891
|
-
return { records: [], needsRepair: true };
|
|
892
|
-
}
|
|
893
|
-
if (!isEnvelope(parsed)) {
|
|
894
|
-
this.tryRemove();
|
|
895
|
-
return { records: [], needsRepair: true };
|
|
896
|
-
}
|
|
897
|
-
const byTarget = /* @__PURE__ */ new Map();
|
|
898
|
-
let needsRepair = estimatedBytes(raw) > this.maxStorageBytes;
|
|
899
|
-
for (const candidate of parsed.records) {
|
|
900
|
-
if (!isValidStoredRecord(candidate, timestamp, this.maxAgeMs, this.maxFutureSkewMs)) {
|
|
901
|
-
needsRepair = true;
|
|
902
|
-
continue;
|
|
903
|
-
}
|
|
904
|
-
if (estimatedBytes(JSON.stringify(candidate)) > this.maxRecordBytes) {
|
|
905
|
-
needsRepair = true;
|
|
906
|
-
continue;
|
|
907
|
-
}
|
|
908
|
-
const current = byTarget.get(candidate.targetKey);
|
|
909
|
-
if (!current || compareStoredRecords(candidate, current) > 0) {
|
|
910
|
-
if (current) needsRepair = true;
|
|
911
|
-
byTarget.set(candidate.targetKey, cloneRecord(candidate));
|
|
912
|
-
} else {
|
|
913
|
-
needsRepair = true;
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
|
-
const records = [...byTarget.values()].sort(compareNewestFirst);
|
|
917
|
-
while (records.length > this.maxEntries) {
|
|
918
|
-
records.pop();
|
|
919
|
-
needsRepair = true;
|
|
920
|
-
}
|
|
921
|
-
while (records.length > 0 && estimatedBytes(serializeEnvelope(records)) > this.maxStorageBytes) {
|
|
922
|
-
records.pop();
|
|
923
|
-
needsRepair = true;
|
|
924
|
-
}
|
|
925
|
-
if (needsRepair) {
|
|
926
|
-
this.persistBounded(records);
|
|
927
|
-
}
|
|
928
|
-
return { records, needsRepair };
|
|
929
|
-
}
|
|
930
|
-
persistBounded(records, protectedTarget) {
|
|
931
|
-
if (!this.storage) {
|
|
932
|
-
return { stored: false, evicted: 0, reason: "storage-unavailable" };
|
|
933
|
-
}
|
|
934
|
-
const next = [...records].sort(compareNewestFirst);
|
|
935
|
-
let evicted = 0;
|
|
936
|
-
while (next.length > this.maxEntries) {
|
|
937
|
-
if (!evictOldest(next, protectedTarget)) break;
|
|
938
|
-
evicted += 1;
|
|
939
|
-
}
|
|
940
|
-
let serialized = serializeEnvelope(next);
|
|
941
|
-
while (estimatedBytes(serialized) > this.maxStorageBytes) {
|
|
942
|
-
if (!evictOldest(next, protectedTarget)) {
|
|
943
|
-
return { stored: false, evicted, reason: "quota-exceeded" };
|
|
944
|
-
}
|
|
945
|
-
evicted += 1;
|
|
946
|
-
serialized = serializeEnvelope(next);
|
|
947
|
-
}
|
|
948
|
-
for (; ; ) {
|
|
949
|
-
try {
|
|
950
|
-
this.storage.setItem(this.storageKey, serialized);
|
|
951
|
-
return { stored: true, evicted };
|
|
952
|
-
} catch {
|
|
953
|
-
if (!evictOldest(next, protectedTarget)) {
|
|
954
|
-
return { stored: false, evicted, reason: "quota-exceeded" };
|
|
955
|
-
}
|
|
956
|
-
evicted += 1;
|
|
957
|
-
serialized = serializeEnvelope(next);
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
}
|
|
961
|
-
persistExact(records) {
|
|
962
|
-
if (!this.storage) return false;
|
|
963
|
-
if (records.length === 0) {
|
|
964
|
-
try {
|
|
965
|
-
this.storage.removeItem(this.storageKey);
|
|
966
|
-
return true;
|
|
967
|
-
} catch {
|
|
968
|
-
return false;
|
|
969
|
-
}
|
|
970
|
-
}
|
|
971
|
-
try {
|
|
972
|
-
this.storage.setItem(this.storageKey, serializeEnvelope(records));
|
|
973
|
-
return true;
|
|
974
|
-
} catch {
|
|
975
|
-
return false;
|
|
976
|
-
}
|
|
977
|
-
}
|
|
978
|
-
tryRemove() {
|
|
979
|
-
try {
|
|
980
|
-
this.storage?.removeItem(this.storageKey);
|
|
981
|
-
} catch {
|
|
982
|
-
}
|
|
983
|
-
}
|
|
984
|
-
};
|
|
985
|
-
function getDefaultDocumentRecoveryStorage() {
|
|
986
|
-
try {
|
|
987
|
-
const candidate = Reflect.get(globalThis, "localStorage");
|
|
988
|
-
if (!isStorage(candidate)) return null;
|
|
989
|
-
return candidate;
|
|
990
|
-
} catch {
|
|
991
|
-
return null;
|
|
992
|
-
}
|
|
993
|
-
}
|
|
994
|
-
function serializeEnvelope(records) {
|
|
995
|
-
const envelope = {
|
|
996
|
-
schemaVersion: DOCUMENT_RECOVERY_JOURNAL_SCHEMA_VERSION,
|
|
997
|
-
records
|
|
998
|
-
};
|
|
999
|
-
return JSON.stringify(envelope);
|
|
1000
|
-
}
|
|
1001
|
-
function estimatedBytes(value) {
|
|
1002
|
-
return value.length * 2;
|
|
1003
|
-
}
|
|
1004
|
-
function positiveInteger(value, fallback) {
|
|
1005
|
-
return Number.isSafeInteger(value) && (value ?? 0) > 0 ? value : fallback;
|
|
1006
|
-
}
|
|
1007
|
-
function nonNegativeFinite(value, fallback) {
|
|
1008
|
-
return Number.isFinite(value) && (value ?? -1) >= 0 ? value : fallback;
|
|
1009
|
-
}
|
|
1010
|
-
function isEnvelope(value) {
|
|
1011
|
-
if (!isObject(value)) return false;
|
|
1012
|
-
return value.schemaVersion === DOCUMENT_RECOVERY_JOURNAL_SCHEMA_VERSION && Array.isArray(value.records);
|
|
1013
|
-
}
|
|
1014
|
-
function isValidStoredRecord(value, timestamp, maxAgeMs, maxFutureSkewMs) {
|
|
1015
|
-
if (!isObject(value)) return false;
|
|
1016
|
-
if (!isValidTargetKey(value.targetKey) || !isNonNegativeSafeInteger(value.generation) || !isNonNegativeSafeInteger(value.revision) || typeof value.content !== "string" || value.persistedContent !== null && typeof value.persistedContent !== "string" || !isNonNegativeFiniteNumber(value.createdAt) || !isNonNegativeFiniteNumber(value.updatedAt) || value.createdAt > value.updatedAt) {
|
|
1017
|
-
return false;
|
|
1018
|
-
}
|
|
1019
|
-
if (!Number.isFinite(timestamp) || timestamp < 0) return false;
|
|
1020
|
-
if (value.updatedAt > timestamp + maxFutureSkewMs) return false;
|
|
1021
|
-
return timestamp - value.updatedAt <= maxAgeMs;
|
|
1022
|
-
}
|
|
1023
|
-
function isValidWrite(value) {
|
|
1024
|
-
return isValidTargetKey(value.targetKey) && isNonNegativeSafeInteger(value.generation) && isNonNegativeSafeInteger(value.revision) && typeof value.content === "string" && (value.persistedContent === null || typeof value.persistedContent === "string");
|
|
1025
|
-
}
|
|
1026
|
-
function isValidAcknowledgement(value) {
|
|
1027
|
-
return isValidTargetKey(value.targetKey) && isNonNegativeSafeInteger(value.generation) && isNonNegativeSafeInteger(value.persistedRevision);
|
|
1028
|
-
}
|
|
1029
|
-
function isValidTargetKey(value) {
|
|
1030
|
-
return typeof value === "string" && value.length > 0 && value.length <= MAX_TARGET_KEY_LENGTH;
|
|
1031
|
-
}
|
|
1032
|
-
function isOptionalGeneration(value) {
|
|
1033
|
-
return value === void 0 || isNonNegativeSafeInteger(value);
|
|
1034
|
-
}
|
|
1035
|
-
function isNonNegativeSafeInteger(value) {
|
|
1036
|
-
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
1037
|
-
}
|
|
1038
|
-
function isNonNegativeFiniteNumber(value) {
|
|
1039
|
-
return typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
1040
|
-
}
|
|
1041
|
-
function isObject(value) {
|
|
1042
|
-
return typeof value === "object" && value !== null;
|
|
1043
|
-
}
|
|
1044
|
-
function isStorage(value) {
|
|
1045
|
-
if (!isObject(value)) return false;
|
|
1046
|
-
return typeof value.getItem === "function" && typeof value.setItem === "function" && typeof value.removeItem === "function";
|
|
1047
|
-
}
|
|
1048
|
-
function compareGenerationRevision(left, right) {
|
|
1049
|
-
if (left.generation !== right.generation) return left.generation - right.generation;
|
|
1050
|
-
return left.revision - right.revision;
|
|
1051
|
-
}
|
|
1052
|
-
function compareStoredRecords(left, right) {
|
|
1053
|
-
return compareGenerationRevision(left, right) || left.updatedAt - right.updatedAt;
|
|
1054
|
-
}
|
|
1055
|
-
function compareNewestFirst(left, right) {
|
|
1056
|
-
return right.updatedAt - left.updatedAt || right.revision - left.revision;
|
|
1057
|
-
}
|
|
1058
|
-
function cloneRecord(record) {
|
|
1059
|
-
return { ...record };
|
|
1060
|
-
}
|
|
1061
|
-
function evictOldest(records, protectedTarget) {
|
|
1062
|
-
let oldestIndex = -1;
|
|
1063
|
-
for (let index = 0; index < records.length; index += 1) {
|
|
1064
|
-
if (records[index].targetKey === protectedTarget) continue;
|
|
1065
|
-
if (oldestIndex < 0 || compareNewestFirst(records[index], records[oldestIndex]) > 0) {
|
|
1066
|
-
oldestIndex = index;
|
|
1067
|
-
}
|
|
1068
|
-
}
|
|
1069
|
-
if (oldestIndex < 0) return false;
|
|
1070
|
-
records.splice(oldestIndex, 1);
|
|
1071
|
-
return true;
|
|
1072
|
-
}
|
|
1073
|
-
|
|
1074
865
|
export {
|
|
866
|
+
FileSystemMoveRecoveryError,
|
|
867
|
+
FileSystemPartialMoveError,
|
|
868
|
+
isFileSystemPartialMoveError,
|
|
869
|
+
isFileSystemMoveRecoveryError,
|
|
870
|
+
isFileSystemMoveStateError,
|
|
871
|
+
describePartialMove,
|
|
872
|
+
describeEntryMove,
|
|
1075
873
|
DocumentCommitConflictError,
|
|
1076
874
|
DocumentSessionConflictError,
|
|
1077
875
|
DocumentSession,
|
|
1078
|
-
createFileSystemDocumentTarget
|
|
1079
|
-
DOCUMENT_RECOVERY_JOURNAL_STORAGE_KEY,
|
|
1080
|
-
DOCUMENT_RECOVERY_JOURNAL_SCHEMA_VERSION,
|
|
1081
|
-
DocumentRecoveryJournal,
|
|
1082
|
-
getDefaultDocumentRecoveryStorage
|
|
876
|
+
createFileSystemDocumentTarget
|
|
1083
877
|
};
|