swaggie 1.5.3-beta.2 → 1.5.3-beta.4

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
@@ -313,14 +313,14 @@ function error(e) {
313
313
 
314
314
  | Supported | Not supported |
315
315
  | ------------------------------------------------------------------------------ | ----------------------------------------------- |
316
- | OpenAPI 3 | Swagger, Open API 2.0 |
316
+ | OpenAPI 3, OpenAPI 3.1, OpenAPI 3.2 | Swagger, Open API 2.0 |
317
317
  | `allOf`, `oneOf`, `anyOf`, `$ref` to schemas | `not` |
318
- | Spec formats: `JSON`, `YAML` | Very complex query params |
318
+ | Spec formats: `JSON`, `YAML` | VERY complex query params |
319
319
  | Extensions: `x-position`, `x-name`, `x-enumNames`, `x-enum-varnames` | Multiple response types (only one will be used) |
320
320
  | Content types: `JSON`, `text`, `multipart/form-data` | Multiple request types (only one will be used) |
321
- | Content types: `application/x-www-form-urlencoded`, `application/octet-stream` | References to other spec files |
322
- | Different types of enum definitions (+ OpenAPI 3.1 support for enums) | OpenAPI callbacks |
323
- | 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 |
324
324
  | Getting documents from remote locations or as path reference (local file) | |
325
325
  | Grouping endpoints by tags + handle gracefully duplicate operation ids | |
326
326
 
@@ -15,13 +15,13 @@ var _utils = require('../utils');
15
15
  }
16
16
 
17
17
  const viewData = {
18
- servicePrefix: clientOptions.servicePrefix || '',
18
+ servicePrefix: clientOptions.servicePrefix,
19
19
  clients: files
20
20
  .filter((c) => c)
21
21
  .map((c) => ({
22
- fileName: (clientOptions.servicePrefix || '') + c,
23
- className: `${(clientOptions.servicePrefix || '') + c}Client`,
24
- camelCaseName: _case.camel.call(void 0, `${(clientOptions.servicePrefix || '') + c}Client`),
22
+ fileName: clientOptions.servicePrefix + c,
23
+ className: `${clientOptions.servicePrefix + c}Client`,
24
+ camelCaseName: _case.camel.call(void 0, `${clientOptions.servicePrefix + c}Client`),
25
25
  })),
26
26
  };
27
27
 
