@bendyline/docblocks 1.1.2 → 2.0.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/README.md +76 -11
- package/dist/chunk-2D7Y4IR3.js +1293 -0
- package/dist/chunk-2D7Y4IR3.js.map +1 -0
- package/dist/chunk-5MYAICRE.js +957 -0
- package/dist/chunk-5MYAICRE.js.map +1 -0
- package/dist/chunk-75UUF6C4.js +802 -0
- package/dist/chunk-75UUF6C4.js.map +1 -0
- package/dist/chunk-AEHCVWU5.js +25 -0
- package/dist/chunk-AEHCVWU5.js.map +1 -0
- package/dist/chunk-AYZ4HN5M.js +20 -0
- package/dist/chunk-AYZ4HN5M.js.map +1 -0
- package/dist/chunk-EGNDADXB.js +111 -0
- package/dist/chunk-EGNDADXB.js.map +1 -0
- package/dist/chunk-EZKJZUNJ.js +13 -0
- package/dist/chunk-EZKJZUNJ.js.map +1 -0
- package/dist/chunk-FNW556FN.js +75 -0
- package/dist/chunk-FNW556FN.js.map +1 -0
- package/dist/chunk-GCPBEMMH.js +563 -0
- package/dist/chunk-GCPBEMMH.js.map +1 -0
- package/dist/chunk-J32GU3F3.js +1664 -0
- package/dist/chunk-J32GU3F3.js.map +1 -0
- package/dist/chunk-PGR7LK6D.js +1062 -0
- package/dist/chunk-PGR7LK6D.js.map +1 -0
- package/dist/chunk-RQJRQF46.js +787 -0
- package/dist/chunk-RQJRQF46.js.map +1 -0
- package/dist/{chunk-ME76RUMR.js → chunk-RVQHY3XU.js} +15 -1
- package/dist/chunk-RVQHY3XU.js.map +1 -0
- package/dist/chunk-SI2LU2RP.js +224 -0
- package/dist/chunk-SI2LU2RP.js.map +1 -0
- package/dist/chunk-W5UUUII5.js +142 -0
- package/dist/chunk-W5UUUII5.js.map +1 -0
- package/dist/document/document-session.d.ts +127 -0
- package/dist/document/document-session.d.ts.map +1 -0
- package/dist/document/document-session.js +749 -0
- package/dist/document/document-session.js.map +1 -0
- package/dist/document/filesystem-target.d.ts +17 -0
- package/dist/document/filesystem-target.d.ts.map +1 -0
- package/dist/document/filesystem-target.js +70 -0
- package/dist/document/filesystem-target.js.map +1 -0
- package/dist/document/index.d.ts +6 -0
- package/dist/document/index.d.ts.map +1 -0
- package/dist/document/index.js +4 -0
- package/dist/document/index.js.map +1 -0
- package/dist/document/recovery-journal.d.ts +125 -0
- package/dist/document/recovery-journal.d.ts.map +1 -0
- package/dist/document/recovery-journal.js +385 -0
- package/dist/document/recovery-journal.js.map +1 -0
- package/dist/document/types.d.ts +89 -0
- package/dist/document/types.d.ts.map +1 -0
- package/dist/document/types.js +9 -0
- package/dist/document/types.js.map +1 -0
- package/dist/filesystem/commit-lock.d.ts +3 -0
- package/dist/filesystem/commit-lock.d.ts.map +1 -0
- package/dist/filesystem/commit-lock.js +20 -0
- package/dist/filesystem/commit-lock.js.map +1 -0
- package/dist/filesystem/dbk-workspace.d.ts +34 -0
- package/dist/filesystem/dbk-workspace.d.ts.map +1 -0
- package/dist/filesystem/dbk-workspace.js +85 -0
- package/dist/filesystem/dbk-workspace.js.map +1 -0
- package/dist/filesystem/electron-provider-v2.d.ts +36 -0
- package/dist/filesystem/electron-provider-v2.d.ts.map +1 -0
- package/dist/filesystem/electron-provider-v2.js +503 -0
- package/dist/filesystem/electron-provider-v2.js.map +1 -0
- package/dist/filesystem/electron-provider.d.ts +6 -3
- package/dist/filesystem/electron-provider.d.ts.map +1 -1
- package/dist/filesystem/electron-provider.js +18 -13
- package/dist/filesystem/electron-provider.js.map +1 -1
- package/dist/filesystem/electron.d.ts +4 -0
- package/dist/filesystem/electron.d.ts.map +1 -0
- package/dist/filesystem/electron.js +4 -0
- package/dist/filesystem/electron.js.map +1 -0
- package/dist/filesystem/filesystem-content-container.d.ts +1 -1
- package/dist/filesystem/filesystem-content-container.d.ts.map +1 -1
- package/dist/filesystem/filesystem-content-container.js +50 -13
- package/dist/filesystem/filesystem-content-container.js.map +1 -1
- package/dist/filesystem/fs-error.d.ts +46 -0
- package/dist/filesystem/fs-error.d.ts.map +1 -0
- package/dist/filesystem/fs-error.js +170 -0
- package/dist/filesystem/fs-error.js.map +1 -0
- package/dist/filesystem/index.d.ts +15 -1
- package/dist/filesystem/index.d.ts.map +1 -1
- package/dist/filesystem/index.js +13 -0
- package/dist/filesystem/index.js.map +1 -1
- package/dist/filesystem/indexeddb-content-container.d.ts +3 -1
- package/dist/filesystem/indexeddb-content-container.d.ts.map +1 -1
- package/dist/filesystem/indexeddb-content-container.js +18 -11
- package/dist/filesystem/indexeddb-content-container.js.map +1 -1
- package/dist/filesystem/indexeddb-provider-v2.d.ts +96 -0
- package/dist/filesystem/indexeddb-provider-v2.d.ts.map +1 -0
- package/dist/filesystem/indexeddb-provider-v2.js +1241 -0
- package/dist/filesystem/indexeddb-provider-v2.js.map +1 -0
- package/dist/filesystem/indexeddb-provider.d.ts +21 -14
- package/dist/filesystem/indexeddb-provider.d.ts.map +1 -1
- package/dist/filesystem/indexeddb-provider.js +111 -261
- package/dist/filesystem/indexeddb-provider.js.map +1 -1
- package/dist/filesystem/indexeddb-store.d.ts +34 -0
- package/dist/filesystem/indexeddb-store.d.ts.map +1 -0
- package/dist/filesystem/indexeddb-store.js +165 -0
- package/dist/filesystem/indexeddb-store.js.map +1 -0
- package/dist/filesystem/indexeddb.d.ts +5 -0
- package/dist/filesystem/indexeddb.d.ts.map +1 -0
- package/dist/filesystem/indexeddb.js +5 -0
- package/dist/filesystem/indexeddb.js.map +1 -0
- package/dist/filesystem/legacy-v2-adapter.d.ts +62 -0
- package/dist/filesystem/legacy-v2-adapter.d.ts.map +1 -0
- package/dist/filesystem/legacy-v2-adapter.js +499 -0
- package/dist/filesystem/legacy-v2-adapter.js.map +1 -0
- package/dist/filesystem/memory-provider-v2.d.ts +92 -0
- package/dist/filesystem/memory-provider-v2.d.ts.map +1 -0
- package/dist/filesystem/memory-provider-v2.js +615 -0
- package/dist/filesystem/memory-provider-v2.js.map +1 -0
- package/dist/filesystem/memory-provider.d.ts +54 -0
- package/dist/filesystem/memory-provider.d.ts.map +1 -0
- package/dist/filesystem/memory-provider.js +132 -0
- package/dist/filesystem/memory-provider.js.map +1 -0
- package/dist/filesystem/memory.d.ts +4 -0
- package/dist/filesystem/memory.d.ts.map +1 -0
- package/dist/filesystem/memory.js +4 -0
- package/dist/filesystem/memory.js.map +1 -0
- package/dist/filesystem/move-entry.d.ts +9 -0
- package/dist/filesystem/move-entry.d.ts.map +1 -0
- package/dist/filesystem/move-entry.js +103 -0
- package/dist/filesystem/move-entry.js.map +1 -0
- package/dist/filesystem/move-error.d.ts +53 -0
- package/dist/filesystem/move-error.d.ts.map +1 -0
- package/dist/filesystem/move-error.js +99 -0
- package/dist/filesystem/move-error.js.map +1 -0
- package/dist/filesystem/native-provider-v2.d.ts +75 -0
- package/dist/filesystem/native-provider-v2.d.ts.map +1 -0
- package/dist/filesystem/native-provider-v2.js +849 -0
- package/dist/filesystem/native-provider-v2.js.map +1 -0
- package/dist/filesystem/native-provider.d.ts +9 -1
- package/dist/filesystem/native-provider.d.ts.map +1 -1
- package/dist/filesystem/native-provider.js +172 -37
- package/dist/filesystem/native-provider.js.map +1 -1
- package/dist/filesystem/native.d.ts +4 -0
- package/dist/filesystem/native.d.ts.map +1 -0
- package/dist/filesystem/native.js +4 -0
- package/dist/filesystem/native.js.map +1 -0
- package/dist/filesystem/types.d.ts +37 -4
- package/dist/filesystem/types.d.ts.map +1 -1
- package/dist/filesystem/types.js +11 -3
- package/dist/filesystem/types.js.map +1 -1
- package/dist/filesystem/v2.d.ts +172 -0
- package/dist/filesystem/v2.d.ts.map +1 -0
- package/dist/filesystem/v2.js +16 -0
- package/dist/filesystem/v2.js.map +1 -0
- package/dist/filesystem/workspace-path.d.ts +21 -0
- package/dist/filesystem/workspace-path.d.ts.map +1 -0
- package/dist/filesystem/workspace-path.js +80 -0
- package/dist/filesystem/workspace-path.js.map +1 -0
- package/dist/host/filesystem-v2.d.ts +45 -0
- package/dist/host/filesystem-v2.d.ts.map +1 -0
- package/dist/host/filesystem-v2.js +13 -0
- package/dist/host/filesystem-v2.js.map +1 -0
- package/dist/host/git.d.ts +200 -0
- package/dist/host/git.d.ts.map +1 -0
- package/dist/host/git.js +15 -0
- package/dist/host/git.js.map +1 -0
- package/dist/host/index.d.ts +4 -1
- package/dist/host/index.d.ts.map +1 -1
- package/dist/host/index.js +2 -0
- package/dist/host/index.js.map +1 -1
- package/dist/host/types.d.ts +114 -31
- package/dist/host/types.d.ts.map +1 -1
- package/dist/host/wire-policy.d.ts +22 -0
- package/dist/host/wire-policy.d.ts.map +1 -0
- package/dist/host/wire-policy.js +92 -0
- package/dist/host/wire-policy.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/index.d.ts +4 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +4 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/types.d.ts +466 -0
- package/dist/mcp/types.d.ts.map +1 -0
- package/dist/mcp/types.js +25 -0
- package/dist/mcp/types.js.map +1 -0
- package/dist/mcp/wire.d.ts +29 -0
- package/dist/mcp/wire.d.ts.map +1 -0
- package/dist/mcp/wire.js +1046 -0
- package/dist/mcp/wire.js.map +1 -0
- package/dist/mcp/zod.d.ts +5763 -0
- package/dist/mcp/zod.d.ts.map +1 -0
- package/dist/mcp/zod.js +908 -0
- package/dist/mcp/zod.js.map +1 -0
- package/dist/types-CX893hgH.d.ts +468 -0
- package/dist/types-CsX6EN2t.d.ts +301 -0
- package/dist/types-eOlt9J7n.d.ts +478 -0
- package/dist/vscode/index.d.ts +3 -0
- package/dist/vscode/index.d.ts.map +1 -0
- package/dist/vscode/index.js +3 -0
- package/dist/vscode/index.js.map +1 -0
- package/dist/vscode/messages.d.ts +204 -0
- package/dist/vscode/messages.d.ts.map +1 -0
- package/dist/vscode/messages.js +494 -0
- package/dist/vscode/messages.js.map +1 -0
- package/dist/workspace/index.d.ts +3 -2
- package/dist/workspace/index.d.ts.map +1 -1
- package/dist/workspace/index.js +2 -1
- package/dist/workspace/index.js.map +1 -1
- package/dist/workspace/reconcile-electron.d.ts +19 -0
- package/dist/workspace/reconcile-electron.d.ts.map +1 -0
- package/dist/workspace/reconcile-electron.js +42 -0
- package/dist/workspace/reconcile-electron.js.map +1 -0
- package/dist/workspace/types.d.ts +41 -1
- package/dist/workspace/types.d.ts.map +1 -1
- package/dist/workspace/types.js +0 -4
- package/dist/workspace/types.js.map +1 -1
- package/dist/workspace/workspace-manager.d.ts +17 -5
- package/dist/workspace/workspace-manager.d.ts.map +1 -1
- package/dist/workspace/workspace-manager.js +55 -11
- package/dist/workspace/workspace-manager.js.map +1 -1
- package/dist/workspace-path-CWVrcuPL.d.ts +21 -0
- package/package.json +43 -2
- package/src/document/document-session.ts +834 -0
- package/src/document/filesystem-target.ts +109 -0
- package/src/document/index.ts +41 -0
- package/src/document/recovery-journal.ts +536 -0
- package/src/document/types.ts +116 -0
- package/src/filesystem/commit-lock.ts +29 -0
- package/src/filesystem/dbk-workspace.ts +135 -0
- package/src/filesystem/electron-provider-v2.ts +630 -0
- package/src/filesystem/electron-provider.ts +25 -14
- package/src/filesystem/electron.ts +3 -0
- package/src/filesystem/filesystem-content-container.ts +53 -15
- package/src/filesystem/fs-error.ts +234 -0
- package/src/filesystem/index.ts +119 -0
- package/src/filesystem/indexeddb-content-container.ts +20 -12
- package/src/filesystem/indexeddb-provider-v2.ts +1740 -0
- package/src/filesystem/indexeddb-provider.ts +126 -300
- package/src/filesystem/indexeddb-store.ts +207 -0
- package/src/filesystem/indexeddb.ts +12 -0
- package/src/filesystem/legacy-v2-adapter.ts +779 -0
- package/src/filesystem/memory-provider-v2.ts +896 -0
- package/src/filesystem/memory-provider.ts +180 -0
- package/src/filesystem/memory.ts +15 -0
- package/src/filesystem/move-entry.ts +142 -0
- package/src/filesystem/move-error.ts +159 -0
- package/src/filesystem/native-provider-v2.ts +1165 -0
- package/src/filesystem/native-provider.ts +194 -41
- package/src/filesystem/native.ts +16 -0
- package/src/filesystem/types.ts +49 -4
- package/src/filesystem/v2.ts +231 -0
- package/src/filesystem/workspace-path.ts +92 -0
- package/src/host/filesystem-v2.ts +104 -0
- package/src/host/git.ts +246 -0
- package/src/host/index.ts +46 -0
- package/src/host/types.ts +155 -34
- package/src/host/wire-policy.ts +103 -0
- package/src/index.ts +2 -1
- package/src/mcp/index.ts +3 -0
- package/src/mcp/types.ts +594 -0
- package/src/mcp/wire.ts +1280 -0
- package/src/mcp/zod.ts +1000 -0
- package/src/vscode/index.ts +2 -0
- package/src/vscode/messages.ts +704 -0
- package/src/workspace/index.ts +8 -1
- package/src/workspace/reconcile-electron.ts +65 -0
- package/src/workspace/types.ts +39 -1
- package/src/workspace/workspace-manager.ts +71 -10
- package/dist/chunk-GUNM43XZ.js +0 -63
- package/dist/chunk-GUNM43XZ.js.map +0 -1
- package/dist/chunk-IM7LCQJ6.js +0 -850
- package/dist/chunk-IM7LCQJ6.js.map +0 -1
- package/dist/chunk-ME76RUMR.js.map +0 -1
- package/dist/index-pgBHBQ9G.d.ts +0 -213
|
@@ -6,12 +6,18 @@
|
|
|
6
6
|
* the API (Chrome, Edge). Feature-detect with `isNativeFileSystemSupported()`.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import type { FileSystemProvider, FileSystemEntry, FileMeta } from './types.js';
|
|
9
|
+
import type { FileCommitResult, FileSystemProvider, FileSystemEntry, FileMeta } from './types.js';
|
|
10
|
+
import { withFileCommitLock } from './commit-lock.js';
|
|
11
|
+
import { NativeFileSystemProviderV2 } from './native-provider-v2.js';
|
|
12
|
+
import { parseWorkspacePath } from './workspace-path.js';
|
|
10
13
|
|
|
11
14
|
// ── Feature detection ──────────────────────────────────────────────
|
|
12
15
|
|
|
13
16
|
export function isNativeFileSystemSupported(): boolean {
|
|
14
|
-
return
|
|
17
|
+
return (
|
|
18
|
+
typeof globalThis !== 'undefined' &&
|
|
19
|
+
typeof (globalThis as { showDirectoryPicker?: unknown }).showDirectoryPicker === 'function'
|
|
20
|
+
);
|
|
15
21
|
}
|
|
16
22
|
|
|
17
23
|
// ── Handle persistence (IndexedDB, structured clone) ───────────────
|
|
@@ -89,7 +95,33 @@ export async function removeDirectoryHandle(workspaceId: string): Promise<void>
|
|
|
89
95
|
// ── Helpers ────────────────────────────────────────────────────────
|
|
90
96
|
|
|
91
97
|
function normalisePath(p: string): string {
|
|
92
|
-
return p
|
|
98
|
+
return parseWorkspacePath(p);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function errorName(error: unknown): string | undefined {
|
|
102
|
+
return typeof error === 'object' && error !== null && 'name' in error
|
|
103
|
+
? String((error as { name?: unknown }).name)
|
|
104
|
+
: undefined;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function isMissingEntryError(error: unknown): boolean {
|
|
108
|
+
const name = errorName(error);
|
|
109
|
+
return name === 'NotFoundError' || name === 'TypeMismatchError';
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function isPermissionError(error: unknown): boolean {
|
|
113
|
+
const name = errorName(error);
|
|
114
|
+
return name === 'NotAllowedError' || name === 'SecurityError';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
class NativeMoveRecoveryError extends Error {
|
|
118
|
+
readonly causes: readonly unknown[];
|
|
119
|
+
|
|
120
|
+
constructor(message: string, causes: readonly unknown[]) {
|
|
121
|
+
super(message);
|
|
122
|
+
this.name = 'NativeMoveRecoveryError';
|
|
123
|
+
this.causes = causes;
|
|
124
|
+
}
|
|
93
125
|
}
|
|
94
126
|
|
|
95
127
|
/**
|
|
@@ -106,8 +138,9 @@ async function resolveDir(
|
|
|
106
138
|
for (const part of parts) {
|
|
107
139
|
try {
|
|
108
140
|
current = await current.getDirectoryHandle(part);
|
|
109
|
-
} catch {
|
|
110
|
-
return null;
|
|
141
|
+
} catch (error: unknown) {
|
|
142
|
+
if (isMissingEntryError(error)) return null;
|
|
143
|
+
throw error;
|
|
111
144
|
}
|
|
112
145
|
}
|
|
113
146
|
return current;
|
|
@@ -146,11 +179,25 @@ function toWritableBinary(data: ArrayBuffer | Uint8Array): ArrayBuffer {
|
|
|
146
179
|
return copy.buffer;
|
|
147
180
|
}
|
|
148
181
|
|
|
182
|
+
async function writeAndClose(
|
|
183
|
+
writable: FileSystemWritableFileStream,
|
|
184
|
+
data: string | ArrayBuffer,
|
|
185
|
+
): Promise<void> {
|
|
186
|
+
try {
|
|
187
|
+
await writable.write(data);
|
|
188
|
+
await writable.close();
|
|
189
|
+
} catch (error: unknown) {
|
|
190
|
+
await writable.abort(error).catch(() => undefined);
|
|
191
|
+
throw error;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
149
195
|
// ── Implementation ─────────────────────────────────────────────────
|
|
150
196
|
|
|
151
197
|
export class NativeFileSystemProvider implements FileSystemProvider {
|
|
152
198
|
readonly id: string;
|
|
153
199
|
readonly label: string;
|
|
200
|
+
readonly v2: NativeFileSystemProviderV2;
|
|
154
201
|
|
|
155
202
|
private root: FileSystemDirectoryHandle;
|
|
156
203
|
|
|
@@ -158,11 +205,17 @@ export class NativeFileSystemProvider implements FileSystemProvider {
|
|
|
158
205
|
this.id = id;
|
|
159
206
|
this.label = root.name;
|
|
160
207
|
this.root = root;
|
|
208
|
+
this.v2 = new NativeFileSystemProviderV2(id, root);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
private withMutationLock<T>(operation: () => Promise<T>): Promise<T> {
|
|
212
|
+
return withFileCommitLock(`native:${this.id}:workspace`, operation);
|
|
161
213
|
}
|
|
162
214
|
|
|
163
|
-
private async copyDirectory(oldDirPath: string, newDirPath: string): Promise<
|
|
215
|
+
private async copyDirectory(oldDirPath: string, newDirPath: string): Promise<void> {
|
|
164
216
|
const source = await resolveDir(this.root, oldDirPath);
|
|
165
|
-
if (!source)
|
|
217
|
+
if (!source)
|
|
218
|
+
throw new DOMException(`Source directory does not exist: ${oldDirPath}`, 'NotFoundError');
|
|
166
219
|
await resolveDirCreate(this.root, newDirPath);
|
|
167
220
|
|
|
168
221
|
for await (const [name, handle] of source as unknown as AsyncIterable<
|
|
@@ -175,11 +228,9 @@ export class NativeFileSystemProvider implements FileSystemProvider {
|
|
|
175
228
|
} else {
|
|
176
229
|
const fileHandle = await source.getFileHandle(name);
|
|
177
230
|
const file = await fileHandle.getFile();
|
|
178
|
-
await this.
|
|
231
|
+
await this.writeBinaryUnlocked(newChild, await file.arrayBuffer());
|
|
179
232
|
}
|
|
180
233
|
}
|
|
181
|
-
|
|
182
|
-
return true;
|
|
183
234
|
}
|
|
184
235
|
|
|
185
236
|
async readFile(path: string): Promise<string | null> {
|
|
@@ -190,30 +241,70 @@ export class NativeFileSystemProvider implements FileSystemProvider {
|
|
|
190
241
|
const fileHandle = await dir.getFileHandle(baseName(p));
|
|
191
242
|
const file = await fileHandle.getFile();
|
|
192
243
|
return file.text();
|
|
193
|
-
} catch {
|
|
194
|
-
return null;
|
|
244
|
+
} catch (error: unknown) {
|
|
245
|
+
if (isMissingEntryError(error)) return null;
|
|
246
|
+
throw error;
|
|
195
247
|
}
|
|
196
248
|
}
|
|
197
249
|
|
|
198
250
|
async writeFile(path: string, content: string): Promise<void> {
|
|
251
|
+
return this.withMutationLock(() => this.writeFileUnlocked(path, content));
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
private async writeFileUnlocked(path: string, content: string): Promise<void> {
|
|
199
255
|
const p = normalisePath(path);
|
|
200
256
|
const dir = await resolveDirCreate(this.root, parentDir(p));
|
|
201
257
|
const fileHandle = await dir.getFileHandle(baseName(p), { create: true });
|
|
202
258
|
const writable = await fileHandle.createWritable();
|
|
203
|
-
await writable
|
|
204
|
-
|
|
259
|
+
await writeAndClose(writable, content);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async commitFile(
|
|
263
|
+
path: string,
|
|
264
|
+
content: string,
|
|
265
|
+
expectedContent: string | null,
|
|
266
|
+
): Promise<FileCommitResult> {
|
|
267
|
+
const p = normalisePath(path);
|
|
268
|
+
return this.withMutationLock(async () => {
|
|
269
|
+
const current = await this.readFile(p);
|
|
270
|
+
if (current !== expectedContent && current !== content) {
|
|
271
|
+
const meta = await this.stat(p);
|
|
272
|
+
return {
|
|
273
|
+
status: 'conflict',
|
|
274
|
+
content: current,
|
|
275
|
+
version: meta?.lastModified ?? null,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
await this.writeFileUnlocked(p, content);
|
|
279
|
+
const meta = await this.stat(p);
|
|
280
|
+
return { status: 'committed', version: meta?.lastModified ?? null };
|
|
281
|
+
});
|
|
205
282
|
}
|
|
206
283
|
|
|
207
284
|
async delete(path: string): Promise<void> {
|
|
285
|
+
return this.withMutationLock(() => this.deleteUnlocked(path));
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
private async deleteUnlocked(path: string): Promise<void> {
|
|
208
289
|
const p = normalisePath(path);
|
|
290
|
+
if (!p) throw new Error('Cannot delete the filesystem root');
|
|
209
291
|
const parent = parentDir(p);
|
|
210
292
|
const name = baseName(p);
|
|
211
293
|
const dir = await resolveDir(this.root, parent);
|
|
212
294
|
if (!dir) return;
|
|
213
|
-
|
|
295
|
+
try {
|
|
296
|
+
await dir.removeEntry(name, { recursive: true });
|
|
297
|
+
} catch (error: unknown) {
|
|
298
|
+
if (isMissingEntryError(error)) return;
|
|
299
|
+
throw error;
|
|
300
|
+
}
|
|
214
301
|
}
|
|
215
302
|
|
|
216
303
|
async rename(oldPath: string, newPath: string): Promise<void> {
|
|
304
|
+
return this.withMutationLock(() => this.renameUnlocked(oldPath, newPath));
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
private async renameUnlocked(oldPath: string, newPath: string): Promise<void> {
|
|
217
308
|
const op = normalisePath(oldPath);
|
|
218
309
|
const np = normalisePath(newPath);
|
|
219
310
|
if (op === np) return;
|
|
@@ -223,25 +314,72 @@ export class NativeFileSystemProvider implements FileSystemProvider {
|
|
|
223
314
|
if (np.startsWith(op + '/')) {
|
|
224
315
|
throw new Error('Cannot move a directory into itself');
|
|
225
316
|
}
|
|
317
|
+
if (!(await this.exists(op))) {
|
|
318
|
+
throw new Error(`Source does not exist: ${oldPath}`);
|
|
319
|
+
}
|
|
320
|
+
if (await this.exists(np)) {
|
|
321
|
+
throw new Error(`Destination exists: ${newPath}`);
|
|
322
|
+
}
|
|
226
323
|
|
|
227
324
|
// The File System Access API doesn't have a native rename.
|
|
228
325
|
// Copy → delete.
|
|
229
326
|
const oldParent = await resolveDir(this.root, parentDir(op));
|
|
230
|
-
if (!oldParent)
|
|
327
|
+
if (!oldParent) {
|
|
328
|
+
throw new DOMException(`Source does not exist: ${oldPath}`, 'NotFoundError');
|
|
329
|
+
}
|
|
231
330
|
|
|
331
|
+
let fileHandle: FileSystemFileHandle | null = null;
|
|
232
332
|
try {
|
|
233
|
-
|
|
333
|
+
fileHandle = await oldParent.getFileHandle(baseName(op));
|
|
334
|
+
} catch (error: unknown) {
|
|
335
|
+
if (!isMissingEntryError(error)) throw error;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (fileHandle) {
|
|
234
339
|
const file = await fileHandle.getFile();
|
|
235
|
-
await this.
|
|
236
|
-
|
|
340
|
+
await this.writeBinaryUnlocked(np, await file.arrayBuffer());
|
|
341
|
+
try {
|
|
342
|
+
await this.deleteUnlocked(op);
|
|
343
|
+
} catch (error: unknown) {
|
|
344
|
+
try {
|
|
345
|
+
await this.deleteUnlocked(np);
|
|
346
|
+
} catch (rollbackError: unknown) {
|
|
347
|
+
throw new NativeMoveRecoveryError(
|
|
348
|
+
`Move partially completed and rollback failed: ${oldPath} -> ${newPath}`,
|
|
349
|
+
[error, rollbackError],
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
throw error;
|
|
353
|
+
}
|
|
237
354
|
return;
|
|
238
|
-
} catch {
|
|
239
|
-
// Not a file; try directory below.
|
|
240
355
|
}
|
|
241
356
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
357
|
+
try {
|
|
358
|
+
await this.copyDirectory(op, np);
|
|
359
|
+
} catch (error: unknown) {
|
|
360
|
+
try {
|
|
361
|
+
await this.deleteUnlocked(np);
|
|
362
|
+
} catch (rollbackError: unknown) {
|
|
363
|
+
throw new NativeMoveRecoveryError(
|
|
364
|
+
`Move failed and destination cleanup failed: ${oldPath} -> ${newPath}`,
|
|
365
|
+
[error, rollbackError],
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
throw error;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
try {
|
|
372
|
+
await this.deleteUnlocked(op);
|
|
373
|
+
} catch (error: unknown) {
|
|
374
|
+
try {
|
|
375
|
+
await this.deleteUnlocked(np);
|
|
376
|
+
} catch (rollbackError: unknown) {
|
|
377
|
+
throw new NativeMoveRecoveryError(
|
|
378
|
+
`Move partially completed and rollback failed: ${oldPath} -> ${newPath}`,
|
|
379
|
+
[error, rollbackError],
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
throw error;
|
|
245
383
|
}
|
|
246
384
|
}
|
|
247
385
|
|
|
@@ -281,19 +419,23 @@ export class NativeFileSystemProvider implements FileSystemProvider {
|
|
|
281
419
|
try {
|
|
282
420
|
await dir.getFileHandle(name);
|
|
283
421
|
return true;
|
|
284
|
-
} catch {
|
|
422
|
+
} catch (fileError: unknown) {
|
|
423
|
+
if (!isMissingEntryError(fileError)) throw fileError;
|
|
285
424
|
try {
|
|
286
425
|
await dir.getDirectoryHandle(name);
|
|
287
426
|
return true;
|
|
288
|
-
} catch {
|
|
427
|
+
} catch (directoryError: unknown) {
|
|
428
|
+
if (!isMissingEntryError(directoryError)) throw directoryError;
|
|
289
429
|
return false;
|
|
290
430
|
}
|
|
291
431
|
}
|
|
292
432
|
}
|
|
293
433
|
|
|
294
434
|
async createDirectory(path: string): Promise<void> {
|
|
295
|
-
|
|
296
|
-
|
|
435
|
+
return this.withMutationLock(async () => {
|
|
436
|
+
const p = normalisePath(path);
|
|
437
|
+
await resolveDirCreate(this.root, p);
|
|
438
|
+
});
|
|
297
439
|
}
|
|
298
440
|
|
|
299
441
|
async stat(path: string): Promise<FileMeta | null> {
|
|
@@ -310,8 +452,9 @@ export class NativeFileSystemProvider implements FileSystemProvider {
|
|
|
310
452
|
size: file.size,
|
|
311
453
|
lastModified: new Date(file.lastModified).toISOString(),
|
|
312
454
|
};
|
|
313
|
-
} catch {
|
|
314
|
-
return null;
|
|
455
|
+
} catch (error: unknown) {
|
|
456
|
+
if (isMissingEntryError(error)) return null;
|
|
457
|
+
throw error;
|
|
315
458
|
}
|
|
316
459
|
}
|
|
317
460
|
|
|
@@ -323,18 +466,22 @@ export class NativeFileSystemProvider implements FileSystemProvider {
|
|
|
323
466
|
const fileHandle = await dir.getFileHandle(baseName(p));
|
|
324
467
|
const file = await fileHandle.getFile();
|
|
325
468
|
return file.arrayBuffer();
|
|
326
|
-
} catch {
|
|
327
|
-
return null;
|
|
469
|
+
} catch (error: unknown) {
|
|
470
|
+
if (isMissingEntryError(error)) return null;
|
|
471
|
+
throw error;
|
|
328
472
|
}
|
|
329
473
|
}
|
|
330
474
|
|
|
331
475
|
async writeBinary(path: string, data: ArrayBuffer | Uint8Array): Promise<void> {
|
|
476
|
+
return this.withMutationLock(() => this.writeBinaryUnlocked(path, data));
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
private async writeBinaryUnlocked(path: string, data: ArrayBuffer | Uint8Array): Promise<void> {
|
|
332
480
|
const p = normalisePath(path);
|
|
333
481
|
const dir = await resolveDirCreate(this.root, parentDir(p));
|
|
334
482
|
const fileHandle = await dir.getFileHandle(baseName(p), { create: true });
|
|
335
483
|
const writable = await fileHandle.createWritable();
|
|
336
|
-
await writable
|
|
337
|
-
await writable.close();
|
|
484
|
+
await writeAndClose(writable, toWritableBinary(data));
|
|
338
485
|
}
|
|
339
486
|
}
|
|
340
487
|
|
|
@@ -349,8 +496,10 @@ export async function openNativeFolder(): Promise<NativeFileSystemProvider> {
|
|
|
349
496
|
}
|
|
350
497
|
|
|
351
498
|
const handle = await (
|
|
352
|
-
globalThis as unknown as {
|
|
353
|
-
|
|
499
|
+
globalThis as unknown as {
|
|
500
|
+
showDirectoryPicker: (options: { mode: 'readwrite' }) => Promise<FileSystemDirectoryHandle>;
|
|
501
|
+
}
|
|
502
|
+
).showDirectoryPicker({ mode: 'readwrite' });
|
|
354
503
|
const id = `native-${handle.name}-${Date.now()}`;
|
|
355
504
|
await storeDirectoryHandle(id, handle);
|
|
356
505
|
return new NativeFileSystemProvider(id, handle);
|
|
@@ -373,11 +522,15 @@ export async function restoreNativeFolder(
|
|
|
373
522
|
queryPermission(desc: { mode: string }): Promise<string>;
|
|
374
523
|
requestPermission(desc: { mode: string }): Promise<string>;
|
|
375
524
|
};
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
525
|
+
try {
|
|
526
|
+
if ((await h.queryPermission(opts)) === 'granted') {
|
|
527
|
+
return new NativeFileSystemProvider(workspaceId, handle);
|
|
528
|
+
}
|
|
529
|
+
if ((await h.requestPermission(opts)) === 'granted') {
|
|
530
|
+
return new NativeFileSystemProvider(workspaceId, handle);
|
|
531
|
+
}
|
|
532
|
+
} catch (error: unknown) {
|
|
533
|
+
if (!isPermissionError(error)) throw error;
|
|
381
534
|
}
|
|
382
535
|
|
|
383
536
|
return null;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Browser File System Access API provider entry point. */
|
|
2
|
+
export {
|
|
3
|
+
NativeFileSystemProvider,
|
|
4
|
+
isNativeFileSystemSupported,
|
|
5
|
+
openNativeFolder,
|
|
6
|
+
restoreNativeFolder,
|
|
7
|
+
storeDirectoryHandle,
|
|
8
|
+
loadDirectoryHandle,
|
|
9
|
+
removeDirectoryHandle,
|
|
10
|
+
} from './native-provider.js';
|
|
11
|
+
export {
|
|
12
|
+
NativeFileSystemProviderV2,
|
|
13
|
+
NativeFileSystemMoveRecoveryError,
|
|
14
|
+
type NativeFileSystemMovePathState,
|
|
15
|
+
type NativeFileSystemMoveRecoveryState,
|
|
16
|
+
} from './native-provider-v2.js';
|
package/src/filesystem/types.ts
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* FileSystemProvider — abstract interface for a virtual filesystem.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Legacy text-oriented facade retained while consumers migrate to
|
|
5
|
+
* FileSystemProviderV2. New persistence code must use the provider's `v2`
|
|
6
|
+
* contract so paths, errors, operation modes, and capabilities are explicit.
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
9
|
// ── Entry types ────────────────────────────────────────────────────
|
|
9
10
|
|
|
11
|
+
import type { FileSystemProviderV2 } from './v2.js';
|
|
12
|
+
|
|
10
13
|
export interface FileEntry {
|
|
11
14
|
kind: 'file';
|
|
12
15
|
name: string;
|
|
@@ -28,8 +31,17 @@ export interface FileMeta {
|
|
|
28
31
|
lastModified: string;
|
|
29
32
|
}
|
|
30
33
|
|
|
34
|
+
export type FileCommitResult =
|
|
35
|
+
| { status: 'committed'; version: string | null }
|
|
36
|
+
| { status: 'conflict'; content: string | null; version: string | null };
|
|
37
|
+
|
|
31
38
|
// ── Provider interface ─────────────────────────────────────────────
|
|
32
39
|
|
|
40
|
+
/**
|
|
41
|
+
* @deprecated Compatibility facade for external providers and older callers.
|
|
42
|
+
* Implement new backends with `FileSystemProviderV2`; built-in facades expose
|
|
43
|
+
* their authoritative implementation through `v2`.
|
|
44
|
+
*/
|
|
33
45
|
export interface FileSystemProvider {
|
|
34
46
|
/** Unique identifier for this provider instance. */
|
|
35
47
|
readonly id: string;
|
|
@@ -37,16 +49,33 @@ export interface FileSystemProvider {
|
|
|
37
49
|
/** Human-readable label (e.g., folder name or "Browser Storage"). */
|
|
38
50
|
readonly label: string;
|
|
39
51
|
|
|
52
|
+
/** Optional correctness-first implementation exposed during the v1-to-v2 migration. */
|
|
53
|
+
readonly v2?: FileSystemProviderV2;
|
|
54
|
+
|
|
40
55
|
/** Read the text content of a file. Returns null if the file doesn't exist. */
|
|
41
56
|
readFile(path: string): Promise<string | null>;
|
|
42
57
|
|
|
43
58
|
/** Write text content to a file, creating it (and parent dirs) if needed. */
|
|
44
59
|
writeFile(path: string, content: string): Promise<void>;
|
|
45
60
|
|
|
46
|
-
/**
|
|
61
|
+
/**
|
|
62
|
+
* Conditionally replace a text file when it still matches the caller's
|
|
63
|
+
* persisted baseline. Production providers implement this at their
|
|
64
|
+
* strongest available serialization boundary.
|
|
65
|
+
*/
|
|
66
|
+
commitFile?(
|
|
67
|
+
path: string,
|
|
68
|
+
content: string,
|
|
69
|
+
expectedContent: string | null,
|
|
70
|
+
): Promise<FileCommitResult>;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Legacy recursive removal of a file or directory subtree. Workspace root
|
|
74
|
+
* deletion is forbidden. Prefer v2 `remove({ recursive })`.
|
|
75
|
+
*/
|
|
47
76
|
delete(path: string): Promise<void>;
|
|
48
77
|
|
|
49
|
-
/** Rename or move an entry. */
|
|
78
|
+
/** Rename or move an entry. Rejects missing sources and existing destinations. */
|
|
50
79
|
rename(oldPath: string, newPath: string): Promise<void>;
|
|
51
80
|
|
|
52
81
|
/** List immediate children of a directory. */
|
|
@@ -67,3 +96,19 @@ export interface FileSystemProvider {
|
|
|
67
96
|
/** Write raw binary content. */
|
|
68
97
|
writeBinary(path: string, data: ArrayBuffer | Uint8Array): Promise<void>;
|
|
69
98
|
}
|
|
99
|
+
|
|
100
|
+
export type FileSystemProviderWithV2 = FileSystemProvider & {
|
|
101
|
+
readonly v2: FileSystemProviderV2;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
/** Discover a provider-owned v2 implementation without coupling consumers to a concrete class. */
|
|
105
|
+
export function hasFileSystemProviderV2(
|
|
106
|
+
provider: FileSystemProvider,
|
|
107
|
+
): provider is FileSystemProviderWithV2 {
|
|
108
|
+
return provider.v2 !== undefined && provider.v2 !== null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Return the provider-owned v2 implementation when migration support is available. */
|
|
112
|
+
export function getFileSystemProviderV2(provider: FileSystemProvider): FileSystemProviderV2 | null {
|
|
113
|
+
return provider.v2 ?? null;
|
|
114
|
+
}
|