@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 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
+ [![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](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":"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"}
@@ -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"}
@@ -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>;