openapi-contract-kit 0.0.1 → 0.0.3
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/CHANGELOG.md +10 -0
- package/README.md +13 -5
- package/dist/bin/openapi-contract-kit.d.ts +2 -0
- package/dist/bin/openapi-contract-kit.js +3 -0
- package/dist/src/cli/formatOutput.d.ts +6 -0
- package/dist/src/cli/formatOutput.js +32 -0
- package/dist/src/generator/config.d.ts +3 -0
- package/dist/src/generator/config.js +72 -0
- package/dist/src/generator/documents.d.ts +11 -0
- package/dist/src/generator/documents.js +92 -0
- package/dist/src/generator/emitEndpoints.d.ts +2 -0
- package/{src/generator/emitEndpoints.mjs → dist/src/generator/emitEndpoints.js} +48 -80
- package/dist/src/generator/emitMakers.d.ts +2 -0
- package/dist/src/generator/emitMakers.js +328 -0
- package/dist/src/generator/emitTypes.d.ts +3 -0
- package/dist/src/generator/emitTypes.js +87 -0
- package/dist/src/generator/generateOpenApiRuntime.d.ts +3 -0
- package/dist/src/generator/generateOpenApiRuntime.js +41 -0
- package/dist/src/generator/model.d.ts +2 -0
- package/dist/src/generator/model.js +278 -0
- package/dist/src/generator/schemaModel.d.ts +9 -0
- package/dist/src/generator/schemaModel.js +408 -0
- package/dist/src/generator/types.d.ts +82 -0
- package/dist/src/generator/writeOutput.d.ts +5 -0
- package/dist/src/generator/writeOutput.js +141 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +1 -0
- package/dist/src/runtime.d.ts +14 -0
- package/dist/src/runtime.js +1 -0
- package/package.json +30 -18
- package/bin/openapi-contract-kit.mjs +0 -5
- package/src/generator/config.mjs +0 -92
- package/src/generator/documents.mjs +0 -119
- package/src/generator/emitMakers.mjs +0 -459
- package/src/generator/emitTypes.mjs +0 -104
- package/src/generator/generateOpenApiRuntime.mjs +0 -48
- package/src/generator/model.mjs +0 -415
- package/src/generator/schemaModel.mjs +0 -497
- package/src/generator/writeOutput.mjs +0 -196
- package/src/index.d.ts +0 -16
- package/src/index.mjs +0 -4
- package/src/runtime.d.ts +0 -14
- /package/{src/runtime.mjs → dist/src/generator/types.js} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.0.3] - 2026-09-12
|
|
6
|
+
|
|
7
|
+
- Fixed GitHub Release publishing from detached tag checkouts by disabling pnpm branch checks in the publish workflow.
|
|
8
|
+
|
|
9
|
+
## [0.0.2] - 2026-09-12
|
|
10
|
+
|
|
11
|
+
- Added linting to the development and CI workflows.
|
|
12
|
+
- Streamlined the test workflow around the build and unit-test commands.
|
|
13
|
+
- Updated the CI and publishing workflows for the current toolchain.
|
|
14
|
+
|
|
5
15
|
## [0.1.0] - 2026-09-11
|
|
6
16
|
|
|
7
17
|
- Initial public release.
|
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# openapi-contract-kit
|
|
2
2
|
|
|
3
|
+
[](https://github.com/Clinsmann/openapi-contract-kit/actions/workflows/ci.yml)
|
|
4
|
+
|
|
3
5
|
JSON-first OpenAPI 3.1 contract generation and runtime validation for TypeScript applications.
|
|
4
6
|
|
|
5
7
|
`openapi-contract-kit` reads a JSON OpenAPI document and generates:
|
|
@@ -14,9 +16,11 @@ JSON-first OpenAPI 3.1 contract generation and runtime validation for TypeScript
|
|
|
14
16
|
## Installation
|
|
15
17
|
|
|
16
18
|
```bash
|
|
17
|
-
|
|
19
|
+
pnpm add -D openapi-contract-kit
|
|
18
20
|
```
|
|
19
21
|
|
|
22
|
+
Requires Node.js 26 and pnpm 12.3.4.
|
|
23
|
+
|
|
20
24
|
## Quick start
|
|
21
25
|
|
|
22
26
|
Create `openapi.config.json`:
|
|
@@ -42,7 +46,7 @@ Add a generation script:
|
|
|
42
46
|
Generate contracts:
|
|
43
47
|
|
|
44
48
|
```bash
|
|
45
|
-
|
|
49
|
+
pnpm run openapi:generate
|
|
46
50
|
```
|
|
47
51
|
|
|
48
52
|
## Generated validation
|
|
@@ -59,6 +63,8 @@ if (!result.ok) {
|
|
|
59
63
|
|
|
60
64
|
Validation errors contain a path, keyword, and message. Successful validation returns the original input without mutating or cloning it.
|
|
61
65
|
|
|
66
|
+
The focused fixture and acceptance tests cover structural validation: object shape, declared fields, primitive types, arrays, nullability, references, unions, and additional-property behavior. They do not require form-level checks such as email format, string length, patterns, or numeric ranges. Those constraints remain available when declared in consumer schemas.
|
|
67
|
+
|
|
62
68
|
## Supported input
|
|
63
69
|
|
|
64
70
|
The generator supports JSON OpenAPI 3.1 documents with objects, primitive types, nullable values, arrays, enums, constants, unions, local `$ref` references, additional-property rules, string and numeric constraints, and email format validation.
|
|
@@ -74,9 +80,11 @@ The generator supports JSON OpenAPI 3.1 documents with objects, primitive types,
|
|
|
74
80
|
## Development
|
|
75
81
|
|
|
76
82
|
```bash
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
83
|
+
pnpm install
|
|
84
|
+
pnpm run lint
|
|
85
|
+
pnpm run typecheck
|
|
86
|
+
pnpm test
|
|
87
|
+
pnpm run pack:check
|
|
80
88
|
```
|
|
81
89
|
|
|
82
90
|
## Versioning
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { GenerateOpenApiRuntimeResult } from '../generator/types.js';
|
|
2
|
+
export type CliOutputStream = {
|
|
3
|
+
readonly isTTY?: boolean;
|
|
4
|
+
};
|
|
5
|
+
export declare function formatSuccessOutput(result: GenerateOpenApiRuntimeResult, stream: CliOutputStream): string;
|
|
6
|
+
export declare function formatFailureOutput(message: string, stream: CliOutputStream): string;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { styleText } from 'node:util';
|
|
2
|
+
function canUseColor(stream) {
|
|
3
|
+
return (stream.isTTY === true &&
|
|
4
|
+
process.env.NO_COLOR === undefined &&
|
|
5
|
+
process.env.TERM !== 'dumb');
|
|
6
|
+
}
|
|
7
|
+
function styleCliText(format, text, stream) {
|
|
8
|
+
return canUseColor(stream)
|
|
9
|
+
? styleText(format, text, { validateStream: false })
|
|
10
|
+
: text;
|
|
11
|
+
}
|
|
12
|
+
function formatCount(count, singular) {
|
|
13
|
+
return `${count} ${singular}${count === 1 ? '' : 's'}`;
|
|
14
|
+
}
|
|
15
|
+
export function formatSuccessOutput(result, stream) {
|
|
16
|
+
if (stream.isTTY !== true) {
|
|
17
|
+
return `Generated ${result.schemaCount} schemas and ${result.operationCount} endpoints.\n`;
|
|
18
|
+
}
|
|
19
|
+
const marker = styleCliText('green', '✔', stream);
|
|
20
|
+
const heading = styleCliText('bold', 'OpenAPI generation complete', stream);
|
|
21
|
+
const schemas = styleCliText('dim', `Schemas: ${formatCount(result.schemaCount, 'schema')}`, stream);
|
|
22
|
+
const endpoints = styleCliText('dim', `Endpoints: ${formatCount(result.operationCount, 'endpoint')}`, stream);
|
|
23
|
+
return `${marker} ${heading}\n\n ${schemas}\n ${endpoints}\n`;
|
|
24
|
+
}
|
|
25
|
+
export function formatFailureOutput(message, stream) {
|
|
26
|
+
if (stream.isTTY !== true) {
|
|
27
|
+
return `OpenAPI generation failed: ${message}\n`;
|
|
28
|
+
}
|
|
29
|
+
const marker = styleCliText('red', '✖', stream);
|
|
30
|
+
const heading = styleCliText('bold', 'OpenAPI generation failed', stream);
|
|
31
|
+
return `${marker} ${heading}\n\n ${message}\n`;
|
|
32
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { GenerateOpenApiRuntimeOptions, GeneratorConfig, UnknownRecord } from './types.js';
|
|
2
|
+
export declare function isRecord(value: unknown): value is UnknownRecord;
|
|
3
|
+
export declare function resolveGeneratorConfig(options?: GenerateOpenApiRuntimeOptions): Promise<GeneratorConfig>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { dirname, isAbsolute, resolve } from 'node:path';
|
|
3
|
+
const CONFIG_KEYS = new Set(['specPath', 'outDir', 'runtimeImport']);
|
|
4
|
+
const FLAG_NAMES = new Map([
|
|
5
|
+
['--config', 'configPath'],
|
|
6
|
+
['--spec', 'specPath'],
|
|
7
|
+
['--out', 'outDir'],
|
|
8
|
+
['--runtime-import', 'runtimeImport'],
|
|
9
|
+
]);
|
|
10
|
+
export function isRecord(value) {
|
|
11
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
12
|
+
}
|
|
13
|
+
function parseArguments(argv, cwd) {
|
|
14
|
+
const values = {};
|
|
15
|
+
for (let index = 0; index < argv.length; index += 2) {
|
|
16
|
+
const flag = argv[index];
|
|
17
|
+
const key = flag === undefined ? undefined : FLAG_NAMES.get(flag);
|
|
18
|
+
const value = argv[index + 1];
|
|
19
|
+
if (key === undefined) {
|
|
20
|
+
throw new Error(`Unknown argument "${flag ?? ''}"`);
|
|
21
|
+
}
|
|
22
|
+
if (typeof value !== 'string' || value.length === 0) {
|
|
23
|
+
throw new Error(`Missing value for "${flag}"`);
|
|
24
|
+
}
|
|
25
|
+
values[key] = isAbsolute(value) ? value : resolve(cwd, value);
|
|
26
|
+
}
|
|
27
|
+
return values;
|
|
28
|
+
}
|
|
29
|
+
function requirePath(config, key, configPath) {
|
|
30
|
+
const value = config[key];
|
|
31
|
+
if (typeof value !== 'string' || value.trim().length === 0) {
|
|
32
|
+
throw new Error(`Config "${configPath}" requires a non-empty "${key}"`);
|
|
33
|
+
}
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
export async function resolveGeneratorConfig(options = {}) {
|
|
37
|
+
const argv = options.argv ?? [];
|
|
38
|
+
const cwd = options.cwd ?? process.cwd();
|
|
39
|
+
const cli = parseArguments(argv, cwd);
|
|
40
|
+
const configPath = cli.configPath ?? resolve(cwd, 'openapi.config.json');
|
|
41
|
+
let config;
|
|
42
|
+
try {
|
|
43
|
+
config = JSON.parse(await readFile(configPath, 'utf8'));
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
47
|
+
throw new Error(`Unable to read OpenAPI config "${configPath}": ${message}`);
|
|
48
|
+
}
|
|
49
|
+
if (!isRecord(config)) {
|
|
50
|
+
throw new Error(`OpenAPI config "${configPath}" must contain an object`);
|
|
51
|
+
}
|
|
52
|
+
for (const key of Object.keys(config)) {
|
|
53
|
+
if (!CONFIG_KEYS.has(key)) {
|
|
54
|
+
throw new Error(`Unknown OpenAPI config key "${key}"`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const configDirectory = dirname(configPath);
|
|
58
|
+
const resolveConfigPath = (key) => {
|
|
59
|
+
const value = requirePath(config, key, configPath);
|
|
60
|
+
return isAbsolute(value) ? value : resolve(configDirectory, value);
|
|
61
|
+
};
|
|
62
|
+
const runtimeImport = cli.runtimeImport ?? requirePath(config, 'runtimeImport', configPath);
|
|
63
|
+
if (runtimeImport.includes('\\') || runtimeImport.startsWith('.')) {
|
|
64
|
+
throw new Error(`Config "${configPath}" requires a package runtimeImport`);
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
configPath,
|
|
68
|
+
specPath: cli.specPath ?? resolveConfigPath('specPath'),
|
|
69
|
+
outDir: cli.outDir ?? resolveConfigPath('outDir'),
|
|
70
|
+
runtimeImport,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ResolvedReference, UnknownRecord } from './types.js';
|
|
2
|
+
export declare function isRecord(value: unknown): value is UnknownRecord;
|
|
3
|
+
export declare function escapePointerSegment(segment: string): string;
|
|
4
|
+
export declare function pointerChild(pointer: string, segment: string | number): string;
|
|
5
|
+
export declare function locationOf(documentPath: string, pointer: string): string;
|
|
6
|
+
export declare function requireRecord(value: unknown, location: string, label: string): UnknownRecord;
|
|
7
|
+
export declare class DocumentStore {
|
|
8
|
+
#private;
|
|
9
|
+
load(documentPath: string): Promise<UnknownRecord>;
|
|
10
|
+
resolveReference(reference: unknown, fromDocumentPath: string): Promise<ResolvedReference>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { dirname, extname, resolve } from 'node:path';
|
|
3
|
+
export function isRecord(value) {
|
|
4
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
5
|
+
}
|
|
6
|
+
export function escapePointerSegment(segment) {
|
|
7
|
+
return segment.replaceAll('~', '~0').replaceAll('/', '~1');
|
|
8
|
+
}
|
|
9
|
+
export function pointerChild(pointer, segment) {
|
|
10
|
+
return `${pointer}/${escapePointerSegment(String(segment))}`;
|
|
11
|
+
}
|
|
12
|
+
export function locationOf(documentPath, pointer) {
|
|
13
|
+
return `${documentPath}#${pointer}`;
|
|
14
|
+
}
|
|
15
|
+
export function requireRecord(value, location, label) {
|
|
16
|
+
if (!isRecord(value)) {
|
|
17
|
+
throw new Error(`${label} at ${location} must be an object`);
|
|
18
|
+
}
|
|
19
|
+
return value;
|
|
20
|
+
}
|
|
21
|
+
export class DocumentStore {
|
|
22
|
+
#documents = new Map();
|
|
23
|
+
async load(documentPath) {
|
|
24
|
+
const absolutePath = resolve(documentPath);
|
|
25
|
+
const existing = this.#documents.get(absolutePath);
|
|
26
|
+
if (existing !== undefined) {
|
|
27
|
+
return existing;
|
|
28
|
+
}
|
|
29
|
+
const pending = this.#read(absolutePath);
|
|
30
|
+
this.#documents.set(absolutePath, pending);
|
|
31
|
+
return pending;
|
|
32
|
+
}
|
|
33
|
+
async #read(documentPath) {
|
|
34
|
+
let source;
|
|
35
|
+
try {
|
|
36
|
+
source = await readFile(documentPath, 'utf8');
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
40
|
+
throw new Error(`Unable to read OpenAPI document "${documentPath}": ${message}`);
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
const extension = extname(documentPath).toLowerCase();
|
|
44
|
+
if (extension !== '.json') {
|
|
45
|
+
throw new Error('Only JSON OpenAPI documents are supported');
|
|
46
|
+
}
|
|
47
|
+
return requireRecord(JSON.parse(source), documentPath, 'OpenAPI document');
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
51
|
+
throw new Error(`Unable to parse OpenAPI document "${documentPath}": ${message}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async resolveReference(reference, fromDocumentPath) {
|
|
55
|
+
if (typeof reference !== 'string' || reference.length === 0) {
|
|
56
|
+
throw new Error(`Invalid $ref at ${fromDocumentPath}`);
|
|
57
|
+
}
|
|
58
|
+
if (/^[A-Za-z][A-Za-z\d+.-]*:/u.test(reference)) {
|
|
59
|
+
throw new Error(`Remote $ref "${reference}" is unsupported`);
|
|
60
|
+
}
|
|
61
|
+
const hashIndex = reference.indexOf('#');
|
|
62
|
+
const filePart = hashIndex === -1 ? reference : reference.slice(0, hashIndex);
|
|
63
|
+
const fragment = hashIndex === -1 ? '' : reference.slice(hashIndex + 1);
|
|
64
|
+
const documentPath = filePart.length === 0
|
|
65
|
+
? fromDocumentPath
|
|
66
|
+
: resolve(dirname(fromDocumentPath), decodeURIComponent(filePart));
|
|
67
|
+
const document = await this.load(documentPath);
|
|
68
|
+
const decodedFragment = decodeURIComponent(fragment);
|
|
69
|
+
if (decodedFragment.length > 0 && !decodedFragment.startsWith('/')) {
|
|
70
|
+
throw new Error(`Anchor $ref "${reference}" is unsupported`);
|
|
71
|
+
}
|
|
72
|
+
let value = document;
|
|
73
|
+
for (const encodedSegment of decodedFragment.split('/').slice(1)) {
|
|
74
|
+
const segment = encodedSegment
|
|
75
|
+
.replaceAll('~1', '/')
|
|
76
|
+
.replaceAll('~0', '~');
|
|
77
|
+
if (!isRecord(value) && !Array.isArray(value)) {
|
|
78
|
+
throw new Error(`Unresolved $ref "${reference}" from ${fromDocumentPath}`);
|
|
79
|
+
}
|
|
80
|
+
if (!Object.prototype.hasOwnProperty.call(value, segment)) {
|
|
81
|
+
throw new Error(`Unresolved $ref "${reference}" from ${fromDocumentPath}`);
|
|
82
|
+
}
|
|
83
|
+
value = Reflect.get(value, segment);
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
canonicalKey: locationOf(documentPath, decodedFragment),
|
|
87
|
+
documentPath,
|
|
88
|
+
pointer: decodedFragment,
|
|
89
|
+
value,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -1,93 +1,64 @@
|
|
|
1
|
-
import { join } from 'node:path';
|
|
2
|
-
|
|
3
1
|
function renderResponseType(responses) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
return `{ readonly status: ${status}; readonly body: ${bodyType} }`;
|
|
2
|
+
if (responses.length === 0) {
|
|
3
|
+
return 'never';
|
|
4
|
+
}
|
|
5
|
+
return responses
|
|
6
|
+
.map(({ schemaName, status }) => {
|
|
7
|
+
const bodyType = schemaName ?? 'null';
|
|
8
|
+
return `{ readonly status: ${status}; readonly body: ${bodyType} }`;
|
|
12
9
|
})
|
|
13
|
-
|
|
10
|
+
.join(' | ');
|
|
14
11
|
}
|
|
15
|
-
|
|
16
12
|
function renderStatusCases(responses) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
);
|
|
28
|
-
} else {
|
|
29
|
-
lines.push(
|
|
30
|
-
` const result = make${response.schemaName}(parts.body);`,
|
|
31
|
-
` if (!result.ok) {`,
|
|
32
|
-
` return result;`,
|
|
33
|
-
` }`,
|
|
34
|
-
` return { ok: true, value: { status: ${response.status}, body: result.value } };`
|
|
35
|
-
);
|
|
13
|
+
const lines = [];
|
|
14
|
+
for (const response of responses) {
|
|
15
|
+
lines.push(` case ${response.status}: {`);
|
|
16
|
+
if (response.schemaName === null) {
|
|
17
|
+
lines.push(` if (parts.body !== null) {`, ` return failure(['body'], 'nullBody', 'Expected a null response body');`, ` }`, ` return { ok: true, value: { status: ${response.status}, body: null } };`);
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
lines.push(` const result = make${response.schemaName}(parts.body);`, ` if (!result.ok) {`, ` return result;`, ` }`, ` return { ok: true, value: { status: ${response.status}, body: result.value } };`);
|
|
21
|
+
}
|
|
22
|
+
lines.push(' }');
|
|
36
23
|
}
|
|
37
|
-
lines.push(
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
lines.push(
|
|
41
|
-
` default:`,
|
|
42
|
-
` return failure(['status'], 'status', 'Undocumented response status');`
|
|
43
|
-
);
|
|
44
|
-
return lines.join('\n');
|
|
24
|
+
lines.push(` default:`, ` return failure(['status'], 'status', 'Undocumented response status');`);
|
|
25
|
+
return lines.join('\n');
|
|
45
26
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
(
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
(response) => !response.isSuccess
|
|
53
|
-
);
|
|
54
|
-
const schemaNames = new Set();
|
|
55
|
-
|
|
56
|
-
if (operation.request.schemaName !== null) {
|
|
57
|
-
schemaNames.add(operation.request.schemaName);
|
|
58
|
-
}
|
|
59
|
-
for (const response of operation.responses) {
|
|
60
|
-
if (response.schemaName !== null) {
|
|
61
|
-
schemaNames.add(response.schemaName);
|
|
27
|
+
function emitEndpoint(operation, config) {
|
|
28
|
+
const successResponses = operation.responses.filter((response) => response.isSuccess);
|
|
29
|
+
const errorResponses = operation.responses.filter((response) => !response.isSuccess);
|
|
30
|
+
const schemaNames = new Set();
|
|
31
|
+
if (operation.request.schemaName !== null) {
|
|
32
|
+
schemaNames.add(operation.request.schemaName);
|
|
62
33
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
operation.request.schemaName
|
|
76
|
-
|
|
34
|
+
for (const response of operation.responses) {
|
|
35
|
+
if (response.schemaName !== null) {
|
|
36
|
+
schemaNames.add(response.schemaName);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const sortedSchemaNames = [...schemaNames].sort();
|
|
40
|
+
const typeImport = sortedSchemaNames.length === 0
|
|
41
|
+
? ''
|
|
42
|
+
: `import type { ${sortedSchemaNames.join(', ')} } from '../quickpay-api';\n`;
|
|
43
|
+
const makerImports = sortedSchemaNames
|
|
44
|
+
.map((name) => `import { make${name} } from '../schemas/${name}';`)
|
|
45
|
+
.join('\n');
|
|
46
|
+
const requestType = operation.request.schemaName ?? 'null';
|
|
47
|
+
const requestMaker = operation.request.schemaName === null
|
|
48
|
+
? `export function makeRequest(input: unknown): Result<null> {
|
|
77
49
|
if (input !== null) {
|
|
78
50
|
return failure([], 'nullBody', 'Expected null');
|
|
79
51
|
}
|
|
80
52
|
|
|
81
53
|
return { ok: true, value: null };
|
|
82
54
|
}`
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
return `/**
|
|
55
|
+
: `export const makeRequest = make${operation.request.schemaName};`;
|
|
56
|
+
return `/**
|
|
86
57
|
* Generated by openapi-contract-kit.
|
|
87
58
|
* Do not edit directly.
|
|
88
59
|
*/
|
|
89
60
|
|
|
90
|
-
import type { Result, ValidationPath } from ${JSON.stringify(runtimeImport)};
|
|
61
|
+
import type { Result, ValidationPath } from ${JSON.stringify(config.runtimeImport)};
|
|
91
62
|
${typeImport}${makerImports}${makerImports.length > 0 ? '\n' : ''}
|
|
92
63
|
export const URL = ${JSON.stringify(operation.path)};
|
|
93
64
|
export const METHOD = ${JSON.stringify(operation.method)};
|
|
@@ -171,12 +142,9 @@ export const makeResponse = Object.assign(makeAnyResponse, {
|
|
|
171
142
|
});
|
|
172
143
|
`;
|
|
173
144
|
}
|
|
174
|
-
|
|
175
145
|
export function emitEndpointModules(model, config) {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
])
|
|
181
|
-
);
|
|
146
|
+
return new Map(model.operations.map((operation) => [
|
|
147
|
+
`endpoints/${operation.name}.ts`,
|
|
148
|
+
emitEndpoint(operation, config),
|
|
149
|
+
]));
|
|
182
150
|
}
|