swaggie 1.8.0 → 1.8.1

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
@@ -80,7 +80,8 @@ swaggie -s https://petstore3.swagger.io/api/v3/openapi.json -o ./client/petstore
80
80
  -t, --template <string> Template to use for code generation (default: "axios")
81
81
  -m, --mode <mode> Generation mode: "full" or "schemas" (default: "full")
82
82
  -d, --schemaStyle <style> Schema object style: "interface" or "type" (default: "interface")
83
- --preferAny Use "any" instead of "unknown" for untyped values (default: false)
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)
84
85
  --skipDeprecated Exclude deprecated operations from the output (default: false)
85
86
  --servicePrefix Prefix for service names — useful when generating multiple APIs
86
87
  --allowDots Use dot notation to serialize nested object query params
@@ -123,6 +124,7 @@ swaggie -c swaggie.config.json
123
124
  "nullableStrategy": "ignore",
124
125
  "generationMode": "full",
125
126
  "schemaDeclarationStyle": "interface",
127
+ "enumDeclarationStyle": "union",
126
128
  "queryParamsSerialization": {
127
129
  "arrayFormat": "repeat",
128
130
  "allowDots": true
@@ -279,6 +281,14 @@ Use `schemaDeclarationStyle` (or CLI `--schemaStyle`) to control object schema o
279
281
  | `"interface"`| `export interface Tag { ... }` (default) |
280
282
  | `"type"` | `export type Tag = { ... };` |
281
283
 
284
+ ### Enum Declaration Style
285
+
286
+ Use `enumDeclarationStyle` (or CLI `--enumStyle`) for plain string enums:
287
+ - `"union"` (default): `export type Status = "active" | "disabled";`
288
+ - `"enum"`: `export enum Status { active = "active", disabled = "disabled" }`
289
+
290
+ Note: this applies only to plain string enums. Non-string enums are still emitted as union types.
291
+
282
292
  ### Parameter Modifiers
283
293
 
284
294
  Sometimes an API spec marks a parameter as required, but your client handles it in an interceptor and you don't want it cluttering every method signature. Parameter modifiers let you override this globally without touching the spec.
@@ -360,12 +370,12 @@ Swaggie only needs a JSON or YAML OpenAPI spec file — it does not require a ru
360
370
  | Supported | Not Supported |
361
371
  | ------------------------------------------------------------------------------ | ---------------------------------------------------- |
362
372
  | OpenAPI 3.0, 3.1, 3.2 | Swagger / OpenAPI 2.0 |
363
- | `allOf`, `oneOf`, `anyOf`, `$ref` | `not` keyword |
373
+ | `allOf`, `oneOf`, `anyOf`, `$ref`, external $refs | `not` keyword |
364
374
  | Spec formats: JSON, YAML | Very complex query parameter structures |
365
375
  | Extensions: `x-position`, `x-name`, `x-enumNames`, `x-enum-varnames` | Multiple response types (only the first is used) |
366
376
  | Content types: JSON, plain text, multipart/form-data | Multiple request body types (only the first is used) |
367
- | Content types: `application/x-www-form-urlencoded`, `application/octet-stream` | References to external spec files |
368
- | Various enum definition styles | OpenAPI callbacks and webhooks |
377
+ | Content types: `application/x-www-form-urlencoded`, `application/octet-stream` | OpenAPI callbacks and webhooks |
378
+ | Various enum definition styles, support for additionalProperties | |
369
379
  | Nullable types, path inheritance, JSDoc descriptions | |
370
380
  | Remote URLs and local file paths as spec source | |
371
381
  | Grouping by tags, graceful handling of duplicate operation IDs | |
package/dist/cli.js CHANGED
@@ -23,6 +23,10 @@ const schemaStyleOption = new (0, _commander.Option)(
23
23
  '-d, --schemaStyle <style>',
24
24
  'Schema object declaration style'
25
25
  ).choices(['interface', 'type']);
26
+ const enumStyleOption = new (0, _commander.Option)(
27
+ '--enumStyle <style>',
28
+ 'Enum declaration style for plain string enums'
29
+ ).choices(['union', 'enum']);
26
30
 
27
31
  const program = new (0, _commander.Command)();
28
32
  program
@@ -64,7 +68,8 @@ program
64
68
  )
