sliftutils 1.4.11 → 1.4.12
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/index.d.ts +12 -28
- package/package.json +1 -1
- package/storage/FileFolderAPI.d.ts +12 -28
- package/storage/FileFolderAPI.tsx +20 -31
- package/storage/remoteFileStorage.ts +21 -8
package/index.d.ts
CHANGED
|
@@ -1343,6 +1343,8 @@ declare module "sliftutils/storage/FileFolderAPI" {
|
|
|
1343
1343
|
}
|
|
1344
1344
|
}
|
|
1345
1345
|
export type FileWrapper = {
|
|
1346
|
+
readonly kind: "file";
|
|
1347
|
+
readonly name: string;
|
|
1346
1348
|
getFile(): Promise<{
|
|
1347
1349
|
size: number;
|
|
1348
1350
|
lastModified: number;
|
|
@@ -1360,6 +1362,8 @@ declare module "sliftutils/storage/FileFolderAPI" {
|
|
|
1360
1362
|
}>;
|
|
1361
1363
|
};
|
|
1362
1364
|
export type DirectoryWrapper = {
|
|
1365
|
+
readonly kind: "directory";
|
|
1366
|
+
readonly name: string;
|
|
1363
1367
|
removeEntry(key: string, options?: {
|
|
1364
1368
|
recursive?: boolean;
|
|
1365
1369
|
}): Promise<void>;
|
|
@@ -1369,25 +1373,15 @@ declare module "sliftutils/storage/FileFolderAPI" {
|
|
|
1369
1373
|
getDirectoryHandle(key: string, options?: {
|
|
1370
1374
|
create?: boolean;
|
|
1371
1375
|
}): Promise<DirectoryWrapper>;
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
{
|
|
1375
|
-
kind: "file";
|
|
1376
|
-
name: string;
|
|
1377
|
-
getFile(): Promise<FileWrapper>;
|
|
1378
|
-
} | {
|
|
1379
|
-
kind: "directory";
|
|
1380
|
-
name: string;
|
|
1381
|
-
getDirectoryHandle(key: string, options?: {
|
|
1382
|
-
create?: boolean;
|
|
1383
|
-
}): Promise<DirectoryWrapper>;
|
|
1384
|
-
}
|
|
1385
|
-
]>;
|
|
1376
|
+
entries(): AsyncIterableIterator<[string, FileWrapper | DirectoryWrapper]>;
|
|
1377
|
+
[Symbol.asyncIterator](): AsyncIterableIterator<[string, FileWrapper | DirectoryWrapper]>;
|
|
1386
1378
|
};
|
|
1387
1379
|
export declare function setFileAPIKey(key: string): void;
|
|
1388
1380
|
export declare class NodeJSFileHandleWrapper implements FileWrapper {
|
|
1389
1381
|
private filePath;
|
|
1390
1382
|
constructor(filePath: string);
|
|
1383
|
+
readonly kind: "file";
|
|
1384
|
+
get name(): string;
|
|
1391
1385
|
getFile(): Promise<{
|
|
1392
1386
|
size: number;
|
|
1393
1387
|
lastModified: number;
|
|
@@ -1407,6 +1401,9 @@ declare module "sliftutils/storage/FileFolderAPI" {
|
|
|
1407
1401
|
export declare class NodeJSDirectoryHandleWrapper implements DirectoryWrapper {
|
|
1408
1402
|
private rootPath;
|
|
1409
1403
|
constructor(rootPath: string);
|
|
1404
|
+
readonly kind: "directory";
|
|
1405
|
+
get name(): string;
|
|
1406
|
+
entries(): AsyncIterableIterator<[string, FileWrapper | DirectoryWrapper]>;
|
|
1410
1407
|
removeEntry(key: string, options?: {
|
|
1411
1408
|
recursive?: boolean;
|
|
1412
1409
|
}): Promise<void>;
|
|
@@ -1416,20 +1413,7 @@ declare module "sliftutils/storage/FileFolderAPI" {
|
|
|
1416
1413
|
getDirectoryHandle(key: string, options?: {
|
|
1417
1414
|
create?: boolean;
|
|
1418
1415
|
}): Promise<DirectoryWrapper>;
|
|
1419
|
-
[Symbol.asyncIterator](): AsyncIterableIterator<[
|
|
1420
|
-
string,
|
|
1421
|
-
{
|
|
1422
|
-
kind: "file";
|
|
1423
|
-
name: string;
|
|
1424
|
-
getFile(): Promise<FileWrapper>;
|
|
1425
|
-
} | {
|
|
1426
|
-
kind: "directory";
|
|
1427
|
-
name: string;
|
|
1428
|
-
getDirectoryHandle(key: string, options?: {
|
|
1429
|
-
create?: boolean;
|
|
1430
|
-
}): Promise<DirectoryWrapper>;
|
|
1431
|
-
}
|
|
1432
|
-
]>;
|
|
1416
|
+
[Symbol.asyncIterator](): AsyncIterableIterator<[string, FileWrapper | DirectoryWrapper]>;
|
|
1433
1417
|
}
|
|
1434
1418
|
export declare const getDirectoryHandle: {
|
|
1435
1419
|
(): Promise<DirectoryWrapper>;
|
package/package.json
CHANGED
|
@@ -29,6 +29,8 @@ declare global {
|
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
export type FileWrapper = {
|
|
32
|
+
readonly kind: "file";
|
|
33
|
+
readonly name: string;
|
|
32
34
|
getFile(): Promise<{
|
|
33
35
|
size: number;
|
|
34
36
|
lastModified: number;
|
|
@@ -46,6 +48,8 @@ export type FileWrapper = {
|
|
|
46
48
|
}>;
|
|
47
49
|
};
|
|
48
50
|
export type DirectoryWrapper = {
|
|
51
|
+
readonly kind: "directory";
|
|
52
|
+
readonly name: string;
|
|
49
53
|
removeEntry(key: string, options?: {
|
|
50
54
|
recursive?: boolean;
|
|
51
55
|
}): Promise<void>;
|
|
@@ -55,25 +59,15 @@ export type DirectoryWrapper = {
|
|
|
55
59
|
getDirectoryHandle(key: string, options?: {
|
|
56
60
|
create?: boolean;
|
|
57
61
|
}): Promise<DirectoryWrapper>;
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
{
|
|
61
|
-
kind: "file";
|
|
62
|
-
name: string;
|
|
63
|
-
getFile(): Promise<FileWrapper>;
|
|
64
|
-
} | {
|
|
65
|
-
kind: "directory";
|
|
66
|
-
name: string;
|
|
67
|
-
getDirectoryHandle(key: string, options?: {
|
|
68
|
-
create?: boolean;
|
|
69
|
-
}): Promise<DirectoryWrapper>;
|
|
70
|
-
}
|
|
71
|
-
]>;
|
|
62
|
+
entries(): AsyncIterableIterator<[string, FileWrapper | DirectoryWrapper]>;
|
|
63
|
+
[Symbol.asyncIterator](): AsyncIterableIterator<[string, FileWrapper | DirectoryWrapper]>;
|
|
72
64
|
};
|
|
73
65
|
export declare function setFileAPIKey(key: string): void;
|
|
74
66
|
export declare class NodeJSFileHandleWrapper implements FileWrapper {
|
|
75
67
|
private filePath;
|
|
76
68
|
constructor(filePath: string);
|
|
69
|
+
readonly kind: "file";
|
|
70
|
+
get name(): string;
|
|
77
71
|
getFile(): Promise<{
|
|
78
72
|
size: number;
|
|
79
73
|
lastModified: number;
|
|
@@ -93,6 +87,9 @@ export declare class NodeJSFileHandleWrapper implements FileWrapper {
|
|
|
93
87
|
export declare class NodeJSDirectoryHandleWrapper implements DirectoryWrapper {
|
|
94
88
|
private rootPath;
|
|
95
89
|
constructor(rootPath: string);
|
|
90
|
+
readonly kind: "directory";
|
|
91
|
+
get name(): string;
|
|
92
|
+
entries(): AsyncIterableIterator<[string, FileWrapper | DirectoryWrapper]>;
|
|
96
93
|
removeEntry(key: string, options?: {
|
|
97
94
|
recursive?: boolean;
|
|
98
95
|
}): Promise<void>;
|
|
@@ -102,20 +99,7 @@ export declare class NodeJSDirectoryHandleWrapper implements DirectoryWrapper {
|
|
|
102
99
|
getDirectoryHandle(key: string, options?: {
|
|
103
100
|
create?: boolean;
|
|
104
101
|
}): Promise<DirectoryWrapper>;
|
|
105
|
-
[Symbol.asyncIterator](): AsyncIterableIterator<[
|
|
106
|
-
string,
|
|
107
|
-
{
|
|
108
|
-
kind: "file";
|
|
109
|
-
name: string;
|
|
110
|
-
getFile(): Promise<FileWrapper>;
|
|
111
|
-
} | {
|
|
112
|
-
kind: "directory";
|
|
113
|
-
name: string;
|
|
114
|
-
getDirectoryHandle(key: string, options?: {
|
|
115
|
-
create?: boolean;
|
|
116
|
-
}): Promise<DirectoryWrapper>;
|
|
117
|
-
}
|
|
118
|
-
]>;
|
|
102
|
+
[Symbol.asyncIterator](): AsyncIterableIterator<[string, FileWrapper | DirectoryWrapper]>;
|
|
119
103
|
}
|
|
120
104
|
export declare const getDirectoryHandle: {
|
|
121
105
|
(): Promise<DirectoryWrapper>;
|
|
@@ -36,7 +36,13 @@ declare global {
|
|
|
36
36
|
// DO NOT enable this is isNode
|
|
37
37
|
const USE_INDEXED_DB = false;
|
|
38
38
|
|
|
39
|
+
// These mirror the subset of the native FileSystemFileHandle / FileSystemDirectoryHandle API we use, so
|
|
40
|
+
// the native browser handles, the Node handles, and the remote handles are all interchangeable — and
|
|
41
|
+
// code written against the native handle (e.g. a recursive walk over `handle.entries()`) works on any of
|
|
42
|
+
// them. kind/name and entries() are part of that contract.
|
|
39
43
|
export type FileWrapper = {
|
|
44
|
+
readonly kind: "file";
|
|
45
|
+
readonly name: string;
|
|
40
46
|
getFile(): Promise<{
|
|
41
47
|
size: number;
|
|
42
48
|
lastModified: number;
|
|
@@ -52,18 +58,14 @@ export type FileWrapper = {
|
|
|
52
58
|
}>;
|
|
53
59
|
};
|
|
54
60
|
export type DirectoryWrapper = {
|
|
61
|
+
readonly kind: "directory";
|
|
62
|
+
readonly name: string;
|
|
55
63
|
removeEntry(key: string, options?: { recursive?: boolean }): Promise<void>;
|
|
56
64
|
getFileHandle(key: string, options?: { create?: boolean }): Promise<FileWrapper>;
|
|
57
65
|
getDirectoryHandle(key: string, options?: { create?: boolean }): Promise<DirectoryWrapper>;
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
getFile(): Promise<FileWrapper>;
|
|
62
|
-
} | {
|
|
63
|
-
kind: "directory";
|
|
64
|
-
name: string;
|
|
65
|
-
getDirectoryHandle(key: string, options?: { create?: boolean }): Promise<DirectoryWrapper>;
|
|
66
|
-
}]>;
|
|
66
|
+
// Each entry IS a handle (file or directory), so a recursive walk keeps working at every level.
|
|
67
|
+
entries(): AsyncIterableIterator<[string, FileWrapper | DirectoryWrapper]>;
|
|
68
|
+
[Symbol.asyncIterator](): AsyncIterableIterator<[string, FileWrapper | DirectoryWrapper]>;
|
|
67
69
|
};
|
|
68
70
|
|
|
69
71
|
let displayData = observable({
|
|
@@ -222,6 +224,8 @@ class ServerConnectForm extends preact.Component<{ onConnected: (config: RemoteC
|
|
|
222
224
|
export class NodeJSFileHandleWrapper implements FileWrapper {
|
|
223
225
|
constructor(private filePath: string) {
|
|
224
226
|
}
|
|
227
|
+
readonly kind = "file" as const;
|
|
228
|
+
get name() { return path.basename(this.filePath); }
|
|
225
229
|
|
|
226
230
|
async getFile() {
|
|
227
231
|
const stats = await fs.promises.stat(this.filePath);
|
|
@@ -285,6 +289,9 @@ export class NodeJSFileHandleWrapper implements FileWrapper {
|
|
|
285
289
|
export class NodeJSDirectoryHandleWrapper implements DirectoryWrapper {
|
|
286
290
|
constructor(private rootPath: string) {
|
|
287
291
|
}
|
|
292
|
+
readonly kind = "directory" as const;
|
|
293
|
+
get name() { return path.basename(this.rootPath); }
|
|
294
|
+
entries() { return this[Symbol.asyncIterator](); }
|
|
288
295
|
|
|
289
296
|
async removeEntry(key: string, options?: { recursive?: boolean }) {
|
|
290
297
|
const entryPath = path.join(this.rootPath, key);
|
|
@@ -332,36 +339,18 @@ export class NodeJSDirectoryHandleWrapper implements DirectoryWrapper {
|
|
|
332
339
|
return new NodeJSDirectoryHandleWrapper(dirPath);
|
|
333
340
|
}
|
|
334
341
|
|
|
335
|
-
async *[Symbol.asyncIterator](): AsyncIterableIterator<[string, {
|
|
336
|
-
kind: "file";
|
|
337
|
-
name: string;
|
|
338
|
-
getFile(): Promise<FileWrapper>;
|
|
339
|
-
} | {
|
|
340
|
-
kind: "directory";
|
|
341
|
-
name: string;
|
|
342
|
-
getDirectoryHandle(key: string, options?: { create?: boolean }): Promise<DirectoryWrapper>;
|
|
343
|
-
}]> {
|
|
342
|
+
async *[Symbol.asyncIterator](): AsyncIterableIterator<[string, FileWrapper | DirectoryWrapper]> {
|
|
344
343
|
// Ensure directory exists
|
|
345
344
|
await fs.promises.mkdir(this.rootPath, { recursive: true });
|
|
346
345
|
|
|
347
346
|
const entries = await fs.promises.readdir(this.rootPath, { withFileTypes: true });
|
|
348
347
|
|
|
349
348
|
for (const entry of entries) {
|
|
349
|
+
const childPath = path.join(this.rootPath, entry.name);
|
|
350
350
|
if (entry.isFile()) {
|
|
351
|
-
yield [entry.name,
|
|
352
|
-
kind: "file",
|
|
353
|
-
name: entry.name,
|
|
354
|
-
getFile: async () => new NodeJSFileHandleWrapper(path.join(this.rootPath, entry.name))
|
|
355
|
-
}];
|
|
351
|
+
yield [entry.name, new NodeJSFileHandleWrapper(childPath)];
|
|
356
352
|
} else if (entry.isDirectory()) {
|
|
357
|
-
|
|
358
|
-
yield [entry.name, {
|
|
359
|
-
kind: "directory",
|
|
360
|
-
name: entry.name,
|
|
361
|
-
getDirectoryHandle: async (key: string, options?: { create?: boolean }) => {
|
|
362
|
-
return new NodeJSDirectoryHandleWrapper(dirPath).getDirectoryHandle(key, options);
|
|
363
|
-
}
|
|
364
|
-
}];
|
|
353
|
+
yield [entry.name, new NodeJSDirectoryHandleWrapper(childPath)];
|
|
365
354
|
}
|
|
366
355
|
}
|
|
367
356
|
}
|
|
@@ -184,11 +184,15 @@ class RangeCache {
|
|
|
184
184
|
}
|
|
185
185
|
|
|
186
186
|
const joinPath = (base: string, key: string) => (base ? base + "/" + key : key);
|
|
187
|
+
const baseName = (p: string) => p.split("/").filter(Boolean).pop() || "";
|
|
187
188
|
|
|
188
189
|
class RemoteFileWrapper implements FileWrapper {
|
|
189
190
|
// `stat` is supplied when the parent already statted us (avoids a second /info). `createIntent` means
|
|
190
191
|
// this was opened with create:true, so a missing file reads as empty (it'll be created on write).
|
|
191
192
|
constructor(private conn: Connection, private filePath: string, private stat?: Stat, private createIntent = false) { }
|
|
193
|
+
// Mirror the native FileSystemFileHandle shape so code written against it works unchanged.
|
|
194
|
+
readonly kind = "file" as const;
|
|
195
|
+
get name() { return baseName(this.filePath); }
|
|
192
196
|
async getFile() {
|
|
193
197
|
let stat = this.stat ?? await this.conn.stat(this.filePath);
|
|
194
198
|
if (!stat) {
|
|
@@ -220,17 +224,21 @@ class RemoteFileWrapper implements FileWrapper {
|
|
|
220
224
|
|
|
221
225
|
class RemoteDirectoryWrapper implements DirectoryWrapper {
|
|
222
226
|
constructor(private conn: Connection, private dirPath: string) { }
|
|
227
|
+
// Mirror the native FileSystemDirectoryHandle shape (name/kind/entries) so code written against the
|
|
228
|
+
// native API — e.g. recursive walks using `handle.entries()` — works the same over the network.
|
|
229
|
+
readonly kind = "directory" as const;
|
|
230
|
+
get name() { return baseName(this.dirPath); }
|
|
223
231
|
async removeEntry(key: string): Promise<void> {
|
|
224
232
|
await this.conn.remove(joinPath(this.dirPath, key));
|
|
225
233
|
}
|
|
226
|
-
async getFileHandle(key: string, options?: { create?: boolean }): Promise<
|
|
234
|
+
async getFileHandle(key: string, options?: { create?: boolean }): Promise<RemoteFileWrapper> {
|
|
227
235
|
const p = joinPath(this.dirPath, key);
|
|
228
236
|
if (options?.create) return new RemoteFileWrapper(this.conn, p, undefined, true);
|
|
229
237
|
const stat = await this.conn.stat(p); // matches the File API: throw if missing
|
|
230
238
|
if (!stat || stat.dir) throw enoent(p);
|
|
231
239
|
return new RemoteFileWrapper(this.conn, p, stat, false);
|
|
232
240
|
}
|
|
233
|
-
async getDirectoryHandle(key: string, options?: { create?: boolean }): Promise<
|
|
241
|
+
async getDirectoryHandle(key: string, options?: { create?: boolean }): Promise<RemoteDirectoryWrapper> {
|
|
234
242
|
const p = joinPath(this.dirPath, key);
|
|
235
243
|
if (!options?.create) {
|
|
236
244
|
const stat = await this.conn.stat(p);
|
|
@@ -238,17 +246,22 @@ class RemoteDirectoryWrapper implements DirectoryWrapper {
|
|
|
238
246
|
}
|
|
239
247
|
return new RemoteDirectoryWrapper(this.conn, p); // dirs are created lazily on first write
|
|
240
248
|
}
|
|
241
|
-
|
|
249
|
+
// Each entry is itself a real handle (a sub-wrapper), so a recursive walk over .entries() keeps
|
|
250
|
+
// working at every level — exactly like the native API.
|
|
251
|
+
async *[Symbol.asyncIterator](): AsyncIterableIterator<[string, RemoteFileWrapper | RemoteDirectoryWrapper]> {
|
|
242
252
|
const entries = await this.conn.list(this.dirPath);
|
|
243
253
|
for (const e of entries) {
|
|
244
254
|
const childPath = joinPath(this.dirPath, e.name);
|
|
245
|
-
|
|
246
|
-
yield [e.name, { kind: "directory", name: e.name, getDirectoryHandle: (k, o) => new RemoteDirectoryWrapper(this.conn, childPath).getDirectoryHandle(k, o) }];
|
|
247
|
-
} else {
|
|
248
|
-
yield [e.name, { kind: "file", name: e.name, getFile: async () => new RemoteFileWrapper(this.conn, childPath) }];
|
|
249
|
-
}
|
|
255
|
+
yield [e.name, e.dir ? new RemoteDirectoryWrapper(this.conn, childPath) : new RemoteFileWrapper(this.conn, childPath)];
|
|
250
256
|
}
|
|
251
257
|
}
|
|
258
|
+
entries() { return this[Symbol.asyncIterator](); }
|
|
259
|
+
keys() { return mapAsync(this[Symbol.asyncIterator](), ([name]) => name); }
|
|
260
|
+
values() { return mapAsync(this[Symbol.asyncIterator](), ([, handle]) => handle); }
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async function* mapAsync<T, U>(it: AsyncIterableIterator<T>, fn: (v: T) => U): AsyncIterableIterator<U> {
|
|
264
|
+
for await (const v of it) yield fn(v);
|
|
252
265
|
}
|
|
253
266
|
|
|
254
267
|
// A DirectoryWrapper rooted at a remote server. Drop-in for the Node / File-API handles.
|