npm package: @sveltext/cloudflare-platform-runtime
Description: Lightweight runtime helpers for cron and queues on Cloudflare, remote-functions style.
Latest version: 0.1.0

# @sveltext/cloudflare-platform-runtime

Tiny helpers that make Cloudflare cron and queue jobs feel like SvelteKit remote functions. Use them in `.server.ts` / `.remote.ts` modules and let [`@sveltext/adapter-cloudflare-platform`](../adapter-cloudflare-platform/README.md) embed the metadata into your Worker bundle.

---

## Features at a glance

| Helper | Purpose | Notes |
| ------ | ------- | ----- |
| `cron(config, handler)` | Declare scheduled jobs using Cloudflare cron syntax (`CRON_TZ` compatible). | Supports manual HTTP triggers (`manual: true` or `manual: { route, headers }`). |
| `queue(config, consumer?)` | Define queue producers and optional push consumers. | Automatically wires producer `.send()` implementations; supports DLQ + named queues. |
| `withEdgeCache(body, opts)` | Convenience helper to set public cache headers on responses. | Default `s-maxage=300`, `max-age=0`. |

Everything is ESM-friendly and tree-shakeable.

---

## Installation

```bash
pnpm add @sveltext/cloudflare-platform-runtime
# plus @sveltext/adapter-cloudflare-platform in your SvelteKit adapter config
```

---

## Usage

### Cron jobs

```ts
// src/routes/+page.server.ts
import { cron } from '@sveltext/cloudflare-platform-runtime';

export const nightly = cron(
  {
    schedule: '0 3 * * *',                // 03:00 UTC
    manual: { route: 'nightly', headers: { 'x-secret': 'dev-token' } },
    name: 'nightly-report'
  },
  async ({ platform, log }) => {
    const { DB } = platform.env;
    await DB.prepare('DELETE FROM sessions WHERE expires_at < datetime()').run();
    log.info('Expired sessions removed');
  }
);
```

### Queue producers & consumers

```ts
import { queue } from '@sveltext/cloudflare-platform-runtime';

type EmailJob = { to: string; subject: string; body: string };

export const sendEmail = queue<EmailJob>(
  {
    binding: 'MAILS',             // matches wrangler queues.producers[].binding
    queue: 'project-mails',       // optional but validates config
    consume: true,                // register a consumer in this worker
    deadLetter: 'MAILS_DLQ',
    name: 'email-dispatch'
  },
  async (message, { env, log }) => {
    log.info('sending email to', message.body.to);
    await env.MAIL_SERVICE.fetch('https://api.mail/send', {
      method: 'POST',
      body: JSON.stringify(message.body)
    });
    message.ack();
  }
);

// Later in your app:
await sendEmail({ to: 'hi@example.com', subject: 'Hello', body: '…' });
```

### Edge caching helper

```ts
import { withEdgeCache } from '@sveltext/cloudflare-platform-runtime/cache';

export const GET = () => {
  const data = JSON.stringify({ status: 'ok' });
  return withEdgeCache(new Response(data, { headers: { 'content-type': 'application/json' } }), {
    sMaxAge: 60,
    vary: ['authorization']
  });
};
```

---

## Wrangler configuration

The runtime reads job metadata from the module system; the adapter ensures Wrangler configuration matches by validating:

- `triggers.crons` – must cover every schedule declared via `cron()`.
- `queues.producers` / `queues.consumers` – must include the `binding`, `queue`, and (if provided) `dead_letter_queue`.
- `compatibility_date >= 2024-10-22` when workflows are present (necessary for remote functions).

Example `wrangler.toml` excerpt:

```toml
[[triggers.crons]]
schedule = "0 3 * * *"

[[queues.producers]]
binding = "MAILS"
queue = "project-mails"

[[queues.consumers]]
queue = "project-mails"
dead_letter_queue = "project-mails-dlq"
```

---

## Migration tips

Coming from manual exports (e.g. `export const onRequest = ...` or raw queue consumers):

1. Replace your functions with `cron()` / `queue()` calls.
2. Remove manual registration logic in `hooks.server.ts` or custom workers – the adapter embeds the metadata automatically.
3. Update `wrangler.toml` according to the validation hints produced during `pnpm build`.

The runtime intentionally mirrors SvelteKit remote functions: the default export is callable (producer), and metadata lives on a non-enumerable `META` symbol.

---

## API reference

### `cron(config, handler)`

| Option | Type | Default | Description |
| ------ | ---- | ------- | ----------- |
| `schedule` | `string \| string[]` | – | CRON expression(s) in Cloudflare syntax. |
| `manual` | `boolean \| { route?: string; headers?: Record<string,string>; allowWhenDisabled?: boolean }` | `false` | Enables manual HTTP triggering via the adapter’s `manual.base`. |
| `manualHeaders` | `Record<string,string>` | – | Deprecated – use `manual.headers`. |
| `name` | `string` | Derived from export identifier | Surfaced in logs and validation hints. |

The handler signature is `(ctx: CronCtx) => any`, where `ctx.platform.env` matches your Wrangler bindings.

### `queue(config, consumer?)`

| Option | Type | Description |
| ------ | ---- | ----------- |
| `binding` | `string` | Queue binding name (`wrangler.toml` producers). |
| `queue` | `string?` | Explicit queue name (`queues.producers[].queue`). |
| `consume` | `boolean \| string \| { queue: string }` | Register a consumer in this worker; string/object lets you specify the queue name used in `wrangler.toml`. |
| `deadLetter` | `string?` | Dead-letter queue binding. |
| `name` | `string?` | Friendly name for logs / hints. |

If `consumer` is omitted, the helper only registers a producer (useful for remote-only queues).

### `withEdgeCache(body, opts?)`

`opts` shape:

```ts
{
  sMaxAge?: number; // default 300s
  maxAge?: number;  // default 0
  vary?: string[];  // adds Vary header
}
```

---

## Typings & ambient declarations

The package ships ESM `dist/` bundles with TypeScript declaration files. An ambient definition augments `App.Platform` and supplies `ImportMeta['url']` so `import.meta.url` works under strict TS configs.

---

## Troubleshooting

| Issue | Explanation |
| ----- | ----------- |
| Missing `import.meta.url` in types | Ensure your tsconfig extends the ambient declarations (the package ships them automatically). |
| Queue producers throw “producer not initialised” in dev | The adapter stubs producers only when it’s aware of the binding. Check that `wrangler.toml` defines the binding and re-run `pnpm build`. |
| Cron job silently skips | Verify `triggers.crons` covers every schedule. Build output includes hints if something’s missing. |

---

Need a deeper dive into the worker integration? See the adapter README – it covers job serialisation, manual triggers, validation messages, and the `emulate().jobs` helper. Happy shipping! 🚀