@kubb/plugin-msw 6.2.0-canary.20261111T122007 → 6.2.1
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/dist/index.cjs +365 -210
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -2
- package/dist/index.d.ts +1 -2
- package/dist/index.js +366 -211
- package/dist/index.js.map +1 -1
- package/docs/guides/mock-api.md +17 -12
- package/docs/options.md +14 -8
- package/docs/overview.md +1 -1
- package/kubb-extension.json +1 -1
- package/package.json +7 -7
package/dist/index.cjs
CHANGED
|
@@ -7,6 +7,238 @@ let _kubb_plugin_faker = require("@kubb/plugin-faker");
|
|
|
7
7
|
let _kubb_plugin_ts = require("@kubb/plugin-ts");
|
|
8
8
|
let _kubb_renderer_jsx = require("@kubb/renderer-jsx");
|
|
9
9
|
let _kubb_renderer_jsx_jsx_runtime = require("@kubb/renderer-jsx/jsx-runtime");
|
|
10
|
+
// kubb-extension.json
|
|
11
|
+
var compatibility = {
|
|
12
|
+
"kubb": ">=5.18.4",
|
|
13
|
+
"node": ">=22.22"
|
|
14
|
+
};
|
|
15
|
+
// package.json
|
|
16
|
+
var version = "6.2.1";
|
|
17
|
+
// src/generators/handlersGenerator.ts
|
|
18
|
+
/**
|
|
19
|
+
* Aggregate generator enabled by `pluginMsw({ handlers: true })`. Emits a
|
|
20
|
+
* `handlers.ts` file that re-exports every generated handler in operation
|
|
21
|
+
* order, ready to spread into `setupServer(...handlers)` or
|
|
22
|
+
* `setupWorker(...handlers)`.
|
|
23
|
+
*/
|
|
24
|
+
const handlersGenerator = (0, _kubb_kit.defineGenerator)({
|
|
25
|
+
name: "plugin-msw",
|
|
26
|
+
operations(nodes, ctx) {
|
|
27
|
+
const { resolver, config, root } = ctx;
|
|
28
|
+
const { output, group } = ctx.options;
|
|
29
|
+
const handlersName = resolver.handler.listName();
|
|
30
|
+
const createHandlersName = resolver.resolveName({
|
|
31
|
+
key: ["handlers", "create"],
|
|
32
|
+
name: _kubb_kit.casing.camel(handlersName, { prefix: "create" })
|
|
33
|
+
});
|
|
34
|
+
const file = resolver.file({
|
|
35
|
+
name: handlersName,
|
|
36
|
+
extname: ".ts",
|
|
37
|
+
root,
|
|
38
|
+
output,
|
|
39
|
+
group: group ?? void 0
|
|
40
|
+
});
|
|
41
|
+
const handlerNames = nodes.map((node) => resolver.handler.name(node));
|
|
42
|
+
const imports = nodes.map((node, index) => {
|
|
43
|
+
const operationName = handlerNames[index];
|
|
44
|
+
const operationFile = resolver.file({
|
|
45
|
+
name: resolver.name(node.operationId),
|
|
46
|
+
extname: ".ts",
|
|
47
|
+
tag: node.tags[0] ?? "default",
|
|
48
|
+
path: node.path,
|
|
49
|
+
root,
|
|
50
|
+
output,
|
|
51
|
+
group: group ?? void 0
|
|
52
|
+
});
|
|
53
|
+
return _kubb_kit.ast.factory.createImport({
|
|
54
|
+
name: [operationName],
|
|
55
|
+
root: file.path,
|
|
56
|
+
path: operationFile.path
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
const handlers = handlerNames.map((name) => `${name}(undefined, options)`);
|
|
60
|
+
return [_kubb_kit.ast.factory.createFile({
|
|
61
|
+
baseName: file.baseName,
|
|
62
|
+
path: file.path,
|
|
63
|
+
meta: file.meta,
|
|
64
|
+
banner: resolver.default.banner(ctx.meta, {
|
|
65
|
+
output,
|
|
66
|
+
config,
|
|
67
|
+
file: {
|
|
68
|
+
path: file.path,
|
|
69
|
+
baseName: file.baseName
|
|
70
|
+
}
|
|
71
|
+
}),
|
|
72
|
+
footer: resolver.default.footer(ctx.meta, {
|
|
73
|
+
output,
|
|
74
|
+
config,
|
|
75
|
+
file: {
|
|
76
|
+
path: file.path,
|
|
77
|
+
baseName: file.baseName
|
|
78
|
+
}
|
|
79
|
+
}),
|
|
80
|
+
imports,
|
|
81
|
+
sources: [_kubb_kit.ast.factory.createSource({
|
|
82
|
+
name: createHandlersName,
|
|
83
|
+
isIndexable: true,
|
|
84
|
+
isExportable: true,
|
|
85
|
+
nodes: [_kubb_kit.ast.factory.createText(`export function ${createHandlersName}(options?: { baseURL?: string }) {
|
|
86
|
+
return [${handlers.join(", ")}] as const
|
|
87
|
+
}`)]
|
|
88
|
+
}), _kubb_kit.ast.factory.createSource({
|
|
89
|
+
name: handlersName,
|
|
90
|
+
isIndexable: true,
|
|
91
|
+
isExportable: true,
|
|
92
|
+
nodes: [_kubb_kit.ast.factory.createText(`export const ${handlersName} = ${createHandlersName}()`)]
|
|
93
|
+
})]
|
|
94
|
+
})];
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
// ../../internals/utils/src/reserved.ts
|
|
98
|
+
/**
|
|
99
|
+
* JavaScript and Java reserved words.
|
|
100
|
+
* @link https://github.com/jonschlinkert/reserved/blob/master/index.js
|
|
101
|
+
*/
|
|
102
|
+
const reservedWords = /* @__PURE__ */ new Set([
|
|
103
|
+
"abstract",
|
|
104
|
+
"arguments",
|
|
105
|
+
"await",
|
|
106
|
+
"boolean",
|
|
107
|
+
"break",
|
|
108
|
+
"byte",
|
|
109
|
+
"case",
|
|
110
|
+
"catch",
|
|
111
|
+
"char",
|
|
112
|
+
"class",
|
|
113
|
+
"const",
|
|
114
|
+
"continue",
|
|
115
|
+
"debugger",
|
|
116
|
+
"default",
|
|
117
|
+
"delete",
|
|
118
|
+
"do",
|
|
119
|
+
"double",
|
|
120
|
+
"else",
|
|
121
|
+
"enum",
|
|
122
|
+
"eval",
|
|
123
|
+
"export",
|
|
124
|
+
"extends",
|
|
125
|
+
"false",
|
|
126
|
+
"final",
|
|
127
|
+
"finally",
|
|
128
|
+
"float",
|
|
129
|
+
"for",
|
|
130
|
+
"function",
|
|
131
|
+
"goto",
|
|
132
|
+
"if",
|
|
133
|
+
"implements",
|
|
134
|
+
"import",
|
|
135
|
+
"in",
|
|
136
|
+
"instanceof",
|
|
137
|
+
"int",
|
|
138
|
+
"interface",
|
|
139
|
+
"let",
|
|
140
|
+
"long",
|
|
141
|
+
"native",
|
|
142
|
+
"new",
|
|
143
|
+
"null",
|
|
144
|
+
"package",
|
|
145
|
+
"private",
|
|
146
|
+
"protected",
|
|
147
|
+
"public",
|
|
148
|
+
"return",
|
|
149
|
+
"short",
|
|
150
|
+
"static",
|
|
151
|
+
"super",
|
|
152
|
+
"switch",
|
|
153
|
+
"synchronized",
|
|
154
|
+
"this",
|
|
155
|
+
"throw",
|
|
156
|
+
"throws",
|
|
157
|
+
"transient",
|
|
158
|
+
"true",
|
|
159
|
+
"try",
|
|
160
|
+
"typeof",
|
|
161
|
+
"var",
|
|
162
|
+
"void",
|
|
163
|
+
"volatile",
|
|
164
|
+
"while",
|
|
165
|
+
"with",
|
|
166
|
+
"yield",
|
|
167
|
+
"Array",
|
|
168
|
+
"Date",
|
|
169
|
+
"hasOwnProperty",
|
|
170
|
+
"Infinity",
|
|
171
|
+
"isFinite",
|
|
172
|
+
"isNaN",
|
|
173
|
+
"isPrototypeOf",
|
|
174
|
+
"length",
|
|
175
|
+
"Math",
|
|
176
|
+
"name",
|
|
177
|
+
"NaN",
|
|
178
|
+
"Number",
|
|
179
|
+
"Object",
|
|
180
|
+
"prototype",
|
|
181
|
+
"String",
|
|
182
|
+
"toString",
|
|
183
|
+
"undefined",
|
|
184
|
+
"valueOf"
|
|
185
|
+
]);
|
|
186
|
+
/**
|
|
187
|
+
* Returns `true` when `name` is a syntactically valid JavaScript variable name.
|
|
188
|
+
*
|
|
189
|
+
* @example
|
|
190
|
+
* ```ts
|
|
191
|
+
* isValidVarName('status') // true
|
|
192
|
+
* isValidVarName('class') // false (reserved word)
|
|
193
|
+
* isValidVarName('42foo') // false (starts with digit)
|
|
194
|
+
* ```
|
|
195
|
+
*/
|
|
196
|
+
function isValidVarName(name) {
|
|
197
|
+
if (!name || reservedWords.has(name)) return false;
|
|
198
|
+
return isIdentifier(name);
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Returns `true` when `name` is syntactically a valid identifier, ignoring reserved words.
|
|
202
|
+
*
|
|
203
|
+
* Reserved words and globals (`class`, `name`, `Date`, …) are valid as bare object-literal keys
|
|
204
|
+
* even though they are not valid variable names, so use this (not {@link isValidVarName}) when
|
|
205
|
+
* deciding whether an object key needs quoting.
|
|
206
|
+
*
|
|
207
|
+
* @example
|
|
208
|
+
* ```ts
|
|
209
|
+
* isIdentifier('name') // true
|
|
210
|
+
* isIdentifier('x-total')// false
|
|
211
|
+
* ```
|
|
212
|
+
*/
|
|
213
|
+
function isIdentifier(name) {
|
|
214
|
+
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
|
|
215
|
+
}
|
|
216
|
+
// ../../internals/utils/src/codegen.ts
|
|
217
|
+
/**
|
|
218
|
+
* An object key, quoted only when it is not identifier-shaped. Reserved words such as `class` stay bare.
|
|
219
|
+
*
|
|
220
|
+
* @example
|
|
221
|
+
* ```ts
|
|
222
|
+
* objectKey('x-total') // "'x-total'"
|
|
223
|
+
* ```
|
|
224
|
+
*/
|
|
225
|
+
function objectKey(name) {
|
|
226
|
+
return isIdentifier(name) ? name : _kubb_kit.text.quote(name);
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* A JSON value as a JavaScript literal, with single-quoted strings and identifier keys left unquoted.
|
|
230
|
+
*
|
|
231
|
+
* @example
|
|
232
|
+
* ```ts
|
|
233
|
+
* toLiteral({ id: 1, 'x-tag': ['a'] }) // "{ id: 1, 'x-tag': ['a'] }"
|
|
234
|
+
* ```
|
|
235
|
+
*/
|
|
236
|
+
function toLiteral(value) {
|
|
237
|
+
if (typeof value === "string") return _kubb_kit.text.quote(value);
|
|
238
|
+
if (Array.isArray(value)) return `[${value.map(toLiteral).join(", ")}]`;
|
|
239
|
+
if (value && typeof value === "object") return `{ ${Object.entries(value).map(([key, item]) => `${objectKey(key)}: ${toLiteral(item)}`).join(", ")} }`;
|
|
240
|
+
return String(value);
|
|
241
|
+
}
|
|
10
242
|
// ../../internals/shared/src/operation.ts
|
|
11
243
|
/**
|
|
12
244
|
* Builds the `ResolverFileParams` every operation generator passes to
|
|
@@ -72,19 +304,12 @@ function resolveOperationFile(options) {
|
|
|
72
304
|
return file;
|
|
73
305
|
});
|
|
74
306
|
}
|
|
75
|
-
function baseContentType(contentType) {
|
|
76
|
-
return contentType.split(";")[0].trim().toLowerCase();
|
|
77
|
-
}
|
|
78
307
|
/**
|
|
79
|
-
*
|
|
80
|
-
* `application/x-ndjson`, `application/json-seq`, or any media type with an OpenAPI 3.2 `itemSchema`.
|
|
308
|
+
* How a content entry is framed as a stream: `'events'` for Server-Sent Events, `'lines'` for JSON Lines.
|
|
81
309
|
*/
|
|
82
|
-
function
|
|
83
|
-
|
|
84
|
-
return
|
|
85
|
-
}
|
|
86
|
-
function isJsonContentType(baseType) {
|
|
87
|
-
return baseType === "application/json" || baseType.endsWith("+json") || baseType === "text/json";
|
|
310
|
+
function getStreamFraming(entry) {
|
|
311
|
+
if (_kubb_kit.ast.mediaType.getBase(entry) === "text/event-stream") return "events";
|
|
312
|
+
return entry.stream && _kubb_kit.ast.mediaType.isStream(entry) ? "lines" : void 0;
|
|
88
313
|
}
|
|
89
314
|
/**
|
|
90
315
|
* Parser for one response content entry.
|
|
@@ -95,10 +320,10 @@ function isJsonContentType(baseType) {
|
|
|
95
320
|
* `image/*`, `audio/*`, and `video/*` are blobs.
|
|
96
321
|
*/
|
|
97
322
|
function responseTypeForContent(entry) {
|
|
98
|
-
const baseType =
|
|
323
|
+
const baseType = _kubb_kit.ast.mediaType.getBase(entry);
|
|
99
324
|
if (!baseType) return void 0;
|
|
100
|
-
if (
|
|
101
|
-
if (
|
|
325
|
+
if (getStreamFraming(entry)) return "stream";
|
|
326
|
+
if (_kubb_kit.ast.mediaType.isJson(baseType)) return void 0;
|
|
102
327
|
if (entry.schema?.type === "blob") return "blob";
|
|
103
328
|
if (baseType.startsWith("text/")) return "text";
|
|
104
329
|
if (isBinaryContentType(baseType)) return "blob";
|
|
@@ -115,14 +340,14 @@ function isBinaryContentType(baseType) {
|
|
|
115
340
|
function toResponseContent(entry, entries = [entry]) {
|
|
116
341
|
const { contentType, schema } = entry;
|
|
117
342
|
if (!schema || entry.stream) return entry;
|
|
118
|
-
const baseType =
|
|
119
|
-
const resolved = _kubb_kit.ast.
|
|
343
|
+
const baseType = _kubb_kit.ast.mediaType.getBase(contentType);
|
|
344
|
+
const resolved = _kubb_kit.ast.ref.getSchema(schema);
|
|
120
345
|
if (isBinaryContentType(baseType)) return resolved.type === "blob" ? entry : {
|
|
121
346
|
...entry,
|
|
122
347
|
schema: _kubb_kit.ast.factory.createSchema({ type: "blob" }),
|
|
123
348
|
keysToOmit: null
|
|
124
349
|
};
|
|
125
|
-
const isPlainMediaType = Boolean(baseType) && !
|
|
350
|
+
const isPlainMediaType = Boolean(baseType) && !_kubb_kit.ast.mediaType.isJson(baseType) && !baseType.includes("*");
|
|
126
351
|
if (resolved.type === "blob") return isPlainMediaType && !entries.every((item) => item.schema?.type === "blob") ? {
|
|
127
352
|
...entry,
|
|
128
353
|
schema: _kubb_kit.ast.factory.createSchema({ type: "string" }),
|
|
@@ -138,36 +363,6 @@ function toResponseContent(entry, entries = [entry]) {
|
|
|
138
363
|
keysToOmit: null
|
|
139
364
|
} : entry;
|
|
140
365
|
}
|
|
141
|
-
function getStatusCodeNumber(statusCode) {
|
|
142
|
-
const code = Number(statusCode);
|
|
143
|
-
return Number.isNaN(code) ? null : code;
|
|
144
|
-
}
|
|
145
|
-
function isSuccessStatusCode(statusCode) {
|
|
146
|
-
const code = getStatusCodeNumber(statusCode);
|
|
147
|
-
return code !== null && code >= 200 && code < 300;
|
|
148
|
-
}
|
|
149
|
-
function getSuccessResponses(responses) {
|
|
150
|
-
return responses.filter((response) => isSuccessStatusCode(response.statusCode));
|
|
151
|
-
}
|
|
152
|
-
function getOperationSuccessResponses(node) {
|
|
153
|
-
return getSuccessResponses(node.responses);
|
|
154
|
-
}
|
|
155
|
-
function getPrimarySuccessResponse(node) {
|
|
156
|
-
return getOperationSuccessResponses(node)[0] ?? null;
|
|
157
|
-
}
|
|
158
|
-
function resolveResponseTypes(node, resolver) {
|
|
159
|
-
const types = [];
|
|
160
|
-
for (const response of node.responses) {
|
|
161
|
-
if (response.statusCode === "default") {
|
|
162
|
-
types.push(["default", resolver.response.response(node)]);
|
|
163
|
-
continue;
|
|
164
|
-
}
|
|
165
|
-
const code = getStatusCodeNumber(response.statusCode);
|
|
166
|
-
if (code === null) continue;
|
|
167
|
-
types.push([code, isSuccessStatusCode(code) ? resolver.response.response(node) : resolver.response.status(node, response.statusCode)]);
|
|
168
|
-
}
|
|
169
|
-
return types;
|
|
170
|
-
}
|
|
171
366
|
// ../../internals/shared/src/resolver.ts
|
|
172
367
|
/**
|
|
173
368
|
* Builds the allocation key for a declaration an operation owns. `scope` names which declaration
|
|
@@ -195,124 +390,25 @@ function operationKey(node, ...scope) {
|
|
|
195
390
|
function getEnumNames(ctx) {
|
|
196
391
|
return ctx.memo("enumNames:set", () => new Set((ctx.driver.inputNode?.schemas ?? []).flatMap((schema) => schema.type === "enum" && schema.name ? [schema.name] : [])));
|
|
197
392
|
}
|
|
198
|
-
//
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
* - `path` groups use the second path segment (`/pet/findByStatus` → `pet`).
|
|
204
|
-
* - other groups use the camelCased group (`pet store` → `petStore`).
|
|
205
|
-
*
|
|
206
|
-
* A user-provided `group.name` always wins over the default namer, so callers stay in
|
|
207
|
-
* control of their output folders. Returns `null` when grouping is disabled, matching the
|
|
208
|
-
* per-plugin convention.
|
|
209
|
-
*
|
|
210
|
-
* @param group - The user-supplied group option, or `undefined` to disable grouping.
|
|
211
|
-
*
|
|
212
|
-
* @example
|
|
213
|
-
* ```ts
|
|
214
|
-
* createGroupConfig(group) // shared across every plugin
|
|
215
|
-
* ```
|
|
216
|
-
*/
|
|
217
|
-
function createGroupConfig(group) {
|
|
218
|
-
if (!group) return null;
|
|
219
|
-
const defaultName = (ctx) => {
|
|
220
|
-
if (group.type === "path") return `${ctx.group.split("/")[1]}`;
|
|
221
|
-
return (0, _kubb_kit.camelCase)(ctx.group);
|
|
222
|
-
};
|
|
223
|
-
return {
|
|
224
|
-
...group,
|
|
225
|
-
name: group.name ? group.name : defaultName
|
|
226
|
-
};
|
|
393
|
+
// src/utils.ts
|
|
394
|
+
function toSimpleStyle(value) {
|
|
395
|
+
if (Array.isArray(value)) return value.join(",");
|
|
396
|
+
if (typeof value === "object" && value !== null) return Object.entries(value).flat().join(",");
|
|
397
|
+
return String(value);
|
|
227
398
|
}
|
|
228
|
-
// kubb-extension.json
|
|
229
|
-
var compatibility = {
|
|
230
|
-
"kubb": ">=5.16.0",
|
|
231
|
-
"node": ">=22.22"
|
|
232
|
-
};
|
|
233
|
-
// package.json
|
|
234
|
-
var version = "6.2.0-canary.20261111T122007";
|
|
235
|
-
// src/generators/handlersGenerator.ts
|
|
236
399
|
/**
|
|
237
|
-
*
|
|
238
|
-
* `handlers.ts` file that re-exports every generated handler in operation
|
|
239
|
-
* order, ready to spread into `setupServer(...handlers)` or
|
|
240
|
-
* `setupWorker(...handlers)`.
|
|
400
|
+
* The declared headers a mock sends, each with its example's `serializedValue` or `value`, else its schema's `default` or first example.
|
|
241
401
|
*/
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
const
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
const file = resolver.file({
|
|
253
|
-
name: handlersName,
|
|
254
|
-
extname: ".ts",
|
|
255
|
-
root,
|
|
256
|
-
output,
|
|
257
|
-
group: group ?? void 0
|
|
258
|
-
});
|
|
259
|
-
const handlerNames = nodes.map((node) => resolver.handler.name(node));
|
|
260
|
-
const imports = nodes.map((node, index) => {
|
|
261
|
-
const operationName = handlerNames[index];
|
|
262
|
-
const operationFile = resolver.file({
|
|
263
|
-
name: resolver.name(node.operationId),
|
|
264
|
-
extname: ".ts",
|
|
265
|
-
tag: node.tags[0] ?? "default",
|
|
266
|
-
path: node.path,
|
|
267
|
-
root,
|
|
268
|
-
output,
|
|
269
|
-
group: group ?? void 0
|
|
270
|
-
});
|
|
271
|
-
return _kubb_kit.ast.factory.createImport({
|
|
272
|
-
name: [operationName],
|
|
273
|
-
root: file.path,
|
|
274
|
-
path: operationFile.path
|
|
275
|
-
});
|
|
276
|
-
});
|
|
277
|
-
const handlers = handlerNames.map((name) => `${name}(undefined, options)`);
|
|
278
|
-
return [_kubb_kit.ast.factory.createFile({
|
|
279
|
-
baseName: file.baseName,
|
|
280
|
-
path: file.path,
|
|
281
|
-
meta: file.meta,
|
|
282
|
-
banner: resolver.default.banner(ctx.meta, {
|
|
283
|
-
output,
|
|
284
|
-
config,
|
|
285
|
-
file: {
|
|
286
|
-
path: file.path,
|
|
287
|
-
baseName: file.baseName
|
|
288
|
-
}
|
|
289
|
-
}),
|
|
290
|
-
footer: resolver.default.footer(ctx.meta, {
|
|
291
|
-
output,
|
|
292
|
-
config,
|
|
293
|
-
file: {
|
|
294
|
-
path: file.path,
|
|
295
|
-
baseName: file.baseName
|
|
296
|
-
}
|
|
297
|
-
}),
|
|
298
|
-
imports,
|
|
299
|
-
sources: [_kubb_kit.ast.factory.createSource({
|
|
300
|
-
name: createHandlersName,
|
|
301
|
-
isIndexable: true,
|
|
302
|
-
isExportable: true,
|
|
303
|
-
nodes: [_kubb_kit.ast.factory.createText(`export function ${createHandlersName}(options?: { baseURL?: string }) {
|
|
304
|
-
return [${handlers.join(", ")}] as const
|
|
305
|
-
}`)]
|
|
306
|
-
}), _kubb_kit.ast.factory.createSource({
|
|
307
|
-
name: handlersName,
|
|
308
|
-
isIndexable: true,
|
|
309
|
-
isExportable: true,
|
|
310
|
-
nodes: [_kubb_kit.ast.factory.createText(`export const ${handlersName} = ${createHandlersName}()`)]
|
|
311
|
-
})]
|
|
312
|
-
})];
|
|
313
|
-
}
|
|
314
|
-
});
|
|
315
|
-
// src/utils.ts
|
|
402
|
+
function getMockHeaders(response) {
|
|
403
|
+
return (response?.headers ?? []).flatMap((header) => {
|
|
404
|
+
if (header.name.toLowerCase() === "content-type") return [];
|
|
405
|
+
const example = header.examples?.find((entry) => entry.serializedValue !== void 0 || entry.value !== void 0);
|
|
406
|
+
const headerValue = example?.serializedValue ?? example?.value ?? header.schema.default ?? header.schema.examples?.[0];
|
|
407
|
+
if (headerValue === void 0 || headerValue === null) return [];
|
|
408
|
+
const headerText = toSimpleStyle(headerValue);
|
|
409
|
+
return /^[\t\x20-\x7E\x80-\xFF]*$/.test(headerText) ? [[header.name, headerText]] : [];
|
|
410
|
+
});
|
|
411
|
+
}
|
|
316
412
|
/**
|
|
317
413
|
* Returns the base URL as source code. A `${...}` value stays a template literal.
|
|
318
414
|
*/
|
|
@@ -340,13 +436,12 @@ function hasResponseSchema(response) {
|
|
|
340
436
|
* content types, JSON is preferred (the faker mock body is JSON), otherwise the first declared type.
|
|
341
437
|
*/
|
|
342
438
|
function getResponseContentType(response) {
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
return typeof value === "string" && value.length > 0 ? value : null;
|
|
439
|
+
const contentType = getResponseEntry(response)?.contentType;
|
|
440
|
+
return typeof contentType === "string" && contentType.length > 0 ? contentType : null;
|
|
346
441
|
}
|
|
347
|
-
function
|
|
348
|
-
const
|
|
349
|
-
return
|
|
442
|
+
function getResponseEntry(response) {
|
|
443
|
+
const contents = response?.content ?? [];
|
|
444
|
+
return contents.find((entry) => _kubb_kit.ast.mediaType.isJson(entry)) ?? contents[0];
|
|
350
445
|
}
|
|
351
446
|
/**
|
|
352
447
|
* The first content type of a response that the client reads as text, such as `application/xml`, or `null` when none is.
|
|
@@ -354,49 +449,109 @@ function isJson(contentType) {
|
|
|
354
449
|
function getTextContentType(response) {
|
|
355
450
|
return (response?.content ?? []).find((entry) => {
|
|
356
451
|
const schema = toResponseContent(entry, response?.content).schema;
|
|
357
|
-
return !isJson(entry
|
|
452
|
+
return !_kubb_kit.ast.mediaType.isJson(entry) && schema && _kubb_kit.ast.ref.getSchema(schema).primitive === "string";
|
|
358
453
|
})?.contentType ?? null;
|
|
359
454
|
}
|
|
455
|
+
const bigintReplacer = `(_key, value) => {
|
|
456
|
+
if (typeof value !== 'bigint') return value
|
|
457
|
+
if (value > Number.MAX_SAFE_INTEGER || value < Number.MIN_SAFE_INTEGER) throw new TypeError(\`Cannot serialize \${value}n as JSON without losing precision.\`)
|
|
458
|
+
return Number(value)
|
|
459
|
+
}`;
|
|
460
|
+
/** The `JSON.stringify` call for `body`, with a replacer when the response holds an `int64`, which `JSON.stringify` throws on. */
|
|
461
|
+
function stringify(body, response) {
|
|
462
|
+
return (response?.content ?? []).some((entry) => entry.schema && _kubb_kit.ast.schema.find(entry.schema, {
|
|
463
|
+
predicate: (schema) => schema.type === "bigint",
|
|
464
|
+
followRefs: true
|
|
465
|
+
}) !== void 0) ? `JSON.stringify(${body}, ${bigintReplacer})` : `JSON.stringify(${body})`;
|
|
466
|
+
}
|
|
360
467
|
/**
|
|
361
468
|
* The statements that return a mocked `Response` for `body`. A string body for a response the client reads as text goes
|
|
362
469
|
* out as raw text with that content type, so an XML mock is not sent as a quoted JSON string. Any other body is JSON.
|
|
363
470
|
*/
|
|
364
471
|
function responseReturn(body, status, response) {
|
|
365
472
|
const contentType = getContentType(response);
|
|
473
|
+
const headers = getMockHeaders(response);
|
|
474
|
+
const name = isValidVarName(body) ? body : "body";
|
|
475
|
+
const prelude = name === body ? "" : `const body = ${body}\n `;
|
|
476
|
+
if (contentType && _kubb_kit.ast.mediaType.getBase(contentType) === "text/event-stream") {
|
|
477
|
+
const entry = getResponseEntry(response);
|
|
478
|
+
const serialized = entry?.examples?.find((example) => example.serializedValue !== void 0)?.serializedValue;
|
|
479
|
+
const fallback = serialized === void 0 ? "''" : toLiteral(serialized);
|
|
480
|
+
if (entry?.stream?.shape !== "event") return `${prelude}return ${responseLiteral(`${name} === undefined ? ${fallback} : \`\${${eventDataLines(name, response)}}\\n\\n\``, status, contentType, headers)}`;
|
|
481
|
+
return `${prelude}const event = ${name} as { event?: unknown; id?: unknown; retry?: unknown; data?: unknown } | undefined
|
|
482
|
+
const lines = [
|
|
483
|
+
...(['event', 'id', 'retry'] as const).filter((field) => event?.[field] !== undefined).map((field) => \`\${field}: \${event?.[field]}\`),
|
|
484
|
+
// A client dispatches an event only once it has a \`data\` line, so an event without data still sends an empty one.
|
|
485
|
+
event?.data === undefined ? 'data:' : ${eventDataLines("event.data", response)},
|
|
486
|
+
]
|
|
487
|
+
|
|
488
|
+
return ${responseLiteral(`event === undefined ? ${fallback} : \`\${lines.join('\\n')}\\n\\n\``, status, contentType, headers)}`;
|
|
489
|
+
}
|
|
366
490
|
const textContentType = hasResponseSchema(response) ? getTextContentType(response) : null;
|
|
367
|
-
if (!textContentType) return `return ${responseLiteral(
|
|
368
|
-
|
|
369
|
-
return `${name === body ? "" : `const body = ${body}\n `}if (typeof ${name} === 'string') return ${responseLiteral(name, status, textContentType)}
|
|
491
|
+
if (!textContentType) return `return ${responseLiteral(stringify(body, response), status, contentType, headers)}`;
|
|
492
|
+
return `${prelude}if (typeof ${name} === 'string') return ${responseLiteral(name, status, textContentType, headers)}
|
|
370
493
|
|
|
371
|
-
return ${responseLiteral(
|
|
494
|
+
return ${responseLiteral(stringify(name, response), status, contentType, headers)}`;
|
|
495
|
+
}
|
|
496
|
+
/**
|
|
497
|
+
* The `data:` lines of one Server-Sent Event. A string goes out as it is, since a client keeps string `data` as text.
|
|
498
|
+
*/
|
|
499
|
+
function eventDataLines(body, response) {
|
|
500
|
+
return `(typeof ${body} === 'string' ? ${body} : ${stringify(body, response)}).split('\\n').map((line) => \`data: \${line}\`).join('\\n')`;
|
|
372
501
|
}
|
|
373
502
|
/**
|
|
374
|
-
*
|
|
375
|
-
|
|
503
|
+
* The spec example of the response entry the mock sends, as source code, or `null` when that entry has none.
|
|
504
|
+
*/
|
|
505
|
+
function getExampleBody(response) {
|
|
506
|
+
const entry = getResponseEntry(response);
|
|
507
|
+
const example = entry?.examples?.find((item) => item.value !== void 0 || item.serializedValue !== void 0);
|
|
508
|
+
if (!entry || !example) return null;
|
|
509
|
+
if (example.value !== void 0) return toLiteral(example.value);
|
|
510
|
+
if (!_kubb_kit.ast.mediaType.isJson(entry)) return _kubb_kit.ast.mediaType.getBase(entry) !== "text/event-stream" && getTextContentType(response) === entry.contentType ? toLiteral(example.serializedValue) : null;
|
|
511
|
+
try {
|
|
512
|
+
return toLiteral(JSON.parse(example.serializedValue));
|
|
513
|
+
} catch {
|
|
514
|
+
return null;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
const mswMethods = /* @__PURE__ */ new Set([
|
|
518
|
+
"GET",
|
|
519
|
+
"POST",
|
|
520
|
+
"PUT",
|
|
521
|
+
"PATCH",
|
|
522
|
+
"DELETE",
|
|
523
|
+
"HEAD",
|
|
524
|
+
"OPTIONS"
|
|
525
|
+
]);
|
|
526
|
+
/**
|
|
527
|
+
* Converts an HTTP method to its lowercase MSW equivalent (e.g., 'POST' → 'post'). MSW has no `http.query`, `http.trace` or
|
|
528
|
+
* handler for a custom `additionalOperations` method, so such an operation maps to `http.all` and its handler checks the
|
|
529
|
+
* method itself.
|
|
376
530
|
*/
|
|
377
531
|
function getMswMethod(node) {
|
|
378
|
-
return node.method
|
|
532
|
+
return mswMethods.has(node.method) ? node.method.toLowerCase() : "all";
|
|
379
533
|
}
|
|
380
534
|
/**
|
|
381
|
-
* The `headers` entry of a mocked `Response` init, or an empty string when there is no
|
|
535
|
+
* The `headers` entry of a mocked `Response` init, or an empty string when there is no header to send.
|
|
382
536
|
*/
|
|
383
|
-
function headersBlock(contentType) {
|
|
384
|
-
|
|
537
|
+
function headersBlock(contentType, headers) {
|
|
538
|
+
const entries = [...contentType ? [`'Content-Type': ${_kubb_kit.text.quote(contentType)}`] : [], ...headers.map(([name, value]) => `${_kubb_kit.text.quote(name)}: ${_kubb_kit.text.quote(value)}`)];
|
|
539
|
+
return entries.length ? `\n headers: {\n ${entries.join(",\n ")}\n },` : "";
|
|
385
540
|
}
|
|
386
541
|
/**
|
|
387
542
|
* The `new HttpResponse<string>(...)` expression a mocked handler returns.
|
|
388
543
|
*/
|
|
389
|
-
function responseLiteral(body, status, contentType) {
|
|
544
|
+
function responseLiteral(body, status, contentType, headers) {
|
|
390
545
|
return `new HttpResponse<string>(${body}, {
|
|
391
|
-
status: ${status},${headersBlock(contentType)}
|
|
546
|
+
status: ${status},${headersBlock(contentType, headers)}
|
|
392
547
|
})`;
|
|
393
548
|
}
|
|
394
549
|
// src/components/Mock.tsx
|
|
395
|
-
function Mock({ baseURL = "", name, fakerName, typeName, requestTypeName, node }) {
|
|
550
|
+
function Mock({ baseURL = "", name, fakerName, responseName, typeName, requestTypeName, node }) {
|
|
396
551
|
const method = getMswMethod(node);
|
|
397
|
-
const methodGuard =
|
|
398
|
-
const successResponse =
|
|
399
|
-
const statusCode = successResponse ?
|
|
552
|
+
const methodGuard = method === "all" ? `if (info.request.method !== ${_kubb_kit.text.quote(node.method)}) return\n\n ` : "";
|
|
553
|
+
const successResponse = _kubb_kit.ast.http.responses.findPrimary(node);
|
|
554
|
+
const statusCode = successResponse ? _kubb_kit.ast.http.status.toCode(successResponse) : 200;
|
|
400
555
|
const url = _kubb_kit.Url.toPath(node.path);
|
|
401
556
|
const dataType = hasResponseSchema(successResponse) ? typeName : "string | number | boolean | null | object";
|
|
402
557
|
const paramType = fakerName ? typeName : dataType;
|
|
@@ -404,7 +559,10 @@ function Mock({ baseURL = "", name, fakerName, typeName, requestTypeName, node }
|
|
|
404
559
|
const params = `data?: ${paramType} | ${requestBodyType ? `HttpResponseResolver<Record<string, string>, ${requestBodyType}>` : `((info: Parameters<Parameters<typeof http.${method}>[1]>[0]) => Response | Promise<Response>)`}, options?: { baseURL?: string }`;
|
|
405
560
|
const httpCall = requestBodyType ? `http.${method}<Record<string, string>, ${requestBodyType}>` : `http.${method}`;
|
|
406
561
|
const urlLiteral = `\`\${options?.baseURL ?? ${resolveDefaultBaseURL(baseURL)}}${url.replace(/([^/]):/g, "$1\\\\:")}\``;
|
|
407
|
-
const
|
|
562
|
+
const example = fakerName ? null : getExampleBody(successResponse);
|
|
563
|
+
const exampleBody = example ? `data === undefined ? ${example} : data` : "data";
|
|
564
|
+
const responseBody = fakerName ? `data ?? ${fakerName}()` : exampleBody;
|
|
565
|
+
const responseStatements = responseName && !example ? `return ${responseName}(${responseBody})` : responseReturn(responseBody, statusCode, successResponse);
|
|
408
566
|
return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Source, {
|
|
409
567
|
name,
|
|
410
568
|
isIndexable: true,
|
|
@@ -416,16 +574,15 @@ function Mock({ baseURL = "", name, fakerName, typeName, requestTypeName, node }
|
|
|
416
574
|
children: `return ${httpCall}(${urlLiteral}, function handler(info) {
|
|
417
575
|
${methodGuard}if(typeof data === 'function') return data(info)
|
|
418
576
|
|
|
419
|
-
${
|
|
577
|
+
${responseStatements}
|
|
420
578
|
})`
|
|
421
579
|
})
|
|
422
580
|
});
|
|
423
581
|
}
|
|
424
582
|
// src/components/Response.tsx
|
|
425
583
|
function Response({ name, typeName, response }) {
|
|
426
|
-
const
|
|
427
|
-
const
|
|
428
|
-
const responseName = `${name}Response${statusCode}`;
|
|
584
|
+
const params = `data?: ${typeName}`;
|
|
585
|
+
const responseName = `${name}Response${response.statusCode}`;
|
|
429
586
|
return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(_kubb_renderer_jsx.File.Source, {
|
|
430
587
|
name: responseName,
|
|
431
588
|
isIndexable: true,
|
|
@@ -435,7 +592,7 @@ function Response({ name, typeName, response }) {
|
|
|
435
592
|
export: true,
|
|
436
593
|
params,
|
|
437
594
|
children: `
|
|
438
|
-
${responseReturn("data",
|
|
595
|
+
${responseReturn("data", _kubb_kit.ast.http.status.toCode(response), response)}`
|
|
439
596
|
})
|
|
440
597
|
});
|
|
441
598
|
}
|
|
@@ -450,7 +607,7 @@ const mswGenerator = (0, _kubb_kit.defineGenerator)({
|
|
|
450
607
|
name: "msw",
|
|
451
608
|
renderer: _kubb_renderer_jsx.jsxRenderer,
|
|
452
609
|
operation(node, ctx) {
|
|
453
|
-
if (!_kubb_kit.ast.
|
|
610
|
+
if (!_kubb_kit.ast.http.isOperation(node)) return null;
|
|
454
611
|
const { driver, resolver, config, root } = ctx;
|
|
455
612
|
const { output, parser, baseURL, group } = ctx.options;
|
|
456
613
|
const fileName = resolver.name(node.operationId);
|
|
@@ -492,8 +649,8 @@ const mswGenerator = (0, _kubb_kit.defineGenerator)({
|
|
|
492
649
|
}),
|
|
493
650
|
responseName: tsResolver.response.response(node)
|
|
494
651
|
};
|
|
495
|
-
const successResponses =
|
|
496
|
-
const referencedNames =
|
|
652
|
+
const successResponses = _kubb_kit.ast.http.responses.filterSuccess(node);
|
|
653
|
+
const referencedNames = _kubb_kit.ast.http.responses.getImportedNames(node.responses);
|
|
497
654
|
const enumOptions = pluginTs.options?.enum;
|
|
498
655
|
const enumNames = getEnumNames(ctx);
|
|
499
656
|
const hasResponseNameCollision = referencedNames.some((name) => {
|
|
@@ -504,14 +661,15 @@ const mswGenerator = (0, _kubb_kit.defineGenerator)({
|
|
|
504
661
|
isEnum: enumNames.has(name)
|
|
505
662
|
}) === type.responseName;
|
|
506
663
|
});
|
|
507
|
-
const types =
|
|
508
|
-
|
|
509
|
-
return [code, hasResponseNameCollision && successResponse ? tsResolver.response.status(node, successResponse.statusCode) : typeName];
|
|
664
|
+
const types = node.responses.filter((response) => response.statusCode !== "default" && _kubb_kit.ast.http.status.getClass(response) !== 1).map((response) => {
|
|
665
|
+
return [response, _kubb_kit.ast.http.status.isSuccess(response) && !hasResponseNameCollision ? type.responseName : tsResolver.response.status(node, response.statusCode)];
|
|
510
666
|
});
|
|
511
|
-
const responseTypeNames = types.map(([, typeName]) => typeName)
|
|
667
|
+
const responseTypeNames = types.map(([, typeName]) => typeName);
|
|
512
668
|
const mockResponseName = hasResponseNameCollision && successResponses[0] ? tsResolver.response.status(node, successResponses[0].statusCode) : type.responseName;
|
|
513
669
|
const hasSuccessSchema = successResponses.some((response) => hasResponseSchema(response));
|
|
514
|
-
const
|
|
670
|
+
const primaryResponse = _kubb_kit.ast.http.responses.findPrimary(node);
|
|
671
|
+
const responseHelperName = primaryResponse && hasResponseSchema(primaryResponse) && types.some(([response, typeName]) => response.statusCode === primaryResponse.statusCode && typeName === mockResponseName) ? `${mock.name}Response${primaryResponse.statusCode}` : void 0;
|
|
672
|
+
const requestName = _kubb_kit.ast.http.requestBody.getSchema(node) ? tsResolver.response.body(node) : null;
|
|
515
673
|
return /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsxs)(_kubb_renderer_jsx.File, {
|
|
516
674
|
baseName: mock.file.baseName,
|
|
517
675
|
path: mock.file.path,
|
|
@@ -562,20 +720,17 @@ const mswGenerator = (0, _kubb_kit.defineGenerator)({
|
|
|
562
720
|
root: mock.file.path,
|
|
563
721
|
path: faker.file.path
|
|
564
722
|
}),
|
|
565
|
-
types.
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
response,
|
|
571
|
-
name: mock.name
|
|
572
|
-
}, typeName);
|
|
573
|
-
}),
|
|
723
|
+
types.map(([response, typeName]) => /* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(Response, {
|
|
724
|
+
typeName,
|
|
725
|
+
response,
|
|
726
|
+
name: mock.name
|
|
727
|
+
}, response.statusCode)),
|
|
574
728
|
/* @__PURE__ */ (0, _kubb_renderer_jsx_jsx_runtime.jsx)(Mock, {
|
|
575
729
|
name: mock.name,
|
|
576
730
|
typeName: mockResponseName,
|
|
577
731
|
requestTypeName: requestName,
|
|
578
732
|
fakerName: faker && hasSuccessSchema ? faker.name : void 0,
|
|
733
|
+
responseName: responseHelperName,
|
|
579
734
|
node,
|
|
580
735
|
baseURL
|
|
581
736
|
})
|
|
@@ -602,7 +757,7 @@ const mswGenerator = (0, _kubb_kit.defineGenerator)({
|
|
|
602
757
|
const resolverMsw = (0, _kubb_kit.createResolver)({
|
|
603
758
|
pluginName: "plugin-msw",
|
|
604
759
|
name(name) {
|
|
605
|
-
return this.memo(`name:${name}`, () =>
|
|
760
|
+
return this.memo(`name:${name}`, () => _kubb_kit.casing.camel(name, { suffix: "handler" }));
|
|
606
761
|
},
|
|
607
762
|
handler: {
|
|
608
763
|
name(node) {
|
|
@@ -677,7 +832,7 @@ const pluginMsw = (0, _kubb_kit.defineExtension)({
|
|
|
677
832
|
output,
|
|
678
833
|
parser,
|
|
679
834
|
baseURL,
|
|
680
|
-
group:
|
|
835
|
+
group: group ?? null,
|
|
681
836
|
exclude,
|
|
682
837
|
include,
|
|
683
838
|
override,
|