65
69
  .addOption(arrayFormatOption)
66
70
  .addOption(modeOption)
67
- .addOption(schemaStyleOption);
71
+ .addOption(schemaStyleOption)
72
+ .addOption(enumStyleOption);
68
73
 
69
74
  program.parse(process.argv);
70
75
 
@@ -70,7 +70,7 @@ function renderSchema(
70
70
  return result.join('\n');
71
71
  }
72
72
  if ('enum' in schema) {
73
- result.push(renderEnumType(safeName, schema));
73
+ result.push(renderEnumType(safeName, schema, options));
74
74
  return result.join('\n');
75
75
  }
76
76
 
@@ -83,7 +83,15 @@ function renderSchema(
83
83
  if ('allOf' in schema) {
84
84
  const types = _swagger.getRefCompositeTypes.call(void 0, schema);
85
85
  const mergedSchema = getMergedCompositeObjects(schema);
86
+ const objectType = _swagger.getTypeFromSchema.call(void 0, mergedSchema, options);
86
87
  const objectContents = generateObjectTypeContents(mergedSchema, options);
88
+ const hasAdditionalProperties = !!mergedSchema.additionalProperties;
89
+
90
+ if (hasAdditionalProperties) {
91
+ const compositeTypes = [...types, objectType].join(' & ');
92
+ result.push(`export type ${safeName} = ${compositeTypes};`);
93
+ return `${result.join('\n')}\n`;
94
+ }
87
95
 
88
96
  if (useTypeAliases) {
89
97
  const compositeTypes = [...types, `{${objectContents ? `\n${objectContents}\n` : ''}}`].join(' & ');
@@ -95,7 +103,7 @@ function renderSchema(
95
103
  result.push(`export interface ${safeName} ${extensions}{`);
96
104
  result.push(objectContents);
97
105
  } else if ('oneOf' in schema || 'anyOf' in schema) {
98
- const typeDefinition = getTypesFromAnyOrOneOf(schema, options);
106
+ const typeDefinition = _swagger.getTypeFromSchema.call(void 0, schema, options);
99
107
  result.push(`export type ${safeName} = ${typeDefinition};`);
100
108
 
101
109
  return `${result.join('\n')}\n`;
@@ -105,7 +113,15 @@ function renderSchema(
105
113
  result.push(`export type ${safeName} = ${generateItemsType(schema.items, options)}[];`);
106
114
  return result.join('\n');
107
115
  } else {
116
+ const objectType = _swagger.getTypeFromSchema.call(void 0, schema, options);
117
+ const hasAdditionalProperties = !!schema.additionalProperties;
118
+
108
119
  const objectContents = generateObjectTypeContents(schema, options);
120
+ if (hasAdditionalProperties) {
121
+ result.push(`export type ${safeName} = ${objectType};`);
122
+ return `${result.join('\n')}\n`;
123
+ }
124
+
109
125
  if (useTypeAliases) {
110
126
  result.push(`export type ${safeName} = {`);
111
127
  result.push(objectContents);
@@ -119,21 +135,6 @@ function renderSchema(
119
135
  return `${result.join('\n')}\n}\n`;
120
136
  }
121
137
 
122
- /**
123
- * Generates the type definition for an `anyOf` or `oneOf` schema.
124
- * @param schema - The schema object to generate the type definition for.
125
- * @param options - The options for the generation.
126
- * @returns The type definition for the `anyOf` or `oneOf` schema.
127
- */
128
- function getTypesFromAnyOrOneOf(schema, options) {
129
- const composite = schema.allOf || schema.oneOf || schema.anyOf;
130
- if (!composite) {
131
- return '';
132
- }
133
-
134
- return composite.map((s) => _swagger.getTypeFromSchema.call(void 0, s, options)).join(' | ');
135
- }
136
-
137
138
  /**
138
139
  * Generates the inline contents of an object type.
139
140
  */
@@ -182,11 +183,36 @@ function renderExtendedEnumType(name, def) {
182
183
  /**
183
184
  * Render simple enum types (just a union of values)
184
185
  */
185
- function renderEnumType(name, def) {
186
+ function renderEnumType(name, def, options) {
187
+ if (options.enumDeclarationStyle === 'enum' && shouldRenderStringEnumDeclaration(def)) {
188
+ return renderStringEnumDeclaration(name, def);
189
+ }
190
+
186
191
  const values = def.enum.map((v) => (typeof v === 'number' ? v : `"${v}"`)).join(' | ');
187
192
  return `export type ${name} = ${values};\n`;
188
193
  }
189
194
 
195
+ function shouldRenderStringEnumDeclaration(def)
196
+
197
+ {
198
+ return (
199
+ def.type === 'string' &&
200
+ Array.isArray(def.enum) &&
201
+ def.enum.every((value) => typeof value === 'string')
202
+ );
203
+ }
204
+
205
+ function renderStringEnumDeclaration(name, def) {
206
+ let res = `export enum ${name} {\n`;
207
+ for (let index = 0; index < def.enum.length; index++) {
208
+ const value = def.enum[index];
209
+ const memberName = _nullishCoalesce(_utils.escapePropName.call(void 0, value), () => ( `VALUE_${index}`));
210
+ res += ` ${memberName} = ${JSON.stringify(value)},\n`;
211
+ }
212
+
213
+ return `${res}}\n`;
214
+ }
215
+
190
216
  /**
191
217
  * OpenApi 3.1 introduced a new way to define enums that we support here.
192
218
  */
@@ -267,7 +293,7 @@ function getMergedCompositeObjects(schema) {
267
293
  subSchemas.push(safeSchema);
268
294
  }
269
295
 
270
- return deepMerge({}, ...subSchemas);
296
+ return deepMerge({}, ...subSchemas) ;
271
297
  }
272
298
 
273
299
  function isObject(item) {
package/dist/index.js CHANGED
@@ -83,6 +83,7 @@ function readFile(filePath) {
83
83
  arrayFormat,
84
84
  mode,
85
85
  schemaStyle,
86
+ enumStyle,
86
87
  template,
87
88
  queryParamsSerialization = {},
88
89
  ...rest
@@ -104,6 +105,8 @@ function readFile(filePath) {
104
105
  generationMode: _nullishCoalesce(_nullishCoalesce(mode, () => ( rest.generationMode)), () => ( _swagger.APP_DEFAULTS.generationMode)),
105
106
  schemaDeclarationStyle:
106
107
  _nullishCoalesce(_nullishCoalesce(schemaStyle, () => ( rest.schemaDeclarationStyle)), () => ( _swagger.APP_DEFAULTS.schemaDeclarationStyle)),
108
+ enumDeclarationStyle:
109
+ _nullishCoalesce(_nullishCoalesce(enumStyle, () => ( rest.enumDeclarationStyle)), () => ( _swagger.APP_DEFAULTS.enumDeclarationStyle)),
107
110
  queryParamsSerialization: mergedQueryParamsSerialization,
108
111
  };
109
112
  } exports.prepareAppOptions = prepareAppOptions;
@@ -192,20 +192,16 @@ function getNestedTypeFromSchema(
192
192
  */
193
193
  function getTypeFromObject(
194
194
  schema,
195
- options
195
+ options,
196
+ requiredOverride
196
197
  ) {
197
198
  const unknownType = options.preferAny ? 'any' : 'unknown';
198
-
199
- if (schema.additionalProperties) {
200
- const extraProps = schema.additionalProperties;
201
- return `{ [key: string]: ${
202
- extraProps === true ? 'any' : getTypeFromSchemaResolved(extraProps, options)
203
- } }`;
204
- }
199
+ let objectWithNamedPropsType = '';
200
+ let objectWithIndexSignatureType = '';
205
201
 
206
202
  if (schema.properties) {
207
203
  const props = Object.keys(schema.properties);
208
- const required = schema.required || [];
204
+ const required = _nullishCoalesce(_nullishCoalesce(requiredOverride, () => ( schema.required)), () => ( []));
209
205
  const result = [];
210
206
 
211
207
  for (const prop of props) {
@@ -217,7 +213,26 @@ function getTypeFromObject(
217
213
  );
218
214
  }
219
215
 
220
- return `{ ${result.join('\n')} }`;
216
+ objectWithNamedPropsType = `{ ${result.join('\n')} }`;
217
+ }
218
+
219
+ if (schema.additionalProperties) {
220
+ const extraProps = schema.additionalProperties;
221
+ objectWithIndexSignatureType = `{ [key: string]: ${
222
+ extraProps === true ? 'any' : getTypeFromSchemaResolved(extraProps, options)
223
+ } }`;
224
+ }
225
+
226
+ if (objectWithNamedPropsType && objectWithIndexSignatureType) {
227
+ return `${objectWithNamedPropsType} & ${objectWithIndexSignatureType}`;
228
+ }
229
+
230
+ if (objectWithNamedPropsType) {
231
+ return objectWithNamedPropsType;
232
+ }
233
+
234
+ if (objectWithIndexSignatureType) {
235
+ return objectWithIndexSignatureType;
221
236
  }
222
237
 
223
238
  return unknownType;
@@ -229,11 +244,64 @@ function getTypeFromObject(
229
244
  function getTypeFromComposites(schema, options) {
230
245
  const composite = schema.allOf || schema.oneOf || schema.anyOf;
231
246
 
247
+ if (!composite) {
248
+ return options.preferAny ? 'any' : 'unknown';
249
+ }
250
+
251
+ const isUnionComposite = !!schema.oneOf || !!schema.anyOf;
252
+ const hasParentObjectShape = !!schema.properties || !!schema.additionalProperties;
253
+
254
+ if (isUnionComposite && hasParentObjectShape) {
255
+ const fallbackType = options.preferAny ? 'any' : 'unknown';
256
+ const parentObjectType = getTypeFromObject(schema, options);
257
+ const parentRequired = schema.required || [];
258
+ const parentPropSet = new Set(Object.keys(schema.properties || {}));
259
+
260
+ return composite
261
+ .map((subSchema) => {
262
+ if (!('$ref' in subSchema) && isRequiredOnlyCompositeBranch(subSchema)) {
263
+ const branchRequired = subSchema.required || [];
264
+ const validRequired = branchRequired.filter((name) => {
265
+ const isKnown = parentPropSet.has(name);
266
+ if (!isKnown) {
267
+ console.warn(
268
+ `Composite required key '${name}' is not present in schema properties and will be ignored.`
269
+ );
270
+ }
271
+ return isKnown;
272
+ });
273
+
274
+ return getTypeFromObject(schema, options, Array.from(new Set([...parentRequired, ...validRequired])));
275
+ }
276
+
277
+ const subType = getTypeFromSchemaResolved(subSchema, options);
278
+ if (subType === fallbackType) {
279
+ return parentObjectType;
280
+ }
281
+
282
+ return `${parentObjectType} & ${subType}`;
283
+ })
284
+ .join(' | ');
285
+ }
286
+
232
287
  return composite
233
288
  .map((s) => getTypeFromSchemaResolved(s, options))
234
289
  .join(schema.allOf ? ' & ' : ' | ');
235
290
  }
236
291
 
292
+ function isRequiredOnlyCompositeBranch(schema) {
293
+ return (
294
+ Array.isArray(schema.required) &&
295
+ !schema.type &&
296
+ !schema.properties &&
297
+ !schema.additionalProperties &&
298
+ !schema.allOf &&
299
+ !schema.oneOf &&
300
+ !schema.anyOf &&
301
+ !schema.enum
302
+ );
303
+ }
304
+
237
305
  /**
238
306
  * Escapes name so it can be used as a valid identifier in the generated code.
239
307
  * Component names can contain certain characters that are not allowed in identifiers.
@@ -264,6 +332,7 @@ function getTypeFromComposites(schema, options) {
264
332
  nullableStrategy: 'ignore',
265
333
  generationMode: 'full',
266
334
  schemaDeclarationStyle: 'interface',
335
+ enumDeclarationStyle: 'union',
267
336
  queryParamsSerialization: {
268
337
  allowDots: true,
269
338
  arrayFormat: 'repeat',
@@ -283,6 +352,7 @@ function getTypeFromComposites(schema, options) {
283
352
  nullableStrategy: _nullishCoalesce(opts.nullableStrategy, () => ( exports.APP_DEFAULTS.nullableStrategy)),
284
353
  generationMode: _nullishCoalesce(opts.generationMode, () => ( exports.APP_DEFAULTS.generationMode)),
285
354
  schemaDeclarationStyle: _nullishCoalesce(opts.schemaDeclarationStyle, () => ( exports.APP_DEFAULTS.schemaDeclarationStyle)),
355
+ enumDeclarationStyle: _nullishCoalesce(opts.enumDeclarationStyle, () => ( exports.APP_DEFAULTS.enumDeclarationStyle)),
286
356
  queryParamsSerialization: {
287
357
  ...exports.APP_DEFAULTS.queryParamsSerialization,
288
358
  ...opts.queryParamsSerialization,
package/dist/types.d.ts CHANGED
@@ -33,6 +33,8 @@ export interface ClientOptions {
33
33
  generationMode?: GenerationMode;
34
34
  /** Controls whether object schemas are emitted as interfaces or type aliases */
35
35
  schemaDeclarationStyle?: SchemaDeclarationStyle;
36
+ /** Controls whether plain string enums are emitted as unions or TypeScript enums */
37
+ enumDeclarationStyle?: EnumDeclarationStyle;
36
38
  /** Offers ability to adjust the OpenAPI spec before it is processed */
37
39
  modifiers?: {
38
40
  /** Global-level modifiers for parameter with a given name */
@@ -46,6 +48,7 @@ export interface CliOptions extends FullAppOptions {
46
48
  arrayFormat?: ArrayFormat;
47
49
  mode?: GenerationMode;
48
50
  schemaStyle?: SchemaDeclarationStyle;
51
+ enumStyle?: EnumDeclarationStyle;
49
52
  }
50
53
  export interface FullAppOptions extends ClientOptions {
51
54
  /** Path to the configuration file that contains actual config to be used */
@@ -58,6 +61,7 @@ export type ArrayFormat = 'indices' | 'repeat' | 'brackets';
58
61
  export type NullableStrategy = 'include' | 'nullableAsOptional' | 'ignore';
59
62
  export type GenerationMode = 'full' | 'schemas';
60
63
  export type SchemaDeclarationStyle = 'interface' | 'type';
64
+ export type EnumDeclarationStyle = 'union' | 'enum';
61
65
  /**
62
66
  * Internal options type used throughout the app after `prepareAppOptions` has run.
63
67
  * All fields that have defaults are required here so the rest of the codebase never
@@ -69,6 +73,7 @@ export interface AppOptions extends ClientOptions {
69
73
  nullableStrategy: NullableStrategy;
70
74
  generationMode: GenerationMode;
71
75
  schemaDeclarationStyle: SchemaDeclarationStyle;
76
+ enumDeclarationStyle: EnumDeclarationStyle;
72
77
  queryParamsSerialization: {
73
78
  allowDots: boolean;
74
79
  arrayFormat: ArrayFormat;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "swaggie",
3
- "version": "1.8.0",
3
+ "version": "1.8.1",
4
4
  "description": "Generate a fully typed TypeScript API client from your OpenAPI 3 spec",
5
5
  "author": {
6
6
  "name": "Piotr Dabrowski",