Skip to content

feat(fs/unstable): add open, openSync, and FsFile class #6524

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Apr 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions _tools/node_test_runner/run_test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,14 @@ import "../../collections/union_test.ts";
import "../../collections/unzip_test.ts";
import "../../collections/without_all_test.ts";
import "../../collections/zip_test.ts";
import "../../fs/_node_fs_file_test.ts";
import "../../fs/unstable_chown_test.ts";
import "../../fs/unstable_copy_file_test.ts";
import "../../fs/unstable_link_test.ts";
import "../../fs/unstable_make_temp_dir_test.ts";
import "../../fs/unstable_make_temp_file_test.ts";
import "../../fs/unstable_mkdir_test.ts";
import "../../fs/unstable_open_test.ts";
import "../../fs/unstable_read_dir_test.ts";
import "../../fs/unstable_read_file_test.ts";
import "../../fs/unstable_read_link_test.ts";
Expand Down
77 changes: 77 additions & 0 deletions fs/_get_fs_flag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,18 @@

import { getNodeFs } from "./_utils.ts";
import type { WriteFileOptions } from "./unstable_types.ts";
import type { OpenOptions } from "./unstable_open.ts";

type WriteBooleanOptions = Pick<
WriteFileOptions,
"append" | "create" | "createNew"
>;

type OpenBooleanOptions = Pick<
OpenOptions,
"read" | "write" | "append" | "truncate" | "create" | "createNew"
>;

/**
* Uses the boolean options specified in {@linkcode WriteFileOptions} to
* construct the composite flag value to pass to the `flag` option in the
Expand All @@ -31,3 +37,74 @@
}
return flag;
}

/**
* Uses the boolean options specified in {@linkcode OpenOptions} to construct the
* composite flag value to pass to the `flag` option in the Node.js `open`
* function.
*/
export function getOpenFsFlag(opt: OpenBooleanOptions): number {
const { O_APPEND, O_CREAT, O_EXCL, O_WRONLY, O_RDONLY, O_RDWR, O_TRUNC } =
getNodeFs().constants;

Check warning on line 48 in fs/_get_fs_flag.ts

View check run for this annotation

Codecov / codecov/patch

fs/_get_fs_flag.ts#L45-L48

Added lines #L45 - L48 were not covered by tests

if (
!opt.read && !opt.write && !opt.append && !opt.truncate && !opt.create &&
!opt.createNew
) {
throw new Error("'options' requires at least one option to be true");
}

Check warning on line 55 in fs/_get_fs_flag.ts

View check run for this annotation

Codecov / codecov/patch

fs/_get_fs_flag.ts#L50-L55

Added lines #L50 - L55 were not covered by tests

if (!opt.write && opt.truncate) {
throw new Error("'truncate' option requires 'write' to be true");
}

Check warning on line 59 in fs/_get_fs_flag.ts

View check run for this annotation

Codecov / codecov/patch

fs/_get_fs_flag.ts#L57-L59

Added lines #L57 - L59 were not covered by tests

if ((opt.create || opt.createNew) && !(opt.write || opt.append)) {
throw new Error(
"'create' or 'createNew' options require 'write' or 'append' to be true",

Check warning on line 63 in fs/_get_fs_flag.ts

View check run for this annotation

Codecov / codecov/patch

fs/_get_fs_flag.ts#L61-L63

Added lines #L61 - L63 were not covered by tests
);
}

Check warning on line 65 in fs/_get_fs_flag.ts

View check run for this annotation

Codecov / codecov/patch

fs/_get_fs_flag.ts#L65

Added line #L65 was not covered by tests

// This error is added to match the Deno runtime. Deno throws a `TypeError`
// (os error 22) for this OpenOption combo. Under Node.js, the bitmask
Comment on lines +67 to +68
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks reasonable. Nice

// combinations of (O_RDWR | O_TRUNC | O_APPEND) and
// (O_WRONLY | O_TRUNC | O_APPEND) to open files are valid.
if (opt.write && opt.append && opt.truncate) {
throw new TypeError("Invalid argument.");
}

Check warning on line 73 in fs/_get_fs_flag.ts

View check run for this annotation

Codecov / codecov/patch

fs/_get_fs_flag.ts#L67-L73

Added lines #L67 - L73 were not covered by tests

let flag = O_RDONLY;
if (opt.read && !opt.write) {
flag |= O_RDONLY;
}

Check warning on line 78 in fs/_get_fs_flag.ts

View check run for this annotation

Codecov / codecov/patch

fs/_get_fs_flag.ts#L75-L78

Added lines #L75 - L78 were not covered by tests

if (opt.read && opt.write) {
flag |= O_RDWR;
}

Check warning on line 82 in fs/_get_fs_flag.ts

View check run for this annotation

Codecov / codecov/patch

fs/_get_fs_flag.ts#L80-L82

Added lines #L80 - L82 were not covered by tests

if (!opt.read && opt.write) {
flag |= O_WRONLY;
}

Check warning on line 86 in fs/_get_fs_flag.ts

View check run for this annotation

Codecov / codecov/patch

fs/_get_fs_flag.ts#L84-L86

Added lines #L84 - L86 were not covered by tests

if (opt.create || opt.createNew) {
flag |= O_CREAT;
}

Check warning on line 90 in fs/_get_fs_flag.ts

View check run for this annotation

Codecov / codecov/patch

fs/_get_fs_flag.ts#L88-L90

Added lines #L88 - L90 were not covered by tests

if (opt.createNew) {
flag |= O_EXCL;
}

Check warning on line 94 in fs/_get_fs_flag.ts

View check run for this annotation

Codecov / codecov/patch

fs/_get_fs_flag.ts#L92-L94

Added lines #L92 - L94 were not covered by tests

if (opt.append) {
flag |= O_APPEND;
if (!opt.read) {
flag |= O_WRONLY;
} else {
flag |= O_RDWR;
}
}

Check warning on line 103 in fs/_get_fs_flag.ts

View check run for this annotation

Codecov / codecov/patch

fs/_get_fs_flag.ts#L96-L103

Added lines #L96 - L103 were not covered by tests

if (opt.truncate) {
flag |= O_TRUNC;
}

Check warning on line 107 in fs/_get_fs_flag.ts

View check run for this annotation

Codecov / codecov/patch

fs/_get_fs_flag.ts#L105-L107

Added lines #L105 - L107 were not covered by tests

return flag;
}

