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. |
||
|---|---|---|
| .. | ||
| _registry.js | ||
| apiBaseUrl.js | ||
| DataSourceManager.js | ||
| README.md | ||
| sources.json | ||
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,longitudeare required system fields — a pin withlat===0 && lng===0is dropped during normalization.- A
fieldMappingsvalue may be an array (composite key, joined with_). - rest-api rows often arrive as
{ value, label }objects —DataSourceManager.normalizeDataunwraps these tofield+fieldLabelautomatically. - Never inline data or stub the API — connections call the real source. (See the
never-stub-always-real-apirule;pray-toolsis the live default.)
Add a custom connection (full checklist)
- Add an entry under
sourcesinsources.json(this folder) following the contract above. If your source is a REST API on the pray-tools shape, copy thepray-toolsentry and editendpoints/defaultFields/fieldMappings. - Map every system field your map reads. Unmapped fields normalize to
''/0; for CSV,validateFieldMappings()logs a[DataSourceManager]warning naming the missing columns. - Set
activeSourceto your connection's id (or leave the default and pass the id todataSourceManager.setActiveSource(id)). - For a rest-api source, give the host the base URL — inject before the bundle loads:
(or set<script>window.MAP_APP_API_URL = 'https://api.example.com';</script>VITE_API_BASE_URLat build time). SeeapiBaseUrl.jsfor the full priority order. - 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.