Changelog
What changed in each release of typebase-io and typebase-io-cli.
Last updated on
typebase-io and typebase-io-cli are released together and always share a version number, so upgrade them as a pair:
npm install typebase-io@latestnpm install -D typebase-io-cli@latestRun npx typebase-io-cli codegen after every upgrade. _generated/ is written by the version that generated it, so a folder
left over from an older release can describe a context your server no longer provides.
0.1.23
2026-10-07.
Added
- The CLI collects anonymous usage data: the command you ran, the options you passed, and your CLI, Node and OS versions.
Option values are sent only when they come from a fixed list, such as
--provider, so URLs, paths and secrets never leave your machine. The first command in a project prints a notice once. Opt out with"telemetry": { "enabled": false }intypebase.json, or withTYPEBASE_TELEMETRY_DISABLED=1orDO_NOT_TRACK=1.
0.1.22
2026-10-05.
Changed
q.defineRelationsrequires every table your schema exports. A table added toschema.tsand left out ofrelations.tsis now a type error on the call that names it, such asProperty 'events' is missing in type '{ todos: {}; }', instead of passing silently. Register a table with no relations as{}.
Fixed
auth generatewrites arelations.tsthat type-checks when yourq.defineRelationscallback takes no parameter. It wrote the auth relations asr.one.users(...)either way, so() => ({ todos: {} })ended inCannot find name 'r'. A callback without a parameter now getsradded, and one that names it differently keeps its name, which the auth relations are written through.
0.1.21
2026-10-02.
Added
startlogs every request the local server answers, on one line once the response is sent: the method, the path, the status, and how long it took. Until now a local run printed nothing for a request that worked and only the bare error for one that didn't, so finding out what your frontend actually called meant addingconsole.logto your actions.- A request to an action names the action and the input it was called with, as the client sent it, so a request your schema rejected still shows what was rejected.
- Auth and local storage requests are logged too, without their body, so the password a sign-in sends never reaches your terminal. Query strings are left out of every path, since links such as email verification carry their token there.
- A 4xx is logged as a warning and a 5xx as an error, and the error an action threw is printed under its request line with its stack. An auth or
storage handler that throws gets a line ending in
failed. - Only
startbuilds a server that logs.generate-serveranddeploybuild the same server as before.
Fixed
- An
auth.tsthat writes better-auth hooks withcreateAuthMiddlewareand rejects requests withAuthError, both fromtypebase-io/server, builds a server that starts. The generated auth file dropped everytypebase-ioimport while keeping the code that used them, so the server crashed at boot withcreateAuthMiddleware is not defined, andAuthErrorwould have failed the first time a hook threw it. Both are now imported frombetter-auth/api, where they come from, and keep any local name you gave them.
Changed
typebase-io/servernow exports only the API you write against.Action,filterActions,createPublisher,createStorage, and theActionBuilderandGetDBBuildertypes moved totypebase-io/internal, andtypebase-io/server/local-storageis nowtypebase-io/internal/local-storage. They exist for the code the CLI generates and were never meant to be imported by hand, so your editor's autocomplete ontypebase-io/serverno longer offers them. Code you write is unaffected unless it imported one of them directly.- Run
codegenafter upgrading. A_generated/server.tswritten by an older CLI still importsfilterActionsand the builder types fromtypebase-io/server, and stops type-checking until it is regenerated.deploy,start, andgenerate-serverregenerate it before every build, so a server they build is never affected.
0.1.20
2026-09-30.
Fixed
- The
dbpublisher delivers events with the types they were published with. ADatein a payload used to reach subscribers, and your client, as an ISO string, because events are stored in ajsonbcolumn and JSON has no date type;bigint,Set,Map, andURLvalues were flattened the same way. Payloads are now stored with the same serializer oRPC uses to send them to the client, socreatedAt: z.date()arrives as aDate. Events stored before this release are still delivered exactly as they were, so a client resuming across the upgrade misses nothing.
Changed
- Publishing a payload that holds a
BloborFilenow fails straight away, naming the event, instead of storing an empty object in its place. The events table only holds JSON: upload the file to storage and publish its key.
0.1.19
2026-09-28.
Fixed
deployto Vercel serves your server again. Since 0.1.17 split the generated entry point intosrc/index.jsandsrc/server.js, every Vercel deploy failed at boot withInvalid export found in module "/var/task/src/server.js". The default export must be a function or server.Vercel's Hono preset only accepts an entry file that importshonoby name, and checkssrc/indexbeforesrc/server;src/index.jsno longer imported it, so Vercel skipped it and loadedsrc/server.js, which has no default export. The Hono entry file now importshonoitself, so Vercel pickssrc/index.js.package.json'smaindidn't help, because Vercel only falls back to it when no file matches.- The generated entry point imports
"dotenv/config"with double quotes, like every other import in the generated server.
0.1.18
2026-09-28.
Added
-
defineStorage: declare a storage provider and named buckets intypebase/storage.ts, and every action gets a typedstorage. Until now, files meant leaving Typebase: picking a storage service, creating one bucket per environment by hand, copying credentials between dashboards, and wiring an SDK into every action that needed one.- The provider is
vercel(Blob),cloudflare(R2), orfilesystem, chosen independently of your server provider, so a Worker can keep its files in Vercel Blob and a Deno Deploy server can use either. - Each bucket is
publicorprivate.storage.bucket(name)only accepts a declared name,publicUrlexists only on public buckets andsignedUrlonly on private ones, so a misspelled bucket or a permanent link to a private file is a type error. Every bucket also hassignedUploadUrl, for browser uploads that never pass through your server, and files-sdk'supload,download,head,exists,delete,copy,move,list,listAll, andsearch. Per-bucketprefix,plugins, andhooksare passed to files-sdk unchanged. - Every bucket exists once per target, named
<project>-<bucket>-<target>. The project part is chosen on the first sync and frozen intypebase.json, so renaming your server project or switching server providers never points your code at a fresh set of empty buckets.
- The provider is
-
storage sync <target>creates every declared bucket missing for the target, adopts the ones that exist, warns about buckets you no longer declare, and writes their credentials to your project-root.env. It never deletes or empties a bucket, and it stops without changing anything when an existing bucket's access differs from the declaration. On Cloudflare, public buckets get their r2.dev domain and each target gets one R2-only token scoped to its buckets, widened in place as buckets are added. -
A local run keeps a
vercelorcloudflarestorage in local storage: the same buckets, on disk in the server cache, with public, signed, and signed upload URLs served and verified by the local server, so browser upload flows work locally.--dev-storageand--prod-storagerun against the real buckets instead, independently of the database flags. -
generate-server --local-storagebuilds a server that uses local storage and serves its files, at/storageor, for an embedded server, at--storage-path(server.storagePath). Without the flag, a generated server reads the same storage variables a deployed one does. -
init --with-storagescaffolds a storage file with a publicavatarsand a privatedocumentsbucket, plusgetAvatarUploadUrlandgetDocumentUrlactions that use them.
Changed
deployruns bucket sync for its target before it builds, then sets the storage credentials on your server provider next toDATABASE_URL, so a deploy never ships against a bucket that doesn't exist. Your own Vercel or Cloudflare login token is never put on the server.codegennow also has to be rerun whenstorage.tsis added or removed, the same aspublisher.ts. Editing an existing one doesn't.generate-serverseeds the storage credentials into a standalone server's.env, preferring their_DEVvalues, the same way it does forDATABASE_URL.
0.1.17
2026-09-07.
Added
-
generate-server --embeddedgenerates a server for your existing application to mount, rather than one that owns a process of its own. Until now the only way into an existing application was to open the generated entry point, copy the handler construction out of it, and delete the rest — a fork that went stale on the next build, and in watch mode on every keystroke. An embedded build writes what you mount and regenerates it with everything else.{ "server": { "embedded": true } }intypebase.jsonmakes it the default; omitting it, orfalse, keeps the standalone server you already run, which behaves exactly as before.- Every adapter exports the same three names —
typebaseHandler,router, andauthwhere the project has auth — so switching adapters doesn't touch your integration code. Only the mount's shape follows the adapter: a Fastify plugin, a Hono sub-application, a Connect-style Node middleware that callsnext()when the request wasn't Typebase's, and a fetch handler for Bun, Deno, and Cloudflare. The page carries one example each. - It emits loose source — no
package.json,tsconfig.json, or.env— because a nested package inside your application means a second copy ofdrizzle-orm,better-auth, orfastifyloaded in one process. Your application owns the dependencies, the environment, and the cross-origin policy; the first build reports what it needs: an install command for your package manager listing only what yourpackage.jsonlacks, warnings where your versions disagree with the generated ones, the environment keys the server reads, that CORS is now yours to configure, and the snippet that mounts it. Watch rebuilds stay quiet. - TypeScript output uses
.jsimport specifiers for its own relative imports, so it compiles under an ordinary applicationtsconfig.jsonundernodenextandbundleralike.--output esmand--output cjsare still available, but an embedded build ships no manifest, so its.jsfiles are interpreted using the host's"type"— match the format to the application you are mounting into. - An embedded Fastify build registers its catch-all content type parser inside the plugin, and Fastify scopes parsers to the plugin they belong to, so mounting Typebase leaves your other routes' body parsing alone — a webhook that verifies a signature over the raw body keeps working. A standalone Fastify server still registers it on the root instance, as it always has; it owns the process, so there is nothing else to affect.
- The default output directory is
_handlerrather than_server, so a fragment meant for a host application is never confused with a runnable server.--out-dirandserver.outDirstill override it.
- Every adapter exports the same three names —
-
--actions-pathand--auth-path, alsoserver.actionsPathandserver.authPath, move where an embedded server serves your actions and your auth. They are independent of each other, and default to/rpcand/api/auth, so a build that sets neither is unchanged. The auth path is written into the generated auth config as better-auth'sbasePathtoo, so the framework and the route can't disagree — and abasePathyou set in your ownauth.tswins, with a warning saying so. A custom actions path changes the URL your client points at. Both are embedded-only: passing either flag to a standalone build is an error, while a value inherited fromtypebase.jsonis ignored, so one configuration file still serves every command.
Changed
- Every generated server now splits its entry point in two:
src/server.tsbuilds the router, the RPC handler, the auth wiring, and the mount, andsrc/index.tsstarts it listening. A standalone build emits both and behaves exactly as before, including thepackage.jsonentry point and start script. An embedded build emits only the first, which is why an embedded server can't drift from the runnable one — the runnable one is built on top of it. - Both modes now write a
typebase-server.jsonmarker next to the generated source, recording the adapter, the mode, the CLI version, the required dependencies with their versions, and the environment keys the server reads. The safety check that refuses to replace a directory it doesn't recognize reads it, which is what makes an embedded output directory inside your own source tree safe to regenerate. Directories generated before this release are still recognized by their@typebase-io/serverpackage.json, so upgrading doesn't make your next build refuse to run. The same file is a machine-readable dependency list you can script an install from. generate-server --portand--embeddednow conflict, in either order, and--portis also rejected whenserver.embeddedselected an embedded server: nothing would listen on it. Aserver.portinherited fromtypebase.jsonis ignored instead.startalways builds a standalone server and ignoresserver.embeddedentirely.
0.1.16
2026-09-02.
Added
-
start: one command that runs your Typebase server on your own machine. It builds the server, installs its dependencies, brings your database in step with your schema, starts it, and then rebuilds, re-syncs and restarts on every change insidetypebase/. No directory change, no manual install, no separate database step. It replaces the four-stepgenerate-serversequence for local work;generate-serveris unchanged and remains the command for a server you want to open, read, or self-host.- The database is chosen explicitly and there is no fallback chain: no flag reads
DATABASE_URL_LOCAL,--dev-databasereadsDATABASE_URL_DEV,--prod-databasereadsDATABASE_URL, and--database-urltakes one directly. A project with a schema and an empty key stops immediately naming that key, rather than starting a server that dies seconds later on environment validation. A project with no schema skips the database step. - Push mode pushes and migrations mode applies pending migrations, both on every change under
typebase/db/. The destructive-change prompt is preserved, with--skip-schema-changes-confirmationto answer it in advance. Drift warns and continues rather than refusing, matchingdb <target> migrate, so the loop keeps working while you are still deciding what the migration should say. - The generated server is built into a server cache outside your project, so a local run never adds a file to
review, commit, or ignore, and
typebase/_server/is left untouched. It persists between runs, which is what keeps the loop fast, and prunes itself when the project that owns it is deleted. - The install and the database step are skipped on a rebuild when the content that feeds them has not changed, so editing an action never opens a database connection or pays for an install. Both checks live in memory, so restarting the command redoes everything.
- The output format is chosen by asking your Node whether it can execute TypeScript: Node 22.18 or newer runs it directly and skips transpiling.
--outputoverrides it. Other options:--port,--command,--install-command. TYPEBASE_APP_URL_LOCALis written to your project-root.envon every run, so a client readingTYPEBASE_APP_URL_LOCAL || TYPEBASE_APP_URL_DEV || TYPEBASE_APP_URLreaches a local run whenever one is going. Your database URLs are read and never written. An auth secret is generated and saved where a later deploy will find it.- A project with an
auth.tsgets the local URL written into the generated server's auth config asbaseURL, following--port, so better-auth can build callbacks and email links without theBase URL could not be determinedwarning. AbaseURLyou set intypebase/auth.tsyourself is left alone.
- The database is chosen explicitly and there is no fallback chain: no flag reads
-
Migrations: an opt-in alternative to pushing, where schema changes are recorded as SQL files you read, edit, commit and review, and reach a database only when applied. A project is in migrations mode when
typebase/db/migrations/exists.db migrations generatediffs your schema files against the last recorded migration and writes the difference. It is offline and contacts no database.--namenames it,--customgives you an empty file to write SQL into yourself, and--ignore-conflictsoverrides the forked-history check.db <target> migrateapplies pending migrations todev,prod, or, withdb local migrate --url, a Postgres you run yourself. Each migration runs exactly once per target.db migrations initadopts migrations on a project whose databases were built by push, recording a baseline and marking the databases that already have those tables as having applied it. It never provisions a target that has no database.init --with-migrationsstarts a new project in migrations mode, with the scaffolded schema recorded as its first migration.auth generaterecords a migration for the auth tables it adds.- Migrations ship with
generate-serveroutput and the deployed bundle, and the generated drizzle config points at them.
-
generate-serverfills the generated server's.envfrom your project-root.env, so a server you run locally starts with the values the CLI already collected instead of you copying them across. Only the keys the server validates are copied —DATABASE_URL,BETTER_AUTH_SECRET, and whatever you declared inenv.ts— andDATABASE_URLcomes fromDATABASE_URL_DEVwhen there is one, so a local server reaches the dev branch rather than production. Keys already in the file are never overwritten. Provider tokens are not copied, anddeployis unchanged: it syncs variables to the provider and never puts a.envin the bundle. -
generate-server --command "<command>"runs a command inside the generated server once it has been generated. With--watchit restarts that command on every rebuild, so what is running is always the latest build:npx typebase-io-cli generate-server --watch --command "pnpm start". Stopping the watcher stops the command, including the server a package script started rather than just the script itself. A build that fails leaves the running command alone, and a command that exits does not stop the watcher. -
--skip-confirmationondb <target> pushand--skip-schema-changes-confirmationondeployanswer the destructive-change prompt in advance, for pipelines with nobody at the keyboard. The warnings are still printed either way, so the log records what was dropped.
Changed
db <target> pushrefuses to run in migrations mode and names themigratecommand for that target. Projects that do not opt into migrations are unaffected.deployapplies pending migrations instead of pushing when a project is in migrations mode, and refuses to deploy when your schema files hold changes no migration records. Note that migrations are applied before the new server ships.db pullrefuses in migrations mode without--force, and offers to adopt migrations in a project that has none.- The drizzle config written into a generated server points
outat./src/db/migrationsrather than an unused./drizzledirectory. generate-serverwrites apnpm-workspace.yamlnext to the generatedpackage.jsonfor pnpm projects, making the generated server a workspace root in its own right. Without it, installing inside a generated server that sits under a pnpm workspace walked up to your workspace root, installed your own projects instead, and left the generated server with no dependencies at all — while still reporting success..yarnrc.ymlfor yarn berry andbunfig.tomlfor bun are written the same way; npm and yarn classic need no extra file.
0.1.15
2026-08-26.
Added
InferStreamEventfromtypebase-io/server: give it theRouterOutputsentry of a streaming action and it gives you the type of one event, so you no longer have to unwrap the async iterable by hand to type the state a stream feeds.- The CLI checks itself against the installed
typebase-iobefore running a command and warns when the two versions differ, since they are released together and a mismatched pair can generate code your server doesn't understand.npx typebase-io-cli --versionprints the CLI's own version.
Changed
- A stream declared without
.output()now reports the same type as one declared with it. Before, itsRouterOutputsentry was the generator type of the handler that happened to implement it —AsyncGenerator<Event, void, unknown>— so adding an output schema, or rewriting the handler as something other than a generator function, silently changed the action's public type. Both now describe the same event iterator, matching the value an oRPC client actually hands back. Nothing changes at runtime, but code that namedAsyncGeneratorexplicitly to type a stream needs updating;InferStreamEventis the replacement.
0.1.14
2026-08-24.
Added
- Streaming actions: swap
.handler()for.stream()and an action becomes an async generator that pushes events to the client over SSE for as long as it's listening..output()describes one event rather than the whole response, and the generator getssignalandlastEventIdon top of the usual handler context. The compiler enforces that a stream yields at least once, that events are yielded rather than returned, and that each one matches.output(). definePublisher: declare the events your actions publish intypebase/publisher.ts, and every action gets a typedpublisheron its context. A mutation publishes an event, a stream subscribed to it forwards the event to every client watching. Event names and payloads are checked at compile time, andpublish(name, payload, { tx })joins a transaction so a rollback publishes nothing.- The
dbpublisher provider keeps events in aneventstable in your own database, polls it on a loop shared by every subscriber on the instance, and tags each event with its row id, so a client that reconnects resumes from the last event it saw instead of missing whatever was published while it was away. The table is declared in your owndb/schema.tslike any other. withEventMetaandgetEventMetafromtypebase-io/server, for attaching anidorretryto an event and reading it back.init --with-db-publisher: scaffoldspublisher.ts, theeventstable, its relation, and example actions that publish and stream. Like--with-auth, it can't be combined with--skip-example.consumeStreamfromtypebase-io/client, for reading a stream with callbacks instead offor await. Create a client first, then pass the stream call:client.path.to.stream()forcreateRouterClient, orclient.path.to.stream.call()forcreateTanstackQueryClient. It starts immediately and returns an unsubscribe function, so UI code must start it from the component's mount lifecycle and unsubscribe during cleanup rather than calling it during render.typebase-io/client/pluginsre-exports every oRPC client plugin, soClientRetryPluginand the rest work without adding a dependency. Streams don't reconnect on their own, so this is what you reach for to make a dropped connection resume.
Changed
codegennow also has to be rerun whenpublisher.tsis added or removed, the same asauth.tsandenv.ts. Editing an existing one doesn't need it.
0.1.13
2026-08-19.
Added
db pull: read an existing PostgreSQL database and write it todb/schema.tsanddb/relations.ts, then regenerate the types. Column types, defaults, primary keys, unique constraints, indexes, foreign keys, enums and views all come across, every table gets registered in relations (Drizzle only writes the ones that have a foreign key), and the result is written in the shorthand a Typebase schema is normally written in. It asks before replacing existing files unless you pass--force.
0.1.12
2026-08-17.
Added
config: prints thetypebase.jsonJSON Schema with every option annotated with the value in effect and whether it came from a default. Read-only, and the one command that doesn't needtypebase-ioinstalled, so tooling can read a project's setup without reimplementing the schema or its defaults.initnow creates atypebase.jsonholding a$schemareference, so your editor autocompletes the config from the start. Values already in an existing file are kept.
0.1.11
2026-08-02.
Added
generate-server --watchkeeps the command running and rebuildstypebase/_server/whenever anything insidetypebase/changes. See Work locally.
0.1.10
2026-08-01.
Fixed
- Simplified the
envtype, so editors show the keys you declared instead of an expanded internal type.
0.1.9
2026-08-01.
Added
defineEnv: declare the variables your server needs intypebase/env.tsand read them from the handler context asenv, each one a plainstring. The generated server validates the whole schema before it serves a single request, so a missing variable is a startup error instead of anundefinedsurfacing deep inside a handler later.DATABASE_URLandBETTER_AUTH_SECRETare added for you.- The generated server carries its own env module, and
@t3-oss/env-coreis added to its dependencies only when the project needs one.
Changed
- Empty database and relation types are
neverrather than{}, so a project without a schema no longer offers adbyou can't use. - A project with an
auth.tsbut nodb/schema.tsis now refused instead of building a server whose auth could never work. - Removed
skipLoadEnv.
Fixed
- Types are generated before they are validated, so the first run type-checks against fresh types.
- Type errors inside
_generated/are no longer reported as yours.
0.1.8
2026-07-29.
Added
logs <dev|prod>: stream runtime logs from Vercel, Cloudflare Workers or Deno Deploy until you stop it.deploy <target> --logstails the server as soon as the deploy goes live.
Fixed
proxyToTypebasestripsx-forwarded-*headers from the proxied request, which some providers rejected.
0.1.7
2026-07-20.
Added
server.portis part of the config schema, so editors validate it intypebase.json.
Changed
generate-serverremoves the files an earlier run left behind instead of writing over them.
Fixed
init --with-authwrites the correct relations for the example.- Clearer failures instead of silent ones: an unreadable
auth.ts, code that cannot be parsed, and relations thatauth generatecannot update now stop with an explanation.
0.1.6
2026-07-03.
Added
RouterInputsandRouterOutputsin the generated server types, plusInferRouterInputsandInferRouterOutputsfromtypebase-io, so you can name an action's input or output type without redeclaring its shape. See Generated Code.
0.1.5
2026-07-03.
Fixed
auth generateadds the plugin import to the generated auth file when a plugin needs one.- A formatting error in generated files.
0.1.4
2026-07-01.
Changed
- The CLI got a test suite, and CI now runs it on every change.
0.1.3
2026-06-10.
Changed
env <target> addtakes--no-encryptedinstead of--encrypted, so values are encrypted unless you opt out.
Fixed
- Writing the project
.envkeeps the variables already in it. - Router generation handles nested action files correctly.
- Type validation ignores
_generated/. - A command run in a project without
typebase-ioinstalled says so, and failures exit with a non-zero code. defineAuthwarns when it cannot readtrustedOriginsinstead of carrying on with an empty list.
0.1.2
2026-06-08.
Added
- Every built-in better-auth plugin is re-exported through
typebase-io/server/auth-pluginsandtypebase-io/client/auth-plugins, so you don't have to depend onbetter-authdirectly. See Auth. init --skip-examplestill writes basedb/schema.tsanddb/relations.tsfiles.
Changed
deployregenerates types as part of every deploy.
0.1.1
2026-06-03.
Fixed
defineAuthinfers its options correctly, so plugin and provider config reaches the client types.- The example relations file generated with
--with-authregisters the auth tables.
0.1.0
2026-05-13.
Added
- The AI Skill: a single
SKILL.mdthat teaches a coding agent the file layout, CLI commands and conventions. generate-server --port, with a new default port.- Dev deploys save their URL to
.envasTYPEBASE_APP_URL_DEV, so the dev and prod URLs coexist. - The generated server records a
packageManagerfield and installs with the package manager your project uses.
Fixed
- Vercel: a placeholder is deployed on a first
devdeploy when there is no production deployment yet. - Neon: the CLI waits for a new dev branch to be ready, and no longer copies data into it.
- Deno Deploy: fixed the deploy flow and a terminal read error.
- Only actions end up on the router; helpers exported alongside them are ignored.