|
| 1 | +# ADR-0027: Polyglot docs composition — per-language api surfaces, model once |
| 2 | + |
| 3 | +**Status:** Accepted |
| 4 | +**Date:** 2026-06-06 |
| 5 | +**Extends:** ADR-0025 (unified docs door) — generalizes its single api surface to a |
| 6 | +per-language list so a polyglot solution gets one cross-linked doc tree. |
| 7 | +**Relates to:** ADR-0020 (Tier-1 per-port vs Tier-2 shared). |
| 8 | + |
| 9 | +> This is **SP-1** of a two-part program. SP-2 adds the Java api surface emitting |
| 10 | +> into this contract (its own ADR/spec). This ADR records the composition contract; |
| 11 | +> it does not build any non-TS surface. |
| 12 | +
|
| 13 | +## Context |
| 14 | + |
| 15 | +A polyglot solution (e.g. TypeScript + Java over the same meta models) needs ONE |
| 16 | +coherent doc tree: the **model docs once** (language-independent) + **one SDK/api |
| 17 | +reference per language** (idiomatic), all cross-linked. ADR-0025 unified the docs |
| 18 | +door but documented a single api surface. Generating the model docs once per |
| 19 | +language would duplicate language-neutral output (the Tier-2 trap); documenting |
| 20 | +only one language ignores the others. |
| 21 | + |
| 22 | +## Decision |
| 23 | + |
| 24 | +Documentation surfaces are declared as an explicit **per-language list** in the |
| 25 | +`docs:` config: |
| 26 | + |
| 27 | +```ts |
| 28 | +docs: { |
| 29 | + outDir, layout, baseUrl, |
| 30 | + surfaces, // model/api on-off selector |
| 31 | + apiSurfaces: [ { lang, subDir, baseUrl? } ] // default [{ lang:"ts", subDir:"api" }] |
| 32 | +} |
| 33 | +``` |
| 34 | + |
| 35 | +- **Model once, api per language.** The model surface (Tier-2 shared engine) is |
| 36 | + generated once; each entry in `apiSurfaces` is a per-language api surface |
| 37 | + (Tier-1) under its own `subDir`. The model page links **every** declared surface |
| 38 | + (`**API reference:** [TypeScript](api/ts/Order.md) · [Java](api/java/Order.md)`). |
| 39 | +- **Explicit list, not auto/manifest.** This matches the proven pattern for |
| 40 | + one-model→many-language tooling — buf (`buf.gen.yaml` `plugins[]`, per-language |
| 41 | + `out:`) and Smithy (`smithy-build.json` projections/plugins) — and our own |
| 42 | + `generators[]`/`targets{}`. A multi-repo **manifest** (Backstage-catalog style) |
| 43 | + is a portal/federation layer ABOVE this; it is **deferred**. The internal |
| 44 | + resolver consumes a source-agnostic resolved surface list, so a manifest can feed |
| 45 | + the same contract later without reworking the generators. |
| 46 | +- **Cross-link base: relative or `baseUrl`.** `apiSurfaceHref` returns a relative |
| 47 | + path when the surface is in the same tree (one-tree topology) and an absolute |
| 48 | + `baseUrl/page` when the surface declares a `baseUrl` (federated / separate repo). |
| 49 | + Same code path yields both topologies. |
| 50 | +- **Per-port command, shared model engine** (inherited from ADR-0025). Each port's |
| 51 | + `docs` command emits the api surfaces whose `lang` it owns + links all declared; |
| 52 | + the model engine stays singular/shared (NOT reimplemented per language). For a |
| 53 | + polyglot TS+Java solution the model docs come from the TS toolchain once, so the |
| 54 | + deferred non-TS model-engine-reach decision (ADR-0025) is not on the critical path. |
| 55 | + |
| 56 | +## Consequences |
| 57 | + |
| 58 | +- A solution declares its languages once; the model page is solution-complete |
| 59 | + (links all surfaces) even though each port emits only its own api surface. |
| 60 | +- One-tree (relative) and federated multi-repo (`baseUrl`) topologies are the same |
| 61 | + mechanism, differing only by whether a `baseUrl` is set. |
| 62 | +- No model-doc duplication across languages; the Tier-2/Tier-1 split is preserved |
| 63 | + (model surface shared, api surfaces per-port). |
| 64 | +- SP-2 (Java api surface) plugs into this contract by emitting into its `subDir` |
| 65 | + and cross-linking back to the shared model root. |
| 66 | + |
| 67 | +## Alternatives considered |
| 68 | + |
| 69 | +- **Per-language self-contained doc sets** (each language emits model + its api): |
| 70 | + rejected — duplicates the language-neutral model docs (drift + waste). |
| 71 | +- **A solution manifest as the primary declaration** (Backstage-style): deferred — |
| 72 | + it is a federation/portal layer above per-project generation; the explicit |
| 73 | + config list is the right foundation, with the resolver kept manifest-ready. |
| 74 | + |
| 75 | +## Scope |
| 76 | + |
| 77 | +Built in TypeScript (the only port with both surfaces today). The `apiSurfaces` |
| 78 | +schema, the model→multi-link rendering, `apiSurfaceHref`, and the multi-surface |
| 79 | +cross-link conformance gate are TS-now; they are the cross-port contract for the |
| 80 | +SP-2 fan-out. |
0 commit comments