Warehouse Analytics Sources

Status: draft contract primitives for issue #184.

The SDK now exposes provider-neutral descriptors for CARTO-style analytics sources:

CARTO-style concept Honua descriptor
table source WarehouseTableSourceDescriptor / defineWarehouseTableSource()
SQL query source WarehouseQuerySourceDescriptor / defineWarehouseQuerySource()
tileset source WarehouseTilesetSourceDescriptor / defineWarehouseTilesetSource()
H3 source IndexedSpatialSourceDescriptor with kind: "h3-index"
Quadbin source IndexedSpatialSourceDescriptor with kind: "quadbin-index"
generic indexed source IndexedSpatialSourceDescriptor with kind: "indexed-spatial"

Descriptors live in @honua/sdk-js/contract. They are serializable metadata, not credential containers. SQL text, table identifiers, tileset ids, index resolution, cache partitions, and non-secret authorization scope ids belong in the descriptor. API keys, database passwords, OAuth tokens, and warehouse sessions belong in transport/control-plane configuration.

import {
  defineIndexedSpatialSource,
  defineQueryTileSource,
  buildAnalyticsSourceCacheKey,
} from "@honua/sdk-js/contract";

const incidentCells = defineIndexedSpatialSource({
  id: "warehouse.incidents.h3",
  provider: "carto",
  sql: {
    text: "select h3_cell, severity, exposure from incidents where status <> 'closed'",
    dialect: "postgres",
  },
  index: {
    modelId: "h3",
    cellIdField: "h3_cell",
    resolution: 8,
    hierarchy: "parent-child",
    coverage: { kind: "bounded", complete: false },
  },
  cache: {
    key: {
      sourceVersion: "materialized-view-17",
      authorizationScope: "role:ops",
    },
  },
  fallback: { mode: "disabled" },
});

const tiles = defineQueryTileSource({
  id: "incident-h3-tiles",
  source: incidentCells,
  endpoint: { baseUrl: "https://honua.example.com/query-tiles" },
  query: { where: "severity >= 3", outFields: ["severity", "exposure"] },
  projection: { fields: ["severity", "exposure"] },
  cache: { key: { styleFilters: { severity: "high" }, extra: { indexResolution: 8 } } },
});

const widgetCacheKey = buildAnalyticsSourceCacheKey(incidentCells, {
  operation: "widget",
  cache: {
    filters: { severity: ["critical", "high"] },
    indexResolution: 8,
    widgetProjection: { kind: "histogram", field: "exposure" },
  },
});

Cache And Pushdown

buildAnalyticsSourceCacheKey() includes source kind, table/query/tileset identity, SQL text, index model/resolution, source version, authorization scope, filters, and widget/style projections. Query-tile and widget helpers reuse the same descriptor key so maps, charts, and crossfilters share a source identity without sharing secrets.

assessAnalyticsSourcePushdown() reports whether a descriptor advertises sql, tiles, widgets, spatialAggregate, crossfilter, or metadata pushdown. Unsupported capabilities return structured DegradedReason objects. Large warehouse sources should use fallback: { mode: "disabled" } unless a server-owned bounded fallback is explicitly available; the SDK should not infer an unbounded browser materialization path.

Spatial Aggregation

SpatialAggregationRequest and SpatialAggregationMetadata can carry the same analytics descriptor. Index metadata now records hierarchy and coverage so H3, Quadbin, and provider-specific grids can advertise parent/child behavior, bounded or sparse coverage, and progressive refinement without callers parsing cell ids.

Production execution remains a server/platform concern. This SDK slice defines the client-side contract, cache identity, degradation vocabulary, fixture coverage, and query-tile/widget integration points.