@kubb/plugin-mcp 5.0.0-beta.99 → 5.0.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 CHANGED
@@ -19,231 +19,18 @@ var __copyProps = (to, from, except, desc) => {
19
19
  }
20
20
  return to;
21
21
  };
22
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
22
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
23
23
  value: mod,
24
24
  enumerable: true
25
25
  }) : target, mod));
26
26
  //#endregion
27
+ let kubb_kit = require("kubb/kit");
27
28
  let node_path = require("node:path");
28
29
  node_path = __toESM(node_path, 1);
29
- let kubb_kit = require("kubb/kit");
30
30
  let kubb_jsx = require("kubb/jsx");
31
31
  let kubb_jsx_jsx_runtime = require("kubb/jsx/jsx-runtime");
32
32
  let _kubb_plugin_ts = require("@kubb/plugin-ts");
33
33
  let _kubb_plugin_zod = require("@kubb/plugin-zod");
34
- //#region ../../internals/utils/src/casing.ts
35
- /**
36
- * Shared implementation for camelCase and PascalCase conversion.
37
- * Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
38
- * and capitalizes each word according to `pascal`.
39
- *
40
- * When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
41
- */
42
- function toCamelOrPascal(text, pascal) {
43
- return text.trim().replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/(\d)([a-z])/g, "$1 $2").split(/[\s\-_./\\:]+/).filter(Boolean).map((word, i) => {
44
- if (word.length > 1 && word === word.toUpperCase()) return word;
45
- return (i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()) + word.slice(1);
46
- }).join("").replace(/[^a-zA-Z0-9]/g, "");
47
- }
48
- /**
49
- * Converts `text` to camelCase.
50
- *
51
- * @example Word boundaries
52
- * `camelCase('hello-world') // 'helloWorld'`
53
- *
54
- * @example With a prefix
55
- * `camelCase('tag', { prefix: 'create' }) // 'createTag'`
56
- */
57
- function camelCase(text, { prefix = "", suffix = "" } = {}) {
58
- return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
59
- }
60
- //#endregion
61
- //#region ../../internals/utils/src/reserved.ts
62
- /**
63
- * JavaScript and Java reserved words.
64
- * @link https://github.com/jonschlinkert/reserved/blob/master/index.js
65
- */
66
- const reservedWords = /* @__PURE__ */ new Set([
67
- "abstract",
68
- "arguments",
69
- "boolean",
70
- "break",
71
- "byte",
72
- "case",
73
- "catch",
74
- "char",
75
- "class",
76
- "const",
77
- "continue",
78
- "debugger",
79
- "default",
80
- "delete",
81
- "do",
82
- "double",
83
- "else",
84
- "enum",
85
- "eval",
86
- "export",
87
- "extends",
88
- "false",
89
- "final",
90
- "finally",
91
- "float",
92
- "for",
93
- "function",
94
- "goto",
95
- "if",
96
- "implements",
97
- "import",
98
- "in",
99
- "instanceof",
100
- "int",
101
- "interface",
102
- "let",
103
- "long",
104
- "native",
105
- "new",
106
- "null",
107
- "package",
108
- "private",
109
- "protected",
110
- "public",
111
- "return",
112
- "short",
113
- "static",
114
- "super",
115
- "switch",
116
- "synchronized",
117
- "this",
118
- "throw",
119
- "throws",
120
- "transient",
121
- "true",
122
- "try",
123
- "typeof",
124
- "var",
125
- "void",
126
- "volatile",
127
- "while",
128
- "with",
129
- "yield",
130
- "Array",
131
- "Date",
132
- "hasOwnProperty",
133
- "Infinity",
134
- "isFinite",
135
- "isNaN",
136
- "isPrototypeOf",
137
- "length",
138
- "Math",
139
- "name",
140
- "NaN",
141
- "Number",
142
- "Object",
143
- "prototype",
144
- "String",
145
- "toString",
146
- "undefined",
147
- "valueOf"
148
- ]);
149
- /**
150
- * Returns `true` when `name` is a syntactically valid JavaScript variable name.
151
- *
152
- * @example
153
- * ```ts
154
- * isValidVarName('status') // true
155
- * isValidVarName('class') // false (reserved word)
156
- * isValidVarName('42foo') // false (starts with digit)
157
- * ```
158
- */
159
- function isValidVarName(name) {
160
- if (!name || reservedWords.has(name)) return false;
161
- return isIdentifier(name);
162
- }
163
- /**
164
- * Returns `true` when `name` is syntactically a valid identifier, ignoring reserved words.
165
- *
166
- * Reserved words and globals (`class`, `name`, `Date`, …) are valid as bare object-literal keys
167
- * even though they are not valid variable names, so use this (not {@link isValidVarName}) when
168
- * deciding whether an object key needs quoting.
169
- *
170
- * @example
171
- * ```ts
172
- * isIdentifier('name') // true
173
- * isIdentifier('x-total')// false
174
- * ```
175
- */
176
- function isIdentifier(name) {
177
- return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
178
- }
179
- //#endregion
180
- //#region ../../internals/utils/src/url.ts
181
- /**
182
- * Keeps the OpenAPI parameter name as-is when it is already a valid JS identifier, and
183
- * camelCases it only enough to become one otherwise (for example a hyphenated path segment).
184
- */
185
- function transformParam(raw) {
186
- return isValidVarName(raw) ? raw : camelCase(raw);
187
- }
188
- /**
189
- * Helpers for OpenAPI/Swagger paths.
190
- */
191
- var Url = class Url {
192
- /**
193
- * Converts an OpenAPI/Swagger path to Express-style colon syntax.
194
- *
195
- * @example
196
- * Url.toPath('/pet/{petId}') // '/pet/:petId'
197
- */
198
- static toPath(path) {
199
- return path.replace(/\{([^}]+)\}/g, ":$1");
200
- }
201
- /**
202
- * Rewrites OpenAPI placeholder names while keeping the `{...}` braces, so the generated `url`
203
- * literal aligns with the grouped `path` request option that the runtime client interpolates by
204
- * key.
205
- *
206
- * @example
207
- * Url.toSafeTemplate('/user/{monetary-account-id}') // '/user/{monetaryAccountId}'
208
- */
209
- static toSafeTemplate(path) {
210
- return path.replace(/\{([^}]+)\}/g, (_, name) => `{${transformParam(name)}}`);
211
- }
212
- /**
213
- * Converts an OpenAPI/Swagger path to a TypeScript template literal string.
214
- * `prefix` is prepended inside the literal, and `replacer` transforms each parameter name.
215
- *
216
- * @example
217
- * Url.toTemplateString('/pet/{petId}') // '`/pet/${petId}`'
218
- *
219
- * @example
220
- * Url.toTemplateString('/pet/{petId}', { prefix: 'https://api' }) // '`https://api/pet/${petId}`'
221
- */
222
- static toTemplateString(path, { prefix, replacer } = {}) {
223
- const result = path.split(/\{([^}]+)\}/).map((part, i) => {
224
- if (i % 2 === 0) return part;
225
- const param = transformParam(part);
226
- return `\${${replacer ? replacer(param) : param}}`;
227
- }).join("");
228
- return `\`${prefix ?? ""}${result}\``;
229
- }
230
- /**
231
- * Converts an OpenAPI/Swagger path to a template literal that reads each parameter off the
232
- * grouped `path` request option, e.g. `/pet/{petId}` becomes `` `/pet/${path.petId}` ``. Parameter
233
- * names match the generated `path` type, and `prefix` is prepended inside the literal. Shared by
234
- * the client and cypress generators that pass a grouped `path` object.
235
- *
236
- * @example
237
- * Url.toGroupedTemplateString('/pet/{petId}') // '`/pet/${path.petId}`'
238
- */
239
- static toGroupedTemplateString(path, { prefix } = {}) {
240
- return Url.toTemplateString(path, {
241
- prefix,
242
- replacer: (name) => `path.${name}`
243
- });
244
- }
245
- };
246
- //#endregion
247
34
  //#region ../../internals/shared/src/params.ts
