v4.0.0
Byline 4.0 is a boundary-hardening major release. It makes several implicit contracts explicit and enforced: server-only lifecycle code leaves portable collection schemas, database adapters must provide the full transaction and audit surface, request-scoped reads bind to one stable authority, and route configuration is resolved and validated up front. Existing documents and uploads do not need to be rewritten — 4.0.0 adds no Byline-owned database or search-index schema migration over 3.21.0 — but application wiring changes are required.
📖 Migration guide: Upgrading from 3.21 to 4.0 (
docs/01-getting-started/03-upgrading-to-v4.md) walks through package alignment, route configuration, moving server-only hooks into the registry, the client-bundle boundary plugin, generated types, request contexts, custom-adapter contracts, and the validation gates — with before/after code for each step.
Highlights
-
@byline/core/@byline/host-tanstack-start— a new host-agnosticServerConfig.hooksregistry lets server-only lifecycle and upload hooks leave the portable collection schema. Previously a schema attached hooks via a dynamicimport()(or a framework-specificcreateServerOnlyFnwrapper) to keep their server-only import graph — caches, search clients, Node built-ins, secrets — out of the browser bundle. In 4.0 hooks register in aserver-hooks.tsmodule imported only from the server bootstrap and passed toinitBylineCore({ hooks }), so the client graph can never reach them by construction, on any host framework. Thebyline/collection directory is now portable across TanStack Start, Next.js, or a plain Node host. Collection hooks are keyed by collection path; upload hooks by a canonical, index-free schema field path (publications.files.filesGroup.publicationFile, with a block-type segment after a blocks field), so deeply nested upload fields are first-class. -
@byline/host-tanstack-start— a new Vite plugin,bylineClientHookBoundary(exported from@byline/host-tanstack-start/vite), fails a production build if any server lifecycle module underbyline/collectionsis reachable from the emitted client bundle. This converts a silent bundling leak into a hard build failure and complements source-graph tests — a production optimizer can strip an accidental import that a dev server would still evaluate. -
monorepo — read authorization was hardened across the board:
beforeReadrow-scoping predicates are compiled in strict security mode and fail closed; version history is authorized per immutable version; admin tree reads honor row scoping (with a system-client bypass for tooling); and authorized search reports the authorized page count without leaking provider-wide totals or facets. These strengthen the guarantees available to multi-tenant and owner-scoped deployments.
Bug Fixes
-
@byline/core— scopedbeforeReadhook contexts could bypass theafterReadrecursion guard. Per-read recursion state (and the rich-text reader cache) is now canonicalized through one logical-root resolver, so a hook that threads its scopedreadContextinto a nested read of an in-flight document is correctly caught withERR_READ_RECURSIONinstead of re-entering and observing unredacted fields. -
@byline/host-tanstack-start— request-context factories minted a freshrequestIdper call, so any two reads sharing aReadContext(the admin tree view, hook-threaded nested reads) bound different request authorities and threwReadContext cannot be reused across request authorities. The admin, public, and viewer client factories now resolve one stableRequestContextper HTTP request, which also caps session verification and refresh-token rotation at one per request. The client-bundle hook guard's module matcher was also broadened to catch deeply nestedhooks.ts/*-hooks.tsmodules. -
@byline/cli— the CLI planned^3.21.0and rejected^4.0.0from hardcoded constants that release automation never synced with the package version. Compatibility is now derived from the CLI's own package version (deriveBylineReleasePolicy), so the built 4.x CLI installs and accepts 4.x automatically and this class of drift cannot recur. Separately, the "generated route predecessor" fixture matched no shipped template, which could block a customized 3.21routes.tsfrom migrating; it now uses the exact 3.21 release source and rewrites recognized generated files to the resolved v4 form.
Chores
- monorepo — added repository workflow skills and the v4 migration guide; removed sample document fixtures from the webapp.
Migrations
- monorepo — move server-only collection and upload hooks out of schemas into a
ServerConfig.hooksregistry (byline/collections/server-hooks.ts, imported only from the server bootstrap), and installbylineClientHookBoundary()in the app'svite.config.ts. Regenerate collection types (pnpm byline:generate) after the schema edits. The app-owned typed client boundary file is renamedbyline/client.server.ts→byline/clients.server.ts(plural). The v4 CLI recognizes generated 3.21 predecessors and can install missing canonical files, but treats bespoke application files as an audit, not an automatic rewrite.
Breaking Changes
-
@byline/core/@byline/db-postgres— breaking: a customIDbAdaptermust now implement the complete accountability contract —withTransaction,commands.audit/queries.audit, a transaction-scopedgetDocumentSystemFieldsForUpdate, the updated tree-mutation return values and collection identifiers, andpromoteChildrenAndRemoveFromTree. These are required capabilities, not optional negotiation; the boot validator fails fast when they are absent. Applications on@byline/db-postgresget the new behavior by updating the package. -
@byline/core— breaking:RoutesConfigis now the resolved three-path shape (admin,api,signIn). Configure routes viaresolveRoutes({ admin, api, signIn })and readroutes.signIn; paths may be multi-segment but must not overlap, and unsafe / protocol-relative / encoded / query- or fragment-bearing values are rejected at boot. LegacysignInPathroute-factory input andSignInForm.callbackUrlremain as deprecated compatibility paths; sign-in redirects are constrained to safe root-relative destinations inside the configured admin tree. -
@byline/core— breaking: documentdeletereturns a structured committed outcome. The database delete, audit rows, and tree child-promotion commit atomically; post-commit storage or hook failures are reported as a committed result carrying side-effect failures rather than throwing as if the delete rolled back. Callers that relied on the old throw-on-post-commit-failure behavior must read the result. -
@byline/client/@byline/host-tanstack-start— breaking: aRequestContextfactory must be request-stable — every resolution within one logical request returns the same context instance andrequestId, or two reads sharing aReadContextwill throw. The host's own factories satisfy this automatically; custom hosts and directcreateBylineClientfactories must memoize per request. The publicreadContext.beforeReadCacheproperty is deprecated and ignored for security. -
@byline/cli— breaking: the supported dependency floor is now 4.x. Use the 4.x CLI only with 4.x@byline/*packages; do not mix Byline majors in one application.
All other @byline/* packages bumped to 4.0.0 in lockstep with no behavioural changes this cycle.