Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/samgutentag/bcycle-map/llms.txt

Use this file to discover all available pages before exploring further.

BCycle Map runs on three Cloudflare Workers that each own a distinct slice of the data pipeline: a scheduled poller that fetches GBFS feeds, an HTTP Worker that serves every /api/* endpoint the frontend calls, and a daily smoke Worker that runs a live end-to-end poll against every enabled system and files a GitHub Issue on any failure. This page covers manual deployment with Wrangler, the automated CI flow, log tailing, and post-deploy verification.

The three Workers at a glance

WorkerConfig fileEntry pointTrigger
bcycle-map-pollerwrangler.tomlsrc/workers/poller.tsCron every 5 min
bcycle-map-read-apiwrangler.read-api.tomlsrc/workers/read-api.tsHTTP fetch
bcycle-map-smokewrangler.smoke.tomlsrc/workers/smoke.tsCron daily at 09:00 UTC

bcycle-map-poller

The poller is the heartbeat of the system. Every five minutes its scheduled handler calls pollOnce() for each enabled entry in systems.json, fetches the GBFS discovery feed and the three required sub-feeds (station_information, station_status, system_information), normalizes them into an internal KVValue shape, and writes two KV keys per system: a system:<id>:latest key for the read API to serve, and a system:<id>:buffer:<yyyy-mm-dd-hh> key that accumulates the hour’s snapshots for compaction into R2 parquet. At 5-minute cadence the poller issues 288 invocations per day per system — well under the Workers Free tier cap of 1,000 cron invocations per day.
Each active system costs approximately 576 KV puts per day (2 puts × 288 ticks). The Workers Free tier allows 1,000 KV writes per day, so two active systems already exceeds the cap. Disable a system in systems.json before adding a second one, or upgrade to Workers Paid.

bcycle-map-read-api

The read API is a plain HTTP Worker — no cron, no bindings beyond KV and R2. It routes incoming requests by URL pattern to one of several handlers:
Endpoint patternWhat it returns
GET /api/systems/:id/currentLatest KV snapshot for a system
GET /api/systemsCross-system index from R2 (systems-index.json)
GET /api/systems/:id/activityRolling activity log from R2
GET /api/systems/:id/tripsTrips derived from R2 parquet over an arbitrary window
GET /api/systems/:id/snapshotsDown-sampled station-level bike counts from R2 parquet
GET /api/systems/:id/partitionsR2 parquet partition keys for a time range
GET /api/systems/:id/stations/:sid/recentPre-computed typical hourly availability profile
POST /api/beaconAnalytics pageview / event ingest
GET /api/insightsAggregated analytics events
GET /api/geocodeAddress → lat/lng proxy (uses server-side Google Maps key)

bcycle-map-smoke

The smoke Worker runs daily at 09:00 UTC. It calls pollOnce() — the same function the poller uses — against every enabled system, but without writing to KV or R2. If pollOnce() throws for any system (fetch error, missing sub-feed, or normalization failure), fileIssueIfNoneOpen() opens a GitHub Issue labelled smoke-failure on the configured repo. A second failure on the same day appends a comment rather than opening a duplicate issue.
The smoke Worker requires two Worker secrets — GITHUB_TOKEN and GITHUB_REPO — to be set via npx wrangler secret put. If either is absent, the Worker logs a warning and exits without filing an issue.

Deploying manually with Wrangler

Run these three commands from the repo root after npm install. Each command reads its respective TOML file and uploads a fresh Worker bundle:
npx wrangler deploy                                    # bcycle-map-poller
npx wrangler deploy --config wrangler.read-api.toml    # bcycle-map-read-api
npx wrangler deploy --config wrangler.smoke.toml       # bcycle-map-smoke
Each command prints the deployed *.workers.dev URL. Save the read-API URL — the frontend needs it in VITE_API_BASE.
The KV namespace IDs in each TOML file must be populated before deploying. If the files still contain PLACEHOLDER_REPLACE_AT_DEPLOY, run npx wrangler kv namespace create GBFS_KV, capture the returned id and preview_id, and paste them into all three TOML files.

Automated deployment via GitHub Actions

The deploy-workers.yml workflow handles CI deployment on every push to main. It avoids redeploying all three Workers when only one changed.

How change detection works

A changes job runs first and outputs three boolean flags — poller, read_api, smoke — based on a diff of the files touched by the push:
  • Changes to src/shared/**, package.json, package-lock.json, or the workflow file itself mark all three Workers for redeploy (shared code touches every bundle).
  • Changes to src/workers/poller.ts or wrangler.toml mark only the poller.
  • Changes to src/workers/read-api.ts or wrangler.read-api.toml mark only the read API.
  • Changes to src/workers/smoke.ts or wrangler.smoke.toml mark only the smoke Worker.
Each of the three deploy jobs is gated on its flag: if: needs.changes.outputs.poller == 'true', and so on.

Manual dispatch with target selection

The workflow also accepts workflow_dispatch with a target input so you can push a single Worker without a code change:
gh workflow run deploy-workers.yml -f target=all
The workflow requires two repository secrets:
SecretWhere to find it
CF_ACCOUNT_IDnpx wrangler whoami
CLOUDFLARE_API_TOKENCloudflare → My Profile → API Tokens → Workers deploy template

Tailing Worker logs

Stream live log output from any Worker using wrangler tail:
npx wrangler tail bcycle-map-poller
npx wrangler tail bcycle-map-read-api
Each cron tick prints a line per system with any console.log, console.warn, or console.error calls from the handler. KV put failures (e.g., daily cap exhausted) surface here before they affect the frontend.

Verifying a successful deployment

After the first 5-minute cron tick fires, the latest KV snapshot should be populated. Verify with a direct API call:
curl https://bcycle-map-read-api.<account>.workers.dev/api/systems/bcycle_santabarbara/current
A successful response is a JSON object containing a stations array of ~85 entries. A 404 not found means the cron hasn’t fired yet — wait up to five minutes and retry.
The Workers Free tier cron scheduler is eventually consistent. The first tick after a fresh deploy may fire up to a full cron interval late. If the endpoint still returns 404 after ten minutes, check wrangler tail bcycle-map-poller for errors.

Build docs developers (and LLMs) love