@b9g/filesystem 0.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/package.json +71 -0
- package/src/bun-s3.d.ts +71 -0
- package/src/bun-s3.js +244 -0
- package/src/index.d.ts +5 -0
- package/src/index.js +17 -0
- package/src/memory.d.ts +18 -0
- package/src/memory.js +223 -0
- package/src/node.d.ts +67 -0
- package/src/node.js +266 -0
- package/src/registry.d.ts +43 -0
- package/src/registry.js +63 -0
- package/src/types.d.ts +31 -0
- package/src/types.js +1 -0
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@b9g/filesystem",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Universal File System Access API implementations for all platforms",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"devDependencies": {
|
|
7
|
+
"@b9g/libuild": "^0.1.10",
|
|
8
|
+
"@types/bun": "^1.2.2"
|
|
9
|
+
},
|
|
10
|
+
"peerDependencies": {
|
|
11
|
+
"@types/wicg-file-system-access": "^2023.10.7"
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"types": "src/index.d.ts",
|
|
15
|
+
"module": "src/index.js",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./src/index.d.ts",
|
|
19
|
+
"import": "./src/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./bun-s3": {
|
|
22
|
+
"types": "./src/bun-s3.d.ts",
|
|
23
|
+
"import": "./src/bun-s3.js"
|
|
24
|
+
},
|
|
25
|
+
"./bun-s3.js": {
|
|
26
|
+
"types": "./src/bun-s3.d.ts",
|
|
27
|
+
"import": "./src/bun-s3.js"
|
|
28
|
+
},
|
|
29
|
+
"./index": {
|
|
30
|
+
"types": "./src/index.d.ts",
|
|
31
|
+
"import": "./src/index.js"
|
|
32
|
+
},
|
|
33
|
+
"./index.js": {
|
|
34
|
+
"types": "./src/index.d.ts",
|
|
35
|
+
"import": "./src/index.js"
|
|
36
|
+
},
|
|
37
|
+
"./memory": {
|
|
38
|
+
"types": "./src/memory.d.ts",
|
|
39
|
+
"import": "./src/memory.js"
|
|
40
|
+
},
|
|
41
|
+
"./memory.js": {
|
|
42
|
+
"types": "./src/memory.d.ts",
|
|
43
|
+
"import": "./src/memory.js"
|
|
44
|
+
},
|
|
45
|
+
"./node": {
|
|
46
|
+
"types": "./src/node.d.ts",
|
|
47
|
+
"import": "./src/node.js"
|
|
48
|
+
},
|
|
49
|
+
"./node.js": {
|
|
50
|
+
"types": "./src/node.d.ts",
|
|
51
|
+
"import": "./src/node.js"
|
|
52
|
+
},
|
|
53
|
+
"./registry": {
|
|
54
|
+
"types": "./src/registry.d.ts",
|
|
55
|
+
"import": "./src/registry.js"
|
|
56
|
+
},
|
|
57
|
+
"./registry.js": {
|
|
58
|
+
"types": "./src/registry.d.ts",
|
|
59
|
+
"import": "./src/registry.js"
|
|
60
|
+
},
|
|
61
|
+
"./types": {
|
|
62
|
+
"types": "./src/types.d.ts",
|
|
63
|
+
"import": "./src/types.js"
|
|
64
|
+
},
|
|
65
|
+
"./types.js": {
|
|
66
|
+
"types": "./src/types.d.ts",
|
|
67
|
+
"import": "./src/types.js"
|
|
68
|
+
},
|
|
69
|
+
"./package.json": "./package.json"
|
|
70
|
+
}
|
|
71
|
+
}
|
package/src/bun-s3.d.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bun S3 implementation of File System Access API using built-in S3 support
|
|
3
|
+
*
|
|
4
|
+
* Leverages Bun's native S3Client and S3File for high-performance
|
|
5
|
+
* cloud storage operations with File System Access API compatibility.
|
|
6
|
+
*/
|
|
7
|
+
import type { FileSystemAdapter, FileSystemConfig } from "@b9g/filesystem";
|
|
8
|
+
/**
|
|
9
|
+
* Bun S3 implementation of FileSystemWritableFileStream
|
|
10
|
+
*/
|
|
11
|
+
export declare class BunS3FileSystemWritableFileStream extends WritableStream<Uint8Array> {
|
|
12
|
+
private s3file;
|
|
13
|
+
private writer;
|
|
14
|
+
constructor(s3file: any);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Bun S3 implementation of FileSystemFileHandle
|
|
18
|
+
*/
|
|
19
|
+
export declare class BunS3FileSystemFileHandle implements FileSystemFileHandle {
|
|
20
|
+
private s3Client;
|
|
21
|
+
private key;
|
|
22
|
+
readonly kind: "file";
|
|
23
|
+
readonly name: string;
|
|
24
|
+
constructor(s3Client: any, // Bun S3Client
|
|
25
|
+
key: string);
|
|
26
|
+
getFile(): Promise<File>;
|
|
27
|
+
createWritable(): Promise<FileSystemWritableFileStream>;
|
|
28
|
+
createSyncAccessHandle(): Promise<FileSystemSyncAccessHandle>;
|
|
29
|
+
isSameEntry(other: FileSystemHandle): Promise<boolean>;
|
|
30
|
+
queryPermission(): Promise<PermissionState>;
|
|
31
|
+
requestPermission(): Promise<PermissionState>;
|
|
32
|
+
private getMimeType;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Bun S3 implementation of FileSystemDirectoryHandle
|
|
36
|
+
*/
|
|
37
|
+
export declare class BunS3FileSystemDirectoryHandle implements FileSystemDirectoryHandle {
|
|
38
|
+
private s3Client;
|
|
39
|
+
private prefix;
|
|
40
|
+
readonly kind: "directory";
|
|
41
|
+
readonly name: string;
|
|
42
|
+
constructor(s3Client: any, // Bun S3Client
|
|
43
|
+
prefix: string);
|
|
44
|
+
getFileHandle(name: string, options?: {
|
|
45
|
+
create?: boolean;
|
|
46
|
+
}): Promise<FileSystemFileHandle>;
|
|
47
|
+
getDirectoryHandle(name: string, options?: {
|
|
48
|
+
create?: boolean;
|
|
49
|
+
}): Promise<FileSystemDirectoryHandle>;
|
|
50
|
+
removeEntry(name: string, options?: {
|
|
51
|
+
recursive?: boolean;
|
|
52
|
+
}): Promise<void>;
|
|
53
|
+
resolve(_possibleDescendant: FileSystemHandle): Promise<string[] | null>;
|
|
54
|
+
entries(): AsyncIterableIterator<[string, FileSystemHandle]>;
|
|
55
|
+
keys(): AsyncIterableIterator<string>;
|
|
56
|
+
values(): AsyncIterableIterator<FileSystemHandle>;
|
|
57
|
+
isSameEntry(other: FileSystemHandle): Promise<boolean>;
|
|
58
|
+
queryPermission(): Promise<PermissionState>;
|
|
59
|
+
requestPermission(): Promise<PermissionState>;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Bun S3 filesystem adapter using Bun's native S3Client
|
|
63
|
+
*/
|
|
64
|
+
export declare class BunS3FileSystemAdapter implements FileSystemAdapter {
|
|
65
|
+
private config;
|
|
66
|
+
private s3Client;
|
|
67
|
+
constructor(s3Client: any, config?: FileSystemConfig);
|
|
68
|
+
getFileSystemRoot(name?: string): Promise<FileSystemDirectoryHandle>;
|
|
69
|
+
getConfig(): FileSystemConfig;
|
|
70
|
+
dispose(): Promise<void>;
|
|
71
|
+
}
|
package/src/bun-s3.js
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/// <reference types="./bun-s3.d.ts" />
|
|
2
|
+
// src/bun-s3.ts
|
|
3
|
+
var BunS3FileSystemWritableFileStream = class extends WritableStream {
|
|
4
|
+
constructor(s3file) {
|
|
5
|
+
super({
|
|
6
|
+
start: async () => {
|
|
7
|
+
this.writer = this.s3file.writer();
|
|
8
|
+
},
|
|
9
|
+
write: async (chunk) => {
|
|
10
|
+
await this.writer.write(chunk);
|
|
11
|
+
},
|
|
12
|
+
close: async () => {
|
|
13
|
+
await this.writer.end();
|
|
14
|
+
},
|
|
15
|
+
abort: async () => {
|
|
16
|
+
await this.writer.abort?.();
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
this.s3file = s3file;
|
|
20
|
+
}
|
|
21
|
+
writer;
|
|
22
|
+
};
|
|
23
|
+
var BunS3FileSystemFileHandle = class _BunS3FileSystemFileHandle {
|
|
24
|
+
constructor(s3Client, key) {
|
|
25
|
+
this.s3Client = s3Client;
|
|
26
|
+
this.key = key;
|
|
27
|
+
this.name = key.split("/").pop() || key;
|
|
28
|
+
}
|
|
29
|
+
kind = "file";
|
|
30
|
+
name;
|
|
31
|
+
async getFile() {
|
|
32
|
+
const s3file = this.s3Client.file(this.key);
|
|
33
|
+
try {
|
|
34
|
+
const exists = await s3file.exists();
|
|
35
|
+
if (!exists) {
|
|
36
|
+
throw new DOMException("File not found", "NotFoundError");
|
|
37
|
+
}
|
|
38
|
+
const stats = await s3file.stat();
|
|
39
|
+
const blob = s3file;
|
|
40
|
+
return new File([blob], this.name, {
|
|
41
|
+
lastModified: stats?.lastModified ? new Date(stats.lastModified).getTime() : Date.now(),
|
|
42
|
+
type: stats?.contentType || this.getMimeType(this.key)
|
|
43
|
+
});
|
|
44
|
+
} catch (error) {
|
|
45
|
+
if (error.message?.includes("NoSuchKey") || error.message?.includes("Not Found")) {
|
|
46
|
+
throw new DOMException("File not found", "NotFoundError");
|
|
47
|
+
}
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async createWritable() {
|
|
52
|
+
const s3file = this.s3Client.file(this.key);
|
|
53
|
+
return new BunS3FileSystemWritableFileStream(s3file);
|
|
54
|
+
}
|
|
55
|
+
async createSyncAccessHandle() {
|
|
56
|
+
throw new DOMException(
|
|
57
|
+
"Synchronous access handles are not supported for S3 storage",
|
|
58
|
+
"InvalidStateError"
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
async isSameEntry(other) {
|
|
62
|
+
if (other.kind !== "file")
|
|
63
|
+
return false;
|
|
64
|
+
if (!(other instanceof _BunS3FileSystemFileHandle))
|
|
65
|
+
return false;
|
|
66
|
+
return this.key === other.key;
|
|
67
|
+
}
|
|
68
|
+
async queryPermission() {
|
|
69
|
+
return "granted";
|
|
70
|
+
}
|
|
71
|
+
async requestPermission() {
|
|
72
|
+
return "granted";
|
|
73
|
+
}
|
|
74
|
+
getMimeType(key) {
|
|
75
|
+
const ext = key.split(".").pop()?.toLowerCase();
|
|
76
|
+
const mimeTypes = {
|
|
77
|
+
txt: "text/plain",
|
|
78
|
+
html: "text/html",
|
|
79
|
+
css: "text/css",
|
|
80
|
+
js: "text/javascript",
|
|
81
|
+
json: "application/json",
|
|
82
|
+
png: "image/png",
|
|
83
|
+
jpg: "image/jpeg",
|
|
84
|
+
jpeg: "image/jpeg",
|
|
85
|
+
gif: "image/gif",
|
|
86
|
+
svg: "image/svg+xml",
|
|
87
|
+
pdf: "application/pdf",
|
|
88
|
+
zip: "application/zip"
|
|
89
|
+
};
|
|
90
|
+
return mimeTypes[ext || ""] || "application/octet-stream";
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
var BunS3FileSystemDirectoryHandle = class _BunS3FileSystemDirectoryHandle {
|
|
94
|
+
constructor(s3Client, prefix) {
|
|
95
|
+
this.s3Client = s3Client;
|
|
96
|
+
this.prefix = prefix;
|
|
97
|
+
this.prefix = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
|
|
98
|
+
this.name = this.prefix.split("/").pop() || "root";
|
|
99
|
+
}
|
|
100
|
+
kind = "directory";
|
|
101
|
+
name;
|
|
102
|
+
async getFileHandle(name, options) {
|
|
103
|
+
const key = this.prefix ? `${this.prefix}/${name}` : name;
|
|
104
|
+
const s3file = this.s3Client.file(key);
|
|
105
|
+
const exists = await s3file.exists();
|
|
106
|
+
if (!exists && options?.create) {
|
|
107
|
+
await s3file.write("");
|
|
108
|
+
} else if (!exists) {
|
|
109
|
+
throw new DOMException("File not found", "NotFoundError");
|
|
110
|
+
}
|
|
111
|
+
return new BunS3FileSystemFileHandle(this.s3Client, key);
|
|
112
|
+
}
|
|
113
|
+
async getDirectoryHandle(name, options) {
|
|
114
|
+
const newPrefix = this.prefix ? `${this.prefix}/${name}` : name;
|
|
115
|
+
if (options?.create) {
|
|
116
|
+
const markerKey = `${newPrefix}/.shovel_directory_marker`;
|
|
117
|
+
const markerFile = this.s3Client.file(markerKey);
|
|
118
|
+
const exists = await markerFile.exists();
|
|
119
|
+
if (!exists) {
|
|
120
|
+
await markerFile.write("");
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return new _BunS3FileSystemDirectoryHandle(this.s3Client, newPrefix);
|
|
124
|
+
}
|
|
125
|
+
async removeEntry(name, options) {
|
|
126
|
+
const key = this.prefix ? `${this.prefix}/${name}` : name;
|
|
127
|
+
const s3file = this.s3Client.file(key);
|
|
128
|
+
const fileExists = await s3file.exists();
|
|
129
|
+
if (fileExists) {
|
|
130
|
+
await s3file.delete();
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (options?.recursive) {
|
|
134
|
+
const dirPrefix = `${key}/`;
|
|
135
|
+
const files = await this.s3Client.list({ prefix: dirPrefix });
|
|
136
|
+
const deletePromises = files.map(
|
|
137
|
+
(file) => this.s3Client.file(file.Key || file.key).delete()
|
|
138
|
+
);
|
|
139
|
+
await Promise.all(deletePromises);
|
|
140
|
+
const markerFile = this.s3Client.file(`${key}/.shovel_directory_marker`);
|
|
141
|
+
const markerExists = await markerFile.exists();
|
|
142
|
+
if (markerExists) {
|
|
143
|
+
await markerFile.delete();
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
throw new DOMException(
|
|
147
|
+
"Directory is not empty",
|
|
148
|
+
"InvalidModificationError"
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
async resolve(_possibleDescendant) {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
async *entries() {
|
|
156
|
+
const listPrefix = this.prefix ? `${this.prefix}/` : "";
|
|
157
|
+
try {
|
|
158
|
+
const result = await this.s3Client.list({
|
|
159
|
+
prefix: listPrefix,
|
|
160
|
+
delimiter: "/"
|
|
161
|
+
// Only get immediate children
|
|
162
|
+
});
|
|
163
|
+
if (result.Contents) {
|
|
164
|
+
for (const item of result.Contents) {
|
|
165
|
+
const key = item.Key || item.key;
|
|
166
|
+
if (key && key !== listPrefix) {
|
|
167
|
+
const name = key.substring(listPrefix.length);
|
|
168
|
+
if (!name.includes("/") && !name.endsWith(".shovel_directory_marker")) {
|
|
169
|
+
yield [name, new BunS3FileSystemFileHandle(this.s3Client, key)];
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (result.CommonPrefixes) {
|
|
175
|
+
for (const prefix of result.CommonPrefixes) {
|
|
176
|
+
const prefixKey = prefix.Prefix || prefix.prefix;
|
|
177
|
+
if (prefixKey) {
|
|
178
|
+
const name = prefixKey.substring(listPrefix.length).replace(/\/$/, "");
|
|
179
|
+
if (name) {
|
|
180
|
+
yield [
|
|
181
|
+
name,
|
|
182
|
+
new _BunS3FileSystemDirectoryHandle(
|
|
183
|
+
this.s3Client,
|
|
184
|
+
prefixKey.replace(/\/$/, "")
|
|
185
|
+
)
|
|
186
|
+
];
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
} catch (error) {
|
|
192
|
+
throw new DOMException("Directory not found", "NotFoundError");
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
async *keys() {
|
|
196
|
+
for await (const [name] of this.entries()) {
|
|
197
|
+
yield name;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
async *values() {
|
|
201
|
+
for await (const [, handle] of this.entries()) {
|
|
202
|
+
yield handle;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
async isSameEntry(other) {
|
|
206
|
+
if (other.kind !== "directory")
|
|
207
|
+
return false;
|
|
208
|
+
if (!(other instanceof _BunS3FileSystemDirectoryHandle))
|
|
209
|
+
return false;
|
|
210
|
+
return this.prefix === other.prefix;
|
|
211
|
+
}
|
|
212
|
+
async queryPermission() {
|
|
213
|
+
return "granted";
|
|
214
|
+
}
|
|
215
|
+
async requestPermission() {
|
|
216
|
+
return "granted";
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
var BunS3FileSystemAdapter = class {
|
|
220
|
+
config;
|
|
221
|
+
s3Client;
|
|
222
|
+
constructor(s3Client, config = {}) {
|
|
223
|
+
this.config = {
|
|
224
|
+
name: "bun-s3",
|
|
225
|
+
...config
|
|
226
|
+
};
|
|
227
|
+
this.s3Client = s3Client;
|
|
228
|
+
}
|
|
229
|
+
async getFileSystemRoot(name = "default") {
|
|
230
|
+
const prefix = `filesystems/${name}`;
|
|
231
|
+
return new BunS3FileSystemDirectoryHandle(this.s3Client, prefix);
|
|
232
|
+
}
|
|
233
|
+
getConfig() {
|
|
234
|
+
return { ...this.config };
|
|
235
|
+
}
|
|
236
|
+
async dispose() {
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
export {
|
|
240
|
+
BunS3FileSystemAdapter,
|
|
241
|
+
BunS3FileSystemDirectoryHandle,
|
|
242
|
+
BunS3FileSystemFileHandle,
|
|
243
|
+
BunS3FileSystemWritableFileStream
|
|
244
|
+
};
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type { FileSystemAdapter, FileSystemConfig } from "./types.js";
|
|
2
|
+
export { MemoryFileSystemAdapter } from "./memory.js";
|
|
3
|
+
export { NodeFileSystemAdapter, NodeFileSystemDirectoryHandle, NodeFileSystemFileHandle } from "./node.js";
|
|
4
|
+
export { BunS3FileSystemAdapter, BunS3FileSystemDirectoryHandle, BunS3FileSystemFileHandle } from "./bun-s3.js";
|
|
5
|
+
export { FileSystemRegistry, getFileSystemRoot } from "./registry.js";
|
package/src/index.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/// <reference types="./index.d.ts" />
|
|
2
|
+
// src/index.ts
|
|
3
|
+
import { MemoryFileSystemAdapter } from "./memory.js";
|
|
4
|
+
import { NodeFileSystemAdapter, NodeFileSystemDirectoryHandle, NodeFileSystemFileHandle } from "./node.js";
|
|
5
|
+
import { BunS3FileSystemAdapter, BunS3FileSystemDirectoryHandle, BunS3FileSystemFileHandle } from "./bun-s3.js";
|
|
6
|
+
import { FileSystemRegistry, getFileSystemRoot } from "./registry.js";
|
|
7
|
+
export {
|
|
8
|
+
BunS3FileSystemAdapter,
|
|
9
|
+
BunS3FileSystemDirectoryHandle,
|
|
10
|
+
BunS3FileSystemFileHandle,
|
|
11
|
+
FileSystemRegistry,
|
|
12
|
+
MemoryFileSystemAdapter,
|
|
13
|
+
NodeFileSystemAdapter,
|
|
14
|
+
NodeFileSystemDirectoryHandle,
|
|
15
|
+
NodeFileSystemFileHandle,
|
|
16
|
+
getFileSystemRoot
|
|
17
|
+
};
|
package/src/memory.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory implementation of File System Access API
|
|
3
|
+
*
|
|
4
|
+
* Provides a complete filesystem interface using in-memory data structures.
|
|
5
|
+
* Useful for testing, development, and temporary storage scenarios.
|
|
6
|
+
*/
|
|
7
|
+
import type { FileSystemAdapter, FileSystemConfig } from "./types.js";
|
|
8
|
+
/**
|
|
9
|
+
* Memory filesystem adapter
|
|
10
|
+
*/
|
|
11
|
+
export declare class MemoryFileSystemAdapter implements FileSystemAdapter {
|
|
12
|
+
private config;
|
|
13
|
+
private filesystems;
|
|
14
|
+
constructor(config?: FileSystemConfig);
|
|
15
|
+
getFileSystemRoot(name?: string): Promise<FileSystemDirectoryHandle>;
|
|
16
|
+
getConfig(): FileSystemConfig;
|
|
17
|
+
dispose(): Promise<void>;
|
|
18
|
+
}
|
package/src/memory.js
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/// <reference types="./memory.d.ts" />
|
|
2
|
+
// src/memory.ts
|
|
3
|
+
var MemoryFileSystemWritableFileStream = class extends WritableStream {
|
|
4
|
+
constructor(onClose) {
|
|
5
|
+
super({
|
|
6
|
+
write: (chunk) => {
|
|
7
|
+
this.chunks.push(chunk);
|
|
8
|
+
return Promise.resolve();
|
|
9
|
+
},
|
|
10
|
+
close: () => {
|
|
11
|
+
const totalLength = this.chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
12
|
+
const content = new Uint8Array(totalLength);
|
|
13
|
+
let offset = 0;
|
|
14
|
+
for (const chunk of this.chunks) {
|
|
15
|
+
content.set(chunk, offset);
|
|
16
|
+
offset += chunk.length;
|
|
17
|
+
}
|
|
18
|
+
this.onClose(content);
|
|
19
|
+
return Promise.resolve();
|
|
20
|
+
},
|
|
21
|
+
abort: () => {
|
|
22
|
+
this.chunks = [];
|
|
23
|
+
return Promise.resolve();
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
this.onClose = onClose;
|
|
27
|
+
}
|
|
28
|
+
chunks = [];
|
|
29
|
+
};
|
|
30
|
+
var MemoryFileSystemFileHandle = class _MemoryFileSystemFileHandle {
|
|
31
|
+
constructor(file, updateFile) {
|
|
32
|
+
this.file = file;
|
|
33
|
+
this.updateFile = updateFile;
|
|
34
|
+
this.name = file.name;
|
|
35
|
+
}
|
|
36
|
+
kind = "file";
|
|
37
|
+
name;
|
|
38
|
+
async getFile() {
|
|
39
|
+
return new File([this.file.content], this.file.name, {
|
|
40
|
+
lastModified: this.file.lastModified,
|
|
41
|
+
type: this.file.type
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
async createWritable() {
|
|
45
|
+
return new MemoryFileSystemWritableFileStream((content) => {
|
|
46
|
+
this.updateFile(content);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
async createSyncAccessHandle() {
|
|
50
|
+
throw new DOMException(
|
|
51
|
+
"Synchronous access handles are not supported in memory filesystem",
|
|
52
|
+
"InvalidStateError"
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
async isSameEntry(other) {
|
|
56
|
+
if (other.kind !== "file")
|
|
57
|
+
return false;
|
|
58
|
+
if (!(other instanceof _MemoryFileSystemFileHandle))
|
|
59
|
+
return false;
|
|
60
|
+
return this.file === other.file;
|
|
61
|
+
}
|
|
62
|
+
async queryPermission() {
|
|
63
|
+
return "granted";
|
|
64
|
+
}
|
|
65
|
+
async requestPermission() {
|
|
66
|
+
return "granted";
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
var MemoryFileSystemDirectoryHandle = class _MemoryFileSystemDirectoryHandle {
|
|
70
|
+
constructor(directory) {
|
|
71
|
+
this.directory = directory;
|
|
72
|
+
this.name = directory.name;
|
|
73
|
+
}
|
|
74
|
+
kind = "directory";
|
|
75
|
+
name;
|
|
76
|
+
async getFileHandle(name, options) {
|
|
77
|
+
const file = this.directory.files.get(name);
|
|
78
|
+
if (!file && options?.create) {
|
|
79
|
+
const newFile = {
|
|
80
|
+
name,
|
|
81
|
+
content: new Uint8Array(0),
|
|
82
|
+
lastModified: Date.now(),
|
|
83
|
+
type: "application/octet-stream"
|
|
84
|
+
};
|
|
85
|
+
this.directory.files.set(name, newFile);
|
|
86
|
+
return new MemoryFileSystemFileHandle(newFile, (content, type) => {
|
|
87
|
+
newFile.content = content;
|
|
88
|
+
newFile.lastModified = Date.now();
|
|
89
|
+
if (type)
|
|
90
|
+
newFile.type = type;
|
|
91
|
+
});
|
|
92
|
+
} else if (!file) {
|
|
93
|
+
throw new DOMException("File not found", "NotFoundError");
|
|
94
|
+
}
|
|
95
|
+
return new MemoryFileSystemFileHandle(file, (content, type) => {
|
|
96
|
+
file.content = content;
|
|
97
|
+
file.lastModified = Date.now();
|
|
98
|
+
if (type)
|
|
99
|
+
file.type = type;
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
async getDirectoryHandle(name, options) {
|
|
103
|
+
const dir = this.directory.directories.get(name);
|
|
104
|
+
if (!dir && options?.create) {
|
|
105
|
+
const newDir = {
|
|
106
|
+
name,
|
|
107
|
+
files: /* @__PURE__ */ new Map(),
|
|
108
|
+
directories: /* @__PURE__ */ new Map()
|
|
109
|
+
};
|
|
110
|
+
this.directory.directories.set(name, newDir);
|
|
111
|
+
return new _MemoryFileSystemDirectoryHandle(newDir);
|
|
112
|
+
} else if (!dir) {
|
|
113
|
+
throw new DOMException("Directory not found", "NotFoundError");
|
|
114
|
+
}
|
|
115
|
+
return new _MemoryFileSystemDirectoryHandle(dir);
|
|
116
|
+
}
|
|
117
|
+
async removeEntry(name, options) {
|
|
118
|
+
if (this.directory.files.has(name)) {
|
|
119
|
+
this.directory.files.delete(name);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
const dir = this.directory.directories.get(name);
|
|
123
|
+
if (dir) {
|
|
124
|
+
if (dir.files.size > 0 || dir.directories.size > 0) {
|
|
125
|
+
if (!options?.recursive) {
|
|
126
|
+
throw new DOMException(
|
|
127
|
+
"Directory is not empty",
|
|
128
|
+
"InvalidModificationError"
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
this.directory.directories.delete(name);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
throw new DOMException("Entry not found", "NotFoundError");
|
|
136
|
+
}
|
|
137
|
+
async resolve(possibleDescendant) {
|
|
138
|
+
if (possibleDescendant instanceof MemoryFileSystemFileHandle) {
|
|
139
|
+
if (this.directory.files.has(possibleDescendant.name)) {
|
|
140
|
+
return [possibleDescendant.name];
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (possibleDescendant instanceof _MemoryFileSystemDirectoryHandle) {
|
|
144
|
+
if (this.directory.directories.has(possibleDescendant.name)) {
|
|
145
|
+
return [possibleDescendant.name];
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
async *entries() {
|
|
151
|
+
for (const [name, file] of this.directory.files) {
|
|
152
|
+
yield [
|
|
153
|
+
name,
|
|
154
|
+
new MemoryFileSystemFileHandle(file, (content, type) => {
|
|
155
|
+
file.content = content;
|
|
156
|
+
file.lastModified = Date.now();
|
|
157
|
+
if (type)
|
|
158
|
+
file.type = type;
|
|
159
|
+
})
|
|
160
|
+
];
|
|
161
|
+
}
|
|
162
|
+
for (const [name, dir] of this.directory.directories) {
|
|
163
|
+
yield [name, new _MemoryFileSystemDirectoryHandle(dir)];
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
async *keys() {
|
|
167
|
+
for (const name of this.directory.files.keys()) {
|
|
168
|
+
yield name;
|
|
169
|
+
}
|
|
170
|
+
for (const name of this.directory.directories.keys()) {
|
|
171
|
+
yield name;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
async *values() {
|
|
175
|
+
for (const [, handle] of this.entries()) {
|
|
176
|
+
yield handle;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
async isSameEntry(other) {
|
|
180
|
+
if (other.kind !== "directory")
|
|
181
|
+
return false;
|
|
182
|
+
if (!(other instanceof _MemoryFileSystemDirectoryHandle))
|
|
183
|
+
return false;
|
|
184
|
+
return this.directory === other.directory;
|
|
185
|
+
}
|
|
186
|
+
async queryPermission() {
|
|
187
|
+
return "granted";
|
|
188
|
+
}
|
|
189
|
+
async requestPermission() {
|
|
190
|
+
return "granted";
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
var MemoryFileSystemAdapter = class {
|
|
194
|
+
config;
|
|
195
|
+
filesystems = /* @__PURE__ */ new Map();
|
|
196
|
+
constructor(config = {}) {
|
|
197
|
+
this.config = {
|
|
198
|
+
name: "memory",
|
|
199
|
+
...config
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
async getFileSystemRoot(name = "default") {
|
|
203
|
+
if (!this.filesystems.has(name)) {
|
|
204
|
+
const root2 = {
|
|
205
|
+
name: "root",
|
|
206
|
+
files: /* @__PURE__ */ new Map(),
|
|
207
|
+
directories: /* @__PURE__ */ new Map()
|
|
208
|
+
};
|
|
209
|
+
this.filesystems.set(name, root2);
|
|
210
|
+
}
|
|
211
|
+
const root = this.filesystems.get(name);
|
|
212
|
+
return new MemoryFileSystemDirectoryHandle(root);
|
|
213
|
+
}
|
|
214
|
+
getConfig() {
|
|
215
|
+
return { ...this.config };
|
|
216
|
+
}
|
|
217
|
+
async dispose() {
|
|
218
|
+
this.filesystems.clear();
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
export {
|
|
222
|
+
MemoryFileSystemAdapter
|
|
223
|
+
};
|
package/src/node.d.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Node.js implementation of File System Access API
|
|
3
|
+
*
|
|
4
|
+
* Implements FileSystemDirectoryHandle and FileSystemFileHandle using fs/promises
|
|
5
|
+
* to provide universal File System Access API on Node.js platforms.
|
|
6
|
+
*/
|
|
7
|
+
import type { FileSystemAdapter, FileSystemConfig } from "./types.js";
|
|
8
|
+
/**
|
|
9
|
+
* Node.js implementation of FileSystemWritableFileStream
|
|
10
|
+
*/
|
|
11
|
+
export declare class NodeFileSystemWritableFileStream extends WritableStream<Uint8Array> {
|
|
12
|
+
private filePath;
|
|
13
|
+
constructor(filePath: string);
|
|
14
|
+
write(data: Uint8Array | string): Promise<void>;
|
|
15
|
+
close(): Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Node.js implementation of FileSystemFileHandle
|
|
19
|
+
*/
|
|
20
|
+
export declare class NodeFileSystemFileHandle implements FileSystemFileHandle {
|
|
21
|
+
private filePath;
|
|
22
|
+
readonly kind: "file";
|
|
23
|
+
readonly name: string;
|
|
24
|
+
constructor(filePath: string);
|
|
25
|
+
getFile(): Promise<File>;
|
|
26
|
+
createWritable(): Promise<FileSystemWritableFileStream>;
|
|
27
|
+
createSyncAccessHandle(): Promise<FileSystemSyncAccessHandle>;
|
|
28
|
+
isSameEntry(other: FileSystemHandle): Promise<boolean>;
|
|
29
|
+
queryPermission(): Promise<PermissionState>;
|
|
30
|
+
requestPermission(): Promise<PermissionState>;
|
|
31
|
+
private getMimeType;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Node.js implementation of FileSystemDirectoryHandle
|
|
35
|
+
*/
|
|
36
|
+
export declare class NodeFileSystemDirectoryHandle implements FileSystemDirectoryHandle {
|
|
37
|
+
private dirPath;
|
|
38
|
+
readonly kind: "directory";
|
|
39
|
+
readonly name: string;
|
|
40
|
+
constructor(dirPath: string);
|
|
41
|
+
getFileHandle(name: string, options?: {
|
|
42
|
+
create?: boolean;
|
|
43
|
+
}): Promise<FileSystemFileHandle>;
|
|
44
|
+
getDirectoryHandle(name: string, options?: {
|
|
45
|
+
create?: boolean;
|
|
46
|
+
}): Promise<FileSystemDirectoryHandle>;
|
|
47
|
+
removeEntry(name: string, options?: {
|
|
48
|
+
recursive?: boolean;
|
|
49
|
+
}): Promise<void>;
|
|
50
|
+
resolve(_possibleDescendant: FileSystemHandle): Promise<string[] | null>;
|
|
51
|
+
entries(): AsyncIterableIterator<[string, FileSystemHandle]>;
|
|
52
|
+
keys(): AsyncIterableIterator<string>;
|
|
53
|
+
values(): AsyncIterableIterator<FileSystemHandle>;
|
|
54
|
+
isSameEntry(other: FileSystemHandle): Promise<boolean>;
|
|
55
|
+
queryPermission(): Promise<PermissionState>;
|
|
56
|
+
requestPermission(): Promise<PermissionState>;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Node.js filesystem adapter
|
|
60
|
+
*/
|
|
61
|
+
export declare class NodeFileSystemAdapter implements FileSystemAdapter {
|
|
62
|
+
private config;
|
|
63
|
+
private rootPath;
|
|
64
|
+
constructor(config?: FileSystemConfig);
|
|
65
|
+
getFileSystemRoot(name?: string): Promise<FileSystemDirectoryHandle>;
|
|
66
|
+
getConfig(): FileSystemConfig;
|
|
67
|
+
}
|
package/src/node.js
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/// <reference types="./node.d.ts" />
|
|
2
|
+
// src/node.ts
|
|
3
|
+
import * as fs from "fs/promises";
|
|
4
|
+
import * as path from "path";
|
|
5
|
+
import { createWriteStream } from "fs";
|
|
6
|
+
var NodeFileSystemWritableFileStream = class extends WritableStream {
|
|
7
|
+
constructor(filePath) {
|
|
8
|
+
const writeStream = createWriteStream(filePath);
|
|
9
|
+
super({
|
|
10
|
+
write(chunk) {
|
|
11
|
+
return new Promise((resolve, reject) => {
|
|
12
|
+
writeStream.write(chunk, (error) => {
|
|
13
|
+
if (error)
|
|
14
|
+
reject(error);
|
|
15
|
+
else
|
|
16
|
+
resolve();
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
},
|
|
20
|
+
close() {
|
|
21
|
+
return new Promise((resolve, reject) => {
|
|
22
|
+
writeStream.end((error) => {
|
|
23
|
+
if (error)
|
|
24
|
+
reject(error);
|
|
25
|
+
else
|
|
26
|
+
resolve();
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
},
|
|
30
|
+
abort() {
|
|
31
|
+
writeStream.destroy();
|
|
32
|
+
return Promise.resolve();
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
this.filePath = filePath;
|
|
36
|
+
}
|
|
37
|
+
// File System Access API compatibility methods
|
|
38
|
+
async write(data) {
|
|
39
|
+
const writer = this.getWriter();
|
|
40
|
+
try {
|
|
41
|
+
if (typeof data === "string") {
|
|
42
|
+
await writer.write(new TextEncoder().encode(data));
|
|
43
|
+
} else {
|
|
44
|
+
await writer.write(data);
|
|
45
|
+
}
|
|
46
|
+
} finally {
|
|
47
|
+
writer.releaseLock();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async close() {
|
|
51
|
+
const writer = this.getWriter();
|
|
52
|
+
try {
|
|
53
|
+
await writer.close();
|
|
54
|
+
} finally {
|
|
55
|
+
writer.releaseLock();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
var NodeFileSystemFileHandle = class _NodeFileSystemFileHandle {
|
|
60
|
+
constructor(filePath) {
|
|
61
|
+
this.filePath = filePath;
|
|
62
|
+
this.name = path.basename(filePath);
|
|
63
|
+
}
|
|
64
|
+
kind = "file";
|
|
65
|
+
name;
|
|
66
|
+
async getFile() {
|
|
67
|
+
try {
|
|
68
|
+
const stats = await fs.stat(this.filePath);
|
|
69
|
+
const buffer = await fs.readFile(this.filePath);
|
|
70
|
+
return new File([buffer], this.name, {
|
|
71
|
+
lastModified: stats.mtime.getTime(),
|
|
72
|
+
// Attempt to determine MIME type from extension
|
|
73
|
+
type: this.getMimeType(this.filePath)
|
|
74
|
+
});
|
|
75
|
+
} catch (error) {
|
|
76
|
+
if (error.code === "ENOENT") {
|
|
77
|
+
throw new DOMException("File not found", "NotFoundError");
|
|
78
|
+
}
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
async createWritable() {
|
|
83
|
+
await fs.mkdir(path.dirname(this.filePath), { recursive: true });
|
|
84
|
+
return new NodeFileSystemWritableFileStream(this.filePath);
|
|
85
|
+
}
|
|
86
|
+
async createSyncAccessHandle() {
|
|
87
|
+
throw new DOMException(
|
|
88
|
+
"Synchronous access handles are only available in workers",
|
|
89
|
+
"InvalidStateError"
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
async isSameEntry(other) {
|
|
93
|
+
if (other.kind !== "file")
|
|
94
|
+
return false;
|
|
95
|
+
if (!(other instanceof _NodeFileSystemFileHandle))
|
|
96
|
+
return false;
|
|
97
|
+
return this.filePath === other.filePath;
|
|
98
|
+
}
|
|
99
|
+
async queryPermission() {
|
|
100
|
+
return "granted";
|
|
101
|
+
}
|
|
102
|
+
async requestPermission() {
|
|
103
|
+
return "granted";
|
|
104
|
+
}
|
|
105
|
+
getMimeType(filePath) {
|
|
106
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
107
|
+
const mimeTypes = {
|
|
108
|
+
".txt": "text/plain",
|
|
109
|
+
".html": "text/html",
|
|
110
|
+
".css": "text/css",
|
|
111
|
+
".js": "text/javascript",
|
|
112
|
+
".json": "application/json",
|
|
113
|
+
".png": "image/png",
|
|
114
|
+
".jpg": "image/jpeg",
|
|
115
|
+
".jpeg": "image/jpeg",
|
|
116
|
+
".gif": "image/gif",
|
|
117
|
+
".svg": "image/svg+xml",
|
|
118
|
+
".pdf": "application/pdf",
|
|
119
|
+
".zip": "application/zip"
|
|
120
|
+
};
|
|
121
|
+
return mimeTypes[ext] || "application/octet-stream";
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
var NodeFileSystemDirectoryHandle = class _NodeFileSystemDirectoryHandle {
|
|
125
|
+
constructor(dirPath) {
|
|
126
|
+
this.dirPath = dirPath;
|
|
127
|
+
this.name = path.basename(dirPath);
|
|
128
|
+
}
|
|
129
|
+
kind = "directory";
|
|
130
|
+
name;
|
|
131
|
+
async getFileHandle(name, options) {
|
|
132
|
+
const filePath = path.join(this.dirPath, name);
|
|
133
|
+
try {
|
|
134
|
+
const stats = await fs.stat(filePath);
|
|
135
|
+
if (!stats.isFile()) {
|
|
136
|
+
throw new DOMException(
|
|
137
|
+
"Path exists but is not a file",
|
|
138
|
+
"TypeMismatchError"
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
} catch (error) {
|
|
142
|
+
if (error.code === "ENOENT") {
|
|
143
|
+
if (options?.create) {
|
|
144
|
+
await fs.mkdir(this.dirPath, { recursive: true });
|
|
145
|
+
await fs.writeFile(filePath, "");
|
|
146
|
+
} else {
|
|
147
|
+
throw new DOMException("File not found", "NotFoundError");
|
|
148
|
+
}
|
|
149
|
+
} else {
|
|
150
|
+
throw error;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return new NodeFileSystemFileHandle(filePath);
|
|
154
|
+
}
|
|
155
|
+
async getDirectoryHandle(name, options) {
|
|
156
|
+
const subDirPath = path.join(this.dirPath, name);
|
|
157
|
+
try {
|
|
158
|
+
const stats = await fs.stat(subDirPath);
|
|
159
|
+
if (!stats.isDirectory()) {
|
|
160
|
+
throw new DOMException(
|
|
161
|
+
"Path exists but is not a directory",
|
|
162
|
+
"TypeMismatchError"
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
} catch (error) {
|
|
166
|
+
if (error.code === "ENOENT") {
|
|
167
|
+
if (options?.create) {
|
|
168
|
+
await fs.mkdir(subDirPath, { recursive: true });
|
|
169
|
+
} else {
|
|
170
|
+
throw new DOMException("Directory not found", "NotFoundError");
|
|
171
|
+
}
|
|
172
|
+
} else {
|
|
173
|
+
throw error;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return new _NodeFileSystemDirectoryHandle(subDirPath);
|
|
177
|
+
}
|
|
178
|
+
async removeEntry(name, options) {
|
|
179
|
+
const entryPath = path.join(this.dirPath, name);
|
|
180
|
+
try {
|
|
181
|
+
const stats = await fs.stat(entryPath);
|
|
182
|
+
if (stats.isDirectory()) {
|
|
183
|
+
await fs.rmdir(entryPath, { recursive: options?.recursive });
|
|
184
|
+
} else {
|
|
185
|
+
await fs.unlink(entryPath);
|
|
186
|
+
}
|
|
187
|
+
} catch (error) {
|
|
188
|
+
if (error.code === "ENOENT") {
|
|
189
|
+
throw new DOMException("Entry not found", "NotFoundError");
|
|
190
|
+
}
|
|
191
|
+
throw error;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
async resolve(_possibleDescendant) {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
async *entries() {
|
|
198
|
+
try {
|
|
199
|
+
const entries = await fs.readdir(this.dirPath, { withFileTypes: true });
|
|
200
|
+
for (const entry of entries) {
|
|
201
|
+
const entryPath = path.join(this.dirPath, entry.name);
|
|
202
|
+
if (entry.isDirectory()) {
|
|
203
|
+
yield [entry.name, new _NodeFileSystemDirectoryHandle(entryPath)];
|
|
204
|
+
} else if (entry.isFile()) {
|
|
205
|
+
yield [entry.name, new NodeFileSystemFileHandle(entryPath)];
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
} catch (error) {
|
|
209
|
+
if (error.code === "ENOENT") {
|
|
210
|
+
throw new DOMException("Directory not found", "NotFoundError");
|
|
211
|
+
}
|
|
212
|
+
throw error;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
async *keys() {
|
|
216
|
+
for await (const [name] of this.entries()) {
|
|
217
|
+
yield name;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
async *values() {
|
|
221
|
+
for await (const [, handle] of this.entries()) {
|
|
222
|
+
yield handle;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
async isSameEntry(other) {
|
|
226
|
+
if (other.kind !== "directory")
|
|
227
|
+
return false;
|
|
228
|
+
if (!(other instanceof _NodeFileSystemDirectoryHandle))
|
|
229
|
+
return false;
|
|
230
|
+
return this.dirPath === other.dirPath;
|
|
231
|
+
}
|
|
232
|
+
async queryPermission() {
|
|
233
|
+
return "granted";
|
|
234
|
+
}
|
|
235
|
+
async requestPermission() {
|
|
236
|
+
return "granted";
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
var NodeFileSystemAdapter = class {
|
|
240
|
+
config;
|
|
241
|
+
rootPath;
|
|
242
|
+
constructor(config = {}) {
|
|
243
|
+
this.config = {
|
|
244
|
+
name: "node",
|
|
245
|
+
...config
|
|
246
|
+
};
|
|
247
|
+
this.rootPath = config.rootPath || path.join(process.cwd(), "dist");
|
|
248
|
+
}
|
|
249
|
+
async getFileSystemRoot(name = "default") {
|
|
250
|
+
const bucketPath = path.join(this.rootPath, name);
|
|
251
|
+
try {
|
|
252
|
+
await fs.mkdir(bucketPath, { recursive: true });
|
|
253
|
+
} catch (error) {
|
|
254
|
+
}
|
|
255
|
+
return new NodeFileSystemDirectoryHandle(bucketPath);
|
|
256
|
+
}
|
|
257
|
+
getConfig() {
|
|
258
|
+
return { ...this.config };
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
export {
|
|
262
|
+
NodeFileSystemAdapter,
|
|
263
|
+
NodeFileSystemDirectoryHandle,
|
|
264
|
+
NodeFileSystemFileHandle,
|
|
265
|
+
NodeFileSystemWritableFileStream
|
|
266
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filesystem adapter registry
|
|
3
|
+
* Manages registration and retrieval of filesystem adapters
|
|
4
|
+
*/
|
|
5
|
+
import type { FileSystemAdapter } from "./types.js";
|
|
6
|
+
/**
|
|
7
|
+
* Global registry of filesystem adapters
|
|
8
|
+
*/
|
|
9
|
+
declare class Registry {
|
|
10
|
+
private adapters;
|
|
11
|
+
private defaultAdapter;
|
|
12
|
+
constructor();
|
|
13
|
+
/**
|
|
14
|
+
* Register a filesystem adapter with a name
|
|
15
|
+
*/
|
|
16
|
+
register(name: string, adapter: FileSystemAdapter): void;
|
|
17
|
+
/**
|
|
18
|
+
* Get a filesystem adapter by name
|
|
19
|
+
*/
|
|
20
|
+
get(name?: string): FileSystemAdapter | null;
|
|
21
|
+
/**
|
|
22
|
+
* Set the default filesystem adapter
|
|
23
|
+
*/
|
|
24
|
+
setDefault(adapter: FileSystemAdapter): void;
|
|
25
|
+
/**
|
|
26
|
+
* Get all registered adapter names
|
|
27
|
+
*/
|
|
28
|
+
getAdapterNames(): string[];
|
|
29
|
+
/**
|
|
30
|
+
* Clear all registered adapters
|
|
31
|
+
*/
|
|
32
|
+
clear(): void;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Global filesystem registry instance
|
|
36
|
+
*/
|
|
37
|
+
export declare const FileSystemRegistry: Registry;
|
|
38
|
+
/**
|
|
39
|
+
* Get a file system root handle using the registered adapters
|
|
40
|
+
* @param name Optional adapter name (uses default if not specified)
|
|
41
|
+
*/
|
|
42
|
+
export declare function getFileSystemRoot(name?: string): Promise<FileSystemDirectoryHandle>;
|
|
43
|
+
export {};
|
package/src/registry.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/// <reference types="./registry.d.ts" />
|
|
2
|
+
// src/registry.ts
|
|
3
|
+
import { MemoryFileSystemAdapter } from "./memory.js";
|
|
4
|
+
var Registry = class {
|
|
5
|
+
adapters = /* @__PURE__ */ new Map();
|
|
6
|
+
defaultAdapter;
|
|
7
|
+
constructor() {
|
|
8
|
+
this.defaultAdapter = new MemoryFileSystemAdapter();
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Register a filesystem adapter with a name
|
|
12
|
+
*/
|
|
13
|
+
register(name, adapter) {
|
|
14
|
+
this.adapters.set(name, adapter);
|
|
15
|
+
if (!this.defaultAdapter) {
|
|
16
|
+
this.defaultAdapter = adapter;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Get a filesystem adapter by name
|
|
21
|
+
*/
|
|
22
|
+
get(name) {
|
|
23
|
+
if (!name) {
|
|
24
|
+
return this.defaultAdapter;
|
|
25
|
+
}
|
|
26
|
+
return this.adapters.get(name) || this.defaultAdapter;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Set the default filesystem adapter
|
|
30
|
+
*/
|
|
31
|
+
setDefault(adapter) {
|
|
32
|
+
this.defaultAdapter = adapter;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Get all registered adapter names
|
|
36
|
+
*/
|
|
37
|
+
getAdapterNames() {
|
|
38
|
+
return Array.from(this.adapters.keys());
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Clear all registered adapters
|
|
42
|
+
*/
|
|
43
|
+
clear() {
|
|
44
|
+
this.adapters.clear();
|
|
45
|
+
this.defaultAdapter = null;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
var FileSystemRegistry = new Registry();
|
|
49
|
+
async function getFileSystemRoot(name) {
|
|
50
|
+
const adapter = FileSystemRegistry.get(name);
|
|
51
|
+
if (!adapter) {
|
|
52
|
+
if (name) {
|
|
53
|
+
throw new Error(`No filesystem adapter registered with name: ${name}`);
|
|
54
|
+
} else {
|
|
55
|
+
throw new Error("No default filesystem adapter registered");
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return await adapter.getFileSystemRoot(name);
|
|
59
|
+
}
|
|
60
|
+
export {
|
|
61
|
+
FileSystemRegistry,
|
|
62
|
+
getFileSystemRoot
|
|
63
|
+
};
|
package/src/types.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core filesystem adapter interface and types
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Configuration for filesystem adapters
|
|
6
|
+
*/
|
|
7
|
+
export interface FileSystemConfig {
|
|
8
|
+
/** Human readable name for this filesystem */
|
|
9
|
+
name?: string;
|
|
10
|
+
/** Platform-specific configuration */
|
|
11
|
+
[key: string]: any;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Core interface that all filesystem adapters must implement
|
|
15
|
+
* Provides File System Access API compatibility across all platforms
|
|
16
|
+
*/
|
|
17
|
+
export interface FileSystemAdapter {
|
|
18
|
+
/**
|
|
19
|
+
* Get a root directory handle for the filesystem
|
|
20
|
+
* @param name Optional filesystem name/bucket identifier
|
|
21
|
+
*/
|
|
22
|
+
getFileSystemRoot(name?: string): Promise<FileSystemDirectoryHandle>;
|
|
23
|
+
/**
|
|
24
|
+
* Get configuration for this adapter
|
|
25
|
+
*/
|
|
26
|
+
getConfig(): FileSystemConfig;
|
|
27
|
+
/**
|
|
28
|
+
* Cleanup resources when adapter is no longer needed
|
|
29
|
+
*/
|
|
30
|
+
dispose?(): Promise<void>;
|
|
31
|
+
}
|
package/src/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="./types.d.ts" />
|