velocious 1.0.666 → 1.0.668
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 +40 -11
- package/build/background-jobs/adapter-client.js +22 -0
- package/build/background-jobs/adapter.js +15 -0
- package/build/background-jobs/client.js +147 -1
- package/build/background-jobs/job-semantics.js +30 -0
- package/build/background-jobs/job.js +26 -1
- package/build/background-jobs/local-adapter.js +36 -8
- package/build/background-jobs/local-store.js +410 -39
- package/build/background-jobs/main.js +69 -2
- package/build/background-jobs/platform-job.js +26 -1
- package/build/background-jobs/runtime.js +58 -0
- package/build/background-jobs/store.js +374 -6
- package/build/background-jobs/types.js +27 -2
- package/build/background-jobs/web/controller.js +1 -0
- package/build/configuration.js +30 -6
- package/build/database/drivers/mssql/column.js +2 -2
- package/build/database/drivers/mssql/table.js +19 -5
- package/build/database/pool/async-tracked-multi-connection.js +2 -1
- package/build/database/query/alter-table-base.js +8 -1
- package/build/database/record/attachments/store.js +2 -2
- package/build/environment-handlers/node/cli/commands/test/timing-manifest/merge.js +2 -48
- package/build/environment-handlers/node/cli/commands/test.js +17 -69
- package/build/frontend-model-controller.js +22 -2
- package/build/frontend-models/websocket-channel.js +18 -0
- package/build/http-server/client/websocket-session.js +41 -4
- package/build/http-server/websocket-channel.js +14 -0
- package/build/http-server/websocket-event-log-store.js +85 -12
- package/build/http-server/websocket-events-host.js +17 -7
- package/build/src/background-jobs/adapter-client.d.ts +17 -0
- package/build/src/background-jobs/adapter-client.d.ts.map +1 -1
- package/build/src/background-jobs/adapter-client.js +19 -1
- package/build/src/background-jobs/adapter.d.ts +15 -0
- package/build/src/background-jobs/adapter.d.ts.map +1 -1
- package/build/src/background-jobs/adapter.js +14 -1
- package/build/src/background-jobs/client.d.ts +17 -0
- package/build/src/background-jobs/client.d.ts.map +1 -1
- package/build/src/background-jobs/client.js +129 -1
- package/build/src/background-jobs/job-semantics.d.ts +18 -0
- package/build/src/background-jobs/job-semantics.d.ts.map +1 -1
- package/build/src/background-jobs/job-semantics.js +28 -1
- package/build/src/background-jobs/job.d.ts +15 -0
- package/build/src/background-jobs/job.d.ts.map +1 -1
- package/build/src/background-jobs/job.js +21 -2
- package/build/src/background-jobs/local-adapter.d.ts +23 -8
- package/build/src/background-jobs/local-adapter.d.ts.map +1 -1
- package/build/src/background-jobs/local-adapter.js +35 -9
- package/build/src/background-jobs/local-store.d.ts +120 -5
- package/build/src/background-jobs/local-store.d.ts.map +1 -1
- package/build/src/background-jobs/local-store.js +366 -40
- package/build/src/background-jobs/main.d.ts +24 -2
- package/build/src/background-jobs/main.d.ts.map +1 -1
- package/build/src/background-jobs/main.js +68 -3
- package/build/src/background-jobs/platform-job.d.ts +15 -0
- package/build/src/background-jobs/platform-job.d.ts.map +1 -1
- package/build/src/background-jobs/platform-job.js +19 -2
- package/build/src/background-jobs/runtime.d.ts +39 -0
- package/build/src/background-jobs/runtime.d.ts.map +1 -1
- package/build/src/background-jobs/runtime.js +49 -1
- package/build/src/background-jobs/store.d.ts +100 -1
- package/build/src/background-jobs/store.d.ts.map +1 -1
- package/build/src/background-jobs/store.js +334 -8
- package/build/src/background-jobs/types.d.ts +97 -4
- package/build/src/background-jobs/types.d.ts.map +1 -1
- package/build/src/background-jobs/types.js +28 -3
- package/build/src/background-jobs/web/controller.d.ts.map +1 -1
- package/build/src/background-jobs/web/controller.js +2 -1
- package/build/src/configuration.d.ts +18 -1
- package/build/src/configuration.d.ts.map +1 -1
- package/build/src/configuration.js +28 -6
- package/build/src/database/drivers/mssql/column.d.ts.map +1 -1
- package/build/src/database/drivers/mssql/column.js +2 -2
- package/build/src/database/drivers/mssql/table.d.ts.map +1 -1
- package/build/src/database/drivers/mssql/table.js +19 -5
- package/build/src/database/pool/async-tracked-multi-connection.d.ts.map +1 -1
- package/build/src/database/pool/async-tracked-multi-connection.js +3 -2
- package/build/src/database/query/alter-table-base.d.ts.map +1 -1
- package/build/src/database/query/alter-table-base.js +9 -2
- package/build/src/database/record/attachments/store.js +3 -3
- package/build/src/environment-handlers/node/cli/commands/test/timing-manifest/merge.d.ts.map +1 -1
- package/build/src/environment-handlers/node/cli/commands/test/timing-manifest/merge.js +3 -47
- package/build/src/environment-handlers/node/cli/commands/test.d.ts +2 -28
- package/build/src/environment-handlers/node/cli/commands/test.d.ts.map +1 -1
- package/build/src/environment-handlers/node/cli/commands/test.js +10 -61
- package/build/src/frontend-model-controller.d.ts.map +1 -1
- package/build/src/frontend-model-controller.js +18 -3
- package/build/src/frontend-models/websocket-channel.d.ts +9 -0
- package/build/src/frontend-models/websocket-channel.d.ts.map +1 -1
- package/build/src/frontend-models/websocket-channel.js +16 -1
- package/build/src/http-server/client/websocket-session.d.ts +25 -2
- package/build/src/http-server/client/websocket-session.d.ts.map +1 -1
- package/build/src/http-server/client/websocket-session.js +40 -5
- package/build/src/http-server/websocket-channel.d.ts +11 -0
- package/build/src/http-server/websocket-channel.d.ts.map +1 -1
- package/build/src/http-server/websocket-channel.js +14 -1
- package/build/src/http-server/websocket-event-log-store.d.ts +60 -41
- package/build/src/http-server/websocket-event-log-store.d.ts.map +1 -1
- package/build/src/http-server/websocket-event-log-store.js +79 -12
- package/build/src/http-server/websocket-events-host.d.ts +10 -3
- package/build/src/http-server/websocket-events-host.d.ts.map +1 -1
- package/build/src/http-server/websocket-events-host.js +17 -8
- package/build/src/testing/test-files-finder.d.ts +17 -117
- package/build/src/testing/test-files-finder.d.ts.map +1 -1
- package/build/src/testing/test-files-finder.js +29 -294
- package/build/src/testing/test-filter-parser.d.ts +32 -22
- package/build/src/testing/test-filter-parser.d.ts.map +1 -1
- package/build/src/testing/test-filter-parser.js +25 -217
- package/build/src/testing/test-profile-activity.d.ts +1 -6
- package/build/src/testing/test-profile-activity.d.ts.map +1 -1
- package/build/src/testing/test-profile-activity.js +2 -12
- package/build/src/testing/test-profile-output.d.ts +1 -40
- package/build/src/testing/test-profile-output.d.ts.map +1 -1
- package/build/src/testing/test-profile-output.js +2 -217
- package/build/src/testing/test-profiler.d.ts +15 -659
- package/build/src/testing/test-profiler.d.ts.map +1 -1
- package/build/src/testing/test-profiler.js +35 -833
- package/build/src/testing/test-runner.d.ts +10 -1
- package/build/src/testing/test-runner.d.ts.map +1 -1
- package/build/src/testing/test-runner.js +26 -4
- package/build/src/testing/test-suite-splitter.d.ts +1 -128
- package/build/src/testing/test-suite-splitter.d.ts.map +1 -1
- package/build/src/testing/test-suite-splitter.js +2 -259
- package/build/src/testing/timing-manifest.d.ts +5 -93
- package/build/src/testing/timing-manifest.d.ts.map +1 -1
- package/build/src/testing/timing-manifest.js +4 -283
- package/build/src/testing/velocious-attempt-executor.d.ts.map +1 -1
- package/build/src/testing/velocious-attempt-executor.js +8 -2
- package/build/testing/test-files-finder.js +32 -343
- package/build/testing/test-filter-parser.js +29 -248
- package/build/testing/test-profile-activity.js +1 -12
- package/build/testing/test-profile-output.js +8 -253
- package/build/testing/test-profiler.js +38 -895
- package/build/testing/test-runner.js +33 -9
- package/build/testing/test-suite-splitter.js +1 -301
- package/build/testing/timing-manifest.js +10 -344
- package/build/testing/velocious-attempt-executor.js +7 -1
- package/package.json +3 -3
- package/scripts/test-browser.js +30 -6
- package/scripts/verify-docker-dev-environment.js +13 -1
- package/src/background-jobs/adapter-client.js +22 -0
- package/src/background-jobs/adapter.js +15 -0
- package/src/background-jobs/client.js +147 -1
- package/src/background-jobs/job-semantics.js +30 -0
- package/src/background-jobs/job.js +26 -1
- package/src/background-jobs/local-adapter.js +36 -8
- package/src/background-jobs/local-store.js +410 -39
- package/src/background-jobs/main.js +69 -2
- package/src/background-jobs/platform-job.js +26 -1
- package/src/background-jobs/runtime.js +58 -0
- package/src/background-jobs/store.js +374 -6
- package/src/background-jobs/types.js +27 -2
- package/src/background-jobs/web/controller.js +1 -0
- package/src/configuration.js +30 -6
- package/src/database/drivers/mssql/column.js +2 -2
- package/src/database/drivers/mssql/table.js +19 -5
- package/src/database/pool/async-tracked-multi-connection.js +2 -1
- package/src/database/query/alter-table-base.js +8 -1
- package/src/database/record/attachments/store.js +2 -2
- package/src/environment-handlers/node/cli/commands/test/timing-manifest/merge.js +2 -48
- package/src/environment-handlers/node/cli/commands/test.js +17 -69
- package/src/frontend-model-controller.js +22 -2
- package/src/frontend-models/websocket-channel.js +18 -0
- package/src/http-server/client/websocket-session.js +41 -4
- package/src/http-server/websocket-channel.js +14 -0
- package/src/http-server/websocket-event-log-store.js +85 -12
- package/src/http-server/websocket-events-host.js +17 -7
- package/src/testing/test-files-finder.js +32 -343
- package/src/testing/test-filter-parser.js +29 -248
- package/src/testing/test-profile-activity.js +1 -12
- package/src/testing/test-profile-output.js +8 -253
- package/src/testing/test-profiler.js +38 -895
- package/src/testing/test-runner.js +33 -9
- package/src/testing/test-suite-splitter.js +1 -301
- package/src/testing/timing-manifest.js +10 -344
- package/src/testing/velocious-attempt-executor.js +7 -1
|
@@ -10,6 +10,7 @@ import normalizeBackgroundJobError from "./normalize-error.js"
|
|
|
10
10
|
import { coordinateSharedTransactionConnection } from "../testing/shared-transaction-connection-coordinator.js"
|
|
11
11
|
import stableJsonStringify from "../utils/stable-json.js"
|
|
12
12
|
import {
|
|
13
|
+
BACKGROUND_JOB_TERMINAL_STATUSES,
|
|
13
14
|
BACKGROUND_JOB_EXECUTION_MODES,
|
|
14
15
|
DEFAULT_BACKGROUND_JOB_EXECUTION_MODE,
|
|
15
16
|
DEFAULT_BACKGROUND_JOB_QUEUE,
|
|
@@ -18,7 +19,9 @@ import {
|
|
|
18
19
|
normalizeBackgroundJobExecutionMode,
|
|
19
20
|
normalizeBackgroundJobMaxRetries,
|
|
20
21
|
normalizeBackgroundJobQueue,
|
|
22
|
+
normalizeBackgroundJobScheduleKey,
|
|
21
23
|
normalizeBackgroundJobScheduledAtMs,
|
|
24
|
+
normalizeBackgroundJobStatus,
|
|
22
25
|
rescheduledBackgroundJobAtMs,
|
|
23
26
|
retryDelayMs
|
|
24
27
|
} from "./job-semantics.js"
|
|
@@ -100,6 +103,9 @@ const JOBS_INDEX_COLUMN_NAMES = [
|
|
|
100
103
|
]
|
|
101
104
|
const IDEMPOTENCY_KEYS_TABLE = "background_job_idempotency_keys"
|
|
102
105
|
const SCHEDULE_KEYS_TABLE = "background_job_schedule_keys"
|
|
106
|
+
const SCHEDULE_ORDER_WATERMARKS_TABLE = "background_job_schedule_order_watermarks"
|
|
107
|
+
const SCHEDULE_ORDER_WATERMARK_MIGRATION_VERSION = "20260911120000"
|
|
108
|
+
const SCHEDULE_HISTORY_ORDER_INDEX = "index_background_jobs_schedule_history_order"
|
|
103
109
|
const CONCURRENCY_TABLE = "background_job_concurrency"
|
|
104
110
|
const COUNTS_REVISION_TABLE = "background_job_count_revisions"
|
|
105
111
|
const COUNTS_REVISION_KEY = "counts"
|
|
@@ -918,7 +924,9 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
|
|
|
918
924
|
previousStatus = "handed_off"
|
|
919
925
|
}
|
|
920
926
|
|
|
921
|
-
await this.
|
|
927
|
+
const scheduleOrder = await this._nextScheduleOrder(db, normalizedScheduleKey)
|
|
928
|
+
|
|
929
|
+
await this._insertPreparedJob(db, {preparedJob, scheduleKey: normalizedScheduleKey, scheduleOrder})
|
|
922
930
|
await db.upsert({
|
|
923
931
|
tableName: SCHEDULE_KEYS_TABLE,
|
|
924
932
|
data: {schedule_key: normalizedScheduleKey, job_id: preparedJob.jobId},
|
|
@@ -989,6 +997,78 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
|
|
|
989
997
|
})
|
|
990
998
|
}
|
|
991
999
|
|
|
1000
|
+
/**
|
|
1001
|
+
* Reads stable ownership and optional latest terminal history in one fenced transaction.
|
|
1002
|
+
* @param {string} scheduleKey - Stable logical schedule key.
|
|
1003
|
+
* @param {{includeLatestTerminal?: boolean}} [options] - Lookup options.
|
|
1004
|
+
* @returns {Promise<import("./types.js").BackgroundJobScheduledLookupResult>} - Normalized public jobs.
|
|
1005
|
+
*/
|
|
1006
|
+
async getScheduledJob(scheduleKey, {includeLatestTerminal = false} = {}) {
|
|
1007
|
+
await this.ensureReady()
|
|
1008
|
+
|
|
1009
|
+
const normalizedScheduleKey = this._normalizeScheduleKey(scheduleKey)
|
|
1010
|
+
|
|
1011
|
+
if (typeof includeLatestTerminal !== "boolean") {
|
|
1012
|
+
throw VelociousError.safe("background job includeLatestTerminal must be a boolean")
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
return await this._serializedCountMutation(async (db) => {
|
|
1016
|
+
return await this._scheduledJobLookup(db, {
|
|
1017
|
+
includeLatestTerminal,
|
|
1018
|
+
scheduleKey: normalizedScheduleKey
|
|
1019
|
+
})
|
|
1020
|
+
}, {
|
|
1021
|
+
advisoryLock: {
|
|
1022
|
+
failureMessage: "Failed to acquire background job schedule-key lock",
|
|
1023
|
+
name: this._scheduleKeyLockName(normalizedScheduleKey)
|
|
1024
|
+
}
|
|
1025
|
+
})
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
/**
|
|
1029
|
+
* Moves only a future queued stable owner to the current time.
|
|
1030
|
+
* @param {string} scheduleKey - Stable logical schedule key.
|
|
1031
|
+
* @returns {Promise<import("./types.js").BackgroundJobWakeResult>} - Exact wake outcome.
|
|
1032
|
+
*/
|
|
1033
|
+
async wakeScheduled(scheduleKey) {
|
|
1034
|
+
await this.ensureReady()
|
|
1035
|
+
|
|
1036
|
+
const normalizedScheduleKey = this._normalizeScheduleKey(scheduleKey)
|
|
1037
|
+
|
|
1038
|
+
return await this._serializedCountMutation(async (db) => {
|
|
1039
|
+
const job = await this._scheduledOwnerJob(db, normalizedScheduleKey)
|
|
1040
|
+
|
|
1041
|
+
if (!job || (job.status !== "queued" && job.status !== "handed_off")) return {jobId: null, outcome: "not_found"}
|
|
1042
|
+
if (job.status === "handed_off") return {jobId: job.id, outcome: "handed_off"}
|
|
1043
|
+
|
|
1044
|
+
const nowMs = this.clock.now()
|
|
1045
|
+
|
|
1046
|
+
if (Number(job.scheduledAtMs) <= nowMs) return {jobId: job.id, outcome: "already_due"}
|
|
1047
|
+
|
|
1048
|
+
const affectedRows = await this._updateAffectedRows(db, {
|
|
1049
|
+
tableName: JOBS_TABLE,
|
|
1050
|
+
data: {scheduled_at_ms: nowMs},
|
|
1051
|
+
conditions: {id: job.id, scheduled_at_ms: job.scheduledAtMs, status: "queued"}
|
|
1052
|
+
})
|
|
1053
|
+
|
|
1054
|
+
if (affectedRows === 1) return {jobId: job.id, outcome: "woken"}
|
|
1055
|
+
|
|
1056
|
+
const currentJob = await this._scheduledOwnerJob(db, normalizedScheduleKey)
|
|
1057
|
+
|
|
1058
|
+
if (currentJob?.status === "handed_off") return {jobId: currentJob.id, outcome: "handed_off"}
|
|
1059
|
+
if (currentJob?.status === "queued" && Number(currentJob.scheduledAtMs) <= nowMs) {
|
|
1060
|
+
return {jobId: currentJob.id, outcome: "already_due"}
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
return {jobId: null, outcome: "not_found"}
|
|
1064
|
+
}, {
|
|
1065
|
+
advisoryLock: {
|
|
1066
|
+
failureMessage: "Failed to acquire background job schedule-key lock",
|
|
1067
|
+
name: this._scheduleKeyLockName(normalizedScheduleKey)
|
|
1068
|
+
}
|
|
1069
|
+
})
|
|
1070
|
+
}
|
|
1071
|
+
|
|
992
1072
|
/**
|
|
993
1073
|
* Runs next available job.
|
|
994
1074
|
* @param {object} [args] - Options.
|
|
@@ -1764,6 +1844,22 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
|
|
|
1764
1844
|
if (await db.tableExists(MAIL_DELIVERY_OPERATIONS_TABLE)) await db.query(`DELETE FROM ${db.quoteTable(MAIL_DELIVERY_OPERATIONS_TABLE)}`)
|
|
1765
1845
|
if (await db.tableExists(IDEMPOTENCY_KEYS_TABLE)) await db.query(`DELETE FROM ${db.quoteTable(IDEMPOTENCY_KEYS_TABLE)}`)
|
|
1766
1846
|
if (await db.tableExists(SCHEDULE_KEYS_TABLE)) await db.query(`DELETE FROM ${db.quoteTable(SCHEDULE_KEYS_TABLE)}`)
|
|
1847
|
+
if (await db.tableExists(SCHEDULE_ORDER_WATERMARKS_TABLE)) {
|
|
1848
|
+
const watermarkRows = await db
|
|
1849
|
+
.newQuery()
|
|
1850
|
+
.from(SCHEDULE_ORDER_WATERMARKS_TABLE)
|
|
1851
|
+
.select("schedule_key")
|
|
1852
|
+
.results()
|
|
1853
|
+
|
|
1854
|
+
for (const watermarkRow of watermarkRows) {
|
|
1855
|
+
await db.delete({
|
|
1856
|
+
tableName: SCHEDULE_ORDER_WATERMARKS_TABLE,
|
|
1857
|
+
conditions: {
|
|
1858
|
+
schedule_key: String(/** @type {Record<string, ReturnType<typeof JSON.parse>>} */ (watermarkRow).schedule_key)
|
|
1859
|
+
}
|
|
1860
|
+
})
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1767
1863
|
await db.query(`DELETE FROM ${db.quoteTable(JOBS_TABLE)}`)
|
|
1768
1864
|
if (await db.tableExists(CONCURRENCY_TABLE)) await db.query(`DELETE FROM ${db.quoteTable(CONCURRENCY_TABLE)}`)
|
|
1769
1865
|
const deltas = Object.fromEntries(Object.entries(snapshot.counts).map(([key, value]) => [key, -value]))
|
|
@@ -1858,9 +1954,10 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
|
|
|
1858
1954
|
* @param {object} args - Insert input.
|
|
1859
1955
|
* @param {PreparedBackgroundJob} args.preparedJob - Prepared job.
|
|
1860
1956
|
* @param {string | null} args.scheduleKey - Historical stable key.
|
|
1957
|
+
* @param {number | null} [args.scheduleOrder] - Monotonic stable ownership order.
|
|
1861
1958
|
* @returns {Promise<void>} - Resolves after insertion.
|
|
1862
1959
|
*/
|
|
1863
|
-
async _insertPreparedJob(db, {preparedJob, scheduleKey}) {
|
|
1960
|
+
async _insertPreparedJob(db, {preparedJob, scheduleKey, scheduleOrder = null}) {
|
|
1864
1961
|
const {concurrency} = preparedJob
|
|
1865
1962
|
|
|
1866
1963
|
if (concurrency) {
|
|
@@ -1885,6 +1982,7 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
|
|
|
1885
1982
|
scheduled_at_ms: preparedJob.scheduledAtMs,
|
|
1886
1983
|
created_at_ms: preparedJob.createdAtMs,
|
|
1887
1984
|
schedule_key: scheduleKey,
|
|
1985
|
+
schedule_order: scheduleOrder,
|
|
1888
1986
|
concurrency_key: concurrency?.concurrencyKey || null,
|
|
1889
1987
|
max_concurrency: concurrency?.maxConcurrency || null,
|
|
1890
1988
|
timeout_ms: preparedJob.timeoutMs,
|
|
@@ -1936,9 +2034,7 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
|
|
|
1936
2034
|
* @returns {string} - Validated key.
|
|
1937
2035
|
*/
|
|
1938
2036
|
_normalizeScheduleKey(scheduleKey) {
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
throw VelociousError.safe("background job scheduleKey must be a non-empty string of at most 255 characters")
|
|
2037
|
+
return normalizeBackgroundJobScheduleKey(scheduleKey)
|
|
1942
2038
|
}
|
|
1943
2039
|
|
|
1944
2040
|
/**
|
|
@@ -2118,6 +2214,7 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
|
|
|
2118
2214
|
table.bigint("scheduled_at_ms", {null: false, index: true})
|
|
2119
2215
|
table.bigint("created_at_ms", {null: false, index: true})
|
|
2120
2216
|
table.string("schedule_key", {null: true, index: true})
|
|
2217
|
+
table.bigint("schedule_order", {null: true})
|
|
2121
2218
|
table.bigint("handed_off_at_ms", {null: true, index: true})
|
|
2122
2219
|
table.string("handoff_id", {null: true})
|
|
2123
2220
|
table.bigint("completed_at_ms", {null: true})
|
|
@@ -2224,6 +2321,8 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
|
|
|
2224
2321
|
|
|
2225
2322
|
await this._ensureQueueColumn(db)
|
|
2226
2323
|
await this._ensureScheduleKeyColumn(db)
|
|
2324
|
+
await this._ensureScheduleOrderColumn(db)
|
|
2325
|
+
await this._ensureScheduleOrderWatermarksTable(db)
|
|
2227
2326
|
await this._ensureJobTimeoutColumn(db)
|
|
2228
2327
|
await this._ensureChildAcceptanceColumns(db)
|
|
2229
2328
|
await this._ensureJobsTableIndexesOnce(db)
|
|
@@ -2375,6 +2474,117 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
|
|
|
2375
2474
|
}
|
|
2376
2475
|
}
|
|
2377
2476
|
|
|
2477
|
+
/**
|
|
2478
|
+
* Idempotently adds monotonic schedule ownership history and its lookup index.
|
|
2479
|
+
* Existing rows remain null and use the documented legacy fallback ordering.
|
|
2480
|
+
* @param {import("../database/drivers/base.js").default} db - Database connection.
|
|
2481
|
+
* @returns {Promise<void>} - Resolves when ensured.
|
|
2482
|
+
*/
|
|
2483
|
+
async _ensureScheduleOrderColumn(db) {
|
|
2484
|
+
const lockName = `${MIGRATION_SCOPE}:schedule_order_column`
|
|
2485
|
+
const acquired = await db.acquireAdvisoryLock(lockName)
|
|
2486
|
+
|
|
2487
|
+
if (!acquired) throw new Error("Failed to acquire background jobs schedule-order schema lock")
|
|
2488
|
+
|
|
2489
|
+
try {
|
|
2490
|
+
db.clearSchemaCache()
|
|
2491
|
+
let table = await db.getTableByNameOrFail(JOBS_TABLE)
|
|
2492
|
+
|
|
2493
|
+
if (!(await table.getColumnByName("schedule_order"))) {
|
|
2494
|
+
const tableData = new TableData(JOBS_TABLE)
|
|
2495
|
+
|
|
2496
|
+
tableData.bigint("schedule_order", {null: true})
|
|
2497
|
+
for (const sql of await db.alterTableSQLs(tableData)) await db.query(sql)
|
|
2498
|
+
db.clearSchemaCache()
|
|
2499
|
+
table = await db.getTableByNameOrFail(JOBS_TABLE)
|
|
2500
|
+
}
|
|
2501
|
+
|
|
2502
|
+
const indexNames = new Set((await table.getIndexes()).map((index) => index.getName()))
|
|
2503
|
+
|
|
2504
|
+
if (!indexNames.has(SCHEDULE_HISTORY_ORDER_INDEX)) {
|
|
2505
|
+
const sqls = await db.createIndexSQLs({
|
|
2506
|
+
columns: ["schedule_key", "schedule_order", "created_at_ms", "id"],
|
|
2507
|
+
ifNotExists: db.getType() === "sqlite",
|
|
2508
|
+
name: SCHEDULE_HISTORY_ORDER_INDEX,
|
|
2509
|
+
tableName: JOBS_TABLE
|
|
2510
|
+
})
|
|
2511
|
+
|
|
2512
|
+
for (const sql of sqls) await db.query(sql)
|
|
2513
|
+
db.clearSchemaCache()
|
|
2514
|
+
}
|
|
2515
|
+
} finally {
|
|
2516
|
+
await db.releaseAdvisoryLock(lockName)
|
|
2517
|
+
}
|
|
2518
|
+
}
|
|
2519
|
+
|
|
2520
|
+
/**
|
|
2521
|
+
* Creates the retention-independent schedule-order high-water table and
|
|
2522
|
+
* initializes it from the greatest retained ordered row for every key.
|
|
2523
|
+
* Legacy rows whose order is null deliberately do not establish a watermark.
|
|
2524
|
+
* @param {import("../database/drivers/base.js").default} db - Database connection.
|
|
2525
|
+
* @returns {Promise<void>} - Resolves when ensured and backfilled.
|
|
2526
|
+
*/
|
|
2527
|
+
async _ensureScheduleOrderWatermarksTable(db) {
|
|
2528
|
+
const migrationVersion = SCHEDULE_ORDER_WATERMARK_MIGRATION_VERSION
|
|
2529
|
+
const migrationKey = this._migrationKey(migrationVersion)
|
|
2530
|
+
const tableExists = await db.tableExists(SCHEDULE_ORDER_WATERMARKS_TABLE)
|
|
2531
|
+
|
|
2532
|
+
if (tableExists && await this._hasMigration(db, migrationVersion)) return
|
|
2533
|
+
|
|
2534
|
+
const acquired = await db.acquireAdvisoryLock(migrationKey)
|
|
2535
|
+
|
|
2536
|
+
if (!acquired) throw new Error("Failed to acquire background jobs schedule-order watermark schema lock")
|
|
2537
|
+
|
|
2538
|
+
try {
|
|
2539
|
+
db.clearSchemaCache()
|
|
2540
|
+
const lockedTableExists = await db.tableExists(SCHEDULE_ORDER_WATERMARKS_TABLE)
|
|
2541
|
+
const alreadyApplied = await this._hasMigration(db, migrationVersion)
|
|
2542
|
+
|
|
2543
|
+
if (!lockedTableExists) {
|
|
2544
|
+
const table = new TableData(SCHEDULE_ORDER_WATERMARKS_TABLE, {ifNotExists: true})
|
|
2545
|
+
|
|
2546
|
+
table.string("schedule_key", {primaryKey: true})
|
|
2547
|
+
table.bigint("high_water_mark", {null: false})
|
|
2548
|
+
await db.createTable(table)
|
|
2549
|
+
db.clearSchemaCache()
|
|
2550
|
+
}
|
|
2551
|
+
|
|
2552
|
+
// Rebuild a missing table even when its migration ledger survived. The
|
|
2553
|
+
// retained job rows are the only compatible source for that recovery;
|
|
2554
|
+
// once rows are pruned, normal schema durability protects the watermark.
|
|
2555
|
+
if (!lockedTableExists || !alreadyApplied) await this._backfillScheduleOrderWatermarks(db)
|
|
2556
|
+
if (!alreadyApplied) await this._recordMigration(db, migrationVersion)
|
|
2557
|
+
} finally {
|
|
2558
|
+
await db.releaseAdvisoryLock(migrationKey)
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2561
|
+
|
|
2562
|
+
/**
|
|
2563
|
+
* Backfills each key from its greatest retained non-legacy ownership order.
|
|
2564
|
+
* @param {import("../database/drivers/base.js").default} db - Database connection.
|
|
2565
|
+
* @returns {Promise<void>} - Resolves after all retained keys are represented.
|
|
2566
|
+
*/
|
|
2567
|
+
async _backfillScheduleOrderWatermarks(db) {
|
|
2568
|
+
const keyRows = await db
|
|
2569
|
+
.newQuery()
|
|
2570
|
+
.from(JOBS_TABLE)
|
|
2571
|
+
.select("schedule_key")
|
|
2572
|
+
.whereNot({schedule_key: null})
|
|
2573
|
+
.whereNot({schedule_order: null})
|
|
2574
|
+
.distinct()
|
|
2575
|
+
.results()
|
|
2576
|
+
|
|
2577
|
+
for (const keyRow of keyRows) {
|
|
2578
|
+
const scheduleKey = String(/** @type {Record<string, ReturnType<typeof JSON.parse>>} */ (keyRow).schedule_key)
|
|
2579
|
+
const retainedOrder = await this._greatestRetainedScheduleOrder(db, scheduleKey)
|
|
2580
|
+
const currentWatermark = await this._scheduleOrderWatermark(db, scheduleKey)
|
|
2581
|
+
|
|
2582
|
+
if (retainedOrder === null) continue
|
|
2583
|
+
if (currentWatermark !== null && currentWatermark >= retainedOrder) continue
|
|
2584
|
+
await this._writeScheduleOrderWatermark(db, {scheduleKey, scheduleOrder: retainedOrder})
|
|
2585
|
+
}
|
|
2586
|
+
}
|
|
2587
|
+
|
|
2378
2588
|
/**
|
|
2379
2589
|
* Idempotently adds the `queue` column to an existing jobs table. Existing
|
|
2380
2590
|
* rows read back as the default queue (see {@link _normalizeJobRow}), so no
|
|
@@ -2557,6 +2767,163 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
|
|
|
2557
2767
|
return this._normalizeJobRow(rows[0])
|
|
2558
2768
|
}
|
|
2559
2769
|
|
|
2770
|
+
/**
|
|
2771
|
+
* Reads the job currently named by one stable owner row.
|
|
2772
|
+
* @param {import("../database/drivers/base.js").default} db - Database connection.
|
|
2773
|
+
* @param {string} scheduleKey - Validated stable schedule key.
|
|
2774
|
+
* @returns {Promise<import("./types.js").BackgroundJobRow | null>} - Normalized owner job.
|
|
2775
|
+
*/
|
|
2776
|
+
async _scheduledOwnerJob(db, scheduleKey) {
|
|
2777
|
+
const ownerRows = await db
|
|
2778
|
+
.newQuery()
|
|
2779
|
+
.from(SCHEDULE_KEYS_TABLE)
|
|
2780
|
+
.where({schedule_key: scheduleKey})
|
|
2781
|
+
.limit(1)
|
|
2782
|
+
.results()
|
|
2783
|
+
const ownerRow = ownerRows[0]
|
|
2784
|
+
|
|
2785
|
+
if (!ownerRow) return null
|
|
2786
|
+
|
|
2787
|
+
return await this._getJobRowById(db, String(ownerRow.job_id))
|
|
2788
|
+
}
|
|
2789
|
+
|
|
2790
|
+
/**
|
|
2791
|
+
* Assigns the next ownership order while the caller holds the schedule-key
|
|
2792
|
+
* advisory lock and count-revision transaction fence. The independent
|
|
2793
|
+
* watermark survives both ownership release and terminal-history pruning.
|
|
2794
|
+
* @param {import("../database/drivers/base.js").default} db - Database connection.
|
|
2795
|
+
* @param {string} scheduleKey - Validated stable schedule key.
|
|
2796
|
+
* @returns {Promise<number>} - Next monotonic ownership order.
|
|
2797
|
+
*/
|
|
2798
|
+
async _nextScheduleOrder(db, scheduleKey) {
|
|
2799
|
+
const durableOrder = await this._scheduleOrderWatermark(db, scheduleKey)
|
|
2800
|
+
const retainedOrder = await this._greatestRetainedScheduleOrder(db, scheduleKey)
|
|
2801
|
+
let currentOrder = durableOrder
|
|
2802
|
+
|
|
2803
|
+
if (retainedOrder !== null && (currentOrder === null || retainedOrder > currentOrder)) currentOrder = retainedOrder
|
|
2804
|
+
const nextOrder = currentOrder === null ? 1 : currentOrder + 1
|
|
2805
|
+
|
|
2806
|
+
if (!Number.isSafeInteger(nextOrder)) {
|
|
2807
|
+
throw new Error(`Background job schedule ownership order exhausted for ${scheduleKey}`)
|
|
2808
|
+
}
|
|
2809
|
+
|
|
2810
|
+
await this._writeScheduleOrderWatermark(db, {scheduleKey, scheduleOrder: nextOrder})
|
|
2811
|
+
|
|
2812
|
+
return nextOrder
|
|
2813
|
+
}
|
|
2814
|
+
|
|
2815
|
+
/**
|
|
2816
|
+
* Finds the greatest retained non-legacy ownership order for migration and
|
|
2817
|
+
* rolling-upgrade compatibility. It is never the sole durability boundary.
|
|
2818
|
+
* @param {import("../database/drivers/base.js").default} db - Database connection.
|
|
2819
|
+
* @param {string} scheduleKey - Validated stable schedule key.
|
|
2820
|
+
* @returns {Promise<number | null>} - Greatest retained order, or null.
|
|
2821
|
+
*/
|
|
2822
|
+
async _greatestRetainedScheduleOrder(db, scheduleKey) {
|
|
2823
|
+
const rows = await db
|
|
2824
|
+
.newQuery()
|
|
2825
|
+
.from(JOBS_TABLE)
|
|
2826
|
+
.select("schedule_order")
|
|
2827
|
+
.where({schedule_key: scheduleKey})
|
|
2828
|
+
.whereNot({schedule_order: null})
|
|
2829
|
+
.order("schedule_order DESC")
|
|
2830
|
+
.limit(1)
|
|
2831
|
+
.results()
|
|
2832
|
+
const row = rows[0]
|
|
2833
|
+
|
|
2834
|
+
if (!row) return null
|
|
2835
|
+
|
|
2836
|
+
return this._validatedScheduleOrder(
|
|
2837
|
+
/** @type {Record<string, ReturnType<typeof JSON.parse>>} */ (row).schedule_order
|
|
2838
|
+
)
|
|
2839
|
+
}
|
|
2840
|
+
|
|
2841
|
+
/**
|
|
2842
|
+
* Reads and validates one retention-independent schedule-order watermark.
|
|
2843
|
+
* @param {import("../database/drivers/base.js").default} db - Database connection.
|
|
2844
|
+
* @param {string} scheduleKey - Validated stable schedule key.
|
|
2845
|
+
* @returns {Promise<number | null>} - Current watermark, or null before first ownership.
|
|
2846
|
+
*/
|
|
2847
|
+
async _scheduleOrderWatermark(db, scheduleKey) {
|
|
2848
|
+
const rows = await db
|
|
2849
|
+
.newQuery()
|
|
2850
|
+
.from(SCHEDULE_ORDER_WATERMARKS_TABLE)
|
|
2851
|
+
.select("high_water_mark")
|
|
2852
|
+
.where({schedule_key: scheduleKey})
|
|
2853
|
+
.limit(1)
|
|
2854
|
+
.results()
|
|
2855
|
+
const row = rows[0]
|
|
2856
|
+
|
|
2857
|
+
if (!row) return null
|
|
2858
|
+
|
|
2859
|
+
return this._validatedScheduleOrder(
|
|
2860
|
+
/** @type {Record<string, ReturnType<typeof JSON.parse>>} */ (row).high_water_mark
|
|
2861
|
+
)
|
|
2862
|
+
}
|
|
2863
|
+
|
|
2864
|
+
/**
|
|
2865
|
+
* Persists one schedule-order watermark without exposing it as a job row.
|
|
2866
|
+
* @param {import("../database/drivers/base.js").default} db - Database connection.
|
|
2867
|
+
* @param {object} args - Watermark identity and value.
|
|
2868
|
+
* @param {string} args.scheduleKey - Validated stable schedule key.
|
|
2869
|
+
* @param {number} args.scheduleOrder - Validated monotonic order.
|
|
2870
|
+
* @returns {Promise<void>} - Resolves after persistence.
|
|
2871
|
+
*/
|
|
2872
|
+
async _writeScheduleOrderWatermark(db, {scheduleKey, scheduleOrder}) {
|
|
2873
|
+
await db.upsert({
|
|
2874
|
+
tableName: SCHEDULE_ORDER_WATERMARKS_TABLE,
|
|
2875
|
+
data: {high_water_mark: scheduleOrder, schedule_key: scheduleKey},
|
|
2876
|
+
conflictColumns: ["schedule_key"],
|
|
2877
|
+
updateColumns: ["high_water_mark"]
|
|
2878
|
+
})
|
|
2879
|
+
}
|
|
2880
|
+
|
|
2881
|
+
/**
|
|
2882
|
+
* Validates an ownership order loaded from durable storage.
|
|
2883
|
+
* @param {ReturnType<typeof JSON.parse>} value - Stored order.
|
|
2884
|
+
* @returns {number} - Positive safe integer ownership order.
|
|
2885
|
+
*/
|
|
2886
|
+
_validatedScheduleOrder(value) {
|
|
2887
|
+
const scheduleOrder = this._normalizeNumber(value)
|
|
2888
|
+
|
|
2889
|
+
if (scheduleOrder === null || !Number.isSafeInteger(scheduleOrder) || scheduleOrder < 1) {
|
|
2890
|
+
throw new Error(`Invalid background job schedule ownership order: ${scheduleOrder}`)
|
|
2891
|
+
}
|
|
2892
|
+
|
|
2893
|
+
return scheduleOrder
|
|
2894
|
+
}
|
|
2895
|
+
|
|
2896
|
+
/**
|
|
2897
|
+
* Builds a stable-schedule lookup exclusively from normalized job rows.
|
|
2898
|
+
* @param {import("../database/drivers/base.js").default} db - Database connection.
|
|
2899
|
+
* @param {object} args - Lookup options.
|
|
2900
|
+
* @param {boolean} args.includeLatestTerminal - Whether terminal history is requested.
|
|
2901
|
+
* @param {string} args.scheduleKey - Validated stable schedule key.
|
|
2902
|
+
* @returns {Promise<import("./types.js").BackgroundJobScheduledLookupResult>} - Normalized public jobs.
|
|
2903
|
+
*/
|
|
2904
|
+
async _scheduledJobLookup(db, {includeLatestTerminal, scheduleKey}) {
|
|
2905
|
+
const ownerJob = await this._scheduledOwnerJob(db, scheduleKey)
|
|
2906
|
+
const currentJob = ownerJob && (ownerJob.status === "queued" || ownerJob.status === "handed_off") ? ownerJob : null
|
|
2907
|
+
|
|
2908
|
+
if (!includeLatestTerminal) return {currentJob, latestTerminalJob: null}
|
|
2909
|
+
|
|
2910
|
+
const terminalStatuses = BACKGROUND_JOB_TERMINAL_STATUSES.map((status) => db.quote(status)).join(", ")
|
|
2911
|
+
const terminalRows = await db
|
|
2912
|
+
.newQuery()
|
|
2913
|
+
.from(JOBS_TABLE)
|
|
2914
|
+
.where({schedule_key: scheduleKey})
|
|
2915
|
+
.where(`${db.quoteColumn("status")} IN (${terminalStatuses})`)
|
|
2916
|
+
.order(`CASE WHEN ${db.quoteColumn("schedule_order")} IS NULL THEN 0 ELSE 1 END DESC`)
|
|
2917
|
+
.order("schedule_order DESC")
|
|
2918
|
+
.order("created_at_ms DESC")
|
|
2919
|
+
.order("id DESC")
|
|
2920
|
+
.limit(1)
|
|
2921
|
+
.results()
|
|
2922
|
+
const latestTerminalJob = terminalRows[0] ? this._normalizeJobRow(terminalRows[0]) : null
|
|
2923
|
+
|
|
2924
|
+
return {currentJob, latestTerminalJob}
|
|
2925
|
+
}
|
|
2926
|
+
|
|
2560
2927
|
/**
|
|
2561
2928
|
* Releases ownership only when the key still points at the expected job.
|
|
2562
2929
|
* @param {import("../database/drivers/base.js").default} db - Database connection.
|
|
@@ -2738,7 +3105,8 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
|
|
|
2738
3105
|
executionMode,
|
|
2739
3106
|
queue: row.queue ? String(row.queue) : DEFAULT_BACKGROUND_JOB_QUEUE,
|
|
2740
3107
|
scheduleKey: row.schedule_key ? String(row.schedule_key) : null,
|
|
2741
|
-
|
|
3108
|
+
scheduleOrder: this._normalizeNumber(row.schedule_order),
|
|
3109
|
+
status: normalizeBackgroundJobStatus(row.status ? String(row.status) : "queued"),
|
|
2742
3110
|
attempts: this._normalizeNumber(row.attempts),
|
|
2743
3111
|
maxRetries: this._normalizeNumber(row.max_retries),
|
|
2744
3112
|
scheduledAtMs: this._normalizeNumber(row.scheduled_at_ms),
|
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
/** @typedef {"candidate" | "active" | "retired"} BackgroundJobsGenerationInitialState */
|
|
7
7
|
/** @typedef {"starting" | "candidate" | "active" | "retiring" | "retired" | "stopped"} BackgroundJobsGenerationLifecycleState */
|
|
8
8
|
/** @typedef {"missing-generation" | "unexpected-generation" | "malformed-generation" | "generation-mismatch" | "worker-admission-retired" | "worker-has-no-recoverable-handoffs"} BackgroundJobsGenerationRejectionReason */
|
|
9
|
+
/** @typedef {"queued" | "handed_off"} BackgroundJobActiveStatus */
|
|
10
|
+
/** @typedef {"cancelled" | "completed" | "failed" | "orphaned"} BackgroundJobTerminalStatus */
|
|
11
|
+
/** @typedef {BackgroundJobActiveStatus | BackgroundJobTerminalStatus} BackgroundJobStatus */
|
|
9
12
|
/** @typedef {"exit" | "process-error" | "ipc-send"} PooledRunnerFailureOrigin */
|
|
10
13
|
/** @typedef {"starting" | "running" | "retiring"} PooledRunnerLifecycleState */
|
|
11
14
|
/** @typedef {"unexpected" | "job-timeout" | "worker-shutdown-timeout"} PooledRunnerTerminationReason */
|
|
@@ -96,6 +99,8 @@
|
|
|
96
99
|
* @property {(args: {jobName: string, args: Array<ReturnType<typeof JSON.parse>>, options?: BackgroundJobOptions, producerInvocationId?: string, producerProof?: BackgroundJobProducerProof}) => Promise<string>} enqueue - Enqueues a job.
|
|
97
100
|
* @property {(args: {scheduleKey: string, jobName: string, args: Array<ReturnType<typeof JSON.parse>>, options?: BackgroundJobOptions}) => Promise<BackgroundJobReplacementResult>} replaceScheduled - Replaces a stable schedule.
|
|
98
101
|
* @property {(args: {scheduleKey: string}) => Promise<BackgroundJobCancellationResult>} cancelScheduled - Cancels a stable schedule.
|
|
102
|
+
* @property {(args: {scheduleKey: string, includeLatestTerminal?: boolean}) => Promise<BackgroundJobScheduledLookupResult>} getScheduledJob - Reads stable schedule ownership and history.
|
|
103
|
+
* @property {(args: {scheduleKey: string}) => Promise<BackgroundJobWakeResult>} wakeScheduled - Expedites a stable schedule owner.
|
|
99
104
|
*/
|
|
100
105
|
/**
|
|
101
106
|
* @typedef {object} BackgroundJobHandoff
|
|
@@ -154,7 +159,8 @@
|
|
|
154
159
|
* @property {BackgroundJobExecutionMode} executionMode - How the job should run.
|
|
155
160
|
* @property {string} queue - Queue name (defaults to `"default"`).
|
|
156
161
|
* @property {string | null} scheduleKey - Stable logical schedule key retained for history.
|
|
157
|
-
* @property {
|
|
162
|
+
* @property {number | null} scheduleOrder - Transaction-assigned monotonic ownership order for this schedule key; Node preserves its high-water mark across terminal-history pruning. Null for legacy/non-scheduled rows.
|
|
163
|
+
* @property {BackgroundJobStatus} status - Current job status.
|
|
158
164
|
* @property {number | null} attempts - Failure attempts count.
|
|
159
165
|
* @property {number | null} maxRetries - Max retry attempts.
|
|
160
166
|
* @property {number | null} scheduledAtMs - Next scheduled time in ms.
|
|
@@ -191,6 +197,19 @@
|
|
|
191
197
|
* @property {string | null} jobId - Detached owner's job id, when one was active.
|
|
192
198
|
* @property {BackgroundJobCancellationOutcome} outcome - Truthful best-effort outcome.
|
|
193
199
|
*/
|
|
200
|
+
/**
|
|
201
|
+
* @typedef {object} BackgroundJobScheduledLookupResult
|
|
202
|
+
* @property {BackgroundJobRow | null} currentJob - Current queued or handed-off owner.
|
|
203
|
+
* @property {BackgroundJobRow | null} latestTerminalJob - Latest terminal history when requested.
|
|
204
|
+
*/
|
|
205
|
+
/**
|
|
206
|
+
* @typedef {"woken" | "already_due" | "handed_off" | "not_found"} BackgroundJobWakeOutcome
|
|
207
|
+
*/
|
|
208
|
+
/**
|
|
209
|
+
* @typedef {object} BackgroundJobWakeResult
|
|
210
|
+
* @property {string | null} jobId - Current owner's durable job id, when found.
|
|
211
|
+
* @property {BackgroundJobWakeOutcome} outcome - Exact wake outcome.
|
|
212
|
+
*/
|
|
194
213
|
/**
|
|
195
214
|
* @typedef {object} BackgroundJobFailureEvent
|
|
196
215
|
* @property {BackgroundJobRow} job - Updated job row after failure handling.
|
|
@@ -222,6 +241,12 @@
|
|
|
222
241
|
* @typedef {{type: "cancel-scheduled", scheduleKey: string}} BackgroundJobCancelScheduledMessage
|
|
223
242
|
* @typedef {{type: "schedule-cancelled", jobId: string | null, outcome: BackgroundJobCancellationOutcome}} BackgroundJobScheduleCancelledMessage
|
|
224
243
|
* @typedef {{type: "cancel-scheduled-error", error?: string}} BackgroundJobCancelScheduledErrorMessage
|
|
244
|
+
* @typedef {{type: "get-scheduled-job", scheduleKey: string, includeLatestTerminal?: boolean}} BackgroundJobGetScheduledMessage
|
|
245
|
+
* @typedef {{type: "scheduled-job", currentJob: BackgroundJobRow | null, latestTerminalJob: BackgroundJobRow | null}} BackgroundJobScheduledMessage
|
|
246
|
+
* @typedef {{type: "get-scheduled-job-error", error?: string}} BackgroundJobGetScheduledErrorMessage
|
|
247
|
+
* @typedef {{type: "wake-scheduled", scheduleKey: string}} BackgroundJobWakeScheduledMessage
|
|
248
|
+
* @typedef {{type: "schedule-woken", jobId: string | null, outcome: BackgroundJobWakeOutcome}} BackgroundJobScheduleWokenMessage
|
|
249
|
+
* @typedef {{type: "wake-scheduled-error", error?: string}} BackgroundJobWakeScheduledErrorMessage
|
|
225
250
|
* @typedef {{type: "job", payload: BackgroundJobPayload}} BackgroundJobJobMessage
|
|
226
251
|
* @typedef {{type: "job-accepted", jobId: string, handoffId?: string, workerId?: string, handedOffAtMs?: number, receivedAtMs?: number, startedAtMs?: number, childInstanceId?: string, childPid?: number}} BackgroundJobAcceptedMessage
|
|
227
252
|
* @typedef {{type: "job-complete", jobId: string, handoffId?: string, workerId?: string, handedOffAtMs?: number}} BackgroundJobCompleteMessage
|
|
@@ -231,7 +256,7 @@
|
|
|
231
256
|
* @typedef {{type: "job-update-error", jobId: string, error?: string}} BackgroundJobUpdateErrorMessage
|
|
232
257
|
*/
|
|
233
258
|
/**
|
|
234
|
-
* @typedef {BackgroundJobHelloMessage | BackgroundJobGenerationAcceptedMessage | BackgroundJobGenerationRejectedMessage | BackgroundJobReadyMessage | BackgroundJobDrainingMessage | BackgroundJobHeartbeatMessage | BackgroundJobEnqueueMessage | BackgroundJobEnqueuedMessage | BackgroundJobEnqueueErrorMessage | BackgroundJobReplaceScheduledMessage | BackgroundJobScheduleReplacedMessage | BackgroundJobReplaceScheduledErrorMessage | BackgroundJobCancelScheduledMessage | BackgroundJobScheduleCancelledMessage | BackgroundJobCancelScheduledErrorMessage | BackgroundJobJobMessage | BackgroundJobAcceptedMessage | BackgroundJobCompleteMessage | BackgroundJobFailedMessage | BackgroundJobRescheduleMessage | BackgroundJobUpdatedMessage | BackgroundJobUpdateErrorMessage} BackgroundJobSocketMessage
|
|
259
|
+
* @typedef {BackgroundJobHelloMessage | BackgroundJobGenerationAcceptedMessage | BackgroundJobGenerationRejectedMessage | BackgroundJobReadyMessage | BackgroundJobDrainingMessage | BackgroundJobHeartbeatMessage | BackgroundJobEnqueueMessage | BackgroundJobEnqueuedMessage | BackgroundJobEnqueueErrorMessage | BackgroundJobReplaceScheduledMessage | BackgroundJobScheduleReplacedMessage | BackgroundJobReplaceScheduledErrorMessage | BackgroundJobCancelScheduledMessage | BackgroundJobScheduleCancelledMessage | BackgroundJobCancelScheduledErrorMessage | BackgroundJobGetScheduledMessage | BackgroundJobScheduledMessage | BackgroundJobGetScheduledErrorMessage | BackgroundJobWakeScheduledMessage | BackgroundJobScheduleWokenMessage | BackgroundJobWakeScheduledErrorMessage | BackgroundJobJobMessage | BackgroundJobAcceptedMessage | BackgroundJobCompleteMessage | BackgroundJobFailedMessage | BackgroundJobRescheduleMessage | BackgroundJobUpdatedMessage | BackgroundJobUpdateErrorMessage} BackgroundJobSocketMessage
|
|
235
260
|
*/
|
|
236
261
|
|
|
237
262
|
export const nothing = {}
|
|
@@ -207,6 +207,7 @@ export default class VelociousBackgroundJobsWebController extends Controller {
|
|
|
207
207
|
maxRetries: job.maxRetries,
|
|
208
208
|
orphanedAtMs: job.orphanedAtMs,
|
|
209
209
|
scheduleKey: job.scheduleKey,
|
|
210
|
+
scheduleOrder: job.scheduleOrder,
|
|
210
211
|
scheduledAtMs: job.scheduledAtMs,
|
|
211
212
|
status: job.status,
|
|
212
213
|
workerId: job.workerId
|
package/build/configuration.js
CHANGED
|
@@ -408,6 +408,13 @@ export default class VelociousConfiguration {
|
|
|
408
408
|
* @type {Map<string, typeof import("./http-server/websocket-channel.js").default>} */
|
|
409
409
|
this._websocketChannelClasses = new Map()
|
|
410
410
|
|
|
411
|
+
/**
|
|
412
|
+
* Channel types registered with `{liveOnly: true}`: their traffic is
|
|
413
|
+
* never persisted for replay, and `markChannelInterested` rejects the
|
|
414
|
+
* name.
|
|
415
|
+
* @type {Set<string>} */
|
|
416
|
+
this._liveOnlyWebsocketChannels = new Set()
|
|
417
|
+
|
|
411
418
|
/**
|
|
412
419
|
* Stores the websocket channel subscriptions value.
|
|
413
420
|
* @type {Map<string, Set<import("./http-server/websocket-channel.js").default>>} - channelType → live subscriptions across all sessions.
|
|
@@ -1126,6 +1133,7 @@ export default class VelociousConfiguration {
|
|
|
1126
1133
|
}
|
|
1127
1134
|
|
|
1128
1135
|
return {
|
|
1136
|
+
liveOnlyChannels: Array.from(this._liveOnlyWebsocketChannels),
|
|
1129
1137
|
pausedSessions: this._pausedWebsocketSessions.size,
|
|
1130
1138
|
registeredChannels: Array.from(this._websocketChannelClasses.keys()),
|
|
1131
1139
|
registeredConnections: Array.from(this._websocketConnectionClasses.keys()),
|
|
@@ -2966,12 +2974,15 @@ export default class VelociousConfiguration {
|
|
|
2966
2974
|
* Clients subscribe via `{type: "channel-subscribe", channelType: name, ...}`.
|
|
2967
2975
|
* @param {string} name - Client-facing channel type name.
|
|
2968
2976
|
* @param {typeof import("./http-server/websocket-channel.js").default} ChannelClass - Websocket channel class.
|
|
2977
|
+
* @param {{liveOnly?: boolean}} [options] - Registration options.
|
|
2969
2978
|
* @returns {void}
|
|
2970
2979
|
*/
|
|
2971
|
-
registerWebsocketChannel(name, ChannelClass) {
|
|
2980
|
+
registerWebsocketChannel(name, ChannelClass, {liveOnly = false} = {}) {
|
|
2972
2981
|
if (!name) throw new Error("Channel name is required")
|
|
2973
2982
|
if (!ChannelClass) throw new Error("ChannelClass is required")
|
|
2974
2983
|
this._websocketChannelClasses.set(name, ChannelClass)
|
|
2984
|
+
|
|
2985
|
+
if (liveOnly) this._liveOnlyWebsocketChannels.add(name)
|
|
2975
2986
|
}
|
|
2976
2987
|
|
|
2977
2988
|
/**
|
|
@@ -2983,6 +2994,17 @@ export default class VelociousConfiguration {
|
|
|
2983
2994
|
return this._websocketChannelClasses.get(name)
|
|
2984
2995
|
}
|
|
2985
2996
|
|
|
2997
|
+
/**
|
|
2998
|
+
* Whether a channel type was registered with `{liveOnly: true}`.
|
|
2999
|
+
* Live-only channels are never persisted for replay: the event-log
|
|
3000
|
+
* store's `markChannelInterested` throws for their names.
|
|
3001
|
+
* @param {string} name - Channel type name to look up.
|
|
3002
|
+
* @returns {boolean} - Whether the channel is declared live-only.
|
|
3003
|
+
*/
|
|
3004
|
+
isWebsocketChannelLiveOnly(name) {
|
|
3005
|
+
return this._liveOnlyWebsocketChannels.has(name)
|
|
3006
|
+
}
|
|
3007
|
+
|
|
2986
3008
|
/**
|
|
2987
3009
|
* Tracks a live channel subscription in the global routing registry.
|
|
2988
3010
|
* Called by the session when `canSubscribe()` resolves truthy; the
|
|
@@ -3336,11 +3358,13 @@ export default class VelociousConfiguration {
|
|
|
3336
3358
|
}
|
|
3337
3359
|
const previousDelivery = this._localBroadcastDeliveryTails.get(subscription)
|
|
3338
3360
|
const delivery = this.withoutCurrentConnectionContexts(() => {
|
|
3339
|
-
return (
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3361
|
+
return this.runWithTestSharedConnectionContexts(() => {
|
|
3362
|
+
return (previousDelivery || Promise.resolve())
|
|
3363
|
+
.then(() => this._deliverWebsocketChannelBroadcast(subscription, body, deliveryMetadata))
|
|
3364
|
+
.catch((error) => {
|
|
3365
|
+
console.error(`broadcastToChannel: ${name} subscription ${subscription.subscriptionId} deliverBroadcast threw`, error)
|
|
3366
|
+
})
|
|
3367
|
+
})
|
|
3344
3368
|
})
|
|
3345
3369
|
|
|
3346
3370
|
this._localBroadcastDeliveryTails.set(subscription, delivery)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
3
|
import BaseColumn from "../base-column.js"
|
|
4
|
-
import {digg} from "diggerize"
|
|
4
|
+
import { digg } from "diggerize"
|
|
5
5
|
|
|
6
6
|
export default class VelociousDatabaseDriversMssqlColumn extends BaseColumn {
|
|
7
7
|
/**
|
|
@@ -37,6 +37,6 @@ export default class VelociousDatabaseDriversMssqlColumn extends BaseColumn {
|
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
getPrimaryKey() { return digg(this, "data", "
|
|
40
|
+
getPrimaryKey() { return digg(this, "data", "isPrimaryKey") === 1 }
|
|
41
41
|
getType() { return digg(this, "data", "DATA_TYPE") }
|
|
42
42
|
}
|