typedapi-client-helpers 0.1.11 → 0.1.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -68,7 +68,7 @@ Configuration can be supplied in the `config` section of your `package.json`, wi
68
68
  | `apiOutput` | `src/api` | Directory where the generated API files are written. |
69
69
  | `typedApiUseTypeOnlyImports` | `false` | Generates `import type` statements for imports that are only used as types. |
70
70
  | `typedApiUseFilterFormValues` | `false` | Generates paginated methods that accept `FilterFormValues[]`, page values, and sort values instead of a prebuilt query object. |
71
- | `typedApiDefaultFunctionsPath` | `src/defaultApiFunctions` | Import path used by generated method files for shared success and error handlers. |
71
+ | `typedApiDefaultFunctionsPath` | `defaultApiFunctions` | Import path used by generated method files for shared success and error handlers. |
72
72
  | `typedApiDefaultSuccessHandler` | `handleGoodResult` | Name of the default success handler function imported into generated methods. |
73
73
  | `typedApiDefaultErrorHandler` | `handleErrors` | Name of the default error handler function imported into generated methods. |
74
74
 
@@ -118,12 +118,12 @@ src/
118
118
  └── index.ts
119
119
  ```
120
120
 
121
- | Folder or file | Description |
122
- | ------------------------ | --------------------------------------------------------------------------------------------------------------- |
123
- | `generated/` | Raw client, models, route classes, and HTTP client generated by `swagger-typescript-api`. |
124
- | `methods/` | Typed wrapper methods generated by this package. These call the raw generated client and return `ApiResult<T>`. |
125
- | `index.ts` | Barrel file that exports generated contracts, HTTP client types, and wrapper methods. |
126
- | `defaultApiFunctions.ts` | Optional shared success and error handlers created when the configured default handler file does not exist. |
121
+ | Folder or file | Description |
122
+ | ------------------------ | ------------------------------------------------------------------------------------------------------------------------- |
123
+ | `generated/` | Raw client, models, and HTTP client generated by `swagger-typescript-api`. Route type files are removed after generation. |
124
+ | `methods/` | Typed wrapper methods generated by this package. These call the raw generated client and return `ApiResult<T>`. |
125
+ | `index.ts` | Barrel file that exports generated contracts, HTTP client types, and wrapper methods. |
126
+ | `defaultApiFunctions.ts` | Optional shared success and error handlers created when the configured default handler file does not exist. |
127
127
 
128
128
  ## Calling generated methods
129
129
 
@@ -148,36 +148,84 @@ Checking `result.ok` narrows the result automatically.
148
148
 
149
149
  ## Generated method argument order
150
150
 
151
- Generated wrappers preserve the original generated API arguments and then add optional wrapper-only arguments at the end.
151
+ Generated wrappers preserve the original generated API arguments and then add an optional method options object.
152
152
 
153
153
  For query methods:
154
154
 
155
155
  ```ts
156
- await getSuppliers(query, onSuccess, onError, params);
156
+ await getSuppliers(query, {
157
+ onSuccess,
158
+ onError,
159
+ params,
160
+ });
161
+ ```
162
+
163
+ For query methods without a query object, pass `undefined` as the first argument when you want to provide options:
164
+
165
+ ```ts
166
+ await exportPartners(undefined, {
167
+ onError: handleError,
168
+ });
157
169
  ```
158
170
 
159
171
  For non-query methods:
160
172
 
161
173
  ```ts
162
- await updateSupplier(id, body, onSuccess, onError, params);
174
+ await updateSupplier(id, body, {
175
+ onSuccess,
176
+ onError,
177
+ params,
178
+ });
179
+ ```
180
+
181
+ For multipart/form-data methods:
182
+
183
+ ```ts
184
+ await uploadSupplierLogo(data, {
185
+ onSuccess,
186
+ onError,
187
+ params,
188
+ });
163
189
  ```
164
190
 
165
191
  For filter-form paginated methods when `typedApiUseFilterFormValues` is enabled:
166
192
 
167
193
  ```ts
