6.6 KiB
6.6 KiB
Tasks: enterprise-api-gateway
Change ID: enterprise-api-gateway
Package: @suaveplan/api-gateway
Target path: api/framework/api-gateway
Prerequisite packages on Verdaccio: @suaveplan/error, @suaveplan/types, @suaveplan/logger, @suaveplan/retry, @suaveplan/rate-limiter, @suaveplan/events, @suaveplan/api
0. Pre-flight (non-negotiable)
- 0.1 Confirm clean working tree (
git statusreports nothing) and no in-flight worktrees with uncommitted work (git worktree listthengit statusin each) - 0.2 If any worktrees exist under
.claude/worktrees/, account for each one: still-active agent, work-pending-merge, or stale-and-prune. Stale worktrees MUST be removed (git worktree remove) before opening a new one — code that has been lost twice on main is the failure mode this guards against. - 0.3 Confirm the package set in
MANIFEST.mdmatchesfind packages -maxdepth 3 -name package.jsoncount. Drift here means a prior change archived without regenerating the manifest.
1. Scaffold
- Create
api/framework/api-gateway/with all subdirs per layout. package.jsonv0.0.0,"sideEffects": false, subpath exports per feature module.tsconfig.jsonextendinglibrary-node.json.vitest.config.tsviacreateTestConfig().- Scripts:
lint,typecheck,test,test:coverage,build,docs:check,bench.
2. Types, schemas, errors
src/types/common.ts— all interfaces and type aliases listed in proposal.src/types/common.md(≥200 words, API reference, both examples).src/schemas/config.schema.ts,route.schema.ts,circuit-breaker.schema.ts— Zod schemas;createGatewayparses withGatewayConfigSchema.parse(config).src/errors/gateway-errors.ts— 6-code union, metadata,registerGatewayErrorCodes(),GatewayErrorre-export.gateway-errors.test.tsandgateway-errors.md.
3. Router
Router: path-pattern matching (static +:param+ wildcard*), method matching, ordered by specificity (static > param > wildcard).match(req)returns first match ornull.router.test.ts: static match, param extraction, wildcard, method mismatch returns null, method*matches any.router.md.
4. LoadBalancer
- Strategies:
round-robin(stateful counter),random,weighted(weight-proportional selection),consistent-hash(FNV-1a on a configurable key). - Skip targets with
health === 'unhealthy'. balancer.test.ts: each strategy, all-unhealthy throwsGatewayError(GATEWAY_NO_ROUTE).balancer.md.
5. TransformerPipeline + helpers
TransformerPipeline: ordered list ofRequestTransformer | ResponseTransformer;runapplies request transformers,runResponseapplies response transformers; catch on transformer throwsGatewayError(GATEWAY_TRANSFORM_FAILED).- Implement all six helper factories listed in proposal.
transformer.test.ts: each helper, pipeline ordering, error wrapping.transformer.md.
6. GatewayCircuitBreaker
- States:
closed→open(afterfailureThresholdconsecutive failures) →half-open(afterresetTimeoutms) →closed(aftersuccessThresholdsuccesses in half-open). record(success)transitions state; emitsgateway:circuit:stateevent on@suaveplan/eventsbus when state changes.- Injectable
Clockfor timeout tracking. circuit-breaker.test.ts: full state machine cycle; half-open success restores, failure re-opens; open throwsGatewayError(GATEWAY_CIRCUIT_OPEN).circuit-breaker.md.
7. HealthChecker
HealthCheckerinterface:start(),stop(),getHealth(targetId): TargetHealth.- Default interval-based implementation: probe via configurable
probe: (target: Target) => Promise<boolean>; updatetarget.health; emitgateway:target:healthevent on state change. health.test.ts: probe called on interval, healthy → unhealthy transition emits event,stop()clears interval.health.md.
8. Gateway
createGateway(config): parse config via Zod, construct router + balancer + circuit-breaker + transformer pipeline.handle(request):- Rate-limit check (if limiter injected) → 429 via
GatewayError. Router.match→ null throwsGatewayError(GATEWAY_NO_ROUTE).- Circuit-breaker open → throws
GatewayError(GATEWAY_CIRCUIT_OPEN). - Load-balance to target.
- Apply request transformers.
- Call
config.fetcherwith retry policy (@suaveplan/retry). - Apply response transformers.
- Record circuit-breaker outcome.
- Return
GatewayResult.
- Rate-limit check (if limiter injected) → 429 via
- Wrap entire flow in OTel span
gateway.handle. gateway.test.ts(≤500 lines): happy path, no-route, circuit open, upstream timeout, retry succeeds on second attempt, transformer rewrite applied.gateway.md.
9. Hono adapter
gatewayMiddleware(gateway: IGateway): MiddlewareHandler— converts HonoRequest→GatewayRequest, callsgateway.handle, mapsGatewayResult→ HonoResponse.hono-adapter.test.ts: 200 forwarded, 404 no-route, circuit-open → 503.hono-adapter.md.
10. Property-based tests (tests/property.test.ts)
- Router invariant: for any route table + request,
matchreturns either a consistent route or null (never throws); matched route's method contains the request method. - Balancer fairness: round-robin distributes N requests across K healthy targets with equal ±1 frequency.
- Circuit-breaker monotonicity: for any sequence of boolean outcomes, the state sequence never transitions
open → closeddirectly (always throughhalf-open). - Timeout 30 000 ms on each
it().
11. Benchmarks (src/__benchmarks__/gateway.bench.ts)
gateway_handle_passthrough:handle()with 1 target, no transforms, in-process no-op fetcher.gateway_handle_5_transforms: same but with 5 transformers.
12. Integration tests (tests/integration.test.ts)
- Wire a real Hono upstream server;
Gateway.handleproxies to it; assert response forwarded correctly. - Circuit-breaker trips after threshold; half-open probe restores.
13. Docs and publish
- All co-located
.mdfiles (≥200 words, both examples, full API reference). - Root
README.md. bunx turbo lint typecheck test --filter=@suaveplan/api-gateway— green.bunx turbo test:coverage --filter=@suaveplan/api-gateway— 100%.bun scripts/publish.ts --package @suaveplan/api-gateway.- Archive →
openspec/archive/2026-04-24-enterprise-api-gateway/.