Skip to content

Commit a9fe160

Browse files
authored
feat(io): re-introduce IO functions (#4128)
1 parent 15a97b2 commit a9fe160

26 files changed

+639
-106
lines changed

archive/tar_test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { Tar } from "./tar.ts";
1515
import { Untar } from "./untar.ts";
1616
import { Buffer } from "../io/buffer.ts";
1717
import { copy } from "../streams/copy.ts";
18-
import { readAll } from "../streams/read_all.ts";
18+
import { readAll } from "../io/read_all.ts";
1919
import { filePath, testdataDir } from "./_test_common.ts";
2020

2121
Deno.test("createTarArchive", async function () {

archive/untar.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import {
3737
UstarFields,
3838
ustarStructure,
3939
} from "./_common.ts";
40-
import { readAll } from "../streams/read_all.ts";
40+
import { readAll } from "../io/read_all.ts";
4141
import type { Reader } from "../io/types.ts";
4242

4343
/**

archive/untar_test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
} from "./untar.ts";
1111
import { Buffer } from "../io/buffer.ts";
1212
import { copy } from "../streams/copy.ts";
13-
import { readAll } from "../streams/read_all.ts";
13+
import { readAll } from "../io/read_all.ts";
1414
import { filePath, testdataDir } from "./_test_common.ts";
1515

1616
interface TestEntry {

http/server_test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
import { ConnInfo, serve, serveListener, Server, serveTls } from "./server.ts";
33
import { mockConn as createMockConn } from "./_mock_conn.ts";
44
import { dirname, fromFileUrl, join, resolve } from "../path/mod.ts";
5-
import { writeAll } from "../streams/write_all.ts";
6-
import { readAll } from "../streams/read_all.ts";
5+
import { writeAll } from "../io/write_all.ts";
6+
import { readAll } from "../io/read_all.ts";
77
import { delay } from "../async/mod.ts";
88
import {
99
assert,

io/_common.ts

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
2+
// This module is browser compatible.
3+
4+
import type { Closer } from "./types.ts";
5+
6+
export function isCloser(value: unknown): value is Closer {
7+
return typeof value === "object" && value !== null && value !== undefined &&
8+
"close" in value &&
9+
// deno-lint-ignore no-explicit-any
10+
typeof (value as Record<string, any>)["close"] === "function";
11+
}

io/_constants.ts

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
2+
// This module is browser compatible.
3+
4+
export const DEFAULT_CHUNK_SIZE = 16_640;
5+
export const DEFAULT_BUFFER_SIZE = 32 * 1024;

io/_test_common.ts

+11
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,14 @@ export class BinaryReader implements Reader {
2727
return Promise.resolve(p.byteLength);
2828
}
2929
}
30+
31+
// N controls how many iterations of certain checks are performed.
32+
const N = 100;
33+
34+
export function init(): Uint8Array {
35+
const testBytes = new Uint8Array(N);
36+
for (let i = 0; i < N; i++) {
37+
testBytes[i] = "a".charCodeAt(0) + (i % 26);
38+
}
39+
return testBytes;
40+
}

io/buffer_test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
assertThrows,
1111
} from "../assert/mod.ts";
1212
import { Buffer } from "./buffer.ts";
13-
import { writeAllSync } from "../streams/write_all.ts";
13+
import { writeAllSync } from "./write_all.ts";
1414

1515
const MAX_SIZE = 2 ** 32 - 2;
1616
// N controls how many iterations of certain checks are performed.

io/copy.ts

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
2+
// This module is browser compatible.
3+
4+
import { DEFAULT_BUFFER_SIZE } from "./_constants.ts";
5+
import type { Reader, Writer } from "./types.ts";
6+
7+
/**
8+
* Copies from `src` to `dst` until either EOF (`null`) is read from `src` or
9+
* an error occurs. It resolves to the number of bytes copied or rejects with
10+
* the first error encountered while copying.
11+
*
12+
* @example
13+
* ```ts
14+
* import { copy } from "https://deno.land/std@$STD_VERSION/io/copy.ts";
15+
*
16+
* const source = await Deno.open("my_file.txt");
17+
* const bytesCopied1 = await copy(source, Deno.stdout);
18+
* const destination = await Deno.create("my_file_2.txt");
19+
* const bytesCopied2 = await copy(source, destination);
20+
* ```
21+
*
22+
* @param src The source to copy from
23+
* @param dst The destination to copy to
24+
* @param options Can be used to tune size of the buffer. Default size is 32kB
25+
*/
26+
export async function copy(
27+
src: Reader,
28+
dst: Writer,
29+
options?: {
30+
bufSize?: number;
31+
},
32+
): Promise<number> {
33+
let n = 0;
34+
const bufSize = options?.bufSize ?? DEFAULT_BUFFER_SIZE;
35+
const b = new Uint8Array(bufSize);
36+
let gotEOF = false;
37+
while (gotEOF === false) {
38+
const result = await src.read(b);
39+
if (result === null) {
40+
gotEOF = true;
41+
} else {
42+
let nwritten = 0;
43+
while (nwritten < result) {
44+
nwritten += await dst.write(b.subarray(nwritten, result));
45+
}
46+
n += nwritten;
47+
}
48+
}
49+
return n;
50+
}

io/copy_test.ts

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
2+
import { copy } from "./copy.ts";
3+
import { assertEquals } from "../assert/assert_equals.ts";
4+
5+
const SRC_PATH = "./io/testdata/copy-src.txt";
6+
const DST_PATH = "./io/testdata/copy-dst.txt";
7+
8+
Deno.test("copy()", async () => {
9+
try {
10+
using srcFile = await Deno.open(SRC_PATH);
11+
using dstFile = await Deno.open(DST_PATH, {
12+
create: true,
13+
write: true,
14+
});
15+
await copy(srcFile, dstFile);
16+
const srcOutput = await Deno.readFile(SRC_PATH);
17+
const dstOutput = await Deno.readFile(DST_PATH);
18+
assertEquals(srcOutput, dstOutput);
19+
} finally {
20+
await Deno.remove(DST_PATH);
21+
}
22+
});

io/limited_reader_test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { assertEquals } from "../assert/mod.ts";
33
import { LimitedReader } from "./limited_reader.ts";
44
import { StringWriter } from "./string_writer.ts";
55
import { copy } from "../streams/copy.ts";
6-
import { readAll } from "../streams/read_all.ts";
6+
import { readAll } from "./read_all.ts";
77
import { StringReader } from "./string_reader.ts";
88

99
Deno.test("ioLimitedReader", async function () {

io/mod.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,16 @@
77
* utilities are also deprecated. Consider using web streams instead.
88
*
99
* @module
10-
* @deprecated (will be removed after 1.0.0) Use the [Web Streams API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Streams_API} instead.
1110
*/
1211

1312
export * from "./buf_reader.ts";
1413
export * from "./buf_writer.ts";
1514
export * from "./buffer.ts";
15+
export * from "./copy.ts";
1616
export * from "./copy_n.ts";
1717
export * from "./limited_reader.ts";
1818
export * from "./multi_reader.ts";
19+
export * from "./read_all.ts";
1920
export * from "./read_delim.ts";
2021
export * from "./read_int.ts";
2122
export * from "./read_lines.ts";
@@ -26,3 +27,7 @@ export * from "./read_string_delim.ts";
2627
export * from "./slice_long_to_bytes.ts";
2728
export * from "./string_reader.ts";
2829
export * from "./string_writer.ts";
30+
export * from "./to_readable_stream.ts";
31+
export * from "./to_writable_stream.ts";
32+
export * from "./types.ts";
33+
export * from "./write_all.ts";

io/read_all.ts

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
2+
// This module is browser compatible.
3+
4+
import { concat } from "../bytes/concat.ts";
5+
import { DEFAULT_CHUNK_SIZE } from "./_constants.ts";
6+
import type { Reader, ReaderSync } from "./types.ts";
7+
8+
/**
9+
* Read {@linkcode Reader} `r` until EOF (`null`) and resolve to the content as
10+
* {@linkcode Uint8Array}.
11+
*
12+
* @example
13+
* ```ts
14+
* import { readAll } from "https://deno.land/std@$STD_VERSION/io/read_all.ts";
15+
*
16+
* // Example from stdin
17+
* const stdinContent = await readAll(Deno.stdin);
18+
*
19+
* // Example from file
20+
* const file = await Deno.open("my_file.txt", {read: true});
21+
* const myFileContent = await readAll(file);
22+
* file.close();
23+
* ```
24+
*/
25+
export async function readAll(reader: Reader): Promise<Uint8Array> {
26+
const chunks: Uint8Array[] = [];
27+
while (true) {
28+
let chunk = new Uint8Array(DEFAULT_CHUNK_SIZE);
29+
const n = await reader.read(chunk);
30+
if (n === null) {
31+
break;
32+
}
33+
if (n < DEFAULT_CHUNK_SIZE) {
34+
chunk = chunk.subarray(0, n);
35+
}
36+
chunks.push(chunk);
37+
}
38+
return concat(chunks);
39+
}
40+
41+
/**
42+
* Synchronously reads {@linkcode ReaderSync} `r` until EOF (`null`) and returns
43+
* the content as {@linkcode Uint8Array}.
44+
*
45+
* @example
46+
* ```ts
47+
* import { readAllSync } from "https://deno.land/std@$STD_VERSION/io/read_all.ts";
48+
*
49+
* // Example from stdin
50+
* const stdinContent = readAllSync(Deno.stdin);
51+
*
52+
* // Example from file
53+
* const file = Deno.openSync("my_file.txt", {read: true});
54+
* const myFileContent = readAllSync(file);
55+
* file.close();
56+
* ```
57+
*/
58+
export function readAllSync(reader: ReaderSync): Uint8Array {
59+
const chunks: Uint8Array[] = [];
60+
while (true) {
61+
const chunk = new Uint8Array(DEFAULT_CHUNK_SIZE);
62+
const n = reader.readSync(chunk);
63+
if (n === null) {
64+
break;
65+
}
66+
if (n < DEFAULT_CHUNK_SIZE) {
67+
chunks.push(chunk.subarray(0, n));
68+
break;
69+
}
70+
chunks.push(chunk);
71+
}
72+
return concat(chunks);
73+
}

io/read_all_test.ts

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
2+
3+
import { assert, assertEquals } from "../assert/mod.ts";
4+
import { readAll, readAllSync } from "./read_all.ts";
5+
import { Buffer } from "./buffer.ts";
6+
import { init } from "./_test_common.ts";
7+
8+
Deno.test("readAll()", async () => {
9+
const testBytes = init();
10+
assert(testBytes);
11+
const reader = new Buffer(testBytes.buffer);
12+
const actualBytes = await readAll(reader);
13+
assertEquals(testBytes, actualBytes);
14+
});
15+
16+
Deno.test("readAllSync()", () => {
17+
const testBytes = init();
18+
assert(testBytes);
19+
const reader = new Buffer(testBytes.buffer);
20+
const actualBytes = readAllSync(reader);
21+
assertEquals(testBytes, actualBytes);
22+
});

io/testdata/copy-src.txt

+1
Large diffs are not rendered by default.

io/to_readable_stream.ts

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
2+
// This module is browser compatible.
3+
4+
import { DEFAULT_CHUNK_SIZE } from "./_constants.ts";
5+
import { isCloser } from "./_common.ts";
6+
import type { Closer, Reader } from "./types.ts";
7+
8+
/** Options for {@linkcode toReadableStream}. */
9+
export interface ToReadableStreamOptions {
10+
/** If the `reader` is also a `Closer`, automatically close the `reader`
11+
* when `EOF` is encountered, or a read error occurs.
12+
*
13+
* @default {true}
14+
*/
15+
autoClose?: boolean;
16+
17+
/** The size of chunks to allocate to read, the default is ~16KiB, which is
18+
* the maximum size that Deno operations can currently support. */
19+
chunkSize?: number;
20+
21+
/** The queuing strategy to create the `ReadableStream` with. */
22+
strategy?: QueuingStrategy<Uint8Array>;
23+
}
24+
25+
/**
26+
* Create a {@linkcode ReadableStream} of {@linkcode Uint8Array}s from a
27+
* {@linkcode Reader}.
28+
*
29+
* When the pull algorithm is called on the stream, a chunk from the reader
30+
* will be read. When `null` is returned from the reader, the stream will be
31+
* closed along with the reader (if it is also a `Closer`).
32+
*
33+
* @example
34+
* ```ts
35+
* import { toReadableStream } from "https://deno.land/std@$STD_VERSION/io/to_readable_stream.ts";
36+
*
37+
* const file = await Deno.open("./file.txt", { read: true });
38+
* const fileStream = toReadableStream(file);
39+
* ```
40+
*/
41+
export function toReadableStream(
42+
reader: Reader | (Reader & Closer),
43+
{
44+
autoClose = true,
45+
chunkSize = DEFAULT_CHUNK_SIZE,
46+
strategy,
47+
}: ToReadableStreamOptions = {},
48+
): ReadableStream<Uint8Array> {
49+
return new ReadableStream({
50+
async pull(controller) {
51+
const chunk = new Uint8Array(chunkSize);
52+
try {
53+
const read = await reader.read(chunk);
54+
if (read === null) {
55+
if (isCloser(reader) && autoClose) {
56+
reader.close();
57+
}
58+
controller.close();
59+
return;
60+
}
61+
controller.enqueue(chunk.subarray(0, read));
62+
} catch (e) {
63+
controller.error(e);
64+
if (isCloser(reader)) {
65+
reader.close();
66+
}
67+
}
68+
},
69+
cancel() {
70+
if (isCloser(reader) && autoClose) {
71+
reader.close();
72+
}
73+
},
74+
}, strategy);
75+
}

0 commit comments

Comments
 (0)