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