@byline/db-mysql 4.8.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 (75) hide show
  1. package/LICENSE +373 -0
  2. package/README.md +283 -0
  3. package/dist/database/schema/auth.d.ts +951 -0
  4. package/dist/database/schema/auth.js +226 -0
  5. package/dist/database/schema/common.d.ts +168 -0
  6. package/dist/database/schema/common.js +130 -0
  7. package/dist/database/schema/index.d.ts +3419 -0
  8. package/dist/database/schema/index.js +895 -0
  9. package/dist/database/schema/schema-pins.test.node.d.ts +52 -0
  10. package/dist/database/schema/schema-pins.test.node.js +398 -0
  11. package/dist/index.d.ts +79 -0
  12. package/dist/index.js +151 -0
  13. package/dist/lib/boot-check.d.ts +22 -0
  14. package/dist/lib/boot-check.js +42 -0
  15. package/dist/lib/boot-check.test.node.d.ts +8 -0
  16. package/dist/lib/boot-check.test.node.js +46 -0
  17. package/dist/lib/db-manager.d.ts +54 -0
  18. package/dist/lib/db-manager.js +49 -0
  19. package/dist/lib/test-db.d.ts +37 -0
  20. package/dist/lib/test-db.js +111 -0
  21. package/dist/lib/test-helper.d.ts +33 -0
  22. package/dist/lib/test-helper.js +68 -0
  23. package/dist/modules/admin/admin-permissions-repository.d.ts +35 -0
  24. package/dist/modules/admin/admin-permissions-repository.js +101 -0
  25. package/dist/modules/admin/admin-preferences-repository.d.ts +62 -0
  26. package/dist/modules/admin/admin-preferences-repository.js +156 -0
  27. package/dist/modules/admin/admin-roles-repository.d.ts +11 -0
  28. package/dist/modules/admin/admin-roles-repository.js +209 -0
  29. package/dist/modules/admin/admin-store.d.ts +20 -0
  30. package/dist/modules/admin/admin-store.js +30 -0
  31. package/dist/modules/admin/admin-users-repository.d.ts +11 -0
  32. package/dist/modules/admin/admin-users-repository.js +303 -0
  33. package/dist/modules/admin/index.d.ts +27 -0
  34. package/dist/modules/admin/index.js +27 -0
  35. package/dist/modules/admin/refresh-tokens-repository.d.ts +34 -0
  36. package/dist/modules/admin/refresh-tokens-repository.js +160 -0
  37. package/dist/modules/audit/audit-commands.d.ts +29 -0
  38. package/dist/modules/audit/audit-commands.js +40 -0
  39. package/dist/modules/audit/audit-queries.d.ts +41 -0
  40. package/dist/modules/audit/audit-queries.js +169 -0
  41. package/dist/modules/counters/counters-commands.d.ts +54 -0
  42. package/dist/modules/counters/counters-commands.js +121 -0
  43. package/dist/modules/counters/tests/counters-concurrency.test.d.ts +8 -0
  44. package/dist/modules/counters/tests/counters-concurrency.test.js +111 -0
  45. package/dist/modules/storage/classify-error.d.ts +34 -0
  46. package/dist/modules/storage/classify-error.js +50 -0
  47. package/dist/modules/storage/classify-error.test.node.d.ts +8 -0
  48. package/dist/modules/storage/classify-error.test.node.js +89 -0
  49. package/dist/modules/storage/normalize-row.d.ts +55 -0
  50. package/dist/modules/storage/normalize-row.js +135 -0
  51. package/dist/modules/storage/normalize-row.test.node.d.ts +8 -0
  52. package/dist/modules/storage/normalize-row.test.node.js +89 -0
  53. package/dist/modules/storage/storage-commands.d.ts +443 -0
  54. package/dist/modules/storage/storage-commands.js +1263 -0
  55. package/dist/modules/storage/storage-insert.d.ts +21 -0
  56. package/dist/modules/storage/storage-insert.js +152 -0
  57. package/dist/modules/storage/storage-queries.d.ts +805 -0
  58. package/dist/modules/storage/storage-queries.js +1815 -0
  59. package/dist/modules/storage/storage-store-manifest.d.ts +77 -0
  60. package/dist/modules/storage/storage-store-manifest.js +168 -0
  61. package/dist/modules/storage/storage-utils.d.ts +49 -0
  62. package/dist/modules/storage/storage-utils.js +76 -0
  63. package/dist/modules/storage/tests/dialect-pins.integration.test.d.ts +8 -0
  64. package/dist/modules/storage/tests/dialect-pins.integration.test.js +266 -0
  65. package/dist/modules/storage/tests/storage-commands.test.d.ts +8 -0
  66. package/dist/modules/storage/tests/storage-commands.test.js +324 -0
  67. package/dist/modules/storage/tests/storage-document-paths.test.d.ts +8 -0
  68. package/dist/modules/storage/tests/storage-document-paths.test.js +214 -0
  69. package/dist/modules/storage/tests/storage-document-tree.test.d.ts +8 -0
  70. package/dist/modules/storage/tests/storage-document-tree.test.js +361 -0
  71. package/dist/modules/storage/tests/storage-queries.test.d.ts +8 -0
  72. package/dist/modules/storage/tests/storage-queries.test.js +685 -0
  73. package/dist/modules/storage/tests/storage-status-and-lifecycle.test.d.ts +8 -0
  74. package/dist/modules/storage/tests/storage-status-and-lifecycle.test.js +268 -0
  75. package/package.json +91 -0
