npm package: @sarfarajey/cf-workers-kit
Description: Cloudflare Workers utilities: JSON response builder, timing-safe comparison, env binding resolution, and a cron scheduler
Author: sarfarajey
Latest version: 1.0.0

# @sarfarajey/cf-workers-kit

Cloudflare Workers utilities for building HTTP APIs.

## Install

```bash
npm install @sarfarajey/cf-workers-kit
```

## HTTP Utilities (`import from '@sarfarajey/cf-workers-kit'`)

### `respond(data, status?, options?)`

JSON response builder with hardened security headers (`X-Content-Type-Options`, `Cache-Control: no-store`, `Strict-Transport-Security`, `Content-Security-Policy`, `X-Frame-Options`).

```js
import { respond } from '@sarfarajey/cf-workers-kit';

export default {
  async fetch(request) {
    return respond({ ok: true });                          // 200
    return respond({ error: 'Not found' }, 404);          // 404
    return respond({ data }, 200, { pretty: true });      // pretty-printed
    return respond({ data }, 200, { headers: { 'X-Custom': 'value' } });
  }
};
```

### `timingSafeEqual(a, b)`

Constant-time string comparison using Web Crypto HMAC — no Node.js `crypto` module required.

```js
import { timingSafeEqual } from '@sarfarajey/cf-workers-kit';

const ok = await timingSafeEqual(request.headers.get('x-secret'), env.MY_SECRET);
if (!ok) return respond({ error: 'Unauthorized' }, 401);
```

### `checkBodySize(request, maxBytes?)`

Returns a 413 `Response` if the `content-length` header exceeds `maxBytes` (default: 64 KB), otherwise `null`. Call before `request.json()`.

```js
import { checkBodySize, respond } from '@sarfarajey/cf-workers-kit';

const sizeError = checkBodySize(request, 32_768);
if (sizeError) return sizeError;
const body = await request.json();
```

### `getBearerToken(request)`

Extracts the token from an `Authorization: Bearer <token>` header.

```js
import { getBearerToken } from '@sarfarajey/cf-workers-kit';

const token = getBearerToken(request);
```

### `resolveScopedBinding(env, baseKey, runtimeEnvironment, options?)`

Resolves a Cloudflare env binding with per-environment suffix fallback. Useful when you have `SECRET_DEVELOPMENT`, `SECRET_PREVIEW`, `SECRET_PRODUCTION` variants in your `wrangler.toml`.

```js
import { resolveScopedBinding, inferRuntimeEnvironment } from '@sarfarajey/cf-workers-kit';

const runtime = inferRuntimeEnvironment(request, {
  productionHosts: ['example.com'],
  previewHosts: ['preview.example.com'],
});

const { value: apiKey } = resolveScopedBinding(env, 'MY_API_KEY', runtime);
```

### `inferRuntimeEnvironment(request, config?)`

Infers `'development' | 'preview' | 'production'` from the request hostname.

- `localhost` / `127.0.0.1` → `'development'`
- Matches `previewHosts` or ends with `.pages.dev` → `'preview'`
- Everything else → `'production'`

---

## Cron Scheduler (`import from '@sarfarajey/cf-workers-kit/scheduler'`)

Pure-JS 5-field cron parser and job runner for Cloudflare Workers.

```js
import { matchesCronExpression, selectDueJobs } from '@sarfarajey/cf-workers-kit/scheduler';

// Test a single expression
matchesCronExpression('0 6 * * *', new Date()); // true at 06:00 UTC

// Pick which jobs from a config are due right now (with lookback for missed fires)
const config = {
  version: 1,
  jobs: [
    {
      id: 'nightly-report',
      binding: 'REPORT_WORKER',
      schedule: '0 2 * * *',
      enabled: true,
    }
  ]
};
const due = selectDueJobs(config, new Date());
```

**Supported syntax:**

| Pattern | Meaning |
|---------|---------|
| `*`     | Every value |
| `5`     | Exact value |
| `1-5`   | Range |
| `*/15`  | Step from min |
| `1-30/2`| Step within range |
| `MON-FRI` | Day-of-week names |
| `JAN,JUL` | Month names, comma-separated |

## License

MIT