taxa-server 0.1.0

axum web server for taxa: reproduces the HTTP contract + serves the embedded D3 frontend.
// Thin fetch wrappers for the taxa generic data routes. The manifest is
// fetched once at boot (app.js) and shared via getManifest().

// App base path (server-injected window.__BASE__): "" at root, "/<app>" behind the
// internal LB. fetch() isn't affected by <base href>/import maps, so prefix it here.
const BASE = (typeof window !== "undefined" && window.__BASE__) || "";

async function _get(path) {
  const url = BASE + path;
  const r = await fetch(url);
  if (!r.ok) throw new Error(`${url}: ${r.status} ${r.statusText}`);
  return r.json();
}
async function _post(path, body) {
  const url = BASE + path;
  const r = await fetch(url, {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify(body),
  });
  if (!r.ok) throw new Error(`${url}: ${r.status} ${r.statusText}`);
  return r.json();
}

// Entity ids may contain slashes (e.g. "owner/repo"); keep them as real path
// separators (the routes use a {path} converter) while encoding everything else.
const _eid = (id) => encodeURIComponent(id).replace(/%2F/g, "/");

// Multi-dataset: per-dataset calls are prefixed with /d/<id>; the default dataset
// is also served at top-level /api (empty base). `/api/datasets` (the switcher
// list) and `/static/*` are NOT dataset-scoped.
let _base = "";

export const API = {
  setDataset: (id) => { _base = id ? `/d/${encodeURIComponent(id)}` : ""; },
  datasets:   () => _get("/api/datasets"),
  manifest:   () => _get(`${_base}/api/manifest`),
  treemap:    (body) => _post(`${_base}/api/treemap`, body),
  geo:        (body) => _post(`${_base}/api/geo`, body),
  // The group/per-branch series view (treemap.js consumes this via timeseries.js).
  timeseriesTreemap: (body) => _post(`${_base}/api/series`, body),
  scatter:    (body) => _post(`${_base}/api/scatter`, body),
  search:     (q, axis) => _get(`${_base}/api/search?q=${encodeURIComponent(q)}` +
    (axis ? `&axis=${encodeURIComponent(axis)}` : "")),
  // Entity detail.
  entity:     (id) => _get(`${_base}/api/entity/${_eid(id)}`),
  entitySeries: (id, metric, window, resolution) =>
    _get(`${_base}/api/entity/${_eid(id)}/series` +
         `?metric=${encodeURIComponent(metric)}&window=${encodeURIComponent(window)}` +
         `&resolution=${encodeURIComponent(resolution || "d")}`),
  entityOhlc: (id, window, resolution) =>
    _get(`${_base}/api/entity/${_eid(id)}/ohlc` +
         `?window=${encodeURIComponent(window)}&resolution=${encodeURIComponent(resolution || "d")}`),
  filterOptions: (facet) => _get(`${_base}/api/filter-options/${encodeURIComponent(facet)}`),
};