@thi.ng/block-fs 0.3.0 → 0.4.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/CHANGELOG.md +15 -1
- package/README.md +66 -5
- package/cli.d.ts +2 -0
- package/cli.js +33 -18
- package/fs.d.ts +4 -0
- package/fs.js +8 -1
- package/package.json +6 -3
- package/storage/memory.d.ts +1 -1
- package/storage/memory.js +4 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
|
-
- **Last updated**: 2025-04-
|
|
3
|
+
- **Last updated**: 2025-04-06T13:54:45Z
|
|
4
4
|
- **Generator**: [thi.ng/monopub](https://thi.ng/monopub)
|
|
5
5
|
|
|
6
6
|
All notable changes to this project will be documented in this file.
|
|
@@ -11,6 +11,20 @@ See [Conventional Commits](https://conventionalcommits.org/) for commit guidelin
|
|
|
11
11
|
**Note:** Unlisted _patch_ versions only involve non-code or otherwise excluded changes
|
|
12
12
|
and/or version bumps of transitive dependencies.
|
|
13
13
|
|
|
14
|
+
## [0.4.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/block-fs@0.4.0) (2025-04-06)
|
|
15
|
+
|
|
16
|
+
#### 🚀 Features
|
|
17
|
+
|
|
18
|
+
- add support for wrapping `ArrayBuffer` ([e23f008](https://github.com/thi-ng/umbrella/commit/e23f008))
|
|
19
|
+
- update `MemoryBlockStorageOpts.buffer` to allow array buffers
|
|
20
|
+
- update `MemoryBlockStorage` ctor
|
|
21
|
+
- auto-infer MIME type in `.readAsObjectURL()` ([8fbcebd](https://github.com/thi-ng/umbrella/commit/8fbcebd))
|
|
22
|
+
- use `preferredTypeForPath()` as MIME type fallback
|
|
23
|
+
- update deps
|
|
24
|
+
- update CLI, add include/exclude regexp, logging ([ef04e09](https://github.com/thi-ng/umbrella/commit/ef04e09))
|
|
25
|
+
- add support for multiple include/exclude regexps in `convert` command
|
|
26
|
+
- add `--quiet` flag to disable logging
|
|
27
|
+
|
|
14
28
|
## [0.3.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/block-fs@0.3.0) (2025-04-02)
|
|
15
29
|
|
|
16
30
|
#### 🚀 Features
|
package/README.md
CHANGED
|
@@ -28,6 +28,8 @@
|
|
|
28
28
|
- [Installation](#installation)
|
|
29
29
|
- [Dependencies](#dependencies)
|
|
30
30
|
- [API](#api)
|
|
31
|
+
- [Basic usage](#basic-usage)
|
|
32
|
+
- [Working with a converted file system blob](#working-with-a-converted-file-system-blob)
|
|
31
33
|
- [Authors](#authors)
|
|
32
34
|
- [License](#license)
|
|
33
35
|
|
|
@@ -119,7 +121,8 @@ Once bundled, the binary blob can then be used together with
|
|
|
119
121
|
[`MemoryBlockStorage`](https://docs.thi.ng/umbrella/block-fs/classes/MemoryBlockStorage.html)
|
|
120
122
|
and [`BlockFS`](https://docs.thi.ng/umbrella/block-fs/classes/BlockFS.html) for
|
|
121
123
|
other purposes (e.g. distributed with your web app to provide a virtual
|
|
122
|
-
filesystem).
|
|
124
|
+
filesystem). Also see [API example further
|
|
125
|
+
below](#working-with-a-converted-file-system-blob).
|
|
123
126
|
|
|
124
127
|
Example usage to bundle the source directory of this package:
|
|
125
128
|
|
|
@@ -138,15 +141,31 @@ General usage:
|
|
|
138
141
|
```text
|
|
139
142
|
npx @thi.ng/block-fs convert --help
|
|
140
143
|
|
|
141
|
-
|
|
144
|
+
█ █ █ │
|
|
145
|
+
██ █ │
|
|
146
|
+
█ █ █ █ █ █ █ █ │ @thi.ng/block-fs 0.4.0
|
|
147
|
+
█ █ █ █ █ █ █ █ █ │ Block-based storage & file system layer
|
|
148
|
+
█ │
|
|
149
|
+
█ █ │
|
|
150
|
+
|
|
151
|
+
Usage: blockfs <cmd> [opts] input [...]
|
|
152
|
+
blockfs <cmd> --help
|
|
153
|
+
|
|
154
|
+
Available commands:
|
|
155
|
+
|
|
156
|
+
convert : Convert file tree into single BlockFS blob
|
|
157
|
+
list : List file tree of a BlockFS blob
|
|
142
158
|
|
|
143
159
|
Flags:
|
|
144
160
|
|
|
145
|
-
-
|
|
161
|
+
-q, --quiet Disable logging
|
|
162
|
+
-v, --verbose Display extra logging information
|
|
146
163
|
|
|
147
164
|
Main:
|
|
148
165
|
|
|
149
166
|
-bs BYTES, --block-size BYTES Block size (default: 1024)
|
|
167
|
+
-i EXT, --exclude EXT [multiple] File exclusion regexp
|
|
168
|
+
-i EXT, --include EXT [multiple] File inclusion regexp
|
|
150
169
|
-n INT, --num-blocks INT Number of blocks (multiple of 8)
|
|
151
170
|
-o STR, --out STR [required] Output file path
|
|
152
171
|
```
|
|
@@ -252,7 +271,7 @@ For Node.js REPL:
|
|
|
252
271
|
const bf = await import("@thi.ng/block-fs");
|
|
253
272
|
```
|
|
254
273
|
|
|
255
|
-
Package sizes (brotli'd, pre-treeshake): ESM: 4.
|
|
274
|
+
Package sizes (brotli'd, pre-treeshake): ESM: 4.50 KB
|
|
256
275
|
|
|
257
276
|
## Dependencies
|
|
258
277
|
|
|
@@ -264,6 +283,7 @@ Package sizes (brotli'd, pre-treeshake): ESM: 4.43 KB
|
|
|
264
283
|
- [@thi.ng/errors](https://github.com/thi-ng/umbrella/tree/develop/packages/errors)
|
|
265
284
|
- [@thi.ng/file-io](https://github.com/thi-ng/umbrella/tree/develop/packages/file-io)
|
|
266
285
|
- [@thi.ng/logger](https://github.com/thi-ng/umbrella/tree/develop/packages/logger)
|
|
286
|
+
- [@thi.ng/mime](https://github.com/thi-ng/umbrella/tree/develop/packages/mime)
|
|
267
287
|
- [@thi.ng/random](https://github.com/thi-ng/umbrella/tree/develop/packages/random)
|
|
268
288
|
- [@thi.ng/strings](https://github.com/thi-ng/umbrella/tree/develop/packages/strings)
|
|
269
289
|
|
|
@@ -273,7 +293,9 @@ Note: @thi.ng/api is in _most_ cases a type-only import (not used at runtime)
|
|
|
273
293
|
|
|
274
294
|
[Generated API docs](https://docs.thi.ng/umbrella/block-fs/)
|
|
275
295
|
|
|
276
|
-
|
|
296
|
+
### Basic usage
|
|
297
|
+
|
|
298
|
+
```ts tangle:export/readme-1.ts
|
|
277
299
|
import { BlockFS, MemoryBlockStorage } from "@thi.ng/block-fs";
|
|
278
300
|
|
|
279
301
|
// create in-memory storage (64KB)
|
|
@@ -335,6 +357,45 @@ for await (let entry of fs.root.tree()) {
|
|
|
335
357
|
// /deeply/nested/paths/are-ok 4n 2025-04-01T20:18:55.919Z
|
|
336
358
|
```
|
|
337
359
|
|
|
360
|
+
### Working with a converted file system blob
|
|
361
|
+
|
|
362
|
+
This example shows how to use a binary blob created via the [CLI `blockfs
|
|
363
|
+
convert` command](#convert-file-tree-into-single-blockfs-blob) as a virtual file
|
|
364
|
+
system...
|
|
365
|
+
|
|
366
|
+
```ts tangle:export/readme-2.ts
|
|
367
|
+
import { BlockFS, MemoryBlockStorage } from "@thi.ng/block-fs";
|
|
368
|
+
|
|
369
|
+
// load binary blob
|
|
370
|
+
const response = await fetch("./blocks.dat");
|
|
371
|
+
const buffer = await response.arrayBuffer();
|
|
372
|
+
|
|
373
|
+
// wrap as block storage
|
|
374
|
+
const storage = new MemoryBlockStorage({
|
|
375
|
+
buffer,
|
|
376
|
+
blockSize: 1024,
|
|
377
|
+
numBlocks: buffer.byteLength / 1024
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
// wrap as file system
|
|
381
|
+
const fs = new BlockFS(storage);
|
|
382
|
+
|
|
383
|
+
// list all entries (recursive)
|
|
384
|
+
for await(let f of fs.root.tree()) {
|
|
385
|
+
console.log(f.path);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// list all entries in a directory
|
|
389
|
+
const dir = (await fs.entryForPath("/path/to/dir")).directory;
|
|
390
|
+
for await (let f of dir) {
|
|
391
|
+
console.log(f.path);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// load an image as blob URL (MIME type is inferred automatically)
|
|
395
|
+
const img = new Image();
|
|
396
|
+
img.src = await fs.readAsObjectURL("/assets/test.jpg");
|
|
397
|
+
```
|
|
398
|
+
|
|
338
399
|
## Authors
|
|
339
400
|
|
|
340
401
|
- [Karsten Schmidt](https://thi.ng)
|
package/cli.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { type Command, type CommandCtx } from "@thi.ng/args";
|
|
2
2
|
interface CLIOpts {
|
|
3
3
|
verbose: boolean;
|
|
4
|
+
quiet: boolean;
|
|
4
5
|
}
|
|
5
6
|
interface ConvertOpts extends CLIOpts {
|
|
6
7
|
numBlocks?: number;
|
|
7
8
|
blockSize: number;
|
|
9
|
+
exclude?: string[];
|
|
8
10
|
include?: string[];
|
|
9
11
|
out: string;
|
|
10
12
|
}
|
package/cli.js
CHANGED
|
@@ -28,25 +28,30 @@ const ARG_BLOCKSIZE = {
|
|
|
28
28
|
}
|
|
29
29
|
})
|
|
30
30
|
};
|
|
31
|
-
const collectFiles = (
|
|
32
|
-
|
|
31
|
+
const collectFiles = ({
|
|
32
|
+
opts: { include, exclude },
|
|
33
|
+
inputs
|
|
34
|
+
}) => {
|
|
35
|
+
const root = resolve(inputs[0]);
|
|
33
36
|
const filtered = [];
|
|
34
37
|
const dirs = /* @__PURE__ */ new Set();
|
|
38
|
+
const $include = include?.map((x) => new RegExp(x));
|
|
39
|
+
const $exclude = exclude?.map((x) => new RegExp(x));
|
|
35
40
|
let total = 0;
|
|
36
41
|
for (let f of files(root)) {
|
|
37
42
|
const stats = statSync(f);
|
|
38
|
-
if (
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
43
|
+
if ($exclude && $exclude.some((x) => x.test(f))) continue;
|
|
44
|
+
if ($include && !$include.some((x) => x.test(f))) continue;
|
|
45
|
+
const dest = relative(root, f);
|
|
46
|
+
filtered.push({
|
|
47
|
+
src: f,
|
|
48
|
+
dest,
|
|
49
|
+
size: stats.size,
|
|
50
|
+
ctime: stats.ctimeMs,
|
|
51
|
+
mtime: stats.mtimeMs
|
|
52
|
+
});
|
|
53
|
+
dirs.add(dirname(dest));
|
|
54
|
+
total += stats.size;
|
|
50
55
|
}
|
|
51
56
|
return { files: filtered, dirs: [...dirs], size: total };
|
|
52
57
|
};
|
|
@@ -77,9 +82,14 @@ const CONVERT = {
|
|
|
77
82
|
desc: "Output file path",
|
|
78
83
|
optional: false
|
|
79
84
|
}),
|
|
85
|
+
exclude: strings({
|
|
86
|
+
alias: "e",
|
|
87
|
+
desc: "File exclusion regexp",
|
|
88
|
+
hint: "EXT"
|
|
89
|
+
}),
|
|
80
90
|
include: strings({
|
|
81
91
|
alias: "i",
|
|
82
|
-
desc: "
|
|
92
|
+
desc: "File inclusion regexp",
|
|
83
93
|
hint: "EXT"
|
|
84
94
|
})
|
|
85
95
|
},
|
|
@@ -102,7 +112,7 @@ const CONVERT = {
|
|
|
102
112
|
const bfs = new BlockFS(storage, { logger: ctx.logger });
|
|
103
113
|
await bfs.init();
|
|
104
114
|
for (let f of collected.files) {
|
|
105
|
-
ctx.logger.
|
|
115
|
+
ctx.logger.info("writing file:", f.dest);
|
|
106
116
|
await bfs.writeFile(f.dest, readBinary(f.src));
|
|
107
117
|
const entry = await bfs.entryForPath(f.dest);
|
|
108
118
|
entry.ctime = f.ctime;
|
|
@@ -179,7 +189,11 @@ cliApp({
|
|
|
179
189
|
opts: {
|
|
180
190
|
verbose: flag({
|
|
181
191
|
alias: "v",
|
|
182
|
-
desc: "Display extra
|
|
192
|
+
desc: "Display extra logging information"
|
|
193
|
+
}),
|
|
194
|
+
quiet: flag({
|
|
195
|
+
alias: "q",
|
|
196
|
+
desc: "Disable logging"
|
|
183
197
|
})
|
|
184
198
|
},
|
|
185
199
|
commands: {
|
|
@@ -188,7 +202,8 @@ cliApp({
|
|
|
188
202
|
},
|
|
189
203
|
name: "blockfs",
|
|
190
204
|
ctx: async (ctx) => {
|
|
191
|
-
if (ctx.opts.
|
|
205
|
+
if (ctx.opts.quiet) ctx.logger.level = LogLevel.NONE;
|
|
206
|
+
else if (ctx.opts.verbose) ctx.logger.level = LogLevel.DEBUG;
|
|
192
207
|
return ctx;
|
|
193
208
|
},
|
|
194
209
|
start: 3,
|
package/fs.d.ts
CHANGED
|
@@ -160,6 +160,10 @@ export declare class BlockFS {
|
|
|
160
160
|
* object URL, optionally typed with given MIME type.
|
|
161
161
|
*
|
|
162
162
|
* @remarks
|
|
163
|
+
* If `type` is omitted, it will be attempted to be inferred automatically
|
|
164
|
+
* via [thi.ng/mime](https://thi.ng/mime).
|
|
165
|
+
*
|
|
166
|
+
* @remarks
|
|
163
167
|
* Reference:
|
|
164
168
|
*
|
|
165
169
|
* - https://developer.mozilla.org/en-US/docs/Web/API/Blob#creating_a_url_representing_the_contents_of_a_typed_array
|
package/fs.js
CHANGED
|
@@ -5,6 +5,7 @@ import { assert } from "@thi.ng/errors/assert";
|
|
|
5
5
|
import { illegalArgs } from "@thi.ng/errors/illegal-arguments";
|
|
6
6
|
import { illegalState } from "@thi.ng/errors/illegal-state";
|
|
7
7
|
import { NULL_LOGGER } from "@thi.ng/logger/null";
|
|
8
|
+
import { preferredTypeForPath } from "@thi.ng/mime";
|
|
8
9
|
import {
|
|
9
10
|
EntryType
|
|
10
11
|
} from "./api.js";
|
|
@@ -289,6 +290,10 @@ class BlockFS {
|
|
|
289
290
|
* object URL, optionally typed with given MIME type.
|
|
290
291
|
*
|
|
291
292
|
* @remarks
|
|
293
|
+
* If `type` is omitted, it will be attempted to be inferred automatically
|
|
294
|
+
* via [thi.ng/mime](https://thi.ng/mime).
|
|
295
|
+
*
|
|
296
|
+
* @remarks
|
|
292
297
|
* Reference:
|
|
293
298
|
*
|
|
294
299
|
* - https://developer.mozilla.org/en-US/docs/Web/API/Blob#creating_a_url_representing_the_contents_of_a_typed_array
|
|
@@ -298,7 +303,9 @@ class BlockFS {
|
|
|
298
303
|
*/
|
|
299
304
|
async readAsObjectURL(path, type) {
|
|
300
305
|
return URL.createObjectURL(
|
|
301
|
-
new Blob([await this.readFile(path)], {
|
|
306
|
+
new Blob([await this.readFile(path)], {
|
|
307
|
+
type: type ?? (isString(path) ? preferredTypeForPath(path) : void 0)
|
|
308
|
+
})
|
|
302
309
|
);
|
|
303
310
|
}
|
|
304
311
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thi.ng/block-fs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Customizable block-based storage, adapters & file system layer",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "./index.js",
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
"@thi.ng/errors": "^2.5.31",
|
|
51
51
|
"@thi.ng/file-io": "^2.1.34",
|
|
52
52
|
"@thi.ng/logger": "^3.1.6",
|
|
53
|
+
"@thi.ng/mime": "^2.7.7",
|
|
53
54
|
"@thi.ng/random": "^4.1.16",
|
|
54
55
|
"@thi.ng/strings": "^3.9.10"
|
|
55
56
|
},
|
|
@@ -60,14 +61,16 @@
|
|
|
60
61
|
"typescript": "^5.8.2"
|
|
61
62
|
},
|
|
62
63
|
"keywords": [
|
|
64
|
+
"async",
|
|
63
65
|
"binary",
|
|
64
66
|
"block",
|
|
65
67
|
"cli",
|
|
66
68
|
"conversion",
|
|
67
69
|
"file",
|
|
68
|
-
"
|
|
70
|
+
"filesystem",
|
|
69
71
|
"memory",
|
|
70
72
|
"memory-mapped",
|
|
73
|
+
"mime",
|
|
71
74
|
"nodejs",
|
|
72
75
|
"path",
|
|
73
76
|
"storage",
|
|
@@ -129,5 +132,5 @@
|
|
|
129
132
|
"status": "alpha",
|
|
130
133
|
"year": 2024
|
|
131
134
|
},
|
|
132
|
-
"gitHead": "
|
|
135
|
+
"gitHead": "c88a589f33207b02a43172313b38ea09571265f1\n"
|
|
133
136
|
}
|
package/storage/memory.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ export interface MemoryBlockStorageOpts extends BlockStorageOpts {
|
|
|
16
16
|
* Optional, pre-defined/loaded byte buffer. Must have at least `numBlocks *
|
|
17
17
|
* blockSize` capacity.
|
|
18
18
|
*/
|
|
19
|
-
buffer?: Uint8Array;
|
|
19
|
+
buffer?: Uint8Array | ArrayBufferLike;
|
|
20
20
|
}
|
|
21
21
|
export declare class MemoryBlockStorage extends ABlockStorage<MemoryBlock> {
|
|
22
22
|
buffer: Uint8Array;
|
package/storage/memory.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isArrayBufferLike } from "@thi.ng/checks/is-arraybufferlike";
|
|
1
2
|
import { illegalArgs } from "@thi.ng/errors/illegal-arguments";
|
|
2
3
|
import { ABlockStorage } from "./astorage.js";
|
|
3
4
|
class MemoryBlock {
|
|
@@ -27,12 +28,13 @@ class MemoryBlockStorage extends ABlockStorage {
|
|
|
27
28
|
constructor(opts) {
|
|
28
29
|
super(opts);
|
|
29
30
|
const size = this.numBlocks * this.blockSize;
|
|
30
|
-
|
|
31
|
+
const buffer = opts.buffer ? isArrayBufferLike(opts.buffer) ? new Uint8Array(opts.buffer) : opts.buffer : void 0;
|
|
32
|
+
if (buffer && buffer.length < size) {
|
|
31
33
|
illegalArgs(
|
|
32
34
|
`given buffer is too small, expected at least ${size} bytes`
|
|
33
35
|
);
|
|
34
36
|
}
|
|
35
|
-
this.buffer =
|
|
37
|
+
this.buffer = buffer ?? new Uint8Array(size);
|
|
36
38
|
}
|
|
37
39
|
async hasBlock(id) {
|
|
38
40
|
this.ensureValidID(id);
|