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
package/dist/module.d.ts
CHANGED
|
@@ -20,8 +20,14 @@ interface ApiContractModuleOptions {
|
|
|
20
20
|
description?: string;
|
|
21
21
|
output?: string;
|
|
22
22
|
};
|
|
23
|
-
/**
|
|
24
|
-
|
|
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:
|
|
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:
|
|
57
|
+
mocks: mocksEnabled
|
|
56
58
|
});
|
|
57
59
|
addImports([
|
|
58
60
|
{ from: "nuxt-api-contract/client", name: "defineApiContract" },
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { mockContract } from "./contract.mjs";
|
|
2
|
+
function zodDef(schema) {
|
|
3
|
+
return schema._def;
|
|
4
|
+
}
|
|
5
|
+
export function createRng(seed) {
|
|
6
|
+
let state = seed >>> 0;
|
|
7
|
+
return function() {
|
|
8
|
+
state = state + 1831565813 | 0;
|
|
9
|
+
let t = state;
|
|
10
|
+
t = Math.imul(t ^ t >>> 15, t | 1);
|
|
11
|
+
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
|
|
12
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function strHash(value) {
|
|
16
|
+
let hash = 2166136261;
|
|
17
|
+
for (let i = 0; i < value.length; i++) {
|
|
18
|
+
hash ^= value.charCodeAt(i);
|
|
19
|
+
hash = Math.imul(hash, 16777619);
|
|
20
|
+
}
|
|
21
|
+
return hash >>> 0;
|
|
22
|
+
}
|
|
23
|
+
function pick(items, rng) {
|
|
24
|
+
return items[Math.floor(rng() * items.length) % items.length];
|
|
25
|
+
}
|
|
26
|
+
function intBetween(rng, min, max) {
|
|
27
|
+
return Math.floor(min + rng() * (max - min + 1));
|
|
28
|
+
}
|
|
29
|
+
const FIRST_NAMES = ["John", "Jane", "Alex", "Maria", "Ivan", "Elena"];
|
|
30
|
+
const LAST_NAMES = ["Doe", "Smith", "Brown", "Ivanov", "Miller"];
|
|
31
|
+
const WORDS = ["mock", "demo", "sample", "alpha", "beta", "gamma"];
|
|
32
|
+
const MOCK_EPOCH = Date.UTC(2024, 0, 1);
|
|
33
|
+
const YEAR_MS = 365 * 24 * 3600 * 1e3;
|
|
34
|
+
function mockUuid(rng) {
|
|
35
|
+
const hex = () => Math.floor(rng() * 65535).toString(16).padStart(4, "0");
|
|
36
|
+
return `${hex()}${hex()}-4${hex().slice(1)}-8${hex().slice(1)}-4${hex().slice(1)}-${hex()}${hex()}${hex()}`;
|
|
37
|
+
}
|
|
38
|
+
function mockString(key, def, ctx) {
|
|
39
|
+
const lower = key.toLowerCase();
|
|
40
|
+
const checks = def.checks ?? [];
|
|
41
|
+
const check = (kind) => checks.find((c) => c.kind === kind)?.value;
|
|
42
|
+
const format = checks.find((c) => c.kind === "format")?.value;
|
|
43
|
+
const hasCheck = (kind) => checks.some((c) => c.kind === kind);
|
|
44
|
+
let value;
|
|
45
|
+
if (format === "email" || hasCheck("email") || lower.includes("email")) {
|
|
46
|
+
const name = pick(FIRST_NAMES, ctx.rng).toLowerCase();
|
|
47
|
+
value = `${name}.${pick(LAST_NAMES, ctx.rng).toLowerCase()}@example.com`;
|
|
48
|
+
} else if (hasCheck("uuid")) {
|
|
49
|
+
value = mockUuid(ctx.rng);
|
|
50
|
+
} else if (hasCheck("datetime") || lower === "createdat" || lower === "updatedat" || lower.endsWith("date") || lower.endsWith("_at")) {
|
|
51
|
+
value = new Date(MOCK_EPOCH - Math.floor(ctx.rng() * YEAR_MS)).toISOString();
|
|
52
|
+
} else if (hasCheck("url") || lower.endsWith("url") || lower.endsWith("link")) {
|
|
53
|
+
value = `https://example.com/${pick(WORDS, ctx.rng)}/${intBetween(ctx.rng, 1, 999)}`;
|
|
54
|
+
} else if (lower.includes("phone") || lower.includes("tel")) {
|
|
55
|
+
value = `+1 555 010 ${intBetween(ctx.rng, 1e3, 9999)}`;
|
|
56
|
+
} else if (lower === "id" || lower.endsWith("_id") || lower.endsWith("id")) {
|
|
57
|
+
value = `id-${intBetween(ctx.rng, 1, 99999)}`;
|
|
58
|
+
} else if (lower.includes("slug")) {
|
|
59
|
+
value = `${pick(WORDS, ctx.rng)}-${intBetween(ctx.rng, 1, 999)}`;
|
|
60
|
+
} else if (lower === "name" || lower.endsWith("name")) {
|
|
61
|
+
value = `${pick(FIRST_NAMES, ctx.rng)} ${pick(LAST_NAMES, ctx.rng)}`;
|
|
62
|
+
} else if (lower === "title") {
|
|
63
|
+
value = `${pick(WORDS, ctx.rng)} title`;
|
|
64
|
+
} else if (lower.includes("password") || lower.includes("token") || lower.includes("secret")) {
|
|
65
|
+
value = "********";
|
|
66
|
+
} else if (lower.includes("description") || lower === "bio" || lower === "text") {
|
|
67
|
+
value = `Mock ${pick(WORDS, ctx.rng)} description for automated testing.`;
|
|
68
|
+
} else {
|
|
69
|
+
value = `${pick(WORDS, ctx.rng)}-${intBetween(ctx.rng, 1, 9999)}`;
|
|
70
|
+
}
|
|
71
|
+
const minLength = check("min");
|
|
72
|
+
if (minLength !== void 0) {
|
|
73
|
+
while (value.length < minLength) value += "-filler";
|
|
74
|
+
}
|
|
75
|
+
const maxLength = check("max");
|
|
76
|
+
if (maxLength !== void 0 && value.length > maxLength) {
|
|
77
|
+
value = value.slice(0, maxLength);
|
|
78
|
+
}
|
|
79
|
+
return value;
|
|
80
|
+
}
|
|
81
|
+
function mockNumber(def, ctx) {
|
|
82
|
+
const checks = def.checks ?? [];
|
|
83
|
+
const isInt = checks.some((c) => c.kind === "int");
|
|
84
|
+
const min = checks.find((c) => c.kind === "min")?.value ?? 1;
|
|
85
|
+
const max = checks.find((c) => c.kind === "max")?.value ?? 100;
|
|
86
|
+
const multipleOf = checks.find((c) => c.kind === "multipleOf")?.value;
|
|
87
|
+
if (multipleOf !== void 0 && multipleOf > 0) {
|
|
88
|
+
const minMul = Math.max(1, Math.ceil(min / multipleOf));
|
|
89
|
+
const maxMul = Math.max(minMul, Math.floor(max / multipleOf));
|
|
90
|
+
return intBetween(ctx.rng, minMul, maxMul) * multipleOf;
|
|
91
|
+
}
|
|
92
|
+
return isInt ? intBetween(ctx.rng, min, max) : Math.round((min + ctx.rng() * (max - min)) * 100) / 100;
|
|
93
|
+
}
|
|
94
|
+
function singular(key) {
|
|
95
|
+
return key.endsWith("s") && key.length > 1 ? key.slice(0, -1) : key;
|
|
96
|
+
}
|
|
97
|
+
export function generateMockValue(schema, key, ctx) {
|
|
98
|
+
if (ctx.depth > 6) return "mock";
|
|
99
|
+
const def = zodDef(schema);
|
|
100
|
+
const kind = def.typeName ?? "unknown";
|
|
101
|
+
ctx.depth++;
|
|
102
|
+
try {
|
|
103
|
+
switch (kind) {
|
|
104
|
+
case "ZodString":
|
|
105
|
+
return mockString(key, def, ctx);
|
|
106
|
+
case "ZodNumber":
|
|
107
|
+
return mockNumber(def, ctx);
|
|
108
|
+
case "ZodBoolean":
|
|
109
|
+
return ctx.rng() < 0.75;
|
|
110
|
+
case "ZodDate":
|
|
111
|
+
return new Date(MOCK_EPOCH - Math.floor(ctx.rng() * YEAR_MS));
|
|
112
|
+
case "ZodNull":
|
|
113
|
+
return null;
|
|
114
|
+
case "ZodLiteral":
|
|
115
|
+
return def.value;
|
|
116
|
+
case "ZodEnum":
|
|
117
|
+
case "ZodNativeEnum": {
|
|
118
|
+
const values = def.values ?? [];
|
|
119
|
+
return values.length > 0 ? pick(values, ctx.rng) : "mock";
|
|
120
|
+
}
|
|
121
|
+
case "ZodArray": {
|
|
122
|
+
const element = def.type ?? def.innerType;
|
|
123
|
+
const count = intBetween(ctx.rng, 1, 3);
|
|
124
|
+
const result = [];
|
|
125
|
+
for (let i = 0; i < count; i++) {
|
|
126
|
+
result.push(element ? generateMockValue(element, singular(key), ctx) : null);
|
|
127
|
+
}
|
|
128
|
+
return result;
|
|
129
|
+
}
|
|
130
|
+
case "ZodObject": {
|
|
131
|
+
const shape = typeof def.shape === "function" ? def.shape() : def.shape;
|
|
132
|
+
const result = {};
|
|
133
|
+
for (const [childKey, child] of Object.entries(shape ?? {})) {
|
|
134
|
+
result[childKey] = generateMockValue(child, childKey, ctx);
|
|
135
|
+
}
|
|
136
|
+
return result;
|
|
137
|
+
}
|
|
138
|
+
case "ZodUnion":
|
|
139
|
+
case "ZodDiscriminatedUnion": {
|
|
140
|
+
const options = def.options ?? [];
|
|
141
|
+
return options.length > 0 ? generateMockValue(pick(options, ctx.rng), key, ctx) : "mock";
|
|
142
|
+
}
|
|
143
|
+
case "ZodIntersection":
|
|
144
|
+
return def.left ? generateMockValue(def.left, key, ctx) : "mock";
|
|
145
|
+
case "ZodRecord": {
|
|
146
|
+
return { [`mock${key}`]: def.valueType ? generateMockValue(def.valueType, key, ctx) : "mock" };
|
|
147
|
+
}
|
|
148
|
+
case "ZodTuple": {
|
|
149
|
+
return (def.items ?? []).map((item, index) => generateMockValue(item, `${key}${index}`, ctx));
|
|
150
|
+
}
|
|
151
|
+
case "ZodOptional":
|
|
152
|
+
case "ZodCatch":
|
|
153
|
+
case "ZodBranded": {
|
|
154
|
+
return def.innerType ? generateMockValue(def.innerType, key, ctx) : "mock";
|
|
155
|
+
}
|
|
156
|
+
case "ZodNullable": {
|
|
157
|
+
return def.innerType ? generateMockValue(def.innerType, key, ctx) : null;
|
|
158
|
+
}
|
|
159
|
+
case "ZodDefault": {
|
|
160
|
+
try {
|
|
161
|
+
return typeof def.defaultValue === "function" ? def.defaultValue() : def.defaultValue;
|
|
162
|
+
} catch {
|
|
163
|
+
return def.innerType ? generateMockValue(def.innerType, key, ctx) : "mock";
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
case "ZodEffects": {
|
|
167
|
+
const inner = def.schema ?? def.innerType;
|
|
168
|
+
return inner ? generateMockValue(inner, key, ctx) : "mock";
|
|
169
|
+
}
|
|
170
|
+
default:
|
|
171
|
+
return "mock";
|
|
172
|
+
}
|
|
173
|
+
} finally {
|
|
174
|
+
ctx.depth--;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
export function generateMockResponse(contract, options) {
|
|
178
|
+
const seed = ((options?.seed ?? 42) ^ strHash(contract.name ?? contract.path)) >>> 0;
|
|
179
|
+
const ctx = { rng: createRng(seed), depth: 0 };
|
|
180
|
+
if (!contract.response) return {};
|
|
181
|
+
return generateMockValue(contract.response, contract.name ?? "response", ctx);
|
|
182
|
+
}
|
|
183
|
+
export function autoMockContract(contract, options) {
|
|
184
|
+
mockContract(contract, {
|
|
185
|
+
response: () => generateMockResponse(contract, options),
|
|
186
|
+
delay: options?.delay
|
|
187
|
+
});
|
|
188
|
+
}
|
package/dist/server.d.mts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
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
|
-
|
|
4
|
-
|
|
5
|
-
export {
|
|
3
|
+
export { s as serializeQuery, a as serializeQueryValue, b as stableStringify } from './shared/nuxt-api-contract.DZfOzVaB.mjs';
|
|
4
|
+
import { a as AnyApiContract } from './shared/nuxt-api-contract.CFG8gzJH.mjs';
|
|
5
|
+
export { A as API_CONTRACT_KIND, 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';
|
|
6
|
+
export { C as ContractHandler, a as ContractHandlerContext, d as defineContractHandler } from './shared/nuxt-api-contract.VCevNWQV.mjs';
|
|
6
7
|
import { z, ZodType } from 'zod';
|
|
7
8
|
import 'h3';
|
|
8
9
|
|
|
@@ -10,6 +11,8 @@ type ResponseValidationMode = 'never' | 'development' | 'always';
|
|
|
10
11
|
interface RuntimeApiContractConfig {
|
|
11
12
|
validateResponse?: ResponseValidationMode;
|
|
12
13
|
mocks?: boolean;
|
|
14
|
+
/** Generate mock responses for contracts without an explicit mock. */
|
|
15
|
+
mocksAuto?: boolean;
|
|
13
16
|
}
|
|
14
17
|
/** Reads runtime contract config from the current Nitro runtime config. */
|
|
15
18
|
declare function readRuntimeConfig(getConfig: () => unknown): RuntimeApiContractConfig;
|
package/dist/server.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
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
|
-
|
|
4
|
-
|
|
5
|
-
export {
|
|
3
|
+
export { s as serializeQuery, a as serializeQueryValue, b as stableStringify } from './shared/nuxt-api-contract.DZfOzVaB.js';
|
|
4
|
+
import { a as AnyApiContract } from './shared/nuxt-api-contract.CFG8gzJH.js';
|
|
5
|
+
export { A as API_CONTRACT_KIND, 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';
|
|
6
|
+
export { C as ContractHandler, a as ContractHandlerContext, d as defineContractHandler } from './shared/nuxt-api-contract.B_UvrNC8.js';
|
|
6
7
|
import { z, ZodType } from 'zod';
|
|
7
8
|
import 'h3';
|
|
8
9
|
|
|
@@ -10,6 +11,8 @@ type ResponseValidationMode = 'never' | 'development' | 'always';
|
|
|
10
11
|
interface RuntimeApiContractConfig {
|
|
11
12
|
validateResponse?: ResponseValidationMode;
|
|
12
13
|
mocks?: boolean;
|
|
14
|
+
/** Generate mock responses for contracts without an explicit mock. */
|
|
15
|
+
mocksAuto?: boolean;
|
|
13
16
|
}
|
|
14
17
|
/** Reads runtime contract config from the current Nitro runtime config. */
|
|
15
18
|
declare function readRuntimeConfig(getConfig: () => unknown): RuntimeApiContractConfig;
|
package/dist/server.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { b as buildRequestPath, c as clearContractRegistry, d as defineApiContract, g as getContractByName, i as isApiContract,
|
|
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';
|
|
1
|
+
import { e as getContractMock, A as ApiError, s as serializeApiError, B as BUILT_IN_ERROR_CODES } from './shared/nuxt-api-contract.CHdRLlU7.mjs';
|
|
2
|
+
export { b as buildRequestPath, c as clearContractRegistry, a as createApiError, d as defineApiContract, g as getContractByName, i as isApiContract, f as isApiError, l as listRegisteredContracts, m as mockContract, p as parseApiErrorPayload, r as registerContract, t as toApiError } from './shared/nuxt-api-contract.CHdRLlU7.mjs';
|
|
5
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';
|
|
6
5
|
import { defineEventHandler, getRequestHeaders, getQuery, readValidatedBody, setResponseStatus } from 'h3';
|
|
7
|
-
import {
|
|
8
|
-
|
|
6
|
+
import { g as generateMockResponse } from './shared/nuxt-api-contract.BWgzVTNN.mjs';
|
|
7
|
+
import { v as validateContractInput, r as readRuntimeConfig, s as shouldValidateResponse, a as validateContractResponse } from './shared/nuxt-api-contract.CAIOgncy.mjs';
|
|
8
|
+
export { b as rawQuerySchema } from './shared/nuxt-api-contract.CAIOgncy.mjs';
|
|
9
9
|
import 'zod';
|
|
10
10
|
|
|
11
11
|
const CONTRACT_HANDLER_META = Symbol.for("nuxt-api-contract.contractHandlerMeta");
|
|
@@ -52,7 +52,8 @@ function defineContractHandler(contract, handler) {
|
|
|
52
52
|
event
|
|
53
53
|
};
|
|
54
54
|
const runtimeConfig = readRuntimeConfig(() => useNitroRuntimeConfig(event));
|
|
55
|
-
const
|
|
55
|
+
const explicitMock = getContractMock(contract);
|
|
56
|
+
const mock = explicitMock ?? (runtimeConfig.mocksAuto ? { response: () => generateMockResponse(contract) } : void 0);
|
|
56
57
|
if (runtimeConfig.mocks && mock?.response) {
|
|
57
58
|
const mocked = await mock.response();
|
|
58
59
|
return finalize(contract, mocked, runtimeConfig);
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { ZodType } from 'zod';
|
|
2
|
+
import { a as AnyApiContract, i as ContractHandlerResponse } from './nuxt-api-contract.CFG8gzJH.mjs';
|
|
3
|
+
import { C as ContractMock } from './nuxt-api-contract.BgPd-YXc.mjs';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Deterministic mock-data generation from contract schemas (client-safe:
|
|
7
|
+
* only depends on zod). Used by `autoMockContract`, the `apiContract.mocks:
|
|
8
|
+
* 'auto'` mode and the standalone mock server.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
interface MockGenerateOptions {
|
|
12
|
+
/** Seed for deterministic generation (default: 42). */
|
|
13
|
+
seed?: number;
|
|
14
|
+
}
|
|
15
|
+
interface RngContext {
|
|
16
|
+
rng: () => number;
|
|
17
|
+
depth: number;
|
|
18
|
+
}
|
|
19
|
+
/** Mulberry32 — small, fast, deterministic PRNG. */
|
|
20
|
+
declare function createRng(seed: number): () => number;
|
|
21
|
+
/** Walks a Zod schema and produces a plausible mock value. */
|
|
22
|
+
declare function generateMockValue(schema: ZodType, key: string, ctx: RngContext): unknown;
|
|
23
|
+
/**
|
|
24
|
+
* Generates a mock response matching the contract's `response` schema.
|
|
25
|
+
* Deterministic for a given contract + seed.
|
|
26
|
+
*/
|
|
27
|
+
declare function generateMockResponse<C extends AnyApiContract>(contract: C, options?: MockGenerateOptions): ContractHandlerResponse<C>;
|
|
28
|
+
/**
|
|
29
|
+
* Registers a generated mock for a contract — the "mock preset" produced
|
|
30
|
+
* from the contract itself:
|
|
31
|
+
*
|
|
32
|
+
* ```ts
|
|
33
|
+
* import { autoMockContract } from 'nuxt-api-contract/client'
|
|
34
|
+
* autoMockContract(GetUser) // response generated from the schema
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
declare function autoMockContract<C extends AnyApiContract>(contract: C, options?: MockGenerateOptions & Pick<ContractMock<C>, 'delay'>): void;
|
|
38
|
+
|
|
39
|
+
export { autoMockContract as a, generateMockValue as b, createRng as c, generateMockResponse as g };
|
|
40
|
+
export type { MockGenerateOptions as M };
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { m as mockContract } from './nuxt-api-contract.CHdRLlU7.mjs';
|
|
2
|
+
|
|
3
|
+
function zodDef(schema) {
|
|
4
|
+
return schema._def;
|
|
5
|
+
}
|
|
6
|
+
function createRng(seed) {
|
|
7
|
+
let state = seed >>> 0;
|
|
8
|
+
return function() {
|
|
9
|
+
state = state + 1831565813 | 0;
|
|
10
|
+
let t = state;
|
|
11
|
+
t = Math.imul(t ^ t >>> 15, t | 1);
|
|
12
|
+
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
|
|
13
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function strHash(value) {
|
|
17
|
+
let hash = 2166136261;
|
|
18
|
+
for (let i = 0; i < value.length; i++) {
|
|
19
|
+
hash ^= value.charCodeAt(i);
|
|
20
|
+
hash = Math.imul(hash, 16777619);
|
|
21
|
+
}
|
|
22
|
+
return hash >>> 0;
|
|
23
|
+
}
|
|
24
|
+
function pick(items, rng) {
|
|
25
|
+
return items[Math.floor(rng() * items.length) % items.length];
|
|
26
|
+
}
|
|
27
|
+
function intBetween(rng, min, max) {
|
|
28
|
+
return Math.floor(min + rng() * (max - min + 1));
|
|
29
|
+
}
|
|
30
|
+
const FIRST_NAMES = ["John", "Jane", "Alex", "Maria", "Ivan", "Elena"];
|
|
31
|
+
const LAST_NAMES = ["Doe", "Smith", "Brown", "Ivanov", "Miller"];
|
|
32
|
+
const WORDS = ["mock", "demo", "sample", "alpha", "beta", "gamma"];
|
|
33
|
+
const MOCK_EPOCH = Date.UTC(2024, 0, 1);
|
|
34
|
+
const YEAR_MS = 365 * 24 * 3600 * 1e3;
|
|
35
|
+
function mockUuid(rng) {
|
|
36
|
+
const hex = () => Math.floor(rng() * 65535).toString(16).padStart(4, "0");
|
|
37
|
+
return `${hex()}${hex()}-4${hex().slice(1)}-8${hex().slice(1)}-4${hex().slice(1)}-${hex()}${hex()}${hex()}`;
|
|
38
|
+
}
|
|
39
|
+
function mockString(key, def, ctx) {
|
|
40
|
+
const lower = key.toLowerCase();
|
|
41
|
+
const checks = def.checks ?? [];
|
|
42
|
+
const check = (kind) => checks.find((c) => c.kind === kind)?.value;
|
|
43
|
+
const format = checks.find((c) => c.kind === "format")?.value;
|
|
44
|
+
const hasCheck = (kind) => checks.some((c) => c.kind === kind);
|
|
45
|
+
let value;
|
|
46
|
+
if (format === "email" || hasCheck("email") || lower.includes("email")) {
|
|
47
|
+
const name = pick(FIRST_NAMES, ctx.rng).toLowerCase();
|
|
48
|
+
value = `${name}.${pick(LAST_NAMES, ctx.rng).toLowerCase()}@example.com`;
|
|
49
|
+
} else if (hasCheck("uuid")) {
|
|
50
|
+
value = mockUuid(ctx.rng);
|
|
51
|
+
} else if (hasCheck("datetime") || lower === "createdat" || lower === "updatedat" || lower.endsWith("date") || lower.endsWith("_at")) {
|
|
52
|
+
value = new Date(MOCK_EPOCH - Math.floor(ctx.rng() * YEAR_MS)).toISOString();
|
|
53
|
+
} else if (hasCheck("url") || lower.endsWith("url") || lower.endsWith("link")) {
|
|
54
|
+
value = `https://example.com/${pick(WORDS, ctx.rng)}/${intBetween(ctx.rng, 1, 999)}`;
|
|
55
|
+
} else if (lower.includes("phone") || lower.includes("tel")) {
|
|
56
|
+
value = `+1 555 010 ${intBetween(ctx.rng, 1e3, 9999)}`;
|
|
57
|
+
} else if (lower === "id" || lower.endsWith("_id") || lower.endsWith("id")) {
|
|
58
|
+
value = `id-${intBetween(ctx.rng, 1, 99999)}`;
|
|
59
|
+
} else if (lower.includes("slug")) {
|
|
60
|
+
value = `${pick(WORDS, ctx.rng)}-${intBetween(ctx.rng, 1, 999)}`;
|
|
61
|
+
} else if (lower === "name" || lower.endsWith("name")) {
|
|
62
|
+
value = `${pick(FIRST_NAMES, ctx.rng)} ${pick(LAST_NAMES, ctx.rng)}`;
|
|
63
|
+
} else if (lower === "title") {
|
|
64
|
+
value = `${pick(WORDS, ctx.rng)} title`;
|
|
65
|
+
} else if (lower.includes("password") || lower.includes("token") || lower.includes("secret")) {
|
|
66
|
+
value = "********";
|
|
67
|
+
} else if (lower.includes("description") || lower === "bio" || lower === "text") {
|
|
68
|
+
value = `Mock ${pick(WORDS, ctx.rng)} description for automated testing.`;
|
|
69
|
+
} else {
|
|
70
|
+
value = `${pick(WORDS, ctx.rng)}-${intBetween(ctx.rng, 1, 9999)}`;
|
|
71
|
+
}
|
|
72
|
+
const minLength = check("min");
|
|
73
|
+
if (minLength !== void 0) {
|
|
74
|
+
while (value.length < minLength) value += "-filler";
|
|
75
|
+
}
|
|
76
|
+
const maxLength = check("max");
|
|
77
|
+
if (maxLength !== void 0 && value.length > maxLength) {
|
|
78
|
+
value = value.slice(0, maxLength);
|
|
79
|
+
}
|
|
80
|
+
return value;
|
|
81
|
+
}
|
|
82
|
+
function mockNumber(def, ctx) {
|
|
83
|
+
const checks = def.checks ?? [];
|
|
84
|
+
const isInt = checks.some((c) => c.kind === "int");
|
|
85
|
+
const min = checks.find((c) => c.kind === "min")?.value ?? 1;
|
|
86
|
+
const max = checks.find((c) => c.kind === "max")?.value ?? 100;
|
|
87
|
+
const multipleOf = checks.find((c) => c.kind === "multipleOf")?.value;
|
|
88
|
+
if (multipleOf !== void 0 && multipleOf > 0) {
|
|
89
|
+
const minMul = Math.max(1, Math.ceil(min / multipleOf));
|
|
90
|
+
const maxMul = Math.max(minMul, Math.floor(max / multipleOf));
|
|
91
|
+
return intBetween(ctx.rng, minMul, maxMul) * multipleOf;
|
|
92
|
+
}
|
|
93
|
+
return isInt ? intBetween(ctx.rng, min, max) : Math.round((min + ctx.rng() * (max - min)) * 100) / 100;
|
|
94
|
+
}
|
|
95
|
+
function singular(key) {
|
|
96
|
+
return key.endsWith("s") && key.length > 1 ? key.slice(0, -1) : key;
|
|
97
|
+
}
|
|
98
|
+
function generateMockValue(schema, key, ctx) {
|
|
99
|
+
if (ctx.depth > 6) return "mock";
|
|
100
|
+
const def = zodDef(schema);
|
|
101
|
+
const kind = def.typeName ?? "unknown";
|
|
102
|
+
ctx.depth++;
|
|
103
|
+
try {
|
|
104
|
+
switch (kind) {
|
|
105
|
+
case "ZodString":
|
|
106
|
+
return mockString(key, def, ctx);
|
|
107
|
+
case "ZodNumber":
|
|
108
|
+
return mockNumber(def, ctx);
|
|
109
|
+
case "ZodBoolean":
|
|
110
|
+
return ctx.rng() < 0.75;
|
|
111
|
+
case "ZodDate":
|
|
112
|
+
return new Date(MOCK_EPOCH - Math.floor(ctx.rng() * YEAR_MS));
|
|
113
|
+
case "ZodNull":
|
|
114
|
+
return null;
|
|
115
|
+
case "ZodLiteral":
|
|
116
|
+
return def.value;
|
|
117
|
+
case "ZodEnum":
|
|
118
|
+
case "ZodNativeEnum": {
|
|
119
|
+
const values = def.values ?? [];
|
|
120
|
+
return values.length > 0 ? pick(values, ctx.rng) : "mock";
|
|
121
|
+
}
|
|
122
|
+
case "ZodArray": {
|
|
123
|
+
const element = def.type ?? def.innerType;
|
|
124
|
+
const count = intBetween(ctx.rng, 1, 3);
|
|
125
|
+
const result = [];
|
|
126
|
+
for (let i = 0; i < count; i++) {
|
|
127
|
+
result.push(element ? generateMockValue(element, singular(key), ctx) : null);
|
|
128
|
+
}
|
|
129
|
+
return result;
|
|
130
|
+
}
|
|
131
|
+
case "ZodObject": {
|
|
132
|
+
const shape = typeof def.shape === "function" ? def.shape() : def.shape;
|
|
133
|
+
const result = {};
|
|
134
|
+
for (const [childKey, child] of Object.entries(shape ?? {})) {
|
|
135
|
+
result[childKey] = generateMockValue(child, childKey, ctx);
|
|
136
|
+
}
|
|
137
|
+
return result;
|
|
138
|
+
}
|
|
139
|
+
case "ZodUnion":
|
|
140
|
+
case "ZodDiscriminatedUnion": {
|
|
141
|
+
const options = def.options ?? [];
|
|
142
|
+
return options.length > 0 ? generateMockValue(pick(options, ctx.rng), key, ctx) : "mock";
|
|
143
|
+
}
|
|
144
|
+
case "ZodIntersection":
|
|
145
|
+
return def.left ? generateMockValue(def.left, key, ctx) : "mock";
|
|
146
|
+
case "ZodRecord": {
|
|
147
|
+
return { [`mock${key}`]: def.valueType ? generateMockValue(def.valueType, key, ctx) : "mock" };
|
|
148
|
+
}
|
|
149
|
+
case "ZodTuple": {
|
|
150
|
+
return (def.items ?? []).map((item, index) => generateMockValue(item, `${key}${index}`, ctx));
|
|
151
|
+
}
|
|
152
|
+
case "ZodOptional":
|
|
153
|
+
case "ZodCatch":
|
|
154
|
+
case "ZodBranded": {
|
|
155
|
+
return def.innerType ? generateMockValue(def.innerType, key, ctx) : "mock";
|
|
156
|
+
}
|
|
157
|
+
case "ZodNullable": {
|
|
158
|
+
return def.innerType ? generateMockValue(def.innerType, key, ctx) : null;
|
|
159
|
+
}
|
|
160
|
+
case "ZodDefault": {
|
|
161
|
+
try {
|
|
162
|
+
return typeof def.defaultValue === "function" ? def.defaultValue() : def.defaultValue;
|
|
163
|
+
} catch {
|
|
164
|
+
return def.innerType ? generateMockValue(def.innerType, key, ctx) : "mock";
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
case "ZodEffects": {
|
|
168
|
+
const inner = def.schema ?? def.innerType;
|
|
169
|
+
return inner ? generateMockValue(inner, key, ctx) : "mock";
|
|
170
|
+
}
|
|
171
|
+
default:
|
|
172
|
+
return "mock";
|
|
173
|
+
}
|
|
174
|
+
} finally {
|
|
175
|
+
ctx.depth--;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
function generateMockResponse(contract, options) {
|
|
179
|
+
const seed = ((options?.seed ?? 42) ^ strHash(contract.name ?? contract.path)) >>> 0;
|
|
180
|
+
const ctx = { rng: createRng(seed), depth: 0 };
|
|
181
|
+
if (!contract.response) return {};
|
|
182
|
+
return generateMockValue(contract.response, contract.name ?? "response", ctx);
|
|
183
|
+
}
|
|
184
|
+
function autoMockContract(contract, options) {
|
|
185
|
+
mockContract(contract, {
|
|
186
|
+
response: () => generateMockResponse(contract, options),
|
|
187
|
+
delay: options?.delay
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export { autoMockContract as a, generateMockValue as b, createRng as c, generateMockResponse as g };
|
|
@@ -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.js';
|
|
4
4
|
|
|
5
5
|
type Infer<T> = T extends z.ZodType ? z.infer<T> : never;
|
|
6
6
|
/**
|
|
@@ -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.mjs';
|
|
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 };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { ZodType } from 'zod';
|
|
2
|
+
import { a as AnyApiContract, i as ContractHandlerResponse } from './nuxt-api-contract.CFG8gzJH.js';
|
|
3
|
+
import { C as ContractMock } from './nuxt-api-contract.C7KxMQHa.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Deterministic mock-data generation from contract schemas (client-safe:
|
|
7
|
+
* only depends on zod). Used by `autoMockContract`, the `apiContract.mocks:
|
|
8
|
+
* 'auto'` mode and the standalone mock server.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
interface MockGenerateOptions {
|
|
12
|
+
/** Seed for deterministic generation (default: 42). */
|
|
13
|
+
seed?: number;
|
|
14
|
+
}
|
|
15
|
+
interface RngContext {
|
|
16
|
+
rng: () => number;
|
|
17
|
+
depth: number;
|
|
18
|
+
}
|
|
19
|
+
/** Mulberry32 — small, fast, deterministic PRNG. */
|
|
20
|
+
declare function createRng(seed: number): () => number;
|
|
21
|
+
/** Walks a Zod schema and produces a plausible mock value. */
|
|
22
|
+
declare function generateMockValue(schema: ZodType, key: string, ctx: RngContext): unknown;
|
|
23
|
+
/**
|
|
24
|
+
* Generates a mock response matching the contract's `response` schema.
|
|
25
|
+
* Deterministic for a given contract + seed.
|
|
26
|
+
*/
|
|
27
|
+
declare function generateMockResponse<C extends AnyApiContract>(contract: C, options?: MockGenerateOptions): ContractHandlerResponse<C>;
|
|
28
|
+
/**
|
|
29
|
+
* Registers a generated mock for a contract — the "mock preset" produced
|
|
30
|
+
* from the contract itself:
|
|
31
|
+
*
|
|
32
|
+
* ```ts
|
|
33
|
+
* import { autoMockContract } from 'nuxt-api-contract/client'
|
|
34
|
+
* autoMockContract(GetUser) // response generated from the schema
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
declare function autoMockContract<C extends AnyApiContract>(contract: C, options?: MockGenerateOptions & Pick<ContractMock<C>, 'delay'>): void;
|
|
38
|
+
|
|
39
|
+
export { autoMockContract as a, generateMockValue as b, createRng as c, generateMockResponse as g };
|
|
40
|
+
export type { MockGenerateOptions as M };
|