248
35
  /**
249
36
  * Drops parameters that share the same name, keeping the first.
@@ -282,11 +69,30 @@ function operationFileEntry(node, name, extname = ".ts") {
282
69
  path: node.path
283
70
  };
284
71
  }
72
+ /**
73
+ * Resolves a dependency plugin's generated file for `node.operationId`, cached in `cache` (the
74
+ * current node's `ctx.cache`) under the resolver's own plugin name. Several dependents reading the
75
+ * same dependency for the same operation in one pass (a query plugin's several hook generators, the
76
+ * MCP handler, ...) share one computed name and path instead of each calling `resolver.file` again.
77
+ *
78
+ * @example Cache `plugin-ts`'s file for the current operation
79
+ * ```ts
80
+ * const fileTs = resolveDependencyOperationFile({ cache: ctx.cache, node, resolver: tsResolver, root, output })
81
+ * ```
82
+ */
83
+ function resolveDependencyOperationFile(options) {
84
+ const { cache, node, resolver, root, output, group } = options;
85
+ return cache.ensureItem(`${resolver.pluginName}:operationFile`, () => resolver.file({
86
+ ...operationFileEntry(node, node.operationId),
87
+ root,
88
+ output,
89
+ group: group ?? void 0
90
+ }));
91
+ }
285
92
  function getOperationLink(node, link) {
286
93
  if (!link) return null;
287
94
  if (typeof link === "function") return link(node) ?? null;
288
- if (link === "urlPath") return node.path ? `{@link ${Url.toPath(node.path)}}` : null;
289
- return node.path ? `{@link ${node.path.replaceAll("{", ":").replaceAll("}", "")}}` : null;
95
+ return node.path ? `{@link ${kubb_kit.Url.toPath(node.path)}}` : null;
290
96
  }
