@byline/db-mysql 4.14.1 → 4.16.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/dist/database/schema/common.js +1 -1
- package/dist/database/schema/index.d.ts +575 -0
- package/dist/database/schema/index.js +81 -1
- package/dist/database/schema/schema-pins.test.node.js +45 -0
- package/dist/index.js +3 -0
- package/dist/modules/scheduler/scheduler-store.d.ts +13 -0
- package/dist/modules/scheduler/scheduler-store.js +237 -0
- package/dist/modules/storage/publish-schedules.d.ts +85 -0
- package/dist/modules/storage/publish-schedules.js +406 -0
- package/dist/modules/storage/storage-commands.d.ts +2 -0
- package/dist/modules/storage/storage-commands.js +3 -0
- package/dist/modules/storage/storage-queries.d.ts +2 -0
- package/dist/modules/storage/storage-queries.js +3 -0
- package/package.json +6 -6
|
@@ -0,0 +1,406 @@
|
|
|
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 { randomUUID } from 'node:crypto';
|
|
9
|
+
import { sql } from 'drizzle-orm';
|
|
10
|
+
import { affectedRowCount, toDate } from './storage-utils.js';
|
|
11
|
+
const LAST_ERROR_MAX_LENGTH = 2_048;
|
|
12
|
+
function requiredDate(value, column) {
|
|
13
|
+
const date = toDate(value, column);
|
|
14
|
+
if (date === null)
|
|
15
|
+
throw new Error(`publish schedules: ${column} unexpectedly returned null`);
|
|
16
|
+
return date;
|
|
17
|
+
}
|
|
18
|
+
function toSchedule(row) {
|
|
19
|
+
return {
|
|
20
|
+
documentId: row.document_id,
|
|
21
|
+
collectionId: row.collection_id,
|
|
22
|
+
targetVersionId: row.target_version_id,
|
|
23
|
+
publishAt: requiredDate(row.publish_at, 'publish_at'),
|
|
24
|
+
state: row.state,
|
|
25
|
+
suspendedAt: toDate(row.suspended_at, 'suspended_at'),
|
|
26
|
+
suspendedReason: row.suspended_reason,
|
|
27
|
+
scheduledBy: row.scheduled_by,
|
|
28
|
+
lastAuthorizedBy: row.last_authorized_by,
|
|
29
|
+
lastAuthorizedAt: requiredDate(row.last_authorized_at, 'last_authorized_at'),
|
|
30
|
+
scheduledAt: requiredDate(row.scheduled_at, 'scheduled_at'),
|
|
31
|
+
updatedAt: requiredDate(row.updated_at, 'updated_at'),
|
|
32
|
+
executionToken: row.execution_token,
|
|
33
|
+
executionExpiresAt: toDate(row.execution_expires_at, 'execution_expires_at'),
|
|
34
|
+
lastAttemptAt: toDate(row.last_attempt_at, 'last_attempt_at'),
|
|
35
|
+
nextAttemptAt: requiredDate(row.next_attempt_at, 'next_attempt_at'),
|
|
36
|
+
attemptCount: Number(row.attempt_count),
|
|
37
|
+
lastError: row.last_error,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function resultRows(result) {
|
|
41
|
+
return result[0];
|
|
42
|
+
}
|
|
43
|
+
function firstRow(result) {
|
|
44
|
+
return resultRows(result)[0];
|
|
45
|
+
}
|
|
46
|
+
function sanitizedError(error) {
|
|
47
|
+
return error
|
|
48
|
+
.replace(/\r?\n[\s\S]*$/, '')
|
|
49
|
+
.trim()
|
|
50
|
+
.slice(0, LAST_ERROR_MAX_LENGTH);
|
|
51
|
+
}
|
|
52
|
+
/** MySQL scheduled-publication writes on the ambient document transaction. */
|
|
53
|
+
export class DocumentPublishScheduleCommands {
|
|
54
|
+
dbManager;
|
|
55
|
+
constructor(dbManager) {
|
|
56
|
+
this.dbManager = dbManager;
|
|
57
|
+
}
|
|
58
|
+
get db() {
|
|
59
|
+
return this.dbManager.get();
|
|
60
|
+
}
|
|
61
|
+
async lockCurrentVersion(params) {
|
|
62
|
+
const document = await this.db.execute(sql `
|
|
63
|
+
SELECT id
|
|
64
|
+
FROM byline_documents
|
|
65
|
+
WHERE id = ${params.documentId}
|
|
66
|
+
AND collection_id = ${params.collectionId}
|
|
67
|
+
FOR UPDATE
|
|
68
|
+
`);
|
|
69
|
+
if (resultRows(document).length === 0)
|
|
70
|
+
return null;
|
|
71
|
+
const current = await this.db.execute(sql `
|
|
72
|
+
SELECT id
|
|
73
|
+
FROM byline_document_versions
|
|
74
|
+
WHERE document_id = ${params.documentId}
|
|
75
|
+
AND collection_id = ${params.collectionId}
|
|
76
|
+
AND is_deleted = false
|
|
77
|
+
ORDER BY id DESC
|
|
78
|
+
LIMIT 1
|
|
79
|
+
`);
|
|
80
|
+
return firstRow(current)?.id ?? null;
|
|
81
|
+
}
|
|
82
|
+
async lockSchedule(documentId) {
|
|
83
|
+
const result = await this.db.execute(sql `
|
|
84
|
+
SELECT *
|
|
85
|
+
FROM byline_document_publish_schedules
|
|
86
|
+
WHERE document_id = ${documentId}
|
|
87
|
+
FOR UPDATE
|
|
88
|
+
`);
|
|
89
|
+
const row = firstRow(result);
|
|
90
|
+
return row === undefined ? null : toSchedule(row);
|
|
91
|
+
}
|
|
92
|
+
async readSchedule(documentId) {
|
|
93
|
+
const result = await this.db.execute(sql `
|
|
94
|
+
SELECT * FROM byline_document_publish_schedules WHERE document_id = ${documentId}
|
|
95
|
+
`);
|
|
96
|
+
const row = firstRow(result);
|
|
97
|
+
if (row === undefined)
|
|
98
|
+
throw new Error('publish schedules: schedule row disappeared');
|
|
99
|
+
return toSchedule(row);
|
|
100
|
+
}
|
|
101
|
+
async schedule(params) {
|
|
102
|
+
const currentVersionId = await this.lockCurrentVersion(params);
|
|
103
|
+
if (currentVersionId === null)
|
|
104
|
+
return { status: 'document_not_found' };
|
|
105
|
+
if (currentVersionId !== params.expectedVersionId)
|
|
106
|
+
return { status: 'version_mismatch' };
|
|
107
|
+
const previous = await this.lockSchedule(params.documentId);
|
|
108
|
+
const validation = await this.db.execute(sql `
|
|
109
|
+
SELECT
|
|
110
|
+
(${params.publishAt} > CURRENT_TIMESTAMP(6)) AS publish_at_is_future,
|
|
111
|
+
(${previous?.executionToken ?? null} IS NOT NULL
|
|
112
|
+
AND ${previous?.executionExpiresAt ?? null} > CURRENT_TIMESTAMP(6)) AS execution_is_live
|
|
113
|
+
`);
|
|
114
|
+
const flags = firstRow(validation);
|
|
115
|
+
if (!flags?.publish_at_is_future)
|
|
116
|
+
return { status: 'publish_at_not_future' };
|
|
117
|
+
if (flags.execution_is_live)
|
|
118
|
+
return { status: 'execution_in_progress' };
|
|
119
|
+
if (previous === null) {
|
|
120
|
+
await this.db.execute(sql `
|
|
121
|
+
INSERT INTO byline_document_publish_schedules (
|
|
122
|
+
document_id, collection_id, target_version_id, publish_at,
|
|
123
|
+
state, suspended_at, suspended_reason,
|
|
124
|
+
scheduled_by, last_authorized_by, last_authorized_at,
|
|
125
|
+
execution_token, execution_expires_at, last_attempt_at,
|
|
126
|
+
next_attempt_at, attempt_count, last_error, updated_at
|
|
127
|
+
) VALUES (
|
|
128
|
+
${params.documentId},
|
|
129
|
+
${params.collectionId},
|
|
130
|
+
${params.expectedVersionId},
|
|
131
|
+
${params.publishAt},
|
|
132
|
+
'armed', NULL, NULL,
|
|
133
|
+
${params.actorId}, ${params.actorId}, CURRENT_TIMESTAMP(6),
|
|
134
|
+
NULL, NULL, NULL,
|
|
135
|
+
${params.publishAt}, 0, NULL, CURRENT_TIMESTAMP(6)
|
|
136
|
+
)
|
|
137
|
+
`);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
await this.db.execute(sql `
|
|
141
|
+
UPDATE byline_document_publish_schedules SET
|
|
142
|
+
target_version_id = ${params.expectedVersionId},
|
|
143
|
+
publish_at = ${params.publishAt},
|
|
144
|
+
state = 'armed',
|
|
145
|
+
suspended_at = NULL,
|
|
146
|
+
suspended_reason = NULL,
|
|
147
|
+
last_authorized_by = ${params.actorId},
|
|
148
|
+
last_authorized_at = CURRENT_TIMESTAMP(6),
|
|
149
|
+
execution_token = NULL,
|
|
150
|
+
execution_expires_at = NULL,
|
|
151
|
+
last_attempt_at = NULL,
|
|
152
|
+
next_attempt_at = ${params.publishAt},
|
|
153
|
+
attempt_count = 0,
|
|
154
|
+
last_error = NULL,
|
|
155
|
+
updated_at = CURRENT_TIMESTAMP(6)
|
|
156
|
+
WHERE document_id = ${params.documentId}
|
|
157
|
+
`);
|
|
158
|
+
}
|
|
159
|
+
return { status: 'scheduled', schedule: await this.readSchedule(params.documentId), previous };
|
|
160
|
+
}
|
|
161
|
+
async confirm(params) {
|
|
162
|
+
const currentVersionId = await this.lockCurrentVersion(params);
|
|
163
|
+
if (currentVersionId === null || currentVersionId !== params.expectedVersionId) {
|
|
164
|
+
return { status: 'version_mismatch' };
|
|
165
|
+
}
|
|
166
|
+
const previous = await this.lockSchedule(params.documentId);
|
|
167
|
+
if (previous === null || previous.collectionId !== params.collectionId) {
|
|
168
|
+
return { status: 'schedule_not_found' };
|
|
169
|
+
}
|
|
170
|
+
if (previous.state !== 'needs_reconfirm')
|
|
171
|
+
return { status: 'not_suspended' };
|
|
172
|
+
await this.db.execute(sql `
|
|
173
|
+
UPDATE byline_document_publish_schedules SET
|
|
174
|
+
target_version_id = ${params.expectedVersionId},
|
|
175
|
+
state = 'armed',
|
|
176
|
+
suspended_at = NULL,
|
|
177
|
+
suspended_reason = NULL,
|
|
178
|
+
last_authorized_by = ${params.actorId},
|
|
179
|
+
last_authorized_at = CURRENT_TIMESTAMP(6),
|
|
180
|
+
execution_token = NULL,
|
|
181
|
+
execution_expires_at = NULL,
|
|
182
|
+
last_attempt_at = NULL,
|
|
183
|
+
next_attempt_at = publish_at,
|
|
184
|
+
attempt_count = 0,
|
|
185
|
+
last_error = NULL,
|
|
186
|
+
updated_at = CURRENT_TIMESTAMP(6)
|
|
187
|
+
WHERE document_id = ${params.documentId}
|
|
188
|
+
`);
|
|
189
|
+
return {
|
|
190
|
+
status: 'confirmed',
|
|
191
|
+
schedule: await this.readSchedule(params.documentId),
|
|
192
|
+
previousTargetVersionId: previous.targetVersionId,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
async cancel(params) {
|
|
196
|
+
const locked = await this.lockSchedule(params.documentId);
|
|
197
|
+
if (locked === null || locked.collectionId !== params.collectionId)
|
|
198
|
+
return null;
|
|
199
|
+
const result = await this.db.execute(sql `
|
|
200
|
+
DELETE FROM byline_document_publish_schedules
|
|
201
|
+
WHERE document_id = ${params.documentId}
|
|
202
|
+
AND collection_id = ${params.collectionId}
|
|
203
|
+
`);
|
|
204
|
+
return affectedRowCount(result) === 1 ? locked : null;
|
|
205
|
+
}
|
|
206
|
+
async suspendForContentEdit(params) {
|
|
207
|
+
const locked = await this.lockSchedule(params.documentId);
|
|
208
|
+
if (locked === null || locked.collectionId !== params.collectionId) {
|
|
209
|
+
return { status: 'schedule_not_found' };
|
|
210
|
+
}
|
|
211
|
+
if (locked.state === 'needs_reconfirm')
|
|
212
|
+
return { status: 'already_suspended' };
|
|
213
|
+
await this.db.execute(sql `
|
|
214
|
+
UPDATE byline_document_publish_schedules SET
|
|
215
|
+
state = 'needs_reconfirm',
|
|
216
|
+
suspended_at = CURRENT_TIMESTAMP(6),
|
|
217
|
+
suspended_reason = 'content_edited',
|
|
218
|
+
execution_token = NULL,
|
|
219
|
+
execution_expires_at = NULL,
|
|
220
|
+
updated_at = CURRENT_TIMESTAMP(6)
|
|
221
|
+
WHERE document_id = ${params.documentId}
|
|
222
|
+
`);
|
|
223
|
+
return { status: 'suspended', schedule: await this.readSchedule(params.documentId) };
|
|
224
|
+
}
|
|
225
|
+
async claimDue(params) {
|
|
226
|
+
const batchSize = Math.max(0, Math.trunc(params.batchSize));
|
|
227
|
+
if (batchSize === 0)
|
|
228
|
+
return [];
|
|
229
|
+
return this.db.transaction(async (tx) => {
|
|
230
|
+
const selected = await tx.execute(sql `
|
|
231
|
+
SELECT document_id, execution_token
|
|
232
|
+
FROM byline_document_publish_schedules
|
|
233
|
+
WHERE state = 'armed'
|
|
234
|
+
AND publish_at <= CURRENT_TIMESTAMP(6)
|
|
235
|
+
AND next_attempt_at <= CURRENT_TIMESTAMP(6)
|
|
236
|
+
AND (execution_expires_at IS NULL OR execution_expires_at <= CURRENT_TIMESTAMP(6))
|
|
237
|
+
ORDER BY publish_at, document_id
|
|
238
|
+
LIMIT ${batchSize}
|
|
239
|
+
FOR UPDATE SKIP LOCKED
|
|
240
|
+
`);
|
|
241
|
+
const candidates = resultRows(selected);
|
|
242
|
+
const claimed = [];
|
|
243
|
+
for (const candidate of candidates) {
|
|
244
|
+
const token = randomUUID();
|
|
245
|
+
const update = await tx.execute(sql `
|
|
246
|
+
UPDATE byline_document_publish_schedules SET
|
|
247
|
+
execution_token = ${token},
|
|
248
|
+
execution_expires_at = TIMESTAMPADD(
|
|
249
|
+
MICROSECOND,
|
|
250
|
+
${params.leaseMs} * 1000,
|
|
251
|
+
CURRENT_TIMESTAMP(6)
|
|
252
|
+
),
|
|
253
|
+
last_attempt_at = CURRENT_TIMESTAMP(6),
|
|
254
|
+
attempt_count = attempt_count + 1,
|
|
255
|
+
updated_at = CURRENT_TIMESTAMP(6)
|
|
256
|
+
WHERE document_id = ${candidate.document_id}
|
|
257
|
+
`);
|
|
258
|
+
if (affectedRowCount(update) !== 1) {
|
|
259
|
+
throw new Error(`publish schedules: locked row '${candidate.document_id}' disappeared`);
|
|
260
|
+
}
|
|
261
|
+
const reread = await tx.execute(sql `
|
|
262
|
+
SELECT *
|
|
263
|
+
FROM byline_document_publish_schedules
|
|
264
|
+
WHERE document_id = ${candidate.document_id}
|
|
265
|
+
`);
|
|
266
|
+
const row = firstRow(reread);
|
|
267
|
+
if (row === undefined) {
|
|
268
|
+
throw new Error(`publish schedules: claimed row '${candidate.document_id}' disappeared`);
|
|
269
|
+
}
|
|
270
|
+
const schedule = toSchedule(row);
|
|
271
|
+
if (schedule.executionToken === null ||
|
|
272
|
+
schedule.executionExpiresAt === null ||
|
|
273
|
+
schedule.lastAttemptAt === null) {
|
|
274
|
+
throw new Error('publish schedules: claimed row returned without its execution fence');
|
|
275
|
+
}
|
|
276
|
+
claimed.push({
|
|
277
|
+
...schedule,
|
|
278
|
+
executionToken: schedule.executionToken,
|
|
279
|
+
executionExpiresAt: schedule.executionExpiresAt,
|
|
280
|
+
lastAttemptAt: schedule.lastAttemptAt,
|
|
281
|
+
databaseNow: schedule.lastAttemptAt,
|
|
282
|
+
recoveredExpiredClaim: candidate.execution_token !== null,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
return claimed;
|
|
286
|
+
}, { isolationLevel: 'read committed' });
|
|
287
|
+
}
|
|
288
|
+
async lockClaim(params) {
|
|
289
|
+
const result = await this.db.execute(sql `
|
|
290
|
+
SELECT *
|
|
291
|
+
FROM byline_document_publish_schedules
|
|
292
|
+
WHERE document_id = ${params.documentId}
|
|
293
|
+
AND execution_token = ${params.executionToken}
|
|
294
|
+
FOR UPDATE
|
|
295
|
+
`);
|
|
296
|
+
const row = firstRow(result);
|
|
297
|
+
return row === undefined ? null : toSchedule(row);
|
|
298
|
+
}
|
|
299
|
+
async deleteClaim(params) {
|
|
300
|
+
const result = await this.db.execute(sql `
|
|
301
|
+
DELETE FROM byline_document_publish_schedules
|
|
302
|
+
WHERE document_id = ${params.documentId}
|
|
303
|
+
AND execution_token = ${params.executionToken}
|
|
304
|
+
`);
|
|
305
|
+
return affectedRowCount(result) === 1;
|
|
306
|
+
}
|
|
307
|
+
async suspendClaimForContentEdit(params) {
|
|
308
|
+
const result = await this.db.execute(sql `
|
|
309
|
+
UPDATE byline_document_publish_schedules SET
|
|
310
|
+
state = 'needs_reconfirm',
|
|
311
|
+
suspended_at = CURRENT_TIMESTAMP(6),
|
|
312
|
+
suspended_reason = 'content_edited',
|
|
313
|
+
execution_token = NULL,
|
|
314
|
+
execution_expires_at = NULL,
|
|
315
|
+
last_attempt_at = NULL,
|
|
316
|
+
next_attempt_at = publish_at,
|
|
317
|
+
attempt_count = 0,
|
|
318
|
+
last_error = NULL,
|
|
319
|
+
updated_at = CURRENT_TIMESTAMP(6)
|
|
320
|
+
WHERE document_id = ${params.documentId}
|
|
321
|
+
AND execution_token = ${params.executionToken}
|
|
322
|
+
`);
|
|
323
|
+
return affectedRowCount(result) === 1;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* `attempt_count` is read but not assigned, so MySQL's left-to-right SET
|
|
327
|
+
* evaluation cannot shift the backoff sequence. If it is ever assigned in
|
|
328
|
+
* this statement, keep `next_attempt_at` before that assignment.
|
|
329
|
+
*/
|
|
330
|
+
async releaseClaim(params) {
|
|
331
|
+
const result = await this.db.execute(sql `
|
|
332
|
+
UPDATE byline_document_publish_schedules SET
|
|
333
|
+
execution_token = NULL,
|
|
334
|
+
execution_expires_at = NULL,
|
|
335
|
+
last_error = ${sanitizedError(params.error)},
|
|
336
|
+
next_attempt_at = TIMESTAMPADD(
|
|
337
|
+
MINUTE,
|
|
338
|
+
CASE attempt_count
|
|
339
|
+
WHEN 1 THEN 1
|
|
340
|
+
WHEN 2 THEN 2
|
|
341
|
+
WHEN 3 THEN 4
|
|
342
|
+
WHEN 4 THEN 8
|
|
343
|
+
ELSE 15
|
|
344
|
+
END,
|
|
345
|
+
CURRENT_TIMESTAMP(6)
|
|
346
|
+
),
|
|
347
|
+
updated_at = CURRENT_TIMESTAMP(6)
|
|
348
|
+
WHERE document_id = ${params.documentId}
|
|
349
|
+
AND execution_token = ${params.executionToken}
|
|
350
|
+
`);
|
|
351
|
+
return affectedRowCount(result) === 1;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
/** MySQL scheduled-publication reads. */
|
|
355
|
+
export class DocumentPublishScheduleQueries {
|
|
356
|
+
db;
|
|
357
|
+
constructor(db) {
|
|
358
|
+
this.db = db;
|
|
359
|
+
}
|
|
360
|
+
async get(params) {
|
|
361
|
+
const result = await this.db.execute(sql `
|
|
362
|
+
SELECT *
|
|
363
|
+
FROM byline_document_publish_schedules
|
|
364
|
+
WHERE document_id = ${params.documentId}
|
|
365
|
+
AND collection_id = ${params.collectionId}
|
|
366
|
+
`);
|
|
367
|
+
const row = firstRow(result);
|
|
368
|
+
return row === undefined ? null : toSchedule(row);
|
|
369
|
+
}
|
|
370
|
+
async list(params) {
|
|
371
|
+
if (params.collectionIds.length === 0)
|
|
372
|
+
return { schedules: [], total: 0 };
|
|
373
|
+
const collectionList = sql.join(params.collectionIds.map((id) => sql `${id}`), sql `, `);
|
|
374
|
+
const statePredicate = params.states === undefined
|
|
375
|
+
? sql ``
|
|
376
|
+
: params.states.length === 0
|
|
377
|
+
? sql `AND false`
|
|
378
|
+
: sql `AND state IN (${sql.join(params.states.map((state) => sql `${state}`), sql `, `)})`;
|
|
379
|
+
const actorPredicate = params.lastAuthorizedBy === undefined
|
|
380
|
+
? sql ``
|
|
381
|
+
: sql `AND last_authorized_by = ${params.lastAuthorizedBy}`;
|
|
382
|
+
const offset = Math.max(0, (params.page - 1) * params.pageSize);
|
|
383
|
+
const pageSize = Math.max(0, Math.trunc(params.pageSize));
|
|
384
|
+
const count = await this.db.execute(sql `
|
|
385
|
+
SELECT count(*) AS total
|
|
386
|
+
FROM byline_document_publish_schedules
|
|
387
|
+
WHERE collection_id IN (${collectionList})
|
|
388
|
+
${statePredicate}
|
|
389
|
+
${actorPredicate}
|
|
390
|
+
`);
|
|
391
|
+
const total = Number(firstRow(count)?.total ?? 0);
|
|
392
|
+
if (pageSize === 0 || total === 0)
|
|
393
|
+
return { schedules: [], total };
|
|
394
|
+
const rows = await this.db.execute(sql `
|
|
395
|
+
SELECT *
|
|
396
|
+
FROM byline_document_publish_schedules
|
|
397
|
+
WHERE collection_id IN (${collectionList})
|
|
398
|
+
${statePredicate}
|
|
399
|
+
${actorPredicate}
|
|
400
|
+
ORDER BY publish_at, document_id
|
|
401
|
+
LIMIT ${pageSize}
|
|
402
|
+
OFFSET ${offset}
|
|
403
|
+
`);
|
|
404
|
+
return { schedules: resultRows(rows).map(toSchedule), total };
|
|
405
|
+
}
|
|
406
|
+
}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* Copyright (c) Infonomic Company Limited
|
|
7
7
|
*/
|
|
8
8
|
import type { CollectionDefinition, ICollectionCommands, IDocumentCommands, TreeDeleteMutationResult, TreeMutationResult } from '@byline/core';
|
|
9
|
+
import { DocumentPublishScheduleCommands } from './publish-schedules.js';
|
|
9
10
|
import type { DBManager } from '../../lib/db-manager.js';
|
|
10
11
|
/**
|
|
11
12
|
* CollectionCommands
|
|
@@ -78,6 +79,7 @@ export declare class CollectionCommands implements ICollectionCommands {
|
|
|
78
79
|
export declare class DocumentCommands implements IDocumentCommands {
|
|
79
80
|
private dbManager;
|
|
80
81
|
private defaultContentLocale;
|
|
82
|
+
readonly publishSchedules: DocumentPublishScheduleCommands;
|
|
81
83
|
constructor(dbManager: DBManager, defaultContentLocale: string);
|
|
82
84
|
/**
|
|
83
85
|
* The executor for this call — the ambient transaction when a
|
|
@@ -10,6 +10,7 @@ import { and, desc, eq, inArray, ne, notInArray, sql } from 'drizzle-orm';
|
|
|
10
10
|
import { v7 as uuidv7 } from 'uuid';
|
|
11
11
|
import { booleanStore, collections, currentDocumentsView, datetimeStore, documentAvailableLocales, documentPaths, documentRelationships, documents, documentVersions, fileStore, jsonStore, metaStore, numericStore, relationStore, textStore, } from '../../database/schema/index.js';
|
|
12
12
|
import { classifyError } from './classify-error.js';
|
|
13
|
+
import { DocumentPublishScheduleCommands } from './publish-schedules.js';
|
|
13
14
|
import { prepareFieldInsertBuckets } from './storage-insert.js';
|
|
14
15
|
import { affectedRowCount, getFirstOrThrow } from './storage-utils.js';
|
|
15
16
|
/**
|
|
@@ -135,9 +136,11 @@ export class CollectionCommands {
|
|
|
135
136
|
export class DocumentCommands {
|
|
136
137
|
dbManager;
|
|
137
138
|
defaultContentLocale;
|
|
139
|
+
publishSchedules;
|
|
138
140
|
constructor(dbManager, defaultContentLocale) {
|
|
139
141
|
this.dbManager = dbManager;
|
|
140
142
|
this.defaultContentLocale = defaultContentLocale;
|
|
143
|
+
this.publishSchedules = new DocumentPublishScheduleCommands(dbManager);
|
|
141
144
|
}
|
|
142
145
|
/**
|
|
143
146
|
* The executor for this call — the ambient transaction when a
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
*/
|
|
25
25
|
import type { CollectionDefinition, DocumentFilter, FieldFilterOperator, FieldSort, FlattenedStore, ICollectionQueries, IDocumentQueries, MissingLocalePolicy, ReadMode } from '@byline/core';
|
|
26
26
|
import type { MySql2Database } from 'drizzle-orm/mysql2';
|
|
27
|
+
import { DocumentPublishScheduleQueries } from './publish-schedules.js';
|
|
27
28
|
import type * as schema from '../../database/schema/index.js';
|
|
28
29
|
import type { DBManager } from '../../lib/db-manager.js';
|
|
29
30
|
type DatabaseConnection = MySql2Database<typeof schema>;
|
|
@@ -74,6 +75,7 @@ export declare class CollectionQueries implements ICollectionQueries {
|
|
|
74
75
|
* DocumentQueries
|
|
75
76
|
*/
|
|
76
77
|
export declare class DocumentQueries implements IDocumentQueries {
|
|
78
|
+
readonly publishSchedules: DocumentPublishScheduleQueries;
|
|
77
79
|
private db;
|
|
78
80
|
private transactionDb;
|
|
79
81
|
private collections;
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
import { ERR_DATABASE, ERR_NOT_FOUND, extractFlattenedFieldValue, getLogger, orderByContentLocale, resolveIdentityField, resolveStoreTypes, restoreFieldSetData, } from '@byline/core';
|
|
26
26
|
import { and, desc, eq, inArray, isNotNull, isNull, sql } from 'drizzle-orm';
|
|
27
27
|
import { collections, currentDocumentsView, currentPublishedDocumentsView, documentAvailableLocales, documentPaths, documentRelationships, documents, documentVersionLocales, documentVersions, metaStore, } from '../../database/schema/index.js';
|
|
28
|
+
import { DocumentPublishScheduleQueries } from './publish-schedules.js';
|
|
28
29
|
import { normalizeRow } from './normalize-row.js';
|
|
29
30
|
import { allStoreTypes, storeSelectList, storeTableNames, } from './storage-store-manifest.js';
|
|
30
31
|
import { toDate } from './storage-utils.js';
|
|
@@ -61,12 +62,14 @@ export class CollectionQueries {
|
|
|
61
62
|
* DocumentQueries
|
|
62
63
|
*/
|
|
63
64
|
export class DocumentQueries {
|
|
65
|
+
publishSchedules;
|
|
64
66
|
db;
|
|
65
67
|
transactionDb;
|
|
66
68
|
collections;
|
|
67
69
|
defaultContentLocale;
|
|
68
70
|
collectionPathCache = new Map();
|
|
69
71
|
constructor(db, collections, defaultContentLocale, transactionDb) {
|
|
72
|
+
this.publishSchedules = new DocumentPublishScheduleQueries(db);
|
|
70
73
|
this.db = db;
|
|
71
74
|
this.transactionDb = transactionDb;
|
|
72
75
|
this.collections = collections;
|
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.
|
|
5
|
+
"version": "4.16.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.4",
|
|
52
52
|
"uuid": "^14.0.2",
|
|
53
|
-
"@byline/admin": "4.
|
|
54
|
-
"@byline/core": "4.
|
|
53
|
+
"@byline/admin": "4.16.0",
|
|
54
|
+
"@byline/core": "4.16.0"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
57
|
"@biomejs/biome": "2.5.9",
|
|
@@ -65,9 +65,9 @@
|
|
|
65
65
|
"tsx": "^4.23.12",
|
|
66
66
|
"typescript": "^7.0.2",
|
|
67
67
|
"vitest": "^4.1.11",
|
|
68
|
-
"@byline/client": "4.
|
|
69
|
-
"@byline/
|
|
70
|
-
"@byline/
|
|
68
|
+
"@byline/client": "4.16.0",
|
|
69
|
+
"@byline/db-conformance": "0.0.10",
|
|
70
|
+
"@byline/auth": "4.16.0"
|
|
71
71
|
},
|
|
72
72
|
"publishConfig": {
|
|
73
73
|
"access": "public",
|