@lunora/scheduler 1.0.0-alpha.14 → 1.0.0-alpha.16

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/README.md CHANGED
@@ -98,6 +98,7 @@ import { internal } from "@/lunora/_generated/api";
98
98
  const crons = cronJobs();
99
99
 
100
100
  crons.interval("clear presence", { minutes: 30 }, internal.presence.clear, {});
101
+ crons.hourly("sweep sessions", { minuteUTC: 17 }, internal.presence.sweep, {});
101
102
  crons.daily("send digest", { hourUTC: 9, minuteUTC: 0 }, internal.email.digest, {});
102
103
 
103
104
  export default crons;
package/dist/index.d.mts CHANGED
@@ -440,6 +440,19 @@ interface DailySchedule {
440
440
  /** 0–59. */
441
441
  minuteUTC: number;
442
442
  }
443
+ /**
444
+ * Hourly recurrence at a fixed minute past the hour.
445
+ *
446
+ * `crons.interval({ hours: 1 })` compiles to the same expression, but the
447
+ * asymmetry of having `daily`/`weekly`/`monthly` and no `hourly` is its own
448
+ * papercut — and unlike the interval form this one lets
449
+ * the caller place the job off the hour boundary, which is how you stop a
450
+ * dozen hourly jobs from stampeding at `:00`.
451
+ */
452
+ interface HourlySchedule {
453
+ /** 0–59. */
454
+ minuteUTC: number;
455
+ }
443
456
  /** Weekly recurrence at a fixed UTC time on a given weekday. */
