@statewalker/webrun-files 0.9.0 → 0.10.1
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 +56 -3
- package/dist/cjs/index.cjs +126 -0
- package/dist/esm/index.js +124 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/list-order.d.ts +28 -0
- package/dist/list-order.d.ts.map +1 -0
- package/dist/types.d.ts +13 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +8 -5
- package/src/index.ts +5 -0
- package/src/list-order.ts +149 -0
- package/src/types.ts +13 -0
package/README.md
CHANGED
|
@@ -13,6 +13,8 @@ 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-sqlite` - SQLite (node:sqlite, Cloudflare Durable Objects, D1)
|
|
17
|
+
- `@statewalker/webrun-files-http` - Serve and consume a `FilesApi` over HTTP (fetch-based stubs)
|
|
16
18
|
- `@statewalker/webrun-files-composite` - Mount multiple backends into a unified filesystem
|
|
17
19
|
|
|
18
20
|
## The FilesApi Interface
|
|
@@ -175,6 +177,33 @@ for await (const entry of files.list('/project', { recursive: true })) {
|
|
|
175
177
|
}
|
|
176
178
|
```
|
|
177
179
|
|
|
180
|
+
### Listing order and resuming
|
|
181
|
+
|
|
182
|
+
Every implementation yields `list()` entries in **strictly increasing path order, compared by
|
|
183
|
+
Unicode code point** — the order of UTF-8 bytes, which is also how SQLite and S3 list — for
|
|
184
|
+
recursive and non-recursive listings alike. `ListOptions.after` resumes after any path, whether or
|
|
185
|
+
not it exists:
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
// Read a big directory in chunks of 256 without keeping an iterator open.
|
|
189
|
+
let after: string | undefined;
|
|
190
|
+
for (;;) {
|
|
191
|
+
const chunk: FileInfo[] = [];
|
|
192
|
+
for await (const entry of files.list('/data', { recursive: true, after })) {
|
|
193
|
+
chunk.push(entry);
|
|
194
|
+
if (chunk.length === 256) break;
|
|
195
|
+
}
|
|
196
|
+
if (chunk.length === 0) break;
|
|
197
|
+
await handle(chunk);
|
|
198
|
+
after = chunk[chunk.length - 1].path;
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Two consequences of the order: a directory comes before its descendants, but they are not
|
|
203
|
+
contiguous with it (`/a-x` and `/a.txt` sort between `/a` and `/a/b`, because `-` and `.` sort
|
|
204
|
+
before `/`); and JavaScript's `<` is not this order — it misorders characters above U+FFFF — so
|
|
205
|
+
compare paths with `comparePaths`.
|
|
206
|
+
|
|
178
207
|
### File Management
|
|
179
208
|
|
|
180
209
|
```typescript
|
|
@@ -184,6 +213,22 @@ await files.move('/temp/draft.txt', '/documents/final.txt');
|
|
|
184
213
|
await files.remove('/old-stuff'); // Recursively deletes directories
|
|
185
214
|
```
|
|
186
215
|
|
|
216
|
+
### Listing helpers for backends
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
import { comparePaths, listInPathOrder, mergeInPathOrder } from '@statewalker/webrun-files';
|
|
220
|
+
|
|
221
|
+
comparePaths(a, b); // the listing order: negative, zero or positive
|
|
222
|
+
|
|
223
|
+
// A backend that reads one directory at a time: children(dir) returns its direct
|
|
224
|
+
// entries in any order. Reads a directory only when the listing reaches it, and
|
|
225
|
+
// never one whose whole subtree sorts at or before options.after.
|
|
226
|
+
listInPathOrder(dir, children, { recursive, after });
|
|
227
|
+
|
|
228
|
+
// Merge listings that are each already ordered; the first stream wins a shared path.
|
|
229
|
+
mergeInPathOrder([streamA, streamB]);
|
|
230
|
+
```
|
|
231
|
+
|
|
187
232
|
## Type Reference
|
|
188
233
|
|
|
189
234
|
```typescript
|
|
@@ -198,7 +243,7 @@ import type {
|
|
|
198
243
|
FileEntryLocation, // { name: string, path: string }
|
|
199
244
|
FileKind, // "file" | "directory"
|
|
200
245
|
ReadOptions, // { start?: number, length?: number, signal?: AbortSignal }
|
|
201
|
-
ListOptions, // { recursive?: boolean }
|
|
246
|
+
ListOptions, // { recursive?: boolean, after?: string }
|
|
202
247
|
} from '@statewalker/webrun-files';
|
|
203
248
|
```
|
|
204
249
|
|
|
@@ -208,8 +253,16 @@ An implementation must return **exactly** one variant: a file with both numbers
|
|
|
208
253
|
present, a directory with nothing but its `kind`. An implementation that knows a
|
|
209
254
|
directory's modification time drops it rather than offering a value that would
|
|
210
255
|
be present on one backend and missing on the next. The parametrized suite in
|
|
211
|
-
`@statewalker/webrun-files-tests`
|
|
212
|
-
`createFilesApiTests` runs it
|
|
256
|
+
`@statewalker/webrun-files-tests` — a workspace package of this monorepo, not
|
|
257
|
+
published to npm — checks this at runtime: `createFilesApiTests` runs it (as
|
|
258
|
+
`createFileStatsConformanceTests`) for you.
|
|
259
|
+
|
|
260
|
+
A listing must also follow the order above and honour `after` exactly: the
|
|
261
|
+
entries after a path are the same, in the same order, as the matching suffix of
|
|
262
|
+
the full listing. `createFilesApiTests` checks that too
|
|
263
|
+
(`createListOrderTests`), against a fixture of names chosen to break naive
|
|
264
|
+
orderings. `listInPathOrder` gives both properties to a backend that can read
|
|
265
|
+
one directory at a time.
|
|
213
266
|
|
|
214
267
|
## License
|
|
215
268
|
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -89,6 +89,129 @@ async function writeText(files, path, content) {
|
|
|
89
89
|
await files.write(path, [new TextEncoder().encode(content)]);
|
|
90
90
|
}
|
|
91
91
|
//#endregion
|
|
92
|
+
//#region src/list-order.ts
|
|
93
|
+
/**
|
|
94
|
+
* The order every `FilesApi.list()` yields: paths compared by Unicode code
|
|
95
|
+
* point, which is the same as comparing their UTF-8 bytes — the order of
|
|
96
|
+
* SQLite's `BINARY` collation and of S3 key listings.
|
|
97
|
+
*
|
|
98
|
+
* Not JavaScript's `<`, which compares UTF-16 code units and so puts a
|
|
99
|
+
* character above U+FFFF (a surrogate pair) before U+E000–U+FFFF.
|
|
100
|
+
*/
|
|
101
|
+
function comparePaths(a, b) {
|
|
102
|
+
const length = Math.min(a.length, b.length);
|
|
103
|
+
for (let i = 0; i < length; i++) {
|
|
104
|
+
const x = a.charCodeAt(i);
|
|
105
|
+
const y = b.charCodeAt(i);
|
|
106
|
+
if (x === y) continue;
|
|
107
|
+
const xSurrogate = x >= 55296 && x <= 57343;
|
|
108
|
+
if (xSurrogate !== (y >= 55296 && y <= 57343)) return xSurrogate ? 1 : -1;
|
|
109
|
+
return x - y;
|
|
110
|
+
}
|
|
111
|
+
return a.length - b.length;
|
|
112
|
+
}
|
|
113
|
+
/** Whether a listing resuming after `after` can still yield anything under `dir`. */
|
|
114
|
+
function subtreeMayFollow(dir, after) {
|
|
115
|
+
return after === void 0 || comparePaths(after, `${dir === "/" ? "" : dir}0`) < 0;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* List a tree in path order for backends that read one directory at a time.
|
|
119
|
+
* `children(dir)` returns a directory's direct entries in any order.
|
|
120
|
+
*
|
|
121
|
+
* Pending entries sit in a min-heap keyed by path. A directory's children all
|
|
122
|
+
* sort after it, so pushing them when the directory is taken never yields out
|
|
123
|
+
* of order. Children are read only when the listing reaches their directory,
|
|
124
|
+
* and never for a directory whose whole subtree lies at or before `after`.
|
|
125
|
+
*/
|
|
126
|
+
async function* listInPathOrder(dir, children, options = {}) {
|
|
127
|
+
const { recursive = false, after } = options;
|
|
128
|
+
const heap = new PathHeap((entry) => entry.path);
|
|
129
|
+
if (!subtreeMayFollow(dir, after)) return;
|
|
130
|
+
for (const entry of await children(dir)) heap.push(entry);
|
|
131
|
+
for (let entry = heap.pop(); entry; entry = heap.pop()) {
|
|
132
|
+
if (after === void 0 || comparePaths(entry.path, after) > 0) yield entry;
|
|
133
|
+
if (recursive && entry.kind === "directory" && subtreeMayFollow(entry.path, after)) for (const child of await children(entry.path)) heap.push(child);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Merge streams that are each already in path order into one. When several
|
|
138
|
+
* streams yield the same path, the entry from the earliest stream wins and
|
|
139
|
+
* the others are dropped. At most one entry per stream is pulled ahead, and
|
|
140
|
+
* every stream is closed when the consumer stops.
|
|
141
|
+
*/
|
|
142
|
+
async function* mergeInPathOrder(streams) {
|
|
143
|
+
const iterators = streams.map((stream) => stream[Symbol.asyncIterator]());
|
|
144
|
+
const heads = [];
|
|
145
|
+
const done = [];
|
|
146
|
+
const pull = async (i) => {
|
|
147
|
+
const next = await iterators[i].next();
|
|
148
|
+
done[i] = next.done === true;
|
|
149
|
+
heads[i] = next.done ? void 0 : next.value;
|
|
150
|
+
};
|
|
151
|
+
try {
|
|
152
|
+
await Promise.all(iterators.map((_, i) => pull(i)));
|
|
153
|
+
for (;;) {
|
|
154
|
+
let min = -1;
|
|
155
|
+
for (let i = 0; i < heads.length; i++) {
|
|
156
|
+
const head = heads[i];
|
|
157
|
+
if (!head) continue;
|
|
158
|
+
const current = min === -1 ? void 0 : heads[min];
|
|
159
|
+
if (!current || comparePaths(head.path, current.path) < 0) min = i;
|
|
160
|
+
}
|
|
161
|
+
if (min === -1) return;
|
|
162
|
+
const entry = heads[min];
|
|
163
|
+
for (let i = min + 1; i < heads.length; i++) while (heads[i] && heads[i]?.path === entry.path) await pull(i);
|
|
164
|
+
heads[min] = void 0;
|
|
165
|
+
yield entry;
|
|
166
|
+
await pull(min);
|
|
167
|
+
}
|
|
168
|
+
} finally {
|
|
169
|
+
await Promise.all(iterators.map(async (iterator, i) => {
|
|
170
|
+
if (!done[i]) await iterator.return?.();
|
|
171
|
+
}));
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/** A binary min-heap ordered by {@link comparePaths}. */
|
|
175
|
+
var PathHeap = class {
|
|
176
|
+
#items = [];
|
|
177
|
+
#key;
|
|
178
|
+
constructor(key) {
|
|
179
|
+
this.#key = key;
|
|
180
|
+
}
|
|
181
|
+
push(item) {
|
|
182
|
+
const items = this.#items;
|
|
183
|
+
items.push(item);
|
|
184
|
+
for (let i = items.length - 1; i > 0;) {
|
|
185
|
+
const parent = i - 1 >> 1;
|
|
186
|
+
if (this.#less(items[parent], items[i])) break;
|
|
187
|
+
[items[parent], items[i]] = [items[i], items[parent]];
|
|
188
|
+
i = parent;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
pop() {
|
|
192
|
+
const items = this.#items;
|
|
193
|
+
const top = items[0];
|
|
194
|
+
const last = items.pop();
|
|
195
|
+
if (items.length > 0 && last !== void 0) {
|
|
196
|
+
items[0] = last;
|
|
197
|
+
for (let i = 0;;) {
|
|
198
|
+
const left = 2 * i + 1;
|
|
199
|
+
const right = left + 1;
|
|
200
|
+
let smallest = i;
|
|
201
|
+
if (left < items.length && this.#less(items[left], items[smallest])) smallest = left;
|
|
202
|
+
if (right < items.length && this.#less(items[right], items[smallest])) smallest = right;
|
|
203
|
+
if (smallest === i) break;
|
|
204
|
+
[items[smallest], items[i]] = [items[i], items[smallest]];
|
|
205
|
+
i = smallest;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return top;
|
|
209
|
+
}
|
|
210
|
+
#less(a, b) {
|
|
211
|
+
return comparePaths(this.#key(a), this.#key(b)) < 0;
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
//#endregion
|
|
92
215
|
//#region src/path-utils.ts
|
|
93
216
|
/**
|
|
94
217
|
* Path manipulation utilities for virtual filesystem paths.
|
|
@@ -142,9 +265,12 @@ function extname(path) {
|
|
|
142
265
|
}
|
|
143
266
|
//#endregion
|
|
144
267
|
exports.basename = basename;
|
|
268
|
+
exports.comparePaths = comparePaths;
|
|
145
269
|
exports.dirname = dirname;
|
|
146
270
|
exports.extname = extname;
|
|
147
271
|
exports.joinPath = joinPath;
|
|
272
|
+
exports.listInPathOrder = listInPathOrder;
|
|
273
|
+
exports.mergeInPathOrder = mergeInPathOrder;
|
|
148
274
|
exports.normalizePath = normalizePath;
|
|
149
275
|
exports.readAt = readAt;
|
|
150
276
|
exports.readFile = readFile;
|
package/dist/esm/index.js
CHANGED
|
@@ -88,6 +88,129 @@ async function writeText(files, path, content) {
|
|
|
88
88
|
await files.write(path, [new TextEncoder().encode(content)]);
|
|
89
89
|
}
|
|
90
90
|
//#endregion
|
|
91
|
+
//#region src/list-order.ts
|
|
92
|
+
/**
|
|
93
|
+
* The order every `FilesApi.list()` yields: paths compared by Unicode code
|
|
94
|
+
* point, which is the same as comparing their UTF-8 bytes — the order of
|
|
95
|
+
* SQLite's `BINARY` collation and of S3 key listings.
|
|
96
|
+
*
|
|
97
|
+
* Not JavaScript's `<`, which compares UTF-16 code units and so puts a
|
|
98
|
+
* character above U+FFFF (a surrogate pair) before U+E000–U+FFFF.
|
|
99
|
+
*/
|
|
100
|
+
function comparePaths(a, b) {
|
|
101
|
+
const length = Math.min(a.length, b.length);
|
|
102
|
+
for (let i = 0; i < length; i++) {
|
|
103
|
+
const x = a.charCodeAt(i);
|
|
104
|
+
const y = b.charCodeAt(i);
|
|
105
|
+
if (x === y) continue;
|
|
106
|
+
const xSurrogate = x >= 55296 && x <= 57343;
|
|
107
|
+
if (xSurrogate !== (y >= 55296 && y <= 57343)) return xSurrogate ? 1 : -1;
|
|
108
|
+
return x - y;
|
|
109
|
+
}
|
|
110
|
+
return a.length - b.length;
|
|
111
|
+
}
|
|
112
|
+
/** Whether a listing resuming after `after` can still yield anything under `dir`. */
|
|
113
|
+
function subtreeMayFollow(dir, after) {
|
|
114
|
+
return after === void 0 || comparePaths(after, `${dir === "/" ? "" : dir}0`) < 0;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* List a tree in path order for backends that read one directory at a time.
|
|
118
|
+
* `children(dir)` returns a directory's direct entries in any order.
|
|
119
|
+
*
|
|
120
|
+
* Pending entries sit in a min-heap keyed by path. A directory's children all
|
|
121
|
+
* sort after it, so pushing them when the directory is taken never yields out
|
|
122
|
+
* of order. Children are read only when the listing reaches their directory,
|
|
123
|
+
* and never for a directory whose whole subtree lies at or before `after`.
|
|
124
|
+
*/
|
|
125
|
+
async function* listInPathOrder(dir, children, options = {}) {
|
|
126
|
+
const { recursive = false, after } = options;
|
|
127
|
+
const heap = new PathHeap((entry) => entry.path);
|
|
128
|
+
if (!subtreeMayFollow(dir, after)) return;
|
|
129
|
+
for (const entry of await children(dir)) heap.push(entry);
|
|
130
|
+
for (let entry = heap.pop(); entry; entry = heap.pop()) {
|
|
131
|
+
if (after === void 0 || comparePaths(entry.path, after) > 0) yield entry;
|
|
132
|
+
if (recursive && entry.kind === "directory" && subtreeMayFollow(entry.path, after)) for (const child of await children(entry.path)) heap.push(child);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Merge streams that are each already in path order into one. When several
|
|
137
|
+
* streams yield the same path, the entry from the earliest stream wins and
|
|
138
|
+
* the others are dropped. At most one entry per stream is pulled ahead, and
|
|
139
|
+
* every stream is closed when the consumer stops.
|
|
140
|
+
*/
|
|
141
|
+
async function* mergeInPathOrder(streams) {
|
|
142
|
+
const iterators = streams.map((stream) => stream[Symbol.asyncIterator]());
|
|
143
|
+
const heads = [];
|
|
144
|
+
const done = [];
|
|
145
|
+
const pull = async (i) => {
|
|
146
|
+
const next = await iterators[i].next();
|
|
147
|
+
done[i] = next.done === true;
|
|
148
|
+
heads[i] = next.done ? void 0 : next.value;
|
|
149
|
+
};
|
|
150
|
+
try {
|
|
151
|
+
await Promise.all(iterators.map((_, i) => pull(i)));
|
|
152
|
+
for (;;) {
|
|
153
|
+
let min = -1;
|
|
154
|
+
for (let i = 0; i < heads.length; i++) {
|
|
155
|
+
const head = heads[i];
|
|
156
|
+
if (!head) continue;
|
|
157
|
+
const current = min === -1 ? void 0 : heads[min];
|
|
158
|
+
if (!current || comparePaths(head.path, current.path) < 0) min = i;
|
|
159
|
+
}
|
|
160
|
+
if (min === -1) return;
|
|
161
|
+
const entry = heads[min];
|
|
162
|
+
for (let i = min + 1; i < heads.length; i++) while (heads[i] && heads[i]?.path === entry.path) await pull(i);
|
|
163
|
+
heads[min] = void 0;
|
|
164
|
+
yield entry;
|
|
165
|
+
await pull(min);
|
|
166
|
+
}
|
|
167
|
+
} finally {
|
|
168
|
+
await Promise.all(iterators.map(async (iterator, i) => {
|
|
169
|
+
if (!done[i]) await iterator.return?.();
|
|
170
|
+
}));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/** A binary min-heap ordered by {@link comparePaths}. */
|
|
174
|
+
var PathHeap = class {
|
|
175
|
+
#items = [];
|
|
176
|
+
#key;
|
|
177
|
+
constructor(key) {
|
|
178
|
+
this.#key = key;
|
|
179
|
+
}
|
|
180
|
+
push(item) {
|
|
181
|
+
const items = this.#items;
|
|
182
|
+
items.push(item);
|
|
183
|
+
for (let i = items.length - 1; i > 0;) {
|
|
184
|
+
const parent = i - 1 >> 1;
|
|
185
|
+
if (this.#less(items[parent], items[i])) break;
|
|
186
|
+
[items[parent], items[i]] = [items[i], items[parent]];
|
|
187
|
+
i = parent;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
pop() {
|
|
191
|
+
const items = this.#items;
|
|
192
|
+
const top = items[0];
|
|
193
|
+
const last = items.pop();
|
|
194
|
+
if (items.length > 0 && last !== void 0) {
|
|
195
|
+
items[0] = last;
|
|
196
|
+
for (let i = 0;;) {
|
|
197
|
+
const left = 2 * i + 1;
|
|
198
|
+
const right = left + 1;
|
|
199
|
+
let smallest = i;
|
|
200
|
+
if (left < items.length && this.#less(items[left], items[smallest])) smallest = left;
|
|
201
|
+
if (right < items.length && this.#less(items[right], items[smallest])) smallest = right;
|
|
202
|
+
if (smallest === i) break;
|
|
203
|
+
[items[smallest], items[i]] = [items[i], items[smallest]];
|
|
204
|
+
i = smallest;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return top;
|
|
208
|
+
}
|
|
209
|
+
#less(a, b) {
|
|
210
|
+
return comparePaths(this.#key(a), this.#key(b)) < 0;
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
//#endregion
|
|
91
214
|
//#region src/path-utils.ts
|
|
92
215
|
/**
|
|
93
216
|
* Path manipulation utilities for virtual filesystem paths.
|
|
@@ -140,4 +263,4 @@ function extname(path) {
|
|
|
140
263
|
return name.substring(dotIndex);
|
|
141
264
|
}
|
|
142
265
|
//#endregion
|
|
143
|
-
export { basename, dirname, extname, joinPath, normalizePath, readAt, readFile, readRange, readText, tryReadFile, tryReadText, writeText };
|
|
266
|
+
export { basename, comparePaths, dirname, extname, joinPath, listInPathOrder, mergeInPathOrder, normalizePath, readAt, readFile, readRange, readText, tryReadFile, tryReadText, writeText };
|
package/dist/index.d.ts
CHANGED
|
@@ -11,8 +11,12 @@
|
|
|
11
11
|
* - @statewalker/webrun-files-node (Node.js)
|
|
12
12
|
* - @statewalker/webrun-files-browser (browser)
|
|
13
13
|
* - @statewalker/webrun-files-s3 (S3)
|
|
14
|
+
* - @statewalker/webrun-files-sqlite (SQLite: node:sqlite, Durable Objects, D1)
|
|
15
|
+
* - @statewalker/webrun-files-http (a FilesApi served and consumed over HTTP)
|
|
16
|
+
* - @statewalker/webrun-files-composite (mount several backends as one tree)
|
|
14
17
|
*/
|
|
15
18
|
export { readAt, readFile, readRange, readText, tryReadFile, tryReadText, writeText, } from "./file-utils.js";
|
|
19
|
+
export { comparePaths, listInPathOrder, mergeInPathOrder } from "./list-order.js";
|
|
16
20
|
export { basename, dirname, extname, joinPath, normalizePath, } from "./path-utils.js";
|
|
17
21
|
export type { DirectoryEntryInfo, DirectoryEntryStats, FileEntryInfo, FileEntryLocation, FileEntryStats, FileInfo, FileKind, FileStats, FilesApi, ListOptions, ReadOptions, } from "./types.js";
|
|
18
22
|
//# 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
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,EACL,MAAM,EACN,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,WAAW,EACX,WAAW,EACX,SAAS,GACV,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAElF,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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { FileInfo, ListOptions } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* The order every `FilesApi.list()` yields: paths compared by Unicode code
|
|
4
|
+
* point, which is the same as comparing their UTF-8 bytes — the order of
|
|
5
|
+
* SQLite's `BINARY` collation and of S3 key listings.
|
|
6
|
+
*
|
|
7
|
+
* Not JavaScript's `<`, which compares UTF-16 code units and so puts a
|
|
8
|
+
* character above U+FFFF (a surrogate pair) before U+E000–U+FFFF.
|
|
9
|
+
*/
|
|
10
|
+
export declare function comparePaths(a: string, b: string): number;
|
|
11
|
+
/**
|
|
12
|
+
* List a tree in path order for backends that read one directory at a time.
|
|
13
|
+
* `children(dir)` returns a directory's direct entries in any order.
|
|
14
|
+
*
|
|
15
|
+
* Pending entries sit in a min-heap keyed by path. A directory's children all
|
|
16
|
+
* sort after it, so pushing them when the directory is taken never yields out
|
|
17
|
+
* of order. Children are read only when the listing reaches their directory,
|
|
18
|
+
* and never for a directory whose whole subtree lies at or before `after`.
|
|
19
|
+
*/
|
|
20
|
+
export declare function listInPathOrder(dir: string, children: (dir: string) => Promise<Iterable<FileInfo>> | Iterable<FileInfo>, options?: ListOptions): AsyncGenerator<FileInfo>;
|
|
21
|
+
/**
|
|
22
|
+
* Merge streams that are each already in path order into one. When several
|
|
23
|
+
* streams yield the same path, the entry from the earliest stream wins and
|
|
24
|
+
* the others are dropped. At most one entry per stream is pulled ahead, and
|
|
25
|
+
* every stream is closed when the consumer stops.
|
|
26
|
+
*/
|
|
27
|
+
export declare function mergeInPathOrder(streams: AsyncIterable<FileInfo>[]): AsyncGenerator<FileInfo>;
|
|
28
|
+
//# sourceMappingURL=list-order.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-order.d.ts","sourceRoot":"","sources":["../src/list-order.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAExD;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAczD;AAQD;;;;;;;;GAQG;AACH,wBAAuB,eAAe,CACpC,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,GAAG,QAAQ,CAAC,QAAQ,CAAC,EAC3E,OAAO,GAAE,WAAgB,GACxB,cAAc,CAAC,QAAQ,CAAC,CAY1B;AAED;;;;;GAKG;AACH,wBAAuB,gBAAgB,CACrC,OAAO,EAAE,aAAa,CAAC,QAAQ,CAAC,EAAE,GACjC,cAAc,CAAC,QAAQ,CAAC,CAoC1B"}
|
package/dist/types.d.ts
CHANGED
|
@@ -27,6 +27,15 @@ export interface ReadOptions {
|
|
|
27
27
|
export interface ListOptions {
|
|
28
28
|
/** If true, lists all descendants recursively. Defaults to false. */
|
|
29
29
|
recursive?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Yield only entries whose path sorts strictly after this one, in
|
|
32
|
+
* `comparePaths` order. The path need not exist, and may lie outside
|
|
33
|
+
* the listed directory: before it, everything is yielded; past it, nothing.
|
|
34
|
+
*
|
|
35
|
+
* Resuming with the last path yielded continues a listing exactly where it
|
|
36
|
+
* stopped, without holding an iterator open between the calls.
|
|
37
|
+
*/
|
|
38
|
+
after?: string;
|
|
30
39
|
}
|
|
31
40
|
/**
|
|
32
41
|
* Metadata of a regular file.
|
|
@@ -116,6 +125,10 @@ export interface FilesApi {
|
|
|
116
125
|
/**
|
|
117
126
|
* List directory contents.
|
|
118
127
|
* Returns empty iterable for non-existent or non-directory paths.
|
|
128
|
+
*
|
|
129
|
+
* Entries are yielded in strictly increasing path order, compared by
|
|
130
|
+
* Unicode code point (`comparePaths`) — the same for recursive and
|
|
131
|
+
* non-recursive listings. `options.after` resumes after a given path.
|
|
119
132
|
*/
|
|
120
133
|
list(path: string, options?: ListOptions): AsyncIterable<FileInfo>;
|
|
121
134
|
/**
|
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;
|
|
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;IACpB;;;;;;;OAOG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;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;;;;;;;OAOG;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.10.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "FilesApi interface and utilities for cross-platform filesystem abstraction",
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"types": "./dist/index.d.ts",
|
|
20
20
|
"exports": {
|
|
21
21
|
".": {
|
|
22
|
+
"source": "./src/index.ts",
|
|
22
23
|
"types": "./dist/index.d.ts",
|
|
23
24
|
"import": "./dist/esm/index.js",
|
|
24
25
|
"require": "./dist/cjs/index.cjs"
|
|
@@ -29,10 +30,11 @@
|
|
|
29
30
|
"src"
|
|
30
31
|
],
|
|
31
32
|
"devDependencies": {
|
|
32
|
-
"@types/node": "^26.
|
|
33
|
+
"@types/node": "^26.6.4",
|
|
33
34
|
"rimraf": "^6.1.3",
|
|
34
|
-
"rolldown": "^1.2.
|
|
35
|
-
"typescript": "^7.0.2"
|
|
35
|
+
"rolldown": "^1.2.12",
|
|
36
|
+
"typescript": "^7.0.2",
|
|
37
|
+
"vitest": "^5.0.3"
|
|
36
38
|
},
|
|
37
39
|
"sideEffects": false,
|
|
38
40
|
"publishConfig": {
|
|
@@ -40,6 +42,7 @@
|
|
|
40
42
|
},
|
|
41
43
|
"scripts": {
|
|
42
44
|
"build": "rimraf dist && rolldown -c && tsc --emitDeclarationOnly --declaration",
|
|
43
|
-
"lint": "biome lint src"
|
|
45
|
+
"lint": "biome lint src",
|
|
46
|
+
"test": "vitest run"
|
|
44
47
|
}
|
|
45
48
|
}
|
package/src/index.ts
CHANGED
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
* - @statewalker/webrun-files-node (Node.js)
|
|
12
12
|
* - @statewalker/webrun-files-browser (browser)
|
|
13
13
|
* - @statewalker/webrun-files-s3 (S3)
|
|
14
|
+
* - @statewalker/webrun-files-sqlite (SQLite: node:sqlite, Durable Objects, D1)
|
|
15
|
+
* - @statewalker/webrun-files-http (a FilesApi served and consumed over HTTP)
|
|
16
|
+
* - @statewalker/webrun-files-composite (mount several backends as one tree)
|
|
14
17
|
*/
|
|
15
18
|
|
|
16
19
|
// File utilities
|
|
@@ -23,6 +26,8 @@ export {
|
|
|
23
26
|
tryReadText,
|
|
24
27
|
writeText,
|
|
25
28
|
} from "./file-utils.js";
|
|
29
|
+
// Listing order
|
|
30
|
+
export { comparePaths, listInPathOrder, mergeInPathOrder } from "./list-order.js";
|
|
26
31
|
// Path utilities
|
|
27
32
|
export {
|
|
28
33
|
basename,
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import type { FileInfo, ListOptions } from "./types.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The order every `FilesApi.list()` yields: paths compared by Unicode code
|
|
5
|
+
* point, which is the same as comparing their UTF-8 bytes — the order of
|
|
6
|
+
* SQLite's `BINARY` collation and of S3 key listings.
|
|
7
|
+
*
|
|
8
|
+
* Not JavaScript's `<`, which compares UTF-16 code units and so puts a
|
|
9
|
+
* character above U+FFFF (a surrogate pair) before U+E000–U+FFFF.
|
|
10
|
+
*/
|
|
11
|
+
export function comparePaths(a: string, b: string): number {
|
|
12
|
+
const length = Math.min(a.length, b.length);
|
|
13
|
+
for (let i = 0; i < length; i++) {
|
|
14
|
+
const x = a.charCodeAt(i);
|
|
15
|
+
const y = b.charCodeAt(i);
|
|
16
|
+
if (x === y) continue;
|
|
17
|
+
const xSurrogate = x >= 0xd800 && x <= 0xdfff;
|
|
18
|
+
const ySurrogate = y >= 0xd800 && y <= 0xdfff;
|
|
19
|
+
// A surrogate stands for a code point above U+FFFF, so it outranks any
|
|
20
|
+
// other unit; two surrogates compare in code point order as they are.
|
|
21
|
+
if (xSurrogate !== ySurrogate) return xSurrogate ? 1 : -1;
|
|
22
|
+
return x - y;
|
|
23
|
+
}
|
|
24
|
+
return a.length - b.length;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Whether a listing resuming after `after` can still yield anything under `dir`. */
|
|
28
|
+
function subtreeMayFollow(dir: string, after: string | undefined): boolean {
|
|
29
|
+
// Every descendant of `dir` lies between `dir + "/"` and `dir + "0"`.
|
|
30
|
+
return after === undefined || comparePaths(after, `${dir === "/" ? "" : dir}0`) < 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* List a tree in path order for backends that read one directory at a time.
|
|
35
|
+
* `children(dir)` returns a directory's direct entries in any order.
|
|
36
|
+
*
|
|
37
|
+
* Pending entries sit in a min-heap keyed by path. A directory's children all
|
|
38
|
+
* sort after it, so pushing them when the directory is taken never yields out
|
|
39
|
+
* of order. Children are read only when the listing reaches their directory,
|
|
40
|
+
* and never for a directory whose whole subtree lies at or before `after`.
|
|
41
|
+
*/
|
|
42
|
+
export async function* listInPathOrder(
|
|
43
|
+
dir: string,
|
|
44
|
+
children: (dir: string) => Promise<Iterable<FileInfo>> | Iterable<FileInfo>,
|
|
45
|
+
options: ListOptions = {},
|
|
46
|
+
): AsyncGenerator<FileInfo> {
|
|
47
|
+
const { recursive = false, after } = options;
|
|
48
|
+
const heap = new PathHeap<FileInfo>((entry) => entry.path);
|
|
49
|
+
if (!subtreeMayFollow(dir, after)) return;
|
|
50
|
+
for (const entry of await children(dir)) heap.push(entry);
|
|
51
|
+
|
|
52
|
+
for (let entry = heap.pop(); entry; entry = heap.pop()) {
|
|
53
|
+
if (after === undefined || comparePaths(entry.path, after) > 0) yield entry;
|
|
54
|
+
if (recursive && entry.kind === "directory" && subtreeMayFollow(entry.path, after)) {
|
|
55
|
+
for (const child of await children(entry.path)) heap.push(child);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Merge streams that are each already in path order into one. When several
|
|
62
|
+
* streams yield the same path, the entry from the earliest stream wins and
|
|
63
|
+
* the others are dropped. At most one entry per stream is pulled ahead, and
|
|
64
|
+
* every stream is closed when the consumer stops.
|
|
65
|
+
*/
|
|
66
|
+
export async function* mergeInPathOrder(
|
|
67
|
+
streams: AsyncIterable<FileInfo>[],
|
|
68
|
+
): AsyncGenerator<FileInfo> {
|
|
69
|
+
const iterators = streams.map((stream) => stream[Symbol.asyncIterator]());
|
|
70
|
+
const heads: (FileInfo | undefined)[] = [];
|
|
71
|
+
const done: boolean[] = [];
|
|
72
|
+
const pull = async (i: number) => {
|
|
73
|
+
const next = await iterators[i].next();
|
|
74
|
+
done[i] = next.done === true;
|
|
75
|
+
heads[i] = next.done ? undefined : next.value;
|
|
76
|
+
};
|
|
77
|
+
try {
|
|
78
|
+
await Promise.all(iterators.map((_, i) => pull(i)));
|
|
79
|
+
for (;;) {
|
|
80
|
+
let min = -1;
|
|
81
|
+
for (let i = 0; i < heads.length; i++) {
|
|
82
|
+
const head = heads[i];
|
|
83
|
+
if (!head) continue;
|
|
84
|
+
const current = min === -1 ? undefined : heads[min];
|
|
85
|
+
if (!current || comparePaths(head.path, current.path) < 0) min = i;
|
|
86
|
+
}
|
|
87
|
+
if (min === -1) return;
|
|
88
|
+
const entry = heads[min] as FileInfo;
|
|
89
|
+
// Drop the same path from later streams before yielding.
|
|
90
|
+
for (let i = min + 1; i < heads.length; i++) {
|
|
91
|
+
while (heads[i] && heads[i]?.path === entry.path) await pull(i);
|
|
92
|
+
}
|
|
93
|
+
heads[min] = undefined;
|
|
94
|
+
yield entry;
|
|
95
|
+
await pull(min);
|
|
96
|
+
}
|
|
97
|
+
} finally {
|
|
98
|
+
await Promise.all(
|
|
99
|
+
iterators.map(async (iterator, i) => {
|
|
100
|
+
if (!done[i]) await iterator.return?.();
|
|
101
|
+
}),
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** A binary min-heap ordered by {@link comparePaths}. */
|
|
107
|
+
class PathHeap<T> {
|
|
108
|
+
readonly #items: T[] = [];
|
|
109
|
+
readonly #key: (item: T) => string;
|
|
110
|
+
|
|
111
|
+
constructor(key: (item: T) => string) {
|
|
112
|
+
this.#key = key;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
push(item: T): void {
|
|
116
|
+
const items = this.#items;
|
|
117
|
+
items.push(item);
|
|
118
|
+
for (let i = items.length - 1; i > 0; ) {
|
|
119
|
+
const parent = (i - 1) >> 1;
|
|
120
|
+
if (this.#less(items[parent], items[i])) break;
|
|
121
|
+
[items[parent], items[i]] = [items[i], items[parent]];
|
|
122
|
+
i = parent;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
pop(): T | undefined {
|
|
127
|
+
const items = this.#items;
|
|
128
|
+
const top = items[0];
|
|
129
|
+
const last = items.pop();
|
|
130
|
+
if (items.length > 0 && last !== undefined) {
|
|
131
|
+
items[0] = last;
|
|
132
|
+
for (let i = 0; ; ) {
|
|
133
|
+
const left = 2 * i + 1;
|
|
134
|
+
const right = left + 1;
|
|
135
|
+
let smallest = i;
|
|
136
|
+
if (left < items.length && this.#less(items[left], items[smallest])) smallest = left;
|
|
137
|
+
if (right < items.length && this.#less(items[right], items[smallest])) smallest = right;
|
|
138
|
+
if (smallest === i) break;
|
|
139
|
+
[items[smallest], items[i]] = [items[i], items[smallest]];
|
|
140
|
+
i = smallest;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return top;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
#less(a: T, b: T): boolean {
|
|
147
|
+
return comparePaths(this.#key(a), this.#key(b)) < 0;
|
|
148
|
+
}
|
|
149
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -30,6 +30,15 @@ export interface ReadOptions {
|
|
|
30
30
|
export interface ListOptions {
|
|
31
31
|
/** If true, lists all descendants recursively. Defaults to false. */
|
|
32
32
|
recursive?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Yield only entries whose path sorts strictly after this one, in
|
|
35
|
+
* `comparePaths` order. The path need not exist, and may lie outside
|
|
36
|
+
* the listed directory: before it, everything is yielded; past it, nothing.
|
|
37
|
+
*
|
|
38
|
+
* Resuming with the last path yielded continues a listing exactly where it
|
|
39
|
+
* stopped, without holding an iterator open between the calls.
|
|
40
|
+
*/
|
|
41
|
+
after?: string;
|
|
33
42
|
}
|
|
34
43
|
|
|
35
44
|
/**
|
|
@@ -128,6 +137,10 @@ export interface FilesApi {
|
|
|
128
137
|
/**
|
|
129
138
|
* List directory contents.
|
|
130
139
|
* Returns empty iterable for non-existent or non-directory paths.
|
|
140
|
+
*
|
|
141
|
+
* Entries are yielded in strictly increasing path order, compared by
|
|
142
|
+
* Unicode code point (`comparePaths`) — the same for recursive and
|
|
143
|
+
* non-recursive listings. `options.after` resumes after a given path.
|
|
131
144
|
*/
|
|
132
145
|
list(path: string, options?: ListOptions): AsyncIterable<FileInfo>;
|
|
133
146
|
|