@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,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,268 @@
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 { afterAll, beforeAll, describe, expect, it } from 'vitest';
9
+ import { setupTestDB, teardownTestDB } from '../../../lib/test-helper.js';
10
+ const timestamp = Date.now();
11
+ function first(rows) {
12
+ const row = rows[0];
13
+ if (row == null)
14
+ throw new Error('expected at least one row, got none');
15
+ return row;
16
+ }
17
+ async function queryRows(pool, sql, params) {
18
+ const [rows] = await pool.query(sql, params);
19
+ return rows;
20
+ }
21
+ async function queryOne(pool, sql, params) {
22
+ return first(await queryRows(pool, sql, params));
23
+ }
24
+ const PostsCollectionConfig = {
25
+ path: `lifecycle-test-posts-${timestamp}`,
26
+ labels: { singular: 'Post', plural: 'Posts' },
27
+ fields: [
28
+ { name: 'title', type: 'text', localized: true },
29
+ {
30
+ name: 'links',
31
+ type: 'array',
32
+ fields: [{ name: 'label', type: 'text' }],
33
+ },
34
+ ],
35
+ };
36
+ describe('DocumentCommands status/archive/soft-delete/order-key/delete-locale (mysql, live database)', () => {
37
+ let testDb;
38
+ let rawPool;
39
+ let collectionId;
40
+ beforeAll(async () => {
41
+ testDb = setupTestDB([PostsCollectionConfig]);
42
+ rawPool = testDb.pool;
43
+ const created = first(await testDb.commandBuilders.collections.create(PostsCollectionConfig.path, PostsCollectionConfig));
44
+ collectionId = created.id;
45
+ });
46
+ afterAll(async () => {
47
+ await testDb.commandBuilders.collections.delete(collectionId);
48
+ await teardownTestDB();
49
+ });
50
+ describe('setDocumentStatus', () => {
51
+ it('mutates the version row in place — no new version minted', async () => {
52
+ const created = await testDb.commandBuilders.documents.createDocumentVersion({
53
+ collectionId,
54
+ collectionVersion: 1,
55
+ collectionConfig: PostsCollectionConfig,
56
+ action: 'create',
57
+ documentData: { title: 'Status Doc', links: [] },
58
+ locale: 'all',
59
+ status: 'draft',
60
+ });
61
+ const versionId = created.document.id;
62
+ const documentId = created.document.document_id;
63
+ await testDb.commandBuilders.documents.setDocumentStatus({
64
+ document_version_id: versionId,
65
+ status: 'published',
66
+ });
67
+ const versionRow = await queryOne(rawPool, 'SELECT status FROM byline_document_versions WHERE id = ?', [versionId]);
68
+ expect(versionRow.status).toBe('published');
69
+ const allVersions = await queryRows(rawPool, 'SELECT id FROM byline_document_versions WHERE document_id = ?', [documentId]);
70
+ expect(allVersions.length).toBe(1); // still one version — status is metadata, not content
71
+ });
72
+ });
73
+ describe('archivePublishedVersions', () => {
74
+ it('archives every published version of a document except an excluded one', async () => {
75
+ const v1 = await testDb.commandBuilders.documents.createDocumentVersion({
76
+ collectionId,
77
+ collectionVersion: 1,
78
+ collectionConfig: PostsCollectionConfig,
79
+ action: 'create',
80
+ documentData: { title: 'Archive Doc v1', links: [] },
81
+ locale: 'all',
82
+ status: 'published',
83
+ });
84
+ const documentId = v1.document.document_id;
85
+ const v2 = await testDb.commandBuilders.documents.createDocumentVersion({
86
+ documentId,
87
+ collectionId,
88
+ collectionVersion: 1,
89
+ collectionConfig: PostsCollectionConfig,
90
+ action: 'update',
91
+ documentData: { title: 'Archive Doc v2', links: [] },
92
+ locale: 'all',
93
+ status: 'published',
94
+ });
95
+ const updated = await testDb.commandBuilders.documents.archivePublishedVersions({
96
+ document_id: documentId,
97
+ currentStatus: 'published',
98
+ excludeVersionId: v2.document.id,
99
+ });
100
+ expect(updated).toBe(1);
101
+ const v1Row = await queryOne(rawPool, 'SELECT status FROM byline_document_versions WHERE id = ?', [v1.document.id]);
102
+ expect(v1Row.status).toBe('archived');
103
+ const v2Row = await queryOne(rawPool, 'SELECT status FROM byline_document_versions WHERE id = ?', [v2.document.id]);
104
+ expect(v2Row.status).toBe('published'); // excluded — untouched
105
+ const versionCount = await queryRows(rawPool, 'SELECT id FROM byline_document_versions WHERE document_id = ?', [documentId]);
106
+ expect(versionCount.length).toBe(2); // no new version minted by archiving
107
+ });
108
+ it('returns 0 when no version matches the target status', async () => {
109
+ const created = await testDb.commandBuilders.documents.createDocumentVersion({
110
+ collectionId,
111
+ collectionVersion: 1,
112
+ collectionConfig: PostsCollectionConfig,
113
+ action: 'create',
114
+ documentData: { title: 'Never Published', links: [] },
115
+ locale: 'all',
116
+ status: 'draft',
117
+ });
118
+ const updated = await testDb.commandBuilders.documents.archivePublishedVersions({
119
+ document_id: created.document.document_id,
120
+ });
121
+ expect(updated).toBe(0);
122
+ });
123
+ });
124
+ describe('softDeleteDocument', () => {
125
+ it('marks every version of a document as deleted', async () => {
126
+ const v1 = await testDb.commandBuilders.documents.createDocumentVersion({
127
+ collectionId,
128
+ collectionVersion: 1,
129
+ collectionConfig: PostsCollectionConfig,
130
+ action: 'create',
131
+ documentData: { title: 'Delete Doc v1', links: [] },
132
+ locale: 'all',
133
+ status: 'draft',
134
+ });
135
+ const documentId = v1.document.document_id;
136
+ await testDb.commandBuilders.documents.createDocumentVersion({
137
+ documentId,
138
+ collectionId,
139
+ collectionVersion: 1,
140
+ collectionConfig: PostsCollectionConfig,
141
+ action: 'update',
142
+ documentData: { title: 'Delete Doc v2', links: [] },
143
+ locale: 'all',
144
+ status: 'draft',
145
+ });
146
+ const affected = await testDb.commandBuilders.documents.softDeleteDocument({
147
+ document_id: documentId,
148
+ });
149
+ expect(affected).toBe(2);
150
+ const rows = await queryRows(rawPool, 'SELECT is_deleted FROM byline_document_versions WHERE document_id = ?', [documentId]);
151
+ expect(rows.length).toBe(2); // rows preserved, only tombstoned
152
+ expect(rows.every((r) => r.is_deleted === 1)).toBe(true);
153
+ });
154
+ it('returns 0 for a document with no version rows (defensive guard)', async () => {
155
+ const affected = await testDb.commandBuilders.documents.softDeleteDocument({
156
+ document_id: '00000000-0000-7000-8000-000000000000',
157
+ });
158
+ expect(affected).toBe(0);
159
+ });
160
+ });
161
+ describe('setOrderKey', () => {
162
+ it('writes order_key on byline_documents without touching the version stream', async () => {
163
+ const created = await testDb.commandBuilders.documents.createDocumentVersion({
164
+ collectionId,
165
+ collectionVersion: 1,
166
+ collectionConfig: PostsCollectionConfig,
167
+ action: 'create',
168
+ documentData: { title: 'Order Key Doc', links: [] },
169
+ locale: 'all',
170
+ status: 'draft',
171
+ });
172
+ const documentId = created.document.document_id;
173
+ await testDb.commandBuilders.documents.setOrderKey({
174
+ document_id: documentId,
175
+ order_key: 'a0',
176
+ });
177
+ const docRow = await queryOne(rawPool, 'SELECT order_key FROM byline_documents WHERE id = ?', [documentId]);
178
+ expect(docRow.order_key).toBe('a0');
179
+ const versionCount = await queryRows(rawPool, 'SELECT id FROM byline_document_versions WHERE document_id = ?', [documentId]);
180
+ expect(versionCount.length).toBe(1); // no new version
181
+ });
182
+ });
183
+ describe('deleteDocumentLocale', () => {
184
+ it('writes a new version carrying forward every locale except the deleted one, plus meta identities', async () => {
185
+ const v1 = await testDb.commandBuilders.documents.createDocumentVersion({
186
+ collectionId,
187
+ collectionVersion: 1,
188
+ collectionConfig: PostsCollectionConfig,
189
+ action: 'create',
190
+ documentData: {
191
+ title: { en: 'Hello', es: 'Hola', fr: 'Bonjour' },
192
+ links: [{ label: 'one' }],
193
+ },
194
+ locale: 'all',
195
+ status: 'draft',
196
+ });
197
+ const documentId = v1.document.document_id;
198
+ const firstVersionId = v1.document.id;
199
+ const result = await testDb.commandBuilders.documents.deleteDocumentLocale({
200
+ documentId,
201
+ locale: 'es',
202
+ status: 'draft',
203
+ });
204
+ expect(result).not.toBeNull();
205
+ expect(result?.previousVersionId).toBe(firstVersionId);
206
+ const newVersionId = result?.newVersionId;
207
+ expect(newVersionId).not.toBe(firstVersionId);
208
+ // A genuinely new, distinct version row.
209
+ const versionRows = await queryRows(rawPool, 'SELECT id, event_type, status, change_summary FROM byline_document_versions WHERE document_id = ? ORDER BY id', [documentId]);
210
+ expect(versionRows.length).toBe(2);
211
+ const newVersionRow = versionRows.find((r) => r.id === newVersionId);
212
+ expect(newVersionRow.event_type).toBe('delete_locale');
213
+ expect(newVersionRow.status).toBe('draft');
214
+ expect(newVersionRow.change_summary).toBe('deleted content locale es');
215
+ // 'en' and 'fr' carried forward; 'es' dropped.
216
+ const titleRows = await queryRows(rawPool, "SELECT locale, value FROM byline_store_text WHERE document_version_id = ? AND field_name = 'title' ORDER BY locale", [newVersionId]);
217
+ expect(titleRows.map((r) => [r.locale, r.value])).toEqual([
218
+ ['en', 'Hello'],
219
+ ['fr', 'Bonjour'],
220
+ ]);
221
+ // The prior version is untouched (immutability) — still has all three.
222
+ const prevTitleRows = await queryRows(rawPool, "SELECT locale FROM byline_store_text WHERE document_version_id = ? AND field_name = 'title' ORDER BY locale", [firstVersionId]);
223
+ expect(prevTitleRows.map((r) => r.locale)).toEqual(['en', 'es', 'fr']);
224
+ // Meta identity rows (the array item's stable _id) carried forward too.
225
+ const newMetaRows = await queryRows(rawPool, 'SELECT type, path, item_id FROM byline_store_meta WHERE document_version_id = ?', [newVersionId]);
226
+ const prevMetaRows = await queryRows(rawPool, 'SELECT type, path, item_id FROM byline_store_meta WHERE document_version_id = ?', [firstVersionId]);
227
+ expect(newMetaRows.length).toBe(prevMetaRows.length);
228
+ expect(newMetaRows.length).toBeGreaterThan(0);
229
+ expect(new Set(newMetaRows.map((r) => r.item_id))).toEqual(new Set(prevMetaRows.map((r) => r.item_id)));
230
+ // The completeness ledger no longer advertises 'es'.
231
+ const ledgerRows = await queryRows(rawPool, 'SELECT locale FROM byline_document_version_locales WHERE document_version_id = ? ORDER BY locale', [newVersionId]);
232
+ expect(ledgerRows.map((r) => r.locale)).toEqual(['en', 'fr']);
233
+ });
234
+ it('returns null when the document has no current (non-deleted) version', async () => {
235
+ const result = await testDb.commandBuilders.documents.deleteDocumentLocale({
236
+ documentId: '00000000-0000-7000-8000-000000000000',
237
+ locale: 'es',
238
+ });
239
+ expect(result).toBeNull();
240
+ });
241
+ it('returns null when every version of the document has been soft-deleted', async () => {
242
+ // Distinct from the "wholly absent document" case above — this
243
+ // document genuinely exists and has version rows, but all of them
244
+ // carry `is_deleted = true`, so the `AND is_deleted = false` filter
245
+ // in `deleteDocumentLocale`'s current-version lookup excludes every
246
+ // one of them, the same guard it hits for an absent document.
247
+ const created = await testDb.commandBuilders.documents.createDocumentVersion({
248
+ collectionId,
249
+ collectionVersion: 1,
250
+ collectionConfig: PostsCollectionConfig,
251
+ action: 'create',
252
+ documentData: { title: { en: 'Soft-Deleted', es: 'Borrado', fr: 'Supprimé' } },
253
+ locale: 'all',
254
+ status: 'draft',
255
+ });
256
+ const documentId = created.document.document_id;
257
+ const affected = await testDb.commandBuilders.documents.softDeleteDocument({
258
+ document_id: documentId,
259
+ });
260
+ expect(affected).toBe(1);
261
+ const result = await testDb.commandBuilders.documents.deleteDocumentLocale({
262
+ documentId,
263
+ locale: 'es',
264
+ });
265
+ expect(result).toBeNull();
266
+ });
267
+ });
268
+ });
package/package.json ADDED
@@ -0,0 +1,91 @@
1
+ {
2
+ "name": "@byline/db-mysql",
3
+ "private": false,
4
+ "license": "MPL-2.0",
5
+ "version": "4.8.0",
6
+ "engines": {
7
+ "node": ">=20.9.0"
8
+ },
9
+ "description": "Byline CMS db mysql package",
10
+ "keywords": [
11
+ "cms",
12
+ "headless cms",
13
+ "content management"
14
+ ],
15
+ "homepage": "https://github.com/Byline-CMS/bylinecms.dev",
16
+ "bugs": {
17
+ "url": "https://github.com/Byline-CMS/bylinecms.dev/issues"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/Byline-CMS/bylinecms.dev.git",
22
+ "directory": "packages/db-mysql"
23
+ },
24
+ "type": "module",
25
+ "main": "dist/index.js",
26
+ "index": "dist/index.js",
27
+ "types": "dist/index.d.ts",
28
+ "exports": {
29
+ ".": {
30
+ "types": "./dist/index.d.ts",
31
+ "import": "./dist/index.js",
32
+ "require": "./dist/index.js"
33
+ },
34
+ "./admin": {
35
+ "types": "./dist/modules/admin/index.d.ts",
36
+ "import": "./dist/modules/admin/index.js",
37
+ "require": "./dist/modules/admin/index.js"
38
+ },
39
+ "./schema": {
40
+ "types": "./dist/database/schema/index.d.ts",
41
+ "import": "./dist/database/schema/index.js",
42
+ "require": "./dist/database/schema/index.js"
43
+ },
44
+ "./package.json": "./package.json"
45
+ },
46
+ "files": [
47
+ "dist"
48
+ ],
49
+ "dependencies": {
50
+ "drizzle-orm": "^0.45.2",
51
+ "mysql2": "^3.23.1",
52
+ "uuid": "^14.0.1",
53
+ "@byline/admin": "4.8.0",
54
+ "@byline/core": "4.8.0"
55
+ },
56
+ "devDependencies": {
57
+ "@biomejs/biome": "2.5.4",
58
+ "@types/node": "^26.1.1",
59
+ "chokidar": "^5.0.0",
60
+ "chokidar-cli": "^3.0.0",
61
+ "dotenv": "^17.4.2",
62
+ "drizzle-kit": "^0.31.10",
63
+ "npm-run-all": "^4.1.5",
64
+ "tsc-alias": "^1.9.1",
65
+ "tsx": "^4.23.1",
66
+ "typescript": "^7.0.2",
67
+ "vitest": "^4.1.10",
68
+ "@byline/auth": "4.8.0",
69
+ "@byline/db-conformance": "0.0.2",
70
+ "@byline/client": "4.8.0"
71
+ },
72
+ "publishConfig": {
73
+ "access": "public",
74
+ "registry": "https://registry.npmjs.org/"
75
+ },
76
+ "scripts": {
77
+ "dev": "chokidar 'src/**/*' -c 'npm-run-all build'",
78
+ "build": "tsc -p tsconfig.json && tsc-alias",
79
+ "clean": "node scripts/clean.js node_modules dist build .turbo",
80
+ "lint": "biome check --write --unsafe --diagnostic-level=error",
81
+ "db:init": "cd src/database && ./db_init.sh",
82
+ "drizzle:generate": "tsc -p tsconfig.json && drizzle-kit generate --config=drizzle.config.ts",
83
+ "drizzle:migrate": "drizzle-kit migrate --config=drizzle.config.ts",
84
+ "drizzle:push": "tsc -p tsconfig.json && drizzle-kit push --config=drizzle.config.ts",
85
+ "drizzle:drop": "drizzle-kit drop --config=drizzle.config.ts",
86
+ "test": "vitest run --mode=node",
87
+ "test:integration": "LOG_LEVEL=off vitest run --mode=integration",
88
+ "test:watch": "LOG_LEVEL=off vitest --mode=integration",
89
+ "typecheck": "tsc --noEmit"
90
+ }
91
+ }