@statewalker/webrun-files 0.7.0 → 0.9.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 +48 -11
- package/dist/cjs/index.cjs +3 -5
- package/dist/esm/index.js +2 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/types.d.ts +55 -12
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/index.ts +5 -0
- package/src/types.ts +58 -12
package/README.md
CHANGED
|
@@ -13,6 +13,7 @@ For actual filesystem implementations, install one of these packages:
|
|
|
13
13
|
- `@statewalker/webrun-files-node` - Node.js filesystem
|
|
14
14
|
- `@statewalker/webrun-files-browser` - Browser File System Access API
|
|
15
15
|
- `@statewalker/webrun-files-s3` - AWS S3 / S3-compatible storage
|
|
16
|
+
- `@statewalker/webrun-files-composite` - Mount multiple backends into a unified filesystem
|
|
16
17
|
|
|
17
18
|
## The FilesApi Interface
|
|
18
19
|
|
|
@@ -110,7 +111,7 @@ await files.write('/large.bin', generateChunks());
|
|
|
110
111
|
```typescript
|
|
111
112
|
import { normalizePath, joinPath, dirname, basename, extname } from '@statewalker/webrun-files';
|
|
112
113
|
|
|
113
|
-
normalizePath('//foo
|
|
114
|
+
normalizePath('//foo/./bar//baz/'); // '/foo/bar/baz'
|
|
114
115
|
joinPath('/foo', 'bar', 'baz.txt'); // '/foo/bar/baz.txt'
|
|
115
116
|
dirname('/foo/bar/baz.txt'); // '/foo/bar'
|
|
116
117
|
basename('/foo/bar/baz.txt'); // 'baz.txt'
|
|
@@ -129,21 +130,43 @@ if (await files.exists('/config.json')) {
|
|
|
129
130
|
|
|
130
131
|
### Getting Metadata
|
|
131
132
|
|
|
133
|
+
`FileStats` is a discriminated union on `kind`. A file always reports a `size`
|
|
134
|
+
and a `lastModified`; a directory reports neither, because a directory has no
|
|
135
|
+
size and a modification time for one is unavailable on stores where a directory
|
|
136
|
+
is only a key prefix. Narrow on `kind` and the fields for that kind are then
|
|
137
|
+
known to be present:
|
|
138
|
+
|
|
132
139
|
```typescript
|
|
133
140
|
const info = await files.stats('/photo.jpg');
|
|
134
|
-
if (info) {
|
|
135
|
-
console.log(`Size: ${info.size} bytes`);
|
|
141
|
+
if (info?.kind === 'file') {
|
|
142
|
+
console.log(`Size: ${info.size} bytes`); // number, never undefined
|
|
136
143
|
console.log(`Modified: ${new Date(info.lastModified)}`);
|
|
137
|
-
|
|
144
|
+
} else if (info?.kind === 'directory') {
|
|
145
|
+
console.log('a directory'); // nothing else to report
|
|
138
146
|
}
|
|
139
147
|
```
|
|
140
148
|
|
|
149
|
+
A zero-byte file is the file variant with `size: 0`, so test the `kind` rather
|
|
150
|
+
than the truthiness of `size`:
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
// WRONG - reads an empty file as though it had no size
|
|
154
|
+
if (info.size) { /* ... */ }
|
|
155
|
+
|
|
156
|
+
// RIGHT
|
|
157
|
+
if (info.kind === 'file') { /* info.size may legitimately be 0 */ }
|
|
158
|
+
```
|
|
159
|
+
|
|
141
160
|
### Listing Directories
|
|
142
161
|
|
|
162
|
+
`FileInfo` is the same union plus `name` and `path`, so a listing narrows per
|
|
163
|
+
entry exactly as `stats()` does:
|
|
164
|
+
|
|
143
165
|
```typescript
|
|
144
166
|
// List direct children
|
|
145
167
|
for await (const entry of files.list('/documents')) {
|
|
146
|
-
|
|
168
|
+
const size = entry.kind === 'file' ? `${entry.size} bytes` : '';
|
|
169
|
+
console.log(`${entry.name} (${entry.kind}) ${size}`);
|
|
147
170
|
}
|
|
148
171
|
|
|
149
172
|
// List recursively
|
|
@@ -165,15 +188,29 @@ await files.remove('/old-stuff'); // Recursively deletes directories
|
|
|
165
188
|
|
|
166
189
|
```typescript
|
|
167
190
|
import type {
|
|
168
|
-
FilesApi,
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
191
|
+
FilesApi, // Core interface for backends
|
|
192
|
+
FileStats, // Metadata from stats(): FileEntryStats | DirectoryEntryStats
|
|
193
|
+
FileEntryStats, // { kind: "file", size: number, lastModified: number }
|
|
194
|
+
DirectoryEntryStats, // { kind: "directory" }
|
|
195
|
+
FileInfo, // Metadata from list(): FileEntryInfo | DirectoryEntryInfo
|
|
196
|
+
FileEntryInfo, // FileEntryStats & { name, path }
|
|
197
|
+
DirectoryEntryInfo, // DirectoryEntryStats & { name, path }
|
|
198
|
+
FileEntryLocation, // { name: string, path: string }
|
|
199
|
+
FileKind, // "file" | "directory"
|
|
200
|
+
ReadOptions, // { start?: number, length?: number, signal?: AbortSignal }
|
|
201
|
+
ListOptions, // { recursive?: boolean }
|
|
174
202
|
} from '@statewalker/webrun-files';
|
|
175
203
|
```
|
|
176
204
|
|
|
205
|
+
### Implementing a backend
|
|
206
|
+
|
|
207
|
+
An implementation must return **exactly** one variant: a file with both numbers
|
|
208
|
+
present, a directory with nothing but its `kind`. An implementation that knows a
|
|
209
|
+
directory's modification time drops it rather than offering a value that would
|
|
210
|
+
be present on one backend and missing on the next. The parametrized suite in
|
|
211
|
+
`@statewalker/webrun-files-tests` checks this at runtime, and
|
|
212
|
+
`createFilesApiTests` runs it for you.
|
|
213
|
+
|
|
177
214
|
## License
|
|
178
215
|
|
|
179
216
|
MIT
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
//#region src/file-utils.ts
|
|
3
3
|
/**
|
|
4
4
|
* Collect async iterable chunks into a single Uint8Array.
|
|
@@ -10,7 +10,7 @@ async function collectChunks(chunks) {
|
|
|
10
10
|
parts.push(chunk);
|
|
11
11
|
totalLength += chunk.length;
|
|
12
12
|
}
|
|
13
|
-
if (parts.length === 0) return new Uint8Array(0);
|
|
13
|
+
if (parts.length === 0) return /* @__PURE__ */ new Uint8Array(0);
|
|
14
14
|
if (parts.length === 1) return parts[0];
|
|
15
15
|
const result = new Uint8Array(totalLength);
|
|
16
16
|
let offset = 0;
|
|
@@ -88,7 +88,6 @@ async function readAt(files, path, buffer, bufferOffset, length, position) {
|
|
|
88
88
|
async function writeText(files, path, content) {
|
|
89
89
|
await files.write(path, [new TextEncoder().encode(content)]);
|
|
90
90
|
}
|
|
91
|
-
|
|
92
91
|
//#endregion
|
|
93
92
|
//#region src/path-utils.ts
|
|
94
93
|
/**
|
|
@@ -141,7 +140,6 @@ function extname(path) {
|
|
|
141
140
|
if (dotIndex <= 0) return "";
|
|
142
141
|
return name.substring(dotIndex);
|
|
143
142
|
}
|
|
144
|
-
|
|
145
143
|
//#endregion
|
|
146
144
|
exports.basename = basename;
|
|
147
145
|
exports.dirname = dirname;
|
|
@@ -154,4 +152,4 @@ exports.readRange = readRange;
|
|
|
154
152
|
exports.readText = readText;
|
|
155
153
|
exports.tryReadFile = tryReadFile;
|
|
156
154
|
exports.tryReadText = tryReadText;
|
|
157
|
-
exports.writeText = writeText;
|
|
155
|
+
exports.writeText = writeText;
|
package/dist/esm/index.js
CHANGED
|
@@ -9,7 +9,7 @@ async function collectChunks(chunks) {
|
|
|
9
9
|
parts.push(chunk);
|
|
10
10
|
totalLength += chunk.length;
|
|
11
11
|
}
|
|
12
|
-
if (parts.length === 0) return new Uint8Array(0);
|
|
12
|
+
if (parts.length === 0) return /* @__PURE__ */ new Uint8Array(0);
|
|
13
13
|
if (parts.length === 1) return parts[0];
|
|
14
14
|
const result = new Uint8Array(totalLength);
|
|
15
15
|
let offset = 0;
|
|
@@ -87,7 +87,6 @@ async function readAt(files, path, buffer, bufferOffset, length, position) {
|
|
|
87
87
|
async function writeText(files, path, content) {
|
|
88
88
|
await files.write(path, [new TextEncoder().encode(content)]);
|
|
89
89
|
}
|
|
90
|
-
|
|
91
90
|
//#endregion
|
|
92
91
|
//#region src/path-utils.ts
|
|
93
92
|
/**
|
|
@@ -140,6 +139,5 @@ function extname(path) {
|
|
|
140
139
|
if (dotIndex <= 0) return "";
|
|
141
140
|
return name.substring(dotIndex);
|
|
142
141
|
}
|
|
143
|
-
|
|
144
142
|
//#endregion
|
|
145
|
-
export { basename, dirname, extname, joinPath, normalizePath, readAt, readFile, readRange, readText, tryReadFile, tryReadText, writeText };
|
|
143
|
+
export { basename, dirname, extname, joinPath, normalizePath, readAt, readFile, readRange, readText, tryReadFile, tryReadText, writeText };
|
package/dist/index.d.ts
CHANGED
|
@@ -14,5 +14,5 @@
|
|
|
14
14
|
*/
|
|
15
15
|
export { readAt, readFile, readRange, readText, tryReadFile, tryReadText, writeText, } from "./file-utils.js";
|
|
16
16
|
export { basename, dirname, extname, joinPath, normalizePath, } from "./path-utils.js";
|
|
17
|
-
export type { FileInfo, FileKind, FileStats, FilesApi, ListOptions, ReadOptions, } from "./types.js";
|
|
17
|
+
export type { DirectoryEntryInfo, DirectoryEntryStats, FileEntryInfo, FileEntryLocation, FileEntryStats, FileInfo, FileKind, FileStats, FilesApi, ListOptions, ReadOptions, } from "./types.js";
|
|
18
18
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,EACL,MAAM,EACN,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,WAAW,EACX,WAAW,EACX,SAAS,GACV,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,QAAQ,EACR,OAAO,EACP,OAAO,EACP,QAAQ,EACR,aAAa,GACd,MAAM,iBAAiB,CAAC;AAEzB,YAAY,EACV,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,WAAW,EACX,WAAW,GACZ,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,EACL,MAAM,EACN,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,WAAW,EACX,WAAW,EACX,SAAS,GACV,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EACL,QAAQ,EACR,OAAO,EACP,OAAO,EACP,QAAQ,EACR,aAAa,GACd,MAAM,iBAAiB,CAAC;AAEzB,YAAY,EACV,kBAAkB,EAClB,mBAAmB,EACnB,aAAa,EACb,iBAAiB,EACjB,cAAc,EACd,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,WAAW,EACX,WAAW,GACZ,MAAM,YAAY,CAAC"}
|
package/dist/types.d.ts
CHANGED
|
@@ -29,25 +29,68 @@ export interface ListOptions {
|
|
|
29
29
|
recursive?: boolean;
|
|
30
30
|
}
|
|
31
31
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
32
|
+
* Metadata of a regular file.
|
|
33
|
+
*
|
|
34
|
+
* `size` and `lastModified` are REQUIRED. Every storage that can hold a file
|
|
35
|
+
* can say how large it is and when it last changed; an implementation that
|
|
36
|
+
* cannot is claiming less than it must, and the omission must surface as a
|
|
37
|
+
* failure rather than as an `undefined` a consumer has to defend against.
|
|
34
38
|
*/
|
|
35
|
-
export interface
|
|
36
|
-
kind:
|
|
37
|
-
|
|
38
|
-
|
|
39
|
+
export interface FileEntryStats {
|
|
40
|
+
kind: "file";
|
|
41
|
+
/** Length in bytes. A zero-byte file is `0`, never `undefined`. */
|
|
42
|
+
size: number;
|
|
43
|
+
/** Modification time as milliseconds since the epoch. */
|
|
44
|
+
lastModified: number;
|
|
39
45
|
}
|
|
40
46
|
/**
|
|
41
|
-
*
|
|
42
|
-
*
|
|
47
|
+
* Metadata of a directory.
|
|
48
|
+
*
|
|
49
|
+
* Deliberately carries nothing else. A directory has no size, and a
|
|
50
|
+
* modification time is available on some storages (memory, a local
|
|
51
|
+
* filesystem) and unavailable on others (an object store, where a directory
|
|
52
|
+
* is a common prefix that exists only as an artefact of key naming). Neither
|
|
53
|
+
* value may be relied upon, so neither is part of the contract: an
|
|
54
|
+
* implementation that happens to know one drops it here instead of leaking a
|
|
55
|
+
* value consumers would have to treat as optional anyway.
|
|
43
56
|
*/
|
|
44
|
-
export interface
|
|
57
|
+
export interface DirectoryEntryStats {
|
|
58
|
+
kind: "directory";
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* File or directory metadata returned by `stats()`.
|
|
62
|
+
*
|
|
63
|
+
* A DISCRIMINATED UNION on `kind`, not a single shape with optional fields.
|
|
64
|
+
* The optionality this replaces never expressed what a given implementation
|
|
65
|
+
* could report — it expressed a property of the entry kind, collapsed into
|
|
66
|
+
* one type because `kind` was an ordinary field. Narrow on `kind` and the
|
|
67
|
+
* fields that exist for that kind are then known to be present:
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* const stats = await api.stats("/some/path");
|
|
71
|
+
* if (stats?.kind === "file") {
|
|
72
|
+
* console.log(stats.size, stats.lastModified); // both `number`
|
|
73
|
+
* }
|
|
74
|
+
*/
|
|
75
|
+
export type FileStats = FileEntryStats | DirectoryEntryStats;
|
|
76
|
+
/** Name and path carried by every entry a `list()` yields. */
|
|
77
|
+
export interface FileEntryLocation {
|
|
45
78
|
name: string;
|
|
46
79
|
path: string;
|
|
47
|
-
kind: FileKind;
|
|
48
|
-
size?: number;
|
|
49
|
-
lastModified?: number;
|
|
50
80
|
}
|
|
81
|
+
/** A file yielded by `list()`. */
|
|
82
|
+
export interface FileEntryInfo extends FileEntryStats, FileEntryLocation {
|
|
83
|
+
}
|
|
84
|
+
/** A directory yielded by `list()`. */
|
|
85
|
+
export interface DirectoryEntryInfo extends DirectoryEntryStats, FileEntryLocation {
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* File or directory information returned by `list()`.
|
|
89
|
+
*
|
|
90
|
+
* The same union as {@link FileStats}, plus `name` and `path`, so a listing
|
|
91
|
+
* consumer narrows per entry exactly as a `stats()` consumer does.
|
|
92
|
+
*/
|
|
93
|
+
export type FileInfo = FileEntryInfo | DirectoryEntryInfo;
|
|
51
94
|
/**
|
|
52
95
|
* Cross-platform filesystem abstraction interface.
|
|
53
96
|
*
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC;AAE5C;;;;;;;;;;GAUG;AACH,MAAM,WAAW,WAAW;IAC1B,yDAAyD;IACzD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iEAAiE;IACjE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4CAA4C;IAC5C,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,qEAAqE;IACrE,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC;AAE5C;;;;;;;;;;GAUG;AACH,MAAM,WAAW,WAAW;IAC1B,yDAAyD;IACzD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iEAAiE;IACjE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4CAA4C;IAC5C,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,qEAAqE;IACrE,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,mEAAmE;IACnE,IAAI,EAAE,MAAM,CAAC;IACb,yDAAyD;IACzD,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,WAAW,CAAC;CACnB;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,SAAS,GAAG,cAAc,GAAG,mBAAmB,CAAC;AAE7D,8DAA8D;AAC9D,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,kCAAkC;AAClC,MAAM,WAAW,aAAc,SAAQ,cAAc,EAAE,iBAAiB;CAAG;AAE3E,uCAAuC;AACvC,MAAM,WAAW,kBAAmB,SAAQ,mBAAmB,EAAE,iBAAiB;CAAG;AAErF;;;;;GAKG;AACH,MAAM,MAAM,QAAQ,GAAG,aAAa,GAAG,kBAAkB,CAAC;AAE1D;;;;;GAKG;AACH,MAAM,WAAW,QAAQ;IACvB;;;OAGG;IACH,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,aAAa,CAAC,UAAU,CAAC,CAAC;IAErE;;;OAGG;IACH,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,aAAa,CAAC,UAAU,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE9F;;;OAGG;IACH,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEnC;;;OAGG;IACH,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;IAEnE;;;OAGG;IACH,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC,CAAC;IAEpD;;OAEG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAEvC;;;OAGG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAEvC;;;OAGG;IACH,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAEvD;;;OAGG;IACH,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACxD"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@statewalker/webrun-files",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "FilesApi interface and utilities for cross-platform filesystem abstraction",
|
|
@@ -29,10 +29,10 @@
|
|
|
29
29
|
"src"
|
|
30
30
|
],
|
|
31
31
|
"devDependencies": {
|
|
32
|
-
"@types/node": "^
|
|
33
|
-
"rimraf": "^6.1.
|
|
34
|
-
"rolldown": "^1.
|
|
35
|
-
"typescript": "^
|
|
32
|
+
"@types/node": "^26.2.0",
|
|
33
|
+
"rimraf": "^6.1.3",
|
|
34
|
+
"rolldown": "^1.2.4",
|
|
35
|
+
"typescript": "^7.0.2"
|
|
36
36
|
},
|
|
37
37
|
"sideEffects": false,
|
|
38
38
|
"publishConfig": {
|
package/src/index.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -33,27 +33,73 @@ export interface ListOptions {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
36
|
+
* Metadata of a regular file.
|
|
37
|
+
*
|
|
38
|
+
* `size` and `lastModified` are REQUIRED. Every storage that can hold a file
|
|
39
|
+
* can say how large it is and when it last changed; an implementation that
|
|
40
|
+
* cannot is claiming less than it must, and the omission must surface as a
|
|
41
|
+
* failure rather than as an `undefined` a consumer has to defend against.
|
|
42
|
+
*/
|
|
43
|
+
export interface FileEntryStats {
|
|
44
|
+
kind: "file";
|
|
45
|
+
/** Length in bytes. A zero-byte file is `0`, never `undefined`. */
|
|
46
|
+
size: number;
|
|
47
|
+
/** Modification time as milliseconds since the epoch. */
|
|
48
|
+
lastModified: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Metadata of a directory.
|
|
53
|
+
*
|
|
54
|
+
* Deliberately carries nothing else. A directory has no size, and a
|
|
55
|
+
* modification time is available on some storages (memory, a local
|
|
56
|
+
* filesystem) and unavailable on others (an object store, where a directory
|
|
57
|
+
* is a common prefix that exists only as an artefact of key naming). Neither
|
|
58
|
+
* value may be relied upon, so neither is part of the contract: an
|
|
59
|
+
* implementation that happens to know one drops it here instead of leaking a
|
|
60
|
+
* value consumers would have to treat as optional anyway.
|
|
38
61
|
*/
|
|
39
|
-
export interface
|
|
40
|
-
kind:
|
|
41
|
-
size?: number;
|
|
42
|
-
lastModified?: number;
|
|
62
|
+
export interface DirectoryEntryStats {
|
|
63
|
+
kind: "directory";
|
|
43
64
|
}
|
|
44
65
|
|
|
45
66
|
/**
|
|
46
|
-
* File or directory
|
|
47
|
-
*
|
|
67
|
+
* File or directory metadata returned by `stats()`.
|
|
68
|
+
*
|
|
69
|
+
* A DISCRIMINATED UNION on `kind`, not a single shape with optional fields.
|
|
70
|
+
* The optionality this replaces never expressed what a given implementation
|
|
71
|
+
* could report — it expressed a property of the entry kind, collapsed into
|
|
72
|
+
* one type because `kind` was an ordinary field. Narrow on `kind` and the
|
|
73
|
+
* fields that exist for that kind are then known to be present:
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* const stats = await api.stats("/some/path");
|
|
77
|
+
* if (stats?.kind === "file") {
|
|
78
|
+
* console.log(stats.size, stats.lastModified); // both `number`
|
|
79
|
+
* }
|
|
48
80
|
*/
|
|
49
|
-
export
|
|
81
|
+
export type FileStats = FileEntryStats | DirectoryEntryStats;
|
|
82
|
+
|
|
83
|
+
/** Name and path carried by every entry a `list()` yields. */
|
|
84
|
+
export interface FileEntryLocation {
|
|
50
85
|
name: string;
|
|
51
86
|
path: string;
|
|
52
|
-
kind: FileKind;
|
|
53
|
-
size?: number;
|
|
54
|
-
lastModified?: number;
|
|
55
87
|
}
|
|
56
88
|
|
|
89
|
+
/** A file yielded by `list()`. */
|
|
90
|
+
export interface FileEntryInfo extends FileEntryStats, FileEntryLocation {}
|
|
91
|
+
|
|
92
|
+
/** A directory yielded by `list()`. */
|
|
93
|
+
export interface DirectoryEntryInfo extends DirectoryEntryStats, FileEntryLocation {}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* File or directory information returned by `list()`.
|
|
97
|
+
*
|
|
98
|
+
* The same union as {@link FileStats}, plus `name` and `path`, so a listing
|
|
99
|
+
* consumer narrows per entry exactly as a `stats()` consumer does.
|
|
100
|
+
*/
|
|
101
|
+
export type FileInfo = FileEntryInfo | DirectoryEntryInfo;
|
|
102
|
+
|
|
57
103
|
/**
|
|
58
104
|
* Cross-platform filesystem abstraction interface.
|
|
59
105
|
*
|