@byline/cli 4.9.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.
Files changed (183) hide show
  1. package/README.md +56 -15
  2. package/dist/cli.js +16 -0
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/doctor.d.ts +8 -0
  5. package/dist/commands/doctor.d.ts.map +1 -1
  6. package/dist/commands/doctor.js +8 -5
  7. package/dist/commands/doctor.js.map +1 -1
  8. package/dist/commands/init.d.ts +2 -1
  9. package/dist/commands/init.d.ts.map +1 -1
  10. package/dist/commands/init.js.map +1 -1
  11. package/dist/commands/setup-checks.d.ts +2 -0
  12. package/dist/commands/setup-checks.d.ts.map +1 -1
  13. package/dist/commands/setup-checks.js +14 -6
  14. package/dist/commands/setup-checks.js.map +1 -1
  15. package/dist/commands/setup-flow.test.d.ts +2 -0
  16. package/dist/commands/setup-flow.test.d.ts.map +1 -0
  17. package/dist/commands/setup-flow.test.js +230 -0
  18. package/dist/commands/setup-flow.test.js.map +1 -0
  19. package/dist/commands/setup.d.ts +30 -1
  20. package/dist/commands/setup.d.ts.map +1 -1
  21. package/dist/commands/setup.js +71 -35
  22. package/dist/commands/setup.js.map +1 -1
  23. package/dist/context.d.ts +4 -1
  24. package/dist/context.d.ts.map +1 -1
  25. package/dist/context.js +3 -0
  26. package/dist/context.js.map +1 -1
  27. package/dist/lib/baseline-drift.test.d.ts +2 -0
  28. package/dist/lib/baseline-drift.test.d.ts.map +1 -0
  29. package/dist/lib/baseline-drift.test.js +97 -0
  30. package/dist/lib/baseline-drift.test.js.map +1 -0
  31. package/dist/lib/baseline.d.ts +10 -0
  32. package/dist/lib/baseline.d.ts.map +1 -0
  33. package/dist/lib/baseline.js +12 -0
  34. package/dist/lib/baseline.js.map +1 -0
  35. package/dist/lib/database/adapters.d.ts +54 -0
  36. package/dist/lib/database/adapters.d.ts.map +1 -0
  37. package/dist/lib/database/adapters.js +83 -0
  38. package/dist/lib/database/adapters.js.map +1 -0
  39. package/dist/lib/database/adapters.test.d.ts +2 -0
  40. package/dist/lib/database/adapters.test.d.ts.map +1 -0
  41. package/dist/lib/database/adapters.test.js +36 -0
  42. package/dist/lib/database/adapters.test.js.map +1 -0
  43. package/dist/lib/database/dialect.d.ts +12 -0
  44. package/dist/lib/database/dialect.d.ts.map +1 -0
  45. package/dist/lib/database/dialect.js +53 -0
  46. package/dist/lib/database/dialect.js.map +1 -0
  47. package/dist/lib/database/dialect.test.d.ts +2 -0
  48. package/dist/lib/database/dialect.test.d.ts.map +1 -0
  49. package/dist/lib/database/dialect.test.js +58 -0
  50. package/dist/lib/database/dialect.test.js.map +1 -0
  51. package/dist/lib/database/mysql.d.ts +21 -0
  52. package/dist/lib/database/mysql.d.ts.map +1 -0
  53. package/dist/lib/database/mysql.js +152 -0
  54. package/dist/lib/database/mysql.js.map +1 -0
  55. package/dist/lib/database/mysql.test.d.ts +2 -0
  56. package/dist/lib/database/mysql.test.d.ts.map +1 -0
  57. package/dist/lib/database/mysql.test.js +45 -0
  58. package/dist/lib/database/mysql.test.js.map +1 -0
  59. package/dist/lib/database/postgres.d.ts +8 -0
  60. package/dist/lib/database/postgres.d.ts.map +1 -0
  61. package/dist/lib/database/postgres.js +142 -0
  62. package/dist/lib/database/postgres.js.map +1 -0
  63. package/dist/lib/database/postgres.test.d.ts +2 -0
  64. package/dist/lib/database/postgres.test.d.ts.map +1 -0
  65. package/dist/lib/database/postgres.test.js +14 -0
  66. package/dist/lib/database/postgres.test.js.map +1 -0
  67. package/dist/lib/database/provisioner.d.ts +35 -0
  68. package/dist/lib/database/provisioner.d.ts.map +1 -0
  69. package/dist/lib/database/provisioner.js +20 -0
  70. package/dist/lib/database/provisioner.js.map +1 -0
  71. package/dist/lib/database/provisioner.test.d.ts +2 -0
  72. package/dist/lib/database/provisioner.test.d.ts.map +1 -0
  73. package/dist/lib/database/provisioner.test.js +32 -0
  74. package/dist/lib/database/provisioner.test.js.map +1 -0
  75. package/dist/lib/database/selection.d.ts +11 -0
  76. package/dist/lib/database/selection.d.ts.map +1 -0
  77. package/dist/lib/database/selection.js +52 -0
  78. package/dist/lib/database/selection.js.map +1 -0
  79. package/dist/lib/database/selection.test.d.ts +2 -0
  80. package/dist/lib/database/selection.test.d.ts.map +1 -0
  81. package/dist/lib/database/selection.test.js +58 -0
  82. package/dist/lib/database/selection.test.js.map +1 -0
  83. package/dist/lib/database/state.d.ts +4 -0
  84. package/dist/lib/database/state.d.ts.map +1 -0
  85. package/dist/lib/database/state.js +10 -0
  86. package/dist/lib/database/state.js.map +1 -0
  87. package/dist/lib/database/state.test.d.ts +2 -0
  88. package/dist/lib/database/state.test.d.ts.map +1 -0
  89. package/dist/lib/database/state.test.js +17 -0
  90. package/dist/lib/database/state.test.js.map +1 -0
  91. package/dist/lib/database/urls.d.ts +19 -0
  92. package/dist/lib/database/urls.d.ts.map +1 -0
  93. package/dist/lib/database/urls.js +14 -0
  94. package/dist/lib/database/urls.js.map +1 -0
  95. package/dist/lib/database/urls.test.d.ts +2 -0
  96. package/dist/lib/database/urls.test.d.ts.map +1 -0
  97. package/dist/lib/database/urls.test.js +31 -0
  98. package/dist/lib/database/urls.test.js.map +1 -0
  99. package/dist/lib/dependency-version.d.ts.map +1 -1
  100. package/dist/lib/dependency-version.js +5 -4
  101. package/dist/lib/dependency-version.js.map +1 -1
  102. package/dist/lib/template-hook-boundary.test.node.js +9 -4
  103. package/dist/lib/template-hook-boundary.test.node.js.map +1 -1
  104. package/dist/manifest/deps.d.ts +8 -1
  105. package/dist/manifest/deps.d.ts.map +1 -1
  106. package/dist/manifest/deps.js +40 -13
  107. package/dist/manifest/deps.js.map +1 -1
  108. package/dist/manifest/env.d.ts +4 -1
  109. package/dist/manifest/env.d.ts.map +1 -1
  110. package/dist/manifest/env.js +12 -6
  111. package/dist/manifest/env.js.map +1 -1
  112. package/dist/phases/db-init.d.ts +2 -1
  113. package/dist/phases/db-init.d.ts.map +1 -1
  114. package/dist/phases/db-init.js +141 -124
  115. package/dist/phases/db-init.js.map +1 -1
  116. package/dist/phases/db-init.test.d.ts +2 -0
  117. package/dist/phases/db-init.test.d.ts.map +1 -0
  118. package/dist/phases/db-init.test.js +144 -0
  119. package/dist/phases/db-init.test.js.map +1 -0
  120. package/dist/phases/db.d.ts +0 -8
  121. package/dist/phases/db.d.ts.map +1 -1
  122. package/dist/phases/db.js +69 -49
  123. package/dist/phases/db.js.map +1 -1
  124. package/dist/phases/db.test.d.ts +2 -0
  125. package/dist/phases/db.test.d.ts.map +1 -0
  126. package/dist/phases/db.test.js +69 -0
  127. package/dist/phases/db.test.js.map +1 -0
  128. package/dist/phases/deps.d.ts.map +1 -1
  129. package/dist/phases/deps.js +7 -4
  130. package/dist/phases/deps.js.map +1 -1
  131. package/dist/phases/deps.test.js +67 -5
  132. package/dist/phases/deps.test.js.map +1 -1
  133. package/dist/phases/env.d.ts.map +1 -1
  134. package/dist/phases/env.js +30 -21
  135. package/dist/phases/env.js.map +1 -1
  136. package/dist/phases/env.test.d.ts +2 -0
  137. package/dist/phases/env.test.d.ts.map +1 -0
  138. package/dist/phases/env.test.js +79 -0
  139. package/dist/phases/env.test.js.map +1 -0
  140. package/dist/phases/scaffold-smoke.test.js +44 -10
  141. package/dist/phases/scaffold-smoke.test.js.map +1 -1
  142. package/dist/phases/scaffold.d.ts.map +1 -1
  143. package/dist/phases/scaffold.js +32 -23
  144. package/dist/phases/scaffold.js.map +1 -1
  145. package/dist/phases/scaffold.test.js +22 -0
  146. package/dist/phases/scaffold.test.js.map +1 -1
  147. package/dist/phases/wire/vite-config.d.ts.map +1 -1
  148. package/dist/phases/wire/vite-config.js +2 -0
  149. package/dist/phases/wire/vite-config.js.map +1 -1
  150. package/dist/phases/wire/vite-config.test.js +25 -2
  151. package/dist/phases/wire/vite-config.test.js.map +1 -1
  152. package/dist/state.d.ts +6 -0
  153. package/dist/state.d.ts.map +1 -1
  154. package/dist/state.js +40 -4
  155. package/dist/state.js.map +1 -1
  156. package/dist/state.test.d.ts +2 -0
  157. package/dist/state.test.d.ts.map +1 -0
  158. package/dist/state.test.js +77 -0
  159. package/dist/state.test.js.map +1 -0
  160. package/dist/templates/byline-examples/collections/docs/schema.ts +7 -7
  161. package/dist/templates/byline-examples/collections/media/schema.ts +6 -6
  162. package/dist/templates/byline-examples/collections/news/schema.ts +6 -6
  163. package/dist/templates/byline-examples/collections/news-categories/schema.ts +6 -6
  164. package/dist/templates/byline-examples/collections/pages/schema.ts +6 -6
  165. package/dist/templates/dialects/mysql/byline/server.config.ts +105 -0
  166. package/dist/templates/dialects/mysql/byline-examples/server.config.ts +115 -0
  167. package/dist/templates/host/vite.config.ts +4 -3
  168. package/dist/templates/migrations/mysql/0000_unknown_kulan_gath.sql +412 -0
  169. package/dist/templates/migrations/mysql/meta/_journal.json +13 -0
  170. package/dist/test-helpers.d.ts +14 -2
  171. package/dist/test-helpers.d.ts.map +1 -1
  172. package/dist/test-helpers.js +12 -9
  173. package/dist/test-helpers.js.map +1 -1
  174. package/dist/types.d.ts +3 -0
  175. package/dist/types.d.ts.map +1 -1
  176. package/package.json +7 -2
  177. package/dist/templates/migrations/meta/0000_snapshot.json +0 -3471
  178. /package/dist/templates/{byline → dialects/postgres/byline}/server.config.ts +0 -0
  179. /package/dist/templates/{byline-examples → dialects/postgres/byline-examples}/scripts/backfill-version-locales.ts +0 -0
  180. /package/dist/templates/{byline-examples → dialects/postgres/byline-examples}/scripts/re-anchor.ts +0 -0
  181. /package/dist/templates/{byline-examples → dialects/postgres/byline-examples}/server.config.ts +0 -0
  182. /package/dist/templates/migrations/{0000_mushy_luckman.sql → postgres/0000_mushy_luckman.sql} +0 -0
  183. /package/dist/templates/migrations/{meta → postgres/meta}/_journal.json +0 -0
