@statewalker/webrun-files-browser 0.9.0 → 0.10.1
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/README.md +11 -3
- package/dist/browser-files-api.d.ts +3 -0
- package/dist/browser-files-api.d.ts.map +1 -1
- package/dist/cjs/index.cjs +29 -27
- package/dist/esm/index.js +30 -28
- package/package.json +7 -6
- package/src/browser-files-api.ts +38 -37
package/README.md
CHANGED
|
@@ -115,7 +115,8 @@ class BrowserFilesApi implements FilesApi {
|
|
|
115
115
|
### Helper Functions
|
|
116
116
|
|
|
117
117
|
```typescript
|
|
118
|
-
//
|
|
118
|
+
// Returns a BrowserFilesApi for the stored handle, or for one chosen in a directory picker;
|
|
119
|
+
// throws if permission is denied or the directory is no longer accessible
|
|
119
120
|
function openBrowserFilesApi(options?: OpenBrowserFilesApiOptions): Promise<BrowserFilesApi>;
|
|
120
121
|
|
|
121
122
|
interface OpenBrowserFilesApiOptions {
|
|
@@ -141,9 +142,16 @@ function verifyPermission(
|
|
|
141
142
|
): Promise<boolean>;
|
|
142
143
|
```
|
|
143
144
|
|
|
145
|
+
## Ordered Listings
|
|
146
|
+
|
|
147
|
+
Directory handles enumerate in no defined order, so `list()` reads each directory (fetching each
|
|
148
|
+
file's `File` for its size and time) and yields entries in path order, as every `FilesApi` does. A
|
|
149
|
+
recursive listing reads a directory only when the listing reaches it, and with `{ after }` skips any
|
|
150
|
+
directory whose whole subtree sorts at or before the cursor.
|
|
151
|
+
|
|
144
152
|
## Native Move Support
|
|
145
153
|
|
|
146
|
-
The `move()` method attempts to use the browser's native [`FileSystemHandle.move()`](https://developer.mozilla.org/en-US/docs/Web/API/FileSystemHandle/move) when available (Chrome 110+, Edge 110+). This provides an atomic, in-place rename/move without copying file data. On older browsers or polyfills that lack native `move()`, the method falls back to copy-then-delete.
|
|
154
|
+
The `move()` method attempts to use the browser's native [`FileSystemHandle.move()`](https://developer.mozilla.org/en-US/docs/Web/API/FileSystemHandle/move) when available (Chrome 110+, Edge 110+). This provides an atomic, in-place rename/move without copying file data. On older browsers or polyfills that lack native `move()`, or when the native call throws, the method falls back to copy-then-delete, which is not atomic.
|
|
147
155
|
|
|
148
156
|
## Browser Support
|
|
149
157
|
|
|
@@ -169,7 +177,7 @@ When working with persistent directory handles:
|
|
|
169
177
|
```typescript
|
|
170
178
|
import { verifyPermission, isHandlerAccessible } from '@statewalker/webrun-files-browser';
|
|
171
179
|
|
|
172
|
-
// Check if
|
|
180
|
+
// Check read-write permission, prompting the user if it is not already granted (needs a user gesture)
|
|
173
181
|
const hasPermission = await verifyPermission(directoryHandle, true);
|
|
174
182
|
|
|
175
183
|
// Check if the directory still exists and is accessible
|
|
@@ -27,7 +27,10 @@ export declare class BrowserFilesApi implements FilesApi {
|
|
|
27
27
|
read(path: string, options?: ReadOptions): AsyncIterable<Uint8Array>;
|
|
28
28
|
write(path: string, content: Iterable<Uint8Array> | AsyncIterable<Uint8Array>): Promise<void>;
|
|
29
29
|
mkdir(path: string): Promise<void>;
|
|
30
|
+
/** Directory handles enumerate in no defined order, so the tree is walked in `comparePaths` order. */
|
|
30
31
|
list(path: string, options?: ListOptions): AsyncIterable<FileInfo>;
|
|
32
|
+
/** A directory's direct entries, in handle order; none if it does not exist. */
|
|
33
|
+
private readChildren;
|
|
31
34
|
stats(path: string): Promise<FileStats | undefined>;
|
|
32
35
|
exists(path: string): Promise<boolean>;
|
|
33
36
|
remove(path: string): Promise<boolean>;
|
|
@@ -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,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,WAAW,EACX,WAAW,EACZ,MAAM,2BAA2B,CAAC;
|
|
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;AAcnC,MAAM,WAAW,sBAAsB;IACrC;;;OAGG;IACH,UAAU,EAAE,yBAAyB,CAAC;CACvC;AAED;;;;;;GAMG;AACH,qBAAa,eAAgB,YAAW,QAAQ;IAC9C,OAAO,CAAC,UAAU,CAA4B;IAE9C,YAAY,OAAO,EAAE,sBAAsB,EAE1C;YAEa,kBAAkB;YAkBlB,aAAa;IAkBpB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,aAAa,CAAC,UAAU,CAAC,CA2B1E;IAEK,KAAK,CACT,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,aAAa,CAAC,UAAU,CAAC,GACxD,OAAO,CAAC,IAAI,CAAC,CAoBf;IAEK,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGvC;IAED,sGAAsG;IAC/F,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,aAAa,CAAC,QAAQ,CAAC,CAExE;IAED,gFAAgF;YAClE,YAAY;IAiCpB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC,CA6BxD;IAEK,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAG3C;IAEK,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAc3C;IAEK,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CA8B3D;IAEK,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CA2E3D;CACF;AAED;;GAEG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,eAAe,CAAC,CAGhE"}
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -75,38 +75,40 @@ var BrowserFilesApi = class {
|
|
|
75
75
|
const normalized = (0, _statewalker_webrun_files.normalizePath)(path);
|
|
76
76
|
await this.getDirectoryHandle(normalized, { create: true });
|
|
77
77
|
}
|
|
78
|
+
/** Directory handles enumerate in no defined order, so the tree is walked in `comparePaths` order. */
|
|
78
79
|
async *list(path, options) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
yield* (0, _statewalker_webrun_files.listInPathOrder)((0, _statewalker_webrun_files.normalizePath)(path), (dir) => this.readChildren(dir), options);
|
|
81
|
+
}
|
|
82
|
+
/** A directory's direct entries, in handle order; none if it does not exist. */
|
|
83
|
+
async readChildren(dir) {
|
|
84
|
+
const dirHandle = await this.getDirectoryHandle(dir);
|
|
85
|
+
if (!dirHandle) return [];
|
|
86
|
+
const infos = [];
|
|
83
87
|
for await (const [name, handle] of dirHandle.entries()) {
|
|
84
|
-
const entryPath = (0, _statewalker_webrun_files.joinPath)(
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
kind: "directory",
|
|
89
|
-
name,
|
|
90
|
-
path: entryPath
|
|
91
|
-
};
|
|
92
|
-
else {
|
|
93
|
-
let file;
|
|
94
|
-
try {
|
|
95
|
-
file = await handle.getFile();
|
|
96
|
-
} catch {
|
|
97
|
-
continue;
|
|
98
|
-
}
|
|
99
|
-
info = {
|
|
100
|
-
kind: "file",
|
|
88
|
+
const entryPath = (0, _statewalker_webrun_files.joinPath)(dir, name);
|
|
89
|
+
if (handle.kind === "directory") {
|
|
90
|
+
infos.push({
|
|
91
|
+
kind: "directory",
|
|
101
92
|
name,
|
|
102
|
-
path: entryPath
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
};
|
|
93
|
+
path: entryPath
|
|
94
|
+
});
|
|
95
|
+
continue;
|
|
106
96
|
}
|
|
107
|
-
|
|
108
|
-
|
|
97
|
+
let file;
|
|
98
|
+
try {
|
|
99
|
+
file = await handle.getFile();
|
|
100
|
+
} catch {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
infos.push({
|
|
104
|
+
kind: "file",
|
|
105
|
+
name,
|
|
106
|
+
path: entryPath,
|
|
107
|
+
size: file.size,
|
|
108
|
+
lastModified: file.lastModified
|
|
109
|
+
});
|
|
109
110
|
}
|
|
111
|
+
return infos;
|
|
110
112
|
}
|
|
111
113
|
async stats(path) {
|
|
112
114
|
const normalized = (0, _statewalker_webrun_files.normalizePath)(path);
|
package/dist/esm/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { basename, dirname, joinPath, normalizePath } from "@statewalker/webrun-files";
|
|
1
|
+
import { basename, dirname, joinPath, listInPathOrder, normalizePath } from "@statewalker/webrun-files";
|
|
2
2
|
//#region src/browser-files-api.ts
|
|
3
3
|
/**
|
|
4
4
|
* Browser filesystem implementation using the File System Access API.
|
|
@@ -74,38 +74,40 @@ var BrowserFilesApi = class {
|
|
|
74
74
|
const normalized = normalizePath(path);
|
|
75
75
|
await this.getDirectoryHandle(normalized, { create: true });
|
|
76
76
|
}
|
|
77
|
+
/** Directory handles enumerate in no defined order, so the tree is walked in `comparePaths` order. */
|
|
77
78
|
async *list(path, options) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
yield* listInPathOrder(normalizePath(path), (dir) => this.readChildren(dir), options);
|
|
80
|
+
}
|
|
81
|
+
/** A directory's direct entries, in handle order; none if it does not exist. */
|
|
82
|
+
async readChildren(dir) {
|
|
83
|
+
const dirHandle = await this.getDirectoryHandle(dir);
|
|
84
|
+
if (!dirHandle) return [];
|
|
85
|
+
const infos = [];
|
|
82
86
|
for await (const [name, handle] of dirHandle.entries()) {
|
|
83
|
-
const entryPath = joinPath(
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
kind: "directory",
|
|
88
|
-
name,
|
|
89
|
-
path: entryPath
|
|
90
|
-
};
|
|
91
|
-
else {
|
|
92
|
-
let file;
|
|
93
|
-
try {
|
|
94
|
-
file = await handle.getFile();
|
|
95
|
-
} catch {
|
|
96
|
-
continue;
|
|
97
|
-
}
|
|
98
|
-
info = {
|
|
99
|
-
kind: "file",
|
|
87
|
+
const entryPath = joinPath(dir, name);
|
|
88
|
+
if (handle.kind === "directory") {
|
|
89
|
+
infos.push({
|
|
90
|
+
kind: "directory",
|
|
100
91
|
name,
|
|
101
|
-
path: entryPath
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
};
|
|
92
|
+
path: entryPath
|
|
93
|
+
});
|
|
94
|
+
continue;
|
|
105
95
|
}
|
|
106
|
-
|
|
107
|
-
|
|
96
|
+
let file;
|
|
97
|
+
try {
|
|
98
|
+
file = await handle.getFile();
|
|
99
|
+
} catch {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
infos.push({
|
|
103
|
+
kind: "file",
|
|
104
|
+
name,
|
|
105
|
+
path: entryPath,
|
|
106
|
+
size: file.size,
|
|
107
|
+
lastModified: file.lastModified
|
|
108
|
+
});
|
|
108
109
|
}
|
|
110
|
+
return infos;
|
|
109
111
|
}
|
|
110
112
|
async stats(path) {
|
|
111
113
|
const normalized = normalizePath(path);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@statewalker/webrun-files-browser",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Browser File System Access API implementation for webrun-files",
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"types": "./dist/index.d.ts",
|
|
20
20
|
"exports": {
|
|
21
21
|
".": {
|
|
22
|
+
"source": "./src/index.ts",
|
|
22
23
|
"types": "./dist/index.d.ts",
|
|
23
24
|
"import": "./dist/esm/index.js",
|
|
24
25
|
"require": "./dist/cjs/index.cjs"
|
|
@@ -29,16 +30,16 @@
|
|
|
29
30
|
"src"
|
|
30
31
|
],
|
|
31
32
|
"dependencies": {
|
|
32
|
-
"@statewalker/webrun-files": "^0.
|
|
33
|
+
"@statewalker/webrun-files": "^0.10.1"
|
|
33
34
|
},
|
|
34
35
|
"devDependencies": {
|
|
35
|
-
"@types/node": "^26.
|
|
36
|
+
"@types/node": "^26.6.4",
|
|
36
37
|
"native-file-system-adapter": "^3.0.1",
|
|
37
38
|
"rimraf": "^6.1.3",
|
|
38
|
-
"rolldown": "^1.2.
|
|
39
|
+
"rolldown": "^1.2.12",
|
|
39
40
|
"typescript": "^7.0.2",
|
|
40
|
-
"vitest": "^
|
|
41
|
-
"@statewalker/webrun-files-tests": "
|
|
41
|
+
"vitest": "^5.0.3",
|
|
42
|
+
"@statewalker/webrun-files-tests": "^3.0.0"
|
|
42
43
|
},
|
|
43
44
|
"sideEffects": false,
|
|
44
45
|
"publishConfig": {
|
package/src/browser-files-api.ts
CHANGED
|
@@ -12,7 +12,13 @@ import type {
|
|
|
12
12
|
ListOptions,
|
|
13
13
|
ReadOptions,
|
|
14
14
|
} from "@statewalker/webrun-files";
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
basename,
|
|
17
|
+
dirname,
|
|
18
|
+
joinPath,
|
|
19
|
+
listInPathOrder,
|
|
20
|
+
normalizePath,
|
|
21
|
+
} from "@statewalker/webrun-files";
|
|
16
22
|
|
|
17
23
|
/** Chrome 110+ exposes move() on FileSystemHandle; not yet in TS DOM types. */
|
|
18
24
|
interface MovableHandle extends FileSystemHandle {
|
|
@@ -136,48 +142,43 @@ export class BrowserFilesApi implements FilesApi {
|
|
|
136
142
|
await this.getDirectoryHandle(normalized, { create: true });
|
|
137
143
|
}
|
|
138
144
|
|
|
145
|
+
/** Directory handles enumerate in no defined order, so the tree is walked in `comparePaths` order. */
|
|
139
146
|
async *list(path: string, options?: ListOptions): AsyncIterable<FileInfo> {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
if (!dirHandle) return;
|
|
143
|
-
|
|
144
|
-
const recursive = options?.recursive ?? false;
|
|
147
|
+
yield* listInPathOrder(normalizePath(path), (dir) => this.readChildren(dir), options);
|
|
148
|
+
}
|
|
145
149
|
|
|
150
|
+
/** A directory's direct entries, in handle order; none if it does not exist. */
|
|
151
|
+
private async readChildren(dir: string): Promise<FileInfo[]> {
|
|
152
|
+
const dirHandle = await this.getDirectoryHandle(dir);
|
|
153
|
+
if (!dirHandle) return [];
|
|
154
|
+
const infos: FileInfo[] = [];
|
|
146
155
|
for await (const [name, handle] of dirHandle.entries()) {
|
|
147
|
-
const entryPath = joinPath(
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
if (isDirectory) {
|
|
152
|
-
info = { kind: "directory", name, path: entryPath };
|
|
153
|
-
} else {
|
|
154
|
-
// A file must report both a size and a time, so the `File` behind the
|
|
155
|
-
// handle has to be obtained before the entry can be yielded at all.
|
|
156
|
-
// `getFile()` rejects when the entry was removed between the directory
|
|
157
|
-
// read and this call, or when permission was revoked mid-iteration;
|
|
158
|
-
// either way there is no file left to describe, and skipping it is
|
|
159
|
-
// the same answer a listing taken a moment later would have given.
|
|
160
|
-
let file: File;
|
|
161
|
-
try {
|
|
162
|
-
file = await (handle as FileSystemFileHandle).getFile();
|
|
163
|
-
} catch {
|
|
164
|
-
continue;
|
|
165
|
-
}
|
|
166
|
-
info = {
|
|
167
|
-
kind: "file",
|
|
168
|
-
name,
|
|
169
|
-
path: entryPath,
|
|
170
|
-
size: file.size,
|
|
171
|
-
lastModified: file.lastModified,
|
|
172
|
-
};
|
|
156
|
+
const entryPath = joinPath(dir, name);
|
|
157
|
+
if (handle.kind === "directory") {
|
|
158
|
+
infos.push({ kind: "directory", name, path: entryPath });
|
|
159
|
+
continue;
|
|
173
160
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
161
|
+
// A file must report both a size and a time, so the `File` behind the
|
|
162
|
+
// handle has to be obtained before the entry can be listed at all.
|
|
163
|
+
// `getFile()` rejects when the entry was removed between the directory
|
|
164
|
+
// read and this call, or when permission was revoked mid-iteration;
|
|
165
|
+
// either way there is no file left to describe, and skipping it is
|
|
166
|
+
// the same answer a listing taken a moment later would have given.
|
|
167
|
+
let file: File;
|
|
168
|
+
try {
|
|
169
|
+
file = await (handle as FileSystemFileHandle).getFile();
|
|
170
|
+
} catch {
|
|
171
|
+
continue;
|
|
179
172
|
}
|
|
173
|
+
infos.push({
|
|
174
|
+
kind: "file",
|
|
175
|
+
name,
|
|
176
|
+
path: entryPath,
|
|
177
|
+
size: file.size,
|
|
178
|
+
lastModified: file.lastModified,
|
|
179
|
+
});
|
|
180
180
|
}
|
|
181
|
+
return infos;
|
|
181
182
|
}
|
|
182
183
|
|
|
183
184
|
async stats(path: string): Promise<FileStats | undefined> {
|