The canonical language for this workspace. Promote a term here only once it is understood; prefer these words everywhere.
| To do this | Run this |
|---|---|
| Run a TypeScript file | bun run script.ts (or bun script.ts) |
| Run a file, restart on change | bun --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 arguments | Bun.argv.slice(2) — index 0 is the bun binary, 1 the script path |
| Exit nonzero on failure | process.exit(1) or set process.exitCode = 1 |
| Print to stderr | console.error("...") |
| Copy a file to terminal | await Bun.write(Bun.stdout, Bun.file("path")) |
| Read whole stdin into a string | await Bun.stdin.text() |
| Write text to a file | await Bun.write("out.txt", data) |
| Read a file's contents | await Bun.file("in.txt").text() |
| Parse JSON from a file | await Bun.file("data.json").json() |
| Run a command synchronously | Bun.spawnSync(["cmd", "arg1"]) |
| Capture + abort-on-failure | the 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 URL | await fetch(url) then await res.json() |
| Load secrets | auto-loaded .env → process.env.TOKEN |
| Open a SQLite database file | import { Database } from "bun:sqlite" then new Database("x.db") |
| Run DDL (create table) | db.exec("CREATE TABLE IF NOT EXISTS ...") |
| Insert/upsert a row | db.query("INSERT OR REPLACE ... VALUES (?,?,?)").run(a,b,c) |
| Get one typed row | db.query<T>(sql).get(...params) — returns T | null |
| Atomic multi-write | db.transaction(fn) — returns a callable wrapper |
| Create a hosted D1 database | wrangler d1 create <name> — save the printed binding block |
| Add the D1 binding to a Worker | the 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 database | wrangler d1 execute <db> --command="SELECT …" + --local/--remote |
| List D1 databases | wrangler d1 list |
| Query D1 from a Worker | await env.DB.prepare(sql).bind(v).run() / .all() / .first() |
| Call a D1-backed API from Bun | fetch(url, { method, headers, body: JSON.stringify(data) }) with a typed response cast and res.ok guard |
| Store a Worker secret | wrangler secret put NOTE_KEY (prod) or a .dev.vars file (local — the Worker’s twin of .env) |
| Send an API key from Bun | headers: { "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 fire | wrangler tail <worker> — invocation shows cron and scheduledTime |
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