@bendyline/docblocks 2.7.2 → 2.7.4

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.
Files changed (52) hide show
  1. package/THIRD_PARTY_NOTICES.txt +4 -4
  2. package/dist/{capabilities-BvUC17t9.d.ts → capabilities-BJEkIsO1.d.ts} +9 -4
  3. package/dist/{chunk-KQ6756AJ.js → chunk-3GSMJ4MQ.js} +1 -1
  4. package/dist/{chunk-YWYT6K6J.js → chunk-3LXXRIO2.js} +1 -1
  5. package/dist/{chunk-STUIS5UZ.js → chunk-7LPIGCBG.js} +17 -3
  6. package/dist/{chunk-UK7R3RC7.js → chunk-BC42VRYW.js} +1 -1
  7. package/dist/{chunk-IGB2VSRE.js → chunk-FIEMYP2N.js} +2 -2
  8. package/dist/{chunk-M5ZNQGR5.js → chunk-IYKGM3PV.js} +1 -1
  9. package/dist/{chunk-6UPO2GE6.js → chunk-K5HRMSXD.js} +3 -3
  10. package/dist/{chunk-NM5KF6YF.js → chunk-KXQDMZFH.js} +9 -1
  11. package/dist/{chunk-UDEWLHQI.js → chunk-LDAG5NTQ.js} +3 -3
  12. package/dist/{chunk-7SN7YQL4.js → chunk-LJLQYXVL.js} +99 -3
  13. package/dist/{chunk-L657M3XG.js → chunk-PSGAZKWO.js} +2 -2
  14. package/dist/{chunk-HKOOHM67.js → chunk-Q3OFZWJS.js} +3 -3
  15. package/dist/{chunk-AYWKIZLD.js → chunk-UTH3HYCZ.js} +5 -1
  16. package/dist/{chunk-QS5WTLUZ.js → chunk-XI2LYBRR.js} +167 -0
  17. package/dist/document/index.d.ts +2 -2
  18. package/dist/document/index.js +3 -3
  19. package/dist/filesystem/electron.d.ts +5 -5
  20. package/dist/filesystem/electron.js +4 -4
  21. package/dist/filesystem/host.d.ts +4 -4
  22. package/dist/filesystem/host.js +4 -4
  23. package/dist/filesystem/index.d.ts +27 -8
  24. package/dist/filesystem/index.js +22 -12
  25. package/dist/filesystem/indexeddb.d.ts +2 -2
  26. package/dist/filesystem/indexeddb.js +4 -4
  27. package/dist/filesystem/memory.d.ts +2 -2
  28. package/dist/filesystem/memory.js +4 -4
  29. package/dist/filesystem/native.d.ts +2 -2
  30. package/dist/filesystem/native.js +4 -4
  31. package/dist/{filesystem-v2-C8AdSXUN.d.ts → filesystem-v2-CXdAPR27.d.ts} +1 -1
  32. package/dist/host/index.d.ts +5 -5
  33. package/dist/host/index.js +1 -1
  34. package/dist/host/mobile.d.ts +2 -2
  35. package/dist/host/mobile.js +1 -1
  36. package/dist/{host-provider-v2-CV0joXaL.d.ts → host-provider-v2-DEWMPiRD.d.ts} +2 -2
  37. package/dist/index.d.ts +8 -7
  38. package/dist/index.js +23 -13
  39. package/dist/mcp/index.js +2 -2
  40. package/dist/mcp/zod.js +2 -2
  41. package/dist/reconcile-electron-Bz-iAY_B.d.ts +142 -0
  42. package/dist/{types-BvDnr3YU.d.ts → types-DLteNrK0.d.ts} +3 -1
  43. package/dist/{v2-BVnZZyd4.d.ts → v2-DwxWjFC0.d.ts} +12 -1
  44. package/dist/vscode/index.d.ts +28 -2
  45. package/dist/vscode/index.js +21 -3
  46. package/dist/workspace/index.d.ts +41 -141
  47. package/dist/workspace/index.js +16 -1
  48. package/dist/workspace-index/index.d.ts +3 -1
  49. package/dist/workspace-index/index.js +18 -2
  50. package/dist/workspace-settings/index.d.ts +1 -1
  51. package/dist/workspace-settings/index.js +3 -3
  52. package/package.json +2 -2
@@ -14,7 +14,7 @@ This notice is included in the published npm tarball. Transitive packages instal
14
14
 
15
15
  | Package | Version | License | Source |
16
16
  | --- | --- | --- | --- |
17
- | @bendyline/squisq | 2.12.0 | MIT | https://github.com/bendyline/squisq |
17
+ | @bendyline/squisq | 2.12.5 | MIT | https://github.com/bendyline/squisq |
18
18
  | @types/debug | 4.1.12 | MIT | https://github.com/DefinitelyTyped/DefinitelyTyped |
19
19
  | @types/hast | 3.0.4 | MIT | https://github.com/DefinitelyTyped/DefinitelyTyped |
