typecompass 1.0.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.
Potentially problematic release.
This version of typecompass might be problematic. Click here for more details.
- package/README.md +364 -0
- package/package.json +50 -0
package/README.md
ADDED
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
# TypeCompass
|
|
2
|
+
|
|
3
|
+
**Navigate your API with type-safe precision** ðŸ§
|
|
4
|
+
|
|
5
|
+
TypeCompass is a lightweight TypeScript library that lets you define API contracts once and use them everywhere — with full type safety, automatic OpenAPI generation, and zero runtime overhead.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
✨ **Type-Safe Contracts** - Define routes with Zod schemas, get full TypeScript inference
|
|
10
|
+
🔄 **Client & Server** - Use the same contract for backend validation and frontend calls
|
|
11
|
+
📚 **Auto OpenAPI** - Generate OpenAPI 3.0 specs automatically from your contracts
|
|
12
|
+
🎯 **Zero Runtime Cost** - Contracts are just types on the client side
|
|
13
|
+
🔗 **Combinable** - Merge multiple service contracts into one unified API
|
|
14
|
+
🎨 **Framework Agnostic** - Works with Express, Fastify, Hono, or any Node.js framework
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install typecompass zod
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
### 1. Define a Contract
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
// contracts/auth.contract.ts
|
|
28
|
+
import { defineRoutes } from 'typecompass'
|
|
29
|
+
import { z } from 'zod'
|
|
30
|
+
|
|
31
|
+
export const AuthContract = defineRoutes({
|
|
32
|
+
login: {
|
|
33
|
+
method: "POST",
|
|
34
|
+
path: "/auth/login",
|
|
35
|
+
summary: "User Login",
|
|
36
|
+
tags: ["Auth"],
|
|
37
|
+
body: z.object({
|
|
38
|
+
email: z.string().email(),
|
|
39
|
+
password: z.string().min(8),
|
|
40
|
+
}),
|
|
41
|
+
responses: {
|
|
42
|
+
200: {
|
|
43
|
+
schema: z.object({
|
|
44
|
+
token: z.string(),
|
|
45
|
+
user: z.object({
|
|
46
|
+
id: z.string(),
|
|
47
|
+
email: z.string(),
|
|
48
|
+
}),
|
|
49
|
+
}),
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
register: {
|
|
55
|
+
method: "POST",
|
|
56
|
+
path: "/auth/register",
|
|
57
|
+
summary: "User Registration",
|
|
58
|
+
tags: ["Auth"],
|
|
59
|
+
body: z.object({
|
|
60
|
+
email: z.string().email(),
|
|
61
|
+
password: z.string().min(8),
|
|
62
|
+
name: z.string(),
|
|
63
|
+
}),
|
|
64
|
+
},
|
|
65
|
+
})
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 2. Use on the Backend
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
// server.ts
|
|
72
|
+
import express from 'express'
|
|
73
|
+
import { AuthContract } from './contracts/auth.contract'
|
|
74
|
+
|
|
75
|
+
const app = express()
|
|
76
|
+
|
|
77
|
+
app.post('/auth/login', async (req, res) => {
|
|
78
|
+
// Validate with the contract schema
|
|
79
|
+
const data = AuthContract.login.body.parse(req.body)
|
|
80
|
+
|
|
81
|
+
// TypeScript knows the shape of data!
|
|
82
|
+
const user = await loginUser(data.email, data.password)
|
|
83
|
+
|
|
84
|
+
res.json({ token: user.token, user })
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
// Generate OpenAPI documentation
|
|
88
|
+
app.get('/openapi.json', (req, res) => {
|
|
89
|
+
const spec = AuthContract.generateOpenApi({
|
|
90
|
+
title: "Auth API",
|
|
91
|
+
version: "1.0.0",
|
|
92
|
+
servers: [{ url: "https://api.example.com", description: "Production" }],
|
|
93
|
+
})
|
|
94
|
+
res.json(spec)
|
|
95
|
+
})
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### 3. Use on the Frontend
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
// api/client.ts
|
|
102
|
+
import { AuthContract } from '@mychange/api-contracts'
|
|
103
|
+
import { initClient } from 'typecompass/client'
|
|
104
|
+
import axios from 'axios'
|
|
105
|
+
|
|
106
|
+
const axiosInstance = axios.create({
|
|
107
|
+
baseURL: 'https://api.example.com',
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
const client = initClient(AuthContract, axiosInstance)
|
|
111
|
+
|
|
112
|
+
// Fully typed API calls!
|
|
113
|
+
const response = await client.login({
|
|
114
|
+
body: {
|
|
115
|
+
email: "user@example.com",
|
|
116
|
+
password: "secret123",
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
// TypeScript knows response.data.token exists
|
|
121
|
+
console.log(response.data.token)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Combining Multiple Contracts
|
|
125
|
+
|
|
126
|
+
For larger applications with multiple services:
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
// contracts/index.ts
|
|
130
|
+
import { combineContracts } from 'typecompass'
|
|
131
|
+
import { AuthContract } from './auth.contract'
|
|
132
|
+
import { UsersContract } from './users.contract'
|
|
133
|
+
import { PaymentsContract } from './payments.contract'
|
|
134
|
+
|
|
135
|
+
export const ApiContract = combineContracts({
|
|
136
|
+
auth: AuthContract,
|
|
137
|
+
users: UsersContract,
|
|
138
|
+
payments: PaymentsContract,
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
// Generate unified OpenAPI spec
|
|
142
|
+
export const openApiSpec = ApiContract.generateOpenApi({
|
|
143
|
+
title: "MyChange API",
|
|
144
|
+
version: "1.0.0",
|
|
145
|
+
servers: [
|
|
146
|
+
{ url: "https://api.mychange.com", description: "Production" },
|
|
147
|
+
{ url: "http://localhost:3000", description: "Development" },
|
|
148
|
+
],
|
|
149
|
+
securitySchemes: {
|
|
150
|
+
BearerAuth: {
|
|
151
|
+
type: "http",
|
|
152
|
+
scheme: "bearer",
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
defaultSecurity: ["BearerAuth"],
|
|
156
|
+
})
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Client with Combined Contracts
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
// Frontend usage
|
|
163
|
+
import { ApiContract } from '@mychange/api-contracts'
|
|
164
|
+
import { initCombinedClient } from 'typecompass/client'
|
|
165
|
+
|
|
166
|
+
const client = initCombinedClient(ApiContract, axiosInstance)
|
|
167
|
+
|
|
168
|
+
// Namespaced by service
|
|
169
|
+
await client.auth.login({ body: {...} })
|
|
170
|
+
await client.users.getProfile({ params: { id: "123" } })
|
|
171
|
+
await client.payments.createCharge({ body: {...} })
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Advanced Features
|
|
175
|
+
|
|
176
|
+
### Path Parameters
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
const UsersContract = defineRoutes({
|
|
180
|
+
getUser: {
|
|
181
|
+
method: "GET",
|
|
182
|
+
path: "/users/:id",
|
|
183
|
+
summary: "Get User by ID",
|
|
184
|
+
params: z.object({
|
|
185
|
+
id: z.string(),
|
|
186
|
+
}),
|
|
187
|
+
},
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
// Client usage
|
|
191
|
+
await client.getUser({ params: { id: "user-123" } })
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Query Parameters
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
const ProductsContract = defineRoutes({
|
|
198
|
+
searchProducts: {
|
|
199
|
+
method: "GET",
|
|
200
|
+
path: "/products",
|
|
201
|
+
summary: "Search Products",
|
|
202
|
+
query: z.object({
|
|
203
|
+
q: z.string(),
|
|
204
|
+
category: z.string().optional(),
|
|
205
|
+
limit: z.number().default(10),
|
|
206
|
+
}),
|
|
207
|
+
},
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
// Client usage
|
|
211
|
+
await client.searchProducts({
|
|
212
|
+
query: { q: "laptop", category: "electronics", limit: 20 }
|
|
213
|
+
})
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Custom Security
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
const AdminContract = defineRoutes({
|
|
220
|
+
deleteUser: {
|
|
221
|
+
method: "DELETE",
|
|
222
|
+
path: "/admin/users/:id",
|
|
223
|
+
summary: "Delete User",
|
|
224
|
+
tags: ["Admin"],
|
|
225
|
+
security: ["BearerAuth", "AdminRole"],
|
|
226
|
+
params: z.object({
|
|
227
|
+
id: z.string(),
|
|
228
|
+
}),
|
|
229
|
+
},
|
|
230
|
+
})
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Schema Examples
|
|
234
|
+
|
|
235
|
+
Add examples to your schemas for better documentation:
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
import { z } from 'zod'
|
|
239
|
+
|
|
240
|
+
const createUserSchema = z.object({
|
|
241
|
+
email: z.string().email().example("john@example.com"),
|
|
242
|
+
name: z.string().example("John Doe"),
|
|
243
|
+
age: z.number().min(18).example(25),
|
|
244
|
+
})
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## Publishing Contracts
|
|
248
|
+
|
|
249
|
+
For teams with separate frontend/backend repos, publish your contracts as an npm package:
|
|
250
|
+
|
|
251
|
+
### 1. Create Contracts Package
|
|
252
|
+
packages/api-contracts/package.json
|
|
253
|
+
```json
|
|
254
|
+
|
|
255
|
+
{
|
|
256
|
+
"name": "@mychange/api-contracts",
|
|
257
|
+
"version": "1.0.0",
|
|
258
|
+
"private": true,
|
|
259
|
+
"main": "./dist/index.js",
|
|
260
|
+
"types": "./dist/index.d.ts",
|
|
261
|
+
"dependencies": {
|
|
262
|
+
"typecompass": "^1.0.0"
|
|
263
|
+
},
|
|
264
|
+
"peerDependencies": {
|
|
265
|
+
"zod": "^3.0.0"
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### 2. Export Combined Contract
|
|
271
|
+
|
|
272
|
+
```typescript
|
|
273
|
+
// packages/api-contracts/src/index.ts
|
|
274
|
+
export { ApiContract } from './contracts'
|
|
275
|
+
export type { ApiContract as ApiContractType } from './contracts'
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### 3. Install in Frontend
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
npm install @mychange/api-contracts
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
import { ApiContract } from '@mychange/api-contracts'
|
|
286
|
+
import { initCombinedClient } from 'typecompass/client'
|
|
287
|
+
|
|
288
|
+
const api = initCombinedClient(ApiContract, axiosInstance)
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
## API Reference
|
|
292
|
+
|
|
293
|
+
### `defineRoutes(routes)`
|
|
294
|
+
|
|
295
|
+
Define a single service contract.
|
|
296
|
+
|
|
297
|
+
**Parameters:**
|
|
298
|
+
- `routes` - Object mapping route names to route configurations
|
|
299
|
+
|
|
300
|
+
**Returns:** `Contract<T>`
|
|
301
|
+
|
|
302
|
+
### `combineContracts(contracts)`
|
|
303
|
+
|
|
304
|
+
Combine multiple contracts into one.
|
|
305
|
+
|
|
306
|
+
**Parameters:**
|
|
307
|
+
- `contracts` - Object mapping namespace to contracts
|
|
308
|
+
|
|
309
|
+
**Returns:** `CombinedContract<T>`
|
|
310
|
+
|
|
311
|
+
### `generateOpenApi(contract, config)`
|
|
312
|
+
|
|
313
|
+
Generate OpenAPI 3.0 specification.
|
|
314
|
+
|
|
315
|
+
**Parameters:**
|
|
316
|
+
- `contract` - Contract or CombinedContract
|
|
317
|
+
- `config` - OpenAPI configuration (title, version, servers, etc.)
|
|
318
|
+
|
|
319
|
+
**Returns:** OpenAPI JSON object
|
|
320
|
+
|
|
321
|
+
### `initClient(contract, api)`
|
|
322
|
+
|
|
323
|
+
Initialize type-safe client for a single contract.
|
|
324
|
+
|
|
325
|
+
**Parameters:**
|
|
326
|
+
- `contract` - Contract to use
|
|
327
|
+
- `api` - Axios-like API provider
|
|
328
|
+
|
|
329
|
+
**Returns:** Typed client object
|
|
330
|
+
|
|
331
|
+
### `initCombinedClient(contract, api)`
|
|
332
|
+
|
|
333
|
+
Initialize type-safe client for combined contracts.
|
|
334
|
+
|
|
335
|
+
**Parameters:**
|
|
336
|
+
- `contract` - CombinedContract to use
|
|
337
|
+
- `api` - Axios-like API provider
|
|
338
|
+
|
|
339
|
+
**Returns:** Namespaced typed client object
|
|
340
|
+
|
|
341
|
+
## Route Configuration
|
|
342
|
+
|
|
343
|
+
```typescript
|
|
344
|
+
interface RouteConfig {
|
|
345
|
+
method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE"
|
|
346
|
+
path: string // e.g., "/users/:id"
|
|
347
|
+
summary: string // Short description
|
|
348
|
+
tags?: string[] // For OpenAPI grouping
|
|
349
|
+
security?: string[] // Security scheme names
|
|
350
|
+
body?: z.ZodType // Request body schema
|
|
351
|
+
query?: z.ZodType // Query parameters schema
|
|
352
|
+
params?: z.ZodType // Path parameters schema
|
|
353
|
+
responses?: Record<number, ResponseConfig>
|
|
354
|
+
description?: string // Long description
|
|
355
|
+
}
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
## License
|
|
359
|
+
|
|
360
|
+
MIT © MyChange
|
|
361
|
+
|
|
362
|
+
## Contributing
|
|
363
|
+
|
|
364
|
+
Contributions welcome! Please open an issue or PR.
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "typecompass",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Type-safe API contracts with OpenAPI generation for TypeScript",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"require": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./client": {
|
|
14
|
+
"types": "./dist/client.d.ts",
|
|
15
|
+
"import": "./dist/client.js",
|
|
16
|
+
"require": "./dist/client.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist",
|
|
21
|
+
"README.md",
|
|
22
|
+
"LICENSE"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsc",
|
|
26
|
+
"dev": "tsc --watch",
|
|
27
|
+
"prepublishOnly": "bun run build"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"openapi",
|
|
31
|
+
"typescript",
|
|
32
|
+
"api",
|
|
33
|
+
"contract",
|
|
34
|
+
"type-safe",
|
|
35
|
+
"zod",
|
|
36
|
+
"rest",
|
|
37
|
+
"client",
|
|
38
|
+
"server"
|
|
39
|
+
],
|
|
40
|
+
"author": "MyChange",
|
|
41
|
+
"license": "MIT",
|
|
42
|
+
"peerDependencies": {
|
|
43
|
+
"zod": "^4.3.6"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/bun": "^1.3.8",
|
|
47
|
+
"typescript": "^5.0.0",
|
|
48
|
+
"zod": "^4.3.6"
|
|
49
|
+
}
|
|
50
|
+
}
|