Check warning on line 110 in fs/_get_fs_flag.ts

View check run for this annotation

Codecov / codecov/patch

fs/_get_fs_flag.ts#L109-L110

Added lines #L109 - L110 were not covered by tests
217 changes: 217 additions & 0 deletions fs/_node_fs_file.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
// Copyright 2018-2025 the Deno authors. MIT license.

import { getNodeFs, getNodeStream, getNodeTty, getNodeUtil } from "./_utils.ts";
import { mapError } from "./_map_error.ts";
import type { FileInfo, FsFile, SetRawOptions } from "./unstable_types.ts";
import { toFileInfo } from "./_to_file_info.ts";

/**
* The internal class to convert a Node file descriptor into a FsFile object.
*/
export class NodeFsFile implements FsFile {
#nodeFs = getNodeFs();
#nodeStream = getNodeStream();
#nodeTty = getNodeTty();
#nodeUtil = getNodeUtil();
#nodeReadFd = this.#nodeUtil.promisify(this.#nodeFs.read);
#nodeWriteFd = this.#nodeUtil.promisify(this.#nodeFs.write);

Check warning on line 17 in fs/_node_fs_file.ts

View check run for this annotation

Codecov / codecov/patch

fs/_node_fs_file.ts#L12-L17

Added lines #L12 - L17 were not covered by tests

#closed: boolean;
#rid: number;
#readableStream?: ReadableStream<Uint8Array>;
#writableStream?: WritableStream<Uint8Array>;

Check warning on line 22 in fs/_node_fs_file.ts

View check run for this annotation

Codecov / codecov/patch

fs/_node_fs_file.ts#L19-L22

Added lines #L19 - L22 were not covered by tests

constructor(fd: number) {
this.#rid = fd;
this.#closed = false;
}

Check warning on line 27 in fs/_node_fs_file.ts

View check run for this annotation

Codecov / codecov/patch

fs/_node_fs_file.ts#L24-L27

Added lines #L24 - L27 were not covered by tests

get readable(): ReadableStream<Uint8Array> {
if (this.#readableStream == null) {
const readStream = this.#nodeFs.createReadStream(null as unknown, {
fd: this.#rid,
autoClose: false,
});
this.#readableStream = this.#nodeStream.Readable.toWeb(readStream);
}
return this.#readableStream as ReadableStream;
}

Check warning on line 38 in fs/_node_fs_file.ts

View check run for this annotation

Codecov / codecov/patch

fs/_node_fs_file.ts#L29-L38

Added lines #L29 - L38 were not covered by tests

get writable(): WritableStream<Uint8Array> {
if (this.#writableStream == null) {
const writeStream = this.#nodeFs.createWriteStream(null as unknown, {
fd: this.#rid,
autoClose: false,
});
this.#writableStream = this.#nodeStream.Writable.toWeb(writeStream);
}
return this.#writableStream as WritableStream;
}

