@byline/core 4.8.0 → 4.10.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.
- package/dist/@types/collection-types.d.ts +4 -4
- package/dist/@types/db-types.d.ts +11 -11
- package/dist/@types/search-types.d.ts +60 -11
- package/dist/@types/search-types.js +2 -1
- package/dist/@types/site-config.d.ts +7 -7
- package/dist/auth/assert-actor-can-perform.d.ts +1 -1
- package/dist/auth/assert-actor-can-perform.js +1 -1
- package/dist/auth/register-collection-abilities.d.ts +1 -1
- package/dist/auth/register-collection-abilities.js +1 -1
- package/dist/config/config.d.ts +1 -1
- package/dist/config/config.js +1 -1
- package/dist/core.d.ts +1 -1
- package/dist/core.js +1 -1
- package/dist/lib/errors.d.ts +1 -1
- package/dist/lib/errors.js +1 -1
- package/dist/schemas/zod/builder.js +2 -2
- package/dist/services/build-search-document.d.ts +1 -1
- package/dist/services/build-search-document.js +1 -1
- package/dist/services/document-lifecycle/audit.d.ts +1 -1
- package/dist/services/document-lifecycle/audit.js +2 -2
- package/dist/services/document-lifecycle/context.d.ts +1 -1
- package/dist/services/document-lifecycle/create.d.ts +1 -1
- package/dist/services/document-lifecycle/delete.js +1 -1
- package/dist/services/document-lifecycle/internals.d.ts +1 -1
- package/dist/services/document-lifecycle/internals.js +2 -2
- package/dist/services/document-lifecycle/status.js +1 -1
- package/dist/services/document-lifecycle/system-fields.d.ts +1 -1
- package/dist/services/document-lifecycle/system-fields.js +1 -1
- package/dist/services/document-lifecycle/update.d.ts +2 -2
- package/dist/services/document-lifecycle.test.node.js +3 -3
- package/dist/services/validate-search-config.js +2 -2
- 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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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
|
|
20
|
-
* re-deriving types.
|
|
21
|
-
* `@byline/search-postgres`; external drivers
|
|
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({
|
|
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/
|
|
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
|
-
*
|
|
275
|
-
*
|
|
276
|
-
*
|
|
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
|
|
327
|
+
reindex(opts: {
|
|
279
328
|
collectionPath?: string;
|
|
280
329
|
}): Promise<void>;
|
|
281
330
|
}
|
|
@@ -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/
|
|
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/
|
|
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
|
|
419
|
-
*
|
|
420
|
-
* opts into search (`CollectionDefinition.search`) but no
|
|
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({
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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;
|
package/dist/config/config.d.ts
CHANGED
|
@@ -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/
|
|
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;
|
package/dist/config/config.js
CHANGED
|
@@ -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/
|
|
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/
|
|
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/
|
|
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) {
|
package/dist/lib/errors.d.ts
CHANGED
|
@@ -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/
|
|
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
|
/**
|
package/dist/lib/errors.js
CHANGED
|
@@ -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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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 —
|
|
31
|
-
|
|
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.
|
|
5
|
+
"version": "4.10.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.
|
|
85
|
+
"@byline/auth": "4.10.0"
|
|
86
86
|
},
|
|
87
87
|
"devDependencies": {
|
|
88
88
|
"@biomejs/biome": "2.5.4",
|