@lunora/scheduler 1.0.0-alpha.126 → 1.0.0-alpha.128

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.
Files changed (29) hide show
  1. package/dist/index.d.mts +4 -30
  2. package/dist/index.d.ts +4 -30
  3. package/dist/index.mjs +1 -1
  4. package/dist/packem_shared/{CRON_SCHEDULE_KINDS-On2YWczq.mjs → CRON_SCHEDULE_KINDS-cpzyyYDs.mjs} +1 -1
  5. package/dist/packem_shared/MAX_RETRY_ATTEMPTS-C7-WenJ5.mjs +1 -0
  6. package/dist/packem_shared/{assertScheduleDelay-CVTqeF04.mjs → assertScheduleDelay-CVFZanKn.mjs} +1 -1
  7. package/dist/packem_shared/{assertScheduleInstant-DCqHdiVt.mjs → assertScheduleInstant-DcEKtg0c.mjs} +1 -1
  8. package/dist/packem_shared/assertValidCronExpression-Bs7NjxLJ.mjs +1 -0
  9. package/dist/packem_shared/{createCronTrigger-Ci0xpx1L.mjs → createCronTrigger-CbeXmMIg.mjs} +1 -1
  10. package/dist/packem_shared/createQueueConsumer-B2s3Xod5.mjs +4 -0
  11. package/dist/packem_shared/createScheduler-3p6oGhSi.mjs +1 -0
  12. package/dist/packem_shared/{createSchedulerHost-cdArZpNX.mjs → createSchedulerHost-Cjqd9wDF.mjs} +1 -1
  13. package/dist/packem_shared/createWorkpool-Bo9avoNF.mjs +1 -0
  14. package/dist/packem_shared/resolve-schedule-id-B2ghxgFm.mjs +1 -0
  15. package/dist/packem_shared/resolveScheduleId-BEV7mby4.mjs +1 -0
  16. package/dist/packem_shared/validate-cron-full.d-Bemx45E8.d.mts +28 -0
  17. package/dist/packem_shared/validate-cron-full.d-Bemx45E8.d.ts +28 -0
  18. package/dist/validate-cron-full.d.mts +1 -3
  19. package/dist/validate-cron-full.d.ts +1 -3
  20. package/dist/validate-cron-full.mjs +1 -1
  21. package/package.json +3 -2
  22. package/dist/packem_shared/MAX_RETRY_ATTEMPTS-BnrPhYes.mjs +0 -1
  23. package/dist/packem_shared/assertValidCronExpression-BSVKB5wG.mjs +0 -1
  24. package/dist/packem_shared/base64-junLSktH.mjs +0 -1
  25. package/dist/packem_shared/createQueueConsumer-B-2nHgJ1.mjs +0 -4
  26. package/dist/packem_shared/createScheduler-DpLHXye6.mjs +0 -1
  27. package/dist/packem_shared/createWorkpool-BtS_ttVJ.mjs +0 -1
  28. package/dist/packem_shared/resolveScheduleId-DuLCFI_l.mjs +0 -1
  29. package/dist/packem_shared/wire-codec-BqRJjIKh.mjs +0 -1
package/dist/index.d.mts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { SchedulerHost } from '@lunora/platform';
2
+ export { a as assertValidCronExpression, i as isValidCronExpression, w as warnIfSecondsLeading } from "./packem_shared/validate-cron-full.d-Bemx45E8.mjs";
2
3
  /**
3
4
  * The generated function-reference type, shared by every package that needs to
4
5
  * infer a call's args or return from `api.<file>.<fn>`.
@@ -396,7 +397,7 @@ interface MessageBatchLike<Body = unknown> {
396
397
  /** The wire payload Lunora puts on the queue: a function dispatch. */
