Skip to content

Commit

Permalink
feat: node (denoland/deno#3319)
Browse files Browse the repository at this point in the history
  • Loading branch information
zekth authored and caspervonb committed Jan 24, 2021
1 parent 3e61396 commit 3a5dacd
Show file tree
Hide file tree
Showing 7 changed files with 327 additions and 0 deletions.
7 changes: 7 additions & 0 deletions node/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Deno Node compatibility

This module is meant to have a compatibility layer for the
[nodeJS standard library](https://nodejs.org/docs/latest-v12.x/api/).

**Warning** : Any function of this module should not be referred anywhere in the
deno standard library as it's a compatiblity module.
35 changes: 35 additions & 0 deletions node/_utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
export function notImplemented(msg?: string): never {
const message = msg ? `Not implemented: ${msg}` : "Not implemented";
throw new Error(message);
}

// API helpers

export type MaybeNull<T> = T | null;
export type MaybeDefined<T> = T | undefined;
export type MaybeEmpty<T> = T | null | undefined;

export function intoCallbackAPI<T>(
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
func: (...args: any[]) => Promise<T>,
cb: MaybeEmpty<(err: MaybeNull<Error>, value: MaybeEmpty<T>) => void>,
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
...args: any[]
): void {
func(...args)
.then(value => cb && cb(null, value))
.catch(err => cb && cb(err, null));
}

export function intoCallbackAPIWithIntercept<T1, T2>(
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
func: (...args: any[]) => Promise<T1>,
interceptor: (v: T1) => T2,
cb: MaybeEmpty<(err: MaybeNull<Error>, value: MaybeEmpty<T2>) => void>,
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
...args: any[]
): void {
func(...args)
.then(value => cb && cb(null, interceptor(value)))
.catch(err => cb && cb(err, null));
}
68 changes: 68 additions & 0 deletions node/fs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { notImplemented, intoCallbackAPIWithIntercept } from "./_utils.ts";
const { readFile: denoReadFile, readFileSync: denoReadFileSync } = Deno;

type ReadFileCallback = (err: Error | null, data: string | Uint8Array) => void;

interface ReadFileOptions {
encoding?: string | null;
flag?: string;
}

function getEncoding(
optOrCallback?: ReadFileOptions | ReadFileCallback
): string | null {
if (!optOrCallback || typeof optOrCallback === "function") {
return null;
} else {
if (optOrCallback.encoding) {
if (
optOrCallback.encoding === "utf8" ||
optOrCallback.encoding === "utf-8"
) {
return "utf8";
} else {
notImplemented();
}
}
return null;
}
}

function maybeDecode(
data: Uint8Array,
encoding: string | null
): string | Uint8Array {
if (encoding === "utf8") {
return new TextDecoder().decode(data);
}
return data;
}

export function readFile(
path: string,
optOrCallback: ReadFileCallback | ReadFileOptions,
callback?: ReadFileCallback
): void {
let cb: ReadFileCallback | undefined;
if (typeof optOrCallback === "function") {
cb = optOrCallback;
} else {
cb = callback;
}

const encoding = getEncoding(optOrCallback);

intoCallbackAPIWithIntercept<Uint8Array, string | Uint8Array>(
denoReadFile,
(data: Uint8Array): string | Uint8Array => maybeDecode(data, encoding),
cb,
path
);
}

export function readFileSync(
path: string,
opt?: ReadFileOptions
): string | Uint8Array {
return maybeDecode(denoReadFileSync(path), getEncoding(opt));
}
47 changes: 47 additions & 0 deletions node/fs_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { readFile, readFileSync } from "./fs.ts";
import { test } from "../testing/mod.ts";
import * as path from "../path/mod.ts";
import { assertEquals, assert } from "../testing/asserts.ts";

const testData = path.resolve(path.join("node", "testdata", "hello.txt"));

// Need to convert to promises, otherwise test() won't report error correctly.
test(async function readFileSuccess() {
const data = await new Promise((res, rej) => {
readFile(testData, (err, data) => {
if (err) {
rej(err);
}
res(data);
});
});

assert(data instanceof Uint8Array);
assertEquals(new TextDecoder().decode(data as Uint8Array), "hello world");
});

test(async function readFileEncodeUtf8Success() {
const data = await new Promise((res, rej) => {
readFile(testData, { encoding: "utf8" }, (err, data) => {
if (err) {
rej(err);
}
res(data);
});
});

assertEquals(typeof data, "string");
assertEquals(data as string, "hello world");
});

test(function readFileSyncSuccess() {
const data = readFileSync(testData);
assert(data instanceof Uint8Array);
assertEquals(new TextDecoder().decode(data as Uint8Array), "hello world");
});

test(function readFileEncodeUtf8Success() {
const data = readFileSync(testData, { encoding: "utf8" });
assertEquals(typeof data, "string");
assertEquals(data as string, "hello world");
});
1 change: 1 addition & 0 deletions node/testdata/hello.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
hello world
47 changes: 47 additions & 0 deletions node/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
export function isArray(value: unknown): boolean {
return Array.isArray(value);
}

export function isBoolean(value: unknown): boolean {
return typeof value === "boolean" || value instanceof Boolean;
}

export function isNull(value: unknown): boolean {
return value === null;
}

export function isNullOrUndefined(value: unknown): boolean {
return value === null || value === undefined;
}

export function isNumber(value: unknown): boolean {
return typeof value === "number" || value instanceof Number;
}

export function isString(value: unknown): boolean {
return typeof value === "string" || value instanceof String;
}

export function isSymbol(value: unknown): boolean {
return typeof value === "symbol";
}

export function isUndefined(value: unknown): boolean {
return value === undefined;
}

export function isObject(value: unknown): boolean {
return value !== null && typeof value === "object";
}

export function isError(e: unknown): boolean {
return e instanceof Error;
}

export function isFunction(value: unknown): boolean {
return typeof value === "function";
}

export function isRegExp(value: unknown): boolean {
return value instanceof RegExp;
}
122 changes: 122 additions & 0 deletions node/util_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { test } from "../testing/mod.ts";
import { assert } from "../testing/asserts.ts";
import * as util from "./util.ts";

test({
name: "[util] isBoolean",
fn() {
assert(util.isBoolean(true));
assert(util.isBoolean(new Boolean()));
assert(util.isBoolean(new Boolean(true)));
assert(util.isBoolean(false));
assert(!util.isBoolean("deno"));
assert(!util.isBoolean("true"));
}
});

test({
name: "[util] isNull",
fn() {
let n;
assert(util.isNull(null));
assert(!util.isNull(n));
assert(!util.isNull(0));
assert(!util.isNull({}));
}
});

test({
name: "[util] isNullOrUndefined",
fn() {
let n;
assert(util.isNullOrUndefined(null));
assert(util.isNullOrUndefined(n));
assert(!util.isNullOrUndefined({}));
assert(!util.isNullOrUndefined("undefined"));
}
});

test({
name: "[util] isNumber",
fn() {
assert(util.isNumber(666));
assert(util.isNumber(new Number(666)));
assert(!util.isNumber("999"));
assert(!util.isNumber(null));
}
});

test({
name: "[util] isString",
fn() {
assert(util.isString("deno"));
assert(util.isString(new String("DIO")));
assert(!util.isString(1337));
}
});

test({
name: "[util] isSymbol",
fn() {}
});

test({
name: "[util] isUndefined",
fn() {
let t;
assert(util.isUndefined(t));
assert(!util.isUndefined("undefined"));
assert(!util.isUndefined({}));
}
});

test({
name: "[util] isObject",
fn() {
const dio = { stand: "Za Warudo" };
assert(util.isObject(dio));
assert(util.isObject(new RegExp(/Toki Wo Tomare/)));
assert(!util.isObject("Jotaro"));
}
});

test({
name: "[util] isError",
fn() {
const java = new Error();
const nodejs = new TypeError();
const deno = "Future";
assert(util.isError(java));
assert(util.isError(nodejs));
assert(!util.isError(deno));
}
});

test({
name: "[util] isFunction",
fn() {
const f = function(): void {};
assert(util.isFunction(f));
assert(!util.isFunction({}));
assert(!util.isFunction(new RegExp(/f/)));
}
});

test({
name: "[util] isRegExp",
fn() {
assert(util.isRegExp(new RegExp(/f/)));
assert(util.isRegExp(/fuManchu/));
assert(!util.isRegExp({ evil: "eye" }));
assert(!util.isRegExp(null));
}
});

test({
name: "[util] isArray",
fn() {
assert(util.isArray([]));
assert(!util.isArray({ yaNo: "array" }));
assert(!util.isArray(null));
}
});

0 comments on commit 3a5dacd

Please sign in to comment.