@zintrust/core 0.1.41 → 0.1.42
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/package.json +17 -1
- package/src/boot/bootstrap.js +27 -11
- package/src/boot/registry/runtime.d.ts.map +1 -1
- package/src/boot/registry/runtime.js +11 -0
- package/src/cli/CLI.d.ts.map +1 -1
- package/src/cli/CLI.js +12 -0
- package/src/cli/commands/ConfigCommand.d.ts.map +1 -1
- package/src/cli/commands/ConfigCommand.js +3 -5
- package/src/cli/commands/D1LearnCommand.d.ts +9 -0
- package/src/cli/commands/D1LearnCommand.d.ts.map +1 -0
- package/src/cli/commands/D1LearnCommand.js +143 -0
- package/src/cli/commands/D1MigrateCommand.d.ts.map +1 -1
- package/src/cli/commands/D1MigrateCommand.js +55 -16
- package/src/cli/commands/InitContainerCommand.d.ts.map +1 -1
- package/src/cli/commands/InitContainerCommand.js +21 -6
- package/src/cli/commands/InitEcosystemCommand.d.ts +6 -0
- package/src/cli/commands/InitEcosystemCommand.d.ts.map +1 -0
- package/src/cli/commands/InitEcosystemCommand.js +51 -0
- package/src/cli/commands/MigrateCommand.d.ts.map +1 -1
- package/src/cli/commands/MigrateCommand.js +78 -36
- package/src/cli/commands/MigrateWorkerCommand.d.ts.map +1 -1
- package/src/cli/commands/MigrateWorkerCommand.js +36 -2
- package/src/cli/commands/PutCommand.d.ts +6 -0
- package/src/cli/commands/PutCommand.d.ts.map +1 -0
- package/src/cli/commands/PutCommand.js +173 -0
- package/src/cli/commands/QueueRecoveryCommand.d.ts.map +1 -1
- package/src/cli/commands/QueueRecoveryCommand.js +113 -14
- package/src/cli/commands/ScheduleListCommand.d.ts +6 -0
- package/src/cli/commands/ScheduleListCommand.d.ts.map +1 -0
- package/src/cli/commands/ScheduleListCommand.js +62 -0
- package/src/cli/commands/ScheduleRunCommand.d.ts +6 -0
- package/src/cli/commands/ScheduleRunCommand.d.ts.map +1 -0
- package/src/cli/commands/ScheduleRunCommand.js +32 -0
- package/src/cli/commands/ScheduleStartCommand.d.ts +6 -0
- package/src/cli/commands/ScheduleStartCommand.d.ts.map +1 -0
- package/src/cli/commands/ScheduleStartCommand.js +40 -0
- package/src/cli/commands/SecretsCommand.d.ts.map +1 -1
- package/src/cli/commands/SecretsCommand.js +2 -2
- package/src/cli/commands/schedule/ScheduleCliSupport.d.ts +6 -0
- package/src/cli/commands/schedule/ScheduleCliSupport.d.ts.map +1 -0
- package/src/cli/commands/schedule/ScheduleCliSupport.js +55 -0
- package/src/cli/config/ConfigManager.d.ts.map +1 -1
- package/src/cli/config/ConfigManager.js +8 -1
- package/src/cli/d1/D1SqlMigrations.d.ts.map +1 -1
- package/src/cli/d1/D1SqlMigrations.js +11 -1
- package/src/cli/d1/WranglerConfig.d.ts.map +1 -1
- package/src/cli/d1/WranglerConfig.js +34 -2
- package/src/cli/services/VersionChecker.d.ts.map +1 -1
- package/src/cli/services/VersionChecker.js +5 -1
- package/src/cli/utils/DatabaseCliUtils.d.ts.map +1 -1
- package/src/cli/utils/DatabaseCliUtils.js +6 -1
- package/src/cli/utils/EnvFileLoader.d.ts.map +1 -1
- package/src/cli/utils/EnvFileLoader.js +33 -14
- package/src/cli.d.ts +5 -0
- package/src/cli.d.ts.map +1 -0
- package/src/cli.js +4 -0
- package/src/collections/index.d.ts +2 -2
- package/src/collections/index.d.ts.map +1 -1
- package/src/collections/index.js +1 -1
- package/src/common/RemoteSignedJson.d.ts.map +1 -1
- package/src/common/RemoteSignedJson.js +49 -23
- package/src/common/utility.d.ts.map +1 -1
- package/src/common/utility.js +2 -6
- package/src/config/cloudflare.d.ts.map +1 -1
- package/src/config/cloudflare.js +19 -8
- package/src/config/env.js +2 -2
- package/src/helper/index.d.ts +225 -0
- package/src/helper/index.d.ts.map +1 -0
- package/src/helper/index.js +347 -0
- package/src/index.d.ts +3 -6
- package/src/index.d.ts.map +1 -1
- package/src/index.js +7 -9
- package/src/migrations/MigrationDiscovery.d.ts.map +1 -1
- package/src/migrations/MigrationDiscovery.js +2 -1
- package/src/orm/DatabaseAdapter.d.ts +1 -0
- package/src/orm/DatabaseAdapter.d.ts.map +1 -1
- package/src/orm/SchemaStatemenWriter.d.ts +15 -0
- package/src/orm/SchemaStatemenWriter.d.ts.map +1 -0
- package/src/orm/SchemaStatemenWriter.js +78 -0
- package/src/orm/adapters/D1Adapter.d.ts.map +1 -1
- package/src/orm/adapters/D1Adapter.js +52 -2
- package/src/orm/adapters/D1RemoteAdapter.d.ts.map +1 -1
- package/src/orm/adapters/D1RemoteAdapter.js +137 -89
- package/src/orm/adapters/MySQLProxyAdapter.d.ts.map +1 -1
- package/src/orm/adapters/MySQLProxyAdapter.js +100 -81
- package/src/orm/adapters/PostgreSQLProxyAdapter.d.ts.map +1 -1
- package/src/orm/adapters/PostgreSQLProxyAdapter.js +26 -10
- package/src/orm/adapters/SqlProxyAdapterUtils.d.ts.map +1 -1
- package/src/orm/adapters/SqlProxyAdapterUtils.js +2 -1
- package/src/orm/adapters/SqlProxyRegistryMode.d.ts +12 -0
- package/src/orm/adapters/SqlProxyRegistryMode.d.ts.map +1 -0
- package/src/orm/adapters/SqlProxyRegistryMode.js +24 -0
- package/src/orm/adapters/SqlServerProxyAdapter.d.ts +3 -0
- package/src/orm/adapters/SqlServerProxyAdapter.d.ts.map +1 -1
- package/src/orm/adapters/SqlServerProxyAdapter.js +125 -117
- package/src/orm/migrations/MigrationStore.js +1 -1
- package/src/proxy/ProxyRequestParsing.d.ts +9 -0
- package/src/proxy/ProxyRequestParsing.d.ts.map +1 -0
- package/src/proxy/ProxyRequestParsing.js +16 -0
- package/src/proxy/RequestValidator.d.ts.map +1 -1
- package/src/proxy/RequestValidator.js +2 -1
- package/src/proxy/SigningService.js +2 -2
- package/src/proxy/SqlProxyDbOverrides.d.ts +17 -0
- package/src/proxy/SqlProxyDbOverrides.d.ts.map +1 -0
- package/src/proxy/SqlProxyDbOverrides.js +1 -0
- package/src/proxy/SqlProxyServerDeps.d.ts +12 -0
- package/src/proxy/SqlProxyServerDeps.d.ts.map +1 -0
- package/src/proxy/SqlProxyServerDeps.js +9 -0
- package/src/proxy/StatementPayloadValidator.d.ts +13 -0
- package/src/proxy/StatementPayloadValidator.d.ts.map +1 -0
- package/src/proxy/StatementPayloadValidator.js +18 -0
- package/src/proxy/StatementRegistryLoader.d.ts +2 -0
- package/src/proxy/StatementRegistryLoader.d.ts.map +1 -0
- package/src/proxy/StatementRegistryLoader.js +36 -0
- package/src/proxy/StatementRegistryResolver.d.ts +15 -0
- package/src/proxy/StatementRegistryResolver.d.ts.map +1 -0
- package/src/proxy/StatementRegistryResolver.js +34 -0
- package/src/proxy/d1/ZintrustD1Proxy.d.ts +2 -1
- package/src/proxy/d1/ZintrustD1Proxy.d.ts.map +1 -1
- package/src/proxy/d1/ZintrustD1Proxy.js +2 -1
- package/src/proxy/isMutatingSql.d.ts +2 -0
- package/src/proxy/isMutatingSql.d.ts.map +1 -0
- package/src/proxy/isMutatingSql.js +12 -0
- package/src/proxy/kv/ZintrustKvProxy.d.ts +2 -1
- package/src/proxy/kv/ZintrustKvProxy.d.ts.map +1 -1
- package/src/proxy/kv/ZintrustKvProxy.js +2 -1
- package/src/proxy/mysql/MySqlProxyServer.d.ts +2 -8
- package/src/proxy/mysql/MySqlProxyServer.d.ts.map +1 -1
- package/src/proxy/mysql/MySqlProxyServer.js +84 -51
- package/src/proxy/postgres/PostgresProxyServer.d.ts +2 -8
- package/src/proxy/postgres/PostgresProxyServer.d.ts.map +1 -1
- package/src/proxy/postgres/PostgresProxyServer.js +86 -48
- package/src/proxy/smtp/SmtpProxyServer.d.ts.map +1 -1
- package/src/proxy/smtp/SmtpProxyServer.js +6 -5
- package/src/proxy/sqlserver/SqlServerProxyServer.d.ts +2 -8
- package/src/proxy/sqlserver/SqlServerProxyServer.d.ts.map +1 -1
- package/src/proxy/sqlserver/SqlServerProxyServer.js +84 -49
- package/src/proxy.d.ts +4 -0
- package/src/proxy.d.ts.map +1 -0
- package/src/proxy.js +3 -0
- package/src/scheduler/Schedule.d.ts +36 -0
- package/src/scheduler/Schedule.d.ts.map +1 -0
- package/src/scheduler/Schedule.js +197 -0
- package/src/scheduler/ScheduleHttpGateway.d.ts +8 -0
- package/src/scheduler/ScheduleHttpGateway.d.ts.map +1 -0
- package/src/scheduler/ScheduleHttpGateway.js +196 -0
- package/src/scheduler/ScheduleRunner.d.ts +6 -0
- package/src/scheduler/ScheduleRunner.d.ts.map +1 -1
- package/src/scheduler/ScheduleRunner.js +166 -29
- package/src/scheduler/SchedulerRuntime.d.ts +15 -0
- package/src/scheduler/SchedulerRuntime.d.ts.map +1 -0
- package/src/scheduler/SchedulerRuntime.js +79 -0
- package/src/scheduler/cron/Cron.d.ts +19 -0
- package/src/scheduler/cron/Cron.d.ts.map +1 -0
- package/src/scheduler/cron/Cron.js +200 -0
- package/src/scheduler/leader/SchedulerLeader.d.ts +14 -0
- package/src/scheduler/leader/SchedulerLeader.d.ts.map +1 -0
- package/src/scheduler/leader/SchedulerLeader.js +187 -0
- package/src/scheduler/state/ScheduleStateStore.d.ts +27 -0
- package/src/scheduler/state/ScheduleStateStore.d.ts.map +1 -0
- package/src/scheduler/state/ScheduleStateStore.js +27 -0
- package/src/scheduler/types.d.ts +10 -0
- package/src/scheduler/types.d.ts.map +1 -1
- package/src/schedules/index.d.ts +1 -0
- package/src/schedules/index.d.ts.map +1 -1
- package/src/schedules/index.js +1 -0
- package/src/schedules/job-tracking-cleanup.d.ts +4 -0
- package/src/schedules/job-tracking-cleanup.d.ts.map +1 -0
- package/src/schedules/job-tracking-cleanup.js +116 -0
- package/src/schedules/log-cleanup.d.ts +1 -2
- package/src/schedules/log-cleanup.d.ts.map +1 -1
- package/src/schedules/log-cleanup.js +12 -15
- package/src/security/Sanitizer.d.ts.map +1 -1
- package/src/security/Sanitizer.js +1 -9
- package/src/security/SignedRequest.d.ts.map +1 -1
- package/src/security/SignedRequest.js +2 -2
- package/src/templates/docker/docker-compose.ecosystem.yml.tpl +301 -0
- package/src/templates/docker/docker-compose.schedules.yml.tpl +84 -0
- package/src/templates/project/basic/app/Schedules/index.ts.tpl +0 -0
- package/src/templates/project/basic/config/database.ts.tpl +1 -1
- package/src/toolkit/Secrets/Manifest.d.ts.map +1 -1
- package/src/toolkit/Secrets/Manifest.js +5 -7
- package/src/tools/mail/drivers/Smtp.d.ts.map +1 -1
- package/src/tools/mail/drivers/Smtp.js +7 -1
- package/src/tools/queue/JobReconciliationRunner.d.ts.map +1 -1
- package/src/tools/queue/JobReconciliationRunner.js +7 -39
- package/src/tools/queue/JobRecoveryDaemon.d.ts.map +1 -1
- package/src/tools/queue/JobRecoveryDaemon.js +116 -18
- package/src/tools/queue/JobStateTracker.d.ts +10 -1
- package/src/tools/queue/JobStateTracker.d.ts.map +1 -1
- package/src/tools/queue/JobStateTracker.js +24 -2
- package/src/tools/queue/JobStateTrackerDbPersistence.d.ts.map +1 -1
- package/src/tools/queue/JobStateTrackerDbPersistence.js +93 -2
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Env } from '../../config/env.js';
|
|
2
2
|
import { Logger } from '../../config/logger.js';
|
|
3
3
|
import { ErrorFactory } from '../../exceptions/ZintrustError.js';
|
|
4
|
+
import { useDatabase } from '../../orm/Database.js';
|
|
4
5
|
import { JobStateTracker } from './JobStateTracker.js';
|
|
5
6
|
import { Queue } from './Queue.js';
|
|
6
7
|
const parsePayload = (payload) => {
|
|
@@ -108,28 +109,78 @@ export const JobRecoveryDaemon = Object.freeze({
|
|
|
108
109
|
return 'manual_review';
|
|
109
110
|
}
|
|
110
111
|
const payload = parsePayload(record.payload);
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
112
|
+
// 30s, 1m, 3m backoff strategy
|
|
113
|
+
const getBackoffMs = (attempt) => {
|
|
114
|
+
if (attempt === 0)
|
|
115
|
+
return 30000;
|
|
116
|
+
if (attempt === 1)
|
|
117
|
+
return 60000;
|
|
118
|
+
return 180000;
|
|
119
|
+
};
|
|
120
|
+
const backoffMs = getBackoffMs(record.attempts);
|
|
121
|
+
try {
|
|
122
|
+
await Queue.enqueue(record.queueName, {
|
|
123
|
+
...payload,
|
|
124
|
+
uniqueId: record.jobId, // Preserves job ID (prevents duplication)
|
|
125
|
+
attempts: maxAttempts,
|
|
126
|
+
_currentAttempts: record.attempts + 1,
|
|
127
|
+
timestamp: Date.now() + backoffMs,
|
|
128
|
+
});
|
|
129
|
+
// Once the job is in QUEUE_DRIVER (or already exists), we never re-enqueue from DB/recovery again.
|
|
130
|
+
await JobStateTracker.handedOffToQueue({
|
|
131
|
+
queueName: record.queueName,
|
|
132
|
+
jobId: record.jobId,
|
|
133
|
+
reason: 'Enqueue-fallback job handed off to QUEUE_DRIVER',
|
|
134
|
+
});
|
|
135
|
+
return 'requeued';
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
139
|
+
const message = errorMessage.toLowerCase();
|
|
140
|
+
if (message.includes('jobid') && message.includes('already exists')) {
|
|
141
|
+
await JobStateTracker.handedOffToQueue({
|
|
142
|
+
queueName: record.queueName,
|
|
143
|
+
jobId: record.jobId,
|
|
144
|
+
reason: 'Job already exists in queue driver',
|
|
145
|
+
});
|
|
146
|
+
return 'requeued';
|
|
147
|
+
}
|
|
148
|
+
await JobStateTracker.pendingRecovery({
|
|
149
|
+
queueName: record.queueName,
|
|
150
|
+
jobId: record.jobId,
|
|
151
|
+
reason: 'Enqueue-fallback retry failed during recovery daemon run',
|
|
152
|
+
attempts: record.attempts + 1,
|
|
153
|
+
maxAttempts,
|
|
154
|
+
retryAt: new Date(Date.now() + backoffMs).toISOString(),
|
|
155
|
+
error,
|
|
156
|
+
});
|
|
157
|
+
throw error;
|
|
158
|
+
}
|
|
124
159
|
},
|
|
125
160
|
async runOnce() {
|
|
126
161
|
const minAgeMs = Math.max(0, Env.getInt('JOB_RECOVERY_MIN_AGE_MS', 5000));
|
|
127
162
|
const candidates = JobStateTracker.listRecoverable(minAgeMs);
|
|
163
|
+
const persisted = await listRecoverableFromPersistence(minAgeMs);
|
|
164
|
+
// De-dupe jobs that exist in both in-memory tracker and persistence.
|
|
165
|
+
const seen = new Set();
|
|
166
|
+
const allCandidates = [...candidates, ...persisted].filter((row) => {
|
|
167
|
+
const key = `${row.queueName}:${row.jobId}`;
|
|
168
|
+
if (seen.has(key))
|
|
169
|
+
return false;
|
|
170
|
+
seen.add(key);
|
|
171
|
+
return true;
|
|
172
|
+
});
|
|
128
173
|
let requeued = 0;
|
|
129
174
|
let deadLetter = 0;
|
|
130
175
|
let manualReview = 0;
|
|
131
|
-
const
|
|
132
|
-
|
|
176
|
+
const concurrency = 10;
|
|
177
|
+
const batches = [];
|
|
178
|
+
for (let offset = 0; offset < allCandidates.length; offset += concurrency) {
|
|
179
|
+
const slice = allCandidates.slice(offset, offset + concurrency);
|
|
180
|
+
batches.push(Promise.all(slice.map(async (candidate) => this.recoverOne(candidate))));
|
|
181
|
+
}
|
|
182
|
+
const batchResults = await Promise.all(batches);
|
|
183
|
+
batchResults.flat().forEach((result) => {
|
|
133
184
|
if (result === 'requeued')
|
|
134
185
|
requeued += 1;
|
|
135
186
|
if (result === 'dead_letter')
|
|
@@ -137,16 +188,16 @@ export const JobRecoveryDaemon = Object.freeze({
|
|
|
137
188
|
if (result === 'manual_review')
|
|
138
189
|
manualReview += 1;
|
|
139
190
|
});
|
|
140
|
-
if (
|
|
191
|
+
if (allCandidates.length > 0) {
|
|
141
192
|
Logger.info('Queue recovery daemon completed scan', {
|
|
142
|
-
scanned:
|
|
193
|
+
scanned: allCandidates.length,
|
|
143
194
|
requeued,
|
|
144
195
|
deadLetter,
|
|
145
196
|
manualReview,
|
|
146
197
|
});
|
|
147
198
|
}
|
|
148
199
|
return {
|
|
149
|
-
scanned:
|
|
200
|
+
scanned: allCandidates.length,
|
|
150
201
|
requeued,
|
|
151
202
|
deadLetter,
|
|
152
203
|
manualReview,
|
|
@@ -202,4 +253,51 @@ export const JobRecoveryDaemon = Object.freeze({
|
|
|
202
253
|
};
|
|
203
254
|
},
|
|
204
255
|
});
|
|
256
|
+
const toSqlDateTime = (value) => value.toISOString().slice(0, 19).replace('T', ' ');
|
|
257
|
+
const getPersistenceDb = () => useDatabase(undefined, Env.get('JOB_TRACKING_DB_CONNECTION', 'default'));
|
|
258
|
+
const listRecoverableFromPersistence = async (minAgeMs) => {
|
|
259
|
+
if (!Env.getBool('JOB_TRACKING_PERSISTENCE_ENABLED', false))
|
|
260
|
+
return [];
|
|
261
|
+
const db = getPersistenceDb();
|
|
262
|
+
const cutoff = new Date(Date.now() - Math.max(0, Math.floor(minAgeMs)));
|
|
263
|
+
const rows = await db
|
|
264
|
+
.table(Env.get('JOB_TRACKING_DB_TABLE', 'zintrust_jobs'))
|
|
265
|
+
.select('queue_name', 'job_id', 'attempts', 'max_attempts', 'payload_json', 'retry_at', 'updated_at')
|
|
266
|
+
.where('status', '=', 'pending_recovery')
|
|
267
|
+
.where('updated_at', '<=', toSqlDateTime(cutoff))
|
|
268
|
+
.limit(Math.max(1, Env.getInt('JOB_RECOVERY_DB_SCAN_LIMIT', 100)))
|
|
269
|
+
.get();
|
|
270
|
+
return (rows ?? []).map((row) => {
|
|
271
|
+
let payload = {};
|
|
272
|
+
try {
|
|
273
|
+
payload = JSON.parse(String(row.payload_json ?? '{}'));
|
|
274
|
+
}
|
|
275
|
+
catch {
|
|
276
|
+
payload = {};
|
|
277
|
+
}
|
|
278
|
+
const attempts = typeof row.attempts === 'number' && Number.isFinite(row.attempts)
|
|
279
|
+
? Math.max(0, Math.floor(row.attempts))
|
|
280
|
+
: 0;
|
|
281
|
+
const maxAttempts = typeof row.max_attempts === 'number' && Number.isFinite(row.max_attempts)
|
|
282
|
+
? Math.max(1, Math.floor(row.max_attempts))
|
|
283
|
+
: undefined;
|
|
284
|
+
const updatedAtRaw = typeof row.updated_at === 'string' ? row.updated_at : undefined;
|
|
285
|
+
const updatedAt = updatedAtRaw !== undefined && updatedAtRaw.trim().length > 0
|
|
286
|
+
? updatedAtRaw
|
|
287
|
+
: new Date(0).toISOString();
|
|
288
|
+
const retryAtRaw = typeof row.retry_at === 'string' ? row.retry_at : undefined;
|
|
289
|
+
const retryAt = retryAtRaw !== undefined && retryAtRaw.trim().length > 0 ? retryAtRaw : undefined;
|
|
290
|
+
return {
|
|
291
|
+
queueName: String(row.queue_name),
|
|
292
|
+
jobId: String(row.job_id),
|
|
293
|
+
status: 'pending_recovery',
|
|
294
|
+
attempts,
|
|
295
|
+
maxAttempts,
|
|
296
|
+
createdAt: new Date().toISOString(),
|
|
297
|
+
updatedAt,
|
|
298
|
+
payload,
|
|
299
|
+
retryAt,
|
|
300
|
+
};
|
|
301
|
+
});
|
|
302
|
+
};
|
|
205
303
|
export default JobRecoveryDaemon;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type JobTrackingStatus = 'pending' | 'active' | 'completed' | 'failed' | 'stalled' | 'timeout' | 'pending_recovery' | 'dead_letter' | 'manual_review' | 'delayed';
|
|
1
|
+
export type JobTrackingStatus = 'pending' | 'active' | 'enqueued' | 'completed' | 'failed' | 'stalled' | 'timeout' | 'pending_recovery' | 'dead_letter' | 'manual_review' | 'delayed';
|
|
2
2
|
export type JobTrackingRecord = {
|
|
3
3
|
jobId: string;
|
|
4
4
|
queueName: string;
|
|
@@ -44,6 +44,7 @@ export declare const JobStateTracker: Readonly<{
|
|
|
44
44
|
queueName: string;
|
|
45
45
|
jobId: string;
|
|
46
46
|
payload?: unknown;
|
|
47
|
+
attempts?: number;
|
|
47
48
|
maxAttempts?: number;
|
|
48
49
|
expectedCompletionAt?: string;
|
|
49
50
|
idempotencyKey?: string;
|
|
@@ -91,6 +92,9 @@ export declare const JobStateTracker: Readonly<{
|
|
|
91
92
|
pendingRecovery(input: {
|
|
92
93
|
queueName: string;
|
|
93
94
|
jobId: string;
|
|
95
|
+
attempts?: number;
|
|
96
|
+
maxAttempts?: number;
|
|
97
|
+
retryAt?: string;
|
|
94
98
|
reason?: string;
|
|
95
99
|
error?: unknown;
|
|
96
100
|
}): Promise<void>;
|
|
@@ -100,6 +104,11 @@ export declare const JobStateTracker: Readonly<{
|
|
|
100
104
|
reason?: string;
|
|
101
105
|
retryAt?: string;
|
|
102
106
|
}): Promise<void>;
|
|
107
|
+
handedOffToQueue(input: {
|
|
108
|
+
queueName: string;
|
|
109
|
+
jobId: string;
|
|
110
|
+
reason?: string;
|
|
111
|
+
}): Promise<void>;
|
|
103
112
|
setTerminalStatus(input: {
|
|
104
113
|
queueName: string;
|
|
105
114
|
jobId: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JobStateTracker.d.ts","sourceRoot":"","sources":["../../../../src/tools/queue/JobStateTracker.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"JobStateTracker.d.ts","sourceRoot":"","sources":["../../../../src/tools/queue/JobStateTracker.ts"],"names":[],"mappings":"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"}
|
|
@@ -175,7 +175,7 @@ export const JobStateTracker = Object.freeze({
|
|
|
175
175
|
if (isEnabled() === false)
|
|
176
176
|
return;
|
|
177
177
|
const transition = updateStatus(input.queueName, input.jobId, 'pending', 'Job enqueued', {
|
|
178
|
-
attempts: 0,
|
|
178
|
+
attempts: typeof input.attempts === 'number' ? input.attempts : 0,
|
|
179
179
|
maxAttempts: toFinitePositiveInt(input.maxAttempts),
|
|
180
180
|
payload: QueueDataRedactor.sanitizePayload(input.payload),
|
|
181
181
|
expectedCompletionAt: input.expectedCompletionAt,
|
|
@@ -259,6 +259,11 @@ export const JobStateTracker = Object.freeze({
|
|
|
259
259
|
if (isEnabled() === false)
|
|
260
260
|
return;
|
|
261
261
|
const transition = updateStatus(input.queueName, input.jobId, 'pending_recovery', input.reason ?? 'Job pending recovery', {
|
|
262
|
+
attempts: typeof input.attempts === 'number' && Number.isFinite(input.attempts)
|
|
263
|
+
? Math.max(0, Math.floor(input.attempts))
|
|
264
|
+
: undefined,
|
|
265
|
+
maxAttempts: toFinitePositiveInt(input.maxAttempts),
|
|
266
|
+
retryAt: input.retryAt,
|
|
262
267
|
error: normalizeError(input.error) ?? input.reason,
|
|
263
268
|
lastErrorCode: normalizeErrorCode(input.error),
|
|
264
269
|
});
|
|
@@ -273,6 +278,16 @@ export const JobStateTracker = Object.freeze({
|
|
|
273
278
|
});
|
|
274
279
|
await persistLatest(input.queueName, input.jobId, transition);
|
|
275
280
|
},
|
|
281
|
+
async handedOffToQueue(input) {
|
|
282
|
+
if (isEnabled() === false)
|
|
283
|
+
return;
|
|
284
|
+
const transition = updateStatus(input.queueName, input.jobId, 'enqueued', input.reason ?? 'Job handed off to queue driver', {
|
|
285
|
+
retryAt: undefined,
|
|
286
|
+
timeoutAt: undefined,
|
|
287
|
+
error: undefined,
|
|
288
|
+
});
|
|
289
|
+
await persistLatest(input.queueName, input.jobId, transition);
|
|
290
|
+
},
|
|
276
291
|
async setTerminalStatus(input) {
|
|
277
292
|
if (isEnabled() === false)
|
|
278
293
|
return;
|
|
@@ -345,12 +360,19 @@ export const JobStateTracker = Object.freeze({
|
|
|
345
360
|
},
|
|
346
361
|
listRecoverable(maxAgeMs, queueName) {
|
|
347
362
|
const threshold = Date.now() - Math.max(0, Math.floor(maxAgeMs));
|
|
348
|
-
const recoverable = new Set(['pending_recovery'
|
|
363
|
+
const recoverable = new Set(['pending_recovery']);
|
|
349
364
|
return Array.from(trackedJobs.values()).filter((row) => {
|
|
350
365
|
if (!recoverable.has(row.status))
|
|
351
366
|
return false;
|
|
352
367
|
if (queueName !== undefined && row.queueName !== queueName)
|
|
353
368
|
return false;
|
|
369
|
+
// Respect scheduled retry window to avoid hot-loop recovery churn.
|
|
370
|
+
if (row.retryAt !== undefined && row.retryAt.trim().length > 0) {
|
|
371
|
+
const retryAtMs = new Date(row.retryAt).getTime();
|
|
372
|
+
if (!Number.isNaN(retryAtMs) && retryAtMs > Date.now()) {
|
|
373
|
+
return false;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
354
376
|
const reference = toEpochMs(row.updatedAt);
|
|
355
377
|
if (reference === null)
|
|
356
378
|
return false;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JobStateTrackerDbPersistence.d.ts","sourceRoot":"","sources":["../../../../src/tools/queue/JobStateTrackerDbPersistence.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"JobStateTrackerDbPersistence.d.ts","sourceRoot":"","sources":["../../../../src/tools/queue/JobStateTrackerDbPersistence.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,iCAAiC,EAGlC,MAAM,wBAAwB,CAAC;AAGhC,KAAK,wBAAwB,GAAG;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC;AAoMF,eAAO,MAAM,kCAAkC,GAC7C,UAAS,wBAA6B,KACrC,iCAwDF,CAAC;AAEF,eAAO,MAAM,6CAA6C,QAAO,OAsBhE,CAAC;AAEF,eAAe,kCAAkC,CAAC"}
|
|
@@ -13,6 +13,10 @@ const toJson = (value) => {
|
|
|
13
13
|
return null;
|
|
14
14
|
}
|
|
15
15
|
};
|
|
16
|
+
const toNonNullJson = (value) => {
|
|
17
|
+
const json = toJson(value);
|
|
18
|
+
return json ?? null;
|
|
19
|
+
};
|
|
16
20
|
const toSqlDateTime = (isoLike) => {
|
|
17
21
|
if (typeof isoLike !== 'string' || isoLike.trim().length === 0)
|
|
18
22
|
return null;
|
|
@@ -58,7 +62,7 @@ const serializeJobRecord = (record, options) => {
|
|
|
58
62
|
status: record.status,
|
|
59
63
|
attempts: record.attempts,
|
|
60
64
|
max_attempts: record.maxAttempts ?? null,
|
|
61
|
-
payload_json:
|
|
65
|
+
payload_json: toNonNullJson(record.payload),
|
|
62
66
|
result_json: options.persistResult === false ? null : toJson(record.result),
|
|
63
67
|
last_error: record.lastError ?? null,
|
|
64
68
|
last_error_code: record.lastErrorCode ?? null,
|
|
@@ -78,6 +82,78 @@ const serializeJobRecord = (record, options) => {
|
|
|
78
82
|
updated_at: toSqlDateTime(record.updatedAt),
|
|
79
83
|
};
|
|
80
84
|
};
|
|
85
|
+
const serializeJobRecordForInsert = (record, options) => {
|
|
86
|
+
const payload = serializeJobRecord(record, options);
|
|
87
|
+
// Ensure payload_json is always non-null for inserts (some schemas require NOT NULL).
|
|
88
|
+
if (payload['payload_json'] === null) {
|
|
89
|
+
payload['payload_json'] = '{}';
|
|
90
|
+
}
|
|
91
|
+
return payload;
|
|
92
|
+
};
|
|
93
|
+
const applyUpdateEntries = (update, entries) => {
|
|
94
|
+
entries.forEach((entry) => {
|
|
95
|
+
if (entry.enabled)
|
|
96
|
+
update[entry.key] = entry.value;
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
|
+
const resolveResultEntry = (record, options, base) => {
|
|
100
|
+
if (options.persistResult === false) {
|
|
101
|
+
return { key: 'result_json', enabled: true, value: null };
|
|
102
|
+
}
|
|
103
|
+
const enabled = record.result !== undefined && base['result_json'] !== null;
|
|
104
|
+
return { key: 'result_json', enabled, value: base['result_json'] };
|
|
105
|
+
};
|
|
106
|
+
const serializeJobRecordForUpdate = (record, options) => {
|
|
107
|
+
const base = serializeJobRecord(record, options);
|
|
108
|
+
const update = {
|
|
109
|
+
status: base['status'],
|
|
110
|
+
attempts: base['attempts'],
|
|
111
|
+
updated_at: base['updated_at'],
|
|
112
|
+
};
|
|
113
|
+
applyUpdateEntries(update, [
|
|
114
|
+
{ key: 'max_attempts', enabled: record.maxAttempts !== undefined, value: base['max_attempts'] },
|
|
115
|
+
resolveResultEntry(record, options, base),
|
|
116
|
+
{ key: 'last_error', enabled: record.lastError !== undefined, value: base['last_error'] },
|
|
117
|
+
{
|
|
118
|
+
key: 'last_error_code',
|
|
119
|
+
enabled: record.lastErrorCode !== undefined,
|
|
120
|
+
value: base['last_error_code'],
|
|
121
|
+
},
|
|
122
|
+
{ key: 'retry_at', enabled: record.retryAt !== undefined, value: base['retry_at'] },
|
|
123
|
+
{ key: 'timeout_at', enabled: record.timeoutAt !== undefined, value: base['timeout_at'] },
|
|
124
|
+
{ key: 'heartbeat_at', enabled: record.heartbeatAt !== undefined, value: base['heartbeat_at'] },
|
|
125
|
+
{
|
|
126
|
+
key: 'expected_completion_at',
|
|
127
|
+
enabled: record.expectedCompletionAt !== undefined,
|
|
128
|
+
value: base['expected_completion_at'],
|
|
129
|
+
},
|
|
130
|
+
{ key: 'worker_name', enabled: record.workerName !== undefined, value: base['worker_name'] },
|
|
131
|
+
{
|
|
132
|
+
key: 'worker_instance_id',
|
|
133
|
+
enabled: record.workerInstanceId !== undefined,
|
|
134
|
+
value: base['worker_instance_id'],
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
key: 'worker_region',
|
|
138
|
+
enabled: record.workerRegion !== undefined,
|
|
139
|
+
value: base['worker_region'],
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
key: 'worker_version',
|
|
143
|
+
enabled: record.workerVersion !== undefined,
|
|
144
|
+
value: base['worker_version'],
|
|
145
|
+
},
|
|
146
|
+
{ key: 'recovered_at', enabled: record.recoveredAt !== undefined, value: base['recovered_at'] },
|
|
147
|
+
{
|
|
148
|
+
key: 'idempotency_key',
|
|
149
|
+
enabled: record.idempotencyKey !== undefined,
|
|
150
|
+
value: base['idempotency_key'],
|
|
151
|
+
},
|
|
152
|
+
{ key: 'started_at', enabled: record.startedAt !== undefined, value: base['started_at'] },
|
|
153
|
+
{ key: 'completed_at', enabled: record.completedAt !== undefined, value: base['completed_at'] },
|
|
154
|
+
]);
|
|
155
|
+
return update;
|
|
156
|
+
};
|
|
81
157
|
const serializeTransition = (transition) => {
|
|
82
158
|
return {
|
|
83
159
|
job_id: transition.jobId,
|
|
@@ -94,17 +170,27 @@ export const createJobStateTrackerDbPersistence = (options = {}) => {
|
|
|
94
170
|
const connectionName = getConnectionName(options);
|
|
95
171
|
const jobsTable = getJobsTable(options);
|
|
96
172
|
const transitionsTable = getTransitionsTable(options);
|
|
173
|
+
const persistTransitions = () => Env.getBool('JOB_TRACKING_PERSIST_TRANSITIONS_ENABLED', false);
|
|
174
|
+
const shouldInsertNewRow = (record) => {
|
|
175
|
+
// zintrust_jobs is an enqueue-fallback buffer: only jobs that failed to enqueue
|
|
176
|
+
// should be inserted into persistence. Once the job is in QUEUE_DRIVER, we only
|
|
177
|
+
// update the existing row (e.g., status=enqueued) but never create new rows.
|
|
178
|
+
return record.status === 'pending_recovery';
|
|
179
|
+
};
|
|
97
180
|
const upsertJob = async (record) => {
|
|
98
181
|
const db = getDatabase(connectionName);
|
|
99
182
|
if (db === null)
|
|
100
183
|
return;
|
|
101
|
-
const payload = serializeJobRecord(record, options);
|
|
102
184
|
const existing = await db
|
|
103
185
|
.table(jobsTable)
|
|
104
186
|
.where('job_id', '=', record.jobId)
|
|
105
187
|
.where('queue_name', '=', record.queueName)
|
|
106
188
|
.first();
|
|
107
189
|
if (existing) {
|
|
190
|
+
const existingStatus = typeof existing.status === 'string' ? existing.status.trim().toLowerCase() : '';
|
|
191
|
+
if (existingStatus === 'enqueued')
|
|
192
|
+
return;
|
|
193
|
+
const payload = serializeJobRecordForUpdate(record, options);
|
|
108
194
|
await db
|
|
109
195
|
.table(jobsTable)
|
|
110
196
|
.where('job_id', '=', record.jobId)
|
|
@@ -112,9 +198,14 @@ export const createJobStateTrackerDbPersistence = (options = {}) => {
|
|
|
112
198
|
.update(payload);
|
|
113
199
|
return;
|
|
114
200
|
}
|
|
201
|
+
if (!shouldInsertNewRow(record))
|
|
202
|
+
return;
|
|
203
|
+
const payload = serializeJobRecordForInsert(record, options);
|
|
115
204
|
await db.table(jobsTable).insert(payload);
|
|
116
205
|
};
|
|
117
206
|
const insertTransition = async (transition) => {
|
|
207
|
+
if (persistTransitions() === false)
|
|
208
|
+
return;
|
|
118
209
|
const db = getDatabase(connectionName);
|
|
119
210
|
if (db === null)
|
|
120
211
|
return;
|