nuxt-api-contract 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +32 -7
  2. package/dist/chunks/server.mjs +162 -0
  3. package/dist/cli.mjs +33 -1
  4. package/dist/client.d.mts +5 -69
  5. package/dist/client.d.ts +5 -69
  6. package/dist/client.mjs +3 -1
  7. package/dist/composables.mjs +2 -1
  8. package/dist/mock.d.mts +54 -0
  9. package/dist/mock.d.ts +54 -0
  10. package/dist/mock.mjs +8 -0
  11. package/dist/module.d.mts +8 -2
  12. package/dist/module.d.ts +8 -2
  13. package/dist/module.mjs +4 -2
  14. package/dist/runtime/shared/mock.mjs +188 -0
  15. package/dist/server.d.mts +7 -4
  16. package/dist/server.d.ts +7 -4
  17. package/dist/server.mjs +6 -3
  18. package/dist/shared/nuxt-api-contract.BOxyDvRy.d.mts +40 -0
  19. package/dist/shared/{nuxt-api-contract.S1zqCiJX.d.ts → nuxt-api-contract.B_UvrNC8.d.ts} +1 -1
  20. package/dist/shared/nuxt-api-contract.Bd2Y7Lx0.mjs +191 -0
  21. package/dist/shared/nuxt-api-contract.BgPd-YXc.d.mts +53 -0
  22. package/dist/shared/nuxt-api-contract.BuWjAHJe.d.ts +40 -0
  23. package/dist/shared/nuxt-api-contract.C7KxMQHa.d.ts +53 -0
  24. package/dist/shared/{nuxt-api-contract.CPm9WbWA.d.mts → nuxt-api-contract.CFG8gzJH.d.mts} +2 -2
  25. package/dist/shared/{nuxt-api-contract.CPm9WbWA.d.ts → nuxt-api-contract.CFG8gzJH.d.ts} +2 -2
  26. package/dist/shared/nuxt-api-contract.DZfOzVaB.d.mts +16 -0
  27. package/dist/shared/nuxt-api-contract.DZfOzVaB.d.ts +16 -0
  28. package/dist/shared/nuxt-api-contract.Dr4tPqGB.mjs +38 -0
  29. package/dist/shared/{nuxt-api-contract.QDGSGaVY.mjs → nuxt-api-contract.V15soI_f.mjs} +1 -38
  30. package/dist/shared/{nuxt-api-contract.B9JBCRk8.d.mts → nuxt-api-contract.VCevNWQV.d.mts} +1 -1
  31. package/dist/shared.d.mts +4 -2
  32. package/dist/shared.d.ts +4 -2
  33. package/dist/shared.mjs +3 -1
  34. package/package.json +5 -1
package/README.md CHANGED
@@ -237,13 +237,38 @@ generation never fails.
237
237
  ## Mocking
238
238
 
239
239
  ```ts
240
- import { mockContract } from 'nuxt-api-contract/client'
240
+ import { mockContract, autoMockContract } from 'nuxt-api-contract/client'
241
241
 
242
242
  mockContract(GetUser, { response: () => ({ id: '1', name: 'Mocked User' }) })
243
+ autoMockContract(GetUser, { seed: 42 }) // generated from the response schema
243
244
  ```
244
245
 
245
- Enable `apiContract: { mocks: true }` and contract handlers return the mock
246
- response (still validated against the response schema).
246
+ Enable `apiContract: { mocks: true | 'auto' }` and contract handlers return
247
+ mock responses (still validated against the response schema). With `'auto'`,
248
+ contracts without an explicit `mockContract()` fall back to generated data.
249
+
250
+ ### Standalone mock server
251
+
252
+ Serve contract endpoints without a Nuxt build (frontend development against a
253
+ not-yet-implemented backend):
254
+
255
+ ```bash
256
+ npx nuxt-api-contract mock contracts/index.ts --port 4000 --seed 42
257
+ # add --lenient to skip request validation, --delay 300 for artificial latency
258
+ ```
259
+
260
+ Or programmatically:
261
+
262
+ ```ts
263
+ import { startMockServer } from 'nuxt-api-contract/mock'
264
+
265
+ const mock = await startMockServer({ contracts: [GetUser, ListUsers], port: 4000 })
266
+ // GET /__mock/contracts lists available endpoints; CORS is enabled.
267
+ await mock.close()
268
+ ```
269
+
270
+ Responses are deterministic for a given `--seed`; password/token fields are
271
+ always masked.
247
272
 
