Initial Commit
This commit is contained in:
174
openspec/changes/enterprise-api-shared/proposal.md
Normal file
174
openspec/changes/enterprise-api-shared/proposal.md
Normal file
@@ -0,0 +1,174 @@
|
||||
# OpenSpec Change: enterprise-api-shared
|
||||
|
||||
- **Change ID**: enterprise-api-shared
|
||||
- **Package**: `@suaveplan/api-shared`
|
||||
- **Category**: api/framework
|
||||
- **Target path**: `api/framework/api-shared`
|
||||
- **Status**: PROPOSED
|
||||
- **Date**: 2026-04-24
|
||||
- **Reference spec**: `/mnt/s/web/suaveplan/enterprise/api-shared.md`
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
Deliver `@suaveplan/api-shared` on the genesis branch from scratch at enterprise quality. The package provides shared utilities, middleware factories, and types consumed by every `@suaveplan/api-plugin-*` package: response helpers (success/error envelopes, pagination), middleware factories (request-id, correlation-id, tenant-context, request-logging), Zod schema utilities (paginated query parsing, sort/filter normalisation), and typed Hono context extensions. Genesis adds structured errors, OTel instrumentation, `@suaveplan/logger` integration, and a full benchmark suite.
|
||||
|
||||
---
|
||||
|
||||
## Current-state audit (genesis)
|
||||
|
||||
The package does not exist on genesis yet — fresh build. No prior source, no test results, no coverage data.
|
||||
|
||||
---
|
||||
|
||||
## Available workspace deps on genesis
|
||||
|
||||
| Package | Role |
|
||||
|---|---|
|
||||
| `@suaveplan/error` (`workspace:^`) | `ApiSharedError` base + error codes |
|
||||
| `@suaveplan/types` (`workspace:^`) | `LoggerLike`, `EventBusLike` |
|
||||
| `@suaveplan/logger` (`workspace:^`) | Request-logging middleware |
|
||||
| `@suaveplan/api` (`workspace:^`) | `PluginContext` type, `HonoApp` |
|
||||
| `@suaveplan/api-schemas` (`workspace:^`) | Pagination / sort / filter Zod schemas |
|
||||
| `hono` (`catalog:server`) | Middleware type + context |
|
||||
| `@hono/zod-openapi` (`catalog:server`) | Typed route helpers |
|
||||
| `zod` (`catalog:forms`) | Schema validation |
|
||||
| `@opentelemetry/api` (`catalog:observability`) | Spans + metrics |
|
||||
| `tslib` (`catalog:build`) | Runtime helpers |
|
||||
|
||||
---
|
||||
|
||||
## Public API surface
|
||||
|
||||
### Response helpers
|
||||
|
||||
```ts
|
||||
function successResponse<T>(data: T, meta?: ResponseMeta): ApiSuccessResponse<T>
|
||||
function errorResponse(error: SuaveplanError): ApiErrorResponse
|
||||
function paginatedResponse<T>(items: T[], pagination: PaginationMeta): ApiPaginatedResponse<T>
|
||||
function noContentResponse(): Response // 204
|
||||
```
|
||||
|
||||
### Middleware factories
|
||||
|
||||
```ts
|
||||
function requestIdMiddleware(): MiddlewareHandler // X-Request-ID header inject + propagate
|
||||
function correlationIdMiddleware(): MiddlewareHandler // X-Correlation-ID propagate
|
||||
function tenantContextMiddleware(): MiddlewareHandler // extract tenantId into Hono context
|
||||
function requestLoggingMiddleware(logger: LoggerLike): MiddlewareHandler
|
||||
function errorHandlerMiddleware(): MiddlewareHandler // converts SuaveplanError → RFC 7807
|
||||
```
|
||||
|
||||
### Schema utilities
|
||||
|
||||
```ts
|
||||
function parsePaginationQuery(query: unknown): PaginationQuery
|
||||
function parseSortQuery(query: unknown, allowedFields: string[]): SortQuery
|
||||
function parseFilterQuery(query: unknown, allowedFields: string[]): FilterQuery
|
||||
```
|
||||
|
||||
### Types
|
||||
|
||||
```ts
|
||||
interface ApiSuccessResponse<T> { success: true; data: T; meta?: ResponseMeta }
|
||||
interface ApiErrorResponse { success: false; error: ApiErrorEnvelope }
|
||||
interface ApiPaginatedResponse<T> extends ApiSuccessResponse<T[]> { pagination: PaginationMeta }
|
||||
interface PaginationMeta { page: number; pageSize: number; total: number; totalPages: number }
|
||||
interface ResponseMeta { requestId?: string; timestamp?: string; [key: string]: unknown }
|
||||
```
|
||||
|
||||
### Error codes
|
||||
|
||||
```ts
|
||||
type ApiSharedErrorCode =
|
||||
| "API_SHARED_INVALID_PAGINATION"
|
||||
| "API_SHARED_INVALID_SORT"
|
||||
| "API_SHARED_INVALID_FILTER"
|
||||
| "API_SHARED_MISSING_TENANT"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Genesis additions
|
||||
|
||||
1. **RFC 7807 Problem+JSON** error handler middleware — converts `SuaveplanError` to structured problem detail body.
|
||||
2. **OTel** — `requestLoggingMiddleware` records `http.request_duration_ms` histogram; correlation-id propagated as span attribute.
|
||||
3. **Zod schemas** for all public options (pagination, sort, filter) exported for downstream reuse.
|
||||
4. **`@suaveplan/api-schemas` delegation** — `parsePaginationQuery` wraps `@suaveplan/api-schemas`' `PaginationQuerySchema`.
|
||||
5. **Benchmark suite** measuring middleware chain overhead.
|
||||
|
||||
---
|
||||
|
||||
## File layout
|
||||
|
||||
```text
|
||||
api/framework/api-shared/
|
||||
package.json
|
||||
tsconfig.json
|
||||
vitest.config.ts
|
||||
src/
|
||||
index.ts
|
||||
constants/index.ts
|
||||
types/
|
||||
common.ts
|
||||
common.md
|
||||
errors/
|
||||
api-shared-errors.ts
|
||||
api-shared-errors.md
|
||||
api-shared-errors.test.ts
|
||||
index.ts
|
||||
responses/
|
||||
response-helpers/
|
||||
response-helpers.ts
|
||||
response-helpers.md
|
||||
response-helpers.test.ts
|
||||
index.ts
|
||||
middleware/
|
||||
request-id/
|
||||
request-id.ts
|
||||
request-id.md
|
||||
request-id.test.ts
|
||||
index.ts
|
||||
correlation-id/
|
||||
correlation-id.ts
|
||||
correlation-id.md
|
||||
correlation-id.test.ts
|
||||
index.ts
|
||||
tenant-context/
|
||||
tenant-context.ts
|
||||
tenant-context.md
|
||||
tenant-context.test.ts
|
||||
index.ts
|
||||
request-logging/
|
||||
request-logging.ts
|
||||
request-logging.md
|
||||
request-logging.test.ts
|
||||
index.ts
|
||||
error-handler/
|
||||
error-handler.ts
|
||||
error-handler.md
|
||||
error-handler.test.ts
|
||||
index.ts
|
||||
schemas/
|
||||
pagination/
|
||||
pagination.ts
|
||||
pagination.md
|
||||
pagination.test.ts
|
||||
index.ts
|
||||
sort-filter/
|
||||
sort-filter.ts
|
||||
sort-filter.md
|
||||
sort-filter.test.ts
|
||||
index.ts
|
||||
__benchmarks__/
|
||||
middleware.bench.ts
|
||||
tests/
|
||||
integration.test.ts
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Impact
|
||||
|
||||
Every `api-plugin-*` package imports from `@suaveplan/api-shared`. This package must be published before any plugin can be built. Prerequisites: `@suaveplan/error`, `@suaveplan/types`, `@suaveplan/logger`, `@suaveplan/api`, `@suaveplan/api-schemas`.
|
||||
120
openspec/changes/enterprise-api-shared/tasks.md
Normal file
120
openspec/changes/enterprise-api-shared/tasks.md
Normal file
@@ -0,0 +1,120 @@
|
||||
# Tasks: enterprise-api-shared
|
||||
|
||||
**Change ID**: enterprise-api-shared
|
||||
**Package**: `@suaveplan/api-shared`
|
||||
**Target path**: `api/framework/api-shared`
|
||||
**Prerequisite packages on Verdaccio**: `@suaveplan/error`, `@suaveplan/types`, `@suaveplan/logger`, `@suaveplan/api`, `@suaveplan/api-schemas`
|
||||
|
||||
---
|
||||
|
||||
## 0. Pre-flight (non-negotiable)
|
||||
|
||||
- [ ] 0.1 Confirm clean working tree (`git status` reports nothing) and no in-flight worktrees with uncommitted work (`git worktree list` then `git status` in 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.md` matches `find packages -maxdepth 3 -name package.json` count. Drift here means a prior change archived without regenerating the manifest.
|
||||
|
||||
## 1. Scaffold
|
||||
|
||||
- [ ] Create `api/framework/api-shared/` with `src/`, `tests/`, `src/__benchmarks__/`, and all feature subdirectories.
|
||||
- [ ] Write `package.json` at version `0.1.0`: `"type": "module"`, `"sideEffects": false`, exports map with `"."` barrel + per-feature subpaths.
|
||||
- [ ] Write `tsconfig.json` extending `@suaveplan/typescript-config/library-node.json`.
|
||||
- [ ] Write `vitest.config.ts` using `createTestConfig()`.
|
||||
- [ ] Add `lint`, `typecheck`, `test`, `test:coverage`, `build`, `docs:check`, `bench` scripts.
|
||||
|
||||
---
|
||||
|
||||
## 2. Types and errors
|
||||
|
||||
- [ ] Define shared TS interfaces in `src/types/common.ts`: `ApiSuccessResponse`, `ApiErrorResponse`, `ApiPaginatedResponse`, `PaginationMeta`, `PaginationQuery`, `SortQuery`, `FilterQuery`, `ResponseMeta`.
|
||||
- [ ] Write `src/types/common.md`.
|
||||
- [ ] Implement `src/errors/api-shared-errors.ts`: 4-code `ApiSharedErrorCode` union, metadata, `registerApiSharedErrorCodes()`.
|
||||
- [ ] Write `api-shared-errors.test.ts` and `api-shared-errors.md`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Response helpers
|
||||
|
||||
- [ ] Implement `successResponse<T>`, `errorResponse`, `paginatedResponse`, `noContentResponse`.
|
||||
- [ ] `errorResponse` converts `SuaveplanError` to RFC 7807 `application/problem+json` body.
|
||||
- [ ] Write `response-helpers.test.ts` (success envelope shape, error problem+json, paginated shape, 204 status).
|
||||
- [ ] Write `response-helpers.md`.
|
||||
|
||||
---
|
||||
|
||||
## 4. Middleware: request-id
|
||||
|
||||
- [ ] Read `X-Request-ID` header; generate UUID v4 via `crypto.randomUUID()` if absent; set on response headers and Hono context variable `c.set('requestId', ...)`.
|
||||
- [ ] Write `request-id.test.ts`: header forwarded, generated if missing.
|
||||
- [ ] Write `request-id.md`.
|
||||
|
||||
---
|
||||
|
||||
## 5. Middleware: correlation-id
|
||||
|
||||
- [ ] Read `X-Correlation-ID`; propagate to response + context + OTel span attribute `correlation_id`.
|
||||
- [ ] Write `correlation-id.test.ts` and `correlation-id.md`.
|
||||
|
||||
---
|
||||
|
||||
## 6. Middleware: tenant-context
|
||||
|
||||
- [ ] Extract `X-Tenant-ID` header; set `c.set('tenantId', ...)`. If missing and `required: true` option set, return 400 via `ApiSharedError(API_SHARED_MISSING_TENANT)`.
|
||||
- [ ] Write `tenant-context.test.ts`: header present, absent (pass-through), absent (required throws).
|
||||
- [ ] Write `tenant-context.md`.
|
||||
|
||||
---
|
||||
|
||||
## 7. Middleware: request-logging
|
||||
|
||||
- [ ] On request start: log method, path, requestId at `debug`.
|
||||
- [ ] On response: log status, duration (ms), requestId at `info`.
|
||||
- [ ] Record `http.request_duration_ms` histogram with `http.method` + `http.status_code` attributes.
|
||||
- [ ] Write `request-logging.test.ts` (log entries emitted, duration recorded).
|
||||
- [ ] Write `request-logging.md`.
|
||||
|
||||
---
|
||||
|
||||
## 8. Middleware: error-handler
|
||||
|
||||
- [ ] Catch unhandled errors in Hono; if `SuaveplanError`, call `errorResponse`; else wrap in a generic 500 problem detail.
|
||||
- [ ] Never leak stack traces in production (`NODE_ENV !== 'development'`).
|
||||
- [ ] Write `error-handler.test.ts`: SuaveplanError → problem+json, unknown error → 500.
|
||||
- [ ] Write `error-handler.md`.
|
||||
|
||||
---
|
||||
|
||||
## 9. Schema utilities
|
||||
|
||||
- [ ] `parsePaginationQuery` — wraps `@suaveplan/api-schemas` `PaginationQuerySchema`; throws `ApiSharedError(API_SHARED_INVALID_PAGINATION)` on failure.
|
||||
- [ ] `parseSortQuery` — validates `sortBy` against `allowedFields`; throws `ApiSharedError(API_SHARED_INVALID_SORT)`.
|
||||
- [ ] `parseFilterQuery` — validates operator keys against `allowedFields`; throws `ApiSharedError(API_SHARED_INVALID_FILTER)`.
|
||||
- [ ] Write tests (valid, invalid, unknown field) and `.md` for each.
|
||||
|
||||
---
|
||||
|
||||
## 10. Benchmarks (`src/__benchmarks__/middleware.bench.ts`)
|
||||
|
||||
- [ ] Benchmark 5-middleware chain (requestId + correlationId + tenantContext + requestLogging + errorHandler) on a Hono test app.
|
||||
|
||||
---
|
||||
|
||||
## 11. Integration tests (`tests/integration.test.ts`)
|
||||
|
||||
- [ ] Mount all middleware on a test Hono app; verify request-id/correlation-id headers in response, tenant missing → 400, SuaveplanError → problem+json body.
|
||||
|
||||
---
|
||||
|
||||
## 12. Docs
|
||||
|
||||
- [ ] All co-located `.md` files (≥200 words, both examples, full API reference).
|
||||
- [ ] Root `README.md`.
|
||||
- [ ] `docs:check` passes.
|
||||
|
||||
---
|
||||
|
||||
## 13. Verify and publish
|
||||
|
||||
- [ ] `bunx turbo lint typecheck test --filter=@suaveplan/api-shared` — green.
|
||||
- [ ] `bunx turbo test:coverage --filter=@suaveplan/api-shared` — 100%.
|
||||
- [ ] `bun scripts/publish.ts --package @suaveplan/api-shared`.
|
||||
- [ ] Archive change → `openspec/archive/2026-04-24-enterprise-api-shared/`.
|
||||
Reference in New Issue
Block a user