typedapi-client-helpers 0.1.16 → 0.2.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.
Files changed (137) hide show
  1. package/README.md +220 -490
  2. package/dist/index.cjs +274 -47
  3. package/dist/index.d.cts +79 -95
  4. package/dist/index.d.ts +79 -95
  5. package/dist/index.js +267 -47
  6. package/package.json +30 -13
  7. package/scripts/generate-api-minimal.mjs +158 -785
  8. package/scripts/openapi-generator/controller-template.mjs +246 -0
  9. package/scripts/openapi-generator/http-client-template.mjs +34 -0
  10. package/scripts/openapi-generator/index.mjs +113 -0
  11. package/scripts/openapi-generator/method-template.mjs +440 -0
  12. package/scripts/openapi-generator/names.mjs +145 -0
  13. package/scripts/openapi-generator/openapi-utils.mjs +147 -0
  14. package/scripts/openapi-generator/schema.mjs +455 -0
  15. package/bin/typedapi-generate.js +0 -3
  16. package/dist/api/generated/Import.d.ts +0 -22
  17. package/dist/api/generated/Import.d.ts.map +0 -1
  18. package/dist/api/generated/Import.js +0 -48
  19. package/dist/api/generated/Import.js.map +0 -1
  20. package/dist/api/generated/ImportRoute.d.ts +0 -30
  21. package/dist/api/generated/ImportRoute.d.ts.map +0 -1
  22. package/dist/api/generated/ImportRoute.js +0 -13
  23. package/dist/api/generated/ImportRoute.js.map +0 -1
  24. package/dist/api/generated/Order.d.ts +0 -38
  25. package/dist/api/generated/Order.d.ts.map +0 -1
  26. package/dist/api/generated/Order.js +0 -75
  27. package/dist/api/generated/Order.js.map +0 -1
  28. package/dist/api/generated/OrderRoute.d.ts +0 -83
  29. package/dist/api/generated/OrderRoute.d.ts.map +0 -1
  30. package/dist/api/generated/OrderRoute.js +0 -13
  31. package/dist/api/generated/OrderRoute.js.map +0 -1
  32. package/dist/api/generated/Product.d.ts +0 -62
  33. package/dist/api/generated/Product.d.ts.map +0 -1
  34. package/dist/api/generated/Product.js +0 -114
  35. package/dist/api/generated/Product.js.map +0 -1
  36. package/dist/api/generated/ProductRoute.d.ts +0 -140
  37. package/dist/api/generated/ProductRoute.d.ts.map +0 -1
  38. package/dist/api/generated/ProductRoute.js +0 -13
  39. package/dist/api/generated/ProductRoute.js.map +0 -1
  40. package/dist/api/generated/Report.d.ts +0 -14
  41. package/dist/api/generated/Report.d.ts.map +0 -1
  42. package/dist/api/generated/Report.js +0 -32
  43. package/dist/api/generated/Report.js.map +0 -1
  44. package/dist/api/generated/ReportRoute.d.ts +0 -23
  45. package/dist/api/generated/ReportRoute.d.ts.map +0 -1
  46. package/dist/api/generated/ReportRoute.js +0 -13
  47. package/dist/api/generated/ReportRoute.js.map +0 -1
  48. package/dist/api/generated/Supplier.d.ts +0 -46
  49. package/dist/api/generated/Supplier.d.ts.map +0 -1
  50. package/dist/api/generated/Supplier.js +0 -87
  51. package/dist/api/generated/Supplier.js.map +0 -1
  52. package/dist/api/generated/SupplierRoute.d.ts +0 -101
  53. package/dist/api/generated/SupplierRoute.d.ts.map +0 -1
  54. package/dist/api/generated/SupplierRoute.js +0 -13
  55. package/dist/api/generated/SupplierRoute.js.map +0 -1
  56. package/dist/api/generated/data-contracts.d.ts +0 -277
  57. package/dist/api/generated/data-contracts.d.ts.map +0 -1
  58. package/dist/api/generated/data-contracts.js +0 -26
  59. package/dist/api/generated/data-contracts.js.map +0 -1
  60. package/dist/api/generated/http-client.d.ts +0 -61
  61. package/dist/api/generated/http-client.d.ts.map +0 -1
  62. package/dist/api/generated/http-client.js +0 -171
  63. package/dist/api/generated/http-client.js.map +0 -1
  64. package/dist/api/index.d.ts +0 -8
  65. package/dist/api/index.d.ts.map +0 -1
  66. package/dist/api/index.js +0 -8
  67. package/dist/api/index.js.map +0 -1
  68. package/dist/api/methods/Import.api.d.ts +0 -6
  69. package/dist/api/methods/Import.api.d.ts.map +0 -1
  70. package/dist/api/methods/Import.api.js +0 -27
  71. package/dist/api/methods/Import.api.js.map +0 -1
  72. package/dist/api/methods/Order.api.d.ts +0 -29
  73. package/dist/api/methods/Order.api.d.ts.map +0 -1
  74. package/dist/api/methods/Order.api.js +0 -52
  75. package/dist/api/methods/Order.api.js.map +0 -1
  76. package/dist/api/methods/Product.api.d.ts +0 -39
  77. package/dist/api/methods/Product.api.d.ts.map +0 -1
  78. package/dist/api/methods/Product.api.js +0 -69
  79. package/dist/api/methods/Product.api.js.map +0 -1
  80. package/dist/api/methods/Report.api.d.ts +0 -6
  81. package/dist/api/methods/Report.api.d.ts.map +0 -1
  82. package/dist/api/methods/Report.api.js +0 -19
  83. package/dist/api/methods/Report.api.js.map +0 -1
  84. package/dist/api/methods/Supplier.api.d.ts +0 -35
  85. package/dist/api/methods/Supplier.api.d.ts.map +0 -1
  86. package/dist/api/methods/Supplier.api.js +0 -63
  87. package/dist/api/methods/Supplier.api.js.map +0 -1
  88. package/dist/index.d.ts.map +0 -1
  89. package/dist/index.js.map +0 -1
  90. package/dist/interfaces/ApiResult.d.ts +0 -17
  91. package/dist/interfaces/ApiResult.d.ts.map +0 -1
  92. package/dist/interfaces/ApiResult.js +0 -2
  93. package/dist/interfaces/ApiResult.js.map +0 -1
  94. package/dist/interfaces/Filter.d.ts +0 -31
  95. package/dist/interfaces/Filter.d.ts.map +0 -1
  96. package/dist/interfaces/Filter.js +0 -2
  97. package/dist/interfaces/Filter.js.map +0 -1
  98. package/dist/interfaces/SortType.d.ts +0 -7
  99. package/dist/interfaces/SortType.d.ts.map +0 -1
  100. package/dist/interfaces/SortType.js +0 -2
  101. package/dist/interfaces/SortType.js.map +0 -1
  102. package/dist/types/ApiCallOptions.d.ts +0 -9
  103. package/dist/types/ApiCallOptions.d.ts.map +0 -1
  104. package/dist/types/ApiCallOptions.js +0 -2
  105. package/dist/types/ApiCallOptions.js.map +0 -1
  106. package/dist/types/ApiMethodTypes.d.ts +0 -24
  107. package/dist/types/ApiMethodTypes.d.ts.map +0 -1
  108. package/dist/types/ApiMethodTypes.js +0 -2
  109. package/dist/types/ApiMethodTypes.js.map +0 -1
  110. package/dist/types/HttpResponse.d.ts +0 -16
  111. package/dist/types/HttpResponse.d.ts.map +0 -1
  112. package/dist/types/HttpResponse.js +0 -2
  113. package/dist/types/HttpResponse.js.map +0 -1
  114. package/dist/utils/SortTypeFromSortDirection.d.ts +0 -22
  115. package/dist/utils/SortTypeFromSortDirection.d.ts.map +0 -1
  116. package/dist/utils/SortTypeFromSortDirection.js +0 -45
  117. package/dist/utils/SortTypeFromSortDirection.js.map +0 -1
  118. package/dist/utils/buildQuery.d.ts +0 -11
  119. package/dist/utils/buildQuery.d.ts.map +0 -1
  120. package/dist/utils/buildQuery.js +0 -94
  121. package/dist/utils/buildQuery.js.map +0 -1
  122. package/dist/utils/extractArgsCallbacksAndParams.d.ts +0 -22
  123. package/dist/utils/extractArgsCallbacksAndParams.d.ts.map +0 -1
  124. package/dist/utils/extractArgsCallbacksAndParams.js +0 -44
  125. package/dist/utils/extractArgsCallbacksAndParams.js.map +0 -1
  126. package/dist/utils/extractArgsToastsAndParams.d.ts +0 -18
  127. package/dist/utils/extractArgsToastsAndParams.d.ts.map +0 -1
  128. package/dist/utils/extractArgsToastsAndParams.js +0 -40
  129. package/dist/utils/extractArgsToastsAndParams.js.map +0 -1
  130. package/dist/utils/handleApiResponse.d.ts +0 -9
  131. package/dist/utils/handleApiResponse.d.ts.map +0 -1
  132. package/dist/utils/handleApiResponse.js +0 -49
  133. package/dist/utils/handleApiResponse.js.map +0 -1
  134. package/dist/utils/toFormData.d.ts +0 -8
  135. package/dist/utils/toFormData.d.ts.map +0 -1
  136. package/dist/utils/toFormData.js +0 -68
  137. package/dist/utils/toFormData.js.map +0 -1
