@byline/cli 3.14.0 → 3.15.1

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.
@@ -1 +1 @@
1
- {"version":3,"file":"deps.d.ts","sourceRoot":"","sources":["../../src/manifest/deps.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,KAAK,CAAA;AAEnD;;;;;GAKG;AACH,MAAM,MAAM,eAAe,GAAG,YAAY,CAAA;AAE1C,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,QAAQ,CAAA;IACf,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAA;IACZ,qEAAqE;IACrE,QAAQ,CAAC,EAAE,eAAe,CAAA;CAC3B;AASD,eAAO,MAAM,cAAc,WAAW,CAAA;AAEtC,eAAO,MAAM,SAAS,EAAE,SAAS,OAAO,EAiK9B,CAAA"}
1
+ {"version":3,"file":"deps.d.ts","sourceRoot":"","sources":["../../src/manifest/deps.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,KAAK,CAAA;AAEnD;;;;;GAKG;AACH,MAAM,MAAM,eAAe,GAAG,YAAY,CAAA;AAE1C,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,QAAQ,CAAA;IACf,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAA;IACZ,qEAAqE;IACrE,QAAQ,CAAC,EAAE,eAAe,CAAA;CAC3B;AASD,eAAO,MAAM,cAAc,WAAW,CAAA;AAEtC,eAAO,MAAM,SAAS,EAAE,SAAS,OAAO,EAuK9B,CAAA"}
@@ -78,6 +78,12 @@ export const DEP_SPECS = [
78
78
  group: 'byline',
79
79
  note: 'Lexical-backed richtext field + server populate',
80
80
  },
81
+ {
82
+ name: '@byline/search-postgres',
83
+ version: BYLINE_VERSION,
84
+ group: 'byline',
85
+ note: 'built-in Postgres full-text search provider; registered in byline/server.config.ts, drives collections/docs indexing hooks',
86
+ },
81
87
  {
82
88
  name: '@byline/storage-local',
83
89
  version: BYLINE_VERSION,
@@ -1 +1 @@
1
- {"version":3,"file":"deps.js","sourceRoot":"","sources":["../../src/manifest/deps.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"deps.js","sourceRoot":"","sources":["../../src/manifest/deps.ts"],"names":[],"mappings":"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"}
@@ -6,8 +6,9 @@
6
6
  * Copyright (c) Infonomic Company Limited
7
7
  */
8
8
 
9
- import { type CollectionAdminConfig, type ColumnDefinition, defineAdmin } from '@byline/core'
10
9
  import { DateTimeFormatter } from '@byline/admin/react'
10
+ import { type CollectionAdminConfig, type ColumnDefinition, defineAdmin } from '@byline/core'
11
+ import { ReindexButton } from '@byline/host-tanstack-start/admin-shell/collections/reindex-button'
11
12
 
12
13
  import { SummaryLength } from '~/components/summary-length.js'
13
14
 
@@ -70,12 +71,12 @@ export const DocsAdmin: CollectionAdminConfig = defineAdmin(Docs, {
70
71
  * picker modal (opened from a `relation` field widget). Omit to fall back to a
71
72
  * single-line render of `useAsTitle` + `path`.
72
73
  *
73
- * Shape matches `ColumnDefinition` so formatters can be reused across list and picker.
74
+ * Shape matches `ColumnDefinition` so formatters can be reused across list and item view.
74
75
  *
75
76
  * @example
76
- * picker: [{ fieldName: 'title', label: 'Title' }] // or defined above as `pickerViewColumns`
77
+ * itemView: [{ fieldName: 'title', label: 'Title' }] // or defined above as `pickerViewColumns`
77
78
  */
78
- // picker: [],
79
+ // itemView: [],
79
80
 
80
81
  /**
81
82
  * Custom list-view component that completely replaces the default table-based `ListView`
@@ -89,6 +90,16 @@ export const DocsAdmin: CollectionAdminConfig = defineAdmin(Docs, {
89
90
  */
90
91
  // listView: undefined,
91
92
 
93
+ /**
94
+ * Header action components for the default list view — a reusable slot in
95
+ * the list header (the Payload `beforeList`/`afterList` analog). `ReindexButton`
96
+ * rebuilds this collection's search index; it self-gates on the
97
+ * `collections.docs.reindex` ability, so it only appears for actors who
98
+ * hold it. Drop this (and the `search` config in schema.ts) if you don't
99
+ * want search on this collection. See docs/05-reading-and-delivery/07-search.md.
100
+ */
101
+ listActions: [ReindexButton],
102
+
92
103
  /**
93
104
  * Group name for organising this collection in the admin sidebar navigation.
94
105
  *
@@ -0,0 +1,112 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+
9
+ /**
10
+ * Server-only lifecycle hooks for the `docs` collection.
11
+ *
12
+ * ──────────────────────────────────────────────────────────────────────────
13
+ * Why this file exists (the affordance)
14
+ * ──────────────────────────────────────────────────────────────────────────
15
+ * A collection *schema* (`./schema.ts`) is **isomorphic** — Byline bundles it
16
+ * into the browser admin as well as the server, because the admin reads field
17
+ * config from it. Anything the schema *statically imports* therefore ships to
18
+ * the client. Hook *bodies*, however, only ever run server-side.
19
+ *
20
+ * Search indexing is the canonical reason this matters: it imports
21
+ * `getSystemBylineClient` from `@byline/host-tanstack-start`, a server-only
22
+ * module. Importing that from the schema would drag server code into the
23
+ * browser bundle (fatal in dev). The schema avoids it by referencing these
24
+ * hooks through a **dynamic import** instead of an inline object:
25
+ *
26
+ * // schema.ts
27
+ * hooks: () => import('./hooks.js')
28
+ *
29
+ * Because the schema reaches this module only through `import()`, this file —
30
+ * and its entire transitive import graph — is **structurally absent** from
31
+ * the client bundle. `@byline/core` resolves the loader once on the server
32
+ * (memoized) and calls these hooks exactly as it would inline ones. The
33
+ * upshot: **inside this file we may statically import server-only modules
34
+ * directly.** See docs/04-collections → "Hooks must not statically import
35
+ * server-only code".
36
+ *
37
+ * ──────────────────────────────────────────────────────────────────────────
38
+ * Search indexing
39
+ * ──────────────────────────────────────────────────────────────────────────
40
+ * Indexing rides the same lifecycle hooks. The orchestration lives in
41
+ * `@byline/client`: `indexDocument` re-reads the *published* view per locale
42
+ * and upserts / removes (so publish, unpublish, draft-over-published, and
43
+ * plain edits all converge on the same idempotent path); `removeFromIndex`
44
+ * drops every locale on delete.
45
+ *
46
+ * Resolve the client with `getSystemBylineClient()` (super-admin context, no
47
+ * session cookies) — NOT the request-scoped `getAdminBylineClient()`.
48
+ * Indexing is background maintenance that runs from out-of-band write paths
49
+ * too (the import-docs script, seeds, migrations), where there is no HTTP
50
+ * request to read a session cookie from. See
51
+ * docs/05-reading-and-delivery/07-search.md.
52
+ */
53
+
54
+ import { defineHooks } from '@byline/core'
55
+ import { getSystemBylineClient } from '@byline/host-tanstack-start/integrations/byline-client'
56
+
57
+ export default defineHooks({
58
+ beforeCreate: async ({ data, collectionPath }) => {
59
+ console.log(`beforeCreate: creating a document in '${collectionPath}'`, data)
60
+ },
61
+ afterCreate: async ({ collectionPath, documentId, documentVersionId }) => {
62
+ console.log(
63
+ `afterCreate: document ${documentId} (version ${documentVersionId}) created in '${collectionPath}'`
64
+ )
65
+ await getSystemBylineClient().collection(collectionPath).indexDocument(documentId)
66
+ },
67
+ beforeUpdate: async ({ data, collectionPath }) => {
68
+ console.log(`beforeUpdate: updating a document in '${collectionPath}'`, data)
69
+ },
70
+ afterUpdate: async ({ collectionPath, documentId, documentVersionId }) => {
71
+ console.log(
72
+ `afterUpdate: document ${documentId} (version ${documentVersionId}) in '${collectionPath}' updated`
73
+ )
74
+ await getSystemBylineClient().collection(collectionPath).indexDocument(documentId)
75
+ },
76
+ beforeStatusChange: async ({ documentId, collectionPath, previousStatus, nextStatus }) => {
77
+ console.log(
78
+ `beforeStatusChange: ${documentId} in '${collectionPath}' ${previousStatus} → ${nextStatus}`
79
+ )
80
+ },
81
+ // Publish / unpublish flow through the status lifecycle (not afterUpdate),
82
+ // so re-index here too — this is when content becomes visible / invisible
83
+ // to anonymous traffic.
84
+ afterStatusChange: async ({ documentId, collectionPath, previousStatus, nextStatus }) => {
85
+ console.log(
86
+ `afterStatusChange: ${documentId} in '${collectionPath}' ${previousStatus} → ${nextStatus}`
87
+ )
88
+ await getSystemBylineClient().collection(collectionPath).indexDocument(documentId)
89
+ },
90
+ beforeUnpublish: async ({ documentId, collectionPath }) => {
91
+ console.log(`beforeUnpublish: unpublishing ${documentId} in '${collectionPath}'`)
92
+ },
93
+ afterUnpublish: async ({ documentId, collectionPath }) => {
94
+ console.log(`afterUnpublish: ${documentId} in '${collectionPath}' unpublished`)
95
+ await getSystemBylineClient().collection(collectionPath).indexDocument(documentId)
96
+ },
97
+ beforeDelete: async ({ documentId, collectionPath }) => {
98
+ console.log(`beforeDelete: deleting ${documentId} in '${collectionPath}'`)
99
+ },
100
+ afterDelete: async ({ documentId, collectionPath }) => {
101
+ console.log(`afterDelete: ${documentId} in '${collectionPath}' deleted`)
102
+ await getSystemBylineClient().collection(collectionPath).removeFromIndex(documentId)
103
+ },
104
+ // Fires on any structural tree change (place / reorder / re-parent /
105
+ // promote-on-delete) for a `tree: true` collection. The payload carries the
106
+ // affected set; a docs site typically invalidates its nav / breadcrumb /
107
+ // prev-next caches here. A tree move doesn't change a document's indexable
108
+ // text, so there's nothing to re-index.
109
+ afterTreeChange: async ({ collectionPath }) => {
110
+ console.log(`afterTreeChange: tree structure changed in '${collectionPath}'`)
111
+ },
112
+ })
@@ -41,90 +41,29 @@ export const Docs = defineCollection({
41
41
  // placement from the source directory layout (see
42
42
  // byline/scripts/import-docs.ts --tree).
43
43
  tree: true,
44
- search: { fields: ['title'] },
44
+ // Collection search config (docs/05-reading-and-delivery/07-search.md).
45
+ // Each key names the part a field plays in the index: `body` fields feed
46
+ // the full-text search vector (`title` is display-only unless listed here,
47
+ // so we include it and boost it into the heaviest weight class); `facets`,
48
+ // `filters`, and `zones` round out the surface. Opting in here is what
49
+ // makes the lifecycle hooks in ./hooks.ts index this collection — and it
50
+ // requires a `search` provider registered in byline/server.config.ts.
51
+ search: { body: [{ field: 'title', boost: 2 }, 'summary'] },
45
52
  useAsTitle: 'title',
46
53
  useAsPath: 'title',
47
54
  advertiseLocales: true, // Renders the available-locales sidebar widget.
48
55
  linksInEditor: true, // See type definition for details.
49
- // All hooks can be a single function or an array of functions.
50
- // If an array is provided, the functions will be executed in sequence.
51
- hooks: {
52
- beforeCreate: async ({ data, collectionPath }) => {
53
- // Example: beforeCreate hook
54
- console.log(
55
- `beforeCreate: Creating a new document in collection ${collectionPath} with data:`,
56
- data
57
- )
58
- },
59
- afterCreate: async ({ data, collectionPath, documentId, documentVersionId }) => {
60
- // Example: log the creation of a new document.
61
- console.log(
62
- `afterCreate: Document created with ID ${documentId} and version ID ${documentVersionId} in collection ${collectionPath}`
63
- )
64
- },
65
- beforeUpdate: async ({ data, originalData, collectionPath }) => {
66
- // Example: prevent a document from being published if it doesn't have a title.
67
- console.log(
68
- `beforeUpdate: Updating a document in collection ${collectionPath} with data:`,
69
- data
70
- )
71
- },
72
- afterUpdate: async ({ data, originalData, collectionPath, documentId, documentVersionId }) => {
73
- // Example: log the update of a document.
74
- console.log(
75
- `afterUpdate: Document with ID ${documentId} and version ID ${documentVersionId} in collection ${collectionPath} was updated`
76
- )
77
- },
78
- beforeStatusChange: async ({
79
- documentId,
80
- documentVersionId,
81
- collectionPath,
82
- previousStatus,
83
- nextStatus,
84
- }) => {
85
- console.log(
86
- `beforeStatusChange: Changing status of document in collection ${collectionPath} from ${previousStatus} to ${nextStatus} with document ID ${documentId} and version ID ${documentVersionId}`
87
- )
88
- },
89
- afterStatusChange: async ({
90
- documentId,
91
- documentVersionId,
92
- collectionPath,
93
- previousStatus,
94
- nextStatus,
95
- }) => {
96
- console.log(
97
- `afterStatusChange: Status of document in collection ${collectionPath} changed from ${previousStatus} to ${nextStatus} with document ID ${documentId} and version ID ${documentVersionId}`
98
- )
99
- },
100
- beforeUnpublish: async ({ documentId, collectionPath }) => {
101
- console.log(
102
- `beforeUnpublish: Unpublishing document in collection ${collectionPath} with document ID ${documentId}.`
103
- )
104
- },
105
- afterUnpublish: async ({ documentId, collectionPath }) => {
106
- console.log(
107
- `afterUnpublish: Document in collection ${collectionPath} with document ID ${documentId} unpublished.`
108
- )
109
- },
110
- beforeDelete: async ({ documentId, collectionPath }) => {
111
- console.log(
112
- `beforeDelete: Deleting document in collection ${collectionPath} with document ID ${documentId}.`
113
- )
114
- },
115
- afterDelete: async ({ documentId, collectionPath }) => {
116
- console.log(
117
- `afterDelete: Document in collection ${collectionPath} with document ID ${documentId} deleted.`
118
- )
119
- },
120
- // Fires on any structural tree change (place / reorder / re-parent /
121
- // promote-on-delete) for a `tree: true` collection. The payload carries the
122
- // affected set; a docs site typically invalidates its nav / breadcrumb /
123
- // prev-next caches here.
124
- afterTreeChange: async ({ collectionPath }) => {
125
- console.log(`afterTreeChange: tree structure changed in collection ${collectionPath}.`)
126
- },
127
- },
56
+ // Collection lifecycle hooks. Hook *bodies* only ever run server-side, but
57
+ // a collection *schema* is isomorphic Byline bundles it into the browser
58
+ // admin too (the admin reads field config from it), so anything the schema
59
+ // *statically imports* ships to the client. Search indexing needs a
60
+ // server-only import (`getSystemBylineClient` from
61
+ // `@byline/host-tanstack-start`), which would crash the browser bundle if
62
+ // imported here. The **loader form** a thunk that dynamically imports a
63
+ // sibling module — keeps `./hooks.ts` and its entire import graph out of
64
+ // the client bundle while running exactly like inline hooks on the server.
65
+ // See ./hooks.ts for the worked example and the full explanation.
66
+ hooks: () => import('./hooks.js'),
128
67
  fields: [
129
68
  {
130
69
  name: 'title',
@@ -97,9 +97,9 @@ export const MediaAdmin: CollectionAdminConfig = defineAdmin(Media, {
97
97
  * picker modal (opened from a `relation` field widget). Omit to fall back to a
98
98
  * single-line render of `useAsTitle` + `path`.
99
99
  *
100
- * Shape matches `ColumnDefinition` so formatters can be reused across list and picker.
100
+ * Shape matches `ColumnDefinition` so formatters can be reused across list and item view.
101
101
  */
102
- picker: pickerViewColumns,
102
+ itemView: pickerViewColumns,
103
103
 
104
104
  /**
105
105
  * Custom list-view component that completely replaces the default table-based `ListView`
@@ -81,12 +81,12 @@ export const NewsAdmin: CollectionAdminConfig = defineAdmin(News, {
81
81
  * picker modal (opened from a `relation` field widget). Omit to fall back to a
82
82
  * single-line render of `useAsTitle` + `path`.
83
83
  *
84
- * Shape matches `ColumnDefinition` so formatters can be reused across list and picker.
84
+ * Shape matches `ColumnDefinition` so formatters can be reused across list and item view.
85
85
  *
86
86
  * @example
87
- * picker: [{ fieldName: 'title', label: 'Title' }] // or defined above as `pickerViewColumns`
87
+ * itemView: [{ fieldName: 'title', label: 'Title' }] // or defined above as `pickerViewColumns`
88
88
  */
89
- // picker: [],
89
+ // itemView: [],
90
90
 
91
91
  /**
92
92
  * Custom list-view component that completely replaces the default table-based `ListView`
@@ -74,5 +74,5 @@ const pickerViewColumns: ColumnDefinition[] = [
74
74
 
75
75
  export const NewsCategoriesAdmin: CollectionAdminConfig = defineAdmin(NewsCategories, {
76
76
  columns: listViewColumns,
77
- picker: pickerViewColumns,
77
+ itemView: pickerViewColumns,
78
78
  })
@@ -78,12 +78,12 @@ export const PagesAdmin: CollectionAdminConfig = defineAdmin(Pages, {
78
78
  * picker modal (opened from a `relation` field widget). Omit to fall back to a
79
79
  * single-line render of `useAsTitle` + `path`.
80
80
  *
81
- * Shape matches `ColumnDefinition` so formatters can be reused across list and picker.
81
+ * Shape matches `ColumnDefinition` so formatters can be reused across list and item view.
82
82
  *
83
83
  * @example
84
- * picker: [{ fieldName: 'title', label: 'Title' }] // or defined above as `pickerViewColumns`
84
+ * itemView: [{ fieldName: 'title', label: 'Title' }] // or defined above as `pickerViewColumns`
85
85
  */
86
- // picker: [],
86
+ // itemView: [],
87
87
 
88
88
  /**
89
89
  * Custom list-view component that completely replaces the default table-based `ListView`
@@ -24,7 +24,9 @@ import {
24
24
  lexicalEditorEmbedServer,
25
25
  lexicalEditorPopulateServer,
26
26
  lexicalEditorToMarkdownServer,
27
+ lexicalEditorToTextServer,
27
28
  } from '@byline/richtext-lexical/server'
29
+ import { migrate, postgresSearch } from '@byline/search-postgres'
28
30
  import { localStorageProvider } from '@byline/storage-local'
29
31
 
30
32
  // Import collection definitions directly from schema files — NOT the full
@@ -103,6 +105,24 @@ async function buildBylineCore(): Promise<BylineCore<AdminStore>> {
103
105
  // : undefined,
104
106
  })
105
107
 
108
+ // Ensure the search-index schema before the provider serves any traffic.
109
+ // `@byline/search-postgres` owns its schema (numbered SQL inside the
110
+ // package) and applies it via `migrate(pool)` — it is NOT part of the
111
+ // host's Drizzle migration stream. We run it deliberately here, rather
112
+ // than relying on the driver's `autoMigrate` option, so startup DDL is an
113
+ // explicit, awaited step. Reuses the adapter's pool — no second
114
+ // connection. See docs/05-reading-and-delivery/07-search.md.
115
+ //
116
+ // Wrapped defensively: a migration failure degrades search but must not
117
+ // take down the whole app at boot. We log loudly and continue. (For
118
+ // locked-down Postgres where the app role can't run DDL, drop this call and
119
+ // apply `migrations/0001_init.sql` by hand as a deploy step instead.)
120
+ try {
121
+ await migrate(db.pool, { log: (m) => console.log(m) })
122
+ } catch (err) {
123
+ console.error('[search-postgres] migrate failed — search may be unavailable:', err)
124
+ }
125
+
106
126
  const adminStore = createAdminStore(db.drizzle)
107
127
 
108
128
  // Built-in JWT session provider. Signing secret comes from the
@@ -211,8 +231,21 @@ async function buildBylineCore(): Promise<BylineCore<AdminStore>> {
211
231
  // surface (`.md` content routes, `llms.txt`). Pure JSON walk — no
212
232
  // client needed. Remove if you don't expose the markdown surface.
213
233
  toMarkdown: lexicalEditorToMarkdownServer(),
234
+ // Plain-text extractor for search indexing — flattens rich-text
235
+ // `body` fields to indexable text for `buildSearchDocument`. Pure
236
+ // JSON walk, no client. Required by any collection that lists a
237
+ // richText field in its `search.body`. See
238
+ // docs/05-reading-and-delivery/07-search.md.
239
+ toText: lexicalEditorToTextServer(),
214
240
  },
215
241
  },
242
+ // Built-in Postgres full-text search provider. Reuses the adapter's pool
243
+ // (no second connection); the search index lives in the same database.
244
+ // Collections opt in via their `search` config (see
245
+ // collections/docs/schema.ts) and keep the index live via lifecycle
246
+ // hooks (see collections/docs/hooks.ts). Remove this registration if no
247
+ // collection declares a `search` config.
248
+ search: postgresSearch({ pool: db.pool, defaultLocale: i18n.content.defaultLocale }),
216
249
  })
217
250
 
218
251
  // Register admin-subsystem abilities (admin.users.*, admin.roles.*) on
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@byline/cli",
3
3
  "private": false,
4
4
  "license": "MPL-2.0",
5
- "version": "3.14.0",
5
+ "version": "3.15.1",
6
6
  "engines": {
7
7
  "node": ">=20.9.0"
8
8
  },