168
- await getSuppliers(
169
- filters,
170
- page,
171
- pageSize,
172
- sortBy,
173
- sortDirection,
194
+ await getSuppliers(filters, page, pageSize, sortBy, sortDirection, {
174
195
  onSuccess,
175
196
  onError,
176
197
  params,
177
- );
198
+ });
178
199
  ```
179
200
 
180
- The final `params` object is forwarded to the generated HTTP client and can contain values such as headers or other request options.
201
+ The `params` property is forwarded to the generated HTTP client and can contain values such as headers or other request options.
202
+
203
+ Every generated wrapper method gets its own options type. The type name is based on the method name.
204
+
205
+ ```ts
206
+ export type GetSuppliersOptions = {
207
+ onSuccess?: ApiSuccessHandler<GetSuppliersResponse>;
208
+ onError?: ApiErrorHandler<GetSuppliersError>;
209
+ params?: RequestParams;
210
+ };
211
+ ```
212
+
213
+ Examples of generated option type names:
214
+
215
+ ```ts
216
+ GetSuppliersOptions;
217
+ CreateSupplierOptions;
218
+ UpdateSupplierOptions;
219
+ ExportPartnersOptions;
220
+ ```
221
+
222
+ This makes it possible to pass only `onError` without also passing `onSuccess`.
223
+
224
+ ```ts
225
+ await getSuppliers(undefined, {
226
+ onError: handleError,
227
+ });
228
+ ```
181
229
 
182
230
  ## Success and error callbacks
183
231
 
@@ -195,28 +243,98 @@ function onError(result: ApiErrorResult<SupplierListResponse>) {
195
243
  console.error(result.status, result.error);
196
244
  }
197
245
 
198
- await getSuppliers({ pageNumber: 1, pageSize: 25 }, onSuccess, onError);
246
+ await getSuppliers(
247
+ { pageNumber: 1, pageSize: 25 },
248
+ {
249
+ onSuccess,
250
+ onError,
251
+ },
252
+ );
199
253
  ```
200
254
 
201
255
  The generated method still returns `ApiResult<T>` after callbacks run.
202
256
 
257
+ You can also pass only one callback.
258
+
259
+ ```ts
260
+ await getSuppliers(
261
+ { pageNumber: 1, pageSize: 25 },
262
+ {
263
+ onError,
264
+ },
265
+ );
266
+ ```
267
+
268
+ When default handlers are configured, generated methods use them when no method-specific callback is supplied.
269
+
270
+ ```ts
271
+ await getSuppliers(
272
+ { pageNumber: 1, pageSize: 25 },
273
+ {
274
+ onSuccess: customSuccessHandler,
275
+ },
276
+ );
277
+ ```
278
+
279
+ In this example, the custom success handler is used for success results, and the configured default error handler is still used for error results.
280
+
203
281
  ## Custom request parameters
204
282
 
205
- The final wrapper argument can contain request parameters supported by the generated HTTP client.
283
+ The method options object can contain request parameters supported by the generated HTTP client.
206
284
 
207
285
  ```ts
208
286
  const result = await getSuppliers(
209
287
  { pageNumber: 1, pageSize: 25 },
210
- undefined,
211
- undefined,
212
288
  {
213
- headers: {
214
- Authorization: `Bearer ${token}`,
289
+ params: {
290
+ headers: {
291
+ Authorization: `Bearer ${token}`,
292
+ },
293
+ },
294
+ },
295
+ );
296
+ ```
297
+
298
+ Request parameters can also be combined with callbacks.
299
+
300
+ ```ts
301
+ const result = await getSuppliers(
302
+ { pageNumber: 1, pageSize: 25 },
303
+ {
304
+ onError: handleError,
305
+ params: {
306
+ headers: {
307
+ Authorization: `Bearer ${token}`,
308
+ },
215
309
  },
216
310
  },
217
311
  );
218
312
  ```
219
313
 
314
+ ## Query methods without required filters
315
+
316
+ Some generated API methods require a query object at the raw generated client level, even when all query properties are optional.
317
+
318
+ The wrapper methods allow the query argument to be omitted. Internally, they pass an empty object to the raw generated method.
319
+
320
+ ```ts
321
+ await exportPartners();
322
+ ```
323
+
324
+ is handled like:
325
+
326
+ ```ts
327
+ await exportPartners({});
328
+ ```
329
+
330
+ When you want to pass options but no query, pass `undefined` as the first argument.
331
+
332
+ ```ts
333
+ await exportPartners(undefined, {
334
+ onError: handleError,
335
+ });
336
+ ```
337
+
220
338
  ## Filter-form generation
221
339
 
222
340
  Set `typedApiUseFilterFormValues` to `true` to generate paginated methods that accept filter form values.