444
457
  interface WeeklySchedule extends DailySchedule {
445
458
  /** Long weekday name, case-insensitive (e.g. `"monday"`). */
@@ -477,7 +490,7 @@ interface CronJob {
477
490
  workflow?: string;
478
491
  }
479
492
  /** The ergonomic builder methods, excluding the raw `.cron` escape hatch. */
480
- type CronScheduleKind = "daily" | "interval" | "monthly" | "weekly";
493
+ type CronScheduleKind = "daily" | "hourly" | "interval" | "monthly" | "weekly";
481
494
  /** The ergonomic schedule kinds as a runtime set (codegen reads this to detect cron builder methods). */
482
495
  declare const CRON_SCHEDULE_KINDS: ReadonlySet<CronScheduleKind>;
483
496
  /**
@@ -486,7 +499,7 @@ declare const CRON_SCHEDULE_KINDS: ReadonlySet<CronScheduleKind>;
486
499
  * compilation when it statically lifts a `crons.{kind}(...)` call out of the
487
500
  * AST — codegen imports this directly (no duplicated mirror).
488
501
  */
489
- declare const compileCronSchedule: (kind: CronScheduleKind, schedule: DailySchedule | IntervalSchedule | MonthlySchedule | WeeklySchedule) => string;
502
+ declare const compileCronSchedule: (kind: CronScheduleKind, schedule: DailySchedule | HourlySchedule | IntervalSchedule | MonthlySchedule | WeeklySchedule) => string;
490
503
  /**
491
504
  * Builder returned by {@link cronJobs}. Each method registers one recurring
492
505
  * job; the compiled expression is validated immediately so authoring mistakes
@@ -501,6 +514,8 @@ interface CronJobsBuilder {
501
514
  cron: <T extends CronTarget>(name: string, cronExpr: string, target: T, args?: CronTargetArgs<T>) => CronJobsBuilder;
502
515
  /** Daily at `hourUTC:minuteUTC` (UTC). The target may be a function or a durable workflow (`workflows.&lt;name>`). */
503
516
  daily: <T extends CronTarget>(name: string, schedule: DailySchedule, target: T, args?: CronTargetArgs<T>) => CronJobsBuilder;
517
+ /** Hourly at `minuteUTC` past the hour. The target may be a function or a durable workflow (`workflows.&lt;name>`). */
518
+ hourly: <T extends CronTarget>(name: string, schedule: HourlySchedule, target: T, args?: CronTargetArgs<T>) => CronJobsBuilder;
504
519
  /** Every `{ seconds | minutes | hours }`. The target may be a function or a durable workflow (`workflows.&lt;name>`). */
505
520
  interval: <T extends CronTarget>(name: string, schedule: IntervalSchedule, target: T, args?: CronTargetArgs<T>) => CronJobsBuilder;
506
521
  /** Snapshot of the registered jobs, in declaration order. */
@@ -891,4 +906,4 @@ declare const isValidCronExpression: (schedule: string) => boolean;
891
906
  * job (`cron job "send digest"`) vs. the bare trigger.
892
907
  */
893
908
  declare const assertValidCronExpression: (schedule: string, context?: string) => void;
894
- export { type ArgsOf, CRON_SCHEDULE_KINDS, type CronJob, type CronJobsBuilder, type CronScheduleKind, type CronTarget, type CronTriggerOptions, type CronTriggerSnippet, type DailySchedule, type DurableObjectIdLike, type DurableObjectJurisdiction, type DurableObjectNamespaceLike, type DurableObjectStubLike, type EnqueueOptions, type FunctionReference, type HttpDispatcherOptions, type IntervalSchedule, type LunoraSchedulerOptions, type MessageBatchLike, type MonthlySchedule, type QueueConsumerOptions, type QueueDispatch, type QueueEnqueueOptions, type QueueJob, type QueueLike, type QueueMessageLike, type QueueSendOptionsLike, type QueueSendRequestLike, type QueueWorkpool, type QueueWorkpoolOptions, type RetryPolicy, type RunOptions, type ScheduleRecord, type Scheduler, SchedulerDO, type SchedulerDOState, type SchedulerEnv, type SchedulerHostOptions, type SchedulerPoolStatus, type SchedulerStatus, type WeeklySchedule, type WorkflowReference, type Workpool, type WorkpoolOptions, assertValidCronExpression, compileCronSchedule, createCronTrigger, createQueueConsumer, createQueueWorkpool, createScheduler, createSchedulerHost, createWorkpool, cronJobs, httpDispatcher, isValidCronExpression, isWorkflowReference };
909
+ export { type ArgsOf, CRON_SCHEDULE_KINDS, type CronJob, type CronJobsBuilder, type CronScheduleKind, type CronTarget, type CronTriggerOptions, type CronTriggerSnippet, type DailySchedule, type DurableObjectIdLike, type DurableObjectJurisdiction, type DurableObjectNamespaceLike, type DurableObjectStubLike, type EnqueueOptions, type FunctionReference, type HourlySchedule, type HttpDispatcherOptions, type IntervalSchedule, type LunoraSchedulerOptions, type MessageBatchLike, type MonthlySchedule, type QueueConsumerOptions, type QueueDispatch, type QueueEnqueueOptions, type QueueJob, type QueueLike, type QueueMessageLike, type QueueSendOptionsLike, type QueueSendRequestLike, type QueueWorkpool, type QueueWorkpoolOptions, type RetryPolicy, type RunOptions, type ScheduleRecord, type Scheduler, SchedulerDO, type SchedulerDOState, type SchedulerEnv, type SchedulerHostOptions, type SchedulerPoolStatus, type SchedulerStatus, type WeeklySchedule, type WorkflowReference, type Workpool, type WorkpoolOptions, assertValidCronExpression, compileCronSchedule, createCronTrigger, createQueueConsumer, createQueueWorkpool, createScheduler, createSchedulerHost, createWorkpool, cronJobs, httpDispatcher, isValidCronExpression, isWorkflowReference };
package/dist/index.d.ts CHANGED
@@ -440,6 +440,19 @@ interface DailySchedule {
440
440
  /** 0–59. */
441
441
  minuteUTC: number;
442
442
  }
443
+ /**
444
+ * Hourly recurrence at a fixed minute past the hour.
445
+ *
446
+ * `crons.interval({ hours: 1 })` compiles to the same expression, but the
447
+ * asymmetry of having `daily`/`weekly`/`monthly` and no `hourly` is its own
448
+ * papercut — and unlike the interval form this one lets
449
+ * the caller place the job off the hour boundary, which is how you stop a
450
+ * dozen hourly jobs from stampeding at `:00`.
451
+ */
452
+ interface HourlySchedule {
453
+ /** 0–59. */
454
+ minuteUTC: number;
455
+ }
443
456
  /** Weekly recurrence at a fixed UTC time on a given weekday. */
444
457
  interface WeeklySchedule extends DailySchedule {
445
458
  /** Long weekday name, case-insensitive (e.g. `"monday"`). */
@@ -477,7 +490,7 @@ interface CronJob {
477
490
  workflow?: string;
478
491
  }
479
492
  /** The ergonomic builder methods, excluding the raw `.cron` escape hatch. */
480
- type CronScheduleKind = "daily" | "interval" | "monthly" | "weekly";
493
+ type CronScheduleKind = "daily" | "hourly" | "interval" | "monthly" | "weekly";
481
494
  /** The ergonomic schedule kinds as a runtime set (codegen reads this to detect cron builder methods). */
482
495
  declare const CRON_SCHEDULE_KINDS: ReadonlySet<CronScheduleKind>;
483
496
  /**
@@ -486,7 +499,7 @@ declare const CRON_SCHEDULE_KINDS: ReadonlySet<CronScheduleKind>;
486
499
  * compilation when it statically lifts a `crons.{kind}(...)` call out of the
487
500
  * AST — codegen imports this directly (no duplicated mirror).
488
501
  */
489
- declare const compileCronSchedule: (kind: CronScheduleKind, schedule: DailySchedule | IntervalSchedule | MonthlySchedule | WeeklySchedule) => string;
502
+ declare const compileCronSchedule: (kind: CronScheduleKind, schedule: DailySchedule | HourlySchedule | IntervalSchedule | MonthlySchedule | WeeklySchedule) => string;
490
503
  /**
491
504
  * Builder returned by {@link cronJobs}. Each method registers one recurring
492
505
  * job; the compiled expression is validated immediately so authoring mistakes
@@ -501,6 +514,8 @@ interface CronJobsBuilder {
501
514
  cron: <T extends CronTarget>(name: string, cronExpr: string, target: T, args?: CronTargetArgs<T>) => CronJobsBuilder;
502
515
  /** Daily at `hourUTC:minuteUTC` (UTC). The target may be a function or a durable workflow (`workflows.&lt;name>`). */
503
516
  daily: <T extends CronTarget>(name: string, schedule: DailySchedule, target: T, args?: CronTargetArgs<T>) => CronJobsBuilder;
517
+ /** Hourly at `minuteUTC` past the hour. The target may be a function or a durable workflow (`workflows.&lt;name>`). */
518
+ hourly: <T extends CronTarget>(name: string, schedule: HourlySchedule, target: T, args?: CronTargetArgs<T>) => CronJobsBuilder;
504
519
  /** Every `{ seconds | minutes | hours }`. The target may be a function or a durable workflow (`workflows.&lt;name>`). */
505
520
  interval: <T extends CronTarget>(name: string, schedule: IntervalSchedule, target: T, args?: CronTargetArgs<T>) => CronJobsBuilder;
506
521
  /** Snapshot of the registered jobs, in declaration order. */
@@ -891,4 +906,4 @@ declare const isValidCronExpression: (schedule: string) => boolean;
891
906
  * job (`cron job "send digest"`) vs. the bare trigger.
892
907
  */
893
908
  declare const assertValidCronExpression: (schedule: string, context?: string) => void;
894
- export { type ArgsOf, CRON_SCHEDULE_KINDS, type CronJob, type CronJobsBuilder, type CronScheduleKind, type CronTarget, type CronTriggerOptions, type CronTriggerSnippet, type DailySchedule, type DurableObjectIdLike, type DurableObjectJurisdiction, type DurableObjectNamespaceLike, type DurableObjectStubLike, type EnqueueOptions, type FunctionReference, type HttpDispatcherOptions, type IntervalSchedule, type LunoraSchedulerOptions, type MessageBatchLike, type MonthlySchedule, type QueueConsumerOptions, type QueueDispatch, type QueueEnqueueOptions, type QueueJob, type QueueLike, type QueueMessageLike, type QueueSendOptionsLike, type QueueSendRequestLike, type QueueWorkpool, type QueueWorkpoolOptions, type RetryPolicy, type RunOptions, type ScheduleRecord, type Scheduler, SchedulerDO, type SchedulerDOState, type SchedulerEnv, type SchedulerHostOptions, type SchedulerPoolStatus, type SchedulerStatus, type WeeklySchedule, type WorkflowReference, type Workpool, type WorkpoolOptions, assertValidCronExpression, compileCronSchedule, createCronTrigger, createQueueConsumer, createQueueWorkpool, createScheduler, createSchedulerHost, createWorkpool, cronJobs, httpDispatcher, isValidCronExpression, isWorkflowReference };
909
+ export { type ArgsOf, CRON_SCHEDULE_KINDS, type CronJob, type CronJobsBuilder, type CronScheduleKind, type CronTarget, type CronTriggerOptions, type CronTriggerSnippet, type DailySchedule, type DurableObjectIdLike, type DurableObjectJurisdiction, type DurableObjectNamespaceLike, type DurableObjectStubLike, type EnqueueOptions, type FunctionReference, type HourlySchedule, type HttpDispatcherOptions, type IntervalSchedule, type LunoraSchedulerOptions, type MessageBatchLike, type MonthlySchedule, type QueueConsumerOptions, type QueueDispatch, type QueueEnqueueOptions, type QueueJob, type QueueLike, type QueueMessageLike, type QueueSendOptionsLike, type QueueSendRequestLike, type QueueWorkpool, type QueueWorkpoolOptions, type RetryPolicy, type RunOptions, type ScheduleRecord, type Scheduler, SchedulerDO, type SchedulerDOState, type SchedulerEnv, type SchedulerHostOptions, type SchedulerPoolStatus, type SchedulerStatus, type WeeklySchedule, type WorkflowReference, type Workpool, type WorkpoolOptions, assertValidCronExpression, compileCronSchedule, createCronTrigger, createQueueConsumer, createQueueWorkpool, createScheduler, createSchedulerHost, createWorkpool, cronJobs, httpDispatcher, isValidCronExpression, isWorkflowReference };
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{default as o}from"./packem_shared/createScheduler-CsRAEtdb.mjs";import{default as p}from"./packem_shared/createWorkpool-DyVdhB6o.mjs";import{createCronTrigger as c}from"./packem_shared/createCronTrigger-CyPdVKNF.mjs";import{CRON_SCHEDULE_KINDS as f,compileCronSchedule as l,cronJobs as m}from"./packem_shared/CRON_SCHEDULE_KINDS-CZFw97XW.mjs";import{createQueueConsumer as x,createQueueWorkpool as i,httpDispatcher as n}from"./packem_shared/createQueueConsumer-B0qNXRUJ.mjs";import{SchedulerDO as C}from"./packem_shared/SchedulerDO-DGgeZ-uI.mjs";import{createSchedulerHost as S}from"./packem_shared/createSchedulerHost-C5XacwXv.mjs";import{isWorkflowReference as E}from"./packem_shared/isWorkflowReference-CT3tdefh.mjs";import{assertValidCronExpression as W,isValidCronExpression as g}from"./packem_shared/assertValidCronExpression-DHvO7Vm9.mjs";export{f as CRON_SCHEDULE_KINDS,C as SchedulerDO,W as assertValidCronExpression,l as compileCronSchedule,c as createCronTrigger,x as createQueueConsumer,i as createQueueWorkpool,o as createScheduler,S as createSchedulerHost,p as createWorkpool,m as cronJobs,n as httpDispatcher,g as isValidCronExpression,E as isWorkflowReference};
1
+ import{default as o}from"./packem_shared/createScheduler-CsRAEtdb.mjs";import{default as p}from"./packem_shared/createWorkpool-DyVdhB6o.mjs";import{createCronTrigger as c}from"./packem_shared/createCronTrigger-CyPdVKNF.mjs";import{CRON_SCHEDULE_KINDS as f,compileCronSchedule as l,cronJobs as m}from"./packem_shared/CRON_SCHEDULE_KINDS-DgupPI9t.mjs";import{createQueueConsumer as x,createQueueWorkpool as i,httpDispatcher as n}from"./packem_shared/createQueueConsumer-B0qNXRUJ.mjs";import{SchedulerDO as C}from"./packem_shared/SchedulerDO-DGgeZ-uI.mjs";import{createSchedulerHost as S}from"./packem_shared/createSchedulerHost-C5XacwXv.mjs";import{isWorkflowReference as E}from"./packem_shared/isWorkflowReference-CT3tdefh.mjs";import{assertValidCronExpression as W,isValidCronExpression as g}from"./packem_shared/assertValidCronExpression-DHvO7Vm9.mjs";export{f as CRON_SCHEDULE_KINDS,C as SchedulerDO,W as assertValidCronExpression,l as compileCronSchedule,c as createCronTrigger,x as createQueueConsumer,i as createQueueWorkpool,o as createScheduler,S as createSchedulerHost,p as createWorkpool,m as cronJobs,n as httpDispatcher,g as isValidCronExpression,E as isWorkflowReference};
@@ -0,0 +1 @@
1
+ import{LunoraError as s}from"@lunora/errors";import{isWorkflowReference as m}from"./isWorkflowReference-CT3tdefh.mjs";import{assertValidCronExpression as h}from"./assertValidCronExpression-DHvO7Vm9.mjs";const f={friday:5,monday:1,saturday:6,sunday:0,thursday:4,tuesday:2,wednesday:3},w=24,y=" For a daily or longer schedule use crons.daily(name, { hourUTC, minuteUTC }, …), crons.weekly(name, { dayOfWeek, hourUTC, minuteUTC }, …) or crons.monthly(name, { day, hourUTC, minuteUTC }, …).",l=(e,o,r,t)=>{if(!Number.isInteger(e)||e<r||e>t)throw new s("INTERNAL",`@lunora/scheduler: cronJobs ${o} must be an integer in [${r.toFixed(0)}, ${t.toFixed(0)}], got ${String(e)}`);return e.toFixed(0)},c=(e,o,r)=>{const t=l(e,o,1,r-1);if(r%e!==0)throw new s("INTERNAL",`@lunora/scheduler: ${o} must evenly divide ${r.toFixed(0)} for a fixed "every ${e.toFixed(0)}" interval — cron "*/${e.toFixed(0)}" means "at values divisible by ${e.toFixed(0)}", which wraps unevenly; pick a divisor of ${r.toFixed(0)}`);return t},T=e=>{const o=["seconds","minutes","hours"].filter(n=>e[n]!==void 0);if(o.length!==1){const n=Object.entries(e).filter(([,a])=>a!==void 0).map(([a])=>a);throw new s("INTERNAL",`@lunora/scheduler: interval schedule must specify exactly one of { seconds, minutes, hours }, got { ${n.join(", ")} }.${y}`)}const r=o[0],t=e[r];if(r==="hours"&&t>=w)throw new s("INTERNAL",`@lunora/scheduler: interval.hours is capped at 23, got ${String(t)} — an interval repeats WITHIN a day rather than spanning one.${y}`);return r==="seconds"?`*/${c(t,"interval.seconds",60)} * * * * *`:r==="minutes"?`*/${c(t,"interval.minutes",60)} * * * *`:`0 */${c(t,"interval.hours",24)} * * *`},$=e=>`${l(e.minuteUTC,"hourly.minuteUTC",0,59)} * * * *`,C=e=>{const o=l(e.minuteUTC,"daily.minuteUTC",0,59),r=l(e.hourUTC,"daily.hourUTC",0,23);return`${o} ${r} * * *`},p=e=>{const o=f[e.dayOfWeek];if(o===void 0)throw new s("INTERNAL",`@lunora/scheduler: weekly schedule has invalid dayOfWeek "${e.dayOfWeek}"`);const r=l(e.minuteUTC,"weekly.minuteUTC",0,59),t=l(e.hourUTC,"weekly.hourUTC",0,23);return`${r} ${t} * * ${o.toFixed(0)}`},v=e=>{const o=l(e.day,"monthly.day",1,31),r=l(e.minuteUTC,"monthly.minuteUTC",0,59),t=l(e.hourUTC,"monthly.hourUTC",0,23);return`${r} ${t} ${o} * *`},k=new Set(["daily","hourly","interval","monthly","weekly"]),d=(e,o)=>{switch(e){case"daily":return C(o);case"hourly":return $(o);case"interval":return T(o);case"monthly":return v(o);case"weekly":return p(o);default:throw new s("INTERNAL",`@lunora/scheduler: unknown cron schedule kind "${String(e)}"`)}},b=()=>{const e=[],o=new Set,r=(n,a,i,u)=>{if(typeof n!="string"||n.trim()==="")throw new s("INTERNAL","@lunora/scheduler: cron job name must be a non-empty string");if(o.has(n))throw new s("INTERNAL",`@lunora/scheduler: duplicate cron job name "${n}" — names must be unique within one cronJobs()`);if(m(i)){h(a,`cron expression for job "${n}"`),o.add(n),e.push({args:u??{},cron:a,name:n,workflow:typeof i.name=="string"?i.name:""});return}if(!i||typeof i.__lunoraRef!="string")throw new s("INTERNAL",`@lunora/scheduler: cron job "${n}" requires a function reference (e.g. internal.email.digest) or a workflow reference`);h(a,`cron expression for job "${n}"`),o.add(n),e.push({args:u??{},cron:a,functionPath:i.__lunoraRef,name:n})},t={cron(n,a,i,u){return r(n,a,i,u),t},daily(n,a,i,u){return r(n,d("daily",a),i,u),t},hourly(n,a,i,u){return r(n,d("hourly",a),i,u),t},interval(n,a,i,u){return r(n,d("interval",a),i,u),t},jobs:()=>[...e],monthly(n,a,i,u){return r(n,d("monthly",a),i,u),t},weekly(n,a,i,u){return r(n,d("weekly",a),i,u),t}};return t};export{k as CRON_SCHEDULE_KINDS,d as compileCronSchedule,b as cronJobs};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/scheduler",
3
- "version": "1.0.0-alpha.14",
3
+ "version": "1.0.0-alpha.16",
4
4
  "description": "Scheduling for Lunora: runAfter / runAt and Cron Triggers via SchedulerDO",
5
5
  "keywords": [
6
6
  "cloudflare",
@@ -46,7 +46,7 @@
46
46
  "access": "public"
47
47
  },
48
48
  "dependencies": {
49
- "@lunora/errors": "1.0.0-alpha.9",
49
+ "@lunora/errors": "1.0.0-alpha.10",
50
50
  "@lunora/platform": "1.0.0-alpha.1",
51
51
  "cron-parser": "5.6.2"
52
52
  },
@@ -1 +0,0 @@
1
- import{LunoraError as u}from"@lunora/errors";import{isWorkflowReference as m}from"./isWorkflowReference-CT3tdefh.mjs";import{assertValidCronExpression as h}from"./assertValidCronExpression-DHvO7Vm9.mjs";const y={friday:5,monday:1,saturday:6,sunday:0,thursday:4,tuesday:2,wednesday:3},l=(e,r,n,t)=>{if(!Number.isInteger(e)||e<n||e>t)throw new u("INTERNAL",`@lunora/scheduler: cronJobs ${r} must be an integer in [${n.toFixed(0)}, ${t.toFixed(0)}], got ${String(e)}`);return e.toFixed(0)},c=(e,r,n)=>{const t=l(e,r,1,n-1);if(n%e!==0)throw new u("INTERNAL",`@lunora/scheduler: ${r} must evenly divide ${n.toFixed(0)} for a fixed "every ${e.toFixed(0)}" interval — cron "*/${e.toFixed(0)}" means "at values divisible by ${e.toFixed(0)}", which wraps unevenly; pick a divisor of ${n.toFixed(0)}`);return t},f=e=>{const r=["seconds","minutes","hours"].filter(o=>e[o]!==void 0);if(r.length!==1)throw new u("INTERNAL","@lunora/scheduler: interval schedule must specify exactly one of { seconds, minutes, hours }");const n=r[0],t=e[n];return n==="seconds"?`*/${c(t,"interval.seconds",60)} * * * * *`:n==="minutes"?`*/${c(t,"interval.minutes",60)} * * * *`:`0 */${c(t,"interval.hours",24)} * * *`},w=e=>{const r=l(e.minuteUTC,"daily.minuteUTC",0,59),n=l(e.hourUTC,"daily.hourUTC",0,23);return`${r} ${n} * * *`},$=e=>{const r=y[e.dayOfWeek];if(r===void 0)throw new u("INTERNAL",`@lunora/scheduler: weekly schedule has invalid dayOfWeek "${e.dayOfWeek}"`);const n=l(e.minuteUTC,"weekly.minuteUTC",0,59),t=l(e.hourUTC,"weekly.hourUTC",0,23);return`${n} ${t} * * ${r.toFixed(0)}`},T=e=>{const r=l(e.day,"monthly.day",1,31),n=l(e.minuteUTC,"monthly.minuteUTC",0,59),t=l(e.hourUTC,"monthly.hourUTC",0,23);return`${n} ${t} ${r} * *`},C=new Set(["daily","interval","monthly","weekly"]),d=(e,r)=>{switch(e){case"daily":return w(r);case"interval":return f(r);case"monthly":return T(r);case"weekly":return $(r);default:throw new u("INTERNAL",`@lunora/scheduler: unknown cron schedule kind "${String(e)}"`)}},b=()=>{const e=[],r=new Set,n=(o,s,i,a)=>{if(typeof o!="string"||o.trim()==="")throw new u("INTERNAL","@lunora/scheduler: cron job name must be a non-empty string");if(r.has(o))throw new u("INTERNAL",`@lunora/scheduler: duplicate cron job name "${o}" — names must be unique within one cronJobs()`);if(m(i)){h(s,`cron expression for job "${o}"`),r.add(o),e.push({args:a??{},cron:s,name:o,workflow:typeof i.name=="string"?i.name:""});return}if(!i||typeof i.__lunoraRef!="string")throw new u("INTERNAL",`@lunora/scheduler: cron job "${o}" requires a function reference (e.g. internal.email.digest) or a workflow reference`);h(s,`cron expression for job "${o}"`),r.add(o),e.push({args:a??{},cron:s,functionPath:i.__lunoraRef,name:o})},t={cron(o,s,i,a){return n(o,s,i,a),t},daily(o,s,i,a){return n(o,d("daily",s),i,a),t},interval(o,s,i,a){return n(o,d("interval",s),i,a),t},jobs:()=>[...e],monthly(o,s,i,a){return n(o,d("monthly",s),i,a),t},weekly(o,s,i,a){return n(o,d("weekly",s),i,a),t}};return t};export{C as CRON_SCHEDULE_KINDS,d as compileCronSchedule,b as cronJobs};