@bendyline/docblocks 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-J32GU3F3.js → chunk-6IYMTYMQ.js} +249 -221
- package/dist/chunk-6IYMTYMQ.js.map +1 -0
- package/dist/{chunk-75UUF6C4.js → chunk-B56KWCSE.js} +2 -2
- package/dist/{chunk-RVQHY3XU.js → chunk-BNKV7MTO.js} +1 -1
- package/dist/{chunk-RVQHY3XU.js.map → chunk-BNKV7MTO.js.map} +1 -1
- package/dist/chunk-BXMF5YQJ.js +149 -0
- package/dist/chunk-BXMF5YQJ.js.map +1 -0
- package/dist/{chunk-PGR7LK6D.js → chunk-COYQXQK3.js} +14 -5
- package/dist/chunk-COYQXQK3.js.map +1 -0
- package/dist/{chunk-RQJRQF46.js → chunk-DN7A33LL.js} +42 -49
- package/dist/chunk-DN7A33LL.js.map +1 -0
- package/dist/chunk-FA7UNOXK.js +20 -0
- package/dist/chunk-FA7UNOXK.js.map +1 -0
- package/dist/chunk-GM5YXU4L.js +29 -0
- package/dist/chunk-GM5YXU4L.js.map +1 -0
- package/dist/{chunk-SI2LU2RP.js → chunk-JS6XFWQK.js} +2 -1
- package/dist/chunk-JS6XFWQK.js.map +1 -0
- package/dist/{chunk-EGNDADXB.js → chunk-MNSZ7EZN.js} +2 -2
- package/dist/chunk-P3DVZQ6S.js +228 -0
- package/dist/chunk-P3DVZQ6S.js.map +1 -0
- package/dist/chunk-P4WATND4.js +52 -0
- package/dist/chunk-P4WATND4.js.map +1 -0
- package/dist/chunk-RVPGQBWR.js +169 -0
- package/dist/chunk-RVPGQBWR.js.map +1 -0
- package/dist/chunk-U722UUFN.js +9 -0
- package/dist/chunk-U722UUFN.js.map +1 -0
- package/dist/{chunk-GCPBEMMH.js → chunk-VTCSF7VS.js} +11 -21
- package/dist/chunk-VTCSF7VS.js.map +1 -0
- package/dist/{chunk-2D7Y4IR3.js → chunk-WJKYY5QR.js} +123 -66
- package/dist/chunk-WJKYY5QR.js.map +1 -0
- package/dist/{chunk-5MYAICRE.js → chunk-XUML3MKV.js} +44 -56
- package/dist/chunk-XUML3MKV.js.map +1 -0
- package/dist/document/index.d.ts +358 -6
- package/dist/document/index.js +24 -3
- package/dist/document/index.js.map +1 -1
- package/dist/filesystem/electron.d.ts +73 -4
- package/dist/filesystem/electron.js +16 -3
- package/dist/filesystem/electron.js.map +1 -1
- package/dist/filesystem/index.d.ts +228 -22
- package/dist/filesystem/index.js +133 -19
- package/dist/filesystem/index.js.map +1 -1
- package/dist/filesystem/indexeddb.d.ts +284 -5
- package/dist/filesystem/indexeddb.js +22 -4
- package/dist/filesystem/indexeddb.js.map +1 -1
- package/dist/filesystem/memory.d.ts +152 -4
- package/dist/filesystem/memory.js +12 -3
- package/dist/filesystem/memory.js.map +1 -1
- package/dist/filesystem/native.d.ts +136 -4
- package/dist/filesystem/native.js +27 -3
- package/dist/filesystem/native.js.map +1 -1
- package/dist/host/index.d.ts +32 -9
- package/dist/host/index.js +26 -29
- package/dist/host/index.js.map +1 -1
- package/dist/index.d.ts +14 -10
- package/dist/index.js +204 -9
- package/dist/index.js.map +1 -1
- package/dist/mcp/index.d.ts +33 -4
- package/dist/mcp/index.js +31 -3
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/zod.d.ts +637 -635
- package/dist/mcp/zod.js +700 -884
- package/dist/mcp/zod.js.map +1 -1
- package/dist/share/index.d.ts +43 -0
- package/dist/share/index.js +13 -0
- package/dist/share/index.js.map +1 -0
- package/dist/{types-CsX6EN2t.d.ts → types-BEg5SQ1_.d.ts} +1 -1
- package/dist/{types-eOlt9J7n.d.ts → types-CueK0tvU.d.ts} +34 -11
- package/dist/vscode/index.d.ts +220 -3
- package/dist/vscode/index.js +382 -2
- package/dist/vscode/index.js.map +1 -1
- package/dist/workspace/index.d.ts +141 -4
- package/dist/workspace/index.js +28 -2
- package/dist/workspace/index.js.map +1 -1
- package/package.json +9 -5
- package/dist/chunk-2D7Y4IR3.js.map +0 -1
- package/dist/chunk-5MYAICRE.js.map +0 -1
- package/dist/chunk-AYZ4HN5M.js +0 -20
- package/dist/chunk-AYZ4HN5M.js.map +0 -1
- package/dist/chunk-GCPBEMMH.js.map +0 -1
- package/dist/chunk-J32GU3F3.js.map +0 -1
- package/dist/chunk-PGR7LK6D.js.map +0 -1
- package/dist/chunk-RQJRQF46.js.map +0 -1
- package/dist/chunk-SI2LU2RP.js.map +0 -1
- package/dist/chunk-W5UUUII5.js +0 -142
- package/dist/chunk-W5UUUII5.js.map +0 -1
- package/dist/document/document-session.d.ts +0 -127
- package/dist/document/document-session.d.ts.map +0 -1
- package/dist/document/document-session.js +0 -749
- package/dist/document/document-session.js.map +0 -1
- package/dist/document/filesystem-target.d.ts +0 -17
- package/dist/document/filesystem-target.d.ts.map +0 -1
- package/dist/document/filesystem-target.js +0 -70
- package/dist/document/filesystem-target.js.map +0 -1
- package/dist/document/index.d.ts.map +0 -1
- package/dist/document/recovery-journal.d.ts +0 -125
- package/dist/document/recovery-journal.d.ts.map +0 -1
- package/dist/document/recovery-journal.js +0 -385
- package/dist/document/recovery-journal.js.map +0 -1
- package/dist/document/types.d.ts +0 -89
- package/dist/document/types.d.ts.map +0 -1
- package/dist/document/types.js +0 -9
- package/dist/document/types.js.map +0 -1
- package/dist/filesystem/commit-lock.d.ts +0 -3
- package/dist/filesystem/commit-lock.d.ts.map +0 -1
- package/dist/filesystem/commit-lock.js +0 -20
- package/dist/filesystem/commit-lock.js.map +0 -1
- package/dist/filesystem/dbk-workspace.d.ts +0 -34
- package/dist/filesystem/dbk-workspace.d.ts.map +0 -1
- package/dist/filesystem/dbk-workspace.js +0 -85
- package/dist/filesystem/dbk-workspace.js.map +0 -1
- package/dist/filesystem/electron-provider-v2.d.ts +0 -36
- package/dist/filesystem/electron-provider-v2.d.ts.map +0 -1
- package/dist/filesystem/electron-provider-v2.js +0 -503
- package/dist/filesystem/electron-provider-v2.js.map +0 -1
- package/dist/filesystem/electron-provider.d.ts +0 -35
- package/dist/filesystem/electron-provider.d.ts.map +0 -1
- package/dist/filesystem/electron-provider.js +0 -69
- package/dist/filesystem/electron-provider.js.map +0 -1
- package/dist/filesystem/electron.d.ts.map +0 -1
- package/dist/filesystem/file-media-provider.d.ts +0 -23
- package/dist/filesystem/file-media-provider.d.ts.map +0 -1
- package/dist/filesystem/file-media-provider.js +0 -85
- package/dist/filesystem/file-media-provider.js.map +0 -1
- package/dist/filesystem/filesystem-content-container.d.ts +0 -24
- package/dist/filesystem/filesystem-content-container.d.ts.map +0 -1
- package/dist/filesystem/filesystem-content-container.js +0 -153
- package/dist/filesystem/filesystem-content-container.js.map +0 -1
- package/dist/filesystem/fs-error.d.ts +0 -46
- package/dist/filesystem/fs-error.d.ts.map +0 -1
- package/dist/filesystem/fs-error.js +0 -170
- package/dist/filesystem/fs-error.js.map +0 -1
- package/dist/filesystem/index.d.ts.map +0 -1
- package/dist/filesystem/indexeddb-content-container.d.ts +0 -22
- package/dist/filesystem/indexeddb-content-container.d.ts.map +0 -1
- package/dist/filesystem/indexeddb-content-container.js +0 -100
- package/dist/filesystem/indexeddb-content-container.js.map +0 -1
- package/dist/filesystem/indexeddb-provider-v2.d.ts +0 -96
- package/dist/filesystem/indexeddb-provider-v2.d.ts.map +0 -1
- package/dist/filesystem/indexeddb-provider-v2.js +0 -1241
- package/dist/filesystem/indexeddb-provider-v2.js.map +0 -1
- package/dist/filesystem/indexeddb-provider.d.ts +0 -39
- package/dist/filesystem/indexeddb-provider.d.ts.map +0 -1
- package/dist/filesystem/indexeddb-provider.js +0 -145
- package/dist/filesystem/indexeddb-provider.js.map +0 -1
- package/dist/filesystem/indexeddb-store.d.ts +0 -34
- package/dist/filesystem/indexeddb-store.d.ts.map +0 -1
- package/dist/filesystem/indexeddb-store.js +0 -165
- package/dist/filesystem/indexeddb-store.js.map +0 -1
- package/dist/filesystem/indexeddb.d.ts.map +0 -1
- package/dist/filesystem/legacy-v2-adapter.d.ts +0 -62
- package/dist/filesystem/legacy-v2-adapter.d.ts.map +0 -1
- package/dist/filesystem/legacy-v2-adapter.js +0 -499
- package/dist/filesystem/legacy-v2-adapter.js.map +0 -1
- package/dist/filesystem/memory-provider-v2.d.ts +0 -92
- package/dist/filesystem/memory-provider-v2.d.ts.map +0 -1
- package/dist/filesystem/memory-provider-v2.js +0 -615
- package/dist/filesystem/memory-provider-v2.js.map +0 -1
- package/dist/filesystem/memory-provider.d.ts +0 -54
- package/dist/filesystem/memory-provider.d.ts.map +0 -1
- package/dist/filesystem/memory-provider.js +0 -132
- package/dist/filesystem/memory-provider.js.map +0 -1
- package/dist/filesystem/memory.d.ts.map +0 -1
- package/dist/filesystem/move-entry.d.ts +0 -9
- package/dist/filesystem/move-entry.d.ts.map +0 -1
- package/dist/filesystem/move-entry.js +0 -103
- package/dist/filesystem/move-entry.js.map +0 -1
- package/dist/filesystem/move-error.d.ts +0 -53
- package/dist/filesystem/move-error.d.ts.map +0 -1
- package/dist/filesystem/move-error.js +0 -99
- package/dist/filesystem/move-error.js.map +0 -1
- package/dist/filesystem/native-provider-v2.d.ts +0 -75
- package/dist/filesystem/native-provider-v2.d.ts.map +0 -1
- package/dist/filesystem/native-provider-v2.js +0 -849
- package/dist/filesystem/native-provider-v2.js.map +0 -1
- package/dist/filesystem/native-provider.d.ts +0 -53
- package/dist/filesystem/native-provider.d.ts.map +0 -1
- package/dist/filesystem/native-provider.js +0 -471
- package/dist/filesystem/native-provider.js.map +0 -1
- package/dist/filesystem/native.d.ts.map +0 -1
- package/dist/filesystem/types.d.ts +0 -83
- package/dist/filesystem/types.d.ts.map +0 -1
- package/dist/filesystem/types.js +0 -16
- package/dist/filesystem/types.js.map +0 -1
- package/dist/filesystem/v2.d.ts +0 -172
- package/dist/filesystem/v2.d.ts.map +0 -1
- package/dist/filesystem/v2.js +0 -16
- package/dist/filesystem/v2.js.map +0 -1
- package/dist/filesystem/workspace-path.d.ts +0 -21
- package/dist/filesystem/workspace-path.d.ts.map +0 -1
- package/dist/filesystem/workspace-path.js +0 -80
- package/dist/filesystem/workspace-path.js.map +0 -1
- package/dist/host/filesystem-v2.d.ts +0 -45
- package/dist/host/filesystem-v2.d.ts.map +0 -1
- package/dist/host/filesystem-v2.js +0 -13
- package/dist/host/filesystem-v2.js.map +0 -1
- package/dist/host/git.d.ts +0 -200
- package/dist/host/git.d.ts.map +0 -1
- package/dist/host/git.js +0 -15
- package/dist/host/git.js.map +0 -1
- package/dist/host/index.d.ts.map +0 -1
- package/dist/host/types.d.ts +0 -232
- package/dist/host/types.d.ts.map +0 -1
- package/dist/host/types.js +0 -10
- package/dist/host/types.js.map +0 -1
- package/dist/host/wire-policy.d.ts +0 -22
- package/dist/host/wire-policy.d.ts.map +0 -1
- package/dist/host/wire-policy.js +0 -92
- package/dist/host/wire-policy.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/mcp/index.d.ts.map +0 -1
- package/dist/mcp/types.d.ts +0 -466
- package/dist/mcp/types.d.ts.map +0 -1
- package/dist/mcp/types.js +0 -25
- package/dist/mcp/types.js.map +0 -1
- package/dist/mcp/wire.d.ts +0 -29
- package/dist/mcp/wire.d.ts.map +0 -1
- package/dist/mcp/wire.js +0 -1046
- package/dist/mcp/wire.js.map +0 -1
- package/dist/mcp/zod.d.ts.map +0 -1
- package/dist/vscode/index.d.ts.map +0 -1
- package/dist/vscode/messages.d.ts +0 -204
- package/dist/vscode/messages.d.ts.map +0 -1
- package/dist/vscode/messages.js +0 -494
- package/dist/vscode/messages.js.map +0 -1
- package/dist/workspace/index.d.ts.map +0 -1
- package/dist/workspace/reconcile-electron.d.ts +0 -19
- package/dist/workspace/reconcile-electron.d.ts.map +0 -1
- package/dist/workspace/reconcile-electron.js +0 -42
- package/dist/workspace/reconcile-electron.js.map +0 -1
- package/dist/workspace/types.d.ts +0 -69
- package/dist/workspace/types.d.ts.map +0 -1
- package/dist/workspace/types.js +0 -2
- package/dist/workspace/types.js.map +0 -1
- package/dist/workspace/workspace-manager.d.ts +0 -47
- package/dist/workspace/workspace-manager.d.ts.map +0 -1
- package/dist/workspace/workspace-manager.js +0 -126
- package/dist/workspace/workspace-manager.js.map +0 -1
- package/src/document/document-session.ts +0 -834
- package/src/document/filesystem-target.ts +0 -109
- package/src/document/index.ts +0 -41
- package/src/document/recovery-journal.ts +0 -536
- package/src/document/types.ts +0 -116
- package/src/filesystem/commit-lock.ts +0 -29
- package/src/filesystem/dbk-workspace.ts +0 -135
- package/src/filesystem/electron-provider-v2.ts +0 -630
- package/src/filesystem/electron-provider.ts +0 -99
- package/src/filesystem/electron.ts +0 -3
- package/src/filesystem/file-media-provider.ts +0 -103
- package/src/filesystem/filesystem-content-container.ts +0 -164
- package/src/filesystem/fs-error.ts +0 -234
- package/src/filesystem/index.ts +0 -143
- package/src/filesystem/indexeddb-content-container.ts +0 -112
- package/src/filesystem/indexeddb-provider-v2.ts +0 -1740
- package/src/filesystem/indexeddb-provider.ts +0 -171
- package/src/filesystem/indexeddb-store.ts +0 -207
- package/src/filesystem/indexeddb.ts +0 -12
- package/src/filesystem/legacy-v2-adapter.ts +0 -779
- package/src/filesystem/memory-provider-v2.ts +0 -896
- package/src/filesystem/memory-provider.ts +0 -180
- package/src/filesystem/memory.ts +0 -15
- package/src/filesystem/move-entry.ts +0 -142
- package/src/filesystem/move-error.ts +0 -159
- package/src/filesystem/native-provider-v2.ts +0 -1165
- package/src/filesystem/native-provider.ts +0 -537
- package/src/filesystem/native.ts +0 -16
- package/src/filesystem/types.ts +0 -114
- package/src/filesystem/v2.ts +0 -231
- package/src/filesystem/workspace-path.ts +0 -92
- package/src/host/filesystem-v2.ts +0 -104
- package/src/host/git.ts +0 -246
- package/src/host/index.ts +0 -93
- package/src/host/types.ts +0 -274
- package/src/host/wire-policy.ts +0 -103
- package/src/index.ts +0 -10
- package/src/mcp/index.ts +0 -3
- package/src/mcp/types.ts +0 -594
- package/src/mcp/wire.ts +0 -1280
- package/src/mcp/zod.ts +0 -1000
- package/src/vscode/index.ts +0 -2
- package/src/vscode/messages.ts +0 -704
- package/src/workspace/index.ts +0 -17
- package/src/workspace/reconcile-electron.ts +0 -65
- package/src/workspace/types.ts +0 -67
- package/src/workspace/workspace-manager.ts +0 -151
- /package/dist/{chunk-75UUF6C4.js.map → chunk-B56KWCSE.js.map} +0 -0
- /package/dist/{chunk-EGNDADXB.js.map → chunk-MNSZ7EZN.js.map} +0 -0
|
@@ -1,779 +0,0 @@
|
|
|
1
|
-
import { FsError, fsErrorFromUnknown, type FsOperation } from './fs-error.js';
|
|
2
|
-
import type { FileSystemEntry, FileSystemProvider } from './types.js';
|
|
3
|
-
import {
|
|
4
|
-
parseFileSystemVersion,
|
|
5
|
-
type FileSystemAtomicity,
|
|
6
|
-
type FileSystemCaseSensitivity,
|
|
7
|
-
type FileSystemCreateDirectoryOptions,
|
|
8
|
-
type FileSystemDirectorySnapshot,
|
|
9
|
-
type FileSystemDurability,
|
|
10
|
-
type FileSystemEntrySnapshot,
|
|
11
|
-
type FileSystemFileRead,
|
|
12
|
-
type FileSystemFileSnapshot,
|
|
13
|
-
type FileSystemMoveOptions,
|
|
14
|
-
type FileSystemProviderCapabilities,
|
|
15
|
-
type FileSystemProviderV2,
|
|
16
|
-
type FileSystemRemoveOptions,
|
|
17
|
-
type FileSystemRemoveResult,
|
|
18
|
-
type FileSystemSnapshot,
|
|
19
|
-
type FileSystemSnapshotEntry,
|
|
20
|
-
type FileSystemSnapshotFile,
|
|
21
|
-
type FileSystemSymlinkPolicy,
|
|
22
|
-
type FileSystemVersion,
|
|
23
|
-
type FileSystemWatchListener,
|
|
24
|
-
type FileSystemWatchOptions,
|
|
25
|
-
type FileSystemWatchSubscription,
|
|
26
|
-
type FileSystemWriteOptions,
|
|
27
|
-
} from './v2.js';
|
|
28
|
-
import {
|
|
29
|
-
WORKSPACE_ROOT,
|
|
30
|
-
parseWorkspacePath,
|
|
31
|
-
workspacePathBasename,
|
|
32
|
-
workspacePathContains,
|
|
33
|
-
workspacePathDirname,
|
|
34
|
-
workspacePathToLegacy,
|
|
35
|
-
type WorkspacePath,
|
|
36
|
-
} from './workspace-path.js';
|
|
37
|
-
|
|
38
|
-
export type LegacyFilePayloadModel =
|
|
39
|
-
| 'split'
|
|
40
|
-
| 'binary-first-text-fallback'
|
|
41
|
-
| 'binary-authoritative';
|
|
42
|
-
|
|
43
|
-
export interface LegacyFileSystemProviderV2AdapterOptions {
|
|
44
|
-
/**
|
|
45
|
-
* Split stores expose independent raw text and binary channels and reject
|
|
46
|
-
* dual payloads. Binary-first stores synthesize text for binary records, so
|
|
47
|
-
* the adapter only asks for text after readBinary() returns null. Binary-
|
|
48
|
-
* authoritative stores expose every file through readBinary().
|
|
49
|
-
*/
|
|
50
|
-
readonly payloadModel: LegacyFilePayloadModel;
|
|
51
|
-
readonly writeAtomicity?: FileSystemAtomicity;
|
|
52
|
-
readonly moveAtomicity?: FileSystemAtomicity;
|
|
53
|
-
readonly caseSensitivity?: FileSystemCaseSensitivity;
|
|
54
|
-
readonly symlinkPolicy?: FileSystemSymlinkPolicy;
|
|
55
|
-
readonly durability?: FileSystemDurability;
|
|
56
|
-
/** Optional ownership hook; omitted when the adapter does not own the v1 provider. */
|
|
57
|
-
readonly dispose?: () => void | Promise<void>;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
interface CapturedTree {
|
|
61
|
-
readonly entries: ReadonlyMap<WorkspacePath, FileSystemSnapshotEntry>;
|
|
62
|
-
readonly version: FileSystemVersion;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const EPOCH = new Date(0).toISOString();
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Correctness bridge for hardened v1 providers while storage backends migrate.
|
|
69
|
-
*
|
|
70
|
-
* The adapter deliberately serializes every observable operation. This makes a
|
|
71
|
-
* version check and its mutation indivisible among v2 callers, but cannot make
|
|
72
|
-
* calls that bypass the adapter participate in that boundary. Content-derived
|
|
73
|
-
* versions make those external changes visible on the next adapter operation.
|
|
74
|
-
*/
|
|
75
|
-
export class LegacyFileSystemProviderV2Adapter implements FileSystemProviderV2 {
|
|
76
|
-
public readonly id: string;
|
|
77
|
-
public readonly label: string;
|
|
78
|
-
public readonly capabilities: FileSystemProviderCapabilities;
|
|
79
|
-
|
|
80
|
-
private operationTail: Promise<void> = Promise.resolve();
|
|
81
|
-
private readonly subscriptions = new Set<FileSystemWatchSubscription>();
|
|
82
|
-
private closing = false;
|
|
83
|
-
private disposed = false;
|
|
84
|
-
private disposePromise: Promise<void> | null = null;
|
|
85
|
-
|
|
86
|
-
public constructor(
|
|
87
|
-
private readonly provider: FileSystemProvider,
|
|
88
|
-
private readonly options: LegacyFileSystemProviderV2AdapterOptions,
|
|
89
|
-
) {
|
|
90
|
-
this.id = provider.id;
|
|
91
|
-
this.label = provider.label;
|
|
92
|
-
this.capabilities = Object.freeze({
|
|
93
|
-
writeAtomicity: options.writeAtomicity ?? 'none',
|
|
94
|
-
moveAtomicity: options.moveAtomicity ?? 'none',
|
|
95
|
-
// A tree snapshot spans multiple v1 calls; callers that bypass this
|
|
96
|
-
// adapter can still interleave, even though adapter operations serialize.
|
|
97
|
-
snapshotAtomicity: 'none',
|
|
98
|
-
conditionalWrite: 'process',
|
|
99
|
-
recursiveRemove: true,
|
|
100
|
-
watch: false,
|
|
101
|
-
caseSensitivity: options.caseSensitivity ?? 'platform',
|
|
102
|
-
symlinkPolicy: options.symlinkPolicy ?? 'unsupported',
|
|
103
|
-
durability: options.durability ?? 'best-effort',
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
public async stat(path: WorkspacePath): Promise<FileSystemEntrySnapshot | null> {
|
|
108
|
-
const canonical = this.canonical(path);
|
|
109
|
-
return this.enqueue('stat', async () => {
|
|
110
|
-
const entry = (await this.captureTree('stat')).entries.get(canonical);
|
|
111
|
-
return entry ? copyEntrySnapshot(entry) : null;
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
public async readFile(path: WorkspacePath): Promise<FileSystemFileRead | null> {
|
|
116
|
-
const canonical = this.canonical(path);
|
|
117
|
-
return this.enqueue('read', async () => {
|
|
118
|
-
const entry = (await this.captureTree('read')).entries.get(canonical);
|
|
119
|
-
if (!entry) return null;
|
|
120
|
-
if (entry.kind !== 'file') {
|
|
121
|
-
throw this.error('type-mismatch', 'read', canonical, 'Expected a file, found a directory.');
|
|
122
|
-
}
|
|
123
|
-
return Object.freeze({ entry: fileMetadata(entry), data: entry.data.slice(0) });
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
public async readDirectory(path: WorkspacePath): Promise<readonly FileSystemEntrySnapshot[]> {
|
|
128
|
-
const canonical = this.canonical(path);
|
|
129
|
-
return this.enqueue('list', async () => {
|
|
130
|
-
const tree = await this.captureTree('list');
|
|
131
|
-
const entry = tree.entries.get(canonical);
|
|
132
|
-
if (!entry) throw this.error('not-found', 'list', canonical, 'Directory does not exist.');
|
|
133
|
-
if (entry.kind !== 'directory') {
|
|
134
|
-
throw this.error('type-mismatch', 'list', canonical, 'Expected a directory, found a file.');
|
|
135
|
-
}
|
|
136
|
-
const children = [...tree.entries.entries()]
|
|
137
|
-
.filter(([entryPath]) => entryPath && workspacePathDirname(entryPath) === canonical)
|
|
138
|
-
.map(([, child]) => copyEntrySnapshot(child))
|
|
139
|
-
.sort(compareSnapshots);
|
|
140
|
-
return Object.freeze(children);
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
public async writeFile(
|
|
145
|
-
path: WorkspacePath,
|
|
146
|
-
data: ArrayBuffer | Uint8Array,
|
|
147
|
-
options: FileSystemWriteOptions = {},
|
|
148
|
-
): Promise<FileSystemFileSnapshot> {
|
|
149
|
-
const canonical = this.canonicalNonRoot(path, 'write');
|
|
150
|
-
const ownedData = copyBytes(data);
|
|
151
|
-
return this.enqueue('write', async () => {
|
|
152
|
-
const tree = await this.captureTree('write');
|
|
153
|
-
const existing = tree.entries.get(canonical);
|
|
154
|
-
if (existing?.kind === 'directory') {
|
|
155
|
-
throw this.error(
|
|
156
|
-
'type-mismatch',
|
|
157
|
-
'write',
|
|
158
|
-
canonical,
|
|
159
|
-
'Cannot replace a directory with a file.',
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const mode = options.mode ?? 'upsert';
|
|
164
|
-
if (mode === 'create' && existing) {
|
|
165
|
-
throw this.error('already-exists', 'write', canonical, 'File already exists.');
|
|
166
|
-
}
|
|
167
|
-
if (mode === 'replace' && !existing) {
|
|
168
|
-
throw this.error('not-found', 'write', canonical, 'File does not exist.');
|
|
169
|
-
}
|
|
170
|
-
this.assertExpectedVersion(existing, options.expectedVersion, 'write', canonical);
|
|
171
|
-
this.assertParents(tree, canonical, options.createParents ?? false, 'write');
|
|
172
|
-
|
|
173
|
-
await this.invoke(
|
|
174
|
-
() => this.provider.writeBinary(workspacePathToLegacy(canonical), ownedData),
|
|
175
|
-
'write',
|
|
176
|
-
canonical,
|
|
177
|
-
);
|
|
178
|
-
const written = (await this.captureTree('write')).entries.get(canonical);
|
|
179
|
-
if (written?.kind !== 'file' || !equalBytes(written.data, ownedData)) {
|
|
180
|
-
throw this.error(
|
|
181
|
-
'io',
|
|
182
|
-
'write',
|
|
183
|
-
canonical,
|
|
184
|
-
'The legacy provider did not persist the requested bytes.',
|
|
185
|
-
);
|
|
186
|
-
}
|
|
187
|
-
return fileMetadata(written);
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
public async createDirectory(
|
|
192
|
-
path: WorkspacePath,
|
|
193
|
-
options: FileSystemCreateDirectoryOptions = {},
|
|
194
|
-
): Promise<FileSystemDirectorySnapshot> {
|
|
195
|
-
const canonical = this.canonical(path);
|
|
196
|
-
return this.enqueue('create-directory', async () => {
|
|
197
|
-
const tree = await this.captureTree('create-directory');
|
|
198
|
-
const existing = tree.entries.get(canonical);
|
|
199
|
-
if (existing) {
|
|
200
|
-
if (existing.kind !== 'directory') {
|
|
201
|
-
throw this.error(
|
|
202
|
-
'type-mismatch',
|
|
203
|
-
'create-directory',
|
|
204
|
-
canonical,
|
|
205
|
-
'Cannot replace a file with a directory.',
|
|
206
|
-
);
|
|
207
|
-
}
|
|
208
|
-
if ((options.mode ?? 'ensure') === 'create') {
|
|
209
|
-
throw this.error(
|
|
210
|
-
'already-exists',
|
|
211
|
-
'create-directory',
|
|
212
|
-
canonical,
|
|
213
|
-
'Directory already exists.',
|
|
214
|
-
);
|
|
215
|
-
}
|
|
216
|
-
return copyDirectorySnapshot(existing);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
this.assertParents(tree, canonical, options.createParents ?? false, 'create-directory');
|
|
220
|
-
await this.invoke(
|
|
221
|
-
() => this.provider.createDirectory(workspacePathToLegacy(canonical)),
|
|
222
|
-
'create-directory',
|
|
223
|
-
canonical,
|
|
224
|
-
);
|
|
225
|
-
const created = (await this.captureTree('create-directory')).entries.get(canonical);
|
|
226
|
-
if (created?.kind !== 'directory') {
|
|
227
|
-
throw this.error(
|
|
228
|
-
'io',
|
|
229
|
-
'create-directory',
|
|
230
|
-
canonical,
|
|
231
|
-
'The legacy provider did not create the directory.',
|
|
232
|
-
);
|
|
233
|
-
}
|
|
234
|
-
return copyDirectorySnapshot(created);
|
|
235
|
-
});
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
public async remove(
|
|
239
|
-
path: WorkspacePath,
|
|
240
|
-
options: FileSystemRemoveOptions = {},
|
|
241
|
-
): Promise<FileSystemRemoveResult> {
|
|
242
|
-
const canonical = this.canonicalNonRoot(path, 'remove');
|
|
243
|
-
return this.enqueue('remove', async () => {
|
|
244
|
-
const tree = await this.captureTree('remove');
|
|
245
|
-
const existing = tree.entries.get(canonical);
|
|
246
|
-
if (!existing) {
|
|
247
|
-
if ((options.missing ?? 'error') === 'ignore') {
|
|
248
|
-
return Object.freeze({ removed: false, version: tree.version });
|
|
249
|
-
}
|
|
250
|
-
throw this.error('not-found', 'remove', canonical, 'Entry does not exist.');
|
|
251
|
-
}
|
|
252
|
-
this.assertExpectedVersion(existing, options.expectedVersion, 'remove', canonical);
|
|
253
|
-
if (
|
|
254
|
-
existing.kind === 'directory' &&
|
|
255
|
-
!options.recursive &&
|
|
256
|
-
[...tree.entries.keys()].some((candidate) =>
|
|
257
|
-
workspacePathContains(canonical, candidate, false),
|
|
258
|
-
)
|
|
259
|
-
) {
|
|
260
|
-
throw this.error('not-empty', 'remove', canonical, 'Directory is not empty.');
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
await this.invoke(
|
|
264
|
-
() => this.provider.delete(workspacePathToLegacy(canonical)),
|
|
265
|
-
'remove',
|
|
266
|
-
canonical,
|
|
267
|
-
);
|
|
268
|
-
const after = await this.captureTree('remove');
|
|
269
|
-
if (
|
|
270
|
-
[...after.entries.keys()].some((candidate) => workspacePathContains(canonical, candidate))
|
|
271
|
-
) {
|
|
272
|
-
throw this.error(
|
|
273
|
-
'io',
|
|
274
|
-
'remove',
|
|
275
|
-
canonical,
|
|
276
|
-
'The legacy provider did not remove the complete entry.',
|
|
277
|
-
);
|
|
278
|
-
}
|
|
279
|
-
return Object.freeze({ removed: true, version: after.version });
|
|
280
|
-
});
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
public async move(
|
|
284
|
-
oldPath: WorkspacePath,
|
|
285
|
-
newPath: WorkspacePath,
|
|
286
|
-
options: FileSystemMoveOptions = {},
|
|
287
|
-
): Promise<FileSystemEntrySnapshot> {
|
|
288
|
-
const sourcePath = this.canonicalNonRoot(oldPath, 'move');
|
|
289
|
-
const destinationPath = this.canonicalNonRoot(newPath, 'move');
|
|
290
|
-
return this.enqueue('move', async () => {
|
|
291
|
-
const tree = await this.captureTree('move');
|
|
292
|
-
const source = tree.entries.get(sourcePath);
|
|
293
|
-
if (!source) throw this.error('not-found', 'move', sourcePath, 'Source does not exist.');
|
|
294
|
-
this.assertExpectedVersion(source, options.expectedVersion, 'move', sourcePath);
|
|
295
|
-
if (sourcePath === destinationPath) return copyEntrySnapshot(source);
|
|
296
|
-
if (tree.entries.has(destinationPath)) {
|
|
297
|
-
throw new FsError('already-exists', 'Destination already exists.', {
|
|
298
|
-
operation: 'move',
|
|
299
|
-
path: sourcePath,
|
|
300
|
-
destinationPath,
|
|
301
|
-
});
|
|
302
|
-
}
|
|
303
|
-
if (
|
|
304
|
-
source.kind === 'directory' &&
|
|
305
|
-
workspacePathContains(sourcePath, destinationPath, false)
|
|
306
|
-
) {
|
|
307
|
-
throw new FsError('invalid-path', 'Cannot move a directory into itself.', {
|
|
308
|
-
operation: 'move',
|
|
309
|
-
path: sourcePath,
|
|
310
|
-
destinationPath,
|
|
311
|
-
});
|
|
312
|
-
}
|
|
313
|
-
this.assertParents(tree, destinationPath, options.createParents ?? false, 'move');
|
|
314
|
-
|
|
315
|
-
const sourceEntries = [...tree.entries.entries()].filter(([candidate]) =>
|
|
316
|
-
workspacePathContains(sourcePath, candidate),
|
|
317
|
-
);
|
|
318
|
-
await this.invoke(
|
|
319
|
-
() =>
|
|
320
|
-
this.provider.rename(
|
|
321
|
-
workspacePathToLegacy(sourcePath),
|
|
322
|
-
workspacePathToLegacy(destinationPath),
|
|
323
|
-
),
|
|
324
|
-
'move',
|
|
325
|
-
sourcePath,
|
|
326
|
-
destinationPath,
|
|
327
|
-
);
|
|
328
|
-
|
|
329
|
-
const after = await this.captureTree('move');
|
|
330
|
-
if (
|
|
331
|
-
[...after.entries.keys()].some((candidate) => workspacePathContains(sourcePath, candidate))
|
|
332
|
-
) {
|
|
333
|
-
throw this.error(
|
|
334
|
-
'io',
|
|
335
|
-
'move',
|
|
336
|
-
sourcePath,
|
|
337
|
-
'The legacy provider left source entries behind.',
|
|
338
|
-
);
|
|
339
|
-
}
|
|
340
|
-
for (const [oldEntryPath, oldEntry] of sourceEntries) {
|
|
341
|
-
const suffix = oldEntryPath.slice(sourcePath.length);
|
|
342
|
-
const movedPath = parseWorkspacePath(`${destinationPath}${suffix}`);
|
|
343
|
-
const moved = after.entries.get(movedPath);
|
|
344
|
-
if (!moved || moved.kind !== oldEntry.kind || moved.version !== oldEntry.version) {
|
|
345
|
-
throw new FsError(
|
|
346
|
-
'io',
|
|
347
|
-
'The legacy provider did not move the complete entry unchanged.',
|
|
348
|
-
{
|
|
349
|
-
operation: 'move',
|
|
350
|
-
path: oldEntryPath,
|
|
351
|
-
destinationPath: movedPath,
|
|
352
|
-
},
|
|
353
|
-
);
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
return copyEntrySnapshot(after.entries.get(destinationPath)!);
|
|
357
|
-
});
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
public async snapshot(): Promise<FileSystemSnapshot> {
|
|
361
|
-
return this.enqueue('snapshot', async () => {
|
|
362
|
-
const tree = await this.captureTree('snapshot');
|
|
363
|
-
const entries = [...tree.entries.values()]
|
|
364
|
-
.sort((left, right) => compareText(left.path, right.path))
|
|
365
|
-
.map((entry) => copySnapshotEntry(entry));
|
|
366
|
-
return Object.freeze({ version: tree.version, entries: Object.freeze(entries) });
|
|
367
|
-
});
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
public watch(
|
|
371
|
-
_listener: FileSystemWatchListener,
|
|
372
|
-
options: FileSystemWatchOptions = {},
|
|
373
|
-
): FileSystemWatchSubscription {
|
|
374
|
-
this.assertAccepting('watch');
|
|
375
|
-
const error = new FsError(
|
|
376
|
-
'not-supported',
|
|
377
|
-
'The legacy adapter cannot observe external changes.',
|
|
378
|
-
{
|
|
379
|
-
operation: 'watch',
|
|
380
|
-
},
|
|
381
|
-
);
|
|
382
|
-
let closed = false;
|
|
383
|
-
let disposePromise: Promise<void> | null = null;
|
|
384
|
-
const ready = Promise.reject(error);
|
|
385
|
-
void ready.catch((caught: FsError) => {
|
|
386
|
-
try {
|
|
387
|
-
options.onError?.(caught);
|
|
388
|
-
} catch {
|
|
389
|
-
// An observational error callback cannot change subscription state.
|
|
390
|
-
}
|
|
391
|
-
});
|
|
392
|
-
const subscription: FileSystemWatchSubscription = {
|
|
393
|
-
get closed() {
|
|
394
|
-
return closed;
|
|
395
|
-
},
|
|
396
|
-
ready,
|
|
397
|
-
dispose: () => {
|
|
398
|
-
if (disposePromise) return disposePromise;
|
|
399
|
-
closed = true;
|
|
400
|
-
this.subscriptions.delete(subscription);
|
|
401
|
-
disposePromise = Promise.resolve();
|
|
402
|
-
return disposePromise;
|
|
403
|
-
},
|
|
404
|
-
};
|
|
405
|
-
this.subscriptions.add(subscription);
|
|
406
|
-
return subscription;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
public dispose(): Promise<void> {
|
|
410
|
-
if (this.disposePromise) return this.disposePromise;
|
|
411
|
-
this.closing = true;
|
|
412
|
-
const run = this.operationTail.then(async () => {
|
|
413
|
-
try {
|
|
414
|
-
await Promise.all([...this.subscriptions].map((subscription) => subscription.dispose()));
|
|
415
|
-
await this.options.dispose?.();
|
|
416
|
-
} catch (error: unknown) {
|
|
417
|
-
throw fsErrorFromUnknown(error, { operation: 'dispose' });
|
|
418
|
-
} finally {
|
|
419
|
-
this.disposed = true;
|
|
420
|
-
}
|
|
421
|
-
});
|
|
422
|
-
this.operationTail = run.then(
|
|
423
|
-
() => undefined,
|
|
424
|
-
() => undefined,
|
|
425
|
-
);
|
|
426
|
-
this.disposePromise = run;
|
|
427
|
-
return run;
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
private enqueue<T>(operation: FsOperation, task: () => Promise<T>): Promise<T> {
|
|
431
|
-
this.assertAccepting(operation);
|
|
432
|
-
const run = this.operationTail.then(task);
|
|
433
|
-
this.operationTail = run.then(
|
|
434
|
-
() => undefined,
|
|
435
|
-
() => undefined,
|
|
436
|
-
);
|
|
437
|
-
return run;
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
private assertAccepting(operation: FsOperation): void {
|
|
441
|
-
if (!this.closing && !this.disposed) return;
|
|
442
|
-
throw new FsError('disposed', 'Filesystem provider is disposed.', { operation });
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
private canonical(path: WorkspacePath): WorkspacePath {
|
|
446
|
-
return parseWorkspacePath(path);
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
private canonicalNonRoot(path: WorkspacePath, operation: FsOperation): WorkspacePath {
|
|
450
|
-
const canonical = this.canonical(path);
|
|
451
|
-
if (!canonical) {
|
|
452
|
-
throw this.error(
|
|
453
|
-
'invalid-path',
|
|
454
|
-
operation,
|
|
455
|
-
canonical,
|
|
456
|
-
'The workspace root is not an entry target.',
|
|
457
|
-
);
|
|
458
|
-
}
|
|
459
|
-
return canonical;
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
private assertExpectedVersion(
|
|
463
|
-
entry: FileSystemSnapshotEntry | undefined,
|
|
464
|
-
expected: FileSystemVersion | null | undefined,
|
|
465
|
-
operation: FsOperation,
|
|
466
|
-
path: WorkspacePath,
|
|
467
|
-
): void {
|
|
468
|
-
if (expected === undefined) return;
|
|
469
|
-
if (expected === null ? !entry : entry?.version === expected) return;
|
|
470
|
-
throw this.error(
|
|
471
|
-
'conflict',
|
|
472
|
-
operation,
|
|
473
|
-
path,
|
|
474
|
-
'Entry version does not match the expected version.',
|
|
475
|
-
);
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
private assertParents(
|
|
479
|
-
tree: CapturedTree,
|
|
480
|
-
path: WorkspacePath,
|
|
481
|
-
createParents: boolean,
|
|
482
|
-
operation: FsOperation,
|
|
483
|
-
): void {
|
|
484
|
-
let parent = workspacePathDirname(path);
|
|
485
|
-
const parents: WorkspacePath[] = [];
|
|
486
|
-
while (parent) {
|
|
487
|
-
parents.unshift(parent);
|
|
488
|
-
parent = workspacePathDirname(parent);
|
|
489
|
-
}
|
|
490
|
-
for (const candidate of parents) {
|
|
491
|
-
const entry = tree.entries.get(candidate);
|
|
492
|
-
if (entry?.kind === 'file') {
|
|
493
|
-
throw this.error('type-mismatch', operation, candidate, 'A parent path is a file.');
|
|
494
|
-
}
|
|
495
|
-
if (!entry && !createParents) {
|
|
496
|
-
throw this.error('not-found', operation, candidate, 'Parent directory does not exist.');
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
private async captureTree(operation: FsOperation): Promise<CapturedTree> {
|
|
502
|
-
const entries = new Map<WorkspacePath, FileSystemSnapshotEntry>();
|
|
503
|
-
const visiting = new Set<WorkspacePath>();
|
|
504
|
-
|
|
505
|
-
const visitDirectory = async (path: WorkspacePath): Promise<FileSystemDirectorySnapshot> => {
|
|
506
|
-
if (visiting.has(path)) {
|
|
507
|
-
throw this.error('io', operation, path, 'The legacy provider returned a directory cycle.');
|
|
508
|
-
}
|
|
509
|
-
visiting.add(path);
|
|
510
|
-
const listed = await this.invoke(
|
|
511
|
-
() => this.provider.readDirectory(workspacePathToLegacy(path)),
|
|
512
|
-
operation,
|
|
513
|
-
path,
|
|
514
|
-
);
|
|
515
|
-
if (!Array.isArray(listed)) {
|
|
516
|
-
throw this.error(
|
|
517
|
-
'io',
|
|
518
|
-
operation,
|
|
519
|
-
path,
|
|
520
|
-
'The legacy provider returned an invalid directory listing.',
|
|
521
|
-
);
|
|
522
|
-
}
|
|
523
|
-
const children: FileSystemSnapshotEntry[] = [];
|
|
524
|
-
const seen = new Set<WorkspacePath>();
|
|
525
|
-
for (const candidate of listed as readonly unknown[]) {
|
|
526
|
-
if (!isLegacyEntry(candidate)) {
|
|
527
|
-
throw this.error(
|
|
528
|
-
'io',
|
|
529
|
-
operation,
|
|
530
|
-
path,
|
|
531
|
-
'The legacy provider returned an invalid directory entry.',
|
|
532
|
-
);
|
|
533
|
-
}
|
|
534
|
-
const legacyEntry = candidate;
|
|
535
|
-
const childPath = parseWorkspacePath(legacyEntry.path);
|
|
536
|
-
if (
|
|
537
|
-
!childPath ||
|
|
538
|
-
workspacePathDirname(childPath) !== path ||
|
|
539
|
-
workspacePathBasename(childPath) !== legacyEntry.name
|
|
540
|
-
) {
|
|
541
|
-
throw this.error(
|
|
542
|
-
'io',
|
|
543
|
-
operation,
|
|
544
|
-
path,
|
|
545
|
-
'The legacy provider returned a non-child entry.',
|
|
546
|
-
);
|
|
547
|
-
}
|
|
548
|
-
if (seen.has(childPath) || entries.has(childPath)) {
|
|
549
|
-
throw this.error(
|
|
550
|
-
'io',
|
|
551
|
-
operation,
|
|
552
|
-
childPath,
|
|
553
|
-
'The legacy provider returned duplicate entry paths.',
|
|
554
|
-
);
|
|
555
|
-
}
|
|
556
|
-
seen.add(childPath);
|
|
557
|
-
|
|
558
|
-
if (legacyEntry.kind === 'directory') {
|
|
559
|
-
children.push(await visitDirectory(childPath));
|
|
560
|
-
} else {
|
|
561
|
-
const file = await this.captureFile(childPath, operation);
|
|
562
|
-
entries.set(childPath, file);
|
|
563
|
-
children.push(file);
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
children.sort(compareSnapshots);
|
|
567
|
-
const version = await contentVersion(
|
|
568
|
-
'directory',
|
|
569
|
-
new TextEncoder().encode(
|
|
570
|
-
children
|
|
571
|
-
.map((child) => `${child.kind}\0${child.name.length}:${child.name}\0${child.version}`)
|
|
572
|
-
.join('\n'),
|
|
573
|
-
),
|
|
574
|
-
operation,
|
|
575
|
-
path,
|
|
576
|
-
);
|
|
577
|
-
const directory = Object.freeze({
|
|
578
|
-
kind: 'directory' as const,
|
|
579
|
-
path,
|
|
580
|
-
name: workspacePathBasename(path),
|
|
581
|
-
size: null,
|
|
582
|
-
version,
|
|
583
|
-
lastModified: newestLastModified(children),
|
|
584
|
-
});
|
|
585
|
-
entries.set(path, directory);
|
|
586
|
-
visiting.delete(path);
|
|
587
|
-
return directory;
|
|
588
|
-
};
|
|
589
|
-
|
|
590
|
-
const root = await visitDirectory(WORKSPACE_ROOT);
|
|
591
|
-
return { entries, version: root.version };
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
private async captureFile(
|
|
595
|
-
path: WorkspacePath,
|
|
596
|
-
operation: FsOperation,
|
|
597
|
-
): Promise<FileSystemSnapshotFile> {
|
|
598
|
-
const legacyPath = workspacePathToLegacy(path);
|
|
599
|
-
let data: ArrayBuffer | null;
|
|
600
|
-
if (this.options.payloadModel === 'binary-authoritative') {
|
|
601
|
-
const binary = await this.invoke(() => this.provider.readBinary(legacyPath), operation, path);
|
|
602
|
-
data = binary ? binary.slice(0) : null;
|
|
603
|
-
} else if (this.options.payloadModel === 'binary-first-text-fallback') {
|
|
604
|
-
const binary = await this.invoke(() => this.provider.readBinary(legacyPath), operation, path);
|
|
605
|
-
if (binary) {
|
|
606
|
-
data = binary.slice(0);
|
|
607
|
-
} else {
|
|
608
|
-
const text = await this.invoke(() => this.provider.readFile(legacyPath), operation, path);
|
|
609
|
-
data = text === null ? null : new TextEncoder().encode(text).buffer;
|
|
610
|
-
}
|
|
611
|
-
} else {
|
|
612
|
-
const binary = await this.invoke(() => this.provider.readBinary(legacyPath), operation, path);
|
|
613
|
-
const text = await this.invoke(() => this.provider.readFile(legacyPath), operation, path);
|
|
614
|
-
if (binary !== null && text !== null) {
|
|
615
|
-
throw this.error(
|
|
616
|
-
'conflict',
|
|
617
|
-
operation,
|
|
618
|
-
path,
|
|
619
|
-
'The split legacy store contains both text and binary payloads.',
|
|
620
|
-
);
|
|
621
|
-
}
|
|
622
|
-
data = binary
|
|
623
|
-
? binary.slice(0)
|
|
624
|
-
: text === null
|
|
625
|
-
? null
|
|
626
|
-
: new TextEncoder().encode(text).buffer;
|
|
627
|
-
}
|
|
628
|
-
if (!data) {
|
|
629
|
-
throw this.error(
|
|
630
|
-
'io',
|
|
631
|
-
operation,
|
|
632
|
-
path,
|
|
633
|
-
'The legacy provider listed a file without a payload.',
|
|
634
|
-
);
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
const meta = await this.invoke(() => this.provider.stat(legacyPath), operation, path);
|
|
638
|
-
if (
|
|
639
|
-
!meta ||
|
|
640
|
-
parseWorkspacePath(meta.path) !== path ||
|
|
641
|
-
meta.name !== workspacePathBasename(path) ||
|
|
642
|
-
meta.size !== data.byteLength ||
|
|
643
|
-
!Number.isFinite(Date.parse(meta.lastModified))
|
|
644
|
-
) {
|
|
645
|
-
throw this.error(
|
|
646
|
-
'io',
|
|
647
|
-
operation,
|
|
648
|
-
path,
|
|
649
|
-
'The legacy provider returned inconsistent file metadata.',
|
|
650
|
-
);
|
|
651
|
-
}
|
|
652
|
-
return Object.freeze({
|
|
653
|
-
kind: 'file' as const,
|
|
654
|
-
path,
|
|
655
|
-
name: workspacePathBasename(path),
|
|
656
|
-
size: data.byteLength,
|
|
657
|
-
version: await contentVersion('file', new Uint8Array(data), operation, path),
|
|
658
|
-
lastModified: meta.lastModified,
|
|
659
|
-
data,
|
|
660
|
-
});
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
private async invoke<T>(
|
|
664
|
-
task: () => Promise<T>,
|
|
665
|
-
operation: FsOperation,
|
|
666
|
-
path?: WorkspacePath,
|
|
667
|
-
destinationPath?: WorkspacePath,
|
|
668
|
-
): Promise<T> {
|
|
669
|
-
try {
|
|
670
|
-
return await task();
|
|
671
|
-
} catch (error: unknown) {
|
|
672
|
-
throw fsErrorFromUnknown(error, { operation, path, destinationPath });
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
private error(
|
|
677
|
-
code: ConstructorParameters<typeof FsError>[0],
|
|
678
|
-
operation: FsOperation,
|
|
679
|
-
path: WorkspacePath,
|
|
680
|
-
message: string,
|
|
681
|
-
): FsError {
|
|
682
|
-
return new FsError(code, message, { operation, path });
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
async function contentVersion(
|
|
687
|
-
kind: 'file' | 'directory',
|
|
688
|
-
data: Uint8Array,
|
|
689
|
-
operation: FsOperation,
|
|
690
|
-
path: WorkspacePath,
|
|
691
|
-
): Promise<FileSystemVersion> {
|
|
692
|
-
const prefix = new TextEncoder().encode(`${kind}\0`);
|
|
693
|
-
const input = new Uint8Array(prefix.byteLength + data.byteLength);
|
|
694
|
-
input.set(prefix);
|
|
695
|
-
input.set(data, prefix.byteLength);
|
|
696
|
-
let digest: ArrayBuffer;
|
|
697
|
-
try {
|
|
698
|
-
digest = await globalThis.crypto.subtle.digest('SHA-256', input.buffer as ArrayBuffer);
|
|
699
|
-
} catch (error: unknown) {
|
|
700
|
-
throw fsErrorFromUnknown(error, { operation, path, defaultCode: 'not-supported' });
|
|
701
|
-
}
|
|
702
|
-
const hex = [...new Uint8Array(digest)]
|
|
703
|
-
.map((value) => value.toString(16).padStart(2, '0'))
|
|
704
|
-
.join('');
|
|
705
|
-
return parseFileSystemVersion(`sha256:${hex}`);
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
function isLegacyEntry(value: unknown): value is FileSystemEntry {
|
|
709
|
-
if (!value || typeof value !== 'object') return false;
|
|
710
|
-
const candidate = value as Partial<Record<keyof FileSystemEntry, unknown>>;
|
|
711
|
-
return (
|
|
712
|
-
(candidate.kind === 'file' || candidate.kind === 'directory') &&
|
|
713
|
-
typeof candidate.name === 'string' &&
|
|
714
|
-
typeof candidate.path === 'string'
|
|
715
|
-
);
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
function copyBytes(data: ArrayBuffer | Uint8Array): ArrayBuffer {
|
|
719
|
-
const source = ArrayBuffer.isView(data)
|
|
720
|
-
? new Uint8Array(data.buffer, data.byteOffset, data.byteLength)
|
|
721
|
-
: new Uint8Array(data);
|
|
722
|
-
const copy = new Uint8Array(source.byteLength);
|
|
723
|
-
copy.set(source);
|
|
724
|
-
return copy.buffer as ArrayBuffer;
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
function equalBytes(left: ArrayBuffer, right: ArrayBuffer): boolean {
|
|
728
|
-
const leftBytes = new Uint8Array(left);
|
|
729
|
-
const rightBytes = new Uint8Array(right);
|
|
730
|
-
return (
|
|
731
|
-
leftBytes.byteLength === rightBytes.byteLength &&
|
|
732
|
-
leftBytes.every((value, index) => value === rightBytes[index])
|
|
733
|
-
);
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
function fileMetadata(file: FileSystemSnapshotFile): FileSystemFileSnapshot {
|
|
737
|
-
return Object.freeze({
|
|
738
|
-
kind: 'file',
|
|
739
|
-
path: file.path,
|
|
740
|
-
name: file.name,
|
|
741
|
-
size: file.size,
|
|
742
|
-
version: file.version,
|
|
743
|
-
lastModified: file.lastModified,
|
|
744
|
-
});
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
function copyDirectorySnapshot(
|
|
748
|
-
directory: FileSystemDirectorySnapshot,
|
|
749
|
-
): FileSystemDirectorySnapshot {
|
|
750
|
-
return Object.freeze({ ...directory });
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
function copyEntrySnapshot(entry: FileSystemSnapshotEntry): FileSystemEntrySnapshot {
|
|
754
|
-
return entry.kind === 'file' ? fileMetadata(entry) : copyDirectorySnapshot(entry);
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
function copySnapshotEntry(entry: FileSystemSnapshotEntry): FileSystemSnapshotEntry {
|
|
758
|
-
return entry.kind === 'file'
|
|
759
|
-
? Object.freeze({ ...fileMetadata(entry), data: entry.data.slice(0) })
|
|
760
|
-
: copyDirectorySnapshot(entry);
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
function newestLastModified(children: readonly FileSystemSnapshotEntry[]): string {
|
|
764
|
-
let newest = 0;
|
|
765
|
-
for (const child of children) {
|
|
766
|
-
const timestamp = Date.parse(child.lastModified);
|
|
767
|
-
if (Number.isFinite(timestamp)) newest = Math.max(newest, timestamp);
|
|
768
|
-
}
|
|
769
|
-
return newest ? new Date(newest).toISOString() : EPOCH;
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
function compareSnapshots(left: FileSystemEntrySnapshot, right: FileSystemEntrySnapshot): number {
|
|
773
|
-
if (left.kind !== right.kind) return left.kind === 'directory' ? -1 : 1;
|
|
774
|
-
return compareText(left.name, right.name);
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
function compareText(left: string, right: string): number {
|
|
778
|
-
return left < right ? -1 : left > right ? 1 : 0;
|
|
779
|
-
}
|