@b9g/filesystem 0.1.0

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