swaggie 1.9.0 → 2.0.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 (46) hide show
  1. package/README.md +117 -31
  2. package/dist/browser.js +24 -5
  3. package/dist/cli.js +54 -7
  4. package/dist/gen/genMocks.js +453 -0
  5. package/dist/gen/genOperations.js +112 -20
  6. package/dist/gen/genTypes.js +6 -5
  7. package/dist/gen/header.js +0 -1
  8. package/dist/gen/index.js +14 -1
  9. package/dist/generated/bundledTemplates.js +17 -19
  10. package/dist/index.js +17 -1
  11. package/dist/swagger/operations.js +16 -7
  12. package/dist/swagger/typesExtractor.js +12 -11
  13. package/dist/types.d.ts +55 -5
  14. package/dist/utils/documentLoader.js +1 -3
  15. package/dist/utils/fileUtils.js +22 -0
  16. package/dist/utils/refResolver.js +9 -21
  17. package/dist/utils/templateEngine.js +18 -0
  18. package/dist/utils/templateManager.js +123 -14
  19. package/dist/utils/templateValidator.js +127 -0
  20. package/dist/utils/utils.js +19 -13
  21. package/package.json +5 -4
  22. package/templates/axios/operation.ejs +25 -21
  23. package/templates/fetch/operation.ejs +4 -0
  24. package/templates/ng1/operation.ejs +11 -3
  25. package/templates/ng2/baseClient.ejs +9 -49
  26. package/templates/ng2/client.ejs +3 -7
  27. package/templates/ng2/operation.ejs +34 -17
  28. package/templates/swr/baseClient.ejs +7 -0
  29. package/templates/swr/client.ejs +63 -0
  30. package/templates/swr/swrMutationOperation.ejs +32 -0
  31. package/templates/swr/swrOperation.ejs +18 -0
  32. package/templates/tsq/baseClient.ejs +1 -0
  33. package/templates/tsq/client.ejs +67 -0
  34. package/templates/tsq/mutationOperation.ejs +31 -0
  35. package/templates/tsq/queryOperation.ejs +19 -0
  36. package/templates/xior/operation.ejs +25 -21
  37. package/templates/swr-axios/barrel.ejs +0 -58
  38. package/templates/swr-axios/baseClient.ejs +0 -20
  39. package/templates/swr-axios/client.ejs +0 -21
  40. package/templates/swr-axios/operation.ejs +0 -40
  41. package/templates/swr-axios/swrOperation.ejs +0 -50
  42. package/templates/tsq-xior/barrel.ejs +0 -0
  43. package/templates/tsq-xior/baseClient.ejs +0 -14
  44. package/templates/tsq-xior/client.ejs +0 -22
  45. package/templates/tsq-xior/operation.ejs +0 -40
  46. package/templates/tsq-xior/queryOperation.ejs +0 -30
