typedapi-client-helpers 0.1.11 → 0.1.12

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "typedapi-client-helpers",
3
- "version": "0.1.11",
3
+ "version": "0.1.12",
4
4
  "description": "Reusable typed OpenAPI client helpers.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -33,7 +33,8 @@ const config = {
33
33
  const swaggerFile =
34
34
  process.env.SWAGGER_FILE ??
35
35
  process.env.npm_config_swagger_file ??
36
- config.swaggerFile;
36
+ config.swaggerFile ??
37
+ "swagger/swagger.json";
37
38
 
38
39
  const swaggerUrl =
39
40
  process.env.SWAGGER_URL ??
@@ -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
 
@@ -110,6 +112,10 @@ function toQueryName(methodName) {
110
112
  return `${pascalCase(methodName)}Query`;
111
113
  }
112
114
 
115
+ function toOptionsName(methodName) {
116
+ return `${pascalCase(methodName)}Options`;
117
+ }
118
+
113
119
  function cleanDirectory(directory) {
114
120
  if (fs.existsSync(directory)) {
115
121
  fs.rmSync(directory, { recursive: true, force: true });
@@ -157,6 +163,7 @@ async function generateOpenApiClient() {
157
163
  }
158
164
 
159
165
  await generateApi(generateOptions);
166
+
160
167
  removeGeneratedRouteFiles();
161
168
  }
162
169
 
@@ -226,7 +233,6 @@ ${createImportStatement({
226
233
  typeOnly: true,
227
234
  })}
228
235
 
229
-
230
236
  export function ${defaultSuccessHandlerName}<T>(
231
237
  _response: ApiSuccessResult<T>,
232
238
  ): void | Promise<void> {
@@ -374,102 +380,97 @@ function getAllTsFiles(dir) {
374
380
  return results;
375
381
  }
376
382
 
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
- );
383
+ function getGeneratedMethods(source) {
384
+ const methodRegex = /^\s*(\w+)\s*=\s*\(/gm;
385
+ const matches = [...source.matchAll(methodRegex)];
384
386
 
385
- for (const filePath of tsFiles) {
386
- if (
387
- filePath.endsWith("data-contracts.ts") ||
388
- filePath.endsWith("http-client.ts")
389
- ) {
390
- continue;
391
- }
387
+ return matches.map((match, index) => {
388
+ const methodName = match[1];
389
+ const startIndex = match.index ?? 0;
390
+ const endIndex =
391
+ index + 1 < matches.length ? matches[index + 1].index : source.length;
392
392
 
393
- const originalContent = fs.readFileSync(filePath, "utf8");
393
+ const methodSource = source.slice(startIndex, endIndex);
394
394
 
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;
395
+ return {
396
+ methodName,
397
+ methodSource,
398
+ isFormData: methodSource.includes("type: ContentType.FormData"),
399
+ };
400
+ });
401
+ }
397
402
 
398
- let fileChanges = 0;
403
+ function splitTopLevelArguments(value) {
404
+ const args = [];
405
+ let current = "";
406
+ let depth = 0;
407
+ let quote = "";
399
408
 
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
- );
409
+ for (let index = 0; index < value.length; index++) {
410
+ const char = value[index];
411
+ const previousChar = value[index - 1];
406
412
 
407
- fileChanges++;
408
- changedMethods++;
413
+ if (quote) {
414
+ current += char;
409
415
 
410
- return `${start}FormData${rest}`;
411
- },
412
- );
416
+ if (char === quote && previousChar !== "\\") {
417
+ quote = "";
418
+ }
413
419
 
414
- if (fileChanges > 0) {
415
- await writeFormattedFile(filePath, updatedContent);
420
+ continue;
421
+ }
416
422
 
417
- console.log(
418
- `Updated ${fileChanges} multipart method(s) in ${path.relative(
419
- process.cwd(),
420
- filePath,
421
- )}`,
422
- );
423
+ if (char === '"' || char === "'" || char === "`") {
424
+ quote = char;
425
+ current += char;
426
+ continue;
423
427
  }
424
- }
425
428
 
426
- if (changedMethods === 0) {
427
- throw new Error(
428
- "No multipart methods were converted. Check the generated method format and output directory.",
429
- );
430
- }
429
+ if (char === "(" || char === "{" || char === "[" || char === "<") {
430
+ depth++;
431
+ current += char;
432
+ continue;
433
+ }
431
434
 
432
- console.log(`Converted ${changedMethods} multipart method(s) to FormData.`);
433
- }
435
+ if (char === ")" || char === "}" || char === "]" || char === ">") {
436
+ depth--;
437
+ current += char;
438
+ continue;
439
+ }
434
440
 
435
- function isMultipartMethod(source, methodName) {
436
- const startPattern = new RegExp(`\\b${methodName}\\s*=\\s*\\(`);
441
+ if (char === "," && depth === 0) {
442
+ if (current.trim()) {
443
+ args.push(current.trim());
444
+ }
437
445
 
438
- const startMatch = startPattern.exec(source);
446
+ current = "";
447
+ continue;
448
+ }
439
449
 
440
- if (!startMatch) {
441
- return false;
450
+ current += char;
442
451
  }
443
452
 
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);
453
+ if (current.trim()) {
454
+ args.push(current.trim());
455
+ }
451
456
 
452
- return methodSource.includes("type: ContentType.FormData");
457
+ return args;
453
458
  }
