nuxt-api-contract 0.1.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.
Files changed (44) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +385 -0
  3. package/dist/cli.d.mts +1 -0
  4. package/dist/cli.d.ts +1 -0
  5. package/dist/cli.mjs +99 -0
  6. package/dist/client.d.mts +70 -0
  7. package/dist/client.d.ts +70 -0
  8. package/dist/client.mjs +3 -0
  9. package/dist/composables.d.mts +45 -0
  10. package/dist/composables.d.ts +45 -0
  11. package/dist/composables.mjs +97 -0
  12. package/dist/module.d.mts +35 -0
  13. package/dist/module.d.ts +35 -0
  14. package/dist/module.mjs +145 -0
  15. package/dist/openapi.d.mts +46 -0
  16. package/dist/openapi.d.ts +46 -0
  17. package/dist/openapi.mjs +312 -0
  18. package/dist/runtime/server/devtoolsRoute.mjs +6 -0
  19. package/dist/runtime/server/openapiRoute.mjs +6 -0
  20. package/dist/runtime/shared/contract.mjs +77 -0
  21. package/dist/runtime/shared/errors.mjs +71 -0
  22. package/dist/runtime/shared/format.mjs +32 -0
  23. package/dist/runtime/shared/serialization.mjs +36 -0
  24. package/dist/runtime/shared/types.mjs +1 -0
  25. package/dist/server.d.mts +41 -0
  26. package/dist/server.d.ts +41 -0
  27. package/dist/server.mjs +94 -0
  28. package/dist/shared/nuxt-api-contract.B9JBCRk8.d.mts +37 -0
  29. package/dist/shared/nuxt-api-contract.CPm9WbWA.d.mts +165 -0
  30. package/dist/shared/nuxt-api-contract.CPm9WbWA.d.ts +165 -0
  31. package/dist/shared/nuxt-api-contract.D31EDcwH.d.mts +109 -0
  32. package/dist/shared/nuxt-api-contract.D31EDcwH.d.ts +109 -0
  33. package/dist/shared/nuxt-api-contract.DCAU2j7t.mjs +34 -0
  34. package/dist/shared/nuxt-api-contract.DDpgZj2g.mjs +66 -0
  35. package/dist/shared/nuxt-api-contract.QDGSGaVY.mjs +117 -0
  36. package/dist/shared/nuxt-api-contract.S1zqCiJX.d.ts +37 -0
  37. package/dist/shared/nuxt-api-contract.obS6uV8A.mjs +73 -0
  38. package/dist/shared.d.mts +4 -0
  39. package/dist/shared.d.ts +4 -0
  40. package/dist/shared.mjs +3 -0
  41. package/dist/testing.d.mts +55 -0
  42. package/dist/testing.d.ts +55 -0
  43. package/dist/testing.mjs +49 -0
  44. package/package.json +104 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 nuxt-api-contract contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,385 @@
