@statewalker/webrun-files-browser 0.5.0 → 0.7.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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2022 statewalker
3
+ Copyright (c) 2022-2026 statewalker
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # @statewalker/webrun-files-browser
2
2
 
3
- Browser File System Access API implementation for the `@statewalker/webrun-files` package.
3
+ Browser File System Access API implementation of the `FilesApi` interface from `@statewalker/webrun-files`.
4
4
 
5
5
  ## Overview
6
6
 
7
- This package provides an `IFilesApi` implementation that works in modern browsers using the [File System Access API](https://developer.mozilla.org/en-US/docs/Web/API/File_System_API). It allows web applications to read and write files to:
7
+ This package provides a `FilesApi` implementation that works in modern browsers using the [File System Access API](https://developer.mozilla.org/en-US/docs/Web/API/File_System_API). It allows web applications to read and write files to:
8
8
 
9
9
  - **User-selected directories** via `showDirectoryPicker()`
10
10
  - **Origin Private File System (OPFS)** - a sandboxed filesystem that persists between sessions
@@ -20,100 +20,95 @@ npm install @statewalker/webrun-files-browser @statewalker/webrun-files
20
20
  ### With User-Selected Directory
21
21
 
22
22
  ```typescript
23
- import { openBrowserFilesApi } from "@statewalker/webrun-files-browser";
24
- import { FilesApi } from "@statewalker/webrun-files";
23
+ import { openBrowserFilesApi } from '@statewalker/webrun-files-browser';
24
+ import { readText, writeText } from '@statewalker/webrun-files';
25
25
 
26
26
  // Opens a directory picker dialog (requires user gesture)
27
- const browserFs = await openBrowserFilesApi();
28
- const api = new FilesApi(browserFs);
27
+ const files = await openBrowserFilesApi();
29
28
 
30
29
  // Write a file
31
- await api.write("/notes/hello.txt", [
32
- new TextEncoder().encode("Hello, World!")
33
- ]);
30
+ await writeText(files, '/notes/hello.txt', 'Hello, World!');
34
31
 
35
32
  // Read a file
36
- const content = await api.readFile("/notes/hello.txt");
37
- console.log(new TextDecoder().decode(content)); // "Hello, World!"
33
+ const content = await readText(files, '/notes/hello.txt');
34
+ console.log(content); // "Hello, World!"
38
35
 
39
36
  // List directory contents
40
- for await (const entry of api.list("/notes")) {
37
+ for await (const entry of files.list('/notes')) {
41
38
  console.log(entry.name, entry.kind);
42
39
  }
43
40
  ```
44
41
 
42
+ ### With Persistent Directory Handle
43
+
44
+ Persist the directory handle using IndexedDB (via `idb-keyval` or similar) to avoid prompting the user every time:
45
+
46
+ ```typescript
47
+ import { openBrowserFilesApi } from '@statewalker/webrun-files-browser';
48
+ import { get, set, del } from 'idb-keyval';
49
+
50
+ // Opens directory picker if no stored handle, otherwise reuses stored one
51
+ const files = await openBrowserFilesApi({
52
+ handlerKey: 'my-project-dir', // Key for storing the handle
53
+ readwrite: true, // Request read-write access
54
+ get, // Retrieve stored handle
55
+ set, // Store the handle
56
+ del, // Delete stored handle if inaccessible
57
+ });
58
+ ```
59
+
45
60
  ### With Origin Private File System (OPFS)
46
61
 
47
62
  OPFS provides a sandboxed filesystem that doesn't require user interaction:
48
63
 
49
64
  ```typescript
50
- import { getOPFSFilesApi } from "@statewalker/webrun-files-browser";
51
- import { FilesApi } from "@statewalker/webrun-files";
65
+ import { getOPFSFilesApi } from '@statewalker/webrun-files-browser';
66
+ import { writeText } from '@statewalker/webrun-files';
52
67
 
53
68
  // No user gesture required
54
- const opfsFs = await getOPFSFilesApi();
55
- const api = new FilesApi(opfsFs);
69
+ const files = await getOPFSFilesApi();
56
70
 
57
- // Use the same API as above
58
- await api.write("/data/config.json", [
59
- new TextEncoder().encode('{"theme": "dark"}')
60
- ]);
71
+ // Use the same API
72
+ await writeText(files, '/data/config.json', '{"theme": "dark"}');
61
73
  ```
62
74
 
63
75
  ### With Custom Directory Handle
64
76
 
65
- You can also create a `BrowserFilesApi` instance with any `FileSystemDirectoryHandle`:
77
+ Create a `BrowserFilesApi` instance with any `FileSystemDirectoryHandle`:
66
78
 
67
79
  ```typescript
68
- import { BrowserFilesApi } from "@statewalker/webrun-files-browser";
69
- import { FilesApi } from "@statewalker/webrun-files";
80
+ import { BrowserFilesApi } from '@statewalker/webrun-files-browser';
70
81
 
71
82
  // Get a directory handle from drag & drop, IndexedDB, etc.
72
83
  const directoryHandle = await getDirectoryHandleSomehow();
73
84
 
74
- const browserFs = new BrowserFilesApi({ rootHandle: directoryHandle });
75
- const api = new FilesApi(browserFs);
85
+ const files = new BrowserFilesApi({ rootHandle: directoryHandle });
76
86
  ```
77
87
 
78
88
  ## API Reference
79
89
 
80
90
  ### BrowserFilesApi
81
91
 
82
- Main class implementing `IFilesApi` for browser environments.
92
+ Main class implementing `FilesApi` for browser environments.
83
93
 
84
94
  ```typescript
85
95
  interface BrowserFilesApiOptions {
86
96
  rootHandle: FileSystemDirectoryHandle;
87
97
  }
88
98
 
89
- class BrowserFilesApi implements IFilesApi {
99
+ class BrowserFilesApi implements FilesApi {
90
100
  constructor(options: BrowserFilesApiOptions);
91
101
 
92
- // Core IFilesApi methods
93
- list(file: FileRef, options?: ListOptions): AsyncGenerator<FileInfo>;
94
- stats(file: FileRef): Promise<FileInfo | undefined>;
95
- remove(file: FileRef): Promise<boolean>;
96
- open(file: FileRef): Promise<FileHandle>;
97
-
98
- // Optional methods (implemented)
99
- mkdir(file: FileRef): Promise<void>;
100
- move(source: FileRef, target: FileRef): Promise<boolean>;
101
- copy(source: FileRef, target: FileRef, options?: CopyOptions): Promise<boolean>;
102
- }
103
- ```
104
-
105
- ### BrowserFileHandle
106
-
107
- File handle class for random access read/write operations.
108
-
109
- ```typescript
110
- class BrowserFileHandle implements FileHandle {
111
- readonly size: number;
112
-
113
- close(): Promise<void>;
114
- appendFile(data: BinaryStream, options?: AppendOptions): Promise<number>;
115
- createReadStream(options?: ReadStreamOptions): AsyncGenerator<Uint8Array>;
116
- createWriteStream(data: BinaryStream, options?: WriteStreamOptions): Promise<number>;
102
+ // All FilesApi methods
103
+ read(path: string, options?: ReadOptions): AsyncIterable<Uint8Array>;
104
+ write(path: string, content: Iterable<Uint8Array> | AsyncIterable<Uint8Array>): Promise<void>;
105
+ mkdir(path: string): Promise<void>;
106
+ list(path: string, options?: ListOptions): AsyncIterable<FileInfo>;
107
+ stats(path: string): Promise<FileStats | undefined>;
108
+ exists(path: string): Promise<boolean>;
109
+ remove(path: string): Promise<boolean>;
110
+ move(source: string, target: string): Promise<boolean>;
111
+ copy(source: string, target: string): Promise<boolean>;
117
112
  }
118
113
  ```
119
114
 
@@ -121,49 +116,29 @@ class BrowserFileHandle implements FileHandle {
121
116
 
122
117
  ```typescript
123
118
  // Opens a directory picker and returns a BrowserFilesApi instance
124
- function openBrowserFilesApi(): Promise<BrowserFilesApi>;
119
+ function openBrowserFilesApi(options?: OpenBrowserFilesApiOptions): Promise<BrowserFilesApi>;
120
+
121
+ interface OpenBrowserFilesApiOptions {
122
+ handlerKey?: string; // Key for storing the handle (default: "root-dir")
123
+ readwrite?: boolean; // Request read-write access (default: true)
124
+ get?: (key: string) => Promise<FileSystemDirectoryHandle | undefined>;
125
+ set?: (key: string, handler: FileSystemDirectoryHandle) => Promise<void>;
126
+ del?: (key: string) => Promise<void>;
127
+ }
125
128
 
126
129
  // Returns a BrowserFilesApi backed by the Origin Private File System
127
130
  function getOPFSFilesApi(): Promise<BrowserFilesApi>;
128
- ```
129
-
130
- ## How It Works
131
-
132
- ### Directory Navigation
133
-
134
- The implementation navigates the filesystem by traversing `FileSystemDirectoryHandle` entries. Paths like `/docs/notes/file.txt` are resolved by getting directory handles step by step:
135
-
136
- ```
137
- root → "docs" → "notes" → "file.txt"
138
- ```
139
-
140
- ### Reading Files
141
131
 
142
- Files are read using `File.slice()` for efficient partial reads. This enables random access without loading the entire file into memory:
132
+ // Checks if a file system handle is still accessible
133
+ function isHandlerAccessible(
134
+ fileHandle: FileSystemFileHandle | FileSystemDirectoryHandle
135
+ ): Promise<boolean>;
143
136
 
144
- ```typescript
145
- const handle = await api.open("/large-file.bin");
146
- // Read only bytes 1000-2000
147
- for await (const chunk of handle.createReadStream({ start: 1000, end: 2000 })) {
148
- console.log(chunk);
149
- }
150
- await handle.close();
151
- ```
152
-
153
- ### Writing Files
154
-
155
- Writes use `FileSystemWritableFileStream`:
156
-
157
- - **Full writes** create a new writable stream and write all data
158
- - **Partial writes** (with `start` option) preserve content before the start position
159
- - **Appends** seek to the end of the file before writing
160
-
161
- ### Directory Creation
162
-
163
- Directories are created automatically when writing files. You can also create empty directories:
164
-
165
- ```typescript
166
- await api.mkdir("/path/to/new/directory");
137
+ // Verifies and requests file system permissions for a handle
138
+ function verifyPermission(
139
+ fileHandle: FileSystemFileHandle | FileSystemDirectoryHandle,
140
+ readWrite?: boolean // default: false
141
+ ): Promise<boolean>;
167
142
  ```
168
143
 
169
144
  ## Browser Support
@@ -174,7 +149,7 @@ The File System Access API is supported in:
174
149
  - Opera 72+
175
150
  - Chrome for Android 86+
176
151
 
177
- For other browsers, you may need a polyfill like [native-file-system-adapter](https://github.com/nicolo-ribaudo/native-file-system-adapter).
152
+ For other browsers, you may need a polyfill like [native-file-system-adapter](https://github.com/jimmywarting/native-file-system-adapter).
178
153
 
179
154
  ## Security Considerations
180
155
 
@@ -183,19 +158,40 @@ For other browsers, you may need a polyfill like [native-file-system-adapter](ht
183
158
  - **Permission prompts**: Users must grant permission to access directories
184
159
  - **OPFS isolation**: Origin Private File System is isolated per-origin and not accessible to the user
185
160
 
186
- ## Testing
161
+ ## Permission and Accessibility Utilities
187
162
 
188
- For testing in Node.js environments, we use [native-file-system-adapter](https://github.com/jimmywarting/native-file-system-adapter) which provides a memory backend:
163
+ When working with persistent directory handles:
189
164
 
190
165
  ```typescript
191
- import { getOriginPrivateDirectory } from "native-file-system-adapter";
192
- import { BrowserFilesApi } from "@statewalker/webrun-files-browser";
166
+ import { verifyPermission, isHandlerAccessible } from '@statewalker/webrun-files-browser';
193
167
 
194
- const rootHandle = await getOriginPrivateDirectory(
195
- import("native-file-system-adapter/src/adapters/memory.js")
196
- );
168
+ // Check if we have read-write permission
169
+ const hasPermission = await verifyPermission(directoryHandle, true);
170
+
171
+ // Check if the directory still exists and is accessible
172
+ const isAccessible = await isHandlerAccessible(directoryHandle);
173
+
174
+ if (!hasPermission) {
175
+ console.log('Permission denied - user may need to re-grant access');
176
+ }
177
+
178
+ if (!isAccessible) {
179
+ console.log('Directory no longer accessible - may have been deleted or moved');
180
+ }
181
+ ```
182
+
183
+ ## Testing
184
+
185
+ For testing in Node.js environments, use [native-file-system-adapter](https://github.com/jimmywarting/native-file-system-adapter) which provides a memory backend:
186
+
187
+ ```typescript
188
+ import { getOriginPrivateDirectory } from 'native-file-system-adapter';
189
+ // @ts-expect-error - no type declarations
190
+ import * as driver from 'native-file-system-adapter/src/adapters/memory.js';
191
+ import { BrowserFilesApi } from '@statewalker/webrun-files-browser';
197
192
 
198
- const api = new BrowserFilesApi({ rootHandle });
193
+ const rootHandle = await getOriginPrivateDirectory(driver);
194
+ const files = new BrowserFilesApi({ rootHandle });
199
195
  ```
200
196
 
201
197
  ## License
@@ -1,10 +1,10 @@
1
1
  /**
2
- * Browser implementation of IFilesApi using the File System Access API.
2
+ * Browser implementation of FilesApi using the File System Access API.
3
3
  *
4
4
  * Provides a filesystem-like interface over browser's FileSystemDirectoryHandle.
5
5
  * Works with directories obtained via showDirectoryPicker() or OPFS.
6
6
  */
7
- import type { CopyOptions, FileHandle, FileInfo, FileRef, IFilesApi, ListOptions } from "@statewalker/webrun-files";
7
+ import type { FileInfo, FileStats, FilesApi, ListOptions, ReadOptions } from "@statewalker/webrun-files";
8
8
  export interface BrowserFilesApiOptions {
9
9
  /**
10
10
  * Root FileSystemDirectoryHandle to use as the filesystem root.
@@ -12,58 +12,30 @@ export interface BrowserFilesApiOptions {
12
12
  */
13
13
  rootHandle: FileSystemDirectoryHandle;
14
14
  }
15
- export declare class BrowserFilesApi implements IFilesApi {
15
+ /**
16
+ * Browser filesystem implementation using the File System Access API.
17
+ *
18
+ * Provides FilesApi interface over browser's FileSystemDirectoryHandle.
19
+ * This enables web applications to read/write files in user-selected directories
20
+ * or the Origin Private File System (OPFS).
21
+ */
22
+ export declare class BrowserFilesApi implements FilesApi {
16
23
  private rootHandle;
17
24
  constructor(options: BrowserFilesApiOptions);
18
- /**
19
- * Navigates to a directory handle from a path.
20
- * Returns null if the directory doesn't exist and create is false.
21
- */
22
25
  private getDirectoryHandle;
23
- /**
24
- * Gets a file handle from a path.
25
- * Returns null if the file doesn't exist and create is false.
26
- */
27
26
  private getFileHandle;
28
- /**
29
- * Lists entries in a directory.
30
- */
31
- list(file: FileRef, options?: ListOptions): AsyncGenerator<FileInfo>;
32
- /**
33
- * Gets file or directory stats.
34
- */
35
- stats(file: FileRef): Promise<FileInfo | undefined>;
36
- /**
37
- * Removes a file or directory.
38
- */
39
- remove(file: FileRef): Promise<boolean>;
40
- /**
41
- * Opens a file for reading and writing.
42
- * Creates the file and parent directories if they don't exist.
43
- */
44
- open(file: FileRef): Promise<FileHandle>;
45
- /**
46
- * Creates a directory (and parent directories if needed).
47
- */
48
- mkdir(file: FileRef): Promise<void>;
49
- /**
50
- * Moves a file or directory using copy + delete.
51
- * The File System Access API doesn't have a native move operation.
52
- */
53
- move(source: FileRef, target: FileRef): Promise<boolean>;
54
- /**
55
- * Copies a file or directory.
56
- */
57
- copy(source: FileRef, target: FileRef, options?: CopyOptions): Promise<boolean>;
27
+ read(path: string, options?: ReadOptions): AsyncIterable<Uint8Array>;
28
+ write(path: string, content: Iterable<Uint8Array> | AsyncIterable<Uint8Array>): Promise<void>;
29
+ mkdir(path: string): Promise<void>;
30
+ list(path: string, options?: ListOptions): AsyncIterable<FileInfo>;
31
+ stats(path: string): Promise<FileStats | undefined>;
32
+ exists(path: string): Promise<boolean>;
33
+ remove(path: string): Promise<boolean>;
34
+ move(source: string, target: string): Promise<boolean>;
35
+ copy(source: string, target: string): Promise<boolean>;
58
36
  }
59
- /**
60
- * Opens a browser directory picker and returns a BrowserFilesApi instance.
61
- * Only works in secure contexts (HTTPS) and requires user gesture.
62
- */
63
- export declare function openBrowserFilesApi(): Promise<BrowserFilesApi>;
64
37
  /**
65
38
  * Gets a BrowserFilesApi instance backed by the Origin Private File System (OPFS).
66
- * OPFS provides a sandboxed filesystem that persists between sessions.
67
39
  */
68
40
  export declare function getOPFSFilesApi(): Promise<BrowserFilesApi>;
69
41
  //# sourceMappingURL=browser-files-api.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"browser-files-api.d.ts","sourceRoot":"","sources":["../src/browser-files-api.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,WAAW,EACX,UAAU,EACV,QAAQ,EACR,OAAO,EACP,SAAS,EACT,WAAW,EACZ,MAAM,2BAA2B,CAAC;AAInC,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,UAAU,EAAE,yBAAyB,CAAC;CACvC;AAED,qBAAa,eAAgB,YAAW,SAAS;IAC/C,OAAO,CAAC,UAAU,CAA4B;gBAElC,OAAO,EAAE,sBAAsB;IAI3C;;;OAGG;YACW,kBAAkB;IAkBhC;;;OAGG;YACW,aAAa;IAkB3B;;OAEG;IACI,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,GAAE,WAAgB,GAAG,cAAc,CAAC,QAAQ,CAAC;IAsC/E;;OAEG;IACG,KAAK,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC;IA8CzD;;OAEG;IACG,MAAM,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAgB7C;;;OAGG;IACG,IAAI,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC;IAkB9C;;OAEG;IACG,KAAK,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAKzC;;;OAGG;IACG,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAM9D;;OAEG;IACG,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,OAAO,CAAC;CAiF1F;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,eAAe,CAAC,CAIpE;AAED;;;GAGG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,eAAe,CAAC,CAGhE"}
1
+ {"version":3,"file":"browser-files-api.d.ts","sourceRoot":"","sources":["../src/browser-files-api.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,WAAW,EACX,WAAW,EACZ,MAAM,2BAA2B,CAAC;AAGnC,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,UAAU,EAAE,yBAAyB,CAAC;CACvC;AAED;;;;;;GAMG;AACH,qBAAa,eAAgB,YAAW,QAAQ;IAC9C,OAAO,CAAC,UAAU,CAA4B;gBAElC,OAAO,EAAE,sBAAsB;YAI7B,kBAAkB;YAkBlB,aAAa;IAkBpB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,aAAa,CAAC,UAAU,CAAC;IA6BrE,KAAK,CACT,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,aAAa,CAAC,UAAU,CAAC,GACxD,OAAO,CAAC,IAAI,CAAC;IAsBV,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKjC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,aAAa,CAAC,QAAQ,CAAC;IAqCnE,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC;IAqCnD,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAKtC,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAgBtC,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAMtD,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CA4E7D;AAED;;GAEG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,eAAe,CAAC,CAGhE"}