Check warning on line 49 in fs/_node_fs_file.ts

View check run for this annotation

Codecov / codecov/patch

fs/_node_fs_file.ts#L40-L49

Added lines #L40 - L49 were not covered by tests

[Symbol.dispose](): void {
if (!this.#closed) {
this.close();
}
}

Check warning on line 55 in fs/_node_fs_file.ts

View check run for this annotation

Codecov / codecov/patch

fs/_node_fs_file.ts#L52-L55

Added lines #L52 - L55 were not covered by tests

close(): void {
this.#closed = true;
this.#nodeFs.closeSync(this.#rid);
}

Check warning on line 60 in fs/_node_fs_file.ts

View check run for this annotation

Codecov / codecov/patch

fs/_node_fs_file.ts#L57-L60

Added lines #L57 - L60 were not covered by tests

isTerminal(): boolean {
return this.#nodeTty.isatty(this.#rid);
}

Check warning on line 64 in fs/_node_fs_file.ts

View check run for this annotation

Codecov / codecov/patch

fs/_node_fs_file.ts#L62-L64

Added lines #L62 - L64 were not covered by tests

// deno-lint-ignore require-await no-unused-vars
async lock(exclusive?: boolean): Promise<void> {
throw new Error("Method not implemented.");
}

Check warning on line 69 in fs/_node_fs_file.ts

View check run for this annotation

Codecov / codecov/patch

fs/_node_fs_file.ts#L67-L69

Added lines #L67 - L69 were not covered by tests

// deno-lint-ignore no-unused-vars
lockSync(exclusive?: boolean): void {
throw new Error("Method not implemented.");
}

Check warning on line 74 in fs/_node_fs_file.ts

View check run for this annotation

Codecov / codecov/patch

fs/_node_fs_file.ts#L72-L74

Added lines #L72 - L74 were not covered by tests

async read(p: Uint8Array): Promise<number | null> {
try {
const { bytesRead } = await this.#nodeReadFd(
this.#rid,
p,
0,
p.length,
null,

Check warning on line 83 in fs/_node_fs_file.ts

View check run for this annotation

Codecov / codecov/patch

fs/_node_fs_file.ts#L76-L83

Added lines #L76 - L83 were not covered by tests
);
return bytesRead === 0 ? null : bytesRead;
} catch (error) {
throw mapError(error);
}
}

Check warning on line 89 in fs/_node_fs_file.ts

View check run for this annotation

Codecov / codecov/patch

fs/_node_fs_file.ts#L85-L89

Added lines #L85 - L89 were not covered by tests

readSync(p: Uint8Array): number | null {
try {
const bytesRead = this.#nodeFs.readSync(this.#rid, p);
return bytesRead === 0 ? null : bytesRead;
} catch (error) {
throw mapError(error);
}
}

Check warning on line 98 in fs/_node_fs_file.ts

View check run for this annotation

Codecov / codecov/patch

fs/_node_fs_file.ts#L91-L98

Added lines #L91 - L98 were not covered by tests

//deno-lint-ignore no-unused-vars
setRaw(mode: boolean, options?: SetRawOptions): void {
throw new Error("Method not implemented.");
}

Check warning on line 103 in fs/_node_fs_file.ts

View check run for this annotation

Codecov / codecov/patch

fs/_node_fs_file.ts#L101-L103

Added lines #L101 - L103 were not covered by tests