@@ -248,6 +366,19 @@ const filters: FilterFormValues<GetSuppliersQuery>[] = [
248
366
  const result = await getSuppliers(filters, 1, 25, "name", "Ascending");
249
367
  ```
250
368
 
369
+ You can also pass method options as the final argument.
370
+
371
+ ```ts
372
+ const result = await getSuppliers(filters, 1, 25, "name", "Ascending", {
373
+ onError: handleError,
374
+ params: {
375
+ headers: {
376
+ Authorization: `Bearer ${token}`,
377
+ },
378
+ },
379
+ });
380
+ ```
381
+
251
382
  Empty filter values are ignored by `buildQuery`.
252
383
 
253
384
  ## Multipart uploads
@@ -278,29 +409,30 @@ Arrays are appended as repeated fields, files keep their filename, primitives be
278
409
 
279
410
  ### Filter and sort types
280
411
 
281
- | Export | Description |
282
- | -------------------------- | ---------------------------------------------------------------------------------------------------------------- |
283
- | `FilterType` | Union of supported filter input types: number, date, string, timespan, boolean, boolean-button, and OptionValue. |
284
- | `OptionValue` | Option object with a display `name` and submitted `value`. |
285
- | `FilterFormValues<TQuery>` | Configuration object for one filter field mapped to a query property. |
286
- | `SortType` | UI sort state: Default, Neutral, Ascending, or Descending. |
287
- | `SortDirection` | Sort direction value accepted by generated query objects. Supports string or number values. |
288
- | `ApiSortDirection` | Sort direction values accepted by the generated API: Default, Ascending, or Descending. |
289
- | `sortTypes` | Constant list of all supported UI sort states. |
412
+ | Export | Description |
413
+ | -------------------------- | ------------------------------------------------------------------------------------------- |
414
+ | `FilterType` | Union of supported filter input types used by generated filter-form methods. |
415
+ | `OptionValue` | Option object with a display `name` and submitted `value`. |
416
+ | `FilterFormValues<TQuery>` | Configuration object for one filter field mapped to a query property. |
417
+ | `SortType` | UI sort state: Default, Neutral, Ascending, or Descending. |
418
+ | `SortDirection` | Sort direction value accepted by generated query objects. Supports string or number values. |
419
+ | `ApiSortDirection` | Sort direction values accepted by the generated API: Default, Ascending, or Descending. |
420
+ | `sortTypes` | Constant list of all supported UI sort states. |
290
421
 
291
422
  ### Callback and method types
292
423
 
293
- | Export | Description |
294
- | ------------------------------- | -------------------------------------------------------------------------------------------- |
295
- | `ApiSuccessHandler<TResponse>` | Callback type for successful API results. |
296
- | `ApiErrorHandler<TResponse>` | Callback type for failed API results. |
297
- | `ApiMethodCallbacks<TResponse>` | Object shape for optional success callback, error callback, and request params. |
298
- | `ExtractResponse<T>` | Extracts the response body type from a generated method promise. |
299
- | `ExtractError<T>` | Extracts the error body type from a generated method promise. |
300
- | `UnwrapArray<T>` | Extracts the item type from an array, otherwise keeps the original type. |
301
- | `ExtractDataIfPaginated<T>` | Extracts the item type from a `{ data?: T[] }` paginated response. |
302
- | `SortableKeys<T>` | Produces keys that can be used as sortable fields for a response type. |
303
- | `WithoutRequestParams<T>` | Removes a trailing optional `RequestParams` argument from a generated method argument tuple. |
424
+ | Export | Description |
425
+ | ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
426
+ | `ApiSuccessHandler<TResponse>` | Callback type for successful API results. |
427
+ | `ApiErrorHandler<TResponse>` | Callback type for failed API results. |
428
+ | `ApiMethodCallbacks<TResponse>` | Object shape containing optional `onSuccess`, `onError`, and `params` properties. |
429
+ | Generated method options, such as `GetSuppliersOptions` | Per-method options object generated for wrapper methods. Contains optional `onSuccess`, `onError`, and `params`. |
430
+ | `ExtractResponse<T>` | Extracts the response body type from a generated method promise. |
431
+ | `ExtractError<T>` | Extracts the error body type from a generated method promise. |
432
+ | `UnwrapArray<T>` | Extracts the item type from an array, otherwise keeps the original type. |
433
+ | `ExtractDataIfPaginated<T>` | Extracts the item type from a `{ data?: T[] }` paginated response. |
434
+ | `SortableKeys<T>` | Produces keys that can be used as sortable fields for a response type. |
435
+ | `WithoutRequestParams<T>` | Removes a trailing optional `RequestParams` argument from a generated method argument tuple. |
304
436
 
305
437
  ### HTTP types
306
438
 
@@ -311,16 +443,16 @@ Arrays are appended as repeated fields, files keep their filename, primitives be
311
443
 
312
444
  ### Utility functions
313
445
 
314
- | Export | Description |
315
- | ---------------------------------- | ----------------------------------------------------------------------------------------------------------- |
316
- | `buildQuery<TQuery, TSortModel>()` | Builds a query object from filter form values, page values, and sort values. |
317
- | `extractArgsCallbacksAndParams()` | Splits wrapper arguments into original API arguments, success callback, error callback, and request params. |
318
- | `extractArgsToastsAndParams()` | Backwards-compatible extractor for wrapper arguments with success/error handlers and request params. |
319
- | `handleApiResponse()` | Executes a generated API call and converts the response or thrown error into `ApiResult<T>`. |
320
- | `HandleApiResponseOptions<T>` | Options object for passing success and error callbacks to `handleApiResponse`. |
321
- | `getSortTypeFromSortDirection()` | Converts an API sort direction into a UI `SortType`. |
322
- | `getSortDirectionFromSortType()` | Converts a UI `SortType` into an API sort direction. |
323
- | `toFormData()` | Converts an object payload into `FormData`. |
446
+ | Export | Description |
447
+ | ---------------------------------- | ---------------------------------------------------------------------------------------------------- |
448
+ | `buildQuery<TQuery, TSortModel>()` | Builds a query object from filter form values, page values, and sort values. |
449
+ | `extractArgsCallbacksAndParams()` | Backwards-compatible helper for older generated files that used positional callbacks. |
450
+ | `extractArgsToastsAndParams()` | Backwards-compatible extractor for wrapper arguments with success/error handlers and request params. |
451
+ | `handleApiResponse()` | Executes a generated API call and converts the response or thrown error into `ApiResult<T>`. |
452
+ | `HandleApiResponseOptions<T>` | Options object for passing success and error callbacks to `handleApiResponse`. |
453
+ | `getSortTypeFromSortDirection()` | Converts an API sort direction into a UI `SortType`. |
454
+ | `getSortDirectionFromSortType()` | Converts a UI `SortType` into an API sort direction. |
455
+ | `toFormData()` | Converts an object payload into `FormData`. |
324
456
 
325
457
  ### Default callback functions
326
458
 
@@ -338,8 +470,6 @@ Additional README files are included in these folders:
338
470
  | `src/interfaces/README.md` | Documents exported interfaces and result/filter/sort types. |
339
471
  | `src/types/README.md` | Documents helper types used by generated wrapper methods. |
340
472
  | `src/utils/README.md` | Documents runtime helper functions. |
341
- | `scripts/README.md` | Documents the API generator script. |
342
- | `bin/README.md` | Documents the command-line entry point. |
343
473
 
344
474
  ## Build the package
345
475
 
@@ -355,4 +485,6 @@ npm run generate:api
355
485
 
356
486
  ## Notes
357
487
 
358
- Generated files inside `src/api` and `dist/api` depend on the Swagger document used during generation. Their exact method names, model names, and route files will change when the OpenAPI document changes.
488
+ Generated files inside `src/api` and `dist/api` depend on the Swagger document used during generation. Their exact method names and model names will change when the OpenAPI document changes.
489
+
490
+ Generated route type files ending in `Route.ts` are removed from the generated output.
package/dist/index.d.cts CHANGED
@@ -189,6 +189,17 @@ type ApiMethodCallbacks<TResponse> = {
189
189
  params?: RequestParams;
190
190
  };
191
191
 
192
+ type ApiMethodOptions<TMethodReturn, TRequestParams = RuntimeRequestParams> = {
193
+ onSuccess?: ApiSuccessHandler<ExtractResponse<TMethodReturn>>;
194
+ onError?: ApiErrorHandler<ExtractError<TMethodReturn>>;
195
+ params?: TRequestParams;
196
+ };
197
+
198
+ type ApiMethodArguments<TMethod extends (...args: any[]) => unknown, TRequestParams = RuntimeRequestParams> = Parameters<TMethod> extends [
199
+ ...infer Arguments,
200
+ TRequestParams?
201
+ ] ? Arguments : Parameters<TMethod>;
202
+
192
203
  /**
193
204
  * Sort direction value accepted by generated query objects.
194
205
  *
@@ -287,4 +298,4 @@ declare function getSortDirectionFromSortType(sortType: SortType | null | undefi
287
298
  */
288
299
  declare function toFormData<T extends object>(payload: T): FormData;
289
300
 
290
- export { type ApiErrorHandler, type ApiErrorResult, type ApiMethodCallbacks, type ApiResult, type ApiSortDirection, type ApiSuccessHandler, type ApiSuccessResult, type ExtractDataIfPaginated, type ExtractError, type ExtractResponse, type FilterFormValues, type FilterType, type HandleApiResponseOptions, type HttpResponse, type OptionValue, type RuntimeRequestParams, type SortDirection, type SortType, type SortableKeys, type UnwrapArray, type WithoutRequestParams, buildQuery, extractArgsCallbacksAndParams, extractArgsToastsAndParams, getSortDirectionFromSortType, getSortTypeFromSortDirection, handleApiResponse, sortTypes, toFormData };
301
+ export { type ApiErrorHandler, type ApiErrorResult, type ApiMethodArguments, type ApiMethodCallbacks, type ApiMethodOptions, type ApiResult, type ApiSortDirection, type ApiSuccessHandler, type ApiSuccessResult, type ExtractDataIfPaginated, type ExtractError, type ExtractResponse, type FilterFormValues, type FilterType, type HandleApiResponseOptions, type HttpResponse, type OptionValue, type RuntimeRequestParams, type SortDirection, type SortType, type SortableKeys, type UnwrapArray, type WithoutRequestParams, buildQuery, extractArgsCallbacksAndParams, extractArgsToastsAndParams, getSortDirectionFromSortType, getSortTypeFromSortDirection, handleApiResponse, sortTypes, toFormData };
package/dist/index.d.ts CHANGED
@@ -189,6 +189,17 @@ type ApiMethodCallbacks<TResponse> = {
189
189
  params?: RequestParams;
190
190
  };
191
191
 
192
+ type ApiMethodOptions<TMethodReturn, TRequestParams = RuntimeRequestParams> = {
193
+ onSuccess?: ApiSuccessHandler<ExtractResponse<TMethodReturn>>;
194
+ onError?: ApiErrorHandler<ExtractError<TMethodReturn>>;
195
+ params?: TRequestParams;
196
+ };
197
+
198
+ type ApiMethodArguments<TMethod extends (...args: any[]) => unknown, TRequestParams = RuntimeRequestParams> = Parameters<TMethod> extends [
199
+ ...infer Arguments,
200
+ TRequestParams?
201
+ ] ? Arguments : Parameters<TMethod>;
202
+
192
203
  /**
193
204
  * Sort direction value accepted by generated query objects.
194
205
  *
@@ -287,4 +298,4 @@ declare function getSortDirectionFromSortType(sortType: SortType | null | undefi
287
298
  */
288
299
  declare function toFormData<T extends object>(payload: T): FormData;
289
300
 
290
- export { type ApiErrorHandler, type ApiErrorResult, type ApiMethodCallbacks, type ApiResult, type ApiSortDirection, type ApiSuccessHandler, type ApiSuccessResult, type ExtractDataIfPaginated, type ExtractError, type ExtractResponse, type FilterFormValues, type FilterType, type HandleApiResponseOptions, type HttpResponse, type OptionValue, type RuntimeRequestParams, type SortDirection, type SortType, type SortableKeys, type UnwrapArray, type WithoutRequestParams, buildQuery, extractArgsCallbacksAndParams, extractArgsToastsAndParams, getSortDirectionFromSortType, getSortTypeFromSortDirection, handleApiResponse, sortTypes, toFormData };
301
+ export { type ApiErrorHandler, type ApiErrorResult, type ApiMethodArguments, type ApiMethodCallbacks, type ApiMethodOptions, type ApiResult, type ApiSortDirection, type ApiSuccessHandler, type ApiSuccessResult, type ExtractDataIfPaginated, type ExtractError, type ExtractResponse, type FilterFormValues, type FilterType, type HandleApiResponseOptions, type HttpResponse, type OptionValue, type RuntimeRequestParams, type SortDirection, type SortType, type SortableKeys, type UnwrapArray, type WithoutRequestParams, buildQuery, extractArgsCallbacksAndParams, extractArgsToastsAndParams, getSortDirectionFromSortType, getSortTypeFromSortDirection, handleApiResponse, sortTypes, toFormData };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "typedapi-client-helpers",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "description": "Reusable typed OpenAPI client helpers.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -38,10 +38,11 @@ const swaggerFile =
38
38
  const swaggerUrl =
39
39
  process.env.SWAGGER_URL ??
40
40
  process.env.npm_config_swagger_url ??
41
- config.swaggerUrl ??
42
- "https://localhost:7000/swagger/v1/swagger.json";
41
+ config.swaggerUrl;
43
42
 
44
- const swaggerInput = swaggerFile ? path.resolve(cwd, swaggerFile) : swaggerUrl;
43
+ const swaggerInput = swaggerFile
44
+ ? path.resolve(cwd, swaggerFile)
45
+ : (swaggerUrl ?? path.resolve(cwd, "swagger/swagger.json"));
45
46
 
46
47
  const apiRoot = path.resolve(
47
48
  cwd,
@@ -50,6 +51,7 @@ const apiRoot = path.resolve(
50
51
  config.apiOutput ??
51
52
  "src/api",
52
53
  );
54
+
53
55
  const generatedDir = path.join(apiRoot, "generated");
54
56
  const methodsDir = path.join(apiRoot, "methods");
55
57
 
@@ -157,6 +159,7 @@ async function generateOpenApiClient() {
157
159
  }
158
160
 
159
161
  await generateApi(generateOptions);
162
+
160
163
  removeGeneratedRouteFiles();
161
164
  }
162
165
 
@@ -226,7 +229,6 @@ ${createImportStatement({
226
229
  typeOnly: true,
227
230
  })}
228
231
 
229
-
230
232
  export function ${defaultSuccessHandlerName}<T>(
231
233
  _response: ApiSuccessResult<T>,
232
234
  ): void | Promise<void> {
@@ -374,102 +376,97 @@ function getAllTsFiles(dir) {
374
376
  return results;
375
377
  }
376
378
 
377
- async function convertGeneratedUploadMethods(apiDir) {
378
- const tsFiles = getAllTsFiles(apiDir);
379
- let changedMethods = 0;
380
-
381
- console.log(
382
- `Checking ${tsFiles.length} generated TypeScript files for multipart methods...`,
383
- );
379
+ function getGeneratedMethods(source) {
380
+ const methodRegex = /^\s*(\w+)\s*=\s*\(/gm;
381
+ const matches = [...source.matchAll(methodRegex)];
384
382
 
385
- for (const filePath of tsFiles) {
386
- if (
387
- filePath.endsWith("data-contracts.ts") ||
388
- filePath.endsWith("http-client.ts")
389
- ) {
390
- continue;
391
- }
383
+ return matches.map((match, index) => {
384
+ const methodName = match[1];
385
+ const startIndex = match.index ?? 0;
386
+ const endIndex =
387
+ index + 1 < matches.length ? matches[index + 1].index : source.length;
392
388
 
393
- const originalContent = fs.readFileSync(filePath, "utf8");
389
+ const methodSource = source.slice(startIndex, endIndex);
394
390
 
395
- const methodRegex =
396
- /(\b\w+\s*=\s*\(\s*data\s*:\s*)([^,\n]+)(,\s*params\s*:\s*RequestParams\s*=\s*\{\}\s*,?\s*\)\s*=>\s*this\.request<[\s\S]*?type:\s*ContentType\.FormData,[\s\S]*?\}\);)/g;
391
+ return {
392
+ methodName,
393
+ methodSource,
394
+ isFormData: methodSource.includes("type: ContentType.FormData"),
395
+ };
396
+ });
397
+ }
397
398
 
398
- let fileChanges = 0;
399
+ function splitTopLevelArguments(value) {
400
+ const args = [];
401
+ let current = "";
402
+ let depth = 0;
403
+ let quote = "";
399
404
 
400
- const updatedContent = originalContent.replace(
401
- methodRegex,
402
- (_match, start, currentType, rest) => {
403
- console.log(
404
- ` Replacing multipart payload type "${currentType.trim()}" with FormData`,
405
- );
405
+ for (let index = 0; index < value.length; index++) {
406
+ const char = value[index];
407
+ const previousChar = value[index - 1];
406
408
 
407
- fileChanges++;
408
- changedMethods++;
409
+ if (quote) {
410
+ current += char;
409
411
 
410
- return `${start}FormData${rest}`;
411
- },
412
- );
412
+ if (char === quote && previousChar !== "\\") {
413
+ quote = "";
414
+ }
413
415
 
414
- if (fileChanges > 0) {
415
- await writeFormattedFile(filePath, updatedContent);
416
+ continue;
417
+ }
416
418
 
417
- console.log(
418
- `Updated ${fileChanges} multipart method(s) in ${path.relative(
419
- process.cwd(),
420
- filePath,
421
- )}`,
422
- );
419
+ if (char === '"' || char === "'" || char === "`") {
420
+ quote = char;
421
+ current += char;
422
+ continue;
423
423
  }
