react-query-lightbase-codegen 3.1.7 → 3.1.8

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.
@@ -2,6 +2,33 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.generateApiClient = generateApiClient;
4
4
  const utils_1 = require("../utils");
5
+ /**
6
+ * Walks a request body schema (including allOf/oneOf/anyOf composition) and returns
7
+ * the union of property names contributed by all branches. Used to pick body fields
8
+ * out of the merged Params object so path/query/header values aren't sent as the body.
9
+ */
10
+ function collectBodyPropertyNames(schema, spec, visitedRefs = new Set()) {
11
+ if (!schema)
12
+ return [];
13
+ if ("$ref" in schema) {
14
+ if (visitedRefs.has(schema.$ref))
15
+ return [];
16
+ const next = new Set(visitedRefs).add(schema.$ref);
17
+ return collectBodyPropertyNames((0, utils_1.resolveSchema)(schema, spec), spec, next);
18
+ }
19
+ const names = new Set();
20
+ if (schema.properties)
21
+ Object.keys(schema.properties).forEach((k) => names.add(k));
22
+ for (const key of ["allOf", "oneOf", "anyOf"]) {
23
+ const branches = schema[key];
24
+ if (!branches)
25
+ continue;
26
+ for (const branch of branches) {
27
+ collectBodyPropertyNames(branch, spec, visitedRefs).forEach((n) => names.add(n));
28
+ }
29
+ }
30
+ return Array.from(names);
31
+ }
5
32
  function generateAxiosMethod(operation, spec) {
6
33
  const { method, path, operationId, summary, description, deprecated, parameters, requestBody, responses } = operation;
7
34
  // Generate JSDoc
@@ -59,9 +86,13 @@ function generateAxiosMethod(operation, spec) {
59
86
  : undefined;
60
87
  const requestBodyContent = requestBody && "content" in requestBody ? (0, utils_1.getContentSchema)(requestBody.content) : undefined;
61
88
  const requestBodySchema = requestBodyContent ? (0, utils_1.resolveSchema)(requestBodyContent, spec) : undefined;
89
+ // Resolve property names contributed by the body schema, walking allOf/oneOf/anyOf so
90
+ // composition-based bodies are still treated as object bodies (not "primitive").
91
+ const bodyPropertyNames = requestBodySchema ? collectBodyPropertyNames(requestBodySchema, spec) : [];
92
+ const hasObjectBody = bodyPropertyNames.length > 0;
62
93
  // Check if request body is a primitive type (string, number, boolean)
63
94
  const isPrimitiveRequestBody = requestBodySchema &&
64
- !requestBodySchema.properties &&
95
+ !hasObjectBody &&
65
96
  !requestBodySchema.type?.includes("object") &&
66
97
  !requestBodySchema.type?.includes("array");
67
98
  // Add request body type if it exists
@@ -98,11 +129,9 @@ function generateAxiosMethod(operation, spec) {
98
129
  ${queryParams.map((p) => `["${p.name}"]: data["${p.name}"]`).join(",\n ")}
99
130
  };`
100
131
  : "",
101
- requestBodySchema?.properties && !formDataSchema?.properties
132
+ hasObjectBody && !formDataSchema?.properties
102
133
  ? `const bodyData = {
103
- ${Object.entries(requestBodySchema.properties)
104
- .map(([key]) => `["${key}"]: data["${key}"]`)
105
- .join(",\n ")}
134
+ ${bodyPropertyNames.map((key) => `["${key}"]: data["${key}"]`).join(",\n ")}
106
135
  };`
107
136
  : "",
108
137
  formDataSchema?.properties
@@ -129,8 +158,8 @@ function generateAxiosMethod(operation, spec) {
129
158
  // Note: Don't set Content-Type for FormData - Axios will set it automatically with the correct boundary
130
159
  requestBody
131
160
  ? responseType === "void"
132
- ? `await apiClient.${method}<${responseType}>(url, ${formDataSchema?.properties || requestBodySchema?.properties ? "bodyData" : "data"}, axiosConfig);`
133
- : `const res = await apiClient.${method}<${responseType}>(url, ${formDataSchema?.properties || requestBodySchema?.properties ? "bodyData" : "data"}, axiosConfig);`
161
+ ? `await apiClient.${method}<${responseType}>(url, ${formDataSchema?.properties || hasObjectBody ? "bodyData" : "data"}, axiosConfig);`
162
+ : `const res = await apiClient.${method}<${responseType}>(url, ${formDataSchema?.properties || hasObjectBody ? "bodyData" : "data"}, axiosConfig);`
134
163
  : responseType === "void"
135
164
  ? `await apiClient.${method}<${responseType}>(url, axiosConfig);`
136
165
  : `const res = await apiClient.${method}<${responseType}>(url, axiosConfig);`,
@@ -39,82 +39,67 @@ function generateTypeDefinitions(spec) {
39
39
  output += generateTypeDefinition(name, schema);
40
40
  generatedTypes.add(name);
41
41
  }
42
- // Generate request/response types
43
- if (spec.paths) {
44
- for (const [path, pathItem] of Object.entries(spec.paths)) {
45
- for (const [method, operation] of Object.entries(pathItem)) {
46
- if (method === "$ref")
47
- continue;
48
- const operationObject = operation;
49
- if (!operationObject)
50
- continue;
51
- const { operationId: badOperationId, requestBody, responses, parameters } = operationObject;
52
- const operationId = `${(0, utils_1.sanitizeTypeName)(badOperationId || `${path.replace(/\W+/g, "_")}`)}`;
53
- // Generate request body type
54
- if (requestBody) {
55
- const content = requestBody.content;
56
- const requestSchema = (0, utils_1.getContentSchema)(content);
57
- if (requestSchema) {
58
- const typeName = `${operationId}Request`;
59
- output += generateTypeDefinition(typeName, requestSchema);
60
- }
61
- }
62
- // Generate response types
63
- const errorTypes = [];
64
- if (responses) {
65
- for (const [code, response] of Object.entries(responses)) {
66
- const responseObj = response;
67
- const responseSchema = (0, utils_1.getContentSchema)(responseObj.content);
68
- if (responseSchema) {
69
- const typeName = `${operationId}Response${code}`;
70
- output += generateTypeDefinition(typeName, responseSchema);
71
- // Track non-2xx responses for error union type
72
- if (!code.startsWith("2")) {
73
- errorTypes.push(typeName);
74
- }
75
- }
76
- }
77
- }
78
- // Generate error union type if there are error responses
79
- if (errorTypes.length > 0) {
80
- output += `export type ${(0, utils_1.pascalCase)(operationId)}Error = ${errorTypes.join(" | ")};\n\n`;
81
- }
82
- // Build data type parts
83
- const dataProps = [];
84
- const urlParams = (parameters?.filter((p) => "in" in p && p.in === "path") ||
85
- []);
86
- const queryParams = (parameters?.filter((p) => "in" in p && p.in === "query") ||
87
- []);
88
- const headerParams = (parameters?.filter((p) => "in" in p && p.in === "header") ||
89
- []);
90
- const cookieParams = (parameters?.filter((p) => "in" in p && p.in === "cookie") ||
91
- []);
92
- // Add path, query, header, and cookie parameters
93
- urlParams.forEach((p) => dataProps.push(formatParamProperty(p, true))); // Path params always required
94
- queryParams.forEach((p) => dataProps.push(formatParamProperty(p)));
95
- headerParams.forEach((p) => dataProps.push(formatParamProperty(p)));
96
- cookieParams.forEach((p) => dataProps.push(formatParamProperty(p)));
97
- // Add request body type if it exists
98
- const hasData = (parameters && parameters.length > 0) || requestBody;
99
- let dataType = "undefined";
100
- const namedType = (0, utils_1.pascalCase)(operationId);
101
- if (hasData) {
102
- if (requestBody && dataProps.length > 0) {
103
- dataType = `${namedType}Request & { ${dataProps.join("; ")} }`;
104
- }
105
- else if (requestBody) {
106
- dataType = `${namedType}Request`;
107
- }
108
- else if (dataProps.length > 0) {
109
- dataType = `{ ${dataProps.join("; ")} }`;
110
- }
111
- else {
112
- dataType = "Record<string, never>";
42
+ // Generate request/response types. Use the same operation-collection helper as the
43
+ // client generator so pathItem-level parameters and $ref parameters stay in sync.
44
+ for (const { operationId, parameters, requestBody, responses } of (0, utils_1.collectOperations)(spec)) {
45
+ // Generate request body type
46
+ if (requestBody) {
47
+ const requestSchema = (0, utils_1.getContentSchema)(requestBody.content);
48
+ if (requestSchema) {
49
+ const typeName = `${operationId}Request`;
50
+ output += generateTypeDefinition(typeName, requestSchema);
51
+ }
52
+ }
53
+ // Generate response types
54
+ const errorTypes = [];
55
+ if (responses) {
56
+ for (const [code, response] of Object.entries(responses)) {
57
+ const responseObj = response;
58
+ const responseSchema = (0, utils_1.getContentSchema)(responseObj.content);
59
+ if (responseSchema) {
60
+ const typeName = `${operationId}Response${code}`;
61
+ output += generateTypeDefinition(typeName, responseSchema);
62
+ // Track non-2xx responses for error union type
63
+ if (!code.startsWith("2")) {
64
+ errorTypes.push(typeName);
113
65
  }
114
- output += `\n\nexport type ${(0, utils_1.pascalCase)(operationId)}Params = ${dataType};\n\n`;
115
66
  }
116
67
  }
117
68
  }
69
+ // Generate error union type if there are error responses
70
+ if (errorTypes.length > 0) {
71
+ output += `export type ${(0, utils_1.pascalCase)(operationId)}Error = ${errorTypes.join(" | ")};\n\n`;
72
+ }
73
+ // Build data type parts
74
+ const dataProps = [];
75
+ const urlParams = parameters.filter((p) => p.in === "path");
76
+ const queryParams = parameters.filter((p) => p.in === "query");
77
+ const headerParams = parameters.filter((p) => p.in === "header");
78
+ const cookieParams = parameters.filter((p) => p.in === "cookie");
79
+ // Add path, query, header, and cookie parameters
80
+ urlParams.forEach((p) => dataProps.push(formatParamProperty(p, true))); // Path params always required
81
+ queryParams.forEach((p) => dataProps.push(formatParamProperty(p)));
82
+ headerParams.forEach((p) => dataProps.push(formatParamProperty(p)));
83
+ cookieParams.forEach((p) => dataProps.push(formatParamProperty(p)));
84
+ // Add request body type if it exists
85
+ const hasData = parameters.length > 0 || requestBody;
86
+ let dataType = "undefined";
87
+ const namedType = (0, utils_1.pascalCase)(operationId);
88
+ if (hasData) {
89
+ if (requestBody && dataProps.length > 0) {
90
+ dataType = `${namedType}Request & { ${dataProps.join("; ")} }`;
91
+ }
92
+ else if (requestBody) {
93
+ dataType = `${namedType}Request`;
94
+ }
95
+ else if (dataProps.length > 0) {
96
+ dataType = `{ ${dataProps.join("; ")} }`;
97
+ }
98
+ else {
99
+ dataType = "Record<string, never>";
100
+ }
101
+ output += `\n\nexport type ${(0, utils_1.pascalCase)(operationId)}Params = ${dataType};\n\n`;
102
+ }
118
103
  }
119
104
  return output;
120
105
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-query-lightbase-codegen",
3
- "version": "3.1.7",
3
+ "version": "3.1.8",
4
4
  "license": "MIT",
5
5
  "description": "Generate Axios API clients and React Query options from OpenAPI specifications",
6
6
  "exports": "./dist/index.js",
@@ -9,6 +9,34 @@ import {
9
9
  specTitle,
10
10
  } from "../utils";
11
11
 
12
+ /**
13
+ * Walks a request body schema (including allOf/oneOf/anyOf composition) and returns
14
+ * the union of property names contributed by all branches. Used to pick body fields
15
+ * out of the merged Params object so path/query/header values aren't sent as the body.
16
+ */
17
+ function collectBodyPropertyNames(
18
+ schema: OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject | undefined,
19
+ spec: OpenAPIV3.Document,
20
+ visitedRefs: Set<string> = new Set()
21
+ ): string[] {
22
+ if (!schema) return [];
23
+ if ("$ref" in schema) {
24
+ if (visitedRefs.has(schema.$ref)) return [];
25
+ const next = new Set(visitedRefs).add(schema.$ref);
26
+ return collectBodyPropertyNames(resolveSchema(schema, spec), spec, next);
27
+ }
28
+ const names = new Set<string>();
29
+ if (schema.properties) Object.keys(schema.properties).forEach((k) => names.add(k));
30
+ for (const key of ["allOf", "oneOf", "anyOf"] as const) {
31
+ const branches = schema[key];
32
+ if (!branches) continue;
33
+ for (const branch of branches) {
34
+ collectBodyPropertyNames(branch, spec, visitedRefs).forEach((n) => names.add(n));
35
+ }
36
+ }
37
+ return Array.from(names);
38
+ }
39
+
12
40
  function generateAxiosMethod(operation: OperationInfo, spec: OpenAPIV3.Document): string {
13
41
  const { method, path, operationId, summary, description, deprecated, parameters, requestBody, responses } =
14
42
  operation;
@@ -76,10 +104,15 @@ function generateAxiosMethod(operation: OperationInfo, spec: OpenAPIV3.Document)
76
104
  requestBody && "content" in requestBody ? getContentSchema(requestBody.content) : undefined;
77
105
  const requestBodySchema = requestBodyContent ? resolveSchema(requestBodyContent, spec) : undefined;
78
106
 
107
+ // Resolve property names contributed by the body schema, walking allOf/oneOf/anyOf so
108
+ // composition-based bodies are still treated as object bodies (not "primitive").
109
+ const bodyPropertyNames = requestBodySchema ? collectBodyPropertyNames(requestBodySchema, spec) : [];
110
+ const hasObjectBody = bodyPropertyNames.length > 0;
111
+
79
112
  // Check if request body is a primitive type (string, number, boolean)
80
113
  const isPrimitiveRequestBody =
81
114
  requestBodySchema &&
82
- !requestBodySchema.properties &&
115
+ !hasObjectBody &&
83
116
  !requestBodySchema.type?.includes("object") &&
84
117
  !requestBodySchema.type?.includes("array");
85
118
 
@@ -123,11 +156,9 @@ function generateAxiosMethod(operation: OperationInfo, spec: OpenAPIV3.Document)
123
156
  };`
124
157
  : "",
125
158
 
126
- requestBodySchema?.properties && !formDataSchema?.properties
159
+ hasObjectBody && !formDataSchema?.properties
127
160
  ? `const bodyData = {
128
- ${Object.entries(requestBodySchema.properties)
129
- .map(([key]) => `["${key}"]: data["${key}"]`)
130
- .join(",\n ")}
161
+ ${bodyPropertyNames.map((key) => `["${key}"]: data["${key}"]`).join(",\n ")}
131
162
  };`
132
163
  : "",
133
164
 
@@ -155,8 +186,8 @@ function generateAxiosMethod(operation: OperationInfo, spec: OpenAPIV3.Document)
155
186
  // Note: Don't set Content-Type for FormData - Axios will set it automatically with the correct boundary
156
187
  requestBody
157
188
  ? responseType === "void"
158
- ? `await apiClient.${method}<${responseType}>(url, ${formDataSchema?.properties || requestBodySchema?.properties ? "bodyData" : "data"}, axiosConfig);`
159
- : `const res = await apiClient.${method}<${responseType}>(url, ${formDataSchema?.properties || requestBodySchema?.properties ? "bodyData" : "data"}, axiosConfig);`
189
+ ? `await apiClient.${method}<${responseType}>(url, ${formDataSchema?.properties || hasObjectBody ? "bodyData" : "data"}, axiosConfig);`
190
+ : `const res = await apiClient.${method}<${responseType}>(url, ${formDataSchema?.properties || hasObjectBody ? "bodyData" : "data"}, axiosConfig);`
160
191
  : responseType === "void"
161
192
  ? `await apiClient.${method}<${responseType}>(url, axiosConfig);`
162
193
  : `const res = await apiClient.${method}<${responseType}>(url, axiosConfig);`,
@@ -1,5 +1,6 @@
1
1
  import type { OpenAPIV3 } from "openapi-types";
2
2
  import {
3
+ collectOperations,
3
4
  getContentSchema,
4
5
  getTypeFromSchema,
5
6
  pascalCase,
@@ -56,86 +57,71 @@ export function generateTypeDefinitions(spec: OpenAPIV3.Document): string {
56
57
  generatedTypes.add(name);
57
58
  }
58
59
 
59
- // Generate request/response types
60
- if (spec.paths) {
61
- for (const [path, pathItem] of Object.entries(spec.paths)) {
62
- for (const [method, operation] of Object.entries(pathItem as OpenAPIV3.PathItemObject)) {
63
- if (method === "$ref") continue;
64
-
65
- const operationObject = operation as OpenAPIV3.OperationObject;
66
- if (!operationObject) continue;
67
- const { operationId: badOperationId, requestBody, responses, parameters } = operationObject;
68
- const operationId = `${sanitizeTypeName(badOperationId || `${path.replace(/\W+/g, "_")}`)}`;
69
-
70
- // Generate request body type
71
- if (requestBody) {
72
- const content = (requestBody as OpenAPIV3.RequestBodyObject).content;
73
- const requestSchema = getContentSchema(content);
74
- if (requestSchema) {
75
- const typeName = `${operationId}Request`;
76
- output += generateTypeDefinition(typeName, requestSchema as OpenAPIV3.SchemaObject);
77
- }
78
- }
60
+ // Generate request/response types. Use the same operation-collection helper as the
61
+ // client generator so pathItem-level parameters and $ref parameters stay in sync.
62
+ for (const { operationId, parameters, requestBody, responses } of collectOperations(spec)) {
63
+ // Generate request body type
64
+ if (requestBody) {
65
+ const requestSchema = getContentSchema(requestBody.content);
66
+ if (requestSchema) {
67
+ const typeName = `${operationId}Request`;
68
+ output += generateTypeDefinition(typeName, requestSchema as OpenAPIV3.SchemaObject);
69
+ }
70
+ }
79
71
 
80
- // Generate response types
81
- const errorTypes: string[] = [];
82
- if (responses) {
83
- for (const [code, response] of Object.entries(responses)) {
84
- const responseObj = response as OpenAPIV3.ResponseObject;
85
- const responseSchema = getContentSchema(responseObj.content);
86
- if (responseSchema) {
87
- const typeName = `${operationId}Response${code}`;
88
- output += generateTypeDefinition(typeName, responseSchema as OpenAPIV3.SchemaObject);
89
-
90
- // Track non-2xx responses for error union type
91
- if (!code.startsWith("2")) {
92
- errorTypes.push(typeName);
93
- }
94
- }
72
+ // Generate response types
73
+ const errorTypes: string[] = [];
74
+ if (responses) {
75
+ for (const [code, response] of Object.entries(responses)) {
76
+ const responseObj = response as OpenAPIV3.ResponseObject;
77
+ const responseSchema = getContentSchema(responseObj.content);
78
+ if (responseSchema) {
79
+ const typeName = `${operationId}Response${code}`;
80
+ output += generateTypeDefinition(typeName, responseSchema as OpenAPIV3.SchemaObject);
81
+
82
+ // Track non-2xx responses for error union type
83
+ if (!code.startsWith("2")) {
84
+ errorTypes.push(typeName);
95
85
  }
96
86
  }
87
+ }
88
+ }
97
89
 
98
- // Generate error union type if there are error responses
99
- if (errorTypes.length > 0) {
100
- output += `export type ${pascalCase(operationId)}Error = ${errorTypes.join(" | ")};\n\n`;
101
- }
90
+ // Generate error union type if there are error responses
91
+ if (errorTypes.length > 0) {
92
+ output += `export type ${pascalCase(operationId)}Error = ${errorTypes.join(" | ")};\n\n`;
93
+ }
102
94
 
103
- // Build data type parts
104
- const dataProps: string[] = [];
105
-
106
- const urlParams = (parameters?.filter((p) => "in" in p && p.in === "path") ||
107
- []) as OpenAPIV3.ParameterObject[];
108
- const queryParams = (parameters?.filter((p) => "in" in p && p.in === "query") ||
109
- []) as OpenAPIV3.ParameterObject[];
110
- const headerParams = (parameters?.filter((p) => "in" in p && p.in === "header") ||
111
- []) as OpenAPIV3.ParameterObject[];
112
- const cookieParams = (parameters?.filter((p) => "in" in p && p.in === "cookie") ||
113
- []) as OpenAPIV3.ParameterObject[];
114
-
115
- // Add path, query, header, and cookie parameters
116
- urlParams.forEach((p) => dataProps.push(formatParamProperty(p, true))); // Path params always required
117
- queryParams.forEach((p) => dataProps.push(formatParamProperty(p)));
118
- headerParams.forEach((p) => dataProps.push(formatParamProperty(p)));
119
- cookieParams.forEach((p) => dataProps.push(formatParamProperty(p)));
120
-
121
- // Add request body type if it exists
122
- const hasData = (parameters && parameters.length > 0) || requestBody;
123
-
124
- let dataType = "undefined";
125
- const namedType = pascalCase(operationId);
126
- if (hasData) {
127
- if (requestBody && dataProps.length > 0) {
128
- dataType = `${namedType}Request & { ${dataProps.join("; ")} }`;
129
- } else if (requestBody) {
130
- dataType = `${namedType}Request`;
131
- } else if (dataProps.length > 0) {
132
- dataType = `{ ${dataProps.join("; ")} }`;
133
- } else {
134
- dataType = "Record<string, never>";
135
- }
136
- output += `\n\nexport type ${pascalCase(operationId)}Params = ${dataType};\n\n`;
137
- }
95
+ // Build data type parts
96
+ const dataProps: string[] = [];
97
+
98
+ const urlParams = parameters.filter((p) => p.in === "path");
99
+ const queryParams = parameters.filter((p) => p.in === "query");
100
+ const headerParams = parameters.filter((p) => p.in === "header");
101
+ const cookieParams = parameters.filter((p) => p.in === "cookie");
102
+
103
+ // Add path, query, header, and cookie parameters
104
+ urlParams.forEach((p) => dataProps.push(formatParamProperty(p, true))); // Path params always required
105
+ queryParams.forEach((p) => dataProps.push(formatParamProperty(p)));
106
+ headerParams.forEach((p) => dataProps.push(formatParamProperty(p)));
107
+ cookieParams.forEach((p) => dataProps.push(formatParamProperty(p)));
108
+
109
+ // Add request body type if it exists
110
+ const hasData = parameters.length > 0 || requestBody;
111
+
112
+ let dataType = "undefined";
113
+ const namedType = pascalCase(operationId);
114
+ if (hasData) {
115
+ if (requestBody && dataProps.length > 0) {
116
+ dataType = `${namedType}Request & { ${dataProps.join("; ")} }`;
117
+ } else if (requestBody) {
118
+ dataType = `${namedType}Request`;
119
+ } else if (dataProps.length > 0) {
120
+ dataType = `{ ${dataProps.join("; ")} }`;
121
+ } else {
122
+ dataType = "Record<string, never>";
138
123
  }
124
+ output += `\n\nexport type ${pascalCase(operationId)}Params = ${dataType};\n\n`;
139
125
  }
140
126
  }
141
127