@byline/db-mysql 4.14.0 → 4.15.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/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 +5 -5
|
@@ -418,3 +418,48 @@ describe('schema pins — document-paths case-sensitive collation (project-owner
|
|
|
418
418
|
expect(pathColumn?.getSQLType()).toBe('varchar(255) COLLATE utf8mb4_bin');
|
|
419
419
|
});
|
|
420
420
|
});
|
|
421
|
+
describe('schema pins — scheduled publication', () => {
|
|
422
|
+
const cfg = getTableConfig(coreSchema.documentPublishSchedules);
|
|
423
|
+
it('pins one row per document and exactly the three lifecycle ownership foreign keys', () => {
|
|
424
|
+
const documentId = cfg.columns.find((column) => column.name === 'document_id');
|
|
425
|
+
expect(documentId?.primary).toBe(true);
|
|
426
|
+
const references = cfg.foreignKeys.map((foreignKey) => ({
|
|
427
|
+
name: foreignKey.getName(),
|
|
428
|
+
local: foreignKey.reference().columns.map((column) => column.name),
|
|
429
|
+
onDelete: foreignKey.onDelete,
|
|
430
|
+
}));
|
|
431
|
+
expect(references).toEqual([
|
|
432
|
+
{
|
|
433
|
+
name: 'fk_publish_schedules_document',
|
|
434
|
+
local: ['document_id'],
|
|
435
|
+
onDelete: 'cascade',
|
|
436
|
+
},
|
|
437
|
+
{
|
|
438
|
+
name: 'fk_publish_schedules_collection',
|
|
439
|
+
local: ['collection_id'],
|
|
440
|
+
onDelete: 'cascade',
|
|
441
|
+
},
|
|
442
|
+
{
|
|
443
|
+
name: 'fk_publish_schedules_target_version',
|
|
444
|
+
local: ['target_version_id'],
|
|
445
|
+
onDelete: 'cascade',
|
|
446
|
+
},
|
|
447
|
+
]);
|
|
448
|
+
});
|
|
449
|
+
it('pins the bounded state and suspension-reason constraints', () => {
|
|
450
|
+
expect(cfg.checks.map((constraint) => constraint.name).sort()).toEqual([
|
|
451
|
+
'check_publish_schedules_state',
|
|
452
|
+
'check_publish_schedules_suspended_reason',
|
|
453
|
+
]);
|
|
454
|
+
});
|
|
455
|
+
it('pins state as the MySQL due-index discriminator and execution-expiry recovery', () => {
|
|
456
|
+
const due = cfg.indexes.find((candidate) => candidate.config.name === 'idx_document_publish_schedules_due');
|
|
457
|
+
expect(due?.config.columns.map((column) => ('name' in column ? column.name : null))).toEqual([
|
|
458
|
+
'state',
|
|
459
|
+
'next_attempt_at',
|
|
460
|
+
'publish_at',
|
|
461
|
+
]);
|
|
462
|
+
const expiry = cfg.indexes.find((candidate) => candidate.config.name === 'idx_document_publish_schedules_execution_expiry');
|
|
463
|
+
expect(expiry?.config.columns.map((column) => ('name' in column ? column.name : null))).toEqual(['execution_expires_at']);
|
|
464
|
+
});
|
|
465
|
+
});
|
package/dist/index.js
CHANGED
|
@@ -13,6 +13,7 @@ import { DBManagerImpl, TXManagerImpl } from './lib/db-manager.js';
|
|
|
13
13
|
import { createAuditCommands } from './modules/audit/audit-commands.js';
|
|
14
14
|
import { createAuditQueries } from './modules/audit/audit-queries.js';
|
|
15
15
|
import { createCounterCommands } from './modules/counters/counters-commands.js';
|
|
16
|
+
import { createSchedulerStore } from './modules/scheduler/scheduler-store.js';
|
|
16
17
|
import { classifyError } from './modules/storage/classify-error.js';
|
|
17
18
|
import { createCommandBuilders } from './modules/storage/storage-commands.js';
|
|
18
19
|
import { createQueryBuilders } from './modules/storage/storage-queries.js';
|
|
@@ -85,6 +86,7 @@ export const mysqlAdapter = ({ connectionString, collections, defaultContentLoca
|
|
|
85
86
|
const counterCommands = createCounterCommands(pool);
|
|
86
87
|
const auditCommands = createAuditCommands(dbManager);
|
|
87
88
|
const auditQueries = createAuditQueries(db);
|
|
89
|
+
const schedulerStore = createSchedulerStore(db);
|
|
88
90
|
// Boot check: run lazily on the pool's first physical connection rather
|
|
89
91
|
// than eagerly here, because `mysqlAdapter` is synchronous (mirroring
|
|
90
92
|
// `pgAdapter`) and cannot await a round trip before returning. mysql2
|
|
@@ -144,6 +146,7 @@ export const mysqlAdapter = ({ connectionString, collections, defaultContentLoca
|
|
|
144
146
|
audit: auditQueries,
|
|
145
147
|
},
|
|
146
148
|
withTransaction: (fn) => txManager.withTransaction(fn),
|
|
149
|
+
scheduler: schedulerStore,
|
|
147
150
|
classifyError,
|
|
148
151
|
drizzle: db,
|
|
149
152
|
pool,
|
|
@@ -0,0 +1,13 @@
|
|
|
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 { ISchedulerStore } from '@byline/core';
|
|
9
|
+
import type { MySql2Database } from 'drizzle-orm/mysql2';
|
|
10
|
+
import type * as schema from '../../database/schema/index.js';
|
|
11
|
+
type DatabaseConnection = MySql2Database<typeof schema>;
|
|
12
|
+
export declare function createSchedulerStore(db: DatabaseConnection): ISchedulerStore;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,237 @@
|
|
|
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
|
+
* MySQL implementation of `ISchedulerStore` (specs/2026-08-22-scheduler.md).
|
|
10
|
+
* Every due and lease comparison uses database UTC time, and every write after
|
|
11
|
+
* a claim is fenced by the claim's token.
|
|
12
|
+
*
|
|
13
|
+
* MySQL has no `UPDATE ... RETURNING`, so `claim()` locks the eligible row,
|
|
14
|
+
* updates it, and returns the pre-claim schedule from one READ COMMITTED
|
|
15
|
+
* transaction. The transaction is deliberately store-owned rather than ambient:
|
|
16
|
+
* scheduler claims must not inherit a caller's longer transaction boundary.
|
|
17
|
+
*/
|
|
18
|
+
import { randomUUID } from 'node:crypto';
|
|
19
|
+
import { MAX_BACKOFF_MS } from '@byline/core/scheduler';
|
|
20
|
+
import { sql } from 'drizzle-orm';
|
|
21
|
+
import { affectedRowCount, toDate } from '../storage/storage-utils.js';
|
|
22
|
+
/** Doc comment on `ISchedulerStore.fail`: 2048 characters, never a stack trace. */
|
|
23
|
+
const LAST_ERROR_MAX_LENGTH = 2048;
|
|
24
|
+
/** Doc comment on `ISchedulerStore.health`: `lease_owner` capped at 255 characters. */
|
|
25
|
+
const LEASE_OWNER_MAX_LENGTH = 255;
|
|
26
|
+
const MAX_BACKOFF_SECONDS = MAX_BACKOFF_MS / 1000;
|
|
27
|
+
function requiredDate(value, context) {
|
|
28
|
+
const date = toDate(value, context);
|
|
29
|
+
if (date === null) {
|
|
30
|
+
throw new Error(`scheduler store: ${context} unexpectedly returned null`);
|
|
31
|
+
}
|
|
32
|
+
return date;
|
|
33
|
+
}
|
|
34
|
+
class SchedulerStore {
|
|
35
|
+
db;
|
|
36
|
+
constructor(db) {
|
|
37
|
+
this.db = db;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* One conflict-tolerant upsert per definition. `interval_ms` always follows
|
|
41
|
+
* code, while an unleased `next_run_at` is only pulled earlier when the new
|
|
42
|
+
* interval would otherwise leave the task waiting too long.
|
|
43
|
+
*/
|
|
44
|
+
async reconcile(tasks) {
|
|
45
|
+
for (const task of tasks) {
|
|
46
|
+
await this.db.execute(sql `
|
|
47
|
+
INSERT INTO byline_recurring_tasks (name, interval_ms, next_run_at, last_status)
|
|
48
|
+
VALUES (
|
|
49
|
+
${task.name},
|
|
50
|
+
${task.intervalMs},
|
|
51
|
+
TIMESTAMPADD(MICROSECOND, ${task.intervalMs} * 1000, CURRENT_TIMESTAMP(6)),
|
|
52
|
+
'never_run'
|
|
53
|
+
)
|
|
54
|
+
ON DUPLICATE KEY UPDATE
|
|
55
|
+
next_run_at = CASE
|
|
56
|
+
WHEN lease_token IS NULL
|
|
57
|
+
AND next_run_at > TIMESTAMPADD(
|
|
58
|
+
MICROSECOND,
|
|
59
|
+
${task.intervalMs} * 1000,
|
|
60
|
+
CURRENT_TIMESTAMP(6)
|
|
61
|
+
)
|
|
62
|
+
THEN TIMESTAMPADD(MICROSECOND, ${task.intervalMs} * 1000, CURRENT_TIMESTAMP(6))
|
|
63
|
+
ELSE next_run_at
|
|
64
|
+
END,
|
|
65
|
+
interval_ms = ${task.intervalMs},
|
|
66
|
+
updated_at = CURRENT_TIMESTAMP(6)
|
|
67
|
+
`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Atomic claim through a locking read and fenced update on one physical
|
|
72
|
+
* connection. Under READ COMMITTED, contenders blocked on `FOR UPDATE`
|
|
73
|
+
* re-check the eligibility predicate after the winner commits and return
|
|
74
|
+
* null instead of observing the stale due row.
|
|
75
|
+
*/
|
|
76
|
+
async claim(params) {
|
|
77
|
+
return this.db.transaction(async (tx) => {
|
|
78
|
+
const selected = (await tx.execute(sql `
|
|
79
|
+
SELECT
|
|
80
|
+
name,
|
|
81
|
+
next_run_at AS scheduled_for,
|
|
82
|
+
CURRENT_TIMESTAMP(6) AS database_now,
|
|
83
|
+
(lease_expires_at IS NOT NULL) AS recovered_expired_lease
|
|
84
|
+
FROM byline_recurring_tasks
|
|
85
|
+
WHERE name = ${params.name}
|
|
86
|
+
AND next_run_at <= CURRENT_TIMESTAMP(6)
|
|
87
|
+
AND (lease_expires_at IS NULL OR lease_expires_at <= CURRENT_TIMESTAMP(6))
|
|
88
|
+
FOR UPDATE
|
|
89
|
+
`));
|
|
90
|
+
const row = selected[0][0];
|
|
91
|
+
if (row === undefined)
|
|
92
|
+
return null;
|
|
93
|
+
const leaseToken = randomUUID();
|
|
94
|
+
const result = await tx.execute(sql `
|
|
95
|
+
UPDATE byline_recurring_tasks SET
|
|
96
|
+
lease_token = ${leaseToken},
|
|
97
|
+
lease_owner = ${params.owner.slice(0, LEASE_OWNER_MAX_LENGTH)},
|
|
98
|
+
lease_expires_at = TIMESTAMPADD(
|
|
99
|
+
MICROSECOND,
|
|
100
|
+
${params.leaseMs} * 1000,
|
|
101
|
+
CURRENT_TIMESTAMP(6)
|
|
102
|
+
),
|
|
103
|
+
last_started_at = CURRENT_TIMESTAMP(6),
|
|
104
|
+
last_status = 'running',
|
|
105
|
+
updated_at = CURRENT_TIMESTAMP(6)
|
|
106
|
+
WHERE name = ${params.name}
|
|
107
|
+
`);
|
|
108
|
+
if (affectedRowCount(result) !== 1) {
|
|
109
|
+
throw new Error(`scheduler store: locked claim row '${params.name}' disappeared`);
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
name: row.name,
|
|
113
|
+
leaseToken,
|
|
114
|
+
scheduledFor: requiredDate(row.scheduled_for, 'scheduled_for'),
|
|
115
|
+
databaseNow: requiredDate(row.database_now, 'database_now'),
|
|
116
|
+
recoveredExpiredLease: Boolean(row.recovered_expired_lease),
|
|
117
|
+
};
|
|
118
|
+
}, { isolationLevel: 'read committed' });
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Do not add an expiry predicate: expiry makes a row claimable but does not
|
|
122
|
+
* invalidate the current token until another claimant replaces it.
|
|
123
|
+
*/
|
|
124
|
+
async renew(params) {
|
|
125
|
+
const result = await this.db.execute(sql `
|
|
126
|
+
UPDATE byline_recurring_tasks SET
|
|
127
|
+
lease_expires_at = TIMESTAMPADD(
|
|
128
|
+
MICROSECOND,
|
|
129
|
+
${params.leaseMs} * 1000,
|
|
130
|
+
CURRENT_TIMESTAMP(6)
|
|
131
|
+
),
|
|
132
|
+
updated_at = CURRENT_TIMESTAMP(6)
|
|
133
|
+
WHERE name = ${params.name} AND lease_token = ${params.leaseToken}
|
|
134
|
+
`);
|
|
135
|
+
return affectedRowCount(result) === 1;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Complete a token-matched run and schedule from the persisted cadence.
|
|
139
|
+
* `interval_ms` is read but not assigned here, so assignment order is
|
|
140
|
+
* currently irrelevant. If a future change adds it to this SET list, keep
|
|
141
|
+
* that assignment after `next_run_at` so the persisted pre-update cadence is
|
|
142
|
+
* retained; see `fail()` for MySQL's left-to-right assignment rule.
|
|
143
|
+
*/
|
|
144
|
+
async complete(params) {
|
|
145
|
+
const nextRunAt = params.workRemaining
|
|
146
|
+
? sql `CURRENT_TIMESTAMP(6)`
|
|
147
|
+
: sql `TIMESTAMPADD(MICROSECOND, interval_ms * 1000, CURRENT_TIMESTAMP(6))`;
|
|
148
|
+
const result = await this.db.execute(sql `
|
|
149
|
+
UPDATE byline_recurring_tasks SET
|
|
150
|
+
last_succeeded_at = CURRENT_TIMESTAMP(6),
|
|
151
|
+
last_status = 'succeeded',
|
|
152
|
+
last_duration_ms = ${params.durationMs},
|
|
153
|
+
consecutive_failures = 0,
|
|
154
|
+
last_error = NULL,
|
|
155
|
+
lease_token = NULL,
|
|
156
|
+
lease_owner = NULL,
|
|
157
|
+
lease_expires_at = NULL,
|
|
158
|
+
next_run_at = ${nextRunAt},
|
|
159
|
+
updated_at = CURRENT_TIMESTAMP(6)
|
|
160
|
+
WHERE name = ${params.name} AND lease_token = ${params.leaseToken}
|
|
161
|
+
`);
|
|
162
|
+
return affectedRowCount(result) === 1;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Record a token-matched failure with 1, 2, 4, 8, then 15 minute backoff.
|
|
166
|
+
* MySQL evaluates single-table UPDATE assignments from left to right, so
|
|
167
|
+
* `next_run_at` intentionally reads the old `consecutive_failures` before
|
|
168
|
+
* the following assignment increments it.
|
|
169
|
+
*/
|
|
170
|
+
async fail(params) {
|
|
171
|
+
const result = await this.db.execute(sql `
|
|
172
|
+
UPDATE byline_recurring_tasks SET
|
|
173
|
+
last_failed_at = CURRENT_TIMESTAMP(6),
|
|
174
|
+
last_status = 'failed',
|
|
175
|
+
last_duration_ms = ${params.durationMs},
|
|
176
|
+
last_error = ${params.error.slice(0, LAST_ERROR_MAX_LENGTH)},
|
|
177
|
+
lease_token = NULL,
|
|
178
|
+
lease_owner = NULL,
|
|
179
|
+
lease_expires_at = NULL,
|
|
180
|
+
next_run_at = TIMESTAMPADD(
|
|
181
|
+
SECOND,
|
|
182
|
+
LEAST(
|
|
183
|
+
60 * CAST(POWER(2, LEAST(consecutive_failures, 4)) AS UNSIGNED),
|
|
184
|
+
${MAX_BACKOFF_SECONDS}
|
|
185
|
+
),
|
|
186
|
+
CURRENT_TIMESTAMP(6)
|
|
187
|
+
),
|
|
188
|
+
consecutive_failures = consecutive_failures + 1,
|
|
189
|
+
updated_at = CURRENT_TIMESTAMP(6)
|
|
190
|
+
WHERE name = ${params.name} AND lease_token = ${params.leaseToken}
|
|
191
|
+
`);
|
|
192
|
+
return affectedRowCount(result) === 1;
|
|
193
|
+
}
|
|
194
|
+
/** Return selected health rows, or every row when names is omitted. */
|
|
195
|
+
async health(names) {
|
|
196
|
+
if (names?.length === 0)
|
|
197
|
+
return [];
|
|
198
|
+
const whereClause = names === undefined
|
|
199
|
+
? sql ``
|
|
200
|
+
: sql `WHERE name IN (${sql.join(names.map((name) => sql `${name}`), sql `, `)})`;
|
|
201
|
+
const result = (await this.db.execute(sql `
|
|
202
|
+
SELECT
|
|
203
|
+
name,
|
|
204
|
+
interval_ms,
|
|
205
|
+
next_run_at,
|
|
206
|
+
last_status,
|
|
207
|
+
last_started_at,
|
|
208
|
+
last_succeeded_at,
|
|
209
|
+
last_failed_at,
|
|
210
|
+
last_duration_ms,
|
|
211
|
+
consecutive_failures,
|
|
212
|
+
last_error,
|
|
213
|
+
(lease_expires_at IS NOT NULL AND lease_expires_at <= CURRENT_TIMESTAMP(6)) AS lease_expired,
|
|
214
|
+
CURRENT_TIMESTAMP(6) AS database_now
|
|
215
|
+
FROM byline_recurring_tasks
|
|
216
|
+
${whereClause}
|
|
217
|
+
ORDER BY name
|
|
218
|
+
`));
|
|
219
|
+
return result[0].map((row) => ({
|
|
220
|
+
name: row.name,
|
|
221
|
+
intervalMs: Number(row.interval_ms),
|
|
222
|
+
nextRunAt: requiredDate(row.next_run_at, 'next_run_at'),
|
|
223
|
+
lastStatus: row.last_status,
|
|
224
|
+
lastStartedAt: toDate(row.last_started_at, 'last_started_at'),
|
|
225
|
+
lastSucceededAt: toDate(row.last_succeeded_at, 'last_succeeded_at'),
|
|
226
|
+
lastFailedAt: toDate(row.last_failed_at, 'last_failed_at'),
|
|
227
|
+
lastDurationMs: row.last_duration_ms === null ? null : Number(row.last_duration_ms),
|
|
228
|
+
consecutiveFailures: Number(row.consecutive_failures),
|
|
229
|
+
lastError: row.last_error,
|
|
230
|
+
leaseExpired: Boolean(row.lease_expired),
|
|
231
|
+
databaseNow: requiredDate(row.database_now, 'database_now'),
|
|
232
|
+
}));
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
export function createSchedulerStore(db) {
|
|
236
|
+
return new SchedulerStore(db);
|
|
237
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
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 { ClaimedDocumentPublishSchedule, ConfirmDocumentPublishScheduleResult, DocumentPublishSchedule, DocumentPublishSchedulePage, DocumentPublishScheduleState, IDocumentPublishScheduleCommands, IDocumentPublishScheduleQueries, ScheduleDocumentPublishResult, SuspendDocumentPublishScheduleResult } from '@byline/core';
|
|
9
|
+
import type { MySql2Database } from 'drizzle-orm/mysql2';
|
|
10
|
+
import type * as schema from '../../database/schema/index.js';
|
|
11
|
+
import type { DBManager } from '../../lib/db-manager.js';
|
|
12
|
+
type DatabaseConnection = MySql2Database<typeof schema>;
|
|
13
|
+
/** MySQL scheduled-publication writes on the ambient document transaction. */
|
|
14
|
+
export declare class DocumentPublishScheduleCommands implements IDocumentPublishScheduleCommands {
|
|
15
|
+
private dbManager;
|
|
16
|
+
constructor(dbManager: DBManager);
|
|
17
|
+
private get db();
|
|
18
|
+
private lockCurrentVersion;
|
|
19
|
+
private lockSchedule;
|
|
20
|
+
private readSchedule;
|
|
21
|
+
schedule(params: {
|
|
22
|
+
documentId: string;
|
|
23
|
+
collectionId: string;
|
|
24
|
+
expectedVersionId: string;
|
|
25
|
+
publishAt: Date;
|
|
26
|
+
actorId: string | null;
|
|
27
|
+
}): Promise<ScheduleDocumentPublishResult>;
|
|
28
|
+
confirm(params: {
|
|
29
|
+
documentId: string;
|
|
30
|
+
collectionId: string;
|
|
31
|
+
expectedVersionId: string;
|
|
32
|
+
actorId: string | null;
|
|
33
|
+
}): Promise<ConfirmDocumentPublishScheduleResult>;
|
|
34
|
+
cancel(params: {
|
|
35
|
+
documentId: string;
|
|
36
|
+
collectionId: string;
|
|
37
|
+
}): Promise<DocumentPublishSchedule | null>;
|
|
38
|
+
suspendForContentEdit(params: {
|
|
39
|
+
documentId: string;
|
|
40
|
+
collectionId: string;
|
|
41
|
+
}): Promise<SuspendDocumentPublishScheduleResult>;
|
|
42
|
+
claimDue(params: {
|
|
43
|
+
batchSize: number;
|
|
44
|
+
leaseMs: number;
|
|
45
|
+
}): Promise<ClaimedDocumentPublishSchedule[]>;
|
|
46
|
+
lockClaim(params: {
|
|
47
|
+
documentId: string;
|
|
48
|
+
executionToken: string;
|
|
49
|
+
}): Promise<DocumentPublishSchedule | null>;
|
|
50
|
+
deleteClaim(params: {
|
|
51
|
+
documentId: string;
|
|
52
|
+
executionToken: string;
|
|
53
|
+
}): Promise<boolean>;
|
|
54
|
+
suspendClaimForContentEdit(params: {
|
|
55
|
+
documentId: string;
|
|
56
|
+
executionToken: string;
|
|
57
|
+
}): Promise<boolean>;
|
|
58
|
+
/**
|
|
59
|
+
* `attempt_count` is read but not assigned, so MySQL's left-to-right SET
|
|
60
|
+
* evaluation cannot shift the backoff sequence. If it is ever assigned in
|
|
61
|
+
* this statement, keep `next_attempt_at` before that assignment.
|
|
62
|
+
*/
|
|
63
|
+
releaseClaim(params: {
|
|
64
|
+
documentId: string;
|
|
65
|
+
executionToken: string;
|
|
66
|
+
error: string;
|
|
67
|
+
}): Promise<boolean>;
|
|
68
|
+
}
|
|
69
|
+
/** MySQL scheduled-publication reads. */
|
|
70
|
+
export declare class DocumentPublishScheduleQueries implements IDocumentPublishScheduleQueries {
|
|
71
|
+
private db;
|
|
72
|
+
constructor(db: DatabaseConnection);
|
|
73
|
+
get(params: {
|
|
74
|
+
documentId: string;
|
|
75
|
+
collectionId: string;
|
|
76
|
+
}): Promise<DocumentPublishSchedule | null>;
|
|
77
|
+
list(params: {
|
|
78
|
+
collectionIds: readonly string[];
|
|
79
|
+
states?: readonly DocumentPublishScheduleState[];
|
|
80
|
+
lastAuthorizedBy?: string;
|
|
81
|
+
page: number;
|
|
82
|
+
pageSize: number;
|
|
83
|
+
}): Promise<DocumentPublishSchedulePage>;
|
|
84
|
+
}
|
|
85
|
+
export {};
|