initial commit

This commit is contained in:
i2p
2026-08-27 21:09:14 +00:00
commit a5b6d59437
12681 changed files with 3253832 additions and 0 deletions
@@ -0,0 +1,134 @@
import { expect, test } from "bun:test";
import { bunEnv, bunExe, tempDir } from "harness";
import path from "path";
const runtimePath = path.join(import.meta.dir, "..", "..", "..", "packages", "bun-lambda", "runtime.ts");
// The runtime only uses aws4fetch for outgoing WebSocket messages, which these
// tests never send, so a stub keeps the test offline.
const aws4fetchStub = `export class AwsClient {
constructor() {}
async fetch() {
return new Response(null, { status: 200 });
}
}
`;
test("lambda HTTP events cannot override the request authority", async () => {
const runtimeSource = await Bun.file(runtimePath).text();
using dir = tempDir("bun-lambda", {
"runtime.ts": runtimeSource,
"handler.ts": `export default {
async fetch(request) {
return new Response(request.url);
},
};
`,
"node_modules/aws4fetch/package.json": JSON.stringify({ name: "aws4fetch", version: "1.0.0", main: "index.js" }),
"node_modules/aws4fetch/index.js": aws4fetchStub,
});
const events = [
{
requestId: "req-v2",
event: {
version: "2.0",
requestContext: {
requestId: "req-v2",
domainName: "api.example.com",
http: { method: "GET", path: "//attacker.example/reset" },
},
headers: { "Host": "evil.example", "X-Forwarded-Proto": "https" },
isBase64Encoded: false,
},
},
{
requestId: "req-v1",
event: {
requestContext: {
requestId: "req-v1",
domainName: "api.example.com",
httpMethod: "GET",
path: "//attacker.example/reset",
},
headers: {},
multiValueHeaders: { "Host": ["evil.example"], "X-Forwarded-Proto": ["https"] },
isBase64Encoded: false,
},
},
];
let nextInvocation = 0;
const resolvers = new Map<string, (value: any) => void>();
const responses = new Map<string, Promise<any>>();
for (const { requestId } of events) {
responses.set(requestId, new Promise(resolve => resolvers.set(requestId, resolve)));
}
using server = Bun.serve({
port: 0,
async fetch(req) {
const url = new URL(req.url);
if (url.pathname === "/2018-06-01/runtime/invocation/next") {
if (nextInvocation >= events.length) {
// No more events: a non-ok status makes the runtime exit cleanly.
return new Response(null, { status: 500 });
}
const { requestId, event } = events[nextInvocation++];
return new Response(JSON.stringify(event), {
headers: {
"Content-Type": "application/json",
"Lambda-Runtime-Aws-Request-Id": requestId,
"Lambda-Runtime-Trace-Id": "trace-id",
"Lambda-Runtime-Invoked-Function-Arn": "arn:aws:lambda:us-east-1:123456789012:function:test",
"Lambda-Runtime-Deadline-Ms": String(Date.now() + 60_000),
},
});
}
const match = url.pathname.match(/^\/2018-06-01\/runtime\/invocation\/([^/]+)\/response$/);
if (match) {
resolvers.get(match[1])?.(await req.json());
return new Response(null, { status: 202 });
}
// Anything else (init/invocation errors) fails the assertions with useful context.
const failure = { unexpected: url.pathname, body: await req.text() };
for (const resolve of resolvers.values()) {
resolve(failure);
}
return new Response(null, { status: 202 });
},
});
await using proc = Bun.spawn({
cmd: [bunExe(), "runtime.ts"],
cwd: String(dir),
env: {
...bunEnv,
AWS_LAMBDA_RUNTIME_API: `localhost:${server.port}`,
_HANDLER: "handler.fetch",
LAMBDA_TASK_ROOT: String(dir),
},
stdout: "pipe",
stderr: "pipe",
stdin: "ignore",
});
const [v2Response, v1Response] = await Promise.all([responses.get("req-v2"), responses.get("req-v1")]);
const decodeBody = (response: any): string =>
response.isBase64Encoded ? Buffer.from(response.body, "base64").toString("utf8") : response.body;
// Payload format v2: the path from the event must not be able to change the
// origin, and the authority comes from requestContext.domainName.
expect(v2Response.unexpected).toBeUndefined();
const v2Url = new URL(decodeBody(v2Response));
expect(v2Url.origin).toBe("https://api.example.com");
expect(v2Url.pathname).toBe("//attacker.example/reset");
// Payload format v1.
expect(v1Response.unexpected).toBeUndefined();
const v1Url = new URL(decodeBody(v1Response));
expect(v1Url.origin).toBe("https://api.example.com");
expect(v1Url.pathname).toBe("//attacker.example/reset");
});
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,31 @@
import { expect, test } from "bun:test";
const eachWithDifferingTypes = test.each([
["hello", "world"],
["hello", 2],
]);
eachWithDifferingTypes("each with differing types", (a, b) => {
expect(typeof a).toBe("string");
expect(["world", 2]).toContain(b);
});
const eachWithConst = test.each([
["hello", "world"],
["hello", "alistair"],
] as const);
eachWithConst("each with `as const`", (a, b) => {
expect(a).toBe("hello");
expect(["world", "alistair"]).toContain(b);
});
const eachWithConstAndDifferingTypes = test.each([
["hello", "world"],
["hello", 2],
] as const);
eachWithConstAndDifferingTypes("each with `as const` and differing types", (a, b) => {
expect(a).toBe("hello");
expect(["world", 2]).toContain(b);
});
@@ -0,0 +1,26 @@
import { expectType } from "./utilities";
async function example(): Promise<Blob> {
const response = await fetch("foo");
return await response.blob();
}
import { Blob as NodeBlob } from "node:buffer";
async function example2(): Promise<NodeBlob> {
const response = await fetch("foo");
return await response.blob();
}
expectType(Blob.prototype).extends<{
json(): Promise<unknown>;
bytes(): Promise<Uint8Array>;
text(): Promise<string>;
formData(): Promise<FormData>;
}>();
expectType(new Blob(["hello"])).extends<{
json(): Promise<unknown>;
bytes(): Promise<Uint8Array>;
text(): Promise<string>;
formData(): Promise<FormData>;
}>();
@@ -0,0 +1,28 @@
import { describe, expect, it, jest, mock, spyOn } from "bun:test";
class AnyDTO {
anyField: string = "any_value";
}
class AnyClass {
async anyMethod(): Promise<AnyDTO> {
return new AnyDTO();
}
}
const anyObject: AnyClass = {
anyMethod: jest.fn(),
};
describe("Any describe", () => {
it("should return any value", async () => {
spyOn(anyObject, "anyMethod").mockResolvedValue({ anyField: "any_value" });
const anyValue = await anyObject.anyMethod();
expect(anyValue).toEqual({ anyField: "any_value" });
});
});
const mockSomething = mock((): string => "hi");
mockSomething.mockImplementation(() => "hello");
mockSomething.mockReturnValue("hello");
@@ -0,0 +1,42 @@
// This page test can return once we implement ssg/ssr/rsc again
// import { join } from "path";
// import { expectType } from "./utilities";
// // we're just checking types here really
// declare function markdownToJSX(markdown: string): React.ReactNode;
// type Params = {
// slug: string;
// };
// const Index: Bun.__experimental.SSGPage<Params> = async ({ params }) => {
// expectType(params.slug).is<string>();
// const content = await Bun.file(join(process.cwd(), "posts", params.slug + ".md")).text();
// const node = markdownToJSX(content);
// return <div>{node}</div>;
// };
// expectType(Index.displayName).is<string | undefined>();
// export default Index;
// export const getStaticPaths: Bun.__experimental.GetStaticPaths<Params> = async () => {
// const glob = new Bun.Glob("**/*.md");
// const postsDir = join(process.cwd(), "posts");
// const paths: Bun.__experimental.SSGPaths<Params> = [];
// for (const file of glob.scanSync({ cwd: postsDir })) {
// const slug = file.replace(/\.md$/, "");
// paths.push({
// params: { slug },
// });
// }
// return { paths };
// };
export {};
@@ -0,0 +1,15 @@
import { expectType } from "./utilities";
const buffer = new ArrayBuffer(1024, {
maxByteLength: 2048,
});
console.log(buffer.byteLength); // 1024
buffer.resize(2048);
console.log(buffer.byteLength); // 2048
TextDecoder;
const buf = new SharedArrayBuffer(1024);
buf.grow(2048);
expectType(buffer[Symbol.toStringTag]).extends<string>();
@@ -0,0 +1,32 @@
import { expectType } from "./utilities";
async function* listReleases() {
for (let page = 1; ; page++) {
const response = await fetch(`https://api.github.com/repos/oven-sh/bun/releases?page=${page}`);
const releases = (await response.json()) as Array<{ data: string }>;
if (!releases.length) {
break;
}
for (const release of releases) {
yield release;
}
}
}
await Array.fromAsync(listReleases());
// Tests from issue #8484
// https://github.com/oven-sh/bun/issues/8484
async function* naturals() {
for (let i = 0; i < 10; i++) {
yield i;
}
}
const test1 = await Array.fromAsync(naturals(), n => Promise.resolve(`${n}`));
expectType<string[]>(test1);
const test2 = await Array.fromAsync([Promise.resolve(1), Promise.resolve(2)]);
expectType<number[]>(test2);
export {};
@@ -0,0 +1,40 @@
// Test Atomics global type definitions with TypeScript
declare const buffer: SharedArrayBuffer;
declare const view: Int32Array;
declare const view16: Int16Array;
declare const view8: Int8Array;
declare const viewU32: Uint32Array;
declare const bigView: BigInt64Array;
// Test basic Atomics operations - type signatures only
const stored: number = Atomics.store(view, 0, 42);
const loaded: number = Atomics.load(view, 0);
const added: number = Atomics.add(view, 0, 8);
const subtracted: number = Atomics.sub(view, 0, 5);
// Test compare and exchange operations
const exchanged: number = Atomics.compareExchange(view, 0, 50, 100);
const swapped: number = Atomics.exchange(view, 0, 200);
// Test bitwise operations
const anded: number = Atomics.and(view, 0, 0xFF);
const ored: number = Atomics.or(view, 0, 0x10);
const xored: number = Atomics.xor(view, 0, 0x0F);
// Test utility functions
const lockFree4: boolean = Atomics.isLockFree(4);
const lockFree8: boolean = Atomics.isLockFree(8);
// Test synchronization primitives
const waitResult: "ok" | "not-equal" | "timed-out" = Atomics.wait(view, 0, 0, 1000);
const notified: number = Atomics.notify(view, 0, 1);
// Test with different integer TypedArray types
const stored16: number = Atomics.store(view16, 0, 42);
const loaded8: number = Atomics.load(view8, 0);
const addedU32: number = Atomics.add(viewU32, 0, 1);
// Test BigInt64Array support
const storedBig: bigint = Atomics.store(bigView, 0, 42n);
const loadedBig: bigint = Atomics.load(bigView, 0);
const addedBig: bigint = Atomics.add(bigView, 0, 8n);
@@ -0,0 +1,11 @@
const channel = new BroadcastChannel("my-channel");
const message = { hello: "world" };
channel.onmessage = event => {
console.log(event);
};
channel.postMessage(message);
const error = new Error("hello world");
const clone = structuredClone(error);
console.log(clone.message); // "hello world"
@@ -0,0 +1,68 @@
import { expectAssignable, expectType } from "./utilities";
Bun.build({
entrypoints: ["hey"],
splitting: false,
});
// Build.CompileTarget should accept SIMD variants (issue #26247)
expectAssignable<Bun.Build.CompileTarget>("bun-linux-x64-modern");
expectAssignable<Bun.Build.CompileTarget>("bun-linux-x64-baseline");
expectAssignable<Bun.Build.CompileTarget>("bun-linux-arm64-modern");
expectAssignable<Bun.Build.CompileTarget>("bun-linux-arm64-baseline");
expectAssignable<Bun.Build.CompileTarget>("bun-linux-x64-modern-glibc");
expectAssignable<Bun.Build.CompileTarget>("bun-linux-x64-modern-musl");
expectAssignable<Bun.Build.CompileTarget>("bun-darwin-x64-modern");
expectAssignable<Bun.Build.CompileTarget>("bun-darwin-arm64-baseline");
expectAssignable<Bun.Build.CompileTarget>("bun-windows-x64-modern");
Bun.build({
entrypoints: ["hey"],
splitting: false,
compile: {},
});
Bun.build({
entrypoints: ["hey"],
plugins: [
{
name: "my-terrible-plugin",
setup(build) {
expectType(build).is<Bun.PluginBuilder>();
build.onResolve({ filter: /^hey$/ }, args => {
expectType(args).is<Bun.OnResolveArgs>();
return { path: args.path };
});
build.onLoad({ filter: /^hey$/ }, args => {
expectType(args).is<Bun.OnLoadArgs>();
return { contents: "hey", loader: "js" };
});
build.onStart(() => {});
build.onEnd(result => {
expectType(result).is<Bun.BuildOutput>();
expectType(result.success).is<boolean>();
expectType(result.outputs).is<Bun.BuildArtifact[]>();
expectType(result.logs).is<Array<BuildMessage | ResolveMessage>>();
});
build.onBeforeParse(
{
namespace: "file",
filter: /\.tsx$/,
},
{
napiModule: {},
symbol: "replace_foo_with_bar",
// external: myNativeAddon.getSharedState()
},
);
},
},
],
});
+95
View File
@@ -0,0 +1,95 @@
import type { BunFile, BunPlugin, FileBlob } from "bun";
import * as tsd from "./utilities";
{
const _plugin: BunPlugin = {
name: "asdf",
setup() {},
};
_plugin;
}
{
// tslint:disable-next-line:no-void-expression
const arg = Bun.plugin({
name: "arg",
setup() {},
});
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type
tsd.expectType<void>(arg);
}
{
// tslint:disable-next-line:no-void-expression
const arg = Bun.plugin({
name: "arg",
async setup() {},
});
tsd.expectType<Promise<void>>(arg);
}
{
const f = Bun.file("asdf");
tsd.expectType<BunFile>(f);
tsd.expectType<FileBlob>(f);
}
{
Bun.spawn(["anything"], {
env: process.env,
});
Bun.spawn(["anything"], {
env: { ...process.env },
});
Bun.spawn(["anything"], {
env: { ...process.env, dummy: "" },
});
}
{
Bun.TOML.parse("asdf = asdf");
}
DOMException;
tsd
.expectType(
Bun.secrets.get({
service: "hey",
name: "hey",
}),
)
.is<Promise<string | null>>();
tsd
.expectType(
Bun.secrets.set({
service: "hey",
name: "hey",
value: "hey",
allowUnrestrictedAccess: true,
}),
)
.is<Promise<void>>();
tsd
.expectType(
Bun.secrets.delete({
service: "hey",
name: "hey",
}),
)
.is<Promise<boolean>>();
tsd
.expectType(
Bun.mmap("./data.bin", {
shared: true,
sync: false,
offset: 4096,
size: 1024,
}),
)
.is<Uint8Array<ArrayBuffer>>();
tsd.expectType(Bun.mmap("./data.bin", { offset: 4096 })).is<Uint8Array<ArrayBuffer>>();
tsd.expectType(Bun.mmap("./data.bin", { size: 1024 })).is<Uint8Array<ArrayBuffer>>();
@@ -0,0 +1,46 @@
/**
* Type tests for the "bun:bundle" module.
*/
import { feature } from "bun:bundle";
import { expectType } from "./utilities";
// feature() returns boolean
expectType(feature("DEBUG")).is<boolean>();
// Import alias works
import { feature as checkFeature } from "bun:bundle";
expectType(checkFeature("FLAG")).is<boolean>();
// Bun.build features option accepts string array
Bun.build({
entrypoints: ["./index.ts"],
outdir: "./dist",
features: ["FEATURE_A", "FEATURE_B"],
});
// Error cases:
// @ts-expect-error - feature() requires exactly one argument
feature();
// @ts-expect-error - feature() requires a string argument
feature(123);
// @ts-expect-error - feature() requires a string argument
feature(true);
// @ts-expect-error - feature() requires a string argument
feature(null);
// @ts-expect-error - feature() requires a string argument
feature(undefined);
// @ts-expect-error - feature() doesn't accept multiple arguments
feature("A", "B");
// @ts-expect-error - feature() doesn't accept objects
feature({ flag: "DEBUG" });
// @ts-expect-error - feature() doesn't accept arrays
feature(["DEBUG"]);
@@ -0,0 +1,24 @@
import c2 from "console";
import c1 from "node:console";
c1.log();
c2.log();
async () => {
// tslint:disable-next-line:await-promise
for await (const line of c1) {
console.log("Received:", line);
}
// tslint:disable-next-line:await-promise
for await (const line of c2) {
console.log("Received:", line);
}
// tslint:disable-next-line:await-promise
for await (const line of console) {
console.log("Received:", line);
}
return null;
};
@@ -0,0 +1,84 @@
import { expectType } from "./utilities";
// -- Bun.cron() --
// 5-field expressions
Bun.cron("./worker.ts", "* * * * *", "all-stars");
Bun.cron("./worker.ts", "30 2 * * 1", "weekly-report");
Bun.cron("./worker.ts", "0 0 1 1 *", "new-year");
Bun.cron("./worker.ts", "*/15 * * * *", "every-15-min");
Bun.cron("./worker.ts", "0 9 * * 1-5", "weekday-morning");
Bun.cron("./worker.ts", "0 0 1-15/2 1-3 *", "biweekly-q1");
Bun.cron("./worker.ts", "0,15,30,45 * * * *", "quarter-hours");
Bun.cron("./worker.ts", "30 2 * * MON", "weekly-named");
Bun.cron("./worker.ts", "0 0 * * MON-FRI", "weekday-range");
Bun.cron("./worker.ts", "0 0 * JAN-MAR *", "month-range");
// All nicknames
Bun.cron("./worker.ts", "@yearly", "yearly");
Bun.cron("./worker.ts", "@annually", "annually");
Bun.cron("./worker.ts", "@monthly", "monthly");
Bun.cron("./worker.ts", "@weekly", "weekly");
Bun.cron("./worker.ts", "@daily", "daily");
Bun.cron("./worker.ts", "@midnight", "midnight");
Bun.cron("./worker.ts", "@hourly", "hourly");
// -- Bun.cron.parse() --
expectType(Bun.cron.parse("* * * * *")).is<Date | null>();
expectType(Bun.cron.parse("@daily")).is<Date | null>();
expectType(Bun.cron.parse("30 9 * * MON-FRI")).is<Date | null>();
expectType(Bun.cron.parse("@hourly", new Date())).is<Date | null>();
expectType(Bun.cron.parse("@hourly", Date.now())).is<Date | null>();
expectType(Bun.cron.parse("0 0 1 1 *", Date.UTC(2025, 0, 1))).is<Date | null>();
// -- Bun.cron.remove() --
expectType(Bun.cron.remove("weekly-report")).is<Promise<void>>();
// -- Return type --
expectType(Bun.cron("./worker.ts", "@daily", "daily")).is<Promise<void>>();
// -- In-process callback overload --
expectType(Bun.cron("* * * * *", () => {})).is<Bun.CronJob>();
expectType(Bun.cron("@hourly", async () => {})).is<Bun.CronJob>();
expectType(Bun.cron("*/30 * * * *", () => fetch("http://x"))).is<Bun.CronJob>();
Bun.cron("* * * * *", function () {
this.stop();
});
using job = Bun.cron("0 * * * *", () => {});
expectType(job.cron).is<string>();
expectType(job.stop()).is<Bun.CronJob>();
expectType(job.ref()).is<Bun.CronJob>();
expectType(job.unref()).is<Bun.CronJob>();
expectType(job[Symbol.dispose]()).is<void>();
// -- @ts-expect-error cases --
// @ts-expect-error - missing schedule and title
Bun.cron("./worker.ts");
// -- Cron type is accessible --
declare const schedule: Bun.CronWithAutocomplete;
// -- CronController type is accessible --
declare const controller: Bun.CronController;
expectType(controller.type).is<"scheduled">();
expectType(controller.cron).is<string>();
expectType(controller.scheduledTime).is<number>();
// -- { tz } option --
expectType(Bun.cron.parse("@hourly", Date.now(), { tz: "UTC" })).is<Date | null>();
expectType(Bun.cron.parse("0 9 * * *", new Date(), { tz: "America/New_York" })).is<Date | null>();
expectType(Bun.cron("* * * * *", () => {}, { tz: "UTC" })).is<Bun.CronJob>();
expectType(Bun.cron("0 9 * * *", async () => {}, { tz: "America/New_York" })).is<Bun.CronJob>();
// -- CronOptions type is accessible --
declare const opts: Bun.CronOptions;
expectType(opts.tz).is<string | undefined>();
@@ -0,0 +1,41 @@
import { expectType } from "./utilities";
crypto.getRandomValues(new Uint8Array(1));
// TODO(@alii): Failing with @types/[email protected]
// crypto.subtle.deriveKey(
// "HMAC",
// await crypto.subtle.importKey("raw", new TextEncoder().encode("secret"), "HMAC", false, ["deriveKey"]),
// { name: "HMAC", hash: "SHA-256" },
// false,
// ["sign", "verify"],
// );
await crypto.subtle.generateKey("HMAC", false, ["sign", "verify"]);
expectType<CryptoKeyPair>(
await crypto.subtle.generateKey({ namedCurve: "Ed25519" } as import("node:crypto").webcrypto.EcKeyGenParams, false, [
"sign",
"verify",
]),
);
declare const key: CryptoKey;
crypto.subtle.digest("SHA-256", new TextEncoder().encode("secret"));
crypto.subtle.exportKey("jwk", key);
crypto.subtle.importKey("raw", new TextEncoder().encode("secret"), "HMAC", false, ["sign", "verify"]);
crypto.subtle.encrypt("AES-CBC", key, new TextEncoder().encode("secret"));
crypto.subtle.decrypt("AES-CBC", key, new TextEncoder().encode("secret"));
expectType(crypto.getRandomValues(new Uint8Array(1))).is<Uint8Array<ArrayBuffer>>();
expectType(crypto.subtle.digest("SHA-256", new TextEncoder().encode("secret"))).is<Promise<ArrayBuffer>>();
expectType(crypto.subtle.importKey("raw", new TextEncoder().encode("secret"), "HMAC", false, ["sign", "verify"])).is<
Promise<CryptoKey>
>();
expectType(crypto.subtle.encrypt("AES-CBC", key, new TextEncoder().encode("secret"))).is<Promise<ArrayBuffer>>();
expectType(crypto.subtle.decrypt("AES-CBC", key, new TextEncoder().encode("secret"))).is<Promise<ArrayBuffer>>();
expectType(crypto.randomUUID()).is<`${string}-${string}-${string}-${string}-${string}`>();
expectType(
crypto.timingSafeEqual(new TextEncoder().encode("secret"), new TextEncoder().encode("secret")),
).is<boolean>();
@@ -0,0 +1,12 @@
import diagnostics_channel from "diagnostics_channel";
// Create a channel object
const channel = diagnostics_channel.channel("my-channel");
// Subscribe to the channel
channel.subscribe((message, name) => {
console.log("Received message:", message);
});
// Publish a message to the channel
channel.publish({ some: "data" });
+19
View File
@@ -0,0 +1,19 @@
import { dns as bun_dns } from "bun";
import * as dns from "node:dns";
import { expectType } from "./utilities";
dns.resolve("asdf", "A", () => {});
dns.reverse("asdf", () => {});
dns.getServers();
expectType(Bun.dns.getCacheStats()).is<{
cacheHitsCompleted: number;
cacheHitsInflight: number;
cacheMisses: number;
size: number;
errors: number;
totalCount: number;
}>();
expectType(Bun.dns.V4MAPPED).is<number>();
expectType(bun_dns.prefetch("bun.sh")).is<void>();
+25
View File
@@ -0,0 +1,25 @@
import { INSPECT_MAX_BYTES } from "buffer";
INSPECT_MAX_BYTES;
{
new Blob([]);
}
{
new MessagePort();
}
{
new MessageChannel();
}
{
new BroadcastChannel("zxgdfg");
}
{
new Response("asdf");
}
{
Response.json({ asdf: "asdf" }).ok;
const r = Response.json({ hello: "world" });
r.body;
}
+59
View File
@@ -0,0 +1,59 @@
import { expectType } from "./utilities";
import { env as bun_env } from "bun";
import { env as node_env } from "node:process";
declare module "bun" {
interface Env {
FOO: "FOO";
}
}
expectType(Bun.env.FOO).is<"FOO">();
expectType(process.env.FOO).is<"FOO">();
expectType(import.meta.env.FOO).is<"FOO">();
expectType(bun_env.FOO).is<"FOO">();
expectType(node_env.FOO).is<"FOO">();
declare global {
namespace NodeJS {
interface ProcessEnv {
BAR: "BAR";
}
}
}
expectType(Bun.env.BAR).is<"BAR">();
expectType(process.env.BAR).is<"BAR">();
expectType(import.meta.env.BAR).is<"BAR">();
expectType(node_env.BAR).is<"BAR">();
expectType(bun_env.BAR).is<"BAR">();
declare global {
interface ImportMetaEnv {
BAZ: "BAZ";
}
}
expectType(Bun.env.BAZ).is<"BAZ">();
// expectType(process.env.BAZ).is<"BAZ">(); // ProcessEnv does NOT extend ImportMetaEnv
expectType(import.meta.env.BAZ).is<"BAZ">();
// expectType(node_env.BAZ).is<"BAZ">(); // ProcessEnv does NOT extend ImportMetaEnv
expectType(bun_env.BAZ).is<"BAZ">();
expectType(Bun.env.OTHER).is<string | undefined>();
expectType(process.env.OTHER).is<string | undefined>();
expectType(import.meta.env.OTHER).is<string | undefined>();
expectType(node_env.OTHER).is<string | undefined>();
expectType(bun_env.OTHER).is<string | undefined>();
function isAllSame<T>(a: T, b: T, c: T, d: T, e: T) {
return a === b && b === c && c === d && d === e;
}
//prettier-ignore
{
isAllSame <"FOO"> (process.env.FOO, Bun.env.FOO, import.meta.env.FOO, node_env.FOO, bun_env.FOO);
isAllSame <"BAR"> (process.env.BAR, Bun.env.BAR, import.meta.env.BAR, node_env.BAR, bun_env.BAR);
isAllSame <"BAZ"> ( "BAZ", Bun.env.BAZ, import.meta.env.BAZ, "BAZ", bun_env.BAZ); // ProcessEnv does NOT extend ImportMetaEnv
isAllSame <string | undefined> (process.env.OTHER, Bun.env.OTHER, import.meta.env.OTHER, node_env.OTHER, bun_env.OTHER);
}
@@ -0,0 +1,20 @@
import { EventEmitter } from "events";
import { expectType } from "./utilities";
// eslint-disable-next-line @definitelytyped/no-single-element-tuple-type
// EventEmitter<
// const e1 = new EventEmitter<{ a: [string] }>();
// e1.on("a", (arg) => {
// expectType<string>(arg);
// });
// // @ts-expect-error
// e1.on("qwer", (_) => {});
const e2 = new EventEmitter();
e2.on("qwer", (_: any) => {
_;
});
e2.on("asdf", arg => {
expectType<any>(arg);
});
+334
View File
@@ -0,0 +1,334 @@
// Valid body types
fetch("https://example.com", { body: "string body" });
fetch("https://example.com", { body: JSON.stringify({ key: "value" }) });
fetch("https://example.com", { body: new Blob(["blob content"]) });
fetch("https://example.com", { body: new File(["file content"], "file.txt") });
fetch("https://example.com", { body: new ArrayBuffer(8) });
fetch("https://example.com", { body: new Uint8Array([1, 2, 3, 4]) });
fetch("https://example.com", { body: new Int32Array([1, 2, 3, 4]) });
fetch("https://example.com", { body: new DataView(new ArrayBuffer(8)) });
fetch("https://example.com", { body: new URLSearchParams({ key: "value" }) });
fetch("https://example.com", { body: new FormData() });
fetch("https://example.com", { body: new ReadableStream() });
fetch("https://example.com", { body: Buffer.from("buffer content") });
fetch("https://example.com", { body: Bun.file("path") });
fetch("https://example.com", { body: Bun.file("hey").stream() });
fetch("https://example.com", { body: new Response("bun").body });
fetch("https://example.com", { body: Bun.s3.file("hey") });
fetch("https://example.com", { body: Bun.s3.file("hey").stream() });
fetch("https://example.com", { body: Bun.s3.file("hey").readable });
async function* asyncGenerator() {
yield "chunk1";
yield "chunk2";
}
fetch("https://example.com", { body: asyncGenerator() });
const asyncIterable = {
async *[Symbol.asyncIterator]() {
yield "data1";
yield "data2";
},
};
fetch("https://example.com", { body: asyncIterable });
fetch("https://example.com").then(res => {
fetch("https://example.com", { body: res.body });
});
const req = new Request("https://example.com", { body: "request body" });
fetch("https://example.com", { body: req.body });
fetch("https://example.com", { body: null });
fetch("https://example.com", { body: undefined });
fetch("https://example.com", {}); // No body
{
function* syncGenerator() {
yield new Uint8Array([1, 2, 3]);
yield new Uint8Array([4, 5, 6]);
}
// @ts-expect-error Unsupported
fetch("https://example.com", { body: syncGenerator() });
}
{
const iterable = {
*[Symbol.iterator]() {
yield new Uint8Array([7, 8, 9]);
},
};
// @ts-expect-error normal iterators are not supported
fetch("https://example.com", { body: iterable });
}
{
// @ts-expect-error
fetch("https://example.com", { body: 123 });
}
{
// @ts-expect-error
fetch("https://example.com", { body: true });
}
{
// @ts-expect-error
fetch("https://example.com", { body: false });
}
{
// @ts-expect-error
fetch("https://example.com", { body: { plain: "object" } });
}
{
// @ts-expect-error
fetch("https://example.com", { body: ["array", "of", "strings"] });
}
{
// @ts-expect-error
fetch("https://example.com", { body: new Date() });
}
{
// @ts-expect-error
fetch("https://example.com", { body: /regex/ });
}
{
// @ts-expect-error
fetch("https://example.com", { body: Symbol("symbol") });
}
{
// @ts-expect-error
fetch("https://example.com", { body: BigInt(123) });
}
{
// @ts-expect-error
fetch("https://example.com", { body: new Map() });
}
{
// @ts-expect-error
fetch("https://example.com", { body: new Set() });
}
{
// @ts-expect-error
fetch("https://example.com", { body: new WeakMap() });
}
{
// @ts-expect-error
fetch("https://example.com", { body: new WeakSet() });
}
{
// @ts-expect-error
fetch("https://example.com", { body: Promise.resolve("promise") });
}
{
// @ts-expect-error
fetch("https://example.com", { body: () => "function" });
}
{
// @ts-expect-error
fetch("https://example.com", { body: class MyClass {} });
}
{
// @ts-expect-error
fetch("https://example.com", { body: new Error("error") });
}
{
fetch("https://example.com", { method: "GET", body: "should not have body but types should still allow it" });
fetch("https://example.com", { method: "HEAD", body: "should not have body but types should still allow it" });
}
{
const multipartForm = new FormData();
multipartForm.append("field1", "value1");
multipartForm.append("file", new File(["content"], "test.txt"));
fetch("https://example.com", { body: multipartForm });
}
{
const searchParams = new URLSearchParams();
searchParams.append("key1", "value1");
searchParams.append("key2", "value2");
fetch("https://example.com", { body: searchParams });
}
{
fetch("https://example.com", { body: new SharedArrayBuffer(16) });
}
{
fetch("https://example.com", { body: new Float32Array([1.1, 2.2, 3.3]) });
fetch("https://example.com", { body: new Float64Array([1.1, 2.2, 3.3]) });
fetch("https://example.com", { body: new Int8Array([-128, 0, 127]) });
fetch("https://example.com", { body: new Uint16Array([0, 32768, 65535]) });
fetch("https://example.com", { body: new BigInt64Array([BigInt(1), BigInt(2)]) });
fetch("https://example.com", { body: new BigUint64Array([BigInt(1), BigInt(2)]) });
}
{
const textStream = new ReadableStream<string>({
start(controller) {
controller.enqueue("chunk1");
controller.enqueue("chunk2");
controller.close();
},
});
fetch("https://example.com", { body: textStream });
}
{
const byteStream = new ReadableStream<Uint8Array<ArrayBuffer>>({
start(controller) {
controller.enqueue(new Uint8Array([1, 2, 3]));
controller.enqueue(new Uint8Array([4, 5, 6]));
controller.close();
},
});
fetch("https://example.com", { body: byteStream });
}
{
async function notGenerator() {
return "not a generator";
}
// @ts-expect-error - Invalid async without generator
fetch("https://example.com", { body: notGenerator() });
}
{
const invalidIterable = {
notAnIterator() {
return "invalid";
},
};
// @ts-expect-error - Invalid object without proper iterator
fetch("https://example.com", { body: invalidIterable });
}
if (typeof process !== "undefined") {
// @ts-expect-error - Node.js specific invalid types
fetch("https://example.com", { body: process });
}
{
// @ts-expect-error - Invalid number array (not typed)
fetch("https://example.com", { body: [1, 2, 3, 4] });
}
{
// @ts-expect-error - Invalid nested structure
fetch("https://example.com", { body: { nested: { object: { structure: "invalid" } } } });
}
{
// @ts-expect-error - NaN
fetch("https://example.com", { body: NaN });
}
{
// @ts-expect-error - Infinity
fetch("https://example.com", { body: Infinity });
}
{
// @ts-expect-error - -Infinity
fetch("https://example.com", { body: -Infinity });
}
// Proxy option types
{
// String proxy URL is valid
fetch("https://example.com", { proxy: "http://proxy.example.com:8080" });
fetch("https://example.com", { proxy: "https://user:[email protected]:8080" });
}
{
// Object proxy with url is valid
fetch("https://example.com", {
proxy: {
url: "http://proxy.example.com:8080",
},
});
}
{
// Object proxy with url as a URL object is valid
fetch("https://example.com", {
proxy: {
url: new URL("http://proxy.example.com:8080"),
},
});
}
{
// Object proxy with url and headers (plain object) is valid
fetch("https://example.com", {
proxy: {
url: "http://proxy.example.com:8080",
headers: {
"Proxy-Authorization": "Bearer token",
"X-Custom-Header": "value",
},
},
});
}
{
// Object proxy with url and headers (Headers instance) is valid
fetch("https://example.com", {
proxy: {
url: "http://proxy.example.com:8080",
headers: new Headers({ "Proxy-Authorization": "Bearer token" }),
},
});
}
{
// Object proxy with url and headers (array of tuples) is valid
fetch("https://example.com", {
proxy: {
url: "http://proxy.example.com:8080",
headers: [
["Proxy-Authorization", "Bearer token"],
["X-Custom", "value"],
],
},
});
}
{
// @ts-expect-error - Proxy object without url is invalid
fetch("https://example.com", { proxy: { headers: { "X-Custom": "value" } } });
}
{
// @ts-expect-error - Proxy url must be string, not number
fetch("https://example.com", { proxy: { url: 8080 } });
}
{
// @ts-expect-error - Proxy must be string or object, not number
fetch("https://example.com", { proxy: 8080 });
}
{
// @ts-expect-error - Proxy must be string or object, not boolean
fetch("https://example.com", { proxy: true });
}
{
// @ts-expect-error - Proxy must be string or object, not array
fetch("https://example.com", { proxy: ["http://proxy.example.com"] });
}
+183
View File
@@ -0,0 +1,183 @@
import { dlopen, FFIType, JSCallback, read, suffix, type CString, type Pointer } from "bun:ffi";
import * as tsd from "./utilities";
// `suffix` is either "dylib", "so", or "dll" depending on the platform
// you don't have to use "suffix", it's just there for convenience
const path = `libsqlite3.${suffix}`;
const lib = dlopen(
path, // a library name or file path
{
sqlite3_libversion: {
// no arguments, returns a string
args: [],
returns: FFIType.cstring,
},
add: {
args: [FFIType.i32, FFIType.i32],
returns: FFIType.i32,
},
ptr_type: {
args: [FFIType.pointer],
returns: FFIType.pointer,
},
fn_type: {
args: [FFIType.function],
returns: FFIType.function,
},
allArgs: {
args: [
FFIType.char, // string
FFIType.int8_t,
FFIType.i8,
FFIType.uint8_t,
FFIType.u8,
FFIType.int16_t,
FFIType.i16,
FFIType.uint16_t,
FFIType.u16,
FFIType.int32_t,
FFIType.i32,
FFIType.int,
FFIType.uint32_t,
FFIType.u32,
FFIType.int64_t,
FFIType.i64,
FFIType.uint64_t,
FFIType.u64,
FFIType.double,
FFIType.f64,
FFIType.float,
FFIType.f32,
FFIType.bool,
FFIType.ptr,
FFIType.pointer,
FFIType.void,
FFIType.cstring,
FFIType.i64_fast,
FFIType.u64_fast,
],
returns: FFIType.void,
},
},
);
declare const ptr: Pointer;
tsd.expectType<string | null>(lib.symbols.sqlite3_libversion());
tsd.expectType<number>(lib.symbols.add(1, 2));
tsd.expectType<Pointer | bigint | null>(lib.symbols.ptr_type(ptr));
tsd.expectType<Pointer | bigint | null>(lib.symbols.fn_type(new JSCallback(() => {}, {})));
function _arg(
...params: [
number,
number,
number,
number,
number,
number,
number,
number,
number,
number,
number,
number,
number,
number,
number,
number,
number,
number,
number,
number,
number,
number,
boolean,
Pointer,
Pointer,
// tslint:disable-next-line: void-return
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type
void,
CString,
number | bigint,
number | bigint,
]
) {
console.log("asdf");
}
_arg;
type libParams = Parameters<(typeof lib)["symbols"]["allArgs"]>;
tsd.expectTypeEquals<libParams[0], number>(true);
tsd.expectTypeEquals<libParams[1], number>(true);
tsd.expectTypeEquals<libParams[2], number>(true);
tsd.expectTypeEquals<libParams[3], number>(true);
tsd.expectTypeEquals<libParams[4], number>(true);
tsd.expectTypeEquals<libParams[5], number>(true);
tsd.expectTypeEquals<libParams[6], number>(true);
tsd.expectTypeEquals<libParams[7], number>(true);
tsd.expectTypeEquals<libParams[8], number>(true);
tsd.expectTypeEquals<libParams[9], number>(true);
tsd.expectTypeEquals<libParams[10], number>(true);
tsd.expectTypeEquals<libParams[11], number>(true);
tsd.expectTypeEquals<libParams[12], number>(true);
tsd.expectTypeEquals<libParams[13], number>(true);
tsd.expectTypeEquals<libParams[14], number>(true);
tsd.expectTypeEquals<libParams[15], number>(true);
tsd.expectTypeEquals<libParams[16], number>(true);
tsd.expectTypeEquals<libParams[17], number>(true);
tsd.expectTypeEquals<libParams[18], number>(true);
tsd.expectTypeEquals<libParams[19], number>(true);
tsd.expectTypeEquals<libParams[20], number>(true);
tsd.expectTypeEquals<libParams[21], number>(true);
tsd.expectTypeEquals<libParams[22], boolean>(true);
tsd.expectTypeEquals<libParams[23], Pointer>(true);
tsd.expectTypeEquals<libParams[24], Pointer>(true);
tsd.expectTypeEquals<libParams[25], undefined>(true);
tsd.expectTypeEquals<libParams[26], CString>(true);
tsd.expectTypeEquals<libParams[27], number | bigint>(true);
tsd.expectTypeEquals<libParams[28], number | bigint>(true);
// tslint:disable-next-line:no-object-literal-type-assertion
const as_const_test = {
sqlite3_libversion: {
args: [],
returns: FFIType.cstring,
},
multi_args: {
args: [FFIType.i32, FFIType.f32],
returns: FFIType.void,
},
no_returns: {
args: [FFIType.i32],
},
no_args: {
returns: FFIType.i32,
},
} as const;
const lib2 = dlopen(path, as_const_test);
tsd.expectType<string | null>(lib2.symbols.sqlite3_libversion());
// tslint:disable-next-line:no-void-expression
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type
tsd.expectType<void>(lib2.symbols.multi_args(1, 2));
tsd.expectTypeEquals<ReturnType<(typeof lib2)["symbols"]["no_returns"]>, undefined>(true);
tsd.expectTypeEquals<Parameters<(typeof lib2)["symbols"]["no_args"]>, []>(true);
tsd.expectType<number>(read.u8(ptr));
tsd.expectType<number>(read.u8(ptr, 0));
tsd.expectType<number>(read.i8(ptr, 0));
tsd.expectType<number>(read.u16(ptr, 0));
tsd.expectType<number>(read.i16(ptr, 0));
tsd.expectType<number>(read.u32(ptr, 0));
tsd.expectType<number>(read.i32(ptr, 0));
tsd.expectType<bigint>(read.u64(ptr, 0));
tsd.expectType<bigint>(read.i64(ptr, 0));
tsd.expectType<number>(read.f32(ptr, 0));
tsd.expectType<number>(read.f64(ptr, 0));
tsd.expectType<number>(read.ptr(ptr, 0));
tsd.expectType<number>(read.intptr(ptr, 0));
@@ -0,0 +1 @@
{ "bun": "is cool", "fact": true }
+22
View File
@@ -0,0 +1,22 @@
import { constants, readdir, watch } from "node:fs";
constants.O_APPEND;
import * as fs from "fs";
import { exists } from "fs/promises";
import * as tsd from "./utilities";
tsd.expectType<Promise<boolean>>(exists("/etc/passwd"));
tsd.expectType<Promise<boolean>>(fs.promises.exists("/etc/passwd"));
// file path
watch(".", (eventType, filename) => {
console.log(`event type = ${eventType}`);
if (filename) {
console.log(`filename = ${filename}`);
}
});
await Bun.file("sdf").exists();
readdir(".", { recursive: true }, (err, files) => {});
@@ -0,0 +1,13 @@
import { FileSystemRouter } from "bun";
import { expectType } from "./utilities";
const router = new FileSystemRouter({
dir: "/pages",
style: "nextjs",
});
const match = router.match("/");
expectType<string>(match?.name!);
expectType<string>(match?.pathname!);
expectType<Record<string, string>>(match?.query!);
expectType<Record<string, string>>(match?.params!);
@@ -0,0 +1,342 @@
import * as fs from "fs";
import * as fsPromises from "fs/promises";
import { expectAssignable, expectType } from "./utilities";
// FileBlob
expectType<ReadableStream<Uint8Array>>(Bun.file("index.test-d.ts").stream());
expectType<Promise<ArrayBuffer>>(Bun.file("index.test-d.ts").arrayBuffer());
expectType<Promise<Uint8Array>>(Bun.file("index.test-d.ts").bytes());
expectType<Promise<string>>(Bun.file("index.test-d.ts").text());
expectType<number>(Bun.file("index.test-d.ts").size);
expectType<string>(Bun.file("index.test-d.ts").type);
// Hash
expectType<string>(new Bun.MD4().update("test").digest("hex"));
expectType<string>(new Bun.MD5().update("test").digest("hex"));
expectType<string>(new Bun.SHA1().update("test").digest("hex"));
expectType<string>(new Bun.SHA224().update("test").digest("hex"));
expectType<string>(new Bun.SHA256().update("test").digest("hex"));
expectType<string>(new Bun.SHA384().update("test").digest("hex"));
expectType<string>(new Bun.SHA512().update("test").digest("hex"));
expectType<string>(new Bun.SHA512_256().update("test").digest("hex"));
// Zlib Functions
expectType<Uint8Array>(Bun.deflateSync(new Uint8Array(128)));
expectType<Uint8Array>(Bun.gzipSync(new Uint8Array(128)));
expectType<Uint8Array>(
Bun.deflateSync(new Uint8Array(128), {
level: -1,
memLevel: 8,
strategy: 0,
windowBits: 15,
}),
);
expectType<Uint8Array>(Bun.gzipSync(new Uint8Array(128), { level: 9, memLevel: 6, windowBits: 27 }));
expectType<Uint8Array>(Bun.inflateSync(new Uint8Array(64))); // Pretend this is DEFLATE compressed data
expectType<Uint8Array>(Bun.gunzipSync(new Uint8Array(64))); // Pretend this is GZIP compressed data
expectAssignable<Bun.ZlibCompressionOptions>({ windowBits: -11 });
// Other
expectType<Promise<number>>(Bun.write("test.json", "lol"));
expectType<Promise<number>>(Bun.write("test.json", new ArrayBuffer(32)));
expectType<URL>(Bun.pathToFileURL("/foo/bar.txt"));
expectType<string>(Bun.fileURLToPath(new URL("file:///foo/bar.txt")));
// Testing ../fs.d.ts
expectType<string>(fs.readFileSync("./index.d.ts", { encoding: "utf-8" }).toString());
expectType<boolean>(fs.existsSync("./index.d.ts"));
expectType<void>(fs.accessSync("./index.d.ts"));
expectType<void>(fs.appendFileSync("./index.d.ts", "test"));
expectType<void>(fs.mkdirSync("./index.d.ts"));
// Testing ^promises.d.ts
expectType<string>((await fsPromises.readFile("./index.d.ts", { encoding: "utf-8" })).toString());
expectType<Promise<void>>(fsPromises.access("./index.d.ts"));
expectType<Promise<void>>(fsPromises.appendFile("./index.d.ts", "test"));
expectType<Promise<void>>(fsPromises.mkdir("./index.d.ts"));
Bun.env;
Bun.version;
setImmediate;
clearImmediate;
setInterval;
clearInterval;
setTimeout;
clearTimeout;
const arg = new AbortSignal();
arg;
const e = new CustomEvent("asdf");
console.log(e);
exports;
module.exports;
global.AbortController;
global.Bun;
const er = new DOMException();
er.name;
er.HIERARCHY_REQUEST_ERR;
new Request(new Request("https://example.com"), {});
new Request("", { method: "POST" });
Bun.sleepSync(1); // sleep for 1 ms (not recommended)
await Bun.sleep(1); // sleep for 1 ms (recommended)
Blob;
WebSocket;
Request;
Response;
Headers;
FormData;
URL;
URLSearchParams;
ReadableStream;
WritableStream;
TransformStream;
ByteLengthQueuingStrategy;
CountQueuingStrategy;
TextEncoder;
TextDecoder;
ReadableStreamDefaultReader;
ReadableStreamBYOBReader;
ReadableStreamDefaultController;
ReadableByteStreamController;
WritableStreamDefaultWriter;
declare function stuff(arg: Blob): any;
declare function stuff(arg: WebSocket): any;
declare function stuff(arg: Request): any;
declare function stuff(arg: Response): any;
declare function stuff(arg: Headers): any;
declare function stuff(arg: FormData): any;
declare function stuff(arg: URL): any;
declare function stuff(arg: URLSearchParams): any;
declare function stuff(arg: ReadableStream): any;
declare function stuff(arg: WritableStream): any;
declare function stuff(arg: TransformStream): any;
declare function stuff(arg: ByteLengthQueuingStrategy): any;
declare function stuff(arg: CountQueuingStrategy): any;
declare function stuff(arg: TextEncoder): any;
declare function stuff(arg: TextDecoder): any;
declare function stuff(arg: ReadableStreamDefaultReader): any;
declare function stuff(arg: ReadableStreamDefaultController): any;
declare function stuff(arg: WritableStreamDefaultWriter): any;
stuff("asdf" as any as Blob);
new ReadableStream();
new WritableStream();
new Worker("asdfasdf");
new Worker("asdfasdf").onmessage;
new File([{} as Blob], "asdf");
new Crypto();
new ShadowRealm();
new ErrorEvent("asdf");
new CloseEvent("asdf");
new MessageEvent("asdf");
new CustomEvent("asdf");
// new Loader();
const readableStream = new ReadableStream();
const writableStream = new WritableStream();
{
const a = new ByteLengthQueuingStrategy({ highWaterMark: 0 });
a.highWaterMark;
}
{
const a = new ReadableStreamDefaultController();
a.close();
}
{
const a = new ReadableStreamDefaultReader(readableStream);
await a.cancel();
}
{
const a = new WritableStreamDefaultController();
a.error();
}
{
const a = new WritableStreamDefaultWriter(writableStream);
await a.close();
}
{
const a = new TransformStream();
a.readable;
}
{
const a = new TransformStreamDefaultController();
a.enqueue("asdf");
}
{
const a = new CountQueuingStrategy({ highWaterMark: 0 });
a.highWaterMark;
}
{
const a = new DOMException();
a.DATA_CLONE_ERR;
}
{
const a = new SubtleCrypto();
await a.decrypt("asdf", new CryptoKey(), new Uint8Array());
}
{
const a = new CryptoKey();
a.algorithm;
}
{
const a = new BuildError();
a.level;
}
{
const a = new ResolveError();
a.level;
}
{
const a = new AbortController();
a;
}
{
const a = new AbortSignal();
a.aborted;
}
{
const a = new Request("asdf");
await a.json();
a.cache;
}
{
const a = new Response();
await a.text();
a.ok;
}
{
const a = new FormData();
a.delete("asdf");
a.get("asdf");
a.append("asdf", "asdf");
a.set("asdf", "asdf");
a.forEach((value, key) => {
console.log(value, key);
});
a.entries();
a.get("asdf");
a.getAll("asdf");
a.has("asdf");
a.keys();
a.values();
a.toString();
}
{
const a = new Headers();
a.append("asdf", "asdf");
}
{
const a = new EventTarget();
a.dispatchEvent(new Event("asdf"));
}
{
const a = new Event("asdf");
a.bubbles;
a.composedPath()[0];
}
{
const a = new Blob();
a.size;
}
{
const a = new File(["asdf"], "stuff.txt ");
a.name;
}
{
performance.now();
}
{
const a = new URL("asdf");
a.host;
a.href;
}
{
new URLSearchParams();
new URLSearchParams("");
new URLSearchParams([]);
}
{
const a = new TextDecoder();
a.decode(new Uint8Array());
}
{
const a = new TextEncoder();
a.encode("asdf");
}
{
const a = new BroadcastChannel("stuff");
a.close();
}
{
const a = new MessageChannel();
a.port1;
new MessageChannel().port1.addEventListener("message", console.log);
}
{
const a = new MessagePort();
a.close();
}
{
var a!: RequestInit;
a.mode;
a.credentials;
}
{
var b!: ResponseInit;
b.status;
}
{
const ws = new WebSocket("ws://www.host.com/path");
ws.send("asdf");
new WebSocket("url", {
headers: { "Authorization": "my key" },
});
}
atob("asf");
btoa("asdf");
setInterval(() => {}, 1000);
setTimeout(() => {}, 1000);
clearInterval(1);
clearTimeout(1);
setImmediate(() => {});
clearImmediate(1);
const err = new Error("test");
err.cause = "asdf";
err.cause = new Error("asdf");
err.cause = new RangeError("asdf");
err.cause = new TypeError("asdf");
err.cause = new URIError("asdf");
expectType<unknown>(err.cause);
expectType<typeof Error>().is<ErrorConstructor>();
new Error("asdf", {
cause: "asdf",
});
new Error("asdf", {
cause: new Error("asdf"),
});
// @ts-expect-error this interface is defined top level in globals.d.ts so we
// are making sure that .d.ts is a module and that anything top level doesn't
// leak to userland
expectType<BunConsumerConvenienceMethods>();
@@ -0,0 +1,7 @@
Bun.hash.wyhash("asdf", 1234n);
// https://github.com/oven-sh/bun/issues/26043
// Bun.hash.crc32 accepts optional seed parameter for incremental CRC32 computation
let crc = 0;
crc = Bun.hash.crc32(new Uint8Array([1, 2, 3]), crc);
crc = Bun.hash.crc32(new Uint8Array([4, 5, 6]), crc);
@@ -0,0 +1,9 @@
const headers = new Headers();
headers.append("Set-Cookie", "a=1");
headers.append("Set-Cookie", "b=1; Secure");
// both of these are no longer in the types
// because Headers is declared with `class` in @types/node
// and I can't find a way to add them to the prototype
// console.log(headers.getAll("Set-Cookie")); // ["a=1", "b=1; Secure"]
// console.log(headers.toJSON()); // { "set-cookie": "a=1, b=1; Secure" }
@@ -0,0 +1,57 @@
import * as http from "http";
const server = new http.Server({});
server.address;
server.close();
server.eventNames;
server.getMaxListeners();
server.listeners;
server.on;
server.once;
server.prependListener;
server.prependOnceListener;
server.rawListeners;
server.removeAllListeners;
server.removeListener;
server.setMaxListeners;
server;
const agent = new http.Agent({});
http.globalAgent;
http.maxHeaderSize;
console.log(Object.getOwnPropertyNames(agent));
const req = http.request({ host: "localhost", port: 3000, method: "GET" });
req.abort;
req.end();
export {};
// URLSearchParams should be iterable
const sp = new URLSearchParams("q=foo&bar=baz");
for (const q of sp) {
console.log(q);
}
fetch("https://example.com", {
s3: {
accessKeyId: "123",
secretAccessKey: "456",
},
proxy: "cool",
});
const a = new Response(async function* () {
yield new Uint8Array([50, 60, 70]);
yield "hey";
await Bun.sleep(500);
});
const b_generator = async function* () {
await Bun.sleep(500);
yield new Uint8Array([1, 2, 3]);
yield "it works!";
};
const b = new Response(b_generator());
for (const r of await Promise.all([a.text(), b.text()])) console.log(r);
+474
View File
@@ -0,0 +1,474 @@
import fact from "./file.json";
console.log(fact);
import * as test from "bun:test";
test.describe;
test.it;
const options: Bun.TLSOptions = {
keyFile: "",
};
process.assert;
const error = new Error("hello world");
const clone = structuredClone(error);
console.log(clone.message); // "hello world"
new SubtleCrypto();
declare const mySubtleCrypto: SubtleCrypto;
new CryptoKey();
declare const myCryptoKey: CryptoKey;
import * as sqlite from "bun:sqlite";
sqlite.Database;
Bun satisfies typeof import("bun");
expectType(Bun).is<typeof import("bun")>();
expectType<typeof import("bun")>().is<typeof Bun>();
type ConstructorOf<T> = new (...args: any[]) => T;
import * as NodeTLS from "node:tls";
import * as TLS from "tls";
process.revision;
NodeTLS satisfies typeof TLS;
TLS satisfies typeof NodeTLS;
type NodeTLSOverrideTest = NodeTLS.BunConnectionOptions;
type TLSOverrideTest = TLS.BunConnectionOptions;
WebAssembly.Global;
WebAssembly.Memory;
WebAssembly.compile;
WebAssembly.compileStreaming;
WebAssembly.instantiate;
WebAssembly.instantiateStreaming;
WebAssembly.validate;
WebAssembly.Global satisfies ConstructorOf<Bun.WebAssembly.Global>;
WebAssembly.Memory satisfies ConstructorOf<Bun.WebAssembly.Memory>;
type wasmglobalthing = Bun.WebAssembly.Global;
type S3OptionsFromNamespace = Bun.S3Options;
type S3OptionsFromImport = import("bun").S3Options;
type c = import("bun").S3Client;
Bun.s3.file("").name;
const client = new Bun.S3Client({
secretAccessKey: "",
});
new TextEncoder();
client.file("");
Bun.fetch;
// just some APIs
new Request("url");
new Response();
new Headers();
new URL("");
new URLSearchParams([["cool", "stuff"]]);
new File([], "filename", { type: "text/plain" });
new Blob([], { type: "text/plain" });
new ReadableStream();
new WritableStream();
new TransformStream();
new AbortSignal();
new AbortController();
AbortSignal.timeout(200);
AbortSignal.any([new AbortSignal()]);
AbortSignal.abort(200);
new TextDecoder();
new TextEncoder();
fetch("url", {
proxy: "",
});
fetch(new URL("url"), {
proxy: "",
});
Bun.fetch(new URL("url"), {
proxy: "",
});
Bun.S3Client;
Bun.$`hey`;
type b = Bun.$.ShellPromise;
const myShellPromise: Bun.$.ShellPromise = Bun.$`hey`;
const myShellError: Bun.$.ShellError = new Bun.$.ShellError();
expectType(myShellPromise).is<Bun.$.ShellPromise>();
expectType(myShellError).is<Bun.$.ShellError>();
const myShellConstructor: typeof Bun.$.Shell = Bun.$.Shell;
const myShellPromiseConstructor: typeof Bun.$.ShellPromise = Bun.$.ShellPromise;
const myShellErrorConstructor: typeof Bun.$.ShellError = Bun.$.ShellError;
expectType(myShellConstructor).is<typeof Bun.$.Shell>();
expectType(myShellPromiseConstructor).is<typeof Bun.$.ShellPromise>();
expectType(myShellErrorConstructor).is<typeof Bun.$.ShellError>();
const myShellInstance: Bun.$ = new Bun.$.Shell();
await myShellInstance`hey`;
expectType(Bun.$).is<Bun.$>();
const myOtherShell = Bun.$.nothrow();
expectType(myOtherShell).is<Bun.$>();
expectType(myShellInstance).is<typeof Bun.$>();
await Bun.$.nothrow().throws(false).env({ TEST: "cool" }).cwd("/")`exit 0`;
await myShellInstance.nothrow().throws(false).env({ TEST: "cool" }).cwd("/")`exit 0`;
Bun.$;
declare const e: unknown;
if (e instanceof Bun.$.ShellError) {
expectType(e.exitCode).is<number>();
expectType(e.stderr).is<Buffer>();
expectType(e.stdout).is<Buffer>();
}
new Promise(resolve => {
resolve(1);
});
import.meta.hot.on("bun:bun:beforeFullReloadBut also allows anything", () => {
//
});
new Map();
new Set();
new WeakMap();
new WeakSet();
new Map();
new Set();
new WeakMap();
Promise.try(() => {
return 1;
});
Promise.try(() => {
throw new Error("test");
});
Promise.try((message: string) => {
throw new Error(message);
}, "Bun");
declare const myReadableStream: ReadableStream<string>;
for await (const chunk of myReadableStream) {
console.log(chunk);
expectType(chunk).is<string>();
}
for await (const chunk of Bun.stdin.stream()) {
// chunk is Uint8Array
// this converts it to text (assumes ASCII encoding)
const chunkText = Buffer.from(chunk).toString();
console.log(`Chunk: ${chunkText}`);
expectType(chunk).is<Uint8Array<ArrayBuffer>>();
expectType(chunkText).is<string>();
}
const myAsyncGenerator = async function* () {
yield new Uint8Array([1, 2, 3]);
yield new Uint8Array([4, 5, 6]);
};
new Response(myAsyncGenerator());
const statuses = [200, 400, 401, 403, 404, 500, 501, 502, 503, 504];
const r = new Request("", {
body: "",
});
await fetch(r);
await fetch("", {
tls: {
key: Bun.file("key.pem"),
cert: Bun.file("cert.pem"),
ca: [Bun.file("ca.pem")],
rejectUnauthorized: false,
},
});
r.method;
r.body;
r.headers.get("content-type");
new Request("", {});
new Bun.$.ShellError() instanceof Bun.$.ShellError;
await r.json();
await r.text();
declare const headers: Headers;
headers.toJSON();
const req1 = new Request("", {
body: "",
});
for (const header of new Headers()) {
console.log(header);
}
fetch("", {
tls: {
rejectUnauthorized: false,
checkServerIdentity: () => {
return undefined;
},
},
});
req1.body;
req1.json();
req1.formData();
req1.arrayBuffer();
req1.blob();
req1.text();
req1.arrayBuffer();
req1.blob();
req1.headers;
req1.headers.toJSON();
new ReadableStream({});
const body = await fetch(req1);
Bun.fetch satisfies typeof fetch;
Bun.fetch.preconnect satisfies typeof fetch.preconnect;
await body.text();
fetch;
fetch.preconnect(new URL(""));
Bun.serve({
port: 3000,
fetch: () => new Response("ok"),
tls: {
key: Bun.file(""), // do this!
cert: Bun.file(""), // do this!
},
});
import type { BinaryLike } from "node:crypto";
declare function asIs(value: BinaryLike): BinaryLike;
asIs(Buffer.from("Hey", "utf-8"));
new URL("", "");
const myUrl: URL = new URL("");
URL.canParse;
URL.createObjectURL;
URL.revokeObjectURL;
declare const myBodyInit: Bun.BodyInit;
declare const myHeadersInit: Bun.HeadersInit;
await new Blob().text();
await new Blob().json();
await new Blob().arrayBuffer();
await new Blob().bytes();
await new Blob().formData();
await new File(["code"], "name.ts").text();
await new File(["code"], "name.ts").json();
await new File(["code"], "name.ts").arrayBuffer();
await new File(["code"], "name.ts").bytes();
await new File(["code"], "name.ts").formData();
await Bun.file("test").text();
await Bun.file("test").json();
await Bun.file("test").arrayBuffer();
await Bun.file("test").bytes();
await Bun.file("test").formData();
new MessagePort();
new File(["code"], "name.ts");
URL.parse("bun.sh");
URL.parse("bun.sh", "bun.sh");
Error.isError(new Error());
Response.json("");
Response.redirect("bun.sh", 300);
Response.error();
Response.redirect("bun.sh", 302);
Response.redirect("bun.sh", {
headers: {
"x-bun": "is cool",
},
});
Bun.inspect.custom;
Bun.inspect;
fetch.preconnect("bun.sh");
Bun.fetch.preconnect("bun.sh");
new Uint8Array().toBase64();
Bun.fetch("", {
proxy: "",
s3: {
acl: "public-read",
},
});
new HTMLRewriter()
.on("script", {
element(element) {
console.log(element.getAttribute("src"));
},
})
.transform(new Response('<script src="/main.js"></script>'));
Buffer.from("foo").equals(Buffer.from("bar"));
const myHeaders: Headers = new Headers();
myHeaders.append("x-bun", "is cool");
myHeaders.get("x-bun");
myHeaders.has("x-bun");
myHeaders.set("x-bun", "is cool");
myHeaders.delete("x-bun");
myHeaders.getSetCookie();
myHeaders.toJSON();
myHeaders.count;
myHeaders.getAll("set-cookie");
myHeaders.getAll("Set-Cookie");
// @ts-expect-error
myHeaders.getAll("Should fail");
const myRequest: Request = new Request("", {
headers: new Headers(myHeaders),
body: "",
method: "GET",
redirect: "follow",
credentials: "include",
mode: "cors",
referrer: "about:client",
referrerPolicy: "no-referrer",
window: null,
});
const myResponse: Response = new Response("", {
headers: new Headers([]),
status: 200,
statusText: "OK",
});
const myRequestInit: RequestInit = {
body: "",
method: "GET",
};
declare const requestInitKeys: `evaluate-${keyof RequestInit}`;
requestInitKeys satisfies string;
Bun.serve({
fetch(req) {
req.headers;
const headers = req.headers.toJSON();
const body = req.method === "GET" || req.method === "HEAD" ? undefined : req.body;
return new Response(body, {
headers,
status: statuses[Math.floor(Math.random() * statuses.length)] ?? 200,
});
},
});
import.meta.hot.accept();
import.meta.hot.data;
fetch("", {
tls: {
rejectUnauthorized: false,
},
});
new AbortController();
const myAbortController: AbortController = new AbortController();
new AbortSignal();
const myAbortSignal: AbortSignal = new AbortSignal();
import { serve } from "bun";
new Worker("", {
type: "module",
preload: ["preload.ts"],
});
serve({
fetch(req) {
const headers = req.headers.toJSON();
const body = req.method === "GET" || req.method === "HEAD" ? undefined : req.body;
return new Response(body, {
headers,
status: statuses[Math.floor(Math.random() * statuses.length)] ?? 200,
});
},
});
import { s3 } from "bun";
import { expectType } from "./utilities";
s3.file("");
declare const key: string;
declare const cert: string;
Bun.serve({
fetch: () => new Response("ok"),
tls: {
key,
cert,
},
});
const signal = AbortSignal.timeout(1000);
expectType(signal).is<AbortSignal>();
expectType(signal.aborted).is<boolean>();
expectType(RegExp.escape("foo.bar")).is<string>();
const controller = new AbortController();
expectType(controller.signal).is<AbortSignal>();
expectType(controller.abort()).is<void>();
expectType(controller.abort("reason")).is<void>();
expectType(controller.signal.aborted).is<boolean>();
controller.signal.addEventListener("abort", event => {
expectType(event).is<Event>();
});
controller.signal.removeEventListener("abort", event => {
expectType(event).is<Event>();
});
@@ -0,0 +1,44 @@
// This is (for now) very loose implementation reference, mostly type testing
import { expectType } from "./utilities";
const mySecurityScanner: Bun.Security.Scanner = {
version: "1",
scan: async ({ packages }) => {
const response = await fetch("https://threat-feed.example.com");
if (!response.ok) {
throw new Error("Unable to fetch threat feed");
}
// Would recommend using a schema library or something to validate here. You
// should throw if the parsing fails rather than returning no advisories,
// this code needs to be defensive...
const myThreatFeed = (await response.json()) as Array<{
package: string;
version: string;
url: string;
description: string;
category: "unhealthy" | "spam" | "malware"; // Imagine some other categories...
}>;
return myThreatFeed.flatMap((threat): Bun.Security.Advisory[] => {
const match = packages.some(p => p.name === threat.package && p.version === threat.version);
if (!match) {
return [];
}
return [
{
level: threat.category === "malware" ? "fatal" : "warn",
package: threat.package,
url: threat.url,
description: threat.description,
},
];
});
},
};
expectType(mySecurityScanner).toBeDefined();
+78
View File
@@ -0,0 +1,78 @@
import { deepEquals } from "bun";
import {
callerSourceOrigin,
deserialize,
drainMicrotasks,
edenGC,
fullGC,
gcAndSweep,
heapSize,
heapStats,
memoryUsage,
noFTL,
noOSRExitFuzzing,
numberOfDFGCompiles,
optimizeNextInvocation,
profile,
reoptimizationRetryCount,
serialize,
startSamplingProfiler,
totalCompileTime,
type SamplingProfile,
type SamplingProfileStackFrame,
type SamplingProfileStackTraces,
} from "bun:jsc";
import { expectType } from "./utilities";
const obj = { a: 1, b: 2 };
const buffer = serialize(obj);
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const clone = deserialize(buffer);
if (deepEquals(obj, clone)) {
console.log("They are equal!");
}
expectType(gcAndSweep()).is<number>();
expectType(fullGC()).is<number>();
expectType(edenGC()).is<number>();
expectType(heapSize()).is<number>();
const stats = heapStats();
expectType(stats.heapSize).is<number>();
expectType(stats.objectTypeCounts).is<Record<string, number>>();
expectType(stats.protectedObjectTypeCounts).is<Record<string, number>>();
expectType(memoryUsage().current).is<number>();
expectType(memoryUsage().pageFaults).is<number>();
expectType(callerSourceOrigin()).is<string | null>();
function add(a: number, b: number) {
return a + b;
}
expectType(noFTL(add)).is<void>();
expectType(noOSRExitFuzzing(add)).is<void>();
expectType(optimizeNextInvocation(add)).is<void>();
expectType(numberOfDFGCompiles(add)).is<number>();
expectType(reoptimizationRetryCount(add)).is<number>();
expectType(totalCompileTime()).is<number>();
expectType(drainMicrotasks()).is<void>();
startSamplingProfiler();
startSamplingProfiler("/tmp/profile");
startSamplingProfiler(undefined, 100);
const syncProfile = profile(add, 100, 1, 2);
expectType(syncProfile).is<SamplingProfile>();
expectType(syncProfile.functions).is<string>();
expectType(syncProfile.bytecodes).is<string>();
expectType(syncProfile.stackTraces).is<SamplingProfileStackTraces>();
expectType(syncProfile.stackTraces.interval).is<number>();
expectType(syncProfile.stackTraces.traces[0]!.frames).is<SamplingProfileStackFrame[]>();
expectType(syncProfile.stackTraces.traces[0]!.frames[0]!.sourceURL).is<string | undefined>();
expectType(syncProfile.stackTraces.sources[0]!.url).is<string | undefined>();
const asyncProfile = profile(async () => {});
expectType(asyncProfile).is<Promise<SamplingProfile>>();
@@ -0,0 +1,31 @@
import { jest, mock } from "bun:test";
import { expectType } from "./utilities";
const mock1 = mock((arg: string) => {
return arg.length;
});
const arg1 = mock1("1");
expectType<number>(arg1);
mock;
type arg2 = jest.Spied<() => string>;
declare var arg2: arg2;
arg2.mock.calls[0];
mock;
// @ts-expect-error
jest.fn<() => Promise<string>>().mockReturnValue("asdf");
// @ts-expect-error
jest.fn<() => string>().mockReturnValue(24);
jest.fn<() => string>().mockReturnValue("24");
jest.fn<() => Promise<string>>().mockResolvedValue("asdf");
// @ts-expect-error
jest.fn<() => string>().mockResolvedValue(24);
// @ts-expect-error
jest.fn<() => string>().mockResolvedValue("24");
jest.fn().mockClear();
jest.fn().mockReset();
jest.fn().mockRejectedValueOnce(new Error());
+11
View File
@@ -0,0 +1,11 @@
import * as net from "node:net";
const socket = net.connect({
port: 80,
host: "localhost",
});
socket.connect({
port: 80,
host: "localhost",
});
@@ -0,0 +1,14 @@
{
"name": "fixture",
"module": "index.ts",
"scripts": {
"check": "tsc --noEmit -p ./tsconfig.json"
},
"type": "module",
"dependencies": {
"typescript": "latest"
},
"resolutions": {
"@types/node": "latest"
}
}
@@ -0,0 +1,7 @@
import { performance as _performance } from "node:perf_hooks";
performance.now();
performance.timeOrigin;
_performance.now();
_performance.timeOrigin;
@@ -0,0 +1,50 @@
process.memoryUsage();
process.cpuUsage().system;
process.cpuUsage().user;
process.on("SIGINT", () => {
console.log("Interrupt from keyboard");
});
process.on("beforeExit", code => {
console.log("Event loop is empty and no work is left to schedule.", code);
});
process.on("exit", code => {
console.log("Exiting with code:", code);
});
process.kill(123, "SIGTERM");
process.getegid!();
process.geteuid!();
process.getgid!();
process.getgroups!();
process.getuid!();
process.once("SIGINT", () => {
console.log("Interrupt from keyboard");
});
// commented methods are not yet implemented
console.log(process.allowedNodeEnvironmentFlags);
// console.log(process.channel);
// console.log(process.connected);
// console.log(process.constrainedMemory);
console.log(process.debugPort);
// console.log(process.disconnect);
// console.log(process.getActiveResourcesInfo);
// console.log(process.setActiveResourcesInfo);
// console.log(process.setuid);
// console.log(process.setgid);
// console.log(process.setegid);
// console.log(process.seteuid);
// console.log(process.setgroups);
// console.log(process.hasUncaughtExceptionCaptureCallback);
// console.log(process.initGroups);
console.log(process.listenerCount("exit"));
console.log(process.memoryUsage());
// console.log(process.report);
// console.log(process.resourceUsage);
// console.log(process.setSourceMapsEnabled());
// console.log(process.send);
process.reallyExit();
process.assert(false, "PleAsE don't Use THIs It IS dEpReCATED");
@@ -0,0 +1,11 @@
import * as readline from "node:readline/promises";
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
terminal: true,
});
await rl.question("What is your age?\n").then(answer => {
console.log("Your age is: " + answer);
});
@@ -0,0 +1,31 @@
import { expectType } from "./utilities";
expectType(Bun.redis.publish("hello", "world")).is<Promise<number>>();
const copy = await Bun.redis.duplicate();
expectType(copy.connected).is<boolean>();
expectType(copy).is<Bun.RedisClient>();
const listener: Bun.RedisClient.StringPubSubListener = (message, channel) => {
expectType(message).is<string>();
expectType(channel).is<string>();
};
Bun.redis.subscribe("hello", listener);
// Buffer subscriptions are not yet implemented
// const bufferListener: Bun.RedisClient.BufferPubSubListener = (message, channel) => {
// expectType(message).is<Uint8Array<ArrayBuffer>>();
// expectType(channel).is<string>();
// };
// Bun.redis.subscribe("hello", bufferListener);
expectType(
copy.subscribe("hello", message => {
expectType(message).is<string>();
}),
).is<Promise<number>>();
await copy.unsubscribe();
await copy.unsubscribe("hello");
expectType(copy.unsubscribe("hello", () => {})).is<Promise<void>>();
+31
View File
@@ -0,0 +1,31 @@
import { s3 } from "bun";
async function doFileOps(file: Bun.S3File) {
console.log(file.bucket);
console.log(file.presign());
console.log(file.presign({ expiresIn: 1, method: "PUT" }));
console.log(file.type);
await file.json();
await file.arrayBuffer();
await file.delete();
await file.formData();
for await (const chunk of file.readable) {
console.log(chunk);
}
}
doFileOps(s3.file("stream.bin"));
doFileOps(
new Bun.S3Client({
accessKeyId: "123",
}).file("stream.bin"),
);
doFileOps(
s3.file("stream.bin", {
type: "application/octet-stream",
}),
);
@@ -0,0 +1,869 @@
// This file is checked in the `bun-types.test.ts` integration test for successful typechecking, but also checked
// on its own to make sure that the types line up with actual implementation of Bun.serve()
import { expect, it } from "bun:test";
import fs from "node:fs";
import os from "node:os";
import { join } from "node:path";
import html from "./html.html";
import { expectType } from "./utilities";
// XXX: importing this from "harness" caused a failure in bun-types.test.ts
function tmpdirSync(pattern: string = "bun.test."): string {
return fs.mkdtempSync(join(fs.realpathSync.native(os.tmpdir()), pattern));
}
export default {
fetch: req => Response.json(req.url),
websocket: {
message(ws) {
expectType(ws.data).is<{ name: string }>();
},
},
routes: {
"/": req => {
expectType(req.params).is<Record<string, string>>();
},
},
} satisfies Bun.Serve.Options<{ name: string }>;
function expectInstanceOf<T>(value: unknown, constructor: new (...args: any[]) => T): asserts value is T {
expect(value).toBeInstanceOf(constructor);
}
function test<T = undefined, R extends string = string>(
name: string,
options: Bun.Serve.Options<T, R>,
{
onConstructorFailure,
overrideExpectBehavior,
skip: skipOptions,
}: {
onConstructorFailure?: (error: Error) => void | Promise<void>;
overrideExpectBehavior?: (server: NoInfer<Bun.Server<T>>) => void | Promise<void>;
skip?: boolean;
} = {},
) {
const skip = skipOptions || ("unix" in options && typeof options.unix === "string" && process.platform === "win32");
async function testServer(server: Bun.Server<T>) {
if (overrideExpectBehavior) {
await overrideExpectBehavior(server);
} else {
expectInstanceOf(server.url, URL);
expect(server.hostname).toBeDefined();
expect(server.port).toBeGreaterThan(0);
expect(server.url.toString()).toStartWith("http");
expect(await fetch(server.url)).toBeInstanceOf(Response);
}
}
it.skipIf(skip)(name, async () => {
try {
using server = Bun.serve(options);
try {
await testServer(server);
} finally {
await server.stop(true);
}
} catch (error) {
if (onConstructorFailure) {
expectInstanceOf(error, Error);
await onConstructorFailure(error);
} else throw error;
}
});
}
test("basic", {
routes: {
"/123": {
"GET": new Response("Cool/great"),
},
},
fetch(req) {
console.log(req.url); // => http://localhost:3000/
return new Response("Hello World");
},
});
test(
"basic + tls",
{
fetch(req) {
console.log(req.url); // => http://localhost:3000/
return new Response("Hello World");
},
tls: {
key: "ca.pem",
cert: "cert.pem",
},
},
{
onConstructorFailure: error => {
expect(error.message).toContain("error:0900006e:PEM routines:OPENSSL_internal:NO_START_LINE");
},
},
);
test(
"basic + invalid route value",
{
routes: {
"/": new Response("Hello World"),
// @ts-expect-error Invalid value
"/2": null,
},
},
{
onConstructorFailure: error => {
expect(error.message).toContain("'routes' expects a Record<string,");
},
},
);
test("basic + websocket + upgrade", {
websocket: {
message(ws, message) {
expectType<typeof ws>().is<Bun.ServerWebSocket<undefined>>();
ws.send(message);
expectType(message).is<string | Buffer<ArrayBuffer>>();
},
},
fetch(req, server) {
expectType(req).is<Request>();
// Upgrade to a ServerWebSocket if we can
// This automatically checks for the `Sec-WebSocket-Key` header
// meaning you don't have to check headers, you can just call `upgrade()`
if (server.upgrade(req)) {
// When upgrading, we return undefined since we don't want to send a Response
// return;
}
return new Response("Regular HTTP response");
},
});
test("basic + websocket + upgrade + all handlers", {
fetch(req, server) {
expectType(server.upgrade).is<
(
req: Request,
options: {
data?: { name: string };
headers?: Bun.HeadersInit;
},
) => boolean
>;
const url = new URL(req.url);
if (url.pathname === "/chat") {
if (
server.upgrade(req, {
data: {
name: new URL(req.url).searchParams.get("name") || "Friend",
},
headers: {
"Set-Cookie": "name=" + new URL(req.url).searchParams.get("name"),
},
})
) {
return;
}
}
return new Response("Expected a websocket connection", { status: 400 });
},
websocket: {
data: {} as { name: string },
open(ws) {
console.log("WebSocket opened");
ws.subscribe("the-group-chat");
},
message(ws, message) {
expectType(message).is<string | Buffer<ArrayBuffer>>();
ws.publish("the-group-chat", `${ws.data.name}: ${message.toString()}`);
},
close(ws, code, reason) {
expectType(code).is<number>();
expectType(reason).is<string>();
ws.publish("the-group-chat", `${ws.data.name} left the chat`);
},
drain(ws) {
expectType(ws.data.name).is<string>();
console.log("Please send me data. I am ready to receive it.");
},
perMessageDeflate: true,
},
});
test(
"basic error handling",
{
fetch(req) {
throw new Error("woops!");
},
error(error) {
return new Response(`<pre>${error.message}\n${error.stack}</pre>`, {
status: 500,
headers: {
"Content-Type": "text/html",
},
});
},
},
{
overrideExpectBehavior: async server => {
const res = await fetch(server.url);
expect(res.status).toBe(500);
expect(await res.text()).toContain("woops!");
},
},
);
test("port 0 + websocket + upgrade", {
port: 0,
fetch(req, server) {
server.upgrade(req);
if (Math.random() > 0.5) return undefined;
return new Response();
},
websocket: {
message(ws) {
expectType(ws).is<Bun.ServerWebSocket<undefined>>();
},
},
});
test(
"basic unix socket",
{
unix: `${tmpdirSync()}/bun.sock`,
fetch() {
return new Response();
},
},
{
overrideExpectBehavior: server => {
expect(server.hostname).toBeUndefined();
expect(server.port).toBeUndefined();
expect(server.url.toString()).toStartWith("unix://");
},
},
);
test(
"basic unix socket + websocket + upgrade",
{
unix: `${tmpdirSync()}/bun.sock`,
fetch(req, server) {
server.upgrade(req);
if (Math.random() > 0.5) return undefined;
return new Response();
},
websocket: { message() {} },
},
{
overrideExpectBehavior: server => {
expect(server.hostname).toBeUndefined();
expect(server.port).toBeUndefined();
expect(server.url.toString()).toStartWith("unix://");
},
},
);
test(
"basic unix socket + websocket + upgrade + tls",
{
unix: `${tmpdirSync()}/bun.sock`,
fetch(req, server) {
server.upgrade(req);
if (Math.random() > 0.5) return undefined;
return new Response();
},
websocket: { message() {} },
tls: {},
},
{
overrideExpectBehavior: server => {
expect(server.hostname).toBeUndefined();
expect(server.port).toBeUndefined();
expect(server.url.toString()).toStartWith("unix://");
},
},
);
test(
"basic unix socket 2",
{
unix: `${tmpdirSync()}/bun.sock`,
fetch(req, server) {
if (server.upgrade(req)) {
return;
}
return new Response();
},
websocket: { message() {} },
},
{
overrideExpectBehavior: server => {
expect(server.hostname).toBeUndefined();
expect(server.port).toBeUndefined();
expect(server.url.toString()).toStartWith("unix://");
},
},
);
test(
"basic unix socket + upgrade + cheap request to check upgrade",
{
unix: `${tmpdirSync()}/bun.sock`,
fetch(req, server) {
if (server.upgrade(req)) {
return;
}
return new Response("failed to upgrade", { status: 500 });
},
websocket: {
message: () => {},
},
},
{
overrideExpectBehavior: async server => {
expect(server.hostname).toBeUndefined();
expect(server.port).toBeUndefined();
expect(server.url.toString()).toStartWith("unix://");
async function cheapRequest(request: string) {
const p = Promise.withResolvers<void>();
let chunks: string[] = [];
const sock = await Bun.connect({
unix: server.url.toString(),
socket: {
data: (socket, chunk) => {
chunks.push(chunk.toString());
if (chunks.length === 1) {
p.resolve();
}
},
},
});
sock.write(request);
await p.promise;
return chunks.join("\n");
}
const result = await cheapRequest(
"GET / HTTP/1.1\r\n" +
"Host: example.com\r\n" +
"Upgrade: websocket\r\n" +
"Connection: Upgrade\r\n" +
"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" +
"Sec-WebSocket-Version: 13\r\n" +
"\r\n",
);
expect(result).toContain("HTTP/1.1 101 Switching Protocols\r\n");
expect(result).toContain("Upgrade: websocket\r\n");
expect(result).toContain("Connection: Upgrade\r\n");
expect(result).toContain("Sec-WebSocket-Accept: ");
},
},
);
test(
"basic unix socket + routes",
{
unix: `${tmpdirSync()}/bun.sock`,
routes: {
"/": new Response("Hello World"),
},
},
{
overrideExpectBehavior: server => {
expect(server.hostname).toBeUndefined();
expect(server.port).toBeUndefined();
expect(server.url.toString()).toStartWith("unix://");
},
},
);
test(
"unix socket with no routes or fetch handler (should fail)",
// @ts-expect-error - Missing fetch or routes
{
unix: `${tmpdirSync()}/bun.sock`,
},
{
onConstructorFailure: error => {
expect(error.message).toContain("Bun.serve() needs either:");
expect(error.message).toContain("A routes object:");
expect(error.message).toContain("Or a fetch handler");
},
},
);
test("basic routes + fetch + websocket + upgrade", {
routes: {
"/:test": req => {
return new Response(req.params.test);
},
},
fetch: (req, server) => {
if (!server.upgrade(req)) {
return new Response("not upgraded");
}
},
websocket: {
message: ws => {
ws.data;
ws.send(" ");
},
},
});
test("basic routes + fetch", {
routes: {
"/:test": req => {
return new Response(req.params.test);
},
},
fetch: (req, server) => {
return new Response("cool");
},
});
test("very basic fetch", {
fetch: (req, server) => {
return new Response("cool");
},
});
test("very basic single route with url params", {
routes: {
"/:test": req => {
return new Response(req.params.test);
},
},
});
test("very basic fetch with websocket message handler", {
fetch: () => new Response("ok"),
websocket: {
message: ws => {
expectType(ws).is<Bun.ServerWebSocket<undefined>>();
},
},
});
test("yet another basic fetch and websocket message handler", {
websocket: {
message: ws => {
expectType(ws).is<Bun.ServerWebSocket<undefined>>();
},
},
fetch: (req, server) => {
if (server.upgrade(req)) {
return;
}
return new Response("not upgraded");
},
});
test("websocket + upgrade on a route path", {
websocket: {
message: ws => {
expectType(ws).is<Bun.ServerWebSocket<undefined>>();
},
},
routes: {
"/ws": (req, server) => {
if (server.upgrade(req)) {
return;
}
return new Response("not upgraded");
},
},
});
const files = {} as Record<string, Bun.BunFile>;
test("permutations of valid route values", {
routes: {
"/this/:test": Bun.file(import.meta.file),
"/index.test-d.ts": Bun.file("index.test-d.ts"),
// @ts-expect-error this is invalid
"/index.test-d.ts.2": () => Bun.file("index.test-d.ts"),
"/ping": new Response("pong"),
"/": html,
// @ts-expect-error this is invalid, but hopefully not for too long
"/index.html": new Response(html),
...files,
},
fetch: (req, server) => {
return new Response("cool");
},
});
test("basic websocket upgrade and ws publish/subscribe to topics", {
fetch(req, server) {
server.upgrade(req);
},
websocket: {
open(ws) {
console.log("WebSocket opened");
ws.subscribe("test-channel");
},
message(ws, message) {
ws.publish("test-channel", `${message.toString()}`);
},
perMessageDeflate: true,
},
});
test(
"port with unix socket (is a type error)",
// @ts-expect-error Cannot pass unix and port
{
unix: `${tmpdirSync()}/bun.sock`,
port: 0,
fetch() {
return new Response();
},
},
{
overrideExpectBehavior: server => {
expect(server.hostname).toBeUndefined();
expect(server.port).toBeUndefined();
expect(server.url.toString()).toStartWith("unix://");
},
},
);
test(
"port with unix socket with websocket + upgrade (is a type error)",
// @ts-expect-error cannot pass unix and port at same time
{
unix: `${tmpdirSync()}/bun.sock`,
port: 0,
fetch(req, server) {
server.upgrade(req);
if (Math.random() > 0.5) return undefined;
return new Response();
},
websocket: { message: ws => expectType(ws).is<Bun.ServerWebSocket<undefined>>() },
},
{
overrideExpectBehavior: server => {
expect(server.hostname).toBeUndefined();
expect(server.port).toBeUndefined();
expect(server.url.toString()).toStartWith("unix://");
},
},
);
test("hostname: 0.0.0.0 (default - listen on all interfaces)", {
hostname: "0.0.0.0",
fetch() {
return new Response("listening on all interfaces");
},
});
test("hostname: 127.0.0.1 (localhost only)", {
hostname: "127.0.0.1",
fetch() {
return new Response("listening on localhost only");
},
});
test("hostname: localhost", {
hostname: "localhost",
fetch() {
return new Response("listening on localhost");
},
});
test(
"hostname: custom IPv4 address",
{
hostname: "192.168.1.100",
fetch() {
return new Response("custom hostname");
},
},
{
onConstructorFailure: error => {
expect(error.message).toContain("Failed to start server");
},
},
);
test("port: number type", {
port: 3000,
fetch() {
return new Response("port as number");
},
});
test("port: string type", {
port: "3001",
fetch() {
return new Response("port as string");
},
});
test("port: 0 (random port assignment)", {
port: 0,
fetch() {
return new Response("random port");
},
});
test(
"port: from environment variable",
{
port: process.env.PORT || "3002",
fetch() {
return new Response("port from env");
},
},
{
overrideExpectBehavior: server => {
expect(server.port).toBeGreaterThan(0);
expect(server.url).toBeDefined();
},
},
);
test("reusePort: false (default)", {
reusePort: false,
port: 0,
fetch() {
return new Response("reusePort false");
},
});
test("reusePort: true", {
reusePort: true,
port: 0,
fetch() {
return new Response("reusePort true");
},
});
test("ipv6Only: false (default)", {
ipv6Only: false,
port: 0,
fetch() {
return new Response("ipv6Only false");
},
});
test("idleTimeout: default (10 seconds)", {
port: 0,
fetch() {
return new Response("default idleTimeout");
},
});
test("idleTimeout: custom value (30 seconds)", {
idleTimeout: 30,
port: 0,
fetch() {
return new Response("custom idleTimeout");
},
});
test("idleTimeout: 0 (no timeout)", {
idleTimeout: 0,
port: 0,
fetch() {
return new Response("no idleTimeout");
},
});
test("maxRequestBodySize: default (128MB)", {
port: 0,
fetch() {
return new Response("default maxRequestBodySize");
},
});
test("maxRequestBodySize: custom small value", {
maxRequestBodySize: 1024 * 1024, // 1MB
port: 0,
fetch() {
return new Response("small maxRequestBodySize");
},
});
test("maxRequestBodySize: custom large value", {
maxRequestBodySize: 1024 * 1024 * 1024, // 1GB
port: 0,
fetch() {
return new Response("large maxRequestBodySize");
},
});
test("development: true", {
development: true,
port: 0,
fetch() {
return new Response("development mode on");
},
});
test("development: false", {
development: false,
port: 0,
fetch() {
return new Response("development mode off");
},
});
test("development: defaults to process.env.NODE_ENV !== 'production'", {
development: process.env.NODE_ENV !== "production",
port: 0,
fetch() {
return new Response("development from env");
},
});
test(
"error callback handles errors",
{
port: 0,
fetch() {
throw new Error("Test error");
},
error(error) {
return new Response(`Error handled: ${error.message}`, { status: 500 });
},
},
{
overrideExpectBehavior: async server => {
const res = await fetch(server.url);
expect(res.status).toBe(500);
expect(await res.text()).toBe("Error handled: Test error");
},
},
);
test(
"error callback with async handler",
{
port: 0,
fetch() {
throw new Error("Async test error");
},
async error(error) {
await new Promise(resolve => setTimeout(resolve, 10));
return new Response(`Async error handled: ${error.message}`, { status: 503 });
},
},
{
overrideExpectBehavior: async server => {
const res = await fetch(server.url);
expect(res.status).toBe(503);
expect(await res.text()).toBe("Async error handled: Async test error");
},
},
);
test("id: custom server identifier", {
id: "my-custom-server-id",
port: 0,
fetch() {
return new Response("server with custom id");
},
});
test("id: null (no identifier)", {
id: null,
port: 0,
fetch() {
return new Response("server with null id");
},
});
test("multiple properties combined", {
hostname: "127.0.0.1",
port: 0,
reusePort: true,
idleTimeout: 20,
maxRequestBodySize: 1024 * 1024 * 10, // 10MB
development: true,
id: "combined-test-server",
fetch(req) {
return Response.json({
url: req.url,
method: req.method,
});
},
error(error) {
return new Response(`Combined server error: ${error.message}`, { status: 500 });
},
});
test("#24819 regression", {
development: !process.env.production,
routes: {
"/health": {
GET: new Response("OK"),
POST: req => {
expectType(req).is<Bun.BunRequest<"/health">>();
return Response.json("Sup");
},
},
},
});
// @ts-expect-error
test("#24819 regression with no response requires websocket", {
development: !process.env.production,
routes: {
"/health": {
GET: new Response("OK"),
POST: req => {
expectType(req).is<Bun.BunRequest<"/health">>();
},
},
},
});
test("#24819 regression with websocket is happy", {
websocket: {
message: console.log,
},
development: !process.env.production,
routes: {
"/health": {
GET: new Response("OK"),
POST: req => {
expectType(req).is<Bun.BunRequest<"/health">>();
},
},
},
});
@@ -0,0 +1,86 @@
// This file is merely types only, you (probably) want to put the tests in ./serve-types.test.ts instead
import { expectType } from "./utilities";
Bun.serve({
routes: {
"/:id/:test": req => {
expectType(req.params).is<{ id: string; test: string }>();
},
},
fetch: () => new Response("hello"),
websocket: {
message(ws, message) {
expectType(ws.data).is<undefined>();
expectType(message).is<string | Buffer<ArrayBuffer>>();
},
},
});
const s1 = Bun.serve({
routes: {
"/ws/:name": req => {
expectType(req.params.name).is<string>();
s1.upgrade(req, {
data: { name: req.params.name },
});
},
},
websocket: {
data: {} as { name: string },
message(ws) {
ws.send(JSON.stringify(ws.data));
},
},
});
const s2 = Bun.serve({
routes: {
"/ws/:name": req => {
expectType(req.params.name).is<string>();
// @ts-expect-error - Should error because data was not passed
s2.upgrade(req, {});
},
},
websocket: {
data: {} as { name: string },
message(ws) {
expectType(ws.data).is<{ name: string }>();
},
},
});
const s3 = Bun.serve({
routes: {
"/ws/:name": req => {
expectType(req.params.name).is<string>();
// @ts-expect-error - Should error because data and object was not passed
s3.upgrade(req);
},
},
websocket: {
data: {} as { name: string },
message(ws) {
expectType(ws.data).is<{ name: string }>();
},
},
});
const s4 = Bun.serve({
routes: {
"/ws/:name": req => {
expectType(req.params.name).is<string>();
s4.upgrade(req);
},
},
websocket: {
message(ws) {
expectType(ws.data).is<undefined>();
},
},
});
+228
View File
@@ -0,0 +1,228 @@
import type {
FileSink,
NullSubprocess,
PipedSubprocess,
ReadableSubprocess,
SyncSubprocess,
WritableSubprocess,
} from "bun";
import * as tsd from "./utilities";
Bun.spawn(["echo", "hello"]);
function depromise<T>(_promise: Promise<T>): T {
return "asdf" as any as T;
}
{
// Test cases for https://github.com/oven-sh/bun/issues/17274
{
const proc = Bun.spawn(["cat"], {
stdin: "pipe",
});
proc.stdin.write("hello");
}
{
const proc = Bun.spawn(["cat"], {
stdin: "pipe",
onExit(proc, exitCode, signalCode, error) {
tsd.expectType(proc).is<Bun.Subprocess<"pipe", "pipe", "inherit">>();
console.log(`Process exited: ${exitCode}`);
},
});
proc.stdin.write("hello");
}
}
{
const proc = Bun.spawn(["echo", "hello"], {
cwd: "./path/to/subdir", // specify a working direcory
env: { ...process.env, FOO: "bar" }, // specify environment variables
onExit(proc, exitCode, signalCode, error) {
// exit handler
},
});
tsd.expectType(proc.pid).is<number>();
tsd.expectType(proc.stdout).is<ReadableStream<Uint8Array<ArrayBuffer>>>();
tsd.expectType(proc.stderr).is<undefined>();
tsd.expectType(proc.stdin).is<undefined>();
}
{
const proc = Bun.spawn(["cat"], {
stdin: depromise(fetch("https://raw.githubusercontent.com/oven-sh/bun/main/examples/hashing.js")),
});
const text = depromise(proc.stdout.text());
console.log(text); // "const input = "hello world".repeat(400); ..."
}
{
const proc = Bun.spawn(["cat"], {
stdio: ["pipe", "pipe", "pipe", Bun.file("build.zip")],
});
tsd.expectType(proc.stdio[0]).is<null>();
tsd.expectType(proc.stdio[1]).is<null>();
tsd.expectType(proc.stdio[2]).is<null>();
tsd.expectType(proc.stdio[3]).is<number | null | undefined>();
tsd.expectType(proc.stdin).is<FileSink>();
tsd.expectType(proc.stdout).is<ReadableStream<Uint8Array<ArrayBuffer>>>();
tsd.expectType(proc.stderr).is<ReadableStream<Uint8Array<ArrayBuffer>>>();
}
{
const proc = Bun.spawn(["cat"], {
stdin: "pipe", // return a FileSink for writing
});
// enqueue string data
proc.stdin.write("hello");
// enqueue binary data
const enc = new TextEncoder();
proc.stdin.write(enc.encode(" world!"));
enc.encodeInto(" world!", {} as any as Uint8Array);
// Bun-specific overloads
// these fail when lib.dom.d.ts is present
enc.encodeInto(" world!", new Uint32Array(124));
enc.encodeInto(" world!", {} as any as DataView);
// send buffered data
await proc.stdin.flush();
// close the input stream
await proc.stdin.end();
}
{
const proc = Bun.spawn(["echo", "hello"]);
const text = depromise(proc.stdout.text());
console.log(text); // => "hello"
}
{
const proc = Bun.spawn(["echo", "hello"], {
onExit(proc, exitCode, signalCode, error) {
// exit handler
},
});
await proc.exited; // resolves when process exit
proc.killed; // boolean — was the process killed?
proc.exitCode; // null | number
proc.signalCode; // null | "SIGABRT" | "SIGALRM" | ...
proc.kill();
proc.killed; // true
proc.kill(); // specify an exit code
proc.unref();
}
{
const proc = Bun.spawn(["echo", "hello"], {
stdio: ["pipe", "pipe", "pipe"],
});
tsd.expectType<FileSink>(proc.stdin);
tsd.expectType<ReadableStream<Uint8Array>>(proc.stdout);
tsd.expectType<ReadableStream<Uint8Array>>(proc.stderr);
}
{
const proc = Bun.spawn(["echo", "hello"], {
stdio: ["inherit", "inherit", "inherit"],
});
tsd.expectType<undefined>(proc.stdin);
tsd.expectType<undefined>(proc.stdout);
tsd.expectType<undefined>(proc.stderr);
}
{
const proc = Bun.spawn(["echo", "hello"], {
stdio: ["ignore", "ignore", "ignore"],
});
tsd.expectType<undefined>(proc.stdin);
tsd.expectType<undefined>(proc.stdout);
tsd.expectType<undefined>(proc.stderr);
}
{
const proc = Bun.spawn(["echo", "hello"], {
stdio: [null, null, null],
});
tsd.expectType(proc.stdin).is<undefined>();
tsd.expectType(proc.stdout).is<undefined>();
tsd.expectType(proc.stderr).is<undefined>();
}
{
const proc = Bun.spawn(["echo", "hello"], {
stdio: [new Request("1"), null, null],
});
tsd.expectType<number>(proc.stdin);
}
{
const proc = Bun.spawn(["echo", "hello"], {
stdio: [new Response("1"), null, null],
});
tsd.expectType<number>(proc.stdin);
}
{
const proc = Bun.spawn(["echo", "hello"], {
stdio: [new Uint8Array([]), null, null],
});
tsd.expectType<number>(proc.stdin);
}
tsd.expectAssignable<PipedSubprocess>(Bun.spawn([], { stdio: ["pipe", "pipe", "pipe"] }));
tsd.expectAssignable<ReadableSubprocess>(Bun.spawn([], { stdio: ["ignore", "pipe", "pipe"] }));
tsd.expectAssignable<ReadableSubprocess>(Bun.spawn([], { stdio: ["pipe", "pipe", "pipe"] }));
tsd.expectAssignable<WritableSubprocess>(Bun.spawn([], { stdio: ["pipe", "pipe", "pipe"] }));
tsd.expectAssignable<WritableSubprocess>(Bun.spawn([], { stdio: ["pipe", "ignore", "inherit"] }));
tsd.expectAssignable<NullSubprocess>(Bun.spawn([], { stdio: ["ignore", "inherit", "ignore"] }));
tsd.expectAssignable<NullSubprocess>(Bun.spawn([], { stdio: [null, null, null] }));
tsd.expectAssignable<SyncSubprocess<Bun.SpawnOptions.Readable, Bun.SpawnOptions.Readable>>(Bun.spawnSync([], {}));
// Lazy option types (async only)
{
// valid: lazy usable with async spawn
const p1 = Bun.spawn(["echo", "hello"], {
stdout: "pipe",
stderr: "pipe",
lazy: true,
});
tsd.expectType(p1.stdout).is<ReadableStream<Uint8Array<ArrayBuffer>>>();
}
{
// valid: lazy false is also allowed
const p2 = Bun.spawn(["echo", "hello"], {
stdout: "pipe",
stderr: "pipe",
lazy: false,
});
tsd.expectType(p2.stderr).is<ReadableStream<Uint8Array<ArrayBuffer>>>();
}
{
// invalid: lazy is not supported in spawnSync
Bun.spawnSync(["echo", "hello"], {
stdout: "pipe",
stderr: "pipe",
// @ts-expect-error lazy applies only to async spawn
lazy: true,
});
}
{
// invalid: lazy is not supported in spawnSync (object overload)
// prettier-ignore
// @ts-expect-error lazy applies to async spawn
Bun.spawnSync({ cmd: ["echo", "hello"], stdout: "pipe", stderr: "pipe", lazy: true,
});
}
+301
View File
@@ -0,0 +1,301 @@
import { sql } from "bun";
import { expectAssignable, expectType } from "./utilities";
{
const postgres = new Bun.SQL();
const id = 1;
await postgres`select * from users where id = ${id}`;
}
{
const postgres = new Bun.SQL("postgres://localhost:5432/mydb");
const id = 1;
await postgres`select * from users where id = ${id}`;
}
{
const postgres = new Bun.SQL({ url: "postgres://localhost:5432/mydb" });
const id = 1;
await postgres`select * from users where id = ${id}`;
}
{
const postgres = new Bun.SQL();
postgres("ok");
}
const sql1 = new Bun.SQL();
const sql2 = new Bun.SQL("postgres://localhost:5432/mydb");
const sql3 = new Bun.SQL(new URL("postgres://localhost:5432/mydb"));
const sql4 = new Bun.SQL({ url: "postgres://localhost:5432/mydb", idleTimeout: 1000 });
const query1 = sql1<string>`SELECT * FROM users WHERE id = ${1}`;
const query2 = sql2({ foo: "bar" });
query1.cancel().simple().execute().raw().values();
expectType(query1).extends<Promise<any>>();
expectType(query1).extends<Promise<string>>();
sql1.connect();
sql1.close();
sql1.end();
sql1.flush();
const reservedPromise: Promise<Bun.ReservedSQL> = sql1.reserve();
sql1.begin(async txn => {
txn`SELECT 1`;
await txn.savepoint("sp", async sp => {
sp`SELECT 2`;
});
});
expectType(
sql1.transaction(async txn => {
txn`SELECT 3`;
}),
).is<Promise<void>>();
expectType(
sql1.begin("read write", async txn => {
txn`SELECT 4`;
}),
).is<Promise<void>>();
expectType(
sql1.transaction("read write", async txn => {
txn`SELECT 5`;
}),
).is<Promise<void>>();
expectType(
sql1.beginDistributed("foo", async txn => {
txn`SELECT 6`;
}),
).is<Promise<void>>();
expectType(
sql1.distributed("bar", async txn => {
txn`SELECT 7`;
}),
).is<Promise<void>>();
expectType(
sql1.beginDistributed("foo", async txn => {
txn`SELECT 8`;
}),
).is<Promise<void>>();
{
const tx = await sql1.transaction(async txn => {
return [await txn<[9]>`SELECT 9`, await txn<[10]>`SELECT 10`];
});
expectType(tx).is<readonly [[9], [10]]>();
}
{
const tx = await sql1.begin(async txn => {
return [await txn<[9]>`SELECT 9`, await txn<[10]>`SELECT 10`];
});
expectType(tx).is<readonly [[9], [10]]>();
}
{
const tx = await sql1.distributed("name", async txn => {
return [await txn<[9]>`SELECT 9`, await txn<[10]>`SELECT 10`];
});
expectType(tx).is<readonly [[9], [10]]>();
}
expectType(sql1.unsafe("SELECT * FROM users")).is<Bun.SQL.Query<any>>();
expectType(sql1.unsafe<{ id: string }[]>("SELECT * FROM users")).is<Bun.SQL.Query<{ id: string }[]>>();
expectType(sql1.file("query.sql", [1, 2, 3])).is<Bun.SQL.Query<any>>();
sql1.reserve().then(reserved => {
reserved.release();
expectType(reserved<[8]>`SELECT 8`).is<Bun.SQL.Query<[8]>>();
});
sql1.begin(async txn => {
txn.savepoint("sp", async sp => {
sp`SELECT 9`;
});
});
sql1.begin(async txn => {
txn.savepoint(async sp => {
sp`SELECT 10`;
});
});
// @ts-expect-error
sql1.commitDistributed();
// @ts-expect-error
sql1.rollbackDistributed();
// @ts-expect-error
sql1.file(123);
// @ts-expect-error
sql1.unsafe(123);
// @ts-expect-error
sql1.begin("read write", 123);
// @ts-expect-error
sql1.transaction("read write", 123);
const sqlQueryAny: Bun.SQL.Query<any> = {} as any;
const sqlQueryNumber: Bun.SQL.Query<number> = {} as any;
const sqlQueryString: Bun.SQL.Query<string> = {} as any;
expectAssignable<Promise<any>>(sqlQueryAny);
expectAssignable<Promise<number>>(sqlQueryNumber);
expectAssignable<Promise<string>>(sqlQueryString);
expectType(sqlQueryNumber).is<Bun.SQL.Query<number>>();
expectType(sqlQueryString).is<Bun.SQL.Query<string>>();
expectType(sqlQueryNumber).is<Bun.SQL.Query<number>>();
const queryA = sql`SELECT 1`;
expectType(queryA).is<Bun.SQL.Query<any>>();
expectType(await queryA).is<any>();
const queryB = sql({ foo: "bar" });
expectType(queryB).is<Bun.SQL.Helper<{ foo: string }>>();
expectType(sql).is<Bun.SQL>();
const opts2 = { url: "postgres://localhost" } satisfies Bun.SQL.Options;
expectType(opts2).extends<Bun.SQL.Options>();
const txCb = (async sql => [sql<[1]>`SELECT 1`]) satisfies Bun.SQL.TransactionContextCallback<unknown>;
const spCb = (async sql => [sql<[2]>`SELECT 2`]) satisfies Bun.SQL.SavepointContextCallback<unknown>;
expectType(await sql.begin(txCb)).is<[1][]>();
expectType(await sql.begin(spCb)).is<[2][]>();
expectType(queryA.cancel()).is<Bun.SQL.Query<any>>();
expectType(queryA.simple()).is<Bun.SQL.Query<any>>();
expectType(queryA.execute()).is<Bun.SQL.Query<any>>();
expectType(queryA.raw()).is<Bun.SQL.Query<any>>();
expectType(queryA.values()).is<Bun.SQL.Query<any>>();
declare const queryNum: Bun.SQL.Query<number>;
expectType(queryNum.cancel()).is<Bun.SQL.Query<number>>();
expectType(queryNum.simple()).is<Bun.SQL.Query<number>>();
expectType(queryNum.execute()).is<Bun.SQL.Query<number>>();
expectType(queryNum.raw()).is<Bun.SQL.Query<number>>();
expectType(queryNum.values()).is<Bun.SQL.Query<number>>();
expectType(await queryNum.cancel()).is<number>();
expectType(await queryNum.simple()).is<number>();
expectType(await queryNum.execute()).is<number>();
expectType(await queryNum.raw()).is<number>();
expectType(await queryNum.values()).is<number>();
expectType<Bun.SQL.Options>({
password: () => "hey",
pass: async () => "hey",
});
expectType<Bun.SQL.Options>({
password: "hey",
});
expectType(sql({ name: "Alice", email: "[email protected]" })).is<
Bun.SQL.Helper<{
name: string;
email: string;
}>
>();
expectType(
sql([
{ name: "Alice", email: "[email protected]" },
{ name: "Bob", email: "[email protected]" },
]),
).is<
Bun.SQL.Helper<{
name: string;
email: string;
}>
>();
const userWithAge = { name: "Alice", email: "[email protected]", age: 25 };
expectType(sql(userWithAge, "name", "email")).is<
Bun.SQL.Helper<{
name: string;
email: string;
}>
>();
const users = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
];
expectType(sql(users, "id")).is<Bun.SQL.Helper<{ id: number }>>();
expectType(sql([1, 2, 3])).is<Bun.SQL.Helper<number[]>>();
expectType(sql([1, 2, 3] as const)).is<Bun.SQL.Helper<readonly [1, 2, 3]>>();
expectType(sql("users")).is<Bun.SQL.Query<any>>();
expectType(sql<1>("users")).is<Bun.SQL.Query<1>>();
declare const user: { name: "Alice"; email: "[email protected]" };
// @ts-expect-error - missing key in object
sql(user, "notAKey");
// @ts-expect-error - wrong type for key argument
sql(user, 123);
// @ts-expect-error - array of objects, missing key
sql(users, "notAKey");
// @ts-expect-error - array of numbers, extra key argument
sql([1, 2, 3], "notAKey");
// Degenerate helper inputs that throw at runtime are rejected at the type
// level too. See https://github.com/oven-sh/bun/issues/32155.
// @ts-expect-error - bare null is not a valid helper value
sql(null);
// @ts-expect-error - bare undefined is not a valid helper value
sql(undefined);
// @ts-expect-error - null item in a keyed WHERE IN helper
sql([null], "id");
// @ts-expect-error - null single object in an UPDATE helper with a column
sql(null, "name");
// @ts-expect-error - undefined item in an UPDATE/keyed helper
sql([undefined], "name");
// Still allowed: a null/primitive array binds NULL for WHERE IN.
sql([null]);
sql([1, null, 2]);
// check the deprecated stuff still exists
expectType<Bun.SQLQuery<"hey">>();
expectType<Bun.SQLTransactionContextCallback<"hey">>();
expectType<Bun.SQLSavepointContextCallback<"hey">>();
// check some types exist
expectType<Bun.SQL.AwaitPromisesArray<[]>>;
expectType<Bun.SQL.SQLiteOptions>;
expectType<Bun.SQL.PostgresOrMySQLOptions>;
expectType<Bun.SQL.ContextCallbackResult<unknown>>;
declare const aSqlInstance: Bun.SQL;
expectType(aSqlInstance.options.host).is<string | undefined>(); // property exists in postgres/mysql/mariadb options
expectType(aSqlInstance.options.safeIntegers).is<boolean | undefined>(); // property exits in sqlite options
@@ -0,0 +1,52 @@
import { type Changes, Database, constants } from "bun:sqlite";
import { expectType } from "./utilities";
expectType(constants.SQLITE_FCNTL_BEGIN_ATOMIC_WRITE).is<number>();
expectType<Record<string, number>>(constants);
const db = new Database(":memory:");
const query1 = db.query<
{ name: string; dob: number }, // return type first
{ $id: string }
>("select name, dob from users where id = $id");
query1.all({ $id: "asdf" }); // => {name: string; dob:string}[]
const query2 = db.query<
{ name: string; dob: number },
[string, number] // pass tuple for positional params
>("select ?1 as name, ?2 as dob");
const allResults = query2.all("Shaq", 50); // => {name: string; dob:string}[]
const getResults = query2.get("Shaq", 50); // => {name: string; dob:string}[]
// tslint:disable-next-line:no-void-expression
const runResults = query2.run("Shaq", 50); // => {name: string; dob:string}[]
expectType<Array<{ name: string; dob: number }>>(allResults);
expectType<{ name: string; dob: number } | null>(getResults);
// tslint:disable-next-line:invalid-void
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type
expectType<Changes>(runResults);
const query3 = db.prepare<
{ name: string; dob: number }, // return type first
// eslint-disable-next-line @definitelytyped/no-single-element-tuple-type
[{ $id: string }]
>("select name, dob from users where id = $id");
const allResults3 = query3.all({ $id: "asdf" });
expectType<Array<{ name: string; dob: number }>>(allResults3);
db.exec("CREATE TABLE cats (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE, age INTEGER)");
const insert = db.prepare("INSERT INTO cats (name, age) VALUES ($name, $age)");
const insertManyCats = db.transaction((cats: Array<{ $name: string; $age: number }>) => {
for (const cat of cats) insert.run(cat);
});
insertManyCats([
{
$name: "Joey",
$age: 2,
},
{ $name: "Sally", $age: 4 },
{ $name: "Junior", $age: 1 },
// @ts-expect-error - Should fail
{ fail: true },
]);
@@ -0,0 +1,3 @@
process.stdin;
process.stdout;
process.stderr;
@@ -0,0 +1,87 @@
import { expectType } from "./utilities";
new ReadableStream<string>({
start(controller) {
controller.enqueue("hello");
controller.enqueue("world");
// @ts-expect-error
controller.enqueue(2);
controller.close();
},
});
// This will have type errors when lib.dom.d.ts is present
// Not fixable because ReadableStream has no ReadableStreamConstructor interface
// we can merge into. See https://github.com/microsoft/TypeScript-DOM-lib-generator/pull/1941
// for details about when/why/how TypeScript might support this.
new ReadableStream({
type: "direct",
async pull(controller) {
controller.write(new TextEncoder().encode("Hello, world!"));
},
});
declare const uint8stream: ReadableStream<Uint8Array<ArrayBuffer>>;
for await (const chunk of uint8stream) {
expectType(chunk).is<Uint8Array<ArrayBuffer>>();
}
declare const uint8Array: Uint8Array<ArrayBuffer>;
expectType(uint8Array).is<Uint8Array<ArrayBuffer>>();
declare const uint8Writable: WritableStream<Uint8Array<ArrayBuffer>>;
declare const uint8Transform: TransformStream<Uint8Array<ArrayBuffer>, Uint8Array<ArrayBuffer>>;
const writer = uint8Writable.getWriter();
await writer.write(uint8Array);
await writer.close();
for await (const chunk of uint8Transform.readable) {
expectType(chunk).is<Uint8Array<ArrayBuffer>>();
}
declare const stream: ReadableStream<Uint8Array>;
expectType(stream.json()).is<Promise<any>>();
expectType(stream.bytes()).is<Promise<Uint8Array<ArrayBuffer>>>();
expectType(stream.text()).is<Promise<string>>();
expectType(stream.blob()).is<Promise<Blob>>();
import { ReadableStream as NodeStreamReadableStream } from "node:stream/web";
declare const node_stream: NodeStreamReadableStream<Uint8Array>;
expectType(node_stream.json()).is<Promise<any>>();
expectType(node_stream.bytes()).is<Promise<Uint8Array<ArrayBuffer>>>();
expectType(node_stream.text()).is<Promise<string>>();
expectType(node_stream.blob()).is<Promise<Blob>>();
Bun.file("./foo.csv").stream().pipeThrough(new TextDecoderStream()).pipeThrough(new TextEncoderStream());
Bun.file("./foo.csv").stream().pipeThrough(new CompressionStream("gzip")).pipeThrough(new DecompressionStream("gzip"));
Bun.file("./foo.csv").stream().pipeThrough(new CompressionStream("brotli")).pipeThrough(new DecompressionStream("brotli"));
Bun.file("./foo.csv").stream().pipeThrough(new CompressionStream("zstd")).pipeThrough(new DecompressionStream("zstd"));
Bun.file("./foo.csv")
.stream()
.pipeThrough(new TextDecoderStream())
.pipeTo(
new WritableStream({
write(chunk) {
expectType(chunk).is<string>();
},
}),
);
// @ts-expect-error These properties do not exist right now
expectType(new ReadableStream().arrayBuffer());
// @ts-expect-error These properties do not exist right now
expectType(new ReadableStream().formData());
expectType(new Blob([]).text()).is<Promise<string>>();
expectType(new Blob([]).arrayBuffer()).is<Promise<ArrayBuffer>>();
expectType(new Blob([]).bytes()).is<Promise<Uint8Array<ArrayBuffer>>>();
expectType(new Blob([]).json()).is<Promise<any>>();
expectType(new Blob([]).formData()).is<Promise<FormData>>();
expectType(new Blob([]).stream()).is<ReadableStream<Uint8Array<ArrayBuffer>>>();
+167
View File
@@ -0,0 +1,167 @@
import * as Bun from "bun";
await Bun.connect({
data: { arg: "asdf" },
socket: {
data(socket) {
socket.data.arg.toLocaleLowerCase();
},
open() {
console.log("asdf");
},
},
hostname: "adsf",
port: 324,
});
await Bun.connect({
data: { arg: "asdf" },
socket: {
data(socket) {
socket.data.arg.toLowerCase();
},
open() {
console.log("asdf");
},
},
hostname: "adsf",
port: 324,
});
await Bun.connect({
data: { arg: "asdf" },
socket: {
data(socket) {
socket.data.arg.toLowerCase();
},
open() {
console.log("asdf");
},
},
unix: "asdf",
});
await Bun.connect({
data: { arg: "asdf" },
socket: {
data(socket) {
socket.data.arg.toLowerCase();
},
open() {
console.log("asdf");
},
},
unix: "asdf",
});
Bun.listen({
data: { arg: "asdf" },
socket: {
data(socket) {
socket.data.arg.toLowerCase();
},
open() {
console.log("asdf");
},
},
hostname: "adsf",
port: 324,
});
Bun.listen({
data: { arg: "asdf" },
socket: {
data(socket) {
socket.data.arg.toLowerCase();
},
open() {
console.log("asdf");
},
},
hostname: "adsf",
port: 324,
tls: {
certFile: "asdf",
keyFile: "adsf",
},
});
Bun.listen({
data: { arg: "asdf" },
socket: {
data(socket) {
socket.data.arg.toLowerCase();
},
open() {
console.log("asdf");
},
},
hostname: "adsf",
port: 324,
tls: {
cert: "asdf",
key: Bun.file("adsf"),
ca: Buffer.from("asdf"),
},
});
Bun.listen({
data: { arg: "asdf" },
socket: {
data(socket) {
socket.data.arg.toLowerCase();
},
open() {
console.log("asdf");
},
},
unix: "asdf",
});
const listener = Bun.listen({
data: { arg: "asdf" },
socket: {
data(socket) {
socket.data.arg.toLowerCase();
},
open() {
console.log("asdf");
},
},
unix: "asdf",
});
listener.data.arg = "asdf";
// @ts-expect-error arg is string
listener.data.arg = 234;
// listener.reload({
// data: {arg: 'asdf'},
// });
listener.reload({
socket: {
open() {},
// ...listener.
},
});
// Test Socket.reload() type signature (issue #26290)
// The socket instance's reload() method should also accept { socket: handler }
await Bun.connect({
data: { arg: "asdf" },
socket: {
open(socket) {
// Socket.reload() should accept { socket: handler }, not handler directly
socket.reload({
socket: {
open() {},
data() {},
},
});
},
data() {},
},
hostname: "localhost",
port: 1,
});
+423
View File
@@ -0,0 +1,423 @@
import {
afterAll,
afterEach,
beforeAll,
beforeEach,
describe,
expect,
expectTypeOf,
jest,
type Matchers,
mock,
type Mock,
spyOn,
test,
xdescribe,
xit,
xtest,
} from "bun:test";
import { expectType } from "./utilities";
const hooks = [beforeAll, beforeEach, afterAll, afterEach];
for (const hook of hooks) {
hook(() => {
// ...
});
// eslint-disable-next-line
hook(async () => {
// ...
return;
});
hook((done: (err?: unknown) => void) => {
done();
done(new Error());
done("Error");
});
}
describe("bun:test", () => {
describe("expect()", () => {
test("toThrow()", () => {
function fail() {
throw new Error("Bad");
}
expect(fail).toThrow();
expect(fail).toThrow("Bad");
expect(fail).toThrow(/bad/i);
expect(fail).toThrow(Error);
expect(fail).toThrow(new Error("Bad"));
});
});
test("expect()", () => {
expect(1).toBe(1);
expect(1).not.toBe(2);
// @ts-expect-error
expect({ a: 1 }).toEqual<{ a: number }>({ a: 1, b: undefined });
// @ts-expect-error
expect({ a: 1 }).toEqual<{ a: number; b: number }>({ a: 1, b: undefined });
// Support passing a type parameter to force exact type matching
expect({ a: 1 }).toEqual<{ a: number; b: number }>({ a: 1, b: 1 });
expect({ a: 1 }).toStrictEqual({ a: 1 });
expect(new Set()).toHaveProperty("size");
expect(new Uint8Array()).toHaveProperty("byteLength", 0);
expect([]).toHaveLength(0);
expect(["bun"]).toContain("bun");
expect("hello").toContain("bun");
expect(true).toBeTruthy();
expect(false).toBeFalsy();
expect(Math.PI).toBeGreaterThan(3.14);
expect(Math.PI).toBeGreaterThan(3n);
expect(Math.PI).toBeGreaterThanOrEqual(3.14);
expect(Math.PI).toBeGreaterThanOrEqual(3n);
expect(NaN).toBeNaN();
expect(null).toBeNull();
expect(undefined).toBeUndefined();
expect(undefined).not.toBeDefined();
});
});
test.each([1, 2, 3])("test.each", a => {
expectType<1 | 2 | 3>(a);
});
// inference should work when data is passed directly in
test.each([
["a", true, 5],
["b", false, 1234],
])("test.each", (a, b, c) => {
expectType<string>(a);
expectType<boolean>(b);
expectType<number | string>(c);
});
describe.each([
["a", true, 5],
["b", false, 5],
])("test.each", (a, b, c) => {
expectType<string>(a);
expectType<boolean>(b);
expectType<number | string>(c);
});
describe.each([
["a", true, 5],
["b", false, "asdf"],
])("test.each", (a, b, c) => {
expectType<string>(a);
expectType<boolean>(b);
expectType<number | string>(c);
});
// @ts-expect-error
describe.each([{ asdf: "asdf" }, { asdf: "asdf" }])("test.each", (a, b, c) => {
// this test was wrong because this describe.each call will only have one argument, not three.
// it is now marked with ts-expect-error and the fixed test is below.
});
describe.each([{ asdf: "asdf" }, { asdf: "asdf" }])("test.each", a => {
expectType<{ asdf: string }>(a);
});
test.each([{ asdf: "asdf" }, { asdf: "asdf" }])("test.each", (a, done) => {
expectType<{ asdf: string }>(a);
expectType<(err?: unknown) => void>(done);
});
// no inference on data
const data = [
["a", true, 5],
["b", false, "asdf"],
];
test.each(data)("test.each", (a, b, c) => {
expectType<string | number | boolean | ((err?: unknown) => void)>(a);
expectType<string | number | boolean | ((err?: unknown) => void)>(b);
expectType<string | number | boolean | ((err?: unknown) => void)>(c);
});
describe.each(data)("test.each", (a, b, c) => {
expectType<string | number | boolean>(a);
expectType<string | number | boolean>(b);
expectType<string | number | boolean>(c);
});
// as const
const dataAsConst = [
["a", true, 5],
["b", false, "asdf"],
] as const;
test.each(dataAsConst)("test.each", (...args) => {
expectType<string>(args[0]);
expectType<boolean>(args[1]);
expectType<string | number>(args[2]);
});
describe.each(dataAsConst)("test.each", (...args) => {
expectType<string>(args[0]);
expectType<boolean>(args[1]);
expectType<string | number>(args[2]);
});
describe.each(dataAsConst)("test.each", (a, b, c) => {
expectType<"a" | "b">(a);
expectType<boolean>(b);
expectType<5 | "asdf">(c);
});
expect().pass();
expect().fail();
expectType(expect()).is<import("bun:test").Matchers<undefined>>();
expectType(expect<string>()).is<import("bun:test").Matchers<string | undefined>>();
expectType(expect("")).is<import("bun:test").Matchers<string>>();
expectType(expect<string>("")).is<import("bun:test").Matchers<string>>();
expectType(expect(undefined, "Fail message")).is<import("bun:test").Matchers<undefined>>();
expectType(expect<string>(undefined, "Fail message")).is<import("bun:test").Matchers<string | undefined>>();
expectType(expect("", "Fail message")).is<import("bun:test").Matchers<string>>();
expectType(expect<string>("", "Fail message")).is<import("bun:test").Matchers<string>>();
describe("Matcher Overload Type Tests", () => {
const num = 1;
const str = "hello";
const numArr = [1, 2, 3];
const strArr = ["a", "b", "c"];
const mixedArr = [1, "a", true];
const obj = { a: 1, b: "world", 10: true };
const numSet = new Set([10, 20]);
test("toBe", () => {
expect(num).toBe(1);
expect(str).toBe("hello");
// @ts-expect-error - Type 'string' is not assignable to type 'number'.
expect(num).toBe<number>("1");
// @ts-expect-error - Type 'number' is not assignable to type 'string'.
expect(str).toBe<string>(123);
// @ts-expect-error - Type 'boolean' is not assignable to type 'number'.
expect(num).toBe<number>(true);
// @ts-expect-error - Too many arguments for specific overload
expect(num).toBe<number>(1, 2);
// @ts-expect-error - Expecting number, passed function
expect(num).toBe<number>(() => {});
});
test("toEqual", () => {
expect(numArr).toEqual([1, 2, 3]);
expect(obj).toEqual({ a: 1, b: "world", 10: true });
// @ts-expect-error - Type 'string' is not assignable to type 'number' at index 0.
expect(numArr).toEqual<number[]>(["1", 2, 3]);
// @ts-expect-error - Property 'c' is missing in type '{ a: number; b: string; 10: boolean; }'.
expect(obj).toEqual<typeof obj>({ a: 1, b: "world", c: false });
// @ts-expect-error - Type 'boolean' is not assignable to type 'number[]'.
expect(numArr).toEqual<number[]>(true);
// @ts-expect-error - Too many arguments for specific overload
expect(numArr).toEqual<number[]>([1, 2], [3]);
// @ts-expect-error - Expecting object, passed number
expect(obj).toEqual<object>(123);
});
test("toStrictEqual", () => {
expect(numArr).toStrictEqual([1, 2, 3]);
expect(obj).toStrictEqual({ a: 1, b: "world", 10: true });
// @ts-expect-error - Type 'string' is not assignable to type 'number' at index 0.
expect(numArr).toStrictEqual<number[]>(["1", 2, 3]);
// @ts-expect-error - Properties are missing
expect(obj).toStrictEqual<typeof obj>({ a: 1 });
// @ts-expect-error - Type 'boolean' is not assignable to type 'number[]'.
expect(numArr).toStrictEqual<number[]>(true);
// @ts-expect-error - Too many arguments for specific overload
expect(numArr).toStrictEqual<number[]>([1, 2], [3]);
// @ts-expect-error - Expecting object, passed number
expect(obj).toStrictEqual<object>(123);
});
test("toBeOneOf", () => {
expect(num).toBeOneOf([1, 2, 3]);
expect(str).toBeOneOf(strArr);
expect(num).toBeOneOf(numSet);
// @ts-expect-error - Argument of type 'number[]' is not assignable to parameter of type 'Iterable<string>'.
expect(str).toBeOneOf<Iterable<string>>(numArr);
// @ts-expect-error - Argument of type 'string[]' is not assignable to parameter of type 'Iterable<number>'.
expect(num).toBeOneOf<Iterable<number>>(strArr);
// @ts-expect-error - Argument of type 'Set<number>' is not assignable to parameter of type 'Iterable<string>'.
expect(str).toBeOneOf<Iterable<string>>(numSet);
// @ts-expect-error - Argument must be iterable
expect(num).toBeOneOf<number>(1);
// @ts-expect-error - Expecting string iterable, passed number iterable
expect(str).toBeOneOf<Iterable<string>>([1, 2, 3]);
});
test("toContainKey", () => {
expect(obj).toContainKey("a");
expect(obj).toContainKey("b");
// @ts-expect-error simple check for key does not exist
expect(obj).toContainKey("c");
expect(obj).toContainKey(10); // object key is number
// @ts-expect-error - Argument of type '"c"' is not assignable to parameter of type 'number | "a" | "b"'.
expect(obj).toContainKey<typeof obj>("c");
// @ts-expect-error - Argument of type 'boolean' is not assignable to parameter of type 'string | number'.
expect(obj).toContainKey<typeof obj>(true);
// @ts-expect-error - Too many arguments for specific overload
expect(obj).toContainKey<typeof obj>("a", "b");
// @ts-expect-error - Argument of type 'symbol' is not assignable to parameter of type 'string | number'.
expect(obj).toContainKey<typeof obj>(Symbol("a"));
});
test("toContainAllKeys", () => {
expect(obj).toContainAllKeys(["a", "b"]);
expect(obj).toContainAllKeys([10, "a"]);
// @ts-expect-error simple check for key does not exist
expect(obj).toContainAllKeys(["c"]);
// @ts-expect-error - Type '"c"' is not assignable to type 'number | "a" | "b"'.
expect(obj).toContainAllKeys<(typeof obj)[]>(["a", "c"]);
// @ts-expect-error - Type 'boolean' is not assignable to type 'string | number'.
expect(obj).toContainAllKeys<(typeof obj)[]>(["a", true]);
// @ts-expect-error - Argument must be an array
expect(obj).toContainAllKeys<Array<typeof obj>>("a");
// @ts-expect-error - Array element type 'symbol' is not assignable to 'string | number'.
expect(obj).toContainAllKeys<(typeof obj)[]>(["a", Symbol("b")]);
});
test("toContainAnyKeys", () => {
expect(obj).toContainAnyKeys(["a", "b", 10]);
// @ts-expect-error simple check for key does not exist
expect(obj).toContainAnyKeys(["c"]);
// @ts-expect-error - 11 is not a key
expect(obj).toContainAnyKeys(["a", "b", 11]);
// @ts-expect-error - c is not a key
expect(obj).toContainAnyKeys(["a", "c"]); // c doesn't exist, but 'a' does
// @ts-expect-error d is not a key
expect(obj).toContainAnyKeys([10, "d"]);
// @ts-expect-error - Type '"c"' is not assignable to type 'number | "a" | "b"'. Type '"d"' is not assignable to type 'number | "a" | "b"'.
expect(obj).toContainAnyKeys<(typeof obj)[]>(["c", "d"]);
// @ts-expect-error - Type 'boolean' is not assignable to type 'string | number'.
expect(obj).toContainAnyKeys<(typeof obj)[]>([true, false]);
// @ts-expect-error - Argument must be an array
expect(obj).toContainAnyKeys<Array<typeof obj>>("a");
// @ts-expect-error - Array element type 'symbol' is not assignable to 'string | number'.
expect(obj).toContainAnyKeys<(typeof obj)[]>([Symbol("a")]);
});
test("toContainKeys", () => {
// Alias for toContainAllKeys
expect(obj).toContainKeys(["a", "b"]);
expect(obj).toContainKeys([10, "a"]);
// @ts-expect-error simple check for key does not exist
expect(obj).toContainKeys(["c"]);
// @ts-expect-error - Type '"c"' is not assignable to type 'number | "a" | "b"'.
expect(obj).toContainKeys<(typeof obj)[]>(["a", "c"]);
// @ts-expect-error - Type 'boolean' is not assignable to type 'string | number'.
expect(obj).toContainKeys<(typeof obj)[]>(["a", true]);
// @ts-expect-error - Argument must be an array
expect(obj).toContainKeys<Array<typeof obj>>("a");
// @ts-expect-error - Array element type 'symbol' is not assignable to 'string | number'.
expect(obj).toContainKeys<(typeof obj)[]>(["a", Symbol("b")]);
});
test("toContainEqual", () => {
expect(mixedArr).toContainEqual(1);
expect(mixedArr).toContainEqual("a");
expect(mixedArr).toContainEqual(true);
// @ts-expect-error - Argument of type 'null' is not assignable to parameter of type 'string | number | boolean'.
expect(mixedArr).toContainEqual<string | number | boolean>(null);
// @ts-expect-error - Argument of type 'number[]' is not assignable to parameter of type 'string | number | boolean'.
expect(mixedArr).toContainEqual<string | number | boolean>(numArr);
// @ts-expect-error - Too many arguments for specific overload
expect(mixedArr).toContainEqual<string | number | boolean>(1, 2);
// @ts-expect-error - Expecting string | number | boolean, got object
expect(mixedArr).toContainEqual<string | number | boolean>({ a: 1 });
});
});
const mySpyOnObjectWithOptionalMethod: {
optionalMethod?: (input: { question: string }) => { answer: string };
} = {
optionalMethod: input => ({ answer: `Aswer to ${input.question}` }),
};
const mySpiedMethodOfOptional = spyOn(mySpyOnObjectWithOptionalMethod, "optionalMethod");
mySpiedMethodOfOptional({ question: "asdf" });
expectType<Mock<(input: { question: string }) => { answer: string }>>(mySpiedMethodOfOptional);
const myNormalSpyOnObject = {
normalMethod: (name: string) => `Hello ${name}`,
};
const myNormalSpiedMethod = spyOn(myNormalSpyOnObject, "normalMethod");
myNormalSpiedMethod("asdf");
expectType<Mock<(name: string) => string>>(myNormalSpiedMethod);
const spy = spyOn(console, "log");
expectType(spy.mock.calls).is<any[][]>();
jest.spyOn(console, "log");
jest.fn(() => 123 as const);
xtest("", () => {});
xdescribe("", () => {});
xit("", () => {});
test("expectTypeOf basic type checks", () => {
expectTypeOf({ name: "test" }).toMatchObjectType<{ name: string }>();
// @ts-expect-error
expectTypeOf({ name: 123 }).toMatchObjectType<{ name: string }>();
});
mock.clearAllMocks();
test
.each([
[1, 2, 3],
[4, 5, 6],
])
.todo("test.each", (a, b, c, done) => {
expectType<number>(a);
expectType<number>(b);
expectType<number>(c);
expectType<(err?: unknown) => void>(done);
});
describe.each([
[1, 2, 3],
[4, 5, 6],
])("describe.each", (a, b, c) => {
expectType<number>(a);
expectType<number>(b);
expectType<number>(c);
});
declare let mylist: number[];
describe.each(mylist)("describe.each", a => {
expectTypeOf(a).toBeNumber();
});
test.each(mylist)("test.each", (a, done) => {
expectTypeOf(a).toBeNumber();
expectType<(err?: unknown) => void>(done);
});
// Advanced use case tests for #18511:
// 1. => When assignable to, we should pass (e.g. new Set() is assignable to Set<string>).
// But when unassigbale, we should type error (e.g `string` is not assignable to `"bun"`)
// 2. => Expect that exact matches pass
// 3. => Expect that when we opt out of type safety, any value can be passed
declare const input: "bun" | "baz" | null;
declare const expected: string;
// @ts-expect-error
/** 1. **/ expect(input).toBe(expected); // Type error - string is not assignable to `'bun' | ...`
/** 2. **/ expect(input).toBe("bun"); // happy!
/** 3. **/ expect(input).toBe<string>(expected); // happy! We opted out of type safety for this expectation
declare const setOfStrings: Set<string>;
/** 1. **/ expect(setOfStrings).toBe(new Set()); // this is inferrable to Set<string> so this should pass
/** 2. **/ expect(setOfStrings).toBe(new Set<string>()); // exact, so we are happy!
/** 3. **/ expect(setOfStrings).toBe<Set<string>>(new Set()); // happy! We opted out of type safety for this expectation
// Cases for #24591
declare const unknownMatchers: Matchers<unknown>;
unknownMatchers.toContainKeys(["a", "b"]);
unknownMatchers.toContainAnyKeys(["a", "b"]);
unknownMatchers.toContainAllKeys(["a", "b"]);
unknownMatchers.toContainKey("a");
unknownMatchers.toContainEqual([""]);
unknownMatchers.toEqual(["a", "b"]);
unknownMatchers.toBeCloseTo(2);
unknownMatchers.toBe("a");
@@ -0,0 +1,220 @@
// This test is currently failing, but it's not so important that it blocks a release
// type utf_8 = "unicode-1-1-utf-8" | "utf-8" | "utf8";
// type ibm866 = "866" | "cp866" | "csibm866" | "ibm866";
// type iso_8859_2 =
// | "csisolatin2"
// | "iso-8859-2"
// | "iso-ir-101"
// | "iso8859-2"
// | "iso88592"
// | "iso_8859-2"
// | "iso_8859-2:1987"
// | "l2"
// | "latin2";
// type iso_8859_3 =
// | "csisolatin3"
// | "iso-8859-3"
// | "iso-ir-109"
// | "iso8859-3"
// | "iso88593"
// | "iso_8859-3"
// | "iso_8859-3:1988"
// | "l3"
// | "latin3";
// type iso_8859_4 =
// | "csisolatin4"
// | "iso-8859-4"
// | "iso-ir-110"
// | "iso8859-4"
// | "iso88594"
// | "iso_8859-4"
// | "iso_8859-4:1988"
// | "l4"
// | "latin4";
// type iso_8859_5 =
// | "csisolatincyrillic"
// | "cyrillic"
// | "iso-8859-5"
// | "iso-ir-144"
// | "iso88595"
// | "iso_8859-5"
// | "iso_8859-5:1988";
// type iso_8859_6 =
// | "arabic"
// | "asmo-708"
// | "csiso88596e"
// | "csiso88596i"
// | "csisolatinarabic"
// | "ecma-114"
// | "iso-8859-6"
// | "iso-8859-6-e"
// | "iso-8859-6-i"
// | "iso-ir-127"
// | "iso8859-6"
// | "iso88596"
// | "iso_8859-6"
// | "iso_8859-6:1987";
// type iso_8859_7 =
// | "csisolatingreek"
// | "ecma-118"
// | "elot_928"
// | "greek"
// | "greek8"
// | "iso-8859-7"
// | "iso-ir-126"
// | "iso8859-7"
// | "iso88597"
// | "iso_8859-7"
// | "iso_8859-7:1987"
// | "sun_eu_greek";
// type iso_8859_8 =
// | "csiso88598e"
// | "csisolatinhebrew"
// | "hebrew"
// | "iso-8859-8"
// | "iso-8859-8-e"
// | "iso-ir-138"
// | "iso8859-8"
// | "iso88598"
// | "iso_8859-8"
// | "iso_8859-8:1988"
// | "visual";
// type iso_8859_8i = "csiso88598i" | "iso-8859-8-i" | "logical";
// type iso_8859_10 = "csisolatin6" | "iso-8859-10" | "iso-ir-157" | "iso8859-10" | "iso885910" | "l6" | "latin6";
// type iso_8859_13 = "iso-8859-13" | "iso8859-13" | "iso885913";
// type iso_8859_14 = "iso-8859-14" | "iso8859-14" | "iso885914";
// type iso_8859_15 = "csisolatin9" | "iso-8859-15" | "iso8859-15" | "iso885915" | "l9" | "latin9";
// type iso_8859_16 = "iso-8859-16";
// type koi8_r = "cskoi8r" | "koi" | "koi8" | "koi8-r" | "koi8_r";
// type koi8_u = "koi8-u";
// type macintosh = "csmacintosh" | "mac" | "macintosh" | "x-mac-roman";
// type windows_874 = "dos-874" | "iso-8859-11" | "iso8859-11" | "iso885911" | "tis-620" | "windows-874";
// type windows_1250 = "cp1250" | "windows-1250" | "x-cp1250";
// type windows_1251 = "cp1251" | "windows-1251" | "x-cp1251";
// type windows_1252 =
// | "ansi_x3.4-1968"
// | "ascii"
// | "cp1252"
// | "cp819"
// | "csisolatin1"
// | "ibm819"
// | "iso-8859-1"
// | "iso-ir-100"
// | "iso8859-1"
// | "iso88591"
// | "iso_8859-1"
// | "iso_8859-1:1987"
// | "l1"
// | "latin1"
// | "us-ascii"
// | "windows-1252"
// | "x-cp1252";
// type windows_1253 = "cp1253" | "windows-1253" | "x-cp1253";
// type windows_1254 =
// | "cp1254"
// | "csisolatin5"
// | "iso-8859-9"
// | "iso-ir-148"
// | "iso8859-9"
// | "iso88599"
// | "iso_8859-9"
// | "iso_8859-9:1989"
// | "l5"
// | "latin5"
// | "windows-1254"
// | "x-cp1254";
// type windows_1255 = "cp1255" | "windows-1255" | "x-cp1255";
// type windows_1256 = "cp1256" | "windows-1256" | "x-cp1256";
// type windows_1257 = "cp1257" | "windows-1257" | "x-cp1257";
// type windows_1258 = "cp1258" | "windows-1258" | "x-cp1258";
// type x_mac_cyrillic = "x-mac-cyrillic" | "x-mac-ukrainian";
// type gbk =
// | "chinese"
// | "csgb2312"
// | "csiso58gb231280"
// | "gb2312"
// | "gb_2312"
// | "gb_2312-80"
// | "gbk"
// | "iso-ir-58"
// | "x-gbk";
// type gb18030 = "gb18030";
// type hz_gb_2312 = "hz-gb-2312";
// type big5 = "big5" | "big5-hkscs" | "cn-big5" | "csbig5" | "x-x-big5";
// type euc_jp = "cseucpkdfmtjapanese" | "euc-jp" | "x-euc-jp";
// type iso_2022_jp = "csiso2022jp" | "iso-2022-jp";
// type shift_jis = "csshiftjis" | "ms_kanji" | "shift-jis" | "shift_jis" | "sjis" | "windows-31j" | "x-sjis";
// type euc_kr =
// | "cseuckr"
// | "csksc56011987"
// | "euc-kr"
// | "iso-ir-149"
// | "korean"
// | "ks_c_5601-1987"
// | "ks_c_5601-1989"
// | "ksc5601"
// | "ksc_5601"
// | "windows-949";
// type iso_2022_kr = "csiso2022kr" | "iso-2022-kr";
// type utf_16be = "utf-16be";
// type utf_16le = "utf-16" | "utf-16le";
// type x_user_defined = "x-user-defined";
// type replacement = "iso-2022-cn" | "iso-2022-cn-ext";
// type TextEncoding =
// | utf_8
// | ibm866
// | iso_8859_2
// | iso_8859_3
// | iso_8859_4
// | iso_8859_5
// | iso_8859_6
// | iso_8859_7
// | iso_8859_8
// | iso_8859_8i
// | iso_8859_10
// | iso_8859_13
// | iso_8859_14
// | iso_8859_15
// | iso_8859_16
// | koi8_r
// | koi8_u
// | macintosh
// | windows_874
// | windows_1250
// | windows_1251
// | windows_1252
// | windows_1253
// | windows_1254
// | windows_1255
// | windows_1256
// | windows_1257
// | windows_1258
// | x_mac_cyrillic
// | gbk
// | gb18030
// | hz_gb_2312
// | big5
// | euc_jp
// | iso_2022_jp
// | shift_jis
// | euc_kr
// | iso_2022_kr
// | utf_16be
// | utf_16le
// | x_user_defined
// | replacement;
// export type TextDecoderOptions = ConstructorParameters<typeof TextDecoder>[1];
// function decode(encoding: TextEncoding, array: ArrayBufferView | ArrayBuffer, options?: TextDecoderOptions): string {
// const decoder = new TextDecoder(encoding, options);
// return decoder.decode(array);
// }
// decode("utf-8", new Uint8Array([0x41, 0x42, 0x43]), {
// fatal: true,
// });
export {};
+32
View File
@@ -0,0 +1,32 @@
import tls from "node:tls";
tls.getCiphers()[0];
tls.connect({
host: "localhost",
port: 80,
ca: "asdf",
cert: "path to cert",
});
tls.connect({
host: "localhost",
port: 80,
ca: Bun.file("asdf"),
cert: Bun.file("path to cert"),
ciphers: "adsf",
});
tls.connect({
host: "localhost",
port: 80,
ca: Buffer.from("asdf"),
cert: Buffer.from("asdf"),
});
tls.connect({
host: "localhost",
port: 80,
ca: new Uint8Array([1, 2, 3]),
cert: new Uint8Array([1, 2, 3]),
});
@@ -0,0 +1,10 @@
import { TOML } from "bun";
import data from "./bunfig.toml";
import { expectType } from "./utilities";
expectType<any>(data);
expectType(Bun.TOML.parse(data)).is<object>();
expectType(TOML.parse(data)).is<object>();
// `undefined` when the input is `undefined`, a function, or a symbol.
expectType(Bun.TOML.stringify({ abc: "def" })).is<string | undefined>();
expectType(TOML.stringify({ abc: "def" })).is<string | undefined>();
+49
View File
@@ -0,0 +1,49 @@
import * as tty from "tty";
const rs = new tty.ReadStream(234, {
allowHalfOpen: true,
readable: true,
signal: new AbortSignal(),
writable: true,
});
const ws = new tty.WriteStream(234);
process.stdin.setRawMode(true);
process.stdin.setRawMode(false);
process.stdin.isRaw;
process.stdin.setRawMode(true).isRaw;
rs.isRaw;
rs.setRawMode(true);
rs.setRawMode(false);
rs.setRawMode(true).isRaw;
rs.isTTY;
ws.isPaused;
ws.isTTY;
ws.bytesWritten;
ws.bytesRead;
ws.columns;
ws.rows;
ws.isTTY;
ws.clearLine(1);
ws.clearLine(0);
ws.clearScreenDown();
ws.cursorTo(1);
ws.cursorTo(1, 2);
ws.cursorTo(1, () => {});
ws.cursorTo(1, 2, () => {});
ws.moveCursor(1, 2);
ws.moveCursor(1, 2, () => {});
ws.clearLine(1, () => {});
ws.clearLine(0, () => {});
ws.clearScreenDown(() => {});
ws.cursorTo(1, () => {});
process.stdout.clearLine;
process.stdout.clearScreenDown;
process.stdout.cursorTo;
process.stdout.moveCursor;
process.stdout.getColorDepth;
process.stdout.getWindowSize;
+58
View File
@@ -0,0 +1,58 @@
import * as Bun from "bun";
import { expectType } from "./utilities";
const socket = await Bun.udpSocket({
port: 0,
});
expectType(socket.hostname).is<string>();
expectType(socket.port).is<number>();
expectType(socket.address).is<Bun.SocketAddress>();
expectType(socket.binaryType).is<Bun.BinaryType>();
expectType(socket.closed).is<boolean>();
expectType(socket.send("Hello", 41234, "127.0.0.1")).is<boolean>();
expectType(socket.send(new Uint8Array([1, 2, 3]), 41234, "127.0.0.1")).is<boolean>();
expectType(socket.sendMany(["Hello", 41234, "127.0.0.1", "World", 41235, "127.0.0.2"])).is<number>();
expectType(socket.setBroadcast(true)).is<boolean>();
expectType(socket.setTTL(64)).is<number>();
expectType(socket.setMulticastTTL(2)).is<number>();
expectType(socket.setMulticastLoopback(true)).is<boolean>();
expectType(socket.setMulticastInterface("192.168.1.100")).is<boolean>();
expectType(socket.addMembership("224.0.0.1")).is<boolean>();
expectType(socket.addMembership("224.0.0.1", "192.168.1.100")).is<boolean>();
expectType(socket.dropMembership("224.0.0.1")).is<boolean>();
expectType(socket.dropMembership("224.0.0.1", "192.168.1.100")).is<boolean>();
expectType(socket.addSourceSpecificMembership("10.0.0.1", "232.0.0.1")).is<boolean>();
expectType(socket.addSourceSpecificMembership("10.0.0.1", "232.0.0.1", "192.168.1.100")).is<boolean>();
expectType(socket.dropSourceSpecificMembership("10.0.0.1", "232.0.0.1")).is<boolean>();
expectType(socket.dropSourceSpecificMembership("10.0.0.1", "232.0.0.1", "192.168.1.100")).is<boolean>();
expectType(socket.ref()).is<void>();
expectType(socket.unref()).is<void>();
expectType(socket.close()).is<void>();
const connectedSocket = await Bun.udpSocket({
port: 0,
connect: {
hostname: "127.0.0.1",
port: 41234,
},
});
expectType(connectedSocket.remoteAddress).is<Bun.SocketAddress>();
expectType(connectedSocket.send("Hello")).is<boolean>();
expectType(connectedSocket.send(new Uint8Array([1, 2, 3]))).is<boolean>();
expectType(connectedSocket.sendMany(["Hello", "World"])).is<number>();
expectType(connectedSocket.setBroadcast(false)).is<boolean>();
expectType(connectedSocket.setTTL(128)).is<number>();
expectType(connectedSocket.setMulticastTTL(1)).is<number>();
expectType(connectedSocket.setMulticastLoopback(false)).is<boolean>();
connectedSocket.close();
+21
View File
@@ -0,0 +1,21 @@
const myUrl = new URL("hello");
myUrl.searchParams.toJSON();
const mySearchParams = new URLSearchParams("hello");
mySearchParams.toJSON();
import { URL as NodeURL, URLSearchParams as NodeURLSearchParams } from "node:url";
const nodeUrl = new NodeURL("hello");
nodeUrl.searchParams.toJSON();
const nodeSearchParams = new NodeURLSearchParams("hello");
nodeSearchParams.toJSON();
import { URL as UrlURL, URLSearchParams as UrlURLSearchParams } from "url";
const urlUrl = new UrlURL("hello");
urlUrl.searchParams.toJSON();
const urlSearchParams = new UrlURLSearchParams("hello");
urlSearchParams.toJSON();
@@ -0,0 +1,7 @@
import util from "node:util";
import types from "node:util/types";
util.types;
types.isAnyArrayBuffer;
types.isCryptoKey;
util.inspect;
@@ -0,0 +1,42 @@
type IfEquals<T, U, Y = true, N = false> = (<G>() => G extends T ? 1 : 2) extends <G>() => G extends U ? 1 : 2 ? Y : N;
export function expectType<T>(): {
/**
* @example
* ```ts
* expectType<number>().is<1>(); // fail
* expectType<number>().is<any>(); // fail
* expectType<any>().is<number>(); // fail
* expectType<number>().is<unknown>(); // fail
* expectType<number>().is<number>(); // pass
* expectType<Uint8Array>().is<Uint8Array>(); // pass
* ```
*/
is<X extends T>(...args: IfEquals<X, T> extends true ? [] : [expected: X, but_got: T]): void;
extends<X>(...args: T extends X ? [] : [expected: T, but_got: X]): void;
};
export function expectType<T>(arg: T): {
/**
* @example
* ```ts
* expectType(my_number).is<1>(); // fail
* expectType(my_number).is<any>(); // fail
* expectType(my_any).is<number>(); // fail
* expectType(my_number).is<unknown>(); // fail
* expectType(my_number).is<number>(); // pass
* expectType(my_Uint8Array).is<Uint8Array>(); // pass
* ```
*/
is<X extends T>(...args: IfEquals<X, T> extends true ? [] : [expected: X, but_got: T]): void;
extends<X>(...args: T extends X ? [] : [expected: T, but_got: X]): void;
toBeDefined(...args: undefined extends T ? [expected_something_but_got: undefined] : []): void;
};
export function expectType<T>(arg?: T) {
return { is() {}, extends() {} };
}
export declare function expectNotEmpty<T>(...args: [keyof T] extends [never] ? [value: never] : [value?: T]): void;
export declare const expectAssignable: <T>(expression: T) => void;
export declare const expectTypeEquals: <T, S>(expression: T extends S ? (S extends T ? true : false) : false) => void;
@@ -0,0 +1,42 @@
async () => {
// Fetch and compile a WebAssembly module
const response = await fetch("module.wasm");
const buffer = await response.arrayBuffer();
const module = await WebAssembly.compile(buffer);
// Create a WebAssembly Memory object
const memory = new WebAssembly.Memory({ initial: 1 });
// Create a WebAssembly Table object
const table = new WebAssembly.Table({ initial: 1, element: "anyfunc" });
// Instantiate the WebAssembly module
const instance = await WebAssembly.instantiate(module, {
js: {
log: (arg: any) => console.log("Logging from WASM:", arg),
tableFunc: () => console.log("Table function called"),
},
env: {
memory: memory,
table: table,
},
});
// Exported WebAssembly functions
const { exportedFunction } = instance.exports;
exportedFunction;
// Call an exported WebAssembly function
// exportedFunction();
// Interact with WebAssembly memory
const uint8Array = new Uint8Array(memory.buffer);
uint8Array[0] = 1; // Modify memory
// Use the WebAssembly Table
table.set(0, instance.exports.exportedTableFunction);
// eslint-disable-next-line
table.get(0)(); // Call a function stored in the table
// Additional operations with instance, memory, and table can be performed here
};
@@ -0,0 +1,271 @@
import { expectType } from "./utilities";
// WebSocket constructor tests
{
// Constructor with string URL only
new WebSocket("wss://dev.local");
// Constructor with string URL and protocols array
new WebSocket("wss://dev.local", ["proto1", "proto2"]);
// Constructor with string URL and single protocol string
new WebSocket("wss://dev.local", "proto1");
// Constructor with URL object only
new WebSocket(new URL("wss://dev.local"));
// Constructor with URL object and protocols array
new WebSocket(new URL("wss://dev.local"), ["proto1", "proto2"]);
// Constructor with URL object and single protocol string
new WebSocket(new URL("wss://dev.local"), "proto1");
// Constructor with string URL and options object with protocols
new WebSocket("wss://dev.local", {
protocols: ["proto1", "proto2"],
});
// Constructor with string URL and options object with protocol
new WebSocket("wss://dev.local", {
protocol: "proto1",
});
// Constructor with URL object and options with TLS settings
new WebSocket(new URL("wss://dev.local"), {
protocol: "proto1",
tls: {
rejectUnauthorized: false,
},
});
// Constructor with headers
new WebSocket("wss://dev.local", {
headers: {
"Cookie": "session=123456",
"User-Agent": "BunWebSocketTest",
},
});
// Constructor with full options object
new WebSocket("wss://dev.local", {
protocols: ["proto1", "proto2"],
headers: {
"Cookie": "session=123456",
},
tls: {
rejectUnauthorized: true,
},
});
}
// Assignability test
{
function toAny<T>(value: T): any {
return value;
}
const AnySocket = toAny(WebSocket);
const ws: WebSocket = new AnySocket("wss://dev.local");
ws.close();
ws.addEventListener("open", e => expectType(e).is<Event>());
ws.addEventListener("message", e => expectType(e).is<MessageEvent>());
ws.addEventListener("message", (e: MessageEvent<string>) => expectType(e).is<MessageEvent<string>>());
ws.addEventListener("message", (e: MessageEvent<string>) => expectType(e.data).is<string>());
}
// WebSocket static properties test
{
expectType(WebSocket.CONNECTING).is<0>();
expectType(WebSocket.OPEN).is<1>();
expectType(WebSocket.CLOSING).is<2>();
expectType(WebSocket.CLOSED).is<3>();
const instance: WebSocket = null as never;
expectType(instance.CONNECTING).is<0>();
expectType(instance.OPEN).is<1>();
expectType(instance.CLOSING).is<2>();
expectType(instance.CLOSED).is<3>();
}
// WebSocket event handlers test
{
const ws = new WebSocket("wss://dev.local");
// Using event handler properties
ws.onopen = (event: Event) => {
expectType(event).is<Event>();
};
ws.onmessage = (event: MessageEvent<string>) => {
expectType(event.data).is<string>();
};
ws.onerror = (event: Event) => {
expectType(event).is<Event>();
};
ws.onclose = (event: CloseEvent) => {
expectType(event).is<CloseEvent>();
expectType(event.code).is<number>();
expectType(event.reason).is<string>();
expectType(event.wasClean).is<boolean>();
};
// Using event handler properties without typing the agument
ws.onopen = event => {
expectType(event).is<Event>();
};
ws.onmessage = event => {
expectType(event.data).is<any>();
if (typeof event.data === "string") {
expectType(event.data).is<string>();
} else if (event.data instanceof ArrayBuffer) {
expectType(event.data).is<ArrayBuffer>();
}
};
ws.onerror = event => {
expectType(event).is<Event>();
};
ws.onclose = event => {
expectType(event).is<CloseEvent>();
expectType(event.code).is<number>();
expectType(event.reason).is<string>();
expectType(event.wasClean).is<boolean>();
};
}
// WebSocket addEventListener test
{
const ws = new WebSocket("wss://dev.local");
// Event handler functions
const handleOpen = (event: Event) => {
expectType(event).is<Event>();
};
const handleMessage = (event: MessageEvent<string>) => {
expectType(event.data).is<string>();
};
const handleError = (event: Event) => {
expectType(event).is<Event>();
};
const handleClose = (event: CloseEvent) => {
expectType(event).is<CloseEvent>();
expectType(event.code).is<number>();
expectType(event.reason).is<string>();
expectType(event.wasClean).is<boolean>();
};
// Add event listeners
ws.addEventListener("open", handleOpen);
ws.addEventListener("message", handleMessage);
ws.addEventListener("error", handleError);
ws.addEventListener("close", handleClose);
// Remove event listeners
ws.removeEventListener("open", handleOpen);
ws.removeEventListener("message", handleMessage);
ws.removeEventListener("error", handleError);
ws.removeEventListener("close", handleClose);
}
// WebSocket property access test
{
const ws = new WebSocket("wss://dev.local");
// Read various properties
expectType(ws.readyState).is<0 | 2 | 1 | 3>();
expectType(ws.bufferedAmount).is<number>();
expectType(ws.url).is<string>();
expectType(ws.protocol).is<string>();
expectType(ws.extensions).is<string>();
// Legacy URL property (deprecated but exists)
expectType(ws.URL).is<string>();
// Set binary type
ws.binaryType = "arraybuffer";
ws.binaryType = "nodebuffer";
}
// WebSocket send method test
{
const ws = new WebSocket("wss://dev.local");
// Send string data
ws.send("Hello, server!");
// Send ArrayBuffer
const buffer = new ArrayBuffer(10);
ws.send(buffer);
// Send ArrayBufferView (Uint8Array)
const uint8Array = new Uint8Array(buffer);
ws.send(uint8Array);
// --------------------------------------- //
// `.send(blob)` is not supported yet
// --------------------------------------- //
// // Send Blob
// const blob = new Blob(["Hello, server!"]);
// ws.send(blob);
// --------------------------------------- //
}
// WebSocket close method test
{
const ws = new WebSocket("wss://dev.local");
// Close without parameters
ws.close();
// Close with code
ws.close(1000);
// Close with code and reason
ws.close(1001, "Going away");
}
// Bun-specific WebSocket extensions test
{
const ws = new WebSocket("wss://dev.local");
// Send ping frame with no data
ws.ping();
// Send ping frame with string data
ws.ping("ping data");
// Send ping frame with ArrayBuffer
const pingBuffer = new ArrayBuffer(4);
ws.ping(pingBuffer);
// Send ping frame with ArrayBufferView
const pingView = new Uint8Array(pingBuffer);
ws.ping(pingView);
// Send pong frame with no data
ws.pong();
// Send pong frame with string data
ws.pong("pong data");
// Send pong frame with ArrayBuffer
const pongBuffer = new ArrayBuffer(4);
ws.pong(pongBuffer);
// Send pong frame with ArrayBufferView
const pongView = new Uint8Array(pongBuffer);
ws.pong(pongView);
// Terminate the connection immediately
ws.terminate();
}
@@ -0,0 +1,62 @@
import { Worker as NodeWorker } from "node:worker_threads";
import * as tsd from "./utilities";
const webWorker = new Worker("./worker.js");
webWorker.addEventListener("message", event => {
tsd.expectType<MessageEvent>(event);
});
webWorker.addEventListener("error", event => {
tsd.expectType<ErrorEvent>(event);
});
webWorker.addEventListener("messageerror", event => {
tsd.expectType<MessageEvent>(event);
});
webWorker.onmessage = ev => "asdf";
webWorker.onmessageerror = ev => "asdf";
webWorker.postMessage("asdf", []);
webWorker.terminate();
webWorker.addEventListener("close", () => {});
webWorker.removeEventListener("sadf", () => {});
// these methods don't exist if lib.dom.d.ts is present
webWorker.ref();
webWorker.unref();
webWorker.threadId;
const nodeWorker = new NodeWorker("./worker.ts");
nodeWorker.on("message", event => {
console.log("Message from worker:", event);
});
nodeWorker.postMessage("Hello from main thread!");
const workerURL = new URL("worker.ts", "/path/to/").href;
const _worker2 = new Worker(workerURL);
nodeWorker.postMessage("hello");
webWorker.onmessage = event => {
console.log(event.data);
};
// On the worker thread, `postMessage` is automatically "routed" to the parent thread.
postMessage({ hello: "world" });
// On the main thread
nodeWorker.postMessage({ hello: "world" });
// ...some time later
await nodeWorker.terminate();
// Bun.pathToFileURL
const _worker3 = new Worker(new URL("worker.ts", "/path/to/").href, {
ref: true,
smol: true,
credentials: "same-origin",
name: "a name",
env: {
envValue: "hello",
},
});
export { _worker2, _worker3, nodeWorker as worker };
+54
View File
@@ -0,0 +1,54 @@
import { XML } from "bun";
import doc from "./data.xml";
import { expectType } from "./utilities";
expectType(doc).is<XML.Document>();
expectType(Bun.XML.parse("<a/>")).is<XML.Document>();
expectType(XML.parse(new Uint8Array())).is<XML.Document>();
expectType(XML.parse("<a/>", { compact: true })).is<XML.Document>();
expectType(XML.parse("<a/>", { compact: false })).is<XML.Node>();
expectType(XML.parse("<a/>", { compact: false }).children).is<
Array<string | XML.Node | XML.Comment | XML.ProcessingInstruction>
>();
expectType(XML.parse("<a/>", {} as XML.ParseOptions)).is<XML.Document | XML.Node>();
// The compact value space is closed: narrowing needs no casts.
{
const root: XML.Value | undefined = XML.parse("<a/>").a;
if (typeof root === "object") {
const child = root.item;
expectType(child).is<XML.Value | XML.Value[] | undefined>();
for (const item of Array.isArray(child) ? child : [child]) {
if (typeof item === "object") expectType(item["@id"]).is<XML.Value | XML.Value[] | undefined>();
else expectType(item).is<string | undefined>();
}
}
}
// Tree children discriminate by key.
for (const c of XML.parse("<a/>", { compact: false }).children) {
if (typeof c === "string") expectType(c).is<string>();
else if ("name" in c) expectType(c).is<XML.Node>();
else if ("comment" in c) expectType(c).is<XML.Comment>();
else expectType(c).is<XML.ProcessingInstruction>();
}
// @ts-expect-error
XML.parse({});
// @ts-expect-error
XML.parse("<a/>", { compact: "no" });
// @ts-expect-error - reserved for a reviver, not accepted yet
XML.parse("<a/>", (key: string, value: unknown) => value);
expectType(XML.stringify({ a: { "@id": "1", b: ["x"] } })).is<string>();
expectType(XML.stringify({ name: "a", attributes: {}, children: ["x"] } satisfies XML.Node, null, 2)).is<string>();
expectType(
XML.stringify({ name: "a", children: ["x", 1, null, { comment: "c" }, { target: "p", data: "" }, { name: "b" }] }),
).is<string>();
expectType(XML.stringify(XML.parse("<a/>", { compact: false }))).is<string>();
// `undefined` when the input is `undefined`, a function, or a symbol.
expectType(XML.stringify(undefined)).is<string | undefined>();
expectType(XML.stringify(Symbol() as unknown)).is<string | undefined>();
// @ts-expect-error
XML.stringify({ a: "1" }, (key: string, value: unknown) => value);
// @ts-expect-error
XML.stringify({ a: "1" }, null, 123n);
@@ -0,0 +1,10 @@
import { expectType } from "./utilities";
expectType(Bun.YAML.parse("")).is<unknown>();
// @ts-expect-error
expectType(Bun.YAML.parse({})).is<unknown>();
expectType(Bun.YAML.stringify({ abc: "def"})).is<string>();
// @ts-expect-error
expectType(Bun.YAML.stringify("hi", {})).is<string>();
// @ts-expect-error
expectType(Bun.YAML.stringify("hi", null, 123n)).is<string>();
@@ -0,0 +1,102 @@
import { describe, expect, test } from "bun:test";
import { bunEnv, bunExe, isWindows, tempDir } from "harness";
import { join } from "node:path";
// @datadog/[email protected] ships node-gyp-build prebuilds for linux-{x64,arm64}
// (glibc + musl), darwin-{x64,arm64} and win32-x64 at ABI 147, matching Bun's
// process.versions.modules. win32-arm64 has no prebuild — skip there rather
// than fall through to a node-gyp source build.
const hasPrebuild = !(isWindows && process.arch === "arm64");
describe.skipIf(!hasPrebuild)("@datadog/pprof", () => {
test("TimeProfiler start/stop returns a populated profile", async () => {
using dir = tempDir("datadog-pprof", {
"package.json": JSON.stringify({
name: "datadog-pprof-fixture",
version: "0.0.0",
dependencies: {
"@datadog/pprof": "5.17.0",
},
trustedDependencies: ["@datadog/pprof"],
}),
"index.js": /* js */ `
const { time } = require("@datadog/pprof");
function hotLoop() {
// Busy-spin long enough for the 1ms wall sampler to capture real
// samples on slow debug/ASAN builds; this is CPU work, not a sleep.
const start = Date.now();
let acc = 0;
while (Date.now() - start < 300) {
for (let i = 0; i < 1000; i++) acc += Math.sqrt(i);
}
return acc;
}
time.start({ intervalMicros: 1000, durationMillis: 60000 });
hotLoop();
const profile = time.stop();
const strings = profile.stringTable.strings;
const summary = {
sampleCount: profile.sample.length,
locationCount: profile.location.length,
functionCount: profile.function.length,
stringCount: strings.length,
hasHotLoop: strings.includes("hotLoop"),
period: Number(profile.period),
};
process.stdout.write(JSON.stringify(summary) + "\\n");
`,
});
{
await using install = Bun.spawn({
cmd: [bunExe(), "install"],
env: bunEnv,
cwd: String(dir),
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([
install.stdout.text(),
install.stderr.text(),
install.exited,
]);
if (exitCode !== 0) {
throw new Error(`bun install failed (exit ${exitCode})\nstdout:\n${stdout}\nstderr:\n${stderr}`);
}
}
await using proc = Bun.spawn({
cmd: [bunExe(), join(String(dir), "index.js")],
env: bunEnv,
cwd: String(dir),
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(stderr).toBe("");
const summary = JSON.parse(stdout.trim());
expect(summary).toEqual({
sampleCount: expect.any(Number),
locationCount: expect.any(Number),
functionCount: expect.any(Number),
stringCount: expect.any(Number),
hasHotLoop: true,
period: expect.any(Number),
});
expect(summary.sampleCount).toBeGreaterThan(0);
expect(summary.locationCount).toBeGreaterThan(0);
expect(summary.functionCount).toBeGreaterThan(0);
expect(summary.stringCount).toBeGreaterThan(0);
// serializeTimeProfile recomputes intervalMicros from wall-clock/hit-count,
// clamps it to [intervalMicros, 2*intervalMicros], then ×1000 → nanoseconds.
expect(summary.period).toBeGreaterThanOrEqual(1_000_000);
expect(summary.period).toBeLessThanOrEqual(2_000_000);
expect(exitCode).toBe(0);
}, 120_000);
});
+2
View File
@@ -0,0 +1,2 @@
console.log("hello");
console.log("estrella");
+195
View File
@@ -0,0 +1,195 @@
import { spawn } from "bun";
import { beforeAll, describe, expect, setDefaultTimeout, test } from "bun:test";
import { cp, rm, writeFile } from "fs/promises";
import { bunExe, bunEnv as env, isArm64, isWindows, tempDir } from "harness";
import { join } from "path";
// [email protected] does not support win32-arm64 at runtime
const isWindowsArm64 = isWindows && isArm64;
beforeAll(() => {
setDefaultTimeout(1000 * 60 * 5);
});
describe.concurrent("esbuild integration test", () => {
test("install and use esbuild", async () => {
using dir = tempDir("esbuild-test", {
"package.json": JSON.stringify({
name: "bun-esbuild-test",
version: "1.0.0",
}),
});
const packageDir = dir + "";
var { stdout, stderr, exited } = spawn({
cmd: [bunExe(), "install", "[email protected]"],
cwd: packageDir,
stdout: "pipe",
stdin: "pipe",
stderr: "pipe",
env,
});
var err = await stderr.text();
var out = await stdout.text();
expect(err).toContain("Saved lockfile");
expect(out).toContain("[email protected]");
expect(await exited).toBe(0);
({ stdout, stderr, exited } = spawn({
cmd: [bunExe(), "esbuild", "--version"],
cwd: packageDir,
stdout: "pipe",
stdin: "pipe",
stderr: "pipe",
env,
}));
err = await stderr.text();
out = await stdout.text();
expect(err).toBe("");
expect(out).toContain("0.19.8");
expect(await exited).toBe(0);
});
test.skipIf(isWindowsArm64)("install and use estrella", async () => {
using dir = tempDir("esbuild-estrella-test", {
"package.json": JSON.stringify({
name: "bun-esbuild-estrella-test",
version: "1.0.0",
}),
});
const packageDir = dir + "";
let { stdout, stderr, exited } = spawn({
cmd: [bunExe(), "install", "[email protected]"],
cwd: packageDir,
stdout: "pipe",
stdin: "pipe",
stderr: "pipe",
env,
});
let exitCode = 0;
let err = "";
let out = "";
[err, out, exitCode] = await Promise.all([new Response(stderr).text(), new Response(stdout).text(), exited]);
expect(err).toContain("Saved lockfile");
expect(out).toContain("[email protected]");
expect(exitCode).toBe(0);
({ stdout, stderr, exited } = spawn({
cmd: [bunExe(), "estrella", "--estrella-version"],
cwd: packageDir,
stdout: "pipe",
stdin: "pipe",
stderr: "pipe",
env,
}));
[err, out, exitCode] = await Promise.all([new Response(stderr).text(), new Response(stdout).text(), exited]);
expect(err).toBe("");
expect(out).toContain("1.4.1");
expect(exitCode).toBe(0);
await cp(join(import.meta.dir, "build-file.js"), join(packageDir, "build-file.js"));
({ stdout, stderr, exited } = spawn({
cmd: [bunExe(), "estrella", "build-file.js"],
cwd: packageDir,
stdout: "pipe",
stdin: "pipe",
stderr: "pipe",
env,
}));
[err, out, exitCode] = await Promise.all([stderr.text(), stdout.text(), exited]);
await rm(join(packageDir, "node_modules"), { recursive: true, force: true });
await rm(join(packageDir, "bun.lockb"), { force: true });
await writeFile(
join(packageDir, "package.json"),
JSON.stringify({
name: "bun-esbuild-estrella-test",
version: "1.0.0",
dependencies: {
"estrella": "1.4.1",
// different version of esbuild
"esbuild": "0.19.8",
},
}),
);
({ stdout, stderr, exited } = spawn({
cmd: [bunExe(), "install"],
cwd: packageDir,
stdout: "pipe",
stdin: "pipe",
stderr: "pipe",
env,
}));
[err, out, exitCode] = await Promise.all([stderr.text(), stdout.text(), exited]);
expect(err).toContain("Saved lockfile");
expect(out).toContain("[email protected]");
expect(out).toContain("[email protected]");
expect(exitCode).toBe(0);
({ stdout, stderr, exited } = spawn({
cmd: [bunExe(), "estrella", "--estrella-version"],
cwd: packageDir,
stdout: "pipe",
stdin: "pipe",
stderr: "pipe",
env,
}));
[err, out, exitCode] = await Promise.all([stderr.text(), stdout.text(), exited]);
expect(err).toBe("");
expect(out).toContain("1.4.1");
expect(exitCode).toBe(0);
({ stdout, stderr, exited } = spawn({
cmd: [bunExe(), "esbuild", "--version"],
cwd: packageDir,
stdout: "pipe",
stdin: "pipe",
stderr: "pipe",
env,
}));
[err, out, exitCode] = await Promise.all([stderr.text(), stdout.text(), exited]);
expect(err).toBe("");
expect(out).toContain("0.19.8");
expect(exitCode).toBe(0);
({ stdout, stderr, exited } = spawn({
cmd: [bunExe(), "esbuild", "--version"],
cwd: join(packageDir, "node_modules/estrella"),
stdout: "pipe",
stdin: "pipe",
stderr: "pipe",
env,
}));
[err, out, exitCode] = await Promise.all([stderr.text(), stdout.text(), exited]);
expect(err).toBe("");
expect(out).toContain("0.11.23");
expect(exitCode).toBe(0);
({ stdout, stderr, exited } = spawn({
cmd: [bunExe(), "estrella", "build-file.js"],
cwd: packageDir,
stdout: "pipe",
stdin: "pipe",
stderr: "pipe",
env,
}));
[err, out, exitCode] = await Promise.all([stderr.text(), stdout.text(), exited]);
expect(err).toBe("");
expect(out).toBe('console.log("hello"),console.log("estrella");\n');
expect(exitCode).toBe(0);
});
});
+50
View File
@@ -0,0 +1,50 @@
# Welcome to your Expo app 👋
This is an [Expo](https://expo.dev) project created with [`create-expo-app`](https://www.npmjs.com/package/create-expo-app).
## Get started
1. Install dependencies
```bash
npm install
```
2. Start the app
```bash
npx expo start
```
In the output, you'll find options to open the app in a
- [development build](https://docs.expo.dev/develop/development-builds/introduction/)
- [Android emulator](https://docs.expo.dev/workflow/android-studio-emulator/)
- [iOS simulator](https://docs.expo.dev/workflow/ios-simulator/)
- [Expo Go](https://expo.dev/go), a limited sandbox for trying out app development with Expo
You can start developing by editing the files inside the **app** directory. This project uses [file-based routing](https://docs.expo.dev/router/introduction).
## Get a fresh project
When you're ready, run:
```bash
npm run reset-project
```
This command will move the starter code to the **app-example** directory and create a blank **app** directory where you can start developing.
## Learn more
To learn more about developing your project with Expo, look at the following resources:
- [Expo documentation](https://docs.expo.dev/): Learn fundamentals, or go into advanced topics with our [guides](https://docs.expo.dev/guides).
- [Learn Expo tutorial](https://docs.expo.dev/tutorial/introduction/): Follow a step-by-step tutorial where you'll create a project that runs on Android, iOS, and the web.
## Join the community
Join our community of developers creating universal apps.
- [Expo on GitHub](https://github.com/expo/expo): View our open source platform and contribute.
- [Discord community](https://chat.expo.dev): Chat with Expo users and ask questions.
+36
View File
@@ -0,0 +1,36 @@
{
"expo": {
"name": "expo-app",
"slug": "expo-app",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/images/icon.png",
"scheme": "myapp",
"userInterfaceStyle": "automatic",
"splash": {
"image": "./assets/images/splash.png",
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"ios": {
"supportsTablet": true
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/images/adaptive-icon.png",
"backgroundColor": "#ffffff"
}
},
"web": {
"bundler": "metro",
"output": "static",
"favicon": "./assets/images/favicon.png"
},
"plugins": [
"expo-router"
],
"experiments": {
"typedRoutes": true
}
}
}
@@ -0,0 +1,35 @@
import { Tabs } from "expo-router";
import { TabBarIcon } from "@/components/navigation/TabBarIcon";
import { Colors } from "@/constants/Colors";
import { useColorScheme } from "@/hooks/useColorScheme";
export default function TabLayout() {
const colorScheme = useColorScheme();
return (
<Tabs
screenOptions={{
tabBarActiveTintColor: Colors[colorScheme ?? "light"].tint,
headerShown: false,
}}
>
<Tabs.Screen
name="index"
options={{
title: "Home",
tabBarIcon: ({ color, focused }) => <TabBarIcon name={focused ? "home" : "home-outline"} color={color} />,
}}
/>
<Tabs.Screen
name="explore"
options={{
title: "Explore",
tabBarIcon: ({ color, focused }) => (
<TabBarIcon name={focused ? "code-slash" : "code-slash-outline"} color={color} />
),
}}
/>
</Tabs>
);
}
@@ -0,0 +1,99 @@
import Ionicons from "@expo/vector-icons/Ionicons";
import { Image, Platform, StyleSheet } from "react-native";
import { Collapsible } from "@/components/Collapsible";
import { ExternalLink } from "@/components/ExternalLink";
import ParallaxScrollView from "@/components/ParallaxScrollView";
import { ThemedText } from "@/components/ThemedText";
import { ThemedView } from "@/components/ThemedView";
export default function TabTwoScreen() {
return (
<ParallaxScrollView
headerBackgroundColor={{ light: "#D0D0D0", dark: "#353636" }}
headerImage={<Ionicons size={310} name="code-slash" style={styles.headerImage} />}
>
<ThemedView style={styles.titleContainer}>
<ThemedText type="title">Explore</ThemedText>
</ThemedView>
<ThemedText>This app includes example code to help you get started.</ThemedText>
<Collapsible title="File-based routing">
<ThemedText>
This app has two screens: <ThemedText type="defaultSemiBold">app/(tabs)/index.tsx</ThemedText> and{" "}
<ThemedText type="defaultSemiBold">app/(tabs)/explore.tsx</ThemedText>
</ThemedText>
<ThemedText>
The layout file in <ThemedText type="defaultSemiBold">app/(tabs)/_layout.tsx</ThemedText> sets up the tab
navigator.
</ThemedText>
<ExternalLink href="https://docs.expo.dev/router/introduction">
<ThemedText type="link">Learn more</ThemedText>
</ExternalLink>
</Collapsible>
<Collapsible title="Android, iOS, and web support">
<ThemedText>
You can open this project on Android, iOS, and the web. To open the web version, press{" "}
<ThemedText type="defaultSemiBold">w</ThemedText> in the terminal running this project.
</ThemedText>
</Collapsible>
<Collapsible title="Images">
<ThemedText>
For static images, you can use the <ThemedText type="defaultSemiBold">@2x</ThemedText> and{" "}
<ThemedText type="defaultSemiBold">@3x</ThemedText> suffixes to provide files for different screen densities
</ThemedText>
<Image source={require("@/assets/images/react-logo.png")} style={{ alignSelf: "center" }} />
<ExternalLink href="https://reactnative.dev/docs/images">
<ThemedText type="link">Learn more</ThemedText>
</ExternalLink>
</Collapsible>
<Collapsible title="Custom fonts">
<ThemedText>
Open <ThemedText type="defaultSemiBold">app/_layout.tsx</ThemedText> to see how to load{" "}
<ThemedText style={{ fontFamily: "SpaceMono" }}>custom fonts such as this one.</ThemedText>
</ThemedText>
<ExternalLink href="https://docs.expo.dev/versions/latest/sdk/font">
<ThemedText type="link">Learn more</ThemedText>
</ExternalLink>
</Collapsible>
<Collapsible title="Light and dark mode components">
<ThemedText>
This template has light and dark mode support. The{" "}
<ThemedText type="defaultSemiBold">useColorScheme()</ThemedText> hook lets you inspect what the user's current
color scheme is, and so you can adjust UI colors accordingly.
</ThemedText>
<ExternalLink href="https://docs.expo.dev/develop/user-interface/color-themes/">
<ThemedText type="link">Learn more</ThemedText>
</ExternalLink>
</Collapsible>
<Collapsible title="Animations">
<ThemedText>
This template includes an example of an animated component. The{" "}
<ThemedText type="defaultSemiBold">components/HelloWave.tsx</ThemedText> component uses the powerful{" "}
<ThemedText type="defaultSemiBold">react-native-reanimated</ThemedText> library to create a waving hand
animation.
</ThemedText>
{Platform.select({
ios: (
<ThemedText>
The <ThemedText type="defaultSemiBold">components/ParallaxScrollView.tsx</ThemedText> component provides a
parallax effect for the header image.
</ThemedText>
),
})}
</Collapsible>
</ParallaxScrollView>
);
}
const styles = StyleSheet.create({
headerImage: {
color: "#808080",
bottom: -90,
left: -35,
position: "absolute",
},
titleContainer: {
flexDirection: "row",
gap: 8,
},
});
@@ -0,0 +1,60 @@
import { Image, Platform, StyleSheet } from "react-native";
import { HelloWave } from "@/components/HelloWave";
import ParallaxScrollView from "@/components/ParallaxScrollView";
import { ThemedText } from "@/components/ThemedText";
import { ThemedView } from "@/components/ThemedView";
export default function HomeScreen() {
return (
<ParallaxScrollView
headerBackgroundColor={{ light: "#A1CEDC", dark: "#1D3D47" }}
headerImage={<Image source={require("@/assets/images/partial-react-logo.png")} style={styles.reactLogo} />}
>
<ThemedView style={styles.titleContainer}>
<ThemedText type="title">Welcome!</ThemedText>
<HelloWave />
</ThemedView>
<ThemedView style={styles.stepContainer}>
<ThemedText type="subtitle">Step 1: Try it</ThemedText>
<ThemedText>
Edit <ThemedText type="defaultSemiBold">app/(tabs)/index.tsx</ThemedText> to see changes. Press{" "}
<ThemedText type="defaultSemiBold">{Platform.select({ ios: "cmd + d", android: "cmd + m" })}</ThemedText> to
open developer tools.
</ThemedText>
</ThemedView>
<ThemedView style={styles.stepContainer}>
<ThemedText type="subtitle">Step 2: Explore</ThemedText>
<ThemedText>Tap the Explore tab to learn more about what's included in this starter app.</ThemedText>
</ThemedView>
<ThemedView style={styles.stepContainer}>
<ThemedText type="subtitle">Step 3: Get a fresh start</ThemedText>
<ThemedText>
When you're ready, run <ThemedText type="defaultSemiBold">npm run reset-project</ThemedText> to get a fresh{" "}
<ThemedText type="defaultSemiBold">app</ThemedText> directory. This will move the current{" "}
<ThemedText type="defaultSemiBold">app</ThemedText> to{" "}
<ThemedText type="defaultSemiBold">app-example</ThemedText>.
</ThemedText>
</ThemedView>
</ParallaxScrollView>
);
}
const styles = StyleSheet.create({
titleContainer: {
flexDirection: "row",
alignItems: "center",
gap: 8,
},
stepContainer: {
gap: 8,
marginBottom: 8,
},
reactLogo: {
height: 178,
width: 290,
bottom: 0,
left: 0,
position: "absolute",
},
});
+39
View File
@@ -0,0 +1,39 @@
import { ScrollViewStyleReset } from "expo-router/html";
import { type PropsWithChildren } from "react";
/**
* This file is web-only and used to configure the root HTML for every web page during static rendering.
* The contents of this function only run in Node.js environments and do not have access to the DOM or browser APIs.
*/
export default function Root({ children }: PropsWithChildren) {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
{/*
Disable body scrolling on web. This makes ScrollView components work closer to how they do on native.
However, body scrolling is often nice to have for mobile web. If you want to enable it, remove this line.
*/}
<ScrollViewStyleReset />
{/* Using raw CSS styles as an escape-hatch to ensure the background color never flickers in dark-mode. */}
<style dangerouslySetInnerHTML={{ __html: responsiveBackground }} />
{/* Add any additional <head> elements that you want globally available on web... */}
</head>
<body>{children}</body>
</html>
);
}
const responsiveBackground = `
body {
background-color: #fff;
}
@media (prefers-color-scheme: dark) {
body {
background-color: #000;
}
}`;
@@ -0,0 +1,32 @@
import { Link, Stack } from "expo-router";
import { StyleSheet } from "react-native";
import { ThemedText } from "@/components/ThemedText";
import { ThemedView } from "@/components/ThemedView";
export default function NotFoundScreen() {
return (
<>
<Stack.Screen options={{ title: "Oops!" }} />
<ThemedView style={styles.container}>
<ThemedText type="title">This screen doesn't exist.</ThemedText>
<Link href="/" style={styles.link}>
<ThemedText type="link">Go to home screen!</ThemedText>
</Link>
</ThemedView>
</>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: "center",
justifyContent: "center",
padding: 20,
},
link: {
marginTop: 15,
paddingVertical: 15,
},
});
+37
View File
@@ -0,0 +1,37 @@
import { DarkTheme, DefaultTheme, ThemeProvider } from "@react-navigation/native";
import { useFonts } from "expo-font";
import { Stack } from "expo-router";
import * as SplashScreen from "expo-splash-screen";
import { useEffect } from "react";
import "react-native-reanimated";
import { useColorScheme } from "@/hooks/useColorScheme";
// Prevent the splash screen from auto-hiding before asset loading is complete.
SplashScreen.preventAutoHideAsync();
export default function RootLayout() {
const colorScheme = useColorScheme();
const [loaded] = useFonts({
SpaceMono: require("../assets/fonts/SpaceMono-Regular.ttf"),
});
useEffect(() => {
if (loaded) {
SplashScreen.hideAsync();
}
}, [loaded]);
if (!loaded) {
return null;
}
return (
<ThemeProvider value={colorScheme === "dark" ? DarkTheme : DefaultTheme}>
<Stack>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen name="+not-found" />
</Stack>
</ThemeProvider>
);
}
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

@@ -0,0 +1,6 @@
module.exports = function (api) {
api.cache(true);
return {
presets: ["babel-preset-expo"],
};
};
File diff suppressed because it is too large Load Diff
+1
View File
@@ -0,0 +1 @@
install.cache = false
@@ -0,0 +1,38 @@
import Ionicons from "@expo/vector-icons/Ionicons";
import { PropsWithChildren, useState } from "react";
import { StyleSheet, TouchableOpacity, useColorScheme } from "react-native";
import { ThemedText } from "@/components/ThemedText";
import { ThemedView } from "@/components/ThemedView";
import { Colors } from "@/constants/Colors";
export function Collapsible({ children, title }: PropsWithChildren & { title: string }) {
const [isOpen, setIsOpen] = useState(false);
const theme = useColorScheme() ?? "light";
return (
<ThemedView>
<TouchableOpacity style={styles.heading} onPress={() => setIsOpen(value => !value)} activeOpacity={0.8}>
<Ionicons
name={isOpen ? "chevron-down" : "chevron-forward-outline"}
size={18}
color={theme === "light" ? Colors.light.icon : Colors.dark.icon}
/>
<ThemedText type="defaultSemiBold">{title}</ThemedText>
</TouchableOpacity>
{isOpen && <ThemedView style={styles.content}>{children}</ThemedView>}
</ThemedView>
);
}
const styles = StyleSheet.create({
heading: {
flexDirection: "row",
alignItems: "center",
gap: 6,
},
content: {
marginTop: 6,
marginLeft: 24,
},
});
@@ -0,0 +1,24 @@
import { Link } from "expo-router";
import { openBrowserAsync } from "expo-web-browser";
import { type ComponentProps } from "react";
import { Platform } from "react-native";
type Props = Omit<ComponentProps<typeof Link>, "href"> & { href: string };
export function ExternalLink({ href, ...rest }: Props) {
return (
<Link
target="_blank"
{...rest}
href={href}
onPress={async event => {
if (Platform.OS !== "web") {
// Prevent the default behavior of linking to the default browser on native.
event.preventDefault();
// Open the link in an in-app browser.
await openBrowserAsync(href);
}
}}
/>
);
}
@@ -0,0 +1,37 @@
import { StyleSheet } from "react-native";
import Animated, {
useAnimatedStyle,
useSharedValue,
withRepeat,
withSequence,
withTiming,
} from "react-native-reanimated";
import { ThemedText } from "@/components/ThemedText";
export function HelloWave() {
const rotationAnimation = useSharedValue(0);
rotationAnimation.value = withRepeat(
withSequence(withTiming(25, { duration: 150 }), withTiming(0, { duration: 150 })),
4, // Run the animation 4 times
);
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ rotate: `${rotationAnimation.value}deg` }],
}));
return (
<Animated.View style={animatedStyle}>
<ThemedText style={styles.text}>👋</ThemedText>
</Animated.View>
);
}
const styles = StyleSheet.create({
text: {
fontSize: 28,
lineHeight: 32,
marginTop: -6,
},
});
@@ -0,0 +1,64 @@
import type { PropsWithChildren, ReactElement } from "react";
import { StyleSheet, useColorScheme } from "react-native";
import Animated, { interpolate, useAnimatedRef, useAnimatedStyle, useScrollViewOffset } from "react-native-reanimated";
import { ThemedView } from "@/components/ThemedView";
const HEADER_HEIGHT = 250;
type Props = PropsWithChildren<{
headerImage: ReactElement;
headerBackgroundColor: { dark: string; light: string };
}>;
export default function ParallaxScrollView({ children, headerImage, headerBackgroundColor }: Props) {
const colorScheme = useColorScheme() ?? "light";
const scrollRef = useAnimatedRef<Animated.ScrollView>();
const scrollOffset = useScrollViewOffset(scrollRef);
const headerAnimatedStyle = useAnimatedStyle(() => {
return {
transform: [
{
translateY: interpolate(
scrollOffset.value,
[-HEADER_HEIGHT, 0, HEADER_HEIGHT],
[-HEADER_HEIGHT / 2, 0, HEADER_HEIGHT * 0.75],
),
},
{
scale: interpolate(scrollOffset.value, [-HEADER_HEIGHT, 0, HEADER_HEIGHT], [2, 1, 1]),
},
],
};
});
return (
<ThemedView style={styles.container}>
<Animated.ScrollView ref={scrollRef} scrollEventThrottle={16}>
<Animated.View
style={[styles.header, { backgroundColor: headerBackgroundColor[colorScheme] }, headerAnimatedStyle]}
>
{headerImage}
</Animated.View>
<ThemedView style={styles.content}>{children}</ThemedView>
</Animated.ScrollView>
</ThemedView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
header: {
height: 250,
overflow: "hidden",
},
content: {
flex: 1,
padding: 32,
gap: 16,
overflow: "hidden",
},
});
@@ -0,0 +1,54 @@
import { StyleSheet, Text, type TextProps } from "react-native";
import { useThemeColor } from "@/hooks/useThemeColor";
export type ThemedTextProps = TextProps & {
lightColor?: string;
darkColor?: string;
type?: "default" | "title" | "defaultSemiBold" | "subtitle" | "link";
};
export function ThemedText({ style, lightColor, darkColor, type = "default", ...rest }: ThemedTextProps) {
const color = useThemeColor({ light: lightColor, dark: darkColor }, "text");
return (
<Text
style={[
{ color },
type === "default" ? styles.default : undefined,
type === "title" ? styles.title : undefined,
type === "defaultSemiBold" ? styles.defaultSemiBold : undefined,
type === "subtitle" ? styles.subtitle : undefined,
type === "link" ? styles.link : undefined,
style,
]}
{...rest}
/>
);
}
const styles = StyleSheet.create({
default: {
fontSize: 16,
lineHeight: 24,
},
defaultSemiBold: {
fontSize: 16,
lineHeight: 24,
fontWeight: "600",
},
title: {
fontSize: 32,
fontWeight: "bold",
lineHeight: 32,
},
subtitle: {
fontSize: 20,
fontWeight: "bold",
},
link: {
lineHeight: 30,
fontSize: 16,
color: "#0a7ea4",
},
});
@@ -0,0 +1,14 @@
import { View, type ViewProps } from "react-native";
import { useThemeColor } from "@/hooks/useThemeColor";
export type ThemedViewProps = ViewProps & {
lightColor?: string;
darkColor?: string;
};
export function ThemedView({ style, lightColor, darkColor, ...otherProps }: ThemedViewProps) {
const backgroundColor = useThemeColor({ light: lightColor, dark: darkColor }, "background");
return <View style={[{ backgroundColor }, style]} {...otherProps} />;
}
@@ -0,0 +1,9 @@
// You can explore the built-in icon families and icons on the web at https://icons.expo.fyi/
import Ionicons from "@expo/vector-icons/Ionicons";
import { type IconProps } from "@expo/vector-icons/build/createIconSet";
import { type ComponentProps } from "react";
export function TabBarIcon({ style, ...rest }: IconProps<ComponentProps<typeof Ionicons>["name"]>) {
return <Ionicons size={28} style={[{ marginBottom: -3 }, style]} {...rest} />;
}
@@ -0,0 +1,26 @@
/**
* Below are the colors that are used in the app. The colors are defined in the light and dark mode.
* There are many other ways to style your app. For example, [Nativewind](https://www.nativewind.dev/), [Tamagui](https://tamagui.dev/), [unistyles](https://reactnativeunistyles.vercel.app), etc.
*/
const tintColorLight = "#0a7ea4";
const tintColorDark = "#fff";
export const Colors = {
light: {
text: "#11181C",
background: "#fff",
tint: tintColorLight,
icon: "#687076",
tabIconDefault: "#687076",
tabIconSelected: tintColorLight,
},
dark: {
text: "#ECEDEE",
background: "#151718",
tint: tintColorDark,
icon: "#9BA1A6",
tabIconDefault: "#9BA1A6",
tabIconSelected: tintColorDark,
},
};
+36
View File
@@ -0,0 +1,36 @@
import { beforeAll, expect, setDefaultTimeout, test } from "bun:test";
import fs from "fs/promises";
import { bunEnv, bunExe, tmpdirSync } from "../../harness";
const tmpdir = tmpdirSync();
beforeAll(async () => {
setDefaultTimeout(1000 * 60 * 4);
await fs.rm(tmpdir, { recursive: true, force: true });
await fs.cp(import.meta.dir, tmpdir, { recursive: true, force: true });
});
test("expo export works (no ajv issues)", async () => {
console.log({ tmpdir });
let { exitCode } = Bun.spawnSync([bunExe(), "install"], {
stderr: "inherit",
stdout: "inherit",
cwd: tmpdir,
env: bunEnv,
});
expect(exitCode).toBe(0);
({ exitCode } = Bun.spawnSync([bunExe(), "run", "export"], {
stdout: "inherit",
stderr: "inherit",
stdin: "inherit",
cwd: tmpdir,
env: {
...bunEnv,
PORT: "0",
},
}));
// just check exit code for now
expect(exitCode).toBe(0);
});
@@ -0,0 +1 @@
export { useColorScheme } from "react-native";
@@ -0,0 +1,8 @@
// NOTE: The default React Native styling doesn't support server rendering.
// Server rendered styles should not change between the first render of the HTML
// and the first render on the client. Typically, web developers will use CSS media queries
// to render different styles on the client and server, these aren't directly supported in React Native
// but can be achieved using a styling library like Nativewind.
export function useColorScheme() {
return "light";
}
@@ -0,0 +1,22 @@
/**
* Learn more about light and dark modes:
* https://docs.expo.dev/guides/color-schemes/
*/
import { useColorScheme } from "react-native";
import { Colors } from "@/constants/Colors";
export function useThemeColor(
props: { light?: string; dark?: string },
colorName: keyof typeof Colors.light & keyof typeof Colors.dark,
) {
const theme = useColorScheme() ?? "light";
const colorFromProps = props[theme];
if (colorFromProps) {
return colorFromProps;
} else {
return Colors[theme][colorName];
}
}
+73
View File
@@ -0,0 +1,73 @@
{
"name": "expo-app",
"main": "expo-router/entry",
"version": "1.0.0",
"scripts": {
"start": "expo start",
"reset-project": "node ./scripts/reset-project.js",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web",
"lint": "expo lint",
"export": "expo export -p web"
},
"dependencies": {
"@expo/vector-icons": "14.0.2",
"@gorhom/bottom-sheet": "4.6.4",
"@hookform/resolvers": "3.9.0",
"@tanstack/react-query": "5.55.4",
"axios": "1.7.7",
"date-fns": "3.6.0",
"expo": "51.0.32",
"expo-checkbox": "3.0.0",
"expo-constants": "16.0.2",
"expo-font": "12.0.10",
"expo-image": "1.12.15",
"expo-image-picker": "15.0.7",
"expo-linear-gradient": "13.0.2",
"expo-linking": "6.3.1",
"expo-router": "3.5.23",
"expo-secure-store": "13.0.2",
"expo-web-browser": "13.0.3",
"expo-status-bar": "1.12.1",
"expo-updates": "0.25.24",
"react": "18.3.1",
"react-dom": "18.3.1",
"react-hook-form": "7.53.0",
"react-native": "0.75.2",
"react-native-dropdown-picker": "5.4.6",
"react-native-gesture-handler": "2.19.0",
"react-native-masked-text": "1.13.0",
"react-native-modal": "13.0.1",
"react-native-reanimated": "3.15.1",
"react-native-safe-area-context": "4.11.0",
"react-native-screens": "3.34.0",
"react-native-svg": "15.6.0",
"react-native-toast-message": "2.2.0",
"react-native-web": "0.19.12",
"styled-components": "6.1.13",
"yup": "1.4.0"
},
"devDependencies": {
"@babel/core": "7.25.2",
"@types/react": "18.3.5",
"@types/styled-components-react-native": "5.2.5",
"@typescript-eslint/eslint-plugin": "8.5.0",
"@typescript-eslint/parser": "8.5.0",
"babel-plugin-module-resolver": "5.0.2",
"eslint": "9.10.0",
"eslint-config-airbnb": "19.0.4",
"eslint-config-prettier": "9.1.0",
"eslint-import-resolver-typescript": "3.6.3",
"eslint-plugin-import": "2.30.0",
"eslint-plugin-jsx-a11y": "6.10.0",
"eslint-plugin-prefer-arrow-functions": "3.4.1",
"eslint-plugin-prettier": "5.2.1",
"eslint-plugin-react": "7.35.2",
"eslint-plugin-react-hooks": "4.6.2",
"prettier": "3.3.3",
"react-native-svg-transformer": "1.5.0",
"typescript": "5.6.2"
},
"private": true
}

Some files were not shown because too many files have changed in this diff Show More