248
273
  ## Testing
249
274
 
@@ -322,6 +347,7 @@ and DevTools are not part of any runtime import chain (importing
322
347
  | `nuxt-api-contract/server` | `defineContractHandler`, validation helpers |
323
348
  | `nuxt-api-contract/testing` | `callContract` |
324
349
  | `nuxt-api-contract/openapi` | OpenAPI generator |
350
+ | `nuxt-api-contract/mock` | Standalone mock server + mock generators |
325
351
  | `nuxt-api-contract/shared` | Shared primitives |
326
352
 
327
353
  ## Limitations
@@ -330,15 +356,14 @@ and DevTools are not part of any runtime import chain (importing
330
356
  - Request bodies are JSON; `multipart/form-data` (file uploads) is planned —
331
357
  the contract abstraction already does not assume JSON-only bodies.
332
358
  - OpenAPI conversion is best-effort for `transform` / `refine` / `preprocess`.
333
- - The standalone mock server (`nuxt-api-contract mock`) is not implemented yet.
334
359
  - Auto-discovery is directory-based (`contracts/`, `server/contracts/`) rather
335
360
  than a build-time scanner.
336
361
 
337
362
  ## Roadmap
338
363
 
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,
364
+ See [ROADMAP.md](ROADMAP.md). In short: **0.1.0** core contracts + OpenAPI +
365
+ DevTools and **0.2.0** standalone mock server / generated mocks are released;
366
+ next: 0.3.0 extended contract testing, 0.4.0 OpenAPI client generation,
342
367
  0.5.0 external API contracts, 0.6.0 contract versioning, 1.0.0 stable API.
343
368
 
344
369
  ## Development
@@ -0,0 +1,162 @@
1
+ import { createServer } from 'node:http';
2
+ import { B as BUILT_IN_ERROR_CODES, A as ApiError, s as serializeApiError } from '../shared/nuxt-api-contract.obS6uV8A.mjs';
3
+ import { v as validateContractInput, a as validateContractResponse } from '../shared/nuxt-api-contract.DDpgZj2g.mjs';
4
+ import { g as generateMockResponse } from '../shared/nuxt-api-contract.Bd2Y7Lx0.mjs';
5
+ import 'zod';
6
+ import '../shared/nuxt-api-contract.DCAU2j7t.mjs';
7
+ import '../shared/nuxt-api-contract.V15soI_f.mjs';
8
+
9
+ function pathToRegex(path) {
10
+ const names = [];
11
+ const source = path.split("/").map((segment) => {
12
+ const match = /^:([A-Za-z_][A-Za-z0-9_]*)$/.exec(segment);
13
+ if (!match) return segment.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
14
+ names.push(match[1]);
15
+ return `(?<${match[1]}>[^/]+)`;
16
+ }).join("/");
17
+ return { regex: new RegExp(`^${source}/?$`), names };
18
+ }
19
+ function corsHeaders() {
20
+ return {
21
+ "access-control-allow-origin": "*",
22
+ "access-control-allow-methods": "GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS",
23
+ "access-control-allow-headers": "content-type, authorization, x-requested-with"
24
+ };
25
+ }
26
+ function send(res, status, payload, extra = {}) {
27
+ const body = JSON.stringify(payload);
28
+ res.writeHead(status, {
29
+ "content-type": "application/json; charset=utf-8",
30
+ "x-mock-server": "nuxt-api-contract",
31
+ ...corsHeaders(),
32
+ ...extra
33
+ });
34
+ res.end(body);
35
+ }
36
+ function sleep(ms) {
37
+ return new Promise((resolve) => setTimeout(resolve, ms));
38
+ }
39
+ function buildMockMatchers(contracts) {
40
+ return contracts.map((contract) => {
41
+ const { regex, names } = pathToRegex(contract.path);
42
+ return { contract, method: contract.method, regex, names };
43
+ });
44
+ }
45
+ async function readJsonBody(req) {
46
+ const chunks = [];
47
+ for await (const chunk of req) chunks.push(chunk);
48
+ if (chunks.length === 0) return { ok: true, value: void 0 };
49
+ try {
50
+ return { ok: true, value: JSON.parse(Buffer.concat(chunks).toString("utf8")) };
51
+ } catch {
52
+ return { ok: false, value: void 0 };
53
+ }
54
+ }
55
+ function errorPayload(error) {
56
+ return serializeApiError(error);
57
+ }
58
+ function createMockServer(options) {
59
+ const matchers = buildMockMatchers(options.contracts);
60
+ return createServer(async (req, res) => {
61
+ const started = Date.now();
62
+ try {
63
+ if (req.method === "OPTIONS") {
64
+ res.writeHead(204, corsHeaders());
65
+ res.end();
66
+ return;
67
+ }
68
+ const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
69
+ const method = (req.method ?? "GET").toUpperCase();
70
+ const pathMatch = matchers.find((matcher) => matcher.method === method && matcher.regex.test(url.pathname)) ?? matchers.find((matcher) => matcher.regex.test(url.pathname));
71
+ if (url.pathname === "/__mock/contracts" || url.pathname === "/__mock/contracts/") {
72
+ send(res, 200, {
73
+ contracts: options.contracts.map((contract2) => ({
74
+ method: contract2.method,
75
+ path: contract2.path,
76
+ name: contract2.name ?? null,
77
+ tags: contract2.tags ?? []
78
+ }))
79
+ });
80
+ return;
81
+ }
82
+ if (!pathMatch) {
83
+ send(res, 404, { error: { code: BUILT_IN_ERROR_CODES.notFound, message: `No mock for ${method} ${url.pathname}` } });
84
+ return;
85
+ }
86
+ if (pathMatch.method !== method) {
87
+ send(res, 405, { error: { code: BUILT_IN_ERROR_CODES.methodNotAllowed, message: `${pathMatch.method} ${pathMatch.contract.path} does not accept ${method}` } });
88
+ return;
89
+ }
90
+ const match = pathMatch;
91
+ const contract = match.contract;
92
+ const params = {};
93
+ const exec = match.regex.exec(url.pathname);
94
+ for (const name of match.names) {
95
+ params[name] = decodeURIComponent(exec?.groups?.[name] ?? "");
96
+ }
97
+ const query = {};
98
+ for (const [key, value] of url.searchParams.entries()) {
99
+ query[key] = value;
100
+ }
101
+ let body;
102
+ let bodyError;
103
+ if (method !== "GET" && method !== "HEAD") {
104
+ const parsed = await readJsonBody(req);
105
+ if (!parsed.ok) {
106
+ bodyError = new ApiError({ code: BUILT_IN_ERROR_CODES.validation, message: "Request body must be valid JSON", statusCode: 400 });
107
+ }
108
+ body = parsed.value;
109
+ }
110
+ if (options.delay) await sleep(options.delay);
111
+ if (!options.lenient) {
112
+ try {
113
+ if (bodyError) throw bodyError;
114
+ if (contract.params) validateContractInput(contract, "params", contract.params, params);
115
+ if (contract.query) validateContractInput(contract, "query", contract.query, query);
116
+ if (method !== "GET" && method !== "HEAD" && contract.body) {
117
+ validateContractInput(contract, "body", contract.body, body);
118
+ }
119
+ } catch (error) {
120
+ const apiError = error instanceof ApiError ? error : new ApiError({ code: BUILT_IN_ERROR_CODES.internal, message: String(error) });
121
+ send(res, apiError.statusCode, errorPayload(apiError));
122
+ return;
123
+ }
124
+ }
125
+ let response;
126
+ try {
127
+ response = generateMockResponse(contract, { seed: options.seed });
128
+ if (contract.response) {
129
+ validateContractResponse(contract, contract.response, response);
130
+ }
131
+ } catch (error) {
132
+ send(res, 500, { error: { code: BUILT_IN_ERROR_CODES.internal, message: error instanceof Error ? error.message : String(error) } });
133
+ return;
134
+ }
135
+ send(res, 200, response, { "x-mock-time": String(Date.now() - started) });
136
+ } catch (error) {
137
+ send(res, 500, { error: { code: BUILT_IN_ERROR_CODES.internal, message: error instanceof Error ? error.message : String(error) } });
138
+ }
139
+ });
140
+ }
141
+ function startMockServer(options) {
142
+ const server = createMockServer(options);
143
+ return new Promise((resolve, reject) => {
144
+ server.once("error", reject);
145
+ server.listen(options.port ?? 4e3, options.host ?? "127.0.0.1", () => {
146
+ const address = server.address();
147
+ const port = typeof address === "object" && address !== null ? address.port : options.port ?? 4e3;
148
+ const host = options.host ?? "127.0.0.1";
149
+ resolve({
150
+ server,
151
+ port,
152
+ host,
153
+ url: `http://${host === "0.0.0.0" ? "127.0.0.1" : host}:${port}`,
154
+ close: () => new Promise((resolveClose, rejectClose) => {
155
+ server.close((error) => error ? rejectClose(error) : resolveClose());
156
+ })
157
+ });
158
+ });
159
+ });
160
+ }
161
+
162
+ export { buildMockMatchers, createMockServer, startMockServer };
package/dist/cli.mjs CHANGED
@@ -69,7 +69,39 @@ async function main() {
69
69
  console.log(`[nuxt-api-contract] OpenAPI document with ${contracts.length} contract(s) written to ${output}`);
70
70
  return;
71
71
  }
72
- console.error(`[nuxt-api-contract] Unknown command "${command ?? ""}". Available commands: openapi`);
72
+ if (command === "mock") {
73
+ const entry = positional[0];
74
+ if (!entry || !existsSync(entry)) {
75
+ console.error("[nuxt-api-contract] Usage: nuxt-api-contract mock <entry> [--port 4000] [--seed 42] [--lenient]");
76
+ process.exitCode = 1;
77
+ return;
78
+ }
79
+ const contracts = await loadContractsFromEntry(resolve(entry));
80
+ const { startMockServer } = await import('./chunks/server.mjs');
81
+ const seed = flags.seed !== void 0 && flags.seed !== true ? Number(flags.seed) : void 0;
82
+ const handle = await startMockServer({
83
+ contracts,
84
+ port: flags.port !== void 0 && flags.port !== true ? Number(flags.port) : 4e3,
85
+ host: typeof flags.host === "string" ? flags.host : "127.0.0.1",
86
+ seed,
87
+ delay: flags.delay !== void 0 && flags.delay !== true ? Number(flags.delay) : void 0,
88
+ lenient: flags.lenient === true
89
+ });
90
+ console.log(`[nuxt-api-contract] Mock server listening on ${handle.url} (seed: ${seed ?? "randomized per contract"}, lenient: ${flags.lenient === true})`);
91
+ for (const contract of contracts) {
92
+ console.log(` ${contract.method.padEnd(6)} ${handle.url}${contract.path}`);
93
+ }
94
+ console.log(" GET /__mock/contracts (list endpoints)");
95
+ console.log("Press Ctrl+C to stop.");
96
+ const shutdown = async () => {
97
+ await handle.close();
98
+ process.exit(0);
99
+ };
100
+ process.on("SIGINT", shutdown);
101
+ process.on("SIGTERM", shutdown);
102
+ return;
103
+ }
104
+ console.error(`[nuxt-api-contract] Unknown command "${command ?? ""}". Available commands: openapi, mock`);
73
105
  process.exitCode = 1;
74
106
  }
75
107
  function toMinimalYaml(value, indent = 0) {
package/dist/client.d.mts CHANGED
@@ -1,70 +1,6 @@
1
+ export { C as ContractMock, M as MockResponseInput, 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 } from './shared/nuxt-api-contract.BgPd-YXc.mjs';
1
2
  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 };
3
+ export { s as serializeQuery, a as serializeQueryValue, b as stableStringify } from './shared/nuxt-api-contract.DZfOzVaB.mjs';
4
+ export { A as API_CONTRACT_KIND, a as AnyApiContract, b as ApiContract, c as ApiContractDefinition, d as ApiRequestOptions, e as AuthConfig, C as ContractBodyInput, f as ContractClientResponse, g as ContractErrorCode, h as ContractFromDefinition, i as ContractHandlerResponse, 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, M as MaybePromise, P as PathParams, R as ResolvedApiRequestOptions, S as SplitPath } from './shared/nuxt-api-contract.CFG8gzJH.mjs';
5
+ export { M as MockGenerateOptions, a as autoMockContract, g as generateMockResponse } from './shared/nuxt-api-contract.BOxyDvRy.mjs';
6
+ import 'zod';
package/dist/client.d.ts CHANGED
@@ -1,70 +1,6 @@
1
+ export { C as ContractMock, M as MockResponseInput, 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 } from './shared/nuxt-api-contract.C7KxMQHa.js';
1
2
  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 };