20
20
  | @types/katex | 0.16.8 | MIT | https://github.com/DefinitelyTyped/DefinitelyTyped |
@@ -132,7 +132,7 @@ This notice is included in the published npm tarball. Transitive packages instal
132
132
  Package-local license, copying, and notice files are reproduced below. Where a published package omits its repository license, the reviewed aggregate or README identified in `Source files` supplies the retained material.
133
133
 
134
134
  ==============================================================================
135
- Components: @bendyline/squisq@2.12.0
135
+ Components: @bendyline/squisq@2.12.5
136
136
  Source files: node_modules/@bendyline/squisq/LICENSE
137
137
  ==============================================================================
138
138
  MIT License
@@ -158,7 +158,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
158
158
  SOFTWARE.
159
159
 
160
160
  ==============================================================================
161
- Components: @bendyline/squisq@2.12.0
161
+ Components: @bendyline/squisq@2.12.5
162
162
  Source files: node_modules/@bendyline/squisq/NOTICE.md
163
163
  ==============================================================================
164
164
  # Third-Party Notices for @bendyline/squisq
@@ -189,7 +189,7 @@ third-party license texts for bundled code and data are shipped in
189
189
  THIRD_PARTY_LICENSES.txt.
190
190
 
191
191
  ==============================================================================
192
- Components: @bendyline/squisq@2.12.0
192
+ Components: @bendyline/squisq@2.12.5
193
193
  Source files: node_modules/@bendyline/squisq/THIRD_PARTY_LICENSES.txt
194
194
  ==============================================================================
195
195
  THIRD-PARTY LICENSES FOR @bendyline/squisq
@@ -1,5 +1,5 @@
1
- import { F as FileCommitResult, c as FileSystemEntry, b as FileMeta } from './types-BvDnr3YU.js';
2
- import { D as DocBlocksHostFsV2API } from './filesystem-v2-C8AdSXUN.js';
1
+ import { F as FileCommitResult, c as FileSystemEntry, b as FileMeta } from './types-DLteNrK0.js';
2
+ import { D as DocBlocksHostFsV2API } from './filesystem-v2-CXdAPR27.js';
3
3
 
4
4
  /** Provider-neutral catalog management. IDs never authorize filesystem access. */
5
5
  interface AiKnowledgeCatalog {
@@ -190,9 +190,11 @@ interface AiChatMessage {
190
190
  * What the request is for.
191
191
  *
192
192
  * This exists so the host can apply different budgets to a person waiting on a
193
- * draft and a background review pass. It is never sent to the model.
193
+ * draft and a background review pass. It is never sent to the model. Only
194
+ * `write` may spend its output budget reasoning; `study` (summaries, quizzes
195
+ * and flashcards, made in many small calls with explicit caps) never does.
194
196
  */
195
- type AiChatPurpose = 'write' | 'review' | 'illustrate' | 'chat';
197
+ type AiChatPurpose = 'write' | 'review' | 'illustrate' | 'study' | 'chat';
196
198
  interface AiChatRequest {
197
199
  readonly messages: readonly AiChatMessage[];
198
200
  /** Opaque provider model id. Omitted means "the host's current choice". */
@@ -890,6 +892,8 @@ interface HostExportTargetGrant {
890
892
  }
891
893
  /** Native export target selection and binary file writing. */
892
894
  interface DocBlocksHostExportAPI {
895
+ /** Open a successfully saved grant in its default app; Markdown opens in DocBlocks. */
896
+ openSaved?(documentId: string, grantId: string): Promise<void>;
893
897
  /** A share-sheet outcome is never a durable export target. Android can only report presentation. */
894
898
  share?(filename: string, data: ArrayBuffer | Uint8Array): Promise<'shared' | 'presented' | 'cancelled'>;
895
899
  /** Resolve an exact remembered grant or a display-only host suggestion. */
@@ -1186,6 +1190,7 @@ interface HostCapabilities {
1186
1190
  readonly exportDestinations: boolean;
1187
1191
  /** Remembered, re-resolvable export targets (`resolveTarget`/`pickTarget`). */
1188
1192
  readonly exportRememberedTargets: boolean;
1193
+ readonly openSavedExports: boolean;
1189
1194
  readonly externalResources: boolean;
1190
1195
  readonly openRequests: boolean;
1191
1196
  readonly menuCommands: boolean;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  workspacePathDirname
3
- } from "./chunk-AYWKIZLD.js";
3
+ } from "./chunk-UTH3HYCZ.js";
4
4
 
5
5
  // src/filesystem/internal/parent-chain.ts
6
6
  function parentChain(path) {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  tryParseWorkspacePath
3
- } from "./chunk-AYWKIZLD.js";
3
+ } from "./chunk-UTH3HYCZ.js";
4
4
 
5
5
  // src/mcp/types.ts
6
6
  var DOCBLOCKS_MCP_WIRE_VERSION = 1;
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  FILE_SYSTEM_TRANSFER_LIMITS,
3
3
  maybeGetDocBlocksHost
4
- } from "./chunk-NM5KF6YF.js";
4
+ } from "./chunk-KXQDMZFH.js";
5
5
  import {
6
6
  parseFileSystemCapabilities
7
7
  } from "./chunk-245W57QP.js";
