aboutsummaryrefslogtreecommitdiff
path: root/src/js/builtins
diff options
context:
space:
mode:
Diffstat (limited to 'src/js/builtins')
-rw-r--r--src/js/builtins/BunBuiltinNames.h267
-rw-r--r--src/js/builtins/BundlerPlugin.ts370
-rw-r--r--src/js/builtins/ByteLengthQueuingStrategy.ts42
-rw-r--r--src/js/builtins/ConsoleObject.ts84
-rw-r--r--src/js/builtins/CountQueuingStrategy.ts42
-rw-r--r--src/js/builtins/ImportMetaObject.ts257
-rw-r--r--src/js/builtins/JSBufferConstructor.ts67
-rw-r--r--src/js/builtins/JSBufferPrototype.ts495
-rw-r--r--src/js/builtins/ProcessObjectInternals.ts676
-rw-r--r--src/js/builtins/README.md53
-rw-r--r--src/js/builtins/ReadableByteStreamController.ts93
-rw-r--r--src/js/builtins/ReadableByteStreamInternals.ts656
-rw-r--r--src/js/builtins/ReadableStream.ts421
-rw-r--r--src/js/builtins/ReadableStreamBYOBReader.ts80
-rw-r--r--src/js/builtins/ReadableStreamBYOBRequest.ts66
-rw-r--r--src/js/builtins/ReadableStreamDefaultController.ts63
-rw-r--r--src/js/builtins/ReadableStreamDefaultReader.ts185
-rw-r--r--src/js/builtins/ReadableStreamInternals.ts1799
-rw-r--r--src/js/builtins/StreamInternals.ts268
-rw-r--r--src/js/builtins/TransformStream.ts106
-rw-r--r--src/js/builtins/TransformStreamDefaultController.ts60
-rw-r--r--src/js/builtins/TransformStreamInternals.ts348
-rw-r--r--src/js/builtins/WritableStreamDefaultController.ts48
-rw-r--r--src/js/builtins/WritableStreamDefaultWriter.ts104
-rw-r--r--src/js/builtins/WritableStreamInternals.ts790
-rw-r--r--src/js/builtins/builtins.d.ts492
-rw-r--r--src/js/builtins/codegen/builtin-parser.ts89
-rw-r--r--src/js/builtins/codegen/helpers.ts25
-rw-r--r--src/js/builtins/codegen/index.ts627
-rw-r--r--src/js/builtins/codegen/replacements.ts100
-rw-r--r--src/js/builtins/tsconfig.json7
31 files changed, 8780 insertions, 0 deletions
diff --git a/src/js/builtins/BunBuiltinNames.h b/src/js/builtins/BunBuiltinNames.h
new file mode 100644
index 000000000..640d122ca
--- /dev/null
+++ b/src/js/builtins/BunBuiltinNames.h
@@ -0,0 +1,267 @@
+#pragma once
+
+#include "JavaScriptCore/BuiltinUtils.h"
+#include "root.h"
+
+namespace WebCore {
+
+using namespace JSC;
+
+#if !defined(BUN_ADDITIONAL_PRIVATE_IDENTIFIERS)
+#define BUN_ADDITIONAL_PRIVATE_IDENTIFIERS(macro)
+#endif
+
+#define BUN_COMMON_PRIVATE_IDENTIFIERS_EACH_PROPERTY_NAME(macro) \
+ macro(AbortSignal) \
+ macro(Buffer) \
+ macro(Bun) \
+ macro(Loader) \
+ macro(ReadableByteStreamController) \
+ macro(ReadableStream) \
+ macro(ReadableStreamBYOBReader) \
+ macro(ReadableStreamBYOBRequest) \
+ macro(ReadableStreamDefaultController) \
+ macro(ReadableStreamDefaultReader) \
+ macro(TransformStream) \
+ macro(TransformStreamDefaultController) \
+ macro(WritableStream) \
+ macro(WritableStreamDefaultController) \
+ macro(WritableStreamDefaultWriter) \
+ macro(_events) \
+ macro(abortAlgorithm) \
+ macro(abortSteps) \
+ macro(addEventListener) \
+ macro(appendFromJS) \
+ macro(argv) \
+ macro(assignToStream) \
+ macro(associatedReadableByteStreamController) \
+ macro(autoAllocateChunkSize) \
+ macro(backpressure) \
+ macro(backpressureChangePromise) \
+ macro(basename) \
+ macro(body) \
+ macro(bunNativePtr) \
+ macro(bunNativeType) \
+ macro(byobRequest) \
+ macro(cancel) \
+ macro(cancelAlgorithm) \
+ macro(chdir) \
+ macro(cloneArrayBuffer) \
+ macro(close) \
+ macro(closeAlgorithm) \
+ macro(closeRequest) \
+ macro(closeRequested) \
+ macro(closed) \
+ macro(closedPromise) \
+ macro(closedPromiseCapability) \
+ macro(code) \
+ macro(commonJSSymbol) \
+ macro(connect) \
+ macro(consumeReadableStream) \
+ macro(controlledReadableStream) \
+ macro(controller) \
+ macro(cork) \
+ macro(createEmptyReadableStream) \
+ macro(createFIFO) \
+ macro(createNativeReadableStream) \
+ macro(createReadableStream) \
+ macro(createUninitializedArrayBuffer) \
+ macro(createWritableStreamFromInternal) \
+ macro(cwd) \
+ macro(data) \
+ macro(dataView) \
+ macro(decode) \
+ macro(delimiter) \
+ macro(destroy) \
+ macro(dir) \
+ macro(direct) \
+ macro(dirname) \
+ macro(disturbed) \
+ macro(document) \
+ macro(encode) \
+ macro(encoding) \
+ macro(end) \
+ macro(errno) \
+ macro(errorSteps) \
+ macro(execArgv) \
+ macro(exports) \
+ macro(extname) \
+ macro(failureKind) \
+ macro(fatal) \
+ macro(fetch) \
+ macro(fetchRequest) \
+ macro(file) \
+ macro(filePath) \
+ macro(fillFromJS) \
+ macro(filter) \
+ macro(finishConsumingStream) \
+ macro(flush) \
+ macro(flushAlgorithm) \
+ macro(format) \
+ macro(fulfillModuleSync) \
+ macro(get) \
+ macro(getInternalWritableStream) \
+ macro(handleEvent) \
+ macro(hash) \
+ macro(header) \
+ macro(headers) \
+ macro(highWaterMark) \
+ macro(host) \
+ macro(hostname) \
+ macro(href) \
+ macro(ignoreBOM) \
+ macro(importer) \
+ macro(inFlightCloseRequest) \
+ macro(inFlightWriteRequest) \
+ macro(initializeWith) \
+ macro(internalRequire) \
+ macro(internalStream) \
+ macro(internalWritable) \
+ macro(isAbortSignal) \
+ macro(isAbsolute) \
+ macro(isDisturbed) \
+ macro(isPaused) \
+ macro(isWindows) \
+ macro(join) \
+ macro(kind) \
+ macro(lazy) \
+ macro(lazyLoad) \
+ macro(lazyStreamPrototypeMap) \
+ macro(loadCJS2ESM) \
+ macro(loadModule) \
+ macro(localStreams) \
+ macro(main) \
+ macro(makeDOMException) \
+ macro(makeGetterTypeError) \
+ macro(makeThisTypeError) \
+ macro(map) \
+ macro(method) \
+ macro(nextTick) \
+ macro(normalize) \
+ macro(on) \
+ macro(once) \
+ macro(options) \
+ macro(origin) \
+ macro(ownerReadableStream) \
+ macro(parse) \
+ macro(password) \
+ macro(patch) \
+ macro(path) \
+ macro(pathname) \
+ macro(pause) \
+ macro(pendingAbortRequest) \
+ macro(pendingPullIntos) \
+ macro(pid) \
+ macro(pipe) \
+ macro(port) \
+ macro(post) \
+ macro(ppid) \
+ macro(prependEventListener) \
+ macro(process) \
+ macro(protocol) \
+ macro(pull) \
+ macro(pullAgain) \
+ macro(pullAlgorithm) \
+ macro(pulling) \
+ macro(put) \
+ macro(queue) \
+ macro(read) \
+ macro(readIntoRequests) \
+ macro(readRequests) \
+ macro(readable) \
+ macro(readableStreamController) \
+ macro(readableStreamToArray) \
+ macro(reader) \
+ macro(readyPromise) \
+ macro(readyPromiseCapability) \
+ macro(redirect) \
+ macro(relative) \
+ macro(releaseLock) \
+ macro(removeEventListener) \
+ macro(require) \
+ macro(requireESM) \
+ macro(requireMap) \
+ macro(resolve) \
+ macro(resolveSync) \
+ macro(resume) \
+ macro(search) \
+ macro(searchParams) \
+ macro(self) \
+ macro(sep) \
+ macro(setBody) \
+ macro(setStatus) \
+ macro(setup) \
+ macro(sink) \
+ macro(size) \
+ macro(start) \
+ macro(startAlgorithm) \
+ macro(startConsumingStream) \
+ macro(startDirectStream) \
+ macro(started) \
+ macro(startedPromise) \
+ macro(state) \
+ macro(status) \
+ macro(storedError) \
+ macro(strategy) \
+ macro(strategyHWM) \
+ macro(strategySizeAlgorithm) \
+ macro(stream) \
+ macro(streamClosed) \
+ macro(streamClosing) \
+ macro(streamErrored) \
+ macro(streamReadable) \
+ macro(streamWaiting) \
+ macro(streamWritable) \
+ macro(structuredCloneForStream) \
+ macro(syscall) \
+ macro(textDecoderStreamDecoder) \
+ macro(textDecoderStreamTransform) \
+ macro(textEncoderStreamEncoder) \
+ macro(textEncoderStreamTransform) \
+ macro(toNamespacedPath) \
+ macro(trace) \
+ macro(transformAlgorithm) \
+ macro(uncork) \
+ macro(underlyingByteSource) \
+ macro(underlyingSink) \
+ macro(underlyingSource) \
+ macro(unpipe) \
+ macro(unshift) \
+ macro(url) \
+ macro(username) \
+ macro(version) \
+ macro(versions) \
+ macro(view) \
+ macro(whenSignalAborted) \
+ macro(writable) \
+ macro(write) \
+ macro(writeAlgorithm) \
+ macro(writeRequests) \
+ macro(writer) \
+ macro(writing) \
+ macro(written) \
+ BUN_ADDITIONAL_PRIVATE_IDENTIFIERS(macro) \
+
+class BunBuiltinNames {
+public:
+ // FIXME: Remove the __attribute__((nodebug)) when <rdar://68246686> is fixed.
+#if COMPILER(CLANG)
+ __attribute__((nodebug))
+#endif
+ explicit BunBuiltinNames(JSC::VM& vm)
+ : m_vm(vm)
+ BUN_COMMON_PRIVATE_IDENTIFIERS_EACH_PROPERTY_NAME(INITIALIZE_BUILTIN_NAMES)
+ {
+#define EXPORT_NAME(name) m_vm.propertyNames->appendExternalName(name##PublicName(), name##PrivateName());
+ BUN_COMMON_PRIVATE_IDENTIFIERS_EACH_PROPERTY_NAME(EXPORT_NAME)
+#undef EXPORT_NAME
+ }
+
+ BUN_COMMON_PRIVATE_IDENTIFIERS_EACH_PROPERTY_NAME(DECLARE_BUILTIN_IDENTIFIER_ACCESSOR)
+
+private:
+ JSC::VM& m_vm;
+ BUN_COMMON_PRIVATE_IDENTIFIERS_EACH_PROPERTY_NAME(DECLARE_BUILTIN_NAMES)
+};
+
+} // namespace WebCore
diff --git a/src/js/builtins/BundlerPlugin.ts b/src/js/builtins/BundlerPlugin.ts
new file mode 100644
index 000000000..831a6614e
--- /dev/null
+++ b/src/js/builtins/BundlerPlugin.ts
@@ -0,0 +1,370 @@
+import type {
+ AnyFunction,
+ BuildConfig,
+ BunPlugin,
+ OnLoadCallback,
+ OnLoadResult,
+ OnLoadResultObject,
+ OnLoadResultSourceCode,
+ OnResolveCallback,
+ PluginBuilder,
+ PluginConstraints,
+} from "bun";
+
+// This API expects 4 functions:
+// It should be generic enough to reuse for Bun.plugin() eventually, too.
+interface BundlerPlugin {
+ onLoad: Map<string, [RegExp, OnLoadCallback][]>;
+ onResolve: Map<string, [RegExp, OnResolveCallback][]>;
+ onLoadAsync(
+ internalID,
+ sourceCode: string | Uint8Array | ArrayBuffer | DataView | null,
+ loaderKey: number | null,
+ ): void;
+ onResolveAsync(internalID, a, b, c): void;
+ addError(internalID, error, number): void;
+ addFilter(filter, namespace, number): void;
+}
+
+// Extra types
+type Setup = BunPlugin["setup"];
+type MinifyObj = Exclude<BuildConfig["minify"], boolean>;
+interface BuildConfigExt extends BuildConfig {
+ // we support esbuild-style entryPoints
+ entryPoints?: string[];
+ // plugins is guaranteed to not be null
+ plugins: BunPlugin[];
+}
+interface PluginBuilderExt extends PluginBuilder {
+ // these functions aren't implemented yet, so we dont publicly expose them
+ resolve: AnyFunction;
+ onStart: AnyFunction;
+ onEnd: AnyFunction;
+ onDispose: AnyFunction;
+ // we partially support initialOptions. it's read-only and a subset of
+ // all options mapped to their esbuild names
+ initialOptions: any;
+ // we set this to an empty object
+ esbuild: any;
+}
+
+export function runSetupFunction(this: BundlerPlugin, setup: Setup, config: BuildConfigExt) {
+ var onLoadPlugins = new Map<string, [RegExp, AnyFunction][]>();
+ var onResolvePlugins = new Map<string, [RegExp, AnyFunction][]>();
+
+ function validate(filterObject: PluginConstraints, callback, map) {
+ if (!filterObject || !$isObject(filterObject)) {
+ throw new TypeError('Expected an object with "filter" RegExp');
+ }
+
+ if (!callback || !$isCallable(callback)) {
+ throw new TypeError("callback must be a function");
+ }
+
+ var { filter, namespace = "file" } = filterObject;
+
+ if (!filter) {
+ throw new TypeError('Expected an object with "filter" RegExp');
+ }
+
+ if (!$isRegExpObject(filter)) {
+ throw new TypeError("filter must be a RegExp");
+ }
+
+ if (namespace && !(typeof namespace === "string")) {
+ throw new TypeError("namespace must be a string");
+ }
+
+ if ((namespace?.length ?? 0) === 0) {
+ namespace = "file";
+ }
+
+ if (!/^([/$a-zA-Z0-9_\\-]+)$/.test(namespace)) {
+ throw new TypeError("namespace can only contain $a-zA-Z0-9_\\-");
+ }
+
+ var callbacks = map.$get(namespace);
+
+ if (!callbacks) {
+ map.$set(namespace, [[filter, callback]]);
+ } else {
+ $arrayPush(callbacks, [filter, callback]);
+ }
+ }
+
+ function onLoad(filterObject, callback) {
+ validate(filterObject, callback, onLoadPlugins);
+ }
+
+ function onResolve(filterObject, callback) {
+ validate(filterObject, callback, onResolvePlugins);
+ }
+
+ const processSetupResult = () => {
+ var anyOnLoad = false,
+ anyOnResolve = false;
+
+ for (var [namespace, callbacks] of onLoadPlugins.entries()) {
+ for (var [filter] of callbacks) {
+ this.addFilter(filter, namespace, 1);
+ anyOnLoad = true;
+ }
+ }
+
+ for (var [namespace, callbacks] of onResolvePlugins.entries()) {
+ for (var [filter] of callbacks) {
+ this.addFilter(filter, namespace, 0);
+ anyOnResolve = true;
+ }
+ }
+
+ if (anyOnResolve) {
+ var onResolveObject = this.onResolve;
+ if (!onResolveObject) {
+ this.onResolve = onResolvePlugins;
+ } else {
+ for (var [namespace, callbacks] of onResolvePlugins.entries()) {
+ var existing = onResolveObject.$get(namespace) as [RegExp, AnyFunction][];
+
+ if (!existing) {
+ onResolveObject.$set(namespace, callbacks);
+ } else {
+ onResolveObject.$set(namespace, existing.concat(callbacks));
+ }
+ }
+ }
+ }
+
+ if (anyOnLoad) {
+ var onLoadObject = this.onLoad;
+ if (!onLoadObject) {
+ this.onLoad = onLoadPlugins;
+ } else {
+ for (var [namespace, callbacks] of onLoadPlugins.entries()) {
+ var existing = onLoadObject.$get(namespace) as [RegExp, AnyFunction][];
+
+ if (!existing) {
+ onLoadObject.$set(namespace, callbacks);
+ } else {
+ onLoadObject.$set(namespace, existing.concat(callbacks));
+ }
+ }
+ }
+ }
+
+ return anyOnLoad || anyOnResolve;
+ };
+
+ var setupResult = setup({
+ config: config,
+ onDispose: notImplementedIssueFn(2771, "On-dispose callbacks"),
+ onEnd: notImplementedIssueFn(2771, "On-end callbacks"),
+ onLoad,
+ onResolve,
+ onStart: notImplementedIssueFn(2771, "On-start callbacks"),
+ resolve: notImplementedIssueFn(2771, "build.resolve()"),
+ // esbuild's options argument is different, we provide some interop
+ initialOptions: {
+ ...config,
+ bundle: true,
+ entryPoints: config.entrypoints ?? config.entryPoints ?? [],
+ minify: typeof config.minify === "boolean" ? config.minify : false,
+ minifyIdentifiers: config.minify === true || (config.minify as MinifyObj)?.identifiers,
+ minifyWhitespace: config.minify === true || (config.minify as MinifyObj)?.whitespace,
+ minifySyntax: config.minify === true || (config.minify as MinifyObj)?.syntax,
+ outbase: config.root,
+ platform: config.target === "bun" ? "node" : config.target,
+ },
+ esbuild: {},
+ } satisfies PluginBuilderExt as PluginBuilder);
+
+ if (setupResult && $isPromise(setupResult)) {
+ if ($getPromiseInternalField(setupResult, $promiseFieldFlags) & $promiseStateFulfilled) {
+ setupResult = $getPromiseInternalField(setupResult, $promiseFieldReactionsOrResult);
+ } else {
+ return setupResult.$then(processSetupResult);
+ }
+ }
+
+ return processSetupResult();
+}
+
+export function runOnResolvePlugins(this: BundlerPlugin, specifier, inputNamespace, importer, internalID, kindId) {
+ // Must be kept in sync with ImportRecord.label
+ const kind = $ImportKindIdToLabel[kindId];
+
+ var promiseResult: any = (async (inputPath, inputNamespace, importer, kind) => {
+ var { onResolve, onLoad } = this;
+ var results = onResolve.$get(inputNamespace);
+ if (!results) {
+ this.onResolveAsync(internalID, null, null, null);
+ return null;
+ }
+
+ for (let [filter, callback] of results) {
+ if (filter.test(inputPath)) {
+ var result = callback({
+ path: inputPath,
+ importer,
+ namespace: inputNamespace,
+ // resolveDir
+ kind,
+ // pluginData
+ });
+
+ while (
+ result &&
+ $isPromise(result) &&
+ ($getPromiseInternalField(result, $promiseFieldFlags) & $promiseStateMask) === $promiseStateFulfilled
+ ) {
+ result = $getPromiseInternalField(result, $promiseFieldReactionsOrResult);
+ }
+
+ if (result && $isPromise(result)) {
+ result = await result;
+ }
+
+ if (!result || !$isObject(result)) {
+ continue;
+ }
+
+ var { path, namespace: userNamespace = inputNamespace, external } = result;
+ if (!(typeof path === "string") || !(typeof userNamespace === "string")) {
+ throw new TypeError("onResolve plugins must return an object with a string 'path' and string 'loader' field");
+ }
+
+ if (!path) {
+ continue;
+ }
+
+ if (!userNamespace) {
+ userNamespace = inputNamespace;
+ }
+ if (typeof external !== "boolean" && !$isUndefinedOrNull(external)) {
+ throw new TypeError('onResolve plugins "external" field must be boolean or unspecified');
+ }
+
+ if (!external) {
+ if (userNamespace === "file") {
+ if (process.platform !== "win32") {
+ if (path[0] !== "/" || path.includes("..")) {
+ throw new TypeError('onResolve plugin "path" must be absolute when the namespace is "file"');
+ }
+ } else {
+ // TODO: Windows
+ }
+ }
+ if (userNamespace === "dataurl") {
+ if (!path.startsWith("data:")) {
+ throw new TypeError('onResolve plugin "path" must start with "data:" when the namespace is "dataurl"');
+ }
+ }
+
+ if (userNamespace && userNamespace !== "file" && (!onLoad || !onLoad.$has(userNamespace))) {
+ throw new TypeError(`Expected onLoad plugin for namespace ${userNamespace} to exist`);
+ }
+ }
+ this.onResolveAsync(internalID, path, userNamespace, external);
+ return null;
+ }
+ }
+
+ this.onResolveAsync(internalID, null, null, null);
+ return null;
+ })(specifier, inputNamespace, importer, kind);
+
+ while (
+ promiseResult &&
+ $isPromise(promiseResult) &&
+ ($getPromiseInternalField(promiseResult, $promiseFieldFlags) & $promiseStateMask) === $promiseStateFulfilled
+ ) {
+ promiseResult = $getPromiseInternalField(promiseResult, $promiseFieldReactionsOrResult);
+ }
+
+ if (promiseResult && $isPromise(promiseResult)) {
+ promiseResult.then(
+ () => {},
+ e => {
+ this.addError(internalID, e, 0);
+ },
+ );
+ }
+}
+
+export function runOnLoadPlugins(this: BundlerPlugin, internalID, path, namespace, defaultLoaderId) {
+ const LOADERS_MAP = $LoaderLabelToId;
+ const loaderName = $LoaderIdToLabel[defaultLoaderId];
+
+ var promiseResult = (async (internalID, path, namespace, defaultLoader) => {
+ var results = this.onLoad.$get(namespace);
+ if (!results) {
+ this.onLoadAsync(internalID, null, null);
+ return null;
+ }
+
+ for (let [filter, callback] of results) {
+ if (filter.test(path)) {
+ var result = callback({
+ path,
+ namespace,
+ // suffix
+ // pluginData
+ loader: defaultLoader,
+ });
+
+ while (
+ result &&
+ $isPromise(result) &&
+ ($getPromiseInternalField(result, $promiseFieldFlags) & $promiseStateMask) === $promiseStateFulfilled
+ ) {
+ result = $getPromiseInternalField(result, $promiseFieldReactionsOrResult);
+ }
+
+ if (result && $isPromise(result)) {
+ result = await result;
+ }
+
+ if (!result || !$isObject(result)) {
+ continue;
+ }
+
+ var { contents, loader = defaultLoader } = result as OnLoadResultSourceCode & OnLoadResultObject;
+ if (!(typeof contents === "string") && !$isTypedArrayView(contents)) {
+ throw new TypeError('onLoad plugins must return an object with "contents" as a string or Uint8Array');
+ }
+
+ if (!(typeof loader === "string")) {
+ throw new TypeError('onLoad plugins must return an object with "loader" as a string');
+ }
+
+ const chosenLoader = LOADERS_MAP[loader];
+ if (chosenLoader === undefined) {
+ throw new TypeError(`Loader ${loader} is not supported.`);
+ }
+
+ this.onLoadAsync(internalID, contents, chosenLoader);
+ return null;
+ }
+ }
+
+ this.onLoadAsync(internalID, null, null);
+ return null;
+ })(internalID, path, namespace, loaderName);
+
+ while (
+ promiseResult &&
+ $isPromise(promiseResult) &&
+ ($getPromiseInternalField(promiseResult, $promiseFieldFlags) & $promiseStateMask) === $promiseStateFulfilled
+ ) {
+ promiseResult = $getPromiseInternalField(promiseResult, $promiseFieldReactionsOrResult);
+ }
+
+ if (promiseResult && $isPromise(promiseResult)) {
+ promiseResult.then(
+ () => {},
+ e => {
+ this.addError(internalID, e, 1);
+ },
+ );
+ }
+}
diff --git a/src/js/builtins/ByteLengthQueuingStrategy.ts b/src/js/builtins/ByteLengthQueuingStrategy.ts
new file mode 100644
index 000000000..fc3f3d998
--- /dev/null
+++ b/src/js/builtins/ByteLengthQueuingStrategy.ts
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2015 Canon Inc.
+ * Copyright (C) 2015 Igalia S.L.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+$getter;
+export function highWaterMark(this: any) {
+ const highWaterMark = $getByIdDirectPrivate(this, "highWaterMark");
+ if (highWaterMark === undefined)
+ throw new TypeError("ByteLengthQueuingStrategy.highWaterMark getter called on incompatible |this| value.");
+
+ return highWaterMark;
+}
+
+export function size(chunk) {
+ return chunk.byteLength;
+}
+
+export function initializeByteLengthQueuingStrategy(this: any, parameters: any) {
+ $putByIdDirectPrivate(this, "highWaterMark", $extractHighWaterMarkFromQueuingStrategyInit(parameters));
+}
diff --git a/src/js/builtins/ConsoleObject.ts b/src/js/builtins/ConsoleObject.ts
new file mode 100644
index 000000000..45746459a
--- /dev/null
+++ b/src/js/builtins/ConsoleObject.ts
@@ -0,0 +1,84 @@
+$overriddenName = "[Symbol.asyncIterator]";
+export function asyncIterator(this: Console) {
+ const Iterator = async function* ConsoleAsyncIterator() {
+ const stream = Bun.stdin.stream();
+ var reader = stream.getReader();
+
+ // TODO: use builtin
+ var decoder = new (globalThis as any).TextDecoder("utf-8", { fatal: false }) as TextDecoder;
+ var deferredError;
+ var indexOf = Bun.indexOfLine;
+
+ try {
+ while (true) {
+ var done, value;
+ var pendingChunk;
+ const firstResult = reader.readMany();
+ if ($isPromise(firstResult)) {
+ ({ done, value } = await firstResult);
+ } else {
+ ({ done, value } = firstResult);
+ }
+
+ if (done) {
+ if (pendingChunk) {
+ yield decoder.decode(pendingChunk);
+ }
+ return;
+ }
+
+ var actualChunk;
+ // we assume it was given line-by-line
+ for (const chunk of value) {
+ actualChunk = chunk;
+ if (pendingChunk) {
+ actualChunk = Buffer.concat([pendingChunk, chunk]);
+ pendingChunk = null;
+ }
+
+ var last = 0;
+ // TODO: "\r", 0x4048, 0x4049, 0x404A, 0x404B, 0x404C, 0x404D, 0x404E, 0x404F
+ var i = indexOf(actualChunk, last);
+ while (i !== -1) {
+ yield decoder.decode(actualChunk.subarray(last, i));
+ last = i + 1;
+ i = indexOf(actualChunk, last);
+ }
+
+ pendingChunk = actualChunk.subarray(last);
+ }
+ }
+ } catch (e) {
+ deferredError = e;
+ } finally {
+ reader.releaseLock();
+
+ if (deferredError) {
+ throw deferredError;
+ }
+ }
+ };
+
+ const symbol = globalThis.Symbol.asyncIterator;
+ this[symbol] = Iterator;
+ return Iterator();
+}
+
+export function write(this: Console, input) {
+ var writer = $getByIdDirectPrivate(this, "writer");
+ if (!writer) {
+ var length = $toLength(input?.length ?? 0);
+ writer = Bun.stdout.writer({ highWaterMark: length > 65536 ? length : 65536 });
+ $putByIdDirectPrivate(this, "writer", writer);
+ }
+
+ var wrote = writer.write(input);
+
+ const count = $argumentCount();
+ for (var i = 1; i < count; i++) {
+ wrote += writer.write($argument(i));
+ }
+
+ writer.flush(true);
+ return wrote;
+}
diff --git a/src/js/builtins/CountQueuingStrategy.ts b/src/js/builtins/CountQueuingStrategy.ts
new file mode 100644
index 000000000..a72dca1ca
--- /dev/null
+++ b/src/js/builtins/CountQueuingStrategy.ts
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2015 Canon Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+$getter;
+export function highWaterMark(this: any) {
+ const highWaterMark = $getByIdDirectPrivate(this, "highWaterMark");
+
+ if (highWaterMark === undefined)
+ throw new TypeError("CountQueuingStrategy.highWaterMark getter called on incompatible |this| value.");
+
+ return highWaterMark;
+}
+
+export function size() {
+ return 1;
+}
+
+export function initializeCountQueuingStrategy(this: any, parameters: any) {
+ $putByIdDirectPrivate(this, "highWaterMark", $extractHighWaterMarkFromQueuingStrategyInit(parameters));
+}
diff --git a/src/js/builtins/ImportMetaObject.ts b/src/js/builtins/ImportMetaObject.ts
new file mode 100644
index 000000000..2df0f0c98
--- /dev/null
+++ b/src/js/builtins/ImportMetaObject.ts
@@ -0,0 +1,257 @@
+type ImportMetaObject = Partial<ImportMeta>;
+
+export function loadCJS2ESM(this: ImportMetaObject, resolvedSpecifier: string) {
+ var loader = Loader;
+ var queue = $createFIFO();
+ var key = resolvedSpecifier;
+ while (key) {
+ // we need to explicitly check because state could be $ModuleFetch
+ // it will throw this error if we do not:
+ // $throwTypeError("Requested module is already fetched.");
+ var entry = loader.registry.$get(key);
+
+ if (!entry || !entry.state || entry.state <= $ModuleFetch) {
+ $fulfillModuleSync(key);
+ entry = loader.registry.$get(key)!;
+ }
+
+ // entry.fetch is a Promise<SourceCode>
+ // SourceCode is not a string, it's a JSC::SourceCode object
+ // this pulls it out of the promise without delaying by a tick
+ // the promise is already fullfilled by $fullfillModuleSync
+ var sourceCodeObject = $getPromiseInternalField(entry.fetch, $promiseFieldReactionsOrResult);
+ // parseModule() returns a Promise, but the value is already fulfilled
+ // so we just pull it out of the promise here once again
+ // But, this time we do it a little more carefully because this is a JSC function call and not bun source code
+ var moduleRecordPromise = loader.parseModule(key, sourceCodeObject);
+ var module = entry.module;
+ if (!module && moduleRecordPromise && $isPromise(moduleRecordPromise)) {
+ var reactionsOrResult = $getPromiseInternalField(moduleRecordPromise, $promiseFieldReactionsOrResult);
+ var flags = $getPromiseInternalField(moduleRecordPromise, $promiseFieldFlags);
+ var state = flags & $promiseStateMask;
+ // this branch should never happen, but just to be safe
+ if (state === $promiseStatePending || (reactionsOrResult && $isPromise(reactionsOrResult))) {
+ throw new TypeError(`require() async module "${key}" is unsupported`);
+ } else if (state === $promiseStateRejected) {
+ // TODO: use SyntaxError but preserve the specifier
+ throw new TypeError(`${reactionsOrResult?.message ?? "An error occurred"} while parsing module \"${key}\"`);
+ }
+ entry.module = module = reactionsOrResult;
+ } else if (moduleRecordPromise && !module) {
+ entry.module = module = moduleRecordPromise as LoaderModule;
+ }
+
+ // This is very similar to "requestInstantiate" in ModuleLoader.js in JavaScriptCore.
+ $setStateToMax(entry, $ModuleLink);
+ var dependenciesMap = module.dependenciesMap;
+ var requestedModules = loader.requestedModules(module);
+ var dependencies = $newArrayWithSize<string>(requestedModules.length);
+ for (var i = 0, length = requestedModules.length; i < length; ++i) {
+ var depName = requestedModules[i];
+ // optimization: if it starts with a slash then it's an absolute path
+ // we don't need to run the resolver a 2nd time
+ var depKey = depName[0] === "/" ? depName : loader.resolve(depName, key);
+ var depEntry = loader.ensureRegistered(depKey);
+ if (depEntry.state < $ModuleLink) {
+ queue.push(depKey);
+ }
+
+ $putByValDirect(dependencies, i, depEntry);
+ dependenciesMap.$set(depName, depEntry);
+ }
+
+ entry.dependencies = dependencies;
+ // All dependencies resolved, set instantiate and satisfy field directly.
+ entry.instantiate = Promise.resolve(entry);
+ entry.satisfy = Promise.resolve(entry);
+ key = queue.shift();
+ while (key && (loader.registry.$get(key)?.state ?? $ModuleFetch) >= $ModuleLink) {
+ key = queue.shift();
+ }
+ }
+
+ var linkAndEvaluateResult = loader.linkAndEvaluateModule(resolvedSpecifier, undefined);
+ if (linkAndEvaluateResult && $isPromise(linkAndEvaluateResult)) {
+ // if you use top-level await, or any dependencies use top-level await, then we throw here
+ // this means the module will still actually load eventually, but that's okay.
+ throw new TypeError(`require() async module \"${resolvedSpecifier}\" is unsupported`);
+ }
+
+ return loader.registry.$get(resolvedSpecifier);
+}
+
+export function requireESM(this: ImportMetaObject, resolved) {
+ var entry = Loader.registry.$get(resolved);
+
+ if (!entry || !entry.evaluated) {
+ entry = $loadCJS2ESM(resolved);
+ }
+
+ if (!entry || !entry.evaluated || !entry.module) {
+ throw new TypeError(`require() failed to evaluate module "${resolved}". This is an internal consistentency error.`);
+ }
+ var exports = Loader.getModuleNamespaceObject(entry.module);
+ if (exports[$commonJSSymbol] === 0) {
+ // CommonJS module created via `Bun::CommonJSModuleRecord`
+ // We will refer to the requireMap to get the exports
+ return;
+ }
+
+ var commonJS = exports.default;
+ var cjs = commonJS?.[$commonJSSymbol];
+ if (cjs === 0) {
+ return commonJS;
+ } else if (cjs && $isCallable(commonJS)) {
+ return commonJS();
+ }
+
+ return exports;
+}
+
+export function internalRequire(this: ImportMetaObject, resolved) {
+ var cached = $requireMap.$get(resolved);
+ const last5 = resolved.substring(resolved.length - 5);
+ if (cached) {
+ if (last5 === ".node") {
+ return cached.exports;
+ }
+ return cached;
+ }
+
+ // TODO: remove this hardcoding
+ if (last5 === ".json") {
+ var fs = (globalThis[Symbol.for("_fs")] ||= Bun.fs());
+ var exports = JSON.parse(fs.readFileSync(resolved, "utf8"));
+ $requireMap.$set(resolved, exports);
+ return exports;
+ } else if (last5 === ".node") {
+ var module = { exports: {} };
+ process.dlopen(module, resolved);
+ $requireMap.$set(resolved, module);
+ return module.exports;
+ } else if (last5 === ".toml") {
+ var fs = (globalThis[Symbol.for("_fs")] ||= Bun.fs());
+ var exports = Bun.TOML.parse(fs.readFileSync(resolved, "utf8"));
+ $requireMap.$set(resolved, exports);
+ return exports;
+ } else {
+ var exports = $requireESM(resolved);
+ const cachedExports = $requireMap.$get(resolved);
+ if (cachedExports) {
+ return cachedExports;
+ }
+
+ $requireMap.$set(resolved, exports);
+ return exports;
+ }
+}
+
+export function createRequireCache() {
+ class Module {
+ id;
+ parent;
+ filename;
+ children = [];
+ paths = [];
+
+ constructor(filename) {
+ this.id = filename;
+ // TODO: windows
+ const lastSlash = filename.lastIndexOf("/");
+ if (lastSlash !== -1 && filename.length > lastSlash + 1) {
+ this.filename = filename.substring(lastSlash + 1);
+ } else {
+ this.filename = filename;
+ }
+ }
+
+ get loaded() {
+ return true;
+ }
+
+ require(path) {
+ return $internalRequire($resolveSync(path, this.id));
+ }
+
+ get exports() {
+ return $requireMap.$get(this.id) ?? {};
+ }
+
+ set exports(value) {
+ $requireMap.$set(this.id, value);
+ }
+ }
+
+ var moduleMap = new Map();
+
+ return new Proxy(
+ {},
+ {
+ get(target, key: string) {
+ const entry = $requireMap.$get(key);
+ if (entry) {
+ var mod = moduleMap.$get(key);
+ if (!mod) {
+ mod = new Module(key);
+ moduleMap.$set(key, mod);
+ }
+ return mod;
+ }
+ },
+ set(target, key: string, value) {
+ if (!moduleMap.$has(key)) {
+ moduleMap.$set(key, new Module(key));
+ }
+
+ $requireMap.$set(key, value?.exports);
+
+ return true;
+ },
+
+ has(target, key: string) {
+ return $requireMap.$has(key);
+ },
+
+ deleteProperty(target, key: string) {
+ moduleMap.$delete(key);
+ $requireMap.$delete(key);
+ Loader.registry.$delete(key);
+ return true;
+ },
+
+ ownKeys(target) {
+ return [...$requireMap.$keys()];
+ },
+
+ // In Node, require.cache has a null prototype
+ getPrototypeOf(target) {
+ return null;
+ },
+
+ getOwnPropertyDescriptor(target, key: string) {
+ if ($requireMap.$has(key)) {
+ return {
+ configurable: true,
+ enumerable: true,
+ };
+ }
+ },
+ },
+ );
+}
+
+$sloppy;
+export function require(this: ImportMetaObject, name) {
+ var from = this?.path ?? arguments.callee.path;
+
+ if (typeof name !== "string") {
+ throw new TypeError("require(name) must be a string");
+ }
+
+ return $internalRequire($resolveSync(name, from));
+}
+
+$getter;
+export function main(this: ImportMetaObject) {
+ return this.path === Bun.main;
+}
diff --git a/src/js/builtins/JSBufferConstructor.ts b/src/js/builtins/JSBufferConstructor.ts
new file mode 100644
index 000000000..debc62d51
--- /dev/null
+++ b/src/js/builtins/JSBufferConstructor.ts
@@ -0,0 +1,67 @@
+export function from(items) {
+ if ($isUndefinedOrNull(items)) {
+ throw new TypeError(
+ "The first argument must be one of type string, Buffer, ArrayBuffer, Array, or Array-like Object.",
+ );
+ }
+
+ // TODO: figure out why private symbol not found
+ if (
+ typeof items === "string" ||
+ (typeof items === "object" &&
+ ($isTypedArrayView(items) ||
+ items instanceof ArrayBuffer ||
+ items instanceof SharedArrayBuffer ||
+ items instanceof String))
+ ) {
+ switch ($argumentCount()) {
+ case 1: {
+ return new $Buffer(items);
+ }
+ case 2: {
+ return new $Buffer(items, $argument(1));
+ }
+ default: {
+ return new $Buffer(items, $argument(1), $argument(2));
+ }
+ }
+ }
+
+ var arrayLike = $toObject(
+ items,
+ "The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object.",
+ ) as ArrayLike<any>;
+
+ if (!$isJSArray(arrayLike)) {
+ const toPrimitive = $tryGetByIdWithWellKnownSymbol(items, "toPrimitive");
+
+ if (toPrimitive) {
+ const primitive = toPrimitive.$call(items, "string");
+
+ if (typeof primitive === "string") {
+ switch ($argumentCount()) {
+ case 1: {
+ return new $Buffer(primitive);
+ }
+ case 2: {
+ return new $Buffer(primitive, $argument(1));
+ }
+ default: {
+ return new $Buffer(primitive, $argument(1), $argument(2));
+ }
+ }
+ }
+ }
+
+ if (!("length" in arrayLike) || $isCallable(arrayLike)) {
+ throw new TypeError(
+ "The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object.",
+ );
+ }
+ }
+
+ // Don't pass the second argument because Node's Buffer.from doesn't accept
+ // a function and Uint8Array.from requires it if it exists
+ // That means we cannot use $tailCallFowrardArguments here, sadly
+ return new $Buffer(Uint8Array.from(arrayLike).buffer);
+}
diff --git a/src/js/builtins/JSBufferPrototype.ts b/src/js/builtins/JSBufferPrototype.ts
new file mode 100644
index 000000000..97b25b9b2
--- /dev/null
+++ b/src/js/builtins/JSBufferPrototype.ts
@@ -0,0 +1,495 @@
+// The fastest way as of April 2022 is to use DataView.
+// DataView has intrinsics that cause inlining
+
+interface BufferExt extends Buffer {
+ $dataView?: DataView;
+
+ toString(encoding?: BufferEncoding, start?: number, end?: number): string;
+ toString(offset: number, length: number, encoding?: BufferEncoding): string;
+}
+
+export function setBigUint64(this: BufferExt, offset, value, le) {
+ return (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setBigUint64(
+ offset,
+ value,
+ le,
+ );
+}
+export function readInt8(this: BufferExt, offset) {
+ return (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getInt8(offset);
+}
+export function readUInt8(this: BufferExt, offset) {
+ return (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getUint8(offset);
+}
+export function readInt16LE(this: BufferExt, offset) {
+ return (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getInt16(offset, true);
+}
+export function readInt16BE(this: BufferExt, offset) {
+ return (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getInt16(offset, false);
+}
+export function readUInt16LE(this: BufferExt, offset) {
+ return (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getUint16(offset, true);
+}
+export function readUInt16BE(this: BufferExt, offset) {
+ return (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getUint16(offset, false);
+}
+export function readInt32LE(this: BufferExt, offset) {
+ return (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getInt32(offset, true);
+}
+export function readInt32BE(this: BufferExt, offset) {
+ return (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getInt32(offset, false);
+}
+export function readUInt32LE(this: BufferExt, offset) {
+ return (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getUint32(offset, true);
+}
+export function readUInt32BE(this: BufferExt, offset) {
+ return (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getUint32(offset, false);
+}
+
+export function readIntLE(this: BufferExt, offset, byteLength) {
+ const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength));
+ switch (byteLength) {
+ case 1: {
+ return view.getInt8(offset);
+ }
+ case 2: {
+ return view.getInt16(offset, true);
+ }
+ case 3: {
+ const val = view.getUint16(offset, true) + view.getUint8(offset + 2) * 2 ** 16;
+ return val | ((val & (2 ** 23)) * 0x1fe);
+ }
+ case 4: {
+ return view.getInt32(offset, true);
+ }
+ case 5: {
+ const last = view.getUint8(offset + 4);
+ return (last | ((last & (2 ** 7)) * 0x1fffffe)) * 2 ** 32 + view.getUint32(offset, true);
+ }
+ case 6: {
+ const last = view.getUint16(offset + 4, true);
+ return (last | ((last & (2 ** 15)) * 0x1fffe)) * 2 ** 32 + view.getUint32(offset, true);
+ }
+ }
+ throw new RangeError("byteLength must be >= 1 and <= 6");
+}
+export function readIntBE(this: BufferExt, offset, byteLength) {
+ const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength));
+ switch (byteLength) {
+ case 1: {
+ return view.getInt8(offset);
+ }
+ case 2: {
+ return view.getInt16(offset, false);
+ }
+ case 3: {
+ const val = view.getUint16(offset + 1, false) + view.getUint8(offset) * 2 ** 16;
+ return val | ((val & (2 ** 23)) * 0x1fe);
+ }
+ case 4: {
+ return view.getInt32(offset, false);
+ }
+ case 5: {
+ const last = view.getUint8(offset);
+ return (last | ((last & (2 ** 7)) * 0x1fffffe)) * 2 ** 32 + view.getUint32(offset + 1, false);
+ }
+ case 6: {
+ const last = view.getUint16(offset, false);
+ return (last | ((last & (2 ** 15)) * 0x1fffe)) * 2 ** 32 + view.getUint32(offset + 2, false);
+ }
+ }
+ throw new RangeError("byteLength must be >= 1 and <= 6");
+}
+export function readUIntLE(this: BufferExt, offset, byteLength) {
+ const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength));
+ switch (byteLength) {
+ case 1: {
+ return view.getUint8(offset);
+ }
+ case 2: {
+ return view.getUint16(offset, true);
+ }
+ case 3: {
+ return view.getUint16(offset, true) + view.getUint8(offset + 2) * 2 ** 16;
+ }
+ case 4: {
+ return view.getUint32(offset, true);
+ }
+ case 5: {
+ return view.getUint8(offset + 4) * 2 ** 32 + view.getUint32(offset, true);
+ }
+ case 6: {
+ return view.getUint16(offset + 4, true) * 2 ** 32 + view.getUint32(offset, true);
+ }
+ }
+ throw new RangeError("byteLength must be >= 1 and <= 6");
+}
+export function readUIntBE(this: BufferExt, offset, byteLength) {
+ const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength));
+ switch (byteLength) {
+ case 1: {
+ return view.getUint8(offset);
+ }
+ case 2: {
+ return view.getUint16(offset, false);
+ }
+ case 3: {
+ return view.getUint16(offset + 1, false) + view.getUint8(offset) * 2 ** 16;
+ }
+ case 4: {
+ return view.getUint32(offset, false);
+ }
+ case 5: {
+ const last = view.getUint8(offset);
+ return (last | ((last & (2 ** 7)) * 0x1fffffe)) * 2 ** 32 + view.getUint32(offset + 1, false);
+ }
+ case 6: {
+ const last = view.getUint16(offset, false);
+ return (last | ((last & (2 ** 15)) * 0x1fffe)) * 2 ** 32 + view.getUint32(offset + 2, false);
+ }
+ }
+ throw new RangeError("byteLength must be >= 1 and <= 6");
+}
+
+export function readFloatLE(this: BufferExt, offset) {
+ return (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getFloat32(offset, true);
+}
+export function readFloatBE(this: BufferExt, offset) {
+ return (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getFloat32(offset, false);
+}
+export function readDoubleLE(this: BufferExt, offset) {
+ return (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getFloat64(offset, true);
+}
+export function readDoubleBE(this: BufferExt, offset) {
+ return (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getFloat64(offset, false);
+}
+export function readBigInt64LE(this: BufferExt, offset) {
+ return (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getBigInt64(offset, true);
+}
+export function readBigInt64BE(this: BufferExt, offset) {
+ return (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getBigInt64(offset, false);
+}
+export function readBigUInt64LE(this: BufferExt, offset) {
+ return (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getBigUint64(offset, true);
+}
+export function readBigUInt64BE(this: BufferExt, offset) {
+ return (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).getBigUint64(offset, false);
+}
+
+export function writeInt8(this: BufferExt, value, offset) {
+ (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setInt8(offset, value);
+ return offset + 1;
+}
+export function writeUInt8(this: BufferExt, value, offset) {
+ (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setUint8(offset, value);
+ return offset + 1;
+}
+export function writeInt16LE(this: BufferExt, value, offset) {
+ (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setInt16(offset, value, true);
+ return offset + 2;
+}
+export function writeInt16BE(this: BufferExt, value, offset) {
+ (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setInt16(offset, value, false);
+ return offset + 2;
+}
+export function writeUInt16LE(this: BufferExt, value, offset) {
+ (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setUint16(offset, value, true);
+ return offset + 2;
+}
+export function writeUInt16BE(this: BufferExt, value, offset) {
+ (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setUint16(offset, value, false);
+ return offset + 2;
+}
+export function writeInt32LE(this: BufferExt, value, offset) {
+ (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setInt32(offset, value, true);
+ return offset + 4;
+}
+export function writeInt32BE(this: BufferExt, value, offset) {
+ (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setInt32(offset, value, false);
+ return offset + 4;
+}
+export function writeUInt32LE(this: BufferExt, value, offset) {
+ (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setUint32(offset, value, true);
+ return offset + 4;
+}
+export function writeUInt32BE(this: BufferExt, value, offset) {
+ (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setUint32(offset, value, false);
+ return offset + 4;
+}
+
+export function writeIntLE(this: BufferExt, value, offset, byteLength) {
+ const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength));
+ switch (byteLength) {
+ case 1: {
+ view.setInt8(offset, value);
+ break;
+ }
+ case 2: {
+ view.setInt16(offset, value, true);
+ break;
+ }
+ case 3: {
+ view.setUint16(offset, value & 0xffff, true);
+ view.setInt8(offset + 2, Math.floor(value * 2 ** -16));
+ break;
+ }
+ case 4: {
+ view.setInt32(offset, value, true);
+ break;
+ }
+ case 5: {
+ view.setUint32(offset, value | 0, true);
+ view.setInt8(offset + 4, Math.floor(value * 2 ** -32));
+ break;
+ }
+ case 6: {
+ view.setUint32(offset, value | 0, true);
+ view.setInt16(offset + 4, Math.floor(value * 2 ** -32), true);
+ break;
+ }
+ default: {
+ throw new RangeError("byteLength must be >= 1 and <= 6");
+ }
+ }
+ return offset + byteLength;
+}
+export function writeIntBE(this: BufferExt, value, offset, byteLength) {
+ const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength));
+ switch (byteLength) {
+ case 1: {
+ view.setInt8(offset, value);
+ break;
+ }
+ case 2: {
+ view.setInt16(offset, value, false);
+ break;
+ }
+ case 3: {
+ view.setUint16(offset + 1, value & 0xffff, false);
+ view.setInt8(offset, Math.floor(value * 2 ** -16));
+ break;
+ }
+ case 4: {
+ view.setInt32(offset, value, false);
+ break;
+ }
+ case 5: {
+ view.setUint32(offset + 1, value | 0, false);
+ view.setInt8(offset, Math.floor(value * 2 ** -32));
+ break;
+ }
+ case 6: {
+ view.setUint32(offset + 2, value | 0, false);
+ view.setInt16(offset, Math.floor(value * 2 ** -32), false);
+ break;
+ }
+ default: {
+ throw new RangeError("byteLength must be >= 1 and <= 6");
+ }
+ }
+ return offset + byteLength;
+}
+export function writeUIntLE(this: BufferExt, value, offset, byteLength) {
+ const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength));
+ switch (byteLength) {
+ case 1: {
+ view.setUint8(offset, value);
+ break;
+ }
+ case 2: {
+ view.setUint16(offset, value, true);
+ break;
+ }
+ case 3: {
+ view.setUint16(offset, value & 0xffff, true);
+ view.setUint8(offset + 2, Math.floor(value * 2 ** -16));
+ break;
+ }
+ case 4: {
+ view.setUint32(offset, value, true);
+ break;
+ }
+ case 5: {
+ view.setUint32(offset, value | 0, true);
+ view.setUint8(offset + 4, Math.floor(value * 2 ** -32));
+ break;
+ }
+ case 6: {
+ view.setUint32(offset, value | 0, true);
+ view.setUint16(offset + 4, Math.floor(value * 2 ** -32), true);
+ break;
+ }
+ default: {
+ throw new RangeError("byteLength must be >= 1 and <= 6");
+ }
+ }
+ return offset + byteLength;
+}
+export function writeUIntBE(this: BufferExt, value, offset, byteLength) {
+ const view = (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength));
+ switch (byteLength) {
+ case 1: {
+ view.setUint8(offset, value);
+ break;
+ }
+ case 2: {
+ view.setUint16(offset, value, false);
+ break;
+ }
+ case 3: {
+ view.setUint16(offset + 1, value & 0xffff, false);
+ view.setUint8(offset, Math.floor(value * 2 ** -16));
+ break;
+ }
+ case 4: {
+ view.setUint32(offset, value, false);
+ break;
+ }
+ case 5: {
+ view.setUint32(offset + 1, value | 0, false);
+ view.setUint8(offset, Math.floor(value * 2 ** -32));
+ break;
+ }
+ case 6: {
+ view.setUint32(offset + 2, value | 0, false);
+ view.setUint16(offset, Math.floor(value * 2 ** -32), false);
+ break;
+ }
+ default: {
+ throw new RangeError("byteLength must be >= 1 and <= 6");
+ }
+ }
+ return offset + byteLength;
+}
+
+export function writeFloatLE(this: BufferExt, value, offset) {
+ (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setFloat32(offset, value, true);
+ return offset + 4;
+}
+
+export function writeFloatBE(this: BufferExt, value, offset) {
+ (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setFloat32(offset, value, false);
+ return offset + 4;
+}
+
+export function writeDoubleLE(this: BufferExt, value, offset) {
+ (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setFloat64(offset, value, true);
+ return offset + 8;
+}
+
+export function writeDoubleBE(this: BufferExt, value, offset) {
+ (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setFloat64(offset, value, false);
+ return offset + 8;
+}
+
+export function writeBigInt64LE(this: BufferExt, value, offset) {
+ (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setBigInt64(offset, value, true);
+ return offset + 8;
+}
+
+export function writeBigInt64BE(this: BufferExt, value, offset) {
+ (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setBigInt64(offset, value, false);
+ return offset + 8;
+}
+
+export function writeBigUInt64LE(this: BufferExt, value, offset) {
+ (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setBigUint64(offset, value, true);
+ return offset + 8;
+}
+
+export function writeBigUInt64BE(this: BufferExt, value, offset) {
+ (this.$dataView ||= new DataView(this.buffer, this.byteOffset, this.byteLength)).setBigUint64(offset, value, false);
+ return offset + 8;
+}
+
+export function utf8Write(this: BufferExt, text, offset, length) {
+ return this.write(text, offset, length, "utf8");
+}
+export function ucs2Write(this: BufferExt, text, offset, length) {
+ return this.write(text, offset, length, "ucs2");
+}
+export function utf16leWrite(this: BufferExt, text, offset, length) {
+ return this.write(text, offset, length, "utf16le");
+}
+export function latin1Write(this: BufferExt, text, offset, length) {
+ return this.write(text, offset, length, "latin1");
+}
+export function asciiWrite(this: BufferExt, text, offset, length) {
+ return this.write(text, offset, length, "ascii");
+}
+export function base64Write(this: BufferExt, text, offset, length) {
+ return this.write(text, offset, length, "base64");
+}
+export function base64urlWrite(this: BufferExt, text, offset, length) {
+ return this.write(text, offset, length, "base64url");
+}
+export function hexWrite(this: BufferExt, text, offset, length) {
+ return this.write(text, offset, length, "hex");
+}
+
+export function utf8Slice(this: BufferExt, offset, length) {
+ return this.toString(offset, length, "utf8");
+}
+export function ucs2Slice(this: BufferExt, offset, length) {
+ return this.toString(offset, length, "ucs2");
+}
+export function utf16leSlice(this: BufferExt, offset, length) {
+ return this.toString(offset, length, "utf16le");
+}
+export function latin1Slice(this: BufferExt, offset, length) {
+ return this.toString(offset, length, "latin1");
+}
+export function asciiSlice(this: BufferExt, offset, length) {
+ return this.toString(offset, length, "ascii");
+}
+export function base64Slice(this: BufferExt, offset, length) {
+ return this.toString(offset, length, "base64");
+}
+export function base64urlSlice(this: BufferExt, offset, length) {
+ return this.toString(offset, length, "base64url");
+}
+export function hexSlice(this: BufferExt, offset, length) {
+ return this.toString(offset, length, "hex");
+}
+
+export function toJSON(this: BufferExt) {
+ const type = "Buffer";
+ const data = Array.from(this);
+ return { type, data };
+}
+
+export function slice(this: BufferExt, start, end) {
+ var { buffer, byteOffset, byteLength } = this;
+
+ function adjustOffset(offset, length) {
+ // Use Math.trunc() to convert offset to an integer value that can be larger
+ // than an Int32. Hence, don't use offset | 0 or similar techniques.
+ offset = $trunc(offset);
+ if (offset === 0 || isNaN(offset)) {
+ return 0;
+ } else if (offset < 0) {
+ offset += length;
+ return offset > 0 ? offset : 0;
+ } else {
+ return offset < length ? offset : length;
+ }
+ }
+
+ var start_ = adjustOffset(start, byteLength);
+ var end_ = end !== undefined ? adjustOffset(end, byteLength) : byteLength;
+ return new $Buffer(buffer, byteOffset + start_, end_ > start_ ? end_ - start_ : 0);
+}
+
+$getter;
+export function parent(this: BufferExt) {
+ return $isObject(this) && this instanceof $Buffer ? this.buffer : undefined;
+}
+
+$getter;
+export function offset(this: BufferExt) {
+ return $isObject(this) && this instanceof $Buffer ? this.byteOffset : undefined;
+}
+
+export function inspect(this: BufferExt, recurseTimes, ctx) {
+ return Bun.inspect(this);
+}
diff --git a/src/js/builtins/ProcessObjectInternals.ts b/src/js/builtins/ProcessObjectInternals.ts
new file mode 100644
index 000000000..8b24e68ba
--- /dev/null
+++ b/src/js/builtins/ProcessObjectInternals.ts
@@ -0,0 +1,676 @@
+/*
+ * Copyright 2023 Codeblog Corp. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+export function binding(bindingName) {
+ if (bindingName !== "constants")
+ throw new TypeError(
+ "process.binding() is not supported in Bun. If that breaks something, please file an issue and include a reproducible code sample.",
+ );
+
+ var cache = globalThis.Symbol.for("process.bindings.constants");
+ var constants = globalThis[cache];
+ if (!constants) {
+ // TODO: make this less hacky.
+ // This calls require("node:fs").constants
+ // except, outside an ESM module.
+ const { constants: fs } = globalThis[globalThis.Symbol.for("Bun.lazy")]("createImportMeta", "node:process").require(
+ "node:fs",
+ );
+ constants = {
+ fs,
+ zlib: {},
+ crypto: {},
+ os: Bun._Os().constants,
+ };
+ globalThis[cache] = constants;
+ }
+ return constants;
+}
+
+export function getStdioWriteStream(fd_, rawRequire) {
+ var module = { path: "node:process", require: rawRequire };
+ var require = path => module.require(path);
+
+ function createStdioWriteStream(fd_) {
+ var { Duplex, eos, destroy } = require("node:stream");
+ var StdioWriteStream = class StdioWriteStream extends Duplex {
+ #writeStream;
+ #readStream;
+
+ #readable = true;
+ #writable = true;
+ #fdPath;
+
+ #onClose;
+ #onDrain;
+ #onFinish;
+ #onReadable;
+ #isTTY;
+
+ get isTTY() {
+ return (this.#isTTY ??= require("node:tty").isatty(fd_));
+ }
+
+ get fd() {
+ return fd_;
+ }
+
+ constructor(fd) {
+ super({ readable: true, writable: true });
+ this.#fdPath = `/dev/fd/${fd}`;
+ }
+
+ #onFinished(err) {
+ const cb = this.#onClose;
+ this.#onClose = null;
+
+ if (cb) {
+ cb(err);
+ } else if (err) {
+ this.destroy(err);
+ } else if (!this.#readable && !this.#writable) {
+ this.destroy();
+ }
+ }
+
+ _destroy(err, callback) {
+ if (!err && this.#onClose !== null) {
+ var AbortError = class AbortError extends Error {
+ code: string;
+ name: string;
+ constructor(message = "The operation was aborted", options = void 0) {
+ if (options !== void 0 && typeof options !== "object") {
+ throw new Error(`Invalid AbortError options:\n\n${JSON.stringify(options, null, 2)}`);
+ }
+ super(message, options);
+ this.code = "ABORT_ERR";
+ this.name = "AbortError";
+ }
+ };
+ err = new AbortError();
+ }
+
+ this.#onDrain = null;
+ this.#onFinish = null;
+ if (this.#onClose === null) {
+ callback(err);
+ } else {
+ this.#onClose = callback;
+ if (this.#writeStream) destroy(this.#writeStream, err);
+ if (this.#readStream) destroy(this.#readStream, err);
+ }
+ }
+
+ _write(chunk, encoding, callback) {
+ if (!this.#writeStream) {
+ var { createWriteStream } = require("node:fs");
+ var stream = (this.#writeStream = createWriteStream(this.#fdPath));
+
+ stream.on("finish", () => {
+ if (this.#onFinish) {
+ const cb = this.#onFinish;
+ this.#onFinish = null;
+ cb();
+ }
+ });
+
+ stream.on("drain", () => {
+ if (this.#onDrain) {
+ const cb = this.#onDrain;
+ this.#onDrain = null;
+ cb();
+ }
+ });
+
+ eos(stream, err => {
+ this.#writable = false;
+ if (err) {
+ destroy(stream, err);
+ }
+ this.#onFinished(err);
+ });
+ }
+ if (stream.write(chunk, encoding)) {
+ callback();
+ } else {
+ this.#onDrain = callback;
+ }
+ }
+
+ _final(callback) {
+ this.#writeStream && this.#writeStream.end();
+ this.#onFinish = callback;
+ }
+
+ #loadReadStream() {
+ var { createReadStream } = require("node:fs");
+
+ var readStream = (this.#readStream = createReadStream(this.#fdPath));
+
+ readStream.on("readable", () => {
+ if (this.#onReadable) {
+ const cb = this.#onReadable;
+ this.#onReadable = null;
+ cb();
+ } else {
+ this.read();
+ }
+ });
+
+ readStream.on("end", () => {
+ this.push(null);
+ });
+
+ eos(readStream, err => {
+ this.#readable = false;
+ if (err) {
+ destroy(readStream, err);
+ }
+ this.#onFinished(err);
+ });
+ return readStream;
+ }
+
+ _read() {
+ var stream = this.#readStream;
+ if (!stream) {
+ stream = this.#loadReadStream();
+ }
+
+ while (true) {
+ const buf = stream.read();
+ if (buf === null || !this.push(buf)) {
+ return;
+ }
+ }
+ }
+ };
+ return new StdioWriteStream(fd_);
+ }
+
+ var { EventEmitter } = require("node:events");
+
+ function isFastEncoding(encoding) {
+ if (!encoding) return true;
+
+ var normalied = encoding.toLowerCase();
+ return normalied === "utf8" || normalied === "utf-8" || normalied === "buffer" || normalied === "binary";
+ }
+
+ var readline;
+
+ var FastStdioWriteStream = class StdioWriteStream extends EventEmitter {
+ #fd;
+ #innerStream;
+ #writer;
+ #isTTY;
+
+ bytesWritten = 0;
+
+ setDefaultEncoding(encoding) {
+ if (this.#innerStream || !isFastEncoding(encoding)) {
+ this.#ensureInnerStream();
+ return this.#innerStream.setDefaultEncoding(encoding);
+ }
+ }
+
+ #createWriter() {
+ switch (this.#fd) {
+ case 1: {
+ var writer = Bun.stdout.writer({ highWaterMark: 0 });
+ writer.unref();
+ return writer;
+ }
+
+ case 2: {
+ var writer = Bun.stderr.writer({ highWaterMark: 0 });
+ writer.unref();
+ return writer;
+ }
+ default: {
+ throw new Error("Unsupported writer");
+ }
+ }
+ }
+
+ #getWriter() {
+ return (this.#writer ??= this.#createWriter());
+ }
+
+ constructor(fd_) {
+ super();
+ this.#fd = fd_;
+ }
+
+ get fd() {
+ return this.#fd;
+ }
+
+ get isTTY() {
+ return (this.#isTTY ??= require("node:tty").isatty(this.#fd));
+ }
+
+ cursorTo(x, y, callback) {
+ return (readline ??= require("readline")).cursorTo(this, x, y, callback);
+ }
+
+ moveCursor(dx, dy, callback) {
+ return (readline ??= require("readline")).moveCursor(this, dx, dy, callback);
+ }
+
+ clearLine(dir, callback) {
+ return (readline ??= require("readline")).clearLine(this, dir, callback);
+ }
+
+ clearScreenDown(callback) {
+ return (readline ??= require("readline")).clearScreenDown(this, callback);
+ }
+
+ // TODO: once implemented this.columns and this.rows should be uncommented
+ // getWindowSize() {
+ // return [this.columns, this.rows];
+ // }
+
+ ref() {
+ this.#getWriter().ref();
+ }
+
+ unref() {
+ this.#getWriter().unref();
+ }
+
+ on(event, listener) {
+ if (event === "close" || event === "finish") {
+ this.#ensureInnerStream();
+ return this.#innerStream.on(event, listener);
+ }
+
+ if (event === "drain") {
+ return super.on("drain", listener);
+ }
+
+ if (event === "error") {
+ return super.on("error", listener);
+ }
+
+ return super.on(event, listener);
+ }
+
+ get _writableState() {
+ this.#ensureInnerStream();
+ return this.#innerStream._writableState;
+ }
+
+ get _readableState() {
+ this.#ensureInnerStream();
+ return this.#innerStream._readableState;
+ }
+
+ pipe(destination) {
+ this.#ensureInnerStream();
+ return this.#innerStream.pipe(destination);
+ }
+
+ unpipe(destination) {
+ this.#ensureInnerStream();
+ return this.#innerStream.unpipe(destination);
+ }
+
+ #ensureInnerStream() {
+ if (this.#innerStream) return;
+ this.#innerStream = createStdioWriteStream(this.#fd);
+ const events = this.eventNames();
+ for (const event of events) {
+ this.#innerStream.on(event, (...args) => {
+ this.emit(event, ...args);
+ });
+ }
+ }
+
+ #write1(chunk) {
+ var writer = this.#getWriter();
+ const writeResult = writer.write(chunk);
+ this.bytesWritten += writeResult;
+ const flushResult = writer.flush(false);
+ return !!(writeResult || flushResult);
+ }
+
+ #writeWithEncoding(chunk, encoding) {
+ if (!isFastEncoding(encoding)) {
+ this.#ensureInnerStream();
+ return this.#innerStream.write(chunk, encoding);
+ }
+
+ return this.#write1(chunk);
+ }
+
+ #performCallback(cb, err?: any) {
+ if (err) {
+ this.emit("error", err);
+ }
+
+ try {
+ cb(err ? err : null);
+ } catch (err2) {
+ this.emit("error", err2);
+ }
+ }
+
+ #writeWithCallbackAndEncoding(chunk, encoding, callback) {
+ if (!isFastEncoding(encoding)) {
+ this.#ensureInnerStream();
+ return this.#innerStream.write(chunk, encoding, callback);
+ }
+
+ var writer = this.#getWriter();
+ const writeResult = writer.write(chunk);
+ const flushResult = writer.flush(true);
+ if (flushResult?.then) {
+ flushResult.then(
+ () => {
+ this.#performCallback(callback);
+ this.emit("drain");
+ },
+ err => this.#performCallback(callback, err),
+ );
+ return false;
+ }
+
+ queueMicrotask(() => {
+ this.#performCallback(callback);
+ });
+
+ return !!(writeResult || flushResult);
+ }
+
+ write(chunk, encoding, callback) {
+ const result = this._write(chunk, encoding, callback);
+
+ if (result) {
+ this.emit("drain");
+ }
+
+ return result;
+ }
+
+ get hasColors() {
+ return Bun.tty[this.#fd].hasColors;
+ }
+
+ _write(chunk, encoding, callback) {
+ var inner = this.#innerStream;
+ if (inner) {
+ return inner.write(chunk, encoding, callback);
+ }
+
+ switch (arguments.length) {
+ case 0: {
+ var error = new Error("Invalid arguments");
+ error.code = "ERR_INVALID_ARG_TYPE";
+ throw error;
+ }
+ case 1: {
+ return this.#write1(chunk);
+ }
+ case 2: {
+ if (typeof encoding === "function") {
+ return this.#writeWithCallbackAndEncoding(chunk, "", encoding);
+ } else if (typeof encoding === "string") {
+ return this.#writeWithEncoding(chunk, encoding);
+ }
+ }
+ default: {
+ if (
+ (typeof encoding !== "undefined" && typeof encoding !== "string") ||
+ (typeof callback !== "undefined" && typeof callback !== "function")
+ ) {
+ var error = new Error("Invalid arguments");
+ error.code = "ERR_INVALID_ARG_TYPE";
+ throw error;
+ }
+
+ if (typeof callback === "undefined") {
+ return this.#writeWithEncoding(chunk, encoding);
+ }
+
+ return this.#writeWithCallbackAndEncoding(chunk, encoding, callback);
+ }
+ }
+ }
+
+ destroy() {
+ return this;
+ }
+
+ end() {
+ return this;
+ }
+ };
+
+ return new FastStdioWriteStream(fd_);
+}
+
+export function getStdinStream(fd_, rawRequire, Bun) {
+ var module = { path: "node:process", require: rawRequire };
+ var require = path => module.require(path);
+
+ var { Duplex, eos, destroy } = require("node:stream");
+
+ var StdinStream = class StdinStream extends Duplex {
+ #reader;
+ // TODO: investigate https://github.com/oven-sh/bun/issues/1607
+
+ #readRef;
+ #writeStream;
+
+ #readable = true;
+ #unrefOnRead = false;
+ #writable = true;
+
+ #onFinish;
+ #onClose;
+ #onDrain;
+
+ get isTTY() {
+ return require("tty").isatty(fd_);
+ }
+
+ get fd() {
+ return fd_;
+ }
+
+ constructor() {
+ super({ readable: true, writable: true });
+ }
+
+ #onFinished(err?) {
+ const cb = this.#onClose;
+ this.#onClose = null;
+
+ if (cb) {
+ cb(err);
+ } else if (err) {
+ this.destroy(err);
+ } else if (!this.#readable && !this.#writable) {
+ this.destroy();
+ }
+ }
+
+ _destroy(err, callback) {
+ if (!err && this.#onClose !== null) {
+ var AbortError = class AbortError extends Error {
+ constructor(message = "The operation was aborted", options = void 0) {
+ if (options !== void 0 && typeof options !== "object") {
+ throw new Error(`Invalid AbortError options:\n\n${JSON.stringify(options, null, 2)}`);
+ }
+ super(message, options);
+ this.code = "ABORT_ERR";
+ this.name = "AbortError";
+ }
+ };
+ err = new AbortError();
+ }
+
+ if (this.#onClose === null) {
+ callback(err);
+ } else {
+ this.#onClose = callback;
+ if (this.#writeStream) destroy(this.#writeStream, err);
+ }
+ }
+
+ setRawMode(mode) {}
+
+ on(name, callback) {
+ // Streams don't generally required to present any data when only
+ // `readable` events are present, i.e. `readableFlowing === false`
+ //
+ // However, Node.js has a this quirk whereby `process.stdin.read()`
+ // blocks under TTY mode, thus looping `.read()` in this particular
+ // case would not result in truncation.
+ //
+ // Therefore the following hack is only specific to `process.stdin`
+ // and does not apply to the underlying Stream implementation.
+ if (name === "readable") {
+ this.ref();
+ this.#unrefOnRead = true;
+ }
+ return super.on(name, callback);
+ }
+
+ pause() {
+ this.unref();
+ return super.pause();
+ }
+
+ resume() {
+ this.ref();
+ return super.resume();
+ }
+
+ ref() {
+ this.#reader ??= Bun.stdin.stream().getReader();
+ this.#readRef ??= setInterval(() => {}, 1 << 30);
+ }
+
+ unref() {
+ if (this.#readRef) {
+ clearInterval(this.#readRef);
+ this.#readRef = null;
+ }
+ }
+
+ async #readInternal() {
+ try {
+ var done, value;
+ const read = this.#reader.readMany();
+
+ // read same-tick if possible
+ if (!read?.then) {
+ ({ done, value } = read);
+ } else {
+ ({ done, value } = await read);
+ }
+
+ if (!done) {
+ this.push(value[0]);
+
+ // shouldn't actually happen, but just in case
+ const length = value.length;
+ for (let i = 1; i < length; i++) {
+ this.push(value[i]);
+ }
+ } else {
+ this.push(null);
+ this.pause();
+ this.#readable = false;
+ this.#onFinished();
+ }
+ } catch (err) {
+ this.#readable = false;
+ this.#onFinished(err);
+ }
+ }
+
+ _read(size) {
+ if (this.#unrefOnRead) {
+ this.unref();
+ this.#unrefOnRead = false;
+ }
+ this.#readInternal();
+ }
+
+ #constructWriteStream() {
+ var { createWriteStream } = require("node:fs");
+ var writeStream = (this.#writeStream = createWriteStream("/dev/fd/0"));
+
+ writeStream.on("finish", () => {
+ if (this.#onFinish) {
+ const cb = this.#onFinish;
+ this.#onFinish = null;
+ cb();
+ }
+ });
+
+ writeStream.on("drain", () => {
+ if (this.#onDrain) {
+ const cb = this.#onDrain;
+ this.#onDrain = null;
+ cb();
+ }
+ });
+
+ eos(writeStream, err => {
+ this.#writable = false;
+ if (err) {
+ destroy(writeStream, err);
+ }
+ this.#onFinished(err);
+ });
+
+ return writeStream;
+ }
+
+ _write(chunk, encoding, callback) {
+ var writeStream = this.#writeStream;
+ if (!writeStream) {
+ writeStream = this.#constructWriteStream();
+ }
+
+ if (writeStream.write(chunk, encoding)) {
+ callback();
+ } else {
+ this.#onDrain = callback;
+ }
+ }
+
+ _final(callback) {
+ this.#writeStream.end();
+ this.#onFinish = (...args) => callback(...args);
+ }
+ };
+
+ return new StdinStream();
+}
diff --git a/src/js/builtins/README.md b/src/js/builtins/README.md
new file mode 100644
index 000000000..67b8882ee
--- /dev/null
+++ b/src/js/builtins/README.md
@@ -0,0 +1,53 @@
+# JavaScript Builtins
+
+**TLDR** — When files in this directory change, run:
+
+```bash
+# Delete the built files
+$ make regenerate-bindings
+# Re-link the binary without compiling zig (so it's faster)
+$ make bun-link-lld-debug
+```
+
+TypeScript files in [./ts](./ts) are bundled into C++ Headers that can access JavaScriptCore intrinsics. These files use special globals that are prefixed with `$`.
+
+```js
+$getter
+export function foo() {
+ return $getByIdDirectPrivate(this, "superSecret");
+}
+```
+
+It looks kind of like decorators but they're not. They let you directly call engine intrinsics and help with avoiding prototype pollution issues.
+
+V8 has a [similar feature](https://v8.dev/blog/embedded-builtins) (they use `%` instead of `@`)
+
+They usually are accompanied by a C++ file.
+
+We use a custom code generator located in `./codegen` which contains a regex-based parser that separates each function into it's own bundling context, so syntax like top level variables / functions will not work.
+
+You can also use `process.platform` and `process.arch` in these files. The values are inlined and DCE'd.
+
+## Generating builtins
+
+To regenerate the builtins, run this from Bun's project root (where the `Makefile` is)
+
+```bash
+$ make builtins
+```
+
+You'll want to also rebuild all the C++ bindings or you will get strange crashes on start
+
+```bash
+$ make clean-bindings
+```
+
+The `make regenerate-bindings` command will clean and rebuild the bindings.
+
+Also, you can run the code generator manually.
+
+```bash
+$ bun ./codegen/index.ts
+# pass --minify to minify (make passes this by default)
+# pass --keep-tmp to keep the temporary ./tmp folder, which contains processed pre-bundled .ts files
+```
diff --git a/src/js/builtins/ReadableByteStreamController.ts b/src/js/builtins/ReadableByteStreamController.ts
new file mode 100644
index 000000000..888f241bc
--- /dev/null
+++ b/src/js/builtins/ReadableByteStreamController.ts
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2016 Canon Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+export function initializeReadableByteStreamController(this, stream, underlyingByteSource, highWaterMark) {
+ if (arguments.length !== 4 && arguments[3] !== $isReadableStream)
+ throw new TypeError("ReadableByteStreamController constructor should not be called directly");
+
+ return $privateInitializeReadableByteStreamController.$call(this, stream, underlyingByteSource, highWaterMark);
+}
+
+export function enqueue(this, chunk) {
+ if (!$isReadableByteStreamController(this)) throw $makeThisTypeError("ReadableByteStreamController", "enqueue");
+
+ if ($getByIdDirectPrivate(this, "closeRequested"))
+ throw new TypeError("ReadableByteStreamController is requested to close");
+
+ if ($getByIdDirectPrivate($getByIdDirectPrivate(this, "controlledReadableStream"), "state") !== $streamReadable)
+ throw new TypeError("ReadableStream is not readable");
+
+ if (!$isObject(chunk) || !ArrayBuffer.$isView(chunk)) throw new TypeError("Provided chunk is not a TypedArray");
+
+ return $readableByteStreamControllerEnqueue(this, chunk);
+}
+
+export function error(this, error) {
+ if (!$isReadableByteStreamController(this)) throw $makeThisTypeError("ReadableByteStreamController", "error");
+
+ if ($getByIdDirectPrivate($getByIdDirectPrivate(this, "controlledReadableStream"), "state") !== $streamReadable)
+ throw new TypeError("ReadableStream is not readable");
+
+ $readableByteStreamControllerError(this, error);
+}
+
+export function close(this) {
+ if (!$isReadableByteStreamController(this)) throw $makeThisTypeError("ReadableByteStreamController", "close");
+
+ if ($getByIdDirectPrivate(this, "closeRequested")) throw new TypeError("Close has already been requested");
+
+ if ($getByIdDirectPrivate($getByIdDirectPrivate(this, "controlledReadableStream"), "state") !== $streamReadable)
+ throw new TypeError("ReadableStream is not readable");
+
+ $readableByteStreamControllerClose(this);
+}
+
+$getter;
+export function byobRequest(this) {
+ if (!$isReadableByteStreamController(this)) throw $makeGetterTypeError("ReadableByteStreamController", "byobRequest");
+
+ var request = $getByIdDirectPrivate(this, "byobRequest");
+ if (request === undefined) {
+ var pending = $getByIdDirectPrivate(this, "pendingPullIntos");
+ const firstDescriptor = pending.peek();
+ if (firstDescriptor) {
+ const view = new Uint8Array(
+ firstDescriptor.buffer,
+ firstDescriptor.byteOffset + firstDescriptor.bytesFilled,
+ firstDescriptor.byteLength - firstDescriptor.bytesFilled,
+ );
+ $putByIdDirectPrivate(this, "byobRequest", new ReadableStreamBYOBRequest(this, view, $isReadableStream));
+ }
+ }
+
+ return $getByIdDirectPrivate(this, "byobRequest");
+}
+
+$getter;
+export function desiredSize(this) {
+ if (!$isReadableByteStreamController(this)) throw $makeGetterTypeError("ReadableByteStreamController", "desiredSize");
+
+ return $readableByteStreamControllerGetDesiredSize(this);
+}
diff --git a/src/js/builtins/ReadableByteStreamInternals.ts b/src/js/builtins/ReadableByteStreamInternals.ts
new file mode 100644
index 000000000..f44c385b4
--- /dev/null
+++ b/src/js/builtins/ReadableByteStreamInternals.ts
@@ -0,0 +1,656 @@
+/*
+ * Copyright (C) 2016 Canon Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+// @internal
+
+export function privateInitializeReadableByteStreamController(this, stream, underlyingByteSource, highWaterMark) {
+ if (!$isReadableStream(stream)) throw new TypeError("ReadableByteStreamController needs a ReadableStream");
+
+ // readableStreamController is initialized with null value.
+ if ($getByIdDirectPrivate(stream, "readableStreamController") !== null)
+ throw new TypeError("ReadableStream already has a controller");
+
+ $putByIdDirectPrivate(this, "controlledReadableStream", stream);
+ $putByIdDirectPrivate(this, "underlyingByteSource", underlyingByteSource);
+ $putByIdDirectPrivate(this, "pullAgain", false);
+ $putByIdDirectPrivate(this, "pulling", false);
+ $readableByteStreamControllerClearPendingPullIntos(this);
+ $putByIdDirectPrivate(this, "queue", $newQueue());
+ $putByIdDirectPrivate(this, "started", 0);
+ $putByIdDirectPrivate(this, "closeRequested", false);
+
+ let hwm = $toNumber(highWaterMark);
+ if (isNaN(hwm) || hwm < 0) throw new RangeError("highWaterMark value is negative or not a number");
+ $putByIdDirectPrivate(this, "strategyHWM", hwm);
+
+ let autoAllocateChunkSize = underlyingByteSource.autoAllocateChunkSize;
+ if (autoAllocateChunkSize !== undefined) {
+ autoAllocateChunkSize = $toNumber(autoAllocateChunkSize);
+ if (autoAllocateChunkSize <= 0 || autoAllocateChunkSize === Infinity || autoAllocateChunkSize === -Infinity)
+ throw new RangeError("autoAllocateChunkSize value is negative or equal to positive or negative infinity");
+ }
+ $putByIdDirectPrivate(this, "autoAllocateChunkSize", autoAllocateChunkSize);
+ $putByIdDirectPrivate(this, "pendingPullIntos", $createFIFO());
+
+ const controller = this;
+ $promiseInvokeOrNoopNoCatch($getByIdDirectPrivate(controller, "underlyingByteSource"), "start", [controller]).$then(
+ () => {
+ $putByIdDirectPrivate(controller, "started", 1);
+ $assert(!$getByIdDirectPrivate(controller, "pulling"));
+ $assert(!$getByIdDirectPrivate(controller, "pullAgain"));
+ $readableByteStreamControllerCallPullIfNeeded(controller);
+ },
+ error => {
+ if ($getByIdDirectPrivate(stream, "state") === $streamReadable)
+ $readableByteStreamControllerError(controller, error);
+ },
+ );
+
+ $putByIdDirectPrivate(this, "cancel", $readableByteStreamControllerCancel);
+ $putByIdDirectPrivate(this, "pull", $readableByteStreamControllerPull);
+
+ return this;
+}
+
+export function readableStreamByteStreamControllerStart(this, controller) {
+ $putByIdDirectPrivate(controller, "start", undefined);
+}
+
+export function privateInitializeReadableStreamBYOBRequest(this, controller, view) {
+ $putByIdDirectPrivate(this, "associatedReadableByteStreamController", controller);
+ $putByIdDirectPrivate(this, "view", view);
+}
+
+export function isReadableByteStreamController(controller) {
+ // Same test mechanism as in isReadableStreamDefaultController (ReadableStreamInternals.js).
+ // See corresponding function for explanations.
+ return $isObject(controller) && !!$getByIdDirectPrivate(controller, "underlyingByteSource");
+}
+
+export function isReadableStreamBYOBRequest(byobRequest) {
+ // Same test mechanism as in isReadableStreamDefaultController (ReadableStreamInternals.js).
+ // See corresponding function for explanations.
+ return $isObject(byobRequest) && !!$getByIdDirectPrivate(byobRequest, "associatedReadableByteStreamController");
+}
+
+export function isReadableStreamBYOBReader(reader) {
+ // Spec tells to return true only if reader has a readIntoRequests internal slot.
+ // However, since it is a private slot, it cannot be checked using hasOwnProperty().
+ // Since readIntoRequests is initialized with an empty array, the following test is ok.
+ return $isObject(reader) && !!$getByIdDirectPrivate(reader, "readIntoRequests");
+}
+
+export function readableByteStreamControllerCancel(controller, reason) {
+ var pendingPullIntos = $getByIdDirectPrivate(controller, "pendingPullIntos");
+ var first = pendingPullIntos.peek();
+ if (first) first.bytesFilled = 0;
+
+ $putByIdDirectPrivate(controller, "queue", $newQueue());
+ return $promiseInvokeOrNoop($getByIdDirectPrivate(controller, "underlyingByteSource"), "cancel", [reason]);
+}
+
+export function readableByteStreamControllerError(controller, e) {
+ $assert(
+ $getByIdDirectPrivate($getByIdDirectPrivate(controller, "controlledReadableStream"), "state") === $streamReadable,
+ );
+ $readableByteStreamControllerClearPendingPullIntos(controller);
+ $putByIdDirectPrivate(controller, "queue", $newQueue());
+ $readableStreamError($getByIdDirectPrivate(controller, "controlledReadableStream"), e);
+}
+
+export function readableByteStreamControllerClose(controller) {
+ $assert(!$getByIdDirectPrivate(controller, "closeRequested"));
+ $assert(
+ $getByIdDirectPrivate($getByIdDirectPrivate(controller, "controlledReadableStream"), "state") === $streamReadable,
+ );
+
+ if ($getByIdDirectPrivate(controller, "queue").size > 0) {
+ $putByIdDirectPrivate(controller, "closeRequested", true);
+ return;
+ }
+
+ var first = $getByIdDirectPrivate(controller, "pendingPullIntos")?.peek();
+ if (first) {
+ if (first.bytesFilled > 0) {
+ const e = $makeTypeError("Close requested while there remain pending bytes");
+ $readableByteStreamControllerError(controller, e);
+ throw e;
+ }
+ }
+
+ $readableStreamClose($getByIdDirectPrivate(controller, "controlledReadableStream"));
+}
+
+export function readableByteStreamControllerClearPendingPullIntos(controller) {
+ $readableByteStreamControllerInvalidateBYOBRequest(controller);
+ var existing = $getByIdDirectPrivate(controller, "pendingPullIntos");
+ if (existing !== undefined) {
+ existing.clear();
+ } else {
+ $putByIdDirectPrivate(controller, "pendingPullIntos", $createFIFO());
+ }
+}
+
+export function readableByteStreamControllerGetDesiredSize(controller) {
+ const stream = $getByIdDirectPrivate(controller, "controlledReadableStream");
+ const state = $getByIdDirectPrivate(stream, "state");
+
+ if (state === $streamErrored) return null;
+ if (state === $streamClosed) return 0;
+
+ return $getByIdDirectPrivate(controller, "strategyHWM") - $getByIdDirectPrivate(controller, "queue").size;
+}
+
+export function readableStreamHasBYOBReader(stream) {
+ const reader = $getByIdDirectPrivate(stream, "reader");
+ return reader !== undefined && $isReadableStreamBYOBReader(reader);
+}
+
+export function readableStreamHasDefaultReader(stream) {
+ const reader = $getByIdDirectPrivate(stream, "reader");
+ return reader !== undefined && $isReadableStreamDefaultReader(reader);
+}
+
+export function readableByteStreamControllerHandleQueueDrain(controller) {
+ $assert(
+ $getByIdDirectPrivate($getByIdDirectPrivate(controller, "controlledReadableStream"), "state") === $streamReadable,
+ );
+ if (!$getByIdDirectPrivate(controller, "queue").size && $getByIdDirectPrivate(controller, "closeRequested"))
+ $readableStreamClose($getByIdDirectPrivate(controller, "controlledReadableStream"));
+ else $readableByteStreamControllerCallPullIfNeeded(controller);
+}
+
+export function readableByteStreamControllerPull(controller) {
+ const stream = $getByIdDirectPrivate(controller, "controlledReadableStream");
+ $assert($readableStreamHasDefaultReader(stream));
+ if ($getByIdDirectPrivate(controller, "queue").content?.isNotEmpty()) {
+ const entry = $getByIdDirectPrivate(controller, "queue").content.shift();
+ $getByIdDirectPrivate(controller, "queue").size -= entry.byteLength;
+ $readableByteStreamControllerHandleQueueDrain(controller);
+ let view;
+ try {
+ view = new Uint8Array(entry.buffer, entry.byteOffset, entry.byteLength);
+ } catch (error) {
+ return Promise.$reject(error);
+ }
+ return $createFulfilledPromise({ value: view, done: false });
+ }
+
+ if ($getByIdDirectPrivate(controller, "autoAllocateChunkSize") !== undefined) {
+ let buffer;
+ try {
+ buffer = $createUninitializedArrayBuffer($getByIdDirectPrivate(controller, "autoAllocateChunkSize"));
+ } catch (error) {
+ return Promise.$reject(error);
+ }
+ const pullIntoDescriptor = {
+ buffer,
+ byteOffset: 0,
+ byteLength: $getByIdDirectPrivate(controller, "autoAllocateChunkSize"),
+ bytesFilled: 0,
+ elementSize: 1,
+ ctor: Uint8Array,
+ readerType: "default",
+ };
+ $getByIdDirectPrivate(controller, "pendingPullIntos").push(pullIntoDescriptor);
+ }
+
+ const promise = $readableStreamAddReadRequest(stream);
+ $readableByteStreamControllerCallPullIfNeeded(controller);
+ return promise;
+}
+
+export function readableByteStreamControllerShouldCallPull(controller) {
+ const stream = $getByIdDirectPrivate(controller, "controlledReadableStream");
+
+ if ($getByIdDirectPrivate(stream, "state") !== $streamReadable) return false;
+ if ($getByIdDirectPrivate(controller, "closeRequested")) return false;
+ if (!($getByIdDirectPrivate(controller, "started") > 0)) return false;
+ const reader = $getByIdDirectPrivate(stream, "reader");
+
+ if (
+ reader &&
+ ($getByIdDirectPrivate(reader, "readRequests")?.isNotEmpty() || !!$getByIdDirectPrivate(reader, "bunNativePtr"))
+ )
+ return true;
+ if (
+ $readableStreamHasBYOBReader(stream) &&
+ $getByIdDirectPrivate($getByIdDirectPrivate(stream, "reader"), "readIntoRequests")?.isNotEmpty()
+ )
+ return true;
+ if ($readableByteStreamControllerGetDesiredSize(controller) > 0) return true;
+ return false;
+}
+
+export function readableByteStreamControllerCallPullIfNeeded(controller) {
+ if (!$readableByteStreamControllerShouldCallPull(controller)) return;
+
+ if ($getByIdDirectPrivate(controller, "pulling")) {
+ $putByIdDirectPrivate(controller, "pullAgain", true);
+ return;
+ }
+
+ $assert(!$getByIdDirectPrivate(controller, "pullAgain"));
+ $putByIdDirectPrivate(controller, "pulling", true);
+ $promiseInvokeOrNoop($getByIdDirectPrivate(controller, "underlyingByteSource"), "pull", [controller]).$then(
+ () => {
+ $putByIdDirectPrivate(controller, "pulling", false);
+ if ($getByIdDirectPrivate(controller, "pullAgain")) {
+ $putByIdDirectPrivate(controller, "pullAgain", false);
+ $readableByteStreamControllerCallPullIfNeeded(controller);
+ }
+ },
+ error => {
+ if (
+ $getByIdDirectPrivate($getByIdDirectPrivate(controller, "controlledReadableStream"), "state") ===
+ $streamReadable
+ )
+ $readableByteStreamControllerError(controller, error);
+ },
+ );
+}
+
+export function transferBufferToCurrentRealm(buffer) {
+ // FIXME: Determine what should be done here exactly (what is already existing in current
+ // codebase and what has to be added). According to spec, Transfer operation should be
+ // performed in order to transfer buffer to current realm. For the moment, simply return
+ // received buffer.
+ return buffer;
+}
+
+export function readableStreamReaderKind(reader) {
+ if (!!$getByIdDirectPrivate(reader, "readRequests")) return $getByIdDirectPrivate(reader, "bunNativePtr") ? 3 : 1;
+
+ if (!!$getByIdDirectPrivate(reader, "readIntoRequests")) return 2;
+
+ return 0;
+}
+
+export function readableByteStreamControllerEnqueue(controller, chunk) {
+ const stream = $getByIdDirectPrivate(controller, "controlledReadableStream");
+ $assert(!$getByIdDirectPrivate(controller, "closeRequested"));
+ $assert($getByIdDirectPrivate(stream, "state") === $streamReadable);
+
+ switch (
+ $getByIdDirectPrivate(stream, "reader") ? $readableStreamReaderKind($getByIdDirectPrivate(stream, "reader")) : 0
+ ) {
+ /* default reader */
+ case 1: {
+ if (!$getByIdDirectPrivate($getByIdDirectPrivate(stream, "reader"), "readRequests")?.isNotEmpty())
+ $readableByteStreamControllerEnqueueChunk(
+ controller,
+ $transferBufferToCurrentRealm(chunk.buffer),
+ chunk.byteOffset,
+ chunk.byteLength,
+ );
+ else {
+ $assert(!$getByIdDirectPrivate(controller, "queue").content.size());
+ const transferredView =
+ chunk.constructor === Uint8Array ? chunk : new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength);
+ $readableStreamFulfillReadRequest(stream, transferredView, false);
+ }
+ break;
+ }
+
+ /* BYOB */
+ case 2: {
+ $readableByteStreamControllerEnqueueChunk(
+ controller,
+ $transferBufferToCurrentRealm(chunk.buffer),
+ chunk.byteOffset,
+ chunk.byteLength,
+ );
+ $readableByteStreamControllerProcessPullDescriptors(controller);
+ break;
+ }
+
+ /* NativeReader */
+ case 3: {
+ // reader.$enqueueNative($getByIdDirectPrivate(reader, "bunNativePtr"), chunk);
+
+ break;
+ }
+
+ default: {
+ $assert(!$isReadableStreamLocked(stream));
+ $readableByteStreamControllerEnqueueChunk(
+ controller,
+ $transferBufferToCurrentRealm(chunk.buffer),
+ chunk.byteOffset,
+ chunk.byteLength,
+ );
+ break;
+ }
+ }
+}
+
+// Spec name: readableByteStreamControllerEnqueueChunkToQueue.
+export function readableByteStreamControllerEnqueueChunk(controller, buffer, byteOffset, byteLength) {
+ $getByIdDirectPrivate(controller, "queue").content.push({
+ buffer: buffer,
+ byteOffset: byteOffset,
+ byteLength: byteLength,
+ });
+ $getByIdDirectPrivate(controller, "queue").size += byteLength;
+}
+
+export function readableByteStreamControllerRespondWithNewView(controller, view) {
+ $assert($getByIdDirectPrivate(controller, "pendingPullIntos").isNotEmpty());
+
+ let firstDescriptor = $getByIdDirectPrivate(controller, "pendingPullIntos").peek();
+
+ if (firstDescriptor.byteOffset + firstDescriptor.bytesFilled !== view.byteOffset)
+ throw new RangeError("Invalid value for view.byteOffset");
+
+ if (firstDescriptor.byteLength !== view.byteLength) throw new RangeError("Invalid value for view.byteLength");
+
+ firstDescriptor.buffer = view.buffer;
+ $readableByteStreamControllerRespondInternal(controller, view.byteLength);
+}
+
+export function readableByteStreamControllerRespond(controller, bytesWritten) {
+ bytesWritten = $toNumber(bytesWritten);
+
+ if (isNaN(bytesWritten) || bytesWritten === Infinity || bytesWritten < 0)
+ throw new RangeError("bytesWritten has an incorrect value");
+
+ $assert($getByIdDirectPrivate(controller, "pendingPullIntos").isNotEmpty());
+
+ $readableByteStreamControllerRespondInternal(controller, bytesWritten);
+}
+
+export function readableByteStreamControllerRespondInternal(controller, bytesWritten) {
+ let firstDescriptor = $getByIdDirectPrivate(controller, "pendingPullIntos").peek();
+ let stream = $getByIdDirectPrivate(controller, "controlledReadableStream");
+
+ if ($getByIdDirectPrivate(stream, "state") === $streamClosed) {
+ if (bytesWritten !== 0) throw new TypeError("bytesWritten is different from 0 even though stream is closed");
+ $readableByteStreamControllerRespondInClosedState(controller, firstDescriptor);
+ } else {
+ $assert($getByIdDirectPrivate(stream, "state") === $streamReadable);
+ $readableByteStreamControllerRespondInReadableState(controller, bytesWritten, firstDescriptor);
+ }
+}
+
+export function readableByteStreamControllerRespondInReadableState(controller, bytesWritten, pullIntoDescriptor) {
+ if (pullIntoDescriptor.bytesFilled + bytesWritten > pullIntoDescriptor.byteLength)
+ throw new RangeError("bytesWritten value is too great");
+
+ $assert(
+ $getByIdDirectPrivate(controller, "pendingPullIntos").isEmpty() ||
+ $getByIdDirectPrivate(controller, "pendingPullIntos").peek() === pullIntoDescriptor,
+ );
+ $readableByteStreamControllerInvalidateBYOBRequest(controller);
+ pullIntoDescriptor.bytesFilled += bytesWritten;
+
+ if (pullIntoDescriptor.bytesFilled < pullIntoDescriptor.elementSize) return;
+
+ $readableByteStreamControllerShiftPendingDescriptor(controller);
+ const remainderSize = pullIntoDescriptor.bytesFilled % pullIntoDescriptor.elementSize;
+
+ if (remainderSize > 0) {
+ const end = pullIntoDescriptor.byteOffset + pullIntoDescriptor.bytesFilled;
+ const remainder = $cloneArrayBuffer(pullIntoDescriptor.buffer, end - remainderSize, remainderSize);
+ $readableByteStreamControllerEnqueueChunk(controller, remainder, 0, remainder.byteLength);
+ }
+
+ pullIntoDescriptor.buffer = $transferBufferToCurrentRealm(pullIntoDescriptor.buffer);
+ pullIntoDescriptor.bytesFilled -= remainderSize;
+ $readableByteStreamControllerCommitDescriptor(
+ $getByIdDirectPrivate(controller, "controlledReadableStream"),
+ pullIntoDescriptor,
+ );
+ $readableByteStreamControllerProcessPullDescriptors(controller);
+}
+
+export function readableByteStreamControllerRespondInClosedState(controller, firstDescriptor) {
+ firstDescriptor.buffer = $transferBufferToCurrentRealm(firstDescriptor.buffer);
+ $assert(firstDescriptor.bytesFilled === 0);
+
+ if ($readableStreamHasBYOBReader($getByIdDirectPrivate(controller, "controlledReadableStream"))) {
+ while (
+ $getByIdDirectPrivate(
+ $getByIdDirectPrivate($getByIdDirectPrivate(controller, "controlledReadableStream"), "reader"),
+ "readIntoRequests",
+ )?.isNotEmpty()
+ ) {
+ let pullIntoDescriptor = $readableByteStreamControllerShiftPendingDescriptor(controller);
+ $readableByteStreamControllerCommitDescriptor(
+ $getByIdDirectPrivate(controller, "controlledReadableStream"),
+ pullIntoDescriptor,
+ );
+ }
+ }
+}
+
+// Spec name: readableByteStreamControllerProcessPullIntoDescriptorsUsingQueue (shortened for readability).
+export function readableByteStreamControllerProcessPullDescriptors(controller) {
+ $assert(!$getByIdDirectPrivate(controller, "closeRequested"));
+ while ($getByIdDirectPrivate(controller, "pendingPullIntos").isNotEmpty()) {
+ if ($getByIdDirectPrivate(controller, "queue").size === 0) return;
+ let pullIntoDescriptor = $getByIdDirectPrivate(controller, "pendingPullIntos").peek();
+ if ($readableByteStreamControllerFillDescriptorFromQueue(controller, pullIntoDescriptor)) {
+ $readableByteStreamControllerShiftPendingDescriptor(controller);
+ $readableByteStreamControllerCommitDescriptor(
+ $getByIdDirectPrivate(controller, "controlledReadableStream"),
+ pullIntoDescriptor,
+ );
+ }
+ }
+}
+
+// Spec name: readableByteStreamControllerFillPullIntoDescriptorFromQueue (shortened for readability).
+export function readableByteStreamControllerFillDescriptorFromQueue(controller, pullIntoDescriptor) {
+ const currentAlignedBytes =
+ pullIntoDescriptor.bytesFilled - (pullIntoDescriptor.bytesFilled % pullIntoDescriptor.elementSize);
+ const maxBytesToCopy =
+ $getByIdDirectPrivate(controller, "queue").size < pullIntoDescriptor.byteLength - pullIntoDescriptor.bytesFilled
+ ? $getByIdDirectPrivate(controller, "queue").size
+ : pullIntoDescriptor.byteLength - pullIntoDescriptor.bytesFilled;
+ const maxBytesFilled = pullIntoDescriptor.bytesFilled + maxBytesToCopy;
+ const maxAlignedBytes = maxBytesFilled - (maxBytesFilled % pullIntoDescriptor.elementSize);
+ let totalBytesToCopyRemaining = maxBytesToCopy;
+ let ready = false;
+
+ if (maxAlignedBytes > currentAlignedBytes) {
+ totalBytesToCopyRemaining = maxAlignedBytes - pullIntoDescriptor.bytesFilled;
+ ready = true;
+ }
+
+ while (totalBytesToCopyRemaining > 0) {
+ let headOfQueue = $getByIdDirectPrivate(controller, "queue").content.peek();
+ const bytesToCopy =
+ totalBytesToCopyRemaining < headOfQueue.byteLength ? totalBytesToCopyRemaining : headOfQueue.byteLength;
+ // Copy appropriate part of pullIntoDescriptor.buffer to headOfQueue.buffer.
+ // Remark: this implementation is not completely aligned on the definition of CopyDataBlockBytes
+ // operation of ECMAScript (the case of Shared Data Block is not considered here, but it doesn't seem to be an issue).
+ const destStart = pullIntoDescriptor.byteOffset + pullIntoDescriptor.bytesFilled;
+ // FIXME: As indicated in comments of bug 172717, access to set is not safe. However, using prototype.$set.$call does
+ // not work ($set is undefined). A safe way to do that is needed.
+ new Uint8Array(pullIntoDescriptor.buffer).set(
+ new Uint8Array(headOfQueue.buffer, headOfQueue.byteOffset, bytesToCopy),
+ destStart,
+ );
+
+ if (headOfQueue.byteLength === bytesToCopy) $getByIdDirectPrivate(controller, "queue").content.shift();
+ else {
+ headOfQueue.byteOffset += bytesToCopy;
+ headOfQueue.byteLength -= bytesToCopy;
+ }
+
+ $getByIdDirectPrivate(controller, "queue").size -= bytesToCopy;
+ $assert(
+ $getByIdDirectPrivate(controller, "pendingPullIntos").isEmpty() ||
+ $getByIdDirectPrivate(controller, "pendingPullIntos").peek() === pullIntoDescriptor,
+ );
+ $readableByteStreamControllerInvalidateBYOBRequest(controller);
+ pullIntoDescriptor.bytesFilled += bytesToCopy;
+ totalBytesToCopyRemaining -= bytesToCopy;
+ }
+
+ if (!ready) {
+ $assert($getByIdDirectPrivate(controller, "queue").size === 0);
+ $assert(pullIntoDescriptor.bytesFilled > 0);
+ $assert(pullIntoDescriptor.bytesFilled < pullIntoDescriptor.elementSize);
+ }
+
+ return ready;
+}
+
+// Spec name: readableByteStreamControllerShiftPendingPullInto (renamed for consistency).
+export function readableByteStreamControllerShiftPendingDescriptor(controller) {
+ let descriptor = $getByIdDirectPrivate(controller, "pendingPullIntos").shift();
+ $readableByteStreamControllerInvalidateBYOBRequest(controller);
+ return descriptor;
+}
+
+export function readableByteStreamControllerInvalidateBYOBRequest(controller) {
+ if ($getByIdDirectPrivate(controller, "byobRequest") === undefined) return;
+ const byobRequest = $getByIdDirectPrivate(controller, "byobRequest");
+ $putByIdDirectPrivate(byobRequest, "associatedReadableByteStreamController", undefined);
+ $putByIdDirectPrivate(byobRequest, "view", undefined);
+ $putByIdDirectPrivate(controller, "byobRequest", undefined);
+}
+
+// Spec name: readableByteStreamControllerCommitPullIntoDescriptor (shortened for readability).
+export function readableByteStreamControllerCommitDescriptor(stream, pullIntoDescriptor) {
+ $assert($getByIdDirectPrivate(stream, "state") !== $streamErrored);
+ let done = false;
+ if ($getByIdDirectPrivate(stream, "state") === $streamClosed) {
+ $assert(!pullIntoDescriptor.bytesFilled);
+ done = true;
+ }
+ let filledView = $readableByteStreamControllerConvertDescriptor(pullIntoDescriptor);
+ if (pullIntoDescriptor.readerType === "default") $readableStreamFulfillReadRequest(stream, filledView, done);
+ else {
+ $assert(pullIntoDescriptor.readerType === "byob");
+ $readableStreamFulfillReadIntoRequest(stream, filledView, done);
+ }
+}
+
+// Spec name: readableByteStreamControllerConvertPullIntoDescriptor (shortened for readability).
+export function readableByteStreamControllerConvertDescriptor(pullIntoDescriptor) {
+ $assert(pullIntoDescriptor.bytesFilled <= pullIntoDescriptor.byteLength);
+ $assert(pullIntoDescriptor.bytesFilled % pullIntoDescriptor.elementSize === 0);
+
+ return new pullIntoDescriptor.ctor(
+ pullIntoDescriptor.buffer,
+ pullIntoDescriptor.byteOffset,
+ pullIntoDescriptor.bytesFilled / pullIntoDescriptor.elementSize,
+ );
+}
+
+export function readableStreamFulfillReadIntoRequest(stream, chunk, done) {
+ const readIntoRequest = $getByIdDirectPrivate($getByIdDirectPrivate(stream, "reader"), "readIntoRequests").shift();
+ $fulfillPromise(readIntoRequest, { value: chunk, done: done });
+}
+
+export function readableStreamBYOBReaderRead(reader, view) {
+ const stream = $getByIdDirectPrivate(reader, "ownerReadableStream");
+ $assert(!!stream);
+
+ $putByIdDirectPrivate(stream, "disturbed", true);
+ if ($getByIdDirectPrivate(stream, "state") === $streamErrored)
+ return Promise.$reject($getByIdDirectPrivate(stream, "storedError"));
+
+ return $readableByteStreamControllerPullInto($getByIdDirectPrivate(stream, "readableStreamController"), view);
+}
+
+export function readableByteStreamControllerPullInto(controller, view) {
+ const stream = $getByIdDirectPrivate(controller, "controlledReadableStream");
+ let elementSize = 1;
+ // Spec describes that in the case where view is a TypedArray, elementSize
+ // should be set to the size of an element (e.g. 2 for UInt16Array). For
+ // DataView, BYTES_PER_ELEMENT is undefined, contrary to the same property
+ // for TypedArrays.
+ // FIXME: Getting BYTES_PER_ELEMENT like this is not safe (property is read-only
+ // but can be modified if the prototype is redefined). A safe way of getting
+ // it would be to determine which type of ArrayBufferView view is an instance
+ // of based on typed arrays private variables. However, this is not possible due
+ // to bug 167697, which prevents access to typed arrays through their private
+ // names unless public name has already been met before.
+ if (view.BYTES_PER_ELEMENT !== undefined) elementSize = view.BYTES_PER_ELEMENT;
+
+ // FIXME: Getting constructor like this is not safe. A safe way of getting
+ // it would be to determine which type of ArrayBufferView view is an instance
+ // of, and to assign appropriate constructor based on this (e.g. ctor =
+ // $Uint8Array). However, this is not possible due to bug 167697, which
+ // prevents access to typed arrays through their private names unless public
+ // name has already been met before.
+ const ctor = view.constructor;
+
+ const pullIntoDescriptor = {
+ buffer: view.buffer,
+ byteOffset: view.byteOffset,
+ byteLength: view.byteLength,
+ bytesFilled: 0,
+ elementSize,
+ ctor,
+ readerType: "byob",
+ };
+
+ var pending = $getByIdDirectPrivate(controller, "pendingPullIntos");
+ if (pending?.isNotEmpty()) {
+ pullIntoDescriptor.buffer = $transferBufferToCurrentRealm(pullIntoDescriptor.buffer);
+ pending.push(pullIntoDescriptor);
+ return $readableStreamAddReadIntoRequest(stream);
+ }
+
+ if ($getByIdDirectPrivate(stream, "state") === $streamClosed) {
+ const emptyView = new ctor(pullIntoDescriptor.buffer, pullIntoDescriptor.byteOffset, 0);
+ return $createFulfilledPromise({ value: emptyView, done: true });
+ }
+
+ if ($getByIdDirectPrivate(controller, "queue").size > 0) {
+ if ($readableByteStreamControllerFillDescriptorFromQueue(controller, pullIntoDescriptor)) {
+ const filledView = $readableByteStreamControllerConvertDescriptor(pullIntoDescriptor);
+ $readableByteStreamControllerHandleQueueDrain(controller);
+ return $createFulfilledPromise({ value: filledView, done: false });
+ }
+ if ($getByIdDirectPrivate(controller, "closeRequested")) {
+ const e = $makeTypeError("Closing stream has been requested");
+ $readableByteStreamControllerError(controller, e);
+ return Promise.$reject(e);
+ }
+ }
+
+ pullIntoDescriptor.buffer = $transferBufferToCurrentRealm(pullIntoDescriptor.buffer);
+ $getByIdDirectPrivate(controller, "pendingPullIntos").push(pullIntoDescriptor);
+ const promise = $readableStreamAddReadIntoRequest(stream);
+ $readableByteStreamControllerCallPullIfNeeded(controller);
+ return promise;
+}
+
+export function readableStreamAddReadIntoRequest(stream) {
+ $assert($isReadableStreamBYOBReader($getByIdDirectPrivate(stream, "reader")));
+ $assert(
+ $getByIdDirectPrivate(stream, "state") === $streamReadable ||
+ $getByIdDirectPrivate(stream, "state") === $streamClosed,
+ );
+
+ const readRequest = $newPromise();
+ $getByIdDirectPrivate($getByIdDirectPrivate(stream, "reader"), "readIntoRequests").push(readRequest);
+
+ return readRequest;
+}
diff --git a/src/js/builtins/ReadableStream.ts b/src/js/builtins/ReadableStream.ts
new file mode 100644
index 000000000..613f869e5
--- /dev/null
+++ b/src/js/builtins/ReadableStream.ts
@@ -0,0 +1,421 @@
+/*
+ * Copyright (C) 2015 Canon Inc.
+ * Copyright (C) 2015 Igalia.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+export function initializeReadableStream(this: any, underlyingSource: UnderlyingSource, strategy: any) {
+ if (underlyingSource === undefined)
+ underlyingSource = { $bunNativeType: 0, $bunNativePtr: 0, $lazy: false } as UnderlyingSource;
+ if (strategy === undefined) strategy = {};
+
+ if (!$isObject(underlyingSource)) throw new TypeError("ReadableStream constructor takes an object as first argument");
+
+ if (strategy !== undefined && !$isObject(strategy))
+ throw new TypeError("ReadableStream constructor takes an object as second argument, if any");
+
+ $putByIdDirectPrivate(this, "state", $streamReadable);
+
+ $putByIdDirectPrivate(this, "reader", undefined);
+
+ $putByIdDirectPrivate(this, "storedError", undefined);
+
+ $putByIdDirectPrivate(this, "disturbed", false);
+
+ // Initialized with null value to enable distinction with undefined case.
+ $putByIdDirectPrivate(this, "readableStreamController", null);
+ $putByIdDirectPrivate(this, "bunNativeType", $getByIdDirectPrivate(underlyingSource, "bunNativeType") ?? 0);
+ $putByIdDirectPrivate(this, "bunNativePtr", $getByIdDirectPrivate(underlyingSource, "bunNativePtr") ?? 0);
+
+ const isDirect = underlyingSource.type === "direct";
+ // direct streams are always lazy
+ const isUnderlyingSourceLazy = !!underlyingSource.$lazy;
+ const isLazy = isDirect || isUnderlyingSourceLazy;
+
+ // FIXME: We should introduce https://streams.spec.whatwg.org/#create-readable-stream.
+ // For now, we emulate this with underlyingSource with private properties.
+ if ($getByIdDirectPrivate(underlyingSource, "pull") !== undefined && !isLazy) {
+ const size = $getByIdDirectPrivate(strategy, "size");
+ const highWaterMark = $getByIdDirectPrivate(strategy, "highWaterMark");
+ $putByIdDirectPrivate(this, "highWaterMark", highWaterMark);
+ $putByIdDirectPrivate(this, "underlyingSource", undefined);
+ $setupReadableStreamDefaultController(
+ this,
+ underlyingSource,
+ size,
+ highWaterMark !== undefined ? highWaterMark : 1,
+ $getByIdDirectPrivate(underlyingSource, "start"),
+ $getByIdDirectPrivate(underlyingSource, "pull"),
+ $getByIdDirectPrivate(underlyingSource, "cancel"),
+ );
+
+ return this;
+ }
+ if (isDirect) {
+ $putByIdDirectPrivate(this, "underlyingSource", underlyingSource);
+ $putByIdDirectPrivate(this, "highWaterMark", $getByIdDirectPrivate(strategy, "highWaterMark"));
+ $putByIdDirectPrivate(this, "start", () => $createReadableStreamController(this, underlyingSource, strategy));
+ } else if (isLazy) {
+ const autoAllocateChunkSize = underlyingSource.autoAllocateChunkSize;
+ $putByIdDirectPrivate(this, "highWaterMark", undefined);
+ $putByIdDirectPrivate(this, "underlyingSource", undefined);
+ $putByIdDirectPrivate(
+ this,
+ "highWaterMark",
+ autoAllocateChunkSize || $getByIdDirectPrivate(strategy, "highWaterMark"),
+ );
+
+ $putByIdDirectPrivate(this, "start", () => {
+ const instance = $lazyLoadStream(this, autoAllocateChunkSize);
+ if (instance) {
+ $createReadableStreamController(this, instance, strategy);
+ }
+ });
+ } else {
+ $putByIdDirectPrivate(this, "underlyingSource", undefined);
+ $putByIdDirectPrivate(this, "highWaterMark", $getByIdDirectPrivate(strategy, "highWaterMark"));
+ $putByIdDirectPrivate(this, "start", undefined);
+ $createReadableStreamController(this, underlyingSource, strategy);
+ }
+
+ return this;
+}
+
+$linkTimeConstant;
+export function readableStreamToArray(stream: ReadableStream): Promise<unknown[]> {
+ // this is a direct stream
+ var underlyingSource = $getByIdDirectPrivate(stream, "underlyingSource");
+ if (underlyingSource !== undefined) {
+ return $readableStreamToArrayDirect(stream, underlyingSource);
+ }
+
+ return $readableStreamIntoArray(stream);
+}
+
+$linkTimeConstant;
+export function readableStreamToText(stream: ReadableStream): Promise<string> {
+ // this is a direct stream
+ var underlyingSource = $getByIdDirectPrivate(stream, "underlyingSource");
+ if (underlyingSource !== undefined) {
+ return $readableStreamToTextDirect(stream, underlyingSource);
+ }
+
+ return $readableStreamIntoText(stream);
+}
+
+$linkTimeConstant;
+export function readableStreamToArrayBuffer(stream: ReadableStream<ArrayBuffer>): Promise<ArrayBuffer> | ArrayBuffer {
+ // this is a direct stream
+ var underlyingSource = $getByIdDirectPrivate(stream, "underlyingSource");
+
+ if (underlyingSource !== undefined) {
+ return $readableStreamToArrayBufferDirect(stream, underlyingSource);
+ }
+
+ var array = Bun.readableStreamToArray(stream);
+ if ($isPromise(array)) {
+ return array.$then(Bun.concatArrayBuffers);
+ }
+
+ return Bun.concatArrayBuffers(array);
+}
+
+$linkTimeConstant;
+export function readableStreamToJSON(stream: ReadableStream): unknown {
+ return Bun.readableStreamToText(stream).$then(globalThis.JSON.parse);
+}
+
+$linkTimeConstant;
+export function readableStreamToBlob(stream: ReadableStream): Promise<Blob> {
+ return Promise.resolve(Bun.readableStreamToArray(stream)).$then(array => new Blob(array));
+}
+
+$linkTimeConstant;
+export function consumeReadableStream(nativePtr, nativeType, inputStream) {
+ const symbol = globalThis.Symbol.for("Bun.consumeReadableStreamPrototype");
+ var cached = globalThis[symbol];
+ if (!cached) {
+ cached = globalThis[symbol] = [];
+ }
+ var Prototype = cached[nativeType];
+ if (Prototype === undefined) {
+ var [doRead, doError, doReadMany, doClose, onClose, deinit] =
+ globalThis[globalThis.Symbol.for("Bun.lazy")](nativeType);
+
+ Prototype = class NativeReadableStreamSink {
+ handleError: any;
+ handleClosed: any;
+ processResult: any;
+
+ constructor(reader, ptr) {
+ this.#ptr = ptr;
+ this.#reader = reader;
+ this.#didClose = false;
+
+ this.handleError = this._handleError.bind(this);
+ this.handleClosed = this._handleClosed.bind(this);
+ this.processResult = this._processResult.bind(this);
+
+ reader.closed.then(this.handleClosed, this.handleError);
+ }
+
+ _handleClosed() {
+ if (this.#didClose) return;
+ this.#didClose = true;
+ var ptr = this.#ptr;
+ this.#ptr = 0;
+ doClose(ptr);
+ deinit(ptr);
+ }
+
+ _handleError(error) {
+ if (this.#didClose) return;
+ this.#didClose = true;
+ var ptr = this.#ptr;
+ this.#ptr = 0;
+ doError(ptr, error);
+ deinit(ptr);
+ }
+
+ #ptr;
+ #didClose = false;
+ #reader;
+
+ _handleReadMany({ value, done, size }) {
+ if (done) {
+ this.handleClosed();
+ return;
+ }
+
+ if (this.#didClose) return;
+
+ doReadMany(this.#ptr, value, done, size);
+ }
+
+ read() {
+ if (!this.#ptr) return $throwTypeError("ReadableStreamSink is already closed");
+
+ return this.processResult(this.#reader.read());
+ }
+
+ _processResult(result) {
+ if (result && $isPromise(result)) {
+ const flags = $getPromiseInternalField(result, $promiseFieldFlags);
+ if (flags & $promiseStateFulfilled) {
+ const fulfilledValue = $getPromiseInternalField(result, $promiseFieldReactionsOrResult);
+ if (fulfilledValue) {
+ result = fulfilledValue;
+ }
+ }
+ }
+
+ if (result && $isPromise(result)) {
+ result.then(this.processResult, this.handleError);
+ return null;
+ }
+
+ if (result.done) {
+ this.handleClosed();
+ return 0;
+ } else if (result.value) {
+ return result.value;
+ } else {
+ return -1;
+ }
+ }
+
+ readMany() {
+ if (!this.#ptr) return $throwTypeError("ReadableStreamSink is already closed");
+ return this.processResult(this.#reader.readMany());
+ }
+ };
+
+ const minlength = nativeType + 1;
+ if (cached.length < minlength) {
+ cached.length = minlength;
+ }
+ $putByValDirect(cached, nativeType, Prototype);
+ }
+
+ if ($isReadableStreamLocked(inputStream)) {
+ throw new TypeError("Cannot start reading from a locked stream");
+ }
+
+ return new Prototype(inputStream.getReader(), nativePtr);
+}
+
+$linkTimeConstant;
+export function createEmptyReadableStream() {
+ var stream = new ReadableStream({
+ pull() {},
+ } as any);
+ $readableStreamClose(stream);
+ return stream;
+}
+
+$linkTimeConstant;
+export function createNativeReadableStream(nativePtr, nativeType, autoAllocateChunkSize) {
+ return new ReadableStream({
+ $lazy: true,
+ $bunNativeType: nativeType,
+ $bunNativePtr: nativePtr,
+ autoAllocateChunkSize: autoAllocateChunkSize,
+ });
+}
+
+export function cancel(this, reason) {
+ if (!$isReadableStream(this)) return Promise.$reject($makeThisTypeError("ReadableStream", "cancel"));
+
+ if ($isReadableStreamLocked(this)) return Promise.$reject($makeTypeError("ReadableStream is locked"));
+
+ return $readableStreamCancel(this, reason);
+}
+
+export function getReader(this, options) {
+ if (!$isReadableStream(this)) throw $makeThisTypeError("ReadableStream", "getReader");
+
+ const mode = $toDictionary(options, {}, "ReadableStream.getReader takes an object as first argument").mode;
+ if (mode === undefined) {
+ var start_ = $getByIdDirectPrivate(this, "start");
+ if (start_) {
+ $putByIdDirectPrivate(this, "start", undefined);
+ start_();
+ }
+
+ return new ReadableStreamDefaultReader(this);
+ }
+ // String conversion is required by spec, hence double equals.
+ if (mode == "byob") {
+ return new ReadableStreamBYOBReader(this);
+ }
+
+ throw new TypeError("Invalid mode is specified");
+}
+
+export function pipeThrough(this, streams, options) {
+ const transforms = streams;
+
+ const readable = transforms["readable"];
+ if (!$isReadableStream(readable)) throw $makeTypeError("readable should be ReadableStream");
+
+ const writable = transforms["writable"];
+ const internalWritable = $getInternalWritableStream(writable);
+ if (!$isWritableStream(internalWritable)) throw $makeTypeError("writable should be WritableStream");
+
+ let preventClose = false;
+ let preventAbort = false;
+ let preventCancel = false;
+ let signal;
+ if (!$isUndefinedOrNull(options)) {
+ if (!$isObject(options)) throw $makeTypeError("options must be an object");
+
+ preventAbort = !!options["preventAbort"];
+ preventCancel = !!options["preventCancel"];
+ preventClose = !!options["preventClose"];
+
+ signal = options["signal"];
+ if (signal !== undefined && !$isAbortSignal(signal)) throw $makeTypeError("options.signal must be AbortSignal");
+ }
+
+ if (!$isReadableStream(this)) throw $makeThisTypeError("ReadableStream", "pipeThrough");
+
+ if ($isReadableStreamLocked(this)) throw $makeTypeError("ReadableStream is locked");
+
+ if ($isWritableStreamLocked(internalWritable)) throw $makeTypeError("WritableStream is locked");
+
+ $readableStreamPipeToWritableStream(this, internalWritable, preventClose, preventAbort, preventCancel, signal);
+
+ return readable;
+}
+
+export function pipeTo(this, destination) {
+ if (!$isReadableStream(this)) return Promise.$reject($makeThisTypeError("ReadableStream", "pipeTo"));
+
+ if ($isReadableStreamLocked(this)) return Promise.$reject($makeTypeError("ReadableStream is locked"));
+
+ // FIXME: https://bugs.webkit.org/show_bug.cgi?id=159869.
+ // Built-in generator should be able to parse function signature to compute the function length correctly.
+ let options = $argument(1);
+
+ let preventClose = false;
+ let preventAbort = false;
+ let preventCancel = false;
+ let signal;
+ if (!$isUndefinedOrNull(options)) {
+ if (!$isObject(options)) return Promise.$reject($makeTypeError("options must be an object"));
+
+ try {
+ preventAbort = !!options["preventAbort"];
+ preventCancel = !!options["preventCancel"];
+ preventClose = !!options["preventClose"];
+
+ signal = options["signal"];
+ } catch (e) {
+ return Promise.$reject(e);
+ }
+
+ if (signal !== undefined && !$isAbortSignal(signal))
+ return Promise.$reject(new TypeError("options.signal must be AbortSignal"));
+ }
+
+ const internalDestination = $getInternalWritableStream(destination);
+ if (!$isWritableStream(internalDestination))
+ return Promise.$reject(new TypeError("ReadableStream pipeTo requires a WritableStream"));
+
+ if ($isWritableStreamLocked(internalDestination)) return Promise.$reject(new TypeError("WritableStream is locked"));
+
+ return $readableStreamPipeToWritableStream(
+ this,
+ internalDestination,
+ preventClose,
+ preventAbort,
+ preventCancel,
+ signal,
+ );
+}
+
+export function tee(this) {
+ if (!$isReadableStream(this)) throw $makeThisTypeError("ReadableStream", "tee");
+
+ return $readableStreamTee(this, false);
+}
+
+$getter;
+export function locked(this) {
+ if (!$isReadableStream(this)) throw $makeGetterTypeError("ReadableStream", "locked");
+
+ return $isReadableStreamLocked(this);
+}
+
+export function values(this, options) {
+ var prototype = ReadableStream.prototype;
+ $readableStreamDefineLazyIterators(prototype);
+ return prototype.values.$call(this, options);
+}
+
+$linkTimeConstant;
+export function lazyAsyncIterator(this) {
+ var prototype = ReadableStream.prototype;
+ $readableStreamDefineLazyIterators(prototype);
+ return prototype[globalThis.Symbol.asyncIterator].$call(this);
+}
diff --git a/src/js/builtins/ReadableStreamBYOBReader.ts b/src/js/builtins/ReadableStreamBYOBReader.ts
new file mode 100644
index 000000000..5ebfddb19
--- /dev/null
+++ b/src/js/builtins/ReadableStreamBYOBReader.ts
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2017 Canon Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY CANON INC. AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL CANON INC. AND ITS CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+export function initializeReadableStreamBYOBReader(this, stream) {
+ if (!$isReadableStream(stream)) throw new TypeError("ReadableStreamBYOBReader needs a ReadableStream");
+ if (!$isReadableByteStreamController($getByIdDirectPrivate(stream, "readableStreamController")))
+ throw new TypeError("ReadableStreamBYOBReader needs a ReadableByteStreamController");
+ if ($isReadableStreamLocked(stream)) throw new TypeError("ReadableStream is locked");
+
+ $readableStreamReaderGenericInitialize(this, stream);
+ $putByIdDirectPrivate(this, "readIntoRequests", $createFIFO());
+
+ return this;
+}
+
+export function cancel(this, reason) {
+ if (!$isReadableStreamBYOBReader(this))
+ return Promise.$reject($makeThisTypeError("ReadableStreamBYOBReader", "cancel"));
+
+ if (!$getByIdDirectPrivate(this, "ownerReadableStream"))
+ return Promise.$reject($makeTypeError("cancel() called on a reader owned by no readable stream"));
+
+ return $readableStreamReaderGenericCancel(this, reason);
+}
+
+export function read(this, view: DataView) {
+ if (!$isReadableStreamBYOBReader(this))
+ return Promise.$reject($makeThisTypeError("ReadableStreamBYOBReader", "read"));
+
+ if (!$getByIdDirectPrivate(this, "ownerReadableStream"))
+ return Promise.$reject($makeTypeError("read() called on a reader owned by no readable stream"));
+
+ if (!$isObject(view)) return Promise.$reject($makeTypeError("Provided view is not an object"));
+
+ if (!ArrayBuffer.$isView(view)) return Promise.$reject($makeTypeError("Provided view is not an ArrayBufferView"));
+
+ if (view.byteLength === 0) return Promise.$reject($makeTypeError("Provided view cannot have a 0 byteLength"));
+
+ return $readableStreamBYOBReaderRead(this, view);
+}
+
+export function releaseLock(this) {
+ if (!$isReadableStreamBYOBReader(this)) throw $makeThisTypeError("ReadableStreamBYOBReader", "releaseLock");
+
+ if (!$getByIdDirectPrivate(this, "ownerReadableStream")) return;
+
+ if ($getByIdDirectPrivate(this, "readIntoRequests")?.isNotEmpty())
+ throw new TypeError("There are still pending read requests, cannot release the lock");
+
+ $readableStreamReaderGenericRelease(this);
+}
+
+$getter;
+export function closed(this) {
+ if (!$isReadableStreamBYOBReader(this))
+ return Promise.$reject($makeGetterTypeError("ReadableStreamBYOBReader", "closed"));
+
+ return $getByIdDirectPrivate(this, "closedPromiseCapability").$promise;
+}
diff --git a/src/js/builtins/ReadableStreamBYOBRequest.ts b/src/js/builtins/ReadableStreamBYOBRequest.ts
new file mode 100644
index 000000000..1354f9349
--- /dev/null
+++ b/src/js/builtins/ReadableStreamBYOBRequest.ts
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2017 Canon Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+export function initializeReadableStreamBYOBRequest(this, controller, view) {
+ if (arguments.length !== 3 && arguments[2] !== $isReadableStream)
+ throw new TypeError("ReadableStreamBYOBRequest constructor should not be called directly");
+
+ return $privateInitializeReadableStreamBYOBRequest.$call(this, controller, view);
+}
+
+export function respond(this, bytesWritten) {
+ if (!$isReadableStreamBYOBRequest(this)) throw $makeThisTypeError("ReadableStreamBYOBRequest", "respond");
+
+ if ($getByIdDirectPrivate(this, "associatedReadableByteStreamController") === undefined)
+ throw new TypeError("ReadableStreamBYOBRequest.associatedReadableByteStreamController is undefined");
+
+ return $readableByteStreamControllerRespond(
+ $getByIdDirectPrivate(this, "associatedReadableByteStreamController"),
+ bytesWritten,
+ );
+}
+
+export function respondWithNewView(this, view) {
+ if (!$isReadableStreamBYOBRequest(this)) throw $makeThisTypeError("ReadableStreamBYOBRequest", "respond");
+
+ if ($getByIdDirectPrivate(this, "associatedReadableByteStreamController") === undefined)
+ throw new TypeError("ReadableStreamBYOBRequest.associatedReadableByteStreamController is undefined");
+
+ if (!$isObject(view)) throw new TypeError("Provided view is not an object");
+
+ if (!ArrayBuffer.$isView(view)) throw new TypeError("Provided view is not an ArrayBufferView");
+
+ return $readableByteStreamControllerRespondWithNewView(
+ $getByIdDirectPrivate(this, "associatedReadableByteStreamController"),
+ view,
+ );
+}
+
+$getter;
+export function view(this) {
+ if (!$isReadableStreamBYOBRequest(this)) throw $makeGetterTypeError("ReadableStreamBYOBRequest", "view");
+
+ return $getByIdDirectPrivate(this, "view");
+}
diff --git a/src/js/builtins/ReadableStreamDefaultController.ts b/src/js/builtins/ReadableStreamDefaultController.ts
new file mode 100644
index 000000000..912cd1acb
--- /dev/null
+++ b/src/js/builtins/ReadableStreamDefaultController.ts
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2015 Canon Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+export function initializeReadableStreamDefaultController(this, stream, underlyingSource, size, highWaterMark) {
+ if (arguments.length !== 5 && arguments[4] !== $isReadableStream)
+ throw new TypeError("ReadableStreamDefaultController constructor should not be called directly");
+
+ return $privateInitializeReadableStreamDefaultController.$call(this, stream, underlyingSource, size, highWaterMark);
+}
+
+export function enqueue(this, chunk) {
+ if (!$isReadableStreamDefaultController(this)) throw $makeThisTypeError("ReadableStreamDefaultController", "enqueue");
+
+ if (!$readableStreamDefaultControllerCanCloseOrEnqueue(this))
+ throw new TypeError("ReadableStreamDefaultController is not in a state where chunk can be enqueued");
+
+ return $readableStreamDefaultControllerEnqueue(this, chunk);
+}
+
+export function error(this, err) {
+ if (!$isReadableStreamDefaultController(this)) throw $makeThisTypeError("ReadableStreamDefaultController", "error");
+
+ $readableStreamDefaultControllerError(this, err);
+}
+
+export function close(this) {
+ if (!$isReadableStreamDefaultController(this)) throw $makeThisTypeError("ReadableStreamDefaultController", "close");
+
+ if (!$readableStreamDefaultControllerCanCloseOrEnqueue(this))
+ throw new TypeError("ReadableStreamDefaultController is not in a state where it can be closed");
+
+ $readableStreamDefaultControllerClose(this);
+}
+
+$getter;
+export function desiredSize(this) {
+ if (!$isReadableStreamDefaultController(this))
+ throw $makeGetterTypeError("ReadableStreamDefaultController", "desiredSize");
+
+ return $readableStreamDefaultControllerGetDesiredSize(this);
+}
diff --git a/src/js/builtins/ReadableStreamDefaultReader.ts b/src/js/builtins/ReadableStreamDefaultReader.ts
new file mode 100644
index 000000000..ecd553ed5
--- /dev/null
+++ b/src/js/builtins/ReadableStreamDefaultReader.ts
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2015 Canon Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+export function initializeReadableStreamDefaultReader(this, stream) {
+ if (!$isReadableStream(stream)) throw new TypeError("ReadableStreamDefaultReader needs a ReadableStream");
+ if ($isReadableStreamLocked(stream)) throw new TypeError("ReadableStream is locked");
+
+ $readableStreamReaderGenericInitialize(this, stream);
+ $putByIdDirectPrivate(this, "readRequests", $createFIFO());
+
+ return this;
+}
+
+export function cancel(this, reason) {
+ if (!$isReadableStreamDefaultReader(this))
+ return Promise.$reject($makeThisTypeError("ReadableStreamDefaultReader", "cancel"));
+
+ if (!$getByIdDirectPrivate(this, "ownerReadableStream"))
+ return Promise.$reject(new TypeError("cancel() called on a reader owned by no readable stream"));
+
+ return $readableStreamReaderGenericCancel(this, reason);
+}
+
+export function readMany(this) {
+ if (!$isReadableStreamDefaultReader(this))
+ throw new TypeError("ReadableStreamDefaultReader.readMany() should not be called directly");
+
+ const stream = $getByIdDirectPrivate(this, "ownerReadableStream");
+ if (!stream) throw new TypeError("readMany() called on a reader owned by no readable stream");
+
+ const state = $getByIdDirectPrivate(stream, "state");
+ $putByIdDirectPrivate(stream, "disturbed", true);
+ if (state === $streamClosed) return { value: [], size: 0, done: true };
+ else if (state === $streamErrored) {
+ throw $getByIdDirectPrivate(stream, "storedError");
+ }
+
+ var controller = $getByIdDirectPrivate(stream, "readableStreamController");
+ var queue = $getByIdDirectPrivate(controller, "queue");
+
+ if (!queue) {
+ // This is a ReadableStream direct controller implemented in JS
+ // It hasn't been started yet.
+ return controller.$pull(controller).$then(function ({ done, value }) {
+ return done ? { done: true, value: [], size: 0 } : { value: [value], size: 1, done: false };
+ });
+ }
+
+ const content = queue.content;
+ var size = queue.size;
+ var values = content.toArray(false);
+
+ var length = values.length;
+
+ if (length > 0) {
+ var outValues = $newArrayWithSize(length);
+ if ($isReadableByteStreamController(controller)) {
+ {
+ const buf = values[0];
+ if (!(ArrayBuffer.$isView(buf) || buf instanceof ArrayBuffer)) {
+ $putByValDirect(outValues, 0, new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength));
+ } else {
+ $putByValDirect(outValues, 0, buf);
+ }
+ }
+
+ for (var i = 1; i < length; i++) {
+ const buf = values[i];
+ if (!(ArrayBuffer.$isView(buf) || buf instanceof ArrayBuffer)) {
+ $putByValDirect(outValues, i, new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength));
+ } else {
+ $putByValDirect(outValues, i, buf);
+ }
+ }
+ } else {
+ $putByValDirect(outValues, 0, values[0].value);
+ for (var i = 1; i < length; i++) {
+ $putByValDirect(outValues, i, values[i].value);
+ }
+ }
+
+ $resetQueue($getByIdDirectPrivate(controller, "queue"));
+
+ if ($getByIdDirectPrivate(controller, "closeRequested"))
+ $readableStreamClose($getByIdDirectPrivate(controller, "controlledReadableStream"));
+ else if ($isReadableStreamDefaultController(controller))
+ $readableStreamDefaultControllerCallPullIfNeeded(controller);
+ else if ($isReadableByteStreamController(controller)) $readableByteStreamControllerCallPullIfNeeded(controller);
+
+ return { value: outValues, size, done: false };
+ }
+
+ var onPullMany = result => {
+ if (result.done) {
+ return { value: [], size: 0, done: true };
+ }
+ var controller = $getByIdDirectPrivate(stream, "readableStreamController");
+
+ var queue = $getByIdDirectPrivate(controller, "queue");
+ var value = [result.value].concat(queue.content.toArray(false));
+ var length = value.length;
+
+ if ($isReadableByteStreamController(controller)) {
+ for (var i = 0; i < length; i++) {
+ const buf = value[i];
+ if (!(ArrayBuffer.$isView(buf) || buf instanceof ArrayBuffer)) {
+ const { buffer, byteOffset, byteLength } = buf;
+ $putByValDirect(value, i, new Uint8Array(buffer, byteOffset, byteLength));
+ }
+ }
+ } else {
+ for (var i = 1; i < length; i++) {
+ $putByValDirect(value, i, value[i].value);
+ }
+ }
+
+ var size = queue.size;
+ $resetQueue(queue);
+
+ if ($getByIdDirectPrivate(controller, "closeRequested"))
+ $readableStreamClose($getByIdDirectPrivate(controller, "controlledReadableStream"));
+ else if ($isReadableStreamDefaultController(controller))
+ $readableStreamDefaultControllerCallPullIfNeeded(controller);
+ else if ($isReadableByteStreamController(controller)) $readableByteStreamControllerCallPullIfNeeded(controller);
+
+ return { value: value, size: size, done: false };
+ };
+
+ var pullResult = controller.$pull(controller);
+ if (pullResult && $isPromise(pullResult)) {
+ return pullResult.$then(onPullMany);
+ }
+
+ return onPullMany(pullResult);
+}
+
+export function read(this) {
+ if (!$isReadableStreamDefaultReader(this))
+ return Promise.$reject($makeThisTypeError("ReadableStreamDefaultReader", "read"));
+ if (!$getByIdDirectPrivate(this, "ownerReadableStream"))
+ return Promise.$reject(new TypeError("read() called on a reader owned by no readable stream"));
+
+ return $readableStreamDefaultReaderRead(this);
+}
+
+export function releaseLock(this) {
+ if (!$isReadableStreamDefaultReader(this)) throw $makeThisTypeError("ReadableStreamDefaultReader", "releaseLock");
+
+ if (!$getByIdDirectPrivate(this, "ownerReadableStream")) return;
+
+ if ($getByIdDirectPrivate(this, "readRequests")?.isNotEmpty())
+ throw new TypeError("There are still pending read requests, cannot release the lock");
+
+ $readableStreamReaderGenericRelease(this);
+}
+
+$getter;
+export function closed(this) {
+ if (!$isReadableStreamDefaultReader(this))
+ return Promise.$reject($makeGetterTypeError("ReadableStreamDefaultReader", "closed"));
+
+ return $getByIdDirectPrivate(this, "closedPromiseCapability").$promise;
+}
diff --git a/src/js/builtins/ReadableStreamInternals.ts b/src/js/builtins/ReadableStreamInternals.ts
new file mode 100644
index 000000000..0c4e816f4
--- /dev/null
+++ b/src/js/builtins/ReadableStreamInternals.ts
@@ -0,0 +1,1799 @@
+/*
+ * Copyright (C) 2015 Canon Inc. All rights reserved.
+ * Copyright (C) 2015 Igalia.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// @internal
+
+export function readableStreamReaderGenericInitialize(reader, stream) {
+ $putByIdDirectPrivate(reader, "ownerReadableStream", stream);
+ $putByIdDirectPrivate(stream, "reader", reader);
+ if ($getByIdDirectPrivate(stream, "state") === $streamReadable)
+ $putByIdDirectPrivate(reader, "closedPromiseCapability", $newPromiseCapability(Promise));
+ else if ($getByIdDirectPrivate(stream, "state") === $streamClosed)
+ $putByIdDirectPrivate(reader, "closedPromiseCapability", {
+ $promise: Promise.$resolve(),
+ });
+ else {
+ $assert($getByIdDirectPrivate(stream, "state") === $streamErrored);
+ $putByIdDirectPrivate(reader, "closedPromiseCapability", {
+ $promise: $newHandledRejectedPromise($getByIdDirectPrivate(stream, "storedError")),
+ });
+ }
+}
+
+export function privateInitializeReadableStreamDefaultController(this, stream, underlyingSource, size, highWaterMark) {
+ if (!$isReadableStream(stream)) throw new TypeError("ReadableStreamDefaultController needs a ReadableStream");
+
+ // readableStreamController is initialized with null value.
+ if ($getByIdDirectPrivate(stream, "readableStreamController") !== null)
+ throw new TypeError("ReadableStream already has a controller");
+
+ $putByIdDirectPrivate(this, "controlledReadableStream", stream);
+ $putByIdDirectPrivate(this, "underlyingSource", underlyingSource);
+ $putByIdDirectPrivate(this, "queue", $newQueue());
+ $putByIdDirectPrivate(this, "started", -1);
+ $putByIdDirectPrivate(this, "closeRequested", false);
+ $putByIdDirectPrivate(this, "pullAgain", false);
+ $putByIdDirectPrivate(this, "pulling", false);
+ $putByIdDirectPrivate(this, "strategy", $validateAndNormalizeQueuingStrategy(size, highWaterMark));
+
+ return this;
+}
+
+export function readableStreamDefaultControllerError(controller, error) {
+ const stream = $getByIdDirectPrivate(controller, "controlledReadableStream");
+ if ($getByIdDirectPrivate(stream, "state") !== $streamReadable) return;
+ $putByIdDirectPrivate(controller, "queue", $newQueue());
+
+ $readableStreamError(stream, error);
+}
+
+export function readableStreamPipeTo(stream, sink) {
+ $assert($isReadableStream(stream));
+
+ const reader = new ReadableStreamDefaultReader(stream);
+
+ $getByIdDirectPrivate(reader, "closedPromiseCapability").$promise.$then(
+ () => {},
+ e => {
+ sink.error(e);
+ },
+ );
+
+ function doPipe() {
+ $readableStreamDefaultReaderRead(reader).$then(
+ function (result) {
+ if (result.done) {
+ sink.close();
+ return;
+ }
+ try {
+ sink.enqueue(result.value);
+ } catch (e) {
+ sink.error("ReadableStream chunk enqueueing in the sink failed");
+ return;
+ }
+ doPipe();
+ },
+ function (e) {
+ sink.error(e);
+ },
+ );
+ }
+ doPipe();
+}
+
+export function acquireReadableStreamDefaultReader(stream) {
+ var start = $getByIdDirectPrivate(stream, "start");
+ if (start) {
+ start.$call(stream);
+ }
+
+ return new ReadableStreamDefaultReader(stream);
+}
+
+// https://streams.spec.whatwg.org/#set-up-readable-stream-default-controller, starting from step 6.
+// The other part is implemented in privateInitializeReadableStreamDefaultController.
+export function setupReadableStreamDefaultController(
+ stream,
+ underlyingSource,
+ size,
+ highWaterMark,
+ startMethod,
+ pullMethod,
+ cancelMethod,
+) {
+ const controller = new ReadableStreamDefaultController(
+ stream,
+ underlyingSource,
+ size,
+ highWaterMark,
+ $isReadableStream,
+ );
+
+ const pullAlgorithm = () => $promiseInvokeOrNoopMethod(underlyingSource, pullMethod, [controller]);
+ const cancelAlgorithm = reason => $promiseInvokeOrNoopMethod(underlyingSource, cancelMethod, [reason]);
+
+ $putByIdDirectPrivate(controller, "pullAlgorithm", pullAlgorithm);
+ $putByIdDirectPrivate(controller, "cancelAlgorithm", cancelAlgorithm);
+ $putByIdDirectPrivate(controller, "pull", $readableStreamDefaultControllerPull);
+ $putByIdDirectPrivate(controller, "cancel", $readableStreamDefaultControllerCancel);
+ $putByIdDirectPrivate(stream, "readableStreamController", controller);
+
+ $readableStreamDefaultControllerStart(controller);
+}
+
+export function createReadableStreamController(stream, underlyingSource, strategy) {
+ const type = underlyingSource.type;
+ const typeString = $toString(type);
+
+ if (typeString === "bytes") {
+ // if (!$readableByteStreamAPIEnabled())
+ // $throwTypeError("ReadableByteStreamController is not implemented");
+
+ if (strategy.highWaterMark === undefined) strategy.highWaterMark = 0;
+ if (strategy.size !== undefined) $throwRangeError("Strategy for a ReadableByteStreamController cannot have a size");
+
+ $putByIdDirectPrivate(
+ stream,
+ "readableStreamController",
+ new ReadableByteStreamController(stream, underlyingSource, strategy.highWaterMark, $isReadableStream),
+ );
+ } else if (typeString === "direct") {
+ var highWaterMark = strategy?.highWaterMark;
+ $initializeArrayBufferStream.$call(stream, underlyingSource, highWaterMark);
+ } else if (type === undefined) {
+ if (strategy.highWaterMark === undefined) strategy.highWaterMark = 1;
+
+ $setupReadableStreamDefaultController(
+ stream,
+ underlyingSource,
+ strategy.size,
+ strategy.highWaterMark,
+ underlyingSource.start,
+ underlyingSource.pull,
+ underlyingSource.cancel,
+ );
+ } else throw new RangeError("Invalid type for underlying source");
+}
+
+export function readableStreamDefaultControllerStart(controller) {
+ if ($getByIdDirectPrivate(controller, "started") !== -1) return;
+
+ const underlyingSource = $getByIdDirectPrivate(controller, "underlyingSource");
+ const startMethod = underlyingSource.start;
+ $putByIdDirectPrivate(controller, "started", 0);
+
+ $promiseInvokeOrNoopMethodNoCatch(underlyingSource, startMethod, [controller]).$then(
+ () => {
+ $putByIdDirectPrivate(controller, "started", 1);
+ $assert(!$getByIdDirectPrivate(controller, "pulling"));
+ $assert(!$getByIdDirectPrivate(controller, "pullAgain"));
+ $readableStreamDefaultControllerCallPullIfNeeded(controller);
+ },
+ error => {
+ $readableStreamDefaultControllerError(controller, error);
+ },
+ );
+}
+
+// FIXME: Replace readableStreamPipeTo by below function.
+// This method implements the latest https://streams.spec.whatwg.org/#readable-stream-pipe-to.
+export function readableStreamPipeToWritableStream(
+ source,
+ destination,
+ preventClose,
+ preventAbort,
+ preventCancel,
+ signal,
+) {
+ // const isDirectStream = !!$getByIdDirectPrivate(source, "start");
+
+ $assert($isReadableStream(source));
+ $assert($isWritableStream(destination));
+ $assert(!$isReadableStreamLocked(source));
+ $assert(!$isWritableStreamLocked(destination));
+ $assert(signal === undefined || $isAbortSignal(signal));
+
+ if ($getByIdDirectPrivate(source, "underlyingByteSource") !== undefined)
+ return Promise.$reject("Piping to a readable bytestream is not supported");
+
+ let pipeState: any = {
+ source: source,
+ destination: destination,
+ preventAbort: preventAbort,
+ preventCancel: preventCancel,
+ preventClose: preventClose,
+ signal: signal,
+ };
+
+ pipeState.reader = $acquireReadableStreamDefaultReader(source);
+ pipeState.writer = $acquireWritableStreamDefaultWriter(destination);
+
+ $putByIdDirectPrivate(source, "disturbed", true);
+
+ pipeState.finalized = false;
+ pipeState.shuttingDown = false;
+ pipeState.promiseCapability = $newPromiseCapability(Promise);
+ pipeState.pendingReadPromiseCapability = $newPromiseCapability(Promise);
+ pipeState.pendingReadPromiseCapability.$resolve.$call();
+ pipeState.pendingWritePromise = Promise.$resolve();
+
+ if (signal !== undefined) {
+ const algorithm = reason => {
+ if (pipeState.finalized) return;
+
+ $pipeToShutdownWithAction(
+ pipeState,
+ () => {
+ const shouldAbortDestination =
+ !pipeState.preventAbort && $getByIdDirectPrivate(pipeState.destination, "state") === "writable";
+ const promiseDestination = shouldAbortDestination
+ ? $writableStreamAbort(pipeState.destination, reason)
+ : Promise.$resolve();
+
+ const shouldAbortSource =
+ !pipeState.preventCancel && $getByIdDirectPrivate(pipeState.source, "state") === $streamReadable;
+ const promiseSource = shouldAbortSource
+ ? $readableStreamCancel(pipeState.source, reason)
+ : Promise.$resolve();
+
+ let promiseCapability = $newPromiseCapability(Promise);
+ let shouldWait = true;
+ let handleResolvedPromise = () => {
+ if (shouldWait) {
+ shouldWait = false;
+ return;
+ }
+ promiseCapability.$resolve.$call();
+ };
+ let handleRejectedPromise = e => {
+ promiseCapability.$reject.$call(undefined, e);
+ };
+ promiseDestination.$then(handleResolvedPromise, handleRejectedPromise);
+ promiseSource.$then(handleResolvedPromise, handleRejectedPromise);
+ return promiseCapability.$promise;
+ },
+ reason,
+ );
+ };
+ if ($whenSignalAborted(signal, algorithm)) return pipeState.promiseCapability.$promise;
+ }
+
+ $pipeToErrorsMustBePropagatedForward(pipeState);
+ $pipeToErrorsMustBePropagatedBackward(pipeState);
+ $pipeToClosingMustBePropagatedForward(pipeState);
+ $pipeToClosingMustBePropagatedBackward(pipeState);
+
+ $pipeToLoop(pipeState);
+
+ return pipeState.promiseCapability.$promise;
+}
+
+export function pipeToLoop(pipeState) {
+ if (pipeState.shuttingDown) return;
+
+ $pipeToDoReadWrite(pipeState).$then(result => {
+ if (result) $pipeToLoop(pipeState);
+ });
+}
+
+export function pipeToDoReadWrite(pipeState) {
+ $assert(!pipeState.shuttingDown);
+
+ pipeState.pendingReadPromiseCapability = $newPromiseCapability(Promise);
+ $getByIdDirectPrivate(pipeState.writer, "readyPromise").$promise.$then(
+ () => {
+ if (pipeState.shuttingDown) {
+ pipeState.pendingReadPromiseCapability.$resolve.$call(undefined, false);
+ return;
+ }
+
+ $readableStreamDefaultReaderRead(pipeState.reader).$then(
+ result => {
+ const canWrite = !result.done && $getByIdDirectPrivate(pipeState.writer, "stream") !== undefined;
+ pipeState.pendingReadPromiseCapability.$resolve.$call(undefined, canWrite);
+ if (!canWrite) return;
+
+ pipeState.pendingWritePromise = $writableStreamDefaultWriterWrite(pipeState.writer, result.value);
+ },
+ e => {
+ pipeState.pendingReadPromiseCapability.$resolve.$call(undefined, false);
+ },
+ );
+ },
+ e => {
+ pipeState.pendingReadPromiseCapability.$resolve.$call(undefined, false);
+ },
+ );
+ return pipeState.pendingReadPromiseCapability.$promise;
+}
+
+export function pipeToErrorsMustBePropagatedForward(pipeState) {
+ const action = () => {
+ pipeState.pendingReadPromiseCapability.$resolve.$call(undefined, false);
+ const error = $getByIdDirectPrivate(pipeState.source, "storedError");
+ if (!pipeState.preventAbort) {
+ $pipeToShutdownWithAction(pipeState, () => $writableStreamAbort(pipeState.destination, error), error);
+ return;
+ }
+ $pipeToShutdown(pipeState, error);
+ };
+
+ if ($getByIdDirectPrivate(pipeState.source, "state") === $streamErrored) {
+ action();
+ return;
+ }
+
+ $getByIdDirectPrivate(pipeState.reader, "closedPromiseCapability").$promise.$then(undefined, action);
+}
+
+export function pipeToErrorsMustBePropagatedBackward(pipeState) {
+ const action = () => {
+ const error = $getByIdDirectPrivate(pipeState.destination, "storedError");
+ if (!pipeState.preventCancel) {
+ $pipeToShutdownWithAction(pipeState, () => $readableStreamCancel(pipeState.source, error), error);
+ return;
+ }
+ $pipeToShutdown(pipeState, error);
+ };
+ if ($getByIdDirectPrivate(pipeState.destination, "state") === "errored") {
+ action();
+ return;
+ }
+ $getByIdDirectPrivate(pipeState.writer, "closedPromise").$promise.$then(undefined, action);
+}
+
+export function pipeToClosingMustBePropagatedForward(pipeState) {
+ const action = () => {
+ pipeState.pendingReadPromiseCapability.$resolve.$call(undefined, false);
+ // const error = $getByIdDirectPrivate(pipeState.source, "storedError");
+ if (!pipeState.preventClose) {
+ $pipeToShutdownWithAction(pipeState, () =>
+ $writableStreamDefaultWriterCloseWithErrorPropagation(pipeState.writer),
+ );
+ return;
+ }
+ $pipeToShutdown(pipeState);
+ };
+ if ($getByIdDirectPrivate(pipeState.source, "state") === $streamClosed) {
+ action();
+ return;
+ }
+ $getByIdDirectPrivate(pipeState.reader, "closedPromiseCapability").$promise.$then(action, undefined);
+}
+
+export function pipeToClosingMustBePropagatedBackward(pipeState) {
+ if (
+ !$writableStreamCloseQueuedOrInFlight(pipeState.destination) &&
+ $getByIdDirectPrivate(pipeState.destination, "state") !== "closed"
+ )
+ return;
+
+ // $assert no chunks have been read/written
+
+ const error = new TypeError("closing is propagated backward");
+ if (!pipeState.preventCancel) {
+ $pipeToShutdownWithAction(pipeState, () => $readableStreamCancel(pipeState.source, error), error);
+ return;
+ }
+ $pipeToShutdown(pipeState, error);
+}
+
+export function pipeToShutdownWithAction(pipeState, action) {
+ if (pipeState.shuttingDown) return;
+
+ pipeState.shuttingDown = true;
+
+ const hasError = arguments.length > 2;
+ const error = arguments[2];
+ const finalize = () => {
+ const promise = action();
+ promise.$then(
+ () => {
+ if (hasError) $pipeToFinalize(pipeState, error);
+ else $pipeToFinalize(pipeState);
+ },
+ e => {
+ $pipeToFinalize(pipeState, e);
+ },
+ );
+ };
+
+ if (
+ $getByIdDirectPrivate(pipeState.destination, "state") === "writable" &&
+ !$writableStreamCloseQueuedOrInFlight(pipeState.destination)
+ ) {
+ pipeState.pendingReadPromiseCapability.$promise.$then(
+ () => {
+ pipeState.pendingWritePromise.$then(finalize, finalize);
+ },
+ e => $pipeToFinalize(pipeState, e),
+ );
+ return;
+ }
+
+ finalize();
+}
+
+export function pipeToShutdown(pipeState) {
+ if (pipeState.shuttingDown) return;
+
+ pipeState.shuttingDown = true;
+
+ const hasError = arguments.length > 1;
+ const error = arguments[1];
+ const finalize = () => {
+ if (hasError) $pipeToFinalize(pipeState, error);
+ else $pipeToFinalize(pipeState);
+ };
+
+ if (
+ $getByIdDirectPrivate(pipeState.destination, "state") === "writable" &&
+ !$writableStreamCloseQueuedOrInFlight(pipeState.destination)
+ ) {
+ pipeState.pendingReadPromiseCapability.$promise.$then(
+ () => {
+ pipeState.pendingWritePromise.$then(finalize, finalize);
+ },
+ e => $pipeToFinalize(pipeState, e),
+ );
+ return;
+ }
+ finalize();
+}
+
+export function pipeToFinalize(pipeState) {
+ $writableStreamDefaultWriterRelease(pipeState.writer);
+ $readableStreamReaderGenericRelease(pipeState.reader);
+
+ // Instead of removing the abort algorithm as per spec, we make it a no-op which is equivalent.
+ pipeState.finalized = true;
+
+ if (arguments.length > 1) pipeState.promiseCapability.$reject.$call(undefined, arguments[1]);
+ else pipeState.promiseCapability.$resolve.$call();
+}
+
+export function readableStreamTee(stream, shouldClone) {
+ $assert($isReadableStream(stream));
+ $assert(typeof shouldClone === "boolean");
+
+ var start_ = $getByIdDirectPrivate(stream, "start");
+ if (start_) {
+ $putByIdDirectPrivate(stream, "start", undefined);
+ start_();
+ }
+
+ const reader = new $ReadableStreamDefaultReader(stream);
+
+ const teeState = {
+ closedOrErrored: false,
+ canceled1: false,
+ canceled2: false,
+ reason1: undefined,
+ reason2: undefined,
+ };
+
+ teeState.cancelPromiseCapability = $newPromiseCapability(Promise);
+
+ const pullFunction = $readableStreamTeePullFunction(teeState, reader, shouldClone);
+
+ const branch1Source = {};
+ $putByIdDirectPrivate(branch1Source, "pull", pullFunction);
+ $putByIdDirectPrivate(branch1Source, "cancel", $readableStreamTeeBranch1CancelFunction(teeState, stream));
+
+ const branch2Source = {};
+ $putByIdDirectPrivate(branch2Source, "pull", pullFunction);
+ $putByIdDirectPrivate(branch2Source, "cancel", $readableStreamTeeBranch2CancelFunction(teeState, stream));
+
+ const branch1 = new $ReadableStream(branch1Source);
+ const branch2 = new $ReadableStream(branch2Source);
+
+ $getByIdDirectPrivate(reader, "closedPromiseCapability").$promise.$then(undefined, function (e) {
+ if (teeState.closedOrErrored) return;
+ $readableStreamDefaultControllerError(branch1.$readableStreamController, e);
+ $readableStreamDefaultControllerError(branch2.$readableStreamController, e);
+ teeState.closedOrErrored = true;
+ if (!teeState.canceled1 || !teeState.canceled2) teeState.cancelPromiseCapability.$resolve.$call();
+ });
+
+ // Additional fields compared to the spec, as they are needed within pull/cancel functions.
+ teeState.branch1 = branch1;
+ teeState.branch2 = branch2;
+
+ return [branch1, branch2];
+}
+
+export function readableStreamTeePullFunction(teeState, reader, shouldClone) {
+ return function () {
+ Promise.prototype.$then.$call($readableStreamDefaultReaderRead(reader), function (result) {
+ $assert($isObject(result));
+ $assert(typeof result.done === "boolean");
+ if (result.done && !teeState.closedOrErrored) {
+ if (!teeState.canceled1) $readableStreamDefaultControllerClose(teeState.branch1.$readableStreamController);
+ if (!teeState.canceled2) $readableStreamDefaultControllerClose(teeState.branch2.$readableStreamController);
+ teeState.closedOrErrored = true;
+ if (!teeState.canceled1 || !teeState.canceled2) teeState.cancelPromiseCapability.$resolve.$call();
+ }
+ if (teeState.closedOrErrored) return;
+ if (!teeState.canceled1)
+ $readableStreamDefaultControllerEnqueue(teeState.branch1.$readableStreamController, result.value);
+ if (!teeState.canceled2)
+ $readableStreamDefaultControllerEnqueue(
+ teeState.branch2.$readableStreamController,
+ shouldClone ? $structuredCloneForStream(result.value) : result.value,
+ );
+ });
+ };
+}
+
+export function readableStreamTeeBranch1CancelFunction(teeState, stream) {
+ return function (r) {
+ teeState.canceled1 = true;
+ teeState.reason1 = r;
+ if (teeState.canceled2) {
+ $readableStreamCancel(stream, [teeState.reason1, teeState.reason2]).$then(
+ teeState.cancelPromiseCapability.$resolve,
+ teeState.cancelPromiseCapability.$reject,
+ );
+ }
+ return teeState.cancelPromiseCapability.$promise;
+ };
+}
+
+export function readableStreamTeeBranch2CancelFunction(teeState, stream) {
+ return function (r) {
+ teeState.canceled2 = true;
+ teeState.reason2 = r;
+ if (teeState.canceled1) {
+ $readableStreamCancel(stream, [teeState.reason1, teeState.reason2]).$then(
+ teeState.cancelPromiseCapability.$resolve,
+ teeState.cancelPromiseCapability.$reject,
+ );
+ }
+ return teeState.cancelPromiseCapability.$promise;
+ };
+}
+
+export function isReadableStream(stream) {
+ // Spec tells to return true only if stream has a readableStreamController internal slot.
+ // However, since it is a private slot, it cannot be checked using hasOwnProperty().
+ // Therefore, readableStreamController is initialized with null value.
+ return $isObject(stream) && $getByIdDirectPrivate(stream, "readableStreamController") !== undefined;
+}
+
+export function isReadableStreamDefaultReader(reader) {
+ // Spec tells to return true only if reader has a readRequests internal slot.
+ // However, since it is a private slot, it cannot be checked using hasOwnProperty().
+ // Since readRequests is initialized with an empty array, the following test is ok.
+ return $isObject(reader) && !!$getByIdDirectPrivate(reader, "readRequests");
+}
+
+export function isReadableStreamDefaultController(controller) {
+ // Spec tells to return true only if controller has an underlyingSource internal slot.
+ // However, since it is a private slot, it cannot be checked using hasOwnProperty().
+ // underlyingSource is obtained in ReadableStream constructor: if undefined, it is set
+ // to an empty object. Therefore, following test is ok.
+ return $isObject(controller) && !!$getByIdDirectPrivate(controller, "underlyingSource");
+}
+
+export function readDirectStream(stream, sink, underlyingSource) {
+ $putByIdDirectPrivate(stream, "underlyingSource", undefined);
+ $putByIdDirectPrivate(stream, "start", undefined);
+
+ function close(stream, reason) {
+ if (reason && underlyingSource?.cancel) {
+ try {
+ var prom = underlyingSource.cancel(reason);
+ $markPromiseAsHandled(prom);
+ } catch (e) {}
+
+ underlyingSource = undefined;
+ }
+
+ if (stream) {
+ $putByIdDirectPrivate(stream, "readableStreamController", undefined);
+ $putByIdDirectPrivate(stream, "reader", undefined);
+ if (reason) {
+ $putByIdDirectPrivate(stream, "state", $streamErrored);
+ $putByIdDirectPrivate(stream, "storedError", reason);
+ } else {
+ $putByIdDirectPrivate(stream, "state", $streamClosed);
+ }
+ stream = undefined;
+ }
+ }
+
+ if (!underlyingSource.pull) {
+ close();
+ return;
+ }
+
+ if (!$isCallable(underlyingSource.pull)) {
+ close();
+ $throwTypeError("pull is not a function");
+ return;
+ }
+
+ $putByIdDirectPrivate(stream, "readableStreamController", sink);
+ const highWaterMark = $getByIdDirectPrivate(stream, "highWaterMark");
+
+ sink.start({
+ highWaterMark: !highWaterMark || highWaterMark < 64 ? 64 : highWaterMark,
+ });
+
+ $startDirectStream.$call(sink, stream, underlyingSource.pull, close);
+ $putByIdDirectPrivate(stream, "reader", {});
+
+ var maybePromise = underlyingSource.pull(sink);
+ sink = undefined;
+ if (maybePromise && $isPromise(maybePromise)) {
+ return maybePromise.$then(() => {});
+ }
+}
+
+$linkTimeConstant;
+export function assignToStream(stream, sink) {
+ // The stream is either a direct stream or a "default" JS stream
+ var underlyingSource = $getByIdDirectPrivate(stream, "underlyingSource");
+
+ // we know it's a direct stream when $underlyingSource is set
+ if (underlyingSource) {
+ try {
+ return $readDirectStream(stream, sink, underlyingSource);
+ } catch (e) {
+ throw e;
+ } finally {
+ underlyingSource = undefined;
+ stream = undefined;
+ sink = undefined;
+ }
+ }
+
+ return $readStreamIntoSink(stream, sink, true);
+}
+
+export async function readStreamIntoSink(stream, sink, isNative) {
+ var didClose = false;
+ var didThrow = false;
+ try {
+ var reader = stream.getReader();
+ var many = reader.readMany();
+ if (many && $isPromise(many)) {
+ many = await many;
+ }
+ if (many.done) {
+ didClose = true;
+ return sink.end();
+ }
+ var wroteCount = many.value.length;
+ const highWaterMark = $getByIdDirectPrivate(stream, "highWaterMark");
+ if (isNative)
+ $startDirectStream.$call(sink, stream, undefined, () => !didThrow && $markPromiseAsHandled(stream.cancel()));
+
+ sink.start({ highWaterMark: highWaterMark || 0 });
+
+ for (var i = 0, values = many.value, length = many.value.length; i < length; i++) {
+ sink.write(values[i]);
+ }
+
+ var streamState = $getByIdDirectPrivate(stream, "state");
+ if (streamState === $streamClosed) {
+ didClose = true;
+ return sink.end();
+ }
+
+ while (true) {
+ var { value, done } = await reader.read();
+ if (done) {
+ didClose = true;
+ return sink.end();
+ }
+
+ sink.write(value);
+ }
+ } catch (e) {
+ didThrow = true;
+
+ try {
+ reader = undefined;
+ const prom = stream.cancel(e);
+ $markPromiseAsHandled(prom);
+ } catch (j) {}
+
+ if (sink && !didClose) {
+ didClose = true;
+ try {
+ sink.close(e);
+ } catch (j) {
+ throw new globalThis.AggregateError([e, j]);
+ }
+ }
+
+ throw e;
+ } finally {
+ if (reader) {
+ try {
+ reader.releaseLock();
+ } catch (e) {}
+ reader = undefined;
+ }
+ sink = undefined;
+ var streamState = $getByIdDirectPrivate(stream, "state");
+ if (stream) {
+ // make it easy for this to be GC'd
+ // but don't do property transitions
+ var readableStreamController = $getByIdDirectPrivate(stream, "readableStreamController");
+ if (readableStreamController) {
+ if ($getByIdDirectPrivate(readableStreamController, "underlyingSource"))
+ $putByIdDirectPrivate(readableStreamController, "underlyingSource", undefined);
+ if ($getByIdDirectPrivate(readableStreamController, "controlledReadableStream"))
+ $putByIdDirectPrivate(readableStreamController, "controlledReadableStream", undefined);
+
+ $putByIdDirectPrivate(stream, "readableStreamController", null);
+ if ($getByIdDirectPrivate(stream, "underlyingSource"))
+ $putByIdDirectPrivate(stream, "underlyingSource", undefined);
+ readableStreamController = undefined;
+ }
+
+ if (!didThrow && streamState !== $streamClosed && streamState !== $streamErrored) {
+ $readableStreamClose(stream);
+ }
+ stream = undefined;
+ }
+ }
+}
+
+export function handleDirectStreamError(e) {
+ var controller = this;
+ var sink = controller.$sink;
+ if (sink) {
+ $putByIdDirectPrivate(controller, "sink", undefined);
+ try {
+ sink.close(e);
+ } catch (f) {}
+ }
+
+ this.error = this.flush = this.write = this.close = this.end = $onReadableStreamDirectControllerClosed;
+
+ if (typeof this.$underlyingSource.close === "function") {
+ try {
+ this.$underlyingSource.close.$call(this.$underlyingSource, e);
+ } catch (e) {}
+ }
+
+ try {
+ var pend = controller._pendingRead;
+ if (pend) {
+ controller._pendingRead = undefined;
+ $rejectPromise(pend, e);
+ }
+ } catch (f) {}
+ var stream = controller.$controlledReadableStream;
+ if (stream) $readableStreamError(stream, e);
+}
+
+export function handleDirectStreamErrorReject(e) {
+ $handleDirectStreamError.$call(this, e);
+ return Promise.$reject(e);
+}
+
+export function onPullDirectStream(controller) {
+ var stream = controller.$controlledReadableStream;
+ if (!stream || $getByIdDirectPrivate(stream, "state") !== $streamReadable) return;
+
+ // pull is in progress
+ // this is a recursive call
+ // ignore it
+ if (controller._deferClose === -1) {
+ return;
+ }
+
+ controller._deferClose = -1;
+ controller._deferFlush = -1;
+ var deferClose;
+ var deferFlush;
+
+ // Direct streams allow $pull to be called multiple times, unlike the spec.
+ // Backpressure is handled by the destination, not by the underlying source.
+ // In this case, we rely on the heuristic that repeatedly draining in the same tick
+ // is bad for performance
+ // this code is only run when consuming a direct stream from JS
+ // without the HTTP server or anything else
+ try {
+ var result = controller.$underlyingSource.pull(controller);
+
+ if (result && $isPromise(result)) {
+ if (controller._handleError === undefined) {
+ controller._handleError = $handleDirectStreamErrorReject.bind(controller);
+ }
+
+ Promise.prototype.catch.$call(result, controller._handleError);
+ }
+ } catch (e) {
+ return $handleDirectStreamErrorReject.$call(controller, e);
+ } finally {
+ deferClose = controller._deferClose;
+ deferFlush = controller._deferFlush;
+ controller._deferFlush = controller._deferClose = 0;
+ }
+
+ var promiseToReturn;
+
+ if (controller._pendingRead === undefined) {
+ controller._pendingRead = promiseToReturn = $newPromise();
+ } else {
+ promiseToReturn = $readableStreamAddReadRequest(stream);
+ }
+
+ // they called close during $pull()
+ // we delay that
+ if (deferClose === 1) {
+ var reason = controller._deferCloseReason;
+ controller._deferCloseReason = undefined;
+ $onCloseDirectStream.$call(controller, reason);
+ return promiseToReturn;
+ }
+
+ // not done, but they called flush()
+ if (deferFlush === 1) {
+ $onFlushDirectStream.$call(controller);
+ }
+
+ return promiseToReturn;
+}
+
+export function noopDoneFunction() {
+ return Promise.$resolve({ value: undefined, done: true });
+}
+
+export function onReadableStreamDirectControllerClosed(reason) {
+ $throwTypeError("ReadableStreamDirectController is now closed");
+}
+
+export function onCloseDirectStream(reason) {
+ var stream = this.$controlledReadableStream;
+ if (!stream || $getByIdDirectPrivate(stream, "state") !== $streamReadable) return;
+
+ if (this._deferClose !== 0) {
+ this._deferClose = 1;
+ this._deferCloseReason = reason;
+ return;
+ }
+
+ $putByIdDirectPrivate(stream, "state", $streamClosing);
+ if (typeof this.$underlyingSource.close === "function") {
+ try {
+ this.$underlyingSource.close.$call(this.$underlyingSource, reason);
+ } catch (e) {}
+ }
+
+ var flushed;
+ try {
+ flushed = this.$sink.end();
+ $putByIdDirectPrivate(this, "sink", undefined);
+ } catch (e) {
+ if (this._pendingRead) {
+ var read = this._pendingRead;
+ this._pendingRead = undefined;
+ $rejectPromise(read, e);
+ }
+ $readableStreamError(stream, e);
+ return;
+ }
+
+ this.error = this.flush = this.write = this.close = this.end = $onReadableStreamDirectControllerClosed;
+
+ var reader = $getByIdDirectPrivate(stream, "reader");
+
+ if (reader && $isReadableStreamDefaultReader(reader)) {
+ var _pendingRead = this._pendingRead;
+ if (_pendingRead && $isPromise(_pendingRead) && flushed?.byteLength) {
+ this._pendingRead = undefined;
+ $fulfillPromise(_pendingRead, { value: flushed, done: false });
+ $readableStreamClose(stream);
+ return;
+ }
+ }
+
+ if (flushed?.byteLength) {
+ var requests = $getByIdDirectPrivate(reader, "readRequests");
+ if (requests?.isNotEmpty()) {
+ $readableStreamFulfillReadRequest(stream, flushed, false);
+ $readableStreamClose(stream);
+ return;
+ }
+
+ $putByIdDirectPrivate(stream, "state", $streamReadable);
+ this.$pull = () => {
+ var thisResult = $createFulfilledPromise({
+ value: flushed,
+ done: false,
+ });
+ flushed = undefined;
+ $readableStreamClose(stream);
+ stream = undefined;
+ return thisResult;
+ };
+ } else if (this._pendingRead) {
+ var read = this._pendingRead;
+ this._pendingRead = undefined;
+ $putByIdDirectPrivate(this, "pull", $noopDoneFunction);
+ $fulfillPromise(read, { value: undefined, done: true });
+ }
+
+ $readableStreamClose(stream);
+}
+
+export function onFlushDirectStream() {
+ var stream = this.$controlledReadableStream;
+ var reader = $getByIdDirectPrivate(stream, "reader");
+ if (!reader || !$isReadableStreamDefaultReader(reader)) {
+ return;
+ }
+
+ var _pendingRead = this._pendingRead;
+ this._pendingRead = undefined;
+ if (_pendingRead && $isPromise(_pendingRead)) {
+ var flushed = this.$sink.flush();
+ if (flushed?.byteLength) {
+ this._pendingRead = $getByIdDirectPrivate(stream, "readRequests")?.shift();
+ $fulfillPromise(_pendingRead, { value: flushed, done: false });
+ } else {
+ this._pendingRead = _pendingRead;
+ }
+ } else if ($getByIdDirectPrivate(stream, "readRequests")?.isNotEmpty()) {
+ var flushed = this.$sink.flush();
+ if (flushed?.byteLength) {
+ $readableStreamFulfillReadRequest(stream, flushed, false);
+ }
+ } else if (this._deferFlush === -1) {
+ this._deferFlush = 1;
+ }
+}
+
+export function createTextStream(highWaterMark) {
+ var sink;
+ var array = [];
+ var hasString = false;
+ var hasBuffer = false;
+ var rope = "";
+ var estimatedLength = $toLength(0);
+ var capability = $newPromiseCapability(Promise);
+ var calledDone = false;
+
+ sink = {
+ start() {},
+ write(chunk) {
+ if (typeof chunk === "string") {
+ var chunkLength = $toLength(chunk.length);
+ if (chunkLength > 0) {
+ rope += chunk;
+ hasString = true;
+ // TODO: utf16 byte length
+ estimatedLength += chunkLength;
+ }
+
+ return chunkLength;
+ }
+
+ if (!chunk || !($ArrayBuffer.$isView(chunk) || chunk instanceof $ArrayBuffer)) {
+ $throwTypeError("Expected text, ArrayBuffer or ArrayBufferView");
+ }
+
+ const byteLength = $toLength(chunk.byteLength);
+ if (byteLength > 0) {
+ hasBuffer = true;
+ if (rope.length > 0) {
+ $arrayPush(array, rope, chunk);
+ rope = "";
+ } else {
+ $arrayPush(array, chunk);
+ }
+ }
+ estimatedLength += byteLength;
+ return byteLength;
+ },
+
+ flush() {
+ return 0;
+ },
+
+ end() {
+ if (calledDone) {
+ return "";
+ }
+ return sink.fulfill();
+ },
+
+ fulfill() {
+ calledDone = true;
+ const result = sink.finishInternal();
+
+ $fulfillPromise(capability.$promise, result);
+ return result;
+ },
+
+ finishInternal() {
+ if (!hasString && !hasBuffer) {
+ return "";
+ }
+
+ if (hasString && !hasBuffer) {
+ return rope;
+ }
+
+ if (hasBuffer && !hasString) {
+ return new globalThis.TextDecoder().decode($Bun.concatArrayBuffers(array));
+ }
+
+ // worst case: mixed content
+
+ var arrayBufferSink = new $Bun.ArrayBufferSink();
+ arrayBufferSink.start({
+ highWaterMark: estimatedLength,
+ asUint8Array: true,
+ });
+ for (let item of array) {
+ arrayBufferSink.write(item);
+ }
+ array.length = 0;
+ if (rope.length > 0) {
+ arrayBufferSink.write(rope);
+ rope = "";
+ }
+
+ // TODO: use builtin
+ return new globalThis.TextDecoder().decode(arrayBufferSink.end());
+ },
+
+ close() {
+ try {
+ if (!calledDone) {
+ calledDone = true;
+ sink.fulfill();
+ }
+ } catch (e) {}
+ },
+ };
+
+ return [sink, capability];
+}
+
+export function initializeTextStream(underlyingSource, highWaterMark) {
+ var [sink, closingPromise] = $createTextStream(highWaterMark);
+
+ var controller = {
+ $underlyingSource: underlyingSource,
+ $pull: $onPullDirectStream,
+ $controlledReadableStream: this,
+ $sink: sink,
+ close: $onCloseDirectStream,
+ write: sink.write,
+ error: $handleDirectStreamError,
+ end: $onCloseDirectStream,
+ $close: $onCloseDirectStream,
+ flush: $onFlushDirectStream,
+ _pendingRead: undefined,
+ _deferClose: 0,
+ _deferFlush: 0,
+ _deferCloseReason: undefined,
+ _handleError: undefined,
+ };
+
+ $putByIdDirectPrivate(this, "readableStreamController", controller);
+ $putByIdDirectPrivate(this, "underlyingSource", undefined);
+ $putByIdDirectPrivate(this, "start", undefined);
+ return closingPromise;
+}
+
+export function initializeArrayStream(underlyingSource, highWaterMark) {
+ var array = [];
+ var closingPromise = $newPromiseCapability(Promise);
+ var calledDone = false;
+
+ function fulfill() {
+ calledDone = true;
+ closingPromise.$resolve.$call(undefined, array);
+ return array;
+ }
+
+ var sink = {
+ start() {},
+ write(chunk) {
+ $arrayPush(array, chunk);
+ return chunk.byteLength || chunk.length;
+ },
+
+ flush() {
+ return 0;
+ },
+
+ end() {
+ if (calledDone) {
+ return [];
+ }
+ return fulfill();
+ },
+
+ close() {
+ if (!calledDone) {
+ fulfill();
+ }
+ },
+ };
+
+ var controller = {
+ $underlyingSource: underlyingSource,
+ $pull: $onPullDirectStream,
+ $controlledReadableStream: this,
+ $sink: sink,
+ close: $onCloseDirectStream,
+ write: sink.write,
+ error: $handleDirectStreamError,
+ end: $onCloseDirectStream,
+ $close: $onCloseDirectStream,
+ flush: $onFlushDirectStream,
+ _pendingRead: undefined,
+ _deferClose: 0,
+ _deferFlush: 0,
+ _deferCloseReason: undefined,
+ _handleError: undefined,
+ };
+
+ $putByIdDirectPrivate(this, "readableStreamController", controller);
+ $putByIdDirectPrivate(this, "underlyingSource", undefined);
+ $putByIdDirectPrivate(this, "start", undefined);
+ return closingPromise;
+}
+
+export function initializeArrayBufferStream(underlyingSource, highWaterMark) {
+ // This is the fallback implementation for direct streams
+ // When we don't know what the destination type is
+ // We assume it is a Uint8Array.
+
+ var opts =
+ highWaterMark && typeof highWaterMark === "number"
+ ? { highWaterMark, stream: true, asUint8Array: true }
+ : { stream: true, asUint8Array: true };
+ var sink = new $Bun.ArrayBufferSink();
+ sink.start(opts);
+
+ var controller = {
+ $underlyingSource: underlyingSource,
+ $pull: $onPullDirectStream,
+ $controlledReadableStream: this,
+ $sink: sink,
+ close: $onCloseDirectStream,
+ write: sink.write.bind(sink),
+ error: $handleDirectStreamError,
+ end: $onCloseDirectStream,
+ $close: $onCloseDirectStream,
+ flush: $onFlushDirectStream,
+ _pendingRead: undefined,
+ _deferClose: 0,
+ _deferFlush: 0,
+ _deferCloseReason: undefined,
+ _handleError: undefined,
+ };
+
+ $putByIdDirectPrivate(this, "readableStreamController", controller);
+ $putByIdDirectPrivate(this, "underlyingSource", undefined);
+ $putByIdDirectPrivate(this, "start", undefined);
+}
+
+export function readableStreamError(stream, error) {
+ $assert($isReadableStream(stream));
+ $assert($getByIdDirectPrivate(stream, "state") === $streamReadable);
+ $putByIdDirectPrivate(stream, "state", $streamErrored);
+ $putByIdDirectPrivate(stream, "storedError", error);
+
+ const reader = $getByIdDirectPrivate(stream, "reader");
+
+ if (!reader) return;
+
+ if ($isReadableStreamDefaultReader(reader)) {
+ const requests = $getByIdDirectPrivate(reader, "readRequests");
+ $putByIdDirectPrivate(reader, "readRequests", $createFIFO());
+ for (var request = requests.shift(); request; request = requests.shift()) $rejectPromise(request, error);
+ } else {
+ $assert($isReadableStreamBYOBReader(reader));
+ const requests = $getByIdDirectPrivate(reader, "readIntoRequests");
+ $putByIdDirectPrivate(reader, "readIntoRequests", $createFIFO());
+ for (var request = requests.shift(); request; request = requests.shift()) $rejectPromise(request, error);
+ }
+
+ $getByIdDirectPrivate(reader, "closedPromiseCapability").$reject.$call(undefined, error);
+ const promise = $getByIdDirectPrivate(reader, "closedPromiseCapability").$promise;
+ $markPromiseAsHandled(promise);
+}
+
+export function readableStreamDefaultControllerShouldCallPull(controller) {
+ const stream = $getByIdDirectPrivate(controller, "controlledReadableStream");
+
+ if (!$readableStreamDefaultControllerCanCloseOrEnqueue(controller)) return false;
+ if (!($getByIdDirectPrivate(controller, "started") === 1)) return false;
+ if (
+ (!$isReadableStreamLocked(stream) ||
+ !$getByIdDirectPrivate($getByIdDirectPrivate(stream, "reader"), "readRequests")?.isNotEmpty()) &&
+ $readableStreamDefaultControllerGetDesiredSize(controller) <= 0
+ )
+ return false;
+ const desiredSize = $readableStreamDefaultControllerGetDesiredSize(controller);
+ $assert(desiredSize !== null);
+ return desiredSize > 0;
+}
+
+export function readableStreamDefaultControllerCallPullIfNeeded(controller) {
+ // FIXME: use $readableStreamDefaultControllerShouldCallPull
+ const stream = $getByIdDirectPrivate(controller, "controlledReadableStream");
+
+ if (!$readableStreamDefaultControllerCanCloseOrEnqueue(controller)) return;
+ if (!($getByIdDirectPrivate(controller, "started") === 1)) return;
+ if (
+ (!$isReadableStreamLocked(stream) ||
+ !$getByIdDirectPrivate($getByIdDirectPrivate(stream, "reader"), "readRequests")?.isNotEmpty()) &&
+ $readableStreamDefaultControllerGetDesiredSize(controller) <= 0
+ )
+ return;
+
+ if ($getByIdDirectPrivate(controller, "pulling")) {
+ $putByIdDirectPrivate(controller, "pullAgain", true);
+ return;
+ }
+
+ $assert(!$getByIdDirectPrivate(controller, "pullAgain"));
+ $putByIdDirectPrivate(controller, "pulling", true);
+
+ $getByIdDirectPrivate(controller, "pullAlgorithm")
+ .$call(undefined)
+ .$then(
+ function () {
+ $putByIdDirectPrivate(controller, "pulling", false);
+ if ($getByIdDirectPrivate(controller, "pullAgain")) {
+ $putByIdDirectPrivate(controller, "pullAgain", false);
+
+ $readableStreamDefaultControllerCallPullIfNeeded(controller);
+ }
+ },
+ function (error) {
+ $readableStreamDefaultControllerError(controller, error);
+ },
+ );
+}
+
+export function isReadableStreamLocked(stream) {
+ $assert($isReadableStream(stream));
+ return !!$getByIdDirectPrivate(stream, "reader");
+}
+
+export function readableStreamDefaultControllerGetDesiredSize(controller) {
+ const stream = $getByIdDirectPrivate(controller, "controlledReadableStream");
+ const state = $getByIdDirectPrivate(stream, "state");
+
+ if (state === $streamErrored) return null;
+ if (state === $streamClosed) return 0;
+
+ return $getByIdDirectPrivate(controller, "strategy").highWaterMark - $getByIdDirectPrivate(controller, "queue").size;
+}
+
+export function readableStreamReaderGenericCancel(reader, reason) {
+ const stream = $getByIdDirectPrivate(reader, "ownerReadableStream");
+ $assert(!!stream);
+ return $readableStreamCancel(stream, reason);
+}
+
+export function readableStreamCancel(stream, reason) {
+ $putByIdDirectPrivate(stream, "disturbed", true);
+ const state = $getByIdDirectPrivate(stream, "state");
+ if (state === $streamClosed) return Promise.$resolve();
+ if (state === $streamErrored) return Promise.$reject($getByIdDirectPrivate(stream, "storedError"));
+ $readableStreamClose(stream);
+
+ var controller = $getByIdDirectPrivate(stream, "readableStreamController");
+ var cancel = controller.$cancel;
+ if (cancel) {
+ return cancel(controller, reason).$then(function () {});
+ }
+
+ var close = controller.close;
+ if (close) {
+ return Promise.$resolve(controller.close(reason));
+ }
+
+ $throwTypeError("ReadableStreamController has no cancel or close method");
+}
+
+export function readableStreamDefaultControllerCancel(controller, reason) {
+ $putByIdDirectPrivate(controller, "queue", $newQueue());
+ return $getByIdDirectPrivate(controller, "cancelAlgorithm").$call(undefined, reason);
+}
+
+export function readableStreamDefaultControllerPull(controller) {
+ var queue = $getByIdDirectPrivate(controller, "queue");
+ if (queue.content.isNotEmpty()) {
+ const chunk = $dequeueValue(queue);
+ if ($getByIdDirectPrivate(controller, "closeRequested") && queue.content.isEmpty())
+ $readableStreamClose($getByIdDirectPrivate(controller, "controlledReadableStream"));
+ else $readableStreamDefaultControllerCallPullIfNeeded(controller);
+
+ return $createFulfilledPromise({ value: chunk, done: false });
+ }
+ const pendingPromise = $readableStreamAddReadRequest($getByIdDirectPrivate(controller, "controlledReadableStream"));
+ $readableStreamDefaultControllerCallPullIfNeeded(controller);
+ return pendingPromise;
+}
+
+export function readableStreamDefaultControllerClose(controller) {
+ $assert($readableStreamDefaultControllerCanCloseOrEnqueue(controller));
+ $putByIdDirectPrivate(controller, "closeRequested", true);
+ if ($getByIdDirectPrivate(controller, "queue")?.content?.isEmpty())
+ $readableStreamClose($getByIdDirectPrivate(controller, "controlledReadableStream"));
+}
+
+export function readableStreamClose(stream) {
+ $assert($getByIdDirectPrivate(stream, "state") === $streamReadable);
+ $putByIdDirectPrivate(stream, "state", $streamClosed);
+ if (!$getByIdDirectPrivate(stream, "reader")) return;
+
+ if ($isReadableStreamDefaultReader($getByIdDirectPrivate(stream, "reader"))) {
+ const requests = $getByIdDirectPrivate($getByIdDirectPrivate(stream, "reader"), "readRequests");
+ if (requests.isNotEmpty()) {
+ $putByIdDirectPrivate($getByIdDirectPrivate(stream, "reader"), "readRequests", $createFIFO());
+
+ for (var request = requests.shift(); request; request = requests.shift())
+ $fulfillPromise(request, { value: undefined, done: true });
+ }
+ }
+
+ $getByIdDirectPrivate($getByIdDirectPrivate(stream, "reader"), "closedPromiseCapability").$resolve.$call();
+}
+
+export function readableStreamFulfillReadRequest(stream, chunk, done) {
+ const readRequest = $getByIdDirectPrivate($getByIdDirectPrivate(stream, "reader"), "readRequests").shift();
+ $fulfillPromise(readRequest, { value: chunk, done: done });
+}
+
+export function readableStreamDefaultControllerEnqueue(controller, chunk) {
+ const stream = $getByIdDirectPrivate(controller, "controlledReadableStream");
+ // this is checked by callers
+ $assert($readableStreamDefaultControllerCanCloseOrEnqueue(controller));
+
+ if (
+ $isReadableStreamLocked(stream) &&
+ $getByIdDirectPrivate($getByIdDirectPrivate(stream, "reader"), "readRequests")?.isNotEmpty()
+ ) {
+ $readableStreamFulfillReadRequest(stream, chunk, false);
+ $readableStreamDefaultControllerCallPullIfNeeded(controller);
+ return;
+ }
+
+ try {
+ let chunkSize = 1;
+ if ($getByIdDirectPrivate(controller, "strategy").size !== undefined)
+ chunkSize = $getByIdDirectPrivate(controller, "strategy").size(chunk);
+ $enqueueValueWithSize($getByIdDirectPrivate(controller, "queue"), chunk, chunkSize);
+ } catch (error) {
+ $readableStreamDefaultControllerError(controller, error);
+ throw error;
+ }
+ $readableStreamDefaultControllerCallPullIfNeeded(controller);
+}
+
+export function readableStreamDefaultReaderRead(reader) {
+ const stream = $getByIdDirectPrivate(reader, "ownerReadableStream");
+ $assert(!!stream);
+ const state = $getByIdDirectPrivate(stream, "state");
+
+ $putByIdDirectPrivate(stream, "disturbed", true);
+ if (state === $streamClosed) return $createFulfilledPromise({ value: undefined, done: true });
+ if (state === $streamErrored) return Promise.$reject($getByIdDirectPrivate(stream, "storedError"));
+ $assert(state === $streamReadable);
+
+ return $getByIdDirectPrivate(stream, "readableStreamController").$pull(
+ $getByIdDirectPrivate(stream, "readableStreamController"),
+ );
+}
+
+export function readableStreamAddReadRequest(stream) {
+ $assert($isReadableStreamDefaultReader($getByIdDirectPrivate(stream, "reader")));
+ $assert($getByIdDirectPrivate(stream, "state") == $streamReadable);
+
+ const readRequest = $newPromise();
+
+ $getByIdDirectPrivate($getByIdDirectPrivate(stream, "reader"), "readRequests").push(readRequest);
+
+ return readRequest;
+}
+
+export function isReadableStreamDisturbed(stream) {
+ $assert($isReadableStream(stream));
+ return $getByIdDirectPrivate(stream, "disturbed");
+}
+
+export function readableStreamReaderGenericRelease(reader) {
+ $assert(!!$getByIdDirectPrivate(reader, "ownerReadableStream"));
+ $assert($getByIdDirectPrivate($getByIdDirectPrivate(reader, "ownerReadableStream"), "reader") === reader);
+
+ if ($getByIdDirectPrivate($getByIdDirectPrivate(reader, "ownerReadableStream"), "state") === $streamReadable)
+ $getByIdDirectPrivate(reader, "closedPromiseCapability").$reject.$call(
+ undefined,
+ $makeTypeError("releasing lock of reader whose stream is still in readable state"),
+ );
+ else
+ $putByIdDirectPrivate(reader, "closedPromiseCapability", {
+ $promise: $newHandledRejectedPromise($makeTypeError("reader released lock")),
+ });
+
+ const promise = $getByIdDirectPrivate(reader, "closedPromiseCapability").$promise;
+ $markPromiseAsHandled(promise);
+ $putByIdDirectPrivate($getByIdDirectPrivate(reader, "ownerReadableStream"), "reader", undefined);
+ $putByIdDirectPrivate(reader, "ownerReadableStream", undefined);
+}
+
+export function readableStreamDefaultControllerCanCloseOrEnqueue(controller) {
+ return (
+ !$getByIdDirectPrivate(controller, "closeRequested") &&
+ $getByIdDirectPrivate($getByIdDirectPrivate(controller, "controlledReadableStream"), "state") === $streamReadable
+ );
+}
+
+export function lazyLoadStream(stream, autoAllocateChunkSize) {
+ var nativeType = $getByIdDirectPrivate(stream, "bunNativeType");
+ var nativePtr = $getByIdDirectPrivate(stream, "bunNativePtr");
+ var Prototype = $lazyStreamPrototypeMap.$get(nativeType);
+ if (Prototype === undefined) {
+ var [pull, start, cancel, setClose, deinit, setRefOrUnref, drain] = $lazyLoad(nativeType);
+ var closer = [false];
+ var handleResult;
+ function handleNativeReadableStreamPromiseResult(val) {
+ var { c, v } = this;
+ this.c = undefined;
+ this.v = undefined;
+ handleResult(val, c, v);
+ }
+
+ function callClose(controller) {
+ try {
+ controller.close();
+ } catch (e) {
+ globalThis.reportError(e);
+ }
+ }
+
+ handleResult = function handleResult(result, controller, view) {
+ if (result && $isPromise(result)) {
+ return result.then(
+ handleNativeReadableStreamPromiseResult.bind({
+ c: controller,
+ v: view,
+ }),
+ err => controller.error(err),
+ );
+ } else if (typeof result === "number") {
+ if (view && view.byteLength === result && view.buffer === controller.byobRequest?.view?.buffer) {
+ controller.byobRequest.respondWithNewView(view);
+ } else {
+ controller.byobRequest.respond(result);
+ }
+ } else if (result.constructor === $Uint8Array) {
+ controller.enqueue(result);
+ }
+
+ if (closer[0] || result === false) {
+ $enqueueJob(callClose, controller);
+ closer[0] = false;
+ }
+ };
+
+ function createResult(tag, controller, view, closer) {
+ closer[0] = false;
+
+ var result;
+ try {
+ result = pull(tag, view, closer);
+ } catch (err) {
+ return controller.error(err);
+ }
+
+ return handleResult(result, controller, view);
+ }
+
+ const registry = deinit ? new FinalizationRegistry(deinit) : null;
+ Prototype = class NativeReadableStreamSource {
+ constructor(tag, autoAllocateChunkSize, drainValue) {
+ this.#tag = tag;
+ this.#cancellationToken = {};
+ this.pull = this.#pull.bind(this);
+ this.cancel = this.#cancel.bind(this);
+ this.autoAllocateChunkSize = autoAllocateChunkSize;
+
+ if (drainValue !== undefined) {
+ this.start = controller => {
+ controller.enqueue(drainValue);
+ };
+ }
+
+ if (registry) {
+ registry.register(this, tag, this.#cancellationToken);
+ }
+ }
+
+ #cancellationToken;
+ pull;
+ cancel;
+ start;
+
+ #tag;
+ type = "bytes";
+ autoAllocateChunkSize = 0;
+
+ static startSync = start;
+
+ #pull(controller) {
+ var tag = this.#tag;
+
+ if (!tag) {
+ controller.close();
+ return;
+ }
+
+ createResult(tag, controller, controller.byobRequest.view, closer);
+ }
+
+ #cancel(reason) {
+ var tag = this.#tag;
+
+ registry && registry.unregister(this.#cancellationToken);
+ setRefOrUnref && setRefOrUnref(tag, false);
+ cancel(tag, reason);
+ }
+ static deinit = deinit;
+ static drain = drain;
+ };
+ $lazyStreamPrototypeMap.$set(nativeType, Prototype);
+ }
+
+ const chunkSize = Prototype.startSync(nativePtr, autoAllocateChunkSize);
+ var drainValue;
+ const { drain: drainFn, deinit: deinitFn } = Prototype;
+ if (drainFn) {
+ drainValue = drainFn(nativePtr);
+ }
+
+ // empty file, no need for native back-and-forth on this
+ if (chunkSize === 0) {
+ deinit && nativePtr && $enqueueJob(deinit, nativePtr);
+
+ if ((drainValue?.byteLength ?? 0) > 0) {
+ return {
+ start(controller) {
+ controller.enqueue(drainValue);
+ controller.close();
+ },
+ type: "bytes",
+ };
+ }
+
+ return {
+ start(controller) {
+ controller.close();
+ },
+ type: "bytes",
+ };
+ }
+
+ return new Prototype(nativePtr, chunkSize, drainValue);
+}
+
+export function readableStreamIntoArray(stream) {
+ var reader = stream.getReader();
+ var manyResult = reader.readMany();
+
+ async function processManyResult(result) {
+ if (result.done) {
+ return [];
+ }
+
+ var chunks = result.value || [];
+
+ while (true) {
+ var thisResult = await reader.read();
+ if (thisResult.done) {
+ break;
+ }
+ chunks = chunks.concat(thisResult.value);
+ }
+
+ return chunks;
+ }
+
+ if (manyResult && $isPromise(manyResult)) {
+ return manyResult.$then(processManyResult);
+ }
+
+ return processManyResult(manyResult);
+}
+
+export function readableStreamIntoText(stream) {
+ const [textStream, closer] = $createTextStream($getByIdDirectPrivate(stream, "highWaterMark"));
+ const prom = $readStreamIntoSink(stream, textStream, false);
+ if (prom && $isPromise(prom)) {
+ return Promise.$resolve(prom).$then(closer.$promise);
+ }
+ return closer.$promise;
+}
+
+export function readableStreamToArrayBufferDirect(stream, underlyingSource) {
+ var sink = new $Bun.ArrayBufferSink();
+ $putByIdDirectPrivate(stream, "underlyingSource", undefined);
+ var highWaterMark = $getByIdDirectPrivate(stream, "highWaterMark");
+ sink.start(highWaterMark ? { highWaterMark } : {});
+ var capability = $newPromiseCapability(Promise);
+ var ended = false;
+ var pull = underlyingSource.pull;
+ var close = underlyingSource.close;
+
+ var controller = {
+ start() {},
+ close(reason) {
+ if (!ended) {
+ ended = true;
+ if (close) {
+ close();
+ }
+
+ $fulfillPromise(capability.$promise, sink.end());
+ }
+ },
+ end() {
+ if (!ended) {
+ ended = true;
+ if (close) {
+ close();
+ }
+ $fulfillPromise(capability.$promise, sink.end());
+ }
+ },
+ flush() {
+ return 0;
+ },
+ write: sink.write.bind(sink),
+ };
+
+ var didError = false;
+ try {
+ const firstPull = pull(controller);
+ if (firstPull && $isObject(firstPull) && $isPromise(firstPull)) {
+ return (async function (controller, promise, pull) {
+ while (!ended) {
+ await pull(controller);
+ }
+ return await promise;
+ })(controller, promise, pull);
+ }
+
+ return capability.$promise;
+ } catch (e) {
+ didError = true;
+ $readableStreamError(stream, e);
+ return Promise.$reject(e);
+ } finally {
+ if (!didError && stream) $readableStreamClose(stream);
+ controller = close = sink = pull = stream = undefined;
+ }
+}
+
+export async function readableStreamToTextDirect(stream, underlyingSource) {
+ const capability = $initializeTextStream.$call(stream, underlyingSource, undefined);
+ var reader = stream.getReader();
+
+ while ($getByIdDirectPrivate(stream, "state") === $streamReadable) {
+ var thisResult = await reader.read();
+ if (thisResult.done) {
+ break;
+ }
+ }
+
+ try {
+ reader.releaseLock();
+ } catch (e) {}
+ reader = undefined;
+ stream = undefined;
+
+ return capability.$promise;
+}
+
+export async function readableStreamToArrayDirect(stream, underlyingSource) {
+ const capability = $initializeArrayStream.$call(stream, underlyingSource, undefined);
+ underlyingSource = undefined;
+ var reader = stream.getReader();
+ try {
+ while ($getByIdDirectPrivate(stream, "state") === $streamReadable) {
+ var thisResult = await reader.read();
+ if (thisResult.done) {
+ break;
+ }
+ }
+
+ try {
+ reader.releaseLock();
+ } catch (e) {}
+ reader = undefined;
+
+ return Promise.$resolve(capability.$promise);
+ } catch (e) {
+ throw e;
+ } finally {
+ stream = undefined;
+ reader = undefined;
+ }
+}
+
+export function readableStreamDefineLazyIterators(prototype) {
+ var asyncIterator = globalThis.Symbol.asyncIterator;
+
+ var ReadableStreamAsyncIterator = async function* ReadableStreamAsyncIterator(stream, preventCancel) {
+ var reader = stream.getReader();
+ var deferredError;
+ try {
+ while (true) {
+ var done, value;
+ const firstResult = reader.readMany();
+ if ($isPromise(firstResult)) {
+ ({ done, value } = await firstResult);
+ } else {
+ ({ done, value } = firstResult);
+ }
+
+ if (done) {
+ return;
+ }
+ yield* value;
+ }
+ } catch (e) {
+ deferredError = e;
+ } finally {
+ reader.releaseLock();
+
+ if (!preventCancel) {
+ stream.cancel(deferredError);
+ }
+
+ if (deferredError) {
+ throw deferredError;
+ }
+ }
+ };
+ var createAsyncIterator = function asyncIterator() {
+ return ReadableStreamAsyncIterator(this, false);
+ };
+ var createValues = function values({ preventCancel = false } = { preventCancel: false }) {
+ return ReadableStreamAsyncIterator(this, preventCancel);
+ };
+ $Object.$defineProperty(prototype, asyncIterator, { value: createAsyncIterator });
+ $Object.$defineProperty(prototype, "values", { value: createValues });
+ return prototype;
+}
diff --git a/src/js/builtins/StreamInternals.ts b/src/js/builtins/StreamInternals.ts
new file mode 100644
index 000000000..b42dc2f57
--- /dev/null
+++ b/src/js/builtins/StreamInternals.ts
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2015 Canon Inc.
+ * Copyright (C) 2015 Igalia.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// @internal
+
+export function markPromiseAsHandled(promise: Promise<unknown>) {
+ $assert($isPromise(promise));
+ $putPromiseInternalField(
+ promise,
+ $promiseFieldFlags,
+ $getPromiseInternalField(promise, $promiseFieldFlags) | $promiseFlagsIsHandled,
+ );
+}
+
+export function shieldingPromiseResolve(result) {
+ const promise = Promise.$resolve(result);
+ if (promise.$then === undefined) promise.$then = Promise.prototype.$then;
+ return promise;
+}
+
+export function promiseInvokeOrNoopMethodNoCatch(object, method, args) {
+ if (method === undefined) return Promise.$resolve();
+ return $shieldingPromiseResolve(method.$apply(object, args));
+}
+
+export function promiseInvokeOrNoopNoCatch(object, key, args) {
+ return $promiseInvokeOrNoopMethodNoCatch(object, object[key], args);
+}
+
+export function promiseInvokeOrNoopMethod(object, method, args) {
+ try {
+ return $promiseInvokeOrNoopMethodNoCatch(object, method, args);
+ } catch (error) {
+ return Promise.$reject(error);
+ }
+}
+
+export function promiseInvokeOrNoop(object, key, args) {
+ try {
+ return $promiseInvokeOrNoopNoCatch(object, key, args);
+ } catch (error) {
+ return Promise.$reject(error);
+ }
+}
+
+export function promiseInvokeOrFallbackOrNoop(object, key1, args1, key2, args2) {
+ try {
+ const method = object[key1];
+ if (method === undefined) return $promiseInvokeOrNoopNoCatch(object, key2, args2);
+ return $shieldingPromiseResolve(method.$apply(object, args1));
+ } catch (error) {
+ return Promise.$reject(error);
+ }
+}
+
+export function validateAndNormalizeQueuingStrategy(size, highWaterMark) {
+ if (size !== undefined && typeof size !== "function") throw new TypeError("size parameter must be a function");
+
+ const newHighWaterMark = $toNumber(highWaterMark);
+
+ if (isNaN(newHighWaterMark) || newHighWaterMark < 0)
+ throw new RangeError("highWaterMark value is negative or not a number");
+
+ return { size: size, highWaterMark: newHighWaterMark };
+}
+
+$linkTimeConstant;
+export function createFIFO() {
+ var slice = Array.prototype.slice;
+
+ class Denqueue {
+ constructor() {
+ this._head = 0;
+ this._tail = 0;
+ // this._capacity = 0;
+ this._capacityMask = 0x3;
+ this._list = $newArrayWithSize(4);
+ }
+
+ _head;
+ _tail;
+ _capacityMask;
+ _list;
+
+ size() {
+ if (this._head === this._tail) return 0;
+ if (this._head < this._tail) return this._tail - this._head;
+ else return this._capacityMask + 1 - (this._head - this._tail);
+ }
+
+ isEmpty() {
+ return this.size() == 0;
+ }
+
+ isNotEmpty() {
+ return this.size() > 0;
+ }
+
+ shift() {
+ var { _head: head, _tail, _list, _capacityMask } = this;
+ if (head === _tail) return undefined;
+ var item = _list[head];
+ $putByValDirect(_list, head, undefined);
+ head = this._head = (head + 1) & _capacityMask;
+ if (head < 2 && _tail > 10000 && _tail <= _list.length >>> 2) this._shrinkArray();
+ return item;
+ }
+
+ peek() {
+ if (this._head === this._tail) return undefined;
+ return this._list[this._head];
+ }
+
+ push(item) {
+ var tail = this._tail;
+ $putByValDirect(this._list, tail, item);
+ this._tail = (tail + 1) & this._capacityMask;
+ if (this._tail === this._head) {
+ this._growArray();
+ }
+ // if (this._capacity && this.size() > this._capacity) {
+ // this.shift();
+ // }
+ }
+
+ toArray(fullCopy) {
+ var list = this._list;
+ var len = $toLength(list.length);
+
+ if (fullCopy || this._head > this._tail) {
+ var _head = $toLength(this._head);
+ var _tail = $toLength(this._tail);
+ var total = $toLength(len - _head + _tail);
+ var array = $newArrayWithSize(total);
+ var j = 0;
+ for (var i = _head; i < len; i++) $putByValDirect(array, j++, list[i]);
+ for (var i = 0; i < _tail; i++) $putByValDirect(array, j++, list[i]);
+ return array;
+ } else {
+ return slice.$call(list, this._head, this._tail);
+ }
+ }
+
+ clear() {
+ this._head = 0;
+ this._tail = 0;
+ this._list.fill(undefined);
+ }
+
+ _growArray() {
+ if (this._head) {
+ // copy existing data, head to end, then beginning to tail.
+ this._list = this.toArray(true);
+ this._head = 0;
+ }
+
+ // head is at 0 and array is now full, safe to extend
+ this._tail = $toLength(this._list.length);
+
+ this._list.length <<= 1;
+ this._capacityMask = (this._capacityMask << 1) | 1;
+ }
+
+ shrinkArray() {
+ this._list.length >>>= 1;
+ this._capacityMask >>>= 1;
+ }
+ }
+
+ return new Denqueue();
+}
+
+export function newQueue() {
+ return { content: $createFIFO(), size: 0 };
+}
+
+export function dequeueValue(queue) {
+ const record = queue.content.shift();
+ queue.size -= record.size;
+ // As described by spec, below case may occur due to rounding errors.
+ if (queue.size < 0) queue.size = 0;
+ return record.value;
+}
+
+export function enqueueValueWithSize(queue, value, size) {
+ size = $toNumber(size);
+ if (!isFinite(size) || size < 0) throw new RangeError("size has an incorrect value");
+
+ queue.content.push({ value, size });
+ queue.size += size;
+}
+
+export function peekQueueValue(queue) {
+ return queue.content.peek()?.value;
+}
+
+export function resetQueue(queue) {
+ $assert("content" in queue);
+ $assert("size" in queue);
+ queue.content.clear();
+ queue.size = 0;
+}
+
+export function extractSizeAlgorithm(strategy) {
+ const sizeAlgorithm = strategy.size;
+
+ if (sizeAlgorithm === undefined) return () => 1;
+
+ if (typeof sizeAlgorithm !== "function") throw new TypeError("strategy.size must be a function");
+
+ return chunk => {
+ return sizeAlgorithm(chunk);
+ };
+}
+
+export function extractHighWaterMark(strategy, defaultHWM) {
+ const highWaterMark = strategy.highWaterMark;
+
+ if (highWaterMark === undefined) return defaultHWM;
+
+ if (isNaN(highWaterMark) || highWaterMark < 0)
+ throw new RangeError("highWaterMark value is negative or not a number");
+
+ return $toNumber(highWaterMark);
+}
+
+export function extractHighWaterMarkFromQueuingStrategyInit(init: { highWaterMark?: number }) {
+ if (!$isObject(init)) throw new TypeError("QueuingStrategyInit argument must be an object.");
+ const { highWaterMark } = init;
+ if (highWaterMark === undefined) throw new TypeError("QueuingStrategyInit.highWaterMark member is required.");
+
+ return $toNumber(highWaterMark);
+}
+
+export function createFulfilledPromise(value) {
+ const promise = $newPromise();
+ $fulfillPromise(promise, value);
+ return promise;
+}
+
+export function toDictionary(value, defaultValue, errorMessage) {
+ if (value === undefined || value === null) return defaultValue;
+ if (!$isObject(value)) throw new TypeError(errorMessage);
+ return value;
+}
diff --git a/src/js/builtins/TransformStream.ts b/src/js/builtins/TransformStream.ts
new file mode 100644
index 000000000..54467db39
--- /dev/null
+++ b/src/js/builtins/TransformStream.ts
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2020 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+export function initializeTransformStream(this) {
+ let transformer = arguments[0];
+
+ // This is the path for CreateTransformStream.
+ if ($isObject(transformer) && $getByIdDirectPrivate(transformer, "TransformStream")) return this;
+
+ let writableStrategy = arguments[1];
+ let readableStrategy = arguments[2];
+
+ if (transformer === undefined) transformer = null;
+
+ if (readableStrategy === undefined) readableStrategy = {};
+
+ if (writableStrategy === undefined) writableStrategy = {};
+
+ let transformerDict = {};
+ if (transformer !== null) {
+ if ("start" in transformer) {
+ transformerDict["start"] = transformer["start"];
+ if (typeof transformerDict["start"] !== "function") $throwTypeError("transformer.start should be a function");
+ }
+ if ("transform" in transformer) {
+ transformerDict["transform"] = transformer["transform"];
+ if (typeof transformerDict["transform"] !== "function")
+ $throwTypeError("transformer.transform should be a function");
+ }
+ if ("flush" in transformer) {
+ transformerDict["flush"] = transformer["flush"];
+ if (typeof transformerDict["flush"] !== "function") $throwTypeError("transformer.flush should be a function");
+ }
+
+ if ("readableType" in transformer) throw new RangeError("TransformStream transformer has a readableType");
+ if ("writableType" in transformer) throw new RangeError("TransformStream transformer has a writableType");
+ }
+
+ const readableHighWaterMark = $extractHighWaterMark(readableStrategy, 0);
+ const readableSizeAlgorithm = $extractSizeAlgorithm(readableStrategy);
+
+ const writableHighWaterMark = $extractHighWaterMark(writableStrategy, 1);
+ const writableSizeAlgorithm = $extractSizeAlgorithm(writableStrategy);
+
+ const startPromiseCapability = $newPromiseCapability(Promise);
+ $initializeTransformStream(
+ this,
+ startPromiseCapability.$promise,
+ writableHighWaterMark,
+ writableSizeAlgorithm,
+ readableHighWaterMark,
+ readableSizeAlgorithm,
+ );
+ $setUpTransformStreamDefaultControllerFromTransformer(this, transformer, transformerDict);
+
+ if ("start" in transformerDict) {
+ const controller = $getByIdDirectPrivate(this, "controller");
+ const startAlgorithm = () => $promiseInvokeOrNoopMethodNoCatch(transformer, transformerDict["start"], [controller]);
+ startAlgorithm().$then(
+ () => {
+ // FIXME: We probably need to resolve start promise with the result of the start algorithm.
+ startPromiseCapability.$resolve.$call();
+ },
+ error => {
+ startPromiseCapability.$reject.$call(undefined, error);
+ },
+ );
+ } else startPromiseCapability.$resolve.$call();
+
+ return this;
+}
+
+$getter;
+export function readable() {
+ if (!$isTransformStream(this)) throw $makeThisTypeError("TransformStream", "readable");
+
+ return $getByIdDirectPrivate(this, "readable");
+}
+
+export function writable() {
+ if (!$isTransformStream(this)) throw $makeThisTypeError("TransformStream", "writable");
+
+ return $getByIdDirectPrivate(this, "writable");
+}
diff --git a/src/js/builtins/TransformStreamDefaultController.ts b/src/js/builtins/TransformStreamDefaultController.ts
new file mode 100644
index 000000000..1045498b8
--- /dev/null
+++ b/src/js/builtins/TransformStreamDefaultController.ts
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2020 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+export function initializeTransformStreamDefaultController(this) {
+ return this;
+}
+
+$getter;
+export function desiredSize(this) {
+ if (!$isTransformStreamDefaultController(this))
+ throw $makeThisTypeError("TransformStreamDefaultController", "enqueue");
+
+ const stream = $getByIdDirectPrivate(this, "stream");
+ const readable = $getByIdDirectPrivate(stream, "readable");
+ const readableController = $getByIdDirectPrivate(readable, "readableStreamController");
+
+ return $readableStreamDefaultControllerGetDesiredSize(readableController);
+}
+
+export function enqueue(this, chunk) {
+ if (!$isTransformStreamDefaultController(this))
+ throw $makeThisTypeError("TransformStreamDefaultController", "enqueue");
+
+ $transformStreamDefaultControllerEnqueue(this, chunk);
+}
+
+export function error(this, e) {
+ if (!$isTransformStreamDefaultController(this)) throw $makeThisTypeError("TransformStreamDefaultController", "error");
+
+ $transformStreamDefaultControllerError(this, e);
+}
+
+export function terminate(this) {
+ if (!$isTransformStreamDefaultController(this))
+ throw $makeThisTypeError("TransformStreamDefaultController", "terminate");
+
+ $transformStreamDefaultControllerTerminate(this);
+}
diff --git a/src/js/builtins/TransformStreamInternals.ts b/src/js/builtins/TransformStreamInternals.ts
new file mode 100644
index 000000000..9994d1282
--- /dev/null
+++ b/src/js/builtins/TransformStreamInternals.ts
@@ -0,0 +1,348 @@
+/*
+ * Copyright (C) 2020 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// @internal
+
+export function isTransformStream(stream) {
+ return $isObject(stream) && !!$getByIdDirectPrivate(stream, "readable");
+}
+
+export function isTransformStreamDefaultController(controller) {
+ return $isObject(controller) && !!$getByIdDirectPrivate(controller, "transformAlgorithm");
+}
+
+export function createTransformStream(
+ startAlgorithm,
+ transformAlgorithm,
+ flushAlgorithm,
+ writableHighWaterMark,
+ writableSizeAlgorithm,
+ readableHighWaterMark,
+ readableSizeAlgorithm,
+) {
+ if (writableHighWaterMark === undefined) writableHighWaterMark = 1;
+ if (writableSizeAlgorithm === undefined) writableSizeAlgorithm = () => 1;
+ if (readableHighWaterMark === undefined) readableHighWaterMark = 0;
+ if (readableSizeAlgorithm === undefined) readableSizeAlgorithm = () => 1;
+ $assert(writableHighWaterMark >= 0);
+ $assert(readableHighWaterMark >= 0);
+
+ const transform = {};
+ $putByIdDirectPrivate(transform, "TransformStream", true);
+
+ const stream = new TransformStream(transform);
+ const startPromiseCapability = $newPromiseCapability(Promise);
+ $initializeTransformStream(
+ stream,
+ startPromiseCapability.$promise,
+ writableHighWaterMark,
+ writableSizeAlgorithm,
+ readableHighWaterMark,
+ readableSizeAlgorithm,
+ );
+
+ const controller = new TransformStreamDefaultController();
+ $setUpTransformStreamDefaultController(stream, controller, transformAlgorithm, flushAlgorithm);
+
+ startAlgorithm().$then(
+ () => {
+ startPromiseCapability.$resolve.$call();
+ },
+ error => {
+ startPromiseCapability.$reject.$call(undefined, error);
+ },
+ );
+
+ return stream;
+}
+
+export function initializeTransformStream(
+ stream,
+ startPromise,
+ writableHighWaterMark,
+ writableSizeAlgorithm,
+ readableHighWaterMark,
+ readableSizeAlgorithm,
+) {
+ const startAlgorithm = () => {
+ return startPromise;
+ };
+ const writeAlgorithm = chunk => {
+ return $transformStreamDefaultSinkWriteAlgorithm(stream, chunk);
+ };
+ const abortAlgorithm = reason => {
+ return $transformStreamDefaultSinkAbortAlgorithm(stream, reason);
+ };
+ const closeAlgorithm = () => {
+ return $transformStreamDefaultSinkCloseAlgorithm(stream);
+ };
+ const writable = $createWritableStream(
+ startAlgorithm,
+ writeAlgorithm,
+ closeAlgorithm,
+ abortAlgorithm,
+ writableHighWaterMark,
+ writableSizeAlgorithm,
+ );
+
+ const pullAlgorithm = () => {
+ return $transformStreamDefaultSourcePullAlgorithm(stream);
+ };
+ const cancelAlgorithm = reason => {
+ $transformStreamErrorWritableAndUnblockWrite(stream, reason);
+ return Promise.$resolve();
+ };
+ const underlyingSource = {};
+ $putByIdDirectPrivate(underlyingSource, "start", startAlgorithm);
+ $putByIdDirectPrivate(underlyingSource, "pull", pullAlgorithm);
+ $putByIdDirectPrivate(underlyingSource, "cancel", cancelAlgorithm);
+ const options = {};
+ $putByIdDirectPrivate(options, "size", readableSizeAlgorithm);
+ $putByIdDirectPrivate(options, "highWaterMark", readableHighWaterMark);
+ const readable = new ReadableStream(underlyingSource, options);
+
+ // The writable to expose to JS through writable getter.
+ $putByIdDirectPrivate(stream, "writable", writable);
+ // The writable to use for the actual transform algorithms.
+ $putByIdDirectPrivate(stream, "internalWritable", $getInternalWritableStream(writable));
+
+ $putByIdDirectPrivate(stream, "readable", readable);
+ $putByIdDirectPrivate(stream, "backpressure", undefined);
+ $putByIdDirectPrivate(stream, "backpressureChangePromise", undefined);
+
+ $transformStreamSetBackpressure(stream, true);
+ $putByIdDirectPrivate(stream, "controller", undefined);
+}
+
+export function transformStreamError(stream, e) {
+ const readable = $getByIdDirectPrivate(stream, "readable");
+ const readableController = $getByIdDirectPrivate(readable, "readableStreamController");
+ $readableStreamDefaultControllerError(readableController, e);
+
+ $transformStreamErrorWritableAndUnblockWrite(stream, e);
+}
+
+export function transformStreamErrorWritableAndUnblockWrite(stream, e) {
+ $transformStreamDefaultControllerClearAlgorithms($getByIdDirectPrivate(stream, "controller"));
+
+ const writable = $getByIdDirectPrivate(stream, "internalWritable");
+ $writableStreamDefaultControllerErrorIfNeeded($getByIdDirectPrivate(writable, "controller"), e);
+
+ if ($getByIdDirectPrivate(stream, "backpressure")) $transformStreamSetBackpressure(stream, false);
+}
+
+export function transformStreamSetBackpressure(stream, backpressure) {
+ $assert($getByIdDirectPrivate(stream, "backpressure") !== backpressure);
+
+ const backpressureChangePromise = $getByIdDirectPrivate(stream, "backpressureChangePromise");
+ if (backpressureChangePromise !== undefined) backpressureChangePromise.$resolve.$call();
+
+ $putByIdDirectPrivate(stream, "backpressureChangePromise", $newPromiseCapability(Promise));
+ $putByIdDirectPrivate(stream, "backpressure", backpressure);
+}
+
+export function setUpTransformStreamDefaultController(stream, controller, transformAlgorithm, flushAlgorithm) {
+ $assert($isTransformStream(stream));
+ $assert($getByIdDirectPrivate(stream, "controller") === undefined);
+
+ $putByIdDirectPrivate(controller, "stream", stream);
+ $putByIdDirectPrivate(stream, "controller", controller);
+ $putByIdDirectPrivate(controller, "transformAlgorithm", transformAlgorithm);
+ $putByIdDirectPrivate(controller, "flushAlgorithm", flushAlgorithm);
+}
+
+export function setUpTransformStreamDefaultControllerFromTransformer(stream, transformer, transformerDict) {
+ const controller = new TransformStreamDefaultController();
+ let transformAlgorithm = chunk => {
+ try {
+ $transformStreamDefaultControllerEnqueue(controller, chunk);
+ } catch (e) {
+ return Promise.$reject(e);
+ }
+ return Promise.$resolve();
+ };
+ let flushAlgorithm = () => {
+ return Promise.$resolve();
+ };
+
+ if ("transform" in transformerDict)
+ transformAlgorithm = chunk => {
+ return $promiseInvokeOrNoopMethod(transformer, transformerDict["transform"], [chunk, controller]);
+ };
+
+ if ("flush" in transformerDict) {
+ flushAlgorithm = () => {
+ return $promiseInvokeOrNoopMethod(transformer, transformerDict["flush"], [controller]);
+ };
+ }
+
+ $setUpTransformStreamDefaultController(stream, controller, transformAlgorithm, flushAlgorithm);
+}
+
+export function transformStreamDefaultControllerClearAlgorithms(controller) {
+ // We set transformAlgorithm to true to allow GC but keep the isTransformStreamDefaultController check.
+ $putByIdDirectPrivate(controller, "transformAlgorithm", true);
+ $putByIdDirectPrivate(controller, "flushAlgorithm", undefined);
+}
+
+export function transformStreamDefaultControllerEnqueue(controller, chunk) {
+ const stream = $getByIdDirectPrivate(controller, "stream");
+ const readable = $getByIdDirectPrivate(stream, "readable");
+ const readableController = $getByIdDirectPrivate(readable, "readableStreamController");
+
+ $assert(readableController !== undefined);
+ if (!$readableStreamDefaultControllerCanCloseOrEnqueue(readableController))
+ $throwTypeError("TransformStream.readable cannot close or enqueue");
+
+ try {
+ $readableStreamDefaultControllerEnqueue(readableController, chunk);
+ } catch (e) {
+ $transformStreamErrorWritableAndUnblockWrite(stream, e);
+ throw $getByIdDirectPrivate(readable, "storedError");
+ }
+
+ const backpressure = !$readableStreamDefaultControllerShouldCallPull(readableController);
+ if (backpressure !== $getByIdDirectPrivate(stream, "backpressure")) {
+ $assert(backpressure);
+ $transformStreamSetBackpressure(stream, true);
+ }
+}
+
+export function transformStreamDefaultControllerError(controller, e) {
+ $transformStreamError($getByIdDirectPrivate(controller, "stream"), e);
+}
+
+export function transformStreamDefaultControllerPerformTransform(controller, chunk) {
+ const promiseCapability = $newPromiseCapability(Promise);
+
+ const transformPromise = $getByIdDirectPrivate(controller, "transformAlgorithm").$call(undefined, chunk);
+ transformPromise.$then(
+ () => {
+ promiseCapability.$resolve();
+ },
+ r => {
+ $transformStreamError($getByIdDirectPrivate(controller, "stream"), r);
+ promiseCapability.$reject.$call(undefined, r);
+ },
+ );
+ return promiseCapability.$promise;
+}
+
+export function transformStreamDefaultControllerTerminate(controller) {
+ const stream = $getByIdDirectPrivate(controller, "stream");
+ const readable = $getByIdDirectPrivate(stream, "readable");
+ const readableController = $getByIdDirectPrivate(readable, "readableStreamController");
+
+ // FIXME: Update readableStreamDefaultControllerClose to make this check.
+ if ($readableStreamDefaultControllerCanCloseOrEnqueue(readableController))
+ $readableStreamDefaultControllerClose(readableController);
+ const error = $makeTypeError("the stream has been terminated");
+ $transformStreamErrorWritableAndUnblockWrite(stream, error);
+}
+
+export function transformStreamDefaultSinkWriteAlgorithm(stream, chunk) {
+ const writable = $getByIdDirectPrivate(stream, "internalWritable");
+
+ $assert($getByIdDirectPrivate(writable, "state") === "writable");
+
+ const controller = $getByIdDirectPrivate(stream, "controller");
+
+ if ($getByIdDirectPrivate(stream, "backpressure")) {
+ const promiseCapability = $newPromiseCapability(Promise);
+
+ const backpressureChangePromise = $getByIdDirectPrivate(stream, "backpressureChangePromise");
+ $assert(backpressureChangePromise !== undefined);
+ backpressureChangePromise.$promise.$then(
+ () => {
+ const state = $getByIdDirectPrivate(writable, "state");
+ if (state === "erroring") {
+ promiseCapability.$reject.$call(undefined, $getByIdDirectPrivate(writable, "storedError"));
+ return;
+ }
+
+ $assert(state === "writable");
+ $transformStreamDefaultControllerPerformTransform(controller, chunk).$then(
+ () => {
+ promiseCapability.$resolve();
+ },
+ e => {
+ promiseCapability.$reject.$call(undefined, e);
+ },
+ );
+ },
+ e => {
+ promiseCapability.$reject.$call(undefined, e);
+ },
+ );
+
+ return promiseCapability.$promise;
+ }
+ return $transformStreamDefaultControllerPerformTransform(controller, chunk);
+}
+
+export function transformStreamDefaultSinkAbortAlgorithm(stream, reason) {
+ $transformStreamError(stream, reason);
+ return Promise.$resolve();
+}
+
+export function transformStreamDefaultSinkCloseAlgorithm(stream) {
+ const readable = $getByIdDirectPrivate(stream, "readable");
+ const controller = $getByIdDirectPrivate(stream, "controller");
+ const readableController = $getByIdDirectPrivate(readable, "readableStreamController");
+
+ const flushAlgorithm = $getByIdDirectPrivate(controller, "flushAlgorithm");
+ $assert(flushAlgorithm !== undefined);
+ const flushPromise = $getByIdDirectPrivate(controller, "flushAlgorithm").$call();
+ $transformStreamDefaultControllerClearAlgorithms(controller);
+
+ const promiseCapability = $newPromiseCapability(Promise);
+ flushPromise.$then(
+ () => {
+ if ($getByIdDirectPrivate(readable, "state") === $streamErrored) {
+ promiseCapability.$reject.$call(undefined, $getByIdDirectPrivate(readable, "storedError"));
+ return;
+ }
+
+ // FIXME: Update readableStreamDefaultControllerClose to make this check.
+ if ($readableStreamDefaultControllerCanCloseOrEnqueue(readableController))
+ $readableStreamDefaultControllerClose(readableController);
+ promiseCapability.$resolve();
+ },
+ r => {
+ $transformStreamError($getByIdDirectPrivate(controller, "stream"), r);
+ promiseCapability.$reject.$call(undefined, $getByIdDirectPrivate(readable, "storedError"));
+ },
+ );
+ return promiseCapability.$promise;
+}
+
+export function transformStreamDefaultSourcePullAlgorithm(stream) {
+ $assert($getByIdDirectPrivate(stream, "backpressure"));
+ $assert($getByIdDirectPrivate(stream, "backpressureChangePromise") !== undefined);
+
+ $transformStreamSetBackpressure(stream, false);
+
+ return $getByIdDirectPrivate(stream, "backpressureChangePromise").$promise;
+}
diff --git a/src/js/builtins/WritableStreamDefaultController.ts b/src/js/builtins/WritableStreamDefaultController.ts
new file mode 100644
index 000000000..1a3ddc290
--- /dev/null
+++ b/src/js/builtins/WritableStreamDefaultController.ts
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2020 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+export function initializeWritableStreamDefaultController(this) {
+ $putByIdDirectPrivate(this, "queue", $newQueue());
+ $putByIdDirectPrivate(this, "abortSteps", reason => {
+ const result = $getByIdDirectPrivate(this, "abortAlgorithm").$call(undefined, reason);
+ $writableStreamDefaultControllerClearAlgorithms(this);
+ return result;
+ });
+
+ $putByIdDirectPrivate(this, "errorSteps", () => {
+ $resetQueue($getByIdDirectPrivate(this, "queue"));
+ });
+
+ return this;
+}
+
+export function error(this, e) {
+ if ($getByIdDirectPrivate(this, "abortSteps") === undefined)
+ throw $makeThisTypeError("WritableStreamDefaultController", "error");
+
+ const stream = $getByIdDirectPrivate(this, "stream");
+ if ($getByIdDirectPrivate(stream, "state") !== "writable") return;
+ $writableStreamDefaultControllerError(this, e);
+}
diff --git a/src/js/builtins/WritableStreamDefaultWriter.ts b/src/js/builtins/WritableStreamDefaultWriter.ts
new file mode 100644
index 000000000..795b43892
--- /dev/null
+++ b/src/js/builtins/WritableStreamDefaultWriter.ts
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2020 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+export function initializeWritableStreamDefaultWriter(stream) {
+ // stream can be a WritableStream if WritableStreamDefaultWriter constructor is called directly from JS
+ // or an InternalWritableStream in other code paths.
+ const internalStream = $getInternalWritableStream(stream);
+ if (internalStream) stream = internalStream;
+
+ if (!$isWritableStream(stream)) $throwTypeError("WritableStreamDefaultWriter constructor takes a WritableStream");
+
+ $setUpWritableStreamDefaultWriter(this, stream);
+ return this;
+}
+
+$getter;
+export function closed() {
+ if (!$isWritableStreamDefaultWriter(this))
+ return Promise.$reject($makeGetterTypeError("WritableStreamDefaultWriter", "closed"));
+
+ return $getByIdDirectPrivate(this, "closedPromise").$promise;
+}
+
+$getter;
+export function desiredSize() {
+ if (!$isWritableStreamDefaultWriter(this)) throw $makeThisTypeError("WritableStreamDefaultWriter", "desiredSize");
+
+ if ($getByIdDirectPrivate(this, "stream") === undefined) $throwTypeError("WritableStreamDefaultWriter has no stream");
+
+ return $writableStreamDefaultWriterGetDesiredSize(this);
+}
+
+$getter;
+export function ready() {
+ if (!$isWritableStreamDefaultWriter(this))
+ return Promise.$reject($makeThisTypeError("WritableStreamDefaultWriter", "ready"));
+
+ return $getByIdDirectPrivate(this, "readyPromise").$promise;
+}
+
+export function abort(reason) {
+ if (!$isWritableStreamDefaultWriter(this))
+ return Promise.$reject($makeThisTypeError("WritableStreamDefaultWriter", "abort"));
+
+ if ($getByIdDirectPrivate(this, "stream") === undefined)
+ return Promise.$reject($makeTypeError("WritableStreamDefaultWriter has no stream"));
+
+ return $writableStreamDefaultWriterAbort(this, reason);
+}
+
+export function close() {
+ if (!$isWritableStreamDefaultWriter(this))
+ return Promise.$reject($makeThisTypeError("WritableStreamDefaultWriter", "close"));
+
+ const stream = $getByIdDirectPrivate(this, "stream");
+ if (stream === undefined) return Promise.$reject($makeTypeError("WritableStreamDefaultWriter has no stream"));
+
+ if ($writableStreamCloseQueuedOrInFlight(stream))
+ return Promise.$reject($makeTypeError("WritableStreamDefaultWriter is being closed"));
+
+ return $writableStreamDefaultWriterClose(this);
+}
+
+export function releaseLock() {
+ if (!$isWritableStreamDefaultWriter(this)) throw $makeThisTypeError("WritableStreamDefaultWriter", "releaseLock");
+
+ const stream = $getByIdDirectPrivate(this, "stream");
+ if (stream === undefined) return;
+
+ $assert($getByIdDirectPrivate(stream, "writer") !== undefined);
+ $writableStreamDefaultWriterRelease(this);
+}
+
+export function write(chunk) {
+ if (!$isWritableStreamDefaultWriter(this))
+ return Promise.$reject($makeThisTypeError("WritableStreamDefaultWriter", "write"));
+
+ if ($getByIdDirectPrivate(this, "stream") === undefined)
+ return Promise.$reject($makeTypeError("WritableStreamDefaultWriter has no stream"));
+
+ return $writableStreamDefaultWriterWrite(this, chunk);
+}
diff --git a/src/js/builtins/WritableStreamInternals.ts b/src/js/builtins/WritableStreamInternals.ts
new file mode 100644
index 000000000..f436a285e
--- /dev/null
+++ b/src/js/builtins/WritableStreamInternals.ts
@@ -0,0 +1,790 @@
+/*
+ * Copyright (C) 2015 Canon Inc.
+ * Copyright (C) 2015 Igalia
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// @internal
+
+export function isWritableStream(stream) {
+ return $isObject(stream) && !!$getByIdDirectPrivate(stream, "underlyingSink");
+}
+
+export function isWritableStreamDefaultWriter(writer) {
+ return $isObject(writer) && !!$getByIdDirectPrivate(writer, "closedPromise");
+}
+
+export function acquireWritableStreamDefaultWriter(stream) {
+ return new WritableStreamDefaultWriter(stream);
+}
+
+// https://streams.spec.whatwg.org/#create-writable-stream
+export function createWritableStream(
+ startAlgorithm,
+ writeAlgorithm,
+ closeAlgorithm,
+ abortAlgorithm,
+ highWaterMark,
+ sizeAlgorithm,
+) {
+ $assert(typeof highWaterMark === "number" && !isNaN(highWaterMark) && highWaterMark >= 0);
+
+ const internalStream = {};
+ $initializeWritableStreamSlots(internalStream, {});
+ const controller = new WritableStreamDefaultController();
+
+ $setUpWritableStreamDefaultController(
+ internalStream,
+ controller,
+ startAlgorithm,
+ writeAlgorithm,
+ closeAlgorithm,
+ abortAlgorithm,
+ highWaterMark,
+ sizeAlgorithm,
+ );
+
+ return $createWritableStreamFromInternal(internalStream);
+}
+
+export function createInternalWritableStreamFromUnderlyingSink(underlyingSink, strategy) {
+ const stream = {};
+
+ if (underlyingSink === undefined) underlyingSink = {};
+
+ if (strategy === undefined) strategy = {};
+
+ if (!$isObject(underlyingSink)) $throwTypeError("WritableStream constructor takes an object as first argument");
+
+ if ("type" in underlyingSink) $throwRangeError("Invalid type is specified");
+
+ const sizeAlgorithm = $extractSizeAlgorithm(strategy);
+ const highWaterMark = $extractHighWaterMark(strategy, 1);
+
+ const underlyingSinkDict = {};
+ if ("start" in underlyingSink) {
+ underlyingSinkDict["start"] = underlyingSink["start"];
+ if (typeof underlyingSinkDict["start"] !== "function") $throwTypeError("underlyingSink.start should be a function");
+ }
+ if ("write" in underlyingSink) {
+ underlyingSinkDict["write"] = underlyingSink["write"];
+ if (typeof underlyingSinkDict["write"] !== "function") $throwTypeError("underlyingSink.write should be a function");
+ }
+ if ("close" in underlyingSink) {
+ underlyingSinkDict["close"] = underlyingSink["close"];
+ if (typeof underlyingSinkDict["close"] !== "function") $throwTypeError("underlyingSink.close should be a function");
+ }
+ if ("abort" in underlyingSink) {
+ underlyingSinkDict["abort"] = underlyingSink["abort"];
+ if (typeof underlyingSinkDict["abort"] !== "function") $throwTypeError("underlyingSink.abort should be a function");
+ }
+
+ $initializeWritableStreamSlots(stream, underlyingSink);
+ $setUpWritableStreamDefaultControllerFromUnderlyingSink(
+ stream,
+ underlyingSink,
+ underlyingSinkDict,
+ highWaterMark,
+ sizeAlgorithm,
+ );
+
+ return stream;
+}
+
+export function initializeWritableStreamSlots(stream, underlyingSink) {
+ $putByIdDirectPrivate(stream, "state", "writable");
+ $putByIdDirectPrivate(stream, "storedError", undefined);
+ $putByIdDirectPrivate(stream, "writer", undefined);
+ $putByIdDirectPrivate(stream, "controller", undefined);
+ $putByIdDirectPrivate(stream, "inFlightWriteRequest", undefined);
+ $putByIdDirectPrivate(stream, "closeRequest", undefined);
+ $putByIdDirectPrivate(stream, "inFlightCloseRequest", undefined);
+ $putByIdDirectPrivate(stream, "pendingAbortRequest", undefined);
+ $putByIdDirectPrivate(stream, "writeRequests", $createFIFO());
+ $putByIdDirectPrivate(stream, "backpressure", false);
+ $putByIdDirectPrivate(stream, "underlyingSink", underlyingSink);
+}
+
+export function writableStreamCloseForBindings(stream) {
+ if ($isWritableStreamLocked(stream))
+ return Promise.$reject($makeTypeError("WritableStream.close method can only be used on non locked WritableStream"));
+
+ if ($writableStreamCloseQueuedOrInFlight(stream))
+ return Promise.$reject(
+ $makeTypeError("WritableStream.close method can only be used on a being close WritableStream"),
+ );
+
+ return $writableStreamClose(stream);
+}
+
+export function writableStreamAbortForBindings(stream, reason) {
+ if ($isWritableStreamLocked(stream))
+ return Promise.$reject($makeTypeError("WritableStream.abort method can only be used on non locked WritableStream"));
+
+ return $writableStreamAbort(stream, reason);
+}
+
+export function isWritableStreamLocked(stream) {
+ return $getByIdDirectPrivate(stream, "writer") !== undefined;
+}
+
+export function setUpWritableStreamDefaultWriter(writer, stream) {
+ if ($isWritableStreamLocked(stream)) $throwTypeError("WritableStream is locked");
+
+ $putByIdDirectPrivate(writer, "stream", stream);
+ $putByIdDirectPrivate(stream, "writer", writer);
+
+ const readyPromiseCapability = $newPromiseCapability(Promise);
+ const closedPromiseCapability = $newPromiseCapability(Promise);
+ $putByIdDirectPrivate(writer, "readyPromise", readyPromiseCapability);
+ $putByIdDirectPrivate(writer, "closedPromise", closedPromiseCapability);
+
+ const state = $getByIdDirectPrivate(stream, "state");
+ if (state === "writable") {
+ if ($writableStreamCloseQueuedOrInFlight(stream) || !$getByIdDirectPrivate(stream, "backpressure"))
+ readyPromiseCapability.$resolve.$call();
+ } else if (state === "erroring") {
+ readyPromiseCapability.$reject.$call(undefined, $getByIdDirectPrivate(stream, "storedError"));
+ $markPromiseAsHandled(readyPromiseCapability.$promise);
+ } else if (state === "closed") {
+ readyPromiseCapability.$resolve.$call();
+ closedPromiseCapability.$resolve.$call();
+ } else {
+ $assert(state === "errored");
+ const storedError = $getByIdDirectPrivate(stream, "storedError");
+ readyPromiseCapability.$reject.$call(undefined, storedError);
+ $markPromiseAsHandled(readyPromiseCapability.$promise);
+ closedPromiseCapability.$reject.$call(undefined, storedError);
+ $markPromiseAsHandled(closedPromiseCapability.$promise);
+ }
+}
+
+export function writableStreamAbort(stream, reason) {
+ const state = $getByIdDirectPrivate(stream, "state");
+ if (state === "closed" || state === "errored") return Promise.$resolve();
+
+ const pendingAbortRequest = $getByIdDirectPrivate(stream, "pendingAbortRequest");
+ if (pendingAbortRequest !== undefined) return pendingAbortRequest.promise.$promise;
+
+ $assert(state === "writable" || state === "erroring");
+ let wasAlreadyErroring = false;
+ if (state === "erroring") {
+ wasAlreadyErroring = true;
+ reason = undefined;
+ }
+
+ const abortPromiseCapability = $newPromiseCapability(Promise);
+ $putByIdDirectPrivate(stream, "pendingAbortRequest", {
+ promise: abortPromiseCapability,
+ reason: reason,
+ wasAlreadyErroring: wasAlreadyErroring,
+ });
+
+ if (!wasAlreadyErroring) $writableStreamStartErroring(stream, reason);
+ return abortPromiseCapability.$promise;
+}
+
+export function writableStreamClose(stream) {
+ const state = $getByIdDirectPrivate(stream, "state");
+ if (state === "closed" || state === "errored")
+ return Promise.$reject($makeTypeError("Cannot close a writable stream that is closed or errored"));
+
+ $assert(state === "writable" || state === "erroring");
+ $assert(!$writableStreamCloseQueuedOrInFlight(stream));
+
+ const closePromiseCapability = $newPromiseCapability(Promise);
+ $putByIdDirectPrivate(stream, "closeRequest", closePromiseCapability);
+
+ const writer = $getByIdDirectPrivate(stream, "writer");
+ if (writer !== undefined && $getByIdDirectPrivate(stream, "backpressure") && state === "writable")
+ $getByIdDirectPrivate(writer, "readyPromise").$resolve.$call();
+
+ $writableStreamDefaultControllerClose($getByIdDirectPrivate(stream, "controller"));
+
+ return closePromiseCapability.$promise;
+}
+
+export function writableStreamAddWriteRequest(stream) {
+ $assert($isWritableStreamLocked(stream));
+ $assert($getByIdDirectPrivate(stream, "state") === "writable");
+
+ const writePromiseCapability = $newPromiseCapability(Promise);
+ const writeRequests = $getByIdDirectPrivate(stream, "writeRequests");
+ writeRequests.push(writePromiseCapability);
+ return writePromiseCapability.$promise;
+}
+
+export function writableStreamCloseQueuedOrInFlight(stream) {
+ return (
+ $getByIdDirectPrivate(stream, "closeRequest") !== undefined ||
+ $getByIdDirectPrivate(stream, "inFlightCloseRequest") !== undefined
+ );
+}
+
+export function writableStreamDealWithRejection(stream, error) {
+ const state = $getByIdDirectPrivate(stream, "state");
+ if (state === "writable") {
+ $writableStreamStartErroring(stream, error);
+ return;
+ }
+
+ $assert(state === "erroring");
+ $writableStreamFinishErroring(stream);
+}
+
+export function writableStreamFinishErroring(stream) {
+ $assert($getByIdDirectPrivate(stream, "state") === "erroring");
+ $assert(!$writableStreamHasOperationMarkedInFlight(stream));
+
+ $putByIdDirectPrivate(stream, "state", "errored");
+
+ const controller = $getByIdDirectPrivate(stream, "controller");
+ $getByIdDirectPrivate(controller, "errorSteps").$call();
+
+ const storedError = $getByIdDirectPrivate(stream, "storedError");
+ const requests = $getByIdDirectPrivate(stream, "writeRequests");
+ for (var request = requests.shift(); request; request = requests.shift())
+ request.$reject.$call(undefined, storedError);
+
+ // TODO: is this still necessary?
+ $putByIdDirectPrivate(stream, "writeRequests", $createFIFO());
+
+ const abortRequest = $getByIdDirectPrivate(stream, "pendingAbortRequest");
+ if (abortRequest === undefined) {
+ $writableStreamRejectCloseAndClosedPromiseIfNeeded(stream);
+ return;
+ }
+
+ $putByIdDirectPrivate(stream, "pendingAbortRequest", undefined);
+ if (abortRequest.wasAlreadyErroring) {
+ abortRequest.promise.$reject.$call(undefined, storedError);
+ $writableStreamRejectCloseAndClosedPromiseIfNeeded(stream);
+ return;
+ }
+
+ $getByIdDirectPrivate(controller, "abortSteps")
+ .$call(undefined, abortRequest.reason)
+ .$then(
+ () => {
+ abortRequest.promise.$resolve.$call();
+ $writableStreamRejectCloseAndClosedPromiseIfNeeded(stream);
+ },
+ reason => {
+ abortRequest.promise.$reject.$call(undefined, reason);
+ $writableStreamRejectCloseAndClosedPromiseIfNeeded(stream);
+ },
+ );
+}
+
+export function writableStreamFinishInFlightClose(stream) {
+ const inFlightCloseRequest = $getByIdDirectPrivate(stream, "inFlightCloseRequest");
+ inFlightCloseRequest.$resolve.$call();
+
+ $putByIdDirectPrivate(stream, "inFlightCloseRequest", undefined);
+
+ const state = $getByIdDirectPrivate(stream, "state");
+ $assert(state === "writable" || state === "erroring");
+
+ if (state === "erroring") {
+ $putByIdDirectPrivate(stream, "storedError", undefined);
+ const abortRequest = $getByIdDirectPrivate(stream, "pendingAbortRequest");
+ if (abortRequest !== undefined) {
+ abortRequest.promise.$resolve.$call();
+ $putByIdDirectPrivate(stream, "pendingAbortRequest", undefined);
+ }
+ }
+
+ $putByIdDirectPrivate(stream, "state", "closed");
+
+ const writer = $getByIdDirectPrivate(stream, "writer");
+ if (writer !== undefined) $getByIdDirectPrivate(writer, "closedPromise").$resolve.$call();
+
+ $assert($getByIdDirectPrivate(stream, "pendingAbortRequest") === undefined);
+ $assert($getByIdDirectPrivate(stream, "storedError") === undefined);
+}
+
+export function writableStreamFinishInFlightCloseWithError(stream, error) {
+ const inFlightCloseRequest = $getByIdDirectPrivate(stream, "inFlightCloseRequest");
+ $assert(inFlightCloseRequest !== undefined);
+ inFlightCloseRequest.$reject.$call(undefined, error);
+
+ $putByIdDirectPrivate(stream, "inFlightCloseRequest", undefined);
+
+ const state = $getByIdDirectPrivate(stream, "state");
+ $assert(state === "writable" || state === "erroring");
+
+ const abortRequest = $getByIdDirectPrivate(stream, "pendingAbortRequest");
+ if (abortRequest !== undefined) {
+ abortRequest.promise.$reject.$call(undefined, error);
+ $putByIdDirectPrivate(stream, "pendingAbortRequest", undefined);
+ }
+
+ $writableStreamDealWithRejection(stream, error);
+}
+
+export function writableStreamFinishInFlightWrite(stream) {
+ const inFlightWriteRequest = $getByIdDirectPrivate(stream, "inFlightWriteRequest");
+ $assert(inFlightWriteRequest !== undefined);
+ inFlightWriteRequest.$resolve.$call();
+
+ $putByIdDirectPrivate(stream, "inFlightWriteRequest", undefined);
+}
+
+export function writableStreamFinishInFlightWriteWithError(stream, error) {
+ const inFlightWriteRequest = $getByIdDirectPrivate(stream, "inFlightWriteRequest");
+ $assert(inFlightWriteRequest !== undefined);
+ inFlightWriteRequest.$reject.$call(undefined, error);
+
+ $putByIdDirectPrivate(stream, "inFlightWriteRequest", undefined);
+
+ const state = $getByIdDirectPrivate(stream, "state");
+ $assert(state === "writable" || state === "erroring");
+
+ $writableStreamDealWithRejection(stream, error);
+}
+
+export function writableStreamHasOperationMarkedInFlight(stream) {
+ return (
+ $getByIdDirectPrivate(stream, "inFlightWriteRequest") !== undefined ||
+ $getByIdDirectPrivate(stream, "inFlightCloseRequest") !== undefined
+ );
+}
+
+export function writableStreamMarkCloseRequestInFlight(stream) {
+ const closeRequest = $getByIdDirectPrivate(stream, "closeRequest");
+ $assert($getByIdDirectPrivate(stream, "inFlightCloseRequest") === undefined);
+ $assert(closeRequest !== undefined);
+
+ $putByIdDirectPrivate(stream, "inFlightCloseRequest", closeRequest);
+ $putByIdDirectPrivate(stream, "closeRequest", undefined);
+}
+
+export function writableStreamMarkFirstWriteRequestInFlight(stream) {
+ const writeRequests = $getByIdDirectPrivate(stream, "writeRequests");
+ $assert($getByIdDirectPrivate(stream, "inFlightWriteRequest") === undefined);
+ $assert(writeRequests.isNotEmpty());
+
+ const writeRequest = writeRequests.shift();
+ $putByIdDirectPrivate(stream, "inFlightWriteRequest", writeRequest);
+}
+
+export function writableStreamRejectCloseAndClosedPromiseIfNeeded(stream) {
+ $assert($getByIdDirectPrivate(stream, "state") === "errored");
+
+ const storedError = $getByIdDirectPrivate(stream, "storedError");
+
+ const closeRequest = $getByIdDirectPrivate(stream, "closeRequest");
+ if (closeRequest !== undefined) {
+ $assert($getByIdDirectPrivate(stream, "inFlightCloseRequest") === undefined);
+ closeRequest.$reject.$call(undefined, storedError);
+ $putByIdDirectPrivate(stream, "closeRequest", undefined);
+ }
+
+ const writer = $getByIdDirectPrivate(stream, "writer");
+ if (writer !== undefined) {
+ const closedPromise = $getByIdDirectPrivate(writer, "closedPromise");
+ closedPromise.$reject.$call(undefined, storedError);
+ $markPromiseAsHandled(closedPromise.$promise);
+ }
+}
+
+export function writableStreamStartErroring(stream, reason) {
+ $assert($getByIdDirectPrivate(stream, "storedError") === undefined);
+ $assert($getByIdDirectPrivate(stream, "state") === "writable");
+
+ const controller = $getByIdDirectPrivate(stream, "controller");
+ $assert(controller !== undefined);
+
+ $putByIdDirectPrivate(stream, "state", "erroring");
+ $putByIdDirectPrivate(stream, "storedError", reason);
+
+ const writer = $getByIdDirectPrivate(stream, "writer");
+ if (writer !== undefined) $writableStreamDefaultWriterEnsureReadyPromiseRejected(writer, reason);
+
+ if (!$writableStreamHasOperationMarkedInFlight(stream) && $getByIdDirectPrivate(controller, "started") === 1)
+ $writableStreamFinishErroring(stream);
+}
+
+export function writableStreamUpdateBackpressure(stream, backpressure) {
+ $assert($getByIdDirectPrivate(stream, "state") === "writable");
+ $assert(!$writableStreamCloseQueuedOrInFlight(stream));
+
+ const writer = $getByIdDirectPrivate(stream, "writer");
+ if (writer !== undefined && backpressure !== $getByIdDirectPrivate(stream, "backpressure")) {
+ if (backpressure) $putByIdDirectPrivate(writer, "readyPromise", $newPromiseCapability(Promise));
+ else $getByIdDirectPrivate(writer, "readyPromise").$resolve.$call();
+ }
+ $putByIdDirectPrivate(stream, "backpressure", backpressure);
+}
+
+export function writableStreamDefaultWriterAbort(writer, reason) {
+ const stream = $getByIdDirectPrivate(writer, "stream");
+ $assert(stream !== undefined);
+ return $writableStreamAbort(stream, reason);
+}
+
+export function writableStreamDefaultWriterClose(writer) {
+ const stream = $getByIdDirectPrivate(writer, "stream");
+ $assert(stream !== undefined);
+ return $writableStreamClose(stream);
+}
+
+export function writableStreamDefaultWriterCloseWithErrorPropagation(writer) {
+ const stream = $getByIdDirectPrivate(writer, "stream");
+ $assert(stream !== undefined);
+
+ const state = $getByIdDirectPrivate(stream, "state");
+
+ if ($writableStreamCloseQueuedOrInFlight(stream) || state === "closed") return Promise.$resolve();
+
+ if (state === "errored") return Promise.$reject($getByIdDirectPrivate(stream, "storedError"));
+
+ $assert(state === "writable" || state === "erroring");
+ return $writableStreamDefaultWriterClose(writer);
+}
+
+export function writableStreamDefaultWriterEnsureClosedPromiseRejected(writer, error) {
+ let closedPromiseCapability = $getByIdDirectPrivate(writer, "closedPromise");
+ let closedPromise = closedPromiseCapability.$promise;
+
+ if (($getPromiseInternalField(closedPromise, $promiseFieldFlags) & $promiseStateMask) !== $promiseStatePending) {
+ closedPromiseCapability = $newPromiseCapability(Promise);
+ closedPromise = closedPromiseCapability.$promise;
+ $putByIdDirectPrivate(writer, "closedPromise", closedPromiseCapability);
+ }
+
+ closedPromiseCapability.$reject.$call(undefined, error);
+ $markPromiseAsHandled(closedPromise);
+}
+
+export function writableStreamDefaultWriterEnsureReadyPromiseRejected(writer, error) {
+ let readyPromiseCapability = $getByIdDirectPrivate(writer, "readyPromise");
+ let readyPromise = readyPromiseCapability.$promise;
+
+ if (($getPromiseInternalField(readyPromise, $promiseFieldFlags) & $promiseStateMask) !== $promiseStatePending) {
+ readyPromiseCapability = $newPromiseCapability(Promise);
+ readyPromise = readyPromiseCapability.$promise;
+ $putByIdDirectPrivate(writer, "readyPromise", readyPromiseCapability);
+ }
+
+ readyPromiseCapability.$reject.$call(undefined, error);
+ $markPromiseAsHandled(readyPromise);
+}
+
+export function writableStreamDefaultWriterGetDesiredSize(writer) {
+ const stream = $getByIdDirectPrivate(writer, "stream");
+ $assert(stream !== undefined);
+
+ const state = $getByIdDirectPrivate(stream, "state");
+
+ if (state === "errored" || state === "erroring") return null;
+
+ if (state === "closed") return 0;
+
+ return $writableStreamDefaultControllerGetDesiredSize($getByIdDirectPrivate(stream, "controller"));
+}
+
+export function writableStreamDefaultWriterRelease(writer) {
+ const stream = $getByIdDirectPrivate(writer, "stream");
+ $assert(stream !== undefined);
+ $assert($getByIdDirectPrivate(stream, "writer") === writer);
+
+ const releasedError = $makeTypeError("writableStreamDefaultWriterRelease");
+
+ $writableStreamDefaultWriterEnsureReadyPromiseRejected(writer, releasedError);
+ $writableStreamDefaultWriterEnsureClosedPromiseRejected(writer, releasedError);
+
+ $putByIdDirectPrivate(stream, "writer", undefined);
+ $putByIdDirectPrivate(writer, "stream", undefined);
+}
+
+export function writableStreamDefaultWriterWrite(writer, chunk) {
+ const stream = $getByIdDirectPrivate(writer, "stream");
+ $assert(stream !== undefined);
+
+ const controller = $getByIdDirectPrivate(stream, "controller");
+ $assert(controller !== undefined);
+ const chunkSize = $writableStreamDefaultControllerGetChunkSize(controller, chunk);
+
+ if (stream !== $getByIdDirectPrivate(writer, "stream"))
+ return Promise.$reject($makeTypeError("writer is not stream's writer"));
+
+ const state = $getByIdDirectPrivate(stream, "state");
+ if (state === "errored") return Promise.$reject($getByIdDirectPrivate(stream, "storedError"));
+
+ if ($writableStreamCloseQueuedOrInFlight(stream) || state === "closed")
+ return Promise.$reject($makeTypeError("stream is closing or closed"));
+
+ if ($writableStreamCloseQueuedOrInFlight(stream) || state === "closed")
+ return Promise.$reject($makeTypeError("stream is closing or closed"));
+
+ if (state === "erroring") return Promise.$reject($getByIdDirectPrivate(stream, "storedError"));
+
+ $assert(state === "writable");
+
+ const promise = $writableStreamAddWriteRequest(stream);
+ $writableStreamDefaultControllerWrite(controller, chunk, chunkSize);
+ return promise;
+}
+
+export function setUpWritableStreamDefaultController(
+ stream,
+ controller,
+ startAlgorithm,
+ writeAlgorithm,
+ closeAlgorithm,
+ abortAlgorithm,
+ highWaterMark,
+ sizeAlgorithm,
+) {
+ $assert($isWritableStream(stream));
+ $assert($getByIdDirectPrivate(stream, "controller") === undefined);
+
+ $putByIdDirectPrivate(controller, "stream", stream);
+ $putByIdDirectPrivate(stream, "controller", controller);
+
+ $resetQueue($getByIdDirectPrivate(controller, "queue"));
+
+ $putByIdDirectPrivate(controller, "started", -1);
+ $putByIdDirectPrivate(controller, "startAlgorithm", startAlgorithm);
+ $putByIdDirectPrivate(controller, "strategySizeAlgorithm", sizeAlgorithm);
+ $putByIdDirectPrivate(controller, "strategyHWM", highWaterMark);
+ $putByIdDirectPrivate(controller, "writeAlgorithm", writeAlgorithm);
+ $putByIdDirectPrivate(controller, "closeAlgorithm", closeAlgorithm);
+ $putByIdDirectPrivate(controller, "abortAlgorithm", abortAlgorithm);
+
+ const backpressure = $writableStreamDefaultControllerGetBackpressure(controller);
+ $writableStreamUpdateBackpressure(stream, backpressure);
+
+ $writableStreamDefaultControllerStart(controller);
+}
+
+export function writableStreamDefaultControllerStart(controller) {
+ if ($getByIdDirectPrivate(controller, "started") !== -1) return;
+
+ $putByIdDirectPrivate(controller, "started", 0);
+
+ const startAlgorithm = $getByIdDirectPrivate(controller, "startAlgorithm");
+ $putByIdDirectPrivate(controller, "startAlgorithm", undefined);
+ const stream = $getByIdDirectPrivate(controller, "stream");
+ return Promise.$resolve(startAlgorithm.$call()).$then(
+ () => {
+ const state = $getByIdDirectPrivate(stream, "state");
+ $assert(state === "writable" || state === "erroring");
+ $putByIdDirectPrivate(controller, "started", 1);
+ $writableStreamDefaultControllerAdvanceQueueIfNeeded(controller);
+ },
+ error => {
+ const state = $getByIdDirectPrivate(stream, "state");
+ $assert(state === "writable" || state === "erroring");
+ $putByIdDirectPrivate(controller, "started", 1);
+ $writableStreamDealWithRejection(stream, error);
+ },
+ );
+}
+
+export function setUpWritableStreamDefaultControllerFromUnderlyingSink(
+ stream,
+ underlyingSink,
+ underlyingSinkDict,
+ highWaterMark,
+ sizeAlgorithm,
+) {
+ const controller = new $WritableStreamDefaultController();
+
+ let startAlgorithm = () => {};
+ let writeAlgorithm = () => {
+ return Promise.$resolve();
+ };
+ let closeAlgorithm = () => {
+ return Promise.$resolve();
+ };
+ let abortAlgorithm = () => {
+ return Promise.$resolve();
+ };
+
+ if ("start" in underlyingSinkDict) {
+ const startMethod = underlyingSinkDict["start"];
+ startAlgorithm = () => $promiseInvokeOrNoopMethodNoCatch(underlyingSink, startMethod, [controller]);
+ }
+ if ("write" in underlyingSinkDict) {
+ const writeMethod = underlyingSinkDict["write"];
+ writeAlgorithm = chunk => $promiseInvokeOrNoopMethod(underlyingSink, writeMethod, [chunk, controller]);
+ }
+ if ("close" in underlyingSinkDict) {
+ const closeMethod = underlyingSinkDict["close"];
+ closeAlgorithm = () => $promiseInvokeOrNoopMethod(underlyingSink, closeMethod, []);
+ }
+ if ("abort" in underlyingSinkDict) {
+ const abortMethod = underlyingSinkDict["abort"];
+ abortAlgorithm = reason => $promiseInvokeOrNoopMethod(underlyingSink, abortMethod, [reason]);
+ }
+
+ $setUpWritableStreamDefaultController(
+ stream,
+ controller,
+ startAlgorithm,
+ writeAlgorithm,
+ closeAlgorithm,
+ abortAlgorithm,
+ highWaterMark,
+ sizeAlgorithm,
+ );
+}
+
+export function writableStreamDefaultControllerAdvanceQueueIfNeeded(controller) {
+ const stream = $getByIdDirectPrivate(controller, "stream");
+
+ if ($getByIdDirectPrivate(controller, "started") !== 1) return;
+
+ $assert(stream !== undefined);
+ if ($getByIdDirectPrivate(stream, "inFlightWriteRequest") !== undefined) return;
+
+ const state = $getByIdDirectPrivate(stream, "state");
+ $assert(state !== "closed" || state !== "errored");
+ if (state === "erroring") {
+ $writableStreamFinishErroring(stream);
+ return;
+ }
+
+ const queue = $getByIdDirectPrivate(controller, "queue");
+
+ if (queue.content?.isEmpty() ?? false) return;
+
+ const value = $peekQueueValue(queue);
+ if (value === $isCloseSentinel) $writableStreamDefaultControllerProcessClose(controller);
+ else $writableStreamDefaultControllerProcessWrite(controller, value);
+}
+
+export function isCloseSentinel() {}
+
+export function writableStreamDefaultControllerClearAlgorithms(controller) {
+ $putByIdDirectPrivate(controller, "writeAlgorithm", undefined);
+ $putByIdDirectPrivate(controller, "closeAlgorithm", undefined);
+ $putByIdDirectPrivate(controller, "abortAlgorithm", undefined);
+ $putByIdDirectPrivate(controller, "strategySizeAlgorithm", undefined);
+}
+
+export function writableStreamDefaultControllerClose(controller) {
+ $enqueueValueWithSize($getByIdDirectPrivate(controller, "queue"), $isCloseSentinel, 0);
+ $writableStreamDefaultControllerAdvanceQueueIfNeeded(controller);
+}
+
+export function writableStreamDefaultControllerError(controller, error) {
+ const stream = $getByIdDirectPrivate(controller, "stream");
+ $assert(stream !== undefined);
+ $assert($getByIdDirectPrivate(stream, "state") === "writable");
+
+ $writableStreamDefaultControllerClearAlgorithms(controller);
+ $writableStreamStartErroring(stream, error);
+}
+
+export function writableStreamDefaultControllerErrorIfNeeded(controller, error) {
+ const stream = $getByIdDirectPrivate(controller, "stream");
+ if ($getByIdDirectPrivate(stream, "state") === "writable") $writableStreamDefaultControllerError(controller, error);
+}
+
+export function writableStreamDefaultControllerGetBackpressure(controller) {
+ const desiredSize = $writableStreamDefaultControllerGetDesiredSize(controller);
+ return desiredSize <= 0;
+}
+
+export function writableStreamDefaultControllerGetChunkSize(controller, chunk) {
+ try {
+ return $getByIdDirectPrivate(controller, "strategySizeAlgorithm").$call(undefined, chunk);
+ } catch (e) {
+ $writableStreamDefaultControllerErrorIfNeeded(controller, e);
+ return 1;
+ }
+}
+
+export function writableStreamDefaultControllerGetDesiredSize(controller) {
+ return $getByIdDirectPrivate(controller, "strategyHWM") - $getByIdDirectPrivate(controller, "queue").size;
+}
+
+export function writableStreamDefaultControllerProcessClose(controller) {
+ const stream = $getByIdDirectPrivate(controller, "stream");
+
+ $writableStreamMarkCloseRequestInFlight(stream);
+ $dequeueValue($getByIdDirectPrivate(controller, "queue"));
+
+ $assert($getByIdDirectPrivate(controller, "queue").content?.isEmpty());
+
+ const sinkClosePromise = $getByIdDirectPrivate(controller, "closeAlgorithm").$call();
+ $writableStreamDefaultControllerClearAlgorithms(controller);
+
+ sinkClosePromise.$then(
+ () => {
+ $writableStreamFinishInFlightClose(stream);
+ },
+ reason => {
+ $writableStreamFinishInFlightCloseWithError(stream, reason);
+ },
+ );
+}
+
+export function writableStreamDefaultControllerProcessWrite(controller, chunk) {
+ const stream = $getByIdDirectPrivate(controller, "stream");
+
+ $writableStreamMarkFirstWriteRequestInFlight(stream);
+
+ const sinkWritePromise = $getByIdDirectPrivate(controller, "writeAlgorithm").$call(undefined, chunk);
+
+ sinkWritePromise.$then(
+ () => {
+ $writableStreamFinishInFlightWrite(stream);
+ const state = $getByIdDirectPrivate(stream, "state");
+ $assert(state === "writable" || state === "erroring");
+
+ $dequeueValue($getByIdDirectPrivate(controller, "queue"));
+ if (!$writableStreamCloseQueuedOrInFlight(stream) && state === "writable") {
+ const backpressure = $writableStreamDefaultControllerGetBackpressure(controller);
+ $writableStreamUpdateBackpressure(stream, backpressure);
+ }
+ $writableStreamDefaultControllerAdvanceQueueIfNeeded(controller);
+ },
+ reason => {
+ const state = $getByIdDirectPrivate(stream, "state");
+ if (state === "writable") $writableStreamDefaultControllerClearAlgorithms(controller);
+
+ $writableStreamFinishInFlightWriteWithError(stream, reason);
+ },
+ );
+}
+
+export function writableStreamDefaultControllerWrite(controller, chunk, chunkSize) {
+ try {
+ $enqueueValueWithSize($getByIdDirectPrivate(controller, "queue"), chunk, chunkSize);
+
+ const stream = $getByIdDirectPrivate(controller, "stream");
+
+ const state = $getByIdDirectPrivate(stream, "state");
+ if (!$writableStreamCloseQueuedOrInFlight(stream) && state === "writable") {
+ const backpressure = $writableStreamDefaultControllerGetBackpressure(controller);
+ $writableStreamUpdateBackpressure(stream, backpressure);
+ }
+ $writableStreamDefaultControllerAdvanceQueueIfNeeded(controller);
+ } catch (e) {
+ $writableStreamDefaultControllerErrorIfNeeded(controller, e);
+ }
+}
diff --git a/src/js/builtins/builtins.d.ts b/src/js/builtins/builtins.d.ts
new file mode 100644
index 000000000..d2e7037dc
--- /dev/null
+++ b/src/js/builtins/builtins.d.ts
@@ -0,0 +1,492 @@
+// Typedefs for JSC intrinsics. Instead of @, we use $
+type TODO = any;
+
+/** Place this directly above a function declaration (like a decorator) to make it a getter. */
+declare const $getter: never;
+/** Assign to this directly above a function declaration (like a decorator) to override the function's display name. */
+declare var $overriddenName: string;
+/** ??? */
+declare var $linkTimeConstant: never;
+/** Assign to this directly above a function declaration (like a decorator) to set visibility */
+declare var $visibility: "Public" | "Private";
+/** ??? */
+declare var $nakedConstructor: never;
+/** Assign to this directly above a function declaration (like a decorator) to set intrinsic */
+declare var $intrinsic: string;
+declare var $constructor;
+/** Place this directly above a function declaration (like a decorator) to NOT include "use strict" */
+declare var $sloppy;
+
+declare function $extractHighWaterMarkFromQueuingStrategyInit(obj: any): any;
+
+// JSC defines their intrinsics in a nice list here:
+// https://github.com/WebKit/WebKit/blob/main/Source/JavaScriptCore/bytecode/BytecodeIntrinsicRegistry.h
+//
+// And implemented here: (search for "emit_intrinsic_<name>", like "emit_intrinsic_arrayPush")
+// https://github.com/WebKit/WebKit/blob/main/Source/JavaScriptCore/bytecompiler/NodesCodegen.cpp
+
+/** Assert a value is true */
+declare function $assert(index: any): void;
+/** returns `arguments[index]` */
+declare function $argument<T = any>(index: number): any;
+/** returns number of arguments */
+declare function $argumentCount(): number;
+/** array.push(item) */
+declare function $arrayPush(array: T[], item: T): void;
+/** gets a property on an object */
+declare function $getByIdDirect<T = any>(obj: any, key: string): T;
+/**
+ * gets a private property on an object. translates to the `op_get_by_id_direct` bytecode.
+ *
+ * TODO: clarify what private means exactly.
+ */
+declare function $getByIdDirectPrivate<T = any>(obj: any, key: string): T;
+/**
+ * gets a property on an object
+ */
+declare function $getByValWithThis(target: any, receiver: any, propertyKey: string): void;
+/** gets the prototype of an object */
+declare function $getPrototypeOf(value: any): any;
+/** gets an internal property on a promise
+ *
+ * You can pass
+ * - $promiseFieldFlags - get a number with flags
+ * - $promiseFieldReactionsOrResult - get the result (like Bun.peek)
+ */
+declare function $getPromiseInternalField<K extends PromiseFieldType, V>(
+ promise: Promise<V>,
+ key: K,
+): PromiseFieldToValue<K, V>;
+declare function $getGeneratorInternalField(): TODO;
+declare function $getAsyncGeneratorInternalField(): TODO;
+declare function $getAbstractModuleRecordInternalField(): TODO;
+declare function $getArrayIteratorInternalField(): TODO;
+declare function $getStringIteratorInternalField(): TODO;
+declare function $getMapIteratorInternalField(): TODO;
+declare function $getSetIteratorInternalField(): TODO;
+declare function $getProxyInternalField(): TODO;
+declare function $idWithProfile(): TODO;
+declare function $isObject(obj: unknown): obj is object;
+declare function $isCallable(fn: unknown): fn is CallableFunction;
+declare function $isConstructor(fn: unknown): fn is { new (...args: any[]): any };
+declare function $isJSArray(obj: unknown): obj is any[];
+declare function $isProxyObject(obj: unknown): obj is Proxy;
+declare function $isDerivedArray(): TODO;
+declare function $isGenerator(obj: unknown): obj is Generator<any, any, any>;
+declare function $isAsyncGenerator(obj: unknown): obj is AsyncGenerator<any, any, any>;
+declare function $isPromise(obj: unknown): obj is Promise<any>;
+declare function $isRegExpObject(obj: unknown): obj is RegExp;
+declare function $isMap<K, V>(obj: unknown): obj is Map<K, V>;
+declare function $isSet<V>(obj: unknown): obj is Set<V>;
+declare function $isShadowRealm(obj: unknown): obj is ShadowRealm;
+declare function $isStringIterator(obj: unknown): obj is Iterator<string>;
+declare function $isArrayIterator(obj: unknown): obj is Iterator<any>;
+declare function $isMapIterator(obj: unknown): obj is Iterator<any>;
+declare function $isSetIterator(obj: unknown): obj is Iterator<any>;
+declare function $isUndefinedOrNull(obj: unknown): obj is null | undefined;
+declare function $tailCallForwardArguments(): TODO;
+/**
+ * **NOTE** - use `throw new TypeError()` instead. it compiles to the same builtin
+ * @deprecated
+ */
+declare function $throwTypeError(message: string): never;
+/**
+ * **NOTE** - use `throw new RangeError()` instead. it compiles to the same builtin
+ * @deprecated
+ */
+declare function $throwRangeError(message: string): never;
+/**
+ * **NOTE** - use `throw new OutOfMemoryError()` instead. it compiles to the same builtin
+ * @deprecated
+ */
+declare function $throwOutOfMemoryError(): never;
+declare function $tryGetById(): TODO;
+declare function $tryGetByIdWithWellKnownSymbol(obj: any, key: WellKnownSymbol): any;
+declare function $putByIdDirect(obj: any, key: PropertyKey, value: any): void;
+declare function $putByIdDirectPrivate(obj: any, key: PropertyKey, value: any): void;
+declare function $putByValDirect(obj: any, key: PropertyKey, value: any): void;
+declare function $putByValWithThisSloppy(): TODO;
+declare function $putByValWithThisStrict(): TODO;
+declare function $putPromiseInternalField<T extends PromiseFieldType, P extends Promise<any>>(
+ promise: P,
+ key: T,
+ value: PromiseFieldToValue<T, P>,
+): void;
+declare function $putGeneratorInternalField(): TODO;
+declare function $putAsyncGeneratorInternalField(): TODO;
+declare function $putArrayIteratorInternalField(): TODO;
+declare function $putStringIteratorInternalField(): TODO;
+declare function $putMapIteratorInternalField(): TODO;
+declare function $putSetIteratorInternalField(): TODO;
+declare function $superSamplerBegin(): TODO;
+declare function $superSamplerEnd(): TODO;
+declare function $toNumber(x: any): number;
+declare function $toString(x: any): string;
+declare function $toPropertyKey(x: any): PropertyKey;
+/**
+ * Often used like
+ * `$toObject(this, "Class.prototype.method requires that |this| not be null or undefined");`
+ */
+declare function $toObject(object: any, errorMessage?: string): object;
+declare function $newArrayWithSize<T>(size: number): T[];
+declare function $newArrayWithSpecies(): TODO;
+declare function $newPromise(): TODO;
+declare function $createPromise(): TODO;
+declare const $iterationKindKey: TODO;
+declare const $iterationKindValue: TODO;
+declare const $iterationKindEntries: TODO;
+declare const $MAX_ARRAY_INDEX: number;
+declare const $MAX_STRING_LENGTH: number;
+declare const $MAX_SAFE_INTEGER: number;
+declare const $ModuleFetch: number;
+declare const $ModuleTranslate: number;
+declare const $ModuleInstantiate: number;
+declare const $ModuleSatisfy: number;
+declare const $ModuleLink: number;
+declare const $ModuleReady: number;
+declare const $promiseRejectionReject: TODO;
+declare const $promiseRejectionHandle: TODO;
+declare const $promiseStatePending: number;
+declare const $promiseStateFulfilled: number;
+declare const $promiseStateRejected: number;
+declare const $promiseStateMask: number;
+declare const $promiseFlagsIsHandled: number;
+declare const $promiseFlagsIsFirstResolvingFunctionCalled: number;
+declare const $promiseFieldFlags: unique symbol;
+declare const $promiseFieldReactionsOrResult: unique symbol;
+declare const $proxyFieldTarget: TODO;
+declare const $proxyFieldHandler: TODO;
+declare const $generatorFieldState: TODO;
+declare const $generatorFieldNext: TODO;
+declare const $generatorFieldThis: TODO;
+declare const $generatorFieldFrame: TODO;
+declare const $generatorFieldContext: TODO;
+declare const $GeneratorResumeModeNormal: TODO;
+declare const $GeneratorResumeModeThrow: TODO;
+declare const $GeneratorResumeModeReturn: TODO;
+declare const $GeneratorStateCompleted: TODO;
+declare const $GeneratorStateExecuting: TODO;
+declare const $arrayIteratorFieldIndex: TODO;
+declare const $arrayIteratorFieldIteratedObject: TODO;
+declare const $arrayIteratorFieldKind: TODO;
+declare const $mapIteratorFieldMapBucket: TODO;
+declare const $mapIteratorFieldKind: TODO;
+declare const $setIteratorFieldSetBucket: TODO;
+declare const $setIteratorFieldKind: TODO;
+declare const $stringIteratorFieldIndex: TODO;
+declare const $stringIteratorFieldIteratedString: TODO;
+declare const $asyncGeneratorFieldSuspendReason: TODO;
+declare const $asyncGeneratorFieldQueueFirst: TODO;
+declare const $asyncGeneratorFieldQueueLast: TODO;
+declare const $AsyncGeneratorStateCompleted: TODO;
+declare const $AsyncGeneratorStateExecuting: TODO;
+declare const $AsyncGeneratorStateAwaitingReturn: TODO;
+declare const $AsyncGeneratorStateSuspendedStart: TODO;
+declare const $AsyncGeneratorStateSuspendedYield: TODO;
+declare const $AsyncGeneratorSuspendReasonYield: TODO;
+declare const $AsyncGeneratorSuspendReasonAwait: TODO;
+declare const $AsyncGeneratorSuspendReasonNone: TODO;
+declare const $abstractModuleRecordFieldState: TODO;
+
+// We define our intrinsics in ./BunBuiltinNames.h. Some of those are globals.
+
+declare var $_events: TODO;
+declare function $abortAlgorithm(): TODO;
+declare function $abortSteps(): TODO;
+declare function $addEventListener(): TODO;
+declare function $appendFromJS(): TODO;
+declare function $argv(): TODO;
+declare function $assignToStream(): TODO;
+declare function $associatedReadableByteStreamController(): TODO;
+declare function $autoAllocateChunkSize(): TODO;
+declare function $backpressure(): TODO;
+declare function $backpressureChangePromise(): TODO;
+declare function $basename(): TODO;
+declare function $body(): TODO;
+declare function $bunNativePtr(): TODO;
+declare function $bunNativeType(): TODO;
+declare function $byobRequest(): TODO;
+declare function $cancel(): TODO;
+declare function $cancelAlgorithm(): TODO;
+declare function $chdir(): TODO;
+declare function $cloneArrayBuffer(a, b, c): TODO;
+declare function $close(): TODO;
+declare function $closeAlgorithm(): TODO;
+declare function $closeRequest(): TODO;
+declare function $closeRequested(): TODO;
+declare function $closed(): TODO;
+declare function $closedPromise(): TODO;
+declare function $closedPromiseCapability(): TODO;
+declare function $code(): TODO;
+declare const $commonJSSymbol: unique symbol;
+declare function $connect(): TODO;
+declare function $consumeReadableStream(): TODO;
+declare function $controlledReadableStream(): TODO;
+declare function $controller(): TODO;
+declare function $cork(): TODO;
+declare function $createEmptyReadableStream(): TODO;
+declare function $createFIFO(): TODO;
+declare function $createNativeReadableStream(): TODO;
+declare function $createReadableStream(): TODO;
+declare function $createUninitializedArrayBuffer(size: number): ArrayBuffer;
+declare function $createWritableStreamFromInternal(): TODO;
+declare function $cwd(): TODO;
+declare function $data(): TODO;
+declare function $dataView(): TODO;
+declare function $decode(): TODO;
+declare function $delimiter(): TODO;
+declare function $destroy(): TODO;
+declare function $dir(): TODO;
+declare function $direct(): TODO;
+declare function $dirname(): TODO;
+declare function $disturbed(): TODO;
+declare function $document(): TODO;
+declare function $encode(): TODO;
+declare function $encoding(): TODO;
+declare function $end(): TODO;
+declare function $errno(): TODO;
+declare function $errorSteps(): TODO;
+declare function $execArgv(): TODO;
+declare function $extname(): TODO;
+declare function $failureKind(): TODO;
+declare function $fatal(): TODO;
+declare function $fetch(): TODO;
+declare function $fetchRequest(): TODO;
+declare function $file(): TODO;
+declare function $filePath(): TODO;
+declare function $fillFromJS(): TODO;
+declare function $filter(): TODO;
+declare function $finishConsumingStream(): TODO;
+declare function $flush(): TODO;
+declare function $flushAlgorithm(): TODO;
+declare function $format(): TODO;
+declare function $fulfillModuleSync(key: string): void;
+declare function $get(): TODO;
+declare function $getInternalWritableStream(writable: WritableStream): TODO;
+declare function $handleEvent(): TODO;
+declare function $hash(): TODO;
+declare function $header(): TODO;
+declare function $headers(): TODO;
+declare function $highWaterMark(): TODO;
+declare function $host(): TODO;
+declare function $hostname(): TODO;
+declare function $href(): TODO;
+declare function $ignoreBOM(): TODO;
+declare function $importer(): TODO;
+declare function $inFlightCloseRequest(): TODO;
+declare function $inFlightWriteRequest(): TODO;
+declare function $initializeWith(): TODO;
+declare function $internalRequire(path: string): TODO;
+declare function $internalStream(): TODO;
+declare function $internalWritable(): TODO;
+declare function $isAbortSignal(signal: unknown): signal is AbortSignal;
+declare function $isAbsolute(): TODO;
+declare function $isDisturbed(): TODO;
+declare function $isPaused(): TODO;
+declare function $isWindows(): TODO;
+declare function $join(): TODO;
+declare function $kind(): TODO;
+declare function $lazy(): TODO;
+declare function $lazyLoad(): TODO;
+declare function $lazyStreamPrototypeMap(): TODO;
+declare function $loadModule(): TODO;
+declare function $localStreams(): TODO;
+declare function $main(): TODO;
+declare function $makeDOMException(): TODO;
+declare function $makeGetterTypeError(className: string, prop: string): Error;
+declare function $makeThisTypeError(className: string, method: string): Error;
+declare function $map(): TODO;
+declare function $method(): TODO;
+declare function $nextTick(): TODO;
+declare function $normalize(): TODO;
+declare function $on(): TODO;
+declare function $once(): TODO;
+declare function $options(): TODO;
+declare function $origin(): TODO;
+declare function $ownerReadableStream(): TODO;
+declare function $parse(): TODO;
+declare function $password(): TODO;
+declare function $patch(): TODO;
+declare function $path(): TODO;
+declare function $pathname(): TODO;
+declare function $pause(): TODO;
+declare function $pendingAbortRequest(): TODO;
+declare function $pendingPullIntos(): TODO;
+declare function $pid(): TODO;
+declare function $pipe(): TODO;
+declare function $port(): TODO;
+declare function $post(): TODO;
+declare function $ppid(): TODO;
+declare function $prependEventListener(): TODO;
+declare function $process(): TODO;
+declare function $protocol(): TODO;
+declare function $pull(): TODO;
+declare function $pullAgain(): TODO;
+declare function $pullAlgorithm(): TODO;
+declare function $pulling(): TODO;
+declare function $put(): TODO;
+declare function $queue(): TODO;
+declare function $read(): TODO;
+declare function $readIntoRequests(): TODO;
+declare function $readRequests(): TODO;
+declare function $readable(): TODO;
+declare function $readableStreamController(): TODO;
+declare function $readableStreamToArray(): TODO;
+declare function $reader(): TODO;
+declare function $readyPromise(): TODO;
+declare function $readyPromiseCapability(): TODO;
+declare function $redirect(): TODO;
+declare function $relative(): TODO;
+declare function $releaseLock(): TODO;
+declare function $removeEventListener(): TODO;
+declare function $require(): TODO;
+declare function $requireESM(path: string): any;
+declare const $requireMap: Map<string, TODO>;
+declare function $resolve(name: string, from: string): Promise<string>;
+declare function $resolveSync(name: string, from: string): string;
+declare function $resume(): TODO;
+declare function $search(): TODO;
+declare function $searchParams(): TODO;
+declare function $self(): TODO;
+declare function $sep(): TODO;
+declare function $setBody(): TODO;
+declare function $setStatus(): TODO;
+declare function $setup(): TODO;
+declare function $sink(): TODO;
+declare function $size(): TODO;
+declare function $start(): TODO;
+declare function $startAlgorithm(): TODO;
+declare function $startConsumingStream(): TODO;
+declare function $startDirectStream(): TODO;
+declare function $started(): TODO;
+declare function $startedPromise(): TODO;
+declare function $state(): TODO;
+declare function $status(): TODO;
+declare function $storedError(): TODO;
+declare function $strategy(): TODO;
+declare function $strategyHWM(): TODO;
+declare function $strategySizeAlgorithm(): TODO;
+declare function $stream(): TODO;
+declare function $streamClosed(): TODO;
+declare function $streamClosing(): TODO;
+declare function $streamErrored(): TODO;
+declare function $streamReadable(): TODO;
+declare function $streamWaiting(): TODO;
+declare function $streamWritable(): TODO;
+declare function $structuredCloneForStream(): TODO;
+declare function $syscall(): TODO;
+declare function $textDecoderStreamDecoder(): TODO;
+declare function $textDecoderStreamTransform(): TODO;
+declare function $textEncoderStreamEncoder(): TODO;
+declare function $textEncoderStreamTransform(): TODO;
+declare function $toNamespacedPath(): TODO;
+declare function $trace(): TODO;
+declare function $transformAlgorithm(): TODO;
+declare function $uncork(): TODO;
+declare function $underlyingByteSource(): TODO;
+declare function $underlyingSink(): TODO;
+declare function $underlyingSource(): TODO;
+declare function $unpipe(): TODO;
+declare function $unshift(): TODO;
+declare function $url(): TODO;
+declare function $username(): TODO;
+declare function $version(): TODO;
+declare function $versions(): TODO;
+declare function $view(): TODO;
+declare function $whenSignalAborted(signal: AbortSignal, cb: (reason: any) => void): TODO;
+declare function $writable(): TODO;
+declare function $write(): TODO;
+declare function $writeAlgorithm(): TODO;
+declare function $writeRequests(): TODO;
+declare function $writer(): TODO;
+declare function $writing(): TODO;
+declare function $written(): TODO;
+
+// The following I cannot find any definitions of, but they are functional.
+declare function $toLength(length: number): number;
+declare function $isTypedArrayView(obj: unknown): obj is ArrayBufferView | DataView | Uint8Array;
+declare function $setStateToMax(target: any, state: number): void;
+declare function $trunc(target: number): number;
+declare function $newPromiseCapability(C: PromiseConstructor): TODO;
+/** @deprecated, use new TypeError instead */
+declare function $makeTypeError(message: string): TypeError;
+declare function $newHandledRejectedPromise(error: unknown): Promise<never>;
+
+// Types used in the above functions
+type PromiseFieldType = typeof $promiseFieldFlags | typeof $promiseFieldReactionsOrResult;
+type PromiseFieldToValue<X extends PromiseFieldType, V> = X extends typeof $promiseFieldFlags
+ ? number
+ : X extends typeof $promiseFieldReactionsOrResult
+ ? V | any
+ : any;
+type WellKnownSymbol = keyof { [K in keyof SymbolConstructor as SymbolConstructor[K] extends symbol ? K : never]: K };
+
+// You can also `@` on any method on a classes to avoid prototype pollution and secret internals
+type ClassWithIntrinsics<T> = { [K in keyof T as T[K] extends Function ? `$${K}` : never]: T[K] };
+
+declare interface Map<K, V> extends ClassWithIntrinsics<Map<K, V>> {}
+declare interface CallableFunction extends ClassWithIntrinsics<CallableFunction> {}
+declare interface Promise<T> extends ClassWithIntrinsics<Promise<T>> {}
+declare interface ArrayBufferConstructor<T> extends ClassWithIntrinsics<ArrayBufferConstructor<T>> {}
+declare interface PromiseConstructor<T> extends ClassWithIntrinsics<PromiseConstructor<T>> {}
+
+declare interface UnderlyingSource {
+ $lazy: boolean;
+ $bunNativeType: number;
+ $bunNativePtr: number;
+ autoAllocateChunkSize?: number;
+}
+
+declare class OutOfMemoryError {
+ constructor();
+}
+
+declare class ReadableStreamDefaultController {
+ constructor(
+ stream: unknown,
+ underlyingSource: unknown,
+ size: unknown,
+ highWaterMark: unknown,
+ $isReadableStream: typeof $isReadableStream,
+ );
+}
+declare class ReadableByteStreamController {
+ constructor(
+ stream: unknown,
+ underlyingSource: unknown,
+ strategy: unknown,
+ $isReadableStream: typeof $isReadableStream,
+ );
+}
+declare class ReadableStreamBYOBRequest {
+ constructor(stream: unknown, view: unknown, $isReadableStream: typeof $isReadableStream);
+}
+declare class ReadableStreamBYOBReader {
+ constructor(stream: unknown);
+}
+
+// Inlining our enum types
+declare const $ImportKindIdToLabel: Array<import("bun").ImportKind>;
+declare const $ImportKindLabelToId: Record<import("bun").ImportKind, number>;
+declare const $LoaderIdToLabel: Array<import("bun").Loader>;
+declare const $LoaderLabelToId: Record<import("bun").Loader, number>;
+
+// not a builtin, but a build-time macro of our own
+/** Returns a not implemented error that points to a github issue. */
+declare function notImplementedIssue(issueNumber: number, description: string): Error;
+/** Return a function that throws a not implemented error that points to a github issue */
+declare function notImplementedIssueFn(issueNumber: number, description: string): (...args: any[]) => never;
+
+declare type JSCSourceCodeObject = unique symbol;
+
+declare interface Function {
+ path: string;
+}
+
+declare var $Buffer: {
+ new (a: any, b?: any, c?: any): Buffer;
+};
+
+declare interface Error {
+ code?: string;
+}
diff --git a/src/js/builtins/codegen/builtin-parser.ts b/src/js/builtins/codegen/builtin-parser.ts
new file mode 100644
index 000000000..e96d79c63
--- /dev/null
+++ b/src/js/builtins/codegen/builtin-parser.ts
@@ -0,0 +1,89 @@
+import { applyReplacements } from "./replacements";
+
+/**
+ * Slices a string until it hits a }, but keeping in mind JS comments,
+ * regex, template literals, comments, and matching {
+ *
+ * Used to extract function bodies without parsing the code.
+ *
+ * If you pass replace=true, it will run replacements on the code
+ */
+export function sliceSourceCode(
+ contents: string,
+ replace: boolean,
+): { result: string; rest: string; usesThis: boolean } {
+ let bracketCount = 0;
+ let i = 0;
+ let result = "";
+ let usesThis = false;
+ while (contents.length) {
+ // TODO: template literal, regexp
+ // these are important because our replacement logic would replace intrinsics
+ // within these, when it should remain as the literal dollar.
+ // but this isn't used in the codebase
+ i = contents.match(/\/\*|\/\/|'|"|{|}|`/)?.index ?? contents.length;
+ const chunk = replace ? applyReplacements(contents.slice(0, i)) : contents.slice(0, i);
+ if (chunk.includes("this")) usesThis = true;
+ result += chunk;
+ contents = contents.slice(i);
+ if (!contents.length) break;
+ if (contents.startsWith("/*")) {
+ i = contents.slice(2).indexOf("*/") + 2;
+ } else if (contents.startsWith("//")) {
+ i = contents.slice(2).indexOf("\n") + 2;
+ } else if (contents.startsWith("'")) {
+ i = contents.slice(1).match(/(?<!\\)'/)!.index! + 2;
+ } else if (contents.startsWith('"')) {
+ i = contents.slice(1).match(/(?<!\\)"/)!.index! + 2;
+ } else if (contents.startsWith("`")) {
+ const { result: result2, rest } = sliceTemplateLiteralSourceCode(contents.slice(1), replace);
+ result += "`" + result2;
+ contents = rest;
+ continue;
+ } else if (contents.startsWith("{")) {
+ bracketCount++;
+ i = 1;
+ } else if (contents.startsWith("}")) {
+ bracketCount--;
+ if (bracketCount <= 0) {
+ result += "}";
+ contents = contents.slice(1);
+ break;
+ }
+ i = 1;
+ } else {
+ throw new Error("TODO");
+ }
+ result += contents.slice(0, i);
+ contents = contents.slice(i);
+ }
+
+ return { result, rest: contents, usesThis };
+}
+
+function sliceTemplateLiteralSourceCode(contents: string, replace: boolean) {
+ let i = 0;
+ let result = "";
+ let usesThis = false;
+ while (contents.length) {
+ i = contents.match(/`|\${/)!.index!;
+ result += contents.slice(0, i);
+ contents = contents.slice(i);
+ if (!contents.length) break;
+ if (contents.startsWith("`")) {
+ result += "`";
+ contents = contents.slice(1);
+ break;
+ } else if (contents.startsWith("$")) {
+ const { result: result2, rest, usesThis: usesThisVal } = sliceSourceCode(contents.slice(1), replace);
+ result += "$" + result2;
+ contents = rest;
+ usesThis ||= usesThisVal;
+ continue;
+ } else {
+ throw new Error("TODO");
+ }
+ }
+
+ return { result, rest: contents, usesThis };
+}
diff --git a/src/js/builtins/codegen/helpers.ts b/src/js/builtins/codegen/helpers.ts
new file mode 100644
index 000000000..6345f8ffa
--- /dev/null
+++ b/src/js/builtins/codegen/helpers.ts
@@ -0,0 +1,25 @@
+export function fmtCPPString(str: string) {
+ return (
+ '"' +
+ str
+ .replace(/\\/g, "\\\\")
+ .replace(/"/g, '\\"')
+ .replace(/\n/g, "\\n")
+ .replace(/\r/g, "\\r")
+ .replace(/\t/g, "\\t")
+ .replace(/\?/g, "\\?") + // https://stackoverflow.com/questions/1234582
+ '"'
+ );
+}
+
+export function cap(str: string) {
+ return str[0].toUpperCase() + str.slice(1);
+}
+
+export function low(str: string) {
+ if (str.startsWith("JS")) {
+ return "js" + str.slice(2);
+ }
+
+ return str[0].toLowerCase() + str.slice(1);
+}
diff --git a/src/js/builtins/codegen/index.ts b/src/js/builtins/codegen/index.ts
new file mode 100644
index 000000000..e20601a15
--- /dev/null
+++ b/src/js/builtins/codegen/index.ts
@@ -0,0 +1,627 @@
+import { existsSync, mkdirSync, readdirSync, rmSync, writeFileSync } from "fs";
+import path from "path";
+import { sliceSourceCode } from "./builtin-parser";
+import { applyGlobalReplacements, enums, globalsToPrefix } from "./replacements";
+import { cap, fmtCPPString, low } from "./helpers";
+
+console.log("Bundling Bun builtins...");
+
+const MINIFY = process.argv.includes("--minify") || process.argv.includes("-m");
+const PARALLEL = process.argv.includes("--parallel") || process.argv.includes("-p");
+const KEEP_TMP = process.argv.includes("--keep-tmp") || process.argv.includes("-k");
+
+const SRC_DIR = path.join(import.meta.dir, "../");
+const OUT_DIR = path.join(SRC_DIR, "../out");
+const TMP_DIR = path.join(SRC_DIR, "../out/tmp");
+
+if (existsSync(TMP_DIR)) rmSync(TMP_DIR, { recursive: true });
+mkdirSync(TMP_DIR);
+
+const define = {
+ "process.env.NODE_ENV": "development",
+ "process.platform": process.platform,
+ "process.arch": process.arch,
+};
+
+for (const name in enums) {
+ const value = enums[name];
+ if (typeof value !== "object") throw new Error("Invalid enum object " + name + " defined in " + import.meta.file);
+ if (typeof value === null) throw new Error("Invalid enum object " + name + " defined in " + import.meta.file);
+ const keys = Array.isArray(value) ? value : Object.keys(value).filter(k => !k.match(/^[0-9]+$/));
+ define[`__intrinsic__${name}IdToLabel`] = "[" + keys.map(k => `"${k}"`).join(", ") + "]";
+ define[`__intrinsic__${name}LabelToId`] = "{" + keys.map(k => `"${k}": ${keys.indexOf(k)}`).join(", ") + "}";
+}
+
+for (const name of globalsToPrefix) {
+ define[name] = "__intrinsic__" + name;
+}
+
+interface ParsedBuiltin {
+ name: string;
+ params: string[];
+ directives: Record<string, any>;
+ source: string;
+ async: boolean;
+}
+interface BundledBuiltin {
+ name: string;
+ directives: Record<string, any>;
+ isGetter: boolean;
+ isConstructor: boolean;
+ isLinkTimeConstant: boolean;
+ isNakedConstructor: boolean;
+ intrinsic: string;
+ overriddenName: string;
+ source: string;
+ params: string[];
+ visibility: string;
+}
+
+/**
+ * Source .ts file --> Array<bundled js function code>
+ */
+async function processFileSplit(filename: string): Promise<{ functions: BundledBuiltin[]; internal: boolean }> {
+ const basename = path.basename(filename, ".ts");
+ let contents = await Bun.file(filename).text();
+
+ contents = applyGlobalReplacements(contents);
+
+ // first approach doesnt work perfectly because we actually need to split each function declaration
+ // and then compile those separately
+
+ const consumeWhitespace = /^\s*/;
+ const consumeTopLevelContent = /^(\/\*|\/\/|type|import|interface|\$|export (?:async )?function|(?:async )?function)/;
+ const consumeEndOfType = /;|.(?=export|type|interface|\$|\/\/|\/\*|function)/;
+
+ const functions: ParsedBuiltin[] = [];
+ let directives: Record<string, any> = {};
+ const bundledFunctions: BundledBuiltin[] = [];
+ let internal = false;
+
+ while (contents.length) {
+ contents = contents.replace(consumeWhitespace, "");
+ if (!contents.length) break;
+ const match = contents.match(consumeTopLevelContent);
+ if (!match) {
+ throw new SyntaxError("Could not process input:\n" + contents.slice(0, contents.indexOf("\n")));
+ }
+ contents = contents.slice(match.index!);
+ if (match[1] === "import") {
+ // TODO: we may want to do stuff with these
+ const i = contents.indexOf(";");
+ contents = contents.slice(i + 1);
+ } else if (match[1] === "/*") {
+ const i = contents.indexOf("*/") + 2;
+ internal ||= contents.slice(0, i).includes("@internal");
+ contents = contents.slice(i);
+ } else if (match[1] === "//") {
+ const i = contents.indexOf("\n") + 1;
+ internal ||= contents.slice(0, i).includes("@internal");
+ contents = contents.slice(i);
+ } else if (match[1] === "type" || match[1] === "export type") {
+ const i = contents.search(consumeEndOfType);
+ contents = contents.slice(i + 1);
+ } else if (match[1] === "interface") {
+ contents = sliceSourceCode(contents, false).rest;
+ } else if (match[1] === "$") {
+ const directive = contents.match(/^\$([a-zA-Z0-9]+)(?:\s*=\s*([^\n]+?))?\s*;?\n/);
+ if (!directive) {
+ throw new SyntaxError("Could not parse directive:\n" + contents.slice(0, contents.indexOf("\n")));
+ }
+ const name = directive[1];
+ let value;
+ try {
+ value = directive[2] ? JSON.parse(directive[2]) : true;
+ } catch (error) {
+ throw new SyntaxError("Could not parse directive value " + directive[2] + " (must be JSON parsable)");
+ }
+ if (name === "constructor") {
+ throw new SyntaxError("$constructor not implemented");
+ }
+ if (name === "nakedConstructor") {
+ throw new SyntaxError("$nakedConstructor not implemented");
+ }
+ directives[name] = value;
+ contents = contents.slice(directive[0].length);
+ } else if (match[1] === "export function" || match[1] === "export async function") {
+ const declaration = contents.match(
+ /^export\s+(async\s+)?function\s+([a-zA-Z0-9]+)\s*\(([^)]*)\)(?:\s*:\s*([^{\n]+))?\s*{?/,
+ );
+ if (!declaration)
+ throw new SyntaxError("Could not parse function declaration:\n" + contents.slice(0, contents.indexOf("\n")));
+
+ const async = !!declaration[1];
+ const name = declaration[2];
+ const paramString = declaration[3];
+ const params =
+ paramString.trim().length === 0 ? [] : paramString.split(",").map(x => x.replace(/:.+$/, "").trim());
+ if (params[0] === "this") {
+ params.shift();
+ }
+
+ const { result, rest } = sliceSourceCode(contents.slice(declaration[0].length - 1), true);
+ functions.push({
+ name,
+ params,
+ directives,
+ source: result.trim().slice(1, -1),
+ async,
+ });
+ contents = rest;
+ directives = {};
+ } else if (match[1] === "function" || match[1] === "async function") {
+ const fnname = contents.match(/^function ([a-zA-Z0-9]+)\(([^)]*)\)(?:\s*:\s*([^{\n]+))?\s*{?/)![1];
+ throw new SyntaxError("All top level functions must be exported: " + fnname);
+ } else {
+ throw new Error("TODO: parse " + match[1]);
+ }
+ }
+
+ for (const fn of functions) {
+ const tmpFile = path.join(TMP_DIR, `${basename}.${fn.name}.ts`);
+
+ // not sure if this optimization works properly in jsc builtins
+ // const useThis = fn.usesThis;
+ const useThis = true;
+
+ // TODO: we should use format=IIFE so we could bundle imports and extra functions.
+ await Bun.write(
+ tmpFile,
+ `// @ts-nocheck
+// GENERATED TEMP FILE - DO NOT EDIT
+// Sourced from ${path.relative(TMP_DIR, filename)}
+
+// do not allow the bundler to rename a symbol to $
+($);
+
+$$capture_start$$(${fn.async ? "async " : ""}${
+ useThis
+ ? `function(${fn.params.join(",")})`
+ : `${fn.params.length === 1 ? fn.params[0] : `(${fn.params.join(",")})`}=>`
+ } {${fn.source}}).$$capture_end$$;
+`,
+ );
+ await Bun.sleep(1);
+ const build = await Bun.build({
+ entrypoints: [tmpFile],
+ define,
+ minify: true,
+ });
+ if (!build.success) {
+ throw new AggregateError(build.logs, "Failed bundling builtin function " + fn.name + " from " + basename + ".ts");
+ }
+ if (build.outputs.length !== 1) {
+ throw new Error("expected one output");
+ }
+ const output = await build.outputs[0].text();
+ const captured = output.match(/\$\$capture_start\$\$([\s\S]+)\.\$\$capture_end\$\$/)![1];
+ const finalReplacement =
+ (fn.directives.sloppy ? captured : captured.replace(/function\s*\(.*?\)\s*{/, '$&"use strict";'))
+ .replace(/^\((async )?function\(/, "($1function (")
+ .replace(/__intrinsic__/g, "@") + "\n";
+
+ bundledFunctions.push({
+ name: fn.name,
+ directives: fn.directives,
+ source: finalReplacement,
+ params: fn.params,
+ visibility: fn.directives.visibility ?? (fn.directives.linkTimeConstant ? "Private" : "Public"),
+ isGetter: !!fn.directives.getter,
+ isConstructor: !!fn.directives.constructor,
+ isLinkTimeConstant: !!fn.directives.linkTimeConstant,
+ isNakedConstructor: !!fn.directives.nakedConstructor,
+ intrinsic: fn.directives.intrinsic ?? "NoIntrinsic",
+ overriddenName: fn.directives.getter
+ ? `"get ${fn.name}"_s`
+ : fn.directives.overriddenName
+ ? `"${fn.directives.overriddenName}"_s`
+ : "ASCIILiteral()",
+ });
+ }
+
+ return {
+ functions: bundledFunctions,
+ internal,
+ };
+}
+
+const filesToProcess = readdirSync(SRC_DIR).filter(x => x.endsWith(".ts") && !x.endsWith(".d.ts"));
+
+const files: Array<{ basename: string; functions: BundledBuiltin[]; internal: boolean }> = [];
+async function processFile(x: string) {
+ const basename = path.basename(x, ".ts");
+ try {
+ files.push({
+ basename,
+ ...(await processFileSplit(path.join(SRC_DIR, x))),
+ });
+ } catch (error) {
+ console.error("Failed to process file: " + basename + ".ts");
+ console.error(error);
+ process.exit(1);
+ }
+}
+
+// Bun seems to crash if this is parallelized, :(
+if (PARALLEL) {
+ await Promise.all(filesToProcess.map(processFile));
+} else {
+ for (const x of filesToProcess) {
+ await processFile(x);
+ }
+}
+
+// C++ codegen
+let bundledCPP = `// Generated by \`bun src/js/builtins/codegen\`
+// Do not edit by hand.
+namespace Zig { class GlobalObject; }
+#include "root.h"
+#include "config.h"
+#include "JSDOMGlobalObject.h"
+#include "WebCoreJSClientData.h"
+#include <JavaScriptCore/JSObjectInlines.h>
+
+namespace WebCore {
+
+`;
+
+for (const { basename, functions } of files) {
+ bundledCPP += `/* ${basename}.ts */\n`;
+ const lowerBasename = low(basename);
+ for (const fn of functions) {
+ const name = `${lowerBasename}${cap(fn.name)}Code`;
+ bundledCPP += `// ${fn.name}
+const JSC::ConstructAbility s_${name}ConstructAbility = JSC::ConstructAbility::CannotConstruct;
+const JSC::ConstructorKind s_${name}ConstructorKind = JSC::ConstructorKind::None;
+const JSC::ImplementationVisibility s_${name}ImplementationVisibility = JSC::ImplementationVisibility::${fn.visibility};
+const int s_${name}Length = ${fn.source.length};
+static const JSC::Intrinsic s_${name}Intrinsic = JSC::NoIntrinsic;
+const char* const s_${name} = ${fmtCPPString(fn.source)};
+
+`;
+ }
+ bundledCPP += `#define DEFINE_BUILTIN_GENERATOR(codeName, functionName, overriddenName, argumentCount) \\
+JSC::FunctionExecutable* codeName##Generator(JSC::VM& vm) \\
+{\\
+ JSVMClientData* clientData = static_cast<JSVMClientData*>(vm.clientData); \\
+ return clientData->builtinFunctions().${lowerBasename}Builtins().codeName##Executable()->link(vm, nullptr, clientData->builtinFunctions().${lowerBasename}Builtins().codeName##Source(), std::nullopt, s_##codeName##Intrinsic); \\
+}
+WEBCORE_FOREACH_${basename.toUpperCase()}_BUILTIN_CODE(DEFINE_BUILTIN_GENERATOR)
+#undef DEFINE_BUILTIN_GENERATOR
+
+`;
+}
+
+bundledCPP += `
+
+JSBuiltinInternalFunctions::JSBuiltinInternalFunctions(JSC::VM& vm)
+ : m_vm(vm)
+`;
+
+for (const { basename, internal } of files) {
+ if (internal) {
+ bundledCPP += ` , m_${low(basename)}(vm)\n`;
+ }
+}
+
+bundledCPP += `
+{
+ UNUSED_PARAM(vm);
+}
+
+template<typename Visitor>
+void JSBuiltinInternalFunctions::visit(Visitor& visitor)
+{
+`;
+for (const { basename, internal } of files) {
+ if (internal) bundledCPP += ` m_${low(basename)}.visit(visitor);\n`;
+}
+
+bundledCPP += `
+ UNUSED_PARAM(visitor);
+}
+
+template void JSBuiltinInternalFunctions::visit(AbstractSlotVisitor&);
+template void JSBuiltinInternalFunctions::visit(SlotVisitor&);
+
+SUPPRESS_ASAN void JSBuiltinInternalFunctions::initialize(Zig::GlobalObject& globalObject)
+{
+ UNUSED_PARAM(globalObject);
+`;
+
+for (const { basename, internal } of files) {
+ if (internal) {
+ bundledCPP += ` m_${low(basename)}.init(globalObject);\n`;
+ }
+}
+
+bundledCPP += `
+ JSVMClientData& clientData = *static_cast<JSVMClientData*>(m_vm.clientData);
+ Zig::GlobalObject::GlobalPropertyInfo staticGlobals[] = {
+`;
+
+for (const { basename, internal } of files) {
+ if (internal) {
+ bundledCPP += `#define DECLARE_GLOBAL_STATIC(name) \\
+ Zig::GlobalObject::GlobalPropertyInfo( \\
+ clientData.builtinFunctions().${low(basename)}Builtins().name##PrivateName(), ${low(
+ basename,
+ )}().m_##name##Function.get() , JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly),
+ WEBCORE_FOREACH_${basename.toUpperCase()}_BUILTIN_FUNCTION_NAME(DECLARE_GLOBAL_STATIC)
+ #undef DECLARE_GLOBAL_STATIC
+ `;
+ }
+}
+
+bundledCPP += `
+ };
+ globalObject.addStaticGlobals(staticGlobals, std::size(staticGlobals));
+ UNUSED_PARAM(clientData);
+}
+
+} // namespace WebCore
+`;
+
+// C++ Header codegen
+let bundledHeader = `// Generated by \`bun src/js/builtins/codegen\`
+// Do not edit by hand.
+#pragma once
+namespace Zig { class GlobalObject; }
+#include "root.h"
+#include <JavaScriptCore/BuiltinUtils.h>
+#include <JavaScriptCore/Identifier.h>
+#include <JavaScriptCore/JSFunction.h>
+#include <JavaScriptCore/UnlinkedFunctionExecutable.h>
+#include <JavaScriptCore/VM.h>
+#include <JavaScriptCore/WeakInlines.h>
+
+namespace JSC {
+class FunctionExecutable;
+}
+
+namespace WebCore {
+`;
+for (const { basename, functions, internal } of files) {
+ bundledHeader += `/* ${basename}.ts */
+`;
+ const lowerBasename = low(basename);
+
+ for (const fn of functions) {
+ const name = `${lowerBasename}${cap(fn.name)}Code`;
+ bundledHeader += `// ${fn.name}
+#define WEBCORE_BUILTIN_${basename.toUpperCase()}_${fn.name.toUpperCase()} 1
+extern const char* const s_${name};
+extern const int s_${name}Length;
+extern const JSC::ConstructAbility s_${name}ConstructAbility;
+extern const JSC::ConstructorKind s_${name}ConstructorKind;
+extern const JSC::ImplementationVisibility s_${name}ImplementationVisibility;
+
+`;
+ }
+ bundledHeader += `#define WEBCORE_FOREACH_${basename.toUpperCase()}_BUILTIN_DATA(macro) \\\n`;
+ for (const fn of functions) {
+ bundledHeader += ` macro(${fn.name}, ${lowerBasename}${cap(fn.name)}, ${fn.params.length}) \\\n`;
+ }
+ bundledHeader += "\n";
+ bundledHeader += `#define WEBCORE_FOREACH_${basename.toUpperCase()}_BUILTIN_CODE(macro) \\\n`;
+ for (const fn of functions) {
+ const name = `${lowerBasename}${cap(fn.name)}Code`;
+ bundledHeader += ` macro(${name}, ${fn.name}, ${fn.overriddenName}, s_${name}Length) \\\n`;
+ }
+ bundledHeader += "\n";
+ bundledHeader += `#define WEBCORE_FOREACH_${basename.toUpperCase()}_BUILTIN_FUNCTION_NAME(macro) \\\n`;
+ for (const fn of functions) {
+ bundledHeader += ` macro(${fn.name}) \\\n`;
+ }
+ bundledHeader += `
+#define DECLARE_BUILTIN_GENERATOR(codeName, functionName, overriddenName, argumentCount) \\
+ JSC::FunctionExecutable* codeName##Generator(JSC::VM&);
+
+WEBCORE_FOREACH_${basename.toUpperCase()}_BUILTIN_CODE(DECLARE_BUILTIN_GENERATOR)
+#undef DECLARE_BUILTIN_GENERATOR
+
+class ${basename}BuiltinsWrapper : private JSC::WeakHandleOwner {
+public:
+ explicit ${basename}BuiltinsWrapper(JSC::VM& vm)
+ : m_vm(vm)
+ WEBCORE_FOREACH_${basename.toUpperCase()}_BUILTIN_FUNCTION_NAME(INITIALIZE_BUILTIN_NAMES)
+#define INITIALIZE_BUILTIN_SOURCE_MEMBERS(name, functionName, overriddenName, length) , m_##name##Source(JSC::makeSource(StringImpl::createWithoutCopying(s_##name, length), { }))
+ WEBCORE_FOREACH_${basename.toUpperCase()}_BUILTIN_CODE(INITIALIZE_BUILTIN_SOURCE_MEMBERS)
+#undef INITIALIZE_BUILTIN_SOURCE_MEMBERS
+ {
+ }
+
+#define EXPOSE_BUILTIN_EXECUTABLES(name, functionName, overriddenName, length) \\
+ JSC::UnlinkedFunctionExecutable* name##Executable(); \\
+ const JSC::SourceCode& name##Source() const { return m_##name##Source; }
+ WEBCORE_FOREACH_${basename.toUpperCase()}_BUILTIN_CODE(EXPOSE_BUILTIN_EXECUTABLES)
+#undef EXPOSE_BUILTIN_EXECUTABLES
+
+ WEBCORE_FOREACH_${basename.toUpperCase()}_BUILTIN_FUNCTION_NAME(DECLARE_BUILTIN_IDENTIFIER_ACCESSOR)
+
+ void exportNames();
+
+private:
+ JSC::VM& m_vm;
+
+ WEBCORE_FOREACH_${basename.toUpperCase()}_BUILTIN_FUNCTION_NAME(DECLARE_BUILTIN_NAMES)
+
+#define DECLARE_BUILTIN_SOURCE_MEMBERS(name, functionName, overriddenName, length) \\
+ JSC::SourceCode m_##name##Source;\\
+ JSC::Weak<JSC::UnlinkedFunctionExecutable> m_##name##Executable;
+ WEBCORE_FOREACH_${basename.toUpperCase()}_BUILTIN_CODE(DECLARE_BUILTIN_SOURCE_MEMBERS)
+#undef DECLARE_BUILTIN_SOURCE_MEMBERS
+
+};
+
+#define DEFINE_BUILTIN_EXECUTABLES(name, functionName, overriddenName, length) \\
+inline JSC::UnlinkedFunctionExecutable* ${basename}BuiltinsWrapper::name##Executable() \\
+{\\
+ if (!m_##name##Executable) {\\
+ JSC::Identifier executableName = functionName##PublicName();\\
+ if (overriddenName)\\
+ executableName = JSC::Identifier::fromString(m_vm, overriddenName);\\
+ m_##name##Executable = JSC::Weak<JSC::UnlinkedFunctionExecutable>(JSC::createBuiltinExecutable(m_vm, m_##name##Source, executableName, s_##name##ImplementationVisibility, s_##name##ConstructorKind, s_##name##ConstructAbility), this, &m_##name##Executable);\\
+ }\\
+ return m_##name##Executable.get();\\
+}
+WEBCORE_FOREACH_${basename.toUpperCase()}_BUILTIN_CODE(DEFINE_BUILTIN_EXECUTABLES)
+#undef DEFINE_BUILTIN_EXECUTABLES
+
+inline void ${basename}BuiltinsWrapper::exportNames()
+{
+#define EXPORT_FUNCTION_NAME(name) m_vm.propertyNames->appendExternalName(name##PublicName(), name##PrivateName());
+ WEBCORE_FOREACH_${basename.toUpperCase()}_BUILTIN_FUNCTION_NAME(EXPORT_FUNCTION_NAME)
+#undef EXPORT_FUNCTION_NAME
+}
+`;
+
+ if (internal) {
+ bundledHeader += `class ${basename}BuiltinFunctions {
+public:
+ explicit ${basename}BuiltinFunctions(JSC::VM& vm) : m_vm(vm) { }
+
+ void init(JSC::JSGlobalObject&);
+ template<typename Visitor> void visit(Visitor&);
+
+public:
+ JSC::VM& m_vm;
+
+#define DECLARE_BUILTIN_SOURCE_MEMBERS(functionName) \\
+ JSC::WriteBarrier<JSC::JSFunction> m_##functionName##Function;
+ WEBCORE_FOREACH_${basename.toUpperCase()}_BUILTIN_FUNCTION_NAME(DECLARE_BUILTIN_SOURCE_MEMBERS)
+#undef DECLARE_BUILTIN_SOURCE_MEMBERS
+};
+
+inline void ${basename}BuiltinFunctions::init(JSC::JSGlobalObject& globalObject)
+{
+#define EXPORT_FUNCTION(codeName, functionName, overriddenName, length) \\
+ m_##functionName##Function.set(m_vm, &globalObject, JSC::JSFunction::create(m_vm, codeName##Generator(m_vm), &globalObject));
+ WEBCORE_FOREACH_${basename.toUpperCase()}_BUILTIN_CODE(EXPORT_FUNCTION)
+#undef EXPORT_FUNCTION
+}
+
+template<typename Visitor>
+inline void ${basename}BuiltinFunctions::visit(Visitor& visitor)
+{
+#define VISIT_FUNCTION(name) visitor.append(m_##name##Function);
+ WEBCORE_FOREACH_${basename.toUpperCase()}_BUILTIN_FUNCTION_NAME(VISIT_FUNCTION)
+#undef VISIT_FUNCTION
+}
+
+template void ${basename}BuiltinFunctions::visit(JSC::AbstractSlotVisitor&);
+template void ${basename}BuiltinFunctions::visit(JSC::SlotVisitor&);
+ `;
+ }
+}
+bundledHeader += `class JSBuiltinFunctions {
+public:
+ explicit JSBuiltinFunctions(JSC::VM& vm)
+ : m_vm(vm)
+`;
+
+for (const { basename } of files) {
+ bundledHeader += ` , m_${low(basename)}Builtins(m_vm)\n`;
+}
+
+bundledHeader += `
+ {
+`;
+
+for (const { basename, internal } of files) {
+ if (internal) {
+ bundledHeader += ` m_${low(basename)}Builtins.exportNames();\n`;
+ }
+}
+
+bundledHeader += ` }
+`;
+
+for (const { basename } of files) {
+ bundledHeader += ` ${basename}BuiltinsWrapper& ${low(basename)}Builtins() { return m_${low(
+ basename,
+ )}Builtins; }\n`;
+}
+
+bundledHeader += `
+private:
+ JSC::VM& m_vm;
+`;
+
+for (const { basename } of files) {
+ bundledHeader += ` ${basename}BuiltinsWrapper m_${low(basename)}Builtins;\n`;
+}
+
+bundledHeader += `;
+};
+
+class JSBuiltinInternalFunctions {
+public:
+ explicit JSBuiltinInternalFunctions(JSC::VM&);
+
+ template<typename Visitor> void visit(Visitor&);
+ void initialize(Zig::GlobalObject&);
+`;
+
+for (const { basename, internal } of files) {
+ if (internal) {
+ bundledHeader += ` ${basename}BuiltinFunctions& ${low(basename)}() { return m_${low(basename)}; }\n`;
+ }
+}
+
+bundledHeader += `
+private:
+ JSC::VM& m_vm;
+`;
+
+for (const { basename, internal } of files) {
+ if (internal) {
+ bundledHeader += ` ${basename}BuiltinFunctions m_${low(basename)};\n`;
+ }
+}
+
+bundledHeader += `
+};
+
+} // namespace WebCore
+`;
+
+await Bun.write(path.join(OUT_DIR, "WebCoreJSBuiltins.h"), bundledHeader);
+await Bun.write(path.join(OUT_DIR, "WebCoreJSBuiltins.cpp"), bundledCPP);
+
+// Generate TS types
+let dts = `// Generated by \`bun src/js/builtins/codegen\`
+// Do not edit by hand.
+type RemoveThis<F> = F extends (this: infer T, ...args: infer A) => infer R ? (...args: A) => R : F;
+`;
+
+for (const { basename, functions, internal } of files) {
+ if (internal) {
+ dts += `\n// ${basename}.ts\n`;
+ for (const fn of functions) {
+ dts += `declare const \$${fn.name}: RemoveThis<typeof import("${path.relative(
+ OUT_DIR,
+ path.join(SRC_DIR, basename),
+ )}")[${JSON.stringify(fn.name)}]>;\n`;
+ }
+ }
+}
+
+await Bun.write(path.join(OUT_DIR, "WebCoreJSBuiltins.d.ts"), dts);
+
+const totalJSSize = files.reduce(
+ (acc, { functions }) => acc + functions.reduce((acc, fn) => acc + fn.source.length, 0),
+ 0,
+);
+
+if (!KEEP_TMP) {
+ await rmSync(TMP_DIR, { recursive: true });
+}
+
+console.log(
+ `Embedded JS size: %s bytes (across %s functions, %s files)`,
+ totalJSSize,
+ files.reduce((acc, { functions }) => acc + functions.length, 0),
+ files.length,
+);
+console.log(`[${performance.now().toFixed(1)}ms]`);
diff --git a/src/js/builtins/codegen/replacements.ts b/src/js/builtins/codegen/replacements.ts
new file mode 100644
index 000000000..05c81b901
--- /dev/null
+++ b/src/js/builtins/codegen/replacements.ts
@@ -0,0 +1,100 @@
+import { LoaderKeys } from "../../../api/schema";
+
+// This is a list of extra syntax replacements to do. Kind of like macros
+// These are only run on code itself, not string contents or comments.
+export const replacements: ReplacementRule[] = [
+ { from: /\bthrow new TypeError\b/g, to: "$throwTypeError" },
+ { from: /\bthrow new RangeError\b/g, to: "$throwRangeError" },
+ { from: /\bthrow new OutOfMemoryError\b/g, to: "$throwOutOfMemoryError" },
+ { from: /\bnew TypeError\b/g, to: "$makeTypeError" },
+];
+
+// These rules are run on the entire file, including within strings.
+export const globalReplacements: ReplacementRule[] = [
+ {
+ from: /\bnotImplementedIssue\(\s*([0-9]+)\s*,\s*((?:"[^"]*"|'[^']+'))\s*\)/g,
+ to: "new TypeError(`${$2} is not implemented yet. See https://github.com/oven-sh/bun/issues/$1`)",
+ },
+ {
+ from: /\bnotImplementedIssueFn\(\s*([0-9]+)\s*,\s*((?:"[^"]*"|'[^']+'))\s*\)/g,
+ to: "() => $throwTypeError(`${$2} is not implemented yet. See https://github.com/oven-sh/bun/issues/$1`)",
+ },
+];
+
+// This is a list of globals we should access using @ notation
+// undefined -> __intrinsic__undefined -> @undefined
+export const globalsToPrefix = [
+ "AbortSignal",
+ "Array",
+ "ArrayBuffer",
+ "Buffer",
+ "Bun",
+ "Infinity",
+ "Loader",
+ "Promise",
+ "ReadableByteStreamController",
+ "ReadableStream",
+ "ReadableStreamBYOBReader",
+ "ReadableStreamBYOBRequest",
+ "ReadableStreamDefaultController",
+ "ReadableStreamDefaultReader",
+ "TransformStream",
+ "TransformStreamDefaultController",
+ "Uint8Array",
+ "WritableStream",
+ "WritableStreamDefaultController",
+ "WritableStreamDefaultWriter",
+ "isFinite",
+ "isNaN",
+ "undefined",
+];
+
+// These enums map to $<enum>IdToLabel and $<enum>LabelToId
+// Make sure to define in ./builtins.d.ts
+export const enums = {
+ Loader: LoaderKeys,
+ ImportKind: [
+ "entry-point",
+ "import-statement",
+ "require-call",
+ "dynamic-import",
+ "require-resolve",
+ "import-rule",
+ "url-token",
+ "internal",
+ ],
+};
+
+// These identifiers have typedef but not present at runtime (converted with replacements)
+// If they are present in the bundle after runtime, we warn at the user.
+// TODO: implement this check.
+export const warnOnIdentifiersNotPresentAtRuntime = [
+ //
+ "OutOfMemoryError",
+ "notImplementedIssue",
+ "notImplementedIssueFn",
+];
+
+export interface ReplacementRule {
+ from: RegExp;
+ to: string;
+ global?: boolean;
+}
+
+/** Applies source code replacements as defined in `replacements` */
+export function applyReplacements(src: string) {
+ let result = src.replace(/\$([a-zA-Z0-9_]+)\b/gm, `__intrinsic__$1`);
+ for (const replacement of replacements) {
+ result = result.replace(replacement.from, replacement.to.replaceAll("$", "__intrinsic__"));
+ }
+ return result;
+}
+
+/** Applies source code replacements as defined in `globalReplacements` */
+export function applyGlobalReplacements(src: string) {
+ let result = src;
+ for (const replacement of globalReplacements) {
+ result = result.replace(replacement.from, replacement.to.replaceAll("$", "__intrinsic__"));
+ }
+ return result;
+}
diff --git a/src/js/builtins/tsconfig.json b/src/js/builtins/tsconfig.json
new file mode 100644
index 000000000..f8b12c5c3
--- /dev/null
+++ b/src/js/builtins/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "extends": "../../../tsconfig.base.json",
+ "compilerOptions": {
+ "noEmit": true
+ },
+ "include": [".", "../private.d.ts", "builtins.d.ts", "../generated/builtins/WebCoreJSBuiltins.d.ts", "../../../packages/bun-types/index.d.ts"]
+}