Skip to content

Commit e97b2fd

Browse files
zekthdenobot
authored andcommitted
feat: node (denoland/deno#3319)
1 parent d1af1e5 commit e97b2fd

File tree

7 files changed

+327
-0
lines changed

7 files changed

+327
-0
lines changed

node/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Deno Node compatibility
2+
3+
This module is meant to have a compatibility layer for the
4+
[nodeJS standard library](https://nodejs.org/docs/latest-v12.x/api/).
5+
6+
**Warning** : Any function of this module should not be referred anywhere in the
7+
deno standard library as it's a compatiblity module.

node/_utils.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
export function notImplemented(msg?: string): never {
2+
const message = msg ? `Not implemented: ${msg}` : "Not implemented";
3+
throw new Error(message);
4+
}
5+
6+
// API helpers
7+
8+
export type MaybeNull<T> = T | null;
9+
export type MaybeDefined<T> = T | undefined;
10+
export type MaybeEmpty<T> = T | null | undefined;
11+
12+
export function intoCallbackAPI<T>(
13+
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
14+
func: (...args: any[]) => Promise<T>,
15+
cb: MaybeEmpty<(err: MaybeNull<Error>, value: MaybeEmpty<T>) => void>,
16+
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
17+
...args: any[]
18+
): void {
19+
func(...args)
20+
.then(value => cb && cb(null, value))
21+
.catch(err => cb && cb(err, null));
22+
}
23+
24+
export function intoCallbackAPIWithIntercept<T1, T2>(
25+
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
26+
func: (...args: any[]) => Promise<T1>,
27+
interceptor: (v: T1) => T2,
28+
cb: MaybeEmpty<(err: MaybeNull<Error>, value: MaybeEmpty<T2>) => void>,
29+
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
30+
...args: any[]
31+
): void {
32+
func(...args)
33+
.then(value => cb && cb(null, interceptor(value)))
34+
.catch(err => cb && cb(err, null));
35+
}

node/fs.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { notImplemented, intoCallbackAPIWithIntercept } from "./_utils.ts";
2+
const { readFile: denoReadFile, readFileSync: denoReadFileSync } = Deno;
3+
4+
type ReadFileCallback = (err: Error | null, data: string | Uint8Array) => void;
5+
6+
interface ReadFileOptions {
7+
encoding?: string | null;
8+
flag?: string;
9+
}
10+
11+
function getEncoding(
12+
optOrCallback?: ReadFileOptions | ReadFileCallback
13+
): string | null {
14+
if (!optOrCallback || typeof optOrCallback === "function") {
15+
return null;
16+
} else {
17+
if (optOrCallback.encoding) {
18+
if (
19+
optOrCallback.encoding === "utf8" ||
20+
optOrCallback.encoding === "utf-8"
21+
) {
22+
return "utf8";
23+
} else {
24+
notImplemented();
25+
}
26+
}
27+
return null;
28+
}
29+
}
30+
31+
function maybeDecode(
32+
data: Uint8Array,
33+
encoding: string | null
34+
): string | Uint8Array {
35+
if (encoding === "utf8") {
36+
return new TextDecoder().decode(data);
37+
}
38+
return data;
39+
}
40+
41+
export function readFile(
42+
path: string,
43+
optOrCallback: ReadFileCallback | ReadFileOptions,
44+
callback?: ReadFileCallback
45+
): void {
46+
let cb: ReadFileCallback | undefined;
47+
if (typeof optOrCallback === "function") {
48+
cb = optOrCallback;
49+
} else {
50+
cb = callback;
51+
}
52+
53+
const encoding = getEncoding(optOrCallback);
54+
55+
intoCallbackAPIWithIntercept<Uint8Array, string | Uint8Array>(
56+
denoReadFile,
57+
(data: Uint8Array): string | Uint8Array => maybeDecode(data, encoding),
58+
cb,
59+
path
60+
);
61+
}
62+
63+
export function readFileSync(
64+
path: string,
65+
opt?: ReadFileOptions
66+
): string | Uint8Array {
67+
return maybeDecode(denoReadFileSync(path), getEncoding(opt));
68+
}

node/fs_test.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { readFile, readFileSync } from "./fs.ts";
2+
import { test } from "../testing/mod.ts";
3+
import * as path from "../path/mod.ts";
4+
import { assertEquals, assert } from "../testing/asserts.ts";
5+
6+
const testData = path.resolve(path.join("node", "testdata", "hello.txt"));
7+
8+
// Need to convert to promises, otherwise test() won't report error correctly.
9+
test(async function readFileSuccess() {
10+
const data = await new Promise((res, rej) => {
11+
readFile(testData, (err, data) => {
12+
if (err) {
13+
rej(err);
14+
}
15+
res(data);
16+
});
17+
});
18+
19+
assert(data instanceof Uint8Array);
20+
assertEquals(new TextDecoder().decode(data as Uint8Array), "hello world");
21+
});
22+
23+
test(async function readFileEncodeUtf8Success() {
24+
const data = await new Promise((res, rej) => {
25+
readFile(testData, { encoding: "utf8" }, (err, data) => {
26+
if (err) {
27+
rej(err);
28+
}
29+
res(data);
30+
});
31+
});
32+
33+
assertEquals(typeof data, "string");
34+
assertEquals(data as string, "hello world");
35+
});
36+
37+
test(function readFileSyncSuccess() {
38+
const data = readFileSync(testData);
39+
assert(data instanceof Uint8Array);
40+
assertEquals(new TextDecoder().decode(data as Uint8Array), "hello world");
41+
});
42+
43+
test(function readFileEncodeUtf8Success() {
44+
const data = readFileSync(testData, { encoding: "utf8" });
45+
assertEquals(typeof data, "string");
46+
assertEquals(data as string, "hello world");
47+
});

node/testdata/hello.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
hello world

node/util.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
export function isArray(value: unknown): boolean {
2+
return Array.isArray(value);
3+
}
4+
5+
export function isBoolean(value: unknown): boolean {
6+
return typeof value === "boolean" || value instanceof Boolean;
7+
}
8+
9+
export function isNull(value: unknown): boolean {
10+
return value === null;
11+
}
12+
13+
export function isNullOrUndefined(value: unknown): boolean {
14+
return value === null || value === undefined;
15+
}
16+
17+
export function isNumber(value: unknown): boolean {
18+
return typeof value === "number" || value instanceof Number;
19+
}
20+
21+
export function isString(value: unknown): boolean {
22+
return typeof value === "string" || value instanceof String;
23+
}
24+
25+
export function isSymbol(value: unknown): boolean {
26+
return typeof value === "symbol";
27+
}
28+
29+
export function isUndefined(value: unknown): boolean {
30+
return value === undefined;
31+
}
32+
33+
export function isObject(value: unknown): boolean {
34+
return value !== null && typeof value === "object";
35+
}
36+
37+
export function isError(e: unknown): boolean {
38+
return e instanceof Error;
39+
}
40+
41+
export function isFunction(value: unknown): boolean {
42+
return typeof value === "function";
43+
}
44+
45+
export function isRegExp(value: unknown): boolean {
46+
return value instanceof RegExp;
47+
}

node/util_test.ts

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import { test } from "../testing/mod.ts";
2+
import { assert } from "../testing/asserts.ts";
3+
import * as util from "./util.ts";
4+
5+
test({
6+
name: "[util] isBoolean",
7+
fn() {
8+
assert(util.isBoolean(true));
9+
assert(util.isBoolean(new Boolean()));
10+
assert(util.isBoolean(new Boolean(true)));
11+
assert(util.isBoolean(false));
12+
assert(!util.isBoolean("deno"));
13+
assert(!util.isBoolean("true"));
14+
}
15+
});
16+
17+
test({
18+
name: "[util] isNull",
19+
fn() {
20+
let n;
21+
assert(util.isNull(null));
22+
assert(!util.isNull(n));
23+
assert(!util.isNull(0));
24+
assert(!util.isNull({}));
25+
}
26+
});
27+
28+
test({
29+
name: "[util] isNullOrUndefined",
30+
fn() {
31+
let n;
32+
assert(util.isNullOrUndefined(null));
33+
assert(util.isNullOrUndefined(n));
34+
assert(!util.isNullOrUndefined({}));
35+
assert(!util.isNullOrUndefined("undefined"));
36+
}
37+
});
38+
39+
test({
40+
name: "[util] isNumber",
41+
fn() {
42+
assert(util.isNumber(666));
43+
assert(util.isNumber(new Number(666)));
44+
assert(!util.isNumber("999"));
45+
assert(!util.isNumber(null));
46+
}
47+
});
48+
49+
test({
50+
name: "[util] isString",
51+
fn() {
52+
assert(util.isString("deno"));
53+
assert(util.isString(new String("DIO")));
54+
assert(!util.isString(1337));
55+
}
56+
});
57+
58+
test({
59+
name: "[util] isSymbol",
60+
fn() {}
61+
});
62+
63+
test({
64+
name: "[util] isUndefined",
65+
fn() {
66+
let t;
67+
assert(util.isUndefined(t));
68+
assert(!util.isUndefined("undefined"));
69+
assert(!util.isUndefined({}));
70+
}
71+
});
72+
73+
test({
74+
name: "[util] isObject",
75+
fn() {
76+
const dio = { stand: "Za Warudo" };
77+
assert(util.isObject(dio));
78+
assert(util.isObject(new RegExp(/Toki Wo Tomare/)));
79+
assert(!util.isObject("Jotaro"));
80+
}
81+
});
82+
83+
test({
84+
name: "[util] isError",
85+
fn() {
86+
const java = new Error();
87+
const nodejs = new TypeError();
88+
const deno = "Future";
89+
assert(util.isError(java));
90+
assert(util.isError(nodejs));
91+
assert(!util.isError(deno));
92+
}
93+
});
94+
95+
test({
96+
name: "[util] isFunction",
97+
fn() {
98+
const f = function(): void {};
99+
assert(util.isFunction(f));
100+
assert(!util.isFunction({}));
101+
assert(!util.isFunction(new RegExp(/f/)));
102+
}
103+
});
104+
105+
test({
106+
name: "[util] isRegExp",
107+
fn() {
108+
assert(util.isRegExp(new RegExp(/f/)));
109+
assert(util.isRegExp(/fuManchu/));
110+
assert(!util.isRegExp({ evil: "eye" }));
111+
assert(!util.isRegExp(null));
112+
}
113+
});
114+
115+
test({
116+
name: "[util] isArray",
117+
fn() {
118+
assert(util.isArray([]));
119+
assert(!util.isArray({ yaNo: "array" }));
120+
assert(!util.isArray(null));
121+
}
122+
});

0 commit comments

Comments
 (0)