genoc 0.2.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.
Files changed (72) hide show
  1. package/README.md +30 -163
  2. package/dist/analyzer/analyze.d.ts +18 -0
  3. package/dist/analyzer/analyze.js +534 -0
  4. package/dist/analyzer/naming.js +3 -2
  5. package/dist/analyzer/path-analyzer.d.ts +45 -6
  6. package/dist/analyzer/path-analyzer.js +82 -81
  7. package/dist/analyzer/schema-mapper.d.ts +3 -2
  8. package/dist/analyzer/schema-mapper.js +16 -8
  9. package/dist/analyzer/types.d.ts +119 -0
  10. package/dist/cli/app.d.ts +5 -2
  11. package/dist/cli/app.js +30 -4
  12. package/dist/cli/config-loader.d.ts +20 -0
  13. package/dist/cli/config-loader.js +186 -0
  14. package/dist/cli/config-schema.d.ts +105 -0
  15. package/dist/cli/config-schema.js +183 -0
  16. package/dist/cli/impl.d.ts +9 -1
  17. package/dist/cli/impl.js +190 -31
  18. package/dist/generator/client-generator.d.ts +13 -14
  19. package/dist/generator/client-generator.js +45 -77
  20. package/dist/generator/contracts-generator.d.ts +11 -9
  21. package/dist/generator/contracts-generator.js +45 -482
  22. package/dist/generator/method-generator.d.ts +1 -1
  23. package/dist/generator/method-generator.js +10 -150
  24. package/dist/parser/ref-resolver.d.ts +1 -1
  25. package/dist/parser/ref-resolver.js +1 -2
  26. package/dist/parser/spec-reader.d.ts +3 -2
  27. package/dist/parser/spec-reader.js +5 -4
  28. package/dist/parser/version/index.d.ts +4 -16
  29. package/dist/parser/version/index.js +3 -14
  30. package/dist/parser/version/v3.0/validator.d.ts +1 -1
  31. package/dist/parser/version/v3.1/validator.d.ts +17 -0
  32. package/dist/parser/version/v3.1/validator.js +126 -0
  33. package/dist/parser/version/validate.d.ts +9 -0
  34. package/dist/parser/version/validate.js +12 -0
  35. package/dist/parser/version/version-detector.d.ts +21 -0
  36. package/dist/parser/version/version-detector.js +19 -0
  37. package/dist/pipeline.d.ts +13 -0
  38. package/dist/pipeline.js +24 -0
  39. package/dist/types/client.d.ts +1 -12
  40. package/dist/utils/generator-helpers.d.ts +23 -25
  41. package/dist/utils/generator-helpers.js +144 -76
  42. package/dist/utils/operation-naming.d.ts +84 -0
  43. package/dist/utils/operation-naming.js +178 -0
  44. package/dist/utils/proxy.d.ts +6 -0
  45. package/dist/utils/proxy.js +81 -0
  46. package/package.json +5 -8
  47. package/dist/generator/error-types.d.ts +0 -13
  48. package/dist/generator/error-types.js +0 -67
  49. package/dist/index.d.ts +0 -8
  50. package/dist/index.js +0 -8
  51. package/dist/parser/validators.d.ts +0 -7
  52. package/dist/parser/validators.js +0 -79
  53. package/dist/parser/version/normalized-spec.d.ts +0 -199
  54. package/dist/parser/version/registry.d.ts +0 -28
  55. package/dist/parser/version/registry.js +0 -44
  56. package/dist/parser/version/v3.0/index.d.ts +0 -3
  57. package/dist/parser/version/v3.0/index.js +0 -3
  58. package/dist/parser/version/v3.0/normalizer.d.ts +0 -15
  59. package/dist/parser/version/v3.0/normalizer.js +0 -389
  60. package/dist/parser/version/v3.0/strategy.d.ts +0 -27
  61. package/dist/parser/version/v3.0/strategy.js +0 -96
  62. package/dist/parser/version/v3.1/index.d.ts +0 -1
  63. package/dist/parser/version/v3.1/index.js +0 -1
  64. package/dist/parser/version/v3.1/strategy.d.ts +0 -42
  65. package/dist/parser/version/v3.1/strategy.js +0 -513
  66. package/dist/parser/version/v3.2/index.d.ts +0 -4
  67. package/dist/parser/version/v3.2/index.js +0 -4
  68. package/dist/parser/version/v3.2/strategy.d.ts +0 -39
  69. package/dist/parser/version/v3.2/strategy.js +0 -57
  70. package/dist/parser/version/version-strategy.d.ts +0 -31
  71. package/dist/parser/version/version-strategy.js +0 -1
  72. /package/dist/{parser/version/normalized-spec.js → analyzer/types.js} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genoc",
