Skip to content

docs(adr): stitch gen (selective OpenAPI codegen) + client publishing#328

Open
rejifald wants to merge 10 commits into
mainfrom
worktree-gen-openapi-design
Open

docs(adr): stitch gen (selective OpenAPI codegen) + client publishing#328
rejifald wants to merge 10 commits into
mainfrom
worktree-gen-openapi-design

Conversation

@rejifald

Copy link
Copy Markdown
Owner

Two cross-linked ADRs capturing a design discussion on letting service owners declare an API as stitches, publish it as a package, and generate stitches from an OpenAPI spec.

ADR 0013 — stitch gen: selective, eject-model codegen from OpenAPI

The ingestion counterpart to the existing stitch export --openapi / from-curl.

  • Selective, not scaffold-everything — interactive multi-select (or --only/--tag/--grep) over operations; you pick what becomes a stitch.
  • Eject, not managed-regen — one-shot emit of readable source the author owns and reshuffles; no overlay/merge engine.
  • Co-locate by operation — the stitch directory is the unit of deletion; private schemas/types die with it, shared (fan-in ≥2) hoist to _shared/.
  • Ownership = fan-in over the condensed $ref graph — SCC condensation so recursive clusters move as a unit (and get z.lazy/v.lazy).
  • Atomic schemas (and types) — one component per file so an operation pulls exactly its transitive closure; types-only/valibot/zod tiers skewed for frontend bundle size. Consistent with ADR 0011 (emits validator source, no core schema engine).
  • Spec gaps → // TODO (auth env var, throttle, pagination) — no required config.
  • Self-owned orphan detection — a generated manifest (exact) + a zero-dep ESM import scanner (reshuffle-robust); no knip/ts-prune.
  • Reserves the seam for a future stitch audit/lint family (e.g. "every operation must declare input + output").

ADR 0014 — Publishing stitch-based clients

seam() already is the client, so publishing is mostly a recipe.

  • Ship the recipe + a create-stitch-package scaffold now (factory, call-time secrets, typed .d.ts, bundled drift snapshot).
  • Gate defineClient (the declared override-contract: require/allowOverride/lock) on demonstrated demand, per the project's primitive bar (cf. ADR 0011 / inferBearer). The generator may create that demand.

Both ADRs are Proposed, docs-only, no code. All verify gates (format, typecheck, test, exports, build-docs) pass.

🤖 Generated with Claude Code

rejifald and others added 3 commits June 28, 2026 15:33
…blishing

ADR 0013 — `stitch gen`: selective, eject-model codegen from OpenAPI.
Cherry-pick operations into stitches, co-locate by operation so a stitch
directory is the unit of deletion, fan-in-on-condensed-$ref-graph ownership,
atomic schemas/types for frontend bundle-frugality, validator tiers skewed
for the frontend, and self-owned orphan detection (manifest + zero-dep import
scanner). Reserves the seam for a future `stitch audit`/lint command family.

ADR 0014 — Publishing stitch-based clients: recipe + scaffold now, with
`defineClient` (the declared override-contract) gated on demonstrated demand,
per the project's primitive bar (cf. ADR 0011).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Q1 YAML/$ref: JSON native, YAML via lazy optional, local JSON-pointer
hand-rolled, remote $ref out. Q2 validator default: types-only (loud notice),
valibot recommended runtime tier. Q3 layout: --layout dir default, flat/single
escape hatches. Q4 naming: sanitized operationId, else camelCase(method+path),
dedupe by doc-order suffix. Q5: ship `stitch gen prune`, reuse scanner in a
future `stitch audit` (no `doctor` surface).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… (v1)

Implements ADR 0013's first cut: `stitch gen openapi <spec> --out <dir>`.

- Selective (--all/--tag/--only/--grep); selective-by-default (a selector is required).
- Co-locate by operation (dir layout) or one file per op (flat); fan-in ownership over
  the transitive $ref closure places component types in _shared/ (≥2 ops) or private to
  the one op that uses them — which keeps recursive clusters together for free.
- types-only tier (ADR 0013 Q2 default): emits TS types + a typed `stitch<T>()`, no
  runtime validator; a notice says validation/drift are off.
- Auth maps securitySchemes → bearer/apiKey/basic with env() placeholders (oauth2 warns).
- Emits client.ts seam, per-op stitches, atomic type files, index.ts, and a
  .stitch-gen.json ownership manifest. JSON specs parse natively; YAML via a lazy optional.

Pure planGen in src/gen-openapi.ts (CLI-only; never in the core/browser bundle), wired
through cli.ts. Verified end-to-end against the Swagger Petstore spec: generated client
typechecks under strict mode against stitchapi and response types flow through calls.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
rejifald added a commit that referenced this pull request Jun 28, 2026
PR #328 (open) already claims ADR 0013 (gen) and 0014 (publishing). Renumber the
schema-anchored drift ADR to 0015 and update all references; the planned
expose-raw follow-up becomes 0016.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
rejifald added a commit that referenced this pull request Jun 28, 2026
…331)

