-
Notifications
You must be signed in to change notification settings - Fork 638
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(fs/unstable): add readFile and readFileSync (#6394)
- Loading branch information
Showing
5 changed files
with
207 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
// Copyright 2018-2025 the Deno authors. MIT license. | ||
|
||
import { getNodeFs, isDeno } from "./_utils.ts"; | ||
import type { ReadFileOptions } from "./unstable_types.ts"; | ||
import { mapError } from "./_map_error.ts"; | ||
|
||
/** | ||
* Reads and resolves to the entire contents of a file as an array of bytes. | ||
* `TextDecoder` can be used to transform the bytes to string if required. | ||
* | ||
* Requires `allow-read` permission. | ||
* | ||
* @example Usage | ||
* ```ts | ||
* import { readFile } from "@std/fs/unstable-read-file"; | ||
* const decoder = new TextDecoder("utf-8"); | ||
* const data = await readFile("README.md"); | ||
* console.log(decoder.decode(data)); | ||
* ``` | ||
* | ||
* @tags allow-read | ||
* | ||
* @param path The path to the file. | ||
* @param options Options when reading a file. See {@linkcode ReadFileOptions}. | ||
* @returns A promise that resolves to a `Uint8Array` of the file contents. | ||
*/ | ||
export async function readFile( | ||
path: string | URL, | ||
options?: ReadFileOptions, | ||
): Promise<Uint8Array> { | ||
if (isDeno) { | ||
return Deno.readFile(path, { ...options }); | ||
} else { | ||
const { signal } = options ?? {}; | ||
try { | ||
const buf = await getNodeFs().promises.readFile(path, { signal }); | ||
return new Uint8Array(buf.buffer, buf.byteOffset, buf.length); | ||
} catch (error) { | ||
throw mapError(error); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Synchronously reads and returns the entire contents of a file as an array | ||
* of bytes. `TextDecoder` can be used to transform the bytes to string if | ||
* required. | ||
* | ||
* Requires `allow-read` permission. | ||
* | ||
* @example Usage | ||
* ```ts | ||
* import { readFileSync } from "@std/fs/unstable-read-file"; | ||
* const decoder = new TextDecoder("utf-8"); | ||
* const data = readFileSync("README.md"); | ||
* console.log(decoder.decode(data)); | ||
* ``` | ||
* | ||
* @tags allow-read | ||
* | ||
* @param path The path to the file. | ||
* @returns A `Uint8Array` of bytes representing the file contents. | ||
*/ | ||
export function readFileSync(path: string | URL): Uint8Array { | ||
if (isDeno) { | ||
return Deno.readFileSync(path); | ||
} else { | ||
try { | ||
const buf = getNodeFs().readFileSync(path); | ||
return new Uint8Array(buf.buffer, buf.byteOffset, buf.length); | ||
} catch (error) { | ||
throw mapError(error); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
// Copyright 2018-2025 the Deno authors. MIT license. | ||
|
||
import { | ||
assert, | ||
assertEquals, | ||
assertRejects, | ||
assertThrows, | ||
unreachable, | ||
} from "@std/assert"; | ||
import { readFile, readFileSync } from "./unstable_read_file.ts"; | ||
import { NotFound } from "./unstable_errors.js"; | ||
import { isDeno } from "./_utils.ts"; | ||
import { dirname, join, resolve } from "node:path"; | ||
import { fileURLToPath } from "node:url"; | ||
|
||
const moduleDir = dirname(fileURLToPath(import.meta.url)); | ||
const testdataDir = resolve(moduleDir, "testdata"); | ||
const testFile = join(testdataDir, "copy_file.txt"); | ||
|
||
Deno.test("readFile() reads a file", async () => { | ||
const decoder = new TextDecoder("utf-8"); | ||
const data = await readFile(testFile); | ||
|
||
assert(data.byteLength > 0); | ||
assertEquals(decoder.decode(data), "txt"); | ||
}); | ||
|
||
Deno.test("readFile() is called repeatedly", async () => { | ||
for (let i = 0; i < 256; i++) { | ||
await readFile(testFile); | ||
} | ||
}); | ||
|
||
Deno.test("readFile() rejects with Error when reading a file path to a directory", async () => { | ||
await assertRejects(async () => { | ||
await readFile(testdataDir); | ||
}, Error); | ||
}); | ||
|
||
Deno.test("readFile() handles an AbortSignal", async () => { | ||
const ac = new AbortController(); | ||
queueMicrotask(() => ac.abort()); | ||
|
||
const error = await assertRejects(async () => { | ||
await readFile(testFile, { signal: ac.signal }); | ||
}, Error); | ||
assertEquals(error.name, "AbortError"); | ||
}); | ||
|
||
Deno.test("readFile() handles an AbortSignal with a reason", async () => { | ||
const ac = new AbortController(); | ||
const reasonErr = new Error(); | ||
queueMicrotask(() => ac.abort(reasonErr)); | ||
|
||
const error = await assertRejects(async () => { | ||
await readFile(testFile, { signal: ac.signal }); | ||
}, Error); | ||
|
||
if (isDeno) { | ||
assertEquals(error, ac.signal.reason); | ||
} else { | ||
assertEquals(error.cause, ac.signal.reason); | ||
} | ||
}); | ||
|
||
Deno.test("readFile() handles an AbortSignal with a primitive reason value", async () => { | ||
const ac = new AbortController(); | ||
const reasonErr = "Some string"; | ||
queueMicrotask(() => ac.abort(reasonErr)); | ||
|
||
try { | ||
await readFile(testFile, { signal: ac.signal }); | ||
unreachable(); | ||
} catch (error) { | ||
if (isDeno) { | ||
assertEquals(error, ac.signal.reason); | ||
} else { | ||
const errorValue = error as Error; | ||
assertEquals(errorValue.cause, ac.signal.reason); | ||
} | ||
} | ||
}); | ||
|
||
Deno.test("readFile() handles cleanup of an AbortController", async () => { | ||
const ac = new AbortController(); | ||
await readFile(testFile, { signal: ac.signal }); | ||
}); | ||
|
||
Deno.test("readFile() rejects with NotFound when reading from a non-existent file", async () => { | ||
await assertRejects(async () => { | ||
await readFile("non-existent-file.txt"); | ||
}, NotFound); | ||
}); | ||
|
||
Deno.test("readFileSync() reads a file", () => { | ||
const decoder = new TextDecoder("utf-8"); | ||
const data = readFileSync(testFile); | ||
|
||
assert(data.byteLength > 0); | ||
assertEquals(decoder.decode(data), "txt"); | ||
}); | ||
|
||
Deno.test("readFileSync() is called repeatedly", () => { | ||
for (let i = 0; i < 256; i++) { | ||
readFileSync(testFile); | ||
} | ||
}); | ||
|
||
Deno.test("readFileSync() throws with Error when reading a file path to a directory", () => { | ||
assertThrows(() => { | ||
readFileSync(testdataDir); | ||
}, Error); | ||
}); | ||
|
||
Deno.test("readFileSync() throws with NotFound when reading from a non-existent file", () => { | ||
assertThrows(() => { | ||
readFileSync("non-existent-file.txt"); | ||
}, NotFound); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters