cfsa-antigravity 2.0.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -0
- package/package.json +1 -1
- package/template/.agent/instructions/commands.md +8 -32
- package/template/.agent/instructions/example.md +21 -0
- package/template/.agent/instructions/patterns.md +3 -3
- package/template/.agent/instructions/tech-stack.md +71 -23
- package/template/.agent/instructions/workflow.md +12 -1
- package/template/.agent/rules/completion-checklist.md +6 -0
- package/template/.agent/rules/security-first.md +3 -3
- package/template/.agent/rules/vertical-slices.md +1 -1
- package/template/.agent/skill-library/MANIFEST.md +6 -0
- package/template/.agent/skill-library/stack/devops/git-advanced/SKILL.md +972 -0
- package/template/.agent/skill-library/stack/devops/git-workflow/SKILL.md +420 -0
- package/template/.agent/skills/api-versioning/SKILL.md +44 -298
- package/template/.agent/skills/api-versioning/references/typescript.md +157 -0
- package/template/.agent/skills/architecture-mapping/SKILL.md +13 -13
- package/template/.agent/skills/bootstrap-agents/SKILL.md +151 -152
- package/template/.agent/skills/clean-code/SKILL.md +64 -118
- package/template/.agent/skills/clean-code/references/typescript.md +126 -0
- package/template/.agent/skills/database-schema-design/SKILL.md +93 -317
- package/template/.agent/skills/database-schema-design/references/relational.md +228 -0
- package/template/.agent/skills/error-handling-patterns/SKILL.md +62 -557
- package/template/.agent/skills/error-handling-patterns/references/go.md +162 -0
- package/template/.agent/skills/error-handling-patterns/references/python.md +262 -0
- package/template/.agent/skills/error-handling-patterns/references/rust.md +112 -0
- package/template/.agent/skills/error-handling-patterns/references/typescript.md +178 -0
- package/template/.agent/skills/idea-extraction/SKILL.md +322 -224
- package/template/.agent/skills/logging-best-practices/SKILL.md +108 -767
- package/template/.agent/skills/logging-best-practices/references/go.md +49 -0
- package/template/.agent/skills/logging-best-practices/references/python.md +52 -0
- package/template/.agent/skills/logging-best-practices/references/typescript.md +215 -0
- package/template/.agent/skills/migration-management/SKILL.md +127 -311
- package/template/.agent/skills/migration-management/references/relational.md +214 -0
- package/template/.agent/skills/parallel-feature-development/SKILL.md +34 -43
- package/template/.agent/skills/pipeline-rubrics/references/be-rubric.md +1 -1
- package/template/.agent/skills/pipeline-rubrics/references/ia-rubric.md +2 -2
- package/template/.agent/skills/pipeline-rubrics/references/scoring.md +1 -1
- package/template/.agent/skills/pipeline-rubrics/references/vision-rubric.md +2 -1
- package/template/.agent/skills/prd-templates/SKILL.md +23 -6
- package/template/.agent/skills/prd-templates/references/be-spec-template.md +2 -2
- package/template/.agent/skills/prd-templates/references/decomposition-templates.md +2 -2
- package/template/.agent/skills/prd-templates/references/engineering-standards-template.md +2 -0
- package/template/.agent/skills/prd-templates/references/fe-spec-template.md +1 -1
- package/template/.agent/skills/prd-templates/references/fractal-cx-template.md +58 -0
- package/template/.agent/skills/prd-templates/references/fractal-feature-template.md +93 -0
- package/template/.agent/skills/prd-templates/references/fractal-node-index-template.md +55 -0
- package/template/.agent/skills/prd-templates/references/ideation-crosscut-template.md +26 -47
- package/template/.agent/skills/prd-templates/references/ideation-index-template.md +47 -31
- package/template/.agent/skills/prd-templates/references/operational-templates.md +1 -1
- package/template/.agent/skills/prd-templates/references/placeholder-workflow-mapping.md +50 -21
- package/template/.agent/skills/prd-templates/references/skill-loading-protocol.md +32 -0
- package/template/.agent/skills/prd-templates/references/slice-completion-gates.md +29 -0
- package/template/.agent/skills/prd-templates/references/spec-coverage-sweep.md +3 -3
- package/template/.agent/skills/prd-templates/references/tdd-testing-policy.md +39 -0
- package/template/.agent/skills/prd-templates/references/vision-template.md +8 -8
- package/template/.agent/skills/regex-patterns/SKILL.md +122 -540
- package/template/.agent/skills/regex-patterns/references/go.md +44 -0
- package/template/.agent/skills/regex-patterns/references/javascript.md +63 -0
- package/template/.agent/skills/regex-patterns/references/python.md +77 -0
- package/template/.agent/skills/regex-patterns/references/rust.md +43 -0
- package/template/.agent/skills/resolve-ambiguity/SKILL.md +1 -1
- package/template/.agent/skills/session-continuity/SKILL.md +11 -9
- package/template/.agent/skills/session-continuity/protocols/02-progress-generation.md +2 -2
- package/template/.agent/skills/session-continuity/protocols/04-pattern-extraction.md +1 -1
- package/template/.agent/skills/session-continuity/protocols/05-session-close.md +1 -1
- package/template/.agent/skills/session-continuity/protocols/09-parallel-claim.md +1 -1
- package/template/.agent/skills/session-continuity/protocols/10-placeholder-verification-gate.md +57 -78
- package/template/.agent/skills/session-continuity/protocols/11-parallel-synthesis.md +1 -1
- package/template/.agent/skills/spec-writing/SKILL.md +1 -1
- package/template/.agent/skills/tdd-workflow/SKILL.md +94 -317
- package/template/.agent/skills/tdd-workflow/references/typescript.md +231 -0
- package/template/.agent/skills/testing-strategist/SKILL.md +74 -687
- package/template/.agent/skills/testing-strategist/references/typescript.md +328 -0
- package/template/.agent/skills/workflow-automation/SKILL.md +62 -154
- package/template/.agent/skills/workflow-automation/references/inngest.md +88 -0
- package/template/.agent/skills/workflow-automation/references/temporal.md +64 -0
- package/template/.agent/workflows/bootstrap-agents-fill.md +85 -143
- package/template/.agent/workflows/bootstrap-agents-provision.md +90 -107
- package/template/.agent/workflows/create-prd-architecture.md +23 -16
- package/template/.agent/workflows/create-prd-compile.md +11 -12
- package/template/.agent/workflows/create-prd-design-system.md +1 -1
- package/template/.agent/workflows/create-prd-security.md +9 -11
- package/template/.agent/workflows/create-prd-stack.md +10 -4
- package/template/.agent/workflows/create-prd.md +9 -9
- package/template/.agent/workflows/decompose-architecture-structure.md +4 -6
- package/template/.agent/workflows/decompose-architecture-validate.md +18 -1
- package/template/.agent/workflows/decompose-architecture.md +18 -3
- package/template/.agent/workflows/evolve-contract.md +11 -11
- package/template/.agent/workflows/evolve-feature-classify.md +14 -6
- package/template/.agent/workflows/ideate-discover.md +72 -107
- package/template/.agent/workflows/ideate-extract.md +84 -63
- package/template/.agent/workflows/ideate-validate.md +26 -22
- package/template/.agent/workflows/ideate.md +9 -9
- package/template/.agent/workflows/implement-slice-setup.md +25 -23
- package/template/.agent/workflows/implement-slice-tdd.md +73 -89
- package/template/.agent/workflows/implement-slice.md +4 -4
- package/template/.agent/workflows/plan-phase-preflight.md +6 -2
- package/template/.agent/workflows/plan-phase-write.md +6 -8
- package/template/.agent/workflows/remediate-pipeline-assess.md +2 -1
- package/template/.agent/workflows/resolve-ambiguity.md +2 -2
- package/template/.agent/workflows/update-architecture-map.md +22 -5
- package/template/.agent/workflows/validate-phase-quality.md +155 -0
- package/template/.agent/workflows/validate-phase-readiness.md +167 -0
- package/template/.agent/workflows/validate-phase.md +19 -157
- package/template/.agent/workflows/verify-infrastructure.md +10 -10
- package/template/.agent/workflows/write-architecture-spec-design.md +23 -14
- package/template/.agent/workflows/write-be-spec-classify.md +25 -21
- package/template/.agent/workflows/write-be-spec.md +1 -1
- package/template/.agent/workflows/write-fe-spec-classify.md +6 -12
- package/template/.agent/workflows/write-fe-spec-write.md +1 -1
- package/template/AGENTS.md +6 -2
- package/template/GEMINI.md +5 -3
- package/template/docs/README.md +10 -10
- package/template/docs/kit-architecture.md +126 -33
- package/template/docs/plans/ideation/README.md +8 -3
- package/template/.agent/skills/prd-templates/references/ideation-domain-template.md +0 -55
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: api-versioning
|
|
3
3
|
description: "Manage API versioning and evolution with URL/header/query strategies, deprecation workflows, breaking change classification, sunset headers, and consumer-driven contract testing. Use when designing versioning strategy, deprecating endpoints, or evolving API contracts."
|
|
4
|
-
version:
|
|
4
|
+
version: 2.0.0
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# API Versioning & Evolution
|
|
8
8
|
|
|
9
9
|
APIs are contracts with consumers. Breaking that contract destroys trust. This skill covers how to version, evolve, and deprecate APIs without breaking clients.
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## Stack-Specific References
|
|
12
|
+
|
|
13
|
+
After reading the methodology below, read the reference matching your surface's framework:
|
|
14
|
+
|
|
15
|
+
| Framework | Reference |
|
|
16
|
+
|-----------|-----------|
|
|
17
|
+
| TypeScript / Node.js | `references/typescript.md` |
|
|
12
18
|
|
|
13
|
-
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Versioning Strategies
|
|
14
22
|
|
|
15
23
|
| Strategy | Example | Pros | Cons |
|
|
16
24
|
|----------|---------|------|------|
|
|
@@ -23,94 +31,16 @@ APIs are contracts with consumers. Breaking that contract destroys trust. This s
|
|
|
23
31
|
|
|
24
32
|
Use **URL path versioning** for public APIs (simplicity and discoverability). Use **header versioning** for internal/partner APIs (cleaner resource model).
|
|
25
33
|
|
|
26
|
-
### URL Path Versioning
|
|
27
|
-
|
|
28
|
-
```typescript
|
|
29
|
-
// src/pages/api/v1/models/[id].ts
|
|
30
|
-
export async function GET({ params }: APIContext) {
|
|
31
|
-
// V1 response shape
|
|
32
|
-
return new Response(JSON.stringify({
|
|
33
|
-
id: params.id,
|
|
34
|
-
name: model.name,
|
|
35
|
-
provider: model.provider,
|
|
36
|
-
// V1 had a flat pricing field
|
|
37
|
-
price_per_token: model.pricePerToken,
|
|
38
|
-
}));
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// src/pages/api/v2/models/[id].ts
|
|
42
|
-
export async function GET({ params }: APIContext) {
|
|
43
|
-
// V2 response shape (nested pricing)
|
|
44
|
-
return new Response(JSON.stringify({
|
|
45
|
-
id: params.id,
|
|
46
|
-
name: model.name,
|
|
47
|
-
provider: model.provider,
|
|
48
|
-
// V2 has structured pricing
|
|
49
|
-
pricing: {
|
|
50
|
-
input: model.inputPrice,
|
|
51
|
-
output: model.outputPrice,
|
|
52
|
-
currency: 'USD',
|
|
53
|
-
unit: 'per_million_tokens',
|
|
54
|
-
},
|
|
55
|
-
}));
|
|
56
|
-
}
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
### Header Versioning
|
|
60
|
-
|
|
61
|
-
```typescript
|
|
62
|
-
// Version routing middleware
|
|
63
|
-
function versionRouter(handlers: Record<string, Handler>): Handler {
|
|
64
|
-
return async (context) => {
|
|
65
|
-
const version = context.request.headers.get('API-Version')
|
|
66
|
-
?? context.url.searchParams.get('version')
|
|
67
|
-
?? DEFAULT_VERSION;
|
|
68
|
-
|
|
69
|
-
const handler = handlers[version];
|
|
70
|
-
if (!handler) {
|
|
71
|
-
return new Response(JSON.stringify({
|
|
72
|
-
type: 'https://api.example.com/problems/unsupported-version',
|
|
73
|
-
title: 'Unsupported API Version',
|
|
74
|
-
status: 400,
|
|
75
|
-
detail: `API version '${version}' is not supported. Supported versions: ${Object.keys(handlers).join(', ')}`,
|
|
76
|
-
}), { status: 400, headers: { 'Content-Type': 'application/problem+json' } });
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const response = await handler(context);
|
|
80
|
-
response.headers.set('API-Version', version);
|
|
81
|
-
return response;
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Usage
|
|
86
|
-
export const GET = versionRouter({
|
|
87
|
-
'1': handleV1,
|
|
88
|
-
'2': handleV2,
|
|
89
|
-
});
|
|
90
|
-
```
|
|
91
|
-
|
|
92
34
|
---
|
|
93
35
|
|
|
94
36
|
## Default Version Behavior
|
|
95
37
|
|
|
96
|
-
When a client does not specify a version, the API must behave predictably.
|
|
97
|
-
|
|
98
38
|
| Strategy | Behavior | When to Use |
|
|
99
39
|
|----------|----------|-------------|
|
|
100
40
|
| **Default to latest** | Unversioned requests get the newest version | Internal APIs with controlled consumers |
|
|
101
41
|
| **Default to oldest supported** | Unversioned requests get V1 | Public APIs (avoid surprise breakage) |
|
|
102
42
|
| **Require explicit version** | Return 400 if no version specified | Strict APIs where ambiguity is unacceptable |
|
|
103
43
|
|
|
104
|
-
```typescript
|
|
105
|
-
const DEFAULT_VERSION = '1'; // Conservative default for public APIs
|
|
106
|
-
|
|
107
|
-
function resolveVersion(request: Request): string {
|
|
108
|
-
return request.headers.get('API-Version')
|
|
109
|
-
?? request.url.searchParams.get('version')
|
|
110
|
-
?? DEFAULT_VERSION;
|
|
111
|
-
}
|
|
112
|
-
```
|
|
113
|
-
|
|
114
44
|
---
|
|
115
45
|
|
|
116
46
|
## Breaking vs Non-Breaking Changes
|
|
@@ -119,25 +49,25 @@ function resolveVersion(request: Request): string {
|
|
|
119
49
|
|
|
120
50
|
| Change | Example | Why It Is Safe |
|
|
121
51
|
|--------|---------|---------------|
|
|
122
|
-
| Add optional field to response | `"avatar_url": "..."` added
|
|
52
|
+
| Add optional field to response | `"avatar_url": "..."` added | Clients ignore unknown fields |
|
|
123
53
|
| Add optional query parameter | `?sort=name` now supported | Existing queries still work |
|
|
124
54
|
| Add new endpoint | `POST /api/v1/webhooks` | Does not affect existing endpoints |
|
|
125
|
-
| Widen accepted input types | Field accepts `string
|
|
55
|
+
| Widen accepted input types | Field accepts `string | number` | Existing valid inputs remain valid |
|
|
126
56
|
| Add optional request field | `"metadata": {}` now accepted | Existing requests without it still work |
|
|
127
|
-
| Relax validation | Max length
|
|
57
|
+
| Relax validation | Max length 100 → 200 | Previously valid inputs still valid |
|
|
128
58
|
|
|
129
59
|
### Breaking (Requires New Version)
|
|
130
60
|
|
|
131
61
|
| Change | Example | Why It Breaks |
|
|
132
62
|
|--------|---------|---------------|
|
|
133
63
|
| Remove field from response | `price_per_token` removed | Clients reading this field break |
|
|
134
|
-
| Rename field | `price_per_token`
|
|
64
|
+
| Rename field | `price_per_token` → `pricing` | Clients reading old name break |
|
|
135
65
|
| Change field type | `price` from number to object | Parsing breaks |
|
|
136
66
|
| Remove endpoint | `DELETE /api/v1/legacy` | Clients calling it get 404 |
|
|
137
67
|
| Add required request field | `"region"` now required | Existing requests missing it fail |
|
|
138
|
-
| Tighten validation | Max length
|
|
68
|
+
| Tighten validation | Max length 200 → 100 | Previously valid inputs rejected |
|
|
139
69
|
| Change error response format | Different error shape | Client error handling breaks |
|
|
140
|
-
| Change authentication scheme | Bearer token
|
|
70
|
+
| Change authentication scheme | Bearer token → API key | Auth headers break |
|
|
141
71
|
|
|
142
72
|
---
|
|
143
73
|
|
|
@@ -145,29 +75,16 @@ function resolveVersion(request: Request): string {
|
|
|
145
75
|
|
|
146
76
|
The safest evolution strategy: never remove or rename, only add.
|
|
147
77
|
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
price_per_token:
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
id: string;
|
|
159
|
-
name: string;
|
|
160
|
-
price_per_token: number; // KEPT for backward compatibility
|
|
161
|
-
pricing: { // ADDED new structured field
|
|
162
|
-
input: number;
|
|
163
|
-
output: number;
|
|
164
|
-
currency: string;
|
|
165
|
-
unit: string;
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
// Clients reading price_per_token still work.
|
|
169
|
-
// New clients use pricing object.
|
|
170
|
-
// Remove price_per_token only in V2.
|
|
78
|
+
```
|
|
79
|
+
V1 response:
|
|
80
|
+
{ id, name, price_per_token }
|
|
81
|
+
|
|
82
|
+
V1.1 response (additive — no new version needed):
|
|
83
|
+
{ id, name, price_per_token, pricing: { input, output, currency, unit } }
|
|
84
|
+
|
|
85
|
+
Clients reading price_per_token still work.
|
|
86
|
+
New clients use pricing object.
|
|
87
|
+
Remove price_per_token only in V2.
|
|
171
88
|
```
|
|
172
89
|
|
|
173
90
|
---
|
|
@@ -176,101 +93,32 @@ interface ModelV1_1 {
|
|
|
176
93
|
|
|
177
94
|
### Sunset Header (RFC 8594)
|
|
178
95
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
response.headers.set('Link', `<${docUrl}>; rel="sunset"`);
|
|
184
|
-
return response;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// Usage
|
|
188
|
-
const response = await handleV1Request(context);
|
|
189
|
-
addDeprecationHeaders(
|
|
190
|
-
response,
|
|
191
|
-
'Sat, 01 Mar 2026 00:00:00 GMT',
|
|
192
|
-
'https://docs.example.com/migration/v1-to-v2'
|
|
193
|
-
);
|
|
194
|
-
```
|
|
96
|
+
Every deprecated endpoint/version must include these headers:
|
|
97
|
+
- `Deprecation: true`
|
|
98
|
+
- `Sunset: <HTTP date>` — when this version will stop working
|
|
99
|
+
- `Link: <migration-url>; rel="sunset"`
|
|
195
100
|
|
|
196
101
|
### Deprecation Timeline
|
|
197
102
|
|
|
198
103
|
| Phase | Duration | Actions |
|
|
199
104
|
|-------|----------|---------|
|
|
200
105
|
| **Announce** | T-6 months | Add `Deprecation: true` header, publish migration guide |
|
|
201
|
-
| **Warn** | T-3 months | Add `Sunset` header with date,
|
|
106
|
+
| **Warn** | T-3 months | Add `Sunset` header with date, email consumers |
|
|
202
107
|
| **Monitor** | T-1 month | Track usage, notify active consumers directly |
|
|
203
108
|
| **Sunset** | T-0 | Return 410 Gone with migration link |
|
|
204
109
|
| **Remove** | T+3 months | Remove code (keep tests to prevent regression) |
|
|
205
110
|
|
|
206
|
-
### Sunset Response
|
|
207
|
-
|
|
208
|
-
```typescript
|
|
209
|
-
function sunsetResponse(migrationUrl: string): Response {
|
|
210
|
-
return new Response(JSON.stringify({
|
|
211
|
-
type: 'https://api.example.com/problems/gone',
|
|
212
|
-
title: 'API Version Removed',
|
|
213
|
-
status: 410,
|
|
214
|
-
detail: `This API version has been sunset. Please migrate to the latest version.`,
|
|
215
|
-
migrationGuide: migrationUrl,
|
|
216
|
-
}), {
|
|
217
|
-
status: 410,
|
|
218
|
-
headers: {
|
|
219
|
-
'Content-Type': 'application/problem+json',
|
|
220
|
-
'Link': `<${migrationUrl}>; rel="successor-version"`,
|
|
221
|
-
},
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
```
|
|
225
|
-
|
|
226
111
|
---
|
|
227
112
|
|
|
228
113
|
## Consumer-Driven Contract Testing
|
|
229
114
|
|
|
230
|
-
Consumers define the contract they depend on. The provider runs these contracts in CI
|
|
231
|
-
|
|
232
|
-
```typescript
|
|
233
|
-
// consumer-contracts/model-service.contract.test.ts
|
|
234
|
-
import { describe, it, expect } from 'vitest';
|
|
235
|
-
|
|
236
|
-
describe('Model API Contract (Consumer: Dashboard)', () => {
|
|
237
|
-
it('GET /api/v1/models/:id returns expected shape', async () => {
|
|
238
|
-
const response = await fetch(`${API_URL}/api/v1/models/gpt-4`);
|
|
239
|
-
const data = await response.json();
|
|
240
|
-
|
|
241
|
-
// Consumer only depends on these fields --- adding fields is fine
|
|
242
|
-
expect(data).toMatchObject({
|
|
243
|
-
id: expect.any(String),
|
|
244
|
-
name: expect.any(String),
|
|
245
|
-
provider: expect.any(String),
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
// These fields must exist with these types
|
|
249
|
-
expect(typeof data.id).toBe('string');
|
|
250
|
-
expect(typeof data.name).toBe('string');
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
it('returns 404 for unknown model', async () => {
|
|
254
|
-
const response = await fetch(`${API_URL}/api/v1/models/nonexistent`);
|
|
255
|
-
expect(response.status).toBe(404);
|
|
256
|
-
});
|
|
257
|
-
});
|
|
258
|
-
```
|
|
115
|
+
Consumers define the contract they depend on. The provider runs these contracts in CI.
|
|
259
116
|
|
|
260
|
-
**
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
runs-on: ubuntu-latest
|
|
266
|
-
steps:
|
|
267
|
-
- name: Run consumer contracts
|
|
268
|
-
run: pnpm test:contracts
|
|
269
|
-
# Deploy only if contracts pass
|
|
270
|
-
deploy:
|
|
271
|
-
needs: contract-tests
|
|
272
|
-
# ...
|
|
273
|
-
```
|
|
117
|
+
**Concept:**
|
|
118
|
+
1. Each API consumer writes contract tests specifying fields they depend on
|
|
119
|
+
2. Provider runs ALL consumer contracts in CI before deploy
|
|
120
|
+
3. If a consumer contract breaks, the deploy is blocked
|
|
121
|
+
4. Adding new fields never breaks contracts (consumers ignore unknown fields)
|
|
274
122
|
|
|
275
123
|
---
|
|
276
124
|
|
|
@@ -285,118 +133,16 @@ deprecate(api): mark /api/v1/legacy/search as deprecated
|
|
|
285
133
|
breaking(api): remove price_per_token field from /api/v2/models response
|
|
286
134
|
```
|
|
287
135
|
|
|
288
|
-
### Generated Changelog
|
|
289
|
-
|
|
290
|
-
```markdown
|
|
291
|
-
## API Changelog
|
|
292
|
-
|
|
293
|
-
### v2.3.0 (2026-02-15)
|
|
294
|
-
|
|
295
|
-
#### Added
|
|
296
|
-
- `GET /api/v1/webhooks` - List registered webhooks
|
|
297
|
-
- `POST /api/v1/webhooks` - Register a new webhook
|
|
298
|
-
|
|
299
|
-
#### Deprecated
|
|
300
|
-
- `GET /api/v1/legacy/search` - Use `GET /api/v1/search` instead. Sunset: 2026-08-01.
|
|
301
|
-
|
|
302
|
-
#### Fixed
|
|
303
|
-
- Pagination cursor encoding now handles special characters correctly
|
|
304
|
-
|
|
305
|
-
### v2.0.0 (2026-01-01)
|
|
306
|
-
|
|
307
|
-
#### Breaking Changes
|
|
308
|
-
- Removed `price_per_token` field from model responses. Use `pricing` object instead.
|
|
309
|
-
- See [migration guide](https://docs.example.com/migration/v1-to-v2).
|
|
310
|
-
```
|
|
311
|
-
|
|
312
136
|
---
|
|
313
137
|
|
|
314
138
|
## Migration Guides
|
|
315
139
|
|
|
316
|
-
Every version bump must include a migration guide
|
|
317
|
-
|
|
318
|
-
```markdown
|
|
319
|
-
# Migrating from API v1 to v2
|
|
320
|
-
|
|
321
|
-
## Timeline
|
|
322
|
-
- **v1 deprecated:** January 1, 2026
|
|
323
|
-
- **v1 sunset:** July 1, 2026
|
|
324
|
-
- **v1 removed:** October 1, 2026
|
|
140
|
+
Every version bump must include a migration guide:
|
|
325
141
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
**Before (v1):**
|
|
331
|
-
```json
|
|
332
|
-
{ "price_per_token": 0.00003 }
|
|
333
|
-
```
|
|
334
|
-
|
|
335
|
-
**After (v2):**
|
|
336
|
-
```json
|
|
337
|
-
{ "pricing": { "input": 0.00001, "output": 0.00003, "currency": "USD", "unit": "per_million_tokens" } }
|
|
338
|
-
```
|
|
339
|
-
|
|
340
|
-
**Migration steps:**
|
|
341
|
-
1. Update your response type definitions
|
|
342
|
-
2. Replace `model.price_per_token` with `model.pricing.output`
|
|
343
|
-
3. Test with v2 endpoint
|
|
344
|
-
4. Update API-Version header to `2`
|
|
345
|
-
```
|
|
346
|
-
|
|
347
|
-
---
|
|
348
|
-
|
|
349
|
-
## Version Routing Middleware
|
|
350
|
-
|
|
351
|
-
```typescript
|
|
352
|
-
// src/middleware/api-version.ts
|
|
353
|
-
type VersionedHandlers = Record<string, Handler>;
|
|
354
|
-
|
|
355
|
-
export function createVersionedRoute(
|
|
356
|
-
handlers: VersionedHandlers,
|
|
357
|
-
options: {
|
|
358
|
-
defaultVersion?: string;
|
|
359
|
-
deprecated?: Record<string, { sunset: string; migrationUrl: string }>;
|
|
360
|
-
} = {}
|
|
361
|
-
): Handler {
|
|
362
|
-
const { defaultVersion, deprecated = {} } = options;
|
|
363
|
-
|
|
364
|
-
return async (context) => {
|
|
365
|
-
const version = resolveVersion(context.request) ?? defaultVersion;
|
|
366
|
-
|
|
367
|
-
if (!version) {
|
|
368
|
-
return problemResponse({
|
|
369
|
-
type: 'https://api.example.com/problems/version-required',
|
|
370
|
-
title: 'API Version Required',
|
|
371
|
-
status: 400,
|
|
372
|
-
detail: 'Please specify an API version via the API-Version header.',
|
|
373
|
-
});
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
if (!(version in handlers)) {
|
|
377
|
-
return problemResponse({
|
|
378
|
-
type: 'https://api.example.com/problems/unsupported-version',
|
|
379
|
-
title: 'Unsupported API Version',
|
|
380
|
-
status: 400,
|
|
381
|
-
detail: `Version '${version}' is not supported.`,
|
|
382
|
-
});
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
const response = await handlers[version](context);
|
|
386
|
-
response.headers.set('API-Version', version);
|
|
387
|
-
|
|
388
|
-
// Add deprecation headers if applicable
|
|
389
|
-
if (version in deprecated) {
|
|
390
|
-
const { sunset, migrationUrl } = deprecated[version];
|
|
391
|
-
response.headers.set('Deprecation', 'true');
|
|
392
|
-
response.headers.set('Sunset', sunset);
|
|
393
|
-
response.headers.set('Link', `<${migrationUrl}>; rel="sunset"`);
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
return response;
|
|
397
|
-
};
|
|
398
|
-
}
|
|
399
|
-
```
|
|
142
|
+
1. **Timeline** — deprecation date, sunset date, removal date
|
|
143
|
+
2. **Breaking changes** — before/after for each changed field or endpoint
|
|
144
|
+
3. **Migration steps** — numbered steps to update client code
|
|
145
|
+
4. **Testing instructions** — how to verify migration works
|
|
400
146
|
|
|
401
147
|
---
|
|
402
148
|
|
|
@@ -407,7 +153,7 @@ export function createVersionedRoute(
|
|
|
407
153
|
| Increment version for every change | Version only on breaking changes |
|
|
408
154
|
| Remove old version without notice | Follow deprecation timeline (6+ months) |
|
|
409
155
|
| Different versioning per endpoint | Consistent strategy across the entire API |
|
|
410
|
-
| Version in the response body only | Use URL path or headers
|
|
156
|
+
| Version in the response body only | Use URL path or headers — visible and consistent |
|
|
411
157
|
| No default version behavior | Define and document the default |
|
|
412
158
|
| Breaking change without migration guide | Every breaking change needs a guide |
|
|
413
159
|
| No consumer notification | Email, changelog, and headers all used together |
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# TypeScript API Versioning Patterns
|
|
2
|
+
|
|
3
|
+
Language-specific patterns for the `api-versioning` skill. Read `SKILL.md` first for universal methodology.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## URL Path Versioning
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
// src/pages/api/v1/models/[id].ts
|
|
11
|
+
export async function GET({ params }: APIContext) {
|
|
12
|
+
return new Response(JSON.stringify({
|
|
13
|
+
id: params.id,
|
|
14
|
+
name: model.name,
|
|
15
|
+
provider: model.provider,
|
|
16
|
+
price_per_token: model.pricePerToken, // V1 flat field
|
|
17
|
+
}));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// src/pages/api/v2/models/[id].ts
|
|
21
|
+
export async function GET({ params }: APIContext) {
|
|
22
|
+
return new Response(JSON.stringify({
|
|
23
|
+
id: params.id,
|
|
24
|
+
name: model.name,
|
|
25
|
+
provider: model.provider,
|
|
26
|
+
pricing: { // V2 structured pricing
|
|
27
|
+
input: model.inputPrice,
|
|
28
|
+
output: model.outputPrice,
|
|
29
|
+
currency: 'USD',
|
|
30
|
+
unit: 'per_million_tokens',
|
|
31
|
+
},
|
|
32
|
+
}));
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Header Versioning Middleware
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
function versionRouter(handlers: Record<string, Handler>): Handler {
|
|
40
|
+
return async (context) => {
|
|
41
|
+
const version = context.request.headers.get('API-Version')
|
|
42
|
+
?? context.url.searchParams.get('version')
|
|
43
|
+
?? DEFAULT_VERSION;
|
|
44
|
+
|
|
45
|
+
const handler = handlers[version];
|
|
46
|
+
if (!handler) {
|
|
47
|
+
return new Response(JSON.stringify({
|
|
48
|
+
type: 'https://api.example.com/problems/unsupported-version',
|
|
49
|
+
title: 'Unsupported API Version',
|
|
50
|
+
status: 400,
|
|
51
|
+
detail: `API version '${version}' is not supported.`,
|
|
52
|
+
}), { status: 400, headers: { 'Content-Type': 'application/problem+json' } });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const response = await handler(context);
|
|
56
|
+
response.headers.set('API-Version', version);
|
|
57
|
+
return response;
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Sunset Headers
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
function addDeprecationHeaders(response: Response, sunsetDate: string, docUrl: string): Response {
|
|
66
|
+
response.headers.set('Deprecation', 'true');
|
|
67
|
+
response.headers.set('Sunset', sunsetDate);
|
|
68
|
+
response.headers.set('Link', `<${docUrl}>; rel="sunset"`);
|
|
69
|
+
return response;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function sunsetResponse(migrationUrl: string): Response {
|
|
73
|
+
return new Response(JSON.stringify({
|
|
74
|
+
type: 'https://api.example.com/problems/gone',
|
|
75
|
+
title: 'API Version Removed',
|
|
76
|
+
status: 410,
|
|
77
|
+
detail: 'This API version has been sunset. Please migrate.',
|
|
78
|
+
migrationGuide: migrationUrl,
|
|
79
|
+
}), {
|
|
80
|
+
status: 410,
|
|
81
|
+
headers: {
|
|
82
|
+
'Content-Type': 'application/problem+json',
|
|
83
|
+
'Link': `<${migrationUrl}>; rel="successor-version"`,
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Consumer-Driven Contract Tests (Vitest)
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import { describe, it, expect } from 'vitest';
|
|
93
|
+
|
|
94
|
+
describe('Model API Contract (Consumer: Dashboard)', () => {
|
|
95
|
+
it('GET /api/v1/models/:id returns expected shape', async () => {
|
|
96
|
+
const response = await fetch(`${API_URL}/api/v1/models/gpt-4`);
|
|
97
|
+
const data = await response.json();
|
|
98
|
+
|
|
99
|
+
expect(data).toMatchObject({
|
|
100
|
+
id: expect.any(String),
|
|
101
|
+
name: expect.any(String),
|
|
102
|
+
provider: expect.any(String),
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('returns 404 for unknown model', async () => {
|
|
107
|
+
const response = await fetch(`${API_URL}/api/v1/models/nonexistent`);
|
|
108
|
+
expect(response.status).toBe(404);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Full Version Routing Middleware
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
export function createVersionedRoute(
|
|
117
|
+
handlers: Record<string, Handler>,
|
|
118
|
+
options: {
|
|
119
|
+
defaultVersion?: string;
|
|
120
|
+
deprecated?: Record<string, { sunset: string; migrationUrl: string }>;
|
|
121
|
+
} = {}
|
|
122
|
+
): Handler {
|
|
123
|
+
const { defaultVersion, deprecated = {} } = options;
|
|
124
|
+
|
|
125
|
+
return async (context) => {
|
|
126
|
+
const version = resolveVersion(context.request) ?? defaultVersion;
|
|
127
|
+
|
|
128
|
+
if (!version) {
|
|
129
|
+
return problemResponse({
|
|
130
|
+
type: 'https://api.example.com/problems/version-required',
|
|
131
|
+
title: 'API Version Required', status: 400,
|
|
132
|
+
detail: 'Please specify an API version via the API-Version header.',
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (!(version in handlers)) {
|
|
137
|
+
return problemResponse({
|
|
138
|
+
type: 'https://api.example.com/problems/unsupported-version',
|
|
139
|
+
title: 'Unsupported API Version', status: 400,
|
|
140
|
+
detail: `Version '${version}' is not supported.`,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const response = await handlers[version](context);
|
|
145
|
+
response.headers.set('API-Version', version);
|
|
146
|
+
|
|
147
|
+
if (version in deprecated) {
|
|
148
|
+
const { sunset, migrationUrl } = deprecated[version];
|
|
149
|
+
response.headers.set('Deprecation', 'true');
|
|
150
|
+
response.headers.set('Sunset', sunset);
|
|
151
|
+
response.headers.set('Link', `<${migrationUrl}>; rel="sunset"`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return response;
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
```
|