@terreno/jobs 57.4.0 → 57.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/scheduler.js CHANGED
@@ -1,178 +1,76 @@
1
1
  "use strict";
2
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
- return new (P || (P = Promise))(function (resolve, reject) {
5
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
- step((generator = generator.apply(thisArg, _arguments || [])).next());
9
- });
10
- };
11
- var __generator = (this && this.__generator) || function (thisArg, body) {
12
- 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);
13
- return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
14
- function verb(n) { return function (v) { return step([n, v]); }; }
15
- function step(op) {
16
- if (f) throw new TypeError("Generator is already executing.");
17
- while (g && (g = 0, op[0] && (_ = 0)), _) try {
18
- 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;
19
- if (y = 0, t) op = [op[0] & 2, t.value];
20
- switch (op[0]) {
21
- case 0: case 1: t = op; break;
22
- case 4: _.label++; return { value: op[1], done: false };
23
- case 5: _.label++; y = op[1]; op = [0]; continue;
24
- case 7: op = _.ops.pop(); _.trys.pop(); continue;
25
- default:
26
- if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
27
- if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
28
- if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
29
- if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
30
- if (t[2]) _.ops.pop();
31
- _.trys.pop(); continue;
32
- }
33
- op = body.call(thisArg, _);
34
- } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
35
- if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
36
- }
37
- };
38
- var __values = (this && this.__values) || function(o) {
39
- var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
40
- if (m) return m.call(o);
41
- if (o && typeof o.length === "number") return {
42
- next: function () {
43
- if (o && i >= o.length) o = void 0;
44
- return { value: o && o[i++], done: !o };
45
- }
46
- };
47
- throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
48
- };
49
2
  Object.defineProperty(exports, "__esModule", { value: true });
50
3
  exports.tickDueSchedules = exports.buildScheduleRunIdempotencyKey = void 0;
51
- var api_1 = require("@terreno/api");
52
- var luxon_1 = require("luxon");
53
- var dispatchError_1 = require("./dispatchError");
54
- var job_1 = require("./models/job");
55
- var jobSchedule_1 = require("./models/jobSchedule");
56
- var modelTypes_1 = require("./modelTypes");
57
- var scheduleCron_1 = require("./scheduleCron");
4
+ const api_1 = require("@terreno/api");
5
+ const luxon_1 = require("luxon");
6
+ const dispatchError_1 = require("./dispatchError");
7
+ const job_1 = require("./models/job");
8
+ const jobSchedule_1 = require("./models/jobSchedule");
9
+ const modelTypes_1 = require("./modelTypes");
10
+ const scheduleCron_1 = require("./scheduleCron");
58
11
  /** @internal */
59
- var buildScheduleRunIdempotencyKey = function (scheduleId, dueRunAt) {
60
- return "schedule:".concat(scheduleId, ":").concat(dueRunAt.toISOString());
61
- };
12
+ const buildScheduleRunIdempotencyKey = (scheduleId, dueRunAt) => `schedule:${scheduleId}:${dueRunAt.toISOString()}`;
62
13
  exports.buildScheduleRunIdempotencyKey = buildScheduleRunIdempotencyKey;
