Skip to content

v4.0.0

Choose a tag to compare

@58bits 58bits released this 15 Jul 20:45

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-agnostic ServerConfig.hooks registry lets server-only lifecycle and upload hooks leave the portable collection schema. Previously a schema attached hooks via a dynamic import() (or a framework-specific createServerOnlyFn wrapper) 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 a server-hooks.ts module imported only from the server bootstrap and passed to initBylineCore({ hooks }), so the client graph can never reach them by construction, on any host framework. The byline/ 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 under byline/collections is 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: beforeRead row-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 — scoped beforeRead hook contexts could bypass the afterRead recursion 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 scoped readContext into a nested read of an in-flight document is correctly caught with ERR_READ_RECURSION instead of re-entering and observing unredacted fields.

  • @byline/host-tanstack-start — request-context factories minted a fresh requestId per call, so any two reads sharing a ReadContext (the admin tree view, hook-threaded nested reads) bound different request authorities and threw ReadContext cannot be reused across request authorities. The admin, public, and viewer client factories now resolve one stable RequestContext per 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 nested hooks.ts / *-hooks.ts modules.

  • @byline/cli — the CLI planned ^3.21.0 and rejected ^4.0.0 from 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.21 routes.ts from 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.hooks registry (byline/collections/server-hooks.ts, imported only from the server bootstrap), and install bylineClientHookBoundary() in the app's vite.config.ts. Regenerate collection types (pnpm byline:generate) after the schema edits. The app-owned typed client boundary file is renamed byline/client.server.tsbyline/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-postgresbreaking: a custom IDbAdapter must now implement the complete accountability contract — withTransaction, commands.audit / queries.audit, a transaction-scoped getDocumentSystemFieldsForUpdate, the updated tree-mutation return values and collection identifiers, and promoteChildrenAndRemoveFromTree. These are required capabilities, not optional negotiation; the boot validator fails fast when they are absent. Applications on @byline/db-postgres get the new behavior by updating the package.

  • @byline/corebreaking: RoutesConfig is now the resolved three-path shape (admin, api, signIn). Configure routes via resolveRoutes({ admin, api, signIn }) and read routes.signIn; paths may be multi-segment but must not overlap, and unsafe / protocol-relative / encoded / query- or fragment-bearing values are rejected at boot. Legacy signInPath route-factory input and SignInForm.callbackUrl remain as deprecated compatibility paths; sign-in redirects are constrained to safe root-relative destinations inside the configured admin tree.

  • @byline/corebreaking: document delete returns 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-startbreaking: a RequestContext factory must be request-stable — every resolution within one logical request returns the same context instance and requestId, or two reads sharing a ReadContext will throw. The host's own factories satisfy this automatically; custom hosts and direct createBylineClient factories must memoize per request. The public readContext.beforeReadCache property is deprecated and ignored for security.

  • @byline/clibreaking: 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.