Skip to content

Commit 64fdc22

Browse files
committed
feat(client): add generated typescript client
1 parent b4561fc commit 64fdc22

13 files changed

Lines changed: 1333 additions & 19 deletions

File tree

.github/workflows/ci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ jobs:
3737
- name: OpenAPI contract
3838
run: npm run contract
3939

40+
- name: Generated client
41+
run: npm run client:check
42+
4043
- name: Test
4144
run: npm test
4245

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2323
- Versioned `/api/v1/*` endpoints with `{ data, meta }` response envelopes, cache metadata, and structured error bodies.
2424
- OpenAPI-backed response contract tests for the v1 success and structured error envelopes.
2525
- Provider circuit breaker for repeated GNews failures, including Prometheus circuit event metrics and `503` short-circuit responses.
26+
- OpenAPI-generated TypeScript client types, a small v1 `NewsApiClient` wrapper, and CI drift checks for generated client output.
2627

2728
### Changed
2829

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ Full diagram and component notes live in [docs/ARCHITECTURE.md](docs/ARCHITECTUR
2525
- **Container:** multi-stage [Dockerfile](Dockerfile) (non-root user, healthcheck).
2626
- **Deploy:** Example [Kubernetes manifests](deploy/k8s/).
2727

28-
**Automation:** [CI](.github/workflows/ci.yml) (Node **20**/**22**), [CodeQL](.github/workflows/codeql.yml), [Codecov](https://codecov.io) upload, [dependency review](.github/workflows/dependency-review.yml), [SBOM](.github/workflows/supply-chain.yml), [provenance attest](.github/workflows/provenance.yml), [releases on tags](.github/workflows/release.yml), and [Dependabot](.github/dependabot.yml) (npm, Docker, Actions). Details: [docs/CI.md](docs/CI.md). Operations: [docs/OPERATIONS.md](docs/OPERATIONS.md). Security: [SECURITY.md](SECURITY.md).
28+
**Automation:** [CI](.github/workflows/ci.yml) (Node **20**/**22**), [CodeQL](.github/workflows/codeql.yml), [Codecov](https://codecov.io) upload, [dependency review](.github/workflows/dependency-review.yml), [SBOM](.github/workflows/supply-chain.yml), [provenance attest](.github/workflows/provenance.yml), [releases on tags](.github/workflows/release.yml), and [Dependabot](.github/dependabot.yml) (npm, Docker, Actions). Details: [docs/CI.md](docs/CI.md). Operations: [docs/OPERATIONS.md](docs/OPERATIONS.md). TypeScript client: [docs/CLIENT.md](docs/CLIENT.md). Security: [SECURITY.md](SECURITY.md).
2929

3030
Deployment guide: [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md) covers safe public-demo settings and Render/Fly/Railway Docker paths.
3131

@@ -132,6 +132,8 @@ Legacy errors: `{ "error": "message" }`. Versioned `/api/v1/*` errors: `{ "error
132132
| `npm run test:coverage` | Tests + coverage. |
133133
| `npm run lint` | ESLint. |
134134
| `npm run contract` | Validate `docs/openapi.yaml` with Redocly CLI. |
135+
| `npm run client:generate` | Generate TypeScript client types from `docs/openapi.yaml`. |
136+
| `npm run client:check` | Regenerate client types and fail if checked-in output is stale. |
135137
| `npm run smoke` | Curl-based smoke test against a running instance (`BASE_URL`, `QUERY`, `COUNT`, optional `CLIENT_API_KEY`). |
136138
| `npm run smoke:docker` | Compose smoke test: boot the image against a fake GNews provider and run `npm run smoke`. |
137139
| `npm run benchmark:local` | Builds the app, starts a fake GNews provider, and measures cold searches vs warm cache hits. See [docs/BENCHMARKS.md](docs/BENCHMARKS.md). |
@@ -144,6 +146,7 @@ Legacy errors: `{ "error": "message" }`. Versioned `/api/v1/*` errors: `{ "error
144146
- `src/logger.ts` — Pino + request logging.
145147
- `src/middleware/` — Security headers, rate limit, trust proxy, metrics observer, optional client API key, errors.
146148
- `src/http/responses.ts` — Versioned response envelope helpers.
149+
- `src/client/` — OpenAPI-generated TypeScript types and small v1 client wrapper.
147150
- `src/cache/store.ts` — Pluggable cache: memory or Redis.
148151
- `src/metrics/register.ts` — Prometheus registry: HTTP, cache, and upstream provider metrics.
149152
- `src/providers/gnewsProvider.ts` — GNews adapter: provider params, payload validation, timeouts, upstream instrumentation.

docs/CI.md

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,27 +10,28 @@ The [workflow](../.github/workflows/ci.yml) runs on `ubuntu-latest` with **Node.
1010
2. **`npm audit --audit-level=high`** — fails the job if high or critical advisories remain.
1111
3. **`npm run lint`**[ESLint](https://eslint.org/) on `src/`, `test/`, and `vitest.config.ts`.
1212
4. **`npm run contract`**[Redocly CLI](https://redocly.com/docs/cli) validates `docs/openapi.yaml` so the published API contract stays parseable and policy-compliant.
13-
5. **`npm test`**[Vitest](https://vitest.dev/). GNews is **not** called: tests mock `axios`; no API key in GitHub Actions. Response contract tests compile selected `docs/openapi.yaml` schemas and validate real HTTP responses.
14-
6. **`npm run test:coverage`****Node 22 only**; uploads the `coverage/` directory (including `lcov.info`) as a workflow artifact named `coverage-lcov`.
15-
7. **[Codecov](https://codecov.io)****Node 22 only**; uploads `coverage/lcov.info`. For private repos set repository secret `CODECOV_TOKEN`. `fail_ci_if_error` is off so missing token does not break the build.
16-
8. **`npm run build`** — TypeScript compile to `dist/`.
13+
5. **`npm run client:check`** — regenerates OpenAPI TypeScript client types and fails if checked-in generated output is stale.
14+
6. **`npm test`**[Vitest](https://vitest.dev/). GNews is **not** called: tests mock `axios`; no API key in GitHub Actions. Response contract tests compile selected `docs/openapi.yaml` schemas and validate real HTTP responses.
15+
7. **`npm run test:coverage`****Node 22 only**; uploads the `coverage/` directory (including `lcov.info`) as a workflow artifact named `coverage-lcov`.
16+
8. **[Codecov](https://codecov.io)****Node 22 only**; uploads `coverage/lcov.info`. For private repos set repository secret `CODECOV_TOKEN`. `fail_ci_if_error` is off so missing token does not break the build.
17+
9. **`npm run build`** — TypeScript compile to `dist/`.
1718

1819
### Container (`docker` job)
1920

20-
9. **`npm run smoke:docker`** — Compose boots the production image against a GNews-compatible fake provider, waits for readiness, then runs `npm run smoke` against the exposed API.
21-
10. **Buildx build**[Dockerfile](../Dockerfile) with **`provenance: mode=max`** and **SBOM** (no registry push). Validates supply-chain metadata generation in CI.
21+
10. **`npm run smoke:docker`** — Compose boots the production image against a GNews-compatible fake provider, waits for readiness, then runs `npm run smoke` against the exposed API.
22+
11. **Buildx build**[Dockerfile](../Dockerfile) with **`provenance: mode=max`** and **SBOM** (no registry push). Validates supply-chain metadata generation in CI.
2223

2324
### Pull requests only
2425

25-
11. **[Dependency review](../.github/workflows/dependency-review.yml)** — flags vulnerable or blocked dependencies introduced by the PR.
26+
12. **[Dependency review](../.github/workflows/dependency-review.yml)** — flags vulnerable or blocked dependencies introduced by the PR.
2627

2728
### Every push / PR (supply chain)
2829

29-
12. **[SBOM](../.github/workflows/supply-chain.yml)**[Anchore SBOM Action](https://github.com/anchore/sbom-action) produces SPDX JSON and uploads it as a workflow artifact.
30+
13. **[SBOM](../.github/workflows/supply-chain.yml)**[Anchore SBOM Action](https://github.com/anchore/sbom-action) produces SPDX JSON and uploads it as a workflow artifact.
3031

3132
### `main` branch pushes only
3233

33-
13. **[Provenance](../.github/workflows/provenance.yml)**[build provenance attestation](https://github.com/actions/attest-build-provenance) for `package-lock.json` (best-effort; `continue-on-error` if attestations are unavailable on the plan).
34+
14. **[Provenance](../.github/workflows/provenance.yml)**[build provenance attestation](https://github.com/actions/attest-build-provenance) for `package-lock.json` (best-effort; `continue-on-error` if attestations are unavailable on the plan).
3435

3536
### Code scanning (`CodeQL` workflow)
3637

@@ -51,6 +52,7 @@ npm ci
5152
npm audit --audit-level=high
5253
npm run lint
5354
npm run contract
55+
npm run client:check
5456
npm test
5557
npm run build
5658
docker build .

docs/CLIENT.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# TypeScript Client
2+
3+
The repository includes a tiny typed client wrapper for the versioned API plus generated
4+
types from `docs/openapi.yaml`.
5+
6+
## Regenerate types
7+
8+
```bash
9+
npm run client:generate
10+
```
11+
12+
CI runs:
13+
14+
```bash
15+
npm run client:check
16+
```
17+
18+
That command regenerates `src/client/openapi-types.ts` and fails if the checked-in generated
19+
types drift from the OpenAPI contract.
20+
21+
## Usage
22+
23+
```ts
24+
import { NewsApiClient, NewsApiClientError } from "./src/client/newsApiClient";
25+
26+
const client = new NewsApiClient({
27+
baseUrl: "https://your-news-api.example",
28+
apiKey: process.env.NEWS_API_CLIENT_KEY,
29+
});
30+
31+
const result = await client.searchArticles({
32+
query: "postgres",
33+
count: 5,
34+
lang: "en",
35+
sortBy: "relevance",
36+
});
37+
38+
console.log(result.meta.cache, result.data.map((article) => article.title));
39+
```
40+
41+
Structured errors from `/api/v1/*` become `NewsApiClientError` instances:
42+
43+
```ts
44+
try {
45+
await client.searchArticles({ query: "" });
46+
} catch (err) {
47+
if (err instanceof NewsApiClientError) {
48+
console.error(err.status, err.code, err.requestId);
49+
}
50+
}
51+
```
52+
53+
The wrapper intentionally targets `/api/v1/*` only. Legacy `/api/articles*` routes remain
54+
available for backward compatibility, but new consumers should use v1 envelopes.

eslint.config.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export default tseslint.config(
55
eslint.configs.recommended,
66
...tseslint.configs.recommended,
77
{
8-
ignores: ["dist/**", "node_modules/**", "coverage/**"],
8+
ignores: ["dist/**", "node_modules/**", "coverage/**", "src/client/openapi-types.ts"],
99
},
1010
{
1111
rules: {

0 commit comments

Comments
 (0)