@byline/core 1.8.0 → 1.8.2

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.
@@ -516,9 +516,7 @@ export interface UploadHooks {
516
516
  * by populate. A hook that performs its own reads should thread this
517
517
  * context back in via `client.collection(...).findById(id, { _readContext:
518
518
  * readContext })` so the visited set and read budget are preserved —
519
- * essential to foreclose the A→B→A loop (see
520
- * `docs/analysis/RELATIONSHIPS-ANALYSIS.md` § "Special consideration:
521
- * recursive-read safety").
519
+ * essential to foreclose the A→B→A loop (see `docs/RELATIONSHIPS.md`).
522
520
  */
523
521
  export interface AfterReadContext {
524
522
  /** The raw reconstructed document. Mutate in place — changes persist. */
@@ -551,9 +549,8 @@ export interface AfterReadContext {
551
549
  * - `collectionPath` — the collection being queried (useful when the
552
550
  * same hook function is reused across collections).
553
551
  *
554
- * See `docs/analysis/AUTHN-AUTHZ-ANALYSIS.md` (Phase 7) for the strategic
555
- * rationale and `docs/analysis/ACCESS-CONTROL-RECIPES.md` for worked
556
- * examples.
552
+ * See `docs/AUTHN-AUTHZ.md` for the strategic rationale and
553
+ * `docs/ACCESS-CONTROL-RECIPES.md` for worked examples.
557
554
  */
558
555
  export interface BeforeReadContext {
559
556
  collectionPath: string;
@@ -630,7 +627,7 @@ export interface CollectionHooks {
630
627
  * read-side row scoping (multi-tenant, owner-only-drafts, soft-delete
631
628
  * hide, etc). Returning `void` applies no scoping. Multiple functions
632
629
  * combine with implicit AND. See
633
- * `docs/analysis/ACCESS-CONTROL-RECIPES.md`.
630
+ * `docs/ACCESS-CONTROL-RECIPES.md`.
634
631
  */
635
632
  beforeRead?: BeforeReadHookSlot;
636
633
  /**
@@ -675,11 +672,12 @@ export interface CollectionDefinition {
675
672
  */
676
673
  useAsTitle?: string;
677
674
  /**
678
- * Names the field whose value initialises this collection's
679
- * `documentVersions.path` column. The value is slugified (in the
680
- * default content locale) using the installation slugifier and stored
681
- * as system metadata `path` itself is a reserved name and cannot be
682
- * declared as a field.
675
+ * Names the field whose value initialises a document's `path` row in
676
+ * `byline_document_paths` (the dedicated per-(document, locale) URL slug
677
+ * table, separate from `documentVersions`). The value is slugified (in
678
+ * the default content locale) using the installation slugifier and
679
+ * stored as system metadata — `path` itself is a reserved name and
680
+ * cannot be declared as a field.
683
681
  *
684
682
  * `path` is sticky after creation: subsequent updates do not
685
683
  * re-derive. Users edit it via the system path widget; collections
@@ -285,10 +285,9 @@ export interface IDocumentQueries {
285
285
  */
286
286
  filters?: DocumentFilter[];
287
287
  /**
288
- * Request-scoped auth context. Plumbing only in Phase 0 — adapters
289
- * currently ignore it. Phase 4 uses it for `collections.<path>.read`
290
- * ability assertion and for `beforeRead`-hook query scoping.
291
- * See docs/analysis/AUTHN-AUTHZ-ANALYSIS.md.
288
+ * Request-scoped auth context. Adapters thread it through to
289
+ * `assertActorCanPerform` for ability assertion and to `beforeRead`
290
+ * hooks for query scoping. See docs/AUTHN-AUTHZ.md.
292
291
  */
293
292
  requestContext?: RequestContext;
294
293
  /**
@@ -306,9 +305,12 @@ export interface IDocumentQueries {
306
305
  * Fetch only the current version's metadata row (no field reconstruction).
307
306
  *
308
307
  * Use this when the caller only needs `{document_version_id, status,
309
- * path, ...}` — for example, workflow transitions that read the current
310
- * status and version ID before mutating. Skipping reconstruction avoids
311
- * the full 7-way UNION ALL and the meta-row fetch.
308
+ * created_at, updated_at}` — for example, workflow transitions that read
309
+ * the current status and version ID before mutating. Skipping
310
+ * reconstruction avoids the full 7-way UNION ALL and the meta-row fetch.
311
+ *
312
+ * `path` is intentionally not returned: callers that need it should
313
+ * use `getDocumentById` (which projects the locale-resolved path).
312
314
  *
313
315
  * Returns `null` when the document does not exist (or has been soft-deleted).
314
316
  */
@@ -319,7 +321,6 @@ export interface IDocumentQueries {
319
321
  document_version_id: string;
320
322
  document_id: string;
321
323
  collection_id: string;
322
- path: string;
323
324
  status: string;
324
325
  created_at: Date;
325
326
  updated_at: Date;
@@ -462,7 +463,7 @@ export interface IDocumentQueries {
462
463
  /** Text search across the collection's configured search fields. */
463
464
  query?: string;
464
465
  sort?: FieldSort;
465
- /** Document-level sort column (created_at, updated_at, path). Used when sort is not a field-level sort. */
466
+ /** Document-level sort column (`created_at`, `updated_at`). Used when `sort` is not a field-level sort. */
466
467
  orderBy?: string;
467
468
  orderDirection?: 'asc' | 'desc';
468
469
  locale?: string;
@@ -123,8 +123,9 @@ export interface ServerConfig<TAdminStore = unknown> extends BaseConfig {
123
123
  */
124
124
  storage?: IStorageProvider;
125
125
  /**
126
- * Installation-wide slugifier used to derive `documentVersions.path`
127
- * from the field named by `CollectionDefinition.useAsPath`.
126
+ * Installation-wide slugifier used to derive a document's `path` (stored
127
+ * in `byline_document_paths`) from the field named by
128
+ * `CollectionDefinition.useAsPath`.
128
129
  *
129
130
  * Falls back to the default `slugify` from `@byline/core` when not set.
130
131
  * Must be pure and synchronous — it runs server-side at write time and
@@ -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/analysis/AUTHN-AUTHZ-ANALYSIS.md §8.
36
+ * See docs/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/analysis/AUTHN-AUTHZ-ANALYSIS.md §8.
36
+ * See docs/AUTHN-AUTHZ.md.
37
37
  */
38
38
  export function assertActorCanPerform(context, collectionPath, verb) {
39
39
  if (!context) {
@@ -29,7 +29,7 @@ import type { CollectionDefinition } from '../@types/index.js';
29
29
  * avoids hidden conditional logic downstream.
30
30
  *
31
31
  * Called from `initBylineCore()` for each declared collection. See
32
- * docs/analysis/AUTHN-AUTHZ-ANALYSIS.md §3 and Phase 1.
32
+ * docs/AUTHN-AUTHZ.md.
33
33
  */
34
34
  export declare function registerCollectionAbilities(registry: AbilityRegistry, definition: CollectionDefinition): void;
35
35
  /** The ability suffixes that every collection contributes. Exposed for contract tests. */
@@ -27,7 +27,7 @@
27
27
  * avoids hidden conditional logic downstream.
28
28
  *
29
29
  * Called from `initBylineCore()` for each declared collection. See
30
- * docs/analysis/AUTHN-AUTHZ-ANALYSIS.md §3 and Phase 1.
30
+ * docs/AUTHN-AUTHZ.md.
31
31
  */
32
32
  export function registerCollectionAbilities(registry, definition) {
33
33
  const path = definition.path;
package/dist/core.d.ts CHANGED
@@ -37,9 +37,8 @@ export interface BylineCore<TAdminStore = unknown> {
37
37
  * `registerAbility()` — or directly against `core.abilities` — typically
38
38
  * during server bootstrap and before any admin UI renders.
39
39
  *
40
- * Consumed at runtime by `AdminAuth.assertAbility()` (Phase 4) and at
41
- * design time by the admin role-editor UI (Phase 6). See
42
- * docs/analysis/AUTHN-AUTHZ-ANALYSIS.md §3.
40
+ * Consumed at runtime by `AdminAuth.assertAbility()` and at design
41
+ * time by the admin role-editor UI. See docs/AUTHN-AUTHZ.md.
43
42
  */
44
43
  abilities: AbilityRegistry;
45
44
  /** Convenience wrapper around `abilities.register()`. */
@@ -43,7 +43,10 @@ export interface ParsedWhere {
43
43
  status?: string;
44
44
  /** Text search query (for collection-configured search fields). */
45
45
  query?: string;
46
- /** Filter on document_versions.path with an operator. */
46
+ /**
47
+ * Filter on a document's `path` (resolved against `byline_document_paths`
48
+ * via the locale priority chain) with an operator.
49
+ */
47
50
  pathFilter?: {
48
51
  operator: FieldFilterOperator;
49
52
  value: string;
@@ -41,7 +41,7 @@ const categoriesCollection = defineCollection({
41
41
  fields: [
42
42
  { name: 'name', type: 'text', label: 'Name', localized: true },
43
43
  // `slug`, not `path` — `path` is a reserved key that resolves to the
44
- // target version's `document_versions.path` column inside a nested
44
+ // target document's `byline_document_paths` row inside a nested
45
45
  // sub-clause (same precedence as the top level), so a real `path`
46
46
  // field would be unreachable through the where clause.
47
47
  { name: 'slug', type: 'text', label: 'Slug' },
@@ -245,9 +245,9 @@ describe('parseWhere', () => {
245
245
  });
246
246
  it('promotes `path` inside a nested sub-where to a DocumentColumnFilter', async () => {
247
247
  // Reserved-key precedence: `path` inside a relation sub-clause maps to
248
- // the target version's `document_versions.path` column, never to a
249
- // field of the same name. The adapter wires it to `td${depth}.path`
250
- // via the inner relation scope.
248
+ // the target document's `byline_document_paths` row, never to a field
249
+ // of the same name. The adapter resolves it via a `pathProjection`
250
+ // subquery scoped to the inner relation hop's `td${depth}.document_id`.
251
251
  const result = await parseWhere({ category: { path: 'news' } }, testCollection, ctx);
252
252
  expect(result.filters).toHaveLength(1);
253
253
  expect(result.filters[0]).toEqual({
@@ -83,10 +83,12 @@ export interface DocumentLifecycleContext {
83
83
  * entry points will call `context.requestContext?.actor?.assertAbility(...)`
84
84
  * before any storage mutation.
85
85
  *
86
- * Optional in Phase 0 so that existing callers (admin server fns, seed
87
- * scripts, tests) continue to compile. Phase 4 tightens the type.
86
+ * Optional so that internal-tooling callers (seed scripts, migration
87
+ * tools) continue to compile. Production write paths always supply it
88
+ * — `assertActorCanPerform` runs at every lifecycle entry and rejects
89
+ * a missing context.
88
90
  *
89
- * See docs/analysis/AUTHN-AUTHZ-ANALYSIS.md.
91
+ * See docs/AUTHN-AUTHZ.md.
90
92
  */
91
93
  requestContext?: RequestContext;
92
94
  }
@@ -96,7 +96,8 @@ function extractDocumentId(document) {
96
96
  return document?.document_id ?? '';
97
97
  }
98
98
  /**
99
- * Derive a `documentVersions.path` value at create time.
99
+ * Derive the `path` value written into `byline_document_paths` at
100
+ * create time.
100
101
  *
101
102
  * 1. `definition.useAsPath` set → slugify the named source field's value
102
103
  * in the default content locale.
@@ -25,7 +25,7 @@
25
25
  * documents. The guard is in place from day one so that the hook work
26
26
  * in Phase 4+ cannot reintroduce the problem.
27
27
  *
28
- * See docs/analysis/RELATIONSHIPS-ANALYSIS.md for the full design rationale.
28
+ * See docs/RELATIONSHIPS.md for the full design rationale.
29
29
  *
30
30
  * ---------------------------------------------------------------------
31
31
  * DSL summary
@@ -15,8 +15,7 @@
15
15
  * shape, populate-spec match, `_resolved` skip, richText null handling).
16
16
  *
17
17
  * Both `collectRelationLeaves` (populate.ts) and `collectRichTextLeaves`
18
- * (richtext-populate.ts) are now thin filters over this primitive — see
19
- * docs/TODO.md "walkFieldTree" entry for the rationale.
18
+ * (richtext-populate.ts) are thin filters over this primitive.
20
19
  */
21
20
  import { type Field, type FieldSet } from '../@types/field-types.js';
22
21
  /**
@@ -15,8 +15,7 @@
15
15
  * shape, populate-spec match, `_resolved` skip, richText null handling).
16
16
  *
17
17
  * Both `collectRelationLeaves` (populate.ts) and `collectRichTextLeaves`
18
- * (richtext-populate.ts) are now thin filters over this primitive — see
19
- * docs/TODO.md "walkFieldTree" entry for the rationale.
18
+ * (richtext-populate.ts) are thin filters over this primitive.
20
19
  */
21
20
  import { isArrayField, isBlocksField, isGroupField, } from '../@types/field-types.js';
22
21
  /**
@@ -9,7 +9,8 @@
9
9
  * Context passed to a `SlugifierFn`.
10
10
  *
11
11
  * `locale` - The content locale being slugified (e.g. the default
12
- * content locale when deriving `documentVersions.path`).
12
+ * content locale when deriving a document's `path`
13
+ * row in `byline_document_paths`).
13
14
  * `collectionPath` - The collection that owns the value being slugified.
14
15
  * Lets installation-supplied slugifiers branch by
15
16
  * collection if URL policies differ.
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": "1.8.0",
5
+ "version": "1.8.2",
6
6
  "engines": {
7
7
  "node": ">=20.9.0"
8
8
  },
@@ -79,7 +79,7 @@
79
79
  "sharp": "^0.34.5",
80
80
  "uuid": "^14.0.0",
81
81
  "zod": "^4.4.2",
82
- "@byline/auth": "1.8.0"
82
+ "@byline/auth": "1.8.2"
83
83
  },
84
84
  "devDependencies": {
85
85
  "@biomejs/biome": "2.4.14",