@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,111 @@
|
|
|
1
|
+
import { DateTime } from "luxon";
|
|
2
|
+
import { type ExecuteJobOutcome, type JobExecutionHost } from "../jobExecutor";
|
|
3
|
+
import type { JobDocument } from "../modelTypes";
|
|
4
|
+
import type { JobRunner } from "../types";
|
|
5
|
+
/** Vercel Queues maximum delay and retention window (7 days). */
|
|
6
|
+
export declare const VERCEL_QUEUE_MAX_DELAY_SECONDS = 604800;
|
|
7
|
+
export declare const VERCEL_QUEUE_DEFAULT_RETENTION_SECONDS = 86400;
|
|
8
|
+
export declare const VERCEL_QUEUE_MIN_RETENTION_SECONDS = 60;
|
|
9
|
+
export declare const VERCEL_QUEUE_MIN_VISIBILITY_TIMEOUT_SECONDS = 30;
|
|
10
|
+
export declare const VERCEL_QUEUE_MAX_VISIBILITY_TIMEOUT_SECONDS = 3600;
|
|
11
|
+
export declare const VERCEL_QUEUE_DEFAULT_VISIBILITY_TIMEOUT_SECONDS = 1800;
|
|
12
|
+
export declare const VERCEL_QUEUE_LOCKED_RETRY_SECONDS = 30;
|
|
13
|
+
export interface VercelQueueSendOptions {
|
|
14
|
+
delaySeconds?: number;
|
|
15
|
+
headers?: Record<string, string>;
|
|
16
|
+
idempotencyKey?: string;
|
|
17
|
+
region?: string;
|
|
18
|
+
retentionSeconds?: number;
|
|
19
|
+
}
|
|
20
|
+
export interface VercelQueueSendResult {
|
|
21
|
+
messageId: string | null;
|
|
22
|
+
}
|
|
23
|
+
/** Narrow `send` seam — inject in tests; production loads `@vercel/queue`. */
|
|
24
|
+
export type VercelQueueSendFn = (topic: string, payload: unknown, options?: VercelQueueSendOptions) => Promise<VercelQueueSendResult>;
|
|
25
|
+
/** `@vercel/queue` 0.5.1 message metadata (all fields required). */
|
|
26
|
+
export interface VercelQueueMessageMetadata {
|
|
27
|
+
consumerGroup: string;
|
|
28
|
+
createdAt: Date;
|
|
29
|
+
deliveryCount: number;
|
|
30
|
+
expiresAt: Date;
|
|
31
|
+
messageId: string;
|
|
32
|
+
region: string;
|
|
33
|
+
topicName: string;
|
|
34
|
+
}
|
|
35
|
+
export type VercelQueueRetryDirective = {
|
|
36
|
+
acknowledge: true;
|
|
37
|
+
} | {
|
|
38
|
+
afterSeconds: number;
|
|
39
|
+
};
|
|
40
|
+
export type VercelQueueMessageHandler = (message: unknown, metadata: VercelQueueMessageMetadata) => Promise<void>;
|
|
41
|
+
export type VercelQueueRetryHandler = (error: unknown, metadata: VercelQueueMessageMetadata) => VercelQueueRetryDirective | undefined;
|
|
42
|
+
export type VercelHandleCallbackFn = (handler: VercelQueueMessageHandler, options?: VercelHandleCallbackOptions) => VercelQueueRouteHandler;
|
|
43
|
+
export interface VercelHandleCallbackOptions {
|
|
44
|
+
retry?: VercelQueueRetryHandler;
|
|
45
|
+
visibilityTimeoutSeconds?: number;
|
|
46
|
+
}
|
|
47
|
+
export type VercelQueueRouteRequest = Request | {
|
|
48
|
+
request: Request;
|
|
49
|
+
};
|
|
50
|
+
export type VercelQueueRouteHandler = (request: VercelQueueRouteRequest) => Promise<Response>;
|
|
51
|
+
export interface VercelQueuesRunnerConfig {
|
|
52
|
+
/** Injected `send` for tests. Production wiring loads `@vercel/queue`. */
|
|
53
|
+
send?: VercelQueueSendFn;
|
|
54
|
+
region?: string;
|
|
55
|
+
retentionSeconds?: number;
|
|
56
|
+
/** Queue topic published by {@link VercelQueuesRunner.enqueue}. */
|
|
57
|
+
topic: string;
|
|
58
|
+
}
|
|
59
|
+
export interface VercelQueuesConsumerOptions {
|
|
60
|
+
handleCallback?: VercelHandleCallbackFn;
|
|
61
|
+
host: JobExecutionHost;
|
|
62
|
+
signal?: AbortSignal;
|
|
63
|
+
visibilityTimeoutSeconds?: number;
|
|
64
|
+
}
|
|
65
|
+
export declare class VercelQueueRunAtBeyondLimitError extends Error {
|
|
66
|
+
readonly maxDelaySeconds: number;
|
|
67
|
+
readonly runAt: Date;
|
|
68
|
+
constructor(runAt: Date, maxDelaySeconds: number);
|
|
69
|
+
}
|
|
70
|
+
export declare class VercelQueueConsumerControl extends Error {
|
|
71
|
+
readonly directive: VercelQueueRetryDirective;
|
|
72
|
+
constructor(directive: VercelQueueRetryDirective);
|
|
73
|
+
}
|
|
74
|
+
export declare const computeVercelQueueDelaySeconds: (runAt: Date, now?: DateTime<true>) => number | undefined;
|
|
75
|
+
export declare const resolveVercelQueueSendRetentionSeconds: (delaySeconds: number | undefined, configuredRetention?: number) => number;
|
|
76
|
+
/** Clamp queue retry visibility changes to the vendor 0.5.1 range (30s–1h). */
|
|
77
|
+
export declare const clampVercelQueueRetryAfterSeconds: (seconds: number) => number;
|
|
78
|
+
/**
|
|
79
|
+
* Seconds until {@link runAt}, clamped for `handleCallback` retry visibility (30–3600).
|
|
80
|
+
* not_due jobs more than 1h away retry at 3600s until due.
|
|
81
|
+
*/
|
|
82
|
+
export declare const computeVercelQueueRetryAfterSeconds: (runAt: Date, now?: DateTime<true>) => number;
|
|
83
|
+
export declare const buildVercelQueueSendOptions: (job: JobDocument, config: {
|
|
84
|
+
region?: string;
|
|
85
|
+
retentionSeconds?: number;
|
|
86
|
+
}, now?: DateTime<true>) => VercelQueueSendOptions;
|
|
87
|
+
export declare const resolveVercelQueueConsumerRetry: (error: unknown) => VercelQueueRetryDirective | undefined;
|
|
88
|
+
export declare const applyVercelQueueExecutionOutcome: (outcome: ExecuteJobOutcome) => void;
|
|
89
|
+
/**
|
|
90
|
+
* Dispatch-only runner that publishes `{jobId}` to a Vercel Queue topic via `send`.
|
|
91
|
+
*
|
|
92
|
+
* Required `vercel.json` consumer (private route — platform auth only):
|
|
93
|
+
* `"experimentalTriggers": [{ "type": "queue/v2beta", "topic": "<topic>" }]`
|
|
94
|
+
*/
|
|
95
|
+
export declare class VercelQueuesRunner implements JobRunner {
|
|
96
|
+
readonly id = "vercel-queues";
|
|
97
|
+
/**
|
|
98
|
+
* Vercel push consumers are private `handleCallback` routes, not Express
|
|
99
|
+
* `POST /jobs/execute`. Use {@link createVercelQueuesConsumer} on the consumer route.
|
|
100
|
+
*/
|
|
101
|
+
readonly requiresExecuteRoute = false;
|
|
102
|
+
private readonly config;
|
|
103
|
+
private readonly send;
|
|
104
|
+
constructor(config: VercelQueuesRunnerConfig);
|
|
105
|
+
enqueue(job: JobDocument): Promise<void>;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Wraps vendor `handleCallback` to execute a queued `{jobId}` through Terreno's
|
|
109
|
+
* Mongo-backed {@link executeJobById} seam. Platform auth stays in the vendor handler.
|
|
110
|
+
*/
|
|
111
|
+
export declare const createVercelQueuesConsumer: (options: VercelQueuesConsumerOptions) => VercelQueueRouteHandler;
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __extends = (this && this.__extends) || (function () {
|
|
3
|
+
var extendStatics = function (d, b) {
|
|
4
|
+
extendStatics = Object.setPrototypeOf ||
|
|
5
|
+
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
|
6
|
+
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
|
|
7
|
+
return extendStatics(d, b);
|
|
8
|
+
};
|
|
9
|
+
return function (d, b) {
|
|
10
|
+
if (typeof b !== "function" && b !== null)
|
|
11
|
+
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
|
|
12
|
+
extendStatics(d, b);
|
|
13
|
+
function __() { this.constructor = d; }
|
|
14
|
+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
|
15
|
+
};
|
|
16
|
+
})();
|
|
17
|
+
var __assign = (this && this.__assign) || function () {
|
|
18
|
+
__assign = Object.assign || function(t) {
|
|
19
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
20
|
+
s = arguments[i];
|
|
21
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
22
|
+
t[p] = s[p];
|
|
23
|
+
}
|
|
24
|
+
return t;
|
|
25
|
+
};
|
|
26
|
+
return __assign.apply(this, arguments);
|
|
27
|
+
};
|
|
28
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
29
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
30
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
31
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
32
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
33
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
34
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
38
|
+
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);
|
|
39
|
+
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
40
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
41
|
+
function step(op) {
|
|
42
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
43
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
44
|
+
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;
|
|
45
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
46
|
+
switch (op[0]) {
|
|
47
|
+
case 0: case 1: t = op; break;
|
|
48
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
49
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
50
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
51
|
+
default:
|
|
52
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
53
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
54
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
55
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
56
|
+
if (t[2]) _.ops.pop();
|
|
57
|
+
_.trys.pop(); continue;
|
|
58
|
+
}
|
|
59
|
+
op = body.call(thisArg, _);
|
|
60
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
61
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
65
|
+
exports.createVercelQueuesConsumer = exports.VercelQueuesRunner = exports.applyVercelQueueExecutionOutcome = exports.resolveVercelQueueConsumerRetry = exports.buildVercelQueueSendOptions = exports.computeVercelQueueRetryAfterSeconds = exports.clampVercelQueueRetryAfterSeconds = exports.resolveVercelQueueSendRetentionSeconds = exports.computeVercelQueueDelaySeconds = exports.VercelQueueConsumerControl = exports.VercelQueueRunAtBeyondLimitError = exports.VERCEL_QUEUE_LOCKED_RETRY_SECONDS = exports.VERCEL_QUEUE_DEFAULT_VISIBILITY_TIMEOUT_SECONDS = exports.VERCEL_QUEUE_MAX_VISIBILITY_TIMEOUT_SECONDS = exports.VERCEL_QUEUE_MIN_VISIBILITY_TIMEOUT_SECONDS = exports.VERCEL_QUEUE_MIN_RETENTION_SECONDS = exports.VERCEL_QUEUE_DEFAULT_RETENTION_SECONDS = exports.VERCEL_QUEUE_MAX_DELAY_SECONDS = void 0;
|
|
66
|
+
var node_module_1 = require("node:module");
|
|
67
|
+
var luxon_1 = require("luxon");
|
|
68
|
+
var jobExecutor_1 = require("../jobExecutor");
|
|
69
|
+
/** Vercel Queues maximum delay and retention window (7 days). */
|
|
70
|
+
exports.VERCEL_QUEUE_MAX_DELAY_SECONDS = 604800;
|
|
71
|
+
exports.VERCEL_QUEUE_DEFAULT_RETENTION_SECONDS = 86400;
|
|
72
|
+
exports.VERCEL_QUEUE_MIN_RETENTION_SECONDS = 60;
|
|
73
|
+
exports.VERCEL_QUEUE_MIN_VISIBILITY_TIMEOUT_SECONDS = 30;
|
|
74
|
+
exports.VERCEL_QUEUE_MAX_VISIBILITY_TIMEOUT_SECONDS = 3600;
|
|
75
|
+
exports.VERCEL_QUEUE_DEFAULT_VISIBILITY_TIMEOUT_SECONDS = 1800;
|
|
76
|
+
exports.VERCEL_QUEUE_LOCKED_RETRY_SECONDS = 30;
|
|
77
|
+
var nodeRequire = (0, node_module_1.createRequire)(__filename);
|
|
78
|
+
var VercelQueueRunAtBeyondLimitError = /** @class */ (function (_super) {
|
|
79
|
+
__extends(VercelQueueRunAtBeyondLimitError, _super);
|
|
80
|
+
function VercelQueueRunAtBeyondLimitError(runAt, maxDelaySeconds) {
|
|
81
|
+
var _this = _super.call(this, "Job runAt ".concat(runAt.toISOString(), " exceeds Vercel Queue maximum delay of ").concat(maxDelaySeconds, " seconds")) || this;
|
|
82
|
+
_this.name = "VercelQueueRunAtBeyondLimitError";
|
|
83
|
+
_this.runAt = runAt;
|
|
84
|
+
_this.maxDelaySeconds = maxDelaySeconds;
|
|
85
|
+
return _this;
|
|
86
|
+
}
|
|
87
|
+
return VercelQueueRunAtBeyondLimitError;
|
|
88
|
+
}(Error));
|
|
89
|
+
exports.VercelQueueRunAtBeyondLimitError = VercelQueueRunAtBeyondLimitError;
|
|
90
|
+
var VercelQueueConsumerControl = /** @class */ (function (_super) {
|
|
91
|
+
__extends(VercelQueueConsumerControl, _super);
|
|
92
|
+
function VercelQueueConsumerControl(directive) {
|
|
93
|
+
var _this = _super.call(this, "Vercel queue consumer control") || this;
|
|
94
|
+
_this.name = "VercelQueueConsumerControl";
|
|
95
|
+
_this.directive = directive;
|
|
96
|
+
return _this;
|
|
97
|
+
}
|
|
98
|
+
return VercelQueueConsumerControl;
|
|
99
|
+
}(Error));
|
|
100
|
+
exports.VercelQueueConsumerControl = VercelQueueConsumerControl;
|
|
101
|
+
var requireNonEmpty = function (value, label) {
|
|
102
|
+
var trimmed = value === null || value === void 0 ? void 0 : value.trim();
|
|
103
|
+
if (!trimmed) {
|
|
104
|
+
throw new Error("VercelQueuesRunner requires ".concat(label));
|
|
105
|
+
}
|
|
106
|
+
return trimmed;
|
|
107
|
+
};
|
|
108
|
+
var validateRetentionSeconds = function (retentionSeconds) {
|
|
109
|
+
if (retentionSeconds === undefined) {
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
if (!Number.isFinite(retentionSeconds) ||
|
|
113
|
+
retentionSeconds < exports.VERCEL_QUEUE_MIN_RETENTION_SECONDS ||
|
|
114
|
+
retentionSeconds > exports.VERCEL_QUEUE_MAX_DELAY_SECONDS) {
|
|
115
|
+
throw new Error("VercelQueuesRunner retentionSeconds must be between ".concat(exports.VERCEL_QUEUE_MIN_RETENTION_SECONDS, " and ").concat(exports.VERCEL_QUEUE_MAX_DELAY_SECONDS, " seconds"));
|
|
116
|
+
}
|
|
117
|
+
return retentionSeconds;
|
|
118
|
+
};
|
|
119
|
+
var validateVisibilityTimeoutSeconds = function (visibilityTimeoutSeconds) {
|
|
120
|
+
if (visibilityTimeoutSeconds === undefined) {
|
|
121
|
+
return exports.VERCEL_QUEUE_DEFAULT_VISIBILITY_TIMEOUT_SECONDS;
|
|
122
|
+
}
|
|
123
|
+
if (!Number.isFinite(visibilityTimeoutSeconds) ||
|
|
124
|
+
visibilityTimeoutSeconds < exports.VERCEL_QUEUE_MIN_VISIBILITY_TIMEOUT_SECONDS ||
|
|
125
|
+
visibilityTimeoutSeconds > exports.VERCEL_QUEUE_MAX_VISIBILITY_TIMEOUT_SECONDS) {
|
|
126
|
+
throw new Error("VercelQueuesConsumer visibilityTimeoutSeconds must be between ".concat(exports.VERCEL_QUEUE_MIN_VISIBILITY_TIMEOUT_SECONDS, " and ").concat(exports.VERCEL_QUEUE_MAX_VISIBILITY_TIMEOUT_SECONDS, " seconds"));
|
|
127
|
+
}
|
|
128
|
+
return visibilityTimeoutSeconds;
|
|
129
|
+
};
|
|
130
|
+
var resolveConfig = function (config) {
|
|
131
|
+
var _a;
|
|
132
|
+
return ({
|
|
133
|
+
region: ((_a = config.region) === null || _a === void 0 ? void 0 : _a.trim()) || undefined,
|
|
134
|
+
retentionSeconds: validateRetentionSeconds(config.retentionSeconds),
|
|
135
|
+
topic: requireNonEmpty(config.topic, "topic"),
|
|
136
|
+
});
|
|
137
|
+
};
|
|
138
|
+
var loadQueueSend = function () {
|
|
139
|
+
try {
|
|
140
|
+
var module_1 = nodeRequire("@vercel/queue");
|
|
141
|
+
return module_1.send;
|
|
142
|
+
}
|
|
143
|
+
catch (_a) {
|
|
144
|
+
throw new Error("VercelQueuesRunner requires optional peer dependency @vercel/queue. " +
|
|
145
|
+
"Install it with: bun add @vercel/queue");
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
var loadHandleCallback = function () {
|
|
149
|
+
try {
|
|
150
|
+
var module_2 = nodeRequire("@vercel/queue");
|
|
151
|
+
return module_2.handleCallback;
|
|
152
|
+
}
|
|
153
|
+
catch (_a) {
|
|
154
|
+
throw new Error("createVercelQueuesConsumer requires optional peer dependency @vercel/queue. " +
|
|
155
|
+
"Install it with: bun add @vercel/queue");
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
var readJobId = function (message) {
|
|
159
|
+
if (!message || typeof message !== "object" || !("jobId" in message)) {
|
|
160
|
+
return undefined;
|
|
161
|
+
}
|
|
162
|
+
var jobId = message.jobId;
|
|
163
|
+
if (typeof jobId !== "string") {
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
var trimmed = jobId.trim();
|
|
167
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
168
|
+
};
|
|
169
|
+
var computeVercelQueueDelaySeconds = function (runAt, now) {
|
|
170
|
+
if (now === void 0) { now = luxon_1.DateTime.utc(); }
|
|
171
|
+
var scheduled = luxon_1.DateTime.fromJSDate(runAt, { zone: "utc" });
|
|
172
|
+
if (scheduled <= now) {
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
var delaySeconds = Math.floor(scheduled.diff(now, "seconds").seconds);
|
|
176
|
+
if (delaySeconds <= 0) {
|
|
177
|
+
return undefined;
|
|
178
|
+
}
|
|
179
|
+
if (delaySeconds > exports.VERCEL_QUEUE_MAX_DELAY_SECONDS) {
|
|
180
|
+
throw new VercelQueueRunAtBeyondLimitError(runAt, exports.VERCEL_QUEUE_MAX_DELAY_SECONDS);
|
|
181
|
+
}
|
|
182
|
+
return delaySeconds;
|
|
183
|
+
};
|
|
184
|
+
exports.computeVercelQueueDelaySeconds = computeVercelQueueDelaySeconds;
|
|
185
|
+
var resolveVercelQueueSendRetentionSeconds = function (delaySeconds, configuredRetention) {
|
|
186
|
+
var baseRetention = configuredRetention !== null && configuredRetention !== void 0 ? configuredRetention : exports.VERCEL_QUEUE_DEFAULT_RETENTION_SECONDS;
|
|
187
|
+
if (delaySeconds === undefined) {
|
|
188
|
+
return baseRetention;
|
|
189
|
+
}
|
|
190
|
+
return Math.max(baseRetention, delaySeconds);
|
|
191
|
+
};
|
|
192
|
+
exports.resolveVercelQueueSendRetentionSeconds = resolveVercelQueueSendRetentionSeconds;
|
|
193
|
+
/** Clamp queue retry visibility changes to the vendor 0.5.1 range (30s–1h). */
|
|
194
|
+
var clampVercelQueueRetryAfterSeconds = function (seconds) {
|
|
195
|
+
return Math.max(exports.VERCEL_QUEUE_MIN_VISIBILITY_TIMEOUT_SECONDS, Math.min(seconds, exports.VERCEL_QUEUE_MAX_VISIBILITY_TIMEOUT_SECONDS));
|
|
196
|
+
};
|
|
197
|
+
exports.clampVercelQueueRetryAfterSeconds = clampVercelQueueRetryAfterSeconds;
|
|
198
|
+
/**
|
|
199
|
+
* Seconds until {@link runAt}, clamped for `handleCallback` retry visibility (30–3600).
|
|
200
|
+
* not_due jobs more than 1h away retry at 3600s until due.
|
|
201
|
+
*/
|
|
202
|
+
var computeVercelQueueRetryAfterSeconds = function (runAt, now) {
|
|
203
|
+
if (now === void 0) { now = luxon_1.DateTime.utc(); }
|
|
204
|
+
var seconds = Math.ceil(luxon_1.DateTime.fromJSDate(runAt, { zone: "utc" }).diff(now, "seconds").seconds);
|
|
205
|
+
if (seconds <= 0) {
|
|
206
|
+
return exports.VERCEL_QUEUE_MIN_VISIBILITY_TIMEOUT_SECONDS;
|
|
207
|
+
}
|
|
208
|
+
return (0, exports.clampVercelQueueRetryAfterSeconds)(seconds);
|
|
209
|
+
};
|
|
210
|
+
exports.computeVercelQueueRetryAfterSeconds = computeVercelQueueRetryAfterSeconds;
|
|
211
|
+
var buildVercelQueueSendOptions = function (job, config, now) {
|
|
212
|
+
if (now === void 0) { now = luxon_1.DateTime.utc(); }
|
|
213
|
+
var delaySeconds = (0, exports.computeVercelQueueDelaySeconds)(job.runAt, now);
|
|
214
|
+
var retentionSeconds = (0, exports.resolveVercelQueueSendRetentionSeconds)(delaySeconds, config.retentionSeconds);
|
|
215
|
+
return __assign(__assign({ idempotencyKey: job._id.toString(), retentionSeconds: retentionSeconds }, (config.region ? { region: config.region } : {})), (delaySeconds !== undefined ? { delaySeconds: delaySeconds } : {}));
|
|
216
|
+
};
|
|
217
|
+
exports.buildVercelQueueSendOptions = buildVercelQueueSendOptions;
|
|
218
|
+
var normalizeRetryDirective = function (directive) {
|
|
219
|
+
if ("acknowledge" in directive) {
|
|
220
|
+
return directive;
|
|
221
|
+
}
|
|
222
|
+
return { afterSeconds: (0, exports.clampVercelQueueRetryAfterSeconds)(directive.afterSeconds) };
|
|
223
|
+
};
|
|
224
|
+
var resolveVercelQueueConsumerRetry = function (error) {
|
|
225
|
+
if (error instanceof VercelQueueConsumerControl) {
|
|
226
|
+
return normalizeRetryDirective(error.directive);
|
|
227
|
+
}
|
|
228
|
+
return undefined;
|
|
229
|
+
};
|
|
230
|
+
exports.resolveVercelQueueConsumerRetry = resolveVercelQueueConsumerRetry;
|
|
231
|
+
var applyVercelQueueExecutionOutcome = function (outcome) {
|
|
232
|
+
if (outcome.kind === "not_found") {
|
|
233
|
+
throw new VercelQueueConsumerControl({ acknowledge: true });
|
|
234
|
+
}
|
|
235
|
+
if (outcome.kind === "noop") {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
if (outcome.kind === "conflict") {
|
|
239
|
+
if (outcome.reason === "not_due") {
|
|
240
|
+
throw new VercelQueueConsumerControl({
|
|
241
|
+
afterSeconds: (0, exports.computeVercelQueueRetryAfterSeconds)(outcome.runAt),
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
throw new VercelQueueConsumerControl({ afterSeconds: exports.VERCEL_QUEUE_LOCKED_RETRY_SECONDS });
|
|
245
|
+
}
|
|
246
|
+
if (outcome.job.status === "pending") {
|
|
247
|
+
var now = luxon_1.DateTime.utc();
|
|
248
|
+
var afterSeconds = outcome.job.runAt > now.toJSDate()
|
|
249
|
+
? (0, exports.computeVercelQueueRetryAfterSeconds)(outcome.job.runAt, now)
|
|
250
|
+
: exports.VERCEL_QUEUE_MIN_VISIBILITY_TIMEOUT_SECONDS;
|
|
251
|
+
throw new VercelQueueConsumerControl({ afterSeconds: afterSeconds });
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
exports.applyVercelQueueExecutionOutcome = applyVercelQueueExecutionOutcome;
|
|
255
|
+
/**
|
|
256
|
+
* Dispatch-only runner that publishes `{jobId}` to a Vercel Queue topic via `send`.
|
|
257
|
+
*
|
|
258
|
+
* Required `vercel.json` consumer (private route — platform auth only):
|
|
259
|
+
* `"experimentalTriggers": [{ "type": "queue/v2beta", "topic": "<topic>" }]`
|
|
260
|
+
*/
|
|
261
|
+
var VercelQueuesRunner = /** @class */ (function () {
|
|
262
|
+
function VercelQueuesRunner(config) {
|
|
263
|
+
var _a;
|
|
264
|
+
this.id = "vercel-queues";
|
|
265
|
+
/**
|
|
266
|
+
* Vercel push consumers are private `handleCallback` routes, not Express
|
|
267
|
+
* `POST /jobs/execute`. Use {@link createVercelQueuesConsumer} on the consumer route.
|
|
268
|
+
*/
|
|
269
|
+
this.requiresExecuteRoute = false;
|
|
270
|
+
this.config = resolveConfig(config);
|
|
271
|
+
this.send = (_a = config.send) !== null && _a !== void 0 ? _a : loadQueueSend();
|
|
272
|
+
}
|
|
273
|
+
VercelQueuesRunner.prototype.enqueue = function (job) {
|
|
274
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
275
|
+
var payload, options;
|
|
276
|
+
return __generator(this, function (_a) {
|
|
277
|
+
switch (_a.label) {
|
|
278
|
+
case 0:
|
|
279
|
+
payload = { jobId: job._id.toString() };
|
|
280
|
+
options = (0, exports.buildVercelQueueSendOptions)(job, this.config);
|
|
281
|
+
return [4 /*yield*/, this.send(this.config.topic, payload, options)];
|
|
282
|
+
case 1:
|
|
283
|
+
_a.sent();
|
|
284
|
+
return [2 /*return*/];
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
};
|
|
289
|
+
return VercelQueuesRunner;
|
|
290
|
+
}());
|
|
291
|
+
exports.VercelQueuesRunner = VercelQueuesRunner;
|
|
292
|
+
/**
|
|
293
|
+
* Wraps vendor `handleCallback` to execute a queued `{jobId}` through Terreno's
|
|
294
|
+
* Mongo-backed {@link executeJobById} seam. Platform auth stays in the vendor handler.
|
|
295
|
+
*/
|
|
296
|
+
var createVercelQueuesConsumer = function (options) {
|
|
297
|
+
var _a, _b;
|
|
298
|
+
var handleCallback = (_a = options.handleCallback) !== null && _a !== void 0 ? _a : loadHandleCallback();
|
|
299
|
+
var signal = (_b = options.signal) !== null && _b !== void 0 ? _b : AbortSignal.timeout(30 * 60 * 1000);
|
|
300
|
+
var visibilityTimeoutSeconds = validateVisibilityTimeoutSeconds(options.visibilityTimeoutSeconds);
|
|
301
|
+
return handleCallback(function (message) { return __awaiter(void 0, void 0, void 0, function () {
|
|
302
|
+
var jobId, outcome;
|
|
303
|
+
return __generator(this, function (_a) {
|
|
304
|
+
switch (_a.label) {
|
|
305
|
+
case 0:
|
|
306
|
+
jobId = readJobId(message);
|
|
307
|
+
if (!jobId) {
|
|
308
|
+
throw new VercelQueueConsumerControl({ acknowledge: true });
|
|
309
|
+
}
|
|
310
|
+
return [4 /*yield*/, (0, jobExecutor_1.executeJobById)({
|
|
311
|
+
host: options.host,
|
|
312
|
+
jobId: jobId,
|
|
313
|
+
signal: signal,
|
|
314
|
+
})];
|
|
315
|
+
case 1:
|
|
316
|
+
outcome = _a.sent();
|
|
317
|
+
(0, exports.applyVercelQueueExecutionOutcome)(outcome);
|
|
318
|
+
return [2 /*return*/];
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
}); }, {
|
|
322
|
+
retry: exports.resolveVercelQueueConsumerRetry,
|
|
323
|
+
visibilityTimeoutSeconds: visibilityTimeoutSeconds,
|
|
324
|
+
});
|
|
325
|
+
};
|
|
326
|
+
exports.createVercelQueuesConsumer = createVercelQueuesConsumer;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare const resolveScheduleTimezone: ({ defaultTimezone, scheduleTimezone, }: {
|
|
2
|
+
defaultTimezone: string;
|
|
3
|
+
scheduleTimezone?: string;
|
|
4
|
+
}) => string;
|
|
5
|
+
/** @internal */
|
|
6
|
+
export declare const assertValidIanaTimezone: (timezone: string) => void;
|
|
7
|
+
/** @internal */
|
|
8
|
+
export declare const assertValidCronExpression: (cron: string, timezone: string) => void;
|
|
9
|
+
export declare const validateScheduleDefinition: ({ cron, defaultTimezone, scheduleTimezone, }: {
|
|
10
|
+
cron: string;
|
|
11
|
+
defaultTimezone: string;
|
|
12
|
+
scheduleTimezone?: string;
|
|
13
|
+
}) => string;
|
|
14
|
+
export declare const computeInitialNextRunAt: (cron: string, timezone: string) => Date;
|
|
15
|
+
export declare const computeNextRunAtAfter: (cron: string, timezone: string, after: Date) => Date;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.computeNextRunAtAfter = exports.computeInitialNextRunAt = exports.validateScheduleDefinition = exports.assertValidCronExpression = exports.assertValidIanaTimezone = exports.resolveScheduleTimezone = void 0;
|
|
4
|
+
var api_1 = require("@terreno/api");
|
|
5
|
+
var cron_1 = require("cron");
|
|
6
|
+
var luxon_1 = require("luxon");
|
|
7
|
+
var INVALID_CRON_TITLE = "Invalid cron expression";
|
|
8
|
+
var INVALID_TIMEZONE_TITLE = "Invalid IANA timezone";
|
|
9
|
+
var resolveScheduleTimezone = function (_a) {
|
|
10
|
+
var defaultTimezone = _a.defaultTimezone, scheduleTimezone = _a.scheduleTimezone;
|
|
11
|
+
return scheduleTimezone !== null && scheduleTimezone !== void 0 ? scheduleTimezone : defaultTimezone;
|
|
12
|
+
};
|
|
13
|
+
exports.resolveScheduleTimezone = resolveScheduleTimezone;
|
|
14
|
+
/** @internal */
|
|
15
|
+
var assertValidIanaTimezone = function (timezone) {
|
|
16
|
+
var _a;
|
|
17
|
+
var zoneCheck = luxon_1.DateTime.now().setZone(timezone);
|
|
18
|
+
if (!zoneCheck.isValid) {
|
|
19
|
+
throw new api_1.APIError({
|
|
20
|
+
detail: (_a = zoneCheck.invalidReason) !== null && _a !== void 0 ? _a : "Timezone is not a valid IANA identifier",
|
|
21
|
+
status: 400,
|
|
22
|
+
title: INVALID_TIMEZONE_TITLE,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
exports.assertValidIanaTimezone = assertValidIanaTimezone;
|
|
27
|
+
/** @internal */
|
|
28
|
+
var assertValidCronExpression = function (cron, timezone) {
|
|
29
|
+
try {
|
|
30
|
+
new cron_1.CronTime(cron, timezone);
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
var detail = error instanceof Error ? error.message : String(error);
|
|
34
|
+
throw new api_1.APIError({
|
|
35
|
+
cause: error instanceof Error ? error : undefined,
|
|
36
|
+
detail: detail,
|
|
37
|
+
status: 400,
|
|
38
|
+
title: INVALID_CRON_TITLE,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
exports.assertValidCronExpression = assertValidCronExpression;
|
|
43
|
+
var validateScheduleDefinition = function (_a) {
|
|
44
|
+
var cron = _a.cron, defaultTimezone = _a.defaultTimezone, scheduleTimezone = _a.scheduleTimezone;
|
|
45
|
+
var timezone = (0, exports.resolveScheduleTimezone)({
|
|
46
|
+
defaultTimezone: defaultTimezone,
|
|
47
|
+
scheduleTimezone: scheduleTimezone,
|
|
48
|
+
});
|
|
49
|
+
(0, exports.assertValidIanaTimezone)(timezone);
|
|
50
|
+
(0, exports.assertValidCronExpression)(cron, timezone);
|
|
51
|
+
return timezone;
|
|
52
|
+
};
|
|
53
|
+
exports.validateScheduleDefinition = validateScheduleDefinition;
|
|
54
|
+
var computeInitialNextRunAt = function (cron, timezone) {
|
|
55
|
+
return (0, exports.computeNextRunAtAfter)(cron, timezone, luxon_1.DateTime.utc().toJSDate());
|
|
56
|
+
};
|
|
57
|
+
exports.computeInitialNextRunAt = computeInitialNextRunAt;
|
|
58
|
+
var computeNextRunAtAfter = function (cron, timezone, after) {
|
|
59
|
+
return new cron_1.CronTime(cron, timezone).getNextDateFrom(after, timezone).toJSDate();
|
|
60
|
+
};
|
|
61
|
+
exports.computeNextRunAtAfter = computeNextRunAtAfter;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { JobsService } from "./jobsService";
|
|
2
|
+
/** @internal */
|
|
3
|
+
export declare const buildScheduleRunIdempotencyKey: (scheduleId: string, dueRunAt: Date) => string;
|
|
4
|
+
export declare const tickDueSchedules: ({ jobsService, now, }: {
|
|
5
|
+
defaultTimezone?: string;
|
|
6
|
+
jobsService: JobsService;
|
|
7
|
+
now?: Date;
|
|
8
|
+
}) => Promise<void>;
|