Skip to content

feat(platform): move REST contracts to oRPC and commit openapi.json - #34

Open
clbotdev wants to merge 3 commits into
mainfrom
feat/orpc-migration
Open

clbotdev wants to merge 3 commits into
mainfrom
feat/orpc-migration

Conversation

@clbotdev

Copy link
Copy Markdown
Contributor

Why

ts-rest hasn't had a release since June 2025. Hosted FrontLit serves each product's committed apps/api/openapi.json at <product-website>/openapi.json, so that file has to be generated and committed as part of every release. ADR 0017 records the decision, and a survey of SendLit, FrontLit, CourseLit and MediaLit backs it up.

What changes

  • oRPC contracts on Express. The reference product and the template define their REST API with @orpc/contract. It is served by oRPC's handler running as Express middleware. Requests the contract doesn't match fall through to the rest of the app, so auth, webhooks and MCP don't change. We keep Express and Zod 3.
  • @codelitdev/platform/rest (new):
    • platformErrors, encodePlatformErrorBody and platformErrorResponseBodySchema keep both the runtime error body and the documented one as { code, message, details? }. A route can also declare its own error body, as readiness does for its 503.
    • listOperationIds, normalizeOpenApiDocument, and writeOpenApiDocument/runOpenApiScript, used by the products' OpenAPI scripts.
    • It has no runtime dependency on oRPC.
  • Committed OpenAPI standard:
    • generate:openapi builds apps/api/openapi.json from the contract alone, without starting the server.
    • check:openapi fails CI when the file is stale. It runs in check:drift and in the template's code-quality.yml.
    • version-packages regenerates the file, so release PRs commit it.
    • Biome ignores openapi.json, so formatting doesn't make it stale.
  • Conformance: platform-conformance fails a product whose committed document no longer matches its contract.
  • Preset: pins @orpc/* at 1.16.1. ts-rest, the @anatine/zod-openapi patch and the ts-deepmerge override are removed.

Breaking (minor before 1.0): platform-conformance adapters must now return committedDocumentCurrent from openapiDocument().

Testing

  • bun run verify passes: lint, preset check, typecheck, drift checks including check:openapi, all package tests, build, and the packed-package checks.
  • bun run template:smoke passes.
  • I also created a product from the CLI and ran it against Postgres:
    • All of these passed: frozen install, check:openapi, check:drift, lint, typecheck, build, and the 17 tests.
    • Requests over HTTP returned the expected results: 401 unauthenticated, 400 credential_ambiguous and validation_failed, 201 / 200 / 204 with an empty body, 404 not_found, and unmatched routes falling through.
    • After a contract edit, check:openapi failed until generate:openapi was run.

Follow-ups

  • Each product migrates separately, in this order: CourseLit, MediaLit, FrontLit, then SendLit. SendLit goes last because its public SDK is affected.
  • The template smoke test's live-server check runs only when DATABASE_URL is set. Check that CI's smoke step actually gets it.

ts-rest has been unmaintained since June 2025. The reference product and
the template now define their REST API as an oRPC contract served by
oRPC's handler inside Express, so auth, webhooks and MCP are unchanged.

- @codelitdev/platform/rest keeps error bodies and the generated
  OpenAPI in the platform { code, message, details? } shape, and adds
  helpers for listing operation ids and writing or checking the
  OpenAPI file. It has no runtime dependency on oRPC.
- Every product commits apps/api/openapi.json, generated from the
  contract alone. generate:openapi writes it and check:openapi fails
  CI when it is stale; the release version step regenerates it.
- platform-conformance fails a product whose committed document no
  longer matches its contract (committedDocumentCurrent).
- The preset pins @orpc/* 1.16.1. ts-rest, the @anatine/zod-openapi
  patch and the ts-deepmerge override are removed.

See ADR 0017.
The template's API and contract packages now both depend on
@codelitdev/platform. doctor kept whichever manifest it read last, and
directory read order differs by filesystem, so a vulnerable pin could
be hidden and the minimumSecure test failed in CI.
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.

2 participants