@svazqz/api-contract-kit 0.1.6-alpha
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/LICENSE.md +9 -0
- package/README.md +448 -0
- package/dist/api-contract-kit.cjs.js +2 -0
- package/dist/api-contract-kit.cjs.js.map +1 -0
- package/dist/api-contract-kit.d.ts +1 -0
- package/dist/api-contract-kit.es.js +65 -0
- package/dist/api-contract-kit.es.js.map +1 -0
- package/dist/client.cjs.js +2 -0
- package/dist/client.cjs.js.map +1 -0
- package/dist/client.d.ts +13 -0
- package/dist/client.es.js +68 -0
- package/dist/client.es.js.map +1 -0
- package/dist/next-api-generator.d.ts +3 -0
- package/dist/open-api/index.d.ts +18 -0
- package/dist/open-api.cjs.js +2 -0
- package/dist/open-api.cjs.js.map +1 -0
- package/dist/open-api.es.js +3969 -0
- package/dist/open-api.es.js.map +1 -0
- package/dist/react-query.cjs.js +53 -0
- package/dist/react-query.cjs.js.map +1 -0
- package/dist/react-query.d.ts +14 -0
- package/dist/react-query.es.js +3055 -0
- package/dist/react-query.es.js.map +1 -0
- package/dist/server.cjs.js +2 -0
- package/dist/server.cjs.js.map +1 -0
- package/dist/server.d.ts +12 -0
- package/dist/server.es.js +2852 -0
- package/dist/server.es.js.map +1 -0
- package/dist/types.d.ts +67 -0
- package/dist/utils.d.ts +7 -0
- package/package.json +82 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Sergio Vazquez
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
# api-contract-kit
|
|
2
|
+
|
|
3
|
+
`api-contract-kit` is a TypeScript-first library for defining HTTP APIs once and reusing that definition across:
|
|
4
|
+
|
|
5
|
+
- server handlers
|
|
6
|
+
- client consumers
|
|
7
|
+
- OpenAPI generation
|
|
8
|
+
|
|
9
|
+
The library is now framework-agnostic at its core, with adapter layers for multiple server frameworks.
|
|
10
|
+
|
|
11
|
+
## Migration Notice
|
|
12
|
+
|
|
13
|
+
This package was renamed from `@svazqz/next-api-generator` to `@svazqz/api-contract-kit`.
|
|
14
|
+
|
|
15
|
+
Replace imports and installation commands to use the new package name.
|
|
16
|
+
|
|
17
|
+
## Why this library exists
|
|
18
|
+
|
|
19
|
+
Many projects duplicate API contracts in multiple places:
|
|
20
|
+
|
|
21
|
+
- route handlers
|
|
22
|
+
- frontend consumers
|
|
23
|
+
- OpenAPI specs
|
|
24
|
+
- runtime validation code
|
|
25
|
+
|
|
26
|
+
This library removes that duplication by making your endpoint definition the source of truth.
|
|
27
|
+
|
|
28
|
+
## Design Principles
|
|
29
|
+
|
|
30
|
+
1. **Contract-first development**
|
|
31
|
+
- Define method/path/schemas before implementing business logic.
|
|
32
|
+
2. **Framework-agnostic core**
|
|
33
|
+
- Build around standard `Request`/`Response` so the same contract works in Node and Edge runtimes.
|
|
34
|
+
3. **Runtime validation with Zod**
|
|
35
|
+
- Validate query/body/output at runtime, while keeping full static inference in TypeScript.
|
|
36
|
+
4. **Pluggable transport format**
|
|
37
|
+
- JSON is the default codec, but request/response encoding is extensible.
|
|
38
|
+
5. **Optional integrations**
|
|
39
|
+
- React Query integration is available, but not required.
|
|
40
|
+
6. **Documentation automation**
|
|
41
|
+
- OpenAPI metadata is generated from the same endpoint definitions used at runtime.
|
|
42
|
+
|
|
43
|
+
## Capabilities
|
|
44
|
+
|
|
45
|
+
### API Definition
|
|
46
|
+
|
|
47
|
+
- Define endpoint path and method.
|
|
48
|
+
- Attach Zod schemas for:
|
|
49
|
+
- URL params
|
|
50
|
+
- query params
|
|
51
|
+
- payload/body
|
|
52
|
+
- response/output
|
|
53
|
+
- Add OpenAPI metadata:
|
|
54
|
+
- summary
|
|
55
|
+
- description
|
|
56
|
+
- tags
|
|
57
|
+
- operationId
|
|
58
|
+
- deprecated
|
|
59
|
+
- security requirements
|
|
60
|
+
- custom responses
|
|
61
|
+
- Attach auth function and custom error mapping.
|
|
62
|
+
- Attach custom codec for non-JSON protocols.
|
|
63
|
+
|
|
64
|
+
### Server Runtime
|
|
65
|
+
|
|
66
|
+
- Framework-agnostic wrapper built on Fetch `Request`/`Response`.
|
|
67
|
+
- Default validation flow:
|
|
68
|
+
- URL params
|
|
69
|
+
- query params
|
|
70
|
+
- payload
|
|
71
|
+
- handler execution
|
|
72
|
+
- optional response validation
|
|
73
|
+
- Structured error handling through an overridable `errorMapper`.
|
|
74
|
+
- Auth handling through an overridable `auth(request)` function.
|
|
75
|
+
- Default JSON response encoding, with codec support for custom formats.
|
|
76
|
+
|
|
77
|
+
### Client Runtime
|
|
78
|
+
|
|
79
|
+
- Framework-agnostic typed client (`createClient` and `apiConsumer`).
|
|
80
|
+
- Supports:
|
|
81
|
+
- typed params/query/body payloads
|
|
82
|
+
- request headers
|
|
83
|
+
- `AbortSignal`
|
|
84
|
+
- configurable fetch implementation
|
|
85
|
+
- optional dynamic headers provider
|
|
86
|
+
- Optional React Query adapter shipped as a separate module.
|
|
87
|
+
|
|
88
|
+
### OpenAPI
|
|
89
|
+
|
|
90
|
+
- Generates OpenAPI definitions from endpoint contracts.
|
|
91
|
+
- Supports operation metadata and response customization.
|
|
92
|
+
- CLI-oriented workflow intended for CI/CD pipelines.
|
|
93
|
+
- Produces static OpenAPI JSON suitable for artifact publishing and static docs hosting.
|
|
94
|
+
|
|
95
|
+
## Runtime & Framework Support
|
|
96
|
+
|
|
97
|
+
### Core Runtime
|
|
98
|
+
|
|
99
|
+
The core is Fetch-based (`Request`/`Response`) to support both:
|
|
100
|
+
|
|
101
|
+
- **Node runtimes**
|
|
102
|
+
- **Edge-compatible runtimes**
|
|
103
|
+
|
|
104
|
+
### Server Adapters
|
|
105
|
+
|
|
106
|
+
First-party adapters are available in the server module:
|
|
107
|
+
|
|
108
|
+
- `nextAdapter`
|
|
109
|
+
- `expressAdapter`
|
|
110
|
+
- `fastifyAdapter`
|
|
111
|
+
- `honoAdapter`
|
|
112
|
+
- `nestAdapter` (via Express-style adapter strategy)
|
|
113
|
+
|
|
114
|
+
You can also use `apiWrapper` directly when your runtime already speaks Fetch.
|
|
115
|
+
|
|
116
|
+
## Installation
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
npm install @svazqz/api-contract-kit zod @asteasolutions/zod-to-openapi
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
If you want React Query integration:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
npm install @tanstack/react-query
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Package Entry Points
|
|
129
|
+
|
|
130
|
+
- `@svazqz/api-contract-kit/dist/api-contract-kit`
|
|
131
|
+
- `@svazqz/api-contract-kit/dist/server`
|
|
132
|
+
- `@svazqz/api-contract-kit/dist/client`
|
|
133
|
+
- `@svazqz/api-contract-kit/dist/react-query` (optional integration)
|
|
134
|
+
|
|
135
|
+
## Core Types and Architecture Decisions
|
|
136
|
+
|
|
137
|
+
### `ServerFnDefinition`
|
|
138
|
+
|
|
139
|
+
This is the central contract type. It contains endpoint information, runtime behavior hooks, validation schemas, and OpenAPI metadata.
|
|
140
|
+
|
|
141
|
+
Architectural decision:
|
|
142
|
+
|
|
143
|
+
- keep one definition object as the single source of truth
|
|
144
|
+
- prevent drift between implementation, client code, and docs
|
|
145
|
+
|
|
146
|
+
### `HandlerFn`
|
|
147
|
+
|
|
148
|
+
Handler functions are typed from your schemas. This gives strongly typed payload/query/params inside your business logic.
|
|
149
|
+
|
|
150
|
+
Architectural decision:
|
|
151
|
+
|
|
152
|
+
- infer types from Zod schemas to avoid parallel interface maintenance
|
|
153
|
+
|
|
154
|
+
### `ApiCodec`
|
|
155
|
+
|
|
156
|
+
A codec defines how request bodies are decoded and response bodies are encoded.
|
|
157
|
+
|
|
158
|
+
Architectural decision:
|
|
159
|
+
|
|
160
|
+
- default to JSON for usability
|
|
161
|
+
- keep protocol support open for future formats (for example protobuf)
|
|
162
|
+
|
|
163
|
+
### `ErrorMapper`
|
|
164
|
+
|
|
165
|
+
Error mapping is centralized and overridable.
|
|
166
|
+
|
|
167
|
+
Architectural decision:
|
|
168
|
+
|
|
169
|
+
- provide sensible defaults
|
|
170
|
+
- let applications align API error shape/status with their platform standards
|
|
171
|
+
|
|
172
|
+
## Quick Start
|
|
173
|
+
|
|
174
|
+
### 1) Define schemas
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
import { z } from 'zod';
|
|
178
|
+
import { extendZodWithOpenApi } from '@asteasolutions/zod-to-openapi';
|
|
179
|
+
|
|
180
|
+
extendZodWithOpenApi(z);
|
|
181
|
+
|
|
182
|
+
export const Coordinates = z
|
|
183
|
+
.object({
|
|
184
|
+
latitude: z.number(),
|
|
185
|
+
longitude: z.number(),
|
|
186
|
+
})
|
|
187
|
+
.openapi('Coordinates');
|
|
188
|
+
|
|
189
|
+
export const LocationData = z
|
|
190
|
+
.object({
|
|
191
|
+
city: z.string(),
|
|
192
|
+
state: z.string(),
|
|
193
|
+
country: z.string(),
|
|
194
|
+
})
|
|
195
|
+
.openapi('LocationData');
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### 2) Define endpoints
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
import { createAPIDefinition } from '@svazqz/api-contract-kit/dist/api-contract-kit';
|
|
202
|
+
import { Coordinates, LocationData } from './schemas';
|
|
203
|
+
|
|
204
|
+
export const getGeoData = createAPIDefinition({
|
|
205
|
+
method: 'get',
|
|
206
|
+
path: '/geo',
|
|
207
|
+
schemas: {
|
|
208
|
+
queryParams: Coordinates,
|
|
209
|
+
response: LocationData,
|
|
210
|
+
},
|
|
211
|
+
openapi: {
|
|
212
|
+
summary: 'Get geo data',
|
|
213
|
+
description: 'Returns city/state/country from coordinates',
|
|
214
|
+
tags: ['geo'],
|
|
215
|
+
operationId: 'getGeoData',
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
export const postGeoData = createAPIDefinition({
|
|
220
|
+
method: 'post',
|
|
221
|
+
path: '/geo',
|
|
222
|
+
schemas: {
|
|
223
|
+
payload: Coordinates,
|
|
224
|
+
response: LocationData,
|
|
225
|
+
},
|
|
226
|
+
openapi: {
|
|
227
|
+
summary: 'Create geo lookup',
|
|
228
|
+
tags: ['geo'],
|
|
229
|
+
operationId: 'postGeoData',
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### 3) Implement server handlers
|
|
235
|
+
|
|
236
|
+
#### Next.js App Router
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
import { nextAdapter } from '@svazqz/api-contract-kit/dist/server';
|
|
240
|
+
import { getGeoData, postGeoData } from './api-definitions';
|
|
241
|
+
|
|
242
|
+
export const GET = nextAdapter(getGeoData, async (_request, query) => {
|
|
243
|
+
const lat = query?.latitude;
|
|
244
|
+
const lon = query?.longitude;
|
|
245
|
+
const response = await fetch(`https://geocode.xyz/${lat},${lon}?json=1`);
|
|
246
|
+
const json = await response.json();
|
|
247
|
+
const full = json.standard || json;
|
|
248
|
+
return {
|
|
249
|
+
city: full.city,
|
|
250
|
+
state: full.state,
|
|
251
|
+
country: full.country,
|
|
252
|
+
};
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
export const POST = nextAdapter(postGeoData, async (_request, _query, _params, payload) => {
|
|
256
|
+
const lat = payload?.latitude;
|
|
257
|
+
const lon = payload?.longitude;
|
|
258
|
+
const response = await fetch(`https://geocode.xyz/${lat},${lon}?json=1`);
|
|
259
|
+
const json = await response.json();
|
|
260
|
+
const full = json.standard || json;
|
|
261
|
+
return {
|
|
262
|
+
city: full.city,
|
|
263
|
+
state: full.state,
|
|
264
|
+
country: full.country,
|
|
265
|
+
};
|
|
266
|
+
});
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
#### Express
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
import express from 'express';
|
|
273
|
+
import { expressAdapter } from '@svazqz/api-contract-kit/dist/server';
|
|
274
|
+
import { getGeoData } from './api-definitions';
|
|
275
|
+
|
|
276
|
+
const app = express();
|
|
277
|
+
app.use(express.json());
|
|
278
|
+
|
|
279
|
+
app.get('/geo', expressAdapter(getGeoData, async (_request, query) => {
|
|
280
|
+
return {
|
|
281
|
+
city: 'CDMX',
|
|
282
|
+
state: 'CDMX',
|
|
283
|
+
country: 'MX',
|
|
284
|
+
};
|
|
285
|
+
}));
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
#### Fastify
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
import Fastify from 'fastify';
|
|
292
|
+
import { fastifyAdapter } from '@svazqz/api-contract-kit/dist/server';
|
|
293
|
+
import { getGeoData } from './api-definitions';
|
|
294
|
+
|
|
295
|
+
const app = Fastify();
|
|
296
|
+
|
|
297
|
+
app.get('/geo', fastifyAdapter(getGeoData, async () => {
|
|
298
|
+
return {
|
|
299
|
+
city: 'CDMX',
|
|
300
|
+
state: 'CDMX',
|
|
301
|
+
country: 'MX',
|
|
302
|
+
};
|
|
303
|
+
}));
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
#### Hono
|
|
307
|
+
|
|
308
|
+
```typescript
|
|
309
|
+
import { Hono } from 'hono';
|
|
310
|
+
import { honoAdapter } from '@svazqz/api-contract-kit/dist/server';
|
|
311
|
+
import { getGeoData } from './api-definitions';
|
|
312
|
+
|
|
313
|
+
const app = new Hono();
|
|
314
|
+
|
|
315
|
+
app.get('/geo', honoAdapter(getGeoData, async () => {
|
|
316
|
+
return {
|
|
317
|
+
city: 'CDMX',
|
|
318
|
+
state: 'CDMX',
|
|
319
|
+
country: 'MX',
|
|
320
|
+
};
|
|
321
|
+
}));
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### 4) Consume from client (framework-agnostic)
|
|
325
|
+
|
|
326
|
+
#### Using `createClient`
|
|
327
|
+
|
|
328
|
+
```typescript
|
|
329
|
+
import { createClient } from '@svazqz/api-contract-kit/dist/client';
|
|
330
|
+
import { getGeoData } from './api-definitions';
|
|
331
|
+
|
|
332
|
+
const client = createClient({
|
|
333
|
+
baseUrl: 'https://my-api.example.com',
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
const result = await client.call(getGeoData, {
|
|
337
|
+
query: { latitude: 19.43, longitude: -99.13 },
|
|
338
|
+
});
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
#### Using `apiConsumer`
|
|
342
|
+
|
|
343
|
+
```typescript
|
|
344
|
+
import { apiConsumer } from '@svazqz/api-contract-kit/dist/client';
|
|
345
|
+
import { postGeoData } from './api-definitions';
|
|
346
|
+
|
|
347
|
+
const callPostGeo = apiConsumer(postGeoData, {
|
|
348
|
+
baseUrl: 'https://my-api.example.com',
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
const result = await callPostGeo({
|
|
352
|
+
body: { latitude: 19.43, longitude: -99.13 },
|
|
353
|
+
});
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### 5) Optional React Query adapter
|
|
357
|
+
|
|
358
|
+
```typescript
|
|
359
|
+
import { createReactQueryAdapter } from '@svazqz/api-contract-kit/dist/react-query';
|
|
360
|
+
import { getGeoData } from './api-definitions';
|
|
361
|
+
|
|
362
|
+
const rq = createReactQueryAdapter({
|
|
363
|
+
baseUrl: 'https://my-api.example.com',
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
const { query } = rq.useApiQuery(
|
|
367
|
+
getGeoData,
|
|
368
|
+
{ query: { latitude: 19.43, longitude: -99.13 } },
|
|
369
|
+
{ enabled: true },
|
|
370
|
+
);
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
## Advanced Configuration
|
|
374
|
+
|
|
375
|
+
### Auth
|
|
376
|
+
|
|
377
|
+
Provide `auth(request)` in your API definition. It can return:
|
|
378
|
+
|
|
379
|
+
- `true` / `false`
|
|
380
|
+
- or an object with `ok`, `status`, `body`, `headers`
|
|
381
|
+
|
|
382
|
+
This allows centralized authorization behavior before business logic runs.
|
|
383
|
+
|
|
384
|
+
### Error Mapping
|
|
385
|
+
|
|
386
|
+
Provide `errorMapper` in your definition to transform runtime/validation/auth errors into your API error contract.
|
|
387
|
+
|
|
388
|
+
### Custom Codecs
|
|
389
|
+
|
|
390
|
+
You can provide `codec` in your definition to customize request decoding and response encoding.
|
|
391
|
+
|
|
392
|
+
Current built-in default: JSON codec.
|
|
393
|
+
|
|
394
|
+
Architectural rationale:
|
|
395
|
+
|
|
396
|
+
- keep the transport layer extensible without changing business handlers
|
|
397
|
+
|
|
398
|
+
## OpenAPI Generation
|
|
399
|
+
|
|
400
|
+
The OpenAPI generator uses your endpoint definitions and metadata to build a static JSON document.
|
|
401
|
+
|
|
402
|
+
CLI binary:
|
|
403
|
+
|
|
404
|
+
```bash
|
|
405
|
+
export-open-api
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
The generator accepts positional arguments for:
|
|
409
|
+
|
|
410
|
+
- API definitions index module path
|
|
411
|
+
- output directory (optional, defaults to `dist`)
|
|
412
|
+
- API path prefix (optional, defaults to `/api`)
|
|
413
|
+
|
|
414
|
+
Typical CI flow:
|
|
415
|
+
|
|
416
|
+
1. Build your project definitions.
|
|
417
|
+
2. Run OpenAPI export.
|
|
418
|
+
3. Publish generated JSON artifact.
|
|
419
|
+
4. Serve static API docs (for example in GitHub Pages) from that artifact.
|
|
420
|
+
|
|
421
|
+
## Current Architectural Trade-offs
|
|
422
|
+
|
|
423
|
+
1. **Fetch-based core**
|
|
424
|
+
- Great portability to Edge and modern runtimes.
|
|
425
|
+
- Requires adapter conversion for classic Node middleware stacks.
|
|
426
|
+
2. **Zod as contract runtime**
|
|
427
|
+
- Strong validation and type inference.
|
|
428
|
+
- Requires schema discipline in all endpoint definitions.
|
|
429
|
+
3. **Optional React Query module**
|
|
430
|
+
- Keeps core lightweight and framework-agnostic.
|
|
431
|
+
- Users needing hooks must install adapter peer dependencies.
|
|
432
|
+
4. **Codec abstraction**
|
|
433
|
+
- Future-proofs transport format strategy.
|
|
434
|
+
- Adds a small abstraction layer to runtime pipeline.
|
|
435
|
+
|
|
436
|
+
## Project Status
|
|
437
|
+
|
|
438
|
+
The library now supports a modular architecture with:
|
|
439
|
+
|
|
440
|
+
- framework-agnostic server core
|
|
441
|
+
- multi-framework adapters
|
|
442
|
+
- framework-agnostic client core
|
|
443
|
+
- optional React Query integration
|
|
444
|
+
- OpenAPI automation designed for CI/CD pipelines
|
|
445
|
+
|
|
446
|
+
## Donate
|
|
447
|
+
|
|
448
|
+
[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=BTJPCXNPH43YC)
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const R=e=>{var a,p,i,r,c,u,y,h,m,d,l,P,g,q,j,C,T,x,$,b;const o=`${e.openapiPathPrefix??"/api"}`,s=`${e.endpoint??e.path??""}`,t=e.codec??A,n={method:e.method,path:`${o}${s}`,summary:((a=e.openapi)==null?void 0:a.summary)??"",request:{},responses:{}},I=(p=e.schemas)!=null&&p.response?{200:{description:((c=(r=(i=e.openapi)==null?void 0:i.responses)==null?void 0:r[200])==null?void 0:c.description)??"",content:{[t.responseContentType]:{schema:(u=e.schemas)==null?void 0:u.response}}}}:{},O=((y=e.openapi)==null?void 0:y.responses)??{};return n.responses={...I,...O},(h=e.schemas)!=null&&h.queryParams&&(n.request.query=(d=(m=e.schemas)==null?void 0:m.queryParams)==null?void 0:d.openapi("Query Params")),(l=e.schemas)!=null&&l.payload&&(n.request.body={description:"Body",content:{[t.requestContentType]:{schema:(P=e.schemas)==null?void 0:P.payload}},required:!0}),(g=e.schemas)!=null&&g.urlArgs&&(n.request.params=(j=(q=e.schemas)==null?void 0:q.urlArgs)==null?void 0:j.openapi("URL Params")),n.description=(C=e.openapi)==null?void 0:C.description,n.tags=(T=e.openapi)==null?void 0:T.tags,n.operationId=(x=e.openapi)==null?void 0:x.operationId,n.deprecated=($=e.openapi)==null?void 0:$.deprecated,n.security=(b=e.openapi)==null?void 0:b.security,n},A={id:"json",requestContentType:"application/json",responseContentType:"application/json",decodeRequest:async e=>{if(!(e.headers.get("content-type")||"").includes("application/json")){const t=await e.text();if(!t)return;try{return JSON.parse(t)}catch{return t}}const s=await e.text();if(s)return JSON.parse(s)},encodeResponse:async e=>({body:JSON.stringify(e),headers:{"content-type":"application/json"}})},S=e=>{const o=`${e.path??e.endpoint??""}`,s=o.startsWith("/")?o:`/${o}`,t={method:"get",...e,path:s==="/"?"":s,endpoint:s==="/"?"":s,codec:e.codec??A};return t.apiConfig=R(t),t};exports.createAPIDefinition=S;
|
|
2
|
+
//# sourceMappingURL=api-contract-kit.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-contract-kit.cjs.js","sources":["../src/utils.ts","../src/next-api-generator.ts"],"sourcesContent":["import { ZodType, ZodNumber, ZodBoolean } from 'zod';\nimport { ApiCodec, ServerFnDefinition } from './types';\nimport { RouteConfig } from '@asteasolutions/zod-to-openapi';\n\nexport function validateQueryParams<\n URLParams extends ZodType,\n QueryParams extends ZodType,\n Body extends ZodType,\n Response extends ZodType,\n>(\n request: Request,\n def: ServerFnDefinition<URLParams, QueryParams, Body, Response>,\n) {\n const queryParams = {};\n const params = new URL(request.url).searchParams.keys();\n for (const param of params) {\n if ((def.schemas?.queryParams as any).shape[param] instanceof ZodNumber) {\n (queryParams as unknown as any)[param as unknown as string] = Number(\n new URL(request.url).searchParams.get(param),\n );\n } else if (\n (def.schemas?.queryParams as any).shape[param] instanceof ZodBoolean\n ) {\n (queryParams as unknown as any)[param as unknown as string] = Boolean(\n new URL(request.url).searchParams.get(param),\n );\n } else {\n (queryParams as unknown as any)[param as unknown as string] =\n new URL(request.url).searchParams.get(param);\n }\n }\n def.schemas?.queryParams?.parse(queryParams);\n return queryParams;\n}\n\nexport async function validatePayload<\n URLParams extends ZodType,\n QueryParams extends ZodType,\n Body extends ZodType,\n Response extends ZodType,\n>(\n request: Request,\n def: ServerFnDefinition<URLParams, QueryParams, Body, Response>,\n ProtoClass: any,\n) {\n let parsedPayload: Body | undefined = undefined;\n\n if (ProtoClass) {\n try {\n const lResponse = await request.arrayBuffer();\n parsedPayload = ProtoClass.decode(new Uint8Array(lResponse));\n } catch {\n throw new Error('Protocol buffer parsing error');\n }\n } else {\n parsedPayload = await request.json();\n }\n def.schemas?.payload?.parse(parsedPayload);\n return parsedPayload;\n}\n\nexport const setOpenAPIMetadata = (_def: any) => {\n const openapiPathPrefix = `${_def.openapiPathPrefix ?? '/api'}`;\n const endpoint = `${_def.endpoint ?? _def.path ?? ''}`;\n const codec = _def.codec ?? jsonCodec;\n\n const apiConfig = {\n method: _def.method,\n path: `${openapiPathPrefix}${endpoint}`,\n summary: _def.openapi?.summary ?? '',\n request: {},\n responses: {},\n } as RouteConfig;\n\n const baseResponses =\n _def.schemas?.response\n ? {\n 200: {\n description: _def.openapi?.responses?.[200]?.description ?? '',\n content: {\n [codec.responseContentType]: {\n schema: _def.schemas?.response,\n },\n },\n },\n }\n : {};\n\n const customResponses = _def.openapi?.responses ?? {};\n apiConfig.responses = { ...baseResponses, ...customResponses };\n\n if (_def.schemas?.queryParams) {\n (apiConfig.request as any).query = (\n _def.schemas?.queryParams as any\n )?.openapi('Query Params');\n }\n if (_def.schemas?.payload) {\n (apiConfig.request as any).body = {\n description: 'Body',\n content: {\n [codec.requestContentType]: {\n schema: _def.schemas?.payload,\n },\n },\n required: true,\n };\n }\n if (_def.schemas?.urlArgs) {\n (apiConfig.request as any).params = (_def.schemas?.urlArgs as any)?.openapi(\n 'URL Params',\n );\n }\n\n (apiConfig as any).description = _def.openapi?.description;\n (apiConfig as any).tags = _def.openapi?.tags;\n (apiConfig as any).operationId = _def.openapi?.operationId;\n (apiConfig as any).deprecated = _def.openapi?.deprecated;\n (apiConfig as any).security = _def.openapi?.security;\n\n return apiConfig;\n};\n\nexport const jsonCodec: ApiCodec = {\n id: 'json',\n requestContentType: 'application/json',\n responseContentType: 'application/json',\n decodeRequest: async (request) => {\n const contentType = request.headers.get('content-type') || '';\n if (!contentType.includes('application/json')) {\n const text = await request.text();\n if (!text) return undefined;\n try {\n return JSON.parse(text);\n } catch {\n return text;\n }\n }\n const text = await request.text();\n if (!text) return undefined;\n return JSON.parse(text);\n },\n encodeResponse: async (value) => ({\n body: JSON.stringify(value),\n headers: { 'content-type': 'application/json' },\n }),\n};\n","import { ZodType } from 'zod';\nimport { jsonCodec, setOpenAPIMetadata } from './utils';\nimport { ServerFnDefinition } from './types';\n\nexport const createAPIDefinition = <\n URLParams extends ZodType,\n QueryParams extends ZodType,\n Body extends ZodType,\n ResponseSchema extends ZodType,\n>(\n def: ServerFnDefinition<URLParams, QueryParams, Body, ResponseSchema>,\n): ServerFnDefinition<URLParams, QueryParams, Body, ResponseSchema> => {\n const path = `${def.path ?? def.endpoint ?? ''}`;\n const normalizedPath = path.startsWith('/') ? path : `/${path}`;\n\n const _def = {\n method: 'get',\n ...def,\n ...{\n path: normalizedPath === '/' ? '' : normalizedPath,\n endpoint: normalizedPath === '/' ? '' : normalizedPath,\n },\n codec: def.codec ?? jsonCodec,\n };\n\n (_def as any).apiConfig = setOpenAPIMetadata(_def);\n\n return _def;\n};\n"],"names":["setOpenAPIMetadata","_def","openapiPathPrefix","endpoint","codec","jsonCodec","apiConfig","_a","baseResponses","_b","_e","_d","_c","_f","customResponses","_g","_h","_j","_i","_k","_l","_m","_o","_n","_p","_q","_r","_s","_t","request","text","value","createAPIDefinition","def","path","normalizedPath"],"mappings":"gFA6DO,MAAMA,EAAsBC,GAAc,6CAC/C,MAAMC,EAAoB,GAAGD,EAAK,mBAAqB,MAAM,GACvDE,EAAW,GAAGF,EAAK,UAAYA,EAAK,MAAQ,EAAE,GAC9CG,EAAQH,EAAK,OAASI,EAEtBC,EAAY,CAChB,OAAQL,EAAK,OACb,KAAM,GAAGC,CAAiB,GAAGC,CAAQ,GACrC,UAASI,EAAAN,EAAK,UAAL,YAAAM,EAAc,UAAW,GAClC,QAAS,CAAA,EACT,UAAW,CAAA,CAAC,EAGRC,GACJC,EAAAR,EAAK,UAAL,MAAAQ,EAAc,SACV,CACE,IAAK,CACH,cAAaC,GAAAC,GAAAC,EAAAX,EAAK,UAAL,YAAAW,EAAc,YAAd,YAAAD,EAA0B,OAA1B,YAAAD,EAAgC,cAAe,GAC5D,QAAS,CACP,CAACN,EAAM,mBAAmB,EAAG,CAC3B,QAAQS,EAAAZ,EAAK,UAAL,YAAAY,EAAc,QAAA,CACxB,CACF,CACF,EAEF,CAAA,EAEAC,IAAkBC,EAAAd,EAAK,UAAL,YAAAc,EAAc,YAAa,CAAA,EACnD,OAAAT,EAAU,UAAY,CAAE,GAAGE,EAAe,GAAGM,CAAA,GAEzCE,EAAAf,EAAK,UAAL,MAAAe,EAAc,cACfV,EAAU,QAAgB,OACzBW,GAAAC,EAAAjB,EAAK,UAAL,YAAAiB,EAAc,cAAd,YAAAD,EACC,QAAQ,kBAETE,EAAAlB,EAAK,UAAL,MAAAkB,EAAc,UACfb,EAAU,QAAgB,KAAO,CAChC,YAAa,OACb,QAAS,CACP,CAACF,EAAM,kBAAkB,EAAG,CAC1B,QAAQgB,EAAAnB,EAAK,UAAL,YAAAmB,EAAc,OAAA,CACxB,EAEF,SAAU,EAAA,IAGVC,EAAApB,EAAK,UAAL,MAAAoB,EAAc,UACff,EAAU,QAAgB,QAAUgB,GAAAC,EAAAtB,EAAK,UAAL,YAAAsB,EAAc,UAAd,YAAAD,EAA+B,QAClE,eAIHhB,EAAkB,aAAckB,EAAAvB,EAAK,UAAL,YAAAuB,EAAc,YAC9ClB,EAAkB,MAAOmB,EAAAxB,EAAK,UAAL,YAAAwB,EAAc,KACvCnB,EAAkB,aAAcoB,EAAAzB,EAAK,UAAL,YAAAyB,EAAc,YAC9CpB,EAAkB,YAAaqB,EAAA1B,EAAK,UAAL,YAAA0B,EAAc,WAC7CrB,EAAkB,UAAWsB,EAAA3B,EAAK,UAAL,YAAA2B,EAAc,SAErCtB,CACT,EAEaD,EAAsB,CACjC,GAAI,OACJ,mBAAoB,mBACpB,oBAAqB,mBACrB,cAAe,MAAOwB,GAAY,CAEhC,GAAI,EADgBA,EAAQ,QAAQ,IAAI,cAAc,GAAK,IAC1C,SAAS,kBAAkB,EAAG,CAC7C,MAAMC,EAAO,MAAMD,EAAQ,KAAA,EAC3B,GAAI,CAACC,EAAM,OACX,GAAI,CACF,OAAO,KAAK,MAAMA,CAAI,CACxB,MAAQ,CACN,OAAOA,CACT,CACF,CACA,MAAMA,EAAO,MAAMD,EAAQ,KAAA,EAC3B,GAAKC,EACL,OAAO,KAAK,MAAMA,CAAI,CACxB,EACA,eAAgB,MAAOC,IAAW,CAChC,KAAM,KAAK,UAAUA,CAAK,EAC1B,QAAS,CAAE,eAAgB,kBAAA,CAAmB,EAElD,EC7IaC,EAMXC,GACqE,CACrE,MAAMC,EAAO,GAAGD,EAAI,MAAQA,EAAI,UAAY,EAAE,GACxCE,EAAiBD,EAAK,WAAW,GAAG,EAAIA,EAAO,IAAIA,CAAI,GAEvDjC,EAAO,CACX,OAAQ,MACR,GAAGgC,EAED,KAAME,IAAmB,IAAM,GAAKA,EACpC,SAAUA,IAAmB,IAAM,GAAKA,EAE1C,MAAOF,EAAI,OAAS5B,CAAA,EAGrB,OAAAJ,EAAa,UAAYD,EAAmBC,CAAI,EAE1CA,CACT"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createAPIDefinition } from './next-api-generator';
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const b = (e) => {
|
|
2
|
+
var a, p, r, i, c, u, h, y, m, d, P, l, g, q, x, C, j, T, $, R;
|
|
3
|
+
const o = `${e.openapiPathPrefix ?? "/api"}`, n = `${e.endpoint ?? e.path ?? ""}`, t = e.codec ?? A, s = {
|
|
4
|
+
method: e.method,
|
|
5
|
+
path: `${o}${n}`,
|
|
6
|
+
summary: ((a = e.openapi) == null ? void 0 : a.summary) ?? "",
|
|
7
|
+
request: {},
|
|
8
|
+
responses: {}
|
|
9
|
+
}, I = (p = e.schemas) != null && p.response ? {
|
|
10
|
+
200: {
|
|
11
|
+
description: ((c = (i = (r = e.openapi) == null ? void 0 : r.responses) == null ? void 0 : i[200]) == null ? void 0 : c.description) ?? "",
|
|
12
|
+
content: {
|
|
13
|
+
[t.responseContentType]: {
|
|
14
|
+
schema: (u = e.schemas) == null ? void 0 : u.response
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
} : {}, O = ((h = e.openapi) == null ? void 0 : h.responses) ?? {};
|
|
19
|
+
return s.responses = { ...I, ...O }, (y = e.schemas) != null && y.queryParams && (s.request.query = (d = (m = e.schemas) == null ? void 0 : m.queryParams) == null ? void 0 : d.openapi("Query Params")), (P = e.schemas) != null && P.payload && (s.request.body = {
|
|
20
|
+
description: "Body",
|
|
21
|
+
content: {
|
|
22
|
+
[t.requestContentType]: {
|
|
23
|
+
schema: (l = e.schemas) == null ? void 0 : l.payload
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
required: !0
|
|
27
|
+
}), (g = e.schemas) != null && g.urlArgs && (s.request.params = (x = (q = e.schemas) == null ? void 0 : q.urlArgs) == null ? void 0 : x.openapi(
|
|
28
|
+
"URL Params"
|
|
29
|
+
)), s.description = (C = e.openapi) == null ? void 0 : C.description, s.tags = (j = e.openapi) == null ? void 0 : j.tags, s.operationId = (T = e.openapi) == null ? void 0 : T.operationId, s.deprecated = ($ = e.openapi) == null ? void 0 : $.deprecated, s.security = (R = e.openapi) == null ? void 0 : R.security, s;
|
|
30
|
+
}, A = {
|
|
31
|
+
id: "json",
|
|
32
|
+
requestContentType: "application/json",
|
|
33
|
+
responseContentType: "application/json",
|
|
34
|
+
decodeRequest: async (e) => {
|
|
35
|
+
if (!(e.headers.get("content-type") || "").includes("application/json")) {
|
|
36
|
+
const t = await e.text();
|
|
37
|
+
if (!t) return;
|
|
38
|
+
try {
|
|
39
|
+
return JSON.parse(t);
|
|
40
|
+
} catch {
|
|
41
|
+
return t;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const n = await e.text();
|
|
45
|
+
if (n)
|
|
46
|
+
return JSON.parse(n);
|
|
47
|
+
},
|
|
48
|
+
encodeResponse: async (e) => ({
|
|
49
|
+
body: JSON.stringify(e),
|
|
50
|
+
headers: { "content-type": "application/json" }
|
|
51
|
+
})
|
|
52
|
+
}, J = (e) => {
|
|
53
|
+
const o = `${e.path ?? e.endpoint ?? ""}`, n = o.startsWith("/") ? o : `/${o}`, t = {
|
|
54
|
+
method: "get",
|
|
55
|
+
...e,
|
|
56
|
+
path: n === "/" ? "" : n,
|
|
57
|
+
endpoint: n === "/" ? "" : n,
|
|
58
|
+
codec: e.codec ?? A
|
|
59
|
+
};
|
|
60
|
+
return t.apiConfig = b(t), t;
|
|
61
|
+
};
|
|
62
|
+
export {
|
|
63
|
+
J as createAPIDefinition
|
|
64
|
+
};
|
|
65
|
+
//# sourceMappingURL=api-contract-kit.es.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-contract-kit.es.js","sources":["../src/utils.ts","../src/next-api-generator.ts"],"sourcesContent":["import { ZodType, ZodNumber, ZodBoolean } from 'zod';\nimport { ApiCodec, ServerFnDefinition } from './types';\nimport { RouteConfig } from '@asteasolutions/zod-to-openapi';\n\nexport function validateQueryParams<\n URLParams extends ZodType,\n QueryParams extends ZodType,\n Body extends ZodType,\n Response extends ZodType,\n>(\n request: Request,\n def: ServerFnDefinition<URLParams, QueryParams, Body, Response>,\n) {\n const queryParams = {};\n const params = new URL(request.url).searchParams.keys();\n for (const param of params) {\n if ((def.schemas?.queryParams as any).shape[param] instanceof ZodNumber) {\n (queryParams as unknown as any)[param as unknown as string] = Number(\n new URL(request.url).searchParams.get(param),\n );\n } else if (\n (def.schemas?.queryParams as any).shape[param] instanceof ZodBoolean\n ) {\n (queryParams as unknown as any)[param as unknown as string] = Boolean(\n new URL(request.url).searchParams.get(param),\n );\n } else {\n (queryParams as unknown as any)[param as unknown as string] =\n new URL(request.url).searchParams.get(param);\n }\n }\n def.schemas?.queryParams?.parse(queryParams);\n return queryParams;\n}\n\nexport async function validatePayload<\n URLParams extends ZodType,\n QueryParams extends ZodType,\n Body extends ZodType,\n Response extends ZodType,\n>(\n request: Request,\n def: ServerFnDefinition<URLParams, QueryParams, Body, Response>,\n ProtoClass: any,\n) {\n let parsedPayload: Body | undefined = undefined;\n\n if (ProtoClass) {\n try {\n const lResponse = await request.arrayBuffer();\n parsedPayload = ProtoClass.decode(new Uint8Array(lResponse));\n } catch {\n throw new Error('Protocol buffer parsing error');\n }\n } else {\n parsedPayload = await request.json();\n }\n def.schemas?.payload?.parse(parsedPayload);\n return parsedPayload;\n}\n\nexport const setOpenAPIMetadata = (_def: any) => {\n const openapiPathPrefix = `${_def.openapiPathPrefix ?? '/api'}`;\n const endpoint = `${_def.endpoint ?? _def.path ?? ''}`;\n const codec = _def.codec ?? jsonCodec;\n\n const apiConfig = {\n method: _def.method,\n path: `${openapiPathPrefix}${endpoint}`,\n summary: _def.openapi?.summary ?? '',\n request: {},\n responses: {},\n } as RouteConfig;\n\n const baseResponses =\n _def.schemas?.response\n ? {\n 200: {\n description: _def.openapi?.responses?.[200]?.description ?? '',\n content: {\n [codec.responseContentType]: {\n schema: _def.schemas?.response,\n },\n },\n },\n }\n : {};\n\n const customResponses = _def.openapi?.responses ?? {};\n apiConfig.responses = { ...baseResponses, ...customResponses };\n\n if (_def.schemas?.queryParams) {\n (apiConfig.request as any).query = (\n _def.schemas?.queryParams as any\n )?.openapi('Query Params');\n }\n if (_def.schemas?.payload) {\n (apiConfig.request as any).body = {\n description: 'Body',\n content: {\n [codec.requestContentType]: {\n schema: _def.schemas?.payload,\n },\n },\n required: true,\n };\n }\n if (_def.schemas?.urlArgs) {\n (apiConfig.request as any).params = (_def.schemas?.urlArgs as any)?.openapi(\n 'URL Params',\n );\n }\n\n (apiConfig as any).description = _def.openapi?.description;\n (apiConfig as any).tags = _def.openapi?.tags;\n (apiConfig as any).operationId = _def.openapi?.operationId;\n (apiConfig as any).deprecated = _def.openapi?.deprecated;\n (apiConfig as any).security = _def.openapi?.security;\n\n return apiConfig;\n};\n\nexport const jsonCodec: ApiCodec = {\n id: 'json',\n requestContentType: 'application/json',\n responseContentType: 'application/json',\n decodeRequest: async (request) => {\n const contentType = request.headers.get('content-type') || '';\n if (!contentType.includes('application/json')) {\n const text = await request.text();\n if (!text) return undefined;\n try {\n return JSON.parse(text);\n } catch {\n return text;\n }\n }\n const text = await request.text();\n if (!text) return undefined;\n return JSON.parse(text);\n },\n encodeResponse: async (value) => ({\n body: JSON.stringify(value),\n headers: { 'content-type': 'application/json' },\n }),\n};\n","import { ZodType } from 'zod';\nimport { jsonCodec, setOpenAPIMetadata } from './utils';\nimport { ServerFnDefinition } from './types';\n\nexport const createAPIDefinition = <\n URLParams extends ZodType,\n QueryParams extends ZodType,\n Body extends ZodType,\n ResponseSchema extends ZodType,\n>(\n def: ServerFnDefinition<URLParams, QueryParams, Body, ResponseSchema>,\n): ServerFnDefinition<URLParams, QueryParams, Body, ResponseSchema> => {\n const path = `${def.path ?? def.endpoint ?? ''}`;\n const normalizedPath = path.startsWith('/') ? path : `/${path}`;\n\n const _def = {\n method: 'get',\n ...def,\n ...{\n path: normalizedPath === '/' ? '' : normalizedPath,\n endpoint: normalizedPath === '/' ? '' : normalizedPath,\n },\n codec: def.codec ?? jsonCodec,\n };\n\n (_def as any).apiConfig = setOpenAPIMetadata(_def);\n\n return _def;\n};\n"],"names":["setOpenAPIMetadata","_def","_a","_b","_c","_d","_e","_f","_g","_h","_i","_j","_k","_l","_m","_n","_o","_p","_q","_r","_s","_t","openapiPathPrefix","endpoint","codec","jsonCodec","apiConfig","baseResponses","customResponses","request","text","value","createAPIDefinition","def","path","normalizedPath"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const l={id:"json",requestContentType:"application/json",responseContentType:"application/json",decodeRequest:async e=>{if(!(e.headers.get("content-type")||"").includes("application/json")){const r=await e.text();if(!r)return;try{return JSON.parse(r)}catch{return r}}const s=await e.text();if(s)return JSON.parse(s)},encodeResponse:async e=>({body:JSON.stringify(e),headers:{"content-type":"application/json"}})},u=(e,t)=>{let s=`${e}${t.query?`?${new URLSearchParams(t.query||{})}`:""}`;return t&&t.urlParams&&Object.keys(t.urlParams).forEach(r=>{s=s.replace(`{${r}}`,t==null?void 0:t.urlParams[r])}),s},p=e=>{const t=e.fetchFn??fetch,s=e.headersProvider;return{call:async(n,a)=>{const c=u((n==null?void 0:n.endpoint)||(n==null?void 0:n.path)||"/",a),h=await(s==null?void 0:s())??{},o=`${e.baseUrl}${c}`,d=await t(o,{method:n.method||"get",body:a.body===void 0?void 0:JSON.stringify(a.body),headers:{...h,...a.headers??{},...a.body===void 0?{}:{"content-type":l.requestContentType}},signal:a.signal});if(!d.ok){const b=await d.text();throw new Error(b||`Request failed with status ${d.status}`)}return await l.decodeRequest(d)}}},y=(e,t)=>async s=>{var c;const r=(c=globalThis==null?void 0:globalThis.process)==null?void 0:c.env,n=(r==null?void 0:r.NEXT_PUBLIC_BASE_API_URL)||"";return p({baseUrl:(t==null?void 0:t.baseUrl)??n,fetchFn:t==null?void 0:t.fetchFn,headersProvider:t==null?void 0:t.headersProvider}).call(e,s)};exports.apiConsumer=y;exports.createClient=p;
|
|
2
|
+
//# sourceMappingURL=client.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.cjs.js","sources":["../src/utils.ts","../src/client.ts"],"sourcesContent":["import { ZodType, ZodNumber, ZodBoolean } from 'zod';\nimport { ApiCodec, ServerFnDefinition } from './types';\nimport { RouteConfig } from '@asteasolutions/zod-to-openapi';\n\nexport function validateQueryParams<\n URLParams extends ZodType,\n QueryParams extends ZodType,\n Body extends ZodType,\n Response extends ZodType,\n>(\n request: Request,\n def: ServerFnDefinition<URLParams, QueryParams, Body, Response>,\n) {\n const queryParams = {};\n const params = new URL(request.url).searchParams.keys();\n for (const param of params) {\n if ((def.schemas?.queryParams as any).shape[param] instanceof ZodNumber) {\n (queryParams as unknown as any)[param as unknown as string] = Number(\n new URL(request.url).searchParams.get(param),\n );\n } else if (\n (def.schemas?.queryParams as any).shape[param] instanceof ZodBoolean\n ) {\n (queryParams as unknown as any)[param as unknown as string] = Boolean(\n new URL(request.url).searchParams.get(param),\n );\n } else {\n (queryParams as unknown as any)[param as unknown as string] =\n new URL(request.url).searchParams.get(param);\n }\n }\n def.schemas?.queryParams?.parse(queryParams);\n return queryParams;\n}\n\nexport async function validatePayload<\n URLParams extends ZodType,\n QueryParams extends ZodType,\n Body extends ZodType,\n Response extends ZodType,\n>(\n request: Request,\n def: ServerFnDefinition<URLParams, QueryParams, Body, Response>,\n ProtoClass: any,\n) {\n let parsedPayload: Body | undefined = undefined;\n\n if (ProtoClass) {\n try {\n const lResponse = await request.arrayBuffer();\n parsedPayload = ProtoClass.decode(new Uint8Array(lResponse));\n } catch {\n throw new Error('Protocol buffer parsing error');\n }\n } else {\n parsedPayload = await request.json();\n }\n def.schemas?.payload?.parse(parsedPayload);\n return parsedPayload;\n}\n\nexport const setOpenAPIMetadata = (_def: any) => {\n const openapiPathPrefix = `${_def.openapiPathPrefix ?? '/api'}`;\n const endpoint = `${_def.endpoint ?? _def.path ?? ''}`;\n const codec = _def.codec ?? jsonCodec;\n\n const apiConfig = {\n method: _def.method,\n path: `${openapiPathPrefix}${endpoint}`,\n summary: _def.openapi?.summary ?? '',\n request: {},\n responses: {},\n } as RouteConfig;\n\n const baseResponses =\n _def.schemas?.response\n ? {\n 200: {\n description: _def.openapi?.responses?.[200]?.description ?? '',\n content: {\n [codec.responseContentType]: {\n schema: _def.schemas?.response,\n },\n },\n },\n }\n : {};\n\n const customResponses = _def.openapi?.responses ?? {};\n apiConfig.responses = { ...baseResponses, ...customResponses };\n\n if (_def.schemas?.queryParams) {\n (apiConfig.request as any).query = (\n _def.schemas?.queryParams as any\n )?.openapi('Query Params');\n }\n if (_def.schemas?.payload) {\n (apiConfig.request as any).body = {\n description: 'Body',\n content: {\n [codec.requestContentType]: {\n schema: _def.schemas?.payload,\n },\n },\n required: true,\n };\n }\n if (_def.schemas?.urlArgs) {\n (apiConfig.request as any).params = (_def.schemas?.urlArgs as any)?.openapi(\n 'URL Params',\n );\n }\n\n (apiConfig as any).description = _def.openapi?.description;\n (apiConfig as any).tags = _def.openapi?.tags;\n (apiConfig as any).operationId = _def.openapi?.operationId;\n (apiConfig as any).deprecated = _def.openapi?.deprecated;\n (apiConfig as any).security = _def.openapi?.security;\n\n return apiConfig;\n};\n\nexport const jsonCodec: ApiCodec = {\n id: 'json',\n requestContentType: 'application/json',\n responseContentType: 'application/json',\n decodeRequest: async (request) => {\n const contentType = request.headers.get('content-type') || '';\n if (!contentType.includes('application/json')) {\n const text = await request.text();\n if (!text) return undefined;\n try {\n return JSON.parse(text);\n } catch {\n return text;\n }\n }\n const text = await request.text();\n if (!text) return undefined;\n return JSON.parse(text);\n },\n encodeResponse: async (value) => ({\n body: JSON.stringify(value),\n headers: { 'content-type': 'application/json' },\n }),\n};\n","import { ZodType } from 'zod';\nimport { APIConsumerPayload, ConsumerFn, DTO, ServerFnDefinition } from './types';\nimport { jsonCodec } from './utils';\n\nconst getEndpointWithArgsAndQuery = <\n URLParams extends ZodType,\n QueryParams extends ZodType,\n Body extends ZodType,\n _ResponseSchema extends ZodType,\n>(\n endpoint: string,\n consumerPayload: APIConsumerPayload<\n DTO<URLParams>,\n DTO<QueryParams>,\n DTO<Body>\n >,\n) => {\n let url = `${endpoint}${\n consumerPayload.query\n ? `?${new URLSearchParams(consumerPayload.query || {})}`\n : ''\n }`;\n\n if (consumerPayload && consumerPayload.urlParams) {\n Object.keys(consumerPayload.urlParams).forEach(\n (arg: keyof typeof consumerPayload.urlParams) => {\n url = url.replace(\n `{${arg as string}}`,\n consumerPayload?.urlParams![arg],\n );\n },\n );\n }\n\n return url;\n};\n\nexport type ApiClientConfig = {\n baseUrl: string;\n fetchFn?: typeof fetch;\n headersProvider?:\n | (() => Promise<Record<string, string>>)\n | (() => Record<string, string>);\n};\n\nexport const createClient = (config: ApiClientConfig) => {\n const fetchFn = config.fetchFn ?? fetch;\n const headersProvider = config.headersProvider;\n\n const call = async <\n URLParams extends ZodType,\n QueryParams extends ZodType,\n Body extends ZodType,\n ResponseSchema extends ZodType,\n >(\n apiDefinition: ServerFnDefinition<URLParams, QueryParams, Body, ResponseSchema>,\n consumerPayload: APIConsumerPayload<\n DTO<URLParams>,\n DTO<QueryParams>,\n DTO<Body>\n >,\n ): Promise<DTO<ResponseSchema>> => {\n const endpointKey = getEndpointWithArgsAndQuery(\n apiDefinition?.endpoint || apiDefinition?.path || '/',\n consumerPayload,\n );\n\n const resolvedHeaders =\n (await headersProvider?.()) ?? ({} as Record<string, string>);\n\n const url = `${config.baseUrl}${endpointKey}`;\n const response = await fetchFn(url, {\n method: apiDefinition.method || 'get',\n body:\n consumerPayload.body === undefined\n ? undefined\n : JSON.stringify(consumerPayload.body),\n headers: {\n ...resolvedHeaders,\n ...(consumerPayload.headers ?? {}),\n ...(consumerPayload.body === undefined\n ? {}\n : { 'content-type': jsonCodec.requestContentType }),\n },\n signal: consumerPayload.signal,\n });\n\n if (!response.ok) {\n const text = await response.text();\n throw new Error(text || `Request failed with status ${response.status}`);\n }\n\n const parsed = (await jsonCodec.decodeRequest(response)) as DTO<ResponseSchema>;\n return parsed;\n };\n\n return { call };\n};\n\nexport const apiConsumer =\n <\n URLParams extends ZodType,\n QueryParams extends ZodType,\n Body extends ZodType,\n ResponseSchema extends ZodType,\n >(\n apiDefinition: ServerFnDefinition<URLParams, QueryParams, Body, ResponseSchema>,\n config?: Partial<ApiClientConfig> & { baseUrl: string },\n ): ConsumerFn<URLParams, QueryParams, Body, ResponseSchema> =>\n async (\n consumerPayload: APIConsumerPayload<\n DTO<URLParams>,\n DTO<QueryParams>,\n DTO<Body>\n >,\n ) => {\n const runtimeEnv = (globalThis as any)?.process?.env;\n const fallbackBaseUrl =\n runtimeEnv?.NEXT_PUBLIC_BASE_API_URL || '';\n\n const client = createClient({\n baseUrl: config?.baseUrl ?? fallbackBaseUrl,\n fetchFn: config?.fetchFn,\n headersProvider: config?.headersProvider,\n });\n\n return client.call(apiDefinition, consumerPayload);\n };\n"],"names":["jsonCodec","request","text","value","getEndpointWithArgsAndQuery","endpoint","consumerPayload","url","arg","createClient","config","fetchFn","headersProvider","apiDefinition","endpointKey","resolvedHeaders","response","apiConsumer","runtimeEnv","_a","fallbackBaseUrl"],"mappings":"gFA0HO,MAAMA,EAAsB,CACjC,GAAI,OACJ,mBAAoB,mBACpB,oBAAqB,mBACrB,cAAe,MAAOC,GAAY,CAEhC,GAAI,EADgBA,EAAQ,QAAQ,IAAI,cAAc,GAAK,IAC1C,SAAS,kBAAkB,EAAG,CAC7C,MAAMC,EAAO,MAAMD,EAAQ,KAAA,EAC3B,GAAI,CAACC,EAAM,OACX,GAAI,CACF,OAAO,KAAK,MAAMA,CAAI,CACxB,MAAQ,CACN,OAAOA,CACT,CACF,CACA,MAAMA,EAAO,MAAMD,EAAQ,KAAA,EAC3B,GAAKC,EACL,OAAO,KAAK,MAAMA,CAAI,CACxB,EACA,eAAgB,MAAOC,IAAW,CAChC,KAAM,KAAK,UAAUA,CAAK,EAC1B,QAAS,CAAE,eAAgB,kBAAA,CAAmB,EAElD,EC7IMC,EAA8B,CAMlCC,EACAC,IAKG,CACH,IAAIC,EAAM,GAAGF,CAAQ,GACnBC,EAAgB,MACZ,IAAI,IAAI,gBAAgBA,EAAgB,OAAS,CAAA,CAAE,CAAC,GACpD,EACN,GAEA,OAAIA,GAAmBA,EAAgB,WACrC,OAAO,KAAKA,EAAgB,SAAS,EAAE,QACpCE,GAAgD,CAC/CD,EAAMA,EAAI,QACR,IAAIC,CAAa,IACjBF,GAAA,YAAAA,EAAiB,UAAWE,EAAG,CAEnC,CAAA,EAIGD,CACT,EAUaE,EAAgBC,GAA4B,CACvD,MAAMC,EAAUD,EAAO,SAAW,MAC5BE,EAAkBF,EAAO,gBAiD/B,MAAO,CAAE,KA/CI,MAMXG,EACAP,IAKiC,CACjC,MAAMQ,EAAcV,GAClBS,GAAA,YAAAA,EAAe,YAAYA,GAAA,YAAAA,EAAe,OAAQ,IAClDP,CAAA,EAGIS,EACH,MAAMH,GAAA,YAAAA,MAAyB,CAAA,EAE5BL,EAAM,GAAGG,EAAO,OAAO,GAAGI,CAAW,GACrCE,EAAW,MAAML,EAAQJ,EAAK,CAClC,OAAQM,EAAc,QAAU,MAChC,KACEP,EAAgB,OAAS,OACrB,OACA,KAAK,UAAUA,EAAgB,IAAI,EACzC,QAAS,CACP,GAAGS,EACH,GAAIT,EAAgB,SAAW,CAAA,EAC/B,GAAIA,EAAgB,OAAS,OACzB,CAAA,EACA,CAAE,eAAgBN,EAAU,kBAAA,CAAmB,EAErD,OAAQM,EAAgB,MAAA,CACzB,EAED,GAAI,CAACU,EAAS,GAAI,CAChB,MAAMd,EAAO,MAAMc,EAAS,KAAA,EAC5B,MAAM,IAAI,MAAMd,GAAQ,8BAA8Bc,EAAS,MAAM,EAAE,CACzE,CAGA,OADgB,MAAMhB,EAAU,cAAcgB,CAAQ,CAExD,CAES,CACX,EAEaC,EACX,CAMEJ,EACAH,IAEF,MACEJ,GAKG,OACH,MAAMY,GAAcC,EAAA,mCAAoB,UAApB,YAAAA,EAA6B,IAC3CC,GACJF,GAAA,YAAAA,EAAY,2BAA4B,GAQ1C,OANeT,EAAa,CAC1B,SAASC,GAAA,YAAAA,EAAQ,UAAWU,EAC5B,QAASV,GAAA,YAAAA,EAAQ,QACjB,gBAAiBA,GAAA,YAAAA,EAAQ,eAAA,CAC1B,EAEa,KAAKG,EAAeP,CAAe,CACnD"}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ZodType } from 'zod';
|
|
2
|
+
import { APIConsumerPayload, ConsumerFn, DTO, ServerFnDefinition } from './types';
|
|
3
|
+
export type ApiClientConfig = {
|
|
4
|
+
baseUrl: string;
|
|
5
|
+
fetchFn?: typeof fetch;
|
|
6
|
+
headersProvider?: (() => Promise<Record<string, string>>) | (() => Record<string, string>);
|
|
7
|
+
};
|
|
8
|
+
export declare const createClient: (config: ApiClientConfig) => {
|
|
9
|
+
call: <URLParams extends ZodType, QueryParams extends ZodType, Body extends ZodType, ResponseSchema extends ZodType>(apiDefinition: ServerFnDefinition<URLParams, QueryParams, Body, ResponseSchema>, consumerPayload: APIConsumerPayload<DTO<URLParams>, DTO<QueryParams>, DTO<Body>>) => Promise<DTO<ResponseSchema>>;
|
|
10
|
+
};
|
|
11
|
+
export declare const apiConsumer: <URLParams extends ZodType, QueryParams extends ZodType, Body extends ZodType, ResponseSchema extends ZodType>(apiDefinition: ServerFnDefinition<URLParams, QueryParams, Body, ResponseSchema>, config?: Partial<ApiClientConfig> & {
|
|
12
|
+
baseUrl: string;
|
|
13
|
+
}) => ConsumerFn<URLParams, QueryParams, Body, ResponseSchema>;
|