swaggie 1.5.3-beta.1 → 1.5.3-beta.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
@@ -103,6 +103,7 @@ Sample configuration looks like this:
103
103
  "preferAny": true,
104
104
  "servicePrefix": "",
105
105
  "dateFormat": "Date", // "string" | "Date"
106
+ "nullableStrategy": "ignore", // "ignore" | "include" | "nullableAsOptional"
106
107
  "queryParamsSerialization": {
107
108
  "arrayFormat": "repeat", // "repeat" | "brackets" | "indices"
108
109
  "allowDots": true
@@ -162,6 +163,24 @@ Once you know what your backend expects, you can adjust the configuration file a
162
163
  }
163
164
  ```
164
165
 
166
+ ### Nullable Strategy
167
+
168
+ OpenAPI 3.0 allows marking fields as `nullable: true`. Swaggie provides three strategies for translating this into TypeScript, controlled by the `nullableStrategy` option:
169
+
170
+ | Value | Description |
171
+ | ---------------------- | ---------------------------------------------------------------------------------- |
172
+ | `"ignore"` (default) | `nullable: true` is ignored. Types are generated as if `nullable` was not set. |
173
+ | `"include"` | `nullable: true` appends `\| null` to the TypeScript type (e.g. `string \| null`). |
174
+ | `"nullableAsOptional"` | `nullable: true` makes the property optional (`?`) instead of adding `\| null`. |
175
+
176
+ **Examples** for a schema with `tenant: { type: 'string', nullable: true }` (required):
177
+
178
+ ```typescript
179
+ // nullableStrategy: "ignore" → tenant: string;
180
+ // nullableStrategy: "include" → tenant: string | null;
181
+ // nullableStrategy: "nullableAsOptional" → tenant?: string;
182
+ ```
183
+
165
184
  ### Code Quality
166
185
 
167
186
  > Please note that it's **recommended** to pipe Swaggie command to some prettifier like `prettier`, `biome` or `dprint` to make the generated code look not only nice, but also persistent.
@@ -294,14 +313,14 @@ function error(e) {
294
313
 
295
314
  | Supported | Not supported |
296
315
  | ------------------------------------------------------------------------------ | ----------------------------------------------- |
297
- | OpenAPI 3 | Swagger, Open API 2.0 |
316
+ | OpenAPI 3, OpenAPI 3.1, OpenAPI 3.2 | Swagger, Open API 2.0 |
298
317
  | `allOf`, `oneOf`, `anyOf`, `$ref` to schemas | `not` |
299
- | Spec formats: `JSON`, `YAML` | Very complex query params |
318
+ | Spec formats: `JSON`, `YAML` | VERY complex query params |
300
319
  | Extensions: `x-position`, `x-name`, `x-enumNames`, `x-enum-varnames` | Multiple response types (only one will be used) |
301
320
  | Content types: `JSON`, `text`, `multipart/form-data` | Multiple request types (only one will be used) |
302
- | Content types: `application/x-www-form-urlencoded`, `application/octet-stream` | References to other spec files |
303
- | Different types of enum definitions (+ OpenAPI 3.1 support for enums) | OpenAPI callbacks |
304
- | Paths inheritance, comments (descriptions), nullable | OpenAPI webhooks |
321
+ | Content types: `application/x-www-form-urlencoded`, `application/octet-stream` | References to external spec files |
322
+ | Different types of enum definitions | OpenAPI callbacks |
323
+ | Paths inheritance, comments (descriptions), nullable, `["<TYPE>", null]` | OpenAPI webhooks |
305
324
  | Getting documents from remote locations or as path reference (local file) | |
306
325
  | Grouping endpoints by tags + handle gracefully duplicate operation ids | |
307
326
 
@@ -98,7 +98,7 @@ function prepareClient(
98
98
  const [respObject, responseContentType] = _utils.getBestResponse.call(void 0, op);
99
99
  const returnType = _swagger.getParameterType.call(void 0, respObject, options);
100
100
 
101
- const body = getRequestBody(op.requestBody);
101
+ const body = getRequestBody(op.requestBody, options);
102
102
  const queryParams = getParams(op.parameters , options, ['query']);
103
103
  const params = getParams(op.parameters , options);
104
104
 
@@ -278,7 +278,10 @@ function prepareUrl(path) {
278
278
  );
279
279
  } exports.getParamName = getParamName;
280
280
 
281
- function getRequestBody(reqBody) {
281
+ function getRequestBody(
282
+ reqBody,
283
+ options
284
+ ) {
282
285
  if (reqBody && 'content' in reqBody) {
283
286
  const [bodyContent, contentType] = _utils.getBestContentType.call(void 0, reqBody);
284
287
  const isFormData = contentType === 'form-data';
@@ -287,7 +290,7 @@ function getRequestBody(reqBody) {
287
290
  return {
288
291
  originalName: _nullishCoalesce(reqBody['x-name'], () => ( 'body')),
289
292
  name: getParamName(_nullishCoalesce(reqBody['x-name'], () => ( 'body'))),
290
- type: isFormData ? 'FormData' : _swagger.getParameterType.call(void 0, bodyContent, {}),
293
+ type: isFormData ? 'FormData' : _swagger.getParameterType.call(void 0, bodyContent, options),
291
294
  optional: !reqBody.required,
292
295
  original: reqBody,
293
296
  contentType,
@@ -200,7 +200,9 @@ function renderTypeProp(
200
200
  if ('description' in definition || 'title' in definition) {
201
201
  lines.push(_jsDocs.renderComment.call(void 0, _nullishCoalesce(definition.description, () => ( definition.title))));
202
202
  }
203
- const optionalMark = required ? '' : '?';
203
+
204
+ const isOptional = !required || isNullableAsOptional(definition, options);
205
+ const optionalMark = isOptional ? '?' : '';
204
206
  // If prop name is not a valid identifier, we need to wrap it in quotes.
205
207
  // We can't use getSafeIdentifier here because it will affect the data model.
206
208
  const safePropName = _utils.escapePropName.call(void 0, propName);
@@ -209,6 +211,26 @@ function renderTypeProp(
209
211
  return lines.join('\n');
210
212
  }
211
213
 
214
+ /**
215
+ * When nullableAsOptional strategy is set, nullable properties are treated as optional.
216
+ * Supports both OA3.0 (nullable: true) and OA3.1 (type: ["string", "null"]).
217
+ * @returns True if the property should be treated as optional, false otherwise.
218
+ */
219
+ function isNullableAsOptional(
220
+ definition,
221
+ options
222
+ ) {
223
+ if ('$ref' in definition) {
224
+ return false;
225
+ }
226
+
227
+ return (
228
+ options.nullableStrategy === 'nullableAsOptional' &&
229
+ (definition.nullable === true ||
230
+ (Array.isArray(definition.type) && definition.type.includes('null')))
231
+ );
232
+ }
233
+
212
234
  function getMergedCompositeObjects(schema) {
213
235
  const { allOf, oneOf, anyOf, ...safeSchema } = schema;
214
236
  const composite = allOf || oneOf || anyOf || [];
@@ -1,4 +1,4 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true});
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }
2
2
 
3
3
  var _utils = require('../utils');
4
4
 
@@ -52,7 +52,15 @@ var _utils = require('../utils');
52
52
  return 'null';
53
53
  }
54
54
 
55
- const isNullableSuffix = 'nullable' in schema && schema.nullable === true ? ' | null' : '';
55
+ // OpenAPI 3.1 nullable: type is an array containing 'null', e.g. ["string", "null"]
56
+ if (Array.isArray(schema.type)) {
57
+ return getTypeFromOA31ArrayType(schema , options);
58
+ }
59
+
60
+ // OpenAPI 3.0 nullable: nullable: true
61
+ const isNullable = 'nullable' in schema && schema.nullable === true;
62
+ const strategy = _nullishCoalesce(options.nullableStrategy, () => ( 'ignore'));
63
+ const isNullableSuffix = isNullable && strategy === 'include' ? ' | null' : '';
56
64
  const type = getTypeFromSchemaInternal(schema, options);
57
65
 
58
66
  if (isNullableSuffix && type.endsWith('| null')) {
@@ -61,6 +69,58 @@ var _utils = require('../utils');
61
69
  return type + isNullableSuffix;
62
70
  } exports.getTypeFromSchema = getTypeFromSchema;
63
71
 
72
+ /**
73
+ * Handles OpenAPI 3.1 schemas where `type` is an array (e.g. `["string", "null"]`).
74
+ * The presence of `"null"` in the array is the OA3.1 way of marking a field as nullable.
75
+ * Respects `nullableStrategy` the same way as OA3.0 `nullable: true`.
76
+ */
77
+ function getTypeFromOA31ArrayType(
78
+ schema,
79
+ options
80
+ ) {
81
+ const unknownType = options.preferAny ? 'any' : 'unknown';
82
+ const types = schema.type ;
83
+ const isNullable = types.includes('null');
84
+ const nonNullTypes = types.filter((t) => t !== 'null');
85
+
86
+ // Build the base type from the non-null types
87
+ let baseType;
88
+ if (nonNullTypes.length === 0) {
89
+ baseType = 'null';
90
+ } else if (nonNullTypes.length === 1) {
91
+ // Synthesize a single-type schema to reuse existing resolution logic
92
+ const singleTypeSchema = { ...schema, type: nonNullTypes[0] } ;
93
+ baseType = getTypeFromSchemaInternal(singleTypeSchema, options);
94
+ } else {
95
+ // Multiple non-null types — resolve each independently and join as a union
96
+ baseType = nonNullTypes
97
+ .map((t) => getTypeFromSchemaInternal({ ...schema, type: t } , options))
98
+ .join(' | ');
99
+ }
100
+
101
+ if (!isNullable) {
102
+ return baseType || unknownType;
103
+ }
104
+
105
+ // All types were 'null' — just return 'null' regardless of strategy
106
+ if (nonNullTypes.length === 0) {
107
+ return 'null';
108
+ }
109
+
110
+ const strategy = _nullishCoalesce(options.nullableStrategy, () => ( 'ignore'));
111
+ if (strategy === 'include') {
112
+ // We don't want multiple nulls in the type string
113
+ if (baseType.endsWith('| null')) {
114
+ return baseType;
115
+ }
116
+ return `${baseType} | null`;
117
+ }
118
+
119
+ // 'ignore' and 'nullableAsOptional' — null is stripped from the type itself
120
+ // (for nullableAsOptional, the optionality is applied at the property level in genTypes.ts)
121
+ return baseType || unknownType;
122
+ }
123
+
64
124
  function getTypeFromSchemaInternal(
65
125
  schema,
66
126
  options
@@ -102,9 +162,23 @@ function getNestedTypeFromSchema(
102
162
  schema,
103
163
  options
104
164
  ) {
105
- if (('nullable' in schema && schema.nullable === true) || ('enum' in schema && schema.enum)) {
165
+ const strategy = _nullishCoalesce(options.nullableStrategy, () => ( 'ignore'));
166
+
167
+ // OA3.0 nullable: true
168
+ const isOA30NullableAndActive =
169
+ 'nullable' in schema && schema.nullable === true && strategy === 'include';
170
+
171
+ // OA3.1 nullable: type array containing 'null'
172
+ const isOA31NullableAndActive =
173
+ 'type' in schema &&
174
+ Array.isArray(schema.type) &&
175
+ schema.type.includes('null') &&
176
+ strategy === 'include';
177
+
178
+ if (isOA30NullableAndActive || isOA31NullableAndActive || ('enum' in schema && schema.enum)) {
106
179
  return `(${getTypeFromSchema(schema, options)})`;
107
180
  }
181
+
108
182
  return getTypeFromSchema(schema, options);
109
183
  }
110
184
 
package/dist/types.d.ts CHANGED
@@ -20,6 +20,13 @@ export interface ClientOptions {
20
20
  servicePrefix?: string;
21
21
  /** How date should be handled. It does not do any special serialization */
22
22
  dateFormat?: DateSupport;
23
+ /**
24
+ * Controls how OpenAPI 'nullable' is translated into TypeScript types. Default: 'ignore'.
25
+ * 'include' - 'nullable: true' appends `| null` to the TypeScript type (e.g. `string | null`).
26
+ * 'nullableAsOptional' - 'nullable: true' makes the property optional (`?`) instead of adding `| null`.
27
+ * 'ignore' - 'nullable: true' is ignored.
28
+ */
29
+ nullableStrategy?: NullableStrategy;
23
30
  /** Options for query parameters serialization */
24
31
  queryParamsSerialization: QueryParamsSerializationOptions;
25
32
  /** Offers ability to adjust the OpenAPI spec before it is processed */
@@ -42,6 +49,7 @@ export type Template = 'axios' | 'fetch' | 'ng1' | 'ng2' | 'swr-axios' | 'xior'
42
49
  export type HttpMethod = 'get' | 'put' | 'post' | 'delete' | 'options' | 'head' | 'patch';
43
50
  export type DateSupport = 'string' | 'Date';
44
51
  export type ArrayFormat = 'indices' | 'repeat' | 'brackets';
52
+ export type NullableStrategy = 'include' | 'nullableAsOptional' | 'ignore';
45
53
  /**
46
54
  * Local type that represent Operation as understood by Swaggie
47
55
  **/
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "swaggie",
3
- "version": "1.5.3-beta.1",
3
+ "version": "1.5.3-beta.3",
4
4
  "description": "Generate TypeScript REST client code from an OpenAPI spec",
5
5
  "author": {
6
6
  "name": "Piotr Dabrowski",