Compare commits

..

1 Commits

Author SHA1 Message Date
Zack Radisic
c78df80a1f WIP code cache 2025-03-24 13:14:08 -07:00
1389 changed files with 133781 additions and 198242 deletions

View File

@@ -1,14 +1,13 @@
---
description: JavaScript class implemented in C++
globs: *.cpp
alwaysApply: false
---
# Implementing JavaScript classes in C++
If there is a publicly accessible Constructor and Prototype, then there are 3 classes:
- IF there are C++ class members we need a destructor, so `class Foo : public JSC::DestructibleObject`, if no C++ class fields (only JS properties) then we don't need a class at all usually. We can instead use JSC::constructEmptyObject(vm, structure) and `putDirectOffset` like in [NodeFSStatBinding.cpp](mdc:src/bun.js/bindings/NodeFSStatBinding.cpp).
- IF there are C++ class members we need a destructor, so `class Foo : public JSC::DestructibleObject`, if no C++ class fields (only JS properties) then we don't need a class at all usually. We can instead use JSC::constructEmptyObject(vm, structure) and `putDirectOffset` like in [NodeFSBinding.cpp](mdc:src/bun.js/bindings/NodeFSBinding.cpp).
- class FooPrototype : public JSC::JSNonFinalObject
- class FooConstructor : public JSC::InternalFunction
@@ -19,7 +18,6 @@ If there are C++ fields on the Foo class, the Foo class will need an iso subspac
Usually you'll need to #include "root.h" at the top of C++ files or you'll get lint errors.
Generally, defining the subspace looks like this:
```c++
class Foo : public JSC::DestructibleObject {
@@ -47,7 +45,6 @@ It's better to put it in the .cpp file instead of the .h file, when possible.
## Defining properties
Define properties on the prototype. Use a const HashTableValues like this:
```C++
static JSC_DECLARE_HOST_FUNCTION(jsX509CertificateProtoFuncCheckEmail);
static JSC_DECLARE_HOST_FUNCTION(jsX509CertificateProtoFuncCheckHost);
@@ -161,7 +158,6 @@ void JSX509CertificatePrototype::finishCreation(VM& vm)
```
### Getter definition:
```C++
JSC_DEFINE_CUSTOM_GETTER(jsX509CertificateGetter_ca, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName))
@@ -216,6 +212,7 @@ JSC_DEFINE_HOST_FUNCTION(jsX509CertificateProtoFuncToJSON, (JSGlobalObject * glo
}
```
### Constructor definition
```C++
@@ -262,6 +259,7 @@ private:
};
```
### Structure caching
If there's a class, prototype, and constructor:
@@ -281,7 +279,6 @@ void GlobalObject::finishCreation(VM& vm) {
```
Then, implement the function that creates the structure:
```c++
void setupX509CertificateClassStructure(LazyClassStructure::Initializer& init)
{
@@ -304,12 +301,11 @@ If there's only a class, use `JSC::LazyProperty<JSGlobalObject, Structure>` inst
1. Add the `JSC::LazyProperty<JSGlobalObject, Structure>` to @ZigGlobalObject.h
2. Initialize the class structure in @ZigGlobalObject.cpp in `void GlobalObject::finishCreation(VM& vm)`
3. Visit the lazy property in visitChildren in @ZigGlobalObject.cpp in `void GlobalObject::visitChildrenImpl`
void GlobalObject::finishCreation(VM& vm) {
// ...
void GlobalObject::finishCreation(VM& vm) {
// ...
this.m_myLazyProperty.initLater([](const JSC::LazyProperty<JSC::JSGlobalObject, JSC::Structure>::Initializer& init) {
init.set(Bun::initMyStructure(init.vm, reinterpret_cast<Zig::GlobalObject\*>(init.owner)));
});
init.set(Bun::initMyStructure(init.vm, reinterpret_cast<Zig::GlobalObject*>(init.owner)));
});
```
Then, implement the function that creates the structure:
@@ -320,7 +316,7 @@ Structure* setupX509CertificateStructure(JSC::VM &vm, Zig::GlobalObject* globalO
auto* prototypeStructure = JSX509CertificatePrototype::createStructure(init.vm, init.global, init.global->objectPrototype());
auto* prototype = JSX509CertificatePrototype::create(init.vm, init.global, prototypeStructure);
// If there is no prototype or it only has
// If there is no prototype or it only has
auto* structure = JSX509Certificate::createStructure(init.vm, init.global, prototype);
init.setPrototype(prototype);
@@ -329,6 +325,7 @@ Structure* setupX509CertificateStructure(JSC::VM &vm, Zig::GlobalObject* globalO
}
```
Then, use the structure by calling `globalObject.m_myStructureName.get(globalObject)`
```C++
@@ -381,14 +378,12 @@ extern "C" JSC::EncodedJSValue Bun__JSBigIntStatsObjectConstructor(Zig::GlobalOb
```
Zig:
```zig
extern "c" fn Bun__JSBigIntStatsObjectConstructor(*JSC.JSGlobalObject) JSC.JSValue;
pub const getBigIntStatsConstructor = Bun__JSBigIntStatsObjectConstructor;
```
To create an object (instance) of a JS class defined in C++ from Zig, follow the \_\_toJS convention like this:
To create an object (instance) of a JS class defined in C++ from Zig, follow the __toJS convention like this:
```c++
// X509* is whatever we need to create the object
extern "C" EncodedJSValue Bun__X509__toJS(Zig::GlobalObject* globalObject, X509* cert)
@@ -400,13 +395,12 @@ extern "C" EncodedJSValue Bun__X509__toJS(Zig::GlobalObject* globalObject, X509*
```
And from Zig:
```zig
const X509 = opaque {
// ... class
// ... class
extern fn Bun__X509__toJS(*JSC.JSGlobalObject, *X509) JSC.JSValue;
pub fn toJS(this: *X509, globalObject: *JSC.JSGlobalObject) JSC.JSValue {
return Bun__X509__toJS(globalObject, this);
}

View File

@@ -1,498 +0,0 @@
---
description: How Zig works with JavaScriptCore bindings generator
globs:
alwaysApply: false
---
# Bun's JavaScriptCore Class Bindings Generator
This document explains how Bun's class bindings generator works to bridge Zig and JavaScript code through JavaScriptCore (JSC).
## Architecture Overview
Bun's binding system creates a seamless bridge between JavaScript and Zig, allowing Zig implementations to be exposed as JavaScript classes. The system has several key components:
1. **Zig Implementation** (.zig files)
2. **JavaScript Interface Definition** (.classes.ts files)
3. **Generated Code** (C++/Zig files that connect everything)
## Class Definition Files
### JavaScript Interface (.classes.ts)
The `.classes.ts` files define the JavaScript API using a declarative approach:
```typescript
// Example: encoding.classes.ts
define({
name: "TextDecoder",
constructor: true,
JSType: "object",
finalize: true,
proto: {
decode: {
// Function definition
args: 1,
},
encoding: {
// Getter with caching
getter: true,
cache: true,
},
fatal: {
// Read-only property
getter: true,
},
ignoreBOM: {
// Read-only property
getter: true,
}
}
});
```
Each class definition specifies:
- The class name
- Whether it has a constructor
- JavaScript type (object, function, etc.)
- Properties and methods in the `proto` field
- Caching strategy for properties
- Finalization requirements
### Zig Implementation (.zig)
The Zig files implement the native functionality:
```zig
// Example: TextDecoder.zig
pub const TextDecoder = struct {
// Expose generated bindings as `js` namespace with trait conversion methods
pub const js = JSC.Codegen.JSTextDecoder;
pub const toJS = js.toJS;
pub const fromJS = js.fromJS;
pub const fromJSDirect = js.fromJSDirect;
// Internal state
encoding: []const u8,
fatal: bool,
ignoreBOM: bool,
// Constructor implementation - note use of globalObject
pub fn constructor(
globalObject: *JSGlobalObject,
callFrame: *JSC.CallFrame,
) bun.JSError!*TextDecoder {
// Implementation
return bun.new(TextDecoder, .{
// Fields
});
}
// Prototype methods - note return type includes JSError
pub fn decode(
this: *TextDecoder,
globalObject: *JSGlobalObject,
callFrame: *JSC.CallFrame,
) bun.JSError!JSC.JSValue {
// Implementation
}
// Getters
pub fn getEncoding(this: *TextDecoder, globalObject: *JSGlobalObject) JSC.JSValue {
return JSC.JSValue.createStringFromUTF8(globalObject, this.encoding);
}
pub fn getFatal(this: *TextDecoder, globalObject: *JSGlobalObject) JSC.JSValue {
return JSC.JSValue.jsBoolean(this.fatal);
}
// Cleanup - note standard pattern of using deinit/deref
fn deinit(this: *TextDecoder) void {
// Release any retained resources
// Free the pointer at the end.
bun.destroy(this);
}
// Finalize - called by JS garbage collector. This should call deinit, or deref if reference counted.
pub fn finalize(this: *TextDecoder) void {
this.deinit();
}
};
```
Key components in the Zig file:
- The struct containing native state
- `pub const js = JSC.Codegen.JS<ClassName>` to include generated code
- Constructor and methods using `bun.JSError!JSValue` return type for proper error handling
- Consistent use of `globalObject` parameter name instead of `ctx`
- Methods matching the JavaScript interface
- Getters/setters for properties
- Proper resource cleanup pattern with `deinit()` and `finalize()`
## Code Generation System
The binding generator produces C++ code that connects JavaScript and Zig:
1. **JSC Class Structure**: Creates C++ classes for the JS object, prototype, and constructor
2. **Memory Management**: Handles GC integration through JSC's WriteBarrier
3. **Method Binding**: Connects JS function calls to Zig implementations
4. **Type Conversion**: Converts between JS values and Zig types
5. **Property Caching**: Implements the caching system for properties
The generated C++ code includes:
- A JSC wrapper class (`JSTextDecoder`)
- A prototype class (`JSTextDecoderPrototype`)
- A constructor function (`JSTextDecoderConstructor`)
- Function bindings (`TextDecoderPrototype__decodeCallback`)
- Property getters/setters (`TextDecoderPrototype__encodingGetterWrap`)
## CallFrame Access
The `CallFrame` object provides access to JavaScript execution context:
```zig
pub fn decode(
this: *TextDecoder,
globalObject: *JSGlobalObject,
callFrame: *JSC.CallFrame
) bun.JSError!JSC.JSValue {
// Get arguments
const input = callFrame.argument(0);
const options = callFrame.argument(1);
// Get this value
const thisValue = callFrame.thisValue();
// Implementation with error handling
if (input.isUndefinedOrNull()) {
return globalObject.throw("Input cannot be null or undefined", .{});
}
// Return value or throw error
return JSC.JSValue.jsString(globalObject, "result");
}
```
CallFrame methods include:
- `argument(i)`: Get the i-th argument
- `argumentCount()`: Get the number of arguments
- `thisValue()`: Get the `this` value
- `callee()`: Get the function being called
## Property Caching and GC-Owned Values
The `cache: true` option in property definitions enables JSC's WriteBarrier to efficiently store values:
```typescript
encoding: {
getter: true,
cache: true, // Enable caching
}
```
### C++ Implementation
In the generated C++ code, caching uses JSC's WriteBarrier:
```cpp
JSC_DEFINE_CUSTOM_GETTER(TextDecoderPrototype__encodingGetterWrap, (...)) {
auto& vm = JSC::getVM(lexicalGlobalObject);
Zig::GlobalObject *globalObject = reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject);
auto throwScope = DECLARE_THROW_SCOPE(vm);
JSTextDecoder* thisObject = jsCast<JSTextDecoder*>(JSValue::decode(encodedThisValue));
JSC::EnsureStillAliveScope thisArg = JSC::EnsureStillAliveScope(thisObject);
// Check for cached value and return if present
if (JSValue cachedValue = thisObject->m_encoding.get())
return JSValue::encode(cachedValue);
// Get value from Zig implementation
JSC::JSValue result = JSC::JSValue::decode(
TextDecoderPrototype__getEncoding(thisObject->wrapped(), globalObject)
);
RETURN_IF_EXCEPTION(throwScope, {});
// Store in cache for future access
thisObject->m_encoding.set(vm, thisObject, result);
RELEASE_AND_RETURN(throwScope, JSValue::encode(result));
}
```
### Zig Accessor Functions
For each cached property, the generator creates Zig accessor functions that allow Zig code to work with these GC-owned values:
```zig
// External function declarations
extern fn TextDecoderPrototype__encodingSetCachedValue(JSC.JSValue, *JSC.JSGlobalObject, JSC.JSValue) callconv(JSC.conv) void;
extern fn TextDecoderPrototype__encodingGetCachedValue(JSC.JSValue) callconv(JSC.conv) JSC.JSValue;
/// `TextDecoder.encoding` setter
/// This value will be visited by the garbage collector.
pub fn encodingSetCached(thisValue: JSC.JSValue, globalObject: *JSC.JSGlobalObject, value: JSC.JSValue) void {
JSC.markBinding(@src());
TextDecoderPrototype__encodingSetCachedValue(thisValue, globalObject, value);
}
/// `TextDecoder.encoding` getter
/// This value will be visited by the garbage collector.
pub fn encodingGetCached(thisValue: JSC.JSValue) ?JSC.JSValue {
JSC.markBinding(@src());
const result = TextDecoderPrototype__encodingGetCachedValue(thisValue);
if (result == .zero)
return null;
return result;
}
```
### Benefits of GC-Owned Values
This system provides several key benefits:
1. **Automatic Memory Management**: The JavaScriptCore GC tracks and manages these values
2. **Proper Garbage Collection**: The WriteBarrier ensures values are properly visited during GC
3. **Consistent Access**: Zig code can easily get/set these cached JS values
4. **Performance**: Cached values avoid repeated computation or serialization
### Use Cases
GC-owned cached values are particularly useful for:
1. **Computed Properties**: Store expensive computation results
2. **Lazily Created Objects**: Create objects only when needed, then cache them
3. **References to Other Objects**: Store references to other JS objects that need GC tracking
4. **Memoization**: Cache results based on input parameters
The WriteBarrier mechanism ensures that any JS values stored in this way are properly tracked by the garbage collector.
## Memory Management and Finalization
The binding system handles memory management across the JavaScript/Zig boundary:
1. **Object Creation**: JavaScript `new TextDecoder()` creates both a JS wrapper and a Zig struct
2. **Reference Tracking**: JSC's GC tracks all JS references to the object
3. **Finalization**: When the JS object is collected, the finalizer releases Zig resources
Bun uses a consistent pattern for resource cleanup:
```zig
// Resource cleanup method - separate from finalization
pub fn deinit(this: *TextDecoder) void {
// Release resources like strings
this._encoding.deref(); // String deref pattern
// Free any buffers
if (this.buffer) |buffer| {
bun.default_allocator.free(buffer);
}
}
// Called by the GC when object is collected
pub fn finalize(this: *TextDecoder) void {
JSC.markBinding(@src()); // For debugging
this.deinit(); // Clean up resources
bun.default_allocator.destroy(this); // Free the object itself
}
```
Some objects that hold references to other JS objects use `.deref()` instead:
```zig
pub fn finalize(this: *SocketAddress) void {
JSC.markBinding(@src());
this._presentation.deref(); // Release references
this.destroy();
}
```
## Error Handling with JSError
Bun uses `bun.JSError!JSValue` return type for proper error handling:
```zig
pub fn decode(
this: *TextDecoder,
globalObject: *JSGlobalObject,
callFrame: *JSC.CallFrame
) bun.JSError!JSC.JSValue {
// Throwing an error
if (callFrame.argumentCount() < 1) {
return globalObject.throw("Missing required argument", .{});
}
// Or returning a success value
return JSC.JSValue.jsString(globalObject, "Success!");
}
```
This pattern allows Zig functions to:
1. Return JavaScript values on success
2. Throw JavaScript exceptions on error
3. Propagate errors automatically through the call stack
## Type Safety and Error Handling
The binding system includes robust error handling:
```cpp
// Example of type checking in generated code
JSTextDecoder* thisObject = jsDynamicCast<JSTextDecoder*>(callFrame->thisValue());
if (UNLIKELY(!thisObject)) {
scope.throwException(lexicalGlobalObject,
Bun::createInvalidThisError(lexicalGlobalObject, callFrame->thisValue(), "TextDecoder"_s));
return {};
}
```
## Prototypal Inheritance
The binding system creates proper JavaScript prototype chains:
1. **Constructor**: JSTextDecoderConstructor with standard .prototype property
2. **Prototype**: JSTextDecoderPrototype with methods and properties
3. **Instances**: Each JSTextDecoder instance with __proto__ pointing to prototype
This ensures JavaScript inheritance works as expected:
```cpp
// From generated code
void JSTextDecoderConstructor::finishCreation(VM& vm, JSC::JSGlobalObject* globalObject, JSTextDecoderPrototype* prototype)
{
Base::finishCreation(vm, 0, "TextDecoder"_s, PropertyAdditionMode::WithoutStructureTransition);
// Set up the prototype chain
putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly);
ASSERT(inherits(info()));
}
```
## Performance Considerations
The binding system is optimized for performance:
1. **Direct Pointer Access**: JavaScript objects maintain a direct pointer to Zig objects
2. **Property Caching**: WriteBarrier caching avoids repeated native calls for stable properties
3. **Memory Management**: JSC garbage collection integrated with Zig memory management
4. **Type Conversion**: Fast paths for common JavaScript/Zig type conversions
## Creating a New Class Binding
To create a new class binding in Bun:
1. **Define the class interface** in a `.classes.ts` file:
```typescript
define({
name: "MyClass",
constructor: true,
finalize: true,
proto: {
myMethod: {
args: 1,
},
myProperty: {
getter: true,
cache: true,
}
}
});
```
2. **Implement the native functionality** in a `.zig` file:
```zig
pub const MyClass = struct {
// Generated bindings
pub const js = JSC.Codegen.JSMyClass;
pub const toJS = js.toJS;
pub const fromJS = js.fromJS;
pub const fromJSDirect = js.fromJSDirect;
// State
value: []const u8,
pub const new = bun.TrivialNew(@This());
// Constructor
pub fn constructor(
globalObject: *JSGlobalObject,
callFrame: *JSC.CallFrame,
) bun.JSError!*MyClass {
const arg = callFrame.argument(0);
// Implementation
}
// Method
pub fn myMethod(
this: *MyClass,
globalObject: *JSGlobalObject,
callFrame: *JSC.CallFrame,
) bun.JSError!JSC.JSValue {
// Implementation
}
// Getter
pub fn getMyProperty(this: *MyClass, globalObject: *JSGlobalObject) JSC.JSValue {
return JSC.JSValue.jsString(globalObject, this.value);
}
// Resource cleanup
pub fn deinit(this: *MyClass) void {
// Clean up resources
}
pub fn finalize(this: *MyClass) void {
this.deinit();
bun.destroy(this);
}
};
```
3. **The binding generator** creates all necessary C++ and Zig glue code to connect JavaScript and Zig, including:
- C++ class definitions
- Method and property bindings
- Memory management utilities
- GC integration code
## Generated Code Structure
The binding generator produces several components:
### 1. C++ Classes
For each Zig class, the system generates:
- **JS<Class>**: Main wrapper that holds a pointer to the Zig object (`JSTextDecoder`)
- **JS<Class>Prototype**: Contains methods and properties (`JSTextDecoderPrototype`)
- **JS<Class>Constructor**: Implementation of the JavaScript constructor (`JSTextDecoderConstructor`)
### 2. C++ Methods and Properties
- **Method Callbacks**: `TextDecoderPrototype__decodeCallback`
- **Property Getters/Setters**: `TextDecoderPrototype__encodingGetterWrap`
- **Initialization Functions**: `finishCreation` methods for setting up the class
### 3. Zig Bindings
- **External Function Declarations**:
```zig
extern fn TextDecoderPrototype__decode(*TextDecoder, *JSC.JSGlobalObject, *JSC.CallFrame) callconv(JSC.conv) JSC.EncodedJSValue;
```
- **Cached Value Accessors**:
```zig
pub fn encodingGetCached(thisValue: JSC.JSValue) ?JSC.JSValue { ... }
pub fn encodingSetCached(thisValue: JSC.JSValue, globalObject: *JSC.JSGlobalObject, value: JSC.JSValue) void { ... }
```
- **Constructor Helpers**:
```zig
pub fn create(globalObject: *JSC.JSGlobalObject) bun.JSError!JSC.JSValue { ... }
```
### 4. GC Integration
- **Memory Cost Calculation**: `estimatedSize` method
- **Child Visitor Methods**: `visitChildrenImpl` and `visitAdditionalChildren`
- **Heap Analysis**: `analyzeHeap` for debugging memory issues
This architecture makes it possible to implement high-performance native functionality in Zig while exposing a clean, idiomatic JavaScript API to users.

View File

@@ -1,8 +0,0 @@
# Add commits to ignore in `git blame`. This allows large stylistic refactors to
# avoid mucking up blames.
#
# To configure git to use this, run:
#
# git config blame.ignoreRevsFile .git-blame-ignore-revs
#
4ec410e0d7c5f6a712c323444edbf56b48d432d8 # make @import("bun") work in zig (#19096)

View File

@@ -28,7 +28,7 @@ This adds a new flag --bail to bun test. When set, it will stop running tests af
- [ ] I checked the lifetime of memory allocated to verify it's (1) freed and (2) only freed when it should be
- [ ] I included a test for the new code, or an existing test covers it
- [ ] JSValue used outside of the stack is either wrapped in a JSC.Strong or is JSValueProtect'ed
- [ ] JSValue used outside outside of the stack is either wrapped in a JSC.Strong or is JSValueProtect'ed
- [ ] I wrote TypeScript/JavaScript tests and they pass locally (`bun-debug test test-file-name.test`)
-->

View File

@@ -4,7 +4,6 @@ on:
push:
paths:
- "docs/**"
- "packages/bun-types/**.d.ts"
- "CONTRIBUTING.md"
branches:
- main

View File

@@ -5,7 +5,8 @@ on:
workflow_dispatch:
env:
BUN_VERSION: "1.2.10"
BUN_VERSION: "1.2.0"
OXLINT_VERSION: "0.15.0"
jobs:
lint-js:
@@ -18,4 +19,4 @@ jobs:
with:
bun-version: ${{ env.BUN_VERSION }}
- name: Lint
run: bun lint
run: bunx oxlint --config oxlint.json --quiet --format github

View File

@@ -50,14 +50,9 @@ jobs:
exit 1
fi
LATEST_TAG_SHA=$(curl -sL "https://api.github.com/repos/c-ares/c-ares/git/ref/tags/$LATEST_TAG" | jq -r '.object.sha')
if [ -z "$LATEST_TAG_SHA" ] || [ "$LATEST_TAG_SHA" = "null" ]; then
echo "Error: Could not fetch SHA for tag $LATEST_TAG"
exit 1
fi
LATEST_SHA=$(curl -sL "https://api.github.com/repos/c-ares/c-ares/git/ref/tags/$LATEST_TAG_SHA" | jq -r '.object.sha')
LATEST_SHA=$(curl -sL "https://api.github.com/repos/c-ares/c-ares/git/ref/tags/$LATEST_TAG" | jq -r '.object.sha')
if [ -z "$LATEST_SHA" ] || [ "$LATEST_SHA" = "null" ]; then
echo "Error: Could not fetch SHA for tag $LATEST_TAG @ $LATEST_TAG_SHA"
echo "Error: Could not fetch SHA for tag $LATEST_TAG"
exit 1
fi

View File

@@ -50,14 +50,9 @@ jobs:
exit 1
fi
LATEST_TAG_SHA=$(curl -sL "https://api.github.com/repos/libarchive/libarchive/git/ref/tags/$LATEST_TAG" | jq -r '.object.sha')
if [ -z "$LATEST_TAG_SHA" ] || [ "$LATEST_TAG_SHA" = "null" ]; then
echo "Error: Could not fetch SHA for tag $LATEST_TAG"
exit 1
fi
LATEST_SHA=$(curl -sL "https://api.github.com/repos/libarchive/libarchive/git/tags/$LATEST_TAG_SHA" | jq -r '.object.sha')
LATEST_SHA=$(curl -sL "https://api.github.com/repos/libarchive/libarchive/git/ref/tags/$LATEST_TAG" | jq -r '.object.sha')
if [ -z "$LATEST_SHA" ] || [ "$LATEST_SHA" = "null" ]; then
echo "Error: Could not fetch SHA for tag $LATEST_TAG @ $LATEST_TAG_SHA"
echo "Error: Could not fetch SHA for tag $LATEST_TAG"
exit 1
fi

View File

@@ -50,14 +50,9 @@ jobs:
exit 1
fi
LATEST_TAG_SHA=$(curl -sL "https://api.github.com/repos/ebiggers/libdeflate/git/ref/tags/$LATEST_TAG" | jq -r '.object.sha')
if [ -z "$LATEST_TAG_SHA" ] || [ "$LATEST_TAG_SHA" = "null" ]; then
echo "Error: Could not fetch SHA for tag $LATEST_TAG"
exit 1
fi
LATEST_SHA=$(curl -sL "https://api.github.com/repos/ebiggers/libdeflate/git/tags/$LATEST_TAG_SHA" | jq -r '.object.sha')
LATEST_SHA=$(curl -sL "https://api.github.com/repos/ebiggers/libdeflate/git/ref/tags/$LATEST_TAG" | jq -r '.object.sha')
if [ -z "$LATEST_SHA" ] || [ "$LATEST_SHA" = "null" ]; then
echo "Error: Could not fetch SHA for tag $LATEST_TAG @ $LATEST_TAG_SHA"
echo "Error: Could not fetch SHA for tag $LATEST_TAG"
exit 1
fi

View File

@@ -50,14 +50,9 @@ jobs:
exit 1
fi
LATEST_TAG_SHA=$(curl -sL "https://api.github.com/repos/cloudflare/lol-html/git/ref/tags/$LATEST_TAG" | jq -r '.object.sha')
if [ -z "$LATEST_TAG_SHA" ] || [ "$LATEST_TAG_SHA" = "null" ]; then
echo "Error: Could not fetch SHA for tag $LATEST_TAG"
exit 1
fi
LATEST_SHA=$(curl -sL "https://api.github.com/repos/cloudflare/lol-html/git/tags/$LATEST_TAG_SHA" | jq -r '.object.sha')
LATEST_SHA=$(curl -sL "https://api.github.com/repos/cloudflare/lol-html/git/ref/tags/$LATEST_TAG" | jq -r '.object.sha')
if [ -z "$LATEST_SHA" ] || [ "$LATEST_SHA" = "null" ]; then
echo "Error: Could not fetch SHA for tag $LATEST_TAG @ $LATEST_TAG_SHA"
echo "Error: Could not fetch SHA for tag $LATEST_TAG"
exit 1
fi

View File

@@ -50,14 +50,9 @@ jobs:
exit 1
fi
LATEST_TAG_SHA=$(curl -sL "https://api.github.com/repos/litespeedtech/ls-hpack/git/ref/tags/$LATEST_TAG" | jq -r '.object.sha')
if [ -z "$LATEST_TAG_SHA" ] || [ "$LATEST_TAG_SHA" = "null" ]; then
echo "Error: Could not fetch SHA for tag $LATEST_TAG"
exit 1
fi
LATEST_SHA=$(curl -sL "https://api.github.com/repos/litespeedtech/ls-hpack/git/tags/$LATEST_TAG_SHA" | jq -r '.object.sha')
LATEST_SHA=$(curl -sL "https://api.github.com/repos/litespeedtech/ls-hpack/git/ref/tags/$LATEST_TAG" | jq -r '.object.sha')
if [ -z "$LATEST_SHA" ] || [ "$LATEST_SHA" = "null" ]; then
echo "Error: Could not fetch SHA for tag $LATEST_TAG @ $LATEST_TAG_SHA"
echo "Error: Could not fetch SHA for tag $LATEST_TAG"
exit 1
fi

1
.gitignore vendored
View File

@@ -153,7 +153,6 @@ test/cli/install/registry/packages/publish-pkg-*
test/cli/install/registry/packages/@secret/publish-pkg-8
test/js/third_party/prisma/prisma/sqlite/dev.db-journal
tmp
codegen-for-zig-team.tar.gz
# Dependencies
/vendor

6
.vscode/launch.json generated vendored
View File

@@ -1118,11 +1118,7 @@
"request": "attach",
"name": "rr",
"trace": "Off",
"setupCommands": [
"handle SIGPWR nostop noprint pass",
"source ${workspaceFolder}/misctools/gdb/std_gdb_pretty_printers.py",
"source ${workspaceFolder}/misctools/gdb/zig_gdb_pretty_printers.py",
],
"setupCommands": ["handle SIGPWR nostop noprint pass"],
},
],
"inputs": [

View File

@@ -30,7 +30,7 @@
"zig.initialSetupDone": true,
"zig.buildOption": "build",
"zig.zls.zigLibPath": "${workspaceFolder}/vendor/zig/lib",
"zig.buildArgs": ["-Dgenerated-code=./build/debug/codegen", "--watch", "-fincremental"],
"zig.buildArgs": ["-Dgenerated-code=./build/debug/codegen"],
"zig.zls.buildOnSaveStep": "check",
// "zig.zls.enableBuildOnSave": true,
// "zig.buildOnSave": true,
@@ -146,8 +146,6 @@
"*.mdc": "markdown",
"array": "cpp",
"ios": "cpp",
"oxlint.json": "jsonc",
"bun.lock": "jsonc",
},
"C_Cpp.files.exclude": {
"**/.vscode": true,

View File

@@ -53,39 +53,39 @@ $ brew install bun
## Install LLVM
Bun requires LLVM 19 (`clang` is part of LLVM). This version requirement is to match WebKit (precompiled), as mismatching versions will cause memory allocation failures at runtime. In most cases, you can install LLVM through your system package manager:
Bun requires LLVM 18 (`clang` is part of LLVM). This version requirement is to match WebKit (precompiled), as mismatching versions will cause memory allocation failures at runtime. In most cases, you can install LLVM through your system package manager:
{% codetabs group="os" %}
```bash#macOS (Homebrew)
$ brew install llvm@19
$ brew install llvm@18
```
```bash#Ubuntu/Debian
$ # LLVM has an automatic installation script that is compatible with all versions of Ubuntu
$ wget https://apt.llvm.org/llvm.sh -O - | sudo bash -s -- 19 all
$ wget https://apt.llvm.org/llvm.sh -O - | sudo bash -s -- 18 all
```
```bash#Arch
$ sudo pacman -S llvm clang lld
$ sudo pacman -S llvm clang18 lld
```
```bash#Fedora
$ sudo dnf install llvm clang lld-devel
$ sudo dnf install llvm18 clang18 lld18-devel
```
```bash#openSUSE Tumbleweed
$ sudo zypper install clang19 lld19 llvm19
$ sudo zypper install clang18 lld18 llvm18
```
{% /codetabs %}
If none of the above solutions apply, you will have to install it [manually](https://github.com/llvm/llvm-project/releases/tag/llvmorg-19.1.7).
Make sure Clang/LLVM 19 is in your path:
Make sure Clang/LLVM 18 is in your path:
```bash
$ which clang-19
$ which clang-18
```
If not, run this to manually add it:
@@ -94,13 +94,13 @@ If not, run this to manually add it:
```bash#macOS (Homebrew)
# use fish_add_path if you're using fish
# use path+="$(brew --prefix llvm@19)/bin" if you are using zsh
$ export PATH="$(brew --prefix llvm@19)/bin:$PATH"
# use path+="$(brew --prefix llvm@18)/bin" if you are using zsh
$ export PATH="$(brew --prefix llvm@18)/bin:$PATH"
```
```bash#Arch
# use fish_add_path if you're using fish
$ export PATH="$PATH:/usr/lib/llvm19/bin"
$ export PATH="$PATH:/usr/lib/llvm18/bin"
```
{% /codetabs %}
@@ -134,16 +134,6 @@ We recommend adding `./build/debug` to your `$PATH` so that you can run `bun-deb
$ bun-debug
```
## Running debug builds
The `bd` package.json script compiles and runs a debug build of Bun, only printing the output of the build process if it fails.
```sh
$ bun bd <args>
$ bun bd test foo.test.ts
$ bun bd ./foo.ts
```
## Code generation scripts
Several code generation scripts are used during Bun's build process. These are run automatically when changes are made to certain files.
@@ -260,7 +250,7 @@ The issue may manifest when initially running `bun setup` as Clang being unable
```
The C++ compiler
"/usr/bin/clang++-19"
"/usr/bin/clang++-18"
is not able to compile a simple test program.
```

2
LATEST
View File

@@ -1 +1 @@
1.2.10
1.2.5

View File

@@ -1,44 +0,0 @@
import { bench, run } from "../runner.mjs";
import crypto from "node:crypto";
import { Buffer } from "node:buffer";
const keylen = { "aes-128-gcm": 16, "aes-192-gcm": 24, "aes-256-gcm": 32 };
const sizes = [4 * 1024, 1024 * 1024];
const ciphers = ["aes-128-gcm", "aes-192-gcm", "aes-256-gcm"];
const messages = {};
sizes.forEach(size => {
messages[size] = Buffer.alloc(size, "b");
});
const keys = {};
ciphers.forEach(cipher => {
keys[cipher] = crypto.randomBytes(keylen[cipher]);
});
// Fixed IV and AAD
const iv = crypto.randomBytes(12);
const associate_data = Buffer.alloc(16, "z");
for (const cipher of ciphers) {
for (const size of sizes) {
const message = messages[size];
const key = keys[cipher];
bench(`${cipher} ${size / 1024}KB`, () => {
const alice = crypto.createCipheriv(cipher, key, iv);
alice.setAAD(associate_data);
const enc = alice.update(message);
alice.final();
const tag = alice.getAuthTag();
const bob = crypto.createDecipheriv(cipher, key, iv);
bob.setAuthTag(tag);
bob.setAAD(associate_data);
bob.update(enc);
bob.final();
});
}
}
await run();

View File

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

View File

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

View File

@@ -1,28 +0,0 @@
import ioredis from "ioredis";
const redis = process.argv.includes("--redis=native")
? Bun.redis
: new ioredis("redis://localhost:6379", {
enableAutoPipelining: true,
});
const isBun = globalThis.Bun && redis === Bun.redis;
for (let count of [100, 1000]) {
function iterate() {
const promises = new Array(count);
for (let i = 0; i < count; i++) {
promises[i] = redis.get("greeting");
}
return Promise.all(promises);
}
const label = isBun ? `Bun.redis` : `ioredis`;
console.time(`GET 'greeting' batches of ${count} - ${label} (${count} iterations)`);
for (let i = 0; i < 1000; i++) {
await iterate();
}
console.timeEnd(`GET 'greeting' batches of ${count} - ${label} (${count} iterations)`);
}
process.exit(0);

View File

@@ -1,14 +0,0 @@
import { bench, run } from "../runner.mjs";
const url = "http://localhost:3000/";
const clonable = new Request(url);
bench("request.clone().method", () => {
return clonable.clone().method;
});
bench("new Request(url).method", () => {
return new Request(url).method;
});
await run();

313
build.zig
View File

@@ -4,7 +4,7 @@ const builtin = @import("builtin");
const Build = std.Build;
const Step = Build.Step;
const Compile = Step.Compile;
const LazyPath = Build.LazyPath;
const LazyPath = Step.LazyPath;
const Target = std.Target;
const ResolvedTarget = std.Build.ResolvedTarget;
const CrossTarget = std.zig.CrossTarget;
@@ -18,21 +18,21 @@ const OperatingSystem = @import("src/env.zig").OperatingSystem;
const pathRel = fs.path.relative;
/// When updating this, make sure to adjust SetupZig.cmake
/// Do not rename this constant. It is scanned by some scripts to determine which zig version to install.
const recommended_zig_version = "0.14.0";
// comptime {
// if (!std.mem.eql(u8, builtin.zig_version_string, recommended_zig_version)) {
// @compileError(
// "" ++
// "Bun requires Zig version " ++ recommended_zig_version ++ ", but you have " ++
// builtin.zig_version_string ++ ". This is automatically configured via Bun's " ++
// "CMake setup. You likely meant to run `bun run build`. If you are trying to " ++
// "upgrade the Zig compiler, edit ZIG_COMMIT in cmake/tools/SetupZig.cmake or " ++
// "comment this error out.",
// );
// }
// }
comptime {
if (!std.mem.eql(u8, builtin.zig_version_string, recommended_zig_version)) {
@compileError(
"" ++
"Bun requires Zig version " ++ recommended_zig_version ++ ", but you have " ++
builtin.zig_version_string ++ ". This is automatically configured via Bun's " ++
"CMake setup. You likely meant to run `bun run build`. If you are trying to " ++
"upgrade the Zig compiler, edit ZIG_COMMIT in cmake/tools/SetupZig.cmake or " ++
"comment this error out.",
);
}
}
const zero_sha = "0000000000000000000000000000000000000000";
@@ -93,7 +93,6 @@ const BunBuildOptions = struct {
opts.addOption(bool, "baseline", this.isBaseline());
opts.addOption(bool, "enable_logs", this.enable_logs);
opts.addOption([]const u8, "reported_nodejs_version", b.fmt("{}", .{this.reported_nodejs_version}));
opts.addOption(bool, "zig_self_hosted_backend", this.no_llvm);
const mod = opts.createModule();
this.cached_options_module = mod;
@@ -154,6 +153,13 @@ pub fn build(b: *Build) !void {
std.log.info("zig compiler v{s}", .{builtin.zig_version_string});
checked_file_exists = std.AutoHashMap(u64, void).init(b.allocator);
// TODO: Upgrade path for 0.14.0
// b.graph.zig_lib_directory = brk: {
// const sub_path = "vendor/zig/lib";
// const dir = try b.build_root.handle.openDir(sub_path, .{});
// break :brk .{ .handle = dir, .path = try b.build_root.join(b.graph.arena, &.{sub_path}) };
// };
var target_query = b.standardTargetOptionsQueryOnly(.{});
const optimize = b.standardOptimizeOption(.{});
@@ -199,7 +205,10 @@ pub fn build(b: *Build) !void {
const bun_version = b.option([]const u8, "version", "Value of `Bun.version`") orelse "0.0.0";
b.reference_trace = b.reference_trace orelse 32;
b.reference_trace = ref_trace: {
const trace = b.option(u32, "reference-trace", "Set the reference trace") orelse 24;
break :ref_trace if (trace == 0) null else trace;
};
const obj_format = b.option(ObjectFormat, "obj_format", "Output file for object files") orelse .obj;
@@ -276,40 +285,6 @@ pub fn build(b: *Build) !void {
step.dependOn(addInstallObjectFile(b, bun_obj, "bun-zig", obj_format));
}
// zig build test
{
var step = b.step("test", "Build Bun's unit test suite");
var o = build_options;
var unit_tests = b.addTest(.{
.name = "bun-test",
.optimize = build_options.optimize,
.root_source_file = b.path("src/unit_test.zig"),
.test_runner = .{ .path = b.path("src/main_test.zig"), .mode = .simple },
.target = build_options.target,
.use_llvm = !build_options.no_llvm,
.use_lld = if (build_options.os == .mac) false else !build_options.no_llvm,
.omit_frame_pointer = false,
.strip = false,
});
configureObj(b, &o, unit_tests);
// Setting `linker_allow_shlib_undefined` causes the linker to ignore
// all undefined symbols. We want this because all we care about is the
// object file Zig creates; we perform our own linking later. There is
// currently no way to make a test build that only creates an object
// file w/o creating an executable.
//
// See: https://github.com/ziglang/zig/issues/23374
unit_tests.linker_allow_shlib_undefined = true;
unit_tests.link_function_sections = true;
unit_tests.link_data_sections = true;
unit_tests.bundle_ubsan_rt = false;
const bin = unit_tests.getEmittedBin();
const obj = bin.dirname().path(b, "bun-test.o");
const cpy_obj = b.addInstallFile(obj, "bun-test.o");
step.dependOn(&cpy_obj.step);
}
// zig build windows-shim
{
var step = b.step("windows-shim", "Build the Windows shim (bun_shim_impl.exe + bun_shim_debug.exe)");
@@ -333,22 +308,6 @@ pub fn build(b: *Build) !void {
b.default_step.dependOn(step);
}
// zig build watch
// const enable_watch_step = b.option(bool, "watch_step", "Enable the watch step. This reads more files so it is off by default") orelse false;
// if (no_llvm or enable_watch_step) {
// self_hosted_watch.selfHostedExeBuild(b, &build_options) catch @panic("OOM");
// }
// zig build check-debug
{
const step = b.step("check-debug", "Check for semantic analysis errors on some platforms");
addMultiCheck(b, step, build_options, &.{
.{ .os = .windows, .arch = .x86_64 },
.{ .os = .mac, .arch = .aarch64 },
.{ .os = .linux, .arch = .x86_64 },
}, &.{.Debug});
}
// zig build check-all
{
const step = b.step("check-all", "Check for semantic analysis errors on all supported platforms");
@@ -402,22 +361,7 @@ pub fn build(b: *Build) !void {
// zig build translate-c-headers
{
const step = b.step("translate-c", "Copy generated translated-c-headers.zig to zig-out");
for ([_]TargetDescription{
.{ .os = .windows, .arch = .x86_64 },
.{ .os = .mac, .arch = .x86_64 },
.{ .os = .mac, .arch = .aarch64 },
.{ .os = .linux, .arch = .x86_64 },
.{ .os = .linux, .arch = .aarch64 },
.{ .os = .linux, .arch = .x86_64, .musl = true },
.{ .os = .linux, .arch = .aarch64, .musl = true },
}) |t| {
const resolved = t.resolveTarget(b);
step.dependOn(
&b.addInstallFile(getTranslateC(b, resolved, .Debug), b.fmt("translated-c-headers/{s}.zig", .{
resolved.result.zigTriple(b.allocator) catch @panic("OOM"),
})).step,
);
}
step.dependOn(&b.addInstallFile(getTranslateC(b, b.graph.host, .Debug).getOutput(), "translated-c-headers.zig").step);
}
// zig build enum-extractor
@@ -434,32 +378,23 @@ pub fn build(b: *Build) !void {
}
}
const TargetDescription = struct {
os: OperatingSystem,
arch: Arch,
musl: bool = false,
fn resolveTarget(desc: TargetDescription, b: *Build) std.Build.ResolvedTarget {
return b.resolveTargetQuery(.{
.os_tag = OperatingSystem.stdOSTag(desc.os),
.cpu_arch = desc.arch,
.cpu_model = getCpuModel(desc.os, desc.arch) orelse .determined_by_arch_os,
.os_version_min = getOSVersionMin(desc.os),
.glibc_version = if (desc.musl) null else getOSGlibCVersion(desc.os),
});
}
};
fn addMultiCheck(
pub fn addMultiCheck(
b: *Build,
parent_step: *Step,
root_build_options: BunBuildOptions,
to_check: []const TargetDescription,
to_check: []const struct { os: OperatingSystem, arch: Arch, musl: bool = false },
optimize: []const std.builtin.OptimizeMode,
) void {
for (to_check) |check| {
for (optimize) |mode| {
const check_target = check.resolveTarget(b);
const check_target = b.resolveTargetQuery(.{
.os_tag = OperatingSystem.stdOSTag(check.os),
.cpu_arch = check.arch,
.cpu_model = getCpuModel(check.os, check.arch) orelse .determined_by_arch_os,
.os_version_min = getOSVersionMin(check.os),
.glibc_version = if (check.musl) null else getOSGlibCVersion(check.os),
});
var options: BunBuildOptions = .{
.target = check_target,
.os = check.os,
@@ -483,13 +418,7 @@ fn addMultiCheck(
}
}
fn getTranslateC(b: *Build, initial_target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode) LazyPath {
const target = b.resolveTargetQuery(q: {
var query = initial_target.query;
if (query.os_tag == .windows)
query.abi = .gnu;
break :q query;
});
fn getTranslateC(b: *Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode) *Step.TranslateC {
const translate_c = b.addTranslateC(.{
.root_source_file = b.path("src/c-headers-for-zig.h"),
.target = target,
@@ -505,72 +434,28 @@ fn getTranslateC(b: *Build, initial_target: std.Build.ResolvedTarget, optimize:
const str, const value = entry;
translate_c.defineCMacroRaw(b.fmt("{s}={d}", .{ str, @intFromBool(value) }));
}
if (target.result.os.tag == .windows) {
// translate-c is unable to translate the unsuffixed windows functions
// like `SetCurrentDirectory` since they are defined with an odd macro
// that translate-c doesn't handle.
//
// #define SetCurrentDirectory __MINGW_NAME_AW(SetCurrentDirectory)
//
// In these cases, it's better to just reference the underlying function
// directly: SetCurrentDirectoryW. To make the error better, a post
// processing step is applied to the translate-c file.
//
// Additionally, this step makes it so that decls like NTSTATUS and
// HANDLE point to the standard library structures.
const helper_exe = b.addExecutable(.{
.name = "process_windows_translate_c",
.root_module = b.createModule(.{
.root_source_file = b.path("src/codegen/process_windows_translate_c.zig"),
.target = b.graph.host,
.optimize = .Debug,
}),
});
const in = translate_c.getOutput();
const run = b.addRunArtifact(helper_exe);
run.addFileArg(in);
const out = run.addOutputFileArg("c-headers-for-zig.zig");
return out;
}
return translate_c.getOutput();
return translate_c;
}
pub fn addBunObject(b: *Build, opts: *BunBuildOptions) *Compile {
// Create `@import("bun")`, containing most of Bun's code.
const bun = b.createModule(.{
.root_source_file = b.path("src/bun.zig"),
});
bun.addImport("bun", bun); // allow circular "bun" import
addInternalImports(b, bun, opts);
const root = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
// Root module gets compilation flags. Forwarded as default to dependencies.
.target = opts.target,
.optimize = opts.optimize,
});
root.addImport("bun", bun);
const obj = b.addObject(.{
.name = if (opts.optimize == .Debug) "bun-debug" else "bun",
.root_module = root,
.root_source_file = switch (opts.os) {
.wasm => b.path("root_wasm.zig"),
else => b.path("src/main.zig"),
// else => b.path("root_css.zig"),
},
.target = opts.target,
.optimize = opts.optimize,
.use_llvm = !opts.no_llvm,
.use_lld = if (opts.os == .mac) false else !opts.no_llvm,
// https://github.com/ziglang/zig/issues/17430
.pic = true,
.omit_frame_pointer = false,
.strip = false, // stripped at the end
});
configureObj(b, opts, obj);
return obj;
}
fn configureObj(b: *Build, opts: *BunBuildOptions, obj: *Compile) void {
// Flags on root module get used for the compilation
obj.root_module.omit_frame_pointer = false;
obj.root_module.strip = false; // stripped at the end
// https://github.com/ziglang/zig/issues/17430
obj.root_module.pic = true;
// Object options
obj.use_llvm = !opts.no_llvm;
obj.use_lld = if (opts.os == .mac) false else !opts.no_llvm;
if (opts.enable_asan) {
if (@hasField(Build.Module, "sanitize_address")) {
obj.root_module.sanitize_address = true;
@@ -580,7 +465,7 @@ fn configureObj(b: *Build, opts: *BunBuildOptions, obj: *Compile) void {
}
}
obj.bundle_compiler_rt = false;
obj.bundle_ubsan_rt = false;
obj.root_module.omit_frame_pointer = false;
// Link libc
if (opts.os != .wasm) {
@@ -590,7 +475,6 @@ fn configureObj(b: *Build, opts: *BunBuildOptions, obj: *Compile) void {
// Disable stack probing on x86 so we don't need to include compiler_rt
if (opts.arch.isX86()) {
// TODO: enable on debug please.
obj.root_module.stack_check = false;
obj.root_module.stack_protector = false;
}
@@ -605,18 +489,17 @@ fn configureObj(b: *Build, opts: *BunBuildOptions, obj: *Compile) void {
obj.root_module.valgrind = true;
}
}
addInternalPackages(b, obj, opts);
obj.root_module.addImport("build_options", opts.buildOptionsModule(b));
const translate_c = getTranslateC(b, opts.target, opts.optimize);
obj.root_module.addImport("translated-c-headers", translate_c.createModule());
return obj;
}
const ObjectFormat = enum {
/// Emitting LLVM bc files could allow a stronger LTO pass, however it
/// doesn't yet work. It is left accessible with `-Dobj_format=bc` or in
/// CMake with `-DZIG_OBJECT_FORMAT=bc`.
///
/// To use LLVM bitcode from Zig, more work needs to be done. Currently, an install of
/// LLVM 18.1.7 does not compatible with what bitcode Zig 0.13 outputs (has LLVM 18.1.7)
/// Change to "bc" to experiment, "Invalid record" means it is not valid output.
bc,
/// Emit a .o / .obj file for the bun-zig object.
obj,
};
@@ -646,21 +529,16 @@ fn exists(path: []const u8) bool {
return true;
}
fn addInternalImports(b: *Build, mod: *Module, opts: *BunBuildOptions) void {
fn addInternalPackages(b: *Build, obj: *Compile, opts: *BunBuildOptions) void {
const os = opts.os;
mod.addImport("build_options", opts.buildOptionsModule(b));
const translate_c = getTranslateC(b, opts.target, opts.optimize);
mod.addImport("translated-c-headers", b.createModule(.{ .root_source_file = translate_c }));
const zlib_internal_path = switch (os) {
.windows => "src/deps/zlib.win32.zig",
.linux, .mac => "src/deps/zlib.posix.zig",
else => null,
};
if (zlib_internal_path) |path| {
mod.addAnonymousImport("zlib-internal", .{
obj.root_module.addAnonymousImport("zlib-internal", .{
.root_source_file = b.path(path),
});
}
@@ -670,7 +548,7 @@ fn addInternalImports(b: *Build, mod: *Module, opts: *BunBuildOptions) void {
.windows => "src/async/windows_event_loop.zig",
else => "src/async/stub_event_loop.zig",
};
mod.addAnonymousImport("async", .{
obj.root_module.addAnonymousImport("async", .{
.root_source_file = b.path(async_path),
});
@@ -718,7 +596,7 @@ fn addInternalImports(b: *Build, mod: *Module, opts: *BunBuildOptions) void {
entry.import
else
entry.file;
mod.addAnonymousImport(import_path, .{
obj.root_module.addAnonymousImport(import_path, .{
.root_source_file = .{ .cwd_relative = path },
});
}
@@ -728,37 +606,16 @@ fn addInternalImports(b: *Build, mod: *Module, opts: *BunBuildOptions) void {
.{ .import = "completions-zsh", .file = b.path("completions/bun.zsh") },
.{ .import = "completions-fish", .file = b.path("completions/bun.fish") },
}) |entry| {
mod.addAnonymousImport(entry.import, .{
obj.root_module.addAnonymousImport(entry.import, .{
.root_source_file = entry.file,
});
}
if (os == .windows) {
mod.addAnonymousImport("bun_shim_impl.exe", .{
obj.root_module.addAnonymousImport("bun_shim_impl.exe", .{
.root_source_file = opts.windowsShim(b).exe.getEmittedBin(),
});
}
// Finally, make it so all modules share the same import table.
propagateImports(mod) catch @panic("OOM");
}
/// Makes all imports of `source_mod` visible to all of its dependencies.
/// Does not replace existing imports.
fn propagateImports(source_mod: *Module) !void {
var seen = std.AutoHashMap(*Module, void).init(source_mod.owner.graph.arena);
defer seen.deinit();
var queue = std.ArrayList(*Module).init(source_mod.owner.graph.arena);
defer queue.deinit();
try queue.appendSlice(source_mod.import_table.values());
while (queue.pop()) |mod| {
if ((try seen.getOrPut(mod)).found_existing) continue;
try queue.appendSlice(mod.import_table.values());
for (source_mod.import_table.keys(), source_mod.import_table.values()) |k, v|
if (mod.import_table.get(k) == null)
mod.addImport(k, v);
}
}
fn validateGeneratedPath(path: []const u8) void {
@@ -787,34 +644,30 @@ const WindowsShim = struct {
const exe = b.addExecutable(.{
.name = "bun_shim_impl",
.root_module = b.createModule(.{
.root_source_file = path,
.target = target,
.optimize = .ReleaseFast,
.unwind_tables = .none,
.omit_frame_pointer = true,
.strip = true,
.sanitize_thread = false,
.single_threaded = true,
.link_libc = false,
}),
.linkage = .static,
.root_source_file = path,
.target = target,
.optimize = .ReleaseFast,
.use_llvm = true,
.use_lld = true,
.unwind_tables = .none,
.omit_frame_pointer = true,
.strip = true,
.linkage = .static,
.sanitize_thread = false,
.single_threaded = true,
.link_libc = false,
});
const dbg = b.addExecutable(.{
.name = "bun_shim_debug",
.root_module = b.createModule(.{
.root_source_file = path,
.target = target,
.optimize = .Debug,
.single_threaded = true,
.link_libc = false,
}),
.linkage = .static,
.root_source_file = path,
.target = target,
.optimize = .Debug,
.use_llvm = true,
.use_lld = true,
.linkage = .static,
.single_threaded = true,
.link_libc = false,
});
return .{ .exe = exe, .dbg = dbg };

View File

@@ -29,6 +29,7 @@
"name": "bun-types",
"dependencies": {
"@types/node": "*",
"@types/ws": "~8.5.10",
},
"devDependencies": {
"@biomejs/biome": "^1.5.3",
@@ -164,6 +165,8 @@
"@types/semver": ["@types/semver@7.5.8", "", {}, "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ=="],
"@types/ws": ["@types/ws@8.5.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-4+q7P5h3SpJxaBft0Dzpbr6lmMaqh0Jr2tbhJZ/luAwvD7ohSCniYkwz/pLxuT2h0EOa6QADgJj1Ko+TzRfZ+w=="],
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@7.16.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "7.16.1", "@typescript-eslint/type-utils": "7.16.1", "@typescript-eslint/utils": "7.16.1", "@typescript-eslint/visitor-keys": "7.16.1", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", "ts-api-utils": "^1.3.0" }, "peerDependencies": { "@typescript-eslint/parser": "^7.0.0", "eslint": "^8.56.0" } }, "sha512-SxdPak/5bO0EnGktV05+Hq8oatjAYVY3Zh2bye9pGZy6+jwyR3LG3YKkV4YatlsgqXP28BTeVm9pqwJM96vf2A=="],
"@typescript-eslint/parser": ["@typescript-eslint/parser@7.16.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "7.16.1", "@typescript-eslint/types": "7.16.1", "@typescript-eslint/typescript-estree": "7.16.1", "@typescript-eslint/visitor-keys": "7.16.1", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.56.0" } }, "sha512-u+1Qx86jfGQ5i4JjK33/FnawZRpsLxRnKzGE6EABZ40KxVT/vWsiZFEBBHjFOljmmV3MBYOHEKi0Jm9hbAOClA=="],
@@ -912,6 +915,8 @@
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
"@types/ws/@types/node": ["@types/node@20.12.14", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-scnD59RpYD91xngrQQLGkE+6UrHUPzeKZWhhjBSa3HSkwjbQc38+q3RoIVEwxQGRw3M+j5hpNAM+lgV3cVormg=="],
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
"@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
@@ -1002,6 +1007,8 @@
"@definitelytyped/utils/which/isexe": ["isexe@3.1.1", "", {}, "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ=="],
"@types/ws/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
"are-we-there-yet/readable-stream/isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="],

View File

@@ -142,14 +142,6 @@ if(UNIX)
-fno-unwind-tables
-fno-asynchronous-unwind-tables
)
# needed for libuv stubs because they use
# C23 feature which lets you define parameter without
# name
register_compiler_flags(
DESCRIPTION "Allow C23 extensions"
-Wno-c23-extensions
)
endif()
register_compiler_flags(

View File

@@ -423,7 +423,7 @@ function(register_command)
# libbun-profile.a is now over 5gb in size, compress it first
list(APPEND CMD_COMMANDS COMMAND ${CMAKE_COMMAND} -E chdir ${BUILD_PATH} rm -r ${BUILD_PATH}/codegen)
list(APPEND CMD_COMMANDS COMMAND ${CMAKE_COMMAND} -E chdir ${BUILD_PATH} rm -r ${CACHE_PATH})
list(APPEND CMD_COMMANDS COMMAND ${CMAKE_COMMAND} -E chdir ${BUILD_PATH} gzip -1 libbun-profile.a)
list(APPEND CMD_COMMANDS COMMAND ${CMAKE_COMMAND} -E chdir ${BUILD_PATH} gzip -6 libbun-profile.a)
list(APPEND CMD_COMMANDS COMMAND ${CMAKE_COMMAND} -E chdir ${BUILD_PATH} buildkite-agent artifact upload libbun-profile.a.gz)
else()
list(APPEND CMD_COMMANDS COMMAND ${CMAKE_COMMAND} -E chdir ${BUILD_PATH} buildkite-agent artifact upload ${filename})
@@ -633,7 +633,7 @@ function(register_repository)
set(GIT_PATH ${VENDOR_PATH}/${GIT_NAME})
endif()
set(GIT_EFFECTIVE_OUTPUTS ${GIT_PATH}/.ref)
set(GIT_EFFECTIVE_OUTPUTS)
foreach(output ${GIT_OUTPUTS})
list(APPEND GIT_EFFECTIVE_OUTPUTS ${GIT_PATH}/${output})
endforeach()
@@ -751,17 +751,11 @@ function(register_cmake_command)
list(APPEND MAKE_EFFECTIVE_ARGS --fresh)
endif()
set(MAKE_SOURCES)
if(TARGET clone-${MAKE_TARGET})
list(APPEND MAKE_SOURCES ${MAKE_CWD}/.ref)
endif()
register_command(
COMMENT "Configuring ${MAKE_TARGET}"
TARGET configure-${MAKE_TARGET}
COMMAND ${CMAKE_COMMAND} ${MAKE_EFFECTIVE_ARGS}
CWD ${MAKE_CWD}
SOURCES ${MAKE_SOURCES}
OUTPUTS ${MAKE_BUILD_PATH}/CMakeCache.txt
)
@@ -813,7 +807,6 @@ function(register_cmake_command)
TARGETS configure-${MAKE_TARGET}
COMMAND ${CMAKE_COMMAND} ${MAKE_BUILD_ARGS}
CWD ${MAKE_CWD}
SOURCES ${MAKE_SOURCES}
ARTIFACTS ${MAKE_ARTIFACTS}
)

View File

@@ -26,15 +26,6 @@ else()
setx(DEBUG OFF)
endif()
optionx(BUN_TEST BOOL "Build Bun's unit test suite instead of the normal build" DEFAULT OFF)
if (BUN_TEST)
setx(TEST ON)
else()
setx(TEST OFF)
endif()
if(CMAKE_BUILD_TYPE MATCHES "MinSizeRel")
setx(ENABLE_SMOL ON)
endif()
@@ -71,14 +62,7 @@ if(ARCH STREQUAL "x64")
optionx(ENABLE_BASELINE BOOL "If baseline features should be used for older CPUs (e.g. disables AVX, AVX2)" DEFAULT OFF)
endif()
# Disabling logs by default for tests yields faster builds
if (DEBUG AND NOT TEST)
set(DEFAULT_ENABLE_LOGS ON)
else()
set(DEFAULT_ENABLE_LOGS OFF)
endif()
optionx(ENABLE_LOGS BOOL "If debug logs should be enabled" DEFAULT ${DEFAULT_ENABLE_LOGS})
optionx(ENABLE_LOGS BOOL "If debug logs should be enabled" DEFAULT ${DEBUG})
optionx(ENABLE_ASSERTIONS BOOL "If debug assertions should be enabled" DEFAULT ${DEBUG})
optionx(ENABLE_CANARY BOOL "If canary features should be enabled" DEFAULT ON)

View File

@@ -29,9 +29,6 @@ else()
endif()
set(ZIG_NAME bootstrap-${ZIG_ARCH}-${ZIG_OS_ABI})
if(ZIG_COMPILER_SAFE)
set(ZIG_NAME ${ZIG_NAME}-ReleaseSafe)
endif()
set(ZIG_FILENAME ${ZIG_NAME}.zip)
if(CMAKE_HOST_WIN32)

View File

@@ -4,7 +4,7 @@ register_repository(
REPOSITORY
oven-sh/boringssl
COMMIT
7a5d984c69b0c34c4cbb56c6812eaa5b9bef485c
914b005ef3ece44159dca0ffad74eb42a9f6679f
)
register_cmake_command(

View File

@@ -12,10 +12,6 @@ else()
set(bunStrip bun)
endif()
if(TEST)
set(bun ${bun}-test)
endif()
set(bunExe ${bun}${CMAKE_EXECUTABLE_SUFFIX})
if(bunStrip)
@@ -532,6 +528,7 @@ file(GLOB_RECURSE BUN_ZIG_SOURCES ${CONFIGURE_DEPENDS}
list(APPEND BUN_ZIG_SOURCES
${CWD}/build.zig
${CWD}/src/main.zig
${BUN_BINDGEN_ZIG_OUTPUTS}
)
@@ -553,13 +550,7 @@ else()
list(APPEND BUN_ZIG_GENERATED_SOURCES ${BUN_BAKE_RUNTIME_OUTPUTS})
endif()
if (TEST)
set(BUN_ZIG_OUTPUT ${BUILD_PATH}/bun-test.o)
set(ZIG_STEPS test)
else()
set(BUN_ZIG_OUTPUT ${BUILD_PATH}/bun-zig.o)
set(ZIG_STEPS obj)
endif()
set(BUN_ZIG_OUTPUT ${BUILD_PATH}/bun-zig.o)
if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm|ARM|arm64|ARM64|aarch64|AARCH64")
if(APPLE)
@@ -588,10 +579,10 @@ register_command(
GROUP
console
COMMENT
"Building src/*.zig into ${BUN_ZIG_OUTPUT} for ${ZIG_TARGET}"
"Building src/*.zig for ${ZIG_TARGET}"
COMMAND
${ZIG_EXECUTABLE}
build ${ZIG_STEPS}
build obj
${CMAKE_ZIG_FLAGS}
--prefix ${BUILD_PATH}
-Dobj_format=${ZIG_OBJECT_FORMAT}
@@ -605,7 +596,6 @@ register_command(
-Dcodegen_path=${CODEGEN_PATH}
-Dcodegen_embed=$<IF:$<BOOL:${CODEGEN_EMBED}>,true,false>
--prominent-compile-errors
--summary all
${ZIG_FLAGS_BUN}
ARTIFACTS
${BUN_ZIG_OUTPUT}
@@ -645,8 +635,6 @@ file(GLOB BUN_C_SOURCES ${CONFIGURE_DEPENDS}
${BUN_USOCKETS_SOURCE}/src/eventing/*.c
${BUN_USOCKETS_SOURCE}/src/internal/*.c
${BUN_USOCKETS_SOURCE}/src/crypto/*.c
${CWD}/src/bun.js/bindings/uv-posix-polyfills.c
${CWD}/src/bun.js/bindings/uv-posix-stubs.c
)
if(WIN32)
@@ -797,10 +785,6 @@ target_include_directories(${bun} PRIVATE
${NODEJS_HEADERS_PATH}/include
)
if(NOT WIN32)
target_include_directories(${bun} PRIVATE ${CWD}/src/bun.js/bindings/libuv)
endif()
if(LINUX)
include(CheckIncludeFiles)
check_include_files("sys/queue.h" HAVE_SYS_QUEUE_H)
@@ -909,7 +893,6 @@ if(NOT WIN32)
-Werror=sometimes-uninitialized
-Werror=unused
-Wno-unused-function
-Wno-c++23-lambda-attributes
-Wno-nullability-completeness
-Werror
)
@@ -926,7 +909,6 @@ if(NOT WIN32)
-Werror=nonnull
-Werror=move
-Werror=sometimes-uninitialized
-Wno-c++23-lambda-attributes
-Wno-nullability-completeness
-Werror
)
@@ -1089,7 +1071,6 @@ set(BUN_DEPENDENCIES
BoringSSL
Brotli
Cares
Highway
LibDeflate
LolHtml
Lshpack

View File

@@ -1,33 +0,0 @@
register_repository(
NAME
highway
REPOSITORY
google/highway
COMMIT
12b325bc1793dee68ab2157995a690db859fe9e0
)
set(HIGHWAY_CMAKE_ARGS
# Build a static library
-DBUILD_SHARED_LIBS=OFF
# Enable position-independent code for linking into the main executable
-DCMAKE_POSITION_INDEPENDENT_CODE=ON
# Disable unnecessary components
-DHWY_ENABLE_TESTS=OFF
-DHWY_ENABLE_EXAMPLES=OFF
-DHWY_ENABLE_CONTRIB=OFF
# Disable building of the install target
-DHWY_ENABLE_INSTALL=OFF
)
register_cmake_command(
TARGET
highway
LIBRARIES
hwy
ARGS
${HIGHWAY_CMAKE_ARGS}
INCLUDES
.
hwy
)

View File

@@ -4,7 +4,7 @@ register_repository(
REPOSITORY
ebiggers/libdeflate
COMMIT
78051988f96dc8d8916310d8b24021f01bd9e102
733848901289eca058804ca0737f8796875204c8
)
register_cmake_command(

View File

@@ -2,7 +2,7 @@ option(WEBKIT_VERSION "The version of WebKit to use")
option(WEBKIT_LOCAL "If a local version of WebKit should be used instead of downloading")
if(NOT WEBKIT_VERSION)
set(WEBKIT_VERSION 06820714a7990ea77c78157f9eeaabaf56c2098a)
set(WEBKIT_VERSION 91bf2baced1b1309c7e05f19177c97fefec20976)
endif()
string(SUBSTRING ${WEBKIT_VERSION} 0 16 WEBKIT_VERSION_PREFIX)

View File

@@ -20,7 +20,7 @@ else()
unsupported(CMAKE_SYSTEM_NAME)
endif()
set(ZIG_COMMIT "a207204ee57a061f2fb96c7bae0c491b609e73a5")
set(ZIG_COMMIT "cd1995944508e4c946deb75bd70947d302e0db37")
optionx(ZIG_TARGET STRING "The zig target to use" DEFAULT ${DEFAULT_ZIG_TARGET})
if(CMAKE_BUILD_TYPE STREQUAL "Release")
@@ -50,7 +50,6 @@ optionx(ZIG_OBJECT_FORMAT "obj|bc" "Output file format for Zig object files" DEF
optionx(ZIG_LOCAL_CACHE_DIR FILEPATH "The path to local the zig cache directory" DEFAULT ${CACHE_PATH}/zig/local)
optionx(ZIG_GLOBAL_CACHE_DIR FILEPATH "The path to the global zig cache directory" DEFAULT ${CACHE_PATH}/zig/global)
optionx(ZIG_COMPILER_SAFE BOOL "Download a ReleaseSafe build of the Zig compiler. Only availble on macos aarch64." DEFAULT ${BUILDKITE})
setenv(ZIG_LOCAL_CACHE_DIR ${ZIG_LOCAL_CACHE_DIR})
setenv(ZIG_GLOBAL_CACHE_DIR ${ZIG_GLOBAL_CACHE_DIR})
@@ -79,7 +78,6 @@ register_command(
-DZIG_PATH=${ZIG_PATH}
-DZIG_COMMIT=${ZIG_COMMIT}
-DENABLE_ASAN=${ENABLE_ASAN}
-DZIG_COMPILER_SAFE=${ZIG_COMPILER_SAFE}
-P ${CWD}/cmake/scripts/DownloadZig.cmake
SOURCES
${CWD}/cmake/scripts/DownloadZig.cmake

View File

@@ -55,7 +55,7 @@ RUN apt-get update -qq \
&& which bun \
&& bun --version
FROM debian:bookworm-slim
FROM debian:bullseye-slim
# Disable the runtime transpiler cache by default inside Docker containers.
# On ephemeral containers, the cache is not useful

View File

@@ -56,7 +56,7 @@ RUN apt-get update -qq \
&& rm -f "bun-linux-$build.zip" SHASUMS256.txt.asc SHASUMS256.txt \
&& chmod +x /usr/local/bin/bun
FROM debian:bookworm
FROM debian:bullseye
COPY docker-entrypoint.sh /usr/local/bin
COPY --from=build /usr/local/bin/bun /usr/local/bin/bun

View File

@@ -1,449 +0,0 @@
Bun provides native APIs for working with HTTP cookies through `Bun.Cookie` and `Bun.CookieMap`. These APIs offer fast, easy-to-use methods for parsing, generating, and manipulating cookies in HTTP requests and responses.
## CookieMap class
`Bun.CookieMap` provides a Map-like interface for working with collections of cookies. It implements the `Iterable` interface, allowing you to use it with `for...of` loops and other iteration methods.
```ts
// Empty cookie map
const cookies = new Bun.CookieMap();
// From a cookie string
const cookies1 = new Bun.CookieMap("name=value; foo=bar");
// From an object
const cookies2 = new Bun.CookieMap({
session: "abc123",
theme: "dark",
});
// From an array of name/value pairs
const cookies3 = new Bun.CookieMap([
["session", "abc123"],
["theme", "dark"],
]);
```
### In HTTP servers
In Bun's HTTP server, the `cookies` property on the request object (in `routes`) is an instance of `CookieMap`:
```ts
const server = Bun.serve({
routes: {
"/": req => {
// Access request cookies
const cookies = req.cookies;
// Get a specific cookie
const sessionCookie = cookies.get("session");
if (sessionCookie != null) {
console.log(sessionCookie);
}
// Check if a cookie exists
if (cookies.has("theme")) {
// ...
}
// Set a cookie, it will be automatically applied to the response
cookies.set("visited", "true");
return new Response("Hello");
},
},
});
console.log("Server listening at: " + server.url);
```
### Methods
#### `get(name: string): string | null`
Retrieves a cookie by name. Returns `null` if the cookie doesn't exist.
```ts
// Get by name
const cookie = cookies.get("session");
if (cookie != null) {
console.log(cookie);
}
```
#### `has(name: string): boolean`
Checks if a cookie with the given name exists.
```ts
// Check if cookie exists
if (cookies.has("session")) {
// Cookie exists
}
```
#### `set(name: string, value: string): void`
#### `set(options: CookieInit): void`
#### `set(cookie: Cookie): void`
Adds or updates a cookie in the map. Cookies default to `{ path: "/", sameSite: "lax" }`.
```ts
// Set by name and value
cookies.set("session", "abc123");
// Set using options object
cookies.set({
name: "theme",
value: "dark",
maxAge: 3600,
secure: true,
});
// Set using Cookie instance
const cookie = new Bun.Cookie("visited", "true");
cookies.set(cookie);
```
#### `delete(name: string): void`
#### `delete(options: CookieStoreDeleteOptions): void`
Removes a cookie from the map. When applied to a Response, this adds a cookie with an empty string value and an expiry date in the past. A cookie will only delete successfully on the browser if the domain and path is the same as it was when the cookie was created.
```ts
// Delete by name using default domain and path.
cookies.delete("session");
// Delete with domain/path options.
cookies.delete({
name: "session",
domain: "example.com",
path: "/admin",
});
```
#### `toJSON(): Record<string, string>`
Converts the cookie map to a serializable format.
```ts
const json = cookies.toJSON();
```
#### `toSetCookieHeaders(): string[]`
Returns an array of values for Set-Cookie headers that can be used to apply all cookie changes.
When using `Bun.serve()`, you don't need to call this method explicitly. Any changes made to the `req.cookies` map are automatically applied to the response headers. This method is primarily useful when working with other HTTP server implementations.
```js
import { createServer } from "node:http";
import { CookieMap } from "bun";
const server = createServer((req, res) => {
const cookieHeader = req.headers.cookie || "";
const cookies = new CookieMap(cookieHeader);
cookies.set("view-count", Number(cookies.get("view-count") || "0") + 1);
cookies.delete("session");
res.writeHead(200, {
"Content-Type": "text/plain",
"Set-Cookie": cookies.toSetCookieHeaders(),
});
res.end(`Found ${cookies.size} cookies`);
});
server.listen(3000, () => {
console.log("Server running at http://localhost:3000/");
});
```
### Iteration
`CookieMap` provides several methods for iteration:
```ts
// Iterate over [name, cookie] entries
for (const [name, value] of cookies) {
console.log(`${name}: ${value}`);
}
// Using entries()
for (const [name, value] of cookies.entries()) {
console.log(`${name}: ${value}`);
}
// Using keys()
for (const name of cookies.keys()) {
console.log(name);
}
// Using values()
for (const value of cookies.values()) {
console.log(value);
}
// Using forEach
cookies.forEach((value, name) => {
console.log(`${name}: ${value}`);
});
```
### Properties
#### `size: number`
Returns the number of cookies in the map.
```ts
console.log(cookies.size); // Number of cookies
```
## Cookie class
`Bun.Cookie` represents an HTTP cookie with its name, value, and attributes.
```ts
import { Cookie } from "bun";
// Create a basic cookie
const cookie = new Bun.Cookie("name", "value");
// Create a cookie with options
const secureSessionCookie = new Bun.Cookie("session", "abc123", {
domain: "example.com",
path: "/admin",
expires: new Date(Date.now() + 86400000), // 1 day
httpOnly: true,
secure: true,
sameSite: "strict",
});
// Parse from a cookie string
const parsedCookie = new Bun.Cookie("name=value; Path=/; HttpOnly");
// Create from an options object
const objCookie = new Bun.Cookie({
name: "theme",
value: "dark",
maxAge: 3600,
secure: true,
});
```
### Constructors
```ts
// Basic constructor with name/value
new Bun.Cookie(name: string, value: string);
// Constructor with name, value, and options
new Bun.Cookie(name: string, value: string, options: CookieInit);
// Constructor from cookie string
new Bun.Cookie(cookieString: string);
// Constructor from cookie object
new Bun.Cookie(options: CookieInit);
```
### Properties
```ts
cookie.name; // string - Cookie name
cookie.value; // string - Cookie value
cookie.domain; // string | null - Domain scope (null if not specified)
cookie.path; // string - URL path scope (defaults to "/")
cookie.expires; // number | undefined - Expiration timestamp (ms since epoch)
cookie.secure; // boolean - Require HTTPS
cookie.sameSite; // "strict" | "lax" | "none" - SameSite setting
cookie.partitioned; // boolean - Whether the cookie is partitioned (CHIPS)
cookie.maxAge; // number | undefined - Max age in seconds
cookie.httpOnly; // boolean - Accessible only via HTTP (not JavaScript)
```
### Methods
#### `isExpired(): boolean`
Checks if the cookie has expired.
```ts
// Expired cookie (Date in the past)
const expiredCookie = new Bun.Cookie("name", "value", {
expires: new Date(Date.now() - 1000),
});
console.log(expiredCookie.isExpired()); // true
// Valid cookie (Using maxAge instead of expires)
const validCookie = new Bun.Cookie("name", "value", {
maxAge: 3600, // 1 hour in seconds
});
console.log(validCookie.isExpired()); // false
// Session cookie (no expiration)
const sessionCookie = new Bun.Cookie("name", "value");
console.log(sessionCookie.isExpired()); // false
```
#### `serialize(): string`
#### `toString(): string`
Returns a string representation of the cookie suitable for a `Set-Cookie` header.
```ts
const cookie = new Bun.Cookie("session", "abc123", {
domain: "example.com",
path: "/admin",
expires: new Date(Date.now() + 86400000),
secure: true,
httpOnly: true,
sameSite: "strict",
});
console.log(cookie.serialize());
// => "session=abc123; Domain=example.com; Path=/admin; Expires=Sun, 19 Mar 2025 15:03:26 GMT; Secure; HttpOnly; SameSite=strict"
console.log(cookie.toString());
// => "session=abc123; Domain=example.com; Path=/admin; Expires=Sun, 19 Mar 2025 15:03:26 GMT; Secure; HttpOnly; SameSite=strict"
```
#### `toJSON(): CookieInit`
Converts the cookie to a plain object suitable for JSON serialization.
```ts
const cookie = new Bun.Cookie("session", "abc123", {
secure: true,
httpOnly: true,
});
const json = cookie.toJSON();
// => {
// name: "session",
// value: "abc123",
// path: "/",
// secure: true,
// httpOnly: true,
// sameSite: "lax",
// partitioned: false
// }
// Works with JSON.stringify
const jsonString = JSON.stringify(cookie);
```
### Static methods
#### `Cookie.parse(cookieString: string): Cookie`
Parses a cookie string into a `Cookie` instance.
```ts
const cookie = Bun.Cookie.parse("name=value; Path=/; Secure; SameSite=Lax");
console.log(cookie.name); // "name"
console.log(cookie.value); // "value"
console.log(cookie.path); // "/"
console.log(cookie.secure); // true
console.log(cookie.sameSite); // "lax"
```
#### `Cookie.from(name: string, value: string, options?: CookieInit): Cookie`
Factory method to create a cookie.
```ts
const cookie = Bun.Cookie.from("session", "abc123", {
httpOnly: true,
secure: true,
maxAge: 3600,
});
```
## Types
```ts
interface CookieInit {
name?: string;
value?: string;
domain?: string;
/** Defaults to '/'. To allow the browser to set the path, use an empty string. */
path?: string;
expires?: number | Date | string;
secure?: boolean;
/** Defaults to `lax`. */
sameSite?: CookieSameSite;
httpOnly?: boolean;
partitioned?: boolean;
maxAge?: number;
}
interface CookieStoreDeleteOptions {
name: string;
domain?: string | null;
path?: string;
}
interface CookieStoreGetOptions {
name?: string;
url?: string;
}
type CookieSameSite = "strict" | "lax" | "none";
class Cookie {
constructor(name: string, value: string, options?: CookieInit);
constructor(cookieString: string);
constructor(cookieObject?: CookieInit);
readonly name: string;
value: string;
domain?: string;
path: string;
expires?: Date;
secure: boolean;
sameSite: CookieSameSite;
partitioned: boolean;
maxAge?: number;
httpOnly: boolean;
isExpired(): boolean;
serialize(): string;
toString(): string;
toJSON(): CookieInit;
static parse(cookieString: string): Cookie;
static from(name: string, value: string, options?: CookieInit): Cookie;
}
class CookieMap implements Iterable<[string, string]> {
constructor(init?: string[][] | Record<string, string> | string);
get(name: string): string | null;
toSetCookieHeaders(): string[];
has(name: string): boolean;
set(name: string, value: string, options?: CookieInit): void;
set(options: CookieInit): void;
delete(name: string): void;
delete(options: CookieStoreDeleteOptions): void;
delete(name: string, options: Omit<CookieStoreDeleteOptions, "name">): void;
toJSON(): Record<string, string>;
readonly size: number;
entries(): IterableIterator<[string, string]>;
keys(): IterableIterator<string>;
values(): IterableIterator<string>;
forEach(callback: (value: string, key: string, map: CookieMap) => void): void;
[Symbol.iterator](): IterableIterator<[string, string]>;
}
```

View File

@@ -61,7 +61,6 @@ Routes in `Bun.serve()` receive a `BunRequest` (which extends [`Request`](https:
// Simplified for brevity
interface BunRequest<T extends string> extends Request {
params: Record<T, string>;
readonly cookies: CookieMap;
}
```
@@ -935,83 +934,6 @@ const server = Bun.serve({
Returns `null` for closed requests or Unix domain sockets.
## Working with Cookies
Bun provides a built-in API for working with cookies in HTTP requests and responses. The `BunRequest` object includes a `cookies` property that provides a `CookieMap` for easily accessing and manipulating cookies. When using `routes`, `Bun.serve()` automatically tracks `request.cookies.set` and applies them to the response.
### Reading cookies
Read cookies from incoming requests using the `cookies` property on the `BunRequest` object:
```ts
Bun.serve({
routes: {
"/profile": req => {
// Access cookies from the request
const userId = req.cookies.get("user_id");
const theme = req.cookies.get("theme") || "light";
return Response.json({
userId,
theme,
message: "Profile page",
});
},
},
});
```
### Setting cookies
To set cookies, use the `set` method on the `CookieMap` from the `BunRequest` object.
```ts
Bun.serve({
routes: {
"/login": req => {
const cookies = req.cookies;
// Set a cookie with various options
cookies.set("user_id", "12345", {
maxAge: 60 * 60 * 24 * 7, // 1 week
httpOnly: true,
secure: true,
path: "/",
});
// Add a theme preference cookie
cookies.set("theme", "dark");
// Modified cookies from the request are automatically applied to the response
return new Response("Login successful");
},
},
});
```
`Bun.serve()` automatically tracks modified cookies from the request and applies them to the response.
### Deleting cookies
To delete a cookie, use the `delete` method on the `request.cookies` (`CookieMap`) object:
```ts
Bun.serve({
routes: {
"/logout": req => {
// Delete the user_id cookie
req.cookies.delete("user_id", {
path: "/",
});
return new Response("Logged out successfully");
},
},
});
```
Deleted cookies become a `Set-Cookie` header on the response with the `maxAge` set to `0` and an empty `value`.
## Server Metrics
### server.pendingRequests and server.pendingWebSockets

View File

@@ -1,492 +0,0 @@
Bun provides native bindings for working with Redis databases with a modern, Promise-based API. The interface is designed to be simple and performant, with built-in connection management, fully typed responses, and TLS support. **New in Bun v1.2.9**
```ts
import { redis } from "bun";
// Set a key
await redis.set("greeting", "Hello from Bun!");
// Get a key
const greeting = await redis.get("greeting");
console.log(greeting); // "Hello from Bun!"
// Increment a counter
await redis.set("counter", 0);
await redis.incr("counter");
// Check if a key exists
const exists = await redis.exists("greeting");
// Delete a key
await redis.del("greeting");
```
## Getting Started
To use the Redis client, you first need to create a connection:
```ts
import { redis, RedisClient } from "bun";
// Using the default client (reads connection info from environment)
// process.env.REDIS_URL is used by default
await redis.set("hello", "world");
const result = await redis.get("hello");
// Creating a custom client
const client = new RedisClient("redis://username:password@localhost:6379");
await client.set("counter", "0");
await client.incr("counter");
```
By default, the client reads connection information from the following environment variables (in order of precedence):
- `REDIS_URL`
- If not set, defaults to `"redis://localhost:6379"`
### Connection Lifecycle
The Redis client automatically handles connections in the background:
```ts
// No connection is made until a command is executed
const client = new RedisClient();
// First command initiates the connection
await client.set("key", "value");
// Connection remains open for subsequent commands
await client.get("key");
// Explicitly close the connection when done
client.close();
```
You can also manually control the connection lifecycle:
```ts
const client = new RedisClient();
// Explicitly connect
await client.connect();
// Run commands
await client.set("key", "value");
// Disconnect when done
client.close();
```
## Basic Operations
### String Operations
```ts
// Set a key
await redis.set("user:1:name", "Alice");
// Get a key
const name = await redis.get("user:1:name");
// Delete a key
await redis.del("user:1:name");
// Check if a key exists
const exists = await redis.exists("user:1:name");
// Set expiration (in seconds)
await redis.set("session:123", "active");
await redis.expire("session:123", 3600); // expires in 1 hour
// Get time to live (in seconds)
const ttl = await redis.ttl("session:123");
```
### Numeric Operations
```ts
// Set initial value
await redis.set("counter", "0");
// Increment by 1
await redis.incr("counter");
// Decrement by 1
await redis.decr("counter");
```
### Hash Operations
```ts
// Set multiple fields in a hash
await redis.hmset("user:123", [
"name",
"Alice",
"email",
"alice@example.com",
"active",
"true",
]);
// Get multiple fields from a hash
const userFields = await redis.hmget("user:123", ["name", "email"]);
console.log(userFields); // ["Alice", "alice@example.com"]
// Increment a numeric field in a hash
await redis.hincrby("user:123", "visits", 1);
// Increment a float field in a hash
await redis.hincrbyfloat("user:123", "score", 1.5);
```
### Set Operations
```ts
// Add member to set
await redis.sadd("tags", "javascript");
// Remove member from set
await redis.srem("tags", "javascript");
// Check if member exists in set
const isMember = await redis.sismember("tags", "javascript");
// Get all members of a set
const allTags = await redis.smembers("tags");
// Get a random member
const randomTag = await redis.srandmember("tags");
// Pop (remove and return) a random member
const poppedTag = await redis.spop("tags");
```
## Advanced Usage
### Command Execution and Pipelining
The client automatically pipelines commands, improving performance by sending multiple commands in a batch and processing responses as they arrive.
```ts
// Commands are automatically pipelined by default
const [infoResult, listResult] = await Promise.all([
redis.get("user:1:name"),
redis.get("user:2:email"),
]);
```
To disable automatic pipelining, you can set the `enableAutoPipelining` option to `false`:
```ts
const client = new RedisClient("redis://localhost:6379", {
enableAutoPipelining: false,
});
```
### Raw Commands
When you need to use commands that don't have convenience methods, you can use the `send` method:
```ts
// Run any Redis command
const info = await redis.send("INFO", []);
// LPUSH to a list
await redis.send("LPUSH", ["mylist", "value1", "value2"]);
// Get list range
const list = await redis.send("LRANGE", ["mylist", "0", "-1"]);
```
The `send` method allows you to use any Redis command, even ones that don't have dedicated methods in the client. The first argument is the command name, and the second argument is an array of string arguments.
### Connection Events
You can register handlers for connection events:
```ts
const client = new RedisClient();
// Called when successfully connected to Redis server
client.onconnect = () => {
console.log("Connected to Redis server");
};
// Called when disconnected from Redis server
client.onclose = error => {
console.error("Disconnected from Redis server:", error);
};
// Manually connect/disconnect
await client.connect();
client.close();
```
### Connection Status and Monitoring
```ts
// Check if connected
console.log(client.connected); // boolean indicating connection status
// Check amount of data buffered (in bytes)
console.log(client.bufferedAmount);
```
### Type Conversion
The Redis client handles automatic type conversion for Redis responses:
- Integer responses are returned as JavaScript numbers
- Bulk strings are returned as JavaScript strings
- Simple strings are returned as JavaScript strings
- Null bulk strings are returned as `null`
- Array responses are returned as JavaScript arrays
- Error responses throw JavaScript errors with appropriate error codes
- Boolean responses (RESP3) are returned as JavaScript booleans
- Map responses (RESP3) are returned as JavaScript objects
- Set responses (RESP3) are returned as JavaScript arrays
Special handling for specific commands:
- `EXISTS` returns a boolean instead of a number (1 becomes true, 0 becomes false)
- `SISMEMBER` returns a boolean (1 becomes true, 0 becomes false)
The following commands disable automatic pipelining:
- `AUTH`
- `INFO`
- `QUIT`
- `EXEC`
- `MULTI`
- `WATCH`
- `SCRIPT`
- `SELECT`
- `CLUSTER`
- `DISCARD`
- `UNWATCH`
- `PIPELINE`
- `SUBSCRIBE`
- `UNSUBSCRIBE`
- `UNPSUBSCRIBE`
## Connection Options
When creating a client, you can pass various options to configure the connection:
```ts
const client = new RedisClient("redis://localhost:6379", {
// Connection timeout in milliseconds (default: 10000)
connectionTimeout: 5000,
// Idle timeout in milliseconds (default: 0 = no timeout)
idleTimeout: 30000,
// Whether to automatically reconnect on disconnection (default: true)
autoReconnect: true,
// Maximum number of reconnection attempts (default: 10)
maxRetries: 10,
// Whether to queue commands when disconnected (default: true)
enableOfflineQueue: true,
// Whether to automatically pipeline commands (default: true)
enableAutoPipelining: true,
// TLS options (default: false)
tls: true,
// Alternatively, provide custom TLS config:
// tls: {
// rejectUnauthorized: true,
// ca: "path/to/ca.pem",
// cert: "path/to/cert.pem",
// key: "path/to/key.pem",
// }
});
```
### Reconnection Behavior
When a connection is lost, the client automatically attempts to reconnect with exponential backoff:
1. The client starts with a small delay (50ms) and doubles it with each attempt
2. Reconnection delay is capped at 2000ms (2 seconds)
3. The client attempts to reconnect up to `maxRetries` times (default: 10)
4. Commands executed during disconnection are:
- Queued if `enableOfflineQueue` is true (default)
- Rejected immediately if `enableOfflineQueue` is false
## Supported URL Formats
The Redis client supports various URL formats:
```ts
// Standard Redis URL
new RedisClient("redis://localhost:6379");
new RedisClient("redis://localhost:6379");
// With authentication
new RedisClient("redis://username:password@localhost:6379");
// With database number
new RedisClient("redis://localhost:6379/0");
// TLS connections
new RedisClient("rediss://localhost:6379");
new RedisClient("rediss://localhost:6379");
new RedisClient("redis+tls://localhost:6379");
new RedisClient("redis+tls://localhost:6379");
// Unix socket connections
new RedisClient("redis+unix:///path/to/socket");
new RedisClient("redis+unix:///path/to/socket");
// TLS over Unix socket
new RedisClient("redis+tls+unix:///path/to/socket");
new RedisClient("redis+tls+unix:///path/to/socket");
```
## Error Handling
The Redis client throws typed errors for different scenarios:
```ts
try {
await redis.get("non-existent-key");
} catch (error) {
if (error.code === "ERR_REDIS_CONNECTION_CLOSED") {
console.error("Connection to Redis server was closed");
} else if (error.code === "ERR_REDIS_AUTHENTICATION_FAILED") {
console.error("Authentication failed");
} else {
console.error("Unexpected error:", error);
}
}
```
Common error codes:
- `ERR_REDIS_CONNECTION_CLOSED` - Connection to the server was closed
- `ERR_REDIS_AUTHENTICATION_FAILED` - Failed to authenticate with the server
- `ERR_REDIS_INVALID_RESPONSE` - Received an invalid response from the server
## Example Use Cases
### Caching
```ts
async function getUserWithCache(userId) {
const cacheKey = `user:${userId}`;
// Try to get from cache first
const cachedUser = await redis.get(cacheKey);
if (cachedUser) {
return JSON.parse(cachedUser);
}
// Not in cache, fetch from database
const user = await database.getUser(userId);
// Store in cache for 1 hour
await redis.set(cacheKey, JSON.stringify(user));
await redis.expire(cacheKey, 3600);
return user;
}
```
### Rate Limiting
```ts
async function rateLimit(ip, limit = 100, windowSecs = 3600) {
const key = `ratelimit:${ip}`;
// Increment counter
const count = await redis.incr(key);
// Set expiry if this is the first request in window
if (count === 1) {
await redis.expire(key, windowSecs);
}
// Check if limit exceeded
return {
limited: count > limit,
remaining: Math.max(0, limit - count),
};
}
```
### Session Storage
```ts
async function createSession(userId, data) {
const sessionId = crypto.randomUUID();
const key = `session:${sessionId}`;
// Store session with expiration
await redis.hmset(key, [
"userId",
userId.toString(),
"created",
Date.now().toString(),
"data",
JSON.stringify(data),
]);
await redis.expire(key, 86400); // 24 hours
return sessionId;
}
async function getSession(sessionId) {
const key = `session:${sessionId}`;
// Get session data
const exists = await redis.exists(key);
if (!exists) return null;
const [userId, created, data] = await redis.hmget(key, [
"userId",
"created",
"data",
]);
return {
userId: Number(userId),
created: Number(created),
data: JSON.parse(data),
};
}
```
## Implementation Notes
Bun's Redis client is implemented in Zig and uses the Redis Serialization Protocol (RESP3). It manages connections efficiently and provides automatic reconnection with exponential backoff.
The client supports pipelining commands, meaning multiple commands can be sent without waiting for the replies to previous commands. This significantly improves performance when sending multiple commands in succession.
### RESP3 Protocol Support
Bun's Redis client uses the newer RESP3 protocol by default, which provides more data types and features compared to RESP2:
- Better error handling with typed errors
- Native Boolean responses
- Map/Dictionary responses (key-value objects)
- Set responses
- Double (floating point) values
- BigNumber support for large integer values
When connecting to Redis servers using older versions that don't support RESP3, the client automatically fallbacks to compatible modes.
## Limitations and Future Plans
Current limitations of the Redis client we are planning to address in future versions:
- [ ] No dedicated API for pub/sub functionality (though you can use the raw command API)
- [ ] Transactions (MULTI/EXEC) must be done through raw commands for now
- [ ] Streams are supported but without dedicated methods
Unsupported features:
- Redis Sentinel
- Redis Cluster

View File

@@ -619,48 +619,6 @@ When the S3 Object Storage service returns an error (that is, not Bun), it will
The `S3Client` class provides several static methods for interacting with S3.
### `S3Client.write` (static)
To write data directly to a path in the bucket, you can use the `S3Client.write` static method.
```ts
import { S3Client } from "bun";
const credentials = {
accessKeyId: "your-access-key",
secretAccessKey: "your-secret-key",
bucket: "my-bucket",
// endpoint: "https://s3.us-east-1.amazonaws.com",
// endpoint: "https://<account-id>.r2.cloudflarestorage.com", // Cloudflare R2
};
// Write string
await S3Client.write("my-file.txt", "Hello World");
// Write JSON with type
await S3Client.write(
"data.json",
JSON.stringify({hello: "world"}),
{
...credentials,
type: "application/json",
}
);
// Write from fetch
const res = await fetch("https://example.com/data");
await S3Client.write("data.bin", res, credentials);
// Write with ACL
await S3Client.write("public.html", html, {
...credentials,
acl: "public-read",
type: "text/html"
});
```
This is equivalent to calling `new S3Client(credentials).write("my-file.txt", "Hello World")`.
### `S3Client.presign` (static)
To generate a presigned URL for an S3 file, you can use the `S3Client.presign` static method.
@@ -684,45 +642,6 @@ const url = S3Client.presign("my-file.txt", {
This is equivalent to calling `new S3Client(credentials).presign("my-file.txt", { expiresIn: 3600 })`.
### `S3Client.list` (static)
To list some or all (up to 1,000) objects in a bucket, you can use the `S3Client.list` static method.
```ts
import { S3Client } from "bun";
const credentials = {
accessKeyId: "your-access-key",
secretAccessKey: "your-secret-key",
bucket: "my-bucket",
// endpoint: "https://s3.us-east-1.amazonaws.com",
// endpoint: "https://<account-id>.r2.cloudflarestorage.com", // Cloudflare R2
};
// List (up to) 1000 objects in the bucket
const allObjects = await S3Client.list(null, credentials);
// List (up to) 500 objects under `uploads/` prefix, with owner field for each object
const uploads = await S3Client.list({
prefix: 'uploads/',
maxKeys: 500,
fetchOwner: true,
}, credentials);
// Check if more results are available
if (uploads.isTruncated) {
// List next batch of objects under `uploads/` prefix
const moreUploads = await S3Client.list({
prefix: 'uploads/',
maxKeys: 500,
startAfter: uploads.contents!.at(-1).key
fetchOwner: true,
}, credentials);
}
```
This is equivalent to calling `new S3Client(credentials).list()`.
### `S3Client.exists` (static)
To check if an S3 file exists, you can use the `S3Client.exists` static method.
@@ -735,7 +654,6 @@ const credentials = {
secretAccessKey: "your-secret-key",
bucket: "my-bucket",
// endpoint: "https://s3.us-east-1.amazonaws.com",
// endpoint: "https://<account-id>.r2.cloudflarestorage.com", // Cloudflare R2
};
const exists = await S3Client.exists("my-file.txt", credentials);
@@ -752,26 +670,6 @@ const s3file = s3.file("my-file.txt", {
const exists = await s3file.exists();
```
### `S3Client.size` (static)
To quickly check the size of S3 file without downloading it, you can use the `S3Client.size` static method.
```ts
import { S3Client } from "bun";
const credentials = {
accessKeyId: "your-access-key",
secretAccessKey: "your-secret-key",
bucket: "my-bucket",
// endpoint: "https://s3.us-east-1.amazonaws.com",
// endpoint: "https://<account-id>.r2.cloudflarestorage.com", // Cloudflare R2
};
const bytes = await S3Client.size("my-file.txt", credentials);
```
This is equivalent to calling `new S3Client(credentials).size("my-file.txt")`.
### `S3Client.stat` (static)
To get the size, etag, and other metadata of an S3 file, you can use the `S3Client.stat` static method.
@@ -784,7 +682,6 @@ const credentials = {
secretAccessKey: "your-secret-key",
bucket: "my-bucket",
// endpoint: "https://s3.us-east-1.amazonaws.com",
// endpoint: "https://<account-id>.r2.cloudflarestorage.com", // Cloudflare R2
};
const stat = await S3Client.stat("my-file.txt", credentials);

View File

@@ -253,19 +253,6 @@ const proc = Bun.spawn({
The `killSignal` option also controls which signal is sent when an AbortSignal is aborted.
## Using maxBuffer
For spawnSync, you can limit the maximum number of bytes of output before the process is killed:
```ts
// KIll 'yes' after it emits over 100 bytes of output
const result = Bun.spawnSync({
cmd: ["yes"], // or ["bun", "exec", "yes"] on windows
maxBuffer: 100,
});
// process exits
```
## Inter-process communication (IPC)
Bun supports direct inter-process communication channel between two `bun` processes. To receive messages from a spawned Bun subprocess, specify an `ipc` handler.
@@ -436,7 +423,6 @@ namespace SpawnOptions {
signal?: AbortSignal;
timeout?: number;
killSignal?: string | number;
maxBuffer?: number;
}
type Readable =

View File

@@ -240,7 +240,7 @@ const result = await sql.unsafe(
### Execute and Cancelling Queries
Bun's SQL is lazy, which means it will only start executing when awaited or executed with `.execute()`.
Bun's SQL is lazy that means its will only start executing when awaited or executed with `.execute()`.
You can cancel a query that is currently executing by calling the `cancel()` method on the query object.
```ts

View File

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

View File

@@ -265,25 +265,12 @@ export default {
page("test/time", "Dates and times", {
description: "Control the date & time in your tests for more reliable and deterministic tests",
}),
page("test/coverage", "Code coverage", {
description: "Generate code coverage reports with `bun test --coverage`",
}),
page("test/reporters", "Test reporters", {
description: "Add a junit reporter to your test runs",
}),
page("test/configuration", "Test configuration", {
description: "Configure the test runner with bunfig.toml",
}),
page("test/runtime-behavior", "Runtime behavior", {
description: "Learn how the test runner affects Bun's runtime behavior",
}),
page("test/discovery", "Finding tests", {
description: "Learn how the test runner discovers tests",
}),
page("test/dom", "DOM testing", {
description: "Write headless tests for UI and React/Vue/Svelte/Lit components with happy-dom",
}),
page("test/coverage", "Code coverage", {
description: "Generate code coverage reports with `bun test --coverage`",
}),
divider("Package runner"),
page("cli/bunx", "`bunx`", {
@@ -344,9 +331,6 @@ export default {
page("api/file-io", "File I/O", {
description: `Read and write files fast with Bun's heavily optimized file system API.`,
}), // "`Bun.write`"),
page("api/redis", "Redis client", {
description: `Bun provides a fast, native Redis client with automatic command pipelining for better performance.`,
}),
page("api/import-meta", "import.meta", {
description: `Module-scoped metadata and utilities`,
}), // "`bun:sqlite`"),
@@ -371,24 +355,24 @@ export default {
page("api/spawn", "Child processes", {
description: `Spawn sync and async child processes with easily configurable input and output streams.`,
}), // "`Bun.spawn`"),
page("api/html-rewriter", "HTMLRewriter", {
description: `Parse and transform HTML with Bun's native HTMLRewriter API, inspired by Cloudflare Workers.`,
}), // "`HTMLRewriter`"),
page("api/transpiler", "Transpiler", {
description: `Bun exposes its internal transpiler as a pluggable API.`,
}), // "`Bun.Transpiler`"),
page("api/hashing", "Hashing", {
description: `Native support for a range of fast hashing algorithms.`,
}), // "`Bun.serve`"),
page("api/console", "Console", {
description: `Bun implements a Node.js-compatible \`console\` object with colorized output and deep pretty-printing.`,
}), // "`Node-API`"),
page("api/cookie", "Cookie", {
description: "Bun's native Cookie API simplifies working with HTTP cookies.",
}), // "`Node-API`"),
page("api/ffi", "FFI", {
description: `Call native code from JavaScript with Bun's foreign function interface (FFI) API.`,
}), // "`bun:ffi`"),
page("api/cc", "C Compiler", {
description: `Build & run native C from JavaScript with Bun's native C compiler API`,
}), // "`bun:ffi`"),
page("api/html-rewriter", "HTMLRewriter", {
description: `Parse and transform HTML with Bun's native HTMLRewriter API, inspired by Cloudflare Workers.`,
}), // "`HTMLRewriter`"),
page("api/test", "Testing", {
description: `Bun's built-in test runner is fast and uses Jest-compatible syntax.`,
}), // "`bun:test`"),
@@ -414,9 +398,6 @@ export default {
page("api/color", "Color", {
description: `Bun's color function leverages Bun's CSS parser for parsing, normalizing, and converting colors from user input to a variety of output formats.`,
}), // "`Color`"),
page("api/transpiler", "Transpiler", {
description: `Bun exposes its internal transpiler as a pluggable API.`,
}), // "`Bun.Transpiler`"),
// divider("Dev Server"),
// page("bun-dev", "Vanilla"),

View File

@@ -32,7 +32,7 @@ pub fn add(global: *JSC.JSGlobalObject, a: i32, b: i32) !i32 {
const gen = bun.gen.math; // "math" being this file's basename
const std = @import("std");
const bun = @import("bun");
const bun = @import("root").bun;
const JSC = bun.JSC;
```

View File

@@ -104,7 +104,9 @@ This page is updated regularly to reflect compatibility status of the latest ver
### [`node:crypto`](https://nodejs.org/api/crypto.html)
🟡 Missing `secureHeapUsed` `setEngine` `setFips`
🟡 Missing `ECDH` `checkPrime` `checkPrimeSync` `generatePrime` `generatePrimeSync` `hkdf` `hkdfSync` `secureHeapUsed` `setEngine` `setFips`
Some methods are not optimized yet.
### [`node:domain`](https://nodejs.org/api/domain.html)
@@ -116,7 +118,7 @@ This page is updated regularly to reflect compatibility status of the latest ver
### [`node:module`](https://nodejs.org/api/module.html)
🟡 Missing `syncBuiltinESMExports`, `Module#load()`. Overriding `require.cache` is supported for ESM & CJS modules. `module._extensions`, `module._pathCache`, `module._cache` are no-ops. `module.register` is not implemented and we recommend using a [`Bun.plugin`](https://bun.sh/docs/runtime/plugins) in the meantime.
🟡 Missing `runMain` `syncBuiltinESMExports`, `Module#load()`. Overriding `require.cache` is supported for ESM & CJS modules. `module._extensions`, `module._pathCache`, `module._cache` are no-ops. `module.register` is not implemented and we recommend using a [`Bun.plugin`](https://bun.sh/docs/runtime/plugins) in the meantime.
### [`node:net`](https://nodejs.org/api/net.html)
@@ -376,7 +378,7 @@ The table below lists all globals implemented by Node.js and Bun's current compa
### [`require()`](https://nodejs.org/api/globals.html#require)
🟢 Fully implemented, including [`require.main`](https://nodejs.org/api/modules.html#requiremain), [`require.cache`](https://nodejs.org/api/modules.html#requirecache), [`require.resolve`](https://nodejs.org/api/modules.html#requireresolverequest-options).
🟢 Fully implemented, including [`require.main`](https://nodejs.org/api/modules.html#requiremain), [`require.cache`](https://nodejs.org/api/modules.html#requirecache), [`require.resolve`](https://nodejs.org/api/modules.html#requireresolverequest-options). `require.extensions` is a stub.
### [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response)

View File

@@ -1,87 +0,0 @@
Configure `bun test` via `bunfig.toml` file and command-line options. This page documents the available configuration options for `bun test`.
## bunfig.toml options
You can configure `bun test` behavior by adding a `[test]` section to your `bunfig.toml` file:
```toml
[test]
# Options go here
```
### Test discovery
#### root
The `root` option specifies a root directory for test discovery, overriding the default behavior of scanning from the project root.
```toml
[test]
root = "src" # Only scan for tests in the src directory
```
### Reporters
#### reporter.junit
Configure the JUnit reporter output file path directly in the config file:
```toml
[test.reporter]
junit = "path/to/junit.xml" # Output path for JUnit XML report
```
This complements the `--reporter=junit` and `--reporter-outfile` CLI flags.
### Memory usage
#### smol
Enable the `--smol` memory-saving mode specifically for the test runner:
```toml
[test]
smol = true # Reduce memory usage during test runs
```
This is equivalent to using the `--smol` flag on the command line.
### Coverage options
In addition to the options documented in the [coverage documentation](./coverage.md), the following options are available:
#### coverageSkipTestFiles
Exclude files matching test patterns (e.g., \*.test.ts) from the coverage report:
```toml
[test]
coverageSkipTestFiles = true # Exclude test files from coverage reports
```
#### coverageThreshold (Object form)
The coverage threshold can be specified either as a number (as shown in the coverage documentation) or as an object with specific thresholds:
```toml
[test]
# Set specific thresholds for different coverage metrics
coverageThreshold = { lines = 0.9, functions = 0.8, statements = 0.85 }
```
Setting any of these enables `fail_on_low_coverage`, causing the test run to fail if coverage is below the threshold.
#### coverageIgnoreSourcemaps
Internally, Bun transpiles every file. That means code coverage must also go through sourcemaps before they can be reported. We expose this as a flag to allow you to opt out of this behavior, but it will be confusing because during the transpilation process, Bun may move code around and change variable names. This option is mostly useful for debugging coverage issues.
```toml
[test]
coverageIgnoreSourcemaps = true # Don't use sourcemaps for coverage analysis
```
When using this option, you probably want to stick a `// @bun` comment at the top of the source file to opt out of the transpilation process.
### Install settings inheritance
The `bun test` command inherits relevant network and installation configuration (registry, cafile, prefer, exact, etc.) from the `[install]` section of bunfig.toml. This is important if tests need to interact with private registries or require specific install behaviors triggered during the test run.

View File

@@ -52,22 +52,9 @@ It is possible to specify a coverage threshold in `bunfig.toml`. If your test su
coverageThreshold = 0.9
# to set different thresholds for lines and functions
coverageThreshold = { lines = 0.9, functions = 0.9, statements = 0.9 }
coverageThreshold = { lines = 0.9, functions = 0.9 }
```
Setting any of these thresholds enables `fail_on_low_coverage`, causing the test run to fail if coverage is below the threshold.
### Exclude test files from coverage
By default, test files themselves are included in coverage reports. You can exclude them with:
```toml
[test]
coverageSkipTestFiles = true # default false
```
This will exclude files matching test patterns (e.g., _.test.ts, _\_spec.js) from the coverage report.
### Sourcemaps
Internally, Bun transpiles all files by default, so Bun automatically generates an internal [source map](https://web.dev/source-maps/) that maps lines of your original source code onto Bun's internal representation. If for any reason you want to disable this, set `test.coverageIgnoreSourcemaps` to `true`; this will rarely be desirable outside of advanced use cases.
@@ -77,14 +64,6 @@ Internally, Bun transpiles all files by default, so Bun automatically generates
coverageIgnoreSourcemaps = true # default false
```
### Coverage defaults
By default, coverage reports:
1. Exclude `node_modules` directories
2. Exclude files loaded via non-JS/TS loaders (e.g., .css, .txt) unless a custom JS loader is specified
3. Include test files themselves (can be disabled with `coverageSkipTestFiles = true` as shown above)
### Coverage reporters
By default, coverage reports will be printed to the console.

View File

@@ -1,85 +0,0 @@
bun test's file discovery mechanism determines which files to run as tests. Understanding how it works helps you structure your test files effectively.
## Default Discovery Logic
By default, `bun test` recursively searches the project directory for files that match specific patterns:
- `*.test.{js|jsx|ts|tsx}` - Files ending with `.test.js`, `.test.jsx`, `.test.ts`, or `.test.tsx`
- `*_test.{js|jsx|ts|tsx}` - Files ending with `_test.js`, `_test.jsx`, `_test.ts`, or `_test.tsx`
- `*.spec.{js|jsx|ts|tsx}` - Files ending with `.spec.js`, `.spec.jsx`, `.spec.ts`, or `.spec.tsx`
- `*_spec.{js|jsx|ts|tsx}` - Files ending with `_spec.js`, `_spec.jsx`, `_spec.ts`, or `_spec.tsx`
## Exclusions
By default, Bun test ignores:
- `node_modules` directories
- Hidden directories (those starting with a period `.`)
- Files that don't have JavaScript-like extensions (based on available loaders)
## Customizing Test Discovery
### Position Arguments as Filters
You can filter which test files run by passing additional positional arguments to `bun test`:
```bash
$ bun test <filter> <filter> ...
```
Any test file with a path that contains one of the filters will run. These filters are simple substring matches, not glob patterns.
For example, to run all tests in a `utils` directory:
```bash
$ bun test utils
```
This would match files like `src/utils/string.test.ts` and `lib/utils/array_test.js`.
### Specifying Exact File Paths
To run a specific file in the test runner, make sure the path starts with `./` or `/` to distinguish it from a filter name:
```bash
$ bun test ./test/specific-file.test.ts
```
### Filter by Test Name
To filter tests by name rather than file path, use the `-t`/`--test-name-pattern` flag with a regex pattern:
```sh
# run all tests with "addition" in the name
$ bun test --test-name-pattern addition
```
The pattern is matched against a concatenated string of the test name prepended with the labels of all its parent describe blocks, separated by spaces. For example, a test defined as:
```js
describe("Math", () => {
describe("operations", () => {
test("should add correctly", () => {
// ...
});
});
});
```
Would be matched against the string "Math operations should add correctly".
### Changing the Root Directory
By default, Bun looks for test files starting from the current working directory. You can change this with the `root` option in your `bunfig.toml`:
```toml
[test]
root = "src" # Only scan for tests in the src directory
```
## Execution Order
Tests are run in the following order:
1. Test files are executed sequentially (not in parallel)
2. Within each file, tests run sequentially based on their definition order

View File

@@ -56,9 +56,9 @@ The following properties and methods are implemented on mock functions.
- [x] [mockFn.mock.instances](https://jestjs.io/docs/mock-function-api#mockfnmockinstances)
- [x] [mockFn.mock.contexts](https://jestjs.io/docs/mock-function-api#mockfnmockcontexts)
- [x] [mockFn.mock.lastCall](https://jestjs.io/docs/mock-function-api#mockfnmocklastcall)
- [x] [mockFn.mockClear()](https://jestjs.io/docs/mock-function-api#mockfnmockclear) - Clears call history
- [x] [mockFn.mockReset()](https://jestjs.io/docs/mock-function-api#mockfnmockreset) - Clears call history and removes implementation
- [x] [mockFn.mockRestore()](https://jestjs.io/docs/mock-function-api#mockfnmockrestore) - Restores original implementation
- [x] [mockFn.mockClear()](https://jestjs.io/docs/mock-function-api#mockfnmockclear)
- [x] [mockFn.mockReset()](https://jestjs.io/docs/mock-function-api#mockfnmockreset)
- [x] [mockFn.mockRestore()](https://jestjs.io/docs/mock-function-api#mockfnmockrestore)
- [x] [mockFn.mockImplementation(fn)](https://jestjs.io/docs/mock-function-api#mockfnmockimplementationfn)
- [x] [mockFn.mockImplementationOnce(fn)](https://jestjs.io/docs/mock-function-api#mockfnmockimplementationoncefn)
- [x] [mockFn.mockName(name)](https://jestjs.io/docs/mock-function-api#mockfnmocknamename)
@@ -197,59 +197,7 @@ After resolution, the mocked module is stored in the ES Module registry **and**
The callback function is called lazily, only if the module is imported or required. This means that you can use `mock.module()` to mock modules that don't exist yet, and it means that you can use `mock.module()` to mock modules that are imported by other modules.
### Module Mock Implementation Details
Understanding how `mock.module()` works helps you use it more effectively:
1. **Cache Interaction**: Module mocks interacts with both ESM and CommonJS module caches.
2. **Lazy Evaluation**: The mock factory callback is only evaluated when the module is actually imported or required.
3. **Path Resolution**: Bun automatically resolves the module specifier as though you were doing an import, supporting:
- Relative paths (`'./module'`)
- Absolute paths (`'/path/to/module'`)
- Package names (`'lodash'`)
4. **Import Timing Effects**:
- When mocking before first import: No side effects from the original module occur
- When mocking after import: The original module's side effects have already happened
- For this reason, using `--preload` is recommended for mocks that need to prevent side effects
5. **Live Bindings**: Mocked ESM modules maintain live bindings, so changing the mock will update all existing imports
## Global Mock Functions
### Clear all mocks with `mock.clearAllMocks()`
Reset all mock function state (calls, results, etc.) without restoring their original implementation:
```ts
import { expect, mock, test } from "bun:test";
const random1 = mock(() => Math.random());
const random2 = mock(() => Math.random());
test("clearing all mocks", () => {
random1();
random2();
expect(random1).toHaveBeenCalledTimes(1);
expect(random2).toHaveBeenCalledTimes(1);
mock.clearAllMocks();
expect(random1).toHaveBeenCalledTimes(0);
expect(random2).toHaveBeenCalledTimes(0);
// Note: implementations are preserved
expect(typeof random1()).toBe("number");
expect(typeof random2()).toBe("number");
});
```
This resets the `.mock.calls`, `.mock.instances`, `.mock.contexts`, and `.mock.results` properties of all mocks, but unlike `mock.restore()`, it does not restore the original implementation.
### Restore all function mocks with `mock.restore()`
## Restore all function mocks to their original values with `mock.restore()`
Instead of manually restoring each mock individually with `mockFn.mockRestore()`, restore all mocks with one command by calling `mock.restore()`. Doing so does not reset the value of modules overridden with `mock.module()`.
@@ -286,28 +234,3 @@ test('foo, bar, baz', () => {
expect(bazSpy).toBe('baz');
});
```
## Vitest Compatibility
For added compatibility with tests written for [Vitest](https://vitest.dev/), Bun provides the `vi` global object as an alias for parts of the Jest mocking API:
```ts
import { test, expect } from "bun:test";
// Using the 'vi' alias similar to Vitest
test("vitest compatibility", () => {
const mockFn = vi.fn(() => 42);
mockFn();
expect(mockFn).toHaveBeenCalled();
// The following functions are available on the vi object:
// vi.fn
// vi.spyOn
// vi.mock
// vi.restoreAllMocks
// vi.clearAllMocks
});
```
This makes it easier to port tests from Vitest to Bun without having to rewrite all your mocks.

View File

@@ -1,108 +0,0 @@
bun test supports different output formats through reporters. This document covers both built-in reporters and how to implement your own custom reporters.
## Built-in Reporters
### Default Console Reporter
By default, bun test outputs results to the console in a human-readable format:
```sh
test/package-json-lint.test.ts:
✓ test/package.json [0.88ms]
✓ test/js/third_party/grpc-js/package.json [0.18ms]
✓ test/js/third_party/svelte/package.json [0.21ms]
✓ test/js/third_party/express/package.json [1.05ms]
4 pass
0 fail
4 expect() calls
Ran 4 tests in 1.44ms
```
When a terminal doesn't support colors, the output avoids non-ascii characters:
```sh
test/package-json-lint.test.ts:
(pass) test/package.json [0.48ms]
(pass) test/js/third_party/grpc-js/package.json [0.10ms]
(pass) test/js/third_party/svelte/package.json [0.04ms]
(pass) test/js/third_party/express/package.json [0.04ms]
4 pass
0 fail
4 expect() calls
Ran 4 tests across 1 files. [0.66ms]
```
### JUnit XML Reporter
For CI/CD environments, Bun supports generating JUnit XML reports. JUnit XML is a widely-adopted format for test results that can be parsed by many CI/CD systems, including GitLab, Jenkins, and others.
#### Using the JUnit Reporter
To generate a JUnit XML report, use the `--reporter=junit` flag along with `--reporter-outfile` to specify the output file:
```sh
$ bun test --reporter=junit --reporter-outfile=./junit.xml
```
This continues to output to the console as usual while also writing the JUnit XML report to the specified path at the end of the test run.
#### Configuring via bunfig.toml
You can also configure the JUnit reporter in your `bunfig.toml` file:
```toml
[test.reporter]
junit = "path/to/junit.xml" # Output path for JUnit XML report
```
#### Environment Variables in JUnit Reports
The JUnit reporter automatically includes environment information as `<properties>` in the XML output. This can be helpful for tracking test runs in CI environments.
Specifically, it includes the following environment variables when available:
| Environment Variable | Property Name | Description |
| ----------------------------------------------------------------------- | ------------- | ---------------------- |
| `GITHUB_RUN_ID`, `GITHUB_SERVER_URL`, `GITHUB_REPOSITORY`, `CI_JOB_URL` | `ci` | CI build information |
| `GITHUB_SHA`, `CI_COMMIT_SHA`, `GIT_SHA` | `commit` | Git commit identifiers |
| System hostname | `hostname` | Machine hostname |
This makes it easier to track which environment and commit a particular test run was for.
#### Current Limitations
The JUnit reporter currently has a few limitations that will be addressed in future updates:
- `stdout` and `stderr` output from individual tests are not included in the report
- Precise timestamp fields per test case are not included
### GitHub Actions reporter
Bun test automatically detects when it's running inside GitHub Actions and emits GitHub Actions annotations to the console directly. No special configuration is needed beyond installing Bun and running `bun test`.
For a GitHub Actions workflow configuration example, see the [CI/CD integration](../cli/test.md#cicd-integration) section of the CLI documentation.
## Custom Reporters
Bun allows developers to implement custom test reporters by extending the WebKit Inspector Protocol with additional testing-specific domains.
### Inspector Protocol for Testing
To support test reporting, Bun extends the standard WebKit Inspector Protocol with two custom domains:
1. **TestReporter**: Reports test discovery, execution start, and completion events
2. **LifecycleReporter**: Reports errors and exceptions during test execution
These extensions allow you to build custom reporting tools that can receive detailed information about test execution in real-time.
### Key Events
Custom reporters can listen for these key events:
- `TestReporter.found`: Emitted when a test is discovered
- `TestReporter.start`: Emitted when a test starts running
- `TestReporter.end`: Emitted when a test completes
- `Console.messageAdded`: Emitted when console output occurs during a test
- `LifecycleReporter.error`: Emitted when an error or exception occurs

View File

@@ -1,93 +0,0 @@
`bun test` is deeply integrated with Bun's runtime. This is part of what makes `bun test` fast and simple to use.
#### `$NODE_ENV` environment variable
`bun test` automatically sets `$NODE_ENV` to `"test"` unless it's already set in the environment or via .env files. This is standard behavior for most test runners and helps ensure consistent test behavior.
```ts
import { test, expect } from "bun:test";
test("NODE_ENV is set to test", () => {
expect(process.env.NODE_ENV).toBe("test");
});
```
#### `$TZ` environment variable
By default, all `bun test` runs use UTC (`Etc/UTC`) as the time zone unless overridden by the `TZ` environment variable. This ensures consistent date and time behavior across different development environments.
#### Test Timeouts
Each test has a default timeout of 5000ms (5 seconds) if not explicitly overridden. Tests that exceed this timeout will fail. This can be changed globally with the `--timeout` flag or per-test as the third parameter to the test function.
## Error Handling
### Unhandled Errors
`bun test` tracks unhandled promise rejections and errors that occur between tests. If such errors occur, the final exit code will be non-zero (specifically, the count of such errors), even if all tests pass.
This helps catch errors in asynchronous code that might otherwise go unnoticed:
```ts
import { test } from "bun:test";
test("test 1", () => {
// This test passes
});
// This error happens outside any test
setTimeout(() => {
throw new Error("Unhandled error");
}, 0);
test("test 2", () => {
// This test also passes
});
// The test run will still fail with a non-zero exit code
// because of the unhandled error
```
Internally, this occurs with a higher precedence than `process.on("unhandledRejection")` or `process.on("uncaughtException")`, which makes it simpler to integrate with existing code.
## Using General CLI Flags with Tests
Several Bun CLI flags can be used with `bun test` to modify its behavior:
### Memory Usage
- `--smol`: Reduces memory usage for the test runner VM
### Debugging
- `--inspect`, `--inspect-brk`: Attaches the debugger to the test runner process
### Module Loading
- `--preload`: Runs scripts before test files (useful for global setup/mocks)
- `--define`: Sets compile-time constants
- `--loader`: Configures custom loaders
- `--tsconfig-override`: Uses a different tsconfig
- `--conditions`: Sets package.json conditions for module resolution
- `--env-file`: Loads environment variables for tests
### Installation-related Flags
- `--prefer-offline`, `--frozen-lockfile`, etc.: Affect any network requests or auto-installs during test execution
## Watch and Hot Reloading
When running `bun test` with the `--watch` flag, the test runner will watch for file changes and re-run affected tests.
The `--hot` flag provides similar functionality but is more aggressive about trying to preserve state between runs. For most test scenarios, `--watch` is the recommended option.
## Global Variables
The following globals are automatically available in test files without importing (though they can be imported from `bun:test` if preferred):
- `test`, `it`: Define tests
- `describe`: Group tests
- `expect`: Make assertions
- `beforeAll`, `beforeEach`, `afterAll`, `afterEach`: Lifecycle hooks
- `jest`: Jest global object
- `vi`: Vitest compatibility alias for common jest methods

View File

@@ -1,7 +1,3 @@
Snapshot testing saves the output of a value and compares it against future test runs. This is particularly useful for UI components, complex objects, or any output that needs to remain consistent.
## Basic snapshots
Snapshot tests are written using the `.toMatchSnapshot()` matcher:
```ts
@@ -17,52 +13,3 @@ The first time this test is run, the argument to `expect` will be serialized and
```bash
$ bun test --update-snapshots
```
## Inline snapshots
For smaller values, you can use inline snapshots with `.toMatchInlineSnapshot()`. These snapshots are stored directly in your test file:
```ts
import { test, expect } from "bun:test";
test("inline snapshot", () => {
// First run: snapshot will be inserted automatically
expect({ hello: "world" }).toMatchInlineSnapshot();
// After first run, the test file will be updated to:
// expect({ hello: "world" }).toMatchInlineSnapshot(`
// {
// "hello": "world",
// }
// `);
});
```
When you run the test, Bun automatically updates the test file itself with the generated snapshot string. This makes the tests more portable and easier to understand, since the expected output is right next to the test.
### Using inline snapshots
1. Write your test with `.toMatchInlineSnapshot()`
2. Run the test once
3. Bun automatically updates your test file with the snapshot
4. On subsequent runs, the value will be compared against the inline snapshot
Inline snapshots are particularly useful for small, simple values where it's helpful to see the expected output right in the test file.
## Error snapshots
You can also snapshot error messages using `.toThrowErrorMatchingSnapshot()` and `.toThrowErrorMatchingInlineSnapshot()`:
```ts
import { test, expect } from "bun:test";
test("error snapshot", () => {
expect(() => {
throw new Error("Something went wrong");
}).toThrowErrorMatchingSnapshot();
expect(() => {
throw new Error("Another error");
}).toThrowErrorMatchingInlineSnapshot();
});
```

View File

@@ -74,29 +74,9 @@ test("it was 2020, for a moment.", () => {
});
```
## Get mocked time with `jest.now()`
When you're using mocked time (with `setSystemTime` or `useFakeTimers`), you can use `jest.now()` to get the current mocked timestamp:
```ts
import { test, expect, jest } from "bun:test";
test("get the current mocked time", () => {
jest.useFakeTimers();
jest.setSystemTime(new Date("2020-01-01T00:00:00.000Z"));
expect(Date.now()).toBe(1577836800000); // Jan 1, 2020 timestamp
expect(jest.now()).toBe(1577836800000); // Same value
jest.useRealTimers();
});
```
This is useful when you need to access the mocked time directly without creating a new Date object.
## Set the time zone
By default, the time zone for all `bun test` runs is set to UTC (`Etc/UTC`) unless overridden. To change the time zone, either pass the `$TZ` environment variable to `bun test`.
To change the time zone, either pass the `$TZ` environment variable to `bun test`.
```sh
TZ=America/Los_Angeles bun test

View File

@@ -78,11 +78,9 @@ test("wat", async () => {
In `bun:test`, test timeouts throw an uncatchable exception to force the test to stop running and fail. We also kill any child processes that were spawned in the test to avoid leaving behind zombie processes lurking in the background.
The default timeout for each test is 5000ms (5 seconds) if not overridden by this timeout option or `jest.setDefaultTimeout()`.
### 🧟 Zombie process killer
When a test times out and processes spawned in the test via `Bun.spawn`, `Bun.spawnSync`, or `node:child_process` are not killed, they will be automatically killed and a message will be logged to the console. This prevents zombie processes from lingering in the background after timed-out tests.
When a test times out and processes spawned in the test via `Bun.spawn`, `Bun.spawnSync`, or `node:child_process` are not killed, they will be automatically killed and a message will be logged to the console.
## `test.skip`
@@ -127,7 +125,7 @@ fix the test.
## `test.only`
To run a particular test or suite of tests use `test.only()` or `describe.only()`.
To run a particular test or suite of tests use `test.only()` or `describe.only()`. Once declared, running `bun test --only` will only execute tests/suites that have been marked with `.only()`. Running `bun test` without the `--only` option with `test.only()` declared will result in all tests in the given suite being executed _up to_ the test with `.only()`. `describe.only()` functions the same in both execution scenarios.
```ts
import { test, describe } from "bun:test";
@@ -199,121 +197,22 @@ test.todoIf(macOS)("runs on posix", () => {
});
```
## `test.failing`
## `test.each`
Use `test.failing()` when you know a test is currently failing but you want to track it and be notified when it starts passing. This inverts the test result:
- A failing test marked with `.failing()` will pass
- A passing test marked with `.failing()` will fail (with a message indicating it's now passing and should be fixed)
```ts
// This will pass because the test is failing as expected
test.failing("math is broken", () => {
expect(0.1 + 0.2).toBe(0.3); // fails due to floating point precision
});
// This will fail with a message that the test is now passing
test.failing("fixed bug", () => {
expect(1 + 1).toBe(2); // passes, but we expected it to fail
});
```
This is useful for tracking known bugs that you plan to fix later, or for implementing test-driven development.
## Conditional Tests for Describe Blocks
The conditional modifiers `.if()`, `.skipIf()`, and `.todoIf()` can also be applied to `describe` blocks, affecting all tests within the suite:
```ts
const isMacOS = process.platform === "darwin";
// Only runs the entire suite on macOS
describe.if(isMacOS)("macOS-specific features", () => {
test("feature A", () => {
// only runs on macOS
});
test("feature B", () => {
// only runs on macOS
});
});
// Skips the entire suite on Windows
describe.skipIf(process.platform === "win32")("Unix features", () => {
test("feature C", () => {
// skipped on Windows
});
});
// Marks the entire suite as TODO on Linux
describe.todoIf(process.platform === "linux")("Upcoming Linux support", () => {
test("feature D", () => {
// marked as TODO on Linux
});
});
```
## `test.each` and `describe.each`
To run the same test with multiple sets of data, use `test.each`. This creates a parametrized test that runs once for each test case provided.
To return a function for multiple cases in a table of tests, use `test.each`.
```ts
const cases = [
[1, 2, 3],
[3, 4, 7],
[3, 4, 5],
];
test.each(cases)("%p + %p should be %p", (a, b, expected) => {
expect(a + b).toBe(expected);
// runs once for each test case provided
});
```
You can also use `describe.each` to create a parametrized suite that runs once for each test case:
```ts
describe.each([
[1, 2, 3],
[3, 4, 7],
])("add(%i, %i)", (a, b, expected) => {
test(`returns ${expected}`, () => {
expect(a + b).toBe(expected);
});
test(`sum is greater than each value`, () => {
expect(a + b).toBeGreaterThan(a);
expect(a + b).toBeGreaterThan(b);
});
});
```
### Argument Passing
How arguments are passed to your test function depends on the structure of your test cases:
- If a table row is an array (like `[1, 2, 3]`), each element is passed as an individual argument
- If a row is not an array (like an object), it's passed as a single argument
```ts
// Array items passed as individual arguments
test.each([
[1, 2, 3],
[4, 5, 9],
])("add(%i, %i) = %i", (a, b, expected) => {
expect(a + b).toBe(expected);
});
// Object items passed as a single argument
test.each([
{ a: 1, b: 2, expected: 3 },
{ a: 4, b: 5, expected: 9 },
])("add($a, $b) = $expected", data => {
expect(data.a + data.b).toBe(data.expected);
});
```
### Format Specifiers
There are a number of options available for formatting the test title:
There are a number of options available for formatting the case label depending on its type.
{% table %}
@@ -364,68 +263,6 @@ There are a number of options available for formatting the test title:
{% /table %}
#### Examples
```ts
// Basic specifiers
test.each([
["hello", 123],
["world", 456],
])("string: %s, number: %i", (str, num) => {
// "string: hello, number: 123"
// "string: world, number: 456"
});
// %p for pretty-format output
test.each([
[{ name: "Alice" }, { a: 1, b: 2 }],
[{ name: "Bob" }, { x: 5, y: 10 }],
])("user %p with data %p", (user, data) => {
// "user { name: 'Alice' } with data { a: 1, b: 2 }"
// "user { name: 'Bob' } with data { x: 5, y: 10 }"
});
// %# for index
test.each(["apple", "banana"])("fruit #%# is %s", fruit => {
// "fruit #0 is apple"
// "fruit #1 is banana"
});
```
## Assertion Counting
Bun supports verifying that a specific number of assertions were called during a test:
### expect.hasAssertions()
Use `expect.hasAssertions()` to verify that at least one assertion is called during a test:
```ts
test("async work calls assertions", async () => {
expect.hasAssertions(); // Will fail if no assertions are called
const data = await fetchData();
expect(data).toBeDefined();
});
```
This is especially useful for async tests to ensure your assertions actually run.
### expect.assertions(count)
Use `expect.assertions(count)` to verify that a specific number of assertions are called during a test:
```ts
test("exactly two assertions", () => {
expect.assertions(2); // Will fail if not exactly 2 assertions are called
expect(1 + 1).toBe(2);
expect("hello").toContain("ell");
});
```
This helps ensure all your assertions run, especially in complex async code with multiple code paths.
## Matchers
Bun implements the following matchers. Full Jest compatibility is on the roadmap; track progress [here](https://github.com/oven-sh/bun/issues/1825).

View File

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

85
instructions.md Normal file
View File

@@ -0,0 +1,85 @@
ZACK IMPORTANT INFORMATION for implementing `cachedDataRejected`:
- cachedDataRejected is set in two scenarios: A) invalid binary data or B) source code mismatch
- this means that we need to parse the code in the constrcutre of `vm.Script` (this will also solve the sourceMappingURL test not passing)
- this means that we probably need to copy the code from `JSC::evaluate(...)` (inside `Intepreter.cpp`)
- the way that function works is that it does `ProgramExecutable* program = ProgramExecutable::create(globalObject, source)`
- then it does some shit to compile it to bytecode and runs it
- the parsing of the source code and checking the CodeCache happens in `ProgramExecutable::initializeGlobalProperties(...)`
- this calls `CodeCache::getUnlinkedProgramCodeBlock`
- which is a wrapper around `CodeCache::getUnlinkedGlobalCodeBlock`
- which finally calls `CodeCache::findAndUpdateArgs` which will call `fetchFromDiskImpl` downstream
- `fetchFromDisk` is called when IT IS NOT FOUND IN THE CACHE, it will try to get it from the SourceProvider
- we should probably always set `cachedDataRejected` to true
- if it calls upon `fetchFromDisk` and it is successful we can set `cachedDataRejected` to false
- not sure if it will then later add it to the cache map
# Background
I am working on the node:vm module in the Bun JavaScript runtime which uses JavaScriptCore (JSC) as its JavaScript engine.
I am implementing the `cachedData` option for `new vm.Script()`.
I want to add support to the `cachedDataRejected` property to the `vm.Script` class.
This property is set to false if the cached data is rejected by JSC because it does not match the input source code.
# Your to-do list
- [ ] Add an overriddable method to `JSC::SourceProvider` called `isBytecodeCacheValid` and `setBytecodeCacheValid`
- [ ] Update `fetchFromDiskImpl` in `vendor/WebKit/Source/JavaScriptCore/runtime/CodeCache.cpp`
- [ ] We need to add code which checks that
## Add an overriddable method to `JSC::SourceProvider` called `isBytecodeCacheValid` and `setBytecodeCacheValid`
The `setBytecodeCacheValid` method will be called by JSC if the bytecode cache associated with the source provider is valid.
The `isBytecodeCacheValid` method will be called by JSC to check if the bytecode cache associated with the source provider is valid.
You will add this in `vendor/WebKit/Source/JavaScriptCore/parser/SourceProvider.h`.
Make sure to wrap these changes in the `#if USE(BUN_JSC_ADDITIONS)` macro wrapper.
## Update `fetchFromDiskImpl` in `vendor/WebKit/Source/JavaScriptCore/runtime/CodeCache.cpp`
This is the definition of this function:
```cpp
UnlinkedCodeBlockType* fetchFromDiskImpl(VM& vm, const SourceCodeKey& key)
{
RefPtr<CachedBytecode> cachedBytecode = key.source().provider().cachedBytecode();
if (!cachedBytecode || !cachedBytecode->size())
return nullptr;
return decodeCodeBlock<UnlinkedCodeBlockType>(vm, key, *cachedBytecode);
}
```
Basically the `return decodeCodeBlock<UnlinkedCodeBlockType>(vm, key, *cachedBytecode);` line will return a `nullptr` if it could not decode the bytecod
NOTE: this is just ONE way that the cached data can be rejected; when it is invalid
## Add a check in `constructScript` in `NodeVM.cpp` to check the bytecode cache source matches the input source code
ANOTHER way that the cached data can be rejected is if the source code of the bytecode cache does not match the input source code.
## Appendix: Node.js documentation for `new vm.Script()`
Documentation from Node.js:
```
# new vm.Script(code[, options])
- `code` <string> The JavaScript code to compile.
- `options` <Object> | <string>
- `filename` <string> Specifies the filename used in stack traces produced by this script. Default: 'evalmachine.<anonymous>'.
- `lineOffset` <number> Specifies the line number offset that is displayed in stack traces produced by this script. Default: 0.
- `columnOffset` <number> Specifies the first-line column number offset that is displayed in stack traces produced by this script. Default: 0.
- `cachedData` <Buffer> | <TypedArray> | <DataView> Provides an optional Buffer or TypedArray, or DataView with V8's code cache data for the supplied source. When supplied, the `cachedDataRejected` value will be set to either true or false depending on acceptance of the data by V8.
- `produceCachedData` <boolean> When true and no cachedData is present, V8 will attempt to produce code cache data for code. Upon success, a Buffer with V8's code cache data will be produced and stored in the `cachedData` property of the returned vm.Script instance. The `cachedDataProduced` value will be set to either true or false depending on whether code cache data is produced successfully. This option is deprecated in favor of `script.createCachedData()`. Default: false.
- `importModuleDynamically` <Function> | <vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER> Used to specify how the modules should be loaded during the evaluation of this script when `import()` is called. This option is part of the experimental modules API. We do not recommend using it in a production environment. For detailed information, see Support of dynamic import() in compilation APIs.
If `options` is a string, then it specifies the filename.
Creating a new `vm.Script` object compiles code but does not run it. The compiled `vm.Script` can be run later multiple times. The code is not bound to any global object; rather, it is bound before each run, just for that run.
```

View File

@@ -1,7 +1,7 @@
const std = @import("std");
const path_handler = @import("../src/resolver/resolve_path.zig");
const bun = @import("bun");
const bun = @import("root").bun;
const string = bun.string;
const Output = bun.Output;
const Global = bun.Global;

View File

@@ -1,142 +0,0 @@
# pretty printing for the standard library.
# put "source /path/to/stage2_gdb_pretty_printers.py" in ~/.gdbinit to load it automatically.
import re
import gdb.printing
# Handles both ArrayList and ArrayListUnmanaged.
class ArrayListPrinter:
def __init__(self, val):
self.val = val
def to_string(self):
type = self.val.type.name[len('std.array_list.'):]
type = re.sub(r'^ArrayListAligned(Unmanaged)?\((.*),null\)$', r'ArrayList\1(\2)', type)
return '%s of length %s, capacity %s' % (type, self.val['items']['len'], self.val['capacity'])
def children(self):
for i in range(self.val['items']['len']):
item = self.val['items']['ptr'] + i
yield ('[%d]' % i, item.dereference())
def display_hint(self):
return 'array'
class MultiArrayListPrinter:
def __init__(self, val):
self.val = val
def child_type(self):
(helper_fn, _) = gdb.lookup_symbol('%s.dbHelper' % self.val.type.name)
return helper_fn.type.fields()[1].type.target()
def to_string(self):
type = self.val.type.name[len('std.multi_array_list.'):]
return '%s of length %s, capacity %s' % (type, self.val['len'], self.val['capacity'])
def slice(self):
fields = self.child_type().fields()
base = self.val['bytes']
cap = self.val['capacity']
len = self.val['len']
if len == 0:
return
fields = sorted(fields, key=lambda field: field.type.alignof, reverse=True)
for field in fields:
ptr = base.cast(field.type.pointer()).dereference().cast(field.type.array(len - 1))
base += field.type.sizeof * cap
yield (field.name, ptr)
def children(self):
for i, (name, ptr) in enumerate(self.slice()):
yield ('[%d]' % i, name)
yield ('[%d]' % i, ptr)
def display_hint(self):
return 'map'
# Handles both HashMap and HashMapUnmanaged.
class HashMapPrinter:
def __init__(self, val):
self.type = val.type
is_managed = re.search(r'^std\.hash_map\.HashMap\(', self.type.name)
self.val = val['unmanaged'] if is_managed else val
def header_ptr_type(self):
(helper_fn, _) = gdb.lookup_symbol('%s.dbHelper' % self.val.type.name)
return helper_fn.type.fields()[1].type
def header(self):
if self.val['metadata'] == 0:
return None
return (self.val['metadata'].cast(self.header_ptr_type()) - 1).dereference()
def to_string(self):
type = self.type.name[len('std.hash_map.'):]
type = re.sub(r'^HashMap(Unmanaged)?\((.*),std.hash_map.AutoContext\(.*$', r'AutoHashMap\1(\2)', type)
hdr = self.header()
if hdr is not None:
cap = hdr['capacity']
else:
cap = 0
return '%s of length %s, capacity %s' % (type, self.val['size'], cap)
def children(self):
hdr = self.header()
if hdr is None:
return
is_map = self.display_hint() == 'map'
for i in range(hdr['capacity']):
metadata = self.val['metadata'] + i
if metadata.dereference()['used'] == 1:
yield ('[%d]' % i, (hdr['keys'] + i).dereference())
if is_map:
yield ('[%d]' % i, (hdr['values'] + i).dereference())
def display_hint(self):
for field in self.header_ptr_type().target().fields():
if field.name == 'values':
return 'map'
return 'array'
# Handles both ArrayHashMap and ArrayHashMapUnmanaged.
class ArrayHashMapPrinter:
def __init__(self, val):
self.type = val.type
is_managed = re.search(r'^std\.array_hash_map\.ArrayHashMap\(', self.type.name)
self.val = val['unmanaged'] if is_managed else val
def to_string(self):
type = self.type.name[len('std.array_hash_map.'):]
type = re.sub(r'^ArrayHashMap(Unmanaged)?\((.*),std.array_hash_map.AutoContext\(.*$', r'AutoArrayHashMap\1(\2)', type)
return '%s of length %s' % (type, self.val['entries']['len'])
def children(self):
entries = MultiArrayListPrinter(self.val['entries'])
len = self.val['entries']['len']
fields = {}
for name, ptr in entries.slice():
fields[str(name)] = ptr
for i in range(len):
if 'key' in fields:
yield ('[%d]' % i, fields['key'][i])
else:
yield ('[%d]' % i, '{}')
if 'value' in fields:
yield ('[%d]' % i, fields['value'][i])
def display_hint(self):
for name, ptr in MultiArrayListPrinter(self.val['entries']).slice():
if name == 'value':
return 'map'
return 'array'
pp = gdb.printing.RegexpCollectionPrettyPrinter('Zig standard library')
pp.add_printer('ArrayList', r'^std\.array_list\.ArrayListAligned(Unmanaged)?\(.*\)$', ArrayListPrinter)
pp.add_printer('MultiArrayList', r'^std\.multi_array_list\.MultiArrayList\(.*\)$', MultiArrayListPrinter)
pp.add_printer('HashMap', r'^std\.hash_map\.HashMap(Unmanaged)?\(.*\)$', HashMapPrinter)
pp.add_printer('ArrayHashMap', r'^std\.array_hash_map\.ArrayHashMap(Unmanaged)?\(.*\)$', ArrayHashMapPrinter)
gdb.printing.register_pretty_printer(gdb.current_objfile(), pp)

View File

@@ -1,63 +0,0 @@
# pretty printing for the language.
# put "source /path/to/zig_gdb_pretty_printers.py" in ~/.gdbinit to load it automatically.
import gdb.printing
class ZigPrettyPrinter(gdb.printing.PrettyPrinter):
def __init__(self):
super().__init__('Zig')
def __call__(self, val):
tag = val.type.tag
if tag is None:
return None
if tag == '[]u8':
return StringPrinter(val)
if tag.startswith('[]'):
return SlicePrinter(val)
if tag.startswith('?'):
return OptionalPrinter(val)
return None
class SlicePrinter:
def __init__(self, val):
self.val = val
def to_string(self):
return f"{self.val['len']} items at {self.val['ptr']}"
def children(self):
def it(val):
for i in range(int(val['len'])):
item = val['ptr'] + i
yield (f'[{i}]', item.dereference())
return it(self.val)
def display_hint(self):
return 'array'
class StringPrinter:
def __init__(self, val):
self.val = val
def to_string(self):
return self.val['ptr'].string(length=int(self.val['len']))
def display_hint(self):
return 'string'
class OptionalPrinter:
def __init__(self, val):
self.val = val
def to_string(self):
if self.val['some']:
return self.val['data']
else:
return 'null'
gdb.printing.register_pretty_printer(gdb.current_objfile(), ZigPrettyPrinter())

View File

@@ -98,7 +98,7 @@ chunks.push(`// Auto-generated file. Do not edit.
// This used to be a comptime block, but it made the build too slow.
// Compressing the completions list saves about 100 KB of binary size.
const std = @import("std");
const bun = @import("bun");
const bun = @import("root").bun;
const zstd = bun.zstd;
const Environment = bun.Environment;

View File

@@ -1,5 +1,5 @@
const std = @import("std");
const bun = @import("bun");
const bun = @import("root").bun;
const string = bun.string;
const Output = bun.Output;
const Global = bun.Global;
@@ -12,6 +12,7 @@ const C = bun.C;
const clap = @import("../src/deps/zig-clap/clap.zig");
const URL = @import("../src/url.zig").URL;
const Headers = @import("../src/bun.js/webcore/response.zig").Headers;
const Method = @import("../src/http/method.zig").Method;
const ColonListType = @import("../src/cli/colon_list_type.zig").ColonListType;
const HeadersTuple = ColonListType(string, noop_resolver);

View File

@@ -61,6 +61,7 @@ zig_keywords = {
'try',
'union',
'unreachable',
'usingnamespace',
'var',
'volatile',
'while',

View File

@@ -1,6 +1,6 @@
// most of this file is copy pasted from other files in misctools
const std = @import("std");
const bun = @import("bun");
const bun = @import("root").bun;
const string = bun.string;
const Output = bun.Output;
const Global = bun.Global;

View File

@@ -1,7 +1,7 @@
const std = @import("std");
const path_handler = @import("../src/resolver/resolve_path.zig");
const bun = @import("bun");
const bun = @import("root").bun;
const string = bun.string;
const Output = bun.Output;
const Global = bun.Global;

View File

@@ -1,7 +1,7 @@
const std = @import("std");
const path_handler = @import("../src/resolver/resolve_path.zig");
const bun = @import("bun");
const bun = @import("root").bun;
const string = bun.string;
const Output = bun.Output;
const Global = bun.Global;

View File

@@ -1,7 +1,7 @@
const std = @import("std");
const path_handler = @import("../src/resolver/resolve_path.zig");
const bun = @import("bun");
const bun = @import("root").bun;
const string = bun.string;
const Output = bun.Output;
const Global = bun.Global;

View File

@@ -1,10 +1,10 @@
{
"$schema": "https://raw.githubusercontent.com/oxc-project/oxc/refs/heads/main/npm/oxlint/configuration_schema.json",
"categories": {
"correctness": "error"
"correctness": "warn" // TODO: gradually fix bugs and turn this to error
},
"rules": {
"const-comparisons": "error",
"const-comparisons": "off", // TODO: there's a bug when comparing private identifiers. Re-enable once it's fixed.
"no-cond-assign": "error",
"no-const-assign": "error",
"no-debugger": "error",
@@ -13,35 +13,12 @@
"no-empty-pattern": "error",
"import/no-duplicates": "error",
"no-control-regex": "off",
"no-useless-escape": "off",
"no-this-alias": "off", // many intentional this aliases
"triple-slash-reference": "off", // many intentional triple slash references
// This rule is dumb.
// Array.from is MUCH slower than new Array(size).
"no-new-array": "off",
// We have custom thenables. This is not a bug.
"no-thenable": "off",
"no-undef-init": "error",
// We use this in some cases. The ordering is deliberate.
"no-unsafe-finally": "off",
// We use !!$debug to check if the debugger is enabled.
// Boolean() is also generally slower than !!.
"no-extra-boolean-cast": "off",
// Eslint is not a type checker.
"no-throw-literal": "off"
"no-useless-escape": "off" // there's a lot of these. Should be fixed eventually.
},
"ignorePatterns": [
"vendor",
"build",
"bench",
"test/snapshots/**",
"bench/react-hello-world/*.js",
"bun.lock",
@@ -52,16 +29,8 @@
"test/js/**/*bad.js",
"test/bundler/transpiler/decorators.test.ts", // uses `arguments` as decorator
"test/bundler/native-plugin.test.ts", // parser doesn't handle import metadata
"test/bundler/transpiler/with-statement-works.js", // parser doesn't allow `with` statement
"test/js/node/module/extensions-fixture", // these files are not meant to be linted
"test/cli/run/module-type-fixture",
"test/bundler/transpiler/with-statement-works.js", // parser doesn't allow `with` statement
// TODO: fix these
"src/js/node/http2.ts",
"src/js/node/http.ts"
"test/bundler/transpiler/with-statement-works.js" // parser doesn't allow `with` statement
],
"overrides": [
{
"files": ["test/**", "examples/**", "packages/bun-internal/test/runners/**"],

View File

@@ -1,7 +1,7 @@
{
"private": true,
"name": "bun",
"version": "1.2.11",
"version": "1.2.6",
"workspaces": [
"./packages/bun-types"
],
@@ -31,8 +31,6 @@
},
"scripts": {
"build": "bun run build:debug",
"watch": "bun zig build check --watch -fincremental --prominent-compile-errors",
"bd": "(bun run --silent build:debug &> /tmp/bun.debug.build.log || (cat /tmp/bun.debug.build.log && rm -rf /tmp/bun.debug.build.log && exit 1)) && rm -f /tmp/bun.debug.build.log && ./build/debug/bun-debug",
"build:debug": "bun ./scripts/build.mjs -GNinja -DCMAKE_BUILD_TYPE=Debug -B build/debug",
"build:valgrind": "bun ./scripts/build.mjs -GNinja -DCMAKE_BUILD_TYPE=Debug -DENABLE_BASELINE=ON -ENABLE_VALGRIND=ON -B build/debug-valgrind",
"build:release": "bun ./scripts/build.mjs -GNinja -DCMAKE_BUILD_TYPE=Release -B build/release",
@@ -46,21 +44,17 @@
"build:release:with_logs": "cmake . -DCMAKE_BUILD_TYPE=Release -DENABLE_LOGS=true -GNinja -Bbuild-release && ninja -Cbuild-release",
"build:debug-zig-release": "cmake . -DCMAKE_BUILD_TYPE=Release -DZIG_OPTIMIZE=Debug -GNinja -Bbuild-debug-zig-release && ninja -Cbuild-debug-zig-release",
"css-properties": "bun run src/css/properties/generate_properties.ts",
"uv-posix-stubs": "bun run src/bun.js/bindings/libuv/generate_uv_posix_stubs.ts",
"bump": "bun ./scripts/bump.ts",
"typecheck": "tsc --noEmit && cd test && bun run typecheck",
"fmt": "bun run prettier",
"fmt:cpp": "bun run clang-format",
"fmt:zig": "bun run zig-format",
"lint": "bunx oxlint --config=oxlint.json --format=github src/js",
"lint": "oxlint --config oxlint.json",
"lint:fix": "oxlint --config oxlint.json --fix",
"test": "node scripts/runner.node.mjs --exec-path ./build/debug/bun-debug",
"test:release": "node scripts/runner.node.mjs --exec-path ./build/release/bun",
"banned": "bun test test/internal/ban-words.test.ts",
"zig": "vendor/zig/zig.exe",
"zig:test": "bun ./scripts/build.mjs -GNinja -DCMAKE_BUILD_TYPE=Debug -DBUN_TEST=ON -B build/debug",
"zig:test:release": "bun ./scripts/build.mjs -GNinja -DCMAKE_BUILD_TYPE=Release -DBUNTEST=ON -B build/release",
"zig:test:ci": "bun ./scripts/build.mjs -GNinja -DCMAKE_BUILD_TYPE=Release -DBUN_TEST=ON -DZIG_OPTIMIZE=ReleaseSafe -DCMAKE_VERBOSE_MAKEFILE=ON -DCI=true -B build/release-ci --verbose --fresh",
"zig:fmt": "bun run zig-format",
"zig:check": "bun run zig build check --summary new",
"zig:check-all": "bun run zig build check-all --summary new",
@@ -80,7 +74,6 @@
"prettier:check": "bun run analysis:no-llvm --target prettier-check",
"prettier:extra": "bun run analysis:no-llvm --target prettier-extra",
"prettier:diff": "bun run analysis:no-llvm --target prettier-diff",
"node:test": "node ./scripts/runner.node.mjs --quiet --exec-path=$npm_execpath --node-tests ",
"clean:zig": "rm -rf build/debug/cache/zig build/debug/CMakeCache.txt 'build/debug/*.o' .zig-cache zig-out || true"
"node:test": "node ./scripts/runner.node.mjs --quiet --exec-path=$npm_execpath --node-tests "
}
}

View File

@@ -1,3 +1,2 @@
index.js
protocol/*.json
protocol/v8

View File

@@ -1,354 +1 @@
# bun-inspector-protocol
`bun-inspector-protocol` is a TypeScript library that provides a comprehensive interface for interacting with the WebKit Inspector Protocol. This package makes it easy to build debugging tools, IDE integrations, and other developer tools that communicate with Bun's JavaScript runtime.
You can use this library with Node.js or Bun.
## Overview
The WebKit Inspector Protocol is a JSON-based protocol similar to the Chrome DevTools Protocol. It allows external tools to interact with Bun's JavaScript runtime for debugging, profiling, and instrumentation purposes.
## Features
- 🌐 **WebSocket communication**: Connect to Bun's debugging endpoint via WebSockets
- 🔌 **Socket communication**: Connect via Unix/TCP sockets for local debugging
- 🔄 **Full API typing**: Complete TypeScript definitions for the protocol
- 📊 **Object preview utilities**: Format runtime objects for display
- 🔄 **Event-driven architecture**: Subscribe to specific debugging events
- 🧩 **Promise-based API**: Clean, modern async interface
## Installation
```bash
bun add bun-inspector-protocol
# npm install bun-inspector-protocol
# yarn add bun-inspector-protocol
# pnpm add bun-inspector-protocol
```
## Basic Usage
The first step is to spawn a Bun process with the inspector attached. There are a few different ways to do this.
The `--inspect-wait` flag is the easiest way to spawn a Bun process with the inspector attached.
```bash
bun --inspect-wait my-script.ts
```
From there, it will start a WebSocket server defaulting to port 9229 and you will need to read the output from stdout to get the URL of the inspector:
```bash
bun --inspect-wait my-script.ts 2>&1 | grep -o '\sws://.*$'
```
From there, you can connect to the inspector using the `WebSocketInspector` class:
```typescript
import { WebSocketInspector } from "bun-inspector-protocol";
// Create a new inspector client
const inspector = new WebSocketInspector("ws://localhost:9229/ws");
```
### Connecting via WebSocket
```typescript
import { WebSocketInspector } from "bun-inspector-protocol";
// Create a new inspector client
const inspector = new WebSocketInspector("ws://localhost:9229/ws");
// Listen for connection events
inspector.on("Inspector.connected", () => {
console.log("Connected to debugger!");
});
inspector.on("Inspector.error", error => {
console.error("Inspector error:", error);
});
// Connect to the debugger
await inspector.start();
// Enable the Runtime domain
await inspector.send("Runtime.enable");
// Execute some code in the target context
const result = await inspector.send("Runtime.evaluate", {
expression: "2 + 2",
returnByValue: true,
});
console.log("Evaluation result:", result.result.value); // 4
// Close the connection
inspector.close();
```
### Connecting via Socket (for Local Debugging)
```typescript
import { NodeSocketInspector } from "bun-inspector-protocol";
import { Socket } from "node:net";
// Create a socket connection
const socket = new Socket();
socket.connect("/path/to/debug/socket");
// Create a new inspector client
const inspector = new NodeSocketInspector(socket);
// Set up event listeners and use the API as with WebSocketInspector
inspector.on("Inspector.connected", () => {
console.log("Connected to debugger via socket!");
});
await inspector.start();
// Use the same API as WebSocketInspector from here...
```
## Event Handling
The inspector emits various events you can listen for:
```typescript
// Listen for specific protocol events
inspector.on("Debugger.scriptParsed", params => {
console.log("Script parsed:", params.url);
});
// Listen for breakpoint hits
inspector.on("Debugger.paused", params => {
console.log("Execution paused at:", params.callFrames[0].location);
});
// Listen for console messages
inspector.on("Runtime.consoleAPICalled", params => {
console.log(
"Console message:",
params.args
.map(arg =>
// Use the included utility to format objects
remoteObjectToString(arg, true),
)
.join(" "),
);
});
```
## Protocol Domains
The WebKit Inspector Protocol is organized into domains that group related functionality. Based on the JavaScriptCore protocol implementation, the following domains are available:
### Console Domain
- Console message capturing and monitoring
- Support for different logging channels and levels (xml, javascript, network, etc.)
- Methods: `enable`, `disable`, `clearMessages`, `setLoggingChannelLevel`, etc.
- Events: `messageAdded`, `messageRepeatCountUpdated`, `messagesCleared`
### Debugger Domain
- Comprehensive debugging capabilities
- Setting and managing breakpoints (conditional, URL-based, symbolic)
- Execution control (pause, resume, step, etc.)
- Stack frame inspection and manipulation
- Methods: `enable`, `setBreakpoint`, `resume`, `stepInto`, `evaluateOnCallFrame`, etc.
- Events: `scriptParsed`, `breakpointResolved`, `paused`, `resumed`
### Heap Domain
- Memory management and garbage collection monitoring
- Heap snapshot creation and analysis
- Memory leak detection with tracking
- Methods: `enable`, `gc`, `snapshot`, `startTracking`, `stopTracking`
- Events: `garbageCollected`, `trackingStart`, `trackingComplete`
### Inspector Domain
- Core inspector functionality
- Methods: `enable`, `disable`, `initialized`
- Events: `evaluateForTestInFrontend`, `inspect`
### LifecycleReporter Domain
- Process lifecycle management
- Error reporting
- Methods: `enable`, `preventExit`, `stopPreventingExit`
- Events: `reload`, `error`
### Runtime Domain
- JavaScript runtime interaction
- Expression evaluation
- Object property inspection
- Promise handling
- Type profiling and control flow analysis
- Methods: `evaluate`, `callFunctionOn`, `getProperties`, `awaitPromise`, etc.
- Events: `executionContextCreated`
### ScriptProfiler Domain
- Script execution profiling
- Performance tracking
- Methods: `startTracking`, `stopTracking`
- Events: `trackingStart`, `trackingUpdate`, `trackingComplete`
### TestReporter Domain
- Test execution monitoring
- Test status reporting (pass, fail, timeout, skip, todo)
- Methods: `enable`, `disable`
- Events: `found`, `start`, `end`
Each domain has its own set of commands, events, and data types. Refer to the TypeScript definitions in this package for complete API details.
## Working with Remote Objects
When evaluating expressions, you'll often receive remote object references. Use the `remoteObjectToString` utility to convert these to string representations:
```typescript
import { remoteObjectToString } from "bun-inspector-protocol";
const result = await inspector.send("Runtime.evaluate", {
expression: "{ a: 1, b: { c: 'hello' } }",
});
console.log(remoteObjectToString(result.result, true));
// Output: {a: 1, b: {c: "hello"}}
```
## Message Structure
The protocol uses a simple JSON-based message format:
### Requests
```typescript
interface Request<T> {
id: number; // Unique request identifier
method: string; // Domain.method name format
params: T; // Method-specific parameters
}
```
### Responses
```typescript
interface Response<T> {
id: number; // Matching request identifier
result?: T; // Method-specific result (on success)
error?: {
// Error information (on failure)
code?: string;
message: string;
};
}
```
### Events
```typescript
interface Event<T> {
method: string; // Domain.event name format
params: T; // Event-specific parameters
}
```
### Setting Breakpoints
```typescript
// Set a breakpoint by URL
const { breakpointId } = await inspector.send("Debugger.setBreakpointByUrl", {
lineNumber: 42,
url: "/app/foo.ts",
condition: "x > 5", // Optional condition
});
// Set a breakpoint with custom actions
await inspector.send("Debugger.setBreakpoint", {
location: { scriptId: "123", lineNumber: 10 },
options: {
condition: "count > 5",
actions: [
{ type: "log", data: "Breakpoint hit!" },
{ type: "evaluate", data: "console.log('Custom breakpoint action')" },
],
autoContinue: true,
},
});
// Remove a breakpoint
await inspector.send("Debugger.removeBreakpoint", { breakpointId });
```
### Memory Profiling
```typescript
// Start heap tracking
await inspector.send("Heap.enable");
await inspector.send("Heap.startTracking");
// Listen for GC events
inspector.on("Heap.garbageCollected", ({ collection }) => {
console.log(
`GC completed: ${collection.type} (${collection.endTime - collection.startTime}ms)`,
);
});
// ... perform operations to analyze ...
// Get heap snapshot
const { snapshotData } = await inspector.send("Heap.stopTracking");
// Process snapshotData to find memory leaks
```
### Script Profiling
```typescript
// Start script profiling with sampling
await inspector.send("ScriptProfiler.startTracking", { includeSamples: true });
// Listen for profiling updates
inspector.on("ScriptProfiler.trackingUpdate", event => {
console.log("Profiling event:", event);
});
// Stop profiling to get complete data
inspector.on("ScriptProfiler.trackingComplete", data => {
if (data.samples) {
// Process stack traces
console.log(`Collected ${data.samples.stackTraces.length} stack traces`);
}
});
await inspector.send("ScriptProfiler.stopTracking");
```
## Protocol Differences from Upstream WebKit
Notable Bun-specific additions include:
- `LifecycleReporter` domain for process lifecycle management
- Enhanced `TestReporter` domain for test framework integration
- Additional utilities for script and heap profiling
## Building Tools with the Protocol
This library is ideal for building:
- IDE extensions and debuggers
- Performance monitoring tools
- Testing frameworks with runtime instrumentation
- Hot module reloading systems
- Custom REPL environments
- Profiling and optimization tools
## Full API Reference
For complete API documentation, please refer to the TypeScript definitions included in this package. The definitions provide comprehensive information about all available commands, events, and their parameters.
## License
MIT

View File

@@ -1,5 +1,4 @@
export type * from "./src/inspector/index";
export * from "./src/inspector/websocket";
export * from "./src/inspector/node-socket";
export type * from "./src/protocol/index";
export * from "./src/util/preview";
export type * from "./src/inspector/index.js";
export * from "./src/inspector/websocket.js";
export type * from "./src/protocol/index.js";
export * from "./src/util/preview.js";

View File

@@ -1,19 +1,7 @@
{
"name": "bun-inspector-protocol",
"version": "0.0.2",
"scripts": {
"build": "bun build --target=node --outfile=index.js --minify-syntax ./index.ts --external=ws"
},
"version": "0.0.1",
"dependencies": {
"ws": "^8.13.0"
},
"main": "./index.js",
"type": "module",
"files": [
"index.js",
"index.ts",
"src",
"README.md",
"LICENSE"
]
}
}

View File

@@ -4,12 +4,12 @@
"": {
"name": "bun-plugin-svelte",
"devDependencies": {
"@threlte/core": "8.0.1",
"bun-types": "canary",
"svelte": "^5.20.4",
},
"peerDependencies": {
"svelte": "^5",
"typescript": "^5",
},
},
},
@@ -26,8 +26,6 @@
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="],
"@threlte/core": ["@threlte/core@8.0.1", "", { "dependencies": { "mitt": "^3.0.1" }, "peerDependencies": { "svelte": ">=5", "three": ">=0.155" } }, "sha512-vy1xRQppJFNmfPTeiRQue+KmYFsbPgVhwuYXRTvVrwPeD2oYz43gxUeOpe1FACeGKxrxZykeKJF5ebVvl7gBxw=="],
"@types/estree": ["@types/estree@1.0.6", "", {}, "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="],
"@types/node": ["@types/node@22.13.5", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-+lTU0PxZXn0Dr1NBtC7Y8cR21AJr87dLLU953CWA6pMxxv/UDc7jYAY90upcrie1nRcD6XNG5HOYEDtgW5TxAg=="],
@@ -56,11 +54,9 @@
"magic-string": ["magic-string@0.30.17", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" } }, "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA=="],
"mitt": ["mitt@3.0.1", "", {}, "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="],
"svelte": ["svelte@5.20.4", "", { "dependencies": { "@ampproject/remapping": "^2.3.0", "@jridgewell/sourcemap-codec": "^1.5.0", "@types/estree": "^1.0.5", "acorn": "^8.12.1", "acorn-typescript": "^1.4.13", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "esm-env": "^1.2.1", "esrap": "^1.4.3", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-2Mo/AfObaw9zuD0u1JJ7sOVzRCGcpETEyDkLbtkcctWpCMCIyT0iz83xD8JT29SR7O4SgswuPRIDYReYF/607A=="],
"three": ["three@0.174.0", "", {}, "sha512-p+WG3W6Ov74alh3geCMkGK9NWuT62ee21cV3jEnun201zodVF4tCE5aZa2U122/mkLRmhJJUQmLLW1BH00uQJQ=="],
"typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="],
"undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],

View File

@@ -1,6 +1,6 @@
{
"name": "bun-plugin-svelte",
"version": "0.0.6",
"version": "0.0.5",
"description": "Official Svelte plugin for Bun",
"repository": {
"type": "git",

View File

@@ -11,11 +11,7 @@ describe("SveltePlugin", () => {
expect(() => SveltePlugin(undefined)).not.toThrow();
});
it.each([1, "hi", {}, "Client"])("throws if forceSide is not 'client' or 'server' (%p)", (forceSide: any) => {
it.each([null, 1, "hi", {}, "Client"])("throws if forceSide is not 'client' or 'server' (%p)", (forceSide: any) => {
expect(() => SveltePlugin({ forceSide })).toThrow(TypeError);
});
it.each([null, undefined])("forceSide may be nullish", (forceSide: any) => {
expect(() => SveltePlugin({ forceSide })).not.toThrow();
});
});

View File

@@ -2,7 +2,7 @@ import { describe, beforeAll, it, expect } from "bun:test";
import type { BuildConfig } from "bun";
import type { CompileOptions } from "svelte/compiler";
import { getBaseCompileOptions, validateOptions, type SvelteOptions } from "./options";
import { getBaseCompileOptions, type SvelteOptions } from "./options";
describe("getBaseCompileOptions", () => {
describe("when no options are provided", () => {
@@ -42,13 +42,4 @@ describe("getBaseCompileOptions", () => {
);
},
);
}); // getBaseCompileOptions
describe("validateOptions(options)", () => {
it.each(["", 1, null, undefined, true, false, Symbol("hi")])(
"throws if options is not an object (%p)",
(badOptions: any) => {
expect(() => validateOptions(badOptions)).toThrow();
},
);
}); // validateOptions
});

View File

@@ -2,8 +2,7 @@ import { strict as assert } from "node:assert";
import { type BuildConfig } from "bun";
import type { CompileOptions, ModuleCompileOptions } from "svelte/compiler";
type OverrideCompileOptions = Pick<CompileOptions, "customElement" | "runes" | "modernAst" | "namespace">;
export interface SvelteOptions extends Pick<CompileOptions, "runes"> {
export interface SvelteOptions {
/**
* Force client-side or server-side generation.
*
@@ -21,11 +20,6 @@ export interface SvelteOptions extends Pick<CompileOptions, "runes"> {
* Defaults to `true` when run via Bun's dev server, `false` otherwise.
*/
development?: boolean;
/**
* Options to forward to the Svelte compiler.
*/
compilerOptions?: OverrideCompileOptions;
}
/**
@@ -33,24 +27,15 @@ export interface SvelteOptions extends Pick<CompileOptions, "runes"> {
*/
export function validateOptions(options: unknown): asserts options is SvelteOptions {
assert(options && typeof options === "object", new TypeError("bun-svelte-plugin: options must be an object"));
const opts = options as Record<keyof SvelteOptions, unknown>;
if (opts.forceSide != null) {
if (typeof opts.forceSide !== "string") {
throw new TypeError("bun-svelte-plugin: forceSide must be a string, got " + typeof opts.forceSide);
}
switch (opts.forceSide) {
if ("forceSide" in options) {
switch (options.forceSide) {
case "client":
case "server":
break;
default:
throw new TypeError(`bun-svelte-plugin: forceSide must be either 'client' or 'server', got ${opts.forceSide}`);
}
}
if (opts.compilerOptions) {
if (typeof opts.compilerOptions !== "object") {
throw new TypeError("bun-svelte-plugin: compilerOptions must be an object");
throw new TypeError(
`bun-svelte-plugin: forceSide must be either 'client' or 'server', got ${options.forceSide}`,
);
}
}
}
@@ -59,10 +44,7 @@ export function validateOptions(options: unknown): asserts options is SvelteOpti
* @internal
*/
export function getBaseCompileOptions(pluginOptions: SvelteOptions, config: Partial<BuildConfig>): CompileOptions {
let {
development = false,
compilerOptions: { customElement, runes, modernAst, namespace } = kEmptyObject as OverrideCompileOptions,
} = pluginOptions;
let { development = false } = pluginOptions;
const { minify = false } = config;
const shouldMinify = Boolean(minify);
@@ -86,10 +68,6 @@ export function getBaseCompileOptions(pluginOptions: SvelteOptions, config: Part
preserveWhitespace: !minifyWhitespace,
preserveComments: !shouldMinify,
dev: development,
customElement,
runes,
modernAst,
namespace,
cssHash({ css }) {
// same prime number seed used by svelte/compiler.
// TODO: ensure this provides enough entropy
@@ -131,4 +109,3 @@ function generateSide(pluginOptions: SvelteOptions, config: Partial<BuildConfig>
}
export const hash = (content: string): string => Bun.hash(content, 5381).toString(36);
const kEmptyObject = Object.create(null);

View File

@@ -24,32 +24,13 @@ afterAll(() => {
}
});
describe("given a hello world component", () => {
const entrypoints = [fixturePath("foo.svelte")];
it("when no options are provided, builds successfully", async () => {
const res = await Bun.build({
entrypoints,
outdir,
plugins: [SveltePlugin()],
});
expect(res.success).toBeTrue();
});
describe("when a custom element is provided", () => {
let res: BuildOutput;
beforeAll(async () => {
res = await Bun.build({
entrypoints,
outdir,
plugins: [SveltePlugin({ compilerOptions: { customElement: true } })],
});
});
it("builds successfully", () => {
expect(res.success).toBeTrue();
});
it("hello world component", async () => {
const res = await Bun.build({
entrypoints: [fixturePath("foo.svelte")],
outdir,
plugins: [SveltePlugin()],
});
expect(res.success).toBeTrue();
});
describe("when importing `.svelte.ts` files with ESM", () => {

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`.
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).
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,6 +28,8 @@ 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

@@ -19,7 +19,13 @@ declare module "*/bun.lock" {
}
declare module "*.html" {
// In Bun v1.2, this might change to Bun.HTMLBundle
// In Bun v1.2, we might change this 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;
}

View File

@@ -1,114 +0,0 @@
# 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

View File

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

View File

@@ -1,19 +1,4 @@
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`
*/
@@ -53,6 +38,21 @@ 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,187 +1,192 @@
declare module "bun" {
type HMREventNames =
| "beforeUpdate"
| "afterUpdate"
| "beforeFullReload"
| "beforePrune"
| "invalidate"
| "error"
| "ws:disconnect"
| "ws:connect";
export {};
/**
* The event names for the dev server
*/
type HMREvent = `bun:${HMREventNames}` | (string & {});
}
interface ImportMeta {
/**
* Hot module replacement APIs. This value is `undefined` in production and
* can be used in an `if` statement to check if HMR APIs are available
*
* ```ts
* if (import.meta.hot) {
* // HMR APIs are available
* }
* ```
*
* However, this check is usually not needed as Bun will dead-code-eliminate
* calls to all of the HMR APIs in production builds.
*
* https://bun.sh/docs/bundler/hmr
*/
hot: {
/**
* `import.meta.hot.data` maintains state between module instances during
* hot replacement, enabling data transfer from previous to new versions.
* When `import.meta.hot.data` is written to, Bun will mark this module as
* capable of self-accepting (equivalent of calling `accept()`).
*
* @example
* ```ts
* const root = import.meta.hot.data.root ??= createRoot(elem);
* root.render(<App />); // re-use an existing root
* ```
*
* In production, `data` is inlined to be `{}`. This is handy because Bun
* knows it can minify `{}.prop ??= value` into `value` in production.
*/
data: any;
/**
* Indicate that this module can be replaced simply by re-evaluating the
* file. After a hot update, importers of this module will be
* automatically patched.
*
* When `import.meta.hot.accept` is not used, the page will reload when
* the file updates, and a console message shows which files were checked.
*
* @example
* ```ts
* import { getCount } from "./foo";
*
* console.log("count is ", getCount());
*
* import.meta.hot.accept();
* ```
*/
accept(): void;
/**
* Indicate that this module can be replaced by evaluating the new module,
* and then calling the callback with the new module. In this mode, the
* importers do not get patched. This is to match Vite, which is unable
* to patch their import statements. Prefer using `import.meta.hot.accept()`
* without an argument as it usually makes your code easier to understand.
*
* When `import.meta.hot.accept` is not used, the page will reload when
* the file updates, and a console message shows which files were checked.
*
* @example
* ```ts
* export const count = 0;
*
* import.meta.hot.accept((newModule) => {
* if (newModule) {
* // newModule is undefined when SyntaxError happened
* console.log('updated: count is now ', newModule.count)
* }
* });
* ```
*
* In production, calls to this are dead-code-eliminated.
*/
accept(cb: (newModule: any | undefined) => void): void;
/**
* Indicate that a dependency's module can be accepted. When the dependency
* is updated, the callback will be called with the new module.
*
* When `import.meta.hot.accept` is not used, the page will reload when
* the file updates, and a console message shows which files were checked.
*
* @example
* ```ts
* import.meta.hot.accept('./foo', (newModule) => {
* if (newModule) {
* // newModule is undefined when SyntaxError happened
* console.log('updated: count is now ', newModule.count)
* }
* });
* ```
*/
accept(specifier: string, callback: (newModule: any) => void): void;
/**
* Indicate that a dependency's module can be accepted. This variant
* accepts an array of dependencies, where the callback will receive
* the one updated module, and `undefined` for the rest.
*
* When `import.meta.hot.accept` is not used, the page will reload when
* the file updates, and a console message shows which files were checked.
*/
accept(specifiers: string[], callback: (newModules: (any | undefined)[]) => void): void;
/**
* Attach an on-dispose callback. This is called:
* - Just before the module is replaced with another copy (before the next is loaded)
* - After the module is detached (removing all imports to this module)
*
* This callback is not called on route navigation or when the browser tab closes.
*
* Returning a promise will delay module replacement until the module is
* disposed. All dispose callbacks are called in parallel.
*/
dispose(cb: (data: any) => void | Promise<void>): void;
/**
* No-op
* @deprecated
*/
decline(): void;
// NOTE TO CONTRIBUTORS ////////////////////////////////////////
// Callback is currently never called for `.prune()` //
// so the types are commented out until we support it. //
////////////////////////////////////////////////////////////////
// /**
// * Attach a callback that is called when the module is removed from the module graph.
// *
// * This can be used to clean up resources that were created when the module was loaded.
// * Unlike `import.meta.hot.dispose()`, this pairs much better with `accept` and `data` to manage stateful resources.
// *
// * @example
// * ```ts
// * export const ws = (import.meta.hot.data.ws ??= new WebSocket(location.origin));
// *
// * import.meta.hot.prune(() => {
// * ws.close();
// * });
// * ```
// */
// prune(callback: () => void): void;
/**
* Listen for an event from the dev server
*
* For compatibility with Vite, event names are also available via vite:* prefix instead of bun:*.
*
* https://bun.sh/docs/bundler/hmr#import-meta-hot-on-and-off
* @param event The event to listen to
* @param callback The callback to call when the event is emitted
*/
on(event: Bun.HMREvent, callback: () => void): void;
/**
* Stop listening for an event from the dev server
*
* For compatibility with Vite, event names are also available via vite:* prefix instead of bun:*.
*
* https://bun.sh/docs/bundler/hmr#import-meta-hot-on-and-off
* @param event The event to stop listening to
* @param callback The callback to stop listening to
*/
off(event: Bun.HMREvent, callback: () => void): void;
};
declare global {
namespace Bun {
type HMREventNames =
| "bun:ready"
| "bun:beforeUpdate"
| "bun:afterUpdate"
| "bun:beforeFullReload"
| "bun:beforePrune"
| "bun:invalidate"
| "bun:error"
| "bun:ws:disconnect"
| "bun:ws:connect";
/**
* The event names for the dev server
*/
type HMREvent = `bun:${HMREventNames}` | (string & {});
}
interface ImportMeta {
/**
* Hot module replacement APIs. This value is `undefined` in production and
* can be used in an `if` statement to check if HMR APIs are available
*
* ```ts
* if (import.meta.hot) {
* // HMR APIs are available
* }
* ```
*
* However, this check is usually not needed as Bun will dead-code-eliminate
* calls to all of the HMR APIs in production builds.
*
* https://bun.sh/docs/bundler/hmr
*/
hot: {
/**
* `import.meta.hot.data` maintains state between module instances during
* hot replacement, enabling data transfer from previous to new versions.
* When `import.meta.hot.data` is written to, Bun will mark this module as
* capable of self-accepting (equivalent of calling `accept()`).
*
* @example
* ```ts
* const root = import.meta.hot.data.root ??= createRoot(elem);
* root.render(<App />); // re-use an existing root
* ```
*
* In production, `data` is inlined to be `{}`. This is handy because Bun
* knows it can minify `{}.prop ??= value` into `value` in production.
*/
data: any;
/**
* Indicate that this module can be replaced simply by re-evaluating the
* file. After a hot update, importers of this module will be
* automatically patched.
*
* When `import.meta.hot.accept` is not used, the page will reload when
* the file updates, and a console message shows which files were checked.
*
* @example
* ```ts
* import { getCount } from "./foo";
*
* console.log("count is ", getCount());
*
* import.meta.hot.accept();
* ```
*/
accept(): void;
/**
* Indicate that this module can be replaced by evaluating the new module,
* and then calling the callback with the new module. In this mode, the
* importers do not get patched. This is to match Vite, which is unable
* to patch their import statements. Prefer using `import.meta.hot.accept()`
* without an argument as it usually makes your code easier to understand.
*
* When `import.meta.hot.accept` is not used, the page will reload when
* the file updates, and a console message shows which files were checked.
*
* @example
* ```ts
* export const count = 0;
*
* import.meta.hot.accept((newModule) => {
* if (newModule) {
* // newModule is undefined when SyntaxError happened
* console.log('updated: count is now ', newModule.count)
* }
* });
* ```
*
* In production, calls to this are dead-code-eliminated.
*/
accept(cb: (newModule: any | undefined) => void): void;
/**
* Indicate that a dependency's module can be accepted. When the dependency
* is updated, the callback will be called with the new module.
*
* When `import.meta.hot.accept` is not used, the page will reload when
* the file updates, and a console message shows which files were checked.
*
* @example
* ```ts
* import.meta.hot.accept('./foo', (newModule) => {
* if (newModule) {
* // newModule is undefined when SyntaxError happened
* console.log('updated: count is now ', newModule.count)
* }
* });
* ```
*/
accept(specifier: string, callback: (newModule: any) => void): void;
/**
* Indicate that a dependency's module can be accepted. This variant
* accepts an array of dependencies, where the callback will receive
* the one updated module, and `undefined` for the rest.
*
* When `import.meta.hot.accept` is not used, the page will reload when
* the file updates, and a console message shows which files were checked.
*/
accept(specifiers: string[], callback: (newModules: (any | undefined)[]) => void): void;
/**
* Attach an on-dispose callback. This is called:
* - Just before the module is replaced with another copy (before the next is loaded)
* - After the module is detached (removing all imports to this module)
*
* This callback is not called on route navigation or when the browser tab closes.
*
* Returning a promise will delay module replacement until the module is
* disposed. All dispose callbacks are called in parallel.
*/
dispose(cb: (data: any) => void | Promise<void>): void;
/**
* No-op
* @deprecated
*/
decline(): void;
// NOTE TO CONTRIBUTORS ////////////////////////////////////////
// Callback is currently never called for `.prune()` //
// so the types are commented out until we support it. //
////////////////////////////////////////////////////////////////
// /**
// * Attach a callback that is called when the module is removed from the module graph.
// *
// * This can be used to clean up resources that were created when the module was loaded.
// * Unlike `import.meta.hot.dispose()`, this pairs much better with `accept` and `data` to manage stateful resources.
// *
// * @example
// * ```ts
// * export const ws = (import.meta.hot.data.ws ??= new WebSocket(location.origin));
// *
// * import.meta.hot.prune(() => {
// * ws.close();
// * });
// * ```
// */
// prune(callback: () => void): void;
/**
* Listen for an event from the dev server
*
* For compatibility with Vite, event names are also available via vite:* prefix instead of bun:*.
*
* https://bun.sh/docs/bundler/hmr#import-meta-hot-on-and-off
* @param event The event to listen to
* @param callback The callback to call when the event is emitted
*/
on(event: Bun.HMREvent, callback: () => void): void;
/**
* Stop listening for an event from the dev server
*
* For compatibility with Vite, event names are also available via vite:* prefix instead of bun:*.
*
* https://bun.sh/docs/bundler/hmr#import-meta-hot-on-and-off
* @param event The event to stop listening to
* @param callback The callback to stop listening to
*/
off(event: Bun.HMREvent, callback: () => void): void;
};
}
}

View File

@@ -1,190 +1,161 @@
/*
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<string | ArrayBuffer | ArrayBufferView>
| (() => AsyncGenerator<string | ArrayBuffer | ArrayBufferView>);
namespace __internal {
type LibOrFallbackHeaders = LibDomIsLoaded extends true ? {} : import("undici-types").Headers;
type LibOrFallbackRequest = LibDomIsLoaded extends true
? {}
: {
readonly cache: import("undici-types").RequestCache;
readonly credentials: import("undici-types").RequestCredentials;
readonly destination: import("undici-types").RequestDestination;
readonly headers: Headers;
readonly integrity: string;
readonly method: string;
readonly mode: import("undici-types").RequestMode;
readonly redirect: import("undici-types").RequestRedirect;
readonly referrer: string;
readonly referrerPolicy: import("undici-types").ReferrerPolicy;
readonly keepalive: boolean;
readonly signal: AbortSignal;
/** @deprecated Setting `.duplex` does nothing in Bun and will be removed in a future release */
readonly duplex: import("undici-types").RequestDuplex;
get url(): string;
get body(): ReadableStream | null;
get bodyUsed(): boolean;
arrayBuffer(): Promise<ArrayBuffer>;
blob(): Promise<Blob>;
formData(): Promise<FormData>;
json(): Promise<unknown>;
text(): Promise<string>;
clone(): Request;
};
type LibOrFallbackResponse = LibDomIsLoaded extends true
? {}
: {
readonly headers: Headers;
readonly ok: boolean;
readonly status: number;
readonly statusText: string;
readonly url: string;
readonly redirected: boolean;
get type(): import("undici-types").ResponseType;
get body(): ReadableStream | null;
get bodyUsed(): boolean;
arrayBuffer(): Promise<ArrayBuffer>;
blob(): Promise<Blob>;
formData(): Promise<FormData>;
json(): Promise<unknown>;
text(): Promise<string>;
clone(): 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.
*
* About 10x faster than `Object.fromEntries(headers.entries())`
*
* Called when you run `JSON.stringify(headers)`
*
* Does not preserve insertion order. Well-known header names are lowercased. Other header names are left as-is.
*/
toJSON(): Record<string, string> & { "set-cookie"?: string[] };
/**
* Get the total number of headers
*/
readonly count: number;
/**
* Get all headers matching the name
*
* Only supports `"Set-Cookie"`. All other headers are empty arrays.
*
* @param name - The header name to get
*
* @returns An array of header values
*
* @example
* ```ts
* const headers = new Headers();
* headers.append("Set-Cookie", "foo=bar");
* headers.append("Set-Cookie", "baz=qux");
* headers.getAll("Set-Cookie"); // ["foo=bar", "baz=qux"]
* ```
*/
getAll(name: "set-cookie" | "Set-Cookie"): string[];
}
interface BunRequestOverride extends LibOrFallbackRequest {
headers: BunHeadersOverride;
}
interface BunResponseOverride extends LibOrFallbackResponse {
headers: BunHeadersOverride;
}
interface BunRequestConstructorOverride {
prototype: BunRequestOverride;
new (requestInfo: string | URL, init?: RequestInit): BunRequestOverride;
new (requestInfo: RequestInit & { url: string }): BunRequestOverride;
new (requestInfo: Request, init?: RequestInit): BunRequestOverride;
}
interface BunResponseConstructorOverride {
prototype: BunResponseOverride;
new (body?: Bun.BodyInit | null | undefined, init?: ResponseInit | undefined): BunResponseOverride;
/**
* 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, init?: ResponseInit | number): BunResponseOverride;
/**
* 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
*/
redirect(url: string, status?: number): BunResponseOverride;
/**
* Create a new {@link Response} that redirects to url
*
* @param url - the URL to redirect to
* @param options - options to pass to the response
*/
redirect(url: string, init?: ResponseInit): BunResponseOverride;
/**
* Create a new {@link Response} that has a network error
*/
error(): BunResponseOverride;
}
}
interface Headers {
/**
* Convert {@link Headers} to a plain JavaScript object.
*
* About 10x faster than `Object.fromEntries(headers.entries())`
*
* Called when you run `JSON.stringify(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
*
* Only supports `"Set-Cookie"`. All other headers are empty arrays.
*
* @param name - The header name to get
*
* @returns An array of header values
*
* @example
* ```ts
* const headers = new Headers();
* headers.append("Set-Cookie", "foo=bar");
* headers.append("Set-Cookie", "baz=qux");
* headers.getAll("Set-Cookie"); // ["foo=bar", "baz=qux"]
* ```
*/
getAll(name: "set-cookie" | "Set-Cookie"): string[];
}
var Headers: {
prototype: Headers;
new (init?: Bun.HeadersInit): Headers;
};
interface Request {
headers: Headers;
}
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"]>;
}
/**
* 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

@@ -13,8 +13,6 @@
* that convert JavaScript types to C types and back. Internally,
* bun uses [tinycc](https://github.com/TinyCC/tinycc), so a big thanks
* goes to Fabrice Bellard and TinyCC maintainers for making this possible.
*
* @category FFI
*/
declare module "bun:ffi" {
enum FFIType {
@@ -545,6 +543,14 @@ 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>;
@@ -602,8 +608,6 @@ declare module "bun:ffi" {
* that convert JavaScript types to C types and back. Internally,
* bun uses [tinycc](https://github.com/TinyCC/tinycc), so a big thanks
* goes to Fabrice Bellard and TinyCC maintainers for making this possible.
*
* @category FFI
*/
function dlopen<Fns extends Record<string, FFIFunction>>(
name: string | import("bun").BunFile | URL,
@@ -622,9 +626,9 @@ declare module "bun:ffi" {
* JavaScript:
* ```js
* import { cc } from "bun:ffi";
* import source from "./hello.c" with {type: "file"};
* import hello from "./hello.c" with {type: "file"};
* const {symbols: {hello}} = cc({
* source,
* source: hello,
* symbols: {
* hello: {
* returns: "cstring",
@@ -677,9 +681,8 @@ declare module "bun:ffi" {
* @example
* ```js
* import { cc } from "bun:ffi";
* import source from "./hello.c" with {type: "file"};
* const {symbols: {hello}} = cc({
* source,
* source: hello,
* define: {
* "NDEBUG": "1",
* },
@@ -704,9 +707,8 @@ declare module "bun:ffi" {
* @example
* ```js
* import { cc } from "bun:ffi";
* import source from "./hello.c" with {type: "file"};
* const {symbols: {hello}} = cc({
* source,
* source: hello,
* flags: ["-framework CoreFoundation", "-framework Security"],
* symbols: {
* hello: {
@@ -1022,8 +1024,6 @@ declare module "bun:ffi" {
* // Do something with rawPtr
* }
* ```
*
* @category FFI
*/
function ptr(view: NodeJS.TypedArray | ArrayBufferLike | DataView, byteOffset?: number): Pointer;
@@ -1048,9 +1048,8 @@ declare module "bun:ffi" {
* thing to do safely. Passing an invalid pointer can crash the program and
* reading beyond the bounds of the pointer will crash the program or cause
* undefined behavior. Use with care!
*
* @category FFI
*/
class CString extends String {
/**
* Get a string from a UTF-8 encoded C string

File diff suppressed because it is too large Load Diff

View File

@@ -147,8 +147,6 @@ declare namespace HTMLRewriterTypes {
* });
* rewriter.transform(await fetch("https://remix.run"));
* ```
*
* @category HTML Manipulation
*/
declare class HTMLRewriter {
constructor();

View File

@@ -1,28 +1,24 @@
// Project: https://github.com/oven-sh/bun
// Definitions by: Bun Contributors <https://github.com/oven-sh/bun/graphs/contributors>
// Definitions by: Jarred Sumner <https://github.com/Jarred-Sumner>
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
/// <reference lib="esnext" />
/// <reference types="ws" />
/// <reference types="node" />
/// <reference path="./globals.d.ts" />
/// <reference path="./s3.d.ts" />
/// <reference path="./fetch.d.ts" />
// contributors: uncomment this to detect conflicts with lib.dom.d.ts
// /// <reference lib="dom" />
/// <reference path="./bun.d.ts" />
/// <reference path="./extensions.d.ts" />
/// <reference path="./devserver.d.ts" />
/// <reference path="./globals.d.ts" />
/// <reference path="./fetch.d.ts" />
/// <reference path="./overrides.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="./redis.d.ts" />
/// <reference path="./shell.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;
/// <reference path="./ambient.d.ts" />
/// <reference path="./devserver.d.ts" />

View File

@@ -8,8 +8,24 @@ declare module "bun:jsc" {
function fullGC(): number;
function edenGC(): number;
function heapSize(): number;
function heapStats(): HeapStats;
function memoryUsage(): MemoryUsage;
function heapStats(): {
heapSize: number;
heapCapacity: number;
extraMemorySize: number;
objectCount: number;
protectedObjectCount: number;
globalObjectCount: number;
protectedGlobalObjectCount: number;
objectTypeCounts: Record<string, number>;
protectedObjectTypeCounts: Record<string, number>;
};
function memoryUsage(): {
current: number;
peak: number;
currentCommit: number;
peakCommit: number;
pageFaults: number;
};
function getRandomSeed(): number;
function setRandomSeed(value: number): void;
function isRope(input: string): boolean;
@@ -62,26 +78,6 @@ declare module "bun:jsc" {
*/
function setTimeZone(timeZone: string): string;
interface HeapStats {
heapSize: number;
heapCapacity: number;
extraMemorySize: number;
objectCount: number;
protectedObjectCount: number;
globalObjectCount: number;
protectedGlobalObjectCount: number;
objectTypeCounts: Record<string, number>;
protectedObjectTypeCounts: Record<string, number>;
}
interface MemoryUsage {
current: number;
peak: number;
currentCommit: number;
peakCommit: number;
pageFaults: number;
}
interface SamplingProfile {
/**
* A formatted summary of the top functions

View File

@@ -1,160 +1,18 @@
export {};
import type { BunFile, Env, PathLike } from "bun";
declare global {
namespace NodeJS {
interface ProcessEnv extends Bun.Env, ImportMetaEnv {}
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: "uv"): {
errname(code: number): string;
UV_E2BIG: number;
UV_EACCES: number;
UV_EADDRINUSE: number;
UV_EADDRNOTAVAIL: number;
UV_EAFNOSUPPORT: number;
UV_EAGAIN: number;
UV_EAI_ADDRFAMILY: number;
UV_EAI_AGAIN: number;
UV_EAI_BADFLAGS: number;
UV_EAI_BADHINTS: number;
UV_EAI_CANCELED: number;
UV_EAI_FAIL: number;
UV_EAI_FAMILY: number;
UV_EAI_MEMORY: number;
UV_EAI_NODATA: number;
UV_EAI_NONAME: number;
UV_EAI_OVERFLOW: number;
UV_EAI_PROTOCOL: number;
UV_EAI_SERVICE: number;
UV_EAI_SOCKTYPE: number;
UV_EALREADY: number;
UV_EBADF: number;
UV_EBUSY: number;
UV_ECANCELED: number;
UV_ECHARSET: number;
UV_ECONNABORTED: number;
UV_ECONNREFUSED: number;
UV_ECONNRESET: number;
UV_EDESTADDRREQ: number;
UV_EEXIST: number;
UV_EFAULT: number;
UV_EFBIG: number;
UV_EHOSTUNREACH: number;
UV_EINTR: number;
UV_EINVAL: number;
UV_EIO: number;
UV_EISCONN: number;
UV_EISDIR: number;
UV_ELOOP: number;
UV_EMFILE: number;
UV_EMSGSIZE: number;
UV_ENAMETOOLONG: number;
UV_ENETDOWN: number;
UV_ENETUNREACH: number;
UV_ENFILE: number;
UV_ENOBUFS: number;
UV_ENODEV: number;
UV_ENOENT: number;
UV_ENOMEM: number;
UV_ENONET: number;
UV_ENOPROTOOPT: number;
UV_ENOSPC: number;
UV_ENOSYS: number;
UV_ENOTCONN: number;
UV_ENOTDIR: number;
UV_ENOTEMPTY: number;
UV_ENOTSOCK: number;
UV_ENOTSUP: number;
UV_EOVERFLOW: number;
UV_EPERM: number;
UV_EPIPE: number;
UV_EPROTO: number;
UV_EPROTONOSUPPORT: number;
UV_EPROTOTYPE: number;
UV_ERANGE: number;
UV_EROFS: number;
UV_ESHUTDOWN: number;
UV_ESPIPE: number;
UV_ESRCH: number;
UV_ETIMEDOUT: number;
UV_ETXTBSY: number;
UV_EXDEV: number;
UV_UNKNOWN: number;
UV_EOF: number;
UV_ENXIO: number;
UV_EMLINK: number;
UV_EHOSTDOWN: number;
UV_EREMOTEIO: number;
UV_ENOTTY: number;
UV_EFTYPE: number;
UV_EILSEQ: number;
UV_ESOCKTNOSUPPORT: number;
UV_ENODATA: number;
UV_EUNATCH: number;
};
binding(m: string): object;
}
interface ProcessVersions extends Dict<string> {
bun: string;
}
interface ProcessEnv extends Env {}
}
}
declare module "fs/promises" {
function exists(path: Bun.PathLike): Promise<boolean>;
function exists(path: PathLike): Promise<boolean>;
}
declare module "tls" {
@@ -164,7 +22,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 | Bun.BunFile | Array<string | Buffer | Bun.BunFile> | undefined;
ca?: string | Buffer | NodeJS.TypedArray | BunFile | Array<string | Buffer | 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
@@ -180,8 +38,8 @@ declare module "tls" {
| string
| Buffer
| NodeJS.TypedArray
| Bun.BunFile
| Array<string | Buffer | NodeJS.TypedArray | Bun.BunFile>
| BunFile
| Array<string | Buffer | NodeJS.TypedArray | BunFile>
| undefined;
/**
* Private keys in PEM format. PEM allows the option of private keys
@@ -196,9 +54,9 @@ declare module "tls" {
key?:
| string
| Buffer
| Bun.BunFile
| BunFile
| NodeJS.TypedArray
| Array<string | Buffer | Bun.BunFile | NodeJS.TypedArray | KeyObject>
| Array<string | Buffer | BunFile | NodeJS.TypedArray | KeyObject>
| undefined;
}

View File

@@ -9,13 +9,14 @@
"directory": "packages/bun-types"
},
"files": [
"./*.d.ts",
"*.d.ts",
"docs/**/*.md",
"docs/*.md"
],
"homepage": "https://bun.sh",
"dependencies": {
"@types/node": "*"
"@types/node": "*",
"@types/ws": "~8.5.10"
},
"devDependencies": {
"@biomejs/biome": "^1.5.3",
@@ -26,7 +27,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",
"build": "bun run copy-docs && bun scripts/build.ts && bun run fmt",
"test": "tsc",
"fmt": "echo $(which biome) && biome format --write ."
},

View File

@@ -1,604 +0,0 @@
declare module "bun" {
export interface RedisOptions {
/**
* Connection timeout in milliseconds
* @default 10000
*/
connectionTimeout?: number;
/**
* Idle timeout in milliseconds
* @default 0 (no timeout)
*/
idleTimeout?: number;
/**
* Whether to automatically reconnect
* @default true
*/
autoReconnect?: boolean;
/**
* Maximum number of reconnection attempts
* @default 10
*/
maxRetries?: number;
/**
* Whether to queue commands when disconnected
* @default true
*/
enableOfflineQueue?: boolean;
/**
* TLS options
* Can be a boolean or an object with TLS options
*/
tls?:
| boolean
| {
key?: string | Buffer;
cert?: string | Buffer;
ca?: string | Buffer | Array<string | Buffer>;
rejectUnauthorized?: boolean;
};
/**
* Whether to enable auto-pipelining
* @default true
*/
enableAutoPipelining?: boolean;
}
export class RedisClient {
/**
* Creates a new Redis client
* @param url URL to connect to, defaults to process.env.VALKEY_URL, process.env.REDIS_URL, or "valkey://localhost:6379"
* @param options Additional options
*
* @example
* ```ts
* const valkey = new RedisClient();
*
* await valkey.set("hello", "world");
*
* console.log(await valkey.get("hello"));
* ```
*/
constructor(url?: string, options?: RedisOptions);
/**
* Whether the client is connected to the Redis server
*/
readonly connected: boolean;
/**
* Amount of data buffered in bytes
*/
readonly bufferedAmount: number;
/**
* Callback fired when the client connects to the Redis server
*/
onconnect: ((this: RedisClient) => void) | null;
/**
* Callback fired when the client disconnects from the Redis server
* @param error The error that caused the disconnection
*/
onclose: ((this: RedisClient, error: Error) => void) | null;
/**
* Connect to the Redis server
* @returns A promise that resolves when connected
*/
connect(): Promise<void>;
/**
* Disconnect from the Redis server
*/
close(): void;
/**
* Send a raw command to the Redis server
* @param command The command to send
* @param args The arguments to the command
* @returns A promise that resolves with the command result
*/
send(command: string, args: string[]): Promise<any>;
/**
* Get the value of a key
* @param key The key to get
* @returns Promise that resolves with the key's value, or null if the key doesn't exist
*/
get(key: string | ArrayBufferView | Blob): Promise<string | null>;
/**
* Set key to hold the string value
* @param key The key to set
* @param value The value to set
* @returns Promise that resolves with "OK" on success
*/
set(key: string | ArrayBufferView | Blob, value: string | ArrayBufferView | Blob): Promise<"OK">;
/**
* Set key to hold the string value with expiration
* @param key The key to set
* @param value The value to set
* @param ex Set the specified expire time, in seconds
* @returns Promise that resolves with "OK" on success
*/
set(
key: string | ArrayBufferView | Blob,
value: string | ArrayBufferView | Blob,
ex: "EX",
seconds: number,
): Promise<"OK">;
/**
* Set key to hold the string value with expiration
* @param key The key to set
* @param value The value to set
* @param px Set the specified expire time, in milliseconds
* @returns Promise that resolves with "OK" on success
*/
set(
key: string | ArrayBufferView | Blob,
value: string | ArrayBufferView | Blob,
px: "PX",
milliseconds: number,
): Promise<"OK">;
/**
* Set key to hold the string value with expiration at a specific Unix timestamp
* @param key The key to set
* @param value The value to set
* @param exat Set the specified Unix time at which the key will expire, in seconds
* @returns Promise that resolves with "OK" on success
*/
set(
key: string | ArrayBufferView | Blob,
value: string | ArrayBufferView | Blob,
exat: "EXAT",
timestampSeconds: number,
): Promise<"OK">;
/**
* Set key to hold the string value with expiration at a specific Unix timestamp
* @param key The key to set
* @param value The value to set
* @param pxat Set the specified Unix time at which the key will expire, in milliseconds
* @returns Promise that resolves with "OK" on success
*/
set(
key: string | ArrayBufferView | Blob,
value: string | ArrayBufferView | Blob,
pxat: "PXAT",
timestampMilliseconds: number,
): Promise<"OK">;
/**
* Set key to hold the string value only if key does not exist
* @param key The key to set
* @param value The value to set
* @param nx Only set the key if it does not already exist
* @returns Promise that resolves with "OK" on success, or null if the key already exists
*/
set(key: string | ArrayBufferView | Blob, value: string | ArrayBufferView | Blob, nx: "NX"): Promise<"OK" | null>;
/**
* Set key to hold the string value only if key already exists
* @param key The key to set
* @param value The value to set
* @param xx Only set the key if it already exists
* @returns Promise that resolves with "OK" on success, or null if the key does not exist
*/
set(key: string | ArrayBufferView | Blob, value: string | ArrayBufferView | Blob, xx: "XX"): Promise<"OK" | null>;
/**
* Set key to hold the string value and return the old value
* @param key The key to set
* @param value The value to set
* @param get Return the old string stored at key, or null if key did not exist
* @returns Promise that resolves with the old value, or null if key did not exist
*/
set(
key: string | ArrayBufferView | Blob,
value: string | ArrayBufferView | Blob,
get: "GET",
): Promise<string | null>;
/**
* Set key to hold the string value and retain the time to live
* @param key The key to set
* @param value The value to set
* @param keepttl Retain the time to live associated with the key
* @returns Promise that resolves with "OK" on success
*/
set(
key: string | ArrayBufferView | Blob,
value: string | ArrayBufferView | Blob,
keepttl: "KEEPTTL",
): Promise<"OK">;
/**
* Set key to hold the string value with various options
* @param key The key to set
* @param value The value to set
* @param options Array of options (EX, PX, EXAT, PXAT, NX, XX, KEEPTTL, GET)
* @returns Promise that resolves with "OK" on success, null if NX/XX condition not met, or the old value if GET is specified
*/
set(
key: string | ArrayBufferView | Blob,
value: string | ArrayBufferView | Blob,
...options: string[]
): Promise<"OK" | string | null>;
/**
* Delete a key
* @param key The key to delete
* @returns Promise that resolves with the number of keys removed
*/
del(key: string | ArrayBufferView | Blob): Promise<number>;
/**
* Increment the integer value of a key by one
* @param key The key to increment
* @returns Promise that resolves with the new value
*/
incr(key: string | ArrayBufferView | Blob): Promise<number>;
/**
* Decrement the integer value of a key by one
* @param key The key to decrement
* @returns Promise that resolves with the new value
*/
decr(key: string | ArrayBufferView | Blob): Promise<number>;
/**
* Determine if a key exists
* @param key The key to check
* @returns Promise that resolves with true if the key exists, false otherwise
*/
exists(key: string | ArrayBufferView | Blob): Promise<boolean>;
/**
* Set a key's time to live in seconds
* @param key The key to set the expiration for
* @param seconds The number of seconds until expiration
* @returns Promise that resolves with 1 if the timeout was set, 0 if not
*/
expire(key: string | ArrayBufferView | Blob, seconds: number): Promise<number>;
/**
* Get the time to live for a key in seconds
* @param key The key to get the TTL for
* @returns Promise that resolves with the TTL, -1 if no expiry, or -2 if key doesn't exist
*/
ttl(key: string | ArrayBufferView | Blob): Promise<number>;
/**
* Set multiple hash fields to multiple values
* @param key The hash key
* @param fieldValues An array of alternating field names and values
* @returns Promise that resolves with "OK" on success
*/
hmset(key: string | ArrayBufferView | Blob, fieldValues: string[]): Promise<string>;
/**
* Get the values of all the given hash fields
* @param key The hash key
* @param fields The fields to get
* @returns Promise that resolves with an array of values
*/
hmget(key: string | ArrayBufferView | Blob, fields: string[]): Promise<Array<string | null>>;
/**
* Check if a value is a member of a set
* @param key The set key
* @param member The member to check
* @returns Promise that resolves with true if the member exists, false otherwise
*/
sismember(key: string | ArrayBufferView | Blob, member: string): Promise<boolean>;
/**
* Add a member to a set
* @param key The set key
* @param member The member to add
* @returns Promise that resolves with 1 if the member was added, 0 if it already existed
*/
sadd(key: string | ArrayBufferView | Blob, member: string): Promise<number>;
/**
* Remove a member from a set
* @param key The set key
* @param member The member to remove
* @returns Promise that resolves with 1 if the member was removed, 0 if it didn't exist
*/
srem(key: string | ArrayBufferView | Blob, member: string): Promise<number>;
/**
* Get all the members in a set
* @param key The set key
* @returns Promise that resolves with an array of all members
*/
smembers(key: string | ArrayBufferView | Blob): Promise<string[]>;
/**
* Get a random member from a set
* @param key The set key
* @returns Promise that resolves with a random member, or null if the set is empty
*/
srandmember(key: string | ArrayBufferView | Blob): Promise<string | null>;
/**
* Remove and return a random member from a set
* @param key The set key
* @returns Promise that resolves with the removed member, or null if the set is empty
*/
spop(key: string | ArrayBufferView | Blob): Promise<string | null>;
/**
* Increment the integer value of a hash field by the given number
* @param key The hash key
* @param field The field to increment
* @param increment The amount to increment by
* @returns Promise that resolves with the new value
*/
hincrby(key: string | ArrayBufferView | Blob, field: string, increment: string | number): Promise<number>;
/**
* Increment the float value of a hash field by the given amount
* @param key The hash key
* @param field The field to increment
* @param increment The amount to increment by
* @returns Promise that resolves with the new value as a string
*/
hincrbyfloat(key: string | ArrayBufferView | Blob, field: string, increment: string | number): Promise<string>;
/**
* Get all the fields and values in a hash
* @param key The hash key
* @returns Promise that resolves with an object containing all fields and values
*/
hgetall(key: string | ArrayBufferView | Blob): Promise<Record<string, string> | null>;
/**
* Get all field names in a hash
* @param key The hash key
* @returns Promise that resolves with an array of field names
*/
hkeys(key: string | ArrayBufferView | Blob): Promise<string[]>;
/**
* Get the number of fields in a hash
* @param key The hash key
* @returns Promise that resolves with the number of fields
*/
hlen(key: string | ArrayBufferView | Blob): Promise<number>;
/**
* Get all values in a hash
* @param key The hash key
* @returns Promise that resolves with an array of values
*/
hvals(key: string | ArrayBufferView | Blob): Promise<string[]>;
/**
* Find all keys matching the given pattern
* @param pattern The pattern to match
* @returns Promise that resolves with an array of matching keys
*/
keys(pattern: string): Promise<string[]>;
/**
* Get the length of a list
* @param key The list key
* @returns Promise that resolves with the length of the list
*/
llen(key: string | ArrayBufferView | Blob): Promise<number>;
/**
* Remove and get the first element in a list
* @param key The list key
* @returns Promise that resolves with the first element, or null if the list is empty
*/
lpop(key: string | ArrayBufferView | Blob): Promise<string | null>;
/**
* Remove the expiration from a key
* @param key The key to persist
* @returns Promise that resolves with 1 if the timeout was removed, 0 if the key doesn't exist or has no timeout
*/
persist(key: string | ArrayBufferView | Blob): Promise<number>;
/**
* Get the expiration time of a key as a UNIX timestamp in milliseconds
* @param key The key to check
* @returns Promise that resolves with the timestamp, or -1 if the key has no expiration, or -2 if the key doesn't exist
*/
pexpiretime(key: string | ArrayBufferView | Blob): Promise<number>;
/**
* Get the time to live for a key in milliseconds
* @param key The key to check
* @returns Promise that resolves with the TTL in milliseconds, or -1 if the key has no expiration, or -2 if the key doesn't exist
*/
pttl(key: string | ArrayBufferView | Blob): Promise<number>;
/**
* Remove and get the last element in a list
* @param key The list key
* @returns Promise that resolves with the last element, or null if the list is empty
*/
rpop(key: string | ArrayBufferView | Blob): Promise<string | null>;
/**
* Get the number of members in a set
* @param key The set key
* @returns Promise that resolves with the cardinality (number of elements) of the set
*/
scard(key: string | ArrayBufferView | Blob): Promise<number>;
/**
* Get the length of the value stored in a key
* @param key The key to check
* @returns Promise that resolves with the length of the string value, or 0 if the key doesn't exist
*/
strlen(key: string | ArrayBufferView | Blob): Promise<number>;
/**
* Get the number of members in a sorted set
* @param key The sorted set key
* @returns Promise that resolves with the cardinality (number of elements) of the sorted set
*/
zcard(key: string | ArrayBufferView | Blob): Promise<number>;
/**
* Remove and return members with the highest scores in a sorted set
* @param key The sorted set key
* @returns Promise that resolves with the removed member and its score, or null if the set is empty
*/
zpopmax(key: string | ArrayBufferView | Blob): Promise<string | null>;
/**
* Remove and return members with the lowest scores in a sorted set
* @param key The sorted set key
* @returns Promise that resolves with the removed member and its score, or null if the set is empty
*/
zpopmin(key: string | ArrayBufferView | Blob): Promise<string | null>;
/**
* Get one or multiple random members from a sorted set
* @param key The sorted set key
* @returns Promise that resolves with a random member, or null if the set is empty
*/
zrandmember(key: string | ArrayBufferView | Blob): Promise<string | null>;
/**
* Append a value to a key
* @param key The key to append to
* @param value The value to append
* @returns Promise that resolves with the length of the string after the append operation
*/
append(key: string | ArrayBufferView | Blob, value: string | ArrayBufferView | Blob): Promise<number>;
/**
* Set the value of a key and return its old value
* @param key The key to set
* @param value The value to set
* @returns Promise that resolves with the old value, or null if the key didn't exist
*/
getset(key: string | ArrayBufferView | Blob, value: string | ArrayBufferView | Blob): Promise<string | null>;
/**
* Prepend one or multiple values to a list
* @param key The list key
* @param value The value to prepend
* @returns Promise that resolves with the length of the list after the push operation
*/
lpush(key: string | ArrayBufferView | Blob, value: string | ArrayBufferView | Blob): Promise<number>;
/**
* Prepend a value to a list, only if the list exists
* @param key The list key
* @param value The value to prepend
* @returns Promise that resolves with the length of the list after the push operation, or 0 if the list doesn't exist
*/
lpushx(key: string | ArrayBufferView | Blob, value: string | ArrayBufferView | Blob): Promise<number>;
/**
* Add one or more members to a HyperLogLog
* @param key The HyperLogLog key
* @param element The element to add
* @returns Promise that resolves with 1 if the HyperLogLog was altered, 0 otherwise
*/
pfadd(key: string | ArrayBufferView | Blob, element: string): Promise<number>;
/**
* Append one or multiple values to a list
* @param key The list key
* @param value The value to append
* @returns Promise that resolves with the length of the list after the push operation
*/
rpush(key: string | ArrayBufferView | Blob, value: string | ArrayBufferView | Blob): Promise<number>;
/**
* Append a value to a list, only if the list exists
* @param key The list key
* @param value The value to append
* @returns Promise that resolves with the length of the list after the push operation, or 0 if the list doesn't exist
*/
rpushx(key: string | ArrayBufferView | Blob, value: string | ArrayBufferView | Blob): Promise<number>;
/**
* Set the value of a key, only if the key does not exist
* @param key The key to set
* @param value The value to set
* @returns Promise that resolves with 1 if the key was set, 0 if the key was not set
*/
setnx(key: string | ArrayBufferView | Blob, value: string | ArrayBufferView | Blob): Promise<number>;
/**
* Get the score associated with the given member in a sorted set
* @param key The sorted set key
* @param member The member to get the score for
* @returns Promise that resolves with the score of the member as a string, or null if the member or key doesn't exist
*/
zscore(key: string | ArrayBufferView | Blob, member: string): Promise<string | null>;
/**
* Get the values of all specified keys
* @param keys The keys to get
* @returns Promise that resolves with an array of values, with null for keys that don't exist
*/
mget(...keys: (string | ArrayBufferView | Blob)[]): Promise<(string | null)[]>;
/**
* Count the number of set bits (population counting) in a string
* @param key The key to count bits in
* @returns Promise that resolves with the number of bits set to 1
*/
bitcount(key: string | ArrayBufferView | Blob): Promise<number>;
/**
* Return a serialized version of the value stored at the specified key
* @param key The key to dump
* @returns Promise that resolves with the serialized value, or null if the key doesn't exist
*/
dump(key: string | ArrayBufferView | Blob): Promise<string | null>;
/**
* Get the expiration time of a key as a UNIX timestamp in seconds
* @param key The key to check
* @returns Promise that resolves with the timestamp, or -1 if the key has no expiration, or -2 if the key doesn't exist
*/
expiretime(key: string | ArrayBufferView | Blob): Promise<number>;
/**
* Get the value of a key and delete the key
* @param key The key to get and delete
* @returns Promise that resolves with the value of the key, or null if the key doesn't exist
*/
getdel(key: string | ArrayBufferView | Blob): Promise<string | null>;
/**
* Get the value of a key and optionally set its expiration
* @param key The key to get
* @returns Promise that resolves with the value of the key, or null if the key doesn't exist
*/
getex(key: string | ArrayBufferView | Blob): Promise<string | null>;
}
/**
* Default Redis client
*
* Connection information populated from one of, in order of preference:
* - `process.env.VALKEY_URL`
* - `process.env.REDIS_URL`
* - `"valkey://localhost:6379"`
*
*/
export const redis: RedisClient;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,379 +0,0 @@
declare module "bun" {
type ShellFunction = (input: Uint8Array) => Uint8Array;
type ShellExpression =
| { toString(): string }
| Array<ShellExpression>
| string
| { raw: string }
| Subprocess
| SpawnOptions.Readable
| SpawnOptions.Writable
| ReadableStream;
/**
* The [Bun shell](https://bun.sh/docs/runtime/shell) is a powerful tool for running shell commands.
*
* @example
* ```ts
* const result = await $`echo "Hello, world!"`.text();
* console.log(result); // "Hello, world!"
* ```
*
* @category Process Management
*/
function $(strings: TemplateStringsArray, ...expressions: ShellExpression[]): $.ShellPromise;
namespace $ {
const Shell: new () => typeof $;
/**
* Perform bash-like brace expansion on the given pattern.
* @param pattern - Brace pattern to expand
*
* @example
* ```js
* const result = braces('index.{js,jsx,ts,tsx}');
* console.log(result) // ['index.js', 'index.jsx', 'index.ts', 'index.tsx']
* ```
*/
function braces(pattern: string): string[];
/**
* Escape strings for input into shell commands.
* @param input
*/
function escape(input: string): string;
/**
*
* Change the default environment variables for shells created by this instance.
*
* @param newEnv Default environment variables to use for shells created by this instance.
* @default process.env
*
* @example
* ```js
* import {$} from 'bun';
* $.env({ BUN: "bun" });
* await $`echo $BUN`;
* // "bun"
* ```
*/
function env(newEnv?: Record<string, string | undefined>): typeof $;
/**
*
* @param newCwd Default working directory to use for shells created by this instance.
*/
function cwd(newCwd?: string): typeof $;
/**
* Configure the shell to not throw an exception on non-zero exit codes.
*/
function nothrow(): typeof $;
/**
* Configure whether or not the shell should throw an exception on non-zero exit codes.
*/
function throws(shouldThrow: boolean): typeof $;
/**
* The `Bun.$.ShellPromise` class represents a shell command that gets executed
* once awaited, or called with `.text()`, `.json()`, etc.
*
* @example
* ```ts
* const myShellPromise = $`echo "Hello, world!"`;
* const result = await myShellPromise.text();
* console.log(result); // "Hello, world!"
* ```
*/
class ShellPromise extends Promise<ShellOutput> {
get stdin(): WritableStream;
/**
* Change the current working directory of the shell.
* @param newCwd - The new working directory
*/
cwd(newCwd: string): this;
/**
* Set environment variables for the shell.
* @param newEnv - The new environment variables
*
* @example
* ```ts
* await $`echo $FOO`.env({ ...process.env, FOO: "LOL!" })
* expect(stdout.toString()).toBe("LOL!");
* ```
*/
env(newEnv: Record<string, string> | undefined): this;
/**
* By default, the shell will write to the current process's stdout and stderr, as well as buffering that output.
*
* This configures the shell to only buffer the output.
*/
quiet(): this;
/**
* Read from stdout as a string, line by line
*
* Automatically calls {@link quiet} to disable echoing to stdout.
*/
lines(): AsyncIterable<string>;
/**
* Read from stdout as a string.
*
* Automatically calls {@link quiet} to disable echoing to stdout.
*
* @param encoding - The encoding to use when decoding the output
* @returns A promise that resolves with stdout as a string
*
* @example
* **Read as UTF-8 string**
* ```ts
* const output = await $`echo hello`.text();
* console.log(output); // "hello\n"
* ```
*
* **Read as base64 string**
* ```ts
* const output = await $`echo ${atob("hello")}`.text("base64");
* console.log(output); // "hello\n"
* ```
*/
text(encoding?: BufferEncoding): Promise<string>;
/**
* Read from stdout as a JSON object
*
* Automatically calls {@link quiet}
*
* @returns A promise that resolves with stdout as a JSON object
* @example
*
* ```ts
* const output = await $`echo '{"hello": 123}'`.json();
* console.log(output); // { hello: 123 }
* ```
*
*/
json(): Promise<any>;
/**
* Read from stdout as an ArrayBuffer
*
* Automatically calls {@link quiet}
* @returns A promise that resolves with stdout as an ArrayBuffer
* @example
*
* ```ts
* const output = await $`echo hello`.arrayBuffer();
* console.log(output); // ArrayBuffer { byteLength: 6 }
* ```
*/
arrayBuffer(): Promise<ArrayBuffer>;
/**
* Read from stdout as a Blob
*
* Automatically calls {@link quiet}
* @returns A promise that resolves with stdout as a Blob
* @example
* ```ts
* const output = await $`echo hello`.blob();
* console.log(output); // Blob { size: 6, type: "" }
* ```
*/
blob(): Promise<Blob>;
/**
* Configure the shell to not throw an exception on non-zero exit codes. Throwing can be re-enabled with `.throws(true)`.
*
* By default, the shell with throw an exception on commands which return non-zero exit codes.
*/
nothrow(): this;
/**
* Configure whether or not the shell should throw an exception on non-zero exit codes.
*
* By default, this is configured to `true`.
*/
throws(shouldThrow: boolean): this;
}
/**
* ShellError represents an error that occurred while executing a shell command with [the Bun Shell](https://bun.sh/docs/runtime/shell).
*
* @example
* ```ts
* try {
* const result = await $`exit 1`;
* } catch (error) {
* if (error instanceof ShellError) {
* console.log(error.exitCode); // 1
* }
* }
* ```
*/
class ShellError extends Error implements ShellOutput {
readonly stdout: Buffer;
readonly stderr: Buffer;
readonly exitCode: number;
/**
* Read from stdout as a string
*
* @param encoding - The encoding to use when decoding the output
* @returns Stdout as a string with the given encoding
*
* @example
* **Read as UTF-8 string**
* ```ts
* const output = await $`echo hello`;
* console.log(output.text()); // "hello\n"
* ```
*
* **Read as base64 string**
* ```ts
* const output = await $`echo ${atob("hello")}`;
* console.log(output.text("base64")); // "hello\n"
* ```
*/
text(encoding?: BufferEncoding): string;
/**
* Read from stdout as a JSON object
*
* @returns Stdout as a JSON object
* @example
*
* ```ts
* const output = await $`echo '{"hello": 123}'`;
* console.log(output.json()); // { hello: 123 }
* ```
*
*/
json(): any;
/**
* Read from stdout as an ArrayBuffer
*
* @returns Stdout as an ArrayBuffer
* @example
*
* ```ts
* const output = await $`echo hello`;
* console.log(output.arrayBuffer()); // ArrayBuffer { byteLength: 6 }
* ```
*/
arrayBuffer(): ArrayBuffer;
/**
* Read from stdout as a Blob
*
* @returns Stdout as a blob
* @example
* ```ts
* const output = await $`echo hello`;
* console.log(output.blob()); // Blob { size: 6, type: "" }
* ```
*/
blob(): Blob;
/**
* Read from stdout as an Uint8Array
*
* @returns Stdout as an Uint8Array
* @example
*```ts
* const output = await $`echo hello`;
* console.log(output.bytes()); // Uint8Array { byteLength: 6 }
* ```
*/
bytes(): Uint8Array;
}
interface ShellOutput {
readonly stdout: Buffer;
readonly stderr: Buffer;
readonly exitCode: number;
/**
* Read from stdout as a string
*
* @param encoding - The encoding to use when decoding the output
* @returns Stdout as a string with the given encoding
*
* @example
* **Read as UTF-8 string**
* ```ts
* const output = await $`echo hello`;
* console.log(output.text()); // "hello\n"
* ```
*
* **Read as base64 string**
* ```ts
* const output = await $`echo ${atob("hello")}`;
* console.log(output.text("base64")); // "hello\n"
* ```
*/
text(encoding?: BufferEncoding): string;
/**
* Read from stdout as a JSON object
*
* @returns Stdout as a JSON object
* @example
*
* ```ts
* const output = await $`echo '{"hello": 123}'`;
* console.log(output.json()); // { hello: 123 }
* ```
*
*/
json(): any;
/**
* Read from stdout as an ArrayBuffer
*
* @returns Stdout as an ArrayBuffer
* @example
*
* ```ts
* const output = await $`echo hello`;
* console.log(output.arrayBuffer()); // ArrayBuffer { byteLength: 6 }
* ```
*/
arrayBuffer(): ArrayBuffer;
/**
* Read from stdout as an Uint8Array
*
* @returns Stdout as an Uint8Array
* @example
*
* ```ts
* const output = await $`echo hello`;
* console.log(output.bytes()); // Uint8Array { byteLength: 6 }
* ```
*/
bytes(): Uint8Array;
/**
* Read from stdout as a Blob
*
* @returns Stdout as a blob
* @example
* ```ts
* const output = await $`echo hello`;
* console.log(output.blob()); // Blob { size: 6, type: "" }
* ```
*/
blob(): Blob;
}
}
}

View File

@@ -6,62 +6,58 @@
* ```ts
* import { Database } from 'bun:sqlite';
*
* const db = new Database('app.db');
* var db = new Database('app.db');
* db.query('SELECT * FROM users WHERE name = ?').all('John');
* // => [{ id: 1, name: 'John' }]
* ```
*
* The following types can be used when binding parameters:
*
* | JavaScript type | SQLite type |
* | --------------- | ---------------------- |
* | `string` | `TEXT` |
* | `number` | `INTEGER` or `DECIMAL` |
* | `boolean` | `INTEGER` (1 or 0) |
* | `Uint8Array` | `BLOB` |
* | `Buffer` | `BLOB` |
* | `bigint` | `INTEGER` |
* | `null` | `NULL` |
* | JavaScript type | SQLite type |
* | -------------- | ----------- |
* | `string` | `TEXT` |
* | `number` | `INTEGER` or `DECIMAL` |
* | `boolean` | `INTEGER` (1 or 0) |
* | `Uint8Array` | `BLOB` |
* | `Buffer` | `BLOB` |
* | `bigint` | `INTEGER` |
* | `null` | `NULL` |
*/
declare module "bun:sqlite" {
/**
* A SQLite3 database
*
* @example
* ```ts
* const db = new Database("mydb.sqlite");
* db.run("CREATE TABLE foo (bar TEXT)");
* db.run("INSERT INTO foo VALUES (?)", ["baz"]);
* console.log(db.query("SELECT * FROM foo").all());
* ```
*
* @example
*
* Open an in-memory database
*
* ```ts
* const db = new Database(":memory:");
* db.run("CREATE TABLE foo (bar TEXT)");
* db.run("INSERT INTO foo VALUES (?)", ["hiiiiii"]);
* console.log(db.query("SELECT * FROM foo").all());
* ```
*
* @example
*
* Open read-only
*
* ```ts
* const db = new Database("mydb.sqlite", {readonly: true});
* ```
*
* @category Database
*/
export class Database implements Disposable {
/**
* Open or create a SQLite3 database
*
* @param filename The filename of the database to open. Pass an empty string (`""`) or `":memory:"` or undefined for an in-memory database.
* @param options defaults to `{readwrite: true, create: true}`. If a number, then it's treated as `SQLITE_OPEN_*` constant flags.
*
* @example
*
* ```ts
* const db = new Database("mydb.sqlite");
* db.run("CREATE TABLE foo (bar TEXT)");
* db.run("INSERT INTO foo VALUES (?)", ["baz"]);
* console.log(db.query("SELECT * FROM foo").all());
* ```
*
* @example
*
* Open an in-memory database
*
* ```ts
* const db = new Database(":memory:");
* db.run("CREATE TABLE foo (bar TEXT)");
* db.run("INSERT INTO foo VALUES (?)", ["hiiiiii"]);
* console.log(db.query("SELECT * FROM foo").all());
* ```
*
* @example
*
* Open read-only
*
* ```ts
* const db = new Database("mydb.sqlite", {readonly: true});
* ```
*/
constructor(
filename?: string,
@@ -159,20 +155,6 @@ declare module "bun:sqlite" {
*
* This does not cache the query, so if you want to run a query multiple times, you should use {@link prepare} instead.
*
* Under the hood, this calls `sqlite3_prepare_v3` followed by `sqlite3_step` and `sqlite3_finalize`.
*
* The following types can be used when binding parameters:
*
* | JavaScript type | SQLite type |
* | --------------- | ---------------------- |
* | `string` | `TEXT` |
* | `number` | `INTEGER` or `DECIMAL` |
* | `boolean` | `INTEGER` (1 or 0) |
* | `Uint8Array` | `BLOB` |
* | `Buffer` | `BLOB` |
* | `bigint` | `INTEGER` |
* | `null` | `NULL` |
*
* @example
* ```ts
* db.run("CREATE TABLE foo (bar TEXT)");
@@ -198,15 +180,30 @@ declare module "bun:sqlite" {
* - `CREATE TEMPORARY TABLE`
*
* @param sql The SQL query to run
*
* @param bindings Optional bindings for the query
*
* @returns `Database` instance
*
* Under the hood, this calls `sqlite3_prepare_v3` followed by `sqlite3_step` and `sqlite3_finalize`.
*
* * The following types can be used when binding parameters:
*
* | JavaScript type | SQLite type |
* | -------------- | ----------- |
* | `string` | `TEXT` |
* | `number` | `INTEGER` or `DECIMAL` |
* | `boolean` | `INTEGER` (1 or 0) |
* | `Uint8Array` | `BLOB` |
* | `Buffer` | `BLOB` |
* | `bigint` | `INTEGER` |
* | `null` | `NULL` |
*/
run<ParamsType extends SQLQueryBindings[]>(sql: string, ...bindings: ParamsType[]): Changes;
run<ParamsType extends SQLQueryBindings[]>(sqlQuery: string, ...bindings: ParamsType[]): Changes;
/**
* This is an alias of {@link Database.run}
This is an alias of {@link Database.prototype.run}
*/
exec<ParamsType extends SQLQueryBindings[]>(sql: string, ...bindings: ParamsType[]): Changes;
exec<ParamsType extends SQLQueryBindings[]>(sqlQuery: string, ...bindings: ParamsType[]): Changes;
/**
* Compile a SQL query and return a {@link Statement} object. This is the
@@ -215,8 +212,6 @@ declare module "bun:sqlite" {
* This **does not execute** the query, but instead prepares it for later
* execution and caches the compiled query if possible.
*
* Under the hood, this calls `sqlite3_prepare_v3`.
*
* @example
* ```ts
* // compile the query
@@ -229,19 +224,21 @@ declare module "bun:sqlite" {
* ```
*
* @param sql The SQL query to compile
*
* @returns `Statment` instance
*
* Under the hood, this calls `sqlite3_prepare_v3`.
*/
query<ReturnType, ParamsType extends SQLQueryBindings | SQLQueryBindings[]>(
sql: string,
): Statement<ReturnType, ParamsType extends any[] ? ParamsType : [ParamsType]>;
sqlQuery: string,
): // eslint-disable-next-line @definitelytyped/no-single-element-tuple-type
Statement<ReturnType, ParamsType extends any[] ? ParamsType : [ParamsType]>;
/**
* Compile a SQL query and return a {@link Statement} object.
*
* This does not cache the compiled query and does not execute the query.
*
* Under the hood, this calls `sqlite3_prepare_v3`.
*
* @example
* ```ts
* // compile the query
@@ -253,12 +250,15 @@ declare module "bun:sqlite" {
* @param sql The SQL query to compile
* @param params Optional bindings for the query
*
* @returns A {@link Statement} instance
* @returns `Statment` instance
*
* Under the hood, this calls `sqlite3_prepare_v3`.
*/
prepare<ReturnType, ParamsType extends SQLQueryBindings | SQLQueryBindings[]>(
sql: string,
sqlQuery: string,
params?: ParamsType,
): Statement<ReturnType, ParamsType extends any[] ? ParamsType : [ParamsType]>;
): // eslint-disable-next-line @definitelytyped/no-single-element-tuple-type
Statement<ReturnType, ParamsType extends any[] ? ParamsType : [ParamsType]>;
/**
* Is the database in a transaction?
@@ -567,8 +567,6 @@ declare module "bun:sqlite" {
*
* This is returned by {@link Database.prepare} and {@link Database.query}.
*
* @category Database
*
* @example
* ```ts
* const stmt = db.prepare("SELECT * FROM foo WHERE bar = ?");
@@ -642,15 +640,15 @@ declare module "bun:sqlite" {
*
* The following types can be used when binding parameters:
*
* | JavaScript type | SQLite type |
* | --------------- | ---------------------- |
* | `string` | `TEXT` |
* | `number` | `INTEGER` or `DECIMAL` |
* | `boolean` | `INTEGER` (1 or 0) |
* | `Uint8Array` | `BLOB` |
* | `Buffer` | `BLOB` |
* | `bigint` | `INTEGER` |
* | `null` | `NULL` |
* | JavaScript type | SQLite type |
* | -------------- | ----------- |
* | `string` | `TEXT` |
* | `number` | `INTEGER` or `DECIMAL` |
* | `boolean` | `INTEGER` (1 or 0) |
* | `Uint8Array` | `BLOB` |
* | `Buffer` | `BLOB` |
* | `bigint` | `INTEGER` |
* | `null` | `NULL` |
*/
get(...params: ParamsType): ReturnType | null;
@@ -683,15 +681,15 @@ declare module "bun:sqlite" {
*
* The following types can be used when binding parameters:
*
* | JavaScript type | SQLite type |
* | --------------- | ---------------------- |
* | `string` | `TEXT` |
* | `number` | `INTEGER` or `DECIMAL` |
* | `boolean` | `INTEGER` (1 or 0) |
* | `Uint8Array` | `BLOB` |
* | `Buffer` | `BLOB` |
* | `bigint` | `INTEGER` |
* | `null` | `NULL` |
* | JavaScript type | SQLite type |
* | -------------- | ----------- |
* | `string` | `TEXT` |
* | `number` | `INTEGER` or `DECIMAL` |
* | `boolean` | `INTEGER` (1 or 0) |
* | `Uint8Array` | `BLOB` |
* | `Buffer` | `BLOB` |
* | `bigint` | `INTEGER` |
* | `null` | `NULL` |
*/
run(...params: ParamsType): Changes;
@@ -723,15 +721,15 @@ declare module "bun:sqlite" {
*
* The following types can be used when binding parameters:
*
* | JavaScript type | SQLite type |
* | --------------- | ---------------------- |
* | `string` | `TEXT` |
* | `number` | `INTEGER` or `DECIMAL` |
* | `boolean` | `INTEGER` (1 or 0) |
* | `Uint8Array` | `BLOB` |
* | `Buffer` | `BLOB` |
* | `bigint` | `INTEGER` |
* | `null` | `NULL` |
* | JavaScript type | SQLite type |
* | ---------------|-------------|
* | `string` | `TEXT` |
* | `number` | `INTEGER` or `DECIMAL` |
* | `boolean` | `INTEGER` (1 or 0) |
* | `Uint8Array` | `BLOB` |
* | `Buffer` | `BLOB` |
* | `bigint` | `INTEGER` |
* | `null` | `NULL` |
*/
values(...params: ParamsType): Array<Array<string | bigint | number | boolean | Uint8Array>>;

View File

@@ -16,8 +16,6 @@
declare module "bun:test" {
/**
* -- Mocks --
*
* @category Testing
*/
export type Mock<T extends (...args: any[]) => any> = JestMock.Mock<T>;
@@ -29,14 +27,11 @@ declare module "bun:test" {
*
* This is useful for mocking modules.
*
* If the module is already loaded, exports are overwritten with the return
* value of `factory`. If the export didn't exist before, it will not be
* added to existing import statements. This is due to how ESM works.
*
* @param id module ID to mock
* @param factory a function returning an object that will be used as the exports of the mocked module
*
* @example
* ## Example
* ```ts
* import { mock } from "bun:test";
*
@@ -50,6 +45,12 @@ declare module "bun:test" {
*
* console.log(await readFile("hello.txt", "utf8")); // hello world
* ```
*
* ## More notes
*
* If the module is already loaded, exports are overwritten with the return
* value of `factory`. If the export didn't exist before, it will not be
* added to existing import statements. This is due to how ESM works.
*/
module(id: string, factory: () => any): void | Promise<void>;
/**
@@ -148,12 +149,6 @@ declare module "bun:test" {
methodOrPropertyValue: K,
): Mock<T[K] extends (...args: any[]) => any ? T[K] : never>;
interface FunctionLike {
readonly name: string;
}
type DescribeLabel = number | string | Function | FunctionLike;
/**
* Describes a group of related tests.
*
@@ -169,34 +164,36 @@ declare module "bun:test" {
*
* @param label the label for the tests
* @param fn the function that defines the tests
*
* @category Testing
*/
interface FunctionLike {
readonly name: string;
}
export interface Describe {
(fn: () => void): void;
(label: DescribeLabel, fn: () => void): void;
(label: number | string | Function | FunctionLike, fn: () => void): void;
/**
* Skips all other tests, except this group of tests.
*
* @param label the label for the tests
* @param fn the function that defines the tests
*/
only(label: DescribeLabel, fn: () => void): void;
only(label: string, fn: () => void): void;
/**
* Skips this group of tests.
*
* @param label the label for the tests
* @param fn the function that defines the tests
*/
skip(label: DescribeLabel, fn: () => void): void;
skip(label: string, fn: () => void): void;
/**
* Marks this group of tests as to be written or to be fixed.
*
* @param label the label for the tests
* @param fn the function that defines the tests
*/
todo(label: DescribeLabel, fn?: () => void): void;
todo(label: string, fn?: () => void): void;
/**
* Runs this group of tests, only if `condition` is true.
*
@@ -204,19 +201,19 @@ declare module "bun:test" {
*
* @param condition if these tests should run
*/
if(condition: boolean): (label: DescribeLabel, fn: () => void) => void;
if(condition: boolean): (label: string, fn: () => void) => void;
/**
* Skips this group of tests, if `condition` is true.
*
* @param condition if these tests should be skipped
*/
skipIf(condition: boolean): (label: DescribeLabel, fn: () => void) => void;
skipIf(condition: boolean): (label: string, fn: () => void) => void;
/**
* Marks this group of tests as to be written or to be fixed, if `condition` is true.
*
* @param condition if these tests should be skipped
*/
todoIf(condition: boolean): (label: DescribeLabel, fn: () => void) => void;
todoIf(condition: boolean): (label: string, fn: () => void) => void;
/**
* Returns a function that runs for each item in `table`.
*
@@ -224,17 +221,13 @@ declare module "bun:test" {
*/
each<T extends Readonly<[any, ...any[]]>>(
table: readonly T[],
): (label: DescribeLabel, fn: (...args: [...T]) => void | Promise<unknown>, options?: number | TestOptions) => void;
): (label: string, fn: (...args: [...T]) => void | Promise<unknown>, options?: number | TestOptions) => void;
each<T extends any[]>(
table: readonly T[],
): (
label: DescribeLabel,
fn: (...args: Readonly<T>) => void | Promise<unknown>,
options?: number | TestOptions,
) => void;
): (label: string, fn: (...args: Readonly<T>) => void | Promise<unknown>, options?: number | TestOptions) => void;
each<T>(
table: T[],
): (label: DescribeLabel, fn: (...args: T[]) => void | Promise<unknown>, options?: number | TestOptions) => void;
): (label: string, fn: (...args: T[]) => void | Promise<unknown>, options?: number | TestOptions) => void;
}
/**
* Describes a group of related tests.
@@ -359,8 +352,6 @@ declare module "bun:test" {
* @param label the label for the test
* @param fn the test function
* @param options the test timeout or options
*
* @category Testing
*/
export interface Test {
(
@@ -431,11 +422,7 @@ declare module "bun: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),
options?: number | TestOptions,
): void;
failing(label: string, fn?: (() => void | Promise<unknown>) | ((done: (err?: unknown) => void) => void)): void;
/**
* Runs this test, if `condition` is true.
*
@@ -596,6 +583,8 @@ declare module "bun:test" {
* @returns never
*
* @example
* ## Example
*
* ```ts
* import { expect, test } from "bun:test";
*
@@ -1787,329 +1776,371 @@ declare module "bun:test" {
}
type MatcherContext = MatcherUtils & MatcherState;
}
namespace JestMock {
declare module "test" {
export type * from "bun:test";
}
declare namespace JestMock {
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
export interface ClassLike {
new (...args: any): any;
}
export type ConstructorLikeKeys<T> = keyof {
[K in keyof T as Required<T>[K] extends ClassLike ? K : never]: T[K];
};
// export const fn: <T extends FunctionLike = UnknownFunction>(
// implementation?: T | undefined,
// ) => Mock<T>;
export type FunctionLike = (...args: any) => any;
export type MethodLikeKeys<T> = keyof {
[K in keyof T as Required<T>[K] extends FunctionLike ? K : never]: T[K];
};
export interface Mock<T extends (...args: any[]) => any> extends MockInstance<T> {
(...args: Parameters<T>): ReturnType<T>;
}
/**
* All what the internal typings need is to be sure that we have any-function.
* `FunctionLike` type ensures that and helps to constrain the type as well.
* The default of `UnknownFunction` makes sure that `any`s do not leak to the
* user side. For instance, calling `fn()` without implementation will return
* a mock of `(...args: Array<unknown>) => unknown` type. If implementation
* is provided, its typings are inferred correctly.
*/
// export interface Mock<T extends FunctionLike = UnknownFunction>
// extends Function,
// MockInstance<T> {
// new (...args: Parameters<T>): ReturnType<T>;
// (...args: Parameters<T>): ReturnType<T>;
// }
// export type Mocked<T> = T extends ClassLike
// ? MockedClass<T>
// : T extends FunctionLike
// ? MockedFunction<T>
// : T extends object
// ? MockedObject<T>
// : T;
// export const mocked: {
// <T extends object>(
// source: T,
// options?: {
// shallow: false;
// },
// ): Mocked<T>;
// <T_1 extends object>(
// source: T_1,
// options: {
// shallow: true;
// },
// ): MockedShallow<T_1>;
// };
// export type MockedClass<T extends ClassLike> = MockInstance<
// (...args: ConstructorParameters<T>) => Mocked<InstanceType<T>>
// > &
// MockedObject<T>;
// export type MockedFunction<T extends FunctionLike> = MockInstance<T> &
// MockedObject<T>;
// type MockedFunctionShallow<T extends FunctionLike> = MockInstance<T> & T;
// export type MockedObject<T extends object> = {
// [K in keyof T]: T[K] extends ClassLike
// ? MockedClass<T[K]>
// : T[K] extends FunctionLike
// ? MockedFunction<T[K]>
// : T[K] extends object
// ? MockedObject<T[K]>
// : T[K];
// } & T;
// type MockedObjectShallow<T extends object> = {
// [K in keyof T]: T[K] extends ClassLike
// ? MockedClass<T[K]>
// : T[K] extends FunctionLike
// ? MockedFunctionShallow<T[K]>
// : T[K];
// } & T;
// export type MockedShallow<T> = T extends ClassLike
// ? MockedClass<T>
// : T extends FunctionLike
// ? MockedFunctionShallow<T>
// : T extends object
// ? MockedObjectShallow<T>
// : T;
// export type MockFunctionMetadata<
// T = unknown,
// MetadataType = MockMetadataType,
// > = MockMetadata<T, MetadataType>;
// export type MockFunctionMetadataType = MockMetadataType;
type MockFunctionResult<T extends FunctionLike = UnknownFunction> =
| MockFunctionResultIncomplete
| MockFunctionResultReturn<T>
| MockFunctionResultThrow;
interface MockFunctionResultIncomplete {
type: "incomplete";
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
* Result of a single call to a mock function that has not yet completed.
* This occurs if you test the result from within the mock function itself,
* or from within a function that was called by the mock.
*/
export interface ClassLike {
new (...args: any): any;
}
export type ConstructorLikeKeys<T> = keyof {
[K in keyof T as Required<T>[K] extends ClassLike ? K : never]: T[K];
};
// export const fn: <T extends FunctionLike = UnknownFunction>(
// implementation?: T | undefined,
// ) => Mock<T>;
export type FunctionLike = (...args: any) => any;
export type MethodLikeKeys<T> = keyof {
[K in keyof T as Required<T>[K] extends FunctionLike ? K : never]: T[K];
};
export interface Mock<T extends (...args: any[]) => any> extends MockInstance<T> {
(...args: Parameters<T>): ReturnType<T>;
}
value: undefined;
}
interface MockFunctionResultReturn<T extends FunctionLike = UnknownFunction> {
type: "return";
/**
* All what the internal typings need is to be sure that we have any-function.
* `FunctionLike` type ensures that and helps to constrain the type as well.
* The default of `UnknownFunction` makes sure that `any`s do not leak to the
* user side. For instance, calling `fn()` without implementation will return
* a mock of `(...args: Array<unknown>) => unknown` type. If implementation
* is provided, its typings are inferred correctly.
* Result of a single call to a mock function that returned.
*/
// export interface Mock<T extends FunctionLike = UnknownFunction>
// extends Function,
// MockInstance<T> {
// new (...args: Parameters<T>): ReturnType<T>;
// (...args: Parameters<T>): ReturnType<T>;
// }
value: ReturnType<T>;
}
// export type Mocked<T> = T extends ClassLike
// ? MockedClass<T>
// : T extends FunctionLike
// ? MockedFunction<T>
// : T extends object
// ? MockedObject<T>
// : T;
interface MockFunctionResultThrow {
type: "throw";
/**
* Result of a single call to a mock function that threw.
*/
value: unknown;
}
// export const mocked: {
// <T extends object>(
// source: T,
// options?: {
// shallow: false;
// },
// ): Mocked<T>;
// <T_1 extends object>(
// source: T_1,
// options: {
// shallow: true;
// },
// ): MockedShallow<T_1>;
// };
interface MockFunctionState<T extends FunctionLike = FunctionLike> {
/**
* List of the call arguments of all calls that have been made to the mock.
*/
calls: Array<Parameters<T>>;
/**
* List of all the object instances that have been instantiated from the mock.
*/
instances: Array<ReturnType<T>>;
/**
* List of all the function contexts that have been applied to calls to the mock.
*/
contexts: Array<ThisParameterType<T>>;
/**
* List of the call order indexes of the mock. Jest is indexing the order of
* invocations of all mocks in a test file. The index is starting with `1`.
*/
invocationCallOrder: number[];
/**
* List of the call arguments of the last call that was made to the mock.
* If the function was not called, it will return `undefined`.
*/
lastCall?: Parameters<T>;
/**
* List of the results of all calls that have been made to the mock.
*/
results: Array<MockFunctionResult<T>>;
}
// export type MockedClass<T extends ClassLike> = MockInstance<
// (...args: ConstructorParameters<T>) => Mocked<InstanceType<T>>
// > &
// MockedObject<T>;
export interface MockInstance<T extends FunctionLike = UnknownFunction> {
_isMockFunction: true;
_protoImpl: Function;
getMockImplementation(): T | undefined;
getMockName(): string;
mock: MockFunctionState<T>;
mockClear(): this;
mockReset(): this;
mockRestore(): void;
mockImplementation(fn: T): this;
mockImplementationOnce(fn: T): this;
withImplementation(fn: T, callback: () => Promise<unknown>): Promise<void>;
withImplementation(fn: T, callback: () => void): void;
mockName(name: string): this;
mockReturnThis(): this;
mockReturnValue(value: ReturnType<T>): this;
mockReturnValueOnce(value: ReturnType<T>): this;
mockResolvedValue(value: ResolveType<T>): this;
mockResolvedValueOnce(value: ResolveType<T>): this;
mockRejectedValue(value: RejectType<T>): this;
mockRejectedValueOnce(value: RejectType<T>): this;
}
// export type MockedFunction<T extends FunctionLike> = MockInstance<T> &
// MockedObject<T>;
// export type MockMetadata<T, MetadataType = MockMetadataType> = {
// ref?: number;
// members?: Record<string, MockMetadata<T>>;
// mockImpl?: T;
// name?: string;
// refID?: number;
// type?: MetadataType;
// value?: T;
// length?: number;
// };
// type MockedFunctionShallow<T extends FunctionLike> = MockInstance<T> & T;
// export type MockMetadataType =
// | "object"
// | "array"
// | "regexp"
// | "function"
// | "constant"
// | "collection"
// | "null"
// | "undefined";
// export type MockedObject<T extends object> = {
// [K in keyof T]: T[K] extends ClassLike
// ? MockedClass<T[K]>
// : T[K] extends FunctionLike
// ? MockedFunction<T[K]>
// : T[K] extends object
// ? MockedObject<T[K]>
// : T[K];
// } & T;
// export class ModuleMocker {
// private readonly _environmentGlobal;
// private _mockState;
// private _mockConfigRegistry;
// private _spyState;
// private _invocationCallCounter;
// /**
// * @see README.md
// * @param global Global object of the test environment, used to create
// * mocks
// */
// constructor(global: typeof globalThis);
// private _getSlots;
// private _ensureMockConfig;
// private _ensureMockState;
// private _defaultMockConfig;
// private _defaultMockState;
// private _makeComponent;
// private _createMockFunction;
// private _generateMock;
// /**
// * Check whether the given property of an object has been already replaced.
// */
// private _findReplacedProperty;
// /**
// * @see README.md
// * @param metadata Metadata for the mock in the schema returned by the
// * getMetadata method of this module.
// */
// generateFromMetadata<T>(metadata: MockMetadata<T>): Mocked<T>;
// /**
// * @see README.md
// * @param component The component for which to retrieve metadata.
// */
// getMetadata<T = unknown>(
// component: T,
// _refs?: Map<T, number>,
// ): MockMetadata<T> | null;
// isMockFunction<T extends FunctionLike = UnknownFunction>(
// fn: MockInstance<T>,
// ): fn is MockInstance<T>;
// isMockFunction<P extends Array<unknown>, R>(
// fn: (...args: P) => R,
// ): fn is Mock<(...args: P) => R>;
// isMockFunction(fn: unknown): fn is Mock<UnknownFunction>;
// fn<T extends FunctionLike = UnknownFunction>(implementation?: T): Mock<T>;
// private _attachMockImplementation;
// spyOn<
// T extends object,
// K extends PropertyLikeKeys<T>,
// A extends "get" | "set",
// >(
// object: T,
// methodKey: K,
// accessType: A,
// ): A extends "get"
// ? SpiedGetter<T[K]>
// : A extends "set"
// ? SpiedSetter<T[K]>
// : never;
// spyOn<
// T extends object,
// K extends ConstructorLikeKeys<T> | MethodLikeKeys<T>,
// V extends Required<T>[K],
// >(
// object: T,
// methodKey: K,
// ): V extends ClassLike | FunctionLike ? Spied<V> : never;
// private _spyOnProperty;
// replaceProperty<
// T extends object,
// K extends PropertyLikeKeys<T>,
// V extends T[K],
// >(object: T, propertyKey: K, value: V): Replaced<T[K]>;
// clearAllMocks(): void;
// resetAllMocks(): void;
// restoreAllMocks(): void;
// private _typeOf;
// mocked<T extends object>(
// source: T,
// options?: {
// shallow: false;
// },
// ): Mocked<T>;
// mocked<T extends object>(
// source: T,
// options: {
// shallow: true;
// },
// ): MockedShallow<T>;
// }
// type MockedObjectShallow<T extends object> = {
// [K in keyof T]: T[K] extends ClassLike
// ? MockedClass<T[K]>
// : T[K] extends FunctionLike
// ? MockedFunctionShallow<T[K]>
// : T[K];
// } & T;
export type PropertyLikeKeys<T> = Exclude<keyof T, ConstructorLikeKeys<T> | MethodLikeKeys<T>>;
// export type MockedShallow<T> = T extends ClassLike
// ? MockedClass<T>
// : T extends FunctionLike
// ? MockedFunctionShallow<T>
// : T extends object
// ? MockedObjectShallow<T>
// : T;
export type RejectType<T extends FunctionLike> = ReturnType<T> extends PromiseLike<any> ? unknown : never;
// export type MockFunctionMetadata<
// T = unknown,
// MetadataType = MockMetadataType,
// > = MockMetadata<T, MetadataType>;
export interface Replaced<T = unknown> {
/**
* Restore property to its original value known at the time of mocking.
*/
restore(): void;
/**
* Change the value of the property.
*/
replaceValue(value: T): this;
}
// export type MockFunctionMetadataType = MockMetadataType;
export function replaceProperty<
T extends object,
K_2 extends Exclude<
keyof T,
| keyof {
[K in keyof T as Required<T>[K] extends ClassLike ? K : never]: T[K];
}
| keyof {
[K_1 in keyof T as Required<T>[K_1] extends FunctionLike ? K_1 : never]: T[K_1];
}
>,
V extends T[K_2],
>(object: T, propertyKey: K_2, value: V): Replaced<T[K_2]>;
type MockFunctionResult<T extends FunctionLike = UnknownFunction> =
| MockFunctionResultIncomplete
| MockFunctionResultReturn<T>
| MockFunctionResultThrow;
export type ResolveType<T extends FunctionLike> = ReturnType<T> extends PromiseLike<infer U> ? U : never;
interface MockFunctionResultIncomplete {
type: "incomplete";
/**
* Result of a single call to a mock function that has not yet completed.
* This occurs if you test the result from within the mock function itself,
* or from within a function that was called by the mock.
*/
value: undefined;
}
export type Spied<T extends ClassLike | FunctionLike> = T extends ClassLike
? SpiedClass<T>
: T extends FunctionLike
? SpiedFunction<T>
: never;
interface MockFunctionResultReturn<T extends FunctionLike = UnknownFunction> {
type: "return";
/**
* Result of a single call to a mock function that returned.
*/
value: ReturnType<T>;
}
export type SpiedClass<T extends ClassLike = UnknownClass> = MockInstance<
(...args: ConstructorParameters<T>) => InstanceType<T>
>;
interface MockFunctionResultThrow {
type: "throw";
/**
* Result of a single call to a mock function that threw.
*/
value: unknown;
}
export type SpiedFunction<T extends FunctionLike = UnknownFunction> = MockInstance<
(...args: Parameters<T>) => ReturnType<T>
>;
interface MockFunctionState<T extends FunctionLike = FunctionLike> {
/**
* List of the call arguments of all calls that have been made to the mock.
*/
calls: Array<Parameters<T>>;
/**
* List of all the object instances that have been instantiated from the mock.
*/
instances: Array<ReturnType<T>>;
/**
* List of all the function contexts that have been applied to calls to the mock.
*/
contexts: Array<ThisParameterType<T>>;
/**
* List of the call order indexes of the mock. Jest is indexing the order of
* invocations of all mocks in a test file. The index is starting with `1`.
*/
invocationCallOrder: number[];
/**
* List of the call arguments of the last call that was made to the mock.
* If the function was not called, it will return `undefined`.
*/
lastCall?: Parameters<T>;
/**
* List of the results of all calls that have been made to the mock.
*/
results: Array<MockFunctionResult<T>>;
}
export type SpiedGetter<T> = MockInstance<() => T>;
export interface MockInstance<T extends FunctionLike = UnknownFunction> {
_isMockFunction: true;
_protoImpl: Function;
getMockImplementation(): T | undefined;
getMockName(): string;
mock: MockFunctionState<T>;
mockClear(): this;
mockReset(): this;
mockRestore(): void;
mockImplementation(fn: T): this;
mockImplementationOnce(fn: T): this;
withImplementation(fn: T, callback: () => Promise<unknown>): Promise<void>;
withImplementation(fn: T, callback: () => void): void;
mockName(name: string): this;
mockReturnThis(): this;
mockReturnValue(value: ReturnType<T>): this;
mockReturnValueOnce(value: ReturnType<T>): this;
mockResolvedValue(value: ResolveType<T>): this;
mockResolvedValueOnce(value: ResolveType<T>): this;
mockRejectedValue(value: RejectType<T>): this;
mockRejectedValueOnce(value: RejectType<T>): this;
}
export type SpiedSetter<T> = MockInstance<(arg: T) => void>;
// export type MockMetadata<T, MetadataType = MockMetadataType> = {
// ref?: number;
// members?: Record<string, MockMetadata<T>>;
// mockImpl?: T;
// name?: string;
// refID?: number;
// type?: MetadataType;
// value?: T;
// length?: number;
// };
export interface SpyInstance<T extends FunctionLike = UnknownFunction> extends MockInstance<T> {}
// export type MockMetadataType =
// | "object"
// | "array"
// | "regexp"
// | "function"
// | "constant"
// | "collection"
// | "null"
// | "undefined";
// export class ModuleMocker {
// private readonly _environmentGlobal;
// private _mockState;
// private _mockConfigRegistry;
// private _spyState;
// private _invocationCallCounter;
// /**
// * @see README.md
// * @param global Global object of the test environment, used to create
// * mocks
// */
// constructor(global: typeof globalThis);
// private _getSlots;
// private _ensureMockConfig;
// private _ensureMockState;
// private _defaultMockConfig;
// private _defaultMockState;
// private _makeComponent;
// private _createMockFunction;
// private _generateMock;
// /**
// * Check whether the given property of an object has been already replaced.
// */
// private _findReplacedProperty;
// /**
// * @see README.md
// * @param metadata Metadata for the mock in the schema returned by the
// * getMetadata method of this module.
// */
// generateFromMetadata<T>(metadata: MockMetadata<T>): Mocked<T>;
// /**
// * @see README.md
// * @param component The component for which to retrieve metadata.
// */
// getMetadata<T = unknown>(
// component: T,
// _refs?: Map<T, number>,
// ): MockMetadata<T> | null;
// isMockFunction<T extends FunctionLike = UnknownFunction>(
// fn: MockInstance<T>,
// ): fn is MockInstance<T>;
// isMockFunction<P extends Array<unknown>, R>(
// fn: (...args: P) => R,
// ): fn is Mock<(...args: P) => R>;
// isMockFunction(fn: unknown): fn is Mock<UnknownFunction>;
// fn<T extends FunctionLike = UnknownFunction>(implementation?: T): Mock<T>;
// private _attachMockImplementation;
// spyOn<
// T extends object,
// K extends PropertyLikeKeys<T>,
// A extends "get" | "set",
// >(
// object: T,
// methodKey: K,
// accessType: A,
// ): A extends "get"
// ? SpiedGetter<T[K]>
// : A extends "set"
// ? SpiedSetter<T[K]>
// : never;
// spyOn<
// T extends object,
// K extends ConstructorLikeKeys<T> | MethodLikeKeys<T>,
// V extends Required<T>[K],
// >(
// object: T,
// methodKey: K,
// ): V extends ClassLike | FunctionLike ? Spied<V> : never;
// private _spyOnProperty;
// replaceProperty<
// T extends object,
// K extends PropertyLikeKeys<T>,
// V extends T[K],
// >(object: T, propertyKey: K, value: V): Replaced<T[K]>;
// clearAllMocks(): void;
// resetAllMocks(): void;
// restoreAllMocks(): void;
// private _typeOf;
// mocked<T extends object>(
// source: T,
// options?: {
// shallow: false;
// },
// ): Mocked<T>;
// mocked<T extends object>(
// source: T,
// options: {
// shallow: true;
// },
// ): MockedShallow<T>;
// }
export type PropertyLikeKeys<T> = Exclude<keyof T, ConstructorLikeKeys<T> | MethodLikeKeys<T>>;
export type RejectType<T extends FunctionLike> = ReturnType<T> extends PromiseLike<any> ? unknown : never;
export interface Replaced<T = unknown> {
/**
* Restore property to its original value known at the time of mocking.
*/
restore(): void;
/**
* Change the value of the property.
*/
replaceValue(value: T): this;
}
export function replaceProperty<
export const spyOn: {
<
T extends object,
K_2 extends Exclude<
keyof T,
@@ -2120,70 +2151,32 @@ declare module "bun:test" {
[K_1 in keyof T as Required<T>[K_1] extends FunctionLike ? K_1 : never]: T[K_1];
}
>,
V extends T[K_2],
>(object: T, propertyKey: K_2, value: V): Replaced<T[K_2]>;
V extends Required<T>[K_2],
A extends "set" | "get",
>(
object: T,
methodKey: K_2,
accessType: A,
): A extends "get" ? SpiedGetter<V> : A extends "set" ? SpiedSetter<V> : never;
<
T_1 extends object,
K_5 extends
| keyof {
[K_3 in keyof T_1 as Required<T_1>[K_3] extends ClassLike ? K_3 : never]: T_1[K_3];
}
| keyof {
[K_4 in keyof T_1 as Required<T_1>[K_4] extends FunctionLike ? K_4 : never]: T_1[K_4];
},
V_1 extends Required<T_1>[K_5],
>(
object: T_1,
methodKey: K_5,
): V_1 extends ClassLike | FunctionLike ? Spied<V_1> : never;
};
export type ResolveType<T extends FunctionLike> = ReturnType<T> extends PromiseLike<infer U> ? U : never;
export type Spied<T extends ClassLike | FunctionLike> = T extends ClassLike
? SpiedClass<T>
: T extends FunctionLike
? SpiedFunction<T>
: never;
export type SpiedClass<T extends ClassLike = UnknownClass> = MockInstance<
(...args: ConstructorParameters<T>) => InstanceType<T>
>;
export type SpiedFunction<T extends FunctionLike = UnknownFunction> = MockInstance<
(...args: Parameters<T>) => ReturnType<T>
>;
export type SpiedGetter<T> = MockInstance<() => T>;
export type SpiedSetter<T> = MockInstance<(arg: T) => void>;
export interface SpyInstance<T extends FunctionLike = UnknownFunction> extends MockInstance<T> {}
export const spyOn: {
<
T extends object,
K_2 extends Exclude<
keyof T,
| keyof {
[K in keyof T as Required<T>[K] extends ClassLike ? K : never]: T[K];
}
| keyof {
[K_1 in keyof T as Required<T>[K_1] extends FunctionLike ? K_1 : never]: T[K_1];
}
>,
V extends Required<T>[K_2],
A extends "set" | "get",
>(
object: T,
methodKey: K_2,
accessType: A,
): A extends "get" ? SpiedGetter<V> : A extends "set" ? SpiedSetter<V> : never;
<
T_1 extends object,
K_5 extends
| keyof {
[K_3 in keyof T_1 as Required<T_1>[K_3] extends ClassLike ? K_3 : never]: T_1[K_3];
}
| keyof {
[K_4 in keyof T_1 as Required<T_1>[K_4] extends FunctionLike ? K_4 : never]: T_1[K_4];
},
V_1 extends Required<T_1>[K_5],
>(
object: T_1,
methodKey: K_5,
): V_1 extends ClassLike | FunctionLike ? Spied<V_1> : never;
};
export interface UnknownClass {
new (...args: unknown[]): unknown;
}
export type UnknownFunction = (...args: unknown[]) => unknown;
export interface UnknownClass {
new (...args: unknown[]): unknown;
}
export type UnknownFunction = (...args: unknown[]) => unknown;
}

View File

@@ -1,4 +1,4 @@
import { expectType } from "./utilities";
import { expectType } from "./utilities.test";
async function* listReleases() {
for (let page = 1; ; page++) {

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