Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
66c8cf3
Move Crypto, TextDecoder, FileSystemRouter, MatchedRoute, ResourceUsa…
dylan-conway Oct 18, 2026
e768c77
Make a cached getter an internal field, fix cpuTime's unchecked excep…
dylan-conway Oct 18, 2026
21e50d7
Take the VM again after the router loads, and give getRandomValues, r…
dylan-conway Oct 18, 2026
da488d8
Raise a decoder's invalid data after its borrow ends, name internal f…
dylan-conway Oct 18, 2026
e7cd930
Share one field between src and scriptSrc, cache the getters that wer…
dylan-conway Oct 18, 2026
2a5804f
Read whether an ArrayBuffer is shared inline, and make the UTF-16 dec…
dylan-conway Oct 18, 2026
2cc999f
Store nothing for an argument's error context, hold bytes that are ne…
dylan-conway Oct 18, 2026
3084fe5
Hold the copy of a shared buffer in what frees only when it holds som…
dylan-conway Oct 18, 2026
ebbfe21
Refuse an expected on any type that does not use it, where the schema…
dylan-conway Oct 18, 2026
90df194
Let an expected that a type does not use do nothing, and give an empt…
dylan-conway Oct 18, 2026
f3415c9
Set up each generated class with its own function in place of a table…
dylan-conway Oct 18, 2026
c57cdeb
Put a class's private names without a transition, and take the slots …
dylan-conway Oct 18, 2026
3c89db9
Expect the header that lists the classes among the outputs the bindge…
dylan-conway Oct 18, 2026
38d40bc
Return the records of ResourceUsage as dictionaries with no prototype…
dylan-conway Oct 18, 2026
3822edb
Name the records of ResourceUsage after it, test their shape, and say…
dylan-conway Oct 18, 2026
8a9778d
Give a date, a string the body made, a record and a union as results …
dylan-conway Oct 18, 2026
618ca1e
Make the objects of params and query with the record of the bindgen, …
dylan-conway Oct 18, 2026
a5bdd75
Convert the key of a record as its values are, make the object with c…
dylan-conway Oct 18, 2026
3f9f911
Refuse a result-only type through one function, drop a getter of the …
dylan-conway Oct 18, 2026
de2df3c
Hold the UTF-8 of a string and the copy of a shared buffer in a Mallo…
dylan-conway Oct 19, 2026
efa303f
Return the too-long and out-of-memory errors of decode() instead of t…
dylan-conway Oct 19, 2026
483e4e8
Document that a method that mutates returns its errors, and the two e…
dylan-conway Oct 19, 2026
9c3546c
Check the length of a decoded string once, and give the three errors …
dylan-conway Oct 19, 2026
7a4438c
Say on create_utf16_string_from_vec that its error is not thrown, and…
dylan-conway Oct 19, 2026
9dceadd
Hold the UTF-8 of a string as main does
dylan-conway Oct 19, 2026
66e313f
Keep the comment on why the copy of a shared buffer is not a Vector
dylan-conway Oct 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude/skills/implementing-jsc-classes-rust/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ description: Creates JavaScript classes using Bun's Rust bindings generator (.cl

Bridge JavaScript and Rust through `.classes.ts` definitions and Rust implementations.

`src/codegen/bindgen.ts` (`*.bind.ts`, `docs/project/bindgen.mdx`) is replacing this generator: typed arguments, and the struct stored inside the JS object. Use it for a class whose struct only the JS object owns. It does not do ref-counted or shared structs, `hasPendingActivity`, `estimatedSize` or writing to a cached getter's slot yet.
`src/codegen/bindgen.ts` (`*.bind.ts`, `docs/project/bindgen.mdx`) is replacing this generator: typed arguments, and the struct stored inside the JS object. Use it for a class whose struct only the JS object owns. It does not do ref-counted or shared structs, `hasPendingActivity` or `estimatedSize` yet.

## Architecture

Expand Down
72 changes: 55 additions & 17 deletions docs/project/bindgen.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,10 @@ A function of a `node:*` module names what Node's own source expects of an argum
`expected: { data: ["Buffer", "TypedArray", "DataView", "string"] }`. A value the argument's type
refuses then throws Node's `ERR_INVALID_ARG_TYPE(name, expected, actual)`, "The "data" argument must
be of type string or an instance of Buffer, TypedArray, or DataView. Received null", and not "data
must be a string, ArrayBuffer or ArrayBufferView". What the type takes is the same.
must be a string, ArrayBuffer or ArrayBufferView". What the type takes is the same. A string in the
place of the list is the whole message: Node's C++ writes its own out that way, and so do the Bun APIs
that have a message of their own. A dictionary named in `expected` reports what is not an object as the
argument, and not under the dictionary's name.

