v4.8.0
Highlights
-
@byline/db-mysql— Byline's second database adapter, published for the first time in this release.mysqlAdapter()implements the sameIDbAdaptercontract as@byline/db-postgresover MySQL 8.0.14+ (InnoDB only), and the subpath@byline/db-mysql/adminships the MySQL-backed admin-store repositories that plug into@byline/admin. Document storage, versioning, patches, workflow, populate, and admin auth behave identically regardless of which database backs an installation, because both adapters pass the same shared behavioural suite — 14 suites, 141 tests, run against each adapter's own test database. Continuous integration runs the MySQL suite against a pinnedmysql:8.0service so the engine floor is genuinely exercised rather than assumed.This adapter is published as preliminary, not as fully supported MySQL support. The storage layer is complete and proven; the ecosystem around it is not. Most importantly there is no MySQL search provider, and this fails at boot rather than degrading quietly:
initBylineCore()throws when a collection declares asearchblock with no provider registered, and Byline's own reference application opts five collections into search. A MySQL installation needs the no-op provider documented in the adapter README.byline initalso scaffolds Postgres only. The criteria for general availability are tracked in #58. Choose MySQL for evaluation, prototypes, or installations that do not need search; choose Postgres for everything else.8.0.14 is not an arbitrary floor. It is the first MySQL release supporting both
LEFT JOIN LATERAL(used by the field-level sort path infindDocuments) and subqueries in a view'sFROMclause — which both current-version views require, since each resolves the current version per document via aROW_NUMBER() OVER (PARTITION BY document_id)window inside a derived table. The adapter refuses to start on an older server, and rejects MariaDB by name. -
@byline/core—IDbAdaptergained an optionalclassifyErrorseam, so core maps database failures to domain errors without inspecting driver-specific error anatomy.rethrowPathConflictnow delegates to it. This is the error-side analogue of the storagenormalizeRowseam, and it is what let a second adapter surface path conflicts correctly without core learning MySQL's error shapes.
Bug Fixes
-
@byline/db-postgres—date,timeanddatetimefield values were arriving from the ordinary read path as raw driver strings rather than parsed values. Two mechanisms compounded: the store manifest's null cast collapses absent temporal columns to a TZ-lesstimestampso the seven-wayUNION ALLtype-aligns, and drizzle's node-postgres session installs a type-parser override returning the identity function forDATE/TIMESTAMP/TIMESTAMPTZ. The adapter now coerces both temporal columns, and every coercion on both adapters throws on unparseable input instead of putting a silentInvalid DateintoClientDocument.fields. See Breaking Changes below for the consumer-visible effect. -
@byline/db-postgres—db_init.shnow percent-decodes the user and password inBYLINE_DB_POSTGRES_CONNECTION_STRING, matching what node-postgres itself does when it parses the same string. A password writtenpa%40sspreviously created the database role with the literalpa%40sswhile the adapter connected aspa@ss— an access-denied failure with nothing pointing at the cause. The script now also rejects a malformed percent-escape with a clear message rather than passing it through. This is stricter than before: a connection string whose password contains a raw, unencoded%(which RFC 3986 requires be written%25) is now refused bydb_init.sh, where node-postgres was already failing on the same input. -
@byline/db-postgres—transactionDbis now a required parameter on the storage query builders. It previously defaulted to a manager handing back the raw connection pool, so omitting it silently causedgetDocumentSystemFieldsForUpdate'sSELECT … FOR UPDATEto take its lock on a pool connection outside the caller's transaction — serialising nothing, with no error and no failing test. Miswiring is now a compile error.
Chores
-
monorepo —
npm-run-allmoved from productiondependenciestodevDependenciesacross every publishable package. It is used only by each package'sdevscript, so consumers were installing an unmaintained tool and its dependency tree at runtime for nothing. Consumers of any@byline/*package get a smaller runtime install footprint with no behavioural change. -
monorepo — continuous integration gained a
mysql:8.0service container alongside Postgres, pinned to the engine floor so the oldest supported server is what gets tested rather than a version comfortably above it.
Breaking Changes
-
@byline/db-postgres— breaking:dateanddatetimefield values now arrive asDateobjects rather than raw driver strings.datevalues anchor to UTC midnight for their calendar day;datetimevalues carry the full instant;timevalues are unchanged and remain a string.Required action: check any code that reads a
dateordatetimefield value and either calls a string method on it (.slice(),.split(),.substring(), a regex) or hands it to a date-parsing library that expects a string (parseISO, strictdayjs). Such code now receives aDateand must useDatemethods, or call.toISOString()itself. Code that wraps the value innew Date(...)before formatting, or that already acceptsstring | Date, needs no change. Note also that serialised payload shape changes:JSON.stringifyof aDateemits2026-03-10T00:00:00.000Zwhere the raw driver string was2026-03-10 00:00:00.123456+00.Why this is a minor rather than a major release: these values were previously undocumented raw driver output, and
packages/core/src/storage/storage-row-types.tsalready typed both columns asDate | string— so code written to handle either shape is unaffected. Every publishable@byline/*package is versioned in one lockstep group, and this correction does not warrant taking all sixteen packages to 5.0.0.
All other @byline/* packages bumped to 4.8.0 in lockstep with no behavioural changes this cycle.