UNPKG

ethercalc

Version:

Multi-User Spreadsheet Server — TypeScript rewrite (Cloudflare fullstack)

170 lines (154 loc) 7.14 kB
/** * Environment bindings available to the Worker at runtime. Kept in one place * so Hono's generic type argument stays in sync with `wrangler.toml`. * * Runtime caveat: under standalone workerd (`config.capnp` * `fromEnvironment` bindings), an env var that is UNSET in the process * environment arrives here as `null`, not `undefined` and not `''`. * Guard optional string fields with truthiness or `!= null`, never * `!== undefined` (see `lib/room-index-access.ts`). */ export interface Env { /** One Durable Object per spreadsheet room. */ ROOM: DurableObjectNamespace; /** * D1 binding — cross-room index mirror (Phase 5.1). Authoritative room * state lives in the DO's own storage; this table provides the * cross-room query surface used by `/_rooms`, `/_roomlinks`, * `/_roomtimes`. Every snapshot mutation in `src/room.ts` upserts * here via `mirrorRoomToD1`; `DELETE /_do/all` removes the row via * `deleteRoomFromD1`. See AGENTS.md §3.3 and §10.2. * * Optional because Node unit tests construct `RoomDO` and the Hono * app without a Miniflare-bound D1; the mirror helpers (and the * `/_rooms*` handlers) no-op / return empty when the binding is * missing. */ readonly DB?: D1Database; /** * Static assets bound via Workers Assets. Used by the stateless route * layer to serve `index.html`, `start.html`, icons, `manifest.appcache`, * and so on. In local tests we may leave this undefined — routes fall * back to a 404 when ASSETS is absent (see `src/routes/assets.ts`). * * The asset pipeline itself (collecting the legacy repo-root files into * an asset dir) lands in Phase 11 (§8). Phase 4 only scaffolds the * binding so the rest of the wiring is ready. */ readonly ASSETS?: Fetcher; /** * HMAC secret used to gate `edit`/`view`/`app` routes and WS `execute` * commands. When unset, the server falls back to identity HMAC and * anonymous operation (matches legacy `--key` absent behavior; see * AGENTS.md §6.4). */ readonly ETHERCALC_KEY?: string; /** * Optional URL basepath prefix (legacy `--basepath`). Threaded through * the redirect builders so deployments behind a sub-path router stay * consistent. Defaults to empty string. */ readonly BASEPATH?: string; /** * DevMode flag. Legacy `src/main.ls` checked `fs.existsSync('.git')` to * decide whether to serve the dynamic `manifest.appcache` stub. Under * Workers we can't hit the filesystem, so we expose an explicit env * var: any truthy string (`'1'` or `'true'`) enables the DevMode path * which returns a fresh-timestamp CACHE MANIFEST body. Defaults to * off; production asset serving goes through `env.ASSETS` for the * static manifest file. */ readonly DEVMODE?: string; /** * Single-grain default room. When set, `GET /` 302-redirects into * `/<ETHERCALC_DEFAULT_ROOM>` instead of serving the "create new * sheet" landing page. Used by Sandstorm grains — where each grain * IS a single sheet — to take the user straight to the spreadsheet * on `/`. Unset on ethercalc.net (shared pool of rooms, where `/` * legitimately is a landing page). */ readonly ETHERCALC_DEFAULT_ROOM?: string; /** * Cloudflare `send_email` binding (Phase 9, §13 Q3). When present, * the cron/email layer wraps it via `BindingEmailSender`. When * unbound (Node unit tests, or deployments that omit the * `[[send_email]]` entry in wrangler.toml), callers fall back to * `StubEmailSender`. Shape is structural — we only invoke `.send()`. */ readonly EMAIL?: { send(message: { from: string; to: string; raw: string | ReadableStream<Uint8Array>; }): Promise<unknown>; }; /** * Default "from" address for outgoing mail (Phase 9). Must be a * verified sender in the Cloudflare dashboard for the bound zone. * Defaults to `noreply@ethercalc.invalid` when unset — the stub * sender ignores it entirely, so this only matters once `EMAIL` is * bound. */ readonly EMAIL_FROM?: string; /** * Operator bearer token that unlocks the legacy migration write routes * and hosted `POST /_/:room/pitr-restore`. When unset, those routes * return `404`; migration and recovery are operator-only actions, not * public surfaces. See `src/lib/migrate-auth.ts` for the verifier and * `src/routes/{migrate,rooms}.ts` for the Hono glue. * * Typical local flow: * echo 'ETHERCALC_MIGRATE_TOKEN="local-only"' > packages/worker/.dev.vars * ./bin/ethercalc # Miniflare * bun run migrate -- --input dump.rdb \ * --target http://127.0.0.1:8000 --token local-only */ readonly ETHERCALC_MIGRATE_TOKEN?: string; /** * Legacy room-index gate, kept for hosted/back-compat (`wrangler.toml` * pins `'1'` for production Cloudflare deploys; the CLI `--cors` flag * sets it too). When enabled it 403s the cross-room discovery * endpoints: `/_rooms`, `/_roomlinks`, `/_roomtimes`, and * `/_exists/:room`. CORS *headers* are no longer affected — they are * emitted unconditionally for embed compatibility. Sensible-fix note: * parsed via `flagEnabled` (boolean-string), so `'0'`/`'false'`/`'no'`/ * `'off'` now read as gate-OFF, where the pre-2026-06 code treated any * non-empty string as gate-ON. Prefer `ETHERCALC_DISABLE_ROOM_INDEX` * below for new configs. */ readonly ETHERCALC_CORS?: string; /** * Clearly named self-host switch for hiding cross-room discovery endpoints: * `/_rooms`, `/_roomlinks`, `/_roomtimes`, and `/_exists/:room`. When unset, * the Worker falls back to the legacy `ETHERCALC_CORS` gate above so hosted * deployments keep their existing default. */ readonly ETHERCALC_DISABLE_ROOM_INDEX?: string; /** * Room TTL in SECONDS (legacy `--expire` / Redis `EXPIRE` semantics, * §13 Q10). When set, the RoomDO's housekeeping `alarm()` wipes any * room whose `meta:updated_at` is older than this TTL — the DO-storage * equivalent of the legacy `EXPIRE snapshot-<room>`. Unset (the * default) means rooms live forever. Wired by `bin/ethercalc` into the * Miniflare env; on Cloudflare it's an optional `[vars]` entry. */ readonly ETHERCALC_EXPIRE?: string; /** * Optional per-IP HTTP rate limit for self-host (§13 Q7). Unset or * false-like → off (hosted deploys rely on the Cloudflare edge). * Bare `1`/`true`/`on` → 10 r/s with burst 30 (nginx recipe default). * Plain number `N``N` requests/s; `window:max``max` per `window` * seconds. Keyed on `CF-Connecting-IP` / first `X-Forwarded-For` hop. */ readonly ETHERCALC_RATELIMIT?: string; /** * Optional per-IP cap on room-creation routes (SH-3). Unset → off. * Bare `1`/`true` → 6 new rooms per minute per source. */ readonly ETHERCALC_ROOM_CREATE_LIMIT?: string; /** * When truthy, honour Sandstorm `X-Sandstorm-Permissions` (SH-6). * Set by `run_grain.sh`; inert on Cloudflare/Docker self-host. */ readonly ETHERCALC_SANDSTORM?: string; }