@happyvertical/smrt-jobs 0.55.21 → 0.55.24

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/index.d.ts CHANGED
@@ -43,7 +43,7 @@ export { type CreateMcpTaskInput, createMcpTaskResult, getMcpTaskMarker, MCP_TAS
43
43
  export { type BackgroundCapable, type BgOptions, withBackgroundJobs, } from './object-extension.js';
44
44
  export { DEFAULT_JOB_RETENTION, DEFAULT_RETENTION_SWEEP_INTERVAL_MS, JOB_EVENTS_RETENTION_TASK, JOBS_RETENTION_TASK, type JobRetentionOptions, type RetentionSweeper, type RetentionSweeperOptions, registerJobRetentionTasks, startRetentionSweeper, unregisterJobRetentionTasks, } from './retention.js';
45
45
  export { createTaskRunner, getActiveJobExecutionContext, isRunnerExecutionContext, JobTimeoutError, TaskRunner, type TaskRunnerConfig, type TaskRunnerEvents, } from './runner.js';
46
- export { createScheduleRunner, getNextCronDate, type ScheduleInfo, ScheduleRunner, type ScheduleRunnerConfig, type ScheduleRunnerEvents, validateCronExpression, } from './schedule-runner.js';
46
+ export { createScheduleRunner, getNextCronDate, SCHEDULED_JOB_QUEUE, type ScheduleInfo, ScheduleRunner, type ScheduleRunnerConfig, type ScheduleRunnerEvents, validateCronExpression, } from './schedule-runner.js';
47
47
  export { type ClaimReadyOptions, type JobStatus, type ListReadyOptions, SmrtJob, SmrtJobCollection, type SmrtJobData, type TimeoutBehavior, } from './smrt-job.js';
48
48
  export { type JobEventCursor, type ListJobEventsOptions, type ListTerminalOutcomesOptions, SmrtJobEvent, SmrtJobEventCollection, type SmrtJobEventData, type SmrtJobEventLevel, type SmrtJobEventType, type SmrtJobTerminalOutcome, type SmrtJobTerminalOutcomePage, type SmrtJobTerminalStatus, } from './smrt-job-event.js';
49
49
  export { type RegisterWorkerInput, SmrtWorker, SmrtWorkerCollection, } from './smrt-worker.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { A as SmrtJobCollection, B as isBackgroundEligibleMethod, C as DEFAULT_RETENTION_SWEEP_INTERVAL_MS, D as startRetentionSweeper, E as registerJobRetentionTasks, F as TenantJobCapExceededError, I as assertWithinTenantCreationCap, L as backgroundEligible, M as SmrtJobEventCollection, N as DEFAULT_TENANT_JOB_CAP, O as unregisterJobRetentionTasks, P as MAX_JOB_RETRIES, R as clampRetries, S as DEFAULT_JOB_RETENTION, T as JOB_EVENTS_RETENTION_TASK, V as markBackgroundEligible, _ as requestMcpTaskInput, a as isRunnerExecutionContext, b as redactErrorForPersistence, c as SmrtWorkerCollection, d as McpTaskNotFoundError, f as McpTaskStore, g as requestMcpTaskContinuation, h as getMcpTaskMarker, i as getActiveJobExecutionContext, j as SmrtJobEvent, k as SmrtJob, l as MCP_TASKS_EXTENSION, m as createMcpTaskResult, n as TaskRunner, o as DEFAULT_TASK_HEARTBEAT_INTERVAL_MS, p as McpTaskSuspendedError, r as createTaskRunner, s as SmrtWorker, t as JobTimeoutError, u as McpTaskCancelledError, v as taskFromJob, w as JOBS_RETENTION_TASK, x as redactErrorMessage, y as JobContextLogger, z as getBackgroundEligibleMethods } from "./chunks/runner-B8_TYooy.js";
1
+ import { A as SmrtJobCollection, B as isBackgroundEligibleMethod, C as DEFAULT_RETENTION_SWEEP_INTERVAL_MS, D as startRetentionSweeper, E as registerJobRetentionTasks, F as TenantJobCapExceededError, I as assertWithinTenantCreationCap, L as backgroundEligible, M as SmrtJobEventCollection, N as DEFAULT_TENANT_JOB_CAP, O as unregisterJobRetentionTasks, P as MAX_JOB_RETRIES, R as clampRetries, S as DEFAULT_JOB_RETENTION, T as JOB_EVENTS_RETENTION_TASK, V as markBackgroundEligible, _ as requestMcpTaskInput, a as isRunnerExecutionContext, b as redactErrorForPersistence, c as SmrtWorkerCollection, d as McpTaskNotFoundError, f as McpTaskStore, g as requestMcpTaskContinuation, h as getMcpTaskMarker, i as getActiveJobExecutionContext, j as SmrtJobEvent, k as SmrtJob, l as MCP_TASKS_EXTENSION, m as createMcpTaskResult, n as TaskRunner, o as DEFAULT_TASK_HEARTBEAT_INTERVAL_MS, p as McpTaskSuspendedError, r as createTaskRunner, s as SmrtWorker, t as JobTimeoutError, u as McpTaskCancelledError, v as taskFromJob, w as JOBS_RETENTION_TASK, x as redactErrorMessage, y as JobContextLogger, z as getBackgroundEligibleMethods } from "./chunks/runner-CbKjN2tD.js";
2
2
  import { c as unregisterLiveWorker, i as registerLiveWorker, n as isWorkerAlive, t as createWorkerKey } from "./chunks/worker-liveness-C1Gjnhax.js";
3
3
  import { ObjectRegistry, SmrtCollection, SmrtObject, detectEngine, field, smrt } from "@happyvertical/smrt-core";
4
4
  import { createLogger } from "@happyvertical/logger";
@@ -875,6 +875,7 @@ function withBackgroundJobs(BaseClass) {
875
875
  }
876
876
  //#endregion
877
877
  //#region src/schedule-runner.ts
878
+ var SCHEDULED_JOB_QUEUE = "agents";
878
879
  var DEFAULT_CONFIG = {
879
880
  id: "",
880
881
  pollInterval: 6e4,
@@ -1114,7 +1115,22 @@ var ScheduleRunner = class extends EventEmitter {
1114
1115
  if (schedule.method_args) methodArgs = typeof schedule.method_args === "string" ? JSON.parse(schedule.method_args) : schedule.method_args;
1115
1116
  let agentConfig = {};
1116
1117
  if (schedule.agent_config) agentConfig = typeof schedule.agent_config === "string" ? JSON.parse(schedule.agent_config) : schedule.agent_config;
1117
- const nextRun = getNextCronDate(schedule.cron);
1118
+ const timezone = typeof schedule.timezone === "string" && schedule.timezone.trim() ? schedule.timezone.trim() : "UTC";
1119
+ try {
1120
+ validateCronExpression(schedule.cron);
1121
+ validateCronTimezone(timezone);
1122
+ } catch (error) {
1123
+ await this.db.query(`UPDATE _smrt_agent_schedules
1124
+ SET status = 'error', last_error = ?
1125
+ WHERE id = ?`, redactErrorForPersistence(error), schedule.id);
1126
+ this.emit("schedule:error", scheduleInfo, error);
1127
+ this.logger.error("Schedule has an invalid cron or time zone", {
1128
+ scheduleId: schedule.id,
1129
+ error
1130
+ });
1131
+ return;
1132
+ }
1133
+ const nextRun = getNextCronDate(schedule.cron, timezone);
1118
1134
  const args = {
1119
1135
  ...methodArgs,
1120
1136
  _scheduleId: schedule.id
@@ -1122,7 +1138,7 @@ var ScheduleRunner = class extends EventEmitter {
1122
1138
  if (Object.keys(agentConfig).length > 0) args._agentConfig = agentConfig;
1123
1139
  const job = await this.jobCollection.enqueueJob({
1124
1140
  tenantId: typeof schedule.tenant_id === "string" && schedule.tenant_id.length > 0 ? schedule.tenant_id : null,
1125
- queue: "agents",
1141
+ queue: SCHEDULED_JOB_QUEUE,
1126
1142
  objectType: canonicalAgentType,
1127
1143
  objectId: schedule.agent_id,
1128
1144
  method: schedule.method || "run",
@@ -1223,48 +1239,133 @@ function validateCronExpression(cron) {
1223
1239
  });
1224
1240
  return parts;
1225
1241
  }
1226
- function getNextCronDate(cron) {
1242
+ var MINUTE_MS = 6e4;
1243
+ var DAY_MS = 24 * (60 * MINUTE_MS);
1244
+ var CRON_SEARCH_HORIZON_MS = 525600 * MINUTE_MS;
1245
+ var zoneFormatters = /* @__PURE__ */ new Map();
1246
+ function resolveZoneOffset(timezone) {
1247
+ if (timezone === void 0 || timezone === null) return (epochMs) => -new Date(epochMs).getTimezoneOffset() * MINUTE_MS;
1248
+ let formatter = zoneFormatters.get(timezone);
1249
+ if (!formatter) {
1250
+ try {
1251
+ formatter = new Intl.DateTimeFormat("en-US", {
1252
+ timeZone: timezone,
1253
+ hourCycle: "h23",
1254
+ year: "numeric",
1255
+ month: "numeric",
1256
+ day: "numeric",
1257
+ hour: "numeric",
1258
+ minute: "numeric",
1259
+ second: "numeric"
1260
+ });
1261
+ } catch {
1262
+ throw new Error(`Invalid cron timezone: "${timezone}" is not a recognized IANA time zone`);
1263
+ }
1264
+ zoneFormatters.set(timezone, formatter);
1265
+ }
1266
+ const zoned = formatter;
1267
+ return (epochMs) => {
1268
+ const fields = {};
1269
+ for (const part of zoned.formatToParts(epochMs)) if (part.type !== "literal") fields[part.type] = Number(part.value);
1270
+ return Date.UTC(fields.year, fields.month - 1, fields.day, fields.hour % 24, fields.minute, fields.second) - (epochMs - (epochMs % 1e3 + 1e3) % 1e3);
1271
+ };
1272
+ }
1273
+ function wallTimeInstants(wallMs, offsetAt) {
1274
+ const offsetBefore = offsetAt(wallMs - DAY_MS);
1275
+ const offsetAfter = offsetAt(wallMs + DAY_MS);
1276
+ const instants = [];
1277
+ for (const offset of /* @__PURE__ */ new Set([offsetBefore, offsetAfter])) {
1278
+ const instant = wallMs - offset;
1279
+ if (offsetAt(instant) === offset) instants.push(instant);
1280
+ }
1281
+ instants.sort((a, b) => a - b);
1282
+ return {
1283
+ instants,
1284
+ offsetBefore,
1285
+ offsetAfter
1286
+ };
1287
+ }
1288
+ function validateCronTimezone(timezone) {
1289
+ resolveZoneOffset(timezone);
1290
+ }
1291
+ function getNextCronDate(cron, timezone) {
1227
1292
  const [minuteExpr, hourExpr, dayExpr, monthExpr, dowExpr] = validateCronExpression(cron);
1228
- const candidate = /* @__PURE__ */ new Date(/* @__PURE__ */ new Date());
1229
- candidate.setSeconds(0);
1230
- candidate.setMilliseconds(0);
1231
- candidate.setMinutes(candidate.getMinutes() + 1);
1293
+ const offsetAt = resolveZoneOffset(timezone);
1232
1294
  const dayIsWildcard = dayExpr === "*";
1233
1295
  const dowIsWildcard = dowExpr === "*";
1234
- const maxIterations = 525600;
1235
- for (let i = 0; i < maxIterations; i++) {
1236
- const dayMatches = matchesCronField(candidate.getDate(), dayExpr);
1237
- const dow = candidate.getDay();
1238
- const dowMatches = matchesCronField(dow, dowExpr) || dow === 0 && matchesCronField(7, dowExpr);
1239
- let dayOfMonthOrWeekMatches;
1240
- if (!dayIsWildcard && !dowIsWildcard) dayOfMonthOrWeekMatches = dayMatches || dowMatches;
1241
- else if (!dayIsWildcard) dayOfMonthOrWeekMatches = dayMatches;
1242
- else if (!dowIsWildcard) dayOfMonthOrWeekMatches = dowMatches;
1243
- else dayOfMonthOrWeekMatches = true;
1244
- if (matchesCronField(candidate.getMonth() + 1, monthExpr) && dayOfMonthOrWeekMatches && matchesCronField(candidate.getHours(), hourExpr) && matchesCronField(candidate.getMinutes(), minuteExpr)) return candidate;
1245
- candidate.setMinutes(candidate.getMinutes() + 1);
1296
+ const fixedTime = !minuteExpr.includes("*") && !hourExpr.includes("*");
1297
+ const dayMatches = (wall) => {
1298
+ const domMatches = matchesCronField(wall.getUTCDate(), dayExpr, CRON_FIELD_RANGES[2]);
1299
+ const dow = wall.getUTCDay();
1300
+ const dowMatches = matchesCronField(dow, dowExpr, CRON_FIELD_RANGES[4]) || dow === 0 && matchesCronField(7, dowExpr, CRON_FIELD_RANGES[4]);
1301
+ if (!dayIsWildcard && !dowIsWildcard) return domMatches || dowMatches;
1302
+ if (!dayIsWildcard) return domMatches;
1303
+ if (!dowIsWildcard) return dowMatches;
1304
+ return true;
1305
+ };
1306
+ const now = Date.now();
1307
+ const threshold = now - (now % MINUTE_MS + MINUTE_MS) % MINUTE_MS + MINUTE_MS;
1308
+ const thresholdOffset = offsetAt(threshold);
1309
+ const thresholdSpread = Math.abs(offsetAt(threshold - DAY_MS) - offsetAt(threshold + DAY_MS));
1310
+ let wallMs = threshold + thresholdOffset - thresholdSpread;
1311
+ const wallEnd = threshold + thresholdOffset + CRON_SEARCH_HORIZON_MS;
1312
+ let best = null;
1313
+ let stopAfterWall = Number.POSITIVE_INFINITY;
1314
+ while (wallMs <= wallEnd && wallMs <= stopAfterWall) {
1315
+ const wall = new Date(wallMs);
1316
+ const year = wall.getUTCFullYear();
1317
+ const month = wall.getUTCMonth();
1318
+ const day = wall.getUTCDate();
1319
+ const hour = wall.getUTCHours();
1320
+ if (!matchesCronField(month + 1, monthExpr, CRON_FIELD_RANGES[3])) {
1321
+ wallMs = Date.UTC(year, month + 1, 1);
1322
+ continue;
1323
+ }
1324
+ if (!dayMatches(wall)) {
1325
+ wallMs = Date.UTC(year, month, day + 1);
1326
+ continue;
1327
+ }
1328
+ if (!matchesCronField(hour, hourExpr, CRON_FIELD_RANGES[1])) {
1329
+ wallMs = Date.UTC(year, month, day, hour + 1);
1330
+ continue;
1331
+ }
1332
+ if (!matchesCronField(wall.getUTCMinutes(), minuteExpr, CRON_FIELD_RANGES[0])) {
1333
+ wallMs += MINUTE_MS;
1334
+ continue;
1335
+ }
1336
+ const { instants, offsetBefore, offsetAfter } = wallTimeInstants(wallMs, offsetAt);
1337
+ let candidates = instants;
1338
+ if (instants.length === 0) candidates = fixedTime ? [wallMs - offsetBefore] : [];
1339
+ else if (instants.length > 1 && fixedTime) candidates = [instants[0]];
1340
+ for (const instant of candidates) if (instant >= threshold && (best === null || instant < best)) {
1341
+ if (best === null) stopAfterWall = wallMs + Math.abs(offsetBefore - offsetAfter);
1342
+ best = instant;
1343
+ }
1344
+ wallMs += MINUTE_MS;
1246
1345
  }
1247
- throw new Error(`Could not find next run date for cron: ${cron}`);
1346
+ if (best === null) throw new Error(`Could not find next run date for cron: ${cron}`);
1347
+ return new Date(best);
1248
1348
  }
1249
- function matchesCronField(value, expr) {
1349
+ function matchesCronField(value, expr, range) {
1250
1350
  if (expr === "*") return true;
1251
- if (expr.includes("/")) {
1252
- const [range, stepStr] = expr.split("/");
1253
- const step = parseInt(stepStr, 10);
1254
- if (range === "*") return value % step === 0;
1255
- if (range.includes("-")) {
1256
- const [startStr, endStr] = range.split("-");
1257
- const start = parseInt(startStr, 10);
1258
- if (value < start || value > parseInt(endStr, 10)) return false;
1259
- return (value - start) % step === 0;
1351
+ return expr.split(",").some((term) => {
1352
+ const [body, stepStr] = term.split("/");
1353
+ const step = stepStr === void 0 ? 1 : parseInt(stepStr, 10);
1354
+ let start;
1355
+ let end;
1356
+ if (body === "*") {
1357
+ start = range.min;
1358
+ end = range.max;
1359
+ } else if (body.includes("-")) {
1360
+ const [startStr, endStr] = body.split("-");
1361
+ start = parseInt(startStr, 10);
1362
+ end = parseInt(endStr, 10);
1363
+ } else {
1364
+ start = parseInt(body, 10);
1365
+ end = stepStr === void 0 ? start : range.max;
1260
1366
  }
1261
- }
1262
- if (expr.includes("-")) {
1263
- const [startStr, endStr] = expr.split("-");
1264
- return value >= parseInt(startStr, 10) && value <= parseInt(endStr, 10);
1265
- }
1266
- if (expr.includes(",")) return expr.split(",").map((v) => parseInt(v.trim(), 10)).includes(value);
1267
- return value === parseInt(expr, 10);
1367
+ return value >= start && value <= end && (value - start) % step === 0;
1368
+ });
1268
1369
  }
1269
1370
  function createScheduleRunner(config) {
1270
1371
  return new ScheduleRunner(config);
@@ -1273,6 +1374,6 @@ function createScheduleRunner(config) {
1273
1374
  //#region src/index.ts
1274
1375
  registerJobRetentionTasks();
1275
1376
  //#endregion
1276
- export { DEFAULT_JOB_RETENTION, DEFAULT_RETENTION_SWEEP_INTERVAL_MS, DEFAULT_TENANT_JOB_CAP, ForgeDelivery, ForgeDeliveryCollection, ForgeProjectionCheckpoint, ForgeProjectionRuntime, JOBS_RETENTION_TASK, JOB_EVENTS_RETENTION_TASK, JobBuilder, JobContextLogger, JobHandle, JobTimeoutError, MAX_JOB_RETRIES, MCP_TASKS_EXTENSION, McpTaskCancelledError, McpTaskNotFoundError, McpTaskStore, McpTaskSuspendedError, ScheduleRunner, SmrtJob, SmrtJobCollection, SmrtJobEvent, SmrtJobEventCollection, SmrtWorker, SmrtWorkerCollection, TaskRunner, TenantJobCapExceededError, assertWithinTenantCreationCap, backgroundEligible, clampRetries, createHmacDurableJobPayloadSigner, createMcpTaskResult, createScheduleRunner, createTaskRunner, createWorkerKey, getActiveJobExecutionContext, getBackgroundEligibleMethods, getMcpTaskMarker, getNextCronDate, isBackgroundEligibleMethod, isRunnerExecutionContext, isWorkerAlive, markBackgroundEligible, parseDelay, priorityToNumber, redactErrorForPersistence, redactErrorMessage, registerJobRetentionTasks, registerLiveWorker, requestMcpTaskContinuation, requestMcpTaskInput, startRetentionSweeper, taskFromJob, unregisterJobRetentionTasks, unregisterLiveWorker, validateCronExpression, withBackgroundJobs };
1377
+ export { DEFAULT_JOB_RETENTION, DEFAULT_RETENTION_SWEEP_INTERVAL_MS, DEFAULT_TENANT_JOB_CAP, ForgeDelivery, ForgeDeliveryCollection, ForgeProjectionCheckpoint, ForgeProjectionRuntime, JOBS_RETENTION_TASK, JOB_EVENTS_RETENTION_TASK, JobBuilder, JobContextLogger, JobHandle, JobTimeoutError, MAX_JOB_RETRIES, MCP_TASKS_EXTENSION, McpTaskCancelledError, McpTaskNotFoundError, McpTaskStore, McpTaskSuspendedError, SCHEDULED_JOB_QUEUE, ScheduleRunner, SmrtJob, SmrtJobCollection, SmrtJobEvent, SmrtJobEventCollection, SmrtWorker, SmrtWorkerCollection, TaskRunner, TenantJobCapExceededError, assertWithinTenantCreationCap, backgroundEligible, clampRetries, createHmacDurableJobPayloadSigner, createMcpTaskResult, createScheduleRunner, createTaskRunner, createWorkerKey, getActiveJobExecutionContext, getBackgroundEligibleMethods, getMcpTaskMarker, getNextCronDate, isBackgroundEligibleMethod, isRunnerExecutionContext, isWorkerAlive, markBackgroundEligible, parseDelay, priorityToNumber, redactErrorForPersistence, redactErrorMessage, registerJobRetentionTasks, registerLiveWorker, requestMcpTaskContinuation, requestMcpTaskInput, startRetentionSweeper, taskFromJob, unregisterJobRetentionTasks, unregisterLiveWorker, validateCronExpression, withBackgroundJobs };
1277
1378
 
1278
1379
  //# sourceMappingURL=index.js.map