swaggie 1.8.1 → 1.8.3

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
@@ -72,21 +72,23 @@ swaggie -s https://petstore3.swagger.io/api/v3/openapi.json -o ./client/petstore
72
72
  **Available options:**
73
73
 
74
74
  ```
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")
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
83
  --enumStyle <style> Enum style for plain string enums: "union" or "enum" (default: "union")
84
- --preferAny Use "any" instead of "unknown" for untyped values (default: false)
85
- --skipDeprecated Exclude deprecated operations from the output (default: false)
86
- --servicePrefix Prefix for service names useful when generating multiple APIs
87
- --allowDots Use dot notation to serialize nested object query params
88
- --arrayFormat How arrays are serialized: "indices", "repeat", or "brackets"
89
- -h, --help Show help
84
+ --dateFormat <format> Date handling in schemas: "Date" or "string"
85
+ --nullables <strategy> Nullable handling: "include", "nullableAsOptional", or "ignore"
86
+ --preferAny Use "any" instead of "unknown" for untyped values (default: false)
87
+ --skipDeprecated Exclude deprecated operations from the output (default: false)
88
+ --servicePrefix Prefix for service names useful when generating multiple APIs
89
+ --allowDots Use dot notation to serialize nested object query params
90
+ --arrayFormat How arrays are serialized: "indices", "repeat", or "brackets"
91
+ -h, --help Show help
90
92
  ```
91
93
 
92
94
  ### Formatting the Output
package/dist/cli.js CHANGED
@@ -27,6 +27,14 @@ const enumStyleOption = new (0, _commander.Option)(
27
27
  '--enumStyle <style>',
28
28
  'Enum declaration style for plain string enums'
29
29
  ).choices(['union', 'enum']);
30
+ const dateFormatOption = new (0, _commander.Option)(
31
+ '--dateFormat <format>',
32
+ 'How date fields are emitted in generated types'
33
+ ).choices(['Date', 'string']);
34
+ const nullableStrategyOption = new (0, _commander.Option)(
35
+ '--nullables <strategy>',
36
+ "Controls how OpenAPI 'nullable' is translated into TypeScript types"
37
+ ).choices(['include', 'nullableAsOptional', 'ignore']);
30
38
 
31
39
  const program = new (0, _commander.Command)();
32
40
  program
@@ -69,7 +77,9 @@ program
69
77
  .addOption(arrayFormatOption)
70
78
  .addOption(modeOption)
71
79
  .addOption(schemaStyleOption)
72
- .addOption(enumStyleOption);
80
+ .addOption(enumStyleOption)
81
+ .addOption(dateFormatOption)
82
+ .addOption(nullableStrategyOption);
73
83
 
74
84
  program.parse(process.argv);
75
85
 
@@ -96,45 +96,63 @@ function prepareClient(
96
96
  }
97
97
 
