swaggie 1.8.4-beta.1 → 1.8.5

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
@@ -1,5 +1,5 @@
1
1
  <div style="text-align: center; margin: 0px auto 30px; max-width: 600px">
2
- <img src="./swaggie.svg" alt="Swaggie logo">
2
+ <img src="./docs/public/swaggie-full.png" alt="Swaggie logo">
3
3
  </div>
4
4
 
5
5
  # Swaggie
@@ -387,7 +387,7 @@ Swaggie only needs a JSON or YAML OpenAPI spec file — it does not require a ru
387
387
  ## Used By
388
388
 
389
389
  <div style="display: flex; gap: 1rem;">
390
- <a href="https://www.britishcouncil.org"><img alt="British Council" src="https://upload.wikimedia.org/wikipedia/commons/thumb/e/e1/BritishCouncil.png/320px-BritishCouncil.png" style="height: 50px;" /></a>
391
- <a href="https://kpmg.com/"><img alt="KPMG" src="https://upload.wikimedia.org/wikipedia/commons/thumb/3/31/KPMG.svg/320px-KPMG.svg.png" style="height: 50px;" /></a>
392
- <a href="https://klarna.com/"><img alt="Klarna" src="https://upload.wikimedia.org/wikipedia/commons/4/40/Klarna_Payment_Badge.svg" style="height: 50px;" /></a>
390
+ <a href="https://www.britishcouncil.org"><img alt="British Council" src="./docs/public/used-in/bc-logo.png" /></a>
391
+ <a href="https://kpmg.com/"><img alt="KPMG" src="./docs/public/used-in/kpmg-logo.png" /></a>
392
+ <a href="https://klarna.com/"><img alt="Klarna" src="./docs/public/used-in/klarna-logo.png" /></a>
393
393
  </div>
@@ -88,19 +88,30 @@ function renderSchema(
88
88
  const objectContents = generateObjectTypeContents(mergedSchema, options, schemaContext);
89
89
  const hasAdditionalProperties = !!mergedSchema.additionalProperties;
90
90
 
91
+ // Detect "orphan" required fields: top-level `required` entries that reference
92
+ // properties from $ref types rather than inline properties. For these we generate
93
+ // Required<Pick<...>> to enforce non-optionality in TypeScript.
94
+ const requiredPickType = getRequiredPickType(schema, mergedSchema, types);
95
+
91
96
  if (hasAdditionalProperties) {
92
- const compositeTypes = [...types, objectType].join(' & ');
97
+ const compositeTypes = [...types, requiredPickType, objectType].filter(Boolean).join(' & ');
93
98
  result.push(`export type ${safeName} = ${compositeTypes};`);
94
99
  return `${result.join('\n')}\n`;
95
100
  }
96
101
 
97
- if (useTypeAliases) {
98
- const compositeTypes = [...types, `{${objectContents ? `\n${objectContents}\n` : ''}}`].join(' & ');
102
+ if (useTypeAliases || requiredPickType) {
103
+ // When requiredPickType is present we must use type alias (intersection) style,
104
+ // because `interface extends` does not allow extending two types that declare the
105
+ // same property with different optionality (e.g. `Team` with `id?` and
106
+ // `Required<Pick<Team, 'id'>>` with `id` — TS2320).
107
+ const objectLiteral = objectContents ? `{\n${objectContents}\n}` : '';
108
+ const allTypes = [...types, requiredPickType, objectLiteral].filter(Boolean);
109
+ const compositeTypes = allTypes.join(' & ');
99
110
  result.push(`export type ${safeName} = ${compositeTypes};`);
100
111
  return `${result.join('\n')}\n`;
101
112
  }
102
113
 
103
- const extensions = types ? `extends ${types.join(', ')} ` : '';
114
+ const extensions = types.length ? `extends ${types.join(', ')} ` : '';
104
115
  result.push(`export interface ${safeName} ${extensions}{`);
105
116
  result.push(objectContents);
106
117
  } else if ('oneOf' in schema || 'anyOf' in schema) {
@@ -288,14 +299,40 @@ function isNullableAsOptional(
288
299
  );
289
300
  }
290
301
 
302
+ /**
303
+ * Detects "orphan" required fields: top-level `required` entries that are not covered
304
+ * by inline `properties` in the merged schema. For these fields, we generate a
305
+ * `Required<Pick<RefType, 'prop1' | 'prop2'>>` expression to enforce non-optionality.
306
+ *
307
+ * @returns A `Required<Pick<...>>` type string, or an empty string if not applicable.
308
+ */
309
+ function getRequiredPickType(
310
+ originalSchema,
311
+ mergedSchema,
312
+ refTypes
313
+ ) {
314
+ const topLevelRequired = originalSchema.required || [];
315
+ if (!topLevelRequired.length || !refTypes.length) return '';
316
+
317
+ const inlineProps = Object.keys(mergedSchema.properties || {});
318
+ const orphanRequired = topLevelRequired.filter((r) => !inlineProps.includes(r));
319
+ if (!orphanRequired.length) return '';
320
+
321
+ const propsUnion = orphanRequired.map((p) => `'${p}'`).join(' | ');
322
+ const baseType = refTypes.length === 1 ? refTypes[0] : refTypes.join(' & ');
323
+
324
+ return `Required<Pick<${baseType}, ${propsUnion}>>`;
325
+ }
326
+
291
327
  function getMergedCompositeObjects(schema) {
292
328
  const { allOf, oneOf, anyOf, ...safeSchema } = schema;
293
329
  const composite = allOf || oneOf || anyOf || [];
294
330
  const subSchemas = composite.filter((v) => !('$ref' in v));
295
331
 
296
- // This is the case where schema itself is of type object, with properties
297
- // and at the same time has sub-schemas like `allOf` or similar
298
- if (safeSchema.type === 'object' && 'properties' in safeSchema) {
332
+ // This is the case where schema itself has properties, required fields,
333
+ // or is of type object — and at the same time has sub-schemas like `allOf` or similar.
334
+ // We include the parent schema data so that `required` and `properties` are not lost.
335
+ if ('properties' in safeSchema || 'required' in safeSchema) {
299
336
  subSchemas.push(safeSchema);
300
337
  }
301
338
 
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "swaggie",
3
- "version": "1.8.4-beta.1",
3
+ "version": "1.8.5",
4
4
  "description": "Generate a fully typed TypeScript API client from your OpenAPI 3 spec",
5
5
  "author": {
6
6
  "name": "Piotr Dabrowski",
7
7
  "url": "https://github.com/yhnavein"
8
8
  },
9
9
  "license": "MIT",
10
- "homepage": "https://github.com/yhnavein/swaggie",
10
+ "homepage": "https://yhnavein.github.io/swaggie/",
11
11
  "repository": {
12
12
  "type": "git",
13
13
  "url": "git+https://github.com/yhnavein/swaggie.git"
@@ -80,6 +80,7 @@
80
80
  "openapi-types": "^12.1.3",
81
81
  "sucrase": "3.35.1",
82
82
  "typescript": "5.9.3",
83
- "vitepress": "^2.0.0-alpha.16"
83
+ "vitepress": "^2.0.0-alpha.16",
84
+ "vitepress-plugin-tabs": "0.8.0"
84
85
  }
85
86
  }