Bun
Using unffi on Bun — bun:ffi, fast 64-bit ints, NAPI interop, zero-copy.
unffi on Bun uses bun:ffi directly. Symbols are JIT-compiled to native trampolines — no JS wrapper overhead per call.
Quick start
import { , } from 'unffi'
const = await ('./libmath.dylib', {
: { : [., .], : . },
: { : [.], : . },
})
const sum = ..(3, 4)const root = ..(2.0)Fast 64-bit integers
t.i64 and t.u64 always return bigint. If you know the value fits in a JS number (≤ 2^53), use the fast variant to skip BigInt allocation:
import { , } from 'unffi'
const = await ('./counter.dylib', {
: { : [], : . },
})
const precise = ..()On Bun, you can also use t.bun.i64_fast instead of t.i64 — it returns number | bigint, skipping BigInt allocation when the value fits in a double.
t.i64 always gives bigint. t.bun.i64_fast gives number | bigint — skips BigInt allocation when the value fits.
i64_fast silently loses precision above 2^53. Only use it when values are bounded.
Zero-copy buffers
Use t.buffer for TypedArray arguments — unffi passes the backing pointer directly:
import { , } from 'unffi'
const = await ('./fill.dylib', {
: { : [., .], : . },
})
const = new (1024)
const result = ..(, .)// buf is populated in-placeCallbacks
Pass a JS function directly where t.fn is in the schema:
import { , } from 'unffi'
const = await ('./sorter.dylib', {
: {
: [., ., .([., .], .)],
: .,
},
})
..(new (10), 10, (a, ) => { return -
})The callback's a and b are typed as number from t.i32.
Async / nonblocking calls
Mark slow symbols async: true to run them on a thread pool:
import { , } from 'unffi'
const = await ('./codec.dylib', {
: { : [., .], : ., : true },
})
const outLen = await ..(new (1024), 1024)NAPI interop
For native addons that expose a Node-API interface, use t.bun.napi_env and t.bun.napi_value:
await using addon = await dlopen('./addon.node', {
napi_register_module_v1: {
args: [t.bun.napi_env, t.bun.napi_value],
returns: t.bun.napi_value,
},
})