{"id":50563000,"url":"https://github.com/myferr/action","last_synced_at":"2026-06-04T12:30:27.792Z","repository":{"id":329664331,"uuid":"1120311861","full_name":"myferr/action","owner":"myferr","description":"TypeScript library for defining and running typesafe cron jobs","archived":false,"fork":false,"pushed_at":"2026-05-18T07:57:02.000Z","size":38,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-18T09:49:20.456Z","etag":null,"topics":["cron","cronjob-scheduler","library","typescript"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/@use-solace/action","language":"TypeScript","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/myferr.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},"funding":{"ko_fi":"myfer"}},"created_at":"2025-12-20T23:45:05.000Z","updated_at":"2026-05-18T07:57:26.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/myferr/action","commit_stats":null,"previous_names":["use-solace/action","myferr/action"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/myferr/action","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/myferr%2Faction","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/myferr%2Faction/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/myferr%2Faction/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/myferr%2Faction/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/myferr","download_url":"https://codeload.github.com/myferr/action/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/myferr%2Faction/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33905358,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-04T02:00:06.755Z","response_time":64,"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":["cron","cronjob-scheduler","library","typescript"],"created_at":"2026-06-04T12:30:27.253Z","updated_at":"2026-06-04T12:30:27.782Z","avatar_url":"https://github.com/myferr.png","language":"TypeScript","funding_links":["https://ko-fi.com/myfer"],"categories":[],"sub_categories":[],"readme":"# @myfer/action\n\nA TypeScript library for defining and running typesafe scheduled actions (cron jobs). Build reliable, type-safe automation with built-in scheduling, logging, and shell command execution.\n\n## Features\n\n- **Type-safe**: Full TypeScript support with strict type checking\n- **Scheduled execution**: Automatic interval-based scheduling with configurable units\n- **Manual execution**: Run actions on-demand via API\n- **Concurrency control**: Prevents duplicate execution of the same action\n- **Error handling**: Built-in error logging and recovery\n- **Bash integration**: Execute shell commands from within actions\n- **Runtime validation**: Schema validation using Zod\n- **Structured logging**: Built-in logging via action context\n- **State management**: Per-action state persistence\n- **ESM support**: Native ES modules\n\n## Installation\n\n```bash\n# bun\nbun add @myfer/action\n# npm\nnpm install @myfer/action\n# yarn\nyarn add @myfer/action\n# pnpm\npnpm add @myfer/action\n```\n\n## Quick Start\n\n```typescript\nimport { define } from \"@myfer/action\";\n\nconst actions = define([\n  {\n    name: \"cleanup-logs\",\n    description: \"Clean up old log files\",\n    interval: { every: 1, unit: \"hours\" },\n    execute: async (ctx) =\u003e {\n      ctx.log.info(\"Starting cleanup...\");\n      // Your action logic here\n      ctx.log.info(\"Cleanup complete\");\n    },\n  },\n]);\n\n// Actions run automatically on their schedule\n// Or trigger manually:\nawait actions.run(\"cleanup-logs\");\n```\n\n## API Reference\n\n### `define(definitions: ActionDefinition[])`\n\nRegisters one or more action definitions and starts the scheduler. Returns an object with a `run` method for manual execution.\n\n**Parameters:**\n\n- `definitions`: An array of `ActionDefinition` objects\n\n**Returns:**\n\n```typescript\n{\n  run: (name: string) =\u003e Promise\u003cvoid\u003e;\n}\n```\n\n**Throws:**\n\n- `Error` if any action definition is invalid (schema validation fails)\n- `Error` if duplicate action names are found\n\n**Example:**\n\n```typescript\nconst actions = define([\n  {\n    name: \"my-action\",\n    description: \"My action description\",\n    execute: async (ctx) =\u003e {\n      /* ... */\n    },\n  },\n]);\n```\n\n### `ActionDefinition`\n\nThe interface for defining an action.\n\n```typescript\ninterface ActionDefinition {\n  name: string; // Unique identifier for the action\n  description: string; // Human-readable description\n  execute: (ctx: ActionContext) =\u003e Promise\u003cvoid\u003e | void; // Main execution function\n  onRun?: (ctx: ActionContext) =\u003e Promise\u003cvoid\u003e | void; // Optional hook that runs once on first execution\n  onComplete?: (ctx: ActionContext) =\u003e Promise\u003cvoid\u003e | void; // Optional hook that runs on every successful completion\n  onError?: (ctx: ActionContext, error: Error) =\u003e Promise\u003cvoid\u003e | void; // Optional hook that runs on execution failure\n  interval?: ActionInterval; // Optional scheduling interval\n}\n```\n\n**Properties:**\n\n- **`name`** (required): A unique string identifier for the action. Used to reference the action when calling `actions.run()`.\n\n- **`description`** (required): A human-readable description of what the action does.\n\n- **`execute`** (required): The main function that performs the action's work. Receives an `ActionContext` object with utilities and can be async or sync.\n\n- **`onRun`** (optional): A callback function that runs after `execute` completes successfully on the **first execution only**. Useful for one-time setup, initialization, or first-run notifications.\n\n- **`onComplete`** (optional): A callback function that runs after `execute` completes successfully on **every execution**. Useful for notifications, logging, or cleanup that should happen after each successful run.\n\n- **`onError`** (optional): A callback function that runs when `execute` throws an error. Receives the context and the error object. Useful for error handling, notifications, cleanup, or recovery logic.\n\n- **`interval`** (optional): Defines when the action should run automatically. If omitted, the action will only run when manually triggered.\n\n### `ActionInterval`\n\nDefines the scheduling interval for an action.\n\n```typescript\ninterface ActionInterval {\n  every: number; // Number of units (must be positive integer)\n  unit?: IntervalUnit; // Unit of time (defaults to 'minutes')\n}\n\ntype IntervalUnit = \"seconds\" | \"minutes\" | \"hours\" | \"days\";\n```\n\n**Properties:**\n\n- **`every`**: A positive integer specifying how many units to wait between executions.\n- **`unit`**: The time unit. Options are:\n  - `'seconds'`: Run every N seconds\n  - `'minutes'`: Run every N minutes (default)\n  - `'hours'`: Run every N hours\n  - `'days'`: Run every N days\n\n**Examples:**\n\n```typescript\n{ every: 30, unit: 'seconds' }  // Every 30 seconds\n{ every: 5, unit: 'minutes' }    // Every 5 minutes\n{ every: 1, unit: 'hours' }      // Every hour\n{ every: 1 }                     // Every 1 minute (default unit)\n```\n\n### `ActionContext`\n\nThe context object passed to action execution functions, providing utilities and state.\n\n```typescript\ninterface ActionContext {\n  log: {\n    info: (message: string) =\u003e void;\n    error: (message: string) =\u003e void;\n  };\n  bash?: {\n    run: (cmd: string, opts?: BashOptions) =\u003e Promise\u003cBashResult\u003e;\n  };\n  now: () =\u003e Date;\n  state?: any;\n}\n```\n\n**Properties:**\n\n- **`log`**: Structured logging utilities\n  - `log.info(message)`: Log an informational message (prefixed with `[action]`)\n  - `log.error(message)`: Log an error message (prefixed with `[action]`)\n\n- **`bash`**: Optional bash command execution utility (see [Bash Utilities](#bash-utilities))\n\n- **`now()`**: Returns the current date/time as a `Date` object\n\n- **`state`**: Optional state object that persists across action executions (currently initialized as empty object)\n\n### Bash Utilities\n\nThe `bash` utility allows you to execute shell commands from within your actions.\n\n#### `bash.run(command, options?)`\n\nExecutes a shell command and returns the result.\n\n**Parameters:**\n\n- `command`: The shell command to execute (string)\n- `options` (optional):\n  ```typescript\n  {\n    cwd?: string;                    // Working directory\n    env?: Record\u003cstring, string\u003e;  // Environment variables (merged with process.env)\n  }\n  ```\n\n**Returns:**\n\n```typescript\nPromise\u003c{\n  exitCode: number; // Process exit code (0 for success)\n  stdout: string; // Standard output\n  stderr: string; // Standard error\n}\u003e;\n```\n\n**Example:**\n\n```typescript\nconst result = await ctx.bash?.run(\"ls -la\", { cwd: \"/tmp\" });\nif (result.exitCode === 0) {\n  ctx.log.info(`Files: ${result.stdout}`);\n} else {\n  ctx.log.error(`Command failed: ${result.stderr}`);\n}\n```\n\n## Examples\n\n### Basic Scheduled Action\n\n```typescript\nimport { define } from \"@myfer/action\";\n\nconst actions = define([\n  {\n    name: \"health-check\",\n    description: \"Check system health every 5 minutes\",\n    interval: { every: 5, unit: \"minutes\" },\n    execute: async (ctx) =\u003e {\n      ctx.log.info(\"Running health check...\");\n      // Perform health check logic\n      const isHealthy = await checkSystemHealth();\n      if (!isHealthy) {\n        ctx.log.error(\"System health check failed!\");\n      } else {\n        ctx.log.info(\"System is healthy\");\n      }\n    },\n  },\n]);\n```\n\n### Action with Post-Execution Hooks\n\n```typescript\nconst actions = define([\n  {\n    name: \"backup-database\",\n    description: \"Backup database every 6 hours\",\n    interval: { every: 6, unit: \"hours\" },\n    execute: async (ctx) =\u003e {\n      ctx.log.info(\"Starting database backup...\");\n      // Perform backup\n      await performBackup();\n      ctx.log.info(\"Backup completed\");\n    },\n    onRun: async (ctx) =\u003e {\n      // This runs only once on the first execution\n      ctx.log.info(\"Sending initial backup notification...\");\n      await sendNotification(\"Backup system initialized\");\n    },\n    onComplete: async (ctx) =\u003e {\n      // This runs after every successful execution\n      ctx.log.info(\"Sending completion notification...\");\n      await sendNotification(\"Backup completed successfully\");\n    },\n  },\n]);\n```\n\n### Action with Bash Commands\n\n```typescript\nconst actions = define([\n  {\n    name: \"cleanup-temp\",\n    description: \"Clean temporary files every hour\",\n    interval: { every: 1, unit: \"hours\" },\n    execute: async (ctx) =\u003e {\n      ctx.log.info(\"Cleaning temporary files...\");\n\n      const result = await ctx.bash?.run(\n        \"find /tmp -type f -mtime +7 -delete\",\n        {\n          env: { TMPDIR: \"/tmp\" },\n        }\n      );\n\n      if (result?.exitCode === 0) {\n        ctx.log.info(\"Temporary files cleaned successfully\");\n      } else {\n        ctx.log.error(`Cleanup failed: ${result?.stderr}`);\n      }\n    },\n  },\n]);\n```\n\n### Manual-Only Action (No Schedule)\n\n```typescript\nconst actions = define([\n  {\n    name: \"migrate-database\",\n    description: \"Run database migrations\",\n    // No interval - only runs when manually triggered\n    execute: async (ctx) =\u003e {\n      ctx.log.info(\"Running database migrations...\");\n      await runMigrations();\n      ctx.log.info(\"Migrations completed\");\n    },\n  },\n]);\n\n// Trigger manually\nawait actions.run(\"migrate-database\");\n```\n\n### Multiple Actions\n\n```typescript\nconst actions = define([\n  {\n    name: \"sync-data\",\n    description: \"Sync data every 15 minutes\",\n    interval: { every: 15, unit: \"minutes\" },\n    execute: async (ctx) =\u003e {\n      await syncData();\n    },\n  },\n  {\n    name: \"generate-report\",\n    description: \"Generate daily report\",\n    interval: { every: 1, unit: \"days\" },\n    execute: async (ctx) =\u003e {\n      await generateReport();\n    },\n  },\n  {\n    name: \"monitor-logs\",\n    description: \"Monitor logs every 30 seconds\",\n    interval: { every: 30, unit: \"seconds\" },\n    execute: async (ctx) =\u003e {\n      await monitorLogs();\n    },\n  },\n]);\n```\n\n### Error Handling\n\nActions have built-in error handling. Errors in `execute` or `onRun` are automatically caught and logged:\n\n```typescript\nconst actions = define([\n  {\n    name: \"risky-operation\",\n    description: \"An action that might fail\",\n    interval: { every: 1, unit: \"hours\" },\n    execute: async (ctx) =\u003e {\n      // If this throws, it's automatically caught and logged\n      await someRiskyOperation();\n    },\n  },\n]);\n```\n\nErrors are logged with the format: `[action] action \u003cname\u003e failed: \u003cerror message\u003e`\n\n### Action with Error Handler\n\nUse `onError` to handle failures with custom logic:\n\n```typescript\nconst actions = define([\n  {\n    name: \"critical-task\",\n    description: \"A task that needs error handling\",\n    interval: { every: 30, unit: \"minutes\" },\n    execute: async (ctx) =\u003e {\n      await performCriticalTask();\n    },\n    onError: async (ctx, error) =\u003e {\n      // Custom error handling\n      ctx.log.error(`Critical task failed: ${error.message}`);\n      await sendAlert(`Action failed: ${error.message}`);\n      await attemptRecovery();\n    },\n  },\n]);\n```\n\nThe `onError` handler receives both the context and the error object, allowing you to implement custom error handling, notifications, or recovery logic. If `onError` itself throws an error, it will be caught and logged separately.\n\n### Action with Completion Handler\n\nUse `onComplete` to run logic after every successful execution:\n\n```typescript\nconst actions = define([\n  {\n    name: \"sync-data\",\n    description: \"Sync data every 15 minutes\",\n    interval: { every: 15, unit: \"minutes\" },\n    execute: async (ctx) =\u003e {\n      await syncData();\n    },\n    onComplete: async (ctx) =\u003e {\n      // This runs after every successful execution\n      ctx.log.info(\"Data sync completed successfully\");\n      await updateLastSyncTime();\n      await notifyTeam(\"Data sync completed\");\n    },\n  },\n]);\n```\n\nThe `onComplete` handler runs after every successful execution (unlike `onRun` which only runs once). This is useful for notifications, logging, or cleanup that should happen after each successful run. If `onComplete` itself throws an error, it will be caught and logged separately.\n\n## Advanced Usage\n\n### Type Safety\n\nThe library is fully typed. Import types for better IDE support:\n\n```typescript\nimport {\n  define,\n  type ActionDefinition,\n  type ActionContext,\n} from \"@myfer/action\";\n\nconst myAction: ActionDefinition = {\n  name: \"typed-action\",\n  description: \"A fully typed action\",\n  execute: async (ctx: ActionContext) =\u003e {\n    // ctx is fully typed\n    ctx.log.info(\"Hello\");\n  },\n};\n```\n\n### Concurrency Control\n\nThe scheduler automatically prevents concurrent execution of the same action. If an action is still running when its next scheduled time arrives, it will be skipped until the current execution completes.\n\n```typescript\nconst actions = define([\n  {\n    name: \"long-running-task\",\n    description: \"A task that might take longer than the interval\",\n    interval: { every: 1, unit: \"minutes\" },\n    execute: async (ctx) =\u003e {\n      // If this takes 2 minutes, the next scheduled run will be skipped\n      await longRunningOperation();\n    },\n  },\n]);\n```\n\n### Scheduler Behavior\n\n- The scheduler checks every 1 second for actions that need to run\n- Actions are scheduled to run at `Date.now() + interval` after registration\n- After each execution, the next run time is recalculated based on the interval\n- The scheduler starts automatically when `define()` is called\n\n### State Management\n\nThe `state` property in `ActionContext` can be used to persist data across executions (currently initialized as an empty object):\n\n```typescript\nconst actions = define([\n  {\n    name: \"counter\",\n    description: \"Count executions\",\n    interval: { every: 1, unit: \"minutes\" },\n    execute: async (ctx) =\u003e {\n      ctx.state.count = (ctx.state.count || 0) + 1;\n      ctx.log.info(`Executed ${ctx.state.count} times`);\n    },\n  },\n]);\n```\n\n## Development\n\n### Building\n\n```bash\nnpm run build\n```\n\nThis compiles TypeScript to JavaScript in the `dist/` directory.\n\n### Type Checking\n\n```bash\nnpm run typecheck\n```\n\nRuns TypeScript compiler in check-only mode (no output files).\n\n### Testing\n\n```bash\nnpm test\n# or\nbun test\n```\n\nRuns the test suite using Bun's test framework.\n\n## Requirements\n\n- Node.js 18+ or Bun\n- TypeScript 5.3+ (for development)\n\n## License\n\nMIT\n\n## Contributing\n\nContributions are welcome! Please feel free to submit a Pull Request.\n\n## Support\n\nFor issues, questions, or contributions, please visit: [github.com/use-solace](https://github.com/use-solace)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmyferr%2Faction","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmyferr%2Faction","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmyferr%2Faction/lists"}