package/README.md CHANGED
@@ -21,13 +21,15 @@ See the [Example section](#example) for a quick demo, or visit the full document
21
21
  ## Features
22
22
 
23
23
  - Generates TypeScript code from OpenAPI 3.0, 3.1, and 3.2 specs
24
- - Supports multiple HTTP client libraries out of the box: `fetch`, `axios`, `xior`, `SWR + axios`, `Angular 1`, `Angular 2+`, and `TanStack Query`
24
+ - Supports multiple HTTP client libraries out of the box: `fetch`, `axios`, `xior`, `Angular 1`, `Angular 2+`; with optional reactive layers (`swr`, `tsq`) that compose with any compatible HTTP client
25
+ - **Auto-generated mock/stub files** for Vitest and Jest — typed spies for every client method and hook, with ergonomic helpers like `mockSWR()` and `mockQuery()`
25
26
  - Custom templates — bring your own to fit your existing codebase
26
27
  - Supports `allOf`, `oneOf`, `anyOf`, `$ref`, nullable types, and various enum definitions
27
28
  - Handles multiple content types: JSON, plain text, multipart/form-data, URL-encoded, and binary
28
29
  - JSDoc comments on all generated functions
29
30
  - Generates a single, small, tree-shakable output file
30
31
  - Dev-only dependency — zero runtime overhead
32
+ - Ability to generate automatic client mocks for `vitest` and `jest`
31
33
 
32
34
  ---
33
35
 
@@ -72,24 +74,27 @@ swaggie -s https://petstore3.swagger.io/api/v3/openapi.json -o ./client/petstore
72
74
  **Available options:**
73
75
 
74
76
  ```
75
- -V, --version Output the version number
76
- -c, --config <path> Path to a JSON configuration file
77
- -s, --src <url|path> URL or file path to the OpenAPI spec
78
- -o, --out <filePath> Output file path (omit to print to stdout)
79
- -b, --baseUrl <string> Default base URL for the generated client (default: "")
80
- -t, --template <string> Template to use for code generation (default: "axios")
81
- -m, --mode <mode> Generation mode: "full" or "schemas" (default: "full")
82
- -d, --schemaStyle <style> Schema object style: "interface" or "type" (default: "interface")
83
- --enumStyle <style> Enum style for plain string enums: "union" or "enum" (default: "union")
84
- --enumNamesStyle <s> Enum member name casing: "original" or "PascalCase" (default: "original")
85
- --dateFormat <format> Date handling in schemas: "Date" or "string"
86
- --nullables <strategy> Nullable handling: "include", "nullableAsOptional", or "ignore"
87
- --preferAny Use "any" instead of "unknown" for untyped values (default: false)
88
- --skipDeprecated Exclude deprecated operations from the output (default: false)
89
- --servicePrefix Prefix for service names — useful when generating multiple APIs
90
- --allowDots Use dot notation to serialize nested object query params
91
- --arrayFormat How arrays are serialized: "indices", "repeat", or "brackets"
92
- -h, --help Show help
77
+ -V, --version Output the version number
78
+ -c, --config <path> Path to a JSON configuration file
79
+ -s, --src <url|path> URL or file path to the OpenAPI spec
80
+ -o, --out <filePath> Output file path (omit to print to stdout)
81
+ -b, --baseUrl <string> Base URL that will be used as a default value in the clients
82
+ -t, --template <string> Template to use. Single name: "axios", "fetch", "xior", "ng1", "ng2", "swr", "tsq". Reactive pair: "swr,axios" / "tsq,xior" / etc. (default: "axios")
83
+ -m, --mode <mode> Generation mode: "full" or "schemas" (default: "full")
84
+ -d, --schemaStyle <style> Schema object style: "interface" or "type" (default: "interface")
85
+ --enumStyle <style> Enum style for plain string enums: "union" or "enum" (default: "union")
86
+ --enumNamesStyle <s> Enum member name casing: "original" or "PascalCase" (default: "original")
87
+ --dateFormat <format> How date fields are emitted in generated types
88
+ --nullables <strategy> Nullable handling: "include", "nullableAsOptional", or "ignore"
89
+ --preferAny Use "any" instead of "unknown" for untyped values (default: false)
90
+ --skipDeprecated Exclude deprecated operations from the output (default: false)
91
+ --servicePrefix Prefix for service names — useful when generating multiple APIs
92
+ --allowDots Use dot notation to serialize nested object query params
93
+ --arrayFormat How arrays are serialized: "indices", "repeat", or "brackets"
94
+ -C, --useClient Prepend 'use client'; directive (Next.js App Router + SWR/TSQ)
95
+ --mocks <path> Output path for a generated mock/stub file (requires --testingFramework and --out)
96
+ -T, --testingFramework <name> Framework for generated mocks: "vitest" or "jest" (requires --mocks and --out)
97
+ -h, --help Show help
93
98
  ```
94
99
 
95
100
  ### Formatting the Output
@@ -142,24 +147,72 @@ The `$schema` field enables autocompletion and inline documentation in most edit
142
147
 
143
148
  ## Templates
144
149
 
145
- Swaggie ships with the following built-in templates:
150
+ Swaggie's templates are split into two independent layers that you can combine freely.
146
151
 
147
- | Template | Description |
148
- | ----------- | ----------------------------------------------------------------------------------------- |
149
- | `axios` | Default. Recommended for React, Vue, and similar frameworks |
150
- | `xior` | Lightweight modern alternative to axios ([xior](https://github.com/suhaotian/xior#intro)) |
151
- | `swr-axios` | SWR hooks for GET requests, backed by axios |
152
- | `tsq-xior` | TanStack Query hooks for GET requests, backed by xior |
153
- | `fetch` | Uses the native browser Fetch API |
154
- | `ng1` | Angular 1 client |
155
- | `ng2` | Angular 2+ client (uses HttpClient and InjectionTokens) |
152
+ ### HTTP client templates
156
153
 
157
- To use a custom template, pass the path to your template directory:
154
+ These are standalone and cover the most common client libraries:
155
+
156
+ | Template | Description |
157
+ | -------- | ----------- |
158
+ | `axios` | Default. Recommended for React, Vue, and most Node.js projects |
159
+ | `fetch` | Native browser/Node 18+ Fetch API — zero runtime dependencies |
160
+ | `xior` | Lightweight Axios-compatible alternative ([xior](https://github.com/suhaotian/xior#intro)) |
161
+ | `ng1` | Angular 1 client |
162
+ | `ng2` | Angular 2+ client (uses `HttpClient` and `InjectionToken`) |
163
+
164
+ ### Reactive query layer templates
165
+
166
+ These add a reactive data-fetching layer (SWR or TanStack Query hooks) on top of any compatible http client. They cannot be used alone — pair them with a basic template using a 2-element array:
167
+
168
+ | Template | Description |
169
+ | -------- | ----------- |
170
+ | `swr` | [SWR](https://swr.vercel.app) hooks for queries and mutations |
171
+ | `tsq` | [TanStack Query](https://tanstack.com/query) hooks for queries and mutations |
172
+
173
+ Compatible http client templates: `axios`, `fetch`, `xior`. Angular clients are not compatible with reactive layers.
174
+
175
+ ### Usage examples
176
+
177
+ **Single http template (existing behaviour):**
178
+
179
+ ```json
180
+ { "template": "axios" }
181
+ ```
182
+
183
+ ```bash
184
+ swaggie -s ./openapi.json -o ./client.ts -t axios
185
+ ```
186
+
187
+ **Pair combination — in config:**
188
+
189
+ ```json
190
+ { "template": ["swr", "axios"] }
191
+ ```
192
+
193
+ ```bash
194
+ # CLI: comma-separated pair
195
+ swaggie -s ./openapi.json -o ./client.ts -t swr,axios
196
+ swaggie -s ./openapi.json -o ./client.ts -t tsq,xior
197
+ swaggie -s ./openapi.json -o ./client.ts -t swr,fetch
198
+ ```
199
+
200
+ **Reactive template only — defaults to `fetch` as the http client:**
201
+
202
+ ```json
203
+ { "template": "swr" }
204
+ ```
205
+
206
+ ### Custom templates
207
+
208
+ Pass the path to your own template directory as the template value:
158
209
 
159
210
  ```bash
160
211
  swaggie -s https://petstore3.swagger.io/api/v3/openapi.json -o ./client/petstore.ts --template ./my-swaggie-template/
161
212
  ```
162
213
 
214
+ Custom paths also work as part of a composite pair: `["./my-l2", "axios"]`.
215
+
163
216
  ---
164
217
 
165
218
  ## Example
@@ -179,7 +232,7 @@ import Axios, { AxiosPromise } from 'axios';
179
232
 
180
233
  const axios = Axios.create({
181
234
  baseURL: '/api',
182
- paramsSerializer: (params) =>
235
+ paramsSerializer: (params: any) =>
183
236
  encodeParams(params, null, {
184
237
  allowDots: true,
185
238
  arrayFormat: 'repeat',
@@ -359,6 +412,39 @@ Either tool needs to be installed separately and configured for your project.
359
412
 
360
413
  ---
361
414
 
415
+ ## Generating Mocks
416
+
417
+ Swaggie can generate a companion mock file alongside your client — a set of typed spy stubs for every method and hook, ready to drop into your tests.
418
+
419
+ ```bash
420
+ swaggie -s ./spec.json -o ./src/api/client.ts -t swr,axios \
421
+ --mocks ./src/__mocks__/api.ts --testingFramework vitest
422
+ ```
423
+
424
+ Or in a config file:
425
+
426
+ ```json
427
+ {
428
+ "src": "./openapi.json",
429
+ "out": "./src/api/client.ts",
430
+ "template": ["swr", "axios"],
431
+ "mocks": "./src/__mocks__/api.ts",
432
+ "testingFramework": "vitest"
433
+ }
434
+ ```
435
+
436
+ The generated mock file exports the same names as the real client, so `vi.mock('./api', () => import('./__mocks__/api'))` is all you need in tests. For (SWR/TSQ) templates, hook stubs come with shorthand helpers:
437
+
438
+ ```ts
439
+ pet.queries.usePetById.mockSWR({ data: { id: 1, name: 'Rex' } });
440
+ pet.mutations.useAddPet.mockSWRMutation({ isMutating: false });
441
+ // TanStack Query equivalents: mockQuery() / mockMutation()
442
+ ```
443
+
444
+ See the [Mocking guide](https://yhnavein.github.io/swaggie/guide/mocking) for full details.
445
+
446
+ ---
447
+
362
448
  ## Using Swaggie Programmatically
363
449
 
364
450
  You can also call Swaggie directly from Node.js/bun/deno/etc:
package/dist/browser.js CHANGED
@@ -9,6 +9,8 @@ var _bundledTemplates = require('./generated/bundledTemplates');
9
9
  var _utils = require('./utils/utils');
10
10
  var _documentLoaderbrowser = require('./utils/documentLoader.browser');
11
11
  var _templateEngine = require('./utils/templateEngine');
12
+ var _templateValidator = require('./utils/templateValidator');
13
+ var _templateManager = require('./utils/templateManager');
12
14
 
13
15
  /**
14
16
  * Browser-friendly code generation entrypoint.
@@ -52,12 +54,27 @@ async function generateCode(spec, options) {
52
54
  return _header.FILE_HEADER + _genTypes2.default.call(void 0, spec, options, false);
53
55
  }
54
56
 
55
- const templateFiles = _bundledTemplates.BUNDLED_TEMPLATES[options.template];
56
- if (!templateFiles) {
57
- throw new Error(`Bundled templates for '${options.template}' are not available`);
57
+ _templateValidator.validateTemplate.call(void 0, options.template);
58
+
59
+ if (Array.isArray(options.template)) {
60
+ const [l2, l1] = options.template;
61
+ const l1Files = _bundledTemplates.BUNDLED_TEMPLATES[l1 ];
62
+ const l2Files = _bundledTemplates.BUNDLED_TEMPLATES[l2 ];
63
+ if (!l1Files) {
64
+ throw new Error(`Bundled templates for L1 '${l1}' are not available`);
65
+ }
66
+ if (!l2Files) {
67
+ throw new Error(`Bundled templates for L2 '${l2}' are not available`);
68
+ }
69
+ _templateEngine.initTemplateEngineFromBundled.call(void 0, _templateManager.mergeTemplateFiles.call(void 0, l1Files, l2Files));
70
+ } else {
71
+ const templateFiles = _bundledTemplates.BUNDLED_TEMPLATES[options.template ];
72
+ if (!templateFiles) {
73
+ throw new Error(`Bundled templates for '${options.template}' are not available`);
74
+ }
75
+ _templateEngine.initTemplateEngineFromBundled.call(void 0, templateFiles);
58
76
  }
59
77
 
60
- _templateEngine.initTemplateEngineFromBundled.call(void 0, templateFiles);
61
78
  const operationsCode = await _genOperations2.default.call(void 0, spec, options);
62
79
 
63
80
  return operationsCode + _genTypes2.default.call(void 0, spec, options);
@@ -70,6 +87,7 @@ async function generateCode(spec, options) {
70
87
  const {
71
88
  allowDots,
72
89
  arrayFormat,
90
+ queryParamsAsObject,
73
91
  mode,
74
92
  schemaStyle,
75
93
  enumStyle,
@@ -86,11 +104,12 @@ async function generateCode(spec, options) {
86
104
  ),
87
105
  ...(allowDots !== undefined ? { allowDots } : {}),
88
106
  ...(arrayFormat !== undefined ? { arrayFormat } : {}),
107
+ ...(queryParamsAsObject !== undefined ? { queryParamsAsObject } : {}),
89
108
  };
90
109
 
91
110
  return {
92
111
  ...rest,
93
- template: _nullishCoalesce(template, () => ( _swagger.APP_DEFAULTS.template)),
112
+ template: _templateValidator.normalizeTemplate.call(void 0, _nullishCoalesce(template, () => ( _swagger.APP_DEFAULTS.template))),
94
113
  servicePrefix: _nullishCoalesce(rest.servicePrefix, () => ( _swagger.APP_DEFAULTS.servicePrefix)),
95
114
  nullableStrategy: _nullishCoalesce(_nullishCoalesce(nullables, () => ( rest.nullableStrategy)), () => ( _swagger.APP_DEFAULTS.nullableStrategy)),
96
115
  generationMode: _nullishCoalesce(_nullishCoalesce(mode, () => ( rest.generationMode)), () => ( _swagger.APP_DEFAULTS.generationMode)),
package/dist/cli.js CHANGED
@@ -13,12 +13,14 @@ const arrayFormatOption = new (0, _commander.Option)(
13
13
  'Determines how arrays should be serialized'
14
14
  ).choices(['indices', 'repeat', 'brackets']);
15
15
 
16
+ const testingFrameworkOption = new (0, _commander.Option)(
17
+ '-T, --testingFramework <framework>',
18
+ 'Test framework for generated mock stubs (requires --mocks and --out)'
19
+ ).choices(['vitest', 'jest']);
20
+
16
21
  const packageJson = readPackageJson();
17
22
 
18
- const modeOption = new (0, _commander.Option)('-m, --mode <mode>', 'Generation mode').choices([
19
- 'full',
20
- 'schemas',
21
- ]);
23
+ const modeOption = new (0, _commander.Option)('-m, --mode <mode>', 'Generation mode').choices(['full', 'schemas']);
22
24
  const schemaStyleOption = new (0, _commander.Option)(
23
25
  '-d, --schemaStyle <style>',
24
26
  'Schema object declaration style'
@@ -39,6 +41,10 @@ const nullableStrategyOption = new (0, _commander.Option)(
39
41
  '--nullables <strategy>',
40
42
  "Controls how OpenAPI 'nullable' is translated into TypeScript types"
41
43
  ).choices(['include', 'nullableAsOptional', 'ignore']);
44
+ const queryParamsAsObjectOption = new (0, _commander.Option)(
45
+ '--queryParamsAsObject [threshold]',
46
+ 'Group query params into a single object; pass a number to group only when query params count is greater than threshold'
47
+ ).argParser(parseQueryParamsAsObjectArg);
42
48
 
43
49
  const program = new (0, _commander.Command)();
44
50
  program
@@ -63,9 +69,18 @@ program
63
69
  .option('-b, --baseUrl <string>', 'Base URL that will be used as a default value in the clients')
64
70
  .option(
65
71
  '-t, --template <string>',
66
- 'Template used for generating API client. Default: "axios". Other: "fetch", "ng1", "ng2", "swr-axios", "xior", "tsq-xior"'
72
+ 'Template used for generating API client. ' +
73
+ 'L1 (HTTP client) templates: "axios" (default), "fetch", "xior", "ng1", "ng2". ' +
74
+ 'L2 (reactive layer) templates must be paired with an L1 using a comma-separated value: ' +
75
+ '"swr,axios", "swr,fetch", "tsq,xior", "tsq,fetch", etc. ' +
76
+ 'Providing only an L2 name (e.g. "swr") defaults to "fetch" as the L1.',
77
+ parseTemplateArg
67
78
  )
68
79
  .option('--preferAny', 'Use "any" type instead of "unknown"')
80
+ .option(
81
+ '-C, --useClient',
82
+ "Prepend 'use client'; to the generated file. Required for Next.js App Router with SWR or TanStack Query hooks"
83
+ )
69
84
  .option(
70
85
  '--skipDeprecated',
71
86
  'Skip deprecated operations. When enabled, deprecated operations will be skipped from the generated code'
@@ -84,13 +99,19 @@ program
84
99
  .addOption(enumStyleOption)
85
100
  .addOption(enumNamesStyleOption)
86
101
  .addOption(dateFormatOption)
87
- .addOption(nullableStrategyOption);
102
+ .addOption(nullableStrategyOption)
103
+ .addOption(queryParamsAsObjectOption)
104
+ .option(
105
+ '--mocks <path>',
106
+ 'Output path for the generated mock/stub file (requires --testingFramework and --out)'
107
+ )
108
+ .addOption(testingFrameworkOption);
88
109
 
89
110
  program.parse(process.argv);
90
111
 
91
112
  const options = program.opts();
92
113
 
93
- _index.runCodeGenerator.call(void 0, options).then(complete, error);
114
+ _index.runCodeGenerator.call(void 0, options ).then(complete, error);
94
115
 
95
116
  function complete([code, opts]) {
96
117
  if (opts.out) {
@@ -116,3 +137,29 @@ function readPackageJson() {
116
137
  throw new Error('Could not read package.json file');
117
138
  }
118
139
  }
140
+
141
+ function parseTemplateArg(value) {
142
+ const parts = value.split(',').map((s) => s.trim());
143
+ if (parts.length === 1) {
144
+ return parts[0];
145
+ }
146
+ if (parts.length === 2) {
147
+ return [parts[0], parts[1]];
148
+ }
149
+ throw new Error(
150
+ `--template accepts at most 2 comma-separated values (e.g. "swr,axios"). Got ${parts.length}.`
151
+ );
152
+ }
153
+
154
+ function parseQueryParamsAsObjectArg(value) {
155
+ if (value === undefined) {
156
+ return true;
157
+ }
158
+
159
+ const threshold = Number(value);
160
+ if (!Number.isInteger(threshold) || threshold < 0) {
161
+ throw new Error('--queryParamsAsObject threshold must be a non-negative integer');
162
+ }
163
+
164
+ return threshold;
165
+ }