@terreno/jobs 57.4.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/LICENSE +21 -0
- package/README.md +56 -0
- package/biome.jsonc +20 -0
- package/bunfig.toml +5 -0
- package/dist/activeJobExecution.d.ts +3 -0
- package/dist/activeJobExecution.js +29 -0
- package/dist/claimLock.d.ts +16 -0
- package/dist/claimLock.js +34 -0
- package/dist/dispatchCompensation.d.ts +3 -0
- package/dist/dispatchCompensation.js +89 -0
- package/dist/dispatchError.d.ts +10 -0
- package/dist/dispatchError.js +33 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +15 -0
- package/dist/jobExecutor.d.ts +34 -0
- package/dist/jobExecutor.js +328 -0
- package/dist/jobsApp.d.ts +61 -0
- package/dist/jobsApp.js +308 -0
- package/dist/jobsService.d.ts +38 -0
- package/dist/jobsService.js +505 -0
- package/dist/modelTypes.d.ts +52 -0
- package/dist/modelTypes.js +4 -0
- package/dist/models/job.d.ts +2 -0
- package/dist/models/job.js +124 -0
- package/dist/models/jobSchedule.d.ts +2 -0
- package/dist/models/jobSchedule.js +48 -0
- package/dist/retryBackoff.d.ts +17 -0
- package/dist/retryBackoff.js +22 -0
- package/dist/routes/jobsAdmin.d.ts +15 -0
- package/dist/routes/jobsAdmin.js +596 -0
- package/dist/routes/jobsExecute.d.ts +12 -0
- package/dist/routes/jobsExecute.js +119 -0
- package/dist/runners/gcpCloudTasks.d.ts +78 -0
- package/dist/runners/gcpCloudTasks.js +256 -0
- package/dist/runners/mongoRunner.d.ts +14 -0
- package/dist/runners/mongoRunner.js +204 -0
- package/dist/runners/vercelQueues.d.ts +111 -0
- package/dist/runners/vercelQueues.js +326 -0
- package/dist/scheduleCron.d.ts +15 -0
- package/dist/scheduleCron.js +61 -0
- package/dist/scheduler.d.ts +8 -0
- package/dist/scheduler.js +178 -0
- package/dist/types.d.ts +64 -0
- package/dist/types.js +2 -0
- package/package.json +82 -0
- package/src/activeJobExecution.ts +28 -0
- package/src/claimLock.ts +37 -0
- package/src/dispatchCompensation.ts +39 -0
- package/src/dispatchError.ts +14 -0
- package/src/index.ts +24 -0
- package/src/jobExecutor.ts +333 -0
- package/src/jobsApp.ts +255 -0
- package/src/jobsService.ts +395 -0
- package/src/modelTypes.ts +78 -0
- package/src/models/job.ts +129 -0
- package/src/models/jobSchedule.ts +57 -0
- package/src/retryBackoff.ts +34 -0
- package/src/routes/jobsAdmin.ts +562 -0
- package/src/routes/jobsExecute.ts +95 -0
- package/src/runners/gcpCloudTasks.ts +290 -0
- package/src/runners/mongoRunner.ts +135 -0
- package/src/runners/vercelQueues.ts +394 -0
- package/src/scheduleCron.ts +65 -0
- package/src/scheduler.ts +112 -0
- package/src/tests/bunSetup.ts +9 -0
- package/src/tests/gcpCloudTasksRunner.test.ts +349 -0
- package/src/tests/jobModel.test.ts +62 -0
- package/src/tests/jobScheduleModel.test.ts +44 -0
- package/src/tests/jobsAdmin.test.ts +728 -0
- package/src/tests/jobsApp.test.ts +113 -0
- package/src/tests/jobsCustomRunner.test.ts +367 -0
- package/src/tests/jobsDelayIdempotency.test.ts +255 -0
- package/src/tests/jobsExecute.test.ts +640 -0
- package/src/tests/jobsRetry.test.ts +420 -0
- package/src/tests/jobsSchedule.test.ts +505 -0
- package/src/tests/jobsTracer.test.ts +165 -0
- package/src/tests/jobsWorkerLifecycle.test.ts +509 -0
- package/src/tests/packageExports.test.ts +24 -0
- package/src/tests/retryBackoff.test.ts +76 -0
- package/src/tests/scheduleCron.test.ts +87 -0
- package/src/tests/vercelQueuesRunner.test.ts +524 -0
- package/src/types.ts +74 -0
- package/tsconfig.json +33 -0
|
@@ -0,0 +1,596 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __assign = (this && this.__assign) || function () {
|
|
3
|
+
__assign = Object.assign || function(t) {
|
|
4
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
5
|
+
s = arguments[i];
|
|
6
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
7
|
+
t[p] = s[p];
|
|
8
|
+
}
|
|
9
|
+
return t;
|
|
10
|
+
};
|
|
11
|
+
return __assign.apply(this, arguments);
|
|
12
|
+
};
|
|
13
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
14
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
15
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
16
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
17
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
18
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
19
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
23
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
|
|
24
|
+
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
25
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
26
|
+
function step(op) {
|
|
27
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
28
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
29
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
30
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
31
|
+
switch (op[0]) {
|
|
32
|
+
case 0: case 1: t = op; break;
|
|
33
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
34
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
35
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
36
|
+
default:
|
|
37
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
38
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
39
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
40
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
41
|
+
if (t[2]) _.ops.pop();
|
|
42
|
+
_.trys.pop(); continue;
|
|
43
|
+
}
|
|
44
|
+
op = body.call(thisArg, _);
|
|
45
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
46
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
50
|
+
var t = {};
|
|
51
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
52
|
+
t[p] = s[p];
|
|
53
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
54
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
55
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
56
|
+
t[p[i]] = s[p[i]];
|
|
57
|
+
}
|
|
58
|
+
return t;
|
|
59
|
+
};
|
|
60
|
+
var __values = (this && this.__values) || function(o) {
|
|
61
|
+
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
|
|
62
|
+
if (m) return m.call(o);
|
|
63
|
+
if (o && typeof o.length === "number") return {
|
|
64
|
+
next: function () {
|
|
65
|
+
if (o && i >= o.length) o = void 0;
|
|
66
|
+
return { value: o && o[i++], done: !o };
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
|
|
70
|
+
};
|
|
71
|
+
var __read = (this && this.__read) || function (o, n) {
|
|
72
|
+
var m = typeof Symbol === "function" && o[Symbol.iterator];
|
|
73
|
+
if (!m) return o;
|
|
74
|
+
var i = m.call(o), r, ar = [], e;
|
|
75
|
+
try {
|
|
76
|
+
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
|
|
77
|
+
}
|
|
78
|
+
catch (error) { e = { error: error }; }
|
|
79
|
+
finally {
|
|
80
|
+
try {
|
|
81
|
+
if (r && !r.done && (m = i["return"])) m.call(i);
|
|
82
|
+
}
|
|
83
|
+
finally { if (e) throw e.error; }
|
|
84
|
+
}
|
|
85
|
+
return ar;
|
|
86
|
+
};
|
|
87
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
88
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
89
|
+
};
|
|
90
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
91
|
+
exports.registerJobsAdminRoutes = void 0;
|
|
92
|
+
var api_1 = require("@terreno/api");
|
|
93
|
+
var luxon_1 = require("luxon");
|
|
94
|
+
var mongoose_1 = __importDefault(require("mongoose"));
|
|
95
|
+
var job_1 = require("../models/job");
|
|
96
|
+
var jobSchedule_1 = require("../models/jobSchedule");
|
|
97
|
+
var DEFAULT_LIMIT = 50;
|
|
98
|
+
var MAX_LIMIT = 200;
|
|
99
|
+
var MAX_Q_LENGTH = 200;
|
|
100
|
+
var VALID_JOB_STATUSES = new Set([
|
|
101
|
+
"cancelled",
|
|
102
|
+
"completed",
|
|
103
|
+
"dead",
|
|
104
|
+
"failed",
|
|
105
|
+
"pending",
|
|
106
|
+
"running",
|
|
107
|
+
"scheduled",
|
|
108
|
+
]);
|
|
109
|
+
var JOB_ADMIN_ITEM_SCHEMA = {
|
|
110
|
+
_id: { type: "string" },
|
|
111
|
+
attemptCount: { type: "number" },
|
|
112
|
+
id: { type: "string" },
|
|
113
|
+
name: { type: "string" },
|
|
114
|
+
status: { type: "string" },
|
|
115
|
+
};
|
|
116
|
+
var SCHEDULE_ADMIN_ITEM_SCHEMA = {
|
|
117
|
+
cron: { type: "string" },
|
|
118
|
+
enabled: { type: "boolean" },
|
|
119
|
+
handlerName: { type: "string" },
|
|
120
|
+
id: { type: "string" },
|
|
121
|
+
name: { type: "string" },
|
|
122
|
+
nextRunAt: { format: "date-time", type: "string" },
|
|
123
|
+
timezone: { type: "string" },
|
|
124
|
+
};
|
|
125
|
+
var pathParam = function (value) {
|
|
126
|
+
if (Array.isArray(value)) {
|
|
127
|
+
return value[0];
|
|
128
|
+
}
|
|
129
|
+
return value;
|
|
130
|
+
};
|
|
131
|
+
var readString = function (value) {
|
|
132
|
+
if (typeof value !== "string") {
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
var trimmed = value.trim();
|
|
136
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
137
|
+
};
|
|
138
|
+
var readPositiveInt = function (value, fallback, max) {
|
|
139
|
+
var parsed = typeof value === "number"
|
|
140
|
+
? value
|
|
141
|
+
: typeof value === "string" && value.trim().length > 0
|
|
142
|
+
? Number(value)
|
|
143
|
+
: Number.NaN;
|
|
144
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
145
|
+
return fallback;
|
|
146
|
+
}
|
|
147
|
+
return Math.min(Math.floor(parsed), max);
|
|
148
|
+
};
|
|
149
|
+
var escapeRegexLiteral = function (value) { return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); };
|
|
150
|
+
var parseIsoDate = function (value, label) {
|
|
151
|
+
var raw = readString(value);
|
|
152
|
+
if (!raw) {
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
var parsed = luxon_1.DateTime.fromISO(raw, { zone: "utc" });
|
|
156
|
+
if (!parsed.isValid) {
|
|
157
|
+
throw new api_1.APIError({ status: 400, title: "Invalid ".concat(label) });
|
|
158
|
+
}
|
|
159
|
+
return parsed.toJSDate();
|
|
160
|
+
};
|
|
161
|
+
var parseSearchQuery = function (value) {
|
|
162
|
+
var raw = readString(value);
|
|
163
|
+
if (!raw) {
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
if (raw.length > MAX_Q_LENGTH) {
|
|
167
|
+
throw new api_1.APIError({ status: 400, title: "Search query too long" });
|
|
168
|
+
}
|
|
169
|
+
return escapeRegexLiteral(raw);
|
|
170
|
+
};
|
|
171
|
+
var parseStatusFilter = function (value) {
|
|
172
|
+
var status = readString(value);
|
|
173
|
+
if (!status) {
|
|
174
|
+
return undefined;
|
|
175
|
+
}
|
|
176
|
+
if (!VALID_JOB_STATUSES.has(status)) {
|
|
177
|
+
throw new api_1.APIError({ status: 400, title: "Invalid status" });
|
|
178
|
+
}
|
|
179
|
+
return status;
|
|
180
|
+
};
|
|
181
|
+
var parseListFilters = function (query) { return ({
|
|
182
|
+
end: parseIsoDate(query.end, "end date"),
|
|
183
|
+
name: readString(query.name),
|
|
184
|
+
q: parseSearchQuery(query.q),
|
|
185
|
+
scheduleId: readString(query.scheduleId),
|
|
186
|
+
start: parseIsoDate(query.start, "start date"),
|
|
187
|
+
status: parseStatusFilter(query.status),
|
|
188
|
+
}); };
|
|
189
|
+
var EMPTY_STATUS_COUNTS = {
|
|
190
|
+
cancelled: 0,
|
|
191
|
+
completed: 0,
|
|
192
|
+
dead: 0,
|
|
193
|
+
failed: 0,
|
|
194
|
+
pending: 0,
|
|
195
|
+
running: 0,
|
|
196
|
+
scheduled: 0,
|
|
197
|
+
};
|
|
198
|
+
var buildStatusCounts = function (grouped) {
|
|
199
|
+
var e_1, _a;
|
|
200
|
+
var byStatus = __assign({}, EMPTY_STATUS_COUNTS);
|
|
201
|
+
var total = 0;
|
|
202
|
+
try {
|
|
203
|
+
for (var grouped_1 = __values(grouped), grouped_1_1 = grouped_1.next(); !grouped_1_1.done; grouped_1_1 = grouped_1.next()) {
|
|
204
|
+
var row = grouped_1_1.value;
|
|
205
|
+
if (!VALID_JOB_STATUSES.has(row._id)) {
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
byStatus[row._id] = row.count;
|
|
209
|
+
total += row.count;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
213
|
+
finally {
|
|
214
|
+
try {
|
|
215
|
+
if (grouped_1_1 && !grouped_1_1.done && (_a = grouped_1.return)) _a.call(grouped_1);
|
|
216
|
+
}
|
|
217
|
+
finally { if (e_1) throw e_1.error; }
|
|
218
|
+
}
|
|
219
|
+
return { byStatus: byStatus, total: total };
|
|
220
|
+
};
|
|
221
|
+
var buildListMatch = function (filters) {
|
|
222
|
+
var match = {};
|
|
223
|
+
if (filters.name) {
|
|
224
|
+
match.name = filters.name;
|
|
225
|
+
}
|
|
226
|
+
if (filters.status) {
|
|
227
|
+
match.status = filters.status;
|
|
228
|
+
}
|
|
229
|
+
if (filters.scheduleId) {
|
|
230
|
+
if (!mongoose_1.default.isValidObjectId(filters.scheduleId)) {
|
|
231
|
+
throw new api_1.APIError({ status: 400, title: "Invalid scheduleId" });
|
|
232
|
+
}
|
|
233
|
+
match.scheduleId = new mongoose_1.default.Types.ObjectId(filters.scheduleId);
|
|
234
|
+
}
|
|
235
|
+
if (filters.start || filters.end) {
|
|
236
|
+
var created = {};
|
|
237
|
+
if (filters.start) {
|
|
238
|
+
created.$gte = filters.start;
|
|
239
|
+
}
|
|
240
|
+
if (filters.end) {
|
|
241
|
+
created.$lte = filters.end;
|
|
242
|
+
}
|
|
243
|
+
match.created = created;
|
|
244
|
+
}
|
|
245
|
+
if (filters.q) {
|
|
246
|
+
match.$or = [
|
|
247
|
+
{ lastError: { $options: "i", $regex: filters.q } },
|
|
248
|
+
{ name: { $options: "i", $regex: filters.q } },
|
|
249
|
+
];
|
|
250
|
+
}
|
|
251
|
+
return match;
|
|
252
|
+
};
|
|
253
|
+
var projectAdminPayload = function (job, redactPayload) {
|
|
254
|
+
if (job.payloadRedacted) {
|
|
255
|
+
return undefined;
|
|
256
|
+
}
|
|
257
|
+
if (!redactPayload) {
|
|
258
|
+
return undefined;
|
|
259
|
+
}
|
|
260
|
+
return redactPayload({
|
|
261
|
+
name: job.name,
|
|
262
|
+
payload: job.payload,
|
|
263
|
+
payloadRedacted: job.payloadRedacted,
|
|
264
|
+
});
|
|
265
|
+
};
|
|
266
|
+
var serializeJob = function (job, redactPayload) {
|
|
267
|
+
var json = job.toJSON();
|
|
268
|
+
var _storedPayload = json.payload, rest = __rest(json, ["payload"]);
|
|
269
|
+
var projectedPayload = projectAdminPayload(job, redactPayload);
|
|
270
|
+
if (projectedPayload === undefined) {
|
|
271
|
+
return __assign(__assign({}, rest), { id: job._id.toString() });
|
|
272
|
+
}
|
|
273
|
+
return __assign(__assign({}, rest), { id: job._id.toString(), payload: projectedPayload });
|
|
274
|
+
};
|
|
275
|
+
var serializeSchedule = function (schedule) {
|
|
276
|
+
var json = schedule.toJSON();
|
|
277
|
+
return __assign(__assign({}, json), { id: schedule._id.toString() });
|
|
278
|
+
};
|
|
279
|
+
var createRequireJobsAdmin = function (accessControl) {
|
|
280
|
+
return function (req) { return __awaiter(void 0, void 0, void 0, function () {
|
|
281
|
+
var user, result;
|
|
282
|
+
return __generator(this, function (_a) {
|
|
283
|
+
switch (_a.label) {
|
|
284
|
+
case 0:
|
|
285
|
+
user = req.user;
|
|
286
|
+
if (!user) {
|
|
287
|
+
throw new api_1.UnauthorizedError("Authentication required");
|
|
288
|
+
}
|
|
289
|
+
if (!accessControl) return [3 /*break*/, 2];
|
|
290
|
+
return [4 /*yield*/, accessControl.can({
|
|
291
|
+
permissions: { admin: [api_1.ADMIN_PAGE_ACTION, "jobs"] },
|
|
292
|
+
user: user,
|
|
293
|
+
})];
|
|
294
|
+
case 1:
|
|
295
|
+
result = _a.sent();
|
|
296
|
+
if (!result.allowed) {
|
|
297
|
+
throw new api_1.ForbiddenError({
|
|
298
|
+
detail: result.reason,
|
|
299
|
+
title: "Jobs admin access required",
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
return [2 /*return*/];
|
|
303
|
+
case 2:
|
|
304
|
+
if (!user.admin) {
|
|
305
|
+
throw new api_1.ForbiddenError("Admin access required");
|
|
306
|
+
}
|
|
307
|
+
return [2 /*return*/];
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
}); };
|
|
311
|
+
};
|
|
312
|
+
var listQueryParameters = function (builder) {
|
|
313
|
+
return builder
|
|
314
|
+
.withQueryParameter("page", { type: "number" }, { required: false })
|
|
315
|
+
.withQueryParameter("limit", { type: "number" }, { required: false })
|
|
316
|
+
.withQueryParameter("name", { type: "string" }, { required: false })
|
|
317
|
+
.withQueryParameter("status", { type: "string" }, { required: false })
|
|
318
|
+
.withQueryParameter("scheduleId", { type: "string" }, { required: false })
|
|
319
|
+
.withQueryParameter("start", { type: "string" }, { required: false })
|
|
320
|
+
.withQueryParameter("end", { type: "string" }, { required: false })
|
|
321
|
+
.withQueryParameter("q", { type: "string" }, { required: false });
|
|
322
|
+
};
|
|
323
|
+
var registerJobsAdminRoutes = function (_a) {
|
|
324
|
+
var app = _a.app, options = _a.options;
|
|
325
|
+
var routeOpenApi = options.openApi ? { openApi: options.openApi } : {};
|
|
326
|
+
var requireJobsAdmin = createRequireJobsAdmin(options.accessControl);
|
|
327
|
+
var basePath = options.basePath;
|
|
328
|
+
var jobsService = options.jobsService, redactPayload = options.redactPayload;
|
|
329
|
+
app.get("".concat(basePath, "/schedules"), [
|
|
330
|
+
(0, api_1.authenticateMiddleware)(),
|
|
331
|
+
(0, api_1.createOpenApiBuilder)(routeOpenApi)
|
|
332
|
+
.withTags(["admin", "jobs"])
|
|
333
|
+
.withSummary("List configured job schedules")
|
|
334
|
+
.withResponse(200, {
|
|
335
|
+
data: {
|
|
336
|
+
items: {
|
|
337
|
+
properties: SCHEDULE_ADMIN_ITEM_SCHEMA,
|
|
338
|
+
type: "object",
|
|
339
|
+
},
|
|
340
|
+
type: "array",
|
|
341
|
+
},
|
|
342
|
+
})
|
|
343
|
+
.build(),
|
|
344
|
+
], (0, api_1.asyncHandler)(function (req, res) { return __awaiter(void 0, void 0, void 0, function () {
|
|
345
|
+
var schedules;
|
|
346
|
+
return __generator(this, function (_a) {
|
|
347
|
+
switch (_a.label) {
|
|
348
|
+
case 0: return [4 /*yield*/, requireJobsAdmin(req)];
|
|
349
|
+
case 1:
|
|
350
|
+
_a.sent();
|
|
351
|
+
return [4 /*yield*/, jobSchedule_1.JobSchedule.find({}).sort("name")];
|
|
352
|
+
case 2:
|
|
353
|
+
schedules = _a.sent();
|
|
354
|
+
return [2 /*return*/, res.json({ data: schedules.map(function (schedule) { return serializeSchedule(schedule); }) })];
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
}); }));
|
|
358
|
+
app.post("".concat(basePath, "/schedules/:name/pause"), [
|
|
359
|
+
(0, api_1.authenticateMiddleware)(),
|
|
360
|
+
(0, api_1.createOpenApiBuilder)(routeOpenApi)
|
|
361
|
+
.withTags(["admin", "jobs"])
|
|
362
|
+
.withSummary("Pause a job schedule")
|
|
363
|
+
.withPathParameter("name", { type: "string" })
|
|
364
|
+
.withResponse(200, { data: { type: "object" } })
|
|
365
|
+
.build(),
|
|
366
|
+
], (0, api_1.asyncHandler)(function (req, res) { return __awaiter(void 0, void 0, void 0, function () {
|
|
367
|
+
var name, schedule;
|
|
368
|
+
return __generator(this, function (_a) {
|
|
369
|
+
switch (_a.label) {
|
|
370
|
+
case 0: return [4 /*yield*/, requireJobsAdmin(req)];
|
|
371
|
+
case 1:
|
|
372
|
+
_a.sent();
|
|
373
|
+
name = pathParam(req.params.name);
|
|
374
|
+
if (!name) {
|
|
375
|
+
throw new api_1.NotFoundError("Schedule not found");
|
|
376
|
+
}
|
|
377
|
+
return [4 /*yield*/, jobsService.pauseSchedule(name)];
|
|
378
|
+
case 2:
|
|
379
|
+
schedule = _a.sent();
|
|
380
|
+
return [2 /*return*/, res.json({ data: serializeSchedule(schedule) })];
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
}); }));
|
|
384
|
+
app.post("".concat(basePath, "/schedules/:name/resume"), [
|
|
385
|
+
(0, api_1.authenticateMiddleware)(),
|
|
386
|
+
(0, api_1.createOpenApiBuilder)(routeOpenApi)
|
|
387
|
+
.withTags(["admin", "jobs"])
|
|
388
|
+
.withSummary("Resume a job schedule")
|
|
389
|
+
.withPathParameter("name", { type: "string" })
|
|
390
|
+
.withResponse(200, { data: { type: "object" } })
|
|
391
|
+
.build(),
|
|
392
|
+
], (0, api_1.asyncHandler)(function (req, res) { return __awaiter(void 0, void 0, void 0, function () {
|
|
393
|
+
var name, schedule;
|
|
394
|
+
return __generator(this, function (_a) {
|
|
395
|
+
switch (_a.label) {
|
|
396
|
+
case 0: return [4 /*yield*/, requireJobsAdmin(req)];
|
|
397
|
+
case 1:
|
|
398
|
+
_a.sent();
|
|
399
|
+
name = pathParam(req.params.name);
|
|
400
|
+
if (!name) {
|
|
401
|
+
throw new api_1.NotFoundError("Schedule not found");
|
|
402
|
+
}
|
|
403
|
+
return [4 /*yield*/, jobsService.resumeSchedule(name)];
|
|
404
|
+
case 2:
|
|
405
|
+
schedule = _a.sent();
|
|
406
|
+
return [2 /*return*/, res.json({ data: serializeSchedule(schedule) })];
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
}); }));
|
|
410
|
+
app.get(basePath, [
|
|
411
|
+
(0, api_1.authenticateMiddleware)(),
|
|
412
|
+
listQueryParameters((0, api_1.createOpenApiBuilder)(routeOpenApi)
|
|
413
|
+
.withTags(["admin", "jobs"])
|
|
414
|
+
.withSummary("List background jobs"))
|
|
415
|
+
.withResponse(200, {
|
|
416
|
+
data: {
|
|
417
|
+
items: {
|
|
418
|
+
properties: JOB_ADMIN_ITEM_SCHEMA,
|
|
419
|
+
type: "object",
|
|
420
|
+
},
|
|
421
|
+
type: "array",
|
|
422
|
+
},
|
|
423
|
+
limit: { type: "number" },
|
|
424
|
+
more: { type: "boolean" },
|
|
425
|
+
page: { type: "number" },
|
|
426
|
+
total: { type: "number" },
|
|
427
|
+
})
|
|
428
|
+
.build(),
|
|
429
|
+
], (0, api_1.asyncHandler)(function (req, res) { return __awaiter(void 0, void 0, void 0, function () {
|
|
430
|
+
var filters, page, limit, skip, match, _a, jobs, total;
|
|
431
|
+
return __generator(this, function (_b) {
|
|
432
|
+
switch (_b.label) {
|
|
433
|
+
case 0: return [4 /*yield*/, requireJobsAdmin(req)];
|
|
434
|
+
case 1:
|
|
435
|
+
_b.sent();
|
|
436
|
+
filters = parseListFilters(req.query);
|
|
437
|
+
page = readPositiveInt(req.query.page, 1, Number.MAX_SAFE_INTEGER);
|
|
438
|
+
limit = readPositiveInt(req.query.limit, DEFAULT_LIMIT, MAX_LIMIT);
|
|
439
|
+
skip = (page - 1) * limit;
|
|
440
|
+
match = buildListMatch(filters);
|
|
441
|
+
return [4 /*yield*/, Promise.all([
|
|
442
|
+
job_1.Job.find(match).sort("-created").skip(skip).limit(limit),
|
|
443
|
+
job_1.Job.countDocuments(match),
|
|
444
|
+
])];
|
|
445
|
+
case 2:
|
|
446
|
+
_a = __read.apply(void 0, [_b.sent(), 2]), jobs = _a[0], total = _a[1];
|
|
447
|
+
return [2 /*return*/, res.json({
|
|
448
|
+
data: jobs.map(function (job) { return serializeJob(job, redactPayload); }),
|
|
449
|
+
limit: limit,
|
|
450
|
+
more: skip + limit < total,
|
|
451
|
+
page: page,
|
|
452
|
+
total: total,
|
|
453
|
+
})];
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
}); }));
|
|
457
|
+
app.get("".concat(basePath, "/stats"), [
|
|
458
|
+
(0, api_1.authenticateMiddleware)(),
|
|
459
|
+
(0, api_1.createOpenApiBuilder)(routeOpenApi)
|
|
460
|
+
.withTags(["admin", "jobs"])
|
|
461
|
+
.withSummary("Aggregate background job counts by status")
|
|
462
|
+
.withResponse(200, {
|
|
463
|
+
data: {
|
|
464
|
+
properties: {
|
|
465
|
+
byStatus: { type: "object" },
|
|
466
|
+
total: { type: "number" },
|
|
467
|
+
},
|
|
468
|
+
type: "object",
|
|
469
|
+
},
|
|
470
|
+
})
|
|
471
|
+
.build(),
|
|
472
|
+
], (0, api_1.asyncHandler)(function (req, res) { return __awaiter(void 0, void 0, void 0, function () {
|
|
473
|
+
var grouped;
|
|
474
|
+
return __generator(this, function (_a) {
|
|
475
|
+
switch (_a.label) {
|
|
476
|
+
case 0: return [4 /*yield*/, requireJobsAdmin(req)];
|
|
477
|
+
case 1:
|
|
478
|
+
_a.sent();
|
|
479
|
+
return [4 /*yield*/, job_1.Job.aggregate([
|
|
480
|
+
{ $group: { _id: "$status", count: { $sum: 1 } } },
|
|
481
|
+
])];
|
|
482
|
+
case 2:
|
|
483
|
+
grouped = _a.sent();
|
|
484
|
+
return [2 /*return*/, res.json({ data: buildStatusCounts(grouped) })];
|
|
485
|
+
}
|
|
486
|
+
});
|
|
487
|
+
}); }));
|
|
488
|
+
app.get("".concat(basePath, "/:id"), [
|
|
489
|
+
(0, api_1.authenticateMiddleware)(),
|
|
490
|
+
(0, api_1.createOpenApiBuilder)(routeOpenApi)
|
|
491
|
+
.withTags(["admin", "jobs"])
|
|
492
|
+
.withSummary("Read one background job")
|
|
493
|
+
.withPathParameter("id", { type: "string" })
|
|
494
|
+
.withResponse(200, { data: { type: "object" } })
|
|
495
|
+
.build(),
|
|
496
|
+
], (0, api_1.asyncHandler)(function (req, res) { return __awaiter(void 0, void 0, void 0, function () {
|
|
497
|
+
var id, job;
|
|
498
|
+
return __generator(this, function (_a) {
|
|
499
|
+
switch (_a.label) {
|
|
500
|
+
case 0: return [4 /*yield*/, requireJobsAdmin(req)];
|
|
501
|
+
case 1:
|
|
502
|
+
_a.sent();
|
|
503
|
+
id = pathParam(req.params.id);
|
|
504
|
+
if (!id || !mongoose_1.default.isValidObjectId(id)) {
|
|
505
|
+
throw new api_1.NotFoundError("Job not found");
|
|
506
|
+
}
|
|
507
|
+
return [4 /*yield*/, job_1.Job.findOneOrNone({ _id: id })];
|
|
508
|
+
case 2:
|
|
509
|
+
job = _a.sent();
|
|
510
|
+
if (!job) {
|
|
511
|
+
throw new api_1.NotFoundError("Job not found");
|
|
512
|
+
}
|
|
513
|
+
return [2 /*return*/, res.json({ data: serializeJob(job, redactPayload) })];
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
}); }));
|
|
517
|
+
app.post("".concat(basePath, "/:id/retry"), [
|
|
518
|
+
(0, api_1.authenticateMiddleware)(),
|
|
519
|
+
(0, api_1.createOpenApiBuilder)(routeOpenApi)
|
|
520
|
+
.withTags(["admin", "jobs"])
|
|
521
|
+
.withSummary("Retry a background job by creating a linked row")
|
|
522
|
+
.withPathParameter("id", { type: "string" })
|
|
523
|
+
.withResponse(200, { data: { type: "object" } })
|
|
524
|
+
.build(),
|
|
525
|
+
], (0, api_1.asyncHandler)(function (req, res) { return __awaiter(void 0, void 0, void 0, function () {
|
|
526
|
+
var id, retry;
|
|
527
|
+
return __generator(this, function (_a) {
|
|
528
|
+
switch (_a.label) {
|
|
529
|
+
case 0: return [4 /*yield*/, requireJobsAdmin(req)];
|
|
530
|
+
case 1:
|
|
531
|
+
_a.sent();
|
|
532
|
+
id = pathParam(req.params.id);
|
|
533
|
+
if (!id || !mongoose_1.default.isValidObjectId(id)) {
|
|
534
|
+
throw new api_1.NotFoundError("Job not found");
|
|
535
|
+
}
|
|
536
|
+
return [4 /*yield*/, jobsService.adminRetryJob(id)];
|
|
537
|
+
case 2:
|
|
538
|
+
retry = (_a.sent()).retry;
|
|
539
|
+
return [2 /*return*/, res.json({ data: serializeJob(retry, redactPayload) })];
|
|
540
|
+
}
|
|
541
|
+
});
|
|
542
|
+
}); }));
|
|
543
|
+
app.post("".concat(basePath, "/:id/requeue"), [
|
|
544
|
+
(0, api_1.authenticateMiddleware)(),
|
|
545
|
+
(0, api_1.createOpenApiBuilder)(routeOpenApi)
|
|
546
|
+
.withTags(["admin", "jobs"])
|
|
547
|
+
.withSummary("Requeue a dead, failed, or cancelled job")
|
|
548
|
+
.withPathParameter("id", { type: "string" })
|
|
549
|
+
.withResponse(200, { data: { type: "object" } })
|
|
550
|
+
.build(),
|
|
551
|
+
], (0, api_1.asyncHandler)(function (req, res) { return __awaiter(void 0, void 0, void 0, function () {
|
|
552
|
+
var id, job;
|
|
553
|
+
return __generator(this, function (_a) {
|
|
554
|
+
switch (_a.label) {
|
|
555
|
+
case 0: return [4 /*yield*/, requireJobsAdmin(req)];
|
|
556
|
+
case 1:
|
|
557
|
+
_a.sent();
|
|
558
|
+
id = pathParam(req.params.id);
|
|
559
|
+
if (!id || !mongoose_1.default.isValidObjectId(id)) {
|
|
560
|
+
throw new api_1.NotFoundError("Job not found");
|
|
561
|
+
}
|
|
562
|
+
return [4 /*yield*/, jobsService.adminRequeueJob(id)];
|
|
563
|
+
case 2:
|
|
564
|
+
job = _a.sent();
|
|
565
|
+
return [2 /*return*/, res.json({ data: serializeJob(job, redactPayload) })];
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
}); }));
|
|
569
|
+
app.post("".concat(basePath, "/:id/cancel"), [
|
|
570
|
+
(0, api_1.authenticateMiddleware)(),
|
|
571
|
+
(0, api_1.createOpenApiBuilder)(routeOpenApi)
|
|
572
|
+
.withTags(["admin", "jobs"])
|
|
573
|
+
.withSummary("Cancel a pending, scheduled, or running job")
|
|
574
|
+
.withPathParameter("id", { type: "string" })
|
|
575
|
+
.withResponse(200, { data: { type: "object" } })
|
|
576
|
+
.build(),
|
|
577
|
+
], (0, api_1.asyncHandler)(function (req, res) { return __awaiter(void 0, void 0, void 0, function () {
|
|
578
|
+
var id, job;
|
|
579
|
+
return __generator(this, function (_a) {
|
|
580
|
+
switch (_a.label) {
|
|
581
|
+
case 0: return [4 /*yield*/, requireJobsAdmin(req)];
|
|
582
|
+
case 1:
|
|
583
|
+
_a.sent();
|
|
584
|
+
id = pathParam(req.params.id);
|
|
585
|
+
if (!id || !mongoose_1.default.isValidObjectId(id)) {
|
|
586
|
+
throw new api_1.NotFoundError("Job not found");
|
|
587
|
+
}
|
|
588
|
+
return [4 /*yield*/, jobsService.adminCancelJob(id)];
|
|
589
|
+
case 2:
|
|
590
|
+
job = _a.sent();
|
|
591
|
+
return [2 /*return*/, res.json({ data: serializeJob(job, redactPayload) })];
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
}); }));
|
|
595
|
+
};
|
|
596
|
+
exports.registerJobsAdminRoutes = registerJobsAdminRoutes;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Application, Request } from "express";
|
|
2
|
+
import { type JobExecutionHost } from "../jobExecutor";
|
|
3
|
+
export type ExecuteAuthVerifier = (req: Request) => boolean | Promise<boolean>;
|
|
4
|
+
export interface RegisterJobsExecuteRouteOptions {
|
|
5
|
+
basePath: string;
|
|
6
|
+
executeAuth: ExecuteAuthVerifier;
|
|
7
|
+
host: JobExecutionHost;
|
|
8
|
+
}
|
|
9
|
+
export declare const registerJobsExecuteRoute: ({ app, options, }: {
|
|
10
|
+
app: Application;
|
|
11
|
+
options: RegisterJobsExecuteRouteOptions;
|
|
12
|
+
}) => void;
|