Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 17 additions & 9 deletions packages/bun-types/globals.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,10 +152,18 @@ declare var ReadableStream: Bun.__internal.UseLibDomIfAvailable<
prototype: ReadableStream;
new <R = any>(underlyingSource?: Bun.UnderlyingSource<R>, strategy?: QueuingStrategy<R>): ReadableStream<R>;
new <R = any>(underlyingSource?: Bun.DirectUnderlyingSource<R>, strategy?: QueuingStrategy<R>): ReadableStream<R>;
new <R extends Uint8Array = Uint8Array<ArrayBuffer>>(
underlyingSource: import("node:stream/web").UnderlyingByteSource,
// A call without type arguments infers `K` as `"bytes"`. Else `K` is `never`, and this returns `ReadableStream<R>`.
new <
R = any,
K extends "bytes" = never,
B extends ([K] extends [never] ? unknown : Uint8Array) = [K] extends [never] ? R : Uint8Array<ArrayBuffer>,
>(
// Always `UnderlyingByteSource`. The other branch is where a call infers `K`.
underlyingSource: [K] extends ["bytes"]
? import("node:stream/web").UnderlyingByteSource
: import("node:stream/web").UnderlyingByteSource & { type: K },
strategy?: { highWaterMark?: number },
): ReadableStream<R>;
): ReadableStream<B>;
}
>;

Expand Down Expand Up @@ -1746,7 +1754,7 @@ declare var URL: Bun.__internal.UseLibDomIfAvailable<
* @param url - The URL to check.
* @param base - The base URL to use.
*/
canParse(url: string, base?: string): boolean;
canParse(url: string | URL, base?: string | URL): boolean;
/**
* Create a `blob:` URL for a {@link Blob}.
*
Expand All @@ -1766,7 +1774,7 @@ declare var URL: Bun.__internal.UseLibDomIfAvailable<
* @param url - The URL to parse.
* @param base - The base URL to use.
*/
parse(url: string, base?: string): URL | null;
parse(url: string | URL, base?: string | URL): URL | null;
}
>;

Expand Down Expand Up @@ -1990,8 +1998,8 @@ declare var Request: Bun.__internal.UseLibDomIfAvailable<
{
prototype: Request;
new (requestInfo: string, init?: RequestInit): Request;
new (requestInfo: RequestInit & { url: string }): Request;
new (requestInfo: Request, init?: RequestInit): Request;
new (requestInfo: RequestInit & { url: string | URL }): Request;
new (requestInfo: string | URL | Request, init?: RequestInit): Request;
}
>;

Expand Down Expand Up @@ -2029,15 +2037,15 @@ declare var Response: Bun.__internal.UseLibDomIfAvailable<
* @param url The URL to redirect to
* @param status The HTTP status code to use for the redirect
*/
redirect(url: string, status?: number): Response;
redirect(url: string | URL, status?: number): Response;

/**
* Create a new {@link Response} that redirects to `url`.
*
* @param url The URL to redirect to
* @param init Options for the response
*/
redirect(url: string, init?: ResponseInit): Response;
redirect(url: string | URL, init?: ResponseInit): Response;

/**
* Create a new {@link Response} that represents a network error.
Expand Down
92 changes: 91 additions & 1 deletion packages/bun-types/wasm.d.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,24 @@
declare module "bun" {
namespace __internal {
interface FallbackWebAssemblyTag {
readonly [Symbol.toStringTag]: "WebAssembly.Tag";
}
// An empty interface, not `{}`: an interface without members is read as its base, and `{}` is an object literal type.
interface LibWebAssemblyTag {}
// lib.dom's `Tag` is empty. It stays empty with lib.dom, so that a cast of an export to `WebAssembly.Tag` is legal.
type LibOrFallbackWebAssemblyTag = LibDomIsLoaded extends true ? LibWebAssemblyTag : FallbackWebAssemblyTag;
}

namespace WebAssembly {
type ImportExportKind = "function" | "global" | "memory" | "table" | "tag";
type TableKind = "anyfunc" | "externref";
type ExportValue = Function | Global | WebAssembly.Memory | WebAssembly.Table;
// Every value fits lib.dom's empty `Tag`, so with lib.dom a tag is no export value, as in lib.dom itself.
type ExportValue =
| Function
| Global
| WebAssembly.Memory
| WebAssembly.Table
| (__internal.LibDomIsLoaded extends true ? never : Tag);
type Exports = Record<string, ExportValue>;
type ImportValue = ExportValue | number;
type Imports = Record<string, ModuleImports>;
Expand Down Expand Up @@ -95,6 +111,55 @@ declare module "bun" {
instance: Instance;
module: Module;
}

interface TagType {
/** The value types of the values that an exception with this tag carries. */
parameters: ValueType[];
}