async stat(): Promise<FileInfo> {
const nodeStatFd = this.#nodeUtil.promisify(this.#nodeFs.fstat);
try {
const fdStat = await nodeStatFd(this.#rid);
return toFileInfo(fdStat);
} catch (error) {
throw mapError(error);
}
}

Check warning on line 113 in fs/_node_fs_file.ts

View check run for this annotation

Codecov / codecov/patch

fs/_node_fs_file.ts#L105-L113

Added lines #L105 - L113 were not covered by tests

statSync(): FileInfo {
try {
const fdStat = this.#nodeFs.fstatSync(this.#rid);
return toFileInfo(fdStat);
} catch (error) {
throw mapError(error);
}
}

Check warning on line 122 in fs/_node_fs_file.ts

View check run for this annotation

Codecov / codecov/patch

fs/_node_fs_file.ts#L115-L122

Added lines #L115 - L122 were not covered by tests

async sync(): Promise<void> {
const nodeFsyncFd = this.#nodeUtil.promisify(this.#nodeFs.fsync);
try {
await nodeFsyncFd(this.#rid);
} catch (error) {
throw mapError(error);
}
}

Check warning on line 131 in fs/_node_fs_file.ts

View check run for this annotation

Codecov / codecov/patch

fs/_node_fs_file.ts#L124-L131

Added lines #L124 - L131 were not covered by tests

syncSync(): void {
try {
this.#nodeFs.fsyncSync(this.#rid);
} catch (error) {
throw mapError(error);
}
}

Check warning on line 139 in fs/_node_fs_file.ts

View check run for this annotation

Codecov / codecov/patch

fs/_node_fs_file.ts#L133-L139

Added lines #L133 - L139 were not covered by tests

async syncData(): Promise<void> {
const nodeFdatasyncFd = this.#nodeUtil.promisify(this.#nodeFs.fdatasync);
try {
await nodeFdatasyncFd(this.#rid);
} catch (error) {
throw mapError(error);
}
}

Check warning on line 148 in fs/_node_fs_file.ts

View check run for this annotation

Codecov / codecov/patch

fs/_node_fs_file.ts#L141-L148

Added lines #L141 - L148 were not covered by tests

syncDataSync(): void {
try {
this.#nodeFs.fdatasyncSync(this.#rid);
} catch (error) {
throw mapError(error);
}
}

Check warning on line 156 in fs/_node_fs_file.ts

View check run for this annotation

Codecov / codecov/patch

fs/_node_fs_file.ts#L150-L156

Added lines #L150 - L156 were not covered by tests

async truncate(len?: number): Promise<void> {
const nodeTruncateFd = this.#nodeUtil.promisify(this.#nodeFs.ftruncate);
try {
await nodeTruncateFd(this.#rid, len);
} catch (error) {
throw mapError(error);
}
}

Check warning on line 165 in fs/_node_fs_file.ts

View check run for this annotation

Codecov / codecov/patch

fs/_node_fs_file.ts#L158-L165

Added lines #L158 - L165 were not covered by tests

truncateSync(len?: number): void {
try {
this.#nodeFs.ftruncateSync(this.#rid, len);
} catch (error) {
throw mapError(error);
}
}

Check warning on line 173 in fs/_node_fs_file.ts

View check run for this annotation

Codecov / codecov/patch

fs/_node_fs_file.ts#L167-L173

Added lines #L167 - L173 were not covered by tests

// deno-lint-ignore require-await
async unlock(): Promise<void> {
throw new Error("Method not implemented.");
}

Check warning on line 178 in fs/_node_fs_file.ts

View check run for this annotation

Codecov / codecov/patch

fs/_node_fs_file.ts#L176-L178

Added lines #L176 - L178 were not covered by tests

unlockSync(): void {
throw new Error("Method not implemented.");
}

Check warning on line 182 in fs/_node_fs_file.ts

View check run for this annotation

Codecov / codecov/patch

fs/_node_fs_file.ts#L180-L182

Added lines #L180 - L182 were not covered by tests

async utime(atime: number | Date, mtime: number | Date): Promise<void> {
const nodeUtimeFd = this.#nodeUtil.promisify(this.#nodeFs.futimes);
try {
await nodeUtimeFd(this.#rid, atime, mtime);
} catch (error) {
throw mapError(error);
}
}

Check warning on line 191 in fs/_node_fs_file.ts

View check run for this annotation

Codecov / codecov/patch

fs/_node_fs_file.ts#L184-L191

Added lines #L184 - L191 were not covered by tests

utimeSync(atime: number | Date, mtime: number | Date): void {
try {
this.#nodeFs.futimesSync(this.#rid, atime, mtime);
} catch (error) {
throw mapError(error);
}
}

Check warning on line 199 in fs/_node_fs_file.ts

View check run for this annotation

Codecov / codecov/patch

fs/_node_fs_file.ts#L193-L199

Added lines #L193 - L199 were not covered by tests

async write(p: Uint8Array): Promise<number> {
try {
const { bytesWritten } = await this.#nodeWriteFd(this.#rid, p);
return bytesWritten;
} catch (error) {
throw mapError(error);
}
}

Check warning on line 208 in fs/_node_fs_file.ts

View check run for this annotation

Codecov / codecov/patch

fs/_node_fs_file.ts#L201-L208

Added lines #L201 - L208 were not covered by tests

writeSync(p: Uint8Array): number {
try {
return this.#nodeFs.writeSync(this.#rid, p);
} catch (error) {
throw mapError(error);
}
}

Check warning on line 216 in fs/_node_fs_file.ts

View check run for this annotation

Codecov / codecov/patch

fs/_node_fs_file.ts#L210-L216

Added lines #L210 - L216 were not covered by tests
}
Loading
Loading