@@ -25,7 +25,7 @@ var _jsDocs = require('./jsDocs');
25
25
  ) {
26
26
  const operations = _swagger.getOperations.call(void 0, spec);
27
27
  const groups = _utils.groupOperationsByGroupName.call(void 0, operations);
28
- const servicePrefix = _nullishCoalesce(options.servicePrefix, () => ( ''));
28
+ const servicePrefix = options.servicePrefix;
29
29
  let result = _utils.renderFile.call(void 0, 'baseClient.ejs', {
30
30
  servicePrefix,
31
31
  baseUrl: options.baseUrl,
@@ -84,10 +84,7 @@ function prepareClient(
84
84
  * @param options
85
85
  * @returns List of operations prepared for client generation
86
86
  */
87
- function prepareOperations(
88
- operations,
89
- options
90
- ) {
87
+ function prepareOperations(operations, options) {
91
88
  let ops = fixDuplicateOperations(operations);
92
89
 
93
90
  if (options.skipDeprecated) {
@@ -201,12 +201,7 @@ function renderTypeProp(
201
201
  lines.push(_jsDocs.renderComment.call(void 0, _nullishCoalesce(definition.description, () => ( definition.title))));
202
202
  }
203
203
 
204
- // When nullableAsOptional strategy is set, nullable properties are treated as optional
205
- const isNullableAsOptional =
206
- options.nullableStrategy === 'nullableAsOptional' &&
207
- !('$ref' in definition) &&
208
- (definition ).nullable === true;
209
- const isOptional = !required || isNullableAsOptional;
204
+ const isOptional = !required || isNullableAsOptional(definition, options);
210
205
  const optionalMark = isOptional ? '?' : '';
211
206
  // If prop name is not a valid identifier, we need to wrap it in quotes.
212
207
  // We can't use getSafeIdentifier here because it will affect the data model.
@@ -216,6 +211,26 @@ function renderTypeProp(
216
211
  return lines.join('\n');
217
212
  }
218
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
+
219
234
  function getMergedCompositeObjects(schema) {
220
235
  const { allOf, oneOf, anyOf, ...safeSchema } = schema;
221
236
  const composite = allOf || oneOf || anyOf || [];
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@
3
3
 
4
4
  var _gen = require('./gen'); var _gen2 = _interopRequireDefault(_gen);
5
5
 
6
+ var _types = require('./types');
6
7
  var _utils = require('./utils');
7
8
 
8
9
  /**
@@ -33,7 +34,7 @@ function verifyOptions(options) {
33
34
  }
34
35
 
35
36
  function gen(spec, options) {
36
- _utils.loadAllTemplateFiles.call(void 0, options.template || 'axios');
37
+ _utils.loadAllTemplateFiles.call(void 0, options.template);
37
38
 
38
39
  return _gen2.default.call(void 0, spec, options);
39
40
  }
@@ -68,20 +69,16 @@ function readFile(filePath) {
68
69
 
69
70
 
70
71
 
71
- const defaultQueryParamsConfig = {
72
- allowDots: true,
73
- arrayFormat: 'repeat' ,
74
- };
75
-
76
72
  /**
77
73
  * CLI options are flat, but within the app we use nested objects.
78
74
  * This function converts flat options structure to the nested one and
79
- * merges it with the default values.
80
- * */
81
- function prepareAppOptions(cliOpts) {
75
+ * merges it with the default values, producing a fully-initialized AppOptions
76
+ * object where every defaultable field is guaranteed to be present.
77
+ */
78
+ function prepareAppOptions(cliOpts) {
82
79
  const { allowDots, arrayFormat, template, queryParamsSerialization = {}, ...rest } = cliOpts;
83
80
  const mergedQueryParamsSerialization = {
84
- ...defaultQueryParamsConfig,
81
+ ..._types.APP_DEFAULTS.queryParamsSerialization,
85
82
  ...Object.fromEntries(
86
83
  Object.entries(queryParamsSerialization).filter(([_, v]) => v !== undefined)
87
84
  ),
@@ -91,7 +88,9 @@ function prepareAppOptions(cliOpts) {
91
88
 
92
89
  return {
93
90
  ...rest,
91
+ template: _nullishCoalesce(template, () => ( _types.APP_DEFAULTS.template)),
92
+ servicePrefix: _nullishCoalesce(rest.servicePrefix, () => ( _types.APP_DEFAULTS.servicePrefix)),
93
+ nullableStrategy: _nullishCoalesce(rest.nullableStrategy, () => ( _types.APP_DEFAULTS.nullableStrategy)),
94
94
  queryParamsSerialization: mergedQueryParamsSerialization,
95
- template: _nullishCoalesce(template, () => ( 'axios')),
96
95
  };
97
- }
96
+ } exports.prepareAppOptions = prepareAppOptions;
@@ -1,4 +1,4 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
2
 
3
3
  var _utils = require('../utils');
4
4
 
@@ -24,7 +24,7 @@ var _utils = require('../utils');
24
24
  return unknownType;
25
25
  }
26
26
 
27
- return getTypeFromSchema(param.schema, options);
27
+ return getTypeFromSchemaResolved(param.schema, options);
28
28
  } exports.getParameterType = getParameterType;
29
29
 
30
30
  /**
@@ -36,6 +36,17 @@ var _utils = require('../utils');
36
36
  function getTypeFromSchema(
37
37
  schema,
38
38
  options
39
+ ) {
40
+ return getTypeFromSchemaResolved(schema, options);
41
+ } exports.getTypeFromSchema = getTypeFromSchema;
42
+
43
+ /**
44
+ * Internal implementation of getTypeFromSchema that operates on fully-resolved AppOptions.
45
+ * All private functions in this module call this version directly to avoid redundant resolution.
46
+ */
47
+ function getTypeFromSchemaResolved(
48
+ schema,
49
+ options
39
50
  ) {
40
51
  const unknownType = options.preferAny ? 'any' : 'unknown';
41
52
 
@@ -52,16 +63,69 @@ var _utils = require('../utils');
52
63
  return 'null';
53
64
  }
54
65
 
66
+ // OpenAPI 3.1 nullable: type is an array containing 'null', e.g. ["string", "null"]
67
+ if (Array.isArray(schema.type)) {
68
+ return getTypeFromOA31ArrayType(schema , options);
69
+ }
70
+
71
+ // OpenAPI 3.0 nullable: nullable: true
55
72
  const isNullable = 'nullable' in schema && schema.nullable === true;
56
- const strategy = _nullishCoalesce(options.nullableStrategy, () => ( 'ignore'));
57
- const isNullableSuffix = isNullable && strategy === 'include' ? ' | null' : '';
73
+ const isNullableSuffix = isNullable && options.nullableStrategy === 'include' ? ' | null' : '';
58
74
  const type = getTypeFromSchemaInternal(schema, options);
59
75
 
60
76
  if (isNullableSuffix && type.endsWith('| null')) {
61
77
  return type;
62
78
  }
63
79
  return type + isNullableSuffix;
64
- } exports.getTypeFromSchema = getTypeFromSchema;
80
+ }
81
+
82
+ /**
83
+ * Handles OpenAPI 3.1 schemas where `type` is an array (e.g. `["string", "null"]`).
84
+ * The presence of `"null"` in the array is the OA3.1 way of marking a field as nullable.
85
+ * Respects `nullableStrategy` the same way as OA3.0 `nullable: true`.
86
+ */
87
+ function getTypeFromOA31ArrayType(schema, options) {
88
+ const unknownType = options.preferAny ? 'any' : 'unknown';
89
+ const types = schema.type ;
90
+ const isNullable = types.includes('null');
91
+ const nonNullTypes = types.filter((t) => t !== 'null');
92
+
93
+ // Build the base type from the non-null types
94
+ let baseType;
95
+ if (nonNullTypes.length === 0) {
96
+ baseType = 'null';
97
+ } else if (nonNullTypes.length === 1) {
98
+ // Synthesize a single-type schema to reuse existing resolution logic
99
+ const singleTypeSchema = { ...schema, type: nonNullTypes[0] } ;
100
+ baseType = getTypeFromSchemaInternal(singleTypeSchema, options);
101
+ } else {
102
+ // Multiple non-null types — resolve each independently and join as a union
103
+ baseType = nonNullTypes
104
+ .map((t) => getTypeFromSchemaInternal({ ...schema, type: t } , options))
105
+ .join(' | ');
106
+ }
107
+
108
+ if (!isNullable) {
109
+ return baseType || unknownType;
110
+ }
111
+
112
+ // All types were 'null' — just return 'null' regardless of strategy
113
+ if (nonNullTypes.length === 0) {
114
+ return 'null';
115
+ }
116
+
117
+ if (options.nullableStrategy === 'include') {
118
+ // We don't want multiple nulls in the type string
119
+ if (baseType.endsWith('| null')) {
120
+ return baseType;
121
+ }
122
+ return `${baseType} | null`;
123
+ }
124
+
125
+ // 'ignore' and 'nullableAsOptional' — null is stripped from the type itself
126
+ // (for nullableAsOptional, the optionality is applied at the property level in genTypes.ts)
127
+ return baseType || unknownType;
128
+ }
65
129
 
66
130
  function getTypeFromSchemaInternal(
67
131
  schema,
@@ -104,13 +168,22 @@ function getNestedTypeFromSchema(
104
168
  schema,
105
169
  options
106
170
  ) {
107
- const strategy = _nullishCoalesce(options.nullableStrategy, () => ( 'ignore'));
108
- const isNullableAndActive =
109
- 'nullable' in schema && schema.nullable === true && strategy === 'include';
110
- if (isNullableAndActive || ('enum' in schema && schema.enum)) {
111
- return `(${getTypeFromSchema(schema, options)})`;
171
+ // OA3.0 nullable: true
172
+ const isOA30NullableAndActive =
173
+ 'nullable' in schema && schema.nullable === true && options.nullableStrategy === 'include';
174
+
175
+ // OA3.1 nullable: type array containing 'null'
176
+ const isOA31NullableAndActive =
177
+ 'type' in schema &&
178
+ Array.isArray(schema.type) &&
179
+ schema.type.includes('null') &&
180
+ options.nullableStrategy === 'include';
181
+
182
+ if (isOA30NullableAndActive || isOA31NullableAndActive || ('enum' in schema && schema.enum)) {
183
+ return `(${getTypeFromSchemaResolved(schema, options)})`;
112
184
  }
113
- return getTypeFromSchema(schema, options);
185
+
186
+ return getTypeFromSchemaResolved(schema, options);
114
187
  }
115
188
 
116
189
  /**
@@ -126,7 +199,7 @@ function getTypeFromObject(
126
199
  if (schema.additionalProperties) {
127
200
  const extraProps = schema.additionalProperties;
128
201
  return `{ [key: string]: ${
129
- extraProps === true ? 'any' : getTypeFromSchema(extraProps, options)
202
+ extraProps === true ? 'any' : getTypeFromSchemaResolved(extraProps, options)
130
203
  } }`;
131
204
  }
132
205
 
@@ -140,7 +213,7 @@ function getTypeFromObject(
140
213
  const isRequired = required.includes(prop);
141
214
  const safePropName = _utils.escapePropName.call(void 0, prop);
142
215
  result.push(
143
- `${safePropName}${isRequired ? '' : '?'}: ${getTypeFromSchema(propDefinition, options)};`
216
+ `${safePropName}${isRequired ? '' : '?'}: ${getTypeFromSchemaResolved(propDefinition, options)};`
144
217
  );
145
218
  }
146
219
 
@@ -156,7 +229,9 @@ function getTypeFromObject(
156
229
  function getTypeFromComposites(schema, options) {
157
230
  const composite = schema.allOf || schema.oneOf || schema.anyOf;
158
231
 
159
- return composite.map((s) => getTypeFromSchema(s, options)).join(schema.allOf ? ' & ' : ' | ');
232
+ return composite
233
+ .map((s) => getTypeFromSchemaResolved(s, options))
234
+ .join(schema.allOf ? ' & ' : ' | ');
160
235
  }
161
236
 
162
237
  /**
package/dist/types.d.ts CHANGED
@@ -50,6 +50,27 @@ export type HttpMethod = 'get' | 'put' | 'post' | 'delete' | 'options' | 'head'
50
50
  export type DateSupport = 'string' | 'Date';
51
51
  export type ArrayFormat = 'indices' | 'repeat' | 'brackets';
52
52
  export type NullableStrategy = 'include' | 'nullableAsOptional' | 'ignore';
53
+ /**
54
+ * Internal options type used throughout the app after `prepareAppOptions` has run.
55
+ * All fields that have defaults are required here so the rest of the codebase never
56
+ * needs to perform its own `?? fallback` logic.
57
+ */
58
+ export interface AppOptions extends ClientOptions {
59
+ template: Template;
60
+ servicePrefix: string;
61
+ nullableStrategy: NullableStrategy;
62
+ queryParamsSerialization: {
63
+ allowDots: boolean;
64
+ arrayFormat: ArrayFormat;
65
+ };
66
+ }
67
+ /** Default values applied to every field of AppOptions that has a default. */
68
+ export declare const APP_DEFAULTS: Partial<AppOptions>;
69
+ /**
70
+ * Fills in all AppOptions defaults for a partial ClientOptions object.
71
+ * Used at the boundary between public API / test helpers and the internal pipeline.
72
+ */
73
+ export declare function resolveOptions(opts: Partial<ClientOptions>): AppOptions;
53
74
  /**
54
75
  * Local type that represent Operation as understood by Swaggie
55
76
  **/
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "swaggie",
3
- "version": "1.5.3-beta.2",
3
+ "version": "1.5.3-beta.4",
4
4
  "description": "Generate TypeScript REST client code from an OpenAPI spec",
5
5
  "author": {
6
6
  "name": "Piotr Dabrowski",