@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/src/bun-s3.js CHANGED
@@ -1,197 +1,248 @@
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
+ 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 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 {
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
- "Directory is not empty",
68
+ `Failed to write file: ${error}`,
148
69
  "InvalidModificationError"
149
70
  );
150
71
  }
151
72
  }
152
- async resolve(_possibleDescendant) {
153
- return null;
154
- }
155
- async *entries() {
156
- const listPrefix = this.prefix ? `${this.prefix}/` : "";
73
+ async listDir(path) {
157
74
  try {
158
- const result = await this.s3Client.list({
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 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)];
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
- const prefixKey = prefix.Prefix || prefix.prefix;
177
- if (prefixKey) {
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
- yield [
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
- if (!(other instanceof _BunS3FileSystemDirectoryHandle))
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
- BunS3FileSystemDirectoryHandle,
241
- BunS3FileSystemFileHandle,
242
- BunS3FileSystemWritableFileStream,
243
- S3Bucket
272
+ S3Bucket,
273
+ S3FileSystemBackend
244
274
  };
package/src/index.d.ts CHANGED
@@ -1,6 +1,215 @@
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 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
+ }