@byline/db-mysql 4.8.0 → 4.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,14 +4,12 @@
4
4
  > exercised, not because MySQL is a fully supported Byline backend yet. The
5
5
  > storage layer is complete and proven — it passes the entire shared
6
6
  > `@byline/db-conformance` behavioural suite, the same suite `@byline/db-postgres`
7
- > passes, with identical results. What is missing is the surrounding ecosystem:
7
+ > passes, with identical results. Search is also implemented; the remaining
8
+ > limitations are in tooling and breadth of environment coverage:
8
9
  >
9
- > - **No search provider.** `@byline/search-postgres` has no MySQL counterpart, and
10
- > this is not merely a missing feature `initBylineCore()` **throws at boot** if
11
- > any collection declares `search` and no provider is registered. See
12
- > [Search on MySQL](#search-on-mysql) below for the one-step workaround you will
13
- > need. Tracked in
14
- > [#52](https://github.com/Byline-CMS/bylinecms.dev/issues/52).
10
+ > - **Search is available.** `@byline/search-mysql` implements the same
11
+ > provider contract and conformance suite as the built-in PostgreSQL search
12
+ > driver. It owns an independent, disposable migration stream.
15
13
  > - **No CLI support.** `byline init` scaffolds a Postgres installation; wiring
16
14
  > MySQL is a manual edit to your `server.config.ts`.
17
15
  > - **Narrower CI coverage than Postgres.** Continuous integration pins MySQL 8.0
@@ -19,8 +17,8 @@
19
17
  > runs under a non-UTC timezone. Tracked in
20
18
  > [#55](https://github.com/Byline-CMS/bylinecms.dev/issues/55).
21
19
  >
22
- > Treat it as suitable for evaluation, prototypes, and installations that do not
23
- > need search. The criteria for general availability are tracked in
20
+ > Treat it as suitable for evaluation, prototypes, and controlled installations.
21
+ > The criteria for general availability are tracked in
24
22
  > [#58](https://github.com/Byline-CMS/bylinecms.dev/issues/58).
25
23
 
26
24
  MySQL adapter for Byline CMS — Drizzle schema, migrations, and the storage /
@@ -198,12 +196,10 @@ databases, and a few divergences are real rather than papered over.
198
196
  are meaning-bearing in some scripts (a Thai tone mark, a Devanagari
199
197
  anusvara, Hebrew niqqud) are never silently folded together the way
200
198
  `ai_ci` would fold them.
201
- - **No search provider yet.** `@byline/search-postgres` has no MySQL
202
- counterpart today a collection's `search` config
203
- (`CollectionDefinition.search`) requires a registered `SearchProvider`,
204
- and none ships for MySQL yet. Track
205
- [the `@byline/search-mysql` follow-up issue](https://github.com/Byline-CMS/bylinecms.dev/issues/52)
206
- for status.
199
+ - **Search uses a dialect-specific provider.** Register
200
+ `@byline/search-mysql` with this adapter. It implements the same portable
201
+ lexical and weighting contract as `@byline/search-postgres`, while owning its
202
+ own MySQL `FULLTEXT` schema and migrations.
207
203
  - **The connection string is the only connection input.** See "Usage"
208
204
  above — this is called out here too because it is the most common way
209
205
  this adapter is misconfigured by a reader coming from the Postgres
@@ -218,56 +214,33 @@ databases, and a few divergences are real rather than papered over.
218
214
 
219
215
  ## Search on MySQL
220
216
 
221
- There is no MySQL search provider yet, and the absence is load-bearing rather
222
- than cosmetic: `validateSearchConfig` runs inside `initBylineCore()` and
223
- **throws** when any collection declares a `search` block but no provider is
224
- registered. Byline's own reference application opts five collections into
225
- search, so a MySQL installation that copies it fails at boot with an error that
226
- does not mention MySQL at all.
227
-
228
- Until `@byline/search-mysql` exists, register a no-op provider. It satisfies
229
- validation, and indexing and querying become silent no-ops rather than errors —
230
- the admin Reindex action and any search page return nothing, which is the honest
231
- answer on this adapter today:
217
+ Register `@byline/search-mysql` with the adapter's existing promise pool. Apply
218
+ the provider's independent migration stream before serving traffic:
232
219
 
233
220
  ```ts
234
- const noopSearch = {
235
- capabilities: {
236
- facets: false,
237
- typoTolerance: false,
238
- semantic: false,
239
- bm25: false,
240
- weighting: false,
241
- highlights: false,
242
- },
243
- async upsert() {},
244
- async remove() {},
245
- async search() {
246
- return { hits: [], total: 0 }
247
- },
248
- }
221
+ import { migrate, mysqlSearch } from '@byline/search-mysql'
222
+
223
+ await migrate(db.pool)
249
224
 
250
225
  await initBylineCore({
251
226
  // …
252
227
  db,
253
- search: noopSearch,
228
+ search: mysqlSearch({ pool: db.pool, defaultLocale }),
254
229
  })
255
230
  ```
256
231
 
257
- The alternative removing the `search` block from every collection definition —
258
- also works, but it discards configuration you will want back when a provider
259
- lands. The no-op keeps your collections declarative and confines the workaround
260
- to one place.
232
+ Search rows are a disposable projection of published versions. The provider
233
+ uses parser-safe portable terms, weighted MySQL `FULLTEXT` indexes, and analyzer
234
+ fingerprints. Its package README documents migrations, capabilities, and the
235
+ clear-and-rebuild procedure.
261
236
 
262
237
  ## Not yet shipped
263
238
 
264
- - **`@byline/search-mysql`** — see [Search on MySQL](#search-on-mysql) above.
265
- Tracked in [issue #52](https://github.com/Byline-CMS/bylinecms.dev/issues/52).
266
239
  - **CLI support.** `byline init` scaffolds a Postgres installation. Adding MySQL
267
240
  to an existing project is a manual edit to `byline/server.config.ts` — swap
268
241
  `pgAdapter` for `mysqlAdapter`, swap the `@byline/db-postgres/admin` import for
269
- `@byline/db-mysql/admin`, drop the `@byline/search-postgres` migrate call, and
270
- register the no-op search provider above.
242
+ `@byline/db-mysql/admin`, and swap `postgresSearch` for `mysqlSearch`. Both
243
+ search packages expose the same `migrate(pool)` shape.
271
244
  - **A MySQL storage-benchmark target.** The design spec flags view
272
245
  materialisation (the `ROW_NUMBER()` window inside a derived table, used
273
246
  by both current-version views) as a question to answer before this
@@ -20,7 +20,7 @@
20
20
  * to 1; bumped by write paths when needed).
21
21
  * - snake_case column names matching the rest of the Byline schema.
22
22
  *
23
- * See docs/06-auth-and-security/01-authn-authz.md for the full data model and present-state
23
+ * See docs/07-auth-and-security/01-authn-authz.md for the full data model and present-state
24
24
  * reference.
25
25
  */
26
26
  import { relations, sql } from 'drizzle-orm';
@@ -86,7 +86,7 @@ export const varcharCaseSensitive = customType({
86
86
  * same tick and receive an *identical* `CURRENT_TIMESTAMP(3)` value, which
87
87
  * is a real correctness gap for anything that orders or windows by these
88
88
  * columns (found via `packages/db-conformance`'s audit activity-feed
89
- * fixture — see docs/09-testing.md and the Task 11 report for the
89
+ * fixture — see docs/10-testing.md and the Task 11 report for the
90
90
  * live-server evidence: four rapid statements produced 2 distinct values at
91
91
  * fsp 3 versus 4 distinct values at fsp 6). Matching pg's precision closes
92
92
  * that gap at the source instead of asking every fixture/consumer to work
@@ -168,7 +168,7 @@ export const documentPaths = mysqlTable('byline_document_paths', {
168
168
  // carries forward across edits and survives restore. Surfaced on reads as
169
169
  // `availableLocales`; the public advertised set is the intersection with the
170
170
  // ledger's `_availableVersionLocales`. Replaced wholesale on write (the lifecycle
171
- // deletes then re-inserts the set), never appended. See docs/07-internationalization/index.md.
171
+ // deletes then re-inserts the set), never appended. See docs/08-internationalization/index.md.
172
172
  export const documentAvailableLocales = mysqlTable('byline_document_available_locales', {
173
173
  document_id: uuidChar('document_id').notNull(),
174
174
  locale: varchar('locale', { length: 10 }).notNull(),
@@ -198,7 +198,7 @@ export const documentAvailableLocales = mysqlTable('byline_document_available_lo
198
198
  // identically in any locale). Computed status-blind at write time and frozen
199
199
  // on the immutable version, so restore / point-in-time reads stay consistent.
200
200
  // Drives `localeFallback: 'strict'` reads via an indexed EXISTS gate without
201
- // scanning the store_* tables. See docs/07-internationalization/index.md.
201
+ // scanning the store_* tables. See docs/08-internationalization/index.md.
202
202
  export const documentVersionLocales = mysqlTable('byline_document_version_locales', {
203
203
  document_version_id: uuidChar('document_version_id').notNull(),
204
204
  locale: varchar('locale', { length: 10 }).notNull(),
@@ -300,7 +300,7 @@ export const currentDocumentsView = mysqlView('byline_current_documents').as((qb
300
300
  // read paths (`buildLocaleChain` / `pathProjection` / field-fallback)
301
301
  // re-base onto the per-document source rather than the mutable global
302
302
  // default — a primary-key join, already present for `order_key`.
303
- // See docs/07-internationalization/index.md.
303
+ // See docs/08-internationalization/index.md.
304
304
  source_locale: documents.source_locale,
305
305
  })
306
306
  .from(sq)
@@ -407,7 +407,7 @@ export const textStore = mysqlTable('byline_store_text', {
407
407
  // no query code references this index name — and Byline's actual
408
408
  // full-text search is a separate, pluggable `SearchProvider` seam
409
409
  // (`@byline/core`), not built on this table's indexes. See
410
- // docs/05-reading-and-delivery/07-search.md.
410
+ // docs/06-search/index.md.
411
411
  index('idx_text_locale_value').on(table.locale, sql `${table.value}(191)`),
412
412
  index('idx_text_path_value').on(table.field_path, sql `${table.value}(191)`),
413
413
  // Unique constraints for unique fields
@@ -860,7 +860,7 @@ export const jsonStoreRelations = relations(jsonStore, ({ one }) => ({
860
860
  // fit without a second migration. Append-only and deliberately **FK-free**: an
861
861
  // audit row is an immutable historical fact that must outlive the document,
862
862
  // collection, or actor it references — a `document.deleted` row cannot be
863
- // allowed to cascade-delete itself. See docs/06-auth-and-security/02-auditability.md — Workstream 2.
863
+ // allowed to cascade-delete itself. See docs/07-auth-and-security/02-auditability.md — Workstream 2.
864
864
  export const auditLog = mysqlTable('byline_audit_log', {
865
865
  id: uuidChar('id').primaryKey(), // UUIDv7 — time-ordered, so id ordering ≈ time ordering
866
866
  document_id: uuidChar('document_id'), // NULL for admin-realm (non-document) events; no FK
package/dist/index.d.ts CHANGED
@@ -36,7 +36,7 @@ export declare const mysqlAdapter: ({ connectionString, collections, defaultCont
36
36
  * for documents whose `source_locale` is not yet backfilled. Per-document
37
37
  * reads and writes otherwise re-base onto each document's own `source_locale`
38
38
  * (carried on the current-documents views), so changing this value does not
39
- * re-interpret existing data. See docs/07-internationalization/index.md.
39
+ * re-interpret existing data. See docs/08-internationalization/index.md.
40
40
  */
41
41
  defaultContentLocale: string;
42
42
  /**
@@ -6,7 +6,7 @@
6
6
  * Copyright (c) Infonomic Company Limited
7
7
  */
8
8
  /**
9
- * Append-only audit-log writes (docs/06-auth-and-security/02-auditability.md — Workstream 2). A deliberately
9
+ * Append-only audit-log writes (docs/07-auth-and-security/02-auditability.md — Workstream 2). A deliberately
10
10
  * dumb command: it inserts one row and knows nothing about *which* changes
11
11
  * warrant an audit entry — that policy lives in the lifecycle services, which
12
12
  * wrap the mutation + this append in `withTransaction` so they commit
@@ -6,7 +6,7 @@
6
6
  * Copyright (c) Infonomic Company Limited
7
7
  */
8
8
  /**
9
- * Audit-log reads (docs/06-auth-and-security/02-auditability.md — Workstreams 3 & 4). Reads run on the pool
9
+ * Audit-log reads (docs/07-auth-and-security/02-auditability.md — Workstreams 3 & 4). Reads run on the pool
10
10
  * directly — they never need to join an audit write's transaction — so this
11
11
  * takes the raw `db` (drizzle over the pool) rather than the `DBManager`.
12
12
  * Access scoping is the caller's responsibility (the document's own read
@@ -205,7 +205,7 @@ export declare class DocumentCommands implements IDocumentCommands {
205
205
  * clause *after* `INSERT INTO <table>` and immediately before the `SELECT`
206
206
  * it modifies (confirmed against a live MySQL 9.7.1 server) — so the two
207
207
  * clauses are transposed relative to the pg original. See
208
- * docs/07-internationalization/index.md.
208
+ * docs/08-internationalization/index.md.
209
209
  */
210
210
  private writeVersionLocaleLedger;
211
211
  /**
@@ -268,7 +268,7 @@ export declare class DocumentCommands implements IDocumentCommands {
268
268
  * **without** minting a new document version or touching workflow status. The
269
269
  * change is immediate and applies across every version of the document; the
270
270
  * public advertised set remains the intersection with the resolved version's
271
- * completeness ledger. See docs/07-internationalization/index.md.
271
+ * completeness ledger. See docs/08-internationalization/index.md.
272
272
  */
273
273
  setDocumentAvailableLocales(params: {
274
274
  documentId: string;
@@ -192,7 +192,7 @@ export class DocumentCommands {
192
192
  // source locale rather than the mutable global default. NULL (a row not
193
193
  // yet touched by `backfillSourceLocales`) falls back to the configured
194
194
  // default — the value it was implicitly authored against.
195
- // See docs/07-internationalization/index.md.
195
+ // See docs/08-internationalization/index.md.
196
196
  let sourceLocale;
197
197
  if (documentId == null) {
198
198
  documentId = uuidv7();
@@ -345,7 +345,7 @@ export class DocumentCommands {
345
345
  // accounts for the per-locale carry-forward in step 5 — not just the
346
346
  // freshly-flattened locale. A version with no localized content at all
347
347
  // records a single `'all'` sentinel (it renders identically in any
348
- // locale). Status-blind by design — see docs/07-internationalization/index.md.
348
+ // locale). Status-blind by design — see docs/08-internationalization/index.md.
349
349
  await this.writeVersionLocaleLedger(tx, documentVersion.id, sourceLocale);
350
350
  return {
351
351
  document: documentVersion,
@@ -446,7 +446,7 @@ export class DocumentCommands {
446
446
  * clause *after* `INSERT INTO <table>` and immediately before the `SELECT`
447
447
  * it modifies (confirmed against a live MySQL 9.7.1 server) — so the two
448
448
  * clauses are transposed relative to the pg original. See
449
- * docs/07-internationalization/index.md.
449
+ * docs/08-internationalization/index.md.
450
450
  */
451
451
  async writeVersionLocaleLedger(tx, versionId, sourceLocale) {
452
452
  await tx.execute(sql `
@@ -558,7 +558,7 @@ export class DocumentCommands {
558
558
  * **without** minting a new document version or touching workflow status. The
559
559
  * change is immediate and applies across every version of the document; the
560
560
  * public advertised set remains the intersection with the resolved version's
561
- * completeness ledger. See docs/07-internationalization/index.md.
561
+ * completeness ledger. See docs/08-internationalization/index.md.
562
562
  */
563
563
  async setDocumentAvailableLocales(params) {
564
564
  await this.db.transaction(async (tx) => {
@@ -128,7 +128,7 @@ export declare class DocumentQueries implements IDocumentQueries {
128
128
  * document, or any document read after the global default is switched, falls
129
129
  * back to the locale it was actually authored in) — otherwise the configured
130
130
  * global default, which is correct for not-yet-anchored rows and for
131
- * row-less lookups (findByPath). See docs/07-internationalization/index.md.
131
+ * row-less lookups (findByPath). See docs/08-internationalization/index.md.
132
132
  */
133
133
  private buildLocaleChain;
134
134
  /**
@@ -161,7 +161,7 @@ export class DocumentQueries {
161
161
  * document, or any document read after the global default is switched, falls
162
162
  * back to the locale it was actually authored in) — otherwise the configured
163
163
  * global default, which is correct for not-yet-anchored rows and for
164
- * row-less lookups (findByPath). See docs/07-internationalization/index.md.
164
+ * row-less lookups (findByPath). See docs/08-internationalization/index.md.
165
165
  */
166
166
  buildLocaleChain(requestedLocale, sourceLocale) {
167
167
  const floor = sourceLocale ?? this.defaultContentLocale;
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@byline/db-mysql",
3
3
  "private": false,
4
4
  "license": "MPL-2.0",
5
- "version": "4.8.0",
5
+ "version": "4.9.0",
6
6
  "engines": {
7
7
  "node": ">=20.9.0"
8
8
  },
@@ -50,8 +50,8 @@
50
50
  "drizzle-orm": "^0.45.2",
51
51
  "mysql2": "^3.23.1",
52
52
  "uuid": "^14.0.1",
53
- "@byline/admin": "4.8.0",
54
- "@byline/core": "4.8.0"
53
+ "@byline/admin": "4.9.0",
54
+ "@byline/core": "4.9.0"
55
55
  },
56
56
  "devDependencies": {
57
57
  "@biomejs/biome": "2.5.4",
@@ -65,9 +65,9 @@
65
65
  "tsx": "^4.23.1",
66
66
  "typescript": "^7.0.2",
67
67
  "vitest": "^4.1.10",
68
- "@byline/auth": "4.8.0",
69
- "@byline/db-conformance": "0.0.2",
70
- "@byline/client": "4.8.0"
68
+ "@byline/db-conformance": "0.0.3",
69
+ "@byline/client": "4.9.0",
70
+ "@byline/auth": "4.9.0"
71
71
  },
72
72
  "publishConfig": {
73
73
  "access": "public",