Skip to main content
Amended by 0158: openapi.json and app-manifest.json are committed; the client is published in CI.

Context

0040 put Encore primitives at each service’s root, and 0041 had stackra gen write static Encore declarations into services/<svc>/__generated__/. That ties every service’s source tree to Encore and puts generated code in review diffs. The owner proposed the reverse: services are plain TypeScript, and the whole Encore application is a build output. This makes Encore one target among possible others.

Decision

  1. Services are plain TypeScript with no encore.* files and no __generated__/ folder.
  2. stackra gen emits a complete Encore application into a gitignored .stackra/encore/: encore.app, one encore.service.ts per service, and generated api(), Topic, Subscription and CronJob adapters. The adapters call the built service packages through the container.
  3. Only app-manifest.json is committed. It is the reviewable record of what gen produced: endpoints, topics, subscriptions and crons.
  4. stackra gen --check runs in CI and fails when app-manifest.json is stale.
  5. stackra dev is gen --watch plus encore run. stackra build is gen plus encore build docker. CI deploys from the generated application.
  6. Encore is a swappable target. Nothing outside .stackra/ and the generator’s Encore emitter knows about Encore.
  7. Authoring is actions (0098): @Action and @Query exposed with @AsEndpoint and the other adapters, plus @OnEvent and @Cron, discovered per 0090. @Controller with @Get, @Post and the rest remains only for raw endpoints. HTTP conventions are unchanged, generated files are never edited by hand, and @Crud is 0105.
  8. The dev loop imports service source (mode B). stackra gen --watch --src makes .stackra/encore/ import each service’s source through TypeScript paths, and the generated tsconfig.json sets experimentalDecorators and useDefineForClassFields: false. Production and CI keep importing built dist. gen bundles service source with esbuild into a cache folder to read metadata, writes a file only when its content changes, and writes a <svc>/dist-stamp.ts hash file inside the app root on every rebuild, because Encore ignores changes outside its app root. An edit reaches a response in about 0.6 s.
  9. The spike passed all six cases: encore run with typed, raw, cookie and bearer endpoints; Pub/Sub and cron; encore test; static analysis of types in .stackra/ with handlers in external packages; encore build docker on arm64 and amd64; encore gen client. The constraints it found are listed under Consequences.

Consequences

  • Reviewers see app-manifest.json changes instead of generated code.
  • Encore errors point into generated code under .stackra/, so the generator must emit source comments that point back to the authored class.
  • The DDD module layout of 0040 stands, minus its decisions 3 and 4, which this ADR replaces. Encore primitives reach modules as container tokens provided by the generated app, which amends 0042 decision 5.
  • Constraints the spike found, which the generator and authors must respect:
    • Topics are referenced directly: the generator emits one switch branch per topic.
    • Classes are matched by export identity, not class.name, so every provider with handlers is exported from its package index.
    • Handlers carry explicit parameter and return type annotations.
    • A typed mapping from domain errors to APIError is required; a plain Error surfaces as a 500.
    • The pipeline order is pnpm build, then stackra gen, then pnpm install, then encore.
    • Each environment needs an Encore infra config once Pub/Sub exists.
    • Importing built dist, the generated app does not need experimentalDecorators; importing source (decision 8) it does.
    • encore build docker hangs inside git worktrees (the CLI loops on the .git file), so Docker builds run in CI or in a normal clone.
    • encore run does not run crons locally.
    • Encore watches only its app root, so source edits reach it only through the stamp file of decision 8.

Enforcement

Planned: stackra gen --check in CI and in pnpm verify. A validator check that no encore.* file or __generated__/ folder exists under services/, and that .stackra/ is gitignored. Nothing checks this today.

Supersedes and sources

Supersedes: 0041 (decisions 1, 3 to 7 restated; output moves from services/<svc>/__generated__/ to .stackra/encore/). Amends 0040 decisions 3 and 4, and 0042 decision 5. Sources: owner decision in chat, 2026-10-19 (Round 9 G3). Amended by 0098 (decision 7). Evidence: spike stackra-spike-gen/FINDINGS.md (local throwaway repository, Encore CLI 1.58.6), all six cases passed; hot-reload spike in the same repository (tools/dev.mjs), about 0.6 s from edit to response.