1
+ # nuxt-api-contract
2
+
3
+ Type-safe API contracts between Nitro server routes and the Nuxt client.
4
+
5
+ Define a contract **once** — get runtime validation, fully typed client calls,
6
+ a unified error format, OpenAPI generation, mocks, contract tests and a
7
+ DevTools panel from the same source of truth.
8
+
9
+ > Status: `0.x` (pre-1.0, SemVer). The public API is intentionally small.
10
+
11
+ ## Why
12
+
13
+ Without contracts, the request/response agreement between server and client
14
+ lives in three disconnected places: a Zod schema (or nothing), a TS interface
15
+ (or nothing) and documentation (or nothing). `nuxt-api-contract` collapses all
16
+ of them into one object:
17
+
18
+ ```text
19
+ ┌── runtime validation (server AND response)
20
+
21
+ Contract ─────────┼── TypeScript types (params / query / body / response)
22
+
23
+ ├── OpenAPI document
24
+
25
+ ├── DevTools panel
26
+
27
+ └── mocks + contract tests
28
+ ```
29
+
30
+ ## Installation
31
+
32
+ ```bash
33
+ npm install nuxt-api-contract zod
34
+ ```
35
+
36
+ Add the module:
37
+
38
+ ```ts
39
+ // nuxt.config.ts
40
+ export default defineNuxtConfig({
41
+ modules: ['nuxt-api-contract'],
42
+ apiContract: {
43
+ validateResponse: 'development',
44
+ openapi: { enabled: true, entry: 'contracts/index.ts' },
45
+ devtools: true,
46
+ mocks: false,
47
+ },
48
+ })
49
+ ```
50
+
51
+ Zod (`^3.23`) is a peer dependency.
52
+
53
+ ## Quick start
54
+
55
+ ```ts
56
+ // contracts/users.ts
57
+ import { z } from 'zod'
58
+ import { defineApiContract } from 'nuxt-api-contract/client'
59
+
60
+ export const GetUser = defineApiContract({
61
+ name: 'GetUser',
62
+ method: 'GET',
63
+ path: '/api/users/:id',
64
+ params: z.object({ id: z.string() }),
65
+ response: z.object({
66
+ id: z.string(),
67
+ name: z.string(),
68
+ email: z.string().email(),
69
+ }),
70
+ })
71
+ ```
72
+
73
+ ```ts
74
+ // server/api/users/[id].get.ts
75
+ import { GetUser } from '../../../contracts/users'
76
+ import { createApiError, defineContractHandler } from 'nuxt-api-contract/server'
77
+
78
+ export default defineContractHandler(GetUser, async ({ params }) => {
79
+ const user = await db.find(params.id) // params is typed & validated
80
+ if (!user) throw createApiError('USER_NOT_FOUND', 'User not found', 404)
81
+ return user
82
+ })
83
+ ```
84
+
85
+ ```vue
86
+ <!-- pages/users/[id].vue -->
87
+ <script setup lang="ts">
88
+ const route = useRoute()
89
+ const { data, error, pending } = await useApi(GetUser, {
90
+ params: { id: route.params.id as string },
91
+ })
92
+ // data.value?.name -> string | undefined (fully typed)
93
+ </script>
94
+ ```
95
+
96
+ Passing `params: { id: 123 }` is a **TypeScript error**; an invalid request is
97
+ rejected at runtime with a `VALIDATION_ERROR`.
98
+
99
+ ## Defining contracts
100
+
101
+ ```ts
102
+ defineApiContract({
103
+ name: 'CreateUser', // optional; registers in the registry
104
+ version: 1, // optional metadata
105
+ method: 'POST', // GET | POST | PUT | PATCH | DELETE | HEAD | OPTIONS
106
+ path: '/api/users', // `:param` segments become required params
107
+ params: z.object({}), // path params schema
108
+ query: z.object({}), // query schema (use z.coerce for numbers)
109
+ body: z.object({}), // JSON body schema
110
+ headers: z.object({}), // raw header schema
111
+ response: z.object({}), // success response schema
112
+ errors: { EMAIL_TAKEN: z.object({}) }, // known error payloads by code
113
+ summary / description / tags, // OpenAPI metadata
114
+ auth: true, // informational / extension point
115
+ metadata: {}, // free-form, consumed by tooling
116
+ })
117
+ ```
118
+
119
+ Types are always **inferred** — you never write generics by hand. Path
120
+ parameters are extracted from the path itself: a contract for
121
+ `/api/posts/:postId/comments/:commentId` requires
122
+ `params: { postId: string, commentId: string }` even without a params schema.
123
+
124
+ ### Query values
125
+
126
+ The browser sends query values as strings. Use `z.coerce.number()` (and
127
+ friends) for numeric query parameters; validation always happens on the server.
128
+
129
+ ## Server handlers
130
+
131
+ `defineContractHandler(contract, handler)` performs, in order:
132
+
133
+ 1. validation of `headers`, `params`, `query`, `body` against the schemas;
134
+ 2. invocation of your handler with **validated, typed** data + the raw `H3Event`;
135
+ 3. optional response validation (see configuration);
136
+ 4. conversion of thrown `ApiError`s into the unified error payload.
137
+
138
+ Handler context:
139
+
140
+ ```ts
141
+ interface ContractHandlerContext {
142
+ params // validated path params
143
+ query // validated query
144
+ body // validated body (undefined for GET/HEAD without body schema)
145
+ headers // validated headers (raw record when no schema)
146
+ event // H3Event
147
+ user? // reserved for auth integrations
148
+ }
149
+ ```
150
+
151
+ ## Client usage
152
+
153
+ ```ts
154
+ // Reactive (SSR-aware, no hydration mismatch):
155
+ const { data, error, pending, refresh } = await useApi(GetUser, { params: { id } })
156
+
157
+ // Imperative (actions, Pinia, event handlers):
158
+ const api = await useApiClient()
159
+ const user = await api.request(GetUser, { params: { id } }) // throws ApiError
160
+ const { data, error } = await api.tryRequest(GetUser, { params: { id } })
161
+ ```
162
+
163
+ Both work in the browser, during SSR and after hydration. During SSR the
164
+ request is executed **inside Nitro** (`event.$fetch`) — no HTTP round-trip to
165
+ itself; the payload is transferred to the client automatically.
166
+
167
+ ## Error handling
168
+
169
+ ```ts
170
+ throw createApiError('USER_NOT_FOUND', 'User not found', 404)
171
+ // or
172
+ throw createApiError({ code: 'VALIDATION_ERROR', statusCode: 400, details })
173
+ ```
174
+
175
+ Wire format:
176
+
177
+ ```json
178
+ { "error": { "code": "USER_NOT_FOUND", "message": "User not found" } }
179
+ ```
180
+
181
+ On the client, `error` (from `useApi`) or the caught value (from
182
+ `useApiClient().request`) is a reconstructed `ApiError` with `code`,
183
+ `statusCode`, `details` and `issues`. Check it with `isApiError(value)`.
184
+
185
+ Validation errors are readable:
186
+
187
+ ```text
188
+ [nuxt-api-contract]
189
+
190
+ Invalid query for GET /api/users
191
+
192
+ query.limit:
193
+ Expected number
194
+ Received string
195
+ ```
196
+
197
+ In production, `received` values are stripped (they may contain passwords or
198
+ tokens) and internal error messages are never leaked.
199
+
200
+ ## SSR
201
+
202
+ Handled automatically by the transport layer:
203
+
204
+ ```text
205
+ Browser → HTTP $fetch
206
+ SSR → internal Nitro call (event.$fetch)
207
+ After hydration → payload from useAsyncData, no refetch, no mismatch
208
+ ```
209
+
210
+ ## OpenAPI
211
+
212
+ Configure the module (build-time generation from a contracts entry file):
213
+
214
+ ```ts
215
+ apiContract: {
216
+ openapi: {
217
+ enabled: true,
218
+ entry: 'contracts/index.ts', // default array or named exports
219
+ path: '/_api-contracts/openapi.json',
220
+ title: 'My API',
221
+ },
222
+ }
223
+ ```
224
+
225
+ Or from the CLI (works without a Nuxt build):
226
+
227
+ ```bash
228
+ npx nuxt-api-contract openapi contracts/index.ts --output openapi.json
229
+ npx nuxt-api-contract openapi contracts/index.ts --output openapi.yaml
230
+ ```
231
+
232
+ Zod → OpenAPI conversion is a separate abstraction layer
233
+ (`nuxt-api-contract/openapi`). Unsupported Zod features (transform, refine,
234
+ preprocess) degrade to the closest representable schema with a warning —
235
+ generation never fails.
236
+
237
+ ## Mocking
238
+
239
+ ```ts
240
+ import { mockContract } from 'nuxt-api-contract/client'
241
+
242
+ mockContract(GetUser, { response: () => ({ id: '1', name: 'Mocked User' }) })
243
+ ```
244
+
245
+ Enable `apiContract: { mocks: true }` and contract handlers return the mock
246
+ response (still validated against the response schema).
247
+
248
+ ## Testing
249
+
250
+ Full pipeline without an HTTP server:
251
+
252
+ ```ts
253
+ import { callContract } from 'nuxt-api-contract/testing'
254
+
255
+ const { data, error } = await callContract(GetUser, handler, { params: { id: '1' } })
256
+ expect(error).toBeNull()
257
+ expect(data.id).toBe('1')
258
+ ```
259
+
260
+ Validation → handler → response-validation run exactly like in production.
261
+ See `test/integration/playground.test.ts` for full-stack tests with
262
+ `@nuxt/test-utils`.
263
+
264
+ ## DevTools
265
+
266
+ When `apiContract.devtools` is enabled in development, a panel lists all
267
+ contracts (method, path, params, tags, error codes) and includes a
268
+ "Try request" form. DevTools is optional — the module works normally without
269
+ it. `@nuxt/devtools-kit` is imported dynamically and guarded.
270
+
271
+ ## Configuration
272
+
273
+ ```ts
274
+ apiContract: {
275
+ validateResponse: 'development', // 'never' | 'development' | 'always'
276
+ mocks: false,
277
+ devtools: true,
278
+ contractsDirs: ['contracts', 'server/contracts'], // contract auto-import dirs
279
+ openapi: {
280
+ enabled: false,
281
+ path: '/_api-contracts/openapi.json',
282
+ entry: 'contracts/index.ts',
283
+ title: undefined, version: undefined, description: undefined,
284
+ },
285
+ }
286
+ ```
287
+
288
+ Runtime config (private, never in `public`):
289
+
290
+ ```json
291
+ { "apiContract": { "validateResponse": "development", "mocks": false } }
292
+ ```
293
+
294
+ ## Architecture
295
+
296
+ ```text
297
+ src/
298
+ ├── module.ts # Nuxt module (build-time)
299
+ ├── cli.ts # `nuxt-api-contract openapi` CLI
300
+ ├── openapi/ # Zod -> OpenAPI (isolated, build-time only)
301
+ ├── client.ts # client-safe barrel (contracts, errors, helpers)
302
+ ├── composables.ts # useApi / useApiClient (requires Nuxt context)
303
+ ├── server.ts # server barrel (handler, validation)
304
+ ├── testing.ts # callContract test helper
305
+ └── runtime/
306
+ ├── shared/ # contract, types, errors, format, serialization
307
+ ├── client/ # transport, useApi
308
+ └── server/ # defineContractHandler, validation, routes
309
+ ```
310
+
311
+ Server-only code never reaches the client bundle; the OpenAPI generator, CLI
312
+ and DevTools are not part of any runtime import chain (importing
313
+ `useApi` adds ~4 kB). See [docs/architecture.md](docs/architecture.md).
314
+
315
+ ## Package exports
316
+
317
+ | Export | Purpose |
318
+ | --- | --- |
319
+ | `nuxt-api-contract` | Module definition (for `modules: []`) |
320
+ | `nuxt-api-contract/client` | Client-safe: `defineApiContract`, `createApiError`, registry, mocks |
321
+ | `nuxt-api-contract/composables` | `useApi`, `useApiClient` (Nuxt context required) |
322
+ | `nuxt-api-contract/server` | `defineContractHandler`, validation helpers |
323
+ | `nuxt-api-contract/testing` | `callContract` |
324
+ | `nuxt-api-contract/openapi` | OpenAPI generator |
325
+ | `nuxt-api-contract/shared` | Shared primitives |
326
+
327
+ ## Limitations
328
+
329
+ - Zod 3.x only (`^3.23`); Zod 4 support is on the roadmap.
330
+ - Request bodies are JSON; `multipart/form-data` (file uploads) is planned —
331
+ the contract abstraction already does not assume JSON-only bodies.
332
+ - OpenAPI conversion is best-effort for `transform` / `refine` / `preprocess`.
333
+ - The standalone mock server (`nuxt-api-contract mock`) is not implemented yet.
334
+ - Auto-discovery is directory-based (`contracts/`, `server/contracts/`) rather
335
+ than a build-time scanner.
336
+
337
+ ## Roadmap
338
+
339
+ See [ROADMAP.md](ROADMAP.md). In short: **0.1.0 (released)** ships core
340
+ contracts, OpenAPI generation and the DevTools panel; next: 0.2.0 standalone
341
+ mock server, 0.3.0 extended contract testing, 0.4.0 OpenAPI client generation,
342
+ 0.5.0 external API contracts, 0.6.0 contract versioning, 1.0.0 stable API.
343
+
344
+ ## Development
345
+
346
+ ```bash
347
+ npm run build # build the package (unbuild)
348
+ npm run test # unit + integration tests
349
+ npm run test:type # type tests (vitest typecheck)
350
+ npm run typecheck # tsc --noEmit
351
+ npm run lint # eslint
352
+ ```
353
+
354
+ ## Publishing & Versioning
355
+
356
+ Versioning follows SemVer with the usual 0.x semantics:
357
+
358
+ - **0.x.y** (current): `y` (patch) — bug fixes; `x` (minor) — features **and**
359
+ documented breaking changes (pre-1.0 policy, always listed in CHANGELOG.md).
360
+ - **1.0.0+**: strict SemVer — breaking changes only in major releases.
361
+
362
+ Release flow:
363
+
364
+ ```bash
365
+ # 1. Bump the version (updates package.json, creates a git tag):
366
+ npm version patch # or: minor | major | prerelease --preid=rc
367
+
368
+ # 2. Push with tags:
369
+ git push --follow-tags
370
+
371
+ # 3. Publish (prepublishOnly runs lint + typecheck + unit/type tests + build):
372
+ npm publish
373
+
374
+ # Pre-release dist-tag (e.g. 0.2.0-rc.1):
375
+ npm publish --tag next
376
+ ```
377
+
378
+ The package name `nuxt-api-contract` is published unscoped with public access.
379
+ `npm pack --dry-run` shows exactly what ships: `dist/**` (bundled entries +
380
+ `dist/runtime` for Nitro routes) plus README/LICENSE/CHANGELOG — no sources,
381
+ tests or playground.
382
+
383
+ ## License
384
+
385
+ [MIT](./LICENSE) © modeusweb
package/dist/cli.d.mts ADDED
@@ -0,0 +1 @@
1
+
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+
package/dist/cli.mjs ADDED
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env node
2
+ import { createJiti } from 'jiti';
3
+ import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
4
+ import { resolve, dirname } from 'node:path';
5
+ import { generateOpenApiDocument, pickContracts } from './openapi.mjs';
6
+
7
+ function parseArgs(argv) {
8
+ const positional = [];
9
+ const flags = {};
10
+ let command;
11
+ for (let i = 0; i < argv.length; i++) {
12
+ const arg = argv[i];
13
+ if (arg.startsWith("--")) {
14
+ const key = arg.slice(2);
15
+ const next = argv[i + 1];
16
+ if (next && !next.startsWith("--")) {
17
+ flags[key] = next;
18
+ i++;
19
+ } else {
20
+ flags[key] = true;
21
+ }
22
+ } else if (!command) {
23
+ command = arg;
24
+ } else {
25
+ positional.push(arg);
26
+ }
27
+ }
28
+ return { command, positional, flags };
29
+ }
30
+ async function loadContractsFromEntry(entry) {
31
+ const jiti = createJiti(import.meta.url, { interopDefault: true });
32
+ const loaded = await jiti.import(entry);
33
+ const values = [];
34
+ if (Array.isArray(loaded)) {
35
+ values.push(...loaded);
36
+ } else if (loaded !== null && typeof loaded === "object") {
37
+ values.push(...Object.values(loaded));
38
+ } else {
39
+ throw new Error(`[nuxt-api-contract] Entry "${entry}" must export an array of contracts or named contract exports.`);
40
+ }
41
+ const contracts = pickContracts(values);
42
+ if (contracts.length === 0) {
43
+ throw new Error(`[nuxt-api-contract] No contracts (kind === 'api-contract') found in "${entry}".`);
44
+ }
45
+ return contracts;
46
+ }
47
+ async function main() {
48
+ const { command, positional, flags } = parseArgs(process.argv.slice(2));
49
+ if (command === "openapi") {
50
+ const entry = positional[0];
51
+ if (!entry || !existsSync(entry)) {
52
+ console.error("[nuxt-api-contract] Usage: nuxt-api-contract openapi <entry> [--output openapi.json]");
53
+ process.exitCode = 1;
54
+ return;
55
+ }
56
+ const contracts = await loadContractsFromEntry(resolve(entry));
57
+ const { document, warnings } = generateOpenApiDocument(contracts, {
58
+ title: typeof flags.title === "string" ? flags.title : void 0,
59
+ version: typeof flags.version === "string" ? flags.version : void 0
60
+ });
61
+ for (const warning of warnings) {
62
+ console.warn(`[nuxt-api-contract] OpenAPI warning (${warning.contract}): ${warning.message}`);
63
+ }
64
+ const output = typeof flags.output === "string" ? flags.output : flags.yaml === true ? "openapi.yaml" : "openapi.json";
65
+ const content = flags.yaml === true ? toMinimalYaml(document) : `${JSON.stringify(document, null, 2)}
66
+ `;
67
+ mkdirSync(dirname(resolve(output)), { recursive: true });
68
+ writeFileSync(resolve(output), content, "utf8");
69
+ console.log(`[nuxt-api-contract] OpenAPI document with ${contracts.length} contract(s) written to ${output}`);
70
+ return;
71
+ }
72
+ console.error(`[nuxt-api-contract] Unknown command "${command ?? ""}". Available commands: openapi`);
73
+ process.exitCode = 1;
74
+ }
75
+ function toMinimalYaml(value, indent = 0) {
76
+ const pad = " ".repeat(indent);
77
+ if (Array.isArray(value)) {
78
+ if (value.length === 0) return "[]";
79
+ return value.map((item) => `${pad}- ${toMinimalYaml(item, indent + 2).trimStart()}`).join("\n");
80
+ }
81
+ if (value !== null && typeof value === "object") {
82
+ const entries = Object.entries(value);
83
+ if (entries.length === 0) return "{}";
84
+ return entries.map(([key, item]) => {
85
+ const rendered = toMinimalYaml(item, indent + 2);
86
+ if (item !== null && typeof item === "object" && (Array.isArray(item) && item.length > 0 || Object.keys(item).length > 0)) {
87
+ return `${pad}${key}:
88
+ ${rendered}`;
89
+ }
90
+ return `${pad}${key}: ${rendered.trimStart()}`;
91
+ }).join("\n");
92
+ }
93
+ if (typeof value === "string") return JSON.stringify(value);
94
+ return String(value);
95
+ }
96
+ main().catch((error) => {
97
+ console.error(error instanceof Error ? error.message : error);
98
+ process.exitCode = 1;
99
+ });
@@ -0,0 +1,70 @@
1
+ export { A as ApiError, a as ApiErrorPayload, B as BUILT_IN_ERROR_CODES, C as CreateApiErrorInput, V as ValidationIssue, c as createApiError, f as formatValidationMessage, i as isApiError, p as parseApiErrorPayload, s as sanitizeIssues, b as serializeApiError, t as toApiError, d as toValidationIssues } from './shared/nuxt-api-contract.D31EDcwH.mjs';
2
+ import { A as AnyApiContract, M as MaybePromise, i as ContractHandlerResponse, c as ApiContractDefinition, h as ContractFromDefinition } from './shared/nuxt-api-contract.CPm9WbWA.mjs';
3
+ export { a as API_CONTRACT_KIND, b as ApiContract, d as ApiRequestOptions, e as AuthConfig, C as ContractBodyInput, f as ContractClientResponse, g as ContractErrorCode, j as ContractHeadersInput, k as ContractParamsInput, l as ContractPathParams, m as ContractQueryInput, E as EmptyObject, n as ExtractSchemaInput, o as ExtractSchemaOutput, H as HttpMethod, I as IsEmptyObject, P as PathParams, R as ResolvedApiRequestOptions, S as SplitPath } from './shared/nuxt-api-contract.CPm9WbWA.mjs';
4
+ import * as zod from 'zod';
5
+
6
+ /**
7
+ * Registers a named contract. Called automatically by `defineApiContract`
8
+ * when a `name` is provided. Duplicate names are overwritten with a warning.
9
+ */
10
+ declare function registerContract(contract: AnyApiContract): void;
11
+ declare function getContractByName(name: string): AnyApiContract | undefined;
12
+ declare function listRegisteredContracts(): AnyApiContract[];
13
+ /** Test helper: clears the registry. */
14
+ declare function clearContractRegistry(): void;
15
+ interface ContractMock<C extends AnyApiContract> {
16
+ /** Factory producing a (validated) response for the contract. */
17
+ response?: () => MaybePromise<MockResponseInput<C>>;
18
+ /** Simulated latency in ms. */
19
+ delay?: number;
20
+ }
21
+ type MockResponseInput<C extends AnyApiContract> = C['response'] extends zod.ZodType ? ContractHandlerResponse<C> : unknown;
22
+ /**
23
+ * Registers a mock implementation for a contract. When the module option
24
+ * `apiContract.mocks` is enabled, contract handlers return the mock response
25
+ * (still validated against the response schema).
26
+ */
27
+ declare function mockContract<C extends AnyApiContract>(contract: C, mock: ContractMock<C>): void;
28
+ declare function getContractMock<C extends AnyApiContract>(contract: C): ContractMock<C> | undefined;
29
+ /**
30
+ * Defines a type-safe API contract.
31
+ *
32
+ * The contract is the single source of truth for runtime validation,
33
+ * TypeScript types, OpenAPI generation, DevTools and mocks.
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * export const GetUser = defineApiContract({
38
+ * method: 'GET',
39
+ * path: '/api/users/:id',
40
+ * params: z.object({ id: z.string().uuid() }),
41
+ * response: z.object({ id: z.string(), name: z.string() }),
42
+ * })
43
+ * ```
44
+ */
45
+ declare function defineApiContract<const TDef extends ApiContractDefinition>(definition: TDef): ContractFromDefinition<TDef>;
46
+ /** Type guard for contract objects. */
47
+ declare function isApiContract(value: unknown): value is AnyApiContract;
48
+ /**
49
+ * Builds the request URL from a contract path and concrete params.
50
+ * Remaining params that are not part of the path are ignored.
51
+ */
52
+ declare function buildRequestPath(path: string, params: Record<string, unknown> | undefined): string;
53
+
54
+ /**
55
+ * Serialization helpers shared by the client transport and cache keys.
56
+ *
57
+ * The protocol intentionally stays minimal: standard JSON plus two common
58
+ * edge cases (Date and bigint). Response serialization relies on Nitro's
59
+ * built-in devalue support, which already handles Date, RegExp, etc.
60
+ */
61
+ /** Converts query values into URL-safe primitives (Date -> ISO, bigint -> string). */
62
+ type SerializedQueryValue = string | number | boolean | Array<string | number | boolean>;
63
+ declare function serializeQueryValue(value: unknown): SerializedQueryValue;
64
+ /** Prepares a query object for `$fetch` / URL building; drops `undefined` entries. */
65
+ declare function serializeQuery(query: Record<string, unknown> | undefined): Record<string, SerializedQueryValue> | undefined;
66
+ /** Deterministic JSON stringify (sorted keys) for stable SSR cache keys. */
67
+ declare function stableStringify(value: unknown): string;
68
+
69
+ export { AnyApiContract, ApiContractDefinition, ContractFromDefinition, ContractHandlerResponse, MaybePromise, buildRequestPath, clearContractRegistry, defineApiContract, getContractByName, getContractMock, isApiContract, listRegisteredContracts, mockContract, registerContract, serializeQuery, serializeQueryValue, stableStringify };
70
+ export type { ContractMock, MockResponseInput };
@@ -0,0 +1,70 @@
1
+ export { A as ApiError, a as ApiErrorPayload, B as BUILT_IN_ERROR_CODES, C as CreateApiErrorInput, V as ValidationIssue, c as createApiError, f as formatValidationMessage, i as isApiError, p as parseApiErrorPayload, s as sanitizeIssues, b as serializeApiError, t as toApiError, d as toValidationIssues } from './shared/nuxt-api-contract.D31EDcwH.js';
2
+ import { A as AnyApiContract, M as MaybePromise, i as ContractHandlerResponse, c as ApiContractDefinition, h as ContractFromDefinition } from './shared/nuxt-api-contract.CPm9WbWA.js';
3
+ export { a as API_CONTRACT_KIND, b as ApiContract, d as ApiRequestOptions, e as AuthConfig, C as ContractBodyInput, f as ContractClientResponse, g as ContractErrorCode, j as ContractHeadersInput, k as ContractParamsInput, l as ContractPathParams, m as ContractQueryInput, E as EmptyObject, n as ExtractSchemaInput, o as ExtractSchemaOutput, H as HttpMethod, I as IsEmptyObject, P as PathParams, R as ResolvedApiRequestOptions, S as SplitPath } from './shared/nuxt-api-contract.CPm9WbWA.js';
4
+ import * as zod from 'zod';
5
+
6
+ /**
7
+ * Registers a named contract. Called automatically by `defineApiContract`
8
+ * when a `name` is provided. Duplicate names are overwritten with a warning.
9
+ */
10
+ declare function registerContract(contract: AnyApiContract): void;
11
+ declare function getContractByName(name: string): AnyApiContract | undefined;
12
+ declare function listRegisteredContracts(): AnyApiContract[];
13
+ /** Test helper: clears the registry. */
14
+ declare function clearContractRegistry(): void;
15
+ interface ContractMock<C extends AnyApiContract> {
16
+ /** Factory producing a (validated) response for the contract. */
17
+ response?: () => MaybePromise<MockResponseInput<C>>;
18
+ /** Simulated latency in ms. */
19
+ delay?: number;
20
+ }
21
+ type MockResponseInput<C extends AnyApiContract> = C['response'] extends zod.ZodType ? ContractHandlerResponse<C> : unknown;
22
+ /**
23
+ * Registers a mock implementation for a contract. When the module option
24
+ * `apiContract.mocks` is enabled, contract handlers return the mock response
25
+ * (still validated against the response schema).
26
+ */
27
+ declare function mockContract<C extends AnyApiContract>(contract: C, mock: ContractMock<C>): void;
28
+ declare function getContractMock<C extends AnyApiContract>(contract: C): ContractMock<C> | undefined;
29
+ /**
30
+ * Defines a type-safe API contract.
31
+ *
32
+ * The contract is the single source of truth for runtime validation,
33
+ * TypeScript types, OpenAPI generation, DevTools and mocks.
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * export const GetUser = defineApiContract({
38
+ * method: 'GET',
39
+ * path: '/api/users/:id',
40
+ * params: z.object({ id: z.string().uuid() }),
41
+ * response: z.object({ id: z.string(), name: z.string() }),
42
+ * })
43
+ * ```
44
+ */
45
+ declare function defineApiContract<const TDef extends ApiContractDefinition>(definition: TDef): ContractFromDefinition<TDef>;
46
+ /** Type guard for contract objects. */
47
+ declare function isApiContract(value: unknown): value is AnyApiContract;
48
+ /**
49
+ * Builds the request URL from a contract path and concrete params.
50
+ * Remaining params that are not part of the path are ignored.
51
+ */
52
+ declare function buildRequestPath(path: string, params: Record<string, unknown> | undefined): string;
53
+
54
+ /**
55
+ * Serialization helpers shared by the client transport and cache keys.
56
+ *
57
+ * The protocol intentionally stays minimal: standard JSON plus two common
58
+ * edge cases (Date and bigint). Response serialization relies on Nitro's
59
+ * built-in devalue support, which already handles Date, RegExp, etc.
60
+ */
61
+ /** Converts query values into URL-safe primitives (Date -> ISO, bigint -> string). */
62
+ type SerializedQueryValue = string | number | boolean | Array<string | number | boolean>;
63
+ declare function serializeQueryValue(value: unknown): SerializedQueryValue;
64
+ /** Prepares a query object for `$fetch` / URL building; drops `undefined` entries. */
65
+ declare function serializeQuery(query: Record<string, unknown> | undefined): Record<string, SerializedQueryValue> | undefined;
66
+ /** Deterministic JSON stringify (sorted keys) for stable SSR cache keys. */
67
+ declare function stableStringify(value: unknown): string;
68
+
69
+ export { AnyApiContract, ApiContractDefinition, ContractFromDefinition, ContractHandlerResponse, MaybePromise, buildRequestPath, clearContractRegistry, defineApiContract, getContractByName, getContractMock, isApiContract, listRegisteredContracts, mockContract, registerContract, serializeQuery, serializeQueryValue, stableStringify };
70
+ export type { ContractMock, MockResponseInput };
@@ -0,0 +1,3 @@
1
+ export { b as buildRequestPath, c as clearContractRegistry, d as defineApiContract, g as getContractByName, a as getContractMock, i as isApiContract, l as listRegisteredContracts, m as mockContract, r as registerContract, s as serializeQuery, e as serializeQueryValue, f as stableStringify } from './shared/nuxt-api-contract.QDGSGaVY.mjs';
2
+ export { A as ApiError, B as BUILT_IN_ERROR_CODES, c as createApiError, i as isApiError, p as parseApiErrorPayload, s as serializeApiError, t as toApiError } from './shared/nuxt-api-contract.obS6uV8A.mjs';
3
+ export { f as formatValidationMessage, s as sanitizeIssues, t as toValidationIssues } from './shared/nuxt-api-contract.DCAU2j7t.mjs';