98
98
  return ops.map((op) => {
99
- const [respObject, responseContentType] = _utils.getBestResponse.call(void 0, op, components);
100
- const returnType = _swagger.getParameterType.call(void 0, respObject, options);
99
+ const operationContext = `${op.method.toUpperCase()} ${op.path} (${op.operationId || 'unknown operationId'})`;
101
100
 
102
- const body = getRequestBody(op.requestBody, components, options);
103
- const queryParams = getParams(op.parameters , options, ['query']);
104
- const params = getParams(op.parameters , options);
101
+ try {
102
+ const [respObject, responseContentType] = _utils.getBestResponse.call(void 0, op, components);
103
+ const returnType = _swagger.getParameterType.call(void 0, respObject, options);
105
104
 
106
- if (body) {
107
- params.unshift(body);
108
- }
105
+ const body = getRequestBody(op.requestBody, components, options);
106
+ const queryParams = getParams(op.parameters , options, ['query']);
107
+ const params = getParams(op.parameters , options);
108
+
109
+ if (body) {
110
+ params.unshift(body);
111
+ }
109
112
 
110
113
  // If all parameters have 'x-position' defined, sort them by it
111
- if (params.every((p) => p.original['x-position'])) {
112
- params.sort((a, b) => a.original['x-position'] - b.original['x-position']);
113
- }
114
+ if (params.every((p) => p.original['x-position'])) {
115
+ params.sort((a, b) => a.original['x-position'] - b.original['x-position']);
116
+ }
114
117
 
115
- markParametersAsSkippable(params);
118
+ markParametersAsSkippable(params);
116
119
 
117
- const headers = getParams(op.parameters , options, ['header']);
118
- // Some libraries need to know the content type of the request body in case of urlencoded body
119
- if (_optionalChain([body, 'optionalAccess', _2 => _2.contentType]) === 'urlencoded') {
120
- headers.push({
121
- originalName: 'Content-Type',
122
- value: 'application/x-www-form-urlencoded',
123
- });
124
- }
120
+ const headers = getParams(
121
+ op.parameters ,
122
+ options,
123
+ ['header']
124
+ );
125
+ // Some libraries need explicit Content-Type for request bodies.
126
+ if (_optionalChain([body, 'optionalAccess', _2 => _2.contentType]) === 'urlencoded') {
127
+ upsertFixedHeader(headers, 'Content-Type', 'application/x-www-form-urlencoded');
128
+ } else if (_optionalChain([body, 'optionalAccess', _3 => _3.contentType]) === 'json' && options.template === 'fetch') {
129
+ upsertFixedHeader(headers, 'Content-Type', 'application/json');
130
+ }
125
131
 
126
- return {
127
- jsDocs: _jsDocs.prepareJsDocsForOperation.call(void 0, op, params),
128
- returnType,
129
- responseContentType,
130
- method: op.method.toUpperCase(),
131
- name: getOperationName(op.operationId, op.group),
132
- url: prepareUrl(op.path),
133
- parameters: params,
134
- query: queryParams,
135
- body,
136
- headers,
137
- };
132
+ return {
133
+ jsDocs: _jsDocs.prepareJsDocsForOperation.call(void 0, op, params),
134
+ returnType,
135
+ responseContentType,
136
+ method: op.method.toUpperCase(),
137
+ name: getOperationName(op.operationId, op.group),
138
+ url: prepareUrl(op.path),
139
+ parameters: params,
140
+ query: queryParams,
141
+ body,
142
+ headers,
143
+ };
144
+ } catch (error) {
145
+ const message = error instanceof Error ? error.message : String(error);
146
+ if (message.includes('Invalid schema at')) {
147
+ throw new Error(
148
+ `Failed to prepare operation ${operationContext}. ` +
149
+ 'Check if schema is valid for this operation. ' +
150
+ 'Most common culprit is `properties.$ref` (use `schema.$ref` at root, or put `$ref` under a named property).'
151
+ );
152
+ }
153
+
154
+ throw new Error(`Failed to prepare operation ${operationContext}: ${message}`);
155
+ }
138
156
  });
139
157
  } exports.prepareOperations = prepareOperations;
140
158
 
@@ -237,10 +255,10 @@ function prepareUrl(path) {
237
255
  type: _swagger.getParameterType.call(void 0, p, options),
238
256
  optional: p.required === undefined || p.required === null ? true : !p.required,
239
257
  original: p,
240
- jsDoc: _optionalChain([p, 'access', _3 => _3.description, 'optionalAccess', _4 => _4.trim, 'call', _5 => _5()]),
258
+ jsDoc: _optionalChain([p, 'access', _4 => _4.description, 'optionalAccess', _5 => _5.trim, 'call', _6 => _6()]),
241
259
  }));
242
260
 
