@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/src/bun-s3.js CHANGED
@@ -1,197 +1,245 @@
1
1
  /// <reference types="./bun-s3.d.ts" />
2
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;
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
- kind = "file";
30
- name;
31
- async getFile() {
32
- const s3file = this.s3Client.file(this.key);
16
+ async stat(path) {
33
17
  try {
34
- const exists = await s3file.exists();
35
- if (!exists) {
36
- throw new DOMException("File not found", "NotFoundError");
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
- if (error.message?.includes("NoSuchKey") || error.message?.includes("Not Found")) {
46
- throw new DOMException("File not found", "NotFoundError");
47
- }
48
- throw error;
34
+ return null;
49
35
  }
50
36
  }
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("");
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 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 {
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
- "Directory is not empty",
65
+ `Failed to write file: ${error}`,
148
66
  "InvalidModificationError"
149
67
  );
150
68
  }
151
69
  }
152
- async resolve(_possibleDescendant) {
153
- return null;
154
- }
155
- async *entries() {
156
- const listPrefix = this.prefix ? `${this.prefix}/` : "";
70
+ async listDir(path) {
157
71
  try {
158
- const result = await this.s3Client.list({
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 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)];
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
- const prefixKey = prefix.Prefix || prefix.prefix;
177
- if (prefixKey) {
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
- yield [
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
- if (!(other instanceof _BunS3FileSystemDirectoryHandle))
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
- BunS3FileSystemDirectoryHandle,
241
- BunS3FileSystemFileHandle,
242
- BunS3FileSystemWritableFileStream,
243
- S3Bucket
269
+ S3Bucket,
270
+ S3FileSystemBackend
244
271
  };
package/src/index.d.ts CHANGED
@@ -1,6 +1,212 @@
1
- export type { Bucket, FileSystemConfig } from "./types.js";
2
- export { MemoryBucket } from "./memory.js";
3
- export { LocalBucket, NodeFileSystemDirectoryHandle, NodeFileSystemFileHandle } from "./node.js";
4
- export { S3Bucket, BunS3FileSystemDirectoryHandle, BunS3FileSystemFileHandle } from "./bun-s3.js";
5
- export { BucketStorage } from "./directory-storage.js";
6
- export { FileSystemRegistry, getDirectoryHandle, getBucket, getFileSystemRoot } from "./registry.js";
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
+ }