@emdzej/csfs-core 0.1.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 +21 -0
- package/README.md +85 -0
- package/dist/errors.d.ts +37 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +52 -0
- package/dist/errors.js.map +1 -0
- package/dist/file.d.ts +109 -0
- package/dist/file.d.ts.map +1 -0
- package/dist/file.js +148 -0
- package/dist/file.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/mime.d.ts +5 -0
- package/dist/mime.d.ts.map +1 -0
- package/dist/mime.js +50 -0
- package/dist/mime.js.map +1 -0
- package/dist/path.d.ts +55 -0
- package/dist/path.d.ts.map +1 -0
- package/dist/path.js +76 -0
- package/dist/path.js.map +1 -0
- package/dist/types.d.ts +133 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +26 -0
- package/dist/types.js.map +1 -0
- package/dist/walk.d.ts +37 -0
- package/dist/walk.d.ts.map +1 -0
- package/dist/walk.js +93 -0
- package/dist/walk.js.map +1 -0
- package/package.json +47 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Michał Jaskólski
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# @emdzej/csfs-core
|
|
2
|
+
|
|
3
|
+
The contract every csfs backend implements, plus the pieces they all need:
|
|
4
|
+
paths, two `CsFile` implementations, MIME lookup, and a tree walker.
|
|
5
|
+
|
|
6
|
+
_Part of [csfs](https://github.com/emdzej/csfs) — a **c**lient **s**ide **f**ile **s**ystem: one read API over static HTTP, a picked directory,
|
|
7
|
+
OPFS, and inside zip archives._
|
|
8
|
+
|
|
9
|
+
You rarely install this alone — a backend package pulls it in. Install it
|
|
10
|
+
directly when you are writing a backend, or when you want to accept "any csfs
|
|
11
|
+
file system" as a parameter type.
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import type { CsFileSystem } from "@emdzej/csfs-core";
|
|
15
|
+
|
|
16
|
+
async function loadIndex(fs: CsFileSystem) {
|
|
17
|
+
const file = await fs.file("/pr/index.dat");
|
|
18
|
+
if (!file) return null; // absence is `null`, not a throw
|
|
19
|
+
return await file.slice(0, 4096).bytes(); // 4 KB, wherever it lives
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
That function works unchanged against HTTP, a picked directory, OPFS, a zip
|
|
24
|
+
archive, or `node:fs`.
|
|
25
|
+
|
|
26
|
+
## The interface
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
interface CsFileSystem {
|
|
30
|
+
readonly kind: string; // "http", "fsa+zip", … — for diagnostics
|
|
31
|
+
file(path: string): Promise<CsFile | null>;
|
|
32
|
+
directory(path: string): Promise<CsDirectory | null>;
|
|
33
|
+
read(path: string): Promise<Uint8Array | null>;
|
|
34
|
+
stat(path: string): Promise<CsStat | null>;
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**A file is modelled on `Blob`** — `size`, `type`, `slice`, `bytes`,
|
|
39
|
+
`arrayBuffer`, `stream`, `text`. Reading _part_ of a file is the operation that
|
|
40
|
+
makes remote data usable: an archive is read from its end, a sorted index is
|
|
41
|
+
binary-searched, a video is seeked. An interface whose only read is "give me the
|
|
42
|
+
whole thing" forces a full download per lookup. Because `Blob` and `File`
|
|
43
|
+
already have this shape, the local backends need no adapter at all.
|
|
44
|
+
|
|
45
|
+
**Absence returns `null`.** Checking whether something exists needs no
|
|
46
|
+
`try`/`catch`. Errors are for a caller asking something impossible, or a host
|
|
47
|
+
misbehaving — which keeps the two apart at the call site.
|
|
48
|
+
|
|
49
|
+
**Writing is a separate interface.** `WritableFileSystem` adds `write`,
|
|
50
|
+
`makeDirectory`, and `remove`. Two of the backends cannot write; folding it into
|
|
51
|
+
one interface would make every consumer check capabilities it never uses.
|
|
52
|
+
|
|
53
|
+
## What is in here
|
|
54
|
+
|
|
55
|
+
| Export | Why |
|
|
56
|
+
| ------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------- |
|
|
57
|
+
| `BlobFile` | a `CsFile` over anything `Blob`-shaped |
|
|
58
|
+
| `RangeFile` | a `CsFile` over a `(start, end) => Promise<Uint8Array>` reader; `slice` composes by arithmetic |
|
|
59
|
+
| `bytesFile`, `blobFile`, `toBlob`, `objectUrl` | constructing files and getting a URL for an `<img>` |
|
|
60
|
+
| `parsePath`, `formatPath`, `normalizePath` | `#` fragment addressing, and `..` resolved without escaping the root |
|
|
61
|
+
| `dirname`, `basename`, `extname`, `joinPath`, `segments` | path arithmetic that does not import `node:path` |
|
|
62
|
+
| `mimeType`, `registerMimeType` | extension → type, so `file.type` is populated on backends that do not report one |
|
|
63
|
+
| `walk`, `walkFileSystem`, `resolveFile`, `resolveDirectory`, `statVia` | depth-first iteration, and the shared implementations of "resolve a path one segment at a time" |
|
|
64
|
+
| `NotDataError`, `RangeUnsupportedError`, `BackendError`, `UnsupportedOperationError` | the failures worth telling apart |
|
|
65
|
+
|
|
66
|
+
`RangeFile` is the one to understand if you are writing a backend: give it a
|
|
67
|
+
size and a range reader, and slicing is free — `slice(100, 200).slice(10, 20)`
|
|
68
|
+
becomes a single read of bytes 110–120, with no request in between.
|
|
69
|
+
|
|
70
|
+
## Paths
|
|
71
|
+
|
|
72
|
+
Always `/`-rooted and `/`-separated, whatever the host uses. A `#` introduces a
|
|
73
|
+
path _inside_ an archive, and nests:
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
parsePath("/a.zip#/b.zip#/deep.txt");
|
|
77
|
+
// { base: "/a.zip", fragments: ["/b.zip", "/deep.txt"] }
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`normalizePath` collapses `.` and `..` and cannot be walked above the root, so a
|
|
81
|
+
path assembled from untrusted input stays inside the tree.
|
|
82
|
+
|
|
83
|
+
## Licence
|
|
84
|
+
|
|
85
|
+
**MIT** — see [LICENSE](https://github.com/emdzej/csfs/blob/main/LICENSE).
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Errors.
|
|
3
|
+
*
|
|
4
|
+
* Absence is *not* an error here: `file()` and `directory()` return `null`, so
|
|
5
|
+
* testing whether something exists needs no try/catch. These are for the cases
|
|
6
|
+
* where a caller asked for something impossible, or the store misbehaved —
|
|
7
|
+
* distinguishable types, because the two need opposite handling. "Not a data
|
|
8
|
+
* tree" means look somewhere else; "not found" is often expected.
|
|
9
|
+
*/
|
|
10
|
+
/** A backend cannot do what was asked — writing to HTTP, for instance. */
|
|
11
|
+
export declare class UnsupportedOperationError extends Error {
|
|
12
|
+
constructor(operation: string, backend: string);
|
|
13
|
+
}
|
|
14
|
+
/** The store answered, but not with what was asked for. */
|
|
15
|
+
export declare class BackendError extends Error {
|
|
16
|
+
readonly path?: string | undefined;
|
|
17
|
+
constructor(message: string, path?: string | undefined);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* A remote host returned a web page where data was expected.
|
|
21
|
+
*
|
|
22
|
+
* Its own type because it needs the opposite handling from a 404: a 404 means
|
|
23
|
+
* *this file* is absent, which is normal, while HTML means the whole tree is
|
|
24
|
+
* somewhere else — a single-page app answers any unknown path with its own
|
|
25
|
+
* HTML and a 200, so a mistyped base URL otherwise looks like a working tree
|
|
26
|
+
* whose every file happens to be a document.
|
|
27
|
+
*/
|
|
28
|
+
export declare class NotDataError extends BackendError {
|
|
29
|
+
readonly url: string;
|
|
30
|
+
readonly contentType: string;
|
|
31
|
+
constructor(url: string, contentType: string);
|
|
32
|
+
}
|
|
33
|
+
/** A host that ignores `Range` would silently return the wrong bytes. */
|
|
34
|
+
export declare class RangeUnsupportedError extends BackendError {
|
|
35
|
+
constructor(url: string, status: number);
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,0EAA0E;AAC1E,qBAAa,yBAA0B,SAAQ,KAAK;gBACtC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAI/C;AAED,2DAA2D;AAC3D,qBAAa,YAAa,SAAQ,KAAK;IAGnC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM;gBADtB,OAAO,EAAE,MAAM,EACN,IAAI,CAAC,EAAE,MAAM,YAAA;CAKzB;AAED;;;;;;;;GAQG;AACH,qBAAa,YAAa,SAAQ,YAAY;IAE1C,QAAQ,CAAC,GAAG,EAAE,MAAM;IACpB,QAAQ,CAAC,WAAW,EAAE,MAAM;gBADnB,GAAG,EAAE,MAAM,EACX,WAAW,EAAE,MAAM;CAK/B;AAED,yEAAyE;AACzE,qBAAa,qBAAsB,SAAQ,YAAY;gBACzC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAIxC"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Errors.
|
|
3
|
+
*
|
|
4
|
+
* Absence is *not* an error here: `file()` and `directory()` return `null`, so
|
|
5
|
+
* testing whether something exists needs no try/catch. These are for the cases
|
|
6
|
+
* where a caller asked for something impossible, or the store misbehaved —
|
|
7
|
+
* distinguishable types, because the two need opposite handling. "Not a data
|
|
8
|
+
* tree" means look somewhere else; "not found" is often expected.
|
|
9
|
+
*/
|
|
10
|
+
/** A backend cannot do what was asked — writing to HTTP, for instance. */
|
|
11
|
+
export class UnsupportedOperationError extends Error {
|
|
12
|
+
constructor(operation, backend) {
|
|
13
|
+
super(`${backend} cannot ${operation}`);
|
|
14
|
+
this.name = "UnsupportedOperationError";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/** The store answered, but not with what was asked for. */
|
|
18
|
+
export class BackendError extends Error {
|
|
19
|
+
path;
|
|
20
|
+
constructor(message, path) {
|
|
21
|
+
super(path ? `${path}: ${message}` : message);
|
|
22
|
+
this.path = path;
|
|
23
|
+
this.name = "BackendError";
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* A remote host returned a web page where data was expected.
|
|
28
|
+
*
|
|
29
|
+
* Its own type because it needs the opposite handling from a 404: a 404 means
|
|
30
|
+
* *this file* is absent, which is normal, while HTML means the whole tree is
|
|
31
|
+
* somewhere else — a single-page app answers any unknown path with its own
|
|
32
|
+
* HTML and a 200, so a mistyped base URL otherwise looks like a working tree
|
|
33
|
+
* whose every file happens to be a document.
|
|
34
|
+
*/
|
|
35
|
+
export class NotDataError extends BackendError {
|
|
36
|
+
url;
|
|
37
|
+
contentType;
|
|
38
|
+
constructor(url, contentType) {
|
|
39
|
+
super(`server returned ${contentType}, not data — is this really a data tree?`, url);
|
|
40
|
+
this.url = url;
|
|
41
|
+
this.contentType = contentType;
|
|
42
|
+
this.name = "NotDataError";
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** A host that ignores `Range` would silently return the wrong bytes. */
|
|
46
|
+
export class RangeUnsupportedError extends BackendError {
|
|
47
|
+
constructor(url, status) {
|
|
48
|
+
super(`expected 206 for a Range request, got ${status} — the host ignores Range`, url);
|
|
49
|
+
this.name = "RangeUnsupportedError";
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,0EAA0E;AAC1E,MAAM,OAAO,yBAA0B,SAAQ,KAAK;IAClD,YAAY,SAAiB,EAAE,OAAe;QAC5C,KAAK,CAAC,GAAG,OAAO,WAAW,SAAS,EAAE,CAAC,CAAC;QACxC,IAAI,CAAC,IAAI,GAAG,2BAA2B,CAAC;IAC1C,CAAC;CACF;AAED,2DAA2D;AAC3D,MAAM,OAAO,YAAa,SAAQ,KAAK;IAG1B;IAFX,YACE,OAAe,EACN,IAAa;QAEtB,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,KAAK,OAAO,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QAFrC,SAAI,GAAJ,IAAI,CAAS;QAGtB,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;IAC7B,CAAC;CACF;AAED;;;;;;;;GAQG;AACH,MAAM,OAAO,YAAa,SAAQ,YAAY;IAEjC;IACA;IAFX,YACW,GAAW,EACX,WAAmB;QAE5B,KAAK,CAAC,mBAAmB,WAAW,0CAA0C,EAAE,GAAG,CAAC,CAAC;QAH5E,QAAG,GAAH,GAAG,CAAQ;QACX,gBAAW,GAAX,WAAW,CAAQ;QAG5B,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;IAC7B,CAAC;CACF;AAED,yEAAyE;AACzE,MAAM,OAAO,qBAAsB,SAAQ,YAAY;IACrD,YAAY,GAAW,EAAE,MAAc;QACrC,KAAK,CAAC,yCAAyC,MAAM,2BAA2B,EAAE,GAAG,CAAC,CAAC;QACvF,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;IACtC,CAAC;CACF"}
|
package/dist/file.d.ts
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File implementations that every backend can share.
|
|
3
|
+
*
|
|
4
|
+
* `BlobFile` covers anything already `Blob`-shaped — a `File` from a picked
|
|
5
|
+
* directory, an OPFS file, a `Blob` built in memory — which is most of them.
|
|
6
|
+
* `RangeFile` covers the rest: a store that can answer "bytes m to n" but has
|
|
7
|
+
* no `Blob`, which is what HTTP is.
|
|
8
|
+
*
|
|
9
|
+
* Both exist so a backend supplies the one primitive it actually has and gets
|
|
10
|
+
* `slice`, `arrayBuffer`, `bytes`, `stream` and `text` for nothing.
|
|
11
|
+
*/
|
|
12
|
+
import type { CsFile } from "./types.js";
|
|
13
|
+
/** Minimal `Blob` surface. `Blob` and `File` both satisfy it as they are. */
|
|
14
|
+
export interface BlobLike {
|
|
15
|
+
readonly size: number;
|
|
16
|
+
readonly type?: string;
|
|
17
|
+
slice(start?: number, end?: number, contentType?: string): BlobLike;
|
|
18
|
+
arrayBuffer(): Promise<ArrayBuffer>;
|
|
19
|
+
stream(): unknown;
|
|
20
|
+
text(): Promise<string>;
|
|
21
|
+
}
|
|
22
|
+
/** A `CsFile` over anything `Blob`-shaped. */
|
|
23
|
+
export declare class BlobFile implements CsFile {
|
|
24
|
+
private readonly blob;
|
|
25
|
+
/** Overrides the blob's own type, which is often `""` from a file handle. */
|
|
26
|
+
private readonly mime?;
|
|
27
|
+
readonly path: string;
|
|
28
|
+
readonly name: string;
|
|
29
|
+
constructor(path: string, blob: BlobLike,
|
|
30
|
+
/** Overrides the blob's own type, which is often `""` from a file handle. */
|
|
31
|
+
mime?: string | undefined);
|
|
32
|
+
get size(): number;
|
|
33
|
+
get type(): string;
|
|
34
|
+
slice(start?: number, end?: number): CsFile;
|
|
35
|
+
arrayBuffer(): Promise<ArrayBuffer>;
|
|
36
|
+
bytes(): Promise<Uint8Array>;
|
|
37
|
+
stream(): ReadableStream<Uint8Array>;
|
|
38
|
+
text(): Promise<string>;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Reads a byte range. The one primitive a remote backend has to provide.
|
|
42
|
+
*
|
|
43
|
+
* `end` is exclusive. A backend may return fewer bytes than asked for only at
|
|
44
|
+
* the end of the file; anything else is a bug in the backend, not something
|
|
45
|
+
* callers should have to tolerate.
|
|
46
|
+
*/
|
|
47
|
+
export type RangeReader = (start: number, end: number) => Promise<Uint8Array>;
|
|
48
|
+
/**
|
|
49
|
+
* A `CsFile` over a range reader — the HTTP case.
|
|
50
|
+
*
|
|
51
|
+
* Slicing composes by arithmetic rather than by fetching, so
|
|
52
|
+
* `file.slice(a, b).slice(c, d)` costs nothing until something is read. That
|
|
53
|
+
* matters for archives: a zip reader slices its way to a central directory
|
|
54
|
+
* through several layers before touching the network once.
|
|
55
|
+
*/
|
|
56
|
+
export declare class RangeFile implements CsFile {
|
|
57
|
+
private readonly total;
|
|
58
|
+
private readonly read;
|
|
59
|
+
private readonly mime;
|
|
60
|
+
/** Window into the underlying object: `[start, end)`. */
|
|
61
|
+
private readonly start;
|
|
62
|
+
private readonly end;
|
|
63
|
+
readonly path: string;
|
|
64
|
+
readonly name: string;
|
|
65
|
+
constructor(path: string, total: number, read: RangeReader, mime?: string,
|
|
66
|
+
/** Window into the underlying object: `[start, end)`. */
|
|
67
|
+
start?: number, end?: number);
|
|
68
|
+
get size(): number;
|
|
69
|
+
get type(): string;
|
|
70
|
+
slice(start?: number, end?: number): CsFile;
|
|
71
|
+
bytes(): Promise<Uint8Array>;
|
|
72
|
+
arrayBuffer(): Promise<ArrayBuffer>;
|
|
73
|
+
stream(): ReadableStream<Uint8Array>;
|
|
74
|
+
text(): Promise<string>;
|
|
75
|
+
}
|
|
76
|
+
/** A `CsFile` over bytes already in memory. */
|
|
77
|
+
export declare function bytesFile(path: string, data: Uint8Array, mime?: string): CsFile;
|
|
78
|
+
/**
|
|
79
|
+
* A `CsFile` over a `Blob` or a `File`.
|
|
80
|
+
*
|
|
81
|
+
* The one-liner for the common case: a file the user picked, dropped, or chose
|
|
82
|
+
* with `<input type="file">`. The path defaults to the file's own name so a
|
|
83
|
+
* caller passing a `File` needs nothing else.
|
|
84
|
+
*/
|
|
85
|
+
export declare function blobFile(blob: BlobLike & {
|
|
86
|
+
name?: string;
|
|
87
|
+
}, path?: string, mime?: string): CsFile;
|
|
88
|
+
/**
|
|
89
|
+
* A `Blob` from bytes, with the type set.
|
|
90
|
+
*
|
|
91
|
+
* Here rather than in each caller because every consumer meets the same
|
|
92
|
+
* friction: TypeScript will not accept a `Uint8Array<ArrayBufferLike>` as a
|
|
93
|
+
* `BlobPart`, since the buffer *might* be a `SharedArrayBuffer`. It never is,
|
|
94
|
+
* for bytes that came out of a file, so the cast belongs at one boundary
|
|
95
|
+
* instead of being rediscovered at every call site.
|
|
96
|
+
*
|
|
97
|
+
* Setting the type matters: an `<img>` will sniff a typeless blob and cope,
|
|
98
|
+
* but an `<iframe>` handed a typeless PDF offers a download rather than
|
|
99
|
+
* rendering it.
|
|
100
|
+
*/
|
|
101
|
+
export declare function toBlob(bytes: Uint8Array, type?: string): Blob;
|
|
102
|
+
/**
|
|
103
|
+
* An object URL for a file's contents.
|
|
104
|
+
*
|
|
105
|
+
* **The caller must revoke it.** A page that mints one per image and never
|
|
106
|
+
* revokes pins every image it has ever shown in memory.
|
|
107
|
+
*/
|
|
108
|
+
export declare function objectUrl(file: CsFile): Promise<string>;
|
|
109
|
+
//# sourceMappingURL=file.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../src/file.ts"],"names":[],"mappings":"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"}
|
package/dist/file.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { basename } from "./path.js";
|
|
2
|
+
/** A `CsFile` over anything `Blob`-shaped. */
|
|
3
|
+
export class BlobFile {
|
|
4
|
+
blob;
|
|
5
|
+
mime;
|
|
6
|
+
path;
|
|
7
|
+
name;
|
|
8
|
+
constructor(path, blob,
|
|
9
|
+
/** Overrides the blob's own type, which is often `""` from a file handle. */
|
|
10
|
+
mime) {
|
|
11
|
+
this.blob = blob;
|
|
12
|
+
this.mime = mime;
|
|
13
|
+
this.path = path;
|
|
14
|
+
this.name = basename(path.split("#").at(-1) ?? path);
|
|
15
|
+
}
|
|
16
|
+
get size() {
|
|
17
|
+
return this.blob.size;
|
|
18
|
+
}
|
|
19
|
+
get type() {
|
|
20
|
+
return this.mime ?? this.blob.type ?? "";
|
|
21
|
+
}
|
|
22
|
+
slice(start, end) {
|
|
23
|
+
return new BlobFile(this.path, this.blob.slice(start, end), this.mime);
|
|
24
|
+
}
|
|
25
|
+
arrayBuffer() {
|
|
26
|
+
return this.blob.arrayBuffer();
|
|
27
|
+
}
|
|
28
|
+
async bytes() {
|
|
29
|
+
return new Uint8Array(await this.blob.arrayBuffer());
|
|
30
|
+
}
|
|
31
|
+
stream() {
|
|
32
|
+
return this.blob.stream();
|
|
33
|
+
}
|
|
34
|
+
text() {
|
|
35
|
+
return this.blob.text();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* A `CsFile` over a range reader — the HTTP case.
|
|
40
|
+
*
|
|
41
|
+
* Slicing composes by arithmetic rather than by fetching, so
|
|
42
|
+
* `file.slice(a, b).slice(c, d)` costs nothing until something is read. That
|
|
43
|
+
* matters for archives: a zip reader slices its way to a central directory
|
|
44
|
+
* through several layers before touching the network once.
|
|
45
|
+
*/
|
|
46
|
+
export class RangeFile {
|
|
47
|
+
total;
|
|
48
|
+
read;
|
|
49
|
+
mime;
|
|
50
|
+
start;
|
|
51
|
+
end;
|
|
52
|
+
path;
|
|
53
|
+
name;
|
|
54
|
+
constructor(path, total, read, mime = "",
|
|
55
|
+
/** Window into the underlying object: `[start, end)`. */
|
|
56
|
+
start = 0, end = total) {
|
|
57
|
+
this.total = total;
|
|
58
|
+
this.read = read;
|
|
59
|
+
this.mime = mime;
|
|
60
|
+
this.start = start;
|
|
61
|
+
this.end = end;
|
|
62
|
+
this.path = path;
|
|
63
|
+
this.name = basename(path.split("#").at(-1) ?? path);
|
|
64
|
+
}
|
|
65
|
+
get size() {
|
|
66
|
+
return Math.max(0, this.end - this.start);
|
|
67
|
+
}
|
|
68
|
+
get type() {
|
|
69
|
+
return this.mime;
|
|
70
|
+
}
|
|
71
|
+
slice(start = 0, end = this.size) {
|
|
72
|
+
// Clamp like `Blob.slice`: negative offsets count from the end, and
|
|
73
|
+
// over-long ranges truncate. A zip reader asking for the last 64 KB of a
|
|
74
|
+
// 20 KB file must get 20 KB rather than an error.
|
|
75
|
+
const size = this.size;
|
|
76
|
+
const from = start < 0 ? Math.max(0, size + start) : Math.min(start, size);
|
|
77
|
+
const to = end < 0 ? Math.max(0, size + end) : Math.min(end, size);
|
|
78
|
+
const lo = this.start + from;
|
|
79
|
+
const hi = this.start + Math.max(from, to);
|
|
80
|
+
return new RangeFile(this.path, this.total, this.read, this.mime, lo, hi);
|
|
81
|
+
}
|
|
82
|
+
async bytes() {
|
|
83
|
+
if (this.size === 0)
|
|
84
|
+
return new Uint8Array(0);
|
|
85
|
+
return await this.read(this.start, this.end);
|
|
86
|
+
}
|
|
87
|
+
async arrayBuffer() {
|
|
88
|
+
const bytes = await this.bytes();
|
|
89
|
+
// A fresh buffer: the view may be a window into a larger one, and handing
|
|
90
|
+
// that out would expose bytes the caller did not ask for.
|
|
91
|
+
return bytes.slice().buffer;
|
|
92
|
+
}
|
|
93
|
+
stream() {
|
|
94
|
+
// One chunk. A backend that can stream natively should override this by
|
|
95
|
+
// supplying its own `CsFile`; this is the correct-but-simple fallback.
|
|
96
|
+
const self = this;
|
|
97
|
+
return new ReadableStream({
|
|
98
|
+
async pull(controller) {
|
|
99
|
+
controller.enqueue(await self.bytes());
|
|
100
|
+
controller.close();
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
async text() {
|
|
105
|
+
return new TextDecoder().decode(await this.bytes());
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/** A `CsFile` over bytes already in memory. */
|
|
109
|
+
export function bytesFile(path, data, mime = "") {
|
|
110
|
+
return new RangeFile(path, data.byteLength, async (start, end) => data.subarray(start, end), mime);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* A `CsFile` over a `Blob` or a `File`.
|
|
114
|
+
*
|
|
115
|
+
* The one-liner for the common case: a file the user picked, dropped, or chose
|
|
116
|
+
* with `<input type="file">`. The path defaults to the file's own name so a
|
|
117
|
+
* caller passing a `File` needs nothing else.
|
|
118
|
+
*/
|
|
119
|
+
export function blobFile(blob, path, mime) {
|
|
120
|
+
const resolved = path ?? `/${blob.name ?? "file"}`;
|
|
121
|
+
return new BlobFile(resolved, blob, mime);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* A `Blob` from bytes, with the type set.
|
|
125
|
+
*
|
|
126
|
+
* Here rather than in each caller because every consumer meets the same
|
|
127
|
+
* friction: TypeScript will not accept a `Uint8Array<ArrayBufferLike>` as a
|
|
128
|
+
* `BlobPart`, since the buffer *might* be a `SharedArrayBuffer`. It never is,
|
|
129
|
+
* for bytes that came out of a file, so the cast belongs at one boundary
|
|
130
|
+
* instead of being rediscovered at every call site.
|
|
131
|
+
*
|
|
132
|
+
* Setting the type matters: an `<img>` will sniff a typeless blob and cope,
|
|
133
|
+
* but an `<iframe>` handed a typeless PDF offers a download rather than
|
|
134
|
+
* rendering it.
|
|
135
|
+
*/
|
|
136
|
+
export function toBlob(bytes, type = "application/octet-stream") {
|
|
137
|
+
return new Blob([bytes], { type });
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* An object URL for a file's contents.
|
|
141
|
+
*
|
|
142
|
+
* **The caller must revoke it.** A page that mints one per image and never
|
|
143
|
+
* revokes pins every image it has ever shown in memory.
|
|
144
|
+
*/
|
|
145
|
+
export async function objectUrl(file) {
|
|
146
|
+
return URL.createObjectURL(toBlob(await file.bytes(), file.type));
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=file.js.map
|
package/dist/file.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file.js","sourceRoot":"","sources":["../src/file.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC"}
|
package/dist/mime.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** The type for a path, or `"application/octet-stream"` when unknown. */
|
|
2
|
+
export declare function mimeType(path: string): string;
|
|
3
|
+
/** Register or override a type. For consumers with their own conventions. */
|
|
4
|
+
export declare function registerMimeType(extension: string, type: string): void;
|
|
5
|
+
//# sourceMappingURL=mime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mime.d.ts","sourceRoot":"","sources":["../src/mime.ts"],"names":[],"mappings":"AA2CA,yEAAyE;AACzE,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE7C;AAED,6EAA6E;AAC7E,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAEtE"}
|
package/dist/mime.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MIME type by extension.
|
|
3
|
+
*
|
|
4
|
+
* Small and deliberate rather than a dependency: what a filesystem needs this
|
|
5
|
+
* for is handing a blob URL to an `<img>` or an `<iframe>`, and those care
|
|
6
|
+
* about a handful of types. An `<img>` will sniff a typeless blob and cope; an
|
|
7
|
+
* `<iframe>` handed a typeless PDF offers a download instead of rendering it,
|
|
8
|
+
* which is the failure this exists to prevent.
|
|
9
|
+
*/
|
|
10
|
+
const TYPES = {
|
|
11
|
+
".png": "image/png",
|
|
12
|
+
".jpg": "image/jpeg",
|
|
13
|
+
".jpeg": "image/jpeg",
|
|
14
|
+
".gif": "image/gif",
|
|
15
|
+
".webp": "image/webp",
|
|
16
|
+
".svg": "image/svg+xml",
|
|
17
|
+
".tif": "image/tiff",
|
|
18
|
+
".tiff": "image/tiff",
|
|
19
|
+
".bmp": "image/bmp",
|
|
20
|
+
".ico": "image/x-icon",
|
|
21
|
+
".pdf": "application/pdf",
|
|
22
|
+
".json": "application/json",
|
|
23
|
+
".xml": "application/xml",
|
|
24
|
+
".html": "text/html",
|
|
25
|
+
".htm": "text/html",
|
|
26
|
+
".css": "text/css",
|
|
27
|
+
".js": "text/javascript",
|
|
28
|
+
".txt": "text/plain; charset=utf-8",
|
|
29
|
+
".csv": "text/csv",
|
|
30
|
+
".md": "text/markdown",
|
|
31
|
+
".zip": "application/zip",
|
|
32
|
+
".wasm": "application/wasm",
|
|
33
|
+
".mp4": "video/mp4",
|
|
34
|
+
".webm": "video/webm",
|
|
35
|
+
".mp3": "audio/mpeg",
|
|
36
|
+
".ogg": "audio/ogg",
|
|
37
|
+
".woff": "font/woff",
|
|
38
|
+
".woff2": "font/woff2",
|
|
39
|
+
".ttf": "font/ttf",
|
|
40
|
+
};
|
|
41
|
+
import { extname } from "./path.js";
|
|
42
|
+
/** The type for a path, or `"application/octet-stream"` when unknown. */
|
|
43
|
+
export function mimeType(path) {
|
|
44
|
+
return TYPES[extname(path)] ?? "application/octet-stream";
|
|
45
|
+
}
|
|
46
|
+
/** Register or override a type. For consumers with their own conventions. */
|
|
47
|
+
export function registerMimeType(extension, type) {
|
|
48
|
+
TYPES[extension.toLowerCase()] = type;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=mime.js.map
|
package/dist/mime.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mime.js","sourceRoot":"","sources":["../src/mime.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,KAAK,GAAqC;IAC9C,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,YAAY;IACpB,OAAO,EAAE,YAAY;IACrB,MAAM,EAAE,WAAW;IACnB,OAAO,EAAE,YAAY;IACrB,MAAM,EAAE,eAAe;IACvB,MAAM,EAAE,YAAY;IACpB,OAAO,EAAE,YAAY;IACrB,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,cAAc;IACtB,MAAM,EAAE,iBAAiB;IACzB,OAAO,EAAE,kBAAkB;IAC3B,MAAM,EAAE,iBAAiB;IACzB,OAAO,EAAE,WAAW;IACpB,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,UAAU;IAClB,KAAK,EAAE,iBAAiB;IACxB,MAAM,EAAE,2BAA2B;IACnC,MAAM,EAAE,UAAU;IAClB,KAAK,EAAE,eAAe;IACtB,MAAM,EAAE,iBAAiB;IACzB,OAAO,EAAE,kBAAkB;IAC3B,MAAM,EAAE,WAAW;IACnB,OAAO,EAAE,YAAY;IACrB,MAAM,EAAE,YAAY;IACpB,MAAM,EAAE,WAAW;IACnB,OAAO,EAAE,WAAW;IACpB,QAAQ,EAAE,YAAY;IACtB,MAAM,EAAE,UAAU;CACnB,CAAC;AAEF,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,yEAAyE;AACzE,MAAM,UAAU,QAAQ,CAAC,IAAY;IACnC,OAAO,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,0BAA0B,CAAC;AAC5D,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,gBAAgB,CAAC,SAAiB,EAAE,IAAY;IAC7D,KAAgC,CAAC,SAAS,CAAC,WAAW,EAAE,CAAC,GAAG,IAAI,CAAC;AACpE,CAAC"}
|
package/dist/path.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Paths, and the archive fragment.
|
|
3
|
+
*
|
|
4
|
+
* A csfs path is POSIX-style and rooted, with one extra piece of syntax: `#`
|
|
5
|
+
* separates a container from a path *inside* it.
|
|
6
|
+
*
|
|
7
|
+
* /dessins/100.zip#/1132C000.png
|
|
8
|
+
* /outer.zip#/inner.zip#/deep/file.txt
|
|
9
|
+
*
|
|
10
|
+
* `#` was chosen over a second argument or a `zip://` scheme because it keeps
|
|
11
|
+
* a location a single string. A path that survives being logged, stored in a
|
|
12
|
+
* manifest, put in a URL fragment or handed between a worker and a page is
|
|
13
|
+
* worth more than a tidier type — and every one of those places already
|
|
14
|
+
* accepts a string.
|
|
15
|
+
*
|
|
16
|
+
* Nesting is supported for the same reason: it costs one loop, and the
|
|
17
|
+
* alternative is a special case that someone eventually hits.
|
|
18
|
+
*/
|
|
19
|
+
/** A path split into its container chain and the final path inside it. */
|
|
20
|
+
export interface ParsedPath {
|
|
21
|
+
/** The outermost path in the host filesystem. Never contains `#`. */
|
|
22
|
+
readonly base: string;
|
|
23
|
+
/**
|
|
24
|
+
* Paths inside each successive container, outermost first.
|
|
25
|
+
*
|
|
26
|
+
* Empty for an ordinary path. For `a.zip#/b.zip#/c.txt` this is
|
|
27
|
+
* `["/b.zip", "/c.txt"]` — note that the *second* element is inside the
|
|
28
|
+
* archive named by the first.
|
|
29
|
+
*/
|
|
30
|
+
readonly fragments: readonly string[];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Normalise a path: single leading slash, no trailing slash, no `.` or `..`.
|
|
34
|
+
*
|
|
35
|
+
* `..` is resolved rather than rejected, and cannot escape the root — a path
|
|
36
|
+
* from a manifest or a URL is untrusted input, and `/a/../../etc` resolving to
|
|
37
|
+
* `/etc` inside someone's data directory is the whole reason to do this here
|
|
38
|
+
* rather than in each backend.
|
|
39
|
+
*/
|
|
40
|
+
export declare function normalizePath(path: string): string;
|
|
41
|
+
/** Split a path on `#`, normalising each piece. */
|
|
42
|
+
export declare function parsePath(path: string): ParsedPath;
|
|
43
|
+
/** Rebuild a path from its pieces. The inverse of `parsePath`. */
|
|
44
|
+
export declare function formatPath(parsed: ParsedPath): string;
|
|
45
|
+
/** Everything before the last segment, or `"/"` at the root. */
|
|
46
|
+
export declare function dirname(path: string): string;
|
|
47
|
+
/** The last segment. `""` for the root. */
|
|
48
|
+
export declare function basename(path: string): string;
|
|
49
|
+
/** The extension including the dot, lower-cased, or `""`. */
|
|
50
|
+
export declare function extname(path: string): string;
|
|
51
|
+
/** Join segments into a normalised path. */
|
|
52
|
+
export declare function joinPath(...parts: string[]): string;
|
|
53
|
+
/** Split a normalised path into its segments, without empties. */
|
|
54
|
+
export declare function segments(path: string): string[];
|
|
55
|
+
//# sourceMappingURL=path.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../src/path.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,0EAA0E;AAC1E,MAAM,WAAW,UAAU;IACzB,qEAAqE;IACrE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;;;OAMG;IACH,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;CACvC;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAWlD;AAED,mDAAmD;AACnD,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAKlD;AAED,kEAAkE;AAClE,wBAAgB,UAAU,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAErD;AAED,gEAAgE;AAChE,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAI5C;AAED,2CAA2C;AAC3C,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAG7C;AAED,6DAA6D;AAC7D,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAI5C;AAED,4CAA4C;AAC5C,wBAAgB,QAAQ,CAAC,GAAG,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,CAEnD;AAED,kEAAkE;AAClE,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAE/C"}
|
package/dist/path.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Paths, and the archive fragment.
|
|
3
|
+
*
|
|
4
|
+
* A csfs path is POSIX-style and rooted, with one extra piece of syntax: `#`
|
|
5
|
+
* separates a container from a path *inside* it.
|
|
6
|
+
*
|
|
7
|
+
* /dessins/100.zip#/1132C000.png
|
|
8
|
+
* /outer.zip#/inner.zip#/deep/file.txt
|
|
9
|
+
*
|
|
10
|
+
* `#` was chosen over a second argument or a `zip://` scheme because it keeps
|
|
11
|
+
* a location a single string. A path that survives being logged, stored in a
|
|
12
|
+
* manifest, put in a URL fragment or handed between a worker and a page is
|
|
13
|
+
* worth more than a tidier type — and every one of those places already
|
|
14
|
+
* accepts a string.
|
|
15
|
+
*
|
|
16
|
+
* Nesting is supported for the same reason: it costs one loop, and the
|
|
17
|
+
* alternative is a special case that someone eventually hits.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Normalise a path: single leading slash, no trailing slash, no `.` or `..`.
|
|
21
|
+
*
|
|
22
|
+
* `..` is resolved rather than rejected, and cannot escape the root — a path
|
|
23
|
+
* from a manifest or a URL is untrusted input, and `/a/../../etc` resolving to
|
|
24
|
+
* `/etc` inside someone's data directory is the whole reason to do this here
|
|
25
|
+
* rather than in each backend.
|
|
26
|
+
*/
|
|
27
|
+
export function normalizePath(path) {
|
|
28
|
+
const parts = [];
|
|
29
|
+
for (const segment of path.split("/")) {
|
|
30
|
+
if (segment === "" || segment === ".")
|
|
31
|
+
continue;
|
|
32
|
+
if (segment === "..") {
|
|
33
|
+
parts.pop();
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
parts.push(segment);
|
|
37
|
+
}
|
|
38
|
+
return `/${parts.join("/")}`;
|
|
39
|
+
}
|
|
40
|
+
/** Split a path on `#`, normalising each piece. */
|
|
41
|
+
export function parsePath(path) {
|
|
42
|
+
const pieces = path.split("#");
|
|
43
|
+
const base = normalizePath(pieces[0] ?? "");
|
|
44
|
+
const fragments = pieces.slice(1).map((p) => normalizePath(p));
|
|
45
|
+
return { base, fragments };
|
|
46
|
+
}
|
|
47
|
+
/** Rebuild a path from its pieces. The inverse of `parsePath`. */
|
|
48
|
+
export function formatPath(parsed) {
|
|
49
|
+
return [parsed.base, ...parsed.fragments].join("#");
|
|
50
|
+
}
|
|
51
|
+
/** Everything before the last segment, or `"/"` at the root. */
|
|
52
|
+
export function dirname(path) {
|
|
53
|
+
const normalized = normalizePath(path);
|
|
54
|
+
const at = normalized.lastIndexOf("/");
|
|
55
|
+
return at <= 0 ? "/" : normalized.slice(0, at);
|
|
56
|
+
}
|
|
57
|
+
/** The last segment. `""` for the root. */
|
|
58
|
+
export function basename(path) {
|
|
59
|
+
const normalized = normalizePath(path);
|
|
60
|
+
return normalized.slice(normalized.lastIndexOf("/") + 1);
|
|
61
|
+
}
|
|
62
|
+
/** The extension including the dot, lower-cased, or `""`. */
|
|
63
|
+
export function extname(path) {
|
|
64
|
+
const name = basename(path);
|
|
65
|
+
const at = name.lastIndexOf(".");
|
|
66
|
+
return at <= 0 ? "" : name.slice(at).toLowerCase();
|
|
67
|
+
}
|
|
68
|
+
/** Join segments into a normalised path. */
|
|
69
|
+
export function joinPath(...parts) {
|
|
70
|
+
return normalizePath(parts.join("/"));
|
|
71
|
+
}
|
|
72
|
+
/** Split a normalised path into its segments, without empties. */
|
|
73
|
+
export function segments(path) {
|
|
74
|
+
return normalizePath(path).split("/").filter(Boolean);
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=path.js.map
|
package/dist/path.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path.js","sourceRoot":"","sources":["../src/path.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAgBH;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QACtC,IAAI,OAAO,KAAK,EAAE,IAAI,OAAO,KAAK,GAAG;YAAE,SAAS;QAChD,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;YACrB,KAAK,CAAC,GAAG,EAAE,CAAC;YACZ,SAAS;QACX,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACtB,CAAC;IACD,OAAO,IAAI,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;AAC/B,CAAC;AAED,mDAAmD;AACnD,MAAM,UAAU,SAAS,CAAC,IAAY;IACpC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC/B,MAAM,IAAI,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAC5C,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/D,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;AAC7B,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,UAAU,CAAC,MAAkB;IAC3C,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACtD,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,OAAO,CAAC,IAAY;IAClC,MAAM,UAAU,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;IACvC,MAAM,EAAE,GAAG,UAAU,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACvC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AACjD,CAAC;AAED,2CAA2C;AAC3C,MAAM,UAAU,QAAQ,CAAC,IAAY;IACnC,MAAM,UAAU,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;IACvC,OAAO,UAAU,CAAC,KAAK,CAAC,UAAU,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,OAAO,CAAC,IAAY;IAClC,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC5B,MAAM,EAAE,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACjC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;AACrD,CAAC;AAED,4CAA4C;AAC5C,MAAM,UAAU,QAAQ,CAAC,GAAG,KAAe;IACzC,OAAO,aAAa,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AACxC,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,QAAQ,CAAC,IAAY;IACnC,OAAO,aAAa,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;AACxD,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The contract every backend implements.
|
|
3
|
+
*
|
|
4
|
+
* Two design decisions shape all of this, and both are worth stating because
|
|
5
|
+
* the obvious alternatives are worse.
|
|
6
|
+
*
|
|
7
|
+
* **A file is modelled on `Blob`, not on `readFile`.** `size`, `slice`,
|
|
8
|
+
* `arrayBuffer`, `stream`, `text` — the same five things a `Blob` gives you.
|
|
9
|
+
* That is not imitation for its own sake: reading *part* of a file is the
|
|
10
|
+
* operation that makes remote data usable at all. An archive is read from its
|
|
11
|
+
* end, a sorted index is binary-searched, a media file is seeked. An interface
|
|
12
|
+
* whose only read is "give me the whole thing" forces a download per lookup,
|
|
13
|
+
* and every backend then grows its own private range API. Because `Blob` and
|
|
14
|
+
* `File` already satisfy this shape, the local backends need no adapter at all.
|
|
15
|
+
*
|
|
16
|
+
* **Reads only.** Writing is a separate, optional interface
|
|
17
|
+
* (`WritableFileSystem`), because two of the three backends cannot write and a
|
|
18
|
+
* combined interface would make every consumer check capabilities it does not
|
|
19
|
+
* use. A read-only filesystem is the common case and should be the plain one.
|
|
20
|
+
*/
|
|
21
|
+
/** A file, or a slice of one. Deliberately `Blob`-shaped. */
|
|
22
|
+
export interface CsFile {
|
|
23
|
+
/** Full path within its filesystem, including any archive fragment. */
|
|
24
|
+
readonly path: string;
|
|
25
|
+
/** Basename, in the case the backing store reports. */
|
|
26
|
+
readonly name: string;
|
|
27
|
+
readonly size: number;
|
|
28
|
+
/** MIME type when the backend knows one; `""` when it does not. */
|
|
29
|
+
readonly type: string;
|
|
30
|
+
/**
|
|
31
|
+
* A view of part of this file, without reading anything yet.
|
|
32
|
+
*
|
|
33
|
+
* `end` is exclusive, as in `Blob.slice`. Negative offsets and out-of-range
|
|
34
|
+
* values clamp rather than throw, again as `Blob.slice` does — a zip reader
|
|
35
|
+
* asking for the last 64 KB of a 20 KB file should get 20 KB, not an error.
|
|
36
|
+
*/
|
|
37
|
+
slice(start?: number, end?: number): CsFile;
|
|
38
|
+
arrayBuffer(): Promise<ArrayBuffer>;
|
|
39
|
+
bytes(): Promise<Uint8Array>;
|
|
40
|
+
stream(): ReadableStream<Uint8Array>;
|
|
41
|
+
text(): Promise<string>;
|
|
42
|
+
}
|
|
43
|
+
export type CsEntry = {
|
|
44
|
+
readonly kind: "file";
|
|
45
|
+
readonly name: string;
|
|
46
|
+
readonly size: number;
|
|
47
|
+
} | {
|
|
48
|
+
readonly kind: "directory";
|
|
49
|
+
readonly name: string;
|
|
50
|
+
};
|
|
51
|
+
/** A directory listing. */
|
|
52
|
+
export interface CsDirectory {
|
|
53
|
+
readonly path: string;
|
|
54
|
+
readonly name: string;
|
|
55
|
+
/** Direct children. */
|
|
56
|
+
entries(): Promise<CsEntry[]>;
|
|
57
|
+
/** A child file, or `null` if it is absent or is a directory. */
|
|
58
|
+
file(name: string): Promise<CsFile | null>;
|
|
59
|
+
/** A child directory, or `null` if it is absent or is a file. */
|
|
60
|
+
directory(name: string): Promise<CsDirectory | null>;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* How names are matched.
|
|
64
|
+
*
|
|
65
|
+
* `"insensitive"` exists because real data sets are inconsistent about case —
|
|
66
|
+
* an index may say `MS43.IPO` where a reference says `ms43.ipo`. It is not the
|
|
67
|
+
* default, because it costs a full listing per lookup on backends that cannot
|
|
68
|
+
* index by name, and because two files differing only in case then become
|
|
69
|
+
* ambiguous.
|
|
70
|
+
*/
|
|
71
|
+
export type CaseSensitivity = "sensitive" | "insensitive";
|
|
72
|
+
/** A read-only filesystem. */
|
|
73
|
+
export interface CsFileSystem {
|
|
74
|
+
/** Short name of the backend, for diagnostics: `"http"`, `"fsa"`, `"opfs"`. */
|
|
75
|
+
readonly kind: string;
|
|
76
|
+
/**
|
|
77
|
+
* Resolve a path to a file.
|
|
78
|
+
*
|
|
79
|
+
* Paths are POSIX-style and rooted: `/pr/Planches.dat`. A leading slash is
|
|
80
|
+
* optional. `null` means "not there", which is the normal, non-exceptional
|
|
81
|
+
* answer — callers should not have to write a try/catch to test existence.
|
|
82
|
+
*
|
|
83
|
+
* A path may address inside an archive with `#`, e.g.
|
|
84
|
+
* `/dessins/100.zip#/1132C000.png`, when the filesystem was wrapped for it.
|
|
85
|
+
*/
|
|
86
|
+
file(path: string): Promise<CsFile | null>;
|
|
87
|
+
/** Resolve a path to a directory. `"/"` is the root. */
|
|
88
|
+
directory(path: string): Promise<CsDirectory | null>;
|
|
89
|
+
/** Shorthand for `file(path)` then `bytes()`. `null` if absent. */
|
|
90
|
+
read(path: string): Promise<Uint8Array | null>;
|
|
91
|
+
/** Does this path exist, and as what? */
|
|
92
|
+
stat(path: string): Promise<CsStat | null>;
|
|
93
|
+
/**
|
|
94
|
+
* A URL the browser can load directly — an `<img>`, `<iframe>` or `<video>`
|
|
95
|
+
* source. Optional: only a backend whose bytes are already addressable can
|
|
96
|
+
* offer one, which in practice means HTTP.
|
|
97
|
+
*
|
|
98
|
+
* Worth asking for rather than always minting a blob. A blob URL requires
|
|
99
|
+
* downloading the whole file first, so a 40 MB PDF cannot show page one
|
|
100
|
+
* until all of it has arrived, and the browser cannot cache it between
|
|
101
|
+
* visits. A real URL lets the browser range-request and cache for itself.
|
|
102
|
+
*
|
|
103
|
+
* `null` when this path has no direct URL — it does not exist, or it lives
|
|
104
|
+
* inside an archive, where the bytes are not addressable on their own. A
|
|
105
|
+
* caller should then fall back to a blob.
|
|
106
|
+
*/
|
|
107
|
+
directUrl?(path: string): Promise<string | null>;
|
|
108
|
+
}
|
|
109
|
+
export interface CsStat {
|
|
110
|
+
readonly kind: "file" | "directory";
|
|
111
|
+
readonly name: string;
|
|
112
|
+
/** Bytes; `0` for a directory, and for backends that cannot say cheaply. */
|
|
113
|
+
readonly size: number;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Writing, for the backends that can.
|
|
117
|
+
*
|
|
118
|
+
* Separate from `CsFileSystem` so a consumer that only reads never sees it,
|
|
119
|
+
* and so a function that needs to write says so in its signature.
|
|
120
|
+
*/
|
|
121
|
+
export interface WritableFileSystem extends CsFileSystem {
|
|
122
|
+
/** Create or replace a file, creating parent directories. */
|
|
123
|
+
write(path: string, data: Uint8Array | ReadableStream<Uint8Array>): Promise<void>;
|
|
124
|
+
/** Create a directory and its parents. Succeeds if it exists. */
|
|
125
|
+
makeDirectory(path: string): Promise<void>;
|
|
126
|
+
/** Remove a file or an empty directory. `recursive` removes a tree. */
|
|
127
|
+
remove(path: string, opts?: {
|
|
128
|
+
recursive?: boolean;
|
|
129
|
+
}): Promise<void>;
|
|
130
|
+
}
|
|
131
|
+
/** True when a filesystem can be written to. */
|
|
132
|
+
export declare function isWritable(fs: CsFileSystem): fs is WritableFileSystem;
|
|
133
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,6DAA6D;AAC7D,MAAM,WAAW,MAAM;IACrB,uEAAuE;IACvE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,uDAAuD;IACvD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,mEAAmE;IACnE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB;;;;;;OAMG;IACH,KAAK,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAE5C,WAAW,IAAI,OAAO,CAAC,WAAW,CAAC,CAAC;IACpC,KAAK,IAAI,OAAO,CAAC,UAAU,CAAC,CAAC;IAC7B,MAAM,IAAI,cAAc,CAAC,UAAU,CAAC,CAAC;IACrC,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;CACzB;AAED,MAAM,MAAM,OAAO,GACf;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACvE;IAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAE1D,2BAA2B;AAC3B,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB,uBAAuB;IACvB,OAAO,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IAC9B,iEAAiE;IACjE,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC3C,iEAAiE;IACjE,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;CACtD;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,eAAe,GAAG,WAAW,GAAG,aAAa,CAAC;AAE1D,8BAA8B;AAC9B,MAAM,WAAW,YAAY;IAC3B,+EAA+E;IAC/E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB;;;;;;;;;OASG;IACH,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAE3C,wDAAwD;IACxD,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAErD,mEAAmE;IACnE,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;IAE/C,yCAAyC;IACzC,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAE3C;;;;;;;;;;;;;OAaG;IACH,SAAS,CAAC,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CAClD;AAED,MAAM,WAAW,MAAM;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,4EAA4E;IAC5E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;;;;;GAKG;AACH,MAAM,WAAW,kBAAmB,SAAQ,YAAY;IACtD,6DAA6D;IAC7D,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,cAAc,CAAC,UAAU,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClF,iEAAiE;IACjE,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3C,uEAAuE;IACvE,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACrE;AAED,gDAAgD;AAChD,wBAAgB,UAAU,CAAC,EAAE,EAAE,YAAY,GAAG,EAAE,IAAI,kBAAkB,CAGrE"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The contract every backend implements.
|
|
3
|
+
*
|
|
4
|
+
* Two design decisions shape all of this, and both are worth stating because
|
|
5
|
+
* the obvious alternatives are worse.
|
|
6
|
+
*
|
|
7
|
+
* **A file is modelled on `Blob`, not on `readFile`.** `size`, `slice`,
|
|
8
|
+
* `arrayBuffer`, `stream`, `text` — the same five things a `Blob` gives you.
|
|
9
|
+
* That is not imitation for its own sake: reading *part* of a file is the
|
|
10
|
+
* operation that makes remote data usable at all. An archive is read from its
|
|
11
|
+
* end, a sorted index is binary-searched, a media file is seeked. An interface
|
|
12
|
+
* whose only read is "give me the whole thing" forces a download per lookup,
|
|
13
|
+
* and every backend then grows its own private range API. Because `Blob` and
|
|
14
|
+
* `File` already satisfy this shape, the local backends need no adapter at all.
|
|
15
|
+
*
|
|
16
|
+
* **Reads only.** Writing is a separate, optional interface
|
|
17
|
+
* (`WritableFileSystem`), because two of the three backends cannot write and a
|
|
18
|
+
* combined interface would make every consumer check capabilities it does not
|
|
19
|
+
* use. A read-only filesystem is the common case and should be the plain one.
|
|
20
|
+
*/
|
|
21
|
+
/** True when a filesystem can be written to. */
|
|
22
|
+
export function isWritable(fs) {
|
|
23
|
+
const candidate = fs;
|
|
24
|
+
return typeof candidate.write === "function" && typeof candidate.makeDirectory === "function";
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAwHH,gDAAgD;AAChD,MAAM,UAAU,UAAU,CAAC,EAAgB;IACzC,MAAM,SAAS,GAAG,EAAiC,CAAC;IACpD,OAAO,OAAO,SAAS,CAAC,KAAK,KAAK,UAAU,IAAI,OAAO,SAAS,CAAC,aAAa,KAAK,UAAU,CAAC;AAChG,CAAC"}
|
package/dist/walk.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { CsDirectory, CsFile, CsFileSystem, CsStat } from "./types.js";
|
|
2
|
+
/** Walk down from a directory to the one at `path`. `null` if absent. */
|
|
3
|
+
export declare function resolveDirectory(root: CsDirectory, path: string): Promise<CsDirectory | null>;
|
|
4
|
+
/** Walk down to the file at `path`. `null` if absent or a directory. */
|
|
5
|
+
export declare function resolveFile(root: CsDirectory, path: string): Promise<CsFile | null>;
|
|
6
|
+
/**
|
|
7
|
+
* `stat` built from `entries`.
|
|
8
|
+
*
|
|
9
|
+
* Asks the parent for its listing rather than trying the file and then the
|
|
10
|
+
* directory: one round trip instead of two, which matters on a backend where
|
|
11
|
+
* each is a network request.
|
|
12
|
+
*/
|
|
13
|
+
export declare function statVia(root: CsDirectory, path: string): Promise<CsStat | null>;
|
|
14
|
+
export interface WalkEntry {
|
|
15
|
+
/** Path from the walk's root, with a leading slash. */
|
|
16
|
+
readonly path: string;
|
|
17
|
+
readonly name: string;
|
|
18
|
+
readonly kind: "file" | "directory";
|
|
19
|
+
/** `0` for directories, and for backends that do not report it in a listing. */
|
|
20
|
+
readonly size: number;
|
|
21
|
+
}
|
|
22
|
+
export interface WalkOptions {
|
|
23
|
+
/** Skip a subtree, or a file, by returning `false`. */
|
|
24
|
+
filter?: (entry: WalkEntry) => boolean;
|
|
25
|
+
/** Yield directories as well as files. Default: files only. */
|
|
26
|
+
includeDirectories?: boolean;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Every file under a directory, depth-first.
|
|
30
|
+
*
|
|
31
|
+
* A generator rather than an array: a tree can hold 228,000 files, and a
|
|
32
|
+
* caller that wants a count, or the first match, should not pay for the rest.
|
|
33
|
+
*/
|
|
34
|
+
export declare function walk(dir: CsDirectory, opts?: WalkOptions, prefix?: string): AsyncGenerator<WalkEntry>;
|
|
35
|
+
/** `walk` from a filesystem's root. */
|
|
36
|
+
export declare function walkFileSystem(fs: CsFileSystem, path?: string, opts?: WalkOptions): AsyncGenerator<WalkEntry>;
|
|
37
|
+
//# sourceMappingURL=walk.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"walk.d.ts","sourceRoot":"","sources":["../src/walk.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,WAAW,EAAW,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAErF,yEAAyE;AACzE,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,WAAW,EACjB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAO7B;AAED,wEAAwE;AACxE,wBAAsB,WAAW,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAMzF;AAED;;;;;;GAMG;AACH,wBAAsB,OAAO,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAUrF;AAED,MAAM,WAAW,SAAS;IACxB,uDAAuD;IACvD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IACpC,gFAAgF;IAChF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,WAAW;IAC1B,uDAAuD;IACvD,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,OAAO,CAAC;IACvC,+DAA+D;IAC/D,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED;;;;;GAKG;AACH,wBAAuB,IAAI,CACzB,GAAG,EAAE,WAAW,EAChB,IAAI,GAAE,WAAgB,EACtB,MAAM,SAAK,GACV,cAAc,CAAC,SAAS,CAAC,CA2B3B;AAED,uCAAuC;AACvC,wBAAuB,cAAc,CACnC,EAAE,EAAE,YAAY,EAChB,IAAI,SAAM,EACV,IAAI,GAAE,WAAgB,GACrB,cAAc,CAAC,SAAS,CAAC,CAI3B"}
|
package/dist/walk.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Walking a tree, and resolving a path through it.
|
|
3
|
+
*
|
|
4
|
+
* Every backend needs the same two things: turn `/a/b/c.txt` into a sequence
|
|
5
|
+
* of lookups, and enumerate a tree depth-first. Both are written once here,
|
|
6
|
+
* against the interface, so a backend supplies only `entries`, `file` and
|
|
7
|
+
* `directory` on a single directory.
|
|
8
|
+
*/
|
|
9
|
+
import { basename, dirname, segments } from "./path.js";
|
|
10
|
+
/** Walk down from a directory to the one at `path`. `null` if absent. */
|
|
11
|
+
export async function resolveDirectory(root, path) {
|
|
12
|
+
let current = root;
|
|
13
|
+
for (const name of segments(path)) {
|
|
14
|
+
if (!current)
|
|
15
|
+
return null;
|
|
16
|
+
current = await current.directory(name);
|
|
17
|
+
}
|
|
18
|
+
return current;
|
|
19
|
+
}
|
|
20
|
+
/** Walk down to the file at `path`. `null` if absent or a directory. */
|
|
21
|
+
export async function resolveFile(root, path) {
|
|
22
|
+
const parent = await resolveDirectory(root, dirname(path));
|
|
23
|
+
if (!parent)
|
|
24
|
+
return null;
|
|
25
|
+
const name = basename(path);
|
|
26
|
+
if (name === "")
|
|
27
|
+
return null;
|
|
28
|
+
return await parent.file(name);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* `stat` built from `entries`.
|
|
32
|
+
*
|
|
33
|
+
* Asks the parent for its listing rather than trying the file and then the
|
|
34
|
+
* directory: one round trip instead of two, which matters on a backend where
|
|
35
|
+
* each is a network request.
|
|
36
|
+
*/
|
|
37
|
+
export async function statVia(root, path) {
|
|
38
|
+
const name = basename(path);
|
|
39
|
+
if (name === "")
|
|
40
|
+
return { kind: "directory", name: "", size: 0 };
|
|
41
|
+
const parent = await resolveDirectory(root, dirname(path));
|
|
42
|
+
if (!parent)
|
|
43
|
+
return null;
|
|
44
|
+
const found = (await parent.entries()).find((e) => e.name === name);
|
|
45
|
+
if (!found)
|
|
46
|
+
return null;
|
|
47
|
+
return found.kind === "file"
|
|
48
|
+
? { kind: "file", name: found.name, size: found.size }
|
|
49
|
+
: { kind: "directory", name: found.name, size: 0 };
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Every file under a directory, depth-first.
|
|
53
|
+
*
|
|
54
|
+
* A generator rather than an array: a tree can hold 228,000 files, and a
|
|
55
|
+
* caller that wants a count, or the first match, should not pay for the rest.
|
|
56
|
+
*/
|
|
57
|
+
export async function* walk(dir, opts = {}, prefix = "") {
|
|
58
|
+
let listing;
|
|
59
|
+
try {
|
|
60
|
+
listing = await dir.entries();
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// An unreadable directory ends that branch rather than the whole walk:
|
|
64
|
+
// one permission-denied subtree should not lose the other 200,000 files.
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
for (const child of listing) {
|
|
68
|
+
const path = `${prefix}/${child.name}`;
|
|
69
|
+
const entry = child.kind === "file"
|
|
70
|
+
? { path, name: child.name, kind: "file", size: child.size }
|
|
71
|
+
: { path, name: child.name, kind: "directory", size: 0 };
|
|
72
|
+
if (opts.filter && !opts.filter(entry))
|
|
73
|
+
continue;
|
|
74
|
+
if (child.kind === "directory") {
|
|
75
|
+
if (opts.includeDirectories)
|
|
76
|
+
yield entry;
|
|
77
|
+
const sub = await dir.directory(child.name);
|
|
78
|
+
if (sub)
|
|
79
|
+
yield* walk(sub, opts, path);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
yield entry;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/** `walk` from a filesystem's root. */
|
|
87
|
+
export async function* walkFileSystem(fs, path = "/", opts = {}) {
|
|
88
|
+
const dir = await fs.directory(path);
|
|
89
|
+
if (!dir)
|
|
90
|
+
return;
|
|
91
|
+
yield* walk(dir, opts, path === "/" ? "" : path.replace(/\/$/, ""));
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=walk.js.map
|
package/dist/walk.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"walk.js","sourceRoot":"","sources":["../src/walk.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@emdzej/csfs-core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Client-side file system: the read contract, paths, and shared file implementations",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/emdzej/csfs.git",
|
|
9
|
+
"directory": "packages/core"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/emdzej/csfs/tree/main/packages/core#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/emdzej/csfs/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"filesystem",
|
|
17
|
+
"browser",
|
|
18
|
+
"zip",
|
|
19
|
+
"range-requests",
|
|
20
|
+
"opfs",
|
|
21
|
+
"file-system-access",
|
|
22
|
+
"http"
|
|
23
|
+
],
|
|
24
|
+
"type": "module",
|
|
25
|
+
"main": "dist/index.js",
|
|
26
|
+
"types": "dist/index.d.ts",
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"default": "./dist/index.js"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist"
|
|
35
|
+
],
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/node": "^22.13.1"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "tsc -b",
|
|
44
|
+
"typecheck": "tsc --noEmit",
|
|
45
|
+
"clean": "rm -rf dist .turbo *.tsbuildinfo"
|
|
46
|
+
}
|
|
47
|
+
}
|