@@ -26,11 +26,12 @@ import {
26
26
  WORKSPACE_ROOT,
27
27
  deserializeFsError,
28
28
  fsErrorFromUnknown,
29
+ isFsErrorCode,
29
30
  isSerializedFsError,
30
31
  parseWorkspacePath,
31
32
  workspacePathBasename,
32
33
  workspacePathDirname
33
- } from "./chunk-AYWKIZLD.js";
34
+ } from "./chunk-UTH3HYCZ.js";
34
35
 
35
36
  // src/filesystem/host-provider-v2.ts
36
37
  var fallbackInstanceSequence = 0;
@@ -419,7 +420,13 @@ function normalizeEntry(entry) {
419
420
  if (!isNonNegativeSafeInteger(entry.size)) {
420
421
  throw new TypeError("Filesystem file size must be a non-negative safe integer.");
421
422
  }
422
- return Object.freeze({ ...common, kind: "file", size: entry.size });
423
+ const readIssue = normalizeReadIssue(entry.readIssue);
424
+ return Object.freeze({
425
+ ...common,
426
+ kind: "file",
427
+ size: entry.size,
428
+ ...readIssue ? { readIssue } : {}
429
+ });
423
430
  }
424
431
  if (entry.kind === "directory") {
425
432
  if (entry.size !== null) throw new TypeError("Filesystem directory size must be null.");
@@ -427,6 +434,13 @@ function normalizeEntry(entry) {
427
434
  }
428
435
  throw new TypeError("Unknown filesystem entry kind.");
429
436
  }
437
+ function normalizeReadIssue(value) {
438
+ if (value === void 0) return void 0;
439
+ if (!isRecord(value) || !isFsErrorCode(value.code) || !isBoundedString(value.message, HOST_WIRE_LIMITS.messageCharacters, 1)) {
440
+ throw new TypeError("Filesystem entry read issue is malformed.");
441
+ }
442
+ return Object.freeze({ code: value.code, message: value.message });
443
+ }
430
444
  function normalizeDirectoryListing(value, parent) {
431
445
  if (!Array.isArray(value)) throw new TypeError("Expected a filesystem directory listing.");
432
446
  const entries = value.map(normalizeEntry);
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseWorkspacePath
3
- } from "./chunk-AYWKIZLD.js";
3
+ } from "./chunk-UTH3HYCZ.js";
4
4
 
5
5
  // src/workspace-settings/settings.ts
6
6
  var WORKSPACE_SETTINGS_DIRECTORY = ".docblocks";
@@ -3,11 +3,11 @@ import {
3
3
  } from "./chunk-XAMILHQP.js";
4
4
  import {
5
5
  decodeUtf8Text
6
- } from "./chunk-M5ZNQGR5.js";
6
+ } from "./chunk-IYKGM3PV.js";
7
7
  import {
8
8
  FsError,
9
9
  parseWorkspacePath
10
- } from "./chunk-AYWKIZLD.js";
10
+ } from "./chunk-UTH3HYCZ.js";
11
11
 
12
12
  // src/filesystem/move-error.ts
13
13
  function locationFromPresence(source, destination) {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  FsError
3
- } from "./chunk-AYWKIZLD.js";
3
+ } from "./chunk-UTH3HYCZ.js";
4
4
 
5
5
  // src/filesystem/utf8.ts
6
6
  function decodeUtf8Text(data, options = {}) {
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-DZB666V7.js";
4
4
  import {
5
5
  parentChain
6
- } from "./chunk-KQ6756AJ.js";
6
+ } from "./chunk-3GSMJ4MQ.js";
7
7
  import {
8
8
  bytesEqual,
9
9
  compareSnapshotsByName,
@@ -15,7 +15,7 @@ import {
15
15
  } from "./chunk-LPIJ6HKL.js";
16
16
  import {
17
17
  decodeUtf8Text
18
- } from "./chunk-M5ZNQGR5.js";
18
+ } from "./chunk-IYKGM3PV.js";
19
19
  import {
20
20
  FsError,
21
21
  WORKSPACE_ROOT,
@@ -25,7 +25,7 @@ import {
25
25
  workspacePathContains,
26
26
  workspacePathDirname,
27
27
  workspacePathJoin
28
- } from "./chunk-AYWKIZLD.js";
28
+ } from "./chunk-UTH3HYCZ.js";
29
29
 
30
30
  // src/filesystem/native-provider-v2.ts
