genoc 0.3.0 → 0.4.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.
Files changed (71) hide show
  1. package/README.md +31 -187
  2. package/dist/analyzer/analyze.d.ts +18 -0
  3. package/dist/analyzer/analyze.js +493 -0
  4. package/dist/analyzer/discriminator-registry.d.ts +108 -0
  5. package/dist/analyzer/discriminator-registry.js +209 -0
  6. package/dist/analyzer/path-analyzer.d.ts +43 -6
  7. package/dist/analyzer/path-analyzer.js +57 -84
  8. package/dist/analyzer/schema-mapper.d.ts +68 -6
  9. package/dist/analyzer/schema-mapper.js +299 -40
  10. package/dist/analyzer/types.d.ts +126 -0
  11. package/dist/cli/app.d.ts +4 -2
  12. package/dist/cli/app.js +23 -4
  13. package/dist/cli/config-loader.d.ts +20 -0
  14. package/dist/cli/config-loader.js +186 -0
  15. package/dist/cli/config-schema.d.ts +105 -0
  16. package/dist/cli/config-schema.js +183 -0
  17. package/dist/cli/impl.d.ts +9 -1
  18. package/dist/cli/impl.js +189 -36
  19. package/dist/generator/client-generator.d.ts +13 -14
  20. package/dist/generator/client-generator.js +45 -77
  21. package/dist/generator/contracts-generator.d.ts +11 -9
  22. package/dist/generator/contracts-generator.js +45 -491
  23. package/dist/generator/method-generator.d.ts +1 -1
  24. package/dist/generator/method-generator.js +10 -150
  25. package/dist/parser/ref-resolver.d.ts +1 -1
  26. package/dist/parser/ref-resolver.js +1 -2
  27. package/dist/parser/version/index.d.ts +4 -16
  28. package/dist/parser/version/index.js +3 -14
  29. package/dist/parser/version/v3.0/validator.d.ts +1 -1
  30. package/dist/parser/version/v3.1/validator.d.ts +17 -0
  31. package/dist/parser/version/v3.1/validator.js +126 -0
  32. package/dist/parser/version/validate.d.ts +9 -0
  33. package/dist/parser/version/validate.js +12 -0
  34. package/dist/parser/version/version-detector.d.ts +21 -0
  35. package/dist/parser/version/version-detector.js +19 -0
  36. package/dist/pipeline.d.ts +15 -0
  37. package/dist/pipeline.js +25 -0
  38. package/dist/types/client.d.ts +0 -12
  39. package/dist/utils/generator-helpers.d.ts +1 -24
  40. package/dist/utils/generator-helpers.js +11 -78
  41. package/dist/utils/operation-naming.d.ts +84 -0
  42. package/dist/utils/operation-naming.js +178 -0
  43. package/dist/utils/proxy.d.ts +2 -0
  44. package/dist/utils/proxy.js +16 -0
  45. package/dist/utils/string.d.ts +7 -0
  46. package/dist/utils/string.js +9 -0
  47. package/package.json +19 -15
  48. package/dist/index.d.ts +0 -9
  49. package/dist/index.js +0 -11
  50. package/dist/parser/validators.d.ts +0 -7
  51. package/dist/parser/validators.js +0 -79
  52. package/dist/parser/version/normalized-spec.d.ts +0 -199
  53. package/dist/parser/version/registry.d.ts +0 -28
  54. package/dist/parser/version/registry.js +0 -44
  55. package/dist/parser/version/v3.0/index.d.ts +0 -3
  56. package/dist/parser/version/v3.0/index.js +0 -3
  57. package/dist/parser/version/v3.0/normalizer.d.ts +0 -15
  58. package/dist/parser/version/v3.0/normalizer.js +0 -389
  59. package/dist/parser/version/v3.0/strategy.d.ts +0 -27
  60. package/dist/parser/version/v3.0/strategy.js +0 -96
  61. package/dist/parser/version/v3.1/index.d.ts +0 -1
  62. package/dist/parser/version/v3.1/index.js +0 -1
  63. package/dist/parser/version/v3.1/strategy.d.ts +0 -42
  64. package/dist/parser/version/v3.1/strategy.js +0 -513
  65. package/dist/parser/version/v3.2/index.d.ts +0 -4
  66. package/dist/parser/version/v3.2/index.js +0 -4
  67. package/dist/parser/version/v3.2/strategy.d.ts +0 -39
  68. package/dist/parser/version/v3.2/strategy.js +0 -57
  69. package/dist/parser/version/version-strategy.d.ts +0 -31
  70. package/dist/parser/version/version-strategy.js +0 -1
  71. /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
+ }
@@ -3,4 +3,6 @@ export type LoadOptions = {
3
3
  };
4
4
  export declare function assertValidProxyUrl(url: string): void;
5
5
  export declare function hasProxyEnv(): boolean;
