genoc 0.1.3 → 0.3.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 +79 -26
- package/dist/analyzer/naming.js +3 -2
- package/dist/analyzer/path-analyzer.d.ts +4 -0
- package/dist/analyzer/path-analyzer.js +31 -1
- package/dist/analyzer/schema-mapper.d.ts +8 -0
- package/dist/analyzer/schema-mapper.js +69 -30
- package/dist/cli/app.d.ts +2 -0
- package/dist/cli/app.js +14 -0
- package/dist/cli/impl.js +11 -1
- package/dist/generator/client-generator.js +9 -44
- package/dist/generator/contracts-generator.d.ts +2 -3
- package/dist/generator/contracts-generator.js +141 -140
- package/dist/generator/method-generator.js +11 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +5 -2
- package/dist/parser/spec-reader.d.ts +3 -2
- package/dist/parser/spec-reader.js +5 -4
- package/dist/runtime/defined-errors.d.ts +17 -0
- package/dist/runtime/defined-errors.js +27 -0
- package/dist/runtime/errors.d.ts +28 -0
- package/dist/runtime/errors.js +45 -0
- package/dist/runtime/index.d.ts +11 -0
- package/dist/runtime/index.js +10 -0
- package/dist/runtime/requester.d.ts +14 -0
- package/dist/runtime/requester.js +1 -0
- package/dist/runtime/responses.d.ts +19 -0
- package/dist/runtime/responses.js +28 -0
- package/dist/types/client.d.ts +2 -0
- package/dist/types/openapi.d.ts +4 -0
- package/dist/utils/generator-helpers.d.ts +60 -5
- package/dist/utils/generator-helpers.js +223 -14
- package/dist/utils/proxy.d.ts +6 -0
- package/dist/utils/proxy.js +81 -0
- package/package.json +7 -2
- package/dist/generator/error-types.d.ts +0 -23
- package/dist/generator/error-types.js +0 -87
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
const SUPPORTED_PROXY_PROTOCOLS = new Set(['http:', 'https:']);
|
|
2
|
+
/** Security: never echo credentials — user:pass@ becomes ***@ in any echoed value. */
|
|
3
|
+
function redactCredentials(proxyUrl) {
|
|
4
|
+
try {
|
|
5
|
+
const url = new URL(proxyUrl);
|
|
6
|
+
if (url.username || url.password) {
|
|
7
|
+
url.username = '***';
|
|
8
|
+
url.password = '';
|
|
9
|
+
}
|
|
10
|
+
return url.toString();
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return proxyUrl;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
const MAX_CAUSE_DEPTH = 5;
|
|
17
|
+
/**
|
|
18
|
+
* undici's fetch() nests the meaningful error up to 3 levels deep via `cause`
|
|
19
|
+
* (e.g. "fetch failed" → "Request was cancelled." → "Proxy response (502) !== 200...").
|
|
20
|
+
* Flatten the chain so the deepest message (proxy status, ECONNREFUSED, ...) is user-visible.
|
|
21
|
+
*/
|
|
22
|
+
function describeFetchError(err) {
|
|
23
|
+
const messages = [];
|
|
24
|
+
let current = err;
|
|
25
|
+
for (let depth = 0; depth <= MAX_CAUSE_DEPTH && current instanceof Error; depth++) {
|
|
26
|
+
if (!messages.includes(current.message)) {
|
|
27
|
+
messages.push(current.message);
|
|
28
|
+
}
|
|
29
|
+
current = current.cause;
|
|
30
|
+
}
|
|
31
|
+
return messages.join(': ');
|
|
32
|
+
}
|
|
33
|
+
export function assertValidProxyUrl(url) {
|
|
34
|
+
let parsed;
|
|
35
|
+
try {
|
|
36
|
+
parsed = new URL(url);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
throw new Error(`Invalid proxy URL "${redactCredentials(url)}": only http:// and https:// proxies are supported`);
|
|
40
|
+
}
|
|
41
|
+
if (!SUPPORTED_PROXY_PROTOCOLS.has(parsed.protocol)) {
|
|
42
|
+
throw new Error(`Invalid proxy URL "${redactCredentials(url)}": only http:// and https:// proxies are supported (got scheme "${parsed.protocol.replace(/:$/, '')}:")`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export function hasProxyEnv() {
|
|
46
|
+
return Boolean(process.env.HTTP_PROXY ||
|
|
47
|
+
process.env.http_proxy ||
|
|
48
|
+
process.env.HTTPS_PROXY ||
|
|
49
|
+
process.env.https_proxy);
|
|
50
|
+
}
|
|
51
|
+
export async function fetchSpec(url, opts) {
|
|
52
|
+
if (opts?.proxy) {
|
|
53
|
+
assertValidProxyUrl(opts.proxy);
|
|
54
|
+
const redacted = redactCredentials(opts.proxy);
|
|
55
|
+
try {
|
|
56
|
+
const { fetch: undiciFetch, ProxyAgent } = await import('undici');
|
|
57
|
+
return (await undiciFetch(url, {
|
|
58
|
+
dispatcher: new ProxyAgent(opts.proxy),
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
throw new Error(`Failed to fetch spec from URL via proxy ${redacted}: ${describeFetchError(err)}`, {
|
|
63
|
+
cause: err,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (hasProxyEnv()) {
|
|
68
|
+
try {
|
|
69
|
+
const { fetch: undiciFetch, EnvHttpProxyAgent } = await import('undici');
|
|
70
|
+
return (await undiciFetch(url, {
|
|
71
|
+
dispatcher: new EnvHttpProxyAgent(),
|
|
72
|
+
}));
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
throw new Error(`Failed to fetch spec from URL via proxy env: ${describeFetchError(err)}`, {
|
|
76
|
+
cause: err,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return fetch(url);
|
|
81
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "genoc",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Generate typed HTTP clients from OpenAPI 3.0 / 3.1 specifications",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"client",
|
|
@@ -28,6 +28,10 @@
|
|
|
28
28
|
".": {
|
|
29
29
|
"import": "./dist/index.js",
|
|
30
30
|
"types": "./dist/index.d.ts"
|
|
31
|
+
},
|
|
32
|
+
"./runtime": {
|
|
33
|
+
"import": "./dist/runtime/index.js",
|
|
34
|
+
"types": "./dist/runtime/index.d.ts"
|
|
31
35
|
}
|
|
32
36
|
},
|
|
33
37
|
"scripts": {
|
|
@@ -39,11 +43,12 @@
|
|
|
39
43
|
"format": "oxfmt --write",
|
|
40
44
|
"format:check": "oxfmt --check",
|
|
41
45
|
"changeset": "changeset",
|
|
42
|
-
"version-packages": "changeset version",
|
|
46
|
+
"version-packages": "changeset version && oxfmt --write",
|
|
43
47
|
"release": "bun run build && changeset publish"
|
|
44
48
|
},
|
|
45
49
|
"dependencies": {
|
|
46
50
|
"@stricli/core": "^1.2.6",
|
|
51
|
+
"undici": "^6.13.0",
|
|
47
52
|
"yaml": "^2.8.4"
|
|
48
53
|
},
|
|
49
54
|
"devDependencies": {
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import type { AnalyzedOperation } from '../analyzer/path-analyzer.js';
|
|
2
|
-
export declare class ApiError<TStatus extends number, TData> extends Error {
|
|
3
|
-
readonly status: TStatus;
|
|
4
|
-
readonly data: TData;
|
|
5
|
-
constructor(status: TStatus, data: TData, message: string);
|
|
6
|
-
}
|
|
7
|
-
export declare class DefaultApiError<TData> extends Error {
|
|
8
|
-
readonly status: number;
|
|
9
|
-
readonly data: TData;
|
|
10
|
-
constructor(status: number, data: TData, message: string);
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* Generate error types for a set of analyzed operations.
|
|
14
|
-
*
|
|
15
|
-
* Produces:
|
|
16
|
-
* 1. `ApiError<TStatus, TData>` class
|
|
17
|
-
* 2. `UnspecifiedApiError` class (extends ApiError, for status codes not in spec)
|
|
18
|
-
* 3. Per-operation error union types (e.g. `GetApiV1ProductsErrors`)
|
|
19
|
-
* 4. Per-operation per-status error type aliases (e.g. `GetApiV1ProductsError400`)
|
|
20
|
-
* 5. `DefaultErrorBody` type for `default` responses
|
|
21
|
-
* 6. Catch-all `UnspecifiedApiError` in error unions for unexpected status codes
|
|
22
|
-
*/
|
|
23
|
-
export declare function generateErrorTypes(operations: AnalyzedOperation[]): string;
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
export class ApiError extends Error {
|
|
2
|
-
status;
|
|
3
|
-
data;
|
|
4
|
-
constructor(status, data, message) {
|
|
5
|
-
super(message);
|
|
6
|
-
this.status = status;
|
|
7
|
-
this.data = data;
|
|
8
|
-
this.name = 'ApiError';
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
export class DefaultApiError extends Error {
|
|
12
|
-
status;
|
|
13
|
-
data;
|
|
14
|
-
constructor(status, data, message) {
|
|
15
|
-
super(message);
|
|
16
|
-
this.status = status;
|
|
17
|
-
this.data = data;
|
|
18
|
-
this.name = 'DefaultApiError';
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Generate error types for a set of analyzed operations.
|
|
23
|
-
*
|
|
24
|
-
* Produces:
|
|
25
|
-
* 1. `ApiError<TStatus, TData>` class
|
|
26
|
-
* 2. `UnspecifiedApiError` class (extends ApiError, for status codes not in spec)
|
|
27
|
-
* 3. Per-operation error union types (e.g. `GetApiV1ProductsErrors`)
|
|
28
|
-
* 4. Per-operation per-status error type aliases (e.g. `GetApiV1ProductsError400`)
|
|
29
|
-
* 5. `DefaultErrorBody` type for `default` responses
|
|
30
|
-
* 6. Catch-all `UnspecifiedApiError` in error unions for unexpected status codes
|
|
31
|
-
*/
|
|
32
|
-
export function generateErrorTypes(operations) {
|
|
33
|
-
const lines = [];
|
|
34
|
-
lines.push(`export class ApiError<TStatus extends number, TData> extends Error {
|
|
35
|
-
constructor(
|
|
36
|
-
public readonly status: TStatus,
|
|
37
|
-
public readonly data: TData,
|
|
38
|
-
message: string,
|
|
39
|
-
) {
|
|
40
|
-
super(message);
|
|
41
|
-
this.name = "ApiError";
|
|
42
|
-
}
|
|
43
|
-
}`);
|
|
44
|
-
lines.push('');
|
|
45
|
-
lines.push(`export class UnspecifiedApiError extends ApiError<number, unknown> {
|
|
46
|
-
constructor(
|
|
47
|
-
status: number,
|
|
48
|
-
data: unknown,
|
|
49
|
-
message: string,
|
|
50
|
-
) {
|
|
51
|
-
super(status, data, message);
|
|
52
|
-
this.name = "UnspecifiedApiError";
|
|
53
|
-
}
|
|
54
|
-
}`);
|
|
55
|
-
lines.push('');
|
|
56
|
-
let needsDefaultErrorBody = false;
|
|
57
|
-
for (const op of operations) {
|
|
58
|
-
const errorResponses = op.responses.filter((r) => !r.isSuccess && r.statusCode !== 'default');
|
|
59
|
-
const defaultResponse = op.responses.find((r) => !r.isSuccess && r.statusCode === 'default');
|
|
60
|
-
if (errorResponses.length === 0 && !defaultResponse) {
|
|
61
|
-
continue;
|
|
62
|
-
}
|
|
63
|
-
const methodName = op.methodName;
|
|
64
|
-
const errorTypeName = (status) => `${methodName}Error${status}`;
|
|
65
|
-
lines.push('');
|
|
66
|
-
for (const err of errorResponses) {
|
|
67
|
-
const tsType = err.tsType || 'unknown';
|
|
68
|
-
lines.push(`export type ${errorTypeName(err.statusCode)} = ${tsType};`);
|
|
69
|
-
}
|
|
70
|
-
const unionParts = [];
|
|
71
|
-
for (const err of errorResponses) {
|
|
72
|
-
const status = Number(err.statusCode);
|
|
73
|
-
unionParts.push(`ApiError<${status}, ${errorTypeName(err.statusCode)}>`);
|
|
74
|
-
}
|
|
75
|
-
if (defaultResponse) {
|
|
76
|
-
needsDefaultErrorBody = true;
|
|
77
|
-
unionParts.push(`ApiError<number, DefaultErrorBody>`);
|
|
78
|
-
}
|
|
79
|
-
unionParts.push(`UnspecifiedApiError`);
|
|
80
|
-
lines.push(`export type ${methodName}Errors = ${unionParts.join('\n | ')};`);
|
|
81
|
-
}
|
|
82
|
-
if (needsDefaultErrorBody) {
|
|
83
|
-
lines.push('');
|
|
84
|
-
lines.push('export type DefaultErrorBody = unknown;');
|
|
85
|
-
}
|
|
86
|
-
return lines.join('\n');
|
|
87
|
-
}
|