genoc 0.4.1 → 0.4.2

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.
@@ -1,34 +1,50 @@
1
1
  import { sanitizeJsDocText } from '../utils/generator-helpers.js';
2
2
  import { getSuccessType, operationEmissions } from '../utils/operation-naming.js';
3
+ /**
4
+ * A `?:` parameter may only be followed by other optional parameters — a
5
+ * required parameter after an optional one is a TS1016 compile error
6
+ * ("A required parameter cannot follow an optional parameter"). Optional
7
+ * parameters that precede a required one are therefore rendered as
8
+ * `name: T | undefined` instead of `name?: T`.
9
+ */
10
+ function renderParams(specs) {
11
+ return specs
12
+ .map((spec, index) => {
13
+ if (!spec.isOptional) {
14
+ return `${spec.name}: ${spec.type}`;
15
+ }
16
+ const hasRequiredAfter = specs.slice(index + 1).some((s) => !s.isOptional);
17
+ if (hasRequiredAfter) {
18
+ return `${spec.name}: ${spec.type} | undefined`;
19
+ }
20
+ return `${spec.name}?: ${spec.type}`;
21
+ })
22
+ .join(', ');
23
+ }
3
24
  function buildParameters(op) {
4
- const params = [];
5
25
  const emissions = operationEmissions(op);
26
+ const specs = [];
6
27
  for (const param of op.pathParams) {
7
- params.push(`${param.name}: string`);
28
+ specs.push({ name: param.name, type: 'string', isOptional: false });
8
29
  }
9
30
  if (emissions.query !== undefined) {
10
31
  const allOptional = op.queryParams.every((p) => !p.required);
11
- const hasRequiredAfter = !!op.requestBody?.required || op.headerParams.some((p) => p.required);
12
- if (allOptional && hasRequiredAfter) {
13
- // All optional query params + required param after: use explicit undefined to avoid "required param cannot follow optional" error
14
- params.push(`query: ${emissions.query} | undefined`);
15
- }
16
- else {
17
- // Normal case: use optional notation
18
- const optional = allOptional ? '?' : '';
19
- params.push(`query${optional}: ${emissions.query}`);
20
- }
32
+ specs.push({ name: 'query', type: emissions.query, isOptional: allOptional });
21
33
  }
22
34
  if (op.requestBody) {
23
- const optional = op.requestBody.required ? '' : '?';
24
- params.push(`body${optional}: ${emissions.body}`);
35
+ // A schema-less body has no named type; the literal keeps the rendered
36
+ // signature byte-identical to the previous template-literal fallback.
37
+ specs.push({
38
+ name: 'body',
39
+ type: emissions.body ?? 'undefined',
40
+ isOptional: !op.requestBody.required,
41
+ });
25
42
  }
26
43
  if (emissions.headers !== undefined) {
27
44
  const allOptional = op.headerParams.every((p) => !p.required);
28
- const optional = allOptional ? '?' : '';
29
- params.push(`headers${optional}: ${emissions.headers}`);
45
+ specs.push({ name: 'headers', type: emissions.headers, isOptional: allOptional });
30
46
  }
31
- return params.join(', ');
47
+ return renderParams(specs);
32
48
  }
33
49
  function buildJsDoc(op) {
34
50
  const lines = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genoc",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "description": "Generate typed HTTP clients from OpenAPI 3.0 / 3.1 specifications",
5
5
  "keywords": [
6
6
  "client",
@@ -47,7 +47,7 @@
47
47
  "zod": "^4.0.0"
48
48
  },
49
49
  "devDependencies": {
50
- "@changesets/cli": "^3.0.3",
50
+ "@changesets/cli": "^2.31.0",
51
51
  "@types/node": "^26.6.2",
52
52
  "oxfmt": "^0.70.0",
53
53
  "oxlint": "^1.85.0",