Studio Package Contracts (@honua/sdk-js/studio)
Status: experimental, implemented for ticket honua-sdk-js#230.
@honua/sdk-js/studio is the single, browser-safe import path for the Studio
package families, their unified validation/preview envelopes, the capability
manifest, and the publish/share/embed contracts. It exists so Console browser
interop, MCP, QGIS, generated apps, and embeds consume one set of
server-owned contract projections instead of forking their own package
schemas.
import {
HONUA_QUERY_PACKAGE_FORMAT_V1,
type HonuaStudioPackage,
type StudioPackageValidationResponse,
tagStudioPackage,
toStudioValidationResponse,
} from "@honua/sdk-js/studio";
MCP / QGIS safety
The barrel exports only types and pure functions and never imports from
operator, esri-compat, web-components, interactions, or realtime —
none of the MapLibre/DOM/Console-coupled modules. MCP servers and QGIS plugins
can import @honua/sdk-js/studio without pulling renderer code or triggering a
build error from Console/Esri internals. This boundary is enforced by a source
scan in test/studio/studio-contracts.test.ts.
The established map and dashboard/app shapes are re-exported here from
their leaf modules (runtime/map-package, generated-app/manifest), so a
consumer reaches every family from one path without taking on the MapLibre
runtime.
The map family additionally has a published JSON Schema,
schemas/honua-map-package.v1.json
(https://honua.io/schemas/honua-map-package.v1.json). It is the canonical
description of the artifact for JS, the CLI, MCP, and Studio alike
(honua-sdk-js#1426), and it is not documentation about the validator — it is
what the validator is built from: npm run map-package-schema:generate
compiles it into the standalone function validateMapPackage runs, and
test/runtime/map-package-schema-drift.test.ts fails the build if the schema
and HonuaMapPackage stop agreeing. exportMapPackage / importMapPackage
(@honua/sdk-js/runtime) move that artifact between clients without embedded
credentials or unbounded data.
The schema deliberately stops at the artifact. Lifecycle states (ephemeral preview, mutable draft, immutable saved version, publication proposal, active publication, superseded), content-hash identity, optimistic concurrency, and actor/tenant/authorization fields belong to the canonical honua-server composition contract and are projected into the SDK through honua-sdk-js#1397 and #1398 — never minted client-side.
Package families and format constants
Each family is gated by a format constant — the canonical version string,
exactly as on MapPackage. A loader refuses any other value.
| Family | Format constant | Format string | Projection type | Stability |
|---|---|---|---|---|
| query | HONUA_QUERY_PACKAGE_FORMAT_V1 |
honua_query_package.v1 |
HonuaQueryPackage |
experimental (stub) |
| analysis | HONUA_ANALYSIS_PACKAGE_FORMAT_V1 |
honua_analysis_package.v1 |
HonuaAnalysisPackage |
experimental (stub) |
| map | HONUA_MAP_PACKAGE_FORMAT_V1 |
honua_map_package.v1 |
HonuaMapPackage |
stable family, re-exported |
| dashboard | HONUA_GENERATED_APP_MANIFEST_FORMAT_V1 |
honua_generated_app_manifest.v1 |
HonuaGeneratedAppManifest |
re-exported |
| report | HONUA_REPORT_PACKAGE_FORMAT_V1 |
honua_report_package.v1 |
HonuaReportPackage |
experimental (stub) |
| form | HONUA_FORM_PACKAGE_FORMAT_V1 |
honua_form_package.v1 |
HonuaFormPackage |
experimental (stub) |
| app | — (uses HonuaGeneratedAppPackage.version) |
— | HonuaGeneratedAppPackage |
re-exported |
| workflow | HONUA_WORKFLOW_PACKAGE_FORMAT_V1 |
honua_workflow_package.v1 |
HonuaWorkflowPackage |
experimental (stub) |
| gp | HONUA_GP_PACKAGE_FORMAT_V1 |
honua_gp_package.v1 |
HonuaGPPackage |
experimental (stub) |
| etl | HONUA_ETL_PACKAGE_FORMAT_V1 |
honua_etl_package.v1 |
HonuaETLPackage |
experimental (stub) |
The stub families (query, analysis, report, form, workflow, gp, etl) have no
finalized server contract yet. Their interfaces are minimal and open-ended
(every field optional beyond packageId/format, plus [extra: string]: unknown) so additive server changes never break a client. They expand once the
matching server contract lands. The map and generated-app (dashboard/app)
families are the established, landed shapes.
Lifecycle status is shared across families via HonuaStudioPackageStatus
("Draft" | "Composing" | "Ready" | "Failed" | "Expired"), the same union as
HonuaMapPackageStatus.
The HonuaStudioPackage discriminated union
HonuaStudioPackage is a union tagged by a client-side packageFamily field.
The server wire shape does not carry packageFamily — projection helpers
add it. Build a tagged value with tagStudioPackage(family, pkg) rather than
casting a raw wire object, then narrow on packageFamily:
const tagged = tagStudioPackage("query", rawQueryPackage);
if (tagged.packageFamily === "query") {
// tagged is narrowed to { packageFamily: "query" } & HonuaQueryPackage
console.log(tagged.querySpec?.where);
}
STUDIO_PACKAGE_FAMILIES enumerates every family discriminant and
isStudioPackageFamily(value) guards an untrusted string.
Unified validation response
Every family returns the same { valid, diagnostics, pkg? } shape through
StudioPackageValidationResponse<T>, so SDK, Console, MCP, and QGIS clients
consume one contract:
interface StudioPackageValidationResponse<T> {
readonly valid: boolean;
readonly diagnostics: readonly StudioPackageDiagnostic[];
readonly pkg?: T;
}
The existing map-family ValidateMapPackageResult is not renamed — it is
structurally bridged:
import { validateMapPackage } from "@honua/sdk-js/runtime";
import { fromMapPackageValidation } from "@honua/sdk-js/studio";
const response = fromMapPackageValidation(validateMapPackage(rawMapPackage));
// response: StudioPackageValidationResponse<HonuaMapPackage>
For other families, the generic adapter takes the field name holding the package:
toStudioValidationResponse<HonuaQueryPackage>(rawQueryResult, "queryPackage");
HonuaMapPackageDiagnostic is already a structural subtype of
StudioPackageDiagnostic (it only adds packageId?), so map diagnostics flow
through unchanged. For convenience, fromMapPackageValidation and
toStudioValidationResponse are also re-exported from @honua/sdk-js/runtime
(tagged @experimental) so existing map-package consumers can bridge from the
path they already use.
StudioPackagePreviewResponse<T> is the parallel envelope for previewing a
package (composed without publishing), carrying optional artifacts and
diagnostics.
Capability manifest
StudioCapabilityManifest lets a client gate UI/tool exposure on what the
connected server actually supports:
import { hasCapability, getCapability } from "@honua/sdk-js/studio";
if (hasCapability(manifest, "package.query")) {
// advertise the query builder
}
hasCapability is true only for an advertised and enabled capability;
getCapability returns the entry regardless of enabled state.
The server capability-manifest endpoint is not yet defined (see Open questions). These are client projection types ahead of that contract.
Publish, share, and embed
StudioPublishRequest<T>, StudioPublishResponse, and StudioEmbedConfig
carry the versioned publication contract for generated artifacts.
HonuaShareRequest / HonuaShareResponse are re-exported from
control-plane/types (single source of truth — not re-declared) so Console
and MCP reach the share contract from the one @honua/sdk-js/studio path
without taking on the full control-plane client.
StudioEmbedConfig.token / embedUrl describe an already-issued embed
descriptor; the type does not imply a token-minting call inside the SDK.
Generated apps
Generated apps consume published map, dashboard, report, and app
package projections from @honua/sdk-js/studio without importing Console or
MapLibre code — the dashboard family is HonuaGeneratedAppManifest and the
app family is HonuaGeneratedAppPackage, both re-exported here. For the
preview runtime that hydrates these manifests, see
docs/generated-app-runtime.md.
Versioning
Every new export is tagged @experimental in JSDoc and is subpath-only (not
re-exported from the root barrels), so it is outside the SDK semver contract
until the matching server contracts land. The format constants follow the
HONUA_X_PACKAGE_FORMAT_V1 = "honua_x_package.v1" pattern.
Cross-surface parity (MCP / QGIS)
The parity layer that makes packages portable across Console, MCP, and QGIS —
a shared provenance envelope, a family-agnostic validateStudioPackage
helper, a documented Vega-Lite chart subset, and cross-surface fixtures — is
documented in studio-package-parity.md
(honua-sdk-js#226).
Open questions (pending server contracts)
These are tracked for follow-up once the server shapes are finalized:
- Whether
dashboardis exactlyHonuaGeneratedAppManifestor a distinct serverDashboardPackagewrapper. - The fetch path for
StudioCapabilityManifest(dedicated endpoint vs. embedded in another response). - Full ETL/workflow/GP package shapes (currently stub-only).
- Whether embed-token issuance is in scope for the SDK client.
- Whether the MCP server (
/mcp) should import package/validation types from@honua/sdk-js/studioin a follow-on.