nuxt-api-contract 0.1.0 → 0.3.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.
- package/README.md +99 -9
- package/dist/chunks/server.mjs +161 -0
- package/dist/cli.mjs +33 -1
- package/dist/client.d.mts +5 -69
- package/dist/client.d.ts +5 -69
- package/dist/client.mjs +3 -2
- package/dist/composables.mjs +2 -2
- package/dist/mock.d.mts +54 -0
- package/dist/mock.d.ts +54 -0
- package/dist/mock.mjs +7 -0
- package/dist/module.d.mts +8 -2
- package/dist/module.d.ts +8 -2
- package/dist/module.mjs +4 -2
- package/dist/runtime/shared/mock.mjs +188 -0
- package/dist/server.d.mts +7 -4
- package/dist/server.d.ts +7 -4
- package/dist/server.mjs +8 -7
- package/dist/shared/nuxt-api-contract.BOxyDvRy.d.mts +40 -0
- package/dist/shared/nuxt-api-contract.BWgzVTNN.mjs +191 -0
- package/dist/shared/{nuxt-api-contract.S1zqCiJX.d.ts → nuxt-api-contract.B_UvrNC8.d.ts} +1 -1
- package/dist/shared/nuxt-api-contract.BgPd-YXc.d.mts +53 -0
- package/dist/shared/nuxt-api-contract.BuWjAHJe.d.ts +40 -0
- package/dist/shared/nuxt-api-contract.C7KxMQHa.d.ts +53 -0
- package/dist/shared/{nuxt-api-contract.DDpgZj2g.mjs → nuxt-api-contract.CAIOgncy.mjs} +1 -1
- package/dist/shared/{nuxt-api-contract.CPm9WbWA.d.mts → nuxt-api-contract.CFG8gzJH.d.mts} +2 -2
- package/dist/shared/{nuxt-api-contract.CPm9WbWA.d.ts → nuxt-api-contract.CFG8gzJH.d.ts} +2 -2
- package/dist/shared/nuxt-api-contract.CHdRLlU7.mjs +152 -0
- package/dist/shared/nuxt-api-contract.DZfOzVaB.d.mts +16 -0
- package/dist/shared/nuxt-api-contract.DZfOzVaB.d.ts +16 -0
- package/dist/shared/nuxt-api-contract.Dr4tPqGB.mjs +38 -0
- package/dist/shared/{nuxt-api-contract.B9JBCRk8.d.mts → nuxt-api-contract.VCevNWQV.d.mts} +1 -1
- package/dist/shared.d.mts +4 -2
- package/dist/shared.d.ts +4 -2
- package/dist/shared.mjs +3 -2
- package/dist/testing.d.mts +111 -6
- package/dist/testing.d.ts +111 -6
- package/dist/testing.mjs +160 -3
- package/package.json +5 -1
- package/dist/shared/nuxt-api-contract.QDGSGaVY.mjs +0 -117
- package/dist/shared/nuxt-api-contract.obS6uV8A.mjs +0 -73
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import * as zod from 'zod';
|
|
2
|
+
import { a as AnyApiContract, M as MaybePromise, i as ContractHandlerResponse, c as ApiContractDefinition, h as ContractFromDefinition } from './nuxt-api-contract.CFG8gzJH.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Registers a named contract. Called automatically by `defineApiContract`
|
|
6
|
+
* when a `name` is provided. Duplicate names are overwritten with a warning.
|
|
7
|
+
*/
|
|
8
|
+
declare function registerContract(contract: AnyApiContract): void;
|
|
9
|
+
declare function getContractByName(name: string): AnyApiContract | undefined;
|
|
10
|
+
declare function listRegisteredContracts(): AnyApiContract[];
|
|
11
|
+
/** Test helper: clears the registry. */
|
|
12
|
+
declare function clearContractRegistry(): void;
|
|
13
|
+
interface ContractMock<C extends AnyApiContract> {
|
|
14
|
+
/** Factory producing a (validated) response for the contract. */
|
|
15
|
+
response?: () => MaybePromise<MockResponseInput<C>>;
|
|
16
|
+
/** Simulated latency in ms. */
|
|
17
|
+
delay?: number;
|
|
18
|
+
}
|
|
19
|
+
type MockResponseInput<C extends AnyApiContract> = C['response'] extends zod.ZodType ? ContractHandlerResponse<C> : unknown;
|
|
20
|
+
/**
|
|
21
|
+
* Registers a mock implementation for a contract. When the module option
|
|
22
|
+
* `apiContract.mocks` is enabled, contract handlers return the mock response
|
|
23
|
+
* (still validated against the response schema).
|
|
24
|
+
*/
|
|
25
|
+
declare function mockContract<C extends AnyApiContract>(contract: C, mock: ContractMock<C>): void;
|
|
26
|
+
declare function getContractMock<C extends AnyApiContract>(contract: C): ContractMock<C> | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* Defines a type-safe API contract.
|
|
29
|
+
*
|
|
30
|
+
* The contract is the single source of truth for runtime validation,
|
|
31
|
+
* TypeScript types, OpenAPI generation, DevTools and mocks.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* export const GetUser = defineApiContract({
|
|
36
|
+
* method: 'GET',
|
|
37
|
+
* path: '/api/users/:id',
|
|
38
|
+
* params: z.object({ id: z.string().uuid() }),
|
|
39
|
+
* response: z.object({ id: z.string(), name: z.string() }),
|
|
40
|
+
* })
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
declare function defineApiContract<const TDef extends ApiContractDefinition>(definition: TDef): ContractFromDefinition<TDef>;
|
|
44
|
+
/** Type guard for contract objects. */
|
|
45
|
+
declare function isApiContract(value: unknown): value is AnyApiContract;
|
|
46
|
+
/**
|
|
47
|
+
* Builds the request URL from a contract path and concrete params.
|
|
48
|
+
* Remaining params that are not part of the path are ignored.
|
|
49
|
+
*/
|
|
50
|
+
declare function buildRequestPath(path: string, params: Record<string, unknown> | undefined): string;
|
|
51
|
+
|
|
52
|
+
export { getContractMock as a, buildRequestPath as b, clearContractRegistry as c, defineApiContract as d, getContractByName as g, isApiContract as i, listRegisteredContracts as l, mockContract as m, registerContract as r };
|
|
53
|
+
export type { ContractMock as C, MockResponseInput as M };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import {
|
|
2
|
+
import { a as createApiError, B as BUILT_IN_ERROR_CODES } from './nuxt-api-contract.CHdRLlU7.mjs';
|
|
3
3
|
import { t as toValidationIssues, s as sanitizeIssues, f as formatValidationMessage } from './nuxt-api-contract.DCAU2j7t.mjs';
|
|
4
4
|
|
|
5
5
|
function readRuntimeConfig(getConfig) {
|
|
@@ -161,5 +161,5 @@ type ResolvedApiRequestOptions = {
|
|
|
161
161
|
*/
|
|
162
162
|
type ExtractSchemaOutput<T> = T extends ZodType<infer Output, any, any> ? Output : unknown;
|
|
163
163
|
|
|
164
|
-
export { API_CONTRACT_KIND as
|
|
165
|
-
export type {
|
|
164
|
+
export { API_CONTRACT_KIND as A };
|
|
165
|
+
export type { ContractBodyInput as C, EmptyObject as E, HttpMethod as H, IsEmptyObject as I, MaybePromise as M, PathParams as P, ResolvedApiRequestOptions as R, SplitPath as S, AnyApiContract as a, 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 };
|
|
@@ -161,5 +161,5 @@ type ResolvedApiRequestOptions = {
|
|
|
161
161
|
*/
|
|
162
162
|
type ExtractSchemaOutput<T> = T extends ZodType<infer Output, any, any> ? Output : unknown;
|
|
163
163
|
|
|
164
|
-
export { API_CONTRACT_KIND as
|
|
165
|
-
export type {
|
|
164
|
+
export { API_CONTRACT_KIND as A };
|
|
165
|
+
export type { ContractBodyInput as C, EmptyObject as E, HttpMethod as H, IsEmptyObject as I, MaybePromise as M, PathParams as P, ResolvedApiRequestOptions as R, SplitPath as S, AnyApiContract as a, 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,152 @@
|
|
|
1
|
+
const API_CONTRACT_KIND = "api-contract";
|
|
2
|
+
|
|
3
|
+
const REGISTRY_KEY = Symbol.for("nuxt-api-contract.registry");
|
|
4
|
+
function getStore() {
|
|
5
|
+
const globalThis_ = globalThis;
|
|
6
|
+
if (!globalThis_[REGISTRY_KEY]) {
|
|
7
|
+
globalThis_[REGISTRY_KEY] = { contracts: /* @__PURE__ */ new Map() };
|
|
8
|
+
}
|
|
9
|
+
return globalThis_[REGISTRY_KEY];
|
|
10
|
+
}
|
|
11
|
+
function registerContract(contract) {
|
|
12
|
+
if (!contract.name) return;
|
|
13
|
+
const store = getStore();
|
|
14
|
+
const existing = store.contracts.get(contract.name);
|
|
15
|
+
if (existing && existing !== contract) {
|
|
16
|
+
console.warn(
|
|
17
|
+
`[nuxt-api-contract] Duplicate contract name "${contract.name}" registered. The latest definition wins.`
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
store.contracts.set(contract.name, contract);
|
|
21
|
+
}
|
|
22
|
+
function getContractByName(name) {
|
|
23
|
+
return getStore().contracts.get(name);
|
|
24
|
+
}
|
|
25
|
+
function listRegisteredContracts() {
|
|
26
|
+
return [...getStore().contracts.values()];
|
|
27
|
+
}
|
|
28
|
+
function clearContractRegistry() {
|
|
29
|
+
getStore().contracts.clear();
|
|
30
|
+
}
|
|
31
|
+
const MOCK_STORE_KEY = Symbol.for("nuxt-api-contract.mocks");
|
|
32
|
+
const mockStore = globalThis[MOCK_STORE_KEY] ??= /* @__PURE__ */ new Map();
|
|
33
|
+
function mockContract(contract, mock) {
|
|
34
|
+
mockStore.set(contract, mock);
|
|
35
|
+
}
|
|
36
|
+
function getContractMock(contract) {
|
|
37
|
+
return mockStore.get(contract);
|
|
38
|
+
}
|
|
39
|
+
function defineApiContract(definition) {
|
|
40
|
+
const contract = {
|
|
41
|
+
kind: API_CONTRACT_KIND,
|
|
42
|
+
name: definition.name,
|
|
43
|
+
version: definition.version,
|
|
44
|
+
method: definition.method,
|
|
45
|
+
path: definition.path,
|
|
46
|
+
params: definition.params,
|
|
47
|
+
query: definition.query,
|
|
48
|
+
body: definition.body,
|
|
49
|
+
headers: definition.headers,
|
|
50
|
+
response: definition.response,
|
|
51
|
+
errors: definition.errors ? Object.freeze({ ...definition.errors }) : void 0,
|
|
52
|
+
summary: definition.summary,
|
|
53
|
+
description: definition.description,
|
|
54
|
+
tags: definition.tags ? Object.freeze([...definition.tags]) : void 0,
|
|
55
|
+
auth: definition.auth,
|
|
56
|
+
metadata: definition.metadata ? Object.freeze({ ...definition.metadata }) : void 0
|
|
57
|
+
};
|
|
58
|
+
Object.freeze(contract);
|
|
59
|
+
if (definition.name) {
|
|
60
|
+
registerContract(contract);
|
|
61
|
+
}
|
|
62
|
+
return contract;
|
|
63
|
+
}
|
|
64
|
+
function isApiContract(value) {
|
|
65
|
+
return typeof value === "object" && value !== null && value.kind === API_CONTRACT_KIND;
|
|
66
|
+
}
|
|
67
|
+
function buildRequestPath(path, params) {
|
|
68
|
+
let url = path;
|
|
69
|
+
for (const match of path.matchAll(/:([A-Za-z_][A-Za-z0-9_]*)/g)) {
|
|
70
|
+
const name = match[1];
|
|
71
|
+
const value = params?.[name];
|
|
72
|
+
if (value === void 0 || value === null) {
|
|
73
|
+
throw new Error(`[nuxt-api-contract] Missing path parameter ":${name}" for ${path}`);
|
|
74
|
+
}
|
|
75
|
+
url = url.replace(`:${name}`, encodeURIComponent(String(value)));
|
|
76
|
+
}
|
|
77
|
+
return url;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
class ApiError extends Error {
|
|
81
|
+
code;
|
|
82
|
+
statusCode;
|
|
83
|
+
details;
|
|
84
|
+
issues;
|
|
85
|
+
constructor(options) {
|
|
86
|
+
super(options.message);
|
|
87
|
+
this.name = "ApiError";
|
|
88
|
+
this.code = options.code;
|
|
89
|
+
this.statusCode = options.statusCode ?? 500;
|
|
90
|
+
this.details = options.details;
|
|
91
|
+
this.issues = options.issues;
|
|
92
|
+
}
|
|
93
|
+
toJSON() {
|
|
94
|
+
return serializeApiError(this);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function createApiError(codeOrOptions, message, statusCode, details) {
|
|
98
|
+
if (typeof codeOrOptions === "string") {
|
|
99
|
+
return new ApiError({ code: codeOrOptions, message: message ?? codeOrOptions, statusCode, details });
|
|
100
|
+
}
|
|
101
|
+
return new ApiError({
|
|
102
|
+
code: codeOrOptions.code,
|
|
103
|
+
message: codeOrOptions.message ?? codeOrOptions.code,
|
|
104
|
+
statusCode: codeOrOptions.statusCode,
|
|
105
|
+
details: codeOrOptions.details,
|
|
106
|
+
issues: codeOrOptions.issues
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
function isApiError(value) {
|
|
110
|
+
return value instanceof ApiError || typeof value === "object" && value !== null && value.name === "ApiError" && typeof value.code === "string";
|
|
111
|
+
}
|
|
112
|
+
function serializeApiError(error) {
|
|
113
|
+
return {
|
|
114
|
+
error: {
|
|
115
|
+
code: error.code,
|
|
116
|
+
message: error.message,
|
|
117
|
+
statusCode: error.statusCode,
|
|
118
|
+
details: error.details,
|
|
119
|
+
issues: error.issues
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function parseApiErrorPayload(value) {
|
|
124
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
125
|
+
const error = value.error;
|
|
126
|
+
if (typeof error !== "object" || error === null) return void 0;
|
|
127
|
+
const { code, message, statusCode, details, issues } = error;
|
|
128
|
+
if (typeof code !== "string" || typeof message !== "string") return void 0;
|
|
129
|
+
return {
|
|
130
|
+
code,
|
|
131
|
+
message,
|
|
132
|
+
statusCode: typeof statusCode === "number" ? statusCode : void 0,
|
|
133
|
+
details,
|
|
134
|
+
issues: Array.isArray(issues) ? issues : void 0
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
function toApiError(value, fallbackMessage = "Internal server error") {
|
|
138
|
+
if (isApiError(value)) return value;
|
|
139
|
+
if (value instanceof Error) {
|
|
140
|
+
return new ApiError({ code: "INTERNAL_ERROR", message: value.message || fallbackMessage, statusCode: 500 });
|
|
141
|
+
}
|
|
142
|
+
return new ApiError({ code: "INTERNAL_ERROR", message: fallbackMessage, statusCode: 500 });
|
|
143
|
+
}
|
|
144
|
+
const BUILT_IN_ERROR_CODES = {
|
|
145
|
+
validation: "VALIDATION_ERROR",
|
|
146
|
+
responseValidation: "API_CONTRACT_RESPONSE_VALIDATION_ERROR",
|
|
147
|
+
internal: "INTERNAL_ERROR",
|
|
148
|
+
notFound: "NOT_FOUND",
|
|
149
|
+
methodNotAllowed: "METHOD_NOT_ALLOWED"
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
export { ApiError as A, BUILT_IN_ERROR_CODES as B, createApiError as a, buildRequestPath as b, clearContractRegistry as c, defineApiContract as d, getContractMock as e, isApiError as f, getContractByName as g, isApiContract as i, listRegisteredContracts as l, mockContract as m, parseApiErrorPayload as p, registerContract as r, serializeApiError as s, toApiError as t };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serialization helpers shared by the client transport and cache keys.
|
|
3
|
+
*
|
|
4
|
+
* The protocol intentionally stays minimal: standard JSON plus two common
|
|
5
|
+
* edge cases (Date and bigint). Response serialization relies on Nitro's
|
|
6
|
+
* built-in devalue support, which already handles Date, RegExp, etc.
|
|
7
|
+
*/
|
|
8
|
+
/** Converts query values into URL-safe primitives (Date -> ISO, bigint -> string). */
|
|
9
|
+
type SerializedQueryValue = string | number | boolean | Array<string | number | boolean>;
|
|
10
|
+
declare function serializeQueryValue(value: unknown): SerializedQueryValue;
|
|
11
|
+
/** Prepares a query object for `$fetch` / URL building; drops `undefined` entries. */
|
|
12
|
+
declare function serializeQuery(query: Record<string, unknown> | undefined): Record<string, SerializedQueryValue> | undefined;
|
|
13
|
+
/** Deterministic JSON stringify (sorted keys) for stable SSR cache keys. */
|
|
14
|
+
declare function stableStringify(value: unknown): string;
|
|
15
|
+
|
|
16
|
+
export { serializeQueryValue as a, stableStringify as b, serializeQuery as s };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serialization helpers shared by the client transport and cache keys.
|
|
3
|
+
*
|
|
4
|
+
* The protocol intentionally stays minimal: standard JSON plus two common
|
|
5
|
+
* edge cases (Date and bigint). Response serialization relies on Nitro's
|
|
6
|
+
* built-in devalue support, which already handles Date, RegExp, etc.
|
|
7
|
+
*/
|
|
8
|
+
/** Converts query values into URL-safe primitives (Date -> ISO, bigint -> string). */
|
|
9
|
+
type SerializedQueryValue = string | number | boolean | Array<string | number | boolean>;
|
|
10
|
+
declare function serializeQueryValue(value: unknown): SerializedQueryValue;
|
|
11
|
+
/** Prepares a query object for `$fetch` / URL building; drops `undefined` entries. */
|
|
12
|
+
declare function serializeQuery(query: Record<string, unknown> | undefined): Record<string, SerializedQueryValue> | undefined;
|
|
13
|
+
/** Deterministic JSON stringify (sorted keys) for stable SSR cache keys. */
|
|
14
|
+
declare function stableStringify(value: unknown): string;
|
|
15
|
+
|
|
16
|
+
export { serializeQueryValue as a, stableStringify as b, serializeQuery as s };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
function serializeQueryValue(value) {
|
|
2
|
+
if (value instanceof Date) return value.toISOString();
|
|
3
|
+
if (typeof value === "bigint") return value.toString();
|
|
4
|
+
if (Array.isArray(value)) return value.map((item) => serializePrimitive(item));
|
|
5
|
+
if (value === void 0 || value === null) return "";
|
|
6
|
+
if (typeof value === "object") return JSON.stringify(value);
|
|
7
|
+
return value;
|
|
8
|
+
}
|
|
9
|
+
function serializePrimitive(value) {
|
|
10
|
+
if (value instanceof Date) return value.toISOString();
|
|
11
|
+
if (typeof value === "bigint") return value.toString();
|
|
12
|
+
if (value === void 0 || value === null || typeof value === "object") return JSON.stringify(value) ?? "";
|
|
13
|
+
return value;
|
|
14
|
+
}
|
|
15
|
+
function serializeQuery(query) {
|
|
16
|
+
if (!query) return void 0;
|
|
17
|
+
const result = {};
|
|
18
|
+
for (const [key, value] of Object.entries(query)) {
|
|
19
|
+
if (value === void 0) continue;
|
|
20
|
+
result[key] = serializeQueryValue(value);
|
|
21
|
+
}
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
24
|
+
function stableStringify(value) {
|
|
25
|
+
return JSON.stringify(sortValue(value));
|
|
26
|
+
}
|
|
27
|
+
function sortValue(value) {
|
|
28
|
+
if (Array.isArray(value)) return value.map((item) => sortValue(item));
|
|
29
|
+
if (value instanceof Date) return value.toISOString();
|
|
30
|
+
if (typeof value === "bigint") return value.toString();
|
|
31
|
+
if (value !== null && typeof value === "object") {
|
|
32
|
+
const entries = Object.entries(value).filter(([, item]) => item !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
33
|
+
return Object.fromEntries(entries.map(([key, item]) => [key, sortValue(item)]));
|
|
34
|
+
}
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export { serializeQueryValue as a, stableStringify as b, serializeQuery as s };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { H3Event, EventHandler } from 'h3';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
-
import {
|
|
3
|
+
import { a as AnyApiContract, M as MaybePromise, i as ContractHandlerResponse } from './nuxt-api-contract.CFG8gzJH.mjs';
|
|
4
4
|
|
|
5
5
|
type Infer<T> = T extends z.ZodType ? z.infer<T> : never;
|
|
6
6
|
/**
|
package/dist/shared.d.mts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
export { ContractMock, MockResponseInput, buildRequestPath, clearContractRegistry, defineApiContract, getContractByName, getContractMock, isApiContract, listRegisteredContracts, mockContract,
|
|
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';
|
|
2
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';
|
|
3
|
-
export {
|
|
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, c as createRng, g as generateMockResponse, b as generateMockValue } from './shared/nuxt-api-contract.BOxyDvRy.mjs';
|
|
4
6
|
import 'zod';
|
package/dist/shared.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
export { ContractMock, MockResponseInput, buildRequestPath, clearContractRegistry, defineApiContract, getContractByName, getContractMock, isApiContract, listRegisteredContracts, mockContract,
|
|
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';
|
|
2
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';
|
|
3
|
-
export {
|
|
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, c as createRng, g as generateMockResponse, b as generateMockValue } from './shared/nuxt-api-contract.BuWjAHJe.js';
|
|
4
6
|
import 'zod';
|
package/dist/shared.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
export { b as buildRequestPath, c as clearContractRegistry, d as defineApiContract, g as getContractByName,
|
|
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';
|
|
1
|
+
export { A as ApiError, B as BUILT_IN_ERROR_CODES, b as buildRequestPath, c as clearContractRegistry, a as createApiError, d as defineApiContract, g as getContractByName, e as getContractMock, i as isApiContract, f as isApiError, l as listRegisteredContracts, m as mockContract, p as parseApiErrorPayload, r as registerContract, s as serializeApiError, t as toApiError } from './shared/nuxt-api-contract.CHdRLlU7.mjs';
|
|
3
2
|
export { f as formatValidationMessage, s as sanitizeIssues, t as toValidationIssues } from './shared/nuxt-api-contract.DCAU2j7t.mjs';
|
|
3
|
+
export { s as serializeQuery, a as serializeQueryValue, b as stableStringify } from './shared/nuxt-api-contract.Dr4tPqGB.mjs';
|
|
4
|
+
export { a as autoMockContract, c as createRng, g as generateMockResponse, b as generateMockValue } from './shared/nuxt-api-contract.BWgzVTNN.mjs';
|
package/dist/testing.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { H3Event, EventHandler } from 'h3';
|
|
2
|
-
import { AnyApiContract, ContractClientResponse } from './client.mjs';
|
|
2
|
+
import { AnyApiContract, ContractClientResponse, HttpMethod } from './client.mjs';
|
|
3
3
|
import { ApiError } from './client.mjs';
|
|
4
4
|
import { ContractHandler } from './server.mjs';
|
|
5
5
|
import 'zod';
|
|
@@ -20,9 +20,9 @@ interface ContractHandlerMeta {
|
|
|
20
20
|
body: unknown;
|
|
21
21
|
headers: unknown;
|
|
22
22
|
event: H3Event;
|
|
23
|
-
}) => Awaitable<unknown>;
|
|
23
|
+
}) => Awaitable$1<unknown>;
|
|
24
24
|
}
|
|
25
|
-
type Awaitable<T> = T | Promise<T>;
|
|
25
|
+
type Awaitable$1<T> = T | Promise<T>;
|
|
26
26
|
/**
|
|
27
27
|
* Calls a contract handler pipeline directly (no HTTP server):
|
|
28
28
|
*
|
|
@@ -47,9 +47,114 @@ declare function callContract<C extends AnyApiContract>(contract: C, handler: Co
|
|
|
47
47
|
data: null;
|
|
48
48
|
error: ApiError;
|
|
49
49
|
}>;
|
|
50
|
-
type EventHandlerLike = (ctx: Record<string, unknown>) => Awaitable<unknown>;
|
|
50
|
+
type EventHandlerLike = (ctx: Record<string, unknown>) => Awaitable$1<unknown>;
|
|
51
51
|
/** Exposed so the module-level `defineContractHandler` can attach metadata. */
|
|
52
52
|
declare const contractHandlerMetaKey: symbol;
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
/**
|
|
55
|
+
* `testContract` — a testing suite bound to a contract + handler.
|
|
56
|
+
*
|
|
57
|
+
* Framework-agnostic: every `expect*` method throws a
|
|
58
|
+
* `ContractAssertionError` on failure, so it works with Vitest, Jest,
|
|
59
|
+
* `node:assert` and any other runner that treats thrown errors as failures.
|
|
60
|
+
*
|
|
61
|
+
* ```ts
|
|
62
|
+
* import { testContract } from 'nuxt-api-contract/testing'
|
|
63
|
+
*
|
|
64
|
+
* const user = testContract(GetUser, handler)
|
|
65
|
+
*
|
|
66
|
+
* it('returns the user', async () => {
|
|
67
|
+
* await user.expectSuccess({ params: { id: '1' } })
|
|
68
|
+
* })
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
|
|
72
|
+
type Awaitable<T> = T | Promise<T>;
|
|
73
|
+
type TestHandler<C extends AnyApiContract> = ContractHandler<C> | EventHandler | ((ctx: Record<string, unknown>) => Awaitable<unknown>);
|
|
74
|
+
interface ContractTestInput {
|
|
75
|
+
params?: Record<string, unknown>;
|
|
76
|
+
query?: Record<string, unknown>;
|
|
77
|
+
body?: unknown;
|
|
78
|
+
headers?: Record<string, string>;
|
|
79
|
+
}
|
|
80
|
+
/** Thrown by every failed `testContract` assertion. */
|
|
81
|
+
declare class ContractAssertionError extends Error {
|
|
82
|
+
constructor(message: string);
|
|
83
|
+
}
|
|
84
|
+
interface ContractTestSuite<C extends AnyApiContract> {
|
|
85
|
+
/** Runs the full pipeline (validation -> handler -> response validation). */
|
|
86
|
+
call(input?: ContractTestInput): Promise<{
|
|
87
|
+
data: ContractClientResponse<C> | null;
|
|
88
|
+
error: ApiError | null;
|
|
89
|
+
}>;
|
|
90
|
+
/** Asserts the call succeeds; returns the typed response data. */
|
|
91
|
+
expectSuccess(input?: ContractTestInput): Promise<ContractClientResponse<C>>;
|
|
92
|
+
/** Asserts the call fails with (optionally) the given code and status. */
|
|
93
|
+
expectError(input?: ContractTestInput, code?: string, statusCode?: number): Promise<ApiError>;
|
|
94
|
+
/** Asserts a `VALIDATION_ERROR`; optionally checks that issues cover the given paths. */
|
|
95
|
+
expectValidationError(input?: ContractTestInput, issuePaths?: string[]): Promise<ApiError>;
|
|
96
|
+
/** Asserts the response validation catches a bad handler output. */
|
|
97
|
+
expectResponseValidationError(input: ContractTestInput, badResponse: unknown | ((ctx: Record<string, unknown>) => unknown)): Promise<ApiError>;
|
|
98
|
+
/** Validates an arbitrary value against the contract's response schema. */
|
|
99
|
+
validateResponse(value: unknown): void;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Creates a test suite for a contract + handler pair.
|
|
103
|
+
*
|
|
104
|
+
* ```ts
|
|
105
|
+
* const suite = testContract(GetUser, handler)
|
|
106
|
+
*
|
|
107
|
+
* await suite.expectSuccess({ params: { id: '1' } })
|
|
108
|
+
* await suite.expectError({ params: { id: 'missing' } }, 'USER_NOT_FOUND', 404)
|
|
109
|
+
* await suite.expectValidationError({ params: { id: '' } }, ['params.id'])
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
declare function testContract<C extends AnyApiContract>(contract: C, handler: TestHandler<C>): ContractTestSuite<C>;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Contract coverage tracking: records which registered contracts have been
|
|
116
|
+
* exercised through `callContract` / `testContract` (and with which outcome).
|
|
117
|
+
* Enable it in a test setup hook and print the report in an after-hook:
|
|
118
|
+
*
|
|
119
|
+
* ```ts
|
|
120
|
+
* import { startContractCoverage, formatContractCoverage, getContractCoverage } from 'nuxt-api-contract/testing'
|
|
121
|
+
*
|
|
122
|
+
* beforeAll(() => startContractCoverage())
|
|
123
|
+
* afterAll(() => console.log(formatContractCoverage(getContractCoverage())))
|
|
124
|
+
* ```
|
|
125
|
+
*/
|
|
126
|
+
|
|
127
|
+
interface ContractCoverageEntry {
|
|
128
|
+
/** Contract name, or `METHOD path` for anonymous contracts. */
|
|
129
|
+
key: string;
|
|
130
|
+
name?: string;
|
|
131
|
+
method: HttpMethod;
|
|
132
|
+
path: string;
|
|
133
|
+
calls: number;
|
|
134
|
+
failures: number;
|
|
135
|
+
}
|
|
136
|
+
interface ContractCoverageReport {
|
|
137
|
+
/** Named contracts registered in the registry at report time. */
|
|
138
|
+
total: number;
|
|
139
|
+
coveredCount: number;
|
|
140
|
+
percent: number;
|
|
141
|
+
covered: ContractCoverageEntry[];
|
|
142
|
+
uncovered: Array<{
|
|
143
|
+
name?: string;
|
|
144
|
+
method: HttpMethod;
|
|
145
|
+
path: string;
|
|
146
|
+
}>;
|
|
147
|
+
}
|
|
148
|
+
/** Starts (and resets) coverage recording. */
|
|
149
|
+
declare function startContractCoverage(): void;
|
|
150
|
+
/** Stops recording and returns the final report. */
|
|
151
|
+
declare function stopContractCoverage(): ContractCoverageReport;
|
|
152
|
+
/** Clears recorded stats without changing the active state. */
|
|
153
|
+
declare function resetContractCoverage(): void;
|
|
154
|
+
/** Builds the coverage report against the currently registered contracts. */
|
|
155
|
+
declare function getContractCoverage(): ContractCoverageReport;
|
|
156
|
+
/** Formats the report as a human-readable table for test output. */
|
|
157
|
+
declare function formatContractCoverage(report: ContractCoverageReport): string;
|
|
158
|
+
|
|
159
|
+
export { ContractAssertionError, callContract, contractHandlerMetaKey, formatContractCoverage, getContractCoverage, resetContractCoverage, startContractCoverage, stopContractCoverage, testContract };
|
|
160
|
+
export type { ContractCoverageEntry, ContractCoverageReport, ContractHandlerMeta, ContractTestInput, ContractTestSuite, TestHandler };
|
package/dist/testing.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { H3Event, EventHandler } from 'h3';
|
|
2
|
-
import { AnyApiContract, ContractClientResponse } from './client.js';
|
|
2
|
+
import { AnyApiContract, ContractClientResponse, HttpMethod } from './client.js';
|
|
3
3
|
import { ApiError } from './client.js';
|
|
4
4
|
import { ContractHandler } from './server.js';
|
|
5
5
|
import 'zod';
|
|
@@ -20,9 +20,9 @@ interface ContractHandlerMeta {
|
|
|
20
20
|
body: unknown;
|
|
21
21
|
headers: unknown;
|
|
22
22
|
event: H3Event;
|
|
23
|
-
}) => Awaitable<unknown>;
|
|
23
|
+
}) => Awaitable$1<unknown>;
|
|
24
24
|
}
|
|
25
|
-
type Awaitable<T> = T | Promise<T>;
|
|
25
|
+
type Awaitable$1<T> = T | Promise<T>;
|
|
26
26
|
/**
|
|
27
27
|
* Calls a contract handler pipeline directly (no HTTP server):
|
|
28
28
|
*
|
|
@@ -47,9 +47,114 @@ declare function callContract<C extends AnyApiContract>(contract: C, handler: Co
|
|
|
47
47
|
data: null;
|
|
48
48
|
error: ApiError;
|
|
49
49
|
}>;
|
|
50
|
-
type EventHandlerLike = (ctx: Record<string, unknown>) => Awaitable<unknown>;
|
|
50
|
+
type EventHandlerLike = (ctx: Record<string, unknown>) => Awaitable$1<unknown>;
|
|
51
51
|
/** Exposed so the module-level `defineContractHandler` can attach metadata. */
|
|
52
52
|
declare const contractHandlerMetaKey: symbol;
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
/**
|
|
55
|
+
* `testContract` — a testing suite bound to a contract + handler.
|
|
56
|
+
*
|
|
57
|
+
* Framework-agnostic: every `expect*` method throws a
|
|
58
|
+
* `ContractAssertionError` on failure, so it works with Vitest, Jest,
|
|
59
|
+
* `node:assert` and any other runner that treats thrown errors as failures.
|
|
60
|
+
*
|
|
61
|
+
* ```ts
|
|
62
|
+
* import { testContract } from 'nuxt-api-contract/testing'
|
|
63
|
+
*
|
|
64
|
+
* const user = testContract(GetUser, handler)
|
|
65
|
+
*
|
|
66
|
+
* it('returns the user', async () => {
|
|
67
|
+
* await user.expectSuccess({ params: { id: '1' } })
|
|
68
|
+
* })
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
|
|
72
|
+
type Awaitable<T> = T | Promise<T>;
|
|
73
|
+
type TestHandler<C extends AnyApiContract> = ContractHandler<C> | EventHandler | ((ctx: Record<string, unknown>) => Awaitable<unknown>);
|
|
74
|
+
interface ContractTestInput {
|
|
75
|
+
params?: Record<string, unknown>;
|
|
76
|
+
query?: Record<string, unknown>;
|
|
77
|
+
body?: unknown;
|
|
78
|
+
headers?: Record<string, string>;
|
|
79
|
+
}
|
|
80
|
+
/** Thrown by every failed `testContract` assertion. */
|
|
81
|
+
declare class ContractAssertionError extends Error {
|
|
82
|
+
constructor(message: string);
|
|
83
|
+
}
|
|
84
|
+
interface ContractTestSuite<C extends AnyApiContract> {
|
|
85
|
+
/** Runs the full pipeline (validation -> handler -> response validation). */
|
|
86
|
+
call(input?: ContractTestInput): Promise<{
|
|
87
|
+
data: ContractClientResponse<C> | null;
|
|
88
|
+
error: ApiError | null;
|
|
89
|
+
}>;
|
|
90
|
+
/** Asserts the call succeeds; returns the typed response data. */
|
|
91
|
+
expectSuccess(input?: ContractTestInput): Promise<ContractClientResponse<C>>;
|
|
92
|
+
/** Asserts the call fails with (optionally) the given code and status. */
|
|
93
|
+
expectError(input?: ContractTestInput, code?: string, statusCode?: number): Promise<ApiError>;
|
|
94
|
+
/** Asserts a `VALIDATION_ERROR`; optionally checks that issues cover the given paths. */
|
|
95
|
+
expectValidationError(input?: ContractTestInput, issuePaths?: string[]): Promise<ApiError>;
|
|
96
|
+
/** Asserts the response validation catches a bad handler output. */
|
|
97
|
+
expectResponseValidationError(input: ContractTestInput, badResponse: unknown | ((ctx: Record<string, unknown>) => unknown)): Promise<ApiError>;
|
|
98
|
+
/** Validates an arbitrary value against the contract's response schema. */
|
|
99
|
+
validateResponse(value: unknown): void;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Creates a test suite for a contract + handler pair.
|
|
103
|
+
*
|
|
104
|
+
* ```ts
|
|
105
|
+
* const suite = testContract(GetUser, handler)
|
|
106
|
+
*
|
|
107
|
+
* await suite.expectSuccess({ params: { id: '1' } })
|
|
108
|
+
* await suite.expectError({ params: { id: 'missing' } }, 'USER_NOT_FOUND', 404)
|
|
109
|
+
* await suite.expectValidationError({ params: { id: '' } }, ['params.id'])
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
declare function testContract<C extends AnyApiContract>(contract: C, handler: TestHandler<C>): ContractTestSuite<C>;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Contract coverage tracking: records which registered contracts have been
|
|
116
|
+
* exercised through `callContract` / `testContract` (and with which outcome).
|
|
117
|
+
* Enable it in a test setup hook and print the report in an after-hook:
|
|
118
|
+
*
|
|
119
|
+
* ```ts
|
|
120
|
+
* import { startContractCoverage, formatContractCoverage, getContractCoverage } from 'nuxt-api-contract/testing'
|
|
121
|
+
*
|
|
122
|
+
* beforeAll(() => startContractCoverage())
|
|
123
|
+
* afterAll(() => console.log(formatContractCoverage(getContractCoverage())))
|
|
124
|
+
* ```
|
|
125
|
+
*/
|
|
126
|
+
|
|
127
|
+
interface ContractCoverageEntry {
|
|
128
|
+
/** Contract name, or `METHOD path` for anonymous contracts. */
|
|
129
|
+
key: string;
|
|
130
|
+
name?: string;
|
|
131
|
+
method: HttpMethod;
|
|
132
|
+
path: string;
|
|
133
|
+
calls: number;
|
|
134
|
+
failures: number;
|
|
135
|
+
}
|
|
136
|
+
interface ContractCoverageReport {
|
|
137
|
+
/** Named contracts registered in the registry at report time. */
|
|
138
|
+
total: number;
|
|
139
|
+
coveredCount: number;
|
|
140
|
+
percent: number;
|
|
141
|
+
covered: ContractCoverageEntry[];
|
|
142
|
+
uncovered: Array<{
|
|
143
|
+
name?: string;
|
|
144
|
+
method: HttpMethod;
|
|
145
|
+
path: string;
|
|
146
|
+
}>;
|
|
147
|
+
}
|
|
148
|
+
/** Starts (and resets) coverage recording. */
|
|
149
|
+
declare function startContractCoverage(): void;
|
|
150
|
+
/** Stops recording and returns the final report. */
|
|
151
|
+
declare function stopContractCoverage(): ContractCoverageReport;
|
|
152
|
+
/** Clears recorded stats without changing the active state. */
|
|
153
|
+
declare function resetContractCoverage(): void;
|
|
154
|
+
/** Builds the coverage report against the currently registered contracts. */
|
|
155
|
+
declare function getContractCoverage(): ContractCoverageReport;
|
|
156
|
+
/** Formats the report as a human-readable table for test output. */
|
|
157
|
+
declare function formatContractCoverage(report: ContractCoverageReport): string;
|
|
158
|
+
|
|
159
|
+
export { ContractAssertionError, callContract, contractHandlerMetaKey, formatContractCoverage, getContractCoverage, resetContractCoverage, startContractCoverage, stopContractCoverage, testContract };
|
|
160
|
+
export type { ContractCoverageEntry, ContractCoverageReport, ContractHandlerMeta, ContractTestInput, ContractTestSuite, TestHandler };
|