doxa-maps/library/api
doxa-maps d5ba21fa76 doxa-maps — portable bundler for embeddable JAMstack maps
Standalone repo that builds embeddable JAMstack map bundles (web components),
composes them into a lazy-loaded SPA via layouts/, and optionally wraps a
Capacitor Android/iOS app — one build, choose your output/CDN. Diátaxis docs.

History reset to a single clean commit; no PII, IPs, or internal terms.
2026-09-10 01:53:07 +00:00
..
_registry.js doxa-maps — portable bundler for embeddable JAMstack maps 2026-09-10 01:53:07 +00:00
apiBaseUrl.js doxa-maps — portable bundler for embeddable JAMstack maps 2026-09-10 01:53:07 +00:00
DataSourceManager.js doxa-maps — portable bundler for embeddable JAMstack maps 2026-09-10 01:53:07 +00:00
README.md doxa-maps — portable bundler for embeddable JAMstack maps 2026-09-10 01:53:07 +00:00
sources.json doxa-maps — portable bundler for embeddable JAMstack maps 2026-09-10 01:53:07 +00:00

API Connections — modular seam

One of the three customization seams (colors/, api/, i18n/). The Apache template ships proven connections; you build your OWN private app-profile with your OWN API by adding a connection — never by editing the engine.

This seam decides where people-group data comes from and how its raw fields map onto the system field names the map/legend/detail components read. The components are source-agnostic: only the connection knows the source's shape.

The three parts

File Role You touch it?
sources.json The customization surface. Declares each connection (type, endpoints/path/url, fieldMappings, imageConfig) and which one is activeSource. Yes — add your connection here.
apiBaseUrl.js Resolves the rest-api base URL at runtime (window.MAP_APP_API_URL → VITE_API_BASE_URL → ''). Lets the host set the URL with no rebuild. Only to read the priority order.
DataSourceManager.js The engine: fetch → normalize → per-source cache. Maps raw rows to system fields, coerces booleans, resolves image URLs. No — shared machinery.
_registry.js The front door: SOURCE_TYPES, SOURCES, getActiveSourceId(), getSourceConfig(), listSources(), plus re-exports of getApiBaseUrl + DataSourceManager. Import from here.

sources.json lives in this folder (library/api/), alongside the engine it drives. Old imports of @map/utils/apiBaseUrl.js and @map/utils/DataSourceManager.js still work via back-compat shims left in ../utils/.

The connection contract

Every entry under sources in sources.json MUST have:

"my-source": {
  "id":   "my-source",          // REQUIRED — must equal the key. listSources()/getSourceConfig() key on it.
  "name": "My Source",          // REQUIRED — human label for any source-picker UI.
  "type": "rest-api",           // REQUIRED — one of: "csv" | "api" | "rest-api" (SOURCE_TYPES).
  // --- locator: pick the one your type needs ---
  "path": "assets/data/my.csv",                 // csv  → fetched relative to MAP_APP_BASE_URL
  "url":  "https://example.com/all.json",        // api  → a single JSON URL
  "endpoints": { "bulk": "/api/people-groups/list" }, // rest-api → base URL comes from apiBaseUrl.js
  "defaultFields": ["slug","name","latitude"],   // rest-api → appended as ?fields=
  "queryParams": { "lang": "en" },               // rest-api → static query params
  // --- the mapping every type needs ---
  "fieldMappings": {                             // REQUIRED — systemField: sourceField (or [a,b] composite)
    "uniqueId": "slug", "name": "name",
    "latitude": "latitude", "longitude": "longitude"
    // …map every system field your map needs; see "systemFields" in sources.json
  },
  "imageConfig": { "useField": "imageUrl", "placeholderUrl": "…" }
}
  • uniqueId, name, latitude, longitude are required system fields — a pin with lat===0 && lng===0 is dropped during normalization.
  • A fieldMappings value may be an array (composite key, joined with _).
  • rest-api rows often arrive as { value, label } objects — DataSourceManager.normalizeData unwraps these to field + fieldLabel automatically.
  • Never inline data or stub the API — connections call the real source. (See the never-stub-always-real-api rule; pray-tools is the live default.)

Add a custom connection (full checklist)

  1. Add an entry under sources in sources.json (this folder) following the contract above. If your source is a REST API on the pray-tools shape, copy the pray-tools entry and edit endpoints / defaultFields / fieldMappings.
  2. Map every system field your map reads. Unmapped fields normalize to ''/0; for CSV, validateFieldMappings() logs a [DataSourceManager] warning naming the missing columns.
  3. Set activeSource to your connection's id (or leave the default and pass the id to dataSourceManager.setActiveSource(id)).
  4. For a rest-api source, give the host the base URL — inject before the bundle loads:
    <script>window.MAP_APP_API_URL = 'https://api.example.com';</script>
    
    (or set VITE_API_BASE_URL at build time). See apiBaseUrl.js for the full priority order.
  5. Verify — resolve and load it:
    import { DataSourceManager, listSources } from '@map/api/_registry.js'
    console.log(listSources())                       // your source appears, active flag correct
    const dsm = new DataSourceManager()
    await dsm.init()
    const rows = await dsm.setActiveSource('my-source')
    console.log(dsm.getStats())                       // totalRecords > 0, coords/photos sane
    

How it integrates with the component library

ProfileLoader.vue / the app-profile creates a DataSourceManager and hands it to composables/useMapData.js (the instance is passed in, not imported — one per <doxa-map>). useMapData calls dsm.init() then dsm.setActiveSource(id), caches the normalized rows in the Pinia dataStore, and feeds them to the map layers. Downstream code only ever sees the system field names from your fieldMappings — which is exactly why a new connection needs zero component changes.