291
97
  /**
292
98
  * Derives the shared `ContentTypeInfo` shape from a list of content types, tracking whether several
@@ -387,16 +193,24 @@ function buildOperationComments(node, options = {}) {
387
193
  if (!splitLines) return filteredComments;
388
194
  return filteredComments.flatMap((text) => text.split(/\r?\n/).map((line) => line.trim())).filter((comment) => Boolean(comment));
389
195
  }
196
+ const operationParameterGroupsByNode = /* @__PURE__ */ new WeakMap();
197
+ /**
198
+ * Groups an operation's parameters by location (`path`/`query`/`header`/`cookie`), deduping each
199
+ * group by name. Every plugin generator visiting the same `OperationNode` shares one AST instance
200
+ * (see `KubbDriver`), so the result is cached per node to avoid re-filtering and re-deduping the
201
+ * same parameters once per plugin.
202
+ */
390
203
  function getOperationParameters(node) {
391
- return {
392
- path: dedupeParams(node.parameters.filter((param) => param.in === "path").map((param) => isValidVarName(param.name) ? param : {
393
- ...param,
394
- name: camelCase(param.name)
395
- })),
204
+ const cached = operationParameterGroupsByNode.get(node);
205
+ if (cached) return cached;
206
+ const groups = {
207
+ path: dedupeParams(node.parameters.filter((param) => param.in === "path")),
396
208
  query: dedupeParams(node.parameters.filter((param) => param.in === "query")),
397
209
  header: dedupeParams(node.parameters.filter((param) => param.in === "header")),
398
210
  cookie: dedupeParams(node.parameters.filter((param) => param.in === "cookie"))
399
211
  };
212
+ operationParameterGroupsByNode.set(node, groups);
213
+ return groups;
400
214
  }
401
215
  function getStatusCodeNumber(statusCode) {
402
216
  const code = Number(statusCode);
@@ -420,6 +234,33 @@ function findSuccessStatusCode(responses) {
420
234
  return null;
421
235
  }
422
236
  //#endregion
237
+ //#region ../../internals/utils/src/casing.ts
238
+ /**
239
+ * Shared implementation for camelCase and PascalCase conversion.
240
+ * Splits on common word boundaries (spaces, hyphens, underscores, dots, slashes, colons)
241
+ * and capitalizes each word according to `pascal`.
242
+ *
243
+ * When `pascal` is `true` the first word is also capitalized (PascalCase), otherwise only subsequent words are.
244
+ */
245
+ function toCamelOrPascal(text, pascal) {
246
+ return text.trim().replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/(\d)([a-z])/g, "$1 $2").split(/[\s\-_./\\:]+/).filter(Boolean).map((word, i) => {
247
+ if (word.length > 1 && word === word.toUpperCase()) return word;
248
+ return (i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()) + word.slice(1);
249
+ }).join("").replace(/[^a-zA-Z0-9]/g, "");
250
+ }
251
+ /**
252
+ * Converts `text` to camelCase.
253
+ *
254
+ * @example Word boundaries
255
+ * `camelCase('hello-world') // 'helloWorld'`
256
+ *
257
+ * @example With a prefix
258
+ * `camelCase('tag', { prefix: 'create' }) // 'createTag'`
259
+ */
260
+ function camelCase(text, { prefix = "", suffix = "" } = {}) {
261
+ return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
262
+ }
263
+ //#endregion
423
264
  //#region ../../internals/shared/src/group.ts
424
265
  /**
425
266
  * Builds the `group` config a Kubb plugin passes to `ctx.setOptions`, applying the
@@ -706,23 +547,30 @@ function resolveContractClient(options) {
706
547
  * (plugin-fetch or plugin-axios). Returns `null` when no contract plugin is in play (the inline
707
548
  * path). The plugin injects `.kubb/client.ts` at the global output root, the same path consumers
708
549
  * read `RequestConfig` / `ResponseErrorConfig` from.
550
+ *
551
+ * The result is cached in the current node's `cache` (`ctx.cache`) under the client plugin's name,
552
+ * so several dependents reading the same client plugin for one operation in a single pass
553
+ * (react-query's query/mutation/infinite generators, vue-query, swr, the MCP handler, ...) share
554
+ * one computed result instead of each re-deriving the name and path.
709
555
  */
710
556
  function resolveClientOperation(options) {
711
- const { clientPlugin, driver, node, root, output } = options;
557
+ const { clientPlugin, driver, node, root, output, cache } = options;
712
558
  if (!clientPlugin) return null;
713
- const resolver = driver.getResolver(clientPlugin.pluginName);
714
- const plugin = driver.getPlugin(clientPlugin.pluginName);
715
- const file = resolver.file({
716
- ...operationFileEntry(node, node.operationId),
717
- root,
718
- output: plugin?.options?.output ?? output,
719
- group: plugin?.options?.group ?? void 0
559
+ return cache.ensureItem(`${clientPlugin.pluginName}:clientOperation`, () => {
560
+ const resolver = driver.getResolver(clientPlugin.pluginName);
561
+ const plugin = driver.getPlugin(clientPlugin.pluginName);
562
+ const file = resolver.file({
563
+ ...operationFileEntry(node, node.operationId),
564
+ root,
565
+ output: plugin?.options?.output ?? output,
566
+ group: plugin?.options?.group ?? void 0
567
+ });
568
+ return {
569
+ name: resolver.name(node.operationId),
570
+ path: file.path,
571
+ clientPath: node_path.default.resolve(root, ".kubb/client.ts")
572
+ };
720
573
  });
721
- return {
722
- name: resolver.name(node.operationId),
723
- path: file.path,
724
- clientPath: node_path.default.resolve(root, ".kubb/client.ts")
725
- };
726
574
  }
727
575
  //#endregion
728
576
  //#region src/generators/mcpGenerator.tsx
@@ -747,7 +595,8 @@ const mcpGenerator = (0, kubb_kit.defineGenerator)({
747
595
  driver,
748
596
  node,
749
597
  root,
750
- output
598
+ output,
599
+ cache: ctx.cache
751
600
  });
752
601
  if (!contractOp) return null;
753
602
  const optionsName = tsResolver.response.options(node);
@@ -762,14 +611,13 @@ const mcpGenerator = (0, kubb_kit.defineGenerator)({
762
611
  output,
763
612
  group: group ?? void 0
764
613
  }),
765
- fileTs: tsResolver.file({
766
- name: node.operationId,
767
- extname: ".ts",
768
- tag: node.tags[0] ?? "default",
769
- path: node.path,
614
+ fileTs: resolveDependencyOperationFile({
615
+ cache: ctx.cache,
616
+ node,
617
+ resolver: tsResolver,
770
618
  root,
771
619
  output: pluginTs.options?.output ?? output,
772
- group: pluginTs.options?.group ?? void 0
620
+ group: pluginTs.options?.group
773
621
  })
774
622
  };
775
623
  return /* @__PURE__ */ (0, kubb_jsx_jsx_runtime.jsxs)(kubb_jsx.File, {