@byline/core 4.8.0 → 4.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/dist/@types/collection-types.d.ts +4 -4
  2. package/dist/@types/db-types.d.ts +11 -11
  3. package/dist/@types/search-types.d.ts +60 -11
  4. package/dist/@types/search-types.js +2 -1
  5. package/dist/@types/site-config.d.ts +7 -7
  6. package/dist/auth/assert-actor-can-perform.d.ts +1 -1
  7. package/dist/auth/assert-actor-can-perform.js +1 -1
  8. package/dist/auth/register-collection-abilities.d.ts +1 -1
  9. package/dist/auth/register-collection-abilities.js +1 -1
  10. package/dist/config/config.d.ts +1 -1
  11. package/dist/config/config.js +1 -1
  12. package/dist/core.d.ts +1 -1
  13. package/dist/core.js +1 -1
  14. package/dist/lib/errors.d.ts +1 -1
  15. package/dist/lib/errors.js +1 -1
  16. package/dist/schemas/zod/builder.js +2 -2
  17. package/dist/services/build-search-document.d.ts +1 -1
  18. package/dist/services/build-search-document.js +1 -1
  19. package/dist/services/document-lifecycle/audit.d.ts +1 -1
  20. package/dist/services/document-lifecycle/audit.js +2 -2
  21. package/dist/services/document-lifecycle/context.d.ts +1 -1
  22. package/dist/services/document-lifecycle/create.d.ts +1 -1
  23. package/dist/services/document-lifecycle/delete.js +1 -1
  24. package/dist/services/document-lifecycle/internals.d.ts +1 -1
  25. package/dist/services/document-lifecycle/internals.js +2 -2
  26. package/dist/services/document-lifecycle/status.js +1 -1
  27. package/dist/services/document-lifecycle/system-fields.d.ts +1 -1
  28. package/dist/services/document-lifecycle/system-fields.js +1 -1
  29. package/dist/services/document-lifecycle/update.d.ts +2 -2
  30. package/dist/services/document-lifecycle.test.node.js +3 -3
  31. package/dist/services/validate-search-config.js +2 -2
  32. package/package.json +2 -2