243
- if (_optionalChain([options, 'access', _6 => _6.modifiers, 'optionalAccess', _7 => _7.parameters])) {
261
+ if (_optionalChain([options, 'access', _7 => _7.modifiers, 'optionalAccess', _8 => _8.parameters])) {
244
262
  for (const [name, modifier] of Object.entries(options.modifiers.parameters)) {
245
263
  const paramIndex = result.findIndex(
246
264
  (p) => p.original.in !== 'path' && (p.originalName === name || p.name === name)
@@ -291,7 +309,7 @@ function getRequestBody(
291
309
  let reqBody;
292
310
  if ('$ref' in rawReqBody) {
293
311
  const refName = rawReqBody.$ref.replace('#/components/requestBodies/', '');
294
- const resolved = _optionalChain([components, 'optionalAccess', _8 => _8.requestBodies, 'optionalAccess', _9 => _9[refName]]);
312
+ const resolved = _optionalChain([components, 'optionalAccess', _9 => _9.requestBodies, 'optionalAccess', _10 => _10[refName]]);
295
313
  if (!resolved || '$ref' in resolved) {
296
314
  console.error(`RequestBody $ref '${rawReqBody.$ref}' not found in components/requestBodies`);
297
315
  return null;
@@ -317,3 +335,19 @@ function getRequestBody(
317
335
 
318
336
  return null;
319
337
  }
338
+
339
+ function upsertFixedHeader(headers, headerName, value) {
340
+ const headerIndex = headers.findIndex(
341
+ (header) => header.originalName.toLowerCase() === headerName.toLowerCase()
342
+ );
343
+
344
+ if (headerIndex >= 0) {
345
+ headers[headerIndex].value = value;
346
+ return;
347
+ }
348
+
349
+ headers.push({
350
+ originalName: headerName,
351
+ value,
352
+ });
353
+ }
@@ -61,6 +61,7 @@ function renderSchema(
61
61
  }
62
62
 
63
63
  const result = [];
64
+ const schemaContext = `components.schemas.${safeName}`;
64
65
  if (_nullishCoalesce(schema.description, () => ( schema.title))) {
65
66
  result.push(_jsDocs.renderComment.call(void 0, _nullishCoalesce(schema.description, () => ( schema.title))));
66
67
  }
@@ -83,8 +84,8 @@ function renderSchema(
83
84
  if ('allOf' in schema) {
84
85
  const types = _swagger.getRefCompositeTypes.call(void 0, schema);
85
86
  const mergedSchema = getMergedCompositeObjects(schema);
86
- const objectType = _swagger.getTypeFromSchema.call(void 0, mergedSchema, options);
87
- const objectContents = generateObjectTypeContents(mergedSchema, options);
87
+ const objectType = _swagger.getTypeFromSchema.call(void 0, mergedSchema, options, `${schemaContext}.allOf`);
88
+ const objectContents = generateObjectTypeContents(mergedSchema, options, schemaContext);
88
89
  const hasAdditionalProperties = !!mergedSchema.additionalProperties;
89
90
 
90
91
  if (hasAdditionalProperties) {
@@ -103,7 +104,7 @@ function renderSchema(
103
104
  result.push(`export interface ${safeName} ${extensions}{`);
104
105
  result.push(objectContents);
105
106
  } else if ('oneOf' in schema || 'anyOf' in schema) {
106
- const typeDefinition = _swagger.getTypeFromSchema.call(void 0, schema, options);
107
+ const typeDefinition = _swagger.getTypeFromSchema.call(void 0, schema, options, schemaContext);
107
108
  result.push(`export type ${safeName} = ${typeDefinition};`);
108
109
 
109
110
  return `${result.join('\n')}\n`;
@@ -113,10 +114,10 @@ function renderSchema(
113
114
  result.push(`export type ${safeName} = ${generateItemsType(schema.items, options)}[];`);
114
115
  return result.join('\n');
115
116
  } else {
116
- const objectType = _swagger.getTypeFromSchema.call(void 0, schema, options);
117
+ const objectType = _swagger.getTypeFromSchema.call(void 0, schema, options, schemaContext);
117
118
  const hasAdditionalProperties = !!schema.additionalProperties;
118
119
 
119
- const objectContents = generateObjectTypeContents(schema, options);
120
+ const objectContents = generateObjectTypeContents(schema, options, schemaContext);
120
121
  if (hasAdditionalProperties) {
121
122
  result.push(`export type ${safeName} = ${objectType};`);
122
123
  return `${result.join('\n')}\n`;
@@ -138,7 +139,11 @@ function renderSchema(
138
139
  /**
139
140
  * Generates the inline contents of an object type.
140
141
  */
141
- function generateObjectTypeContents(schema, options) {
142
+ function generateObjectTypeContents(
143
+ schema,
144
+ options,
145
+ schemaContext = 'components.schemas.unknown'
146
+ ) {
142
147
  const result = [];
143
148
  const required = schema.required || [];
144
149
  const props = Object.keys(schema.properties || {});
@@ -146,7 +151,7 @@ function generateObjectTypeContents(schema, options) {
146
151
  for (const prop of props) {
147
152
  const propDefinition = schema.properties[prop];
148
153
  const isRequired = !!~required.indexOf(prop);
149
- result.push(renderTypeProp(prop, propDefinition, isRequired, options));
154
+ result.push(renderTypeProp(prop, propDefinition, isRequired, options, schemaContext));
150
155
  }
151
156
 
152
157
  return result.join('\n');
@@ -233,10 +238,11 @@ function renderTypeProp(
233
238
  propName,
234
239
  definition,
235
240
  required,
236
- options
241
+ options,
242
+ schemaContext
237
243
  ) {
238
244
  const lines = [];
239
- const type = _swagger.getTypeFromSchema.call(void 0, definition, options);
245
+ const type = _swagger.getTypeFromSchema.call(void 0, definition, options, `${schemaContext}.properties.${propName}`);
240
246
 
241
247
  if ('description' in definition || 'title' in definition) {
242
248
  const renderedComment = _jsDocs.renderComment.call(void 0, _nullishCoalesce(definition.description, () => ( definition.title)));
package/dist/index.js CHANGED
@@ -84,6 +84,7 @@ function readFile(filePath) {
84
84
  mode,
85
85
  schemaStyle,
86
86
  enumStyle,
87
+ nullables,
87
88
  template,
88
89
  queryParamsSerialization = {},
89
90
  ...rest
@@ -101,7 +102,7 @@ function readFile(filePath) {
101
102
  ...rest,
102
103
  template: _nullishCoalesce(template, () => ( _swagger.APP_DEFAULTS.template)),
103
104
  servicePrefix: _nullishCoalesce(rest.servicePrefix, () => ( _swagger.APP_DEFAULTS.servicePrefix)),
104
- nullableStrategy: _nullishCoalesce(rest.nullableStrategy, () => ( _swagger.APP_DEFAULTS.nullableStrategy)),
105
+ nullableStrategy: _nullishCoalesce(_nullishCoalesce(nullables, () => ( rest.nullableStrategy)), () => ( _swagger.APP_DEFAULTS.nullableStrategy)),
105
106
  generationMode: _nullishCoalesce(_nullishCoalesce(mode, () => ( rest.generationMode)), () => ( _swagger.APP_DEFAULTS.generationMode)),
106
107
  schemaDeclarationStyle:
107
108
  _nullishCoalesce(_nullishCoalesce(schemaStyle, () => ( rest.schemaDeclarationStyle)), () => ( _swagger.APP_DEFAULTS.schemaDeclarationStyle)),
@@ -16,7 +16,8 @@ var _utils = require('../utils');
16
16
  */
17
17
  function getParameterType(
18
18
  param,
19
- options
19
+ options,
20
+ context = 'schema'
20
21
  ) {
21
22
  const unknownType = options.preferAny ? 'any' : 'unknown';
22
23
 
@@ -24,7 +25,7 @@ var _utils = require('../utils');
24
25
  return unknownType;
25
26
  }
26
27
 
27
- return getTypeFromSchemaResolved(param.schema, options);
28
+ return getTypeFromSchemaResolved(param.schema, options, `${context}.schema`);
28
29
  } exports.getParameterType = getParameterType;
29
30
 
30
31
  /**
@@ -35,9 +36,10 @@ var _utils = require('../utils');
35
36
  */
36
37
  function getTypeFromSchema(
37
38
  schema,
38
- options
39
+ options,
40
+ context = 'schema'
39
41
  ) {
40
- return getTypeFromSchemaResolved(schema, options);
42
+ return getTypeFromSchemaResolved(schema, options, context);
41
43
  } exports.getTypeFromSchema = getTypeFromSchema;
42
44
 
43
45
  /**
@@ -46,7 +48,8 @@ var _utils = require('../utils');
46
48
  */
47
49
  function getTypeFromSchemaResolved(
48
50
  schema,
49
- options
51
+ options,
52
+ context
50
53
  ) {
51
54
  const unknownType = options.preferAny ? 'any' : 'unknown';
52
55
 
@@ -54,6 +57,17 @@ function getTypeFromSchemaResolved(
54
57
  return unknownType;
55
58
  }
56
59
 
60
+ if (typeof schema !== 'object' || Array.isArray(schema)) {
61
+ if (context.endsWith('.properties.$ref') && typeof schema === 'string') {
62
+ throw new Error(
63
+ `Invalid schema at ${context}: string value under properties.$ref is not a valid schema. ` +
64
+ 'Did you mean to use schema.$ref at the root, or wrap it in a named property (for example properties.data.$ref)?'
65
+ );
66
+ }
67
+
68
+ throw new Error(`Invalid schema at ${context}: expected an object schema, got ${typeof schema}.`);
69
+ }
70
+
57
71
  if ('$ref' in schema) {
58
72
  const refName = schema.$ref.split('/').pop();
59
73
  return getSafeIdentifier(refName) || unknownType;
@@ -65,13 +79,13 @@ function getTypeFromSchemaResolved(
65
79
 
66
80
  // OpenAPI 3.1 nullable: type is an array containing 'null', e.g. ["string", "null"]
67
81
  if (Array.isArray(schema.type)) {
68
- return getTypeFromOA31ArrayType(schema , options);
82
+ return getTypeFromOA31ArrayType(schema , options, context);
69
83
  }
70
84
 
71
85
  // OpenAPI 3.0 nullable: nullable: true
72
86
  const isNullable = 'nullable' in schema && schema.nullable === true;
73
87
  const isNullableSuffix = isNullable && options.nullableStrategy === 'include' ? ' | null' : '';
74
- const type = getTypeFromSchemaInternal(schema, options);
88
+ const type = getTypeFromSchemaInternal(schema, options, context);
75
89
 
76
90
  if (isNullableSuffix && type.endsWith('| null')) {
77
91
  return type;
@@ -84,7 +98,11 @@ function getTypeFromSchemaResolved(
84
98
  * The presence of `"null"` in the array is the OA3.1 way of marking a field as nullable.
85
99
  * Respects `nullableStrategy` the same way as OA3.0 `nullable: true`.
86
100
  */
87
- function getTypeFromOA31ArrayType(schema, options) {
101
+ function getTypeFromOA31ArrayType(
102
+ schema,
103
+ options,
104
+ context
105
+ ) {
88
106
  const unknownType = options.preferAny ? 'any' : 'unknown';
89
107
  const types = schema.type ;
90
108
  const isNullable = types.includes('null');
@@ -97,11 +115,17 @@ function getTypeFromOA31ArrayType(schema, options) {
97
115
  } else if (nonNullTypes.length === 1) {
98
116
  // Synthesize a single-type schema to reuse existing resolution logic
99
117
  const singleTypeSchema = { ...schema, type: nonNullTypes[0] } ;
100
- baseType = getTypeFromSchemaInternal(singleTypeSchema, options);
118
+ baseType = getTypeFromSchemaInternal(singleTypeSchema, options, context);
101
119
  } else {
102
120
  // Multiple non-null types — resolve each independently and join as a union
103
121
  baseType = nonNullTypes
104
- .map((t) => getTypeFromSchemaInternal({ ...schema, type: t } , options))
122
+ .map((t) =>
123
+ getTypeFromSchemaInternal(
124
+ { ...schema, type: t } ,
125
+ options,
126
+ `${context}.type`
127
+ )
128
+ )
105
129
  .join(' | ');
106
130
  }
107
131
 
@@ -129,22 +153,23 @@ function getTypeFromOA31ArrayType(schema, options) {
129
153
 
130
154
  function getTypeFromSchemaInternal(
131
155
  schema,
132
- options
156
+ options,
157
+ context
133
158
  ) {
134
159
  const unknownType = options.preferAny ? 'any' : 'unknown';
135
160
 
136
161
  if ('allOf' in schema || 'oneOf' in schema || 'anyOf' in schema) {
137
- return getTypeFromComposites(schema , options);
162
+ return getTypeFromComposites(schema , options, context);
138
163
  }
139
164
 
140
165
  if (schema.type === 'array') {
141
166
  if (schema.items) {
142
- return `${getNestedTypeFromSchema(schema.items, options)}[]`;
167
+ return `${getNestedTypeFromSchema(schema.items, options, `${context}.items`)}[]`;
143
168
  }
144
169
  return `${unknownType}[]`;
145
170
  }
146
171
  if (schema.type === 'object') {
147
- return getTypeFromObject(schema, options);
172
+ return getTypeFromObject(schema, options, undefined, context);
148
173
  }
149
174
  if ('enum' in schema) {
150
175
  return `${schema.enum.map((v) => JSON.stringify(v)).join(' | ')}`;
@@ -166,7 +191,8 @@ function getTypeFromSchemaInternal(
166
191
 
167
192
  function getNestedTypeFromSchema(
168
193
  schema,
169
- options
194
+ options,
195
+ context
170
196
  ) {
171
197
  // OA3.0 nullable: true
172
198
  const isOA30NullableAndActive =
@@ -180,10 +206,10 @@ function getNestedTypeFromSchema(
180
206
  options.nullableStrategy === 'include';
181
207
 
182
208
  if (isOA30NullableAndActive || isOA31NullableAndActive || ('enum' in schema && schema.enum)) {
183
- return `(${getTypeFromSchemaResolved(schema, options)})`;
209
+ return `(${getTypeFromSchemaResolved(schema, options, context)})`;
184
210
  }
185
211
 
186
- return getTypeFromSchemaResolved(schema, options);
212
+ return getTypeFromSchemaResolved(schema, options, context);
187
213
  }
188
214
 
189
215
  /**
@@ -193,7 +219,8 @@ function getNestedTypeFromSchema(
193
219
  function getTypeFromObject(
194
220
  schema,
195
221
  options,
196
- requiredOverride
222
+ requiredOverride,
223
+ context = 'schema'
197
224
  ) {
198
225
  const unknownType = options.preferAny ? 'any' : 'unknown';
199
226
  let objectWithNamedPropsType = '';
@@ -209,7 +236,11 @@ function getTypeFromObject(
209
236
  const isRequired = required.includes(prop);
210
237
  const safePropName = _utils.escapePropName.call(void 0, prop);
211
238
  result.push(
212
- `${safePropName}${isRequired ? '' : '?'}: ${getTypeFromSchemaResolved(propDefinition, options)};`
239
+ `${safePropName}${isRequired ? '' : '?'}: ${getTypeFromSchemaResolved(
240
+ propDefinition,
241
+ options,
242
+ `${context}.properties.${prop}`
243
+ )};`
213
244
  );
214
245
  }
215
246
 
@@ -219,7 +250,9 @@ function getTypeFromObject(
219
250
  if (schema.additionalProperties) {
220
251
  const extraProps = schema.additionalProperties;
221
252
  objectWithIndexSignatureType = `{ [key: string]: ${
222
- extraProps === true ? 'any' : getTypeFromSchemaResolved(extraProps, options)
253
+ extraProps === true
254
+ ? 'any'
255
+ : getTypeFromSchemaResolved(extraProps, options, `${context}.additionalProperties`)
223
256
  } }`;
224
257
  }
225
258
 
@@ -241,7 +274,11 @@ function getTypeFromObject(
241
274
  /**
242
275
  * Simplified way of extracting correct type from `anyOf`, `oneOf` or `allOf` schema.
243
276
  */
244
- function getTypeFromComposites(schema, options) {
277
+ function getTypeFromComposites(
278
+ schema,
279
+ options,
280
+ context = 'schema'
281
+ ) {
245
282
  const composite = schema.allOf || schema.oneOf || schema.anyOf;
246
283
 
247
284
  if (!composite) {
@@ -253,7 +290,7 @@ function getTypeFromComposites(schema, options) {
253
290
 
254
291
  if (isUnionComposite && hasParentObjectShape) {
255
292
  const fallbackType = options.preferAny ? 'any' : 'unknown';
256
- const parentObjectType = getTypeFromObject(schema, options);
293
+ const parentObjectType = getTypeFromObject(schema, options, undefined, context);
257
294
  const parentRequired = schema.required || [];
258
295
  const parentPropSet = new Set(Object.keys(schema.properties || {}));
259
296
 
@@ -271,10 +308,15 @@ function getTypeFromComposites(schema, options) {
271
308
  return isKnown;
272
309
  });
273
310
 
274
- return getTypeFromObject(schema, options, Array.from(new Set([...parentRequired, ...validRequired])));
311
+ return getTypeFromObject(
312
+ schema,
313
+ options,
314
+ Array.from(new Set([...parentRequired, ...validRequired])),
315
+ context
316
+ );
275
317
  }
276
318
 
277
- const subType = getTypeFromSchemaResolved(subSchema, options);
319
+ const subType = getTypeFromSchemaResolved(subSchema, options, `${context}.composite`);
278
320
  if (subType === fallbackType) {
279
321
  return parentObjectType;
280
322
  }
@@ -285,7 +327,7 @@ function getTypeFromComposites(schema, options) {
285
327
  }
286
328
 
287
329
  return composite
288
- .map((s) => getTypeFromSchemaResolved(s, options))
330
+ .map((s, index) => getTypeFromSchemaResolved(s, options, `${context}.composite[${index}]`))
289
331
  .join(schema.allOf ? ' & ' : ' | ');
290
332
  }
291
333
 
package/dist/types.d.ts CHANGED
@@ -49,6 +49,7 @@ export interface CliOptions extends FullAppOptions {
49
49
  mode?: GenerationMode;
50
50
  schemaStyle?: SchemaDeclarationStyle;
51
51
  enumStyle?: EnumDeclarationStyle;
52
+ nullables?: NullableStrategy;
52
53
  }
53
54
  export interface FullAppOptions extends ClientOptions {
54
55
  /** Path to the configuration file that contains actual config to be used */
@@ -240,12 +240,11 @@ function resolveResponseRef(
240
240
  const sortByKey = (key) => (a, b) => (a[key] > b[key] ? 1 : b[key] > a[key] ? -1 : 0);
241
241
 
242
242
  const orderedContentTypes = [
243
- 'application/json',
244
- 'text/json',
245
243
  'text/plain',
246
244
  'application/x-www-form-urlencoded',
247
245
  'multipart/form-data',
248
246
  ];
247
+ const preferredJsonContentTypes = ['application/json', 'text/json'];
249
248
  function getBestContentType(
250
249
  reqBody
251
250
  ) {
@@ -254,6 +253,19 @@ const orderedContentTypes = [
254
253
  return [null, null];
255
254
  }
256
255
 
256
+ const preferredJsonContentType = preferredJsonContentTypes.find((ct) => contentTypes.includes(ct));
257
+ if (preferredJsonContentType) {
258
+ const typeObject = reqBody.content[preferredJsonContentType];
259
+ const type = getContentType(preferredJsonContentType);
260
+ return [typeObject, type];
261
+ }
262
+
263
+ const jsonLikeContentType = contentTypes.find(isJsonLikeContentType);
264
+ if (jsonLikeContentType) {
265
+ const typeObject = reqBody.content[jsonLikeContentType];
266
+ return [typeObject, 'json'];
267
+ }
268
+
257
269
  const firstContentType = orderedContentTypes.find((ct) => contentTypes.includes(ct));
258
270
  if (firstContentType) {
259
271
  const typeObject = reqBody.content[firstContentType];
@@ -266,6 +278,11 @@ const orderedContentTypes = [
266
278
  return [typeObject, type];
267
279
  } exports.getBestContentType = getBestContentType;
268
280
 
281
+ function isJsonLikeContentType(contentType) {
282
+ const normalized = contentType.split(';')[0].trim().toLowerCase();
283
+ return normalized === 'application/*+json' || /^application\/.+\+json$/.test(normalized);
284
+ }
285
+
269
286
  function getContentType(type) {
270
287
  if (type === 'application/x-www-form-urlencoded') {
271
288
  return 'urlencoded';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "swaggie",
3
- "version": "1.8.1",
3
+ "version": "1.8.3",
4
4
  "description": "Generate a fully typed TypeScript API client from your OpenAPI 3 spec",
5
5
  "author": {
6
6
  "name": "Piotr Dabrowski",
@@ -11,6 +11,21 @@ $config?: RequestInit
11
11
  '<%= parameter.originalName %>': <%= parameter.name %>,
12
12
  <% }); %>})}<% } %>`;
13
13
 
14
+ <% if(it.headers && it.headers.length > 0) { %>
15
+ const { headers: $configHeaders, ...$configRest } = $config ?? {};
16
+ const headers = new Headers({
17
+ <% it.headers.forEach((parameter) => { %>
18
+ <% if (parameter.value) { %>
19
+ '<%= parameter.originalName %>': '<%= parameter.value %>',
20
+ <% } else { %>
21
+ '<%= parameter.originalName %>': <%= parameter.name %> ?? '',
22
+ <% } %>
23
+ <% }); %>
24
+ });
25
+ if ($configHeaders) {
26
+ new Headers($configHeaders).forEach((value, key) => headers.set(key, value));
27
+ }
28
+
14
29
  return fetch(url, {
15
30
  method: '<%= it.method %>',
16
31
  <% if(it.body) { %>
@@ -22,19 +37,24 @@ $config?: RequestInit
22
37
  body: <%= it.body.name %>,
23
38
  <% } %>
24
39
  <% } %>
25
- <% if(it.headers && it.headers.length > 0) { %>
26
- headers: {
27
- <% it.headers.forEach((parameter) => { %>
28
- <% if (parameter.value) { %>
29
- '<%= parameter.originalName %>': '<%= parameter.value %>',
30
- <% } else { %>
31
- '<%= parameter.originalName %>': <%= parameter.name %> ?? '',
32
- <% } %>
33
- <% }); %>
34
- },
40
+ headers,
41
+ ...$configRest,
42
+ })
43
+ <% } else { %>
44
+ return fetch(url, {
45
+ method: '<%= it.method %>',
46
+ <% if(it.body) { %>
47
+ <% if(it.body.contentType === 'json') { %>
48
+ body: JSON.stringify(<%= it.body.name %>),
49
+ <% } else if(it.body.contentType === 'urlencoded') { %>
50
+ body: new URLSearchParams(<%= it.body.name %> as any),
51
+ <% } else { %>
52
+ body: <%= it.body.name %>,
53
+ <% } %>
35
54
  <% } %>
36
55
  ...$config,
37
56
  })
57
+ <% } %>
38
58
  <% if(it.responseContentType === 'binary') { %>
39
59
  .then((response) => response.blob() as Promise<<%~ it.returnType %>>);
40
60
  <% } else if(it.responseContentType === 'text') { %>