summaryrefslogtreecommitdiff
path: root/runtime/js
diff options
context:
space:
mode:
authorNayeem Rahman <nayeemrmn99@gmail.com>2020-12-17 16:37:57 +0000
committerGitHub <noreply@github.com>2020-12-17 17:37:57 +0100
commitffb5f7a4e1d5d4ac488058ca3ec3c0805587fe44 (patch)
treedc7b79a699732680fa309d97e5b4c2bc5f486a4a /runtime/js
parent55dc467b419b8e5897b1c832b04d63e383253d84 (diff)
refactor: Rename runtime/rt to runtime/js (#8806)
Diffstat (limited to 'runtime/js')
-rw-r--r--runtime/js/00_bootstrap_namespace.js9
-rw-r--r--runtime/js/01_build.js26
-rw-r--r--runtime/js/01_colors.js92
-rw-r--r--runtime/js/01_errors.js162
-rw-r--r--runtime/js/01_internals.js23
-rw-r--r--runtime/js/01_version.js26
-rw-r--r--runtime/js/01_web_util.js160
-rw-r--r--runtime/js/02_console.js1732
-rw-r--r--runtime/js/06_util.js148
-rw-r--r--runtime/js/10_dispatch_minimal.js114
-rw-r--r--runtime/js/11_timers.js557
-rw-r--r--runtime/js/11_workers.js206
-rw-r--r--runtime/js/12_io.js135
-rw-r--r--runtime/js/13_buffer.js241
-rw-r--r--runtime/js/27_websocket.js316
-rw-r--r--runtime/js/30_files.js209
-rw-r--r--runtime/js/30_fs.js425
-rw-r--r--runtime/js/30_metrics.js13
-rw-r--r--runtime/js/30_net.js227
-rw-r--r--runtime/js/30_os.js66
-rw-r--r--runtime/js/40_compiler_api.js97
-rw-r--r--runtime/js/40_diagnostics.js23
-rw-r--r--runtime/js/40_error_stack.js23
-rw-r--r--runtime/js/40_fs_events.js54
-rw-r--r--runtime/js/40_net_unstable.js48
-rw-r--r--runtime/js/40_performance.js341
-rw-r--r--runtime/js/40_permissions.js65
-rw-r--r--runtime/js/40_plugins.js13
-rw-r--r--runtime/js/40_process.js122
-rw-r--r--runtime/js/40_read_file.js43
-rw-r--r--runtime/js/40_signals.js265
-rw-r--r--runtime/js/40_testing.js350
-rw-r--r--runtime/js/40_tls.js82
-rw-r--r--runtime/js/40_tty.js28
-rw-r--r--runtime/js/40_write_file.js92
-rw-r--r--runtime/js/41_prompt.js80
-rw-r--r--runtime/js/90_deno_ns.js137
-rw-r--r--runtime/js/99_main.js395
-rw-r--r--runtime/js/README.md59
39 files changed, 7204 insertions, 0 deletions
diff --git a/runtime/js/00_bootstrap_namespace.js b/runtime/js/00_bootstrap_namespace.js
new file mode 100644
index 000000000..514cbe3f0
--- /dev/null
+++ b/runtime/js/00_bootstrap_namespace.js
@@ -0,0 +1,9 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+// The only purpose of this file is to set up "globalThis.__bootstrap" namespace,
+// that is used by scripts in this directory to reference exports between
+// the files.
+
+// This namespace is removed during runtime bootstrapping process.
+
+globalThis.__bootstrap = globalThis.__bootstrap || {};
diff --git a/runtime/js/01_build.js b/runtime/js/01_build.js
new file mode 100644
index 000000000..7c1dc817e
--- /dev/null
+++ b/runtime/js/01_build.js
@@ -0,0 +1,26 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const build = {
+ target: "unknown",
+ arch: "unknown",
+ os: "unknown",
+ vendor: "unknown",
+ env: undefined,
+ };
+
+ function setBuildInfo(target) {
+ const [arch, vendor, os, env] = target.split("-", 4);
+ build.target = target;
+ build.arch = arch;
+ build.vendor = vendor;
+ build.os = os;
+ build.env = env;
+ Object.freeze(build);
+ }
+
+ window.__bootstrap.build = {
+ build,
+ setBuildInfo,
+ };
+})(this);
diff --git a/runtime/js/01_colors.js b/runtime/js/01_colors.js
new file mode 100644
index 000000000..39e4a7a18
--- /dev/null
+++ b/runtime/js/01_colors.js
@@ -0,0 +1,92 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ function code(open, close) {
+ return {
+ open: `\x1b[${open}m`,
+ close: `\x1b[${close}m`,
+ regexp: new RegExp(`\\x1b\\[${close}m`, "g"),
+ };
+ }
+
+ function run(str, code) {
+ return `${code.open}${str.replace(code.regexp, code.open)}${code.close}`;
+ }
+
+ function bold(str) {
+ return run(str, code(1, 22));
+ }
+
+ function italic(str) {
+ return run(str, code(3, 23));
+ }
+
+ function yellow(str) {
+ return run(str, code(33, 39));
+ }
+
+ function cyan(str) {
+ return run(str, code(36, 39));
+ }
+
+ function red(str) {
+ return run(str, code(31, 39));
+ }
+
+ function green(str) {
+ return run(str, code(32, 39));
+ }
+
+ function bgRed(str) {
+ return run(str, code(41, 49));
+ }
+
+ function white(str) {
+ return run(str, code(37, 39));
+ }
+
+ function gray(str) {
+ return run(str, code(90, 39));
+ }
+
+ function magenta(str) {
+ return run(str, code(35, 39));
+ }
+
+ function dim(str) {
+ return run(str, code(2, 22));
+ }
+
+ // https://github.com/chalk/ansi-regex/blob/2b56fb0c7a07108e5b54241e8faec160d393aedb/index.js
+ const ANSI_PATTERN = new RegExp(
+ [
+ "[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)",
+ "(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))",
+ ].join("|"),
+ "g",
+ );
+
+ function stripColor(string) {
+ return string.replace(ANSI_PATTERN, "");
+ }
+
+ function maybeColor(fn) {
+ return !(globalThis.Deno?.noColor ?? false) ? fn : (s) => s;
+ }
+
+ window.__bootstrap.colors = {
+ bold,
+ italic,
+ yellow,
+ cyan,
+ red,
+ green,
+ bgRed,
+ white,
+ gray,
+ magenta,
+ dim,
+ stripColor,
+ maybeColor,
+ };
+})(this);
diff --git a/runtime/js/01_errors.js b/runtime/js/01_errors.js
new file mode 100644
index 000000000..d5933069b
--- /dev/null
+++ b/runtime/js/01_errors.js
@@ -0,0 +1,162 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ class NotFound extends Error {
+ constructor(msg) {
+ super(msg);
+ this.name = "NotFound";
+ }
+ }
+
+ class PermissionDenied extends Error {
+ constructor(msg) {
+ super(msg);
+ this.name = "PermissionDenied";
+ }
+ }
+
+ class ConnectionRefused extends Error {
+ constructor(msg) {
+ super(msg);
+ this.name = "ConnectionRefused";
+ }
+ }
+
+ class ConnectionReset extends Error {
+ constructor(msg) {
+ super(msg);
+ this.name = "ConnectionReset";
+ }
+ }
+
+ class ConnectionAborted extends Error {
+ constructor(msg) {
+ super(msg);
+ this.name = "ConnectionAborted";
+ }
+ }
+
+ class NotConnected extends Error {
+ constructor(msg) {
+ super(msg);
+ this.name = "NotConnected";
+ }
+ }
+
+ class AddrInUse extends Error {
+ constructor(msg) {
+ super(msg);
+ this.name = "AddrInUse";
+ }
+ }
+
+ class AddrNotAvailable extends Error {
+ constructor(msg) {
+ super(msg);
+ this.name = "AddrNotAvailable";
+ }
+ }
+
+ class BrokenPipe extends Error {
+ constructor(msg) {
+ super(msg);
+ this.name = "BrokenPipe";
+ }
+ }
+
+ class AlreadyExists extends Error {
+ constructor(msg) {
+ super(msg);
+ this.name = "AlreadyExists";
+ }
+ }
+
+ class InvalidData extends Error {
+ constructor(msg) {
+ super(msg);
+ this.name = "InvalidData";
+ }
+ }
+
+ class TimedOut extends Error {
+ constructor(msg) {
+ super(msg);
+ this.name = "TimedOut";
+ }
+ }
+
+ class Interrupted extends Error {
+ constructor(msg) {
+ super(msg);
+ this.name = "Interrupted";
+ }
+ }
+
+ class WriteZero extends Error {
+ constructor(msg) {
+ super(msg);
+ this.name = "WriteZero";
+ }
+ }
+
+ class UnexpectedEof extends Error {
+ constructor(msg) {
+ super(msg);
+ this.name = "UnexpectedEof";
+ }
+ }
+
+ class BadResource extends Error {
+ constructor(msg) {
+ super(msg);
+ this.name = "BadResource";
+ }
+ }
+
+ class Http extends Error {
+ constructor(msg) {
+ super(msg);
+ this.name = "Http";
+ }
+ }
+
+ class Busy extends Error {
+ constructor(msg) {
+ super(msg);
+ this.name = "Busy";
+ }
+ }
+
+ class NotSupported extends Error {
+ constructor(msg) {
+ super(msg);
+ this.name = "NotSupported";
+ }
+ }
+
+ const errors = {
+ NotFound,
+ PermissionDenied,
+ ConnectionRefused,
+ ConnectionReset,
+ ConnectionAborted,
+ NotConnected,
+ AddrInUse,
+ AddrNotAvailable,
+ BrokenPipe,
+ AlreadyExists,
+ InvalidData,
+ TimedOut,
+ Interrupted,
+ WriteZero,
+ UnexpectedEof,
+ BadResource,
+ Http,
+ Busy,
+ NotSupported,
+ };
+
+ window.__bootstrap.errors = {
+ errors,
+ };
+})(this);
diff --git a/runtime/js/01_internals.js b/runtime/js/01_internals.js
new file mode 100644
index 000000000..eee9eeaf7
--- /dev/null
+++ b/runtime/js/01_internals.js
@@ -0,0 +1,23 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const internalSymbol = Symbol("Deno.internal");
+
+ // The object where all the internal fields for testing will be living.
+ const internalObject = {};
+
+ // Register a field to internalObject for test access,
+ // through Deno[Deno.internal][name].
+ function exposeForTest(name, value) {
+ Object.defineProperty(internalObject, name, {
+ value,
+ enumerable: false,
+ });
+ }
+
+ window.__bootstrap.internals = {
+ internalSymbol,
+ internalObject,
+ exposeForTest,
+ };
+})(this);
diff --git a/runtime/js/01_version.js b/runtime/js/01_version.js
new file mode 100644
index 000000000..325e1156f
--- /dev/null
+++ b/runtime/js/01_version.js
@@ -0,0 +1,26 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const version = {
+ deno: "",
+ v8: "",
+ typescript: "",
+ };
+
+ function setVersions(
+ denoVersion,
+ v8Version,
+ tsVersion,
+ ) {
+ version.deno = denoVersion;
+ version.v8 = v8Version;
+ version.typescript = tsVersion;
+
+ Object.freeze(version);
+ }
+
+ window.__bootstrap.version = {
+ version,
+ setVersions,
+ };
+})(this);
diff --git a/runtime/js/01_web_util.js b/runtime/js/01_web_util.js
new file mode 100644
index 000000000..a9573a71d
--- /dev/null
+++ b/runtime/js/01_web_util.js
@@ -0,0 +1,160 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const illegalConstructorKey = Symbol("illegalConstructorKey");
+
+ function requiredArguments(
+ name,
+ length,
+ required,
+ ) {
+ if (length < required) {
+ const errMsg = `${name} requires at least ${required} argument${
+ required === 1 ? "" : "s"
+ }, but only ${length} present`;
+ throw new TypeError(errMsg);
+ }
+ }
+
+ const objectCloneMemo = new WeakMap();
+
+ function cloneArrayBuffer(
+ srcBuffer,
+ srcByteOffset,
+ srcLength,
+ _cloneConstructor,
+ ) {
+ // this function fudges the return type but SharedArrayBuffer is disabled for a while anyway
+ return srcBuffer.slice(
+ srcByteOffset,
+ srcByteOffset + srcLength,
+ );
+ }
+
+ /** Clone a value in a similar way to structured cloning. It is similar to a
+ * StructureDeserialize(StructuredSerialize(...)). */
+ function cloneValue(value) {
+ switch (typeof value) {
+ case "number":
+ case "string":
+ case "boolean":
+ case "undefined":
+ case "bigint":
+ return value;
+ case "object": {
+ if (objectCloneMemo.has(value)) {
+ return objectCloneMemo.get(value);
+ }
+ if (value === null) {
+ return value;
+ }
+ if (value instanceof Date) {
+ return new Date(value.valueOf());
+ }
+ if (value instanceof RegExp) {
+ return new RegExp(value);
+ }
+ if (value instanceof SharedArrayBuffer) {
+ return value;
+ }
+ if (value instanceof ArrayBuffer) {
+ const cloned = cloneArrayBuffer(
+ value,
+ 0,
+ value.byteLength,
+ ArrayBuffer,
+ );
+ objectCloneMemo.set(value, cloned);
+ return cloned;
+ }
+ if (ArrayBuffer.isView(value)) {
+ const clonedBuffer = cloneValue(value.buffer);
+ // Use DataViewConstructor type purely for type-checking, can be a
+ // DataView or TypedArray. They use the same constructor signature,
+ // only DataView has a length in bytes and TypedArrays use a length in
+ // terms of elements, so we adjust for that.
+ let length;
+ if (value instanceof DataView) {
+ length = value.byteLength;
+ } else {
+ length = value.length;
+ }
+ return new (value.constructor)(
+ clonedBuffer,
+ value.byteOffset,
+ length,
+ );
+ }
+ if (value instanceof Map) {
+ const clonedMap = new Map();
+ objectCloneMemo.set(value, clonedMap);
+ value.forEach((v, k) => {
+ clonedMap.set(cloneValue(k), cloneValue(v));
+ });
+ return clonedMap;
+ }
+ if (value instanceof Set) {
+ // assumes that cloneValue still takes only one argument
+ const clonedSet = new Set([...value].map(cloneValue));
+ objectCloneMemo.set(value, clonedSet);
+ return clonedSet;
+ }
+
+ // default for objects
+ const clonedObj = {};
+ objectCloneMemo.set(value, clonedObj);
+ const sourceKeys = Object.getOwnPropertyNames(value);
+ for (const key of sourceKeys) {
+ clonedObj[key] = cloneValue(value[key]);
+ }
+ Reflect.setPrototypeOf(clonedObj, Reflect.getPrototypeOf(value));
+ return clonedObj;
+ }
+ case "symbol":
+ case "function":
+ default:
+ throw new DOMException("Uncloneable value in stream", "DataCloneError");
+ }
+ }
+
+ const handlerSymbol = Symbol("eventHandlers");
+ function makeWrappedHandler(handler) {
+ function wrappedHandler(...args) {
+ if (typeof wrappedHandler.handler !== "function") {
+ return;
+ }
+ return wrappedHandler.handler.call(this, ...args);
+ }
+ wrappedHandler.handler = handler;
+ return wrappedHandler;
+ }
+ function defineEventHandler(emitter, name, defaultValue = undefined) {
+ // HTML specification section 8.1.5.1
+ Object.defineProperty(emitter, `on${name}`, {
+ get() {
+ return this[handlerSymbol]?.get(name)?.handler ?? defaultValue;
+ },
+ set(value) {
+ if (!this[handlerSymbol]) {
+ this[handlerSymbol] = new Map();
+ }
+ let handlerWrapper = this[handlerSymbol]?.get(name);
+ if (handlerWrapper) {
+ handlerWrapper.handler = value;
+ } else {
+ handlerWrapper = makeWrappedHandler(value);
+ this.addEventListener(name, handlerWrapper);
+ }
+ this[handlerSymbol].set(name, handlerWrapper);
+ },
+ configurable: true,
+ enumerable: true,
+ });
+ }
+ window.__bootstrap.webUtil = {
+ illegalConstructorKey,
+ requiredArguments,
+ defineEventHandler,
+ cloneValue,
+ };
+})(this);
diff --git a/runtime/js/02_console.js b/runtime/js/02_console.js
new file mode 100644
index 000000000..971837bd6
--- /dev/null
+++ b/runtime/js/02_console.js
@@ -0,0 +1,1732 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const core = window.Deno.core;
+ const exposeForTest = window.__bootstrap.internals.exposeForTest;
+ const colors = window.__bootstrap.colors;
+
+ function isInvalidDate(x) {
+ return isNaN(x.getTime());
+ }
+
+ function hasOwnProperty(obj, v) {
+ if (obj == null) {
+ return false;
+ }
+ return Object.prototype.hasOwnProperty.call(obj, v);
+ }
+
+ // Copyright Joyent, Inc. and other Node contributors. MIT license.
+ // Forked from Node's lib/internal/cli_table.js
+
+ function isTypedArray(x) {
+ return ArrayBuffer.isView(x) && !(x instanceof DataView);
+ }
+
+ const tableChars = {
+ middleMiddle: "─",
+ rowMiddle: "┼",
+ topRight: "┐",
+ topLeft: "┌",
+ leftMiddle: "├",
+ topMiddle: "┬",
+ bottomRight: "┘",
+ bottomLeft: "└",
+ bottomMiddle: "┴",
+ rightMiddle: "┤",
+ left: "│ ",
+ right: " │",
+ middle: " │ ",
+ };
+
+ function isFullWidthCodePoint(code) {
+ // Code points are partially derived from:
+ // http://www.unicode.org/Public/UNIDATA/EastAsianWidth.txt
+ return (
+ code >= 0x1100 &&
+ (code <= 0x115f || // Hangul Jamo
+ code === 0x2329 || // LEFT-POINTING ANGLE BRACKET
+ code === 0x232a || // RIGHT-POINTING ANGLE BRACKET
+ // CJK Radicals Supplement .. Enclosed CJK Letters and Months
+ (code >= 0x2e80 && code <= 0x3247 && code !== 0x303f) ||
+ // Enclosed CJK Letters and Months .. CJK Unified Ideographs Extension A
+ (code >= 0x3250 && code <= 0x4dbf) ||
+ // CJK Unified Ideographs .. Yi Radicals
+ (code >= 0x4e00 && code <= 0xa4c6) ||
+ // Hangul Jamo Extended-A
+ (code >= 0xa960 && code <= 0xa97c) ||
+ // Hangul Syllables
+ (code >= 0xac00 && code <= 0xd7a3) ||
+ // CJK Compatibility Ideographs
+ (code >= 0xf900 && code <= 0xfaff) ||
+ // Vertical Forms
+ (code >= 0xfe10 && code <= 0xfe19) ||
+ // CJK Compatibility Forms .. Small Form Variants
+ (code >= 0xfe30 && code <= 0xfe6b) ||
+ // Halfwidth and Fullwidth Forms
+ (code >= 0xff01 && code <= 0xff60) ||
+ (code >= 0xffe0 && code <= 0xffe6) ||
+ // Kana Supplement
+ (code >= 0x1b000 && code <= 0x1b001) ||
+ // Enclosed Ideographic Supplement
+ (code >= 0x1f200 && code <= 0x1f251) ||
+ // Miscellaneous Symbols and Pictographs 0x1f300 - 0x1f5ff
+ // Emoticons 0x1f600 - 0x1f64f
+ (code >= 0x1f300 && code <= 0x1f64f) ||
+ // CJK Unified Ideographs Extension B .. Tertiary Ideographic Plane
+ (code >= 0x20000 && code <= 0x3fffd))
+ );
+ }
+
+ function getStringWidth(str) {
+ str = colors.stripColor(str).normalize("NFC");
+ let width = 0;
+
+ for (const ch of str) {
+ width += isFullWidthCodePoint(ch.codePointAt(0)) ? 2 : 1;
+ }
+
+ return width;
+ }
+
+ function renderRow(row, columnWidths) {
+ let out = tableChars.left;
+ for (let i = 0; i < row.length; i++) {
+ const cell = row[i];
+ const len = getStringWidth(cell);
+ const needed = (columnWidths[i] - len) / 2;
+ // round(needed) + ceil(needed) will always add up to the amount
+ // of spaces we need while also left justifying the output.
+ out += `${" ".repeat(needed)}${cell}${" ".repeat(Math.ceil(needed))}`;
+ if (i !== row.length - 1) {
+ out += tableChars.middle;
+ }
+ }
+ out += tableChars.right;
+ return out;
+ }
+
+ function cliTable(head, columns) {
+ const rows = [];
+ const columnWidths = head.map((h) => getStringWidth(h));
+ const longestColumn = columns.reduce(
+ (n, a) => Math.max(n, a.length),
+ 0,
+ );
+
+ for (let i = 0; i < head.length; i++) {
+ const column = columns[i];
+ for (let j = 0; j < longestColumn; j++) {
+ if (rows[j] === undefined) {
+ rows[j] = [];
+ }
+ const value = (rows[j][i] = hasOwnProperty(column, j) ? column[j] : "");
+ const width = columnWidths[i] || 0;
+ const counted = getStringWidth(value);
+ columnWidths[i] = Math.max(width, counted);
+ }
+ }
+
+ const divider = columnWidths.map((i) =>
+ tableChars.middleMiddle.repeat(i + 2)
+ );
+
+ let result = `${tableChars.topLeft}${divider.join(tableChars.topMiddle)}` +
+ `${tableChars.topRight}\n${renderRow(head, columnWidths)}\n` +
+ `${tableChars.leftMiddle}${divider.join(tableChars.rowMiddle)}` +
+ `${tableChars.rightMiddle}\n`;
+
+ for (const row of rows) {
+ result += `${renderRow(row, columnWidths)}\n`;
+ }
+
+ result +=
+ `${tableChars.bottomLeft}${divider.join(tableChars.bottomMiddle)}` +
+ tableChars.bottomRight;
+
+ return result;
+ }
+ /* End of forked part */
+
+ const DEFAULT_INSPECT_OPTIONS = {
+ depth: 4,
+ indentLevel: 0,
+ sorted: false,
+ trailingComma: false,
+ compact: true,
+ iterableLimit: 100,
+ showProxy: false,
+ colors: false,
+ getters: false,
+ };
+
+ const DEFAULT_INDENT = " "; // Default indent string
+
+ const LINE_BREAKING_LENGTH = 80;
+ const MIN_GROUP_LENGTH = 6;
+ const STR_ABBREVIATE_SIZE = 100;
+
+ const PROMISE_STRING_BASE_LENGTH = 12;
+
+ class CSI {
+ static kClear = "\x1b[1;1H";
+ static kClearScreenDown = "\x1b[0J";
+ }
+
+ function getClassInstanceName(instance) {
+ if (typeof instance != "object") {
+ return "";
+ }
+ const constructor = instance?.constructor;
+ if (typeof constructor == "function") {
+ return constructor.name ?? "";
+ }
+ return "";
+ }
+
+ function maybeColor(fn, inspectOptions) {
+ return inspectOptions.colors ? fn : (s) => s;
+ }
+
+ function inspectFunction(value, _ctx) {
+ if (customInspect in value && typeof value[customInspect] === "function") {
+ try {
+ return String(value[customInspect]());
+ } catch {
+ // pass
+ }
+ }
+ // Might be Function/AsyncFunction/GeneratorFunction/AsyncGeneratorFunction
+ let cstrName = Object.getPrototypeOf(value)?.constructor?.name;
+ if (!cstrName) {
+ // If prototype is removed or broken,
+ // use generic 'Function' instead.
+ cstrName = "Function";
+ }
+ if (value.name && value.name !== "anonymous") {
+ // from MDN spec
+ return `[${cstrName}: ${value.name}]`;
+ }
+ return `[${cstrName}]`;
+ }
+
+ function inspectIterable(
+ value,
+ ctx,
+ level,
+ options,
+ inspectOptions,
+ ) {
+ const cyan = maybeColor(colors.cyan, inspectOptions);
+ if (level >= inspectOptions.depth) {
+ return cyan(`[${options.typeName}]`);
+ }
+ ctx.add(value);
+
+ const entries = [];
+
+ const iter = value.entries();
+ let entriesLength = 0;
+ const next = () => {
+ return iter.next();
+ };
+ for (const el of iter) {
+ if (entriesLength < inspectOptions.iterableLimit) {
+ entries.push(
+ options.entryHandler(
+ el,
+ ctx,
+ level + 1,
+ inspectOptions,
+ next.bind(iter),
+ ),
+ );
+ }
+ entriesLength++;
+ }
+ ctx.delete(value);
+
+ if (options.sort) {
+ entries.sort();
+ }
+
+ if (entriesLength > inspectOptions.iterableLimit) {
+ const nmore = entriesLength - inspectOptions.iterableLimit;
+ entries.push(`... ${nmore} more items`);
+ }
+
+ const iPrefix = `${options.displayName ? options.displayName + " " : ""}`;
+
+ const initIndentation = `\n${DEFAULT_INDENT.repeat(level + 1)}`;
+ const entryIndentation = `,\n${DEFAULT_INDENT.repeat(level + 1)}`;
+ const closingIndentation = `${inspectOptions.trailingComma ? "," : ""}\n${
+ DEFAULT_INDENT.repeat(level)
+ }`;
+
+ let iContent;
+ if (options.group && entries.length > MIN_GROUP_LENGTH) {
+ const groups = groupEntries(entries, level, value);
+ iContent = `${initIndentation}${
+ groups.join(entryIndentation)
+ }${closingIndentation}`;
+ } else {
+ iContent = entries.length === 0 ? "" : ` ${entries.join(", ")} `;
+ if (
+ colors.stripColor(iContent).length > LINE_BREAKING_LENGTH ||
+ !inspectOptions.compact
+ ) {
+ iContent = `${initIndentation}${
+ entries.join(entryIndentation)
+ }${closingIndentation}`;
+ }
+ }
+
+ return `${iPrefix}${options.delims[0]}${iContent}${options.delims[1]}`;
+ }
+
+ // Ported from Node.js
+ // Copyright Node.js contributors. All rights reserved.
+ function groupEntries(
+ entries,
+ level,
+ value,
+ iterableLimit = 100,
+ ) {
+ let totalLength = 0;
+ let maxLength = 0;
+ let entriesLength = entries.length;
+ if (iterableLimit < entriesLength) {
+ // This makes sure the "... n more items" part is not taken into account.
+ entriesLength--;
+ }
+ const separatorSpace = 2; // Add 1 for the space and 1 for the separator.
+ const dataLen = new Array(entriesLength);
+ // Calculate the total length of all output entries and the individual max
+ // entries length of all output entries.
+ // IN PROGRESS: Colors are being taken into account.
+ for (let i = 0; i < entriesLength; i++) {
+ // Taking colors into account: removing the ANSI color
+ // codes from the string before measuring its length
+ const len = colors.stripColor(entries[i]).length;
+ dataLen[i] = len;
+ totalLength += len + separatorSpace;
+ if (maxLength < len) maxLength = len;
+ }
+ // Add two to `maxLength` as we add a single whitespace character plus a comma
+ // in-between two entries.
+ const actualMax = maxLength + separatorSpace;
+ // Check if at least three entries fit next to each other and prevent grouping
+ // of arrays that contains entries of very different length (i.e., if a single
+ // entry is longer than 1/5 of all other entries combined). Otherwise the
+ // space in-between small entries would be enormous.
+ if (
+ actualMax * 3 + (level + 1) < LINE_BREAKING_LENGTH &&
+ (totalLength / actualMax > 5 || maxLength <= 6)
+ ) {
+ const approxCharHeights = 2.5;
+ const averageBias = Math.sqrt(actualMax - totalLength / entries.length);
+ const biasedMax = Math.max(actualMax - 3 - averageBias, 1);
+ // Dynamically check how many columns seem possible.
+ const columns = Math.min(
+ // Ideally a square should be drawn. We expect a character to be about 2.5
+ // times as high as wide. This is the area formula to calculate a square
+ // which contains n rectangles of size `actualMax * approxCharHeights`.
+ // Divide that by `actualMax` to receive the correct number of columns.
+ // The added bias increases the columns for short entries.
+ Math.round(
+ Math.sqrt(approxCharHeights * biasedMax * entriesLength) / biasedMax,
+ ),
+ // Do not exceed the breakLength.
+ Math.floor((LINE_BREAKING_LENGTH - (level + 1)) / actualMax),
+ // Limit the columns to a maximum of fifteen.
+ 15,
+ );
+ // Return with the original output if no grouping should happen.
+ if (columns <= 1) {
+ return entries;
+ }
+ const tmp = [];
+ const maxLineLength = [];
+ for (let i = 0; i < columns; i++) {
+ let lineMaxLength = 0;
+ for (let j = i; j < entries.length; j += columns) {
+ if (dataLen[j] > lineMaxLength) lineMaxLength = dataLen[j];
+ }
+ lineMaxLength += separatorSpace;
+ maxLineLength[i] = lineMaxLength;
+ }
+ let order = "padStart";
+ if (value !== undefined) {
+ for (let i = 0; i < entries.length; i++) {
+ if (
+ typeof value[i] !== "number" &&
+ typeof value[i] !== "bigint"
+ ) {
+ order = "padEnd";
+ break;
+ }
+ }
+ }
+ // Each iteration creates a single line of grouped entries.
+ for (let i = 0; i < entriesLength; i += columns) {
+ // The last lines may contain less entries than columns.
+ const max = Math.min(i + columns, entriesLength);
+ let str = "";
+ let j = i;
+ for (; j < max - 1; j++) {
+ const lengthOfColorCodes = entries[j].length - dataLen[j];
+ const padding = maxLineLength[j - i] + lengthOfColorCodes;
+ str += `${entries[j]}, `[order](padding, " ");
+ }
+ if (order === "padStart") {
+ const lengthOfColorCodes = entries[j].length - dataLen[j];
+ const padding = maxLineLength[j - i] +
+ lengthOfColorCodes -
+ separatorSpace;
+ str += entries[j].padStart(padding, " ");
+ } else {
+ str += entries[j];
+ }
+ tmp.push(str);
+ }
+ if (iterableLimit < entries.length) {
+ tmp.push(entries[entriesLength]);
+ }
+ entries = tmp;
+ }
+ return entries;
+ }
+
+ function inspectValue(
+ value,
+ ctx,
+ level,
+ inspectOptions,
+ ) {
+ const proxyDetails = core.getProxyDetails(value);
+ if (proxyDetails != null) {
+ return inspectOptions.showProxy
+ ? inspectProxy(proxyDetails, ctx, level, inspectOptions)
+ : inspectValue(proxyDetails[0], ctx, level, inspectOptions);
+ }
+
+ const green = maybeColor(colors.green, inspectOptions);
+ const yellow = maybeColor(colors.yellow, inspectOptions);
+ const dim = maybeColor(colors.dim, inspectOptions);
+ const cyan = maybeColor(colors.cyan, inspectOptions);
+ const bold = maybeColor(colors.bold, inspectOptions);
+ const red = maybeColor(colors.red, inspectOptions);
+
+ switch (typeof value) {
+ case "string":
+ return green(quoteString(value));
+ case "number": // Numbers are yellow
+ // Special handling of -0
+ return yellow(Object.is(value, -0) ? "-0" : `${value}`);
+ case "boolean": // booleans are yellow
+ return yellow(String(value));
+ case "undefined": // undefined is dim
+ return dim(String(value));
+ case "symbol": // Symbols are green
+ return green(maybeQuoteSymbol(value));
+ case "bigint": // Bigints are yellow
+ return yellow(`${value}n`);
+ case "function": // Function string is cyan
+ return cyan(inspectFunction(value, ctx));
+ case "object": // null is bold
+ if (value === null) {
+ return bold("null");
+ }
+
+ if (ctx.has(value)) {
+ // Circular string is cyan
+ return cyan("[Circular]");
+ }
+
+ return inspectObject(value, ctx, level, inspectOptions);
+ default:
+ // Not implemented is red
+ return red("[Not Implemented]");
+ }
+ }
+
+ // We can match Node's quoting behavior exactly by swapping the double quote and
+ // single quote in this array. That would give preference to single quotes.
+ // However, we prefer double quotes as the default.
+ const QUOTES = ['"', "'", "`"];
+
+ /** Surround the string in quotes.
+ *
+ * The quote symbol is chosen by taking the first of the `QUOTES` array which
+ * does not occur in the string. If they all occur, settle with `QUOTES[0]`.
+ *
+ * Insert a backslash before any occurrence of the chosen quote symbol and
+ * before any backslash.
+ */
+ function quoteString(string) {
+ const quote = QUOTES.find((c) => !string.includes(c)) ?? QUOTES[0];
+ const escapePattern = new RegExp(`(?=[${quote}\\\\])`, "g");
+ string = string.replace(escapePattern, "\\");
+ string = replaceEscapeSequences(string);
+ return `${quote}${string}${quote}`;
+ }
+
+ // Replace escape sequences that can modify output.
+ function replaceEscapeSequences(string) {
+ return string
+ .replace(/[\b]/g, "\\b")
+ .replace(/\f/g, "\\f")
+ .replace(/\n/g, "\\n")
+ .replace(/\r/g, "\\r")
+ .replace(/\t/g, "\\t")
+ .replace(/\v/g, "\\v")
+ .replace(
+ // deno-lint-ignore no-control-regex
+ /[\x00-\x1f\x7f-\x9f]/g,
+ (c) => "\\x" + c.charCodeAt(0).toString(16).padStart(2, "0"),
+ );
+ }
+
+ // Surround a string with quotes when it is required (e.g the string not a valid identifier).
+ function maybeQuoteString(string) {
+ if (/^[a-zA-Z_][a-zA-Z_0-9]*$/.test(string)) {
+ return replaceEscapeSequences(string);
+ }
+
+ return quoteString(string);
+ }
+
+ // Surround a symbol's description in quotes when it is required (e.g the description has non printable characters).
+ function maybeQuoteSymbol(symbol) {
+ if (symbol.description === undefined) {
+ return symbol.toString();
+ }
+
+ if (/^[a-zA-Z_][a-zA-Z_.0-9]*$/.test(symbol.description)) {
+ return symbol.toString();
+ }
+
+ return `Symbol(${quoteString(symbol.description)})`;
+ }
+
+ // Print strings when they are inside of arrays or objects with quotes
+ function inspectValueWithQuotes(
+ value,
+ ctx,
+ level,
+ inspectOptions,
+ ) {
+ const green = maybeColor(colors.green, inspectOptions);
+ switch (typeof value) {
+ case "string": {
+ const trunc = value.length > STR_ABBREVIATE_SIZE
+ ? value.slice(0, STR_ABBREVIATE_SIZE) + "..."
+ : value;
+ return green(quoteString(trunc)); // Quoted strings are green
+ }
+ default:
+ return inspectValue(value, ctx, level, inspectOptions);
+ }
+ }
+
+ function inspectArray(
+ value,
+ ctx,
+ level,
+ inspectOptions,
+ ) {
+ const dim = maybeColor(colors.dim, inspectOptions);
+ const options = {
+ typeName: "Array",
+ displayName: "",
+ delims: ["[", "]"],
+ entryHandler: (entry, ctx, level, inspectOptions, next) => {
+ const [index, val] = entry;
+ let i = index;
+ if (!value.hasOwnProperty(i)) {
+ i++;
+ while (!value.hasOwnProperty(i) && i < value.length) {
+ next();
+ i++;
+ }
+ const emptyItems = i - index;
+ const ending = emptyItems > 1 ? "s" : "";
+ return dim(`<${emptyItems} empty item${ending}>`);
+ } else {
+ return inspectValueWithQuotes(val, ctx, level, inspectOptions);
+ }
+ },
+ group: inspectOptions.compact,
+ sort: false,
+ };
+ return inspectIterable(value, ctx, level, options, inspectOptions);
+ }
+
+ function inspectTypedArray(
+ typedArrayName,
+ value,
+ ctx,
+ level,
+ inspectOptions,
+ ) {
+ const valueLength = value.length;
+ const options = {
+ typeName: typedArrayName,
+ displayName: `${typedArrayName}(${valueLength})`,
+ delims: ["[", "]"],
+ entryHandler: (entry, ctx, level, inspectOptions) => {
+ const val = entry[1];
+ return inspectValueWithQuotes(val, ctx, level + 1, inspectOptions);
+ },
+ group: inspectOptions.compact,
+ sort: false,
+ };
+ return inspectIterable(value, ctx, level, options, inspectOptions);
+ }
+
+ function inspectSet(
+ value,
+ ctx,
+ level,
+ inspectOptions,
+ ) {
+ const options = {
+ typeName: "Set",
+ displayName: "Set",
+ delims: ["{", "}"],
+ entryHandler: (entry, ctx, level, inspectOptions) => {
+ const val = entry[1];
+ return inspectValueWithQuotes(val, ctx, level + 1, inspectOptions);
+ },
+ group: false,
+ sort: inspectOptions.sorted,
+ };
+ return inspectIterable(value, ctx, level, options, inspectOptions);
+ }
+
+ function inspectMap(
+ value,
+ ctx,
+ level,
+ inspectOptions,
+ ) {
+ const options = {
+ typeName: "Map",
+ displayName: "Map",
+ delims: ["{", "}"],
+ entryHandler: (entry, ctx, level, inspectOptions) => {
+ const [key, val] = entry;
+ return `${
+ inspectValueWithQuotes(
+ key,
+ ctx,
+ level + 1,
+ inspectOptions,
+ )
+ } => ${inspectValueWithQuotes(val, ctx, level + 1, inspectOptions)}`;
+ },
+ group: false,
+ sort: inspectOptions.sorted,
+ };
+ return inspectIterable(
+ value,
+ ctx,
+ level,
+ options,
+ inspectOptions,
+ );
+ }
+
+ function inspectWeakSet(inspectOptions) {
+ const cyan = maybeColor(colors.cyan, inspectOptions);
+ return `WeakSet { ${cyan("[items unknown]")} }`; // as seen in Node, with cyan color
+ }
+
+ function inspectWeakMap(inspectOptions) {
+ const cyan = maybeColor(colors.cyan, inspectOptions);
+ return `WeakMap { ${cyan("[items unknown]")} }`; // as seen in Node, with cyan color
+ }
+
+ function inspectDate(value, inspectOptions) {
+ // without quotes, ISO format, in magenta like before
+ const magenta = maybeColor(colors.magenta, inspectOptions);
+ return magenta(isInvalidDate(value) ? "Invalid Date" : value.toISOString());
+ }
+
+ function inspectRegExp(value, inspectOptions) {
+ const red = maybeColor(colors.red, inspectOptions);
+ return red(value.toString()); // RegExps are red
+ }
+
+ function inspectStringObject(value, inspectOptions) {
+ const cyan = maybeColor(colors.cyan, inspectOptions);
+ return cyan(`[String: "${value.toString()}"]`); // wrappers are in cyan
+ }
+
+ function inspectBooleanObject(value, inspectOptions) {
+ const cyan = maybeColor(colors.cyan, inspectOptions);
+ return cyan(`[Boolean: ${value.toString()}]`); // wrappers are in cyan
+ }
+
+ function inspectNumberObject(value, inspectOptions) {
+ const cyan = maybeColor(colors.cyan, inspectOptions);
+ return cyan(`[Number: ${value.toString()}]`); // wrappers are in cyan
+ }
+
+ const PromiseState = {
+ Pending: 0,
+ Fulfilled: 1,
+ Rejected: 2,
+ };
+
+ function inspectPromise(
+ value,
+ ctx,
+ level,
+ inspectOptions,
+ ) {
+ const cyan = maybeColor(colors.cyan, inspectOptions);
+ const red = maybeColor(colors.red, inspectOptions);
+
+ const [state, result] = core.getPromiseDetails(value);
+
+ if (state === PromiseState.Pending) {
+ return `Promise { ${cyan("<pending>")} }`;
+ }
+
+ const prefix = state === PromiseState.Fulfilled
+ ? ""
+ : `${red("<rejected>")} `;
+
+ const str = `${prefix}${
+ inspectValueWithQuotes(
+ result,
+ ctx,
+ level + 1,
+ inspectOptions,
+ )
+ }`;
+
+ if (str.length + PROMISE_STRING_BASE_LENGTH > LINE_BREAKING_LENGTH) {
+ return `Promise {\n${DEFAULT_INDENT.repeat(level + 1)}${str}\n}`;
+ }
+
+ return `Promise { ${str} }`;
+ }
+
+ function inspectProxy(
+ targetAndHandler,
+ ctx,
+ level,
+ inspectOptions,
+ ) {
+ return `Proxy ${
+ inspectArray(targetAndHandler, ctx, level, inspectOptions)
+ }`;
+ }
+
+ function inspectRawObject(
+ value,
+ ctx,
+ level,
+ inspectOptions,
+ ) {
+ const cyan = maybeColor(colors.cyan, inspectOptions);
+
+ if (level >= inspectOptions.depth) {
+ return cyan("[Object]"); // wrappers are in cyan
+ }
+ ctx.add(value);
+
+ let baseString;
+
+ let shouldShowDisplayName = false;
+ let displayName = value[
+ Symbol.toStringTag
+ ];
+ if (!displayName) {
+ displayName = getClassInstanceName(value);
+ }
+ if (
+ displayName && displayName !== "Object" && displayName !== "anonymous"
+ ) {
+ shouldShowDisplayName = true;
+ }
+
+ const entries = [];
+ const stringKeys = Object.keys(value);
+ const symbolKeys = Object.getOwnPropertySymbols(value);
+ if (inspectOptions.sorted) {
+ stringKeys.sort();
+ symbolKeys.sort((s1, s2) =>
+ (s1.description ?? "").localeCompare(s2.description ?? "")
+ );
+ }
+
+ const red = maybeColor(colors.red, inspectOptions);
+
+ for (const key of stringKeys) {
+ if (inspectOptions.getters) {
+ let propertyValue;
+ let error = null;
+ try {
+ propertyValue = value[key];
+ } catch (error_) {
+ error = error_;
+ }
+ const inspectedValue = error == null
+ ? inspectValueWithQuotes(
+ propertyValue,
+ ctx,
+ level + 1,
+ inspectOptions,
+ )
+ : red(`[Thrown ${error.name}: ${error.message}]`);
+ entries.push(`${maybeQuoteString(key)}: ${inspectedValue}`);
+ } else {
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
+ if (descriptor.get !== undefined && descriptor.set !== undefined) {
+ entries.push(`${maybeQuoteString(key)}: [Getter/Setter]`);
+ } else if (descriptor.get !== undefined) {
+ entries.push(`${maybeQuoteString(key)}: [Getter]`);
+ } else {
+ entries.push(
+ `${maybeQuoteString(key)}: ${
+ inspectValueWithQuotes(value[key], ctx, level + 1, inspectOptions)
+ }`,
+ );
+ }
+ }
+ }
+
+ for (const key of symbolKeys) {
+ if (inspectOptions.getters) {
+ let propertyValue;
+ let error;
+ try {
+ propertyValue = value[key];
+ } catch (error_) {
+ error = error_;
+ }
+ const inspectedValue = error == null
+ ? inspectValueWithQuotes(
+ propertyValue,
+ ctx,
+ level + 1,
+ inspectOptions,
+ )
+ : red(`Thrown ${error.name}: ${error.message}`);
+ entries.push(`[${maybeQuoteSymbol(key)}]: ${inspectedValue}`);
+ } else {
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
+ if (descriptor.get !== undefined && descriptor.set !== undefined) {
+ entries.push(`[${maybeQuoteSymbol(key)}]: [Getter/Setter]`);
+ } else if (descriptor.get !== undefined) {
+ entries.push(`[${maybeQuoteSymbol(key)}]: [Getter]`);
+ } else {
+ entries.push(
+ `[${maybeQuoteSymbol(key)}]: ${
+ inspectValueWithQuotes(value[key], ctx, level + 1, inspectOptions)
+ }`,
+ );
+ }
+ }
+ }
+
+ // Making sure color codes are ignored when calculating the total length
+ const totalLength = entries.length + level +
+ colors.stripColor(entries.join("")).length;
+
+ ctx.delete(value);
+
+ if (entries.length === 0) {
+ baseString = "{}";
+ } else if (totalLength > LINE_BREAKING_LENGTH || !inspectOptions.compact) {
+ const entryIndent = DEFAULT_INDENT.repeat(level + 1);
+ const closingIndent = DEFAULT_INDENT.repeat(level);
+ baseString = `{\n${entryIndent}${entries.join(`,\n${entryIndent}`)}${
+ inspectOptions.trailingComma ? "," : ""
+ }\n${closingIndent}}`;
+ } else {
+ baseString = `{ ${entries.join(", ")} }`;
+ }
+
+ if (shouldShowDisplayName) {
+ baseString = `${displayName} ${baseString}`;
+ }
+
+ return baseString;
+ }
+
+ function inspectObject(
+ value,
+ consoleContext,
+ level,
+ inspectOptions,
+ ) {
+ if (customInspect in value && typeof value[customInspect] === "function") {
+ try {
+ return String(value[customInspect]());
+ } catch {
+ // pass
+ }
+ }
+ // This non-unique symbol is used to support op_crates, ie.
+ // in op_crates/web we don't want to depend on unique "Deno.customInspect"
+ // symbol defined in the public API. Internal only, shouldn't be used
+ // by users.
+ const nonUniqueCustomInspect = Symbol.for("Deno.customInspect");
+ if (
+ nonUniqueCustomInspect in value &&
+ typeof value[nonUniqueCustomInspect] === "function"
+ ) {
+ try {
+ return String(value[nonUniqueCustomInspect]());
+ } catch {
+ // pass
+ }
+ }
+ if (value instanceof Error) {
+ return String(value.stack);
+ } else if (Array.isArray(value)) {
+ return inspectArray(value, consoleContext, level, inspectOptions);
+ } else if (value instanceof Number) {
+ return inspectNumberObject(value, inspectOptions);
+ } else if (value instanceof Boolean) {
+ return inspectBooleanObject(value, inspectOptions);
+ } else if (value instanceof String) {
+ return inspectStringObject(value, inspectOptions);
+ } else if (value instanceof Promise) {
+ return inspectPromise(value, consoleContext, level, inspectOptions);
+ } else if (value instanceof RegExp) {
+ return inspectRegExp(value, inspectOptions);
+ } else if (value instanceof Date) {
+ return inspectDate(value, inspectOptions);
+ } else if (value instanceof Set) {
+ return inspectSet(value, consoleContext, level, inspectOptions);
+ } else if (value instanceof Map) {
+ return inspectMap(value, consoleContext, level, inspectOptions);
+ } else if (value instanceof WeakSet) {
+ return inspectWeakSet(inspectOptions);
+ } else if (value instanceof WeakMap) {
+ return inspectWeakMap(inspectOptions);
+ } else if (isTypedArray(value)) {
+ return inspectTypedArray(
+ Object.getPrototypeOf(value).constructor.name,
+ value,
+ consoleContext,
+ level,
+ inspectOptions,
+ );
+ } else {
+ // Otherwise, default object formatting
+ return inspectRawObject(value, consoleContext, level, inspectOptions);
+ }
+ }
+
+ const colorKeywords = new Map([
+ ["black", "#000000"],
+ ["silver", "#c0c0c0"],
+ ["gray", "#808080"],
+ ["white", "#ffffff"],
+ ["maroon", "#800000"],
+ ["red", "#ff0000"],
+ ["purple", "#800080"],
+ ["fuchsia", "#ff00ff"],
+ ["green", "#008000"],
+ ["lime", "#00ff00"],
+ ["olive", "#808000"],
+ ["yellow", "#ffff00"],
+ ["navy", "#000080"],
+ ["blue", "#0000ff"],
+ ["teal", "#008080"],
+ ["aqua", "#00ffff"],
+ ["orange", "#ffa500"],
+ ["aliceblue", "#f0f8ff"],
+ ["antiquewhite", "#faebd7"],
+ ["aquamarine", "#7fffd4"],
+ ["azure", "#f0ffff"],
+ ["beige", "#f5f5dc"],
+ ["bisque", "#ffe4c4"],
+ ["blanchedalmond", "#ffebcd"],
+ ["blueviolet", "#8a2be2"],
+ ["brown", "#a52a2a"],
+ ["burlywood", "#deb887"],
+ ["cadetblue", "#5f9ea0"],
+ ["chartreuse", "#7fff00"],
+ ["chocolate", "#d2691e"],
+ ["coral", "#ff7f50"],
+ ["cornflowerblue", "#6495ed"],
+ ["cornsilk", "#fff8dc"],
+ ["crimson", "#dc143c"],
+ ["cyan", "#00ffff"],
+ ["darkblue", "#00008b"],
+ ["darkcyan", "#008b8b"],
+ ["darkgoldenrod", "#b8860b"],
+ ["darkgray", "#a9a9a9"],
+ ["darkgreen", "#006400"],
+ ["darkgrey", "#a9a9a9"],
+ ["darkkhaki", "#bdb76b"],
+ ["darkmagenta", "#8b008b"],
+ ["darkolivegreen", "#556b2f"],
+ ["darkorange", "#ff8c00"],
+ ["darkorchid", "#9932cc"],
+ ["darkred", "#8b0000"],
+ ["darksalmon", "#e9967a"],
+ ["darkseagreen", "#8fbc8f"],
+ ["darkslateblue", "#483d8b"],
+ ["darkslategray", "#2f4f4f"],
+ ["darkslategrey", "#2f4f4f"],
+ ["darkturquoise", "#00ced1"],
+ ["darkviolet", "#9400d3"],
+ ["deeppink", "#ff1493"],
+ ["deepskyblue", "#00bfff"],
+ ["dimgray", "#696969"],
+ ["dimgrey", "#696969"],
+ ["dodgerblue", "#1e90ff"],
+ ["firebrick", "#b22222"],
+ ["floralwhite", "#fffaf0"],
+ ["forestgreen", "#228b22"],
+ ["gainsboro", "#dcdcdc"],
+ ["ghostwhite", "#f8f8ff"],
+ ["gold", "#ffd700"],
+ ["goldenrod", "#daa520"],
+ ["greenyellow", "#adff2f"],
+ ["grey", "#808080"],
+ ["honeydew", "#f0fff0"],
+ ["hotpink", "#ff69b4"],
+ ["indianred", "#cd5c5c"],
+ ["indigo", "#4b0082"],
+ ["ivory", "#fffff0"],
+ ["khaki", "#f0e68c"],
+ ["lavender", "#e6e6fa"],
+ ["lavenderblush", "#fff0f5"],
+ ["lawngreen", "#7cfc00"],
+ ["lemonchiffon", "#fffacd"],
+ ["lightblue", "#add8e6"],
+ ["lightcoral", "#f08080"],
+ ["lightcyan", "#e0ffff"],
+ ["lightgoldenrodyellow", "#fafad2"],
+ ["lightgray", "#d3d3d3"],
+ ["lightgreen", "#90ee90"],
+ ["lightgrey", "#d3d3d3"],
+ ["lightpink", "#ffb6c1"],
+ ["lightsalmon", "#ffa07a"],
+ ["lightseagreen", "#20b2aa"],
+ ["lightskyblue", "#87cefa"],
+ ["lightslategray", "#778899"],
+ ["lightslategrey", "#778899"],
+ ["lightsteelblue", "#b0c4de"],
+ ["lightyellow", "#ffffe0"],
+ ["limegreen", "#32cd32"],
+ ["linen", "#faf0e6"],
+ ["magenta", "#ff00ff"],
+ ["mediumaquamarine", "#66cdaa"],
+ ["mediumblue", "#0000cd"],
+ ["mediumorchid", "#ba55d3"],
+ ["mediumpurple", "#9370db"],
+ ["mediumseagreen", "#3cb371"],
+ ["mediumslateblue", "#7b68ee"],
+ ["mediumspringgreen", "#00fa9a"],
+ ["mediumturquoise", "#48d1cc"],
+ ["mediumvioletred", "#c71585"],
+ ["midnightblue", "#191970"],
+ ["mintcream", "#f5fffa"],
+ ["mistyrose", "#ffe4e1"],
+ ["moccasin", "#ffe4b5"],
+ ["navajowhite", "#ffdead"],
+ ["oldlace", "#fdf5e6"],
+ ["olivedrab", "#6b8e23"],
+ ["orangered", "#ff4500"],
+ ["orchid", "#da70d6"],
+ ["palegoldenrod", "#eee8aa"],
+ ["palegreen", "#98fb98"],
+ ["paleturquoise", "#afeeee"],
+ ["palevioletred", "#db7093"],
+ ["papayawhip", "#ffefd5"],
+ ["peachpuff", "#ffdab9"],
+ ["peru", "#cd853f"],
+ ["pink", "#ffc0cb"],
+ ["plum", "#dda0dd"],
+ ["powderblue", "#b0e0e6"],
+ ["rosybrown", "#bc8f8f"],
+ ["royalblue", "#4169e1"],
+ ["saddlebrown", "#8b4513"],
+ ["salmon", "#fa8072"],
+ ["sandybrown", "#f4a460"],
+ ["seagreen", "#2e8b57"],
+ ["seashell", "#fff5ee"],
+ ["sienna", "#a0522d"],
+ ["skyblue", "#87ceeb"],
+ ["slateblue", "#6a5acd"],
+ ["slategray", "#708090"],
+ ["slategrey", "#708090"],
+ ["snow", "#fffafa"],
+ ["springgreen", "#00ff7f"],
+ ["steelblue", "#4682b4"],
+ ["tan", "#d2b48c"],
+ ["thistle", "#d8bfd8"],
+ ["tomato", "#ff6347"],
+ ["turquoise", "#40e0d0"],
+ ["violet", "#ee82ee"],
+ ["wheat", "#f5deb3"],
+ ["whitesmoke", "#f5f5f5"],
+ ["yellowgreen", "#9acd32"],
+ ["rebeccapurple", "#663399"],
+ ]);
+
+ function parseCssColor(colorString) {
+ if (colorKeywords.has(colorString)) {
+ colorString = colorKeywords.get(colorString);
+ }
+ // deno-fmt-ignore
+ const hashMatch = colorString.match(/^#([\dA-Fa-f]{2})([\dA-Fa-f]{2})([\dA-Fa-f]{2})([\dA-Fa-f]{2})?$/);
+ if (hashMatch != null) {
+ return [
+ Number(`0x${hashMatch[1]}`),
+ Number(`0x${hashMatch[2]}`),
+ Number(`0x${hashMatch[3]}`),
+ ];
+ }
+ // deno-fmt-ignore
+ const smallHashMatch = colorString.match(/^#([\dA-Fa-f])([\dA-Fa-f])([\dA-Fa-f])([\dA-Fa-f])?$/);
+ if (smallHashMatch != null) {
+ return [
+ Number(`0x${smallHashMatch[1]}0`),
+ Number(`0x${smallHashMatch[2]}0`),
+ Number(`0x${smallHashMatch[3]}0`),
+ ];
+ }
+ // deno-fmt-ignore
+ const rgbMatch = colorString.match(/^rgba?\(\s*([+\-]?\d*\.?\d+)\s*,\s*([+\-]?\d*\.?\d+)\s*,\s*([+\-]?\d*\.?\d+)\s*(,\s*([+\-]?\d*\.?\d+)\s*)?\)$/);
+ if (rgbMatch != null) {
+ return [
+ Math.round(Math.max(0, Math.min(255, Number(rgbMatch[1])))),
+ Math.round(Math.max(0, Math.min(255, Number(rgbMatch[2])))),
+ Math.round(Math.max(0, Math.min(255, Number(rgbMatch[3])))),
+ ];
+ }
+ // deno-fmt-ignore
+ const hslMatch = colorString.match(/^hsla?\(\s*([+\-]?\d*\.?\d+)\s*,\s*([+\-]?\d*\.?\d+)%\s*,\s*([+\-]?\d*\.?\d+)%\s*(,\s*([+\-]?\d*\.?\d+)\s*)?\)$/);
+ if (hslMatch != null) {
+ // https://www.rapidtables.com/convert/color/hsl-to-rgb.html
+ let h = Number(hslMatch[1]) % 360;
+ if (h < 0) {
+ h += 360;
+ }
+ const s = Math.max(0, Math.min(100, Number(hslMatch[2]))) / 100;
+ const l = Math.max(0, Math.min(100, Number(hslMatch[3]))) / 100;
+ const c = (1 - Math.abs(2 * l - 1)) * s;
+ const x = c * (1 - Math.abs((h / 60) % 2 - 1));
+ const m = l - c / 2;
+ let r_;
+ let g_;
+ let b_;
+ if (h < 60) {
+ [r_, g_, b_] = [c, x, 0];
+ } else if (h < 120) {
+ [r_, g_, b_] = [x, c, 0];
+ } else if (h < 180) {
+ [r_, g_, b_] = [0, c, x];
+ } else if (h < 240) {
+ [r_, g_, b_] = [0, x, c];
+ } else if (h < 300) {
+ [r_, g_, b_] = [x, 0, c];
+ } else {
+ [r_, g_, b_] = [c, 0, x];
+ }
+ return [
+ Math.round((r_ + m) * 255),
+ Math.round((g_ + m) * 255),
+ Math.round((b_ + m) * 255),
+ ];
+ }
+ return null;
+ }
+
+ function getDefaultCss() {
+ return {
+ backgroundColor: null,
+ color: null,
+ fontWeight: null,
+ fontStyle: null,
+ textDecorationColor: null,
+ textDecorationLine: [],
+ };
+ }
+
+ function parseCss(cssString) {
+ const css = getDefaultCss();
+
+ const rawEntries = [];
+ let inValue = false;
+ let currentKey = null;
+ let parenthesesDepth = 0;
+ let currentPart = "";
+ for (let i = 0; i < cssString.length; i++) {
+ const c = cssString[i];
+ if (c == "(") {
+ parenthesesDepth++;
+ } else if (parenthesesDepth > 0) {
+ if (c == ")") {
+ parenthesesDepth--;
+ }
+ } else if (inValue) {
+ if (c == ";") {
+ const value = currentPart.trim();
+ if (value != "") {
+ rawEntries.push([currentKey, value]);
+ }
+ currentKey = null;
+ currentPart = "";
+ inValue = false;
+ continue;
+ }
+ } else if (c == ":") {
+ currentKey = currentPart.trim();
+ currentPart = "";
+ inValue = true;
+ continue;
+ }
+ currentPart += c;
+ }
+ if (inValue && parenthesesDepth == 0) {
+ const value = currentPart.trim();
+ if (value != "") {
+ rawEntries.push([currentKey, value]);
+ }
+ currentKey = null;
+ currentPart = "";
+ }
+
+ for (const [key, value] of rawEntries) {
+ if (key == "background-color") {
+ const color = parseCssColor(value);
+ if (color != null) {
+ css.backgroundColor = color;
+ }
+ } else if (key == "color") {
+ const color = parseCssColor(value);
+ if (color != null) {
+ css.color = color;
+ }
+ } else if (key == "font-weight") {
+ if (value == "bold") {
+ css.fontWeight = value;
+ }
+ } else if (key == "font-style") {
+ if (["italic", "oblique", "oblique 14deg"].includes(value)) {
+ css.fontStyle = "italic";
+ }
+ } else if (key == "text-decoration-line") {
+ css.textDecorationLine = [];
+ for (const lineType of value.split(/\s+/g)) {
+ if (["line-through", "overline", "underline"].includes(lineType)) {
+ css.textDecorationLine.push(lineType);
+ }
+ }
+ } else if (key == "text-decoration-color") {
+ const color = parseCssColor(value);
+ if (color != null) {
+ css.textDecorationColor = color;
+ }
+ } else if (key == "text-decoration") {
+ css.textDecorationColor = null;
+ css.textDecorationLine = [];
+ for (const arg of value.split(/\s+/g)) {
+ const maybeColor = parseCssColor(arg);
+ if (maybeColor != null) {
+ css.textDecorationColor = maybeColor;
+ } else if (["line-through", "overline", "underline"].includes(arg)) {
+ css.textDecorationLine.push(arg);
+ }
+ }
+ }
+ }
+
+ return css;
+ }
+
+ function colorEquals(color1, color2) {
+ return color1?.[0] == color2?.[0] && color1?.[1] == color2?.[1] &&
+ color1?.[2] == color2?.[2];
+ }
+
+ function cssToAnsi(css, prevCss = null) {
+ prevCss = prevCss ?? getDefaultCss();
+ let ansi = "";
+ if (!colorEquals(css.backgroundColor, prevCss.backgroundColor)) {
+ if (css.backgroundColor != null) {
+ const [r, g, b] = css.backgroundColor;
+ ansi += `\x1b[48;2;${r};${g};${b}m`;
+ } else {
+ ansi += "\x1b[49m";
+ }
+ }
+ if (!colorEquals(css.color, prevCss.color)) {
+ if (css.color != null) {
+ const [r, g, b] = css.color;
+ ansi += `\x1b[38;2;${r};${g};${b}m`;
+ } else {
+ ansi += "\x1b[39m";
+ }
+ }
+ if (css.fontWeight != prevCss.fontWeight) {
+ if (css.fontWeight == "bold") {
+ ansi += `\x1b[1m`;
+ } else {
+ ansi += "\x1b[22m";
+ }
+ }
+ if (css.fontStyle != prevCss.fontStyle) {
+ if (css.fontStyle == "italic") {
+ ansi += `\x1b[3m`;
+ } else {
+ ansi += "\x1b[23m";
+ }
+ }
+ if (!colorEquals(css.textDecorationColor, prevCss.textDecorationColor)) {
+ if (css.textDecorationColor != null) {
+ const [r, g, b] = css.textDecorationColor;
+ ansi += `\x1b[58;2;${r};${g};${b}m`;
+ } else {
+ ansi += "\x1b[59m";
+ }
+ }
+ if (
+ css.textDecorationLine.includes("line-through") !=
+ prevCss.textDecorationLine.includes("line-through")
+ ) {
+ if (css.textDecorationLine.includes("line-through")) {
+ ansi += "\x1b[9m";
+ } else {
+ ansi += "\x1b[29m";
+ }
+ }
+ if (
+ css.textDecorationLine.includes("overline") !=
+ prevCss.textDecorationLine.includes("overline")
+ ) {
+ if (css.textDecorationLine.includes("overline")) {
+ ansi += "\x1b[53m";
+ } else {
+ ansi += "\x1b[55m";
+ }
+ }
+ if (
+ css.textDecorationLine.includes("underline") !=
+ prevCss.textDecorationLine.includes("underline")
+ ) {
+ if (css.textDecorationLine.includes("underline")) {
+ ansi += "\x1b[4m";
+ } else {
+ ansi += "\x1b[24m";
+ }
+ }
+ return ansi;
+ }
+
+ function inspectArgs(args, inspectOptions = {}) {
+ const noColor = globalThis.Deno?.noColor ?? true;
+ const rInspectOptions = { ...DEFAULT_INSPECT_OPTIONS, ...inspectOptions };
+ const first = args[0];
+ let a = 0;
+ let string = "";
+
+ if (typeof first == "string" && args.length > 1) {
+ a++;
+ // Index of the first not-yet-appended character. Use this so we only
+ // have to append to `string` when a substitution occurs / at the end.
+ let appendedChars = 0;
+ let usedStyle = false;
+ let prevCss = null;
+ for (let i = 0; i < first.length - 1; i++) {
+ if (first[i] == "%") {
+ const char = first[++i];
+ if (a < args.length) {
+ let formattedArg = null;
+ if (char == "s") {
+ // Format as a string.
+ formattedArg = String(args[a++]);
+ } else if (["d", "i"].includes(char)) {
+ // Format as an integer.
+ const value = args[a++];
+ if (typeof value == "bigint") {
+ formattedArg = `${value}n`;
+ } else if (typeof value == "number") {
+ formattedArg = `${parseInt(String(value))}`;
+ } else {
+ formattedArg = "NaN";
+ }
+ } else if (char == "f") {
+ // Format as a floating point value.
+ const value = args[a++];
+ if (typeof value == "number") {
+ formattedArg = `${value}`;
+ } else {
+ formattedArg = "NaN";
+ }
+ } else if (["O", "o"].includes(char)) {
+ // Format as an object.
+ formattedArg = inspectValue(
+ args[a++],
+ new Set(),
+ 0,
+ rInspectOptions,
+ );
+ } else if (char == "c") {
+ const value = args[a++];
+ if (!noColor) {
+ const css = parseCss(value);
+ formattedArg = cssToAnsi(css, prevCss);
+ if (formattedArg != "") {
+ usedStyle = true;
+ prevCss = css;
+ }
+ } else {
+ formattedArg = "";
+ }
+ }
+
+ if (formattedArg != null) {
+ string += first.slice(appendedChars, i - 1) + formattedArg;
+ appendedChars = i + 1;
+ }
+ }
+ if (char == "%") {
+ string += first.slice(appendedChars, i - 1) + "%";
+ appendedChars = i + 1;
+ }
+ }
+ }
+ string += first.slice(appendedChars);
+ if (usedStyle) {
+ string += "\x1b[0m";
+ }
+ }
+
+ for (; a < args.length; a++) {
+ if (a > 0) {
+ string += " ";
+ }
+ if (typeof args[a] == "string") {
+ string += args[a];
+ } else {
+ // Use default maximum depth for null or undefined arguments.
+ string += inspectValue(args[a], new Set(), 0, rInspectOptions);
+ }
+ }
+
+ if (rInspectOptions.indentLevel > 0) {
+ const groupIndent = DEFAULT_INDENT.repeat(rInspectOptions.indentLevel);
+ string = groupIndent + string.replaceAll("\n", `\n${groupIndent}`);
+ }
+
+ return string;
+ }
+
+ const countMap = new Map();
+ const timerMap = new Map();
+ const isConsoleInstance = Symbol("isConsoleInstance");
+
+ function getConsoleInspectOptions() {
+ return {
+ ...DEFAULT_INSPECT_OPTIONS,
+ colors: !(globalThis.Deno?.noColor ?? false),
+ };
+ }
+
+ class Console {
+ #printFunc = null;
+ [isConsoleInstance] = false;
+
+ constructor(printFunc) {
+ this.#printFunc = printFunc;
+ this.indentLevel = 0;
+ this[isConsoleInstance] = true;
+
+ // ref https://console.spec.whatwg.org/#console-namespace
+ // For historical web-compatibility reasons, the namespace object for
+ // console must have as its [[Prototype]] an empty object, created as if
+ // by ObjectCreate(%ObjectPrototype%), instead of %ObjectPrototype%.
+ const console = Object.create({});
+ Object.assign(console, this);
+ return console;
+ }
+
+ log = (...args) => {
+ this.#printFunc(
+ inspectArgs(args, {
+ ...getConsoleInspectOptions(),
+ indentLevel: this.indentLevel,
+ }) + "\n",
+ false,
+ );
+ };
+
+ debug = this.log;
+ info = this.log;
+
+ dir = (obj, options = {}) => {
+ this.#printFunc(
+ inspectArgs([obj], { ...getConsoleInspectOptions(), ...options }) +
+ "\n",
+ false,
+ );
+ };
+
+ dirxml = this.dir;
+
+ warn = (...args) => {
+ this.#printFunc(
+ inspectArgs(args, {
+ ...getConsoleInspectOptions(),
+ indentLevel: this.indentLevel,
+ }) + "\n",
+ true,
+ );
+ };
+
+ error = this.warn;
+
+ assert = (condition = false, ...args) => {
+ if (condition) {
+ return;
+ }
+
+ if (args.length === 0) {
+ this.error("Assertion failed");
+ return;
+ }
+
+ const [first, ...rest] = args;
+
+ if (typeof first === "string") {
+ this.error(`Assertion failed: ${first}`, ...rest);
+ return;
+ }
+
+ this.error(`Assertion failed:`, ...args);
+ };
+
+ count = (label = "default") => {
+ label = String(label);
+
+ if (countMap.has(label)) {
+ const current = countMap.get(label) || 0;
+ countMap.set(label, current + 1);
+ } else {
+ countMap.set(label, 1);
+ }
+
+ this.info(`${label}: ${countMap.get(label)}`);
+ };
+
+ countReset = (label = "default") => {
+ label = String(label);
+
+ if (countMap.has(label)) {
+ countMap.set(label, 0);
+ } else {
+ this.warn(`Count for '${label}' does not exist`);
+ }
+ };
+
+ table = (data, properties) => {
+ if (properties !== undefined && !Array.isArray(properties)) {
+ throw new Error(
+ "The 'properties' argument must be of type Array. " +
+ "Received type string",
+ );
+ }
+
+ if (data === null || typeof data !== "object") {
+ return this.log(data);
+ }
+
+ const objectValues = {};
+ const indexKeys = [];
+ const values = [];
+
+ const stringifyValue = (value) =>
+ inspectValueWithQuotes(value, new Set(), 0, {
+ ...DEFAULT_INSPECT_OPTIONS,
+ depth: 1,
+ });
+ const toTable = (header, body) => this.log(cliTable(header, body));
+ const createColumn = (value, shift) => [
+ ...(shift ? [...new Array(shift)].map(() => "") : []),
+ stringifyValue(value),
+ ];
+
+ let resultData;
+ const isSet = data instanceof Set;
+ const isMap = data instanceof Map;
+ const valuesKey = "Values";
+ const indexKey = isSet || isMap ? "(iter idx)" : "(idx)";
+
+ if (data instanceof Set) {
+ resultData = [...data];
+ } else if (data instanceof Map) {
+ let idx = 0;
+ resultData = {};
+
+ data.forEach((v, k) => {
+ resultData[idx] = { Key: k, Values: v };
+ idx++;
+ });
+ } else {
+ resultData = data;
+ }
+
+ let hasPrimitives = false;
+ Object.keys(resultData).forEach((k, idx) => {
+ const value = resultData[k];
+ const primitive = value === null ||
+ (typeof value !== "function" && typeof value !== "object");
+ if (properties === undefined && primitive) {
+ hasPrimitives = true;
+ values.push(stringifyValue(value));
+ } else {
+ const valueObj = value || {};
+ const keys = properties || Object.keys(valueObj);
+ for (const k of keys) {
+ if (primitive || !valueObj.hasOwnProperty(k)) {
+ if (objectValues[k]) {
+ // fill with blanks for idx to avoid misplacing from later values
+ objectValues[k].push("");
+ }
+ } else {
+ if (objectValues[k]) {
+ objectValues[k].push(stringifyValue(valueObj[k]));
+ } else {
+ objectValues[k] = createColumn(valueObj[k], idx);
+ }
+ }
+ }
+ values.push("");
+ }
+
+ indexKeys.push(k);
+ });
+
+ const headerKeys = Object.keys(objectValues);
+ const bodyValues = Object.values(objectValues);
+ const header = [
+ indexKey,
+ ...(properties ||
+ [...headerKeys, !isMap && hasPrimitives && valuesKey]),
+ ].filter(Boolean);
+ const body = [indexKeys, ...bodyValues, values];
+
+ toTable(header, body);
+ };
+
+ time = (label = "default") => {
+ label = String(label);
+
+ if (timerMap.has(label)) {
+ this.warn(`Timer '${label}' already exists`);
+ return;
+ }
+
+ timerMap.set(label, Date.now());
+ };
+
+ timeLog = (label = "default", ...args) => {
+ label = String(label);
+
+ if (!timerMap.has(label)) {
+ this.warn(`Timer '${label}' does not exists`);
+ return;
+ }
+
+ const startTime = timerMap.get(label);
+ const duration = Date.now() - startTime;
+
+ this.info(`${label}: ${duration}ms`, ...args);
+ };
+
+ timeEnd = (label = "default") => {
+ label = String(label);
+
+ if (!timerMap.has(label)) {
+ this.warn(`Timer '${label}' does not exists`);
+ return;
+ }
+
+ const startTime = timerMap.get(label);
+ timerMap.delete(label);
+ const duration = Date.now() - startTime;
+
+ this.info(`${label}: ${duration}ms`);
+ };
+
+ group = (...label) => {
+ if (label.length > 0) {
+ this.log(...label);
+ }
+ this.indentLevel += 2;
+ };
+
+ groupCollapsed = this.group;
+
+ groupEnd = () => {
+ if (this.indentLevel > 0) {
+ this.indentLevel -= 2;
+ }
+ };
+
+ clear = () => {
+ this.indentLevel = 0;
+ this.#printFunc(CSI.kClear, false);
+ this.#printFunc(CSI.kClearScreenDown, false);
+ };
+
+ trace = (...args) => {
+ const message = inspectArgs(
+ args,
+ { ...getConsoleInspectOptions(), indentLevel: 0 },
+ );
+ const err = {
+ name: "Trace",
+ message,
+ };
+ Error.captureStackTrace(err, this.trace);
+ this.error(err.stack);
+ };
+
+ static [Symbol.hasInstance](instance) {
+ return instance[isConsoleInstance];
+ }
+ }
+
+ const customInspect = Symbol("Deno.customInspect");
+
+ function inspect(
+ value,
+ inspectOptions = {},
+ ) {
+ return inspectValue(value, new Set(), 0, {
+ ...DEFAULT_INSPECT_OPTIONS,
+ ...inspectOptions,
+ // TODO(nayeemrmn): Indent level is not supported.
+ indentLevel: 0,
+ });
+ }
+
+ // Expose these fields to internalObject for tests.
+ exposeForTest("Console", Console);
+ exposeForTest("cssToAnsi", cssToAnsi);
+ exposeForTest("inspectArgs", inspectArgs);
+ exposeForTest("parseCss", parseCss);
+ exposeForTest("parseCssColor", parseCssColor);
+
+ window.__bootstrap.console = {
+ CSI,
+ inspectArgs,
+ Console,
+ customInspect,
+ inspect,
+ };
+})(this);
diff --git a/runtime/js/06_util.js b/runtime/js/06_util.js
new file mode 100644
index 000000000..f4804c519
--- /dev/null
+++ b/runtime/js/06_util.js
@@ -0,0 +1,148 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const { build } = window.__bootstrap.build;
+ const internals = window.__bootstrap.internals;
+ let logDebug = false;
+ let logSource = "JS";
+
+ function setLogDebug(debug, source) {
+ logDebug = debug;
+ if (source) {
+ logSource = source;
+ }
+ }
+
+ function log(...args) {
+ if (logDebug) {
+ // if we destructure `console` off `globalThis` too early, we don't bind to
+ // the right console, therefore we don't log anything out.
+ globalThis.console.log(`DEBUG ${logSource} -`, ...args);
+ }
+ }
+
+ class AssertionError extends Error {
+ constructor(msg) {
+ super(msg);
+ this.name = "AssertionError";
+ }
+ }
+
+ function assert(cond, msg = "Assertion failed.") {
+ if (!cond) {
+ throw new AssertionError(msg);
+ }
+ }
+
+ function createResolvable() {
+ let resolve;
+ let reject;
+ const promise = new Promise((res, rej) => {
+ resolve = res;
+ reject = rej;
+ });
+ promise.resolve = resolve;
+ promise.reject = reject;
+ return promise;
+ }
+
+ function immutableDefine(
+ o,
+ p,
+ value,
+ ) {
+ Object.defineProperty(o, p, {
+ value,
+ configurable: false,
+ writable: false,
+ });
+ }
+
+ // Keep in sync with `fromFileUrl()` in `std/path/win32.ts`.
+ function pathFromURLWin32(url) {
+ let path = decodeURIComponent(
+ url.pathname
+ .replace(/^\/*([A-Za-z]:)(\/|$)/, "$1/")
+ .replace(/\//g, "\\")
+ .replace(/%(?![0-9A-Fa-f]{2})/g, "%25"),
+ );
+ if (url.hostname != "") {
+ // Note: The `URL` implementation guarantees that the drive letter and
+ // hostname are mutually exclusive. Otherwise it would not have been valid
+ // to append the hostname and path like this.
+ path = `\\\\${url.hostname}${path}`;
+ }
+ return path;
+ }
+
+ // Keep in sync with `fromFileUrl()` in `std/path/posix.ts`.
+ function pathFromURLPosix(url) {
+ if (url.hostname !== "") {
+ throw new TypeError(`Host must be empty.`);
+ }
+
+ return decodeURIComponent(
+ url.pathname.replace(/%(?![0-9A-Fa-f]{2})/g, "%25"),
+ );
+ }
+
+ function pathFromURL(pathOrUrl) {
+ if (pathOrUrl instanceof URL) {
+ if (pathOrUrl.protocol != "file:") {
+ throw new TypeError("Must be a file URL.");
+ }
+
+ return build.os == "windows"
+ ? pathFromURLWin32(pathOrUrl)
+ : pathFromURLPosix(pathOrUrl);
+ }
+ return pathOrUrl;
+ }
+
+ internals.exposeForTest("pathFromURL", pathFromURL);
+
+ function writable(value) {
+ return {
+ value,
+ writable: true,
+ enumerable: true,
+ configurable: true,
+ };
+ }
+
+ function nonEnumerable(value) {
+ return {
+ value,
+ writable: true,
+ configurable: true,
+ };
+ }
+
+ function readOnly(value) {
+ return {
+ value,
+ enumerable: true,
+ };
+ }
+
+ function getterOnly(getter) {
+ return {
+ get: getter,
+ enumerable: true,
+ };
+ }
+
+ window.__bootstrap.util = {
+ log,
+ setLogDebug,
+ createResolvable,
+ assert,
+ AssertionError,
+ immutableDefine,
+ pathFromURL,
+ writable,
+ nonEnumerable,
+ readOnly,
+ getterOnly,
+ };
+})(this);
diff --git a/runtime/js/10_dispatch_minimal.js b/runtime/js/10_dispatch_minimal.js
new file mode 100644
index 000000000..dceb23e5f
--- /dev/null
+++ b/runtime/js/10_dispatch_minimal.js
@@ -0,0 +1,114 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const core = window.Deno.core;
+ const util = window.__bootstrap.util;
+
+ // Using an object without a prototype because `Map` was causing GC problems.
+ const promiseTableMin = Object.create(null);
+
+ const decoder = new TextDecoder();
+
+ // Note it's important that promiseId starts at 1 instead of 0, because sync
+ // messages are indicated with promiseId 0. If we ever add wrap around logic for
+ // overflows, this should be taken into account.
+ let _nextPromiseId = 1;
+
+ function nextPromiseId() {
+ return _nextPromiseId++;
+ }
+
+ function recordFromBufMinimal(ui8) {
+ const headerLen = 12;
+ const header = ui8.subarray(0, headerLen);
+ const buf32 = new Int32Array(
+ header.buffer,
+ header.byteOffset,
+ header.byteLength / 4,
+ );
+ const promiseId = buf32[0];
+ const arg = buf32[1];
+ const result = buf32[2];
+ let err;
+
+ if (arg < 0) {
+ err = {
+ className: decoder.decode(ui8.subarray(headerLen, headerLen + result)),
+ message: decoder.decode(ui8.subarray(headerLen + result)),
+ };
+ } else if (ui8.length != 12) {
+ throw new TypeError("Malformed response message");
+ }
+
+ return {
+ promiseId,
+ arg,
+ result,
+ err,
+ };
+ }
+
+ function unwrapResponse(res) {
+ if (res.err != null) {
+ const ErrorClass = core.getErrorClass(res.err.className);
+ if (!ErrorClass) {
+ throw new Error(
+ `Unregistered error class: "${res.err.className}"\n ${res.err.message}\n Classes of errors returned from ops should be registered via Deno.core.registerErrorClass().`,
+ );
+ }
+ throw new ErrorClass(res.err.message);
+ }
+ return res.result;
+ }
+
+ const scratch32 = new Int32Array(3);
+ const scratchBytes = new Uint8Array(
+ scratch32.buffer,
+ scratch32.byteOffset,
+ scratch32.byteLength,
+ );
+ util.assert(scratchBytes.byteLength === scratch32.length * 4);
+
+ function asyncMsgFromRust(ui8) {
+ const record = recordFromBufMinimal(ui8);
+ const { promiseId } = record;
+ const promise = promiseTableMin[promiseId];
+ delete promiseTableMin[promiseId];
+ util.assert(promise);
+ promise.resolve(record);
+ }
+
+ async function sendAsync(opName, arg, zeroCopy) {
+ const promiseId = nextPromiseId(); // AKA cmdId
+ scratch32[0] = promiseId;
+ scratch32[1] = arg;
+ scratch32[2] = 0; // result
+ const promise = util.createResolvable();
+ const buf = core.dispatchByName(opName, scratchBytes, zeroCopy);
+ if (buf != null) {
+ const record = recordFromBufMinimal(buf);
+ // Sync result.
+ promise.resolve(record);
+ } else {
+ // Async result.
+ promiseTableMin[promiseId] = promise;
+ }
+
+ const res = await promise;
+ return unwrapResponse(res);
+ }
+
+ function sendSync(opName, arg, zeroCopy) {
+ scratch32[0] = 0; // promiseId 0 indicates sync
+ scratch32[1] = arg;
+ const res = core.dispatchByName(opName, scratchBytes, zeroCopy);
+ const resRecord = recordFromBufMinimal(res);
+ return unwrapResponse(resRecord);
+ }
+
+ window.__bootstrap.dispatchMinimal = {
+ asyncMsgFromRust,
+ sendSync,
+ sendAsync,
+ };
+})(this);
diff --git a/runtime/js/11_timers.js b/runtime/js/11_timers.js
new file mode 100644
index 000000000..5a59844a3
--- /dev/null
+++ b/runtime/js/11_timers.js
@@ -0,0 +1,557 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const assert = window.__bootstrap.util.assert;
+ const core = window.Deno.core;
+ const { sendSync } = window.__bootstrap.dispatchMinimal;
+
+ function opStopGlobalTimer() {
+ core.jsonOpSync("op_global_timer_stop");
+ }
+
+ function opStartGlobalTimer(timeout) {
+ return core.jsonOpSync("op_global_timer_start", { timeout });
+ }
+
+ async function opWaitGlobalTimer() {
+ await core.jsonOpAsync("op_global_timer");
+ }
+
+ const nowBytes = new Uint8Array(8);
+ function opNow() {
+ sendSync("op_now", 0, nowBytes);
+ return new DataView(nowBytes.buffer).getFloat64();
+ }
+
+ function sleepSync(millis = 0) {
+ return core.jsonOpSync("op_sleep_sync", { millis });
+ }
+
+ // Derived from https://github.com/vadimg/js_bintrees. MIT Licensed.
+
+ class RBNode {
+ constructor(data) {
+ this.data = data;
+ this.left = null;
+ this.right = null;
+ this.red = true;
+ }
+
+ getChild(dir) {
+ return dir ? this.right : this.left;
+ }
+
+ setChild(dir, val) {
+ if (dir) {
+ this.right = val;
+ } else {
+ this.left = val;
+ }
+ }
+ }
+
+ class RBTree {
+ #comparator = null;
+ #root = null;
+
+ constructor(comparator) {
+ this.#comparator = comparator;
+ this.#root = null;
+ }
+
+ /** Returns `null` if tree is empty. */
+ min() {
+ let res = this.#root;
+ if (res === null) {
+ return null;
+ }
+ while (res.left !== null) {
+ res = res.left;
+ }
+ return res.data;
+ }
+
+ /** Returns node `data` if found, `null` otherwise. */
+ find(data) {
+ let res = this.#root;
+ while (res !== null) {
+ const c = this.#comparator(data, res.data);
+ if (c === 0) {
+ return res.data;
+ } else {
+ res = res.getChild(c > 0);
+ }
+ }
+ return null;
+ }
+
+ /** returns `true` if inserted, `false` if duplicate. */
+ insert(data) {
+ let ret = false;
+
+ if (this.#root === null) {
+ // empty tree
+ this.#root = new RBNode(data);
+ ret = true;
+ } else {
+ const head = new RBNode(null); // fake tree root
+
+ let dir = 0;
+ let last = 0;
+
+ // setup
+ let gp = null; // grandparent
+ let ggp = head; // grand-grand-parent
+ let p = null; // parent
+ let node = this.#root;
+ ggp.right = this.#root;
+
+ // search down
+ while (true) {
+ if (node === null) {
+ // insert new node at the bottom
+ node = new RBNode(data);
+ p.setChild(dir, node);
+ ret = true;
+ } else if (isRed(node.left) && isRed(node.right)) {
+ // color flip
+ node.red = true;
+ node.left.red = false;
+ node.right.red = false;
+ }
+
+ // fix red violation
+ if (isRed(node) && isRed(p)) {
+ const dir2 = ggp.right === gp;
+
+ assert(gp);
+ if (node === p.getChild(last)) {
+ ggp.setChild(dir2, singleRotate(gp, !last));
+ } else {
+ ggp.setChild(dir2, doubleRotate(gp, !last));
+ }
+ }
+
+ const cmp = this.#comparator(node.data, data);
+
+ // stop if found
+ if (cmp === 0) {
+ break;
+ }
+
+ last = dir;
+ dir = Number(cmp < 0); // Fix type
+
+ // update helpers
+ if (gp !== null) {
+ ggp = gp;
+ }
+ gp = p;
+ p = node;
+ node = node.getChild(dir);
+ }
+
+ // update root
+ this.#root = head.right;
+ }
+
+ // make root black
+ this.#root.red = false;
+
+ return ret;
+ }
+
+ /** Returns `true` if removed, `false` if not found. */
+ remove(data) {
+ if (this.#root === null) {
+ return false;
+ }
+
+ const head = new RBNode(null); // fake tree root
+ let node = head;
+ node.right = this.#root;
+ let p = null; // parent
+ let gp = null; // grand parent
+ let found = null; // found item
+ let dir = 1;
+
+ while (node.getChild(dir) !== null) {
+ const last = dir;
+
+ // update helpers
+ gp = p;
+ p = node;
+ node = node.getChild(dir);
+
+ const cmp = this.#comparator(data, node.data);
+
+ dir = cmp > 0;
+
+ // save found node
+ if (cmp === 0) {
+ found = node;
+ }
+
+ // push the red node down
+ if (!isRed(node) && !isRed(node.getChild(dir))) {
+ if (isRed(node.getChild(!dir))) {
+ const sr = singleRotate(node, dir);
+ p.setChild(last, sr);
+ p = sr;
+ } else if (!isRed(node.getChild(!dir))) {
+ const sibling = p.getChild(!last);
+ if (sibling !== null) {
+ if (
+ !isRed(sibling.getChild(!last)) &&
+ !isRed(sibling.getChild(last))
+ ) {
+ // color flip
+ p.red = false;
+ sibling.red = true;
+ node.red = true;
+ } else {
+ assert(gp);
+ const dir2 = gp.right === p;
+
+ if (isRed(sibling.getChild(last))) {
+ gp.setChild(dir2, doubleRotate(p, last));
+ } else if (isRed(sibling.getChild(!last))) {
+ gp.setChild(dir2, singleRotate(p, last));
+ }
+
+ // ensure correct coloring
+ const gpc = gp.getChild(dir2);
+ assert(gpc);
+ gpc.red = true;
+ node.red = true;
+ assert(gpc.left);
+ gpc.left.red = false;
+ assert(gpc.right);
+ gpc.right.red = false;
+ }
+ }
+ }
+ }
+ }
+
+ // replace and remove if found
+ if (found !== null) {
+ found.data = node.data;
+ assert(p);
+ p.setChild(p.right === node, node.getChild(node.left === null));
+ }
+
+ // update root and make it black
+ this.#root = head.right;
+ if (this.#root !== null) {
+ this.#root.red = false;
+ }
+
+ return found !== null;
+ }
+ }
+
+ function isRed(node) {
+ return node !== null && node.red;
+ }
+
+ function singleRotate(root, dir) {
+ const save = root.getChild(!dir);
+ assert(save);
+
+ root.setChild(!dir, save.getChild(dir));
+ save.setChild(dir, root);
+
+ root.red = true;
+ save.red = false;
+
+ return save;
+ }
+
+ function doubleRotate(root, dir) {
+ root.setChild(!dir, singleRotate(root.getChild(!dir), !dir));
+ return singleRotate(root, dir);
+ }
+
+ const { console } = globalThis;
+ const OriginalDate = Date;
+
+ // Timeout values > TIMEOUT_MAX are set to 1.
+ const TIMEOUT_MAX = 2 ** 31 - 1;
+
+ let globalTimeoutDue = null;
+
+ let nextTimerId = 1;
+ const idMap = new Map();
+ const dueTree = new RBTree((a, b) => a.due - b.due);
+
+ function clearGlobalTimeout() {
+ globalTimeoutDue = null;
+ opStopGlobalTimer();
+ }
+
+ let pendingEvents = 0;
+ const pendingFireTimers = [];
+
+ /** Process and run a single ready timer macrotask.
+ * This function should be registered through Deno.core.setMacrotaskCallback.
+ * Returns true when all ready macrotasks have been processed, false if more
+ * ready ones are available. The Isolate future would rely on the return value
+ * to repeatedly invoke this function until depletion. Multiple invocations
+ * of this function one at a time ensures newly ready microtasks are processed
+ * before next macrotask timer callback is invoked. */
+ function handleTimerMacrotask() {
+ if (pendingFireTimers.length > 0) {
+ fire(pendingFireTimers.shift());
+ return pendingFireTimers.length === 0;
+ }
+ return true;
+ }
+
+ async function setGlobalTimeout(due, now) {
+ // Since JS and Rust don't use the same clock, pass the time to rust as a
+ // relative time value. On the Rust side we'll turn that into an absolute
+ // value again.
+ const timeout = due - now;
+ assert(timeout >= 0);
+ // Send message to the backend.
+ globalTimeoutDue = due;
+ pendingEvents++;
+ // FIXME(bartlomieju): this is problematic, because `clearGlobalTimeout`
+ // is synchronous. That means that timer is cancelled, but this promise is still pending
+ // until next turn of event loop. This leads to "leaking of async ops" in tests;
+ // because `clearTimeout/clearInterval` might be the last statement in test function
+ // `opSanitizer` will immediately complain that there is pending op going on, unless
+ // some timeout/defer is put in place to allow promise resolution.
+ // Ideally `clearGlobalTimeout` doesn't return until this op is resolved, but
+ // I'm not if that's possible.
+ opStartGlobalTimer(timeout);
+ await opWaitGlobalTimer();
+ pendingEvents--;
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define
+ prepareReadyTimers();
+ }
+
+ function prepareReadyTimers() {
+ const now = OriginalDate.now();
+ // Bail out if we're not expecting the global timer to fire.
+ if (globalTimeoutDue === null || pendingEvents > 0) {
+ return;
+ }
+ // After firing the timers that are due now, this will hold the first timer
+ // list that hasn't fired yet.
+ let nextDueNode;
+ while ((nextDueNode = dueTree.min()) !== null && nextDueNode.due <= now) {
+ dueTree.remove(nextDueNode);
+ // Fire all the timers in the list.
+ for (const timer of nextDueNode.timers) {
+ // With the list dropped, the timer is no longer scheduled.
+ timer.scheduled = false;
+ // Place the callback to pending timers to fire.
+ pendingFireTimers.push(timer);
+ }
+ }
+ setOrClearGlobalTimeout(nextDueNode && nextDueNode.due, now);
+ }
+
+ function setOrClearGlobalTimeout(due, now) {
+ if (due == null) {
+ clearGlobalTimeout();
+ } else {
+ setGlobalTimeout(due, now);
+ }
+ }
+
+ function schedule(timer, now) {
+ assert(!timer.scheduled);
+ assert(now <= timer.due);
+ // Find or create the list of timers that will fire at point-in-time `due`.
+ const maybeNewDueNode = { due: timer.due, timers: [] };
+ let dueNode = dueTree.find(maybeNewDueNode);
+ if (dueNode === null) {
+ dueTree.insert(maybeNewDueNode);
+ dueNode = maybeNewDueNode;
+ }
+ // Append the newly scheduled timer to the list and mark it as scheduled.
+ dueNode.timers.push(timer);
+ timer.scheduled = true;
+ // If the new timer is scheduled to fire before any timer that existed before,
+ // update the global timeout to reflect this.
+ if (globalTimeoutDue === null || globalTimeoutDue > timer.due) {
+ setOrClearGlobalTimeout(timer.due, now);
+ }
+ }
+
+ function unschedule(timer) {
+ // Check if our timer is pending scheduling or pending firing.
+ // If either is true, they are not in tree, and their idMap entry
+ // will be deleted soon. Remove it from queue.
+ let index = -1;
+ if ((index = pendingFireTimers.indexOf(timer)) >= 0) {
+ pendingFireTimers.splice(index);
+ return;
+ }
+ // If timer is not in the 2 pending queues and is unscheduled,
+ // it is not in the tree.
+ if (!timer.scheduled) {
+ return;
+ }
+ const searchKey = { due: timer.due, timers: [] };
+ // Find the list of timers that will fire at point-in-time `due`.
+ const list = dueTree.find(searchKey).timers;
+ if (list.length === 1) {
+ // Time timer is the only one in the list. Remove the entire list.
+ assert(list[0] === timer);
+ dueTree.remove(searchKey);
+ // If the unscheduled timer was 'next up', find when the next timer that
+ // still exists is due, and update the global alarm accordingly.
+ if (timer.due === globalTimeoutDue) {
+ const nextDueNode = dueTree.min();
+ setOrClearGlobalTimeout(
+ nextDueNode && nextDueNode.due,
+ OriginalDate.now(),
+ );
+ }
+ } else {
+ // Multiple timers that are due at the same point in time.
+ // Remove this timer from the list.
+ const index = list.indexOf(timer);
+ assert(index > -1);
+ list.splice(index, 1);
+ }
+ }
+
+ function fire(timer) {
+ // If the timer isn't found in the ID map, that means it has been cancelled
+ // between the timer firing and the promise callback (this function).
+ if (!idMap.has(timer.id)) {
+ return;
+ }
+ // Reschedule the timer if it is a repeating one, otherwise drop it.
+ if (!timer.repeat) {
+ // One-shot timer: remove the timer from this id-to-timer map.
+ idMap.delete(timer.id);
+ } else {
+ // Interval timer: compute when timer was supposed to fire next.
+ // However make sure to never schedule the next interval in the past.
+ const now = OriginalDate.now();
+ timer.due = Math.max(now, timer.due + timer.delay);
+ schedule(timer, now);
+ }
+ // Call the user callback. Intermediate assignment is to avoid leaking `this`
+ // to it, while also keeping the stack trace neat when it shows up in there.
+ const callback = timer.callback;
+ callback();
+ }
+
+ function checkThis(thisArg) {
+ if (thisArg !== null && thisArg !== undefined && thisArg !== globalThis) {
+ throw new TypeError("Illegal invocation");
+ }
+ }
+
+ function checkBigInt(n) {
+ if (typeof n === "bigint") {
+ throw new TypeError("Cannot convert a BigInt value to a number");
+ }
+ }
+
+ function setTimer(
+ cb,
+ delay,
+ args,
+ repeat,
+ ) {
+ // Bind `args` to the callback and bind `this` to globalThis(global).
+ const callback = cb.bind(globalThis, ...args);
+ // In the browser, the delay value must be coercible to an integer between 0
+ // and INT32_MAX. Any other value will cause the timer to fire immediately.
+ // We emulate this behavior.
+ const now = OriginalDate.now();
+ if (delay > TIMEOUT_MAX) {
+ console.warn(
+ `${delay} does not fit into` +
+ " a 32-bit signed integer." +
+ "\nTimeout duration was set to 1.",
+ );
+ delay = 1;
+ }
+ delay = Math.max(0, delay | 0);
+
+ // Create a new, unscheduled timer object.
+ const timer = {
+ id: nextTimerId++,
+ callback,
+ args,
+ delay,
+ due: now + delay,
+ repeat,
+ scheduled: false,
+ };
+ // Register the timer's existence in the id-to-timer map.
+ idMap.set(timer.id, timer);
+ // Schedule the timer in the due table.
+ schedule(timer, now);
+ return timer.id;
+ }
+
+ function setTimeout(
+ cb,
+ delay = 0,
+ ...args
+ ) {
+ checkBigInt(delay);
+ checkThis(this);
+ return setTimer(cb, delay, args, false);
+ }
+
+ function setInterval(
+ cb,
+ delay = 0,
+ ...args
+ ) {
+ checkBigInt(delay);
+ checkThis(this);
+ return setTimer(cb, delay, args, true);
+ }
+
+ function clearTimer(id) {
+ id = Number(id);
+ const timer = idMap.get(id);
+ if (timer === undefined) {
+ // Timer doesn't exist any more or never existed. This is not an error.
+ return;
+ }
+ // Unschedule the timer if it is currently scheduled, and forget about it.
+ unschedule(timer);
+ idMap.delete(timer.id);
+ }
+
+ function clearTimeout(id = 0) {
+ checkBigInt(id);
+ if (id === 0) {
+ return;
+ }
+ clearTimer(id);
+ }
+
+ function clearInterval(id = 0) {
+ checkBigInt(id);
+ if (id === 0) {
+ return;
+ }
+ clearTimer(id);
+ }
+
+ window.__bootstrap.timers = {
+ clearInterval,
+ setInterval,
+ clearTimeout,
+ setTimeout,
+ handleTimerMacrotask,
+ opStopGlobalTimer,
+ opStartGlobalTimer,
+ opNow,
+ sleepSync,
+ };
+})(this);
diff --git a/runtime/js/11_workers.js b/runtime/js/11_workers.js
new file mode 100644
index 000000000..62210dfae
--- /dev/null
+++ b/runtime/js/11_workers.js
@@ -0,0 +1,206 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const core = window.Deno.core;
+ const { Window } = window.__bootstrap.globalInterfaces;
+ const { log } = window.__bootstrap.util;
+ const { defineEventHandler } = window.__bootstrap.webUtil;
+
+ function createWorker(
+ specifier,
+ hasSourceCode,
+ sourceCode,
+ useDenoNamespace,
+ name,
+ ) {
+ return core.jsonOpSync("op_create_worker", {
+ specifier,
+ hasSourceCode,
+ sourceCode,
+ name,
+ useDenoNamespace,
+ });
+ }
+
+ function hostTerminateWorker(id) {
+ core.jsonOpSync("op_host_terminate_worker", { id });
+ }
+
+ function hostPostMessage(id, data) {
+ core.jsonOpSync("op_host_post_message", { id }, data);
+ }
+
+ function hostGetMessage(id) {
+ return core.jsonOpAsync("op_host_get_message", { id });
+ }
+
+ const encoder = new TextEncoder();
+ const decoder = new TextDecoder();
+
+ function encodeMessage(data) {
+ const dataJson = JSON.stringify(data);
+ return encoder.encode(dataJson);
+ }
+
+ function decodeMessage(dataIntArray) {
+ const dataJson = decoder.decode(dataIntArray);
+ return JSON.parse(dataJson);
+ }
+
+ class Worker extends EventTarget {
+ #id = 0;
+ #name = "";
+ #terminated = false;
+
+ constructor(specifier, options) {
+ super();
+ const { type = "classic", name = "unknown" } = options ?? {};
+
+ if (type !== "module") {
+ throw new Error(
+ 'Not yet implemented: only "module" type workers are supported',
+ );
+ }
+
+ this.#name = name;
+ const hasSourceCode = false;
+ const sourceCode = decoder.decode(new Uint8Array());
+
+ const useDenoNamespace = options ? !!options.deno : false;
+
+ const { id } = createWorker(
+ specifier,
+ hasSourceCode,
+ sourceCode,
+ useDenoNamespace,
+ options?.name,
+ );
+ this.#id = id;
+ this.#poll();
+ }
+
+ #handleMessage = (msgData) => {
+ let data;
+ try {
+ data = decodeMessage(new Uint8Array(msgData));
+ } catch (e) {
+ const msgErrorEvent = new MessageEvent("messageerror", {
+ cancelable: false,
+ data,
+ });
+ return;
+ }
+
+ const msgEvent = new MessageEvent("message", {
+ cancelable: false,
+ data,
+ });
+
+ this.dispatchEvent(msgEvent);
+ };
+
+ #handleError = (e) => {
+ const event = new ErrorEvent("error", {
+ cancelable: true,
+ message: e.message,
+ lineno: e.lineNumber ? e.lineNumber + 1 : undefined,
+ colno: e.columnNumber ? e.columnNumber + 1 : undefined,
+ filename: e.fileName,
+ error: null,
+ });
+
+ let handled = false;
+
+ this.dispatchEvent(event);
+ if (event.defaultPrevented) {
+ handled = true;
+ }
+
+ return handled;
+ };
+
+ #poll = async () => {
+ while (!this.#terminated) {
+ const event = await hostGetMessage(this.#id);
+
+ // If terminate was called then we ignore all messages
+ if (this.#terminated) {
+ return;
+ }
+
+ const type = event.type;
+
+ if (type === "terminalError") {
+ this.#terminated = true;
+ if (!this.#handleError(event.error)) {
+ if (globalThis instanceof Window) {
+ throw new Error("Unhandled error event reached main worker.");
+ } else {
+ core.jsonOpSync(
+ "op_host_unhandled_error",
+ { message: event.error.message },
+ );
+ }
+ }
+ continue;
+ }
+
+ if (type === "msg") {
+ this.#handleMessage(event.data);
+ continue;
+ }
+
+ if (type === "error") {
+ if (!this.#handleError(event.error)) {
+ if (globalThis instanceof Window) {
+ throw new Error("Unhandled error event reached main worker.");
+ } else {
+ core.jsonOpSync(
+ "op_host_unhandled_error",
+ { message: event.error.message },
+ );
+ }
+ }
+ continue;
+ }
+
+ if (type === "close") {
+ log(`Host got "close" message from worker: ${this.#name}`);
+ this.#terminated = true;
+ return;
+ }
+
+ throw new Error(`Unknown worker event: "${type}"`);
+ }
+ };
+
+ postMessage(message, transferOrOptions) {
+ if (transferOrOptions) {
+ throw new Error(
+ "Not yet implemented: `transfer` and `options` are not supported.",
+ );
+ }
+
+ if (this.#terminated) {
+ return;
+ }
+
+ hostPostMessage(this.#id, encodeMessage(message));
+ }
+
+ terminate() {
+ if (!this.#terminated) {
+ this.#terminated = true;
+ hostTerminateWorker(this.#id);
+ }
+ }
+ }
+
+ defineEventHandler(Worker.prototype, "error");
+ defineEventHandler(Worker.prototype, "message");
+ defineEventHandler(Worker.prototype, "messageerror");
+
+ window.__bootstrap.worker = {
+ Worker,
+ };
+})(this);
diff --git a/runtime/js/12_io.js b/runtime/js/12_io.js
new file mode 100644
index 000000000..006d51cdd
--- /dev/null
+++ b/runtime/js/12_io.js
@@ -0,0 +1,135 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+// Interfaces 100% copied from Go.
+// Documentation liberally lifted from them too.
+// Thank you! We love Go! <3
+
+((window) => {
+ const DEFAULT_BUFFER_SIZE = 32 * 1024;
+ const { sendSync, sendAsync } = window.__bootstrap.dispatchMinimal;
+ // Seek whence values.
+ // https://golang.org/pkg/io/#pkg-constants
+ const SeekMode = {
+ 0: "Start",
+ 1: "Current",
+ 2: "End",
+
+ Start: 0,
+ Current: 1,
+ End: 2,
+ };
+
+ async function copy(
+ src,
+ dst,
+ options,
+ ) {
+ let n = 0;
+ const bufSize = options?.bufSize ?? DEFAULT_BUFFER_SIZE;
+ const b = new Uint8Array(bufSize);
+ let gotEOF = false;
+ while (gotEOF === false) {
+ const result = await src.read(b);
+ if (result === null) {
+ gotEOF = true;
+ } else {
+ let nwritten = 0;
+ while (nwritten < result) {
+ nwritten += await dst.write(b.subarray(nwritten, result));
+ }
+ n += nwritten;
+ }
+ }
+ return n;
+ }
+
+ async function* iter(
+ r,
+ options,
+ ) {
+ const bufSize = options?.bufSize ?? DEFAULT_BUFFER_SIZE;
+ const b = new Uint8Array(bufSize);
+ while (true) {
+ const result = await r.read(b);
+ if (result === null) {
+ break;
+ }
+
+ yield b.subarray(0, result);
+ }
+ }
+
+ function* iterSync(
+ r,
+ options,
+ ) {
+ const bufSize = options?.bufSize ?? DEFAULT_BUFFER_SIZE;
+ const b = new Uint8Array(bufSize);
+ while (true) {
+ const result = r.readSync(b);
+ if (result === null) {
+ break;
+ }
+
+ yield b.subarray(0, result);
+ }
+ }
+
+ function readSync(rid, buffer) {
+ if (buffer.length === 0) {
+ return 0;
+ }
+
+ const nread = sendSync("op_read", rid, buffer);
+ if (nread < 0) {
+ throw new Error("read error");
+ }
+
+ return nread === 0 ? null : nread;
+ }
+
+ async function read(
+ rid,
+ buffer,
+ ) {
+ if (buffer.length === 0) {
+ return 0;
+ }
+
+ const nread = await sendAsync("op_read", rid, buffer);
+ if (nread < 0) {
+ throw new Error("read error");
+ }
+
+ return nread === 0 ? null : nread;
+ }
+
+ function writeSync(rid, data) {
+ const result = sendSync("op_write", rid, data);
+ if (result < 0) {
+ throw new Error("write error");
+ }
+
+ return result;
+ }
+
+ async function write(rid, data) {
+ const result = await sendAsync("op_write", rid, data);
+ if (result < 0) {
+ throw new Error("write error");
+ }
+
+ return result;
+ }
+
+ window.__bootstrap.io = {
+ iterSync,
+ iter,
+ copy,
+ SeekMode,
+ read,
+ readSync,
+ write,
+ writeSync,
+ };
+})(this);
diff --git a/runtime/js/13_buffer.js b/runtime/js/13_buffer.js
new file mode 100644
index 000000000..e06e2138b
--- /dev/null
+++ b/runtime/js/13_buffer.js
@@ -0,0 +1,241 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+// This code has been ported almost directly from Go's src/bytes/buffer.go
+// Copyright 2009 The Go Authors. All rights reserved. BSD license.
+// https://github.com/golang/go/blob/master/LICENSE
+
+((window) => {
+ const { assert } = window.__bootstrap.util;
+
+ // MIN_READ is the minimum ArrayBuffer size passed to a read call by
+ // buffer.ReadFrom. As long as the Buffer has at least MIN_READ bytes beyond
+ // what is required to hold the contents of r, readFrom() will not grow the
+ // underlying buffer.
+ const MIN_READ = 32 * 1024;
+ const MAX_SIZE = 2 ** 32 - 2;
+
+ // `off` is the offset into `dst` where it will at which to begin writing values
+ // from `src`.
+ // Returns the number of bytes copied.
+ function copyBytes(src, dst, off = 0) {
+ const r = dst.byteLength - off;
+ if (src.byteLength > r) {
+ src = src.subarray(0, r);
+ }
+ dst.set(src, off);
+ return src.byteLength;
+ }
+
+ class Buffer {
+ #buf = null; // contents are the bytes buf[off : len(buf)]
+ #off = 0; // read at buf[off], write at buf[buf.byteLength]
+
+ constructor(ab) {
+ if (ab == null) {
+ this.#buf = new Uint8Array(0);
+ return;
+ }
+
+ this.#buf = new Uint8Array(ab);
+ }
+
+ bytes(options = { copy: true }) {
+ if (options.copy === false) return this.#buf.subarray(this.#off);
+ return this.#buf.slice(this.#off);
+ }
+
+ empty() {
+ return this.#buf.byteLength <= this.#off;
+ }
+
+ get length() {
+ return this.#buf.byteLength - this.#off;
+ }
+
+ get capacity() {
+ return this.#buf.buffer.byteLength;
+ }
+
+ truncate(n) {
+ if (n === 0) {
+ this.reset();
+ return;
+ }
+ if (n < 0 || n > this.length) {
+ throw Error("bytes.Buffer: truncation out of range");
+ }
+ this.#reslice(this.#off + n);
+ }
+
+ reset() {
+ this.#reslice(0);
+ this.#off = 0;
+ }
+
+ #tryGrowByReslice = (n) => {
+ const l = this.#buf.byteLength;
+ if (n <= this.capacity - l) {
+ this.#reslice(l + n);
+ return l;
+ }
+ return -1;
+ };
+
+ #reslice = (len) => {
+ assert(len <= this.#buf.buffer.byteLength);
+ this.#buf = new Uint8Array(this.#buf.buffer, 0, len);
+ };
+
+ readSync(p) {
+ if (this.empty()) {
+ // Buffer is empty, reset to recover space.
+ this.reset();
+ if (p.byteLength === 0) {
+ // this edge case is tested in 'bufferReadEmptyAtEOF' test
+ return 0;
+ }
+ return null;
+ }
+ const nread = copyBytes(this.#buf.subarray(this.#off), p);
+ this.#off += nread;
+ return nread;
+ }
+
+ read(p) {
+ const rr = this.readSync(p);
+ return Promise.resolve(rr);
+ }
+
+ writeSync(p) {
+ const m = this.#grow(p.byteLength);
+ return copyBytes(p, this.#buf, m);
+ }
+
+ write(p) {
+ const n = this.writeSync(p);
+ return Promise.resolve(n);
+ }
+
+ #grow = (n) => {
+ const m = this.length;
+ // If buffer is empty, reset to recover space.
+ if (m === 0 && this.#off !== 0) {
+ this.reset();
+ }
+ // Fast: Try to grow by means of a reslice.
+ const i = this.#tryGrowByReslice(n);
+ if (i >= 0) {
+ return i;
+ }
+ const c = this.capacity;
+ if (n <= Math.floor(c / 2) - m) {
+ // We can slide things down instead of allocating a new
+ // ArrayBuffer. We only need m+n <= c to slide, but
+ // we instead let capacity get twice as large so we
+ // don't spend all our time copying.
+ copyBytes(this.#buf.subarray(this.#off), this.#buf);
+ } else if (c + n > MAX_SIZE) {
+ throw new Error("The buffer cannot be grown beyond the maximum size.");
+ } else {
+ // Not enough space anywhere, we need to allocate.
+ const buf = new Uint8Array(Math.min(2 * c + n, MAX_SIZE));
+ copyBytes(this.#buf.subarray(this.#off), buf);
+ this.#buf = buf;
+ }
+ // Restore this.#off and len(this.#buf).
+ this.#off = 0;
+ this.#reslice(Math.min(m + n, MAX_SIZE));
+ return m;
+ };
+
+ grow(n) {
+ if (n < 0) {
+ throw Error("Buffer.grow: negative count");
+ }
+ const m = this.#grow(n);
+ this.#reslice(m);
+ }
+
+ async readFrom(r) {
+ let n = 0;
+ const tmp = new Uint8Array(MIN_READ);
+ while (true) {
+ const shouldGrow = this.capacity - this.length < MIN_READ;
+ // read into tmp buffer if there's not enough room
+ // otherwise read directly into the internal buffer
+ const buf = shouldGrow
+ ? tmp
+ : new Uint8Array(this.#buf.buffer, this.length);
+
+ const nread = await r.read(buf);
+ if (nread === null) {
+ return n;
+ }
+
+ // write will grow if needed
+ if (shouldGrow) this.writeSync(buf.subarray(0, nread));
+ else this.#reslice(this.length + nread);
+
+ n += nread;
+ }
+ }
+
+ readFromSync(r) {
+ let n = 0;
+ const tmp = new Uint8Array(MIN_READ);
+ while (true) {
+ const shouldGrow = this.capacity - this.length < MIN_READ;
+ // read into tmp buffer if there's not enough room
+ // otherwise read directly into the internal buffer
+ const buf = shouldGrow
+ ? tmp
+ : new Uint8Array(this.#buf.buffer, this.length);
+
+ const nread = r.readSync(buf);
+ if (nread === null) {
+ return n;
+ }
+
+ // write will grow if needed
+ if (shouldGrow) this.writeSync(buf.subarray(0, nread));
+ else this.#reslice(this.length + nread);
+
+ n += nread;
+ }
+ }
+ }
+
+ async function readAll(r) {
+ const buf = new Buffer();
+ await buf.readFrom(r);
+ return buf.bytes();
+ }
+
+ function readAllSync(r) {
+ const buf = new Buffer();
+ buf.readFromSync(r);
+ return buf.bytes();
+ }
+
+ async function writeAll(w, arr) {
+ let nwritten = 0;
+ while (nwritten < arr.length) {
+ nwritten += await w.write(arr.subarray(nwritten));
+ }
+ }
+
+ function writeAllSync(w, arr) {
+ let nwritten = 0;
+ while (nwritten < arr.length) {
+ nwritten += w.writeSync(arr.subarray(nwritten));
+ }
+ }
+
+ window.__bootstrap.buffer = {
+ writeAll,
+ writeAllSync,
+ readAll,
+ readAllSync,
+ Buffer,
+ };
+})(this);
diff --git a/runtime/js/27_websocket.js b/runtime/js/27_websocket.js
new file mode 100644
index 000000000..60428c24d
--- /dev/null
+++ b/runtime/js/27_websocket.js
@@ -0,0 +1,316 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const core = window.Deno.core;
+ const { requiredArguments, defineEventHandler } = window.__bootstrap.webUtil;
+ const CONNECTING = 0;
+ const OPEN = 1;
+ const CLOSING = 2;
+ const CLOSED = 3;
+
+ class WebSocket extends EventTarget {
+ #readyState = CONNECTING;
+
+ constructor(url, protocols = []) {
+ super();
+ requiredArguments("WebSocket", arguments.length, 1);
+
+ const wsURL = new URL(url);
+
+ if (wsURL.protocol !== "ws:" && wsURL.protocol !== "wss:") {
+ throw new DOMException(
+ "Only ws & wss schemes are allowed in a WebSocket URL.",
+ "SyntaxError",
+ );
+ }
+
+ if (wsURL.hash !== "" || wsURL.href.endsWith("#")) {
+ throw new DOMException(
+ "Fragments are not allowed in a WebSocket URL.",
+ "SyntaxError",
+ );
+ }
+
+ this.#url = wsURL.href;
+
+ core.jsonOpSync("op_ws_check_permission", {
+ url: this.#url,
+ });
+
+ if (protocols && typeof protocols === "string") {
+ protocols = [protocols];
+ }
+
+ if (
+ protocols.some((x) => protocols.indexOf(x) !== protocols.lastIndexOf(x))
+ ) {
+ throw new DOMException(
+ "Can't supply multiple times the same protocol.",
+ "SyntaxError",
+ );
+ }
+
+ core.jsonOpAsync("op_ws_create", {
+ url: wsURL.href,
+ protocols: protocols.join(", "),
+ }).then((create) => {
+ if (create.success) {
+ this.#rid = create.rid;
+ this.#extensions = create.extensions;
+ this.#protocol = create.protocol;
+
+ if (this.#readyState === CLOSING) {
+ core.jsonOpAsync("op_ws_close", {
+ rid: this.#rid,
+ }).then(() => {
+ this.#readyState = CLOSED;
+
+ const errEvent = new ErrorEvent("error");
+ errEvent.target = this;
+ this.dispatchEvent(errEvent);
+
+ const event = new CloseEvent("close");
+ event.target = this;
+ this.dispatchEvent(event);
+ core.close(this.#rid);
+ });
+ } else {
+ this.#readyState = OPEN;
+ const event = new Event("open");
+ event.target = this;
+ this.dispatchEvent(event);
+
+ this.#eventLoop();
+ }
+ } else {
+ this.#readyState = CLOSED;
+
+ const errEvent = new ErrorEvent("error");
+ errEvent.target = this;
+ this.dispatchEvent(errEvent);
+
+ const closeEvent = new CloseEvent("close");
+ closeEvent.target = this;
+ this.dispatchEvent(closeEvent);
+ }
+ }).catch((err) => {
+ this.#readyState = CLOSED;
+
+ const errorEv = new ErrorEvent(
+ "error",
+ { error: err, message: err.toString() },
+ );
+ errorEv.target = this;
+ this.dispatchEvent(errorEv);
+
+ const closeEv = new CloseEvent("close");
+ closeEv.target = this;
+ this.dispatchEvent(closeEv);
+ });
+ }
+
+ get CONNECTING() {
+ return CONNECTING;
+ }
+ get OPEN() {
+ return OPEN;
+ }
+ get CLOSING() {
+ return CLOSING;
+ }
+ get CLOSED() {
+ return CLOSED;
+ }
+
+ get readyState() {
+ return this.#readyState;
+ }
+
+ #extensions = "";
+ #protocol = "";
+ #url = "";
+ #rid;
+
+ get extensions() {
+ return this.#extensions;
+ }
+ get protocol() {
+ return this.#protocol;
+ }
+
+ #binaryType = "blob";
+ get binaryType() {
+ return this.#binaryType;
+ }
+ set binaryType(value) {
+ if (value === "blob" || value === "arraybuffer") {
+ this.#binaryType = value;
+ }
+ }
+ #bufferedAmount = 0;
+ get bufferedAmount() {
+ return this.#bufferedAmount;
+ }
+
+ get url() {
+ return this.#url;
+ }
+
+ send(data) {
+ requiredArguments("WebSocket.send", arguments.length, 1);
+
+ if (this.#readyState != OPEN) {
+ throw Error("readyState not OPEN");
+ }
+
+ const sendTypedArray = (ta) => {
+ this.#bufferedAmount += ta.size;
+ core.jsonOpAsync("op_ws_send", {
+ rid: this.#rid,
+ }, ta).then(() => {
+ this.#bufferedAmount -= ta.size;
+ });
+ };
+
+ if (data instanceof Blob) {
+ data.slice().arrayBuffer().then((ab) =>
+ sendTypedArray(new DataView(ab))
+ );
+ } else if (
+ data instanceof Int8Array || data instanceof Int16Array ||
+ data instanceof Int32Array || data instanceof Uint8Array ||
+ data instanceof Uint16Array || data instanceof Uint32Array ||
+ data instanceof Uint8ClampedArray || data instanceof Float32Array ||
+ data instanceof Float64Array || data instanceof DataView
+ ) {
+ sendTypedArray(data);
+ } else if (data instanceof ArrayBuffer) {
+ sendTypedArray(new DataView(data));
+ } else {
+ const string = String(data);
+ const encoder = new TextEncoder();
+ const d = encoder.encode(string);
+ this.#bufferedAmount += d.size;
+ core.jsonOpAsync("op_ws_send", {
+ rid: this.#rid,
+ text: string,
+ }).then(() => {
+ this.#bufferedAmount -= d.size;
+ });
+ }
+ }
+
+ close(code, reason) {
+ if (code && (code !== 1000 && !(3000 <= code > 5000))) {
+ throw new DOMException(
+ "The close code must be either 1000 or in the range of 3000 to 4999.",
+ "NotSupportedError",
+ );
+ }
+
+ const encoder = new TextEncoder();
+ if (reason && encoder.encode(reason).byteLength > 123) {
+ throw new DOMException(
+ "The close reason may not be longer than 123 bytes.",
+ "SyntaxError",
+ );
+ }
+
+ if (this.#readyState === CONNECTING) {
+ this.#readyState = CLOSING;
+ } else if (this.#readyState === OPEN) {
+ this.#readyState = CLOSING;
+
+ core.jsonOpAsync("op_ws_close", {
+ rid: this.#rid,
+ code,
+ reason,
+ }).then(() => {
+ this.#readyState = CLOSED;
+ const event = new CloseEvent("close", {
+ wasClean: true,
+ code,
+ reason,
+ });
+ event.target = this;
+ this.dispatchEvent(event);
+ core.close(this.#rid);
+ });
+ }
+ }
+
+ async #eventLoop() {
+ if (this.#readyState === OPEN) {
+ const message = await core.jsonOpAsync(
+ "op_ws_next_event",
+ { rid: this.#rid },
+ );
+ if (message.type === "string" || message.type === "binary") {
+ let data;
+
+ if (message.type === "string") {
+ data = message.data;
+ } else {
+ if (this.binaryType === "blob") {
+ data = new Blob([new Uint8Array(message.data)]);
+ } else {
+ data = new Uint8Array(message.data).buffer;
+ }
+ }
+
+ const event = new MessageEvent("message", {
+ data,
+ origin: this.#url,
+ });
+ event.target = this;
+ this.dispatchEvent(event);
+
+ this.#eventLoop();
+ } else if (message.type === "close") {
+ this.#readyState = CLOSED;
+ const event = new CloseEvent("close", {
+ wasClean: true,
+ code: message.code,
+ reason: message.reason,
+ });
+ event.target = this;
+ this.dispatchEvent(event);
+ } else if (message.type === "error") {
+ this.#readyState = CLOSED;
+
+ const errorEv = new ErrorEvent("error");
+ errorEv.target = this;
+ this.dispatchEvent(errorEv);
+
+ this.#readyState = CLOSED;
+ const closeEv = new CloseEvent("close");
+ closeEv.target = this;
+ this.dispatchEvent(closeEv);
+ }
+ }
+ }
+ }
+
+ Object.defineProperties(WebSocket, {
+ CONNECTING: {
+ value: 0,
+ },
+ OPEN: {
+ value: 1,
+ },
+ CLOSING: {
+ value: 2,
+ },
+ CLOSED: {
+ value: 3,
+ },
+ });
+
+ defineEventHandler(WebSocket.prototype, "message");
+ defineEventHandler(WebSocket.prototype, "error");
+ defineEventHandler(WebSocket.prototype, "close");
+ defineEventHandler(WebSocket.prototype, "open");
+ window.__bootstrap.webSocket = {
+ WebSocket,
+ };
+})(this);
diff --git a/runtime/js/30_files.js b/runtime/js/30_files.js
new file mode 100644
index 000000000..679b184fd
--- /dev/null
+++ b/runtime/js/30_files.js
@@ -0,0 +1,209 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const core = window.Deno.core;
+ const { read, readSync, write, writeSync } = window.__bootstrap.io;
+ const { pathFromURL } = window.__bootstrap.util;
+
+ function seekSync(
+ rid,
+ offset,
+ whence,
+ ) {
+ return core.jsonOpSync("op_seek_sync", { rid, offset, whence });
+ }
+
+ function seek(
+ rid,
+ offset,
+ whence,
+ ) {
+ return core.jsonOpAsync("op_seek_async", { rid, offset, whence });
+ }
+
+ function openSync(
+ path,
+ options = { read: true },
+ ) {
+ checkOpenOptions(options);
+ const mode = options?.mode;
+ const rid = core.jsonOpSync(
+ "op_open_sync",
+ { path: pathFromURL(path), options, mode },
+ );
+
+ return new File(rid);
+ }
+
+ async function open(
+ path,
+ options = { read: true },
+ ) {
+ checkOpenOptions(options);
+ const mode = options?.mode;
+ const rid = await core.jsonOpAsync(
+ "op_open_async",
+ { path: pathFromURL(path), options, mode },
+ );
+
+ return new File(rid);
+ }
+
+ function createSync(path) {
+ return openSync(path, {
+ read: true,
+ write: true,
+ truncate: true,
+ create: true,
+ });
+ }
+
+ function create(path) {
+ return open(path, {
+ read: true,
+ write: true,
+ truncate: true,
+ create: true,
+ });
+ }
+
+ class File {
+ #rid = 0;
+
+ constructor(rid) {
+ this.#rid = rid;
+ }
+
+ get rid() {
+ return this.#rid;
+ }
+
+ write(p) {
+ return write(this.rid, p);
+ }
+
+ writeSync(p) {
+ return writeSync(this.rid, p);
+ }
+
+ read(p) {
+ return read(this.rid, p);
+ }
+
+ readSync(p) {
+ return readSync(this.rid, p);
+ }
+
+ seek(offset, whence) {
+ return seek(this.rid, offset, whence);
+ }
+
+ seekSync(offset, whence) {
+ return seekSync(this.rid, offset, whence);
+ }
+
+ close() {
+ core.close(this.rid);
+ }
+ }
+
+ class Stdin {
+ constructor() {
+ }
+
+ get rid() {
+ return 0;
+ }
+
+ read(p) {
+ return read(this.rid, p);
+ }
+
+ readSync(p) {
+ return readSync(this.rid, p);
+ }
+
+ close() {
+ core.close(this.rid);
+ }
+ }
+
+ class Stdout {
+ constructor() {
+ }
+
+ get rid() {
+ return 1;
+ }
+
+ write(p) {
+ return write(this.rid, p);
+ }
+
+ writeSync(p) {
+ return writeSync(this.rid, p);
+ }
+
+ close() {
+ core.close(this.rid);
+ }
+ }
+
+ class Stderr {
+ constructor() {
+ }
+
+ get rid() {
+ return 2;
+ }
+
+ write(p) {
+ return write(this.rid, p);
+ }
+
+ writeSync(p) {
+ return writeSync(this.rid, p);
+ }
+
+ close() {
+ core.close(this.rid);
+ }
+ }
+
+ const stdin = new Stdin();
+ const stdout = new Stdout();
+ const stderr = new Stderr();
+
+ function checkOpenOptions(options) {
+ if (Object.values(options).filter((val) => val === true).length === 0) {
+ throw new Error("OpenOptions requires at least one option to be true");
+ }
+
+ if (options.truncate && !options.write) {
+ throw new Error("'truncate' option requires 'write' option");
+ }
+
+ const createOrCreateNewWithoutWriteOrAppend =
+ (options.create || options.createNew) &&
+ !(options.write || options.append);
+
+ if (createOrCreateNewWithoutWriteOrAppend) {
+ throw new Error(
+ "'create' or 'createNew' options require 'write' or 'append' option",
+ );
+ }
+ }
+
+ window.__bootstrap.files = {
+ stdin,
+ stdout,
+ stderr,
+ File,
+ create,
+ createSync,
+ open,
+ openSync,
+ seek,
+ seekSync,
+ };
+})(this);
diff --git a/runtime/js/30_fs.js b/runtime/js/30_fs.js
new file mode 100644
index 000000000..33fab01e4
--- /dev/null
+++ b/runtime/js/30_fs.js
@@ -0,0 +1,425 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const core = window.Deno.core;
+ const { pathFromURL } = window.__bootstrap.util;
+ const build = window.__bootstrap.build.build;
+
+ function chmodSync(path, mode) {
+ core.jsonOpSync("op_chmod_sync", { path: pathFromURL(path), mode });
+ }
+
+ async function chmod(path, mode) {
+ await core.jsonOpAsync("op_chmod_async", { path: pathFromURL(path), mode });
+ }
+
+ function chownSync(
+ path,
+ uid,
+ gid,
+ ) {
+ core.jsonOpSync("op_chown_sync", { path: pathFromURL(path), uid, gid });
+ }
+
+ async function chown(
+ path,
+ uid,
+ gid,
+ ) {
+ await core.jsonOpAsync(
+ "op_chown_async",
+ { path: pathFromURL(path), uid, gid },
+ );
+ }
+
+ function copyFileSync(
+ fromPath,
+ toPath,
+ ) {
+ core.jsonOpSync("op_copy_file_sync", {
+ from: pathFromURL(fromPath),
+ to: pathFromURL(toPath),
+ });
+ }
+
+ async function copyFile(
+ fromPath,
+ toPath,
+ ) {
+ await core.jsonOpAsync("op_copy_file_async", {
+ from: pathFromURL(fromPath),
+ to: pathFromURL(toPath),
+ });
+ }
+
+ function cwd() {
+ return core.jsonOpSync("op_cwd");
+ }
+
+ function chdir(directory) {
+ core.jsonOpSync("op_chdir", { directory });
+ }
+
+ function makeTempDirSync(options = {}) {
+ return core.jsonOpSync("op_make_temp_dir_sync", options);
+ }
+
+ function makeTempDir(options = {}) {
+ return core.jsonOpAsync("op_make_temp_dir_async", options);
+ }
+
+ function makeTempFileSync(options = {}) {
+ return core.jsonOpSync("op_make_temp_file_sync", options);
+ }
+
+ function makeTempFile(options = {}) {
+ return core.jsonOpAsync("op_make_temp_file_async", options);
+ }
+
+ function mkdirArgs(path, options) {
+ const args = { path: pathFromURL(path), recursive: false };
+ if (options != null) {
+ if (typeof options.recursive == "boolean") {
+ args.recursive = options.recursive;
+ }
+ if (options.mode) {
+ args.mode = options.mode;
+ }
+ }
+ return args;
+ }
+
+ function mkdirSync(path, options) {
+ core.jsonOpSync("op_mkdir_sync", mkdirArgs(path, options));
+ }
+
+ async function mkdir(
+ path,
+ options,
+ ) {
+ await core.jsonOpAsync("op_mkdir_async", mkdirArgs(path, options));
+ }
+
+ function res(response) {
+ return response.entries;
+ }
+
+ function readDirSync(path) {
+ return res(
+ core.jsonOpSync("op_read_dir_sync", { path: pathFromURL(path) }),
+ )[
+ Symbol.iterator
+ ]();
+ }
+
+ function readDir(path) {
+ const array = core.jsonOpAsync(
+ "op_read_dir_async",
+ { path: pathFromURL(path) },
+ )
+ .then(
+ res,
+ );
+ return {
+ async *[Symbol.asyncIterator]() {
+ yield* await array;
+ },
+ };
+ }
+
+ function readLinkSync(path) {
+ return core.jsonOpSync("op_read_link_sync", { path: pathFromURL(path) });
+ }
+
+ function readLink(path) {
+ return core.jsonOpAsync("op_read_link_async", { path: pathFromURL(path) });
+ }
+
+ function realPathSync(path) {
+ return core.jsonOpSync("op_realpath_sync", { path });
+ }
+
+ function realPath(path) {
+ return core.jsonOpAsync("op_realpath_async", { path });
+ }
+
+ function removeSync(
+ path,
+ options = {},
+ ) {
+ core.jsonOpSync("op_remove_sync", {
+ path: pathFromURL(path),
+ recursive: !!options.recursive,
+ });
+ }
+
+ async function remove(
+ path,
+ options = {},
+ ) {
+ await core.jsonOpAsync("op_remove_async", {
+ path: pathFromURL(path),
+ recursive: !!options.recursive,
+ });
+ }
+
+ function renameSync(oldpath, newpath) {
+ core.jsonOpSync("op_rename_sync", { oldpath, newpath });
+ }
+
+ async function rename(oldpath, newpath) {
+ await core.jsonOpAsync("op_rename_async", { oldpath, newpath });
+ }
+
+ function parseFileInfo(response) {
+ const unix = build.os === "darwin" || build.os === "linux";
+ return {
+ isFile: response.isFile,
+ isDirectory: response.isDirectory,
+ isSymlink: response.isSymlink,
+ size: response.size,
+ mtime: response.mtime != null ? new Date(response.mtime) : null,
+ atime: response.atime != null ? new Date(response.atime) : null,
+ birthtime: response.birthtime != null
+ ? new Date(response.birthtime)
+ : null,
+ // Only non-null if on Unix
+ dev: unix ? response.dev : null,
+ ino: unix ? response.ino : null,
+ mode: unix ? response.mode : null,
+ nlink: unix ? response.nlink : null,
+ uid: unix ? response.uid : null,
+ gid: unix ? response.gid : null,
+ rdev: unix ? response.rdev : null,
+ blksize: unix ? response.blksize : null,
+ blocks: unix ? response.blocks : null,
+ };
+ }
+
+ function fstatSync(rid) {
+ return parseFileInfo(core.jsonOpSync("op_fstat_sync", { rid }));
+ }
+
+ async function fstat(rid) {
+ return parseFileInfo(await core.jsonOpAsync("op_fstat_async", { rid }));
+ }
+
+ async function lstat(path) {
+ const res = await core.jsonOpAsync("op_stat_async", {
+ path: pathFromURL(path),
+ lstat: true,
+ });
+ return parseFileInfo(res);
+ }
+
+ function lstatSync(path) {
+ const res = core.jsonOpSync("op_stat_sync", {
+ path: pathFromURL(path),
+ lstat: true,
+ });
+ return parseFileInfo(res);
+ }
+
+ async function stat(path) {
+ const res = await core.jsonOpAsync("op_stat_async", {
+ path: pathFromURL(path),
+ lstat: false,
+ });
+ return parseFileInfo(res);
+ }
+
+ function statSync(path) {
+ const res = core.jsonOpSync("op_stat_sync", {
+ path: pathFromURL(path),
+ lstat: false,
+ });
+ return parseFileInfo(res);
+ }
+
+ function coerceLen(len) {
+ if (len == null || len < 0) {
+ return 0;
+ }
+
+ return len;
+ }
+
+ function ftruncateSync(rid, len) {
+ core.jsonOpSync("op_ftruncate_sync", { rid, len: coerceLen(len) });
+ }
+
+ async function ftruncate(rid, len) {
+ await core.jsonOpAsync("op_ftruncate_async", { rid, len: coerceLen(len) });
+ }
+
+ function truncateSync(path, len) {
+ core.jsonOpSync("op_truncate_sync", { path, len: coerceLen(len) });
+ }
+
+ async function truncate(path, len) {
+ await core.jsonOpAsync("op_truncate_async", { path, len: coerceLen(len) });
+ }
+
+ function umask(mask) {
+ return core.jsonOpSync("op_umask", { mask });
+ }
+
+ function linkSync(oldpath, newpath) {
+ core.jsonOpSync("op_link_sync", { oldpath, newpath });
+ }
+
+ async function link(oldpath, newpath) {
+ await core.jsonOpAsync("op_link_async", { oldpath, newpath });
+ }
+
+ function toUnixTimeFromEpoch(value) {
+ if (value instanceof Date) {
+ const time = value.valueOf();
+ const seconds = Math.trunc(time / 1e3);
+ const nanoseconds = Math.trunc(time - (seconds * 1e3)) * 1e6;
+
+ return [
+ seconds,
+ nanoseconds,
+ ];
+ }
+
+ const seconds = value;
+ const nanoseconds = 0;
+
+ return [
+ seconds,
+ nanoseconds,
+ ];
+ }
+
+ function futimeSync(
+ rid,
+ atime,
+ mtime,
+ ) {
+ core.jsonOpSync("op_futime_sync", {
+ rid,
+ atime: toUnixTimeFromEpoch(atime),
+ mtime: toUnixTimeFromEpoch(mtime),
+ });
+ }
+
+ async function futime(
+ rid,
+ atime,
+ mtime,
+ ) {
+ await core.jsonOpAsync("op_futime_async", {
+ rid,
+ atime: toUnixTimeFromEpoch(atime),
+ mtime: toUnixTimeFromEpoch(mtime),
+ });
+ }
+
+ function utimeSync(
+ path,
+ atime,
+ mtime,
+ ) {
+ core.jsonOpSync("op_utime_sync", {
+ path,
+ atime: toUnixTimeFromEpoch(atime),
+ mtime: toUnixTimeFromEpoch(mtime),
+ });
+ }
+
+ async function utime(
+ path,
+ atime,
+ mtime,
+ ) {
+ await core.jsonOpAsync("op_utime_async", {
+ path,
+ atime: toUnixTimeFromEpoch(atime),
+ mtime: toUnixTimeFromEpoch(mtime),
+ });
+ }
+
+ function symlinkSync(
+ oldpath,
+ newpath,
+ options,
+ ) {
+ core.jsonOpSync("op_symlink_sync", { oldpath, newpath, options });
+ }
+
+ async function symlink(
+ oldpath,
+ newpath,
+ options,
+ ) {
+ await core.jsonOpAsync("op_symlink_async", { oldpath, newpath, options });
+ }
+
+ function fdatasyncSync(rid) {
+ core.jsonOpSync("op_fdatasync_sync", { rid });
+ }
+
+ async function fdatasync(rid) {
+ await core.jsonOpAsync("op_fdatasync_async", { rid });
+ }
+
+ function fsyncSync(rid) {
+ core.jsonOpSync("op_fsync_sync", { rid });
+ }
+
+ async function fsync(rid) {
+ await core.jsonOpAsync("op_fsync_async", { rid });
+ }
+
+ window.__bootstrap.fs = {
+ cwd,
+ chdir,
+ chmodSync,
+ chmod,
+ chown,
+ chownSync,
+ copyFile,
+ copyFileSync,
+ makeTempFile,
+ makeTempDir,
+ makeTempFileSync,
+ makeTempDirSync,
+ mkdir,
+ mkdirSync,
+ readDir,
+ readDirSync,
+ readLinkSync,
+ readLink,
+ realPathSync,
+ realPath,
+ remove,
+ removeSync,
+ renameSync,
+ rename,
+ fstatSync,
+ fstat,
+ lstat,
+ lstatSync,
+ stat,
+ statSync,
+ ftruncate,
+ ftruncateSync,
+ truncate,
+ truncateSync,
+ umask,
+ link,
+ linkSync,
+ futime,
+ futimeSync,
+ utime,
+ utimeSync,
+ symlink,
+ symlinkSync,
+ fdatasync,
+ fdatasyncSync,
+ fsync,
+ fsyncSync,
+ };
+})(this);
diff --git a/runtime/js/30_metrics.js b/runtime/js/30_metrics.js
new file mode 100644
index 000000000..d44a629cb
--- /dev/null
+++ b/runtime/js/30_metrics.js
@@ -0,0 +1,13 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const core = window.Deno.core;
+
+ function metrics() {
+ return core.jsonOpSync("op_metrics");
+ }
+
+ window.__bootstrap.metrics = {
+ metrics,
+ };
+})(this);
diff --git a/runtime/js/30_net.js b/runtime/js/30_net.js
new file mode 100644
index 000000000..7009f6f8d
--- /dev/null
+++ b/runtime/js/30_net.js
@@ -0,0 +1,227 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const core = window.Deno.core;
+ const { errors } = window.__bootstrap.errors;
+ const { read, write } = window.__bootstrap.io;
+
+ const ShutdownMode = {
+ // See http://man7.org/linux/man-pages/man2/shutdown.2.html
+ // Corresponding to SHUT_RD, SHUT_WR, SHUT_RDWR
+ 0: "Read",
+ 1: "Write",
+ 2: "ReadWrite",
+ Read: 0, // TODO: nonsense, remove me.
+ Write: 1,
+ ReadWrite: 2, // unused
+ };
+
+ function shutdown(rid, how) {
+ return core.jsonOpAsync("op_shutdown", { rid, how });
+ }
+
+ function opAccept(rid, transport) {
+ return core.jsonOpAsync("op_accept", { rid, transport });
+ }
+
+ function opListen(args) {
+ return core.jsonOpSync("op_listen", args);
+ }
+
+ function opConnect(args) {
+ return core.jsonOpAsync("op_connect", args);
+ }
+
+ function opReceive(rid, transport, zeroCopy) {
+ return core.jsonOpAsync(
+ "op_datagram_receive",
+ { rid, transport },
+ zeroCopy,
+ );
+ }
+
+ function opSend(args, zeroCopy) {
+ return core.jsonOpAsync("op_datagram_send", args, zeroCopy);
+ }
+
+ class Conn {
+ #rid = 0;
+ #remoteAddr = null;
+ #localAddr = null;
+ constructor(rid, remoteAddr, localAddr) {
+ this.#rid = rid;
+ this.#remoteAddr = remoteAddr;
+ this.#localAddr = localAddr;
+ }
+
+ get rid() {
+ return this.#rid;
+ }
+
+ get remoteAddr() {
+ return this.#remoteAddr;
+ }
+
+ get localAddr() {
+ return this.#localAddr;
+ }
+
+ write(p) {
+ return write(this.rid, p);
+ }
+
+ read(p) {
+ return read(this.rid, p);
+ }
+
+ close() {
+ core.close(this.rid);
+ }
+
+ // TODO(lucacasonato): make this unavailable in stable
+ closeWrite() {
+ shutdown(this.rid, ShutdownMode.Write);
+ }
+ }
+
+ class Listener {
+ #rid = 0;
+ #addr = null;
+
+ constructor(rid, addr) {
+ this.#rid = rid;
+ this.#addr = addr;
+ }
+
+ get rid() {
+ return this.#rid;
+ }
+
+ get addr() {
+ return this.#addr;
+ }
+
+ async accept() {
+ const res = await opAccept(this.rid, this.addr.transport);
+ return new Conn(res.rid, res.remoteAddr, res.localAddr);
+ }
+
+ async next() {
+ let conn;
+ try {
+ conn = await this.accept();
+ } catch (error) {
+ if (error instanceof errors.BadResource) {
+ return { value: undefined, done: true };
+ }
+ throw error;
+ }
+ return { value: conn, done: false };
+ }
+
+ return(value) {
+ this.close();
+ return Promise.resolve({ value, done: true });
+ }
+
+ close() {
+ core.close(this.rid);
+ }
+
+ [Symbol.asyncIterator]() {
+ return this;
+ }
+ }
+
+ class Datagram {
+ #rid = 0;
+ #addr = null;
+
+ constructor(rid, addr, bufSize = 1024) {
+ this.#rid = rid;
+ this.#addr = addr;
+ this.bufSize = bufSize;
+ }
+
+ get rid() {
+ return this.#rid;
+ }
+
+ get addr() {
+ return this.#addr;
+ }
+
+ async receive(p) {
+ const buf = p || new Uint8Array(this.bufSize);
+ const { size, remoteAddr } = await opReceive(
+ this.rid,
+ this.addr.transport,
+ buf,
+ );
+ const sub = buf.subarray(0, size);
+ return [sub, remoteAddr];
+ }
+
+ send(p, addr) {
+ const remote = { hostname: "127.0.0.1", ...addr };
+
+ const args = { ...remote, rid: this.rid };
+ return opSend(args, p);
+ }
+
+ close() {
+ core.close(this.rid);
+ }
+
+ async *[Symbol.asyncIterator]() {
+ while (true) {
+ try {
+ yield await this.receive();
+ } catch (err) {
+ if (err instanceof errors.BadResource) {
+ break;
+ }
+ throw err;
+ }
+ }
+ }
+ }
+
+ function listen({ hostname, ...options }) {
+ const res = opListen({
+ transport: "tcp",
+ hostname: typeof hostname === "undefined" ? "0.0.0.0" : hostname,
+ ...options,
+ });
+
+ return new Listener(res.rid, res.localAddr);
+ }
+
+ async function connect(options) {
+ let res;
+
+ if (options.transport === "unix") {
+ res = await opConnect(options);
+ } else {
+ res = await opConnect({
+ transport: "tcp",
+ hostname: "127.0.0.1",
+ ...options,
+ });
+ }
+
+ return new Conn(res.rid, res.remoteAddr, res.localAddr);
+ }
+
+ window.__bootstrap.net = {
+ connect,
+ Conn,
+ opConnect,
+ listen,
+ opListen,
+ Listener,
+ shutdown,
+ ShutdownMode,
+ Datagram,
+ };
+})(this);
diff --git a/runtime/js/30_os.js b/runtime/js/30_os.js
new file mode 100644
index 000000000..ebc4e8916
--- /dev/null
+++ b/runtime/js/30_os.js
@@ -0,0 +1,66 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const core = window.Deno.core;
+
+ function loadavg() {
+ return core.jsonOpSync("op_loadavg");
+ }
+
+ function hostname() {
+ return core.jsonOpSync("op_hostname");
+ }
+
+ function osRelease() {
+ return core.jsonOpSync("op_os_release");
+ }
+
+ function systemMemoryInfo() {
+ return core.jsonOpSync("op_system_memory_info");
+ }
+
+ function systemCpuInfo() {
+ return core.jsonOpSync("op_system_cpu_info");
+ }
+
+ function exit(code = 0) {
+ core.jsonOpSync("op_exit", { code });
+ throw new Error("Code not reachable");
+ }
+
+ function setEnv(key, value) {
+ core.jsonOpSync("op_set_env", { key, value });
+ }
+
+ function getEnv(key) {
+ return core.jsonOpSync("op_get_env", { key })[0];
+ }
+
+ function deleteEnv(key) {
+ core.jsonOpSync("op_delete_env", { key });
+ }
+
+ const env = {
+ get: getEnv,
+ toObject() {
+ return core.jsonOpSync("op_env");
+ },
+ set: setEnv,
+ delete: deleteEnv,
+ };
+
+ function execPath() {
+ return core.jsonOpSync("op_exec_path");
+ }
+
+ window.__bootstrap.os = {
+ env,
+ execPath,
+ exit,
+ osRelease,
+ systemMemoryInfo,
+ systemCpuInfo,
+ hostname,
+ loadavg,
+ };
+})(this);
diff --git a/runtime/js/40_compiler_api.js b/runtime/js/40_compiler_api.js
new file mode 100644
index 000000000..ea963b67b
--- /dev/null
+++ b/runtime/js/40_compiler_api.js
@@ -0,0 +1,97 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+// This file contains the runtime APIs which will dispatch work to the internal
+// compiler within Deno.
+((window) => {
+ const core = window.Deno.core;
+ const util = window.__bootstrap.util;
+
+ function opCompile(request) {
+ return core.jsonOpAsync("op_compile", request);
+ }
+
+ function opTranspile(
+ request,
+ ) {
+ return core.jsonOpAsync("op_transpile", request);
+ }
+
+ function checkRelative(specifier) {
+ return specifier.match(/^([\.\/\\]|https?:\/{2}|file:\/{2})/)
+ ? specifier
+ : `./${specifier}`;
+ }
+
+ // TODO(bartlomieju): change return type to interface?
+ function transpileOnly(
+ sources,
+ options = {},
+ ) {
+ util.log("Deno.transpileOnly", { sources: Object.keys(sources), options });
+ const payload = {
+ sources,
+ options: JSON.stringify(options),
+ };
+ return opTranspile(payload);
+ }
+
+ // TODO(bartlomieju): change return type to interface?
+ async function compile(
+ rootName,
+ sources,
+ options = {},
+ ) {
+ const payload = {
+ rootName: sources ? rootName : checkRelative(rootName),
+ sources,
+ options: JSON.stringify(options),
+ bundle: false,
+ };
+ util.log("Deno.compile", {
+ rootName: payload.rootName,
+ sources: !!sources,
+ options,
+ });
+ /** @type {{ emittedFiles: Record<string, string>, diagnostics: any[] }} */
+ const result = await opCompile(payload);
+ util.assert(result.emittedFiles);
+ const maybeDiagnostics = result.diagnostics.length === 0
+ ? undefined
+ : result.diagnostics;
+
+ return [maybeDiagnostics, result.emittedFiles];
+ }
+
+ // TODO(bartlomieju): change return type to interface?
+ async function bundle(
+ rootName,
+ sources,
+ options = {},
+ ) {
+ const payload = {
+ rootName: sources ? rootName : checkRelative(rootName),
+ sources,
+ options: JSON.stringify(options),
+ bundle: true,
+ };
+ util.log("Deno.bundle", {
+ rootName: payload.rootName,
+ sources: !!sources,
+ options,
+ });
+ /** @type {{ emittedFiles: Record<string, string>, diagnostics: any[] }} */
+ const result = await opCompile(payload);
+ const output = result.emittedFiles["deno:///bundle.js"];
+ util.assert(output);
+ const maybeDiagnostics = result.diagnostics.length === 0
+ ? undefined
+ : result.diagnostics;
+ return [maybeDiagnostics, output];
+ }
+
+ window.__bootstrap.compilerApi = {
+ bundle,
+ compile,
+ transpileOnly,
+ };
+})(this);
diff --git a/runtime/js/40_diagnostics.js b/runtime/js/40_diagnostics.js
new file mode 100644
index 000000000..2b7457853
--- /dev/null
+++ b/runtime/js/40_diagnostics.js
@@ -0,0 +1,23 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+// Diagnostic provides an abstraction for advice/errors received from a
+// compiler, which is strongly influenced by the format of TypeScript
+// diagnostics.
+
+((window) => {
+ const DiagnosticCategory = {
+ 0: "Warning",
+ 1: "Error",
+ 2: "Suggestion",
+ 3: "Message",
+
+ Warning: 0,
+ Error: 1,
+ Suggestion: 2,
+ Message: 3,
+ };
+
+ window.__bootstrap.diagnostics = {
+ DiagnosticCategory,
+ };
+})(this);
diff --git a/runtime/js/40_error_stack.js b/runtime/js/40_error_stack.js
new file mode 100644
index 000000000..da2ee51f3
--- /dev/null
+++ b/runtime/js/40_error_stack.js
@@ -0,0 +1,23 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const core = window.Deno.core;
+
+ function opFormatDiagnostics(diagnostics) {
+ return core.jsonOpSync("op_format_diagnostic", diagnostics);
+ }
+
+ function opApplySourceMap(location) {
+ const res = core.jsonOpSync("op_apply_source_map", location);
+ return {
+ fileName: res.fileName,
+ lineNumber: res.lineNumber,
+ columnNumber: res.columnNumber,
+ };
+ }
+
+ window.__bootstrap.errorStack = {
+ opApplySourceMap,
+ opFormatDiagnostics,
+ };
+})(this);
diff --git a/runtime/js/40_fs_events.js b/runtime/js/40_fs_events.js
new file mode 100644
index 000000000..a179e8c1b
--- /dev/null
+++ b/runtime/js/40_fs_events.js
@@ -0,0 +1,54 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const core = window.Deno.core;
+ const { errors } = window.__bootstrap.errors;
+
+ class FsWatcher {
+ #rid = 0;
+
+ constructor(paths, options) {
+ const { recursive } = options;
+ this.#rid = core.jsonOpSync("op_fs_events_open", { recursive, paths });
+ }
+
+ get rid() {
+ return this.#rid;
+ }
+
+ async next() {
+ try {
+ return await core.jsonOpAsync("op_fs_events_poll", {
+ rid: this.rid,
+ });
+ } catch (error) {
+ if (error instanceof errors.BadResource) {
+ return { value: undefined, done: true };
+ } else if (error instanceof errors.Interrupted) {
+ return { value: undefined, done: true };
+ }
+ throw error;
+ }
+ }
+
+ return(value) {
+ core.close(this.rid);
+ return Promise.resolve({ value, done: true });
+ }
+
+ [Symbol.asyncIterator]() {
+ return this;
+ }
+ }
+
+ function watchFs(
+ paths,
+ options = { recursive: true },
+ ) {
+ return new FsWatcher(Array.isArray(paths) ? paths : [paths], options);
+ }
+
+ window.__bootstrap.fsEvents = {
+ watchFs,
+ };
+})(this);
diff --git a/runtime/js/40_net_unstable.js b/runtime/js/40_net_unstable.js
new file mode 100644
index 000000000..fcc899a30
--- /dev/null
+++ b/runtime/js/40_net_unstable.js
@@ -0,0 +1,48 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const net = window.__bootstrap.net;
+
+ function listen(options) {
+ if (options.transport === "unix") {
+ const res = net.opListen(options);
+ return new net.Listener(res.rid, res.localAddr);
+ } else {
+ return net.listen(options);
+ }
+ }
+
+ function listenDatagram(
+ options,
+ ) {
+ let res;
+ if (options.transport === "unixpacket") {
+ res = net.opListen(options);
+ } else {
+ res = net.opListen({
+ transport: "udp",
+ hostname: "127.0.0.1",
+ ...options,
+ });
+ }
+
+ return new net.Datagram(res.rid, res.localAddr);
+ }
+
+ async function connect(
+ options,
+ ) {
+ if (options.transport === "unix") {
+ const res = await net.opConnect(options);
+ return new net.Conn(res.rid, res.remoteAddr, res.localAddr);
+ } else {
+ return net.connect(options);
+ }
+ }
+
+ window.__bootstrap.netUnstable = {
+ connect,
+ listenDatagram,
+ listen,
+ };
+})(this);
diff --git a/runtime/js/40_performance.js b/runtime/js/40_performance.js
new file mode 100644
index 000000000..0a63dc704
--- /dev/null
+++ b/runtime/js/40_performance.js
@@ -0,0 +1,341 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const { opNow } = window.__bootstrap.timers;
+ const { cloneValue, illegalConstructorKey } = window.__bootstrap.webUtil;
+
+ const customInspect = Symbol.for("Deno.customInspect");
+ let performanceEntries = [];
+
+ function findMostRecent(
+ name,
+ type,
+ ) {
+ return performanceEntries
+ .slice()
+ .reverse()
+ .find((entry) => entry.name === name && entry.entryType === type);
+ }
+
+ function convertMarkToTimestamp(mark) {
+ if (typeof mark === "string") {
+ const entry = findMostRecent(mark, "mark");
+ if (!entry) {
+ throw new SyntaxError(`Cannot find mark: "${mark}".`);
+ }
+ return entry.startTime;
+ }
+ if (mark < 0) {
+ throw new TypeError("Mark cannot be negative.");
+ }
+ return mark;
+ }
+
+ function filterByNameType(
+ name,
+ type,
+ ) {
+ return performanceEntries.filter(
+ (entry) =>
+ (name ? entry.name === name : true) &&
+ (type ? entry.entryType === type : true),
+ );
+ }
+
+ function now() {
+ return opNow();
+ }
+
+ class PerformanceEntry {
+ #name = "";
+ #entryType = "";
+ #startTime = 0;
+ #duration = 0;
+
+ get name() {
+ return this.#name;
+ }
+
+ get entryType() {
+ return this.#entryType;
+ }
+
+ get startTime() {
+ return this.#startTime;
+ }
+
+ get duration() {
+ return this.#duration;
+ }
+
+ constructor(
+ name = null,
+ entryType = null,
+ startTime = null,
+ duration = null,
+ key = null,
+ ) {
+ if (key != illegalConstructorKey) {
+ throw new TypeError("Illegal constructor.");
+ }
+ this.#name = name;
+ this.#entryType = entryType;
+ this.#startTime = startTime;
+ this.#duration = duration;
+ }
+
+ toJSON() {
+ return {
+ name: this.#name,
+ entryType: this.#entryType,
+ startTime: this.#startTime,
+ duration: this.#duration,
+ };
+ }
+
+ [customInspect]() {
+ return `${this.constructor.name} { name: "${this.name}", entryType: "${this.entryType}", startTime: ${this.startTime}, duration: ${this.duration} }`;
+ }
+ }
+
+ class PerformanceMark extends PerformanceEntry {
+ #detail = null;
+
+ get detail() {
+ return this.#detail;
+ }
+
+ get entryType() {
+ return "mark";
+ }
+
+ constructor(
+ name,
+ { detail = null, startTime = now() } = {},
+ ) {
+ super(name, "mark", startTime, 0, illegalConstructorKey);
+ if (startTime < 0) {
+ throw new TypeError("startTime cannot be negative");
+ }
+ this.#detail = cloneValue(detail);
+ }
+
+ toJSON() {
+ return {
+ name: this.name,
+ entryType: this.entryType,
+ startTime: this.startTime,
+ duration: this.duration,
+ detail: this.detail,
+ };
+ }
+
+ [customInspect]() {
+ return this.detail
+ ? `${this.constructor.name} {\n detail: ${
+ JSON.stringify(this.detail, null, 2)
+ },\n name: "${this.name}",\n entryType: "${this.entryType}",\n startTime: ${this.startTime},\n duration: ${this.duration}\n}`
+ : `${this.constructor.name} { detail: ${this.detail}, name: "${this.name}", entryType: "${this.entryType}", startTime: ${this.startTime}, duration: ${this.duration} }`;
+ }
+ }
+
+ class PerformanceMeasure extends PerformanceEntry {
+ #detail = null;
+
+ get detail() {
+ return this.#detail;
+ }
+
+ get entryType() {
+ return "measure";
+ }
+
+ constructor(
+ name,
+ startTime,
+ duration,
+ detail = null,
+ key,
+ ) {
+ if (key != illegalConstructorKey) {
+ throw new TypeError("Illegal constructor.");
+ }
+ super(name, "measure", startTime, duration, illegalConstructorKey);
+ this.#detail = cloneValue(detail);
+ }
+
+ toJSON() {
+ return {
+ name: this.name,
+ entryType: this.entryType,
+ startTime: this.startTime,
+ duration: this.duration,
+ detail: this.detail,
+ };
+ }
+
+ [customInspect]() {
+ return this.detail
+ ? `${this.constructor.name} {\n detail: ${
+ JSON.stringify(this.detail, null, 2)
+ },\n name: "${this.name}",\n entryType: "${this.entryType}",\n startTime: ${this.startTime},\n duration: ${this.duration}\n}`
+ : `${this.constructor.name} { detail: ${this.detail}, name: "${this.name}", entryType: "${this.entryType}", startTime: ${this.startTime}, duration: ${this.duration} }`;
+ }
+ }
+
+ class Performance {
+ constructor(key = null) {
+ if (key != illegalConstructorKey) {
+ throw new TypeError("Illegal constructor.");
+ }
+ }
+
+ clearMarks(markName) {
+ if (markName == null) {
+ performanceEntries = performanceEntries.filter(
+ (entry) => entry.entryType !== "mark",
+ );
+ } else {
+ performanceEntries = performanceEntries.filter(
+ (entry) => !(entry.name === markName && entry.entryType === "mark"),
+ );
+ }
+ }
+
+ clearMeasures(measureName) {
+ if (measureName == null) {
+ performanceEntries = performanceEntries.filter(
+ (entry) => entry.entryType !== "measure",
+ );
+ } else {
+ performanceEntries = performanceEntries.filter(
+ (entry) =>
+ !(entry.name === measureName && entry.entryType === "measure"),
+ );
+ }
+ }
+
+ getEntries() {
+ return filterByNameType();
+ }
+
+ getEntriesByName(
+ name,
+ type,
+ ) {
+ return filterByNameType(name, type);
+ }
+
+ getEntriesByType(type) {
+ return filterByNameType(undefined, type);
+ }
+
+ mark(
+ markName,
+ options = {},
+ ) {
+ // 3.1.1.1 If the global object is a Window object and markName uses the
+ // same name as a read only attribute in the PerformanceTiming interface,
+ // throw a SyntaxError. - not implemented
+ const entry = new PerformanceMark(markName, options);
+ // 3.1.1.7 Queue entry - not implemented
+ performanceEntries.push(entry);
+ return entry;
+ }
+
+ measure(
+ measureName,
+ startOrMeasureOptions = {},
+ endMark,
+ ) {
+ if (
+ startOrMeasureOptions && typeof startOrMeasureOptions === "object" &&
+ Object.keys(startOrMeasureOptions).length > 0
+ ) {
+ if (endMark) {
+ throw new TypeError("Options cannot be passed with endMark.");
+ }
+ if (
+ !("start" in startOrMeasureOptions) &&
+ !("end" in startOrMeasureOptions)
+ ) {
+ throw new TypeError(
+ "A start or end mark must be supplied in options.",
+ );
+ }
+ if (
+ "start" in startOrMeasureOptions &&
+ "duration" in startOrMeasureOptions &&
+ "end" in startOrMeasureOptions
+ ) {
+ throw new TypeError(
+ "Cannot specify start, end, and duration together in options.",
+ );
+ }
+ }
+ let endTime;
+ if (endMark) {
+ endTime = convertMarkToTimestamp(endMark);
+ } else if (
+ typeof startOrMeasureOptions === "object" &&
+ "end" in startOrMeasureOptions
+ ) {
+ endTime = convertMarkToTimestamp(startOrMeasureOptions.end);
+ } else if (
+ typeof startOrMeasureOptions === "object" &&
+ "start" in startOrMeasureOptions &&
+ "duration" in startOrMeasureOptions
+ ) {
+ const start = convertMarkToTimestamp(startOrMeasureOptions.start);
+ const duration = convertMarkToTimestamp(startOrMeasureOptions.duration);
+ endTime = start + duration;
+ } else {
+ endTime = now();
+ }
+ let startTime;
+ if (
+ typeof startOrMeasureOptions === "object" &&
+ "start" in startOrMeasureOptions
+ ) {
+ startTime = convertMarkToTimestamp(startOrMeasureOptions.start);
+ } else if (
+ typeof startOrMeasureOptions === "object" &&
+ "end" in startOrMeasureOptions &&
+ "duration" in startOrMeasureOptions
+ ) {
+ const end = convertMarkToTimestamp(startOrMeasureOptions.end);
+ const duration = convertMarkToTimestamp(startOrMeasureOptions.duration);
+ startTime = end - duration;
+ } else if (typeof startOrMeasureOptions === "string") {
+ startTime = convertMarkToTimestamp(startOrMeasureOptions);
+ } else {
+ startTime = 0;
+ }
+ const entry = new PerformanceMeasure(
+ measureName,
+ startTime,
+ endTime - startTime,
+ typeof startOrMeasureOptions === "object"
+ ? startOrMeasureOptions.detail ?? null
+ : null,
+ illegalConstructorKey,
+ );
+ performanceEntries.push(entry);
+ return entry;
+ }
+
+ now() {
+ return now();
+ }
+ }
+
+ const performance = new Performance(illegalConstructorKey);
+
+ window.__bootstrap.performance = {
+ PerformanceEntry,
+ PerformanceMark,
+ PerformanceMeasure,
+ Performance,
+ performance,
+ };
+})(this);
diff --git a/runtime/js/40_permissions.js b/runtime/js/40_permissions.js
new file mode 100644
index 000000000..50d471b6a
--- /dev/null
+++ b/runtime/js/40_permissions.js
@@ -0,0 +1,65 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const core = window.Deno.core;
+ const { illegalConstructorKey } = window.__bootstrap.webUtil;
+
+ function opQuery(desc) {
+ return core.jsonOpSync("op_query_permission", desc).state;
+ }
+
+ function opRevoke(desc) {
+ return core.jsonOpSync("op_revoke_permission", desc).state;
+ }
+
+ function opRequest(desc) {
+ return core.jsonOpSync("op_request_permission", desc).state;
+ }
+
+ class PermissionStatus {
+ constructor(state = null, key = null) {
+ if (key != illegalConstructorKey) {
+ throw new TypeError("Illegal constructor.");
+ }
+ this.state = state;
+ }
+ // TODO(kt3k): implement onchange handler
+ }
+
+ class Permissions {
+ constructor(key) {
+ if (key != illegalConstructorKey) {
+ throw new TypeError("Illegal constructor.");
+ }
+ }
+
+ query(desc) {
+ const state = opQuery(desc);
+ return Promise.resolve(
+ new PermissionStatus(state, illegalConstructorKey),
+ );
+ }
+
+ revoke(desc) {
+ const state = opRevoke(desc);
+ return Promise.resolve(
+ new PermissionStatus(state, illegalConstructorKey),
+ );
+ }
+
+ request(desc) {
+ const state = opRequest(desc);
+ return Promise.resolve(
+ new PermissionStatus(state, illegalConstructorKey),
+ );
+ }
+ }
+
+ const permissions = new Permissions(illegalConstructorKey);
+
+ window.__bootstrap.permissions = {
+ permissions,
+ Permissions,
+ PermissionStatus,
+ };
+})(this);
diff --git a/runtime/js/40_plugins.js b/runtime/js/40_plugins.js
new file mode 100644
index 000000000..f5aefd400
--- /dev/null
+++ b/runtime/js/40_plugins.js
@@ -0,0 +1,13 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const core = window.Deno.core;
+
+ function openPlugin(filename) {
+ return core.jsonOpSync("op_open_plugin", { filename });
+ }
+
+ window.__bootstrap.plugins = {
+ openPlugin,
+ };
+})(this);
diff --git a/runtime/js/40_process.js b/runtime/js/40_process.js
new file mode 100644
index 000000000..b46a1aead
--- /dev/null
+++ b/runtime/js/40_process.js
@@ -0,0 +1,122 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const core = window.Deno.core;
+ const { File } = window.__bootstrap.files;
+ const { readAll } = window.__bootstrap.buffer;
+ const { assert, pathFromURL } = window.__bootstrap.util;
+
+ function opKill(pid, signo) {
+ core.jsonOpSync("op_kill", { pid, signo });
+ }
+
+ function opRunStatus(rid) {
+ return core.jsonOpAsync("op_run_status", { rid });
+ }
+
+ function opRun(request) {
+ assert(request.cmd.length > 0);
+ return core.jsonOpSync("op_run", request);
+ }
+
+ async function runStatus(rid) {
+ const res = await opRunStatus(rid);
+
+ if (res.gotSignal) {
+ const signal = res.exitSignal;
+ return { success: false, code: 128 + signal, signal };
+ } else if (res.exitCode != 0) {
+ return { success: false, code: res.exitCode };
+ } else {
+ return { success: true, code: 0 };
+ }
+ }
+
+ class Process {
+ constructor(res) {
+ this.rid = res.rid;
+ this.pid = res.pid;
+
+ if (res.stdinRid && res.stdinRid > 0) {
+ this.stdin = new File(res.stdinRid);
+ }
+
+ if (res.stdoutRid && res.stdoutRid > 0) {
+ this.stdout = new File(res.stdoutRid);
+ }
+
+ if (res.stderrRid && res.stderrRid > 0) {
+ this.stderr = new File(res.stderrRid);
+ }
+ }
+
+ status() {
+ return runStatus(this.rid);
+ }
+
+ async output() {
+ if (!this.stdout) {
+ throw new TypeError("stdout was not piped");
+ }
+ try {
+ return await readAll(this.stdout);
+ } finally {
+ this.stdout.close();
+ }
+ }
+
+ async stderrOutput() {
+ if (!this.stderr) {
+ throw new TypeError("stderr was not piped");
+ }
+ try {
+ return await readAll(this.stderr);
+ } finally {
+ this.stderr.close();
+ }
+ }
+
+ close() {
+ core.close(this.rid);
+ }
+
+ kill(signo) {
+ opKill(this.pid, signo);
+ }
+ }
+
+ function isRid(arg) {
+ return !isNaN(arg);
+ }
+
+ function run({
+ cmd,
+ cwd = undefined,
+ env = {},
+ stdout = "inherit",
+ stderr = "inherit",
+ stdin = "inherit",
+ }) {
+ if (cmd[0] != null) {
+ cmd[0] = pathFromURL(cmd[0]);
+ }
+ const res = opRun({
+ cmd: cmd.map(String),
+ cwd,
+ env: Object.entries(env),
+ stdin: isRid(stdin) ? "" : stdin,
+ stdout: isRid(stdout) ? "" : stdout,
+ stderr: isRid(stderr) ? "" : stderr,
+ stdinRid: isRid(stdin) ? stdin : 0,
+ stdoutRid: isRid(stdout) ? stdout : 0,
+ stderrRid: isRid(stderr) ? stderr : 0,
+ });
+ return new Process(res);
+ }
+
+ window.__bootstrap.process = {
+ run,
+ Process,
+ kill: opKill,
+ };
+})(this);
diff --git a/runtime/js/40_read_file.js b/runtime/js/40_read_file.js
new file mode 100644
index 000000000..9a36f335b
--- /dev/null
+++ b/runtime/js/40_read_file.js
@@ -0,0 +1,43 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const { open, openSync } = window.__bootstrap.files;
+ const { readAll, readAllSync } = window.__bootstrap.buffer;
+
+ function readFileSync(path) {
+ const file = openSync(path);
+ const contents = readAllSync(file);
+ file.close();
+ return contents;
+ }
+
+ async function readFile(path) {
+ const file = await open(path);
+ const contents = await readAll(file);
+ file.close();
+ return contents;
+ }
+
+ function readTextFileSync(path) {
+ const file = openSync(path);
+ const contents = readAllSync(file);
+ file.close();
+ const decoder = new TextDecoder();
+ return decoder.decode(contents);
+ }
+
+ async function readTextFile(path) {
+ const file = await open(path);
+ const contents = await readAll(file);
+ file.close();
+ const decoder = new TextDecoder();
+ return decoder.decode(contents);
+ }
+
+ window.__bootstrap.readFile = {
+ readFile,
+ readFileSync,
+ readTextFileSync,
+ readTextFile,
+ };
+})(this);
diff --git a/runtime/js/40_signals.js b/runtime/js/40_signals.js
new file mode 100644
index 000000000..091afd66a
--- /dev/null
+++ b/runtime/js/40_signals.js
@@ -0,0 +1,265 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const core = window.Deno.core;
+ const { build } = window.__bootstrap.build;
+ const { errors } = window.__bootstrap.errors;
+
+ function bindSignal(signo) {
+ return core.jsonOpSync("op_signal_bind", { signo });
+ }
+
+ function pollSignal(rid) {
+ return core.jsonOpAsync("op_signal_poll", { rid });
+ }
+
+ function unbindSignal(rid) {
+ core.jsonOpSync("op_signal_unbind", { rid });
+ }
+
+ // From `kill -l`
+ const LinuxSignal = {
+ 1: "SIGHUP",
+ 2: "SIGINT",
+ 3: "SIGQUIT",
+ 4: "SIGILL",
+ 5: "SIGTRAP",
+ 6: "SIGABRT",
+ 7: "SIGBUS",
+ 8: "SIGFPE",
+ 9: "SIGKILL",
+ 10: "SIGUSR1",
+ 11: "SIGSEGV",
+ 12: "SIGUSR2",
+ 13: "SIGPIPE",
+ 14: "SIGALRM",
+ 15: "SIGTERM",
+ 16: "SIGSTKFLT",
+ 17: "SIGCHLD",
+ 18: "SIGCONT",
+ 19: "SIGSTOP",
+ 20: "SIGTSTP",
+ 21: "SIGTTIN",
+ 22: "SIGTTOU",
+ 23: "SIGURG",
+ 24: "SIGXCPU",
+ 25: "SIGXFSZ",
+ 26: "SIGVTALRM",
+ 27: "SIGPROF",
+ 28: "SIGWINCH",
+ 29: "SIGIO",
+ 30: "SIGPWR",
+ 31: "SIGSYS",
+ SIGHUP: 1,
+ SIGINT: 2,
+ SIGQUIT: 3,
+ SIGILL: 4,
+ SIGTRAP: 5,
+ SIGABRT: 6,
+ SIGBUS: 7,
+ SIGFPE: 8,
+ SIGKILL: 9,
+ SIGUSR1: 10,
+ SIGSEGV: 11,
+ SIGUSR2: 12,
+ SIGPIPE: 13,
+ SIGALRM: 14,
+ SIGTERM: 15,
+ SIGSTKFLT: 16,
+ SIGCHLD: 17,
+ SIGCONT: 18,
+ SIGSTOP: 19,
+ SIGTSTP: 20,
+ SIGTTIN: 21,
+ SIGTTOU: 22,
+ SIGURG: 23,
+ SIGXCPU: 24,
+ SIGXFSZ: 25,
+ SIGVTALRM: 26,
+ SIGPROF: 27,
+ SIGWINCH: 28,
+ SIGIO: 29,
+ SIGPWR: 30,
+ SIGSYS: 31,
+ };
+
+ // From `kill -l`
+ const MacOSSignal = {
+ 1: "SIGHUP",
+ 2: "SIGINT",
+ 3: "SIGQUIT",
+ 4: "SIGILL",
+ 5: "SIGTRAP",
+ 6: "SIGABRT",
+ 7: "SIGEMT",
+ 8: "SIGFPE",
+ 9: "SIGKILL",
+ 10: "SIGBUS",
+ 11: "SIGSEGV",
+ 12: "SIGSYS",
+ 13: "SIGPIPE",
+ 14: "SIGALRM",
+ 15: "SIGTERM",
+ 16: "SIGURG",
+ 17: "SIGSTOP",
+ 18: "SIGTSTP",
+ 19: "SIGCONT",
+ 20: "SIGCHLD",
+ 21: "SIGTTIN",
+ 22: "SIGTTOU",
+ 23: "SIGIO",
+ 24: "SIGXCPU",
+ 25: "SIGXFSZ",
+ 26: "SIGVTALRM",
+ 27: "SIGPROF",
+ 28: "SIGWINCH",
+ 29: "SIGINFO",
+ 30: "SIGUSR1",
+ 31: "SIGUSR2",
+ SIGHUP: 1,
+ SIGINT: 2,
+ SIGQUIT: 3,
+ SIGILL: 4,
+ SIGTRAP: 5,
+ SIGABRT: 6,
+ SIGEMT: 7,
+ SIGFPE: 8,
+ SIGKILL: 9,
+ SIGBUS: 10,
+ SIGSEGV: 11,
+ SIGSYS: 12,
+ SIGPIPE: 13,
+ SIGALRM: 14,
+ SIGTERM: 15,
+ SIGURG: 16,
+ SIGSTOP: 17,
+ SIGTSTP: 18,
+ SIGCONT: 19,
+ SIGCHLD: 20,
+ SIGTTIN: 21,
+ SIGTTOU: 22,
+ SIGIO: 23,
+ SIGXCPU: 24,
+ SIGXFSZ: 25,
+ SIGVTALRM: 26,
+ SIGPROF: 27,
+ SIGWINCH: 28,
+ SIGINFO: 29,
+ SIGUSR1: 30,
+ SIGUSR2: 31,
+ };
+
+ const Signal = {};
+
+ function setSignals() {
+ if (build.os === "darwin") {
+ Object.assign(Signal, MacOSSignal);
+ } else {
+ Object.assign(Signal, LinuxSignal);
+ }
+ }
+
+ function signal(signo) {
+ if (build.os === "windows") {
+ throw new Error("not implemented!");
+ }
+ return new SignalStream(signo);
+ }
+
+ const signals = {
+ alarm() {
+ return signal(Signal.SIGALRM);
+ },
+ child() {
+ return signal(Signal.SIGCHLD);
+ },
+ hungup() {
+ return signal(Signal.SIGHUP);
+ },
+ interrupt() {
+ return signal(Signal.SIGINT);
+ },
+ io() {
+ return signal(Signal.SIGIO);
+ },
+ pipe() {
+ return signal(Signal.SIGPIPE);
+ },
+ quit() {
+ return signal(Signal.SIGQUIT);
+ },
+ terminate() {
+ return signal(Signal.SIGTERM);
+ },
+ userDefined1() {
+ return signal(Signal.SIGUSR1);
+ },
+ userDefined2() {
+ return signal(Signal.SIGUSR2);
+ },
+ windowChange() {
+ return signal(Signal.SIGWINCH);
+ },
+ };
+
+ class SignalStream {
+ #disposed = false;
+ #pollingPromise = Promise.resolve(false);
+ #rid = 0;
+
+ constructor(signo) {
+ this.#rid = bindSignal(signo).rid;
+ this.#loop();
+ }
+
+ #pollSignal = async () => {
+ let res;
+ try {
+ res = await pollSignal(this.#rid);
+ } catch (error) {
+ if (error instanceof errors.BadResource) {
+ return true;
+ }
+ throw error;
+ }
+ return res.done;
+ };
+
+ #loop = async () => {
+ do {
+ this.#pollingPromise = this.#pollSignal();
+ } while (!(await this.#pollingPromise) && !this.#disposed);
+ };
+
+ then(
+ f,
+ g,
+ ) {
+ return this.#pollingPromise.then(() => {}).then(f, g);
+ }
+
+ async next() {
+ return { done: await this.#pollingPromise, value: undefined };
+ }
+
+ [Symbol.asyncIterator]() {
+ return this;
+ }
+
+ dispose() {
+ if (this.#disposed) {
+ throw new Error("The stream has already been disposed.");
+ }
+ this.#disposed = true;
+ unbindSignal(this.#rid);
+ }
+ }
+
+ window.__bootstrap.signals = {
+ signal,
+ signals,
+ Signal,
+ SignalStream,
+ setSignals,
+ };
+})(this);
diff --git a/runtime/js/40_testing.js b/runtime/js/40_testing.js
new file mode 100644
index 000000000..082d17fe0
--- /dev/null
+++ b/runtime/js/40_testing.js
@@ -0,0 +1,350 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const core = window.Deno.core;
+ const colors = window.__bootstrap.colors;
+ const { exit } = window.__bootstrap.os;
+ const { Console, inspectArgs } = window.__bootstrap.console;
+ const { stdout } = window.__bootstrap.files;
+ const { exposeForTest } = window.__bootstrap.internals;
+ const { metrics } = window.__bootstrap.metrics;
+ const { assert } = window.__bootstrap.util;
+
+ const disabledConsole = new Console(() => {});
+
+ function delay(ms) {
+ return new Promise((resolve) => {
+ setTimeout(resolve, ms);
+ });
+ }
+
+ function formatDuration(time = 0) {
+ const gray = colors.maybeColor(colors.gray);
+ const italic = colors.maybeColor(colors.italic);
+ const timeStr = `(${time}ms)`;
+ return gray(italic(timeStr));
+ }
+
+ // Wrap test function in additional assertion that makes sure
+ // the test case does not leak async "ops" - ie. number of async
+ // completed ops after the test is the same as number of dispatched
+ // ops. Note that "unref" ops are ignored since in nature that are
+ // optional.
+ function assertOps(fn) {
+ return async function asyncOpSanitizer() {
+ const pre = metrics();
+ await fn();
+ // Defer until next event loop turn - that way timeouts and intervals
+ // cleared can actually be removed from resource table, otherwise
+ // false positives may occur (https://github.com/denoland/deno/issues/4591)
+ await delay(0);
+ const post = metrics();
+ // We're checking diff because one might spawn HTTP server in the background
+ // that will be a pending async op before test starts.
+ const dispatchedDiff = post.opsDispatchedAsync - pre.opsDispatchedAsync;
+ const completedDiff = post.opsCompletedAsync - pre.opsCompletedAsync;
+ assert(
+ dispatchedDiff === completedDiff,
+ `Test case is leaking async ops.
+Before:
+ - dispatched: ${pre.opsDispatchedAsync}
+ - completed: ${pre.opsCompletedAsync}
+After:
+ - dispatched: ${post.opsDispatchedAsync}
+ - completed: ${post.opsCompletedAsync}
+
+Make sure to await all promises returned from Deno APIs before
+finishing test case.`,
+ );
+ };
+ }
+
+ // Wrap test function in additional assertion that makes sure
+ // the test case does not "leak" resources - ie. resource table after
+ // the test has exactly the same contents as before the test.
+ function assertResources(
+ fn,
+ ) {
+ return async function resourceSanitizer() {
+ const pre = core.resources();
+ await fn();
+ const post = core.resources();
+
+ const preStr = JSON.stringify(pre, null, 2);
+ const postStr = JSON.stringify(post, null, 2);
+ const msg = `Test case is leaking resources.
+Before: ${preStr}
+After: ${postStr}
+
+Make sure to close all open resource handles returned from Deno APIs before
+finishing test case.`;
+ assert(preStr === postStr, msg);
+ };
+ }
+
+ const TEST_REGISTRY = [];
+
+ // Main test function provided by Deno, as you can see it merely
+ // creates a new object with "name" and "fn" fields.
+ function test(
+ t,
+ fn,
+ ) {
+ let testDef;
+ const defaults = {
+ ignore: false,
+ only: false,
+ sanitizeOps: true,
+ sanitizeResources: true,
+ };
+
+ if (typeof t === "string") {
+ if (!fn || typeof fn != "function") {
+ throw new TypeError("Missing test function");
+ }
+ if (!t) {
+ throw new TypeError("The test name can't be empty");
+ }
+ testDef = { fn: fn, name: t, ...defaults };
+ } else {
+ if (!t.fn) {
+ throw new TypeError("Missing test function");
+ }
+ if (!t.name) {
+ throw new TypeError("The test name can't be empty");
+ }
+ testDef = { ...defaults, ...t };
+ }
+
+ if (testDef.sanitizeOps) {
+ testDef.fn = assertOps(testDef.fn);
+ }
+
+ if (testDef.sanitizeResources) {
+ testDef.fn = assertResources(testDef.fn);
+ }
+
+ TEST_REGISTRY.push(testDef);
+ }
+
+ const encoder = new TextEncoder();
+
+ function log(msg, noNewLine = false) {
+ if (!noNewLine) {
+ msg += "\n";
+ }
+
+ // Using `stdout` here because it doesn't force new lines
+ // compared to `console.log`; `core.print` on the other hand
+ // is line-buffered and doesn't output message without newline
+ stdout.writeSync(encoder.encode(msg));
+ }
+
+ function reportToConsole(message) {
+ const green = colors.maybeColor(colors.green);
+ const red = colors.maybeColor(colors.red);
+ const yellow = colors.maybeColor(colors.yellow);
+ const redFailed = red("FAILED");
+ const greenOk = green("ok");
+ const yellowIgnored = yellow("ignored");
+ if (message.start != null) {
+ log(`running ${message.start.tests.length} tests`);
+ } else if (message.testStart != null) {
+ const { name } = message.testStart;
+
+ log(`test ${name} ... `, true);
+ return;
+ } else if (message.testEnd != null) {
+ switch (message.testEnd.status) {
+ case "passed":
+ log(`${greenOk} ${formatDuration(message.testEnd.duration)}`);
+ break;
+ case "failed":
+ log(`${redFailed} ${formatDuration(message.testEnd.duration)}`);
+ break;
+ case "ignored":
+ log(`${yellowIgnored} ${formatDuration(message.testEnd.duration)}`);
+ break;
+ }
+ } else if (message.end != null) {
+ const failures = message.end.results.filter((m) => m.error != null);
+ if (failures.length > 0) {
+ log(`\nfailures:\n`);
+
+ for (const { name, error } of failures) {
+ log(name);
+ log(inspectArgs([error]));
+ log("");
+ }
+
+ log(`failures:\n`);
+
+ for (const { name } of failures) {
+ log(`\t${name}`);
+ }
+ }
+ log(
+ `\ntest result: ${message.end.failed ? redFailed : greenOk}. ` +
+ `${message.end.passed} passed; ${message.end.failed} failed; ` +
+ `${message.end.ignored} ignored; ${message.end.measured} measured; ` +
+ `${message.end.filtered} filtered out ` +
+ `${formatDuration(message.end.duration)}\n`,
+ );
+
+ if (message.end.usedOnly && message.end.failed == 0) {
+ log(`${redFailed} because the "only" option was used\n`);
+ }
+ }
+ }
+
+ exposeForTest("reportToConsole", reportToConsole);
+
+ // TODO: already implements AsyncGenerator<RunTestsMessage>, but add as "implements to class"
+ // TODO: implements PromiseLike<RunTestsEndResult>
+ class TestRunner {
+ #usedOnly = false;
+
+ constructor(
+ tests,
+ filterFn,
+ failFast,
+ ) {
+ this.stats = {
+ filtered: 0,
+ ignored: 0,
+ measured: 0,
+ passed: 0,
+ failed: 0,
+ };
+ this.filterFn = filterFn;
+ this.failFast = failFast;
+ const onlyTests = tests.filter(({ only }) => only);
+ this.#usedOnly = onlyTests.length > 0;
+ const unfilteredTests = this.#usedOnly ? onlyTests : tests;
+ this.testsToRun = unfilteredTests.filter(filterFn);
+ this.stats.filtered = unfilteredTests.length - this.testsToRun.length;
+ }
+
+ async *[Symbol.asyncIterator]() {
+ yield { start: { tests: this.testsToRun } };
+
+ const results = [];
+ const suiteStart = +new Date();
+ for (const test of this.testsToRun) {
+ const endMessage = {
+ name: test.name,
+ duration: 0,
+ };
+ yield { testStart: { ...test } };
+ if (test.ignore) {
+ endMessage.status = "ignored";
+ this.stats.ignored++;
+ } else {
+ const start = +new Date();
+ try {
+ await test.fn();
+ endMessage.status = "passed";
+ this.stats.passed++;
+ } catch (err) {
+ endMessage.status = "failed";
+ endMessage.error = err;
+ this.stats.failed++;
+ }
+ endMessage.duration = +new Date() - start;
+ }
+ results.push(endMessage);
+ yield { testEnd: endMessage };
+ if (this.failFast && endMessage.error != null) {
+ break;
+ }
+ }
+
+ const duration = +new Date() - suiteStart;
+
+ yield {
+ end: { ...this.stats, usedOnly: this.#usedOnly, duration, results },
+ };
+ }
+ }
+
+ function createFilterFn(
+ filter,
+ skip,
+ ) {
+ return (def) => {
+ let passes = true;
+
+ if (filter) {
+ if (filter instanceof RegExp) {
+ passes = passes && filter.test(def.name);
+ } else if (filter.startsWith("/") && filter.endsWith("/")) {
+ const filterAsRegex = new RegExp(filter.slice(1, filter.length - 1));
+ passes = passes && filterAsRegex.test(def.name);
+ } else {
+ passes = passes && def.name.includes(filter);
+ }
+ }
+
+ if (skip) {
+ if (skip instanceof RegExp) {
+ passes = passes && !skip.test(def.name);
+ } else {
+ passes = passes && !def.name.includes(skip);
+ }
+ }
+
+ return passes;
+ };
+ }
+
+ exposeForTest("createFilterFn", createFilterFn);
+
+ async function runTests({
+ exitOnFail = true,
+ failFast = false,
+ filter = undefined,
+ skip = undefined,
+ disableLog = false,
+ reportToConsole: reportToConsole_ = true,
+ onMessage = undefined,
+ } = {}) {
+ const filterFn = createFilterFn(filter, skip);
+ const testRunner = new TestRunner(TEST_REGISTRY, filterFn, failFast);
+
+ const originalConsole = globalThis.console;
+
+ if (disableLog) {
+ globalThis.console = disabledConsole;
+ }
+
+ let endMsg;
+
+ for await (const message of testRunner) {
+ if (onMessage != null) {
+ await onMessage(message);
+ }
+ if (reportToConsole_) {
+ reportToConsole(message);
+ }
+ if (message.end != null) {
+ endMsg = message.end;
+ }
+ }
+
+ if (disableLog) {
+ globalThis.console = originalConsole;
+ }
+
+ if ((endMsg.failed > 0 || endMsg?.usedOnly) && exitOnFail) {
+ exit(1);
+ }
+
+ return endMsg;
+ }
+
+ exposeForTest("runTests", runTests);
+
+ window.__bootstrap.testing = {
+ test,
+ };
+})(this);
diff --git a/runtime/js/40_tls.js b/runtime/js/40_tls.js
new file mode 100644
index 000000000..d66e0bd01
--- /dev/null
+++ b/runtime/js/40_tls.js
@@ -0,0 +1,82 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const core = window.Deno.core;
+ const { Listener, Conn } = window.__bootstrap.net;
+
+ function opConnectTls(
+ args,
+ ) {
+ return core.jsonOpAsync("op_connect_tls", args);
+ }
+
+ function opAcceptTLS(rid) {
+ return core.jsonOpAsync("op_accept_tls", { rid });
+ }
+
+ function opListenTls(args) {
+ return core.jsonOpSync("op_listen_tls", args);
+ }
+
+ function opStartTls(args) {
+ return core.jsonOpAsync("op_start_tls", args);
+ }
+
+ async function connectTls({
+ port,
+ hostname = "127.0.0.1",
+ transport = "tcp",
+ certFile = undefined,
+ }) {
+ const res = await opConnectTls({
+ port,
+ hostname,
+ transport,
+ certFile,
+ });
+ return new Conn(res.rid, res.remoteAddr, res.localAddr);
+ }
+
+ class TLSListener extends Listener {
+ async accept() {
+ const res = await opAcceptTLS(this.rid);
+ return new Conn(res.rid, res.remoteAddr, res.localAddr);
+ }
+ }
+
+ function listenTls({
+ port,
+ certFile,
+ keyFile,
+ hostname = "0.0.0.0",
+ transport = "tcp",
+ }) {
+ const res = opListenTls({
+ port,
+ certFile,
+ keyFile,
+ hostname,
+ transport,
+ });
+ return new TLSListener(res.rid, res.localAddr);
+ }
+
+ async function startTls(
+ conn,
+ { hostname = "127.0.0.1", certFile } = {},
+ ) {
+ const res = await opStartTls({
+ rid: conn.rid,
+ hostname,
+ certFile,
+ });
+ return new Conn(res.rid, res.remoteAddr, res.localAddr);
+ }
+
+ window.__bootstrap.tls = {
+ startTls,
+ listenTls,
+ connectTls,
+ TLSListener,
+ };
+})(this);
diff --git a/runtime/js/40_tty.js b/runtime/js/40_tty.js
new file mode 100644
index 000000000..598d33237
--- /dev/null
+++ b/runtime/js/40_tty.js
@@ -0,0 +1,28 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const core = window.Deno.core;
+
+ function consoleSize(rid) {
+ return core.jsonOpSync("op_console_size", { rid });
+ }
+
+ function isatty(rid) {
+ return core.jsonOpSync("op_isatty", { rid });
+ }
+
+ const DEFAULT_SET_RAW_OPTIONS = {
+ cbreak: false,
+ };
+
+ function setRaw(rid, mode, options = {}) {
+ const rOptions = { ...DEFAULT_SET_RAW_OPTIONS, ...options };
+ core.jsonOpSync("op_set_raw", { rid, mode, options: rOptions });
+ }
+
+ window.__bootstrap.tty = {
+ consoleSize,
+ isatty,
+ setRaw,
+ };
+})(this);
diff --git a/runtime/js/40_write_file.js b/runtime/js/40_write_file.js
new file mode 100644
index 000000000..7a9cb1f40
--- /dev/null
+++ b/runtime/js/40_write_file.js
@@ -0,0 +1,92 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+((window) => {
+ const { stat, statSync, chmod, chmodSync } = window.__bootstrap.fs;
+ const { open, openSync } = window.__bootstrap.files;
+ const { writeAll, writeAllSync } = window.__bootstrap.buffer;
+ const { build } = window.__bootstrap.build;
+
+ function writeFileSync(
+ path,
+ data,
+ options = {},
+ ) {
+ if (options.create !== undefined) {
+ const create = !!options.create;
+ if (!create) {
+ // verify that file exists
+ statSync(path);
+ }
+ }
+
+ const openOptions = options.append
+ ? { write: true, create: true, append: true }
+ : { write: true, create: true, truncate: true };
+ const file = openSync(path, openOptions);
+
+ if (
+ options.mode !== undefined &&
+ options.mode !== null &&
+ build.os !== "windows"
+ ) {
+ chmodSync(path, options.mode);
+ }
+
+ writeAllSync(file, data);
+ file.close();
+ }
+
+ async function writeFile(
+ path,
+ data,
+ options = {},
+ ) {
+ if (options.create !== undefined) {
+ const create = !!options.create;
+ if (!create) {
+ // verify that file exists
+ await stat(path);
+ }
+ }
+
+ const openOptions = options.append
+ ? { write: true, create: true, append: true }
+ : { write: true, create: true, truncate: true };
+ const file = await open(path, openOptions);
+
+ if (
+ options.mode !== undefined &&
+ options.mode !== null &&
+ build.os !== "windows"
+ ) {
+ await chmod(path, options.mode);
+ }
+
+ await writeAll(file, data);
+ file.close();
+ }
+
+ function writeTextFileSync(
+ path,
+ data,
+ options = {},
+ ) {
+ const encoder = new TextEncoder();
+ return writeFileSync(path, encoder.encode(data), options);
+ }
+
+ function writeTextFile(
+ path,
+ data,
+ options = {},
+ ) {
+ const encoder = new TextEncoder();
+ return writeFile(path, encoder.encode(data), options);
+ }
+
+ window.__bootstrap.writeFile = {
+ writeTextFile,
+ writeTextFileSync,
+ writeFile,
+ writeFileSync,
+ };
+})(this);
diff --git a/runtime/js/41_prompt.js b/runtime/js/41_prompt.js
new file mode 100644
index 000000000..ec294668b
--- /dev/null
+++ b/runtime/js/41_prompt.js
@@ -0,0 +1,80 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+((window) => {
+ const { stdin } = window.__bootstrap.files;
+ const { isatty } = window.__bootstrap.tty;
+ const LF = "\n".charCodeAt(0);
+ const CR = "\r".charCodeAt(0);
+ const decoder = new TextDecoder();
+ const core = window.Deno.core;
+
+ function alert(message = "Alert") {
+ if (!isatty(stdin.rid)) {
+ return;
+ }
+
+ core.print(`${message} [Enter] `, false);
+
+ readLineFromStdinSync();
+ }
+
+ function confirm(message = "Confirm") {
+ if (!isatty(stdin.rid)) {
+ return false;
+ }
+
+ core.print(`${message} [y/N] `, false);
+
+ const answer = readLineFromStdinSync();
+
+ return answer === "Y" || answer === "y";
+ }
+
+ function prompt(message = "Prompt", defaultValue) {
+ defaultValue ??= null;
+
+ if (!isatty(stdin.rid)) {
+ return null;
+ }
+
+ core.print(`${message} `, false);
+
+ if (defaultValue) {
+ core.print(`[${defaultValue}] `, false);
+ }
+
+ return readLineFromStdinSync() || defaultValue;
+ }
+
+ function readLineFromStdinSync() {
+ const c = new Uint8Array(1);
+ const buf = [];
+
+ while (true) {
+ const n = stdin.readSync(c);
+ if (n === null || n === 0) {
+ break;
+ }
+ if (c[0] === CR) {
+ const n = stdin.readSync(c);
+ if (c[0] === LF) {
+ break;
+ }
+ buf.push(CR);
+ if (n === null || n === 0) {
+ break;
+ }
+ }
+ if (c[0] === LF) {
+ break;
+ }
+ buf.push(c[0]);
+ }
+ return decoder.decode(new Uint8Array(buf));
+ }
+
+ window.__bootstrap.prompt = {
+ alert,
+ confirm,
+ prompt,
+ };
+})(this);
diff --git a/runtime/js/90_deno_ns.js b/runtime/js/90_deno_ns.js
new file mode 100644
index 000000000..9188788ec
--- /dev/null
+++ b/runtime/js/90_deno_ns.js
@@ -0,0 +1,137 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+
+((window) => {
+ const __bootstrap = window.__bootstrap;
+ __bootstrap.denoNs = {
+ test: __bootstrap.testing.test,
+ metrics: __bootstrap.metrics.metrics,
+ Process: __bootstrap.process.Process,
+ run: __bootstrap.process.run,
+ isatty: __bootstrap.tty.isatty,
+ writeFileSync: __bootstrap.writeFile.writeFileSync,
+ writeFile: __bootstrap.writeFile.writeFile,
+ writeTextFileSync: __bootstrap.writeFile.writeTextFileSync,
+ writeTextFile: __bootstrap.writeFile.writeTextFile,
+ readTextFile: __bootstrap.readFile.readTextFile,
+ readTextFileSync: __bootstrap.readFile.readTextFileSync,
+ readFile: __bootstrap.readFile.readFile,
+ readFileSync: __bootstrap.readFile.readFileSync,
+ watchFs: __bootstrap.fsEvents.watchFs,
+ chmodSync: __bootstrap.fs.chmodSync,
+ chmod: __bootstrap.fs.chmod,
+ chown: __bootstrap.fs.chown,
+ chownSync: __bootstrap.fs.chownSync,
+ copyFileSync: __bootstrap.fs.copyFileSync,
+ cwd: __bootstrap.fs.cwd,
+ makeTempDirSync: __bootstrap.fs.makeTempDirSync,
+ makeTempDir: __bootstrap.fs.makeTempDir,
+ makeTempFileSync: __bootstrap.fs.makeTempFileSync,
+ makeTempFile: __bootstrap.fs.makeTempFile,
+ mkdirSync: __bootstrap.fs.mkdirSync,
+ mkdir: __bootstrap.fs.mkdir,
+ chdir: __bootstrap.fs.chdir,
+ copyFile: __bootstrap.fs.copyFile,
+ readDirSync: __bootstrap.fs.readDirSync,
+ readDir: __bootstrap.fs.readDir,
+ readLinkSync: __bootstrap.fs.readLinkSync,
+ readLink: __bootstrap.fs.readLink,
+ realPathSync: __bootstrap.fs.realPathSync,
+ realPath: __bootstrap.fs.realPath,
+ removeSync: __bootstrap.fs.removeSync,
+ remove: __bootstrap.fs.remove,
+ renameSync: __bootstrap.fs.renameSync,
+ rename: __bootstrap.fs.rename,
+ version: __bootstrap.version.version,
+ build: __bootstrap.build.build,
+ statSync: __bootstrap.fs.statSync,
+ lstatSync: __bootstrap.fs.lstatSync,
+ stat: __bootstrap.fs.stat,
+ lstat: __bootstrap.fs.lstat,
+ truncateSync: __bootstrap.fs.truncateSync,
+ truncate: __bootstrap.fs.truncate,
+ errors: __bootstrap.errors.errors,
+ customInspect: __bootstrap.console.customInspect,
+ inspect: __bootstrap.console.inspect,
+ env: __bootstrap.os.env,
+ exit: __bootstrap.os.exit,
+ execPath: __bootstrap.os.execPath,
+ Buffer: __bootstrap.buffer.Buffer,
+ readAll: __bootstrap.buffer.readAll,
+ readAllSync: __bootstrap.buffer.readAllSync,
+ writeAll: __bootstrap.buffer.writeAll,
+ writeAllSync: __bootstrap.buffer.writeAllSync,
+ copy: __bootstrap.io.copy,
+ iter: __bootstrap.io.iter,
+ iterSync: __bootstrap.io.iterSync,
+ SeekMode: __bootstrap.io.SeekMode,
+ read: __bootstrap.io.read,
+ readSync: __bootstrap.io.readSync,
+ write: __bootstrap.io.write,
+ writeSync: __bootstrap.io.writeSync,
+ File: __bootstrap.files.File,
+ open: __bootstrap.files.open,
+ openSync: __bootstrap.files.openSync,
+ create: __bootstrap.files.create,
+ createSync: __bootstrap.files.createSync,
+ stdin: __bootstrap.files.stdin,
+ stdout: __bootstrap.files.stdout,
+ stderr: __bootstrap.files.stderr,
+ seek: __bootstrap.files.seek,
+ seekSync: __bootstrap.files.seekSync,
+ connect: __bootstrap.net.connect,
+ listen: __bootstrap.net.listen,
+ connectTls: __bootstrap.tls.connectTls,
+ listenTls: __bootstrap.tls.listenTls,
+ sleepSync: __bootstrap.timers.sleepSync,
+ fsyncSync: __bootstrap.fs.fsyncSync,
+ fsync: __bootstrap.fs.fsync,
+ fdatasyncSync: __bootstrap.fs.fdatasyncSync,
+ fdatasync: __bootstrap.fs.fdatasync,
+ };
+
+ __bootstrap.denoNsUnstable = {
+ signal: __bootstrap.signals.signal,
+ signals: __bootstrap.signals.signals,
+ Signal: __bootstrap.signals.Signal,
+ SignalStream: __bootstrap.signals.SignalStream,
+ transpileOnly: __bootstrap.compilerApi.transpileOnly,
+ compile: __bootstrap.compilerApi.compile,
+ bundle: __bootstrap.compilerApi.bundle,
+ permissions: __bootstrap.permissions.permissions,
+ Permissions: __bootstrap.permissions.Permissions,
+ PermissionStatus: __bootstrap.permissions.PermissionStatus,
+ openPlugin: __bootstrap.plugins.openPlugin,
+ kill: __bootstrap.process.kill,
+ setRaw: __bootstrap.tty.setRaw,
+ consoleSize: __bootstrap.tty.consoleSize,
+ DiagnosticCategory: __bootstrap.diagnostics.DiagnosticCategory,
+ loadavg: __bootstrap.os.loadavg,
+ hostname: __bootstrap.os.hostname,
+ osRelease: __bootstrap.os.osRelease,
+ systemMemoryInfo: __bootstrap.os.systemMemoryInfo,
+ systemCpuInfo: __bootstrap.os.systemCpuInfo,
+ applySourceMap: __bootstrap.errorStack.opApplySourceMap,
+ formatDiagnostics: __bootstrap.errorStack.opFormatDiagnostics,
+ shutdown: __bootstrap.net.shutdown,
+ ShutdownMode: __bootstrap.net.ShutdownMode,
+ listen: __bootstrap.netUnstable.listen,
+ connect: __bootstrap.netUnstable.connect,
+ listenDatagram: __bootstrap.netUnstable.listenDatagram,
+ startTls: __bootstrap.tls.startTls,
+ fstatSync: __bootstrap.fs.fstatSync,
+ fstat: __bootstrap.fs.fstat,
+ ftruncateSync: __bootstrap.fs.ftruncateSync,
+ ftruncate: __bootstrap.fs.ftruncate,
+ umask: __bootstrap.fs.umask,
+ link: __bootstrap.fs.link,
+ linkSync: __bootstrap.fs.linkSync,
+ futime: __bootstrap.fs.futime,
+ futimeSync: __bootstrap.fs.futimeSync,
+ utime: __bootstrap.fs.utime,
+ utimeSync: __bootstrap.fs.utimeSync,
+ symlink: __bootstrap.fs.symlink,
+ symlinkSync: __bootstrap.fs.symlinkSync,
+ HttpClient: __bootstrap.fetch.HttpClient,
+ createHttpClient: __bootstrap.fetch.createHttpClient,
+ };
+})(this);
diff --git a/runtime/js/99_main.js b/runtime/js/99_main.js
new file mode 100644
index 000000000..f38d51936
--- /dev/null
+++ b/runtime/js/99_main.js
@@ -0,0 +1,395 @@
+// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
+// Removes the `__proto__` for security reasons. This intentionally makes
+// Deno non compliant with ECMA-262 Annex B.2.2.1
+//
+delete Object.prototype.__proto__;
+
+((window) => {
+ const core = Deno.core;
+ const util = window.__bootstrap.util;
+ const eventTarget = window.__bootstrap.eventTarget;
+ const globalInterfaces = window.__bootstrap.globalInterfaces;
+ const dispatchMinimal = window.__bootstrap.dispatchMinimal;
+ const build = window.__bootstrap.build;
+ const version = window.__bootstrap.version;
+ const errorStack = window.__bootstrap.errorStack;
+ const os = window.__bootstrap.os;
+ const timers = window.__bootstrap.timers;
+ const Console = window.__bootstrap.console.Console;
+ const worker = window.__bootstrap.worker;
+ const signals = window.__bootstrap.signals;
+ const { internalSymbol, internalObject } = window.__bootstrap.internals;
+ const performance = window.__bootstrap.performance;
+ const crypto = window.__bootstrap.crypto;
+ const url = window.__bootstrap.url;
+ const headers = window.__bootstrap.headers;
+ const streams = window.__bootstrap.streams;
+ const fileReader = window.__bootstrap.fileReader;
+ const webSocket = window.__bootstrap.webSocket;
+ const fetch = window.__bootstrap.fetch;
+ const prompt = window.__bootstrap.prompt;
+ const denoNs = window.__bootstrap.denoNs;
+ const denoNsUnstable = window.__bootstrap.denoNsUnstable;
+ const errors = window.__bootstrap.errors.errors;
+ const { defineEventHandler } = window.__bootstrap.webUtil;
+
+ let windowIsClosing = false;
+
+ function windowClose() {
+ if (!windowIsClosing) {
+ windowIsClosing = true;
+ // Push a macrotask to exit after a promise resolve.
+ // This is not perfect, but should be fine for first pass.
+ Promise.resolve().then(() =>
+ timers.setTimeout.call(
+ null,
+ () => {
+ // This should be fine, since only Window/MainWorker has .close()
+ os.exit(0);
+ },
+ 0,
+ )
+ );
+ }
+ }
+
+ const encoder = new TextEncoder();
+
+ function workerClose() {
+ if (isClosing) {
+ return;
+ }
+
+ isClosing = true;
+ opCloseWorker();
+ }
+
+ // TODO(bartlomieju): remove these functions
+ // Stuff for workers
+ const onmessage = () => {};
+ const onerror = () => {};
+
+ function postMessage(data) {
+ const dataJson = JSON.stringify(data);
+ const dataIntArray = encoder.encode(dataJson);
+ opPostMessage(dataIntArray);
+ }
+
+ let isClosing = false;
+ async function workerMessageRecvCallback(data) {
+ const msgEvent = new MessageEvent("message", {
+ cancelable: false,
+ data,
+ });
+
+ try {
+ if (globalThis["onmessage"]) {
+ const result = globalThis.onmessage(msgEvent);
+ if (result && "then" in result) {
+ await result;
+ }
+ }
+ globalThis.dispatchEvent(msgEvent);
+ } catch (e) {
+ let handled = false;
+
+ const errorEvent = new ErrorEvent("error", {
+ cancelable: true,
+ message: e.message,
+ lineno: e.lineNumber ? e.lineNumber + 1 : undefined,
+ colno: e.columnNumber ? e.columnNumber + 1 : undefined,
+ filename: e.fileName,
+ error: null,
+ });
+
+ if (globalThis["onerror"]) {
+ const ret = globalThis.onerror(
+ e.message,
+ e.fileName,
+ e.lineNumber,
+ e.columnNumber,
+ e,
+ );
+ handled = ret === true;
+ }
+
+ globalThis.dispatchEvent(errorEvent);
+ if (errorEvent.defaultPrevented) {
+ handled = true;
+ }
+
+ if (!handled) {
+ throw e;
+ }
+ }
+ }
+
+ function opPostMessage(data) {
+ core.jsonOpSync("op_worker_post_message", {}, data);
+ }
+
+ function opCloseWorker() {
+ core.jsonOpSync("op_worker_close");
+ }
+
+ function opMainModule() {
+ return core.jsonOpSync("op_main_module");
+ }
+
+ function runtimeStart(runtimeOptions, source) {
+ const opsMap = core.ops();
+ for (const [name, opId] of Object.entries(opsMap)) {
+ if (name === "op_write" || name === "op_read") {
+ core.setAsyncHandler(opId, dispatchMinimal.asyncMsgFromRust);
+ }
+ }
+
+ core.setMacrotaskCallback(timers.handleTimerMacrotask);
+ version.setVersions(
+ runtimeOptions.denoVersion,
+ runtimeOptions.v8Version,
+ runtimeOptions.tsVersion,
+ );
+ build.setBuildInfo(runtimeOptions.target);
+ util.setLogDebug(runtimeOptions.debugFlag, source);
+ // TODO(bartlomieju): a very crude way to disable
+ // source mapping of errors. This condition is true
+ // only for compiled standalone binaries.
+ let prepareStackTrace;
+ if (runtimeOptions.applySourceMaps) {
+ prepareStackTrace = core.createPrepareStackTrace(
+ errorStack.opApplySourceMap,
+ );
+ } else {
+ prepareStackTrace = core.createPrepareStackTrace();
+ }
+ Error.prepareStackTrace = prepareStackTrace;
+ }
+
+ function registerErrors() {
+ core.registerErrorClass("NotFound", errors.NotFound);
+ core.registerErrorClass("PermissionDenied", errors.PermissionDenied);
+ core.registerErrorClass("ConnectionRefused", errors.ConnectionRefused);
+ core.registerErrorClass("ConnectionReset", errors.ConnectionReset);
+ core.registerErrorClass("ConnectionAborted", errors.ConnectionAborted);
+ core.registerErrorClass("NotConnected", errors.NotConnected);
+ core.registerErrorClass("AddrInUse", errors.AddrInUse);
+ core.registerErrorClass("AddrNotAvailable", errors.AddrNotAvailable);
+ core.registerErrorClass("BrokenPipe", errors.BrokenPipe);
+ core.registerErrorClass("AlreadyExists", errors.AlreadyExists);
+ core.registerErrorClass("InvalidData", errors.InvalidData);
+ core.registerErrorClass("TimedOut", errors.TimedOut);
+ core.registerErrorClass("Interrupted", errors.Interrupted);
+ core.registerErrorClass("WriteZero", errors.WriteZero);
+ core.registerErrorClass("UnexpectedEof", errors.UnexpectedEof);
+ core.registerErrorClass("BadResource", errors.BadResource);
+ core.registerErrorClass("Http", errors.Http);
+ core.registerErrorClass("Busy", errors.Busy);
+ core.registerErrorClass("NotSupported", errors.NotSupported);
+ core.registerErrorClass("Error", Error);
+ core.registerErrorClass("RangeError", RangeError);
+ core.registerErrorClass("ReferenceError", ReferenceError);
+ core.registerErrorClass("SyntaxError", SyntaxError);
+ core.registerErrorClass("TypeError", TypeError);
+ core.registerErrorClass("URIError", URIError);
+ }
+
+ // https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope
+ const windowOrWorkerGlobalScope = {
+ Blob: util.nonEnumerable(fetch.Blob),
+ ByteLengthQueuingStrategy: util.nonEnumerable(
+ streams.ByteLengthQueuingStrategy,
+ ),
+ CloseEvent: util.nonEnumerable(CloseEvent),
+ CountQueuingStrategy: util.nonEnumerable(
+ streams.CountQueuingStrategy,
+ ),
+ CustomEvent: util.nonEnumerable(CustomEvent),
+ DOMException: util.nonEnumerable(DOMException),
+ ErrorEvent: util.nonEnumerable(ErrorEvent),
+ Event: util.nonEnumerable(Event),
+ EventTarget: util.nonEnumerable(EventTarget),
+ File: util.nonEnumerable(fetch.DomFile),
+ FileReader: util.nonEnumerable(fileReader.FileReader),
+ FormData: util.nonEnumerable(fetch.FormData),
+ Headers: util.nonEnumerable(headers.Headers),
+ MessageEvent: util.nonEnumerable(MessageEvent),
+ Performance: util.nonEnumerable(performance.Performance),
+ PerformanceEntry: util.nonEnumerable(performance.PerformanceEntry),
+ PerformanceMark: util.nonEnumerable(performance.PerformanceMark),
+ PerformanceMeasure: util.nonEnumerable(performance.PerformanceMeasure),
+ ProgressEvent: util.nonEnumerable(ProgressEvent),
+ ReadableStream: util.nonEnumerable(streams.ReadableStream),
+ Request: util.nonEnumerable(fetch.Request),
+ Response: util.nonEnumerable(fetch.Response),
+ TextDecoder: util.nonEnumerable(TextDecoder),
+ TextEncoder: util.nonEnumerable(TextEncoder),
+ TransformStream: util.nonEnumerable(streams.TransformStream),
+ URL: util.nonEnumerable(url.URL),
+ URLSearchParams: util.nonEnumerable(url.URLSearchParams),
+ WebSocket: util.nonEnumerable(webSocket.WebSocket),
+ Worker: util.nonEnumerable(worker.Worker),
+ WritableStream: util.nonEnumerable(streams.WritableStream),
+ atob: util.writable(atob),
+ btoa: util.writable(btoa),
+ clearInterval: util.writable(timers.clearInterval),
+ clearTimeout: util.writable(timers.clearTimeout),
+ console: util.writable(new Console(core.print)),
+ crypto: util.readOnly(crypto),
+ fetch: util.writable(fetch.fetch),
+ performance: util.writable(performance.performance),
+ setInterval: util.writable(timers.setInterval),
+ setTimeout: util.writable(timers.setTimeout),
+ };
+
+ const mainRuntimeGlobalProperties = {
+ Window: globalInterfaces.windowConstructorDescriptor,
+ window: util.readOnly(globalThis),
+ self: util.readOnly(globalThis),
+ // TODO(bartlomieju): from MDN docs (https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope)
+ // it seems those two properties should be available to workers as well
+ onload: util.writable(null),
+ onunload: util.writable(null),
+ close: util.writable(windowClose),
+ closed: util.getterOnly(() => windowIsClosing),
+ alert: util.writable(prompt.alert),
+ confirm: util.writable(prompt.confirm),
+ prompt: util.writable(prompt.prompt),
+ };
+
+ const workerRuntimeGlobalProperties = {
+ WorkerGlobalScope: globalInterfaces.workerGlobalScopeConstructorDescriptor,
+ DedicatedWorkerGlobalScope:
+ globalInterfaces.dedicatedWorkerGlobalScopeConstructorDescriptor,
+ self: util.readOnly(globalThis),
+ onmessage: util.writable(onmessage),
+ onerror: util.writable(onerror),
+ // TODO: should be readonly?
+ close: util.nonEnumerable(workerClose),
+ postMessage: util.writable(postMessage),
+ workerMessageRecvCallback: util.nonEnumerable(workerMessageRecvCallback),
+ };
+
+ let hasBootstrapped = false;
+
+ function bootstrapMainRuntime(runtimeOptions) {
+ if (hasBootstrapped) {
+ throw new Error("Worker runtime already bootstrapped");
+ }
+ // Remove bootstrapping data from the global scope
+ delete globalThis.__bootstrap;
+ delete globalThis.bootstrap;
+ util.log("bootstrapMainRuntime");
+ hasBootstrapped = true;
+ Object.defineProperties(globalThis, windowOrWorkerGlobalScope);
+ Object.defineProperties(globalThis, mainRuntimeGlobalProperties);
+ Object.setPrototypeOf(globalThis, Window.prototype);
+ eventTarget.setEventTargetData(globalThis);
+
+ defineEventHandler(window, "load", null);
+ defineEventHandler(window, "unload", null);
+
+ runtimeStart(runtimeOptions);
+ const { args, noColor, pid, ppid, unstableFlag } = runtimeOptions;
+
+ registerErrors();
+
+ const finalDenoNs = {
+ core,
+ internal: internalSymbol,
+ [internalSymbol]: internalObject,
+ resources: core.resources,
+ close: core.close,
+ ...denoNs,
+ };
+ Object.defineProperties(finalDenoNs, {
+ pid: util.readOnly(pid),
+ ppid: util.readOnly(ppid),
+ noColor: util.readOnly(noColor),
+ args: util.readOnly(Object.freeze(args)),
+ mainModule: util.getterOnly(opMainModule),
+ });
+
+ if (unstableFlag) {
+ Object.assign(finalDenoNs, denoNsUnstable);
+ }
+
+ // Setup `Deno` global - we're actually overriding already
+ // existing global `Deno` with `Deno` namespace from "./deno.ts".
+ util.immutableDefine(globalThis, "Deno", finalDenoNs);
+ Object.freeze(globalThis.Deno);
+ Object.freeze(globalThis.Deno.core);
+ Object.freeze(globalThis.Deno.core.sharedQueue);
+ signals.setSignals();
+
+ util.log("args", args);
+ }
+
+ function bootstrapWorkerRuntime(
+ runtimeOptions,
+ name,
+ useDenoNamespace,
+ internalName,
+ ) {
+ if (hasBootstrapped) {
+ throw new Error("Worker runtime already bootstrapped");
+ }
+ // Remove bootstrapping data from the global scope
+ delete globalThis.__bootstrap;
+ delete globalThis.bootstrap;
+ util.log("bootstrapWorkerRuntime");
+ hasBootstrapped = true;
+ Object.defineProperties(globalThis, windowOrWorkerGlobalScope);
+ Object.defineProperties(globalThis, workerRuntimeGlobalProperties);
+ Object.defineProperties(globalThis, { name: util.readOnly(name) });
+ Object.setPrototypeOf(globalThis, DedicatedWorkerGlobalScope.prototype);
+ eventTarget.setEventTargetData(globalThis);
+
+ runtimeStart(
+ runtimeOptions,
+ internalName ?? name,
+ );
+ const { unstableFlag, pid, noColor, args } = runtimeOptions;
+
+ registerErrors();
+
+ const finalDenoNs = {
+ core,
+ internal: internalSymbol,
+ [internalSymbol]: internalObject,
+ resources: core.resources,
+ close: core.close,
+ ...denoNs,
+ };
+ if (useDenoNamespace) {
+ if (unstableFlag) {
+ Object.assign(finalDenoNs, denoNsUnstable);
+ }
+ Object.defineProperties(finalDenoNs, {
+ pid: util.readOnly(pid),
+ noColor: util.readOnly(noColor),
+ args: util.readOnly(Object.freeze(args)),
+ });
+ // Setup `Deno` global - we're actually overriding already
+ // existing global `Deno` with `Deno` namespace from "./deno.ts".
+ util.immutableDefine(globalThis, "Deno", finalDenoNs);
+ Object.freeze(globalThis.Deno);
+ Object.freeze(globalThis.Deno.core);
+ Object.freeze(globalThis.Deno.core.sharedQueue);
+ signals.setSignals();
+ } else {
+ delete globalThis.Deno;
+ util.assert(globalThis.Deno === undefined);
+ }
+ }
+
+ Object.defineProperties(globalThis, {
+ bootstrap: {
+ value: {
+ mainRuntime: bootstrapMainRuntime,
+ workerRuntime: bootstrapWorkerRuntime,
+ },
+ configurable: true,
+ },
+ });
+})(this);
diff --git a/runtime/js/README.md b/runtime/js/README.md
new file mode 100644
index 000000000..b17fa22e5
--- /dev/null
+++ b/runtime/js/README.md
@@ -0,0 +1,59 @@
+# Runtime JavaScript Code
+
+This directory contains Deno runtime code written in plain JavaScript.
+
+Each file is a plain, old **script**, not ES modules. The reason is that
+snapshotting ES modules is much harder, especially if one needs to manipulate
+global scope (like in case of Deno).
+
+Each file is prefixed with a number, telling in which order scripts should be
+loaded into V8 isolate. This is temporary solution and we're striving not to
+require specific order (though it's not 100% obvious if that's feasible).
+
+## Deno Web APIs
+
+This directory facilities Web APIs that are available in Deno.
+
+Please note, that some implementations might not be completely aligned with
+specification.
+
+Some Web APIs are using ops under the hood, eg. `console`, `performance`.
+
+## Implemented Web APIs
+
+- [Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob): for
+ representing opaque binary data.
+- [Console](https://developer.mozilla.org/en-US/docs/Web/API/Console): for
+ logging purposes.
+- [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent),
+ [EventTarget](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget)
+ and
+ [EventListener](https://developer.mozilla.org/en-US/docs/Web/API/EventListener):
+ to work with DOM events.
+ - **Implementation notes:** There is no DOM hierarchy in Deno, so there is no
+ tree for Events to bubble/capture through.
+- [fetch](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch),
+ [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request),
+ [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response),
+ [Body](https://developer.mozilla.org/en-US/docs/Web/API/Body) and
+ [Headers](https://developer.mozilla.org/en-US/docs/Web/API/Headers): modern
+ Promise-based HTTP Request API.
+- [FormData](https://developer.mozilla.org/en-US/docs/Web/API/FormData): access
+ to a `multipart/form-data` serialization.
+- [Performance](https://developer.mozilla.org/en-US/docs/Web/API/Performance):
+ retrieving current time with a high precision.
+- [setTimeout](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout),
+ [setInterval](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setInterval),
+ [clearTimeout](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/clearTimeout):
+ scheduling callbacks in future and
+ [clearInterval](https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/clearInterval).
+- [Stream](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API) for
+ creating, composing, and consuming streams of data.
+- [URL](https://developer.mozilla.org/en-US/docs/Web/API/URL) and
+ [URLSearchParams](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams):
+ to construct and parse URLSs.
+- [Worker](https://developer.mozilla.org/en-US/docs/Web/API/Worker): executing
+ additional code in a separate thread.
+ - **Implementation notes:** Blob URLs are not supported, object ownership
+ cannot be transferred, posted data is serialized to JSON instead of
+ [structured cloning](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm).