The whole surface,part by part
DjiniousMap is one Bun API over two databases, with a React client on top. This page walks the whole surface: what harvests data, what measures it, what draws it, what computes on it, and what keeps every tenant's rows out of every other tenant's queries.
Ingest
Public harvest
A harvester registers datasets from data.gouv.fr, keeping the publisher's own external identifier, source URL, licence and update frequency. Re-harvesting updates the registration in place rather than creating a duplicate.
Connections
Register your own sources and create datasets from them. A connection carries its credentials and its reachability separately from the datasets that use it, so rotating a secret does not orphan a catalogue entry.
Materialization
Copying a dataset's features into the platform is an explicit act with its own job. It downloads the source, detects the format, and inserts in batches into a table created for that dataset alone — `managed.ds_<id>`, one table per dataset, never a shared feature store.
Formats
GeoJSON, GeoPackage and the compressed variants the French open-data catalogue actually publishes, including gzipped GeoJSON measured in hundreds of megabytes. Ingest streams rather than buffering, so source size is bounded by disk, not by memory.


Measurement
CRS inference
The coordinates are measured and matched against candidate projections, producing an inferred EPSG code and a confidence. The publisher's declared code is recorded beside it and never overwritten. When the two disagree, the platform says so and draws from the measured one.
The profile
Feature count, geometry types, spatial index presence, null and invalid geometry rates, temporal columns, declared and inferred SRID, and the WGS 84 bounding box. The bbox is computed in PostGIS because `ST_Transform` lives there and nowhere else.
Quality findings
Each finding states what was measured, why it matters, and the remediation. Excess coordinate precision quantifies the implied accuracy against survey-grade GNSS; stale data names the column and the threshold; invalid geometry reports a rate rather than a verdict.
Undetermined is a result
When the inputs a finding needs are missing — no declared update frequency, no owner, no date-shaped column — the verdict is Undetermined and the page says which inputs were absent. A guess presented as a measurement would be worse than no answer.
Suitability
A dataset that cannot be placed on a map is flagged as unsuitable for visualization, with the finding that caused it. The table preview keeps working: the data is not unusable, it is unmappable, and the platform distinguishes the two.


Composition
Layers
Materialized datasets become layers with their own style, opacity, order and visibility. Fill, stroke, stroke width and point radius are set per layer, and the paint resolves through the product's design tokens, so switching brand palette repaints the map along with the chrome.
Filters
Categorical, range and temporal filters per layer, composable and shareable. A filter authored by a widget is tracked back to it, so removing the widget removes the filters it authored — atomically, rather than leaving orphans behind.
Widgets and cross-filtering
Category, histogram, formula and time-series widgets. Selecting in one widget filters the map and every other widget, which is what makes a multi-layer map explorable rather than merely legible.
Time replay
Any column the profiler identified as temporal can drive a timeline: scrub it, or play it, and the map redraws over the period. The rail appears only when such a column exists, so it never implies a time dimension the data does not have.
Versioned assets
A map is an asset with immutable published versions, a slug and a current version pointer — the same model as a workflow. Publishing freezes a document and hashes its body, so a shared view is exactly the one that was shared.


Computation
Spatial analysis
Run analyses over materialized layers and land the result back in the catalogue as a dataset of its own, profiled like any other. An output is therefore a first-class input, and the lineage is a catalogue relationship rather than a filename convention.
Isochrones
Travel-time polygons from a point or from every feature in a layer, through a pluggable routing provider. The result is a dataset, so it can be styled, filtered and joined like anything else.
Routing
Point-to-point routes with the same provider abstraction, returning a geometry the studio can draw.
Geocoding, both directions
Forward geocoding turns an address column into points; reverse geocoding turns points into addresses. Both run as jobs over a whole dataset rather than one row at a time.
The job queue
Anything that takes time runs on a leased queue with heartbeats. A worker that dies mid-run stops heartbeating and its lease expires, so the job is picked up again instead of being stranded in a running state forever.


Orchestration
Workflow canvas
Author a pipeline as a graph, configure each node, and publish it as a versioned asset. A run streams its progress live, and the stream is durable: reconnecting replays the run from its event log rather than losing what happened while you were away.
Triggers
Automations bind a trigger to a published workflow version. Schedule triggers fire on a cadence; event triggers fire on platform events such as `dataset.refreshed`, so a rebuild follows a harvest without anybody watching for it.
Last-run status
Each automation carries the outcome of its most recent run, so a pipeline that has been failing quietly for a week is visible on the list page rather than only in a log.


Distribution
Public share links
Publish a map behind a share token for readers with no account. The token scopes what is readable; it is not a bypass of the tenancy model but a principal within it.
Embeds
The same viewer runs in embed mode for putting a live map inside another page — including a conversational embed, where the reader asks questions of the map rather than operating its controls.
Provider Interface v1
`map-provider@1` exposes the platform as a service to sibling Djinious applications: scoped service tokens, a versioned operation surface under `/api/provider/v1`, and `/ready`, `/version` and `/metrics` for the operator.


Two databases, and the reason there are two
Everything above rests on a split that is unusual enough to be worth stating plainly: the platform's records live in SurrealDB, and only geometry lives in PostgreSQL.
- SurrealDB owns
- Organizations, users, credentials, sessions, memberships; projects, assets and their immutable versions; datasets, profiles, suitability and quality findings; the audit log; the job queue.
- PostGIS owns
- The materialized feature tables — one per dataset — plus the handful of projected rows the geometry's own policies and functions have to read.
- Why split at all
- Every SurrealDB table is SCHEMAFULL with a PERMISSIONS clause per verb, evaluated against the record the connection signed in as. Geometry stays in PostGIS because ST_Transform, spatial indexes and row-level security on feature tables live there.
- The cost, named
- Three PostgreSQL rows are projected from SurrealDB records, and a projection can drift. A reconcile command enumerates both sides and exits non-zero when they disagree; it is scheduled, not hoped for.
- Identity
- An HttpOnly session cookie holding a 256-bit random token, of which only the SHA-256 hash is stored. Request headers are never consulted for identity.
Walk the surface with us
A demo goes through this page in the running product — harvest something, watch it profile, compose it, and run an analysis on it inside the hour.