mirror of
https://github.com/oven-sh/bun
synced 2026-02-07 09:28:51 +00:00
Compare commits
75 Commits
fix-node-h
...
ben/refcou
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b3dba5a02b | ||
|
|
a1ab2a4780 | ||
|
|
451c1905a8 | ||
|
|
accccbfdaf | ||
|
|
8e0c8a143e | ||
|
|
9ea577efc0 | ||
|
|
54416dad05 | ||
|
|
8f4575c0e4 | ||
|
|
c7edb24520 | ||
|
|
325acfc230 | ||
|
|
7f60375cca | ||
|
|
dac7f22997 | ||
|
|
f5836c2013 | ||
|
|
70ddfb55e6 | ||
|
|
934e41ae59 | ||
|
|
f4ae8c7254 | ||
|
|
2a9569cec4 | ||
|
|
31060a5e2a | ||
|
|
5c0fa6dc21 | ||
|
|
53f311fdd9 | ||
|
|
b40f5c9669 | ||
|
|
317e9d23ab | ||
|
|
11bb3573ea | ||
|
|
39cf0906d1 | ||
|
|
1d655a0232 | ||
|
|
a548c2ec54 | ||
|
|
7740271359 | ||
|
|
75144ab881 | ||
|
|
1dbeed20a9 | ||
|
|
3af6f7a5fe | ||
|
|
1bfccf707b | ||
|
|
21853d08de | ||
|
|
b6502189e8 | ||
|
|
f4ab2e4986 | ||
|
|
57cda4a445 | ||
|
|
49ca2c86e7 | ||
|
|
a08a9c5bfb | ||
|
|
ee8a839500 | ||
|
|
8ee962d79f | ||
|
|
4c3d652f00 | ||
|
|
c21fca08e2 | ||
|
|
77fde278e8 | ||
|
|
517af630e7 | ||
|
|
d8e5335268 | ||
|
|
db492575c8 | ||
|
|
9e580f8413 | ||
|
|
6ba2ba41c6 | ||
|
|
57381d43ed | ||
|
|
90c67c4b79 | ||
|
|
cf9f2bf98e | ||
|
|
8ebd5d53da | ||
|
|
60acfb17f0 | ||
|
|
8735a3f4d6 | ||
|
|
a07844ea13 | ||
|
|
1656bca9ab | ||
|
|
43af1a2283 | ||
|
|
84a21234d4 | ||
|
|
fefdaefb97 | ||
|
|
50eaea19cb | ||
|
|
438d8555c6 | ||
|
|
163a51c0f6 | ||
|
|
8df7064f73 | ||
|
|
99ee90a58f | ||
|
|
46c43d954c | ||
|
|
b37054697b | ||
|
|
5d50281f1a | ||
|
|
6bef525704 | ||
|
|
687a0ab5a4 | ||
|
|
60ae19bded | ||
|
|
be41c884b4 | ||
|
|
73d1b2ff67 | ||
|
|
2312b2c0f2 | ||
|
|
eae2c889ed | ||
|
|
ddd87fef12 | ||
|
|
f36d480919 |
488
.cursor/rules/zig-javascriptcore-classes.mdc
Normal file
488
.cursor/rules/zig-javascriptcore-classes.mdc
Normal file
@@ -0,0 +1,488 @@
|
||||
---
|
||||
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 {
|
||||
// Internal state
|
||||
encoding: []const u8,
|
||||
fatal: bool,
|
||||
ignoreBOM: bool,
|
||||
|
||||
// Use generated bindings
|
||||
pub usingnamespace JSC.Codegen.JSTextDecoder;
|
||||
pub usingnamespace bun.New(@This());
|
||||
|
||||
// Constructor implementation - note use of globalObject
|
||||
pub fn constructor(
|
||||
globalObject: *JSGlobalObject,
|
||||
callFrame: *JSC.CallFrame,
|
||||
) bun.JSError!*TextDecoder {
|
||||
// Implementation
|
||||
}
|
||||
|
||||
// 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
|
||||
pub fn deinit(this: *TextDecoder) void {
|
||||
// Release any retained resources
|
||||
}
|
||||
|
||||
pub fn finalize(this: *TextDecoder) void {
|
||||
this.deinit();
|
||||
// Or sometimes this is used to free memory instead
|
||||
bun.default_allocator.destroy(this);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Key components in the Zig file:
|
||||
- The struct containing native state
|
||||
- `usingnamespace JSC.Codegen.JS<ClassName>` to include generated code
|
||||
- `usingnamespace bun.New(@This())` for object creation helpers
|
||||
- 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 {
|
||||
// State
|
||||
value: []const u8,
|
||||
|
||||
// Generated bindings
|
||||
pub usingnamespace JSC.Codegen.JSMyClass;
|
||||
pub usingnamespace bun.New(@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.default_allocator.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.
|
||||
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
@@ -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 outside of the stack is either wrapped in a JSC.Strong or is JSValueProtect'ed
|
||||
- [ ] JSValue used 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`)
|
||||
-->
|
||||
|
||||
|
||||
@@ -53,39 +53,39 @@ $ brew install bun
|
||||
|
||||
## Install LLVM
|
||||
|
||||
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:
|
||||
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:
|
||||
|
||||
{% codetabs group="os" %}
|
||||
|
||||
```bash#macOS (Homebrew)
|
||||
$ brew install llvm@18
|
||||
$ brew install llvm@19
|
||||
```
|
||||
|
||||
```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 -- 18 all
|
||||
$ wget https://apt.llvm.org/llvm.sh -O - | sudo bash -s -- 19 all
|
||||
```
|
||||
|
||||
```bash#Arch
|
||||
$ sudo pacman -S llvm clang18 lld
|
||||
$ sudo pacman -S llvm clang lld
|
||||
```
|
||||
|
||||
```bash#Fedora
|
||||
$ sudo dnf install llvm18 clang18 lld18-devel
|
||||
$ sudo dnf install llvm clang lld-devel
|
||||
```
|
||||
|
||||
```bash#openSUSE Tumbleweed
|
||||
$ sudo zypper install clang18 lld18 llvm18
|
||||
$ sudo zypper install clang19 lld19 llvm19
|
||||
```
|
||||
|
||||
{% /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 18 is in your path:
|
||||
Make sure Clang/LLVM 19 is in your path:
|
||||
|
||||
```bash
|
||||
$ which clang-18
|
||||
$ which clang-19
|
||||
```
|
||||
|
||||
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@18)/bin" if you are using zsh
|
||||
$ export PATH="$(brew --prefix llvm@18)/bin:$PATH"
|
||||
# use path+="$(brew --prefix llvm@19)/bin" if you are using zsh
|
||||
$ export PATH="$(brew --prefix llvm@19)/bin:$PATH"
|
||||
```
|
||||
|
||||
```bash#Arch
|
||||
# use fish_add_path if you're using fish
|
||||
$ export PATH="$PATH:/usr/lib/llvm18/bin"
|
||||
$ export PATH="$PATH:/usr/lib/llvm19/bin"
|
||||
```
|
||||
|
||||
{% /codetabs %}
|
||||
@@ -134,6 +134,16 @@ 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.
|
||||
@@ -250,7 +260,7 @@ The issue may manifest when initially running `bun setup` as Clang being unable
|
||||
```
|
||||
The C++ compiler
|
||||
|
||||
"/usr/bin/clang++-18"
|
||||
"/usr/bin/clang++-19"
|
||||
|
||||
is not able to compile a simple test program.
|
||||
```
|
||||
|
||||
44
bench/crypto/aes-gcm-throughput.mjs
Normal file
44
bench/crypto/aes-gcm-throughput.mjs
Normal file
@@ -0,0 +1,44 @@
|
||||
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();
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
// Enable latest features
|
||||
"lib": ["ESNext", "DOM"],
|
||||
"lib": ["ESNext"],
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleDetection": "force",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
// Enable latest features
|
||||
"lib": ["ESNext", "DOM"],
|
||||
"lib": ["ESNext"],
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleDetection": "force",
|
||||
|
||||
3
bun.lock
3
bun.lock
@@ -27,9 +27,10 @@
|
||||
},
|
||||
"packages/bun-types": {
|
||||
"name": "bun-types",
|
||||
"version": "1.2.5",
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"@types/ws": "~8.5.10",
|
||||
"@types/ws": "*",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^1.5.3",
|
||||
|
||||
@@ -4,7 +4,7 @@ register_repository(
|
||||
REPOSITORY
|
||||
oven-sh/boringssl
|
||||
COMMIT
|
||||
914b005ef3ece44159dca0ffad74eb42a9f6679f
|
||||
7a5d984c69b0c34c4cbb56c6812eaa5b9bef485c
|
||||
)
|
||||
|
||||
register_cmake_command(
|
||||
|
||||
@@ -785,6 +785,10 @@ 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)
|
||||
|
||||
@@ -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 91bf2baced1b1309c7e05f19177c97fefec20976)
|
||||
set(WEBKIT_VERSION ef31d98a1370e01b7483cabcbe3593d055bea982)
|
||||
endif()
|
||||
|
||||
string(SUBSTRING ${WEBKIT_VERSION} 0 16 WEBKIT_VERSION_PREFIX)
|
||||
|
||||
449
docs/api/cookie.md
Normal file
449
docs/api/cookie.md
Normal file
@@ -0,0 +1,449 @@
|
||||
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]>;
|
||||
}
|
||||
```
|
||||
@@ -61,6 +61,7 @@ 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;
|
||||
}
|
||||
```
|
||||
|
||||
@@ -934,6 +935,83 @@ 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
|
||||
|
||||
@@ -240,7 +240,7 @@ const result = await sql.unsafe(
|
||||
|
||||
### Execute and Cancelling Queries
|
||||
|
||||
Bun's SQL is lazy that means its will only start executing when awaited or executed with `.execute()`.
|
||||
Bun's SQL is lazy, which means it 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
|
||||
|
||||
@@ -15,8 +15,8 @@ Below is the full set of recommended `compilerOptions` for a Bun project. With t
|
||||
```jsonc
|
||||
{
|
||||
"compilerOptions": {
|
||||
// Enable latest features
|
||||
"lib": ["ESNext", "DOM"],
|
||||
// Environment setup & latest features
|
||||
"lib": ["ESNext"],
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleDetection": "force",
|
||||
@@ -33,11 +33,12 @@ Below is the full set of recommended `compilerOptions` for a Bun project. With t
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
|
||||
// Some stricter flags
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
// Some stricter flags (disabled by default)
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noPropertyAccessFromIndexSignature": false,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
15
docs/nav.ts
15
docs/nav.ts
@@ -355,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/transpiler", "Transpiler", {
|
||||
description: `Bun exposes its internal transpiler as a pluggable API.`,
|
||||
}), // "`Bun.Transpiler`"),
|
||||
page("api/html-rewriter", "HTMLRewriter", {
|
||||
description: `Parse and transform HTML with Bun's native HTMLRewriter API, inspired by Cloudflare Workers.`,
|
||||
}), // "`HTMLRewriter`"),
|
||||
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`"),
|
||||
@@ -398,6 +398,9 @@ 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"),
|
||||
|
||||
@@ -104,7 +104,7 @@ This page is updated regularly to reflect compatibility status of the latest ver
|
||||
|
||||
### [`node:crypto`](https://nodejs.org/api/crypto.html)
|
||||
|
||||
🟡 Missing `ECDH` `checkPrime` `checkPrimeSync` `generatePrime` `generatePrimeSync` `hkdf` `hkdfSync` `secureHeapUsed` `setEngine` `setFips`
|
||||
🟡 Missing `secureHeapUsed` `setEngine` `setFips`
|
||||
|
||||
Some methods are not optimized yet.
|
||||
|
||||
@@ -118,7 +118,7 @@ Some methods are not optimized yet.
|
||||
|
||||
### [`node:module`](https://nodejs.org/api/module.html)
|
||||
|
||||
🟡 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.
|
||||
🟡 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.
|
||||
|
||||
### [`node:net`](https://nodejs.org/api/net.html)
|
||||
|
||||
@@ -378,8 +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). `require.extensions` is a stub.
|
||||
|
||||
🟢 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).
|
||||
### [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response)
|
||||
|
||||
🟢 Fully implemented.
|
||||
|
||||
@@ -17,7 +17,7 @@ Bun supports things like top-level await, JSX, and extensioned `.ts` imports, wh
|
||||
```jsonc
|
||||
{
|
||||
"compilerOptions": {
|
||||
// Enable latest features
|
||||
// Environment setup & latest features
|
||||
"lib": ["ESNext"],
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
@@ -35,12 +35,13 @@ Bun supports things like top-level await, JSX, and extensioned `.ts` imports, wh
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
|
||||
// Some stricter flags
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noPropertyAccessFromIndexSignature": true
|
||||
}
|
||||
// Some stricter flags (disabled by default)
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noPropertyAccessFromIndexSignature": false,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -29,7 +29,9 @@
|
||||
"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/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"
|
||||
],
|
||||
"overrides": [
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "bun",
|
||||
"version": "1.2.6",
|
||||
"version": "1.2.8",
|
||||
"workspaces": [
|
||||
"./packages/bun-types"
|
||||
],
|
||||
@@ -31,6 +31,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"build": "bun run build:debug",
|
||||
"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",
|
||||
@@ -44,6 +45,7 @@
|
||||
"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",
|
||||
|
||||
@@ -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,6 +26,8 @@
|
||||
|
||||
"@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=="],
|
||||
@@ -54,9 +56,11 @@
|
||||
|
||||
"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=="],
|
||||
|
||||
"typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="],
|
||||
"three": ["three@0.174.0", "", {}, "sha512-p+WG3W6Ov74alh3geCMkGK9NWuT62ee21cV3jEnun201zodVF4tCE5aZa2U122/mkLRmhJJUQmLLW1BH00uQJQ=="],
|
||||
|
||||
"undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bun-plugin-svelte",
|
||||
"version": "0.0.5",
|
||||
"version": "0.0.6",
|
||||
"description": "Official Svelte plugin for Bun",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -11,7 +11,11 @@ describe("SveltePlugin", () => {
|
||||
expect(() => SveltePlugin(undefined)).not.toThrow();
|
||||
});
|
||||
|
||||
it.each([null, 1, "hi", {}, "Client"])("throws if forceSide is not 'client' or 'server' (%p)", (forceSide: any) => {
|
||||
it.each([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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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, type SvelteOptions } from "./options";
|
||||
import { getBaseCompileOptions, validateOptions, type SvelteOptions } from "./options";
|
||||
|
||||
describe("getBaseCompileOptions", () => {
|
||||
describe("when no options are provided", () => {
|
||||
@@ -42,4 +42,13 @@ 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
|
||||
|
||||
@@ -2,7 +2,8 @@ import { strict as assert } from "node:assert";
|
||||
import { type BuildConfig } from "bun";
|
||||
import type { CompileOptions, ModuleCompileOptions } from "svelte/compiler";
|
||||
|
||||
export interface SvelteOptions {
|
||||
type OverrideCompileOptions = Pick<CompileOptions, "customElement" | "runes" | "modernAst" | "namespace">;
|
||||
export interface SvelteOptions extends Pick<CompileOptions, "runes"> {
|
||||
/**
|
||||
* Force client-side or server-side generation.
|
||||
*
|
||||
@@ -20,6 +21,11 @@ export interface SvelteOptions {
|
||||
* Defaults to `true` when run via Bun's dev server, `false` otherwise.
|
||||
*/
|
||||
development?: boolean;
|
||||
|
||||
/**
|
||||
* Options to forward to the Svelte compiler.
|
||||
*/
|
||||
compilerOptions?: OverrideCompileOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -27,15 +33,24 @@ export interface SvelteOptions {
|
||||
*/
|
||||
export function validateOptions(options: unknown): asserts options is SvelteOptions {
|
||||
assert(options && typeof options === "object", new TypeError("bun-svelte-plugin: options must be an object"));
|
||||
if ("forceSide" in options) {
|
||||
switch (options.forceSide) {
|
||||
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) {
|
||||
case "client":
|
||||
case "server":
|
||||
break;
|
||||
default:
|
||||
throw new TypeError(
|
||||
`bun-svelte-plugin: forceSide must be either 'client' or 'server', got ${options.forceSide}`,
|
||||
);
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,7 +59,10 @@ export function validateOptions(options: unknown): asserts options is SvelteOpti
|
||||
* @internal
|
||||
*/
|
||||
export function getBaseCompileOptions(pluginOptions: SvelteOptions, config: Partial<BuildConfig>): CompileOptions {
|
||||
let { development = false } = pluginOptions;
|
||||
let {
|
||||
development = false,
|
||||
compilerOptions: { customElement, runes, modernAst, namespace } = kEmptyObject as OverrideCompileOptions,
|
||||
} = pluginOptions;
|
||||
const { minify = false } = config;
|
||||
|
||||
const shouldMinify = Boolean(minify);
|
||||
@@ -68,6 +86,10 @@ 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
|
||||
@@ -109,3 +131,4 @@ function generateSide(pluginOptions: SvelteOptions, config: Partial<BuildConfig>
|
||||
}
|
||||
|
||||
export const hash = (content: string): string => Bun.hash(content, 5381).toString(36);
|
||||
const kEmptyObject = Object.create(null);
|
||||
|
||||
@@ -24,13 +24,32 @@ afterAll(() => {
|
||||
}
|
||||
});
|
||||
|
||||
it("hello world component", async () => {
|
||||
const res = await Bun.build({
|
||||
entrypoints: [fixturePath("foo.svelte")],
|
||||
outdir,
|
||||
plugins: [SveltePlugin()],
|
||||
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();
|
||||
});
|
||||
});
|
||||
expect(res.success).toBeTrue();
|
||||
});
|
||||
|
||||
describe("when importing `.svelte.ts` files with ESM", () => {
|
||||
|
||||
@@ -20,7 +20,7 @@ That's it! VS Code and TypeScript automatically load `@types/*` packages into yo
|
||||
|
||||
# Contributing
|
||||
|
||||
The `@types/bun` package is a shim that loads `bun-types`. The `bun-types` package lives in the Bun repo under `packages/bun-types`. It is generated via [./scripts/bundle.ts](./scripts/bundle.ts).
|
||||
The `@types/bun` package is a shim that loads `bun-types`. The `bun-types` package lives in the Bun repo under `packages/bun-types`.
|
||||
|
||||
To add a new file, add it under `packages/bun-types`. Then add a [triple-slash directive](https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html) pointing to it inside [./index.d.ts](./index.d.ts).
|
||||
|
||||
@@ -28,8 +28,6 @@ To add a new file, add it under `packages/bun-types`. Then add a [triple-slash d
|
||||
+ /// <reference path="./newfile.d.ts" />
|
||||
```
|
||||
|
||||
[`./bundle.ts`](./bundle.ts) merges the types in this folder into a single file. To run it:
|
||||
|
||||
```bash
|
||||
bun build
|
||||
```
|
||||
|
||||
114
packages/bun-types/authoring.md
Normal file
114
packages/bun-types/authoring.md
Normal file
@@ -0,0 +1,114 @@
|
||||
# Authoring @types/bun
|
||||
|
||||
These declarations define the `'bun'` module, the `Bun` global variable, and lots of other global declarations like extending the `fetch` interface.
|
||||
|
||||
## The `'bun'` Module
|
||||
|
||||
The `Bun` global variable and the `'bun'` module types are defined with one syntax. It supports declaring both types/interfaces and runtime values:
|
||||
|
||||
```typescript
|
||||
declare module "bun" {
|
||||
// Your types go here
|
||||
interface MyInterface {
|
||||
// ...
|
||||
}
|
||||
|
||||
type MyType = string | number;
|
||||
|
||||
function myFunction(): void;
|
||||
}
|
||||
```
|
||||
|
||||
You can write as many `declare module "bun"` declarations as you need. Symbols will be accessible from other files inside of the declaration, and they will all be merged when the types are evaluated.
|
||||
|
||||
You can consume these declarations in two ways:
|
||||
|
||||
1. Importing it from `'bun'`:
|
||||
|
||||
```typescript
|
||||
import { type MyInterface, type MyType, myFunction } from "bun";
|
||||
|
||||
const myInterface: MyInterface = {};
|
||||
const myType: MyType = "cool";
|
||||
myFunction();
|
||||
```
|
||||
|
||||
2. Using the global `Bun` object:
|
||||
|
||||
```typescript
|
||||
const myInterface: Bun.MyInterface = {};
|
||||
const myType: Bun.MyType = "cool";
|
||||
Bun.myFunction();
|
||||
```
|
||||
|
||||
Consuming them inside the ambient declarations is also easy:
|
||||
|
||||
```ts
|
||||
// These are equivalent
|
||||
type A = import("bun").MyType;
|
||||
type A = Bun.MyType;
|
||||
```
|
||||
|
||||
## File Structure
|
||||
|
||||
Types are organized across multiple `.d.ts` files in the `packages/bun-types` directory:
|
||||
|
||||
- `index.d.ts` - The main entry point that references all other type files
|
||||
- `bun.d.ts` - Core Bun APIs and types
|
||||
- `globals.d.ts` - Global type declarations
|
||||
- `test.d.ts` - Testing-related types
|
||||
- `sqlite.d.ts` - SQLite-related types
|
||||
- ...etc. You can make more files
|
||||
|
||||
Note: The order of references in `index.d.ts` is important - `bun.ns.d.ts` must be referenced last to ensure the `Bun` global gets defined properly.
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. **Type Safety**
|
||||
|
||||
- Please use strict types instead of `any` where possible
|
||||
- Leverage TypeScript's type system features (generics, unions, etc.)
|
||||
- Document complex types with JSDoc comments
|
||||
|
||||
2. **Compatibility**
|
||||
|
||||
- Use `Bun.__internal.UseLibDomIfAvailable<LibDomName extends string, OurType>` for types that might conflict with lib.dom.d.ts (see [`./fetch.d.ts`](./fetch.d.ts) for a real example)
|
||||
- `@types/node` often expects variables to always be defined (this was the biggest cause of most of the conflicts in the past!), so we use the `UseLibDomIfAvailable` type to make sure we don't overwrite `lib.dom.d.ts` but still provide Bun types while simultaneously declaring the variable exists (for Node to work) in the cases that we can.
|
||||
|
||||
3. **Documentation**
|
||||
- Add JSDoc comments for public APIs
|
||||
- Include examples in documentation when helpful
|
||||
- Document default values and important behaviors
|
||||
|
||||
### Internal Types
|
||||
|
||||
For internal types that shouldn't be exposed to users, use the `__internal` namespace:
|
||||
|
||||
```typescript
|
||||
declare module "bun" {
|
||||
namespace __internal {
|
||||
interface MyInternalType {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The internal namespace is mostly used for declaring things that shouldn't be globally accessible on the `bun` namespace, but are still used in public apis. You can see a pretty good example of that in the [`./fetch.d.ts`](./fetch.d.ts) file.
|
||||
|
||||
## Testing Types
|
||||
|
||||
We test our type definitions using a special test file at `fixture/index.ts`. This file contains TypeScript code that exercises our type definitions, but is never actually executed - it's only used to verify that the types work correctly.
|
||||
|
||||
The test file is type-checked in two different environments:
|
||||
|
||||
1. With `lib.dom.d.ts` included - This simulates usage in a browser environment where DOM types are available
|
||||
2. Without `lib.dom.d.ts` - This simulates usage in a server-like environment without DOM types
|
||||
|
||||
Your type definitions must work properly in both environments. This ensures that Bun's types are compatible regardless of whether DOM types are present or not.
|
||||
|
||||
For example, if you're adding types for a new API, you should just add code to `fixture/index.ts` that uses your new API. Doesn't need to work at runtime (e.g. you can fake api keys for example), it's just checking that the types are correct.
|
||||
|
||||
## Questions
|
||||
|
||||
Feel free to open an issue or speak to any of the more TypeScript-focused team members if you need help authoring types or fixing type tests.
|
||||
2749
packages/bun-types/bun.d.ts
vendored
2749
packages/bun-types/bun.d.ts
vendored
File diff suppressed because it is too large
Load Diff
7
packages/bun-types/bun.ns.d.ts
vendored
Normal file
7
packages/bun-types/bun.ns.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
import * as BunModule from "bun";
|
||||
|
||||
declare global {
|
||||
export import Bun = BunModule;
|
||||
}
|
||||
|
||||
export {};
|
||||
30
packages/bun-types/deprecated.d.ts
vendored
30
packages/bun-types/deprecated.d.ts
vendored
@@ -1,4 +1,19 @@
|
||||
declare module "bun" {
|
||||
interface BunMessageEvent<T> {
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
initMessageEvent(
|
||||
type: string,
|
||||
bubbles?: boolean,
|
||||
cancelable?: boolean,
|
||||
data?: any,
|
||||
origin?: string,
|
||||
lastEventId?: string,
|
||||
source?: null,
|
||||
): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Renamed to `ErrorLike`
|
||||
*/
|
||||
@@ -38,21 +53,6 @@ declare namespace NodeJS {
|
||||
}
|
||||
}
|
||||
|
||||
declare namespace Bun {
|
||||
interface MessageEvent {
|
||||
/** @deprecated */
|
||||
initMessageEvent(
|
||||
type: string,
|
||||
bubbles?: boolean,
|
||||
cancelable?: boolean,
|
||||
data?: any,
|
||||
origin?: string,
|
||||
lastEventId?: string,
|
||||
source?: null,
|
||||
): void;
|
||||
}
|
||||
}
|
||||
|
||||
interface CustomEvent<T = any> {
|
||||
/** @deprecated */
|
||||
initCustomEvent(type: string, bubbles?: boolean, cancelable?: boolean, detail?: T): void;
|
||||
|
||||
343
packages/bun-types/devserver.d.ts
vendored
343
packages/bun-types/devserver.d.ts
vendored
@@ -1,192 +1,189 @@
|
||||
export {};
|
||||
declare module "bun" {
|
||||
type HMREventNames =
|
||||
| "beforeUpdate"
|
||||
| "afterUpdate"
|
||||
| "beforeFullReload"
|
||||
| "beforePrune"
|
||||
| "invalidate"
|
||||
| "error"
|
||||
| "ws:disconnect"
|
||||
| "ws:connect";
|
||||
|
||||
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: {
|
||||
/**
|
||||
* 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
|
||||
* `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
|
||||
* if (import.meta.hot) {
|
||||
* // HMR APIs are available
|
||||
* }
|
||||
* const root = import.meta.hot.data.root ??= createRoot(elem);
|
||||
* root.render(<App />); // re-use an existing root
|
||||
* ```
|
||||
*
|
||||
* However, this check is usually not needed as Bun will dead-code-eliminate
|
||||
* calls to all of the HMR APIs in production builds.
|
||||
* In production, `data` is inlined to be `{}`. This is handy because Bun
|
||||
* knows it can minify `{}.prop ??= value` into `value` in production.
|
||||
*
|
||||
*
|
||||
* 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;
|
||||
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 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 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. 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;
|
||||
/**
|
||||
* 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;
|
||||
/**
|
||||
* 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;
|
||||
/**
|
||||
* 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;
|
||||
// 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;
|
||||
/**
|
||||
* 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;
|
||||
};
|
||||
}
|
||||
/**
|
||||
* 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;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -19,13 +19,13 @@ declare module "*/bun.lock" {
|
||||
}
|
||||
|
||||
declare module "*.html" {
|
||||
// In Bun v1.2, we might change this to Bun.HTMLBundle
|
||||
// In Bun v1.2, this might change to Bun.HTMLBundle
|
||||
var contents: any;
|
||||
export = contents;
|
||||
}
|
||||
|
||||
declare module "*.svg" {
|
||||
// Bun 1.2.3 added support for frontend dev server
|
||||
var contents: `${string}.svg`;
|
||||
export = contents;
|
||||
var content: `${string}.svg`;
|
||||
export = content;
|
||||
}
|
||||
223
packages/bun-types/fetch.d.ts
vendored
223
packages/bun-types/fetch.d.ts
vendored
@@ -1,161 +1,72 @@
|
||||
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;
|
||||
};
|
||||
This file does not declare any global types.
|
||||
|
||||
interface Request {
|
||||
headers: Headers;
|
||||
}
|
||||
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.
|
||||
|
||||
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;
|
||||
|
||||
declare module "bun" {
|
||||
type HeadersInit = string[][] | Record<string, string | ReadonlyArray<string>> | Headers;
|
||||
type BodyInit = ReadableStream | Bun.XMLHttpRequestBodyInit | URLSearchParams | AsyncGenerator<Uint8Array>;
|
||||
|
||||
namespace __internal {
|
||||
type LibOrFallbackHeaders = LibDomIsLoaded extends true ? {} : import("undici-types").Headers;
|
||||
type LibOrFallbackRequest = LibDomIsLoaded extends true ? {} : import("undici-types").Request;
|
||||
type LibOrFallbackResponse = LibDomIsLoaded extends true ? {} : import("undici-types").Response;
|
||||
type LibOrFallbackResponseInit = LibDomIsLoaded extends true ? {} : import("undici-types").ResponseInit;
|
||||
type LibOrFallbackRequestInit = LibDomIsLoaded extends true
|
||||
? {}
|
||||
: Omit<import("undici-types").RequestInit, "body" | "headers"> & {
|
||||
body?: Bun.BodyInit | null | undefined;
|
||||
headers?: Bun.HeadersInit;
|
||||
};
|
||||
|
||||
interface BunHeadersOverride extends LibOrFallbackHeaders {
|
||||
/**
|
||||
* Convert {@link Headers} to a plain JavaScript object.
|
||||
*
|
||||
* 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[];
|
||||
}
|
||||
|
||||
interface BunRequestOverride extends LibOrFallbackRequest {
|
||||
headers: BunHeadersOverride;
|
||||
}
|
||||
|
||||
interface BunResponseOverride extends LibOrFallbackResponse {
|
||||
headers: BunHeadersOverride;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
27
packages/bun-types/ffi.d.ts
vendored
27
packages/bun-types/ffi.d.ts
vendored
@@ -13,6 +13,8 @@
|
||||
* 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 {
|
||||
@@ -543,14 +545,6 @@ declare module "bun:ffi" {
|
||||
|
||||
type Symbols = Readonly<Record<string, FFIFunction>>;
|
||||
|
||||
// /**
|
||||
// * Compile a callback function
|
||||
// *
|
||||
// * Returns a function pointer
|
||||
// *
|
||||
// */
|
||||
// export function callback(ffi: FFIFunction, cb: Function): number;
|
||||
|
||||
interface Library<Fns extends Symbols> {
|
||||
symbols: ConvertFns<Fns>;
|
||||
|
||||
@@ -608,6 +602,8 @@ 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,
|
||||
@@ -626,9 +622,9 @@ declare module "bun:ffi" {
|
||||
* JavaScript:
|
||||
* ```js
|
||||
* import { cc } from "bun:ffi";
|
||||
* import hello from "./hello.c" with {type: "file"};
|
||||
* import source from "./hello.c" with {type: "file"};
|
||||
* const {symbols: {hello}} = cc({
|
||||
* source: hello,
|
||||
* source,
|
||||
* symbols: {
|
||||
* hello: {
|
||||
* returns: "cstring",
|
||||
@@ -681,8 +677,9 @@ declare module "bun:ffi" {
|
||||
* @example
|
||||
* ```js
|
||||
* import { cc } from "bun:ffi";
|
||||
* import source from "./hello.c" with {type: "file"};
|
||||
* const {symbols: {hello}} = cc({
|
||||
* source: hello,
|
||||
* source,
|
||||
* define: {
|
||||
* "NDEBUG": "1",
|
||||
* },
|
||||
@@ -707,8 +704,9 @@ declare module "bun:ffi" {
|
||||
* @example
|
||||
* ```js
|
||||
* import { cc } from "bun:ffi";
|
||||
* import source from "./hello.c" with {type: "file"};
|
||||
* const {symbols: {hello}} = cc({
|
||||
* source: hello,
|
||||
* source,
|
||||
* flags: ["-framework CoreFoundation", "-framework Security"],
|
||||
* symbols: {
|
||||
* hello: {
|
||||
@@ -1024,6 +1022,8 @@ declare module "bun:ffi" {
|
||||
* // Do something with rawPtr
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @category FFI
|
||||
*/
|
||||
function ptr(view: NodeJS.TypedArray | ArrayBufferLike | DataView, byteOffset?: number): Pointer;
|
||||
|
||||
@@ -1048,8 +1048,9 @@ 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
|
||||
|
||||
3452
packages/bun-types/globals.d.ts
vendored
3452
packages/bun-types/globals.d.ts
vendored
File diff suppressed because it is too large
Load Diff
2
packages/bun-types/html-rewriter.d.ts
vendored
2
packages/bun-types/html-rewriter.d.ts
vendored
@@ -147,6 +147,8 @@ declare namespace HTMLRewriterTypes {
|
||||
* });
|
||||
* rewriter.transform(await fetch("https://remix.run"));
|
||||
* ```
|
||||
*
|
||||
* @category HTML Manipulation
|
||||
*/
|
||||
declare class HTMLRewriter {
|
||||
constructor();
|
||||
|
||||
24
packages/bun-types/index.d.ts
vendored
24
packages/bun-types/index.d.ts
vendored
@@ -1,24 +1,26 @@
|
||||
// Project: https://github.com/oven-sh/bun
|
||||
// Definitions by: Jarred Sumner <https://github.com/Jarred-Sumner>
|
||||
// Definitions by: Bun Contributors <https://github.com/oven-sh/bun/graphs/contributors>
|
||||
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
|
||||
|
||||
/// <reference lib="esnext" />
|
||||
/// <reference types="ws" />
|
||||
/// <reference types="node" />
|
||||
|
||||
// contributors: uncomment this to detect conflicts with lib.dom.d.ts
|
||||
// /// <reference lib="dom" />
|
||||
|
||||
/// <reference path="./bun.d.ts" />
|
||||
/// <reference path="./globals.d.ts" />
|
||||
/// <reference path="./s3.d.ts" />
|
||||
/// <reference path="./fetch.d.ts" />
|
||||
/// <reference path="./overrides.d.ts" />
|
||||
/// <reference path="./bun.d.ts" />
|
||||
/// <reference path="./extensions.d.ts" />
|
||||
/// <reference path="./devserver.d.ts" />
|
||||
/// <reference path="./ffi.d.ts" />
|
||||
/// <reference path="./test.d.ts" />
|
||||
/// <reference path="./html-rewriter.d.ts" />
|
||||
/// <reference path="./jsc.d.ts" />
|
||||
/// <reference path="./sqlite.d.ts" />
|
||||
/// <reference path="./test.d.ts" />
|
||||
/// <reference path="./wasm.d.ts" />
|
||||
/// <reference path="./overrides.d.ts" />
|
||||
/// <reference path="./deprecated.d.ts" />
|
||||
/// <reference path="./ambient.d.ts" />
|
||||
/// <reference path="./devserver.d.ts" />
|
||||
|
||||
/// <reference path="./bun.ns.d.ts" />
|
||||
|
||||
// @ts-ignore Must disable this so it doesn't conflict with the DOM onmessage type, but still
|
||||
// allows us to declare our own globals that Node's types can "see" and not conflict with
|
||||
declare var onmessage: never;
|
||||
|
||||
71
packages/bun-types/overrides.d.ts
vendored
71
packages/bun-types/overrides.d.ts
vendored
@@ -1,18 +1,71 @@
|
||||
export {};
|
||||
|
||||
import type { BunFile, Env, PathLike } from "bun";
|
||||
|
||||
declare global {
|
||||
namespace NodeJS {
|
||||
interface Process {
|
||||
readonly version: string;
|
||||
browser: boolean;
|
||||
|
||||
/**
|
||||
* Whether you are using Bun
|
||||
*/
|
||||
isBun: true;
|
||||
|
||||
/**
|
||||
* The current git sha of Bun
|
||||
*/
|
||||
revision: string;
|
||||
|
||||
reallyExit(code?: number): never;
|
||||
dlopen(module: { exports: any }, filename: string, flags?: number): void;
|
||||
_exiting: boolean;
|
||||
noDeprecation: boolean;
|
||||
|
||||
binding(m: "constants"): {
|
||||
os: typeof import("node:os").constants;
|
||||
fs: typeof import("node:fs").constants;
|
||||
crypto: typeof import("node:crypto").constants;
|
||||
zlib: typeof import("node:zlib").constants;
|
||||
trace: {
|
||||
TRACE_EVENT_PHASE_BEGIN: number;
|
||||
TRACE_EVENT_PHASE_END: number;
|
||||
TRACE_EVENT_PHASE_COMPLETE: number;
|
||||
TRACE_EVENT_PHASE_INSTANT: number;
|
||||
TRACE_EVENT_PHASE_ASYNC_BEGIN: number;
|
||||
TRACE_EVENT_PHASE_ASYNC_STEP_INTO: number;
|
||||
TRACE_EVENT_PHASE_ASYNC_STEP_PAST: number;
|
||||
TRACE_EVENT_PHASE_ASYNC_END: number;
|
||||
TRACE_EVENT_PHASE_NESTABLE_ASYNC_BEGIN: number;
|
||||
TRACE_EVENT_PHASE_NESTABLE_ASYNC_END: number;
|
||||
TRACE_EVENT_PHASE_NESTABLE_ASYNC_INSTANT: number;
|
||||
TRACE_EVENT_PHASE_FLOW_BEGIN: number;
|
||||
TRACE_EVENT_PHASE_FLOW_STEP: number;
|
||||
TRACE_EVENT_PHASE_FLOW_END: number;
|
||||
TRACE_EVENT_PHASE_METADATA: number;
|
||||
TRACE_EVENT_PHASE_COUNTER: number;
|
||||
TRACE_EVENT_PHASE_SAMPLE: number;
|
||||
TRACE_EVENT_PHASE_CREATE_OBJECT: number;
|
||||
TRACE_EVENT_PHASE_SNAPSHOT_OBJECT: number;
|
||||
TRACE_EVENT_PHASE_DELETE_OBJECT: number;
|
||||
TRACE_EVENT_PHASE_MEMORY_DUMP: number;
|
||||
TRACE_EVENT_PHASE_MARK: number;
|
||||
TRACE_EVENT_PHASE_CLOCK_SYNC: number;
|
||||
TRACE_EVENT_PHASE_ENTER_CONTEXT: number;
|
||||
TRACE_EVENT_PHASE_LEAVE_CONTEXT: number;
|
||||
TRACE_EVENT_PHASE_LINK_IDS: number;
|
||||
};
|
||||
};
|
||||
binding(m: string): object;
|
||||
}
|
||||
|
||||
interface ProcessVersions extends Dict<string> {
|
||||
bun: string;
|
||||
}
|
||||
interface ProcessEnv extends Env {}
|
||||
}
|
||||
}
|
||||
|
||||
declare module "fs/promises" {
|
||||
function exists(path: PathLike): Promise<boolean>;
|
||||
function exists(path: Bun.PathLike): Promise<boolean>;
|
||||
}
|
||||
|
||||
declare module "tls" {
|
||||
@@ -22,7 +75,7 @@ declare module "tls" {
|
||||
* the well-known CAs curated by Mozilla. Mozilla's CAs are completely
|
||||
* replaced when CAs are explicitly specified using this option.
|
||||
*/
|
||||
ca?: string | Buffer | NodeJS.TypedArray | BunFile | Array<string | Buffer | BunFile> | undefined;
|
||||
ca?: string | Buffer | NodeJS.TypedArray | Bun.BunFile | Array<string | Buffer | Bun.BunFile> | undefined;
|
||||
/**
|
||||
* Cert chains in PEM format. One cert chain should be provided per
|
||||
* private key. Each cert chain should consist of the PEM formatted
|
||||
@@ -38,8 +91,8 @@ declare module "tls" {
|
||||
| string
|
||||
| Buffer
|
||||
| NodeJS.TypedArray
|
||||
| BunFile
|
||||
| Array<string | Buffer | NodeJS.TypedArray | BunFile>
|
||||
| Bun.BunFile
|
||||
| Array<string | Buffer | NodeJS.TypedArray | Bun.BunFile>
|
||||
| undefined;
|
||||
/**
|
||||
* Private keys in PEM format. PEM allows the option of private keys
|
||||
@@ -54,9 +107,9 @@ declare module "tls" {
|
||||
key?:
|
||||
| string
|
||||
| Buffer
|
||||
| BunFile
|
||||
| Bun.BunFile
|
||||
| NodeJS.TypedArray
|
||||
| Array<string | Buffer | BunFile | NodeJS.TypedArray | KeyObject>
|
||||
| Array<string | Buffer | Bun.BunFile | NodeJS.TypedArray | KeyObject>
|
||||
| undefined;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,14 +9,14 @@
|
||||
"directory": "packages/bun-types"
|
||||
},
|
||||
"files": [
|
||||
"*.d.ts",
|
||||
"./*.d.ts",
|
||||
"docs/**/*.md",
|
||||
"docs/*.md"
|
||||
],
|
||||
"homepage": "https://bun.sh",
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"@types/ws": "~8.5.10"
|
||||
"@types/ws": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^1.5.3",
|
||||
@@ -27,7 +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 && bun run fmt",
|
||||
"build": "bun run copy-docs && bun scripts/build.ts",
|
||||
"test": "tsc",
|
||||
"fmt": "echo $(which biome) && biome format --write ."
|
||||
},
|
||||
|
||||
831
packages/bun-types/s3.d.ts
vendored
Normal file
831
packages/bun-types/s3.d.ts
vendored
Normal file
@@ -0,0 +1,831 @@
|
||||
declare module "bun" {
|
||||
/**
|
||||
* Fast incremental writer for files and pipes.
|
||||
*
|
||||
* This uses the same interface as {@link ArrayBufferSink}, but writes to a file or pipe.
|
||||
*/
|
||||
interface FileSink {
|
||||
/**
|
||||
* Write a chunk of data to the file.
|
||||
*
|
||||
* If the file descriptor is not writable yet, the data is buffered.
|
||||
*/
|
||||
write(chunk: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer): number;
|
||||
/**
|
||||
* Flush the internal buffer, committing the data to disk or the pipe.
|
||||
*/
|
||||
flush(): number | Promise<number>;
|
||||
/**
|
||||
* Close the file descriptor. This also flushes the internal buffer.
|
||||
*/
|
||||
end(error?: Error): number | Promise<number>;
|
||||
|
||||
start(options?: {
|
||||
/**
|
||||
* Preallocate an internal buffer of this size
|
||||
* This can significantly improve performance when the chunk size is small
|
||||
*/
|
||||
highWaterMark?: number;
|
||||
}): void;
|
||||
|
||||
/**
|
||||
* For FIFOs & pipes, this lets you decide whether Bun's process should
|
||||
* remain alive until the pipe is closed.
|
||||
*
|
||||
* By default, it is automatically managed. While the stream is open, the
|
||||
* process remains alive and once the other end hangs up or the stream
|
||||
* closes, the process exits.
|
||||
*
|
||||
* If you previously called {@link unref}, you can call this again to re-enable automatic management.
|
||||
*
|
||||
* Internally, it will reference count the number of times this is called. By default, that number is 1
|
||||
*
|
||||
* If the file is not a FIFO or pipe, {@link ref} and {@link unref} do
|
||||
* nothing. If the pipe is already closed, this does nothing.
|
||||
*/
|
||||
ref(): void;
|
||||
|
||||
/**
|
||||
* For FIFOs & pipes, this lets you decide whether Bun's process should
|
||||
* remain alive until the pipe is closed.
|
||||
*
|
||||
* If you want to allow Bun's process to terminate while the stream is open,
|
||||
* call this.
|
||||
*
|
||||
* If the file is not a FIFO or pipe, {@link ref} and {@link unref} do
|
||||
* nothing. If the pipe is already closed, this does nothing.
|
||||
*/
|
||||
unref(): void;
|
||||
}
|
||||
|
||||
interface NetworkSink extends FileSink {
|
||||
/**
|
||||
* Write a chunk of data to the network.
|
||||
*
|
||||
* If the network is not writable yet, the data is buffered.
|
||||
*/
|
||||
write(chunk: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer): number;
|
||||
/**
|
||||
* Flush the internal buffer, committing the data to the network.
|
||||
*/
|
||||
flush(): number | Promise<number>;
|
||||
/**
|
||||
* Finish the upload. This also flushes the internal buffer.
|
||||
*/
|
||||
end(error?: Error): number | Promise<number>;
|
||||
|
||||
/**
|
||||
* Get the stat of the file.
|
||||
*/
|
||||
stat(): Promise<import("node:fs").Stats>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration options for S3 operations
|
||||
*/
|
||||
interface S3Options extends BlobPropertyBag {
|
||||
/**
|
||||
* The Access Control List (ACL) policy for the file.
|
||||
* Controls who can access the file and what permissions they have.
|
||||
*
|
||||
* @example
|
||||
* // Setting public read access
|
||||
* const file = s3("public-file.txt", {
|
||||
* acl: "public-read",
|
||||
* bucket: "my-bucket"
|
||||
* });
|
||||
*
|
||||
* @example
|
||||
* // Using with presigned URLs
|
||||
* const url = file.presign({
|
||||
* acl: "public-read",
|
||||
* expiresIn: 3600
|
||||
* });
|
||||
*/
|
||||
acl?:
|
||||
| "private"
|
||||
| "public-read"
|
||||
| "public-read-write"
|
||||
| "aws-exec-read"
|
||||
| "authenticated-read"
|
||||
| "bucket-owner-read"
|
||||
| "bucket-owner-full-control"
|
||||
| "log-delivery-write";
|
||||
|
||||
/**
|
||||
* The S3 bucket name. Can be set via `S3_BUCKET` or `AWS_BUCKET` environment variables.
|
||||
*
|
||||
* @example
|
||||
* // Using explicit bucket
|
||||
* const file = s3("my-file.txt", { bucket: "my-bucket" });
|
||||
*
|
||||
* @example
|
||||
* // Using environment variables
|
||||
* // With S3_BUCKET=my-bucket in .env
|
||||
* const file = s3("my-file.txt");
|
||||
*/
|
||||
bucket?: string;
|
||||
|
||||
/**
|
||||
* The AWS region. Can be set via `S3_REGION` or `AWS_REGION` environment variables.
|
||||
*
|
||||
* @example
|
||||
* const file = s3("my-file.txt", {
|
||||
* bucket: "my-bucket",
|
||||
* region: "us-west-2"
|
||||
* });
|
||||
*/
|
||||
region?: string;
|
||||
|
||||
/**
|
||||
* The access key ID for authentication.
|
||||
* Can be set via `S3_ACCESS_KEY_ID` or `AWS_ACCESS_KEY_ID` environment variables.
|
||||
*/
|
||||
accessKeyId?: string;
|
||||
|
||||
/**
|
||||
* The secret access key for authentication.
|
||||
* Can be set via `S3_SECRET_ACCESS_KEY` or `AWS_SECRET_ACCESS_KEY` environment variables.
|
||||
*/
|
||||
secretAccessKey?: string;
|
||||
|
||||
/**
|
||||
* Optional session token for temporary credentials.
|
||||
* Can be set via `S3_SESSION_TOKEN` or `AWS_SESSION_TOKEN` environment variables.
|
||||
*
|
||||
* @example
|
||||
* // Using temporary credentials
|
||||
* const file = s3("my-file.txt", {
|
||||
* accessKeyId: tempAccessKey,
|
||||
* secretAccessKey: tempSecretKey,
|
||||
* sessionToken: tempSessionToken
|
||||
* });
|
||||
*/
|
||||
sessionToken?: string;
|
||||
|
||||
/**
|
||||
* The S3-compatible service endpoint URL.
|
||||
* Can be set via `S3_ENDPOINT` or `AWS_ENDPOINT` environment variables.
|
||||
*
|
||||
* @example
|
||||
* // AWS S3
|
||||
* const file = s3("my-file.txt", {
|
||||
* endpoint: "https://s3.us-east-1.amazonaws.com"
|
||||
* });
|
||||
*
|
||||
* @example
|
||||
* // Cloudflare R2
|
||||
* const file = s3("my-file.txt", {
|
||||
* endpoint: "https://<account-id>.r2.cloudflarestorage.com"
|
||||
* });
|
||||
*
|
||||
* @example
|
||||
* // DigitalOcean Spaces
|
||||
* const file = s3("my-file.txt", {
|
||||
* endpoint: "https://<region>.digitaloceanspaces.com"
|
||||
* });
|
||||
*
|
||||
* @example
|
||||
* // MinIO (local development)
|
||||
* const file = s3("my-file.txt", {
|
||||
* endpoint: "http://localhost:9000"
|
||||
* });
|
||||
*/
|
||||
endpoint?: string;
|
||||
|
||||
/**
|
||||
* Use virtual hosted style endpoint. default to false, when true if `endpoint` is informed it will ignore the `bucket`
|
||||
*
|
||||
* @example
|
||||
* // Using virtual hosted style
|
||||
* const file = s3("my-file.txt", {
|
||||
* virtualHostedStyle: true,
|
||||
* endpoint: "https://my-bucket.s3.us-east-1.amazonaws.com"
|
||||
* });
|
||||
*/
|
||||
virtualHostedStyle?: boolean;
|
||||
|
||||
/**
|
||||
* The size of each part in multipart uploads (in bytes).
|
||||
* - Minimum: 5 MiB
|
||||
* - Maximum: 5120 MiB
|
||||
* - Default: 5 MiB
|
||||
*
|
||||
* @example
|
||||
* // Configuring multipart uploads
|
||||
* const file = s3("large-file.dat", {
|
||||
* partSize: 10 * 1024 * 1024, // 10 MiB parts
|
||||
* queueSize: 4 // Upload 4 parts in parallel
|
||||
* });
|
||||
*
|
||||
* const writer = file.writer();
|
||||
* // ... write large file in chunks
|
||||
*/
|
||||
partSize?: number;
|
||||
|
||||
/**
|
||||
* Number of parts to upload in parallel for multipart uploads.
|
||||
* - Default: 5
|
||||
* - Maximum: 255
|
||||
*
|
||||
* Increasing this value can improve upload speeds for large files
|
||||
* but will use more memory.
|
||||
*/
|
||||
queueSize?: number;
|
||||
|
||||
/**
|
||||
* Number of retry attempts for failed uploads.
|
||||
* - Default: 3
|
||||
* - Maximum: 255
|
||||
*
|
||||
* @example
|
||||
* // Setting retry attempts
|
||||
* const file = s3("my-file.txt", {
|
||||
* retry: 5 // Retry failed uploads up to 5 times
|
||||
* });
|
||||
*/
|
||||
retry?: number;
|
||||
|
||||
/**
|
||||
* The Content-Type of the file.
|
||||
* Automatically set based on file extension when possible.
|
||||
*
|
||||
* @example
|
||||
* // Setting explicit content type
|
||||
* const file = s3("data.bin", {
|
||||
* type: "application/octet-stream"
|
||||
* });
|
||||
*/
|
||||
type?: string;
|
||||
|
||||
/**
|
||||
* By default, Amazon S3 uses the STANDARD Storage Class to store newly created objects.
|
||||
*
|
||||
* @example
|
||||
* // Setting explicit Storage class
|
||||
* const file = s3("my-file.json", {
|
||||
* storageClass: "STANDARD_IA"
|
||||
* });
|
||||
*/
|
||||
storageClass?:
|
||||
| "STANDARD"
|
||||
| "DEEP_ARCHIVE"
|
||||
| "EXPRESS_ONEZONE"
|
||||
| "GLACIER"
|
||||
| "GLACIER_IR"
|
||||
| "INTELLIGENT_TIERING"
|
||||
| "ONEZONE_IA"
|
||||
| "OUTPOSTS"
|
||||
| "REDUCED_REDUNDANCY"
|
||||
| "SNOW"
|
||||
| "STANDARD_IA";
|
||||
|
||||
/**
|
||||
* @deprecated The size of the internal buffer in bytes. Defaults to 5 MiB. use `partSize` and `queueSize` instead.
|
||||
*/
|
||||
highWaterMark?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for generating presigned URLs
|
||||
*/
|
||||
interface S3FilePresignOptions extends S3Options {
|
||||
/**
|
||||
* Number of seconds until the presigned URL expires.
|
||||
* - Default: 86400 (1 day)
|
||||
*
|
||||
* @example
|
||||
* // Short-lived URL
|
||||
* const url = file.presign({
|
||||
* expiresIn: 3600 // 1 hour
|
||||
* });
|
||||
*
|
||||
* @example
|
||||
* // Long-lived public URL
|
||||
* const url = file.presign({
|
||||
* expiresIn: 7 * 24 * 60 * 60, // 7 days
|
||||
* acl: "public-read"
|
||||
* });
|
||||
*/
|
||||
expiresIn?: number;
|
||||
|
||||
/**
|
||||
* The HTTP method allowed for the presigned URL.
|
||||
*
|
||||
* @example
|
||||
* // GET URL for downloads
|
||||
* const downloadUrl = file.presign({
|
||||
* method: "GET",
|
||||
* expiresIn: 3600
|
||||
* });
|
||||
*
|
||||
* @example
|
||||
* // PUT URL for uploads
|
||||
* const uploadUrl = file.presign({
|
||||
* method: "PUT",
|
||||
* expiresIn: 3600,
|
||||
* type: "application/json"
|
||||
* });
|
||||
*/
|
||||
method?: "GET" | "POST" | "PUT" | "DELETE" | "HEAD";
|
||||
}
|
||||
|
||||
interface S3Stats {
|
||||
size: number;
|
||||
lastModified: Date;
|
||||
etag: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a file in an S3-compatible storage service.
|
||||
* Extends the Blob interface for compatibility with web APIs.
|
||||
*
|
||||
* @category Cloud Storage
|
||||
*/
|
||||
interface S3File extends Blob {
|
||||
/**
|
||||
* The size of the file in bytes.
|
||||
* This is a Promise because it requires a network request to determine the size.
|
||||
*
|
||||
* @example
|
||||
* // Getting file size
|
||||
* const size = await file.size;
|
||||
* console.log(`File size: ${size} bytes`);
|
||||
*
|
||||
* @example
|
||||
* // Check if file is larger than 1MB
|
||||
* if (await file.size > 1024 * 1024) {
|
||||
* console.log("Large file detected");
|
||||
* }
|
||||
*/
|
||||
/**
|
||||
* TODO: figure out how to get the typescript types to not error for this property.
|
||||
*/
|
||||
// size: Promise<number>;
|
||||
|
||||
/**
|
||||
* Creates a new S3File representing a slice of the original file.
|
||||
* Uses HTTP Range headers for efficient partial downloads.
|
||||
*
|
||||
* @param begin - Starting byte offset
|
||||
* @param end - Ending byte offset (exclusive)
|
||||
* @param contentType - Optional MIME type for the slice
|
||||
* @returns A new S3File representing the specified range
|
||||
*
|
||||
* @example
|
||||
* // Reading file header
|
||||
* const header = file.slice(0, 1024);
|
||||
* const headerText = await header.text();
|
||||
*
|
||||
* @example
|
||||
* // Reading with content type
|
||||
* const jsonSlice = file.slice(1024, 2048, "application/json");
|
||||
* const data = await jsonSlice.json();
|
||||
*
|
||||
* @example
|
||||
* // Reading from offset to end
|
||||
* const remainder = file.slice(1024);
|
||||
* const content = await remainder.text();
|
||||
*/
|
||||
slice(begin?: number, end?: number, contentType?: string): S3File;
|
||||
slice(begin?: number, contentType?: string): S3File;
|
||||
slice(contentType?: string): S3File;
|
||||
|
||||
/**
|
||||
* Creates a writable stream for uploading data.
|
||||
* Suitable for large files as it uses multipart upload.
|
||||
*
|
||||
* @param options - Configuration for the upload
|
||||
* @returns A NetworkSink for writing data
|
||||
*
|
||||
* @example
|
||||
* // Basic streaming write
|
||||
* const writer = file.writer({
|
||||
* type: "application/json"
|
||||
* });
|
||||
* writer.write('{"hello": ');
|
||||
* writer.write('"world"}');
|
||||
* await writer.end();
|
||||
*
|
||||
* @example
|
||||
* // Optimized large file upload
|
||||
* const writer = file.writer({
|
||||
* partSize: 10 * 1024 * 1024, // 10MB parts
|
||||
* queueSize: 4, // Upload 4 parts in parallel
|
||||
* retry: 3 // Retry failed parts
|
||||
* });
|
||||
*
|
||||
* // Write large chunks of data efficiently
|
||||
* for (const chunk of largeDataChunks) {
|
||||
* writer.write(chunk);
|
||||
* }
|
||||
* await writer.end();
|
||||
*
|
||||
* @example
|
||||
* // Error handling
|
||||
* const writer = file.writer();
|
||||
* try {
|
||||
* writer.write(data);
|
||||
* await writer.end();
|
||||
* } catch (err) {
|
||||
* console.error('Upload failed:', err);
|
||||
* // Writer will automatically abort multipart upload on error
|
||||
* }
|
||||
*/
|
||||
writer(options?: S3Options): NetworkSink;
|
||||
|
||||
/**
|
||||
* Gets a readable stream of the file's content.
|
||||
* Useful for processing large files without loading them entirely into memory.
|
||||
*
|
||||
* @returns A ReadableStream for the file content
|
||||
*
|
||||
* @example
|
||||
* // Basic streaming read
|
||||
* const stream = file.stream();
|
||||
* for await (const chunk of stream) {
|
||||
* console.log('Received chunk:', chunk);
|
||||
* }
|
||||
*
|
||||
* @example
|
||||
* // Piping to response
|
||||
* const stream = file.stream();
|
||||
* return new Response(stream, {
|
||||
* headers: { 'Content-Type': file.type }
|
||||
* });
|
||||
*
|
||||
* @example
|
||||
* // Processing large files
|
||||
* const stream = file.stream();
|
||||
* const textDecoder = new TextDecoder();
|
||||
* for await (const chunk of stream) {
|
||||
* const text = textDecoder.decode(chunk);
|
||||
* // Process text chunk by chunk
|
||||
* }
|
||||
*/
|
||||
readonly readable: ReadableStream;
|
||||
stream(): ReadableStream;
|
||||
|
||||
/**
|
||||
* The name or path of the file in the bucket.
|
||||
*
|
||||
* @example
|
||||
* const file = s3("folder/image.jpg");
|
||||
* console.log(file.name); // "folder/image.jpg"
|
||||
*/
|
||||
readonly name?: string;
|
||||
|
||||
/**
|
||||
* The bucket name containing the file.
|
||||
*
|
||||
* @example
|
||||
* const file = s3("s3://my-bucket/file.txt");
|
||||
* console.log(file.bucket); // "my-bucket"
|
||||
*/
|
||||
readonly bucket?: string;
|
||||
|
||||
/**
|
||||
* Checks if the file exists in S3.
|
||||
* Uses HTTP HEAD request to efficiently check existence without downloading.
|
||||
*
|
||||
* @returns Promise resolving to true if file exists, false otherwise
|
||||
*
|
||||
* @example
|
||||
* // Basic existence check
|
||||
* if (await file.exists()) {
|
||||
* console.log("File exists in S3");
|
||||
* }
|
||||
*
|
||||
* @example
|
||||
* // With error handling
|
||||
* try {
|
||||
* const exists = await file.exists();
|
||||
* if (!exists) {
|
||||
* console.log("File not found");
|
||||
* }
|
||||
* } catch (err) {
|
||||
* console.error("Error checking file:", err);
|
||||
* }
|
||||
*/
|
||||
exists(): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Uploads data to S3.
|
||||
* Supports various input types and automatically handles large files.
|
||||
*
|
||||
* @param data - The data to upload
|
||||
* @param options - Upload configuration options
|
||||
* @returns Promise resolving to number of bytes written
|
||||
*
|
||||
* @example
|
||||
* // Writing string data
|
||||
* await file.write("Hello World", {
|
||||
* type: "text/plain"
|
||||
* });
|
||||
*
|
||||
* @example
|
||||
* // Writing JSON
|
||||
* const data = { hello: "world" };
|
||||
* await file.write(JSON.stringify(data), {
|
||||
* type: "application/json"
|
||||
* });
|
||||
*
|
||||
* @example
|
||||
* // Writing from Response
|
||||
* const response = await fetch("https://example.com/data");
|
||||
* await file.write(response);
|
||||
*
|
||||
* @example
|
||||
* // Writing with ACL
|
||||
* await file.write(data, {
|
||||
* acl: "public-read",
|
||||
* type: "application/octet-stream"
|
||||
* });
|
||||
*/
|
||||
write(
|
||||
data: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer | Request | Response | BunFile | S3File | Blob,
|
||||
options?: S3Options,
|
||||
): Promise<number>;
|
||||
|
||||
/**
|
||||
* Generates a presigned URL for the file.
|
||||
* Allows temporary access to the file without exposing credentials.
|
||||
*
|
||||
* @param options - Configuration for the presigned URL
|
||||
* @returns Presigned URL string
|
||||
*
|
||||
* @example
|
||||
* // Basic download URL
|
||||
* const url = file.presign({
|
||||
* expiresIn: 3600 // 1 hour
|
||||
* });
|
||||
*
|
||||
* @example
|
||||
* // Upload URL with specific content type
|
||||
* const uploadUrl = file.presign({
|
||||
* method: "PUT",
|
||||
* expiresIn: 3600,
|
||||
* type: "image/jpeg",
|
||||
* acl: "public-read"
|
||||
* });
|
||||
*
|
||||
* @example
|
||||
* // URL with custom permissions
|
||||
* const url = file.presign({
|
||||
* method: "GET",
|
||||
* expiresIn: 7 * 24 * 60 * 60, // 7 days
|
||||
* acl: "public-read"
|
||||
* });
|
||||
*/
|
||||
presign(options?: S3FilePresignOptions): string;
|
||||
|
||||
/**
|
||||
* Deletes the file from S3.
|
||||
*
|
||||
* @returns Promise that resolves when deletion is complete
|
||||
*
|
||||
* @example
|
||||
* // Basic deletion
|
||||
* await file.delete();
|
||||
*
|
||||
* @example
|
||||
* // With error handling
|
||||
* try {
|
||||
* await file.delete();
|
||||
* console.log("File deleted successfully");
|
||||
* } catch (err) {
|
||||
* console.error("Failed to delete file:", err);
|
||||
* }
|
||||
*/
|
||||
delete(): Promise<void>;
|
||||
|
||||
/**
|
||||
* Alias for delete() method.
|
||||
* Provided for compatibility with Node.js fs API naming.
|
||||
*
|
||||
* @example
|
||||
* await file.unlink();
|
||||
*/
|
||||
unlink: S3File["delete"];
|
||||
|
||||
/**
|
||||
* Get the stat of a file in an S3-compatible storage service.
|
||||
*
|
||||
* @returns Promise resolving to S3Stat
|
||||
*/
|
||||
stat(): Promise<S3Stats>;
|
||||
}
|
||||
|
||||
/**
|
||||
* A configured S3 bucket instance for managing files.
|
||||
* The instance is callable to create S3File instances and provides methods
|
||||
* for common operations.
|
||||
*
|
||||
* @example
|
||||
* // Basic bucket setup
|
||||
* const bucket = new S3Client({
|
||||
* bucket: "my-bucket",
|
||||
* accessKeyId: "key",
|
||||
* secretAccessKey: "secret"
|
||||
* });
|
||||
*
|
||||
* // Get file instance
|
||||
* const file = bucket("image.jpg");
|
||||
*
|
||||
* // Common operations
|
||||
* await bucket.write("data.json", JSON.stringify({hello: "world"}));
|
||||
* const url = bucket.presign("file.pdf");
|
||||
* await bucket.unlink("old.txt");
|
||||
*
|
||||
* @category Cloud Storage
|
||||
*/
|
||||
class S3Client {
|
||||
prototype: S3Client;
|
||||
/**
|
||||
* Create a new instance of an S3 bucket so that credentials can be managed
|
||||
* from a single instance instead of being passed to every method.
|
||||
*
|
||||
* @param options The default options to use for the S3 client. Can be
|
||||
* overriden by passing options to the methods.
|
||||
*
|
||||
* ## Keep S3 credentials in a single instance
|
||||
*
|
||||
* @example
|
||||
* const bucket = new Bun.S3Client({
|
||||
* accessKeyId: "your-access-key",
|
||||
* secretAccessKey: "your-secret-key",
|
||||
* bucket: "my-bucket",
|
||||
* endpoint: "https://s3.us-east-1.amazonaws.com",
|
||||
* sessionToken: "your-session-token",
|
||||
* });
|
||||
*
|
||||
* // S3Client is callable, so you can do this:
|
||||
* const file = bucket.file("my-file.txt");
|
||||
*
|
||||
* // or this:
|
||||
* await file.write("Hello Bun!");
|
||||
* await file.text();
|
||||
*
|
||||
* // To delete the file:
|
||||
* await bucket.delete("my-file.txt");
|
||||
*
|
||||
* // To write a file without returning the instance:
|
||||
* await bucket.write("my-file.txt", "Hello Bun!");
|
||||
*
|
||||
*/
|
||||
constructor(options?: S3Options);
|
||||
|
||||
/**
|
||||
* Creates an S3File instance for the given path.
|
||||
*
|
||||
* @example
|
||||
* const file = bucket.file("image.jpg");
|
||||
* await file.write(imageData);
|
||||
* const configFile = bucket("config.json", {
|
||||
* type: "application/json",
|
||||
* acl: "private"
|
||||
* });
|
||||
*/
|
||||
file(path: string, options?: S3Options): S3File;
|
||||
|
||||
/**
|
||||
* Writes data directly to a path in the bucket.
|
||||
* Supports strings, buffers, streams, and web API types.
|
||||
*
|
||||
* @example
|
||||
* // Write string
|
||||
* await bucket.write("hello.txt", "Hello World");
|
||||
*
|
||||
* // Write JSON with type
|
||||
* await bucket.write(
|
||||
* "data.json",
|
||||
* JSON.stringify({hello: "world"}),
|
||||
* {type: "application/json"}
|
||||
* );
|
||||
*
|
||||
* // Write from fetch
|
||||
* const res = await fetch("https://example.com/data");
|
||||
* await bucket.write("data.bin", res);
|
||||
*
|
||||
* // Write with ACL
|
||||
* await bucket.write("public.html", html, {
|
||||
* acl: "public-read",
|
||||
* type: "text/html"
|
||||
* });
|
||||
*/
|
||||
write(
|
||||
path: string,
|
||||
data:
|
||||
| string
|
||||
| ArrayBufferView
|
||||
| ArrayBuffer
|
||||
| SharedArrayBuffer
|
||||
| Request
|
||||
| Response
|
||||
| BunFile
|
||||
| S3File
|
||||
| Blob
|
||||
| File,
|
||||
options?: S3Options,
|
||||
): Promise<number>;
|
||||
|
||||
/**
|
||||
* Generate a presigned URL for temporary access to a file.
|
||||
* Useful for generating upload/download URLs without exposing credentials.
|
||||
*
|
||||
* @example
|
||||
* // Download URL
|
||||
* const downloadUrl = bucket.presign("file.pdf", {
|
||||
* expiresIn: 3600 // 1 hour
|
||||
* });
|
||||
*
|
||||
* // Upload URL
|
||||
* const uploadUrl = bucket.presign("uploads/image.jpg", {
|
||||
* method: "PUT",
|
||||
* expiresIn: 3600,
|
||||
* type: "image/jpeg",
|
||||
* acl: "public-read"
|
||||
* });
|
||||
*
|
||||
* // Long-lived public URL
|
||||
* const publicUrl = bucket.presign("public/doc.pdf", {
|
||||
* expiresIn: 7 * 24 * 60 * 60, // 7 days
|
||||
* acl: "public-read"
|
||||
* });
|
||||
*/
|
||||
presign(path: string, options?: S3FilePresignOptions): string;
|
||||
|
||||
/**
|
||||
* Delete a file from the bucket.
|
||||
*
|
||||
* @example
|
||||
* // Simple delete
|
||||
* await bucket.unlink("old-file.txt");
|
||||
*
|
||||
* // With error handling
|
||||
* try {
|
||||
* await bucket.unlink("file.dat");
|
||||
* console.log("File deleted");
|
||||
* } catch (err) {
|
||||
* console.error("Delete failed:", err);
|
||||
* }
|
||||
*/
|
||||
unlink(path: string, options?: S3Options): Promise<void>;
|
||||
delete: S3Client["unlink"];
|
||||
|
||||
/**
|
||||
* Get the size of a file in bytes.
|
||||
* Uses HEAD request to efficiently get size.
|
||||
*
|
||||
* @example
|
||||
* // Get size
|
||||
* const bytes = await bucket.size("video.mp4");
|
||||
* console.log(`Size: ${bytes} bytes`);
|
||||
*
|
||||
* // Check if file is large
|
||||
* if (await bucket.size("data.zip") > 100 * 1024 * 1024) {
|
||||
* console.log("File is larger than 100MB");
|
||||
* }
|
||||
*/
|
||||
size(path: string, options?: S3Options): Promise<number>;
|
||||
|
||||
/**
|
||||
* Check if a file exists in the bucket.
|
||||
* Uses HEAD request to check existence.
|
||||
*
|
||||
* @example
|
||||
* // Check existence
|
||||
* if (await bucket.exists("config.json")) {
|
||||
* const file = bucket("config.json");
|
||||
* const config = await file.json();
|
||||
* }
|
||||
*
|
||||
* // With error handling
|
||||
* try {
|
||||
* if (!await bucket.exists("required.txt")) {
|
||||
* throw new Error("Required file missing");
|
||||
* }
|
||||
* } catch (err) {
|
||||
* console.error("Check failed:", err);
|
||||
* }
|
||||
*/
|
||||
exists(path: string, options?: S3Options): Promise<boolean>;
|
||||
/**
|
||||
* Get the stat of a file in an S3-compatible storage service.
|
||||
*
|
||||
* @param path The path to the file.
|
||||
* @param options The options to use for the S3 client.
|
||||
*/
|
||||
stat(path: string, options?: S3Options): Promise<S3Stats>;
|
||||
}
|
||||
|
||||
/**
|
||||
* A default instance of S3Client
|
||||
*
|
||||
* Pulls credentials from environment variables. Use `new Bun.S3Client()` if you need to explicitly set credentials.
|
||||
*
|
||||
* @category Cloud Storage
|
||||
*/
|
||||
var s3: S3Client;
|
||||
}
|
||||
4
packages/bun-types/sqlite.d.ts
vendored
4
packages/bun-types/sqlite.d.ts
vendored
@@ -58,6 +58,8 @@ declare module "bun:sqlite" {
|
||||
* ```ts
|
||||
* const db = new Database("mydb.sqlite", {readonly: true});
|
||||
* ```
|
||||
*
|
||||
* @category Database
|
||||
*/
|
||||
constructor(
|
||||
filename?: string,
|
||||
@@ -567,6 +569,8 @@ 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 = ?");
|
||||
|
||||
19
packages/bun-types/test.d.ts
vendored
19
packages/bun-types/test.d.ts
vendored
@@ -16,6 +16,8 @@
|
||||
declare module "bun:test" {
|
||||
/**
|
||||
* -- Mocks --
|
||||
*
|
||||
* @category Testing
|
||||
*/
|
||||
export type Mock<T extends (...args: any[]) => any> = JestMock.Mock<T>;
|
||||
|
||||
@@ -149,6 +151,10 @@ declare module "bun:test" {
|
||||
methodOrPropertyValue: K,
|
||||
): Mock<T[K] extends (...args: any[]) => any ? T[K] : never>;
|
||||
|
||||
interface FunctionLike {
|
||||
readonly name: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes a group of related tests.
|
||||
*
|
||||
@@ -164,11 +170,9 @@ 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;
|
||||
|
||||
@@ -352,6 +356,8 @@ 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 {
|
||||
(
|
||||
@@ -420,7 +426,6 @@ declare module "bun:test" {
|
||||
*
|
||||
* @param label the label for the test
|
||||
* @param fn the test function
|
||||
* @param options the test timeout or options
|
||||
*/
|
||||
failing(label: string, fn?: (() => void | Promise<unknown>) | ((done: (err?: unknown) => void) => void)): void;
|
||||
/**
|
||||
@@ -1778,10 +1783,6 @@ declare module "bun:test" {
|
||||
type MatcherContext = MatcherUtils & MatcherState;
|
||||
}
|
||||
|
||||
declare module "test" {
|
||||
export type * from "bun:test";
|
||||
}
|
||||
|
||||
declare namespace JestMock {
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
|
||||
@@ -5,7 +5,7 @@ declare module "bun" {
|
||||
FOO: "FOO";
|
||||
}
|
||||
}
|
||||
expectType<"FOO">(process.env.FOO);
|
||||
expectType<"FOO">(Bun.env.FOO);
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
@@ -15,7 +15,6 @@ declare global {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
expectType<"BAR">(process.env.BAR);
|
||||
|
||||
process.env.FOO;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"skipLibCheck": true,
|
||||
"declaration": true,
|
||||
"emitDeclarationOnly": true,
|
||||
"noEmit": false,
|
||||
"declarationDir": "out"
|
||||
},
|
||||
"include": ["**/*.ts"],
|
||||
"exclude": ["dist", "node_modules"]
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"skipLibCheck": true,
|
||||
"declaration": true,
|
||||
"emitDeclarationOnly": true,
|
||||
"noEmit": false,
|
||||
"declarationDir": "out"
|
||||
},
|
||||
"include": ["**/*.ts"],
|
||||
"exclude": ["dist", "node_modules"]
|
||||
}
|
||||
|
||||
433
packages/bun-types/wasm.d.ts
vendored
433
packages/bun-types/wasm.d.ts
vendored
@@ -1,270 +1,193 @@
|
||||
export {};
|
||||
|
||||
type _Global<T extends Bun.WebAssembly.ValueType = Bun.WebAssembly.ValueType> = typeof globalThis extends {
|
||||
onerror: any;
|
||||
WebAssembly: { Global: infer T };
|
||||
}
|
||||
? T
|
||||
: Bun.WebAssembly.Global<T>;
|
||||
|
||||
type _CompileError = typeof globalThis extends {
|
||||
onerror: any;
|
||||
WebAssembly: { CompileError: infer T };
|
||||
}
|
||||
? T
|
||||
: Bun.WebAssembly.CompileError;
|
||||
|
||||
type _LinkError = typeof globalThis extends {
|
||||
onerror: any;
|
||||
WebAssembly: { LinkError: infer T };
|
||||
}
|
||||
? T
|
||||
: Bun.WebAssembly.LinkError;
|
||||
|
||||
type _RuntimeError = typeof globalThis extends {
|
||||
onerror: any;
|
||||
WebAssembly: { RuntimeError: infer T };
|
||||
}
|
||||
? T
|
||||
: Bun.WebAssembly.RuntimeError;
|
||||
|
||||
type _Memory = typeof globalThis extends {
|
||||
onerror: any;
|
||||
WebAssembly: { Memory: infer T };
|
||||
}
|
||||
? T
|
||||
: Bun.WebAssembly.Memory;
|
||||
|
||||
type _Instance = typeof globalThis extends {
|
||||
onerror: any;
|
||||
WebAssembly: { Instance: infer T };
|
||||
}
|
||||
? T
|
||||
: Bun.WebAssembly.Instance;
|
||||
|
||||
type _Module = typeof globalThis extends {
|
||||
onerror: any;
|
||||
WebAssembly: { Module: infer T };
|
||||
}
|
||||
? T
|
||||
: Bun.WebAssembly.Module;
|
||||
|
||||
type _Table = typeof globalThis extends {
|
||||
onerror: any;
|
||||
WebAssembly: { Table: infer T };
|
||||
}
|
||||
? T
|
||||
: Bun.WebAssembly.Table;
|
||||
|
||||
declare global {
|
||||
namespace Bun {
|
||||
namespace WebAssembly {
|
||||
type ImportExportKind = "function" | "global" | "memory" | "table";
|
||||
type TableKind = "anyfunc" | "externref";
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
type ExportValue = Function | Global | WebAssembly.Memory | WebAssembly.Table;
|
||||
type Exports = Record<string, ExportValue>;
|
||||
type ImportValue = ExportValue | number;
|
||||
type Imports = Record<string, ModuleImports>;
|
||||
type ModuleImports = Record<string, ImportValue>;
|
||||
|
||||
interface ValueTypeMap {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
anyfunc: Function;
|
||||
externref: any;
|
||||
f32: number;
|
||||
f64: number;
|
||||
i32: number;
|
||||
i64: bigint;
|
||||
v128: never;
|
||||
}
|
||||
|
||||
type ValueType = keyof ValueTypeMap;
|
||||
|
||||
interface GlobalDescriptor<T extends ValueType = ValueType> {
|
||||
mutable?: boolean;
|
||||
value: T;
|
||||
}
|
||||
|
||||
interface Global<T extends ValueType = ValueType> {
|
||||
// <T extends ValueType = ValueType> {
|
||||
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Global/value) */
|
||||
value: ValueTypeMap[T];
|
||||
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Global/valueOf) */
|
||||
valueOf(): ValueTypeMap[T];
|
||||
}
|
||||
|
||||
interface CompileError extends Error {}
|
||||
|
||||
interface LinkError extends Error {}
|
||||
|
||||
interface RuntimeError extends Error {}
|
||||
|
||||
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Instance) */
|
||||
interface Instance {
|
||||
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Instance/exports) */
|
||||
readonly exports: Exports;
|
||||
}
|
||||
|
||||
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Memory) */
|
||||
interface Memory {
|
||||
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Memory/buffer) */
|
||||
readonly buffer: ArrayBuffer;
|
||||
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Memory/grow) */
|
||||
grow(delta: number): number;
|
||||
}
|
||||
|
||||
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Module) */
|
||||
interface Module {}
|
||||
|
||||
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Table) */
|
||||
interface Table {
|
||||
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Table/length) */
|
||||
readonly length: number;
|
||||
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Table/get) */
|
||||
get(index: number): any;
|
||||
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Table/grow) */
|
||||
grow(delta: number, value?: any): number;
|
||||
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Table/set) */
|
||||
set(index: number, value?: any): void;
|
||||
}
|
||||
|
||||
interface MemoryDescriptor {
|
||||
initial: number;
|
||||
maximum?: number;
|
||||
shared?: boolean;
|
||||
}
|
||||
|
||||
interface ModuleExportDescriptor {
|
||||
kind: ImportExportKind;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface ModuleImportDescriptor {
|
||||
kind: ImportExportKind;
|
||||
module: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface TableDescriptor {
|
||||
element: TableKind;
|
||||
initial: number;
|
||||
maximum?: number;
|
||||
}
|
||||
|
||||
interface WebAssemblyInstantiatedSource {
|
||||
instance: Instance;
|
||||
module: Module;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare module "bun" {
|
||||
namespace WebAssembly {
|
||||
interface ValueTypeMap extends Bun.WebAssembly.ValueTypeMap {}
|
||||
interface GlobalDescriptor<T extends keyof ValueTypeMap = keyof ValueTypeMap>
|
||||
extends Bun.WebAssembly.GlobalDescriptor<T> {}
|
||||
interface MemoryDescriptor extends Bun.WebAssembly.MemoryDescriptor {}
|
||||
interface ModuleExportDescriptor extends Bun.WebAssembly.ModuleExportDescriptor {}
|
||||
interface ModuleImportDescriptor extends Bun.WebAssembly.ModuleImportDescriptor {}
|
||||
interface TableDescriptor extends Bun.WebAssembly.TableDescriptor {}
|
||||
interface WebAssemblyInstantiatedSource extends Bun.WebAssembly.WebAssemblyInstantiatedSource {}
|
||||
type ImportExportKind = "function" | "global" | "memory" | "table";
|
||||
type TableKind = "anyfunc" | "externref";
|
||||
type ExportValue = Function | Global | WebAssembly.Memory | WebAssembly.Table;
|
||||
type Exports = Record<string, ExportValue>;
|
||||
type ImportValue = ExportValue | number;
|
||||
type Imports = Record<string, ModuleImports>;
|
||||
type ModuleImports = Record<string, ImportValue>;
|
||||
|
||||
interface LinkError extends _LinkError {}
|
||||
var LinkError: {
|
||||
prototype: LinkError;
|
||||
new (message?: string): LinkError;
|
||||
(message?: string): LinkError;
|
||||
};
|
||||
|
||||
interface CompileError extends _CompileError {}
|
||||
var CompileError: typeof globalThis extends {
|
||||
onerror: any;
|
||||
WebAssembly: { CompileError: infer T };
|
||||
interface ValueTypeMap {
|
||||
anyfunc: Function;
|
||||
externref: any;
|
||||
f32: number;
|
||||
f64: number;
|
||||
i32: number;
|
||||
i64: bigint;
|
||||
v128: never;
|
||||
}
|
||||
? T
|
||||
: {
|
||||
prototype: CompileError;
|
||||
new (message?: string): CompileError;
|
||||
(message?: string): CompileError;
|
||||
};
|
||||
|
||||
interface RuntimeError extends _RuntimeError {}
|
||||
var RuntimeError: {
|
||||
prototype: RuntimeError;
|
||||
new (message?: string): RuntimeError;
|
||||
(message?: string): RuntimeError;
|
||||
};
|
||||
type ValueType = keyof ValueTypeMap;
|
||||
|
||||
interface Global<T extends keyof ValueTypeMap = keyof ValueTypeMap> extends _Global<T> {}
|
||||
var Global: typeof globalThis extends {
|
||||
onerror: any;
|
||||
WebAssembly: { Global: infer T };
|
||||
interface GlobalDescriptor<T extends ValueType = ValueType> {
|
||||
mutable?: boolean;
|
||||
value: T;
|
||||
}
|
||||
? T
|
||||
: {
|
||||
prototype: Global;
|
||||
new <T extends Bun.WebAssembly.ValueType = Bun.WebAssembly.ValueType>(
|
||||
descriptor: GlobalDescriptor<T>,
|
||||
v?: ValueTypeMap[T],
|
||||
): Global<T>;
|
||||
};
|
||||
|
||||
interface Instance extends _Instance {}
|
||||
var Instance: typeof globalThis extends {
|
||||
onerror: any;
|
||||
WebAssembly: { Instance: infer T };
|
||||
interface Global<T extends ValueType = ValueType> {
|
||||
// <T extends ValueType = ValueType> {
|
||||
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Global/value) */
|
||||
value: ValueTypeMap[T];
|
||||
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Global/valueOf) */
|
||||
valueOf(): ValueTypeMap[T];
|
||||
}
|
||||
? T
|
||||
: {
|
||||
prototype: Instance;
|
||||
new (module: Module, importObject?: Bun.WebAssembly.Imports): Instance;
|
||||
};
|
||||
|
||||
interface Memory extends _Memory {}
|
||||
var Memory: {
|
||||
prototype: Memory;
|
||||
new (descriptor: MemoryDescriptor): Memory;
|
||||
};
|
||||
interface CompileError extends Error {}
|
||||
|
||||
interface Module extends _Module {}
|
||||
var Module: typeof globalThis extends {
|
||||
onerror: any;
|
||||
WebAssembly: { Module: infer T };
|
||||
interface LinkError extends Error {}
|
||||
|
||||
interface RuntimeError extends Error {}
|
||||
|
||||
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Instance) */
|
||||
interface Instance {
|
||||
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Instance/exports) */
|
||||
readonly exports: Exports;
|
||||
}
|
||||
? T
|
||||
: {
|
||||
prototype: Module;
|
||||
new (bytes: Bun.BufferSource): Module;
|
||||
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Module/customSections) */
|
||||
customSections(moduleObject: Module, sectionName: string): ArrayBuffer[];
|
||||
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Module/exports) */
|
||||
exports(moduleObject: Module): ModuleExportDescriptor[];
|
||||
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Module/imports) */
|
||||
imports(moduleObject: Module): ModuleImportDescriptor[];
|
||||
};
|
||||
|
||||
interface Table extends _Table {}
|
||||
var Table: {
|
||||
prototype: Table;
|
||||
new (descriptor: TableDescriptor, value?: any): Table;
|
||||
};
|
||||
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Memory) */
|
||||
interface Memory {
|
||||
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Memory/buffer) */
|
||||
readonly buffer: ArrayBuffer;
|
||||
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Memory/grow) */
|
||||
grow(delta: number): number;
|
||||
}
|
||||
|
||||
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/compile) */
|
||||
function compile(bytes: Bun.BufferSource): Promise<Module>;
|
||||
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/compileStreaming) */
|
||||
function compileStreaming(source: Response | PromiseLike<Response>): Promise<Module>;
|
||||
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/instantiate) */
|
||||
function instantiate(
|
||||
bytes: Bun.BufferSource,
|
||||
importObject?: Bun.WebAssembly.Imports,
|
||||
): Promise<WebAssemblyInstantiatedSource>;
|
||||
function instantiate(moduleObject: Module, importObject?: Bun.WebAssembly.Imports): Promise<Instance>;
|
||||
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/instantiateStreaming) */
|
||||
function instantiateStreaming(
|
||||
source: Response | PromiseLike<Response>,
|
||||
importObject?: Bun.WebAssembly.Imports,
|
||||
): Promise<WebAssemblyInstantiatedSource>;
|
||||
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/validate) */
|
||||
function validate(bytes: Bun.BufferSource): boolean;
|
||||
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Module) */
|
||||
interface Module {}
|
||||
|
||||
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Table) */
|
||||
interface Table {
|
||||
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Table/length) */
|
||||
readonly length: number;
|
||||
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Table/get) */
|
||||
get(index: number): any;
|
||||
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Table/grow) */
|
||||
grow(delta: number, value?: any): number;
|
||||
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Table/set) */
|
||||
set(index: number, value?: any): void;
|
||||
}
|
||||
|
||||
interface MemoryDescriptor {
|
||||
initial: number;
|
||||
maximum?: number;
|
||||
shared?: boolean;
|
||||
}
|
||||
|
||||
interface ModuleExportDescriptor {
|
||||
kind: ImportExportKind;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface ModuleImportDescriptor {
|
||||
kind: ImportExportKind;
|
||||
module: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface TableDescriptor {
|
||||
element: TableKind;
|
||||
initial: number;
|
||||
maximum?: number;
|
||||
}
|
||||
|
||||
interface WebAssemblyInstantiatedSource {
|
||||
instance: Instance;
|
||||
module: Module;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare namespace WebAssembly {
|
||||
interface ValueTypeMap extends Bun.WebAssembly.ValueTypeMap {}
|
||||
interface GlobalDescriptor<T extends keyof ValueTypeMap = keyof ValueTypeMap>
|
||||
extends Bun.WebAssembly.GlobalDescriptor<T> {}
|
||||
interface MemoryDescriptor extends Bun.WebAssembly.MemoryDescriptor {}
|
||||
interface ModuleExportDescriptor extends Bun.WebAssembly.ModuleExportDescriptor {}
|
||||
interface ModuleImportDescriptor extends Bun.WebAssembly.ModuleImportDescriptor {}
|
||||
interface TableDescriptor extends Bun.WebAssembly.TableDescriptor {}
|
||||
interface WebAssemblyInstantiatedSource extends Bun.WebAssembly.WebAssemblyInstantiatedSource {}
|
||||
|
||||
interface LinkError extends Bun.WebAssembly.LinkError {}
|
||||
var LinkError: {
|
||||
prototype: LinkError;
|
||||
new (message?: string): LinkError;
|
||||
(message?: string): LinkError;
|
||||
};
|
||||
|
||||
interface CompileError extends Bun.WebAssembly.CompileError {}
|
||||
var CompileError: {
|
||||
prototype: CompileError;
|
||||
new (message?: string): CompileError;
|
||||
(message?: string): CompileError;
|
||||
};
|
||||
|
||||
interface RuntimeError extends Bun.WebAssembly.RuntimeError {}
|
||||
var RuntimeError: {
|
||||
prototype: RuntimeError;
|
||||
new (message?: string): RuntimeError;
|
||||
(message?: string): RuntimeError;
|
||||
};
|
||||
|
||||
interface Global<T extends keyof ValueTypeMap = keyof ValueTypeMap> extends Bun.WebAssembly.Global<T> {}
|
||||
var Global: {
|
||||
prototype: Global;
|
||||
new <T extends Bun.WebAssembly.ValueType = Bun.WebAssembly.ValueType>(
|
||||
descriptor: GlobalDescriptor<T>,
|
||||
v?: ValueTypeMap[T],
|
||||
): Global<T>;
|
||||
};
|
||||
|
||||
interface Instance extends Bun.WebAssembly.Instance {}
|
||||
var Instance: {
|
||||
prototype: Instance;
|
||||
new (module: Module, importObject?: Bun.WebAssembly.Imports): Instance;
|
||||
};
|
||||
|
||||
interface Memory extends Bun.WebAssembly.Memory {}
|
||||
var Memory: {
|
||||
prototype: Memory;
|
||||
new (descriptor: MemoryDescriptor): Memory;
|
||||
};
|
||||
|
||||
interface Module extends Bun.WebAssembly.Module {}
|
||||
var Module: Bun.__internal.UseLibDomIfAvailable<
|
||||
"WebAssembly",
|
||||
{
|
||||
Module: {
|
||||
prototype: Module;
|
||||
new (bytes: Bun.BufferSource): Module;
|
||||
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Module/customSections) */
|
||||
customSections(moduleObject: Module, sectionName: string): ArrayBuffer[];
|
||||
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Module/exports) */
|
||||
exports(moduleObject: Module): ModuleExportDescriptor[];
|
||||
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Module/imports) */
|
||||
imports(moduleObject: Module): ModuleImportDescriptor[];
|
||||
};
|
||||
}
|
||||
>["Module"];
|
||||
|
||||
interface Table extends Bun.WebAssembly.Table {}
|
||||
var Table: {
|
||||
prototype: Table;
|
||||
new (descriptor: TableDescriptor, value?: any): Table;
|
||||
};
|
||||
|
||||
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/compile) */
|
||||
function compile(bytes: Bun.BufferSource): Promise<Module>;
|
||||
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/compileStreaming) */
|
||||
function compileStreaming(source: Response | PromiseLike<Response>): Promise<Module>;
|
||||
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/instantiate) */
|
||||
function instantiate(
|
||||
bytes: Bun.BufferSource,
|
||||
importObject?: Bun.WebAssembly.Imports,
|
||||
): Promise<WebAssemblyInstantiatedSource>;
|
||||
function instantiate(moduleObject: Module, importObject?: Bun.WebAssembly.Imports): Promise<Instance>;
|
||||
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/instantiateStreaming) */
|
||||
function instantiateStreaming(
|
||||
source: Response | PromiseLike<Response>,
|
||||
importObject?: Bun.WebAssembly.Imports,
|
||||
): Promise<WebAssemblyInstantiatedSource>;
|
||||
/** [MDN Reference](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/validate) */
|
||||
function validate(bytes: Bun.BufferSource): boolean;
|
||||
}
|
||||
|
||||
@@ -472,6 +472,7 @@ int us_socket_raw_write(int ssl, us_socket_r s, const char *data, int length, in
|
||||
struct us_socket_t* us_socket_open(int ssl, struct us_socket_t * s, int is_client, char* ip, int ip_length);
|
||||
int us_raw_root_certs(struct us_cert_string_t**out);
|
||||
unsigned int us_get_remote_address_info(char *buf, us_socket_r s, const char **dest, int *port, int *is_ipv6);
|
||||
unsigned int us_get_local_address_info(char *buf, us_socket_r s, const char **dest, int *port, int *is_ipv6);
|
||||
int us_socket_get_error(int ssl, us_socket_r s);
|
||||
|
||||
void us_socket_ref(us_socket_r s);
|
||||
|
||||
@@ -213,21 +213,21 @@ void us_internal_free_closed_sockets(struct us_loop_t *loop) {
|
||||
us_poll_free((struct us_poll_t *) s, loop);
|
||||
s = next;
|
||||
}
|
||||
loop->data.closed_head = 0;
|
||||
loop->data.closed_head = NULL;
|
||||
|
||||
for (struct us_udp_socket_t *s = loop->data.closed_udp_head; s; ) {
|
||||
struct us_udp_socket_t *next = s->next;
|
||||
us_poll_free((struct us_poll_t *) s, loop);
|
||||
s = next;
|
||||
}
|
||||
loop->data.closed_udp_head = 0;
|
||||
loop->data.closed_udp_head = NULL;
|
||||
|
||||
for (struct us_connecting_socket_t *s = loop->data.closed_connecting_head; s; ) {
|
||||
struct us_connecting_socket_t *next = s->next;
|
||||
us_free(s);
|
||||
s = next;
|
||||
}
|
||||
loop->data.closed_connecting_head = 0;
|
||||
loop->data.closed_connecting_head = NULL;
|
||||
}
|
||||
|
||||
void us_internal_free_closed_contexts(struct us_loop_t *loop) {
|
||||
@@ -236,7 +236,7 @@ void us_internal_free_closed_contexts(struct us_loop_t *loop) {
|
||||
us_free(ctx);
|
||||
ctx = next;
|
||||
}
|
||||
loop->data.closed_context_head = 0;
|
||||
loop->data.closed_context_head = NULL;
|
||||
}
|
||||
|
||||
void sweep_timer_cb(struct us_internal_callback_t *cb) {
|
||||
|
||||
@@ -501,6 +501,24 @@ unsigned int us_get_remote_address_info(char *buf, struct us_socket_t *s, const
|
||||
return length;
|
||||
}
|
||||
|
||||
unsigned int us_get_local_address_info(char *buf, struct us_socket_t *s, const char **dest, int *port, int *is_ipv6)
|
||||
{
|
||||
struct bsd_addr_t addr;
|
||||
if (bsd_local_addr(us_poll_fd(&s->p), &addr)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int length = bsd_addr_get_ip_length(&addr);
|
||||
if (!length) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
memcpy(buf, bsd_addr_get_ip(&addr), length);
|
||||
*port = bsd_addr_get_port(&addr);
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
void us_socket_ref(struct us_socket_t *s) {
|
||||
#ifdef LIBUS_USE_LIBUV
|
||||
uv_ref((uv_handle_t*)s->p.uv_p);
|
||||
|
||||
@@ -131,7 +131,7 @@ public:
|
||||
getLoopData()->setCorkedSocket(this, SSL);
|
||||
}
|
||||
|
||||
/* Returns wheter we are corked or not */
|
||||
/* Returns whether we are corked */
|
||||
bool isCorked() {
|
||||
return getLoopData()->isCorkedWith(this);
|
||||
}
|
||||
@@ -182,9 +182,9 @@ public:
|
||||
}
|
||||
|
||||
/* Returns the user space backpressure. */
|
||||
unsigned int getBufferedAmount() {
|
||||
size_t getBufferedAmount() {
|
||||
/* We return the actual amount of bytes in backbuffer, including pendingRemoval */
|
||||
return (unsigned int) getAsyncSocketData()->buffer.totalLength();
|
||||
return getAsyncSocketData()->buffer.totalLength();
|
||||
}
|
||||
|
||||
/* Returns the text representation of an IPv4 or IPv6 address */
|
||||
@@ -222,6 +222,63 @@ public:
|
||||
return addressAsText(getRemoteAddress());
|
||||
}
|
||||
|
||||
/**
|
||||
* Flushes the socket buffer by writing as much data as possible to the underlying socket.
|
||||
*
|
||||
* @return The total number of bytes successfully written to the socket
|
||||
*/
|
||||
size_t flush() {
|
||||
/* Check if socket is valid for operations */
|
||||
if (us_socket_is_closed(SSL, (us_socket_t *) this)) {
|
||||
/* Socket is closed, no flushing is possible */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Get the associated asynchronous socket data structure */
|
||||
AsyncSocketData<SSL> *asyncSocketData = getAsyncSocketData();
|
||||
size_t total_written = 0;
|
||||
|
||||
/* Continue flushing as long as we have data in the buffer */
|
||||
while (asyncSocketData->buffer.length()) {
|
||||
/* Get current buffer size */
|
||||
size_t buffer_len = asyncSocketData->buffer.length();
|
||||
|
||||
/* Limit write size to INT_MAX as the underlying socket API uses int for length */
|
||||
int max_flush_len = std::min(buffer_len, (size_t)INT_MAX);
|
||||
|
||||
/* Attempt to write data to the socket */
|
||||
int written = us_socket_write(SSL, (us_socket_t *) this, asyncSocketData->buffer.data(), max_flush_len, 0);
|
||||
total_written += written;
|
||||
|
||||
/* Check if we couldn't write the entire buffer */
|
||||
if ((unsigned int) written < buffer_len) {
|
||||
/* Remove the successfully written data from the buffer */
|
||||
asyncSocketData->buffer.erase((unsigned int) written);
|
||||
|
||||
/* If we wrote less than we attempted, the socket buffer is likely full
|
||||
* likely is used as an optimization hint to the compiler
|
||||
* since written < buffer_len is very likely to be true
|
||||
*/
|
||||
if(written < max_flush_len) {
|
||||
[[likely]]
|
||||
/* Cannot write more at this time, return what we've written so far */
|
||||
return total_written;
|
||||
}
|
||||
/* If we wrote exactly max_flush_len, we might be able to write more, so continue
|
||||
* This is unlikely to happen, because this would be INT_MAX bytes, which is unlikely to be written in one go
|
||||
* but we keep this check for completeness
|
||||
*/
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Successfully wrote the entire buffer, clear the buffer */
|
||||
asyncSocketData->buffer.clear();
|
||||
}
|
||||
|
||||
/* Return the total number of bytes written during this flush operation */
|
||||
return total_written;
|
||||
}
|
||||
|
||||
/* Write in three levels of prioritization: cork-buffer, syscall, socket-buffer. Always drain if possible.
|
||||
* Returns pair of bytes written (anywhere) and wheter or not this call resulted in the polling for
|
||||
* writable (or we are in a state that implies polling for writable). */
|
||||
@@ -233,7 +290,6 @@ public:
|
||||
|
||||
LoopData *loopData = getLoopData();
|
||||
AsyncSocketData<SSL> *asyncSocketData = getAsyncSocketData();
|
||||
|
||||
/* We are limited if we have a per-socket buffer */
|
||||
if (asyncSocketData->buffer.length()) {
|
||||
size_t buffer_len = asyncSocketData->buffer.length();
|
||||
@@ -261,7 +317,7 @@ public:
|
||||
asyncSocketData->buffer.clear();
|
||||
}
|
||||
|
||||
if (length) {
|
||||
if (length) {
|
||||
if (loopData->isCorkedWith(this)) {
|
||||
/* We are corked */
|
||||
if (LoopData::CORK_BUFFER_SIZE - loopData->getCorkOffset() >= (unsigned int) length) {
|
||||
|
||||
@@ -125,7 +125,7 @@ private:
|
||||
}
|
||||
|
||||
/* Signal broken HTTP request only if we have a pending request */
|
||||
if (httpResponseData->onAborted) {
|
||||
if (httpResponseData->onAborted != nullptr && httpResponseData->userData != nullptr) {
|
||||
httpResponseData->onAborted((HttpResponse<SSL> *)s, httpResponseData->userData);
|
||||
}
|
||||
|
||||
@@ -235,7 +235,7 @@ private:
|
||||
}
|
||||
|
||||
/* Returning from a request handler without responding or attaching an onAborted handler is ill-use */
|
||||
if (!((HttpResponse<SSL> *) s)->hasResponded() && !httpResponseData->onAborted) {
|
||||
if (!((HttpResponse<SSL> *) s)->hasResponded() && !httpResponseData->onAborted && !httpResponseData->socketData) {
|
||||
/* Throw exception here? */
|
||||
std::cerr << "Error: Returning from a request handler without responding or attaching an abort handler is forbidden!" << std::endl;
|
||||
std::terminate();
|
||||
@@ -365,11 +365,32 @@ private:
|
||||
auto *asyncSocket = reinterpret_cast<AsyncSocket<SSL> *>(s);
|
||||
auto *httpResponseData = reinterpret_cast<HttpResponseData<SSL> *>(asyncSocket->getAsyncSocketData());
|
||||
|
||||
/* Attempt to drain the socket buffer before triggering onWritable callback */
|
||||
size_t bufferedAmount = asyncSocket->getBufferedAmount();
|
||||
if (bufferedAmount > 0) {
|
||||
/* Try to flush pending data from the socket's buffer to the network */
|
||||
bufferedAmount -= asyncSocket->flush();
|
||||
|
||||
/* Check if there's still data waiting to be sent after flush attempt */
|
||||
if (bufferedAmount > 0) {
|
||||
/* Socket buffer is not completely empty yet
|
||||
* - Reset the timeout to prevent premature connection closure
|
||||
* - This allows time for another writable event or new request
|
||||
* - Return the socket to indicate we're still processing
|
||||
*/
|
||||
reinterpret_cast<HttpResponse<SSL> *>(s)->resetTimeout();
|
||||
return s;
|
||||
}
|
||||
/* If bufferedAmount is now 0, we've successfully flushed everything
|
||||
* and will fall through to the next section of code
|
||||
*/
|
||||
}
|
||||
|
||||
/* Ask the developer to write data and return success (true) or failure (false), OR skip sending anything and return success (true). */
|
||||
if (httpResponseData->onWritable) {
|
||||
/* We are now writable, so hang timeout again, the user does not have to do anything so we should hang until end or tryEnd rearms timeout */
|
||||
us_socket_timeout(SSL, s, 0);
|
||||
|
||||
|
||||
/* We expect the developer to return whether or not write was successful (true).
|
||||
* If write was never called, the developer should still return true so that we may drain. */
|
||||
bool success = httpResponseData->callOnWritable(reinterpret_cast<HttpResponse<SSL> *>(asyncSocket), httpResponseData->offset);
|
||||
@@ -384,7 +405,7 @@ private:
|
||||
}
|
||||
|
||||
/* Drain any socket buffer, this might empty our backpressure and thus finish the request */
|
||||
/*auto [written, failed] = */asyncSocket->write(nullptr, 0, true, 0);
|
||||
asyncSocket->flush();
|
||||
|
||||
/* Should we close this connection after a response - and is this response really done? */
|
||||
if (httpResponseData->state & HttpResponseData<SSL>::HTTP_CONNECTION_CLOSE) {
|
||||
|
||||
@@ -122,15 +122,10 @@ public:
|
||||
|
||||
/* We do not have tryWrite-like functionalities, so ignore optional in this path */
|
||||
|
||||
/* Do not allow sending 0 chunk here */
|
||||
if (data.length()) {
|
||||
Super::write("\r\n", 2);
|
||||
writeUnsignedHex((unsigned int) data.length());
|
||||
Super::write("\r\n", 2);
|
||||
|
||||
/* Ignoring optional for now */
|
||||
Super::write(data.data(), (int) data.length());
|
||||
}
|
||||
|
||||
/* Write the chunked data if there is any (this will not send zero chunks) */
|
||||
this->write(data, nullptr);
|
||||
|
||||
|
||||
/* Terminating 0 chunk */
|
||||
Super::write("\r\n0\r\n\r\n", 7);
|
||||
@@ -480,6 +475,40 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t length = data.length();
|
||||
|
||||
// Special handling for extremely large data (greater than UINT_MAX bytes)
|
||||
// most clients expect a max of UINT_MAX, so we need to split the write into multiple writes
|
||||
if (length > UINT_MAX) {
|
||||
bool has_failed = false;
|
||||
size_t total_written = 0;
|
||||
// Process full-sized chunks until remaining data is less than UINT_MAX
|
||||
while (length > UINT_MAX) {
|
||||
size_t written = 0;
|
||||
// Write a UINT_MAX-sized chunk and check for failure
|
||||
// even after failure we continue writing because the data will be buffered
|
||||
if(!this->write(data.substr(0, UINT_MAX), &written)) {
|
||||
has_failed = true;
|
||||
}
|
||||
total_written += written;
|
||||
length -= UINT_MAX;
|
||||
data = data.substr(UINT_MAX);
|
||||
}
|
||||
// Handle the final chunk (less than UINT_MAX bytes)
|
||||
if (length > 0) {
|
||||
size_t written = 0;
|
||||
if(!this->write(data, &written)) {
|
||||
has_failed = true;
|
||||
}
|
||||
total_written += written;
|
||||
}
|
||||
if (writtenPtr) {
|
||||
*writtenPtr = total_written;
|
||||
}
|
||||
return !has_failed;
|
||||
}
|
||||
|
||||
|
||||
HttpResponseData<SSL> *httpResponseData = getHttpResponseData();
|
||||
|
||||
if (!(httpResponseData->state & HttpResponseData<SSL>::HTTP_WROTE_CONTENT_LENGTH_HEADER) && !httpResponseData->fromAncientRequest) {
|
||||
@@ -499,17 +528,36 @@ public:
|
||||
Super::write("\r\n", 2);
|
||||
httpResponseData->state |= HttpResponseData<SSL>::HTTP_WRITE_CALLED;
|
||||
}
|
||||
size_t total_written = 0;
|
||||
bool has_failed = false;
|
||||
|
||||
auto [written, failed] = Super::write(data.data(), (int) data.length());
|
||||
// Handle data larger than INT_MAX by writing it in chunks of INT_MAX bytes
|
||||
while (length > INT_MAX) {
|
||||
// Write the maximum allowed chunk size (INT_MAX)
|
||||
auto [written, failed] = Super::write(data.data(), INT_MAX);
|
||||
// If the write failed, set the has_failed flag we continue writting because the data will be buffered
|
||||
has_failed = has_failed || failed;
|
||||
total_written += written;
|
||||
length -= INT_MAX;
|
||||
data = data.substr(INT_MAX);
|
||||
}
|
||||
// Handle the remaining data (less than INT_MAX bytes)
|
||||
if (length > 0) {
|
||||
// Write the final chunk with exact remaining length
|
||||
auto [written, failed] = Super::write(data.data(), (int) length);
|
||||
has_failed = has_failed || failed;
|
||||
total_written += written;
|
||||
}
|
||||
|
||||
/* Reset timeout on each sended chunk */
|
||||
this->resetTimeout();
|
||||
|
||||
if (writtenPtr) {
|
||||
*writtenPtr = written;
|
||||
*writtenPtr = total_written;
|
||||
}
|
||||
|
||||
/* If we did not fail the write, accept more */
|
||||
return !failed;
|
||||
return !has_failed;
|
||||
}
|
||||
|
||||
/* Get the current byte write offset for this Http response */
|
||||
@@ -660,12 +708,6 @@ public:
|
||||
return httpResponseData->socketData;
|
||||
}
|
||||
|
||||
void setSocketData(void* socketData) {
|
||||
HttpResponseData<SSL> *httpResponseData = getHttpResponseData();
|
||||
|
||||
httpResponseData->socketData = socketData;
|
||||
}
|
||||
|
||||
void setWriteOffset(uint64_t offset) {
|
||||
HttpResponseData<SSL> *httpResponseData = getHttpResponseData();
|
||||
|
||||
|
||||
@@ -339,7 +339,7 @@ private:
|
||||
|
||||
/* We store old backpressure since it is unclear whether write drained anything,
|
||||
* however, in case of coming here with 0 backpressure we still need to emit drain event */
|
||||
unsigned int backpressure = asyncSocket->getBufferedAmount();
|
||||
size_t backpressure = asyncSocket->getBufferedAmount();
|
||||
|
||||
/* Drain as much as possible */
|
||||
asyncSocket->write(nullptr, 0);
|
||||
|
||||
@@ -3,8 +3,9 @@ const Watcher = @This();
|
||||
const DebugLogScope = bun.Output.Scoped(.watcher, false);
|
||||
const log = DebugLogScope.log;
|
||||
|
||||
// Consumer-facing
|
||||
watch_events: [max_count]WatchEvent,
|
||||
// This will always be [max_count]WatchEvent,
|
||||
// We avoid statically allocating because it increases the binary size.
|
||||
watch_events: []WatchEvent = &.{},
|
||||
changed_filepaths: [max_count]?[:0]u8,
|
||||
|
||||
/// The platform-specific implementation of the watcher
|
||||
@@ -86,7 +87,7 @@ pub fn init(comptime T: type, ctx: *T, fs: *bun.fs.FileSystem, allocator: std.me
|
||||
.onFileUpdate = &wrapped.onFileUpdateWrapped,
|
||||
.onError = &wrapped.onErrorWrapped,
|
||||
.platform = .{},
|
||||
.watch_events = undefined,
|
||||
.watch_events = try allocator.alloc(WatchEvent, max_count),
|
||||
.changed_filepaths = [_]?[:0]u8{null} ** max_count,
|
||||
};
|
||||
|
||||
@@ -251,7 +252,7 @@ pub fn flushEvictions(this: *Watcher) void {
|
||||
// swapRemove messes up the order
|
||||
// But, it only messes up the order if any elements in the list appear after the item being removed
|
||||
// So if we just sort the list by the biggest index first, that should be fine
|
||||
std.sort.pdq(
|
||||
std.sort.insertion(
|
||||
WatchItemIndex,
|
||||
this.evict_list[0..this.evict_list_i],
|
||||
{},
|
||||
@@ -268,7 +269,7 @@ pub fn flushEvictions(this: *Watcher) void {
|
||||
|
||||
if (!Environment.isWindows) {
|
||||
// on mac and linux we can just close the file descriptor
|
||||
// TODO do we need to call inotify_rm_watch on linux?
|
||||
// we don't need to call inotify_rm_watch on linux because it gets removed when the file descriptor is closed
|
||||
if (fds[item].isValid()) {
|
||||
_ = bun.sys.close(fds[item]);
|
||||
}
|
||||
@@ -279,7 +280,7 @@ pub fn flushEvictions(this: *Watcher) void {
|
||||
last_item = no_watch_item;
|
||||
// This is split into two passes because reading the slice while modified is potentially unsafe.
|
||||
for (this.evict_list[0..this.evict_list_i]) |item| {
|
||||
if (item == last_item) continue;
|
||||
if (item == last_item or this.watchlist.len <= item) continue;
|
||||
this.watchlist.swapRemove(item);
|
||||
last_item = item;
|
||||
}
|
||||
|
||||
@@ -130,6 +130,7 @@ pub const Features = struct {
|
||||
pub var s3: usize = 0;
|
||||
pub var csrf_verify: usize = 0;
|
||||
pub var csrf_generate: usize = 0;
|
||||
pub var unsupported_uv_function: usize = 0;
|
||||
|
||||
comptime {
|
||||
@export(&napi_module_register, .{ .name = "Bun__napi_module_register_count" });
|
||||
|
||||
@@ -360,7 +360,7 @@ pub const TablePrinter = struct {
|
||||
return;
|
||||
}
|
||||
|
||||
if (row_value.isObject()) {
|
||||
if (row_value.getObject()) |obj| {
|
||||
// object ->
|
||||
// - if "properties" arg was provided: iterate the already-created columns (except for the 0-th which is the index)
|
||||
// - otherwise: iterate the object properties, and create the columns on-demand
|
||||
@@ -374,7 +374,7 @@ pub const TablePrinter = struct {
|
||||
var cols_iter = try JSC.JSPropertyIterator(.{
|
||||
.skip_empty_name = false,
|
||||
.include_value = true,
|
||||
}).init(this.globalObject, row_value);
|
||||
}).init(this.globalObject, obj);
|
||||
defer cols_iter.deinit();
|
||||
|
||||
while (try cols_iter.next()) |col_key| {
|
||||
@@ -554,10 +554,11 @@ pub const TablePrinter = struct {
|
||||
}.callback);
|
||||
if (ctx_.err) return error.JSError;
|
||||
} else {
|
||||
const tabular_obj = try this.tabular_data.toObject(globalObject);
|
||||
var rows_iter = try JSC.JSPropertyIterator(.{
|
||||
.skip_empty_name = false,
|
||||
.include_value = true,
|
||||
}).init(globalObject, this.tabular_data);
|
||||
}).init(globalObject, tabular_obj);
|
||||
defer rows_iter.deinit();
|
||||
|
||||
while (try rows_iter.next()) |row_key| {
|
||||
@@ -627,10 +628,13 @@ pub const TablePrinter = struct {
|
||||
}.callback);
|
||||
if (ctx_.err) return error.JSError;
|
||||
} else {
|
||||
const cell = this.tabular_data.toCell() orelse {
|
||||
return globalObject.throwTypeError("tabular_data must be an object or array", .{});
|
||||
};
|
||||
var rows_iter = try JSC.JSPropertyIterator(.{
|
||||
.skip_empty_name = false,
|
||||
.include_value = true,
|
||||
}).init(globalObject, this.tabular_data);
|
||||
}).init(globalObject, cell.toObject(globalObject));
|
||||
defer rows_iter.deinit();
|
||||
|
||||
while (try rows_iter.next()) |row_key| {
|
||||
@@ -3077,14 +3081,15 @@ pub const Formatter = struct {
|
||||
|
||||
if (value.get_unsafe(this.globalThis, "props")) |props| {
|
||||
const prev_quote_strings = this.quote_strings;
|
||||
this.quote_strings = true;
|
||||
defer this.quote_strings = prev_quote_strings;
|
||||
this.quote_strings = true;
|
||||
|
||||
// SAFETY: JSX props are always objects
|
||||
const props_obj = props.getObject().?;
|
||||
var props_iter = try JSC.JSPropertyIterator(.{
|
||||
.skip_empty_name = true,
|
||||
|
||||
.include_value = true,
|
||||
}).init(this.globalThis, props);
|
||||
}).init(this.globalThis, props_obj);
|
||||
defer props_iter.deinit();
|
||||
|
||||
const children_prop = props.get_unsafe(this.globalThis, "children");
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
638
src/bun.js/api/FFIObject.zig
Normal file
638
src/bun.js/api/FFIObject.zig
Normal file
@@ -0,0 +1,638 @@
|
||||
pub fn newCString(globalThis: *JSGlobalObject, value: JSValue, byteOffset: ?JSValue, lengthValue: ?JSValue) JSC.JSValue {
|
||||
switch (FFIObject.getPtrSlice(globalThis, value, byteOffset, lengthValue)) {
|
||||
.err => |err| {
|
||||
return err;
|
||||
},
|
||||
.slice => |slice| {
|
||||
return bun.String.createUTF8ForJS(globalThis, slice);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub const dom_call = JSC.DOMCall("FFI", @This(), "ptr", JSC.DOMEffect.forRead(.TypedArrayProperties));
|
||||
|
||||
pub fn toJS(globalObject: *JSC.JSGlobalObject) JSC.JSValue {
|
||||
const object = JSC.JSValue.createEmptyObject(globalObject, comptime std.meta.fieldNames(@TypeOf(fields)).len + 2);
|
||||
inline for (comptime std.meta.fieldNames(@TypeOf(fields))) |field| {
|
||||
object.put(
|
||||
globalObject,
|
||||
comptime ZigString.static(field),
|
||||
JSC.createCallback(globalObject, comptime ZigString.static(field), 1, comptime @field(fields, field)),
|
||||
);
|
||||
}
|
||||
|
||||
dom_call.put(globalObject, object);
|
||||
object.put(globalObject, ZigString.static("read"), Reader.toJS(globalObject));
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
pub const Reader = struct {
|
||||
pub const DOMCalls = .{
|
||||
.u8 = JSC.DOMCall("Reader", @This(), "u8", JSC.DOMEffect.forRead(.World)),
|
||||
.u16 = JSC.DOMCall("Reader", @This(), "u16", JSC.DOMEffect.forRead(.World)),
|
||||
.u32 = JSC.DOMCall("Reader", @This(), "u32", JSC.DOMEffect.forRead(.World)),
|
||||
.ptr = JSC.DOMCall("Reader", @This(), "ptr", JSC.DOMEffect.forRead(.World)),
|
||||
.i8 = JSC.DOMCall("Reader", @This(), "i8", JSC.DOMEffect.forRead(.World)),
|
||||
.i16 = JSC.DOMCall("Reader", @This(), "i16", JSC.DOMEffect.forRead(.World)),
|
||||
.i32 = JSC.DOMCall("Reader", @This(), "i32", JSC.DOMEffect.forRead(.World)),
|
||||
.i64 = JSC.DOMCall("Reader", @This(), "i64", JSC.DOMEffect.forRead(.World)),
|
||||
.u64 = JSC.DOMCall("Reader", @This(), "u64", JSC.DOMEffect.forRead(.World)),
|
||||
.intptr = JSC.DOMCall("Reader", @This(), "intptr", JSC.DOMEffect.forRead(.World)),
|
||||
.f32 = JSC.DOMCall("Reader", @This(), "f32", JSC.DOMEffect.forRead(.World)),
|
||||
.f64 = JSC.DOMCall("Reader", @This(), "f64", JSC.DOMEffect.forRead(.World)),
|
||||
};
|
||||
|
||||
pub fn toJS(globalThis: *JSC.JSGlobalObject) JSC.JSValue {
|
||||
const obj = JSC.JSValue.createEmptyObject(globalThis, std.meta.fieldNames(@TypeOf(Reader.DOMCalls)).len);
|
||||
|
||||
inline for (comptime std.meta.fieldNames(@TypeOf(Reader.DOMCalls))) |field| {
|
||||
@field(Reader.DOMCalls, field).put(globalThis, obj);
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
pub fn @"u8"(
|
||||
globalObject: *JSGlobalObject,
|
||||
_: JSValue,
|
||||
arguments: []const JSValue,
|
||||
) bun.JSError!JSValue {
|
||||
if (arguments.len == 0 or !arguments[0].isNumber()) {
|
||||
return globalObject.throwInvalidArguments("Expected a pointer", .{});
|
||||
}
|
||||
const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @as(usize, @intCast(arguments[1].to(i32))) else @as(usize, 0);
|
||||
const value = @as(*align(1) u8, @ptrFromInt(addr)).*;
|
||||
return JSValue.jsNumber(value);
|
||||
}
|
||||
pub fn @"u16"(
|
||||
globalObject: *JSGlobalObject,
|
||||
_: JSValue,
|
||||
arguments: []const JSValue,
|
||||
) bun.JSError!JSValue {
|
||||
if (arguments.len == 0 or !arguments[0].isNumber()) {
|
||||
return globalObject.throwInvalidArguments("Expected a pointer", .{});
|
||||
}
|
||||
const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @as(usize, @intCast(arguments[1].to(i32))) else @as(usize, 0);
|
||||
const value = @as(*align(1) u16, @ptrFromInt(addr)).*;
|
||||
return JSValue.jsNumber(value);
|
||||
}
|
||||
pub fn @"u32"(
|
||||
globalObject: *JSGlobalObject,
|
||||
_: JSValue,
|
||||
arguments: []const JSValue,
|
||||
) bun.JSError!JSValue {
|
||||
if (arguments.len == 0 or !arguments[0].isNumber()) {
|
||||
return globalObject.throwInvalidArguments("Expected a pointer", .{});
|
||||
}
|
||||
const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @as(usize, @intCast(arguments[1].to(i32))) else @as(usize, 0);
|
||||
const value = @as(*align(1) u32, @ptrFromInt(addr)).*;
|
||||
return JSValue.jsNumber(value);
|
||||
}
|
||||
pub fn ptr(
|
||||
globalObject: *JSGlobalObject,
|
||||
_: JSValue,
|
||||
arguments: []const JSValue,
|
||||
) bun.JSError!JSValue {
|
||||
if (arguments.len == 0 or !arguments[0].isNumber()) {
|
||||
return globalObject.throwInvalidArguments("Expected a pointer", .{});
|
||||
}
|
||||
const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @as(usize, @intCast(arguments[1].to(i32))) else @as(usize, 0);
|
||||
const value = @as(*align(1) u64, @ptrFromInt(addr)).*;
|
||||
return JSValue.jsNumber(value);
|
||||
}
|
||||
pub fn @"i8"(
|
||||
globalObject: *JSGlobalObject,
|
||||
_: JSValue,
|
||||
arguments: []const JSValue,
|
||||
) bun.JSError!JSValue {
|
||||
if (arguments.len == 0 or !arguments[0].isNumber()) {
|
||||
return globalObject.throwInvalidArguments("Expected a pointer", .{});
|
||||
}
|
||||
const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @as(usize, @intCast(arguments[1].to(i32))) else @as(usize, 0);
|
||||
const value = @as(*align(1) i8, @ptrFromInt(addr)).*;
|
||||
return JSValue.jsNumber(value);
|
||||
}
|
||||
pub fn @"i16"(
|
||||
globalObject: *JSGlobalObject,
|
||||
_: JSValue,
|
||||
arguments: []const JSValue,
|
||||
) bun.JSError!JSValue {
|
||||
if (arguments.len == 0 or !arguments[0].isNumber()) {
|
||||
return globalObject.throwInvalidArguments("Expected a pointer", .{});
|
||||
}
|
||||
const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @as(usize, @intCast(arguments[1].to(i32))) else @as(usize, 0);
|
||||
const value = @as(*align(1) i16, @ptrFromInt(addr)).*;
|
||||
return JSValue.jsNumber(value);
|
||||
}
|
||||
pub fn @"i32"(
|
||||
globalObject: *JSGlobalObject,
|
||||
_: JSValue,
|
||||
arguments: []const JSValue,
|
||||
) bun.JSError!JSValue {
|
||||
if (arguments.len == 0 or !arguments[0].isNumber()) {
|
||||
return globalObject.throwInvalidArguments("Expected a pointer", .{});
|
||||
}
|
||||
const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @as(usize, @intCast(arguments[1].to(i32))) else @as(usize, 0);
|
||||
const value = @as(*align(1) i32, @ptrFromInt(addr)).*;
|
||||
return JSValue.jsNumber(value);
|
||||
}
|
||||
pub fn intptr(
|
||||
globalObject: *JSGlobalObject,
|
||||
_: JSValue,
|
||||
arguments: []const JSValue,
|
||||
) bun.JSError!JSValue {
|
||||
if (arguments.len == 0 or !arguments[0].isNumber()) {
|
||||
return globalObject.throwInvalidArguments("Expected a pointer", .{});
|
||||
}
|
||||
const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @as(usize, @intCast(arguments[1].to(i32))) else @as(usize, 0);
|
||||
const value = @as(*align(1) i64, @ptrFromInt(addr)).*;
|
||||
return JSValue.jsNumber(value);
|
||||
}
|
||||
|
||||
pub fn @"f32"(
|
||||
globalObject: *JSGlobalObject,
|
||||
_: JSValue,
|
||||
arguments: []const JSValue,
|
||||
) bun.JSError!JSValue {
|
||||
if (arguments.len == 0 or !arguments[0].isNumber()) {
|
||||
return globalObject.throwInvalidArguments("Expected a pointer", .{});
|
||||
}
|
||||
const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @as(usize, @intCast(arguments[1].to(i32))) else @as(usize, 0);
|
||||
const value = @as(*align(1) f32, @ptrFromInt(addr)).*;
|
||||
return JSValue.jsNumber(value);
|
||||
}
|
||||
|
||||
pub fn @"f64"(
|
||||
globalObject: *JSGlobalObject,
|
||||
_: JSValue,
|
||||
arguments: []const JSValue,
|
||||
) bun.JSError!JSValue {
|
||||
if (arguments.len == 0 or !arguments[0].isNumber()) {
|
||||
return globalObject.throwInvalidArguments("Expected a pointer", .{});
|
||||
}
|
||||
const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @as(usize, @intCast(arguments[1].to(i32))) else @as(usize, 0);
|
||||
const value = @as(*align(1) f64, @ptrFromInt(addr)).*;
|
||||
return JSValue.jsNumber(value);
|
||||
}
|
||||
|
||||
pub fn @"i64"(
|
||||
globalObject: *JSGlobalObject,
|
||||
_: JSValue,
|
||||
arguments: []const JSValue,
|
||||
) bun.JSError!JSValue {
|
||||
if (arguments.len == 0 or !arguments[0].isNumber()) {
|
||||
return globalObject.throwInvalidArguments("Expected a pointer", .{});
|
||||
}
|
||||
const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @as(usize, @intCast(arguments[1].to(i32))) else @as(usize, 0);
|
||||
const value = @as(*align(1) i64, @ptrFromInt(addr)).*;
|
||||
return JSValue.fromInt64NoTruncate(globalObject, value);
|
||||
}
|
||||
|
||||
pub fn @"u64"(
|
||||
globalObject: *JSGlobalObject,
|
||||
_: JSValue,
|
||||
arguments: []const JSValue,
|
||||
) bun.JSError!JSValue {
|
||||
if (arguments.len == 0 or !arguments[0].isNumber()) {
|
||||
return globalObject.throwInvalidArguments("Expected a pointer", .{});
|
||||
}
|
||||
const addr = arguments[0].asPtrAddress() + if (arguments.len > 1) @as(usize, @intCast(arguments[1].to(i32))) else @as(usize, 0);
|
||||
const value = @as(*align(1) u64, @ptrFromInt(addr)).*;
|
||||
return JSValue.fromUInt64NoTruncate(globalObject, value);
|
||||
}
|
||||
|
||||
pub fn u8WithoutTypeChecks(
|
||||
_: *JSGlobalObject,
|
||||
_: *anyopaque,
|
||||
raw_addr: i64,
|
||||
offset: i32,
|
||||
) callconv(JSC.conv) JSValue {
|
||||
const addr = @as(usize, @intCast(raw_addr)) + @as(usize, @intCast(offset));
|
||||
const value = @as(*align(1) u8, @ptrFromInt(addr)).*;
|
||||
return JSValue.jsNumber(value);
|
||||
}
|
||||
pub fn u16WithoutTypeChecks(
|
||||
_: *JSGlobalObject,
|
||||
_: *anyopaque,
|
||||
raw_addr: i64,
|
||||
offset: i32,
|
||||
) callconv(JSC.conv) JSValue {
|
||||
const addr = @as(usize, @intCast(raw_addr)) + @as(usize, @intCast(offset));
|
||||
const value = @as(*align(1) u16, @ptrFromInt(addr)).*;
|
||||
return JSValue.jsNumber(value);
|
||||
}
|
||||
pub fn u32WithoutTypeChecks(
|
||||
_: *JSGlobalObject,
|
||||
_: *anyopaque,
|
||||
raw_addr: i64,
|
||||
offset: i32,
|
||||
) callconv(JSC.conv) JSValue {
|
||||
const addr = @as(usize, @intCast(raw_addr)) + @as(usize, @intCast(offset));
|
||||
const value = @as(*align(1) u32, @ptrFromInt(addr)).*;
|
||||
return JSValue.jsNumber(value);
|
||||
}
|
||||
pub fn ptrWithoutTypeChecks(
|
||||
_: *JSGlobalObject,
|
||||
_: *anyopaque,
|
||||
raw_addr: i64,
|
||||
offset: i32,
|
||||
) callconv(JSC.conv) JSValue {
|
||||
const addr = @as(usize, @intCast(raw_addr)) + @as(usize, @intCast(offset));
|
||||
const value = @as(*align(1) u64, @ptrFromInt(addr)).*;
|
||||
return JSValue.jsNumber(value);
|
||||
}
|
||||
pub fn i8WithoutTypeChecks(
|
||||
_: *JSGlobalObject,
|
||||
_: *anyopaque,
|
||||
raw_addr: i64,
|
||||
offset: i32,
|
||||
) callconv(JSC.conv) JSValue {
|
||||
const addr = @as(usize, @intCast(raw_addr)) + @as(usize, @intCast(offset));
|
||||
const value = @as(*align(1) i8, @ptrFromInt(addr)).*;
|
||||
return JSValue.jsNumber(value);
|
||||
}
|
||||
pub fn i16WithoutTypeChecks(
|
||||
_: *JSGlobalObject,
|
||||
_: *anyopaque,
|
||||
raw_addr: i64,
|
||||
offset: i32,
|
||||
) callconv(JSC.conv) JSValue {
|
||||
const addr = @as(usize, @intCast(raw_addr)) + @as(usize, @intCast(offset));
|
||||
const value = @as(*align(1) i16, @ptrFromInt(addr)).*;
|
||||
return JSValue.jsNumber(value);
|
||||
}
|
||||
pub fn i32WithoutTypeChecks(
|
||||
_: *JSGlobalObject,
|
||||
_: *anyopaque,
|
||||
raw_addr: i64,
|
||||
offset: i32,
|
||||
) callconv(JSC.conv) JSValue {
|
||||
const addr = @as(usize, @intCast(raw_addr)) + @as(usize, @intCast(offset));
|
||||
const value = @as(*align(1) i32, @ptrFromInt(addr)).*;
|
||||
return JSValue.jsNumber(value);
|
||||
}
|
||||
pub fn intptrWithoutTypeChecks(
|
||||
_: *JSGlobalObject,
|
||||
_: *anyopaque,
|
||||
raw_addr: i64,
|
||||
offset: i32,
|
||||
) callconv(JSC.conv) JSValue {
|
||||
const addr = @as(usize, @intCast(raw_addr)) + @as(usize, @intCast(offset));
|
||||
const value = @as(*align(1) i64, @ptrFromInt(addr)).*;
|
||||
return JSValue.jsNumber(value);
|
||||
}
|
||||
|
||||
pub fn f32WithoutTypeChecks(
|
||||
_: *JSGlobalObject,
|
||||
_: *anyopaque,
|
||||
raw_addr: i64,
|
||||
offset: i32,
|
||||
) callconv(JSC.conv) JSValue {
|
||||
const addr = @as(usize, @intCast(raw_addr)) + @as(usize, @intCast(offset));
|
||||
const value = @as(*align(1) f32, @ptrFromInt(addr)).*;
|
||||
return JSValue.jsNumber(value);
|
||||
}
|
||||
|
||||
pub fn f64WithoutTypeChecks(
|
||||
_: *JSGlobalObject,
|
||||
_: *anyopaque,
|
||||
raw_addr: i64,
|
||||
offset: i32,
|
||||
) callconv(JSC.conv) JSValue {
|
||||
const addr = @as(usize, @intCast(raw_addr)) + @as(usize, @intCast(offset));
|
||||
const value = @as(*align(1) f64, @ptrFromInt(addr)).*;
|
||||
return JSValue.jsNumber(value);
|
||||
}
|
||||
|
||||
pub fn u64WithoutTypeChecks(
|
||||
global: *JSGlobalObject,
|
||||
_: *anyopaque,
|
||||
raw_addr: i64,
|
||||
offset: i32,
|
||||
) callconv(JSC.conv) JSValue {
|
||||
const addr = @as(usize, @intCast(raw_addr)) + @as(usize, @intCast(offset));
|
||||
const value = @as(*align(1) u64, @ptrFromInt(addr)).*;
|
||||
return JSValue.fromUInt64NoTruncate(global, value);
|
||||
}
|
||||
|
||||
pub fn i64WithoutTypeChecks(
|
||||
global: *JSGlobalObject,
|
||||
_: *anyopaque,
|
||||
raw_addr: i64,
|
||||
offset: i32,
|
||||
) callconv(JSC.conv) JSValue {
|
||||
const addr = @as(usize, @intCast(raw_addr)) + @as(usize, @intCast(offset));
|
||||
const value = @as(*align(1) i64, @ptrFromInt(addr)).*;
|
||||
return JSValue.fromInt64NoTruncate(global, value);
|
||||
}
|
||||
};
|
||||
|
||||
pub fn ptr(
|
||||
globalThis: *JSGlobalObject,
|
||||
_: JSValue,
|
||||
arguments: []const JSValue,
|
||||
) JSValue {
|
||||
return switch (arguments.len) {
|
||||
0 => ptr_(globalThis, JSValue.zero, null),
|
||||
1 => ptr_(globalThis, arguments[0], null),
|
||||
else => ptr_(globalThis, arguments[0], arguments[1]),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn ptrWithoutTypeChecks(
|
||||
_: *JSGlobalObject,
|
||||
_: *anyopaque,
|
||||
array: *JSC.JSUint8Array,
|
||||
) callconv(JSC.conv) JSValue {
|
||||
return JSValue.fromPtrAddress(@intFromPtr(array.ptr()));
|
||||
}
|
||||
|
||||
fn ptr_(
|
||||
globalThis: *JSGlobalObject,
|
||||
value: JSValue,
|
||||
byteOffset: ?JSValue,
|
||||
) JSValue {
|
||||
if (value == .zero) {
|
||||
return JSC.JSValue.jsNull();
|
||||
}
|
||||
|
||||
const array_buffer = value.asArrayBuffer(globalThis) orelse {
|
||||
return JSC.toInvalidArguments("Expected ArrayBufferView but received {s}", .{@tagName(value.jsType())}, globalThis);
|
||||
};
|
||||
|
||||
if (array_buffer.len == 0) {
|
||||
return JSC.toInvalidArguments("ArrayBufferView must have a length > 0. A pointer to empty memory doesn't work", .{}, globalThis);
|
||||
}
|
||||
|
||||
var addr: usize = @intFromPtr(array_buffer.ptr);
|
||||
// const Sizes = @import("../bindings/sizes.zig");
|
||||
// assert(addr == @intFromPtr(value.asEncoded().ptr) + Sizes.Bun_FFI_PointerOffsetToTypedArrayVector);
|
||||
|
||||
if (byteOffset) |off| {
|
||||
if (!off.isEmptyOrUndefinedOrNull()) {
|
||||
if (!off.isNumber()) {
|
||||
return JSC.toInvalidArguments("Expected number for byteOffset", .{}, globalThis);
|
||||
}
|
||||
}
|
||||
|
||||
const bytei64 = off.toInt64();
|
||||
if (bytei64 < 0) {
|
||||
addr -|= @as(usize, @intCast(bytei64 * -1));
|
||||
} else {
|
||||
addr += @as(usize, @intCast(bytei64));
|
||||
}
|
||||
|
||||
if (addr > @intFromPtr(array_buffer.ptr) + @as(usize, array_buffer.byte_len)) {
|
||||
return JSC.toInvalidArguments("byteOffset out of bounds", .{}, globalThis);
|
||||
}
|
||||
}
|
||||
|
||||
if (addr > max_addressable_memory) {
|
||||
return JSC.toInvalidArguments("Pointer is outside max addressible memory, which usually means a bug in your program.", .{}, globalThis);
|
||||
}
|
||||
|
||||
if (addr == 0) {
|
||||
return JSC.toInvalidArguments("Pointer must not be 0", .{}, globalThis);
|
||||
}
|
||||
|
||||
if (addr == 0xDEADBEEF or addr == 0xaaaaaaaa or addr == 0xAAAAAAAA) {
|
||||
return JSC.toInvalidArguments("ptr to invalid memory, that would segfault Bun :(", .{}, globalThis);
|
||||
}
|
||||
|
||||
if (comptime Environment.allow_assert) {
|
||||
assert(JSC.JSValue.fromPtrAddress(addr).asPtrAddress() == addr);
|
||||
}
|
||||
|
||||
return JSC.JSValue.fromPtrAddress(addr);
|
||||
}
|
||||
|
||||
const ValueOrError = union(enum) {
|
||||
err: JSValue,
|
||||
slice: []u8,
|
||||
};
|
||||
|
||||
pub fn getPtrSlice(globalThis: *JSGlobalObject, value: JSValue, byteOffset: ?JSValue, byteLength: ?JSValue) ValueOrError {
|
||||
if (!value.isNumber()) {
|
||||
return .{ .err = JSC.toInvalidArguments("ptr must be a number.", .{}, globalThis) };
|
||||
}
|
||||
|
||||
const num = value.asPtrAddress();
|
||||
if (num == 0) {
|
||||
return .{ .err = JSC.toInvalidArguments("ptr cannot be zero, that would segfault Bun :(", .{}, globalThis) };
|
||||
}
|
||||
|
||||
// if (!std.math.isFinite(num)) {
|
||||
// return .{ .err = JSC.toInvalidArguments("ptr must be a finite number.", .{}, globalThis) };
|
||||
// }
|
||||
|
||||
var addr = @as(usize, @bitCast(num));
|
||||
|
||||
if (byteOffset) |byte_off| {
|
||||
if (byte_off.isNumber()) {
|
||||
const off = byte_off.toInt64();
|
||||
if (off < 0) {
|
||||
addr -|= @as(usize, @intCast(off * -1));
|
||||
} else {
|
||||
addr +|= @as(usize, @intCast(off));
|
||||
}
|
||||
|
||||
if (addr == 0) {
|
||||
return .{ .err = JSC.toInvalidArguments("ptr cannot be zero, that would segfault Bun :(", .{}, globalThis) };
|
||||
}
|
||||
|
||||
if (!std.math.isFinite(byte_off.asNumber())) {
|
||||
return .{ .err = JSC.toInvalidArguments("ptr must be a finite number.", .{}, globalThis) };
|
||||
}
|
||||
} else if (!byte_off.isEmptyOrUndefinedOrNull()) {
|
||||
// do nothing
|
||||
} else {
|
||||
return .{ .err = JSC.toInvalidArguments("Expected number for byteOffset", .{}, globalThis) };
|
||||
}
|
||||
}
|
||||
|
||||
if (addr == 0xDEADBEEF or addr == 0xaaaaaaaa or addr == 0xAAAAAAAA) {
|
||||
return .{ .err = JSC.toInvalidArguments("ptr to invalid memory, that would segfault Bun :(", .{}, globalThis) };
|
||||
}
|
||||
|
||||
if (byteLength) |valueLength| {
|
||||
if (!valueLength.isEmptyOrUndefinedOrNull()) {
|
||||
if (!valueLength.isNumber()) {
|
||||
return .{ .err = JSC.toInvalidArguments("length must be a number.", .{}, globalThis) };
|
||||
}
|
||||
|
||||
if (valueLength.asNumber() == 0.0) {
|
||||
return .{ .err = JSC.toInvalidArguments("length must be > 0. This usually means a bug in your code.", .{}, globalThis) };
|
||||
}
|
||||
|
||||
const length_i = valueLength.toInt64();
|
||||
if (length_i < 0) {
|
||||
return .{ .err = JSC.toInvalidArguments("length must be > 0. This usually means a bug in your code.", .{}, globalThis) };
|
||||
}
|
||||
|
||||
if (length_i > max_addressable_memory) {
|
||||
return .{ .err = JSC.toInvalidArguments("length exceeds max addressable memory. This usually means a bug in your code.", .{}, globalThis) };
|
||||
}
|
||||
|
||||
const length = @as(usize, @intCast(length_i));
|
||||
return .{ .slice = @as([*]u8, @ptrFromInt(addr))[0..length] };
|
||||
}
|
||||
}
|
||||
|
||||
return .{ .slice = bun.span(@as([*:0]u8, @ptrFromInt(addr))) };
|
||||
}
|
||||
|
||||
fn getCPtr(value: JSValue) ?usize {
|
||||
// pointer to C function
|
||||
if (value.isNumber()) {
|
||||
const addr = value.asPtrAddress();
|
||||
if (addr > 0) return addr;
|
||||
} else if (value.isBigInt()) {
|
||||
const addr = @as(u64, @bitCast(value.toUInt64NoTruncate()));
|
||||
if (addr > 0) {
|
||||
return addr;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn toArrayBuffer(
|
||||
globalThis: *JSGlobalObject,
|
||||
value: JSValue,
|
||||
byteOffset: ?JSValue,
|
||||
valueLength: ?JSValue,
|
||||
finalizationCtxOrPtr: ?JSValue,
|
||||
finalizationCallback: ?JSValue,
|
||||
) JSC.JSValue {
|
||||
switch (getPtrSlice(globalThis, value, byteOffset, valueLength)) {
|
||||
.err => |erro| {
|
||||
return erro;
|
||||
},
|
||||
.slice => |slice| {
|
||||
var callback: JSC.C.JSTypedArrayBytesDeallocator = null;
|
||||
var ctx: ?*anyopaque = null;
|
||||
if (finalizationCallback) |callback_value| {
|
||||
if (getCPtr(callback_value)) |callback_ptr| {
|
||||
callback = @as(JSC.C.JSTypedArrayBytesDeallocator, @ptrFromInt(callback_ptr));
|
||||
|
||||
if (finalizationCtxOrPtr) |ctx_value| {
|
||||
if (getCPtr(ctx_value)) |ctx_ptr| {
|
||||
ctx = @as(*anyopaque, @ptrFromInt(ctx_ptr));
|
||||
} else if (!ctx_value.isUndefinedOrNull()) {
|
||||
return JSC.toInvalidArguments("Expected user data to be a C pointer (number or BigInt)", .{}, globalThis);
|
||||
}
|
||||
}
|
||||
} else if (!callback_value.isEmptyOrUndefinedOrNull()) {
|
||||
return JSC.toInvalidArguments("Expected callback to be a C pointer (number or BigInt)", .{}, globalThis);
|
||||
}
|
||||
} else if (finalizationCtxOrPtr) |callback_value| {
|
||||
if (getCPtr(callback_value)) |callback_ptr| {
|
||||
callback = @as(JSC.C.JSTypedArrayBytesDeallocator, @ptrFromInt(callback_ptr));
|
||||
} else if (!callback_value.isEmptyOrUndefinedOrNull()) {
|
||||
return JSC.toInvalidArguments("Expected callback to be a C pointer (number or BigInt)", .{}, globalThis);
|
||||
}
|
||||
}
|
||||
|
||||
return JSC.ArrayBuffer.fromBytes(slice, JSC.JSValue.JSType.ArrayBuffer).toJSWithContext(globalThis, ctx, callback, null);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toBuffer(
|
||||
globalThis: *JSGlobalObject,
|
||||
value: JSValue,
|
||||
byteOffset: ?JSValue,
|
||||
valueLength: ?JSValue,
|
||||
finalizationCtxOrPtr: ?JSValue,
|
||||
finalizationCallback: ?JSValue,
|
||||
) JSC.JSValue {
|
||||
switch (getPtrSlice(globalThis, value, byteOffset, valueLength)) {
|
||||
.err => |err| {
|
||||
return err;
|
||||
},
|
||||
.slice => |slice| {
|
||||
var callback: JSC.C.JSTypedArrayBytesDeallocator = null;
|
||||
var ctx: ?*anyopaque = null;
|
||||
if (finalizationCallback) |callback_value| {
|
||||
if (getCPtr(callback_value)) |callback_ptr| {
|
||||
callback = @as(JSC.C.JSTypedArrayBytesDeallocator, @ptrFromInt(callback_ptr));
|
||||
|
||||
if (finalizationCtxOrPtr) |ctx_value| {
|
||||
if (getCPtr(ctx_value)) |ctx_ptr| {
|
||||
ctx = @as(*anyopaque, @ptrFromInt(ctx_ptr));
|
||||
} else if (!ctx_value.isEmptyOrUndefinedOrNull()) {
|
||||
return JSC.toInvalidArguments("Expected user data to be a C pointer (number or BigInt)", .{}, globalThis);
|
||||
}
|
||||
}
|
||||
} else if (!callback_value.isEmptyOrUndefinedOrNull()) {
|
||||
return JSC.toInvalidArguments("Expected callback to be a C pointer (number or BigInt)", .{}, globalThis);
|
||||
}
|
||||
} else if (finalizationCtxOrPtr) |callback_value| {
|
||||
if (getCPtr(callback_value)) |callback_ptr| {
|
||||
callback = @as(JSC.C.JSTypedArrayBytesDeallocator, @ptrFromInt(callback_ptr));
|
||||
} else if (!callback_value.isEmptyOrUndefinedOrNull()) {
|
||||
return JSC.toInvalidArguments("Expected callback to be a C pointer (number or BigInt)", .{}, globalThis);
|
||||
}
|
||||
}
|
||||
|
||||
if (callback != null or ctx != null) {
|
||||
return JSC.JSValue.createBufferWithCtx(globalThis, slice, ctx, callback);
|
||||
}
|
||||
|
||||
return JSC.JSValue.createBuffer(globalThis, slice, null);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toCStringBuffer(
|
||||
globalThis: *JSGlobalObject,
|
||||
value: JSValue,
|
||||
byteOffset: ?JSValue,
|
||||
valueLength: ?JSValue,
|
||||
) JSC.JSValue {
|
||||
switch (getPtrSlice(globalThis, value, byteOffset, valueLength)) {
|
||||
.err => |err| {
|
||||
return err;
|
||||
},
|
||||
.slice => |slice| {
|
||||
return JSC.JSValue.createBuffer(globalThis, slice, null);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getter(
|
||||
globalObject: *JSC.JSGlobalObject,
|
||||
_: *JSC.JSObject,
|
||||
) JSC.JSValue {
|
||||
return FFIObject.toJS(globalObject);
|
||||
}
|
||||
|
||||
const fields = .{
|
||||
.viewSource = JSC.wrapStaticMethod(
|
||||
JSC.FFI,
|
||||
"print",
|
||||
false,
|
||||
),
|
||||
.dlopen = JSC.wrapStaticMethod(JSC.FFI, "open", false),
|
||||
.callback = JSC.wrapStaticMethod(JSC.FFI, "callback", false),
|
||||
.linkSymbols = JSC.wrapStaticMethod(JSC.FFI, "linkSymbols", false),
|
||||
.toBuffer = JSC.wrapStaticMethod(@This(), "toBuffer", false),
|
||||
.toArrayBuffer = JSC.wrapStaticMethod(@This(), "toArrayBuffer", false),
|
||||
.closeCallback = JSC.wrapStaticMethod(JSC.FFI, "closeCallback", false),
|
||||
.CString = JSC.wrapStaticMethod(Bun.FFIObject, "newCString", false),
|
||||
};
|
||||
const max_addressable_memory = std.math.maxInt(u56);
|
||||
|
||||
const JSGlobalObject = JSC.JSGlobalObject;
|
||||
const JSObject = JSC.JSObject;
|
||||
const JSValue = JSC.JSValue;
|
||||
const JSC = bun.JSC;
|
||||
const bun = @import("root").bun;
|
||||
const FFIObject = @This();
|
||||
const Bun = JSC.API.Bun;
|
||||
|
||||
const Environment = bun.Environment;
|
||||
const std = @import("std");
|
||||
const assert = bun.assert;
|
||||
const ZigString = JSC.ZigString;
|
||||
144
src/bun.js/api/HashObject.zig
Normal file
144
src/bun.js/api/HashObject.zig
Normal file
@@ -0,0 +1,144 @@
|
||||
pub const wyhash = hashWrap(std.hash.Wyhash);
|
||||
pub const adler32 = hashWrap(std.hash.Adler32);
|
||||
pub const crc32 = hashWrap(std.hash.Crc32);
|
||||
pub const cityHash32 = hashWrap(std.hash.CityHash32);
|
||||
pub const cityHash64 = hashWrap(std.hash.CityHash64);
|
||||
pub const xxHash32 = hashWrap(struct {
|
||||
pub fn hash(seed: u32, bytes: []const u8) u32 {
|
||||
// sidestep .hash taking in anytype breaking ArgTuple
|
||||
// downstream by forcing a type signature on the input
|
||||
return std.hash.XxHash32.hash(seed, bytes);
|
||||
}
|
||||
});
|
||||
pub const xxHash64 = hashWrap(struct {
|
||||
pub fn hash(seed: u32, bytes: []const u8) u64 {
|
||||
// sidestep .hash taking in anytype breaking ArgTuple
|
||||
// downstream by forcing a type signature on the input
|
||||
return std.hash.XxHash64.hash(seed, bytes);
|
||||
}
|
||||
});
|
||||
pub const xxHash3 = hashWrap(struct {
|
||||
pub fn hash(seed: u32, bytes: []const u8) u64 {
|
||||
// sidestep .hash taking in anytype breaking ArgTuple
|
||||
// downstream by forcing a type signature on the input
|
||||
return std.hash.XxHash3.hash(seed, bytes);
|
||||
}
|
||||
});
|
||||
pub const murmur32v2 = hashWrap(std.hash.murmur.Murmur2_32);
|
||||
pub const murmur32v3 = hashWrap(std.hash.murmur.Murmur3_32);
|
||||
pub const murmur64v2 = hashWrap(std.hash.murmur.Murmur2_64);
|
||||
|
||||
pub fn create(globalThis: *JSC.JSGlobalObject) JSC.JSValue {
|
||||
const function = JSC.createCallback(globalThis, ZigString.static("hash"), 1, wyhash);
|
||||
const fns = comptime .{
|
||||
"wyhash",
|
||||
"adler32",
|
||||
"crc32",
|
||||
"cityHash32",
|
||||
"cityHash64",
|
||||
"xxHash32",
|
||||
"xxHash64",
|
||||
"xxHash3",
|
||||
"murmur32v2",
|
||||
"murmur32v3",
|
||||
"murmur64v2",
|
||||
};
|
||||
inline for (fns) |name| {
|
||||
const value = JSC.createCallback(
|
||||
globalThis,
|
||||
ZigString.static(name),
|
||||
1,
|
||||
@field(HashObject, name),
|
||||
);
|
||||
function.put(globalThis, comptime ZigString.static(name), value);
|
||||
}
|
||||
|
||||
return function;
|
||||
}
|
||||
|
||||
fn hashWrap(comptime Hasher_: anytype) JSC.JSHostZigFunction {
|
||||
return struct {
|
||||
const Hasher = Hasher_;
|
||||
pub fn hash(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
|
||||
const arguments = callframe.arguments_old(2).slice();
|
||||
var args = JSC.Node.ArgumentsSlice.init(globalThis.bunVM(), arguments);
|
||||
defer args.deinit();
|
||||
|
||||
var input: []const u8 = "";
|
||||
var input_slice = ZigString.Slice.empty;
|
||||
defer input_slice.deinit();
|
||||
if (args.nextEat()) |arg| {
|
||||
if (arg.as(JSC.WebCore.Blob)) |blob| {
|
||||
// TODO: files
|
||||
input = blob.sharedView();
|
||||
} else {
|
||||
switch (arg.jsTypeLoose()) {
|
||||
.ArrayBuffer,
|
||||
.Int8Array,
|
||||
.Uint8Array,
|
||||
.Uint8ClampedArray,
|
||||
.Int16Array,
|
||||
.Uint16Array,
|
||||
.Int32Array,
|
||||
.Uint32Array,
|
||||
.Float16Array,
|
||||
.Float32Array,
|
||||
.Float64Array,
|
||||
.BigInt64Array,
|
||||
.BigUint64Array,
|
||||
.DataView,
|
||||
=> {
|
||||
var array_buffer = arg.asArrayBuffer(globalThis) orelse {
|
||||
return globalThis.throwInvalidArguments("ArrayBuffer conversion error", .{});
|
||||
};
|
||||
input = array_buffer.byteSlice();
|
||||
},
|
||||
else => {
|
||||
input_slice = try arg.toSlice(globalThis, bun.default_allocator);
|
||||
input = input_slice.slice();
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// std.hash has inconsistent interfaces
|
||||
//
|
||||
const Function = if (@hasDecl(Hasher, "hashWithSeed")) Hasher.hashWithSeed else Hasher.hash;
|
||||
var function_args: std.meta.ArgsTuple(@TypeOf(Function)) = undefined;
|
||||
if (comptime std.meta.fields(std.meta.ArgsTuple(@TypeOf(Function))).len == 1) {
|
||||
return JSC.JSValue.jsNumber(Function(input));
|
||||
} else {
|
||||
var seed: u64 = 0;
|
||||
if (args.nextEat()) |arg| {
|
||||
if (arg.isNumber() or arg.isBigInt()) {
|
||||
seed = arg.toUInt64NoTruncate();
|
||||
}
|
||||
}
|
||||
if (comptime bun.trait.isNumber(@TypeOf(function_args[0]))) {
|
||||
function_args[0] = @as(@TypeOf(function_args[0]), @truncate(seed));
|
||||
function_args[1] = input;
|
||||
} else {
|
||||
function_args[0] = input;
|
||||
function_args[1] = @as(@TypeOf(function_args[1]), @truncate(seed));
|
||||
}
|
||||
|
||||
const value = @call(.auto, Function, function_args);
|
||||
|
||||
if (@TypeOf(value) == u32) {
|
||||
return JSC.JSValue.jsNumber(@as(u32, @bitCast(value)));
|
||||
}
|
||||
return JSC.JSValue.fromUInt64NoTruncate(globalThis, value);
|
||||
}
|
||||
}
|
||||
}.hash;
|
||||
}
|
||||
|
||||
const HashObject = @This();
|
||||
|
||||
const JSC = bun.JSC;
|
||||
const JSValue = JSC.JSValue;
|
||||
const JSGlobalObject = JSC.JSGlobalObject;
|
||||
const JSObject = JSC.JSObject;
|
||||
const std = @import("std");
|
||||
const bun = @import("root").bun;
|
||||
const ZigString = JSC.ZigString;
|
||||
@@ -426,10 +426,6 @@ pub const JSBundler = struct {
|
||||
}
|
||||
|
||||
if (try config.getOwnObject(globalThis, "define")) |define| {
|
||||
if (!define.isObject()) {
|
||||
return globalThis.throwInvalidArguments("define must be an object", .{});
|
||||
}
|
||||
|
||||
var define_iter = try JSC.JSPropertyIterator(.{
|
||||
.skip_empty_name = true,
|
||||
.include_value = true,
|
||||
|
||||
@@ -332,15 +332,15 @@ fn transformOptionsFromJSC(globalObject: JSC.C.JSContextRef, temp_allocator: std
|
||||
break :define;
|
||||
}
|
||||
|
||||
if (!define.isObject()) {
|
||||
const define_obj = define.getObject() orelse {
|
||||
return globalObject.throwInvalidArguments("define must be an object", .{});
|
||||
}
|
||||
};
|
||||
|
||||
var define_iter = try JSC.JSPropertyIterator(.{
|
||||
.skip_empty_name = true,
|
||||
|
||||
.include_value = true,
|
||||
}).init(globalThis, define);
|
||||
}).init(globalThis, define_obj);
|
||||
defer define_iter.deinit();
|
||||
|
||||
// cannot be a temporary because it may be loaded on different threads.
|
||||
@@ -616,14 +616,14 @@ fn transformOptionsFromJSC(globalObject: JSC.C.JSContextRef, temp_allocator: std
|
||||
}
|
||||
|
||||
if (try exports.getTruthy(globalThis, "replace")) |replace| {
|
||||
if (!replace.isObject()) {
|
||||
const replace_obj = replace.getObject() orelse {
|
||||
return globalObject.throwInvalidArguments("replace must be an object", .{});
|
||||
}
|
||||
};
|
||||
|
||||
var iter = try JSC.JSPropertyIterator(.{
|
||||
.skip_empty_name = true,
|
||||
.include_value = true,
|
||||
}).init(globalThis, replace);
|
||||
}).init(globalThis, replace_obj);
|
||||
defer iter.deinit();
|
||||
|
||||
if (iter.len > 0) {
|
||||
|
||||
72
src/bun.js/api/TOMLObject.zig
Normal file
72
src/bun.js/api/TOMLObject.zig
Normal file
@@ -0,0 +1,72 @@
|
||||
pub fn create(globalThis: *JSC.JSGlobalObject) JSC.JSValue {
|
||||
const object = JSValue.createEmptyObject(globalThis, 1);
|
||||
object.put(
|
||||
globalThis,
|
||||
ZigString.static("parse"),
|
||||
JSC.createCallback(
|
||||
globalThis,
|
||||
ZigString.static("parse"),
|
||||
1,
|
||||
parse,
|
||||
),
|
||||
);
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
pub fn parse(
|
||||
globalThis: *JSC.JSGlobalObject,
|
||||
callframe: *JSC.CallFrame,
|
||||
) bun.JSError!JSC.JSValue {
|
||||
var arena = bun.ArenaAllocator.init(globalThis.allocator());
|
||||
const allocator = arena.allocator();
|
||||
defer arena.deinit();
|
||||
var log = logger.Log.init(default_allocator);
|
||||
const arguments = callframe.arguments_old(1).slice();
|
||||
if (arguments.len == 0 or arguments[0].isEmptyOrUndefinedOrNull()) {
|
||||
return globalThis.throwInvalidArguments("Expected a string to parse", .{});
|
||||
}
|
||||
|
||||
var input_slice = try arguments[0].toSlice(globalThis, bun.default_allocator);
|
||||
defer input_slice.deinit();
|
||||
var source = logger.Source.initPathString("input.toml", input_slice.slice());
|
||||
const parse_result = TOMLParser.parse(&source, &log, allocator, false) catch {
|
||||
return globalThis.throwValue(log.toJS(globalThis, default_allocator, "Failed to parse toml"));
|
||||
};
|
||||
|
||||
// for now...
|
||||
const buffer_writer = js_printer.BufferWriter.init(allocator) catch {
|
||||
return globalThis.throwValue(log.toJS(globalThis, default_allocator, "Failed to print toml"));
|
||||
};
|
||||
var writer = js_printer.BufferPrinter.init(buffer_writer);
|
||||
_ = js_printer.printJSON(
|
||||
*js_printer.BufferPrinter,
|
||||
&writer,
|
||||
parse_result,
|
||||
&source,
|
||||
.{
|
||||
.mangled_props = null,
|
||||
},
|
||||
) catch {
|
||||
return globalThis.throwValue(log.toJS(globalThis, default_allocator, "Failed to print toml"));
|
||||
};
|
||||
|
||||
const slice = writer.ctx.buffer.slice();
|
||||
var out = bun.String.fromUTF8(slice);
|
||||
defer out.deref();
|
||||
|
||||
return out.toJSByParseJSON(globalThis);
|
||||
}
|
||||
|
||||
const TOMLObject = @This();
|
||||
const JSC = bun.JSC;
|
||||
const JSValue = JSC.JSValue;
|
||||
const JSGlobalObject = JSC.JSGlobalObject;
|
||||
const JSObject = JSC.JSObject;
|
||||
const std = @import("std");
|
||||
const ZigString = JSC.ZigString;
|
||||
const logger = bun.logger;
|
||||
const bun = @import("root").bun;
|
||||
const js_printer = bun.js_printer;
|
||||
const default_allocator = bun.default_allocator;
|
||||
const TOMLParser = @import("../../toml/toml_parser.zig").TOML;
|
||||
76
src/bun.js/api/UnsafeObject.zig
Normal file
76
src/bun.js/api/UnsafeObject.zig
Normal file
@@ -0,0 +1,76 @@
|
||||
pub fn create(globalThis: *JSC.JSGlobalObject) JSC.JSValue {
|
||||
const object = JSValue.createEmptyObject(globalThis, 3);
|
||||
const fields = comptime .{
|
||||
.gcAggressionLevel = gcAggressionLevel,
|
||||
.arrayBufferToString = arrayBufferToString,
|
||||
.mimallocDump = dump_mimalloc,
|
||||
};
|
||||
inline for (comptime std.meta.fieldNames(@TypeOf(fields))) |name| {
|
||||
object.put(
|
||||
globalThis,
|
||||
comptime ZigString.static(name),
|
||||
JSC.createCallback(globalThis, comptime ZigString.static(name), 1, comptime @field(fields, name)),
|
||||
);
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
pub fn gcAggressionLevel(
|
||||
globalThis: *JSC.JSGlobalObject,
|
||||
callframe: *JSC.CallFrame,
|
||||
) bun.JSError!JSC.JSValue {
|
||||
const ret = JSValue.jsNumber(@as(i32, @intFromEnum(globalThis.bunVM().aggressive_garbage_collection)));
|
||||
const value = callframe.arguments_old(1).ptr[0];
|
||||
|
||||
if (!value.isEmptyOrUndefinedOrNull()) {
|
||||
switch (value.coerce(i32, globalThis)) {
|
||||
1 => globalThis.bunVM().aggressive_garbage_collection = .mild,
|
||||
2 => globalThis.bunVM().aggressive_garbage_collection = .aggressive,
|
||||
0 => globalThis.bunVM().aggressive_garbage_collection = .none,
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
pub fn arrayBufferToString(
|
||||
globalThis: *JSC.JSGlobalObject,
|
||||
callframe: *JSC.CallFrame,
|
||||
) bun.JSError!JSC.JSValue {
|
||||
const args = callframe.arguments_old(2).slice();
|
||||
if (args.len < 1 or !args[0].isCell() or !args[0].jsType().isTypedArrayOrArrayBuffer()) {
|
||||
return globalThis.throwInvalidArguments("Expected an ArrayBuffer", .{});
|
||||
}
|
||||
|
||||
const array_buffer = JSC.ArrayBuffer.fromTypedArray(globalThis, args[0]);
|
||||
switch (array_buffer.typed_array_type) {
|
||||
.Uint16Array, .Int16Array => {
|
||||
var zig_str = ZigString.init("");
|
||||
zig_str._unsafe_ptr_do_not_use = @as([*]const u8, @ptrCast(@alignCast(array_buffer.ptr)));
|
||||
zig_str.len = array_buffer.len;
|
||||
zig_str.markUTF16();
|
||||
return zig_str.toJS(globalThis);
|
||||
},
|
||||
else => {
|
||||
return ZigString.init(array_buffer.slice()).toJS(globalThis);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
extern fn dump_zone_malloc_stats() void;
|
||||
|
||||
fn dump_mimalloc(globalObject: *JSC.JSGlobalObject, _: *JSC.CallFrame) bun.JSError!JSC.JSValue {
|
||||
globalObject.bunVM().arena.dumpStats();
|
||||
if (bun.heap_breakdown.enabled) {
|
||||
dump_zone_malloc_stats();
|
||||
}
|
||||
return .undefined;
|
||||
}
|
||||
|
||||
const JSC = bun.JSC;
|
||||
const JSValue = JSC.JSValue;
|
||||
const JSGlobalObject = JSC.JSGlobalObject;
|
||||
const JSObject = JSC.JSObject;
|
||||
const std = @import("std");
|
||||
const bun = @import("root").bun;
|
||||
const ZigString = JSC.ZigString;
|
||||
@@ -2501,7 +2501,7 @@ pub const DNSResolver = struct {
|
||||
return globalThis.throwNotEnoughArguments("resolve", 3, arguments.len);
|
||||
}
|
||||
|
||||
const record_type: RecordType = if (arguments.len == 1)
|
||||
const record_type: RecordType = if (arguments.len <= 1)
|
||||
RecordType.default
|
||||
else brk: {
|
||||
const record_type_value = arguments.ptr[1];
|
||||
@@ -2518,7 +2518,7 @@ pub const DNSResolver = struct {
|
||||
}
|
||||
|
||||
break :brk RecordType.map.getWithEql(record_type_str.getZigString(globalThis), JSC.ZigString.eqlComptime) orelse {
|
||||
return globalThis.throwInvalidArgumentType("resolve", "record", "one of: A, AAAA, CAA, CNAME, MX, NS, PTR, SOA, SRV, TXT");
|
||||
return globalThis.throwInvalidArgumentType("resolve", "record", "one of: A, AAAA, ANY, CAA, CNAME, MX, NS, PTR, SOA, SRV, TXT");
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -3248,9 +3248,9 @@ pub const H2FrameParser = struct {
|
||||
return globalObject.throw("Invalid stream id", .{});
|
||||
};
|
||||
|
||||
if (!headers_arg.isObject()) {
|
||||
const headers_obj = headers_arg.getObject() orelse {
|
||||
return globalObject.throw("Expected headers to be an object", .{});
|
||||
}
|
||||
};
|
||||
|
||||
if (!sensitive_arg.isObject()) {
|
||||
return globalObject.throw("Expected sensitiveHeaders to be an object", .{});
|
||||
@@ -3266,7 +3266,7 @@ pub const H2FrameParser = struct {
|
||||
var iter = try JSC.JSPropertyIterator(.{
|
||||
.skip_empty_name = false,
|
||||
.include_value = true,
|
||||
}).init(globalObject, headers_arg);
|
||||
}).init(globalObject, headers_obj);
|
||||
defer iter.deinit();
|
||||
|
||||
var single_value_headers: [SingleValueHeaders.keys().len]bool = undefined;
|
||||
@@ -3595,9 +3595,9 @@ pub const H2FrameParser = struct {
|
||||
const headers_arg = args_list.ptr[2];
|
||||
const sensitive_arg = args_list.ptr[3];
|
||||
|
||||
if (!headers_arg.isObject()) {
|
||||
const headers_obj = headers_arg.getObject() orelse {
|
||||
return globalObject.throw("Expected headers to be an object", .{});
|
||||
}
|
||||
};
|
||||
|
||||
if (!sensitive_arg.isObject()) {
|
||||
return globalObject.throw("Expected sensitiveHeaders to be an object", .{});
|
||||
@@ -3617,7 +3617,7 @@ pub const H2FrameParser = struct {
|
||||
var iter = try JSC.JSPropertyIterator(.{
|
||||
.skip_empty_name = false,
|
||||
.include_value = true,
|
||||
}).init(globalObject, headers_arg);
|
||||
}).init(globalObject, headers_obj);
|
||||
defer iter.deinit();
|
||||
var header_count: u32 = 0;
|
||||
|
||||
|
||||
@@ -159,7 +159,7 @@ pub const ResourceUsage = struct {
|
||||
}
|
||||
};
|
||||
|
||||
pub fn appendEnvpFromJS(globalThis: *JSC.JSGlobalObject, object: JSC.JSValue, envp: *std.ArrayList(?[*:0]const u8), PATH: *[]const u8) bun.JSError!void {
|
||||
pub fn appendEnvpFromJS(globalThis: *JSC.JSGlobalObject, object: *JSC.JSObject, envp: *std.ArrayList(?[*:0]const u8), PATH: *[]const u8) bun.JSError!void {
|
||||
var object_iter = try JSC.JSPropertyIterator(.{ .skip_empty_name = false, .include_value = true }).init(globalThis, object);
|
||||
defer object_iter.deinit();
|
||||
|
||||
@@ -2024,10 +2024,11 @@ pub fn spawnMaybeSync(
|
||||
onExit_.withAsyncContextIfNeeded(globalThis);
|
||||
}
|
||||
|
||||
if (try args.getTruthy(globalThis, "env")) |object| {
|
||||
if (!object.isObject()) {
|
||||
if (try args.getTruthy(globalThis, "env")) |env_arg| {
|
||||
env_arg.ensureStillAlive();
|
||||
const object = env_arg.getObject() orelse {
|
||||
return globalThis.throwInvalidArguments("env must be an object", .{});
|
||||
}
|
||||
};
|
||||
|
||||
override_env = true;
|
||||
// If the env object does not include a $PATH, it must disable path lookup for argv[0]
|
||||
|
||||
@@ -68,10 +68,6 @@ export default [
|
||||
},
|
||||
length: 2,
|
||||
},
|
||||
scryptSync: {
|
||||
fn: "doScryptSync",
|
||||
length: 2,
|
||||
},
|
||||
},
|
||||
klass: {},
|
||||
}),
|
||||
|
||||
32
src/bun.js/api/crypto.zig
Normal file
32
src/bun.js/api/crypto.zig
Normal file
@@ -0,0 +1,32 @@
|
||||
pub fn createCryptoError(globalThis: *JSC.JSGlobalObject, err_code: u32) JSValue {
|
||||
return bun.BoringSSL.ERR_toJS(globalThis, err_code);
|
||||
}
|
||||
|
||||
pub const PasswordObject = @import("./crypto/PasswordObject.zig").PasswordObject;
|
||||
pub const JSPasswordObject = @import("./crypto/PasswordObject.zig").JSPasswordObject;
|
||||
|
||||
pub const CryptoHasher = @import("./crypto/CryptoHasher.zig").CryptoHasher;
|
||||
pub const MD4 = @import("./crypto/CryptoHasher.zig").MD4;
|
||||
pub const MD5 = @import("./crypto/CryptoHasher.zig").MD5;
|
||||
pub const SHA1 = @import("./crypto/CryptoHasher.zig").SHA1;
|
||||
pub const SHA224 = @import("./crypto/CryptoHasher.zig").SHA224;
|
||||
pub const SHA256 = @import("./crypto/CryptoHasher.zig").SHA256;
|
||||
pub const SHA384 = @import("./crypto/CryptoHasher.zig").SHA384;
|
||||
pub const SHA512 = @import("./crypto/CryptoHasher.zig").SHA512;
|
||||
pub const SHA512_256 = @import("./crypto/CryptoHasher.zig").SHA512_256;
|
||||
pub const HMAC = @import("./crypto/HMAC.zig");
|
||||
pub const EVP = @import("./crypto/EVP.zig");
|
||||
|
||||
comptime {
|
||||
CryptoHasher.Extern.@"export"();
|
||||
}
|
||||
|
||||
const std = @import("std");
|
||||
const bun = @import("root").bun;
|
||||
const string = bun.string;
|
||||
const strings = bun.strings;
|
||||
const MutableString = bun.MutableString;
|
||||
const stringZ = bun.stringZ;
|
||||
const JSC = bun.JSC;
|
||||
const JSValue = JSC.JSValue;
|
||||
const JSGlobalObject = JSC.JSGlobalObject;
|
||||
898
src/bun.js/api/crypto/CryptoHasher.zig
Normal file
898
src/bun.js/api/crypto/CryptoHasher.zig
Normal file
@@ -0,0 +1,898 @@
|
||||
pub const CryptoHasher = union(enum) {
|
||||
// HMAC_CTX contains 3 EVP_CTX, so let's store it as a pointer.
|
||||
hmac: ?*HMAC,
|
||||
|
||||
evp: EVP,
|
||||
zig: CryptoHasherZig,
|
||||
|
||||
const Digest = EVP.Digest;
|
||||
|
||||
pub usingnamespace JSC.Codegen.JSCryptoHasher;
|
||||
usingnamespace bun.New(@This());
|
||||
|
||||
// For using only CryptoHasherZig in c++
|
||||
pub const Extern = struct {
|
||||
fn getByName(global: *JSGlobalObject, name_bytes: [*:0]const u8, name_len: usize) callconv(.C) ?*CryptoHasher {
|
||||
const name = name_bytes[0..name_len];
|
||||
|
||||
if (CryptoHasherZig.init(name)) |inner| {
|
||||
return CryptoHasher.new(.{
|
||||
.zig = inner,
|
||||
});
|
||||
}
|
||||
|
||||
const algorithm = EVP.Algorithm.map.get(name) orelse {
|
||||
return null;
|
||||
};
|
||||
|
||||
switch (algorithm) {
|
||||
.ripemd160,
|
||||
.blake2b256,
|
||||
.blake2b512,
|
||||
|
||||
.@"sha512-224",
|
||||
=> {
|
||||
if (algorithm.md()) |md| {
|
||||
return CryptoHasher.new(.{
|
||||
.evp = EVP.init(algorithm, md, global.bunVM().rareData().boringEngine()),
|
||||
});
|
||||
}
|
||||
},
|
||||
else => {
|
||||
return null;
|
||||
},
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
fn getFromOther(global: *JSGlobalObject, other_handle: *CryptoHasher) callconv(.C) ?*CryptoHasher {
|
||||
switch (other_handle.*) {
|
||||
.zig => |other| {
|
||||
const hasher = CryptoHasher.new(.{
|
||||
.zig = other.copy(),
|
||||
});
|
||||
return hasher;
|
||||
},
|
||||
.evp => |other| {
|
||||
return CryptoHasher.new(.{
|
||||
.evp = other.copy(global.bunVM().rareData().boringEngine()) catch {
|
||||
return null;
|
||||
},
|
||||
});
|
||||
},
|
||||
else => {
|
||||
return null;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn destroy(handle: *CryptoHasher) callconv(.C) void {
|
||||
handle.finalize();
|
||||
}
|
||||
|
||||
fn update(handle: *CryptoHasher, input_bytes: [*]const u8, input_len: usize) callconv(.C) bool {
|
||||
const input = input_bytes[0..input_len];
|
||||
|
||||
switch (handle.*) {
|
||||
.zig => {
|
||||
handle.zig.update(input);
|
||||
return true;
|
||||
},
|
||||
.evp => {
|
||||
handle.evp.update(input);
|
||||
return true;
|
||||
},
|
||||
else => {
|
||||
return false;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn digest(handle: *CryptoHasher, global: *JSGlobalObject, buf: [*]u8, buf_len: usize) callconv(.C) u32 {
|
||||
const digest_buf = buf[0..buf_len];
|
||||
switch (handle.*) {
|
||||
.zig => {
|
||||
const res = handle.zig.finalWithLen(digest_buf, buf_len);
|
||||
return @intCast(res.len);
|
||||
},
|
||||
.evp => {
|
||||
const res = handle.evp.final(global.bunVM().rareData().boringEngine(), digest_buf);
|
||||
return @intCast(res.len);
|
||||
},
|
||||
else => {
|
||||
return 0;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn getDigestSize(handle: *CryptoHasher) callconv(.C) u32 {
|
||||
return switch (handle.*) {
|
||||
.zig => |inner| inner.digest_length,
|
||||
.evp => |inner| inner.size(),
|
||||
else => 0,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn @"export"() void {
|
||||
@export(&CryptoHasher.Extern.getByName, .{ .name = "Bun__CryptoHasherExtern__getByName" });
|
||||
@export(&CryptoHasher.Extern.getFromOther, .{ .name = "Bun__CryptoHasherExtern__getFromOther" });
|
||||
@export(&CryptoHasher.Extern.destroy, .{ .name = "Bun__CryptoHasherExtern__destroy" });
|
||||
@export(&CryptoHasher.Extern.update, .{ .name = "Bun__CryptoHasherExtern__update" });
|
||||
@export(&CryptoHasher.Extern.digest, .{ .name = "Bun__CryptoHasherExtern__digest" });
|
||||
@export(&CryptoHasher.Extern.getDigestSize, .{ .name = "Bun__CryptoHasherExtern__getDigestSize" });
|
||||
}
|
||||
};
|
||||
|
||||
pub const digest = JSC.wrapInstanceMethod(CryptoHasher, "digest_", false);
|
||||
pub const hash = JSC.wrapStaticMethod(CryptoHasher, "hash_", false);
|
||||
|
||||
fn throwHmacConsumed(globalThis: *JSC.JSGlobalObject) bun.JSError {
|
||||
return globalThis.throw("HMAC has been consumed and is no longer usable", .{});
|
||||
}
|
||||
|
||||
pub fn getByteLength(this: *CryptoHasher, globalThis: *JSC.JSGlobalObject) JSC.JSValue {
|
||||
return JSC.JSValue.jsNumber(switch (this.*) {
|
||||
.evp => |*inner| inner.size(),
|
||||
.hmac => |inner| if (inner) |hmac| hmac.size() else {
|
||||
throwHmacConsumed(globalThis) catch return .zero;
|
||||
},
|
||||
.zig => |*inner| inner.digest_length,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn getAlgorithm(this: *CryptoHasher, globalObject: *JSC.JSGlobalObject) JSC.JSValue {
|
||||
return switch (this.*) {
|
||||
inline .evp, .zig => |*inner| ZigString.fromUTF8(bun.asByteSlice(@tagName(inner.algorithm))).toJS(globalObject),
|
||||
.hmac => |inner| if (inner) |hmac| ZigString.fromUTF8(bun.asByteSlice(@tagName(hmac.algorithm))).toJS(globalObject) else {
|
||||
throwHmacConsumed(globalObject) catch return .zero;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn getAlgorithms(
|
||||
globalThis_: *JSC.JSGlobalObject,
|
||||
_: JSValue,
|
||||
_: JSValue,
|
||||
) JSC.JSValue {
|
||||
return bun.String.toJSArray(globalThis_, &EVP.Algorithm.names.values);
|
||||
}
|
||||
|
||||
fn hashToEncoding(globalThis: *JSGlobalObject, evp: *EVP, input: JSC.Node.BlobOrStringOrBuffer, encoding: JSC.Node.Encoding) bun.JSError!JSC.JSValue {
|
||||
var output_digest_buf: Digest = undefined;
|
||||
defer input.deinit();
|
||||
|
||||
if (input == .blob and input.blob.isBunFile()) {
|
||||
return globalThis.throw("Bun.file() is not supported here yet (it needs an async version)", .{});
|
||||
}
|
||||
|
||||
const len = evp.hash(globalThis.bunVM().rareData().boringEngine(), input.slice(), &output_digest_buf) orelse {
|
||||
const err = BoringSSL.ERR_get_error();
|
||||
const instance = createCryptoError(globalThis, err);
|
||||
BoringSSL.ERR_clear_error();
|
||||
return globalThis.throwValue(instance);
|
||||
};
|
||||
return encoding.encodeWithMaxSize(globalThis, BoringSSL.EVP_MAX_MD_SIZE, output_digest_buf[0..len]);
|
||||
}
|
||||
|
||||
fn hashToBytes(globalThis: *JSGlobalObject, evp: *EVP, input: JSC.Node.BlobOrStringOrBuffer, output: ?JSC.ArrayBuffer) bun.JSError!JSC.JSValue {
|
||||
var output_digest_buf: Digest = undefined;
|
||||
var output_digest_slice: []u8 = &output_digest_buf;
|
||||
defer input.deinit();
|
||||
|
||||
if (input == .blob and input.blob.isBunFile()) {
|
||||
return globalThis.throw("Bun.file() is not supported here yet (it needs an async version)", .{});
|
||||
}
|
||||
|
||||
if (output) |output_buf| {
|
||||
const size = evp.size();
|
||||
var bytes = output_buf.byteSlice();
|
||||
if (bytes.len < size) {
|
||||
return globalThis.throwInvalidArguments("TypedArray must be at least {d} bytes", .{size});
|
||||
}
|
||||
output_digest_slice = bytes[0..size];
|
||||
}
|
||||
|
||||
const len = evp.hash(globalThis.bunVM().rareData().boringEngine(), input.slice(), output_digest_slice) orelse {
|
||||
const err = BoringSSL.ERR_get_error();
|
||||
const instance = createCryptoError(globalThis, err);
|
||||
BoringSSL.ERR_clear_error();
|
||||
return globalThis.throwValue(instance);
|
||||
};
|
||||
|
||||
if (output) |output_buf| {
|
||||
return output_buf.value;
|
||||
} else {
|
||||
// Clone to GC-managed memory
|
||||
return JSC.ArrayBuffer.createBuffer(globalThis, output_digest_slice[0..len]);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hash_(
|
||||
globalThis: *JSGlobalObject,
|
||||
algorithm: ZigString,
|
||||
input: JSC.Node.BlobOrStringOrBuffer,
|
||||
output: ?JSC.Node.StringOrBuffer,
|
||||
) bun.JSError!JSC.JSValue {
|
||||
var evp = EVP.byName(algorithm, globalThis) orelse return try CryptoHasherZig.hashByName(globalThis, algorithm, input, output) orelse {
|
||||
return globalThis.throwInvalidArguments("Unsupported algorithm \"{any}\"", .{algorithm});
|
||||
};
|
||||
defer evp.deinit();
|
||||
|
||||
if (output) |string_or_buffer| {
|
||||
switch (string_or_buffer) {
|
||||
inline else => |*str| {
|
||||
defer str.deinit();
|
||||
const encoding = JSC.Node.Encoding.from(str.slice()) orelse {
|
||||
return globalThis.ERR_INVALID_ARG_VALUE("Unknown encoding: {s}", .{str.slice()}).throw();
|
||||
};
|
||||
|
||||
return hashToEncoding(globalThis, &evp, input, encoding);
|
||||
},
|
||||
.buffer => |buffer| {
|
||||
return hashToBytes(globalThis, &evp, input, buffer.buffer);
|
||||
},
|
||||
}
|
||||
} else {
|
||||
return hashToBytes(globalThis, &evp, input, null);
|
||||
}
|
||||
}
|
||||
|
||||
// Bun.CryptoHasher(algorithm, hmacKey?: string | Buffer)
|
||||
pub fn constructor(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!*CryptoHasher {
|
||||
const arguments = callframe.arguments_old(2);
|
||||
if (arguments.len == 0) {
|
||||
return globalThis.throwInvalidArguments("Expected an algorithm name as an argument", .{});
|
||||
}
|
||||
|
||||
const algorithm_name = arguments.ptr[0];
|
||||
if (algorithm_name.isEmptyOrUndefinedOrNull() or !algorithm_name.isString()) {
|
||||
return globalThis.throwInvalidArguments("algorithm must be a string", .{});
|
||||
}
|
||||
|
||||
const algorithm = try algorithm_name.getZigString(globalThis);
|
||||
|
||||
if (algorithm.len == 0) {
|
||||
return globalThis.throwInvalidArguments("Invalid algorithm name", .{});
|
||||
}
|
||||
|
||||
const hmac_value = arguments.ptr[1];
|
||||
var hmac_key: ?JSC.Node.StringOrBuffer = null;
|
||||
defer {
|
||||
if (hmac_key) |*key| {
|
||||
key.deinit();
|
||||
}
|
||||
}
|
||||
|
||||
if (!hmac_value.isEmptyOrUndefinedOrNull()) {
|
||||
hmac_key = try JSC.Node.StringOrBuffer.fromJS(globalThis, bun.default_allocator, hmac_value) orelse {
|
||||
return globalThis.throwInvalidArguments("key must be a string or buffer", .{});
|
||||
};
|
||||
}
|
||||
|
||||
return CryptoHasher.new(brk: {
|
||||
if (hmac_key) |*key| {
|
||||
const chosen_algorithm = try algorithm_name.toEnumFromMap(globalThis, "algorithm", EVP.Algorithm, EVP.Algorithm.map);
|
||||
if (chosen_algorithm == .ripemd160) {
|
||||
// crashes at runtime.
|
||||
return globalThis.throw("ripemd160 is not supported", .{});
|
||||
}
|
||||
|
||||
break :brk .{
|
||||
.hmac = HMAC.init(chosen_algorithm, key.slice()) orelse {
|
||||
if (!globalThis.hasException()) {
|
||||
const err = BoringSSL.ERR_get_error();
|
||||
if (err != 0) {
|
||||
const instance = createCryptoError(globalThis, err);
|
||||
BoringSSL.ERR_clear_error();
|
||||
return globalThis.throwValue(instance);
|
||||
} else {
|
||||
return globalThis.throwTODO("HMAC is not supported for this algorithm yet");
|
||||
}
|
||||
}
|
||||
return error.JSError;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
break :brk .{
|
||||
.evp = EVP.byName(algorithm, globalThis) orelse return CryptoHasherZig.constructor(algorithm) orelse {
|
||||
return globalThis.throwInvalidArguments("Unsupported algorithm {any}", .{algorithm});
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
pub fn getter(
|
||||
globalObject: *JSC.JSGlobalObject,
|
||||
_: *JSC.JSObject,
|
||||
) JSC.JSValue {
|
||||
return CryptoHasher.getConstructor(globalObject);
|
||||
}
|
||||
|
||||
pub fn update(this: *CryptoHasher, globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
|
||||
const thisValue = callframe.this();
|
||||
const arguments = callframe.arguments_old(2);
|
||||
const input = arguments.ptr[0];
|
||||
if (input.isEmptyOrUndefinedOrNull()) {
|
||||
return globalThis.throwInvalidArguments("expected blob, string or buffer", .{});
|
||||
}
|
||||
const encoding = arguments.ptr[1];
|
||||
const buffer = try JSC.Node.BlobOrStringOrBuffer.fromJSWithEncodingValue(globalThis, globalThis.bunVM().allocator, input, encoding) orelse {
|
||||
if (!globalThis.hasException()) return globalThis.throwInvalidArguments("expected blob, string or buffer", .{});
|
||||
return error.JSError;
|
||||
};
|
||||
defer buffer.deinit();
|
||||
if (buffer == .blob and buffer.blob.isBunFile()) {
|
||||
return globalThis.throw("Bun.file() is not supported here yet (it needs an async version)", .{});
|
||||
}
|
||||
|
||||
switch (this.*) {
|
||||
.evp => |*inner| {
|
||||
inner.update(buffer.slice());
|
||||
const err = BoringSSL.ERR_get_error();
|
||||
if (err != 0) {
|
||||
const instance = createCryptoError(globalThis, err);
|
||||
BoringSSL.ERR_clear_error();
|
||||
return globalThis.throwValue(instance);
|
||||
}
|
||||
},
|
||||
.hmac => |inner| {
|
||||
const hmac = inner orelse {
|
||||
return throwHmacConsumed(globalThis);
|
||||
};
|
||||
|
||||
hmac.update(buffer.slice());
|
||||
const err = BoringSSL.ERR_get_error();
|
||||
if (err != 0) {
|
||||
const instance = createCryptoError(globalThis, err);
|
||||
BoringSSL.ERR_clear_error();
|
||||
return globalThis.throwValue(instance);
|
||||
}
|
||||
},
|
||||
.zig => |*inner| {
|
||||
inner.update(buffer.slice());
|
||||
return thisValue;
|
||||
},
|
||||
}
|
||||
|
||||
return thisValue;
|
||||
}
|
||||
|
||||
pub fn copy(
|
||||
this: *CryptoHasher,
|
||||
globalObject: *JSC.JSGlobalObject,
|
||||
_: *JSC.CallFrame,
|
||||
) bun.JSError!JSC.JSValue {
|
||||
var new: CryptoHasher = undefined;
|
||||
switch (this.*) {
|
||||
.evp => |*inner| {
|
||||
new = .{ .evp = inner.copy(globalObject.bunVM().rareData().boringEngine()) catch bun.outOfMemory() };
|
||||
},
|
||||
.hmac => |inner| {
|
||||
const hmac = inner orelse {
|
||||
return throwHmacConsumed(globalObject);
|
||||
};
|
||||
new = .{
|
||||
.hmac = hmac.copy() catch {
|
||||
const err = createCryptoError(globalObject, BoringSSL.ERR_get_error());
|
||||
BoringSSL.ERR_clear_error();
|
||||
return globalObject.throwValue(err);
|
||||
},
|
||||
};
|
||||
},
|
||||
.zig => |*inner| {
|
||||
new = .{ .zig = inner.copy() };
|
||||
},
|
||||
}
|
||||
return CryptoHasher.new(new).toJS(globalObject);
|
||||
}
|
||||
|
||||
pub fn digest_(this: *CryptoHasher, globalThis: *JSGlobalObject, output: ?JSC.Node.StringOrBuffer) bun.JSError!JSC.JSValue {
|
||||
if (output) |string_or_buffer| {
|
||||
switch (string_or_buffer) {
|
||||
inline else => |*str| {
|
||||
defer str.deinit();
|
||||
const encoding = JSC.Node.Encoding.from(str.slice()) orelse {
|
||||
return globalThis.ERR_INVALID_ARG_VALUE("Unknown encoding: {s}", .{str.slice()}).throw();
|
||||
};
|
||||
|
||||
return this.digestToEncoding(globalThis, encoding);
|
||||
},
|
||||
.buffer => |buffer| {
|
||||
return this.digestToBytes(
|
||||
globalThis,
|
||||
buffer.buffer,
|
||||
);
|
||||
},
|
||||
}
|
||||
} else {
|
||||
return this.digestToBytes(globalThis, null);
|
||||
}
|
||||
}
|
||||
|
||||
fn digestToBytes(this: *CryptoHasher, globalThis: *JSGlobalObject, output: ?JSC.ArrayBuffer) bun.JSError!JSC.JSValue {
|
||||
var output_digest_buf: EVP.Digest = undefined;
|
||||
var output_digest_slice: []u8 = &output_digest_buf;
|
||||
if (output) |output_buf| {
|
||||
var bytes = output_buf.byteSlice();
|
||||
if (bytes.len < output_digest_buf.len) {
|
||||
return globalThis.throwInvalidArguments(comptime std.fmt.comptimePrint("TypedArray must be at least {d} bytes", .{output_digest_buf.len}), .{});
|
||||
}
|
||||
output_digest_slice = bytes[0..bytes.len];
|
||||
} else {
|
||||
output_digest_buf = std.mem.zeroes(EVP.Digest);
|
||||
}
|
||||
|
||||
const result = this.final(globalThis, output_digest_slice) catch return .zero;
|
||||
if (globalThis.hasException()) {
|
||||
return error.JSError;
|
||||
}
|
||||
|
||||
if (output) |output_buf| {
|
||||
return output_buf.value;
|
||||
} else {
|
||||
// Clone to GC-managed memory
|
||||
return JSC.ArrayBuffer.createBuffer(globalThis, result);
|
||||
}
|
||||
}
|
||||
|
||||
fn digestToEncoding(this: *CryptoHasher, globalThis: *JSGlobalObject, encoding: JSC.Node.Encoding) bun.JSError!JSC.JSValue {
|
||||
var output_digest_buf: EVP.Digest = std.mem.zeroes(EVP.Digest);
|
||||
const output_digest_slice: []u8 = &output_digest_buf;
|
||||
const out = this.final(globalThis, output_digest_slice) catch return .zero;
|
||||
if (globalThis.hasException()) {
|
||||
return error.JSError;
|
||||
}
|
||||
return encoding.encodeWithMaxSize(globalThis, BoringSSL.EVP_MAX_MD_SIZE, out);
|
||||
}
|
||||
|
||||
fn final(this: *CryptoHasher, globalThis: *JSGlobalObject, output_digest_slice: []u8) bun.JSError![]u8 {
|
||||
return switch (this.*) {
|
||||
.hmac => |inner| brk: {
|
||||
const hmac: *HMAC = inner orelse {
|
||||
return throwHmacConsumed(globalThis);
|
||||
};
|
||||
this.hmac = null;
|
||||
defer hmac.deinit();
|
||||
break :brk hmac.final(output_digest_slice);
|
||||
},
|
||||
.evp => |*inner| inner.final(globalThis.bunVM().rareData().boringEngine(), output_digest_slice),
|
||||
.zig => |*inner| inner.final(output_digest_slice),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn finalize(this: *CryptoHasher) void {
|
||||
switch (this.*) {
|
||||
.evp => |*inner| {
|
||||
// https://github.com/oven-sh/bun/issues/3250
|
||||
inner.deinit();
|
||||
},
|
||||
.zig => |*inner| {
|
||||
inner.deinit();
|
||||
},
|
||||
.hmac => |inner| {
|
||||
if (inner) |hmac| {
|
||||
hmac.deinit();
|
||||
}
|
||||
},
|
||||
}
|
||||
this.destroy();
|
||||
}
|
||||
};
|
||||
|
||||
const CryptoHasherZig = struct {
|
||||
algorithm: EVP.Algorithm,
|
||||
state: *anyopaque,
|
||||
digest_length: u8,
|
||||
|
||||
const algo_map = [_]struct { string, type }{
|
||||
.{ "sha3-224", std.crypto.hash.sha3.Sha3_224 },
|
||||
.{ "sha3-256", std.crypto.hash.sha3.Sha3_256 },
|
||||
.{ "sha3-384", std.crypto.hash.sha3.Sha3_384 },
|
||||
.{ "sha3-512", std.crypto.hash.sha3.Sha3_512 },
|
||||
.{ "shake128", std.crypto.hash.sha3.Shake128 },
|
||||
.{ "shake256", std.crypto.hash.sha3.Shake256 },
|
||||
};
|
||||
|
||||
inline fn digestLength(Algorithm: type) comptime_int {
|
||||
return switch (Algorithm) {
|
||||
std.crypto.hash.sha3.Shake128 => 16,
|
||||
std.crypto.hash.sha3.Shake256 => 32,
|
||||
else => Algorithm.digest_length,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn hashByName(globalThis: *JSGlobalObject, algorithm: ZigString, input: JSC.Node.BlobOrStringOrBuffer, output: ?JSC.Node.StringOrBuffer) bun.JSError!?JSC.JSValue {
|
||||
inline for (algo_map) |item| {
|
||||
if (bun.strings.eqlComptime(algorithm.slice(), item[0])) {
|
||||
return try hashByNameInner(globalThis, item[1], input, output);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
fn hashByNameInner(globalThis: *JSGlobalObject, comptime Algorithm: type, input: JSC.Node.BlobOrStringOrBuffer, output: ?JSC.Node.StringOrBuffer) bun.JSError!JSC.JSValue {
|
||||
if (output) |string_or_buffer| {
|
||||
switch (string_or_buffer) {
|
||||
inline else => |*str| {
|
||||
defer str.deinit();
|
||||
const encoding = JSC.Node.Encoding.from(str.slice()) orelse {
|
||||
return globalThis.ERR_INVALID_ARG_VALUE("Unknown encoding: {s}", .{str.slice()}).throw();
|
||||
};
|
||||
|
||||
if (encoding == .buffer) {
|
||||
return hashByNameInnerToBytes(globalThis, Algorithm, input, null);
|
||||
}
|
||||
|
||||
return hashByNameInnerToString(globalThis, Algorithm, input, encoding);
|
||||
},
|
||||
.buffer => |buffer| {
|
||||
return hashByNameInnerToBytes(globalThis, Algorithm, input, buffer.buffer);
|
||||
},
|
||||
}
|
||||
}
|
||||
return hashByNameInnerToBytes(globalThis, Algorithm, input, null);
|
||||
}
|
||||
|
||||
fn hashByNameInnerToString(globalThis: *JSGlobalObject, comptime Algorithm: type, input: JSC.Node.BlobOrStringOrBuffer, encoding: JSC.Node.Encoding) bun.JSError!JSC.JSValue {
|
||||
defer input.deinit();
|
||||
|
||||
if (input == .blob and input.blob.isBunFile()) {
|
||||
return globalThis.throw("Bun.file() is not supported here yet (it needs an async version)", .{});
|
||||
}
|
||||
|
||||
var h = Algorithm.init(.{});
|
||||
h.update(input.slice());
|
||||
|
||||
var out: [digestLength(Algorithm)]u8 = undefined;
|
||||
h.final(&out);
|
||||
|
||||
return encoding.encodeWithSize(globalThis, digestLength(Algorithm), &out);
|
||||
}
|
||||
|
||||
fn hashByNameInnerToBytes(globalThis: *JSGlobalObject, comptime Algorithm: type, input: JSC.Node.BlobOrStringOrBuffer, output: ?JSC.ArrayBuffer) bun.JSError!JSC.JSValue {
|
||||
defer input.deinit();
|
||||
|
||||
if (input == .blob and input.blob.isBunFile()) {
|
||||
return globalThis.throw("Bun.file() is not supported here yet (it needs an async version)", .{});
|
||||
}
|
||||
|
||||
var h = Algorithm.init(.{});
|
||||
const digest_length_comptime = digestLength(Algorithm);
|
||||
|
||||
if (output) |output_buf| {
|
||||
if (output_buf.byteSlice().len < digest_length_comptime) {
|
||||
return globalThis.throwInvalidArguments("TypedArray must be at least {d} bytes", .{digest_length_comptime});
|
||||
}
|
||||
}
|
||||
|
||||
h.update(input.slice());
|
||||
|
||||
if (output) |output_buf| {
|
||||
h.final(output_buf.slice()[0..digest_length_comptime]);
|
||||
return output_buf.value;
|
||||
} else {
|
||||
var out: [digestLength(Algorithm)]u8 = undefined;
|
||||
h.final(&out);
|
||||
// Clone to GC-managed memory
|
||||
return JSC.ArrayBuffer.createBuffer(globalThis, &out);
|
||||
}
|
||||
}
|
||||
|
||||
fn constructor(algorithm: ZigString) ?*CryptoHasher {
|
||||
inline for (algo_map) |item| {
|
||||
if (bun.strings.eqlComptime(algorithm.slice(), item[0])) {
|
||||
return CryptoHasher.new(.{ .zig = .{
|
||||
.algorithm = @field(EVP.Algorithm, item[0]),
|
||||
.state = bun.new(item[1], item[1].init(.{})),
|
||||
.digest_length = digestLength(item[1]),
|
||||
} });
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn init(algorithm: []const u8) ?CryptoHasherZig {
|
||||
inline for (algo_map) |item| {
|
||||
const name, const T = item;
|
||||
if (bun.strings.eqlComptime(algorithm, name)) {
|
||||
const handle: CryptoHasherZig = .{
|
||||
.algorithm = @field(EVP.Algorithm, name),
|
||||
.state = bun.new(T, T.init(.{})),
|
||||
.digest_length = digestLength(T),
|
||||
};
|
||||
|
||||
return handle;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
fn update(self: *CryptoHasherZig, bytes: []const u8) void {
|
||||
inline for (algo_map) |item| {
|
||||
if (self.algorithm == @field(EVP.Algorithm, item[0])) {
|
||||
return item[1].update(@ptrCast(@alignCast(self.state)), bytes);
|
||||
}
|
||||
}
|
||||
@panic("unreachable");
|
||||
}
|
||||
|
||||
fn copy(self: *const CryptoHasherZig) CryptoHasherZig {
|
||||
inline for (algo_map) |item| {
|
||||
if (self.algorithm == @field(EVP.Algorithm, item[0])) {
|
||||
return .{
|
||||
.algorithm = self.algorithm,
|
||||
.state = bun.dupe(item[1], @ptrCast(@alignCast(self.state))),
|
||||
.digest_length = self.digest_length,
|
||||
};
|
||||
}
|
||||
}
|
||||
@panic("unreachable");
|
||||
}
|
||||
|
||||
fn finalWithLen(self: *CryptoHasherZig, output_digest_slice: []u8, res_len: usize) []u8 {
|
||||
inline for (algo_map) |pair| {
|
||||
const name, const T = pair;
|
||||
if (self.algorithm == @field(EVP.Algorithm, name)) {
|
||||
T.final(@ptrCast(@alignCast(self.state)), @ptrCast(output_digest_slice));
|
||||
const reset: *T = @ptrCast(@alignCast(self.state));
|
||||
reset.* = T.init(.{});
|
||||
return output_digest_slice[0..res_len];
|
||||
}
|
||||
}
|
||||
@panic("unreachable");
|
||||
}
|
||||
|
||||
fn final(self: *CryptoHasherZig, output_digest_slice: []u8) []u8 {
|
||||
return self.finalWithLen(output_digest_slice, self.digest_length);
|
||||
}
|
||||
|
||||
fn deinit(self: *CryptoHasherZig) void {
|
||||
inline for (algo_map) |item| {
|
||||
if (self.algorithm == @field(EVP.Algorithm, item[0])) {
|
||||
return bun.destroy(@as(*item[1], @ptrCast(@alignCast(self.state))));
|
||||
}
|
||||
}
|
||||
@panic("unreachable");
|
||||
}
|
||||
};
|
||||
|
||||
fn StaticCryptoHasher(comptime Hasher: type, comptime name: [:0]const u8) type {
|
||||
return struct {
|
||||
hashing: Hasher = Hasher{},
|
||||
digested: bool = false,
|
||||
|
||||
const ThisHasher = @This();
|
||||
|
||||
pub usingnamespace @field(JSC.Codegen, "JS" ++ name);
|
||||
|
||||
pub const digest = JSC.wrapInstanceMethod(ThisHasher, "digest_", false);
|
||||
pub const hash = JSC.wrapStaticMethod(ThisHasher, "hash_", false);
|
||||
|
||||
pub fn getByteLength(
|
||||
_: *@This(),
|
||||
_: *JSC.JSGlobalObject,
|
||||
) JSC.JSValue {
|
||||
return JSC.JSValue.jsNumber(@as(u16, Hasher.digest));
|
||||
}
|
||||
|
||||
pub fn getByteLengthStatic(
|
||||
_: *JSC.JSGlobalObject,
|
||||
_: JSValue,
|
||||
_: JSValue,
|
||||
) JSC.JSValue {
|
||||
return JSC.JSValue.jsNumber(@as(u16, Hasher.digest));
|
||||
}
|
||||
|
||||
fn hashToEncoding(globalThis: *JSGlobalObject, input: JSC.Node.BlobOrStringOrBuffer, encoding: JSC.Node.Encoding) bun.JSError!JSC.JSValue {
|
||||
var output_digest_buf: Hasher.Digest = undefined;
|
||||
|
||||
if (input == .blob and input.blob.isBunFile()) {
|
||||
return globalThis.throw("Bun.file() is not supported here yet (it needs an async version)", .{});
|
||||
}
|
||||
|
||||
if (comptime @typeInfo(@TypeOf(Hasher.hash)).@"fn".params.len == 3) {
|
||||
Hasher.hash(input.slice(), &output_digest_buf, JSC.VirtualMachine.get().rareData().boringEngine());
|
||||
} else {
|
||||
Hasher.hash(input.slice(), &output_digest_buf);
|
||||
}
|
||||
|
||||
return encoding.encodeWithSize(globalThis, Hasher.digest, &output_digest_buf);
|
||||
}
|
||||
|
||||
fn hashToBytes(globalThis: *JSGlobalObject, input: JSC.Node.BlobOrStringOrBuffer, output: ?JSC.ArrayBuffer) bun.JSError!JSC.JSValue {
|
||||
var output_digest_buf: Hasher.Digest = undefined;
|
||||
var output_digest_slice: *Hasher.Digest = &output_digest_buf;
|
||||
if (output) |output_buf| {
|
||||
var bytes = output_buf.byteSlice();
|
||||
if (bytes.len < Hasher.digest) {
|
||||
return globalThis.throwInvalidArguments(comptime std.fmt.comptimePrint("TypedArray must be at least {d} bytes", .{Hasher.digest}), .{});
|
||||
}
|
||||
output_digest_slice = bytes[0..Hasher.digest];
|
||||
}
|
||||
|
||||
if (comptime @typeInfo(@TypeOf(Hasher.hash)).@"fn".params.len == 3) {
|
||||
Hasher.hash(input.slice(), output_digest_slice, JSC.VirtualMachine.get().rareData().boringEngine());
|
||||
} else {
|
||||
Hasher.hash(input.slice(), output_digest_slice);
|
||||
}
|
||||
|
||||
if (output) |output_buf| {
|
||||
return output_buf.value;
|
||||
} else {
|
||||
var array_buffer_out = JSC.ArrayBuffer.fromBytes(bun.default_allocator.dupe(u8, output_digest_slice) catch unreachable, .Uint8Array);
|
||||
return array_buffer_out.toJSUnchecked(globalThis, null);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hash_(
|
||||
globalThis: *JSGlobalObject,
|
||||
input: JSC.Node.BlobOrStringOrBuffer,
|
||||
output: ?JSC.Node.StringOrBuffer,
|
||||
) bun.JSError!JSC.JSValue {
|
||||
defer input.deinit();
|
||||
|
||||
if (input == .blob and input.blob.isBunFile()) {
|
||||
return globalThis.throw("Bun.file() is not supported here yet (it needs an async version)", .{});
|
||||
}
|
||||
|
||||
if (output) |string_or_buffer| {
|
||||
switch (string_or_buffer) {
|
||||
inline else => |*str| {
|
||||
defer str.deinit();
|
||||
const encoding = JSC.Node.Encoding.from(str.slice()) orelse {
|
||||
return globalThis.ERR_INVALID_ARG_VALUE("Unknown encoding: {s}", .{str.slice()}).throw();
|
||||
};
|
||||
|
||||
return hashToEncoding(globalThis, input, encoding);
|
||||
},
|
||||
.buffer => |buffer| {
|
||||
return hashToBytes(globalThis, input, buffer.buffer);
|
||||
},
|
||||
}
|
||||
} else {
|
||||
return hashToBytes(globalThis, input, null);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn constructor(_: *JSC.JSGlobalObject, _: *JSC.CallFrame) bun.JSError!*@This() {
|
||||
const this = try bun.default_allocator.create(@This());
|
||||
this.* = .{ .hashing = Hasher.init() };
|
||||
return this;
|
||||
}
|
||||
|
||||
pub fn getter(
|
||||
globalObject: *JSC.JSGlobalObject,
|
||||
_: *JSC.JSObject,
|
||||
) JSC.JSValue {
|
||||
return ThisHasher.getConstructor(globalObject);
|
||||
}
|
||||
|
||||
pub fn update(this: *@This(), globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
|
||||
if (this.digested) {
|
||||
return globalThis.ERR_INVALID_STATE(name ++ " hasher already digested, create a new instance to update", .{}).throw();
|
||||
}
|
||||
const thisValue = callframe.this();
|
||||
const input = callframe.argument(0);
|
||||
const buffer = try JSC.Node.BlobOrStringOrBuffer.fromJS(globalThis, globalThis.bunVM().allocator, input) orelse {
|
||||
return globalThis.throwInvalidArguments("expected blob or string or buffer", .{});
|
||||
};
|
||||
defer buffer.deinit();
|
||||
|
||||
if (buffer == .blob and buffer.blob.isBunFile()) {
|
||||
return globalThis.throw("Bun.file() is not supported here yet (it needs an async version)", .{});
|
||||
}
|
||||
this.hashing.update(buffer.slice());
|
||||
return thisValue;
|
||||
}
|
||||
|
||||
pub fn digest_(
|
||||
this: *@This(),
|
||||
globalThis: *JSGlobalObject,
|
||||
output: ?JSC.Node.StringOrBuffer,
|
||||
) bun.JSError!JSC.JSValue {
|
||||
if (this.digested) {
|
||||
return globalThis.ERR_INVALID_STATE(name ++ " hasher already digested, create a new instance to digest again", .{}).throw();
|
||||
}
|
||||
if (output) |*string_or_buffer| {
|
||||
switch (string_or_buffer.*) {
|
||||
inline else => |*str| {
|
||||
defer str.deinit();
|
||||
const encoding = JSC.Node.Encoding.from(str.slice()) orelse {
|
||||
return globalThis.ERR_INVALID_ARG_VALUE("Unknown encoding: {s}", .{str.slice()}).throw();
|
||||
};
|
||||
|
||||
return this.digestToEncoding(globalThis, encoding);
|
||||
},
|
||||
.buffer => |*buffer| {
|
||||
return this.digestToBytes(
|
||||
globalThis,
|
||||
buffer.buffer,
|
||||
);
|
||||
},
|
||||
}
|
||||
} else {
|
||||
return this.digestToBytes(globalThis, null);
|
||||
}
|
||||
}
|
||||
|
||||
fn digestToBytes(this: *@This(), globalThis: *JSGlobalObject, output: ?JSC.ArrayBuffer) bun.JSError!JSC.JSValue {
|
||||
var output_digest_buf: Hasher.Digest = undefined;
|
||||
var output_digest_slice: *Hasher.Digest = &output_digest_buf;
|
||||
if (output) |output_buf| {
|
||||
var bytes = output_buf.byteSlice();
|
||||
if (bytes.len < Hasher.digest) {
|
||||
return globalThis.throwInvalidArguments(comptime std.fmt.comptimePrint("TypedArray must be at least {d} bytes", .{Hasher.digest}), .{});
|
||||
}
|
||||
output_digest_slice = bytes[0..Hasher.digest];
|
||||
} else {
|
||||
output_digest_buf = std.mem.zeroes(Hasher.Digest);
|
||||
}
|
||||
|
||||
this.hashing.final(output_digest_slice);
|
||||
this.digested = true;
|
||||
|
||||
if (output) |output_buf| {
|
||||
return output_buf.value;
|
||||
} else {
|
||||
var array_buffer_out = JSC.ArrayBuffer.fromBytes(bun.default_allocator.dupe(u8, &output_digest_buf) catch unreachable, .Uint8Array);
|
||||
return array_buffer_out.toJSUnchecked(globalThis, null);
|
||||
}
|
||||
}
|
||||
|
||||
fn digestToEncoding(this: *@This(), globalThis: *JSGlobalObject, encoding: JSC.Node.Encoding) JSC.JSValue {
|
||||
var output_digest_buf: Hasher.Digest = comptime brk: {
|
||||
var bytes: Hasher.Digest = undefined;
|
||||
var i: usize = 0;
|
||||
while (i < Hasher.digest) {
|
||||
bytes[i] = 0;
|
||||
i += 1;
|
||||
}
|
||||
break :brk bytes;
|
||||
};
|
||||
|
||||
const output_digest_slice: *Hasher.Digest = &output_digest_buf;
|
||||
|
||||
this.hashing.final(output_digest_slice);
|
||||
this.digested = true;
|
||||
|
||||
return encoding.encodeWithSize(globalThis, Hasher.digest, output_digest_slice);
|
||||
}
|
||||
|
||||
pub fn finalize(this: *@This()) void {
|
||||
VirtualMachine.get().allocator.destroy(this);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub const MD4 = StaticCryptoHasher(Hashers.MD4, "MD4");
|
||||
pub const MD5 = StaticCryptoHasher(Hashers.MD5, "MD5");
|
||||
pub const SHA1 = StaticCryptoHasher(Hashers.SHA1, "SHA1");
|
||||
pub const SHA224 = StaticCryptoHasher(Hashers.SHA224, "SHA224");
|
||||
pub const SHA256 = StaticCryptoHasher(Hashers.SHA256, "SHA256");
|
||||
pub const SHA384 = StaticCryptoHasher(Hashers.SHA384, "SHA384");
|
||||
pub const SHA512 = StaticCryptoHasher(Hashers.SHA512, "SHA512");
|
||||
pub const SHA512_256 = StaticCryptoHasher(Hashers.SHA512_256, "SHA512_256");
|
||||
const Crypto = JSC.API.Bun.Crypto;
|
||||
const Hashers = @import("../../../sha.zig");
|
||||
|
||||
const std = @import("std");
|
||||
const bun = @import("root").bun;
|
||||
const string = bun.string;
|
||||
const strings = bun.strings;
|
||||
const MutableString = bun.MutableString;
|
||||
const stringZ = bun.stringZ;
|
||||
const default_allocator = bun.default_allocator;
|
||||
const JSC = bun.JSC;
|
||||
const Async = bun.Async;
|
||||
const ZigString = JSC.ZigString;
|
||||
const JSValue = JSC.JSValue;
|
||||
const JSGlobalObject = JSC.JSGlobalObject;
|
||||
const CallFrame = JSC.CallFrame;
|
||||
const assert = bun.assert;
|
||||
const HMAC = Crypto.HMAC;
|
||||
const EVP = Crypto.EVP;
|
||||
const BoringSSL = bun.BoringSSL.c;
|
||||
const createCryptoError = Crypto.createCryptoError;
|
||||
const VirtualMachine = JSC.VirtualMachine;
|
||||
221
src/bun.js/api/crypto/EVP.zig
Normal file
221
src/bun.js/api/crypto/EVP.zig
Normal file
@@ -0,0 +1,221 @@
|
||||
ctx: BoringSSL.EVP_MD_CTX = undefined,
|
||||
md: *const BoringSSL.EVP_MD = undefined,
|
||||
algorithm: Algorithm,
|
||||
|
||||
// we do this to avoid asking BoringSSL what the digest name is
|
||||
// because that API is confusing
|
||||
pub const Algorithm = enum {
|
||||
// @"DSA-SHA",
|
||||
// @"DSA-SHA1",
|
||||
// @"MD5-SHA1",
|
||||
// @"RSA-MD5",
|
||||
// @"RSA-RIPEMD160",
|
||||
// @"RSA-SHA1",
|
||||
// @"RSA-SHA1-2",
|
||||
// @"RSA-SHA224",
|
||||
// @"RSA-SHA256",
|
||||
// @"RSA-SHA384",
|
||||
// @"RSA-SHA512",
|
||||
// @"ecdsa-with-SHA1",
|
||||
blake2b256,
|
||||
blake2b512,
|
||||
md4,
|
||||
md5,
|
||||
ripemd160,
|
||||
sha1,
|
||||
sha224,
|
||||
sha256,
|
||||
sha384,
|
||||
sha512,
|
||||
@"sha512-224",
|
||||
@"sha512-256",
|
||||
|
||||
@"sha3-224",
|
||||
@"sha3-256",
|
||||
@"sha3-384",
|
||||
@"sha3-512",
|
||||
shake128,
|
||||
shake256,
|
||||
|
||||
pub fn md(this: Algorithm) ?*const BoringSSL.EVP_MD {
|
||||
return switch (this) {
|
||||
.blake2b256 => BoringSSL.EVP_blake2b256(),
|
||||
.blake2b512 => BoringSSL.EVP_blake2b512(),
|
||||
.md4 => BoringSSL.EVP_md4(),
|
||||
.md5 => BoringSSL.EVP_md5(),
|
||||
.ripemd160 => BoringSSL.EVP_ripemd160(),
|
||||
.sha1 => BoringSSL.EVP_sha1(),
|
||||
.sha224 => BoringSSL.EVP_sha224(),
|
||||
.sha256 => BoringSSL.EVP_sha256(),
|
||||
.sha384 => BoringSSL.EVP_sha384(),
|
||||
.sha512 => BoringSSL.EVP_sha512(),
|
||||
.@"sha512-224" => BoringSSL.EVP_sha512_224(),
|
||||
.@"sha512-256" => BoringSSL.EVP_sha512_256(),
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
|
||||
pub const names: std.EnumArray(Algorithm, bun.String) = brk: {
|
||||
var all = std.EnumArray(Algorithm, bun.String).initUndefined();
|
||||
var iter = all.iterator();
|
||||
while (iter.next()) |entry| {
|
||||
entry.value.* = bun.String.init(@tagName(entry.key));
|
||||
}
|
||||
break :brk all;
|
||||
};
|
||||
|
||||
pub const map = bun.ComptimeStringMap(Algorithm, .{
|
||||
.{ "blake2b256", .blake2b256 },
|
||||
.{ "blake2b512", .blake2b512 },
|
||||
.{ "ripemd160", .ripemd160 },
|
||||
.{ "rmd160", .ripemd160 },
|
||||
.{ "md4", .md4 },
|
||||
.{ "md5", .md5 },
|
||||
.{ "sha1", .sha1 },
|
||||
.{ "sha128", .sha1 },
|
||||
.{ "sha224", .sha224 },
|
||||
.{ "sha256", .sha256 },
|
||||
.{ "sha384", .sha384 },
|
||||
.{ "sha512", .sha512 },
|
||||
.{ "sha-1", .sha1 },
|
||||
.{ "sha-224", .sha224 },
|
||||
.{ "sha-256", .sha256 },
|
||||
.{ "sha-384", .sha384 },
|
||||
.{ "sha-512", .sha512 },
|
||||
.{ "sha-512/224", .@"sha512-224" },
|
||||
.{ "sha-512_224", .@"sha512-224" },
|
||||
.{ "sha-512224", .@"sha512-224" },
|
||||
.{ "sha512-224", .@"sha512-224" },
|
||||
.{ "sha-512/256", .@"sha512-256" },
|
||||
.{ "sha-512_256", .@"sha512-256" },
|
||||
.{ "sha-512256", .@"sha512-256" },
|
||||
.{ "sha512-256", .@"sha512-256" },
|
||||
.{ "sha384", .sha384 },
|
||||
.{ "sha3-224", .@"sha3-224" },
|
||||
.{ "sha3-256", .@"sha3-256" },
|
||||
.{ "sha3-384", .@"sha3-384" },
|
||||
.{ "sha3-512", .@"sha3-512" },
|
||||
.{ "shake128", .shake128 },
|
||||
.{ "shake256", .shake256 },
|
||||
// .{ "md5-sha1", .@"MD5-SHA1" },
|
||||
// .{ "dsa-sha", .@"DSA-SHA" },
|
||||
// .{ "dsa-sha1", .@"DSA-SHA1" },
|
||||
// .{ "ecdsa-with-sha1", .@"ecdsa-with-SHA1" },
|
||||
// .{ "rsa-md5", .@"RSA-MD5" },
|
||||
// .{ "rsa-sha1", .@"RSA-SHA1" },
|
||||
// .{ "rsa-sha1-2", .@"RSA-SHA1-2" },
|
||||
// .{ "rsa-sha224", .@"RSA-SHA224" },
|
||||
// .{ "rsa-sha256", .@"RSA-SHA256" },
|
||||
// .{ "rsa-sha384", .@"RSA-SHA384" },
|
||||
// .{ "rsa-sha512", .@"RSA-SHA512" },
|
||||
// .{ "rsa-ripemd160", .@"RSA-RIPEMD160" },
|
||||
});
|
||||
};
|
||||
|
||||
pub fn init(algorithm: Algorithm, md: *const BoringSSL.EVP_MD, engine: *BoringSSL.ENGINE) EVP {
|
||||
bun.BoringSSL.load();
|
||||
|
||||
var ctx: BoringSSL.EVP_MD_CTX = undefined;
|
||||
BoringSSL.EVP_MD_CTX_init(&ctx);
|
||||
_ = BoringSSL.EVP_DigestInit_ex(&ctx, md, engine);
|
||||
return .{
|
||||
.ctx = ctx,
|
||||
.md = md,
|
||||
.algorithm = algorithm,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn reset(this: *EVP, engine: *BoringSSL.ENGINE) void {
|
||||
BoringSSL.ERR_clear_error();
|
||||
_ = BoringSSL.EVP_DigestInit_ex(&this.ctx, this.md, engine);
|
||||
}
|
||||
|
||||
pub fn hash(this: *EVP, engine: *BoringSSL.ENGINE, input: []const u8, output: []u8) ?u32 {
|
||||
BoringSSL.ERR_clear_error();
|
||||
var outsize: c_uint = @min(@as(u16, @truncate(output.len)), this.size());
|
||||
if (BoringSSL.EVP_Digest(input.ptr, input.len, output.ptr, &outsize, this.md, engine) != 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return outsize;
|
||||
}
|
||||
|
||||
pub fn final(this: *EVP, engine: *BoringSSL.ENGINE, output: []u8) []u8 {
|
||||
BoringSSL.ERR_clear_error();
|
||||
var outsize: u32 = @min(@as(u16, @truncate(output.len)), this.size());
|
||||
if (BoringSSL.EVP_DigestFinal_ex(
|
||||
&this.ctx,
|
||||
output.ptr,
|
||||
&outsize,
|
||||
) != 1) {
|
||||
return "";
|
||||
}
|
||||
|
||||
this.reset(engine);
|
||||
|
||||
return output[0..outsize];
|
||||
}
|
||||
|
||||
pub fn update(this: *EVP, input: []const u8) void {
|
||||
BoringSSL.ERR_clear_error();
|
||||
_ = BoringSSL.EVP_DigestUpdate(&this.ctx, input.ptr, input.len);
|
||||
}
|
||||
|
||||
pub fn size(this: *const EVP) u16 {
|
||||
return @as(u16, @truncate(BoringSSL.EVP_MD_CTX_size(&this.ctx)));
|
||||
}
|
||||
|
||||
pub fn copy(this: *const EVP, engine: *BoringSSL.ENGINE) error{OutOfMemory}!EVP {
|
||||
BoringSSL.ERR_clear_error();
|
||||
var new = init(this.algorithm, this.md, engine);
|
||||
if (BoringSSL.EVP_MD_CTX_copy_ex(&new.ctx, &this.ctx) == 0) {
|
||||
return error.OutOfMemory;
|
||||
}
|
||||
return new;
|
||||
}
|
||||
|
||||
pub fn byNameAndEngine(engine: *BoringSSL.ENGINE, name: []const u8) ?EVP {
|
||||
if (Algorithm.map.getWithEql(name, strings.eqlCaseInsensitiveASCIIIgnoreLength)) |algorithm| {
|
||||
if (algorithm.md()) |md| {
|
||||
return EVP.init(algorithm, md, engine);
|
||||
}
|
||||
|
||||
if (BoringSSL.EVP_get_digestbyname(@tagName(algorithm))) |md| {
|
||||
return EVP.init(algorithm, md, engine);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn byName(name: ZigString, global: *JSC.JSGlobalObject) ?EVP {
|
||||
var name_str = name.toSlice(global.allocator());
|
||||
defer name_str.deinit();
|
||||
return byNameAndEngine(global.bunVM().rareData().boringEngine(), name_str.slice());
|
||||
}
|
||||
|
||||
pub fn deinit(this: *EVP) void {
|
||||
// https://github.com/oven-sh/bun/issues/3250
|
||||
_ = BoringSSL.EVP_MD_CTX_cleanup(&this.ctx);
|
||||
}
|
||||
|
||||
pub const Digest = [BoringSSL.EVP_MAX_MD_SIZE]u8;
|
||||
pub const PBKDF2 = @import("./PBKDF2.zig");
|
||||
pub const pbkdf2 = PBKDF2.pbkdf2;
|
||||
|
||||
const std = @import("std");
|
||||
const bun = @import("root").bun;
|
||||
const string = bun.string;
|
||||
const strings = bun.strings;
|
||||
const MutableString = bun.MutableString;
|
||||
const stringZ = bun.stringZ;
|
||||
const default_allocator = bun.default_allocator;
|
||||
const JSC = bun.JSC;
|
||||
const Async = bun.Async;
|
||||
const ZigString = JSC.ZigString;
|
||||
const JSValue = JSC.JSValue;
|
||||
const JSGlobalObject = JSC.JSGlobalObject;
|
||||
const CallFrame = JSC.CallFrame;
|
||||
const assert = bun.assert;
|
||||
const EVP = @This();
|
||||
const BoringSSL = bun.BoringSSL.c;
|
||||
56
src/bun.js/api/crypto/HMAC.zig
Normal file
56
src/bun.js/api/crypto/HMAC.zig
Normal file
@@ -0,0 +1,56 @@
|
||||
ctx: BoringSSL.HMAC_CTX,
|
||||
algorithm: EVP.Algorithm,
|
||||
|
||||
pub usingnamespace bun.New(@This());
|
||||
|
||||
pub fn init(algorithm: EVP.Algorithm, key: []const u8) ?*HMAC {
|
||||
const md = algorithm.md() orelse return null;
|
||||
var ctx: BoringSSL.HMAC_CTX = undefined;
|
||||
BoringSSL.HMAC_CTX_init(&ctx);
|
||||
if (BoringSSL.HMAC_Init_ex(&ctx, key.ptr, @intCast(key.len), md, null) != 1) {
|
||||
BoringSSL.HMAC_CTX_cleanup(&ctx);
|
||||
return null;
|
||||
}
|
||||
return HMAC.new(.{
|
||||
.ctx = ctx,
|
||||
.algorithm = algorithm,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn update(this: *HMAC, data: []const u8) void {
|
||||
_ = BoringSSL.HMAC_Update(&this.ctx, data.ptr, data.len);
|
||||
}
|
||||
|
||||
pub fn size(this: *const HMAC) usize {
|
||||
return BoringSSL.HMAC_size(&this.ctx);
|
||||
}
|
||||
|
||||
pub fn copy(this: *HMAC) !*HMAC {
|
||||
var ctx: BoringSSL.HMAC_CTX = undefined;
|
||||
BoringSSL.HMAC_CTX_init(&ctx);
|
||||
if (BoringSSL.HMAC_CTX_copy(&ctx, &this.ctx) != 1) {
|
||||
BoringSSL.HMAC_CTX_cleanup(&ctx);
|
||||
return error.BoringSSLError;
|
||||
}
|
||||
return HMAC.new(.{
|
||||
.ctx = ctx,
|
||||
.algorithm = this.algorithm,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn final(this: *HMAC, out: []u8) []u8 {
|
||||
var outlen: c_uint = undefined;
|
||||
_ = BoringSSL.HMAC_Final(&this.ctx, out.ptr, &outlen);
|
||||
return out[0..outlen];
|
||||
}
|
||||
|
||||
pub fn deinit(this: *HMAC) void {
|
||||
BoringSSL.HMAC_CTX_cleanup(&this.ctx);
|
||||
this.destroy();
|
||||
}
|
||||
|
||||
const bun = @import("root").bun;
|
||||
const BoringSSL = bun.BoringSSL.c;
|
||||
const JSC = bun.JSC;
|
||||
const EVP = JSC.API.Bun.Crypto.EVP;
|
||||
const HMAC = @This();
|
||||
265
src/bun.js/api/crypto/PBKDF2.zig
Normal file
265
src/bun.js/api/crypto/PBKDF2.zig
Normal file
@@ -0,0 +1,265 @@
|
||||
password: JSC.Node.StringOrBuffer = JSC.Node.StringOrBuffer.empty,
|
||||
salt: JSC.Node.StringOrBuffer = JSC.Node.StringOrBuffer.empty,
|
||||
iteration_count: u32 = 1,
|
||||
length: i32 = 0,
|
||||
algorithm: EVP.Algorithm,
|
||||
|
||||
pub fn run(this: *PBKDF2, output: []u8) bool {
|
||||
const password = this.password.slice();
|
||||
const salt = this.salt.slice();
|
||||
const algorithm = this.algorithm;
|
||||
const iteration_count = this.iteration_count;
|
||||
const length = this.length;
|
||||
|
||||
@memset(output, 0);
|
||||
assert(this.length <= @as(i32, @intCast(output.len)));
|
||||
BoringSSL.ERR_clear_error();
|
||||
const rc = BoringSSL.PKCS5_PBKDF2_HMAC(
|
||||
if (password.len > 0) password.ptr else null,
|
||||
@intCast(password.len),
|
||||
salt.ptr,
|
||||
@intCast(salt.len),
|
||||
@intCast(iteration_count),
|
||||
algorithm.md().?,
|
||||
@intCast(length),
|
||||
output.ptr,
|
||||
);
|
||||
|
||||
if (rc <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
pub const Job = struct {
|
||||
pbkdf2: PBKDF2,
|
||||
output: []u8 = &[_]u8{},
|
||||
task: JSC.WorkPoolTask = .{ .callback = &runTask },
|
||||
promise: JSC.JSPromise.Strong = .{},
|
||||
vm: *JSC.VirtualMachine,
|
||||
err: ?u32 = null,
|
||||
any_task: JSC.AnyTask = undefined,
|
||||
poll: Async.KeepAlive = .{},
|
||||
|
||||
pub usingnamespace bun.New(@This());
|
||||
|
||||
pub fn runTask(task: *JSC.WorkPoolTask) void {
|
||||
const job: *PBKDF2.Job = @fieldParentPtr("task", task);
|
||||
defer job.vm.enqueueTaskConcurrent(JSC.ConcurrentTask.create(job.any_task.task()));
|
||||
job.output = bun.default_allocator.alloc(u8, @as(usize, @intCast(job.pbkdf2.length))) catch {
|
||||
job.err = BoringSSL.EVP_R_MEMORY_LIMIT_EXCEEDED;
|
||||
return;
|
||||
};
|
||||
if (!job.pbkdf2.run(job.output)) {
|
||||
job.err = BoringSSL.ERR_get_error();
|
||||
BoringSSL.ERR_clear_error();
|
||||
|
||||
bun.default_allocator.free(job.output);
|
||||
job.output = &[_]u8{};
|
||||
}
|
||||
}
|
||||
|
||||
pub fn runFromJS(this: *Job) void {
|
||||
defer this.deinit();
|
||||
if (this.vm.isShuttingDown()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const globalThis = this.vm.global;
|
||||
const promise = this.promise.swap();
|
||||
if (this.err) |err| {
|
||||
promise.reject(globalThis, createCryptoError(globalThis, err));
|
||||
return;
|
||||
}
|
||||
|
||||
const output_slice = this.output;
|
||||
assert(output_slice.len == @as(usize, @intCast(this.pbkdf2.length)));
|
||||
const buffer_value = JSC.JSValue.createBuffer(globalThis, output_slice, bun.default_allocator);
|
||||
if (buffer_value == .zero) {
|
||||
promise.reject(globalThis, ZigString.init("Failed to create buffer").toErrorInstance(globalThis));
|
||||
return;
|
||||
}
|
||||
|
||||
this.output = &[_]u8{};
|
||||
promise.resolve(globalThis, buffer_value);
|
||||
}
|
||||
|
||||
pub fn deinit(this: *Job) void {
|
||||
this.poll.unref(this.vm);
|
||||
this.pbkdf2.deinitAndUnprotect();
|
||||
this.promise.deinit();
|
||||
bun.default_allocator.free(this.output);
|
||||
this.destroy();
|
||||
}
|
||||
|
||||
pub fn create(vm: *JSC.VirtualMachine, globalThis: *JSC.JSGlobalObject, data: *const PBKDF2) *Job {
|
||||
var job = Job.new(.{
|
||||
.pbkdf2 = data.*,
|
||||
.vm = vm,
|
||||
.any_task = undefined,
|
||||
});
|
||||
|
||||
job.promise = JSC.JSPromise.Strong.init(globalThis);
|
||||
job.any_task = JSC.AnyTask.New(@This(), &runFromJS).init(job);
|
||||
job.poll.ref(vm);
|
||||
JSC.WorkPool.schedule(&job.task);
|
||||
|
||||
return job;
|
||||
}
|
||||
};
|
||||
|
||||
pub fn deinitAndUnprotect(this: *PBKDF2) void {
|
||||
this.password.deinitAndUnprotect();
|
||||
this.salt.deinitAndUnprotect();
|
||||
}
|
||||
|
||||
pub fn deinit(this: *PBKDF2) void {
|
||||
this.password.deinit();
|
||||
this.salt.deinit();
|
||||
}
|
||||
|
||||
pub fn fromJS(globalThis: *JSC.JSGlobalObject, callFrame: *JSC.CallFrame, is_async: bool) bun.JSError!PBKDF2 {
|
||||
const arg0, const arg1, const arg2, const arg3, const arg4, const arg5 = callFrame.argumentsAsArray(6);
|
||||
|
||||
if (!arg3.isNumber()) {
|
||||
return globalThis.throwInvalidArgumentTypeValue("keylen", "number", arg3);
|
||||
}
|
||||
|
||||
const keylen_num = arg3.asNumber();
|
||||
|
||||
if (std.math.isInf(keylen_num) or std.math.isNan(keylen_num)) {
|
||||
return globalThis.throwRangeError(keylen_num, .{
|
||||
.field_name = "keylen",
|
||||
.msg = "an integer",
|
||||
});
|
||||
}
|
||||
|
||||
if (keylen_num < 0 or keylen_num > std.math.maxInt(i32)) {
|
||||
return globalThis.throwRangeError(keylen_num, .{ .field_name = "keylen", .min = 0, .max = std.math.maxInt(i32) });
|
||||
}
|
||||
|
||||
const keylen: i32 = @intFromFloat(keylen_num);
|
||||
|
||||
if (globalThis.hasException()) {
|
||||
return error.JSError;
|
||||
}
|
||||
|
||||
if (!arg2.isAnyInt()) {
|
||||
return globalThis.throwInvalidArgumentTypeValue("iterations", "number", arg2);
|
||||
}
|
||||
|
||||
const iteration_count = arg2.coerce(i64, globalThis);
|
||||
|
||||
if (!globalThis.hasException() and (iteration_count < 1 or iteration_count > std.math.maxInt(i32))) {
|
||||
return globalThis.throwRangeError(iteration_count, .{ .field_name = "iterations", .min = 1, .max = std.math.maxInt(i32) + 1 });
|
||||
}
|
||||
|
||||
if (globalThis.hasException()) {
|
||||
return error.JSError;
|
||||
}
|
||||
|
||||
const algorithm = brk: {
|
||||
if (!arg4.isString()) {
|
||||
return globalThis.throwInvalidArgumentTypeValue("digest", "string", arg4);
|
||||
}
|
||||
|
||||
invalid: {
|
||||
switch (try EVP.Algorithm.map.fromJSCaseInsensitive(globalThis, arg4) orelse break :invalid) {
|
||||
.shake128, .shake256, .@"sha3-224", .@"sha3-256", .@"sha3-384", .@"sha3-512" => break :invalid,
|
||||
else => |alg| break :brk alg,
|
||||
}
|
||||
}
|
||||
|
||||
if (!globalThis.hasException()) {
|
||||
const slice = try arg4.toSlice(globalThis, bun.default_allocator);
|
||||
defer slice.deinit();
|
||||
const name = slice.slice();
|
||||
return globalThis.ERR_CRYPTO_INVALID_DIGEST("Invalid digest: {s}", .{name}).throw();
|
||||
}
|
||||
return error.JSError;
|
||||
};
|
||||
|
||||
var out = PBKDF2{
|
||||
.iteration_count = @intCast(iteration_count),
|
||||
.length = keylen,
|
||||
.algorithm = algorithm,
|
||||
};
|
||||
defer {
|
||||
if (globalThis.hasException()) {
|
||||
if (is_async)
|
||||
out.deinitAndUnprotect()
|
||||
else
|
||||
out.deinit();
|
||||
}
|
||||
}
|
||||
|
||||
const allow_string_object = true;
|
||||
out.salt = try JSC.Node.StringOrBuffer.fromJSMaybeAsync(globalThis, bun.default_allocator, arg1, is_async, allow_string_object) orelse {
|
||||
return globalThis.throwInvalidArgumentTypeValue("salt", "string or buffer", arg1);
|
||||
};
|
||||
|
||||
if (out.salt.slice().len > std.math.maxInt(i32)) {
|
||||
return globalThis.throwInvalidArguments("salt is too long", .{});
|
||||
}
|
||||
|
||||
out.password = try JSC.Node.StringOrBuffer.fromJSMaybeAsync(globalThis, bun.default_allocator, arg0, is_async, allow_string_object) orelse {
|
||||
return globalThis.throwInvalidArgumentTypeValue("password", "string or buffer", arg0);
|
||||
};
|
||||
|
||||
if (out.password.slice().len > std.math.maxInt(i32)) {
|
||||
return globalThis.throwInvalidArguments("password is too long", .{});
|
||||
}
|
||||
|
||||
if (is_async) {
|
||||
if (!arg5.isFunction()) {
|
||||
return globalThis.throwInvalidArgumentTypeValue("callback", "function", arg5);
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/// For usage in Zig
|
||||
pub fn pbkdf2(
|
||||
output: []u8,
|
||||
password: []const u8,
|
||||
salt: []const u8,
|
||||
iteration_count: u32,
|
||||
algorithm: Algorithm,
|
||||
) ?[]const u8 {
|
||||
var pbk = PBKDF2{
|
||||
.algorithm = algorithm,
|
||||
.password = JSC.Node.StringOrBuffer{ .encoded_slice = JSC.ZigString.Slice.fromUTF8NeverFree(password) },
|
||||
.salt = JSC.Node.StringOrBuffer{ .encoded_slice = JSC.ZigString.Slice.fromUTF8NeverFree(salt) },
|
||||
.iteration_count = iteration_count,
|
||||
.length = @intCast(output.len),
|
||||
};
|
||||
|
||||
if (!pbk.run(output)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
const std = @import("std");
|
||||
const bun = @import("root").bun;
|
||||
const string = bun.string;
|
||||
const strings = bun.strings;
|
||||
const MutableString = bun.MutableString;
|
||||
const stringZ = bun.stringZ;
|
||||
const default_allocator = bun.default_allocator;
|
||||
const JSC = bun.JSC;
|
||||
const Async = bun.Async;
|
||||
const ZigString = JSC.ZigString;
|
||||
const JSValue = JSC.JSValue;
|
||||
const JSGlobalObject = JSC.JSGlobalObject;
|
||||
const CallFrame = JSC.CallFrame;
|
||||
const assert = bun.assert;
|
||||
const EVP = JSC.API.Bun.Crypto.EVP;
|
||||
const Algorithm = EVP.Algorithm;
|
||||
const BoringSSL = bun.BoringSSL.c;
|
||||
const createCryptoError = JSC.API.Bun.Crypto.createCryptoError;
|
||||
const VirtualMachine = JSC.VirtualMachine;
|
||||
const PBKDF2 = @This();
|
||||
772
src/bun.js/api/crypto/PasswordObject.zig
Normal file
772
src/bun.js/api/crypto/PasswordObject.zig
Normal file
@@ -0,0 +1,772 @@
|
||||
pub const PasswordObject = struct {
|
||||
pub const pwhash = std.crypto.pwhash;
|
||||
pub const Algorithm = enum {
|
||||
argon2i,
|
||||
argon2d,
|
||||
argon2id,
|
||||
bcrypt,
|
||||
|
||||
pub const Value = union(Algorithm) {
|
||||
argon2i: Argon2Params,
|
||||
argon2d: Argon2Params,
|
||||
argon2id: Argon2Params,
|
||||
// bcrypt only accepts "cost"
|
||||
bcrypt: u6,
|
||||
|
||||
pub const bcrpyt_default = 10;
|
||||
|
||||
pub const default = Algorithm.Value{
|
||||
.argon2id = .{},
|
||||
};
|
||||
|
||||
pub fn fromJS(globalObject: *JSC.JSGlobalObject, value: JSC.JSValue) bun.JSError!Value {
|
||||
if (value.isObject()) {
|
||||
if (try value.getTruthy(globalObject, "algorithm")) |algorithm_value| {
|
||||
if (!algorithm_value.isString()) {
|
||||
return globalObject.throwInvalidArgumentType("hash", "algorithm", "string");
|
||||
}
|
||||
|
||||
const algorithm_string = try algorithm_value.getZigString(globalObject);
|
||||
|
||||
switch (PasswordObject.Algorithm.label.getWithEql(algorithm_string, JSC.ZigString.eqlComptime) orelse {
|
||||
return globalObject.throwInvalidArgumentType("hash", "algorithm", unknown_password_algorithm_message);
|
||||
}) {
|
||||
.bcrypt => {
|
||||
var algorithm = PasswordObject.Algorithm.Value{
|
||||
.bcrypt = PasswordObject.Algorithm.Value.bcrpyt_default,
|
||||
};
|
||||
|
||||
if (try value.getTruthy(globalObject, "cost")) |rounds_value| {
|
||||
if (!rounds_value.isNumber()) {
|
||||
return globalObject.throwInvalidArgumentType("hash", "cost", "number");
|
||||
}
|
||||
|
||||
const rounds = rounds_value.coerce(i32, globalObject);
|
||||
|
||||
if (rounds < 4 or rounds > 31) {
|
||||
return globalObject.throwInvalidArguments("Rounds must be between 4 and 31", .{});
|
||||
}
|
||||
|
||||
algorithm.bcrypt = @as(u6, @intCast(rounds));
|
||||
}
|
||||
|
||||
return algorithm;
|
||||
},
|
||||
inline .argon2id, .argon2d, .argon2i => |tag| {
|
||||
var argon = Algorithm.Argon2Params{};
|
||||
|
||||
if (try value.getTruthy(globalObject, "timeCost")) |time_value| {
|
||||
if (!time_value.isNumber()) {
|
||||
return globalObject.throwInvalidArgumentType("hash", "timeCost", "number");
|
||||
}
|
||||
|
||||
const time_cost = time_value.coerce(i32, globalObject);
|
||||
|
||||
if (time_cost < 1) {
|
||||
return globalObject.throwInvalidArguments("Time cost must be greater than 0", .{});
|
||||
}
|
||||
|
||||
argon.time_cost = @as(u32, @intCast(time_cost));
|
||||
}
|
||||
|
||||
if (try value.getTruthy(globalObject, "memoryCost")) |memory_value| {
|
||||
if (!memory_value.isNumber()) {
|
||||
return globalObject.throwInvalidArgumentType("hash", "memoryCost", "number");
|
||||
}
|
||||
|
||||
const memory_cost = memory_value.coerce(i32, globalObject);
|
||||
|
||||
if (memory_cost < 1) {
|
||||
return globalObject.throwInvalidArguments("Memory cost must be greater than 0", .{});
|
||||
}
|
||||
|
||||
argon.memory_cost = @as(u32, @intCast(memory_cost));
|
||||
}
|
||||
|
||||
return @unionInit(Algorithm.Value, @tagName(tag), argon);
|
||||
},
|
||||
}
|
||||
|
||||
unreachable;
|
||||
} else {
|
||||
return globalObject.throwInvalidArgumentType("hash", "options.algorithm", "string");
|
||||
}
|
||||
} else if (value.isString()) {
|
||||
const algorithm_string = try value.getZigString(globalObject);
|
||||
|
||||
switch (PasswordObject.Algorithm.label.getWithEql(algorithm_string, JSC.ZigString.eqlComptime) orelse {
|
||||
return globalObject.throwInvalidArgumentType("hash", "algorithm", unknown_password_algorithm_message);
|
||||
}) {
|
||||
.bcrypt => {
|
||||
return PasswordObject.Algorithm.Value{
|
||||
.bcrypt = PasswordObject.Algorithm.Value.bcrpyt_default,
|
||||
};
|
||||
},
|
||||
.argon2id => {
|
||||
return PasswordObject.Algorithm.Value{
|
||||
.argon2id = .{},
|
||||
};
|
||||
},
|
||||
.argon2d => {
|
||||
return PasswordObject.Algorithm.Value{
|
||||
.argon2d = .{},
|
||||
};
|
||||
},
|
||||
.argon2i => {
|
||||
return PasswordObject.Algorithm.Value{
|
||||
.argon2i = .{},
|
||||
};
|
||||
},
|
||||
}
|
||||
} else {
|
||||
return globalObject.throwInvalidArgumentType("hash", "algorithm", "string");
|
||||
}
|
||||
|
||||
unreachable;
|
||||
}
|
||||
};
|
||||
|
||||
pub const Argon2Params = struct {
|
||||
// we don't support the other options right now, but can add them later if someone asks
|
||||
memory_cost: u32 = pwhash.argon2.Params.interactive_2id.m,
|
||||
time_cost: u32 = pwhash.argon2.Params.interactive_2id.t,
|
||||
|
||||
pub fn toParams(this: Argon2Params) pwhash.argon2.Params {
|
||||
return pwhash.argon2.Params{
|
||||
.t = this.time_cost,
|
||||
.m = this.memory_cost,
|
||||
.p = 1,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const argon2 = Algorithm.argon2id;
|
||||
|
||||
pub const label = bun.ComptimeStringMap(
|
||||
Algorithm,
|
||||
.{
|
||||
.{ "argon2i", .argon2i },
|
||||
.{ "argon2d", .argon2d },
|
||||
.{ "argon2id", .argon2id },
|
||||
.{ "bcrypt", .bcrypt },
|
||||
},
|
||||
);
|
||||
|
||||
pub const default = Algorithm.argon2;
|
||||
|
||||
pub fn get(pw: []const u8) ?Algorithm {
|
||||
if (pw[0] != '$') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// PHC format looks like $<algorithm>$<params>$<salt>$<hash><optional stuff>
|
||||
if (strings.hasPrefixComptime(pw[1..], "argon2d$")) {
|
||||
return .argon2d;
|
||||
}
|
||||
if (strings.hasPrefixComptime(pw[1..], "argon2i$")) {
|
||||
return .argon2i;
|
||||
}
|
||||
if (strings.hasPrefixComptime(pw[1..], "argon2id$")) {
|
||||
return .argon2id;
|
||||
}
|
||||
|
||||
if (strings.hasPrefixComptime(pw[1..], "bcrypt")) {
|
||||
return .bcrypt;
|
||||
}
|
||||
|
||||
// https://en.wikipedia.org/wiki/Crypt_(C)
|
||||
if (strings.hasPrefixComptime(pw[1..], "2")) {
|
||||
return .bcrypt;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
pub const HashError = pwhash.Error || error{UnsupportedAlgorithm};
|
||||
|
||||
// This is purposely simple because nobody asked to make it more complicated
|
||||
pub fn hash(
|
||||
allocator: std.mem.Allocator,
|
||||
password: []const u8,
|
||||
algorithm: Algorithm.Value,
|
||||
) HashError![]const u8 {
|
||||
switch (algorithm) {
|
||||
inline .argon2i, .argon2d, .argon2id => |argon| {
|
||||
var outbuf: [4096]u8 = undefined;
|
||||
const hash_options = pwhash.argon2.HashOptions{
|
||||
.params = argon.toParams(),
|
||||
.allocator = allocator,
|
||||
.mode = switch (algorithm) {
|
||||
.argon2i => .argon2i,
|
||||
.argon2d => .argon2d,
|
||||
.argon2id => .argon2id,
|
||||
else => unreachable,
|
||||
},
|
||||
.encoding = .phc,
|
||||
};
|
||||
// warning: argon2's code may spin up threads if paralellism is set to > 0
|
||||
// we don't expose this option
|
||||
// but since it parses from phc format, it's possible that it will be set
|
||||
// eventually we should do something that about that.
|
||||
const out_bytes = try pwhash.argon2.strHash(password, hash_options, &outbuf);
|
||||
return try allocator.dupe(u8, out_bytes);
|
||||
},
|
||||
.bcrypt => |cost| {
|
||||
var outbuf: [4096]u8 = undefined;
|
||||
var outbuf_slice: []u8 = outbuf[0..];
|
||||
var password_to_use = password;
|
||||
// bcrypt silently truncates passwords longer than 72 bytes
|
||||
// we use SHA512 to hash the password if it's longer than 72 bytes
|
||||
if (password.len > 72) {
|
||||
var sha_512 = bun.sha.SHA512.init();
|
||||
defer sha_512.deinit();
|
||||
sha_512.update(password);
|
||||
sha_512.final(outbuf[0..bun.sha.SHA512.digest]);
|
||||
password_to_use = outbuf[0..bun.sha.SHA512.digest];
|
||||
outbuf_slice = outbuf[bun.sha.SHA512.digest..];
|
||||
}
|
||||
|
||||
const hash_options = pwhash.bcrypt.HashOptions{
|
||||
.params = pwhash.bcrypt.Params{
|
||||
.rounds_log = cost,
|
||||
.silently_truncate_password = true,
|
||||
},
|
||||
.allocator = allocator,
|
||||
.encoding = .crypt,
|
||||
};
|
||||
const out_bytes = try pwhash.bcrypt.strHash(password_to_use, hash_options, outbuf_slice);
|
||||
return try allocator.dupe(u8, out_bytes);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verify(
|
||||
allocator: std.mem.Allocator,
|
||||
password: []const u8,
|
||||
previous_hash: []const u8,
|
||||
algorithm: ?Algorithm,
|
||||
) HashError!bool {
|
||||
if (previous_hash.len == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return verifyWithAlgorithm(
|
||||
allocator,
|
||||
password,
|
||||
previous_hash,
|
||||
algorithm orelse Algorithm.get(previous_hash) orelse return error.UnsupportedAlgorithm,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn verifyWithAlgorithm(
|
||||
allocator: std.mem.Allocator,
|
||||
password: []const u8,
|
||||
previous_hash: []const u8,
|
||||
algorithm: Algorithm,
|
||||
) HashError!bool {
|
||||
switch (algorithm) {
|
||||
.argon2id, .argon2d, .argon2i => {
|
||||
pwhash.argon2.strVerify(previous_hash, password, .{ .allocator = allocator }) catch |err| {
|
||||
if (err == error.PasswordVerificationFailed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return err;
|
||||
};
|
||||
return true;
|
||||
},
|
||||
.bcrypt => {
|
||||
var password_to_use = password;
|
||||
var outbuf: [bun.sha.SHA512.digest]u8 = undefined;
|
||||
|
||||
// bcrypt silently truncates passwords longer than 72 bytes
|
||||
// we use SHA512 to hash the password if it's longer than 72 bytes
|
||||
if (password.len > 72) {
|
||||
var sha_512 = bun.sha.SHA512.init();
|
||||
defer sha_512.deinit();
|
||||
sha_512.update(password);
|
||||
sha_512.final(&outbuf);
|
||||
password_to_use = &outbuf;
|
||||
}
|
||||
pwhash.bcrypt.strVerify(previous_hash, password_to_use, .{
|
||||
.allocator = allocator,
|
||||
.silently_truncate_password = true,
|
||||
}) catch |err| {
|
||||
if (err == error.PasswordVerificationFailed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return err;
|
||||
};
|
||||
return true;
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub const JSPasswordObject = struct {
|
||||
const PascalToUpperUnderscoreCaseFormatter = struct {
|
||||
input: []const u8,
|
||||
pub fn format(self: @This(), comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
|
||||
for (self.input) |c| {
|
||||
if (std.ascii.isUpper(c)) {
|
||||
try writer.writeByte('_');
|
||||
try writer.writeByte(c);
|
||||
} else if (std.ascii.isLower(c)) {
|
||||
try writer.writeByte(std.ascii.toUpper(c));
|
||||
} else {
|
||||
try writer.writeByte(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub export fn JSPasswordObject__create(globalObject: *JSC.JSGlobalObject) JSC.JSValue {
|
||||
var object = JSValue.createEmptyObject(globalObject, 4);
|
||||
object.put(
|
||||
globalObject,
|
||||
ZigString.static("hash"),
|
||||
JSC.createCallback(globalObject, ZigString.static("hash"), 2, JSPasswordObject__hash),
|
||||
);
|
||||
object.put(
|
||||
globalObject,
|
||||
ZigString.static("hashSync"),
|
||||
JSC.createCallback(globalObject, ZigString.static("hashSync"), 2, JSPasswordObject__hashSync),
|
||||
);
|
||||
object.put(
|
||||
globalObject,
|
||||
ZigString.static("verify"),
|
||||
JSC.createCallback(globalObject, ZigString.static("verify"), 2, JSPasswordObject__verify),
|
||||
);
|
||||
object.put(
|
||||
globalObject,
|
||||
ZigString.static("verifySync"),
|
||||
JSC.createCallback(globalObject, ZigString.static("verifySync"), 2, JSPasswordObject__verifySync),
|
||||
);
|
||||
return object;
|
||||
}
|
||||
|
||||
const HashJob = struct {
|
||||
algorithm: PasswordObject.Algorithm.Value,
|
||||
password: []const u8,
|
||||
promise: JSC.JSPromise.Strong,
|
||||
event_loop: *JSC.EventLoop,
|
||||
global: *JSC.JSGlobalObject,
|
||||
ref: Async.KeepAlive = .{},
|
||||
task: JSC.WorkPoolTask = .{ .callback = &run },
|
||||
|
||||
pub usingnamespace bun.New(@This());
|
||||
|
||||
pub const Result = struct {
|
||||
value: Value,
|
||||
ref: Async.KeepAlive = .{},
|
||||
|
||||
task: JSC.AnyTask = undefined,
|
||||
promise: JSC.JSPromise.Strong,
|
||||
global: *JSC.JSGlobalObject,
|
||||
|
||||
pub usingnamespace bun.New(@This());
|
||||
|
||||
pub const Value = union(enum) {
|
||||
err: PasswordObject.HashError,
|
||||
hash: []const u8,
|
||||
|
||||
pub fn toErrorInstance(this: Value, globalObject: *JSC.JSGlobalObject) JSC.JSValue {
|
||||
const error_code = std.fmt.allocPrint(bun.default_allocator, "PASSWORD{}", .{PascalToUpperUnderscoreCaseFormatter{ .input = @errorName(this.err) }}) catch bun.outOfMemory();
|
||||
defer bun.default_allocator.free(error_code);
|
||||
const instance = globalObject.createErrorInstance("Password hashing failed with error \"{s}\"", .{@errorName(this.err)});
|
||||
instance.put(globalObject, ZigString.static("code"), JSC.ZigString.init(error_code).toJS(globalObject));
|
||||
return instance;
|
||||
}
|
||||
};
|
||||
|
||||
pub fn runFromJS(this: *Result) void {
|
||||
var promise = this.promise;
|
||||
defer promise.deinit();
|
||||
this.promise = .{};
|
||||
this.ref.unref(this.global.bunVM());
|
||||
const global = this.global;
|
||||
switch (this.value) {
|
||||
.err => {
|
||||
const error_instance = this.value.toErrorInstance(global);
|
||||
this.destroy();
|
||||
promise.reject(global, error_instance);
|
||||
},
|
||||
.hash => |value| {
|
||||
const js_string = JSC.ZigString.init(value).toJS(global);
|
||||
this.destroy();
|
||||
promise.resolve(global, js_string);
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub fn deinit(this: *HashJob) void {
|
||||
this.promise.deinit();
|
||||
bun.freeSensitive(bun.default_allocator, this.password);
|
||||
this.destroy();
|
||||
}
|
||||
|
||||
pub fn getValue(password: []const u8, algorithm: PasswordObject.Algorithm.Value) Result.Value {
|
||||
const value = PasswordObject.hash(bun.default_allocator, password, algorithm) catch |err| {
|
||||
return Result.Value{ .err = err };
|
||||
};
|
||||
return Result.Value{ .hash = value };
|
||||
}
|
||||
|
||||
pub fn run(task: *bun.ThreadPool.Task) void {
|
||||
var this: *HashJob = @fieldParentPtr("task", task);
|
||||
|
||||
var result = Result.new(.{
|
||||
.value = getValue(this.password, this.algorithm),
|
||||
.task = undefined,
|
||||
.promise = this.promise,
|
||||
.global = this.global,
|
||||
.ref = this.ref,
|
||||
});
|
||||
this.promise = .empty;
|
||||
|
||||
result.task = JSC.AnyTask.New(Result, Result.runFromJS).init(result);
|
||||
this.ref = .{};
|
||||
this.event_loop.enqueueTaskConcurrent(JSC.ConcurrentTask.createFrom(&result.task));
|
||||
this.deinit();
|
||||
}
|
||||
};
|
||||
|
||||
pub fn hash(globalObject: *JSC.JSGlobalObject, password: []const u8, algorithm: PasswordObject.Algorithm.Value, comptime sync: bool) bun.JSError!JSC.JSValue {
|
||||
assert(password.len > 0); // caller must check
|
||||
|
||||
if (comptime sync) {
|
||||
const value = HashJob.getValue(password, algorithm);
|
||||
switch (value) {
|
||||
.err => {
|
||||
const error_instance = value.toErrorInstance(globalObject);
|
||||
return globalObject.throwValue(error_instance);
|
||||
},
|
||||
.hash => |h| {
|
||||
return JSC.ZigString.init(h).toJS(globalObject);
|
||||
},
|
||||
}
|
||||
|
||||
unreachable;
|
||||
}
|
||||
|
||||
const promise = JSC.JSPromise.Strong.init(globalObject);
|
||||
|
||||
var job = HashJob.new(.{
|
||||
.algorithm = algorithm,
|
||||
.password = password,
|
||||
.promise = promise,
|
||||
.event_loop = globalObject.bunVM().eventLoop(),
|
||||
.global = globalObject,
|
||||
});
|
||||
job.ref.ref(globalObject.bunVM());
|
||||
JSC.WorkPool.schedule(&job.task);
|
||||
|
||||
return promise.value();
|
||||
}
|
||||
|
||||
pub fn verify(globalObject: *JSC.JSGlobalObject, password: []const u8, prev_hash: []const u8, algorithm: ?PasswordObject.Algorithm, comptime sync: bool) bun.JSError!JSC.JSValue {
|
||||
assert(password.len > 0); // caller must check
|
||||
|
||||
if (comptime sync) {
|
||||
const value = VerifyJob.getValue(password, prev_hash, algorithm);
|
||||
switch (value) {
|
||||
.err => {
|
||||
const error_instance = value.toErrorInstance(globalObject);
|
||||
return globalObject.throwValue(error_instance);
|
||||
},
|
||||
.pass => |pass| {
|
||||
return JSC.JSValue.jsBoolean(pass);
|
||||
},
|
||||
}
|
||||
|
||||
unreachable;
|
||||
}
|
||||
|
||||
var promise = JSC.JSPromise.Strong.init(globalObject);
|
||||
|
||||
const job = VerifyJob.new(.{
|
||||
.algorithm = algorithm,
|
||||
.password = password,
|
||||
.prev_hash = prev_hash,
|
||||
.promise = promise,
|
||||
.event_loop = globalObject.bunVM().eventLoop(),
|
||||
.global = globalObject,
|
||||
});
|
||||
job.ref.ref(globalObject.bunVM());
|
||||
JSC.WorkPool.schedule(&job.task);
|
||||
|
||||
return promise.value();
|
||||
}
|
||||
|
||||
// Once we have bindings generator, this should be replaced with a generated function
|
||||
pub fn JSPasswordObject__hash(globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
|
||||
const arguments_ = callframe.arguments_old(2);
|
||||
const arguments = arguments_.ptr[0..arguments_.len];
|
||||
|
||||
if (arguments.len < 1) {
|
||||
return globalObject.throwNotEnoughArguments("hash", 1, 0);
|
||||
}
|
||||
|
||||
var algorithm = PasswordObject.Algorithm.Value.default;
|
||||
|
||||
if (arguments.len > 1 and !arguments[1].isEmptyOrUndefinedOrNull()) {
|
||||
algorithm = try PasswordObject.Algorithm.Value.fromJS(globalObject, arguments[1]);
|
||||
}
|
||||
|
||||
// TODO: this most likely should error like `hashSync` instead of stringifying.
|
||||
//
|
||||
// fromJS(...) orelse {
|
||||
// return globalObject.throwInvalidArgumentType("hash", "password", "string or TypedArray");
|
||||
// }
|
||||
const password_to_hash = try JSC.Node.StringOrBuffer.fromJSToOwnedSlice(globalObject, arguments[0], bun.default_allocator);
|
||||
errdefer bun.default_allocator.free(password_to_hash);
|
||||
|
||||
if (password_to_hash.len == 0) {
|
||||
return globalObject.throwInvalidArguments("password must not be empty", .{});
|
||||
}
|
||||
|
||||
return hash(globalObject, password_to_hash, algorithm, false);
|
||||
}
|
||||
|
||||
// Once we have bindings generator, this should be replaced with a generated function
|
||||
pub fn JSPasswordObject__hashSync(globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
|
||||
const arguments_ = callframe.arguments_old(2);
|
||||
const arguments = arguments_.ptr[0..arguments_.len];
|
||||
|
||||
if (arguments.len < 1) {
|
||||
return globalObject.throwNotEnoughArguments("hash", 1, 0);
|
||||
}
|
||||
|
||||
var algorithm = PasswordObject.Algorithm.Value.default;
|
||||
|
||||
if (arguments.len > 1 and !arguments[1].isEmptyOrUndefinedOrNull()) {
|
||||
algorithm = try PasswordObject.Algorithm.Value.fromJS(globalObject, arguments[1]);
|
||||
}
|
||||
|
||||
var string_or_buffer = try JSC.Node.StringOrBuffer.fromJS(globalObject, bun.default_allocator, arguments[0]) orelse {
|
||||
return globalObject.throwInvalidArgumentType("hash", "password", "string or TypedArray");
|
||||
};
|
||||
defer string_or_buffer.deinit();
|
||||
|
||||
if (string_or_buffer.slice().len == 0) {
|
||||
return globalObject.throwInvalidArguments("password must not be empty", .{});
|
||||
}
|
||||
|
||||
return hash(globalObject, string_or_buffer.slice(), algorithm, true);
|
||||
}
|
||||
|
||||
const VerifyJob = struct {
|
||||
algorithm: ?PasswordObject.Algorithm = null,
|
||||
password: []const u8,
|
||||
prev_hash: []const u8,
|
||||
promise: JSC.JSPromise.Strong,
|
||||
event_loop: *JSC.EventLoop,
|
||||
global: *JSC.JSGlobalObject,
|
||||
ref: Async.KeepAlive = .{},
|
||||
task: JSC.WorkPoolTask = .{ .callback = &run },
|
||||
|
||||
pub usingnamespace bun.New(@This());
|
||||
|
||||
pub const Result = struct {
|
||||
value: Value,
|
||||
ref: Async.KeepAlive = .{},
|
||||
|
||||
task: JSC.AnyTask = undefined,
|
||||
promise: JSC.JSPromise.Strong,
|
||||
global: *JSC.JSGlobalObject,
|
||||
|
||||
pub usingnamespace bun.New(@This());
|
||||
|
||||
pub const Value = union(enum) {
|
||||
err: PasswordObject.HashError,
|
||||
pass: bool,
|
||||
|
||||
pub fn toErrorInstance(this: Value, globalObject: *JSC.JSGlobalObject) JSC.JSValue {
|
||||
const error_code = std.fmt.allocPrint(bun.default_allocator, "PASSWORD{}", .{PascalToUpperUnderscoreCaseFormatter{ .input = @errorName(this.err) }}) catch bun.outOfMemory();
|
||||
defer bun.default_allocator.free(error_code);
|
||||
const instance = globalObject.createErrorInstance("Password verification failed with error \"{s}\"", .{@errorName(this.err)});
|
||||
instance.put(globalObject, ZigString.static("code"), JSC.ZigString.init(error_code).toJS(globalObject));
|
||||
return instance;
|
||||
}
|
||||
};
|
||||
|
||||
pub fn runFromJS(this: *Result) void {
|
||||
var promise = this.promise;
|
||||
defer promise.deinit();
|
||||
this.promise = .{};
|
||||
this.ref.unref(this.global.bunVM());
|
||||
const global = this.global;
|
||||
switch (this.value) {
|
||||
.err => {
|
||||
const error_instance = this.value.toErrorInstance(global);
|
||||
this.destroy();
|
||||
promise.reject(global, error_instance);
|
||||
},
|
||||
.pass => |pass| {
|
||||
this.destroy();
|
||||
promise.resolve(global, JSC.JSValue.jsBoolean(pass));
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub fn deinit(this: *VerifyJob) void {
|
||||
this.promise.deinit();
|
||||
|
||||
bun.freeSensitive(bun.default_allocator, this.password);
|
||||
bun.freeSensitive(bun.default_allocator, this.prev_hash);
|
||||
|
||||
this.destroy();
|
||||
}
|
||||
|
||||
pub fn getValue(password: []const u8, prev_hash: []const u8, algorithm: ?PasswordObject.Algorithm) Result.Value {
|
||||
const pass = PasswordObject.verify(bun.default_allocator, password, prev_hash, algorithm) catch |err| {
|
||||
return Result.Value{ .err = err };
|
||||
};
|
||||
return Result.Value{ .pass = pass };
|
||||
}
|
||||
|
||||
pub fn run(task: *bun.ThreadPool.Task) void {
|
||||
var this: *VerifyJob = @fieldParentPtr("task", task);
|
||||
|
||||
var result = Result.new(.{
|
||||
.value = getValue(this.password, this.prev_hash, this.algorithm),
|
||||
.task = undefined,
|
||||
.promise = this.promise,
|
||||
.global = this.global,
|
||||
.ref = this.ref,
|
||||
});
|
||||
this.promise = .empty;
|
||||
|
||||
result.task = JSC.AnyTask.New(Result, Result.runFromJS).init(result);
|
||||
this.ref = .{};
|
||||
this.event_loop.enqueueTaskConcurrent(JSC.ConcurrentTask.createFrom(&result.task));
|
||||
this.deinit();
|
||||
}
|
||||
};
|
||||
|
||||
// Once we have bindings generator, this should be replaced with a generated function
|
||||
pub fn JSPasswordObject__verify(globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
|
||||
const arguments_ = callframe.arguments_old(3);
|
||||
const arguments = arguments_.ptr[0..arguments_.len];
|
||||
|
||||
if (arguments.len < 2) {
|
||||
return globalObject.throwNotEnoughArguments("verify", 2, 0);
|
||||
}
|
||||
|
||||
var algorithm: ?PasswordObject.Algorithm = null;
|
||||
|
||||
if (arguments.len > 2 and !arguments[2].isEmptyOrUndefinedOrNull()) {
|
||||
if (!arguments[2].isString()) {
|
||||
return globalObject.throwInvalidArgumentType("verify", "algorithm", "string");
|
||||
}
|
||||
|
||||
const algorithm_string = try arguments[2].getZigString(globalObject);
|
||||
|
||||
algorithm = PasswordObject.Algorithm.label.getWithEql(algorithm_string, JSC.ZigString.eqlComptime) orelse {
|
||||
if (!globalObject.hasException()) {
|
||||
return globalObject.throwInvalidArgumentType("verify", "algorithm", unknown_password_algorithm_message);
|
||||
}
|
||||
return error.JSError;
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: this most likely should error like `verifySync` instead of stringifying.
|
||||
//
|
||||
// fromJS(...) orelse {
|
||||
// return globalObject.throwInvalidArgumentType("hash", "password", "string or TypedArray");
|
||||
// }
|
||||
const owned_password = try JSC.Node.StringOrBuffer.fromJSToOwnedSlice(globalObject, arguments[0], bun.default_allocator);
|
||||
|
||||
// TODO: this most likely should error like `verifySync` instead of stringifying.
|
||||
//
|
||||
// fromJS(...) orelse {
|
||||
// return globalObject.throwInvalidArgumentType("hash", "password", "string or TypedArray");
|
||||
// }
|
||||
const owned_hash = JSC.Node.StringOrBuffer.fromJSToOwnedSlice(globalObject, arguments[1], bun.default_allocator) catch |err| {
|
||||
bun.default_allocator.free(owned_password);
|
||||
return err;
|
||||
};
|
||||
|
||||
if (owned_hash.len == 0) {
|
||||
bun.default_allocator.free(owned_password);
|
||||
return JSC.JSPromise.resolvedPromiseValue(globalObject, JSC.JSValue.jsBoolean(false));
|
||||
}
|
||||
|
||||
if (owned_password.len == 0) {
|
||||
bun.default_allocator.free(owned_hash);
|
||||
return JSC.JSPromise.resolvedPromiseValue(globalObject, JSC.JSValue.jsBoolean(false));
|
||||
}
|
||||
|
||||
return verify(globalObject, owned_password, owned_hash, algorithm, false);
|
||||
}
|
||||
|
||||
// Once we have bindings generator, this should be replaced with a generated function
|
||||
pub fn JSPasswordObject__verifySync(globalObject: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.JSError!JSC.JSValue {
|
||||
const arguments_ = callframe.arguments_old(3);
|
||||
const arguments = arguments_.ptr[0..arguments_.len];
|
||||
|
||||
if (arguments.len < 2) {
|
||||
return globalObject.throwNotEnoughArguments("verify", 2, 0);
|
||||
}
|
||||
|
||||
var algorithm: ?PasswordObject.Algorithm = null;
|
||||
|
||||
if (arguments.len > 2 and !arguments[2].isEmptyOrUndefinedOrNull()) {
|
||||
if (!arguments[2].isString()) {
|
||||
return globalObject.throwInvalidArgumentType("verify", "algorithm", "string");
|
||||
}
|
||||
|
||||
const algorithm_string = try arguments[2].getZigString(globalObject);
|
||||
|
||||
algorithm = PasswordObject.Algorithm.label.getWithEql(algorithm_string, JSC.ZigString.eqlComptime) orelse {
|
||||
if (!globalObject.hasException()) {
|
||||
return globalObject.throwInvalidArgumentType("verify", "algorithm", unknown_password_algorithm_message);
|
||||
}
|
||||
return .zero;
|
||||
};
|
||||
}
|
||||
|
||||
var password = try JSC.Node.StringOrBuffer.fromJS(globalObject, bun.default_allocator, arguments[0]) orelse {
|
||||
return globalObject.throwInvalidArgumentType("verify", "password", "string or TypedArray");
|
||||
};
|
||||
|
||||
var hash_ = try JSC.Node.StringOrBuffer.fromJS(globalObject, bun.default_allocator, arguments[1]) orelse {
|
||||
password.deinit();
|
||||
return globalObject.throwInvalidArgumentType("verify", "hash", "string or TypedArray");
|
||||
};
|
||||
|
||||
defer password.deinit();
|
||||
defer hash_.deinit();
|
||||
|
||||
if (hash_.slice().len == 0) {
|
||||
return JSC.JSValue.jsBoolean(false);
|
||||
}
|
||||
|
||||
if (password.slice().len == 0) {
|
||||
return JSC.JSValue.jsBoolean(false);
|
||||
}
|
||||
|
||||
return verify(globalObject, password.slice(), hash_.slice(), algorithm, true);
|
||||
}
|
||||
};
|
||||
|
||||
const std = @import("std");
|
||||
const bun = @import("root").bun;
|
||||
const string = bun.string;
|
||||
const strings = bun.strings;
|
||||
const MutableString = bun.MutableString;
|
||||
const stringZ = bun.stringZ;
|
||||
const default_allocator = bun.default_allocator;
|
||||
const JSC = bun.JSC;
|
||||
const Async = bun.Async;
|
||||
const ZigString = JSC.ZigString;
|
||||
const JSValue = JSC.JSValue;
|
||||
const JSGlobalObject = JSC.JSGlobalObject;
|
||||
const CallFrame = JSC.CallFrame;
|
||||
const assert = bun.assert;
|
||||
|
||||
const unknown_password_algorithm_message = "unknown algorithm, expected one of: \"bcrypt\", \"argon2id\", \"argon2d\", \"argon2i\" (default is \"argon2id\")";
|
||||
@@ -607,7 +607,8 @@ pub const FFI = struct {
|
||||
return error.JSError;
|
||||
}
|
||||
|
||||
if (try generateSymbols(globalThis, allocator, &compile_c.symbols.map, symbols_object)) |val| {
|
||||
// SAFETY: already checked that symbols_object is an object
|
||||
if (try generateSymbols(globalThis, allocator, &compile_c.symbols.map, symbols_object.getObject().?)) |val| {
|
||||
if (val != .zero and !globalThis.hasException())
|
||||
return globalThis.throwValue(val);
|
||||
return error.JSError;
|
||||
@@ -663,9 +664,9 @@ pub const FFI = struct {
|
||||
}
|
||||
|
||||
if (try object.getTruthy(globalThis, "define")) |define_value| {
|
||||
if (define_value.isObject()) {
|
||||
if (define_value.getObject()) |define_obj| {
|
||||
const Iter = JSC.JSPropertyIterator(.{ .include_value = true, .skip_empty_name = true });
|
||||
var iter = try Iter.init(globalThis, define_value);
|
||||
var iter = try Iter.init(globalThis, define_obj);
|
||||
defer iter.deinit();
|
||||
while (try iter.next()) |entry| {
|
||||
const key = entry.toOwnedSliceZ(allocator) catch bun.outOfMemory();
|
||||
@@ -932,12 +933,11 @@ pub const FFI = struct {
|
||||
}
|
||||
}
|
||||
|
||||
if (object.isEmptyOrUndefinedOrNull() or !object.isObject()) {
|
||||
return JSC.toInvalidArguments("Expected an options object with symbol names", .{}, global);
|
||||
}
|
||||
if (object.isEmptyOrUndefinedOrNull()) return invalidOptionsArg(global);
|
||||
const obj = object.getObject() orelse return invalidOptionsArg(global);
|
||||
|
||||
var symbols = bun.StringArrayHashMapUnmanaged(Function){};
|
||||
if (generateSymbols(global, bun.default_allocator, &symbols, object) catch JSC.JSValue.zero) |val| {
|
||||
if (generateSymbols(global, bun.default_allocator, &symbols, obj) catch JSC.JSValue.zero) |val| {
|
||||
// an error while validating symbols
|
||||
for (symbols.keys()) |key| {
|
||||
allocator.free(@constCast(key));
|
||||
@@ -987,34 +987,20 @@ pub const FFI = struct {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// pub fn dlcompile(global: *JSGlobalObject, object: JSC.JSValue) JSValue {
|
||||
// const allocator = VirtualMachine.get().allocator;
|
||||
/// Creates an Exception object indicating that options object is invalid.
|
||||
/// The exception is not thrown on the VM.
|
||||
fn invalidOptionsArg(global: *JSGlobalObject) JSValue {
|
||||
return JSC.toInvalidArguments("Expected an options object with symbol names", .{}, global);
|
||||
}
|
||||
|
||||
// if (object.isEmptyOrUndefinedOrNull() or !object.isObject()) {
|
||||
// return JSC.toInvalidArguments("Expected an options object with symbol names", .{}, global);
|
||||
// }
|
||||
|
||||
// var symbols = bun.StringArrayHashMapUnmanaged(Function){};
|
||||
// if (generateSymbols(global, &symbols, object) catch JSC.JSValue.zero) |val| {
|
||||
// // an error while validating symbols
|
||||
// for (symbols.keys()) |key| {
|
||||
// allocator.free(@constCast(key));
|
||||
// }
|
||||
// symbols.clearAndFree(allocator);
|
||||
// return val;
|
||||
// }
|
||||
|
||||
// }
|
||||
|
||||
pub fn open(global: *JSGlobalObject, name_str: ZigString, object: JSC.JSValue) JSC.JSValue {
|
||||
pub fn open(global: *JSGlobalObject, name_str: ZigString, object_value: JSC.JSValue) JSC.JSValue {
|
||||
JSC.markBinding(@src());
|
||||
const vm = VirtualMachine.get();
|
||||
var name_slice = name_str.toSlice(bun.default_allocator);
|
||||
defer name_slice.deinit();
|
||||
|
||||
if (object.isEmptyOrUndefinedOrNull() or !object.isObject()) {
|
||||
return JSC.toInvalidArguments("Expected an options object with symbol names", .{}, global);
|
||||
}
|
||||
if (object_value.isEmptyOrUndefinedOrNull()) return invalidOptionsArg(global);
|
||||
const object = object_value.getObject() orelse return invalidOptionsArg(global);
|
||||
|
||||
var filepath_buf: bun.PathBuffer = undefined;
|
||||
const name = brk: {
|
||||
@@ -1163,13 +1149,12 @@ pub const FFI = struct {
|
||||
return .undefined;
|
||||
}
|
||||
|
||||
pub fn linkSymbols(global: *JSGlobalObject, object: JSC.JSValue) JSC.JSValue {
|
||||
pub fn linkSymbols(global: *JSGlobalObject, object_value: JSC.JSValue) JSC.JSValue {
|
||||
JSC.markBinding(@src());
|
||||
const allocator = VirtualMachine.get().allocator;
|
||||
|
||||
if (object.isEmptyOrUndefinedOrNull() or !object.isObject()) {
|
||||
return JSC.toInvalidArguments("Expected an options object with symbol names", .{}, global);
|
||||
}
|
||||
if (object_value.isEmptyOrUndefinedOrNull()) return invalidOptionsArg(global);
|
||||
const object = object_value.getObject() orelse return invalidOptionsArg(global);
|
||||
|
||||
var symbols = bun.StringArrayHashMapUnmanaged(Function){};
|
||||
if (generateSymbols(global, allocator, &symbols, object) catch JSC.JSValue.zero) |val| {
|
||||
@@ -1379,7 +1364,7 @@ pub const FFI = struct {
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn generateSymbols(global: *JSGlobalObject, allocator: Allocator, symbols: *bun.StringArrayHashMapUnmanaged(Function), object: JSC.JSValue) bun.JSError!?JSValue {
|
||||
pub fn generateSymbols(global: *JSGlobalObject, allocator: Allocator, symbols: *bun.StringArrayHashMapUnmanaged(Function), object: *JSC.JSObject) bun.JSError!?JSValue {
|
||||
JSC.markBinding(@src());
|
||||
|
||||
var symbols_iter = try JSC.JSPropertyIterator(.{
|
||||
|
||||
@@ -147,6 +147,9 @@ export default [
|
||||
aborted: {
|
||||
getter: "getAborted",
|
||||
},
|
||||
flags: {
|
||||
getter: "getFlags",
|
||||
},
|
||||
finished: {
|
||||
getter: "getFinished",
|
||||
},
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
1114
src/bun.js/api/server/NodeHTTPResponse.zig
Normal file
1114
src/bun.js/api/server/NodeHTTPResponse.zig
Normal file
File diff suppressed because it is too large
Load Diff
1297
src/bun.js/api/server/ServerWebSocket.zig
Normal file
1297
src/bun.js/api/server/ServerWebSocket.zig
Normal file
File diff suppressed because it is too large
Load Diff
@@ -396,6 +396,7 @@ pub const ArrayBuffer = extern struct {
|
||||
return Stream{ .pos = 0, .buf = this.slice() };
|
||||
}
|
||||
|
||||
// TODO: this can throw an error! should use JSError!JSValue
|
||||
pub fn create(globalThis: *JSC.JSGlobalObject, bytes: []const u8, comptime kind: JSC.JSValue.JSType) JSC.JSValue {
|
||||
JSC.markBinding(@src());
|
||||
return switch (comptime kind) {
|
||||
|
||||
@@ -100,4 +100,11 @@ void JSVMClientData::create(VM* vm, void* bunVM)
|
||||
clientData->builtinFunctions().exportNames();
|
||||
}
|
||||
|
||||
WebCore::HTTPHeaderIdentifiers& JSVMClientData::httpHeaderIdentifiers()
|
||||
{
|
||||
if (!m_httpHeaderIdentifiers)
|
||||
m_httpHeaderIdentifiers.emplace();
|
||||
return *m_httpHeaderIdentifiers;
|
||||
}
|
||||
|
||||
} // namespace WebCore
|
||||
|
||||
@@ -23,7 +23,7 @@ class DOMWrapperWorld;
|
||||
#include <wtf/StdLibExtras.h>
|
||||
#include "WebCoreJSBuiltins.h"
|
||||
#include "JSCTaskScheduler.h"
|
||||
|
||||
#include "HTTPHeaderIdentifiers.h"
|
||||
namespace Zig {
|
||||
}
|
||||
|
||||
@@ -104,6 +104,8 @@ public:
|
||||
|
||||
JSC::GCClient::IsoSubspace& domBuiltinConstructorSpace() { return m_domBuiltinConstructorSpace; }
|
||||
|
||||
WebCore::HTTPHeaderIdentifiers& httpHeaderIdentifiers();
|
||||
|
||||
template<typename Func> void forEachOutputConstraintSpace(const Func& func)
|
||||
{
|
||||
for (auto* space : m_outputConstraintSpaces)
|
||||
@@ -128,6 +130,8 @@ private:
|
||||
|
||||
std::unique_ptr<ExtendedDOMClientIsoSubspaces> m_clientSubspaces;
|
||||
Vector<JSC::IsoSubspace*> m_outputConstraintSpaces;
|
||||
|
||||
std::optional<WebCore::HTTPHeaderIdentifiers> m_httpHeaderIdentifiers;
|
||||
};
|
||||
|
||||
} // namespace WebCore
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
#include <wtf/text/WTFString.h>
|
||||
|
||||
#include "BunClientData.h"
|
||||
#include "CommonJSModuleRecord.h"
|
||||
#include "JSCommonJSModule.h"
|
||||
#include "isBuiltinModule.h"
|
||||
|
||||
#include "ImportMetaObject.h"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include <JavaScriptCore/JSMicrotask.h>
|
||||
#include <JavaScriptCore/ObjectConstructor.h>
|
||||
#include <JavaScriptCore/NumberPrototype.h>
|
||||
#include "CommonJSModuleRecord.h"
|
||||
#include "JSCommonJSModule.h"
|
||||
#include "ErrorCode+List.h"
|
||||
#include "JavaScriptCore/ArgList.h"
|
||||
#include "JavaScriptCore/CallData.h"
|
||||
@@ -363,6 +363,8 @@ static char* toFileURI(std::span<const char> span)
|
||||
|
||||
extern "C" size_t Bun__process_dlopen_count;
|
||||
|
||||
extern "C" void CrashHandler__setDlOpenAction(const char* action);
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(Process_functionDlopen, (JSC::JSGlobalObject * globalObject_, JSC::CallFrame* callFrame))
|
||||
{
|
||||
Zig::GlobalObject* globalObject = reinterpret_cast<Zig::GlobalObject*>(globalObject_);
|
||||
@@ -438,7 +440,9 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionDlopen, (JSC::JSGlobalObject * globalOb
|
||||
HMODULE handle = Bun__LoadLibraryBunString(&filename_str);
|
||||
#else
|
||||
CString utf8 = filename.utf8();
|
||||
CrashHandler__setDlOpenAction(utf8.data());
|
||||
void* handle = dlopen(utf8.data(), RTLD_LAZY);
|
||||
CrashHandler__setDlOpenAction(nullptr);
|
||||
#endif
|
||||
|
||||
globalObject->m_pendingNapiModuleDlopenHandle = handle;
|
||||
|
||||
@@ -38,6 +38,14 @@ public:
|
||||
WTF::StringView m_view {};
|
||||
bool m_isCString { false };
|
||||
|
||||
std::span<const uint8_t> bytes() const
|
||||
{
|
||||
if (m_isCString) {
|
||||
return std::span(reinterpret_cast<const uint8_t*>(m_underlying.data()), m_underlying.length());
|
||||
}
|
||||
return std::span(reinterpret_cast<const uint8_t*>(m_view.span8().data()), m_view.length());
|
||||
}
|
||||
|
||||
std::span<const char> span() const
|
||||
{
|
||||
if (m_isCString) {
|
||||
|
||||
@@ -1,37 +1,14 @@
|
||||
#include "Cookie.h"
|
||||
#include "EncodeURIComponent.h"
|
||||
#include "JSCookie.h"
|
||||
#include "helpers.h"
|
||||
#include <JavaScriptCore/ObjectConstructor.h>
|
||||
#include <wtf/WallTime.h>
|
||||
#include <wtf/text/StringToIntegerConversion.h>
|
||||
#include <JavaScriptCore/DateInstance.h>
|
||||
#include "HTTPParsers.h"
|
||||
namespace WebCore {
|
||||
|
||||
extern "C" JSC::EncodedJSValue Cookie__create(JSDOMGlobalObject* globalObject, const ZigString* name, const ZigString* value, const ZigString* domain, const ZigString* path, double expires, bool secure, int32_t sameSite, bool httpOnly, double maxAge, bool partitioned)
|
||||
{
|
||||
String nameStr = Zig::toString(*name);
|
||||
String valueStr = Zig::toString(*value);
|
||||
String domainStr = Zig::toString(*domain);
|
||||
String pathStr = Zig::toString(*path);
|
||||
|
||||
CookieSameSite sameSiteEnum;
|
||||
switch (sameSite) {
|
||||
case 0:
|
||||
sameSiteEnum = CookieSameSite::Strict;
|
||||
break;
|
||||
case 1:
|
||||
sameSiteEnum = CookieSameSite::Lax;
|
||||
break;
|
||||
case 2:
|
||||
sameSiteEnum = CookieSameSite::None;
|
||||
break;
|
||||
default:
|
||||
sameSiteEnum = CookieSameSite::Strict;
|
||||
}
|
||||
|
||||
auto result = Cookie::create(nameStr, valueStr, domainStr, pathStr, expires, secure, sameSiteEnum, httpOnly, maxAge, partitioned);
|
||||
return JSC::JSValue::encode(WebCore::toJSNewlyCreated(globalObject, globalObject, WTFMove(result)));
|
||||
}
|
||||
|
||||
extern "C" WebCore::Cookie* Cookie__fromJS(JSC::EncodedJSValue value)
|
||||
{
|
||||
return WebCoreCast<WebCore::JSCookie, WebCore::Cookie>(value);
|
||||
@@ -41,12 +18,12 @@ Cookie::~Cookie() = default;
|
||||
|
||||
Cookie::Cookie(const String& name, const String& value,
|
||||
const String& domain, const String& path,
|
||||
double expires, bool secure, CookieSameSite sameSite,
|
||||
int64_t expires, bool secure, CookieSameSite sameSite,
|
||||
bool httpOnly, double maxAge, bool partitioned)
|
||||
: m_name(name)
|
||||
, m_value(value)
|
||||
, m_domain(domain)
|
||||
, m_path(path.isEmpty() ? "/"_s : path)
|
||||
, m_path(path)
|
||||
, m_expires(expires)
|
||||
, m_secure(secure)
|
||||
, m_sameSite(sameSite)
|
||||
@@ -56,25 +33,26 @@ Cookie::Cookie(const String& name, const String& value,
|
||||
{
|
||||
}
|
||||
|
||||
Ref<Cookie> Cookie::create(const String& name, const String& value,
|
||||
ExceptionOr<Ref<Cookie>> Cookie::create(const String& name, const String& value,
|
||||
const String& domain, const String& path,
|
||||
double expires, bool secure, CookieSameSite sameSite,
|
||||
int64_t expires, bool secure, CookieSameSite sameSite,
|
||||
bool httpOnly, double maxAge, bool partitioned)
|
||||
{
|
||||
if (!isValidCookieName(name)) {
|
||||
return Exception { TypeError, "Invalid cookie name: contains invalid characters"_s };
|
||||
}
|
||||
if (!isValidCookiePath(path)) {
|
||||
return Exception { TypeError, "Invalid cookie path: contains invalid characters"_s };
|
||||
}
|
||||
if (!isValidCookieDomain(domain)) {
|
||||
return Exception { TypeError, "Invalid cookie domain: contains invalid characters"_s };
|
||||
}
|
||||
return adoptRef(*new Cookie(name, value, domain, path, expires, secure, sameSite, httpOnly, maxAge, partitioned));
|
||||
}
|
||||
|
||||
Ref<Cookie> Cookie::from(const String& name, const String& value,
|
||||
const String& domain, const String& path,
|
||||
double expires, bool secure, CookieSameSite sameSite,
|
||||
bool httpOnly, double maxAge, bool partitioned)
|
||||
String Cookie::serialize(JSC::VM& vm, const std::span<const Ref<Cookie>> cookies)
|
||||
{
|
||||
return create(name, value, domain, path, expires, secure, sameSite, httpOnly, maxAge, partitioned);
|
||||
}
|
||||
|
||||
String Cookie::serialize(JSC::VM& vm, const Vector<Ref<Cookie>>& cookies)
|
||||
{
|
||||
if (cookies.isEmpty())
|
||||
if (cookies.empty())
|
||||
return emptyString();
|
||||
|
||||
StringBuilder builder;
|
||||
@@ -91,107 +69,106 @@ String Cookie::serialize(JSC::VM& vm, const Vector<Ref<Cookie>>& cookies)
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
ExceptionOr<Ref<Cookie>> Cookie::parse(const String& cookieString)
|
||||
ExceptionOr<Ref<Cookie>> Cookie::parse(StringView cookieString)
|
||||
{
|
||||
// Split the cookieString by semicolons
|
||||
Vector<String> parts = cookieString.split(';');
|
||||
// RFC 6265 sec 4.1.1, RFC 2616 2.2 defines a cookie name consists of one char minimum, plus '='.
|
||||
if (UNLIKELY(cookieString.length() < 2)) {
|
||||
return Exception { TypeError, "Invalid cookie string: empty"_s };
|
||||
}
|
||||
|
||||
if (parts.isEmpty())
|
||||
return Exception { TypeError, "Invalid cookie string: empty string"_s };
|
||||
// Find the first name-value pair
|
||||
size_t firstSemicolonPos = cookieString.find(';');
|
||||
StringView cookiePair = firstSemicolonPos == notFound ? cookieString : cookieString.substring(0, firstSemicolonPos);
|
||||
|
||||
// First part is the name-value pair
|
||||
String nameValueStr = parts[0].trim(isASCIIWhitespace<UChar>);
|
||||
size_t equalsPos = nameValueStr.find('=');
|
||||
|
||||
if (equalsPos == notFound)
|
||||
return Exception { TypeError, "Invalid cookie string: missing '=' in name-value pair"_s };
|
||||
|
||||
String name = nameValueStr.substring(0, equalsPos).trim(isASCIIWhitespace<UChar>);
|
||||
String value = nameValueStr.substring(equalsPos + 1).trim(isASCIIWhitespace<UChar>);
|
||||
size_t firstEqualsPos = cookiePair.find('=');
|
||||
if (UNLIKELY(firstEqualsPos == notFound)) {
|
||||
return Exception { TypeError, "Invalid cookie string: no '=' found"_s };
|
||||
}
|
||||
|
||||
String name = cookiePair.substring(0, firstEqualsPos).trim(isASCIIWhitespace<UChar>).toString();
|
||||
if (name.isEmpty())
|
||||
return Exception { TypeError, "Invalid cookie string: name cannot be empty"_s };
|
||||
|
||||
ASSERT(isValidHTTPHeaderValue(name));
|
||||
String value = cookiePair.substring(firstEqualsPos + 1).trim(isASCIIWhitespace<UChar>).toString();
|
||||
|
||||
// Default values
|
||||
String domain;
|
||||
String path = "/"_s;
|
||||
double expires = 0;
|
||||
double maxAge = 0;
|
||||
int64_t expires = Cookie::emptyExpiresAtValue;
|
||||
bool secure = false;
|
||||
bool httpOnly = false;
|
||||
bool partitioned = false;
|
||||
CookieSameSite sameSite = CookieSameSite::Lax;
|
||||
bool httpOnly = false;
|
||||
double maxAge = std::numeric_limits<double>::quiet_NaN();
|
||||
bool partitioned = false;
|
||||
bool hasMaxAge = false;
|
||||
ASSERT(value.isEmpty() || isValidHTTPHeaderValue(value));
|
||||
// Parse attributes if there are any
|
||||
if (firstSemicolonPos != notFound) {
|
||||
auto attributesString = cookieString.substring(firstSemicolonPos + 1);
|
||||
|
||||
// Parse attributes
|
||||
for (size_t i = 1; i < parts.size(); i++) {
|
||||
String part = parts[i].trim(isASCIIWhitespace<UChar>);
|
||||
size_t attrEqualsPos = part.find('=');
|
||||
for (auto attribute : attributesString.split(';')) {
|
||||
auto trimmedAttribute = attribute.trim(isASCIIWhitespace<UChar>);
|
||||
size_t assignmentPos = trimmedAttribute.find('=');
|
||||
|
||||
String attrName;
|
||||
String attrValue;
|
||||
String attributeName;
|
||||
String attributeValue;
|
||||
|
||||
if (attrEqualsPos == notFound) {
|
||||
// Flag attribute like "Secure"
|
||||
attrName = part.convertToASCIILowercase();
|
||||
attrValue = emptyString();
|
||||
} else {
|
||||
attrName = part.substring(0, attrEqualsPos).trim(isASCIIWhitespace<UChar>).convertToASCIILowercase();
|
||||
attrValue = part.substring(attrEqualsPos + 1).trim(isASCIIWhitespace<UChar>);
|
||||
}
|
||||
|
||||
if (attrName == "domain"_s)
|
||||
domain = attrValue;
|
||||
else if (attrName == "path"_s)
|
||||
path = attrValue;
|
||||
else if (attrName == "expires"_s) {
|
||||
if (!attrValue.containsOnlyLatin1())
|
||||
return Exception { TypeError, "Invalid cookie string: expires is not a valid date"_s };
|
||||
|
||||
if (UNLIKELY(!attrValue.is8Bit())) {
|
||||
auto asLatin1 = attrValue.latin1();
|
||||
if (auto parsed = WTF::parseDate({ reinterpret_cast<const LChar*>(asLatin1.data()), asLatin1.length() })) {
|
||||
expires = parsed;
|
||||
} else {
|
||||
return Exception { TypeError, "Invalid cookie string: expires is not a valid date"_s };
|
||||
}
|
||||
if (assignmentPos != notFound) {
|
||||
attributeName = trimmedAttribute.substring(0, assignmentPos).trim(isASCIIWhitespace<UChar>).convertToASCIILowercase();
|
||||
attributeValue = trimmedAttribute.substring(assignmentPos + 1).trim(isASCIIWhitespace<UChar>).toString();
|
||||
} else {
|
||||
if (auto parsed = WTF::parseDate(attrValue.span<LChar>())) {
|
||||
expires = parsed;
|
||||
} else {
|
||||
return Exception { TypeError, "Invalid cookie string: expires is not a valid date"_s };
|
||||
attributeName = trimmedAttribute.convertToASCIILowercase();
|
||||
attributeValue = emptyString();
|
||||
}
|
||||
|
||||
if (attributeName == "domain"_s) {
|
||||
if (!attributeValue.isEmpty()) {
|
||||
domain = attributeValue.convertToASCIILowercase();
|
||||
}
|
||||
} else if (attributeName == "path"_s) {
|
||||
if (!attributeValue.isEmpty() && attributeValue.startsWith('/'))
|
||||
path = attributeValue;
|
||||
} else if (attributeName == "expires"_s && !hasMaxAge && !attributeValue.isEmpty()) {
|
||||
if (UNLIKELY(!attributeValue.is8Bit())) {
|
||||
auto asLatin1 = attributeValue.latin1();
|
||||
if (auto parsed = WTF::parseDate({ reinterpret_cast<const LChar*>(asLatin1.data()), asLatin1.length() })) {
|
||||
expires = static_cast<int64_t>(parsed);
|
||||
}
|
||||
} else {
|
||||
auto nullTerminated = attributeValue.utf8();
|
||||
if (auto parsed = WTF::parseDate(std::span<const LChar>(reinterpret_cast<const LChar*>(nullTerminated.data()), nullTerminated.length()))) {
|
||||
expires = static_cast<int64_t>(parsed);
|
||||
}
|
||||
}
|
||||
} else if (attributeName == "max-age"_s) {
|
||||
if (auto parsed = WTF::parseIntegerAllowingTrailingJunk<int64_t>(attributeValue); parsed.has_value()) {
|
||||
maxAge = static_cast<double>(parsed.value());
|
||||
hasMaxAge = true;
|
||||
}
|
||||
} else if (attributeName == "secure"_s) {
|
||||
secure = true;
|
||||
} else if (attributeName == "httponly"_s) {
|
||||
httpOnly = true;
|
||||
} else if (attributeName == "partitioned"_s) {
|
||||
partitioned = true;
|
||||
} else if (attributeName == "samesite"_s) {
|
||||
if (WTF::equalIgnoringASCIICase(attributeValue, "strict"_s))
|
||||
sameSite = CookieSameSite::Strict;
|
||||
else if (WTF::equalIgnoringASCIICase(attributeValue, "lax"_s))
|
||||
sameSite = CookieSameSite::Lax;
|
||||
else if (WTF::equalIgnoringASCIICase(attributeValue, "none"_s))
|
||||
sameSite = CookieSameSite::None;
|
||||
}
|
||||
} else if (attrName == "max-age"_s) {
|
||||
if (auto parsed = WTF::parseIntegerAllowingTrailingJunk<int64_t>(attrValue); parsed.has_value()) {
|
||||
maxAge = static_cast<double>(parsed.value());
|
||||
} else {
|
||||
return Exception { TypeError, "Invalid cookie string: max-age is not a number"_s };
|
||||
}
|
||||
} else if (attrName == "secure"_s)
|
||||
secure
|
||||
= true;
|
||||
else if (attrName == "httponly"_s)
|
||||
httpOnly
|
||||
= true;
|
||||
else if (attrName == "partitioned"_s)
|
||||
partitioned
|
||||
= true;
|
||||
else if (attrName == "samesite"_s) {
|
||||
if (WTF::equalIgnoringASCIICase(attrValue, "strict"_s))
|
||||
sameSite = CookieSameSite::Strict;
|
||||
else if (WTF::equalIgnoringASCIICase(attrValue, "lax"_s))
|
||||
sameSite = CookieSameSite::Lax;
|
||||
else if (WTF::equalIgnoringASCIICase(attrValue, "none"_s))
|
||||
sameSite = CookieSameSite::None;
|
||||
}
|
||||
}
|
||||
|
||||
return adoptRef(*new Cookie(name, value, domain, path, expires, secure, sameSite, httpOnly, maxAge, partitioned));
|
||||
return Cookie::create(name, value, domain, path, expires, secure, sameSite, httpOnly, maxAge, partitioned);
|
||||
}
|
||||
|
||||
bool Cookie::isExpired() const
|
||||
{
|
||||
if (m_expires == 0)
|
||||
if (m_expires == Cookie::emptyExpiresAtValue || m_expires < 1)
|
||||
return false; // Session cookie
|
||||
|
||||
auto currentTime = WTF::WallTime::now().secondsSinceEpoch().seconds() * 1000.0;
|
||||
@@ -205,12 +182,74 @@ String Cookie::toString(JSC::VM& vm) const
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
static inline bool isValidCharacterInCookieName(UChar c)
|
||||
{
|
||||
return (c >= 0x21 && c <= 0x3A) || (c == 0x3C) || (c >= 0x3E && c <= 0x7E);
|
||||
}
|
||||
bool Cookie::isValidCookieName(const String& name)
|
||||
{
|
||||
// /^[\u0021-\u003A\u003C\u003E-\u007E]+$/
|
||||
if (name.length() == 0) return false; // disallow empty name
|
||||
if (name.is8Bit()) {
|
||||
for (auto c : name.span8()) {
|
||||
if (!isValidCharacterInCookieName(c)) return false;
|
||||
}
|
||||
} else {
|
||||
for (auto c : name.span16()) {
|
||||
if (!isValidCharacterInCookieName(c)) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
static inline bool isValidCharacterInCookiePath(UChar c)
|
||||
{
|
||||
return (c >= 0x20 && c <= 0x3A) || (c >= 0x3D && c <= 0x7E);
|
||||
}
|
||||
bool Cookie::isValidCookiePath(const String& path)
|
||||
{
|
||||
// /^[\u0020-\u003A\u003D-\u007E]*$/
|
||||
if (path.is8Bit()) {
|
||||
for (auto c : path.span8()) {
|
||||
if (!isValidCharacterInCookiePath(c)) return false;
|
||||
}
|
||||
} else {
|
||||
for (auto c : path.span16()) {
|
||||
if (!isValidCharacterInCookiePath(c)) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline bool isValidCharacterInCookieDomain(UChar c)
|
||||
{
|
||||
return (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '.' || c == '-';
|
||||
}
|
||||
bool Cookie::isValidCookieDomain(const String& domain)
|
||||
{
|
||||
// TODO: /^([.]?[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)([.][a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*$/i
|
||||
// for now, require all characters to be [a-z0-9.-]
|
||||
if (domain.is8Bit()) {
|
||||
for (auto c : domain.span8()) {
|
||||
if (!isValidCharacterInCookieDomain(c)) return false;
|
||||
}
|
||||
} else {
|
||||
for (auto c : domain.span16()) {
|
||||
if (!isValidCharacterInCookieDomain(c)) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Cookie::appendTo(JSC::VM& vm, StringBuilder& builder) const
|
||||
{
|
||||
// Name=Value is the basic format
|
||||
builder.append(m_name);
|
||||
builder.append('=');
|
||||
builder.append(m_value);
|
||||
auto result = encodeURIComponent(vm, m_value, builder);
|
||||
if (result.hasException()) {
|
||||
// m_value contained unpaired surrogate. oops!
|
||||
// fortunately, this never happens because the string has already had invalid surrogate pairs converted to the replacement character
|
||||
}
|
||||
|
||||
// Add domain if present
|
||||
if (!m_domain.isEmpty()) {
|
||||
@@ -218,23 +257,23 @@ void Cookie::appendTo(JSC::VM& vm, StringBuilder& builder) const
|
||||
builder.append(m_domain);
|
||||
}
|
||||
|
||||
if (!m_path.isEmpty() && m_path != "/"_s) {
|
||||
if (!m_path.isEmpty()) {
|
||||
builder.append("; Path="_s);
|
||||
builder.append(m_path);
|
||||
}
|
||||
|
||||
// Add expires if present (not 0)
|
||||
if (m_expires != 0) {
|
||||
// Add expires if present
|
||||
if (hasExpiry()) {
|
||||
builder.append("; Expires="_s);
|
||||
// In a real implementation, this would convert the timestamp to a proper date string
|
||||
// For now, just use a numeric timestamp
|
||||
WTF::GregorianDateTime dateTime;
|
||||
vm.dateCache.msToGregorianDateTime(m_expires * 1000, WTF::TimeType::UTCTime, dateTime);
|
||||
vm.dateCache.msToGregorianDateTime(m_expires, WTF::TimeType::UTCTime, dateTime);
|
||||
builder.append(WTF::makeRFC2822DateString(dateTime.weekDay(), dateTime.monthDay(), dateTime.month(), dateTime.year(), dateTime.hour(), dateTime.minute(), dateTime.second(), dateTime.utcOffsetInMinute()));
|
||||
}
|
||||
|
||||
// Add Max-Age if present
|
||||
if (m_maxAge != 0) {
|
||||
if (!std::isnan(m_maxAge)) {
|
||||
builder.append("; Max-Age="_s);
|
||||
builder.append(String::number(m_maxAge));
|
||||
}
|
||||
@@ -255,13 +294,15 @@ void Cookie::appendTo(JSC::VM& vm, StringBuilder& builder) const
|
||||
|
||||
switch (m_sameSite) {
|
||||
case CookieSameSite::Strict:
|
||||
builder.append("; SameSite=strict"_s);
|
||||
builder.append("; SameSite=Strict"_s);
|
||||
break;
|
||||
case CookieSameSite::Lax:
|
||||
// lax is the default.
|
||||
// lax is the default. but we still need to set it explicitly.
|
||||
// https://groups.google.com/a/chromium.org/g/blink-dev/c/AknSSyQTGYs/m/YKBxPCScCwAJ
|
||||
builder.append("; SameSite=Lax"_s);
|
||||
break;
|
||||
case CookieSameSite::None:
|
||||
builder.append("; SameSite=none"_s);
|
||||
builder.append("; SameSite=None"_s);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -283,10 +324,10 @@ JSC::JSValue Cookie::toJSON(JSC::VM& vm, JSC::JSGlobalObject* globalObject) cons
|
||||
|
||||
object->putDirect(vm, builtinNames.pathPublicName(), JSC::jsString(vm, m_path));
|
||||
|
||||
if (m_expires != 0)
|
||||
object->putDirect(vm, builtinNames.expiresPublicName(), JSC::jsNumber(m_expires));
|
||||
if (hasExpiry())
|
||||
object->putDirect(vm, builtinNames.expiresPublicName(), JSC::DateInstance::create(vm, globalObject->dateStructure(), m_expires));
|
||||
|
||||
if (m_maxAge != 0)
|
||||
if (!std::isnan(m_maxAge))
|
||||
object->putDirect(vm, builtinNames.maxAgePublicName(), JSC::jsNumber(m_maxAge));
|
||||
|
||||
object->putDirect(vm, builtinNames.securePublicName(), JSC::jsBoolean(m_secure));
|
||||
|
||||
@@ -15,37 +15,80 @@ enum class CookieSameSite : uint8_t {
|
||||
|
||||
JSC::JSValue toJS(JSC::JSGlobalObject*, CookieSameSite);
|
||||
|
||||
struct CookieInit {
|
||||
String name = String();
|
||||
String value = String();
|
||||
String domain = String();
|
||||
String path = "/"_s;
|
||||
|
||||
int64_t expires = emptyExpiresAtValue;
|
||||
bool secure = false;
|
||||
CookieSameSite sameSite = CookieSameSite::Lax;
|
||||
bool httpOnly = false;
|
||||
double maxAge = std::numeric_limits<double>::quiet_NaN();
|
||||
bool partitioned = false;
|
||||
static constexpr int64_t emptyExpiresAtValue = std::numeric_limits<int64_t>::min();
|
||||
|
||||
static std::optional<CookieInit> fromJS(JSC::VM& vm, JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSValue value);
|
||||
static std::optional<CookieInit> fromJS(JSC::VM& vm, JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSValue input, String name, String cookieValue);
|
||||
};
|
||||
|
||||
class Cookie : public RefCounted<Cookie> {
|
||||
public:
|
||||
~Cookie();
|
||||
|
||||
static Ref<Cookie> create(const String& name, const String& value,
|
||||
static constexpr int64_t emptyExpiresAtValue = std::numeric_limits<int64_t>::min();
|
||||
static ExceptionOr<Ref<Cookie>> create(const String& name, const String& value,
|
||||
const String& domain, const String& path,
|
||||
double expires, bool secure, CookieSameSite sameSite,
|
||||
int64_t expires, bool secure, CookieSameSite sameSite,
|
||||
bool httpOnly, double maxAge, bool partitioned);
|
||||
|
||||
static ExceptionOr<Ref<Cookie>> parse(const String& cookieString);
|
||||
static Ref<Cookie> from(const String& name, const String& value,
|
||||
const String& domain, const String& path,
|
||||
double expires, bool secure, CookieSameSite sameSite,
|
||||
bool httpOnly, double maxAge, bool partitioned);
|
||||
static ExceptionOr<Ref<Cookie>> create(const CookieInit& init)
|
||||
{
|
||||
if (!isValidCookieName(init.name)) {
|
||||
return Exception { TypeError, "Invalid cookie name: contains invalid characters"_s };
|
||||
}
|
||||
if (!isValidCookiePath(init.path)) {
|
||||
return Exception { TypeError, "Invalid cookie path: contains invalid characters"_s };
|
||||
}
|
||||
if (!isValidCookieDomain(init.domain)) {
|
||||
return Exception { TypeError, "Invalid cookie domain: contains invalid characters"_s };
|
||||
}
|
||||
|
||||
static String serialize(JSC::VM& vm, const Vector<Ref<Cookie>>& cookies);
|
||||
return create(init.name, init.value, init.domain, init.path, init.expires, init.secure, init.sameSite, init.httpOnly, init.maxAge, init.partitioned);
|
||||
}
|
||||
|
||||
static ExceptionOr<Ref<Cookie>> parse(StringView cookieString);
|
||||
|
||||
static String serialize(JSC::VM& vm, const std::span<const Ref<Cookie>> cookies);
|
||||
|
||||
const String& name() const { return m_name; }
|
||||
void setName(const String& name) { m_name = name; }
|
||||
|
||||
const String& value() const { return m_value; }
|
||||
void setValue(const String& value) { m_value = value; }
|
||||
|
||||
const String& domain() const { return m_domain; }
|
||||
void setDomain(const String& domain) { m_domain = domain; }
|
||||
ExceptionOr<void> setDomain(const String& domain)
|
||||
{
|
||||
if (!isValidCookieDomain(domain)) {
|
||||
return Exception { TypeError, "Invalid cookie domain: contains invalid characters"_s };
|
||||
}
|
||||
m_domain = domain;
|
||||
return {};
|
||||
}
|
||||
|
||||
const String& path() const { return m_path; }
|
||||
void setPath(const String& path) { m_path = path; }
|
||||
ExceptionOr<void> setPath(const String& path)
|
||||
{
|
||||
if (!isValidCookiePath(path)) {
|
||||
return Exception { TypeError, "Invalid cookie path: contains invalid characters"_s };
|
||||
}
|
||||
m_path = path;
|
||||
return {};
|
||||
}
|
||||
|
||||
double expires() const { return m_expires; }
|
||||
void setExpires(double expires) { m_expires = expires; }
|
||||
int64_t expires() const { return m_expires; }
|
||||
void setExpires(int64_t ms) { m_expires = ms; }
|
||||
bool hasExpiry() const { return m_expires != emptyExpiresAtValue; }
|
||||
|
||||
bool secure() const { return m_secure; }
|
||||
void setSecure(bool secure) { m_secure = secure; }
|
||||
@@ -69,22 +112,27 @@ public:
|
||||
JSC::JSValue toJSON(JSC::VM& vm, JSC::JSGlobalObject*) const;
|
||||
size_t memoryCost() const;
|
||||
|
||||
static bool isValidCookieName(const String& name);
|
||||
static bool isValidCookieValue(const String& value); // values are uri component encoded, so this isn't needed
|
||||
static bool isValidCookiePath(const String& path);
|
||||
static bool isValidCookieDomain(const String& domain);
|
||||
|
||||
private:
|
||||
Cookie(const String& name, const String& value,
|
||||
const String& domain, const String& path,
|
||||
double expires, bool secure, CookieSameSite sameSite,
|
||||
int64_t expires, bool secure, CookieSameSite sameSite,
|
||||
bool httpOnly, double maxAge, bool partitioned);
|
||||
|
||||
String m_name;
|
||||
String m_value;
|
||||
String m_domain;
|
||||
String m_path;
|
||||
double m_expires;
|
||||
bool m_secure;
|
||||
CookieSameSite m_sameSite;
|
||||
bool m_httpOnly;
|
||||
double m_maxAge;
|
||||
bool m_partitioned;
|
||||
int64_t m_expires = Cookie::emptyExpiresAtValue;
|
||||
bool m_secure = false;
|
||||
CookieSameSite m_sameSite = CookieSameSite::Lax;
|
||||
bool m_httpOnly = false;
|
||||
double m_maxAge = 0;
|
||||
bool m_partitioned = false;
|
||||
};
|
||||
|
||||
} // namespace WebCore
|
||||
|
||||
@@ -1,21 +1,40 @@
|
||||
#include "CookieMap.h"
|
||||
#include "JSCookieMap.h"
|
||||
#include <bun-uws/src/App.h>
|
||||
#include "helpers.h"
|
||||
#include <wtf/text/ParsingUtilities.h>
|
||||
#include <JavaScriptCore/ObjectConstructor.h>
|
||||
|
||||
#include "HTTPParsers.h"
|
||||
#include "decodeURIComponentSIMD.h"
|
||||
#include "BunString.h"
|
||||
namespace WebCore {
|
||||
|
||||
extern "C" JSC::EncodedJSValue CookieMap__create(JSDOMGlobalObject* globalObject, const ZigString* initStr)
|
||||
template<bool isSSL>
|
||||
void CookieMap__writeFetchHeadersToUWSResponse(CookieMap* cookie_map, JSC::JSGlobalObject* global_this, uWS::HttpResponse<isSSL>* res)
|
||||
{
|
||||
String str = Zig::toString(*initStr);
|
||||
auto result = CookieMap::create(std::variant<Vector<Vector<String>>, HashMap<String, String>, String>(str));
|
||||
return JSC::JSValue::encode(WebCore::toJSNewlyCreated(globalObject, globalObject, result.releaseReturnValue()));
|
||||
// Loop over modified cookies and write Set-Cookie headers to the response
|
||||
for (auto& cookie : cookie_map->getAllChanges()) {
|
||||
auto utf8 = cookie->toString(global_this->vm()).utf8();
|
||||
res->writeHeader("Set-Cookie", utf8.data());
|
||||
}
|
||||
}
|
||||
extern "C" void CookieMap__write(CookieMap* cookie_map, JSC::JSGlobalObject* global_this, bool ssl_enabled, void* arg2)
|
||||
{
|
||||
if (ssl_enabled) {
|
||||
CookieMap__writeFetchHeadersToUWSResponse<true>(cookie_map, global_this, reinterpret_cast<uWS::HttpResponse<true>*>(arg2));
|
||||
} else {
|
||||
CookieMap__writeFetchHeadersToUWSResponse<false>(cookie_map, global_this, reinterpret_cast<uWS::HttpResponse<false>*>(arg2));
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" WebCore::CookieMap* CookieMap__fromJS(JSC::EncodedJSValue value)
|
||||
extern "C" void CookieMap__ref(CookieMap* cookie_map)
|
||||
{
|
||||
return WebCoreCast<WebCore::JSCookieMap, WebCore::CookieMap>(value);
|
||||
cookie_map->ref();
|
||||
}
|
||||
|
||||
extern "C" void CookieMap__deref(CookieMap* cookie_map)
|
||||
{
|
||||
cookie_map->deref();
|
||||
}
|
||||
|
||||
CookieMap::~CookieMap() = default;
|
||||
@@ -24,228 +43,186 @@ CookieMap::CookieMap()
|
||||
{
|
||||
}
|
||||
|
||||
CookieMap::CookieMap(const String& cookieString)
|
||||
CookieMap::CookieMap(Vector<Ref<Cookie>>&& cookies)
|
||||
: m_modifiedCookies(WTFMove(cookies))
|
||||
{
|
||||
if (cookieString.isEmpty())
|
||||
return;
|
||||
|
||||
Vector<String> pairs = cookieString.split(';');
|
||||
for (auto& pair : pairs) {
|
||||
pair = pair.trim(isASCIIWhitespace<UChar>);
|
||||
if (pair.isEmpty())
|
||||
continue;
|
||||
|
||||
size_t equalsPos = pair.find('=');
|
||||
if (equalsPos == notFound)
|
||||
continue;
|
||||
|
||||
String name = pair.substring(0, equalsPos).trim(isASCIIWhitespace<UChar>);
|
||||
String value = pair.substring(equalsPos + 1).trim(isASCIIWhitespace<UChar>);
|
||||
|
||||
auto cookie = Cookie::create(name, value, String(), "/"_s, 0, false, CookieSameSite::Lax, false, 0, false);
|
||||
m_cookies.append(WTFMove(cookie));
|
||||
}
|
||||
}
|
||||
|
||||
CookieMap::CookieMap(const HashMap<String, String>& pairs)
|
||||
CookieMap::CookieMap(Vector<KeyValuePair<String, String>>&& cookies)
|
||||
: m_originalCookies(WTFMove(cookies))
|
||||
{
|
||||
for (auto& entry : pairs) {
|
||||
auto cookie = Cookie::create(entry.key, entry.value, String(), "/"_s, 0, false, CookieSameSite::Lax,
|
||||
false, 0, false);
|
||||
m_cookies.append(WTFMove(cookie));
|
||||
}
|
||||
}
|
||||
|
||||
CookieMap::CookieMap(const Vector<Vector<String>>& pairs)
|
||||
{
|
||||
for (const auto& pair : pairs) {
|
||||
if (pair.size() == 2) {
|
||||
auto cookie = Cookie::create(pair[0], pair[1], String(), "/"_s, 0, false, CookieSameSite::Lax, false, 0, false);
|
||||
m_cookies.append(WTFMove(cookie));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ExceptionOr<Ref<CookieMap>> CookieMap::create(std::variant<Vector<Vector<String>>, HashMap<String, String>, String>&& variant)
|
||||
ExceptionOr<Ref<CookieMap>> CookieMap::create(std::variant<Vector<Vector<String>>, HashMap<String, String>, String>&& variant, bool throwOnInvalidCookieString)
|
||||
{
|
||||
auto visitor = WTF::makeVisitor(
|
||||
[&](const Vector<Vector<String>>& pairs) -> ExceptionOr<Ref<CookieMap>> {
|
||||
return adoptRef(*new CookieMap(pairs));
|
||||
Vector<KeyValuePair<String, String>> cookies;
|
||||
for (const auto& pair : pairs) {
|
||||
if (pair.size() == 2) {
|
||||
if (!pair[1].isEmpty() && !isValidHTTPHeaderValue(pair[1])) {
|
||||
if (throwOnInvalidCookieString) {
|
||||
return Exception { TypeError, "Invalid cookie string: cookie value is not valid"_s };
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
cookies.append(KeyValuePair<String, String>(pair[0], pair[1]));
|
||||
} else if (throwOnInvalidCookieString) {
|
||||
return Exception { TypeError, "Invalid cookie string: expected name=value pair"_s };
|
||||
}
|
||||
}
|
||||
return adoptRef(*new CookieMap(WTFMove(cookies)));
|
||||
},
|
||||
[&](const HashMap<String, String>& pairs) -> ExceptionOr<Ref<CookieMap>> {
|
||||
return adoptRef(*new CookieMap(pairs));
|
||||
Vector<KeyValuePair<String, String>> cookies;
|
||||
for (const auto& entry : pairs) {
|
||||
if (!entry.value.isEmpty() && !isValidHTTPHeaderValue(entry.value)) {
|
||||
if (throwOnInvalidCookieString) {
|
||||
return Exception { TypeError, "Invalid cookie string: cookie value is not valid"_s };
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
cookies.append(KeyValuePair<String, String>(entry.key, entry.value));
|
||||
}
|
||||
|
||||
return adoptRef(*new CookieMap(WTFMove(cookies)));
|
||||
},
|
||||
[&](const String& cookieString) -> ExceptionOr<Ref<CookieMap>> {
|
||||
return adoptRef(*new CookieMap(cookieString));
|
||||
StringView forCookieHeader = cookieString;
|
||||
if (forCookieHeader.isEmpty()) {
|
||||
return adoptRef(*new CookieMap());
|
||||
}
|
||||
|
||||
auto pairs = forCookieHeader.split(';');
|
||||
Vector<KeyValuePair<String, String>> cookies;
|
||||
|
||||
bool hasAnyPercentEncoded = forCookieHeader.find('%') != notFound;
|
||||
for (auto pair : pairs) {
|
||||
String name = ""_s;
|
||||
String value = ""_s;
|
||||
|
||||
auto equalsPos = pair.find('=');
|
||||
if (equalsPos == notFound) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto nameView = pair.substring(0, equalsPos).trim(isASCIIWhitespace<UChar>);
|
||||
auto valueView = pair.substring(equalsPos + 1).trim(isASCIIWhitespace<UChar>);
|
||||
|
||||
if (nameView.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (hasAnyPercentEncoded) {
|
||||
Bun::UTF8View utf8View(nameView);
|
||||
name = Bun::decodeURIComponentSIMD(utf8View.bytes());
|
||||
} else {
|
||||
name = nameView.toString();
|
||||
}
|
||||
|
||||
if (hasAnyPercentEncoded) {
|
||||
Bun::UTF8View utf8View(valueView);
|
||||
value = Bun::decodeURIComponentSIMD(utf8View.bytes());
|
||||
} else {
|
||||
value = valueView.toString();
|
||||
}
|
||||
|
||||
cookies.append(KeyValuePair<String, String>(name, value));
|
||||
}
|
||||
|
||||
return adoptRef(*new CookieMap(WTFMove(cookies)));
|
||||
});
|
||||
|
||||
return std::visit(visitor, variant);
|
||||
}
|
||||
|
||||
RefPtr<Cookie> CookieMap::get(const String& name) const
|
||||
std::optional<String> CookieMap::get(const String& name) const
|
||||
{
|
||||
// Return the first cookie with the matching name
|
||||
for (auto& cookie : m_cookies) {
|
||||
if (cookie->name() == name)
|
||||
return RefPtr<Cookie>(cookie.ptr());
|
||||
auto modifiedCookieIndex = m_modifiedCookies.findIf([&](auto& cookie) {
|
||||
return cookie->name() == name;
|
||||
});
|
||||
if (modifiedCookieIndex != notFound) {
|
||||
// a set cookie with an empty value is treated as not existing, because that is what delete() sets
|
||||
if (m_modifiedCookies[modifiedCookieIndex]->value().isEmpty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return std::optional<String>(m_modifiedCookies[modifiedCookieIndex]->value());
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RefPtr<Cookie> CookieMap::get(const CookieStoreGetOptions& options) const
|
||||
{
|
||||
// If name is provided, use that for lookup
|
||||
if (!options.name.isEmpty())
|
||||
return get(options.name);
|
||||
|
||||
// If url is provided, use that for lookup
|
||||
if (!options.url.isEmpty()) {
|
||||
// TODO: Implement URL-based cookie lookup
|
||||
// This would involve parsing the URL, extracting the domain, and
|
||||
// finding the first cookie that matches that domain
|
||||
auto originalCookieIndex = m_originalCookies.findIf([&](auto& cookie) {
|
||||
return cookie.key == name;
|
||||
});
|
||||
if (originalCookieIndex != notFound) {
|
||||
return std::optional<String>(m_originalCookies[originalCookieIndex].value);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
Vector<Ref<Cookie>> CookieMap::getAll(const String& name) const
|
||||
Vector<KeyValuePair<String, String>> CookieMap::getAll() const
|
||||
{
|
||||
// Return all cookies with the matching name
|
||||
Vector<Ref<Cookie>> result;
|
||||
for (auto& cookie : m_cookies) {
|
||||
if (cookie->name() == name)
|
||||
result.append(cookie);
|
||||
Vector<KeyValuePair<String, String>> all;
|
||||
for (const auto& cookie : m_modifiedCookies) {
|
||||
if (cookie->value().isEmpty()) continue;
|
||||
all.append(KeyValuePair<String, String>(cookie->name(), cookie->value()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Vector<Ref<Cookie>> CookieMap::getAll(const CookieStoreGetOptions& options) const
|
||||
{
|
||||
// If name is provided, use that for lookup
|
||||
if (!options.name.isEmpty())
|
||||
return getAll(options.name);
|
||||
|
||||
// If url is provided, use that for lookup
|
||||
if (!options.url.isEmpty()) {
|
||||
// TODO: Implement URL-based cookie lookup
|
||||
// This would involve parsing the URL, extracting the domain, and
|
||||
// finding all cookies that match that domain
|
||||
for (const auto& cookie : m_originalCookies) {
|
||||
all.append(KeyValuePair<String, String>(cookie.key, cookie.value));
|
||||
}
|
||||
|
||||
return Vector<Ref<Cookie>>();
|
||||
return all;
|
||||
}
|
||||
|
||||
bool CookieMap::has(const String& name, const String& value) const
|
||||
bool CookieMap::has(const String& name) const
|
||||
{
|
||||
for (auto& cookie : m_cookies) {
|
||||
if (cookie->name() == name && (value.isEmpty() || cookie->value() == value))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return get(name).has_value();
|
||||
}
|
||||
|
||||
void CookieMap::set(const String& name, const String& value, bool httpOnly, bool partitioned, double maxAge)
|
||||
void CookieMap::removeInternal(const String& name)
|
||||
{
|
||||
// Remove any existing cookies with the same name
|
||||
remove(name);
|
||||
|
||||
// Add the new cookie with proper settings
|
||||
auto cookie = Cookie::create(name, value, String(), "/"_s, 0, false, CookieSameSite::Strict,
|
||||
httpOnly, maxAge, partitioned);
|
||||
m_cookies.append(WTFMove(cookie));
|
||||
}
|
||||
|
||||
// Maintain backward compatibility with code that uses the old signature
|
||||
void CookieMap::set(const String& name, const String& value)
|
||||
{
|
||||
// Remove any existing cookies with the same name
|
||||
remove(name);
|
||||
|
||||
// Add the new cookie
|
||||
auto cookie = Cookie::create(name, value, String(), "/"_s, 0, false, CookieSameSite::Strict, false, 0, false);
|
||||
m_cookies.append(WTFMove(cookie));
|
||||
}
|
||||
|
||||
void CookieMap::set(Ref<Cookie> cookie)
|
||||
{
|
||||
// Remove any existing cookies with the same name
|
||||
remove(cookie->name());
|
||||
|
||||
// Add the new cookie
|
||||
m_cookies.append(WTFMove(cookie));
|
||||
}
|
||||
|
||||
void CookieMap::remove(const String& name)
|
||||
{
|
||||
m_cookies.removeAllMatching([&name](const auto& cookie) {
|
||||
// Remove any existing matching cookies
|
||||
m_originalCookies.removeAllMatching([&](auto& cookie) {
|
||||
return cookie.key == name;
|
||||
});
|
||||
m_modifiedCookies.removeAllMatching([&](auto& cookie) {
|
||||
return cookie->name() == name;
|
||||
});
|
||||
}
|
||||
|
||||
void CookieMap::remove(const CookieStoreDeleteOptions& options)
|
||||
void CookieMap::set(Ref<Cookie> cookie)
|
||||
{
|
||||
removeInternal(cookie->name());
|
||||
// Add the new cookie
|
||||
m_modifiedCookies.append(WTFMove(cookie));
|
||||
}
|
||||
|
||||
ExceptionOr<void> CookieMap::remove(const CookieStoreDeleteOptions& options)
|
||||
{
|
||||
removeInternal(options.name);
|
||||
|
||||
String name = options.name;
|
||||
String domain = options.domain;
|
||||
String path = options.path;
|
||||
|
||||
m_cookies.removeAllMatching([&](const auto& cookie) {
|
||||
if (cookie->name() != name)
|
||||
return false;
|
||||
|
||||
// If domain is specified, it must match
|
||||
if (!domain.isNull() && cookie->domain() != domain)
|
||||
return false;
|
||||
|
||||
// If path is specified, it must match
|
||||
if (!path.isNull() && cookie->path() != path)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
});
|
||||
// Add the new cookie
|
||||
auto cookie_exception = Cookie::create(name, ""_s, domain, path, 1, false, CookieSameSite::Lax, false, std::numeric_limits<double>::quiet_NaN(), false);
|
||||
if (cookie_exception.hasException()) {
|
||||
return cookie_exception.releaseException();
|
||||
}
|
||||
auto cookie = cookie_exception.releaseReturnValue();
|
||||
m_modifiedCookies.append(WTFMove(cookie));
|
||||
return {};
|
||||
}
|
||||
|
||||
Vector<Ref<Cookie>> CookieMap::getCookiesMatchingDomain(const String& domain) const
|
||||
size_t CookieMap::size() const
|
||||
{
|
||||
Vector<Ref<Cookie>> result;
|
||||
for (auto& cookie : m_cookies) {
|
||||
const auto& cookieDomain = cookie->domain();
|
||||
if (cookieDomain.isEmpty() || cookieDomain == domain) {
|
||||
result.append(cookie);
|
||||
}
|
||||
size_t size = 0;
|
||||
for (const auto& cookie : m_modifiedCookies) {
|
||||
if (cookie->value().isEmpty()) continue;
|
||||
size += 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Vector<Ref<Cookie>> CookieMap::getCookiesMatchingPath(const String& path) const
|
||||
{
|
||||
Vector<Ref<Cookie>> result;
|
||||
for (auto& cookie : m_cookies) {
|
||||
// Simple path matching logic - a cookie matches if its path is a prefix of the requested path
|
||||
if (path.startsWith(cookie->path())) {
|
||||
result.append(cookie);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
String CookieMap::toString(JSC::VM& vm) const
|
||||
{
|
||||
if (m_cookies.isEmpty())
|
||||
return emptyString();
|
||||
|
||||
StringBuilder builder;
|
||||
bool first = true;
|
||||
|
||||
for (auto& cookie : m_cookies) {
|
||||
if (!first)
|
||||
builder.append("; "_s);
|
||||
|
||||
cookie->appendTo(vm, builder);
|
||||
|
||||
first = false;
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
size += m_originalCookies.size();
|
||||
return size;
|
||||
}
|
||||
|
||||
JSC::JSValue CookieMap::toJSON(JSC::JSGlobalObject* globalObject) const
|
||||
@@ -253,46 +230,59 @@ JSC::JSValue CookieMap::toJSON(JSC::JSGlobalObject* globalObject) const
|
||||
auto& vm = globalObject->vm();
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
// Create an array of cookie entries
|
||||
auto* array = JSC::constructEmptyArray(globalObject, nullptr, m_cookies.size());
|
||||
RETURN_IF_EXCEPTION(scope, JSC::jsNull());
|
||||
// Create an object to hold cookie key-value pairs
|
||||
auto* object = JSC::constructEmptyObject(globalObject);
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
|
||||
unsigned index = 0;
|
||||
for (const auto& cookie : m_cookies) {
|
||||
// For each cookie, create a [name, cookie JSON] entry
|
||||
auto* entryArray = JSC::constructEmptyArray(globalObject, nullptr, 2);
|
||||
RETURN_IF_EXCEPTION(scope, JSC::jsNull());
|
||||
|
||||
entryArray->putDirectIndex(globalObject, 0, JSC::jsString(vm, cookie->name()));
|
||||
RETURN_IF_EXCEPTION(scope, JSC::jsNull());
|
||||
|
||||
entryArray->putDirectIndex(globalObject, 1, cookie->toJSON(vm, globalObject));
|
||||
RETURN_IF_EXCEPTION(scope, JSC::jsNull());
|
||||
|
||||
array->putDirectIndex(globalObject, index++, entryArray);
|
||||
RETURN_IF_EXCEPTION(scope, JSC::jsNull());
|
||||
// Add modified cookies to the object
|
||||
for (const auto& cookie : m_modifiedCookies) {
|
||||
if (!cookie->value().isEmpty()) {
|
||||
object->putDirect(vm, JSC::Identifier::fromString(vm, cookie->name()), JSC::jsString(vm, cookie->value()));
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
}
|
||||
}
|
||||
|
||||
return array;
|
||||
// Add original cookies to the object
|
||||
for (const auto& cookie : m_originalCookies) {
|
||||
// Skip if this cookie name was already added from modified cookies
|
||||
if (!object->hasProperty(globalObject, JSC::Identifier::fromString(vm, cookie.key))) {
|
||||
object->putDirect(vm, JSC::Identifier::fromString(vm, cookie.key), JSC::jsString(vm, cookie.value));
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
}
|
||||
}
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
size_t CookieMap::memoryCost() const
|
||||
{
|
||||
size_t cost = sizeof(CookieMap);
|
||||
for (auto& cookie : m_cookies) {
|
||||
cost += cookie->memoryCost();
|
||||
for (auto& cookie : m_originalCookies) {
|
||||
cost += cookie.key.sizeInBytes();
|
||||
cost += cookie.value.sizeInBytes();
|
||||
}
|
||||
for (auto& cookie : m_modifiedCookies) {
|
||||
cost += cookie->name().sizeInBytes();
|
||||
cost += cookie->value().sizeInBytes();
|
||||
}
|
||||
return cost;
|
||||
}
|
||||
|
||||
std::optional<CookieMap::KeyValuePair> CookieMap::Iterator::next()
|
||||
std::optional<KeyValuePair<String, String>> CookieMap::Iterator::next()
|
||||
{
|
||||
auto& cookies = m_target->m_cookies;
|
||||
if (m_index >= cookies.size())
|
||||
return std::nullopt;
|
||||
while (m_index < m_target->m_modifiedCookies.size() + m_target->m_originalCookies.size()) {
|
||||
if (m_index >= m_target->m_modifiedCookies.size()) {
|
||||
return m_target->m_originalCookies[(m_index++) - m_target->m_modifiedCookies.size()];
|
||||
}
|
||||
|
||||
auto& cookie = cookies[m_index++];
|
||||
return KeyValuePair(cookie->name(), cookie.ptr());
|
||||
auto result = m_target->m_modifiedCookies[m_index++];
|
||||
if (result->value().isEmpty()) {
|
||||
continue; // deleted; skip
|
||||
}
|
||||
|
||||
return KeyValuePair<String, String>(result->name(), result->value());
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
CookieMap::Iterator::Iterator(CookieMap& cookieMap)
|
||||
|
||||
@@ -12,59 +12,43 @@
|
||||
namespace WebCore {
|
||||
|
||||
struct CookieStoreGetOptions {
|
||||
String name;
|
||||
String url;
|
||||
String name {};
|
||||
String url {};
|
||||
};
|
||||
|
||||
struct CookieStoreDeleteOptions {
|
||||
String name;
|
||||
String domain;
|
||||
String path;
|
||||
String name {};
|
||||
String domain {};
|
||||
String path {};
|
||||
};
|
||||
|
||||
class CookieMap : public RefCounted<CookieMap> {
|
||||
public:
|
||||
~CookieMap();
|
||||
|
||||
static ExceptionOr<Ref<CookieMap>> create(std::variant<Vector<Vector<String>>, HashMap<String, String>, String>&& init);
|
||||
// Define a simple struct to hold the key-value pair
|
||||
|
||||
RefPtr<Cookie> get(const String& name) const;
|
||||
RefPtr<Cookie> get(const CookieStoreGetOptions& options) const;
|
||||
static ExceptionOr<Ref<CookieMap>> create(std::variant<Vector<Vector<String>>, HashMap<String, String>, String>&& init, bool throwOnInvalidCookieString = true);
|
||||
|
||||
Vector<Ref<Cookie>> getAll(const String& name) const;
|
||||
Vector<Ref<Cookie>> getAll(const CookieStoreGetOptions& options) const;
|
||||
std::optional<String> get(const String& name) const;
|
||||
Vector<KeyValuePair<String, String>> getAll() const;
|
||||
Vector<Ref<Cookie>> getAllChanges() const { return m_modifiedCookies; }
|
||||
|
||||
bool has(const String& name, const String& value = String()) const;
|
||||
bool has(const String& name) const;
|
||||
|
||||
void set(const String& name, const String& value, bool httpOnly, bool partitioned, double maxAge);
|
||||
void set(const String& name, const String& value);
|
||||
void set(Ref<Cookie>);
|
||||
|
||||
void remove(const String& name);
|
||||
void remove(const CookieStoreDeleteOptions& options);
|
||||
ExceptionOr<void> remove(const CookieStoreDeleteOptions& options);
|
||||
|
||||
String toString(JSC::VM& vm) const;
|
||||
JSC::JSValue toJSON(JSC::JSGlobalObject*) const;
|
||||
size_t size() const { return m_cookies.size(); }
|
||||
size_t size() const;
|
||||
size_t memoryCost() const;
|
||||
|
||||
// Define a simple struct to hold the key-value pair
|
||||
struct KeyValuePair {
|
||||
KeyValuePair(const String& k, RefPtr<Cookie> c)
|
||||
: key(k)
|
||||
, value(c)
|
||||
{
|
||||
}
|
||||
|
||||
String key;
|
||||
RefPtr<Cookie> value;
|
||||
};
|
||||
|
||||
class Iterator {
|
||||
public:
|
||||
explicit Iterator(CookieMap&);
|
||||
|
||||
std::optional<KeyValuePair> next();
|
||||
std::optional<KeyValuePair<String, String>> next();
|
||||
|
||||
private:
|
||||
Ref<CookieMap> m_target;
|
||||
@@ -76,14 +60,13 @@ public:
|
||||
|
||||
private:
|
||||
CookieMap();
|
||||
CookieMap(const String& cookieString);
|
||||
CookieMap(const HashMap<String, String>& pairs);
|
||||
CookieMap(const Vector<Vector<String>>& pairs);
|
||||
CookieMap(Vector<Ref<Cookie>>&& cookies);
|
||||
CookieMap(Vector<KeyValuePair<String, String>>&& cookies);
|
||||
|
||||
Vector<Ref<Cookie>> getCookiesMatchingDomain(const String& domain) const;
|
||||
Vector<Ref<Cookie>> getCookiesMatchingPath(const String& path) const;
|
||||
void removeInternal(const String& name);
|
||||
|
||||
Vector<Ref<Cookie>> m_cookies;
|
||||
Vector<KeyValuePair<String, String>> m_originalCookies;
|
||||
Vector<Ref<Cookie>> m_modifiedCookies;
|
||||
};
|
||||
|
||||
} // namespace WebCore
|
||||
|
||||
101
src/bun.js/bindings/EncodeURIComponent.cpp
Normal file
101
src/bun.js/bindings/EncodeURIComponent.cpp
Normal file
@@ -0,0 +1,101 @@
|
||||
#include "EncodeURIComponent.h"
|
||||
|
||||
// from JSGlobalObjectFunctions.cpp
|
||||
|
||||
namespace JSC {
|
||||
|
||||
template<typename CharacterType>
|
||||
static WebCore::ExceptionOr<void> encode(VM& vm, const WTF::BitSet<256>& doNotEscape, std::span<const CharacterType> characters, StringBuilder& builder)
|
||||
{
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
// 18.2.6.1.1 Runtime Semantics: Encode ( string, unescapedSet )
|
||||
// https://tc39.github.io/ecma262/#sec-encode
|
||||
|
||||
auto throwException = [] {
|
||||
return WebCore::ExceptionOr<void>(WebCore::Exception { WebCore::EncodingError, "String contained an illegal UTF-16 sequence."_s });
|
||||
};
|
||||
|
||||
builder.reserveCapacity(characters.size());
|
||||
|
||||
// 4. Repeat
|
||||
auto* end = characters.data() + characters.size();
|
||||
for (auto* cursor = characters.data(); cursor != end; ++cursor) {
|
||||
auto character = *cursor;
|
||||
|
||||
// 4-c. If C is in unescapedSet, then
|
||||
if (character < doNotEscape.size() && doNotEscape.get(character)) {
|
||||
// 4-c-i. Let S be a String containing only the code unit C.
|
||||
// 4-c-ii. Let R be a new String value computed by concatenating the previous value of R and S.
|
||||
builder.append(static_cast<LChar>(character));
|
||||
continue;
|
||||
}
|
||||
|
||||
// 4-d-i. If the code unit value of C is not less than 0xDC00 and not greater than 0xDFFF, throw a URIError exception.
|
||||
if (U16_IS_TRAIL(character))
|
||||
return throwException();
|
||||
|
||||
// 4-d-ii. If the code unit value of C is less than 0xD800 or greater than 0xDBFF, then
|
||||
// 4-d-ii-1. Let V be the code unit value of C.
|
||||
char32_t codePoint;
|
||||
if (!U16_IS_LEAD(character))
|
||||
codePoint = character;
|
||||
else {
|
||||
// 4-d-iii. Else,
|
||||
// 4-d-iii-1. Increase k by 1.
|
||||
++cursor;
|
||||
|
||||
// 4-d-iii-2. If k equals strLen, throw a URIError exception.
|
||||
if (cursor == end)
|
||||
return throwException();
|
||||
|
||||
// 4-d-iii-3. Let kChar be the code unit value of the code unit at index k within string.
|
||||
auto trail = *cursor;
|
||||
|
||||
// 4-d-iii-4. If kChar is less than 0xDC00 or greater than 0xDFFF, throw a URIError exception.
|
||||
if (!U16_IS_TRAIL(trail))
|
||||
return throwException();
|
||||
|
||||
// 4-d-iii-5. Let V be UTF16Decode(C, kChar).
|
||||
codePoint = U16_GET_SUPPLEMENTARY(character, trail);
|
||||
}
|
||||
|
||||
// 4-d-iv. Let Octets be the array of octets resulting by applying the UTF-8 transformation to V, and let L be the array size.
|
||||
LChar utf8OctetsBuffer[U8_MAX_LENGTH];
|
||||
unsigned utf8Length = 0;
|
||||
// We can use U8_APPEND_UNSAFE here since codePoint is either
|
||||
// 1. non surrogate one, correct code point.
|
||||
// 2. correct code point generated from validated lead and trail surrogates.
|
||||
U8_APPEND_UNSAFE(utf8OctetsBuffer, utf8Length, codePoint);
|
||||
|
||||
// 4-d-v. Let j be 0.
|
||||
// 4-d-vi. Repeat, while j < L
|
||||
for (unsigned index = 0; index < utf8Length; ++index) {
|
||||
// 4-d-vi-1. Let jOctet be the value at index j within Octets.
|
||||
// 4-d-vi-2. Let S be a String containing three code units "%XY" where XY are two uppercase hexadecimal digits encoding the value of jOctet.
|
||||
// 4-d-vi-3. Let R be a new String value computed by concatenating the previous value of R and S.
|
||||
builder.append('%');
|
||||
builder.append(hex(utf8OctetsBuffer[index], 2));
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static WebCore::ExceptionOr<void> encode(VM& vm, WTF::StringView view, const WTF::BitSet<256>& doNotEscape, StringBuilder& builder)
|
||||
{
|
||||
if (view.is8Bit())
|
||||
return encode(vm, doNotEscape, view.span8(), builder);
|
||||
return encode(vm, doNotEscape, view.span16(), builder);
|
||||
}
|
||||
|
||||
WebCore::ExceptionOr<void> encodeURIComponent(VM& vm, WTF::StringView source, StringBuilder& builder)
|
||||
{
|
||||
static constexpr auto doNotEscapeWhenEncodingURIComponent = makeLatin1CharacterBitSet(
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz"
|
||||
"0123456789"
|
||||
"!'()*-._~");
|
||||
return encode(vm, source, doNotEscapeWhenEncodingURIComponent, builder);
|
||||
}
|
||||
|
||||
}
|
||||
12
src/bun.js/bindings/EncodeURIComponent.h
Normal file
12
src/bun.js/bindings/EncodeURIComponent.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
#include "root.h"
|
||||
|
||||
#include <JavaScriptCore/ObjectConstructor.h>
|
||||
#include <wtf/text/StringToIntegerConversion.h>
|
||||
#include <JavaScriptCore/DateInstance.h>
|
||||
#include "ExceptionOr.h"
|
||||
|
||||
namespace JSC {
|
||||
// errors if the string includes unpaired surrogates
|
||||
WebCore::ExceptionOr<void> encodeURIComponent(VM& vm, WTF::StringView source, StringBuilder& builder);
|
||||
}
|
||||
@@ -647,6 +647,24 @@ JSC::EncodedJSValue INVALID_ARG_TYPE(JSC::ThrowScope& throwScope, JSC::JSGlobalO
|
||||
return {};
|
||||
}
|
||||
|
||||
JSC::EncodedJSValue INVALID_ARG_TYPE_INSTANCE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, WTF::ASCIILiteral arg_name, WTF::ASCIILiteral expected_type, WTF::ASCIILiteral expected_instance_types, JSC::JSValue val_actual_value)
|
||||
{
|
||||
JSC::VM& vm = globalObject->vm();
|
||||
WTF::StringBuilder builder;
|
||||
builder.append("The \""_s);
|
||||
builder.append(arg_name);
|
||||
builder.append("\" argument must be of type "_s);
|
||||
builder.append(expected_type);
|
||||
builder.append(" or an instance of "_s);
|
||||
builder.append(expected_instance_types);
|
||||
builder.append(". Received "_s);
|
||||
determineSpecificType(vm, globalObject, builder, val_actual_value);
|
||||
RETURN_IF_EXCEPTION(throwScope, {});
|
||||
|
||||
throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_INVALID_ARG_TYPE, builder.toString()));
|
||||
return {};
|
||||
}
|
||||
|
||||
// When you want INVALID_ARG_TYPE to say "The argument must be an instance of X. Received Y." instead of "The argument must be of type X. Received Y."
|
||||
JSC::EncodedJSValue INVALID_ARG_INSTANCE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::String& arg_name, const WTF::String& expected_type, JSC::JSValue val_actual_value)
|
||||
{
|
||||
|
||||
@@ -68,6 +68,7 @@ namespace ERR {
|
||||
JSC::EncodedJSValue INVALID_ARG_TYPE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::String& arg_name, const WTF::String& expected_type, JSC::JSValue val_actual_value);
|
||||
JSC::EncodedJSValue INVALID_ARG_TYPE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue arg_name, const WTF::String& expected_type, JSC::JSValue val_actual_value);
|
||||
JSC::EncodedJSValue INVALID_ARG_INSTANCE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::String& arg_name, const WTF::String& expected_type, JSC::JSValue val_actual_value);
|
||||
JSC::EncodedJSValue INVALID_ARG_TYPE_INSTANCE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, WTF::ASCIILiteral arg_name, WTF::ASCIILiteral expected_type, WTF::ASCIILiteral expected_instance_types, JSC::JSValue val_actual_value);
|
||||
JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::String& arg_name, double lower, double upper, JSC::JSValue actual);
|
||||
JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue arg_name, double lower, double upper, JSC::JSValue actual);
|
||||
JSC::EncodedJSValue OUT_OF_RANGE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue arg_name_val, double bound_num, Bound bound, JSC::JSValue actual);
|
||||
|
||||
@@ -35,7 +35,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
namespace WebCore {
|
||||
|
||||
template<typename T> class ExceptionOr {
|
||||
template<typename T> class [[nodiscard]] ExceptionOr {
|
||||
public:
|
||||
using ReturnType = T;
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
#include <JavaScriptCore/LazyProperty.h>
|
||||
#include <JavaScriptCore/LazyPropertyInlines.h>
|
||||
#include <JavaScriptCore/VMTrapsInlines.h>
|
||||
#include "CommonJSModuleRecord.h"
|
||||
#include "JSCommonJSModule.h"
|
||||
#include <JavaScriptCore/JSPromise.h>
|
||||
#include "PathInlines.h"
|
||||
#include "wtf/text/StringView.h"
|
||||
@@ -304,7 +304,7 @@ extern "C" JSC::EncodedJSValue functionImportMeta__resolveSyncPrivate(JSC::JSGlo
|
||||
if (LIKELY(globalObject)) {
|
||||
if (UNLIKELY(globalObject->hasOverriddenModuleResolveFilenameFunction)) {
|
||||
auto overrideHandler = jsCast<JSObject*>(globalObject->m_moduleResolveFilenameFunction.getInitializedOnMainThread(globalObject));
|
||||
if (UNLIKELY(overrideHandler)) {
|
||||
if (LIKELY(overrideHandler)) {
|
||||
ASSERT(overrideHandler->isCallable());
|
||||
JSValue parentModuleObject = globalObject->requireMap()->get(globalObject, from);
|
||||
|
||||
|
||||
@@ -66,9 +66,12 @@ JSObject* JSBunRequest::cookies() const
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
extern "C" void Request__setCookiesOnRequestContext(void* internalZigRequestPointer, CookieMap* cookieMap);
|
||||
|
||||
void JSBunRequest::setCookies(JSObject* cookies)
|
||||
{
|
||||
m_cookies.set(Base::vm(), this, cookies);
|
||||
Request__setCookiesOnRequestContext(this->wrapped(), WebCoreCast<WebCore::JSCookieMap, WebCore::CookieMap>(JSValue::encode(cookies)));
|
||||
}
|
||||
|
||||
JSBunRequest::JSBunRequest(JSC::VM& vm, JSC::Structure* structure, void* sinkPtr)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "JSCookieMap.h"
|
||||
#include "root.h"
|
||||
#include "ZigGeneratedClasses.h"
|
||||
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
const std = @import("std");
|
||||
const bun = @import("root").bun;
|
||||
const GetterSetter = @import("GetterSetter.zig").GetterSetter;
|
||||
const CustomGetterSetter = @import("CustomGetterSetter.zig").CustomGetterSetter;
|
||||
const FFI = @import("FFI.zig");
|
||||
const JSC = bun.JSC;
|
||||
const JSValue = JSC.JSValue;
|
||||
const JSGlobalObject = JSC.JSGlobalObject;
|
||||
const GetterSetter = @import("GetterSetter.zig").GetterSetter;
|
||||
const CustomGetterSetter = @import("CustomGetterSetter.zig").CustomGetterSetter;
|
||||
|
||||
pub const JSCell = opaque {
|
||||
/// Statically cast a cell to a JSObject. Returns null for non-objects.
|
||||
/// Use `toObject` to mutate non-objects into objects.
|
||||
pub fn getObject(this: *JSCell) ?*JSC.JSObject {
|
||||
JSC.markMemberBinding(JSCell, @src());
|
||||
return JSC__JSCell__getObject(this);
|
||||
}
|
||||
|
||||
@@ -17,12 +19,17 @@ pub const JSCell = opaque {
|
||||
///
|
||||
/// Statically casts cells that are already objects, otherwise mutates them
|
||||
/// into objects.
|
||||
///
|
||||
/// ## References
|
||||
/// - [ECMA-262 §7.1.18 ToObject](https://tc39.es/ecma262/#sec-toobject)
|
||||
pub fn toObject(this: *JSCell, global: *JSC.JSGlobalObject) *JSC.JSObject {
|
||||
JSC.markMemberBinding(JSCell, @src());
|
||||
return JSC__JSCell__toObject(this, global);
|
||||
}
|
||||
|
||||
pub fn getType(this: *JSCell) u8 {
|
||||
return JSC__JSCell__getType(this);
|
||||
pub fn getType(this: *const JSCell) u8 {
|
||||
JSC.markMemberBinding(JSCell, @src());
|
||||
return @enumFromInt(JSC__JSCell__getType(this));
|
||||
}
|
||||
|
||||
pub fn toJS(this: *JSCell) JSC.JSValue {
|
||||
@@ -43,7 +50,15 @@ pub const JSCell = opaque {
|
||||
return @as(*CustomGetterSetter, @ptrCast(@alignCast(this)));
|
||||
}
|
||||
|
||||
pub fn ensureStillAlive(this: *JSCell) void {
|
||||
std.mem.doNotOptimizeAway(this);
|
||||
}
|
||||
|
||||
extern fn JSC__JSCell__getObject(this: *JSCell) *JSC.JSObject;
|
||||
extern fn JSC__JSCell__toObject(this: *JSCell, *JSGlobalObject) *JSC.JSObject;
|
||||
// NOTE: this function always returns a JSType, but by using `u8` then
|
||||
// casting it via `@enumFromInt` we can ensure our `JSType` enum matches
|
||||
// WebKit's. This protects us from possible future breaking changes made
|
||||
// when upgrading WebKit.
|
||||
extern fn JSC__JSCell__getType(this: *JSCell) u8;
|
||||
};
|
||||
|
||||
181
src/bun.js/bindings/JSCommonJSExtensions.cpp
Normal file
181
src/bun.js/bindings/JSCommonJSExtensions.cpp
Normal file
@@ -0,0 +1,181 @@
|
||||
#include "JSCommonJSExtensions.h"
|
||||
#include "ZigGlobalObject.h"
|
||||
|
||||
namespace Bun {
|
||||
using namespace JSC;
|
||||
|
||||
const JSC::ClassInfo JSCommonJSExtensions::s_info = { "CommonJSExtensions"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSCommonJSExtensions) };
|
||||
|
||||
// These functions are implemented as no-ops because it doesn't seem like any
|
||||
// projects call them directly. They are defined separately so that assigning
|
||||
// one to the other can be detected and use the corresponding loader.
|
||||
JSC_DEFINE_HOST_FUNCTION(jsLoaderJS, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
|
||||
{
|
||||
auto scope = DECLARE_THROW_SCOPE(globalObject->vm());
|
||||
throwTypeError(globalObject, scope, "Calling Module._extensions[\".js\"] directly is not implemented."_s);
|
||||
return JSValue::encode({});
|
||||
}
|
||||
JSC_DEFINE_HOST_FUNCTION(jsLoaderJSON, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
|
||||
{
|
||||
auto scope = DECLARE_THROW_SCOPE(globalObject->vm());
|
||||
throwTypeError(globalObject, scope, "Calling Module._extensions[\".json\"] directly is not implemented."_s);
|
||||
return JSValue::encode({});
|
||||
}
|
||||
JSC_DEFINE_HOST_FUNCTION(jsLoaderNode, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
|
||||
{
|
||||
auto scope = DECLARE_THROW_SCOPE(globalObject->vm());
|
||||
throwTypeError(globalObject, scope, "Calling Module._extensions[\".node\"] directly is not implemented."_s);
|
||||
return JSValue::encode({});
|
||||
}
|
||||
JSC_DEFINE_HOST_FUNCTION(jsLoaderTS, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
|
||||
{
|
||||
auto scope = DECLARE_THROW_SCOPE(globalObject->vm());
|
||||
throwTypeError(globalObject, scope, "Calling Module._extensions[\".ts\"] directly is not implemented."_s);
|
||||
return JSValue::encode({});
|
||||
}
|
||||
|
||||
void JSCommonJSExtensions::finishCreation(JSC::VM& vm)
|
||||
{
|
||||
Base::finishCreation(vm);
|
||||
ASSERT(inherits(info()));
|
||||
|
||||
Zig::GlobalObject* global = defaultGlobalObject(globalObject());
|
||||
JSC::JSFunction* fnLoadJS = JSC::JSFunction::create(
|
||||
vm,
|
||||
global,
|
||||
2,
|
||||
""_s,
|
||||
jsLoaderJS,
|
||||
JSC::ImplementationVisibility::Public,
|
||||
JSC::Intrinsic::NoIntrinsic,
|
||||
JSC::callHostFunctionAsConstructor);
|
||||
JSC::JSFunction* fnLoadJSON = JSC::JSFunction::create(
|
||||
vm,
|
||||
global,
|
||||
2,
|
||||
""_s,
|
||||
jsLoaderJSON,
|
||||
JSC::ImplementationVisibility::Public,
|
||||
JSC::Intrinsic::NoIntrinsic,
|
||||
JSC::callHostFunctionAsConstructor);
|
||||
JSC::JSFunction* fnLoadNode = JSC::JSFunction::create(
|
||||
vm,
|
||||
global,
|
||||
2,
|
||||
""_s,
|
||||
jsLoaderNode,
|
||||
JSC::ImplementationVisibility::Public,
|
||||
JSC::Intrinsic::NoIntrinsic,
|
||||
JSC::callHostFunctionAsConstructor);
|
||||
JSC::JSFunction* fnLoadTS = JSC::JSFunction::create(
|
||||
vm,
|
||||
global,
|
||||
2,
|
||||
""_s,
|
||||
jsLoaderTS,
|
||||
JSC::ImplementationVisibility::Public,
|
||||
JSC::Intrinsic::NoIntrinsic,
|
||||
JSC::callHostFunctionAsConstructor);
|
||||
|
||||
this->putDirect(vm, JSC::Identifier::fromString(vm, ".js"_s), fnLoadJS, 0);
|
||||
this->putDirect(vm, JSC::Identifier::fromString(vm, ".json"_s), fnLoadJSON, 0);
|
||||
this->putDirect(vm, JSC::Identifier::fromString(vm, ".node"_s), fnLoadNode, 0);
|
||||
this->putDirect(vm, JSC::Identifier::fromString(vm, ".ts"_s), fnLoadTS, 0);
|
||||
this->putDirect(vm, JSC::Identifier::fromString(vm, ".cts"_s), fnLoadTS, 0);
|
||||
this->putDirect(vm, JSC::Identifier::fromString(vm, ".mjs"_s), fnLoadJS, 0);
|
||||
this->putDirect(vm, JSC::Identifier::fromString(vm, ".mts"_s), fnLoadTS, 0);
|
||||
}
|
||||
|
||||
extern "C" void NodeModuleModule__onRequireExtensionModify(
|
||||
Zig::GlobalObject* globalObject,
|
||||
const BunString* key,
|
||||
uint32_t kind,
|
||||
JSC::JSValue value);
|
||||
|
||||
void onAssign(Zig::GlobalObject* globalObject, JSC::PropertyName propertyName, JSC::JSValue value)
|
||||
{
|
||||
if (propertyName.isSymbol()) return;
|
||||
auto* name = propertyName.publicName();
|
||||
if (!name->startsWith("."_s)) return;
|
||||
BunString ext = Bun::toString(name);
|
||||
uint32_t kind = 0;
|
||||
if (value.isCallable()) {
|
||||
JSC::CallData callData = JSC::getCallData(value);
|
||||
if (callData.type == JSC::CallData::Type::Native) {
|
||||
auto* untaggedPtr = callData.native.function.untaggedPtr();
|
||||
if (untaggedPtr == &jsLoaderJS) {
|
||||
kind = 1;
|
||||
} else if (untaggedPtr == &jsLoaderJSON) {
|
||||
kind = 2;
|
||||
} else if (untaggedPtr == &jsLoaderNode) {
|
||||
kind = 3;
|
||||
} else if (untaggedPtr == &jsLoaderTS) {
|
||||
kind = 4;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
kind = -1;
|
||||
}
|
||||
NodeModuleModule__onRequireExtensionModify(globalObject, &ext, kind, value);
|
||||
}
|
||||
|
||||
bool JSCommonJSExtensions::defineOwnProperty(JSC::JSObject* object, JSC::JSGlobalObject* globalObject, JSC::PropertyName propertyName, const JSC::PropertyDescriptor& descriptor, bool shouldThrow)
|
||||
{
|
||||
JSValue value = descriptor.value();
|
||||
if (value) {
|
||||
onAssign(defaultGlobalObject(globalObject), propertyName, value);
|
||||
} else {
|
||||
onAssign(defaultGlobalObject(globalObject), propertyName, JSC::jsUndefined());
|
||||
}
|
||||
return Base::defineOwnProperty(object, globalObject, propertyName, descriptor, shouldThrow);
|
||||
}
|
||||
|
||||
bool JSCommonJSExtensions::put(JSC::JSCell* cell, JSC::JSGlobalObject* globalObject, JSC::PropertyName propertyName, JSC::JSValue value, JSC::PutPropertySlot& slot)
|
||||
{
|
||||
onAssign(defaultGlobalObject(globalObject), propertyName, value);
|
||||
return Base::put(cell, globalObject, propertyName, value, slot);
|
||||
}
|
||||
|
||||
bool JSCommonJSExtensions::deleteProperty(JSC::JSCell* cell, JSC::JSGlobalObject* globalObject, JSC::PropertyName propertyName, JSC::DeletePropertySlot& slot)
|
||||
{
|
||||
bool deleted = Base::deleteProperty(cell, globalObject, propertyName, slot);
|
||||
if (deleted) {
|
||||
onAssign(defaultGlobalObject(globalObject), propertyName, JSC::jsUndefined());
|
||||
}
|
||||
return deleted;
|
||||
}
|
||||
|
||||
extern "C" uint32_t JSCommonJSExtensions__appendFunction(Zig::GlobalObject* globalObject, JSC::JSValue value)
|
||||
{
|
||||
JSCommonJSExtensions* extensions = globalObject->lazyRequireExtensionsObject();
|
||||
extensions->m_registeredFunctions.append(JSC::WriteBarrier<Unknown>());
|
||||
extensions->m_registeredFunctions.last().set(globalObject->vm(), extensions, value);
|
||||
return extensions->m_registeredFunctions.size() - 1;
|
||||
}
|
||||
|
||||
extern "C" void JSCommonJSExtensions__setFunction(Zig::GlobalObject* globalObject, uint32_t index, JSC::JSValue value)
|
||||
{
|
||||
JSCommonJSExtensions* extensions = globalObject->lazyRequireExtensionsObject();
|
||||
extensions->m_registeredFunctions[index].set(globalObject->vm(), globalObject, value);
|
||||
}
|
||||
|
||||
extern "C" uint32_t JSCommonJSExtensions__swapRemove(Zig::GlobalObject* globalObject, uint32_t index)
|
||||
{
|
||||
JSCommonJSExtensions* extensions = globalObject->lazyRequireExtensionsObject();
|
||||
ASSERT(extensions->m_registeredFunctions.size() > 0);
|
||||
if (extensions->m_registeredFunctions.size() == 1) {
|
||||
extensions->m_registeredFunctions.clear();
|
||||
return index;
|
||||
}
|
||||
ASSERT(index < extensions->m_registeredFunctions.size());
|
||||
if (index < (extensions->m_registeredFunctions.size() - 1)) {
|
||||
JSValue last = extensions->m_registeredFunctions.takeLast().get();
|
||||
extensions->m_registeredFunctions[index].set(globalObject->vm(), globalObject, last);
|
||||
return extensions->m_registeredFunctions.size();
|
||||
} else {
|
||||
extensions->m_registeredFunctions.removeLast();
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Bun
|
||||
58
src/bun.js/bindings/JSCommonJSExtensions.h
Normal file
58
src/bun.js/bindings/JSCommonJSExtensions.h
Normal file
@@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
#include "root.h"
|
||||
#include "headers-handwritten.h"
|
||||
#include "BunClientData.h"
|
||||
|
||||
namespace Bun {
|
||||
|
||||
// require.extensions & Module._extensions
|
||||
class JSCommonJSExtensions : public JSC::JSDestructibleObject {
|
||||
public:
|
||||
using Base = JSC::JSDestructibleObject;
|
||||
static constexpr unsigned StructureFlags = Base::StructureFlags | JSC::OverridesPut;
|
||||
~JSCommonJSExtensions();
|
||||
|
||||
WTF::Vector<JSC::WriteBarrier<JSC::Unknown>> m_registeredFunctions;
|
||||
|
||||
static JSCommonJSExtensions* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure)
|
||||
{
|
||||
JSCommonJSExtensions* ptr = new (NotNull, JSC::allocateCell<JSCommonJSExtensions>(vm)) JSCommonJSExtensions(vm, structure);
|
||||
ptr->finishCreation(vm);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
DECLARE_INFO;
|
||||
|
||||
template<typename, JSC::SubspaceAccess mode>
|
||||
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
|
||||
{
|
||||
if constexpr (mode == JSC::SubspaceAccess::Concurrently)
|
||||
return nullptr;
|
||||
return WebCore::subspaceForImpl<JSCommonJSExtensions, WebCore::UseCustomHeapCellType::No>(
|
||||
vm,
|
||||
[](auto& spaces) { return spaces.m_clientSubspaceForJSCommonJSExtensions.get(); },
|
||||
[](auto& spaces, auto&& space) { spaces.m_clientSubspaceForJSCommonJSExtensions = std::forward<decltype(space)>(space); },
|
||||
[](auto& spaces) { return spaces.m_subspaceForJSCommonJSExtensions.get(); },
|
||||
[](auto& spaces, auto&& space) { spaces.m_subspaceForJSCommonJSExtensions = std::forward<decltype(space)>(space); });
|
||||
}
|
||||
|
||||
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
|
||||
{
|
||||
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
|
||||
}
|
||||
|
||||
protected:
|
||||
static bool defineOwnProperty(JSC::JSObject*, JSC::JSGlobalObject*, JSC::PropertyName, const JSC::PropertyDescriptor&, bool shouldThrow);
|
||||
static bool put(JSC::JSCell*, JSC::JSGlobalObject*, JSC::PropertyName, JSC::JSValue, JSC::PutPropertySlot&);
|
||||
static bool deleteProperty(JSC::JSCell*, JSC::JSGlobalObject*, JSC::PropertyName, JSC::DeletePropertySlot&);
|
||||
|
||||
private:
|
||||
JSCommonJSExtensions(JSC::VM& vm, JSC::Structure* structure)
|
||||
: Base(vm, structure)
|
||||
{
|
||||
}
|
||||
|
||||
void finishCreation(JSC::VM&);
|
||||
};
|
||||
|
||||
} // namespace Bun
|
||||
@@ -67,7 +67,7 @@
|
||||
#include <JavaScriptCore/GetterSetter.h>
|
||||
#include "ZigSourceProvider.h"
|
||||
#include <JavaScriptCore/FunctionPrototype.h>
|
||||
#include "CommonJSModuleRecord.h"
|
||||
#include "JSCommonJSModule.h"
|
||||
#include <JavaScriptCore/JSModuleNamespaceObject.h>
|
||||
#include <JavaScriptCore/JSSourceCode.h>
|
||||
#include <JavaScriptCore/LazyPropertyInlines.h>
|
||||
@@ -76,6 +76,7 @@
|
||||
#include "wtf/NakedPtr.h"
|
||||
#include "wtf/URL.h"
|
||||
#include "wtf/text/StringImpl.h"
|
||||
#include "JSCommonJSExtensions.h"
|
||||
|
||||
extern "C" bool Bun__isBunMain(JSC::JSGlobalObject* global, const BunString*);
|
||||
|
||||
@@ -257,12 +258,22 @@ JSC_DEFINE_HOST_FUNCTION(jsFunctionEvaluateCommonJSModule, (JSGlobalObject * lex
|
||||
RELEASE_AND_RETURN(throwScope, JSValue::encode(jsUndefined()));
|
||||
}
|
||||
|
||||
UNUSED_PARAM(referrer);
|
||||
JSValue returnValue = jsNull();
|
||||
if (LIKELY(referrer)) {
|
||||
if (UNLIKELY(referrer->m_childrenValue)) {
|
||||
// It's too hard to append from native code:
|
||||
// referrer.children.indexOf(moduleObject) === -1 && referrer.children.push(moduleObject)
|
||||
returnValue = referrer->m_childrenValue.get();
|
||||
} else {
|
||||
referrer->m_children.append(WriteBarrier<Unknown>());
|
||||
referrer->m_children.last().set(vm, referrer, moduleObject);
|
||||
}
|
||||
}
|
||||
|
||||
moduleObject->load(vm, globalObject);
|
||||
RETURN_IF_EXCEPTION(throwScope, {});
|
||||
|
||||
RELEASE_AND_RETURN(throwScope, JSValue::encode(jsUndefined()));
|
||||
RELEASE_AND_RETURN(throwScope, JSValue::encode(returnValue));
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(requireResolvePathsFunction, (JSGlobalObject * globalObject, CallFrame* callframe))
|
||||
@@ -288,12 +299,31 @@ JSC_DEFINE_CUSTOM_SETTER(jsRequireCacheSetter,
|
||||
return true;
|
||||
}
|
||||
|
||||
JSC_DEFINE_CUSTOM_GETTER(jsRequireExtensionsGetter, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName))
|
||||
{
|
||||
Zig::GlobalObject* thisObject = jsCast<Zig::GlobalObject*>(globalObject);
|
||||
return JSValue::encode(thisObject->lazyRequireExtensionsObject());
|
||||
}
|
||||
|
||||
JSC_DEFINE_CUSTOM_SETTER(jsRequireExtensionsSetter,
|
||||
(JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue,
|
||||
JSC::EncodedJSValue value, JSC::PropertyName propertyName))
|
||||
{
|
||||
JSObject* thisObject = jsDynamicCast<JSObject*>(JSValue::decode(thisValue));
|
||||
if (!thisObject)
|
||||
return false;
|
||||
|
||||
thisObject->putDirect(globalObject->vm(), propertyName, JSValue::decode(value), 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
static const HashTableValue RequireResolveFunctionPrototypeValues[] = {
|
||||
{ "paths"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, requireResolvePathsFunction, 1 } },
|
||||
};
|
||||
|
||||
static const HashTableValue RequireFunctionPrototypeValues[] = {
|
||||
{ "cache"_s, static_cast<unsigned>(JSC::PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsRequireCacheGetter, jsRequireCacheSetter } },
|
||||
{ "extensions"_s, static_cast<unsigned>(JSC::PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsRequireExtensionsGetter, jsRequireExtensionsSetter } },
|
||||
};
|
||||
|
||||
Structure* RequireFunctionPrototype::createStructure(
|
||||
@@ -348,7 +378,7 @@ void RequireFunctionPrototype::finishCreation(JSC::VM& vm)
|
||||
JSC::JSFunction* requireDotMainFunction = JSFunction::create(
|
||||
vm,
|
||||
globalObject,
|
||||
moduleMainCodeGenerator(vm),
|
||||
commonJSMainCodeGenerator(vm),
|
||||
globalObject->globalScope());
|
||||
|
||||
this->putDirectAccessor(
|
||||
@@ -356,13 +386,6 @@ void RequireFunctionPrototype::finishCreation(JSC::VM& vm)
|
||||
JSC::Identifier::fromString(vm, "main"_s),
|
||||
JSC::GetterSetter::create(vm, globalObject, requireDotMainFunction, requireDotMainFunction),
|
||||
PropertyAttribute::Accessor | PropertyAttribute::ReadOnly | 0);
|
||||
|
||||
auto extensions = constructEmptyObject(globalObject);
|
||||
extensions->putDirect(vm, JSC::Identifier::fromString(vm, ".js"_s), jsBoolean(true), 0);
|
||||
extensions->putDirect(vm, JSC::Identifier::fromString(vm, ".json"_s), jsBoolean(true), 0);
|
||||
extensions->putDirect(vm, JSC::Identifier::fromString(vm, ".node"_s), jsBoolean(true), 0);
|
||||
|
||||
this->putDirect(vm, JSC::Identifier::fromString(vm, "extensions"_s), extensions, 0);
|
||||
}
|
||||
|
||||
JSC_DEFINE_CUSTOM_GETTER(getterFilename, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName))
|
||||
@@ -450,6 +473,62 @@ JSC_DEFINE_CUSTOM_GETTER(getterPaths, (JSC::JSGlobalObject * globalObject, JSC::
|
||||
return JSValue::encode(thisObject->m_paths.get());
|
||||
}
|
||||
|
||||
JSC_DEFINE_CUSTOM_SETTER(setterChildren,
|
||||
(JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue,
|
||||
JSC::EncodedJSValue value, JSC::PropertyName propertyName))
|
||||
{
|
||||
JSCommonJSModule* thisObject = jsDynamicCast<JSCommonJSModule*>(JSValue::decode(thisValue));
|
||||
if (!thisObject)
|
||||
return false;
|
||||
thisObject->m_children.clear();
|
||||
thisObject->m_childrenValue.set(globalObject->vm(), thisObject, JSValue::decode(value));
|
||||
return true;
|
||||
}
|
||||
|
||||
JSC_DEFINE_CUSTOM_GETTER(getterChildren, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName))
|
||||
{
|
||||
JSCommonJSModule* mod = jsDynamicCast<JSCommonJSModule*>(JSValue::decode(thisValue));
|
||||
if (UNLIKELY(!mod)) {
|
||||
return JSValue::encode(jsUndefined());
|
||||
}
|
||||
|
||||
if (!mod->m_childrenValue) {
|
||||
auto throwScope = DECLARE_THROW_SCOPE(globalObject->vm());
|
||||
MarkedArgumentBuffer children;
|
||||
children.ensureCapacity(mod->m_children.size());
|
||||
|
||||
// Deduplicate children while preserving insertion order.
|
||||
JSCommonJSModule* last = nullptr;
|
||||
int n = -1;
|
||||
for (WriteBarrier<Unknown> childBarrier : mod->m_children) {
|
||||
JSCommonJSModule* child = jsCast<JSCommonJSModule*>(childBarrier.get());
|
||||
// Check the last module since duplicate imports, if any, will
|
||||
// probably be adjacent. Then just do a linear scan.
|
||||
if (UNLIKELY(last == child)) continue;
|
||||
int i = 0;
|
||||
while (i < n) {
|
||||
if (UNLIKELY(children.at(i).asCell() == child)) goto next;
|
||||
i += 1;
|
||||
}
|
||||
children.append(child);
|
||||
last = child;
|
||||
n += 1;
|
||||
next: {
|
||||
}
|
||||
}
|
||||
|
||||
// Construct the array
|
||||
JSArray* array = JSC::constructArray(globalObject, static_cast<ArrayAllocationProfile*>(nullptr), children);
|
||||
mod->m_childrenValue.set(globalObject->vm(), mod, array);
|
||||
|
||||
mod->m_children.clear();
|
||||
|
||||
return JSValue::encode(array);
|
||||
}
|
||||
|
||||
return JSValue::encode(mod->m_childrenValue.get());
|
||||
}
|
||||
|
||||
JSC_DEFINE_CUSTOM_GETTER(getterLoaded, (JSC::JSGlobalObject * globalObject, JSC::EncodedJSValue thisValue, JSC::PropertyName))
|
||||
{
|
||||
JSCommonJSModule* thisObject = jsDynamicCast<JSCommonJSModule*>(JSValue::decode(thisValue));
|
||||
@@ -510,7 +589,6 @@ JSC_DEFINE_CUSTOM_SETTER(setterParent,
|
||||
thisObject->m_overriddenParent.clear();
|
||||
} else {
|
||||
thisObject->m_parent = {};
|
||||
thisObject->m_overriddenParent.set(globalObject->vm(), thisObject, JSValue::decode(value));
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -528,12 +606,7 @@ JSC_DEFINE_CUSTOM_SETTER(setterLoaded,
|
||||
return true;
|
||||
}
|
||||
|
||||
static JSValue createChildren(VM& vm, JSObject* object)
|
||||
{
|
||||
return constructEmptyArray(object->globalObject(), nullptr, 0);
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(functionCommonJSModuleRecord_compile, (JSGlobalObject * globalObject, CallFrame* callframe))
|
||||
JSC_DEFINE_HOST_FUNCTION(functionJSCommonJSModule_compile, (JSGlobalObject * globalObject, CallFrame* callframe))
|
||||
{
|
||||
auto* moduleObject = jsDynamicCast<JSCommonJSModule*>(callframe->thisValue());
|
||||
if (!moduleObject) {
|
||||
@@ -595,8 +668,8 @@ JSC_DEFINE_HOST_FUNCTION(functionCommonJSModuleRecord_compile, (JSGlobalObject *
|
||||
}
|
||||
|
||||
static const struct HashTableValue JSCommonJSModulePrototypeTableValues[] = {
|
||||
{ "_compile"_s, static_cast<unsigned>(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, functionCommonJSModuleRecord_compile, 2 } },
|
||||
{ "children"_s, static_cast<unsigned>(PropertyAttribute::PropertyCallback), NoIntrinsic, { HashTableValue::LazyPropertyType, createChildren } },
|
||||
{ "_compile"_s, static_cast<unsigned>(PropertyAttribute::Function | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::NativeFunctionType, functionJSCommonJSModule_compile, 2 } },
|
||||
{ "children"_s, static_cast<unsigned>(PropertyAttribute::CustomAccessor | PropertyAttribute::DontEnum), NoIntrinsic, { HashTableValue::GetterSetterType, getterChildren, setterChildren } },
|
||||
{ "filename"_s, static_cast<unsigned>(PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, getterFilename, setterFilename } },
|
||||
{ "id"_s, static_cast<unsigned>(PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, getterId, setterId } },
|
||||
{ "loaded"_s, static_cast<unsigned>(PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, getterLoaded, setterLoaded } },
|
||||
@@ -1016,6 +1089,8 @@ void JSCommonJSModule::visitChildrenImpl(JSCell* cell, Visitor& visitor)
|
||||
visitor.appendHidden(thisObject->m_dirname);
|
||||
visitor.appendHidden(thisObject->m_paths);
|
||||
visitor.appendHidden(thisObject->m_overriddenParent);
|
||||
visitor.appendHidden(thisObject->m_childrenValue);
|
||||
visitor.appendValues(thisObject->m_children.data(), thisObject->m_children.size());
|
||||
}
|
||||
|
||||
DEFINE_VISIT_CHILDREN(JSCommonJSModule);
|
||||
@@ -1089,7 +1164,6 @@ JSC_DEFINE_HOST_FUNCTION(jsFunctionRequireCommonJS, (JSGlobalObject * lexicalGlo
|
||||
// This is always a new JSCommonJSModule object; cast cannot fail.
|
||||
JSCommonJSModule* child = jsCast<JSCommonJSModule*>(callframe->uncheckedArgument(1));
|
||||
|
||||
BunString specifierStr = Bun::toString(specifier);
|
||||
BunString referrerStr = Bun::toString(referrer);
|
||||
BunString typeAttributeStr = { BunStringTag::Dead };
|
||||
String typeAttribute = String();
|
||||
@@ -1120,7 +1194,7 @@ JSC_DEFINE_HOST_FUNCTION(jsFunctionRequireCommonJS, (JSGlobalObject * lexicalGlo
|
||||
globalObject,
|
||||
child,
|
||||
specifierValue,
|
||||
&specifierStr,
|
||||
specifier,
|
||||
&referrerStr,
|
||||
LIKELY(typeAttribute.isEmpty())
|
||||
? nullptr
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user