@pistonite/pure 0.27.0 → 0.28.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 +2 -2
- package/src/fs/FsFile.ts +1 -1
- package/src/fs/FsFileImpl.ts +5 -11
- package/src/fs/FsFileStandaloneImplHandleAPI.ts +3 -9
- package/src/fs/FsFileSystemInternal.ts +1 -1
- package/src/fs/FsImplEntryAPI.ts +12 -48
- package/src/fs/FsImplFileAPI.ts +3 -12
- package/src/fs/FsImplHandleAPI.ts +13 -48
- package/src/fs/FsOpen.ts +27 -71
- package/src/fs/FsOpenFile.ts +2 -7
- package/src/fs/FsSave.ts +4 -11
- package/src/fs/FsSupportStatus.ts +1 -5
- package/src/fs/index.ts +2 -11
- package/src/log/index.ts +1 -5
- package/src/log/logger.ts +2 -6
- package/src/memory/async_erc.ts +4 -9
- package/src/memory/cell.ts +2 -8
- package/src/memory/erc.test.ts +123 -126
- package/src/memory/erc.ts +3 -8
- package/src/memory/persist.ts +1 -4
- package/src/pref/device.ts +3 -12
- package/src/pref/locale.ts +6 -18
- package/src/result/index.ts +1 -3
- package/src/sync/batch.test.ts +4 -6
- package/src/sync/batch.ts +4 -21
- package/src/sync/capture.ts +1 -4
- package/src/sync/debounce.ts +1 -6
- package/src/sync/latest.ts +2 -12
- package/src/sync/serial.test.ts +1 -2
- package/src/sync/serial.ts +3 -12
- package/src/sync/util.ts +1 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pistonite/pure",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Pure TypeScript libraries for my projects",
|
|
6
6
|
"homepage": "https://github.com/Pistonite/pure",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"vitest": "^3.2.4",
|
|
31
|
-
"mono-dev": "0.2.
|
|
31
|
+
"mono-dev": "0.2.7"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"denque": "^2.1.0"
|
package/src/fs/FsFile.ts
CHANGED
|
@@ -33,7 +33,7 @@ export interface FsFile {
|
|
|
33
33
|
* Set the content in memory. Does not save to disk.
|
|
34
34
|
* Does nothing if file is closed
|
|
35
35
|
*/
|
|
36
|
-
setBytes(content: Uint8Array): void;
|
|
36
|
+
setBytes(content: Uint8Array<ArrayBuffer>): void;
|
|
37
37
|
|
|
38
38
|
/**
|
|
39
39
|
* Load the file's content if it's not newer than fs
|
package/src/fs/FsFileImpl.ts
CHANGED
|
@@ -24,7 +24,7 @@ class FsFileImpl implements FsFile {
|
|
|
24
24
|
/** If the file is text */
|
|
25
25
|
private isText: boolean;
|
|
26
26
|
/** Bytes of the file */
|
|
27
|
-
private buffer: Uint8Array | undefined;
|
|
27
|
+
private buffer: Uint8Array<ArrayBuffer> | undefined;
|
|
28
28
|
/** If the content in the buffer is different from the content on FS */
|
|
29
29
|
private isBufferDirty: boolean;
|
|
30
30
|
/** The content string of the file */
|
|
@@ -97,9 +97,7 @@ class FsFileImpl implements FsFile {
|
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
99
|
if (this.buffer === undefined) {
|
|
100
|
-
const err = fsFail(
|
|
101
|
-
"Read was successful, but content was undefined",
|
|
102
|
-
);
|
|
100
|
+
const err = fsFail("Read was successful, but content was undefined");
|
|
103
101
|
return { err };
|
|
104
102
|
}
|
|
105
103
|
return { val: this.buffer };
|
|
@@ -117,7 +115,7 @@ class FsFileImpl implements FsFile {
|
|
|
117
115
|
this.lastModified = new Date().getTime();
|
|
118
116
|
}
|
|
119
117
|
|
|
120
|
-
public setBytes(content: Uint8Array): void {
|
|
118
|
+
public setBytes(content: Uint8Array<ArrayBuffer>): void {
|
|
121
119
|
if (this.closed) {
|
|
122
120
|
return;
|
|
123
121
|
}
|
|
@@ -155,9 +153,7 @@ class FsFileImpl implements FsFile {
|
|
|
155
153
|
}
|
|
156
154
|
this.lastModified = file.lastModified;
|
|
157
155
|
// load the buffer
|
|
158
|
-
const buffer = await tryAsync(
|
|
159
|
-
async () => new Uint8Array(await file.arrayBuffer()),
|
|
160
|
-
);
|
|
156
|
+
const buffer = await tryAsync(async () => new Uint8Array(await file.arrayBuffer()));
|
|
161
157
|
if ("err" in buffer) {
|
|
162
158
|
const err = fsFail(errstr(buffer.err));
|
|
163
159
|
return { err };
|
|
@@ -202,9 +198,7 @@ class FsFileImpl implements FsFile {
|
|
|
202
198
|
|
|
203
199
|
private decodeBuffer() {
|
|
204
200
|
try {
|
|
205
|
-
this.content = new TextDecoder("utf-8", { fatal: true }).decode(
|
|
206
|
-
this.buffer,
|
|
207
|
-
);
|
|
201
|
+
this.content = new TextDecoder("utf-8", { fatal: true }).decode(this.buffer);
|
|
208
202
|
this.isText = true;
|
|
209
203
|
} catch {
|
|
210
204
|
this.content = undefined;
|
|
@@ -104,14 +104,11 @@ export class FsFileStandaloneImplHandleAPI implements FsFileStandalone {
|
|
|
104
104
|
return { err: fsFail(errstr(e)) };
|
|
105
105
|
}
|
|
106
106
|
}
|
|
107
|
-
public async write(content: Uint8Array | string): Promise<FsVoid> {
|
|
107
|
+
public async write(content: Uint8Array<ArrayBuffer> | string): Promise<FsVoid> {
|
|
108
108
|
const writable = await this.isWritable();
|
|
109
109
|
if (!writable) {
|
|
110
110
|
return {
|
|
111
|
-
err: fsErr(
|
|
112
|
-
FsErr.NotSupported,
|
|
113
|
-
"Permission was not granted or API not supported",
|
|
114
|
-
),
|
|
111
|
+
err: fsErr(FsErr.NotSupported, "Permission was not granted or API not supported"),
|
|
115
112
|
};
|
|
116
113
|
}
|
|
117
114
|
try {
|
|
@@ -131,10 +128,7 @@ export class FsFileStandaloneImplHandleAPI implements FsFileStandalone {
|
|
|
131
128
|
}
|
|
132
129
|
if (e.name === "NoMidificationAllowedError") {
|
|
133
130
|
return {
|
|
134
|
-
err: fsErr(
|
|
135
|
-
FsErr.PermissionDenied,
|
|
136
|
-
"Failed to acquire write lock",
|
|
137
|
-
),
|
|
131
|
+
err: fsErr(FsErr.PermissionDenied, "Failed to acquire write lock"),
|
|
138
132
|
};
|
|
139
133
|
}
|
|
140
134
|
if (e.name === "AbortError") {
|
|
@@ -21,7 +21,7 @@ export interface FsFileSystemInternal {
|
|
|
21
21
|
* Returns NotSupported if the browser does not support this
|
|
22
22
|
* Returns PermissionDenied if the operation is supported, but permission is not given
|
|
23
23
|
*/
|
|
24
|
-
write: (path: string, content: Uint8Array) => Promise<FsVoid>;
|
|
24
|
+
write: (path: string, content: Uint8Array<ArrayBuffer>) => Promise<FsVoid>;
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
27
|
* Forget about a file
|
package/src/fs/FsImplEntryAPI.ts
CHANGED
|
@@ -1,20 +1,14 @@
|
|
|
1
1
|
import { type Ok, tryAsync, errstr } from "../result/index.ts";
|
|
2
2
|
|
|
3
3
|
import { FsErr, type FsResult, type FsVoid, fsErr, fsFail } from "./FsError.ts";
|
|
4
|
-
import type {
|
|
5
|
-
FsFileSystem,
|
|
6
|
-
FsFileSystemUninit,
|
|
7
|
-
FsCapabilities,
|
|
8
|
-
} from "./FsFileSystem.ts";
|
|
4
|
+
import type { FsFileSystem, FsFileSystemUninit, FsCapabilities } from "./FsFileSystem.ts";
|
|
9
5
|
import type { FsFile } from "./FsFile.ts";
|
|
10
6
|
import { fsIsRoot, fsNormalize } from "./FsPath.ts";
|
|
11
7
|
import { FsFileMgr } from "./FsFileMgr.ts";
|
|
12
8
|
import type { FsFileSystemInternal } from "./FsFileSystemInternal.ts";
|
|
13
9
|
|
|
14
10
|
/** FsFileSystem implementation that uses FileEntry API */
|
|
15
|
-
export class FsImplEntryAPI
|
|
16
|
-
implements FsFileSystemUninit, FsFileSystem, FsFileSystemInternal
|
|
17
|
-
{
|
|
11
|
+
export class FsImplEntryAPI implements FsFileSystemUninit, FsFileSystem, FsFileSystemInternal {
|
|
18
12
|
public root: string;
|
|
19
13
|
public capabilities: FsCapabilities;
|
|
20
14
|
|
|
@@ -53,12 +47,7 @@ export class FsImplEntryAPI
|
|
|
53
47
|
}),
|
|
54
48
|
);
|
|
55
49
|
if ("err" in entries) {
|
|
56
|
-
const err = fsFail(
|
|
57
|
-
"Failed to list directory `" +
|
|
58
|
-
path +
|
|
59
|
-
"`: " +
|
|
60
|
-
errstr(entries.err),
|
|
61
|
-
);
|
|
50
|
+
const err = fsFail("Failed to list directory `" + path + "`: " + errstr(entries.err));
|
|
62
51
|
return { err };
|
|
63
52
|
}
|
|
64
53
|
|
|
@@ -91,9 +80,7 @@ export class FsImplEntryAPI
|
|
|
91
80
|
}),
|
|
92
81
|
);
|
|
93
82
|
if ("err" in file) {
|
|
94
|
-
const err = fsFail(
|
|
95
|
-
"Failed to read file `" + path + "`: " + errstr(file.err),
|
|
96
|
-
);
|
|
83
|
+
const err = fsFail("Failed to read file `" + path + "`: " + errstr(file.err));
|
|
97
84
|
return { err };
|
|
98
85
|
}
|
|
99
86
|
|
|
@@ -101,10 +88,7 @@ export class FsImplEntryAPI
|
|
|
101
88
|
}
|
|
102
89
|
|
|
103
90
|
public write(): Promise<FsVoid> {
|
|
104
|
-
const err = fsErr(
|
|
105
|
-
FsErr.NotSupported,
|
|
106
|
-
"Write not supported in FileEntry API",
|
|
107
|
-
);
|
|
91
|
+
const err = fsErr(FsErr.NotSupported, "Write not supported in FileEntry API");
|
|
108
92
|
return Promise.resolve({ err });
|
|
109
93
|
}
|
|
110
94
|
|
|
@@ -119,9 +103,7 @@ export class FsImplEntryAPI
|
|
|
119
103
|
}
|
|
120
104
|
|
|
121
105
|
/** Resolve a directory entry. Path must be normalized */
|
|
122
|
-
private async resolveDir(
|
|
123
|
-
path: string,
|
|
124
|
-
): Promise<FsResult<FileSystemDirectoryEntry>> {
|
|
106
|
+
private async resolveDir(path: string): Promise<FsResult<FileSystemDirectoryEntry>> {
|
|
125
107
|
if (fsIsRoot(path)) {
|
|
126
108
|
return { val: this.rootEntry };
|
|
127
109
|
}
|
|
@@ -132,33 +114,20 @@ export class FsImplEntryAPI
|
|
|
132
114
|
}),
|
|
133
115
|
);
|
|
134
116
|
if ("err" in entry) {
|
|
135
|
-
const err = fsFail(
|
|
136
|
-
"Failed to resolve directory `" +
|
|
137
|
-
path +
|
|
138
|
-
"`: " +
|
|
139
|
-
errstr(entry.err),
|
|
140
|
-
);
|
|
117
|
+
const err = fsFail("Failed to resolve directory `" + path + "`: " + errstr(entry.err));
|
|
141
118
|
return { err };
|
|
142
119
|
}
|
|
143
120
|
if (!entry.val.isDirectory) {
|
|
144
|
-
const err = fsErr(
|
|
145
|
-
FsErr.IsFile,
|
|
146
|
-
"Path `" + path + "` is not a directory",
|
|
147
|
-
);
|
|
121
|
+
const err = fsErr(FsErr.IsFile, "Path `" + path + "` is not a directory");
|
|
148
122
|
return { err };
|
|
149
123
|
}
|
|
150
124
|
return entry as Ok<FileSystemDirectoryEntry>;
|
|
151
125
|
}
|
|
152
126
|
|
|
153
127
|
/** Resolve a file entry. Path must be normalized */
|
|
154
|
-
private async resolveFile(
|
|
155
|
-
path: string,
|
|
156
|
-
): Promise<FsResult<FileSystemFileEntry>> {
|
|
128
|
+
private async resolveFile(path: string): Promise<FsResult<FileSystemFileEntry>> {
|
|
157
129
|
if (fsIsRoot(path)) {
|
|
158
|
-
const err = fsErr(
|
|
159
|
-
FsErr.IsDirectory,
|
|
160
|
-
"Path `" + path + "` is not a file",
|
|
161
|
-
);
|
|
130
|
+
const err = fsErr(FsErr.IsDirectory, "Path `" + path + "` is not a file");
|
|
162
131
|
return { err };
|
|
163
132
|
}
|
|
164
133
|
const entry = await tryAsync(
|
|
@@ -168,16 +137,11 @@ export class FsImplEntryAPI
|
|
|
168
137
|
}),
|
|
169
138
|
);
|
|
170
139
|
if ("err" in entry) {
|
|
171
|
-
const err = fsFail(
|
|
172
|
-
"Failed to resolve file `" + path + "`: " + errstr(entry.err),
|
|
173
|
-
);
|
|
140
|
+
const err = fsFail("Failed to resolve file `" + path + "`: " + errstr(entry.err));
|
|
174
141
|
return { err };
|
|
175
142
|
}
|
|
176
143
|
if (!entry.val.isFile) {
|
|
177
|
-
const err = fsErr(
|
|
178
|
-
FsErr.IsDirectory,
|
|
179
|
-
"Path `" + path + "` is not a file",
|
|
180
|
-
);
|
|
144
|
+
const err = fsErr(FsErr.IsDirectory, "Path `" + path + "` is not a file");
|
|
181
145
|
return { err };
|
|
182
146
|
}
|
|
183
147
|
return entry as Ok<FileSystemFileEntry>;
|
package/src/fs/FsImplFileAPI.ts
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import { ilog } from "../log/internal.ts";
|
|
2
2
|
|
|
3
3
|
import type { FsFile } from "./FsFile.ts";
|
|
4
|
-
import type {
|
|
5
|
-
FsFileSystem,
|
|
6
|
-
FsFileSystemUninit,
|
|
7
|
-
FsCapabilities,
|
|
8
|
-
} from "./FsFileSystem.ts";
|
|
4
|
+
import type { FsFileSystem, FsFileSystemUninit, FsCapabilities } from "./FsFileSystem.ts";
|
|
9
5
|
import { FsErr, type FsResult, type FsVoid, fsErr } from "./FsError.ts";
|
|
10
6
|
import { fsIsRoot, fsNormalize } from "./FsPath.ts";
|
|
11
7
|
import { FsFileMgr } from "./FsFileMgr.ts";
|
|
@@ -17,9 +13,7 @@ import type { FsFileSystemInternal } from "./FsFileSystemInternal.ts";
|
|
|
17
13
|
* It's used for Firefox when the File Entries API is not available
|
|
18
14
|
* i.e. opened from <input type="file">
|
|
19
15
|
*/
|
|
20
|
-
export class FsImplFileAPI
|
|
21
|
-
implements FsFileSystemUninit, FsFileSystem, FsFileSystemInternal
|
|
22
|
-
{
|
|
16
|
+
export class FsImplFileAPI implements FsFileSystemUninit, FsFileSystem, FsFileSystemInternal {
|
|
23
17
|
public root: string;
|
|
24
18
|
public capabilities: FsCapabilities;
|
|
25
19
|
|
|
@@ -106,10 +100,7 @@ export class FsImplFileAPI
|
|
|
106
100
|
}
|
|
107
101
|
|
|
108
102
|
public write(): Promise<FsVoid> {
|
|
109
|
-
const err = fsErr(
|
|
110
|
-
FsErr.NotSupported,
|
|
111
|
-
"Write not supported in File API",
|
|
112
|
-
);
|
|
103
|
+
const err = fsErr(FsErr.NotSupported, "Write not supported in File API");
|
|
113
104
|
return Promise.resolve({ err });
|
|
114
105
|
}
|
|
115
106
|
|
|
@@ -5,20 +5,10 @@
|
|
|
5
5
|
|
|
6
6
|
import { tryAsync, errstr } from "../result/index.ts";
|
|
7
7
|
|
|
8
|
-
import type {
|
|
9
|
-
FsFileSystem,
|
|
10
|
-
FsFileSystemUninit,
|
|
11
|
-
FsCapabilities,
|
|
12
|
-
} from "./FsFileSystem.ts";
|
|
8
|
+
import type { FsFileSystem, FsFileSystemUninit, FsCapabilities } from "./FsFileSystem.ts";
|
|
13
9
|
import { FsErr, type FsResult, type FsVoid, fsErr, fsFail } from "./FsError.ts";
|
|
14
10
|
import type { FsFile } from "./FsFile.ts";
|
|
15
|
-
import {
|
|
16
|
-
fsComponents,
|
|
17
|
-
fsGetBase,
|
|
18
|
-
fsGetName,
|
|
19
|
-
fsIsRoot,
|
|
20
|
-
fsNormalize,
|
|
21
|
-
} from "./FsPath.ts";
|
|
11
|
+
import { fsComponents, fsGetBase, fsGetName, fsIsRoot, fsNormalize } from "./FsPath.ts";
|
|
22
12
|
import { FsFileMgr } from "./FsFileMgr.ts";
|
|
23
13
|
import type { FsFileSystemInternal } from "./FsFileSystemInternal.ts";
|
|
24
14
|
|
|
@@ -28,9 +18,7 @@ type PermissionStatus = "granted" | "denied" | "prompt";
|
|
|
28
18
|
* FsFileSystem implementation that uses FileSystem Access API
|
|
29
19
|
* This is only supported in Chrome/Edge
|
|
30
20
|
*/
|
|
31
|
-
export class FsImplHandleAPI
|
|
32
|
-
implements FsFileSystemUninit, FsFileSystem, FsFileSystemInternal
|
|
33
|
-
{
|
|
21
|
+
export class FsImplHandleAPI implements FsFileSystemUninit, FsFileSystem, FsFileSystemInternal {
|
|
34
22
|
public root: string;
|
|
35
23
|
public capabilities: FsCapabilities;
|
|
36
24
|
/** If app requested write access */
|
|
@@ -40,11 +28,7 @@ export class FsImplHandleAPI
|
|
|
40
28
|
|
|
41
29
|
private mgr: FsFileMgr;
|
|
42
30
|
|
|
43
|
-
constructor(
|
|
44
|
-
rootPath: string,
|
|
45
|
-
rootHandle: FileSystemDirectoryHandle,
|
|
46
|
-
write: boolean,
|
|
47
|
-
) {
|
|
31
|
+
constructor(rootPath: string, rootHandle: FileSystemDirectoryHandle, write: boolean) {
|
|
48
32
|
this.root = rootPath;
|
|
49
33
|
this.rootHandle = rootHandle;
|
|
50
34
|
this.writeMode = write;
|
|
@@ -92,10 +76,7 @@ export class FsImplHandleAPI
|
|
|
92
76
|
});
|
|
93
77
|
if ("err" in entries) {
|
|
94
78
|
const err = fsFail(
|
|
95
|
-
"Error reading entries from directory `" +
|
|
96
|
-
path +
|
|
97
|
-
"`: " +
|
|
98
|
-
errstr(entries.err),
|
|
79
|
+
"Error reading entries from directory `" + path + "`: " + errstr(entries.err),
|
|
99
80
|
);
|
|
100
81
|
return { err };
|
|
101
82
|
}
|
|
@@ -116,20 +97,15 @@ export class FsImplHandleAPI
|
|
|
116
97
|
|
|
117
98
|
const file = await tryAsync(() => handle.val.getFile());
|
|
118
99
|
if ("err" in file) {
|
|
119
|
-
const err = fsFail(
|
|
120
|
-
"Failed to read file `" + path + "`: " + errstr(file.err),
|
|
121
|
-
);
|
|
100
|
+
const err = fsFail("Failed to read file `" + path + "`: " + errstr(file.err));
|
|
122
101
|
return { err };
|
|
123
102
|
}
|
|
124
103
|
return file;
|
|
125
104
|
}
|
|
126
105
|
|
|
127
|
-
public async write(path: string, content: Uint8Array): Promise<FsVoid> {
|
|
106
|
+
public async write(path: string, content: Uint8Array<ArrayBuffer>): Promise<FsVoid> {
|
|
128
107
|
if (!this.writeMode) {
|
|
129
|
-
const err = fsErr(
|
|
130
|
-
FsErr.PermissionDenied,
|
|
131
|
-
"Write mode not requested",
|
|
132
|
-
);
|
|
108
|
+
const err = fsErr(FsErr.PermissionDenied, "Write mode not requested");
|
|
133
109
|
return { err };
|
|
134
110
|
}
|
|
135
111
|
const normalized = fsNormalize(path);
|
|
@@ -150,9 +126,7 @@ export class FsImplHandleAPI
|
|
|
150
126
|
return {};
|
|
151
127
|
});
|
|
152
128
|
if ("err" in result) {
|
|
153
|
-
const err = fsFail(
|
|
154
|
-
"Failed to write file `" + path + "`: " + errstr(result.err),
|
|
155
|
-
);
|
|
129
|
+
const err = fsFail("Failed to write file `" + path + "`: " + errstr(result.err));
|
|
156
130
|
return { err };
|
|
157
131
|
}
|
|
158
132
|
return {};
|
|
@@ -173,9 +147,7 @@ export class FsImplHandleAPI
|
|
|
173
147
|
* Resolve the FileSystemDirectoryHandle for a directory.
|
|
174
148
|
* The path must be normalized
|
|
175
149
|
*/
|
|
176
|
-
private async resolveDir(
|
|
177
|
-
path: string,
|
|
178
|
-
): Promise<FsResult<FileSystemDirectoryHandle>> {
|
|
150
|
+
private async resolveDir(path: string): Promise<FsResult<FileSystemDirectoryHandle>> {
|
|
179
151
|
if (fsIsRoot(path)) {
|
|
180
152
|
return { val: this.rootHandle };
|
|
181
153
|
}
|
|
@@ -187,10 +159,7 @@ export class FsImplHandleAPI
|
|
|
187
159
|
if ("err" in next) {
|
|
188
160
|
const dir = parts.join("/");
|
|
189
161
|
const err = fsFail(
|
|
190
|
-
"Failed to resolve directory `" +
|
|
191
|
-
dir +
|
|
192
|
-
"`: " +
|
|
193
|
-
errstr(next.err),
|
|
162
|
+
"Failed to resolve directory `" + dir + "`: " + errstr(next.err),
|
|
194
163
|
);
|
|
195
164
|
return { err };
|
|
196
165
|
}
|
|
@@ -204,9 +173,7 @@ export class FsImplHandleAPI
|
|
|
204
173
|
* Resolve the FileSystemFileHandle for a file.
|
|
205
174
|
* The path must be normalized
|
|
206
175
|
*/
|
|
207
|
-
private async resolveFile(
|
|
208
|
-
path: string,
|
|
209
|
-
): Promise<FsResult<FileSystemFileHandle>> {
|
|
176
|
+
private async resolveFile(path: string): Promise<FsResult<FileSystemFileHandle>> {
|
|
210
177
|
const parent = fsGetBase(path);
|
|
211
178
|
if (parent.err) {
|
|
212
179
|
return parent;
|
|
@@ -224,9 +191,7 @@ export class FsImplHandleAPI
|
|
|
224
191
|
|
|
225
192
|
const file = await tryAsync(() => handle.val.getFileHandle(name.val));
|
|
226
193
|
if ("err" in file) {
|
|
227
|
-
const err = fsFail(
|
|
228
|
-
"Failed to resolve file `" + path + "`: " + errstr(file.err),
|
|
229
|
-
);
|
|
194
|
+
const err = fsFail("Failed to resolve file `" + path + "`: " + errstr(file.err));
|
|
230
195
|
return { err };
|
|
231
196
|
}
|
|
232
197
|
return file;
|
package/src/fs/FsOpen.ts
CHANGED
|
@@ -1,23 +1,14 @@
|
|
|
1
1
|
import { tryCatch, tryAsync, errstr } from "../result/index.ts";
|
|
2
2
|
|
|
3
3
|
import type { FsFileSystem, FsFileSystemUninit } from "./FsFileSystem.ts";
|
|
4
|
-
import {
|
|
5
|
-
FsErr,
|
|
6
|
-
type FsError,
|
|
7
|
-
type FsResult,
|
|
8
|
-
fsErr,
|
|
9
|
-
fsFail,
|
|
10
|
-
} from "./FsError.ts";
|
|
4
|
+
import { FsErr, type FsError, type FsResult, fsErr, fsFail } from "./FsError.ts";
|
|
11
5
|
import { fsGetSupportStatus } from "./FsSupportStatus.ts";
|
|
12
6
|
import { FsImplFileAPI } from "./FsImplFileAPI.ts";
|
|
13
7
|
import { FsImplEntryAPI } from "./FsImplEntryAPI.ts";
|
|
14
8
|
import { FsImplHandleAPI } from "./FsImplHandleAPI.ts";
|
|
15
9
|
|
|
16
10
|
/** Handle for handling top level open errors, and decide if the operation should be retried */
|
|
17
|
-
export type FsOpenRetryHandler = (
|
|
18
|
-
error: FsError,
|
|
19
|
-
attempt: number,
|
|
20
|
-
) => Promise<FsResult<boolean>>;
|
|
11
|
+
export type FsOpenRetryHandler = (error: FsError, attempt: number) => Promise<FsResult<boolean>>;
|
|
21
12
|
|
|
22
13
|
const MAX_RETRY = 10;
|
|
23
14
|
|
|
@@ -105,29 +96,22 @@ async function createWithPicker(
|
|
|
105
96
|
inputElement.type = "file";
|
|
106
97
|
inputElement.webkitdirectory = true;
|
|
107
98
|
|
|
108
|
-
const fsUninit = await new Promise<FsResult<FsFileSystemUninit>>(
|
|
109
|
-
(
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
inputElement.addEventListener("cancel", () => {
|
|
121
|
-
resolve({
|
|
122
|
-
err: fsErr(
|
|
123
|
-
FsErr.UserAbort,
|
|
124
|
-
"User cancelled the operation",
|
|
125
|
-
),
|
|
126
|
-
});
|
|
99
|
+
const fsUninit = await new Promise<FsResult<FsFileSystemUninit>>((resolve) => {
|
|
100
|
+
inputElement.addEventListener("change", (event) => {
|
|
101
|
+
const files = (event.target as HTMLInputElement).files;
|
|
102
|
+
if (!files) {
|
|
103
|
+
const err = fsFail("Failed to get files from input element");
|
|
104
|
+
return resolve({ err });
|
|
105
|
+
}
|
|
106
|
+
resolve(createFromFileList(files));
|
|
107
|
+
});
|
|
108
|
+
inputElement.addEventListener("cancel", () => {
|
|
109
|
+
resolve({
|
|
110
|
+
err: fsErr(FsErr.UserAbort, "User cancelled the operation"),
|
|
127
111
|
});
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
);
|
|
112
|
+
});
|
|
113
|
+
inputElement.click();
|
|
114
|
+
});
|
|
131
115
|
inputElement.remove();
|
|
132
116
|
|
|
133
117
|
if (fsUninit.val) {
|
|
@@ -159,36 +143,21 @@ async function createFromDataTransferItem(
|
|
|
159
143
|
let error: FsError | undefined = undefined;
|
|
160
144
|
const { implementation } = fsGetSupportStatus();
|
|
161
145
|
// Prefer File System Access API since it supports writing
|
|
162
|
-
if (
|
|
163
|
-
"getAsFileSystemHandle" in item &&
|
|
164
|
-
implementation === "FileSystemAccess"
|
|
165
|
-
) {
|
|
146
|
+
if ("getAsFileSystemHandle" in item && implementation === "FileSystemAccess") {
|
|
166
147
|
const handle = await tryAsync(() => getAsFileSystemHandle(item));
|
|
167
148
|
if (handle.val) {
|
|
168
149
|
return createFromFileSystemHandle(handle.val, write);
|
|
169
150
|
}
|
|
170
|
-
error = fsFail(
|
|
171
|
-
|
|
172
|
-
errstr(handle.err),
|
|
173
|
-
);
|
|
174
|
-
} else if (
|
|
175
|
-
"webkitGetAsEntry" in item &&
|
|
176
|
-
implementation === "FileEntry"
|
|
177
|
-
) {
|
|
151
|
+
error = fsFail("Failed to get handle from DataTransferItem: " + errstr(handle.err));
|
|
152
|
+
} else if ("webkitGetAsEntry" in item && implementation === "FileEntry") {
|
|
178
153
|
const entry = tryCatch(() => webkitGetAsEntry(item));
|
|
179
154
|
if (entry.val) {
|
|
180
155
|
return createFromFileSystemEntry(entry.val);
|
|
181
156
|
}
|
|
182
|
-
error = fsFail(
|
|
183
|
-
"Failed to get entry from DataTransferItem: " +
|
|
184
|
-
errstr(entry.err),
|
|
185
|
-
);
|
|
157
|
+
error = fsFail("Failed to get entry from DataTransferItem: " + errstr(entry.err));
|
|
186
158
|
}
|
|
187
159
|
if (!error) {
|
|
188
|
-
const err = fsErr(
|
|
189
|
-
FsErr.NotSupported,
|
|
190
|
-
"No supported API found on the DataTransferItem",
|
|
191
|
-
);
|
|
160
|
+
const err = fsErr(FsErr.NotSupported, "No supported API found on the DataTransferItem");
|
|
192
161
|
return { err };
|
|
193
162
|
}
|
|
194
163
|
// handle error
|
|
@@ -248,15 +217,11 @@ function showDirectoryPicker(write: boolean): Promise<FileSystemHandle> {
|
|
|
248
217
|
* @param e The error to check
|
|
249
218
|
*/
|
|
250
219
|
function isAbortError(e: unknown): boolean {
|
|
251
|
-
return
|
|
252
|
-
!!e && typeof e === "object" && "name" in e && e.name === "AbortError"
|
|
253
|
-
);
|
|
220
|
+
return !!e && typeof e === "object" && "name" in e && e.name === "AbortError";
|
|
254
221
|
}
|
|
255
222
|
|
|
256
223
|
/** Wrapper for DataTransferItem.getAsFileSystemHandle */
|
|
257
|
-
async function getAsFileSystemHandle(
|
|
258
|
-
item: DataTransferItem,
|
|
259
|
-
): Promise<FileSystemHandle> {
|
|
224
|
+
async function getAsFileSystemHandle(item: DataTransferItem): Promise<FileSystemHandle> {
|
|
260
225
|
// @ts-expect-error getAsFileSystemHandle is not in the TS lib
|
|
261
226
|
const handle = await item.getAsFileSystemHandle();
|
|
262
227
|
if (!handle) {
|
|
@@ -283,26 +248,17 @@ function createFromFileSystemHandle(
|
|
|
283
248
|
return { err };
|
|
284
249
|
}
|
|
285
250
|
|
|
286
|
-
const fs = new FsImplHandleAPI(
|
|
287
|
-
handle.name,
|
|
288
|
-
handle as FileSystemDirectoryHandle,
|
|
289
|
-
write,
|
|
290
|
-
);
|
|
251
|
+
const fs = new FsImplHandleAPI(handle.name, handle as FileSystemDirectoryHandle, write);
|
|
291
252
|
|
|
292
253
|
return { val: fs };
|
|
293
254
|
}
|
|
294
255
|
|
|
295
|
-
function createFromFileSystemEntry(
|
|
296
|
-
entry: FileSystemEntry,
|
|
297
|
-
): FsResult<FsFileSystemUninit> {
|
|
256
|
+
function createFromFileSystemEntry(entry: FileSystemEntry): FsResult<FsFileSystemUninit> {
|
|
298
257
|
if (entry.isFile || !entry.isDirectory) {
|
|
299
258
|
const err = fsErr(FsErr.IsFile, "Expected directory");
|
|
300
259
|
return { err };
|
|
301
260
|
}
|
|
302
|
-
const fs = new FsImplEntryAPI(
|
|
303
|
-
entry.name,
|
|
304
|
-
entry as FileSystemDirectoryEntry,
|
|
305
|
-
);
|
|
261
|
+
const fs = new FsImplEntryAPI(entry.name, entry as FileSystemDirectoryEntry);
|
|
306
262
|
return { val: fs };
|
|
307
263
|
}
|
|
308
264
|
|
package/src/fs/FsOpenFile.ts
CHANGED
|
@@ -52,10 +52,7 @@ const fsOpenFileInternal = async (
|
|
|
52
52
|
options: FsFileOpenOptions,
|
|
53
53
|
): Promise<FsResult<FsFileStandalone[]>> => {
|
|
54
54
|
if (isFileSystemAccessAPISupportedForStandaloneFileOpen()) {
|
|
55
|
-
const result = await fsOpenFileWithFileSystemAccessAPI(
|
|
56
|
-
multiple,
|
|
57
|
-
options,
|
|
58
|
-
);
|
|
55
|
+
const result = await fsOpenFileWithFileSystemAccessAPI(multiple, options);
|
|
59
56
|
if (result.val || result.err.code === FsErr.UserAbort) {
|
|
60
57
|
return result;
|
|
61
58
|
}
|
|
@@ -116,9 +113,7 @@ const fsOpenFileWithFileAPI = async (
|
|
|
116
113
|
}
|
|
117
114
|
const array = [];
|
|
118
115
|
for (let i = 0; i < element.files.length; i++) {
|
|
119
|
-
array.push(
|
|
120
|
-
new FsFileStandaloneImplFileAPI(element.files[i]),
|
|
121
|
-
);
|
|
116
|
+
array.push(new FsFileStandaloneImplFileAPI(element.files[i]));
|
|
122
117
|
}
|
|
123
118
|
resolve({ val: array });
|
|
124
119
|
});
|
package/src/fs/FsSave.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { ilog } from "../log/internal.ts";
|
|
|
3
3
|
import { fsFail, type FsVoid } from "./FsError.ts";
|
|
4
4
|
|
|
5
5
|
/** Save (download) a file using Blob */
|
|
6
|
-
export function fsSave(content: string | Uint8Array
|
|
6
|
+
export function fsSave(content: string | Uint8Array<ArrayBuffer>, filename: string): FsVoid {
|
|
7
7
|
const blob = new Blob([content], {
|
|
8
8
|
// maybe lying, but should be fine
|
|
9
9
|
type: "text/plain;charset=utf-8",
|
|
@@ -32,11 +32,7 @@ The above copyright notice and this permission notice shall be included in all c
|
|
|
32
32
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
33
33
|
*/
|
|
34
34
|
|
|
35
|
-
type SaveAsFn = (
|
|
36
|
-
data: Blob | string,
|
|
37
|
-
filename?: string,
|
|
38
|
-
options?: SaveAsFnOptions,
|
|
39
|
-
) => void;
|
|
35
|
+
type SaveAsFn = (data: Blob | string, filename?: string, options?: SaveAsFnOptions) => void;
|
|
40
36
|
type SaveAsFnOptions = { autoBom: boolean };
|
|
41
37
|
|
|
42
38
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
@@ -184,8 +180,7 @@ const saveAs: SaveAsFn = (blob, name?, opts?) => {
|
|
|
184
180
|
const force = blob.type === "application/octet-stream";
|
|
185
181
|
// adoption note: add any
|
|
186
182
|
const isSafari =
|
|
187
|
-
/constructor/i.test((globalThis as any).HTMLElement) ||
|
|
188
|
-
(globalThis as any).safari;
|
|
183
|
+
/constructor/i.test((globalThis as any).HTMLElement) || (globalThis as any).safari;
|
|
189
184
|
const isChromeIOS = /CriOS\/[\d]+/.test(navigator.userAgent);
|
|
190
185
|
|
|
191
186
|
if (
|
|
@@ -196,9 +191,7 @@ const saveAs: SaveAsFn = (blob, name?, opts?) => {
|
|
|
196
191
|
const reader = new FileReader();
|
|
197
192
|
reader.onloadend = function () {
|
|
198
193
|
let url = reader.result as string;
|
|
199
|
-
url = isChromeIOS
|
|
200
|
-
? url
|
|
201
|
-
: url.replace(/^data:[^;]*;/, "data:attachment/file;");
|
|
194
|
+
url = isChromeIOS ? url : url.replace(/^data:[^;]*;/, "data:attachment/file;");
|
|
202
195
|
if (popup) {
|
|
203
196
|
popup.location.href = url;
|
|
204
197
|
} else {
|
|
@@ -71,11 +71,7 @@ function isFileEntrySupported(): boolean {
|
|
|
71
71
|
// Chrome/Edge has this but it's named DirectoryEntry
|
|
72
72
|
// AND, they don't work (I forgot how exactly they don't work)
|
|
73
73
|
|
|
74
|
-
if (
|
|
75
|
-
navigator &&
|
|
76
|
-
navigator.userAgent &&
|
|
77
|
-
navigator.userAgent.includes("Chrome")
|
|
78
|
-
) {
|
|
74
|
+
if (navigator && navigator.userAgent && navigator.userAgent.includes("Chrome")) {
|
|
79
75
|
return false;
|
|
80
76
|
}
|
|
81
77
|
|