agents
Version:
A home for your AI agents
658 lines (657 loc) • 28.8 kB
JavaScript
import { i as _classPrivateFieldInitSpec, n as _classPrivateFieldSet2, r as _assertClassBrand, t as _classPrivateFieldGet2 } from "./classPrivateFieldGet2-DZBYAB34.js";
import { t as LifecycleCapability } from "./capability-B4WbF81e.js";
import { t as _classPrivateMethodInitSpec } from "./classPrivateMethodInitSpec-qMjJ6sHQ.js";
import { isDurableObjectCodeUpdateReset, isPlatformFailure, resolveRetryConfig, tryN, validateRetryOptions } from "./retries.js";
import { parseCronExpression } from "cron-schedule";
//#region src/schedules/schedule-timing.ts
/**
* Pure timing rules for persistent schedules.
*
* A `ScheduleTiming` is the parsed "when and how it runs" half of a
* `Schedule` — everything except identity, callback, and payload. Parsing
* user-facing inputs here keeps Scheduler's storage code to one insert path.
*/
/** Longest allowed gap between interval executions: 30 days. */
const MAX_INTERVAL_SECONDS = 720 * 60 * 60;
/** True when this timing repeats and therefore deduplicates by default. */
function isRecurring(timing) {
return timing.type === "cron" || timing.type === "interval";
}
/**
* Next wall-clock execution time for a cron expression.
*
* @param cron - A standard cron expression.
* @param nowMs - Current epoch time in milliseconds.
* @returns The next execution epoch time in milliseconds.
* @throws For an unparseable cron expression.
*/
function nextCronTimeMs(cron, nowMs) {
return parseCronExpression(cron).getNextDate(new Date(nowMs)).getTime();
}
/**
* Parse a user-facing `when` into schedule timing.
*
* A `Date` runs once at that date, a number runs once after that many
* seconds, and a string is a recurring cron expression.
*
* @param when - The requested execution time or recurrence.
* @param nowMs - Current epoch time in milliseconds.
* @param callback - Callback name, used only in error messages.
* @returns The parsed timing.
* @throws For a `when` value that is not a `Date`, number, or string.
*/
function parseWhen(when, nowMs, callback) {
if (when instanceof Date) return {
type: "scheduled",
time: Math.floor(when.getTime() / 1e3)
};
if (typeof when === "number") return {
type: "delayed",
time: Math.floor((nowMs + when * 1e3) / 1e3),
delayInSeconds: when
};
if (typeof when === "string") return {
type: "cron",
time: Math.floor(nextCronTimeMs(when, nowMs) / 1e3),
cron: when
};
throw new Error(`Invalid schedule type: ${JSON.stringify(when)}(${typeof when}) trying to schedule ${callback}`);
}
/**
* Reject an interval outside the supported range.
*
* @param intervalSeconds - The requested gap between executions.
* @throws For a non-positive interval or one longer than 30 days.
*/
function validateIntervalSeconds(intervalSeconds) {
if (typeof intervalSeconds !== "number" || intervalSeconds <= 0) throw new Error("intervalSeconds must be a positive number");
if (intervalSeconds > 2592e3) throw new Error(`intervalSeconds cannot exceed ${MAX_INTERVAL_SECONDS} seconds (30 days)`);
}
/**
* Parse a fixed interval into schedule timing.
*
* @param intervalSeconds - Seconds between executions.
* @param nowMs - Current epoch time in milliseconds.
* @returns The parsed timing with the first execution one interval from now.
* @throws For an interval outside the supported range.
*/
function parseInterval(intervalSeconds, nowMs) {
validateIntervalSeconds(intervalSeconds);
return {
type: "interval",
time: Math.floor((nowMs + intervalSeconds * 1e3) / 1e3),
intervalSeconds
};
}
//#endregion
//#region src/schedules/scheduler.ts
/**
* Lifecycle scheduling vocabulary. Scheduler validates schedules, resolves
* named callbacks, and pushes jobs into the Lifecycle-owned job queue;
* Lifecycle runs the alarm event loop, retry policy, and physical alarm
* arming. Scheduler owns no storage of its own — each schedule is one job
* whose `fn` is the callback name and whose payload carries the schedule's
* timing vocabulary.
*/
const schedulerCallbackResolvers = /* @__PURE__ */ new WeakMap();
/**
* @internal Supply a composition-root fallback for callback names outside the
* registered map. Agent uses this to keep its historical name-based
* scheduling API (`this.schedule(60, "methodName")`) working: the resolver
* looks the method up on the Agent, and the resolved handler still runs
* inside the Lifecycle host boundary.
*/
function setSchedulerCallbackResolver(scheduler, resolver) {
schedulerCallbackResolvers.set(scheduler, resolver);
}
const SCHEDULE_SCHEMA_VERSION_KEY = "cf_agents:schedules_schema_version";
/** Version 2: schedule rows live in the Lifecycle job queue. */
const CURRENT_SCHEDULE_SCHEMA_VERSION = 2;
/**
* Legacy schedule callbacks whose rows drive chat recovery loops from before
* the job queue carried breaker membership. The migration is the one place
* allowed to know legacy names (it already drops `_cf_keepAliveHeartbeat`
* rows by name): a recovery row migrated without its `recoveryLoop` flag
* would escape the alarm memory-limit breaker (#1825) and could re-trigger
* a doomed loop on an upgraded object.
*/
const LEGACY_RECOVERY_LOOP_CALLBACKS = /* @__PURE__ */ new Set(["_chatRecoveryContinue", "_chatRecoveryRetry"]);
const DEFAULT_RETRY = {
maxAttempts: 3,
baseDelayMs: 100,
maxDelayMs: 3e3
};
function isSchedulerJobPayload(value) {
return typeof value === "object" && value !== null && typeof value.type === "string";
}
var _handlers = /* @__PURE__ */ new WeakMap();
var _retryDefaults = /* @__PURE__ */ new WeakMap();
var _hungScheduleTimeoutSeconds = /* @__PURE__ */ new WeakMap();
var _onError = /* @__PURE__ */ new WeakMap();
var _warnedStartupCallbacks = /* @__PURE__ */ new WeakMap();
var _Scheduler_brand = /* @__PURE__ */ new WeakSet();
/**
* Persistent task scheduling for a Lifecycle Object.
*
* Register callbacks in the constructor and install the instance with
* `Lifecycle.use()`. Scheduler validates schedules and pushes them into the
* Lifecycle job queue; Lifecycle owns the physical alarm and the alarm
* event loop, and Scheduler runs registered callbacks through Lifecycle's
* host invocation boundary when its jobs come due.
*
* @experimental The API surface may change before stabilizing.
*/
var Scheduler = class extends LifecycleCapability {
/**
* Create a persistent Scheduler.
*
* @param options - Registered callbacks plus optional retry,
* hung-interval, and error policy. Registering `callbacks` types
* {@link set} and {@link every} against the map — names and
* payloads are checked where the handlers are declared and where they are
* scheduled. Names outside the map are rejected unless a composition-root
* resolver supplies them — the internal aperture behind `Agent`'s
* name-based scheduling API.
*/
constructor(options = {}) {
super("scheduler");
_classPrivateMethodInitSpec(this, _Scheduler_brand);
_classPrivateFieldInitSpec(this, _handlers, void 0);
_classPrivateFieldInitSpec(this, _retryDefaults, void 0);
_classPrivateFieldInitSpec(this, _hungScheduleTimeoutSeconds, void 0);
_classPrivateFieldInitSpec(this, _onError, void 0);
_classPrivateFieldInitSpec(this, _warnedStartupCallbacks, /* @__PURE__ */ new Set());
_classPrivateFieldSet2(_handlers, this, options.callbacks ?? {});
_classPrivateFieldSet2(_retryDefaults, this, resolveRetryConfig(options.retry, DEFAULT_RETRY));
_classPrivateFieldSet2(_hungScheduleTimeoutSeconds, this, options.hungScheduleTimeoutSeconds ?? 30);
_classPrivateFieldSet2(_onError, this, options.onError);
}
/** Migrate legacy schedule storage into the Lifecycle job queue. */
async onStart() {
_classPrivateFieldGet2(_warnedStartupCallbacks, this).clear();
const storage = this.lifecycle.storage;
if ((await storage.get(SCHEDULE_SCHEMA_VERSION_KEY) ?? 0) >= CURRENT_SCHEDULE_SCHEMA_VERSION) return;
await _assertClassBrand(_Scheduler_brand, this, _migrateLegacyScheduleTable).call(this, storage);
await storage.put(SCHEDULE_SCHEMA_VERSION_KEY, CURRENT_SCHEDULE_SCHEMA_VERSION);
}
/** Drive one due schedule job dispatched by the Lifecycle event loop. */
async onJob(context) {
const { job, attempt } = context;
const timing = job.payload;
if (!isSchedulerJobPayload(timing)) {
console.error(`Malformed schedule job ${job.id}; dropping it`);
return;
}
if (attempt === 1) _assertClassBrand(_Scheduler_brand, this, _emit).call(this, "schedule:execute", {
callback: job.fn,
id: job.id
});
else _assertClassBrand(_Scheduler_brand, this, _emit).call(this, "schedule:retry", {
callback: job.fn,
id: job.id,
attempt,
maxAttempts: resolveRetryConfig(job.retry, _classPrivateFieldGet2(_retryDefaults, this)).maxAttempts
});
if (timing.owner_path) {
try {
await this.lifecycle.routes.to({
key: timing.owner_path_key ?? timing.owner_path,
data: timing.owner_path
}, {
type: "dispatch",
id: job.id,
fn: job.fn,
job: timing,
retry: job.retry
});
} catch (error) {
if (isPlatformFailure(error)) throw error;
console.error(`error dispatching scheduled callback "${job.fn}"`, error);
_assertClassBrand(_Scheduler_brand, this, _emit).call(this, "schedule:error", {
callback: job.fn,
id: job.id,
error: error instanceof Error ? error.message : String(error),
attempts: 0
});
try {
await _classPrivateFieldGet2(_onError, this)?.call(this, error);
} catch {}
return "yield";
}
return _assertClassBrand(_Scheduler_brand, this, _recurrenceOutcome).call(this, timing);
}
const handler = _assertClassBrand(_Scheduler_brand, this, _resolveCallback).call(this, job.fn);
if (!handler) {
console.error(`callback ${job.fn} not found`);
return _assertClassBrand(_Scheduler_brand, this, _recurrenceOutcome).call(this, timing);
}
const schedule = _assertClassBrand(_Scheduler_brand, this, _jobToSchedule).call(this, job, timing);
await this.lifecycle.runInHostContext(() => handler(timing.payload, schedule));
return _assertClassBrand(_Scheduler_brand, this, _recurrenceOutcome).call(this, timing);
}
/** Observe one schedule's terminal application failure. */
async onJobError(context, error) {
const { job } = context;
const timing = job.payload;
if (!isSchedulerJobPayload(timing)) return void 0;
const { maxAttempts } = resolveRetryConfig(job.retry, _classPrivateFieldGet2(_retryDefaults, this));
console.error(`error executing callback "${job.fn}" after ${maxAttempts} attempts`, error);
_assertClassBrand(_Scheduler_brand, this, _emit).call(this, "schedule:error", {
callback: job.fn,
id: job.id,
error: error instanceof Error ? error.message : String(error),
attempts: maxAttempts
});
try {
await _classPrivateFieldGet2(_onError, this)?.call(this, error);
} catch {}
return _assertClassBrand(_Scheduler_brand, this, _recurrenceOutcome).call(this, timing);
}
/** Handle Scheduler protocol messages routed by another Lifecycle. */
async onRoute(context) {
const message = context.payload;
const owner = context.source ?? null;
switch (message.type) {
case "schedule": return _assertClassBrand(_Scheduler_brand, this, _insert).call(this, owner, parseWhen(message.when, Date.now(), message.callback), message.callback, message.payload, message.options);
case "every": return _assertClassBrand(_Scheduler_brand, this, _insert).call(this, owner, parseInterval(message.intervalSeconds, Date.now()), message.callback, message.payload, message.options);
case "get": return _assertClassBrand(_Scheduler_brand, this, _getForOwner).call(this, owner, message.id);
case "list": return _assertClassBrand(_Scheduler_brand, this, _listForOwner).call(this, owner, message.criteria);
case "cancel": return _assertClassBrand(_Scheduler_brand, this, _cancelForOwner).call(this, owner, message.id);
case "dispatch":
await _assertClassBrand(_Scheduler_brand, this, _executeRouted).call(this, message.id, message.fn, message.job, message.retry);
return true;
default: throw new Error("Unknown routed Scheduler message");
}
}
/** Set a delayed, dated, or cron schedule for a registered callback. */
async set(when, callback, payload, options) {
await this.lifecycle.ready();
_assertClassBrand(_Scheduler_brand, this, _validateSchedule).call(this, when, callback, options);
const result = this.lifecycle.routes.source ? await this.lifecycle.routes.toRoot({
type: "schedule",
when,
callback,
payload,
options
}) : await _assertClassBrand(_Scheduler_brand, this, _insert).call(this, null, parseWhen(when, Date.now(), callback), callback, payload, options);
_assertClassBrand(_Scheduler_brand, this, _emitCreated).call(this, result);
return result.schedule;
}
/** Set a fixed-interval schedule for a registered callback. */
async every(intervalSeconds, callback, payload, options) {
await this.lifecycle.ready();
_assertClassBrand(_Scheduler_brand, this, _validateInterval).call(this, intervalSeconds, callback, options?.retry);
const result = this.lifecycle.routes.source ? await this.lifecycle.routes.toRoot({
type: "every",
intervalSeconds,
callback,
payload,
options
}) : await _assertClassBrand(_Scheduler_brand, this, _insert).call(this, null, parseInterval(intervalSeconds, Date.now()), callback, payload, options);
_assertClassBrand(_Scheduler_brand, this, _emitCreated).call(this, result);
return result.schedule;
}
/** Get a schedule by ID. Works inside routed sub-agents. */
async get(id) {
await this.lifecycle.ready();
return this.lifecycle.routes.source ? await this.lifecycle.routes.toRoot({
type: "get",
id
}) : _assertClassBrand(_Scheduler_brand, this, _getForOwner).call(this, null, id);
}
/** List schedules matching criteria. Works inside routed sub-agents. */
async list(criteria = {}) {
await this.lifecycle.ready();
return this.lifecycle.routes.source ? await this.lifecycle.routes.toRoot({
type: "list",
criteria
}) : _assertClassBrand(_Scheduler_brand, this, _listForOwner).call(this, null, criteria);
}
/**
* Cancel one schedule owned by this Scheduler.
*
* @param id - ID of the schedule to cancel.
* @returns True when a schedule was cancelled, false when none matched.
*/
async cancel(id) {
await this.lifecycle.ready();
const result = this.lifecycle.routes.source ? await this.lifecycle.routes.toRoot({
type: "cancel",
id
}) : await _assertClassBrand(_Scheduler_brand, this, _cancelForOwner).call(this, null, id);
if (result.ok && result.callback) _assertClassBrand(_Scheduler_brand, this, _emit).call(this, "schedule:cancel", {
callback: result.callback,
id
});
return result.ok;
}
/**
* @internal Synchronous read backing Agent's deprecated `getSchedule()`.
* Not part of the primitive's contract — use {@link get}. Cannot cross
* Durable Object boundaries and throws inside routed sub-agents.
*/
__DO_NOT_USE_WILL_REMOVE__getSchedule(id) {
if (this.lifecycle.routes.source) throw new Error("getSchedule() is synchronous and cannot read routed schedule storage. Use await getScheduleById(id) on Agent, or await scheduler.get(id) on a standalone Scheduler.");
return _assertClassBrand(_Scheduler_brand, this, _getForOwner).call(this, null, id);
}
/**
* @internal Synchronous read backing Agent's deprecated `getSchedules()`.
* Not part of the primitive's contract — use {@link list}. Cannot cross
* Durable Object boundaries and throws inside routed sub-agents.
*/
__DO_NOT_USE_WILL_REMOVE__getSchedules(criteria = {}) {
if (this.lifecycle.routes.source) throw new Error("getSchedules() is synchronous and cannot read routed schedule storage. Use await listSchedules(criteria) on Agent, or await scheduler.list(criteria) on a standalone Scheduler.");
return _assertClassBrand(_Scheduler_brand, this, _listForOwner).call(this, null, criteria);
}
/** @internal Remove schedules owned by one routed Lifecycle subtree. */
async __DO_NOT_USE_WILL_BREAK__cleanupRoutePrefix(prefix) {
for (const { job, timing } of _assertClassBrand(_Scheduler_brand, this, _ownedJobs).call(this)) {
const ownerKey = timing.owner_path_key ?? timing.owner_path;
if (!timing.owner_path || ownerKey === null || ownerKey === void 0) continue;
if (ownerKey !== prefix && !ownerKey.startsWith(`${prefix}/`)) continue;
_assertClassBrand(_Scheduler_brand, this, _emit).call(this, "schedule:cancel", {
callback: job.fn,
id: job.id
});
await this.lifecycle.jobs.cancel(job.id);
}
}
};
/**
* Move every `cf_agents_schedules` row into the job queue and drop the
* table. Idempotent: a missing table means a fresh object or a completed
* migration. Times convert from epoch seconds to epoch milliseconds.
*/
async function _migrateLegacyScheduleTable(storage) {
if (storage.sql.exec("SELECT name FROM sqlite_master WHERE type='table' AND name='cf_agents_schedules'").toArray().length === 0) return;
const rows = storage.sql.exec("SELECT * FROM cf_agents_schedules").toArray();
for (const row of rows) {
if (row.callback === "_cf_keepAliveHeartbeat") continue;
let payload;
try {
payload = typeof row.payload === "string" ? JSON.parse(row.payload) : void 0;
} catch (error) {
console.error(`Skipping schedule "${row.id}" during job-queue migration: its payload is not valid JSON`, error);
continue;
}
let retry;
try {
retry = typeof row.retry_options === "string" ? JSON.parse(row.retry_options) : void 0;
} catch {
retry = void 0;
}
await this.lifecycle.jobs.push({
id: row.id,
fn: row.callback,
time: row.time * 1e3,
payload: {
payload,
retry,
type: row.type,
delayInSeconds: row.delayInSeconds ?? void 0,
cron: row.cron ?? void 0,
intervalSeconds: row.intervalSeconds ?? void 0,
owner_path: row.owner_path ?? null,
owner_path_key: row.owner_path_key ?? null
},
retry: resolveRetryConfig(retry, _classPrivateFieldGet2(_retryDefaults, this)),
singleflight: row.type === "interval",
hungTimeoutSeconds: _classPrivateFieldGet2(_hungScheduleTimeoutSeconds, this),
recoveryLoop: LEGACY_RECOVERY_LOOP_CALLBACKS.has(row.callback)
});
}
storage.sql.exec("DROP TABLE cf_agents_schedules");
}
/** Advance a recurring schedule; complete a one-shot. */
function _recurrenceOutcome(timing) {
if (timing.type === "cron") return { rescheduleAt: nextCronTimeMs(timing.cron ?? "", Date.now()) };
if (timing.type === "interval") return { rescheduleAt: Date.now() + (timing.intervalSeconds ?? 0) * 1e3 };
}
/**
* Execute a routed schedule locally with the historical retry handling.
* Runs on the owning (facet) Scheduler, outside the root's event loop, so
* it applies its own in-process retry budget. Platform-class failures on a
* one-shot re-throw so the root preserves the job and the durable alarm
* retries on a fresh invocation.
*/
async function _executeRouted(id, fn, timing, retry) {
const handler = _assertClassBrand(_Scheduler_brand, this, _resolveCallback).call(this, fn);
if (!handler) {
console.error(`callback ${fn} not found`);
return;
}
const { maxAttempts, baseDelayMs, maxDelayMs } = resolveRetryConfig(retry, _classPrivateFieldGet2(_retryDefaults, this));
const isOneShot = timing.type === "delayed" || timing.type === "scheduled";
const schedule = _assertClassBrand(_Scheduler_brand, this, _payloadToSchedule).call(this, id, fn, timing);
try {
await tryN(maxAttempts, async (attempt) => {
if (attempt > 1) _assertClassBrand(_Scheduler_brand, this, _emit).call(this, "schedule:retry", {
callback: fn,
id,
attempt,
maxAttempts
});
await this.lifecycle.runInHostContext(() => handler(timing.payload, schedule));
}, {
baseDelayMs,
maxDelayMs,
shouldRetry: (error) => !(isOneShot && isDurableObjectCodeUpdateReset(error))
});
} catch (error) {
if (isOneShot && isPlatformFailure(error)) throw error;
console.error(`error executing callback "${fn}" after ${maxAttempts} attempts`, error);
_assertClassBrand(_Scheduler_brand, this, _emit).call(this, "schedule:error", {
callback: fn,
id,
error: error instanceof Error ? error.message : String(error),
attempts: maxAttempts
});
try {
await _classPrivateFieldGet2(_onError, this)?.call(this, error);
} catch {}
}
}
function _validateSchedule(when, callback, options) {
if (typeof callback !== "string") throw new Error("Callback must be a string");
if (!_assertClassBrand(_Scheduler_brand, this, _hasCallback).call(this, callback)) throw new Error(`Unknown scheduled callback "${callback}": not registered on this Scheduler`);
if (options?.retry) validateRetryOptions(options.retry, _classPrivateFieldGet2(_retryDefaults, this));
if (!(when instanceof Date) && typeof when !== "number" && typeof when !== "string") throw new Error(`Invalid schedule type: ${JSON.stringify(when)}(${typeof when}) trying to schedule ${callback}`);
_assertClassBrand(_Scheduler_brand, this, _warnWhenScheduledDuringStartup).call(this, when, callback, options);
}
function _validateInterval(intervalSeconds, callback, retry) {
validateIntervalSeconds(intervalSeconds);
if (typeof callback !== "string") throw new Error("Callback must be a string");
if (!_assertClassBrand(_Scheduler_brand, this, _hasCallback).call(this, callback)) throw new Error(`Unknown scheduled callback "${callback}": not registered on this Scheduler`);
if (retry) validateRetryOptions(retry, _classPrivateFieldGet2(_retryDefaults, this));
}
/**
* A non-idempotent one-shot created during startup accumulates one row per
* Durable Object wake, whether it came from the host's onStart or another
* startup hook. Warn once per callback; an explicit `idempotent` choice
* (either value) opts out.
*/
function _warnWhenScheduledDuringStartup(when, callback, options) {
if (this.lifecycle.status() !== "starting") return;
if (options?.idempotent !== void 0) return;
if (typeof when === "string") return;
if (_classPrivateFieldGet2(_warnedStartupCallbacks, this).has(callback)) return;
_classPrivateFieldGet2(_warnedStartupCallbacks, this).add(callback);
console.warn(`Scheduling "${callback}" during startup (e.g. onStart()) without { idempotent: true } creates a new row on every Durable Object restart, which can cause duplicate executions. Pass { idempotent: true } to deduplicate, or use an interval schedule for recurring tasks.`);
}
/** Resolve a name to its registered or composition-root-supplied handler. */
function _resolveCallback(name) {
const handler = _classPrivateFieldGet2(_handlers, this)[name];
if (handler) return handler;
return schedulerCallbackResolvers.get(this)?.(name);
}
/** True when a name resolves to a runnable callback. */
function _hasCallback(name) {
return _assertClassBrand(_Scheduler_brand, this, _resolveCallback).call(this, name) !== void 0;
}
/** Every schedule job this Scheduler owns, with its parsed vocabulary. */
function _ownedJobs() {
const owned = [];
for (const job of this.lifecycle.jobs.list()) if (isSchedulerJobPayload(job.payload)) owned.push({
job,
timing: job.payload
});
return owned;
}
/**
* Push a schedule job for the given owner, or return the existing job
* when an idempotent request matches one. One-shot timings deduplicate only
* when `idempotent: true` is passed; recurring timings deduplicate unless
* `idempotent: false` opts out. `created: false` marks a dedup hit so
* callers suppress the `schedule:create` event.
*/
async function _insert(owner, timing, callback, payload, options) {
const idempotent = isRecurring(timing) ? options?.idempotent !== false : Boolean(options?.idempotent);
const recoveryLoop = options?.recoveryLoop;
if (idempotent) {
const existing = _assertClassBrand(_Scheduler_brand, this, _findMatchingJob).call(this, owner?.key ?? null, timing, callback, JSON.stringify(payload));
if (existing) {
if (recoveryLoop && !existing.job.recoveryLoop) await this.lifecycle.jobs.push({
id: existing.job.id,
fn: existing.job.fn,
time: existing.job.time,
payload: existing.job.payload,
retry: existing.job.retry,
singleflight: existing.job.singleflight,
exclusive: existing.job.exclusive,
hungTimeoutSeconds: _classPrivateFieldGet2(_hungScheduleTimeoutSeconds, this),
recoveryLoop: true
});
await this.lifecycle.jobs.rearm();
return {
schedule: _assertClassBrand(_Scheduler_brand, this, _jobToSchedule).call(this, existing.job, existing.timing),
created: false
};
}
}
const jobPayload = {
payload,
retry: options?.retry,
type: timing.type,
delayInSeconds: timing.type === "delayed" ? timing.delayInSeconds : void 0,
cron: timing.type === "cron" ? timing.cron : void 0,
intervalSeconds: timing.type === "interval" ? timing.intervalSeconds : void 0,
owner_path: owner?.data ?? null,
owner_path_key: owner?.key ?? null
};
return {
schedule: {
id: (await this.lifecycle.jobs.push({
fn: callback,
time: timing.time * 1e3,
payload: jobPayload,
retry: resolveRetryConfig(options?.retry, _classPrivateFieldGet2(_retryDefaults, this)),
singleflight: timing.type === "interval",
hungTimeoutSeconds: _classPrivateFieldGet2(_hungScheduleTimeoutSeconds, this),
recoveryLoop
})).id,
callback,
payload,
retry: options?.retry,
...timing
},
created: true
};
}
/** Find the job an idempotent insert deduplicates onto, if any. */
function _findMatchingJob(ownerKey, timing, callback, payloadJson) {
for (const owned of _assertClassBrand(_Scheduler_brand, this, _ownedJobs).call(this)) {
const { job, timing: candidate } = owned;
if (candidate.type !== timing.type) continue;
if (job.fn !== callback) continue;
if ((candidate.owner_path_key ?? null) !== ownerKey) continue;
if (JSON.stringify(candidate.payload) !== payloadJson) continue;
if (timing.type === "cron" && candidate.cron !== timing.cron) continue;
if (timing.type === "interval" && candidate.intervalSeconds !== timing.intervalSeconds) continue;
return owned;
}
}
function _jobToSchedule(job, timing) {
return _assertClassBrand(_Scheduler_brand, this, _payloadToSchedule).call(this, job.id, job.fn, timing, Math.floor(job.time / 1e3));
}
function _payloadToSchedule(id, fn, timing, timeSeconds) {
const base = {
callback: fn,
id,
payload: timing.payload,
retry: timing.retry
};
const time = timeSeconds ?? 0;
switch (timing.type) {
case "scheduled": return {
...base,
time,
type: "scheduled"
};
case "delayed": return {
...base,
delayInSeconds: timing.delayInSeconds ?? 0,
time,
type: "delayed"
};
case "cron": return {
...base,
cron: timing.cron ?? "",
time,
type: "cron"
};
case "interval": return {
...base,
intervalSeconds: timing.intervalSeconds ?? 0,
time,
type: "interval"
};
}
}
function _getForOwner(owner, id) {
const ownerKey = owner?.key ?? null;
const job = this.lifecycle.jobs.get(id);
if (!job || !isSchedulerJobPayload(job.payload)) return void 0;
if ((job.payload.owner_path_key ?? null) !== ownerKey) return void 0;
return _assertClassBrand(_Scheduler_brand, this, _jobToSchedule).call(this, job, job.payload);
}
function _listForOwner(owner, criteria = {}) {
const ownerKey = owner?.key ?? null;
const startSeconds = criteria.timeRange ? Math.floor((criteria.timeRange.start ?? /* @__PURE__ */ new Date(0)).getTime() / 1e3) : null;
const endSeconds = criteria.timeRange ? Math.floor((criteria.timeRange.end ?? /* @__PURE__ */ new Date(999999999999999)).getTime() / 1e3) : null;
const schedules = [];
for (const { job, timing } of _assertClassBrand(_Scheduler_brand, this, _ownedJobs).call(this)) {
if ((timing.owner_path_key ?? null) !== ownerKey) continue;
if (criteria.id && job.id !== criteria.id) continue;
if (criteria.type && timing.type !== criteria.type) continue;
const timeSeconds = Math.floor(job.time / 1e3);
if (startSeconds !== null && timeSeconds < startSeconds) continue;
if (endSeconds !== null && timeSeconds > endSeconds) continue;
schedules.push(_assertClassBrand(_Scheduler_brand, this, _jobToSchedule).call(this, job, timing));
}
return schedules;
}
async function _cancelForOwner(owner, id) {
const ownerKey = owner?.key ?? null;
const job = this.lifecycle.jobs.get(id);
if (!job || !isSchedulerJobPayload(job.payload)) return { ok: false };
if ((job.payload.owner_path_key ?? null) !== ownerKey) return { ok: false };
const callback = job.fn;
await this.lifecycle.jobs.cancel(id);
return {
ok: true,
callback
};
}
function _emit(type, payload) {
this.lifecycle.events.emit(type, payload);
}
function _emitCreated(result) {
if (!result.created) return;
_assertClassBrand(_Scheduler_brand, this, _emit).call(this, "schedule:create", {
callback: result.schedule.callback,
id: result.schedule.id
});
}
//#endregion
export { setSchedulerCallbackResolver as n, Scheduler as t };
//# sourceMappingURL=scheduler-D_KHqxBa.js.map