swaggie 1.9.0-dev.1 → 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
@@ -1,6 +1,28 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true});var _nodefs = require('node:fs');
2
2
  var _nodepath = require('node:path');
3
3
 
4
+ /**
5
+ * Computes the relative import path from `fromFile` to `toFile`, stripping the
6
+ * file extension and ensuring the result always starts with `./` or `../`.
7
+ *
8
+ * @param fromFile - The file that will contain the import statement (e.g. the mock file)
9
+ * @param toFile - The file being imported (e.g. the generated API client file)
10
+ *
11
+ * @example
12
+ * deriveRelativeImport('src/__mocks__/api.ts', 'src/generated/api.ts')
13
+ * // → '../generated/api'
14
+ *
15
+ * deriveRelativeImport('src/api.mock.ts', 'src/api.ts')
16
+ * // → './api'
17
+ */
18
+ function deriveRelativeImport(fromFile, toFile) {
19
+ const rel = _nodepath.relative.call(void 0, _nodepath.dirname.call(void 0, fromFile), toFile);
20
+ const withoutExt = rel.replace(/\.[jt]sx?$/i, '');
21
+ // Normalise backslashes on Windows and ensure a leading ./
22
+ const normalised = withoutExt.replace(/\\/g, '/');
23
+ return normalised.startsWith('.') ? normalised : './' + normalised;
24
+ } exports.deriveRelativeImport = deriveRelativeImport;
25
+
4
26
  function saveFile(filePath, contents) {
5
27
  return new Promise((resolve, reject) => {
6
28
  _nodefs.mkdir.call(void 0, _nodepath.dirname.call(void 0, filePath), { recursive: true }, (err) => {
@@ -5,10 +5,6 @@ var _yaml = require('yaml');
5
5
 
6
6
 
7
7
 
8
-
9
-
10
-
11
-
12
8
  const SUPPORTED_COMPONENT_SECTIONS = new Set([
13
9
  'schemas',
14
10
  'parameters',
@@ -26,8 +22,6 @@ const SUPPORTED_COMPONENT_SECTIONS = new Set([
26
22
 
27
23
 
28
24
 
29
-
30
-
31
25
  /**
32
26
  * Resolves external file refs into local component refs.
33
27
  * For now we only support local file refs (no http/https) and component targets.
@@ -109,9 +103,7 @@ async function resolveRef(
109
103
  }
110
104
 
111
105
  if (!fragment) {
112
- throw new Error(
113
- `External refs must include a JSON pointer fragment: '${rawRef}'`
114
- );
106
+ throw new Error(`External refs must include a JSON pointer fragment: '${rawRef}'`);
115
107
  }
116
108
 
117
109
  if (!fragment.startsWith('/')) {
@@ -136,9 +128,7 @@ async function importRefFromFile(
136
128
 
137
129
  if (!targetInfo) {
138
130
  if (context.resolvingRefs.has(importKey)) {
139
- throw new Error(
140
- `Circular non-component external ref is not supported: '${rawRef}'`
141
- );
131
+ throw new Error(`Circular non-component external ref is not supported: '${rawRef}'`);
142
132
  }
143
133
 
144
134
  const targetCopy = structuredClone(target);
@@ -172,10 +162,7 @@ async function importRefFromFile(
172
162
  }
173
163
 
174
164
  function parseImportTarget(pointer) {
175
- const parts = pointer
176
- .replace(/^#\//, '')
177
- .split('/')
178
- .map(unescapePointerSegment);
165
+ const parts = pointer.replace(/^#\//, '').split('/').map(unescapePointerSegment);
179
166
 
180
167
  if (parts.length === 2) {
181
168
  const [legacySection, name] = parts;
@@ -250,12 +237,13 @@ function getOrCreateComponentAlias(
250
237
  return candidate;
251
238
  }
252
239
 
253
- async function getValueByPointer(filePath, pointer, context) {
240
+ async function getValueByPointer(
241
+ filePath,
242
+ pointer,
243
+ context
244
+ ) {
254
245
  const doc = await loadDocument(filePath, context);
255
- const parts = pointer
256
- .replace(/^#\//, '')
257
- .split('/')
258
- .map(unescapePointerSegment);
246
+ const parts = pointer.replace(/^#\//, '').split('/').map(unescapePointerSegment);
259
247
 
260
248
  let current = doc;
261
249
  for (const part of parts) {
@@ -1,9 +1,11 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }var _eta = require('eta');
2
2
 
3
3
  let engine;
4
+ let loadedFiles = null;
4
5
 
5
6
  function initTemplateEngineFromDirectory(templatesDir) {
6
7
  engine = new (0, _eta.Eta)({ views: templatesDir });
8
+ loadedFiles = null;
7
9
  } exports.initTemplateEngineFromDirectory = initTemplateEngineFromDirectory;
8
10
 
9
11
  function initTemplateEngineFromBundled(templateFiles) {
@@ -18,8 +20,24 @@ let engine;
18
20
  const baseName = _nullishCoalesce(_optionalChain([templatePath, 'access', _ => _.split, 'call', _2 => _2('/'), 'access', _3 => _3.pop, 'call', _4 => _4(), 'optionalAccess', _5 => _5.split, 'call', _6 => _6('\\'), 'access', _7 => _7.pop, 'call', _8 => _8()]), () => ( templatePath));
19
21
  return templateFiles[baseName];
20
22
  };
23
+ loadedFiles = templateFiles;
21
24
  } exports.initTemplateEngineFromBundled = initTemplateEngineFromBundled;
22
25
 
26
+ /**
27
+ * Returns true if the given template file is available in the currently loaded
28
+ * bundled template store. Always returns false when using a directory-based
29
+ * engine (filesystem templates are checked via renderFile directly).
30
+ */
31
+ function hasTemplateFile(templateFile) {
32
+ if (!loadedFiles) {
33
+ return false;
34
+ }
35
+ return (
36
+ templateFile in loadedFiles ||
37
+ Object.keys(loadedFiles).some((k) => _optionalChain([k, 'access', _9 => _9.split, 'call', _10 => _10('/'), 'access', _11 => _11.pop, 'call', _12 => _12(), 'optionalAccess', _13 => _13.split, 'call', _14 => _14('\\'), 'access', _15 => _15.pop, 'call', _16 => _16()]) === templateFile)
38
+ );
39
+ } exports.hasTemplateFile = hasTemplateFile;
40
+
23
41
  /**
24
42
  * Get's a template file and renders it with the provided data.
25
43
  */
@@ -1,6 +1,8 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }var _nodefs = require('node:fs'); var _nodefs2 = _interopRequireDefault(_nodefs);
2
2
  var _nodepath = require('node:path'); var _nodepath2 = _interopRequireDefault(_nodepath);
3
3
  var _templateEngine = require('./templateEngine');
4
+
5
+
4
6
  let bundledTemplates = null;
5
7
 
6
8
 
@@ -9,34 +11,141 @@ let bundledTemplates = null;
9
11
  bundledTemplates = templates;
10
12
  } exports.setBundledTemplates = setBundledTemplates;
11
13
 
12
- function loadAllTemplateFiles(templateName) {
13
- if (!templateName) {
14
+ /**
15
+ * Loads template files for the given template spec and initializes the
16
+ * template engine.
17
+ *
18
+ * Accepts:
19
+ * - A single template name or filesystem path (L1 or custom).
20
+ * - A [L2, L1] tuple: the two template sets are merged, with L2 winning on
21
+ * filename conflicts except for `baseClient.ejs` which is stored as
22
+ * `baseClientL2.ejs` so that both L1 and L2 base clients can be rendered.
23
+ */
24
+ function loadAllTemplateFiles(template) {
25
+ if (!template) {
14
26
  throw new Error('No template name was provided');
15
27
  }
16
28
 
17
- if (loadFromBundledTemplates(templateName)) {
29
+ if (Array.isArray(template)) {
30
+ const [l2, l1] = template;
31
+ const l1Files = resolveTemplateFiles(l1);
32
+ const l2Files = resolveTemplateFiles(l2);
33
+
34
+ if (!l1Files || !l2Files) {
35
+ // At least one side was a filesystem path — use directory overlay
36
+ loadCompositeFromFilesystem(l2, l1);
37
+ return;
38
+ }
39
+
40
+ // Both sides are bundled: merge in memory
41
+ const merged = mergeTemplateFiles(l1Files, l2Files);
42
+ _templateEngine.initTemplateEngineFromBundled.call(void 0, merged);
18
43
  return;
19
44
  }
20
45
 
21
- const templatesDir = _nodefs2.default.existsSync(templateName)
22
- ? templateName
23
- : _nodepath2.default.join(__dirname, '..', '..', 'templates', templateName);
24
-
25
- if (!_nodefs2.default.existsSync(templatesDir)) {
26
- throw new Error(
27
- `Could not find directory with the template (we tried ${templatesDir}). Is the template name correct?`
28
- );
46
+ // Single template
47
+ if (!loadFromBundledTemplates(template)) {
48
+ loadFromFilesystem(template);
29
49
  }
30
- _templateEngine.initTemplateEngineFromDirectory.call(void 0, templatesDir);
31
50
  } exports.loadAllTemplateFiles = loadAllTemplateFiles;
32
51
 
52
+ /**
53
+ * Merges L1 and L2 file sets. L2 wins on filename conflicts, EXCEPT that
54
+ * `baseClient.ejs` from L2 is stored under the key `baseClientL2.ejs` so
55
+ * both L1 and L2 base clients can be rendered independently.
56
+ */
57
+ function mergeTemplateFiles(
58
+ l1Files,
59
+ l2Files
60
+ ) {
61
+ const merged = { ...l1Files };
62
+
63
+ for (const [name, content] of Object.entries(l2Files)) {
64
+ if (name === 'baseClient.ejs') {
65
+ merged['baseClientL2.ejs'] = content;
66
+ } else {
67
+ merged[name] = content;
68
+ }
69
+ }
70
+
71
+ return merged;
72
+ } exports.mergeTemplateFiles = mergeTemplateFiles;
73
+
74
+ // ---------------------------------------------------------------------------
75
+ // Private helpers
76
+ // ---------------------------------------------------------------------------
77
+
78
+ /**
79
+ * Returns the bundled file map for a named template, or null if the name
80
+ * refers to a filesystem path (i.e. the bundled store does not have it).
81
+ */
82
+ function resolveTemplateFiles(templateName) {
83
+ const bundled = _optionalChain([bundledTemplates, 'optionalAccess', _ => _[templateName]]);
84
+ if (bundled) return bundled;
85
+
86
+ // Could be a bundled name that just isn't in the store yet, or a filesystem
87
+ // path. Return null to signal "use filesystem".
88
+ return null;
89
+ }
90
+
33
91
  function loadFromBundledTemplates(templateName) {
34
- const templateFiles = _optionalChain([bundledTemplates, 'optionalAccess', _ => _[templateName]]);
92
+ const templateFiles = _optionalChain([bundledTemplates, 'optionalAccess', _2 => _2[templateName]]);
35
93
  if (!templateFiles) {
36
94
  return false;
37
95
  }
38
96
 
39
97
  _templateEngine.initTemplateEngineFromBundled.call(void 0, templateFiles);
40
-
41
98
  return true;
42
99
  }
100
+
101
+ function loadFromFilesystem(templateName) {
102
+ const templatesDir = resolveDir(templateName);
103
+ _templateEngine.initTemplateEngineFromDirectory.call(void 0, templatesDir);
104
+ }
105
+
106
+ /**
107
+ * For composite [L2, L1] pairs where at least one side lives on the
108
+ * filesystem: read both directories into memory, merge, and initialize a
109
+ * bundled-style engine so the merge logic is uniform.
110
+ */
111
+ function loadCompositeFromFilesystem(l2, l1) {
112
+ const l1Files = getFilesFromSource(l1);
113
+ const l2Files = getFilesFromSource(l2);
114
+ const merged = mergeTemplateFiles(l1Files, l2Files);
115
+ _templateEngine.initTemplateEngineFromBundled.call(void 0, merged);
116
+ }
117
+
118
+ /**
119
+ * Reads all `.ejs` files from a template source (bundled store or filesystem
120
+ * directory) into a plain `Record<filename, content>` map.
121
+ */
122
+ function getFilesFromSource(templateName) {
123
+ // Prefer bundled store
124
+ const bundled = _optionalChain([bundledTemplates, 'optionalAccess', _3 => _3[templateName]]);
125
+ if (bundled) return bundled;
126
+
127
+ const dir = resolveDir(templateName);
128
+ return readEjsFilesFromDir(dir);
129
+ }
130
+
131
+ function resolveDir(templateName) {
132
+ const resolved = _nodefs2.default.existsSync(templateName)
133
+ ? templateName
134
+ : _nodepath2.default.join(__dirname, '..', '..', 'templates', templateName);
135
+
136
+ if (!_nodefs2.default.existsSync(resolved)) {
137
+ throw new Error(
138
+ `Could not find directory with the template (we tried ${resolved}). Is the template name correct?`
139
+ );
140
+ }
141
+ return resolved;
142
+ }
143
+
144
+ function readEjsFilesFromDir(dir) {
145
+ const files = _nodefs2.default.readdirSync(dir).filter((f) => f.endsWith('.ejs'));
146
+ const result = {};
147
+ for (const file of files) {
148
+ result[file] = _nodefs2.default.readFileSync(_nodepath2.default.join(dir, file), 'utf8');
149
+ }
150
+ return result;
151
+ }
@@ -0,0 +1,127 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
+
3
+ const L1_TEMPLATES = ['axios', 'fetch', 'xior', 'ng1', 'ng2'];
4
+ const L2_TEMPLATES = ['swr', 'tsq'];
5
+
6
+ /** L1 templates that are incompatible with any L2 (framework-specific clients) */
7
+ const L2_INCOMPATIBLE_L1 = ['ng1', 'ng2'];
8
+
9
+ /** Legacy composite template names and their replacements */
10
+ const LEGACY_TEMPLATES = {
11
+ 'swr-axios': ['swr', 'axios'],
12
+ 'tsq-xior': ['tsq', 'xior'],
13
+ };
14
+
15
+ /** Default L1 to use when only an L2 template is specified */
16
+ const DEFAULT_L1_FOR_L2 = 'fetch';
17
+
18
+ function isL1Template(name) {
19
+ return (L1_TEMPLATES ).includes(name);
20
+ } exports.isL1Template = isL1Template;
21
+
22
+ function isL2Template(name) {
23
+ return (L2_TEMPLATES ).includes(name);
24
+ } exports.isL2Template = isL2Template;
25
+
26
+ /**
27
+ * Validates that a raw template input is acceptable and throws a descriptive
28
+ * error if not. Does NOT normalize — call `normalizeTemplate` for that.
29
+ *
30
+ * Rules:
31
+ * - Legacy composite names ('swr-axios', 'tsq-xior') → migration error
32
+ * - Single L2 name → allowed (will be normalized to [L2, default-L1] later)
33
+ * - Single L1 name or custom path → allowed
34
+ * - Array with wrong element count → error
35
+ * - Array where first element is not an L2 → error
36
+ * - Array where second element is an incompatible L1 (ng1/ng2) → error
37
+ */
38
+ function validateTemplate(template) {
39
+ if (Array.isArray(template)) {
40
+ const arr = template ;
41
+ if (arr.length !== 2) {
42
+ throw new Error(
43
+ `Invalid template: array must have exactly 2 elements [L2, L1], e.g. ["swr", "axios"]. Got ${arr.length} element(s).`
44
+ );
45
+ }
46
+
47
+ const [l2, l1] = template;
48
+
49
+ // First element must be an L2 template (or a custom path — we can't validate
50
+ // custom paths statically, so we only reject known-bad L1 names here).
51
+ if (isL1Template(l2)) {
52
+ throw new Error(
53
+ `Invalid template pair: "${l2}" is an L1 (HTTP client) template and cannot be used as the first element. ` +
54
+ `The first element must be an L2 template (${L2_TEMPLATES.join(', ')}) or a custom path. ` +
55
+ `To use "${l2}" alone, pass it as a string: template: "${l2}".`
56
+ );
57
+ }
58
+
59
+ if (isL2Template(l2) && isL1Template(l1) && (L2_INCOMPATIBLE_L1 ).includes(l1)) {
60
+ throw new Error(
61
+ `Invalid template pair: "${l1}" is a framework-specific client and is not compatible with L2 template "${l2}". ` +
62
+ `Compatible L1 templates for L2 are: ${L1_TEMPLATES.filter((t) => !L2_INCOMPATIBLE_L1.includes(t)).join(', ')}.`
63
+ );
64
+ }
65
+
66
+ return;
67
+ }
68
+
69
+ // Single string
70
+ if (typeof template === 'string') {
71
+ const legacy = LEGACY_TEMPLATES[template];
72
+ if (legacy) {
73
+ throw new Error(
74
+ `"${template}" is no longer a valid template name. ` +
75
+ `It has been split into separate L1 and L2 templates. ` +
76
+ `Use template: ["${legacy[0]}", "${legacy[1]}"] instead.`
77
+ );
78
+ }
79
+ // Single L2, single L1, or custom path are all fine — L2 alone gets a
80
+ // default L1 applied during normalization.
81
+ }
82
+ } exports.validateTemplate = validateTemplate;
83
+
84
+ /**
85
+ * Normalizes a validated template input into a `ResolvedTemplate`:
86
+ * - Single L2 name → [L2, DEFAULT_L1_FOR_L2]
87
+ * - Everything else passes through unchanged.
88
+ *
89
+ * Call `validateTemplate` before this function.
90
+ */
91
+ function normalizeTemplate(template) {
92
+ if (typeof template === 'string' && isL2Template(template)) {
93
+ return [template, DEFAULT_L1_FOR_L2];
94
+ }
95
+ return template ;
96
+ } exports.normalizeTemplate = normalizeTemplate;
97
+
98
+ /**
99
+ * Returns the effective L1 template name from a resolved template.
100
+ * For arrays, returns the second element; for single strings, returns the string.
101
+ */
102
+ function getL1Template(template) {
103
+ return Array.isArray(template) ? template[1] : template;
104
+ } exports.getL1Template = getL1Template;
105
+
106
+ /**
107
+ * Returns the TypeScript type name for the `$httpConfig` parameter that the
108
+ * L1 template exposes on each operation method.
109
+ *
110
+ * Used by L2 operation partials so the `$httpConfig` parameter they forward
111
+ * to the underlying client method has the correct type regardless of which L1
112
+ * is paired with the L2.
113
+ */
114
+ function getHttpConfigType(template) {
115
+ const l1 = getL1Template(template);
116
+ switch (l1) {
117
+ case 'axios':
118
+ return 'AxiosRequestConfig';
119
+ case 'xior':
120
+ return 'XiorRequestConfig';
121
+ case 'fetch':
122
+ return 'RequestInit';
123
+ default:
124
+ // Custom path or unknown — fall back to a permissive type
125
+ return 'Record<string, unknown>';
126
+ }
127
+ } exports.getHttpConfigType = getHttpConfigType;
@@ -74,7 +74,7 @@ const reservedKeywords = new Set([
74
74
  */
75
75
  function escapeIdentifier(name) {
76
76
  if (!name) {
77
- return name;
77
+ return _nullishCoalesce(name, () => ( ''));
78
78
  }
79
79
 
80
80
  if (reservedKeywords.has(name) || /^[0-9]/.test(name)) {
@@ -139,10 +139,11 @@ const reservedKeywords = new Set([
139
139
  }
140
140
 
141
141
  return operations.reduce((groups, op) => {
142
- if (!groups[op.group]) {
143
- groups[op.group] = [];
142
+ const groupKey = _nullishCoalesce(op.group, () => ( 'default'));
143
+ if (!groups[groupKey]) {
144
+ groups[groupKey] = [];
144
145
  }
145
- groups[op.group].push(op);
146
+ groups[groupKey].push(op);
146
147
  return groups;
147
148
  }, {});
148
149
  } exports.groupOperationsByGroupName = groupOperationsByGroupName;
@@ -219,11 +220,13 @@ function resolveResponseRef(
219
220
  return [];
220
221
  }
221
222
 
222
- return arr.concat().sort(sortByKey(key));
223
+ return arr.concat().sort((a, b) => {
224
+ const aVal = (a )[key] ;
225
+ const bVal = (b )[key] ;
226
+ return aVal != null && bVal != null ? (aVal > bVal ? 1 : bVal > aVal ? -1 : 0) : 0;
227
+ });
223
228
  } exports.orderBy = orderBy;
224
229
 
225
- const sortByKey = (key) => (a, b) => (a[key] > b[key] ? 1 : b[key] > a[key] ? -1 : 0);
226
-
227
230
  const orderedContentTypes = [
228
231
  'text/plain',
229
232
  'application/x-www-form-urlencoded',
@@ -233,32 +236,35 @@ const preferredJsonContentTypes = ['application/json', 'text/json'];
233
236
  function getBestContentType(
234
237
  reqBody
235
238
  ) {
236
- const contentTypes = Object.keys(reqBody.content);
239
+ const content = _nullishCoalesce(reqBody.content, () => ( {}));
240
+ const contentTypes = Object.keys(content);
237
241
  if (contentTypes.length === 0) {
238
242
  return [null, null];
239
243
  }
240
244
 
241
- const preferredJsonContentType = preferredJsonContentTypes.find((ct) => contentTypes.includes(ct));
245
+ const preferredJsonContentType = preferredJsonContentTypes.find((ct) =>
246
+ contentTypes.includes(ct)
247
+ );
242
248
  if (preferredJsonContentType) {
243
- const typeObject = reqBody.content[preferredJsonContentType];
249
+ const typeObject = content[preferredJsonContentType];
244
250
  const type = getContentType(preferredJsonContentType);
245
251
  return [typeObject, type];
246
252
  }
247
253
 
248
254
  const jsonLikeContentType = contentTypes.find(isJsonLikeContentType);
249
255
  if (jsonLikeContentType) {
250
- const typeObject = reqBody.content[jsonLikeContentType];
256
+ const typeObject = content[jsonLikeContentType];
251
257
  return [typeObject, 'json'];
252
258
  }
253
259
 
254
260
  const firstContentType = orderedContentTypes.find((ct) => contentTypes.includes(ct));
255
261
  if (firstContentType) {
256
- const typeObject = reqBody.content[firstContentType];
262
+ const typeObject = content[firstContentType];
257
263
  const type = getContentType(firstContentType);
258
264
  return [typeObject, type];
259
265
  }
260
266
 
261
- const typeObject = reqBody.content[contentTypes[0]];
267
+ const typeObject = content[contentTypes[0]];
262
268
  const type = getContentType(contentTypes[0]);
263
269
  return [typeObject, type];
264
270
  } exports.getBestContentType = getBestContentType;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "swaggie",
3
- "version": "1.9.0-dev.1",
3
+ "version": "2.0.0",
4
4
  "description": "Generate a fully typed TypeScript API client from your OpenAPI 3 spec",
5
5
  "author": {
6
6
  "name": "Piotr Dabrowski",
@@ -40,7 +40,7 @@
40
40
  "build": "bun run bundle-templates && sucrase ./src -d ./dist --transforms typescript,imports && bun run rm-tests && bun run types",
41
41
  "bundle-templates": "bun scripts/bundle-templates.ts",
42
42
  "rm-tests": "find dist/ \\( -name '*.spec.js' -o -name 'types.js' \\) -type f -delete",
43
- "types": "tsc src/types.ts --outDir dist/ --declaration --emitDeclarationOnly && cp test/index.d.ts ./dist/",
43
+ "types": "tsc --project tsconfig.types.json && cp test/index.d.ts ./dist/",
44
44
  "docs:build": "vitepress build docs",
45
45
  "docs:dev": "vitepress dev docs",
46
46
  "docs:preview": "vitepress preview docs"
@@ -66,7 +66,8 @@
66
66
  "service",
67
67
  "typescript",
68
68
  "codegen",
69
- "TanStack Query"
69
+ "TanStack Query",
70
+ "mocks"
70
71
  ],
71
72
  "dependencies": {
72
73
  "case": "^1.6.3",
@@ -79,7 +80,7 @@
79
80
  "bun-types": "1.3.11",
80
81
  "openapi-types": "^12.1.3",
81
82
  "sucrase": "3.35.1",
82
- "typescript": "5.9.3",
83
+ "typescript": "6.0.2",
83
84
  "vitepress": "^2.0.0-alpha.17",
84
85
  "vitepress-plugin-tabs": "0.8.0"
85
86
  }
@@ -10,31 +10,35 @@ $config?: AxiosRequestConfig
10
10
  return axios.request<<%~ it.returnType %>>({
11
11
  url: url,
12
12
  method: '<%= it.method %>',
13
- <% if(it.body) { %>
14
- <% if(it.body.contentType === 'urlencoded') { %>
13
+ <% if(it.body) { -%>
14
+ <% if(it.body.contentType === 'urlencoded') { -%>
15
15
  data: new URLSearchParams(<%= it.body.name %> as any),
16
- <% } else { %>
16
+ <% } else { -%>
17
17
  data: <%= it.body.name %>,
18
- <% } %>
19
- <% } %>
20
- <% if(it.query && it.query.length > 0) { %>
18
+ <% } -%>
19
+ <% } -%>
20
+ <% if(it.query && it.query.length > 0) { -%>
21
21
  params: {
22
- <% it.query.forEach((parameter) => { %>
23
- '<%= parameter.originalName %>': <%= parameter.name %>,
24
- <% }); %>
25
- },
26
- <% } %>
27
- <% if(it.headers && it.headers.length > 0) { %>
22
+ <% it.query.forEach((parameter) => { -%>
23
+ <% if (it.queryParamObject) { -%>
24
+ '<%= parameter.originalName %>': <%= it.queryParamObject.name %>?.<%= parameter.name %>,
25
+ <% } else { -%>
26
+ '<%= parameter.originalName %>': <%= parameter.name %>,
27
+ <% } -%>
28
+ <% }); -%>
29
+ },
30
+ <% } -%>
31
+ <% if(it.headers && it.headers.length > 0) { -%>
28
32
  headers: {
29
- <% it.headers.forEach((parameter) => { %>
30
- <% if (parameter.value) { %>
31
- '<%= parameter.originalName %>': '<%= parameter.value %>',
32
- <% } else { %>
33
- '<%= parameter.originalName %>': <%= parameter.name %>,
34
- <% } %>
35
- <% }); %>
36
- },
37
- <% } %>
33
+ <% it.headers.forEach((parameter) => { -%>
34
+ <% if (parameter.value) { -%>
35
+ '<%= parameter.originalName %>': '<%= parameter.value %>',
36
+ <% } else { -%>
37
+ '<%= parameter.originalName %>': <%= parameter.name %>,
38
+ <% } -%>
39
+ <% }); -%>
40
+ },
41
+ <% } -%>
38
42
  ...$config,
39
43
  });
40
44
  },
@@ -8,7 +8,11 @@ $config?: RequestInit
8
8
  const url = `${defaults.baseUrl}<%= it.url %>?<%
9
9
  if(it.query && it.query.length > 0) { %>${defaults.paramsSerializer({<%
10
10
  it.query.forEach((parameter) => { %>
11
+ <% if (it.queryParamObject) { %>
12
+ '<%= parameter.originalName %>': <%= it.queryParamObject.name %>?.<%= parameter.name %>,
13
+ <% } else { %>
11
14
  '<%= parameter.originalName %>': <%= parameter.name %>,
15
+ <% } %>
12
16
  <% }); %>})}<% } %>`;
13
17
 
14
18
  <% if(it.headers && it.headers.length > 0) { %>
@@ -8,16 +8,24 @@
8
8
  let url = `<%= it.url %>?`;
9
9
  <% if(it.query && it.query.length > 0) { %>
10
10
  <% it.query.forEach((parameter) => { %>
11
- if (<%= parameter.name %> !== undefined) {
11
+ if (<%= it.queryParamObject ? `${it.queryParamObject.name}?.${parameter.name}` : parameter.name %> !== undefined) {
12
12
  <% if(!!parameter.original && parameter.original.type === 'array') { %>
13
- <%= parameter.name %>.forEach(item => { url += serializeQueryParam(item, '<%= parameter.originalName %>') + "&"; });
13
+ <%= it.queryParamObject ? `${it.queryParamObject.name}.${parameter.name}` : parameter.name %>.forEach(item => { url += serializeQueryParam(item, '<%= parameter.originalName %>') + "&"; });
14
14
  <% } else {%>
15
- url += serializeQueryParam(<%= parameter.name %>, '<%= parameter.originalName %>') + "&";
15
+ url += serializeQueryParam(<%= it.queryParamObject ? `${it.queryParamObject.name}.${parameter.name}` : parameter.name %>, '<%= parameter.originalName %>') + "&";
16
16
  <% } %>
17
17
  }
18
18
  <% }); %>
19
19
  <% } %>
20
20
 
21
+ <% if(it.headers && it.headers.length > 0) { it.headers.forEach((parameter) => { %>
22
+ <% if (parameter.value) { %>
23
+ config = { ...config, headers: { ...config?.headers, '<%= parameter.originalName %>': '<%= parameter.value %>' } };
24
+ <% } else { %>
25
+ if (<%= parameter.name %>) {
26
+ config = { ...config, headers: { ...config?.headers, '<%= parameter.originalName %>': <%= parameter.name %> } };
27
+ }
28
+ <% } %><% }); } %>
21
29
  return this.$<%= it.method.toLowerCase() %>(
22
30
  url,
23
31
  <% if(['POST', 'PUT', 'PATCH'].includes(it.method)) { %>