@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/memory.js CHANGED
@@ -1,187 +1,233 @@
1
1
  /// <reference types="./memory.d.ts" />
2
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";
3
+ import {
4
+ ShovelDirectoryHandle,
5
+ ShovelFileHandle
6
+ } from "./index.js";
7
+ var MemoryFileSystemBackend = class {
8
+ #root;
9
+ constructor(root) {
10
+ this.#root = root;
11
+ }
12
+ async stat(path) {
13
+ const entry = this.#resolvePath(path);
14
+ if (!entry)
15
+ return null;
16
+ if ("content" in entry) {
17
+ return { kind: "file" };
18
+ } else {
19
+ return { kind: "directory" };
20
+ }
67
21
  }
68
- };
69
- var MemoryFileSystemDirectoryHandle = class _MemoryFileSystemDirectoryHandle {
70
- constructor(directory) {
71
- this.directory = directory;
72
- this.name = directory.name;
22
+ async readFile(path) {
23
+ const entry = this.#resolvePath(path);
24
+ if (!entry || !("content" in entry)) {
25
+ throw new DOMException("File not found", "NotFoundError");
26
+ }
27
+ return entry.content;
73
28
  }
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 = {
29
+ async writeFile(path, data) {
30
+ const { parentDir, name } = this.#resolveParent(path);
31
+ if (!parentDir) {
32
+ throw new DOMException("Parent directory not found", "NotFoundError");
33
+ }
34
+ const existingFile = parentDir.files.get(name);
35
+ if (existingFile) {
36
+ existingFile.content = data;
37
+ existingFile.lastModified = Date.now();
38
+ } else {
39
+ parentDir.files.set(name, {
80
40
  name,
81
- content: new Uint8Array(0),
41
+ content: data,
82
42
  lastModified: Date.now(),
83
43
  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
44
  });
92
- } else if (!file) {
93
- throw new DOMException("File not found", "NotFoundError");
94
45
  }
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
46
  }
102
- async getDirectoryHandle(name, options) {
103
- const dir = this.directory.directories.get(name);
104
- if (!dir && options?.create) {
105
- const newDir = {
47
+ async listDir(path) {
48
+ const entry = this.#resolvePath(path);
49
+ if (!entry || "content" in entry) {
50
+ throw new DOMException("Directory not found", "NotFoundError");
51
+ }
52
+ const results = [];
53
+ for (const fileName of entry.files.keys()) {
54
+ results.push({ name: fileName, kind: "file" });
55
+ }
56
+ for (const dirName of entry.directories.keys()) {
57
+ results.push({ name: dirName, kind: "directory" });
58
+ }
59
+ return results;
60
+ }
61
+ async createDir(path) {
62
+ const { parentDir, name } = this.#resolveParent(path);
63
+ if (!parentDir) {
64
+ throw new DOMException("Parent directory not found", "NotFoundError");
65
+ }
66
+ if (!parentDir.directories.has(name)) {
67
+ parentDir.directories.set(name, {
106
68
  name,
107
69
  files: /* @__PURE__ */ new Map(),
108
70
  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");
71
+ });
114
72
  }
115
- return new _MemoryFileSystemDirectoryHandle(dir);
116
73
  }
117
- async removeEntry(name, options) {
118
- if (this.directory.files.has(name)) {
119
- this.directory.files.delete(name);
74
+ async remove(path, recursive) {
75
+ const { parentDir, name } = this.#resolveParent(path);
76
+ if (!parentDir) {
77
+ throw new DOMException("Entry not found", "NotFoundError");
78
+ }
79
+ if (parentDir.files.has(name)) {
80
+ parentDir.files.delete(name);
120
81
  return;
121
82
  }
122
- const dir = this.directory.directories.get(name);
83
+ const dir = parentDir.directories.get(name);
123
84
  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
- }
85
+ if ((dir.files.size > 0 || dir.directories.size > 0) && !recursive) {
86
+ throw new DOMException(
87
+ "Directory is not empty",
88
+ "InvalidModificationError"
89
+ );
131
90
  }
132
- this.directory.directories.delete(name);
91
+ parentDir.directories.delete(name);
133
92
  return;
134
93
  }
135
94
  throw new DOMException("Entry not found", "NotFoundError");
136
95
  }
137
- async resolve(possibleDescendant) {
138
- if (possibleDescendant instanceof MemoryFileSystemFileHandle) {
139
- if (this.directory.files.has(possibleDescendant.name)) {
140
- return [possibleDescendant.name];
96
+ #resolvePath(path) {
97
+ if (path.includes("..") || path.includes("\0")) {
98
+ throw new DOMException(
99
+ "Invalid path: contains path traversal or null bytes",
100
+ "NotAllowedError"
101
+ );
102
+ }
103
+ const parts = path.split("/").filter(Boolean);
104
+ if (parts.length === 0) {
105
+ return this.#root;
106
+ }
107
+ for (const part of parts) {
108
+ if (part === "." || part === ".." || part.includes("/") || part.includes("\\")) {
109
+ throw new DOMException("Invalid path component", "NotAllowedError");
141
110
  }
142
111
  }
143
- if (possibleDescendant instanceof _MemoryFileSystemDirectoryHandle) {
144
- if (this.directory.directories.has(possibleDescendant.name)) {
145
- return [possibleDescendant.name];
112
+ let current = this.#root;
113
+ for (let i = 0; i < parts.length - 1; i++) {
114
+ const nextDir = current.directories.get(parts[i]);
115
+ if (!nextDir)
116
+ return null;
117
+ current = nextDir;
118
+ }
119
+ const finalName = parts[parts.length - 1];
120
+ const file = current.files.get(finalName);
121
+ if (file)
122
+ return file;
123
+ const dir = current.directories.get(finalName);
124
+ if (dir)
125
+ return dir;
126
+ return null;
127
+ }
128
+ #resolveParent(path) {
129
+ const parts = path.split("/").filter(Boolean);
130
+ const name = parts.pop() || "";
131
+ if (parts.length === 0) {
132
+ return { parentDir: this.#root, name };
133
+ }
134
+ let current = this.#root;
135
+ for (const part of parts) {
136
+ const nextDir = current.directories.get(part);
137
+ if (!nextDir) {
138
+ return { parentDir: null, name };
146
139
  }
140
+ current = nextDir;
141
+ }
142
+ return { parentDir: current, name };
143
+ }
144
+ };
145
+ var MemoryBucket = class _MemoryBucket {
146
+ kind;
147
+ name;
148
+ #backend;
149
+ constructor(name = "root") {
150
+ this.kind = "directory";
151
+ this.name = name;
152
+ const root = {
153
+ name,
154
+ files: /* @__PURE__ */ new Map(),
155
+ directories: /* @__PURE__ */ new Map()
156
+ };
157
+ this.#backend = new MemoryFileSystemBackend(root);
158
+ }
159
+ async getFileHandle(name, options) {
160
+ const filePath = `/${name}`;
161
+ const stat = await this.#backend.stat(filePath);
162
+ if (!stat && options?.create) {
163
+ await this.#backend.writeFile(filePath, new Uint8Array(0));
164
+ } else if (!stat) {
165
+ throw new DOMException("File not found", "NotFoundError");
166
+ } else if (stat.kind !== "file") {
167
+ throw new DOMException(
168
+ "Path exists but is not a file",
169
+ "TypeMismatchError"
170
+ );
171
+ }
172
+ return new ShovelFileHandle(this.#backend, filePath);
173
+ }
174
+ async getDirectoryHandle(name, options) {
175
+ const dirPath = `/${name}`;
176
+ const stat = await this.#backend.stat(dirPath);
177
+ if (!stat && options?.create) {
178
+ await this.#backend.createDir(dirPath);
179
+ } else if (!stat) {
180
+ throw new DOMException("Directory not found", "NotFoundError");
181
+ } else if (stat.kind !== "directory") {
182
+ throw new DOMException(
183
+ "Path exists but is not a directory",
184
+ "TypeMismatchError"
185
+ );
186
+ }
187
+ return new ShovelDirectoryHandle(this.#backend, dirPath);
188
+ }
189
+ async removeEntry(name, options) {
190
+ const entryPath = `/${name}`;
191
+ await this.#backend.remove(entryPath, options?.recursive);
192
+ }
193
+ async resolve(possibleDescendant) {
194
+ if (!(possibleDescendant instanceof ShovelDirectoryHandle || possibleDescendant instanceof ShovelFileHandle)) {
195
+ return null;
196
+ }
197
+ const descendantPath = possibleDescendant.path;
198
+ if (typeof descendantPath === "string" && descendantPath.startsWith("/")) {
199
+ return descendantPath.split("/").filter(Boolean);
147
200
  }
148
201
  return null;
149
202
  }
150
203
  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)];
204
+ const entries = await this.#backend.listDir("/");
205
+ for (const entry of entries) {
206
+ const entryPath = `/${entry.name}`;
207
+ if (entry.kind === "file") {
208
+ yield [entry.name, new ShovelFileHandle(this.#backend, entryPath)];
209
+ } else {
210
+ yield [entry.name, new ShovelDirectoryHandle(this.#backend, entryPath)];
211
+ }
164
212
  }
165
213
  }
166
214
  async *keys() {
167
- for (const name of this.directory.files.keys()) {
168
- yield name;
169
- }
170
- for (const name of this.directory.directories.keys()) {
215
+ for await (const [name] of this.entries()) {
171
216
  yield name;
172
217
  }
173
218
  }
174
219
  async *values() {
175
- for (const [, handle] of this.entries()) {
220
+ for await (const [, handle] of this.entries()) {
176
221
  yield handle;
177
222
  }
178
223
  }
224
+ [Symbol.asyncIterator]() {
225
+ return this.entries();
226
+ }
179
227
  async isSameEntry(other) {
180
228
  if (other.kind !== "directory")
181
229
  return false;
182
- if (!(other instanceof _MemoryFileSystemDirectoryHandle))
183
- return false;
184
- return this.directory === other.directory;
230
+ return other instanceof _MemoryBucket && other.name === this.name;
185
231
  }
186
232
  async queryPermission() {
187
233
  return "granted";
@@ -190,35 +236,7 @@ var MemoryFileSystemDirectoryHandle = class _MemoryFileSystemDirectoryHandle {
190
236
  return "granted";
191
237
  }
192
238
  };
193
- var MemoryBucket = class {
194
- config;
195
- filesystems = /* @__PURE__ */ new Map();
196
- constructor(config = {}) {
197
- this.config = {
198
- name: "memory",
199
- ...config
200
- };
201
- }
202
- async getDirectoryHandle(name) {
203
- const dirName = name || "root";
204
- if (!this.filesystems.has(dirName)) {
205
- const root2 = {
206
- name: "root",
207
- files: /* @__PURE__ */ new Map(),
208
- directories: /* @__PURE__ */ new Map()
209
- };
210
- this.filesystems.set(dirName, root2);
211
- }
212
- const root = this.filesystems.get(dirName);
213
- return new MemoryFileSystemDirectoryHandle(root);
214
- }
215
- getConfig() {
216
- return { ...this.config };
217
- }
218
- async dispose() {
219
- this.filesystems.clear();
220
- }
221
- };
222
239
  export {
223
- MemoryBucket
240
+ MemoryBucket,
241
+ MemoryFileSystemBackend
224
242
  };
package/src/node.d.ts CHANGED
@@ -1,67 +1,34 @@
1
1
  /**
2
- * Node.js implementation of File System Access API
2
+ * Node.js filesystem implementation
3
3
  *
4
- * Implements FileSystemDirectoryHandle and FileSystemFileHandle using fs/promises
5
- * to provide universal File System Access API on Node.js platforms.
4
+ * Provides NodeBucket (root) and NodeFileSystemBackend for storage operations
5
+ * using Node.js fs module.
6
6
  */
7
- import type { Bucket, FileSystemConfig } from "./types.js";
7
+ import { type FileSystemBackend, ShovelDirectoryHandle } from "./index.js";
8
8
  /**
9
- * Node.js implementation of FileSystemWritableFileStream
9
+ * Node.js storage backend that implements FileSystemBackend
10
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>;
11
+ export declare class NodeFileSystemBackend implements FileSystemBackend {
12
+ #private;
13
+ constructor(rootPath: string);
14
+ stat(filePath: string): Promise<{
15
+ kind: "file" | "directory";
16
+ } | null>;
17
+ readFile(filePath: string): Promise<Uint8Array>;
18
+ writeFile(filePath: string, data: Uint8Array): Promise<void>;
19
+ listDir(dirPath: string): Promise<Array<{
20
+ name: string;
21
+ kind: "file" | "directory";
22
+ }>>;
23
+ createDir(dirPath: string): Promise<void>;
24
+ remove(entryPath: string, recursive?: boolean): Promise<void>;
16
25
  }
17
26
  /**
18
- * Node.js implementation of FileSystemFileHandle
27
+ * Node bucket - root entry point for Node.js filesystem
28
+ * Extends ShovelDirectoryHandle with "/" as root path
19
29
  */
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
- * Local filesystem bucket using Node.js fs
60
- */
61
- export declare class LocalBucket implements Bucket {
62
- private config;
63
- private rootPath;
64
- constructor(config?: FileSystemConfig);
65
- getDirectoryHandle(name: string): Promise<FileSystemDirectoryHandle>;
66
- getConfig(): FileSystemConfig;
30
+ export declare class NodeBucket extends ShovelDirectoryHandle {
31
+ #private;
32
+ constructor(rootPath: string);
33
+ get name(): string;
67
34
  }