Viewpoint: the system's components and the interfaces between them.
flowchart TB
subgraph browser [Browser]
pages[Prerendered pages]
hydration[Client hydration - src/lib/*-api.ts]
leaflet[Leaflet map]
end
subgraph worker [Worker - src/worker]
router[Router - index.ts]
handlers[Handlers - handlers/*.ts]
queries[Query builders - queries.ts, surfer-queries.ts]
docs[API docs - openapi.json, /api/docs]
sched[scheduled - Instagram scoring + cache refresh]
end
assets[Static assets - dist/ via ASSETS binding]
d1[(D1 SPOTS_DB)]
r2[(R2 public bucket)]
graphapi[Instagram Graph API]
pages --> hydration
hydration -->|GET /api/*| router
leaflet -->|GET /api/spots/map?bbox| router
router -->|non-API paths| assets
router --> handlers
handlers --> queries --> d1
router --> docs
pages -->|media URLs| r2
sched --> graphapi
sched --> d1
Interface contracts:
| Interface | Contract |
|---|---|
| Browser ↔ Worker API | OpenAPI 3.1 spec at docs/api/openapi.yaml; JSON envelopes; errors {error, code}; GET only |
| Worker ↔ D1 | Prepared statements via the SPOTS_DB binding; FTS5 for search |
| Worker ↔ static assets | ASSETS.fetch fallthrough; /spots/:slug + /surfers/:slug rewritten to __detail shells |
| Site ↔ R2 | Public bucket URLs baked into payloads (hero images, clips) |
| Cron ↔ Instagram Graph API | Best-effort; results cached in D1 (instagram_feed_cache, instagram_account_scores) |
Layer rules (machine-checked by npm run health via dependency-cruiser): no circular
dependencies; src/lib never imports src/worker; src/** never imports scripts/**.