@@ -10,3 +10,295 @@ Conventions:
1010- Prefer ` @import ` at the ** bottom** of the file, but the auto formatter will move them so you don't need to worry about it.
1111- ** Never** use ` @import() ` inline inside of functions. ** Always** put them at the bottom of the file or containing struct. Imports in Zig are free of side-effects, so there's no such thing as a "dynamic" import.
1212- You must be patient with the build.
13+
14+ ## Prefer Bun APIs over ` std `
15+
16+ ** Always use ` bun.* ` APIs instead of ` std.* ` .** The ` bun ` namespace (` @import("bun") ` ) provides cross-platform wrappers that preserve OS error info and never use ` unreachable ` . Using ` std.fs ` , ` std.posix ` , or ` std.os ` directly is wrong in this codebase.
17+
18+ | Instead of | Use |
19+ | ------------------------------------------------------------ | ------------------------------------ |
20+ | ` std.fs.File ` | ` bun.sys.File ` |
21+ | ` std.fs.cwd() ` | ` bun.FD.cwd() ` |
22+ | ` std.posix.open/read/write/stat/mkdir/unlink/rename/symlink ` | ` bun.sys.* ` equivalents |
23+ | ` std.fs.path.join/dirname/basename ` | ` bun.path.join/dirname/basename ` |
24+ | ` std.mem.eql/indexOf/startsWith ` (for strings) | ` bun.strings.eql/indexOf/startsWith ` |
25+ | ` std.posix.O ` / ` std.posix.mode_t ` / ` std.posix.fd_t ` | ` bun.O ` / ` bun.Mode ` / ` bun.FD ` |
26+ | ` std.process.Child ` | ` bun.spawnSync ` |
27+ | ` catch bun.outOfMemory() ` | ` bun.handleOom(...) ` |
28+
29+ ## ` bun.sys ` — System Calls (` src/sys.zig ` )
30+
31+ All return ` Maybe(T) ` — a tagged union of ` .result: T ` or ` .err: bun.sys.Error ` :
32+
33+ ``` zig
34+ const fd = switch (bun.sys.open(path, bun.O.RDONLY, 0)) {
35+ .result => |fd| fd,
36+ .err => |err| return .{ .err = err },
37+ };
38+ // Or: const fd = try bun.sys.open(path, bun.O.RDONLY, 0).unwrap();
39+ ```
40+
41+ Key functions (all take ` bun.FileDescriptor ` , not ` std.posix.fd_t ` ):
42+
43+ - ` open ` , ` openat ` , ` openA ` (non-sentinel) → ` Maybe(bun.FileDescriptor) `
44+ - ` read ` , ` readAll ` , ` pread ` → ` Maybe(usize) `
45+ - ` write ` , ` pwrite ` , ` writev ` → ` Maybe(usize) `
46+ - ` stat ` , ` fstat ` , ` lstat ` → ` Maybe(bun.Stat) `
47+ - ` mkdir ` , ` unlink ` , ` rename ` , ` symlink ` , ` chmod ` , ` fchmod ` , ` fchown ` → ` Maybe(void) `
48+ - ` readlink ` , ` getFdPath ` , ` getcwd ` → ` Maybe ` of path slice
49+ - ` getFileSize ` , ` dup ` , ` sendfile ` , ` mmap `
50+
51+ Use ` bun.O.RDONLY ` , ` bun.O.WRONLY | bun.O.CREAT | bun.O.TRUNC ` , etc. for open flags.
52+
53+ ### ` bun.sys.File ` (` src/sys/File.zig ` )
54+
55+ Higher-level file handle wrapping ` bun.FileDescriptor ` :
56+
57+ ``` zig
58+ // One-shot read: open + read + close
59+ const bytes = switch (bun.sys.File.readFrom(bun.FD.cwd(), path, allocator)) {
60+ .result => |b| b,
61+ .err => |err| return .{ .err = err },
62+ };
63+
64+ // One-shot write: open + write + close
65+ switch (bun.sys.File.writeFile(bun.FD.cwd(), path, data)) {
66+ .result => {},
67+ .err => |err| return .{ .err = err },
68+ }
69+ ```
70+
71+ Key methods:
72+
73+ - ` File.open/openat/makeOpen ` → ` Maybe(File) ` (` makeOpen ` creates parent dirs)
74+ - ` file.read/readAll/write/writeAll ` — single or looped I/O
75+ - ` file.readToEnd(allocator) ` — read entire file into allocated buffer
76+ - ` File.readFrom(dir_fd, path, allocator) ` — open + read + close
77+ - ` File.writeFile(dir_fd, path, data) ` — open + write + close
78+ - ` file.stat() ` , ` file.close() ` , ` file.writer() ` , ` file.reader() `
79+
80+ ### ` bun.FD ` (` src/fd.zig ` )
81+
82+ Cross-platform file descriptor. Use ` bun.FD.cwd() ` for cwd, ` bun.invalid_fd ` for sentinel, ` fd.close() ` to close.
83+
84+ ### ` bun.sys.Error ` (` src/sys/Error.zig ` )
85+
86+ Preserves errno, syscall tag, and file path. Convert to JS: ` err.toSystemError().toErrorInstance(globalObject) ` .
87+
88+ ## ` bun.strings ` — String Utilities (` src/string/immutable.zig ` )
89+
90+ SIMD-accelerated string operations. Use instead of ` std.mem ` for strings.
91+
92+ ``` zig
93+ // Searching
94+ strings.indexOf(haystack, needle) // ?usize
95+ strings.contains(haystack, needle) // bool
96+ strings.containsChar(haystack, char) // bool
97+ strings.indexOfChar(haystack, char) // ?u32
98+ strings.indexOfAny(str, comptime chars) // ?OptionalUsize (SIMD-accelerated)
99+
100+ // Comparison
101+ strings.eql(a, b) // bool
102+ strings.eqlComptime(str, comptime literal) // bool — optimized
103+ strings.eqlCaseInsensitiveASCII(a, b, comptime true) // 3rd arg = check_len
104+
105+ // Prefix/Suffix
106+ strings.startsWith(str, prefix) // bool
107+ strings.endsWith(str, suffix) // bool
108+ strings.hasPrefixComptime(str, comptime prefix) // bool — optimized
109+ strings.hasSuffixComptime(str, comptime suffix) // bool — optimized
110+
111+ // Trimming
112+ strings.trim(str, comptime chars) // strip from both ends
113+ strings.trimSpaces(str) // strip whitespace
114+
115+ // Encoding conversions
116+ strings.toUTF8Alloc(allocator, utf16) // ![]u8
117+ strings.toUTF16Alloc(allocator, utf8) // !?[]u16
118+ strings.toUTF8FromLatin1(allocator, latin1) // !?Managed(u8)
119+ strings.firstNonASCII(slice) // ?u32
120+ ```
121+
122+ Bun handles UTF-8, Latin-1, and UTF-16/WTF-16 because JSC uses Latin-1 and UTF-16 internally. Latin-1 is NOT UTF-8 — bytes 128-255 are single chars in Latin-1 but invalid UTF-8.
123+
124+ ### ` bun.String ` (` src/string.zig ` )
125+
126+ Bridges Zig and JavaScriptCore. Prefer over ` ZigString ` in new code.
127+
128+ ``` zig
129+ const s = bun.String.cloneUTF8(utf8_slice); // copies into WTFStringImpl
130+ const s = bun.String.borrowUTF8(utf8_slice); // no copy, caller keeps alive
131+ const utf8 = s.toUTF8(allocator); // ZigString.Slice
132+ defer utf8.deinit();
133+ const js_value = s.toJS(globalObject);
134+
135+ // Create a JS string value directly from UTF-8 bytes:
136+ const js_str = try bun.String.createUTF8ForJS(globalObject, utf8_slice);
137+ ```
138+
139+ ## ` bun.path ` — Path Manipulation (` src/resolver/resolve_path.zig ` )
140+
141+ Use instead of ` std.fs.path ` . Platform param: ` .auto ` (current platform), ` .posix ` , ` .windows ` , ` .loose ` (both separators).
142+
143+ ``` zig
144+ // Join paths — uses threadlocal buffer, result must be copied if it needs to persist
145+ bun.path.join(&.{ dir, filename }, .auto)
146+ bun.path.joinZ(&.{ dir, filename }, .auto) // null-terminated
147+
148+ // Join into a caller-provided buffer
149+ bun.path.joinStringBuf(&buf, &.{ a, b }, .auto)
150+ bun.path.joinStringBufZ(&buf, &.{ a, b }, .auto) // null-terminated
151+
152+ // Resolve against an absolute base (like Node.js path.resolve)
153+ bun.path.joinAbsString(cwd, &.{ relative_path }, .auto)
154+ bun.path.joinAbsStringBufZ(cwd, &buf, &.{ relative_path }, .auto)
155+
156+ // Path components
157+ bun.path.dirname(path, .auto)
158+ bun.path.basename(path)
159+
160+ // Relative path between two absolute paths
161+ bun.path.relative(from, to)
162+ bun.path.relativeAlloc(allocator, from, to)
163+
164+ // Normalize (resolve `.` and `..`)
165+ bun.path.normalizeBuf(path, &buf, .auto)
166+
167+ // Null-terminate a path into a buffer
168+ bun.path.z(path, &buf) // returns [:0]const u8
169+ ```
170+
171+ Use ` bun.PathBuffer ` for path buffers: ` var buf: bun.PathBuffer = undefined; `
172+
173+ For pooled path buffers (avoids 64KB stack allocations on Windows):
174+
175+ ``` zig
176+ const buf = bun.path_buffer_pool.get();
177+ defer bun.path_buffer_pool.put(buf);
178+ ```
179+
180+ ## URL Parsing
181+
182+ Prefer ` bun.jsc.URL ` (WHATWG-compliant, backed by WebKit C++) over ` bun.URL.parse ` (internal, doesn't properly handle errors or invalid URLs).
183+
184+ ``` zig
185+ // Parse a URL string (returns null if invalid)
186+ const url = bun.jsc.URL.fromUTF8(href_string) orelse return error.InvalidURL;
187+ defer url.deinit();
188+
189+ url.protocol() // bun.String
190+ url.pathname() // bun.String
191+ url.search() // bun.String
192+ url.hash() // bun.String (includes leading '#')
193+ url.port() // u32 (maxInt(u32) if not set, otherwise u16 range)
194+
195+ // NOTE: host/hostname are SWAPPED vs JS:
196+ url.host() // hostname WITHOUT port (opposite of JS!)
197+ url.hostname() // hostname WITH port (opposite of JS!)
198+
199+ // Normalize a URL string (percent-encode, punycode, etc.)
200+ const normalized = bun.jsc.URL.hrefFromString(bun.String.borrowUTF8(input));
201+ if (normalized.tag == .Dead) return error.InvalidURL;
202+ defer normalized.deref();
203+
204+ // Join base + relative URLs
205+ const joined = bun.jsc.URL.join(base_str, relative_str);
206+ defer joined.deref();
207+
208+ // Convert between file paths and file:// URLs
209+ const file_url = bun.jsc.URL.fileURLFromString(path_str); // path → file://
210+ const file_path = bun.jsc.URL.pathFromFileURL(url_str); // file:// → path
211+ ```
212+
213+ ## MIME Types (` src/http/MimeType.zig ` )
214+
215+ ``` zig
216+ const MimeType = bun.http.MimeType;
217+
218+ // Look up by file extension (without leading dot)
219+ const mime = MimeType.byExtension("html"); // MimeType{ .value = "text/html", .category = .html }
220+ const mime = MimeType.byExtensionNoDefault("xyz"); // ?MimeType (null if unknown)
221+
222+ // Category checks
223+ mime.category // .javascript, .css, .html, .json, .image, .text, .wasm, .font, .video, .audio, ...
224+ mime.category.isCode()
225+ ```
226+
227+ Common constants: ` MimeType.javascript ` , ` MimeType.json ` , ` MimeType.html ` , ` MimeType.css ` , ` MimeType.text ` , ` MimeType.wasm ` , ` MimeType.ico ` , ` MimeType.other ` .
228+
229+ ## Memory & Allocators
230+
231+ ** Use ` bun.default_allocator ` for almost everything.** It's backed by mimalloc.
232+
233+ ` bun.handleOom(expr) ` converts ` error.OutOfMemory ` into a crash without swallowing other errors:
234+
235+ ``` zig
236+ const buf = bun.handleOom(allocator.alloc(u8, size)); // correct
237+ // NOT: allocator.alloc(u8, size) catch bun.outOfMemory() — could swallow non-OOM errors
238+ ```
239+
240+ ## Environment Variables (` src/env_var.zig ` )
241+
242+ Type-safe, cached environment variable accessors via ` bun.env_var ` :
243+
244+ ``` zig
245+ bun.env_var.HOME.get() // ?[]const u8
246+ bun.env_var.CI.get() // ?bool
247+ bun.env_var.BUN_CONFIG_DNS_TIME_TO_LIVE_SECONDS.get() // u64 (has default: 30)
248+ ```
249+
250+ ## Logging (` src/output.zig ` )
251+
252+ ``` zig
253+ const log = bun.Output.scoped(.MY_FEATURE, .visible); // .hidden = opt-in via BUN_DEBUG_MY_FEATURE=1
254+ log("processing {d} items", .{count});
255+
256+ // Color output (convenience wrappers auto-detect TTY):
257+ bun.Output.pretty("<green>success<r>: {s}\n", .{msg});
258+ bun.Output.prettyErrorln("<red>error<r>: {s}", .{msg});
259+ ```
260+
261+ ## Spawning Subprocesses (` src/bun.js/api/bun/process.zig ` )
262+
263+ Use ` bun.spawnSync ` instead of ` std.process.Child ` :
264+
265+ ``` zig
266+ switch (bun.spawnSync(&.{
267+ .argv = argv,
268+ .envp = null, // inherit parent env
269+ .cwd = cwd,
270+ .stdout = .buffer, // capture
271+ .stderr = .inherit, // pass through
272+ .stdin = .ignore,
273+
274+ .windows = if (bun.Environment.isWindows) .{
275+ .loop = bun.jsc.EventLoopHandle.init(bun.jsc.MiniEventLoop.initGlobal(env, null)),
276+ },
277+ }) catch return) {
278+ .err => |err| { /* bun.sys.Error */ },
279+ .result => |result| {
280+ defer result.deinit();
281+ const stdout = result.stdout.items;
282+ if (result.status.isOK()) { ... }
283+ },
284+ }
285+ ```
286+
287+ Options: ` argv: []const []const u8 ` , ` envp: ?[*:null]?[*:0]const u8 ` (null = inherit), ` argv0: ?[*:0]const u8 ` . Stdio: ` .inherit ` , ` .ignore ` , ` .buffer ` .
288+
289+ ## Common Patterns
290+
291+ ``` zig
292+ // Read a file
293+ const contents = switch (bun.sys.File.readFrom(bun.FD.cwd(), path, allocator)) {
294+ .result => |bytes| bytes,
295+ .err => |err| { globalObject.throwValue(err.toSystemError().toErrorInstance(globalObject)); return .zero; },
296+ };
297+
298+ // Create directories recursively
299+ bun.makePath(dir.stdDir(), sub_path) catch |err| { ... };
300+
301+ // Hashing
302+ bun.hash(bytes) // u64 — wyhash
303+ bun.hash32(bytes) // u32
304+ ```
0 commit comments