Bun Scripts: Glossary & Quick Reference

Lessons 0001–0009 · 1 · 2 · 3 · 4 · 5 · 6 · 7 · 8 · 9 · Shared by the Bun and D1 tracks · Print me

The canonical language for this workspace. Promote a term here only once it is understood; prefer these words everywhere.

Commands cheat sheet

To do thisRun this
Run a TypeScript filebun run script.ts (or bun script.ts)
Run a file, restart on changebun --watch run script.ts
Make a script directly executable
#!/usr/bin/env bun   // first line
chmod +x script.ts   // then
/some/script.ts
Read script argumentsBun.argv.slice(2) — index 0 is the bun binary, 1 the script path
Exit nonzero on failureprocess.exit(1) or set process.exitCode = 1
Print to stderrconsole.error("...")
Copy a file to terminalawait Bun.write(Bun.stdout, Bun.file("path"))
Read whole stdin into a stringawait Bun.stdin.text()
Write text to a fileawait Bun.write("out.txt", data)
Read a file's contentsawait Bun.file("in.txt").text()
Parse JSON from a fileawait Bun.file("data.json").json()
Run a command synchronouslyBun.spawnSync(["cmd", "arg1"])
Capture + abort-on-failurethe sh() helper (see Lesson 0003)
Quick bash-like pipeline$\`ls | wc -l\`.text() (Bun Shell, Lesson 0004)
Read command output as JSON$\`echo '{"a":1}'\`.json()
Fetch a URLawait fetch(url) then await res.json()
Load secretsauto-loaded .env → process.env.TOKEN
Open a SQLite database fileimport { Database } from "bun:sqlite" then new Database("x.db")
Run DDL (create table)db.exec("CREATE TABLE IF NOT EXISTS ...")
Insert/upsert a rowdb.query("INSERT OR REPLACE ... VALUES (?,?,?)").run(a,b,c)
Get one typed rowdb.query<T>(sql).get(...params) — returns T | null
Atomic multi-writedb.transaction(fn) — returns a callable wrapper
Create a hosted D1 databasewrangler d1 create <name> — save the printed binding block
Add the D1 binding to a Workerthe d1_databases block in wrangler.jsonc → env.DB in code
Apply a schema (dev / prod)wrangler d1 execute <db> --local --file=schema.sql (or --remote)
Probe a D1 databasewrangler d1 execute <db> --command="SELECT …" + --local/--remote
List D1 databaseswrangler d1 list
Query D1 from a Workerawait env.DB.prepare(sql).bind(v).run() / .all() / .first()
Call a D1-backed API from Bunfetch(url, { method, headers, body: JSON.stringify(data) }) with a typed response cast and res.ok guard
Store a Worker secretwrangler secret put NOTE_KEY (prod) or a .dev.vars file (local — the Worker’s twin of .env)
Send an API key from Bunheaders: { "x-api-key": process.env.NOTE_KEY ?? "" }, kept out of git via .env
Add a cron schedule to a Worker"triggers": { "crons": ["*/5 * * * *"] } in wrangler.jsonc
Simulate a scheduled run (local)curl "localhost:8787/cdn-cgi/handler/scheduled?format=json&cron=*+*+*+*+*"
Watch a deployed schedule firewrangler tail <worker> — invocation shows cron and scheduledTime

Terms

Bun runtime: A single executable that is also a bundler, test runner and package manager. Executes JavaScript and TypeScript directly, with no separate compile step. Avoid: "the Bun compiler", "transpiling to JS first"

Top-level await: Using await at the top level of a script module, outside any async function. Bun supports it in files run directly. Avoid: IIFE-wrapping for await

Shebang: A first line #!/usr/bin/env bun that tells the OS which interpreter to run the file with. Combined with chmod +x, it lets you execute ./script.ts directly. Avoid: "shabang", "hashbang" (use this workspace’s term)

Bun.argv: An array of command-line tokens: argv[0] is the bun binary path, argv[1] the script path, and argv[2] onward the arguments you passed. An exact alias of process.argv. Avoid: $1, ${1:-default} positionals

Exit code: The process return value checked by callers and schedulers; 0 means success, nonzero means failure. process.exit(1) stops immediately; process.exitCode = 1 lets pending work finish. Avoid: relying on $? to notice failures

stderr: The error/log output stream, separate from stdout so data and noise don't mix. Write errors from console.error(); keep stdout for actual output. Avoid: logging errors to stdout

BunFile: A lazy, Blob-compatible handle to a file created by Bun.file(path). Content is not read until you call .text(), .json(), .bytes(), or .stream(). Has .size and .exists(). Avoid: treating BunFile as a string

Bun.write(dest, data): Multi-tool file writer. Destination can be a path, BunFile, or Bun.stdout; data can be a string, Blob, Response, or Uint8Array. Returns Promise<number> — bytes written. Uses the fastest syscall for the current OS. Avoid: Bun.write(Bun.stdout, Bun.stdin) on pipes (fails — read first with Bun.stdin.text())

Bun.stdin / Bun.stdout: Standard input and output exposed as BunFile objects. Use await Bun.stdin.text() to read piped input; use await Bun.write(Bun.stdout, data) to write to the terminal. Avoid: reading Bun.stdin as a stream for simple scripts (use .text())

Bun.spawnSync(cmd): Blocking subprocess execution. Takes an array of strings (no shell parsing). Returns stdout (Buffer), stderr (Buffer or undefined), exitCode, and success. The go-to for daily scripts. Avoid: passing a single command string with quotes

Bun.spawn(cmd): Async subprocess execution. Returns a Subprocess with stdout as a ReadableStream and an .exited promise. Use for parallel long-running jobs; for short synchronous tasks prefer spawnSync. Avoid: using spawn when spawnSync would do (adds unnecessary complexity)

Exit-code propagation: Passing a child process's exit code through to your script's own exit code via process.exit(r.exitCode ?? 1), so callers (cron, CI, bash) see the real failure reason. The Bun twin of set -e. Avoid: returning undefined on failure, catching and swallowing the real code

Bun Shell ($): Bun's cross-platform bash-like shell, imported with import { $ } from "bun". Runs in-process (never invokes /bin/bash), supports pipes, redirection, globs, and cwd/env, and escapes interpolated strings to block injection. Throws ShellError on nonzero exit unless .nothrow() is used. Avoid: $\`bash -c "$input"\` — handing control to a real shell disables the injection protection

res.ok: A boolean on a Response that is true only when the HTTP status is 2xx. Always check it after fetch — a 404/500 still resolves the promise. Avoid: assuming fetch succeeded just because it didn't reject

.env auto-load: Bun automatically reads a .env file in the project directory into process.env (=== Bun.env). No dotenv dependency or config call needed. Keep real .env out of git. Avoid: committing .env; hardcoding secrets in source

bun:sqlite: Bun's embedded, synchronous SQLite module. A full SQL database in a single file — no server, no native driver, no async. Open with new Database("file.db"); use :memory: for throwaway in-memory data. Uses SQLite's C library compiled directly into the Bun binary. Avoid: "bun's built-in database", "lite database"

Prepared statement: A compiled SQL query that you run many times with different parameters. Created by db.query("..."). Has .run(params) (execute, no rows back — returns { changes, lastInsertRowid }), .get(params) (one typed row or null), and .all(params) (array of rows). Parameters ($name or ?) are always bound as data, preventing SQL injection. Avoid: concatenating user input into SQL strings

db.transaction(fn): Wraps a function so all SQL inside executes atomically — either every write applies or none do. Returns a callable that can be invoked repeatedly; each call is its own atomic unit. Essential for multi-row updates (e.g. batching API responses into a table). Avoid: writing several rows outside a transaction and hoping they're consistent

D1: Cloudflare’s serverless SQLite. The same SQL dialect as bun:sqlite, but stored and executed by Cloudflare over the network instead of in a file you open. Query it with wrangler d1 execute from the terminal or through a Worker binding. Created with wrangler d1 create <name>. Avoid: "D1 database is a file you download", saying the SQL differs from bun:sqlite

D1 binding (env.DB): The handle your Worker code uses to reach D1. Declared in d1_databases inside wrangler.jsonc with binding, database_name, and database_id; the binding name becomes a property of env in the fetch handler. Wired automatically to the local store in dev and the hosted DB in prod. Avoid: hardcoding connection strings; sharing database_id in logs

--local / --remote: The two targets for D1 wrangler commands. --local executes against a throwaway SQLite file under .wrangler/state/ (free, instant, for dev); --remote executes against the live hosted database. Same --file/--command input either way, which makes schema rollout a habit rather than an operation. Avoid: applying schema directly --remote without rehearsing --local

prepare().bind() (D1 statement chain): The Worker-side query path that mirrors bun:sqlite’s prepared statements. env.DB.prepare(sql) compiles; .bind(v) feeds ? placeholders (or an object for $name/:name/@name); then .run() writes (no rows back — returns meta.changes, meta.last_row_id), .all() returns { results }, and .first() is the async twin of .get() (one row or null). Everything is async because D1 is a network hop. Avoid: building SQL strings with values — always bind() them

Cron Trigger: A cron expression attached to a Worker in wrangler.jsonc (triggers.crons). Five fields, Quartz-style, evaluated in UTC; weekday numbering is 1 = Sunday (use MON… names). Free plan: 5 per account, 10 ms CPU per run; Paid: 250, 30 s/15 min. Deploys replace all schedules (empty array removes them); changes take up to 15 min to propagate. A Worker with no scheduled() handler drops these events. Avoid: assuming a schedule applies instantly, or porting 0 = Sunday cron habits

scheduled(controller, env, ctx): The handler Cloudflare invokes when a Cron Trigger fires. controller.cron is the exact expression string that fired (branch on it for multiple schedules); controller.scheduledTime is the planned fire time in ms since epoch (new Date(...)). The runtime awaits the returned promise (up to 15 min wall time). Test locally with /cdn-cgi/handler/scheduled under wrangler dev. Avoid: doing the work in fetch() and wondering why the schedule is silent