@@ -0,0 +1,246 @@
1
+ import { getOperationTypes, generatedFileHeader } from "./schema.mjs";
2
+ import { pascalCase, propertyKey, sanitizeIdentifier } from "./names.mjs";
3
+
4
+ function unique(values) {
5
+ return [...new Set(values.filter(Boolean))];
6
+ }
7
+
8
+ function createImport(names, from, typeOnly = false) {
9
+ const cleanNames = unique(names).sort((a, b) => a.localeCompare(b));
10
+ if (cleanNames.length === 0) return "";
11
+ const keyword = typeOnly ? "import type" : "import";
12
+ return `${keyword} { ${cleanNames.join(", ")} } from ${JSON.stringify(from)};`;
13
+ }
14
+
15
+ function pathExpression(routePath, pathParams) {
16
+ const parts = [];
17
+ let cursor = 0;
18
+ const regex = /\{([^}]+)\}/g;
19
+ let match;
20
+
21
+ while ((match = regex.exec(routePath))) {
22
+ const [placeholder, paramName] = match;
23
+ const before = routePath.slice(cursor, match.index);
24
+ if (before) parts.push(JSON.stringify(before));
25
+ parts.push(sanitizeIdentifier(paramName));
26
+ cursor = match.index + placeholder.length;
27
+ }
28
+
29
+ const after = routePath.slice(cursor);
30
+ if (after) parts.push(JSON.stringify(after));
31
+
32
+ if (parts.length === 0) {
33
+ return JSON.stringify(routePath);
34
+ }
35
+
36
+ return parts
37
+ .map((part) =>
38
+ /^"/.test(part) ? part.slice(1, -1).replace(/`/g, "\\`") : `\${${part}}`,
39
+ )
40
+ .join("")
41
+ .replace(/^/, "`")
42
+ .replace(/$/, "`");
43
+ }
44
+
45
+ function requestFormat(responseContentType, responseType) {
46
+ if (responseType === "void") return undefined;
47
+ if (!responseContentType) return undefined;
48
+ if (responseContentType.includes("json")) return "json";
49
+ if (responseContentType.startsWith("text/")) return "text";
50
+ if (
51
+ responseContentType === "application/octet-stream" ||
52
+ responseType === "Blob"
53
+ )
54
+ return "blob";
55
+ return undefined;
56
+ }
57
+
58
+ function contentTypeEnum(contentType) {
59
+ switch (contentType) {
60
+ case "application/json":
61
+ return "ContentType.Json";
62
+ case "application/vnd.api+json":
63
+ return "ContentType.JsonApi";
64
+ case "multipart/form-data":
65
+ return "ContentType.FormData";
66
+ case "application/x-www-form-urlencoded":
67
+ return "ContentType.UrlEncoded";
68
+ case "text/plain":
69
+ return "ContentType.Text";
70
+ default:
71
+ return contentType?.includes("json") ? "ContentType.Json" : undefined;
72
+ }
73
+ }
74
+
75
+ function destructuredParams(operationTypes) {
76
+ const parameters = [
77
+ ...operationTypes.pathParams,
78
+ ...operationTypes.queryParams,
79
+ ];
80
+ return parameters.map((parameter) => sanitizeIdentifier(parameter.name));
81
+ }
82
+
83
+ function queryExpression(operationTypes) {
84
+ if (!operationTypes.hasQueryParams) return undefined;
85
+
86
+ if (!operationTypes.hasPathParams) {
87
+ return "query";
88
+ }
89
+
90
+ const entries = operationTypes.queryParams.map((parameter) => {
91
+ const identifier = sanitizeIdentifier(parameter.name);
92
+ return `${JSON.stringify(parameter.name)}: ${identifier}`;
93
+ });
94
+
95
+ return `{ ${entries.join(", ")} }`;
96
+ }
97
+
98
+ function methodArguments(operationTypes) {
99
+ const args = [];
100
+
101
+ if (operationTypes.paramsTypeName) {
102
+ if (operationTypes.hasPathParams) {
103
+ args.push(
104
+ `{ ${destructuredParams(operationTypes).join(", ")} }: ${operationTypes.paramsTypeName}`,
105
+ );
106
+ } else {
107
+ args.push(`query: ${operationTypes.paramsTypeName} = {}`);
108
+ }
109
+ }
110
+
111
+ if (operationTypes.bodyType) {
112
+ args.push(`data: ${operationTypes.bodyType}`);
113
+ }
114
+
115
+ args.push("params: RequestParams = {}");
116
+ return args.join(", ");
117
+ }
118
+
119
+ function createMethod(operationInfo, operationTypes) {
120
+ const requestItems = [
121
+ `path: ${pathExpression(operationInfo.routePath, operationTypes.pathParams)}`,
122
+ `method: ${JSON.stringify(operationInfo.method)}`,
123
+ ];
124
+
125
+ const query = queryExpression(operationTypes);
126
+ if (query) requestItems.push(`query: ${query}`);
127
+
128
+ if (operationTypes.bodyType) {
129
+ requestItems.push("body: data");
130
+ }
131
+
132
+ const type = contentTypeEnum(operationTypes.contentType);
133
+ if (type) requestItems.push(`type: ${type}`);
134
+
135
+ const format = requestFormat(
136
+ operationTypes.responseContentType,
137
+ operationTypes.responseType,
138
+ );
139
+ if (format) requestItems.push(`format: ${JSON.stringify(format)}`);
140
+
141
+ requestItems.push("...params");
142
+
143
+ const description =
144
+ operationInfo.operation.description ||
145
+ operationInfo.operation.summary ||
146
+ "No description";
147
+ const tags = (operationInfo.operation.tags ?? []).join(", ");
148
+ const requestItemsText = requestItems
149
+ .map((item) => ` ${item},`)
150
+ .join("\n");
151
+
152
+ return `
153
+ /**
154
+ * ${description.replace(/\n/g, "\n * ")}
155
+ *
156
+ * @tags ${tags}
157
+ * @name ${operationTypes.operationId}
158
+ * @request ${operationInfo.method}:${operationInfo.routePath}
159
+ */
160
+ ${operationTypes.methodName} = (${methodArguments(operationTypes)}): Promise<HttpResponse<${operationTypes.responseType}, any>> =>
161
+ this.request<${operationTypes.responseType}, any>({
162
+ ${requestItemsText}
163
+ });`.trimEnd();
164
+ }
165
+
166
+ export function generateController(
167
+ openApi,
168
+ controllerName,
169
+ operations,
170
+ options = {},
171
+ ) {
172
+ const dataContractImports = [];
173
+ const methods = [];
174
+ let needsContentType = false;
175
+
176
+ for (const operationInfo of operations) {
177
+ const operationTypes = getOperationTypes(openApi, operationInfo, options);
178
+ if (!operationTypes.operationId || !operationTypes.methodName) continue;
179
+
180
+ if (operationTypes.paramsTypeName)
181
+ dataContractImports.push(operationTypes.paramsTypeName);
182
+ if (
183
+ operationTypes.bodyType &&
184
+ operationTypes.bodyType !== "Blob" &&
185
+ operationTypes.bodyType !== "File"
186
+ ) {
187
+ dataContractImports.push(operationTypes.bodyType);
188
+ }
189
+ if (!isPrimitiveType(operationTypes.responseType)) {
190
+ dataContractImports.push(
191
+ ...extractTopLevelTypeNames(operationTypes.responseType),
192
+ );
193
+ }
194
+ if (contentTypeEnum(operationTypes.contentType)) needsContentType = true;
195
+
196
+ methods.push(createMethod(operationInfo, operationTypes));
197
+ }
198
+
199
+ const useTypeOnlyImports = options.useTypeOnlyImports !== false;
200
+ const imports = [
201
+ generatedFileHeader(),
202
+ createImport(
203
+ [...(needsContentType ? ["ContentType"] : []), "HttpClient"],
204
+ "../http-client",
205
+ ),
206
+ createImport(
207
+ ["HttpResponse", "RequestParams"],
208
+ "../http-client",
209
+ useTypeOnlyImports,
210
+ ),
211
+ createImport(dataContractImports, "../data-contracts", useTypeOnlyImports),
212
+ ].filter(Boolean);
213
+
214
+ return `${imports.join("\n")}\n\nexport class ${controllerName}<SecurityDataType = unknown> extends HttpClient<SecurityDataType> {\n${methods.map((method) => `${method}\n`).join("\n")}\n}\n`;
215
+ }
216
+
217
+ function isPrimitiveType(type) {
218
+ return /^(void|any|unknown|string|number|boolean|Blob|File|Record<)/.test(
219
+ type,
220
+ );
221
+ }
222
+
223
+ function extractTopLevelTypeNames(type) {
224
+ const withoutLiterals = type.replace(/"(?:\\.|[^"])*"|'(?:\\.|[^'])*'/g, "");
225
+ const matches = withoutLiterals.match(/\b[A-Z][A-Za-z0-9_]*\b/g) ?? [];
226
+ return matches.filter(
227
+ (name) =>
228
+ !["Array", "Record", "Promise", "HttpResponse", "Blob", "File"].includes(
229
+ name,
230
+ ),
231
+ );
232
+ }
233
+
234
+ export function controllerNameForOperation(operationInfo, options = {}) {
235
+ if (options.moduleNameFirstTag !== false) {
236
+ const [tag] = operationInfo.operation.tags ?? [];
237
+ if (tag) return pascalCase(tag, "Default");
238
+ }
239
+
240
+ const firstPathPart =
241
+ operationInfo.routePath.split("/").filter(Boolean)[1] ??
242
+ operationInfo.routePath.split("/").filter(Boolean)[0] ??
243
+ "Default";
244
+
245
+ return pascalCase(firstPathPart, "Default");
246
+ }
@@ -0,0 +1,34 @@
1
+ import { generatedFileHeader } from "./schema.mjs";
2
+
3
+ export function generateHttpClient(openApi, options = {}) {
4
+ const baseUrl = options.baseUrl ?? openApi.servers?.[0]?.url ?? "";
5
+ const runtimePackageName =
6
+ options.runtimePackageName ?? "typedapi-client-helpers";
7
+
8
+ return `${generatedFileHeader()}
9
+
10
+ import { createApiClient } from ${JSON.stringify(runtimePackageName)};
11
+
12
+ export { ContentType } from ${JSON.stringify(runtimePackageName)};
13
+
14
+ export type {
15
+ ApiConfig,
16
+ CancelToken,
17
+ FullRequestParams,
18
+ HttpResponse,
19
+ QueryParamsType,
20
+ RequestParams,
21
+ ResponseFormat,
22
+ RuntimeRequestParams,
23
+ } from ${JSON.stringify(runtimePackageName)};
24
+
25
+ const apiClient = createApiClient({
26
+ baseUrl: ${JSON.stringify(baseUrl)},
27
+ });
28
+
29
+ export const configureApiClient = apiClient.configureApiClient;
30
+ export const setSecurityData = apiClient.setSecurityData;
31
+ export const abortRequest = apiClient.abortRequest;
32
+ export const request = apiClient.request;
33
+ `;
34
+ }
@@ -0,0 +1,113 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { collectOperations } from "./openapi-utils.mjs";
4
+ import { generateDataContracts } from "./schema.mjs";
5
+ import { generateHttpClient } from "./http-client-template.mjs";
6
+ import {
7
+ controllerNameForOperation,
8
+ generateMethodFile,
9
+ methodFileNameForController,
10
+ } from "./method-template.mjs";
11
+
12
+ export async function generateApi(options) {
13
+ const inputPath = path.resolve(options.input);
14
+ const output = path.resolve(options.output);
15
+ const openApi = JSON.parse(fs.readFileSync(inputPath, "utf8"));
16
+
17
+ if (!openApi.paths || typeof openApi.paths !== "object") {
18
+ throw new Error(
19
+ "The OpenAPI document does not contain a valid paths object.",
20
+ );
21
+ }
22
+
23
+ const generatedDir = path.join(output, "generated");
24
+ const methodsDir = path.join(output, "methods");
25
+
26
+ if (options.cleanOutput !== false) {
27
+ cleanApiOutput(output);
28
+ }
29
+
30
+ fs.mkdirSync(generatedDir, { recursive: true });
31
+ fs.mkdirSync(methodsDir, { recursive: true });
32
+
33
+ const operations = collectOperations(openApi).filter(
34
+ (operationInfo) => operationInfo.operation?.operationId,
35
+ );
36
+
37
+ const normalizedOptions = {
38
+ moduleNameFirstTag: options.moduleNameFirstTag !== false,
39
+ defaultResponseAsSuccess: Boolean(options.defaultResponseAsSuccess),
40
+ generateUnionEnums: Boolean(options.generateUnionEnums),
41
+ enumNamesAsValues: Boolean(options.enumNamesAsValues),
42
+ useTypeOnlyImports: options.useTypeOnlyImports !== false,
43
+ useFilterFormValues: options.useFilterFormValues !== false,
44
+ baseUrl: options.baseUrl,
45
+ runtimePackageName: options.runtimePackageName,
46
+ defaultHandlers: options.defaultHandlers,
47
+ };
48
+
49
+ fs.writeFileSync(
50
+ path.join(generatedDir, "data-contracts.ts"),
51
+ generateDataContracts(openApi, operations, normalizedOptions),
52
+ );
53
+
54
+ fs.writeFileSync(
55
+ path.join(generatedDir, "http-client.ts"),
56
+ generateHttpClient(openApi, normalizedOptions),
57
+ );
58
+
59
+ const controllers = new Map();
60
+ for (const operationInfo of operations) {
61
+ const controllerName = controllerNameForOperation(
62
+ operationInfo,
63
+ normalizedOptions,
64
+ );
65
+ const controllerOperations = controllers.get(controllerName) ?? [];
66
+ controllerOperations.push(operationInfo);
67
+ controllers.set(controllerName, controllerOperations);
68
+ }
69
+
70
+ const controllerNames = [...controllers.keys()].sort((a, b) =>
71
+ a.localeCompare(b),
72
+ );
73
+
74
+ for (const controllerName of controllerNames) {
75
+ fs.writeFileSync(
76
+ path.join(methodsDir, methodFileNameForController(controllerName)),
77
+ generateMethodFile(
78
+ openApi,
79
+ controllerName,
80
+ controllers.get(controllerName),
81
+ normalizedOptions,
82
+ ),
83
+ );
84
+ }
85
+
86
+
87
+ return {
88
+ files: [
89
+ path.join(generatedDir, "data-contracts.ts"),
90
+ path.join(generatedDir, "http-client.ts"),
91
+ ...controllerNames.map((controllerName) =>
92
+ path.join(methodsDir, methodFileNameForController(controllerName)),
93
+ ),
94
+ ],
95
+ };
96
+ }
97
+
98
+ function cleanApiOutput(output) {
99
+ const removable = [
100
+ path.join(output, "controllers"),
101
+ path.join(output, "generated"),
102
+ path.join(output, "methods"),
103
+ path.join(output, "data-contracts.ts"),
104
+ path.join(output, "http-client.ts"),
105
+ path.join(output, "index.ts"),
106
+ ];
107
+
108
+ for (const item of removable) {
109
+ if (fs.existsSync(item)) {
110
+ fs.rmSync(item, { recursive: true, force: true });
111
+ }
112
+ }
113
+ }