interface ExceptionOptions {
/** Give the exception a `stack` property with a stack trace. Defaults to `false`. */
traceStack?: boolean | undefined;
}

/**
* The type of a WebAssembly exception: the value types of the values that
* the exception carries.
*
* [MDN Reference](https://developer.mozilla.org/docs/WebAssembly/Reference/JavaScript_interface/Tag)
*/
interface Tag extends __internal.LibOrFallbackWebAssemblyTag {}

/**
* An exception that WebAssembly code throws to JavaScript, or that
* JavaScript throws to WebAssembly code. It is not an instance of `Error`.
*
* [MDN Reference](https://developer.mozilla.org/docs/WebAssembly/Reference/JavaScript_interface/Exception)
*/
interface Exception {
/**
* A stack trace, if the exception was created with `traceStack: true`.
* Otherwise `undefined`.
*
* [MDN Reference](https://developer.mozilla.org/docs/WebAssembly/Reference/JavaScript_interface/Exception/stack)
*/
readonly stack: string | undefined;
/**
* Returns the value at `index` among the values that the exception carries.
*
* Throws a `TypeError` if `exceptionTag` is not the tag of the exception,
* and a `RangeError` if `index` is out of range.
*
* [MDN Reference](https://developer.mozilla.org/docs/WebAssembly/Reference/JavaScript_interface/Exception/getArg)
*/
getArg(exceptionTag: Tag, index: number): any;
/**
* Returns `true` if `exceptionTag` is the tag of the exception.
*
* [MDN Reference](https://developer.mozilla.org/docs/WebAssembly/Reference/JavaScript_interface/Exception/is)
*/
is(exceptionTag: Tag): boolean;
}
}
}

Expand Down Expand Up @@ -172,6 +237,31 @@ declare namespace WebAssembly {
}
>["Module"];

interface TagType extends Bun.WebAssembly.TagType {}
interface ExceptionOptions extends Bun.WebAssembly.ExceptionOptions {}

interface Tag extends Bun.WebAssembly.Tag {}
var Tag: {
prototype: Tag;
new (type: TagType): Tag;
};

interface Exception extends Bun.WebAssembly.Exception {}
var Exception: {
prototype: Exception;
/**
* @param exceptionTag The tag of the exception. `WebAssembly.JSTag` is not accepted.
* @param payload One value for each of the `parameters` of the tag
*/
new (exceptionTag: Tag, payload: any[], options?: ExceptionOptions): Exception;
};

/**
* The tag of a value that JavaScript throws. WebAssembly code that imports
* this tag can catch such a value as an `externref`.
*/
var JSTag: Tag;
Comment thread
claude[bot] marked this conversation as resolved.
Resolved