@@ -844,7 +844,7 @@ export interface AfterReadContext {
844
844
  * - `collectionPath` — the collection being queried (useful when the
845
845
  * same hook function is reused across collections).
846
846
  *
847
- * See `docs/06-auth-and-security/01-authn-authz.md` for the strategic rationale; the Quick
847
+ * See `docs/07-auth-and-security/01-authn-authz.md` for the strategic rationale; the Quick
848
848
  * Reference there carries six worked recipes.
849
849
  */
850
850
  export interface BeforeReadContext {
@@ -937,7 +937,7 @@ export interface CollectionHooks {
937
937
  * that the query layer ANDs onto the caller's `where` to enforce
938
938
  * read-side row scoping (multi-tenant, owner-only-drafts, soft-delete
939
939
  * hide, etc). Returning `void` applies no scoping. Multiple functions
940
- * combine with implicit AND. See `docs/06-auth-and-security/01-authn-authz.md` (Read-side
940
+ * combine with implicit AND. See `docs/07-auth-and-security/01-authn-authz.md` (Read-side
941
941
  * scoping + Quick Reference recipes).
942
942
  */
943
943
  beforeRead?: BeforeReadHookSlot;
@@ -1042,7 +1042,7 @@ export interface CollectionDefinition {
1042
1042
  * of what to index. The implementor names fields by the role they play;
1043
1043
  * core derives each field's type from the schema and assembles the
1044
1044
  * type-enriched `SearchDocument` (see the `SearchProvider` seam in
1045
- * `docs/05-reading-and-delivery/07-search.md`). Nothing is auto-pulled, so
1045
+ * `docs/06-search/01-configuration.md`). Nothing is auto-pulled, so
1046
1046
  * unindexed content (editorial notes, internal fields) never leaks into
1047
1047
  * the index.
1048
1048
  *
@@ -1131,7 +1131,7 @@ export interface CollectionDefinition {
1131
1131
  * collection has at least one `localized` field, so the validator rejects
1132
1132
  * `advertiseLocales: true` on a collection with none.
1133
1133
  *
1134
- * See `docs/07-internationalization/index.md`.
1134
+ * See `docs/08-internationalization/index.md`.
1135
1135
  */
1136
1136
  advertiseLocales?: boolean;
1137
1137
  /**
@@ -40,7 +40,7 @@ export type ReadMode = 'any' | 'published';
40
40
  * safe default for internal/direct reads); `@byline/client` defaults it to
41
41
  * `'fallback'` for application reads. Availability follows path-coverage against
42
42
  * the default content locale; a document with no localized content is available
43
- * in every locale. See `docs/07-internationalization/index.md`.
43
+ * in every locale. See `docs/08-internationalization/index.md`.
44
44
  */
45
45
  export type MissingLocalePolicy = 'empty' | 'fallback' | 'omit';
46
46
  /**
@@ -241,7 +241,7 @@ export interface IDbAdapter {
241
241
  * boot by `initBylineCore` so in-place upgrades self-heal without a manual
242
242
  * step or a migrate-ordering constraint — a no-op (zero rows) once every
243
243
  * document is stamped. Optional so adapters that don't model `source_locale`
244
- * need not implement it. See docs/07-internationalization/index.md.
244
+ * need not implement it. See docs/08-internationalization/index.md.
245
245
  */
246
246
  backfillSourceLocales?: () => Promise<{
247
247
  rowsUpdated: number;
@@ -260,7 +260,7 @@ export interface IDbAdapter {
260
260
  /**
261
261
  * The realm of the actor that performed an audited change. `'admin'` for
262
262
  * admin-user actions, `'user'` reserved for the end-user realm, `'system'`
263
- * for deliberate internal-tooling writes. See docs/06-auth-and-security/02-auditability.md.
263
+ * for deliberate internal-tooling writes. See docs/07-auth-and-security/02-auditability.md.
264
264
  */
265
265
  export type AuditActorRealm = 'admin' | 'user' | 'system';
266
266
  /** Input to `IAuditCommands.append` — one audit-log row. */
@@ -305,7 +305,7 @@ export interface AuditLogPage {
305
305
  }
306
306
  /**
307
307
  * Append-only audit-log writes. The companion read interface is
308
- * `IAuditQueries`. See docs/06-auth-and-security/02-auditability.md — Workstream 2.
308
+ * `IAuditQueries`. See docs/07-auth-and-security/02-auditability.md — Workstream 2.
309
309
  */
310
310
  export interface IAuditCommands {
311
311
  /**
@@ -318,7 +318,7 @@ export interface IAuditCommands {
318
318
  id: string;
319
319
  }>;
320
320
  }
321
- /** Audit-log reads. See docs/06-auth-and-security/02-auditability.md — Workstreams 3 & 4. */
321
+ /** Audit-log reads. See docs/07-auth-and-security/02-auditability.md — Workstreams 3 & 4. */
322
322
  export interface IAuditQueries {
323
323
  /**
324
324
  * The audit history for one document, newest first, paged. Backs the
@@ -332,7 +332,7 @@ export interface IAuditQueries {
332
332
  page_size?: number;
333
333
  }): Promise<AuditLogPage>;
334
334
  /**
335
- * The system-wide activity feed (docs/06-auth-and-security/02-auditability.md — Workstream 4), newest
335
+ * The system-wide activity feed (docs/07-auth-and-security/02-auditability.md — Workstream 4), newest
336
336
  * first, paged and filterable. A read-time **union** of two disjoint event
337
337
  * sources, normalised onto the `AuditLogEntry` shape:
338
338
  *
@@ -517,7 +517,7 @@ export interface IDocumentCommands {
517
517
  * editorial advertised-locale set. `undefined` leaves the existing set
518
518
  * untouched (sticky across versions, like `path`); `[]` clears it. The
519
519
  * locale values are the advertised content locales themselves, not the
520
- * write locale. See `docs/07-internationalization/index.md`.
520
+ * write locale. See `docs/08-internationalization/index.md`.
521
521
  */
522
522
  availableLocales?: string[];
523
523
  locale?: string;
@@ -549,7 +549,7 @@ export interface IDocumentCommands {
549
549
  * the change is immediate and applies across every version. Backs the admin
550
550
  * path widget's direct-write Save. The unique constraint on
551
551
  * `(collection_id, locale, path)` may surface as `ERR_PATH_CONFLICT` from the
552
- * lifecycle layer. See `docs/07-internationalization/index.md`.
552
+ * lifecycle layer. See `docs/08-internationalization/index.md`.
553
553
  */
554
554
  updateDocumentPath(params: {
555
555
  documentId: string;
@@ -563,7 +563,7 @@ export interface IDocumentCommands {
563
563
  * wholesale **without** minting a new version or touching workflow status —
564
564
  * the change is immediate and applies across every version. `[]` clears it.
565
565
  * Backs the admin available-locales widget's direct-write Save. See
566
- * `docs/07-internationalization/index.md`.
566
+ * `docs/08-internationalization/index.md`.
567
567
  */
568
568
  setDocumentAvailableLocales(params: {
569
569
  documentId: string;
@@ -617,7 +617,7 @@ export interface IDocumentCommands {
617
617
  documentId: string;
618
618
  locale: string;
619
619
  status?: string;
620
- /** Acting user id for the version audit trail (`created_by`). See docs/06-auth-and-security/02-auditability.md. */
620
+ /** Acting user id for the version audit trail (`created_by`). See docs/07-auth-and-security/02-auditability.md. */
621
621
  createdBy?: string;
622
622
  }): Promise<{
623
623
  newVersionId: string;
@@ -731,7 +731,7 @@ export interface IDocumentQueries {
731
731
  /**
732
732
  * Request-scoped auth context. Adapters thread it through to
733
733
  * `assertActorCanPerform` for ability assertion and to `beforeRead`
734
- * hooks for query scoping. See docs/06-auth-and-security/01-authn-authz.md.
734
+ * hooks for query scoping. See docs/07-auth-and-security/01-authn-authz.md.
735
735
  */
736
736
  requestContext?: RequestContext;
737
737
  /**
@@ -16,18 +16,19 @@
16
16
  * the provider never sees EAV store rows. The document carries a typed,
17
17
  * role-tagged field projection ({@link SearchField}) so a driver can map
18
18
  * each field onto its own index schema (Postgres store columns + weighted
19
- * tsvector, Solr dynamic fields, a vector store's payload, …) without
20
- * re-deriving types. A built-in Postgres full-text driver ships as
21
- * `@byline/search-postgres`; external drivers implement the same interface
22
- * rather than forking the read path.
19
+ * tsvector, MySQL FULLTEXT columns, Solr dynamic fields, a vector store's
20
+ * payload, …) without re-deriving types. Built-in SQL full-text drivers ship as
21
+ * `@byline/search-postgres` and `@byline/search-mysql`; external drivers
22
+ * implement the same interface rather than forking the read path.
23
23
  *
24
- * The provider factory (e.g. `postgresSearch({ getClient })`) lives in the
24
+ * The provider factory (e.g. `postgresSearch({ pool })` or
25
+ * `mysqlSearch({ pool })`) lives in the
25
26
  * driver package, not here — core declares only the interface and its data
26
27
  * types, the same way `RichTextPopulateFn` is declared in core while
27
28
  * `lexicalEditorPopulateServer()` lives in `@byline/richtext-lexical`. This
28
29
  * keeps `@byline/core` from depending on `@byline/client`.
29
30
  *
30
- * See `docs/05-reading-and-delivery/07-search.md` for the full design.
31
+ * See `docs/06-search/04-provider-contract.md` for the full contract.
31
32
  */
32
33
  import type { QueryPredicate } from './query-predicate.js';
33
34
  /**
@@ -138,14 +139,47 @@ export interface SearchDocument {
138
139
  /** ISO-8601 timestamp of the indexed version. */
139
140
  updatedAt: string;
140
141
  }
142
+ /** How independently analyzed query concepts combine for full-text matching. */
143
+ export type SearchTermOperator = 'all' | 'any';
144
+ /**
145
+ * Phrase behavior for a lexical query:
146
+ *
147
+ * - `auto` — quoted spans are phrases; unquoted text follows `operator`.
148
+ * - `required` — the complete non-empty query is also required as a phrase.
149
+ * - `off` — do not create phrase constraints, including for quoted spans.
150
+ */
151
+ export type SearchPhraseMode = 'auto' | 'required' | 'off';
152
+ /**
153
+ * Provider-neutral full-text matching intent. Providers translate this into
154
+ * their native query syntax; it is product behavior, not a ranking hint.
155
+ */
156
+ export interface SearchMatching {
157
+ /** Whether every concept or any concept must match. Defaults to `all`. */
158
+ operator?: SearchTermOperator;
159
+ /**
160
+ * Require at least this many concepts when `operator` is `any`.
161
+ * Providers that cannot express this advertise that limitation through
162
+ * `capabilities.fullText.minimumShouldMatch`.
163
+ */
164
+ minimumShouldMatch?: number;
165
+ /** Phrase handling. Defaults to `auto`. */
166
+ phrase?: SearchPhraseMode;
167
+ }
168
+ /** Maximum UTF-16 length accepted for one full-text query. */
169
+ export declare const MAX_SEARCH_QUERY_LENGTH = 1024;
141
170
  /**
142
171
  * A search request. Either collection-scoped (`collectionPath`) for
143
172
  * homogeneous results, or zone-scoped (`zone`) for heterogeneous
144
173
  * cross-collection results — see the two `client.search()` entry points.
145
174
  */
146
175
  export interface SearchQuery {
147
- /** The free-text query string. */
176
+ /** The free-text query string, limited to {@link MAX_SEARCH_QUERY_LENGTH}. */
148
177
  query: string;
178
+ /**
179
+ * Explicit full-text matching semantics. Defaults to all concepts required
180
+ * with quoted spans treated as phrases.
181
+ */
182
+ matching?: SearchMatching;
149
183
  /**
150
184
  * Cross-collection scope — every collection indexed into this zone.
151
185
  * Mutually exclusive with `collectionPath` in practice.
@@ -239,6 +273,21 @@ export interface SearchCapabilities {
239
273
  weighting: boolean;
240
274
  /** Matched-snippet highlighting in results. */
241
275
  highlights: boolean;
276
+ /** Detailed full-text analysis and matching capabilities. */
277
+ fullText: {
278
+ /** The backend applies its own language analyzer to original text. */
279
+ nativeAnalysis: boolean;
280
+ /** The backend can index application-produced portable logical tokens. */
281
+ portableAnalysis: boolean;
282
+ /** Supports `matching.operator: 'all'`. */
283
+ allTerms: boolean;
284
+ /** Supports `matching.operator: 'any'`. */
285
+ anyTerms: boolean;
286
+ /** Supports `matching.minimumShouldMatch`. */
287
+ minimumShouldMatch: boolean;
288
+ /** Supports ordered phrase constraints. */
289
+ phrase: boolean;
290
+ };
242
291
  }
243
292
  /**
244
293
  * The provider-agnostic search seam. A driver implements indexing
@@ -271,11 +320,11 @@ export interface SearchProvider {
271
320
  /** Execute a query and return ranked hits. */
272
321
  search(query: SearchQuery): Promise<SearchResults>;
273
322
  /**
274
- * Drop and rebuild a collection's slice (or the whole index). Used for
275
- * first-time backfill, driver swaps, and after a `search` config change.
276
- * Optional not every driver supports bulk rebuild.
323
+ * Clear a collection's slice (or the whole derived index) before the caller
324
+ * rebuilds it. Required because search projections are disposable and must
325
+ * be reproducible after analyzer, schema, or provider changes.
277
326
  */
278
- reindex?(opts: {
327
+ reindex(opts: {
279
328
  collectionPath?: string;
280
329
  }): Promise<void>;
281
330
  }
@@ -5,4 +5,5 @@
5
5
  *
6
6
  * Copyright (c) Infonomic Company Limited
7
7
  */
8
- export {};
8
+ /** Maximum UTF-16 length accepted for one full-text query. */
9
+ export const MAX_SEARCH_QUERY_LENGTH = 1_024;
@@ -70,7 +70,7 @@ export interface BaseConfig {
70
70
  * its path locale, and the completeness ledger — so changing this value
71
71
  * is safe for existing data: they keep reading against the locale they
72
72
  * were authored in. New documents created after the change anchor to the
73
- * new value. See docs/07-internationalization/index.md.
73
+ * new value. See docs/08-internationalization/index.md.
74
74
  *
75
75
  * (Switching this on a live system still needs the one-time
76
76
  * `backfillSourceLocales()` maintenance step to have stamped any rows
@@ -109,7 +109,7 @@ export interface BaseConfig {
109
109
  *
110
110
  * Optional at the type level so `BaseConfig` stays loose for tests
111
111
  * and seed scripts; required at runtime via `validateTranslations`
112
- * whenever `interface.locales` is non-empty. See `docs/07-internationalization/index.md` for
112
+ * whenever `interface.locales` is non-empty. See `docs/08-internationalization/index.md` for
113
113
  * the design.
114
114
  *
115
115
  * The shape is declared inline (rather than imported from
@@ -415,10 +415,10 @@ export interface ServerConfig<TAdminStore = unknown> extends BaseConfig {
415
415
  * beside `db` and `storage`, composed by `initBylineCore()`.
416
416
  *
417
417
  * Drivers ship as separate packages and are constructed via a factory,
418
- * mirroring the richText server adapters. The built-in Postgres
419
- * full-text driver is `@byline/search-postgres`. When any collection
420
- * opts into search (`CollectionDefinition.search`) but no provider is
421
- * registered, `initBylineCore()` fails fast.
418
+ * mirroring the richText server adapters. The built-in SQL full-text drivers
419
+ * are `@byline/search-postgres` and `@byline/search-mysql`. When any
420
+ * collection opts into search (`CollectionDefinition.search`) but no
421
+ * provider is registered, `initBylineCore()` fails fast.
422
422
  *
423
423
  * @example
424
424
  * ```ts
@@ -426,7 +426,7 @@ export interface ServerConfig<TAdminStore = unknown> extends BaseConfig {
426
426
  *
427
427
  * defineServerConfig({
428
428
  * // ...
429
- * search: postgresSearch({ getClient: getAdminBylineClient }),
429
+ * search: postgresSearch({ pool: db.pool }),
430
430
  * })
431
431
  * ```
432
432
  */
@@ -33,6 +33,6 @@ import { type CollectionAbilityVerb } from './register-collection-abilities.js';
33
33
  * bypass this helper — the same escape hatch that skips collection
34
34
  * hooks. Seeds, migrations, and internal tooling live there.
35
35
  *
36
- * See docs/06-auth-and-security/01-authn-authz.md.
36
+ * See docs/07-auth-and-security/01-authn-authz.md.
37
37
  */
38
38
  export declare function assertActorCanPerform(context: RequestContext | undefined, collectionPath: string, verb: CollectionAbilityVerb): void;
@@ -33,7 +33,7 @@ import { collectionAbilityKey, } from './register-collection-abilities.js';
33
33
  * bypass this helper — the same escape hatch that skips collection
34
34
  * hooks. Seeds, migrations, and internal tooling live there.
35
35
  *
36
- * See docs/06-auth-and-security/01-authn-authz.md.
36
+ * See docs/07-auth-and-security/01-authn-authz.md.
37
37
  */
38
38
  export function assertActorCanPerform(context, collectionPath, verb) {
39
39
  if (!context) {
@@ -32,7 +32,7 @@ import type { CollectionDefinition } from '../@types/index.js';
32
32
  * predictable and avoids hidden conditional logic downstream.
33
33
  *
34
34
  * Called from `initBylineCore()` for each declared collection. See
35
- * docs/06-auth-and-security/01-authn-authz.md.
35
+ * docs/07-auth-and-security/01-authn-authz.md.
36
36
  */
37
37
  export declare function registerCollectionAbilities(registry: AbilityRegistry, definition: CollectionDefinition): void;
38
38
  /** The ability suffixes that every collection contributes. Exposed for contract tests. */
@@ -30,7 +30,7 @@
30
30
  * predictable and avoids hidden conditional logic downstream.
31
31
  *
32
32
  * Called from `initBylineCore()` for each declared collection. See
33
- * docs/06-auth-and-security/01-authn-authz.md.
33
+ * docs/07-auth-and-security/01-authn-authz.md.
34
34
  */
35
35
  export function registerCollectionAbilities(registry, definition) {
36
36
  const path = definition.path;
@@ -51,7 +51,7 @@ export declare function getServerConfig(): ResolvedServerConfig;
51
51
  * config-driven at the read source. The payoff is canonical downstream
52
52
  * ordering (display switcher, hreflang `alternates`, sitemap) regardless of
53
53
  * the order a document declared its locales in. Read-time projection only;
54
- * nothing persisted changes. See docs/07-internationalization/index.md.
54
+ * nothing persisted changes. See docs/08-internationalization/index.md.
55
55
  */
56
56
  export declare function orderByContentLocale(codes: string[]): string[];
57
57
  export declare function defineBylineCore(core: unknown): void;
@@ -147,7 +147,7 @@ export function getServerConfig() {
147
147
  * config-driven at the read source. The payoff is canonical downstream
148
148
  * ordering (display switcher, hreflang `alternates`, sitemap) regardless of
149
149
  * the order a document declared its locales in. Read-time projection only;
150
- * nothing persisted changes. See docs/07-internationalization/index.md.
150
+ * nothing persisted changes. See docs/08-internationalization/index.md.
151
151
  */
152
152
  export function orderByContentLocale(codes) {
153
153
  const content = getServerConfigInstance()?.i18n?.content;
package/dist/core.d.ts CHANGED
@@ -38,7 +38,7 @@ export interface BylineCore<TAdminStore = unknown> {
38
38
  * during server bootstrap and before any admin UI renders.
39
39
  *
40
40
  * Consumed at runtime by `AdminAuth.assertAbility()` and at design
41
- * time by the admin role-editor UI. See docs/06-auth-and-security/01-authn-authz.md.
41
+ * time by the admin role-editor UI. See docs/07-auth-and-security/01-authn-authz.md.
42
42
  */
43
43
  abilities: AbilityRegistry;
44
44
  /** Convenience wrapper around `abilities.register()`. */
package/dist/core.js CHANGED
@@ -105,7 +105,7 @@ export const initBylineCore = async (config, pinoLogger) => {
105
105
  // and is a no-op (zero rows) once every document is stamped. Self-heals
106
106
  // in-place upgrades without a manual maintenance step. The write path stamps
107
107
  // new documents directly, so steady-state boots touch nothing. See
108
- // docs/07-internationalization/index.md.
108
+ // docs/08-internationalization/index.md.
109
109
  if (typeof composed.db.backfillSourceLocales === 'function') {
110
110
  const { rowsUpdated } = await composed.db.backfillSourceLocales();
111
111
  if (rowsUpdated > 0) {
@@ -118,7 +118,7 @@ export declare const ERR_PATH_CONFLICT: (opts: BylineErrorOptions, errorConstruc
118
118
  * `commands.audit` / `queries.audit` surfaces. A misconfiguration, not a
119
119
  * user error: an auditability guarantee cannot be honoured non-atomically, so
120
120
  * the write is refused loudly rather than recorded with a gap. See
121
- * docs/03-architecture/03-transactions.md and docs/06-auth-and-security/02-auditability.md.
121
+ * docs/03-architecture/03-transactions.md and docs/07-auth-and-security/02-auditability.md.
122
122
  */
123
123
  export declare const ERR_AUDIT_UNSUPPORTED: (opts: BylineErrorOptions, errorConstructor?: any) => BylineError;
124
124
  /**
@@ -155,7 +155,7 @@ export const ERR_PATH_CONFLICT = createErrorType(ErrorCodes.PATH_CONFLICT, 'warn
155
155
  * `commands.audit` / `queries.audit` surfaces. A misconfiguration, not a
156
156
  * user error: an auditability guarantee cannot be honoured non-atomically, so
157
157
  * the write is refused loudly rather than recorded with a gap. See
158
- * docs/03-architecture/03-transactions.md and docs/06-auth-and-security/02-auditability.md.
158
+ * docs/03-architecture/03-transactions.md and docs/07-auth-and-security/02-auditability.md.
159
159
  */
160
160
  export const ERR_AUDIT_UNSUPPORTED = createErrorType(ErrorCodes.AUDIT_UNSUPPORTED);
161
161
  /**
@@ -247,7 +247,7 @@ export const createBaseSchema = (collection) => {
247
247
  id: z.uuid(),
248
248
  versionId: z.uuid().optional(),
249
249
  path: z.string().optional(),
250
- // The document's content source-locale anchor (see docs/07-internationalization/index.md).
250
+ // The document's content source-locale anchor (see docs/08-internationalization/index.md).
251
251
  // Carried through list/get responses so the admin can badge it; Zod would
252
252
  // otherwise strip it as an undeclared key.
253
253
  sourceLocale: z.string().optional(),
@@ -255,7 +255,7 @@ export const createBaseSchema = (collection) => {
255
255
  hasPublishedVersion: z.boolean().optional(),
256
256
  createdAt: z.iso.datetime(),
257
257
  updatedAt: z.iso.datetime(),
258
- // Version audit metadata — acting user + action (see docs/06-auth-and-security/02-auditability.md — Workstream 1).
258
+ // Version audit metadata — acting user + action (see docs/07-auth-and-security/02-auditability.md — Workstream 1).
259
259
  // Declared so list/get/history responses carry them through the
260
260
  // server-fn parse; Zod would otherwise strip them as undeclared keys.
261
261
  createdBy: z.uuid().optional(),
@@ -10,7 +10,7 @@
10
10
  * `SearchProvider` seam. Walks a collection's role-based `search` config
11
11
  * against one locale-resolved document and emits a single, type-enriched
12
12
  * `SearchDocument` for a driver to index. See
13
- * `docs/05-reading-and-delivery/07-search.md`.
13
+ * `docs/06-search/04-provider-contract.md`.
14
14
  *
15
15
  * Role-based and explicit: only the fields named in `search.{body,facets,
16
16
  * filters}` are projected — nothing is auto-pulled, so unindexed content
@@ -10,7 +10,7 @@
10
10
  * `SearchProvider` seam. Walks a collection's role-based `search` config
11
11
  * against one locale-resolved document and emits a single, type-enriched
12
12
  * `SearchDocument` for a driver to index. See
13
- * `docs/05-reading-and-delivery/07-search.md`.
13
+ * `docs/06-search/04-provider-contract.md`.
14
14
  *
15
15
  * Role-based and explicit: only the fields named in `search.{body,facets,
16
16
  * filters}` are projected — nothing is auto-pulled, so unindexed content
@@ -51,7 +51,7 @@ export interface TreeAuditCapability extends AuditCapability {
51
51
  * **both** `withTransaction` and `commands.audit`. Returns a non-null
52
52
  * capability the caller composes; throws `ERR_AUDIT_UNSUPPORTED` otherwise,
53
53
  * rather than silently skipping the audit row or running it non-atomically.
54
- * See docs/03-architecture/03-transactions.md and docs/06-auth-and-security/02-auditability.md.
54
+ * See docs/03-architecture/03-transactions.md and docs/07-auth-and-security/02-auditability.md.
55
55
  */
56
56
  export declare function requireAuditCapability(db: IDbAdapter): AuditCapability;
57
57
  /**
@@ -7,7 +7,7 @@
7
7
  */
8
8
  /**
9
9
  * Audit-log write helpers for the document-grain lifecycle write-points
10
- * (docs/06-auth-and-security/02-auditability.md — Workstream 2). The audit log records the changes the
10
+ * (docs/07-auth-and-security/02-auditability.md — Workstream 2). The audit log records the changes the
11
11
  * immutable version stream does NOT capture an actor for: non-versioned
12
12
  * system-field writes (path, available-locales), in-place status transitions,
13
13
  * and deletions. Each such mutation and its audit row commit atomically inside
@@ -46,7 +46,7 @@ export function auditActor(ctx) {
46
46
  * **both** `withTransaction` and `commands.audit`. Returns a non-null
47
47
  * capability the caller composes; throws `ERR_AUDIT_UNSUPPORTED` otherwise,
48
48
  * rather than silently skipping the audit row or running it non-atomically.
49
- * See docs/03-architecture/03-transactions.md and docs/06-auth-and-security/02-auditability.md.
49
+ * See docs/03-architecture/03-transactions.md and docs/07-auth-and-security/02-auditability.md.
50
50
  */
51
51
  export function requireAuditCapability(db) {
52
52
  const withTransaction = db.withTransaction;
@@ -73,7 +73,7 @@ export interface DocumentLifecycleContext {
73
73
  * — `assertActorCanPerform` runs at every lifecycle entry and rejects
74
74
  * a missing context.
75
75
  *
76
- * See docs/06-auth-and-security/01-authn-authz.md.
76
+ * See docs/07-auth-and-security/01-authn-authz.md.
77
77
  */
78
78
  requestContext?: RequestContext;
79
79
  }
@@ -39,7 +39,7 @@ export declare function createDocument(ctx: DocumentLifecycleContext, params: {
39
39
  * sidebar widget). Document-grain and sticky like `path`: passed straight
40
40
  * to the storage primitive, which replaces the document's rows wholesale.
41
41
  * `undefined` writes nothing (a new document starts with an empty set —
42
- * the safe opt-in default); `[]` clears it. See docs/07-internationalization/index.md.
42
+ * the safe opt-in default); `[]` clears it. See docs/08-internationalization/index.md.
43
43
  */
44
44
  availableLocales?: string[];
45
45
  }): Promise<CreateDocumentResult>;
@@ -124,7 +124,7 @@ export async function deleteDocument(ctx, params) {
124
124
  // back, so soft-deleted documents cannot leak live edges.
125
125
  // whole-document delete mints no new version, so the version stream
126
126
  // never records it — the audit log is the only place a deletion is
127
- // accountable (docs/06-auth-and-security/02-auditability.md). Storage-file cleanup (step 4) is a
127
+ // accountable (docs/07-auth-and-security/02-auditability.md). Storage-file cleanup (step 4) is a
128
128
  // DB↔external side-effect and stays OUTSIDE the transaction — it is
129
129
  // post-commit, best-effort compensation (docs/03-architecture/03-transactions.md).
130
130
  const treeAudit = definition.tree === true ? requireTreeAuditCapability(db) : undefined;
@@ -28,7 +28,7 @@ import type { DocumentLifecycleContext } from './context.js';
28
28
  * (the seeds/migrations escape hatch) — both yield `undefined` → NULL
29
29
  * `created_by`, which the history strip renders as "unknown". Real
30
30
  * `AdminAuth` / `UserAuth` actors always carry UUID ids, so their attribution
31
- * is unaffected. See docs/06-auth-and-security/02-auditability.md — Workstream 1.
31
+ * is unaffected. See docs/07-auth-and-security/02-auditability.md — Workstream 1.
32
32
  */
33
33
  export declare function actorId(ctx: DocumentLifecycleContext): string | undefined;
34
34
  /**
@@ -36,7 +36,7 @@ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/
36
36
  * (the seeds/migrations escape hatch) — both yield `undefined` → NULL
37
37
  * `created_by`, which the history strip renders as "unknown". Real
38
38
  * `AdminAuth` / `UserAuth` actors always carry UUID ids, so their attribution
39
- * is unaffected. See docs/06-auth-and-security/02-auditability.md — Workstream 1.
39
+ * is unaffected. See docs/07-auth-and-security/02-auditability.md — Workstream 1.
40
40
  */
41
41
  export function actorId(ctx) {
42
42
  const id = ctx.requestContext?.actor?.id;
@@ -179,7 +179,7 @@ export function resolvePathForUpdate(args) {
179
179
  // skip the write (existing path row stays as-is — sticky). The path row
180
180
  // lives under the document's source_locale (its anchor), not the mutable
181
181
  // global default — so this stays correct after the global default is
182
- // switched. See docs/07-internationalization/index.md.
182
+ // switched. See docs/08-internationalization/index.md.
183
183
  return explicitPath ?? undefined;
184
184
  }
185
185
  // Non-source-locale (translation) write: reject any path change with a warn
@@ -89,7 +89,7 @@ export async function changeDocumentStatus(ctx, params) {
89
89
  // 4–5. Mutate status in-place + auto-archive, atomically with the audit
90
90
  // record. Status mutates the version row rather than minting a new
91
91
  // version, so the version stream never captures *who* changed it —
92
- // the audit log is its only accountability home (docs/06-auth-and-security/02-auditability.md).
92
+ // the audit log is its only accountability home (docs/07-auth-and-security/02-auditability.md).
93
93
  const audit = requireAuditCapability(db);
94
94
  const actor = auditActor(ctx);
95
95
  await audit.withTransaction(async () => {
@@ -31,7 +31,7 @@ export interface UpdateDocumentSystemFieldsResult {
31
31
  * version. This service backs the admin path / available-locales widgets'
32
32
  * direct-write Save (the `direct-write` and `both` dirty-reason cases). The
33
33
  * public *advertised* set remains the intersection of `availableLocales` with
34
- * the resolved version's completeness ledger. See docs/07-internationalization/index.md.
34
+ * the resolved version's completeness ledger. See docs/08-internationalization/index.md.
35
35
  *
36
36
  * Flow:
37
37
  * 1. `assertActorCanPerform('update')` — same auth gate as content writes.
@@ -23,7 +23,7 @@ import { invokeHook, resolvePathForUpdate, rethrowPathConflict } from './interna
23
23
  * version. This service backs the admin path / available-locales widgets'
24
24
  * direct-write Save (the `direct-write` and `both` dirty-reason cases). The
25
25
  * public *advertised* set remains the intersection of `availableLocales` with
26
- * the resolved version's completeness ledger. See docs/07-internationalization/index.md.
26
+ * the resolved version's completeness ledger. See docs/08-internationalization/index.md.
27
27
  *
28
28
  * Flow:
29
29
  * 1. `assertActorCanPerform('update')` — same auth gate as content writes.
@@ -43,7 +43,7 @@ export declare function updateDocument(ctx: DocumentLifecycleContext, params: {
43
43
  * The editorial advertised-locale set. `undefined` leaves the existing
44
44
  * set untouched (sticky — document-grain, like `path`); an explicit array
45
45
  * (empty included) replaces it wholesale. Driven by the admin
46
- * available-locales sidebar widget. See docs/07-internationalization/index.md.
46
+ * available-locales sidebar widget. See docs/08-internationalization/index.md.
47
47
  */
48
48
  availableLocales?: string[];
49
49
  }): Promise<UpdateDocumentResult>;
@@ -78,7 +78,7 @@ export declare function updateDocumentWithPatches(ctx: DocumentLifecycleContext,
78
78
  * The editorial advertised-locale set (typically supplied alongside
79
79
  * patches when the admin available-locales widget has been edited).
80
80
  * `undefined` leaves the existing set untouched (sticky); an explicit
81
- * array replaces it wholesale. See docs/07-internationalization/index.md.
81
+ * array replaces it wholesale. See docs/08-internationalization/index.md.
82
82
  */
83
83
  availableLocales?: string[];
84
84
  }): Promise<UpdateDocumentWithPatchesResult>;
@@ -45,7 +45,7 @@ function createMockDb() {
45
45
  const getDocumentSystemFieldsForUpdate = vi.fn().mockResolvedValue(null);
46
46
  const getCurrentVersionMetadata = vi.fn().mockResolvedValue(null);
47
47
  const getCurrentPath = vi.fn().mockResolvedValue('current-path');
48
- // Audit capability (docs/06-auth-and-security/02-auditability.md — W2). `withTransaction` is a passthrough
48
+ // Audit capability (docs/07-auth-and-security/02-auditability.md — W2). `withTransaction` is a passthrough
49
49
  // in unit tests (runs the unit of work immediately, no real tx); `append`
50
50
  // records the calls so write-point tests can assert the audit rows emitted.
51
51
  const auditAppend = vi.fn().mockResolvedValue({ id: 'audit-1' });
@@ -210,7 +210,7 @@ describe('Document lifecycle service', () => {
210
210
  data: { title: 'Hello' },
211
211
  locale: 'en',
212
212
  });
213
- // Audit contract (docs/06-auth-and-security/02-auditability.md — W1): every version row
213
+ // Audit contract (docs/07-auth-and-security/02-auditability.md — W1): every version row
214
214
  // records the actor that created it.
215
215
  expect(createDocumentVersion.mock.calls[0]?.[0].createdBy).toBe(TEST_ACTOR_ID);
216
216
  });
@@ -806,7 +806,7 @@ describe('Document lifecycle service', () => {
806
806
  getCurrentVersionMetadata.mockResolvedValue({ ...metadataRow });
807
807
  const ctx = buildCtx(db);
808
808
  await changeDocumentStatus(ctx, { documentId: 'doc-1', nextStatus: 'published' });
809
- // The mutation + audit row run inside one withTransaction (docs/06-auth-and-security/02-auditability.md).
809
+ // The mutation + audit row run inside one withTransaction (docs/07-auth-and-security/02-auditability.md).
810
810
  expect(withTransaction).toHaveBeenCalledOnce();
811
811
  expect(auditAppend).toHaveBeenCalledWith(expect.objectContaining({
812
812
  documentId: 'doc-1',
@@ -27,6 +27,6 @@ export function validateSearchConfig(collections, adapters) {
27
27
  return;
28
28
  throw new Error(`initBylineCore: ${optedIn.length} collection(s) opt into search ` +
29
29
  `(${optedIn.join(', ')}) but no search provider is registered. ` +
30
- `Wire one via ServerConfig.search — see \`@byline/search-postgres\` ` +
31
- `\`postgresSearch()\` for the built-in Postgres full-text driver.`);
30
+ `Wire one via ServerConfig.search — use \`postgresSearch()\` from ` +
31
+ `\`@byline/search-postgres\` or \`mysqlSearch()\` from \`@byline/search-mysql\`.`);
32
32
  }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@byline/core",
3
3
  "private": false,
4
4
  "license": "MPL-2.0",
5
- "version": "4.8.0",
5
+ "version": "4.9.0",
6
6
  "engines": {
7
7
  "node": ">=20.9.0"
8
8
  },
@@ -82,7 +82,7 @@
82
82
  "sharp": "^0.35.3",
83
83
  "uuid": "^14.0.1",
84
84
  "zod": "^4.4.3",
85
- "@byline/auth": "4.8.0"
85
+ "@byline/auth": "4.9.0"
86
86
  },
87
87
  "devDependencies": {
88
88
  "@biomejs/biome": "2.5.4",