Replace Bun cron parser with portable implementation#1
Open
aashahin wants to merge 1 commit into
Open
Conversation
…ackaging Multi-agent audit of the SDK confirmed 40+ defects; this applies the verified fixes: Durability (Redis adapter): - Claim now flips instance status to running atomically inside the claim Lua script (dedicated status hash field, read via parseInstanceRow), so a crash mid-claim can no longer orphan a job as permanently "queued". - requeue() and recoverStalled() re-enqueue before releasing lease and processing entries: crash windows now risk a tolerated duplicate claim instead of permanent job loss. - recoverStalled() recovers any non-terminal expired job (not only "running") and processing scores store claim time, aligning staleness semantics with the SQLite adapter. - Retention TTLs for terminal instances, step results, dead letters, and configurable idempotency TTL that always covers scheduledAt horizons. - Redis clients are validated at construction; discrete-method clients (set/expire/zrangeByScore/...) work without send(). Scheduler (cron rewrite): - Real 5/6-field cron parser: ranges, lists, steps, month/day names, 7==Sunday, vixie DOM-vs-DOW OR rule, leap years, UTC-consistent math. The old fallback ignored day/month/weekday fields entirely (weekly and monthly schedules fired daily) and the Bun.cron API it deferred to does not exist. - missedRunPolicy now has distinct semantics: skip (freshness window via new CronDefinition.maxDelayMs, default 1h), catch-up-latest, catch-up-all. Bun runtime: - In-process cron uses chained setTimeout timers from the parser; scheduler mode "os" now throws (it silently never ran before). - stop() drains in-flight work; the claim loop halts on shutdown. - Unknown workflow names dead-letter instead of throwing out of the batch; completion-persist failures are logged and retried instead of re-running the already-succeeded workflow; per-cron-run error isolation in tick(). Cloudflare runner/handler: - ctx.dispatch is wrapped in step.do so replays cannot re-fire nested dispatches; legacy envelope normalization derives stable ids from the CF instanceId/timestamp instead of fresh randomness per replay. - SDK non-retryable errors translate to CF NonRetryableError (lazy cloudflare:workflows import); numeric sleeps pass milliseconds as numbers (the "N milliseconds" duration string is invalid on CF); RetryPolicy.maxIntervalMs maps to a CF dynamic delay function. - scheduled() isolates per-run failures; /status errors return 502 instead of crashing; invalid scheduledAt throws a clear validation error; rest-adapter reports partial batch results on the thrown error. Core: - Successful dispatches are no longer reported as failures when an onDispatch hook throws; hooks are isolated and logged. - Step timeouts: only the winning attempt settles (late completions are dropped, no unhandled rejections); concurrent-execution hazard documented. - durationToMs no longer misparses garbage via Date.parse (cron-like strings throw; bare numeric strings are milliseconds). - createRunContext derives the dispatch client from clientConfig like runWorkflowEnvelope; WorkflowRuntimeOptions is exported. - Optional RetryPolicy.jitter (0..1) for desynchronized backoff. Callback/HTTP: - Duplicate callbackSteps stepNames are rejected (same-name steps were silently skipped via step-result caching). - SignedHttpAdapter.getInstance honors timeoutMs and wraps errors in WorkflowSendError; ids-only responses validate count; instance-name caches are FIFO-bounded. Packaging/docs: - Standalone tsconfig (was extending a nonexistent monorepo parent); tests excluded from the published tarball; README updated for runtime support, real scheduler modes, and the new cron semantics. Tests: 58 -> 117 passing; tsc --noEmit clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FkcHw1PpJfVDU3VSFfGcCU
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR replaces the runtime-dependent Bun cron parser with a portable, self-contained cron expression parser and next/previous occurrence engine. The new implementation works across all runtimes (Bun, Cloudflare, Node.js) without external dependencies, while maintaining full cron expression compatibility and improving reliability.
Key Changes
Cron Parser Implementation
Replaced Bun.cron.parse dependency with a custom parser that handles:
*/15,10-50/20)Added next/previous occurrence computation with:
Missed-Run Policy Improvements
Extracted
selectDueOccurrences()function to cleanly apply missed-run policies:"skip"(default): only dispatch if the most recent occurrence is withinmaxDelayMs(default 1 hour)"catch-up-latest": always dispatch the most recent missed occurrence"catch-up-all": dispatch all collected occurrences in chronological orderAdded
maxDelayMsfield to cron definitions for fine-grained control over stale run handlingRuntime Improvements
Bun runtime:
registerOsCron()(unsupported on Bun); throws clear error ifmode: "os"is configuredstop()now waits for in-flight drain to settle before returningstoppingflag to prevent new work during shutdownCloudflare runtime:
instanceIdandtimestampfrom Cloudflare eventctx.dispatch()calls in uniquely-named steps for proper memoization across replayswrapNonRetryable()to translate SDK non-retryable markers to Cloudflare'sNonRetryableErrorAdapter Enhancements
recoverStalled()Testing & Documentation
runtime.test.tsfordurationToMs()and dispatch client derivationretry.test.tsfor backoff delay computation with jitterConfiguration & Build
tsconfig.jsonto standalone configuration (removed extends, explicit compiler options)package.jsonto exclude test files from published packageNotable Implementation Details
getPreviousCronDates()efficiently collects multiple past occurrences byhttps://claude.ai/code/session_01FkcHw1PpJfVDU3VSFfGcCU