397
398
  interface QueueJob {
398
399
  /**
399
- * The call's arguments in WIRE form (`shared/wire-codec`), so a `bigint`,
400
+ * The call's arguments in WIRE form (`packages/wire/src/wire-codec`), so a `bigint`,
400
401
  * `Date` or bytes survives the queue's own JSON serialisation. The producers
401
402
  * encode; the shard's dispatch loop is the single decoder. A custom
402
403
  * {@link QueueDispatch} must forward this untouched — decoding it here and
@@ -656,7 +657,7 @@ declare const compileCronSchedule: (kind: CronScheduleKind, schedule: DailySched
656
657
  */
657
658
  interface CronJobsBuilder {
658
659
  /**
659
- * Raw cron expression escape hatch (5- or 6-field, full cron-parser grammar).
660
+ * Raw cron expression escape hatch (5- or 6-field, full `@visulima/cron-parser` Cloudflare grammar).
660
661
  * The target may be a function (`internal.file.fn`) or a durable workflow
661
662
  * (`workflows.<name>`); a workflow's `args` are inferred from its `params`.
662
663
  */
@@ -1380,33 +1381,6 @@ interface SchedulerHostOptions {
1380
1381
  * The returned host has no `cron` member — see the module docstring.
1381
1382
  */
1382
1383
  declare const createSchedulerHost: (options: SchedulerHostOptions) => SchedulerHost;
1383
- /** Standard cron expression (5- or 6-field) or a supported `@macro`. */
1384
- declare const isValidCronExpression: (schedule: string) => boolean;
1385
- /**
1386
- * A 6-field (seconds-leading) cron expression is legal generic cron grammar
1387
- * but not a Cloudflare Cron Trigger — the platform only understands the
1388
- * 5-field, minute-granularity form and rejects the rest at `wrangler deploy`
1389
- * with a message naming neither the job nor the file. Warn here instead of
1390
- * staying silent, without throwing: unlike the ergonomic `.interval()` form,
1391
- * the raw `.cron()` escape hatch is meant to accept cron grammar this module
1392
- * doesn't otherwise second-guess.
1393
- *
1394
- * Exported (not module-private) because it is the ONE place this advisory is
1395
- * written: the runtime `cronJobs()` builder reaches it via
1396
- * {@link assertValidCronExpression}, and `@lunora/codegen`'s static
1397
- * `discover/crons.ts` calls it directly after its own `isValidCronExpression`
1398
- * check — so a hand-authored 6-field `.cron()` warns whether it's discovered
1399
- * from source at build time or registered at runtime, with one shared message.
1400
- */
1401
- declare const warnIfSecondsLeading: (schedule: string, context: string) => void;
1402
- /**
1403
- * Assert a raw cron expression is well-formed, throwing the same shaped error
1404
- * both cron surfaces use. The `context` prefix lets callers name the offending
1405
- * job (`cron job "send digest"`) vs. the bare trigger. Well-formed but
1406
- * Cloudflare-incompatible (6-field) expressions pass but log a warning — see
1407
- * {@link warnIfSecondsLeading}.
1408
- */
1409
- declare const assertValidCronExpression: (schedule: string, context?: string) => void;
1410
1384
  /**
1411
1385
  * Reject a `delayMs` a scheduler cannot act on, before it reaches the
1412
1386
  * SchedulerDO.
@@ -1452,4 +1426,4 @@ declare const assertScheduleDelay: (delayMs: number, surface: string, argument?:
1452
1426
  * @param surface what to name in the message — the call the instant was passed to (e.g. `"ctx.scheduler.runAt"`)
1453
1427
  */
1454
1428
  declare const assertScheduleInstant: (timestampMs: number, nowMs: number, surface: string) => void;
1455
- 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 FunctionKind, type FunctionReference, type HourlySchedule, type HttpDispatcherOptions, type IntervalSchedule, type LunoraSchedulerOptions, MAX_RETRY_ATTEMPTS, type MessageBatchLike, type MonthlySchedule, type QueueConsumerOptions, type QueueDispatch, type QueueEnqueueOptions, type QueueJob, type QueueLike, type QueueMessageLike, type QueueSendOptionsLike, type QueueSendRequestLike, type QueueWorkpool, type QueueWorkpoolOptions, RETRY_BASE_DELAY_MS, 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, assertScheduleDelay, assertScheduleInstant, assertValidCronExpression, compileCronSchedule, createCronTrigger, createQueueConsumer, createQueueWorkpool, createScheduler, createSchedulerHost, createWorkpool, cronJobs, httpDispatcher, isValidCronExpression, isWorkflowReference, resolveScheduleId, warnIfSecondsLeading };
1429
+ 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 FunctionKind, type FunctionReference, type HourlySchedule, type HttpDispatcherOptions, type IntervalSchedule, type LunoraSchedulerOptions, MAX_RETRY_ATTEMPTS, type MessageBatchLike, type MonthlySchedule, type QueueConsumerOptions, type QueueDispatch, type QueueEnqueueOptions, type QueueJob, type QueueLike, type QueueMessageLike, type QueueSendOptionsLike, type QueueSendRequestLike, type QueueWorkpool, type QueueWorkpoolOptions, RETRY_BASE_DELAY_MS, 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, assertScheduleDelay, assertScheduleInstant, compileCronSchedule, createCronTrigger, createQueueConsumer, createQueueWorkpool, createScheduler, createSchedulerHost, createWorkpool, cronJobs, httpDispatcher, isWorkflowReference, resolveScheduleId };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { SchedulerHost } from '@lunora/platform';
2
+ export { a as assertValidCronExpression, i as isValidCronExpression, w as warnIfSecondsLeading } from "./packem_shared/validate-cron-full.d-Bemx45E8.js";
2
3
  /**
3
4
  * The generated function-reference type, shared by every package that needs to
4
5
  * infer a call's args or return from `api.<file>.<fn>`.
@@ -396,7 +397,7 @@ interface MessageBatchLike<Body = unknown> {
396
397
  /** The wire payload Lunora puts on the queue: a function dispatch. */
397
398
  interface QueueJob {
398
399
  /**
399
- * The call's arguments in WIRE form (`shared/wire-codec`), so a `bigint`,
400
+ * The call's arguments in WIRE form (`packages/wire/src/wire-codec`), so a `bigint`,
400
401
  * `Date` or bytes survives the queue's own JSON serialisation. The producers
401
402
  * encode; the shard's dispatch loop is the single decoder. A custom
402
403
  * {@link QueueDispatch} must forward this untouched — decoding it here and
@@ -656,7 +657,7 @@ declare const compileCronSchedule: (kind: CronScheduleKind, schedule: DailySched
656
657
  */
657
658
  interface CronJobsBuilder {
658
659
  /**
659
- * Raw cron expression escape hatch (5- or 6-field, full cron-parser grammar).
660
+ * Raw cron expression escape hatch (5- or 6-field, full `@visulima/cron-parser` Cloudflare grammar).
660
661
  * The target may be a function (`internal.file.fn`) or a durable workflow
661
662
  * (`workflows.<name>`); a workflow's `args` are inferred from its `params`.
662
663
  */
@@ -1380,33 +1381,6 @@ interface SchedulerHostOptions {
1380
1381
  * The returned host has no `cron` member — see the module docstring.
1381
1382
  */
1382
1383
  declare const createSchedulerHost: (options: SchedulerHostOptions) => SchedulerHost;
1383
- /** Standard cron expression (5- or 6-field) or a supported `@macro`. */
1384
- declare const isValidCronExpression: (schedule: string) => boolean;
1385
- /**
1386
- * A 6-field (seconds-leading) cron expression is legal generic cron grammar
1387
- * but not a Cloudflare Cron Trigger — the platform only understands the
1388
- * 5-field, minute-granularity form and rejects the rest at `wrangler deploy`
1389
- * with a message naming neither the job nor the file. Warn here instead of
1390
- * staying silent, without throwing: unlike the ergonomic `.interval()` form,
1391
- * the raw `.cron()` escape hatch is meant to accept cron grammar this module
1392
- * doesn't otherwise second-guess.
1393
- *
1394
- * Exported (not module-private) because it is the ONE place this advisory is
1395
- * written: the runtime `cronJobs()` builder reaches it via
1396
- * {@link assertValidCronExpression}, and `@lunora/codegen`'s static
1397
- * `discover/crons.ts` calls it directly after its own `isValidCronExpression`
1398
- * check — so a hand-authored 6-field `.cron()` warns whether it's discovered
1399
- * from source at build time or registered at runtime, with one shared message.
1400
- */
1401
- declare const warnIfSecondsLeading: (schedule: string, context: string) => void;
1402
- /**
1403
- * Assert a raw cron expression is well-formed, throwing the same shaped error
1404
- * both cron surfaces use. The `context` prefix lets callers name the offending
1405
- * job (`cron job "send digest"`) vs. the bare trigger. Well-formed but
1406
- * Cloudflare-incompatible (6-field) expressions pass but log a warning — see
1407
- * {@link warnIfSecondsLeading}.
1408
- */
1409
- declare const assertValidCronExpression: (schedule: string, context?: string) => void;
1410
1384
  /**
1411
1385
  * Reject a `delayMs` a scheduler cannot act on, before it reaches the
1412
1386
  * SchedulerDO.
@@ -1452,4 +1426,4 @@ declare const assertScheduleDelay: (delayMs: number, surface: string, argument?:
1452
1426
  * @param surface what to name in the message — the call the instant was passed to (e.g. `"ctx.scheduler.runAt"`)
1453
1427
  */
1454
1428
  declare const assertScheduleInstant: (timestampMs: number, nowMs: number, surface: string) => void;
1455
- 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 FunctionKind, type FunctionReference, type HourlySchedule, type HttpDispatcherOptions, type IntervalSchedule, type LunoraSchedulerOptions, MAX_RETRY_ATTEMPTS, type MessageBatchLike, type MonthlySchedule, type QueueConsumerOptions, type QueueDispatch, type QueueEnqueueOptions, type QueueJob, type QueueLike, type QueueMessageLike, type QueueSendOptionsLike, type QueueSendRequestLike, type QueueWorkpool, type QueueWorkpoolOptions, RETRY_BASE_DELAY_MS, 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, assertScheduleDelay, assertScheduleInstant, assertValidCronExpression, compileCronSchedule, createCronTrigger, createQueueConsumer, createQueueWorkpool, createScheduler, createSchedulerHost, createWorkpool, cronJobs, httpDispatcher, isValidCronExpression, isWorkflowReference, resolveScheduleId, warnIfSecondsLeading };
1429
+ 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 FunctionKind, type FunctionReference, type HourlySchedule, type HttpDispatcherOptions, type IntervalSchedule, type LunoraSchedulerOptions, MAX_RETRY_ATTEMPTS, type MessageBatchLike, type MonthlySchedule, type QueueConsumerOptions, type QueueDispatch, type QueueEnqueueOptions, type QueueJob, type QueueLike, type QueueMessageLike, type QueueSendOptionsLike, type QueueSendRequestLike, type QueueWorkpool, type QueueWorkpoolOptions, RETRY_BASE_DELAY_MS, 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, assertScheduleDelay, assertScheduleInstant, compileCronSchedule, createCronTrigger, createQueueConsumer, createQueueWorkpool, createScheduler, createSchedulerHost, createWorkpool, cronJobs, httpDispatcher, isWorkflowReference, resolveScheduleId };
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{default as o}from"./packem_shared/createScheduler-DpLHXye6.mjs";import{default as a}from"./packem_shared/createWorkpool-BtS_ttVJ.mjs";import{createCronTrigger as f}from"./packem_shared/createCronTrigger-Ci0xpx1L.mjs";import{CRON_SCHEDULE_KINDS as p,compileCronSchedule as c,cronJobs as d}from"./packem_shared/CRON_SCHEDULE_KINDS-On2YWczq.mjs";import{createQueueConsumer as m,createQueueWorkpool as n,httpDispatcher as x}from"./packem_shared/createQueueConsumer-B-2nHgJ1.mjs";import{default as i}from"./packem_shared/resolveScheduleId-DuLCFI_l.mjs";import{MAX_RETRY_ATTEMPTS as E,RETRY_BASE_DELAY_MS as C,SchedulerDO as _}from"./packem_shared/MAX_RETRY_ATTEMPTS-BnrPhYes.mjs";import{createSchedulerHost as R}from"./packem_shared/createSchedulerHost-cdArZpNX.mjs";import{isWorkflowReference as A}from"./packem_shared/isWorkflowReference-CT3tdefh.mjs";import{assertValidCronExpression as g,isValidCronExpression as k,warnIfSecondsLeading as L}from"./packem_shared/assertValidCronExpression-BSVKB5wG.mjs";import{default as W}from"./packem_shared/assertScheduleDelay-CVTqeF04.mjs";import{default as w}from"./packem_shared/assertScheduleInstant-DCqHdiVt.mjs";export{p as CRON_SCHEDULE_KINDS,E as MAX_RETRY_ATTEMPTS,C as RETRY_BASE_DELAY_MS,_ as SchedulerDO,W as assertScheduleDelay,w as assertScheduleInstant,g as assertValidCronExpression,c as compileCronSchedule,f as createCronTrigger,m as createQueueConsumer,n as createQueueWorkpool,o as createScheduler,R as createSchedulerHost,a as createWorkpool,d as cronJobs,x as httpDispatcher,k as isValidCronExpression,A as isWorkflowReference,i as resolveScheduleId,L as warnIfSecondsLeading};
1
+ import{default as o}from"./packem_shared/createScheduler-3p6oGhSi.mjs";import{default as a}from"./packem_shared/createWorkpool-Bo9avoNF.mjs";import{createCronTrigger as f}from"./packem_shared/createCronTrigger-CbeXmMIg.mjs";import{CRON_SCHEDULE_KINDS as p,compileCronSchedule as c,cronJobs as d}from"./packem_shared/CRON_SCHEDULE_KINDS-cpzyyYDs.mjs";import{createQueueConsumer as m,createQueueWorkpool as n,httpDispatcher as x}from"./packem_shared/createQueueConsumer-B2s3Xod5.mjs";import{r as i}from"./packem_shared/resolve-schedule-id-B2ghxgFm.mjs";import{MAX_RETRY_ATTEMPTS as E,RETRY_BASE_DELAY_MS as C,SchedulerDO as _}from"./packem_shared/MAX_RETRY_ATTEMPTS-C7-WenJ5.mjs";import{createSchedulerHost as R}from"./packem_shared/createSchedulerHost-Cjqd9wDF.mjs";import{isWorkflowReference as A}from"./packem_shared/isWorkflowReference-CT3tdefh.mjs";import{assertValidCronExpression as g,isValidCronExpression as k,warnIfSecondsLeading as L}from"./packem_shared/assertValidCronExpression-Bs7NjxLJ.mjs";import{default as W}from"./packem_shared/assertScheduleDelay-CVFZanKn.mjs";import{default as w}from"./packem_shared/assertScheduleInstant-DcEKtg0c.mjs";export{p as CRON_SCHEDULE_KINDS,E as MAX_RETRY_ATTEMPTS,C as RETRY_BASE_DELAY_MS,_ as SchedulerDO,W as assertScheduleDelay,w as assertScheduleInstant,g as assertValidCronExpression,c as compileCronSchedule,f as createCronTrigger,m as createQueueConsumer,n as createQueueWorkpool,o as createScheduler,R as createSchedulerHost,a as createWorkpool,d as cronJobs,x as httpDispatcher,k as isValidCronExpression,A as isWorkflowReference,i as resolveScheduleId,L as warnIfSecondsLeading};
@@ -1 +1 @@
1
- import{LunoraError as l}from"@lunora/errors";import{isWorkflowReference as m}from"./isWorkflowReference-CT3tdefh.mjs";import{assertValidCronExpression as f}from"./assertValidCronExpression-BSVKB5wG.mjs";const w={friday:5,monday:1,saturday:6,sunday:0,thursday:4,tuesday:2,wednesday:3},T=24,h=" 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 }, …).",a=(e,o,r,i)=>{if(!Number.isInteger(e)||e<r||e>i)throw new l("INTERNAL",`@lunora/scheduler: cronJobs ${o} must be an integer in [${r.toFixed(0)}, ${i.toFixed(0)}], got ${String(e)}`);return e.toFixed(0)},y=(e,o,r)=>{const i=a(e,o,1,r-1);if(r%e!==0)throw new l("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 i},$=(e,o)=>{const r=["seconds","minutes","hours"].filter(s=>e[s]!==void 0);if(r.length!==1){const s=Object.entries(e).filter(([,t])=>t!==void 0).map(([t])=>t);throw new l("INTERNAL",`@lunora/scheduler: interval schedule must specify exactly one of { seconds, minutes, hours }, got { ${s.join(", ")} }.${h}`)}const i=r[0],n=e[i];if(i==="hours"&&n>=T)throw new l("INTERNAL",`@lunora/scheduler: interval.hours is capped at 23, got ${String(n)} — an interval repeats WITHIN a day rather than spanning one.${h}`);if(i==="seconds")throw new l("INTERNAL",`@lunora/scheduler: cron job${o?` "${o}"`:""} uses interval.seconds (${String(n)}) — Cloudflare Cron Triggers have a one-minute floor and cannot run sub-minute schedules; \`wrangler deploy\` would reject the resulting 6-field cron expression. Use ctx.scheduler.runAfter/runAt (optionally via a workpool for bounded concurrency) for sub-minute recurrence, or crons.interval(name, { minutes: 1 }, …) for the fastest cron-native cadence.`);return i==="minutes"?`*/${y(n,"interval.minutes",60)} * * * *`:`0 */${y(n,"interval.hours",24)} * * *`},p=e=>`${a(e.minuteUTC,"hourly.minuteUTC",0,59)} * * * *`,C=e=>{const o=a(e.minuteUTC,"daily.minuteUTC",0,59),r=a(e.hourUTC,"daily.hourUTC",0,23);return`${o} ${r} * * *`},N=e=>{const o=w[e.dayOfWeek];if(o===void 0)throw new l("INTERNAL",`@lunora/scheduler: weekly schedule has invalid dayOfWeek "${e.dayOfWeek}"`);const r=a(e.minuteUTC,"weekly.minuteUTC",0,59),i=a(e.hourUTC,"weekly.hourUTC",0,23);return`${r} ${i} * * ${o.toFixed(0)}`},U=e=>{const o=a(e.day,"monthly.day",1,31),r=a(e.minuteUTC,"monthly.minuteUTC",0,59),i=a(e.hourUTC,"monthly.hourUTC",0,23);return`${r} ${i} ${o} * *`},b=new Set(["daily","hourly","interval","monthly","weekly"]),c=(e,o,r)=>{switch(e){case"daily":return C(o);case"hourly":return p(o);case"interval":return $(o,r);case"monthly":return U(o);case"weekly":return N(o);default:throw new l("INTERNAL",`@lunora/scheduler: unknown cron schedule kind "${String(e)}"`)}},k=()=>{const e=[],o=new Set,r=(n,s,t,u)=>{if(typeof n!="string"||n.trim()==="")throw new l("INTERNAL","@lunora/scheduler: cron job name must be a non-empty string");if(o.has(n))throw new l("INTERNAL",`@lunora/scheduler: duplicate cron job name "${n}" — names must be unique within one cronJobs()`);let d;if(m(t))d={workflow:typeof t.name=="string"?t.name:""};else if(t&&typeof t.__lunoraRef=="string")d={functionPath:t.__lunoraRef};else throw new l("INTERNAL",`@lunora/scheduler: cron job "${n}" requires a function reference (e.g. internal.email.digest) or a workflow reference`);f(s,`cron expression for job "${n}"`),o.add(n),e.push({args:u??{},cron:s,name:n,...d})},i={cron(n,s,t,u){return r(n,s,t,u),i},daily(n,s,t,u){return r(n,c("daily",s),t,u),i},hourly(n,s,t,u){return r(n,c("hourly",s),t,u),i},interval(n,s,t,u){return r(n,c("interval",s,n),t,u),i},jobs:()=>[...e],monthly(n,s,t,u){return r(n,c("monthly",s),t,u),i},weekly(n,s,t,u){return r(n,c("weekly",s),t,u),i}};return i};export{b as CRON_SCHEDULE_KINDS,c as compileCronSchedule,k as cronJobs};
1
+ import{LunoraError as l}from"@lunora/errors";import{isWorkflowReference as m}from"./isWorkflowReference-CT3tdefh.mjs";import{assertValidCronExpression as f}from"./assertValidCronExpression-Bs7NjxLJ.mjs";const w={friday:6,monday:2,saturday:7,sunday:1,thursday:5,tuesday:3,wednesday:4},T=24,h=" 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 }, …).",a=(e,o,r,i)=>{if(!Number.isInteger(e)||e<r||e>i)throw new l("INTERNAL",`@lunora/scheduler: cronJobs ${o} must be an integer in [${r.toFixed(0)}, ${i.toFixed(0)}], got ${String(e)}`);return e.toFixed(0)},y=(e,o,r)=>{const i=a(e,o,1,r-1);if(r%e!==0)throw new l("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 i},$=(e,o)=>{const r=["seconds","minutes","hours"].filter(s=>e[s]!==void 0);if(r.length!==1){const s=Object.entries(e).filter(([,t])=>t!==void 0).map(([t])=>t);throw new l("INTERNAL",`@lunora/scheduler: interval schedule must specify exactly one of { seconds, minutes, hours }, got { ${s.join(", ")} }.${h}`)}const i=r[0],n=e[i];if(i==="hours"&&n>=T)throw new l("INTERNAL",`@lunora/scheduler: interval.hours is capped at 23, got ${String(n)} — an interval repeats WITHIN a day rather than spanning one.${h}`);if(i==="seconds")throw new l("INTERNAL",`@lunora/scheduler: cron job${o?` "${o}"`:""} uses interval.seconds (${String(n)}) — Cloudflare Cron Triggers have a one-minute floor and cannot run sub-minute schedules; \`wrangler deploy\` would reject the resulting 6-field cron expression. Use ctx.scheduler.runAfter/runAt (optionally via a workpool for bounded concurrency) for sub-minute recurrence, or crons.interval(name, { minutes: 1 }, …) for the fastest cron-native cadence.`);return i==="minutes"?`*/${y(n,"interval.minutes",60)} * * * *`:`0 */${y(n,"interval.hours",24)} * * *`},p=e=>`${a(e.minuteUTC,"hourly.minuteUTC",0,59)} * * * *`,C=e=>{const o=a(e.minuteUTC,"daily.minuteUTC",0,59),r=a(e.hourUTC,"daily.hourUTC",0,23);return`${o} ${r} * * *`},N=e=>{const o=w[e.dayOfWeek];if(o===void 0)throw new l("INTERNAL",`@lunora/scheduler: weekly schedule has invalid dayOfWeek "${e.dayOfWeek}"`);const r=a(e.minuteUTC,"weekly.minuteUTC",0,59),i=a(e.hourUTC,"weekly.hourUTC",0,23);return`${r} ${i} * * ${o.toFixed(0)}`},U=e=>{const o=a(e.day,"monthly.day",1,31),r=a(e.minuteUTC,"monthly.minuteUTC",0,59),i=a(e.hourUTC,"monthly.hourUTC",0,23);return`${r} ${i} ${o} * *`},b=new Set(["daily","hourly","interval","monthly","weekly"]),c=(e,o,r)=>{switch(e){case"daily":return C(o);case"hourly":return p(o);case"interval":return $(o,r);case"monthly":return U(o);case"weekly":return N(o);default:throw new l("INTERNAL",`@lunora/scheduler: unknown cron schedule kind "${String(e)}"`)}},k=()=>{const e=[],o=new Set,r=(n,s,t,u)=>{if(typeof n!="string"||n.trim()==="")throw new l("INTERNAL","@lunora/scheduler: cron job name must be a non-empty string");if(o.has(n))throw new l("INTERNAL",`@lunora/scheduler: duplicate cron job name "${n}" — names must be unique within one cronJobs()`);let d;if(m(t))d={workflow:typeof t.name=="string"?t.name:""};else if(t&&typeof t.__lunoraRef=="string")d={functionPath:t.__lunoraRef};else throw new l("INTERNAL",`@lunora/scheduler: cron job "${n}" requires a function reference (e.g. internal.email.digest) or a workflow reference`);f(s,`cron expression for job "${n}"`),o.add(n),e.push({args:u??{},cron:s,name:n,...d})},i={cron(n,s,t,u){return r(n,s,t,u),i},daily(n,s,t,u){return r(n,c("daily",s),t,u),i},hourly(n,s,t,u){return r(n,c("hourly",s),t,u),i},interval(n,s,t,u){return r(n,c("interval",s,n),t,u),i},jobs:()=>[...e],monthly(n,s,t,u){return r(n,c("monthly",s),t,u),i},weekly(n,s,t,u){return r(n,c("weekly",s),t,u),i}};return i};export{b as CRON_SCHEDULE_KINDS,c as compileCronSchedule,k as cronJobs};
@@ -0,0 +1 @@
1
+ import{t as N,r as R}from"./resolve-schedule-id-B2ghxgFm.mjs";const x="DISPATCH_IN_PROGRESS",E="x-lunora-dispatch-declined",P=(u,t=200,e)=>{const s=new Headers({"content-type":"application/json"});for(const[a,i]of Object.entries({}))s.set(a,i);return Response.json(u,{headers:s,status:t})},F="lunora-ping",L="lunora-pong",c="id:",m="retry:",g="dead:",y="pool:",w=100,$=5,b=3e4,k=1e3,T=6,C=9e5,M=999999999999999,U=15,A=u=>String(u).padStart(U,"0"),I=u=>Number.isInteger(u)&&u>0&&u<=M;class n{static indexKey(t,e){return`t:${A(t)}:${e}`}static json(t,e=200){return P(t,e)}static error(t,e,s){return n.json({error:{code:e,message:s}},t)}static resolveRetry(t){const e=t.retry,s=typeof e?.maxAttempts=="number"&&Number.isInteger(e.maxAttempts)&&e.maxAttempts>0?e.maxAttempts:$,a=typeof e?.baseMs=="number"&&Number.isFinite(e.baseMs)&&e.baseMs>=0?e.baseMs:b,i=e?.backoff==="linear"?"linear":"exponential",o=typeof e?.maxMs=="number"&&Number.isFinite(e.maxMs)&&e.maxMs>=0?e.maxMs:void 0;return{backoff:i,baseMs:a,maxAttempts:s,maxMs:o}}static normalizeConcurrency(t,e){return typeof t=="number"&&Number.isInteger(t)&&t>0?t:e}static normalizeRetry(t){if(typeof t!="object"||t===null)return;const e=t,s={};return typeof e.maxAttempts=="number"&&Number.isInteger(e.maxAttempts)&&e.maxAttempts>0&&(s.maxAttempts=e.maxAttempts),typeof e.baseMs=="number"&&Number.isFinite(e.baseMs)&&e.baseMs>=0&&(s.baseMs=e.baseMs),(e.backoff==="exponential"||e.backoff==="linear")&&(s.backoff=e.backoff),typeof e.maxMs=="number"&&Number.isFinite(e.maxMs)&&e.maxMs>=0&&(s.maxMs=e.maxMs),Object.keys(s).length===0?void 0:s}static releaseSlot(t,e){if(t.inFlightIds===void 0)return{...t,inFlight:Math.max(0,t.inFlight-1)};const s=t.inFlightIds.filter(a=>a!==e);return{...t,inFlight:s.length,inFlightIds:s}}static releaseFirstSlot(t){if(t.inFlightIds===void 0)return{...t,inFlight:Math.max(0,t.inFlight-1)};const e=t.inFlightIds.slice(0,Math.max(0,t.inFlightIds.length-1));return{...t,inFlight:e.length,inFlightIds:e}}static resolveScheduleTarget(t){const e=typeof t?.functionPath=="string"&&t.functionPath.length>0?t.functionPath:void 0,s=typeof t?.workflow=="string"&&t.workflow.length>0?t.workflow:void 0;if(!(e===void 0&&s===void 0))return{functionPath:e,workflow:s}}state;env;reindexed=!1;poolLock=Promise.resolve();activeLeases=new Map;cancelledLeases=new Set;constructor(t,e){this.state=t,this.env=e,this.armWebSocketKeepalive()}async fetch(t){await this.reindexOrphanedRecords();const e=new URL(t.url);if(e.pathname==="/ws"&&t.headers.get("Upgrade")==="websocket")return this.handleWebSocketUpgrade();switch(`${t.method} ${e.pathname}`){case"GET /dead":return this.handleDeadList(e);case"GET /get":return this.handleGet(e);case"GET /list":return this.handleList(e);case"GET /pool":return this.handlePoolStatus(e);case"GET /status":return this.handleStatus();case"POST /cancel":return this.handleCancel(t);case"POST /complete":return this.handleComplete(t);case"POST /dead/cancel":return this.handleDeadCancel(t);case"POST /dead/retry":return this.handleDeadRetry(t);case"POST /schedule":return this.handleSchedule(t)}return P({error:{code:"NOT_FOUND"}},404)}async alarm(){await this.reindexOrphanedRecords();const t=Date.now(),e=[],s=new Set,a=await this.state.storage.list({end:`t:${A(t)}:~`,limit:100,prefix:"t:"}),i=[];for(const[r,d]of a.entries()){const l=Number.parseInt(r.slice(2,r.indexOf(":",2)),10);Number.isFinite(l)&&l<=t&&i.push({indexKey:r,recordId:d})}const o=await Promise.all(i.map(r=>this.state.storage.get(`${c}${r.recordId}`)));for(const[r,{indexKey:d,recordId:l}]of i.entries()){const h=o[r];h&&s.has(l)?await this.state.storage.delete(d):h?(s.add(l),e.push({claimKey:d,record:h})):await this.state.storage.delete(d)}try{await this.drainDue(e)}finally{await this.rescheduleAlarm()}e.length>0&&await this.broadcastChange()}async dispatch(t){const e=typeof this.env.LUNORA_ORIGIN_URL=="string"&&this.env.LUNORA_ORIGIN_URL.length>0?this.env.LUNORA_ORIGIN_URL:void 0;if(!e)return!1;const s=JSON.stringify({args:t.args,functionPath:t.functionPath,id:t.id,instanceName:t.instanceName,pool:t.pool,scheduledFor:t.scheduledFor,shardKey:t.shardKey,workflow:t.workflow});try{const a={"content-type":"application/json"},i=await this.signDispatch(s);i!==void 0?a["x-lunora-scheduler-signature"]=i:typeof this.env.LUNORA_ADMIN_TOKEN=="string"&&this.env.LUNORA_ADMIN_TOKEN.length>0&&(a.authorization=`Bearer ${this.env.LUNORA_ADMIN_TOKEN}`);const o=await fetch(`${e}/_lunora/scheduler/dispatch`,{body:s,headers:a,method:"POST"});return o.status===409&&o.headers.get(E)==="1"&&await o.json().then(d=>d?.error?.code===x,()=>!1)?"in-progress":o.ok}catch{return!1}}armWebSocketKeepalive(){const t=this.state.setWebSocketAutoResponse;typeof t!="function"||typeof WebSocketRequestResponsePair>"u"||t.call(this.state,new WebSocketRequestResponsePair(F,L))}async drainDue(t){const e=[...t],s=[],a=Math.min(T,e.length);for(let i=0;i<a;i+=1)s.push((async()=>{for(let o=e.shift();o!==void 0;o=e.shift())await this.drainRecordGuarded(o.record,o.claimKey)})());await Promise.all(s)}async withPoolLock(t){const e=this.poolLock.then(t,t);return this.poolLock=e.then(()=>{},()=>{}),e}async drainRecordGuarded(t,e){const s=n.indexKey(Date.now()+C,t.id);try{const a=await this.state.storage.get(`${c}${t.id}`);if(a===void 0){await this.state.storage.delete(e);return}if(JSON.stringify(a)!==JSON.stringify(t))return;await this.state.storage.delete(e),await this.state.storage.put(s,t.id),this.activeLeases.set(t.id,s),await this.drainRecord(t)}catch{try{if(this.cancelledLeases.has(t.id)){await this.state.storage.delete(s);return}if(await this.state.storage.get(`${g}${t.id}`)!==void 0){await this.state.storage.delete(s),await this.state.storage.delete([`${m}${t.id}`,`${c}${t.id}`]);return}await this.state.storage.put(e,t.id),await this.state.storage.delete(s)}catch{}return}finally{this.activeLeases.delete(t.id),this.cancelledLeases.delete(t.id)}try{await this.state.storage.delete(s)}catch{}}async drainRecord(t){if(!await this.reservePoolSlot(t))return!1;const s=await this.dispatch(t),a=s===!0,i=t.pool;if(!a&&i!==void 0&&await this.withPoolLock(async()=>{const o=await this.loadPool(i);await this.savePool(i,n.releaseSlot(o,t.id))}),this.cancelledLeases.has(t.id))return a;if(a){try{await this.state.storage.delete([`${c}${t.id}`,`${m}${t.id}`])}catch{}return!0}return await this.recordRetry(t,s==="in-progress"),!1}async reservePoolSlot(t){const e=t.pool;if(e===void 0)return!0;const s=await this.withPoolLock(async()=>{const a=await this.loadPool(e);if(a.inFlight>=a.maxConcurrency)return!1;const i=a.inFlightIds??[];return i.includes(t.id)||i.push(t.id),a.inFlightIds=i,a.inFlight=i.length,await this.savePool(e,a),!0});return s||await this.requeuePooled(t),s}async handleWebSocketUpgrade(){if(this.state.acceptWebSocket===void 0)return n.error(501,"WS_UNSUPPORTED","WebSocket subscriptions are not supported in this runtime");const t=new WebSocketPair,e=t[0],s=t[1];this.state.acceptWebSocket(s);const a=await this.listPage(c,w);return s.send(JSON.stringify({records:a.records,truncated:a.truncated,type:"jobs"})),new Response(null,{status:101,webSocket:e})}async broadcastChange(){const t=this.state.getWebSockets?.();if(t===void 0||t.length===0)return;const{records:e,truncated:s}=await this.listPage(c,w),a=JSON.stringify({records:e,truncated:s,type:"jobs"});for(const i of t)try{i.send(a)}catch{}}async listPage(t,e,s){const a=await this.state.storage.list({limit:e+1,prefix:t,...s===void 0?{}:{startAfter:s}}),i=[...a.keys()],o=[...a.values()],r=o.length>e;return r?{cursor:i[e-1],records:o.slice(0,e),truncated:r}:{records:o,truncated:r}}async forEachPage(t,e,s=w){let a;for(;;){const i=await this.state.storage.list(a===void 0?{limit:s,prefix:t}:{limit:s,prefix:t,startAfter:a});if(i.size===0)break;for(const[r,d]of i.entries())e(d,r);if(a=[...i.keys()].at(-1),i.size<s)break}}async signDispatch(t){const e=typeof this.env.LUNORA_SCHEDULER_SECRET=="string"?this.env.LUNORA_SCHEDULER_SECRET:void 0;if(!e||e.length===0)return;const s=new TextEncoder,a=await crypto.subtle.importKey("raw",s.encode(e),{hash:"SHA-256",name:"HMAC"},!1,["sign"]),i=await crypto.subtle.sign("HMAC",a,s.encode(t));return N(new Uint8Array(i))}async recordRetry(t,e=!1){if(this.cancelledLeases.has(t.id))return;const s=(t.attempts??0)+(e?0:1),a=(t.attempts??0)+1,{backoff:i,baseMs:o,maxAttempts:r,maxMs:d}=n.resolveRetry(t),l=i==="linear"?o*a:o*2**(a-1),h=d===void 0?l:Math.min(l,d),f=e?Math.max(h,b):h,p=Math.round(Date.now()+f);if(s>r){await this.parkDead(t,s,`after ${String(s)} attempts`);return}if(!I(p)){await this.parkDead(t,s,`at attempt ${String(s)}: the retry backoff exceeded the largest schedulable time`);return}const v={...t,attempts:s,scheduledFor:p};await this.state.storage.put({[`${c}${t.id}`]:v,[`${m}${t.id}`]:v,[n.indexKey(p,t.id)]:t.id})}async parkDead(t,e,s){await this.state.storage.put(`${g}${t.id}`,{...t,attempts:e}),await this.state.storage.delete([`${m}${t.id}`,`${c}${t.id}`]),console.warn(`@lunora/scheduler: job "${t.id}" (${t.functionPath??t.workflow??"unknown"}) parked in dead-letter ${s}`)}async loadPool(t,e){const s=await this.state.storage.get(`${y}${t}`);return s!==void 0?Array.isArray(s.inFlightIds)?{inFlight:s.inFlightIds.length,inFlightIds:[...s.inFlightIds],maxConcurrency:s.maxConcurrency}:{inFlight:Math.max(0,s.inFlight),maxConcurrency:s.maxConcurrency}:{inFlight:0,inFlightIds:[],maxConcurrency:n.normalizeConcurrency(e,1)}}async savePool(t,e){await this.state.storage.put(`${y}${t}`,e)}async requeuePooled(t){if(this.cancelledLeases.has(t.id))return;const e=Date.now()+k,s={...t,scheduledFor:e};await this.state.storage.put({[`${c}${t.id}`]:s,[n.indexKey(e,t.id)]:t.id})}async handleComplete(t){const e=await t.json().catch(()=>{}),s=typeof e?.pool=="string"&&e.pool.length>0?e.pool:void 0,a=typeof e?.id=="string"&&e.id.length>0?e.id:void 0;if(s===void 0)return n.error(400,"INVALID_INPUT","pool is required");const i=await this.withPoolLock(async()=>{const o=await this.loadPool(s),r=a===void 0?n.releaseFirstSlot(o):n.releaseSlot(o,a);return await this.savePool(s,r),r});return await this.armAlarmIfEarlier(Date.now()),n.json({inFlight:i.inFlight})}async handlePoolStatus(t){const e=t.searchParams.get("name");if(e===null||e.length===0)return n.error(400,"INVALID_INPUT","name is required");const s=await this.loadPool(e);let a=0;return await this.forEachPage(c,i=>{i.pool===e&&(a+=1)}),n.json({inFlight:s.inFlight,maxConcurrency:s.maxConcurrency,queued:a})}async handleStatus(){const t=new Map;await this.forEachPage(c,o=>{o.pool!==void 0&&t.set(o.pool,(t.get(o.pool)??0)+1)});const e=[];let s=0,a=0;await this.forEachPage(y,(o,r)=>{const d=r.slice(y.length),l=Math.max(0,o.inFlight),h=t.get(d)??0;e.push({inFlight:l,maxConcurrency:o.maxConcurrency,name:d,queued:h}),s+=h,a+=l});const i={backlog:s,inFlight:a,pools:e};return n.json(i)}async persistPoolCap(t,e){const s=await this.loadPool(t,e);await this.savePool(t,{inFlight:s.inFlight,...s.inFlightIds===void 0?{}:{inFlightIds:s.inFlightIds},maxConcurrency:n.normalizeConcurrency(e,s.maxConcurrency)})}async idConflict(t){if(await this.state.storage.get(`${c}${t}`)!==void 0)return n.error(409,"DUPLICATE_SCHEDULE_ID",`a job with id "${t}" is already scheduled — cancel it first, or schedule under a different id`);if(await this.state.storage.get(`${g}${t}`)!==void 0)return n.error(409,"DUPLICATE_SCHEDULE_ID",`id "${t}" is held by a dead-letter record — retry or cancel it (POST /dead/retry, POST /dead/cancel) first, or schedule under a different id`)}async resolveId(t){let e;try{e=R(t)}catch(s){return n.error(400,"INVALID_SCHEDULE_ID",s instanceof Error?s.message:"invalid `id`")}return t===void 0?e:await this.idConflict(e)??e}async handleSchedule(t){const e=await t.json().catch(()=>{}),s=n.resolveScheduleTarget(e);if(!e||s===void 0)return n.error(400,"INVALID_INPUT","functionPath or workflow is required");const{functionPath:a,workflow:i}=s;if(typeof e.scheduledFor!="number"||!I(e.scheduledFor))return n.error(400,"INVALID_INPUT","scheduledFor must be a positive integer epoch-millisecond number no greater than 999999999999999");if(typeof this.env.LUNORA_ORIGIN_URL!="string"||this.env.LUNORA_ORIGIN_URL.length===0)return n.error(500,"ORIGIN_NOT_CONFIGURED","LUNORA_ORIGIN_URL env binding must be set on the SchedulerDO");const o=typeof e.pool=="string"&&e.pool.length>0?e.pool:void 0,r=typeof e.instanceName=="string"&&e.instanceName.length>0?e.instanceName:void 0,d=n.normalizeRetry(e.retry),l=await this.resolveId(e.id);if(l instanceof Response)return l;const h=l,f={args:e.args??{},enqueuedAt:Date.now(),id:h,...a===void 0?{}:{functionPath:a},...r===void 0?{}:{instanceName:r},...o===void 0?{}:{pool:o},...d===void 0?{}:{retry:d},scheduledFor:e.scheduledFor,shardKey:e.shardKey,...i===void 0?{}:{workflow:i}};return o!==void 0&&await this.persistPoolCap(o,e.maxConcurrency),await this.state.storage.put(`${c}${h}`,f),await this.state.storage.put(n.indexKey(f.scheduledFor,h),h),await this.armAlarmIfEarlier(f.scheduledFor),await this.broadcastChange(),n.json({id:h,scheduledFor:f.scheduledFor})}async handleCancel(t){const e=await t.json().catch(()=>{});if(!e?.id)return n.error(400,"INVALID_INPUT","id is required");const s=await this.state.storage.get(`${c}${e.id}`);return s?(await this.removeRecord(s),await this.rescheduleAlarm(),await this.broadcastChange(),n.json({cancelled:!0})):n.json({cancelled:!1})}async handleList(t){const{cursor:e,records:s,truncated:a}=await this.listPage(c,w,t.searchParams.get("cursor")??void 0);return n.json({cursor:e,records:s,truncated:a})}async handleDeadList(t){const{cursor:e,records:s,truncated:a}=await this.listPage(g,w,t.searchParams.get("cursor")??void 0);return n.json({cursor:e,records:s,truncated:a})}async handleDeadRetry(t){const e=await t.json().catch(()=>{});if(typeof e?.id!="string"||e.id.length===0)return n.error(400,"INVALID_INPUT","id is required");const s=await this.state.storage.get(`${g}${e.id}`);if(s===void 0)return n.json({retried:!1});const a=Date.now(),i={...s,attempts:0,scheduledFor:a};return await this.state.storage.put(`${c}${s.id}`,i),await this.state.storage.put(n.indexKey(a,s.id),s.id),await this.state.storage.delete(`${g}${s.id}`),await this.armAlarmIfEarlier(a),await this.broadcastChange(),n.json({id:s.id,retried:!0,scheduledFor:a})}async handleDeadCancel(t){const e=await t.json().catch(()=>{});if(typeof e?.id!="string"||e.id.length===0)return n.error(400,"INVALID_INPUT","id is required");const s=await this.state.storage.delete(`${g}${e.id}`);return n.json({removed:!!s})}async handleGet(t){const e=t.searchParams.get("id");if(e===null||e.length===0)return n.error(400,"INVALID_INPUT","id is required");const s=await this.state.storage.get(`${c}${e}`);return n.json(s===void 0?{}:{record:s})}async removeRecord(t){const e=[`${c}${t.id}`,n.indexKey(t.scheduledFor,t.id),`${m}${t.id}`],s=this.activeLeases.get(t.id);s!==void 0&&(e.push(s),this.cancelledLeases.add(t.id)),await this.state.storage.delete(e)}async armAlarmIfEarlier(t){const e=await this.state.storage.getAlarm();(e===null||t<e)&&await this.state.storage.setAlarm(t)}async reindexOrphanedRecords(){if(this.reindexed)return;this.reindexed=!0;const t=new Set;await this.forEachPage("t:",s=>{t.add(s)});const e=[];await this.forEachPage(c,s=>{!t.has(s.id)&&I(s.scheduledFor)&&e.push(s)});for(const s of e)await this.state.storage.put(n.indexKey(s.scheduledFor,s.id),s.id),await this.armAlarmIfEarlier(s.scheduledFor);if(await this.state.storage.getAlarm()===null){const s=await this.earliestPendingTime();s!==void 0&&Number.isFinite(s)&&await this.state.storage.setAlarm(s)}}async earliestPendingTime(){const e=(await this.state.storage.list({limit:1,prefix:"t:"})).entries().next();if(e.done)return;const[s]=e.value;return Number.parseInt(s.slice(2,s.indexOf(":",2)),10)}async rescheduleAlarm(){const t=await this.earliestPendingTime();if(t===void 0){await this.state.storage.deleteAlarm();return}Number.isFinite(t)&&await this.state.storage.setAlarm(t)}}export{C as DISPATCH_LEASE_MS,T as MAX_CONCURRENT_DISPATCHES,$ as MAX_RETRY_ATTEMPTS,M as MAX_SCHEDULED_FOR_MS,b as RETRY_BASE_DELAY_MS,n as SchedulerDO};
@@ -1 +1 @@
1
- import{LunoraError as r}from"@lunora/errors";import{MAX_SCHEDULED_FOR_MS as i}from"./MAX_RETRY_ATTEMPTS-BnrPhYes.mjs";const s=(t,e,n="delayMs")=>{if(!Number.isFinite(t)||t<0)throw new r("INVALID_INPUT",`${e}: \`${n}\` must be a non-negative finite number`);if(Date.now()+t>i)throw new r("INVALID_INPUT",`${e}: \`${n}\` is later than the latest schedulable instant (${String(i)} ms)`)};export{s as default};
1
+ import{LunoraError as r}from"@lunora/errors";import{MAX_SCHEDULED_FOR_MS as i}from"./MAX_RETRY_ATTEMPTS-C7-WenJ5.mjs";const s=(t,e,n="delayMs")=>{if(!Number.isFinite(t)||t<0)throw new r("INVALID_INPUT",`${e}: \`${n}\` must be a non-negative finite number`);if(Date.now()+t>i)throw new r("INVALID_INPUT",`${e}: \`${n}\` is later than the latest schedulable instant (${String(i)} ms)`)};export{s as default};
@@ -1 +1 @@
1
- import r from"./assertScheduleDelay-CVTqeF04.mjs";const l=(t,s,a)=>{const e=t-s;r(Number.isFinite(e)&&e<0?0:e,a,"date")};export{l as default};
1
+ import r from"./assertScheduleDelay-CVFZanKn.mjs";const l=(t,s,a)=>{const e=t-s;r(Number.isFinite(e)&&e<0?0:e,a,"date")};export{l as default};
@@ -0,0 +1 @@
1
+ import{LunoraError as o}from"@lunora/errors";import{CronExpressionParser as s}from"@visulima/cron-parser";const n=/\s+/u,t={dialect:"cloudflare",seconds:"optional"},i=r=>{if(typeof r!="string")return!1;const e=r.trim().split(n);if(e.length!==5&&e.length!==6)return!1;try{return s.parse(e.join(" "),t),!0}catch{return!1}},a=(r,e)=>{r.trim().split(n).length===6&&console.warn(`@lunora/scheduler: ${e} "${r}" is a 6-field (seconds-leading) cron expression — Cloudflare Cron Triggers only support the standard 5-field form and will reject this at \`wrangler deploy\`. Drop the seconds field, or use ctx.scheduler.runAfter/runAt for sub-minute work.`)},f=(r,e="cron expression")=>{if(!i(r))throw new o("INTERNAL",`@lunora/scheduler: invalid ${e} "${r}" — expected a standard 5- or 6-field cron expression (e.g. "0 * * * *")`);a(r,e)};export{f as assertValidCronExpression,i as isValidCronExpression,a as warnIfSecondsLeading};
@@ -1 +1 @@
1
- import{LunoraError as n}from"@lunora/errors";import{assertValidCronExpression as s}from"./assertValidCronExpression-BSVKB5wG.mjs";const u=r=>{if(!r.schedule||!r.fn)throw new n("INTERNAL","@lunora/scheduler: createCronTrigger() requires `schedule` and `fn`");s(r.schedule);const e=JSON.stringify({triggers:{crons:[r.schedule]}},void 0,2);return{crons:[r.schedule],dispatcher:{args:r.args??{},functionPath:r.fn.__lunoraRef},wranglerJsonc:e}};export{u as createCronTrigger};
1
+ import{LunoraError as n}from"@lunora/errors";import{assertValidCronExpression as s}from"./assertValidCronExpression-Bs7NjxLJ.mjs";const u=r=>{if(!r.schedule||!r.fn)throw new n("INTERNAL","@lunora/scheduler: createCronTrigger() requires `schedule` and `fn`");s(r.schedule);const e=JSON.stringify({triggers:{crons:[r.schedule]}},void 0,2);return{crons:[r.schedule],dispatcher:{args:r.args??{},functionPath:r.fn.__lunoraRef},wranglerJsonc:e}};export{u as createCronTrigger};
@@ -0,0 +1,4 @@
1
+ import{LunoraError as c,isLunoraError as P}from"@lunora/errors";import{decodeWire as D,encodeWire as K}from"@lunora/wire";const J=9e5,C="DISPATCH_IN_PROGRESS",k="x-lunora-dispatch-declined",Q=(e,t,n)=>{if(t===void 0)return{dispose:()=>{},signal:e};const a=new AbortController,o=setTimeout(()=>{a.abort(n())},t);return{dispose:()=>{clearTimeout(o)},signal:a.signal}},l="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_",I=e=>{let t="",n=0;const a=e.length-2;for(;n<a;n+=3){const r=e[n]<<16|e[n+1]<<8|e[n+2];t+=l.charAt(r>>18&63)+l.charAt(r>>12&63)+l.charAt(r>>6&63)+l.charAt(r&63)}const o=e.length-n;if(o===1){const r=e[n]<<16;t+=l.charAt(r>>18&63)+l.charAt(r>>12&63)}else if(o===2){const r=e[n]<<16|e[n+1]<<8;t+=l.charAt(r>>18&63)+l.charAt(r>>12&63)+l.charAt(r>>6&63)}return t};new TextDecoder;const _=new TextEncoder,$="=",B=e=>{for(let t=0;t<e.length;t+=1)if(e.charCodeAt(t)>255)return!1;return!0},H=e=>I(_.encode(JSON.stringify(e))),W=e=>!e.startsWith($)&&B(e)?e:`${$}${I(_.encode(e))}`,j="/_lunora/scheduler/dispatch",F=3e4,G=e=>{let t=e.length;for(;t>0&&e[t-1]==="/";)t-=1;return e.slice(0,t)},E=Symbol("lunoraDispatchFailure"),q=Symbol("lunoraDispatchDecline"),X=Symbol("lunoraDispatchMessageId"),v=(e,t)=>(t!==void 0&&Object.defineProperty(e,X,{value:t}),e),z=(e,t)=>(Object.defineProperty(e,E,{value:!0}),v(e,t)),V=(e,t,n,a)=>{const{status:o}=t;try{const r=JSON.parse(n)?.error;if(typeof r=="object"&&r!==null&&typeof r.code=="string"){const{code:s,data:i,message:h}=r,f=z(new c(s,typeof h=="string"?h:void 0,{data:i,status:o}),a);return t.headers.get(k)==="1"&&Object.defineProperty(f,q,{value:!0}),f}}catch{}return v(new c("INTERNAL",`${e}: function dispatch failed (${String(o)}): ${n}`,{status:o}),a)},Y=e=>P(e)&&e[E]===!0,Z=e=>Y(e)&&e[q]===!0,R=J/1e3,ee=3,te=async(e,t)=>{const n=t.maxRetries??ee,a=typeof e.attempts=="number"&&e.attempts>n;let o="";if(a&&t.requeue!==void 0)try{return await t.requeue(R),e.ack(),"requeued"}catch(r){o=` Re-enqueueing a delayed copy failed (${r instanceof Error?r.message:String(r)}).`}return e.retry({delaySeconds:R}),a&&console.error(`${t.where}: message ${e.id} was declined (${C}) on its last delivery (attempt ${String(e.attempts)} of ${String(n+1)}) — its earlier delivery is still running the call.${o} It is retried anyway, but its retries are exhausted, so it will be dead-lettered if the queue has a deadLetterQueue and dropped if not. Raise max_retries so a slow call cannot exhaust it.`),"retried"},ne=(e,t,n)=>new c("INTERNAL",`${e}: function dispatch to "${t}" timed out after ${String(n)}ms`,{status:503}),re=e=>{const{label:t}=e,n=globalThis.fetch,a=e.fetchImpl??(typeof n=="function"?n.bind(globalThis):void 0);return async(o,r,s={})=>{if(typeof a!="function")throw new TypeError(`${t}: no fetch implementation available — pass fetchImpl or run on a platform with global fetch`);const i=()=>r??{},h=e.env.LUNORA_ORIGIN_URL;if(typeof h!="string"||h.length===0)throw new c("INTERNAL",`${t}: \`LUNORA_ORIGIN_URL\` must be set on the Worker env so a handler can call back into Lunora functions`);const f=e.env.LUNORA_ADMIN_TOKEN;if(typeof f!="string"||f.length===0)throw new c("INTERNAL",`${t}: \`LUNORA_ADMIN_TOKEN\` must be set on the Worker env to authenticate function dispatch`);const m=`${G(h)}${j}`,g={authorization:`Bearer ${f}`,"content-type":"application/json"};e.traceparent!==void 0&&e.traceparent.length>0&&(g.traceparent=e.traceparent),e.identity?.userId!==void 0&&(g["x-lunora-userid"]=W(e.identity.userId)),e.identity?.claims!==void 0&&(g["x-lunora-identity"]=H(e.identity.claims));const w=s.timeoutMs??F,N=Q(void 0,w,()=>new DOMException(`dispatch timed out after ${String(w)}ms`,"TimeoutError")),A=d=>{throw d instanceof Error&&d.name==="TimeoutError"?ne(t,o.__lunoraRef,w):d};let u;try{try{u=await a(m,{body:JSON.stringify({args:i(),functionPath:o.__lunoraRef,id:s.dedupId,shardKey:s.shardKey}),headers:g,method:"POST",signal:N.signal})}catch(p){return A(p)}if(!u.ok){let p;try{p=await u.text()}catch(M){return A(M)}throw V(t,u,p,s.messageId)}let d;try{d=await u.text()}catch(p){return A(p)}if(d.length===0)return;let y;try{y=JSON.parse(d)}catch{throw new c("INTERNAL",`${t}: function dispatch returned a non-JSON body (${String(u.status)}): ${d}`,{status:u.status})}if(y===null||typeof y!="object"||Array.isArray(y)||!("result"in y))throw new c("INTERNAL",`${t}: function dispatch returned a JSON body that is not a { result } envelope (${String(u.status)}): ${d}`,{status:u.status});return D(y.result)}finally{N.dispose()}}},ae="lunora.requeue.v1",O=new TextEncoder,T=async(e,t)=>crypto.subtle.importKey("raw",O.encode(e),{hash:"SHA-256",name:"HMAC"},!1,[t]),L=(e,t,n)=>O.encode(`${ae}
2
+ ${e}
3
+ ${t}
4
+ ${n}`),oe=e=>[...new Uint8Array(e)].map(t=>t.toString(16).padStart(2,"0")).join(""),se=/^[\da-f]{64}$/u,ie=async(e,t,n,a)=>oe(await crypto.subtle.sign("HMAC",await T(e,"sign"),L(t,n,a))),ce=async(e,t,n,a,o)=>{if(typeof o!="string"||!se.test(o))return!1;const r=Uint8Array.from({length:o.length/2},(s,i)=>Number.parseInt(o.slice(i*2,i*2+2),16));return crypto.subtle.verify("HMAC",await T(e,"verify"),r,L(t,n,a))},S=100,ue=3e5,b=e=>e===void 0?void 0:K(e),ye=e=>{if(!e.queue)throw new c("INTERNAL","@lunora/scheduler: `queue` (a Cloudflare Queue binding) is required");return{enqueue:async(a,o,r={})=>{const s={args:b(o),functionPath:a.__lunoraRef,shardKey:r.shardKey},i=r.delaySeconds===void 0?void 0:{delaySeconds:r.delaySeconds};await e.queue.send(s,i)},enqueueBatch:async(a,o)=>{if(a.length>S)throw new c("VALIDATION_ERROR",`@lunora/scheduler: enqueueBatch exceeds ${String(S)} (got ${String(a.length)}) — split across calls`);const r=a.map(s=>({body:{args:b(s.args),functionPath:s.ref.__lunoraRef,shardKey:s.shardKey}}));await e.queue.sendBatch(r,o)}}},de=e=>typeof e=="object"&&e!==null&&typeof e.functionPath=="string",U="scheduler",x=e=>JSON.stringify({args:e.args,functionPath:e.functionPath,shardKey:e.shardKey}),le=async(e,t,n)=>{const{requeuedFrom:a,requeueMac:o,...r}=e;return typeof a=="string"&&n!==void 0&&await ce(n,U,a,x(r),o).catch(()=>!1)?{id:a,job:r,requeued:!0}:{id:t,job:r,requeued:!1}},pe=e=>async t=>{await Promise.all(t.messages.map(async n=>{let a;try{if(!de(n.body))throw new c("INTERNAL","@lunora/scheduler: queue message body is not a QueueJob (missing functionPath)");a=await le(n.body,n.id,e.requeue?.secret),await e.dispatch(a.job,a.id),n.ack()}catch(o){if(Z(o)&&a!==void 0){const{requeue:r}=e,{id:s,job:i,requeued:h}=a;await te(n,{maxRetries:e.maxRetries,requeue:r===void 0||h?void 0:async f=>{const m=await ie(r.secret,U,s,x(i));await r.queue.send({...i,requeuedFrom:s,requeueMac:m},{delaySeconds:f})},where:`@lunora/scheduler: queue "${t.queue}"`});return}n.retry()}}))},ge=e=>{const t=re({env:{LUNORA_ADMIN_TOKEN:e.adminToken,LUNORA_ORIGIN_URL:e.originUrl},fetchImpl:e.fetchImpl,label:"@lunora/scheduler"}),n=e.timeoutMs??ue;return async(a,o)=>{await t({__lunoraRef:a.functionPath},a.args,{dedupId:o,messageId:o,shardKey:a.shardKey,timeoutMs:n})}};export{pe as createQueueConsumer,ye as createQueueWorkpool,ge as httpDispatcher};
@@ -0,0 +1 @@
1
+ import{LunoraError as w}from"@lunora/errors";import{encodeArgsOrThrow as g,decodeWire as A}from"@lunora/wire";import{a as p,c as d,g as f}from"./do-client-XQayNStI.mjs";import{isWorkflowReference as h}from"./isWorkflowReference-CT3tdefh.mjs";import N from"./assertScheduleDelay-CVFZanKn.mjs";import R from"./assertScheduleInstant-DcEKtg0c.mjs";const v=async e=>{const a=[];let o;for(;;){const c=await e(o);if(a.push(...Array.isArray(c.records)?c.records:[]),c.truncated!==!0||typeof c.cursor!="string"||c.cursor.length===0)return a;if(c.cursor===o)throw new Error("collectPages: the list did not advance its cursor — refusing to page forever");o=c.cursor}},x=e=>typeof e=="string"?e:(h(e)?e.className:e.__lunoraRef)??"<unknown>",m=e=>({...e,args:A(e.args)}),K=e=>{p(e);const a=async(s,r,n,t={})=>{const u=Math.ceil(s instanceof Date?s.getTime():s);R(u,Date.now(),"ctx.scheduler.runAt");const i={args:g("ctx.scheduler.runAt",x(r),n),id:t.id,instanceName:e.instanceName??"default",maxConcurrency:t.pool===void 0?void 0:t.maxConcurrency,pool:t.pool,retry:t.retry,scheduledFor:u,shardKey:t.shardKey};if(h(r)){if(typeof r.className!="string"||r.className.length===0)throw new w("INTERNAL","@lunora/scheduler: workflow/agent schedule target is missing its `className` — pass the generated `workflows.<name>` / `agents.<name>` reference");return(await d(e,"/schedule",{...i,workflow:r.className})).id}const y=typeof r=="string"?r:r.__lunoraRef;return(await d(e,"/schedule",{...i,functionPath:y})).id},o=async(s,r,n,t={})=>(N(s,"ctx.scheduler.runAfter"),a(Date.now()+s,r,n,t)),c=async s=>d(e,"/cancel",{id:s}),l=async s=>(await v(async n=>f(e,n===void 0?s:`${s}?cursor=${encodeURIComponent(n)}`))).map(n=>m(n));return{cancel:c,dead:async()=>l("/dead"),deadRetry:async s=>{const{retried:r}=await d(e,"/dead/retry",{id:s});return r===!0},get:async s=>{const r=await f(e,`/get?id=${encodeURIComponent(s)}`);return r.record===void 0?null:m(r.record)},list:async()=>l("/list"),runAfter:o,runAt:a}};export{K as default};
@@ -1 +1 @@
1
- import u from"./createScheduler-DpLHXye6.mjs";const i=t=>t?.at!==void 0?typeof t.at=="number"?t.at:t.at.getTime():Date.now()+(t?.delayMs??0),m=t=>{const r=u({instanceName:t.instanceName,jurisdiction:t.jurisdiction,namespace:t.namespace}),n=e=>({attempts:e.attempts??0,functionPath:e.functionPath??e.workflow??"",id:e.id,scheduledFor:e.scheduledFor});return{cancel:async e=>{const{cancelled:a}=await r.cancel(e);return a},deadLetter:{list:async()=>(await r.dead()).map(a=>n(a)),requeue:async e=>r.deadRetry(e)},list:async()=>(await r.list()).map(a=>n(a)),schedule:async(e,a,c)=>{const d=r.runAt,s=i(c);return{id:await d(s,e,a,{retry:c?.retry,shardKey:c?.shardKey}),scheduledFor:s}}}};export{m as createSchedulerHost};
1
+ import u from"./createScheduler-3p6oGhSi.mjs";const i=t=>t?.at!==void 0?typeof t.at=="number"?t.at:t.at.getTime():Date.now()+(t?.delayMs??0),m=t=>{const r=u({instanceName:t.instanceName,jurisdiction:t.jurisdiction,namespace:t.namespace}),n=e=>({attempts:e.attempts??0,functionPath:e.functionPath??e.workflow??"",id:e.id,scheduledFor:e.scheduledFor});return{cancel:async e=>{const{cancelled:a}=await r.cancel(e);return a},deadLetter:{list:async()=>(await r.dead()).map(a=>n(a)),requeue:async e=>r.deadRetry(e)},list:async()=>(await r.list()).map(a=>n(a)),schedule:async(e,a,c)=>{const d=r.runAt,s=i(c);return{id:await d(s,e,a,{retry:c?.retry,shardKey:c?.shardKey}),scheduledFor:s}}}};export{m as createSchedulerHost};
@@ -0,0 +1 @@
1
+ import{LunoraError as l}from"@lunora/errors";import{encodeArgsOrThrow as o}from"@lunora/wire";import{a as s,g as m,c as u}from"./do-client-XQayNStI.mjs";import y from"./assertScheduleDelay-CVFZanKn.mjs";const N=e=>{if(s(e),!Number.isInteger(e.maxConcurrency)||e.maxConcurrency<=0)throw new l("INTERNAL","@lunora/scheduler: `maxConcurrency` must be a positive integer");const a=typeof e.name=="string"&&e.name.length>0?e.name:"default";return{cancel:async r=>u(e,"/cancel",{id:r}),enqueue:async(r,t,n={})=>{const c=n.delayMs??0;return y(c,"workpool.enqueue"),u(e,"/schedule",{args:o("workpool.enqueue",r.__lunoraRef,t),functionPath:r.__lunoraRef,instanceName:e.instanceName??"default",maxConcurrency:e.maxConcurrency,pool:a,retry:n.retry,scheduledFor:Math.ceil(Date.now()+c),shardKey:n.shardKey})},name:a,status:async()=>m(e,`/pool?name=${encodeURIComponent(a)}`)}};export{N as default};
@@ -0,0 +1 @@
1
+ import{LunoraError as e}from"@lunora/errors";const a="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_",A=t=>{let n="",o=0;const i=t.length-2;for(;o<i;o+=3){const r=t[o]<<16|t[o+1]<<8|t[o+2];n+=a.charAt(r>>18&63)+a.charAt(r>>12&63)+a.charAt(r>>6&63)+a.charAt(r&63)}const c=t.length-o;if(c===1){const r=t[o]<<16;n+=a.charAt(r>>18&63)+a.charAt(r>>12&63)}else if(c===2){const r=t[o]<<16|t[o+1]<<8;n+=a.charAt(r>>18&63)+a.charAt(r>>12&63)+a.charAt(r>>6&63)}return n},h=/^\w[\w-]{0,63}$/u,l=/^-/u,u=t=>{if(t===void 0)return A(crypto.getRandomValues(new Uint8Array(12))).replace(l,"_");if(typeof t!="string"||!h.test(t))throw new e("INVALID_SCHEDULE_ID","@lunora/scheduler: `id` must be 1-64 characters of [A-Za-z0-9_-] and must not start with `-`; omit it to have one minted",{status:400});return t};export{u as r,A as t};
@@ -0,0 +1 @@
1
+ import{r as e}from"./resolve-schedule-id-B2ghxgFm.mjs";export{e as default};
@@ -0,0 +1,28 @@
1
+ /** True when `schedule` is a Cloudflare Cron Trigger expression (or a 6-field escape-hatch one). */
2
+ declare const isValidCronExpression: (schedule: string) => boolean;
3
+ /**
4
+ * A 6-field (seconds-leading) cron expression is legal generic cron grammar but
5
+ * not a Cloudflare Cron Trigger — the platform only understands the 5-field,
6
+ * minute-granularity form and rejects the rest at `wrangler deploy` with a
7
+ * message naming neither the job nor the file. Warn here instead of staying
8
+ * silent, without throwing: unlike the ergonomic `.interval()` form, the raw
9
+ * `.cron()` escape hatch is meant to accept cron grammar this module doesn't
10
+ * otherwise second-guess.
11
+ *
12
+ * Exported (not module-private) because it is the ONE place this advisory is
13
+ * written: the runtime `cronJobs()` builder reaches it via
14
+ * {@link assertValidCronExpression}, and `@lunora/codegen`'s static
15
+ * `discover/crons.ts` calls it directly after its own `isValidCronExpression`
16
+ * check — so a hand-authored 6-field `.cron()` warns whether it's discovered
17
+ * from source at build time or registered at runtime, with one shared message.
18
+ */
19
+ declare const warnIfSecondsLeading: (schedule: string, context: string) => void;
20
+ /**
21
+ * Assert a raw cron expression is well-formed, throwing the same shaped error
22
+ * both cron surfaces use. The `context` prefix lets callers name the offending
23
+ * job (`cron job "send digest"`) vs. the bare trigger. Well-formed but
24
+ * Cloudflare-incompatible (6-field) expressions pass but log a warning — see
25
+ * {@link warnIfSecondsLeading}.
26
+ */
27
+ declare const assertValidCronExpression: (schedule: string, context?: string) => void;
28
+ export { assertValidCronExpression as a, isValidCronExpression as i, warnIfSecondsLeading as w };
@@ -0,0 +1,28 @@
1
+ /** True when `schedule` is a Cloudflare Cron Trigger expression (or a 6-field escape-hatch one). */
2
+ declare const isValidCronExpression: (schedule: string) => boolean;
3
+ /**
4
+ * A 6-field (seconds-leading) cron expression is legal generic cron grammar but
5
+ * not a Cloudflare Cron Trigger — the platform only understands the 5-field,
6
+ * minute-granularity form and rejects the rest at `wrangler deploy` with a
7
+ * message naming neither the job nor the file. Warn here instead of staying
8
+ * silent, without throwing: unlike the ergonomic `.interval()` form, the raw
9
+ * `.cron()` escape hatch is meant to accept cron grammar this module doesn't
10
+ * otherwise second-guess.
11
+ *
12
+ * Exported (not module-private) because it is the ONE place this advisory is
13
+ * written: the runtime `cronJobs()` builder reaches it via
14
+ * {@link assertValidCronExpression}, and `@lunora/codegen`'s static
15
+ * `discover/crons.ts` calls it directly after its own `isValidCronExpression`
16
+ * check — so a hand-authored 6-field `.cron()` warns whether it's discovered
17
+ * from source at build time or registered at runtime, with one shared message.
18
+ */
19
+ declare const warnIfSecondsLeading: (schedule: string, context: string) => void;
20
+ /**
21
+ * Assert a raw cron expression is well-formed, throwing the same shaped error
22
+ * both cron surfaces use. The `context` prefix lets callers name the offending
23
+ * job (`cron job "send digest"`) vs. the bare trigger. Well-formed but
24
+ * Cloudflare-incompatible (6-field) expressions pass but log a warning — see
25
+ * {@link warnIfSecondsLeading}.
26
+ */
27
+ declare const assertValidCronExpression: (schedule: string, context?: string) => void;
28
+ export { assertValidCronExpression as a, isValidCronExpression as i, warnIfSecondsLeading as w };
@@ -1,3 +1 @@
1
- /** Standard cron expression (5- or 6-field) or a supported `@macro`, per `cron-parser`. */
2
- declare const isValidCronExpression: (schedule: string) => boolean;
3
- export { isValidCronExpression as default };
1
+ export { i as default } from "./packem_shared/validate-cron-full.d-Bemx45E8.mjs";
@@ -1,3 +1 @@
1
- /** Standard cron expression (5- or 6-field) or a supported `@macro`, per `cron-parser`. */
2
- declare const isValidCronExpression: (schedule: string) => boolean;
3
- export { isValidCronExpression as default };
1
+ export { i as default } from "./packem_shared/validate-cron-full.d-Bemx45E8.js";
@@ -1 +1 @@
1
- import{CronExpressionParser as t}from"cron-parser";const e=r=>{if(typeof r!="string"||r.trim()==="")return!1;try{return t.parse(r.trim()),!0}catch{return!1}};export{e as default};
1
+ import{isValidCronExpression as s}from"./packem_shared/assertValidCronExpression-Bs7NjxLJ.mjs";export{s as default};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/scheduler",
3
- "version": "1.0.0-alpha.126",
3
+ "version": "1.0.0-alpha.128",
4
4
  "description": "Scheduling for Lunora: runAfter / runAt and Cron Triggers via SchedulerDO",
5
5
  "keywords": [
6
6
  "cloudflare",
@@ -52,7 +52,8 @@
52
52
  "dependencies": {
53
53
  "@lunora/errors": ">=1.0.0-alpha.51 <2.0.0-0",
54
54
  "@lunora/platform": "1.0.0-alpha.64",
55
- "cron-parser": "5.10.1"
55
+ "@lunora/wire": "1.0.0-alpha.1",
56
+ "@visulima/cron-parser": "1.1.0"
56
57
  },
57
58
  "engines": {
58
59
  "node": "^22.15.0 || >=24.11.0"
@@ -1 +0,0 @@
1
- import{a as N}from"./base64-junLSktH.mjs";import R from"./resolveScheduleId-DuLCFI_l.mjs";const x="DISPATCH_IN_PROGRESS",E="x-lunora-dispatch-declined",P=(u,t=200,e)=>{const s=new Headers({"content-type":"application/json"});for(const[a,i]of Object.entries({}))s.set(a,i);return Response.json(u,{headers:s,status:t})},F="lunora-ping",L="lunora-pong",c="id:",m="retry:",g="dead:",y="pool:",w=100,$=5,b=3e4,k=1e3,T=6,C=9e5,M=999999999999999,U=15,A=u=>String(u).padStart(U,"0"),I=u=>Number.isInteger(u)&&u>0&&u<=M;class n{static indexKey(t,e){return`t:${A(t)}:${e}`}static json(t,e=200){return P(t,e)}static error(t,e,s){return n.json({error:{code:e,message:s}},t)}static resolveRetry(t){const e=t.retry,s=typeof e?.maxAttempts=="number"&&Number.isInteger(e.maxAttempts)&&e.maxAttempts>0?e.maxAttempts:$,a=typeof e?.baseMs=="number"&&Number.isFinite(e.baseMs)&&e.baseMs>=0?e.baseMs:b,i=e?.backoff==="linear"?"linear":"exponential",o=typeof e?.maxMs=="number"&&Number.isFinite(e.maxMs)&&e.maxMs>=0?e.maxMs:void 0;return{backoff:i,baseMs:a,maxAttempts:s,maxMs:o}}static normalizeConcurrency(t,e){return typeof t=="number"&&Number.isInteger(t)&&t>0?t:e}static normalizeRetry(t){if(typeof t!="object"||t===null)return;const e=t,s={};return typeof e.maxAttempts=="number"&&Number.isInteger(e.maxAttempts)&&e.maxAttempts>0&&(s.maxAttempts=e.maxAttempts),typeof e.baseMs=="number"&&Number.isFinite(e.baseMs)&&e.baseMs>=0&&(s.baseMs=e.baseMs),(e.backoff==="exponential"||e.backoff==="linear")&&(s.backoff=e.backoff),typeof e.maxMs=="number"&&Number.isFinite(e.maxMs)&&e.maxMs>=0&&(s.maxMs=e.maxMs),Object.keys(s).length===0?void 0:s}static releaseSlot(t,e){if(t.inFlightIds===void 0)return{...t,inFlight:Math.max(0,t.inFlight-1)};const s=t.inFlightIds.filter(a=>a!==e);return{...t,inFlight:s.length,inFlightIds:s}}static releaseFirstSlot(t){if(t.inFlightIds===void 0)return{...t,inFlight:Math.max(0,t.inFlight-1)};const e=t.inFlightIds.slice(0,Math.max(0,t.inFlightIds.length-1));return{...t,inFlight:e.length,inFlightIds:e}}static resolveScheduleTarget(t){const e=typeof t?.functionPath=="string"&&t.functionPath.length>0?t.functionPath:void 0,s=typeof t?.workflow=="string"&&t.workflow.length>0?t.workflow:void 0;if(!(e===void 0&&s===void 0))return{functionPath:e,workflow:s}}state;env;reindexed=!1;poolLock=Promise.resolve();activeLeases=new Map;cancelledLeases=new Set;constructor(t,e){this.state=t,this.env=e,this.armWebSocketKeepalive()}async fetch(t){await this.reindexOrphanedRecords();const e=new URL(t.url);if(e.pathname==="/ws"&&t.headers.get("Upgrade")==="websocket")return this.handleWebSocketUpgrade();switch(`${t.method} ${e.pathname}`){case"GET /dead":return this.handleDeadList(e);case"GET /get":return this.handleGet(e);case"GET /list":return this.handleList(e);case"GET /pool":return this.handlePoolStatus(e);case"GET /status":return this.handleStatus();case"POST /cancel":return this.handleCancel(t);case"POST /complete":return this.handleComplete(t);case"POST /dead/cancel":return this.handleDeadCancel(t);case"POST /dead/retry":return this.handleDeadRetry(t);case"POST /schedule":return this.handleSchedule(t)}return P({error:{code:"NOT_FOUND"}},404)}async alarm(){await this.reindexOrphanedRecords();const t=Date.now(),e=[],s=new Set,a=await this.state.storage.list({end:`t:${A(t)}:~`,limit:100,prefix:"t:"}),i=[];for(const[r,d]of a.entries()){const l=Number.parseInt(r.slice(2,r.indexOf(":",2)),10);Number.isFinite(l)&&l<=t&&i.push({indexKey:r,recordId:d})}const o=await Promise.all(i.map(r=>this.state.storage.get(`${c}${r.recordId}`)));for(const[r,{indexKey:d,recordId:l}]of i.entries()){const h=o[r];h&&s.has(l)?await this.state.storage.delete(d):h?(s.add(l),e.push({claimKey:d,record:h})):await this.state.storage.delete(d)}try{await this.drainDue(e)}finally{await this.rescheduleAlarm()}e.length>0&&await this.broadcastChange()}async dispatch(t){const e=typeof this.env.LUNORA_ORIGIN_URL=="string"&&this.env.LUNORA_ORIGIN_URL.length>0?this.env.LUNORA_ORIGIN_URL:void 0;if(!e)return!1;const s=JSON.stringify({args:t.args,functionPath:t.functionPath,id:t.id,instanceName:t.instanceName,pool:t.pool,scheduledFor:t.scheduledFor,shardKey:t.shardKey,workflow:t.workflow});try{const a={"content-type":"application/json"},i=await this.signDispatch(s);i!==void 0?a["x-lunora-scheduler-signature"]=i:typeof this.env.LUNORA_ADMIN_TOKEN=="string"&&this.env.LUNORA_ADMIN_TOKEN.length>0&&(a.authorization=`Bearer ${this.env.LUNORA_ADMIN_TOKEN}`);const o=await fetch(`${e}/_lunora/scheduler/dispatch`,{body:s,headers:a,method:"POST"});return o.status===409&&o.headers.get(E)==="1"&&await o.json().then(d=>d?.error?.code===x,()=>!1)?"in-progress":o.ok}catch{return!1}}armWebSocketKeepalive(){const t=this.state.setWebSocketAutoResponse;typeof t!="function"||typeof WebSocketRequestResponsePair>"u"||t.call(this.state,new WebSocketRequestResponsePair(F,L))}async drainDue(t){const e=[...t],s=[],a=Math.min(T,e.length);for(let i=0;i<a;i+=1)s.push((async()=>{for(let o=e.shift();o!==void 0;o=e.shift())await this.drainRecordGuarded(o.record,o.claimKey)})());await Promise.all(s)}async withPoolLock(t){const e=this.poolLock.then(t,t);return this.poolLock=e.then(()=>{},()=>{}),e}async drainRecordGuarded(t,e){const s=n.indexKey(Date.now()+C,t.id);try{const a=await this.state.storage.get(`${c}${t.id}`);if(a===void 0){await this.state.storage.delete(e);return}if(JSON.stringify(a)!==JSON.stringify(t))return;await this.state.storage.delete(e),await this.state.storage.put(s,t.id),this.activeLeases.set(t.id,s),await this.drainRecord(t)}catch{try{if(this.cancelledLeases.has(t.id)){await this.state.storage.delete(s);return}if(await this.state.storage.get(`${g}${t.id}`)!==void 0){await this.state.storage.delete(s),await this.state.storage.delete([`${m}${t.id}`,`${c}${t.id}`]);return}await this.state.storage.put(e,t.id),await this.state.storage.delete(s)}catch{}return}finally{this.activeLeases.delete(t.id),this.cancelledLeases.delete(t.id)}try{await this.state.storage.delete(s)}catch{}}async drainRecord(t){if(!await this.reservePoolSlot(t))return!1;const s=await this.dispatch(t),a=s===!0,i=t.pool;if(!a&&i!==void 0&&await this.withPoolLock(async()=>{const o=await this.loadPool(i);await this.savePool(i,n.releaseSlot(o,t.id))}),this.cancelledLeases.has(t.id))return a;if(a){try{await this.state.storage.delete([`${c}${t.id}`,`${m}${t.id}`])}catch{}return!0}return await this.recordRetry(t,s==="in-progress"),!1}async reservePoolSlot(t){const e=t.pool;if(e===void 0)return!0;const s=await this.withPoolLock(async()=>{const a=await this.loadPool(e);if(a.inFlight>=a.maxConcurrency)return!1;const i=a.inFlightIds??[];return i.includes(t.id)||i.push(t.id),a.inFlightIds=i,a.inFlight=i.length,await this.savePool(e,a),!0});return s||await this.requeuePooled(t),s}async handleWebSocketUpgrade(){if(this.state.acceptWebSocket===void 0)return n.error(501,"WS_UNSUPPORTED","WebSocket subscriptions are not supported in this runtime");const t=new WebSocketPair,e=t[0],s=t[1];this.state.acceptWebSocket(s);const a=await this.listPage(c,w);return s.send(JSON.stringify({records:a.records,truncated:a.truncated,type:"jobs"})),new Response(null,{status:101,webSocket:e})}async broadcastChange(){const t=this.state.getWebSockets?.();if(t===void 0||t.length===0)return;const{records:e,truncated:s}=await this.listPage(c,w),a=JSON.stringify({records:e,truncated:s,type:"jobs"});for(const i of t)try{i.send(a)}catch{}}async listPage(t,e,s){const a=await this.state.storage.list({limit:e+1,prefix:t,...s===void 0?{}:{startAfter:s}}),i=[...a.keys()],o=[...a.values()],r=o.length>e;return r?{cursor:i[e-1],records:o.slice(0,e),truncated:r}:{records:o,truncated:r}}async forEachPage(t,e,s=w){let a;for(;;){const i=await this.state.storage.list(a===void 0?{limit:s,prefix:t}:{limit:s,prefix:t,startAfter:a});if(i.size===0)break;for(const[r,d]of i.entries())e(d,r);if(a=[...i.keys()].at(-1),i.size<s)break}}async signDispatch(t){const e=typeof this.env.LUNORA_SCHEDULER_SECRET=="string"?this.env.LUNORA_SCHEDULER_SECRET:void 0;if(!e||e.length===0)return;const s=new TextEncoder,a=await crypto.subtle.importKey("raw",s.encode(e),{hash:"SHA-256",name:"HMAC"},!1,["sign"]),i=await crypto.subtle.sign("HMAC",a,s.encode(t));return N(new Uint8Array(i))}async recordRetry(t,e=!1){if(this.cancelledLeases.has(t.id))return;const s=(t.attempts??0)+(e?0:1),a=(t.attempts??0)+1,{backoff:i,baseMs:o,maxAttempts:r,maxMs:d}=n.resolveRetry(t),l=i==="linear"?o*a:o*2**(a-1),h=d===void 0?l:Math.min(l,d),f=e?Math.max(h,b):h,p=Math.round(Date.now()+f);if(s>r){await this.parkDead(t,s,`after ${String(s)} attempts`);return}if(!I(p)){await this.parkDead(t,s,`at attempt ${String(s)}: the retry backoff exceeded the largest schedulable time`);return}const v={...t,attempts:s,scheduledFor:p};await this.state.storage.put({[`${c}${t.id}`]:v,[`${m}${t.id}`]:v,[n.indexKey(p,t.id)]:t.id})}async parkDead(t,e,s){await this.state.storage.put(`${g}${t.id}`,{...t,attempts:e}),await this.state.storage.delete([`${m}${t.id}`,`${c}${t.id}`]),console.warn(`@lunora/scheduler: job "${t.id}" (${t.functionPath??t.workflow??"unknown"}) parked in dead-letter ${s}`)}async loadPool(t,e){const s=await this.state.storage.get(`${y}${t}`);return s!==void 0?Array.isArray(s.inFlightIds)?{inFlight:s.inFlightIds.length,inFlightIds:[...s.inFlightIds],maxConcurrency:s.maxConcurrency}:{inFlight:Math.max(0,s.inFlight),maxConcurrency:s.maxConcurrency}:{inFlight:0,inFlightIds:[],maxConcurrency:n.normalizeConcurrency(e,1)}}async savePool(t,e){await this.state.storage.put(`${y}${t}`,e)}async requeuePooled(t){if(this.cancelledLeases.has(t.id))return;const e=Date.now()+k,s={...t,scheduledFor:e};await this.state.storage.put({[`${c}${t.id}`]:s,[n.indexKey(e,t.id)]:t.id})}async handleComplete(t){const e=await t.json().catch(()=>{}),s=typeof e?.pool=="string"&&e.pool.length>0?e.pool:void 0,a=typeof e?.id=="string"&&e.id.length>0?e.id:void 0;if(s===void 0)return n.error(400,"INVALID_INPUT","pool is required");const i=await this.withPoolLock(async()=>{const o=await this.loadPool(s),r=a===void 0?n.releaseFirstSlot(o):n.releaseSlot(o,a);return await this.savePool(s,r),r});return await this.armAlarmIfEarlier(Date.now()),n.json({inFlight:i.inFlight})}async handlePoolStatus(t){const e=t.searchParams.get("name");if(e===null||e.length===0)return n.error(400,"INVALID_INPUT","name is required");const s=await this.loadPool(e);let a=0;return await this.forEachPage(c,i=>{i.pool===e&&(a+=1)}),n.json({inFlight:s.inFlight,maxConcurrency:s.maxConcurrency,queued:a})}async handleStatus(){const t=new Map;await this.forEachPage(c,o=>{o.pool!==void 0&&t.set(o.pool,(t.get(o.pool)??0)+1)});const e=[];let s=0,a=0;await this.forEachPage(y,(o,r)=>{const d=r.slice(y.length),l=Math.max(0,o.inFlight),h=t.get(d)??0;e.push({inFlight:l,maxConcurrency:o.maxConcurrency,name:d,queued:h}),s+=h,a+=l});const i={backlog:s,inFlight:a,pools:e};return n.json(i)}async persistPoolCap(t,e){const s=await this.loadPool(t,e);await this.savePool(t,{inFlight:s.inFlight,...s.inFlightIds===void 0?{}:{inFlightIds:s.inFlightIds},maxConcurrency:n.normalizeConcurrency(e,s.maxConcurrency)})}async idConflict(t){if(await this.state.storage.get(`${c}${t}`)!==void 0)return n.error(409,"DUPLICATE_SCHEDULE_ID",`a job with id "${t}" is already scheduled — cancel it first, or schedule under a different id`);if(await this.state.storage.get(`${g}${t}`)!==void 0)return n.error(409,"DUPLICATE_SCHEDULE_ID",`id "${t}" is held by a dead-letter record — retry or cancel it (POST /dead/retry, POST /dead/cancel) first, or schedule under a different id`)}async resolveId(t){let e;try{e=R(t)}catch(s){return n.error(400,"INVALID_SCHEDULE_ID",s instanceof Error?s.message:"invalid `id`")}return t===void 0?e:await this.idConflict(e)??e}async handleSchedule(t){const e=await t.json().catch(()=>{}),s=n.resolveScheduleTarget(e);if(!e||s===void 0)return n.error(400,"INVALID_INPUT","functionPath or workflow is required");const{functionPath:a,workflow:i}=s;if(typeof e.scheduledFor!="number"||!I(e.scheduledFor))return n.error(400,"INVALID_INPUT","scheduledFor must be a positive integer epoch-millisecond number no greater than 999999999999999");if(typeof this.env.LUNORA_ORIGIN_URL!="string"||this.env.LUNORA_ORIGIN_URL.length===0)return n.error(500,"ORIGIN_NOT_CONFIGURED","LUNORA_ORIGIN_URL env binding must be set on the SchedulerDO");const o=typeof e.pool=="string"&&e.pool.length>0?e.pool:void 0,r=typeof e.instanceName=="string"&&e.instanceName.length>0?e.instanceName:void 0,d=n.normalizeRetry(e.retry),l=await this.resolveId(e.id);if(l instanceof Response)return l;const h=l,f={args:e.args??{},enqueuedAt:Date.now(),id:h,...a===void 0?{}:{functionPath:a},...r===void 0?{}:{instanceName:r},...o===void 0?{}:{pool:o},...d===void 0?{}:{retry:d},scheduledFor:e.scheduledFor,shardKey:e.shardKey,...i===void 0?{}:{workflow:i}};return o!==void 0&&await this.persistPoolCap(o,e.maxConcurrency),await this.state.storage.put(`${c}${h}`,f),await this.state.storage.put(n.indexKey(f.scheduledFor,h),h),await this.armAlarmIfEarlier(f.scheduledFor),await this.broadcastChange(),n.json({id:h,scheduledFor:f.scheduledFor})}async handleCancel(t){const e=await t.json().catch(()=>{});if(!e?.id)return n.error(400,"INVALID_INPUT","id is required");const s=await this.state.storage.get(`${c}${e.id}`);return s?(await this.removeRecord(s),await this.rescheduleAlarm(),await this.broadcastChange(),n.json({cancelled:!0})):n.json({cancelled:!1})}async handleList(t){const{cursor:e,records:s,truncated:a}=await this.listPage(c,w,t.searchParams.get("cursor")??void 0);return n.json({cursor:e,records:s,truncated:a})}async handleDeadList(t){const{cursor:e,records:s,truncated:a}=await this.listPage(g,w,t.searchParams.get("cursor")??void 0);return n.json({cursor:e,records:s,truncated:a})}async handleDeadRetry(t){const e=await t.json().catch(()=>{});if(typeof e?.id!="string"||e.id.length===0)return n.error(400,"INVALID_INPUT","id is required");const s=await this.state.storage.get(`${g}${e.id}`);if(s===void 0)return n.json({retried:!1});const a=Date.now(),i={...s,attempts:0,scheduledFor:a};return await this.state.storage.put(`${c}${s.id}`,i),await this.state.storage.put(n.indexKey(a,s.id),s.id),await this.state.storage.delete(`${g}${s.id}`),await this.armAlarmIfEarlier(a),await this.broadcastChange(),n.json({id:s.id,retried:!0,scheduledFor:a})}async handleDeadCancel(t){const e=await t.json().catch(()=>{});if(typeof e?.id!="string"||e.id.length===0)return n.error(400,"INVALID_INPUT","id is required");const s=await this.state.storage.delete(`${g}${e.id}`);return n.json({removed:!!s})}async handleGet(t){const e=t.searchParams.get("id");if(e===null||e.length===0)return n.error(400,"INVALID_INPUT","id is required");const s=await this.state.storage.get(`${c}${e}`);return n.json(s===void 0?{}:{record:s})}async removeRecord(t){const e=[`${c}${t.id}`,n.indexKey(t.scheduledFor,t.id),`${m}${t.id}`],s=this.activeLeases.get(t.id);s!==void 0&&(e.push(s),this.cancelledLeases.add(t.id)),await this.state.storage.delete(e)}async armAlarmIfEarlier(t){const e=await this.state.storage.getAlarm();(e===null||t<e)&&await this.state.storage.setAlarm(t)}async reindexOrphanedRecords(){if(this.reindexed)return;this.reindexed=!0;const t=new Set;await this.forEachPage("t:",s=>{t.add(s)});const e=[];await this.forEachPage(c,s=>{!t.has(s.id)&&I(s.scheduledFor)&&e.push(s)});for(const s of e)await this.state.storage.put(n.indexKey(s.scheduledFor,s.id),s.id),await this.armAlarmIfEarlier(s.scheduledFor);if(await this.state.storage.getAlarm()===null){const s=await this.earliestPendingTime();s!==void 0&&Number.isFinite(s)&&await this.state.storage.setAlarm(s)}}async earliestPendingTime(){const e=(await this.state.storage.list({limit:1,prefix:"t:"})).entries().next();if(e.done)return;const[s]=e.value;return Number.parseInt(s.slice(2,s.indexOf(":",2)),10)}async rescheduleAlarm(){const t=await this.earliestPendingTime();if(t===void 0){await this.state.storage.deleteAlarm();return}Number.isFinite(t)&&await this.state.storage.setAlarm(t)}}export{C as DISPATCH_LEASE_MS,T as MAX_CONCURRENT_DISPATCHES,$ as MAX_RETRY_ATTEMPTS,M as MAX_SCHEDULED_FOR_MS,b as RETRY_BASE_DELAY_MS,n as SchedulerDO};
@@ -1 +0,0 @@
1
- import{LunoraError as f}from"@lunora/errors";const E=new Set(["@annually","@daily","@hourly","@monthly","@weekly","@yearly"]),N=["JAN","FEB","MAR","APR","MAY","JUN","JUL","AUG","SEP","OCT","NOV","DEC"],T=["SUN","MON","TUE","WED","THU","FRI","SAT"],d={max:59,min:0},A={max:23,min:0},_={allowL:!0,max:31,min:1},h={max:12,min:1,nameBase:1,names:N},x={allowL:!0,allowNth:!0,max:7,min:0,nameBase:0,names:T},c=[d,A,_,h,x],L=[d,...c],w=/\s+/u,S=/^\d{1,3}$/u,R=/^[1-9]\d{0,2}$/u,v=/^(\d{1,3})#([1-5])$/u,O=/^(\d{1,3})L$/u,o=(n,t)=>{if(S.test(n)){const e=Number(n);return e>=t.min&&e<=t.max?e:void 0}const r=t.names?.indexOf(n.toUpperCase())??-1;return r===-1?void 0:r+(t.nameBase??0)},g=(n,t)=>{if(o(n,t)!==void 0)return!0;if(n==="L")return t.allowL===!0&&t.allowNth!==!0;const r=O.exec(n);if(t.allowL===!0&&r!==null)return o(r[1],t)!==void 0;const e=v.exec(n);return t.allowNth===!0&&e!==null&&o(e[1],t)!==void 0},I=(n,t)=>{if(n==="?")return!0;const[r="",e,...i]=n.split("/");if(i.length>0||e!==void 0&&!R.test(e))return!1;if(r==="*")return!0;const[s="",l,...m]=r.split("-");if(m.length>0)return!1;if(l===void 0)return g(s,t);const a=o(s,t),u=o(l,t);return a!==void 0&&u!==void 0&&a<=u},y=(n,t)=>n.split(",").every(r=>I(r,t)),C=n=>{if(typeof n!="string")return!1;const t=n.trim();if(E.has(t))return!0;const r=t.split(w),e=r.length===6?L:c;return(r.length===5||r.length===6)&&r.every((i,s)=>y(i,e[s]))},D=n=>C(n),F=/\s+/u,M=(n,t)=>{n.trim().split(F).length===6&&console.warn(`@lunora/scheduler: ${t} "${n}" is a 6-field (seconds-leading) cron expression — Cloudflare Cron Triggers only support the standard 5-field form and will reject this at \`wrangler deploy\`. Drop the seconds field, or use ctx.scheduler.runAfter/runAt for sub-minute work.`)},p=(n,t="cron expression")=>{if(!D(n))throw new f("INTERNAL",`@lunora/scheduler: invalid ${t} "${n}" — expected a standard 5- or 6-field cron expression (e.g. "0 * * * *")`);M(n,t)};export{p as assertValidCronExpression,D as isValidCronExpression,M as warnIfSecondsLeading};
@@ -1 +0,0 @@
1
- const h=t=>{let r="";for(let a=0;a<t.length;a+=32768)r+=String.fromCharCode(...t.subarray(a,a+32768));return btoa(r)},l=t=>{const r=atob(t),n=new Uint8Array(r.length);for(let a=0;a<r.length;a+=1)n[a]=r.codePointAt(a)??0;return n},c="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_",i=t=>{let r="",n=0;const a=t.length-2;for(;n<a;n+=3){const o=t[n]<<16|t[n+1]<<8|t[n+2];r+=c.charAt(o>>18&63)+c.charAt(o>>12&63)+c.charAt(o>>6&63)+c.charAt(o&63)}const e=t.length-n;if(e===1){const o=t[n]<<16;r+=c.charAt(o>>18&63)+c.charAt(o>>12&63)}else if(e===2){const o=t[n]<<16|t[n+1]<<8;r+=c.charAt(o>>18&63)+c.charAt(o>>12&63)+c.charAt(o>>6&63)}return r};export{i as a,l as f,h as t};
@@ -1,4 +0,0 @@
1
- import{LunoraError as d,isLunoraError as k}from"@lunora/errors";import{a as B}from"./wire-codec-BqRJjIKh.mjs";const Q=9e5,F="DISPATCH_IN_PROGRESS",H="x-lunora-dispatch-declined",W=(e,t,a)=>{if(t===void 0)return{dispose:()=>{},signal:e};const o=new AbortController,r=setTimeout(()=>{o.abort(a())},t);return{dispose:()=>{clearTimeout(r)},signal:o.signal}},G=e=>{let t="";for(let a=0;a<e.length;a+=32768)t+=String.fromCharCode(...e.subarray(a,a+32768));return btoa(t)},V=e=>{const t=atob(e),a=new Uint8Array(t.length);for(let o=0;o<t.length;o+=1)a[o]=t.codePointAt(o)??0;return a},h="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_",q=e=>{let t="",a=0;const o=e.length-2;for(;a<o;a+=3){const n=e[a]<<16|e[a+1]<<8|e[a+2];t+=h.charAt(n>>18&63)+h.charAt(n>>12&63)+h.charAt(n>>6&63)+h.charAt(n&63)}const r=e.length-a;if(r===1){const n=e[a]<<16;t+=h.charAt(n>>18&63)+h.charAt(n>>12&63)}else if(r===2){const n=e[a]<<16|e[a+1]<<8;t+=h.charAt(n>>18&63)+h.charAt(n>>12&63)+h.charAt(n>>6&63)}return t};new TextDecoder;const U=new TextEncoder,N="=",X=e=>{for(let t=0;t<e.length;t+=1)if(e.charCodeAt(t)>255)return!1;return!0},z=e=>q(U.encode(JSON.stringify(e))),Y=e=>!e.startsWith(N)&&X(e)?e:`${N}${q(U.encode(e))}`,Z="$lunora.wire$",R=64,$=1024,v="__proto__",I={BigInt64Array,BigUint64Array,Float32Array,Float64Array,Int8Array,Int16Array,Int32Array,Uint8Array,Uint8ClampedArray,Uint16Array,Uint32Array},O={Error,EvalError,RangeError,ReferenceError,SyntaxError,TypeError,URIError},l=(e,t=0)=>{if(t>R)throw new RangeError(`wire-codec: value nesting exceeds the ${R}-level limit`);if(e===null||typeof e!="object")return e;if(Array.isArray(e)){if(e[0]===Z)switch(e[1]){case"-inf":return-1/0;case"arr":return e[2].map(r=>l(r,t+1));case"bigint":{const r=e[2];if(typeof r!="string"||r.length>$||!/^-?\d+$/.test(r))throw new RangeError(`wire-codec: invalid or over-long bigint (max ${$} digits)`);return BigInt(r)}case"date":{const r=l(e[2],t+1);if(typeof r!="number")throw new TypeError("wire-codec: malformed date — epoch must be a number");return new Date(r)}case"map":{const r=e[2];return new Map(r.map(n=>{if(!Array.isArray(n)||n.length!==2)throw new TypeError("wire-codec: malformed map entry — expected a [key, value] pair");return[l(n[0],t+1),l(n[1],t+1)]}))}case"set":return new Set(e[2].map(r=>l(r,t+1)));case"url":{const r=e[2];if(typeof r!="string")throw new TypeError("wire-codec: malformed url — href must be a string");return new URL(r)}case"error":{const r=e[2],n=e[3];if(typeof r!="string"||typeof n!="string")throw new TypeError("wire-codec: malformed error — name and message must be strings");const s=(Object.hasOwn(O,r)?O[r]:void 0)??Error,c=new s(n);c.name!==r&&Object.defineProperty(c,"name",{configurable:!0,value:r,writable:!0});const i=l(e[4],t+1);if(i===null||typeof i!="object"||Array.isArray(i))throw new TypeError("wire-codec: malformed error — props must be an object");for(const u of Object.keys(i))u===v?Object.defineProperty(c,u,{configurable:!0,enumerable:!0,value:i[u],writable:!0}):c[u]=i[u];return e.length>5&&Object.defineProperty(c,"cause",{configurable:!0,value:l(e[5],t+1),writable:!0}),c}case"bytes":{const r=e[2];if(typeof r!="string")throw new TypeError("wire-codec: malformed bytes — payload must be a base64 string");const n=V(r);if(G(n)!==r)throw new TypeError("wire-codec: malformed bytes — payload must be canonical padded base64");const s=e[3]??"Uint8Array";if(s==="ArrayBuffer")return n.buffer.byteLength===n.byteLength?n.buffer:n.slice().buffer;const c=Object.hasOwn(I,s)?I[s]:void 0;return c?new c(n.slice().buffer):n}case"inf":return 1/0;case"nan":return Number.NaN;case"undefined":return;default:return e.map(r=>l(r,t+1))}return e.map(r=>l(r,t+1))}const a=e,o={};for(const r of Object.keys(a)){const n=l(a[r],t+1);r===v?Object.defineProperty(o,r,{configurable:!0,enumerable:!0,value:n,writable:!0}):o[r]=n}return o},ee="/_lunora/scheduler/dispatch",te=3e4,re=e=>{let t=e.length;for(;t>0&&e[t-1]==="/";)t-=1;return e.slice(0,t)},L=Symbol("lunoraDispatchFailure"),P=Symbol("lunoraDispatchDecline"),ne=Symbol("lunoraDispatchMessageId"),x=(e,t)=>(t!==void 0&&Object.defineProperty(e,ne,{value:t}),e),ae=(e,t)=>(Object.defineProperty(e,L,{value:!0}),x(e,t)),oe=(e,t,a,o)=>{const{status:r}=t;try{const n=JSON.parse(a)?.error;if(typeof n=="object"&&n!==null&&typeof n.code=="string"){const{code:s,data:c,message:i}=n,u=ae(new d(s,typeof i=="string"?i:void 0,{data:c,status:r}),o);return t.headers.get(H)==="1"&&Object.defineProperty(u,P,{value:!0}),u}}catch{}return x(new d("INTERNAL",`${e}: function dispatch failed (${String(r)}): ${a}`,{status:r}),o)},se=e=>k(e)&&e[L]===!0,ce=e=>se(e)&&e[P]===!0,_=Q/1e3,ie=3,ue=async(e,t)=>{const a=t.maxRetries??ie,o=typeof e.attempts=="number"&&e.attempts>a;let r="";if(o&&t.requeue!==void 0)try{return await t.requeue(_),e.ack(),"requeued"}catch(n){r=` Re-enqueueing a delayed copy failed (${n instanceof Error?n.message:String(n)}).`}return e.retry({delaySeconds:_}),o&&console.error(`${t.where}: message ${e.id} was declined (${F}) on its last delivery (attempt ${String(e.attempts)} of ${String(a+1)}) — its earlier delivery is still running the call.${r} It is retried anyway, but its retries are exhausted, so it will be dead-lettered if the queue has a deadLetterQueue and dropped if not. Raise max_retries so a slow call cannot exhaust it.`),"retried"},le=(e,t,a)=>new d("INTERNAL",`${e}: function dispatch to "${t}" timed out after ${String(a)}ms`,{status:503}),de=e=>{const{label:t}=e,a=globalThis.fetch,o=e.fetchImpl??(typeof a=="function"?a.bind(globalThis):void 0);return async(r,n,s={})=>{if(typeof o!="function")throw new TypeError(`${t}: no fetch implementation available — pass fetchImpl or run on a platform with global fetch`);const c=()=>n??{},i=e.env.LUNORA_ORIGIN_URL;if(typeof i!="string"||i.length===0)throw new d("INTERNAL",`${t}: \`LUNORA_ORIGIN_URL\` must be set on the Worker env so a handler can call back into Lunora functions`);const u=e.env.LUNORA_ADMIN_TOKEN;if(typeof u!="string"||u.length===0)throw new d("INTERNAL",`${t}: \`LUNORA_ADMIN_TOKEN\` must be set on the Worker env to authenticate function dispatch`);const w=`${re(i)}${ee}`,m={authorization:`Bearer ${u}`,"content-type":"application/json"};e.traceparent!==void 0&&e.traceparent.length>0&&(m.traceparent=e.traceparent),e.identity?.userId!==void 0&&(m["x-lunora-userid"]=Y(e.identity.userId)),e.identity?.claims!==void 0&&(m["x-lunora-identity"]=z(e.identity.claims));const b=s.timeoutMs??te,E=W(void 0,b,()=>new DOMException(`dispatch timed out after ${String(b)}ms`,"TimeoutError")),A=y=>{throw y instanceof Error&&y.name==="TimeoutError"?le(t,r.__lunoraRef,b):y};let f;try{try{f=await o(w,{body:JSON.stringify({args:c(),functionPath:r.__lunoraRef,id:s.dedupId,shardKey:s.shardKey}),headers:m,method:"POST",signal:E.signal})}catch(g){return A(g)}if(!f.ok){let g;try{g=await f.text()}catch(J){return A(J)}throw oe(t,f,g,s.messageId)}let y;try{y=await f.text()}catch(g){return A(g)}if(y.length===0)return;let p;try{p=JSON.parse(y)}catch{throw new d("INTERNAL",`${t}: function dispatch returned a non-JSON body (${String(f.status)}): ${y}`,{status:f.status})}if(p===null||typeof p!="object"||Array.isArray(p)||!("result"in p))throw new d("INTERNAL",`${t}: function dispatch returned a JSON body that is not a { result } envelope (${String(f.status)}): ${y}`,{status:f.status});return l(p.result)}finally{E.dispose()}}},fe="lunora.requeue.v1",M=new TextEncoder,j=async(e,t)=>crypto.subtle.importKey("raw",M.encode(e),{hash:"SHA-256",name:"HMAC"},!1,[t]),D=(e,t,a)=>M.encode(`${fe}
2
- ${e}
3
- ${t}
4
- ${a}`),ye=e=>[...new Uint8Array(e)].map(t=>t.toString(16).padStart(2,"0")).join(""),he=/^[\da-f]{64}$/u,pe=async(e,t,a,o)=>ye(await crypto.subtle.sign("HMAC",await j(e,"sign"),D(t,a,o))),ge=async(e,t,a,o,r)=>{if(typeof r!="string"||!he.test(r))return!1;const n=Uint8Array.from({length:r.length/2},(s,c)=>Number.parseInt(r.slice(c*2,c*2+2),16));return crypto.subtle.verify("HMAC",await j(e,"verify"),n,D(t,a,o))},S=100,me=3e5,T=e=>e===void 0?void 0:B(e),Ne=e=>{if(!e.queue)throw new d("INTERNAL","@lunora/scheduler: `queue` (a Cloudflare Queue binding) is required");return{enqueue:async(o,r,n={})=>{const s={args:T(r),functionPath:o.__lunoraRef,shardKey:n.shardKey},c=n.delaySeconds===void 0?void 0:{delaySeconds:n.delaySeconds};await e.queue.send(s,c)},enqueueBatch:async(o,r)=>{if(o.length>S)throw new d("VALIDATION_ERROR",`@lunora/scheduler: enqueueBatch exceeds ${String(S)} (got ${String(o.length)}) — split across calls`);const n=o.map(s=>({body:{args:T(s.args),functionPath:s.ref.__lunoraRef,shardKey:s.shardKey}}));await e.queue.sendBatch(n,r)}}},we=e=>typeof e=="object"&&e!==null&&typeof e.functionPath=="string",K="scheduler",C=e=>JSON.stringify({args:e.args,functionPath:e.functionPath,shardKey:e.shardKey}),be=async(e,t,a)=>{const{requeuedFrom:o,requeueMac:r,...n}=e;return typeof o=="string"&&a!==void 0&&await ge(a,K,o,C(n),r).catch(()=>!1)?{id:o,job:n,requeued:!0}:{id:t,job:n,requeued:!1}},Re=e=>async t=>{await Promise.all(t.messages.map(async a=>{let o;try{if(!we(a.body))throw new d("INTERNAL","@lunora/scheduler: queue message body is not a QueueJob (missing functionPath)");o=await be(a.body,a.id,e.requeue?.secret),await e.dispatch(o.job,o.id),a.ack()}catch(r){if(ce(r)&&o!==void 0){const{requeue:n}=e,{id:s,job:c,requeued:i}=o;await ue(a,{maxRetries:e.maxRetries,requeue:n===void 0||i?void 0:async u=>{const w=await pe(n.secret,K,s,C(c));await n.queue.send({...c,requeuedFrom:s,requeueMac:w},{delaySeconds:u})},where:`@lunora/scheduler: queue "${t.queue}"`});return}a.retry()}}))},$e=e=>{const t=de({env:{LUNORA_ADMIN_TOKEN:e.adminToken,LUNORA_ORIGIN_URL:e.originUrl},fetchImpl:e.fetchImpl,label:"@lunora/scheduler"}),a=e.timeoutMs??me;return async(o,r)=>{await t({__lunoraRef:o.functionPath},o.args,{dedupId:r,messageId:r,shardKey:o.shardKey,timeoutMs:a})}};export{Re as createQueueConsumer,Ne as createQueueWorkpool,$e as httpDispatcher};
@@ -1 +0,0 @@
1
- import{LunoraError as w}from"@lunora/errors";import{e as g,d as A}from"./wire-codec-BqRJjIKh.mjs";import{a as p,c as d,g as f}from"./do-client-XQayNStI.mjs";import{isWorkflowReference as h}from"./isWorkflowReference-CT3tdefh.mjs";import N from"./assertScheduleDelay-CVTqeF04.mjs";import R from"./assertScheduleInstant-DCqHdiVt.mjs";const v=async e=>{const a=[];let o;for(;;){const c=await e(o);if(a.push(...Array.isArray(c.records)?c.records:[]),c.truncated!==!0||typeof c.cursor!="string"||c.cursor.length===0)return a;if(c.cursor===o)throw new Error("collectPages: the list did not advance its cursor — refusing to page forever");o=c.cursor}},x=e=>typeof e=="string"?e:(h(e)?e.className:e.__lunoraRef)??"<unknown>",m=e=>({...e,args:A(e.args)}),K=e=>{p(e);const a=async(s,r,n,t={})=>{const u=Math.ceil(s instanceof Date?s.getTime():s);R(u,Date.now(),"ctx.scheduler.runAt");const i={args:g("ctx.scheduler.runAt",x(r),n),id:t.id,instanceName:e.instanceName??"default",maxConcurrency:t.pool===void 0?void 0:t.maxConcurrency,pool:t.pool,retry:t.retry,scheduledFor:u,shardKey:t.shardKey};if(h(r)){if(typeof r.className!="string"||r.className.length===0)throw new w("INTERNAL","@lunora/scheduler: workflow/agent schedule target is missing its `className` — pass the generated `workflows.<name>` / `agents.<name>` reference");return(await d(e,"/schedule",{...i,workflow:r.className})).id}const y=typeof r=="string"?r:r.__lunoraRef;return(await d(e,"/schedule",{...i,functionPath:y})).id},o=async(s,r,n,t={})=>(N(s,"ctx.scheduler.runAfter"),a(Date.now()+s,r,n,t)),c=async s=>d(e,"/cancel",{id:s}),l=async s=>(await v(async n=>f(e,n===void 0?s:`${s}?cursor=${encodeURIComponent(n)}`))).map(n=>m(n));return{cancel:c,dead:async()=>l("/dead"),deadRetry:async s=>{const{retried:r}=await d(e,"/dead/retry",{id:s});return r===!0},get:async s=>{const r=await f(e,`/get?id=${encodeURIComponent(s)}`);return r.record===void 0?null:m(r.record)},list:async()=>l("/list"),runAfter:o,runAt:a}};export{K as default};
@@ -1 +0,0 @@
1
- import{LunoraError as l}from"@lunora/errors";import{e as s}from"./wire-codec-BqRJjIKh.mjs";import{a as o,g as m,c as u}from"./do-client-XQayNStI.mjs";import y from"./assertScheduleDelay-CVTqeF04.mjs";const N=e=>{if(o(e),!Number.isInteger(e.maxConcurrency)||e.maxConcurrency<=0)throw new l("INTERNAL","@lunora/scheduler: `maxConcurrency` must be a positive integer");const a=typeof e.name=="string"&&e.name.length>0?e.name:"default";return{cancel:async r=>u(e,"/cancel",{id:r}),enqueue:async(r,t,n={})=>{const c=n.delayMs??0;return y(c,"workpool.enqueue"),u(e,"/schedule",{args:s("workpool.enqueue",r.__lunoraRef,t),functionPath:r.__lunoraRef,instanceName:e.instanceName??"default",maxConcurrency:e.maxConcurrency,pool:a,retry:n.retry,scheduledFor:Math.ceil(Date.now()+c),shardKey:n.shardKey})},name:a,status:async()=>m(e,`/pool?name=${encodeURIComponent(a)}`)}};export{N as default};
@@ -1 +0,0 @@
1
- import{LunoraError as o}from"@lunora/errors";import{a as r}from"./base64-junLSktH.mjs";const a=/^\w[\w-]{0,63}$/u,n=/^-/u,i=t=>{if(t===void 0)return r(crypto.getRandomValues(new Uint8Array(12))).replace(n,"_");if(typeof t!="string"||!a.test(t))throw new o("INVALID_SCHEDULE_ID","@lunora/scheduler: `id` must be 1-64 characters of [A-Za-z0-9_-] and must not start with `-`; omit it to have one minted",{status:400});return t};export{i as default};
@@ -1 +0,0 @@
1
- import{t as d,f as E}from"./base64-junLSktH.mjs";const o="$lunora.wire$",w=64,g=1024,p="__proto__",A={BigInt64Array,BigUint64Array,Float32Array,Float64Array,Int8Array,Int16Array,Int32Array,Uint8Array,Uint8ClampedArray,Uint16Array,Uint32Array},l={Error,EvalError,RangeError,ReferenceError,SyntaxError,TypeError,URIError},O=e=>{if(e===null||typeof e!="object")return!1;const n=Object.getPrototypeOf(e);return n===null||n===Object.prototype},y=(e,n=0)=>{if(n>w)throw new RangeError(`wire-codec: value nesting exceeds the ${w}-level limit`);if(e===void 0)return[o,"undefined"];if(e===null)return null;const u=typeof e;if(u==="bigint"){const r=e.toString();if(r.length>g)throw new RangeError(`wire-codec: over-long bigint (max ${g} digits)`);return[o,"bigint",r]}if(u==="number"){const r=e;return Number.isNaN(r)?[o,"nan"]:r===1/0?[o,"inf"]:r===-1/0?[o,"-inf"]:r}if(u!=="object")return e;if(e instanceof Date)return[o,"date",y(e.getTime(),n+1)];if(e instanceof Error){const r=e,t=y(Object.fromEntries(Object.keys(r).map(c=>[c,r[c]])),n+1),i=[o,"error",String(r.name),String(r.message),t];return r.cause!==void 0&&i.push(y(r.cause,n+1)),i}if(e instanceof URL)return[o,"url",e.href];if(e instanceof Map)return[o,"map",[...e.entries()].map(([r,t])=>[y(r,n+1),y(t,n+1)])];if(e instanceof Set)return[o,"set",[...e].map(r=>y(r,n+1))];if(e instanceof ArrayBuffer)return[o,"bytes",d(new Uint8Array(e)),"ArrayBuffer"];if(ArrayBuffer.isView(e)){const r=e,t=r.constructor.name,i=new Uint8Array(r.buffer,r.byteOffset,r.byteLength);return t==="Uint8Array"?[o,"bytes",d(i)]:[o,"bytes",d(i),t]}if(Array.isArray(e)){const r=e.map(t=>y(t,n+1));return r.length>0&&r[0]===o?[o,"arr",r]:r}if(!O(e)){const r=e.constructor?.name??"value";throw new TypeError(`wire-codec: cannot encode a ${r} over the Lunora wire — only plain objects, arrays, and the supported built-ins (Date, Error, URL, Map, Set, ArrayBuffer/typed arrays, bigint) round-trip`)}const s=e,a={};for(const r of Object.keys(s)){const t=s[r];if(t===void 0)continue;const i=y(t,n+1);r===p?Object.defineProperty(a,r,{configurable:!0,enumerable:!0,value:i,writable:!0}):a[r]=i}return a},f=(e,n=0)=>{if(n>w)throw new RangeError(`wire-codec: value nesting exceeds the ${w}-level limit`);if(e===null||typeof e!="object")return e;if(Array.isArray(e)){if(e[0]===o)switch(e[1]){case"-inf":return-1/0;case"arr":return e[2].map(r=>f(r,n+1));case"bigint":{const r=e[2];if(typeof r!="string"||r.length>g||!/^-?\d+$/.test(r))throw new RangeError(`wire-codec: invalid or over-long bigint (max ${g} digits)`);return BigInt(r)}case"date":{const r=f(e[2],n+1);if(typeof r!="number")throw new TypeError("wire-codec: malformed date — epoch must be a number");return new Date(r)}case"map":{const r=e[2];return new Map(r.map(t=>{if(!Array.isArray(t)||t.length!==2)throw new TypeError("wire-codec: malformed map entry — expected a [key, value] pair");return[f(t[0],n+1),f(t[1],n+1)]}))}case"set":return new Set(e[2].map(r=>f(r,n+1)));case"url":{const r=e[2];if(typeof r!="string")throw new TypeError("wire-codec: malformed url — href must be a string");return new URL(r)}case"error":{const r=e[2],t=e[3];if(typeof r!="string"||typeof t!="string")throw new TypeError("wire-codec: malformed error — name and message must be strings");const i=(Object.hasOwn(l,r)?l[r]:void 0)??Error,c=new i(t);c.name!==r&&Object.defineProperty(c,"name",{configurable:!0,value:r,writable:!0});const b=f(e[4],n+1);if(b===null||typeof b!="object"||Array.isArray(b))throw new TypeError("wire-codec: malformed error — props must be an object");for(const m of Object.keys(b))m===p?Object.defineProperty(c,m,{configurable:!0,enumerable:!0,value:b[m],writable:!0}):c[m]=b[m];return e.length>5&&Object.defineProperty(c,"cause",{configurable:!0,value:f(e[5],n+1),writable:!0}),c}case"bytes":{const r=e[2];if(typeof r!="string")throw new TypeError("wire-codec: malformed bytes — payload must be a base64 string");const t=E(r);if(d(t)!==r)throw new TypeError("wire-codec: malformed bytes — payload must be canonical padded base64");const i=e[3]??"Uint8Array";if(i==="ArrayBuffer")return t.buffer.byteLength===t.byteLength?t.buffer:t.slice().buffer;const c=Object.hasOwn(A,i)?A[i]:void 0;return c?new c(t.slice().buffer):t}case"inf":return 1/0;case"nan":return Number.NaN;case"undefined":return;default:return e.map(r=>f(r,n+1))}return e.map(a=>f(a,n+1))}const u=e,s={};for(const a of Object.keys(u)){const r=f(u[a],n+1);a===p?Object.defineProperty(s,a,{configurable:!0,enumerable:!0,value:r,writable:!0}):s[a]=r}return s},T=(e,n,u)=>{try{return y(u)}catch(s){throw new TypeError(`${e}: cannot encode args for '${n}' — ${s instanceof Error?s.message:String(s)}`,s instanceof Error?{cause:s}:void 0)}};export{y as a,f as d,T as e};