@bendyline/docblocks 1.1.1 → 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 +108 -17
- 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 +179 -42
- 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 +118 -35
- 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 +201 -45
- 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-LORJUBON.js +0 -848
- package/dist/chunk-LORJUBON.js.map +0 -1
- package/dist/chunk-ME76RUMR.js.map +0 -1
- package/dist/index-Bunj8Kb_.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;
|
|
@@ -139,11 +172,32 @@ function baseName(p: string): string {
|
|
|
139
172
|
return idx === -1 ? p : p.slice(idx + 1);
|
|
140
173
|
}
|
|
141
174
|
|
|
175
|
+
function toWritableBinary(data: ArrayBuffer | Uint8Array): ArrayBuffer {
|
|
176
|
+
if (data instanceof ArrayBuffer) return data;
|
|
177
|
+
const copy = new Uint8Array(data.byteLength);
|
|
178
|
+
copy.set(data);
|
|
179
|
+
return copy.buffer;
|
|
180
|
+
}
|
|
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
|
+
|
|
142
195
|
// ── Implementation ─────────────────────────────────────────────────
|
|
143
196
|
|
|
144
197
|
export class NativeFileSystemProvider implements FileSystemProvider {
|
|
145
198
|
readonly id: string;
|
|
146
199
|
readonly label: string;
|
|
200
|
+
readonly v2: NativeFileSystemProviderV2;
|
|
147
201
|
|
|
148
202
|
private root: FileSystemDirectoryHandle;
|
|
149
203
|
|
|
@@ -151,11 +205,17 @@ export class NativeFileSystemProvider implements FileSystemProvider {
|
|
|
151
205
|
this.id = id;
|
|
152
206
|
this.label = root.name;
|
|
153
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);
|
|
154
213
|
}
|
|
155
214
|
|
|
156
|
-
private async copyDirectory(oldDirPath: string, newDirPath: string): Promise<
|
|
215
|
+
private async copyDirectory(oldDirPath: string, newDirPath: string): Promise<void> {
|
|
157
216
|
const source = await resolveDir(this.root, oldDirPath);
|
|
158
|
-
if (!source)
|
|
217
|
+
if (!source)
|
|
218
|
+
throw new DOMException(`Source directory does not exist: ${oldDirPath}`, 'NotFoundError');
|
|
159
219
|
await resolveDirCreate(this.root, newDirPath);
|
|
160
220
|
|
|
161
221
|
for await (const [name, handle] of source as unknown as AsyncIterable<
|
|
@@ -168,11 +228,9 @@ export class NativeFileSystemProvider implements FileSystemProvider {
|
|
|
168
228
|
} else {
|
|
169
229
|
const fileHandle = await source.getFileHandle(name);
|
|
170
230
|
const file = await fileHandle.getFile();
|
|
171
|
-
await this.
|
|
231
|
+
await this.writeBinaryUnlocked(newChild, await file.arrayBuffer());
|
|
172
232
|
}
|
|
173
233
|
}
|
|
174
|
-
|
|
175
|
-
return true;
|
|
176
234
|
}
|
|
177
235
|
|
|
178
236
|
async readFile(path: string): Promise<string | null> {
|
|
@@ -183,30 +241,70 @@ export class NativeFileSystemProvider implements FileSystemProvider {
|
|
|
183
241
|
const fileHandle = await dir.getFileHandle(baseName(p));
|
|
184
242
|
const file = await fileHandle.getFile();
|
|
185
243
|
return file.text();
|
|
186
|
-
} catch {
|
|
187
|
-
return null;
|
|
244
|
+
} catch (error: unknown) {
|
|
245
|
+
if (isMissingEntryError(error)) return null;
|
|
246
|
+
throw error;
|
|
188
247
|
}
|
|
189
248
|
}
|
|
190
249
|
|
|
191
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> {
|
|
192
255
|
const p = normalisePath(path);
|
|
193
256
|
const dir = await resolveDirCreate(this.root, parentDir(p));
|
|
194
257
|
const fileHandle = await dir.getFileHandle(baseName(p), { create: true });
|
|
195
258
|
const writable = await fileHandle.createWritable();
|
|
196
|
-
await writable
|
|
197
|
-
|
|
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
|
+
});
|
|
198
282
|
}
|
|
199
283
|
|
|
200
284
|
async delete(path: string): Promise<void> {
|
|
285
|
+
return this.withMutationLock(() => this.deleteUnlocked(path));
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
private async deleteUnlocked(path: string): Promise<void> {
|
|
201
289
|
const p = normalisePath(path);
|
|
290
|
+
if (!p) throw new Error('Cannot delete the filesystem root');
|
|
202
291
|
const parent = parentDir(p);
|
|
203
292
|
const name = baseName(p);
|
|
204
293
|
const dir = await resolveDir(this.root, parent);
|
|
205
294
|
if (!dir) return;
|
|
206
|
-
|
|
295
|
+
try {
|
|
296
|
+
await dir.removeEntry(name, { recursive: true });
|
|
297
|
+
} catch (error: unknown) {
|
|
298
|
+
if (isMissingEntryError(error)) return;
|
|
299
|
+
throw error;
|
|
300
|
+
}
|
|
207
301
|
}
|
|
208
302
|
|
|
209
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> {
|
|
210
308
|
const op = normalisePath(oldPath);
|
|
211
309
|
const np = normalisePath(newPath);
|
|
212
310
|
if (op === np) return;
|
|
@@ -216,25 +314,72 @@ export class NativeFileSystemProvider implements FileSystemProvider {
|
|
|
216
314
|
if (np.startsWith(op + '/')) {
|
|
217
315
|
throw new Error('Cannot move a directory into itself');
|
|
218
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
|
+
}
|
|
219
323
|
|
|
220
324
|
// The File System Access API doesn't have a native rename.
|
|
221
325
|
// Copy → delete.
|
|
222
326
|
const oldParent = await resolveDir(this.root, parentDir(op));
|
|
223
|
-
if (!oldParent)
|
|
327
|
+
if (!oldParent) {
|
|
328
|
+
throw new DOMException(`Source does not exist: ${oldPath}`, 'NotFoundError');
|
|
329
|
+
}
|
|
224
330
|
|
|
331
|
+
let fileHandle: FileSystemFileHandle | null = null;
|
|
225
332
|
try {
|
|
226
|
-
|
|
333
|
+
fileHandle = await oldParent.getFileHandle(baseName(op));
|
|
334
|
+
} catch (error: unknown) {
|
|
335
|
+
if (!isMissingEntryError(error)) throw error;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (fileHandle) {
|
|
227
339
|
const file = await fileHandle.getFile();
|
|
228
|
-
await this.
|
|
229
|
-
|
|
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
|
+
}
|
|
230
354
|
return;
|
|
231
|
-
} catch {
|
|
232
|
-
// Not a file; try directory below.
|
|
233
355
|
}
|
|
234
356
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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;
|
|
238
383
|
}
|
|
239
384
|
}
|
|
240
385
|
|
|
@@ -274,19 +419,23 @@ export class NativeFileSystemProvider implements FileSystemProvider {
|
|
|
274
419
|
try {
|
|
275
420
|
await dir.getFileHandle(name);
|
|
276
421
|
return true;
|
|
277
|
-
} catch {
|
|
422
|
+
} catch (fileError: unknown) {
|
|
423
|
+
if (!isMissingEntryError(fileError)) throw fileError;
|
|
278
424
|
try {
|
|
279
425
|
await dir.getDirectoryHandle(name);
|
|
280
426
|
return true;
|
|
281
|
-
} catch {
|
|
427
|
+
} catch (directoryError: unknown) {
|
|
428
|
+
if (!isMissingEntryError(directoryError)) throw directoryError;
|
|
282
429
|
return false;
|
|
283
430
|
}
|
|
284
431
|
}
|
|
285
432
|
}
|
|
286
433
|
|
|
287
434
|
async createDirectory(path: string): Promise<void> {
|
|
288
|
-
|
|
289
|
-
|
|
435
|
+
return this.withMutationLock(async () => {
|
|
436
|
+
const p = normalisePath(path);
|
|
437
|
+
await resolveDirCreate(this.root, p);
|
|
438
|
+
});
|
|
290
439
|
}
|
|
291
440
|
|
|
292
441
|
async stat(path: string): Promise<FileMeta | null> {
|
|
@@ -303,8 +452,9 @@ export class NativeFileSystemProvider implements FileSystemProvider {
|
|
|
303
452
|
size: file.size,
|
|
304
453
|
lastModified: new Date(file.lastModified).toISOString(),
|
|
305
454
|
};
|
|
306
|
-
} catch {
|
|
307
|
-
return null;
|
|
455
|
+
} catch (error: unknown) {
|
|
456
|
+
if (isMissingEntryError(error)) return null;
|
|
457
|
+
throw error;
|
|
308
458
|
}
|
|
309
459
|
}
|
|
310
460
|
|
|
@@ -316,22 +466,22 @@ export class NativeFileSystemProvider implements FileSystemProvider {
|
|
|
316
466
|
const fileHandle = await dir.getFileHandle(baseName(p));
|
|
317
467
|
const file = await fileHandle.getFile();
|
|
318
468
|
return file.arrayBuffer();
|
|
319
|
-
} catch {
|
|
320
|
-
return null;
|
|
469
|
+
} catch (error: unknown) {
|
|
470
|
+
if (isMissingEntryError(error)) return null;
|
|
471
|
+
throw error;
|
|
321
472
|
}
|
|
322
473
|
}
|
|
323
474
|
|
|
324
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> {
|
|
325
480
|
const p = normalisePath(path);
|
|
326
481
|
const dir = await resolveDirCreate(this.root, parentDir(p));
|
|
327
482
|
const fileHandle = await dir.getFileHandle(baseName(p), { create: true });
|
|
328
483
|
const writable = await fileHandle.createWritable();
|
|
329
|
-
|
|
330
|
-
await writable.write(data);
|
|
331
|
-
} else {
|
|
332
|
-
await writable.write(data.buffer as ArrayBuffer);
|
|
333
|
-
}
|
|
334
|
-
await writable.close();
|
|
484
|
+
await writeAndClose(writable, toWritableBinary(data));
|
|
335
485
|
}
|
|
336
486
|
}
|
|
337
487
|
|
|
@@ -346,8 +496,10 @@ export async function openNativeFolder(): Promise<NativeFileSystemProvider> {
|
|
|
346
496
|
}
|
|
347
497
|
|
|
348
498
|
const handle = await (
|
|
349
|
-
globalThis as unknown as {
|
|
350
|
-
|
|
499
|
+
globalThis as unknown as {
|
|
500
|
+
showDirectoryPicker: (options: { mode: 'readwrite' }) => Promise<FileSystemDirectoryHandle>;
|
|
501
|
+
}
|
|
502
|
+
).showDirectoryPicker({ mode: 'readwrite' });
|
|
351
503
|
const id = `native-${handle.name}-${Date.now()}`;
|
|
352
504
|
await storeDirectoryHandle(id, handle);
|
|
353
505
|
return new NativeFileSystemProvider(id, handle);
|
|
@@ -370,11 +522,15 @@ export async function restoreNativeFolder(
|
|
|
370
522
|
queryPermission(desc: { mode: string }): Promise<string>;
|
|
371
523
|
requestPermission(desc: { mode: string }): Promise<string>;
|
|
372
524
|
};
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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;
|
|
378
534
|
}
|
|
379
535
|
|
|
380
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
|
+
}
|