3
+ export { s as serializeQuery, a as serializeQueryValue, b as stableStringify } from './shared/nuxt-api-contract.DZfOzVaB.js';
4
+ export { A as API_CONTRACT_KIND, a as AnyApiContract, b as ApiContract, c as ApiContractDefinition, d as ApiRequestOptions, e as AuthConfig, C as ContractBodyInput, f as ContractClientResponse, g as ContractErrorCode, h as ContractFromDefinition, i as ContractHandlerResponse, 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, M as MaybePromise, P as PathParams, R as ResolvedApiRequestOptions, S as SplitPath } from './shared/nuxt-api-contract.CFG8gzJH.js';
5
+ export { M as MockGenerateOptions, a as autoMockContract, g as generateMockResponse } from './shared/nuxt-api-contract.BuWjAHJe.js';
6
+ import 'zod';
package/dist/client.mjs CHANGED
@@ -1,3 +1,5 @@
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';
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 } from './shared/nuxt-api-contract.V15soI_f.mjs';
2
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
3
  export { f as formatValidationMessage, s as sanitizeIssues, t as toValidationIssues } from './shared/nuxt-api-contract.DCAU2j7t.mjs';
4
+ export { s as serializeQuery, a as serializeQueryValue, b as stableStringify } from './shared/nuxt-api-contract.Dr4tPqGB.mjs';
5
+ export { a as autoMockContract, g as generateMockResponse } from './shared/nuxt-api-contract.Bd2Y7Lx0.mjs';
@@ -1,6 +1,7 @@
1
1
  import { useAsyncData, useNuxtApp, useRequestEvent } from '#imports';