@@ -42,19 +42,19 @@ export const Docs = defineCollection({
42
42
  // byline/scripts/import-docs.ts --tree).
43
43
  tree: true,
44
44
  // Admin list-view quick-search. The collection list route's search box
45
- // matches these top-level text-store fields with substring queries (ILIKE, in
46
- // the Postgres adapter) against `store_text` — a lightweight "find the row I
47
- // mean" affordance that needs no indexing and no lifecycle hooks. Falls back
48
- // to `useAsTitle` when omitted; declared explicitly here for guidance.
45
+ // matches these top-level text-store fields with adapter-native substring
46
+ // queries against `store_text` — a lightweight "find the row I mean"
47
+ // affordance that needs no indexing and no lifecycle hooks. Falls back to
48
+ // `useAsTitle` when omitted; declared explicitly here for guidance.
49
49
  listSearch: ['title'],
50
50
  // Search-*provider* config (docs/06-search/index.md) —
51
51
  // distinct from `listSearch` above. Each key names the part a field plays in
52
52
  // the index: `body` fields feed the full-text search vector (`title` is
53
53
  // display-only unless listed here, so we include it and boost it into the
54
54
  // heaviest weight class); `facets`, `filters`, and `zones` round out the
55
- // surface. This drives the pluggable SearchProvider (full-text search in
56
- // @byline/search-postgres) and requires a `search` provider registered in
57
- // byline/server.config.ts. Unlike `listSearch`, `search` is inert on its own
55
+ // surface. This drives the configured full-text SearchProvider and requires
56
+ // a `search` provider registered in byline/server.config.ts. Unlike
57
+ // `listSearch`, `search` is inert on its own
58
58
  // — opting in here is what makes the index / reindex / deindex lifecycle
59
59
  // hooks in ./hooks.ts keep this collection's provider index in sync.
60
60
  // `content` is a container (blocks) field: naming it walks every text-bearing
@@ -51,14 +51,14 @@ export const Media = defineCollection({
51
51
  }),
52
52
  showStats: true,
53
53
  // Admin list-view quick-search. The collection list route's search box
54
- // matches these top-level text-store fields with substring queries (ILIKE, in
55
- // the Postgres adapter) against `store_text` — a lightweight "find the row I
56
- // mean" affordance that needs no indexing and no lifecycle hooks. Falls back
57
- // to `useAsTitle` when omitted; declared explicitly here for guidance.
54
+ // matches these top-level text-store fields with adapter-native substring
55
+ // queries against `store_text` — a lightweight "find the row I mean"
56
+ // affordance that needs no indexing and no lifecycle hooks. Falls back to
57
+ // `useAsTitle` when omitted; declared explicitly here for guidance.
58
58
  listSearch: ['title'],
59
59
  // Search-*provider* config (docs/06-search/index.md) —
60
- // distinct from `listSearch` above. This drives the pluggable SearchProvider
61
- // (full-text search in @byline/search-postgres) and offers richer options:
60
+ // distinct from `listSearch` above. This drives the configured full-text
61
+ // SearchProvider and offers richer options:
62
62
  // per-field weighting/`boost`, `facets`, `filters`, and `zones`. Unlike
63
63
  // `listSearch`, `search` is inert on its own — it MUST be paired with index /
64
64
  // reindex / deindex document-lifecycle hooks that keep the provider index in
@@ -31,14 +31,14 @@ export const News = defineCollection({
31
31
  }),
32
32
  showStats: true,
33
33
  // Admin list-view quick-search. The collection list route's search box
34
- // matches these top-level text-store fields with substring queries (ILIKE, in
35
- // the Postgres adapter) against `store_text` — a lightweight "find the row I
36
- // mean" affordance that needs no indexing and no lifecycle hooks. Falls back
37
- // to `useAsTitle` when omitted; declared explicitly here for guidance.
34
+ // matches these top-level text-store fields with adapter-native substring
35
+ // queries against `store_text` — a lightweight "find the row I mean"
36
+ // affordance that needs no indexing and no lifecycle hooks. Falls back to
37
+ // `useAsTitle` when omitted; declared explicitly here for guidance.
38
38
  listSearch: ['title'],
39
39
  // Search-*provider* config (docs/06-search/index.md) —
40
- // distinct from `listSearch` above. This drives the pluggable SearchProvider
41
- // (full-text search in @byline/search-postgres) and offers richer options:
40
+ // distinct from `listSearch` above. This drives the configured full-text
41
+ // SearchProvider and offers richer options:
42
42
  // per-field weighting/`boost`, `facets`, `filters`, and `zones`. Unlike
43
43
  // `listSearch`, `search` is inert on its own — it MUST be paired with index /
44
44
  // reindex / deindex document-lifecycle hooks that keep the provider index in
@@ -22,14 +22,14 @@ export const NewsCategories = defineCollection({
22
22
  workflow: SINGLE_STATUS_WORKFLOW,
23
23
  showStats: true,
24
24
  // Admin list-view quick-search. The collection list route's search box
25
- // matches these top-level text-store fields with substring queries (ILIKE, in
26
- // the Postgres adapter) against `store_text` — a lightweight "find the row I
27
- // mean" affordance that needs no indexing and no lifecycle hooks. Falls back
28
- // to `useAsTitle` when omitted; declared explicitly here for guidance.
25
+ // matches these top-level text-store fields with adapter-native substring
26
+ // queries against `store_text` — a lightweight "find the row I mean"
27
+ // affordance that needs no indexing and no lifecycle hooks. Falls back to
28
+ // `useAsTitle` when omitted; declared explicitly here for guidance.
29
29
  listSearch: ['name'],
30
30
  // Search-*provider* config (docs/06-search/index.md) —
31
- // distinct from `listSearch` above. This drives the pluggable SearchProvider
32
- // (full-text search in @byline/search-postgres) and offers richer options:
31
+ // distinct from `listSearch` above. This drives the configured full-text
32
+ // SearchProvider and offers richer options:
33
33
  // per-field weighting/`boost`, `facets`, `filters`, and `zones`. Unlike
34
34
  // `listSearch`, `search` is inert on its own — it MUST be paired with index /
35
35
  // reindex / deindex document-lifecycle hooks that keep the provider index in
@@ -33,14 +33,14 @@ export const Pages = defineCollection({
33
33
  }),
34
34
  showStats: true,
35
35
  // Admin list-view quick-search. The collection list route's search box
36
- // matches these top-level text-store fields with substring queries (ILIKE, in
37
- // the Postgres adapter) against `store_text` — a lightweight "find the row I
38
- // mean" affordance that needs no indexing and no lifecycle hooks. Falls back
39
- // to `useAsTitle` when omitted; declared explicitly here for guidance.
36
+ // matches these top-level text-store fields with adapter-native substring
37
+ // queries against `store_text` — a lightweight "find the row I mean"
38
+ // affordance that needs no indexing and no lifecycle hooks. Falls back to
39
+ // `useAsTitle` when omitted; declared explicitly here for guidance.
40
40
  listSearch: ['title'],
41
41
  // Search-*provider* config (docs/06-search/index.md) —
42
- // distinct from `listSearch` above. This drives the pluggable SearchProvider
43
- // (full-text search in @byline/search-postgres) and offers richer options:
42
+ // distinct from `listSearch` above. This drives the configured full-text
43
+ // SearchProvider and offers richer options:
44
44
  // per-field weighting/`boost`, `facets`, `filters`, and `zones`. Unlike
45
45
  // `listSearch`, `search` is inert on its own — it MUST be paired with index /
46
46
  // reindex / deindex document-lifecycle hooks that keep the provider index in
@@ -0,0 +1,105 @@
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-side Byline bootstrap. Imported as a side-effect from
11
+ * `src/server.ts` and from scripts that need the configured runtime.
12
+ *
13
+ * This is the minimal scaffold: no example collections or search provider.
14
+ */
15
+
16
+ import { type AdminStore, registerAdminAbilities } from '@byline/admin'
17
+ import { JwtSessionProvider } from '@byline/admin/auth'
18
+ import { getAdminBylineClient } from '@byline/client/server'
19
+ import { type BylineCore, initBylineCore } from '@byline/core'
20
+ import { mysqlAdapter } from '@byline/db-mysql'
21
+ import { createAdminStore } from '@byline/db-mysql/admin'
22
+ import { registerTanstackStartHostBridge } from '@byline/host-tanstack-start/integrations/host-bridge'
23
+ import {
24
+ lexicalEditorEmbedServer,
25
+ lexicalEditorPopulateServer,
26
+ lexicalEditorToMarkdownServer,
27
+ } from '@byline/richtext-lexical/server'
28
+ import { localStorageProvider } from '@byline/storage-local'
29
+
30
+ import { collections } from './collections/index.js'
31
+ import { serverHooks } from './collections/server-hooks.js'
32
+ import { i18n } from './i18n.js'
33
+ import { DEFAULT_SERVER_URL, routes } from './routes.js'
34
+
35
+ const serverURL = process.env.VITE_SERVER_URL || DEFAULT_SERVER_URL
36
+
37
+ // HMR-safe singleton. Reusing the resolving Promise prevents development
38
+ // reloads from orphaning MySQL pools and exhausting server connections.
39
+ declare global {
40
+ // biome-ignore lint: globalThis augmentation requires `var` rather than `let`
41
+ var __bylineCoreSingleton__: Promise<BylineCore<AdminStore>> | undefined
42
+ }
43
+
44
+ async function buildBylineCore(): Promise<BylineCore<AdminStore>> {
45
+ const db = mysqlAdapter({
46
+ connectionString: process.env.BYLINE_DB_MYSQL_CONNECTION_STRING || '',
47
+ collections,
48
+ defaultContentLocale: i18n.content.defaultLocale,
49
+ // Optional pool tuning. Omit these values to use the adapter defaults:
50
+ // 20 connections, 2s idle timeout, and 30s connection timeout.
51
+ connectionLimit: process.env.BYLINE_DB_MYSQL_CONNECTION_LIMIT
52
+ ? Number(process.env.BYLINE_DB_MYSQL_CONNECTION_LIMIT)
53
+ : undefined,
54
+ idleTimeout: process.env.BYLINE_DB_MYSQL_IDLE_TIMEOUT_MILLIS
55
+ ? Number(process.env.BYLINE_DB_MYSQL_IDLE_TIMEOUT_MILLIS)
56
+ : undefined,
57
+ connectTimeout: process.env.BYLINE_DB_MYSQL_CONNECTION_TIMEOUT_MILLIS
58
+ ? Number(process.env.BYLINE_DB_MYSQL_CONNECTION_TIMEOUT_MILLIS)
59
+ : undefined,
60
+ })
61
+
62
+ const adminStore = createAdminStore(db.drizzle)
63
+ const signingSecret = process.env.BYLINE_JWT_SECRET
64
+ if (!signingSecret || signingSecret.length < 32) {
65
+ throw new Error(
66
+ 'BYLINE_JWT_SECRET must be set and carry at least 32 bytes of entropy. ' +
67
+ 'Generate one with `openssl rand -base64 48` and add it to your .env.local.'
68
+ )
69
+ }
70
+
71
+ const sessionProvider = new JwtSessionProvider({
72
+ store: adminStore,
73
+ signingSecret,
74
+ })
75
+
76
+ registerTanstackStartHostBridge()
77
+
78
+ const core = await initBylineCore<AdminStore>({
79
+ serverURL,
80
+ i18n,
81
+ routes,
82
+ collections,
83
+ hooks: serverHooks,
84
+ db,
85
+ adminStore,
86
+ storage: localStorageProvider({
87
+ uploadDir: './uploads',
88
+ baseUrl: '/uploads',
89
+ }),
90
+ sessionProvider,
91
+ fields: {
92
+ richText: {
93
+ embed: lexicalEditorEmbedServer({ getClient: getAdminBylineClient }),
94
+ populate: lexicalEditorPopulateServer({ getClient: getAdminBylineClient }),
95
+ toMarkdown: lexicalEditorToMarkdownServer(),
96
+ },
97
+ },
98
+ })
99
+
100
+ registerAdminAbilities(core.abilities)
101
+ return core
102
+ }
103
+
104
+ globalThis.__bylineCoreSingleton__ ??= buildBylineCore()
105
+ await globalThis.__bylineCoreSingleton__
@@ -0,0 +1,115 @@
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-side Byline bootstrap for the MySQL example scaffold.
11
+ */
12
+
13
+ import { type AdminStore, registerAdminAbilities } from '@byline/admin'
14
+ import { JwtSessionProvider } from '@byline/admin/auth'
15
+ import { getAdminBylineClient } from '@byline/client/server'
16
+ import { type BylineCore, initBylineCore } from '@byline/core'
17
+ import { mysqlAdapter } from '@byline/db-mysql'
18
+ import { createAdminStore } from '@byline/db-mysql/admin'
19
+ import { registerTanstackStartHostBridge } from '@byline/host-tanstack-start/integrations/host-bridge'
20
+ import {
21
+ lexicalEditorEmbedServer,
22
+ lexicalEditorPopulateServer,
23
+ lexicalEditorToMarkdownServer,
24
+ lexicalEditorToTextServer,
25
+ } from '@byline/richtext-lexical/server'
26
+ import { migrate, mysqlSearch } from '@byline/search-mysql'
27
+ import { localStorageProvider } from '@byline/storage-local'
28
+
29
+ import { collections } from './collections/index.js'
30
+ import { serverHooks } from './collections/server-hooks.js'
31
+ import { i18n } from './i18n.js'
32
+ import { DEFAULT_SERVER_URL, routes } from './routes.js'
33
+
34
+ const serverURL = process.env.VITE_SERVER_URL || DEFAULT_SERVER_URL
35
+
36
+ // HMR-safe singleton. Reusing the resolving Promise prevents development
37
+ // reloads from orphaning MySQL pools and exhausting server connections.
38
+ declare global {
39
+ // biome-ignore lint: globalThis augmentation requires `var` rather than `let`
40
+ var __bylineCoreSingleton__: Promise<BylineCore<AdminStore>> | undefined
41
+ }
42
+
43
+ async function buildBylineCore(): Promise<BylineCore<AdminStore>> {
44
+ const db = mysqlAdapter({
45
+ connectionString: process.env.BYLINE_DB_MYSQL_CONNECTION_STRING || '',
46
+ collections,
47
+ defaultContentLocale: i18n.content.defaultLocale,
48
+ // Optional pool tuning. Omit these values to use the adapter defaults:
49
+ // 20 connections, 2s idle timeout, and 30s connection timeout.
50
+ connectionLimit: process.env.BYLINE_DB_MYSQL_CONNECTION_LIMIT
51
+ ? Number(process.env.BYLINE_DB_MYSQL_CONNECTION_LIMIT)
52
+ : undefined,
53
+ idleTimeout: process.env.BYLINE_DB_MYSQL_IDLE_TIMEOUT_MILLIS
54
+ ? Number(process.env.BYLINE_DB_MYSQL_IDLE_TIMEOUT_MILLIS)
55
+ : undefined,
56
+ connectTimeout: process.env.BYLINE_DB_MYSQL_CONNECTION_TIMEOUT_MILLIS
57
+ ? Number(process.env.BYLINE_DB_MYSQL_CONNECTION_TIMEOUT_MILLIS)
58
+ : undefined,
59
+ })
60
+
61
+ // Search owns its disposable projection and numbered migration stream.
62
+ // Apply it explicitly at startup with the adapter's existing pool. A
63
+ // migration failure degrades search without taking down the application.
64
+ try {
65
+ await migrate(db.pool, { log: (message) => console.log(message) })
66
+ } catch (error) {
67
+ console.error('[search-mysql] migrate failed — search may be unavailable:', error)
68
+ }
69
+
70
+ const adminStore = createAdminStore(db.drizzle)
71
+ const signingSecret = process.env.BYLINE_JWT_SECRET
72
+ if (!signingSecret || signingSecret.length < 32) {
73
+ throw new Error(
74
+ 'BYLINE_JWT_SECRET must be set and carry at least 32 bytes of entropy. ' +
75
+ 'Generate one with `openssl rand -base64 48` and add it to your .env.local.'
76
+ )
77
+ }
78
+
79
+ const sessionProvider = new JwtSessionProvider({
80
+ store: adminStore,
81
+ signingSecret,
82
+ })
83
+
84
+ registerTanstackStartHostBridge()
85
+
86
+ const core = await initBylineCore<AdminStore>({
87
+ serverURL,
88
+ i18n,
89
+ routes,
90
+ collections,
91
+ hooks: serverHooks,
92
+ db,
93
+ adminStore,
94
+ storage: localStorageProvider({
95
+ uploadDir: './uploads',
96
+ baseUrl: '/uploads',
97
+ }),
98
+ sessionProvider,
99
+ fields: {
100
+ richText: {
101
+ embed: lexicalEditorEmbedServer({ getClient: getAdminBylineClient }),
102
+ populate: lexicalEditorPopulateServer({ getClient: getAdminBylineClient }),
103
+ toMarkdown: lexicalEditorToMarkdownServer(),
104
+ toText: lexicalEditorToTextServer(),
105
+ },
106
+ },
107
+ search: mysqlSearch({ pool: db.pool, defaultLocale: i18n.content.defaultLocale }),
108
+ })
109
+
110
+ registerAdminAbilities(core.abilities)
111
+ return core
112
+ }
113
+
114
+ globalThis.__bylineCoreSingleton__ ??= buildBylineCore()
115
+ await globalThis.__bylineCoreSingleton__
@@ -44,7 +44,7 @@ const bylineSsrNoExternal = [/^@byline\//]
44
44
  // kept external so Node resolves them at runtime instead.
45
45
  // - sharp + @byline/storage-local — image processing (libvips)
46
46
  // - @byline/storage-s3 — bundles the AWS SDK; keep external for Node resolution
47
- // - @byline/db-postgres — depends on `pg` native bindings
47
+ // - @byline/db-postgres + @byline/db-mysql — database drivers
48
48
  // - pino — CJS entrypoints don't execute under Vite's module runner
49
49
  //
50
50
  // `@byline/admin` is intentionally NOT externalised: it now hosts
@@ -53,7 +53,7 @@ const bylineSsrNoExternal = [/^@byline\//]
53
53
  // `import './foo_module.css'`. Node's ESM loader can't handle `.css`,
54
54
  // so admin must flow through Vite's SSR pipeline where the CSS plugin
55
55
  // resolves the side-effect imports. Server-only admin subpaths reach
56
- // db-postgres / storage adapters through composition at runtime, not
56
+ // database / storage adapters through composition at runtime, not
57
57
  // through `@byline/admin`'s own import graph, so this is safe.
58
58
  const ssrExternal = [
59
59
  'sharp',
@@ -61,6 +61,7 @@ const ssrExternal = [
61
61
  '@byline/storage-local',
62
62
  '@byline/storage-s3',
63
63
  '@byline/db-postgres',
64
+ '@byline/db-mysql',
64
65
  ]
65
66
 
66
67
  const config = defineConfig({
@@ -198,7 +199,7 @@ const config = defineConfig({
198
199
  'react-dom',
199
200
  'pino',
200
201
  'sharp',
201
- /^@byline\/(db-postgres|storage-local|storage-s3)/,
202
+ /^@byline\/(db-postgres|db-mysql|storage-local|storage-s3)/,
202
203
  ],
203
204
  },
204
205
  }),