// Copyright 2018-2025 the Deno authors. MIT license. import { EventEmitter } from "node:events"; import { Buffer } from "node:buffer"; import { Mode, promises, read, ReadStream, write, WriteStream } from "node:fs"; import { core, primordials } from "ext:core/mod.js"; export type { BigIntStats, Stats } from "ext:deno_node/_fs/_fs_stat.ts"; import { BinaryOptionsArgument, FileOptionsArgument, ReadOptions, TextOptionsArgument, } from "ext:deno_node/_fs/_fs_common.ts"; import { ftruncatePromise } from "ext:deno_node/_fs/_fs_ftruncate.ts"; export type { BigIntStats, Stats } from "ext:deno_node/_fs/_fs_stat.ts"; import { writevPromise, WriteVResult } from "ext:deno_node/_fs/_fs_writev.ts"; import { fdatasyncPromise } from "ext:deno_node/_fs/_fs_fdatasync.ts"; import { fsyncPromise } from "ext:deno_node/_fs/_fs_fsync.ts"; import { CreateReadStreamOptions, CreateWriteStreamOptions, } from "node:fs/promises"; import { SymbolAsyncDispose } from "ext:deno_web/00_infra.js"; const { Error, ObjectAssign, ObjectPrototypeIsPrototypeOf, Promise, PromiseResolve, SafeArrayIterator, Uint8ArrayPrototype, } = primordials; interface WriteResult { bytesWritten: number; buffer: Buffer | string; } interface ReadResult { bytesRead: number; buffer: Buffer; } type Path = string | Buffer | URL; export class FileHandle extends EventEmitter { #rid: number; #path: Path; constructor(rid: number, path: Path) { super(); this.#rid = rid; this.#path = path; } get fd() { return this.#rid; } read( buffer: Uint8Array, offset?: number, length?: number, position?: number | null, ): Promise; read(options?: ReadOptions): Promise; read( bufferOrOpt: Uint8Array | ReadOptions, offset?: number, length?: number, position?: number | null, ): Promise { if (ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, bufferOrOpt)) { return new Promise((resolve, reject) => { read( this.fd, bufferOrOpt, offset, length, position, (err, bytesRead, buffer) => { if (err) reject(err); else resolve({ buffer, bytesRead }); }, ); }); } else { return new Promise((resolve, reject) => { read(this.fd, bufferOrOpt, (err, bytesRead, buffer) => { if (err) reject(err); else resolve({ buffer, bytesRead }); }); }); } } truncate(len?: number): Promise { return fsCall(ftruncatePromise, this, len); } readFile( opt?: TextOptionsArgument | BinaryOptionsArgument | FileOptionsArgument, ): Promise { return promises.readFile(this, opt); } write( buffer: Buffer, offset: number, length: number, position: number, ): Promise; write(str: string, position: number, encoding: string): Promise; write( bufferOrStr: Uint8Array | string, offsetOrPosition: number, lengthOrEncoding: number | string, position?: number, ): Promise { if (ObjectPrototypeIsPrototypeOf(Uint8ArrayPrototype, bufferOrStr)) { const buffer = bufferOrStr; const offset = offsetOrPosition; const length = lengthOrEncoding; return new Promise((resolve, reject) => { write( this.fd, buffer, offset, length, position, (err, bytesWritten, buffer) => { if (err) reject(err); else resolve({ buffer, bytesWritten }); }, ); }); } else { const str = bufferOrStr; const position = offsetOrPosition; const encoding = lengthOrEncoding; return new Promise((resolve, reject) => { write(this.fd, str, position, encoding, (err, bytesWritten, buffer) => { if (err) reject(err); else resolve({ buffer, bytesWritten }); }); }); } } writeFile(data, options): Promise { return fsCall(promises.writeFile, this, data, options); } writev(buffers: ArrayBufferView[], position?: number): Promise { return fsCall(writevPromise, this, buffers, position); } close(): Promise { // Note that Deno.close is not async return PromiseResolve(core.close(this.fd)); } stat(): Promise; stat(options: { bigint: false }): Promise; stat(options: { bigint: true }): Promise; stat(options?: { bigint: boolean }): Promise { return fsCall(promises.fstat, this, options); } chmod(mode: Mode): Promise { assertNotClosed(this, promises.chmod.name); return promises.chmod(this.#path, mode); } datasync(): Promise { return fsCall(fdatasyncPromise, this); } sync(): Promise { return fsCall(fsyncPromise, this); } utimes( atime: number | string | Date, mtime: number | string | Date, ): Promise { assertNotClosed(this, promises.utimes.name); return promises.utimes(this.#path, atime, mtime); } chown(uid: number, gid: number): Promise { assertNotClosed(this, promises.chown.name); return promises.chown(this.#path, uid, gid); } createReadStream(options?: CreateReadStreamOptions): ReadStream { return new ReadStream(undefined, { ...options, fd: this.fd }); } createWriteStream(options?: CreateWriteStreamOptions): WriteStream { return new WriteStream(undefined, { ...options, fd: this.fd }); } [SymbolAsyncDispose]() { return this.close(); } } function assertNotClosed(handle: FileHandle, syscall: string) { if (handle.fd === -1) { const err = new Error("file closed"); throw ObjectAssign(err, { code: "EBADF", syscall, }); } } function fsCall(fn, handle, ...args) { assertNotClosed(handle, fn.name); return fn(handle.fd, ...new SafeArrayIterator(args)); } export default { FileHandle, };