3
- "version": "0.2.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,7 +42,10 @@
48
42
  },
49
43
  "dependencies": {
50
44
  "@stricli/core": "^1.2.6",
51
- "yaml": "^2.8.4"
45
+ "lilconfig": "^3.1.3",
46
+ "undici": "^6.13.0",
47
+ "yaml": "^2.2.2",
48
+ "zod": "^4.0.0"
52
49
  },
53
50
  "devDependencies": {
54
51
  "@changesets/cli": "^2.31.0",
@@ -1,13 +0,0 @@
1
- import type { AnalyzedOperation } from '../analyzer/path-analyzer.js';
2
- /**
3
- * Generate error types for a set of analyzed operations.
4
- *
5
- * Produces:
6
- * 1. `ApiError<TStatus, TData>` class
7
- * 2. `UnspecifiedApiError` class (extends ApiError, for status codes not in spec)
8
- * 3. Per-operation error union types (e.g. `GetApiV1ProductsErrors`)
9
- * 4. Per-operation per-status error type aliases (e.g. `GetApiV1ProductsError400`)
10
- * 5. `DefaultErrorBody` type for `default` responses
11
- * 6. Catch-all `UnspecifiedApiError` in error unions for unexpected status codes
12
- */
13
- export declare function generateErrorTypes(operations: AnalyzedOperation[]): string;
@@ -1,67 +0,0 @@
1
- /**
2
- * Generate error types for a set of analyzed operations.
3
- *
4
- * Produces:
5
- * 1. `ApiError<TStatus, TData>` class
6
- * 2. `UnspecifiedApiError` class (extends ApiError, for status codes not in spec)
7
- * 3. Per-operation error union types (e.g. `GetApiV1ProductsErrors`)
8
- * 4. Per-operation per-status error type aliases (e.g. `GetApiV1ProductsError400`)
9
- * 5. `DefaultErrorBody` type for `default` responses
10
- * 6. Catch-all `UnspecifiedApiError` in error unions for unexpected status codes
11
- */
12
- export function generateErrorTypes(operations) {
13
- const lines = [];
14
- lines.push(`export class ApiError<TStatus extends number, TData> extends Error {
15
- constructor(
16
- public readonly status: TStatus,
17
- public readonly data: TData,
18
- message: string,
19
- ) {
20
- super(message);
21
- this.name = "ApiError";
22
- }
23
- }`);
24
- lines.push('');
25
- lines.push(`export class UnspecifiedApiError extends ApiError<number, unknown> {
26
- constructor(
27
- status: number,
28
- data: unknown,
29
- message: string,
30
- ) {
31
- super(status, data, message);
32
- this.name = "UnspecifiedApiError";
33
- }
34
- }`);
35
- lines.push('');
36
- let needsDefaultErrorBody = false;
37
- for (const op of operations) {
38
- const errorResponses = op.responses.filter((r) => !r.isSuccess && r.statusCode !== 'default');
39
- const defaultResponse = op.responses.find((r) => !r.isSuccess && r.statusCode === 'default');
40
- if (errorResponses.length === 0 && !defaultResponse) {
41
- continue;
42
- }
43
- const methodName = op.methodName;
44
- const errorTypeName = (status) => `${methodName}Error${status}`;
45
- lines.push('');
46
- for (const err of errorResponses) {
47
- const tsType = err.tsType || 'unknown';
48
- lines.push(`export type ${errorTypeName(err.statusCode)} = ${tsType};`);
49
- }
50
- const unionParts = [];
51
- for (const err of errorResponses) {
52
- const status = Number(err.statusCode);
53
- unionParts.push(`ApiError<${status}, ${errorTypeName(err.statusCode)}>`);
54
- }
55
- if (defaultResponse) {
56
- needsDefaultErrorBody = true;
57
- unionParts.push(`ApiError<number, DefaultErrorBody>`);
58
- }
59
- unionParts.push(`UnspecifiedApiError`);
60
- lines.push(`export type ${methodName}Errors = ${unionParts.join('\n | ')};`);
61
- }
62
- if (needsDefaultErrorBody) {
63
- lines.push('');
64
- lines.push('export type DefaultErrorBody = unknown;');
65
- }
66
- return lines.join('\n');
67
- }
package/dist/index.d.ts DELETED
@@ -1,8 +0,0 @@
1
- import { type ApiClient } from './generator/client-generator.js';
2
- import type { GeneratorConfig } from './types/client.js';
3
- export declare function generateClient(config: GeneratorConfig): Promise<void>;
4
- export { ApiError, UnspecifiedApiError, DefaultApiError, RequesterFailError, StreamResponse, ErrorResponse, decorateWithErrors, isDefinedError, } from './runtime/index.js';
5
- export type { Requester } from './runtime/index.js';
6
- export type { GeneratorConfig, ApiClient };
7
- export type { GenerationOptions } from './generator/client-generator.js';
8
- export { load as loadSpec } from './parser/spec-reader.js';
package/dist/index.js DELETED
@@ -1,8 +0,0 @@
1
- import { generateFullOutput } from './generator/client-generator.js';
2
- import { load } from './parser/spec-reader.js';
3
- export async function generateClient(config) {
4
- const doc = await load(config.input);
5
- await generateFullOutput(doc, config);
6
- }
7
- export { ApiError, UnspecifiedApiError, DefaultApiError, RequesterFailError, StreamResponse, ErrorResponse, decorateWithErrors, isDefinedError, } from './runtime/index.js';
8
- 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
- }
@@ -1,199 +0,0 @@
1
- /**
2
- * Normalized schema with consistent types across OpenAPI versions
3
- */
4
- export interface NormalizedSchema {
5
- /** Normalized types - always an array, even for single types */
6
- types: string[];
7
- /** Format information */
8
- format?: string;
9
- /** Properties object */
10
- properties?: Record<string, NormalizedSchema>;
11
- /** Required properties array */
12
- required?: string[];
13
- /** Array items schema */
14
- items?: NormalizedSchema;
15
- /** Additional properties flag or schema */
16
- additionalProperties?: boolean | NormalizedSchema;
17
- /** Reference pointer */
18
- $ref?: string;
19
- /** AllOf composition */
20
- allOf?: NormalizedSchema[];
21
- /** OneOf composition */
22
- oneOf?: NormalizedSchema[];
23
- /** AnyOf composition */
24
- anyOf?: NormalizedSchema[];
25
- /** Enum values */
26
- enum?: unknown[];
27
- /** Const value */
28
- const?: unknown;
29
- /** Default value */
30
- default?: unknown;
31
- /** Description */
32
- description?: string;
33
- /** Whether nullable */
34
- nullable: boolean;
35
- /** Examples - always normalized to array */
36
- examples: unknown[];
37
- /** File upload information */
38
- fileUpload?: {
39
- binary: boolean;
40
- base64: boolean;
41
- };
42
- /** Exclusive minimum (always number) */
43
- exclusiveMinimum?: number;
44
- /** Exclusive maximum (always number) */
45
- exclusiveMaximum?: number;
46
- /** Minimum value */
47
- minimum?: number;
48
- /** Maximum value */
49
- maximum?: number;
50
- /** Read only */
51
- readOnly?: boolean;
52
- /** Write only */
53
- writeOnly?: boolean;
54
- /** Deprecated */
55
- deprecated?: boolean;
56
- /** Discriminator information */
57
- discriminator?: {
58
- propertyName: string;
59
- mapping?: Record<string, string>;
60
- };
61
- }
62
- /**
63
- * Normalized OpenAPI specification with consistent structure
64
- */
65
- export interface NormalizedSpec {
66
- /** Original OpenAPI version string */
67
- openapi: string;
68
- /** Info object */
69
- info: {
70
- title: string;
71
- version: string;
72
- description?: string;
73
- termsOfService?: string;
74
- contact?: {
75
- name?: string;
76
- url?: string;
77
- email?: string;
78
- };
79
- license?: {
80
- name: string;
81
- url?: string;
82
- identifier?: string;
83
- };
84
- };
85
- /** Servers array */
86
- servers?: Array<{
87
- url: string;
88
- description?: string;
89
- variables?: Record<string, {
90
- default: string;
91
- description?: string;
92
- enum?: string[];
93
- }>;
94
- }>;
95
- /** Paths object - simplified version */
96
- paths?: Record<string, {
97
- $ref?: string;
98
- summary?: string;
99
- description?: string;
100
- get?: NormalizedOperation;
101
- put?: NormalizedOperation;
102
- post?: NormalizedOperation;
103
- delete?: NormalizedOperation;
104
- options?: NormalizedOperation;
105
- head?: NormalizedOperation;
106
- patch?: NormalizedOperation;
107
- trace?: NormalizedOperation;
108
- parameters?: Array<NormalizedParameter>;
109
- servers?: unknown[];
110
- }>;
111
- /** Components object */
112
- components?: {
113
- schemas?: Record<string, NormalizedSchema>;
114
- responses?: Record<string, unknown>;
115
- parameters?: Record<string, unknown>;
116
- requestBodies?: Record<string, unknown>;
117
- headers?: Record<string, unknown>;
118
- securitySchemes?: Record<string, unknown>;
119
- links?: Record<string, unknown>;
120
- callbacks?: Record<string, unknown>;
121
- examples?: Record<string, unknown>;
122
- };
123
- /** Security requirements */
124
- security?: unknown[];
125
- /** Tags */
126
- tags?: unknown[];
127
- /** External documentation */
128
- externalDocs?: {
129
- description?: string;
130
- url: string;
131
- };
132
- /** Webhooks */
133
- webhooks?: Record<string, {
134
- $ref?: string;
135
- summary?: string;
136
- description?: string;
137
- get?: NormalizedOperation;
138
- put?: NormalizedOperation;
139
- post?: NormalizedOperation;
140
- delete?: NormalizedOperation;
141
- options?: NormalizedOperation;
142
- head?: NormalizedOperation;
143
- patch?: NormalizedOperation;
144
- trace?: NormalizedOperation;
145
- parameters?: Array<NormalizedParameter>;
146
- servers?: unknown[];
147
- }>;
148
- }
149
- /**
150
- * Normalized operation object
151
- */
152
- interface NormalizedOperation {
153
- tags?: string[];
154
- summary?: string;
155
- description?: string;
156
- operationId?: string;
157
- parameters?: Array<NormalizedParameter>;
158
- requestBody?: {
159
- description?: string;
160
- content: Record<string, {
161
- schema?: NormalizedSchema;
162
- examples?: Record<string, unknown>;
163
- example?: unknown;
164
- encoding?: Record<string, unknown>;
165
- }>;
166
- required?: boolean;
167
- };
168
- responses: Record<string, {
169
- description: string;
170
- headers?: Record<string, unknown>;
171
- content?: Record<string, {
172
- schema?: NormalizedSchema;
173
- examples?: Record<string, unknown>;
174
- example?: unknown;
175
- encoding?: Record<string, unknown>;
176
- }>;
177
- links?: Record<string, unknown>;
178
- }>;
179
- deprecated?: boolean;
180
- security?: unknown[];
181
- servers?: unknown[];
182
- }
183
- /**
184
- * Normalized parameter object
185
- */
186
- interface NormalizedParameter {
187
- name: string;
188
- in: 'query' | 'path' | 'header' | 'cookie';
189
- description?: string;
190
- required?: boolean;
191
- deprecated?: boolean;
192
- schema?: NormalizedSchema;
193
- style?: string;
194
- explode?: boolean;
195
- allowEmptyValue?: boolean;
196
- example?: unknown;
197
- examples?: Record<string, unknown>;
198
- }
199
- export {};
@@ -1,28 +0,0 @@
1
- import type { VersionStrategy } from './version-strategy.js';
2
- /**
3
- * Registry for managing version strategies
4
- */
5
- export declare class VersionStrategyRegistry {
6
- private strategies;
7
- /**
8
- * Register a version strategy for a specific OpenAPI version
9
- */
10
- register(strategy: VersionStrategy): void;
11
- /**
12
- * Get a version strategy by version string
13
- * @throws Error if strategy is not registered
14
- */
15
- get(version: string): VersionStrategy;
16
- /**
17
- * Detect the version from a raw OpenAPI specification and return the corresponding strategy
18
- */
19
- detectAndResolve(rawSpec: unknown): VersionStrategy;
20
- /**
21
- * Get list of supported version strings
22
- */
23
- listSupported(): string[];
24
- /**
25
- * Check if a version is supported
26
- */
27
- isSupported(version: string): boolean;
28
- }
@@ -1,44 +0,0 @@
1
- import { detectSpecVersion } from './version-detector.js';
2
- /**
3
- * Registry for managing version strategies
4
- */
5
- export class VersionStrategyRegistry {
6
- strategies = new Map();
7
- /**
8
- * Register a version strategy for a specific OpenAPI version
9
- */
10
- register(strategy) {
11
- const version = strategy.version();
12
- this.strategies.set(version, strategy);
13
- }
14
- /**
15
- * Get a version strategy by version string
16
- * @throws Error if strategy is not registered
17
- */
18
- get(version) {
19
- const strategy = this.strategies.get(version);
20
- if (!strategy) {
21
- throw new Error(`No strategy registered for version: ${version}`);
22
- }
23
- return strategy;
24
- }
25
- /**
26
- * Detect the version from a raw OpenAPI specification and return the corresponding strategy
27
- */
28
- detectAndResolve(rawSpec) {
29
- const version = detectSpecVersion(rawSpec);
30
- return this.get(version);
31
- }
32
- /**
33
- * Get list of supported version strings
34
- */
35
- listSupported() {
36
- return Array.from(this.strategies.keys());
37
- }
38
- /**
39
- * Check if a version is supported
40
- */
41
- isSupported(version) {
42
- return this.strategies.has(version);
43
- }
44
- }
@@ -1,3 +0,0 @@
1
- export { V3_0_VersionStrategy } from './strategy.js';
2
- export { normalizeSpec30 } from './normalizer.js';
3
- export { validateSpec30 } from './validator.js';
@@ -1,3 +0,0 @@
1
- export { V3_0_VersionStrategy } from './strategy.js';
2
- export { normalizeSpec30 } from './normalizer.js';
3
- export { validateSpec30 } from './validator.js';
@@ -1,15 +0,0 @@
1
- import type { NormalizedSpec } from '../normalized-spec.js';
2
- /**
3
- * Normalize a raw OpenAPI 3.0.x specification to a consistent format.
4
- *
5
- * 3.0-specific transformations applied during normalization:
6
- * - `nullable: true` → `nullable: true` on the NormalizedSchema
7
- * - `exclusiveMinimum: true` + `minimum: 5` → `exclusiveMinimum: 5`
8
- * - `exclusiveMinimum: false` → removed (not included in output)
9
- * - `example: value` → `examples: [value]`
10
- * - `format: "binary"` → `fileUpload: { binary: true, base64: false }`
11
- * - `format: "byte"` → `fileUpload: { binary: false, base64: true }`
12
- * - `$ref` siblings are stripped (3.0 compliant behavior)
13
- * - Error if `items` is an array (3.1-only tuple syntax)
14
- */
15
- export declare function normalizeSpec30(rawSpec: unknown): NormalizedSpec;