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 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
- [Symbol.asyncIterator](): AsyncIterableIterator<[
1373
- string,
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sliftutils",
3
- "version": "1.4.11",
3
+ "version": "1.4.12",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "files": [
@@ -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
- [Symbol.asyncIterator](): AsyncIterableIterator<[
59
- string,
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
- [Symbol.asyncIterator](): AsyncIterableIterator<[string, {
59
- kind: "file";
60
- name: string;
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
- const dirPath = path.join(this.rootPath, entry.name);
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<FileWrapper> {
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<DirectoryWrapper> {
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
- async *[Symbol.asyncIterator](): AsyncIterableIterator<[string, { kind: "file"; name: string; getFile(): Promise<FileWrapper> } | { kind: "directory"; name: string; getDirectoryHandle(key: string, options?: { create?: boolean }): Promise<DirectoryWrapper> }]> {
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
- if (e.dir) {
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.