31
31
  var NativeFileSystemMoveRecoveryError = class extends FsError {
@@ -27,6 +27,7 @@ var NO_CAPABILITIES = Object.freeze({
27
27
  clipboard: false,
28
28
  exportDestinations: false,
29
29
  exportRememberedTargets: false,
30
+ openSavedExports: false,
30
31
  externalResources: false,
31
32
  openRequests: false,
32
33
  menuCommands: false,
@@ -103,6 +104,7 @@ function deriveHostCapabilities(host) {
103
104
  clipboard: hasMethod(host.clipboard, "writeText"),
104
105
  exportDestinations: hasMethod(exports, "save"),
105
106
  exportRememberedTargets: hasMethod(exports, "resolveTarget") && hasMethod(exports, "pickTarget"),
107
+ openSavedExports: hasMethod(exports, "openSaved"),
106
108
  externalResources: hasMethod(host.external, "readText"),
107
109
  openRequests: hasMethod(host, "onOpenRequest"),
108
110
  menuCommands: hasMethod(host, "onMenuCommand"),
@@ -148,7 +150,13 @@ var AI_WIRE_LIMITS = Object.freeze({
148
150
  downloadBytesCeiling: Number.MAX_SAFE_INTEGER
149
151
  });
150
152
  var CHAT_ROLES = /* @__PURE__ */ new Set(["system", "user", "assistant"]);
151
- var CHAT_PURPOSES = /* @__PURE__ */ new Set(["write", "review", "illustrate", "chat"]);
153
+ var CHAT_PURPOSES = /* @__PURE__ */ new Set([
154
+ "write",
155
+ "review",
156
+ "illustrate",
157
+ "study",
158
+ "chat"
159
+ ]);
152
160
  var REVIEW_MODES = /* @__PURE__ */ new Set(["off", "explicit", "implicit"]);
153
161
  var FINISH_REASONS = /* @__PURE__ */ new Set(["stop", "length", "cancelled"]);
154
162
  var UNAVAILABLE_REASONS = /* @__PURE__ */ new Set([
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parentChainWithRoot
3
- } from "./chunk-KQ6756AJ.js";
3
+ } from "./chunk-3GSMJ4MQ.js";
4
4
  import {
5
5
  compareSnapshotsByName,
6
6
  compareText,
@@ -11,7 +11,7 @@ import {
11
11
  } from "./chunk-LPIJ6HKL.js";
12
12
  import {
13
13
  decodeUtf8Text
14
- } from "./chunk-M5ZNQGR5.js";
14
+ } from "./chunk-IYKGM3PV.js";
15
15
  import {
16
16
  FsError,
17
17
  WORKSPACE_ROOT,
@@ -19,7 +19,7 @@ import {
19
19
  workspacePathBasename,
20
20
  workspacePathContains,
21
21
  workspacePathDirname
22
- } from "./chunk-AYWKIZLD.js";
22
+ } from "./chunk-UTH3HYCZ.js";
23
23
 
24
24
  // src/filesystem/memory-provider-v2.ts
25
25
  var MEMORY_CAPABILITIES = Object.freeze({
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  guessMimeType
3
- } from "./chunk-HKOOHM67.js";
3
+ } from "./chunk-Q3OFZWJS.js";
4
4
  import {
5
5
  bytesEqual,
6
6
  compareSnapshotsByName,
@@ -17,7 +17,7 @@ import {
17
17
  createFileSystemDocumentTarget,
18
18
  describeEntryMove,
19
19
  describePartialMove
20
- } from "./chunk-IGB2VSRE.js";
20
+ } from "./chunk-FIEMYP2N.js";
21
21
  import {
22
22
  getFileSystemProviderV2
23
23
  } from "./chunk-XAMILHQP.js";
@@ -31,7 +31,98 @@ import {
31
31
  workspacePathDirname,
32
32
  workspacePathJoin,
33
33
  workspacePathToLegacy
34
- } from "./chunk-AYWKIZLD.js";
34
+ } from "./chunk-UTH3HYCZ.js";
35
+
36
+ // src/filesystem/wiki-links.ts
37
+ var markdownModule = null;
38
+ function loadMarkdown() {
39
+ markdownModule ?? (markdownModule = import("@bendyline/squisq/markdown"));
40
+ return markdownModule;
41
+ }
42
+ async function suggestWikiArticles(files, directories, documentPath, query, extension = ".md", explicitExtensions = false) {
43
+ const { wikiLinkPath } = await loadMarkdown();
44
+ if (query.length > 2048 || query && !wikiLinkPath(`${query.trimEnd()}x`)) return [];
45
+ const source = parseWorkspacePath(documentPath);
46
+ const parent = workspacePathDirname(source);
47
+ const rootPrefix = parent ? `${parent}/` : "";
48
+ const slash = query.lastIndexOf("/");
49
+ const prefix = query.slice(0, slash + 1);
50
+ const needle = query.slice(slash + 1).toLowerCase();
51
+ const candidates = [];
52
+ const relative = (path) => {
53
+ if (!path.startsWith(rootPrefix)) return null;
54
+ const target = path.slice(rootPrefix.length);
55
+ if (!target.startsWith(prefix)) return null;
56
+ const name = target.slice(prefix.length);
57
+ return name && !name.includes("/") && name.toLowerCase().includes(needle) ? target : null;
58
+ };
59
+ for (const directory of directories) {
60
+ const target = relative(directory);
61
+ if (target && target.length < 2048 && wikiLinkPath(target))
62
+ candidates.push({
63
+ target: `${target}/`,
64
+ label: `${target.slice(prefix.length)}/`,
65
+ kind: "folder"
66
+ });
67
+ }
68
+ const articlePaths = new Set(files.map((file) => file.path));
69
+ for (const file of files) {
70
+ if (file.path === source || !/\.(?:md|html?)$/i.test(file.path)) continue;
71
+ const target = relative(file.path);
72
+ if (!target || !wikiLinkPath(target)) continue;
73
+ const stem = file.path.replace(/\.(?:md|html?)$/i, "");
74
+ const preferred = [`${stem}${extension}`, `${stem}.md`, `${stem}.html`, `${stem}.htm`].find(
75
+ (path) => articlePaths.has(path)
76
+ );
77
+ const insertion = !explicitExtensions && file.path === preferred ? target.replace(/\.(?:md|html?)$/i, "") : target;
78
+ candidates.push({ target: insertion, label: insertion.slice(prefix.length), kind: "article" });
79
+ }
80
+ return candidates.sort(
81
+ (a, b) => Number(b.kind === "folder") - Number(a.kind === "folder") || Number(b.label.toLowerCase().startsWith(needle)) - Number(a.label.toLowerCase().startsWith(needle)) || a.label.localeCompare(b.label) || a.target.localeCompare(b.target)
82
+ ).slice(0, 20);
83
+ }
84
+ async function resolveWikiArticlePath(documentPath, target, extension = ".md") {
85
+ const { wikiLinkPath } = await loadMarkdown();
86
+ const relative = wikiLinkPath(target, extension);
87
+ if (!relative) throw new FsError("invalid-path", "Invalid wiki article name.");
88
+ return workspacePathJoin(workspacePathDirname(parseWorkspacePath(documentPath)), relative);
89
+ }
90
+ async function findWikiArticle(provider, documentPath, target, extension = ".md") {
91
+ const { wikiLinkPath } = await loadMarkdown();
92
+ const preferred = await resolveWikiArticlePath(documentPath, target, extension);
93
+ const candidates = /\.(?:md|html?)$/i.test(target) ? [target] : [.../* @__PURE__ */ new Set([`${target}${extension}`, `${target}.md`, `${target}.html`, `${target}.htm`])];
94
+ for (const relativePath of candidates) {
95
+ const path = await resolveWikiArticlePath(documentPath, relativePath, extension);
96
+ if (await wikiArticleExists(provider, path)) return { path, relativePath, exists: true };
97
+ }
98
+ return { path: preferred, relativePath: wikiLinkPath(target, extension), exists: false };
99
+ }
100
+ async function wikiArticleExists(provider, path) {
101
+ const v2 = getFileSystemProviderV2(provider);
102
+ if (!v2) return await provider.stat(path) !== null;
103
+ const entry = await v2.stat(path);
104
+ if (entry && entry.kind !== "file")
105
+ throw new FsError("type-mismatch", "The article path is a folder.");
106
+ return entry !== null;
107
+ }
108
+ async function ensureWikiArticle(provider, path) {
109
+ if (await wikiArticleExists(provider, path)) return false;
110
+ const v2 = getFileSystemProviderV2(provider);
111
+ if (!v2) throw new FsError("not-supported", "This workspace cannot create articles safely.");
112
+ try {
113
+ await v2.writeFile(path, new Uint8Array(), {
114
+ mode: "create",
115
+ expectedVersion: null,
116
+ createParents: true
117
+ });
118
+ return true;
119
+ } catch (error) {
120
+ if (error instanceof FsError && (error.code === "already-exists" || error.code === "conflict")) {
121
+ if (await wikiArticleExists(provider, path)) return false;
122
+ }
123
+ throw error;
124
+ }
125
+ }
35
126
 
36
127
  // src/filesystem/legacy-v2-adapter.ts
37
128
  var EPOCH = (/* @__PURE__ */ new Date(0)).toISOString();
@@ -1104,6 +1195,11 @@ function createFileMediaProvider(container, markdownBasename) {
1104
1195
  }
1105
1196
 
1106
1197
  export {
1198
+ suggestWikiArticles,
1199
+ resolveWikiArticlePath,
1200
+ findWikiArticle,
1201
+ wikiArticleExists,
1202
+ ensureWikiArticle,
1107
1203
  LegacyFileSystemProviderV2Adapter,
1108
1204
  rewriteCompanionReferences,
1109
1205
  documentCompanionPath,
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  HostFileSystemProviderV2
3
- } from "./chunk-STUIS5UZ.js";
3
+ } from "./chunk-7LPIGCBG.js";
4
4
  import {
5
5
  ELECTRON_FILE_SYSTEM_V2_CAPABILITIES,
6
6
  maybeGetDocBlocksHost
7
- } from "./chunk-NM5KF6YF.js";
7
+ } from "./chunk-KXQDMZFH.js";
8
8
 
9
9
  // src/filesystem/electron-provider-v2.ts
10
10
  var ElectronFileSystemProviderV2 = class extends HostFileSystemProviderV2 {
@@ -7,7 +7,7 @@ import {
7
7
  } from "./chunk-DZB666V7.js";
8
8
  import {
9
9
  parentChainWithRoot
10
- } from "./chunk-KQ6756AJ.js";
10
+ } from "./chunk-3GSMJ4MQ.js";
11
11
  import {
12
12
  isRecord
13
13
  } from "./chunk-73OOROXH.js";
@@ -22,7 +22,7 @@ import {
22
22
  } from "./chunk-LPIJ6HKL.js";
23
23
  import {
24
24
  decodeUtf8Text
25
- } from "./chunk-M5ZNQGR5.js";
25
+ } from "./chunk-IYKGM3PV.js";
26
26
  import {
27
27
  FsError,
28
28
  WORKSPACE_ROOT,
@@ -31,7 +31,7 @@ import {
31
31
  workspacePathBasename,
32
32
  workspacePathContains,
33
33
  workspacePathDirname
34
- } from "./chunk-AYWKIZLD.js";
34
+ } from "./chunk-UTH3HYCZ.js";
35
35
 