63
- var tickOneSchedule = function (_a) { return __awaiter(void 0, [_a], void 0, function (_b) {
64
- var dueRunAt, advancedNextRunAt, claimed, inFlight, idempotencyKey, error_1, shouldRollbackSchedule;
65
- var jobsService = _b.jobsService, now = _b.now, schedule = _b.schedule;
66
- return __generator(this, function (_c) {
67
- switch (_c.label) {
68
- case 0:
69
- dueRunAt = schedule.nextRunAt;
70
- advancedNextRunAt = (0, scheduleCron_1.computeNextRunAtAfter)(schedule.cron, schedule.timezone, now);
71
- return [4 /*yield*/, jobSchedule_1.JobSchedule.findOneAndUpdate({
72
- _id: schedule._id,
73
- enabled: true,
74
- nextRunAt: dueRunAt,
75
- }, {
76
- $set: {
77
- nextRunAt: advancedNextRunAt,
78
- },
79
- }, { returnDocument: "before" })];
80
- case 1:
81
- claimed = _c.sent();
82
- if (!claimed) {
83
- return [2 /*return*/];
84
- }
85
- return [4 /*yield*/, job_1.Job.findOneOrNone({
86
- scheduleId: claimed._id,
87
- status: { $in: modelTypes_1.IN_FLIGHT_JOB_STATUSES },
88
- })];
89
- case 2:
90
- inFlight = _c.sent();
91
- if (inFlight) {
92
- return [2 /*return*/];
93
- }
94
- idempotencyKey = (0, exports.buildScheduleRunIdempotencyKey)(claimed._id.toString(), claimed.nextRunAt);
95
- _c.label = 3;
96
- case 3:
97
- _c.trys.push([3, 5, , 9]);
98
- return [4 /*yield*/, jobsService.enqueue({
99
- idempotencyKey: idempotencyKey,
100
- name: claimed.handlerName,
101
- payload: {},
102
- runAt: claimed.nextRunAt,
103
- scheduleId: claimed._id.toString(),
104
- })];
105
- case 4:
106
- _c.sent();
107
- return [3 /*break*/, 9];
108
- case 5:
109
- error_1 = _c.sent();
110
- shouldRollbackSchedule = !(error_1 instanceof dispatchError_1.JobDispatchError) || error_1.compensationSucceeded;
111
- if (!shouldRollbackSchedule) return [3 /*break*/, 7];
112
- return [4 /*yield*/, jobSchedule_1.JobSchedule.updateOne({ _id: claimed._id, nextRunAt: advancedNextRunAt }, { $set: { nextRunAt: dueRunAt } })];
113
- case 6:
114
- _c.sent();
115
- return [3 /*break*/, 8];
116
- case 7:
117
- api_1.logger.error("[JobSchedule] Dispatch compensation lost race for \"".concat(claimed.name, "\" (").concat(claimed._id.toString(), "); schedule nextRunAt left advanced"));
118
- _c.label = 8;
119
- case 8: throw error_1;
120
- case 9: return [2 /*return*/];
121
- }
14
+ const tickOneSchedule = async ({ jobsService, now, schedule, }) => {
15
+ const dueRunAt = schedule.nextRunAt;
16
+ const advancedNextRunAt = (0, scheduleCron_1.computeNextRunAtAfter)(schedule.cron, schedule.timezone, now);
17
+ const claimed = await jobSchedule_1.JobSchedule.findOneAndUpdate({
18
+ _id: schedule._id,
19
+ enabled: true,
20
+ nextRunAt: dueRunAt,
21
+ }, {
22
+ $set: {
23
+ nextRunAt: advancedNextRunAt,
24
+ },
25
+ }, { returnDocument: "before" });
26
+ if (!claimed) {
27
+ return;
28
+ }
29
+ const inFlight = await job_1.Job.findOneOrNone({
30
+ scheduleId: claimed._id,
31
+ status: { $in: modelTypes_1.IN_FLIGHT_JOB_STATUSES },
122
32
  });
123
- }); };
124
- var tickDueSchedules = function (_a) { return __awaiter(void 0, [_a], void 0, function (_b) {
125
- var dueSchedules, dueSchedules_1, dueSchedules_1_1, schedule, scheduleLog, error_2, e_1_1;
126
- var e_1, _c;
127
- var jobsService = _b.jobsService, _d = _b.now, now = _d === void 0 ? luxon_1.DateTime.utc().toJSDate() : _d;
128
- return __generator(this, function (_e) {
129
- switch (_e.label) {
130
- case 0: return [4 /*yield*/, jobSchedule_1.JobSchedule.find({
131
- enabled: true,
132
- nextRunAt: { $lte: now },
133
- }).sort({ nextRunAt: 1 })];
134
- case 1:
135
- dueSchedules = _e.sent();
136
- _e.label = 2;
137
- case 2:
138
- _e.trys.push([2, 9, 10, 11]);
139
- dueSchedules_1 = __values(dueSchedules), dueSchedules_1_1 = dueSchedules_1.next();
140
- _e.label = 3;
141
- case 3:
142
- if (!!dueSchedules_1_1.done) return [3 /*break*/, 8];
143
- schedule = dueSchedules_1_1.value;
144
- scheduleLog = (0, api_1.createScopedLogger)({
145
- labels: { scheduleName: schedule.name },
146
- prefix: "[JobSchedule]",
147
- });
148
- _e.label = 4;
149
- case 4:
150
- _e.trys.push([4, 6, , 7]);
151
- return [4 /*yield*/, tickOneSchedule({ jobsService: jobsService, now: now, schedule: schedule })];
152
- case 5:
153
- _e.sent();
154
- return [3 /*break*/, 7];
155
- case 6:
156
- error_2 = _e.sent();
157
- api_1.logger.error("[JobSchedule] Tick failed for \"".concat(schedule.name, "\": ").concat(error_2 instanceof Error ? error_2.message : String(error_2)));
158
- scheduleLog.error("Tick failed: ".concat(error_2 instanceof Error ? error_2.message : String(error_2)));
159
- return [3 /*break*/, 7];
160
- case 7:
161
- dueSchedules_1_1 = dueSchedules_1.next();
162
- return [3 /*break*/, 3];
163
- case 8: return [3 /*break*/, 11];
164
- case 9:
165
- e_1_1 = _e.sent();
166
- e_1 = { error: e_1_1 };
167
- return [3 /*break*/, 11];
168
- case 10:
169
- try {
170
- if (dueSchedules_1_1 && !dueSchedules_1_1.done && (_c = dueSchedules_1.return)) _c.call(dueSchedules_1);
171
- }
172
- finally { if (e_1) throw e_1.error; }
173
- return [7 /*endfinally*/];
174
- case 11: return [2 /*return*/];
33
+ if (inFlight) {
34
+ return;
35
+ }
36
+ const idempotencyKey = (0, exports.buildScheduleRunIdempotencyKey)(claimed._id.toString(), claimed.nextRunAt);
37
+ try {
38
+ await jobsService.enqueue({
39
+ idempotencyKey,
40
+ name: claimed.handlerName,
41
+ payload: {},
42
+ runAt: claimed.nextRunAt,
43
+ scheduleId: claimed._id.toString(),
44
+ });
45
+ }
46
+ catch (error) {
47
+ const shouldRollbackSchedule = !(error instanceof dispatchError_1.JobDispatchError) || error.compensationSucceeded;
48
+ if (shouldRollbackSchedule) {
49
+ await jobSchedule_1.JobSchedule.updateOne({ _id: claimed._id, nextRunAt: advancedNextRunAt }, { $set: { nextRunAt: dueRunAt } });
175
50
  }
176
- });
177
- }); };
51
+ else {
52
+ api_1.logger.error(`[JobSchedule] Dispatch compensation lost race for "${claimed.name}" (${claimed._id.toString()}); schedule nextRunAt left advanced`);
53
+ }
54
+ throw error;
55
+ }
56
+ };
57
+ const tickDueSchedules = async ({ jobsService, now = luxon_1.DateTime.utc().toJSDate(), }) => {
58
+ const dueSchedules = await jobSchedule_1.JobSchedule.find({
59
+ enabled: true,
60
+ nextRunAt: { $lte: now },
61
+ }).sort({ nextRunAt: 1 });
62
+ for (const schedule of dueSchedules) {
63
+ const scheduleLog = (0, api_1.createScopedLogger)({
64
+ labels: { scheduleName: schedule.name },
65
+ prefix: "[JobSchedule]",
66
+ });
67
+ try {
68
+ await tickOneSchedule({ jobsService, now, schedule });
69
+ }
70
+ catch (error) {
71
+ api_1.logger.error(`[JobSchedule] Tick failed for "${schedule.name}": ${error instanceof Error ? error.message : String(error)}`);
72
+ scheduleLog.error(`Tick failed: ${error instanceof Error ? error.message : String(error)}`);
73
+ }
74
+ }
75
+ };
178
76
  exports.tickDueSchedules = tickDueSchedules;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "author": "Josh Gachnang",
3
3
  "bugs": {
4
- "url": "https://github.com/FlourishHealth/terreno/issues"
4
+ "url": "https://github.com/TerrenoLabs/terreno/issues"
5
5
  },
6
6
  "dependencies": {
7
7
  "@terreno/api": "57.4.0",
@@ -35,7 +35,7 @@
35
35
  "types": "./dist/runners/vercelQueues.d.ts"
36
36
  }
37
37
  },
38
- "homepage": "https://github.com/FlourishHealth/terreno#readme",
38
+ "homepage": "https://github.com/TerrenoLabs/terreno#readme",
39
39
  "keywords": [
40
40
  "background-jobs",
41
41
  "queue",
@@ -64,7 +64,7 @@
64
64
  },
65
65
  "repository": {
66
66
  "type": "git",
67
- "url": "git+ssh://git@github.com/FlourishHealth/terreno.git"
67
+ "url": "git+ssh://git@github.com/TerrenoLabs/terreno.git"
68
68
  },
69
69
  "scripts": {
70
70
  "compile": "bun tsc",
@@ -78,5 +78,5 @@
78
78
  "test:coverage": "bun run ../scripts/check-coverage.ts"
79
79
  },
80
80
  "types": "dist/index.d.ts",
81
- "version": "57.4.0"
81
+ "version": "57.6.0"
82
82
  }
package/src/models/job.ts CHANGED
@@ -80,12 +80,12 @@ const jobSchema: JobSchema = new mongoose.Schema<JobDocument, JobModel>(
80
80
  },
81
81
  retriedById: {
82
82
  description: "Follow-up job created by an admin retry action",
83
- ref: "Job",
83
+ ref: "TerrenoJob",
84
84
  type: mongoose.Schema.Types.ObjectId,
85
85
  },
86
86
  retriedFromId: {
87
87
  description: "Original job row that this retry was created from",
88
- ref: "Job",
88
+ ref: "TerrenoJob",
89
89
  type: mongoose.Schema.Types.ObjectId,
90
90
  },
91
91
  runAt: {
@@ -96,7 +96,7 @@ const jobSchema: JobSchema = new mongoose.Schema<JobDocument, JobModel>(
96
96
  },
97
97
  scheduleId: {
98
98
  description: "Recurring schedule that enqueued this job, when applicable",
99
- ref: "JobSchedule",
99
+ ref: "TerrenoJobSchedule",
100
100
  type: mongoose.Schema.Types.ObjectId,
101
101
  },
102
102
  status: {
@@ -126,4 +126,4 @@ jobSchema.index(
126
126
  jobSchema.index({status: 1, runAt: 1});
127
127
  jobSchema.index({scheduleId: 1, status: 1});
128
128
 
129
- export const Job = mongoose.model<JobDocument, JobModel>("Job", jobSchema);
129
+ export const Job = mongoose.model<JobDocument, JobModel>("TerrenoJob", jobSchema, "jobs");
@@ -52,6 +52,7 @@ jobScheduleSchema.plugin(findExactlyOne);
52
52
  jobScheduleSchema.index({enabled: 1, nextRunAt: 1});
53
53
 
54
54
  export const JobSchedule = mongoose.model<JobScheduleDocument, JobScheduleModel>(
55
- "JobSchedule",
56
- jobScheduleSchema
55
+ "TerrenoJobSchedule",
56
+ jobScheduleSchema,
57
+ "jobschedules"
57
58
  );
@@ -1,5 +1,6 @@
1
- import {describe, it} from "bun:test";
1
+ import {afterAll, describe, it} from "bun:test";
2
2
  import {assert} from "chai";
3
+ import mongoose from "mongoose";
3
4
 
4
5
  import {
5
6
  getJobsService,
@@ -11,14 +12,29 @@ import {
11
12
  unregisterJobsService,
12
13
  } from "../index";
13
14
 
15
+ afterAll(() => {
16
+ for (const modelName of ["Job", "JobSchedule"]) {
17
+ if (mongoose.models[modelName]) {
18
+ mongoose.deleteModel(modelName);
19
+ }
20
+ }
21
+ });
22
+
14
23
  describe("package public exports", () => {
15
24
  it("re-exports JobsApp, models, MongoJobRunner, and getJobsService", (): void => {
16
25
  assert.equal(JobsApp.name, "JobsApp");
17
26
  assert.equal(MongoJobRunner.name, "MongoJobRunner");
18
- assert.equal(Job.modelName, "Job");
19
- assert.equal(JobSchedule.modelName, "JobSchedule");
27
+ assert.equal(Job.modelName, "TerrenoJob");
28
+ assert.equal(Job.collection.collectionName, "jobs");
29
+ assert.equal(JobSchedule.modelName, "TerrenoJobSchedule");
30
+ assert.equal(JobSchedule.collection.collectionName, "jobschedules");
20
31
  assert.equal(typeof getJobsService, "function");
21
32
  assert.equal(typeof tryGetJobsService, "function");
22
33
  assert.equal(typeof unregisterJobsService, "function");
23
34
  });
35
+
36
+ it("allows consumers to register generic job model names", (): void => {
37
+ assert.doesNotThrow(() => mongoose.model("Job", new mongoose.Schema({})));
38
+ assert.doesNotThrow(() => mongoose.model("JobSchedule", new mongoose.Schema({})));
39
+ });
24
40
  });
package/tsconfig.json CHANGED
@@ -1,33 +1,18 @@
1
1
  {
2
2
  "compilerOptions": {
3
- "allowJs": true,
4
- "allowSyntheticDefaultImports": true,
5
- "baseUrl": "./",
6
3
  "declaration": true,
7
- "downlevelIteration": true,
8
- "esModuleInterop": true,
9
- "forceConsistentCasingInFileNames": true,
10
- "ignoreDeprecations": "6.0",
11
- "jsx": "react",
12
- "lib": ["es2017", "dom", "es2017.object"],
13
- "module": "commonjs",
14
- "moduleResolution": "node",
15
- "noEmit": false,
4
+ "isolatedModules": true,
5
+ "lib": ["es2023"],
6
+ "module": "nodenext",
16
7
  "noImplicitAny": false,
17
8
  "noImplicitReturns": true,
18
- "noImplicitThis": true,
19
- "noUnusedLocals": false,
20
- "noUnusedParameters": false,
21
9
  "outDir": "./dist",
22
10
  "rootDir": "./src",
23
- "preserveConstEnums": true,
24
- "removeComments": false,
25
11
  "skipLibCheck": true,
26
12
  "strict": true,
27
- "strictFunctionTypes": true,
28
- "strictNullChecks": true,
29
- "target": "es5"
13
+ "target": "es2023",
14
+ "types": ["node"]
30
15
  },
31
16
  "exclude": ["node_modules", "src/tests", "**/*.test.ts"],
32
- "include": ["src/**/*.ts*", "src/*.ts"]
17
+ "include": ["src"]
33
18
  }