6
+ /** True when the dynamic `import('undici')` failed because undici is not installed (Node: ERR_MODULE_NOT_FOUND; Bun: "Cannot find package" message). */
7
+ export declare function isMissingUndiciError(err: unknown): boolean;
6
8
  export declare function fetchSpec(url: string, opts?: LoadOptions): Promise<Response>;
@@ -48,6 +48,16 @@ export function hasProxyEnv() {
48
48
  process.env.HTTPS_PROXY ||
49
49
  process.env.https_proxy);
50
50
  }
51
+ /** True when the dynamic `import('undici')` failed because undici is not installed (Node: ERR_MODULE_NOT_FOUND; Bun: "Cannot find package" message). */
52
+ export function isMissingUndiciError(err) {
53
+ if (!(err instanceof Error))
54
+ return false;
55
+ const code = err.code;
56
+ if (code === 'ERR_MODULE_NOT_FOUND')
57
+ return true;
58
+ return /Cannot find (?:module|package) ['"]?undici/.test(err.message);
59
+ }
60
+ const MISSING_UNDICI_MESSAGE = "Proxy support requires the 'undici' package. Install it: npm install undici";
51
61
  export async function fetchSpec(url, opts) {
52
62
  if (opts?.proxy) {
53
63
  assertValidProxyUrl(opts.proxy);
@@ -59,6 +69,9 @@ export async function fetchSpec(url, opts) {
59
69
  }));
60
70
  }
61
71
  catch (err) {
72
+ if (isMissingUndiciError(err)) {
73
+ throw new Error(MISSING_UNDICI_MESSAGE);
74
+ }
62
75
  throw new Error(`Failed to fetch spec from URL via proxy ${redacted}: ${describeFetchError(err)}`, {
63
76
  cause: err,
64
77
  });
@@ -72,6 +85,9 @@ export async function fetchSpec(url, opts) {
72
85
  }));
73
86
  }
74
87
  catch (err) {
88
+ if (isMissingUndiciError(err)) {
89
+ throw new Error(MISSING_UNDICI_MESSAGE);
90
+ }
75
91
  throw new Error(`Failed to fetch spec from URL via proxy env: ${describeFetchError(err)}`, {
76
92
  cause: err,
77
93
  });
@@ -26,6 +26,13 @@ export declare function isReservedWord(str: string): boolean;
26
26
  * @returns Safe TypeScript identifier
27
27
  */
28
28
  export declare function sanitizeIdentifier(str: string): string;
29
+ /**
30
+ * Escape a value for interpolation inside a TypeScript single-quoted string
31
+ * literal (backslash first, then the quote itself)
32
+ * @param value The raw string value
33
+ * @returns The value with `\` and `'` escaped
34
+ */
35
+ export declare function escapeStringLiteral(value: string): string;
29
36
  /**
30
37
  * Quote a key if needed for TypeScript object literals
31
38
  * @param key The key to potentially quote
@@ -163,6 +163,15 @@ export function sanitizeIdentifier(str) {
163
163
  }
164
164
  return final;
165
165
  }
166
+ /**
167
+ * Escape a value for interpolation inside a TypeScript single-quoted string
168
+ * literal (backslash first, then the quote itself)
169
+ * @param value The raw string value
170
+ * @returns The value with `\` and `'` escaped
171
+ */
172
+ export function escapeStringLiteral(value) {
173
+ return value.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
174
+ }
166
175
  /**
167
176
  * Quote a key if needed for TypeScript object literals
168
177
  * @param key The key to potentially quote
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genoc",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
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,18 +42,28 @@
48
42
  },
49
43
  "dependencies": {
50
44
  "@stricli/core": "^1.2.6",
51
- "undici": "^6.13.0",
52
- "yaml": "^2.8.4"
45
+ "lilconfig": "^3.1.3",
46
+ "yaml": "^2.2.2",
47
+ "zod": "^4.0.0"
53
48
  },
54
49
  "devDependencies": {
55
- "@changesets/cli": "^2.31.0",
56
- "@types/node": "^25.6.0",
57
- "oxfmt": "^0.48.0",
58
- "oxlint": "^1.63.0",
50
+ "@changesets/cli": "^3.0.3",
51
+ "@types/node": "^26.6.2",
52
+ "oxfmt": "^0.70.0",
53
+ "oxlint": "^1.85.0",
59
54
  "typescript": "^6.0.3",
60
- "vitest": "^4.1.5"
55
+ "undici": "^8",
56
+ "vitest": "^5.0.1"
57
+ },
58
+ "peerDependencies": {
59
+ "undici": "^6.13.0 || ^7 || ^8"
60
+ },
61
+ "peerDependenciesMeta": {
62
+ "undici": {
63
+ "optional": true
64
+ }
61
65
  },
62
66
  "engines": {
63
- "node": ">=18"
67
+ "node": ">=20.18.1"
64
68
  }
65
69
  }
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
- }