char/avril

(wip) why'd you have to go and make things so complicaaaaated?

git clone https://git.t4t.associates/char/avril

Charlotte Somgenerate fs typedefs from typescript impl766372c

main
30.4 KiB1041 linesraw
1import {
2  promiseResolve,
3  promiseThen as then,
4  safePromise,
5} from "ext:core/promises";
6import {
7  ArrayIsArray,
8  DatePrototypeGetTime as getTime,
9  getter,
10  NumberIsFinite as numberIsFinite,
11  ObjectCreate,
12  ObjectDefineProperty as define,
13  ObjectFreeze as freeze,
14  RangeError,
15  ReflectApply as apply,
16  SymbolIterator,
17  TypedArrayPrototypeGetBuffer as bufferOf,
18  TypedArrayPrototypeGetByteLength as length,
19  TypedArrayPrototypeGetByteOffset as offsetOf,
20  TypedArrayPrototypeGetTag as tag,
21  TypeError,
22  Uint8Array,
23  Uint8ArrayPrototypeSet as setBytes,
24  uncurryThis,
25  WeakMap,
26  WeakMapPrototypeGet as get,
27  WeakMapPrototypeSet as set,
28} from "ext:core/primordials";
29import type { Metadata, WatchEvent } from "ext:core/ops";
30import {
31  op_file_chmod as chmodOp,
32  op_file_chown as chownOp,
33  op_file_close as closeOp,
34  op_file_copy as copyOp,
35  op_file_fchmod as fchmodOp,
36  op_file_fchmod_sync as fchmodSyncOp,
37  op_file_fchown as fchownOp,
38  op_file_fchown_sync as fchownSyncOp,
39  op_file_flock as flockOp,
40  op_file_flock_sync as flockSyncOp,
41  op_file_funlock as funlockOp,
42  op_file_funlock_sync as funlockSyncOp,
43  op_file_futime as futimeOp,
44  op_file_futime_sync as futimeSyncOp,
45  op_file_lchown as lchownOp,
46  op_file_link as linkOp,
47  op_file_lutime as lutimeOp,
48  op_file_mkdir as mkdirOp,
49  op_file_open as openOp,
50  op_file_path_stat as pathStatOp,
51  op_file_path_truncate as truncatePathOp,
52  op_file_read as readOp,
53  op_file_read_dir as readDirOp,
54  op_file_read_link as readLinkOp,
55  op_file_read_sync as readSyncOp,
56  op_file_real_path as realPathOp,
57  op_file_remove as removeOp,
58  op_file_rename as renameOp,
59  op_file_seek as seekOp,
60  op_file_seek_sync as seekSyncOp,
61  op_file_stat as statOp,
62  op_file_stat_sync as statSyncOp,
63  op_file_symlink as symlinkOp,
64  op_file_sync as syncOp,
65  op_file_sync_sync as syncSyncOp,
66  op_file_truncate as truncateOp,
67  op_file_truncate_sync as truncateSyncOp,
68  op_file_url_path as urlPath,
69  op_file_utime as utimeOp,
70  op_file_watch as watchOp,
71  op_file_watch_close as watchCloseOp,
72  op_file_watch_keepalive as watchKeepAliveOp,
73  op_file_watch_next as watchNextOp,
74  op_file_watch_next_unref as watchNextUnrefOp,
75  op_file_watch_ref as watchRefOp,
76  op_file_write as writeOp,
77  op_file_write_sync as writeSyncOp,
78  op_file_writev as writevOp,
79  op_file_writev_sync as writevSyncOp,
80} from "ext:core/ops";
81import { array, dictionary, isObject, required, state } from "ext:web/idl.js";
82import { addListener, removeListener, signals } from "ext:web/events.js";
83import { registerUncloneable } from "ext:web/serialization.js";
84import { URL } from "ext:url/main.js";
85import type {
86  ReadableByteStreamController,
87  WritableStreamDefaultController as WritableController,
88} from "ext:webstreams/streams.js";
89import {
90  byobRequest,
91  enqueue,
92  errorStream,
93  finish,
94  ReadableStream,
95  ReadableStreamBYOBRequest,
96  WritableStream,
97} from "ext:webstreams/streams.js";
98import { errors, failure, result } from "./errors.js";
99
100const handles = new WeakMap<object, HandleState>();
101registerUncloneable(handles);
102const watchers = new WeakMap<object, WatchState>();
103registerUncloneable(watchers);
104const Date = globalThis.Date,
105  FinalizationRegistry = globalThis.FinalizationRegistry;
106const register = uncurryThis(FinalizationRegistry.prototype.register),
107  unregister = uncurryThis(FinalizationRegistry.prototype.unregister);
108const registry = new FinalizationRegistry((id: number) =>
109  closeOp(args(id, true))
110);
111const watchRegistry = new FinalizationRegistry((id: number) =>
112  watchCloseOp(args(id, true))
113);
114const urlHref = getter(URL.prototype, "href"),
115  getView = getter(ReadableStreamBYOBRequest.prototype, "view");
116const writableError = uncurryThis(
117  WritableStreamDefaultController.prototype.error,
118);
119const dispose: typeof Symbol.dispose = Symbol.dispose,
120  asyncIterator: typeof Symbol.asyncIterator = Symbol.asyncIterator,
121  empty = new Uint8Array();
122const watchKinds = freeze(
123  ["create", "modify", "remove", "rename", "rescan"] as const,
124);
125const safeInteger = Number.isSafeInteger,
126  mathTrunc = Math.trunc,
127  mathRound = Math.round;
128const SeekMode = freeze({ Start: 0, Current: 1, End: 2 });
129
130function args<T extends unknown[]>(...items: T): T {
131  const result = array();
132  for (let i = 0; i < items.length; i++) result[i] = items[i];
133  return result as unknown as T;
134}
135function path(value: unknown): string {
136  if (typeof value === "string") return value;
137  let href;
138  try {
139    href = urlHref(value);
140  } catch {
141    throw new TypeError("Expected a path string or file URL");
142  }
143  return result(urlPath(href));
144}
145interface HandleState {
146  id: number | null;
147  owner: object;
148  readable: ReadableStream<Uint8Array> | null;
149  writable: WritableStream<Uint8Array> | null;
150  reader: ReadableByteStreamController | null;
151  writer: WritableController | null;
152}
153interface WatchState {
154  id: number | null;
155  owner: object;
156  referenced: boolean;
157  epoch: number;
158  signal: unknown;
159  onAbort: (() => void) | null;
160}
161function id(data: { id: number | null }): number {
162  if (data.id === null) throw failure("BadResource", "File handle is closed");
163  return data.id;
164}
165function bytes(value: unknown): Uint8Array<ArrayBufferLike> {
166  if (tag(value) !== "Uint8Array") {
167    throw new TypeError("File I/O requires a Uint8Array");
168  }
169  return value as Uint8Array<ArrayBufferLike>;
170}
171function segment(
172  buffer: Uint8Array<ArrayBufferLike>,
173  offset: number,
174): Uint8Array<ArrayBufferLike> {
175  const remaining = length(buffer) - offset;
176  return new Uint8Array(
177    bufferOf(buffer),
178    offsetOf(buffer) + offset,
179    remaining < 65536 ? remaining : 65536,
180  );
181}
182function vectors(buffers: unknown): Uint8Array<ArrayBuffer>[] {
183  if (!ArrayIsArray(buffers)) {
184    throw new TypeError("File I/O requires an array of Uint8Array");
185  }
186  // Snapshot before queueing, without rereading caller-controlled elements.
187  const count = buffers.length, pieces = args<Uint8Array<ArrayBuffer>[]>();
188  if (!safeInteger(count) || count < 0 || count > 4294967295) {
189    throw new RangeError("Invalid vector length");
190  }
191  let total = 0;
192  for (let i = 0; i < count; i++) {
193    const view = buffers[i] as Uint8Array<ArrayBufferLike>;
194    if (tag(view) !== "Uint8Array") {
195      throw new TypeError("File I/O requires an array of Uint8Array");
196    }
197    setBytes(view, empty);
198    const size = length(view);
199    if (size === 0) continue;
200    if (!safeInteger(total + size)) {
201      throw new RangeError("Vector exceeds safe integer range");
202    }
203    total += size;
204    // Fixed snapshot for the native call, which needs unshared storage.
205    pieces[pieces.length] = new Uint8Array(
206      new Uint8Array(bufferOf(view), offsetOf(view), size),
207    );
208  }
209  return pieces;
210}
211function integer(value: unknown, negative = false): number {
212  if (!safeInteger(value) || (!negative && (value as number) < 0)) {
213    throw new RangeError("Expected a safe integer");
214  }
215  return value as number;
216}
217function mode(value: number): number {
218  if (integer(value) > 0o7777) {
219    throw new RangeError("Invalid file mode");
220  }
221  return value;
222}
223function owner(value: number | null | undefined): number | null {
224  if (value === null || value === undefined) return null;
225  if (integer(value) >= 4294967295) {
226    throw new RangeError("Invalid user or group id");
227  }
228  return value;
229}
230function stamp(value: number | Date): [number, number] {
231  let seconds, nanos;
232  if (value instanceof Date) {
233    const ms = getTime(value);
234    if (!numberIsFinite(ms)) {
235      throw new TypeError("Expected a timestamp in seconds or a Date");
236    }
237    // Split integer milliseconds exactly: float seconds would smear the
238    // sub-millisecond remainder across the nanosecond field.
239    seconds = mathTrunc(ms / 1000);
240    nanos = (ms - seconds * 1000) * 1000000;
241  } else {
242    if (typeof value !== "number" || !numberIsFinite(value)) {
243      throw new TypeError("Expected a timestamp in seconds or a Date");
244    }
245    seconds = mathTrunc(value);
246    nanos = mathRound((value - seconds) * 1e9);
247  }
248  if (nanos < 0) {
249    seconds -= 1;
250    nanos += 1e9;
251  } else if (nanos >= 1e9) {
252    seconds += 1;
253    nanos -= 1e9;
254  }
255  if (
256    seconds < -8640000000000 || seconds > 8640000000000 ||
257    (seconds === 8640000000000 && nanos > 0)
258  ) {
259    throw new RangeError("Timestamp out of range");
260  }
261  return [seconds, nanos];
262}
263function metadata(row: Metadata) {
264  return {
265    isFile: row[0],
266    isDirectory: row[1],
267    isSymlink: row[2],
268    size: row[3],
269    mtime: row[4] === null ? null : new Date(row[4]),
270    atime: row[5] === null ? null : new Date(row[5]),
271    birthtime: row[6] === null ? null : new Date(row[6]),
272    mode: row[7],
273    uid: row[8],
274    gid: row[9],
275  };
276}
277function watchId(data: WatchState): number {
278  if (data.id === null) {
279    throw failure("BadResource", "Filesystem watcher is closed");
280  }
281  return data.id;
282}
283function closeWatcher(data: WatchState, quiet = false): void {
284  if (data.id === null) {
285    if (!quiet) watchId(data);
286    return;
287  }
288  const current = data.id;
289  data.id = null;
290  unregister(watchRegistry, data.owner);
291  if (data.signal !== undefined && data.onAbort !== null) {
292    removeListener(data.signal, "abort", data.onAbort);
293  }
294  data.signal = undefined;
295  data.onAbort = null;
296  result(watchCloseOp(args(current, quiet)));
297}
298function keepAlive(data: WatchState, epoch: number): void {
299  if (data.id === null) return;
300  then(watchKeepAliveOp(args(data.id, epoch)), () => {}, () => {});
301}
302function watchEvent(row: WatchEvent) {
303  return freeze({
304    __proto__: null,
305    kind: watchKinds[row[0]],
306    paths: freeze(row[1]),
307  });
308}
309class FsWatcher {
310  declare static readonly [Symbol.hasInstance]: (value: unknown) => value is FsWatcher;
311  constructor() {
312    throw new TypeError("Illegal constructor");
313  }
314  close() {
315    closeWatcher(state(watchers, this));
316  }
317  ref() {
318    const data = state(watchers, this);
319    if (data.id === null || data.referenced) return;
320    data.referenced = true;
321    data.epoch = result(watchRefOp(data.id));
322    keepAlive(data, data.epoch);
323  }
324  unref() {
325    const data = state(watchers, this);
326    if (data.id === null || !data.referenced) return;
327    data.referenced = false;
328    data.epoch = result(watchRefOp(data.id));
329  }
330  [dispose]() {
331    closeWatcher(state(watchers, this), true);
332  }
333  [asyncIterator](): AsyncIterableIterator<ReturnType<typeof watchEvent>, undefined> {
334    const data = state(watchers, this);
335    return {
336      async next() {
337        if (data.id === null) return { done: true, value: undefined };
338        try {
339          const row = result(
340            await safePromise(
341              (data.referenced ? watchNextOp : watchNextUnrefOp)(
342                watchId(data),
343              ),
344            ),
345          );
346          if (row === null || data.id === null) {
347            closeWatcher(data, true);
348            return { done: true, value: undefined };
349          }
350          return { done: false, value: watchEvent(row) };
351        } catch (error) {
352          if (data.id === null) return { done: true, value: undefined };
353          closeWatcher(data, true);
354          throw error;
355        }
356      },
357      return() {
358        closeWatcher(data, true);
359        return promiseResolve({ done: true, value: undefined });
360      },
361      [asyncIterator]() {
362        return this;
363      },
364    };
365  }
366}
367define(FsWatcher.prototype, Symbol.toStringTag, {
368  value: "FsWatcher",
369  configurable: true,
370});
371function watchPaths(value: unknown): string[] {
372  const filenames = args<string[]>();
373  if (typeof value === "string") {
374    filenames[0] = path(value);
375    return filenames;
376  }
377  let href;
378  try {
379    href = urlHref(value);
380  } catch {}
381  if (href !== undefined) {
382    filenames[0] = path(value);
383    return filenames;
384  }
385  if (!isObject(value)) {
386    throw new TypeError("Expected a filesystem path or iterable of paths");
387  }
388  const method = value[SymbolIterator];
389  if (typeof method !== "function") {
390    throw new TypeError("Expected a filesystem path or iterable of paths");
391  }
392  const iterator = apply(method, value, args()) as { next?: unknown } | null,
393    next = iterator?.next;
394  if (!isObject(iterator) || typeof next !== "function") {
395    throw new TypeError("Invalid filesystem path iterator");
396  }
397  let count = 0;
398  while (true) {
399    const step = apply(next, iterator, args());
400    if (!isObject(step)) {
401      throw new TypeError("Invalid filesystem path iterator result");
402    }
403    if (step.done) break;
404    if (count >= 128) {
405      throw new RangeError("Too many filesystem watch paths");
406    }
407    filenames[count++] = path(step.value);
408  }
409  if (count === 0) {
410    throw new TypeError("Expected at least one filesystem watch path");
411  }
412  return filenames;
413}
414function watchFs(paths: unknown, options: unknown = undefined): FsWatcher {
415  required(arguments.length, 1);
416  const filenames = watchPaths(paths),
417    d: Record<PropertyKey, unknown> = options === undefined
418      ? ObjectCreate(null)
419      : dictionary(options);
420  const recursiveValue = d.recursive, signal = d.signal;
421  if (recursiveValue !== undefined && typeof recursiveValue !== "boolean") {
422    throw new TypeError("recursive must be boolean");
423  }
424  if (signal !== undefined && state(signals, signal).aborted) {
425    throw state(signals, signal).reason;
426  }
427  const current = result(
428    watchOp(args(filenames, recursiveValue !== false)),
429  );
430  const watcher = ObjectCreate(FsWatcher.prototype) as FsWatcher;
431  const data: WatchState = {
432    id: current,
433    owner: watcher,
434    referenced: true,
435    epoch: 0,
436    signal: undefined as unknown,
437    onAbort: null as (() => void) | null,
438  };
439  set(watchers, watcher, data);
440  register(watchRegistry, watcher, current, watcher);
441  keepAlive(data, 0);
442  if (signal !== undefined) {
443    data.signal = signal;
444    data.onAbort = () => closeWatcher(data, true);
445    addListener(signal, "abort", data.onAbort, { once: true });
446    if (state(signals, signal).aborted) {
447      const reason = state(signals, signal).reason;
448      closeWatcher(data, true);
449      throw reason;
450    }
451  }
452  return watcher;
453}
454function close(
455  data: HandleState,
456  quiet = false,
457  source: "read" | "write" | null = null,
458): void {
459  if (data.id === null) {
460    if (!quiet) id(data);
461    return;
462  }
463  const current = data.id;
464  data.id = null;
465  unregister(registry, data.owner);
466  const error = failure("BadResource", "File handle is closed");
467  if (data.reader && source !== "read") errorStream(data.reader, error);
468  if (data.writer && source !== "write") writableError(data.writer, error);
469  result(closeOp(args(current, quiet)));
470}
471class FileHandle {
472  declare static readonly [Symbol.hasInstance]: (value: unknown) => value is FileHandle;
473  constructor() {
474    throw new TypeError("Illegal constructor");
475  }
476  async read(buffer: Uint8Array<ArrayBufferLike>): Promise<number | null> {
477    const data = state(handles, this), current = id(data);
478    bytes(buffer);
479    setBytes(buffer, empty);
480    const count = length(buffer),
481      chunk = result(await safePromise(readOp(args(current, count))));
482    setBytes(buffer, empty);
483    if (chunk === null) return null;
484    setBytes(buffer, chunk);
485    return length(chunk);
486  }
487  async write(buffer: Uint8Array<ArrayBufferLike>): Promise<number> {
488    const current = id(state(handles, this));
489    const snapshot = new Uint8Array(segment(bytes(buffer), 0));
490    return result(await safePromise(writeOp(args(current, snapshot))));
491  }
492  async writev(buffers: readonly Uint8Array<ArrayBufferLike>[]): Promise<number> {
493    const current = id(state(handles, this));
494    const snapshot = vectors(buffers);
495    return result(await safePromise(writevOp(args(current, snapshot))));
496  }
497  async seek(offset: number, whence: number): Promise<number> {
498    const current = id(state(handles, this));
499    integer(offset, true);
500    if (whence !== 0 && whence !== 1 && whence !== 2) {
501      throw new TypeError("Invalid seek mode");
502    }
503    return result(await safePromise(seekOp(args(current, offset, whence))));
504  }
505  async stat() {
506    return metadata(
507      result(await safePromise(statOp(id(state(handles, this))))),
508    );
509  }
510  async truncate(length = 0) {
511    result(
512      await safePromise(
513        truncateOp(args(id(state(handles, this)), integer(length))),
514      ),
515    );
516  }
517  async sync() {
518    result(await safePromise(syncOp(args(id(state(handles, this)), false))));
519  }
520  async syncData() {
521    result(await safePromise(syncOp(args(id(state(handles, this)), true))));
522  }
523  async fchmod(fileMode: number): Promise<void> {
524    result(
525      await safePromise(
526        fchmodOp(args(id(state(handles, this)), mode(fileMode))),
527      ),
528    );
529  }
530  async fchown(
531    uid: number | null | undefined,
532    gid: number | null | undefined,
533  ): Promise<void> {
534    result(
535      await safePromise(
536        fchownOp(args(id(state(handles, this)), owner(uid), owner(gid))),
537      ),
538    );
539  }
540  async futime(atime: number | Date, mtime: number | Date): Promise<void> {
541    const before = stamp(atime), after = stamp(mtime);
542    result(
543      await safePromise(
544        futimeOp(
545          args(
546            id(state(handles, this)),
547            before[0],
548            before[1],
549            after[0],
550            after[1],
551          ),
552        ),
553      ),
554    );
555  }
556  async flock(exclusive = true) {
557    if (exclusive !== true && exclusive !== false) {
558      throw new TypeError("Lock mode must be boolean");
559    }
560    // Waiting releases the handle queue, so a lock no longer serializes with
561    // reads and writes: fire-and-forget flock();write() runs unordered, and a
562    // close() landing in the same tick must not observe success past close.
563    result(
564      await safePromise(flockOp(args(id(state(handles, this)), exclusive))),
565    );
566    id(state(handles, this));
567  }
568  async funlock() {
569    result(await safePromise(funlockOp(id(state(handles, this)))));
570  }
571  readSync(buffer: Uint8Array<ArrayBufferLike>): number | null {
572    const data = state(handles, this), current = id(data);
573    bytes(buffer);
574    setBytes(buffer, empty);
575    const count = length(buffer),
576      chunk = result(readSyncOp(args(current, count)));
577    setBytes(buffer, empty);
578    if (chunk === null) return null;
579    setBytes(buffer, chunk);
580    return length(chunk);
581  }
582  writeSync(buffer: Uint8Array<ArrayBufferLike>): number {
583    const current = id(state(handles, this));
584    const snapshot = new Uint8Array(segment(bytes(buffer), 0));
585    return result(writeSyncOp(args(current, snapshot)));
586  }
587  writevSync(buffers: readonly Uint8Array<ArrayBufferLike>[]): number {
588    const current = id(state(handles, this));
589    const snapshot = vectors(buffers);
590    return result(writevSyncOp(args(current, snapshot)));
591  }
592  seekSync(offset: number, whence: number): number {
593    const current = id(state(handles, this));
594    integer(offset, true);
595    if (whence !== 0 && whence !== 1 && whence !== 2) {
596      throw new TypeError("Invalid seek mode");
597    }
598    return result(seekSyncOp(args(current, offset, whence)));
599  }
600  statSync() {
601    return metadata(
602      result(statSyncOp(id(state(handles, this)))),
603    );
604  }
605  truncateSync(length = 0) {
606    result(
607      truncateSyncOp(args(id(state(handles, this)), integer(length))),
608    );
609  }
610  syncSync() {
611    result(syncSyncOp(args(id(state(handles, this)), false)));
612  }
613  syncDataSync() {
614    result(syncSyncOp(args(id(state(handles, this)), true)));
615  }
616  fchmodSync(fileMode: number): void {
617    result(
618      fchmodSyncOp(args(id(state(handles, this)), mode(fileMode))),
619    );
620  }
621  fchownSync(
622    uid: number | null | undefined,
623    gid: number | null | undefined,
624  ): void {
625    result(
626      fchownSyncOp(args(id(state(handles, this)), owner(uid), owner(gid))),
627    );
628  }
629  futimeSync(atime: number | Date, mtime: number | Date): void {
630    const before = stamp(atime), after = stamp(mtime);
631    result(
632      futimeSyncOp(
633        args(
634          id(state(handles, this)),
635          before[0],
636          before[1],
637          after[0],
638          after[1],
639        ),
640      ),
641    );
642  }
643  flockSync(exclusive = true) {
644    if (exclusive !== true && exclusive !== false) {
645      throw new TypeError("Lock mode must be boolean");
646    }
647    result(
648      flockSyncOp(args(id(state(handles, this)), exclusive)),
649    );
650    id(state(handles, this));
651  }
652  funlockSync() {
653    result(funlockSyncOp(id(state(handles, this))));
654  }
655  close() {
656    close(state(handles, this));
657  }
658  [dispose]() {
659    close(state(handles, this), true);
660  }
661  get readable() {
662    const data = state(handles, this);
663    if (data.readable) return data.readable;
664    id(data);
665    data.readable = new ReadableStream({
666      type: "bytes",
667      start(controller) {
668        data.reader = controller;
669      },
670      async pull(controller) {
671        try {
672          const request = byobRequest(controller),
673            view = request && getView(request);
674          const chunk = result<Uint8Array<ArrayBuffer> | null>(
675            await safePromise(
676              readOp(args(id(data), view ? length(view) : 65536)),
677            ),
678          );
679          if (data.id === null) return;
680          if (chunk === null) {
681            finish(controller);
682            close(data, true, "read");
683          } else if (request) {
684            setBytes(view as Uint8Array<ArrayBuffer>, chunk);
685            respond(request, length(chunk));
686          } else enqueue(controller, chunk);
687        } catch (error) {
688          errorStream(controller, error);
689          close(data, true, "read");
690        }
691      },
692      cancel() {
693        close(data, true, "read");
694      },
695    }, { highWaterMark: 0 });
696    return data.readable;
697  }
698  get writable() {
699    const data = state(handles, this);
700    if (data.writable) return data.writable;
701    id(data);
702    data.writable = new WritableStream({
703      start(controller) {
704        data.writer = controller;
705      },
706      async write(buffer: unknown) {
707        try {
708          const snapshot = new Uint8Array(bytes(buffer));
709          let offset = 0;
710          do {
711            const count = result(
712              await safePromise(
713                writeOp(
714                  args(
715                    id(data),
716                    segment(snapshot, offset) as Uint8Array<ArrayBuffer>,
717                  ),
718                ),
719              ),
720            );
721            if (
722              !safeInteger(count) || count < 0 ||
723              count > length(snapshot) - offset ||
724              (count === 0 && offset < length(snapshot))
725            ) throw failure("IoError", "Invalid partial write");
726            offset += count;
727          } while (offset < length(snapshot));
728        } catch (error) {
729          close(data, true, "write");
730          throw error;
731        }
732      },
733      close() {
734        close(data, true, "write");
735      },
736      abort() {
737        close(data, true, "write");
738      },
739    }, {
740      highWaterMark: 65536,
741      size(chunk) {
742        return tag(chunk) === "Uint8Array" ? length(chunk) : 1;
743      },
744    });
745    return data.writable;
746  }
747}
748const respond = uncurryThis(ReadableStreamBYOBRequest.prototype.respond);
749define(FileHandle.prototype, Symbol.toStringTag, {
750  value: "FileHandle",
751  configurable: true,
752});
753
754function openArgs(
755  pathname: unknown,
756  options: unknown,
757): [
758  string,
759  [boolean, boolean, boolean, boolean, boolean, boolean, number | null],
760] {
761  const filename = path(pathname),
762    d = options === undefined ? { read: true } : dictionary(options);
763  const flags: (boolean | number | null)[] = array();
764  const keys = ["read", "write", "append", "truncate", "create", "createNew"];
765  for (let index = 0; index < keys.length; index++) {
766    const key = keys[index], value = d[key];
767    if (value !== undefined && typeof value !== "boolean") {
768      throw new TypeError("Open flags must be boolean");
769    }
770    flags[flags.length] = value === true;
771  }
772  const mode = d.mode;
773  if (mode !== undefined && integer(mode) > 0o7777) {
774    throw new RangeError("Invalid file creation mode");
775  }
776  flags[flags.length] = mode === undefined ? null : mode as number;
777  return args(
778    filename,
779    flags as [
780      boolean,
781      boolean,
782      boolean,
783      boolean,
784      boolean,
785      boolean,
786      number | null,
787    ],
788  );
789}
790function writeOptions(options: unknown): Record<PropertyKey, unknown> {
791  const input = dictionary(options),
792    output: Record<PropertyKey, unknown> = ObjectCreate(null);
793  const keys = ["append", "create", "createNew"];
794  for (let index = 0; index < keys.length; index++) {
795    const key = keys[index], value = input[key];
796    if (value !== undefined && typeof value !== "boolean") {
797      throw new TypeError("Write flags must be boolean");
798    }
799    output[key] = value === undefined ? key === "create" : value;
800  }
801  const mode = input.mode;
802  if (mode !== undefined && integer(mode) > 0o7777) {
803    throw new RangeError("Invalid file creation mode");
804  }
805  output.mode = mode;
806  output.write = true;
807  output.truncate = !output.append;
808  return output;
809}
810function fileHandle(current: number): FileHandle {
811  const handle = ObjectCreate(FileHandle.prototype) as FileHandle;
812  set(handles, handle, {
813    id: current,
814    owner: handle,
815    readable: null,
816    writable: null,
817    reader: null,
818    writer: null,
819  });
820  register(registry, handle, current, handle);
821  return handle;
822}
823async function open(
824  pathname: unknown,
825  options: unknown = undefined,
826): Promise<FileHandle> {
827  required(arguments.length, 1);
828  return fileHandle(
829    result(await safePromise(openOp(openArgs(pathname, options)))),
830  );
831}
832async function create(pathname: unknown): Promise<FileHandle> {
833  required(arguments.length, 1);
834  return await safePromise(open(pathname, {
835    read: true,
836    write: true,
837    create: true,
838    truncate: true,
839  }));
840}
841async function stat(pathname: unknown) {
842  required(arguments.length, 1);
843  return metadata(
844    result(await safePromise(pathStatOp(args(path(pathname), true)))),
845  );
846}
847async function lstat(pathname: unknown) {
848  required(arguments.length, 1);
849  return metadata(
850    result(await safePromise(pathStatOp(args(path(pathname), false)))),
851  );
852}
853async function* readDir(
854  pathname: unknown,
855): AsyncGenerator<
856  { name: string; isFile: boolean; isDirectory: boolean; isSymlink: boolean }
857> {
858  required(arguments.length, 1);
859  const entries = result(await safePromise(readDirOp(path(pathname))));
860  for (let i = 0; i < entries.length; i++) {
861    const row = entries[i];
862    yield {
863      name: row[0],
864      isFile: row[1],
865      isDirectory: row[2],
866      isSymlink: row[3],
867    };
868  }
869}
870async function mkdir(
871  pathname: unknown,
872  options: unknown = undefined,
873): Promise<void> {
874  required(arguments.length, 1);
875  const filename = path(pathname), mode = dictionary(options).mode;
876  if (mode !== undefined && integer(mode) > 0o7777) {
877    throw new RangeError("Invalid directory mode");
878  }
879  result(
880    await safePromise(
881      mkdirOp(args(filename, mode === undefined ? null : mode as number)),
882    ),
883  );
884}
885async function remove(
886  pathname: unknown,
887  options: unknown = undefined,
888): Promise<void> {
889  required(arguments.length, 1);
890  const filename = path(pathname), recursive = dictionary(options).recursive;
891  if (recursive !== undefined && typeof recursive !== "boolean") {
892    throw new TypeError("recursive must be boolean");
893  }
894  result(await safePromise(removeOp(args(filename, recursive === true))));
895}
896async function rename(source: unknown, destination: unknown): Promise<void> {
897  required(arguments.length, 2);
898  result(await safePromise(renameOp(args(path(source), path(destination)))));
899}
900async function copyFile(source: unknown, destination: unknown): Promise<void> {
901  required(arguments.length, 2);
902  result(await safePromise(copyOp(args(path(source), path(destination)))));
903}
904async function symlink(
905  target: unknown,
906  linkPath: unknown,
907  options: unknown = undefined,
908): Promise<void> {
909  required(arguments.length, 2);
910  const contents = path(target),
911    link = path(linkPath),
912    kind = dictionary(options).type;
913  if (kind !== undefined && kind !== "file" && kind !== "dir") {
914    throw new TypeError("Symlink type must be file or dir");
915  }
916  result(
917    await safePromise(
918      symlinkOp(args(contents, link, kind === undefined ? null : kind)),
919    ),
920  );
921}
922async function readLink(pathname: unknown): Promise<string> {
923  required(arguments.length, 1);
924  return result(await safePromise(readLinkOp(path(pathname))));
925}
926async function realPath(pathname: unknown): Promise<string> {
927  required(arguments.length, 1);
928  return result(await safePromise(realPathOp(path(pathname))));
929}
930async function chmod(pathname: unknown, fileMode: number): Promise<void> {
931  required(arguments.length, 2);
932  result(await safePromise(chmodOp(args(path(pathname), mode(fileMode)))));
933}
934async function chown(
935  pathname: unknown,
936  uid: number | null | undefined,
937  gid: number | null | undefined,
938): Promise<void> {
939  required(arguments.length, 3);
940  result(
941    await safePromise(chownOp(args(path(pathname), owner(uid), owner(gid)))),
942  );
943}
944async function lchown(
945  pathname: unknown,
946  uid: number | null | undefined,
947  gid: number | null | undefined,
948): Promise<void> {
949  required(arguments.length, 3);
950  result(
951    await safePromise(lchownOp(args(path(pathname), owner(uid), owner(gid)))),
952  );
953}
954async function utime(
955  pathname: unknown,
956  atime: number | Date,
957  mtime: number | Date,
958): Promise<void> {
959  required(arguments.length, 3);
960  const before = stamp(atime), after = stamp(mtime);
961  result(
962    await safePromise(
963      utimeOp(
964        args(path(pathname), before[0], before[1], after[0], after[1]),
965      ),
966    ),
967  );
968}
969async function lutime(
970  pathname: unknown,
971  atime: number | Date,
972  mtime: number | Date,
973): Promise<void> {
974  required(arguments.length, 3);
975  const before = stamp(atime), after = stamp(mtime);
976  result(
977    await safePromise(
978      lutimeOp(
979        args(path(pathname), before[0], before[1], after[0], after[1]),
980      ),
981    ),
982  );
983}
984async function link(oldpath: unknown, newpath: unknown): Promise<void> {
985  required(arguments.length, 2);
986  result(await safePromise(linkOp(args(path(oldpath), path(newpath)))));
987}
988async function truncate(pathname: unknown, length = 0): Promise<void> {
989  required(arguments.length, 1);
990  result(
991    await safePromise(truncatePathOp(args(path(pathname), integer(length)))),
992  );
993}
994export {
995  chmod,
996  chown,
997  copyFile,
998  create,
999  errors,
1000  FileHandle,
1001  FsWatcher,
1002  lchown,
1003  link,
1004  lstat,
1005  lutime,
1006  mkdir,
1007  open,
1008  readDir,
1009  readLink,
1010  realPath,
1011  remove,
1012  rename,
1013  SeekMode,
1014  stat,
1015  symlink,
1016  truncate,
1017  utime,
1018  watchFs,
1019};
1020export function processHandle(value: unknown): number {
1021  return id(state(handles, value));
1022}
1023export function responseHandle(value: unknown): number | null {
1024  const data = get(handles, value);
1025  return data === undefined ? null : id(data);
1026}
1027export function detachResponseHandle(value: unknown): void {
1028  close(state(handles, value), true);
1029}
1030export {
1031  args,
1032  fileHandle,
1033  integer,
1034  metadata,
1035  mode,
1036  openArgs,
1037  owner,
1038  path,
1039  stamp,
1040  writeOptions,
1041};