> ## Documentation Index
> Fetch the complete documentation index at: https://pulian.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# @hile/schedule

> Run cron or delayed jobs, optionally in distributed mode with Redis lock protection.

# @hile/schedule

Run cron or delayed jobs, optionally in distributed mode with Redis lock protection.

## Choose This Package When

| User asks for            | Use              | Also read                    |
| ------------------------ | ---------------- | ---------------------------- |
| Run cron or delayed jobs | `@hile/schedule` | `packages/infrastructure.md` |

## Use When

Use these packages for database connections, Redis connections, structured logging, typed Redis caches, scheduled jobs, and file-system loaders.

## Do Not Use When

* Do not use default TypeORM or Redis services for multiple connections.
* Do not use `@hile/cache` without returning `new Cache(...)` from the loader function.
* Do not use `@hile/schedule` distributed mode without Redis lock TTLs sized for job duration.

## Install

```bash theme={null}
pnpm add @hile/typeorm @hile/ioredis @hile/logger @hile/cache @hile/schedule @hile/loader
```

## Imports

```ts theme={null}
import typeormService, { transaction } from '@hile/typeorm'
import redisService from '@hile/ioredis'
import { createLogger } from '@hile/logger'
import { Cache, defineCache, RedisCache } from '@hile/cache'
import { Scheduler, defineJob } from '@hile/schedule'
import { scanDirectory, compileRoutePath, toRouterPath, normalizePath, Loader } from '@hile/loader'
```

## Copy-Paste Example

```ts theme={null}
import { loadService } from '@hile/core'
import redisService from '@hile/ioredis'
import { Cache, defineCache, RedisCache } from '@hile/cache'

const userProfile = defineCache('user:{id:string}:profile', async ({ id }) => {
  const user = await loadUser(id)
  return new Cache(user).setExpire(300)
})

const redis = await loadService(redisService)
const cache = new RedisCache('app:', redis)
const users = await cache.loadCache(userProfile)
const user = await users.read({ id: 'u1' })
```

## More Examples

TypeORM transaction with compensation:

```ts theme={null}
await transaction(ds, async (runner, rollback) => {
  const user = await runner.manager.save(User, input)
  rollback(() => redis.del(`user:${user.id}`))
  await runner.manager.save(AuditLog, { userId: user.id, action: 'create' })
  return user
})
```

Distributed scheduled job:

```ts theme={null}
const scheduler = new Scheduler()
scheduler.add('daily-report', '0 8 * * *', async () => {
  await sendDailyReport()
}, {
  distributed: {
    redis,
    ttl: 60_000,
    namespace: 'reports',
    policy: 'skip-if-locked',
  },
})
```

## Runtime And Lifecycle Notes

* `Cache(undefined)` removes the key unless negative caching is configured.
* `Cache#setExpire(seconds)` uses seconds.
* `defineCache` typed placeholders support `string`, `number`, and `boolean`.
* `RedisCache.loadCache()` returns `read`, `write`, `remove`, `has`, and `multi`.
* `RedisCache.removeTag(tag)` removes tagged cache entries.
* `fieldable` caches use Redis hashes and cannot combine with stale or negative cache options.
* `Scheduler.add()` supports cron strings and `{ delay }`.
* `Scheduler.load()` reads default exports from `*.schedule.*` files produced by `defineJob()`.
* `scanDirectory()` matches `.ts`, `.js`, `.tsx`, `.jsx`, and `.mjs`.

## Anti-Patterns

* Returning raw values from `defineCache` handlers instead of `new Cache(value)`.
* Treating cache as source of truth.
* Forgetting to destroy manually created Redis or TypeORM clients.
* Scheduling jobs without idempotency when side effects can repeat.

## Verification Checklist

* Manual Redis clients call `disconnect()` during cleanup.
* Manual TypeORM data sources call `destroy()` during cleanup.
* Cache keys include app/tenant prefixes when shared Redis is used.
* Scheduled jobs have clear duplicate-run policy.

## Related Recipes

### Queue Worker With Idempotency

## Complete Example

```ts theme={null}
// src/services/email-worker.boot.ts
import { defineService, loadService } from '@hile/core'
import redisService from '@hile/ioredis'
import { RedisIdempotency, stableHash } from '@hile/redis-idempotency'
import { RedisStreamQueue, defineQueue } from '@hile/redis-stream-queue'

type EmailPayload = {
  tenantId: string
  userId: string
  template: 'welcome'
}

const emailQueue = defineQueue<EmailPayload>('email')

export default defineService('email.worker', async (shutdown) => {
  const redis = await loadService(redisService)
  const queue = new RedisStreamQueue(redis, { prefix: 'app:' })
  const idempotency = new RedisIdempotency(redis)

  const worker = queue.worker(emailQueue, async (job) => {
    await idempotency.run(
      `idem:email:${job.data.tenantId}:${job.jobId ?? job.id}`,
      () => sendEmail(job.data),
      {
        lockTtl: 60_000,
        resultTtl: 86_400_000,
        fingerprint: stableHash(job.data),
      },
    )
  }, {
    group: 'email-workers',
    consumer: process.env.HOSTNAME ?? `${process.pid}`,
    concurrency: 8,
    claimIdle: 60_000,
  })

  worker.start()
  shutdown(() => worker.stop())

  return { queue, worker }
})
```

Enqueue:

```ts theme={null}
await queue.add(emailQueue, {
  tenantId: 't1',
  userId: 'u1',
  template: 'welcome',
}, {
  jobId: 'welcome:t1:u1',
  maxAttempts: 5,
  backoff: { type: 'exponential', baseMs: 1_000, maxMs: 60_000 },
})
```

## Package-Local AI Guide

This package also ships `AI.md` in npm so agents can read accurate examples after installation.