Nothing else is written by hand, except once per crate: `src/<crate>/generated_bindgen.rs`
includes the generated file, the crate root declares `mod generated_bindgen`, and a
Expand All @@ -76,7 +79,7 @@ not compile.

To reach the function:

- from `src/js`: `$bindgen("bindgen_test.bind.ts", "add")`, which is typed from the schema. An argument's type is what it takes, so one that coerces (`.loose`, `.wrap`, `.enforceRange()`) is `any`;
- from `src/js`: `$bindgen("bindgen_test.bind.ts", "add")`, which is typed from the schema. An argument's type is what it takes, so one that coerces (`.loose`, `.wrap`, `.enforceRange()`) is `any`. A `.nullable` result, and a `.nullable` getter without a setter, is `T | null`;
- from a C++ static property table: `Bun::Bindgen::Generated::BindgenTest::jsAdd` in `GeneratedBindgenTest.h`.

### Namespaces
Expand Down Expand Up @@ -129,7 +132,11 @@ to may run JavaScript, and the compiler cannot see that. Read what is called all
before writing the `SAFETY` comment. `ToString` of an object, a property that is looked up or
set and not defined, and a call to a constructor found on the global object all run JavaScript.
Throwing tells the debugger, which can run code, so after a throw the body returns without
reading what it borrowed.
reading what it borrowed. A `mutates: true` method does not throw: what the debugger runs can call
a method of the same object while the `&mut self` is live. It returns the error, which is thrown
after the borrow ends. Every `heap.create_*` that returns a `Result` throws at once, except
`create_utf16_string_from_vec`. A method that has to call one takes `capability: "js"` and borrows
with `with_mut` (see [the receiver](#the-receiver)).

```rust
// SAFETY: parsing runs no JavaScript. Nor does making the result: its keys are primitives, and its properties are
Expand All @@ -139,10 +146,11 @@ let global = unsafe { heap.global() };

`throws: true` makes the return type a `Result`. The error is a
`bindgen::Error<'a>`, made by `heap.error(..)`, `heap.type_error(..)`,
`heap.range_error(..)`, `heap.error_with_code(ErrorCode::OUT_OF_RANGE, ..)`, `heap.invalid_arguments(..)` or
`bindgen::Error::from_value(value)` (`?` makes one of a `JsError` or an `AllocError`), or any type that
implements `bindgen::IntoError` (`bun_sys::Error`, `SystemError`, `JsError` and a `Box` of
one do), which lets a `capability: "none"` function fail too.
`heap.range_error(..)`, `heap.error_with_code(ErrorCode::OUT_OF_RANGE, ..)`, `heap.invalid_arguments(..)`,
`heap.illegal_constructor()`, `heap.string_too_long()`, `heap.memory_allocation_failed()` or
`bindgen::Error::from_value(value)` (`?` makes one of a `JsError` or an `AllocError`), or any
type that implements `bindgen::IntoError` (`bun_sys::Error`,
`SystemError`, `JsError` and a `Box` of one do), which lets a `capability: "none"` function fail too.

`cx.global()` is the `&JSGlobalObject`, for code that takes one. It borrows all of
`cx`. Such a function can return `JsResult<T>`, and a plain `JSValue` under
Expand Down Expand Up @@ -222,14 +230,25 @@ These come last, in this order (`b.any` already takes `undefined` and `null`, so
- `.default(value)`: what to use when absent; `T`. `.nullable.default(value)` also applies to `null`.

As results, `b.string.utf8` is anything that is `AsRef<[u8]>` and `b.array(T)` anything that
is `IntoIterator`. The body of a `b.fn` or of a namespace's function whose bytes do not outlive
it returns the string itself, from `heap.create_utf8_string(bytes)`. `b.u64.asBigInt` is a
result only: a `u64` that is always returned as a BigInt. `b.bufferSource` as a result is what
is `IntoIterator`. `b.bufferSource` as a result is what
`heap.create_uninitialized_uint8_array(length)`, `heap.create_uint8_array(bytes)`,
`heap.create_buffer(bytes)`, `heap.create_uint8_array_from_box(bytes)` or
`heap.create_buffer_from_box(bytes)` made. The last two take over the allocation, and the two
before them copy.

These are results only:

| Schema | Rust | JavaScript gets |
| ---------------- | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `b.u64.asBigInt` | `u64` | always a BigInt |
| `b.f64.asDate` | `f64` | a `Date` with that time value |
| `b.string.local` | `JsLocal<'a, JSString>` | the string the body made: `heap.create_latin1_string(bytes)`, `heap.create_utf16_string_from_vec(units)`, `heap.create_utf8_string(bytes)` or `heap.common_string(CommonString::..)` |
| `b.record(T)` | anything that is `IntoIterator` of `(key, T)` | an object with an own property for each pair; a key is UTF-8 and one that comes again takes the new value, `T` holds no JS value, and it is not a member or an alternative |

A body that returns `b.any` makes the value with what the generated code calls:
`bindgen::to_js::record`, `to_js::array`, `to_js::utf8` and the others of that module. An empty
object is `heap.create_empty_object()`.

### Numbers

A BigInt is refused unless the schema says `.allowBigInt`, which is for an argument whose
Expand Down Expand Up @@ -299,6 +318,7 @@ The second argument of `b.dictionary` takes:
| `allowArray` | an array is read like any other object, not refused |
| `nonObjectAsEmpty` | a value that is not an object gives every member its default; with `"typeof"` so does a function, as in Node's `if (typeof options !== "object") options = null`; every member must be optional or have a default |
| `fromJS` | also generates `Name::from_js(global, value)`, for a `#[host_fn]` body; one that holds JS values is lent to a closure, `Name::from_js(global, value, \|converted\| ..)` |
| `nullPrototype` | as a result, the object has no prototype, and the inline room of `Object.create(null)` |

### Enumerations

Expand Down Expand Up @@ -339,6 +359,9 @@ loaded. Choosing tests whether the value is a cell once, loads the type byte onc
repeats no test. Strings as UTF-8, dictionaries and unions are then converted in the
place they end up in.

A union is returned as what the alternative it holds is. One that is only returned owns its
strings: `b.string.utf8` in it is a `Vec<u8>`.

### Buffers

`b.bufferSource` is the buffer itself. JavaScript can detach, resize or transfer it, so the
Expand Down Expand Up @@ -508,15 +531,22 @@ outside the object: there is no `estimatedSize` yet.
| `internalFields` | JS values the object holds |
| `configurable` | `false` makes the native properties non-configurable |

Code that has the global object and no capability makes one with
`payload.into_js_value(global)`, which returns a `JSValue`.

A class that JavaScript names but cannot make, like `Crypto`, declares
`construct: { capability: "heap", throws: true }`, and its `constructor` returns
`Err(heap.illegal_constructor())`.

A property of `proto` is one of:

| Property | What it is |
| ------------------------------------------- | ---------------------------------------------------------------------------- |
| `{ args, returns, capability, throws, .. }` | a native function; `privateSymbol` keys it by a private name of `src/js` |
| `{ getter: T, capability, throws }` | an accessor; with `setter: true` it can be assigned |
| `{ getter: T, cache: true, .. }` | an accessor whose value is computed once per object; it cannot have a setter |
| `{ builtin: true, length }` | the export of that name in `src/js/builtins/<class name>.ts` |
| `[{ rustName, args, .. }, ..]` | a native function with [overloads](#overloads) |
| Property | What it is |
| ------------------------------------------- | ------------------------------------------------------------------------ |
| `{ args, returns, capability, throws, .. }` | a native function; `privateSymbol` keys it by a private name of `src/js` |
| `{ getter: T, capability, throws }` | an accessor; with `setter: true` it can be assigned |
| `{ getter: T, cache: true \| "field", .. }` | an accessor that returns an [internal field](#internalfields); no setter |
| `{ builtin: true, length }` | the export of that name in `src/js/builtins/<class name>.ts` |
| `[{ rustName, args, .. }, ..]` | a native function with [overloads](#overloads) |

The properties are in the order of the schema, which `for-in` and `Object.keys` show. The Rust
function is named after the property. `rustName` names it otherwise, which tells apart the
Expand Down Expand Up @@ -567,6 +597,14 @@ allocating or viewing a buffer inside the closure is fine.
`&mut self` method that needs them asks for the wrapper too, with `passThis: true`.
The wrapper reaches the internal fields and not the payload.

Every field starts as `undefined`. A heap snapshot shows what it holds as a property of its name.

A getter with `cache: true` declares a field of its own name, with the same two functions, and
returns it. While the field is `undefined` the getter calls Rust and stores the result, so
`this.set_tag_field(heap, JsLocal::UNDEFINED)` makes the next read of `tag` compute it again.
`cache: "tag"` on another getter declares nothing: it returns that field, of a getter or of
`internalFields`, and fills it from its own Rust function.

### Staying alive

To keep an object alive while native work is pending, hold a
Expand Down
6 changes: 3 additions & 3 deletions docs/runtime/child-process.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -635,9 +635,9 @@ interface ResourceUsage {
};

cpuTime: {
user: number;
system: number;
total: number;
user: bigint;
system: bigint;
total: bigint;
};
maxRSS: number;

Expand Down
7 changes: 6 additions & 1 deletion docs/runtime/file-system-router.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,12 @@ interface Bun {
fileExtensions?: string[];
});

reload(): void;
readonly assetPrefix: string | null;
readonly origin: string | null;
readonly routes: Record<string, string>;
readonly style: "nextjs";

reload(): this;

match(path: string | Request | Response): {
filePath: string;
Expand Down
47 changes: 27 additions & 20 deletions packages/bun-types/bun.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8177,15 +8177,15 @@ declare module "bun" {
/**
* User CPU time used by the process, in microseconds.
*/
user: number;
user: bigint;
/**
* System CPU time used by the process, in microseconds.
*/
system: number;
system: bigint;
/**
* Total CPU time used by the process, in microseconds.
*/
total: number;
total: bigint;
};
/**
* The maximum resident set size (in bytes) used by the process during its lifetime.
Expand Down Expand Up @@ -9180,6 +9180,21 @@ declare module "bun" {
[Symbol.dispose](): void;
}

/** Options for the {@link FileSystemRouter} constructor. */
interface FileSystemRouterOptions {
/**
* The root directory containing the files to route
*/
dir: string;
style: "nextjs";

/** The base path to use when routing */
assetPrefix?: string;
origin?: string;
/** Limit the pages to those with particular file extensions. */
fileExtensions?: string[];
}

/**
* Resolve routes against a directory of files using Next.js-style (`pages`
* directory) conventions.
Expand All @@ -9202,29 +9217,19 @@ declare module "bun" {
* @param options.dir The root directory containing the files to route
* @param options.style The style of router to use (only "nextjs" is supported)
*/
constructor(options: {
/**
* The root directory containing the files to route
*/
dir: string;
style: "nextjs";

/** The base path to use when routing */
assetPrefix?: string;
origin?: string;
/** Limit the pages to those with particular file extensions. */
fileExtensions?: string[];
});
constructor(options: FileSystemRouterOptions);

// todo: URL
match(input: string | Request | Response): MatchedRoute | null;

readonly assetPrefix: string;
readonly origin: string;
readonly style: string;
/** The `assetPrefix` option, or `null` if it was absent or empty */
readonly assetPrefix: string | null;
/** The `origin` option, or `null` if it was absent or empty */
readonly origin: string | null;
readonly style: "nextjs";
readonly routes: Record<string, string>;

reload(): void;
reload(): this;
}

interface MatchedRoute {
Expand All @@ -9251,6 +9256,8 @@ declare module "bun" {
readonly name: string;
readonly kind: "exact" | "catch-all" | "optional-catch-all" | "dynamic";
readonly src: string;
/** @deprecated Use {@link src} */
readonly scriptSrc: string;
}

/**
Expand Down
1 change: 1 addition & 0 deletions scripts/build/codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -950,6 +950,7 @@ export function emitBindgen({ n, cfg, sources, o, dirStamp }: Ctx): void {
...outputNames.map(name => resolve(cfg.codegenDir, `${name}.h`)),
resolve(cfg.codegenDir, "GeneratedBindings+Tables.h"),
resolve(cfg.codegenDir, "GeneratedBindings+Counts.h"),
resolve(cfg.codegenDir, "GeneratedBindings+Classes.h"),
Comment thread
dylan-conway marked this conversation as resolved.
Resolved
];
const rust = [...new Set(compiled.map(src => crates.get(src)))].map(crate =>
resolve(cfg.codegenDir, `generated_bindgen_${crate}.rs`),
Expand Down
Loading
Loading