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
@@ -0,0 +1,94 @@
1
+ import { a as getContractMock } from './shared/nuxt-api-contract.QDGSGaVY.mjs';
2
+ export { b as buildRequestPath, c as clearContractRegistry, d as defineApiContract, g as getContractByName, 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';
3
+ import { A as ApiError, s as serializeApiError, B as BUILT_IN_ERROR_CODES } from './shared/nuxt-api-contract.obS6uV8A.mjs';
4
+ export { c as createApiError, i as isApiError, p as parseApiErrorPayload, t as toApiError } from './shared/nuxt-api-contract.obS6uV8A.mjs';
5
+ export { f as formatValidationMessage, s as sanitizeIssues, t as toValidationIssues } from './shared/nuxt-api-contract.DCAU2j7t.mjs';
6
+ import { defineEventHandler, getRequestHeaders, getQuery, readValidatedBody, setResponseStatus } from 'h3';
7
+ import { v as validateContractInput, r as readRuntimeConfig, s as shouldValidateResponse, a as validateContractResponse } from './shared/nuxt-api-contract.DDpgZj2g.mjs';
8
+ export { b as rawQuerySchema } from './shared/nuxt-api-contract.DDpgZj2g.mjs';
9
+ import 'zod';
10
+
11
+ const CONTRACT_HANDLER_META = Symbol.for("nuxt-api-contract.contractHandlerMeta");
12
+ function defineContractHandler(contract, handler) {
13
+ const eventHandler = defineEventHandler(async (event) => {
14
+ try {
15
+ let headers;
16
+ if (contract.headers) {
17
+ const raw = {};
18
+ for (const [key, value] of Object.entries(getRequestHeaders(event))) {
19
+ if (typeof value === "string") raw[key] = value;
20
+ }
21
+ headers = validateContractInput(contract, "headers", contract.headers, raw);
22
+ } else {
23
+ headers = getRequestHeaders(event);
24
+ }
25
+ let params;
26
+ if (contract.params) {
27
+ const routeParams = event.context.params ?? {};
28
+ params = validateContractInput(contract, "params", contract.params, routeParams);
29
+ } else {
30
+ params = event.context.params ?? {};
31
+ }
32
+ let query;
33
+ if (contract.query) {
34
+ query = validateContractInput(contract, "query", contract.query, getQuery(event));
35
+ } else {
36
+ query = getQuery(event);
37
+ }
38
+ let body;
39
+ if (contract.body) {
40
+ const rawBody = await readValidatedBody(event, (value) => value);
41
+ body = validateContractInput(contract, "body", contract.body, rawBody);
42
+ } else if (contract.method !== "GET" && contract.method !== "HEAD") {
43
+ body = await readValidatedBody(event, (value) => value);
44
+ } else {
45
+ body = void 0;
46
+ }
47
+ const ctx = {
48
+ params,
49
+ query,
50
+ body,
51
+ headers,
52
+ event
53
+ };
54
+ const runtimeConfig = readRuntimeConfig(() => useNitroRuntimeConfig(event));
55
+ const mock = getContractMock(contract);
56
+ if (runtimeConfig.mocks && mock?.response) {
57
+ const mocked = await mock.response();
58
+ return finalize(contract, mocked, runtimeConfig);
59
+ }
60
+ const result = await handler(ctx);
61
+ return finalize(contract, result, runtimeConfig);
62
+ } catch (error) {
63
+ return respondWithError(event, error);
64
+ }
65
+ });
66
+ const withMeta = eventHandler;
67
+ withMeta[CONTRACT_HANDLER_META] = { contract, handler };
68
+ return eventHandler;
69
+ }
70
+ function useNitroRuntimeConfig(event) {
71
+ const ctx = event.context;
72
+ return ctx.nitro?.runtimeConfig ?? ctx._nitro?.runtimeConfig ?? ctx.$config;
73
+ }
74
+ function finalize(contract, result, runtimeConfig) {
75
+ if (contract.response && shouldValidateResponse(runtimeConfig.validateResponse)) {
76
+ return validateContractResponse(contract, contract.response, result);
77
+ }
78
+ return result;
79
+ }
80
+ function respondWithError(event, error) {
81
+ if (error instanceof ApiError) {
82
+ setResponseStatus(event, error.statusCode, error.code);
83
+ return serializeApiError(error);
84
+ }
85
+ setResponseStatus(event, 500, BUILT_IN_ERROR_CODES.internal);
86
+ return {
87
+ error: {
88
+ code: BUILT_IN_ERROR_CODES.internal,
89
+ message: "Internal server error"
90
+ }
91
+ };
92
+ }
93
+
94
+ export { ApiError, BUILT_IN_ERROR_CODES, defineContractHandler, getContractMock, readRuntimeConfig, serializeApiError, shouldValidateResponse, validateContractInput, validateContractResponse };
@@ -0,0 +1,37 @@
1
+ import { H3Event, EventHandler } from 'h3';
2
+ import { z } from 'zod';
3
+ import { A as AnyApiContract, M as MaybePromise, i as ContractHandlerResponse } from './nuxt-api-contract.CPm9WbWA.mjs';
4
+
5
+ type Infer<T> = T extends z.ZodType ? z.infer<T> : never;
6
+ /**
7
+ * Context handed to contract handlers. All data is already validated.
8
+ */
9
+ interface ContractHandlerContext<TParams = Record<string, unknown>, TQuery = Record<string, unknown>, TBody = undefined, THeaders = Record<string, string>> {
10
+ params: TParams;
11
+ query: TQuery;
12
+ body: TBody;
13
+ headers: THeaders;
14
+ /** Raw h3 event, for status codes, cookies, auth, etc. */
15
+ event: H3Event;
16
+ /** Reserved for auth integrations (populated by middleware/plugins). */
17
+ user?: unknown;
18
+ }
19
+ type ContractHandler<C extends AnyApiContract> = (ctx: ContractHandlerContext<C['params'] extends z.ZodType ? Infer<C['params']> : Record<string, unknown>, C['query'] extends z.ZodType ? Infer<C['query']> : Record<string, unknown>, C['body'] extends z.ZodType ? Infer<C['body']> : undefined, C['headers'] extends z.ZodType ? Infer<C['headers']> : Record<string, string>>) => MaybePromise<ContractHandlerResponse<C>>;
20
+ /**
21
+ * Defines a Nitro event handler bound to a contract:
22
+ *
23
+ * 1. validates `params`, `query`, `body` and `headers` against the schemas;
24
+ * 2. invokes the handler with the validated (typed) data;
25
+ * 3. optionally validates the response (see `apiContract.validateResponse`);
26
+ * 4. converts thrown `ApiError`s into the unified error payload.
27
+ *
28
+ * ```ts
29
+ * export default defineContractHandler(GetUser, async ({ params }) => {
30
+ * return { id: params.id, name: 'John' }
31
+ * })
32
+ * ```
33
+ */
34
+ declare function defineContractHandler<C extends AnyApiContract>(contract: C, handler: ContractHandler<C>): EventHandler;
35
+
36
+ export { defineContractHandler as d };
37
+ export type { ContractHandler as C, ContractHandlerContext as a };
@@ -0,0 +1,165 @@
1
+ import { ZodType } from 'zod';
2
+
3
+ /**
4
+ * Marker used to identify contract objects at runtime (and in the registry).
5
+ */
6
+ declare const API_CONTRACT_KIND: "api-contract";
7
+ /**
8
+ * Supported HTTP methods.
9
+ */
10
+ type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
11
+ /**
12
+ * Splits a string path into its segments at the type level.
13
+ * `/api/users/:id` -> ['api', 'users', ':id']
14
+ */
15
+ type SplitPath<TPath extends string> = TPath extends `${infer Head}/${infer Tail}` ? [...SplitPath<Head>, ...SplitPath<Tail>] : TPath extends '' ? [] : [TPath];
16
+ /**
17
+ * Extracts the path parameter object from a path string.
18
+ * `/api/posts/:postId/comments/:commentId` -> { postId: string, commentId: string }
19
+ */
20
+ type PathParams<TPath extends string> = {
21
+ [Segment in SplitPath<TPath>[number] as Segment extends `:${infer Name}` ? Name : never]: string;
22
+ };
23
+ interface AuthConfig {
24
+ required?: boolean;
25
+ roles?: readonly string[];
26
+ [key: string]: unknown;
27
+ }
28
+ /**
29
+ * The input accepted by `defineApiContract()`. All schema fields are optional;
30
+ * generics capture the literal types the user provided.
31
+ */
32
+ interface ApiContractDefinition<TMethod extends HttpMethod = HttpMethod, TPath extends string = string, TParams extends ZodType | undefined = ZodType | undefined, TQuery extends ZodType | undefined = ZodType | undefined, TBody extends ZodType | undefined = ZodType | undefined, THeaders extends ZodType | undefined = ZodType | undefined, TResponse extends ZodType | undefined = ZodType | undefined> {
33
+ /** Unique contract name, used by the registry / DevTools / OpenAPI. */
34
+ name?: string;
35
+ /** Optional contract version (see roadmap: contract versioning). */
36
+ version?: number;
37
+ method: TMethod;
38
+ path: TPath;
39
+ /** Path parameters schema, e.g. `z.object({ id: z.string().uuid() })`. */
40
+ params?: TParams;
41
+ /** Query parameters schema. */
42
+ query?: TQuery;
43
+ /** Request body schema. */
44
+ body?: TBody;
45
+ /** Request headers schema (validated against raw header values). */
46
+ headers?: THeaders;
47
+ /** Successful response schema. */
48
+ response?: TResponse;
49
+ /** Known error payloads keyed by machine-readable error code. */
50
+ errors?: Record<string, ZodType>;
51
+ /** OpenAPI summary. */
52
+ summary?: string;
53
+ /** OpenAPI description. */
54
+ description?: string;
55
+ /** OpenAPI tags. */
56
+ tags?: readonly string[];
57
+ /** Marks the endpoint as requiring authentication (informational / extension point). */
58
+ auth?: boolean | AuthConfig;
59
+ /** Free-form metadata consumed by tooling (DevTools, mocks, docs). */
60
+ metadata?: Record<string, unknown>;
61
+ }
62
+ /**
63
+ * A frozen, type-safe API contract — the single source of truth for
64
+ * runtime validation, TypeScript types, OpenAPI, DevTools and mocks.
65
+ */
66
+ interface ApiContract<TMethod extends HttpMethod = HttpMethod, TPath extends string = string, TParams extends ZodType | undefined = ZodType | undefined, TQuery extends ZodType | undefined = ZodType | undefined, TBody extends ZodType | undefined = ZodType | undefined, THeaders extends ZodType | undefined = ZodType | undefined, TResponse extends ZodType | undefined = ZodType | undefined> {
67
+ readonly kind: typeof API_CONTRACT_KIND;
68
+ readonly method: TMethod;
69
+ readonly path: TPath;
70
+ readonly name: string | undefined;
71
+ readonly version: number | undefined;
72
+ readonly params: TParams;
73
+ readonly query: TQuery;
74
+ readonly body: TBody;
75
+ readonly headers: THeaders;
76
+ readonly response: TResponse;
77
+ readonly errors: Readonly<Record<string, ZodType>> | undefined;
78
+ readonly summary: string | undefined;
79
+ readonly description: string | undefined;
80
+ readonly tags: readonly string[] | undefined;
81
+ readonly auth: boolean | AuthConfig | undefined;
82
+ readonly metadata: Readonly<Record<string, unknown>> | undefined;
83
+ }
84
+ /**
85
+ * Extracts a definition property, defaulting to `undefined` when the key is
86
+ * absent from the captured (const-inferred) literal type.
87
+ */
88
+ type PickDefKey<TDef, TKey extends 'params' | 'query' | 'body' | 'headers' | 'response'> = TKey extends keyof TDef ? TDef[TKey] : undefined;
89
+ /**
90
+ * Maps a definition literal into the resolved contract type.
91
+ */
92
+ type ContractFromDefinition<TDef extends ApiContractDefinition> = ApiContract<TDef['method'], TDef['path'], PickDefKey<TDef, 'params'>, PickDefKey<TDef, 'query'>, PickDefKey<TDef, 'body'>, PickDefKey<TDef, 'headers'>, PickDefKey<TDef, 'response'>>;
93
+ /**
94
+ * Input type produced by a Zod schema, or an empty object when absent.
95
+ */
96
+ type ExtractSchemaInput<T> = T extends ZodType<any, any, infer Input> ? Input : Record<never, never>;
97
+ /**
98
+ * Any contract, with all generics widened. Used as a variance-friendly bound.
99
+ */
100
+ type AnyApiContract = ApiContract<HttpMethod, string, ZodType | undefined, ZodType | undefined, ZodType | undefined, ZodType | undefined, ZodType | undefined>;
101
+ type ContractPathParams<C extends AnyApiContract> = C extends {
102
+ path: infer TPath extends string;
103
+ } ? PathParams<TPath> : Record<never, never>;
104
+ /**
105
+ * The params the client must send: the intersection of the params schema
106
+ * input and the parameters extracted from the path itself.
107
+ */
108
+ type ContractParamsInput<C extends AnyApiContract> = ExtractSchemaInput<C['params']> & ContractPathParams<C>;
109
+ type ContractQueryInput<C extends AnyApiContract> = ExtractSchemaInput<C['query']>;
110
+ type ContractBodyInput<C extends AnyApiContract> = ExtractSchemaInput<C['body']>;
111
+ type ContractHeadersInput<C extends AnyApiContract> = ExtractSchemaInput<C['headers']>;
112
+ /** Response type as returned by handlers (Zod input side). */
113
+ type ContractHandlerResponse<C extends AnyApiContract> = ExtractSchemaInput<C['response']>;
114
+ /** Response type as received by the client (Zod output side). */
115
+ type ContractClientResponse<C extends AnyApiContract> = ExtractSchemaOutput<C['response']>;
116
+ /** Known error codes declared on the contract, plus built-in codes. */
117
+ type ContractErrorCode<C extends AnyApiContract> = (C extends {
118
+ errors: infer TErrors;
119
+ } ? keyof TErrors & string : never) | 'VALIDATION_ERROR' | 'API_CONTRACT_RESPONSE_VALIDATION_ERROR' | 'INTERNAL_ERROR';
120
+ type MaybePromise<T> = T | Promise<T>;
121
+ type EmptyObject = Record<never, never>;
122
+ type IsEmptyObject<T> = keyof T extends never ? true : false;
123
+ /**
124
+ * Request options accepted by `useApi` / `useApiClient`.
125
+ * Requiredness of `params` / `query` / `body` / `headers` is derived from the
126
+ * contract: an option is required when its schema (or the path) demands it,
127
+ * and optional (or rejected) otherwise.
128
+ */
129
+ type ApiRequestOptions<C extends AnyApiContract> = (IsEmptyObject<ContractParamsInput<C>> extends true ? {
130
+ params?: ContractParamsInput<C>;
131
+ } : {
132
+ params: ContractParamsInput<C>;
133
+ }) & (undefined extends C['query'] ? {
134
+ query?: ContractQueryInput<C>;
135
+ } : {
136
+ query: ContractQueryInput<C>;
137
+ }) & (undefined extends C['body'] ? {
138
+ body?: ContractBodyInput<C>;
139
+ } : {
140
+ body: ContractBodyInput<C>;
141
+ }) & (undefined extends C['headers'] ? {
142
+ headers?: ContractHeadersInput<C>;
143
+ } : {
144
+ headers: ContractHeadersInput<C>;
145
+ }) & {
146
+ /** Abort signal forwarded to the underlying fetch. */
147
+ signal?: AbortSignal;
148
+ /** Extra request headers merged after validated ones (e.g. Authorization). */
149
+ extraHeaders?: Record<string, string>;
150
+ };
151
+ /** Loose runtime representation of request options (after serialization). */
152
+ type ResolvedApiRequestOptions = {
153
+ params?: Record<string, unknown>;
154
+ query?: Record<string, unknown>;
155
+ body?: unknown;
156
+ headers?: Record<string, string>;
157
+ signal?: AbortSignal;
158
+ };
159
+ /**
160
+ * Output type produced by a Zod schema, or `unknown` when absent.
161
+ */
162
+ type ExtractSchemaOutput<T> = T extends ZodType<infer Output, any, any> ? Output : unknown;
163
+
164
+ export { API_CONTRACT_KIND as a };
165
+ export type { AnyApiContract as A, ContractBodyInput as C, EmptyObject as E, HttpMethod as H, IsEmptyObject as I, MaybePromise as M, PathParams as P, ResolvedApiRequestOptions as R, SplitPath as S, ApiContract as b, ApiContractDefinition as c, ApiRequestOptions as d, AuthConfig as e, ContractClientResponse as f, ContractErrorCode as g, ContractFromDefinition as h, ContractHandlerResponse as i, ContractHeadersInput as j, ContractParamsInput as k, ContractPathParams as l, ContractQueryInput as m, ExtractSchemaInput as n, ExtractSchemaOutput as o };
@@ -0,0 +1,165 @@
1
+ import { ZodType } from 'zod';
2
+
3
+ /**
4
+ * Marker used to identify contract objects at runtime (and in the registry).
5
+ */
6
+ declare const API_CONTRACT_KIND: "api-contract";
7
+ /**
8
+ * Supported HTTP methods.
9
+ */
10
+ type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
11
+ /**
12
+ * Splits a string path into its segments at the type level.
13
+ * `/api/users/:id` -> ['api', 'users', ':id']
14
+ */
15
+ type SplitPath<TPath extends string> = TPath extends `${infer Head}/${infer Tail}` ? [...SplitPath<Head>, ...SplitPath<Tail>] : TPath extends '' ? [] : [TPath];
16
+ /**
17
+ * Extracts the path parameter object from a path string.
18
+ * `/api/posts/:postId/comments/:commentId` -> { postId: string, commentId: string }
19
+ */
20
+ type PathParams<TPath extends string> = {
21
+ [Segment in SplitPath<TPath>[number] as Segment extends `:${infer Name}` ? Name : never]: string;
22
+ };
23
+ interface AuthConfig {
24
+ required?: boolean;
25
+ roles?: readonly string[];
26
+ [key: string]: unknown;
27
+ }
28
+ /**
29
+ * The input accepted by `defineApiContract()`. All schema fields are optional;
30
+ * generics capture the literal types the user provided.
31
+ */
32
+ interface ApiContractDefinition<TMethod extends HttpMethod = HttpMethod, TPath extends string = string, TParams extends ZodType | undefined = ZodType | undefined, TQuery extends ZodType | undefined = ZodType | undefined, TBody extends ZodType | undefined = ZodType | undefined, THeaders extends ZodType | undefined = ZodType | undefined, TResponse extends ZodType | undefined = ZodType | undefined> {
33
+ /** Unique contract name, used by the registry / DevTools / OpenAPI. */
34
+ name?: string;
35
+ /** Optional contract version (see roadmap: contract versioning). */
36
+ version?: number;
37
+ method: TMethod;
38
+ path: TPath;
39
+ /** Path parameters schema, e.g. `z.object({ id: z.string().uuid() })`. */
40
+ params?: TParams;
41
+ /** Query parameters schema. */
42
+ query?: TQuery;
43
+ /** Request body schema. */
44
+ body?: TBody;
45
+ /** Request headers schema (validated against raw header values). */
46
+ headers?: THeaders;
47
+ /** Successful response schema. */
48
+ response?: TResponse;
49
+ /** Known error payloads keyed by machine-readable error code. */
50
+ errors?: Record<string, ZodType>;
51
+ /** OpenAPI summary. */
52
+ summary?: string;
53
+ /** OpenAPI description. */
54
+ description?: string;
55
+ /** OpenAPI tags. */
56
+ tags?: readonly string[];
57
+ /** Marks the endpoint as requiring authentication (informational / extension point). */
58
+ auth?: boolean | AuthConfig;
59
+ /** Free-form metadata consumed by tooling (DevTools, mocks, docs). */
60
+ metadata?: Record<string, unknown>;
61
+ }
62
+ /**
63
+ * A frozen, type-safe API contract — the single source of truth for
64
+ * runtime validation, TypeScript types, OpenAPI, DevTools and mocks.
65
+ */
66
+ interface ApiContract<TMethod extends HttpMethod = HttpMethod, TPath extends string = string, TParams extends ZodType | undefined = ZodType | undefined, TQuery extends ZodType | undefined = ZodType | undefined, TBody extends ZodType | undefined = ZodType | undefined, THeaders extends ZodType | undefined = ZodType | undefined, TResponse extends ZodType | undefined = ZodType | undefined> {
67
+ readonly kind: typeof API_CONTRACT_KIND;
68
+ readonly method: TMethod;
69
+ readonly path: TPath;
70
+ readonly name: string | undefined;
71
+ readonly version: number | undefined;
72
+ readonly params: TParams;
73
+ readonly query: TQuery;
74
+ readonly body: TBody;
75
+ readonly headers: THeaders;
76
+ readonly response: TResponse;
77
+ readonly errors: Readonly<Record<string, ZodType>> | undefined;
78
+ readonly summary: string | undefined;
79
+ readonly description: string | undefined;
80
+ readonly tags: readonly string[] | undefined;
81
+ readonly auth: boolean | AuthConfig | undefined;
82
+ readonly metadata: Readonly<Record<string, unknown>> | undefined;
83
+ }
84
+ /**
85
+ * Extracts a definition property, defaulting to `undefined` when the key is
86
+ * absent from the captured (const-inferred) literal type.
87
+ */
88
+ type PickDefKey<TDef, TKey extends 'params' | 'query' | 'body' | 'headers' | 'response'> = TKey extends keyof TDef ? TDef[TKey] : undefined;
89
+ /**
90
+ * Maps a definition literal into the resolved contract type.
91
+ */
92
+ type ContractFromDefinition<TDef extends ApiContractDefinition> = ApiContract<TDef['method'], TDef['path'], PickDefKey<TDef, 'params'>, PickDefKey<TDef, 'query'>, PickDefKey<TDef, 'body'>, PickDefKey<TDef, 'headers'>, PickDefKey<TDef, 'response'>>;
93
+ /**
94
+ * Input type produced by a Zod schema, or an empty object when absent.
95
+ */
96
+ type ExtractSchemaInput<T> = T extends ZodType<any, any, infer Input> ? Input : Record<never, never>;
97
+ /**
98
+ * Any contract, with all generics widened. Used as a variance-friendly bound.
99
+ */
100
+ type AnyApiContract = ApiContract<HttpMethod, string, ZodType | undefined, ZodType | undefined, ZodType | undefined, ZodType | undefined, ZodType | undefined>;
101
+ type ContractPathParams<C extends AnyApiContract> = C extends {
102
+ path: infer TPath extends string;
103
+ } ? PathParams<TPath> : Record<never, never>;
104
+ /**
105
+ * The params the client must send: the intersection of the params schema
106
+ * input and the parameters extracted from the path itself.
107
+ */
108
+ type ContractParamsInput<C extends AnyApiContract> = ExtractSchemaInput<C['params']> & ContractPathParams<C>;
109
+ type ContractQueryInput<C extends AnyApiContract> = ExtractSchemaInput<C['query']>;
110
+ type ContractBodyInput<C extends AnyApiContract> = ExtractSchemaInput<C['body']>;
111
+ type ContractHeadersInput<C extends AnyApiContract> = ExtractSchemaInput<C['headers']>;
112
+ /** Response type as returned by handlers (Zod input side). */
113
+ type ContractHandlerResponse<C extends AnyApiContract> = ExtractSchemaInput<C['response']>;
114
+ /** Response type as received by the client (Zod output side). */
115
+ type ContractClientResponse<C extends AnyApiContract> = ExtractSchemaOutput<C['response']>;
116
+ /** Known error codes declared on the contract, plus built-in codes. */
117
+ type ContractErrorCode<C extends AnyApiContract> = (C extends {
118
+ errors: infer TErrors;
119
+ } ? keyof TErrors & string : never) | 'VALIDATION_ERROR' | 'API_CONTRACT_RESPONSE_VALIDATION_ERROR' | 'INTERNAL_ERROR';
120
+ type MaybePromise<T> = T | Promise<T>;
121
+ type EmptyObject = Record<never, never>;
122
+ type IsEmptyObject<T> = keyof T extends never ? true : false;
123
+ /**
124
+ * Request options accepted by `useApi` / `useApiClient`.
125
+ * Requiredness of `params` / `query` / `body` / `headers` is derived from the
126
+ * contract: an option is required when its schema (or the path) demands it,
127
+ * and optional (or rejected) otherwise.
128
+ */
129
+ type ApiRequestOptions<C extends AnyApiContract> = (IsEmptyObject<ContractParamsInput<C>> extends true ? {
130
+ params?: ContractParamsInput<C>;
131
+ } : {
132
+ params: ContractParamsInput<C>;
133
+ }) & (undefined extends C['query'] ? {
134
+ query?: ContractQueryInput<C>;
135
+ } : {
136
+ query: ContractQueryInput<C>;
137
+ }) & (undefined extends C['body'] ? {
138
+ body?: ContractBodyInput<C>;
139
+ } : {
140
+ body: ContractBodyInput<C>;
141
+ }) & (undefined extends C['headers'] ? {
142
+ headers?: ContractHeadersInput<C>;
143
+ } : {
144
+ headers: ContractHeadersInput<C>;
145
+ }) & {
146
+ /** Abort signal forwarded to the underlying fetch. */
147
+ signal?: AbortSignal;
148
+ /** Extra request headers merged after validated ones (e.g. Authorization). */
149
+ extraHeaders?: Record<string, string>;
150
+ };
151
+ /** Loose runtime representation of request options (after serialization). */
152
+ type ResolvedApiRequestOptions = {
153
+ params?: Record<string, unknown>;
154
+ query?: Record<string, unknown>;
155
+ body?: unknown;
156
+ headers?: Record<string, string>;
157
+ signal?: AbortSignal;
158
+ };
159
+ /**
160
+ * Output type produced by a Zod schema, or `unknown` when absent.
161
+ */
162
+ type ExtractSchemaOutput<T> = T extends ZodType<infer Output, any, any> ? Output : unknown;
163
+
164
+ export { API_CONTRACT_KIND as a };
165
+ export type { AnyApiContract as A, ContractBodyInput as C, EmptyObject as E, HttpMethod as H, IsEmptyObject as I, MaybePromise as M, PathParams as P, ResolvedApiRequestOptions as R, SplitPath as S, ApiContract as b, ApiContractDefinition as c, ApiRequestOptions as d, AuthConfig as e, ContractClientResponse as f, ContractErrorCode as g, ContractFromDefinition as h, ContractHandlerResponse as i, ContractHeadersInput as j, ContractParamsInput as k, ContractPathParams as l, ContractQueryInput as m, ExtractSchemaInput as n, ExtractSchemaOutput as o };
@@ -0,0 +1,109 @@
1
+ import { ZodError } from 'zod';
2
+
3
+ /**
4
+ * A sanitized validation issue that is safe to send to the client.
5
+ */
6
+ interface ValidationIssue {
7
+ /** Dotted path inside the validated value, e.g. `query.limit`. */
8
+ path: string;
9
+ /** Human readable message. */
10
+ message: string;
11
+ /** Expected type/shape description, when available. */
12
+ expected?: string;
13
+ /** Short serialized received value. Only populated outside production. */
14
+ received?: string;
15
+ }
16
+ /** Maps a Zod error into sanitized validation issues. */
17
+ declare function toValidationIssues(error: ZodError): ValidationIssue[];
18
+ /**
19
+ * Builds a developer-friendly multi-line message:
20
+ *
21
+ * ```
22
+ * [nuxt-api-contract]
23
+ *
24
+ * Invalid query for GET /api/users
25
+ *
26
+ * query.limit:
27
+ * Expected number
28
+ * Received string
29
+ * ```
30
+ */
31
+ declare function formatValidationMessage(options: {
32
+ subject: string;
33
+ method: string;
34
+ path: string;
35
+ issues: ValidationIssue[];
36
+ includeReceived?: boolean;
37
+ }): string;
38
+ /**
39
+ * Strips received values from issues (used before exposing issues in
40
+ * production, where they may contain sensitive data such as passwords).
41
+ */
42
+ declare function sanitizeIssues(issues: ValidationIssue[]): ValidationIssue[];
43
+
44
+ /**
45
+ * Stable, machine-readable error payload sent over the wire.
46
+ */
47
+ interface ApiErrorPayload {
48
+ error: {
49
+ code: string;
50
+ message: string;
51
+ statusCode?: number;
52
+ details?: unknown;
53
+ issues?: ValidationIssue[];
54
+ };
55
+ }
56
+ /**
57
+ * Unified API error. Thrown on the server (and converted to an h3 error),
58
+ * reconstructed on the client from the error payload.
59
+ */
60
+ declare class ApiError extends Error {
61
+ readonly code: string;
62
+ readonly statusCode: number;
63
+ readonly details?: unknown;
64
+ readonly issues?: ValidationIssue[];
65
+ constructor(options: {
66
+ code: string;
67
+ message: string;
68
+ statusCode?: number;
69
+ details?: unknown;
70
+ issues?: ValidationIssue[];
71
+ });
72
+ toJSON(): ApiErrorPayload;
73
+ }
74
+ type CreateApiErrorInput = {
75
+ code: string;
76
+ message?: string;
77
+ statusCode?: number;
78
+ details?: unknown;
79
+ issues?: ValidationIssue[];
80
+ };
81
+ /**
82
+ * Creates a unified API error.
83
+ *
84
+ * ```ts
85
+ * throw createApiError('USER_NOT_FOUND', 'User not found', 404)
86
+ * throw createApiError({ code: 'VALIDATION_ERROR', statusCode: 400, details })
87
+ * ```
88
+ */
89
+ declare function createApiError(input: CreateApiErrorInput): ApiError;
90
+ declare function createApiError(code: string, message?: string, statusCode?: number, details?: unknown): ApiError;
91
+ /** Type guard for `ApiError` (works across module instances / payload objects). */
92
+ declare function isApiError(value: unknown): value is ApiError;
93
+ /** Serializes an `ApiError` into the wire payload. */
94
+ declare function serializeApiError(error: ApiError): ApiErrorPayload;
95
+ /** Narrows an unknown parsed payload into an `ApiError` payload, if it matches. */
96
+ declare function parseApiErrorPayload(value: unknown): ApiErrorPayload['error'] | undefined;
97
+ /** Wraps an unknown thrown value into an `ApiError`. */
98
+ declare function toApiError(value: unknown, fallbackMessage?: string): ApiError;
99
+ /** Built-in error codes. */
100
+ declare const BUILT_IN_ERROR_CODES: {
101
+ readonly validation: "VALIDATION_ERROR";
102
+ readonly responseValidation: "API_CONTRACT_RESPONSE_VALIDATION_ERROR";
103
+ readonly internal: "INTERNAL_ERROR";
104
+ readonly notFound: "NOT_FOUND";
105
+ readonly methodNotAllowed: "METHOD_NOT_ALLOWED";
106
+ };
107
+
108
+ export { ApiError as A, BUILT_IN_ERROR_CODES as B, serializeApiError as b, createApiError as c, toValidationIssues as d, formatValidationMessage as f, isApiError as i, parseApiErrorPayload as p, sanitizeIssues as s, toApiError as t };
109
+ export type { CreateApiErrorInput as C, ValidationIssue as V, ApiErrorPayload as a };