Some checks are pending
build-gate / build (push) Waiting to run
Driver 2026-09-21: 'a folder called deployment which will store the capacitor
folder, wordpress folder and tauri folder', and 'a folder called documentation
to store the contributing folder so docs are in one place'.
docs/ + contributing/ + CONTRIBUTING.md -> documentation/ (three doc homes into
one). capacitor/ + wordpress/ + src-tauri/ -> deployment/.
The public/ -> dist/ rename the Driver asked about is NOT here: after seeing the
measured blast radius (855 tracked files, 54 source refs, and public/ is the
served web-root four consumers point at) the Driver withdrew it - 'keep the
public folder the same'.
Path references were rewritten mechanically (258 across 87 files), but three
classes needed hand fixes because a prefix regex cannot see them:
- join(ROOT,'docs',...) in 10 internal scripts - a quoted SEGMENT, not a prefix
- tauri frontendDist '../public' -> '../../public', since src-tauri is now one
level deeper; check:tauri caught this exactly as the card predicted
- the WordPress contract test's dirname(__DIR__,2) -> 3, same reason
Also reverted one over-reach: the rewrite renamed a FINDING-CATEGORY label
'docs' in public-hygiene-check.mjs, which is a rule name in its own header, not
a path.
Verified: full build ALL GUARDS PASS, SRI consistent; check:tauri, check:mobile
(cap sync + webDir OK), check:wp-embed, quoted-scripts, capability-citations,
capability-exemplars, library-index, public-hygiene, parse and drop all pass.
88 lines
5.2 KiB
Markdown
88 lines
5.2 KiB
Markdown
# Architecture — data sources (the full contract)
|
|
|
|
> The simple overview is in `library/api/README.md`. This is the complete contract for
|
|
> **adding or overriding** a data connection.
|
|
|
|
The `api/` 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, so a new connection needs zero
|
|
component changes.
|
|
|
|
## The four 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 `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:
|
|
|
|
```jsonc
|
|
"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 (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:
|
|
```html
|
|
<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:
|
|
```js
|
|
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.
|