36
36
  // src/filesystem/indexeddb-provider-v2.ts
37
37
  var ENTRY_SCHEMA_VERSION = 1;
@@ -17,6 +17,9 @@ var FS_ERROR_CODES = /* @__PURE__ */ new Set([
17
17
  "aborted",
18
18
  "io"
19
19
  ]);
20
+ function isFsErrorCode(value) {
21
+ return typeof value === "string" && FS_ERROR_CODES.has(value);
22
+ }
20
23
  var FS_OPERATIONS = /* @__PURE__ */ new Set([
21
24
  "parse-path",
22
25
  "read",
@@ -109,7 +112,7 @@ function mapNodeErrorCodeToFsErrorCode(code) {
109
112
  function isSerializedFsError(value) {
110
113
  if (!value || typeof value !== "object") return false;
111
114
  const candidate = value;
112
- return candidate.name === "FsError" && typeof candidate.code === "string" && FS_ERROR_CODES.has(candidate.code) && typeof candidate.message === "string" && (candidate.operation === null || typeof candidate.operation === "string" && FS_OPERATIONS.has(candidate.operation)) && (candidate.path === null || typeof candidate.path === "string") && (candidate.destinationPath === null || typeof candidate.destinationPath === "string") && typeof candidate.retryable === "boolean";
115
+ return candidate.name === "FsError" && isFsErrorCode(candidate.code) && typeof candidate.message === "string" && (candidate.operation === null || typeof candidate.operation === "string" && FS_OPERATIONS.has(candidate.operation)) && (candidate.path === null || typeof candidate.path === "string") && (candidate.destinationPath === null || typeof candidate.destinationPath === "string") && typeof candidate.retryable === "boolean";
113
116
  }
114
117
  function deserializeFsError(value) {
115
118
  return new FsError(value.code, value.message, {
@@ -205,6 +208,7 @@ function invalidPath(path, message) {
205
208
  }
206
209
 
207
210
  export {
211
+ isFsErrorCode,
208
212
  FsError,
209
213
  mapDomExceptionToFsErrorCode,
210
214
  mapNodeErrorCodeToFsErrorCode,
@@ -4,6 +4,166 @@ import {
4
4
  import {
5
5
  getFileSystemProviderV2
6
6
  } from "./chunk-XAMILHQP.js";
7
+ import {
8
+ parseWorkspacePath
9
+ } from "./chunk-UTH3HYCZ.js";
10
+
11
+ // src/workspace/backup.ts
12
+ var WORKSPACE_BACKUP_MANIFEST = "docblocks-backup.json";
13
+ var WORKSPACE_BACKUP_LIMITS = Object.freeze({
14
+ maxEntries: 1e4,
15
+ maxUncompressedBytes: 128 * 1024 * 1024,
16
+ maxEntryUncompressedBytes: 64 * 1024 * 1024,
17
+ maxCompressionRatio: 1e3
18
+ });
19
+ function exactRecord(value, keys) {
20
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
21
+ throw new Error("Invalid workspace backup metadata.");
22
+ }
23
+ const record = value;
24
+ if (Object.keys(record).length !== keys.length || keys.some((key) => !(key in record))) {
25
+ throw new Error("Unrecognized workspace backup metadata fields.");
26
+ }
27
+ return record;
28
+ }
29
+ function entryPath(value) {
30
+ if (typeof value !== "string" || value.length > 4096)
31
+ throw new Error("Invalid backup entry path.");
32
+ const path = parseWorkspacePath(value);
33
+ if (!path || path !== value)
34
+ throw new Error("Backup entries must have canonical relative paths.");
35
+ return path;
36
+ }
37
+ function parseWorkspaceBackupManifest(value) {
38
+ const record = exactRecord(value, ["format", "version", "workspaces"]);
39
+ if (record.format !== "docblocks-workspace-backup" || record.version !== 1) {
40
+ throw new Error("This workspace backup version is not supported.");
41
+ }
42
+ if (!Array.isArray(record.workspaces) || !record.workspaces.length || record.workspaces.length > 100) {
43
+ throw new Error("A backup must contain between 1 and 100 workspaces.");
44
+ }
45
+ let directoryCount = 0;
46
+ const workspaces = record.workspaces.map((value2, index) => {
47
+ const workspace = exactRecord(value2, ["name", "prefix", "directories"]);
48
+ if (typeof workspace.name !== "string" || !workspace.name.trim() || workspace.name.length > 1024) {
49
+ throw new Error("Invalid backup workspace name.");
50
+ }
51
+ if (workspace.prefix !== `workspaces/${index + 1}` || !Array.isArray(workspace.directories)) {
52
+ throw new Error("Invalid backup workspace location.");
53
+ }
54
+ directoryCount += workspace.directories.length;
55
+ if (directoryCount > WORKSPACE_BACKUP_LIMITS.maxEntries)
56
+ throw new Error("Too many backup directories.");
57
+ const directories = workspace.directories.map(entryPath);
58
+ if (new Set(directories).size !== directories.length)
59
+ throw new Error("Duplicate backup directories.");
60
+ return { name: workspace.name, prefix: workspace.prefix, directories };
61
+ });
62
+ return { format: "docblocks-workspace-backup", version: 1, workspaces };
63
+ }
64
+ async function appendWorkspaceBackup(container, provider, previous) {
65
+ const snapshot = await provider.snapshot();
66
+ const workspace = {
67
+ name: provider.label || "Workspace",
68
+ prefix: `workspaces/${previous.length + 1}`,
69
+ directories: snapshot.entries.filter((entry) => entry.kind === "directory" && entry.path !== "").map((entry) => entry.path)
70
+ };
71
+ const workspaces = [...previous, workspace];
72
+ parseWorkspaceBackupManifest({ format: "docblocks-workspace-backup", version: 1, workspaces });
73
+ for (const entry of snapshot.entries) {
74
+ if (entry.kind === "file") {
75
+ entryPath(entry.path);
76
+ if (entry.data.byteLength > WORKSPACE_BACKUP_LIMITS.maxEntryUncompressedBytes) {
77
+ throw new Error(`Backup file exceeds the 64 MiB restore limit: ${entry.path}`);
78
+ }
79
+ await container.writeFile(`${workspace.prefix}/${entry.path}`, entry.data);
80
+ }
81
+ }
82
+ return workspaces;
83
+ }
84
+ async function finishWorkspaceBackup(container, workspaces) {
85
+ const manifest = parseWorkspaceBackupManifest({
86
+ format: "docblocks-workspace-backup",
87
+ version: 1,
88
+ workspaces
89
+ });
90
+ const bytes = new TextEncoder().encode(JSON.stringify(manifest));
91
+ if (bytes.byteLength > 1024 * 1024) throw new Error("Workspace backup metadata is too large.");
92
+ await container.writeFile(WORKSPACE_BACKUP_MANIFEST, bytes);
93
+ await readWorkspaceBackup(container, "Workspace");
94
+ }
95
+ async function readWorkspaceBackup(container, legacyName) {
96
+ const entries = await container.listFiles();
97
+ if (entries.length > WORKSPACE_BACKUP_LIMITS.maxEntries)
98
+ throw new Error("Too many backup files.");
99
+ const paths = entries.map((entry) => entryPath(entry.path));
100
+ if (new Set(paths).size !== paths.length) throw new Error("Duplicate backup files.");
101
+ const archiveEntries = new Set(paths);
102
+ for (const path of paths) {
103
+ const parts = path.split("/");
104
+ while (parts.length > 1) {
105
+ parts.pop();
106
+ archiveEntries.add(`${parts.join("/")}/`);
107
+ }
108
+ }
109
+ if (archiveEntries.size > WORKSPACE_BACKUP_LIMITS.maxEntries)
110
+ throw new Error("Too many backup entries.");
111
+ const metadata = paths.includes(parseWorkspacePath(WORKSPACE_BACKUP_MANIFEST)) ? await container.readFile(WORKSPACE_BACKUP_MANIFEST) : null;
112
+ if (metadata && metadata.byteLength > 1024 * 1024)
113
+ throw new Error("Workspace backup metadata is too large.");
114
+ const manifest = metadata ? parseWorkspaceBackupManifest(
115
+ JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(metadata))
116
+ ) : null;
117
+ const workspaces = manifest?.workspaces ?? [{ name: legacyName, prefix: "", directories: [] }];
118
+ if (paths.length + workspaces.reduce((count, workspace) => count + workspace.directories.length, 0) > WORKSPACE_BACKUP_LIMITS.maxEntries) {
119
+ throw new Error("Too many backup files and directories.");
120
+ }
121
+ const result = workspaces.map((workspace) => ({
122
+ ...workspace,
123
+ files: []
124
+ }));
125
+ let total = metadata?.byteLength ?? 0;
126
+ for (const path of paths) {
127
+ if (manifest && path === WORKSPACE_BACKUP_MANIFEST) continue;
128
+ const index = manifest ? workspaces.findIndex((workspace) => path.startsWith(`${workspace.prefix}/`)) : 0;
129
+ if (index < 0) throw new Error(`Backup file is outside its workspace: ${path}`);
130
+ const relative = entryPath(manifest ? path.slice(workspaces[index].prefix.length + 1) : path);
131
+ const data = await container.readFile(path);
132
+ if (!data) throw new Error(`Backup file is missing: ${path}`);
133
+ total += data.byteLength;
134
+ if (data.byteLength > WORKSPACE_BACKUP_LIMITS.maxEntryUncompressedBytes || total > WORKSPACE_BACKUP_LIMITS.maxUncompressedBytes) {
135
+ throw new Error("Workspace backup exceeds the restore size limits.");
136
+ }
137
+ result[index].files.push({ path: relative, data });
138
+ }
139
+ for (const workspace of result) {
140
+ const files = new Set(workspace.files.map((file) => file.path));
141
+ for (const path of [...workspace.directories, ...files]) {
142
+ if (workspace.directories.includes(path) && files.has(path))
143
+ throw new Error(`Conflicting backup entry: ${path}`);
144
+ const parts = path.split("/");
145
+ while (parts.length > 1) {
146
+ parts.pop();
147
+ if (files.has(parseWorkspacePath(parts.join("/"))))
148
+ throw new Error(`Backup file is also a parent directory: ${path}`);
149
+ }
150
+ }
151
+ }
152
+ return result;
153
+ }
154
+ async function restoreWorkspaceBackup(workspace, provider) {
155
+ if ((await provider.readDirectory(parseWorkspacePath(""))).length)
156
+ throw new Error("Restore requires a new, empty workspace.");
157
+ for (const path of workspace.directories)
158
+ await provider.createDirectory(path, { createParents: true });
159
+ for (const file of workspace.files) {
160
+ await provider.writeFile(file.path, file.data, {
161
+ mode: "create",
162
+ createParents: true,
163
+ expectedVersion: null
164
+ });
165
+ }
166
+ }
7
167
 
8
168
  // src/workspace/workspace-schema.ts
9
169
  var MAX_IDENTIFIER_CHARACTERS = 256;
@@ -214,6 +374,13 @@ function reconcileElectronWorkspaceDescriptors(local, hostWorkspaces, platform,
214
374
  }
215
375
 
216
376
  export {
377
+ WORKSPACE_BACKUP_MANIFEST,
378
+ WORKSPACE_BACKUP_LIMITS,
379
+ parseWorkspaceBackupManifest,
380
+ appendWorkspaceBackup,
381
+ finishWorkspaceBackup,
382
+ readWorkspaceBackup,
383
+ restoreWorkspaceBackup,
217
384
  parsePersistedWorkspaceList,
218
385
  DEFAULT_WORKSPACE_ID,
219
386
  registerTransientWorkspace,
@@ -1,5 +1,5 @@
1
- import { d as FileSystemProvider } from '../types-BvDnr3YU.js';
2
- import '../v2-BVnZZyd4.js';
1
+ import { d as FileSystemProvider } from '../types-DLteNrK0.js';
2
+ import '../v2-DwxWjFC0.js';
3
3
  import '../workspace-path-CWVrcuPL.js';
4
4
 
5
5
  /**
@@ -9,10 +9,10 @@ import {
9
9
  DocumentSession,
10
10
  DocumentSessionConflictError,
11
11
  createFileSystemDocumentTarget
12
- } from "../chunk-IGB2VSRE.js";
12
+ } from "../chunk-FIEMYP2N.js";
13
13
  import "../chunk-XAMILHQP.js";
14
- import "../chunk-M5ZNQGR5.js";
15
- import "../chunk-AYWKIZLD.js";
14
+ import "../chunk-IYKGM3PV.js";
15
+ import "../chunk-UTH3HYCZ.js";
16
16
  export {
17
17
  DOCUMENT_RECOVERY_JOURNAL_SCHEMA_VERSION,
18
18
  DOCUMENT_RECOVERY_JOURNAL_STORAGE_KEY,