454
459
 
455
- function getGeneratedMethods(source) {
456
- const methodRegex = /^\s*(\w+)\s*=\s*\(/gm;
457
- const matches = [...source.matchAll(methodRegex)];
460
+ function getGeneratedMethodArgumentCount(signatureParams) {
461
+ const args = splitTopLevelArguments(signatureParams);
458
462
 
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;
463
+ if (args.length === 0) {
464
+ return 0;
465
+ }
464
466
 
465
- const methodSource = source.slice(startIndex, endIndex);
467
+ const lastArg = args[args.length - 1];
466
468
 
467
- return {
468
- methodName,
469
- methodSource,
470
- isFormData: methodSource.includes("type: ContentType.FormData"),
471
- };
472
- });
469
+ if (/\bparams\s*:\s*RequestParams\b/.test(lastArg)) {
470
+ return args.length - 1;
471
+ }
472
+
473
+ return args.length;
473
474
  }
474
475
 
475
476
  async function createMethodFiles() {
@@ -518,9 +519,9 @@ async function createMethodFiles() {
518
519
  }
519
520
 
520
521
  const params = signatureMatch[1].trim();
522
+ const methodArgumentCount = getGeneratedMethodArgumentCount(params);
521
523
 
522
524
  const returnTypeMatch = methodSource.match(/this\.request<([^,>]+)/m);
523
-
524
525
  const returnType = returnTypeMatch?.[1] ?? "";
525
526
 
526
527
  const isPaginated =
@@ -540,6 +541,7 @@ async function createMethodFiles() {
540
541
  nonQueryMethods.push({
541
542
  methodName,
542
543
  isFormData,
544
+ methodArgumentCount,
543
545
  });
544
546
  }
545
547
 
@@ -554,9 +556,23 @@ export type ${toQueryName(methodName)} =
554
556
  )
555
557
  .join("\n\n");
556
558
 
557
- const hasPaginatedMethods = paginatedMethods.length > 0;
559
+ const methodOptionTypes = generatedMethods
560
+ .map(({ methodName }) =>
561
+ `
562
+ export type ${toOptionsName(methodName)} = {
563
+ onSuccess?: ApiSuccessHandler<
564
+ ExtractResponse<ReturnType<${className}["${methodName}"]>>
565
+ >;
566
+ onError?: ApiErrorHandler<
567
+ ExtractError<ReturnType<${className}["${methodName}"]>>
568
+ >;
569
+ params?: RequestParams;
570
+ };
571
+ `.trim(),
572
+ )
573
+ .join("\n\n");
558
574
 
559
- const hasNonQueryMethods = nonQueryMethods.length > 0;
575
+ const hasPaginatedMethods = paginatedMethods.length > 0;
560
576
 
