@themartiancompany/opfs 1.8.11 → 2.0.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/README.md +17 -127
- package/{src/fs/support.ts → opfs} +32 -10
- package/package.json +21 -78
- package/README.cn.md +0 -301
- package/dist/main.cjs +0 -1840
- package/dist/main.cjs.map +0 -1
- package/dist/main.mjs +0 -1751
- package/dist/main.mjs.map +0 -1
- package/dist/types.d.ts +0 -920
- package/dist/types.d.ts.map +0 -1
- package/docs/README.md +0 -115
- package/docs/classes/SyncMessenger.md +0 -42
- package/docs/functions/appendFile.md +0 -29
- package/docs/functions/appendFileSync.md +0 -26
- package/docs/functions/assertAbsolutePath.md +0 -29
- package/docs/functions/assertFileUrl.md +0 -29
- package/docs/functions/connectSyncAgent.md +0 -25
- package/docs/functions/copy.md +0 -35
- package/docs/functions/copySync.md +0 -30
- package/docs/functions/createFile.md +0 -27
- package/docs/functions/createFileSync.md +0 -25
- package/docs/functions/deleteTemp.md +0 -23
- package/docs/functions/deleteTempSync.md +0 -19
- package/docs/functions/downloadFile.md +0 -58
- package/docs/functions/emptyDir.md +0 -28
- package/docs/functions/emptyDirSync.md +0 -25
- package/docs/functions/exists.md +0 -29
- package/docs/functions/existsSync.md +0 -26
- package/docs/functions/generateTempPath.md +0 -27
- package/docs/functions/getFileDataByHandle.md +0 -27
- package/docs/functions/getSyncMessenger.md +0 -22
- package/docs/functions/isDirectoryHandle.md +0 -27
- package/docs/functions/isFileHandle.md +0 -27
- package/docs/functions/isFileHandleLike.md +0 -27
- package/docs/functions/isOPFSSupported.md +0 -21
- package/docs/functions/isTempPath.md +0 -27
- package/docs/functions/mkTemp.md +0 -28
- package/docs/functions/mkTempSync.md +0 -25
- package/docs/functions/mkdir.md +0 -27
- package/docs/functions/mkdirSync.md +0 -25
- package/docs/functions/move.md +0 -34
- package/docs/functions/moveSync.md +0 -30
- package/docs/functions/pruneTemp.md +0 -28
- package/docs/functions/pruneTempSync.md +0 -25
- package/docs/functions/readBlobFile.md +0 -28
- package/docs/functions/readBlobFileSync.md +0 -25
- package/docs/functions/readDir.md +0 -28
- package/docs/functions/readDirSync.md +0 -26
- package/docs/functions/readFile.md +0 -132
- package/docs/functions/readFileSync.md +0 -70
- package/docs/functions/readJsonFile.md +0 -35
- package/docs/functions/readJsonFileSync.md +0 -31
- package/docs/functions/readTextFile.md +0 -28
- package/docs/functions/readTextFileSync.md +0 -25
- package/docs/functions/remove.md +0 -27
- package/docs/functions/removeSync.md +0 -25
- package/docs/functions/setSyncMessenger.md +0 -26
- package/docs/functions/startSyncAgent.md +0 -21
- package/docs/functions/stat.md +0 -27
- package/docs/functions/statSync.md +0 -25
- package/docs/functions/toFileSystemHandleLike.md +0 -27
- package/docs/functions/unzip.md +0 -32
- package/docs/functions/unzipFromUrl.md +0 -36
- package/docs/functions/unzipSync.md +0 -26
- package/docs/functions/uploadFile.md +0 -33
- package/docs/functions/writeFile.md +0 -32
- package/docs/functions/writeFileSync.md +0 -30
- package/docs/functions/zip.md +0 -65
- package/docs/functions/zipFromUrl.md +0 -63
- package/docs/functions/zipSync.md +0 -55
- package/docs/interfaces/CopyOptions.md +0 -17
- package/docs/interfaces/DownloadFileTempResponse.md +0 -18
- package/docs/interfaces/ErrorLike.md +0 -18
- package/docs/interfaces/ExistsOptions.md +0 -18
- package/docs/interfaces/FileLike.md +0 -21
- package/docs/interfaces/FileSystemFileHandleLike.md +0 -25
- package/docs/interfaces/FileSystemHandleLike.md +0 -22
- package/docs/interfaces/MoveOptions.md +0 -17
- package/docs/interfaces/ReadDirEntry.md +0 -18
- package/docs/interfaces/ReadDirEntrySync.md +0 -18
- package/docs/interfaces/ReadDirOptions.md +0 -17
- package/docs/interfaces/ReadOptions.md +0 -17
- package/docs/interfaces/SyncAgentOptions.md +0 -19
- package/docs/interfaces/TempOptions.md +0 -19
- package/docs/interfaces/UploadRequestInit.md +0 -21
- package/docs/interfaces/WriteOptions.md +0 -19
- package/docs/interfaces/ZipOptions.md +0 -17
- package/docs/type-aliases/FileEncoding.md +0 -15
- package/docs/type-aliases/FsRequestInit.md +0 -15
- package/docs/type-aliases/ReadFileContent.md +0 -15
- package/docs/type-aliases/WriteFileContent.md +0 -15
- package/docs/type-aliases/WriteSyncFileContent.md +0 -16
- package/docs/variables/CURRENT_DIR.md +0 -15
- package/docs/variables/NOT_FOUND_ERROR.md +0 -18
- package/docs/variables/ROOT_DIR.md +0 -15
- package/docs/variables/TMP_DIR.md +0 -15
- package/src/fs/assertions.ts +0 -63
- package/src/fs/constants.ts +0 -63
- package/src/fs/defines.ts +0 -352
- package/src/fs/helpers.ts +0 -338
- package/src/fs/opfs_core.ts +0 -413
- package/src/fs/opfs_download.ts +0 -174
- package/src/fs/opfs_ext.ts +0 -504
- package/src/fs/opfs_tmp.ts +0 -131
- package/src/fs/opfs_unzip.ts +0 -168
- package/src/fs/opfs_upload.ts +0 -126
- package/src/fs/opfs_zip.ts +0 -314
- package/src/fs/utils.ts +0 -176
- package/src/mod.ts +0 -41
- package/src/worker/helpers.ts +0 -168
- package/src/worker/opfs_worker.ts +0 -298
- package/src/worker/opfs_worker_adapter.ts +0 -666
- package/src/worker/shared.ts +0 -400
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
[comment]: <> (License along with this program.)
|
|
20
20
|
[comment]: <> (If not, see <https://www.gnu.org/licenses/>.)
|
|
21
21
|
|
|
22
|
-
#
|
|
22
|
+
# OPFS Module (`@themartiancompany/opfs`)
|
|
23
23
|
|
|
24
24
|
[](
|
|
@@ -29,63 +29,17 @@
|
|
|
29
29
|
https://img.shields.io/badge/lang-cn-red.svg)](
|
|
30
30
|
README.cn.md)
|
|
31
31
|
|
|
32
|
-
Browser-compatible `fs` module
|
|
33
|
-
[
|
|
34
|
-
https://
|
|
35
|
-
|
|
36
|
-
[
|
|
37
|
-
https://
|
|
38
|
-
[Deno `@std/fs`](
|
|
39
|
-
https://jsr.io/@std/fs)
|
|
40
|
-
APIs.
|
|
32
|
+
Browser-compatible `fs` module obtained combining the
|
|
33
|
+
[Happy OPFS](
|
|
34
|
+
https://github.com/themartiancompany/happy-opfs)
|
|
35
|
+
and the
|
|
36
|
+
[OPFS Tools](
|
|
37
|
+
https://github.com/hughfenghen/opfs-tools).
|
|
41
38
|
|
|
42
39
|
OPFS stands for *origin private file system*
|
|
43
40
|
and it is a file system API for manipulating local
|
|
44
41
|
files in a browser environment.
|
|
45
42
|
|
|
46
|
-
There are significant differences between the
|
|
47
|
-
standard OPFS API and familiar file system APIs
|
|
48
|
-
based on path operations, such as those of
|
|
49
|
-
Node.js and Deno.
|
|
50
|
-
The purpose of this module is to implement an API
|
|
51
|
-
similar to those in the browser, allowing for
|
|
52
|
-
convenient file operations.
|
|
53
|
-
|
|
54
|
-
The return values of asynchronous APIs are of the
|
|
55
|
-
[Result](
|
|
56
|
-
https://github.com/JiangJie/happy-rusty)
|
|
57
|
-
type, similar to Rust's `Result` enum type,
|
|
58
|
-
providing a more user-friendly error handling approach.
|
|
59
|
-
|
|
60
|
-
As to why the library targets Deno, that's because:
|
|
61
|
-
|
|
62
|
-
- early versions of the Node.js fs API were based
|
|
63
|
-
on callback syntax, although newer versions support
|
|
64
|
-
Promise syntax;
|
|
65
|
-
on the other hand, the Deno fs API was designed from
|
|
66
|
-
the beginning with Promise syntax. Therefore, Deno has
|
|
67
|
-
less historical baggage, making it a more suitable choice
|
|
68
|
-
for implementing a native-compatible API;
|
|
69
|
-
- Deno natively supports TypeScript, while Node.js
|
|
70
|
-
currently does not without the use of additional tools.
|
|
71
|
-
|
|
72
|
-
Originally based on the
|
|
73
|
-
[Happy OPFS](
|
|
74
|
-
https://github.com/JiangJie/happy-opfs)
|
|
75
|
-
module by Jian Jie and renamed
|
|
76
|
-
because I've noticed there was no
|
|
77
|
-
`fs.createReadStream` implementation,
|
|
78
|
-
while the other available
|
|
79
|
-
[OPFS Tools](
|
|
80
|
-
https://github.com/hughfenghen/opfs-tools)
|
|
81
|
-
module had but didn't call it verbatim
|
|
82
|
-
so I've thought it may have
|
|
83
|
-
been appropriate to make everybody including
|
|
84
|
-
the module potentially less happy and check
|
|
85
|
-
whether if a third module including the function
|
|
86
|
-
and simply called `opfs` can show how much the
|
|
87
|
-
`namespace/package` model works on the field.
|
|
88
|
-
|
|
89
43
|
## Installation
|
|
90
44
|
|
|
91
45
|
To install a locally built version of the
|
|
@@ -136,44 +90,16 @@ npm \
|
|
|
136
90
|
"@themartiancompany/opfs"
|
|
137
91
|
```
|
|
138
92
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
> [!NOTE]
|
|
142
|
-
The asynchronous interface is to be preferred
|
|
143
|
-
because the main thread does not provide a synchronous interface,
|
|
144
|
-
so in order to force the implementation of synchronous syntax,
|
|
145
|
-
the I/O operation needs to be moved a
|
|
146
|
-
[`Worker`](
|
|
147
|
-
https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API),
|
|
148
|
-
and the main thread needs to be blocked until the
|
|
149
|
-
`Worker` completes its I/O operation, which obviously
|
|
150
|
-
causes performance loss.
|
|
151
|
-
|
|
152
|
-
Also since the `Worker` needs to be started first
|
|
153
|
-
the synchronous interface can only be used after start completion,
|
|
154
|
-
such that any reading and writing before that will fail.
|
|
155
|
-
|
|
156
|
-
**Please note** that in order to share data
|
|
157
|
-
between the main thread and the `Worker`,
|
|
158
|
-
`SharedArrayBuffer` needs to be used and so
|
|
159
|
-
two additional `HTTP Response Headers` are required for this:
|
|
160
|
-
`'Cross-Origin-Opener-Policy': 'same-origin'`
|
|
161
|
-
and
|
|
162
|
-
`'Cross-Origin-Embedder-Policy': 'require-corp'`,
|
|
163
|
-
otherwise a `ReferenceError: SharedArrayBuffer is not defined`
|
|
164
|
-
error will be thrown.
|
|
165
|
-
|
|
166
|
-
The headers are automatically set up respectively by
|
|
167
|
-
Parcel and Serve in the Typescript and
|
|
168
|
-
Javascript examples below.
|
|
169
|
-
|
|
170
|
-
## Examples
|
|
93
|
+
### Documentation
|
|
171
94
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
[OPFS
|
|
175
|
-
https://
|
|
176
|
-
|
|
95
|
+
For API documentation
|
|
96
|
+
refer to the
|
|
97
|
+
[Happy OPFS](
|
|
98
|
+
https://github.com/themartiancompany/happy-opfs/blob/main/docs/README.md),
|
|
99
|
+
[OPFS Tools](
|
|
100
|
+
https://github.com/hughfenghen/opfs-tools/blob/main/docs/api.md) and
|
|
101
|
+
[File System API](
|
|
102
|
+
https://nodejs.org/api/fs.html).
|
|
177
103
|
|
|
178
104
|
### Javascript
|
|
179
105
|
|
|
@@ -198,44 +124,8 @@ libraries.
|
|
|
198
124
|
Refer to the Ahsi repository for more information
|
|
199
125
|
about installing and running it.
|
|
200
126
|
|
|
201
|
-
### Typescript
|
|
202
|
-
|
|
203
|
-
Typescript asynchronous
|
|
204
|
-
([tests/async.ts](
|
|
205
|
-
tests/async.ts))
|
|
206
|
-
and synchronous examples
|
|
207
|
-
([tests/sync.ts](
|
|
208
|
-
tests/sync.ts))
|
|
209
|
-
are made available in the `tests` directory.
|
|
210
|
-
|
|
211
|
-
To build them and make them available in an
|
|
212
|
-
HTTP server run
|
|
213
|
-
|
|
214
|
-
```bash
|
|
215
|
-
npm \
|
|
216
|
-
run \
|
|
217
|
-
start
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
then open
|
|
221
|
-
[https://localhost:8443/](
|
|
222
|
-
https://localhost:8443/)
|
|
223
|
-
in your browser and open the developer
|
|
224
|
-
tools to observe the console output.
|
|
225
|
-
|
|
226
|
-
## Documentation
|
|
227
|
-
|
|
228
|
-
Module API is in the
|
|
229
|
-
[docs](
|
|
230
|
-
docs/README.md)
|
|
231
|
-
directory.
|
|
232
|
-
|
|
233
127
|
## License
|
|
234
128
|
|
|
235
|
-
This software repository
|
|
236
|
-
and Pellegrino Prevete is released
|
|
129
|
+
This software repository is released
|
|
237
130
|
under the terms of the GNU Affero
|
|
238
131
|
General Public License version 3.
|
|
239
|
-
Portions of the works authored by
|
|
240
|
-
Jian are released under GNU General
|
|
241
|
-
Public License version 3.
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
4
|
|
|
3
5
|
/** ----------------------------------------------------------------------
|
|
4
6
|
* Copyright ©
|
|
@@ -24,13 +26,33 @@
|
|
|
24
26
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
25
27
|
*/
|
|
26
28
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
29
|
+
const
|
|
30
|
+
_opfs =
|
|
31
|
+
require(
|
|
32
|
+
"happy-opfs");
|
|
33
|
+
const
|
|
34
|
+
_opfs_tools =
|
|
35
|
+
require(
|
|
36
|
+
"opfs-tools");
|
|
37
|
+
_file =
|
|
38
|
+
_opfs_tools.file;
|
|
39
|
+
|
|
40
|
+
function
|
|
41
|
+
_read_stream_create(
|
|
42
|
+
_input_file,
|
|
43
|
+
_options) {
|
|
44
|
+
const
|
|
45
|
+
_file_obj =
|
|
46
|
+
_file(
|
|
47
|
+
_input_file);
|
|
48
|
+
const
|
|
49
|
+
_writer =
|
|
50
|
+
_file_obj.createWriter();
|
|
51
|
+
return _writer;
|
|
36
52
|
}
|
|
53
|
+
|
|
54
|
+
_opfs.createReadStream =
|
|
55
|
+
_read_stream_create;
|
|
56
|
+
|
|
57
|
+
module.exports =
|
|
58
|
+
_opfs;
|
package/package.json
CHANGED
|
@@ -4,31 +4,23 @@
|
|
|
4
4
|
"description":
|
|
5
5
|
"Browser-compatible (OPFS) Node/Deno 'fs' module.",
|
|
6
6
|
"version":
|
|
7
|
-
"
|
|
7
|
+
"2.0.0",
|
|
8
8
|
"homepage":
|
|
9
9
|
"https://github.com/themartiancompany/opfs",
|
|
10
10
|
"license":
|
|
11
|
-
"
|
|
11
|
+
"AGPL-3.0",
|
|
12
12
|
"bugs": {
|
|
13
13
|
"url":
|
|
14
14
|
"https://github.com/themartiancompany/opfs/issues"
|
|
15
15
|
},
|
|
16
|
-
"author":
|
|
17
|
-
"name":
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
"author":
|
|
17
|
+
{ "name":
|
|
18
|
+
"Pellegrino Prevete",
|
|
19
|
+
"email":
|
|
20
|
+
"pellegrinoprevete@gmail.com",
|
|
21
|
+
"url":
|
|
22
|
+
"https://github.com/tallero"
|
|
23
23
|
},
|
|
24
|
-
"contributors": [ {
|
|
25
|
-
"name":
|
|
26
|
-
"Pellegrino Prevete",
|
|
27
|
-
"email":
|
|
28
|
-
"pellegrinoprevete@gmail.com",
|
|
29
|
-
"url":
|
|
30
|
-
"https://github.com/tallero"
|
|
31
|
-
} ],
|
|
32
24
|
"keywords": [
|
|
33
25
|
"fs",
|
|
34
26
|
"system",
|
|
@@ -50,41 +42,28 @@
|
|
|
50
42
|
"files": [
|
|
51
43
|
"COPYING",
|
|
52
44
|
"README.md",
|
|
53
|
-
"
|
|
54
|
-
"package.json"
|
|
55
|
-
"docs",
|
|
56
|
-
"src",
|
|
57
|
-
"dist"
|
|
45
|
+
"opfs",
|
|
46
|
+
"package.json"
|
|
58
47
|
],
|
|
59
48
|
"source":
|
|
60
49
|
"src/mod.ts",
|
|
61
50
|
"man":
|
|
62
51
|
"man/opfs.1",
|
|
63
52
|
"main":
|
|
64
|
-
"
|
|
53
|
+
"opfs",
|
|
65
54
|
"module":
|
|
66
|
-
"
|
|
67
|
-
"types":
|
|
68
|
-
"dist/types.d.ts",
|
|
55
|
+
"opfs",
|
|
69
56
|
"sideEffects":
|
|
70
57
|
false,
|
|
71
58
|
"scripts": {
|
|
72
|
-
"clean":
|
|
73
|
-
"npx dlx rimraf .parcel-cache dist",
|
|
74
|
-
"check":
|
|
75
|
-
"npx tsc --noEmit",
|
|
76
59
|
"lint":
|
|
77
60
|
"npx eslint .",
|
|
78
61
|
"prebuild":
|
|
79
|
-
"
|
|
62
|
+
"npm run lint",
|
|
80
63
|
"build":
|
|
81
|
-
"npm install --save-dev
|
|
64
|
+
"npm install --save-dev",
|
|
82
65
|
"docs":
|
|
83
|
-
"typedoc"
|
|
84
|
-
"prepublishOnly":
|
|
85
|
-
"npx rollup --config rollup.config.mjs",
|
|
86
|
-
"start":
|
|
87
|
-
"npx parcel serve tests/index.html --dist-dir dist/dev --port 8443 --https --no-cache"
|
|
66
|
+
"typedoc"
|
|
88
67
|
},
|
|
89
68
|
"repository": {
|
|
90
69
|
"type": "git",
|
|
@@ -93,49 +72,13 @@
|
|
|
93
72
|
"devDependencies": {
|
|
94
73
|
"@eslint/js":
|
|
95
74
|
"^9.32.0",
|
|
96
|
-
"@parcel/packager-ts":
|
|
97
|
-
"^2.16.0",
|
|
98
|
-
"@parcel/transformer-typescript-types":
|
|
99
|
-
"^2.16.0",
|
|
100
|
-
"dlx":
|
|
101
|
-
"^0.2.1",
|
|
102
75
|
"eslint":
|
|
103
|
-
"^9.32.0"
|
|
104
|
-
"parcel":
|
|
105
|
-
"^2.16.0",
|
|
106
|
-
"rimraf":
|
|
107
|
-
"^6.1.0",
|
|
108
|
-
"rollup":
|
|
109
|
-
"^4.52.5",
|
|
110
|
-
"rollup-plugin-dts":
|
|
111
|
-
"^6.2.1",
|
|
112
|
-
"rollup-plugin-esbuild":
|
|
113
|
-
"^6.2.1",
|
|
114
|
-
"typedoc":
|
|
115
|
-
"^0.27.9",
|
|
116
|
-
"typedoc-plugin-markdown":
|
|
117
|
-
"4.4.2",
|
|
118
|
-
"typescript":
|
|
119
|
-
"^5.8.3",
|
|
120
|
-
"typescript-eslint":
|
|
121
|
-
"^8.38.0"
|
|
76
|
+
"^9.32.0"
|
|
122
77
|
},
|
|
123
78
|
"dependencies": {
|
|
124
|
-
"
|
|
125
|
-
"
|
|
126
|
-
"
|
|
127
|
-
"
|
|
128
|
-
"fflate":
|
|
129
|
-
"^0.8.2",
|
|
130
|
-
"happy-rusty":
|
|
131
|
-
"^1.5.0",
|
|
132
|
-
"tiny-future":
|
|
133
|
-
"^1.1.0",
|
|
134
|
-
"tiny-invariant":
|
|
135
|
-
"^1.3.3"
|
|
136
|
-
},
|
|
137
|
-
"@parcel/resolver-default": {
|
|
138
|
-
"packageExports":
|
|
139
|
-
true
|
|
79
|
+
"happy-opfs":
|
|
80
|
+
"npm:@themartiancompany/happy-opfs@^1.8.12",
|
|
81
|
+
"opfs-tools":
|
|
82
|
+
"0.7.4"
|
|
140
83
|
}
|
|
141
84
|
}
|
package/README.cn.md
DELETED
|
@@ -1,301 +0,0 @@
|
|
|
1
|
-
[comment]: <> (SPDX-License-Identifier: AGPL-3.0)
|
|
2
|
-
|
|
3
|
-
[comment]: <> (-------------------------------------------------------------)
|
|
4
|
-
[comment]: <> (Copyright © 2024, 2025 Pellegrino Prevete)
|
|
5
|
-
[comment]: <> (All rights reserved)
|
|
6
|
-
[comment]: <> (-------------------------------------------------------------)
|
|
7
|
-
|
|
8
|
-
[comment]: <> (This program is free software: you can redistribute)
|
|
9
|
-
[comment]: <> (it and/or modify it under the terms of the GNU Affero)
|
|
10
|
-
[comment]: <> (General Public License as published by the Free)
|
|
11
|
-
[comment]: <> (Software Foundation, either version 3 of the License.)
|
|
12
|
-
|
|
13
|
-
[comment]: <> (This program is distributed in the hope that it will be useful,)
|
|
14
|
-
[comment]: <> (but WITHOUT ANY WARRANTY; without even the implied warranty of)
|
|
15
|
-
[comment]: <> (MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the)
|
|
16
|
-
[comment]: <> (GNU Affero General Public License for more details.)
|
|
17
|
-
|
|
18
|
-
[comment]: <> (You should have received a copy of the GNU Affero General Public)
|
|
19
|
-
[comment]: <> (License along with this program.)
|
|
20
|
-
[comment]: <> (If not, see <https://www.gnu.org/licenses/>.)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
# 快乐地使用 OPFS
|
|
24
|
-
|
|
25
|
-
[](
|
|
27
|
-
https://npmjs.org/package/@themartiancompany/opfs)
|
|
28
|
-
|
|
29
|
-
[](
|
|
31
|
-
README.md)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
这是一套参考 [Deno Runtime File System](
|
|
35
|
-
https://deno.land/api#File_System)
|
|
36
|
-
和 [Deno @std/fs](
|
|
37
|
-
https://jsr.io/@std/fs)
|
|
38
|
-
API,基于 OPFS 实现的浏览器可用的 fs 模块。
|
|
39
|
-
|
|
40
|
-
## 安装
|
|
41
|
-
|
|
42
|
-
```sh
|
|
43
|
-
npm \
|
|
44
|
-
install \
|
|
45
|
-
--save \
|
|
46
|
-
"@themartiancompany/opfs"
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
## 什么是 OPFS
|
|
50
|
-
|
|
51
|
-
OPFS 是 [Origin private file system](
|
|
52
|
-
https://developer.mozilla.org/en-US/docs/Web/API/File_System_API/Origin_private_file_system)
|
|
53
|
-
的简称,旨在为浏览器环境提供一套文件系统 API 来操作本地文件。
|
|
54
|
-
|
|
55
|
-
## 为什么会有 'opfs`
|
|
56
|
-
|
|
57
|
-
标准的 OPFS API 和我们熟知的基于路径操作的文件系统 API 如 Node.js、Deno
|
|
58
|
-
存在较大的区别,本项目即是为了实现在浏览器中也能拥有 Deno
|
|
59
|
-
一样的 API 方便地操作文件而生。
|
|
60
|
-
|
|
61
|
-
异步 API 的返回值都是 [Result](
|
|
62
|
-
https://github.com/JiangJie/happy-rusty)
|
|
63
|
-
类型,类似 Rust 的 `Result`
|
|
64
|
-
枚举类型,提供更友好的错误处理方式。
|
|
65
|
-
|
|
66
|
-
## 为什么参考 Deno 而不是 Node.js
|
|
67
|
-
|
|
68
|
-
- 早期的 Node.js fs API
|
|
69
|
-
是基于回调的语法,虽然较新的版本支持了 Promise
|
|
70
|
-
语法,而 Deno fs API 则一开始就是基于 Promise
|
|
71
|
-
语法,这样来说的话,Deno 有更少的历史包袱,要实现和 Native
|
|
72
|
-
兼容的 API,选择 Deno 做为参考显然更合适。
|
|
73
|
-
- Deno 原生支持 TypeScript,而 Node.js
|
|
74
|
-
在不借助于其它工具的情况下暂不支持。
|
|
75
|
-
|
|
76
|
-
## 是否支持同步接口
|
|
77
|
-
|
|
78
|
-
**支持**
|
|
79
|
-
|
|
80
|
-
> [!NOTE]
|
|
81
|
-
但更推荐使用异步接口,因为主线程并未提供同步接口,为了强制实现同步语法,需要将 I/O 操作移到 `Worker` 进行,同时主线程需要处于阻塞状态,直到 `Worker` 完成 I/O 操作,这显然会带来性能上的损失。
|
|
82
|
-
|
|
83
|
-
并且由于需要启动 `Worker,同步接口需要在`
|
|
84
|
-
`Worker` 启动后才能使用,在此之前任何读写都会失败。
|
|
85
|
-
|
|
86
|
-
**请注意**,为了在主线程和 `Worker`
|
|
87
|
-
之间共享数据,需要使用 `SharedArrayBuffer`,为此需要两个额外的 `HTTP Response Header`:
|
|
88
|
-
`'Cross-Origin-Opener-Policy': 'same-origin'`
|
|
89
|
-
`'Cross-Origin-Embedder-Policy': 'require-corp'`。
|
|
90
|
-
否则会报错 `ReferenceError: SharedArrayBuffer is not defined`。
|
|
91
|
-
|
|
92
|
-
## 示例
|
|
93
|
-
|
|
94
|
-
```ts
|
|
95
|
-
import * as fs from 'opfs';
|
|
96
|
-
|
|
97
|
-
(async () => {
|
|
98
|
-
const mockServer = 'https://16a6dafa-2258-4a83-88fa-31a409e42b17.mock.pstmn.io';
|
|
99
|
-
const mockTodos = `${ mockServer }/todos`;
|
|
100
|
-
const mockTodo1 = `${ mockTodos }/1`;
|
|
101
|
-
|
|
102
|
-
// Check if OPFS is supported
|
|
103
|
-
console.log(`OPFS is${ isOPFSSupported() ? '' : ' not' } supported`);
|
|
104
|
-
|
|
105
|
-
// Clear all files and folders
|
|
106
|
-
await fs.emptyDir(fs.ROOT_DIR);
|
|
107
|
-
// Recursively create the /happy/opfs directory
|
|
108
|
-
await fs.mkdir('/happy/opfs');
|
|
109
|
-
// Create and write file content
|
|
110
|
-
await fs.writeFile('/happy/opfs/a.txt', 'hello opfs');
|
|
111
|
-
await fs.writeFile('/happy/op-fs/fs.txt', 'hello opfs');
|
|
112
|
-
// Move the file
|
|
113
|
-
await fs.move('/happy/opfs/a.txt', '/happy/b.txt');
|
|
114
|
-
// Append content to the file
|
|
115
|
-
await fs.appendFile('/happy/b.txt', new TextEncoder().encode(' happy opfs'));
|
|
116
|
-
|
|
117
|
-
// File no longer exists
|
|
118
|
-
const statRes = await fs.stat('/happy/opfs/a.txt');
|
|
119
|
-
console.assert(statRes.isErr());
|
|
120
|
-
|
|
121
|
-
console.assert((await fs.readFile('/happy/b.txt')).unwrap().byteLength === 21);
|
|
122
|
-
// Automatically normalize the path
|
|
123
|
-
console.assert((await fs.readTextFile('//happy///b.txt//')).unwrap() === 'hello opfs happy opfs');
|
|
124
|
-
|
|
125
|
-
console.assert((await fs.remove('/happy/not/exists')).isOk());
|
|
126
|
-
console.assert((await fs.remove('/happy/opfs')).isOk());
|
|
127
|
-
|
|
128
|
-
console.assert(!(await fs.exists('/happy/opfs')).unwrap());
|
|
129
|
-
console.assert((await fs.exists('/happy/b.txt')).unwrap());
|
|
130
|
-
console.assert(fs.isFileHandle((await fs.stat('/happy/b.txt')).unwrap()));
|
|
131
|
-
|
|
132
|
-
// Download a file
|
|
133
|
-
const downloadTask = fs.downloadFile(mockSingle, '/todo.json', {
|
|
134
|
-
timeout: 1000,
|
|
135
|
-
onProgress(progressResult): void {
|
|
136
|
-
progressResult.inspect(progress => {
|
|
137
|
-
console.log(`Downloaded ${ progress.completedByteLength }/${ progress.totalByteLength } bytes`);
|
|
138
|
-
});
|
|
139
|
-
},
|
|
140
|
-
});
|
|
141
|
-
const downloadRes = await downloadTask.response;
|
|
142
|
-
if (downloadRes.isOk()) {
|
|
143
|
-
console.assert(downloadRes.unwrap() instanceof Response);
|
|
144
|
-
|
|
145
|
-
const postData = (await fs.readTextFile('/todo.json')).unwrap();
|
|
146
|
-
const postJson: {
|
|
147
|
-
id: number;
|
|
148
|
-
title: string;
|
|
149
|
-
} = JSON.parse(postData);
|
|
150
|
-
console.assert(postJson.id === 1);
|
|
151
|
-
|
|
152
|
-
// Modify the file
|
|
153
|
-
postJson.title = 'happy-opfs';
|
|
154
|
-
await fs.writeFile('/todo.json', JSON.stringify(postJson));
|
|
155
|
-
|
|
156
|
-
// Upload a file
|
|
157
|
-
console.assert((await fs.uploadFile('/todo.json', mockAll).response).unwrap() instanceof Response);
|
|
158
|
-
} else {
|
|
159
|
-
console.assert(downloadRes.unwrapErr() instanceof Error);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
{
|
|
163
|
-
// Download a file to a temporary file
|
|
164
|
-
const downloadTask = fs.downloadFile(mockSingle);
|
|
165
|
-
const downloadRes = await downloadTask.response;
|
|
166
|
-
downloadRes.inspect(x => {
|
|
167
|
-
console.assert(fs.isTempPath(x.tempFilePath));
|
|
168
|
-
console.assert(x.rawResponse instanceof Response);
|
|
169
|
-
});
|
|
170
|
-
if (downloadRes.isOk()) {
|
|
171
|
-
await fs.remove(downloadRes.unwrap().tempFilePath);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// Will create directory
|
|
176
|
-
await fs.emptyDir('/not-exists');
|
|
177
|
-
|
|
178
|
-
// Zip/Unzip
|
|
179
|
-
console.assert((await fs.zip('/happy', '/happy.zip')).isOk());
|
|
180
|
-
console.assert((await fs.zip('/happy')).unwrap().byteLength === (await fs.readFile('/happy.zip')).unwrap().byteLength);
|
|
181
|
-
console.assert((await fs.unzip('/happy.zip', '/happy-2')).isOk());
|
|
182
|
-
console.assert((await fs.unzipFromUrl(mockZipUrl, '/happy-3', {
|
|
183
|
-
onProgress(progressResult) {
|
|
184
|
-
progressResult.inspect(progress => {
|
|
185
|
-
console.log(`Unzipped ${ progress.completedByteLength }/${ progress.totalByteLength } bytes`);
|
|
186
|
-
});
|
|
187
|
-
},
|
|
188
|
-
})).isOk());
|
|
189
|
-
console.assert((await fs.zipFromUrl(mockZipUrl, '/test-zip.zip')).isOk());
|
|
190
|
-
console.assert((await fs.zipFromUrl(mockZipUrl)).unwrap().byteLength === (await fs.readFile('/test-zip.zip')).unwrap().byteLength);
|
|
191
|
-
|
|
192
|
-
// Temp
|
|
193
|
-
console.log(`temp txt file: ${ fs.generateTempPath({
|
|
194
|
-
basename: 'opfs',
|
|
195
|
-
extname: '.txt',
|
|
196
|
-
}) }`);
|
|
197
|
-
console.log(`temp dir: ${ fs.generateTempPath({
|
|
198
|
-
isDirectory: true,
|
|
199
|
-
}) }`);
|
|
200
|
-
(await fs.mkTemp()).inspect(path => {
|
|
201
|
-
console.assert(path.startsWith('/tmp/tmp-'));
|
|
202
|
-
});
|
|
203
|
-
const expired = new Date();
|
|
204
|
-
(await fs.mkTemp({
|
|
205
|
-
basename: 'opfs',
|
|
206
|
-
extname: '.txt',
|
|
207
|
-
})).inspect(path => {
|
|
208
|
-
console.assert(path.startsWith('/tmp/opfs-'));
|
|
209
|
-
console.assert(path.endsWith('.txt'));
|
|
210
|
-
});
|
|
211
|
-
(await fs.mkTemp({
|
|
212
|
-
isDirectory: true,
|
|
213
|
-
basename: '',
|
|
214
|
-
})).inspect(path => {
|
|
215
|
-
console.assert(path.startsWith('/tmp/'));
|
|
216
|
-
});
|
|
217
|
-
console.assert((await Array.fromAsync((await fs.readDir(fs.TMP_DIR)).unwrap())).length === 3);
|
|
218
|
-
await fs.pruneTemp(expired);
|
|
219
|
-
console.assert((await Array.fromAsync((await fs.readDir(fs.TMP_DIR)).unwrap())).length === 2);
|
|
220
|
-
// await fs.deleteTemp();
|
|
221
|
-
// console.assert(!(await fs.exists(fs.TMP_DIR)).unwrap());
|
|
222
|
-
|
|
223
|
-
// Copy
|
|
224
|
-
await fs.mkdir('/happy/copy');
|
|
225
|
-
console.assert((await fs.copy('/happy/b.txt', '/happy-2')).isErr());
|
|
226
|
-
console.assert((await fs.copy('/happy', '/happy-copy')).isOk());
|
|
227
|
-
await fs.appendFile('/happy-copy/b.txt', ' copy');
|
|
228
|
-
console.assert((await fs.readFile('/happy-copy/b.txt')).unwrap().byteLength === 26);
|
|
229
|
-
await fs.appendFile('/happy/op-fs/fs.txt', ' copy');
|
|
230
|
-
await fs.copy('/happy', '/happy-copy', {
|
|
231
|
-
overwrite: false,
|
|
232
|
-
});
|
|
233
|
-
console.assert((await fs.readFile('/happy-copy/b.txt')).unwrap().byteLength === 26);
|
|
234
|
-
|
|
235
|
-
// List all files and folders in the root directory
|
|
236
|
-
for await (const { path, handle } of (await fs.readDir(fs.ROOT_DIR, {
|
|
237
|
-
recursive: true,
|
|
238
|
-
})).unwrap()) {
|
|
239
|
-
const handleLike = await fs.toFileSystemHandleLike(handle);
|
|
240
|
-
if (fs.isFileHandleLike(handleLike)) {
|
|
241
|
-
console.log(`${ path } is a ${ handleLike.kind }, name = ${ handleLike.name }, type = ${ handleLike.type }, size = ${ handleLike.size }, lastModified = ${ handleLike.lastModified }`);
|
|
242
|
-
} else {
|
|
243
|
-
console.log(`${ path } is a ${ handleLike.kind }, name = ${ handleLike.name }`);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// Comment this line to view using OPFS Explorer
|
|
248
|
-
await fs.remove(fs.ROOT_DIR);
|
|
249
|
-
})();
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
以上示例代码可以在 [tests/async.ts](
|
|
253
|
-
tests/async.ts)
|
|
254
|
-
找到,也可以通过以下方式查看运行时效果。
|
|
255
|
-
|
|
256
|
-
```
|
|
257
|
-
git clone https://github.com/themartiancompany/opfs.git
|
|
258
|
-
cd opfs
|
|
259
|
-
pnpm install
|
|
260
|
-
pnpm start
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
通过浏览器打开 [https://localhost:8443/](
|
|
264
|
-
https://localhost:8443/),打开开发者工具观察 console
|
|
265
|
-
的输出。
|
|
266
|
-
|
|
267
|
-
你也可以安装 [OPFS Explorer](
|
|
268
|
-
https://chromewebstore.google.com/detail/acndjpgkpaclldomagafnognkcgjignd)
|
|
269
|
-
浏览器扩展,以便直观地查看文件系统状态。
|
|
270
|
-
|
|
271
|
-
### 同步示例
|
|
272
|
-
|
|
273
|
-
`worker.ts`
|
|
274
|
-
```ts
|
|
275
|
-
import { startSyncAgent } from 'happy-opfs';
|
|
276
|
-
|
|
277
|
-
startSyncAgent();
|
|
278
|
-
```
|
|
279
|
-
|
|
280
|
-
`index.ts`
|
|
281
|
-
```ts
|
|
282
|
-
import { connectSyncAgent, mkdirSync } from 'happy-opfs';
|
|
283
|
-
|
|
284
|
-
await connectSyncAgent({
|
|
285
|
-
worker: new Worker(new URL('worker.ts', import.meta.url), {
|
|
286
|
-
type: 'module'
|
|
287
|
-
}),
|
|
288
|
-
// SharedArrayBuffer size between main thread and worker
|
|
289
|
-
bufferLength: 10 * 1024 * 1024,
|
|
290
|
-
// max wait time at main thread per operation
|
|
291
|
-
opTimeout: 3000,
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
mkdirSync('/happy/opfs');
|
|
295
|
-
// other sync operations
|
|
296
|
-
```
|
|
297
|
-
|
|
298
|
-
详见 [tests/sync.ts](
|
|
299
|
-
tests/sync.ts)。
|
|
300
|
-
|
|
301
|
-
## [文档](docs/README.md)
|