v3.15.0
Highlights
-
@byline/search-postgres(new package) +@byline/core+@byline/client+@byline/host-tanstack-start+@byline/richtext-lexical— full-text search. A pluggableSearchProviderseam now lives in@byline/core(registered onServerConfig.search, validated atinitBylineCore()), with a batteries-included Postgres driver shipping as the brand-new@byline/search-postgrespackage — ranked search with zero new infrastructure, reusing your existingpgAdapterpool (db.pool). Core assembles each document into a type-enrichedSearchDocumentviabuildSearchDocument(); the driver stores one weightedtsvectorrow per(collection_path, document_id, locale)(title → weightA, body fields →A–Dby their declaredboost, facet terms →C), queries withwebsearch_to_tsquery+ts_rank, and returnsts_headlinehighlights. Collections opt in with asearchconfig —{ body, facets, filters, zones }, where the implementor names fields by the role they play and core derives each field's type from the schema. Querying is a first-class Client SDK method parallel tofind():client.collection('docs').search({ query, locale, limit })returns ranked hits (title / path / score / highlights). Per-locale indexing uses the matching Postgresregconfig(en→english, …) with a configurabledefaultLocale. The index stays live through collection lifecycle hooks (indexDocument/removeFromIndex) and rebuilds viaclient.collection(x).reindex()or the admin Reindex button (a reusablelistActionslist-header slot, self-gated on the newcollections.<path>.reindexability). Published-only and safe for public readers. See Getting started with search below for setup. BM25 / vector / hybrid drivers, facet aggregation, structuredwherefiltering, cross-collection zone queries, and attachment text-extraction are sanctioned future phases — surfaced honestly today through the driver'scapabilitiesflags (weighting+highlightsaretrue; the rest arefalse). -
@byline/richtext-lexical—lexicalToTextextractor +toTextserver seam. Rich-textbodyfields flatten to plain, indexable text through the editor-agnosticServerConfig.fields.richText.toTextseam (RichTextToTextFn), implemented for Lexical aslexicalToText/lexicalEditorToTextServer(@byline/richtext-lexical/server) — a recursive text-node accumulator, no markdown. It is the search sibling of the existinglexicalToMarkdownserializer and is what lets a richtext field contribute to a collection's searchablebody. -
@byline/core+@byline/admin—admin.itemView(generalised relation picker). The per-collection relationpickerconfig has been generalised intoadmin.itemView, which does triple duty — what to fetch (projection) and how to render (presentation) — reused by the relation picker,hasManytiles, and (eventually) heterogeneous search-result rows. The oldpickerkey is kept as a deprecated, backward-compatible alias, so existing collections keep working unchanged. A relation column formatter for list views ships alongside it, so relation fields render their target's real title rather than a bare id in admin tables.
Bug Fixes
-
@byline/host-tanstack-start— search indexing now runs through a newgetSystemBylineClient()(super-admin context, no session cookies) instead of the request-scoped admin client. Calling the request-scoped client from a lifecycle hook coupled indexing to a live HTTP request and threwNo StartEvent found in AsyncLocalStoragefrom every out-of-band write path — import scripts, seeds, migrations, the CLI, and tests. Indexing is background maintenance (it reads the published view and bypassesbeforeRead), so the system context is both correct and runtime-agnostic. Lifecycle hooks that index should resolve the client withgetSystemBylineClient(), notgetAdminBylineClient(). -
@byline/host-tanstack-start— thelistActionslist-header slot (and thus the Reindex button) now renders in the tree list view as well as the default list view.
Chores
@byline/cli— the docs collection example template now ships the full working search vertical:@byline/search-postgresadded to the install dep manifest, the provider +migrate+toTextseam registered in the exampleserver.config.ts, the docs schema'ssearchconfig corrected to the current{ body, facets, … }shape, lifecycle hooks moved to the loader form with ahooks.tsthat indexes viagetSystemBylineClient(), and aReindexButtonwired into the docs list header. Earlier in the cycle the example templates were also migrated from the deprecatedpickerconfig toadmin.itemView.
Getting started with search
Search is opt-in. To enable it on an install:
-
Install the driver (new this release):
pnpm add @byline/search-postgres
pgis a peer dependency — you already have it via@byline/db-postgres. -
Apply the search schema.
@byline/search-postgresowns its own schema — numbered SQL files shipped in the package, tracked in its ownbyline_search_migrationstable. It is not part of your app's Drizzle migration stream, so it will not be applied by your normaldrizzle:migrate. Pick one per environment:-
Run the SQL by hand (locked-down / managed Postgres — DBA-reviewable):
psql "$DATABASE_URL" -f node_modules/@byline/search-postgres/migrations/0001_init.sql -
Call
migrate()as a deploy step (recommended for production — deterministic, explicit DDL permissions, idempotent and transactional per file):import { migrate } from '@byline/search-postgres' const { applied } = await migrate(db.pool, { log: (m) => logger.info(m) }) // applied: [1] (empty when already up to date)
-
autoMigrateat boot (development convenience):search: postgresSearch({ pool: db.pool, autoMigrate: true })
-
-
Register the provider on
ServerConfig.search, reusing the adapter's pool — no second connection:import { postgresSearch } from '@byline/search-postgres' search: postgresSearch({ pool: db.pool, defaultLocale: 'en' }),
initBylineCore()fails fast if a collection opts into search but no provider is registered. -
Opt collections in via their
searchconfig ({ body, facets, filters, zones }) and wire indexing in their lifecycle hooks (indexDocument/removeFromIndex) — see thedocscollection in the example app, or the@byline/clidocs template. -
Backfill content that was published before indexing existed with
await client.collection('docs').reindex()(or the admin Reindex button).
Breaking Changes
None. admin.itemView is additive — the prior picker config continues to work as a deprecated alias.
All other @byline/* packages bumped to 3.15.0 in lockstep with no behavioural changes this cycle.