genoc 0.3.0 → 0.4.0
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 +30 -186
- package/dist/analyzer/analyze.d.ts +18 -0
- package/dist/analyzer/analyze.js +534 -0
- package/dist/analyzer/path-analyzer.d.ts +43 -6
- package/dist/analyzer/path-analyzer.js +57 -84
- package/dist/analyzer/schema-mapper.d.ts +3 -2
- package/dist/analyzer/schema-mapper.js +11 -7
- package/dist/analyzer/types.d.ts +119 -0
- package/dist/cli/app.d.ts +4 -2
- package/dist/cli/app.js +23 -4
- package/dist/cli/config-loader.d.ts +20 -0
- package/dist/cli/config-loader.js +186 -0
- package/dist/cli/config-schema.d.ts +105 -0
- package/dist/cli/config-schema.js +183 -0
- package/dist/cli/impl.d.ts +9 -1
- package/dist/cli/impl.js +186 -36
- package/dist/generator/client-generator.d.ts +13 -14
- package/dist/generator/client-generator.js +45 -77
- package/dist/generator/contracts-generator.d.ts +11 -9
- package/dist/generator/contracts-generator.js +45 -491
- package/dist/generator/method-generator.d.ts +1 -1
- package/dist/generator/method-generator.js +10 -150
- package/dist/parser/ref-resolver.d.ts +1 -1
- package/dist/parser/ref-resolver.js +1 -2
- package/dist/parser/version/index.d.ts +4 -16
- package/dist/parser/version/index.js +3 -14
- package/dist/parser/version/v3.0/validator.d.ts +1 -1
- package/dist/parser/version/v3.1/validator.d.ts +17 -0
- package/dist/parser/version/v3.1/validator.js +126 -0
- package/dist/parser/version/validate.d.ts +9 -0
- package/dist/parser/version/validate.js +12 -0
- package/dist/parser/version/version-detector.d.ts +21 -0
- package/dist/parser/version/version-detector.js +19 -0
- package/dist/pipeline.d.ts +13 -0
- package/dist/pipeline.js +24 -0
- package/dist/types/client.d.ts +0 -12
- package/dist/utils/generator-helpers.d.ts +1 -24
- package/dist/utils/generator-helpers.js +11 -78
- package/dist/utils/operation-naming.d.ts +84 -0
- package/dist/utils/operation-naming.js +178 -0
- package/package.json +4 -8
- package/dist/index.d.ts +0 -9
- package/dist/index.js +0 -11
- package/dist/parser/validators.d.ts +0 -7
- package/dist/parser/validators.js +0 -79
- package/dist/parser/version/normalized-spec.d.ts +0 -199
- package/dist/parser/version/registry.d.ts +0 -28
- package/dist/parser/version/registry.js +0 -44
- package/dist/parser/version/v3.0/index.d.ts +0 -3
- package/dist/parser/version/v3.0/index.js +0 -3
- package/dist/parser/version/v3.0/normalizer.d.ts +0 -15
- package/dist/parser/version/v3.0/normalizer.js +0 -389
- package/dist/parser/version/v3.0/strategy.d.ts +0 -27
- package/dist/parser/version/v3.0/strategy.js +0 -96
- package/dist/parser/version/v3.1/index.d.ts +0 -1
- package/dist/parser/version/v3.1/index.js +0 -1
- package/dist/parser/version/v3.1/strategy.d.ts +0 -42
- package/dist/parser/version/v3.1/strategy.js +0 -513
- package/dist/parser/version/v3.2/index.d.ts +0 -4
- package/dist/parser/version/v3.2/index.js +0 -4
- package/dist/parser/version/v3.2/strategy.d.ts +0 -39
- package/dist/parser/version/v3.2/strategy.js +0 -57
- package/dist/parser/version/version-strategy.d.ts +0 -31
- package/dist/parser/version/version-strategy.js +0 -1
- /package/dist/{parser/version/normalized-spec.js → analyzer/types.js} +0 -0
|
@@ -82,27 +82,6 @@ const RESERVED_WORDS = new Set([
|
|
|
82
82
|
function isReservedTypeName(name) {
|
|
83
83
|
return RESERVED_WORDS.has(name);
|
|
84
84
|
}
|
|
85
|
-
/**
|
|
86
|
-
* Reserved type names used by the generated output's built-in classes,
|
|
87
|
-
* functions, and types. User-defined schema names that collide with these
|
|
88
|
-
* are automatically renamed with a suffix to prevent duplicate identifiers.
|
|
89
|
-
*/
|
|
90
|
-
export const RESERVED_TYPE_NAMES = new Set([
|
|
91
|
-
// Classes in contracts.ts
|
|
92
|
-
'StreamResponse',
|
|
93
|
-
'ErrorResponse',
|
|
94
|
-
'ApiError',
|
|
95
|
-
'UnspecifiedApiError',
|
|
96
|
-
'DefaultApiError',
|
|
97
|
-
'RequesterFailError',
|
|
98
|
-
// Types / functions in client.ts
|
|
99
|
-
'Requester',
|
|
100
|
-
'isDefinedError',
|
|
101
|
-
'decorateWithErrors',
|
|
102
|
-
'ApiClient',
|
|
103
|
-
// Interface in contracts.ts (when file uploads present)
|
|
104
|
-
'FileInput',
|
|
105
|
-
]);
|
|
106
85
|
/** Default module specifier generated code imports shared runtime classes from. */
|
|
107
86
|
export const DEFAULT_RUNTIME_IMPORT_PATH = 'genoc/runtime';
|
|
108
87
|
/**
|
|
@@ -187,63 +166,6 @@ export function toPascalCase(str) {
|
|
|
187
166
|
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
188
167
|
.join('');
|
|
189
168
|
}
|
|
190
|
-
/**
|
|
191
|
-
* Build a PascalCase type-name prefix from an operation's method + path.
|
|
192
|
-
* get + /api/v1/products → "GetApiV1Products"
|
|
193
|
-
*
|
|
194
|
-
* Returns the deduped prefix assigned by analyzePaths when available
|
|
195
|
-
* (distinct routes folding to the same identifier get numbered suffixes),
|
|
196
|
-
* otherwise computes it from the operation directly.
|
|
197
|
-
*/
|
|
198
|
-
export function getOperationTypePrefix(op) {
|
|
199
|
-
if (op.typePrefix)
|
|
200
|
-
return op.typePrefix;
|
|
201
|
-
return computeOperationTypePrefix(op);
|
|
202
|
-
}
|
|
203
|
-
function computeOperationTypePrefix(op) {
|
|
204
|
-
const methodPascal = op.method.charAt(0).toUpperCase() + op.method.slice(1).toLowerCase();
|
|
205
|
-
const segments = op.path
|
|
206
|
-
.split('/')
|
|
207
|
-
.filter((s) => s.length > 0)
|
|
208
|
-
.map((s) => {
|
|
209
|
-
const cleaned = s.replace(/[{}]/g, '');
|
|
210
|
-
return sanitizeTypeName(toPascalCase(cleaned));
|
|
211
|
-
});
|
|
212
|
-
return methodPascal + segments.join('');
|
|
213
|
-
}
|
|
214
|
-
/**
|
|
215
|
-
* Determine the success return type for an operation.
|
|
216
|
-
*/
|
|
217
|
-
export function getSuccessType(op) {
|
|
218
|
-
const successResponses = op.responses.filter((r) => r.isSuccess);
|
|
219
|
-
if (successResponses.length === 0) {
|
|
220
|
-
return 'unknown';
|
|
221
|
-
}
|
|
222
|
-
const noContent = successResponses.find((r) => r.tsType === 'void');
|
|
223
|
-
const hasOnlyNoContent = noContent && successResponses.every((r) => r.tsType === 'void');
|
|
224
|
-
if (hasOnlyNoContent) {
|
|
225
|
-
return 'void';
|
|
226
|
-
}
|
|
227
|
-
const withSchema = successResponses.filter((r) => r.tsType !== 'void');
|
|
228
|
-
if (withSchema.length === 0) {
|
|
229
|
-
return 'void';
|
|
230
|
-
}
|
|
231
|
-
const prefix = getOperationTypePrefix(op);
|
|
232
|
-
const types = withSchema.map(() => `${prefix}Response`);
|
|
233
|
-
const unique = [...new Set(types)];
|
|
234
|
-
return unique.join(' | ');
|
|
235
|
-
}
|
|
236
|
-
/**
|
|
237
|
-
* Determine the error type name for an operation.
|
|
238
|
-
*/
|
|
239
|
-
export function getErrorType(op) {
|
|
240
|
-
const prefix = getOperationTypePrefix(op);
|
|
241
|
-
const errorResponses = op.responses.filter((r) => !r.isSuccess && r.statusCode !== 'default');
|
|
242
|
-
if (errorResponses.length === 0) {
|
|
243
|
-
return 'never';
|
|
244
|
-
}
|
|
245
|
-
return `${prefix}Errors`;
|
|
246
|
-
}
|
|
247
169
|
/**
|
|
248
170
|
* Generate the auto-generated header comment.
|
|
249
171
|
*/
|
|
@@ -325,3 +247,14 @@ export function buildTypeJsDoc(schema) {
|
|
|
325
247
|
return `/** ${lines[0]} */`;
|
|
326
248
|
return `/**\n${lines.map((line) => ` * ${line}`).join('\n *\n')}\n */`;
|
|
327
249
|
}
|
|
250
|
+
export function isBinaryContentType(ct) {
|
|
251
|
+
if (ct === 'application/octet-stream')
|
|
252
|
+
return true;
|
|
253
|
+
if (ct.startsWith('image/'))
|
|
254
|
+
return true;
|
|
255
|
+
if (ct.startsWith('video/'))
|
|
256
|
+
return true;
|
|
257
|
+
if (ct.startsWith('audio/'))
|
|
258
|
+
return true;
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { AnalyzedOperation } from '../analyzer/path-analyzer.js';
|
|
2
|
+
/**
|
|
3
|
+
* Canonical runtime class list in the contracts re-export order. Membership
|
|
4
|
+
* source of truth for every runtime class name the generators emit.
|
|
5
|
+
*/
|
|
6
|
+
export declare const RUNTIME_CLASS_NAMES: readonly ["ApiError", "UnspecifiedApiError", "DefaultApiError", "RequesterFailError", "StreamResponse", "ErrorResponse"];
|
|
7
|
+
/**
|
|
8
|
+
* client.ts value imports from contracts.js in site emission order.
|
|
9
|
+
* `DefaultApiError` is conditional (only when an operation has a default
|
|
10
|
+
* error response), so it is appended by the client generator, not listed here.
|
|
11
|
+
*/
|
|
12
|
+
export declare const CLIENT_BASE_VALUE_IMPORTS: readonly ["ApiError", "UnspecifiedApiError", "ErrorResponse", "StreamResponse", "RequesterFailError"];
|
|
13
|
+
/** Fixed client-file surface names (values and types client.ts declares). */
|
|
14
|
+
export declare const CLIENT_SURFACE_NAMES: readonly ["Requester", "isDefinedError", "decorateWithErrors", "ApiClient", "createClient"];
|
|
15
|
+
/**
|
|
16
|
+
* Reserved type names used by the generated output's built-in classes,
|
|
17
|
+
* functions, and types. User-defined schema names that collide with these
|
|
18
|
+
* are automatically renamed with a suffix to prevent duplicate identifiers.
|
|
19
|
+
*
|
|
20
|
+
* Derived — never hand-edited: add new fixed names to the owning constant
|
|
21
|
+
* (`RUNTIME_CLASS_NAMES` / `CLIENT_SURFACE_NAMES`) or, for contracts-only
|
|
22
|
+
* interfaces like `FileInput`, extend the derivation below.
|
|
23
|
+
*/
|
|
24
|
+
export declare const RESERVED_TYPE_NAMES: ReadonlySet<string>;
|
|
25
|
+
/**
|
|
26
|
+
* Build a PascalCase type-name prefix from an operation's method + path.
|
|
27
|
+
* get + /api/v1/products → "GetApiV1Products"
|
|
28
|
+
*
|
|
29
|
+
* Returns the deduped prefix assigned by analyzePaths when available
|
|
30
|
+
* (distinct routes folding to the same identifier get numbered suffixes),
|
|
31
|
+
* otherwise computes it from the operation directly.
|
|
32
|
+
*/
|
|
33
|
+
export declare function getOperationTypePrefix(op: AnalyzedOperation): string;
|
|
34
|
+
/**
|
|
35
|
+
* Determine the success return type for an operation.
|
|
36
|
+
*/
|
|
37
|
+
export declare function getSuccessType(op: AnalyzedOperation): string;
|
|
38
|
+
/**
|
|
39
|
+
* Determine the error type name for an operation.
|
|
40
|
+
*/
|
|
41
|
+
export declare function getErrorType(op: AnalyzedOperation): string;
|
|
42
|
+
/** Name of one per-status error type (`{prefix}Error{status}`). */
|
|
43
|
+
export interface StatusErrorName {
|
|
44
|
+
status: string;
|
|
45
|
+
name: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Inventory of every operation-derived type name the contracts file emits
|
|
49
|
+
* for one operation. A field is present exactly when the contracts loop
|
|
50
|
+
* emits that type — presence and naming are decided here and nowhere else.
|
|
51
|
+
*
|
|
52
|
+
* The client file imports a subset of this inventory (`clientImportedNames`);
|
|
53
|
+
* the asymmetry is deliberate: e.g. 204-only operations emit
|
|
54
|
+
* `{prefix}Response = void` which the client does not import.
|
|
55
|
+
*/
|
|
56
|
+
export interface OperationEmissions {
|
|
57
|
+
query?: string;
|
|
58
|
+
headers?: string;
|
|
59
|
+
body?: string;
|
|
60
|
+
response?: string;
|
|
61
|
+
statusErrors: StatusErrorName[];
|
|
62
|
+
defaultError?: string;
|
|
63
|
+
errorsUnion?: string;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Derive the emission inventory for an operation. Pure function of the
|
|
67
|
+
* AnalyzedOperation — no mapper or resolver state participates in naming
|
|
68
|
+
* or presence decisions.
|
|
69
|
+
*/
|
|
70
|
+
export declare function operationEmissions(op: AnalyzedOperation): OperationEmissions;
|
|
71
|
+
/**
|
|
72
|
+
* Type names the client file imports from contracts for one operation.
|
|
73
|
+
*
|
|
74
|
+
* Deliberately a subset of the emission inventory: the client names the
|
|
75
|
+
* response type only when the success type is a named type — 204-only
|
|
76
|
+
* operations emit `{prefix}Response = void` in contracts, but the client
|
|
77
|
+
* method returns `void` without ever naming the type.
|
|
78
|
+
*/
|
|
79
|
+
export declare function clientImportedNames(op: AnalyzedOperation): string[];
|
|
80
|
+
/**
|
|
81
|
+
* Value imports for the client file: the fixed base list plus
|
|
82
|
+
* `DefaultApiError` when any operation has a default error response.
|
|
83
|
+
*/
|
|
84
|
+
export declare function clientValueImports(operations: AnalyzedOperation[]): string[];
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { sanitizeTypeName, toPascalCase } from './generator-helpers.js';
|
|
2
|
+
/**
|
|
3
|
+
* Canonical runtime class list in the contracts re-export order. Membership
|
|
4
|
+
* source of truth for every runtime class name the generators emit.
|
|
5
|
+
*/
|
|
6
|
+
export const RUNTIME_CLASS_NAMES = [
|
|
7
|
+
'ApiError',
|
|
8
|
+
'UnspecifiedApiError',
|
|
9
|
+
'DefaultApiError',
|
|
10
|
+
'RequesterFailError',
|
|
11
|
+
'StreamResponse',
|
|
12
|
+
'ErrorResponse',
|
|
13
|
+
];
|
|
14
|
+
/**
|
|
15
|
+
* client.ts value imports from contracts.js in site emission order.
|
|
16
|
+
* `DefaultApiError` is conditional (only when an operation has a default
|
|
17
|
+
* error response), so it is appended by the client generator, not listed here.
|
|
18
|
+
*/
|
|
19
|
+
export const CLIENT_BASE_VALUE_IMPORTS = [
|
|
20
|
+
'ApiError',
|
|
21
|
+
'UnspecifiedApiError',
|
|
22
|
+
'ErrorResponse',
|
|
23
|
+
'StreamResponse',
|
|
24
|
+
'RequesterFailError',
|
|
25
|
+
];
|
|
26
|
+
/** Fixed client-file surface names (values and types client.ts declares). */
|
|
27
|
+
export const CLIENT_SURFACE_NAMES = [
|
|
28
|
+
'Requester',
|
|
29
|
+
'isDefinedError',
|
|
30
|
+
'decorateWithErrors',
|
|
31
|
+
'ApiClient',
|
|
32
|
+
'createClient',
|
|
33
|
+
];
|
|
34
|
+
/**
|
|
35
|
+
* Reserved type names used by the generated output's built-in classes,
|
|
36
|
+
* functions, and types. User-defined schema names that collide with these
|
|
37
|
+
* are automatically renamed with a suffix to prevent duplicate identifiers.
|
|
38
|
+
*
|
|
39
|
+
* Derived — never hand-edited: add new fixed names to the owning constant
|
|
40
|
+
* (`RUNTIME_CLASS_NAMES` / `CLIENT_SURFACE_NAMES`) or, for contracts-only
|
|
41
|
+
* interfaces like `FileInput`, extend the derivation below.
|
|
42
|
+
*/
|
|
43
|
+
export const RESERVED_TYPE_NAMES = new Set([
|
|
44
|
+
...RUNTIME_CLASS_NAMES,
|
|
45
|
+
...CLIENT_SURFACE_NAMES,
|
|
46
|
+
'FileInput',
|
|
47
|
+
]);
|
|
48
|
+
/**
|
|
49
|
+
* Build a PascalCase type-name prefix from an operation's method + path.
|
|
50
|
+
* get + /api/v1/products → "GetApiV1Products"
|
|
51
|
+
*
|
|
52
|
+
* Returns the deduped prefix assigned by analyzePaths when available
|
|
53
|
+
* (distinct routes folding to the same identifier get numbered suffixes),
|
|
54
|
+
* otherwise computes it from the operation directly.
|
|
55
|
+
*/
|
|
56
|
+
export function getOperationTypePrefix(op) {
|
|
57
|
+
if (op.typePrefix)
|
|
58
|
+
return op.typePrefix;
|
|
59
|
+
return computeOperationTypePrefix(op);
|
|
60
|
+
}
|
|
61
|
+
function computeOperationTypePrefix(op) {
|
|
62
|
+
const methodPascal = op.method.charAt(0).toUpperCase() + op.method.slice(1).toLowerCase();
|
|
63
|
+
const segments = op.path
|
|
64
|
+
.split('/')
|
|
65
|
+
.filter((s) => s.length > 0)
|
|
66
|
+
.map((s) => {
|
|
67
|
+
const cleaned = s.replace(/[{}]/g, '');
|
|
68
|
+
return sanitizeTypeName(toPascalCase(cleaned));
|
|
69
|
+
});
|
|
70
|
+
return methodPascal + segments.join('');
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Classify a response as void by its finished type text. The finished type
|
|
74
|
+
* is `'void'` exactly for schema-less, non-binary 2xx responses — the same
|
|
75
|
+
* condition the analyzer records as `isVoid`.
|
|
76
|
+
*/
|
|
77
|
+
function isVoidResponse(r) {
|
|
78
|
+
return r.finishedType === 'void';
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Determine the success return type for an operation.
|
|
82
|
+
*/
|
|
83
|
+
export function getSuccessType(op) {
|
|
84
|
+
const successResponses = op.responses.filter((r) => r.isSuccess);
|
|
85
|
+
if (successResponses.length === 0) {
|
|
86
|
+
return 'unknown';
|
|
87
|
+
}
|
|
88
|
+
const noContent = successResponses.find((r) => isVoidResponse(r));
|
|
89
|
+
const hasOnlyNoContent = noContent && successResponses.every((r) => isVoidResponse(r));
|
|
90
|
+
if (hasOnlyNoContent) {
|
|
91
|
+
return 'void';
|
|
92
|
+
}
|
|
93
|
+
const withSchema = successResponses.filter((r) => !isVoidResponse(r));
|
|
94
|
+
if (withSchema.length === 0) {
|
|
95
|
+
return 'void';
|
|
96
|
+
}
|
|
97
|
+
const prefix = getOperationTypePrefix(op);
|
|
98
|
+
const types = withSchema.map(() => `${prefix}Response`);
|
|
99
|
+
const unique = [...new Set(types)];
|
|
100
|
+
return unique.join(' | ');
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Determine the error type name for an operation.
|
|
104
|
+
*/
|
|
105
|
+
export function getErrorType(op) {
|
|
106
|
+
const prefix = getOperationTypePrefix(op);
|
|
107
|
+
const errorResponses = op.responses.filter((r) => !r.isSuccess && r.statusCode !== 'default');
|
|
108
|
+
if (errorResponses.length === 0) {
|
|
109
|
+
return 'never';
|
|
110
|
+
}
|
|
111
|
+
return `${prefix}Errors`;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Derive the emission inventory for an operation. Pure function of the
|
|
115
|
+
* AnalyzedOperation — no mapper or resolver state participates in naming
|
|
116
|
+
* or presence decisions.
|
|
117
|
+
*/
|
|
118
|
+
export function operationEmissions(op) {
|
|
119
|
+
const prefix = getOperationTypePrefix(op);
|
|
120
|
+
const errorResponses = op.responses.filter((r) => !r.isSuccess && r.statusCode !== 'default');
|
|
121
|
+
const statusErrors = errorResponses.map((r) => ({
|
|
122
|
+
status: r.statusCode,
|
|
123
|
+
name: `${prefix}Error${r.statusCode}`,
|
|
124
|
+
}));
|
|
125
|
+
return {
|
|
126
|
+
query: op.queryParams.length > 0 ? `${prefix}Query` : undefined,
|
|
127
|
+
headers: op.headerParams.length > 0 ? `${prefix}Headers` : undefined,
|
|
128
|
+
body: op.requestBody?.hasSchema ? `${prefix}Body` : undefined,
|
|
129
|
+
response: op.responses.some((r) => r.isSuccess) ? `${prefix}Response` : undefined,
|
|
130
|
+
statusErrors,
|
|
131
|
+
defaultError: op.responses.some((r) => !r.isSuccess && r.statusCode === 'default')
|
|
132
|
+
? `${prefix}DefaultError`
|
|
133
|
+
: undefined,
|
|
134
|
+
errorsUnion: statusErrors.length > 0 ? `${prefix}Errors` : undefined,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Type names the client file imports from contracts for one operation.
|
|
139
|
+
*
|
|
140
|
+
* Deliberately a subset of the emission inventory: the client names the
|
|
141
|
+
* response type only when the success type is a named type — 204-only
|
|
142
|
+
* operations emit `{prefix}Response = void` in contracts, but the client
|
|
143
|
+
* method returns `void` without ever naming the type.
|
|
144
|
+
*/
|
|
145
|
+
export function clientImportedNames(op) {
|
|
146
|
+
const emissions = operationEmissions(op);
|
|
147
|
+
const names = [];
|
|
148
|
+
if (emissions.query !== undefined)
|
|
149
|
+
names.push(emissions.query);
|
|
150
|
+
if (emissions.headers !== undefined)
|
|
151
|
+
names.push(emissions.headers);
|
|
152
|
+
if (emissions.body !== undefined)
|
|
153
|
+
names.push(emissions.body);
|
|
154
|
+
if (emissions.response !== undefined) {
|
|
155
|
+
const successType = getSuccessType(op);
|
|
156
|
+
if (successType !== 'void' && successType !== 'unknown' && /^[A-Z]/.test(successType)) {
|
|
157
|
+
names.push(successType);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
for (const statusError of emissions.statusErrors) {
|
|
161
|
+
names.push(statusError.name);
|
|
162
|
+
}
|
|
163
|
+
if (emissions.errorsUnion !== undefined)
|
|
164
|
+
names.push(emissions.errorsUnion);
|
|
165
|
+
if (emissions.defaultError !== undefined)
|
|
166
|
+
names.push(emissions.defaultError);
|
|
167
|
+
return names;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Value imports for the client file: the fixed base list plus
|
|
171
|
+
* `DefaultApiError` when any operation has a default error response.
|
|
172
|
+
*/
|
|
173
|
+
export function clientValueImports(operations) {
|
|
174
|
+
const needsDefaultApiError = operations.some((op) => operationEmissions(op).defaultError !== undefined);
|
|
175
|
+
return needsDefaultApiError
|
|
176
|
+
? [...CLIENT_BASE_VALUE_IMPORTS, 'DefaultApiError']
|
|
177
|
+
: [...CLIENT_BASE_VALUE_IMPORTS];
|
|
178
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "genoc",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Generate typed HTTP clients from OpenAPI 3.0 / 3.1 specifications",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"client",
|
|
@@ -22,13 +22,7 @@
|
|
|
22
22
|
"dist"
|
|
23
23
|
],
|
|
24
24
|
"type": "module",
|
|
25
|
-
"main": "dist/index.js",
|
|
26
|
-
"types": "dist/index.d.ts",
|
|
27
25
|
"exports": {
|
|
28
|
-
".": {
|
|
29
|
-
"import": "./dist/index.js",
|
|
30
|
-
"types": "./dist/index.d.ts"
|
|
31
|
-
},
|
|
32
26
|
"./runtime": {
|
|
33
27
|
"import": "./dist/runtime/index.js",
|
|
34
28
|
"types": "./dist/runtime/index.d.ts"
|
|
@@ -48,8 +42,10 @@
|
|
|
48
42
|
},
|
|
49
43
|
"dependencies": {
|
|
50
44
|
"@stricli/core": "^1.2.6",
|
|
45
|
+
"lilconfig": "^3.1.3",
|
|
51
46
|
"undici": "^6.13.0",
|
|
52
|
-
"yaml": "^2.
|
|
47
|
+
"yaml": "^2.2.2",
|
|
48
|
+
"zod": "^4.0.0"
|
|
53
49
|
},
|
|
54
50
|
"devDependencies": {
|
|
55
51
|
"@changesets/cli": "^2.31.0",
|
package/dist/index.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { type ApiClient } from './generator/client-generator.js';
|
|
2
|
-
import type { GeneratorConfig } from './types/client.js';
|
|
3
|
-
import { type LoadOptions } from './utils/proxy.js';
|
|
4
|
-
export declare function generateClient(config: GeneratorConfig): Promise<void>;
|
|
5
|
-
export { ApiError, UnspecifiedApiError, DefaultApiError, RequesterFailError, StreamResponse, ErrorResponse, decorateWithErrors, isDefinedError, } from './runtime/index.js';
|
|
6
|
-
export type { Requester } from './runtime/index.js';
|
|
7
|
-
export type { GeneratorConfig, ApiClient, LoadOptions };
|
|
8
|
-
export type { GenerationOptions } from './generator/client-generator.js';
|
|
9
|
-
export { load as loadSpec } from './parser/spec-reader.js';
|
package/dist/index.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { generateFullOutput } from './generator/client-generator.js';
|
|
2
|
-
import { load } from './parser/spec-reader.js';
|
|
3
|
-
import { assertValidProxyUrl } from './utils/proxy.js';
|
|
4
|
-
export async function generateClient(config) {
|
|
5
|
-
if (config.proxy)
|
|
6
|
-
assertValidProxyUrl(config.proxy);
|
|
7
|
-
const doc = await load(config.input, { proxy: config.proxy });
|
|
8
|
-
await generateFullOutput(doc, config);
|
|
9
|
-
}
|
|
10
|
-
export { ApiError, UnspecifiedApiError, DefaultApiError, RequesterFailError, StreamResponse, ErrorResponse, decorateWithErrors, isDefinedError, } from './runtime/index.js';
|
|
11
|
-
export { load as loadSpec } from './parser/spec-reader.js';
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { VersionStrategy } from './version/version-strategy.js';
|
|
2
|
-
export interface ValidationResult {
|
|
3
|
-
valid: boolean;
|
|
4
|
-
errors: string[];
|
|
5
|
-
}
|
|
6
|
-
export declare function validateOpenAPIVersion(version: string): boolean;
|
|
7
|
-
export declare function validateSpec(doc: unknown, strategy?: VersionStrategy): ValidationResult;
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
export function validateOpenAPIVersion(version) {
|
|
2
|
-
return typeof version === 'string' && version.startsWith('3.1');
|
|
3
|
-
}
|
|
4
|
-
export function validateSpec(doc, strategy) {
|
|
5
|
-
if (strategy) {
|
|
6
|
-
try {
|
|
7
|
-
const normalized = strategy.normalizeSpec(doc);
|
|
8
|
-
return strategy.validateSpec(normalized);
|
|
9
|
-
}
|
|
10
|
-
catch (error) {
|
|
11
|
-
// normalizeSpec may throw for structurally invalid specs;
|
|
12
|
-
// but re-throw "not yet supported" errors
|
|
13
|
-
if (typeof error === 'object' && error !== null && 'message' in error) {
|
|
14
|
-
const errorMessage = error.message;
|
|
15
|
-
if (errorMessage.includes('not yet supported')) {
|
|
16
|
-
throw error;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
// fall through to default 3.1-only validation below
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
const errors = [];
|
|
23
|
-
if (!doc || typeof doc !== 'object' || Array.isArray(doc)) {
|
|
24
|
-
errors.push('Document must be an object');
|
|
25
|
-
return { valid: false, errors };
|
|
26
|
-
}
|
|
27
|
-
const spec = doc;
|
|
28
|
-
// Check openapi field
|
|
29
|
-
if (!spec.openapi || typeof spec.openapi !== 'string') {
|
|
30
|
-
errors.push("OpenAPI specification must have an 'openapi' field with string value");
|
|
31
|
-
}
|
|
32
|
-
else if (!validateOpenAPIVersion(spec.openapi)) {
|
|
33
|
-
errors.push(`OpenAPI version must start with '3.1', got: ${spec.openapi}`);
|
|
34
|
-
}
|
|
35
|
-
// Check info field
|
|
36
|
-
if (!spec.info || typeof spec.info !== 'object' || Array.isArray(spec.info)) {
|
|
37
|
-
errors.push("OpenAPI specification must have an 'info' field with object value");
|
|
38
|
-
}
|
|
39
|
-
else {
|
|
40
|
-
const info = spec.info;
|
|
41
|
-
if (!info.title || typeof info.title !== 'string') {
|
|
42
|
-
errors.push("Info object must have a 'title' field with string value");
|
|
43
|
-
}
|
|
44
|
-
if (!info.version || typeof info.version !== 'string') {
|
|
45
|
-
errors.push("Info object must have a 'version' field with string value");
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
// Check at least one of paths, components, webhooks exists
|
|
49
|
-
if (!spec.paths && !spec.components && !spec.webhooks) {
|
|
50
|
-
errors.push("OpenAPI specification must have at least one of 'paths', 'components', or 'webhooks'");
|
|
51
|
-
}
|
|
52
|
-
// Validate paths if it exists
|
|
53
|
-
if (spec.paths) {
|
|
54
|
-
if (Array.isArray(spec.paths) || typeof spec.paths !== 'object') {
|
|
55
|
-
errors.push("'paths' field must be an object");
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
// Validate components.schemas if it exists
|
|
59
|
-
if (spec.components && typeof spec.components === 'object' && !Array.isArray(spec.components)) {
|
|
60
|
-
const components = spec.components;
|
|
61
|
-
if (components.schemas) {
|
|
62
|
-
if (Array.isArray(components.schemas) || typeof components.schemas !== 'object') {
|
|
63
|
-
errors.push("'components.schemas' must be an object");
|
|
64
|
-
}
|
|
65
|
-
else {
|
|
66
|
-
const schemas = components.schemas;
|
|
67
|
-
for (const [key, schema] of Object.entries(schemas)) {
|
|
68
|
-
if (typeof schema !== 'object' || Array.isArray(schema)) {
|
|
69
|
-
errors.push(`Schema '${key}' must be an object`);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
return {
|
|
76
|
-
valid: errors.length === 0,
|
|
77
|
-
errors,
|
|
78
|
-
};
|
|
79
|
-
}
|