@b9g/filesystem 0.1.2 → 0.1.5
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 +35 -22
- package/package.json +9 -28
- package/src/bun-s3.d.ts +48 -47
- package/src/bun-s3.js +212 -185
- package/src/index.d.ts +212 -6
- package/src/index.js +335 -17
- package/src/memory.d.ts +70 -12
- package/src/memory.js +189 -171
- package/src/node.d.ts +25 -58
- package/src/node.js +100 -218
- package/src/directory-storage.d.ts +0 -50
- package/src/directory-storage.js +0 -74
- package/src/registry.d.ts +0 -52
- package/src/registry.js +0 -79
- package/src/types.d.ts +0 -31
- package/src/types.js +0 -1
package/src/bun-s3.js
CHANGED
|
@@ -1,197 +1,245 @@
|
|
|
1
1
|
/// <reference types="./bun-s3.d.ts" />
|
|
2
2
|
// src/bun-s3.ts
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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;
|
|
3
|
+
import {
|
|
4
|
+
ShovelDirectoryHandle,
|
|
5
|
+
ShovelFileHandle
|
|
6
|
+
} from "./index.js";
|
|
7
|
+
var S3FileSystemBackend = class {
|
|
8
|
+
#s3Client;
|
|
9
|
+
#bucketName;
|
|
10
|
+
#prefix;
|
|
11
|
+
constructor(s3Client, bucketName, prefix = "") {
|
|
12
|
+
this.#s3Client = s3Client;
|
|
13
|
+
this.#bucketName = bucketName;
|
|
14
|
+
this.#prefix = prefix;
|
|
28
15
|
}
|
|
29
|
-
|
|
30
|
-
name;
|
|
31
|
-
async getFile() {
|
|
32
|
-
const s3file = this.s3Client.file(this.key);
|
|
16
|
+
async stat(path) {
|
|
33
17
|
try {
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
18
|
+
const key = this.#getS3Key(path);
|
|
19
|
+
try {
|
|
20
|
+
await this.#s3Client.head({ key });
|
|
21
|
+
return { kind: "file" };
|
|
22
|
+
} catch (error) {
|
|
23
|
+
const dirPrefix = key.endsWith("/") ? key : `${key}/`;
|
|
24
|
+
const result = await this.#s3Client.list({
|
|
25
|
+
prefix: dirPrefix,
|
|
26
|
+
maxKeys: 1
|
|
27
|
+
});
|
|
28
|
+
if (result.Contents && result.Contents.length > 0) {
|
|
29
|
+
return { kind: "directory" };
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
37
32
|
}
|
|
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
33
|
} catch (error) {
|
|
45
|
-
|
|
46
|
-
throw new DOMException("File not found", "NotFoundError");
|
|
47
|
-
}
|
|
48
|
-
throw error;
|
|
34
|
+
return null;
|
|
49
35
|
}
|
|
50
36
|
}
|
|
51
|
-
async
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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("");
|
|
37
|
+
async readFile(path) {
|
|
38
|
+
try {
|
|
39
|
+
const key = this.#getS3Key(path);
|
|
40
|
+
const result = await this.#s3Client.get({ key });
|
|
41
|
+
if (result.Body) {
|
|
42
|
+
if (result.Body instanceof Uint8Array) {
|
|
43
|
+
return result.Body;
|
|
44
|
+
} else if (typeof result.Body === "string") {
|
|
45
|
+
return new TextEncoder().encode(result.Body);
|
|
46
|
+
} else {
|
|
47
|
+
const arrayBuffer = await result.Body.arrayBuffer();
|
|
48
|
+
return new Uint8Array(arrayBuffer);
|
|
49
|
+
}
|
|
121
50
|
}
|
|
51
|
+
throw new DOMException("File not found", "NotFoundError");
|
|
52
|
+
} catch (error) {
|
|
53
|
+
throw new DOMException("File not found", "NotFoundError");
|
|
122
54
|
}
|
|
123
|
-
return new _BunS3FileSystemDirectoryHandle(this.s3Client, newPrefix);
|
|
124
55
|
}
|
|
125
|
-
async
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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 {
|
|
56
|
+
async writeFile(path, data) {
|
|
57
|
+
try {
|
|
58
|
+
const key = this.#getS3Key(path);
|
|
59
|
+
await this.#s3Client.put({
|
|
60
|
+
key,
|
|
61
|
+
body: data
|
|
62
|
+
});
|
|
63
|
+
} catch (error) {
|
|
146
64
|
throw new DOMException(
|
|
147
|
-
|
|
65
|
+
`Failed to write file: ${error}`,
|
|
148
66
|
"InvalidModificationError"
|
|
149
67
|
);
|
|
150
68
|
}
|
|
151
69
|
}
|
|
152
|
-
async
|
|
153
|
-
return null;
|
|
154
|
-
}
|
|
155
|
-
async *entries() {
|
|
156
|
-
const listPrefix = this.prefix ? `${this.prefix}/` : "";
|
|
70
|
+
async listDir(path) {
|
|
157
71
|
try {
|
|
158
|
-
const
|
|
72
|
+
const dirPrefix = this.#getS3Key(path);
|
|
73
|
+
const listPrefix = dirPrefix ? `${dirPrefix}/` : "";
|
|
74
|
+
const result = await this.#s3Client.list({
|
|
159
75
|
prefix: listPrefix,
|
|
160
76
|
delimiter: "/"
|
|
161
|
-
// Only get immediate children
|
|
162
77
|
});
|
|
78
|
+
const results = [];
|
|
163
79
|
if (result.Contents) {
|
|
164
|
-
for (const
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
yield [name, new BunS3FileSystemFileHandle(this.s3Client, key)];
|
|
80
|
+
for (const object of result.Contents) {
|
|
81
|
+
if (object.Key && object.Key !== listPrefix) {
|
|
82
|
+
const name = object.Key.replace(listPrefix, "");
|
|
83
|
+
if (name && !name.includes("/")) {
|
|
84
|
+
results.push({ name, kind: "file" });
|
|
170
85
|
}
|
|
171
86
|
}
|
|
172
87
|
}
|
|
173
88
|
}
|
|
174
89
|
if (result.CommonPrefixes) {
|
|
175
90
|
for (const prefix of result.CommonPrefixes) {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
const name = prefixKey.substring(listPrefix.length).replace(/\/$/, "");
|
|
91
|
+
if (prefix.Prefix) {
|
|
92
|
+
const name = prefix.Prefix.replace(listPrefix, "").replace("/", "");
|
|
179
93
|
if (name) {
|
|
180
|
-
|
|
181
|
-
name,
|
|
182
|
-
new _BunS3FileSystemDirectoryHandle(
|
|
183
|
-
this.s3Client,
|
|
184
|
-
prefixKey.replace(/\/$/, "")
|
|
185
|
-
)
|
|
186
|
-
];
|
|
94
|
+
results.push({ name, kind: "directory" });
|
|
187
95
|
}
|
|
188
96
|
}
|
|
189
97
|
}
|
|
190
98
|
}
|
|
99
|
+
return results;
|
|
191
100
|
} catch (error) {
|
|
192
101
|
throw new DOMException("Directory not found", "NotFoundError");
|
|
193
102
|
}
|
|
194
103
|
}
|
|
104
|
+
async createDir(path) {
|
|
105
|
+
try {
|
|
106
|
+
const key = this.#getS3Key(path);
|
|
107
|
+
const dirKey = key.endsWith("/") ? key : `${key}/`;
|
|
108
|
+
await this.#s3Client.put({
|
|
109
|
+
key: dirKey,
|
|
110
|
+
body: new Uint8Array(0)
|
|
111
|
+
});
|
|
112
|
+
} catch (error) {
|
|
113
|
+
throw new DOMException(
|
|
114
|
+
`Failed to create directory: ${error}`,
|
|
115
|
+
"InvalidModificationError"
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
async remove(path, recursive) {
|
|
120
|
+
try {
|
|
121
|
+
const key = this.#getS3Key(path);
|
|
122
|
+
try {
|
|
123
|
+
await this.#s3Client.head({ key });
|
|
124
|
+
await this.#s3Client.delete({ key });
|
|
125
|
+
return;
|
|
126
|
+
} catch (error) {
|
|
127
|
+
}
|
|
128
|
+
const dirPrefix = key.endsWith("/") ? key : `${key}/`;
|
|
129
|
+
if (recursive) {
|
|
130
|
+
const result = await this.#s3Client.list({ prefix: dirPrefix });
|
|
131
|
+
if (result.Contents && result.Contents.length > 0) {
|
|
132
|
+
const deleteKeys = result.Contents.map((obj) => ({
|
|
133
|
+
key: obj.Key
|
|
134
|
+
}));
|
|
135
|
+
await this.#s3Client.deleteObjects({ delete: { objects: deleteKeys } });
|
|
136
|
+
}
|
|
137
|
+
} else {
|
|
138
|
+
const result = await this.#s3Client.list({
|
|
139
|
+
prefix: dirPrefix,
|
|
140
|
+
maxKeys: 1
|
|
141
|
+
});
|
|
142
|
+
if (result.Contents && result.Contents.length > 0) {
|
|
143
|
+
throw new DOMException(
|
|
144
|
+
"Directory is not empty",
|
|
145
|
+
"InvalidModificationError"
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
try {
|
|
149
|
+
await this.#s3Client.delete({ key: dirPrefix });
|
|
150
|
+
} catch (error) {
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
} catch (error) {
|
|
154
|
+
if (error instanceof DOMException)
|
|
155
|
+
throw error;
|
|
156
|
+
throw new DOMException("Entry not found", "NotFoundError");
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
#getS3Key(path) {
|
|
160
|
+
if (path.includes("..") || path.includes("\0")) {
|
|
161
|
+
throw new DOMException(
|
|
162
|
+
"Invalid path: contains path traversal or null bytes",
|
|
163
|
+
"NotAllowedError"
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
const cleanPath = path.startsWith("/") ? path.slice(1) : path;
|
|
167
|
+
if (!cleanPath) {
|
|
168
|
+
return this.#prefix;
|
|
169
|
+
}
|
|
170
|
+
const parts = cleanPath.split("/");
|
|
171
|
+
for (const part of parts) {
|
|
172
|
+
if (part === "." || part === ".." || part.includes("\\")) {
|
|
173
|
+
throw new DOMException("Invalid S3 key component", "NotAllowedError");
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return this.#prefix ? `${this.#prefix}/${cleanPath}` : cleanPath;
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
var S3Bucket = class _S3Bucket {
|
|
180
|
+
kind;
|
|
181
|
+
name;
|
|
182
|
+
#backend;
|
|
183
|
+
constructor(s3Client, bucketName, prefix = "") {
|
|
184
|
+
this.kind = "directory";
|
|
185
|
+
this.#backend = new S3FileSystemBackend(s3Client, bucketName, prefix);
|
|
186
|
+
this.name = prefix.split("/").filter(Boolean).pop() || bucketName;
|
|
187
|
+
}
|
|
188
|
+
async getFileHandle(name, options) {
|
|
189
|
+
const filePath = `/${name}`;
|
|
190
|
+
const stat = await this.#backend.stat(filePath);
|
|
191
|
+
if (!stat && options?.create) {
|
|
192
|
+
await this.#backend.writeFile(filePath, new Uint8Array(0));
|
|
193
|
+
} else if (!stat) {
|
|
194
|
+
throw new DOMException("File not found", "NotFoundError");
|
|
195
|
+
} else if (stat.kind !== "file") {
|
|
196
|
+
throw new DOMException(
|
|
197
|
+
"Path exists but is not a file",
|
|
198
|
+
"TypeMismatchError"
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
return new ShovelFileHandle(this.#backend, filePath);
|
|
202
|
+
}
|
|
203
|
+
async getDirectoryHandle(name, options) {
|
|
204
|
+
const dirPath = `/${name}`;
|
|
205
|
+
const stat = await this.#backend.stat(dirPath);
|
|
206
|
+
if (!stat && options?.create) {
|
|
207
|
+
await this.#backend.createDir(dirPath);
|
|
208
|
+
} else if (!stat) {
|
|
209
|
+
throw new DOMException("Directory not found", "NotFoundError");
|
|
210
|
+
} else if (stat.kind !== "directory") {
|
|
211
|
+
throw new DOMException(
|
|
212
|
+
"Path exists but is not a directory",
|
|
213
|
+
"TypeMismatchError"
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
return new ShovelDirectoryHandle(this.#backend, dirPath);
|
|
217
|
+
}
|
|
218
|
+
async removeEntry(name, options) {
|
|
219
|
+
const entryPath = `/${name}`;
|
|
220
|
+
await this.#backend.remove(entryPath, options?.recursive);
|
|
221
|
+
}
|
|
222
|
+
async resolve(possibleDescendant) {
|
|
223
|
+
if (!(possibleDescendant instanceof ShovelDirectoryHandle || possibleDescendant instanceof ShovelFileHandle)) {
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
const descendantPath = possibleDescendant.path;
|
|
227
|
+
if (typeof descendantPath === "string" && descendantPath.startsWith("/")) {
|
|
228
|
+
return descendantPath.split("/").filter(Boolean);
|
|
229
|
+
}
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
async *entries() {
|
|
233
|
+
const entries = await this.#backend.listDir("/");
|
|
234
|
+
for (const entry of entries) {
|
|
235
|
+
const entryPath = `/${entry.name}`;
|
|
236
|
+
if (entry.kind === "file") {
|
|
237
|
+
yield [entry.name, new ShovelFileHandle(this.#backend, entryPath)];
|
|
238
|
+
} else {
|
|
239
|
+
yield [entry.name, new ShovelDirectoryHandle(this.#backend, entryPath)];
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
195
243
|
async *keys() {
|
|
196
244
|
for await (const [name] of this.entries()) {
|
|
197
245
|
yield name;
|
|
@@ -202,12 +250,13 @@ var BunS3FileSystemDirectoryHandle = class _BunS3FileSystemDirectoryHandle {
|
|
|
202
250
|
yield handle;
|
|
203
251
|
}
|
|
204
252
|
}
|
|
253
|
+
[Symbol.asyncIterator]() {
|
|
254
|
+
return this.entries();
|
|
255
|
+
}
|
|
205
256
|
async isSameEntry(other) {
|
|
206
257
|
if (other.kind !== "directory")
|
|
207
258
|
return false;
|
|
208
|
-
|
|
209
|
-
return false;
|
|
210
|
-
return this.prefix === other.prefix;
|
|
259
|
+
return other instanceof _S3Bucket && other.name === this.name;
|
|
211
260
|
}
|
|
212
261
|
async queryPermission() {
|
|
213
262
|
return "granted";
|
|
@@ -216,29 +265,7 @@ var BunS3FileSystemDirectoryHandle = class _BunS3FileSystemDirectoryHandle {
|
|
|
216
265
|
return "granted";
|
|
217
266
|
}
|
|
218
267
|
};
|
|
219
|
-
var S3Bucket = 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 getDirectoryHandle(name) {
|
|
230
|
-
const prefix = name ? `filesystems/${name}` : "filesystems/root";
|
|
231
|
-
return new BunS3FileSystemDirectoryHandle(this.s3Client, prefix);
|
|
232
|
-
}
|
|
233
|
-
getConfig() {
|
|
234
|
-
return { ...this.config };
|
|
235
|
-
}
|
|
236
|
-
async dispose() {
|
|
237
|
-
}
|
|
238
|
-
};
|
|
239
268
|
export {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
BunS3FileSystemWritableFileStream,
|
|
243
|
-
S3Bucket
|
|
269
|
+
S3Bucket,
|
|
270
|
+
S3FileSystemBackend
|
|
244
271
|
};
|
package/src/index.d.ts
CHANGED
|
@@ -1,6 +1,212 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Configuration for filesystem adapters
|
|
3
|
+
*/
|
|
4
|
+
export interface FileSystemConfig {
|
|
5
|
+
/** Human readable name for this filesystem */
|
|
6
|
+
name?: string;
|
|
7
|
+
/** Platform-specific configuration */
|
|
8
|
+
[key: string]: any;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Bucket is a semantic alias for FileSystemDirectoryHandle
|
|
12
|
+
* Represents a named storage bucket that provides direct filesystem access
|
|
13
|
+
*/
|
|
14
|
+
export type Bucket = FileSystemDirectoryHandle;
|
|
15
|
+
/**
|
|
16
|
+
* Permission descriptor for File System Access API
|
|
17
|
+
*/
|
|
18
|
+
export interface FileSystemPermissionDescriptor {
|
|
19
|
+
mode?: "read" | "readwrite";
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Storage backend interface that abstracts filesystem operations
|
|
23
|
+
* across different storage types (memory, local disk, S3, R2, etc.)
|
|
24
|
+
*/
|
|
25
|
+
export interface FileSystemBackend {
|
|
26
|
+
/**
|
|
27
|
+
* Check if entry exists and return its type
|
|
28
|
+
* @param path Path to the entry
|
|
29
|
+
* @returns Entry info if exists, null if not found
|
|
30
|
+
*/
|
|
31
|
+
stat(path: string): Promise<{
|
|
32
|
+
kind: "file" | "directory";
|
|
33
|
+
} | null>;
|
|
34
|
+
/**
|
|
35
|
+
* Read file content as bytes
|
|
36
|
+
* @param path Path to the file
|
|
37
|
+
* @returns File content as Uint8Array
|
|
38
|
+
* @throws NotFoundError if file doesn't exist
|
|
39
|
+
*/
|
|
40
|
+
readFile(path: string): Promise<Uint8Array>;
|
|
41
|
+
/**
|
|
42
|
+
* Write file content
|
|
43
|
+
* @param path Path to the file
|
|
44
|
+
* @param data File content as Uint8Array
|
|
45
|
+
* @throws Error if write fails
|
|
46
|
+
*/
|
|
47
|
+
writeFile(path: string, data: Uint8Array): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* List directory entries
|
|
50
|
+
* @param path Path to the directory
|
|
51
|
+
* @returns Array of entry info (name + type)
|
|
52
|
+
* @throws NotFoundError if directory doesn't exist
|
|
53
|
+
*/
|
|
54
|
+
listDir(path: string): Promise<Array<{
|
|
55
|
+
name: string;
|
|
56
|
+
kind: "file" | "directory";
|
|
57
|
+
}>>;
|
|
58
|
+
/**
|
|
59
|
+
* Create directory (optional - some backends may not support this)
|
|
60
|
+
* @param path Path to the directory to create
|
|
61
|
+
* @throws NotSupportedError if backend doesn't support directory creation
|
|
62
|
+
*/
|
|
63
|
+
createDir?(path: string): Promise<void>;
|
|
64
|
+
/**
|
|
65
|
+
* Remove entry (optional - some backends may not support this)
|
|
66
|
+
* @param path Path to the entry to remove
|
|
67
|
+
* @param recursive Whether to remove directories recursively
|
|
68
|
+
* @throws NotSupportedError if backend doesn't support removal
|
|
69
|
+
*/
|
|
70
|
+
remove?(path: string, recursive?: boolean): Promise<void>;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Shared FileSystemHandle base implementation
|
|
74
|
+
* Provides common functionality for both file and directory handles
|
|
75
|
+
*/
|
|
76
|
+
export declare abstract class ShovelHandle implements FileSystemHandle {
|
|
77
|
+
#private;
|
|
78
|
+
abstract readonly kind: "file" | "directory";
|
|
79
|
+
readonly path: string;
|
|
80
|
+
constructor(backend: FileSystemBackend, path: string);
|
|
81
|
+
get name(): string;
|
|
82
|
+
get backend(): FileSystemBackend;
|
|
83
|
+
isSameEntry(other: FileSystemHandle): Promise<boolean>;
|
|
84
|
+
queryPermission(descriptor?: FileSystemPermissionDescriptor): Promise<PermissionState>;
|
|
85
|
+
requestPermission(descriptor?: FileSystemPermissionDescriptor): Promise<PermissionState>;
|
|
86
|
+
/**
|
|
87
|
+
* Validates that a name is actually a name and not a path
|
|
88
|
+
* The File System Access API only accepts names, not paths
|
|
89
|
+
*/
|
|
90
|
+
validateName(name: string): void;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Shared FileSystemFileHandle implementation that works with any backend
|
|
94
|
+
* Uses non-standard constructor that takes backend + path
|
|
95
|
+
*/
|
|
96
|
+
export declare class ShovelFileHandle extends ShovelHandle implements FileSystemFileHandle {
|
|
97
|
+
#private;
|
|
98
|
+
readonly kind: "file";
|
|
99
|
+
constructor(backend: FileSystemBackend, path: string);
|
|
100
|
+
getFile(): Promise<File>;
|
|
101
|
+
createWritable(): Promise<FileSystemWritableFileStream>;
|
|
102
|
+
createSyncAccessHandle(): Promise<FileSystemSyncAccessHandle>;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Shared FileSystemDirectoryHandle implementation that works with any backend
|
|
106
|
+
* Uses non-standard constructor that takes backend + path
|
|
107
|
+
*/
|
|
108
|
+
export declare class ShovelDirectoryHandle extends ShovelHandle implements FileSystemDirectoryHandle {
|
|
109
|
+
#private;
|
|
110
|
+
readonly kind: "directory";
|
|
111
|
+
constructor(backend: FileSystemBackend, path: string);
|
|
112
|
+
getFileHandle(name: string, options?: {
|
|
113
|
+
create?: boolean;
|
|
114
|
+
}): Promise<FileSystemFileHandle>;
|
|
115
|
+
getDirectoryHandle(name: string, options?: {
|
|
116
|
+
create?: boolean;
|
|
117
|
+
}): Promise<FileSystemDirectoryHandle>;
|
|
118
|
+
removeEntry(name: string, options?: {
|
|
119
|
+
recursive?: boolean;
|
|
120
|
+
}): Promise<void>;
|
|
121
|
+
resolve(possibleDescendant: FileSystemHandle): Promise<string[] | null>;
|
|
122
|
+
entries(): AsyncIterableIterator<[
|
|
123
|
+
string,
|
|
124
|
+
FileSystemFileHandle | FileSystemDirectoryHandle
|
|
125
|
+
]>;
|
|
126
|
+
keys(): AsyncIterableIterator<string>;
|
|
127
|
+
values(): AsyncIterableIterator<FileSystemFileHandle | FileSystemDirectoryHandle>;
|
|
128
|
+
[Symbol.asyncIterator](): AsyncIterableIterator<[
|
|
129
|
+
string,
|
|
130
|
+
FileSystemFileHandle | FileSystemDirectoryHandle
|
|
131
|
+
]>;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Bucket storage interface - parallels CacheStorage for filesystem access
|
|
135
|
+
* This could become a future web standard
|
|
136
|
+
*/
|
|
137
|
+
export interface BucketStorage {
|
|
138
|
+
/**
|
|
139
|
+
* Open a named bucket - returns FileSystemDirectoryHandle (root of that bucket)
|
|
140
|
+
* Well-known names: 'static', 'tmp'
|
|
141
|
+
*/
|
|
142
|
+
open(name: string): Promise<FileSystemDirectoryHandle>;
|
|
143
|
+
/**
|
|
144
|
+
* Check if a named bucket exists
|
|
145
|
+
*/
|
|
146
|
+
has(name: string): Promise<boolean>;
|
|
147
|
+
/**
|
|
148
|
+
* Delete a named bucket and all its contents
|
|
149
|
+
*/
|
|
150
|
+
delete(name: string): Promise<boolean>;
|
|
151
|
+
/**
|
|
152
|
+
* List all available bucket names
|
|
153
|
+
*/
|
|
154
|
+
keys(): Promise<string[]>;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Factory function type for creating buckets
|
|
158
|
+
* @param name Bucket name to create
|
|
159
|
+
* @returns FileSystemDirectoryHandle (Bucket) instance
|
|
160
|
+
*/
|
|
161
|
+
export type BucketFactory = (name: string) => FileSystemDirectoryHandle | Promise<FileSystemDirectoryHandle>;
|
|
162
|
+
/**
|
|
163
|
+
* Custom bucket storage with factory-based bucket creation
|
|
164
|
+
*
|
|
165
|
+
* Provides a registry of named buckets (FileSystemDirectoryHandle instances)
|
|
166
|
+
* with lazy instantiation and singleton behavior per bucket name.
|
|
167
|
+
*
|
|
168
|
+
* Mirrors the CustomCacheStorage pattern for consistency across the platform.
|
|
169
|
+
*/
|
|
170
|
+
export declare class CustomBucketStorage {
|
|
171
|
+
#private;
|
|
172
|
+
/**
|
|
173
|
+
* @param factory Function that creates bucket instances by name
|
|
174
|
+
*/
|
|
175
|
+
constructor(factory: BucketFactory);
|
|
176
|
+
/**
|
|
177
|
+
* Open a named bucket - creates if it doesn't exist
|
|
178
|
+
*
|
|
179
|
+
* @param name Bucket name (e.g., 'tmp', 'dist', 'uploads')
|
|
180
|
+
* @returns FileSystemDirectoryHandle for the bucket
|
|
181
|
+
*/
|
|
182
|
+
open(name: string): Promise<FileSystemDirectoryHandle>;
|
|
183
|
+
/**
|
|
184
|
+
* Check if a named bucket exists
|
|
185
|
+
*
|
|
186
|
+
* @param name Bucket name to check
|
|
187
|
+
* @returns true if bucket has been opened
|
|
188
|
+
*/
|
|
189
|
+
has(name: string): Promise<boolean>;
|
|
190
|
+
/**
|
|
191
|
+
* Delete a named bucket
|
|
192
|
+
*
|
|
193
|
+
* @param name Bucket name to delete
|
|
194
|
+
* @returns true if bucket was deleted, false if it didn't exist
|
|
195
|
+
*/
|
|
196
|
+
delete(name: string): Promise<boolean>;
|
|
197
|
+
/**
|
|
198
|
+
* List all opened bucket names
|
|
199
|
+
*
|
|
200
|
+
* @returns Array of bucket names
|
|
201
|
+
*/
|
|
202
|
+
keys(): Promise<string[]>;
|
|
203
|
+
/**
|
|
204
|
+
* Get statistics about opened buckets (non-standard utility method)
|
|
205
|
+
*
|
|
206
|
+
* @returns Object with bucket statistics
|
|
207
|
+
*/
|
|
208
|
+
getStats(): {
|
|
209
|
+
openInstances: number;
|
|
210
|
+
bucketNames: string[];
|
|
211
|
+
};
|
|
212
|
+
}
|