Skip to content

Commit 9cb089f

Browse files
authored
fix(ext/node): add writev method to FileHandle (#27563)
Part of #25554
1 parent 7616429 commit 9cb089f

File tree

8 files changed

+118
-80
lines changed

8 files changed

+118
-80
lines changed

ext/node/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -487,7 +487,7 @@ deno_core::extension!(deno_node,
487487
"_fs/_fs_watch.ts",
488488
"_fs/_fs_write.mjs",
489489
"_fs/_fs_writeFile.ts",
490-
"_fs/_fs_writev.mjs",
490+
"_fs/_fs_writev.ts",
491491
"_next_tick.ts",
492492
"_process/exiting.ts",
493493
"_process/process.ts",

ext/node/polyfills/_fs/_fs_writev.d.ts

-65
This file was deleted.

ext/node/polyfills/_fs/_fs_writev.mjs ext/node/polyfills/_fs/_fs_writev.ts

+66-7
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,53 @@
55
// deno-lint-ignore-file prefer-primordials
66

77
import { Buffer } from "node:buffer";
8+
import process from "node:process";
9+
import { ErrnoException } from "ext:deno_node/_global.d.ts";
810
import { validateBufferArray } from "ext:deno_node/internal/fs/utils.mjs";
911
import { getValidatedFd } from "ext:deno_node/internal/fs/utils.mjs";
12+
import { WriteVResult } from "ext:deno_node/internal/fs/handle.ts";
1013
import { maybeCallback } from "ext:deno_node/_fs/_fs_common.ts";
1114
import * as io from "ext:deno_io/12_io.js";
1215
import { op_fs_seek_async, op_fs_seek_sync } from "ext:core/ops";
1316

14-
export function writev(fd, buffers, position, callback) {
17+
export interface WriteVResult {
18+
bytesWritten: number;
19+
buffers: ReadonlyArray<ArrayBufferView>;
20+
}
21+
22+
type writeVCallback = (
23+
err: ErrnoException | null,
24+
bytesWritten: number,
25+
buffers: ReadonlyArray<ArrayBufferView>,
26+
) => void;
27+
28+
/**
29+
* Write an array of `ArrayBufferView`s to the file specified by `fd` using`writev()`.
30+
*
31+
* `position` is the offset from the beginning of the file where this data
32+
* should be written. If `typeof position !== 'number'`, the data will be written
33+
* at the current position.
34+
*
35+
* The callback will be given three arguments: `err`, `bytesWritten`, and`buffers`. `bytesWritten` is how many bytes were written from `buffers`.
36+
*
37+
* If this method is `util.promisify()` ed, it returns a promise for an`Object` with `bytesWritten` and `buffers` properties.
38+
*
39+
* It is unsafe to use `fs.writev()` multiple times on the same file without
40+
* waiting for the callback. For this scenario, use {@link createWriteStream}.
41+
*
42+
* On Linux, positional writes don't work when the file is opened in append mode.
43+
* The kernel ignores the position argument and always appends the data to
44+
* the end of the file.
45+
* @since v12.9.0
46+
*/
47+
export function writev(
48+
fd: number,
49+
buffers: ReadonlyArray<ArrayBufferView>,
50+
position?: number | null,
51+
callback?: writeVCallback,
52+
): void {
1553
const innerWritev = async (fd, buffers, position) => {
16-
const chunks = [];
54+
const chunks: Buffer[] = [];
1755
const offset = 0;
1856
for (let i = 0; i < buffers.length; i++) {
1957
if (Buffer.isBuffer(buffers[i])) {
@@ -45,16 +83,24 @@ export function writev(fd, buffers, position, callback) {
4583
if (typeof position !== "number") position = null;
4684

4785
innerWritev(fd, buffers, position).then(
48-
(nwritten) => {
49-
callback(null, nwritten, buffers);
50-
},
86+
(nwritten) => callback(null, nwritten, buffers),
5187
(err) => callback(err),
5288
);
5389
}
5490

55-
export function writevSync(fd, buffers, position) {
91+
/**
92+
* For detailed information, see the documentation of the asynchronous version of
93+
* this API: {@link writev}.
94+
* @since v12.9.0
95+
* @return The number of bytes written.
96+
*/
97+
export function writevSync(
98+
fd: number,
99+
buffers: ArrayBufferView[],
100+
position?: number | null,
101+
): number {
56102
const innerWritev = (fd, buffers, position) => {
57-
const chunks = [];
103+
const chunks: Buffer[] = [];
58104
const offset = 0;
59105
for (let i = 0; i < buffers.length; i++) {
60106
if (Buffer.isBuffer(buffers[i])) {
@@ -85,3 +131,16 @@ export function writevSync(fd, buffers, position) {
85131

86132
return innerWritev(fd, buffers, position);
87133
}
134+
135+
export function writevPromise(
136+
fd: number,
137+
buffers: ArrayBufferView[],
138+
position?: number,
139+
): Promise<WriteVResult> {
140+
return new Promise((resolve, reject) => {
141+
writev(fd, buffers, position, (err, bytesWritten, buffers) => {
142+
if (err) reject(err);
143+
else resolve({ bytesWritten, buffers });
144+
});
145+
});
146+
}

ext/node/polyfills/fs.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ import {
119119
// @deno-types="./_fs/_fs_write.d.ts"
120120
import { write, writeSync } from "ext:deno_node/_fs/_fs_write.mjs";
121121
// @deno-types="./_fs/_fs_writev.d.ts"
122-
import { writev, writevSync } from "ext:deno_node/_fs/_fs_writev.mjs";
122+
import { writev, writevSync } from "ext:deno_node/_fs/_fs_writev.ts";
123123
import { readv, readvSync } from "ext:deno_node/_fs/_fs_readv.ts";
124124
import {
125125
writeFile,

ext/node/polyfills/internal/fs/handle.ts

+9-4
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,16 @@
66
import { EventEmitter } from "node:events";
77
import { Buffer } from "node:buffer";
88
import { Mode, promises, read, write } from "node:fs";
9-
export type { BigIntStats, Stats } from "ext:deno_node/_fs/_fs_stat.ts";
9+
import { core } from "ext:core/mod.js";
1010
import {
1111
BinaryOptionsArgument,
1212
FileOptionsArgument,
1313
ReadOptions,
1414
TextOptionsArgument,
1515
} from "ext:deno_node/_fs/_fs_common.ts";
1616
import { ftruncatePromise } from "ext:deno_node/_fs/_fs_ftruncate.ts";
17-
import { core } from "ext:core/mod.js";
17+
export type { BigIntStats, Stats } from "ext:deno_node/_fs/_fs_stat.ts";
18+
import { writevPromise, WriteVResult } from "ext:deno_node/_fs/_fs_writev.ts";
1819

1920
interface WriteResult {
2021
bytesWritten: number;
@@ -64,15 +65,15 @@ export class FileHandle extends EventEmitter {
6465
position,
6566
(err, bytesRead, buffer) => {
6667
if (err) reject(err);
67-
else resolve({ buffer: buffer, bytesRead: bytesRead });
68+
else resolve({ buffer, bytesRead });
6869
},
6970
);
7071
});
7172
} else {
7273
return new Promise((resolve, reject) => {
7374
read(this.fd, bufferOrOpt, (err, bytesRead, buffer) => {
7475
if (err) reject(err);
75-
else resolve({ buffer: buffer, bytesRead: bytesRead });
76+
else resolve({ buffer, bytesRead });
7677
});
7778
});
7879
}
@@ -137,6 +138,10 @@ export class FileHandle extends EventEmitter {
137138
return fsCall(promises.writeFile, this, data, options);
138139
}
139140

141+
writev(buffers: ArrayBufferView[], position?: number): Promise<WriteVResult> {
142+
return fsCall(writevPromise, this, buffers, position);
143+
}
144+
140145
close(): Promise<void> {
141146
// Note that Deno.close is not async
142147
return Promise.resolve(core.close(this.fd));

ext/node/polyfills/internal/fs/streams.mjs

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { errorOrDestroy } from "ext:deno_node/internal/streams/destroy.mjs";
1818
import { open as fsOpen } from "ext:deno_node/_fs/_fs_open.ts";
1919
import { read as fsRead } from "ext:deno_node/_fs/_fs_read.ts";
2020
import { write as fsWrite } from "ext:deno_node/_fs/_fs_write.mjs";
21-
import { writev as fsWritev } from "ext:deno_node/_fs/_fs_writev.mjs";
21+
import { writev as fsWritev } from "ext:deno_node/_fs/_fs_writev.ts";
2222
import { close as fsClose } from "ext:deno_node/_fs/_fs_close.ts";
2323
import { Buffer } from "node:buffer";
2424
import {

tests/unit_node/_fs/_fs_handle_test.ts

+39
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,45 @@ Deno.test("[node/fs filehandle.writeFile] Write to file", async function () {
118118
assertEquals(decoder.decode(data), "hello world");
119119
});
120120

121+
Deno.test(
122+
"[node/fs filehandle.writev] Write array of buffers to file",
123+
async function () {
124+
const tempFile: string = await Deno.makeTempFile();
125+
const fileHandle = await fs.open(tempFile, "w");
126+
127+
const buffer1 = Buffer.from("hello ");
128+
const buffer2 = Buffer.from("world");
129+
const res = await fileHandle.writev([buffer1, buffer2]);
130+
131+
const data = Deno.readFileSync(tempFile);
132+
await Deno.remove(tempFile);
133+
await fileHandle.close();
134+
135+
assertEquals(res.bytesWritten, 11);
136+
assertEquals(decoder.decode(data), "hello world");
137+
},
138+
);
139+
140+
Deno.test(
141+
"[node/fs filehandle.writev] Write array of buffers to file with position",
142+
async function () {
143+
const tempFile: string = await Deno.makeTempFile();
144+
const fileHandle = await fs.open(tempFile, "w");
145+
146+
const buffer1 = Buffer.from("hello ");
147+
const buffer2 = Buffer.from("world");
148+
await fileHandle.writev([buffer1, buffer2], 0);
149+
const buffer3 = Buffer.from("lorem ipsum");
150+
await fileHandle.writev([buffer3], 6);
151+
152+
const data = Deno.readFileSync(tempFile);
153+
await Deno.remove(tempFile);
154+
await fileHandle.close();
155+
156+
assertEquals(decoder.decode(data), "hello lorem ipsum");
157+
},
158+
);
159+
121160
Deno.test(
122161
"[node/fs filehandle.truncate] Truncate file with length",
123162
async function () {

tools/core_import_map.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
"ext:deno_node/_fs/_fs_stat.ts": "../ext/node/polyfills/_fs/_fs_stat.ts",
3939
"ext:deno_node/_fs/_fs_watch.ts": "../ext/node/polyfills/_fs/_fs_watch.ts",
4040
"ext:deno_node/_fs/_fs_write.mjs": "../ext/node/polyfills/_fs/_fs_write.mjs",
41-
"ext:deno_node/_fs/_fs_writev.mjs": "../ext/node/polyfills/_fs/_fs_writev.mjs",
41+
"ext:deno_node/_fs/_fs_writev.ts": "../ext/node/polyfills/_fs/_fs_writev.ts",
4242
"ext:deno_node/_global.d.ts": "../ext/node/polyfills/_global.d.ts",
4343
"node:_http_agent": "../ext/node/polyfills/_http_agent.mjs",
4444
"node:_http_common": "../ext/node/polyfills/_http_common.ts",

0 commit comments

Comments
 (0)