interface Table extends Bun.WebAssembly.Table {}
var Table: {
prototype: Table;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,12 @@ const builds: Record<string, { files: Record<string, string>; stdout: string }>
},
stdout: "string\nother\nrang\n",
},
"classes without a constructor below a stream of the web, with its type and without": {
files: {
"main.ts": `export {};\nclass Lines extends ReadableStream<string> {}\nclass Pipe extends ReadableStream {}\nconsole.log(new Lines().locked, new Pipe({}).locked);\n`,
},
stdout: "false false\n",
},
"a number of Bun's through a generic function, a generic class, a map, a promise of all and a pattern": {
files: {
"main.ts": `export {};\nfunction same<T>(value: T): T {\n return value;\n}\nclass Box<T> {\n constructor(public held: T) {}\n}\nconst size = () => Bun.file(import.meta.path).size;\nconst one: string = same(size());\nconst two: string = new Box(size()).held;\nconst three: string = new Map([["a", size()]]).get("a")!;\nconst [four] = await Promise.all([size()]);\nconst five: string = four;\nconst { held } = { ...new Box(size()) };\nconst six: string = held;\nconsole.log(typeof one, typeof two, typeof three, typeof five, typeof six);\n`,
Expand All @@ -182,8 +188,8 @@ const builds: Record<string, { files: Record<string, string>; stdout: string }>
const stops: Record<string, { text: string; others?: Record<string, string>; errors: string[] }> = {
// TypeScript's own library for browsers declares these with a type parameter, and tsc says the same of them.
"classes without a constructor below classes of the web that take a type": {
text: `export {};\nclass Told extends CustomEvent {}\nclass Sent extends MessageEvent {}\nclass Pipe extends ReadableStream {}\nconsole.log(new Told("e", { detail: 1 }).detail, new Sent("m", { data: 2 }).data, new Pipe({}).locked);\n`,
errors: ["TS2508", "TS2508", "TS2510", "TS2351", "TS2351"],
text: `export {};\nclass Told extends CustomEvent {}\nclass Sent extends MessageEvent {}\nconsole.log(new Told("e", { detail: 1 }).detail, new Sent("m", { data: 2 }).data);\n`,
errors: ["TS2508", "TS2508", "TS2351", "TS2351"],
},
"a number out of a field in another file, held against a text in the result of a function and at the top": {
text: `import { n } from "./other";\nconst r = inner();\nfunction inner() {\n return n === "one";\n}\nif (n === "two") console.log(r);\nconsole.log("ran");\n`,
Expand Down
75 changes: 75 additions & 0 deletions test/cli/check/web-globals-take-what-the-runtime-takes.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// `bun compile` type checks what it builds, so a statement that runs on Bun and that the declarations of Bun refuse stops the build.
// The program below runs on Bun. The project is the one that `bun init` writes: no library for browsers, and the types of Bun.
import { expect, test } from "bun:test";
import { bunExe, tempDirWithBunTypes } from "harness";
import { run } from "../../aot/harness/run";

const tsconfig = JSON.stringify({
compilerOptions: {
lib: ["ESNext"],
target: "ESNext",
module: "Preserve",
moduleDetection: "force",
moduleResolution: "bundler",
types: ["bun"],
strict: true,
skipLibCheck: true,
noEmit: true,
},
});

const program = `const url = new URL("http://localhost/a/b");
const request = new Request(url, { method: "POST" });
const redirect = Response.redirect(url, 302);
console.log(request.method, new Request({ url }).url, redirect.headers.get("location"));
console.log(Response.redirect(url, { status: 307 }).status, URL.canParse(url), URL.parse(url)?.href);
console.log(URL.canParse("/c", url), URL.parse("c", url)?.href);

class Lines extends ReadableStream<string> {}
class Chunks extends ReadableStream {}
class Bytes<T extends Uint8Array> extends ReadableStream<T> {
constructor() {
super({
type: "bytes",
pull(controller) {
controller.enqueue(new Uint8Array([104, 105]));
controller.close();
},
});
}
}
const bytes: ReadableStream<Uint8Array<ArrayBuffer>> = new ReadableStream({ type: "bytes" });
const lines = new Lines({
start(controller) {
controller.enqueue("line");
controller.close();
},
});
console.log(await lines.text(), new Chunks({ type: "bytes" }) instanceof Chunks, bytes.locked);
const { value: chunk } = await new Bytes().getReader().read();
console.log(chunk instanceof Uint8Array, Buffer.isBuffer(chunk), chunk?.length);

const tag = new WebAssembly.Tag({ parameters: ["i32"] });
const exception = new WebAssembly.Exception(tag, [7]);
console.log(exception.is(tag), exception.is(WebAssembly.JSTag), exception.getArg(tag, 0));
`;

const printed = `POST http://localhost/a/b http://localhost/a/b
307 true http://localhost/a/b
true http://localhost/a/c
line true false
true false 2
true false 7
`;

// One check reads every declaration of Bun and of Node.js, which takes a debug build several seconds.
test("web globals take what the runtime takes", async () => {
using dir = tempDirWithBunTypes("check-web-globals", { "tsconfig.json": tsconfig, "index.ts": program });
expect(await run([bunExe(), "index.ts"], String(dir))).toEqual({ stdout: printed, stderr: "", exitCode: 0 });
const checked = await run([bunExe(), "check", "--no-pretty"], String(dir));
// The summary on stderr has a time in it. Where the check fails, the message shows all three.
expect(checked.exitCode === 0 ? { stdout: checked.stdout, exitCode: 0 } : checked).toEqual({
stdout: "",
exitCode: 0,
});
}, 60_000);
Comment thread
robobun marked this conversation as resolved.
Resolved
Loading
Loading