* feat(drift)!: schema-anchored, diff-based drift detection (ADR 0013)

Replace the snapshot drift mechanism with schema-anchored drift. Validation is
the hard contract (throws on a missing-required/incompatible value and returns
the validated value — coerced, defaulted, unknown keys stripped); soft drift is
the diff of the raw body against that validated value, classified undeclared
(info) / coerced (warn) / defaulted (verbose) and never fatal. This eliminates
the variance false positives a single snapshot baseline produced (#327): an
optional field absent, a nullable null, an empty/heterogeneous array all
validate clean and report nothing.

- core: new zero-dep diff.ts + classifyDiff; engine returns the validated value
  and diffs raw-vs-validated into drift events; DriftOptions = { ignore, severity }
- remove the snapshot subsystem: snapshotFile / readonly / onMissing / learn,
  the `stitch drift generate` CLI, and the critical / watch leveling
- vendor-neutral: works for any validator returning a parsed value (no Zod codes)
- docs: ADR 0013 (supersedes the drift clauses of 0005), READMEs, the drift
  guide / two recipes / error page, OVERVIEW / DESIGN / FEATURE-LENSES, and the
  schema-drift blog post

BREAKING CHANGE: drift() no longer accepts critical / watch / onNew /
snapshotFile / readonly / onMissing; the `stitch drift generate` command is
removed; a call now resolves to the validated value (coerced/defaulted/stripped)
rather than the raw body; a missing required field surfaces as `invalid` (make a
field required to fail, optional to tolerate). Closes #327.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(adr): renumber drift ADR 0013 → 0015 (avoid collision with #328)

PR #328 (open) already claims ADR 0013 (gen) and 0014 (publishing). Renumber the
schema-anchored drift ADR to 0015 and update all references; the planned
expose-raw follow-up becomes 0016.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* style: prettier-format README.md drift table

The merge left the rewritten drift table's separator row unaligned;
`pnpm check:format` (prettier --check) flagged it. Alignment only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
rejifald and others added 2 commits June 28, 2026 17:34
…chapi/openapi

The generator was the one codegen tool whose roadmap pulls in dependencies
(valibot/zod emitters, a YAML parser, future audit/lint) — unlike from-curl and
export --openapi, which are zero-dep, pure-string, and stay in core. Shipping it
inside `stitchapi` would grow that package's tarball and dependency surface even
though it's tree-shaken out of the runtime bundle. So it now lives in its own
package.

- new package @stitchapi/openapi (packages/openapi): planGen library export +
  a `stitch-openapi` bin (`npx @stitchapi/openapi <spec> --out <dir>`). Zero
  runtime deps; no dependency on stitchapi (it emits stitchapi symbols as text).
- removed from core: src/gen-openapi.ts, test/gen-openapi.spec.ts, and the
  `stitch gen` command + HELP block in cli.ts. Core is back to no gen surface.
- ADR 0013 amended (Decision 8, Gates, Status + a packaging addendum) recording
  the move and the dep-trajectory rationale.

Verified: @stitchapi/openapi typechecks + 10 tests pass; core typechecks and its
suite is green (1043, the 10 gen tests moved with the package); the generated
Petstore client still typechecks under strict mode against stitchapi via the new
bin.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Now that the generator is its own package (@stitchapi/openapi), it can take the
deps it needs. `yaml` becomes a real dependency (^2.4.2, resolves to 2.9.0 already
in the lockfile) instead of a lazily-probed optional — so `stitch-openapi
spec.yaml` just works with no extra install. Still imported lazily, so JSON-only
runs never load it, and tsup externalizes it (cli.js unchanged at ~13KB). The
core's zero-dep gate is untouched (the dep lives in @stitchapi/openapi, never in
stitchapi). ADR 0013 Q1 + Decision 8 + addendum reconciled.

Verified against the Petstore YAML spec end-to-end; JSON path unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
rejifald and others added 5 commits June 29, 2026 00:01
The CI-only `check:release` gate requires every publishable package to ship an
Apache-2.0 LICENSE and a README.md (npm always includes them). The new package
was missing both — this adds the canonical LICENSE and a usage README. Not caught
by the local pre-push gate, which doesn't run check:release.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The CI-only check:readme gate regenerates the root README PACKAGES table and the
core README INTEGRATIONS table from the publishable packages; the new package
made them stale. Regenerated via `gen:readme` (snapshot-based, no metric
recompute) — adds the @stitchapi/openapi row under a new "Other" category.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The core bundle grew to ~24 kB (whole entry, min+gzip) on main, and every
advertised quote was updated except apps/docs/lib/source.ts (still ~23 kB) —
which the `size` CI job's check:size-docs flags on this branch. Bump it so all
6 advertised quotes match the measured value.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…kstep

The merge from main moved all publishable packages to 1.0.0-rc.4; the new
package was still at rc.3, which check:release (version lockstep) rejects. Align it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant