{"id":35169869,"url":"https://github.com/acoyfellow/ironalarm","last_synced_at":"2025-12-28T20:06:14.897Z","repository":{"id":329476115,"uuid":"1119770469","full_name":"acoyfellow/ironalarm","owner":"acoyfellow","description":"Reliable task scheduling for Cloudflare Durable Objects, implementing the \"reliable runNow\" pattern for resilient long-running tasks.","archived":false,"fork":false,"pushed_at":"2025-12-21T23:55:36.000Z","size":5468,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-12-22T09:06:07.371Z","etag":null,"topics":["cloudflare","durable-objects"],"latest_commit_sha":null,"homepage":"https://ironalarm.coey.dev","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/acoyfellow.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-12-19T20:24:28.000Z","updated_at":"2025-12-22T04:24:24.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/acoyfellow/ironalarm","commit_stats":null,"previous_names":["acoyfellow/ironalarm"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/acoyfellow/ironalarm","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/acoyfellow%2Fironalarm","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/acoyfellow%2Fironalarm/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/acoyfellow%2Fironalarm/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/acoyfellow%2Fironalarm/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/acoyfellow","download_url":"https://codeload.github.com/acoyfellow/ironalarm/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/acoyfellow%2Fironalarm/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28103418,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-12-28T02:00:05.685Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cloudflare","durable-objects"],"created_at":"2025-12-28T20:05:05.656Z","updated_at":"2025-12-28T20:06:14.887Z","avatar_url":"https://github.com/acoyfellow.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ironalarm\n\n[![npm version](https://img.shields.io/npm/v/ironalarm.svg)](https://www.npmjs.com/package/ironalarm)\n\nReliable task scheduling for Cloudflare Durable Objects, implementing the \"reliable runNow\" pattern for resilient long-running tasks.\n\n## Problem\n\nCloudflare Durable Objects can evict your code after ~144 seconds of inactivity. For long-running operations (like AI agent loops), a single eviction mid-task breaks your workflow. `ironalarm` solves this with a lightweight, userspace implementation that persists task state and uses a 30-second safety alarm net—if evicted, the task automatically retries and resumes from checkpoints.\n\n## Features\n\n- **Reliable execution**: `runNow()` starts immediately + 30s safety alarm for eviction recovery\n- **Future scheduling**: `schedule()` for delayed/recurring tasks\n- **Checkpoints**: User-managed progress tracking for resumable work\n- **Named handlers**: Register task handlers by name (no function serialization)\n- **Fully serializable**: Tasks are just `{ taskName, params, progress }`\n\n## Installation\n\n```bash\nbun install ironalarm\n# or\nbun add ironalarm\n```\n\n## Quick Start\n\n```typescript\nimport { ReliableScheduler } from 'ironalarm';\nimport { Effect } from 'effect';\n\nexport class MyDO {\n  private scheduler: ReliableScheduler;\n\n  constructor(state: DurableObjectState, env: any) {\n    this.scheduler = new ReliableScheduler(state.storage);\n\n    this.scheduler.register('my-task', (sched, taskId, params) =\u003e {\n      return Effect.gen(function* () {\n        const started = yield* Effect.promise(() =\u003e sched.getCheckpoint(taskId, 'started'));\n        if (!started) {\n          yield* Effect.promise(() =\u003e doWork(params));\n          yield* Effect.promise(() =\u003e sched.checkpoint(taskId, 'started', true));\n        }\n        yield* Effect.promise(() =\u003e expensiveOperation());\n        yield* Effect.promise(() =\u003e sched.completeTask(taskId));\n      });\n    });\n  }\n\n  async alarm() {\n    await this.scheduler.alarm();\n  }\n\n  async startTask(params: any) {\n    const taskId = crypto.randomUUID();\n    await this.scheduler.runNow(taskId, 'my-task', params);\n  }\n}\n```\n\n## Infinite Loop Tasks\n\nFor tasks that run forever (like game loops, background processors), use `maxRetries: Infinity`:\n\n```typescript\n// Register an infinite loop handler\nthis.scheduler.register('mining-loop', (sched, taskId, params) =\u003e {\n  return Effect.gen(function* () {\n    while (true) {\n      // Check if cancelled/paused\n      const task = yield* Effect.promise(() =\u003e sched.getTask(taskId));\n      if (!task || task.status === 'paused' || task.status === 'failed') return;\n\n      // Do work\n      yield* Effect.promise(() =\u003e mineResources(params));\n\n      // Wait before next iteration\n      yield* Effect.promise(() =\u003e new Promise(r =\u003e setTimeout(r, 5000)));\n    }\n  });\n});\n\n// Start with infinite retries so it survives DO restarts\nawait this.scheduler.runNow(taskId, 'mining-loop', params, { maxRetries: Infinity });\n```\n\n**Important**: After a DO restart, the Effect generator won't automatically resume. You need to manually restart infinite loop tasks in your constructor:\n\n```typescript\nconstructor(state: DurableObjectState, env: any) {\n  this.scheduler = new ReliableScheduler(state.storage);\n  // ... register handlers ...\n\n  // Resume infinite loop tasks after DO restart\n  this.resumeLoopTasks();\n}\n\nprivate async resumeLoopTasks() {\n  const LOOP_TASKS = ['mining-loop', 'game-state'];\n  const tasks = await this.scheduler.getTasks();\n  for (const task of tasks) {\n    if (task.status === 'running' \u0026\u0026 LOOP_TASKS.includes(task.taskName)) {\n      // Re-run the handler (it will pick up from checkpoints)\n      const handler = this.scheduler.getHandler(task.taskName);\n      if (handler) {\n        Effect.runPromise(handler(this.scheduler, task.taskId, task.params));\n      }\n    }\n  }\n}\n```\n\n## API\n\n### Constructor\n`new ReliableScheduler(storage: DurableObjectStorage)`\n\n### Methods\n\n- `register(taskName, handler)` — Register a named task handler\n- `runNow(taskId, taskName, params?, options?)` — Start immediately with eviction safety\n  - `options.maxRetries` — Override retry limit (default: 3, use `Infinity` for loop tasks)\n- `schedule(at, taskId, taskName, params?)` — Schedule for future time\n- `checkpoint(taskId, key, value)` — Save progress\n- `getCheckpoint(taskId, key)` — Retrieve progress\n- `completeTask(taskId)` — Mark as done\n- `getTask(taskId)` — Get single task by ID\n- `getTasks(status?)` — List all tasks (optionally filter by status)\n- `cancelTask(taskId)` — Cancel/delete a task\n- `pauseTask(taskId)` — Pause a task (removes from queue)\n- `resumeTask(taskId)` — Resume a paused task (re-adds to queue)\n- `clearCompleted()` — Delete all completed tasks, returns count\n- `clearAll()` — Delete all tasks, returns count\n- `getHandler(taskName)` — Get registered handler by name (for manual re-execution)\n- `alarm()` — Call from DO's alarm handler\n\n## Design\n\n- **Eviction safety**: 30s safety alarm retries if evicted\n- **Checkpoints**: Skip already-done work on resume\n- **Named handlers**: No function serialization\n- **Single queue**: One alarm drives all tasks\n- **Retry limits**: Tasks automatically fail after 3 retries (configurable via `maxRetries`)\n- **Pause/resume**: Tasks can be paused and resumed without losing state\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Facoyfellow%2Fironalarm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Facoyfellow%2Fironalarm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Facoyfellow%2Fironalarm/lists"}