2
- import { f as stableStringify, b as buildRequestPath, s as serializeQuery } from './shared/nuxt-api-contract.QDGSGaVY.mjs';
2
+ import { b as buildRequestPath } from './shared/nuxt-api-contract.V15soI_f.mjs';
3
3
  import { p as parseApiErrorPayload, A as ApiError, t as toApiError } from './shared/nuxt-api-contract.obS6uV8A.mjs';
4
+ import { b as stableStringify, s as serializeQuery } from './shared/nuxt-api-contract.Dr4tPqGB.mjs';
4
5
 
5
6
  function createRequestKey(contract, options) {
6
7
  const { params, query, body, extraHeaders, ...rest } = options ?? {};
@@ -0,0 +1,54 @@
1
+ export { M as MockGenerateOptions, a as autoMockContract, c as createRng, g as generateMockResponse, b as generateMockValue } from './shared/nuxt-api-contract.BOxyDvRy.mjs';
2
+ import { Server } from 'node:http';
3
+ import { AnyApiContract } from './client.mjs';
4
+ import 'zod';
5
+ import './shared/nuxt-api-contract.BgPd-YXc.mjs';
6
+
7
+ /**
8
+ * Standalone mock server: serves contract-defined endpoints with
9
+ * deterministic generated responses. Zero framework dependencies — plain
10
+ * `node:http`, so it can run outside Nuxt:
11
+ *
12
+ * ```bash
13
+ * npx nuxt-api-contract mock contracts/index.ts --port 4000 --seed 42
14
+ * ```
15
+ */
16
+
17
+ interface MockServerOptions {
18
+ contracts: AnyApiContract[];
19
+ /** Listen port (default: 4000; `0` picks a random free port). */
20
+ port?: number;
21
+ /** Listen host (default: `127.0.0.1`). */
22
+ host?: string;
23
+ /** Deterministic generation seed. */
24
+ seed?: number;
25
+ /** Artificial latency in ms per request. */
26
+ delay?: number;
27
+ /** Return generated responses even for invalid requests (default: false). */
28
+ lenient?: boolean;
29
+ }
30
+ interface MockServerHandle {
31
+ server: Server;
32
+ port: number;
33
+ host: string;
34
+ url: string;
35
+ close: () => Promise<void>;
36
+ }
37
+ interface Matcher {
38
+ contract: AnyApiContract;
39
+ method: string;
40
+ regex: RegExp;
41
+ names: string[];
42
+ }
43
+ /** Builds an in-memory matcher index over the contracts. */
44
+ declare function buildMockMatchers(contracts: AnyApiContract[]): Matcher[];
45
+ /**
46
+ * Creates (without listening) a mock `node:http` server for the contracts.
47
+ * Prefer `startMockServer` for CLI / test usage.
48
+ */
49
+ declare function createMockServer(options: MockServerOptions): Server;
50
+ /** Creates the mock server and starts listening. Resolves once ready. */
51
+ declare function startMockServer(options: MockServerOptions): Promise<MockServerHandle>;
52
+
53
+ export { buildMockMatchers, createMockServer, startMockServer };
54
+ export type { MockServerHandle, MockServerOptions };
package/dist/mock.d.ts ADDED
@@ -0,0 +1,54 @@
1
+ export { M as MockGenerateOptions, a as autoMockContract, c as createRng, g as generateMockResponse, b as generateMockValue } from './shared/nuxt-api-contract.BuWjAHJe.js';
2
+ import { Server } from 'node:http';
3
+ import { AnyApiContract } from './client.js';
4
+ import 'zod';
5
+ import './shared/nuxt-api-contract.C7KxMQHa.js';
6
+
7
+ /**
8
+ * Standalone mock server: serves contract-defined endpoints with
9
+ * deterministic generated responses. Zero framework dependencies — plain
10
+ * `node:http`, so it can run outside Nuxt:
11
+ *
12
+ * ```bash
13
+ * npx nuxt-api-contract mock contracts/index.ts --port 4000 --seed 42
14
+ * ```
15
+ */
16
+
17
+ interface MockServerOptions {
18
+ contracts: AnyApiContract[];
19
+ /** Listen port (default: 4000; `0` picks a random free port). */
20
+ port?: number;
21
+ /** Listen host (default: `127.0.0.1`). */
22
+ host?: string;
23
+ /** Deterministic generation seed. */
24
+ seed?: number;
25
+ /** Artificial latency in ms per request. */
26
+ delay?: number;
27
+ /** Return generated responses even for invalid requests (default: false). */
28
+ lenient?: boolean;
29
+ }
30
+ interface MockServerHandle {
31
+ server: Server;
32
+ port: number;
33
+ host: string;
34
+ url: string;
35
+ close: () => Promise<void>;
36
+ }
37
+ interface Matcher {
38
+ contract: AnyApiContract;
39
+ method: string;
40
+ regex: RegExp;
41
+ names: string[];
42
+ }
43
+ /** Builds an in-memory matcher index over the contracts. */
44
+ declare function buildMockMatchers(contracts: AnyApiContract[]): Matcher[];
45
+ /**
46
+ * Creates (without listening) a mock `node:http` server for the contracts.
47
+ * Prefer `startMockServer` for CLI / test usage.
48
+ */
49
+ declare function createMockServer(options: MockServerOptions): Server;
50
+ /** Creates the mock server and starts listening. Resolves once ready. */
51
+ declare function startMockServer(options: MockServerOptions): Promise<MockServerHandle>;
52
+
53
+ export { buildMockMatchers, createMockServer, startMockServer };
54
+ export type { MockServerHandle, MockServerOptions };
package/dist/mock.mjs ADDED
@@ -0,0 +1,8 @@
1
+ export { a as autoMockContract, c as createRng, g as generateMockResponse, b as generateMockValue } from './shared/nuxt-api-contract.Bd2Y7Lx0.mjs';
2
+ export { buildMockMatchers, createMockServer, startMockServer } from './chunks/server.mjs';
3
+ import './shared/nuxt-api-contract.V15soI_f.mjs';
4
+ import 'node:http';
5
+ import './shared/nuxt-api-contract.obS6uV8A.mjs';
6
+ import './shared/nuxt-api-contract.DDpgZj2g.mjs';
7
+ import 'zod';
8
+ import './shared/nuxt-api-contract.DCAU2j7t.mjs';
package/dist/module.d.mts CHANGED
@@ -20,8 +20,14 @@ interface ApiContractModuleOptions {
20
20
  description?: string;
21
21
  output?: string;
22
22
  };
23
- /** Enable registered contract mocks (development/testing only). */
24
- mocks?: boolean;
23
+ /**
24
+ * Mock mode:
25
+ * - `false` (default): real handlers.
26
+ * - `true`: use mocks registered via `mockContract()`.
27
+ * - `'auto'`: use registered mocks, and generate mock responses
28
+ * from the response schemas for contracts without an explicit mock.
29
+ */
30
+ mocks?: boolean | 'auto';
25
31
  /** Nuxt DevTools panel (no-op when DevTools is not installed). */
26
32
  devtools?: boolean;
27
33
  /** Directories scanned for contract auto-imports. */
package/dist/module.d.ts CHANGED
@@ -20,8 +20,14 @@ interface ApiContractModuleOptions {
20
20
  description?: string;
21
21
  output?: string;
22
22
  };
23
- /** Enable registered contract mocks (development/testing only). */
24
- mocks?: boolean;
23
+ /**
24
+ * Mock mode:
25
+ * - `false` (default): real handlers.
26
+ * - `true`: use mocks registered via `mockContract()`.
27
+ * - `'auto'`: use registered mocks, and generate mock responses
28
+ * from the response schemas for contracts without an explicit mock.
29
+ */
30
+ mocks?: boolean | 'auto';
25
31
  /** Nuxt DevTools panel (no-op when DevTools is not installed). */
26
32
  devtools?: boolean;
27
33
  /** Directories scanned for contract auto-imports. */
package/dist/module.mjs CHANGED
@@ -47,12 +47,14 @@ const module$1 = defineNuxtModule({
47
47
  async setup(options, nuxt) {
48
48
  const resolver = createResolver(import.meta.url);
49
49
  const rootDir = nuxt.options.rootDir;
50
+ const mocksEnabled = options.mocks === true || options.mocks === "auto";
50
51
  nuxt.options.runtimeConfig.apiContract = defu(nuxt.options.runtimeConfig.apiContract ?? {}, {
51
52
  validateResponse: options.validateResponse ?? "development",
52
- mocks: options.mocks ?? false
53
+ mocks: mocksEnabled,
54
+ mocksAuto: options.mocks === "auto"
53
55
  });
54
56
  nuxt.options.runtimeConfig.public.apiContract = defu(nuxt.options.runtimeConfig.public.apiContract ?? {}, {
55
- mocks: options.mocks ?? false
57
+ mocks: mocksEnabled
56
58
  });
57
59
  addImports([
58
60
  { from: "nuxt-api-contract/client", name: "defineApiContract" },