Compare commits

..

364 Commits

Author SHA1 Message Date
Alistair Smith
6e3ee654f2 Merge remote-tracking branch 'origin' into ali/react 2025-11-04 07:54:17 -08:00
Alistair Smith
e306ac831e Merge branch 'main' into ali/react 2025-10-21 08:06:12 +09:00
Alistair Smith
ed1eb21093 Merge branch 'main' of github.com:oven-sh/bun into ali/react 2025-10-09 15:18:01 -07:00
Alistair Smith
4a4a37796b Merge branch 'main' into ali/react 2025-10-07 22:02:46 -07:00
Alistair Smith
7aef153f5d refactor: Remove experimental types and related references from the codebase 2025-10-07 18:17:00 -07:00
Alistair Smith
3cb64478d7 fix: Merge with 1.3 new Bun.serve types 2025-10-07 17:03:23 -07:00
Alistair Smith
7dfed6b986 Merge branch 'main' of github.com:oven-sh/bun into ali/react 2025-10-07 16:50:14 -07:00
Alistair Smith
5972cf24cb Merge remote-tracking branch 'origin/main' into ali/react 2025-10-06 16:24:19 -07:00
Alistair Smith
329c79364d Merge branch 'main' into ali/react 2025-10-06 16:22:38 -07:00
Alistair Smith
da5bf73494 clean up args 2025-10-03 16:53:59 -07:00
Alistair Smith
fbd58db004 changes 2025-10-03 16:11:37 -07:00
Alistair Smith
81999c26e6 extend the debug timeout a little 2025-10-03 15:05:50 -07:00
Alistair Smith
dde1f9e610 pin peechy 2025-10-03 14:26:50 -07:00
Alistair Smith
c9b5ba1c96 bump builtin react-refresh 2025-10-03 13:17:53 -07:00
Alistair Smith
74f4fcf2a6 Revert launch.json 2025-10-03 12:58:41 -07:00
Alistair Smith
031f12442d fix: We do require react/react-dom 2025-10-03 12:53:48 -07:00
autofix-ci[bot]
a20a718e48 [autofix.ci] apply automated fixes 2025-10-03 18:41:41 +00:00
Alistair Smith
33537b6ec1 Merge branch 'main' into ali/react 2025-10-03 11:38:33 -07:00
Alistair Smith
952436fc4c returnIfException() 2025-10-03 11:36:02 -07:00
Alistair Smith
31352bc646 fix test 2025-10-03 11:36:02 -07:00
Alistair Smith
ff84564c11 fix that 2025-10-03 11:36:02 -07:00
Alistair Smith
cc78a1bca1 fix: re-inject react-refresh in auto mode 2025-10-03 11:36:02 -07:00
Alistair Smith
3ca89abacf remove 2025-10-02 18:33:40 -07:00
Alistair Smith
2f02e4e31d Merge branch 'main' of github.com:oven-sh/bun into ali/react 2025-10-02 18:21:29 -07:00
Alistair Smith
efb508e2ae chore: Bump & pin React 2025-10-02 18:14:47 -07:00
Alistair Smith
86af3dd034 fix test 2025-10-02 17:33:33 -07:00
autofix-ci[bot]
a6d3808ad8 [autofix.ci] apply automated fixes 2025-10-02 22:54:04 +00:00
Alistair Smith
2153fe4163 Merge branch 'main' of github.com:oven-sh/bun into ali/react 2025-10-02 15:50:40 -07:00
Alistair Smith
6977b36215 change 2025-09-30 22:04:08 -07:00
Alistair Smith
ceaab9eda3 tests: Fix spacing in routes test expectation 2025-09-30 19:33:14 -07:00
Alistair Smith
f262e32368 tidy 2025-09-30 19:18:21 -07:00
Alistair Smith
69a76d44f9 types 2025-09-30 18:52:50 -07:00
Alistair Smith
fc9538baf1 move 2025-09-30 18:46:34 -07:00
Alistair Smith
72b7956385 remove old property 2025-09-30 18:13:46 -07:00
Alistair Smith
62b296bb43 message 2025-09-30 18:08:10 -07:00
Alistair Smith
cb14f70a43 remove dated .auto() implementation 2025-09-30 18:05:30 -07:00
Alistair Smith
38511375f8 unused for now 2025-09-30 17:50:14 -07:00
Alistair Smith
06cfe2ead1 bump react 2025-09-30 17:48:05 -07:00
Alistair Smith
4f219503fe fix production.test.ts resolving bun-framework-react 2025-09-30 17:41:22 -07:00
Alistair Smith
b2353d687e Merge branch 'main' of github.com:oven-sh/bun into ali/react 2025-09-30 17:25:53 -07:00
Alistair Smith
65e66099f7 fix: updated load expectation 2025-09-30 17:25:25 -07:00
Alistair Smith
c534f0caa0 Merge branch 'main' into ali/react 2025-09-30 15:21:39 -07:00
Alistair Smith
a873152aeb Revert bundle_v2.zig 2025-09-30 15:19:17 -07:00
Alistair Smith
a17eb07d48 revert some unnecessary changes 2025-09-30 15:15:39 -07:00
Alistair Smith
1381de4d18 Merge branch 'main' of github.com:oven-sh/bun into ali/react 2025-09-30 15:04:48 -07:00
Alistair Smith
8d8b037e94 rm 2025-09-29 16:16:22 -07:00
Alistair Smith
f43a175f72 use options 2025-09-29 16:07:49 -07:00
Alistair Smith
e1ad16f857 Merge branch 'zack/ssg-3' of github.com:oven-sh/bun into ali/react 2025-09-29 15:42:55 -07:00
Zack Radisic
661a246039 fix 2025-09-28 18:02:58 -07:00
Zack Radisic
d0fed20c89 Revert "fix leak"
This reverts commit 1c6165e68a.
2025-09-28 17:43:20 -07:00
Zack Radisic
1c6165e68a fix leak 2025-09-27 15:39:40 -07:00
Zack Radisic
daefbfb453 Merge branch 'main' into zack/ssg-3 2025-09-27 15:09:54 -07:00
Zack Radisic
971e4679cf Merge branch 'zack/ssg-3' of github.com:oven-sh/bun into zack/ssg-3 2025-09-27 14:57:39 -07:00
Zack Radisic
49a9dc7ddf fix banword 2025-09-27 14:55:12 -07:00
Zack Radisic
b5a5fea9ae fix 2025-09-27 00:55:48 -07:00
Zack Radisic
243a237a62 use jsc call conv 2025-09-27 00:29:20 -07:00
Zack Radisic
49cfda12a8 Merge branch 'zack/ssg-3' of https://github.com/oven-sh/bun into zack/ssg-3 2025-09-27 00:28:16 -07:00
Zack Radisic
ab579a3cc3 less confusing 2025-09-27 00:27:35 -07:00
Zack Radisic
8cd9b4eae6 update leaksan.supp 2025-09-27 00:18:34 -07:00
Zack Radisic
dd9860f501 Merge branch 'main' into zack/ssg-3 2025-09-26 23:50:10 -07:00
Zack Radisic
756e590782 fix ban words 2025-09-26 22:07:39 -07:00
Alistair Smith
c62613e765 better file hash 2025-09-26 19:50:25 -07:00
Alistair Smith
7f96bf8f13 Merge branch 'zack/ssg-3' of github.com:oven-sh/bun into ali/react 2025-09-26 19:31:00 -07:00
Alistair Smith
cd800b02f5 decrash 2025-09-26 19:30:53 -07:00
Zack Radisic
de999f78ab resolve more comments 2025-09-26 02:04:35 -07:00
Zack Radisic
24748104ce okie dokie 2025-09-26 01:37:55 -07:00
Zack Radisic
3fca3b97d9 resolve comments 2025-09-26 01:11:29 -07:00
Alistair Smith
4cec2ecdc6 changes 2025-09-26 00:09:23 -07:00
Zack Radisic
cdeb7bfb00 proper event loop stuff 2025-09-25 20:35:42 -07:00
Alistair Smith
98b24f5797 2025-09-25 20:05:45 -07:00
Alistair Smith
678843fb59 Merge branch 'zack/ssg-3' of github.com:oven-sh/bun into ali/react 2025-09-25 20:00:06 -07:00
Alistair Smith
7227745249 0.0.0-canary.7 2025-09-25 19:59:35 -07:00
Zack Radisic
472e2d379f fixes 2025-09-25 19:54:39 -07:00
autofix-ci[bot]
16360c9432 [autofix.ci] apply automated fixes 2025-09-26 02:46:56 +00:00
Alistair Smith
1b3d0d5c40 regen 2025-09-25 19:43:57 -07:00
Alistair Smith
799248bfb4 use vendored rsdb 2025-09-25 19:42:06 -07:00
Alistair Smith
a2689c03e9 change 2025-09-25 19:31:10 -07:00
Zack Radisic
2a7e2c9cf3 Remove unncessary dupe 2025-09-25 19:13:59 -07:00
Alistair Smith
4f0d2a5624 throw if no exports instead of silently failing 2025-09-25 18:40:49 -07:00
Alistair Smith
23230112b0 types 2025-09-25 18:31:29 -07:00
Alistair Smith
c2c2a1685a changes 2025-09-25 18:15:31 -07:00
Alistair Smith
09716704bb pageModule might not resolve 2025-09-25 17:46:18 -07:00
Alistair Smith
2d3223c5a6 Merge branch 'zack/ssg-3' of github.com:oven-sh/bun into ali/react 2025-09-25 17:34:56 -07:00
Alistair Smith
891ea726d6 changes 2025-09-25 17:29:52 -07:00
Zack Radisic
11eddb2cf1 resolve comments 2025-09-25 15:20:27 -07:00
autofix-ci[bot]
0cc63255b1 [autofix.ci] apply automated fixes 2025-09-25 07:05:28 +00:00
Zack Radisic
71a5f9fb26 Delete console logs 2025-09-25 00:02:14 -07:00
Zack Radisic
b257967189 Merge branch 'zack/ssg-3' of github.com:oven-sh/bun into zack/ssg-3 2025-09-25 00:00:48 -07:00
Zack Radisic
4e629753cc better way to do Response.render(...) 2025-09-24 23:57:45 -07:00
Jarred Sumner
7204820f19 Update BakeGlobalObject.cpp 2025-09-24 23:47:29 -07:00
Jarred Sumner
af3a1ffd46 Update BakeGlobalObject.cpp 2025-09-24 23:47:18 -07:00
Jarred Sumner
2ff068dad2 Update visitExpr.zig 2025-09-24 23:07:27 -07:00
Alistair Smith
927065238b allow warnings in bun install 2025-09-24 21:55:31 -07:00
Alistair Smith
fa727b22de remove 2025-09-24 21:45:23 -07:00
Alistair Smith
f20b0ced8e tidy bake harness 2025-09-24 21:41:34 -07:00
Alistair Smith
17fdb5bcdf move this 2025-09-24 21:37:25 -07:00
Alistair Smith
f65d89ff8b changes 2025-09-24 21:34:49 -07:00
Alistair Smith
c1931c11fe lots of Framework interface documentation 2025-09-24 20:24:06 -07:00
Alistair Smith
67d27499c3 canary.5 2025-09-24 19:49:34 -07:00
Alistair Smith
85db75611b canary.3 2025-09-24 19:38:24 -07:00
Alistair Smith
61519b320d reorganise 2025-09-24 19:20:30 -07:00
Alistair Smith
c129d683cd types & other changes 2025-09-24 19:08:31 -07:00
Alistair Smith
0b0ffbf250 Merge branch 'zack/ssg-3' into ali/react 2025-09-24 18:58:13 -07:00
Alistair Smith
c64dd684c8 vendor react-server-dom-bun 2025-09-24 18:55:19 -07:00
Alistair Smith
5aa5906ccf Merge branch 'main' into zack/ssg-3 2025-09-24 18:48:29 -07:00
Alistair Smith
c93d8cf12b changes 2025-09-24 13:53:46 -07:00
Alistair Smith
1a1091fd2c 2025-09-23 21:49:34 -07:00
Alistair Smith
bdf77f968c Merge branch 'zack/ssg-3' of github.com:oven-sh/bun into ali/react 2025-09-23 21:12:39 -07:00
Zack Radisic
457b4a46b3 Merge branch 'zack/ssg-3' of github.com:oven-sh/bun into zack/ssg-3 2025-09-23 21:11:50 -07:00
Zack Radisic
3b2bea9820 better way to escape 2025-09-23 21:11:28 -07:00
Alistair Smith
5b4b99e2c4 Merge branch 'zack/ssg-3' into ali/react 2025-09-23 21:08:04 -07:00
Alistair Smith
da2be3f582 cleanup 2025-09-23 20:55:33 -07:00
Alistair Smith
7282e92e48 try fix ci for bun-framework-react 2025-09-23 20:40:03 -07:00
Alistair Smith
80c28b6280 change 2025-09-23 18:55:03 -07:00
Alistair Smith
e40238fdc2 instant crash 2025-09-23 18:27:05 -07:00
Alistair Smith
166e961202 listen for shell 2025-09-23 18:24:18 -07:00
Zack Radisic
58ecff4e0c Merge branch 'main' into zack/ssg-3 2025-09-23 18:06:43 -07:00
Alistair Smith
a548ae7038 remove default title 2025-09-23 17:54:59 -07:00
Alistair Smith
bbaabedce6 fix: use CatchScope 2025-09-23 16:55:29 -07:00
Alistair Smith
f84f90c09f rm .app test 2025-09-23 16:51:29 -07:00
Zack Radisic
43a7b6518a fix 2025-09-23 15:46:31 -07:00
Alistair Smith
c85ab5218e feat: report "use client" warning on more well known client hooks 2025-09-23 14:53:57 -07:00
Alistair Smith
bade403361 fix: Don't report more than once for missing "use client" calls 2025-09-23 14:45:10 -07:00
Alistair Smith
047eecc90c Merge branch 'zack/ssg-3' of github.com:oven-sh/bun into ali/react 2025-09-23 14:22:31 -07:00
Zack Radisic
f03a1ab1c9 Update 2025-09-23 13:49:33 -07:00
Zack Radisic
1e3057045c Merge branch 'zack/ssg-3' of github.com:oven-sh/bun into zack/ssg-3 2025-09-23 13:46:11 -07:00
Zack Radisic
e92fd08930 make it work 2025-09-23 02:32:19 -07:00
autofix-ci[bot]
deb3e94948 [autofix.ci] apply automated fixes 2025-09-23 09:17:41 +00:00
Zack Radisic
1b01f7c0da Merge branch 'zack/ssg-3' of github.com:oven-sh/bun into zack/ssg-3 2025-09-23 02:12:33 -07:00
Zack Radisic
5e256e4b1f fix 2025-09-23 02:09:43 -07:00
Alistair Smith
fc6fdbe300 rm 2025-09-22 23:20:56 -07:00
Alistair Smith
247629aded cleaning 2025-09-22 20:24:20 -07:00
Alistair Smith
2894e8d309 change 2025-09-22 20:03:16 -07:00
Alistair Smith
cc84e271ff changes 2025-09-22 18:10:57 -07:00
Alistair Smith
c07150d5b1 types 2025-09-22 16:27:10 -07:00
Alistair Smith
b0d3815cf9 Merge branch 'zack/ssg-3' of github.com:oven-sh/bun into ali/react 2025-09-22 16:24:39 -07:00
Zack Radisic
f145d8c30c Merge branch 'main' into zack/ssg-3 2025-09-22 15:59:55 -07:00
Alistair Smith
3a23965581 Merge remote-tracking branch 'origin/zack/ssg-3' into ali/react 2025-09-22 15:56:42 -07:00
Alistair Smith
0b45b9c29e Add request property to bun:app module and enhance type definitions
- Added `request` property to the `bun:app` module for better request handling.
- Updated `hmr-runtime-server.ts` to use `RouteMetadata` for improved type safety.
- Introduced `$ERR_SSR_RESPONSE_EXPECTED` error function in builtins for SSR response handling.
2025-09-22 15:49:29 -07:00
Zack Radisic
9d679811cd get http method 2025-09-22 14:45:41 -07:00
Zack Radisic
cda3eb5396 delete stupid slop tests 2025-09-22 14:43:26 -07:00
Zack Radisic
b17dccc6e0 fix that 2025-09-21 14:31:36 -07:00
Alistair Smith
99a80a6fe6 changes 2025-09-19 18:47:08 -07:00
Alistair Smith
8b7bc0fe59 dont attempt to resolve initial rsc payload when doing first render on server 2025-09-19 18:38:53 -07:00
Alistair Smith
7e89ca3d2f link 2025-09-19 18:26:38 -07:00
Alistair Smith
d8fa01ed41 basic impl 2025-09-19 18:16:09 -07:00
Alistair Smith
361cd05676 move framework entrypoints 2025-09-19 18:09:14 -07:00
Zack Radisic
dbe15d3020 okay fuck 2025-09-19 17:44:08 -07:00
Alistair Smith
3a200e8097 overlay: print err message with whitespace formatting 2025-09-19 17:43:05 -07:00
Alistair Smith
2701292a9f edit react framework 2025-09-19 17:34:38 -07:00
Alistair Smith
61b1aded3e push 2025-09-19 17:27:14 -07:00
Alistair Smith
36a414c087 change import to type and add error handling for synthetic modules 2025-09-19 17:22:28 -07:00
Alistair Smith
ac02036879 Merge remote-tracking branch 'origin/zack/ssg-3' into ali/react 2025-09-19 17:01:07 -07:00
Alistair Smith
612d41185b change versions 2025-09-19 17:00:40 -07:00
Alistair Smith
59b34efea8 link 2025-09-19 16:58:51 -07:00
Alistair Smith
243f3652f1 remove old commented code 2025-09-19 16:52:03 -07:00
Alistair Smith
11dc2fae56 revert HotReloadEvent.zig 2025-09-19 16:36:54 -07:00
Alistair Smith
0919f237a5 remove 2025-09-19 16:13:59 -07:00
Alistair Smith
691e731404 changes 2025-09-19 16:12:32 -07:00
Alistair Smith
1a19be07ee remove unnecessary data output in development mode 2025-09-19 16:10:37 -07:00
Alistair Smith
903ac7bdd5 Refactor DevServer: remove unused bakeDebug output and clean up route bundling logic 2025-09-19 16:00:55 -07:00
Alistair Smith
0ac6b17d4a revert some changes 2025-09-19 15:53:49 -07:00
Alistair Smith
921e3578b1 change 2025-09-19 15:51:05 -07:00
Alistair Smith
101bcb1ea0 there is no is_built_in_react anymore 2025-09-19 15:45:11 -07:00
Alistair Smith
f691ea1e96 rm 2025-09-19 15:44:23 -07:00
Alistair Smith
53208e2538 .staticRouters is not implemented 2025-09-19 15:35:10 -07:00
Alistair Smith
53299d78b1 refactor framework definition and options interface in bun-types; remove package load logic from Framework struct 2025-09-19 15:20:34 -07:00
Alistair Smith
363c4a5c06 revert 2025-09-19 14:29:48 -07:00
Alistair Smith
6e6120640e remove react special case resolution 2025-09-19 14:29:02 -07:00
Alistair Smith
6556138c7b types 2025-09-19 14:08:00 -07:00
Alistair Smith
aea7b196e6 Revert visitExpr 2025-09-19 14:07:51 -07:00
Zack Radisic
b3f92b0889 fix test 2025-09-19 13:52:35 -07:00
Zack Radisic
dab797b834 fix test 2025-09-18 20:52:35 -07:00
Alistair Smith
a8ff3f8ac3 move module map 2025-09-18 18:58:12 -07:00
Alistair Smith
b516eedc67 fix mod 2025-09-18 17:57:46 -07:00
Alistair Smith
bcea163fd2 update framework def 2025-09-18 17:55:28 -07:00
Alistair Smith
a47cbef4ca restore bundler options 2025-09-18 17:39:48 -07:00
Alistair Smith
90e68fa095 use different react bundles 2025-09-18 17:33:49 -07:00
Alistair Smith
da0b090834 use the specific bundles 2025-09-18 17:25:26 -07:00
Alistair Smith
e6aced6637 remove 2025-09-18 17:17:27 -07:00
Alistair Smith
ce560cd318 Merge branch 'zack/ssg-3' of github.com:oven-sh/bun into ali/react 2025-09-18 17:16:07 -07:00
Alistair Smith
e554c4e1ca Update bun-framework-react dependencies and imports
- Changed the version of `react-server-dom-bun` from a local link to a specific experimental version in `package.json` and `bun.lock`.
- Updated the import path for `renderToPipeableStream` to use the `.node` variant for compatibility.
- Added `neo-async` as a dependency for `react-server-dom-bun` to ensure proper functionality.
2025-09-18 14:18:11 -07:00
Zack Radisic
731f42ca72 fix test 2025-09-17 20:49:55 -07:00
Zack Radisic
f33a852a80 merge 2025-09-17 17:14:56 -07:00
Zack Radisic
f5122bdbf1 use import instead of class 2025-09-17 17:09:18 -07:00
Alistair Smith
c29c69b9b5 types 2025-09-17 13:55:46 -07:00
Zack Radisic
916d44fc45 Merge branch 'zack/ssg-3' of github.com:oven-sh/bun into zack/ssg-3 2025-09-16 23:12:15 -07:00
Zack Radisic
421a4f37cd FIX the concurrent request bug 2025-09-16 21:55:05 -07:00
Alistair Smith
d0da7076e6 types 2025-09-16 16:35:41 -07:00
Alistair Smith
ea78d564da bump react 2025-09-16 16:29:45 -07:00
Alistair Smith
6338d55f70 continue 2025-09-16 15:56:29 -07:00
Alistair Smith
d3bdc77274 types 2025-09-16 15:56:24 -07:00
Alistair Smith
ecd2fed665 try with redo of react-server-dom-bun 2025-09-16 15:11:22 -07:00
Alistair Smith
28447ab578 Refactor HotReloadEvent and comment out unused code in BundleV2
- Updated `HotReloadEvent.zig` to use duplicated strings for extra files to prevent potential memory issues.
- Commented out unused code related to "bun:app" in `bundle_v2.zig` to improve clarity and maintainability.
2025-09-15 21:55:59 -07:00
Alistair Smith
3e798f1787 type only import 2025-09-15 21:24:12 -07:00
Alistair Smith
a64f073ad3 minimize 2025-09-15 21:13:45 -07:00
Alistair Smith
bb19610f0d Update Bun framework to use react-server-dom-webpack and upgrade dependencies
- Changed module imports from "react-server-dom-bun" to "react-server-dom-webpack" in multiple files.
- Updated dependencies in package.json and bun.lock to use newer versions of React, React DOM, React Refresh, and the new react-server-dom-webpack package.
- Adjusted type definitions in bun-types to reflect changes in import sources.

This update enhances compatibility with the latest React features and improves the overall framework structure.
2025-09-15 21:00:46 -07:00
Alistair Smith
ed4a887047 Merge branch 'zack/ssg-3' into ali/react 2025-09-15 16:38:40 -07:00
Alistair Smith
894a654e26 print errors during framework resolution 2025-09-15 16:38:27 -07:00
Zack Radisic
99dd08bccb Merge branch 'main' into zack/ssg-3 2025-09-15 16:26:34 -07:00
Alistair Smith
7339d1841b specify absolute path 2025-09-15 15:15:52 -07:00
Alistair Smith
1217e87379 remove builtInModules in zig 2025-09-15 15:07:32 -07:00
Alistair Smith
704661e96f remove support for builtInModules 2025-09-15 15:05:32 -07:00
autofix-ci[bot]
8e659b2dc8 [autofix.ci] apply automated fixes 2025-09-15 20:44:31 +00:00
Alistair Smith
93007de396 Merge branch 'zack/ssg-3' of github.com:oven-sh/bun into ali/react 2025-09-15 13:39:44 -07:00
Zack Radisic
2166f0c200 Merge branch 'main' into zack/ssg-3 2025-09-13 18:43:47 -07:00
Zack Radisic
1a0a081e75 woops 2025-09-13 14:49:13 -07:00
Zack Radisic
2eb33628d1 use jsc call conv 2025-09-12 23:38:47 -07:00
Zack Radisic
56e9c92b4a Merge branch 'zack/ssg-3' of github.com:oven-sh/bun into zack/ssg-3 2025-09-12 19:27:09 -07:00
Zack Radisic
34cfdf039a Fix 2025-09-12 19:26:42 -07:00
Alistair Smith
d4a9c7a161 some helpful logs 2025-09-12 18:38:12 -07:00
Alistair Smith
b5c16dcc1b Enhance DevServer logging for framework initialization and route scanning
This update adds detailed logging to the DevServer's initialization process, including framework configuration details such as server components, React Fast Refresh, and file system router types. Additionally, it logs the directory being scanned for routes and the number of routes found, improving visibility into the server's setup and routing process.
2025-09-12 17:49:45 -07:00
Alistair Smith
0ba166eea3 cleaning 2025-09-12 17:31:43 -07:00
autofix-ci[bot]
1920a7c63c [autofix.ci] apply automated fixes 2025-09-12 23:39:19 +00:00
Zack Radisic
d56005b520 Merge branch 'zack/ssg-3' of github.com:oven-sh/bun into zack/ssg-3 2025-09-12 16:36:03 -07:00
Alistair Smith
2bd5d68047 Add support for --app flag in CLI to run bun.app.ts or bun.app.js
This update allows users to specify the `--app` flag when running Bun commands. If the flag is provided without additional arguments, the CLI will automatically look for and execute the bun.app.ts or bun.app.js file in the current working directory. If the file is not found, an error message will be displayed, guiding users to the expected file locations.
2025-09-12 16:16:17 -07:00
Zack Radisic
9c5c4edac4 Bun.SSRResponse -> import { Response } from 'bun:app' 2025-09-12 15:47:26 -07:00
Alistair Smith
cae0673dc4 load the framework 2025-09-12 14:44:51 -07:00
Alistair Smith
51e18d379f try this 2025-09-12 13:32:13 -07:00
Claude Bot
199781bf4f Fix banned words and package.json lint errors
- Replace bun.outOfMemory() with bun.handleOom(err)
- Replace std.mem.indexOfAny with bun.strings.indexOfAny
- Replace arguments_old with argumentsAsArray
- Fix peechy version to be exact (remove ^)

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-12 04:22:33 +00:00
Zack Radisic
ffeb21c49b add the bun:app module 2025-09-11 17:51:49 -07:00
Alistair Smith
d54ffd8012 dont use dom libs 2025-09-11 17:43:45 -07:00
Alistair Smith
dca34819b6 remove uninit state for a store 2025-09-11 16:45:55 -07:00
Alistair Smith
b4add533e6 fix a bunch of typescript problems in our test suite 2025-09-11 15:30:41 -07:00
autofix-ci[bot]
7afcc8416f [autofix.ci] apply automated fixes 2025-09-11 04:35:31 +00:00
Zack Radisic
1ef578a0b4 Merge branch 'zack/ssg-3' of github.com:oven-sh/bun into zack/ssg-3 2025-09-10 21:33:59 -07:00
Zack Radisic
8be4fb61d0 make it better 2025-09-10 21:33:40 -07:00
Zack Radisic
208ac7fb60 smol changes 2025-09-10 21:33:20 -07:00
Zack Radisic
29b6faadf8 Make MiString 2025-09-10 21:16:36 -07:00
autofix-ci[bot]
99df2e071f [autofix.ci] apply automated fixes 2025-09-11 02:43:47 +00:00
Zack Radisic
a3d91477a8 merge 2025-09-10 19:42:10 -07:00
Zack Radisic
52c3e2e3f8 remove dead code 2025-09-10 19:35:07 -07:00
Zack Radisic
a7e95718ac fix some more stuff 2025-09-10 19:29:06 -07:00
Zack Radisic
db2960d27b fix some C++ stuff 2025-09-10 19:23:17 -07:00
Zack Radisic
bbfac709cc dev server source provider destructor 2025-09-10 19:18:52 -07:00
Zack Radisic
41fbeacee1 escape json if needed 2025-09-10 19:18:40 -07:00
Zack Radisic
24b2929c9a update IncrementalGraph 2025-09-10 18:56:50 -07:00
Zack Radisic
bf992731c6 use correct allocator 2025-09-10 18:50:28 -07:00
Zack Radisic
eafc04cc5d remove that 2025-09-10 18:41:57 -07:00
Zack Radisic
95cacdc6be fix slop tests 2025-09-10 18:05:54 -07:00
Zack Radisic
6cf46e67f6 Merge branch 'zack/ssg-3-bun' into zack/ssg-3 2025-09-10 17:06:27 -07:00
Zack Radisic
f28670ac68 update 2025-09-10 17:05:59 -07:00
Zack Radisic
0df21d7f30 handle scope exceptions 2025-09-10 16:51:06 -07:00
Zack Radisic
39e7e55802 Move SSRResonse -> Bun.SSRResponse 2025-09-10 16:49:30 -07:00
Alistair Smith
a63888ed6d Improve type safety of DOM element creation utils
The improved types ensure only valid HTML attributes can be set for each
element type, making the code more type-safe and self-documenting.
2025-09-09 21:55:48 -07:00
Alistair Smith
e50385879b Fix Router navigation state handling
The changes improve state handling during client-side navigation by: -
Using correct function updates with setAppState - Preserving abort
controller on updates - Adding proper cache ID comparison - Moving
router instance to constants
2025-09-09 21:39:19 -07:00
Alistair Smith
20d2f3805e force passing a cacheId 2025-09-09 21:21:03 -07:00
Alistair Smith
d984f8f5ad Merge branch 'ali/react' of github.com:oven-sh/bun into ali/react 2025-09-09 21:09:58 -07:00
Alistair Smith
9ea2ec876e Refactor the client a lot 2025-09-09 21:06:37 -07:00
Zack Radisic
0919e45c23 clean up 2025-09-09 20:54:55 -07:00
Zack Radisic
fd41a41ab9 remove dead code 2025-09-09 20:49:59 -07:00
Zack Radisic
fc06e1cf14 yoops 2025-09-09 20:18:32 -07:00
Zack Radisic
1778713cbf forgot to deinit 2025-09-09 20:17:13 -07:00
autofix-ci[bot]
02d9da73bd [autofix.ci] apply automated fixes 2025-09-10 02:43:59 +00:00
Alistair Smith
6562275d15 Merge remote-tracking branch 'origin/zack/ssg-3' into ali/react 2025-09-09 19:39:18 -07:00
Alistair Smith
cccae0cc79 Start splitting up the client 2025-09-09 19:37:58 -07:00
Zack Radisic
c10d184448 fix the test 2025-09-09 18:49:45 -07:00
Alistair Smith
0f8a232466 delete sources (now that they're gen'd on build) 2025-09-09 18:42:18 -07:00
autofix-ci[bot]
f47df15c18 [autofix.ci] apply automated fixes 2025-09-10 01:39:39 +00:00
Alistair Smith
f2d3141767 Merge remote-tracking branch 'origin/zack/ssg-3' into ali/react 2025-09-09 18:36:50 -07:00
Zack Radisic
c8b21f207d Fix node test that is failing 2025-09-09 17:25:26 -07:00
Zack Radisic
6357978b90 change that up 2025-09-09 15:05:15 -07:00
Alistair Smith
151d8bb413 refine types for RSC payload handling in bun-framework-react 2025-09-08 19:51:33 -07:00
Alistair Smith
42bfccee3c revert buildstep changes 2025-09-08 19:40:19 -07:00
Alistair Smith
f219a29248 enqueueChunks 2025-09-08 19:28:45 -07:00
Alistair Smith
b588512237 change build 2025-09-08 18:53:39 -07:00
Alistair Smith
3a42ad8b1f make the build simpelr 2025-09-08 18:20:22 -07:00
Alistair Smith
cc1fff363d types 2025-09-08 18:02:17 -07:00
Alistair Smith
ba5e4784aa rm 2025-09-08 17:57:52 -07:00
Alistair Smith
3e747886aa typescript casting 2025-09-08 17:42:31 -07:00
Zack Radisic
9504d14b7a cache the wrap component function 2025-09-08 17:38:38 -07:00
Alistair Smith
911b670621 move types 2025-09-08 17:38:06 -07:00
Alistair Smith
679282b8c6 types 2025-09-08 17:32:50 -07:00
autofix-ci[bot]
1f79bc15a3 [autofix.ci] apply automated fixes 2025-09-09 00:24:14 +00:00
Alistair Smith
80a945f03f fix that 2025-09-08 17:22:06 -07:00
Zack Radisic
43054c9a7f fix it for errors 2025-09-08 17:17:08 -07:00
Zack Radisic
2fad71dd45 use .bytes() but it is broken on errors 2025-09-08 16:59:02 -07:00
Alistair Smith
6b2c3e61ea refactor: update bun-framework-react structure and types
- Remove unused components and utility files from bun-framework-react.
- Update type definitions in bake.private.d.ts to allow for synthetic modules.
- Modify exports in package.json to simplify module access.
- Ensure proper registration of bun:app in the HMR module system.
2025-09-08 16:57:53 -07:00
Alistair Smith
43d447f9fe register bun:app properly 2025-09-08 16:19:50 -07:00
Zack Radisic
8b35b5634a Update stuff 2025-09-08 15:56:21 -07:00
Alistair Smith
811f0888c8 - Change unique symbol types to unknown in bake.private.d.ts for better type flexibility.
- Update onServerSideReload function return type in rendering.d.ts to void.
- Modify onServerSideReload declaration in hmr-module.ts to accept both Promise<void> and void.
- Enhance type safety in registerSynthetic function with a generic interface for built-in modules.
2025-09-08 15:28:51 -07:00
Alistair Smith
a5d7830862 build: add bun-framework-react to cmake source tracking
- Include packages/bun-framework-react in build dependencies
  - Update Sources.json to glob React framework source files
  - Fix TypeScript types in bun-framework-react (Uint8Array generics, assertions)
  - Add package.json exports for React framework modules
2025-09-08 15:18:10 -07:00
Alistair Smith
ef17dc57e4 refactor: update types in bake.private.d.ts and tsconfig.json; remove unused client and server rendering files 2025-09-08 15:08:29 -07:00
Alistair Smith
e58cb4511e changes to overlay and client.tsx entry 2025-09-08 15:02:56 -07:00
Alistair Smith
d44b3db1cb refactor: update server and SSR manifest types in hmr-module.ts 2025-09-08 14:45:38 -07:00
alii
857e25d88c bun scripts/glob-sources.mjs 2025-09-08 21:41:31 +00:00
Alistair Smith
6eee2eeaf6 some ssr changes 2025-09-08 14:40:46 -07:00
Alistair Smith
cc3e4d8319 types changes 2025-09-08 14:24:40 -07:00
Alistair Smith
278c2e7fb6 fix relative path in bake.zig 2025-09-08 14:14:28 -07:00
Alistair Smith
e296928ab9 abortcontroller bun-types tests 2025-09-08 14:14:28 -07:00
Alistair Smith
39c1bf38f5 fix: AbortController missing a definition 2025-09-08 14:14:28 -07:00
Alistair Smith
91d30b4da0 update bun-types from main 2025-09-08 14:14:28 -07:00
autofix-ci[bot]
c2812fff79 [autofix.ci] apply automated fixes 2025-09-08 21:12:05 +00:00
Alistair Smith
e0337f5649 fix types for rendering api 2025-09-08 14:08:23 -07:00
alii
d05768cc18 bun scripts/glob-sources.mjs 2025-09-08 20:58:26 +00:00
Alistair Smith
1ad67908fc nuke bun-react 2025-09-08 13:58:05 -07:00
Alistair Smith
f55e320f41 start extracting parts of the framework out 2025-09-08 13:56:42 -07:00
Zack Radisic
8e0cf4c5e0 update 2025-09-08 13:38:12 -07:00
Zack Radisic
5dcf8a8076 merge 2025-09-08 13:34:37 -07:00
Zack Radisic
d6b155f056 add test 2025-09-08 13:04:32 -07:00
Zack Radisic
5f8393cc99 better comment 2025-09-05 20:12:40 -07:00
Zack Radisic
ee7dfefbe0 Better Response -> BakeResponse transform 2025-09-05 20:10:08 -07:00
Zack Radisic
6d132e628f yoops 2025-09-05 18:04:02 -07:00
Zack Radisic
ae9ecc99c9 cache react element symbols and use JSBunRequest for cookies 2025-09-05 18:03:03 -07:00
Zack Radisic
eeecbfa790 okie dokie 2025-09-05 15:58:38 -07:00
Zack Radisic
862f7378e4 wip response object c++ class thingy 2025-09-04 14:09:34 -07:00
Zack Radisic
636e597b60 transform Response -> SSRResponse 2025-09-04 12:48:23 -07:00
Zack Radisic
6abb9f81eb that took forever to find and fix 2025-09-03 17:29:21 -07:00
Zack Radisic
aa33b11a7a Forgot to commit test file 2025-09-02 13:18:49 -07:00
Zack Radisic
21266f5263 fix compile error 2025-09-02 12:09:02 -07:00
Zack Radisic
c5fc729fde fix tests 2025-09-01 17:18:14 -07:00
Zack Radisic
03d1e48004 holy moly fix all the leaks 2025-09-01 15:45:17 -07:00
Zack Radisic
19b9c4a850 Properly error when using functions not available when streaming = true 2025-08-27 17:26:06 -07:00
Zack Radisic
842503ecb1 fix errors 2025-08-27 16:39:41 -07:00
Zack Radisic
cb9c45c26c always use dev.allocator() to allocate sourcemaps
we honestly should remove this dev.allocator() and just use leak
sanitizer
2025-08-26 14:56:02 -07:00
Zack Radisic
917dcc846f Merge branch 'zack/ssg-3' of github.com:oven-sh/bun into zack/ssg-3 2025-08-26 13:13:34 -07:00
Zack Radisic
0fb277a56e fix more compile errors 2025-08-26 13:12:34 -07:00
autofix-ci[bot]
c343aca21e [autofix.ci] apply automated fixes 2025-08-26 01:33:55 +00:00
Zack Radisic
e89a0f3807 Merge branch 'main' into zack/ssg-3 2025-08-25 18:23:47 -07:00
Zack Radisic
59f12d30b3 response redirect 2025-08-25 17:33:32 -07:00
Zack Radisic
f0d4fa8b63 support return Response.render(...) 2025-08-25 15:36:55 -07:00
Zack Radisic
3fb0a824cb wip 2025-08-21 18:07:19 -07:00
Zack Radisic
ab3566627d comment 2025-08-20 15:11:07 -07:00
Zack Radisic
3906407e5d stuff 2025-08-20 15:07:39 -07:00
Zack Radisic
33447ef2db that was way more complicated then need be 2025-08-20 15:00:46 -07:00
Zack Radisic
3760407908 stuff 2025-08-19 20:07:10 -07:00
Zack Radisic
c1f0ce277d comment that cursed shit 2025-08-19 19:54:31 -07:00
Zack Radisic
bfe3041179 cookie + request 2025-08-19 19:50:10 -07:00
Zack Radisic
5b6344cf3c make it work 2025-08-19 19:29:25 -07:00
Zack Radisic
b4fdf41ea5 WIP 2025-08-19 16:18:28 -07:00
Zack Radisic
b9da6b71f9 stupid hack 2025-08-19 00:36:34 -07:00
Zack Radisic
87487468f3 wip 2025-08-19 00:07:31 -07:00
Zack Radisic
cfdeb42023 WIP 2025-08-18 22:23:30 -07:00
Zack Radisic
20e4c094ac support `streaming = false | true= 2025-08-18 17:13:31 -07:00
Zack Radisic
17be416250 Merge branch 'main' into zack/ssg-3 2025-08-18 16:54:59 -07:00
Zack Radisic
9745f01041 Merge branch 'zack/dev-server-sourcemaps-server-side' into zack/ssg-3 2025-08-13 16:32:48 -07:00
Zack Radisic
16131f92e1 Merge branch 'main' into zack/dev-server-sourcemaps-server-side 2025-08-13 16:32:02 -07:00
Zack Radisic
59a4d0697b fix 2025-08-13 16:31:27 -07:00
Zack Radisic
78a2ae44aa move change back 2025-08-13 16:30:44 -07:00
Zack Radisic
7f295919a9 Merge branch 'main' into zack/ssg-3 2025-08-13 16:28:49 -07:00
Zack Radisic
1d0984b5c4 Merge branch 'main' into zack/dev-server-sourcemaps-server-side 2025-08-08 17:23:33 -07:00
Zack Radisic
dfa93a8ede small 2025-08-08 17:18:19 -07:00
Zack Radisic
c8773c5e30 error modal on ssr error 2025-08-07 20:04:58 -07:00
Zack Radisic
0f74fafc59 cleanup 2025-08-07 18:24:03 -07:00
Zack Radisic
47d6e161fe fix that 2025-08-07 18:10:05 -07:00
Zack Radisic
160625c37c remove debugging stuff 2025-08-06 18:17:46 -07:00
Zack Radisic
1b9b686772 fix compile errors from merge 2025-08-05 20:22:09 -07:00
Zack Radisic
6f3e098bac Merge branch 'main' into zack/dev-server-sourcemaps-server-side 2025-08-05 17:14:04 -07:00
Zack Radisic
4c6b296a7c okie dokie 2025-08-05 17:04:54 -07:00
Zack Radisic
2ab962bf6b small stuff 2025-07-31 15:32:34 -07:00
Zack Radisic
f556fc987c test 2025-07-30 21:56:09 -07:00
Zack Radisic
3a1b12ee61 no need to percent encode or add "file://" to server-side sourcemaps 2025-07-30 17:55:52 -07:00
Zack Radisic
a952b4200e fix that 2025-07-30 15:58:50 -07:00
Zack Radisic
24485fb432 WIP 2025-07-29 17:24:53 -07:00
Zack Radisic
b10fda0487 allow app configuration form Bun.serve(...) 2025-07-28 15:21:39 -07:00
Zack Radisic
740cdaba3d - fix catch-all routes not working in dev server
- fix crash
- fix require bug
2025-07-27 18:31:01 -07:00
Zack Radisic
68be15361a fix use after free 2025-07-27 01:19:53 -07:00
Zack Radisic
c57be8dcdb extra option 2025-07-27 00:57:29 -07:00
Zack Radisic
5115a88126 Merge branch 'main' into zack/ssg-3 2025-07-23 13:49:18 -07:00
Zack Radisic
e992b804c8 fix 2025-07-23 13:47:55 -07:00
Zack Radisic
b92555e099 better error message 2025-07-23 11:35:23 -07:00
Zack Radisic
381848cd69 WIP less noisy errors 2025-07-22 16:40:46 -07:00
Zack Radisic
61f9845f80 Merge branch 'main' into zack/ssg-3 2025-07-21 23:46:54 -07:00
Zack Radisic
abc52da7bb add check for React.useState 2025-07-21 13:31:51 -07:00
2110 changed files with 98777 additions and 121311 deletions

19
.aikido
View File

@@ -1,19 +0,0 @@
exclude:
paths:
- test
- scripts
- bench
- packages/bun-lambda
- packages/bun-release
- packages/bun-wasm
- packages/bun-vscode
- packages/bun-plugin-yaml
- packages/bun-plugin-svelte
- packages/bun-native-plugin-rs
- packages/bun-native-bundler-plugin-api
- packages/bun-inspector-protocol
- packages/bun-inspector-frontend
- packages/bun-error
- packages/bun-debug-adapter-protocol
- packages/bun-build-mdx-rs
- packages/@types/bun

View File

@@ -26,7 +26,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
wget curl git python3 python3-pip ninja-build \
software-properties-common apt-transport-https \
ca-certificates gnupg lsb-release unzip \
libxml2-dev ruby ruby-dev bison gawk perl make golang ccache \
libxml2-dev ruby ruby-dev bison gawk perl make golang \
&& add-apt-repository ppa:ubuntu-toolchain-r/test \
&& apt-get update \
&& apt-get install -y gcc-13 g++-13 libgcc-13-dev libstdc++-13-dev \
@@ -35,8 +35,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
&& wget https://apt.llvm.org/llvm.sh \
&& chmod +x llvm.sh \
&& ./llvm.sh ${LLVM_VERSION} all \
&& rm llvm.sh \
&& rm -rf /var/lib/apt/lists/*
&& rm llvm.sh
RUN --mount=type=tmpfs,target=/tmp \
@@ -111,14 +110,14 @@ ARG BUILDKITE_AGENT_TAGS
# Install Rust nightly
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y \
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y \
&& export PATH=$HOME/.cargo/bin:$PATH \
&& rustup install nightly \
&& rustup default nightly
RUN ARCH=$(if [ "$TARGETARCH" = "arm64" ]; then echo "arm64"; else echo "amd64"; fi) && \
echo "Downloading buildkite" && \
echo "Downloading buildkite" && \
curl -fsSL "https://github.com/buildkite/agent/releases/download/v3.87.0/buildkite-agent-linux-${ARCH}-3.87.0.tar.gz" -o /tmp/buildkite-agent.tar.gz && \
mkdir -p /tmp/buildkite-agent && \
tar -xzf /tmp/buildkite-agent.tar.gz -C /tmp/buildkite-agent && \
@@ -126,18 +125,6 @@ RUN ARCH=$(if [ "$TARGETARCH" = "arm64" ]; then echo "arm64"; else echo "amd64";
RUN mkdir -p /var/cache/buildkite-agent /var/log/buildkite-agent /var/run/buildkite-agent /etc/buildkite-agent /var/lib/buildkite-agent/cache/bun
# The following is necessary to configure buildkite to use a stable
# checkout directory for ccache to be effective.
RUN mkdir -p -m 755 /var/lib/buildkite-agent/hooks && \
cat <<'EOF' > /var/lib/buildkite-agent/hooks/environment
#!/bin/sh
set -efu
export BUILDKITE_BUILD_CHECKOUT_PATH=/var/lib/buildkite-agent/build
EOF
RUN chmod 744 /var/lib/buildkite-agent/hooks/environment
COPY ../*/agent.mjs /var/bun/scripts/
ENV BUN_INSTALL_CACHE=/var/lib/buildkite-agent/cache/bun
@@ -160,7 +147,7 @@ COPY . /workspace/bun
# Install Rust nightly
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y \
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y \
&& export PATH=$HOME/.cargo/bin:$PATH \
&& rustup install nightly \
&& rustup default nightly
@@ -174,4 +161,4 @@ RUN --mount=type=tmpfs,target=/workspace/bun/build \
ls -la \
&& bun run build:release \
&& mkdir -p /target \
&& cp -r /workspace/bun/build/release/bun /target/bun
&& cp -r /workspace/bun/build/release/bun /target/bun

View File

@@ -16,7 +16,6 @@ import {
getEmoji,
getEnv,
getLastSuccessfulBuild,
getSecret,
isBuildkite,
isBuildManual,
isFork,
@@ -124,13 +123,16 @@ const testPlatforms = [
{ os: "darwin", arch: "aarch64", release: "13", tier: "previous" },
{ os: "darwin", arch: "x64", release: "14", tier: "latest" },
{ os: "darwin", arch: "x64", release: "13", tier: "previous" },
{ os: "linux", arch: "aarch64", distro: "debian", release: "13", tier: "latest" },
{ os: "linux", arch: "x64", distro: "debian", release: "13", tier: "latest" },
{ os: "linux", arch: "x64", baseline: true, distro: "debian", release: "13", tier: "latest" },
{ os: "linux", arch: "x64", profile: "asan", distro: "debian", release: "13", tier: "latest" },
{ os: "linux", arch: "aarch64", distro: "debian", release: "12", tier: "latest" },
{ os: "linux", arch: "x64", distro: "debian", release: "12", tier: "latest" },
{ os: "linux", arch: "x64", baseline: true, distro: "debian", release: "12", tier: "latest" },
{ os: "linux", arch: "x64", profile: "asan", distro: "debian", release: "12", tier: "latest" },
{ os: "linux", arch: "aarch64", distro: "ubuntu", release: "25.04", tier: "latest" },
{ os: "linux", arch: "aarch64", distro: "ubuntu", release: "24.04", tier: "latest" },
{ os: "linux", arch: "x64", distro: "ubuntu", release: "25.04", tier: "latest" },
{ os: "linux", arch: "x64", distro: "ubuntu", release: "24.04", tier: "latest" },
{ os: "linux", arch: "x64", baseline: true, distro: "ubuntu", release: "25.04", tier: "latest" },
{ os: "linux", arch: "x64", baseline: true, distro: "ubuntu", release: "24.04", tier: "latest" },
{ os: "linux", arch: "aarch64", abi: "musl", distro: "alpine", release: "3.22", tier: "latest" },
{ os: "linux", arch: "x64", abi: "musl", distro: "alpine", release: "3.22", tier: "latest" },
{ os: "linux", arch: "x64", abi: "musl", baseline: true, distro: "alpine", release: "3.22", tier: "latest" },
@@ -221,7 +223,7 @@ function getImageName(platform, options) {
* @param {number} [limit]
* @link https://buildkite.com/docs/pipelines/command-step#retry-attributes
*/
function getRetry() {
function getRetry(limit = 0) {
return {
manual: {
permit_on_passed: true,
@@ -290,7 +292,7 @@ function getEc2Agent(platform, options, ec2Options) {
* @returns {string}
*/
function getCppAgent(platform, options) {
const { os, arch } = platform;
const { os, arch, distro } = platform;
if (os === "darwin") {
return {
@@ -311,7 +313,7 @@ function getCppAgent(platform, options) {
* @returns {string}
*/
function getLinkBunAgent(platform, options) {
const { os, arch } = platform;
const { os, arch, distro } = platform;
if (os === "darwin") {
return {
@@ -350,7 +352,14 @@ function getZigPlatform() {
* @param {PipelineOptions} options
* @returns {Agent}
*/
function getZigAgent(_platform, options) {
function getZigAgent(platform, options) {
const { arch } = platform;
// Uncomment to restore to using macOS on-prem for Zig.
// return {
// queue: "build-zig",
// };
return getEc2Agent(getZigPlatform(), options, {
instanceType: "r8g.large",
});
@@ -452,6 +461,23 @@ function getBuildCommand(target, options, label) {
return `bun run build:${buildProfile}`;
}
/**
* @param {Platform} platform
* @param {PipelineOptions} options
* @returns {Step}
*/
function getBuildVendorStep(platform, options) {
return {
key: `${getTargetKey(platform)}-build-vendor`,
label: `${getTargetLabel(platform)} - build-vendor`,
agents: getCppAgent(platform, options),
retry: getRetry(),
cancel_on_build_failing: isMergeQueue(),
env: getBuildEnv(platform, options),
command: `${getBuildCommand(platform, options)} --target dependencies`,
};
}
/**
* @param {Platform} platform
* @param {PipelineOptions} options
@@ -501,9 +527,9 @@ function getBuildZigStep(platform, options) {
const toolchain = getBuildToolchain(platform);
return {
key: `${getTargetKey(platform)}-build-zig`,
retry: getRetry(),
label: `${getTargetLabel(platform)} - build-zig`,
agents: getZigAgent(platform, options),
retry: getRetry(),
cancel_on_build_failing: isMergeQueue(),
env: getBuildEnv(platform, options),
command: `${getBuildCommand(platform, options)} --target bun-zig --toolchain ${toolchain}`,
@@ -553,6 +579,7 @@ function getBuildBunStep(platform, options) {
/**
* @typedef {Object} TestOptions
* @property {string} [buildId]
* @property {boolean} [unifiedTests]
* @property {string[]} [testFiles]
* @property {boolean} [dryRun]
*/
@@ -565,13 +592,12 @@ function getBuildBunStep(platform, options) {
*/
function getTestBunStep(platform, options, testOptions = {}) {
const { os, profile } = platform;
const { buildId, testFiles } = testOptions;
const { buildId, unifiedTests, testFiles } = testOptions;
const args = [`--step=${getTargetKey(platform)}-build-bun`];
if (buildId) {
args.push(`--build-id=${buildId}`);
}
if (testFiles) {
args.push(...testFiles.map(testFile => `--include=${testFile}`));
}
@@ -588,7 +614,7 @@ function getTestBunStep(platform, options, testOptions = {}) {
agents: getTestAgent(platform, options),
retry: getRetry(),
cancel_on_build_failing: isMergeQueue(),
parallelism: os === "darwin" ? 2 : 20,
parallelism: unifiedTests ? undefined : os === "darwin" ? 2 : 10,
timeout_in_minutes: profile === "asan" || os === "windows" ? 45 : 30,
env: {
ASAN_OPTIONS: "allow_user_segv_handler=1:disable_coredump=0:detect_leaks=0",
@@ -770,6 +796,8 @@ function getBenchmarkStep() {
* @property {Platform[]} [buildPlatforms]
* @property {Platform[]} [testPlatforms]
* @property {string[]} [testFiles]
* @property {boolean} [unifiedBuilds]
* @property {boolean} [unifiedTests]
*/
/**
@@ -940,6 +968,22 @@ function getOptionsStep() {
default: "false",
options: booleanOptions,
},
{
key: "unified-builds",
select: "Do you want to build each platform in a single step?",
hint: "If true, builds will not be split into separate steps (this will likely slow down the build)",
required: false,
default: "false",
options: booleanOptions,
},
{
key: "unified-tests",
select: "Do you want to run tests in a single step?",
hint: "If true, tests will not be split into separate steps (this will be very slow)",
required: false,
default: "false",
options: booleanOptions,
},
],
};
}
@@ -1005,6 +1049,8 @@ async function getPipelineOptions() {
buildImages: parseBoolean(options["build-images"]),
publishImages: parseBoolean(options["publish-images"]),
testFiles: parseArray(options["test-files"]),
unifiedBuilds: parseBoolean(options["unified-builds"]),
unifiedTests: parseBoolean(options["unified-tests"]),
buildPlatforms: buildPlatformKeys?.length
? buildPlatformKeys.flatMap(key => buildProfiles.map(profile => ({ ...buildPlatformsMap.get(key), profile })))
: Array.from(buildPlatformsMap.values()),
@@ -1070,7 +1116,7 @@ async function getPipeline(options = {}) {
const imagePlatforms = new Map(
buildImages || publishImages
? [...buildPlatforms, ...testPlatforms]
.filter(({ os }) => os !== "darwin")
.filter(({ os }) => os === "linux" || os === "windows")
.map(platform => [getImageKey(platform), platform])
: [],
);
@@ -1086,7 +1132,7 @@ async function getPipeline(options = {}) {
});
}
let { skipBuilds, forceBuilds, dryRun } = options;
let { skipBuilds, forceBuilds, unifiedBuilds, dryRun } = options;
dryRun = dryRun || !!buildImages;
/** @type {string | undefined} */
@@ -1104,7 +1150,7 @@ async function getPipeline(options = {}) {
const includeASAN = !isMainBranch();
if (!buildId) {
let relevantBuildPlatforms = includeASAN
const relevantBuildPlatforms = includeASAN
? buildPlatforms
: buildPlatforms.filter(({ profile }) => profile !== "asan");
@@ -1117,16 +1163,13 @@ async function getPipeline(options = {}) {
dependsOn.push(`${imageKey}-build-image`);
}
const steps = [];
steps.push(getBuildCppStep(target, options));
steps.push(getBuildZigStep(target, options));
steps.push(getLinkBunStep(target, options));
return getStepWithDependsOn(
{
key: getTargetKey(target),
group: getTargetLabel(target),
steps,
steps: unifiedBuilds
? [getBuildBunStep(target, options)]
: [getBuildCppStep(target, options), getBuildZigStep(target, options), getLinkBunStep(target, options)],
},
...dependsOn,
);
@@ -1135,13 +1178,13 @@ async function getPipeline(options = {}) {
}
if (!isMainBranch()) {
const { skipTests, forceTests, testFiles } = options;
const { skipTests, forceTests, unifiedTests, testFiles } = options;
if (!skipTests || forceTests) {
steps.push(
...testPlatforms.map(target => ({
key: getTargetKey(target),
group: getTargetLabel(target),
steps: [getTestBunStep(target, options, { testFiles, buildId })],
steps: [getTestBunStep(target, options, { unifiedTests, testFiles, buildId })],
})),
);
}
@@ -1184,43 +1227,6 @@ async function main() {
console.log("Generated options:", options);
}
startGroup("Querying GitHub for files...");
if (options && isBuildkite && !isMainBranch()) {
/** @type {string[]} */
let allFiles = [];
/** @type {string[]} */
let newFiles = [];
let prFileCount = 0;
try {
console.log("on buildkite: collecting new files from PR");
const per_page = 50;
const { BUILDKITE_PULL_REQUEST } = process.env;
for (let i = 1; i <= 10; i++) {
const res = await fetch(
`https://api.github.com/repos/oven-sh/bun/pulls/${BUILDKITE_PULL_REQUEST}/files?per_page=${per_page}&page=${i}`,
{ headers: { Authorization: `Bearer ${getSecret("GITHUB_TOKEN")}` } },
);
const doc = await res.json();
console.log(`-> page ${i}, found ${doc.length} items`);
if (doc.length === 0) break;
for (const { filename, status } of doc) {
prFileCount += 1;
allFiles.push(filename);
if (status !== "added") continue;
newFiles.push(filename);
}
if (doc.length < per_page) break;
}
console.log(`- PR ${BUILDKITE_PULL_REQUEST}, ${prFileCount} files, ${newFiles.length} new files`);
} catch (e) {
console.error(e);
}
if (allFiles.every(filename => filename.startsWith("docs/"))) {
console.log(`- PR is only docs, skipping tests!`);
return;
}
}
startGroup("Generating pipeline...");
const pipeline = await getPipeline(options);
if (!pipeline) {

View File

@@ -1,184 +0,0 @@
---
name: implementing-jsc-classes-cpp
description: Implements JavaScript classes in C++ using JavaScriptCore. Use when creating new JS classes with C++ bindings, prototypes, or constructors.
---
# Implementing JavaScript Classes in C++
## Class Structure
For publicly accessible Constructor and Prototype, create 3 classes:
1. **`class Foo : public JSC::DestructibleObject`** - if C++ fields exist; otherwise use `JSC::constructEmptyObject` with `putDirectOffset`
2. **`class FooPrototype : public JSC::JSNonFinalObject`**
3. **`class FooConstructor : public JSC::InternalFunction`**
No public constructor? Only Prototype and class needed.
## Iso Subspaces
Classes with C++ fields need subspaces in:
- `src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h`
- `src/bun.js/bindings/webcore/DOMIsoSubspaces.h`
```cpp
template<typename MyClassT, JSC::SubspaceAccess mode>
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) {
if constexpr (mode == JSC::SubspaceAccess::Concurrently)
return nullptr;
return WebCore::subspaceForImpl<MyClassT, WebCore::UseCustomHeapCellType::No>(
vm,
[](auto& spaces) { return spaces.m_clientSubspaceForMyClassT.get(); },
[](auto& spaces, auto&& space) { spaces.m_clientSubspaceForMyClassT = std::forward<decltype(space)>(space); },
[](auto& spaces) { return spaces.m_subspaceForMyClassT.get(); },
[](auto& spaces, auto&& space) { spaces.m_subspaceForMyClassT = std::forward<decltype(space)>(space); });
}
```
## Property Definitions
```cpp
static JSC_DECLARE_HOST_FUNCTION(jsFooProtoFuncMethod);
static JSC_DECLARE_CUSTOM_GETTER(jsFooGetter_property);
static const HashTableValue JSFooPrototypeTableValues[] = {
{ "property"_s, static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsFooGetter_property, 0 } },
{ "method"_s, static_cast<unsigned>(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsFooProtoFuncMethod, 1 } },
};
```
## Prototype Class
```cpp
class JSFooPrototype final : public JSC::JSNonFinalObject {
public:
using Base = JSC::JSNonFinalObject;
static constexpr unsigned StructureFlags = Base::StructureFlags;
static JSFooPrototype* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure) {
JSFooPrototype* prototype = new (NotNull, allocateCell<JSFooPrototype>(vm)) JSFooPrototype(vm, structure);
prototype->finishCreation(vm);
return prototype;
}
template<typename, JSC::SubspaceAccess>
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) { return &vm.plainObjectSpace(); }
DECLARE_INFO;
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) {
auto* structure = JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
structure->setMayBePrototype(true);
return structure;
}
private:
JSFooPrototype(JSC::VM& vm, JSC::Structure* structure) : Base(vm, structure) {}
void finishCreation(JSC::VM& vm);
};
void JSFooPrototype::finishCreation(VM& vm) {
Base::finishCreation(vm);
reifyStaticProperties(vm, JSFoo::info(), JSFooPrototypeTableValues, *this);
JSC_TO_STRING_TAG_WITHOUT_TRANSITION();
}
```
## Getter/Setter/Function Definitions
```cpp
// Getter
JSC_DEFINE_CUSTOM_GETTER(jsFooGetter_prop, (JSGlobalObject* globalObject, EncodedJSValue thisValue, PropertyName)) {
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
JSFoo* thisObject = jsDynamicCast<JSFoo*>(JSValue::decode(thisValue));
if (UNLIKELY(!thisObject)) {
Bun::throwThisTypeError(*globalObject, scope, "JSFoo"_s, "prop"_s);
return {};
}
return JSValue::encode(jsBoolean(thisObject->value()));
}
// Function
JSC_DEFINE_HOST_FUNCTION(jsFooProtoFuncMethod, (JSGlobalObject* globalObject, CallFrame* callFrame)) {
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
auto* thisObject = jsDynamicCast<JSFoo*>(callFrame->thisValue());
if (UNLIKELY(!thisObject)) {
Bun::throwThisTypeError(*globalObject, scope, "Foo"_s, "method"_s);
return {};
}
return JSValue::encode(thisObject->doSomething(vm, globalObject));
}
```
## Constructor Class
```cpp
class JSFooConstructor final : public JSC::InternalFunction {
public:
using Base = JSC::InternalFunction;
static constexpr unsigned StructureFlags = Base::StructureFlags;
static JSFooConstructor* create(JSC::VM& vm, JSC::Structure* structure, JSC::JSObject* prototype) {
JSFooConstructor* constructor = new (NotNull, JSC::allocateCell<JSFooConstructor>(vm)) JSFooConstructor(vm, structure);
constructor->finishCreation(vm, prototype);
return constructor;
}
DECLARE_INFO;
template<typename CellType, JSC::SubspaceAccess>
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm) { return &vm.internalFunctionSpace(); }
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype) {
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::InternalFunctionType, StructureFlags), info());
}
private:
JSFooConstructor(JSC::VM& vm, JSC::Structure* structure) : Base(vm, structure, callFoo, constructFoo) {}
void finishCreation(JSC::VM& vm, JSC::JSObject* prototype) {
Base::finishCreation(vm, 0, "Foo"_s);
putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly);
}
};
```
## Structure Caching
Add to `ZigGlobalObject.h`:
```cpp
JSC::LazyClassStructure m_JSFooClassStructure;
```
Initialize in `ZigGlobalObject.cpp`:
```cpp
m_JSFooClassStructure.initLater([](LazyClassStructure::Initializer& init) {
Bun::initJSFooClassStructure(init);
});
```
Visit in `visitChildrenImpl`:
```cpp
m_JSFooClassStructure.visit(visitor);
```
## Expose to Zig
```cpp
extern "C" JSC::EncodedJSValue Bun__JSFooConstructor(Zig::GlobalObject* globalObject) {
return JSValue::encode(globalObject->m_JSFooClassStructure.constructor(globalObject));
}
extern "C" EncodedJSValue Bun__Foo__toJS(Zig::GlobalObject* globalObject, Foo* foo) {
auto* structure = globalObject->m_JSFooClassStructure.get(globalObject);
return JSValue::encode(JSFoo::create(globalObject->vm(), structure, globalObject, WTFMove(foo)));
}
```
Include `#include "root.h"` at the top of C++ files.

View File

@@ -1,206 +0,0 @@
---
name: implementing-jsc-classes-zig
description: Creates JavaScript classes using Bun's Zig bindings generator (.classes.ts). Use when implementing new JS APIs in Zig with JSC integration.
---
# Bun's JavaScriptCore Class Bindings Generator
Bridge JavaScript and Zig through `.classes.ts` definitions and Zig implementations.
## Architecture
1. **Zig Implementation** (.zig files)
2. **JavaScript Interface Definition** (.classes.ts files)
3. **Generated Code** (C++/Zig files connecting them)
## Class Definition (.classes.ts)
```typescript
define({
name: "TextDecoder",
constructor: true,
JSType: "object",
finalize: true,
proto: {
decode: { args: 1 },
encoding: { getter: true, cache: true },
fatal: { getter: true },
},
});
```
Options:
- `name`: Class name
- `constructor`: Has public constructor
- `JSType`: "object", "function", etc.
- `finalize`: Needs cleanup
- `proto`: Properties/methods
- `cache`: Cache property values via WriteBarrier
## Zig Implementation
```zig
pub const TextDecoder = struct {
pub const js = JSC.Codegen.JSTextDecoder;
pub const toJS = js.toJS;
pub const fromJS = js.fromJS;
pub const fromJSDirect = js.fromJSDirect;
encoding: []const u8,
fatal: bool,
pub fn constructor(
globalObject: *JSGlobalObject,
callFrame: *JSC.CallFrame,
) bun.JSError!*TextDecoder {
return bun.new(TextDecoder, .{ .encoding = "utf-8", .fatal = false });
}
pub fn decode(
this: *TextDecoder,
globalObject: *JSGlobalObject,
callFrame: *JSC.CallFrame,
) bun.JSError!JSC.JSValue {
const args = callFrame.arguments();
if (args.len < 1 or args.ptr[0].isUndefinedOrNull()) {
return globalObject.throw("Input cannot be null", .{});
}
return JSC.JSValue.jsString(globalObject, "result");
}
pub fn getEncoding(this: *TextDecoder, globalObject: *JSGlobalObject) JSC.JSValue {
return JSC.JSValue.createStringFromUTF8(globalObject, this.encoding);
}
fn deinit(this: *TextDecoder) void {
// Release resources
}
pub fn finalize(this: *TextDecoder) void {
this.deinit();
bun.destroy(this);
}
};
```
**Key patterns:**
- Use `bun.JSError!JSValue` return type for error handling
- Use `globalObject` not `ctx`
- `deinit()` for cleanup, `finalize()` called by GC
- Update `src/bun.js/bindings/generated_classes_list.zig`
## CallFrame Access
```zig
const args = callFrame.arguments();
const first_arg = args.ptr[0]; // Access as slice
const argCount = args.len;
const thisValue = callFrame.thisValue();
```
## Property Caching
For `cache: true` properties, generated accessors:
```zig
// Get cached value
pub fn encodingGetCached(thisValue: JSC.JSValue) ?JSC.JSValue {
const result = TextDecoderPrototype__encodingGetCachedValue(thisValue);
if (result == .zero) return null;
return result;
}
// Set cached value
pub fn encodingSetCached(thisValue: JSC.JSValue, globalObject: *JSC.JSGlobalObject, value: JSC.JSValue) void {
TextDecoderPrototype__encodingSetCachedValue(thisValue, globalObject, value);
}
```
## Error Handling
```zig
pub fn method(this: *MyClass, globalObject: *JSGlobalObject, callFrame: *JSC.CallFrame) bun.JSError!JSC.JSValue {
const args = callFrame.arguments();
if (args.len < 1) {
return globalObject.throw("Missing required argument", .{});
}
return JSC.JSValue.jsString(globalObject, "Success!");
}
```
## Memory Management
```zig
pub fn deinit(this: *TextDecoder) void {
this._encoding.deref();
if (this.buffer) |buffer| {
bun.default_allocator.free(buffer);
}
}
pub fn finalize(this: *TextDecoder) void {
JSC.markBinding(@src());
this.deinit();
bun.default_allocator.destroy(this);
}
```
## Creating a New Binding
1. Define interface in `.classes.ts`:
```typescript
define({
name: "MyClass",
constructor: true,
finalize: true,
proto: {
myMethod: { args: 1 },
myProperty: { getter: true, cache: true },
},
});
```
2. Implement in `.zig`:
```zig
pub const MyClass = struct {
pub const js = JSC.Codegen.JSMyClass;
pub const toJS = js.toJS;
pub const fromJS = js.fromJS;
value: []const u8,
pub const new = bun.TrivialNew(@This());
pub fn constructor(globalObject: *JSGlobalObject, callFrame: *JSC.CallFrame) bun.JSError!*MyClass {
return MyClass.new(.{ .value = "" });
}
pub fn myMethod(this: *MyClass, globalObject: *JSGlobalObject, callFrame: *JSC.CallFrame) bun.JSError!JSC.JSValue {
return JSC.JSValue.jsUndefined();
}
pub fn getMyProperty(this: *MyClass, globalObject: *JSGlobalObject) JSC.JSValue {
return JSC.JSValue.jsString(globalObject, this.value);
}
pub fn deinit(this: *MyClass) void {}
pub fn finalize(this: *MyClass) void {
this.deinit();
bun.destroy(this);
}
};
```
3. Add to `src/bun.js/bindings/generated_classes_list.zig`
## Generated Components
- **C++ Classes**: `JSMyClass`, `JSMyClassPrototype`, `JSMyClassConstructor`
- **Method Bindings**: `MyClassPrototype__myMethodCallback`
- **Property Accessors**: `MyClassPrototype__myPropertyGetterWrap`
- **Zig Bindings**: External function declarations, cached value accessors

View File

@@ -1,222 +0,0 @@
---
name: writing-bundler-tests
description: Guides writing bundler tests using itBundled/expectBundled in test/bundler/. Use when creating or modifying bundler, transpiler, or code transformation tests.
---
# Writing Bundler Tests
Bundler tests use `itBundled()` from `test/bundler/expectBundled.ts` to test Bun's bundler.
## Basic Usage
```typescript
import { describe } from "bun:test";
import { itBundled, dedent } from "./expectBundled";
describe("bundler", () => {
itBundled("category/TestName", {
files: {
"index.js": `console.log("hello");`,
},
run: {
stdout: "hello",
},
});
});
```
Test ID format: `category/TestName` (e.g., `banner/CommentBanner`, `minify/Empty`)
## File Setup
```typescript
{
files: {
"index.js": `console.log("test");`,
"lib.ts": `export const foo = 123;`,
"nested/file.js": `export default {};`,
},
entryPoints: ["index.js"], // defaults to first file
runtimeFiles: { // written AFTER bundling
"extra.js": `console.log("added later");`,
},
}
```
## Bundler Options
```typescript
{
outfile: "/out.js",
outdir: "/out",
format: "esm" | "cjs" | "iife",
target: "bun" | "browser" | "node",
// Minification
minifyWhitespace: true,
minifyIdentifiers: true,
minifySyntax: true,
// Code manipulation
banner: "// copyright",
footer: "// end",
define: { "PROD": "true" },
external: ["lodash"],
// Advanced
sourceMap: "inline" | "external",
splitting: true,
treeShaking: true,
drop: ["console"],
}
```
## Runtime Verification
```typescript
{
run: {
stdout: "expected output", // exact match
stdout: /regex/, // pattern match
partialStdout: "contains this", // substring
stderr: "error output",
exitCode: 1,
env: { NODE_ENV: "production" },
runtime: "bun" | "node",
// Runtime errors
error: "ReferenceError: x is not defined",
},
}
```
## Bundle Errors/Warnings
```typescript
{
bundleErrors: {
"/file.js": ["error message 1", "error message 2"],
},
bundleWarnings: {
"/file.js": ["warning message"],
},
}
```
## Dead Code Elimination (DCE)
Add markers in source code:
```javascript
// KEEP - this should survive
const used = 1;
// REMOVE - this should be eliminated
const unused = 2;
```
```typescript
{
dce: true,
dceKeepMarkerCount: 5, // expected KEEP markers
}
```
## Capture Pattern
Verify exact transpilation with `capture()`:
```typescript
itBundled("string/Folding", {
files: {
"index.ts": `capture(\`\${1 + 1}\`);`,
},
capture: ['"2"'], // expected captured value
minifySyntax: true,
});
```
## Post-Bundle Assertions
```typescript
{
onAfterBundle(api) {
api.expectFile("out.js").toContain("console.log");
api.assertFileExists("out.js");
const content = api.readFile("out.js");
expect(content).toMatchSnapshot();
const values = api.captureFile("out.js");
expect(values).toEqual(["2"]);
},
}
```
## Common Patterns
**Simple output verification:**
```typescript
itBundled("banner/Comment", {
banner: "// copyright",
files: { "a.js": `console.log("Hello")` },
onAfterBundle(api) {
api.expectFile("out.js").toContain("// copyright");
},
});
```
**Multi-file CJS/ESM interop:**
```typescript
itBundled("cjs/ImportSyntax", {
files: {
"entry.js": `import lib from './lib.cjs'; console.log(lib);`,
"lib.cjs": `exports.foo = 'bar';`,
},
run: { stdout: '{"foo":"bar"}' },
});
```
**Error handling:**
```typescript
itBundled("edgecase/InvalidLoader", {
files: { "index.js": `...` },
bundleErrors: {
"index.js": ["Unsupported loader type"],
},
});
```
## Test Organization
```text
test/bundler/
├── bundler_banner.test.ts
├── bundler_string.test.ts
├── bundler_minify.test.ts
├── bundler_cjs.test.ts
├── bundler_edgecase.test.ts
├── bundler_splitting.test.ts
├── css/
├── transpiler/
└── expectBundled.ts
```
## Running Tests
```bash
bun bd test test/bundler/bundler_banner.test.ts
BUN_BUNDLER_TEST_FILTER="banner/Comment" bun bd test bundler_banner.test.ts
BUN_BUNDLER_TEST_DEBUG=1 bun bd test bundler_minify.test.ts
```
## Key Points
- Use `dedent` for readable multi-line code
- File paths are relative (e.g., `/index.js`)
- Use `capture()` to verify exact transpilation results
- Use `.toMatchSnapshot()` for complex outputs
- Pass array to `run` for multiple test scenarios

View File

@@ -1,94 +0,0 @@
---
name: writing-dev-server-tests
description: Guides writing HMR/Dev Server tests in test/bake/. Use when creating or modifying dev server, hot reloading, or bundling tests.
---
# Writing HMR/Dev Server Tests
Dev server tests validate hot-reloading robustness and reliability.
## File Structure
- `test/bake/bake-harness.ts` - shared utilities: `devTest`, `prodTest`, `devAndProductionTest`, `Dev` class, `Client` class
- `test/bake/client-fixture.mjs` - subprocess for `Client` (page loading, IPC queries)
- `test/bake/dev/*.test.ts` - dev server and hot reload tests
- `test/bake/dev-and-prod.ts` - tests running on both dev and production mode
## Test Categories
- `bundle.test.ts` - DevServer-specific bundling bugs
- `css.test.ts` - CSS bundling issues
- `plugins.test.ts` - development mode plugins
- `ecosystem.test.ts` - library compatibility (prefer concrete bugs over full package tests)
- `esm.test.ts` - ESM features in development
- `html.test.ts` - HTML file handling
- `react-spa.test.ts` - React, react-refresh transform, server components
- `sourcemap.test.ts` - source map correctness
## devTest Basics
```ts
import { devTest, emptyHtmlFile } from "../bake-harness";
devTest("html file is watched", {
files: {
"index.html": emptyHtmlFile({
scripts: ["/script.ts"],
body: "<h1>Hello</h1>",
}),
"script.ts": `console.log("hello");`,
},
async test(dev) {
await dev.fetch("/").expect.toInclude("<h1>Hello</h1>");
await dev.patch("index.html", { find: "Hello", replace: "World" });
await dev.fetch("/").expect.toInclude("<h1>World</h1>");
await using c = await dev.client("/");
await c.expectMessage("hello");
await c.expectReload(async () => {
await dev.patch("index.html", { find: "World", replace: "Bar" });
});
await c.expectMessage("hello");
},
});
```
## Key APIs
- **`files`**: Initial filesystem state
- **`dev.fetch()`**: HTTP requests
- **`dev.client()`**: Opens browser instance
- **`dev.write/patch/delete`**: Filesystem mutations (wait for hot-reload automatically)
- **`c.expectMessage()`**: Assert console.log output
- **`c.expectReload()`**: Wrap code that causes hard reload
**Important**: Use `dev.write/patch/delete` instead of `node:fs` - they wait for hot-reload.
## Testing Errors
```ts
devTest("import then create", {
files: {
"index.html": `<!DOCTYPE html><html><head></head><body><script type="module" src="/script.ts"></script></body></html>`,
"script.ts": `import data from "./data"; console.log(data);`,
},
async test(dev) {
const c = await dev.client("/", {
errors: ['script.ts:1:18: error: Could not resolve: "./data"'],
});
await c.expectReload(async () => {
await dev.write("data.ts", "export default 'data';");
});
await c.expectMessage("data");
},
});
```
Specify expected errors with the `errors` option:
```ts
await dev.delete("other.ts", {
errors: ['index.ts:1:16: error: Could not resolve: "./other"'],
});
```

View File

@@ -1,268 +0,0 @@
---
name: zig-system-calls
description: Guides using bun.sys for system calls and file I/O in Zig. Use when implementing file operations instead of std.fs or std.posix.
---
# System Calls & File I/O in Zig
Use `bun.sys` instead of `std.fs` or `std.posix` for cross-platform syscalls with proper error handling.
## bun.sys.File (Preferred)
For most file operations, use the `bun.sys.File` wrapper:
```zig
const File = bun.sys.File;
const file = switch (File.open(path, bun.O.RDWR, 0o644)) {
.result => |f| f,
.err => |err| return .{ .err = err },
};
defer file.close();
// Read/write
_ = try file.read(buffer).unwrap();
_ = try file.writeAll(data).unwrap();
// Get file info
const stat = try file.stat().unwrap();
const size = try file.getEndPos().unwrap();
// std.io compatible
const reader = file.reader();
const writer = file.writer();
```
### Complete Example
```zig
const File = bun.sys.File;
pub fn writeFile(path: [:0]const u8, data: []const u8) File.WriteError!void {
const file = switch (File.open(path, bun.O.WRONLY | bun.O.CREAT | bun.O.TRUNC, 0o664)) {
.result => |f| f,
.err => |err| return err.toError(),
};
defer file.close();
_ = switch (file.writeAll(data)) {
.result => {},
.err => |err| return err.toError(),
};
}
```
## Why bun.sys?
| Aspect | bun.sys | std.fs/std.posix |
| ----------- | -------------------------------- | ------------------- |
| Return Type | `Maybe(T)` with detailed Error | Generic error union |
| Windows | Full support with libuv fallback | Limited/POSIX-only |
| Error Info | errno, syscall tag, path, fd | errno only |
| EINTR | Automatic retry | Manual handling |
## Error Handling with Maybe(T)
`bun.sys` functions return `Maybe(T)` - a tagged union:
```zig
const sys = bun.sys;
// Pattern 1: Switch on result/error
switch (sys.read(fd, buffer)) {
.result => |bytes_read| {
// use bytes_read
},
.err => |err| {
// err.errno, err.syscall, err.fd, err.path
if (err.getErrno() == .AGAIN) {
// handle EAGAIN
}
},
}
// Pattern 2: Unwrap with try (converts to Zig error)
const bytes = try sys.read(fd, buffer).unwrap();
// Pattern 3: Unwrap with default
const value = sys.stat(path).unwrapOr(default_stat);
```
## Low-Level File Operations
Only use these when `bun.sys.File` doesn't meet your needs.
### Opening Files
```zig
const sys = bun.sys;
// Use bun.O flags (cross-platform normalized)
const fd = switch (sys.open(path, bun.O.RDONLY, 0)) {
.result => |fd| fd,
.err => |err| return .{ .err = err },
};
defer fd.close();
// Common flags
bun.O.RDONLY, bun.O.WRONLY, bun.O.RDWR
bun.O.CREAT, bun.O.TRUNC, bun.O.APPEND
bun.O.NONBLOCK, bun.O.DIRECTORY
```
### Reading & Writing
```zig
// Single read (may return less than buffer size)
switch (sys.read(fd, buffer)) {
.result => |n| { /* n bytes read */ },
.err => |err| { /* handle error */ },
}
// Read until EOF or buffer full
const total = try sys.readAll(fd, buffer).unwrap();
// Position-based read/write
sys.pread(fd, buffer, offset)
sys.pwrite(fd, data, offset)
// Vector I/O
sys.readv(fd, iovecs)
sys.writev(fd, iovecs)
```
### File Info
```zig
sys.stat(path) // Follow symlinks
sys.lstat(path) // Don't follow symlinks
sys.fstat(fd) // From file descriptor
sys.fstatat(fd, path)
// Linux-only: faster selective stat
sys.statx(path, &.{ .size, .mtime })
```
### Path Operations
```zig
sys.unlink(path)
sys.unlinkat(dir_fd, path)
sys.rename(from, to)
sys.renameat(from_dir, from, to_dir, to)
sys.readlink(path, buf)
sys.readlinkat(fd, path, buf)
sys.link(T, src, dest)
sys.linkat(src_fd, src, dest_fd, dest)
sys.symlink(target, dest)
sys.symlinkat(target, dirfd, dest)
sys.mkdir(path, mode)
sys.mkdirat(dir_fd, path, mode)
sys.rmdir(path)
```
### Permissions
```zig
sys.chmod(path, mode)
sys.fchmod(fd, mode)
sys.fchmodat(fd, path, mode, flags)
sys.chown(path, uid, gid)
sys.fchown(fd, uid, gid)
```
### Closing File Descriptors
Close is on `bun.FD`:
```zig
fd.close(); // Asserts on error (use in defer)
// Or if you need error info:
if (fd.closeAllowingBadFileDescriptor(null)) |err| {
// handle error
}
```
## Directory Operations
```zig
var buf: bun.PathBuffer = undefined;
const cwd = try sys.getcwd(&buf).unwrap();
const cwdZ = try sys.getcwdZ(&buf).unwrap(); // Zero-terminated
sys.chdir(path, destination)
```
### Directory Iteration
Use `bun.DirIterator` instead of `std.fs.Dir.Iterator`:
```zig
var iter = bun.iterateDir(dir_fd);
while (true) {
switch (iter.next()) {
.result => |entry| {
if (entry) |e| {
const name = e.name.slice();
const kind = e.kind; // .file, .directory, .sym_link, etc.
} else {
break; // End of directory
}
},
.err => |err| return .{ .err = err },
}
}
```
## Socket Operations
**Important**: `bun.sys` has limited socket support. For network I/O:
- **Non-blocking sockets**: Use `uws.Socket` (libuwebsockets) exclusively
- **Pipes/blocking I/O**: Use `PipeReader.zig` and `PipeWriter.zig`
Available in bun.sys:
```zig
sys.setsockopt(fd, level, optname, value)
sys.socketpair(domain, socktype, protocol, nonblocking_status)
```
Do NOT use `bun.sys` for socket read/write - use `uws.Socket` instead.
## Other Operations
```zig
sys.ftruncate(fd, size)
sys.lseek(fd, offset, whence)
sys.dup(fd)
sys.dupWithFlags(fd, flags)
sys.fcntl(fd, cmd, arg)
sys.pipe()
sys.mmap(...)
sys.munmap(memory)
sys.access(path, mode)
sys.futimens(fd, atime, mtime)
sys.utimens(path, atime, mtime)
```
## Error Type
```zig
const err: bun.sys.Error = ...;
err.errno // Raw errno value
err.getErrno() // As std.posix.E enum
err.syscall // Which syscall failed (Tag enum)
err.fd // Optional: file descriptor
err.path // Optional: path string
```
## Key Points
- Prefer `bun.sys.File` wrapper for most file operations
- Use low-level `bun.sys` functions only when needed
- Use `bun.O.*` flags instead of `std.os.O.*`
- Handle `Maybe(T)` with switch or `.unwrap()`
- Use `defer fd.close()` for cleanup
- EINTR is handled automatically in most functions
- For sockets, use `uws.Socket` not `bun.sys`

View File

@@ -1,9 +1,5 @@
language: en-US
issue_enrichment:
auto_enrich:
enabled: false
reviews:
profile: assertive
request_changes_workflow: false

View File

@@ -0,0 +1,41 @@
---
description:
globs: src/**/*.cpp,src/**/*.zig
alwaysApply: false
---
### Build Commands
- **Build debug version**: `bun bd` or `bun run build:debug`
- Creates a debug build at `./build/debug/bun-debug`
- Compilation takes ~2.5 minutes
- **Run tests with your debug build**: `bun bd test <test-file>`
- **CRITICAL**: Never use `bun test` directly - it won't include your changes
- **Run any command with debug build**: `bun bd <command>`
### Run a file
To run a file, use:
```sh
bun bd <file> <...args>
```
**CRITICAL**: Never use `bun <file>` directly. It will not have your changes.
### Logging
`BUN_DEBUG_$(SCOPE)=1` enables debug logs for a specific debug log scope.
Debug logs look like this:
```zig
const log = bun.Output.scoped(.${SCOPE}, .hidden);
// ...later
log("MY DEBUG LOG", .{})
```
### Code Generation
Code generation happens automatically as part of the build process. There are no commands to run.

View File

@@ -0,0 +1,139 @@
---
description: Writing HMR/Dev Server tests
globs: test/bake/*
---
# Writing HMR/Dev Server tests
Dev server tests validate that hot-reloading is robust, correct, and reliable. Remember to write thorough, yet concise tests.
## File Structure
- `test/bake/bake-harness.ts` - shared utilities and test harness
- primary test functions `devTest` / `prodTest` / `devAndProductionTest`
- class `Dev` (controls subprocess for dev server)
- class `Client` (controls a happy-dom subprocess for having the page open)
- more helpers
- `test/bake/client-fixture.mjs` - subprocess for what `Client` controls. it loads a page and uses IPC to query parts of the page, run javascript, and much more.
- `test/bake/dev/*.test.ts` - these call `devTest` to test dev server and hot reloading
- `test/bake/dev-and-prod.ts` - these use `devAndProductionTest` to run the same test on dev and production mode. these tests cannot really test hot reloading for obvious reasons.
## Categories
bundle.test.ts - Bundle tests are tests concerning bundling bugs that only occur in DevServer.
css.test.ts - CSS tests concern bundling bugs with CSS files
plugins.test.ts - Plugin tests concern plugins in development mode.
ecosystem.test.ts - These tests involve ensuring certain libraries are correct. It is preferred to test more concrete bugs than testing entire packages.
esm.test.ts - ESM tests are about various esm features in development mode.
html.test.ts - HTML tests are tests relating to HTML files themselves.
react-spa.test.ts - Tests relating to React, our react-refresh transform, and basic server component transforms.
sourcemap.test.ts - Tests verifying source-maps are correct.
## `devTest` Basics
A test takes in two primary inputs: `files` and `async test(dev) {`
```ts
import { devTest, emptyHtmlFile } from "../bake-harness";
devTest("html file is watched", {
files: {
"index.html": emptyHtmlFile({
scripts: ["/script.ts"],
body: "<h1>Hello</h1>",
}),
"script.ts": `
console.log("hello");
`,
},
async test(dev) {
await dev.fetch("/").expect.toInclude("<h1>Hello</h1>");
await dev.fetch("/").expect.toInclude("<h1>Hello</h1>");
await dev.patch("index.html", {
find: "Hello",
replace: "World",
});
await dev.fetch("/").expect.toInclude("<h1>World</h1>");
// Works
await using c = await dev.client("/");
await c.expectMessage("hello");
// Editing HTML reloads
await c.expectReload(async () => {
await dev.patch("index.html", {
find: "World",
replace: "Hello",
});
await dev.fetch("/").expect.toInclude("<h1>Hello</h1>");
});
await c.expectMessage("hello");
await c.expectReload(async () => {
await dev.patch("index.html", {
find: "Hello",
replace: "Bar",
});
await dev.fetch("/").expect.toInclude("<h1>Bar</h1>");
});
await c.expectMessage("hello");
await c.expectReload(async () => {
await dev.patch("script.ts", {
find: "hello",
replace: "world",
});
});
await c.expectMessage("world");
},
});
```
`files` holds the initial state, and the callback runs with the server running. `dev.fetch()` runs HTTP requests, while `dev.client()` opens a browser instance to the code.
Functions `dev.write` and `dev.patch` and `dev.delete` mutate the filesystem. Do not use `node:fs` APIs, as the dev server ones are hooked to wait for hot-reload, and all connected clients to receive changes.
When a change performs a hard-reload, that must be explicitly annotated with `expectReload`. This tells `client-fixture.mjs` that the test is meant to reload the page once; All other hard reloads automatically fail the test.
Client's have `console.log` instrumented, so that any unasserted logs fail the test. This makes it more obvious when an extra reload or re-evaluation. Messages are awaited via `c.expectMessage("log")` or with multiple arguments if there are multiple logs.
## Testing for bundling errors
By default, a client opening a page to an error will fail the test. This makes testing errors explicit.
```ts
devTest("import then create", {
files: {
"index.html": `
<!DOCTYPE html>
<html>
<head></head>
<body>
<script type="module" src="/script.ts"></script>
</body>
</html>
`,
"script.ts": `
import data from "./data";
console.log(data);
`,
},
async test(dev) {
const c = await dev.client("/", {
errors: ['script.ts:1:18: error: Could not resolve: "./data"'],
});
await c.expectReload(async () => {
await dev.write("data.ts", "export default 'data';");
});
await c.expectMessage("data");
},
});
```
Many functions take an options value to allow specifying it will produce errors. For example, this delete is going to cause a resolution failure.
```ts
await dev.delete("other.ts", {
errors: ['index.ts:1:16: error: Could not resolve: "./other"'],
});
```

View File

@@ -0,0 +1,413 @@
---
description: JavaScript class implemented in C++
globs: *.cpp
alwaysApply: false
---
# Implementing JavaScript classes in C++
If there is a publicly accessible Constructor and Prototype, then there are 3 classes:
- IF there are C++ class members we need a destructor, so `class Foo : public JSC::DestructibleObject`, if no C++ class fields (only JS properties) then we don't need a class at all usually. We can instead use JSC::constructEmptyObject(vm, structure) and `putDirectOffset` like in [NodeFSStatBinding.cpp](mdc:src/bun.js/bindings/NodeFSStatBinding.cpp).
- class FooPrototype : public JSC::JSNonFinalObject
- class FooConstructor : public JSC::InternalFunction
If there is no publicly accessible Constructor, just the Prototype and the class is necessary. In some cases, we can avoid the prototype entirely (but that's rare).
If there are C++ fields on the Foo class, the Foo class will need an iso subspace added to [DOMClientIsoSubspaces.h](mdc:src/bun.js/bindings/webcore/DOMClientIsoSubspaces.h) and [DOMIsoSubspaces.h](mdc:src/bun.js/bindings/webcore/DOMIsoSubspaces.h). Prototype and Constructor do not need subspaces.
Usually you'll need to #include "root.h" at the top of C++ files or you'll get lint errors.
Generally, defining the subspace looks like this:
```c++
class Foo : public JSC::DestructibleObject {
// ...
template<typename MyClassT, JSC::SubspaceAccess mode>
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
{
if constexpr (mode == JSC::SubspaceAccess::Concurrently)
return nullptr;
return WebCore::subspaceForImpl<MyClassT, WebCore::UseCustomHeapCellType::No>(
vm,
[](auto& spaces) { return spaces.m_clientSubspaceFor${MyClassT}.get(); },
[](auto& spaces, auto&& space) { spaces.m_clientSubspaceFor${MyClassT} = std::forward<decltype(space)>(space); },
[](auto& spaces) { return spaces.m_subspaceFo${MyClassT}.get(); },
[](auto& spaces, auto&& space) { spaces.m_subspaceFor${MyClassT} = std::forward<decltype(space)>(space); });
}
```
It's better to put it in the .cpp file instead of the .h file, when possible.
## Defining properties
Define properties on the prototype. Use a const HashTableValues like this:
```C++
static JSC_DECLARE_HOST_FUNCTION(jsX509CertificateProtoFuncCheckEmail);
static JSC_DECLARE_HOST_FUNCTION(jsX509CertificateProtoFuncCheckHost);
static JSC_DECLARE_HOST_FUNCTION(jsX509CertificateProtoFuncCheckIP);
static JSC_DECLARE_HOST_FUNCTION(jsX509CertificateProtoFuncCheckIssued);
static JSC_DECLARE_HOST_FUNCTION(jsX509CertificateProtoFuncCheckPrivateKey);
static JSC_DECLARE_HOST_FUNCTION(jsX509CertificateProtoFuncToJSON);
static JSC_DECLARE_HOST_FUNCTION(jsX509CertificateProtoFuncToLegacyObject);
static JSC_DECLARE_HOST_FUNCTION(jsX509CertificateProtoFuncToString);
static JSC_DECLARE_HOST_FUNCTION(jsX509CertificateProtoFuncVerify);
static JSC_DECLARE_CUSTOM_GETTER(jsX509CertificateGetter_ca);
static JSC_DECLARE_CUSTOM_GETTER(jsX509CertificateGetter_fingerprint);
static JSC_DECLARE_CUSTOM_GETTER(jsX509CertificateGetter_fingerprint256);
static JSC_DECLARE_CUSTOM_GETTER(jsX509CertificateGetter_fingerprint512);
static JSC_DECLARE_CUSTOM_GETTER(jsX509CertificateGetter_subject);
static JSC_DECLARE_CUSTOM_GETTER(jsX509CertificateGetter_subjectAltName);
static JSC_DECLARE_CUSTOM_GETTER(jsX509CertificateGetter_infoAccess);
static JSC_DECLARE_CUSTOM_GETTER(jsX509CertificateGetter_keyUsage);
static JSC_DECLARE_CUSTOM_GETTER(jsX509CertificateGetter_issuer);
static JSC_DECLARE_CUSTOM_GETTER(jsX509CertificateGetter_issuerCertificate);
static JSC_DECLARE_CUSTOM_GETTER(jsX509CertificateGetter_publicKey);
static JSC_DECLARE_CUSTOM_GETTER(jsX509CertificateGetter_raw);
static JSC_DECLARE_CUSTOM_GETTER(jsX509CertificateGetter_serialNumber);
static JSC_DECLARE_CUSTOM_GETTER(jsX509CertificateGetter_validFrom);
static JSC_DECLARE_CUSTOM_GETTER(jsX509CertificateGetter_validTo);
static JSC_DECLARE_CUSTOM_GETTER(jsX509CertificateGetter_validFromDate);
static JSC_DECLARE_CUSTOM_GETTER(jsX509CertificateGetter_validToDate);
static const HashTableValue JSX509CertificatePrototypeTableValues[] = {
{ "ca"_s, static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsX509CertificateGetter_ca, 0 } },
{ "checkEmail"_s, static_cast<unsigned>(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsX509CertificateProtoFuncCheckEmail, 2 } },
{ "checkHost"_s, static_cast<unsigned>(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsX509CertificateProtoFuncCheckHost, 2 } },
{ "checkIP"_s, static_cast<unsigned>(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsX509CertificateProtoFuncCheckIP, 1 } },
{ "checkIssued"_s, static_cast<unsigned>(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsX509CertificateProtoFuncCheckIssued, 1 } },
{ "checkPrivateKey"_s, static_cast<unsigned>(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsX509CertificateProtoFuncCheckPrivateKey, 1 } },
{ "fingerprint"_s, static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsX509CertificateGetter_fingerprint, 0 } },
{ "fingerprint256"_s, static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsX509CertificateGetter_fingerprint256, 0 } },
{ "fingerprint512"_s, static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsX509CertificateGetter_fingerprint512, 0 } },
{ "infoAccess"_s, static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsX509CertificateGetter_infoAccess, 0 } },
{ "issuer"_s, static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsX509CertificateGetter_issuer, 0 } },
{ "issuerCertificate"_s, static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsX509CertificateGetter_issuerCertificate, 0 } },
{ "keyUsage"_s, static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsX509CertificateGetter_keyUsage, 0 } },
{ "publicKey"_s, static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsX509CertificateGetter_publicKey, 0 } },
{ "raw"_s, static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsX509CertificateGetter_raw, 0 } },
{ "serialNumber"_s, static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsX509CertificateGetter_serialNumber, 0 } },
{ "subject"_s, static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsX509CertificateGetter_subject, 0 } },
{ "subjectAltName"_s, static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsX509CertificateGetter_subjectAltName, 0 } },
{ "toJSON"_s, static_cast<unsigned>(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsX509CertificateProtoFuncToJSON, 0 } },
{ "toLegacyObject"_s, static_cast<unsigned>(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsX509CertificateProtoFuncToLegacyObject, 0 } },
{ "toString"_s, static_cast<unsigned>(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsX509CertificateProtoFuncToString, 0 } },
{ "validFrom"_s, static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsX509CertificateGetter_validFrom, 0 } },
{ "validFromDate"_s, static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessorOrValue), NoIntrinsic, { HashTableValue::GetterSetterType, jsX509CertificateGetter_validFromDate, 0 } },
{ "validTo"_s, static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessor), NoIntrinsic, { HashTableValue::GetterSetterType, jsX509CertificateGetter_validTo, 0 } },
{ "validToDate"_s, static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::CustomAccessorOrValue), NoIntrinsic, { HashTableValue::GetterSetterType, jsX509CertificateGetter_validToDate, 0 } },
{ "verify"_s, static_cast<unsigned>(PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsX509CertificateProtoFuncVerify, 1 } },
};
```
### Creating a prototype class
Follow a pattern like this:
```c++
class JSX509CertificatePrototype final : public JSC::JSNonFinalObject {
public:
using Base = JSC::JSNonFinalObject;
static constexpr unsigned StructureFlags = Base::StructureFlags;
static JSX509CertificatePrototype* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure)
{
JSX509CertificatePrototype* prototype = new (NotNull, allocateCell<JSX509CertificatePrototype>(vm)) JSX509CertificatePrototype(vm, structure);
prototype->finishCreation(vm);
return prototype;
}
template<typename, JSC::SubspaceAccess>
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
{
return &vm.plainObjectSpace();
}
DECLARE_INFO;
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
{
auto* structure = JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
structure->setMayBePrototype(true);
return structure;
}
private:
JSX509CertificatePrototype(JSC::VM& vm, JSC::Structure* structure)
: Base(vm, structure)
{
}
void finishCreation(JSC::VM& vm);
};
const ClassInfo JSX509CertificatePrototype::s_info = { "X509Certificate"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSX509CertificatePrototype) };
void JSX509CertificatePrototype::finishCreation(VM& vm)
{
Base::finishCreation(vm);
reifyStaticProperties(vm, JSX509Certificate::info(), JSX509CertificatePrototypeTableValues, *this);
JSC_TO_STRING_TAG_WITHOUT_TRANSITION();
}
} // namespace Bun
```
### Getter definition:
```C++
JSC_DEFINE_CUSTOM_GETTER(jsX509CertificateGetter_ca, (JSGlobalObject * globalObject, EncodedJSValue thisValue, PropertyName))
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
JSX509Certificate* thisObject = jsDynamicCast<JSX509Certificate*>(JSValue::decode(thisValue));
if (UNLIKELY(!thisObject)) {
Bun::throwThisTypeError(*globalObject, scope, "JSX509Certificate"_s, "ca"_s);
return {};
}
return JSValue::encode(jsBoolean(thisObject->view().isCA()));
}
```
### Setter definition
```C++
JSC_DEFINE_CUSTOM_SETTER(jsImportMetaObjectSetter_require, (JSGlobalObject * jsGlobalObject, JSC::EncodedJSValue thisValue, JSC::EncodedJSValue encodedValue, PropertyName propertyName))
{
ImportMetaObject* thisObject = jsDynamicCast<ImportMetaObject*>(JSValue::decode(thisValue));
if (UNLIKELY(!thisObject))
return false;
JSValue value = JSValue::decode(encodedValue);
if (!value.isCell()) {
// TODO:
return true;
}
thisObject->requireProperty.set(thisObject->vm(), thisObject, value.asCell());
return true;
}
```
### Function definition
```C++
JSC_DEFINE_HOST_FUNCTION(jsX509CertificateProtoFuncToJSON, (JSGlobalObject * globalObject, CallFrame* callFrame))
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
auto *thisObject = jsDynamicCast<MyClassT*>(callFrame->thisValue());
if (UNLIKELY(!thisObject)) {
Bun::throwThisTypeError(*globalObject, scope, "MyClass"_s, "myFunctionName"_s);
return {};
}
return JSValue::encode(functionThatReturnsJSValue(vm, globalObject, thisObject));
}
```
### Constructor definition
```C++
JSC_DECLARE_HOST_FUNCTION(callStats);
JSC_DECLARE_HOST_FUNCTION(constructStats);
class JSStatsConstructor final : public JSC::InternalFunction {
public:
using Base = JSC::InternalFunction;
static constexpr unsigned StructureFlags = Base::StructureFlags;
static JSStatsConstructor* create(JSC::VM& vm, JSC::Structure* structure, JSC::JSObject* prototype)
{
JSStatsConstructor* constructor = new (NotNull, JSC::allocateCell<JSStatsConstructor>(vm)) JSStatsConstructor(vm, structure);
constructor->finishCreation(vm, prototype);
return constructor;
}
DECLARE_INFO;
template<typename CellType, JSC::SubspaceAccess>
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
{
return &vm.internalFunctionSpace();
}
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
{
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::InternalFunctionType, StructureFlags), info());
}
private:
JSStatsConstructor(JSC::VM& vm, JSC::Structure* structure)
: Base(vm, structure, callStats, constructStats)
{
}
void finishCreation(JSC::VM& vm, JSC::JSObject* prototype)
{
Base::finishCreation(vm, 0, "Stats"_s);
putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly);
}
};
```
### Structure caching
If there's a class, prototype, and constructor:
1. Add the `JSC::LazyClassStructure` to [ZigGlobalObject.h](mdc:src/bun.js/bindings/ZigGlobalObject.h)
2. Initialize the class structure in [ZigGlobalObject.cpp](mdc:src/bun.js/bindings/ZigGlobalObject.cpp) in `void GlobalObject::finishCreation(VM& vm)`
3. Visit the class structure in visitChildren in [ZigGlobalObject.cpp](mdc:src/bun.js/bindings/ZigGlobalObject.cpp) in `void GlobalObject::visitChildrenImpl`
```c++#ZigGlobalObject.cpp
void GlobalObject::finishCreation(VM& vm) {
// ...
m_JSStatsBigIntClassStructure.initLater(
[](LazyClassStructure::Initializer& init) {
// Call the function to initialize our class structure.
Bun::initJSBigIntStatsClassStructure(init);
});
```
Then, implement the function that creates the structure:
```c++
void setupX509CertificateClassStructure(LazyClassStructure::Initializer& init)
{
auto* prototypeStructure = JSX509CertificatePrototype::createStructure(init.vm, init.global, init.global->objectPrototype());
auto* prototype = JSX509CertificatePrototype::create(init.vm, init.global, prototypeStructure);
auto* constructorStructure = JSX509CertificateConstructor::createStructure(init.vm, init.global, init.global->functionPrototype());
auto* constructor = JSX509CertificateConstructor::create(init.vm, init.global, constructorStructure, prototype);
auto* structure = JSX509Certificate::createStructure(init.vm, init.global, prototype);
init.setPrototype(prototype);
init.setStructure(structure);
init.setConstructor(constructor);
}
```
If there's only a class, use `JSC::LazyProperty<JSGlobalObject, Structure>` instead of `JSC::LazyClassStructure`:
1. Add the `JSC::LazyProperty<JSGlobalObject, Structure>` to @ZigGlobalObject.h
2. Initialize the class structure in @ZigGlobalObject.cpp in `void GlobalObject::finishCreation(VM& vm)`
3. Visit the lazy property in visitChildren in @ZigGlobalObject.cpp in `void GlobalObject::visitChildrenImpl`
void GlobalObject::finishCreation(VM& vm) {
// ...
this.m_myLazyProperty.initLater([](const JSC::LazyProperty<JSC::JSGlobalObject, JSC::Structure>::Initializer& init) {
init.set(Bun::initMyStructure(init.vm, reinterpret_cast<Zig::GlobalObject\*>(init.owner)));
});
```
Then, implement the function that creates the structure:
```c++
Structure* setupX509CertificateStructure(JSC::VM &vm, Zig::GlobalObject* globalObject)
{
// If there is a prototype:
auto* prototypeStructure = JSX509CertificatePrototype::createStructure(init.vm, init.global, init.global->objectPrototype());
auto* prototype = JSX509CertificatePrototype::create(init.vm, init.global, prototypeStructure);
// If there is no prototype or it only has
auto* structure = JSX509Certificate::createStructure(init.vm, init.global, prototype);
init.setPrototype(prototype);
init.setStructure(structure);
init.setConstructor(constructor);
}
```
Then, use the structure by calling `globalObject.m_myStructureName.get(globalObject)`
```C++
JSC_DEFINE_HOST_FUNCTION(x509CertificateConstructorConstruct, (JSGlobalObject * globalObject, CallFrame* callFrame))
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
if (!callFrame->argumentCount()) {
Bun::throwError(globalObject, scope, ErrorCode::ERR_MISSING_ARGS, "X509Certificate constructor requires at least one argument"_s);
return {};
}
JSValue arg = callFrame->uncheckedArgument(0);
if (!arg.isCell()) {
Bun::throwError(globalObject, scope, ErrorCode::ERR_INVALID_ARG_TYPE, "X509Certificate constructor argument must be a Buffer, TypedArray, or string"_s);
return {};
}
auto* zigGlobalObject = defaultGlobalObject(globalObject);
Structure* structure = zigGlobalObject->m_JSX509CertificateClassStructure.get(zigGlobalObject);
JSValue newTarget = callFrame->newTarget();
if (UNLIKELY(zigGlobalObject->m_JSX509CertificateClassStructure.constructor(zigGlobalObject) != newTarget)) {
auto scope = DECLARE_THROW_SCOPE(vm);
if (!newTarget) {
throwTypeError(globalObject, scope, "Class constructor X509Certificate cannot be invoked without 'new'"_s);
return {};
}
auto* functionGlobalObject = defaultGlobalObject(getFunctionRealm(globalObject, newTarget.getObject()));
RETURN_IF_EXCEPTION(scope, {});
structure = InternalFunction::createSubclassStructure(globalObject, newTarget.getObject(), functionGlobalObject->NodeVMScriptStructure());
RETURN_IF_EXCEPTION(scope, {});
}
return JSValue::encode(createX509Certificate(vm, globalObject, structure, arg));
}
```
### Expose to Zig
To expose the constructor to zig:
```c++
extern "C" JSC::EncodedJSValue Bun__JSBigIntStatsObjectConstructor(Zig::GlobalObject* globalobject)
{
return JSValue::encode(globalobject->m_JSStatsBigIntClassStructure.constructor(globalobject));
}
```
Zig:
```zig
extern "c" fn Bun__JSBigIntStatsObjectConstructor(*JSC.JSGlobalObject) JSC.JSValue;
pub const getBigIntStatsConstructor = Bun__JSBigIntStatsObjectConstructor;
```
To create an object (instance) of a JS class defined in C++ from Zig, follow the \_\_toJS convention like this:
```c++
// X509* is whatever we need to create the object
extern "C" EncodedJSValue Bun__X509__toJS(Zig::GlobalObject* globalObject, X509* cert)
{
// ... implementation details
auto* structure = globalObject->m_JSX509CertificateClassStructure.get(globalObject);
return JSValue::encode(JSX509Certificate::create(globalObject->vm(), structure, globalObject, WTFMove(cert)));
}
```
And from Zig:
```zig
const X509 = opaque {
// ... class
extern fn Bun__X509__toJS(*JSC.JSGlobalObject, *X509) JSC.JSValue;
pub fn toJS(this: *X509, globalObject: *JSC.JSGlobalObject) JSC.JSValue {
return Bun__X509__toJS(globalObject, this);
}
};
```

View File

@@ -0,0 +1,203 @@
# Registering Functions, Objects, and Modules in Bun
This guide documents the process of adding new functionality to the Bun global object and runtime.
## Overview
Bun's architecture exposes functionality to JavaScript through a set of carefully registered functions, objects, and modules. Most core functionality is implemented in Zig, with JavaScript bindings that make these features accessible to users.
There are several key ways to expose functionality in Bun:
1. **Global Functions**: Direct methods on the `Bun` object (e.g., `Bun.serve()`)
2. **Getter Properties**: Lazily initialized properties on the `Bun` object (e.g., `Bun.sqlite`)
3. **Constructor Classes**: Classes available through the `Bun` object (e.g., `Bun.ValkeyClient`)
4. **Global Modules**: Modules that can be imported directly (e.g., `import {X} from "bun:*"`)
## The Registration Process
Adding new functionality to Bun involves several coordinated steps across multiple files:
### 1. Implement the Core Functionality in Zig
First, implement your feature in Zig, typically in its own directory in `src/`. Examples:
- `src/valkey/` for Redis/Valkey client
- `src/semver/` for SemVer functionality
- `src/smtp/` for SMTP client
### 2. Create JavaScript Bindings
Create bindings that expose your Zig functionality to JavaScript:
- Create a class definition file (e.g., `js_bindings.classes.ts`) to define the JavaScript interface
- Implement `JSYourFeature` struct in a file like `js_your_feature.zig`
Example from a class definition file:
```typescript
// Example from a .classes.ts file
import { define } from "../../codegen/class-definitions";
export default [
define({
name: "YourFeature",
construct: true,
finalize: true,
hasPendingActivity: true,
memoryCost: true,
klass: {},
JSType: "0b11101110",
proto: {
yourMethod: {
fn: "yourZigMethod",
length: 1,
},
property: {
getter: "getProperty",
},
},
values: ["cachedValues"],
}),
];
```
### 3. Register with BunObject in `src/bun.js/bindings/BunObject+exports.h`
Add an entry to the `FOR_EACH_GETTER` macro:
```c
// In BunObject+exports.h
#define FOR_EACH_GETTER(macro) \
macro(CSRF) \
macro(CryptoHasher) \
... \
macro(YourFeature) \
```
### 4. Create a Getter Function in `src/bun.js/api/BunObject.zig`
Implement a getter function in `BunObject.zig` that returns your feature:
```zig
// In BunObject.zig
pub const YourFeature = toJSGetter(Bun.getYourFeatureConstructor);
// In the exportAll() function:
@export(&BunObject.YourFeature, .{ .name = getterName("YourFeature") });
```
### 5. Implement the Getter Function in a Relevant Zig File
Implement the function that creates your object:
```zig
// In your main module file (e.g., src/your_feature/your_feature.zig)
pub fn getYourFeatureConstructor(globalThis: *JSC.JSGlobalObject, _: *JSC.JSObject) JSC.JSValue {
return JSC.API.YourFeature.getConstructor(globalThis);
}
```
### 6. Add to Build System
Ensure your files are included in the build system by adding them to the appropriate targets.
## Example: Adding a New Module
Here's a comprehensive example of adding a hypothetical SMTP module:
1. Create implementation files in `src/smtp/`:
- `index.zig`: Main entry point that exports everything
- `SmtpClient.zig`: Core SMTP client implementation
- `js_smtp.zig`: JavaScript bindings
- `js_bindings.classes.ts`: Class definition
2. Define your JS class in `js_bindings.classes.ts`:
```typescript
import { define } from "../../codegen/class-definitions";
export default [
define({
name: "EmailClient",
construct: true,
finalize: true,
hasPendingActivity: true,
configurable: false,
memoryCost: true,
klass: {},
JSType: "0b11101110",
proto: {
send: {
fn: "send",
length: 1,
},
verify: {
fn: "verify",
length: 0,
},
close: {
fn: "close",
length: 0,
},
},
values: ["connectionPromise"],
}),
];
```
3. Add getter to `BunObject+exports.h`:
```c
#define FOR_EACH_GETTER(macro) \
macro(CSRF) \
... \
macro(SMTP) \
```
4. Add getter function to `BunObject.zig`:
```zig
pub const SMTP = toJSGetter(Bun.getSmtpConstructor);
// In exportAll:
@export(&BunObject.SMTP, .{ .name = getterName("SMTP") });
```
5. Implement getter in your module:
```zig
pub fn getSmtpConstructor(globalThis: *JSC.JSGlobalObject, _: *JSC.JSObject) JSC.JSValue {
return JSC.API.JSEmailClient.getConstructor(globalThis);
}
```
## Best Practices
1. **Follow Naming Conventions**: Align your naming with existing patterns
2. **Reference Existing Modules**: Study similar modules like Valkey or S3Client for guidance
3. **Memory Management**: Be careful with memory management and reference counting
4. **Error Handling**: Use `bun.JSError!JSValue` for proper error propagation
5. **Documentation**: Add JSDoc comments to your JavaScript bindings
6. **Testing**: Add tests for your new functionality
## Common Gotchas
- Be sure to handle reference counting properly with `ref()`/`deref()`
- Always implement proper cleanup in `deinit()` and `finalize()`
- For network operations, manage socket lifetimes correctly
- Use `JSC.Codegen` correctly to generate necessary binding code
## Related Files
- `src/bun.js/bindings/BunObject+exports.h`: Registration of getters and functions
- `src/bun.js/api/BunObject.zig`: Implementation of getters and object creation
- `src/bun.js/api/BunObject.classes.ts`: Class definitions
- `.cursor/rules/zig-javascriptcore-classes.mdc`: More details on class bindings
## Additional Resources
For more detailed information on specific topics:
- See `zig-javascriptcore-classes.mdc` for details on creating JS class bindings
- Review existing modules like `valkey`, `sqlite`, or `s3` for real-world examples

View File

@@ -0,0 +1,91 @@
---
description: Writing tests for Bun
globs:
---
# Writing tests for Bun
## Where tests are found
You'll find all of Bun's tests in the `test/` directory.
* `test/`
* `cli/` - CLI command tests, like `bun install` or `bun init`
* `js/` - JavaScript & TypeScript tests
* `bun/` - `Bun` APIs tests, separated by category, for example: `glob/` for `Bun.Glob` tests
* `node/` - Node.js module tests, separated by module, for example: `assert/` for `node:assert` tests
* `test/` - Vendored Node.js tests, taken from the Node.js repository (does not conform to Bun's test style)
* `web/` - Web API tests, separated by category, for example: `fetch/` for `Request` and `Response` tests
* `third_party/` - npm package tests, to validate that basic usage works in Bun
* `napi/` - N-API tests
* `v8/` - V8 C++ API tests
* `bundler/` - Bundler, transpiler, CSS, and `bun build` tests
* `regression/issue/[number]` - Regression tests, always make one when fixing a particular issue
## How tests are written
Bun's tests are written as JavaScript and TypeScript files with the Jest-style APIs, like `test`, `describe`, and `expect`. They are tested using Bun's own test runner, `bun test`.
```js
import { describe, test, expect } from "bun:test";
import assert, { AssertionError } from "assert";
describe("assert(expr)", () => {
test.each([true, 1, "foo"])(`assert(%p) does not throw`, expr => {
expect(() => assert(expr)).not.toThrow();
});
test.each([false, 0, "", null, undefined])(`assert(%p) throws`, expr => {
expect(() => assert(expr)).toThrow(AssertionError);
});
});
```
## Testing conventions
* See `test/harness.ts` for common test utilities and helpers
* Be rigorous and test for edge-cases and unexpected inputs
* Use data-driven tests, e.g. `test.each`, to reduce boilerplate when possible
* When you need to test Bun as a CLI, use the following pattern:
```js
import { test, expect } from "bun:test";
import { spawn } from "bun";
import { bunExe, bunEnv } from "harness";
test("bun --version", async () => {
const { exited, stdout: stdoutStream, stderr: stderrStream } = spawn({
cmd: [bunExe(), "--version"],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [ exitCode, stdout, stderr ] = await Promise.all([
exited,
new Response(stdoutStream).text(),
new Response(stderrStream).text(),
]);
expect({ exitCode, stdout, stderr }).toMatchObject({
exitCode: 0,
stdout: expect.stringContaining(Bun.version),
stderr: "",
});
});
```
## Before writing a test
* If you are fixing a bug, write the test first and make sure it fails (as expected) with the canary version of Bun
* If you are fixing a Node.js compatibility bug, create a throw-away snippet of code and test that it works as you expect in Node.js, then that it fails (as expected) with the canary version of Bun
* When the expected behaviour is ambigious, defer to matching what happens in Node.js
* Always attempt to find related tests in an existing test file before creating a new test file

View File

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

2
.gitattributes vendored
View File

@@ -16,7 +16,6 @@
*.map text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.md text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.mdc text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.mdx text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.mjs text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.mts text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
@@ -48,6 +47,7 @@ examples/**/* linguist-documentation
vendor/*.c linguist-vendored
vendor/brotli/** linguist-vendored
packages/bun-framework-react/vendor/** linguist-vendored -diff -merge
test/js/node/test/fixtures linguist-vendored
test/js/node/test/common linguist-vendored

66
.github/workflows/claude.yml vendored Normal file
View File

@@ -0,0 +1,66 @@
name: Claude Code
on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
issues:
types: [opened, assigned]
pull_request_review:
types: [submitted]
jobs:
claude:
if: |
github.repository == 'oven-sh/bun' &&
(
(github.event_name == 'issue_comment' && (github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER' || github.event.comment.author_association == 'COLLABORATOR')) ||
(github.event_name == 'pull_request_review_comment' && (github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER' || github.event.comment.author_association == 'COLLABORATOR')) ||
(github.event_name == 'pull_request_review' && (github.event.review.author_association == 'MEMBER' || github.event.review.author_association == 'OWNER' || github.event.review.author_association == 'COLLABORATOR')) ||
(github.event_name == 'issues' && (github.event.issue.author_association == 'MEMBER' || github.event.issue.author_association == 'OWNER' || github.event.issue.author_association == 'COLLABORATOR'))
) &&
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
runs-on: claude
env:
IS_SANDBOX: 1
container:
image: localhost:5000/claude-bun:latest
options: --privileged --user 1000:1000
permissions:
contents: read
id-token: write
steps:
- name: Checkout repository
working-directory: /workspace/bun
run: |
git config --global user.email "claude-bot@bun.sh" && \
git config --global user.name "Claude Bot" && \
git config --global url."git@github.com:".insteadOf "https://github.com/" && \
git config --global url."git@github.com:".insteadOf "http://github.com/" && \
git config --global --add safe.directory /workspace/bun && \
git config --global push.default current && \
git config --global pull.rebase true && \
git config --global init.defaultBranch main && \
git config --global core.editor "vim" && \
git config --global color.ui auto && \
git config --global fetch.prune true && \
git config --global diff.colorMoved zebra && \
git config --global merge.conflictStyle diff3 && \
git config --global rerere.enabled true && \
git config --global core.autocrlf input
git fetch origin ${{ github.event.pull_request.head.sha }}
git checkout ${{ github.event.pull_request.head.ref }}
git reset --hard origin/${{ github.event.pull_request.head.ref }}
- name: Run Claude Code
id: claude
uses: anthropics/claude-code-action@v1
with:
timeout_minutes: "180"
claude_args: |
--dangerously-skip-permissions
--system-prompt "You are working on the Bun codebase"
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}

58
.github/workflows/codex-test-sync.yml vendored Normal file
View File

@@ -0,0 +1,58 @@
name: Codex Test Sync
on:
pull_request:
types: [labeled, opened]
env:
BUN_VERSION: "1.2.15"
jobs:
sync-node-tests:
runs-on: ubuntu-latest
if: |
(github.event.action == 'labeled' && github.event.label.name == 'codex') ||
(github.event.action == 'opened' && contains(github.event.pull_request.labels.*.name, 'codex')) ||
contains(github.head_ref, 'codex')
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout
uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
fetch-depth: 0
- name: Setup Bun
uses: ./.github/actions/setup-bun
with:
bun-version: ${{ env.BUN_VERSION }}
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v44
with:
files: |
test/js/node/test/parallel/**/*.{js,mjs,ts}
test/js/node/test/sequential/**/*.{js,mjs,ts}
- name: Sync tests
if: steps.changed-files.outputs.any_changed == 'true'
shell: bash
run: |
echo "Changed test files:"
echo "${{ steps.changed-files.outputs.all_changed_files }}"
# Process each changed test file
for file in ${{ steps.changed-files.outputs.all_changed_files }}; do
# Extract test name from file path
test_name=$(basename "$file" | sed 's/\.[^.]*$//')
echo "Syncing test: $test_name"
bun node:test:cp "$test_name"
done
- name: Commit changes
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: "Sync Node.js tests with upstream"

24
.github/workflows/docs.yml vendored Normal file
View File

@@ -0,0 +1,24 @@
name: Docs
on:
push:
paths:
- "docs/**"
- "packages/bun-types/**.d.ts"
- "CONTRIBUTING.md"
- "src/cli/install.sh"
- "src/cli/install.ps1"
branches:
- main
jobs:
deploy:
name: Deploy
runs-on: ubuntu-latest
if: ${{ github.repository_owner == 'oven-sh' }}
steps:
# redeploy Vercel site when a file in `docs` changes
# using VERCEL_DEPLOY_HOOK environment variable
- name: Trigger Webhook
run: |
curl -v ${{ secrets.VERCEL_DEPLOY_HOOK }}

View File

@@ -9,7 +9,7 @@ on:
pull_request:
merge_group:
env:
BUN_VERSION: "1.3.2"
BUN_VERSION: "1.2.20"
LLVM_VERSION: "19.1.7"
LLVM_VERSION_MAJOR: "19"

19
.github/workflows/typos.yml vendored Normal file
View File

@@ -0,0 +1,19 @@
name: Typos
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
docs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Spellcheck
uses: crate-ci/typos@v1.29.4
with:
files: docs/**/*

1
.gitignore vendored
View File

@@ -9,7 +9,6 @@
.ninja_deps
.ninja_log
.npm
.npmrc
.npm.gz
.parcel-cache
.swcrc

View File

@@ -7,8 +7,4 @@ src/react-refresh.js
*.min.js
test/snippets
test/js/node/test
test/napi/node-napi-tests
bun.lock
# the output codeblocks need to stay minified
docs/bundler/minifier.mdx

16
.vscode/settings.json vendored
View File

@@ -27,22 +27,18 @@
"git.ignoreLimitWarning": true,
// Zig
// "zig.initialSetupDone": true,
// "zig.buildOption": "build",
"zig.initialSetupDone": true,
"zig.buildOption": "build",
"zig.zls.zigLibPath": "${workspaceFolder}/vendor/zig/lib",
"zig.buildOnSaveArgs": [
"-Dgenerated-code=./build/debug/codegen",
"--watch",
"-fincremental"
],
// "zig.zls.buildOnSaveStep": "check",
"zig.buildArgs": ["-Dgenerated-code=./build/debug/codegen", "--watch", "-fincremental"],
"zig.zls.buildOnSaveStep": "check",
// "zig.zls.enableBuildOnSave": true,
// "zig.buildOnSave": true,
// "zig.buildFilePath": "${workspaceFolder}/build.zig",
"zig.buildFilePath": "${workspaceFolder}/build.zig",
"zig.path": "${workspaceFolder}/vendor/zig/zig.exe",
"zig.zls.path": "${workspaceFolder}/vendor/zig/zls.exe",
"zig.formattingProvider": "zls",
// "zig.zls.enableInlayHints": false,
"zig.zls.enableInlayHints": false,
"[zig]": {
"editor.tabSize": 4,
"editor.useTabStops": false,

View File

@@ -6,7 +6,7 @@ This is the Bun repository - an all-in-one JavaScript runtime & toolkit designed
- **Build Bun**: `bun bd`
- Creates a debug build at `./build/debug/bun-debug`
- **CRITICAL**: do not set a timeout when running `bun bd`
- **CRITICAL**: no need for a timeout, the build is really fast!
- **Run tests with your debug build**: `bun bd test <test-file>`
- **CRITICAL**: Never use `bun test` directly - it won't include your changes
- **Run any command with debug build**: `bun bd <command>`
@@ -38,36 +38,16 @@ If no valid issue number is provided, find the best existing file to modify inst
### Writing Tests
Tests use Bun's Jest-compatible test runner with proper test fixtures.
- For **single-file tests**, prefer `-e` over `tempDir`.
- For **multi-file tests**, prefer `tempDir` and `Bun.spawn`.
Tests use Bun's Jest-compatible test runner with proper test fixtures:
```typescript
import { test, expect } from "bun:test";
import { bunEnv, bunExe, normalizeBunSnapshot, tempDir } from "harness";
test("(single-file test) my feature", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "-e", "console.log('Hello, world!')"],
env: bunEnv,
});
const [stdout, stderr, exitCode] = await Promise.all([
proc.stdout.text(),
proc.stderr.text(),
proc.exited,
]);
expect(normalizeBunSnapshot(stdout)).toMatchInlineSnapshot(`"Hello, world!"`);
expect(exitCode).toBe(0);
});
test("(multi-file test) my feature", async () => {
test("my feature", async () => {
// Create temp directory with test files
using dir = tempDir("test-prefix", {
"index.js": `import { foo } from "./foo.ts"; foo();`,
"foo.ts": `export function foo() { console.log("foo"); }`,
"index.js": `console.log("hello");`,
});
// Spawn Bun process
@@ -94,7 +74,7 @@ test("(multi-file test) my feature", async () => {
- Always use `port: 0`. Do not hardcode ports. Do not use your own random port number function.
- Use `normalizeBunSnapshot` to normalize snapshot output of the test.
- NEVER write tests that check for no "panic" or "uncaught exception" or similar in the test output. These tests will never fail in CI.
- NEVER write tests that check for no "panic" or "uncaught exception" or similar in the test output. That is NOT a valid test.
- Use `tempDir` from `"harness"` to create a temporary directory. **Do not** use `tmpdirSync` or `fs.mkdtempSync` to create temporary directories.
- When spawning processes, tests should expect(stdout).toBe(...) BEFORE expect(exitCode).toBe(0). This gives you a more useful error message on test failure.
- **CRITICAL**: Do not write flaky tests. Do not use `setTimeout` in tests. Instead, `await` the condition to be met. You are not testing the TIME PASSING, you are testing the CONDITION.

View File

@@ -24,6 +24,7 @@ if(CMAKE_HOST_APPLE)
include(SetupMacSDK)
endif()
include(SetupLLVM)
include(SetupCcache)
# --- Project ---
@@ -47,8 +48,6 @@ include(SetupEsbuild)
include(SetupZig)
include(SetupRust)
include(SetupCcache)
# Generate dependency versions header
include(GenerateDependencyVersions)

View File

@@ -27,15 +27,15 @@ $ brew install automake ccache cmake coreutils gnu-sed go icu4c libiconv libtool
```
```bash#Ubuntu/Debian
$ sudo apt install curl wget lsb-release software-properties-common cargo cmake git golang libtool ninja-build pkg-config rustc ruby-full xz-utils
$ sudo apt install curl wget lsb-release software-properties-common cargo ccache cmake git golang libtool ninja-build pkg-config rustc ruby-full xz-utils
```
```bash#Arch
$ sudo pacman -S base-devel cmake git go libiconv libtool make ninja pkg-config python rust sed unzip ruby
$ sudo pacman -S base-devel ccache cmake git go libiconv libtool make ninja pkg-config python rust sed unzip ruby
```
```bash#Fedora
$ sudo dnf install cargo clang19 llvm19 lld19 cmake git golang libtool ninja-build pkg-config rustc ruby libatomic-static libstdc++-static sed unzip which libicu-devel 'perl(Math::BigInt)'
$ sudo dnf install cargo clang19 llvm19 lld19 ccache cmake git golang libtool ninja-build pkg-config rustc ruby libatomic-static libstdc++-static sed unzip which libicu-devel 'perl(Math::BigInt)'
```
```bash#openSUSE Tumbleweed
@@ -65,29 +65,6 @@ $ brew install bun
{% /codetabs %}
### Optional: Install `ccache`
ccache is used to cache compilation artifacts, significantly speeding up builds:
```bash
# For macOS
$ brew install ccache
# For Ubuntu/Debian
$ sudo apt install ccache
# For Arch
$ sudo pacman -S ccache
# For Fedora
$ sudo dnf install ccache
# For openSUSE
$ sudo zypper install ccache
```
Our build scripts will automatically detect and use `ccache` if available. You can check cache statistics with `ccache --show-stats`.
## Install LLVM
Bun requires LLVM 19 (`clang` is part of LLVM). This version requirement is to match WebKit (precompiled), as mismatching versions will cause memory allocation failures at runtime. In most cases, you can install LLVM through your system package manager:
@@ -186,7 +163,7 @@ Bun generally takes about 2.5 minutes to compile a debug build when there are Zi
- Batch up your changes
- Ensure zls is running with incremental watching for LSP errors (if you use VSCode and install Zig and run `bun run build` once to download Zig, this should just work)
- Prefer using the debugger ("CodeLLDB" in VSCode) to step through the code.
- Use debug logs. `BUN_DEBUG_<scope>=1` will enable debug logging for the corresponding `Output.scoped(.<scope>, .hidden)` logs. You can also set `BUN_DEBUG_QUIET_LOGS=1` to disable all debug logging that isn't explicitly enabled. To dump debug logs into a file, `BUN_DEBUG=<path-to-file>.log`. Debug logs are aggressively removed in release builds.
- Use debug logs. `BUN_DEBUG_<scope>=1` will enable debug logging for the corresponding `Output.scoped(.<scope>, .hidden)` logs. You can also set `BUN_DEBUG_QUIET_LOGS=1` to disable all debug logging that isn't explicitly enabled. To dump debug lgos into a file, `BUN_DEBUG=<path-to-file>.log`. Debug logs are aggressively removed in release builds.
- src/js/\*\*.ts changes are pretty much instant to rebuild. C++ changes are a bit slower, but still much faster than the Zig code (Zig is one compilation unit, C++ is many).
## Code generation scripts
@@ -354,6 +331,15 @@ $ bun run build -DUSE_STATIC_LIBATOMIC=OFF
The built version of Bun may not work on other systems if compiled this way.
### ccache conflicts with building TinyCC on macOS
If you run into issues with `ccache` when building TinyCC, try reinstalling ccache
```bash
brew uninstall ccache
brew install ccache
```
## Using bun-debug
- Disable logging: `BUN_DEBUG_QUIET_LOGS=1 bun-debug ...` (to disable all debug logging)

2
LATEST
View File

@@ -1 +1 @@
1.3.6
1.3.1

View File

@@ -54,7 +54,7 @@ Bun supports Linux (x64 & arm64), macOS (x64 & Apple Silicon) and Windows (x64).
curl -fsSL https://bun.com/install | bash
# on windows
powershell -c "irm bun.sh/install.ps1 | iex"
powershell -c "irm bun.com/install.ps1 | iex"
# with npm
npm install -g bun
@@ -104,13 +104,13 @@ bun upgrade --canary
- [File types (Loaders)](https://bun.com/docs/runtime/loaders)
- [TypeScript](https://bun.com/docs/runtime/typescript)
- [JSX](https://bun.com/docs/runtime/jsx)
- [Environment variables](https://bun.com/docs/runtime/environment-variables)
- [Environment variables](https://bun.com/docs/runtime/env)
- [Bun APIs](https://bun.com/docs/runtime/bun-apis)
- [Web APIs](https://bun.com/docs/runtime/web-apis)
- [Node.js compatibility](https://bun.com/docs/runtime/nodejs-compat)
- [Node.js compatibility](https://bun.com/docs/runtime/nodejs-apis)
- [Single-file executable](https://bun.com/docs/bundler/executables)
- [Plugins](https://bun.com/docs/runtime/plugins)
- [Watch mode / Hot Reloading](https://bun.com/docs/runtime/watch-mode)
- [Watch mode / Hot Reloading](https://bun.com/docs/runtime/hot)
- [Module resolution](https://bun.com/docs/runtime/modules)
- [Auto-install](https://bun.com/docs/runtime/autoimport)
- [bunfig.toml](https://bun.com/docs/runtime/bunfig)
@@ -230,7 +230,7 @@ bun upgrade --canary
- Ecosystem
- [Use React and JSX](https://bun.com/guides/ecosystem/react)
- [Use Gel with Bun](https://bun.com/guides/ecosystem/gel)
- [Use EdgeDB with Bun](https://bun.com/guides/ecosystem/edgedb)
- [Use Prisma with Bun](https://bun.com/guides/ecosystem/prisma)
- [Add Sentry to a Bun app](https://bun.com/guides/ecosystem/sentry)
- [Create a Discord bot](https://bun.com/guides/ecosystem/discordjs)

View File

@@ -1,6 +1,5 @@
{
"lockfileVersion": 1,
"configVersion": 0,
"workspaces": {
"": {
"name": "bench",
@@ -23,7 +22,6 @@
"react-dom": "^18.3.1",
"string-width": "7.1.0",
"strip-ansi": "^7.1.0",
"tar": "^7.4.3",
"tinycolor2": "^1.6.0",
"zx": "^7.2.3",
},
@@ -109,8 +107,6 @@
"@fastify/proxy-addr": ["@fastify/proxy-addr@5.0.0", "", { "dependencies": { "@fastify/forwarded": "^3.0.0", "ipaddr.js": "^2.1.0" } }, "sha512-37qVVA1qZ5sgH7KpHkkC4z9SK6StIsIcOmpjvMPXNb3vx2GQxhZocogVYbr2PbbeLCQxYIPDok307xEvRZOzGA=="],
"@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="],
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.1.1", "", { "dependencies": { "@jridgewell/set-array": "^1.0.0", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w=="],
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.0", "", {}, "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w=="],
@@ -185,8 +181,6 @@
"chalk": ["chalk@5.3.0", "", {}, "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w=="],
"chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="],
"color": ["color@4.2.3", "", { "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" } }, "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A=="],
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
@@ -367,10 +361,6 @@
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
"minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
"minizlib": ["minizlib@3.1.0", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw=="],
"mitata": ["mitata@1.0.25", "", {}, "sha512-0v5qZtVW5vwj9FDvYfraR31BMDcRLkhSFWPTLaxx/Z3/EvScfVtAAWtMI2ArIbBcwh7P86dXh0lQWKiXQPlwYA=="],
"ms": ["ms@2.1.2", "", {}, "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="],
@@ -467,8 +457,6 @@
"supports-color": ["supports-color@5.5.0", "", { "dependencies": { "has-flag": "^3.0.0" } }, "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow=="],
"tar": ["tar@7.5.2", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg=="],
"thread-stream": ["thread-stream@3.1.0", "", { "dependencies": { "real-require": "^0.2.0" } }, "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A=="],
"through": ["through@2.3.8", "", {}, "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg=="],
@@ -493,7 +481,7 @@
"which": ["which@3.0.1", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "bin/which.js" } }, "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg=="],
"yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="],
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
"yaml": ["yaml@2.3.4", "", {}, "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA=="],
@@ -513,8 +501,6 @@
"light-my-request/process-warning": ["process-warning@4.0.1", "", {}, "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q=="],
"lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
"npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="],
"ansi-styles/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="],

View File

@@ -1,6 +1,5 @@
{
"lockfileVersion": 1,
"configVersion": 0,
"workspaces": {
"": {
"name": "installbench",
@@ -13,7 +12,7 @@
"@trpc/server": "^11.0.0",
"drizzle-orm": "^0.41.0",
"esbuild": "^0.25.11",
"next": "15.5.7",
"next": "^15.2.3",
"next-auth": "5.0.0-beta.25",
"postgres": "^3.4.4",
"react": "^19.0.0",
@@ -176,23 +175,23 @@
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "3.1.2", "@jridgewell/sourcemap-codec": "1.5.5" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
"@next/env": ["@next/env@15.5.7", "", {}, "sha512-4h6Y2NyEkIEN7Z8YxkA27pq6zTkS09bUSYC0xjd0NpwFxjnIKeZEeH591o5WECSmjpUhLn3H2QLJcDye3Uzcvg=="],
"@next/env": ["@next/env@15.5.6", "", {}, "sha512-3qBGRW+sCGzgbpc5TS1a0p7eNxnOarGVQhZxfvTdnV0gFI61lX7QNtQ4V1TSREctXzYn5NetbUsLvyqwLFJM6Q=="],
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.5.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-IZwtxCEpI91HVU/rAUOOobWSZv4P2DeTtNaCdHqLcTJU4wdNXgAySvKa/qJCgR5m6KI8UsKDXtO2B31jcaw1Yw=="],
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.5.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ES3nRz7N+L5Umz4KoGfZ4XX6gwHplwPhioVRc25+QNsDa7RtUF/z8wJcbuQ2Tffm5RZwuN2A063eapoJ1u4nPg=="],
"@next/swc-darwin-x64": ["@next/swc-darwin-x64@15.5.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-UP6CaDBcqaCBuiq/gfCEJw7sPEoX1aIjZHnBWN9v9qYHQdMKvCKcAVs4OX1vIjeE+tC5EIuwDTVIoXpUes29lg=="],
"@next/swc-darwin-x64": ["@next/swc-darwin-x64@15.5.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-JIGcytAyk9LQp2/nuVZPAtj8uaJ/zZhsKOASTjxDug0SPU9LAM3wy6nPU735M1OqacR4U20LHVF5v5Wnl9ptTA=="],
"@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@15.5.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-NCslw3GrNIw7OgmRBxHtdWFQYhexoUCq+0oS2ccjyYLtcn1SzGzeM54jpTFonIMUjNbHmpKpziXnpxhSWLcmBA=="],
"@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@15.5.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-qvz4SVKQ0P3/Im9zcS2RmfFL/UCQnsJKJwQSkissbngnB/12c6bZTCB0gHTexz1s6d/mD0+egPKXAIRFVS7hQg=="],
"@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@15.5.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-nfymt+SE5cvtTrG9u1wdoxBr9bVB7mtKTcj0ltRn6gkP/2Nu1zM5ei8rwP9qKQP0Y//umK+TtkKgNtfboBxRrw=="],
"@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@15.5.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-FsbGVw3SJz1hZlvnWD+T6GFgV9/NYDeLTNQB2MXoPN5u9VA9OEDy6fJEfePfsUKAhJufFbZLgp0cPxMuV6SV0w=="],
"@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@15.5.7", "", { "os": "linux", "cpu": "x64" }, "sha512-hvXcZvCaaEbCZcVzcY7E1uXN9xWZfFvkNHwbe/n4OkRhFWrs1J1QV+4U1BN06tXLdaS4DazEGXwgqnu/VMcmqw=="],
"@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@15.5.6", "", { "os": "linux", "cpu": "x64" }, "sha512-3QnHGFWlnvAgyxFxt2Ny8PTpXtQD7kVEeaFat5oPAHHI192WKYB+VIKZijtHLGdBBvc16tiAkPTDmQNOQ0dyrA=="],
"@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@15.5.7", "", { "os": "linux", "cpu": "x64" }, "sha512-4IUO539b8FmF0odY6/SqANJdgwn1xs1GkPO5doZugwZ3ETF6JUdckk7RGmsfSf7ws8Qb2YB5It33mvNL/0acqA=="],
"@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@15.5.6", "", { "os": "linux", "cpu": "x64" }, "sha512-OsGX148sL+TqMK9YFaPFPoIaJKbFJJxFzkXZljIgA9hjMjdruKht6xDCEv1HLtlLNfkx3c5w2GLKhj7veBQizQ=="],
"@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.5.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-CpJVTkYI3ZajQkC5vajM7/ApKJUOlm6uP4BknM3XKvJ7VXAvCqSjSLmM0LKdYzn6nBJVSjdclx8nYJSa3xlTgQ=="],
"@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.5.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-ONOMrqWxdzXDJNh2n60H6gGyKed42Ieu6UTVPZteXpuKbLZTH4G4eBMsr5qWgOBA+s7F+uB4OJbZnrkEDnZ5Fg=="],
"@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.5.7", "", { "os": "win32", "cpu": "x64" }, "sha512-gMzgBX164I6DN+9/PGA+9dQiwmTkE4TloBNx8Kv9UiGARsr9Nba7IpcBRA1iTV9vwlYnrE3Uy6I7Aj6qLjQuqw=="],
"@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.5.6", "", { "os": "win32", "cpu": "x64" }, "sha512-pxK4VIjFRx1MY92UycLOOw7dTdvccWsNETQ0kDHkBlcFH1GrTLUjSiHU1ohrznnux6TqRHgv5oflhfIWZwVROQ=="],
"@panva/hkdf": ["@panva/hkdf@1.2.1", "", {}, "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw=="],
@@ -324,7 +323,7 @@
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
"next": ["next@15.5.7", "", { "dependencies": { "@next/env": "15.5.7", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.5.7", "@next/swc-darwin-x64": "15.5.7", "@next/swc-linux-arm64-gnu": "15.5.7", "@next/swc-linux-arm64-musl": "15.5.7", "@next/swc-linux-x64-gnu": "15.5.7", "@next/swc-linux-x64-musl": "15.5.7", "@next/swc-win32-arm64-msvc": "15.5.7", "@next/swc-win32-x64-msvc": "15.5.7", "sharp": "^0.34.3" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-+t2/0jIJ48kUpGKkdlhgkv+zPTEOoXyr60qXe68eB/pl3CMJaLeIGjzp5D6Oqt25hCBiBTt8wEeeAzfJvUKnPQ=="],
"next": ["next@15.5.6", "", { "dependencies": { "@next/env": "15.5.6", "@swc/helpers": "0.5.15", "caniuse-lite": "1.0.30001752", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.5.6", "@next/swc-darwin-x64": "15.5.6", "@next/swc-linux-arm64-gnu": "15.5.6", "@next/swc-linux-arm64-musl": "15.5.6", "@next/swc-linux-x64-gnu": "15.5.6", "@next/swc-linux-x64-musl": "15.5.6", "@next/swc-win32-arm64-msvc": "15.5.6", "@next/swc-win32-x64-msvc": "15.5.6", "sharp": "0.34.4" }, "peerDependencies": { "react": "19.2.0", "react-dom": "19.2.0" }, "bin": { "next": "dist/bin/next" } }, "sha512-zTxsnI3LQo3c9HSdSf91O1jMNsEzIXDShXd4wVdg9y5shwLqBXi4ZtUUJyB86KGVSJLZx0PFONvO54aheGX8QQ=="],
"next-auth": ["next-auth@5.0.0-beta.25", "", { "dependencies": { "@auth/core": "0.37.2" }, "peerDependencies": { "next": "15.5.6", "react": "19.2.0" } }, "sha512-2dJJw1sHQl2qxCrRk+KTQbeH+izFbGFPuJj5eGgBZFYyiYYtvlrBeUw1E/OJJxTRjuxbSYGnCTkUIRsIIW0bog=="],

View File

@@ -26,7 +26,7 @@
"@trpc/server": "^11.0.0",
"drizzle-orm": "^0.41.0",
"esbuild": "^0.25.11",
"next": "15.5.7",
"next": "^15.2.3",
"next-auth": "5.0.0-beta.25",
"postgres": "^3.4.4",
"react": "^19.0.0",

View File

@@ -19,7 +19,6 @@
"react-dom": "^18.3.1",
"string-width": "7.1.0",
"strip-ansi": "^7.1.0",
"tar": "^7.4.3",
"tinycolor2": "^1.6.0",
"zx": "^7.2.3"
},

View File

@@ -13,4 +13,7 @@ export function run(opts = {}) {
}
export const bench = Mitata.bench;
export const group = Mitata.group;
export function group(_name, fn) {
return Mitata.group(fn);
}

View File

@@ -1,477 +0,0 @@
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { Pack, Unpack } from "tar";
import { bench, group, run } from "../runner.mjs";
// Check if Bun.Archive is available
const hasBunArchive = typeof Bun !== "undefined" && typeof Bun.Archive !== "undefined";
// Test data sizes
const smallContent = "Hello, World!";
const mediumContent = Buffer.alloc(10 * 1024, "x").toString(); // 10KB
const largeContent = Buffer.alloc(100 * 1024, "x").toString(); // 100KB
// Create test files for node-tar (it reads from filesystem)
const setupDir = mkdtempSync(join(tmpdir(), "archive-bench-setup-"));
function setupNodeTarFiles(prefix, files) {
const dir = join(setupDir, prefix);
mkdirSync(dir, { recursive: true });
for (const [name, content] of Object.entries(files)) {
const filePath = join(dir, name);
const fileDir = join(filePath, "..");
mkdirSync(fileDir, { recursive: true });
writeFileSync(filePath, content);
}
return dir;
}
// Setup directories for different test cases
const smallFilesDir = setupNodeTarFiles("small", {
"file1.txt": smallContent,
"file2.txt": smallContent,
"file3.txt": smallContent,
});
const mediumFilesDir = setupNodeTarFiles("medium", {
"file1.txt": mediumContent,
"file2.txt": mediumContent,
"file3.txt": mediumContent,
});
const largeFilesDir = setupNodeTarFiles("large", {
"file1.txt": largeContent,
"file2.txt": largeContent,
"file3.txt": largeContent,
});
const manyFilesEntries = {};
for (let i = 0; i < 100; i++) {
manyFilesEntries[`file${i}.txt`] = smallContent;
}
const manyFilesDir = setupNodeTarFiles("many", manyFilesEntries);
// Pre-create archives for extraction benchmarks
let smallTarGzBuffer, mediumTarGzBuffer, largeTarGzBuffer, manyFilesTarGzBuffer;
let smallTarBuffer, mediumTarBuffer, largeTarBuffer, manyFilesTarBuffer;
let smallBunArchiveGz, mediumBunArchiveGz, largeBunArchiveGz, manyFilesBunArchiveGz;
let smallBunArchive, mediumBunArchive, largeBunArchive, manyFilesBunArchive;
// Create tar buffer using node-tar (with optional gzip)
async function createNodeTarBuffer(cwd, files, gzip = false) {
return new Promise(resolve => {
const pack = new Pack({ cwd, gzip });
const bufs = [];
pack.on("data", chunk => bufs.push(chunk));
pack.on("end", () => resolve(Buffer.concat(bufs)));
for (const file of files) {
pack.add(file);
}
pack.end();
});
}
// Extract tar buffer using node-tar
async function extractNodeTarBuffer(buffer, cwd) {
return new Promise((resolve, reject) => {
const unpack = new Unpack({ cwd });
unpack.on("end", resolve);
unpack.on("error", reject);
unpack.end(buffer);
});
}
// Initialize gzipped archives
smallTarGzBuffer = await createNodeTarBuffer(smallFilesDir, ["file1.txt", "file2.txt", "file3.txt"], true);
mediumTarGzBuffer = await createNodeTarBuffer(mediumFilesDir, ["file1.txt", "file2.txt", "file3.txt"], true);
largeTarGzBuffer = await createNodeTarBuffer(largeFilesDir, ["file1.txt", "file2.txt", "file3.txt"], true);
manyFilesTarGzBuffer = await createNodeTarBuffer(manyFilesDir, Object.keys(manyFilesEntries), true);
// Initialize uncompressed archives
smallTarBuffer = await createNodeTarBuffer(smallFilesDir, ["file1.txt", "file2.txt", "file3.txt"], false);
mediumTarBuffer = await createNodeTarBuffer(mediumFilesDir, ["file1.txt", "file2.txt", "file3.txt"], false);
largeTarBuffer = await createNodeTarBuffer(largeFilesDir, ["file1.txt", "file2.txt", "file3.txt"], false);
manyFilesTarBuffer = await createNodeTarBuffer(manyFilesDir, Object.keys(manyFilesEntries), false);
const smallFiles = { "file1.txt": smallContent, "file2.txt": smallContent, "file3.txt": smallContent };
const mediumFiles = { "file1.txt": mediumContent, "file2.txt": mediumContent, "file3.txt": mediumContent };
const largeFiles = { "file1.txt": largeContent, "file2.txt": largeContent, "file3.txt": largeContent };
if (hasBunArchive) {
smallBunArchiveGz = await Bun.Archive.from(smallFiles).bytes("gzip");
mediumBunArchiveGz = await Bun.Archive.from(mediumFiles).bytes("gzip");
largeBunArchiveGz = await Bun.Archive.from(largeFiles).bytes("gzip");
manyFilesBunArchiveGz = await Bun.Archive.from(manyFilesEntries).bytes("gzip");
smallBunArchive = await Bun.Archive.from(smallFiles).bytes();
mediumBunArchive = await Bun.Archive.from(mediumFiles).bytes();
largeBunArchive = await Bun.Archive.from(largeFiles).bytes();
manyFilesBunArchive = await Bun.Archive.from(manyFilesEntries).bytes();
}
// Create reusable extraction directories (overwriting is fine)
const extractDirNodeTar = mkdtempSync(join(tmpdir(), "archive-bench-extract-node-"));
const extractDirBun = mkdtempSync(join(tmpdir(), "archive-bench-extract-bun-"));
const writeDirNodeTar = mkdtempSync(join(tmpdir(), "archive-bench-write-node-"));
const writeDirBun = mkdtempSync(join(tmpdir(), "archive-bench-write-bun-"));
// ============================================================================
// Create .tar (uncompressed) benchmarks
// ============================================================================
group("create .tar (3 small files)", () => {
bench("node-tar", async () => {
await createNodeTarBuffer(smallFilesDir, ["file1.txt", "file2.txt", "file3.txt"], false);
});
if (hasBunArchive) {
bench("Bun.Archive", async () => {
await Bun.Archive.from(smallFiles).bytes();
});
}
});
group("create .tar (3 x 100KB files)", () => {
bench("node-tar", async () => {
await createNodeTarBuffer(largeFilesDir, ["file1.txt", "file2.txt", "file3.txt"], false);
});
if (hasBunArchive) {
bench("Bun.Archive", async () => {
await Bun.Archive.from(largeFiles).bytes();
});
}
});
group("create .tar (100 small files)", () => {
bench("node-tar", async () => {
await createNodeTarBuffer(manyFilesDir, Object.keys(manyFilesEntries), false);
});
if (hasBunArchive) {
bench("Bun.Archive", async () => {
await Bun.Archive.from(manyFilesEntries).bytes();
});
}
});
// ============================================================================
// Create .tar.gz (compressed) benchmarks
// ============================================================================
group("create .tar.gz (3 small files)", () => {
bench("node-tar", async () => {
await createNodeTarBuffer(smallFilesDir, ["file1.txt", "file2.txt", "file3.txt"], true);
});
if (hasBunArchive) {
bench("Bun.Archive", async () => {
await Bun.Archive.from(smallFiles).bytes("gzip");
});
}
});
group("create .tar.gz (3 x 100KB files)", () => {
bench("node-tar", async () => {
await createNodeTarBuffer(largeFilesDir, ["file1.txt", "file2.txt", "file3.txt"], true);
});
if (hasBunArchive) {
bench("Bun.Archive", async () => {
await Bun.Archive.from(largeFiles).bytes("gzip");
});
}
});
group("create .tar.gz (100 small files)", () => {
bench("node-tar", async () => {
await createNodeTarBuffer(manyFilesDir, Object.keys(manyFilesEntries), true);
});
if (hasBunArchive) {
bench("Bun.Archive", async () => {
await Bun.Archive.from(manyFilesEntries).bytes("gzip");
});
}
});
// ============================================================================
// Extract .tar (uncompressed) benchmarks
// ============================================================================
group("extract .tar (3 small files)", () => {
bench("node-tar", async () => {
await extractNodeTarBuffer(smallTarBuffer, extractDirNodeTar);
});
if (hasBunArchive) {
bench("Bun.Archive", async () => {
await Bun.Archive.from(smallBunArchive).extract(extractDirBun);
});
}
});
group("extract .tar (3 x 100KB files)", () => {
bench("node-tar", async () => {
await extractNodeTarBuffer(largeTarBuffer, extractDirNodeTar);
});
if (hasBunArchive) {
bench("Bun.Archive", async () => {
await Bun.Archive.from(largeBunArchive).extract(extractDirBun);
});
}
});
group("extract .tar (100 small files)", () => {
bench("node-tar", async () => {
await extractNodeTarBuffer(manyFilesTarBuffer, extractDirNodeTar);
});
if (hasBunArchive) {
bench("Bun.Archive", async () => {
await Bun.Archive.from(manyFilesBunArchive).extract(extractDirBun);
});
}
});
// ============================================================================
// Extract .tar.gz (compressed) benchmarks
// ============================================================================
group("extract .tar.gz (3 small files)", () => {
bench("node-tar", async () => {
await extractNodeTarBuffer(smallTarGzBuffer, extractDirNodeTar);
});
if (hasBunArchive) {
bench("Bun.Archive", async () => {
await Bun.Archive.from(smallBunArchiveGz).extract(extractDirBun);
});
}
});
group("extract .tar.gz (3 x 100KB files)", () => {
bench("node-tar", async () => {
await extractNodeTarBuffer(largeTarGzBuffer, extractDirNodeTar);
});
if (hasBunArchive) {
bench("Bun.Archive", async () => {
await Bun.Archive.from(largeBunArchiveGz).extract(extractDirBun);
});
}
});
group("extract .tar.gz (100 small files)", () => {
bench("node-tar", async () => {
await extractNodeTarBuffer(manyFilesTarGzBuffer, extractDirNodeTar);
});
if (hasBunArchive) {
bench("Bun.Archive", async () => {
await Bun.Archive.from(manyFilesBunArchiveGz).extract(extractDirBun);
});
}
});
// ============================================================================
// Write .tar to disk benchmarks
// ============================================================================
let writeCounter = 0;
group("write .tar to disk (3 small files)", () => {
bench("node-tar + writeFileSync", async () => {
const buffer = await createNodeTarBuffer(smallFilesDir, ["file1.txt", "file2.txt", "file3.txt"], false);
writeFileSync(join(writeDirNodeTar, `archive-${writeCounter++}.tar`), buffer);
});
if (hasBunArchive) {
bench("Bun.Archive.write", async () => {
await Bun.Archive.write(join(writeDirBun, `archive-${writeCounter++}.tar`), smallFiles);
});
}
});
group("write .tar to disk (3 x 100KB files)", () => {
bench("node-tar + writeFileSync", async () => {
const buffer = await createNodeTarBuffer(largeFilesDir, ["file1.txt", "file2.txt", "file3.txt"], false);
writeFileSync(join(writeDirNodeTar, `archive-${writeCounter++}.tar`), buffer);
});
if (hasBunArchive) {
bench("Bun.Archive.write", async () => {
await Bun.Archive.write(join(writeDirBun, `archive-${writeCounter++}.tar`), largeFiles);
});
}
});
group("write .tar to disk (100 small files)", () => {
bench("node-tar + writeFileSync", async () => {
const buffer = await createNodeTarBuffer(manyFilesDir, Object.keys(manyFilesEntries), false);
writeFileSync(join(writeDirNodeTar, `archive-${writeCounter++}.tar`), buffer);
});
if (hasBunArchive) {
bench("Bun.Archive.write", async () => {
await Bun.Archive.write(join(writeDirBun, `archive-${writeCounter++}.tar`), manyFilesEntries);
});
}
});
// ============================================================================
// Write .tar.gz to disk benchmarks
// ============================================================================
group("write .tar.gz to disk (3 small files)", () => {
bench("node-tar + writeFileSync", async () => {
const buffer = await createNodeTarBuffer(smallFilesDir, ["file1.txt", "file2.txt", "file3.txt"], true);
writeFileSync(join(writeDirNodeTar, `archive-${writeCounter++}.tar.gz`), buffer);
});
if (hasBunArchive) {
bench("Bun.Archive.write", async () => {
await Bun.Archive.write(join(writeDirBun, `archive-${writeCounter++}.tar.gz`), smallFiles, "gzip");
});
}
});
group("write .tar.gz to disk (3 x 100KB files)", () => {
bench("node-tar + writeFileSync", async () => {
const buffer = await createNodeTarBuffer(largeFilesDir, ["file1.txt", "file2.txt", "file3.txt"], true);
writeFileSync(join(writeDirNodeTar, `archive-${writeCounter++}.tar.gz`), buffer);
});
if (hasBunArchive) {
bench("Bun.Archive.write", async () => {
await Bun.Archive.write(join(writeDirBun, `archive-${writeCounter++}.tar.gz`), largeFiles, "gzip");
});
}
});
group("write .tar.gz to disk (100 small files)", () => {
bench("node-tar + writeFileSync", async () => {
const buffer = await createNodeTarBuffer(manyFilesDir, Object.keys(manyFilesEntries), true);
writeFileSync(join(writeDirNodeTar, `archive-${writeCounter++}.tar.gz`), buffer);
});
if (hasBunArchive) {
bench("Bun.Archive.write", async () => {
await Bun.Archive.write(join(writeDirBun, `archive-${writeCounter++}.tar.gz`), manyFilesEntries, "gzip");
});
}
});
// ============================================================================
// Get files array from archive (files() method) benchmarks
// ============================================================================
// Helper to get files array from node-tar (reads all entries into memory)
async function getFilesArrayNodeTar(buffer) {
return new Promise((resolve, reject) => {
const files = new Map();
let pending = 0;
let closed = false;
const maybeResolve = () => {
if (closed && pending === 0) {
resolve(files);
}
};
const unpack = new Unpack({
onReadEntry: entry => {
if (entry.type === "File") {
pending++;
const chunks = [];
entry.on("data", chunk => chunks.push(chunk));
entry.on("end", () => {
const content = Buffer.concat(chunks);
// Create a File-like object similar to Bun.Archive.files()
files.set(entry.path, new Blob([content]));
pending--;
maybeResolve();
});
}
entry.resume(); // Drain the entry
},
});
unpack.on("close", () => {
closed = true;
maybeResolve();
});
unpack.on("error", reject);
unpack.end(buffer);
});
}
group("files() - get all files as Map (3 small files)", () => {
bench("node-tar", async () => {
await getFilesArrayNodeTar(smallTarBuffer);
});
if (hasBunArchive) {
bench("Bun.Archive.files()", async () => {
await Bun.Archive.from(smallBunArchive).files();
});
}
});
group("files() - get all files as Map (3 x 100KB files)", () => {
bench("node-tar", async () => {
await getFilesArrayNodeTar(largeTarBuffer);
});
if (hasBunArchive) {
bench("Bun.Archive.files()", async () => {
await Bun.Archive.from(largeBunArchive).files();
});
}
});
group("files() - get all files as Map (100 small files)", () => {
bench("node-tar", async () => {
await getFilesArrayNodeTar(manyFilesTarBuffer);
});
if (hasBunArchive) {
bench("Bun.Archive.files()", async () => {
await Bun.Archive.from(manyFilesBunArchive).files();
});
}
});
group("files() - get all files as Map from .tar.gz (3 small files)", () => {
bench("node-tar", async () => {
await getFilesArrayNodeTar(smallTarGzBuffer);
});
if (hasBunArchive) {
bench("Bun.Archive.files()", async () => {
await Bun.Archive.from(smallBunArchiveGz).files();
});
}
});
group("files() - get all files as Map from .tar.gz (100 small files)", () => {
bench("node-tar", async () => {
await getFilesArrayNodeTar(manyFilesTarGzBuffer);
});
if (hasBunArchive) {
bench("Bun.Archive.files()", async () => {
await Bun.Archive.from(manyFilesBunArchiveGz).files();
});
}
});
await run();
// Cleanup
rmSync(setupDir, { recursive: true, force: true });
rmSync(extractDirNodeTar, { recursive: true, force: true });
rmSync(extractDirBun, { recursive: true, force: true });
rmSync(writeDirNodeTar, { recursive: true, force: true });
rmSync(writeDirBun, { recursive: true, force: true });

View File

@@ -1,335 +0,0 @@
import { bench, run } from "../runner.mjs";
let sink;
// Integers
bench("int: Array.of(1,2,3,4,5)", () => {
sink = Array.of(1, 2, 3, 4, 5);
});
bench("int: Array.of(100 elements)", () => {
sink = Array.of(
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20,
21,
22,
23,
24,
25,
26,
27,
28,
29,
30,
31,
32,
33,
34,
35,
36,
37,
38,
39,
40,
41,
42,
43,
44,
45,
46,
47,
48,
49,
50,
51,
52,
53,
54,
55,
56,
57,
58,
59,
60,
61,
62,
63,
64,
65,
66,
67,
68,
69,
70,
71,
72,
73,
74,
75,
76,
77,
78,
79,
80,
81,
82,
83,
84,
85,
86,
87,
88,
89,
90,
91,
92,
93,
94,
95,
96,
97,
98,
99,
);
});
// Doubles
bench("double: Array.of(1.1,2.2,3.3,4.4,5.5)", () => {
sink = Array.of(1.1, 2.2, 3.3, 4.4, 5.5);
});
bench("double: Array.of(100 elements)", () => {
sink = Array.of(
0.1,
1.1,
2.1,
3.1,
4.1,
5.1,
6.1,
7.1,
8.1,
9.1,
10.1,
11.1,
12.1,
13.1,
14.1,
15.1,
16.1,
17.1,
18.1,
19.1,
20.1,
21.1,
22.1,
23.1,
24.1,
25.1,
26.1,
27.1,
28.1,
29.1,
30.1,
31.1,
32.1,
33.1,
34.1,
35.1,
36.1,
37.1,
38.1,
39.1,
40.1,
41.1,
42.1,
43.1,
44.1,
45.1,
46.1,
47.1,
48.1,
49.1,
50.1,
51.1,
52.1,
53.1,
54.1,
55.1,
56.1,
57.1,
58.1,
59.1,
60.1,
61.1,
62.1,
63.1,
64.1,
65.1,
66.1,
67.1,
68.1,
69.1,
70.1,
71.1,
72.1,
73.1,
74.1,
75.1,
76.1,
77.1,
78.1,
79.1,
80.1,
81.1,
82.1,
83.1,
84.1,
85.1,
86.1,
87.1,
88.1,
89.1,
90.1,
91.1,
92.1,
93.1,
94.1,
95.1,
96.1,
97.1,
98.1,
99.1,
);
});
// Objects
bench("object: Array.of(obj x5)", () => {
sink = Array.of({ a: 1 }, { a: 2 }, { a: 3 }, { a: 4 }, { a: 5 });
});
bench("object: Array.of(100 elements)", () => {
sink = Array.of(
{ a: 0 },
{ a: 1 },
{ a: 2 },
{ a: 3 },
{ a: 4 },
{ a: 5 },
{ a: 6 },
{ a: 7 },
{ a: 8 },
{ a: 9 },
{ a: 10 },
{ a: 11 },
{ a: 12 },
{ a: 13 },
{ a: 14 },
{ a: 15 },
{ a: 16 },
{ a: 17 },
{ a: 18 },
{ a: 19 },
{ a: 20 },
{ a: 21 },
{ a: 22 },
{ a: 23 },
{ a: 24 },
{ a: 25 },
{ a: 26 },
{ a: 27 },
{ a: 28 },
{ a: 29 },
{ a: 30 },
{ a: 31 },
{ a: 32 },
{ a: 33 },
{ a: 34 },
{ a: 35 },
{ a: 36 },
{ a: 37 },
{ a: 38 },
{ a: 39 },
{ a: 40 },
{ a: 41 },
{ a: 42 },
{ a: 43 },
{ a: 44 },
{ a: 45 },
{ a: 46 },
{ a: 47 },
{ a: 48 },
{ a: 49 },
{ a: 50 },
{ a: 51 },
{ a: 52 },
{ a: 53 },
{ a: 54 },
{ a: 55 },
{ a: 56 },
{ a: 57 },
{ a: 58 },
{ a: 59 },
{ a: 60 },
{ a: 61 },
{ a: 62 },
{ a: 63 },
{ a: 64 },
{ a: 65 },
{ a: 66 },
{ a: 67 },
{ a: 68 },
{ a: 69 },
{ a: 70 },
{ a: 71 },
{ a: 72 },
{ a: 73 },
{ a: 74 },
{ a: 75 },
{ a: 76 },
{ a: 77 },
{ a: 78 },
{ a: 79 },
{ a: 80 },
{ a: 81 },
{ a: 82 },
{ a: 83 },
{ a: 84 },
{ a: 85 },
{ a: 86 },
{ a: 87 },
{ a: 88 },
{ a: 89 },
{ a: 90 },
{ a: 91 },
{ a: 92 },
{ a: 93 },
{ a: 94 },
{ a: 95 },
{ a: 96 },
{ a: 97 },
{ a: 98 },
{ a: 99 },
);
});
await run();

View File

@@ -1,156 +0,0 @@
import { bench, group, run } from "../runner.mjs";
const runAll = !process.argv.includes("--simple");
const small = new Uint8Array(1024);
const medium = new Uint8Array(1024 * 100);
const large = new Uint8Array(1024 * 1024);
for (let i = 0; i < large.length; i++) {
const value = Math.floor(Math.sin(i / 100) * 128 + 128);
if (i < small.length) small[i] = value;
if (i < medium.length) medium[i] = value;
large[i] = value;
}
const format = new Intl.NumberFormat("en-US", { notation: "compact", unit: "byte" });
async function compress(data, format) {
const cs = new CompressionStream(format);
const writer = cs.writable.getWriter();
const reader = cs.readable.getReader();
writer.write(data);
writer.close();
const chunks = [];
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value);
}
const result = new Uint8Array(chunks.reduce((acc, chunk) => acc + chunk.length, 0));
let offset = 0;
for (const chunk of chunks) {
result.set(chunk, offset);
offset += chunk.length;
}
return result;
}
async function decompress(data, format) {
const ds = new DecompressionStream(format);
const writer = ds.writable.getWriter();
const reader = ds.readable.getReader();
writer.write(data);
writer.close();
const chunks = [];
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value);
}
const result = new Uint8Array(chunks.reduce((acc, chunk) => acc + chunk.length, 0));
let offset = 0;
for (const chunk of chunks) {
result.set(chunk, offset);
offset += chunk.length;
}
return result;
}
async function roundTrip(data, format) {
const compressed = await compress(data, format);
return await decompress(compressed, format);
}
const formats = ["deflate", "gzip", "deflate-raw"];
if (runAll) formats.push("brotli", "zstd");
// Small data benchmarks (1KB)
group(`CompressionStream ${format.format(small.length)}`, () => {
for (const fmt of formats) {
try {
new CompressionStream(fmt);
bench(fmt, async () => await compress(small, fmt));
} catch (e) {
// Skip unsupported formats
}
}
});
// Medium data benchmarks (100KB)
group(`CompressionStream ${format.format(medium.length)}`, () => {
for (const fmt of formats) {
try {
new CompressionStream(fmt);
bench(fmt, async () => await compress(medium, fmt));
} catch (e) {}
}
});
// Large data benchmarks (1MB)
group(`CompressionStream ${format.format(large.length)}`, () => {
for (const fmt of formats) {
try {
new CompressionStream(fmt);
bench(fmt, async () => await compress(large, fmt));
} catch (e) {
// Skip unsupported formats
}
}
});
const compressedData = {};
for (const fmt of formats) {
try {
compressedData[fmt] = {
small: await compress(small, fmt),
medium: await compress(medium, fmt),
large: await compress(large, fmt),
};
} catch (e) {
// Skip unsupported formats
}
}
group(`DecompressionStream ${format.format(small.length)}`, () => {
for (const fmt of formats) {
if (compressedData[fmt]) {
bench(fmt, async () => await decompress(compressedData[fmt].small, fmt));
}
}
});
group(`DecompressionStream ${format.format(medium.length)}`, () => {
for (const fmt of formats) {
if (compressedData[fmt]) {
bench(fmt, async () => await decompress(compressedData[fmt].medium, fmt));
}
}
});
group(`DecompressionStream ${format.format(large.length)}`, () => {
for (const fmt of formats) {
if (compressedData[fmt]) {
bench(fmt, async () => await decompress(compressedData[fmt].large, fmt));
}
}
});
group(`roundtrip ${format.format(large.length)}`, () => {
for (const fmt of formats) {
try {
new CompressionStream(fmt);
bench(fmt, async () => await roundTrip(large, fmt));
} catch (e) {
// Skip unsupported formats
}
}
});
await run();

View File

@@ -1,4 +0,0 @@
// Child process for IPC benchmarks - echoes messages back to parent
process.on("message", message => {
process.send(message);
});

View File

@@ -1,45 +0,0 @@
import { fork } from "node:child_process";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { bench, run } from "../runner.mjs";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const childPath = path.join(__dirname, "ipc-json-child.mjs");
const smallMessage = { type: "ping", id: 1 };
const largeString = Buffer.alloc(10 * 1024 * 1024, "A").toString();
const largeMessage = { type: "ping", id: 1, data: largeString };
async function runBenchmark(message, count) {
let received = 0;
const { promise, resolve } = Promise.withResolvers();
const child = fork(childPath, [], {
stdio: ["ignore", "ignore", "ignore", "ipc"],
serialization: "json",
});
child.on("message", () => {
received++;
if (received >= count) {
resolve();
}
});
for (let i = 0; i < count; i++) {
child.send(message);
}
await promise;
child.kill();
}
bench("ipc json - small messages (1000 roundtrips)", async () => {
await runBenchmark(smallMessage, 1000);
});
bench("ipc json - 10MB messages (10 roundtrips)", async () => {
await runBenchmark(largeMessage, 10);
});
await run();

View File

@@ -1,57 +0,0 @@
import { bench, run } from "../runner.mjs";
const obj = { a: 1, b: 2, c: 3 };
const objDeep = { a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8 };
const sym = Symbol("test");
const objWithSymbol = { [sym]: 1, a: 2 };
const objs = [
{ f: 50 },
{ f: 50, g: 70 },
{ g: 50, f: 70 },
{ h: 50, f: 70 },
{ z: 50, f: 70 },
{ k: 50, f: 70 },
];
bench("Object.hasOwn - hit", () => {
return Object.hasOwn(obj, "a");
});
bench("Object.hasOwn - miss", () => {
return Object.hasOwn(obj, "z");
});
bench("Object.hasOwn - symbol hit", () => {
return Object.hasOwn(objWithSymbol, sym);
});
bench("Object.hasOwn - symbol miss", () => {
return Object.hasOwn(objWithSymbol, Symbol("other"));
});
bench("Object.hasOwn - multiple shapes", () => {
let result = true;
for (let i = 0; i < objs.length; i++) {
result = Object.hasOwn(objs[i], "f") && result;
}
return result;
});
bench("Object.prototype.hasOwnProperty - hit", () => {
return obj.hasOwnProperty("a");
});
bench("Object.prototype.hasOwnProperty - miss", () => {
return obj.hasOwnProperty("z");
});
bench("in operator - hit", () => {
return "a" in obj;
});
bench("in operator - miss", () => {
return "z" in obj;
});
await run();

View File

@@ -1,7 +0,0 @@
import { bench, run } from "../runner.mjs";
bench("Promise.race([p1, p2])", async function () {
return await Promise.race([Promise.resolve(1), Promise.resolve(2)]);
});
await run();

View File

@@ -11,10 +11,10 @@ const builtin = ${JSON.stringify(builtin)};
const now = performance.now();
require(builtin);
const end = performance.now();
process.stdout.write(JSON.stringify({ builtin, time: end - now }) + "\\n");
process.stdout.write(JSON.stringify({builtin, time: end - now}) + "\\n");
`,
);
spawnSync(process.execPath, [path], {
const result = spawnSync(typeof Bun !== "undefined" ? "bun" : "node", [path], {
stdio: ["inherit", "inherit", "inherit"],
env: {
...process.env,

View File

@@ -112,40 +112,12 @@ const obj = {
},
};
const smallObj = { id: 1, name: "test" };
const arrayObj = {
items: Array.from({ length: 100 }, (_, i) => ({ id: i, value: `item-${i}` })),
};
bench("Response.json(obj)", () => {
bench("Response.json(obj)", async () => {
return Response.json(obj);
});
bench("new Response(JSON.stringify(obj))", () => {
return new Response(JSON.stringify(obj), {
headers: { "Content-Type": "application/json" },
});
});
bench("Response.json(smallObj)", () => {
return Response.json(smallObj);
});
bench("new Response(JSON.stringify(smallObj))", () => {
return new Response(JSON.stringify(smallObj), {
headers: { "Content-Type": "application/json" },
});
});
bench("Response.json(arrayObj)", () => {
return Response.json(arrayObj);
});
bench("new Response(JSON.stringify(arrayObj))", () => {
return new Response(JSON.stringify(arrayObj), {
headers: { "Content-Type": "application/json" },
});
bench("Response.json(obj).json()", async () => {
return await Response.json(obj).json();
});
await run();

View File

@@ -1,34 +0,0 @@
import { bench, run } from "../runner.mjs";
const shortStr = "The quick brown fox jumps over the lazy dog";
const longStr = shortStr.repeat(100);
bench("String.includes - short, hit (middle)", () => {
return shortStr.includes("jumps");
});
bench("String.includes - short, hit (start)", () => {
return shortStr.includes("The");
});
bench("String.includes - short, hit (end)", () => {
return shortStr.includes("dog");
});
bench("String.includes - short, miss", () => {
return shortStr.includes("cat");
});
bench("String.includes - long, hit (middle)", () => {
return longStr.includes("jumps");
});
bench("String.includes - long, miss", () => {
return longStr.includes("cat");
});
bench("String.includes - with position", () => {
return shortStr.includes("fox", 10);
});
await run();

View File

@@ -1,48 +0,0 @@
import { bench, group, run } from "../runner.mjs";
const patterns = [
{ name: "string pattern", input: "https://(sub.)?example(.com/)foo" },
{ name: "hostname IDN", input: { hostname: "xn--caf-dma.com" } },
{
name: "pathname + search + hash + baseURL",
input: {
pathname: "/foo",
search: "bar",
hash: "baz",
baseURL: "https://example.com:8080",
},
},
{ name: "pathname with regex", input: { pathname: "/([[a-z]--a])" } },
{ name: "named groups", input: { pathname: "/users/:id/posts/:postId" } },
{ name: "wildcard", input: { pathname: "/files/*" } },
];
const testURL = "https://sub.example.com/foo";
group("URLPattern parse (constructor)", () => {
for (const { name, input } of patterns) {
bench(name, () => {
return new URLPattern(input);
});
}
});
group("URLPattern.test()", () => {
for (const { name, input } of patterns) {
const pattern = new URLPattern(input);
bench(name, () => {
return pattern.test(testURL);
});
}
});
group("URLPattern.exec()", () => {
for (const { name, input } of patterns) {
const pattern = new URLPattern(input);
bench(name, () => {
return pattern.exec(testURL);
});
}
});
await run();

View File

@@ -18,6 +18,22 @@ const OperatingSystem = @import("src/env.zig").OperatingSystem;
const pathRel = fs.path.relative;
/// When updating this, make sure to adjust SetupZig.cmake
const recommended_zig_version = "0.14.0";
// comptime {
// if (!std.mem.eql(u8, builtin.zig_version_string, recommended_zig_version)) {
// @compileError(
// "" ++
// "Bun requires Zig version " ++ recommended_zig_version ++ ", but you have " ++
// builtin.zig_version_string ++ ". This is automatically configured via Bun's " ++
// "CMake setup. You likely meant to run `bun run build`. If you are trying to " ++
// "upgrade the Zig compiler, edit ZIG_COMMIT in cmake/tools/SetupZig.cmake or " ++
// "comment this error out.",
// );
// }
// }
const zero_sha = "0000000000000000000000000000000000000000";
const BunBuildOptions = struct {
@@ -32,7 +48,6 @@ const BunBuildOptions = struct {
/// enable debug logs in release builds
enable_logs: bool = false,
enable_asan: bool,
enable_fuzzilli: bool,
enable_valgrind: bool,
use_mimalloc: bool,
tracy_callstack_depth: u16,
@@ -82,10 +97,9 @@ const BunBuildOptions = struct {
opts.addOption(bool, "baseline", this.isBaseline());
opts.addOption(bool, "enable_logs", this.enable_logs);
opts.addOption(bool, "enable_asan", this.enable_asan);
opts.addOption(bool, "enable_fuzzilli", this.enable_fuzzilli);
opts.addOption(bool, "enable_valgrind", this.enable_valgrind);
opts.addOption(bool, "use_mimalloc", this.use_mimalloc);
opts.addOption([]const u8, "reported_nodejs_version", b.fmt("{f}", .{this.reported_nodejs_version}));
opts.addOption([]const u8, "reported_nodejs_version", b.fmt("{}", .{this.reported_nodejs_version}));
opts.addOption(bool, "zig_self_hosted_backend", this.no_llvm);
opts.addOption(bool, "override_no_export_cpp_apis", this.override_no_export_cpp_apis);
@@ -120,8 +134,8 @@ pub fn getOSVersionMin(os: OperatingSystem) ?Target.Query.OsVersion {
pub fn getOSGlibCVersion(os: OperatingSystem) ?Version {
return switch (os) {
// Compiling with a newer glibc than this will break certain cloud environments. See symbols.test.ts.
.linux => .{ .major = 2, .minor = 26, .patch = 0 },
// Compiling with a newer glibc than this will break certain cloud environments.
.linux => .{ .major = 2, .minor = 27, .patch = 0 },
else => null,
};
@@ -257,7 +271,6 @@ pub fn build(b: *Build) !void {
.tracy_callstack_depth = b.option(u16, "tracy_callstack_depth", "") orelse 10,
.enable_logs = b.option(bool, "enable_logs", "Enable logs in release") orelse false,
.enable_asan = b.option(bool, "enable_asan", "Enable asan") orelse false,
.enable_fuzzilli = b.option(bool, "enable_fuzzilli", "Enable fuzzilli instrumentation") orelse false,
.enable_valgrind = b.option(bool, "enable_valgrind", "Enable valgrind") orelse false,
.use_mimalloc = b.option(bool, "use_mimalloc", "Use mimalloc as default allocator") orelse false,
.llvm_codegen_threads = b.option(u32, "llvm_codegen_threads", "Number of threads to use for LLVM codegen") orelse 1,
@@ -277,16 +290,14 @@ pub fn build(b: *Build) !void {
var o = build_options;
var unit_tests = b.addTest(.{
.name = "bun-test",
.optimize = build_options.optimize,
.root_source_file = b.path("src/unit_test.zig"),
.test_runner = .{ .path = b.path("src/main_test.zig"), .mode = .simple },
.root_module = b.createModule(.{
.optimize = build_options.optimize,
.root_source_file = b.path("src/unit_test.zig"),
.target = build_options.target,
.omit_frame_pointer = false,
.strip = false,
}),
.target = build_options.target,
.use_llvm = !build_options.no_llvm,
.use_lld = if (build_options.os == .mac) false else !build_options.no_llvm,
.omit_frame_pointer = false,
.strip = false,
});
configureObj(b, &o, unit_tests);
// Setting `linker_allow_shlib_undefined` causes the linker to ignore
@@ -320,7 +331,6 @@ pub fn build(b: *Build) !void {
var step = b.step("check", "Check for semantic analysis errors");
var bun_check_obj = addBunObject(b, &build_options);
bun_check_obj.generated_bin = null;
// bun_check_obj.use_llvm = false;
step.dependOn(&bun_check_obj.step);
// The default install step will run zig build check. This is so ZLS
@@ -493,7 +503,6 @@ fn addMultiCheck(
.no_llvm = root_build_options.no_llvm,
.enable_asan = root_build_options.enable_asan,
.enable_valgrind = root_build_options.enable_valgrind,
.enable_fuzzilli = root_build_options.enable_fuzzilli,
.use_mimalloc = root_build_options.use_mimalloc,
.override_no_export_cpp_apis = root_build_options.override_no_export_cpp_apis,
};
@@ -607,22 +616,15 @@ fn configureObj(b: *Build, opts: *BunBuildOptions, obj: *Compile) void {
obj.llvm_codegen_threads = opts.llvm_codegen_threads orelse 0;
}
obj.no_link_obj = opts.os != .windows and !opts.no_llvm;
obj.no_link_obj = true;
if (opts.enable_asan and !enableFastBuild(b)) {
if (@hasField(Build.Module, "sanitize_address")) {
if (opts.enable_fuzzilli) {
obj.sanitize_coverage_trace_pc_guard = true;
}
obj.root_module.sanitize_address = true;
} else {
const fail_step = b.addFail("asan is not supported on this platform");
obj.step.dependOn(&fail_step.step);
}
} else if (opts.enable_fuzzilli) {
const fail_step = b.addFail("fuzzilli requires asan");
obj.step.dependOn(&fail_step.step);
}
obj.bundle_compiler_rt = false;
obj.bundle_ubsan_rt = false;
@@ -777,13 +779,6 @@ fn addInternalImports(b: *Build, mod: *Module, opts: *BunBuildOptions) void {
mod.addImport("cpp", cppImport);
cppImport.addImport("bun", mod);
}
{
const ciInfoImport = b.createModule(.{
.root_source_file = (std.Build.LazyPath{ .cwd_relative = opts.codegen_path }).path(b, "ci_info.zig"),
});
mod.addImport("ci_info", ciInfoImport);
ciInfoImport.addImport("bun", mod);
}
inline for (.{
.{ .import = "completions-bash", .file = b.path("completions/bun.bash") },
.{ .import = "completions-zsh", .file = b.path("completions/bun.zsh") },
@@ -809,7 +804,7 @@ fn addInternalImports(b: *Build, mod: *Module, opts: *BunBuildOptions) void {
fn propagateImports(source_mod: *Module) !void {
var seen = std.AutoHashMap(*Module, void).init(source_mod.owner.graph.arena);
defer seen.deinit();
var queue = std.array_list.Managed(*Module).init(source_mod.owner.graph.arena);
var queue = std.ArrayList(*Module).init(source_mod.owner.graph.arena);
defer queue.deinit();
try queue.appendSlice(source_mod.import_table.values());
while (queue.pop()) |mod| {

View File

@@ -1,6 +1,5 @@
{
"lockfileVersion": 1,
"configVersion": 1,
"workspaces": {
"": {
"name": "bun",
@@ -32,11 +31,17 @@
"dependencies": {
"@types/node": "*",
},
"devDependencies": {
"@types/react": "^19",
},
"peerDependencies": {
"@types/react": "^19",
},
},
},
"overrides": {
"@types/bun": "workspace:packages/@types/bun",
"@types/node": "25.0.0",
"@types/node": "24.3.1",
"bun-types": "workspace:packages/bun-types",
},
"packages": {
@@ -86,13 +91,13 @@
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.21.5", "", { "os": "win32", "cpu": "x64" }, "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw=="],
"@lezer/common": ["@lezer/common@1.3.0", "", {}, "sha512-L9X8uHCYU310o99L3/MpJKYxPzXPOS7S0NmBaM7UO/x2Kb2WbmMLSkfvdr1KxRIFYOpbY0Jhn7CfLSUDzL8arQ=="],
"@lezer/common": ["@lezer/common@1.2.3", "", {}, "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA=="],
"@lezer/cpp": ["@lezer/cpp@1.1.3", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-ykYvuFQKGsRi6IcE+/hCSGUhb/I4WPjd3ELhEblm2wS2cOznDFzO+ubK2c+ioysOnlZ3EduV+MVQFCPzAIoY3w=="],
"@lezer/highlight": ["@lezer/highlight@1.2.3", "", { "dependencies": { "@lezer/common": "^1.3.0" } }, "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g=="],
"@lezer/highlight": ["@lezer/highlight@1.2.1", "", { "dependencies": { "@lezer/common": "^1.0.0" } }, "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA=="],
"@lezer/lr": ["@lezer/lr@1.4.3", "", { "dependencies": { "@lezer/common": "^1.0.0" } }, "sha512-yenN5SqAxAPv/qMnpWW0AT7l+SxVrgG+u0tNsRQWqbrz66HIl8DnEbBObvy21J5K7+I1v7gsAnlE2VQ5yYVSeA=="],
"@lezer/lr": ["@lezer/lr@1.4.2", "", { "dependencies": { "@lezer/common": "^1.0.0" } }, "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA=="],
"@octokit/app": ["@octokit/app@14.1.0", "", { "dependencies": { "@octokit/auth-app": "^6.0.0", "@octokit/auth-unauthenticated": "^5.0.0", "@octokit/core": "^5.0.0", "@octokit/oauth-app": "^6.0.0", "@octokit/plugin-paginate-rest": "^9.0.0", "@octokit/types": "^12.0.0", "@octokit/webhooks": "^12.0.4" } }, "sha512-g3uEsGOQCBl1+W1rgfwoRFUIR6PtvB2T1E4RpygeUU5LrLvlOqcxrt5lfykIeRpUPpupreGJUYl70fqMDXdTpw=="],
@@ -146,7 +151,7 @@
"@sentry/types": ["@sentry/types@7.120.4", "", {}, "sha512-cUq2hSSe6/qrU6oZsEP4InMI5VVdD86aypE+ENrQ6eZEVLTCYm1w6XhW1NvIu3UuWh7gZec4a9J7AFpYxki88Q=="],
"@types/aws-lambda": ["@types/aws-lambda@8.10.159", "", {}, "sha512-SAP22WSGNN12OQ8PlCzGzRCZ7QDCwI85dQZbmpz7+mAk+L7j+wI7qnvmdKh+o7A5LaOp6QnOZ2NJphAZQTTHQg=="],
"@types/aws-lambda": ["@types/aws-lambda@8.10.152", "", {}, "sha512-soT/c2gYBnT5ygwiHPmd9a1bftj462NWVk2tKCc1PYHSIacB2UwbTS2zYG4jzag1mRDuzg/OjtxQjQ2NKRB6Rw=="],
"@types/btoa-lite": ["@types/btoa-lite@1.0.2", "", {}, "sha512-ZYbcE2x7yrvNFJiU7xJGrpF/ihpkM7zKgw8bha3LNJSesvTtUNxbpzaT7WXBIryf6jovisrxTBvymxMeLLj1Mg=="],
@@ -156,7 +161,9 @@
"@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
"@types/node": ["@types/node@25.0.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-rl78HwuZlaDIUSeUKkmogkhebA+8K1Hy7tddZuJ3D0xV8pZSfsYGTsliGUol1JPzu9EKnTxPC4L1fiWouStRew=="],
"@types/node": ["@types/node@24.3.1", "", { "dependencies": { "undici-types": "~7.10.0" } }, "sha512-3vXmQDXy+woz+gnrTvuvNrPzekOi+Ds0ReMxw0LzBiK3a+1k0kQn9f2NWk+lgD4rJehFUmYy2gMhJ2ZI+7YP9g=="],
"@types/react": ["@types/react@19.1.10", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-EhBeSYX0Y6ye8pNebpKrwFJq7BoQ8J5SO6NlvNwwHjSj6adXJViPQrKlsyPw7hLBLvckEMO1yxeGdR82YBBlDg=="],
"aggregate-error": ["aggregate-error@3.1.0", "", { "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" } }, "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA=="],
@@ -186,9 +193,11 @@
"constant-case": ["constant-case@3.0.4", "", { "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3", "upper-case": "^2.0.2" } }, "sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ=="],
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
"deprecation": ["deprecation@2.3.1", "", {}, "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ=="],
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
"detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="],
"dot-case": ["dot-case@3.0.4", "", { "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w=="],
@@ -212,29 +221,27 @@
"jws": ["jws@3.2.2", "", { "dependencies": { "jwa": "^1.4.1", "safe-buffer": "^5.0.1" } }, "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA=="],
"lightningcss": ["lightningcss@1.30.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.30.2", "lightningcss-darwin-arm64": "1.30.2", "lightningcss-darwin-x64": "1.30.2", "lightningcss-freebsd-x64": "1.30.2", "lightningcss-linux-arm-gnueabihf": "1.30.2", "lightningcss-linux-arm64-gnu": "1.30.2", "lightningcss-linux-arm64-musl": "1.30.2", "lightningcss-linux-x64-gnu": "1.30.2", "lightningcss-linux-x64-musl": "1.30.2", "lightningcss-win32-arm64-msvc": "1.30.2", "lightningcss-win32-x64-msvc": "1.30.2" } }, "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ=="],
"lightningcss": ["lightningcss@1.30.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.30.1", "lightningcss-darwin-x64": "1.30.1", "lightningcss-freebsd-x64": "1.30.1", "lightningcss-linux-arm-gnueabihf": "1.30.1", "lightningcss-linux-arm64-gnu": "1.30.1", "lightningcss-linux-arm64-musl": "1.30.1", "lightningcss-linux-x64-gnu": "1.30.1", "lightningcss-linux-x64-musl": "1.30.1", "lightningcss-win32-arm64-msvc": "1.30.1", "lightningcss-win32-x64-msvc": "1.30.1" } }, "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg=="],
"lightningcss-android-arm64": ["lightningcss-android-arm64@1.30.2", "", { "os": "android", "cpu": "arm64" }, "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A=="],
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ=="],
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA=="],
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA=="],
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ=="],
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig=="],
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA=="],
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.1", "", { "os": "linux", "cpu": "arm" }, "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q=="],
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.2", "", { "os": "linux", "cpu": "arm" }, "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA=="],
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw=="],
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A=="],
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ=="],
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA=="],
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.1", "", { "os": "linux", "cpu": "x64" }, "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw=="],
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w=="],
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.1", "", { "os": "linux", "cpu": "x64" }, "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ=="],
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA=="],
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA=="],
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ=="],
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw=="],
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.1", "", { "os": "win32", "cpu": "x64" }, "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg=="],
"lodash.includes": ["lodash.includes@4.3.0", "", {}, "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="],
@@ -290,7 +297,7 @@
"scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="],
"semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
"semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
"sentence-case": ["sentence-case@3.0.4", "", { "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3", "upper-case-first": "^2.0.2" } }, "sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg=="],
@@ -306,7 +313,7 @@
"uglify-js": ["uglify-js@3.19.3", "", { "bin": { "uglifyjs": "bin/uglifyjs" } }, "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ=="],
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
"undici-types": ["undici-types@7.10.0", "", {}, "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag=="],
"universal-github-app-jwt": ["universal-github-app-jwt@1.2.0", "", { "dependencies": { "@types/jsonwebtoken": "^9.0.0", "jsonwebtoken": "^9.0.2" } }, "sha512-dncpMpnsKBk0eetwfN8D8OUHGfiDhhJ+mtsbMl+7PfW7mYjiH8LIcqRmYMtzYLgSh47HjfdBtrBwIQ/gizKR3g=="],

View File

@@ -10,4 +10,4 @@ preload = "./test/preload.ts"
[install]
linker = "isolated"
minimumReleaseAge = 259200 # three days
minimumReleaseAge = 1

View File

@@ -51,23 +51,6 @@ if(ENABLE_ASAN)
)
endif()
if(ENABLE_FUZZILLI)
register_compiler_flags(
DESCRIPTION "Enable coverage instrumentation for fuzzing"
-fsanitize-coverage=trace-pc-guard
)
register_linker_flags(
DESCRIPTION "Link coverage instrumentation"
-fsanitize-coverage=trace-pc-guard
)
register_compiler_flags(
DESCRIPTION "Enable fuzzilli-specific code"
-DFUZZILLI_ENABLED
)
endif()
# --- Optimization level ---
if(DEBUG)
register_compiler_flags(

View File

@@ -125,8 +125,7 @@ setx(CWD ${CMAKE_SOURCE_DIR})
setx(BUILD_PATH ${CMAKE_BINARY_DIR})
optionx(CACHE_PATH FILEPATH "The path to the cache directory" DEFAULT ${BUILD_PATH}/cache)
optionx(CACHE_STRATEGY "auto|distributed|local|none" "The strategy to use for caching" DEFAULT
"auto")
optionx(CACHE_STRATEGY "read-write|read-only|write-only|none" "The strategy to use for caching" DEFAULT "read-write")
optionx(CI BOOL "If CI is enabled" DEFAULT OFF)
optionx(ENABLE_ANALYSIS BOOL "If static analysis targets should be enabled" DEFAULT OFF)
@@ -142,39 +141,9 @@ optionx(TMP_PATH FILEPATH "The path to the temporary directory" DEFAULT ${BUILD_
# --- Helper functions ---
# list_filter_out_regex()
#
# Description:
# Filters out elements from a list that match a regex pattern.
#
# Arguments:
# list - The list of strings to traverse
# pattern - The regex pattern to filter out
# touched - A variable to set if any items were removed
function(list_filter_out_regex list pattern touched)
set(result_list "${${list}}")
set(keep_list)
set(was_modified OFF)
foreach(line IN LISTS result_list)
if(line MATCHES "${pattern}")
set(was_modified ON)
else()
list(APPEND keep_list ${line})
endif()
endforeach()
set(${list} "${keep_list}" PARENT_SCOPE)
set(${touched} ${was_modified} PARENT_SCOPE)
endfunction()
# setenv()
# Description:
# Sets an environment variable during the build step, and writes it to a .env file.
#
# See Also:
# unsetenv()
#
# Arguments:
# variable string - The variable to set
# value string - The value to set the variable to
@@ -187,7 +156,13 @@ function(setenv variable value)
if(EXISTS ${ENV_PATH})
file(STRINGS ${ENV_PATH} ENV_FILE ENCODING UTF-8)
list_filter_out_regex(ENV_FILE "^${variable}=" ENV_MODIFIED)
foreach(line ${ENV_FILE})
if(line MATCHES "^${variable}=")
list(REMOVE_ITEM ENV_FILE ${line})
set(ENV_MODIFIED ON)
endif()
endforeach()
if(ENV_MODIFIED)
list(APPEND ENV_FILE "${variable}=${value}")
@@ -203,28 +178,6 @@ function(setenv variable value)
message(STATUS "Set ENV ${variable}: ${value}")
endfunction()
# See setenv()
# Description:
# Exact opposite of setenv().
# Arguments:
# variable string - The variable to unset.
# See Also:
# setenv()
function(unsetenv variable)
set(ENV_PATH ${BUILD_PATH}/.env)
if(NOT EXISTS ${ENV_PATH})
return()
endif()
file(STRINGS ${ENV_PATH} ENV_FILE ENCODING UTF-8)
list_filter_out_regex(ENV_FILE "^${variable}=" ENV_MODIFIED)
if(ENV_MODIFIED)
list(JOIN ENV_FILE "\n" ENV_FILE)
file(WRITE ${ENV_PATH} ${ENV_FILE})
endif()
endfunction()
# satisfies_range()
# Description:
# Check if a version satisfies a version range or list of ranges
@@ -357,7 +310,7 @@ function(find_command)
${FIND_VALIDATOR}
)
if(FIND_REQUIRED AND ${FIND_VARIABLE} MATCHES "NOTFOUND")
if(NOT FIND_REQUIRED STREQUAL "OFF" AND ${FIND_VARIABLE} MATCHES "NOTFOUND")
set(error "Command not found: \"${FIND_NAME}\"")
if(FIND_VERSION)

View File

@@ -127,8 +127,6 @@ if (NOT ENABLE_ASAN)
set(ENABLE_ZIG_ASAN OFF)
endif()
optionx(ENABLE_FUZZILLI BOOL "If fuzzilli support should be enabled" DEFAULT OFF)
if(RELEASE AND LINUX AND CI AND NOT ENABLE_ASSERTIONS AND NOT ENABLE_ASAN)
set(DEFAULT_LTO ON)
else()

View File

@@ -27,6 +27,10 @@
"paths": ["src/bake/*.ts", "src/bake/*/*.{ts,css}"],
"exclude": ["src/bake/generated.ts"]
},
{
"output": "BunFrameworkReactSources.txt",
"paths": ["packages/bun-framework-react/*.{ts,tsx,js,jsx}", "packages/bun-framework-react/src/**/*.{ts,tsx,js,jsx}"]
},
{
"output": "BindgenSources.txt",
"paths": ["src/**/*.bind.ts"]

View File

@@ -34,6 +34,26 @@ register_command(
ALWAYS_RUN
)
if(GIT_CHANGED_SOURCES)
set(CLANG_FORMAT_CHANGED_SOURCES)
foreach(source ${CLANG_FORMAT_SOURCES})
list(FIND GIT_CHANGED_SOURCES ${source} index)
if(NOT ${index} EQUAL -1)
list(APPEND CLANG_FORMAT_CHANGED_SOURCES ${source})
endif()
endforeach()
endif()
if(CLANG_FORMAT_CHANGED_SOURCES)
set(CLANG_FORMAT_DIFF_COMMAND ${CLANG_FORMAT_PROGRAM}
-i # edits files in-place
--verbose
${CLANG_FORMAT_CHANGED_SOURCES}
)
else()
set(CLANG_FORMAT_DIFF_COMMAND ${CMAKE_COMMAND} -E echo "No changed files for clang-format")
endif()
register_command(
TARGET
clang-format-diff

View File

@@ -3,7 +3,7 @@
set(CLANG_TIDY_SOURCES ${BUN_C_SOURCES} ${BUN_CXX_SOURCES})
set(CLANG_TIDY_COMMAND ${CLANG_TIDY_PROGRAM}
-p ${BUILD_PATH}
-p ${BUILD_PATH}
--config-file=${CWD}/.clang-tidy
)
@@ -40,6 +40,27 @@ register_command(
ALWAYS_RUN
)
if(GIT_CHANGED_SOURCES)
set(CLANG_TIDY_CHANGED_SOURCES)
foreach(source ${CLANG_TIDY_SOURCES})
list(FIND GIT_CHANGED_SOURCES ${source} index)
if(NOT ${index} EQUAL -1)
list(APPEND CLANG_TIDY_CHANGED_SOURCES ${source})
endif()
endforeach()
endif()
if(CLANG_TIDY_CHANGED_SOURCES)
set(CLANG_TIDY_DIFF_COMMAND ${CLANG_TIDY_PROGRAM}
${CLANG_TIDY_CHANGED_SOURCES}
--fix
--fix-errors
--fix-notes
)
else()
set(CLANG_TIDY_DIFF_COMMAND ${CMAKE_COMMAND} -E echo "No changed files for clang-tidy")
endif()
register_command(
TARGET
clang-tidy-diff

View File

@@ -92,6 +92,26 @@ register_command(
ALWAYS_RUN
)
if(GIT_CHANGED_SOURCES)
set(PRETTIER_CHANGED_SOURCES)
foreach(source ${PRETTIER_SOURCES})
list(FIND GIT_CHANGED_SOURCES ${source} index)
if(NOT ${index} EQUAL -1)
list(APPEND PRETTIER_CHANGED_SOURCES ${source})
endif()
endforeach()
endif()
if(PRETTIER_CHANGED_SOURCES)
set(PRETTIER_DIFF_COMMAND ${PRETTIER_COMMAND}
--write
--plugin=prettier-plugin-organize-imports
${PRETTIER_CHANGED_SOURCES}
)
else()
set(PRETTIER_DIFF_COMMAND ${CMAKE_COMMAND} -E echo "No changed files for prettier")
endif()
register_command(
TARGET
prettier-diff

View File

@@ -25,6 +25,25 @@ register_command(
ALWAYS_RUN
)
if(GIT_CHANGED_SOURCES)
set(ZIG_FORMAT_CHANGED_SOURCES)
foreach(source ${ZIG_FORMAT_SOURCES})
list(FIND GIT_CHANGED_SOURCES ${source} index)
if(NOT ${index} EQUAL -1)
list(APPEND ZIG_FORMAT_CHANGED_SOURCES ${source})
endif()
endforeach()
endif()
if(ZIG_FORMAT_CHANGED_SOURCES)
set(ZIG_FORMAT_DIFF_COMMAND ${ZIG_EXECUTABLE}
fmt
${ZIG_FORMAT_CHANGED_SOURCES}
)
else()
set(ZIG_FORMAT_DIFF_COMMAND ${CMAKE_COMMAND} -E echo "No changed files for zig-format")
endif()
register_command(
TARGET
zig-format-diff

View File

@@ -317,10 +317,6 @@ set(BUN_CPP_OUTPUTS
${CODEGEN_PATH}/cpp.zig
)
set(BUN_CI_INFO_OUTPUTS
${CODEGEN_PATH}/ci_info.zig
)
register_command(
TARGET
bun-cppbind
@@ -338,21 +334,6 @@ register_command(
${BUN_CPP_OUTPUTS}
)
register_command(
TARGET
bun-ci-info
COMMENT
"Generating CI info"
COMMAND
${BUN_EXECUTABLE}
${CWD}/src/codegen/ci_info.ts
${CODEGEN_PATH}/ci_info.zig
SOURCES
${BUN_JAVASCRIPT_CODEGEN_SOURCES}
OUTPUTS
${BUN_CI_INFO_OUTPUTS}
)
register_command(
TARGET
bun-js-modules
@@ -419,9 +400,12 @@ execute_process(
--command=list-outputs
--sources=${BUN_BINDGENV2_SOURCES_COMMA_SEPARATED}
--codegen-path=${CODEGEN_PATH}
RESULT_VARIABLE bindgen_result
OUTPUT_VARIABLE bindgen_outputs
COMMAND_ERROR_IS_FATAL ANY
)
if(${bindgen_result})
message(FATAL_ERROR "bindgenv2/script.ts exited with non-zero status")
endif()
foreach(output IN LISTS bindgen_outputs)
if(output MATCHES "\.cpp$")
list(APPEND BUN_BINDGENV2_CPP_OUTPUTS ${output})
@@ -628,7 +612,6 @@ set(BUN_ZIG_GENERATED_SOURCES
${BUN_ZIG_GENERATED_CLASSES_OUTPUTS}
${BUN_JAVASCRIPT_OUTPUTS}
${BUN_CPP_OUTPUTS}
${BUN_CI_INFO_OUTPUTS}
${BUN_BINDGENV2_ZIG_OUTPUTS}
)
@@ -692,7 +675,6 @@ register_command(
-Dcpu=${ZIG_CPU}
-Denable_logs=$<IF:$<BOOL:${ENABLE_LOGS}>,true,false>
-Denable_asan=$<IF:$<BOOL:${ENABLE_ZIG_ASAN}>,true,false>
-Denable_fuzzilli=$<IF:$<BOOL:${ENABLE_FUZZILLI}>,true,false>
-Denable_valgrind=$<IF:$<BOOL:${ENABLE_VALGRIND}>,true,false>
-Duse_mimalloc=$<IF:$<BOOL:${USE_MIMALLOC_AS_DEFAULT_ALLOCATOR}>,true,false>
-Dllvm_codegen_threads=${LLVM_ZIG_CODEGEN_THREADS}
@@ -869,7 +851,6 @@ target_include_directories(${bun} PRIVATE
${CODEGEN_PATH}
${VENDOR_PATH}
${VENDOR_PATH}/picohttpparser
${VENDOR_PATH}/zlib
${NODEJS_HEADERS_PATH}/include
${NODEJS_HEADERS_PATH}/include/node
)
@@ -1197,29 +1178,6 @@ set_target_properties(${bun} PROPERTIES LINK_DEPENDS ${BUN_SYMBOLS_PATH})
include(SetupWebKit)
if(BUN_LINK_ONLY)
register_command(
TARGET
${bun}
TARGET_PHASE
POST_BUILD
COMMENT
"Uploading link metadata"
COMMAND
${CMAKE_COMMAND} -E env
BUN_VERSION=${VERSION}
WEBKIT_DOWNLOAD_URL=${WEBKIT_DOWNLOAD_URL}
WEBKIT_VERSION=${WEBKIT_VERSION}
ZIG_COMMIT=${ZIG_COMMIT}
${BUN_EXECUTABLE} ${CWD}/scripts/create-link-metadata.mjs ${BUILD_PATH} ${bun}
SOURCES
${BUN_ZIG_OUTPUT}
${BUN_CPP_OUTPUT}
ARTIFACTS
${BUILD_PATH}/link-metadata.json
)
endif()
if(WIN32)
if(DEBUG)
target_link_libraries(${bun} PRIVATE
@@ -1294,15 +1252,9 @@ if(LINUX)
target_link_libraries(${bun} PUBLIC libatomic.so)
endif()
if(USE_WEBKIT_ICU)
target_link_libraries(${bun} PRIVATE ${WEBKIT_LIB_PATH}/libicudata.a)
target_link_libraries(${bun} PRIVATE ${WEBKIT_LIB_PATH}/libicui18n.a)
target_link_libraries(${bun} PRIVATE ${WEBKIT_LIB_PATH}/libicuuc.a)
else()
# Use system ICU libraries
find_package(ICU REQUIRED COMPONENTS data i18n uc)
target_link_libraries(${bun} PRIVATE ICU::data ICU::i18n ICU::uc)
endif()
target_link_libraries(${bun} PRIVATE ${WEBKIT_LIB_PATH}/libicudata.a)
target_link_libraries(${bun} PRIVATE ${WEBKIT_LIB_PATH}/libicui18n.a)
target_link_libraries(${bun} PRIVATE ${WEBKIT_LIB_PATH}/libicuuc.a)
endif()
if(WIN32)

View File

@@ -4,7 +4,7 @@ register_repository(
REPOSITORY
c-ares/c-ares
COMMIT
3ac47ee46edd8ea40370222f91613fc16c434853
d3a507e920e7af18a5efb7f9f1d8044ed4750013
)
register_cmake_command(

View File

@@ -48,9 +48,6 @@ if(NOT BUILDKITE_BUILD_STATUS EQUAL 0)
endif()
file(READ ${BUILDKITE_BUILD_PATH}/build.json BUILDKITE_BUILD)
# Escape backslashes so CMake doesn't interpret JSON escape sequences (e.g., \n in commit messages)
string(REPLACE "\\" "\\\\" BUILDKITE_BUILD "${BUILDKITE_BUILD}")
string(JSON BUILDKITE_BUILD_UUID GET ${BUILDKITE_BUILD} id)
string(JSON BUILDKITE_JOBS GET ${BUILDKITE_BUILD} jobs)
string(JSON BUILDKITE_JOBS_COUNT LENGTH ${BUILDKITE_JOBS})

View File

@@ -5,12 +5,18 @@ if(NOT ENABLE_CCACHE OR CACHE_STRATEGY STREQUAL "none")
return()
endif()
if (CI AND NOT APPLE)
setenv(CCACHE_DISABLE 1)
return()
endif()
find_command(
VARIABLE
CCACHE_PROGRAM
COMMAND
ccache
REQUIRED
${CI}
)
if(NOT CCACHE_PROGRAM)
@@ -43,6 +49,3 @@ else()
setenv(CCACHE_MAXSIZE 100G)
setenv(CCACHE_SLOPPINESS "pch_defines,time_macros,locale,random_seed,clang_index_store,gcno_cwd")
endif()

View File

@@ -4,9 +4,41 @@ find_command(
COMMAND
git
REQUIRED
${CI}
OFF
)
if(NOT GIT_PROGRAM)
return()
endif()
set(GIT_DIFF_COMMAND ${GIT_PROGRAM} diff --no-color --name-only --diff-filter=AMCR origin/main HEAD)
execute_process(
COMMAND
${GIT_DIFF_COMMAND}
WORKING_DIRECTORY
${CWD}
OUTPUT_STRIP_TRAILING_WHITESPACE
OUTPUT_VARIABLE
GIT_DIFF
ERROR_STRIP_TRAILING_WHITESPACE
ERROR_VARIABLE
GIT_DIFF_ERROR
RESULT_VARIABLE
GIT_DIFF_RESULT
)
if(NOT GIT_DIFF_RESULT EQUAL 0)
message(WARNING "Command failed: ${GIT_DIFF_COMMAND} ${GIT_DIFF_ERROR}")
return()
endif()
string(REPLACE "\n" ";" GIT_CHANGED_SOURCES "${GIT_DIFF}")
if(CI)
set(GIT_CHANGED_SOURCES "${GIT_CHANGED_SOURCES}")
message(STATUS "Set GIT_CHANGED_SOURCES: ${GIT_CHANGED_SOURCES}")
endif()
list(TRANSFORM GIT_CHANGED_SOURCES PREPEND ${CWD}/)
list(LENGTH GIT_CHANGED_SOURCES GIT_CHANGED_SOURCES_COUNT)

View File

@@ -2,7 +2,7 @@ option(WEBKIT_VERSION "The version of WebKit to use")
option(WEBKIT_LOCAL "If a local version of WebKit should be used instead of downloading")
if(NOT WEBKIT_VERSION)
set(WEBKIT_VERSION preview-pr-132-c6592bfb)
set(WEBKIT_VERSION 6d0f3aac0b817cc01a846b3754b21271adedac12)
endif()
string(SUBSTRING ${WEBKIT_VERSION} 0 16 WEBKIT_VERSION_PREFIX)
@@ -28,13 +28,12 @@ if(WEBKIT_LOCAL)
# make jsc-compile-debug jsc-copy-headers
include_directories(
${WEBKIT_PATH}
${WEBKIT_PATH}/JavaScriptCore/Headers
${WEBKIT_PATH}/JavaScriptCore/Headers/JavaScriptCore
${WEBKIT_PATH}/JavaScriptCore/PrivateHeaders
${WEBKIT_PATH}/JavaScriptCore/PrivateHeaders/JavaScriptCore
${WEBKIT_PATH}/bmalloc/Headers
${WEBKIT_PATH}/WTF/Headers
${WEBKIT_PATH}/JavaScriptCore/DerivedSources/inspector
${WEBKIT_PATH}/JavaScriptCore/PrivateHeaders/JavaScriptCore
)
endif()
@@ -91,14 +90,7 @@ if(EXISTS ${WEBKIT_PATH}/package.json)
endif()
endif()
file(
DOWNLOAD ${WEBKIT_DOWNLOAD_URL} ${CACHE_PATH}/${WEBKIT_FILENAME} SHOW_PROGRESS
STATUS WEBKIT_DOWNLOAD_STATUS
)
if(NOT "${WEBKIT_DOWNLOAD_STATUS}" MATCHES "^0;")
message(FATAL_ERROR "Failed to download WebKit: ${WEBKIT_DOWNLOAD_STATUS}")
endif()
file(DOWNLOAD ${WEBKIT_DOWNLOAD_URL} ${CACHE_PATH}/${WEBKIT_FILENAME} SHOW_PROGRESS)
file(ARCHIVE_EXTRACT INPUT ${CACHE_PATH}/${WEBKIT_FILENAME} DESTINATION ${CACHE_PATH} TOUCH)
file(REMOVE ${CACHE_PATH}/${WEBKIT_FILENAME})
file(REMOVE_RECURSE ${WEBKIT_PATH})

View File

@@ -20,7 +20,7 @@ else()
unsupported(CMAKE_SYSTEM_NAME)
endif()
set(ZIG_COMMIT "c1423ff3fc7064635773a4a4616c5bf986eb00fe")
set(ZIG_COMMIT "55fdbfa0c86be86b68d43a4ba761e6909eb0d7b2")
optionx(ZIG_TARGET STRING "The zig target to use" DEFAULT ${DEFAULT_ZIG_TARGET})
if(CMAKE_BUILD_TYPE STREQUAL "Release")
@@ -55,7 +55,13 @@ optionx(ZIG_OBJECT_FORMAT "obj|bc" "Output file format for Zig object files" DEF
optionx(ZIG_LOCAL_CACHE_DIR FILEPATH "The path to local the zig cache directory" DEFAULT ${CACHE_PATH}/zig/local)
optionx(ZIG_GLOBAL_CACHE_DIR FILEPATH "The path to the global zig cache directory" DEFAULT ${CACHE_PATH}/zig/global)
optionx(ZIG_COMPILER_SAFE BOOL "Download a ReleaseSafe build of the Zig compiler." DEFAULT ${CI})
if(CI)
set(ZIG_COMPILER_SAFE_DEFAULT ON)
else()
set(ZIG_COMPILER_SAFE_DEFAULT OFF)
endif()
optionx(ZIG_COMPILER_SAFE BOOL "Download a ReleaseSafe build of the Zig compiler." DEFAULT ${ZIG_COMPILER_SAFE_DEFAULT})
setenv(ZIG_LOCAL_CACHE_DIR ${ZIG_LOCAL_CACHE_DIR})
setenv(ZIG_GLOBAL_CACHE_DIR ${ZIG_GLOBAL_CACHE_DIR})

View File

@@ -35,8 +35,8 @@ end
set -l bun_install_boolean_flags yarn production optional development no-save dry-run force no-cache silent verbose global
set -l bun_install_boolean_flags_descriptions "Write a yarn.lock file (yarn v1)" "Don't install devDependencies" "Add dependency to optionalDependencies" "Add dependency to devDependencies" "Don't update package.json or save a lockfile" "Don't install anything" "Always request the latest versions from the registry & reinstall all dependencies" "Ignore manifest cache entirely" "Don't output anything" "Excessively verbose logging" "Use global folder"
set -l bun_builtin_cmds_without_run dev create help bun upgrade discord install remove add update init pm x
set -l bun_builtin_cmds_accepting_flags create help bun upgrade discord run init link unlink pm x update
set -l bun_builtin_cmds_without_run dev create help bun upgrade discord install remove add init pm x
set -l bun_builtin_cmds_accepting_flags create help bun upgrade discord run init link unlink pm x
function __bun_complete_bins_scripts --inherit-variable bun_builtin_cmds_without_run -d "Emit bun completions for bins and scripts"
# Do nothing if we already have a builtin subcommand,
@@ -148,14 +148,14 @@ complete -c bun \
for i in (seq (count $bun_install_boolean_flags))
complete -c bun \
-n "__fish_seen_subcommand_from install add remove update" -l "$bun_install_boolean_flags[$i]" -d "$bun_install_boolean_flags_descriptions[$i]"
-n "__fish_seen_subcommand_from install add remove" -l "$bun_install_boolean_flags[$i]" -d "$bun_install_boolean_flags_descriptions[$i]"
end
complete -c bun \
-n "__fish_seen_subcommand_from install add remove update" -l 'cwd' -d 'Change working directory'
-n "__fish_seen_subcommand_from install add remove" -l 'cwd' -d 'Change working directory'
complete -c bun \
-n "__fish_seen_subcommand_from install add remove update" -l 'cache-dir' -d 'Choose a cache directory (default: $HOME/.bun/install/cache)'
-n "__fish_seen_subcommand_from install add remove" -l 'cache-dir' -d 'Choose a cache directory (default: $HOME/.bun/install/cache)'
complete -c bun \
-n "__fish_seen_subcommand_from add" -d 'Popular' -a '(__fish__get_bun_packages)'
@@ -183,5 +183,4 @@ complete -c bun -n "__fish_use_subcommand" -a "unlink" -d "Unregister a local np
complete -c bun -n "__fish_use_subcommand" -a "pm" -d "Additional package management utilities" -f
complete -c bun -n "__fish_use_subcommand" -a "x" -d "Execute a package binary, installing if needed" -f
complete -c bun -n "__fish_use_subcommand" -a "outdated" -d "Display the latest versions of outdated dependencies" -f
complete -c bun -n "__fish_use_subcommand" -a "update" -d "Update dependencies to their latest versions" -f
complete -c bun -n "__fish_use_subcommand" -a "publish" -d "Publish your package from local to npm" -f

View File

@@ -1,4 +1,4 @@
FROM debian:trixie-slim AS build
FROM debian:bookworm-slim AS build
# https://github.com/oven-sh/bun/releases
ARG BUN_VERSION=latest
@@ -55,7 +55,7 @@ RUN apt-get update -qq \
&& which bun \
&& bun --version
FROM debian:trixie-slim
FROM debian:bookworm-slim
# Disable the runtime transpiler cache by default inside Docker containers.
# On ephemeral containers, the cache is not useful

View File

@@ -1,4 +1,4 @@
FROM debian:trixie-slim AS build
FROM debian:bookworm-slim AS build
# https://github.com/oven-sh/bun/releases
ARG BUN_VERSION=latest
@@ -56,7 +56,7 @@ RUN apt-get update -qq \
&& rm -f "bun-linux-$build.zip" SHASUMS256.txt.asc SHASUMS256.txt \
&& chmod +x /usr/local/bin/bun
FROM debian:trixie
FROM debian:bookworm
COPY docker-entrypoint.sh /usr/local/bin
COPY --from=build /usr/local/bin/bun /usr/local/bin/bun

View File

@@ -1,4 +1,4 @@
FROM debian:trixie-slim AS build
FROM debian:bookworm-slim AS build
# https://github.com/oven-sh/bun/releases
ARG BUN_VERSION=latest
@@ -55,7 +55,7 @@ RUN apt-get update -qq \
&& which bun \
&& bun --version
FROM gcr.io/distroless/base-nossl-debian13
FROM gcr.io/distroless/base-nossl-debian11
# Disable the runtime transpiler cache by default inside Docker containers.
# On ephemeral containers, the cache is not useful
@@ -71,7 +71,6 @@ ENV PATH "${PATH}:/usr/local/bun-node-fallback-bin"
# Temporarily use the `build`-stage image binaries to create a symlink:
RUN --mount=type=bind,from=build,source=/usr/bin,target=/usr/bin \
--mount=type=bind,from=build,source=/etc/alternatives/which,target=/etc/alternatives/which \
--mount=type=bind,from=build,source=/bin,target=/bin \
--mount=type=bind,from=build,source=/usr/lib,target=/usr/lib \
--mount=type=bind,from=build,source=/lib,target=/lib \

View File

@@ -1,28 +0,0 @@
<p align="center">
<a href="https://bun.com">
<img src="https://github.com/user-attachments/assets/50282090-adfd-4ddb-9e27-c30753c6b161" alt="Logo" height="170" />
</a>
</p>
<h1 align="center">Bun Documentation</h1>
Official documentation for Bun: the fast, all-in-one JavaScript runtime.
## Development
Install the [Mintlify CLI](https://www.npmjs.com/package/mint) to preview the documentation locally:
```bash
bun install -g mint
```
Run the development server:
```bash
mint dev
```
The site will be available at `http://localhost:3000`.
## Contributing
Contributions are welcome! Please open an issue or submit a pull request.

View File

@@ -1,23 +1,41 @@
---
title: Binary Data
description: Working with binary data in JavaScript
---
This page is intended as an introduction to working with binary data in JavaScript. Bun implements a number of data types and utilities for working with binary data, most of which are Web-standard. Any Bun-specific APIs will be noted as such.
Below is a quick "cheat sheet" that doubles as a table of contents. Click an item in the left column to jump to that section.
| Class | Description |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`TypedArray`](#typedarray) | A family of classes that provide an `Array`-like interface for interacting with binary data. Includes `Uint8Array`, `Uint16Array`, `Int8Array`, and more. |
| [`Buffer`](#buffer) | A subclass of `Uint8Array` that implements a wide range of convenience methods. Unlike the other elements in this table, this is a Node.js API (which Bun implements). It can't be used in the browser. |
| [`DataView`](#dataview) | A class that provides a `get/set` API for writing some number of bytes to an `ArrayBuffer` at a particular byte offset. Often used reading or writing binary protocols. |
| [`Blob`](#blob) | A readonly blob of binary data usually representing a file. Has a MIME `type`, a `size`, and methods for converting to `ArrayBuffer`, `ReadableStream`, and string. |
| [`File`](#file) | A subclass of `Blob` that represents a file. Has a `name` and `lastModified` timestamp. There is experimental support in Node.js v20. |
| [`BunFile`](#bunfile) | _Bun only_. A subclass of `Blob` that represents a lazily-loaded file on disk. Created with `Bun.file(path)`. |
{% table %}
---
- [`TypedArray`](#typedarray)
- A family of classes that provide an `Array`-like interface for interacting with binary data. Includes `Uint8Array`, `Uint16Array`, `Int8Array`, and more.
---
- [`Buffer`](#buffer)
- A subclass of `Uint8Array` that implements a wide range of convenience methods. Unlike the other elements in this table, this is a Node.js API (which Bun implements). It can't be used in the browser.
---
- [`DataView`](#dataview)
- A class that provides a `get/set` API for writing some number of bytes to an `ArrayBuffer` at a particular byte offset. Often used reading or writing binary protocols.
---
- [`Blob`](#blob)
- A readonly blob of binary data usually representing a file. Has a MIME `type`, a `size`, and methods for converting to `ArrayBuffer`, `ReadableStream`, and string.
---
- [`File`](#file)
- A subclass of `Blob` that represents a file. Has a `name` and `lastModified` timestamp. There is experimental support in Node.js v20.
---
- [`BunFile`](#bunfile)
- _Bun only_. A subclass of `Blob` that represents a lazily-loaded file on disk. Created with `Bun.file(path)`.
{% /table %}
## `ArrayBuffer` and views
Until 2009, there was no language-native way to store and manipulate binary data in JavaScript. ECMAScript v5 introduced a range of new mechanisms for this. The most fundamental building block is `ArrayBuffer`, a simple data structure that represents a sequence of bytes in memory.
@@ -80,28 +98,70 @@ dv.setFloat64(0, 3.1415);
The following methods are available on `DataView`:
| Getters | Setters |
| -------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| [`getBigInt64()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getBigInt64) | [`setBigInt64()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setBigInt64) |
| [`getBigUint64()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getBigUint64) | [`setBigUint64()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setBigUint64) |
| [`getFloat32()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getFloat32) | [`setFloat32()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setFloat32) |
| [`getFloat64()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getFloat64) | [`setFloat64()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setFloat64) |
| [`getInt16()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getInt16) | [`setInt16()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setInt16) |
| [`getInt32()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getInt32) | [`setInt32()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setInt32) |
| [`getInt8()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getInt8) | [`setInt8()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setInt8) |
| [`getUint16()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getUint16) | [`setUint16()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setUint16) |
| [`getUint32()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getUint32) | [`setUint32()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setUint32) |
| [`getUint8()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getUint8) | [`setUint8()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setUint8) |
{% table %}
- Getters
- Setters
---
- [`getBigInt64()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getBigInt64)
- [`setBigInt64()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setBigInt64)
---
- [`getBigUint64()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getBigUint64)
- [`setBigUint64()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setBigUint64)
---
- [`getFloat32()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getFloat32)
- [`setFloat32()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setFloat32)
---
- [`getFloat64()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getFloat64)
- [`setFloat64()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setFloat64)
---
- [`getInt16()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getInt16)
- [`setInt16()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setInt16)
---
- [`getInt32()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getInt32)
- [`setInt32()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setInt32)
---
- [`getInt8()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getInt8)
- [`setInt8()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setInt8)
---
- [`getUint16()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getUint16)
- [`setUint16()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setUint16)
---
- [`getUint32()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getUint32)
- [`setUint32()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setUint32)
---
- [`getUint8()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/getUint8)
- [`setUint8()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView/setUint8)
{% /table %}
### `TypedArray`
Typed arrays are a family of classes that provide an `Array`-like interface for interacting with data in an `ArrayBuffer`. Whereas a `DataView` lets you write numbers of varying size at a particular offset, a `TypedArray` interprets the underlying bytes as an array of numbers, each of a fixed size.
<Note>
It's common to refer to this family of classes collectively by their shared superclass `TypedArray`. This class as
_internal_ to JavaScript; you can't directly create instances of it, and `TypedArray` is not defined in the global
scope. Think of it as an `interface` or an abstract class.
</Note>
{% callout %}
**Note** — It's common to refer to this family of classes collectively by their shared superclass `TypedArray`. This class as _internal_ to JavaScript; you can't directly create instances of it, and `TypedArray` is not defined in the global scope. Think of it as an `interface` or an abstract class.
{% /callout %}
```ts
const buffer = new ArrayBuffer(3);
@@ -122,32 +182,121 @@ The top row contains the raw bytes, and the later rows contain how these bytes w
The following classes are typed arrays, along with a description of how they interpret the bytes in an `ArrayBuffer`:
Here's the first table formatted as a markdown table:
{% table %}
| Class | Description |
| ------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) | Every one (1) byte is interpreted as an unsigned 8-bit integer. Range 0 to 255. |
| [`Uint16Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint16Array) | Every two (2) bytes are interpreted as an unsigned 16-bit integer. Range 0 to 65535. |
| [`Uint32Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint32Array) | Every four (4) bytes are interpreted as an unsigned 32-bit integer. Range 0 to 4294967295. |
| [`Int8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Int8Array) | Every one (1) byte is interpreted as a signed 8-bit integer. Range -128 to 127. |
| [`Int16Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Int16Array) | Every two (2) bytes are interpreted as a signed 16-bit integer. Range -32768 to 32767. |
| [`Int32Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Int32Array) | Every four (4) bytes are interpreted as a signed 32-bit integer. Range -2147483648 to 2147483647. |
| [`Float16Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Float16Array) | Every two (2) bytes are interpreted as a 16-bit floating point number. Range -6.104e5 to 6.55e4. |
| [`Float32Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Float32Array) | Every four (4) bytes are interpreted as a 32-bit floating point number. Range -3.4e38 to 3.4e38. |
| [`Float64Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Float64Array) | Every eight (8) bytes are interpreted as a 64-bit floating point number. Range -1.7e308 to 1.7e308. |
| [`BigInt64Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt64Array) | Every eight (8) bytes are interpreted as a signed `BigInt`. Range -9223372036854775808 to 9223372036854775807 (though `BigInt` is capable of representing larger numbers). |
| [`BigUint64Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigUint64Array) | Every eight (8) bytes are interpreted as an unsigned `BigInt`. Range 0 to 18446744073709551615 (though `BigInt` is capable of representing larger numbers). |
| [`Uint8ClampedArray`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8ClampedArray) | Same as `Uint8Array`, but automatically "clamps" to the range 0-255 when assigning a value to an element. |
- Class
- Description
---
- [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array)
- Every one (1) byte is interpreted as an unsigned 8-bit integer. Range 0 to 255.
---
- [`Uint16Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint16Array)
- Every two (2) bytes are interpreted as an unsigned 16-bit integer. Range 0 to 65535.
---
- [`Uint32Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint32Array)
- Every four (4) bytes are interpreted as an unsigned 32-bit integer. Range 0 to 4294967295.
---
- [`Int8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Int8Array)
- Every one (1) byte is interpreted as a signed 8-bit integer. Range -128 to 127.
---
- [`Int16Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Int16Array)
- Every two (2) bytes are interpreted as a signed 16-bit integer. Range -32768 to 32767.
---
- [`Int32Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Int32Array)
- Every four (4) bytes are interpreted as a signed 32-bit integer. Range -2147483648 to 2147483647.
---
- [`Float16Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Float16Array)
- Every two (2) bytes are interpreted as a 16-bit floating point number. Range -6.104e5 to 6.55e4.
---
- [`Float32Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Float32Array)
- Every four (4) bytes are interpreted as a 32-bit floating point number. Range -3.4e38 to 3.4e38.
---
- [`Float64Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Float64Array)
- Every eight (8) bytes are interpreted as a 64-bit floating point number. Range -1.7e308 to 1.7e308.
---
- [`BigInt64Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt64Array)
- Every eight (8) bytes are interpreted as a signed `BigInt`. Range -9223372036854775808 to 9223372036854775807 (though `BigInt` is capable of representing larger numbers).
---
- [`BigUint64Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigUint64Array)
- Every eight (8) bytes are interpreted as an unsigned `BigInt`. Range 0 to 18446744073709551615 (though `BigInt` is capable of representing larger numbers).
---
- [`Uint8ClampedArray`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8ClampedArray)
- Same as `Uint8Array`, but automatically "clamps" to the range 0-255 when assigning a value to an element.
{% /table %}
The table below demonstrates how the bytes in an `ArrayBuffer` are interpreted when viewed using different typed array classes.
| | Byte 0 | Byte 1 | Byte 2 | Byte 3 | Byte 4 | Byte 5 | Byte 6 | Byte 7 |
| ---------------- | ------------------- | ---------- | ------------------- | ---------- | -------------------- | ---------- | -------------------- | ---------- |
| `ArrayBuffer` | `00000000` | `00000001` | `00000010` | `00000011` | `00000100` | `00000101` | `00000110` | `00000111` |
| `Uint8Array` | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| `Uint16Array` | 256 (`1 * 256 + 0`) | | 770 (`3 * 256 + 2`) | | 1284 (`5 * 256 + 4`) | | 1798 (`7 * 256 + 6`) | |
| `Uint32Array` | 50462976 | | | | 117835012 | | | |
| `BigUint64Array` | 506097522914230528n | | | | | | | |
{% table %}
---
- `ArrayBuffer`
- `00000000`
- `00000001`
- `00000010`
- `00000011`
- `00000100`
- `00000101`
- `00000110`
- `00000111`
---
- `Uint8Array`
- 0
- 1
- 2
- 3
- 4
- 5
- 6
- 7
---
- `Uint16Array`
- 256 (`1 * 256 + 0`) {% colspan=2 %}
- 770 (`3 * 256 + 2`) {% colspan=2 %}
- 1284 (`5 * 256 + 4`) {% colspan=2 %}
- 1798 (`7 * 256 + 6`) {% colspan=2 %}
---
- `Uint32Array`
- 50462976 {% colspan=4 %}
- 117835012 {% colspan=4 %}
---
- `BigUint64Array`
- 506097522914230528n {% colspan=8 %}
{% /table %}
To create a typed array from a pre-defined `ArrayBuffer`:
@@ -319,7 +468,9 @@ const file = Bun.file("index.txt");
### `File`
<Warning>Browser only. Experimental support in Node.js 20.</Warning>
{% callout %}
Browser only. Experimental support in Node.js 20.
{% /callout %}
[`File`](https://developer.mozilla.org/en-US/docs/Web/API/File) is a subclass of `Blob` that adds a `name` and `lastModified` property. It's commonly used in the browser to represent files uploaded via a `<input type="file">` element. Node.js and Bun implement `File`.
@@ -339,21 +490,15 @@ const file = new File(["<html>Hello</html>"], "index.html", {
Refer to the [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/API/Blob) for complete docs information.
---
## Streams
Streams are an important abstraction for working with binary data without loading it all into memory at once. They are commonly used for reading and writing files, sending and receiving network requests, and processing large amounts of data.
Bun implements the Web APIs [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) and [`WritableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream).
<Note>
Bun also implements the `node:stream` module, including
[`Readable`](https://nodejs.org/api/stream.html#stream_readable_streams),
[`Writable`](https://nodejs.org/api/stream.html#stream_writable_streams), and
[`Duplex`](https://nodejs.org/api/stream.html#stream_duplex_and_transform_streams). For complete documentation, refer
to the Node.js docs.
</Note>
{% callout %}
Bun also implements the `node:stream` module, including [`Readable`](https://nodejs.org/api/stream.html#stream_readable_streams), [`Writable`](https://nodejs.org/api/stream.html#stream_writable_streams), and [`Duplex`](https://nodejs.org/api/stream.html#stream_duplex_and_transform_streams). For complete documentation, refer to the Node.js docs.
{% /callout %}
To create a simple readable stream:
@@ -372,15 +517,12 @@ The contents of this stream can be read chunk-by-chunk with `for await` syntax.
```ts
for await (const chunk of stream) {
console.log(chunk);
// => "hello"
// => "world"
}
// => "hello"
// => "world"
```
For a more complete discussion of streams in Bun, see [API > Streams](/runtime/streams).
---
For a more complete discussion of streams in Bun, see [API > Streams](https://bun.com/docs/api/streams).
## Conversion
@@ -432,6 +574,12 @@ Array.from(new Uint8Array(buf));
new Blob([buf], { type: "text/plain" });
```
<!-- #### To `File`
```ts
new File([buf], "filename.txt", { type: "text/plain", lastModified: Date.now() });
``` -->
#### To `ReadableStream`
The following snippet creates a `ReadableStream` and enqueues the entire `ArrayBuffer` as a single chunk.
@@ -445,7 +593,7 @@ new ReadableStream({
});
```
<Accordion title="With chunking">
{% details summary="With chunking" %}
To stream the `ArrayBuffer` in chunks, use a `Uint8Array` view and enqueue each chunk.
```ts
@@ -462,7 +610,7 @@ new ReadableStream({
});
```
</Accordion>
{% /details %}
### From `TypedArray`
@@ -509,6 +657,12 @@ Array.from(arr);
new Blob([arr.buffer], { type: "text/plain" });
```
<!-- #### To `File`
```ts
new File([arr.buffer], "filename.txt", { type: "text/plain", lastModified: Date.now() });
``` -->
#### To `ReadableStream`
```ts
@@ -520,8 +674,7 @@ new ReadableStream({
});
```
<Accordion title="With chunking">
{% details summary="With chunking" %}
To stream the `ArrayBuffer` in chunks, split the `TypedArray` into chunks and enqueue each one individually.
```ts
@@ -535,7 +688,7 @@ new ReadableStream({
});
```
</Accordion>
{% /details %}
### From `DataView`
@@ -582,6 +735,12 @@ Array.from(view);
new Blob([view.buffer], { type: "text/plain" });
```
<!-- #### To `File`
```ts
new File([view.buffer], "filename.txt", { type: "text/plain", lastModified: Date.now() });
``` -->
#### To `ReadableStream`
```ts
@@ -593,7 +752,7 @@ new ReadableStream({
});
```
<Accordion title="With chunking">
{% details summary="With chunking" %}
To stream the `ArrayBuffer` in chunks, split the `DataView` into chunks and enqueue each one individually.
```ts
@@ -607,7 +766,7 @@ new ReadableStream({
});
```
</Accordion>
{% /details %}
### From `Buffer`
@@ -661,6 +820,12 @@ Array.from(buf);
new Blob([buf], { type: "text/plain" });
```
<!-- #### To `File`
```ts
new File([buf], "filename.txt", { type: "text/plain", lastModified: Date.now() });
``` -->
#### To `ReadableStream`
```ts
@@ -672,7 +837,7 @@ new ReadableStream({
});
```
<Accordion title="With chunking">
{% details summary="With chunking" %}
To stream the `ArrayBuffer` in chunks, split the `Buffer` into chunks and enqueue each one individually.
```ts
@@ -686,7 +851,7 @@ new ReadableStream({
});
```
</Accordion>
{% /details %}
### From `Blob`
@@ -736,6 +901,8 @@ Array.from(await blob.bytes());
blob.stream();
```
<!-- ### From `File` -->
### From `ReadableStream`
It's common to use [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) as a convenient intermediate representation to make it easier to convert `ReadableStream` to other formats.
@@ -837,6 +1004,14 @@ Bun.readableStreamToArray(stream);
new Response(stream).blob();
```
<!-- #### To `File`
```ts
new Response(stream)
.blob()
.then(blob => new File([blob], "filename.txt", { type: "text/plain", lastModified: Date.now() }));
``` -->
#### To `ReadableStream`
To split a `ReadableStream` into two streams that can be consumed independently:
@@ -844,3 +1019,20 @@ To split a `ReadableStream` into two streams that can be consumed independently:
```ts
const [a, b] = stream.tee();
```
<!-- - Use Buffer
- TextEncoder
- `Bun.ArrayBufferSink`
- ReadableStream
- AsyncIterator
- TypedArray vs ArrayBuffer vs DataView
- Bun.indexOfLine
- “direct” readablestream
- readable stream has assumptions about
- its very generic
- all data is copies and queued
- direct : no queueing
- just a write function
- you can write strings
- more synchronous
- corking works better -->

View File

@@ -1,19 +1,12 @@
---
title: C Compiler
description: Compile and run C from JavaScript with low overhead
---
`bun:ffi` has experimental support for compiling and running C from JavaScript with low overhead.
---
## Usage (cc in `bun:ffi`)
See the [introduction blog post](https://bun.com/blog/compile-and-run-c-in-js) for more information.
JavaScript:
```ts hello.ts icon="file-code"
```ts#hello.js
import { cc } from "bun:ffi";
import source from "./hello.c" with { type: "file" };
@@ -34,7 +27,7 @@ console.log("What is the answer to the universe?", hello());
C source:
```c hello.c
```c#hello.c
int hello() {
return 42;
}
@@ -42,8 +35,8 @@ int hello() {
When you run `hello.js`, it will print:
```sh terminal icon="terminal"
bun hello.js
```sh
$ bun hello.js
What is the answer to the universe? 42
```
@@ -51,7 +44,7 @@ Under the hood, `cc` uses [TinyCC](https://bellard.org/tcc/) to compile the C co
### Primitive types
The same `FFIType` values in [`dlopen`](/runtime/ffi) are supported in `cc`.
The same `FFIType` values in [`dlopen`](/docs/api/ffi) are supported in `cc`.
| `FFIType` | C Type | Aliases |
| ---------- | -------------- | --------------------------- |
@@ -87,7 +80,7 @@ You can also pass a `napi_env` to receive the N-API environment used to call the
For example, if you have a string in C, you can return it to JavaScript like this:
```ts hello.ts
```ts#hello.js
import { cc } from "bun:ffi";
import source from "./hello.c" with { type: "file" };
@@ -108,7 +101,7 @@ const result = hello();
And in C:
```c hello.c
```c#hello.c
#include <node/node_api.h>
napi_value hello(napi_env env) {
@@ -120,7 +113,7 @@ napi_value hello(napi_env env) {
You can also use this to return other types like objects and arrays:
```c hello.c
```c#hello.c
#include <node/node_api.h>
napi_value hello(napi_env env) {
@@ -196,7 +189,7 @@ type Defines = Record<string, string>;
cc({
source: "hello.c",
define: {
NDEBUG: "1",
"NDEBUG": "1",
},
});
```

View File

@@ -1,8 +1,3 @@
---
title: Color
description: Format colors as CSS, ANSI, numbers, hex strings, and more
---
`Bun.color(input, outputFormat?)` leverages Bun's CSS parser to parse, normalize, and convert colors from user input to a variety of output formats, including:
| Format | Example |
@@ -247,7 +242,7 @@ Bun.color([255, 0, 0], "HEX"); // "#FF0000"
Like many of Bun's APIs, you can use macros to invoke `Bun.color` at bundle-time for use in client-side JavaScript builds:
```ts client-side.ts
```ts#client-side.ts
import { color } from "bun" with { type: "macro" };
console.log(color("#f00", "css"));

View File

@@ -1,14 +1,6 @@
---
title: Console
description: The console object in Bun
---
<Note>
Bun provides a browser- and Node.js-compatible [console](https://developer.mozilla.org/en-US/docs/Web/API/console)
global. This page only documents Bun-native APIs.
</Note>
---
{% callout %}
**Note** — Bun provides a browser- and Node.js-compatible [console](https://developer.mozilla.org/en-US/docs/Web/API/console) global. This page only documents Bun-native APIs.
{% /callout %}
## Object inspection depth
@@ -27,13 +19,11 @@ console.log(nested);
The CLI flag takes precedence over the configuration file setting.
---
## Reading from stdin
In Bun, the `console` object can be used as an `AsyncIterable` to sequentially read lines from `process.stdin`.
```ts adder.ts icon="/icons/typescript.svg"
```ts
for await (const line of console) {
console.log(line);
}
@@ -41,7 +31,7 @@ for await (const line of console) {
This is useful for implementing interactive programs, like the following addition calculator.
```ts adder.ts icon="/icons/typescript.svg"
```ts#adder.ts
console.log(`Let's add some numbers!`);
console.write(`Count: 0\n> `);
@@ -54,8 +44,8 @@ for await (const line of console) {
To run the file:
```bash terminal icon="terminal"
bun adder.ts
```bash
$ bun adder.ts
Let's add some numbers!
Count: 0
> 5

View File

@@ -1,15 +1,10 @@
---
title: Cookies
description: Use Bun's native APIs for working with HTTP cookies
---
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 title="cookies.ts" icon="/icons/typescript.svg"
```ts
// Empty cookie map
const cookies = new Bun.CookieMap();
@@ -33,7 +28,7 @@ const cookies3 = new Bun.CookieMap([
In Bun's HTTP server, the `cookies` property on the request object (in `routes`) is an instance of `CookieMap`:
```ts title="server.ts" icon="/icons/typescript.svg"
```ts
const server = Bun.serve({
routes: {
"/": req => {
@@ -68,7 +63,7 @@ console.log("Server listening at: " + server.url);
Retrieves a cookie by name. Returns `null` if the cookie doesn't exist.
```ts title="get-cookie.ts" icon="/icons/typescript.svg"
```ts
// Get by name
const cookie = cookies.get("session");
@@ -81,7 +76,7 @@ if (cookie != null) {
Checks if a cookie with the given name exists.
```ts title="has-cookie.ts" icon="/icons/typescript.svg"
```ts
// Check if cookie exists
if (cookies.has("session")) {
// Cookie exists
@@ -96,7 +91,7 @@ if (cookies.has("session")) {
Adds or updates a cookie in the map. Cookies default to `{ path: "/", sameSite: "lax" }`.
```ts title="set-cookie.ts" icon="/icons/typescript.svg"
```ts
// Set by name and value
cookies.set("session", "abc123");
@@ -119,7 +114,7 @@ cookies.set(cookie);
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 title="delete-cookie.ts" icon="/icons/typescript.svg"
```ts
// Delete by name using default domain and path.
cookies.delete("session");
@@ -135,7 +130,7 @@ cookies.delete({
Converts the cookie map to a serializable format.
```ts title="cookie-to-json.ts" icon="/icons/typescript.svg"
```ts
const json = cookies.toJSON();
```
@@ -145,7 +140,7 @@ Returns an array of values for Set-Cookie headers that can be used to apply all
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 title="node-server.js" icon="file-code"
```js
import { createServer } from "node:http";
import { CookieMap } from "bun";
@@ -172,7 +167,7 @@ server.listen(3000, () => {
`CookieMap` provides several methods for iteration:
```ts title="iterate-cookies.ts" icon="/icons/typescript.svg"
```ts
// Iterate over [name, cookie] entries
for (const [name, value] of cookies) {
console.log(`${name}: ${value}`);
@@ -205,7 +200,7 @@ cookies.forEach((value, name) => {
Returns the number of cookies in the map.
```ts title="cookie-size.ts" icon="/icons/typescript.svg"
```ts
console.log(cookies.size); // Number of cookies
```
@@ -213,7 +208,7 @@ console.log(cookies.size); // Number of cookies
`Bun.Cookie` represents an HTTP cookie with its name, value, and attributes.
```ts title="cookie-class.ts" icon="/icons/typescript.svg"
```ts
import { Cookie } from "bun";
// Create a basic cookie
@@ -243,7 +238,7 @@ const objCookie = new Bun.Cookie({
### Constructors
```ts title="constructors.ts" icon="/icons/typescript.svg"
```ts
// Basic constructor with name/value
new Bun.Cookie(name: string, value: string);
@@ -259,7 +254,7 @@ new Bun.Cookie(options: CookieInit);
### Properties
```ts title="cookie-properties.ts" icon="/icons/typescript.svg"
```ts
cookie.name; // string - Cookie name
cookie.value; // string - Cookie value
cookie.domain; // string | null - Domain scope (null if not specified)
@@ -278,7 +273,7 @@ cookie.httpOnly; // boolean - Accessible only via HTTP (not JavaScript)
Checks if the cookie has expired.
```ts title="is-expired.ts" icon="/icons/typescript.svg"
```ts
// Expired cookie (Date in the past)
const expiredCookie = new Bun.Cookie("name", "value", {
expires: new Date(Date.now() - 1000),
@@ -302,7 +297,7 @@ console.log(sessionCookie.isExpired()); // false
Returns a string representation of the cookie suitable for a `Set-Cookie` header.
```ts title="serialize-cookie.ts" icon="/icons/typescript.svg"
```ts
const cookie = new Bun.Cookie("session", "abc123", {
domain: "example.com",
path: "/admin",
@@ -322,7 +317,7 @@ console.log(cookie.toString());
Converts the cookie to a plain object suitable for JSON serialization.
```ts title="cookie-json.ts" icon="/icons/typescript.svg"
```ts
const cookie = new Bun.Cookie("session", "abc123", {
secure: true,
httpOnly: true,
@@ -349,7 +344,7 @@ const jsonString = JSON.stringify(cookie);
Parses a cookie string into a `Cookie` instance.
```ts title="parse-cookie.ts" icon="/icons/typescript.svg"
```ts
const cookie = Bun.Cookie.parse("name=value; Path=/; Secure; SameSite=Lax");
console.log(cookie.name); // "name"
@@ -363,7 +358,7 @@ console.log(cookie.sameSite); // "lax"
Factory method to create a cookie.
```ts title="cookie-from.ts" icon="/icons/typescript.svg"
```ts
const cookie = Bun.Cookie.from("session", "abc123", {
httpOnly: true,
secure: true,
@@ -373,7 +368,7 @@ const cookie = Bun.Cookie.from("session", "abc123", {
## Types
```ts title="types.ts" icon="/icons/typescript.svg"
```ts
interface CookieInit {
name?: string;
value?: string;

View File

@@ -1,9 +1,4 @@
---
title: DNS
description: Use Bun's DNS module to resolve DNS records
---
Bun implements it's own `dns` module, and the `node:dns` module.
Bun implements the `node:dns` module.
```ts
import * as dns from "node:dns";
@@ -13,17 +8,9 @@ console.log(addrs);
// => [{ address: "172.67.161.226", family: 4, ttl: 0 }, ...]
```
```ts
import { dns } from "bun";
dns.prefetch("bun.com", 443);
```
---
## DNS caching in Bun
Bun supports DNS caching. This cache makes repeated connections to the same hosts faster.
In Bun v1.1.9, we added support for DNS caching. This cache makes repeated connections to the same hosts faster.
At the time of writing, we cache up to 255 entries for a maximum of 30 seconds (each). If any connections to a host fail, we remove the entry from the cache. When multiple connections are made to the same host simultaneously, DNS lookups are deduplicated to avoid making multiple requests for the same host.
@@ -52,7 +39,9 @@ An example where you might want to use this is a database driver. When your appl
### `dns.prefetch`
<Warning>This API is experimental and may change in the future.</Warning>
{% callout %}
**🚧** — This API is experimental and may change in the future.
{% /callout %}
To prefetch a DNS entry, you can use the `dns.prefetch` API. This API is useful when you know you'll need to connect to a host soon and want to avoid the initial DNS lookup.
@@ -73,18 +62,28 @@ await fetch("https://bun.com");
### `dns.getCacheStats()`
<Warning>This API is experimental and may change in the future.</Warning>
{% callout %}
**🚧** — This API is experimental and may change in the future.
{% /callout %}
To get the current cache stats, you can use the `dns.getCacheStats` API. This API returns an object with the following properties:
To get the current cache stats, you can use the `dns.getCacheStats` API.
This API returns an object with the following properties:
```ts
{
cacheHitsCompleted: number; // Cache hits completed
cacheHitsInflight: number; // Cache hits in flight
cacheMisses: number; // Cache misses
size: number; // Number of items in the DNS cache
errors: number; // Number of times a connection failed
totalCount: number; // Number of times a connection was requested at all (including cache hits and misses)
// Cache hits
cacheHitsCompleted: number;
cacheHitsInflight: number;
cacheMisses: number;
// Number of items in the DNS cache
size: number;
// Number of times a connection failed
errors: number;
// Number of times a connection was requested at all (including cache hits and misses)
totalCount: number;
}
```

View File

@@ -1,8 +1,3 @@
---
title: Fetch
description: Send HTTP requests with Bun's fetch API
---
Bun implements the WHATWG `fetch` standard, with some extensions to meet the needs of server-side JavaScript.
Bun also implements `node:http`, but `fetch` is generally recommended instead.
@@ -51,7 +46,7 @@ const response = await fetch("http://example.com", {
### Proxying requests
To proxy a request, pass an object with the `proxy` property set to a URL string:
To proxy a request, pass an object with the `proxy` property set to a URL.
```ts
const response = await fetch("http://example.com", {
@@ -59,22 +54,6 @@ const response = await fetch("http://example.com", {
});
```
You can also use an object format to send custom headers to the proxy server:
```ts
const response = await fetch("http://example.com", {
proxy: {
url: "http://proxy.com",
headers: {
"Proxy-Authorization": "Bearer my-token",
"X-Custom-Proxy-Header": "value",
},
},
});
```
The `headers` are sent directly to the proxy in `CONNECT` requests (for HTTPS targets) or in the proxy request (for HTTP targets). If you provide a `Proxy-Authorization` header, it overrides any credentials in the proxy URL.
### Custom headers
To set custom headers, pass an object with the `headers` property set to an object.
@@ -289,7 +268,7 @@ const response = await fetch("s3://my-bucket/path/to/object", {
Note: Only PUT and POST methods support request bodies when using S3. For uploads, Bun automatically uses multipart upload for streaming bodies.
You can read more about Bun's S3 support in the [S3](/runtime/s3) documentation.
You can read more about Bun's S3 support in the [S3](https://bun.com/docs/api/s3) documentation.
#### File URLs - `file://`
@@ -358,7 +337,7 @@ This will print the request and response headers to your terminal:
```sh
[fetch] > HTTP/1.1 GET http://example.com/
[fetch] > Connection: keep-alive
[fetch] > User-Agent: Bun/1.3.3
[fetch] > User-Agent: Bun/$BUN_LATEST_VERSION
[fetch] > Accept: */*
[fetch] > Host: example.com
[fetch] > Accept-Encoding: gzip, deflate, br, zstd
@@ -404,7 +383,7 @@ dns.prefetch("bun.com");
By default, Bun caches and deduplicates DNS queries in-memory for up to 30 seconds. You can see the cache stats by calling `dns.getCacheStats()`:
To learn more about DNS caching in Bun, see the [DNS caching](/runtime/networking/dns) documentation.
To learn more about DNS caching in Bun, see the [DNS caching](https://bun.com/docs/api/dns) documentation.
### Preconnect to a host
@@ -423,7 +402,7 @@ Note: calling `fetch` immediately after `fetch.preconnect` will not make your re
To preconnect to a host at startup, you can pass `--fetch-preconnect`:
```sh
bun --fetch-preconnect https://bun.com ./my-script.ts
$ bun --fetch-preconnect https://bun.com ./my-script.ts
```
This is sort of like `<link rel="preconnect">` in HTML.
@@ -446,7 +425,7 @@ When the limit is exceeded, the requests are queued and sent as soon as the next
You can increase the maximum number of simultaneous connections via the `BUN_CONFIG_MAX_HTTP_REQUESTS` environment variable:
```sh
BUN_CONFIG_MAX_HTTP_REQUESTS=512 bun ./my-script.ts
$ BUN_CONFIG_MAX_HTTP_REQUESTS=512 bun ./my-script.ts
```
The max value for this limit is currently set to 65,336. The maximum port number is 65,535, so it's quite difficult for any one computer to exceed this limit.

View File

@@ -1,17 +1,9 @@
---
title: FFI
description: Use Bun's FFI module to efficiently call native libraries from JavaScript
---
<Warning>
`bun:ffi` is **experimental**, with known bugs and limitations, and should not be relied on in production. The most
stable way to interact with native code from Bun is to write a [Node-API module](/runtime/node-api).
</Warning>
{% callout %}
**⚠️ Warning** — `bun:ffi` is **experimental**, with known bugs and limitations, and should not be relied on in production. The most stable way to interact with native code from Bun is to write a [Node-API module](/docs/api/node-api).
{% /callout %}
Use the built-in `bun:ffi` module to efficiently call native libraries from JavaScript. It works with languages that support the C ABI (Zig, Rust, C/C++, C#, Nim, Kotlin, etc).
---
## dlopen usage (`bun:ffi`)
To print the version number of `sqlite3`:
@@ -41,23 +33,20 @@ const {
console.log(`SQLite 3 version: ${sqlite3_libversion()}`);
```
---
## Performance
According to [our benchmark](https://github.com/oven-sh/bun/tree/main/bench/ffi), `bun:ffi` is roughly 2-6x faster than Node.js FFI via `Node-API`.
<Image src="/images/ffi.png" height="400" />
{% image src="/images/ffi.png" height="400" /%}
Bun generates & just-in-time compiles C bindings that efficiently convert values between JavaScript types and native types. To compile C, Bun embeds [TinyCC](https://github.com/TinyCC/tinycc), a small and fast C compiler.
---
## Usage
### Zig
```zig add.zig icon="file-code"
```zig
// add.zig
pub export fn add(a: i32, b: i32) i32 {
return a + b;
}
@@ -65,8 +54,8 @@ pub export fn add(a: i32, b: i32) i32 {
To compile:
```bash terminal icon="terminal"
zig build-lib add.zig -dynamic -OReleaseFast
```bash
$ zig build-lib add.zig -dynamic -OReleaseFast
```
Pass a path to the shared library and a map of symbols to import into `dlopen`:
@@ -100,7 +89,7 @@ pub extern "C" fn add(a: i32, b: i32) -> i32 {
To compile:
```bash
rustc --crate-type cdylib add.rs
$ rustc --crate-type cdylib add.rs
```
### C++
@@ -116,11 +105,9 @@ extern "C" int32_t add(int32_t a, int32_t b) {
To compile:
```bash
zig build-lib add.cpp -dynamic -lc -lc++
$ zig build-lib add.cpp -dynamic -lc -lc++
```
---
## FFI types
The following `FFIType` values are supported.
@@ -150,13 +137,11 @@ The following `FFIType` values are supported.
Note: `buffer` arguments must be a `TypedArray` or `DataView`.
---
## Strings
JavaScript strings and C-like strings are different, and that complicates using strings with native libraries.
<Accordion title="How are JavaScript strings and C strings different?">
{% details summary="How are JavaScript strings and C strings different?" %}
JavaScript strings:
- UTF16 (2 bytes per letter) or potentially latin1, depending on the JavaScript engine &amp; what characters are used
@@ -169,7 +154,7 @@ C strings:
- The length is not stored. Instead, the string is null-terminated which means the length is the index of the first `\0` it finds
- Mutable
</Accordion>
{% /details %}
To solve this, `bun:ffi` exports `CString` which extends JavaScript's built-in `String` to support null-terminated strings and add a few extras:
@@ -216,11 +201,13 @@ console.log(myString);
When used in `returns`, `FFIType.cstring` coerces the pointer to a JavaScript `string`. When used in `args`, `FFIType.cstring` is identical to `ptr`.
---
## Function pointers
<Note>Async functions are not yet supported</Note>
{% callout %}
**Note** — Async functions are not yet supported.
{% /callout %}
To call a function pointer from JavaScript, use `CFunction`. This is useful if using Node-API (napi) with Bun, and you've already loaded some symbols.
@@ -268,11 +255,13 @@ const lib = linkSymbols({
},
});
const [major, minor, patch] = [lib.symbols.getMajor(), lib.symbols.getMinor(), lib.symbols.getPatch()];
const [major, minor, patch] = [
lib.symbols.getMajor(),
lib.symbols.getMinor(),
lib.symbols.getPatch(),
];
```
---
## Callbacks
Use `JSCallback` to create JavaScript callback functions that can be passed to C/FFI functions. The C/FFI function can call into the JavaScript/TypeScript code. This is useful for asynchronous code or whenever you want to call into JavaScript code from C.
@@ -290,10 +279,13 @@ const {
},
});
const searchIterator = new JSCallback((ptr, length) => /hello/.test(new CString(ptr, length)), {
returns: "bool",
args: ["ptr", "usize"],
});
const searchIterator = new JSCallback(
(ptr, length) => /hello/.test(new CString(ptr, length)),
{
returns: "bool",
args: ["ptr", "usize"],
},
);
const str = Buffer.from("wwutwutwutwutwutwutwutwutwutwutut\0", "utf8");
if (search(ptr(str), searchIterator)) {
@@ -313,17 +305,21 @@ When you're done with a JSCallback, you should call `close()` to free the memory
`JSCallback` has experimental support for thread-safe callbacks. This will be needed if you pass a callback function into a different thread from its instantiation context. You can enable it with the optional `threadsafe` parameter.
Currently, thread-safe callbacks work best when run from another thread that is running JavaScript code, i.e. a [`Worker`](/runtime/workers). A future version of Bun will enable them to be called from any thread (such as new threads spawned by your native library that Bun is not aware of).
Currently, thread-safe callbacks work best when run from another thread that is running JavaScript code, i.e. a [`Worker`](/docs/api/workers). A future version of Bun will enable them to be called from any thread (such as new threads spawned by your native library that Bun is not aware of).
```ts
const searchIterator = new JSCallback((ptr, length) => /hello/.test(new CString(ptr, length)), {
returns: "bool",
args: ["ptr", "usize"],
threadsafe: true, // Optional. Defaults to `false`
});
const searchIterator = new JSCallback(
(ptr, length) => /hello/.test(new CString(ptr, length)),
{
returns: "bool",
args: ["ptr", "usize"],
threadsafe: true, // Optional. Defaults to `false`
},
);
```
<Note>
{% callout %}
**⚡️ Performance tip** — For a slight performance boost, directly pass `JSCallback.prototype.ptr` instead of the `JSCallback` object:
```ts
@@ -344,23 +340,17 @@ setOnResolve(onResolve.ptr);
setOnResolve(onResolve);
```
</Note>
---
{% /callout %}
## Pointers
Bun represents [pointers](<https://en.wikipedia.org/wiki/Pointer_(computer_programming)>) as a `number` in JavaScript.
<Accordion title="How does a 64 bit pointer fit in a JavaScript number?">
{% details summary="How does a 64 bit pointer fit in a JavaScript number?" %}
64-bit processors support up to [52 bits of addressable space](https://en.wikipedia.org/wiki/64-bit_computing#Limits_of_processors). [JavaScript numbers](https://en.wikipedia.org/wiki/Double-precision_floating-point_format#IEEE_754_double-precision_binary_floating-point_format:_binary64) support 53 bits of usable space, so that leaves us with about 11 bits of extra space.
**Why not `BigInt`?** `BigInt` is slower. JavaScript engines allocate a separate `BigInt` which means they can't fit into a regular JavaScript value. If you pass a `BigInt` to a function, it will be converted to a `number`
**Windows Note**: The Windows API type HANDLE does not represent a virtual address, and using `ptr` for it will _not_ work as expected. Use `u64` to safely represent HANDLE values.
</Accordion>
{% /details %}
To convert from a `TypedArray` to a pointer:
@@ -513,7 +503,7 @@ const out = encode_png(
The [auto-generated wrapper](https://github.com/oven-sh/bun/blob/6a65631cbdcae75bfa1e64323a6ad613a922cd1a/src/bun.js/ffi.exports.js#L180-L182) converts the pointer to a `TypedArray`.
<Accordion title="Hardmode">
{% details summary="Hardmode" %}
If you don't want the automatic conversion or you want a pointer to a specific byte offset within the `TypedArray`, you can also directly get the pointer to the `TypedArray`:
@@ -545,7 +535,7 @@ const out = encode_png(
);
```
</Accordion>
{% /details %}
### Reading pointers

View File

@@ -1,15 +1,12 @@
---
title: File I/O
description: Bun provides a set of optimized APIs for reading and writing files.
---
{% callout %}
<Note>
<!-- **Note** — The `Bun.file` and `Bun.write` APIs documented on this page are heavily optimized and represent the recommended way to perform file-system tasks using Bun. Existing Node.js projects may use Bun's [nearly complete](https://bun.com/docs/runtime/nodejs-apis#node-fs) implementation of the [`node:fs`](https://nodejs.org/api/fs.html) module. -->
The `Bun.file` and `Bun.write` APIs documented on this page are heavily optimized and represent the recommended way to perform file-system tasks using Bun. For operations that are not yet available with `Bun.file`, such as `mkdir` or `readdir`, you can use Bun's [nearly complete](/runtime/nodejs-compat#node-fs) implementation of the [`node:fs`](https://nodejs.org/api/fs.html) module.
**Note** — The `Bun.file` and `Bun.write` APIs documented on this page are heavily optimized and represent the recommended way to perform file-system tasks using Bun. For operations that are not yet available with `Bun.file`, such as `mkdir` or `readdir`, you can use Bun's [nearly complete](https://bun.com/docs/runtime/nodejs-apis#node-fs) implementation of the [`node:fs`](https://nodejs.org/api/fs.html) module.
</Note>
{% /callout %}
---
Bun provides a set of optimized APIs for reading and writing files.
## Reading files (`Bun.file()`)
@@ -29,7 +26,6 @@ The reference conforms to the [`Blob`](https://developer.mozilla.org/en-US/docs/
const foo = Bun.file("foo.txt");
await foo.text(); // contents as a string
await foo.json(); // contents as a JSON object
await foo.stream(); // contents as ReadableStream
await foo.arrayBuffer(); // contents as ArrayBuffer
await foo.bytes(); // contents as Uint8Array
@@ -74,8 +70,6 @@ You can delete a file by calling the `.delete()` function.
await Bun.file("logs.json").delete();
```
---
## Writing files (`Bun.write()`)
`Bun.write(destination, data): Promise<number>`
@@ -98,22 +92,88 @@ The second argument is the data to be written. It can be any of the following:
All possible permutations are handled using the fastest available system calls on the current platform.
<Accordion title="See syscalls">
{% details summary="See syscalls" %}
| Output | Input | System call | Platform |
| -------------------- | -------------- | ----------------------------- | -------- |
| file | file | copy_file_range | Linux |
| file | pipe | sendfile | Linux |
| pipe | pipe | splice | Linux |
| terminal | file | sendfile | Linux |
| terminal | terminal | sendfile | Linux |
| socket | file or pipe | sendfile (if http, not https) | Linux |
| file (doesn't exist) | file (path) | clonefile | macOS |
| file (exists) | file | fcopyfile | macOS |
| file | Blob or string | write | macOS |
| file | Blob or string | write | Linux |
{% table %}
</Accordion>
- Output
- Input
- System call
- Platform
---
- file
- file
- copy_file_range
- Linux
---
- file
- pipe
- sendfile
- Linux
---
- pipe
- pipe
- splice
- Linux
---
- terminal
- file
- sendfile
- Linux
---
- terminal
- terminal
- sendfile
- Linux
---
- socket
- file or pipe
- sendfile (if http, not https)
- Linux
---
- file (doesn't exist)
- file (path)
- clonefile
- macOS
---
- file (exists)
- file
- fcopyfile
- macOS
---
- file
- Blob or string
- write
- macOS
---
- file
- Blob or string
- write
- Linux
{% /table %}
{% /details %}
To write a string to disk:
@@ -124,7 +184,7 @@ await Bun.write("output.txt", data);
To copy a file to another location on disk:
```ts
```js
const input = Bun.file("input.txt");
const output = Bun.file("output.txt"); // doesn't exist yet!
await Bun.write(output, input);
@@ -152,8 +212,6 @@ const response = await fetch("https://bun.com");
await Bun.write("index.html", response);
```
---
## Incremental writing with `FileSink`
Bun provides a native incremental file writing API called `FileSink`. To retrieve a `FileSink` instance from a `BunFile`:
@@ -201,8 +259,6 @@ writer.unref();
writer.ref();
```
---
## Directories
Bun's implementation of `node:fs` is fast, and we haven't implemented a Bun-specific API for reading directories just yet. For now, you should use `node:fs` for working with directories in Bun.
@@ -239,15 +295,13 @@ import { mkdir } from "node:fs/promises";
await mkdir("path/to/dir", { recursive: true });
```
---
## Benchmarks
The following is a 3-line implementation of the Linux `cat` command.
```ts cat.ts icon="/icons/typescript.svg"
```ts#cat.ts
// Usage
// bun ./cat.ts ./path-to-file
// $ bun ./cat.ts ./path-to-file
import { resolve } from "path";
@@ -257,15 +311,13 @@ await Bun.write(Bun.stdout, Bun.file(path));
To run the file:
```bash terminal icon="terminal"
bun ./cat.ts ./path-to-file
```bash
$ bun ./cat.ts ./path-to-file
```
It runs 2x faster than GNU `cat` for large files on Linux.
<Frame>![Cat screenshot](/images/cat.jpg)</Frame>
---
{% image src="/images/cat.jpg" /%}
## Reference
@@ -279,7 +331,13 @@ interface Bun {
write(
destination: string | number | BunFile | URL,
input: string | Blob | ArrayBuffer | SharedArrayBuffer | TypedArray | Response,
input:
| string
| Blob
| ArrayBuffer
| SharedArrayBuffer
| TypedArray
| Response,
): Promise<number>;
}
@@ -296,7 +354,9 @@ interface BunFile {
}
export interface FileSink {
write(chunk: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer): number;
write(
chunk: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer,
): number;
flush(): number | Promise<number>;
end(error?: Error): number | Promise<number>;
start(options?: { highWaterMark?: number }): void;

View File

@@ -1,9 +1,4 @@
---
title: File System Router
description: Bun provides a fast API for resolving routes against file-system paths
---
This API is primarily intended for library authors. At the moment only Next.js-style file-system routing is supported, but other styles may be added in the future.
Bun provides a fast API for resolving routes against file-system paths. This API is primarily intended for library authors. At the moment only Next.js-style file-system routing is supported, but other styles may be added in the future.
## Next.js-style
@@ -21,14 +16,13 @@ pages
The `FileSystemRouter` can be used to resolve routes against this directory:
```ts router.ts
```ts
const router = new Bun.FileSystemRouter({
style: "nextjs",
dir: "./pages",
origin: "https://mydomain.com",
assetPrefix: "_next/static/"
});
router.match("/");
// =>

19
docs/api/file.md Normal file
View File

@@ -0,0 +1,19 @@
Bun.js has fast paths for common use cases that make Web APIs live up to the performance demands of servers and CLIs.
`Bun.file(path)` returns a [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob) that represents a lazily-loaded file.
When you pass a file blob to `Bun.write`, Bun automatically uses a faster system call:
```js
const blob = Bun.file("input.txt");
await Bun.write("output.txt", blob);
```
On Linux, this uses the [`copy_file_range`](https://man7.org/linux/man-pages/man2/copy_file_range.2.html) syscall and on macOS, this becomes `clonefile` (or [`fcopyfile`](https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/copyfile.3.html)).
`Bun.write` also supports [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) objects. It automatically converts to a [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob).
```js
// Eventually, this will stream the response to disk but today it buffers
await Bun.write("index.html", await fetch("https://example.com"));
```

View File

@@ -1,7 +1,4 @@
---
title: Glob
description: Use Bun's fast native implementation of file globbing
---
Bun includes a fast native implementation of file globbing.
## Quickstart

387
docs/api/globals.md Normal file
View File

@@ -0,0 +1,387 @@
Bun implements the following globals.
{% table %}
- Global
- Source
- Notes
---
- [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController)
- Web
- &nbsp;
---
- [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal)
- Web
- &nbsp;
---
- [`alert`](https://developer.mozilla.org/en-US/docs/Web/API/Window/alert)
- Web
- Intended for command-line tools
---
- [`Blob`](https://developer.mozilla.org/en-US/docs/Web/API/Blob)
- Web
- &nbsp;
---
- [`Buffer`](https://nodejs.org/api/buffer.html#class-buffer)
- Node.js
- See [Node.js > `Buffer`](https://bun.com/docs/runtime/nodejs-apis#node-buffer)
---
- `Bun`
- Bun
- Subject to change as additional APIs are added
---
- [`ByteLengthQueuingStrategy`](https://developer.mozilla.org/en-US/docs/Web/API/ByteLengthQueuingStrategy)
- Web
- &nbsp;
---
- [`confirm`](https://developer.mozilla.org/en-US/docs/Web/API/Window/confirm)
- Web
- Intended for command-line tools
---
- [`__dirname`](https://nodejs.org/api/globals.html#__dirname)
- Node.js
- &nbsp;
---
- [`__filename`](https://nodejs.org/api/globals.html#__filename)
- Node.js
- &nbsp;
---
- [`atob()`](https://developer.mozilla.org/en-US/docs/Web/API/atob)
- Web
- &nbsp;
---
- [`btoa()`](https://developer.mozilla.org/en-US/docs/Web/API/btoa)
- Web
- &nbsp;
---
- `BuildMessage`
- Bun
- &nbsp;
---
- [`clearImmediate()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/clearImmediate)
- Web
- &nbsp;
---
- [`clearInterval()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/clearInterval)
- Web
- &nbsp;
---
- [`clearTimeout()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/clearTimeout)
- Web
- &nbsp;
---
- [`console`](https://developer.mozilla.org/en-US/docs/Web/API/console)
- Web
- &nbsp;
---
- [`CountQueuingStrategy`](https://developer.mozilla.org/en-US/docs/Web/API/CountQueuingStrategy)
- Web
- &nbsp;
---
- [`Crypto`](https://developer.mozilla.org/en-US/docs/Web/API/Crypto)
- Web
- &nbsp;
---
- [`crypto`](https://developer.mozilla.org/en-US/docs/Web/API/crypto)
- Web
- &nbsp;
---
- [`CryptoKey`](https://developer.mozilla.org/en-US/docs/Web/API/CryptoKey)
- Web
- &nbsp;
---
- [`CustomEvent`](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent)
- Web
- &nbsp;
---
- [`Event`](https://developer.mozilla.org/en-US/docs/Web/API/Event)
- Web
- Also [`ErrorEvent`](https://developer.mozilla.org/en-US/docs/Web/API/ErrorEvent) [`CloseEvent`](https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent) [`MessageEvent`](https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent).
---
- [`EventTarget`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget)
- Web
- &nbsp;
---
- [`exports`](https://nodejs.org/api/globals.html#exports)
- Node.js
- &nbsp;
---
- [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/fetch)
- Web
- &nbsp;
---
- [`FormData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData)
- Web
- &nbsp;
---
- [`global`](https://nodejs.org/api/globals.html#global)
- Node.js
- See [Node.js > `global`](https://bun.com/docs/runtime/nodejs-apis#global).
---
- [`globalThis`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis)
- Cross-platform
- Aliases to `global`
---
- [`Headers`](https://developer.mozilla.org/en-US/docs/Web/API/Headers)
- Web
- &nbsp;
---
- [`HTMLRewriter`](https://bun.com/docs/api/html-rewriter)
- Cloudflare
- &nbsp;
---
- [`JSON`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON)
- Web
- &nbsp;
---
- [`MessageEvent`](https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent)
- Web
- &nbsp;
---
- [`module`](https://nodejs.org/api/globals.html#module)
- Node.js
- &nbsp;
---
- [`performance`](https://developer.mozilla.org/en-US/docs/Web/API/performance)
- Web
- &nbsp;
---
- [`process`](https://nodejs.org/api/process.html)
- Node.js
- See [Node.js > `process`](https://bun.com/docs/runtime/nodejs-apis#node-process)
---
- [`prompt`](https://developer.mozilla.org/en-US/docs/Web/API/Window/prompt)
- Web
- Intended for command-line tools
---
- [`queueMicrotask()`](https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask)
- Web
- &nbsp;
---
- [`ReadableByteStreamController`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableByteStreamController)
- Web
- &nbsp;
---
- [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream)
- Web
- &nbsp;
---
- [`ReadableStreamDefaultController`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultController)
- Web
- &nbsp;
---
- [`ReadableStreamDefaultReader`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultReader)
- Web
- &nbsp;
---
- [`reportError`](https://developer.mozilla.org/en-US/docs/Web/API/reportError)
- Web
- &nbsp;
---
- [`require()`](https://nodejs.org/api/globals.html#require)
- Node.js
- &nbsp;
---
- `ResolveMessage`
- Bun
- &nbsp;
---
- [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response)
- Web
- &nbsp;
---
- [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request)
- Web
- &nbsp;
---
- [`setImmediate()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/setImmediate)
- Web
- &nbsp;
---
- [`setInterval()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/setInterval)
- Web
- &nbsp;
---
- [`setTimeout()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/setTimeout)
- Web
- &nbsp;
---
- [`ShadowRealm`](https://github.com/tc39/proposal-shadowrealm)
- Web
- Stage 3 proposal
---
- [`SubtleCrypto`](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto)
- Web
- &nbsp;
---
- [`DOMException`](https://developer.mozilla.org/en-US/docs/Web/API/DOMException)
- Web
- &nbsp;
---
- [`TextDecoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder)
- Web
- &nbsp;
---
- [`TextEncoder`](https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder)
- Web
- &nbsp;
---
- [`TransformStream`](https://developer.mozilla.org/en-US/docs/Web/API/TransformStream)
- Web
- &nbsp;
---
- [`TransformStreamDefaultController`](https://developer.mozilla.org/en-US/docs/Web/API/TransformStreamDefaultController)
- Web
- &nbsp;
---
- [`URL`](https://developer.mozilla.org/en-US/docs/Web/API/URL)
- Web
- &nbsp;
---
- [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams)
- Web
- &nbsp;
---
- [`WebAssembly`](https://nodejs.org/api/globals.html#webassembly)
- Web
- &nbsp;
---
- [`WritableStream`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStream)
- Web
- &nbsp;
---
- [`WritableStreamDefaultController`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStreamDefaultController)
- Web
- &nbsp;
---
- [`WritableStreamDefaultWriter`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStreamDefaultWriter)
- Web
- &nbsp;
{% /table %}

View File

@@ -1,14 +1,8 @@
---
title: Hashing
description: Bun provides a set of utility functions for hashing and verifying passwords with various cryptographically secure algorithms
---
{% callout %}
<Note>
Bun implements the `createHash` and `createHmac` functions from [`node:crypto`](https://nodejs.org/api/crypto.html) in
addition to the Bun-native APIs documented below.
</Note>
Bun implements the `createHash` and `createHmac` functions from [`node:crypto`](https://nodejs.org/api/crypto.html) in addition to the Bun-native APIs documented below.
---
{% /callout %}
## `Bun.password`
@@ -138,8 +132,6 @@ The format is composed of:
- `salt`: `$xXnlSvPh4ym5KYmxKAuuHVlDvy2QGHBNuI6bJJrRDOs`
- `hash`: `$2YY6M48XmHn+s5NoBaL+ficzXajq2Yj8wut3r0vnrwI`
---
## `Bun.hash`
`Bun.hash` is a collection of utilities for _non-cryptographic_ hashing. Non-cryptographic hashing algorithms are optimized for speed of computation over collision-resistance or security.
@@ -186,14 +178,13 @@ Bun.hash.murmur64v2("data", 1234);
Bun.hash.rapidhash("data", 1234);
```
---
## `Bun.CryptoHasher`
`Bun.CryptoHasher` is a general-purpose utility class that lets you incrementally compute a hash of string or binary data using a range of cryptographic hash algorithms. The following algorithms are supported:
- `"blake2b256"`
- `"blake2b512"`
- `"blake2s256"`
- `"md4"`
- `"md5"`
- `"ripemd160"`
@@ -230,11 +221,24 @@ hasher.update(new ArrayBuffer(10));
If a `string` is passed, an optional second parameter can be used to specify the encoding (default `'utf-8'`). The following encodings are supported:
| Category | Encodings |
| -------------------------- | ------------------------------------------- |
| Binary encodings | `"base64"` `"base64url"` `"hex"` `"binary"` |
| Character encodings | `"utf8"` `"utf-8"` `"utf16le"` `"latin1"` |
| Legacy character encodings | `"ascii"` `"binary"` `"ucs2"` `"ucs-2"` |
{% table %}
---
- Binary encodings
- `"base64"` `"base64url"` `"hex"` `"binary"`
---
- Character encodings
- `"utf8"` `"utf-8"` `"utf16le"` `"latin1"`
---
- Legacy character encodings
- `"ascii"` `"binary"` `"ucs2"` `"ucs-2"`
{% /table %}
```ts
hasher.update("hello world"); // defaults to utf8

View File

@@ -1,12 +1,5 @@
---
title: HTMLRewriter
description: Use Bun's HTMLRewriter to transform HTML documents with CSS selectors
---
HTMLRewriter lets you use CSS selectors to transform HTML documents. It works with `Request`, `Response`, as well as `string`. Bun's implementation is based on Cloudflare's [lol-html](https://github.com/cloudflare/lol-html).
---
## Usage
A common usecase is rewriting URLs in HTML content. Here's an example that rewrites image sources and link URLs to use a CDN domain:
@@ -16,12 +9,16 @@ A common usecase is rewriting URLs in HTML content. Here's an example that rewri
const rewriter = new HTMLRewriter().on("img", {
element(img) {
// Famous rickroll video thumbnail
img.setAttribute("src", "https://img.youtube.com/vi/dQw4w9WgXcQ/maxresdefault.jpg");
img.setAttribute(
"src",
"https://img.youtube.com/vi/dQw4w9WgXcQ/maxresdefault.jpg",
);
// Wrap the image in a link to the video
img.before('<a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ" target="_blank">', {
html: true,
});
img.before(
'<a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ" target="_blank">',
{ html: true },
);
img.after("</a>", { html: true });
// Add some fun alt text
@@ -46,22 +43,21 @@ console.log(result);
This replaces all images with a thumbnail of Rick Astley and wraps each `<img>` in a link, producing a diff like this:
{/* prettier-ignore */}
```html
```html-diff
<html>
<body>
<img src="/cat.jpg" /> <!-- [!code --] -->
<img src="dog.png" /> <!-- [!code --] -->
<img src="https://example.com/bird.webp" /> <!-- [!code --] -->
<a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ" target="_blank"> <!-- [!code ++] -->
<img src="https://img.youtube.com/vi/dQw4w9WgXcQ/maxresdefault.jpg" alt="Definitely not a rickroll" /> <!-- [!code ++] -->
</a> <!-- [!code ++] -->
<a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ" target="_blank"> <!-- [!code ++] -->
<img src="https://img.youtube.com/vi/dQw4w9WgXcQ/maxresdefault.jpg" alt="Definitely not a rickroll" /> <!-- [!code ++] -->
</a> <!-- [!code ++] -->
<a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ" target="_blank"> <!-- [!code ++] -->
<img src="https://img.youtube.com/vi/dQw4w9WgXcQ/maxresdefault.jpg" alt="Definitely not a rickroll" /> <!-- [!code ++] -->
</a> <!-- [!code ++] -->
- <img src="/cat.jpg">
- <img src="dog.png">
- <img src="https://example.com/bird.webp">
+ <a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ" target="_blank">
+ <img src="https://img.youtube.com/vi/dQw4w9WgXcQ/maxresdefault.jpg" alt="Definitely not a rickroll">
+ </a>
+ <a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ" target="_blank">
+ <img src="https://img.youtube.com/vi/dQw4w9WgXcQ/maxresdefault.jpg" alt="Definitely not a rickroll">
+ </a>
+ <a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ" target="_blank">
+ <img src="https://img.youtube.com/vi/dQw4w9WgXcQ/maxresdefault.jpg" alt="Definitely not a rickroll">
+ </a>
</body>
</html>
```
@@ -184,7 +180,10 @@ rewriter.on("div", {
el.setInnerContent(""); // Clear content
// Position manipulation
el.before("Content before").after("Content after").prepend("First child").append("Last child");
el.before("Content before")
.after("Content after")
.prepend("First child")
.append("Last child");
// HTML content insertion
el.before("<span>before</span>", { html: true })
@@ -256,7 +255,11 @@ rewriter.on("*", {
console.log(comment.removed); // Whether comment was removed
// Manipulation
comment.before("Before comment").after("After comment").replace("New comment").remove();
comment
.before("Before comment")
.after("After comment")
.replace("New comment")
.remove();
// HTML content insertion
comment
@@ -326,8 +329,6 @@ try {
}
```
---
## See also
You can also read the [Cloudflare documentation](https://developers.cloudflare.com/workers/runtime-apis/html-rewriter/), which this API is intended to be compatible with.

1408
docs/api/http.md Normal file

File diff suppressed because it is too large Load Diff

69
docs/api/import-meta.md Normal file
View File

@@ -0,0 +1,69 @@
The `import.meta` object is a way for a module to access information about itself. It's part of the JavaScript language, but its contents are not standardized. Each "host" (browser, runtime, etc) is free to implement any properties it wishes on the `import.meta` object.
Bun implements the following properties.
```ts#/path/to/project/file.ts
import.meta.dir; // => "/path/to/project"
import.meta.file; // => "file.ts"
import.meta.path; // => "/path/to/project/file.ts"
import.meta.url; // => "file:///path/to/project/file.ts"
import.meta.main; // `true` if this file is directly executed by `bun run`
// `false` otherwise
import.meta.resolve("zod"); // => "file:///path/to/project/node_modules/zod/index.js"
```
{% table %}
---
- `import.meta.dir`
- Absolute path to the directory containing the current file, e.g. `/path/to/project`. Equivalent to `__dirname` in CommonJS modules (and Node.js)
---
- `import.meta.dirname`
- An alias to `import.meta.dir`, for Node.js compatibility
---
- `import.meta.env`
- An alias to `process.env`.
---
- `import.meta.file`
- The name of the current file, e.g. `index.tsx`
---
- `import.meta.path`
- Absolute path to the current file, e.g. `/path/to/project/index.ts`. Equivalent to `__filename` in CommonJS modules (and Node.js)
---
- `import.meta.filename`
- An alias to `import.meta.path`, for Node.js compatibility
---
- `import.meta.main`
- Indicates whether the current file is the entrypoint to the current `bun` process. Is the file being directly executed by `bun run` or is it being imported?
---
- `import.meta.resolve`
- Resolve a module specifier (e.g. `"zod"` or `"./file.tsx"`) to a url. Equivalent to [`import.meta.resolve` in browsers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import.meta#resolve)
```ts
import.meta.resolve("zod");
// => "file:///path/to/project/node_modules/zod/index.ts"
```
---
- `import.meta.url`
- A `string` url to the current file, e.g. `file:///path/to/project/index.ts`. Equivalent to [`import.meta.url` in browsers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import.meta#url)
{% /table %}

View File

@@ -1,8 +1,3 @@
---
title: Node-API
description: Use Bun's Node-API module to build native add-ons to Node.js
---
Node-API is an interface for building native add-ons to Node.js. Bun implements 95% of this interface from scratch, so most existing Node-API extensions will work with Bun out of the box. Track the completion status of it in [this issue](https://github.com/oven-sh/bun/issues/158).
As in Node.js, `.node` files (Node-API modules) can be required directly in Bun.

View File

@@ -1,13 +1,6 @@
---
title: Redis
description: Use Bun's native Redis client with a Promise-based API
---
Bun provides native bindings for working with Redis databases with a modern, Promise-based API. The interface is designed to be simple and performant, with built-in connection management, fully typed responses, and TLS support. **New in Bun v1.2.9**
<Note>Bun's Redis client supports Redis server versions 7.2 and up.</Note>
Bun provides native bindings for working with Redis databases with a modern, Promise-based API. The interface is designed to be simple and performant, with built-in connection management, fully typed responses, and TLS support.
```ts redis.ts icon="/icons/typescript.svg"
```ts
import { redis } from "bun";
// Set a key
@@ -28,13 +21,11 @@ const exists = await redis.exists("greeting");
await redis.del("greeting");
```
---
## Getting Started
To use the Redis client, you first need to create a connection:
```ts redis.ts icon="/icons/typescript.svg"
```ts
import { redis, RedisClient } from "bun";
// Using the default client (reads connection info from environment)
@@ -58,7 +49,7 @@ By default, the client reads connection information from the following environme
The Redis client automatically handles connections in the background:
```ts redis.ts icon="/icons/typescript.svg"
```ts
// No connection is made until a command is executed
const client = new RedisClient();
@@ -74,7 +65,7 @@ client.close();
You can also manually control the connection lifecycle:
```ts redis.ts icon="/icons/typescript.svg"
```ts
const client = new RedisClient();
// Explicitly connect
@@ -87,13 +78,11 @@ await client.set("key", "value");
client.close();
```
---
## Basic Operations
### String Operations
```ts redis.ts icon="/icons/typescript.svg"
```ts
// Set a key
await redis.set("user:1:name", "Alice");
@@ -119,7 +108,7 @@ const ttl = await redis.ttl("session:123");
### Numeric Operations
```ts redis.ts icon="/icons/typescript.svg"
```ts
// Set initial value
await redis.set("counter", "0");
@@ -132,9 +121,16 @@ await redis.decr("counter");
### Hash Operations
```ts redis.ts icon="/icons/typescript.svg"
```ts
// Set multiple fields in a hash
await redis.hmset("user:123", ["name", "Alice", "email", "alice@example.com", "active", "true"]);
await redis.hmset("user:123", [
"name",
"Alice",
"email",
"alice@example.com",
"active",
"true",
]);
// Get multiple fields from a hash
const userFields = await redis.hmget("user:123", ["name", "email"]);
@@ -153,7 +149,7 @@ await redis.hincrbyfloat("user:123", "score", 1.5);
### Set Operations
```ts redis.ts icon="/icons/typescript.svg"
```ts
// Add member to set
await redis.sadd("tags", "javascript");
@@ -173,25 +169,23 @@ const randomTag = await redis.srandmember("tags");
const poppedTag = await redis.spop("tags");
```
---
## Pub/Sub
Bun provides native bindings for the [Redis
Pub/Sub](https://redis.io/docs/latest/develop/pubsub/) protocol. **New in Bun
1.2.23**
<Warning>
The Redis Pub/Sub feature is experimental. Although we expect it to be stable, we're currently actively looking for
feedback and areas for improvement.
</Warning>
{% callout %}
**🚧** — The Redis Pub/Sub feature is experimental. Although we expect it to be
stable, we're currently actively looking for feedback and areas for improvement.
{% /callout %}
### Basic Usage
To get started publishing messages, you can set up a publisher in
`publisher.ts`:
```typescript publisher.ts icon="/icons/typescript.svg"
```typescript#publisher.ts
import { RedisClient } from "bun";
const writer = new RedisClient("redis://localhost:6739");
@@ -204,7 +198,7 @@ writer.close();
In another file, create the subscriber in `subscriber.ts`:
```typescript subscriber.ts icon="/icons/typescript.svg"
```typescript#subscriber.ts
import { RedisClient } from "bun";
const listener = new RedisClient("redis://localhost:6739");
@@ -217,40 +211,40 @@ await listener.subscribe("general", (message, channel) => {
In one shell, run your subscriber:
```bash terminal icon="terminal"
```bash
bun run subscriber.ts
```
and, in another, run your publisher:
```bash terminal icon="terminal"
```bash
bun run publisher.ts
```
<Note>
The subscription mode takes over the `RedisClient` connection. A
{% callout %}
**Note:** The subscription mode takes over the `RedisClient` connection. A
client with subscriptions can only call `RedisClient.prototype.subscribe()`. In
other words, applications which need to message Redis need a separate
connection, acquirable through `.duplicate()`:
```ts redis.ts icon="/icons/typescript.svg"
```typescript
import { RedisClient } from "bun";
const redis = new RedisClient("redis://localhost:6379");
await redis.connect();
const subscriber = await redis.duplicate(); // [!code ++]
const subscriber = await redis.duplicate();
await subscriber.subscribe("foo", () => {});
await redis.set("bar", "baz");
```
</Note>
{% /callout %}
### Publishing
Publishing messages is done through the `publish()` method:
```typescript redis.ts icon="/icons/typescript.svg"
```typescript
await client.publish(channelName, message);
```
@@ -259,13 +253,13 @@ await client.publish(channelName, message);
The Bun `RedisClient` allows you to subscribe to channels through the
`.subscribe()` method:
```typescript redis.ts icon="/icons/typescript.svg"
```typescript
await client.subscribe(channel, (message, channel) => {});
```
You can unsubscribe through the `.unsubscribe()` method:
```typescript redis.ts icon="/icons/typescript.svg"
```typescript
await client.unsubscribe(); // Unsubscribe from all channels.
await client.unsubscribe(channel); // Unsubscribe a particular channel.
await client.unsubscribe(channel, listener); // Unsubscribe a particular listener.
@@ -277,16 +271,19 @@ await client.unsubscribe(channel, listener); // Unsubscribe a particular listene
The client automatically pipelines commands, improving performance by sending multiple commands in a batch and processing responses as they arrive.
```ts redis.ts icon="/icons/typescript.svg"
```ts
// Commands are automatically pipelined by default
const [infoResult, listResult] = await Promise.all([redis.get("user:1:name"), redis.get("user:2:email")]);
const [infoResult, listResult] = await Promise.all([
redis.get("user:1:name"),
redis.get("user:2:email"),
]);
```
To disable automatic pipelining, you can set the `enableAutoPipelining` option to `false`:
```ts redis.ts icon="/icons/typescript.svg"
```ts
const client = new RedisClient("redis://localhost:6379", {
enableAutoPipelining: false, // [!code ++]
enableAutoPipelining: false,
});
```
@@ -294,7 +291,7 @@ const client = new RedisClient("redis://localhost:6379", {
When you need to use commands that don't have convenience methods, you can use the `send` method:
```ts redis.ts icon="/icons/typescript.svg"
```ts
// Run any Redis command
const info = await redis.send("INFO", []);
@@ -311,7 +308,7 @@ The `send` method allows you to use any Redis command, even ones that don't have
You can register handlers for connection events:
```ts redis.ts icon="/icons/typescript.svg"
```ts
const client = new RedisClient();
// Called when successfully connected to Redis server
@@ -331,7 +328,7 @@ client.close();
### Connection Status and Monitoring
```ts redis.ts icon="/icons/typescript.svg"
```ts
// Check if connected
console.log(client.connected); // boolean indicating connection status
@@ -376,13 +373,11 @@ The following commands disable automatic pipelining:
- `UNSUBSCRIBE`
- `UNPSUBSCRIBE`
---
## Connection Options
When creating a client, you can pass various options to configure the connection:
```ts redis.ts icon="/icons/typescript.svg"
```ts
const client = new RedisClient("redis://localhost:6379", {
// Connection timeout in milliseconds (default: 10000)
connectionTimeout: 5000,
@@ -425,13 +420,11 @@ When a connection is lost, the client automatically attempts to reconnect with e
- Queued if `enableOfflineQueue` is true (default)
- Rejected immediately if `enableOfflineQueue` is false
---
## Supported URL Formats
The Redis client supports various URL formats:
```ts redis.ts icon="/icons/typescript.svg"
```ts
// Standard Redis URL
new RedisClient("redis://localhost:6379");
new RedisClient("redis://localhost:6379");
@@ -457,13 +450,11 @@ new RedisClient("redis+tls+unix:///path/to/socket");
new RedisClient("redis+tls+unix:///path/to/socket");
```
---
## Error Handling
The Redis client throws typed errors for different scenarios:
```ts redis.ts icon="/icons/typescript.svg"
```ts
try {
await redis.get("non-existent-key");
} catch (error) {
@@ -483,13 +474,11 @@ Common error codes:
- `ERR_REDIS_AUTHENTICATION_FAILED` - Failed to authenticate with the server
- `ERR_REDIS_INVALID_RESPONSE` - Received an invalid response from the server
---
## Example Use Cases
### Caching
```ts redis.ts icon="/icons/typescript.svg"
```ts
async function getUserWithCache(userId) {
const cacheKey = `user:${userId}`;
@@ -512,7 +501,7 @@ async function getUserWithCache(userId) {
### Rate Limiting
```ts redis.ts icon="/icons/typescript.svg"
```ts
async function rateLimit(ip, limit = 100, windowSecs = 3600) {
const key = `ratelimit:${ip}`;
@@ -534,13 +523,20 @@ async function rateLimit(ip, limit = 100, windowSecs = 3600) {
### Session Storage
```ts redis.ts icon="/icons/typescript.svg"
```ts
async function createSession(userId, data) {
const sessionId = crypto.randomUUID();
const key = `session:${sessionId}`;
// Store session with expiration
await redis.hmset(key, ["userId", userId.toString(), "created", Date.now().toString(), "data", JSON.stringify(data)]);
await redis.hmset(key, [
"userId",
userId.toString(),
"created",
Date.now().toString(),
"data",
JSON.stringify(data),
]);
await redis.expire(key, 86400); // 24 hours
return sessionId;
@@ -553,7 +549,11 @@ async function getSession(sessionId) {
const exists = await redis.exists(key);
if (!exists) return null;
const [userId, created, data] = await redis.hmget(key, ["userId", "created", "data"]);
const [userId, created, data] = await redis.hmget(key, [
"userId",
"created",
"data",
]);
return {
userId: Number(userId),
@@ -563,19 +563,33 @@ async function getSession(sessionId) {
}
```
---
## Implementation Notes
Bun's Redis client is implemented in Zig and uses the Redis Serialization Protocol (RESP3). It manages connections efficiently and provides automatic reconnection with exponential backoff.
The client supports pipelining commands, meaning multiple commands can be sent without waiting for the replies to previous commands. This significantly improves performance when sending multiple commands in succession.
### RESP3 Protocol Support
Bun's Redis client uses the newer RESP3 protocol by default, which provides more data types and features compared to RESP2:
- Better error handling with typed errors
- Native Boolean responses
- Map/Dictionary responses (key-value objects)
- Set responses
- Double (floating point) values
- BigNumber support for large integer values
When connecting to Redis servers using older versions that don't support RESP3, the client automatically fallbacks to compatible modes.
## Limitations and Future Plans
Current limitations of the Redis client we are planning to address in future versions:
- Transactions (MULTI/EXEC) must be done through raw commands for now
- [ ] Transactions (MULTI/EXEC) must be done through raw commands for now
- [ ] Streams are supported but without dedicated methods
- [ ] Pub/Sub does not currently support binary data, nor pattern-based
subscriptions.
Unsupported features:

View File

@@ -1,19 +1,16 @@
---
title: S3
description: Bun provides fast, native bindings for interacting with S3-compatible object storage services.
---
Production servers often read, upload, and write files to S3-compatible object storage services instead of the local filesystem. Historically, that means local filesystem APIs you use in development can't be used in production. When you use Bun, things are different.
{% callout %}
### Bun's S3 API is fast
<Frame caption="Left: Bun v1.1.44. Right: Node.js v23.6.0">
<img src="/images/bun-s3-node.gif" alt="Bun's S3 API is fast" />
</Frame>
{% image src="https://bun.com/bun-s3-node.gif" alt="Bun's S3 API is fast" caption="Left: Bun v1.1.44. Right: Node.js v23.6.0" /%}
{% /callout %}
Bun provides fast, native bindings for interacting with S3-compatible object storage services. Bun's S3 API is designed to be simple and feel similar to fetch's `Response` and `Blob` APIs (like Bun's local filesystem APIs).
```ts s3.ts icon="/icons/typescript.svg"
```ts
import { s3, write, S3Client } from "bun";
// Bun.s3 reads environment variables for credentials
@@ -55,7 +52,7 @@ There are several ways to interact with Bun's S3 API.
To explicitly set credentials, pass them to the `Bun.S3Client` constructor.
```ts s3.ts icon="/icons/typescript.svg"
```ts
import { S3Client } from "bun";
const client = new S3Client({
@@ -77,7 +74,7 @@ const client = new S3Client({
The **`file`** method in `S3Client` returns a **lazy reference to a file on S3**.
```ts s3.ts icon="/icons/typescript.svg"
```ts
// A lazy reference to a file on S3
const s3file: S3File = client.file("123.json");
```
@@ -88,7 +85,7 @@ Like `Bun.file(path)`, the `S3Client`'s `file` method is synchronous. It does ze
If you've used the `fetch` API, you're familiar with the `Response` and `Blob` APIs. `S3File` extends `Blob`. The same methods that work on `Blob` also work on `S3File`.
```ts s3.ts icon="/icons/typescript.svg"
```ts
// Read an S3File as text
const text = await s3file.text();
@@ -120,7 +117,7 @@ These helper methods not only simplify the API, they also make it faster.
Writing to S3 is just as simple.
```ts s3.ts icon="/icons/typescript.svg"
```ts
// Write a string (replacing the file)
await s3file.write("Hello World!");
@@ -149,7 +146,7 @@ await Bun.write(s3file, "Hello World!");
Bun automatically handles multipart uploads for large files and provides streaming capabilities. The same API that works for local files also works for S3 files.
```ts s3.ts icon="/icons/typescript.svg"
```ts
// Write a large file
const bigFile = Buffer.alloc(10 * 1024 * 1024); // 10MB
const writer = s3file.writer({
@@ -169,8 +166,6 @@ for (let i = 0; i < 10; i++) {
await writer.end();
```
---
## Presigning URLs
When your production service needs to let users upload files to your server, it's often more reliable for the user to upload directly to S3 instead of your server acting as an intermediary.
@@ -179,7 +174,7 @@ To facilitate this, you can presign URLs for S3 files. This generates a URL with
The default behaviour is to generate a `GET` URL that expires in 24 hours. Bun attempts to infer the content type from the file extension. If inference is not possible, it will default to `application/octet-stream`.
```ts s3.ts icon="/icons/typescript.svg"
```ts
import { s3 } from "bun";
// Generate a presigned URL that expires in 24 hours (default)
@@ -203,7 +198,7 @@ const presignedFile = myFile.presign({
To set an ACL (access control list) on a presigned URL, pass the `acl` option:
```ts s3.ts icon="/icons/typescript.svg"
```ts
const url = s3file.presign({
acl: "public-read",
expiresIn: 3600,
@@ -227,7 +222,7 @@ You can pass any of the following ACLs:
To set an expiration time for a presigned URL, pass the `expiresIn` option.
```ts s3.ts icon="/icons/typescript.svg"
```ts
const url = s3file.presign({
// Seconds
expiresIn: 3600, // 1 hour
@@ -244,7 +239,7 @@ const url = s3file.presign({
To set the HTTP method for a presigned URL, pass the `method` option.
```ts s3.ts icon="/icons/typescript.svg"
```ts
const url = s3file.presign({
method: "PUT",
// method: "DELETE",
@@ -259,14 +254,14 @@ const url = s3file.presign({
To quickly redirect users to a presigned URL for an S3 file, pass an `S3File` instance to a `Response` object as the body.
This will automatically redirect the user to the presigned URL for the S3 file, saving you the memory, time, and bandwidth cost of downloading the file to your server and sending it back to the user.
```ts s3.ts icon="/icons/typescript.svg"
```ts
const response = new Response(s3file);
console.log(response);
```
```txt
This will automatically redirect the user to the presigned URL for the S3 file, saving you the memory, time, and bandwidth cost of downloading the file to your server and sending it back to the user.
```ts
Response (0 KB) {
ok: false,
url: "",
@@ -280,8 +275,6 @@ Response (0 KB) {
}
```
---
## Support for S3-Compatible Services
Bun's S3 implementation works with any S3-compatible storage service. Just specify the appropriate endpoint:
@@ -290,7 +283,7 @@ Bun's S3 implementation works with any S3-compatible storage service. Just speci
AWS S3 is the default. You can also pass a `region` option instead of an `endpoint` option for AWS S3.
```ts s3.ts icon="/icons/typescript.svg"
```ts
import { S3Client } from "bun";
// AWS S3
@@ -307,7 +300,7 @@ const s3 = new S3Client({
To use Bun's S3 client with [Google Cloud Storage](https://cloud.google.com/storage), set `endpoint` to `"https://storage.googleapis.com"` in the `S3Client` constructor.
```ts s3.ts icon="/icons/typescript.svg" highlight={8}
```ts
import { S3Client } from "bun";
// Google Cloud Storage
@@ -323,7 +316,7 @@ const gcs = new S3Client({
To use Bun's S3 client with [Cloudflare R2](https://developers.cloudflare.com/r2/), set `endpoint` to the R2 endpoint in the `S3Client` constructor. The R2 endpoint includes your account ID.
```ts s3.ts icon="/icons/typescript.svg" highlight={8}
```ts
import { S3Client } from "bun";
// CloudFlare R2
@@ -339,7 +332,7 @@ const r2 = new S3Client({
To use Bun's S3 client with [DigitalOcean Spaces](https://www.digitalocean.com/products/spaces/), set `endpoint` to the DigitalOcean Spaces endpoint in the `S3Client` constructor.
```ts s3.ts icon="/icons/typescript.svg" highlight={8}
```ts
import { S3Client } from "bun";
const spaces = new S3Client({
@@ -355,7 +348,7 @@ const spaces = new S3Client({
To use Bun's S3 client with [MinIO](https://min.io/), set `endpoint` to the URL that MinIO is running on in the `S3Client` constructor.
```ts s3.ts icon="/icons/typescript.svg" highlight={10}
```ts
import { S3Client } from "bun";
const minio = new S3Client({
@@ -371,9 +364,9 @@ const minio = new S3Client({
### Using Bun's S3Client with supabase
To use Bun's S3 client with [supabase](https://supabase.com/), set `endpoint` to the supabase endpoint in the `S3Client` constructor. The supabase endpoint includes your account ID and /storage/v1/s3 path. Make sure to set Enable connection via S3 protocol on in the supabase dashboard in `https://supabase.com/dashboard/project/<account-id>/settings/storage` and to set the region informed in the same section.
To use Bun's S3 client with [supabase](https://supabase.com/), set `endpoint` to the supabase endpoint in the `S3Client` constructor. The supabase endpoint includes your account ID and /storage/v1/s3 path. Make sure to set Enable connection via S3 protocol on in the supabase dashboard in https://supabase.com/dashboard/project/<account-id>/settings/storage and to set the region informed in the same section.
```ts s3.ts icon="/icons/typescript.svg" highlight={3-10}
```ts
import { S3Client } from "bun";
const supabase = new S3Client({
@@ -387,15 +380,9 @@ const supabase = new S3Client({
### Using Bun's S3Client with S3 Virtual Hosted-Style endpoints
When using a S3 Virtual Hosted-Style endpoint, you need to set the `virtualHostedStyle` option to `true`.
When using a S3 Virtual Hosted-Style endpoint, you need to set the `virtualHostedStyle` option to `true` and if no endpoint is provided, Bun will use region and bucket to infer the endpoint to AWS S3, if no region is provided it will use `us-east-1`. If you provide a the endpoint, there are no need to provide the bucket name.
<Note>
- If you dont specify an endpoint, Bun will automatically determine the AWS S3 endpoint using the provided region and
bucket. - If no region is specified, Bun defaults to us-east-1. - If you explicitly provide an endpoint, you dont
need to specify a bucket name.
</Note>
```ts s3.ts icon="/icons/typescript.svg" highlight={17, 25}
```ts
import { S3Client } from "bun";
// AWS S3 endpoint inferred from region and bucket
@@ -403,7 +390,7 @@ const s3 = new S3Client({
accessKeyId: "access-key",
secretAccessKey: "secret-key",
bucket: "my-bucket",
virtualHostedStyle: true, // [!code ++]
virtualHostedStyle: true,
// endpoint: "https://my-bucket.s3.us-east-1.amazonaws.com",
// region: "us-east-1",
});
@@ -413,7 +400,7 @@ const s3WithEndpoint = new S3Client({
accessKeyId: "access-key",
secretAccessKey: "secret-key",
endpoint: "https://<bucket-name>.s3.<region>.amazonaws.com",
virtualHostedStyle: true, // [!code ++]
virtualHostedStyle: true,
});
// Cloudflare R2
@@ -421,12 +408,10 @@ const r2WithEndpoint = new S3Client({
accessKeyId: "access-key",
secretAccessKey: "secret-key",
endpoint: "https://<bucket-name>.<account-id>.r2.cloudflarestorage.com",
virtualHostedStyle: true, // [!code ++]
virtualHostedStyle: true,
});
```
---
## Credentials
Credentials are one of the hardest parts of using S3, and we've tried to make it as easy as possible. By default, Bun reads the following environment variables for credentials.
@@ -451,7 +436,7 @@ If the `S3_*` environment variable is not set, Bun will also check for the `AWS_
| `bucket` | `AWS_BUCKET` |
| `sessionToken` | `AWS_SESSION_TOKEN` |
These environment variables are read from [`.env` files](/runtime/environment-variables) or from the process environment at initialization time (`process.env` is not used for this).
These environment variables are read from [`.env` files](/docs/runtime/env) or from the process environment at initialization time (`process.env` is not used for this).
These defaults are overridden by the options you pass to `s3.file(credentials)`, `new Bun.S3Client(credentials)`, or any of the methods that accept credentials. So if, for example, you use the same credentials for different buckets, you can set the credentials once in your `.env` file and then pass `bucket: "my-bucket"` to the `s3.file()` function without having to specify all the credentials again.
@@ -459,7 +444,7 @@ These defaults are overridden by the options you pass to `s3.file(credentials)`,
When you're not using environment variables or using multiple buckets, you can create a `S3Client` object to explicitly set credentials.
```ts s3.ts icon="/icons/typescript.svg" highlight={3-11}
```ts
import { S3Client } from "bun";
const client = new S3Client({
@@ -489,14 +474,13 @@ await file.delete();
To upload or write a file to S3, call `write` on the `S3Client` instance.
```ts s3.ts icon="/icons/typescript.svg" highlight={8, 9}
```ts
const client = new Bun.S3Client({
accessKeyId: "your-access-key",
secretAccessKey: "your-secret-key",
endpoint: "https://s3.us-east-1.amazonaws.com",
bucket: "my-bucket",
});
await client.write("my-file.txt", "Hello World!");
await client.write("my-file.txt", new Response("Hello World!"));
@@ -508,7 +492,7 @@ await client.write("my-file.txt", new Response("Hello World!"));
To delete a file from S3, call `delete` on the `S3Client` instance.
```ts s3.ts icon="/icons/typescript.svg" highlight={7}
```ts
const client = new Bun.S3Client({
accessKeyId: "your-access-key",
secretAccessKey: "your-secret-key",
@@ -524,7 +508,7 @@ await client.delete("my-file.txt");
To check if a file exists in S3, call `exists` on the `S3Client` instance.
```ts s3.ts icon="/icons/typescript.svg" highlight={7}
```ts
const client = new Bun.S3Client({
accessKeyId: "your-access-key",
secretAccessKey: "your-secret-key",
@@ -540,7 +524,7 @@ const exists = await client.exists("my-file.txt");
`S3File` instances are created by calling the `S3Client` instance method or the `s3.file()` function. Like `Bun.file()`, `S3File` instances are lazy. They don't refer to something that necessarily exists at the time of creation. That's why all the methods that don't involve network requests are fully synchronous.
```ts Type Reference icon="/icons/typescript.svg" expandable
```ts
interface S3File extends Blob {
slice(start: number, end?: number): S3File;
exists(): Promise<boolean>;
@@ -552,7 +536,14 @@ interface S3File extends Blob {
arrayBuffer(): Promise<ArrayBuffer>;
stream(options: S3Options): ReadableStream;
write(
data: string | Uint8Array | ArrayBuffer | Blob | ReadableStream | Response | Request,
data:
| string
| Uint8Array
| ArrayBuffer
| Blob
| ReadableStream
| Response
| Request,
options?: BlobPropertyBag,
): Promise<number>;
@@ -589,7 +580,7 @@ That means using `S3File` instances with `fetch()`, `Response`, and other web AP
To read a partial range of a file, you can use the `slice` method.
```ts s3.ts icon="/icons/typescript.svg" highlight={1}
```ts
const partial = s3file.slice(0, 1024);
// Read the partial range as a Uint8Array
@@ -605,7 +596,7 @@ Internally, this works by using the HTTP `Range` header to request only the byte
To delete a file from S3, you can use the `delete` method.
```ts s3.ts icon="/icons/typescript.svg" highlight={1}
```ts
await s3file.delete();
// await s3File.unlink();
```
@@ -633,7 +624,7 @@ The `S3Client` class provides several static methods for interacting with S3.
To write data directly to a path in the bucket, you can use the `S3Client.write` static method.
```ts s3.ts icon="/icons/typescript.svg" highlight={12, 15-18, 22, 25-29}
```ts
import { S3Client } from "bun";
const credentials = {
@@ -671,7 +662,7 @@ This is equivalent to calling `new S3Client(credentials).write("my-file.txt", "H
To generate a presigned URL for an S3 file, you can use the `S3Client.presign` static method.
```ts s3.ts icon="/icons/typescript.svg" highlight={11-14}
```ts
import { S3Client } from "bun";
const credentials = {
@@ -694,7 +685,7 @@ This is equivalent to calling `new S3Client(credentials).presign("my-file.txt",
To list some or all (up to 1,000) objects in a bucket, you can use the `S3Client.list` static method.
```ts s3.ts icon="/icons/typescript.svg" highlight={12, 15-20, 24-29}
```ts
import { S3Client } from "bun";
const credentials = {
@@ -733,7 +724,7 @@ This is equivalent to calling `new S3Client(credentials).list()`.
To check if an S3 file exists, you can use the `S3Client.exists` static method.
```ts s3.ts icon="/icons/typescript.svg" highlight={11}
```ts
import { S3Client } from "bun";
const credentials = {
@@ -749,13 +740,12 @@ const exists = await S3Client.exists("my-file.txt", credentials);
The same method also works on `S3File` instances.
```ts s3.ts icon="/icons/typescript.svg" highlight=7}
```ts
import { s3 } from "bun";
const s3file = s3.file("my-file.txt", {
// ...credentials,
...credentials,
});
const exists = await s3file.exists();
```
@@ -763,7 +753,7 @@ const exists = await s3file.exists();
To quickly check the size of S3 file without downloading it, you can use the `S3Client.size` static method.
```ts s3.ts icon="/icons/typescript.svg" highlight={11}
```ts
import { S3Client } from "bun";
const credentials = {
@@ -783,7 +773,7 @@ This is equivalent to calling `new S3Client(credentials).size("my-file.txt")`.
To get the size, etag, and other metadata of an S3 file, you can use the `S3Client.stat` static method.
```ts s3.ts icon="/icons/typescript.svg"
```ts
import { S3Client } from "bun";
const credentials = {
@@ -795,22 +785,19 @@ const credentials = {
};
const stat = await S3Client.stat("my-file.txt", credentials);
```
```txt
{
etag: "\"7a30b741503c0b461cc14157e2df4ad8\"",
lastModified: 2025-01-07T00:19:10.000Z,
size: 1024,
type: "text/plain;charset=utf-8",
}
// {
// etag: "\"7a30b741503c0b461cc14157e2df4ad8\"",
// lastModified: 2025-01-07T00:19:10.000Z,
// size: 1024,
// type: "text/plain;charset=utf-8",
// }
```
### `S3Client.delete` (static)
To delete an S3 file, you can use the `S3Client.delete` static method.
```ts s3.ts icon="/icons/typescript.svg" highlight={10, 15}
```ts
import { S3Client } from "bun";
const credentials = {
@@ -832,14 +819,14 @@ await S3Client.unlink("my-file.txt", credentials);
To make it easier to use the same code for local files and S3 files, the `s3://` protocol is supported in `fetch` and `Bun.file()`.
```ts s3.ts icon="/icons/typescript.svg"
```ts
const response = await fetch("s3://my-bucket/my-file.txt");
const file = Bun.file("s3://my-bucket/my-file.txt");
```
You can additionally pass `s3` options to the `fetch` and `Bun.file` functions.
```ts s3.ts icon="/icons/typescript.svg" highlight={2-6}
```ts
const response = await fetch("s3://my-bucket/my-file.txt", {
s3: {
accessKeyId: "your-access-key",
@@ -847,7 +834,7 @@ const response = await fetch("s3://my-bucket/my-file.txt", {
endpoint: "https://s3.us-east-1.amazonaws.com",
},
headers: {
range: "bytes=0-1023",
"range": "bytes=0-1023",
},
});
```

View File

@@ -1,41 +1,30 @@
---
title: Secrets
description: Use Bun's Secrets API to store and retrieve sensitive credentials securely
---
Store and retrieve sensitive credentials securely using the operating system's native credential storage APIs.
<Warning>This API is new and experimental. It may change in the future.</Warning>
**Experimental:** This API is new and experimental. It may change in the future.
```typescript index.ts icon="/icons/typescript.svg"
```typescript
import { secrets } from "bun";
let githubToken: string | null = await secrets.get({
const githubToken = await secrets.get({
service: "my-cli-tool",
name: "github-token",
});
if (!githubToken) {
githubToken = prompt("Please enter your GitHub token");
const response = await fetch("https://api.github.com/name", {
headers: { "Authorization": `token ${githubToken}` },
});
console.log("Please enter your GitHub token");
} else {
await secrets.set({
service: "my-cli-tool",
name: "github-token",
value: githubToken,
value: prompt("Please enter your GitHub token"),
});
console.log("GitHub token stored");
}
const response = await fetch("https://api.github.com/user", {
headers: { Authorization: `token ${githubToken}` },
});
console.log(`Logged in as ${(await response.json()).login}`);
```
---
## Overview
`Bun.secrets` provides a cross-platform API for managing sensitive credentials that CLI tools and development applications typically store in plaintext files like `~/.npmrc`, `~/.aws/credentials`, or `.env` files. It uses:
@@ -46,12 +35,7 @@ console.log(`Logged in as ${(await response.json()).login}`);
All operations are asynchronous and non-blocking, running on Bun's threadpool.
<Note>
In the future, we may add an additional `provider` option to make this better for production deployment secrets, but
today this API is mostly useful for local development tools.
</Note>
---
Note: in the future, we may add an additional `provider` option to make this better for production deployment secrets, but today this API is mostly useful for local development tools.
## API
@@ -128,8 +112,6 @@ const deleted = await Bun.secrets.delete({
- `Promise<boolean>` - `true` if a credential was deleted, `false` if not found
---
## Examples
### Storing CLI Tool Credentials
@@ -161,7 +143,7 @@ const token = await Bun.secrets.get({
if (token) {
const response = await fetch("https://api.github.com/name", {
headers: {
Authorization: `token ${token}`,
"Authorization": `token ${token}`,
},
});
}
@@ -236,8 +218,6 @@ await Bun.secrets.set({
// The old password is replaced
```
---
## Platform Behavior
### macOS (Keychain)
@@ -279,8 +259,6 @@ await Bun.secrets.set({
- macOS: Keychain Access must be available
- Windows: Credential Manager service must be enabled
---
## Comparison with Environment Variables
Unlike environment variables, `Bun.secrets`:
@@ -293,8 +271,6 @@ Unlike environment variables, `Bun.secrets`:
- ❌ Requires OS credential service
- ❌ Not very useful for deployment secrets (use environment variables in production)
---
## Best Practices
1. **Use descriptive service names**: Match the tool or application name
@@ -318,8 +294,6 @@ Unlike environment variables, `Bun.secrets`:
- ✅ Personal API keys for testing
- ❌ Production servers (use proper secret management)
---
## TypeScript
```typescript
@@ -338,3 +312,8 @@ namespace Bun {
const secrets: Secrets;
}
```
## See Also
- [Environment Variables](./env.md) - For deployment configuration
- [Bun.password](./password.md) - For password hashing and verification

View File

@@ -1,17 +1,12 @@
---
title: Semver
description: Use Bun's semantic versioning API
---
Bun implements a semantic versioning API which can be used to compare versions and determine if a version is compatible with another range of versions. The versions and ranges are designed to be compatible with `node-semver`, which is used by npm clients.
It's about 20x faster than `node-semver`.
<Frame>![Benchmark](https://github.com/oven-sh/bun/assets/709451/94746adc-8aba-4baf-a143-3c355f8e0f78)</Frame>
![Benchmark](https://github.com/oven-sh/bun/assets/709451/94746adc-8aba-4baf-a143-3c355f8e0f78)
Currently, this API provides two functions:
Currently, this API provides two functions :
## `Bun.semver.satisfies(version: string, range: string): boolean`
#### `Bun.semver.satisfies(version: string, range: string): boolean`
Returns `true` if `version` satisfies `range`, otherwise `false`.
@@ -36,7 +31,7 @@ semver.satisfies("1.0.0", "1.0.0 - 1.0.1"); // true
If `range` is invalid, it returns false. If `version` is invalid, it returns false.
## `Bun.semver.order(versionA: string, versionB: string): 0 | 1 | -1`
#### `Bun.semver.order(versionA: string, versionB: string): 0 | 1 | -1`
Returns `0` if `versionA` and `versionB` are equal, `1` if `versionA` is greater than `versionB`, and `-1` if `versionA` is less than `versionB`.

View File

@@ -1,7 +1,4 @@
---
title: Spawn
description: Spawn child processes with `Bun.spawn` or `Bun.spawnSync`
---
Spawn child processes with `Bun.spawn` or `Bun.spawnSync`.
## Spawn a process (`Bun.spawn()`)
@@ -32,25 +29,68 @@ By default, the input stream of the subprocess is undefined; it can be configure
```ts
const proc = Bun.spawn(["cat"], {
stdin: await fetch("https://raw.githubusercontent.com/oven-sh/bun/main/examples/hashing.js"),
stdin: await fetch(
"https://raw.githubusercontent.com/oven-sh/bun/main/examples/hashing.js",
),
});
const text = await proc.stdout.text();
console.log(text); // "const input = "hello world".repeat(400); ..."
```
| Value | Description |
| ------------------------ | ------------------------------------------------ |
| `null` | **Default.** Provide no input to the subprocess |
| `"pipe"` | Return a `FileSink` for fast incremental writing |
| `"inherit"` | Inherit the `stdin` of the parent process |
| `Bun.file()` | Read from the specified file |
| `TypedArray \| DataView` | Use a binary buffer as input |
| `Response` | Use the response `body` as input |
| `Request` | Use the request `body` as input |
| `ReadableStream` | Use a readable stream as input |
| `Blob` | Use a blob as input |
| `number` | Read from the file with a given file descriptor |
{% table %}
---
- `null`
- **Default.** Provide no input to the subprocess
---
- `"pipe"`
- Return a `FileSink` for fast incremental writing
---
- `"inherit"`
- Inherit the `stdin` of the parent process
---
- `Bun.file()`
- Read from the specified file.
---
- `TypedArray | DataView`
- Use a binary buffer as input.
---
- `Response`
- Use the response `body` as input.
---
- `Request`
- Use the request `body` as input.
---
- `ReadableStream`
- Use a readable stream as input.
---
- `Blob`
- Use a blob as input.
---
- `number`
- Read from the file with a given file descriptor.
{% /table %}
The `"pipe"` option lets incrementally write to the subprocess's input stream from the parent process.
@@ -89,7 +129,7 @@ const proc = Bun.spawn(["cat"], {
stdout: "pipe",
});
const output = await proc.stdout.text();
const output = await new Response(proc.stdout).text();
console.log(output); // "Hello from ReadableStream!"
```
@@ -100,24 +140,45 @@ You can read results from the subprocess via the `stdout` and `stderr` propertie
```ts
const proc = Bun.spawn(["bun", "--version"]);
const text = await proc.stdout.text();
console.log(text); // => "1.3.3\n"
console.log(text); // => "$BUN_LATEST_VERSION\n"
```
Configure the output stream by passing one of the following values to `stdout/stderr`:
| Value | Description |
| ------------ | --------------------------------------------------------------------------------------------------- |
| `"pipe"` | **Default for `stdout`.** Pipe the output to a `ReadableStream` on the returned `Subprocess` object |
| `"inherit"` | **Default for `stderr`.** Inherit from the parent process |
| `"ignore"` | Discard the output |
| `Bun.file()` | Write to the specified file |
| `number` | Write to the file with the given file descriptor |
{% table %}
---
- `"pipe"`
- **Default for `stdout`.** Pipe the output to a `ReadableStream` on the returned `Subprocess` object.
---
- `"inherit"`
- **Default for `stderr`.** Inherit from the parent process.
---
- `"ignore"`
- Discard the output.
---
- `Bun.file()`
- Write to the specified file.
---
- `number`
- Write to the file with the given file descriptor.
{% /table %}
## Exit handling
Use the `onExit` callback to listen for the process exiting or being killed.
```ts index.ts icon="/icons/typescript.svg"
```ts
const proc = Bun.spawn(["bun", "--version"], {
onExit(proc, exitCode, signalCode, error) {
// exit handler
@@ -127,7 +188,7 @@ const proc = Bun.spawn(["bun", "--version"], {
For convenience, the `exited` property is a `Promise` that resolves when the process exits.
```ts index.ts icon="/icons/typescript.svg"
```ts
const proc = Bun.spawn(["bun", "--version"]);
await proc.exited; // resolves when process exit
@@ -138,7 +199,7 @@ proc.signalCode; // null | "SIGABRT" | "SIGALRM" | ...
To kill a process:
```ts index.ts icon="/icons/typescript.svg"
```ts
const proc = Bun.spawn(["bun", "--version"]);
proc.kill();
proc.killed; // true
@@ -149,7 +210,7 @@ proc.kill("SIGTERM"); // specify a signal name
The parent `bun` process will not terminate until all child processes have exited. Use `proc.unref()` to detach the child process from the parent.
```ts index.ts icon="/icons/typescript.svg"
```ts
const proc = Bun.spawn(["bun", "--version"]);
proc.unref();
```
@@ -158,7 +219,7 @@ proc.unref();
You can get information about the process's resource usage after it has exited:
```ts index.ts icon="/icons/typescript.svg"
```ts
const proc = Bun.spawn(["bun", "--version"]);
await proc.exited;
@@ -172,7 +233,7 @@ console.log(`CPU time (system): ${usage.cpuTime.system} µs`);
You can abort a subprocess using an `AbortSignal`:
```ts index.ts icon="/icons/typescript.svg"
```ts
const controller = new AbortController();
const { signal } = controller;
@@ -189,7 +250,7 @@ controller.abort();
You can set a timeout for a subprocess to automatically terminate after a specific duration:
```ts index.ts icon="/icons/typescript.svg"
```ts
// Kill the process after 5 seconds
const proc = Bun.spawn({
cmd: ["sleep", "10"],
@@ -201,7 +262,7 @@ await proc.exited; // Will resolve after 5 seconds
By default, timed-out processes are killed with the `SIGTERM` signal. You can specify a different signal with the `killSignal` option:
```ts index.ts icon="/icons/typescript.svg"
```ts
// Kill the process with SIGKILL after 5 seconds
const proc = Bun.spawn({
cmd: ["sleep", "10"],
@@ -216,10 +277,10 @@ The `killSignal` option also controls which signal is sent when an AbortSignal i
For spawnSync, you can limit the maximum number of bytes of output before the process is killed:
```ts index.ts icon="/icons/typescript.svg"
// Kill 'yes' after it emits over 100 bytes of output
```ts
// KIll 'yes' after it emits over 100 bytes of output
const result = Bun.spawnSync({
cmd: ["yes"], // or ["bun", "exec", "yes"] on Windows
cmd: ["yes"], // or ["bun", "exec", "yes"] on windows
maxBuffer: 100,
});
// process exits
@@ -229,7 +290,7 @@ const result = Bun.spawnSync({
Bun supports direct inter-process communication channel between two `bun` processes. To receive messages from a spawned Bun subprocess, specify an `ipc` handler.
```ts parent.ts icon="/icons/typescript.svg"
```ts#parent.ts
const child = Bun.spawn(["bun", "child.ts"], {
ipc(message) {
/**
@@ -241,13 +302,13 @@ const child = Bun.spawn(["bun", "child.ts"], {
The parent process can send messages to the subprocess using the `.send()` method on the returned `Subprocess` instance. A reference to the sending subprocess is also available as the second argument in the `ipc` handler.
```ts parent.ts icon="/icons/typescript.svg"
```ts#parent.ts
const childProc = Bun.spawn(["bun", "child.ts"], {
ipc(message, childProc) {
/**
* The message received from the sub process
**/
childProc.send("Respond to child");
childProc.send("Respond to child")
},
});
@@ -256,17 +317,17 @@ childProc.send("I am your father"); // The parent can send messages to the child
Meanwhile the child process can send messages to its parent using with `process.send()` and receive messages with `process.on("message")`. This is the same API used for `child_process.fork()` in Node.js.
```ts child.ts
```ts#child.ts
process.send("Hello from child as string");
process.send({ message: "Hello from child as object" });
process.on("message", message => {
process.on("message", (message) => {
// print message from parent
console.log(message);
});
```
```ts child.ts
```ts#child.ts
// send a string
process.send("Hello from child as string");
@@ -289,7 +350,7 @@ childProc.disconnect();
To use IPC between a `bun` process and a Node.js process, set `serialization: "json"` in `Bun.spawn`. This is because Node.js and Bun use different JavaScript engines with different object serialization formats.
```js bun-node-ipc.js icon="file-code"
```js#bun-node-ipc.js
if (typeof Bun !== "undefined") {
const prefix = `[bun ${process.versions.bun} 🐇]`;
const node = Bun.spawn({
@@ -313,111 +374,6 @@ if (typeof Bun !== "undefined") {
}
```
---
## Terminal (PTY) support
For interactive terminal applications, you can spawn a subprocess with a pseudo-terminal (PTY) attached using the `terminal` option. This makes the subprocess think it's running in a real terminal, enabling features like colored output, cursor movement, and interactive prompts.
```ts
const proc = Bun.spawn(["bash"], {
terminal: {
cols: 80,
rows: 24,
data(terminal, data) {
// Called when data is received from the terminal
process.stdout.write(data);
},
},
});
// Write to the terminal
proc.terminal.write("echo hello\n");
// Wait for the process to exit
await proc.exited;
// Close the terminal
proc.terminal.close();
```
When the `terminal` option is provided:
- The subprocess sees `process.stdout.isTTY` as `true`
- `stdin`, `stdout`, and `stderr` are all connected to the terminal
- `proc.stdin`, `proc.stdout`, and `proc.stderr` return `null` — use the terminal instead
- Access the terminal via `proc.terminal`
### Terminal options
| Option | Description | Default |
| ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------ |
| `cols` | Number of columns | `80` |
| `rows` | Number of rows | `24` |
| `name` | Terminal type for PTY configuration (set `TERM` env var separately via `env` option) | `"xterm-256color"` |
| `data` | Callback when data is received `(terminal, data) => void` | — |
| `exit` | Callback when PTY stream closes (EOF or error). `exitCode` is PTY lifecycle status (0=EOF, 1=error), not subprocess exit code. Use `proc.exited` for process exit. | — |
| `drain` | Callback when ready for more data `(terminal) => void` | — |
### Terminal methods
The `Terminal` object returned by `proc.terminal` has the following methods:
```ts
// Write data to the terminal
proc.terminal.write("echo hello\n");
// Resize the terminal
proc.terminal.resize(120, 40);
// Set raw mode (disable line buffering and echo)
proc.terminal.setRawMode(true);
// Keep event loop alive while terminal is open
proc.terminal.ref();
proc.terminal.unref();
// Close the terminal
proc.terminal.close();
```
### Reusable Terminal
You can create a terminal independently and reuse it across multiple subprocesses:
```ts
await using terminal = new Bun.Terminal({
cols: 80,
rows: 24,
data(term, data) {
process.stdout.write(data);
},
});
// Spawn first process
const proc1 = Bun.spawn(["echo", "first"], { terminal });
await proc1.exited;
// Reuse terminal for another process
const proc2 = Bun.spawn(["echo", "second"], { terminal });
await proc2.exited;
// Terminal is closed automatically by `await using`
```
When passing an existing `Terminal` object:
- The terminal can be reused across multiple spawns
- You control when to close the terminal
- The `exit` callback fires when you call `terminal.close()`, not when each subprocess exits
- Use `proc.exited` to detect individual subprocess exits
This is useful for running multiple commands in sequence through the same terminal session.
<Note>Terminal support is only available on POSIX systems (Linux, macOS). It is not available on Windows.</Note>
---
## Blocking API (`Bun.spawnSync()`)
Bun provides a synchronous equivalent of `Bun.spawn` called `Bun.spawnSync`. This is a blocking API that supports the same inputs and parameters as `Bun.spawn`. It returns a `SyncSubprocess` object, which differs from `Subprocess` in a few ways.
@@ -435,35 +391,23 @@ console.log(proc.stdout.toString());
As a rule of thumb, the asynchronous `Bun.spawn` API is better for HTTP servers and apps, and `Bun.spawnSync` is better for building command-line tools.
---
## Benchmarks
<Note>
⚡️ Under the hood, `Bun.spawn` and `Bun.spawnSync` use
[`posix_spawn(3)`](https://man7.org/linux/man-pages/man3/posix_spawn.3.html).
</Note>
{%callout%}
⚡️ Under the hood, `Bun.spawn` and `Bun.spawnSync` use [`posix_spawn(3)`](https://man7.org/linux/man-pages/man3/posix_spawn.3.html).
{%/callout%}
Bun's `spawnSync` spawns processes 60% faster than the Node.js `child_process` module.
```bash terminal icon="terminal"
bun spawn.mjs
```
```txt
```bash
$ bun spawn.mjs
cpu: Apple M1 Max
runtime: bun 1.x (arm64-darwin)
benchmark time (avg) (min … max) p75 p99 p995
--------------------------------------------------------- -----------------------------
spawnSync echo hi 888.14 µs/iter (821.83 µs … 1.2 ms) 905.92 µs 1 ms 1.03 ms
```
```sh terminal icon="terminal"
node spawn.node.mjs
```
```txt
$ node spawn.node.mjs
cpu: Apple M1 Max
runtime: node v18.9.1 (arm64-darwin)
@@ -472,19 +416,22 @@ benchmark time (avg) (min … max) p75 p99
spawnSync echo hi 1.47 ms/iter (1.14 ms … 2.64 ms) 1.57 ms 2.37 ms 2.52 ms
```
---
## Reference
A reference of the Spawn API and types are shown below. The real types have complex generics to strongly type the `Subprocess` streams with the options passed to `Bun.spawn` and `Bun.spawnSync`. For full details, find these types as defined [bun.d.ts](https://github.com/oven-sh/bun/blob/main/packages/bun-types/bun.d.ts).
```ts See Typescript Definitions expandable
```ts
interface Bun {
spawn(command: string[], options?: SpawnOptions.OptionsObject): Subprocess;
spawnSync(command: string[], options?: SpawnOptions.OptionsObject): SyncSubprocess;
spawnSync(
command: string[],
options?: SpawnOptions.OptionsObject,
): SyncSubprocess;
spawn(options: { cmd: string[] } & SpawnOptions.OptionsObject): Subprocess;
spawnSync(options: { cmd: string[] } & SpawnOptions.OptionsObject): SyncSubprocess;
spawnSync(
options: { cmd: string[] } & SpawnOptions.OptionsObject,
): SyncSubprocess;
}
namespace SpawnOptions {
@@ -510,7 +457,6 @@ namespace SpawnOptions {
timeout?: number;
killSignal?: string | number;
maxBuffer?: number;
terminal?: TerminalOptions; // PTY support (POSIX only)
}
type Readable =
@@ -539,11 +485,10 @@ namespace SpawnOptions {
}
interface Subprocess extends AsyncDisposable {
readonly stdin: FileSink | number | undefined | null;
readonly stdout: ReadableStream<Uint8Array<ArrayBuffer>> | number | undefined | null;
readonly stderr: ReadableStream<Uint8Array<ArrayBuffer>> | number | undefined | null;
readonly readable: ReadableStream<Uint8Array<ArrayBuffer>> | number | undefined | null;
readonly terminal: Terminal | undefined;
readonly stdin: FileSink | number | undefined;
readonly stdout: ReadableStream<Uint8Array> | number | undefined;
readonly stderr: ReadableStream<Uint8Array> | number | undefined;
readonly readable: ReadableStream<Uint8Array> | number | undefined;
readonly pid: number;
readonly exited: Promise<number>;
readonly exitCode: number | null;
@@ -570,28 +515,6 @@ interface SyncSubprocess {
pid: number;
}
interface TerminalOptions {
cols?: number;
rows?: number;
name?: string;
data?: (terminal: Terminal, data: Uint8Array<ArrayBuffer>) => void;
/** Called when PTY stream closes (EOF or error). exitCode is PTY lifecycle status (0=EOF, 1=error), not subprocess exit code. */
exit?: (terminal: Terminal, exitCode: number, signal: string | null) => void;
drain?: (terminal: Terminal) => void;
}
interface Terminal extends AsyncDisposable {
readonly stdin: number;
readonly stdout: number;
readonly closed: boolean;
write(data: string | BufferSource): number;
resize(cols: number, rows: number): void;
setRawMode(enabled: boolean): void;
ref(): void;
unref(): void;
close(): void;
}
interface ResourceUsage {
contextSwitches: {
voluntary: number;

View File

@@ -1,11 +1,6 @@
---
title: SQL
description: Bun provides native bindings for working with SQL databases through a unified Promise-based API that supports PostgreSQL, MySQL, and SQLite.
---
Bun provides native bindings for working with SQL databases through a unified Promise-based API that supports PostgreSQL, MySQL, and SQLite. The interface is designed to be simple and performant, using tagged template literals for queries and offering features like connection pooling, transactions, and prepared statements.
The interface is designed to be simple and performant, using tagged template literals for queries and offering features like connection pooling, transactions, and prepared statements.
```ts title="db.ts" icon="/icons/typescript.svg"
```ts
import { sql, SQL } from "bun";
// PostgreSQL (default)
@@ -30,25 +25,35 @@ const sqliteResults = await sqlite`
`;
```
### Features
{% features title="Features" %}
- Tagged template literals to protect against SQL injection
- Transactions
- Named & positional parameters
- Connection pooling
- `BigInt` support
- SASL Auth support (SCRAM-SHA-256), MD5, and Clear Text
- Connection timeouts
- Returning rows as data objects, arrays of arrays, or Buffer
- Binary protocol support makes it faster
- TLS support (and auth mode)
- Automatic configuration with environment variable
{% icon size=20 name="Shield" /%} Tagged template literals to protect against SQL injection
---
{% icon size=20 name="GitMerge" /%} Transactions
{% icon size=20 name="Variable" /%} Named & positional parameters
{% icon size=20 name="Network" /%} Connection pooling
{% icon size=20 name="Binary" /%} `BigInt` support
{% icon size=20 name="Key" /%} SASL Auth support (SCRAM-SHA-256), MD5, and Clear Text
{% icon size=20 name="Timer" /%} Connection timeouts
{% icon size=20 name="Database" /%} Returning rows as data objects, arrays of arrays, or `Buffer`
{% icon size=20 name="Code" /%} Binary protocol support makes it faster
{% icon size=20 name="Lock" /%} TLS support (and auth mode)
{% icon size=20 name="Settings" /%} Automatic configuration with environment variable
{% /features %}
## Database Support
`Bun.SQL` provides a unified API for multiple database systems:
Bun.SQL provides a unified API for multiple database systems:
### PostgreSQL
@@ -58,7 +63,7 @@ PostgreSQL is used when:
- The connection string explicitly uses `postgres://` or `postgresql://` protocols
- No connection string is provided and environment variables point to PostgreSQL
```ts title="db.ts" icon="/icons/typescript.svg"
```ts
import { sql } from "bun";
// Uses PostgreSQL if DATABASE_URL is not set or is a PostgreSQL URL
await sql`SELECT ...`;
@@ -72,7 +77,7 @@ await pg`SELECT ...`;
MySQL support is built into Bun.SQL, providing the same tagged template literal interface with full compatibility for MySQL 5.7+ and MySQL 8.0+:
```ts title="db.ts" icon="/icons/typescript.svg"
```ts
import { SQL } from "bun";
// MySQL connection
@@ -106,7 +111,7 @@ const newUsers = [
await mysql`INSERT INTO users ${mysql(newUsers)}`;
```
<Accordion title="MySQL Connection String Formats">
{% details summary="MySQL Connection String Formats" %}
MySQL accepts various URL formats for connection strings:
@@ -125,9 +130,9 @@ new SQL("mysql://user:pass@localhost/db?ssl=true");
new SQL("mysql://user:pass@/database?socket=/var/run/mysqld/mysqld.sock");
```
</Accordion>
{% /details %}
<Accordion title="MySQL-Specific Features">
{% details summary="MySQL-Specific Features" %}
MySQL databases support:
@@ -139,7 +144,7 @@ MySQL databases support:
- **Connection attributes**: Client information sent to server for monitoring
- **Query pipelining**: Execute multiple prepared statements without waiting for responses
</Accordion>
{% /details %}
### SQLite
@@ -153,19 +158,19 @@ const memory = new SQL(":memory:");
const memory2 = new SQL("sqlite://:memory:");
// File-based database
const sql1 = new SQL("sqlite://myapp.db");
const db = new SQL("sqlite://myapp.db");
// Using options object
const sql2 = new SQL({
const db2 = new SQL({
adapter: "sqlite",
filename: "./data/app.db",
});
// For simple filenames, specify adapter explicitly
const sql3 = new SQL("myapp.db", { adapter: "sqlite" });
const db3 = new SQL("myapp.db", { adapter: "sqlite" });
```
<Accordion title="SQLite Connection String Formats">
{% details summary="SQLite Connection String Formats" %}
SQLite accepts various URL formats for connection strings:
@@ -194,18 +199,16 @@ new SQL("sqlite://data.db?mode=rw"); // Read-write mode (no create)
new SQL("sqlite://data.db?mode=rwc"); // Read-write-create mode (default)
```
<Note>
Simple filenames without a protocol (like `"myapp.db"`) require explicitly specifying `{ adapter: "sqlite" }` to avoid ambiguity with PostgreSQL.
</Note>
**Note:** Simple filenames without a protocol (like `"myapp.db"`) require explicitly specifying `{ adapter: "sqlite" }` to avoid ambiguity with PostgreSQL.
</Accordion>
{% /details %}
<Accordion title="SQLite-Specific Options">
{% details summary="SQLite-Specific Options" %}
SQLite databases support additional configuration options:
```ts
const sql = new SQL({
const db = new SQL({
adapter: "sqlite",
filename: "app.db",
@@ -226,9 +229,9 @@ Query parameters in the URL are parsed to set these options:
- `?mode=rw``readonly: false, create: false`
- `?mode=rwc``readonly: false, create: true` (default)
</Accordion>
{% /details %}
## Inserting data
### Inserting data
You can pass JavaScript values directly to the SQL template literal and escaping will be handled for you.
@@ -284,8 +287,6 @@ await sql`INSERT INTO users ${sql(user, "name", "email")}`;
// Only inserts name and email columns, ignoring other fields
```
---
## Query Results
By default, Bun's SQL client returns query results as arrays of objects, where each object represents a row with column names as keys. However, there are cases where you might want the data in a different format. The client provides two additional methods for this purpose.
@@ -319,8 +320,6 @@ const rows = await sql`SELECT * FROM users`.raw();
console.log(rows); // [[Buffer, Buffer], [Buffer, Buffer], [Buffer, Buffer]]
```
---
## SQL Fragments
A common need in database applications is the ability to construct queries dynamically based on runtime conditions. Bun provides safe ways to do this without risking SQL injection.
@@ -392,9 +391,7 @@ await sql`SELECT * FROM products WHERE ids = ANY(${sql.array([1, 2, 3])})`;
// Generates: SELECT * FROM products WHERE ids = ANY(ARRAY[1, 2, 3])
```
<Note>`sql.array` is PostgreSQL-only. Multi-dimensional arrays and NULL elements may not be supported yet.</Note>
---
**Note**: `sql.array` is PostgreSQL-only. Multi-dimensional arrays and NULL elements may not be supported yet.
## `sql``.simple()`
@@ -434,22 +431,27 @@ const result = await sql.unsafe(`
`);
// Using parameters (only one command is allowed)
const result = await sql.unsafe("SELECT " + dangerous + " FROM users WHERE id = $1", [id]);
const result = await sql.unsafe(
"SELECT " + dangerous + " FROM users WHERE id = $1",
[id],
);
```
#### What is SQL Injection?
{% image href="https://xkcd.com/327/" src="https://imgs.xkcd.com/comics/exploits_of_a_mom.png" /%}
### Execute and Cancelling Queries
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
const query = sql`SELECT * FROM users`.execute();
const query = await sql`SELECT * FROM users`.execute();
setTimeout(() => query.cancel(), 100);
await query;
```
---
## Database Environment Variables
`sql` connection parameters can be configured using environment variables. The client checks these variables in a specific order of precedence and automatically detects the database type based on the connection string format.
@@ -571,8 +573,6 @@ DATABASE_URL="file:///absolute/path/to/db.sqlite"
**Note:** PostgreSQL-specific environment variables (`POSTGRES_URL`, `PGHOST`, etc.) are ignored when using SQLite.
---
## Runtime Preconnection
Bun can preconnect to PostgreSQL at startup to improve performance by establishing database connections before your application code runs. This is useful for reducing connection latency on the first database query.
@@ -590,8 +590,6 @@ bun --sql-preconnect --hot index.js
The `--sql-preconnect` flag will automatically establish a PostgreSQL connection using your configured environment variables at startup. If the connection fails, it won't crash your application - the error will be handled gracefully.
---
## Connection Options
You can configure your database connection manually by passing options to the SQL constructor. Options vary depending on the database adapter:
@@ -601,7 +599,7 @@ You can configure your database connection manually by passing options to the SQ
```ts
import { SQL } from "bun";
const sql = new SQL({
const db = new SQL({
// Required for MySQL when using options object
adapter: "mysql",
@@ -622,13 +620,12 @@ const sql = new SQL({
connectionTimeout: 30, // Timeout when establishing new connections
// SSL/TLS options
ssl: "prefer", // or "disable", "require", "verify-ca", "verify-full"
// tls: {
// rejectUnauthorized: true,
// ca: "path/to/ca.pem",
// key: "path/to/key.pem",
// cert: "path/to/cert.pem",
// },
tls: {
rejectUnauthorized: true,
ca: "path/to/ca.pem",
key: "path/to/key.pem",
cert: "path/to/cert.pem",
},
// Callbacks
onconnect: client => {
@@ -649,7 +646,7 @@ const sql = new SQL({
```ts
import { SQL } from "bun";
const sql = new SQL({
const db = new SQL({
// Connection details (adapter is auto-detected as PostgreSQL)
url: "postgres://user:pass@localhost:5432/dbname",
@@ -694,7 +691,7 @@ const sql = new SQL({
```ts
import { SQL } from "bun";
const sql = new SQL({
const db = new SQL({
// Required for SQLite
adapter: "sqlite",
filename: "./data/app.db", // or ":memory:" for in-memory database
@@ -718,16 +715,14 @@ const sql = new SQL({
});
```
<Accordion title="SQLite Connection Notes">
{% details summary="SQLite Connection Notes" %}
- **Connection Pooling**: SQLite doesn't use connection pooling as it's a file-based database. Each `SQL` instance represents a single connection.
- **Transactions**: SQLite supports nested transactions through savepoints, similar to PostgreSQL.
- **Concurrent Access**: SQLite handles concurrent access through file locking. Use WAL mode for better concurrency.
- **Memory Databases**: Using `:memory:` creates a temporary database that exists only for the connection lifetime.
</Accordion>
---
{% /details %}
## Dynamic passwords
@@ -744,8 +739,6 @@ const sql = new SQL(url, {
});
```
---
## SQLite-Specific Features
### Query Execution
@@ -801,8 +794,6 @@ await sqlite`
await sqlite`INSERT INTO flexible VALUES (${1}, ${"text"}, ${123.45}, ${Buffer.from("binary")})`;
```
---
## Transactions
To start a new transaction, use `sql.begin`. This method works for both PostgreSQL and SQLite. For PostgreSQL, it reserves a dedicated connection from the pool. For SQLite, it begins a transaction on the single connection.
@@ -878,8 +869,6 @@ await sql.commitDistributed("tx1");
await sql.rollbackDistributed("tx1");
```
---
## Authentication
Bun supports SCRAM-SHA-256 (SASL), MD5, and Clear Text authentication. SASL is recommended for better security. Check [Postgres SASL Authentication](https://www.postgresql.org/docs/current/sasl-authentication.html) for more information.
@@ -914,17 +903,17 @@ The SSL mode can also be specified in connection strings:
const sql = new SQL("postgres://user:password@localhost/mydb?sslmode=prefer");
// Using verify-full mode
const sql = new SQL("postgres://user:password@localhost/mydb?sslmode=verify-full");
const sql = new SQL(
"postgres://user:password@localhost/mydb?sslmode=verify-full",
);
```
---
## Connection Pooling
Bun's SQL client automatically manages a connection pool, which is a pool of database connections that are reused for multiple queries. This helps to reduce the overhead of establishing and closing connections for each query, and it also helps to manage the number of concurrent connections to the database.
```ts
const sql = new SQL({
const db = new SQL({
// Pool configuration
max: 20, // Maximum 20 concurrent connections
idleTimeout: 30, // Close idle connections after 30s
@@ -936,7 +925,7 @@ const sql = new SQL({
No connection will be made until a query is made.
```ts
const sql = Bun.SQL(); // no connection are created
const sql = Bun.sql(); // no connection are created
await sql`...`; // pool is started until max is reached (if possible), first available connection is used
await sql`...`; // previous connection is reused
@@ -952,8 +941,6 @@ await sql.close({ timeout: 5 }); // wait 5 seconds and close all connections fro
await sql.close({ timeout: 0 }); // close all connections from the pool immediately
```
---
## Reserved Connections
Bun enables you to reserve a connection from the pool, and returns a client that wraps the single connection. This can be used for running queries on an isolated connection.
@@ -976,8 +963,6 @@ try {
} // Automatically released
```
---
## Prepared Statements
By default, Bun's SQL client automatically creates named prepared statements for queries where it can be inferred that the query is static. This provides better performance. However, you can change this behavior by setting `prepare: false` in the connection options:
@@ -1006,8 +991,6 @@ You might want to use `prepare: false` when:
Note that disabling prepared statements may impact performance for queries that are executed frequently with different parameters, as the server needs to parse and plan each query from scratch.
---
## Error Handling
The client provides typed errors for different failure scenarios. Errors are database-specific and extend from base error classes:
@@ -1037,7 +1020,7 @@ try {
}
```
<Accordion title="PostgreSQL-Specific Error Codes">
{% details summary="PostgreSQL-Specific Error Codes" %}
### PostgreSQL Connection Errors
@@ -1104,13 +1087,13 @@ try {
| `ERR_POSTGRES_UNSAFE_TRANSACTION` | Unsafe transaction operation detected |
| `ERR_POSTGRES_INVALID_TRANSACTION_STATE` | Invalid transaction state |
</Accordion>
{% /details %}
### SQLite-Specific Errors
SQLite errors provide error codes and numbers that correspond to SQLite's standard error codes:
<Accordion title="Common SQLite Error Codes">
{% details summary="Common SQLite Error Codes" %}
| Error Code | errno | Description |
| ------------------- | ----- | ---------------------------------------------------- |
@@ -1147,9 +1130,7 @@ try {
}
```
</Accordion>
---
{% /details %}
## Numbers and BigInt
@@ -1164,8 +1145,6 @@ console.log(typeof x, x); // "string" "9223372036854777"
console.log(typeof y, y); // "number" 12345
```
---
## BigInt Instead of Strings
If you need large numbers as BigInt instead of strings, you can enable this by setting the `bigint` option to `true` when initializing the SQL client:
@@ -1180,8 +1159,6 @@ const [{ x }] = await sql`SELECT 9223372036854777 as x`;
console.log(typeof x, x); // "bigint" 9223372036854777n
```
---
## Roadmap
There's still some things we haven't finished yet.
@@ -1190,8 +1167,6 @@ There's still some things we haven't finished yet.
- Column name transforms (e.g. `snake_case` to `camelCase`). This is mostly blocked on a unicode-aware implementation of changing the case in C++ using WebKit's `WTF::String`.
- Column type transforms
---
## Database-Specific Features
#### Authentication Methods
@@ -1303,8 +1278,6 @@ We also haven't implemented some of the more uncommon features like:
- Point & PostGIS types
- All the multi-dimensional integer array types (only a couple of the types are supported)
---
## Common Patterns & Best Practices
### Working with MySQL Result Sets
@@ -1315,7 +1288,8 @@ const result = await mysql`INSERT INTO users (name) VALUES (${"Alice"})`;
console.log(result.lastInsertRowid); // MySQL's LAST_INSERT_ID()
// Handling affected rows
const updated = await mysql`UPDATE users SET active = ${false} WHERE age < ${18}`;
const updated =
await mysql`UPDATE users SET active = ${false} WHERE age < ${18}`;
console.log(updated.affectedRows); // Number of rows updated
// Using MySQL-specific functions
@@ -1348,47 +1322,43 @@ try {
4. **Index properly**: MySQL relies heavily on indexes for query performance
5. **Use `utf8mb4` charset**: It's set by default and handles all Unicode characters
---
## Frequently Asked Questions
<AccordionGroup>
<Accordion title="Why is this `Bun.sql` and not `Bun.postgres`?">
The plan was to add more database drivers in the future. Now with MySQL support added, this unified API supports PostgreSQL, MySQL, and SQLite.
</Accordion>
<Accordion title="How do I know which database adapter is being used?">
The adapter is automatically detected from the connection string:
> Why is this `Bun.sql` and not `Bun.postgres`?
- URLs starting with `mysql://` or `mysql2://` use MySQL
- URLs matching SQLite patterns (`:memory:`, `sqlite://`, `file://`) use SQLite
- Everything else defaults to PostgreSQL
</Accordion>
<Accordion title="Are MySQL stored procedures supported?">
Yes, stored procedures are fully supported including OUT parameters and multiple result sets:
The plan was to add more database drivers in the future. Now with MySQL support added, this unified API supports PostgreSQL, MySQL, and SQLite.
```ts
// Call stored procedure
const results = await mysql`CALL GetUserStats(${userId}, @total_orders)`;
> How do I know which database adapter is being used?
// Get OUT parameter
const outParam = await mysql`SELECT @total_orders as total`;
```
</Accordion>
<Accordion title="Can I use MySQL-specific SQL syntax?">
Yes, you can use any MySQL-specific syntax:
The adapter is automatically detected from the connection string:
```ts
// MySQL-specific syntax works fine
await mysql`SET @user_id = ${userId}`;
await mysql`SHOW TABLES`;
await mysql`DESCRIBE users`;
await mysql`EXPLAIN SELECT * FROM users WHERE id = ${id}`;
```
</Accordion>
- URLs starting with `mysql://` or `mysql2://` use MySQL
- URLs matching SQLite patterns (`:memory:`, `sqlite://`, `file://`) use SQLite
- Everything else defaults to PostgreSQL
</AccordionGroup>
> Are MySQL stored procedures supported?
---
Yes, stored procedures are fully supported including OUT parameters and multiple result sets:
```ts
// Call stored procedure
const results = await mysql`CALL GetUserStats(${userId}, @total_orders)`;
// Get OUT parameter
const outParam = await mysql`SELECT @total_orders as total`;
```
> Can I use MySQL-specific SQL syntax?
Yes, you can use any MySQL-specific syntax:
```ts
// MySQL-specific syntax works fine
await mysql`SET @user_id = ${userId}`;
await mysql`SHOW TABLES`;
await mysql`DESCRIBE users`;
await mysql`EXPLAIN SELECT * FROM users WHERE id = ${id}`;
```
## Why not just use an existing library?

View File

@@ -1,20 +1,11 @@
---
title: SQLite
description: Bun natively implements a high-performance SQLite3 driver.
---
Bun natively implements a high-performance [SQLite3](https://www.sqlite.org/) driver. To use it import from the built-in `bun:sqlite` module.
```ts db.ts icon="/icons/typescript.svg"
```ts
import { Database } from "bun:sqlite";
const db = new Database(":memory:");
const query = db.query("select 'Hello world' as message;");
query.get();
```
```txt
{ message: "Hello world" }
query.get(); // => { message: "Hello world" }
```
The API is simple, synchronous, and fast. Credit to [better-sqlite3](https://github.com/JoshuaWise/better-sqlite3) and its contributors for inspiring the API of `bun:sqlite`.
@@ -32,18 +23,13 @@ Features include:
The `bun:sqlite` module is roughly 3-6x faster than `better-sqlite3` and 8-9x faster than `deno.land/x/sqlite` for read queries. Each driver was benchmarked against the [Northwind Traders](https://github.com/jpwhite3/northwind-SQLite3/blob/46d5f8a64f396f87cd374d1600dbf521523980e8/Northwind_large.sqlite.zip) dataset. View and run the [benchmark source](https://github.com/oven-sh/bun/tree/main/bench/sqlite).
<Frame caption="Benchmarked on an M1 MacBook Pro (64GB) running macOS 12.3.1">
![SQLite benchmarks for Bun, better-sqlite3, and
deno.land/x/sqlite](https://user-images.githubusercontent.com/709451/168459263-8cd51ca3-a924-41e9-908d-cf3478a3b7f3.png)
</Frame>
---
{% image width="738" alt="SQLite benchmarks for Bun, better-sqlite3, and deno.land/x/sqlite" src="https://user-images.githubusercontent.com/709451/168459263-8cd51ca3-a924-41e9-908d-cf3478a3b7f3.png" caption="Benchmarked on an M1 MacBook Pro (64GB) running macOS 12.3.1" /%}
## Database
To open or create a SQLite3 database:
```ts db.ts icon="/icons/typescript.svg"
```ts
import { Database } from "bun:sqlite";
const db = new Database("mydb.sqlite");
@@ -51,7 +37,7 @@ const db = new Database("mydb.sqlite");
To open an in-memory database:
```ts db.ts icon="/icons/typescript.svg"
```ts
import { Database } from "bun:sqlite";
// all of these do the same thing
@@ -62,50 +48,64 @@ const db = new Database("");
To open in `readonly` mode:
```ts db.ts icon="/icons/typescript.svg" highlight={2}
```ts
import { Database } from "bun:sqlite";
const db = new Database("mydb.sqlite", { readonly: true });
```
To create the database if the file doesn't exist:
```ts db.ts icon="/icons/typescript.svg" highlight={2}
```ts
import { Database } from "bun:sqlite";
const db = new Database("mydb.sqlite", { create: true });
```
### Strict mode
{% callout %}
Added in Bun v1.1.14
{% /callout %}
By default, `bun:sqlite` requires binding parameters to include the `$`, `:`, or `@` prefix, and does not throw an error if a parameter is missing.
To instead throw an error when a parameter is missing and allow binding without a prefix, set `strict: true` on the `Database` constructor:
```ts db.ts icon="/icons/typescript.svg" highlight={3}
<!-- prettier-ignore -->
```ts
import { Database } from "bun:sqlite";
const strict = new Database(":memory:", { strict: true });
const strict = new Database(
":memory:",
{ strict: true }
);
// throws error because of the typo:
const query = strict.query("SELECT $message;").all({ messag: "Hello world" });
const query = strict
.query("SELECT $message;")
.all({ message: "Hello world" });
const notStrict = new Database(":memory:");
const notStrict = new Database(
":memory:"
);
// does not throw error:
notStrict.query("SELECT $message;").all({ messag: "Hello world" });
notStrict
.query("SELECT $message;")
.all({ message: "Hello world" });
```
### Load via ES module import
You can also use an import attribute to load a database.
```ts db.ts icon="/icons/typescript.svg" highlight={1}
import db from "./mydb.sqlite" with { type: "sqlite" };
```ts
import db from "./mydb.sqlite" with { "type": "sqlite" };
console.log(db.query("select * from users LIMIT 1").get());
```
This is equivalent to the following:
```ts db.ts icon="/icons/typescript.svg"
```ts
import { Database } from "bun:sqlite";
const db = new Database("./mydb.sqlite");
```
@@ -114,7 +114,7 @@ const db = new Database("./mydb.sqlite");
To close a database connection, but allow existing queries to finish, call `.close(false)`:
```ts db.ts icon="/icons/typescript.svg" highlight={3}
```ts
const db = new Database();
// ... do stuff
db.close(false);
@@ -122,40 +122,33 @@ db.close(false);
To close the database and throw an error if there are any pending queries, call `.close(true)`:
```ts db.ts icon="/icons/typescript.svg" highlight={3}
```ts
const db = new Database();
// ... do stuff
db.close(true);
```
<Note>
`close(false)` is called automatically when the database is garbage collected. It is safe to call multiple times but
has no effect after the first.
</Note>
Note: `close(false)` is called automatically when the database is garbage collected. It is safe to call multiple times but has no effect after the first.
### `using` statement
You can use the `using` statement to ensure that a database connection is closed when the `using` block is exited.
```ts db.ts icon="/icons/typescript.svg" highlight={4, 5}
```ts
import { Database } from "bun:sqlite";
{
using db = new Database("mydb.sqlite");
using query = db.query("select 'Hello world' as message;");
console.log(query.get());
console.log(query.get()); // => { message: "Hello world" }
}
```
```txt
{ message: "Hello world" }
```
### `.serialize()`
`bun:sqlite` supports SQLite's built-in mechanism for [serializing](https://www.sqlite.org/c3ref/serialize.html) and [deserializing](https://www.sqlite.org/c3ref/deserialize.html) databases to and from memory.
```ts db.ts icon="/icons/typescript.svg" highlight={2}
```ts
const olddb = new Database("mydb.sqlite");
const contents = olddb.serialize(); // => Uint8Array
const newdb = Database.deserialize(contents);
@@ -167,34 +160,20 @@ Internally, `.serialize()` calls [`sqlite3_serialize`](https://www.sqlite.org/c3
Use the `db.query()` method on your `Database` instance to [prepare](https://www.sqlite.org/c3ref/prepare.html) a SQL query. The result is a `Statement` instance that will be cached on the `Database` instance. _The query will not be executed._
```ts db.ts icon="/icons/typescript.svg"
```ts
const query = db.query(`select "Hello world" as message`);
```
<Note>
**What does "cached" mean?**
{% callout %}
The caching refers to the **compiled prepared statement** (the SQL bytecode), not the query results. When you call `db.query()` with the same SQL string multiple times, Bun returns the same cached `Statement` object instead of recompiling the SQL.
It is completely safe to reuse a cached statement with different parameter values:
**Note** — Use the `.prepare()` method to prepare a query _without_ caching it on the `Database` instance.
```ts
const query = db.query("SELECT * FROM users WHERE id = ?");
query.get(1); // ✓ Works
query.get(2); // ✓ Also works - parameters are bound fresh each time
query.get(3); // ✓ Still works
```
Use `.prepare()` instead of `.query()` when you want a fresh `Statement` instance that isn't cached, for example if you're dynamically generating SQL and don't want to fill the cache with one-off queries.
```ts
// compile the prepared statement without caching
// compile the prepared statement
const query = db.prepare("SELECT * FROM foo WHERE bar = ?");
```
</Note>
---
{% /callout %}
## WAL mode
@@ -202,18 +181,15 @@ SQLite supports [write-ahead log mode](https://www.sqlite.org/wal.html) (WAL) wh
To enable WAL mode, run this pragma query at the beginning of your application:
```ts db.ts icon="/icons/typescript.svg"
db.run("PRAGMA journal_mode = WAL;");
```ts
db.exec("PRAGMA journal_mode = WAL;");
```
<Accordion title="What is WAL mode?">
{% details summary="What is WAL mode" %}
In WAL mode, writes to the database are written directly to a separate file called the "WAL file" (write-ahead log). This file will be later integrated into the main database file. Think of it as a buffer for pending writes. Refer to the [SQLite docs](https://www.sqlite.org/wal.html) for a more detailed overview.
On macOS, WAL files may be persistent by default. This is not a bug, it is how macOS configured the system version of SQLite.
</Accordion>
---
{% /details %}
## Statements
@@ -221,13 +197,13 @@ A `Statement` is a _prepared query_, which means it's been parsed and compiled i
Create a statement with the `.query` method on your `Database` instance.
```ts db.ts icon="/icons/typescript.svg"
```ts
const query = db.query(`select "Hello world" as message`);
```
Queries can contain parameters. These can be numerical (`?1`) or named (`$param` or `:param` or `@param`).
```ts db.ts icon="/icons/typescript.svg"
```ts
const query = db.query(`SELECT ?1, ?2;`);
const query = db.query(`SELECT $param1, $param2;`);
```
@@ -238,28 +214,32 @@ Values are bound to these parameters when the query is executed. A `Statement` c
To bind values to a statement, pass an object to the `.all()`, `.get()`, `.run()`, or `.values()` method.
```ts db.ts icon="/icons/typescript.svg" highlight={2}
```ts
const query = db.query(`select $message;`);
query.all({ $message: "Hello world" });
```
You can bind using positional parameters too:
```ts db.ts icon="/icons/typescript.svg"
```ts
const query = db.query(`select ?1;`);
query.all("Hello world");
```
#### `strict: true` lets you bind values without prefixes
{% callout %}
Added in Bun v1.1.14
{% /callout %}
By default, the `$`, `:`, and `@` prefixes are **included** when binding values to named parameters. To bind without these prefixes, use the `strict` option in the `Database` constructor.
```ts db.ts icon="/icons/typescript.svg"
```ts
import { Database } from "bun:sqlite";
const db = new Database(":memory:", {
// bind values without prefixes
strict: true, // [!code ++]
strict: true,
});
const query = db.query(`select $message;`);
@@ -275,13 +255,10 @@ query.all({ message: "Hello world" });
Use `.all()` to run a query and get back the results as an array of objects.
```ts db.ts icon="/icons/typescript.svg" highlight={2}
```ts
const query = db.query(`select $message;`);
query.all({ $message: "Hello world" });
```
```txt
[{ message: "Hello world" }]
// => [{ message: "Hello world" }]
```
Internally, this calls [`sqlite3_reset`](https://www.sqlite.org/capi3ref.html#sqlite3_reset) and repeatedly calls [`sqlite3_step`](https://www.sqlite.org/capi3ref.html#sqlite3_step) until it returns `SQLITE_DONE`.
@@ -290,42 +267,44 @@ Internally, this calls [`sqlite3_reset`](https://www.sqlite.org/capi3ref.html#sq
Use `.get()` to run a query and get back the first result as an object.
```ts db.ts icon="/icons/typescript.svg" highlight={2}
```ts
const query = db.query(`select $message;`);
query.get({ $message: "Hello world" });
```
```txt
{ $message: "Hello world" }
// => { $message: "Hello world" }
```
Internally, this calls [`sqlite3_reset`](https://www.sqlite.org/capi3ref.html#sqlite3_reset) followed by [`sqlite3_step`](https://www.sqlite.org/capi3ref.html#sqlite3_step) until it no longer returns `SQLITE_ROW`. If the query returns no rows, `undefined` is returned.
### `.run()`
Use `.run()` to run a query and get back an object with execution metadata. This is useful for schema-modifying queries (e.g. `CREATE TABLE`) or bulk write operations.
Use `.run()` to run a query and get back `undefined`. This is useful for schema-modifying queries (e.g. `CREATE TABLE`) or bulk write operations.
```ts db.ts icon="/icons/typescript.svg" highlight={2}
```ts
const query = db.query(`create table foo;`);
query.run();
```
```txt
{
lastInsertRowid: 0,
changes: 0,
}
// {
// lastInsertRowid: 0,
// changes: 0,
// }
```
Internally, this calls [`sqlite3_reset`](https://www.sqlite.org/capi3ref.html#sqlite3_reset) and calls [`sqlite3_step`](https://www.sqlite.org/capi3ref.html#sqlite3_step) once. Stepping through all the rows is not necessary when you don't care about the results.
{% callout %}
Since Bun v1.1.14, `.run()` returns an object with two properties: `lastInsertRowid` and `changes`.
{% /callout %}
The `lastInsertRowid` property returns the ID of the last row inserted into the database. The `changes` property is the number of rows affected by the query.
### `.as(Class)` - Map query results to a class
{% callout %}
Added in Bun v1.1.14
{% /callout %}
Use `.as(Class)` to run a query and get back the results as instances of a class. This lets you attach methods & getters/setters to results.
```ts db.ts icon="/icons/typescript.svg" highlight={10}
```ts
class Movie {
title: string;
year: number;
@@ -338,14 +317,8 @@ class Movie {
const query = db.query("SELECT title, year FROM movies").as(Movie);
const movies = query.all();
const first = query.get();
console.log(movies[0].isMarvel);
console.log(first.isMarvel);
```
```txt
true
true
console.log(movies[0].isMarvel); // => true
console.log(first.isMarvel); // => true
```
As a performance optimization, the class constructor is not called, default initializers are not run, and private fields are not accessible. This is more like using `Object.create` than `new`. The class's prototype is assigned to the object, methods are attached, and getters/setters are set up, but the constructor is not called.
@@ -356,7 +329,7 @@ The database columns are set as properties on the class instance.
Use `.iterate()` to run a query and incrementally return results. This is useful for large result sets that you want to process one row at a time without loading all the results into memory.
```ts db.ts icon="/icons/typescript.svg" highlight={2}
```ts
const query = db.query("SELECT * FROM foo");
for (const row of query.iterate()) {
console.log(row);
@@ -365,30 +338,29 @@ for (const row of query.iterate()) {
You can also use the `@@iterator` protocol:
```ts db.ts icon="/icons/typescript.svg" highlight={2}
```ts
const query = db.query("SELECT * FROM foo");
for (const row of query) {
console.log(row);
}
```
This feature was added in Bun v1.1.31.
### `.values()`
Use `values()` to run a query and get back all results as an array of arrays.
```ts db.ts icon="/icons/typescript.svg" highlight={3, 4}
```ts
const query = db.query(`select $message;`);
query.values({ $message: "Hello world" });
query.values(2);
```
```txt
[
[ "Iron Man", 2008 ],
[ "The Avengers", 2012 ],
[ "Ant-Man: Quantumania", 2023 ],
]
query.values(2);
// [
// [ "Iron Man", 2008 ],
// [ "The Avengers", 2012 ],
// [ "Ant-Man: Quantumania", 2023 ],
// ]
```
Internally, this calls [`sqlite3_reset`](https://www.sqlite.org/capi3ref.html#sqlite3_reset) and repeatedly calls [`sqlite3_step`](https://www.sqlite.org/capi3ref.html#sqlite3_step) until it returns `SQLITE_DONE`.
@@ -397,7 +369,7 @@ Internally, this calls [`sqlite3_reset`](https://www.sqlite.org/capi3ref.html#sq
Use `.finalize()` to destroy a `Statement` and free any resources associated with it. Once finalized, a `Statement` cannot be executed again. Typically, the garbage collector will do this for you, but explicit finalization may be useful in performance-sensitive applications.
```ts db.ts icon="/icons/typescript.svg" highlight={3}
```ts
const query = db.query("SELECT title, year FROM movies");
const movies = query.all();
query.finalize();
@@ -407,7 +379,7 @@ query.finalize();
Calling `toString()` on a `Statement` instance prints the expanded SQL query. This is useful for debugging.
```ts db.ts icon="/icons/typescript.svg" highlight={6, 9, 12}
```ts
import { Database } from "bun:sqlite";
// setup
@@ -428,34 +400,42 @@ Internally, this calls [`sqlite3_expanded_sql`](https://www.sqlite.org/capi3ref.
Queries can contain parameters. These can be numerical (`?1`) or named (`$param` or `:param` or `@param`). Bind values to these parameters when executing the query:
```ts title="query.ts" icon="/icons/typescript.svg"
{% codetabs %}
```ts#Query
const query = db.query("SELECT * FROM foo WHERE bar = $bar");
const results = query.all({
$bar: "bar",
});
```
```txt
[{ "$bar": "bar" }]
```json#Results
[
{ "$bar": "bar" }
]
```
{% /codetabs %}
Numbered (positional) parameters work too:
```ts db.ts icon="/icons/typescript.svg"
{% codetabs %}
```ts#Query
const query = db.query("SELECT ?1, ?2");
const results = query.all("hello", "goodbye");
```
```txt
```ts#Results
[
{
"?1": "hello",
"?2": "goodbye",
},
];
{
"?1": "hello",
"?2": "goodbye"
}
]
```
---
{% /codetabs %}
## Integers
@@ -467,25 +447,26 @@ By default, `bun:sqlite` returns integers as `number` types. If you need to hand
### `safeIntegers: true`
{% callout %}
Added in Bun v1.1.14
{% /callout %}
When `safeIntegers` is `true`, `bun:sqlite` will return integers as `bigint` types:
```ts db.ts icon="/icons/typescript.svg" highlight={3}
```ts
import { Database } from "bun:sqlite";
const db = new Database(":memory:", { safeIntegers: true });
const query = db.query(`SELECT ${BigInt(Number.MAX_SAFE_INTEGER) + 102n} as max_int`);
const query = db.query(
`SELECT ${BigInt(Number.MAX_SAFE_INTEGER) + 102n} as max_int`,
);
const result = query.get();
console.log(result.max_int);
```
```txt
9007199254741093n
console.log(result.max_int); // => 9007199254741093n
```
When `safeIntegers` is `true`, `bun:sqlite` will throw an error if a `bigint` value in a bound parameter exceeds 64 bits:
```ts db.ts icon="/icons/typescript.svg" highlight={3}
```ts
import { Database } from "bun:sqlite";
const db = new Database(":memory:", { safeIntegers: true });
@@ -496,38 +477,30 @@ const query = db.query("INSERT INTO test (value) VALUES ($value)");
try {
query.run({ $value: BigInt(Number.MAX_SAFE_INTEGER) ** 2n });
} catch (e) {
console.log(e.message);
console.log(e.message); // => BigInt value '81129638414606663681390495662081' is out of range
}
```
```txt
BigInt value '81129638414606663681390495662081' is out of range
```
### `safeIntegers: false` (default)
When `safeIntegers` is `false`, `bun:sqlite` will return integers as `number` types and truncate any bits beyond 53:
```ts db.ts icon="/icons/typescript.svg" highlight={3}
```ts
import { Database } from "bun:sqlite";
const db = new Database(":memory:", { safeIntegers: false });
const query = db.query(`SELECT ${BigInt(Number.MAX_SAFE_INTEGER) + 102n} as max_int`);
const query = db.query(
`SELECT ${BigInt(Number.MAX_SAFE_INTEGER) + 102n} as max_int`,
);
const result = query.get();
console.log(result.max_int);
console.log(result.max_int); // => 9007199254741092
```
```txt
9007199254741092
```
---
## Transactions
Transactions are a mechanism for executing multiple queries in an _atomic_ way; that is, either all of the queries succeed or none of them do. Create a transaction with the `db.transaction()` method:
```ts db.ts icon="/icons/typescript.svg" highlight={2}
```ts
const insertCat = db.prepare("INSERT INTO cats (name) VALUES ($name)");
const insertCats = db.transaction(cats => {
for (const cat of cats) insertCat.run(cat);
@@ -538,32 +511,42 @@ At this stage, we haven't inserted any cats! The call to `db.transaction()` retu
To execute the transaction, call this function. All arguments will be passed through to the wrapped function; the return value of the wrapped function will be returned by the transaction function. The wrapped function also has access to the `this` context as defined where the transaction is executed.
```ts db.ts icon="/icons/typescript.svg" highlight={3}
```ts
const insert = db.prepare("INSERT INTO cats (name) VALUES ($name)");
const insertCats = db.transaction(cats => {
for (const cat of cats) insert.run(cat);
return cats.length;
});
const count = insertCats([{ $name: "Keanu" }, { $name: "Salem" }, { $name: "Crookshanks" }]);
const count = insertCats([
{ $name: "Keanu" },
{ $name: "Salem" },
{ $name: "Crookshanks" },
]);
console.log(`Inserted ${count} cats`);
```
The driver will automatically [`begin`](https://www.sqlite.org/lang_transaction.html) a transaction when `insertCats` is called and `commit` it when the wrapped function returns. If an exception is thrown, the transaction will be rolled back. The exception will propagate as usual; it is not caught.
<Note>
{% callout %}
**Nested transactions** — Transaction functions can be called from inside other transaction functions. When doing so, the inner transaction becomes a [savepoint](https://www.sqlite.org/lang_savepoint.html).
<Accordion title="View nested transaction example">
{% details summary="View nested transaction example" %}
```ts db.ts icon="/icons/typescript.svg"
```ts
// setup
import { Database } from "bun:sqlite";
const db = Database.open(":memory:");
db.run("CREATE TABLE expenses (id INTEGER PRIMARY KEY AUTOINCREMENT, note TEXT, dollars INTEGER);");
db.run("CREATE TABLE cats (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE, age INTEGER)");
const insertExpense = db.prepare("INSERT INTO expenses (note, dollars) VALUES (?, ?)");
db.run(
"CREATE TABLE expenses (id INTEGER PRIMARY KEY AUTOINCREMENT, note TEXT, dollars INTEGER);",
);
db.run(
"CREATE TABLE cats (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE, age INTEGER)",
);
const insertExpense = db.prepare(
"INSERT INTO expenses (note, dollars) VALUES (?, ?)",
);
const insert = db.prepare("INSERT INTO cats (name, age) VALUES ($name, $age)");
const insertCats = db.transaction(cats => {
for (const cat of cats) insert.run(cat);
@@ -581,8 +564,8 @@ adopt([
]);
```
</Accordion>
</Note>
{% /details %}
{% /callout %}
Transactions also come with `deferred`, `immediate`, and `exclusive` versions.
@@ -597,24 +580,24 @@ insertCats.exclusive(cats); // uses "BEGIN EXCLUSIVE"
To load a [SQLite extension](https://www.sqlite.org/loadext.html), call `.loadExtension(name)` on your `Database` instance
```ts db.ts icon="/icons/typescript.svg" highlight={4}
```ts
import { Database } from "bun:sqlite";
const db = new Database();
db.loadExtension("myext");
```
<Note>
{% details summary="For macOS users" %}
**MacOS users** By default, macOS ships with Apple's proprietary build of SQLite, which doesn't support extensions. To use extensions, you'll need to install a vanilla build of SQLite.
```bash terminal icon="terminal"
brew install sqlite
which sqlite # get path to binary
```bash
$ brew install sqlite
$ which sqlite # get path to binary
```
To point `bun:sqlite` to the new build, call `Database.setCustomSQLite(path)` before creating any `Database` instances. (On other operating systems, this is a no-op.) Pass a path to the SQLite `.dylib` file, _not_ the executable. With recent versions of Homebrew this is something like `/opt/homebrew/Cellar/sqlite/<version>/libsqlite3.dylib`.
```ts db.ts icon="/icons/typescript.svg" highlight={3}
```ts
import { Database } from "bun:sqlite";
Database.setCustomSQLite("/path/to/libsqlite.dylib");
@@ -623,13 +606,13 @@ const db = new Database();
db.loadExtension("myext");
```
</Note>
{% /details %}
### `.fileControl(cmd: number, value: any)`
### .fileControl(cmd: number, value: any)
To use the advanced `sqlite3_file_control` API, call `.fileControl(cmd, value)` on your `Database` instance.
```ts db.ts icon="/icons/typescript.svg" highlight={6}
```ts
import { Database, constants } from "bun:sqlite";
const db = new Database();
@@ -644,11 +627,9 @@ db.fileControl(constants.SQLITE_FCNTL_PERSIST_WAL, 0);
- `TypedArray`
- `undefined` or `null`
---
## Reference
```ts Type Reference icon="/icons/typescript.svg" expandable
```ts
class Database {
constructor(
filename: string,
@@ -658,33 +639,25 @@ class Database {
readonly?: boolean;
create?: boolean;
readwrite?: boolean;
safeIntegers?: boolean;
strict?: boolean;
},
);
query<ReturnType, ParamsType>(sql: string): Statement<ReturnType, ParamsType>;
prepare<ReturnType, ParamsType>(sql: string): Statement<ReturnType, ParamsType>;
run(sql: string, params?: SQLQueryBindings): { lastInsertRowid: number; changes: number };
query<Params, ReturnType>(sql: string): Statement<Params, ReturnType>;
run(
sql: string,
params?: SQLQueryBindings,
): { lastInsertRowid: number; changes: number };
exec = this.run;
transaction(insideTransaction: (...args: any) => void): CallableFunction & {
deferred: (...args: any) => void;
immediate: (...args: any) => void;
exclusive: (...args: any) => void;
};
close(throwOnError?: boolean): void;
}
class Statement<ReturnType, ParamsType> {
all(...params: ParamsType[]): ReturnType[];
get(...params: ParamsType[]): ReturnType | null;
run(...params: ParamsType[]): {
class Statement<Params, ReturnType> {
all(params: Params): ReturnType[];
get(params: Params): ReturnType | undefined;
run(params: Params): {
lastInsertRowid: number;
changes: number;
};
values(...params: ParamsType[]): unknown[][];
values(params: Params): unknown[][];
finalize(): void; // destroy statement and clean up resources
toString(): string; // serialize to SQL
@@ -695,7 +668,7 @@ class Statement<ReturnType, ParamsType> {
paramsCount: number; // the number of parameters expected by the statement
native: any; // the native object representing the statement
as<T>(Class: new (...args: any[]) => T): Statement<T, ParamsType>;
as(Class: new () => ReturnType): this;
}
type SQLQueryBindings =

View File

@@ -1,19 +1,10 @@
---
title: Streams
description: Use Bun's streams API to work with binary data without loading it all into memory at once
---
Streams are an important abstraction for working with binary data without loading it all into memory at once. They are commonly used for reading and writing files, sending and receiving network requests, and processing large amounts of data.
Bun implements the Web APIs [`ReadableStream`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) and [`WritableStream`](https://developer.mozilla.org/en-US/docs/Web/API/WritableStream).
<Note>
Bun also implements the `node:stream` module, including
[`Readable`](https://nodejs.org/api/stream.html#stream_readable_streams),
[`Writable`](https://nodejs.org/api/stream.html#stream_writable_streams), and
[`Duplex`](https://nodejs.org/api/stream.html#stream_duplex_and_transform_streams). For complete documentation, refer
to the [Node.js docs](https://nodejs.org/api/stream.html).
</Note>
{% callout %}
Bun also implements the `node:stream` module, including [`Readable`](https://nodejs.org/api/stream.html#stream_readable_streams), [`Writable`](https://nodejs.org/api/stream.html#stream_writable_streams), and [`Duplex`](https://nodejs.org/api/stream.html#stream_duplex_and_transform_streams). For complete documentation, refer to the [Node.js docs](https://nodejs.org/api/stream.html).
{% /callout %}
To create a simple `ReadableStream`:
@@ -32,19 +23,28 @@ The contents of a `ReadableStream` can be read chunk-by-chunk with `for await` s
```ts
for await (const chunk of stream) {
console.log(chunk);
// => "hello"
// => "world"
}
// hello
// world
```
---
`ReadableStream` also provides convenience methods for consuming the entire stream:
```ts
const stream = new ReadableStream({
start(controller) {
controller.enqueue("hello world");
controller.close();
},
});
const data = await stream.text(); // => "hello world"
// Also available: .json(), .bytes(), .blob()
```
## Direct `ReadableStream`
Bun implements an optimized version of `ReadableStream` that avoid unnecessary data copying & queue management logic.
With a traditional `ReadableStream`, chunks of data are _enqueued_. Each chunk is copied into a queue, where it sits until the stream is ready to send more data.
Bun implements an optimized version of `ReadableStream` that avoid unnecessary data copying & queue management logic. With a traditional `ReadableStream`, chunks of data are _enqueued_. Each chunk is copied into a queue, where it sits until the stream is ready to send more data.
```ts
const stream = new ReadableStream({
@@ -60,7 +60,7 @@ With a direct `ReadableStream`, chunks of data are written directly to the strea
```ts
const stream = new ReadableStream({
type: "direct", // [!code ++]
type: "direct",
pull(controller) {
controller.write("hello");
controller.write("world");
@@ -70,8 +70,6 @@ const stream = new ReadableStream({
When using a direct `ReadableStream`, all chunk queueing is handled by the destination. The consumer of the stream receives exactly what is passed to `controller.write()`, without any encoding or modification.
---
## Async generator streams
Bun also supports async generator functions as a source for `Response` and `Request`. This is an easy way to create a `ReadableStream` that fetches data from an asynchronous source.
@@ -113,8 +111,6 @@ const response = new Response({
await response.text(); // "hello"
```
---
## `Bun.ArrayBufferSink`
The `Bun.ArrayBufferSink` class is a fast incremental writer for constructing an `ArrayBuffer` of unknown size.
@@ -134,10 +130,10 @@ sink.end();
To instead retrieve the data as a `Uint8Array`, pass the `asUint8Array` option to the `start` method.
```ts
```ts-diff
const sink = new Bun.ArrayBufferSink();
sink.start({
asUint8Array: true, // [!code ++]
+ asUint8Array: true
});
sink.write("h");
@@ -165,7 +161,7 @@ Once `.end()` is called, no more data can be written to the `ArrayBufferSink`. H
```ts
const sink = new Bun.ArrayBufferSink();
sink.start({
stream: true, // [!code ++]
stream: true,
});
sink.write("h");
@@ -187,15 +183,13 @@ To manually set the size of the internal buffer in bytes, pass a value for `high
```ts
const sink = new Bun.ArrayBufferSink();
sink.start({
highWaterMark: 1024 * 1024, // 1 MB // [!code ++]
highWaterMark: 1024 * 1024, // 1 MB
});
```
---
{% details summary="Reference" %}
## Reference
```ts See Typescript Definitions expandable
```ts
/**
* Fast incremental writer that becomes an `ArrayBuffer` on end().
*/
@@ -216,7 +210,9 @@ export class ArrayBufferSink {
stream?: boolean;
}): void;
write(chunk: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer): number;
write(
chunk: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer,
): number;
/**
* Flush the internal buffer
*
@@ -230,3 +226,5 @@ export class ArrayBufferSink {
end(): ArrayBuffer | Uint8Array<ArrayBuffer>;
}
```
{% /details %}

View File

@@ -1,15 +1,10 @@
---
title: TCP
description: Use Bun's native TCP API to implement performance sensitive systems like database clients, game servers, or anything that needs to communicate over TCP (instead of HTTP)
---
This is a low-level API intended for library authors and for advanced use cases.
Use Bun's native TCP API to implement performance sensitive systems like database clients, game servers, or anything that needs to communicate over TCP (instead of HTTP). This is a low-level API intended for library authors and for advanced use cases.
## Start a server (`Bun.listen()`)
To start a TCP server with `Bun.listen`:
```ts server.ts icon="/icons/typescript.svg"
```ts
Bun.listen({
hostname: "localhost",
port: 8080,
@@ -23,11 +18,11 @@ Bun.listen({
});
```
<Accordion title="An API designed for speed">
{% details summary="An API designed for speed" %}
In Bun, a set of handlers are declared once per server instead of assigning callbacks to each socket, as with Node.js `EventEmitters` or the web-standard `WebSocket` API.
```ts server.ts icon="/icons/typescript.svg"
```ts
Bun.listen({
hostname: "localhost",
port: 8080,
@@ -43,11 +38,11 @@ Bun.listen({
For performance-sensitive servers, assigning listeners to each socket can cause significant garbage collector pressure and increase memory usage. By contrast, Bun only allocates one handler function for each event and shares it among all sockets. This is a small optimization, but it adds up.
</Accordion>
{% /details %}
Contextual data can be attached to a socket in the `open` handler.
```ts server.ts icon="/icons/typescript.svg"
```ts
type SocketData = { sessionId: string };
Bun.listen<SocketData>({
@@ -55,10 +50,10 @@ Bun.listen<SocketData>({
port: 8080,
socket: {
data(socket, data) {
socket.write(`${socket.data.sessionId}: ack`); // [!code ++]
socket.write(`${socket.data.sessionId}: ack`);
},
open(socket) {
socket.data = { sessionId: "abcd" }; // [!code ++]
socket.data = { sessionId: "abcd" };
},
},
});
@@ -66,7 +61,7 @@ Bun.listen<SocketData>({
To enable TLS, pass a `tls` object containing `key` and `cert` fields.
```ts server.ts icon="/icons/typescript.svg"
```ts
Bun.listen({
hostname: "localhost",
port: 8080,
@@ -75,29 +70,33 @@ Bun.listen({
},
tls: {
// can be string, BunFile, TypedArray, Buffer, or array thereof
key: Bun.file("./key.pem"), // [!code ++]
cert: Bun.file("./cert.pem"), // [!code ++]
key: Bun.file("./key.pem"),
cert: Bun.file("./cert.pem"),
},
});
```
The `key` and `cert` fields expect the _contents_ of your TLS key and certificate. This can be a string, `BunFile`, `TypedArray`, or `Buffer`.
```ts server.ts icon="/icons/typescript.svg"
```ts
Bun.listen({
// ...
tls: {
key: Bun.file("./key.pem"), // BunFile
key: fs.readFileSync("./key.pem"), // Buffer
key: fs.readFileSync("./key.pem", "utf8"), // string
key: [Bun.file("./key1.pem"), Bun.file("./key2.pem")], // array of above
// BunFile
key: Bun.file("./key.pem"),
// Buffer
key: fs.readFileSync("./key.pem"),
// string
key: fs.readFileSync("./key.pem", "utf8"),
// array of above
key: [Bun.file("./key1.pem"), Bun.file("./key2.pem")],
},
});
```
The result of `Bun.listen` is a server that conforms to the `TCPSocket` interface.
```ts server.ts icon="/icons/typescript.svg"
```ts
const server = Bun.listen({
/* config*/
});
@@ -110,13 +109,11 @@ server.stop(true);
server.unref();
```
---
## Create a connection (`Bun.connect()`)
Use `Bun.connect` to connect to a TCP server. Specify the server to connect to with `hostname` and `port`. TCP clients can define the same set of handlers as `Bun.listen`, plus a couple client-specific handlers.
```ts server.ts icon="/icons/typescript.svg"
```ts
// The client
const socket = await Bun.connect({
hostname: "localhost",
@@ -143,48 +140,39 @@ To require TLS, specify `tls: true`.
// The client
const socket = await Bun.connect({
// ... config
tls: true, // [!code ++]
tls: true,
});
```
---
## Hot reloading
Both TCP servers and sockets can be hot reloaded with new handlers.
<CodeGroup>
{% codetabs %}
```ts server.ts icon="/icons/typescript.svg"
const server = Bun.listen({
/* config */
});
```ts#Server
const server = Bun.listen({ /* config */ })
// reloads handlers for all active server-side sockets
server.reload({
socket: {
data() {
data(){
// new 'data' handler
},
},
});
}
}
})
```
```ts client.ts icon="/icons/typescript.svg"
const socket = await Bun.connect({
/* config */
});
```ts#Client
const socket = await Bun.connect({ /* config */ })
socket.reload({
data() {
data(){
// new 'data' handler
},
});
}
})
```
</CodeGroup>
---
{% /codetabs %}
## Buffering
@@ -206,14 +194,11 @@ socket.write("hello");
To simplify this for now, consider using Bun's `ArrayBufferSink` with the `{stream: true}` option:
```ts server.ts icon="/icons/typescript.svg"
```ts
import { ArrayBufferSink } from "bun";
const sink = new ArrayBufferSink();
sink.start({
stream: true, // [!code ++]
highWaterMark: 1024,
});
sink.start({ stream: true, highWaterMark: 1024 });
sink.write("h");
sink.write("e");
@@ -231,9 +216,6 @@ queueMicrotask(() => {
});
```
<Note>
**Corking**
Support for corking is planned, but in the meantime backpressure must be managed manually with the `drain` handler.
</Note>
{% callout %}
**Corking** — Support for corking is planned, but in the meantime backpressure must be managed manually with the `drain` handler.
{% /callout %}

View File

@@ -1,8 +1,3 @@
---
title: Transpiler
description: Use Bun's transpiler to transpile JavaScript and TypeScript code
---
Bun exposes its internal transpiler via the `Bun.Transpiler` class. To create an instance of Bun's transpiler:
```ts
@@ -11,14 +6,15 @@ const transpiler = new Bun.Transpiler({
});
```
---
## `.transformSync()`
Transpile code synchronously with the `.transformSync()` method. Modules are not resolved and the code is not executed. The result is a string of vanilla JavaScript code.
<CodeGroup>
```ts transpile.ts icon="/icons/typescript.svg"
<!-- It is synchronous and runs in the same thread as other JavaScript code. -->
{% codetabs %}
```js#Example
const transpiler = new Bun.Transpiler({
loader: 'tsx',
});
@@ -30,10 +26,9 @@ export function Home(props: {title: string}){
}`;
const result = transpiler.transformSync(code);
```
````
```ts output
```js#Result
import { __require as require } from "bun:wrap";
import * as JSX from "react/jsx-dev-runtime";
var jsx = require(JSX).jsxDEV;
@@ -48,9 +43,9 @@ export default jsx(
undefined,
this,
);
````
```
</CodeGroup>
{% /codetabs %}
To override the default loader specified in the `new Bun.Transpiler()` constructor, pass a second argument to `.transformSync()`.
@@ -58,15 +53,11 @@ To override the default loader specified in the `new Bun.Transpiler()` construct
transpiler.transformSync("<div>hi!</div>", "tsx");
```
<Accordion title="Nitty gritty">
{% details summary="Nitty gritty" %}
When `.transformSync` is called, the transpiler is run in the same thread as the currently executed code.
If a macro is used, it will be run in the same thread as the transpiler, but in a separate event loop from the rest of your application. Currently, globals between macros and regular code are shared, which means it is possible (but not recommended) to share states between macros and regular code. Attempting to use AST nodes outside of a macro is undefined behavior.
</Accordion>
---
{% /details %}
## `.transform()`
@@ -84,23 +75,21 @@ Unless you're transpiling _many_ large files, you should probably use `Bun.Trans
await transpiler.transform("<div>hi!</div>", "tsx");
```
<Accordion title="Nitty gritty">
{% details summary="Nitty gritty" %}
The `.transform()` method runs the transpiler in Bun's worker threadpool, so if you run it 100 times, it will run it across `Math.floor($cpu_count * 0.8)` threads, without blocking the main JavaScript thread.
If your code uses a macro, it will potentially spawn a new copy of Bun's JavaScript runtime environment in that new thread.
</Accordion>
{% /details %}
## `.scan()`
The `Transpiler` instance can also scan some source code and return a list of its imports and exports, plus additional metadata about each one. [Type-only](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html#type-only-imports-and-export) imports and exports are ignored.
<CodeGroup>
{% codetabs %}
```ts example.ts icon="/icons/typescript.svg"
```ts#Example
const transpiler = new Bun.Transpiler({
loader: "tsx",
loader: 'tsx',
});
const code = `
@@ -115,9 +104,11 @@ export const name = "hello";
const result = transpiler.scan(code);
```
```json output
```json#Output
{
"exports": ["name"],
"exports": [
"name"
],
"imports": [
{
"kind": "import-statement",
@@ -135,7 +126,7 @@ const result = transpiler.scan(code);
}
```
</CodeGroup>
{% /codetabs %}
Each import in the `imports` array has a `path` and `kind`. Bun categories imports into the following kinds:
@@ -145,18 +136,19 @@ Each import in the `imports` array has a `path` and `kind`. Bun categories impor
- `dynamic-import`: `import('./loader')`
- `import-rule`: `@import 'foo.css'`
- `url-token`: `url('./foo.png')`
---
<!-- - `internal`: `import {foo} from 'bun:internal'`
- `entry-point-build`: `import {foo} from 'bun:entry'`
- `entry-point-run`: `bun ./mymodule` -->
## `.scanImports()`
For performance-sensitive code, you can use the `.scanImports()` method to get a list of imports. It's faster than `.scan()` (especially for large files) but marginally less accurate due to some performance optimizations.
<CodeGroup>
{% codetabs %}
```ts example.ts icon="/icons/typescript.svg"
```ts#Example
const transpiler = new Bun.Transpiler({
loader: "tsx",
loader: 'tsx',
});
const code = `
@@ -171,30 +163,26 @@ export const name = "hello";
const result = transpiler.scanImports(code);
```
```json results icon="file-json"
```json#Results
[
{
"kind": "import-statement",
"path": "react"
},
{
"kind": "require-call",
"path": "./cjs.js"
},
{
"kind": "dynamic-import",
"path": "./loader"
kind: "import-statement",
path: "react"
}, {
kind: "require-call",
path: "./cjs.js"
}, {
kind: "dynamic-import",
path: "./loader"
}
]
```
</CodeGroup>
---
{% /codetabs %}
## Reference
```ts See Typescript Definitions expandable
```ts
type Loader = "jsx" | "js" | "ts" | "tsx";
interface TranspilerOptions {
@@ -278,7 +266,7 @@ type Import = {
// url() in CSS
| "url-token"
// The import was injected by Bun
| "internal"
| "internal" 
// Entry point (not common)
| "entry-point-build"
| "entry-point-run"

View File

@@ -1,7 +1,4 @@
---
title: UDP
description: Use Bun's UDP API to implement services with advanced real-time requirements, such as voice chat.
---
Use Bun's UDP API to implement services with advanced real-time requirements, such as voice chat.
## Bind a UDP socket (`Bun.udpSocket()`)
@@ -16,9 +13,8 @@ Specify a port:
```ts
const socket = await Bun.udpSocket({
port: 41234, // [!code ++]
port: 41234,
});
console.log(socket.port); // 41234
```
@@ -37,7 +33,7 @@ DNS resolution, as it is intended for low-latency operations.
When creating your socket, add a callback to specify what should be done when packets are received:
```ts server.ts icon="/icons/typescript.svg"
```ts
const server = await Bun.udpSocket({
socket: {
data(socket, buf, port, addr) {
@@ -57,7 +53,7 @@ While UDP does not have a concept of a connection, many UDP communications (espe
In such cases it can be beneficial to connect the socket to that peer, which specifies to which address all packets are sent
and restricts incoming packets to that peer only.
```ts server.ts icon="/icons/typescript.svg"
```ts
const server = await Bun.udpSocket({
socket: {
data(socket, buf, port, addr) {
@@ -66,7 +62,6 @@ const server = await Bun.udpSocket({
},
},
});
const client = await Bun.udpSocket({
connect: {
port: server.port,
@@ -87,23 +82,21 @@ of making a system call for each. This is made possible by the `sendMany()` API:
For an unconnected socket, `sendMany` takes an array as its only argument. Each set of three array elements describes a packet:
The first item is the data to be sent, the second is the target port, and the last is the target address.
```ts server.ts icon="/icons/typescript.svg"
```ts
const socket = await Bun.udpSocket({});
// sends 'Hello' to 127.0.0.1:41234, and 'foo' to 1.1.1.1:53 in a single operation
socket.sendMany(["Hello", 41234, "127.0.0.1", "foo", 53, "1.1.1.1"]);
```
With a connected socket, `sendMany` simply takes an array, where each element represents the data to be sent to the peer.
```ts server.ts icon="/icons/typescript.svg"
```ts
const socket = await Bun.udpSocket({
connect: {
port: 41234,
hostname: "localhost",
},
});
socket.sendMany(["foo", "bar", "baz"]);
```
@@ -116,7 +109,8 @@ It may happen that a packet that you're sending does not fit into the operating
has happened when:
- `send` returns `false`
- `sendMany` returns a number smaller than the number of packets you specified. In this case, the `drain` socket handler will be called once the socket becomes writable again:
- `sendMany` returns a number smaller than the number of packets you specified
In this case, the `drain` socket handler will be called once the socket becomes writable again:
```ts
const socket = await Bun.udpSocket({
@@ -127,54 +121,3 @@ const socket = await Bun.udpSocket({
},
});
```
### Socket options
UDP sockets support setting various socket options:
```ts
const socket = await Bun.udpSocket({});
// Enable broadcasting to send packets to a broadcast address
socket.setBroadcast(true);
// Set the IP TTL (time to live) for outgoing packets
socket.setTTL(64);
```
### Multicast
Bun supports multicast operations for UDP sockets. Use `addMembership` and `dropMembership` to join and leave multicast groups:
```ts
const socket = await Bun.udpSocket({});
// Join a multicast group
socket.addMembership("224.0.0.1");
// Join with a specific interface
socket.addMembership("224.0.0.1", "192.168.1.100");
// Leave a multicast group
socket.dropMembership("224.0.0.1");
```
Additional multicast options:
```ts
// Set TTL for multicast packets (number of network hops)
socket.setMulticastTTL(2);
// Control whether multicast packets loop back to the local socket
socket.setMulticastLoopback(true);
// Specify which interface to use for outgoing multicast packets
socket.setMulticastInterface("192.168.1.100");
```
For source-specific multicast (SSM), use `addSourceSpecificMembership` and `dropSourceSpecificMembership`:
```ts
socket.addSourceSpecificMembership("10.0.0.1", "232.0.0.1");
socket.dropSourceSpecificMembership("10.0.0.1", "232.0.0.1");
```

View File

@@ -1,22 +1,17 @@
---
title: Utils
description: Use Bun's utility functions to work with the runtime
---
## `Bun.version`
A `string` containing the version of the `bun` CLI that is currently running.
```ts terminal icon="terminal"
```ts
Bun.version;
// => "1.3.3"
// => "0.6.4"
```
## `Bun.revision`
The git commit of [Bun](https://github.com/oven-sh/bun) that was compiled to create the current `bun` CLI.
```ts terminal icon="terminal"
```ts
Bun.revision;
// => "f02561530fda1ee9396f51c8bc99b38716e38296"
```
@@ -29,7 +24,7 @@ An alias for `process.env`.
An absolute path to the entrypoint of the current program (the file that was executed with `bun run`).
```ts script.ts
```ts#script.ts
Bun.main;
// /path/to/script.ts
```
@@ -132,11 +127,17 @@ The final 8 bytes of the UUID are a cryptographically secure random value. It us
```ts
namespace Bun {
function randomUUIDv7(encoding?: "hex" | "base64" | "base64url" = "hex", timestamp?: number = Date.now()): string;
function randomUUIDv7(
encoding?: "hex" | "base64" | "base64url" = "hex",
timestamp?: number = Date.now(),
): string;
/**
* If you pass "buffer", you get a 16-byte buffer instead of a string.
*/
function randomUUIDv7(encoding: "buffer", timestamp?: number = Date.now()): Buffer;
function randomUUIDv7(
encoding: "buffer",
timestamp?: number = Date.now(),
): Buffer;
// If you only pass a timestamp, you get a hex string
function randomUUIDv7(timestamp?: number = Date.now()): string;
@@ -145,13 +146,13 @@ namespace Bun {
You can optionally set encoding to `"buffer"` to get a 16-byte buffer instead of a string. This can sometimes avoid string conversion overhead.
```ts buffer.ts
```ts#buffer.ts
const buffer = Bun.randomUUIDv7("buffer");
```
`base64` and `base64url` encodings are also supported when you want a slightly shorter string.
```ts base64.ts
```ts#base64.ts
const base64 = Bun.randomUUIDv7("base64");
const base64url = Bun.randomUUIDv7("base64url");
```
@@ -199,7 +200,9 @@ test("peek", () => {
// If we peek a rejected promise, it:
// - returns the error
// - does not mark the promise as handled
const rejected = Promise.reject(new Error("Successfully tested promise rejection"));
const rejected = Promise.reject(
new Error("Successfully tested promise rejection"),
);
expect(peek(rejected).message).toBe("Successfully tested promise rejection");
});
```
@@ -231,11 +234,11 @@ const currentFile = import.meta.url;
Bun.openInEditor(currentFile);
```
You can override this via the `debug.editor` setting in your [`bunfig.toml`](/runtime/bunfig).
You can override this via the `debug.editor` setting in your [`bunfig.toml`](https://bun.com/docs/runtime/bunfig).
```toml bunfig.toml icon="settings"
[debug] # [!code ++]
editor = "code" # [!code ++]
```toml-diff#bunfig.toml
+ [debug]
+ editor = "code"
```
Or specify an editor with the `editor` param. You can also specify a line and column number.
@@ -307,9 +310,7 @@ This function is optimized for large input. On an M1X, it processes 480 MB/s -
20 GB/s, depending on how much data is being escaped and whether there is non-ascii
text. Non-string types will be converted to a string before escaping.
## `Bun.stringWidth()`
<Note>~6,756x faster `string-width` alternative</Note>
## `Bun.stringWidth()` ~6,756x faster `string-width` alternative
Get the column count of a string as it would be displayed in a terminal.
Supports ANSI escape codes, emoji, and wide characters.
@@ -351,7 +352,7 @@ npm/string-width 500 chars ascii 249,710 ns/iter (239,970 ns …
To make `Bun.stringWidth` fast, we've implemented it in Zig using optimized SIMD instructions, accounting for Latin1, UTF-16, and UTF-8 encodings. It passes `string-width`'s tests.
<Accordion title="View full benchmark">
{% details summary="View full benchmark" %}
As a reminder, 1 nanosecond (ns) is 1 billionth of a second. Here's a quick reference for converting between units:
@@ -361,7 +362,7 @@ As a reminder, 1 nanosecond (ns) is 1 billionth of a second. Here's a quick refe
| µs | 1,000 |
| ms | 1 |
```bash terminal icon="terminal"
```js
bun string-width.mjs
cpu: 13th Gen Intel(R) Core(TM) i9-13900
runtime: bun 1.0.29 (x64-linux)
@@ -390,7 +391,7 @@ Bun.stringWidth 19,000 chars ansi+emoji+ascii 114.06 µs/iter (112.86 µs … 1
Bun.stringWidth 95,000 chars ansi+emoji+ascii 572.69 µs/iter (565.52 µs … 607.22 µs) 572.45 µs 604.86 µs 605.21 µs
```
```bash terminal icon="terminal"
```ts
node string-width.mjs
cpu: 13th Gen Intel(R) Core(TM) i9-13900
runtime: node v21.4.0 (x64-linux)
@@ -419,11 +420,11 @@ npm/string-width 19,000 chars ansi+emoji+ascii 27.19 ms/iter (26.89 ms … 2
npm/string-width 95,000 chars ansi+emoji+ascii 3.68 s/iter (3.66 s … 3.7 s) 3.69 s 3.7 s 3.7 s
```
</Accordion>
{% /details %}
TypeScript definition:
```ts expandable
```ts
namespace Bun {
export function stringWidth(
/**
@@ -448,7 +449,7 @@ namespace Bun {
}
```
---
<!-- ## `Bun.enableANSIColors()` -->
## `Bun.fileURLToPath()`
@@ -459,8 +460,6 @@ const path = Bun.fileURLToPath(new URL("file:///foo/bar.txt"));
console.log(path); // "/foo/bar.txt"
```
---
## `Bun.pathToFileURL()`
Converts an absolute path to a `file://` URL.
@@ -470,7 +469,7 @@ const url = Bun.pathToFileURL("/foo/bar.txt");
console.log(url); // "file:///foo/bar.txt"
```
---
<!-- Bun.hash; -->
## `Bun.gzipSync()`
@@ -486,9 +485,9 @@ compressed; // => Uint8Array(30)
Optionally, pass a parameters object as the second argument:
<Accordion title="zlib compression options">
{% details summary="zlib compression options"%}
```ts expandable
```ts
export type ZlibCompressionOptions = {
/**
* The compression level to use. Must be between `-1` and `9`.
@@ -559,9 +558,7 @@ export type ZlibCompressionOptions = {
};
```
</Accordion>
---
{% /details %}
## `Bun.gunzipSync()`
@@ -577,8 +574,6 @@ dec.decode(uncompressed);
// => "hellohellohello..."
```
---
## `Bun.deflateSync()`
Compresses a `Uint8Array` using zlib's DEFLATE algorithm.
@@ -593,8 +588,6 @@ compressed; // => Uint8Array(12)
The second argument supports the same set of configuration options as [`Bun.gzipSync`](#bun-gzipsync).
---
## `Bun.inflateSync()`
Decompresses a `Uint8Array` using zlib's INFLATE algorithm.
@@ -609,8 +602,6 @@ dec.decode(decompressed);
// => "hellohellohello..."
```
---
## `Bun.zstdCompress()` / `Bun.zstdCompressSync()`
Compresses a `Uint8Array` using the Zstandard algorithm.
@@ -645,8 +636,6 @@ dec.decode(decompressedSync);
// => "hellohellohello..."
```
---
## `Bun.inspect()`
Serializes an object to a `string` exactly as it would be printed by `console.log`.
@@ -661,7 +650,7 @@ const str = Bun.inspect(arr);
// => "Uint8Array(3) [ 1, 2, 3 ]"
```
### `Bun.inspect.custom`
## `Bun.inspect.custom`
This is the symbol that Bun uses to implement `Bun.inspect`. You can override this to customize how your objects are printed. It is identical to `util.inspect.custom` in Node.js.
@@ -676,7 +665,7 @@ const foo = new Foo();
console.log(foo); // => "foo"
```
### `Bun.inspect.table(tabularData, properties, options)`
## `Bun.inspect.table(tabularData, properties, options)`
Format tabular data into a string. Like [`console.table`](https://developer.mozilla.org/en-US/docs/Web/API/console/table_static), except it returns a string rather than printing to the console.
@@ -735,8 +724,6 @@ console.log(
);
```
---
## `Bun.nanoseconds()`
Returns the number of nanoseconds since the current `bun` process started, as a `number`. Useful for high-precision timing and benchmarking.
@@ -746,8 +733,6 @@ Bun.nanoseconds();
// => 7288958
```
---
## `Bun.readableStreamTo*()`
Bun implements a set of convenience functions for asynchronously consuming the body of a `ReadableStream` and converting it to various binary formats.
@@ -782,8 +767,6 @@ await Bun.readableStreamToFormData(stream);
await Bun.readableStreamToFormData(stream, multipartFormBoundary);
```
---
## `Bun.resolveSync()`
Resolves a file path or module specifier using Bun's internal module resolution algorithm. The first argument is the path to resolve, and the second argument is the "root". If no match is found, an `Error` is thrown.
@@ -809,11 +792,21 @@ To resolve relative to the directory containing the current file, pass `import.m
Bun.resolveSync("./foo.ts", import.meta.dir);
```
---
## `serialize` & `deserialize` in `bun:jsc`
## `Bun.stripANSI()`
To save a JavaScript value into an ArrayBuffer & back, use `serialize` and `deserialize` from the `"bun:jsc"` module.
<Note>~6-57x faster `strip-ansi` alternative</Note>
```js
import { serialize, deserialize } from "bun:jsc";
const buf = serialize({ foo: "bar" });
const obj = deserialize(buf);
console.log(obj); // => { foo: "bar" }
```
Internally, [`structuredClone`](https://developer.mozilla.org/en-US/docs/Web/API/structuredClone) and [`postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) serialize and deserialize the same way. This exposes the underlying [HTML Structured Clone Algorithm](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm) to JavaScript as an ArrayBuffer.
## `Bun.stripANSI()` ~6-57x faster `strip-ansi` alternative
`Bun.stripANSI(text: string): string`
@@ -831,11 +824,8 @@ console.log(Bun.stripANSI(formatted)); // => "Bold and underlined"
`Bun.stripANSI` is significantly faster than the popular [`strip-ansi`](https://www.npmjs.com/package/strip-ansi) npm package:
```bash terminal icon="terminal"
bun bench/snippets/strip-ansi.mjs
```
```txt
```js
> bun bench/snippets/strip-ansi.mjs
cpu: Apple M3 Max
runtime: bun 1.2.21 (arm64-darwin)
@@ -854,11 +844,8 @@ Bun.stripANSI 212,992 chars long-ansi 227.65 µs/iter 234.50 µs
(216.46 µs … 401.92 µs) 262.25 µs
```
```bash terminal icon="terminal"
node bench/snippets/strip-ansi.mjs
```
```txt
```js
> node bench/snippets/strip-ansi.mjs
cpu: Apple M3 Max
runtime: node 24.6.0 (arm64-darwin)
@@ -878,24 +865,6 @@ npm/strip-ansi 212,992 chars long-ansi 1.36 ms/iter 1.38 ms
```
---
## `serialize` & `deserialize` in `bun:jsc`
To save a JavaScript value into an ArrayBuffer & back, use `serialize` and `deserialize` from the `"bun:jsc"` module.
```js
import { serialize, deserialize } from "bun:jsc";
const buf = serialize({ foo: "bar" });
const obj = deserialize(buf);
console.log(obj); // => { foo: "bar" }
```
Internally, [`structuredClone`](https://developer.mozilla.org/en-US/docs/Web/API/structuredClone) and [`postMessage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) serialize and deserialize the same way. This exposes the underlying [HTML Structured Clone Algorithm](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm) to JavaScript as an ArrayBuffer.
---
## `estimateShallowMemoryUsageOf` in `bun:jsc`
The `estimateShallowMemoryUsageOf` function returns a best-effort estimate of the memory usage of an object in bytes, excluding the memory usage of properties or other objects it references. For accurate per-object memory usage, use `Bun.generateHeapSnapshot`.

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