wrangler.jsonc
Language JSON5.9 KB132 lines
{"$schema": "node_modules/wrangler/config-schema.json","name": "meridian-dashboard","main": "src/index.ts","compatibility_date": "2026-08-24",// Served on <name>.<account-subdomain>.workers.dev. Cloudflare Access// protects workers.dev URLs, so no custom domain and no DNS change to// meridiandisplay.example is required. That matters: the public site's zone is// managed separately, and repointing it to serve an internal board would be// real risk for no benefit.//// Preview URLs default to matching this setting. They are a second door to// the same client data, so cover them in the same Access application or turn// them off. Do not leave one protected and the other open."workers_dev": true,// Cron: 11:00 UTC = 06:00 America/Chicago in CDT, 05:00 in CST.//// Cloudflare crons are UTC only, so this shifts by an hour twice a year. The// page shows `syncedAt` in Meridian time, so a drifted run is visible rather than// silent. If the winter hour matters, add a second trigger and let the// duplicate run overwrite: it is idempotent."triggers": {// Three crons; the Worker branches on which fired.//// The morning and 15-minute crons both run the full sync: statuses move// all day, and a board showing the 6am numbers at 3pm is one people learn// to distrust.//// The difference is history: only the 11:00 run records the day's row, so// the day-over-day arrows keep comparing mornings instead of quarter-hours.// See sync() in src/index.ts.//// What this costs: a couple of dozen upstream requests per sync (the Bitrix// lead pages, a few Plinth pages, Ninety, the tracker tabs) plus the Gmail// reads for the inbox. It costs no model spend: the written briefings come from a// laptop, not from here.//// At 11:00 both patterns fire and Cloudflare invokes twice. That is// harmless: every write here is idempotent, and the two runs compute the// same numbers.//// The every-minute cron is the Sync now button's runner. ctx.waitUntil() in// a fetch handler does not get the budget a scheduled one does, and a// two-minute sync does not fit in it: run from the route, it is cut off// with nothing written. So the button records a request and this cron runs// it, in the context that can. It does nothing at all on the minutes there// is no request pending, which is almost all of them."crons": ["0 11 * * 1-5", "*/15 * * * *", "* * * * *"]},"kv_namespaces": [{"binding": "DASHBOARD",// Replace with the id printed by:// wrangler kv namespace create DASHBOARD"id": "00000000000000000000000000000000",// A separate namespace for `wrangler dev --remote`, which refuses to start// without one. That refusal is doing something useful: --remote runs on// Cloudflare's edge with the real secrets, so without this a test sync// would write real pages and a real inbox read into production KV and the// team would be served whatever a half-finished experiment produced.//// With it, a remote dev run is fully live against Google, Bitrix, Plinth and// Ninety (which is the point, since Worker secrets are the only copy of// the Gmail key) while writing somewhere nobody is reading.//// wrangler kv namespace create DASHBOARD --preview"preview_id": "11111111111111111111111111111111"}],// D1: the end-of-day note and the private list, and the only SQL on this// board. Everything else the board owns is a JSON blob in KV; a note is a// row a day per person, read back as a date range that must include the days// with nothing in them, and that is a table (rule 14). See// apps/worker/migrations/ for the reasoning in full.//// If `wrangler d1 create` is refused with "Authentication error [code:// 10000]" while the printed OAuth scopes include d1:write, re-mint with// `wrangler login`: the cached scope list is wrangler's own record, not// Cloudflare's, and the two can disagree.//// The binding is optional in Env, and stays that way. A Worker that lacks// this binding says the note store is not set up, in those words. It// does not say "no notes written". An unconfigured store and an empty one are// different facts and the board never renders them the same way."d1_databases": [{"binding": "CHECKINS","database_name": "meridian-checkins",// Replace with the id printed by `wrangler d1 create meridian-checkins`."database_id": "00000000-0000-0000-0000-000000000000","migrations_dir": "migrations"}],"vars": {// Widen if orders routinely run longer than this. Each extra day is more// Plinth paging, and Plinth caps at 10 requests / 10 seconds."PLINTH_WINDOW_DAYS": "60",// Who the Worker trusts to say who you are. Both come from the Access// application; neither is a secret. The team domain is in every login URL// and the AUD tag identifies an app, it does not authorise anything.//// Until both are set the Worker refuses every request. That is the intended// order: it cannot verify an assertion it has no keys for, and a board// carrying client names must not fall open while Access is half-configured.//// team domain Zero Trust > Settings > Custom Pages, or any login URL// AUD tag Zero Trust > Access > Applications > (this app) > Overview"ACCESS_TEAM_DOMAIN": "your-team.cloudflareaccess.com","ACCESS_AUD": "REPLACE_WITH_ACCESS_AUD_TAG","DEBUG": "false"},// Secrets are not in this file and must never be. Set them with:// wrangler secret put NINETY_TOKEN// wrangler secret put BITRIX_WEBHOOK_URL// wrangler secret put PLINTH_API_KEY//// NINETY_TOKEN must belong to a company Owner or Admin. GET /todos/company// returns 403 for every other role."observability": {"enabled": true}}