v3.16.0
Highlights
-
@byline/client+@byline/core+@byline/search-postgres— cross-collection zone search +hydrate. The second search entry point is live:client.search({ zone, query })returns heterogeneous hits ranked together across every collection indexed into a named zone, each hit carrying itscollectionPath. Zone membership resolves from the runtime collection definitions via the newresolveSearchZoneshelper — the same rule the indexing assembler applies, so query scope and index contents can't drift. Per-collection read abilities apply with graceful degradation (collections the actor can't read are excluded; the ability error surfaces only when none are readable), and unknown zones throwERR_VALIDATION. Both entry points now takehydrate: true: core batch-reads each collection's hit ids through the normal read path and attaches a shapedClientDocumentashit.document, projected to the collection'sadmin.itemViewcolumns when registered. Hydration also drops stale index entries whose document no longer resolves. New types:ZoneSearchOptions,HydratedSearchHit,ClientSearchResults. -
@byline/client— row-level authorization on search.search()now enforcesbeforeReadrow scoping — "rank in the provider, authorise in core": when a collection configures abeforeReadhook, candidate hit ids are re-resolved through the normal read path (the same predicate merge + SQL compile every other read uses) and unauthorized hits are dropped. Owner-only drafts, multi-tenant isolation and the otherbeforeReadrecipes now hold on search exactly as onfind(). Collections without a hook skip the second query entirely — public search pays nothing. Two documented approximations:total/ facet counts remain the provider's pre-authorization numbers, and a page can come back shorter thanlimitunder scoping (paginate onoffset)._bypassBeforeRead: trueis the system escape hatch. -
@byline/admin— multi-select relation picker forhasManyfields. The picker opens in multi-select mode: rows toggle a check state, already-added targets render as disabled "Already added" rows, and "Add selected (n)" appends the whole batch in pick order — several picks in one trip instead of reopening the modal per item. Single-select relation fields are untouched (the picker branches on amultipleprop). Two new i18n keys ship across all seven admin locales. -
@byline/core+@byline/db-postgres—$some/$every/$nonequery quantifiers. Relation fields in awhereclause now take quantifiers over the target set:{ authors: { $some: { name: 'X' } } },$every(vacuously true for empty sets, Prisma-style), and$none($none: {}= "no targets at all"). A plain nested sub-where remains shorthand for$some; multiple quantifier keys AND together; quantifiers also work on single relations. Unresolvable targets (deleted, or drafts under published reads) are ignored by all three — the same visibility rule populate applies. -
@byline/ui— improved accessibility props for help text, input, and textarea components.
Bug Fixes
@byline/db-postgres— nested relationwherefilters onhasManyfields silently matched nothing: multi-target items store the array index asfield_name('0','1', …) with the field name inparent_path, so the relationEXISTSnever found the rows. hasMany filters now match onparent_path.@byline/cli— the example collection templates carried an obsoletesearch: { fields: [...] }key (inert — the shipped API isbody), and the docs template omitted thecontentcontainer field fromsearch.body, silently excluding document bodies from the index on fresh installs. All five templates repaired.@byline/ui— accessibility fixes for Search, Checkbox, Label, and focus-ring contrast.
Chores
- monorepo — dead-code sweep (−17,500 lines, including a 16,615-line orphaned emoji table in
@byline/richtext-lexicalthat shipped in the published dist) and unused-dependency cleanup across eight packages, now guarded by aknipstep in CI that fails on unused production dependencies. - monorepo — repaired ~164 stale documentation references left by the docs restructure; the admin editor Playwright smoke suite completed its growth checklist (file upload, content-locale switch + translation save, duplicate, restore-version).
Breaking Changes
@byline/client— theBylineClient.searchproperty (which held theSearchProviderinstance) is renamedsearchProvider; thesearchname now belongs to the cross-collection query method. Action: if you read the provider directly off a client instance (client.searchas an object rather than calling it), rename toclient.searchProvider.client.collection(x).search()and theServerConfig.searchconfig key are unchanged — no known consumer touches the property, which is why this rides a minor.
All other @byline/* packages bumped to 3.16.0 in lockstep with no behavioural changes this cycle.