424
- }
425
424
 
426
- if (changedMethods === 0) {
427
- throw new Error(
428
- "No multipart methods were converted. Check the generated method format and output directory.",
429
- );
430
- }
425
+ if (char === "(" || char === "{" || char === "[" || char === "<") {
426
+ depth++;
427
+ current += char;
428
+ continue;
429
+ }
431
430
 
432
- console.log(`Converted ${changedMethods} multipart method(s) to FormData.`);
433
- }
431
+ if (char === ")" || char === "}" || char === "]" || char === ">") {
432
+ depth--;
433
+ current += char;
434
+ continue;
435
+ }
434
436
 
435
- function isMultipartMethod(source, methodName) {
436
- const startPattern = new RegExp(`\\b${methodName}\\s*=\\s*\\(`);
437
+ if (char === "," && depth === 0) {
438
+ if (current.trim()) {
439
+ args.push(current.trim());
440
+ }
437
441
 
438
- const startMatch = startPattern.exec(source);
442
+ current = "";
443
+ continue;
444
+ }
439
445
 
440
- if (!startMatch) {
441
- return false;
446
+ current += char;
442
447
  }
443
448
 
444
- const nextMethodPattern = /^\s*\w+\s*=\s*\(/gm;
445
- nextMethodPattern.lastIndex = startMatch.index + startMatch[0].length;
446
-
447
- const nextMatch = nextMethodPattern.exec(source);
448
- const endIndex = nextMatch?.index ?? source.length;
449
-
450
- const methodSource = source.slice(startMatch.index, endIndex);
449
+ if (current.trim()) {
450
+ args.push(current.trim());
451
+ }
451
452
 
452
- return methodSource.includes("type: ContentType.FormData");
453
+ return args;
453
454
  }
454
455
 
455
- function getGeneratedMethods(source) {
456
- const methodRegex = /^\s*(\w+)\s*=\s*\(/gm;
457
- const matches = [...source.matchAll(methodRegex)];
456
+ function getGeneratedMethodArgumentCount(signatureParams) {
457
+ const args = splitTopLevelArguments(signatureParams);
458
458
 
459
- return matches.map((match, index) => {
460
- const methodName = match[1];
461
- const startIndex = match.index ?? 0;
462
- const endIndex =
463
- index + 1 < matches.length ? matches[index + 1].index : source.length;
459
+ if (args.length === 0) {
460
+ return 0;
461
+ }
464
462
 
465
- const methodSource = source.slice(startIndex, endIndex);
463
+ const lastArg = args[args.length - 1];
466
464
 
467
- return {
468
- methodName,
469
- methodSource,
470
- isFormData: methodSource.includes("type: ContentType.FormData"),
471
- };
472
- });
465
+ if (/\bparams\s*:\s*RequestParams\b/.test(lastArg)) {
466
+ return args.length - 1;
467
+ }
468
+
469
+ return args.length;
473
470
  }
474
471
 
475
472
  async function createMethodFiles() {
@@ -518,9 +515,9 @@ async function createMethodFiles() {
518
515
  }
519
516
 
520
517
  const params = signatureMatch[1].trim();
518
+ const methodArgumentCount = getGeneratedMethodArgumentCount(params);
521
519
 
522
520
  const returnTypeMatch = methodSource.match(/this\.request<([^,>]+)/m);
523
-
524
521
  const returnType = returnTypeMatch?.[1] ?? "";
525
522
 
526
523
  const isPaginated =
@@ -540,6 +537,7 @@ async function createMethodFiles() {
540
537
  nonQueryMethods.push({
541
538
  methodName,
542
539
  isFormData,
540
+ methodArgumentCount,
543
541
  });
544
542
  }
545
543
 
@@ -556,8 +554,6 @@ export type ${toQueryName(methodName)} =
556
554
 
557
555
  const hasPaginatedMethods = paginatedMethods.length > 0;
558
556
 
559
- const hasNonQueryMethods = nonQueryMethods.length > 0;
560
-
561
557
  const hasFormDataMethods = nonQueryMethods.some(
562
558
  ({ isFormData }) => isFormData,
563
559
  );
@@ -578,14 +574,13 @@ export async function ${methodName}(
578
574
  ExtractResponse<ReturnType<${className}["${methodName}"]>>
579
575
  > | null = null,
580
576
  sortDirection?: SortDirection,
581
- onSuccess?: ApiSuccessHandler<
582
- ExtractResponse<ReturnType<${className}["${methodName}"]>>
583
- >,
584
- onError?: ApiErrorHandler<
585
- ExtractError<ReturnType<${className}["${methodName}"]>>
586
- >,
587
- params?: RequestParams
577
+ options: ApiMethodOptions<
578
+ ReturnType<${className}["${methodName}"]>,
579
+ RequestParams
580
+ > = {}
588
581
  ): Promise<ApiResult<ExtractResponse<ReturnType<${className}["${methodName}"]>>>> {
582
+ const { onSuccess, onError, params } = options;
583
+
589
584
  return handleApiResponse(
590
585
  () =>
591
586
  ${instanceName}.${methodName}(
@@ -608,14 +603,13 @@ export async function ${methodName}(
608
603
  return `
609
604
  export async function ${methodName}(
610
605
  query?: ${toQueryName(methodName)},
611
- onSuccess?: ApiSuccessHandler<
612
- ExtractResponse<ReturnType<${className}["${methodName}"]>>
613
- >,
614
- onError?: ApiErrorHandler<
615
- ExtractError<ReturnType<${className}["${methodName}"]>>
616
- >,
617
- params?: RequestParams
606
+ options: ApiMethodOptions<
607
+ ReturnType<${className}["${methodName}"]>,
608
+ RequestParams
609
+ > = {}
618
610
  ): Promise<ApiResult<ExtractResponse<ReturnType<${className}["${methodName}"]>>>> {
611
+ const { onSuccess, onError, params } = options;
612
+
619
613
  return handleApiResponse(
620
614
  () =>
621
615
  ${instanceName}.${methodName}(
@@ -634,13 +628,10 @@ export async function ${methodName}(
634
628
  `
635
629
  export async function ${methodName}(
636
630
  query?: ${toQueryName(methodName)},
637
- onSuccess?: ApiSuccessHandler<
638
- ExtractResponse<ReturnType<${className}["${methodName}"]>>
639
- >,
640
- onError?: ApiErrorHandler<
641
- ExtractError<ReturnType<${className}["${methodName}"]>>
642
- >,
643
- params?: RequestParams
631
+ options: ApiMethodOptions<
632
+ ReturnType<${className}["${methodName}"]>,
633
+ RequestParams
634
+ > = {}
644
635
  ): Promise<
645
636
  ApiResult<
646
637
  ExtractResponse<
@@ -650,6 +641,8 @@ export async function ${methodName}(
650
641
  >
651
642
  >
652
643
  > {
644
+ const { onSuccess, onError, params } = options;
645
+
653
646
  return handleApiResponse(
654
647
  () =>
655
648
  ${instanceName}.${methodName}(
@@ -664,20 +657,17 @@ export async function ${methodName}(
664
657
  .join("\n\n");
665
658
 
666
659
  const nonQueryMethodWrappers = nonQueryMethods
667
- .map(({ methodName, isFormData }) => {
660
+ .map(({ methodName, isFormData, methodArgumentCount }) => {
668
661
  if (isFormData) {
669
662
  return `
670
663
  export async function ${methodName}(
671
664
  data: Parameters<
672
665
  ${className}["${methodName}"]
673
666
  >[0],
674
- onSuccess?: ApiSuccessHandler<
675
- ExtractResponse<ReturnType<${className}["${methodName}"]>>
676
- >,
677
- onError?: ApiErrorHandler<
678
- ExtractError<ReturnType<${className}["${methodName}"]>>
679
- >,
680
- params?: RequestParams
667
+ options: ApiMethodOptions<
668
+ ReturnType<${className}["${methodName}"]>,
669
+ RequestParams
670
+ > = {}
681
671
  ): Promise<
682
672
  ApiResult<
683
673
  ExtractResponse<
@@ -687,6 +677,7 @@ export async function ${methodName}(
687
677
  >
688
678
  >
689
679
  > {
680
+ const { onSuccess, onError, params } = options;
690
681
  const formData = toFormData(data);
691
682
 
692
683
  return handleApiResponse(
@@ -705,25 +696,15 @@ export async function ${methodName}(
705
696
 
706
697
  return `
707
698
  export async function ${methodName}(
708
- ...argsWithCallbacks: [
699
+ ...argsWithOptions: [
709
700
  ...ApiMethodArguments<
710
- ${className}["${methodName}"]
701
+ ${className}["${methodName}"],
702
+ RequestParams
711
703
  >,
712
- ApiSuccessHandler<
713
- ExtractResponse<
714
- ReturnType<
715
- ${className}["${methodName}"]
716
- >
717
- >
718
- >?,
719
- ApiErrorHandler<
720
- ExtractError<
721
- ReturnType<
722
- ${className}["${methodName}"]
723
- >
724
- >
725
- >?,
726
- RequestParams?
704
+ ApiMethodOptions<
705
+ ReturnType<${className}["${methodName}"]>,
706
+ RequestParams
707
+ >?
727
708
  ]
728
709
  ): Promise<
729
710
  ApiResult<
@@ -734,26 +715,17 @@ export async function ${methodName}(
734
715
  >
735
716
  >
736
717
  > {
737
- const {
738
- args,
739
- onSuccess,
740
- onError,
741
- params
742
- } = extractArgsCallbacksAndParams<
743
- ApiMethodArguments<
744
- ${className}["${methodName}"]
745
- >,
746
- ExtractResponse<
747
- ReturnType<
748
- ${className}["${methodName}"]
749
- >
750
- >,
751
- ExtractError<
752
- ReturnType<
753
- ${className}["${methodName}"]
754
- >
755
- >
756
- >(argsWithCallbacks);
718
+ const args = [...argsWithOptions] as unknown[];
719
+
720
+ const options =
721
+ args.length > ${methodArgumentCount}
722
+ ? (args.pop() as ApiMethodOptions<
723
+ ReturnType<${className}["${methodName}"]>,
724
+ RequestParams
725
+ >)
726
+ : {};
727
+
728
+ const { onSuccess, onError, params } = options ?? {};
757
729
 
758
730
  const requestArgs = [
759
731
  ...args,
@@ -776,16 +748,15 @@ export async function ${methodName}(
776
748
 
777
749
  const runtimeValueImportNames = [
778
750
  ...(hasPaginatedMethods && useFilterFormValues ? ["buildQuery"] : []),
779
- ...(hasRegularNonQueryMethods ? ["extractArgsCallbacksAndParams"] : []),
780
751
  ...(hasFormDataMethods ? ["toFormData"] : []),
781
752
  "handleApiResponse",
782
753
  ];
754
+
783
755
  const runtimeTypeImportNames = [
784
756
  "ApiResult",
785
- "ApiSuccessHandler",
786
- "ApiErrorHandler",
757
+ "ApiMethodOptions",
758
+ ...(hasRegularNonQueryMethods ? ["ApiMethodArguments"] : []),
787
759
  "ExtractResponse",
788
- "ExtractError",
789
760
 
790
761
  ...(hasPaginatedMethods && useFilterFormValues
791
762
  ? [
@@ -839,20 +810,6 @@ export async function ${methodName}(
839
810
  })
840
811
  : "";
841
812
 
842
- const apiMethodArgumentsType = hasRegularNonQueryMethods
843
- ? `
844
- type ApiMethodArguments<
845
- TMethod extends (...args: any[]) => unknown
846
- > =
847
- Parameters<TMethod> extends [
848
- ...infer Arguments,
849
- unknown?
850
- ]
851
- ? Arguments
852
- : Parameters<TMethod>;
853
- `.trim()
854
- : "";
855
-
856
813
  const content = `
857
814
  ${runtimeValueImports}
858
815
 
@@ -864,8 +821,6 @@ ${generatedValueImports}
864
821
 
865
822
  ${generatedTypeImports}
866
823
 
867
- ${apiMethodArgumentsType}
868
-
869
824
  /* =======================
870
825
  Query Types
871
826
  ======================= */