561
577
  const hasFormDataMethods = nonQueryMethods.some(
562
578
  ({ isFormData }) => isFormData,
@@ -578,14 +594,10 @@ export async function ${methodName}(
578
594
  ExtractResponse<ReturnType<${className}["${methodName}"]>>
579
595
  > | null = null,
580
596
  sortDirection?: SortDirection,
581
- onSuccess?: ApiSuccessHandler<
582
- ExtractResponse<ReturnType<${className}["${methodName}"]>>
583
- >,
584
- onError?: ApiErrorHandler<
585
- ExtractError<ReturnType<${className}["${methodName}"]>>
586
- >,
587
- params?: RequestParams
597
+ options: ${toOptionsName(methodName)} = {}
588
598
  ): Promise<ApiResult<ExtractResponse<ReturnType<${className}["${methodName}"]>>>> {
599
+ const { onSuccess, onError, params } = options;
600
+
589
601
  return handleApiResponse(
590
602
  () =>
591
603
  ${instanceName}.${methodName}(
@@ -608,14 +620,10 @@ export async function ${methodName}(
608
620
  return `
609
621
  export async function ${methodName}(
610
622
  query?: ${toQueryName(methodName)},
611
- onSuccess?: ApiSuccessHandler<
612
- ExtractResponse<ReturnType<${className}["${methodName}"]>>
613
- >,
614
- onError?: ApiErrorHandler<
615
- ExtractError<ReturnType<${className}["${methodName}"]>>
616
- >,
617
- params?: RequestParams
623
+ options: ${toOptionsName(methodName)} = {}
618
624
  ): Promise<ApiResult<ExtractResponse<ReturnType<${className}["${methodName}"]>>>> {
625
+ const { onSuccess, onError, params } = options;
626
+
619
627
  return handleApiResponse(
620
628
  () =>
621
629
  ${instanceName}.${methodName}(
@@ -634,13 +642,7 @@ export async function ${methodName}(
634
642
  `
635
643
  export async function ${methodName}(
636
644
  query?: ${toQueryName(methodName)},
637
- onSuccess?: ApiSuccessHandler<
638
- ExtractResponse<ReturnType<${className}["${methodName}"]>>
639
- >,
640
- onError?: ApiErrorHandler<
641
- ExtractError<ReturnType<${className}["${methodName}"]>>
642
- >,
643
- params?: RequestParams
645
+ options: ${toOptionsName(methodName)} = {}
644
646
  ): Promise<
645
647
  ApiResult<
646
648
  ExtractResponse<
@@ -650,6 +652,8 @@ export async function ${methodName}(
650
652
  >
651
653
  >
652
654
  > {
655
+ const { onSuccess, onError, params } = options;
656
+
653
657
  return handleApiResponse(
654
658
  () =>
655
659
  ${instanceName}.${methodName}(
@@ -664,20 +668,14 @@ export async function ${methodName}(
664
668
  .join("\n\n");
665
669
 
666
670
  const nonQueryMethodWrappers = nonQueryMethods
667
- .map(({ methodName, isFormData }) => {
671
+ .map(({ methodName, isFormData, methodArgumentCount }) => {
668
672
  if (isFormData) {
669
673
  return `
670
674
  export async function ${methodName}(
671
675
  data: Parameters<
672
676
  ${className}["${methodName}"]
673
677
  >[0],
674
- onSuccess?: ApiSuccessHandler<
675
- ExtractResponse<ReturnType<${className}["${methodName}"]>>
676
- >,
677
- onError?: ApiErrorHandler<
678
- ExtractError<ReturnType<${className}["${methodName}"]>>
679
- >,
680
- params?: RequestParams
678
+ options: ${toOptionsName(methodName)} = {}
681
679
  ): Promise<
682
680
  ApiResult<
683
681
  ExtractResponse<
@@ -687,6 +685,7 @@ export async function ${methodName}(
687
685
  >
688
686
  >
689
687
  > {
688
+ const { onSuccess, onError, params } = options;
690
689
  const formData = toFormData(data);
691
690
 
692
691
  return handleApiResponse(
@@ -705,25 +704,11 @@ export async function ${methodName}(
705
704
 
706
705
  return `
707
706
  export async function ${methodName}(
708
- ...argsWithCallbacks: [
707
+ ...argsWithOptions: [
709
708
  ...ApiMethodArguments<
710
709
  ${className}["${methodName}"]
711
710
  >,
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?
711
+ ${toOptionsName(methodName)}?
727
712
  ]
728
713
  ): Promise<
729
714
  ApiResult<
@@ -734,26 +719,14 @@ export async function ${methodName}(
734
719
  >
735
720
  >
736
721
  > {
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);
722
+ const args = [...argsWithOptions] as unknown[];
723
+
724
+ const options =
725
+ args.length > ${methodArgumentCount}
726
+ ? (args.pop() as ${toOptionsName(methodName)})
727
+ : {};
728
+
729
+ const { onSuccess, onError, params } = options ?? {};
757
730
 
758
731
  const requestArgs = [
759
732
  ...args,
@@ -776,10 +749,10 @@ export async function ${methodName}(
776
749
 
777
750
  const runtimeValueImportNames = [
778
751
  ...(hasPaginatedMethods && useFilterFormValues ? ["buildQuery"] : []),
779
- ...(hasRegularNonQueryMethods ? ["extractArgsCallbacksAndParams"] : []),
780
752
  ...(hasFormDataMethods ? ["toFormData"] : []),
781
753
  "handleApiResponse",
782
754
  ];
755
+
783
756
  const runtimeTypeImportNames = [
784
757
  "ApiResult",
785
758
  "ApiSuccessHandler",
@@ -841,16 +814,16 @@ export async function ${methodName}(
841
814
 
842
815
  const apiMethodArgumentsType = hasRegularNonQueryMethods
843
816
  ? `
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()
817
+ type ApiMethodArguments<
818
+ TMethod extends (...args: any[]) => unknown
819
+ > =
820
+ Parameters<TMethod> extends [
821
+ ...infer Arguments,
822
+ RequestParams?
823
+ ]
824
+ ? Arguments
825
+ : Parameters<TMethod>;
826
+ `.trim()
854
827
  : "";
855
828
 
856
829
  const content = `
@@ -871,6 +844,11 @@ ${apiMethodArgumentsType}
871
844
  ======================= */
872
845
  ${queryTypes}
873
846
 
847
+ /* =======================
848
+ Method Option Types
849
+ ======================= */
850
+ ${methodOptionTypes}
851
+
874
852
  /* =======================
875
853
  API Instance
876
854
  ======================= */