types: Rewrite to avoid conflicts and allow for doc generation (#18024)

Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
This commit is contained in:
Alistair Smith
2025-03-25 21:33:30 +00:00
committed by GitHub
parent 90c67c4b79
commit 57381d43ed
30 changed files with 4405 additions and 3664 deletions

View File

@@ -1,7 +1,7 @@
{
"compilerOptions": {
// Enable latest features
"lib": ["ESNext", "DOM"],
"lib": ["ESNext"],
"target": "ESNext",
"module": "ESNext",
"moduleDetection": "force",

View File

@@ -1,7 +1,7 @@
{
"compilerOptions": {
// Enable latest features
"lib": ["ESNext", "DOM"],
"lib": ["ESNext"],
"target": "ESNext",
"module": "ESNext",
"moduleDetection": "force",

View File

@@ -27,9 +27,10 @@
},
"packages/bun-types": {
"name": "bun-types",
"version": "1.2.5",
"dependencies": {
"@types/node": "*",
"@types/ws": "~8.5.10",
"@types/ws": "*",
},
"devDependencies": {
"@biomejs/biome": "^1.5.3",

View File

@@ -15,8 +15,8 @@ Below is the full set of recommended `compilerOptions` for a Bun project. With t
```jsonc
{
"compilerOptions": {
// Enable latest features
"lib": ["ESNext", "DOM"],
// Environment setup & latest features
"lib": ["ESNext"],
"target": "ESNext",
"module": "ESNext",
"moduleDetection": "force",
@@ -33,11 +33,12 @@ Below is the full set of recommended `compilerOptions` for a Bun project. With t
"strict": true,
"skipLibCheck": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
// Some stricter flags
"noUnusedLocals": true,
"noUnusedParameters": true,
"noPropertyAccessFromIndexSignature": true,
// Some stricter flags (disabled by default)
"noUnusedLocals": false,
"noUnusedParameters": false,
"noPropertyAccessFromIndexSignature": false,
},
}
```

View File

@@ -17,7 +17,7 @@ Bun supports things like top-level await, JSX, and extensioned `.ts` imports, wh
```jsonc
{
"compilerOptions": {
// Enable latest features
// Environment setup & latest features
"lib": ["ESNext"],
"target": "ESNext",
"module": "ESNext",
@@ -35,12 +35,13 @@ Bun supports things like top-level await, JSX, and extensioned `.ts` imports, wh
"strict": true,
"skipLibCheck": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
// Some stricter flags
"noUnusedLocals": true,
"noUnusedParameters": true,
"noPropertyAccessFromIndexSignature": true
}
// Some stricter flags (disabled by default)
"noUnusedLocals": false,
"noUnusedParameters": false,
"noPropertyAccessFromIndexSignature": false,
},
}
```

View File

@@ -20,7 +20,7 @@ That's it! VS Code and TypeScript automatically load `@types/*` packages into yo
# Contributing
The `@types/bun` package is a shim that loads `bun-types`. The `bun-types` package lives in the Bun repo under `packages/bun-types`. It is generated via [./scripts/bundle.ts](./scripts/bundle.ts).
The `@types/bun` package is a shim that loads `bun-types`. The `bun-types` package lives in the Bun repo under `packages/bun-types`.
To add a new file, add it under `packages/bun-types`. Then add a [triple-slash directive](https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html) pointing to it inside [./index.d.ts](./index.d.ts).
@@ -28,8 +28,6 @@ To add a new file, add it under `packages/bun-types`. Then add a [triple-slash d
+ /// <reference path="./newfile.d.ts" />
```
[`./bundle.ts`](./bundle.ts) merges the types in this folder into a single file. To run it:
```bash
bun build
```

View File

@@ -0,0 +1,114 @@
# Authoring @types/bun
These declarations define the `'bun'` module, the `Bun` global variable, and lots of other global declarations like extending the `fetch` interface.
## The `'bun'` Module
The `Bun` global variable and the `'bun'` module types are defined with one syntax. It supports declaring both types/interfaces and runtime values:
```typescript
declare module "bun" {
// Your types go here
interface MyInterface {
// ...
}
type MyType = string | number;
function myFunction(): void;
}
```
You can write as many `declare module "bun"` declarations as you need. Symbols will be accessible from other files inside of the declaration, and they will all be merged when the types are evaluated.
You can consume these declarations in two ways:
1. Importing it from `'bun'`:
```typescript
import { type MyInterface, type MyType, myFunction } from "bun";
const myInterface: MyInterface = {};
const myType: MyType = "cool";
myFunction();
```
2. Using the global `Bun` object:
```typescript
const myInterface: Bun.MyInterface = {};
const myType: Bun.MyType = "cool";
Bun.myFunction();
```
Consuming them inside the ambient declarations is also easy:
```ts
// These are equivalent
type A = import("bun").MyType;
type A = Bun.MyType;
```
## File Structure
Types are organized across multiple `.d.ts` files in the `packages/bun-types` directory:
- `index.d.ts` - The main entry point that references all other type files
- `bun.d.ts` - Core Bun APIs and types
- `globals.d.ts` - Global type declarations
- `test.d.ts` - Testing-related types
- `sqlite.d.ts` - SQLite-related types
- ...etc. You can make more files
Note: The order of references in `index.d.ts` is important - `bun.ns.d.ts` must be referenced last to ensure the `Bun` global gets defined properly.
### Best Practices
1. **Type Safety**
- Please use strict types instead of `any` where possible
- Leverage TypeScript's type system features (generics, unions, etc.)
- Document complex types with JSDoc comments
2. **Compatibility**
- Use `Bun.__internal.UseLibDomIfAvailable<LibDomName extends string, OurType>` for types that might conflict with lib.dom.d.ts (see [`./fetch.d.ts`](./fetch.d.ts) for a real example)
- `@types/node` often expects variables to always be defined (this was the biggest cause of most of the conflicts in the past!), so we use the `UseLibDomIfAvailable` type to make sure we don't overwrite `lib.dom.d.ts` but still provide Bun types while simultaneously declaring the variable exists (for Node to work) in the cases that we can.
3. **Documentation**
- Add JSDoc comments for public APIs
- Include examples in documentation when helpful
- Document default values and important behaviors
### Internal Types
For internal types that shouldn't be exposed to users, use the `__internal` namespace:
```typescript
declare module "bun" {
namespace __internal {
interface MyInternalType {
// ...
}
}
}
```
The internal namespace is mostly used for declaring things that shouldn't be globally accessible on the `bun` namespace, but are still used in public apis. You can see a pretty good example of that in the [`./fetch.d.ts`](./fetch.d.ts) file.
## Testing Types
We test our type definitions using a special test file at `fixture/index.ts`. This file contains TypeScript code that exercises our type definitions, but is never actually executed - it's only used to verify that the types work correctly.
The test file is type-checked in two different environments:
1. With `lib.dom.d.ts` included - This simulates usage in a browser environment where DOM types are available
2. Without `lib.dom.d.ts` - This simulates usage in a server-like environment without DOM types
Your type definitions must work properly in both environments. This ensures that Bun's types are compatible regardless of whether DOM types are present or not.
For example, if you're adding types for a new API, you should just add code to `fixture/index.ts` that uses your new API. Doesn't need to work at runtime (e.g. you can fake api keys for example), it's just checking that the types are correct.
## Questions
Feel free to open an issue or speak to any of the more TypeScript-focused team members if you need help authoring types or fixing type tests.

File diff suppressed because it is too large Load Diff

7
packages/bun-types/bun.ns.d.ts vendored Normal file
View File

@@ -0,0 +1,7 @@
import * as BunModule from "bun";
declare global {
export import Bun = BunModule;
}
export {};

View File

@@ -1,4 +1,19 @@
declare module "bun" {
interface BunMessageEvent<T> {
/**
* @deprecated
*/
initMessageEvent(
type: string,
bubbles?: boolean,
cancelable?: boolean,
data?: any,
origin?: string,
lastEventId?: string,
source?: null,
): void;
}
/**
* @deprecated Renamed to `ErrorLike`
*/
@@ -38,21 +53,6 @@ declare namespace NodeJS {
}
}
declare namespace Bun {
interface MessageEvent {
/** @deprecated */
initMessageEvent(
type: string,
bubbles?: boolean,
cancelable?: boolean,
data?: any,
origin?: string,
lastEventId?: string,
source?: null,
): void;
}
}
interface CustomEvent<T = any> {
/** @deprecated */
initCustomEvent(type: string, bubbles?: boolean, cancelable?: boolean, detail?: T): void;

View File

@@ -1,17 +1,13 @@
export {};
declare global {
namespace Bun {
declare module "bun" {
type HMREventNames =
| "bun:ready"
| "bun:beforeUpdate"
| "bun:afterUpdate"
| "bun:beforeFullReload"
| "bun:beforePrune"
| "bun:invalidate"
| "bun:error"
| "bun:ws:disconnect"
| "bun:ws:connect";
| "beforeUpdate"
| "afterUpdate"
| "beforeFullReload"
| "beforePrune"
| "invalidate"
| "error"
| "ws:disconnect"
| "ws:connect";
/**
* The event names for the dev server
@@ -50,6 +46,8 @@ declare global {
*
* In production, `data` is inlined to be `{}`. This is handy because Bun
* knows it can minify `{}.prop ??= value` into `value` in production.
*
*
*/
data: any;
@@ -189,4 +187,3 @@ declare global {
off(event: Bun.HMREvent, callback: () => void): void;
};
}
}

View File

@@ -19,13 +19,13 @@ declare module "*/bun.lock" {
}
declare module "*.html" {
// In Bun v1.2, we might change this to Bun.HTMLBundle
// In Bun v1.2, this might change to Bun.HTMLBundle
var contents: any;
export = contents;
}
declare module "*.svg" {
// Bun 1.2.3 added support for frontend dev server
var contents: `${string}.svg`;
export = contents;
var content: `${string}.svg`;
export = content;
}

View File

@@ -1,4 +1,30 @@
interface Headers {
/*
This file does not declare any global types.
That should only happen in [./globals.d.ts](./globals.d.ts)
so that our documentation generator can pick it up, as it
expects all globals to be declared in one file.
*/
declare module "bun" {
type HeadersInit = string[][] | Record<string, string | ReadonlyArray<string>> | Headers;
type BodyInit = ReadableStream | Bun.XMLHttpRequestBodyInit | URLSearchParams | AsyncGenerator<Uint8Array>;
namespace __internal {
type LibOrFallbackHeaders = LibDomIsLoaded extends true ? {} : import("undici-types").Headers;
type LibOrFallbackRequest = LibDomIsLoaded extends true ? {} : import("undici-types").Request;
type LibOrFallbackResponse = LibDomIsLoaded extends true ? {} : import("undici-types").Response;
type LibOrFallbackResponseInit = LibDomIsLoaded extends true ? {} : import("undici-types").ResponseInit;
type LibOrFallbackRequestInit = LibDomIsLoaded extends true
? {}
: Omit<import("undici-types").RequestInit, "body" | "headers"> & {
body?: Bun.BodyInit | null | undefined;
headers?: Bun.HeadersInit;
};
interface BunHeadersOverride extends LibOrFallbackHeaders {
/**
* Convert {@link Headers} to a plain JavaScript object.
*
@@ -9,10 +35,12 @@ interface Headers {
* Does not preserve insertion order. Well-known header names are lowercased. Other header names are left as-is.
*/
toJSON(): Record<string, string>;
/**
* Get the total number of headers
*/
readonly count: number;
/**
* Get all headers matching the name
*
@@ -33,129 +61,12 @@ interface Headers {
getAll(name: "set-cookie" | "Set-Cookie"): string[];
}
var Headers: {
prototype: Headers;
new (init?: Bun.HeadersInit): Headers;
};
interface Request {
headers: Headers;
interface BunRequestOverride extends LibOrFallbackRequest {
headers: BunHeadersOverride;
}
var Request: {
prototype: Request;
new (requestInfo: string, requestInit?: RequestInit): Request;
new (requestInfo: RequestInit & { url: string }): Request;
new (requestInfo: Request, requestInit?: RequestInit): Request;
};
var Response: {
new (body?: Bun.BodyInit | null | undefined, init?: Bun.ResponseInit | undefined): Response;
/**
* Create a new {@link Response} with a JSON body
*
* @param body - The body of the response
* @param options - options to pass to the response
*
* @example
*
* ```ts
* const response = Response.json({hi: "there"});
* console.assert(
* await response.text(),
* `{"hi":"there"}`
* );
* ```
* -------
*
* This is syntactic sugar for:
* ```js
* new Response(JSON.stringify(body), {headers: { "Content-Type": "application/json" }})
* ```
* @link https://github.com/whatwg/fetch/issues/1389
*/
json(body?: any, options?: Bun.ResponseInit | number): Response;
/**
* Create a new {@link Response} that redirects to url
*
* @param url - the URL to redirect to
* @param status - the HTTP status code to use for the redirect
*/
// tslint:disable-next-line:unified-signatures
redirect(url: string, status?: number): Response;
/**
* Create a new {@link Response} that redirects to url
*
* @param url - the URL to redirect to
* @param options - options to pass to the response
*/
// tslint:disable-next-line:unified-signatures
redirect(url: string, options?: Bun.ResponseInit): Response;
/**
* Create a new {@link Response} that has a network error
*/
error(): Response;
};
type _BunTLSOptions = import("bun").TLSOptions;
interface BunFetchRequestInitTLS extends _BunTLSOptions {
/**
* Custom function to check the server identity
* @param hostname - The hostname of the server
* @param cert - The certificate of the server
* @returns An error if the server is unauthorized, otherwise undefined
*/
checkServerIdentity?: NonNullable<import("node:tls").ConnectionOptions["checkServerIdentity"]>;
interface BunResponseOverride extends LibOrFallbackResponse {
headers: BunHeadersOverride;
}
}
/**
* BunFetchRequestInit represents additional options that Bun supports in `fetch()` only.
*
* Bun extends the `fetch` API with some additional options, except
* this interface is not quite a `RequestInit`, because they won't work
* if passed to `new Request()`. This is why it's a separate type.
*/
interface BunFetchRequestInit extends RequestInit {
/**
* Override the default TLS options
*/
tls?: BunFetchRequestInitTLS;
}
var fetch: {
/**
* Send a HTTP(s) request
*
* @param request Request object
* @param init A structured value that contains settings for the fetch() request.
*
* @returns A promise that resolves to {@link Response} object.
*/
(request: Request, init?: BunFetchRequestInit): Promise<Response>;
/**
* Send a HTTP(s) request
*
* @param url URL string
* @param init A structured value that contains settings for the fetch() request.
*
* @returns A promise that resolves to {@link Response} object.
*/
(url: string | URL | Request, init?: BunFetchRequestInit): Promise<Response>;
(input: string | URL | globalThis.Request, init?: BunFetchRequestInit): Promise<Response>;
/**
* Start the DNS resolution, TCP connection, and TLS handshake for a request
* before the request is actually sent.
*
* This can reduce the latency of a request when you know there's some
* long-running task that will delay the request starting.
*
* This is a bun-specific API and is not part of the Fetch API specification.
*/
preconnect(url: string | URL): void;
};

View File

@@ -543,14 +543,6 @@ declare module "bun:ffi" {
type Symbols = Readonly<Record<string, FFIFunction>>;
// /**
// * Compile a callback function
// *
// * Returns a function pointer
// *
// */
// export function callback(ffi: FFIFunction, cb: Function): number;
interface Library<Fns extends Symbols> {
symbols: ConvertFns<Fns>;

File diff suppressed because it is too large Load Diff

View File

@@ -1,24 +1,26 @@
// Project: https://github.com/oven-sh/bun
// Definitions by: Jarred Sumner <https://github.com/Jarred-Sumner>
// Definitions by: Bun Contributors <https://github.com/oven-sh/bun/graphs/contributors>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
/// <reference lib="esnext" />
/// <reference types="ws" />
/// <reference types="node" />
// contributors: uncomment this to detect conflicts with lib.dom.d.ts
// /// <reference lib="dom" />
/// <reference path="./bun.d.ts" />
/// <reference path="./globals.d.ts" />
/// <reference path="./s3.d.ts" />
/// <reference path="./fetch.d.ts" />
/// <reference path="./overrides.d.ts" />
/// <reference path="./bun.d.ts" />
/// <reference path="./extensions.d.ts" />
/// <reference path="./devserver.d.ts" />
/// <reference path="./ffi.d.ts" />
/// <reference path="./test.d.ts" />
/// <reference path="./html-rewriter.d.ts" />
/// <reference path="./jsc.d.ts" />
/// <reference path="./sqlite.d.ts" />
/// <reference path="./test.d.ts" />
/// <reference path="./wasm.d.ts" />
/// <reference path="./overrides.d.ts" />
/// <reference path="./deprecated.d.ts" />
/// <reference path="./ambient.d.ts" />
/// <reference path="./devserver.d.ts" />
/// <reference path="./bun.ns.d.ts" />
// @ts-ignore Must disable this so it doesn't conflict with the DOM onmessage type, but still
// allows us to declare our own globals that Node's types can "see" and not conflict with
declare var onmessage: never;

View File

@@ -1,18 +1,71 @@
export {};
import type { BunFile, Env, PathLike } from "bun";
declare global {
namespace NodeJS {
interface Process {
readonly version: string;
browser: boolean;
/**
* Whether you are using Bun
*/
isBun: true;
/**
* The current git sha of Bun
*/
revision: string;
reallyExit(code?: number): never;
dlopen(module: { exports: any }, filename: string, flags?: number): void;
_exiting: boolean;
noDeprecation: boolean;
binding(m: "constants"): {
os: typeof import("node:os").constants;
fs: typeof import("node:fs").constants;
crypto: typeof import("node:crypto").constants;
zlib: typeof import("node:zlib").constants;
trace: {
TRACE_EVENT_PHASE_BEGIN: number;
TRACE_EVENT_PHASE_END: number;
TRACE_EVENT_PHASE_COMPLETE: number;
TRACE_EVENT_PHASE_INSTANT: number;
TRACE_EVENT_PHASE_ASYNC_BEGIN: number;
TRACE_EVENT_PHASE_ASYNC_STEP_INTO: number;
TRACE_EVENT_PHASE_ASYNC_STEP_PAST: number;
TRACE_EVENT_PHASE_ASYNC_END: number;
TRACE_EVENT_PHASE_NESTABLE_ASYNC_BEGIN: number;
TRACE_EVENT_PHASE_NESTABLE_ASYNC_END: number;
TRACE_EVENT_PHASE_NESTABLE_ASYNC_INSTANT: number;
TRACE_EVENT_PHASE_FLOW_BEGIN: number;
TRACE_EVENT_PHASE_FLOW_STEP: number;
TRACE_EVENT_PHASE_FLOW_END: number;
TRACE_EVENT_PHASE_METADATA: number;
TRACE_EVENT_PHASE_COUNTER: number;
TRACE_EVENT_PHASE_SAMPLE: number;
TRACE_EVENT_PHASE_CREATE_OBJECT: number;
TRACE_EVENT_PHASE_SNAPSHOT_OBJECT: number;
TRACE_EVENT_PHASE_DELETE_OBJECT: number;
TRACE_EVENT_PHASE_MEMORY_DUMP: number;
TRACE_EVENT_PHASE_MARK: number;
TRACE_EVENT_PHASE_CLOCK_SYNC: number;
TRACE_EVENT_PHASE_ENTER_CONTEXT: number;
TRACE_EVENT_PHASE_LEAVE_CONTEXT: number;
TRACE_EVENT_PHASE_LINK_IDS: number;
};
};
binding(m: string): object;
}
interface ProcessVersions extends Dict<string> {
bun: string;
}
interface ProcessEnv extends Env {}
}
}
declare module "fs/promises" {
function exists(path: PathLike): Promise<boolean>;
function exists(path: Bun.PathLike): Promise<boolean>;
}
declare module "tls" {
@@ -22,7 +75,7 @@ declare module "tls" {
* the well-known CAs curated by Mozilla. Mozilla's CAs are completely
* replaced when CAs are explicitly specified using this option.
*/
ca?: string | Buffer | NodeJS.TypedArray | BunFile | Array<string | Buffer | BunFile> | undefined;
ca?: string | Buffer | NodeJS.TypedArray | Bun.BunFile | Array<string | Buffer | Bun.BunFile> | undefined;
/**
* Cert chains in PEM format. One cert chain should be provided per
* private key. Each cert chain should consist of the PEM formatted
@@ -38,8 +91,8 @@ declare module "tls" {
| string
| Buffer
| NodeJS.TypedArray
| BunFile
| Array<string | Buffer | NodeJS.TypedArray | BunFile>
| Bun.BunFile
| Array<string | Buffer | NodeJS.TypedArray | Bun.BunFile>
| undefined;
/**
* Private keys in PEM format. PEM allows the option of private keys
@@ -54,9 +107,9 @@ declare module "tls" {
key?:
| string
| Buffer
| BunFile
| Bun.BunFile
| NodeJS.TypedArray
| Array<string | Buffer | BunFile | NodeJS.TypedArray | KeyObject>
| Array<string | Buffer | Bun.BunFile | NodeJS.TypedArray | KeyObject>
| undefined;
}

View File

@@ -1,4 +1,5 @@
{
"version": "1.2.5",
"name": "bun-types",
"license": "MIT",
"types": "./index.d.ts",
@@ -9,14 +10,14 @@
"directory": "packages/bun-types"
},
"files": [
"*.d.ts",
"./*.d.ts",
"docs/**/*.md",
"docs/*.md"
],
"homepage": "https://bun.sh",
"dependencies": {
"@types/node": "*",
"@types/ws": "~8.5.10"
"@types/ws": "*"
},
"devDependencies": {
"@biomejs/biome": "^1.5.3",
@@ -27,7 +28,7 @@
"scripts": {
"prebuild": "echo $(pwd)",
"copy-docs": "rm -rf docs && cp -rL ../../docs/ ./docs && find ./docs -type f -name '*.md' -exec sed -i 's/\\$BUN_LATEST_VERSION/'\"${BUN_VERSION#bun-v}\"'/g' {} +",
"build": "bun run copy-docs && bun scripts/build.ts && bun run fmt",
"build": "bun run copy-docs && bun scripts/build.ts",
"test": "tsc",
"fmt": "echo $(which biome) && biome format --write ."
},

825
packages/bun-types/s3.d.ts vendored Normal file
View File

@@ -0,0 +1,825 @@
declare module "bun" {
/**
* Fast incremental writer for files and pipes.
*
* This uses the same interface as {@link ArrayBufferSink}, but writes to a file or pipe.
*/
interface FileSink {
/**
* Write a chunk of data to the file.
*
* If the file descriptor is not writable yet, the data is buffered.
*/
write(chunk: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer): number;
/**
* Flush the internal buffer, committing the data to disk or the pipe.
*/
flush(): number | Promise<number>;
/**
* Close the file descriptor. This also flushes the internal buffer.
*/
end(error?: Error): number | Promise<number>;
start(options?: {
/**
* Preallocate an internal buffer of this size
* This can significantly improve performance when the chunk size is small
*/
highWaterMark?: number;
}): void;
/**
* For FIFOs & pipes, this lets you decide whether Bun's process should
* remain alive until the pipe is closed.
*
* By default, it is automatically managed. While the stream is open, the
* process remains alive and once the other end hangs up or the stream
* closes, the process exits.
*
* If you previously called {@link unref}, you can call this again to re-enable automatic management.
*
* Internally, it will reference count the number of times this is called. By default, that number is 1
*
* If the file is not a FIFO or pipe, {@link ref} and {@link unref} do
* nothing. If the pipe is already closed, this does nothing.
*/
ref(): void;
/**
* For FIFOs & pipes, this lets you decide whether Bun's process should
* remain alive until the pipe is closed.
*
* If you want to allow Bun's process to terminate while the stream is open,
* call this.
*
* If the file is not a FIFO or pipe, {@link ref} and {@link unref} do
* nothing. If the pipe is already closed, this does nothing.
*/
unref(): void;
}
interface NetworkSink extends FileSink {
/**
* Write a chunk of data to the network.
*
* If the network is not writable yet, the data is buffered.
*/
write(chunk: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer): number;
/**
* Flush the internal buffer, committing the data to the network.
*/
flush(): number | Promise<number>;
/**
* Finish the upload. This also flushes the internal buffer.
*/
end(error?: Error): number | Promise<number>;
/**
* Get the stat of the file.
*/
stat(): Promise<import("node:fs").Stats>;
}
/**
* Configuration options for S3 operations
*/
interface S3Options extends BlobPropertyBag {
/**
* The Access Control List (ACL) policy for the file.
* Controls who can access the file and what permissions they have.
*
* @example
* // Setting public read access
* const file = s3("public-file.txt", {
* acl: "public-read",
* bucket: "my-bucket"
* });
*
* @example
* // Using with presigned URLs
* const url = file.presign({
* acl: "public-read",
* expiresIn: 3600
* });
*/
acl?:
| "private"
| "public-read"
| "public-read-write"
| "aws-exec-read"
| "authenticated-read"
| "bucket-owner-read"
| "bucket-owner-full-control"
| "log-delivery-write";
/**
* The S3 bucket name. Can be set via `S3_BUCKET` or `AWS_BUCKET` environment variables.
*
* @example
* // Using explicit bucket
* const file = s3("my-file.txt", { bucket: "my-bucket" });
*
* @example
* // Using environment variables
* // With S3_BUCKET=my-bucket in .env
* const file = s3("my-file.txt");
*/
bucket?: string;
/**
* The AWS region. Can be set via `S3_REGION` or `AWS_REGION` environment variables.
*
* @example
* const file = s3("my-file.txt", {
* bucket: "my-bucket",
* region: "us-west-2"
* });
*/
region?: string;
/**
* The access key ID for authentication.
* Can be set via `S3_ACCESS_KEY_ID` or `AWS_ACCESS_KEY_ID` environment variables.
*/
accessKeyId?: string;
/**
* The secret access key for authentication.
* Can be set via `S3_SECRET_ACCESS_KEY` or `AWS_SECRET_ACCESS_KEY` environment variables.
*/
secretAccessKey?: string;
/**
* Optional session token for temporary credentials.
* Can be set via `S3_SESSION_TOKEN` or `AWS_SESSION_TOKEN` environment variables.
*
* @example
* // Using temporary credentials
* const file = s3("my-file.txt", {
* accessKeyId: tempAccessKey,
* secretAccessKey: tempSecretKey,
* sessionToken: tempSessionToken
* });
*/
sessionToken?: string;
/**
* The S3-compatible service endpoint URL.
* Can be set via `S3_ENDPOINT` or `AWS_ENDPOINT` environment variables.
*
* @example
* // AWS S3
* const file = s3("my-file.txt", {
* endpoint: "https://s3.us-east-1.amazonaws.com"
* });
*
* @example
* // Cloudflare R2
* const file = s3("my-file.txt", {
* endpoint: "https://<account-id>.r2.cloudflarestorage.com"
* });
*
* @example
* // DigitalOcean Spaces
* const file = s3("my-file.txt", {
* endpoint: "https://<region>.digitaloceanspaces.com"
* });
*
* @example
* // MinIO (local development)
* const file = s3("my-file.txt", {
* endpoint: "http://localhost:9000"
* });
*/
endpoint?: string;
/**
* Use virtual hosted style endpoint. default to false, when true if `endpoint` is informed it will ignore the `bucket`
*
* @example
* // Using virtual hosted style
* const file = s3("my-file.txt", {
* virtualHostedStyle: true,
* endpoint: "https://my-bucket.s3.us-east-1.amazonaws.com"
* });
*/
virtualHostedStyle?: boolean;
/**
* The size of each part in multipart uploads (in bytes).
* - Minimum: 5 MiB
* - Maximum: 5120 MiB
* - Default: 5 MiB
*
* @example
* // Configuring multipart uploads
* const file = s3("large-file.dat", {
* partSize: 10 * 1024 * 1024, // 10 MiB parts
* queueSize: 4 // Upload 4 parts in parallel
* });
*
* const writer = file.writer();
* // ... write large file in chunks
*/
partSize?: number;
/**
* Number of parts to upload in parallel for multipart uploads.
* - Default: 5
* - Maximum: 255
*
* Increasing this value can improve upload speeds for large files
* but will use more memory.
*/
queueSize?: number;
/**
* Number of retry attempts for failed uploads.
* - Default: 3
* - Maximum: 255
*
* @example
* // Setting retry attempts
* const file = s3("my-file.txt", {
* retry: 5 // Retry failed uploads up to 5 times
* });
*/
retry?: number;
/**
* The Content-Type of the file.
* Automatically set based on file extension when possible.
*
* @example
* // Setting explicit content type
* const file = s3("data.bin", {
* type: "application/octet-stream"
* });
*/
type?: string;
/**
* By default, Amazon S3 uses the STANDARD Storage Class to store newly created objects.
*
* @example
* // Setting explicit Storage class
* const file = s3("my-file.json", {
* storageClass: "STANDARD_IA"
* });
*/
storageClass?:
| "STANDARD"
| "DEEP_ARCHIVE"
| "EXPRESS_ONEZONE"
| "GLACIER"
| "GLACIER_IR"
| "INTELLIGENT_TIERING"
| "ONEZONE_IA"
| "OUTPOSTS"
| "REDUCED_REDUNDANCY"
| "SNOW"
| "STANDARD_IA";
/**
* @deprecated The size of the internal buffer in bytes. Defaults to 5 MiB. use `partSize` and `queueSize` instead.
*/
highWaterMark?: number;
}
/**
* Options for generating presigned URLs
*/
interface S3FilePresignOptions extends S3Options {
/**
* Number of seconds until the presigned URL expires.
* - Default: 86400 (1 day)
*
* @example
* // Short-lived URL
* const url = file.presign({
* expiresIn: 3600 // 1 hour
* });
*
* @example
* // Long-lived public URL
* const url = file.presign({
* expiresIn: 7 * 24 * 60 * 60, // 7 days
* acl: "public-read"
* });
*/
expiresIn?: number;
/**
* The HTTP method allowed for the presigned URL.
*
* @example
* // GET URL for downloads
* const downloadUrl = file.presign({
* method: "GET",
* expiresIn: 3600
* });
*
* @example
* // PUT URL for uploads
* const uploadUrl = file.presign({
* method: "PUT",
* expiresIn: 3600,
* type: "application/json"
* });
*/
method?: "GET" | "POST" | "PUT" | "DELETE" | "HEAD";
}
interface S3Stats {
size: number;
lastModified: Date;
etag: string;
type: string;
}
/**
* Represents a file in an S3-compatible storage service.
* Extends the Blob interface for compatibility with web APIs.
*/
interface S3File extends Blob {
/**
* The size of the file in bytes.
* This is a Promise because it requires a network request to determine the size.
*
* @example
* // Getting file size
* const size = await file.size;
* console.log(`File size: ${size} bytes`);
*
* @example
* // Check if file is larger than 1MB
* if (await file.size > 1024 * 1024) {
* console.log("Large file detected");
* }
*/
/**
* TODO: figure out how to get the typescript types to not error for this property.
*/
// size: Promise<number>;
/**
* Creates a new S3File representing a slice of the original file.
* Uses HTTP Range headers for efficient partial downloads.
*
* @param begin - Starting byte offset
* @param end - Ending byte offset (exclusive)
* @param contentType - Optional MIME type for the slice
* @returns A new S3File representing the specified range
*
* @example
* // Reading file header
* const header = file.slice(0, 1024);
* const headerText = await header.text();
*
* @example
* // Reading with content type
* const jsonSlice = file.slice(1024, 2048, "application/json");
* const data = await jsonSlice.json();
*
* @example
* // Reading from offset to end
* const remainder = file.slice(1024);
* const content = await remainder.text();
*/
slice(begin?: number, end?: number, contentType?: string): S3File;
slice(begin?: number, contentType?: string): S3File;
slice(contentType?: string): S3File;
/**
* Creates a writable stream for uploading data.
* Suitable for large files as it uses multipart upload.
*
* @param options - Configuration for the upload
* @returns A NetworkSink for writing data
*
* @example
* // Basic streaming write
* const writer = file.writer({
* type: "application/json"
* });
* writer.write('{"hello": ');
* writer.write('"world"}');
* await writer.end();
*
* @example
* // Optimized large file upload
* const writer = file.writer({
* partSize: 10 * 1024 * 1024, // 10MB parts
* queueSize: 4, // Upload 4 parts in parallel
* retry: 3 // Retry failed parts
* });
*
* // Write large chunks of data efficiently
* for (const chunk of largeDataChunks) {
* writer.write(chunk);
* }
* await writer.end();
*
* @example
* // Error handling
* const writer = file.writer();
* try {
* writer.write(data);
* await writer.end();
* } catch (err) {
* console.error('Upload failed:', err);
* // Writer will automatically abort multipart upload on error
* }
*/
writer(options?: S3Options): NetworkSink;
/**
* Gets a readable stream of the file's content.
* Useful for processing large files without loading them entirely into memory.
*
* @returns A ReadableStream for the file content
*
* @example
* // Basic streaming read
* const stream = file.stream();
* for await (const chunk of stream) {
* console.log('Received chunk:', chunk);
* }
*
* @example
* // Piping to response
* const stream = file.stream();
* return new Response(stream, {
* headers: { 'Content-Type': file.type }
* });
*
* @example
* // Processing large files
* const stream = file.stream();
* const textDecoder = new TextDecoder();
* for await (const chunk of stream) {
* const text = textDecoder.decode(chunk);
* // Process text chunk by chunk
* }
*/
readonly readable: ReadableStream;
stream(): ReadableStream;
/**
* The name or path of the file in the bucket.
*
* @example
* const file = s3("folder/image.jpg");
* console.log(file.name); // "folder/image.jpg"
*/
readonly name?: string;
/**
* The bucket name containing the file.
*
* @example
* const file = s3("s3://my-bucket/file.txt");
* console.log(file.bucket); // "my-bucket"
*/
readonly bucket?: string;
/**
* Checks if the file exists in S3.
* Uses HTTP HEAD request to efficiently check existence without downloading.
*
* @returns Promise resolving to true if file exists, false otherwise
*
* @example
* // Basic existence check
* if (await file.exists()) {
* console.log("File exists in S3");
* }
*
* @example
* // With error handling
* try {
* const exists = await file.exists();
* if (!exists) {
* console.log("File not found");
* }
* } catch (err) {
* console.error("Error checking file:", err);
* }
*/
exists(): Promise<boolean>;
/**
* Uploads data to S3.
* Supports various input types and automatically handles large files.
*
* @param data - The data to upload
* @param options - Upload configuration options
* @returns Promise resolving to number of bytes written
*
* @example
* // Writing string data
* await file.write("Hello World", {
* type: "text/plain"
* });
*
* @example
* // Writing JSON
* const data = { hello: "world" };
* await file.write(JSON.stringify(data), {
* type: "application/json"
* });
*
* @example
* // Writing from Response
* const response = await fetch("https://example.com/data");
* await file.write(response);
*
* @example
* // Writing with ACL
* await file.write(data, {
* acl: "public-read",
* type: "application/octet-stream"
* });
*/
write(
data: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer | Request | Response | BunFile | S3File | Blob,
options?: S3Options,
): Promise<number>;
/**
* Generates a presigned URL for the file.
* Allows temporary access to the file without exposing credentials.
*
* @param options - Configuration for the presigned URL
* @returns Presigned URL string
*
* @example
* // Basic download URL
* const url = file.presign({
* expiresIn: 3600 // 1 hour
* });
*
* @example
* // Upload URL with specific content type
* const uploadUrl = file.presign({
* method: "PUT",
* expiresIn: 3600,
* type: "image/jpeg",
* acl: "public-read"
* });
*
* @example
* // URL with custom permissions
* const url = file.presign({
* method: "GET",
* expiresIn: 7 * 24 * 60 * 60, // 7 days
* acl: "public-read"
* });
*/
presign(options?: S3FilePresignOptions): string;
/**
* Deletes the file from S3.
*
* @returns Promise that resolves when deletion is complete
*
* @example
* // Basic deletion
* await file.delete();
*
* @example
* // With error handling
* try {
* await file.delete();
* console.log("File deleted successfully");
* } catch (err) {
* console.error("Failed to delete file:", err);
* }
*/
delete(): Promise<void>;
/**
* Alias for delete() method.
* Provided for compatibility with Node.js fs API naming.
*
* @example
* await file.unlink();
*/
unlink: S3File["delete"];
/**
* Get the stat of a file in an S3-compatible storage service.
*
* @returns Promise resolving to S3Stat
*/
stat(): Promise<S3Stats>;
}
/**
* A configured S3 bucket instance for managing files.
* The instance is callable to create S3File instances and provides methods
* for common operations.
*
* @example
* // Basic bucket setup
* const bucket = new S3Client({
* bucket: "my-bucket",
* accessKeyId: "key",
* secretAccessKey: "secret"
* });
*
* // Get file instance
* const file = bucket("image.jpg");
*
* // Common operations
* await bucket.write("data.json", JSON.stringify({hello: "world"}));
* const url = bucket.presign("file.pdf");
* await bucket.unlink("old.txt");
*/
class S3Client {
prototype: S3Client;
/**
* Create a new instance of an S3 bucket so that credentials can be managed
* from a single instance instead of being passed to every method.
*
* @param options The default options to use for the S3 client. Can be
* overriden by passing options to the methods.
*
* ## Keep S3 credentials in a single instance
*
* @example
* const bucket = new Bun.S3Client({
* accessKeyId: "your-access-key",
* secretAccessKey: "your-secret-key",
* bucket: "my-bucket",
* endpoint: "https://s3.us-east-1.amazonaws.com",
* sessionToken: "your-session-token",
* });
*
* // S3Client is callable, so you can do this:
* const file = bucket.file("my-file.txt");
*
* // or this:
* await file.write("Hello Bun!");
* await file.text();
*
* // To delete the file:
* await bucket.delete("my-file.txt");
*
* // To write a file without returning the instance:
* await bucket.write("my-file.txt", "Hello Bun!");
*
*/
constructor(options?: S3Options);
/**
* Creates an S3File instance for the given path.
*
* @example
* const file = bucket.file("image.jpg");
* await file.write(imageData);
* const configFile = bucket("config.json", {
* type: "application/json",
* acl: "private"
* });
*/
file(path: string, options?: S3Options): S3File;
/**
* Writes data directly to a path in the bucket.
* Supports strings, buffers, streams, and web API types.
*
* @example
* // Write string
* await bucket.write("hello.txt", "Hello World");
*
* // Write JSON with type
* await bucket.write(
* "data.json",
* JSON.stringify({hello: "world"}),
* {type: "application/json"}
* );
*
* // Write from fetch
* const res = await fetch("https://example.com/data");
* await bucket.write("data.bin", res);
*
* // Write with ACL
* await bucket.write("public.html", html, {
* acl: "public-read",
* type: "text/html"
* });
*/
write(
path: string,
data:
| string
| ArrayBufferView
| ArrayBuffer
| SharedArrayBuffer
| Request
| Response
| BunFile
| S3File
| Blob
| File,
options?: S3Options,
): Promise<number>;
/**
* Generate a presigned URL for temporary access to a file.
* Useful for generating upload/download URLs without exposing credentials.
*
* @example
* // Download URL
* const downloadUrl = bucket.presign("file.pdf", {
* expiresIn: 3600 // 1 hour
* });
*
* // Upload URL
* const uploadUrl = bucket.presign("uploads/image.jpg", {
* method: "PUT",
* expiresIn: 3600,
* type: "image/jpeg",
* acl: "public-read"
* });
*
* // Long-lived public URL
* const publicUrl = bucket.presign("public/doc.pdf", {
* expiresIn: 7 * 24 * 60 * 60, // 7 days
* acl: "public-read"
* });
*/
presign(path: string, options?: S3FilePresignOptions): string;
/**
* Delete a file from the bucket.
*
* @example
* // Simple delete
* await bucket.unlink("old-file.txt");
*
* // With error handling
* try {
* await bucket.unlink("file.dat");
* console.log("File deleted");
* } catch (err) {
* console.error("Delete failed:", err);
* }
*/
unlink(path: string, options?: S3Options): Promise<void>;
delete: S3Client["unlink"];
/**
* Get the size of a file in bytes.
* Uses HEAD request to efficiently get size.
*
* @example
* // Get size
* const bytes = await bucket.size("video.mp4");
* console.log(`Size: ${bytes} bytes`);
*
* // Check if file is large
* if (await bucket.size("data.zip") > 100 * 1024 * 1024) {
* console.log("File is larger than 100MB");
* }
*/
size(path: string, options?: S3Options): Promise<number>;
/**
* Check if a file exists in the bucket.
* Uses HEAD request to check existence.
*
* @example
* // Check existence
* if (await bucket.exists("config.json")) {
* const file = bucket("config.json");
* const config = await file.json();
* }
*
* // With error handling
* try {
* if (!await bucket.exists("required.txt")) {
* throw new Error("Required file missing");
* }
* } catch (err) {
* console.error("Check failed:", err);
* }
*/
exists(path: string, options?: S3Options): Promise<boolean>;
/**
* Get the stat of a file in an S3-compatible storage service.
*
* @param path The path to the file.
* @param options The options to use for the S3 client.
*/
stat(path: string, options?: S3Options): Promise<S3Stats>;
}
/**
* A default instance of S3Client
*
* Pulls credentials from environment variables. Use `new Bun.S3Client()` if you need to explicitly set credentials.
*/
var s3: S3Client;
}

View File

@@ -149,6 +149,10 @@ declare module "bun:test" {
methodOrPropertyValue: K,
): Mock<T[K] extends (...args: any[]) => any ? T[K] : never>;
interface FunctionLike {
readonly name: string;
}
/**
* Describes a group of related tests.
*
@@ -165,10 +169,6 @@ declare module "bun:test" {
* @param label the label for the tests
* @param fn the function that defines the tests
*/
interface FunctionLike {
readonly name: string;
}
export interface Describe {
(fn: () => void): void;
@@ -420,7 +420,6 @@ declare module "bun:test" {
*
* @param label the label for the test
* @param fn the test function
* @param options the test timeout or options
*/
failing(label: string, fn?: (() => void | Promise<unknown>) | ((done: (err?: unknown) => void) => void)): void;
/**
@@ -1778,10 +1777,6 @@ declare module "bun:test" {
type MatcherContext = MatcherUtils & MatcherState;
}
declare module "test" {
export type * from "bun:test";
}
declare namespace JestMock {
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.

View File

@@ -1,67 +1,7 @@
export {};
type _Global<T extends Bun.WebAssembly.ValueType = Bun.WebAssembly.ValueType> = typeof globalThis extends {
onerror: any;
WebAssembly: { Global: infer T };
}
? T
: Bun.WebAssembly.Global<T>;
type _CompileError = typeof globalThis extends {
onerror: any;
WebAssembly: { CompileError: infer T };
}
? T
: Bun.WebAssembly.CompileError;
type _LinkError = typeof globalThis extends {
onerror: any;
WebAssembly: { LinkError: infer T };
}
? T
: Bun.WebAssembly.LinkError;
type _RuntimeError = typeof globalThis extends {
onerror: any;
WebAssembly: { RuntimeError: infer T };
}
? T
: Bun.WebAssembly.RuntimeError;
type _Memory = typeof globalThis extends {
onerror: any;
WebAssembly: { Memory: infer T };
}
? T
: Bun.WebAssembly.Memory;
type _Instance = typeof globalThis extends {
onerror: any;
WebAssembly: { Instance: infer T };
}
? T
: Bun.WebAssembly.Instance;
type _Module = typeof globalThis extends {
onerror: any;
WebAssembly: { Module: infer T };
}
? T
: Bun.WebAssembly.Module;
type _Table = typeof globalThis extends {
onerror: any;
WebAssembly: { Table: infer T };
}
? T
: Bun.WebAssembly.Table;
declare global {
namespace Bun {
declare module "bun" {
namespace WebAssembly {
type ImportExportKind = "function" | "global" | "memory" | "table";
type TableKind = "anyfunc" | "externref";
// eslint-disable-next-line @typescript-eslint/ban-types
type ExportValue = Function | Global | WebAssembly.Memory | WebAssembly.Table;
type Exports = Record<string, ExportValue>;
type ImportValue = ExportValue | number;
@@ -69,7 +9,6 @@ declare global {
type ModuleImports = Record<string, ImportValue>;
interface ValueTypeMap {
// eslint-disable-next-line @typescript-eslint/ban-types
anyfunc: Function;
externref: any;
f32: number;
@@ -159,7 +98,7 @@ declare global {
}
}
namespace WebAssembly {
declare namespace WebAssembly {
interface ValueTypeMap extends Bun.WebAssembly.ValueTypeMap {}
interface GlobalDescriptor<T extends keyof ValueTypeMap = keyof ValueTypeMap>
extends Bun.WebAssembly.GlobalDescriptor<T> {}
@@ -169,39 +108,29 @@ declare global {
interface TableDescriptor extends Bun.WebAssembly.TableDescriptor {}
interface WebAssemblyInstantiatedSource extends Bun.WebAssembly.WebAssemblyInstantiatedSource {}
interface LinkError extends _LinkError {}
interface LinkError extends Bun.WebAssembly.LinkError {}
var LinkError: {
prototype: LinkError;
new (message?: string): LinkError;
(message?: string): LinkError;
};
interface CompileError extends _CompileError {}
var CompileError: typeof globalThis extends {
onerror: any;
WebAssembly: { CompileError: infer T };
}
? T
: {
interface CompileError extends Bun.WebAssembly.CompileError {}
var CompileError: {
prototype: CompileError;
new (message?: string): CompileError;
(message?: string): CompileError;
};
interface RuntimeError extends _RuntimeError {}
interface RuntimeError extends Bun.WebAssembly.RuntimeError {}
var RuntimeError: {
prototype: RuntimeError;
new (message?: string): RuntimeError;
(message?: string): RuntimeError;
};
interface Global<T extends keyof ValueTypeMap = keyof ValueTypeMap> extends _Global<T> {}
var Global: typeof globalThis extends {
onerror: any;
WebAssembly: { Global: infer T };
}
? T
: {
interface Global<T extends keyof ValueTypeMap = keyof ValueTypeMap> extends Bun.WebAssembly.Global<T> {}
var Global: {
prototype: Global;
new <T extends Bun.WebAssembly.ValueType = Bun.WebAssembly.ValueType>(
descriptor: GlobalDescriptor<T>,
@@ -209,30 +138,23 @@ declare global {
): Global<T>;
};
interface Instance extends _Instance {}
var Instance: typeof globalThis extends {
onerror: any;
WebAssembly: { Instance: infer T };
}
? T
: {
interface Instance extends Bun.WebAssembly.Instance {}
var Instance: {
prototype: Instance;
new (module: Module, importObject?: Bun.WebAssembly.Imports): Instance;
};
interface Memory extends _Memory {}
interface Memory extends Bun.WebAssembly.Memory {}
var Memory: {
prototype: Memory;
new (descriptor: MemoryDescriptor): Memory;
};
interface Module extends _Module {}
var Module: typeof globalThis extends {
onerror: any;
WebAssembly: { Module: infer T };
}
? T
: {
interface Module extends Bun.WebAssembly.Module {}
var Module: Bun.__internal.UseLibDomIfAvailable<
"WebAssembly",
{
Module: {
prototype: Module;
new (bytes: Bun.BufferSource): Module;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Module/customSections) */
@@ -242,8 +164,10 @@ declare global {
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Module/imports) */
imports(moduleObject: Module): ModuleImportDescriptor[];
};
}
>["Module"];
interface Table extends _Table {}
interface Table extends Bun.WebAssembly.Table {}
var Table: {
prototype: Table;
new (descriptor: TableDescriptor, value?: any): Table;
@@ -267,4 +191,3 @@ declare global {
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/validate) */
function validate(bytes: Bun.BufferSource): boolean;
}
}

View File

@@ -1,7 +1,7 @@
{
"compilerOptions": {
// Environment setup & latest features
"lib": ["esnext"],
"lib": ["ESNext"],
"target": "ESNext",
"module": "ESNext",
"moduleDetection": "force",

View File

@@ -71,6 +71,7 @@ export function createBunShellTemplateFunction(createShellInterpreter, createPar
stdout: Buffer;
stderr: Buffer;
exitCode: number;
constructor(stdout: Buffer, stderr: Buffer, exitCode: number) {
this.stdout = stdout;
this.stderr = stderr;

View File

@@ -1,18 +1,31 @@
import { fileURLToPath, $ as Shell, ShellError } from "bun";
import { beforeAll, describe, expect, setDefaultTimeout, test } from "bun:test";
import { fileURLToPath, $ as Shell } from "bun";
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
import { cp, mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
const BUN_REPO_ROOT = fileURLToPath(import.meta.resolve("../../../"));
const BUN_TYPES_PACKAGE_ROOT = join(BUN_REPO_ROOT, "packages", "bun-types");
const FIXTURE_DIR = fileURLToPath(import.meta.resolve("./fixture"));
const FIXTURE_SOURCE_DIR = fileURLToPath(import.meta.resolve("./fixture"));
const TSCONFIG_SOURCE_PATH = join(BUN_REPO_ROOT, "src/cli/init/tsconfig.default.json");
const BUN_TYPES_PACKAGE_JSON_PATH = join(BUN_TYPES_PACKAGE_ROOT, "package.json");
const BUN_VERSION = (process.env.BUN_VERSION || Bun.version || process.versions.bun).replace(/^.*v/, "");
const BUN_VERSION = (process.env.BUN_VERSION ?? Bun.version ?? process.versions.bun).replace(/^.*v/, "");
const BUN_TYPES_TARBALL_NAME = `types-bun-${BUN_VERSION}.tgz`;
const $ = Shell.cwd(BUN_REPO_ROOT);
const $ = Shell.cwd(BUN_REPO_ROOT).nothrow();
let TEMP_DIR: string;
let FIXTURE_DIR: string;
beforeAll(async () => {
TEMP_DIR = await mkdtemp(join(tmpdir(), "bun-types-test-"));
FIXTURE_DIR = join(TEMP_DIR, "fixture");
try {
await $`mkdir -p ${FIXTURE_DIR}`;
await cp(FIXTURE_SOURCE_DIR, FIXTURE_DIR, { recursive: true });
await $`
cd ${BUN_TYPES_PACKAGE_ROOT}
bun install
@@ -36,13 +49,12 @@ beforeAll(async () => {
mv package.json.backup package.json
cd ${FIXTURE_DIR}
cp ${TSCONFIG_SOURCE_PATH} tsconfig.json
bun uninstall @types/bun
bun uninstall @types/bun || true
bun add @types/bun@${BUN_TYPES_TARBALL_NAME}
rm ${BUN_TYPES_TARBALL_NAME}
`;
} catch (e) {
if (e instanceof ShellError) {
if (e instanceof Bun.$.ShellError) {
console.log(e.stderr.toString());
}
@@ -50,12 +62,23 @@ beforeAll(async () => {
}
});
beforeAll(() => {
setDefaultTimeout(1000 * 60 * 5);
beforeEach(async () => {
await $`
cd ${FIXTURE_DIR}
cp ${TSCONFIG_SOURCE_PATH} tsconfig.json
sed -i 's/"skipLibCheck": true/"skipLibCheck": false/' tsconfig.json
cat tsconfig.json
`;
});
afterAll(async () => {
if (TEMP_DIR) {
await rm(TEMP_DIR, { recursive: true, force: true });
}
});
describe("@types/bun integration test", () => {
test("it typechecks successfully", async () => {
test("checks without lib.dom.d.ts", async () => {
const p = await $`
cd ${FIXTURE_DIR}
bun run check
@@ -63,4 +86,40 @@ describe("@types/bun integration test", () => {
expect(p.exitCode).toBe(0);
});
test("checks with lib.dom.d.ts", async () => {
const tsconfig = Bun.file(join(FIXTURE_DIR, "tsconfig.json"));
await tsconfig.write(
(await tsconfig.text()).replace(
/"lib": \["ESNext"\]/,
'"lib": ["ESNext", "DOM", "DOM.Iterable", "DOM.AsyncIterable"]',
),
);
const p = await $`
cd ${FIXTURE_DIR}
bun run check
`;
const importantLines = [
`error TS2345: Argument of type 'AsyncGenerator<Uint8Array<ArrayBuffer>, void, unknown>' is not assignable to parameter of type 'BodyInit | null | undefined'.`,
"error TS2769: No overload matches this call.",
"Overload 1 of 3, '(underlyingSource: UnderlyingByteSource, strategy?: { highWaterMark?: number | undefined; } | undefined): ReadableStream<Uint8Array<ArrayBufferLike>>', gave the following error.",
`Type '"direct"' is not assignable to type '"bytes"'`,
"error TS2345: Argument of type '{ headers: { \"x-bun\": string; }; }' is not assignable to parameter of type 'number'.",
"error TS2339: Property 'write' does not exist on type 'ReadableByteStreamController'.",
];
const fullOutput = p.stdout.toString() + p.stderr.toString();
const expectedErrorCount = importantLines.filter(e => e.includes("error")).length;
const actualErrorCount = fullOutput.match(/error/g)?.length ?? 0;
expect(actualErrorCount).toBe(expectedErrorCount);
for (const line of importantLines) {
expect(fullOutput).toContain(line);
}
expect(p.exitCode).toBe(2);
});
});

View File

@@ -1,3 +0,0 @@
node_modules
# generated by test
tsconfig.json

View File

@@ -5,20 +5,18 @@
"name": "fixture",
"dependencies": {
"@types/bun": "types-bun-1.2.6.tgz",
},
"peerDependencies": {
"typescript": "^5.0.0",
"typescript": "^5.8.2",
},
},
},
"packages": {
"@types/bun": ["@types/bun@types-bun-1.2.6.tgz", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }],
"@types/bun": ["@types/bun@types-bun-1.2.6.tgz", { "dependencies": { "@types/node": "*", "@types/ws": "*" } }],
"@types/node": ["@types/node@22.13.10", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw=="],
"@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="],
"@types/ws": ["@types/ws@8.18.0", "", { "dependencies": { "@types/node": "*" } }, "sha512-8svvI3hMyvN0kKCJMvTJP/x6Y/EoQbepff882wL+Sn5QsXb3etnamgrJq4isrBxSJj5L2AuXcI0+bgkoAXGUJw=="],
"typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="],
"typescript": ["typescript@5.8.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ=="],
"undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
}

View File

@@ -1,6 +1,70 @@
import svgpath from "cool.svg";
svgpath satisfies `${string}.svg`;
import * as test from "bun:test";
test.describe;
test.it;
const options: Bun.TLSOptions = {
keyFile: "",
};
process.assert;
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");
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();
@@ -15,7 +79,24 @@ new TransformStream();
new AbortSignal();
new AbortController();
fetch("url");
new TextDecoder();
new TextEncoder();
fetch("url", {
proxy: "",
});
fetch(new URL("url"), {
proxy: "",
});
Bun.fetch(new URL("url"), {
proxy: "",
});
Bun.S3Client;
Bun.$.ShellPromise;
new Bun.$.ShellError();
@@ -56,13 +137,13 @@ Bun.serve({
},
fetch: (req, server) => {
return new Response("upgraded");
return new Response("cool");
},
});
Bun.serve({
fetch: (req, server) => {
return new Response("upgraded");
return new Response("cool");
},
});
@@ -83,6 +164,38 @@ Bun.serve({
},
});
Bun.serve({
websocket: {
message: () => {
//
},
},
fetch: (req, server) => {
if (server.upgrade(req)) {
return;
}
return new Response("not upgraded");
},
});
Bun.serve({
websocket: {
message: () => {
//
},
},
routes: {
"/ws": (req, server) => {
if (server.upgrade(req)) {
return;
}
return new Response("not upgraded");
},
},
});
new Map();
new Set();
new WeakMap();
@@ -91,6 +204,37 @@ 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);
}
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}`);
}
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("", {
@@ -100,6 +244,9 @@ const r = new Request("", {
await fetch(r);
await fetch("", {
tls: {
key: Bun.file("key.pem"),
cert: Bun.file("cert.pem"),
ca: [Bun.file("ca.pem")],
rejectUnauthorized: false,
},
});
@@ -108,6 +255,7 @@ r.method;
r.body;
r.headers.get("content-type");
new Request("", {});
new Bun.$.ShellError() instanceof Bun.$.ShellError;
await r.json();
@@ -120,6 +268,10 @@ const req1 = new Request("", {
body: "",
});
for (const header of new Headers()) {
console.log(header);
}
fetch("", {
tls: {
rejectUnauthorized: false,
@@ -142,52 +294,153 @@ req1.headers;
req1.headers.toJSON();
new ReadableStream({});
new ReadableStream({
type: "direct",
async pull(controller) {
controller.write(new TextEncoder().encode("Hello, world!"));
},
});
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"),
key: Bun.file(""),
cert: Bun.file(""),
// don't do this, use the `tls: {}` options instead
key: Bun.file(""), // dont do it!
cert: Bun.file(""), // dont do it!
tls: {
key: Bun.file(""),
cert: Bun.file(""),
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;
Response.json();
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", {
status: 200,
headers: new Headers(
(() => {
const h = new Headers();
h.set("key", "value");
h.toJSON();
return h;
})(),
),
headers: {
"x-bun": "is cool",
},
});
Bun.fetch.preconnect;
Bun.inspect.custom;
Bun.inspect;
fetch.preconnect("bun.sh");
Bun.fetch.preconnect("bun.sh");
new Uint8Array().toBase64();
Bun.fetch("", {
proxy: "",
s3: {},
s3: {
acl: "public-read",
},
});
new HTMLRewriter()
.on("script", {
element(element) {
console.log(element.getAttribute("src"));
},
})
.transform(new Blob(['<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;
@@ -202,15 +455,22 @@ Bun.serve({
},
});
import.meta.hot.accept();
import.meta.hot.data;
import { serve } from "bun";
new Worker("").on("message", (e: MessageEvent<string>) => {
e;
e.data satisfies string;
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"],

View File

@@ -1,14 +1,12 @@
{
"name": "fixture",
"module": "index.ts",
"peerDependencies": {
"typescript": "^5.0.0"
},
"scripts": {
"check": "tsc --noEmit"
"check": "tsc --noEmit -p ./tsconfig.json"
},
"type": "module",
"dependencies": {
"@types/bun": "types-bun-1.2.6.tgz"
"@types/bun": "types-bun-1.2.6.tgz",
"typescript": "latest"
}
}

View File

@@ -8,7 +8,6 @@
"emitDecoratorMetadata": true
},
"references": [
//
{ "path": "./src" },
{ "path": "./src/bake" },
{ "path": "./src/js" },