Docs / Changelog
Changelog
Release history for Hopak packages, with links to each upgrade guide.
Release notes for Hopak packages. Each section links to the relevant upgrade guide.
Current versions
| Package | Version | Notes |
|---|---|---|
@hopak/core | 1.0.1 | framework core |
@hopak/cli | 1.0.1 | CLI tool |
@hopak/auth | 1.0.1 | auth + OAuth |
@hopak/testing | 1.0.1 | in-process test server |
@hopak/common | 1.0.1 | shared primitives |
1.0.1 — fixes from a full end-to-end pass
Every 1.0 feature was exercised against a real scaffolded project on all three dialects. What that turned up:
- Plugin field types now actually store their values. A registered field produced a table column that Drizzle didn’t know about, so writes were silently dropped (or failed outright when the column was
NOT NULL).ctx.registerFieldnow takes{ storage, schema }— picktext/integer/real/boolean/timestamp/jsonand Hopak wires the column and DDL for all three dialects. model.tableName— the physical table name (post→posts) is now on the model, so raw SQL and migration DDL don’t have to guess it. The docs previously showedFROM post, which never matched a real table.hashPasswordin@hopak/auth— idempotent hashing. A model that hashes inbeforeCreateandcredentialsSignupwould both hash, and the double hash never matched at login.createTestServer({ plugins })— a project using plugin field types could not be booted by the test server at all.crud.*builds validation schemas lazily, so route modules can be imported before plugin setup runs.ctx.setHeaderreaches every response — static files, 404s, 405s, errors, and responses a handler builds itself.- Boot warns about unapplied migrations instead of letting the first request die on a missing table.
1.0.0 — plugins, model hooks, OpenAPI, realtime (breaking)
All five packages move to 1.0.0 as one coordinated release.
New surface — each behind one explicit gesture, invisible until you reach for it:
- Plugins —
hopak().use(plugin): custom field types (ctx.registerField+ aFieldBuildersubclass), plugin middleware,onBoothooks. Built-in fields register through the same registry. - Model lifecycle hooks —
model(name, fields, { hooks }):beforeCreate/afterCreate/beforeUpdate/afterUpdate/beforeDelete/afterDeletearound single-row writes; password hashing becomes one hook. - OpenAPI —
hopak openapiemits an OpenAPI 3.1 document from models + routes; CRUD operations come out fully typed, response schemas strip sensitive fields. - WebSockets —
export const WS = defineWebSocket({...})in a route file; upgrade passes throughbeforemiddleware, so auth gates sockets too. - SSE —
sse()route-handler helper withstream.send/stream.closed. rateLimit()— fixed-window in-process limiter withRetry-After.- Request body limit —
server.maxRequestBodyBytes(default 16 MiB →413). - Typed CRUD —
crud.read(post)is aRouteDefinition<Post>; newWithIncluded<Row, Relations>helper typesincluderesults;builder()is typed on dialect factories. - OAuth PKCE — cookie-bound code verifier; on by default for Google, opt-in for GitHub. GitHub private emails resolve via
/user/emails.
Hardening:
- Static files,
404s and405s now flow through global middleware — access logs and guards see every request. - Headers set via
ctx.setHeadersurvive onto error responses (X-Request-Id,Retry-After). - Conditional requests on static files:
If-None-Match/If-Modified-Since→304. - Concurrent SQLite transactions are queued instead of colliding on the shared connection.
?limit=abcon CRUD lists →400instead ofNaNreaching SQL.hopak migrate up --to <unknown-id>errors instead of silently applying everything.credentialsLoginverifies against a fallback hash when the user doesn’t exist — response timing no longer leaks account existence.
Breaking:
db.execute()removed from the publicDatabaseinterface (deprecated since 0.5).ctx.executeinside migrations stays.- OAuth callbacks (
githubCallback/googleCallback) now requirecallbackUrl— same parameter set for both providers. @hopak/testingdeclares@hopak/core/@hopak/commonas peer dependencies.
0.5.0 — db.sql, db.builder(), deprecated db.execute() (breaking)
- New primitive:
db.sql— tagged-template SQL for the ~5% of queries.model()doesn’t cover. Interpolations become driver-native placeholders (?/$N); values never reach the SQL text, so${attacker}is safe by construction. - Driver-native on every dialect: SQLite routes through
bun:sqlite.prepare().all(), Postgres throughpostgres.js sql.unsafe(), MySQL throughmysql2 pool.execute(). No Drizzle on the raw-SQL read path. db.raw()→db.builder(). Same return type (the dialect’s Drizzle client), clearer name. Breaking rename, mechanical search-and-replace.db.execute(sql, params?)is@deprecated— kept as a thin forwarder so existing migration files keep compiling. New code should usedb.sql. (Actually removed in 1.0.0.)MigrationContextgetsctx.sqlalongsidectx.execute(also deprecated).hopak migrate newscaffoldsctx.sqlby default.- Framework internals (
tracker.ts,introspect.ts) migrated off thedb.raw() as { all?, execute? }cast hack ontodb.sql.
0.4.9 — Prototype-pollution hygiene in deepMerge
deepMergein@hopak/commondrops__proto__,constructor, andprototypekeys from merged sources, so a JSON-parsed payload can no longer swap the result’s prototype chain. First regression tests for thecommonpackage.- Algorithm pinning on JWT verification in
@hopak/auth: the verifier no longer accepts a token signed with a different algorithm from the one configured — closes the algorithm-confusion attack. - Static-file handler re-uses a cached canonical root path (realpath) per handler instance; a symlink that resolves outside the public root still fails cleanly.
FileField.maxSize(n)now actually constrains the Valibot schema at validation time, not just at config-write time.
0.4.0 — Valibot, safer validation
- Validation runtime swapped from Zod to Valibot: ~10× smaller bundle, ~2–3× faster parse.
validate()andbuildModelSchema()keep the same API; model-driven validation is untouched.RouteSchemas.body | query | paramsare nowv.GenericSchemainstead ofz.ZodType.@hopak/coreno longer depends onzod.ZodFieldSchematype export renamed toFieldSchema.
0.3.0 — Migrations
- New
hopak migratesubcommands:init,new,up,down,status. hopak syncbecomes the dev-bootstrap path; it refuses to run onceapp/migrations/has files.hopak devskips boot-timedb.sync()when migrations exist — the runtime never alters the schema on its own.hopak syncnow prints a drift warning when models declare columns the live DB doesn’t have..index()on a field now actually createsCREATE INDEX IF NOT EXISTSduring sync (previously silently ignored).- New public API:
ctx.db.execute(sql, params?)for raw SQL.
0.2.0 — Scaffolded CRUD (breaking)
- CRUD endpoints move from runtime generation to CLI scaffolding via
hopak generate crud <model>. { crud: true }on models is removed; route files become real.tsyou can read and edit.- Dev HTTPS certs require
hopak generate cert— boot no longer invokesopensslbehind your back. @hopak/testingdropswithCrud: true; wire routes via the newcrud.*helpers or passrootDirfor end-to-end tests.- Removed unused model options:
owner,publicRead,auth,softDelete.
0.1.0 — Initial release
- Models, relations, query ergonomics, validation, serialization, typed errors.
- Runtime-generated CRUD via
{ crud: true }, runtime-generated dev certs forserver.https.enabled. - First pass at the CLI:
hopak new,hopak use,hopak sync,hopak check,hopak dev.