@@ -0,0 +1,55 @@
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
+ import type { UnifiedFieldValue } from '@byline/core';
9
+ /**
10
+ * Canonicalise a raw UNION ALL driver row to the shared `UnifiedFieldValue`
11
+ * contract before it reaches `extractFlattenedFieldValue`. Mirrors
12
+ * `packages/db-postgres/src/modules/storage/normalize-row.ts`, whose pg
13
+ * counterpart also coerces `value_date` / `value_timestamp_tz` to `Date`
14
+ * (as of task 13b — see that file's docblock for the pg-specific evidence)
15
+ * but is otherwise closer to an identity cast: the mysql2 driver needs
16
+ * substantially more canonicalisation below, verified against a live
17
+ * MySQL 9.7.1 server rather than assumed (mysql2's declared types are a
18
+ * hypothesis, not fact — see `src/index.ts`'s boot-check comment for the
19
+ * prior instance of this on this program). `value_time` is deliberately
20
+ * left untouched on both adapters — a bare time-of-day has no calendar
21
+ * date or time zone to normalise, and the task-13b "converge on Date"
22
+ * ruling explicitly excludes it (see the `time` fixture in
23
+ * `packages/db-conformance/src/suites/field-types.ts`):
24
+ *
25
+ * - `TINYINT(1)` → `boolean`. mysql2 returns a JS `number` (`0`/`1`) for
26
+ * `TINYINT(1)` columns selected through a raw query, not a `boolean` —
27
+ * confirmed live. Only the three `UnifiedFieldValue` columns backed by a
28
+ * `boolean()` column need the coercion: `boolean_value` (the boolean
29
+ * store's own value), `thumbnail_generated` (file store), and
30
+ * `cascade_delete` (relation store).
31
+ * - `DECIMAL` stays a **string** — confirmed live with the pool's
32
+ * `decimalNumbers: false` option (`src/index.ts`), which must not be
33
+ * undone; MySQL/JS float coercion would lose precision on money/decimal
34
+ * values, matching the pg adapter's `numeric` handling.
35
+ * - `JSON` columns arrive already parsed by the driver — confirmed live
36
+ * (an inserted JS object round-trips as an object, not a JSON string) —
37
+ * so this function must not re-`JSON.parse` them.
38
+ * - `DATE`/`DATETIME` → **string**, not `Date` — the opposite of what an
39
+ * earlier version of this comment claimed. `drizzle-orm`'s mysql2
40
+ * driver installs its own `typeCast` on every raw `db.execute(sql\`...\`)`
41
+ * call (this UNION ALL query has no schema-typed `fields` mapper, so it
42
+ * always takes that path) that unconditionally calls `field.string()`
43
+ * for `TIMESTAMP`/`DATETIME`/`DATE` columns, overriding whatever the
44
+ * pool's own `timezone` option would otherwise do — confirmed live:
45
+ * `value_date` (a `datetimeStore.date_type === 'date'` row) comes back
46
+ * as `'2026-01-15'`; `value_timestamp_tz` (`date_type === 'datetime'`)
47
+ * comes back as `'2026-01-15 10:30:00.123000'`. Both reach
48
+ * `restoreFieldSetData` (`@byline/core`) and become the runtime value
49
+ * of a document's `date`/`datetime` field, so an un-coerced string here
50
+ * was a real, user-facing defect — not just an internal-tooling one —
51
+ * caught by no existing test because none exercised a `date`/`datetime`
52
+ * field type through this UNION ALL path. `toDate` (shared,
53
+ * `storage-utils.js`) and `toDateOnly` below fix it.
54
+ */
55
+ export declare function normalizeRow(row: Record<string, unknown>): UnifiedFieldValue;
@@ -0,0 +1,135 @@
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
+ import { toDate } from './storage-utils.js';
9
+ /**
10
+ * Canonicalise a raw UNION ALL driver row to the shared `UnifiedFieldValue`
11
+ * contract before it reaches `extractFlattenedFieldValue`. Mirrors
12
+ * `packages/db-postgres/src/modules/storage/normalize-row.ts`, whose pg
13
+ * counterpart also coerces `value_date` / `value_timestamp_tz` to `Date`
14
+ * (as of task 13b — see that file's docblock for the pg-specific evidence)
15
+ * but is otherwise closer to an identity cast: the mysql2 driver needs
16
+ * substantially more canonicalisation below, verified against a live
17
+ * MySQL 9.7.1 server rather than assumed (mysql2's declared types are a
18
+ * hypothesis, not fact — see `src/index.ts`'s boot-check comment for the
19
+ * prior instance of this on this program). `value_time` is deliberately
20
+ * left untouched on both adapters — a bare time-of-day has no calendar
21
+ * date or time zone to normalise, and the task-13b "converge on Date"
22
+ * ruling explicitly excludes it (see the `time` fixture in
23
+ * `packages/db-conformance/src/suites/field-types.ts`):
24
+ *
25
+ * - `TINYINT(1)` → `boolean`. mysql2 returns a JS `number` (`0`/`1`) for
26
+ * `TINYINT(1)` columns selected through a raw query, not a `boolean` —
27
+ * confirmed live. Only the three `UnifiedFieldValue` columns backed by a
28
+ * `boolean()` column need the coercion: `boolean_value` (the boolean
29
+ * store's own value), `thumbnail_generated` (file store), and
30
+ * `cascade_delete` (relation store).
31
+ * - `DECIMAL` stays a **string** — confirmed live with the pool's
32
+ * `decimalNumbers: false` option (`src/index.ts`), which must not be
33
+ * undone; MySQL/JS float coercion would lose precision on money/decimal
34
+ * values, matching the pg adapter's `numeric` handling.
35
+ * - `JSON` columns arrive already parsed by the driver — confirmed live
36
+ * (an inserted JS object round-trips as an object, not a JSON string) —
37
+ * so this function must not re-`JSON.parse` them.
38
+ * - `DATE`/`DATETIME` → **string**, not `Date` — the opposite of what an
39
+ * earlier version of this comment claimed. `drizzle-orm`'s mysql2
40
+ * driver installs its own `typeCast` on every raw `db.execute(sql\`...\`)`
41
+ * call (this UNION ALL query has no schema-typed `fields` mapper, so it
42
+ * always takes that path) that unconditionally calls `field.string()`
43
+ * for `TIMESTAMP`/`DATETIME`/`DATE` columns, overriding whatever the
44
+ * pool's own `timezone` option would otherwise do — confirmed live:
45
+ * `value_date` (a `datetimeStore.date_type === 'date'` row) comes back
46
+ * as `'2026-01-15'`; `value_timestamp_tz` (`date_type === 'datetime'`)
47
+ * comes back as `'2026-01-15 10:30:00.123000'`. Both reach
48
+ * `restoreFieldSetData` (`@byline/core`) and become the runtime value
49
+ * of a document's `date`/`datetime` field, so an un-coerced string here
50
+ * was a real, user-facing defect — not just an internal-tooling one —
51
+ * caught by no existing test because none exercised a `date`/`datetime`
52
+ * field type through this UNION ALL path. `toDate` (shared,
53
+ * `storage-utils.js`) and `toDateOnly` below fix it.
54
+ */
55
+ export function normalizeRow(row) {
56
+ return {
57
+ ...row,
58
+ boolean_value: normalizeTinyIntBoolean(row.boolean_value),
59
+ thumbnail_generated: normalizeTinyIntBoolean(row.thumbnail_generated),
60
+ cascade_delete: normalizeTinyIntBoolean(row.cascade_delete),
61
+ value_date: toDateOnly(row.value_date),
62
+ value_timestamp_tz: toDate(row.value_timestamp_tz, 'value_timestamp_tz'),
63
+ };
64
+ }
65
+ /**
66
+ * `'2026-01-15'` (MySQL `DATE` text, no time-of-day component) → a `Date`
67
+ * at **UTC** midnight for that calendar date.
68
+ *
69
+ * This is an elected divergence from node-postgres, not a parity claim — an
70
+ * earlier version of this docblock claimed UTC midnight matches
71
+ * node-postgres's own default parser for a `date` column. It doesn't.
72
+ * node-postgres's default `date` parser is `postgres-date` (see its
73
+ * `index.js:16-17`, whose own comment reads "Force YYYY-MM-DD dates to be
74
+ * parsed as local time") — that mismatch is the reason the UTC-vs-local
75
+ * question was raised in the first place. It's moot in practice, though:
76
+ * pg's `normalize-row.ts` bypasses `postgres-date` entirely (drizzle-orm's
77
+ * node-postgres session installs an identity `getTypeParser` for `DATE`, so
78
+ * that default parser never runs on this read path), and as of task 13b
79
+ * pg's own `toDateOnly` anchors at UTC midnight too, for the same
80
+ * determinism reason: host-local midnight would make the same stored `date`
81
+ * value materialise as a different calendar day depending on which host
82
+ * served it.
83
+ *
84
+ * **Ruling (project owner, task 13b): settled.** Both adapters return a
85
+ * `Date` at UTC midnight for `date` fields — verified together, via the
86
+ * shared `@byline/db-conformance` `field-types.ts` fixtures, under `TZ=UTC`
87
+ * and two non-UTC zones on both sides of it (`Asia/Bangkok`, `+07`, and
88
+ * `America/New_York`, a negative offset — a positive-only check can't
89
+ * distinguish a local-getter bug from a correct UTC one when the fixture's
90
+ * instant is itself UTC midnight). This is no longer provisional.
91
+ *
92
+ * This is the sole gate between the raw driver row and `ClientDocument`'s
93
+ * public `value_date` shape, so a malformed `DATE` string must not fall
94
+ * through as a silent `Invalid Date` — it throws instead, naming the raw
95
+ * value.
96
+ *
97
+ * The `value instanceof Date` branch is a defensive passthrough for a row
98
+ * assembled in-process rather than round-tripped through the driver — not
99
+ * exercised through the live UNION ALL read path today (mysql2's
100
+ * `typeCast` override always hands this function text, per the module
101
+ * docblock above). If a future driver change ever made this column arrive
102
+ * as a `Date` already, this function would trust it unchanged rather than
103
+ * re-normalising it to UTC midnight — and there's no safe way to
104
+ * re-normalise after the fact, because a bare `Date` carries no record of
105
+ * which midnight convention (UTC or host-local) produced it, so
106
+ * re-deriving the calendar day from either its UTC or local getters could
107
+ * silently pick the wrong one depending on the host's offset. Flagging
108
+ * this explicitly rather than adding a "normalisation" that would only be
109
+ * correct for some host timezones — mirrors the identical hazard comment
110
+ * on pg's `toDateOnly`.
111
+ */
112
+ function toDateOnly(value) {
113
+ if (value == null)
114
+ return null;
115
+ if (value instanceof Date)
116
+ return value;
117
+ const date = new Date(`${value}T00:00:00.000Z`);
118
+ if (Number.isNaN(date.getTime())) {
119
+ throw new Error(`normalizeRow: value_date is not a parseable date — got ${JSON.stringify(value)}`);
120
+ }
121
+ return date;
122
+ }
123
+ /**
124
+ * `TINYINT(1)` → `boolean`, tolerant of the value already being a real
125
+ * boolean (a row assembled in-process rather than round-tripped through the
126
+ * driver) or `null`/`undefined` (column absent for this row's field type —
127
+ * only one value column is ever populated per UNION ALL row).
128
+ */
129
+ function normalizeTinyIntBoolean(value) {
130
+ if (value == null)
131
+ return null;
132
+ if (typeof value === 'boolean')
133
+ return value;
134
+ return value !== 0;
135
+ }
@@ -0,0 +1,8 @@
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
+ export {};
@@ -0,0 +1,89 @@
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
+ import { describe, expect, it } from 'vitest';
9
+ import { normalizeRow } from './normalize-row.js';
10
+ describe('normalizeRow (mysql)', () => {
11
+ it('coerces boolean_value from a driver TINYINT(1) number to boolean', () => {
12
+ expect(normalizeRow({ boolean_value: 1 }).boolean_value).toBe(true);
13
+ expect(normalizeRow({ boolean_value: 0 }).boolean_value).toBe(false);
14
+ });
15
+ it('coerces thumbnail_generated and cascade_delete the same way', () => {
16
+ const row = normalizeRow({ thumbnail_generated: 1, cascade_delete: 0 });
17
+ expect(row.thumbnail_generated).toBe(true);
18
+ expect(row.cascade_delete).toBe(false);
19
+ });
20
+ it('passes through null for absent tinyint(1) columns (only one value column is populated per row)', () => {
21
+ const row = normalizeRow({
22
+ boolean_value: null,
23
+ thumbnail_generated: null,
24
+ cascade_delete: null,
25
+ });
26
+ expect(row.boolean_value).toBeNull();
27
+ expect(row.thumbnail_generated).toBeNull();
28
+ expect(row.cascade_delete).toBeNull();
29
+ });
30
+ it('tolerates a value already coerced to a real boolean', () => {
31
+ expect(normalizeRow({ boolean_value: true }).boolean_value).toBe(true);
32
+ expect(normalizeRow({ boolean_value: false }).boolean_value).toBe(false);
33
+ });
34
+ it('leaves a DECIMAL string untouched (decimalNumbers: false at the pool)', () => {
35
+ const row = normalizeRow({ value_decimal: '299.99' });
36
+ expect(row.value_decimal).toBe('299.99');
37
+ expect(typeof row.value_decimal).toBe('string');
38
+ });
39
+ it('leaves an already-parsed JSON value untouched (the driver parses JSON columns itself)', () => {
40
+ const value = { a: 1, b: [1, 2, 3] };
41
+ const row = normalizeRow({ json_value: value });
42
+ expect(row.json_value).toBe(value);
43
+ });
44
+ it('leaves an already-Date value_timestamp_tz untouched (defensive — not expected on the live driver path)', () => {
45
+ const date = new Date('2024-01-15T10:30:00.123Z');
46
+ const row = normalizeRow({ value_timestamp_tz: date });
47
+ expect(row.value_timestamp_tz).toBe(date);
48
+ });
49
+ it('coerces value_timestamp_tz from the driver DATETIME string to a real Date', () => {
50
+ // The shape drizzle-orm's mysql2 driver actually hands back on the raw
51
+ // `db.execute()` path (its own typeCast forces DATETIME to a string) —
52
+ // confirmed live against `packages/db-mysql/scripts` probes for the
53
+ // Task 11 report. Space-separated, no timezone marker (UTC by schema
54
+ // convention).
55
+ const row = normalizeRow({ value_timestamp_tz: '2026-01-15 10:30:00.123456' });
56
+ expect(row.value_timestamp_tz).toBeInstanceOf(Date);
57
+ expect(row.value_timestamp_tz.toISOString()).toBe('2026-01-15T10:30:00.123Z');
58
+ });
59
+ it('passes through null for an absent value_timestamp_tz column', () => {
60
+ expect(normalizeRow({ value_timestamp_tz: null }).value_timestamp_tz).toBeNull();
61
+ });
62
+ it('coerces value_date from the driver DATE string to a UTC-midnight Date', () => {
63
+ const row = normalizeRow({ value_date: '2026-01-15' });
64
+ expect(row.value_date).toBeInstanceOf(Date);
65
+ expect(row.value_date.toISOString()).toBe('2026-01-15T00:00:00.000Z');
66
+ });
67
+ it('leaves an already-Date value_date untouched (defensive)', () => {
68
+ const date = new Date('2026-01-15T00:00:00.000Z');
69
+ const row = normalizeRow({ value_date: date });
70
+ expect(row.value_date).toBe(date);
71
+ });
72
+ it('passes through null for an absent value_date column', () => {
73
+ expect(normalizeRow({ value_date: null }).value_date).toBeNull();
74
+ });
75
+ it('passes through every other column unchanged', () => {
76
+ const row = normalizeRow({
77
+ field_path: 'title',
78
+ field_name: 'title',
79
+ locale: 'en',
80
+ text_value: 'hello',
81
+ });
82
+ expect(row).toMatchObject({
83
+ field_path: 'title',
84
+ field_name: 'title',
85
+ locale: 'en',
86
+ text_value: 'hello',
87
+ });
88
+ });
89
+ });