79 lines
5.6 KiB
Markdown
79 lines
5.6 KiB
Markdown
# Extract PipelineRouter Contract to api-router
|
|
|
|
**Packages**: `@suaveplan/api-router`, `@suaveplan/api-host`, `@suaveplan/api-router-in-memory`, `@suaveplan/api-router-hono`
|
|
**Status**: PROPOSED
|
|
**Type**: architectural-refactor
|
|
|
|
## Why
|
|
|
|
`@suaveplan/api-host` owns the abstract `PipelineRouter` interface AND its conformance harness `runPipelineRouterConformance`. Every concrete router adapter that satisfies the §6 pipeline (currently `api-router-in-memory` and `api-router-hono`'s pipeline facet, eventually every Stage-7 engine) must declare a build dep on `@suaveplan/api-host` to import the harness.
|
|
|
|
`@suaveplan/api-host`, in turn, declares a `peerDependency` + `devDependency` edge on `@suaveplan/api-router-in-memory` (the testing kit's default in-memory adapter, dynamically imported by `createTestHost`). Turborepo refuses to schedule the resulting cycle:
|
|
|
|
```text
|
|
Cyclic dependency detected:
|
|
@suaveplan/api-host#build, @suaveplan/api-router-in-memory#build
|
|
```
|
|
|
|
`bunx turbo test`, `bunx turbo lint typecheck test`, and `bunx turbo build` repo-wide are all blocked. Per-package gates pass via direct `bun run` invocation, but the orchestrator-facing pre-publish gate documented in `CLAUDE.md` is unusable. This has been worked around in every recent agent dispatch — it is now blocking enough that fixing it correctly is a prerequisite to further stage work.
|
|
|
|
The root cause is misplaced ownership. Per `openspec/AGENTS.md §16` (contract + conformance harness colocate with the contract package), `PipelineRouter` belongs in `@suaveplan/api-router` — the existing abstract router-contract package every adapter already depends on. `api-host` is a consumer of that contract, not the contract owner.
|
|
|
|
## What changes
|
|
|
|
### `@suaveplan/api-router` — contract owner (new subpaths)
|
|
|
|
- New feature folder `src/pipeline/`:
|
|
- `pipeline.types.ts` exporting `PipelineRoute`, `PipelineRouteOptions`, `PipelineRouteRegistration`, `PipelineRouter` (relocated verbatim from `api-host/src/request-pipeline/request-pipeline.types.ts`).
|
|
- `pipeline.md` + `index.ts` barrel (named exports, rule 10).
|
|
- `pipeline.test.ts` covering the type-shape invariants the harness asserts on.
|
|
- New feature folder `src/testing/pipeline-router-conformance/`:
|
|
- `pipeline-router-conformance.ts` — `runPipelineRouterConformance({ label, build })` (relocated verbatim from `api-host/src/testing/pipeline-router-conformance/`).
|
|
- `pipeline-router-conformance.md`, `pipeline-router-conformance.types.ts`, `pipeline-router-conformance.test.ts`, `index.ts`.
|
|
- `package.json` subpaths added: `./pipeline`, `./testing/pipeline-router-conformance`. The existing `./testing` barrel re-exports the harness for ergonomic single-subpath consumption.
|
|
|
|
### `@suaveplan/api-host` — consumer (de-owned)
|
|
|
|
- `src/request-pipeline/request-pipeline.types.ts` re-exports the 4 contract symbols from `@suaveplan/api-router/pipeline` (single source of truth) — every internal consumer keeps working without code changes.
|
|
- `src/testing/pipeline-router-conformance/` is **deleted** in favour of the relocated harness. `src/testing/testing.ts` re-exports `runPipelineRouterConformance` from `@suaveplan/api-router/testing/pipeline-router-conformance` to preserve the back-compat path `@suaveplan/api-host/testing` already in use.
|
|
- `dependencies` adds `@suaveplan/api-router`.
|
|
- `peerDependencies` + `peerDependenciesMeta` + `devDependencies`: remove the `@suaveplan/api-router-in-memory` entry. The dynamic-import pattern in `createTestHost` remains — no package-graph edge is needed for runtime resolution.
|
|
|
|
### `@suaveplan/api-router-in-memory` — adapter
|
|
|
|
- `tests/conformance/api-host.test.ts` renamed → `tests/conformance/pipeline-router.test.ts`; import path updates to `@suaveplan/api-router/testing/pipeline-router-conformance` (or `@suaveplan/api-router/testing`).
|
|
- `package.json` `dependencies`: replace `@suaveplan/api-host` with `@suaveplan/api-router` (already present — drop the api-host entry).
|
|
- `vite.config.ts` `external` list updates (`@suaveplan/api-host` → `@suaveplan/api-router`).
|
|
- Docstring mentions of `@suaveplan/api-host`'s `PipelineRouter` rewritten to `@suaveplan/api-router/pipeline`'s `PipelineRouter`.
|
|
|
|
### `@suaveplan/api-router-hono` — adapter
|
|
|
|
- `src/pipeline/pipeline.conformance.test.ts` import path updates to `@suaveplan/api-router/testing/pipeline-router-conformance`.
|
|
- `package.json` `dependencies` swap if api-host was listed — replace with `@suaveplan/api-router`.
|
|
|
|
## Cycle audit (acceptance criterion)
|
|
|
|
After the refactor, `bunx turbo run build --dry=json | jq '...'` MUST show:
|
|
|
|
- `@suaveplan/api-host` build edges: `api-router`, `api-db-adapter`, `api-lifecycle`, `api-loader`, `api-manifest`, `api-secrets`, `api-types`, `error`, `events`, `opentelemetry`, `rate-limiter`, `retry`. NO `api-router-in-memory`.
|
|
- `@suaveplan/api-router-in-memory` build edges: `api-router`, `api-types`, `error`. NO `api-host`.
|
|
- `bunx turbo test --output-logs=errors-only` exit code 0 (no `Cyclic dependency detected` warning).
|
|
|
|
## Back-compat
|
|
|
|
Every existing import path remains valid:
|
|
|
|
- `import { PipelineRouter } from "@suaveplan/api-host/request-pipeline"` → works (re-export).
|
|
- `import { runPipelineRouterConformance } from "@suaveplan/api-host/testing"` → works (re-export).
|
|
|
|
New canonical imports:
|
|
|
|
- `import { PipelineRouter } from "@suaveplan/api-router/pipeline"` (preferred).
|
|
- `import { runPipelineRouterConformance } from "@suaveplan/api-router/testing/pipeline-router-conformance"` (preferred).
|
|
|
|
## Out of scope
|
|
|
|
- Adding a second `PipelineRouter` impl beyond what already exists.
|
|
- Changing the harness's invariants.
|
|
- Stage-7 engine work — that's gate 2.7, not this change.
|