transit-kit 0.1.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/.github/workflows/ci.yml +73 -0
- package/.github/workflows/release.yml +37 -0
- package/README.md +1 -0
- package/dist/cli/cli.d.ts +1 -0
- package/dist/cli/cli.js +20 -0
- package/dist/cli/generateOpenApi.d.ts +2 -0
- package/dist/cli/generateOpenApi.js +152 -0
- package/dist/server/constants/HttpMethods.d.ts +10 -0
- package/dist/server/constants/HttpMethods.js +9 -0
- package/dist/server/constants/HttpStatusCodes.d.ts +48 -0
- package/dist/server/constants/HttpStatusCodes.js +27 -0
- package/dist/server/handlers/api/EndpointDefinition.d.ts +16 -0
- package/dist/server/handlers/api/EndpointDefinition.js +1 -0
- package/dist/server/handlers/api/EndpointHandler.d.ts +6 -0
- package/dist/server/handlers/api/EndpointHandler.js +1 -0
- package/dist/server/handlers/api/HandlerFormDefinition.spec.d.ts +1 -0
- package/dist/server/handlers/api/HandlerFormDefinition.spec.js +19 -0
- package/dist/server/handlers/api/HandlerFromDefinition.d.ts +11 -0
- package/dist/server/handlers/api/HandlerFromDefinition.js +1 -0
- package/dist/server/handlers/api/PathParameters.d.ts +5 -0
- package/dist/server/handlers/api/PathParameters.js +1 -0
- package/dist/server/handlers/api/PathParameters.spec.d.ts +1 -0
- package/dist/server/handlers/api/PathParameters.spec.js +12 -0
- package/dist/server/handlers/api/createApiHandler.d.ts +12 -0
- package/dist/server/handlers/api/createApiHandler.js +20 -0
- package/dist/server/handlers/api/responses/emptyResponse.d.ts +7 -0
- package/dist/server/handlers/api/responses/emptyResponse.js +1 -0
- package/dist/server/handlers/api/responses/index.d.ts +7 -0
- package/dist/server/handlers/api/responses/index.js +1 -0
- package/dist/server/handlers/api/responses/jsonResponse.d.ts +14 -0
- package/dist/server/handlers/api/responses/jsonResponse.js +13 -0
- package/dist/server/index.d.ts +4 -0
- package/dist/server/index.js +3 -0
- package/dist/server/middleware/logging.d.ts +4 -0
- package/dist/server/middleware/logging.js +25 -0
- package/dist/server/middleware/validation.d.ts +4 -0
- package/dist/server/middleware/validation.js +27 -0
- package/dist/server/server.d.ts +21 -0
- package/dist/server/server.js +43 -0
- package/dist/server/utils/funcs.d.ts +1 -0
- package/dist/server/utils/funcs.js +3 -0
- package/dist/server/utils/logging.d.ts +3 -0
- package/dist/server/utils/logging.js +7 -0
- package/dist/server/utils/typeGuards.d.ts +2 -0
- package/dist/server/utils/typeGuards.js +6 -0
- package/dist/server/utils/types.d.ts +3 -0
- package/dist/server/utils/types.js +1 -0
- package/eslint-configs/eslint.base.config.js +30 -0
- package/eslint-configs/eslint.node.config.js +23 -0
- package/eslint-configs/eslint.test.config.js +15 -0
- package/eslint.config.ts +6 -0
- package/package.json +46 -0
- package/prettier.config.js +14 -0
- package/src/cli/cli.ts +37 -0
- package/src/cli/generateOpenApi.ts +217 -0
- package/src/server/constants/HttpMethods.ts +11 -0
- package/src/server/constants/HttpStatusCodes.ts +46 -0
- package/src/server/handlers/api/EndpointDefinition.ts +24 -0
- package/src/server/handlers/api/EndpointHandler.ts +24 -0
- package/src/server/handlers/api/HandlerFormDefinition.spec.ts +120 -0
- package/src/server/handlers/api/HandlerFromDefinition.ts +33 -0
- package/src/server/handlers/api/PathParameters.spec.ts +28 -0
- package/src/server/handlers/api/PathParameters.ts +10 -0
- package/src/server/handlers/api/createApiHandler.ts +44 -0
- package/src/server/handlers/api/responses/emptyResponse.ts +12 -0
- package/src/server/handlers/api/responses/index.ts +15 -0
- package/src/server/handlers/api/responses/jsonResponse.ts +44 -0
- package/src/server/index.ts +4 -0
- package/src/server/middleware/logging.ts +41 -0
- package/src/server/middleware/validation.ts +35 -0
- package/src/server/server.ts +90 -0
- package/src/server/utils/funcs.ts +3 -0
- package/src/server/utils/logging.ts +10 -0
- package/src/server/utils/typeGuards.ts +9 -0
- package/src/server/utils/types.ts +3 -0
- package/transitKit.code-workspace +36 -0
- package/tsconfig.json +15 -0
- package/vitest.config.ts +22 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Application } from "express";
|
|
2
|
+
import z from "zod";
|
|
3
|
+
import { HttpMethod } from "./constants/HttpMethods";
|
|
4
|
+
import { ApiEndpointDefinition } from "./handlers/api/EndpointDefinition";
|
|
5
|
+
import { ApiEndpointHandler } from "./handlers/api/EndpointHandler";
|
|
6
|
+
import { Logger } from "./utils/logging";
|
|
7
|
+
export interface ServerOptions {
|
|
8
|
+
inDevMode?: boolean;
|
|
9
|
+
port?: number;
|
|
10
|
+
logger?: Logger | boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface Server {
|
|
13
|
+
_expressApp: Application;
|
|
14
|
+
_logger: Logger | boolean;
|
|
15
|
+
start: () => void;
|
|
16
|
+
registerApiEndpoint: (endpoint: {
|
|
17
|
+
endpointHandler: ApiEndpointHandler;
|
|
18
|
+
endpointDefinition: ApiEndpointDefinition<string, HttpMethod, z.ZodType, z.ZodType, {}>;
|
|
19
|
+
}) => void;
|
|
20
|
+
}
|
|
21
|
+
export declare function createServer(options: ServerOptions): Server;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import cookieParser from "cookie-parser";
|
|
2
|
+
import express from "express";
|
|
3
|
+
import { buildApiEndpointHandler } from "./handlers/api/createApiHandler";
|
|
4
|
+
import { buildRequestLogger, buildResponseLogger } from "./middleware/logging";
|
|
5
|
+
import { buildBodyValidatorMiddleware, buildQueryValidatorMiddleware, } from "./middleware/validation";
|
|
6
|
+
import { NoOpLogger } from "./utils/logging";
|
|
7
|
+
import { hasNoValue, hasValue } from "./utils/typeGuards";
|
|
8
|
+
export function createServer(options) {
|
|
9
|
+
const { port = 3000, inDevMode = false } = options;
|
|
10
|
+
const logger = options.logger === true
|
|
11
|
+
? console
|
|
12
|
+
: options.logger === false || hasNoValue(options.logger)
|
|
13
|
+
? NoOpLogger
|
|
14
|
+
: options.logger;
|
|
15
|
+
const app = express();
|
|
16
|
+
app.use(express.json());
|
|
17
|
+
app.use(express.urlencoded({ extended: true }));
|
|
18
|
+
app.use(cookieParser());
|
|
19
|
+
app.use(buildRequestLogger(logger, inDevMode));
|
|
20
|
+
app.use(buildResponseLogger(logger, inDevMode));
|
|
21
|
+
return {
|
|
22
|
+
_expressApp: app,
|
|
23
|
+
_logger: logger,
|
|
24
|
+
start: () => {
|
|
25
|
+
app.listen(port);
|
|
26
|
+
},
|
|
27
|
+
registerApiEndpoint: ({ endpointDefinition, endpointHandler }) => {
|
|
28
|
+
registerApiEndpoint(app, endpointDefinition, endpointHandler);
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function registerApiEndpoint(expressApp, endpointDefinition, endpointHandler) {
|
|
33
|
+
const handlerStack = [
|
|
34
|
+
hasValue(endpointDefinition.querySchema)
|
|
35
|
+
? buildQueryValidatorMiddleware(endpointDefinition.querySchema)
|
|
36
|
+
: null,
|
|
37
|
+
hasValue(endpointDefinition.requestBodySchema)
|
|
38
|
+
? buildBodyValidatorMiddleware(endpointDefinition.requestBodySchema)
|
|
39
|
+
: null,
|
|
40
|
+
buildApiEndpointHandler(endpointHandler),
|
|
41
|
+
].filter(hasValue);
|
|
42
|
+
expressApp[endpointDefinition.method](endpointDefinition.path, handlerStack);
|
|
43
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function isEmpty<T>(array: Array<T>): boolean;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { globalIgnores } from "eslint/config";
|
|
2
|
+
import { config } from "typescript-eslint";
|
|
3
|
+
|
|
4
|
+
import eslintJS from "@eslint/js";
|
|
5
|
+
import prettierEslint from "eslint-plugin-prettier/recommended";
|
|
6
|
+
import eslintTS from "typescript-eslint";
|
|
7
|
+
|
|
8
|
+
export default config(
|
|
9
|
+
globalIgnores([
|
|
10
|
+
"*.config.{mjs|js|ts}",
|
|
11
|
+
"coverage",
|
|
12
|
+
"dist",
|
|
13
|
+
"generated",
|
|
14
|
+
"node_modules",
|
|
15
|
+
]),
|
|
16
|
+
eslintJS.configs.recommended,
|
|
17
|
+
eslintTS.configs.recommended,
|
|
18
|
+
{
|
|
19
|
+
rules: {
|
|
20
|
+
"@typescript-eslint/no-unused-vars": [
|
|
21
|
+
"error",
|
|
22
|
+
{
|
|
23
|
+
argsIgnorePattern: "^_",
|
|
24
|
+
destructuredArrayIgnorePattern: "^_",
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
prettierEslint,
|
|
30
|
+
);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import globals from "globals";
|
|
2
|
+
import { config } from "typescript-eslint";
|
|
3
|
+
|
|
4
|
+
import baseConfig from "./eslint.base.config.js";
|
|
5
|
+
|
|
6
|
+
export default config(baseConfig, {
|
|
7
|
+
files: ["**/*.ts"],
|
|
8
|
+
languageOptions: {
|
|
9
|
+
globals: {
|
|
10
|
+
...globals.node,
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
rules: {
|
|
14
|
+
"@typescript-eslint/no-empty-object-type": "off",
|
|
15
|
+
"@typescript-eslint/no-unused-vars": [
|
|
16
|
+
"error",
|
|
17
|
+
{
|
|
18
|
+
argsIgnorePattern: "^_",
|
|
19
|
+
varsIgnorePattern: "^_",
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
},
|
|
23
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import eslintVitest from "@vitest/eslint-plugin";
|
|
2
|
+
import globals from "globals";
|
|
3
|
+
import { config } from "typescript-eslint";
|
|
4
|
+
|
|
5
|
+
export default config(eslintVitest.configs.recommended, {
|
|
6
|
+
files: ["**/*.spec.ts"],
|
|
7
|
+
languageOptions: {
|
|
8
|
+
globals: {
|
|
9
|
+
...globals.vitest,
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
rules: {
|
|
13
|
+
"vitest/expect-expect": "off",
|
|
14
|
+
},
|
|
15
|
+
});
|
package/eslint.config.ts
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "transit-kit",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": "https://github.com/D4rkr34lm/declarative-server",
|
|
7
|
+
"exports": {
|
|
8
|
+
"./server": {
|
|
9
|
+
"import": "./dist/server/index.js",
|
|
10
|
+
"require": "./dist/server/index.js",
|
|
11
|
+
"types": "./dist/server/server.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"bin": "./dist/cli/cli.js",
|
|
15
|
+
"scripts": {
|
|
16
|
+
"lint": "eslint",
|
|
17
|
+
"test": "vitest",
|
|
18
|
+
"build": "rm -rf ./dist && tsc"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/cookie-parser": "^1.4.9",
|
|
22
|
+
"@types/express": "^5.0.0",
|
|
23
|
+
"@vitest/coverage-v8": "^4.0.15",
|
|
24
|
+
"@vitest/eslint-plugin": "^1.5.2",
|
|
25
|
+
"eslint": "^9.39.1",
|
|
26
|
+
"eslint-config-prettier": "^10.1.8",
|
|
27
|
+
"eslint-plugin-prettier": "^5.5.4",
|
|
28
|
+
"jiti": "^2.6.1",
|
|
29
|
+
"prettier": "^3.7.4",
|
|
30
|
+
"tslib": "2.8.1",
|
|
31
|
+
"typescript": "^5.9.3",
|
|
32
|
+
"typescript-eslint": "^8.49.0",
|
|
33
|
+
"vite-tsconfig-paths": "^5.1.4",
|
|
34
|
+
"vitest": "^4.0.15"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"colors": "^1.4.0",
|
|
38
|
+
"commander": "^14.0.2",
|
|
39
|
+
"cookie-parser": "^1.4.7",
|
|
40
|
+
"express": "^4.21.1",
|
|
41
|
+
"express-async-handler": "^1.2.0",
|
|
42
|
+
"glob": "^13.0.0",
|
|
43
|
+
"openapi-types": "^12.1.3",
|
|
44
|
+
"zod": "^4.1.13"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @see https://prettier.io/docs/configuration
|
|
3
|
+
* @type {import("prettier").Config}
|
|
4
|
+
*/
|
|
5
|
+
const config = {
|
|
6
|
+
trailingComma: "all",
|
|
7
|
+
tabWidth: 2,
|
|
8
|
+
semi: true,
|
|
9
|
+
singleQuote: false,
|
|
10
|
+
printWidth: 80,
|
|
11
|
+
endOfLine: "lf",
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export default config;
|
package/src/cli/cli.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import { generateOpenApiDoc } from "./generateOpenApi";
|
|
4
|
+
|
|
5
|
+
const program = new Command();
|
|
6
|
+
|
|
7
|
+
program
|
|
8
|
+
.name("transit-kit")
|
|
9
|
+
.description("CLI of the transitKit backend framework")
|
|
10
|
+
|
|
11
|
+
.version("1.0.0");
|
|
12
|
+
|
|
13
|
+
program
|
|
14
|
+
.command("generate-openapi")
|
|
15
|
+
.option(
|
|
16
|
+
"-o, --output <path>",
|
|
17
|
+
|
|
18
|
+
"Output path for the generated OpenAPI document",
|
|
19
|
+
"openapi.json",
|
|
20
|
+
)
|
|
21
|
+
.option(
|
|
22
|
+
"-t, --target <path>",
|
|
23
|
+
"Target path to search for endpoint definitions",
|
|
24
|
+
".",
|
|
25
|
+
)
|
|
26
|
+
.action(async (options) => {
|
|
27
|
+
const { output, target } = options;
|
|
28
|
+
|
|
29
|
+
const generatedDoc = await generateOpenApiDoc(target);
|
|
30
|
+
|
|
31
|
+
fs.writeFileSync(output, JSON.stringify(generatedDoc, null, 2), {
|
|
32
|
+
encoding: "utf-8",
|
|
33
|
+
});
|
|
34
|
+
console.log(`OpenAPI document generated at: ${output}`);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
program.parse();
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { glob } from "glob";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import z from "zod";
|
|
4
|
+
import { HttpMethod } from "../server/constants/HttpMethods";
|
|
5
|
+
import { ApiEndpointDefinition } from "../server/handlers/api/EndpointDefinition";
|
|
6
|
+
import { GenericResponseSchemaMap } from "../server/handlers/api/responses";
|
|
7
|
+
import { hasNoValue, hasValue } from "../server/utils/typeGuards";
|
|
8
|
+
|
|
9
|
+
import { ZodType } from "zod";
|
|
10
|
+
|
|
11
|
+
import { OpenAPIV3 } from "openapi-types";
|
|
12
|
+
import { isJsonResponseSchema } from "../server/handlers/api/responses/jsonResponse";
|
|
13
|
+
|
|
14
|
+
async function findEndpointDefinitions(targetPath: string) {
|
|
15
|
+
const files = await glob("**/*.ts", {
|
|
16
|
+
cwd: path.resolve(process.cwd(), targetPath),
|
|
17
|
+
ignore: ["**/node_modules/**", "**/*.spec.ts"],
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const definitions = await Promise.all(
|
|
21
|
+
files.map(async (file) => {
|
|
22
|
+
const absolutePath = path.resolve(file);
|
|
23
|
+
const fileUrl = path.toNamespacedPath(absolutePath).startsWith("/")
|
|
24
|
+
? `file://${absolutePath}`
|
|
25
|
+
: `file:///${absolutePath}`;
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const module = await import(fileUrl);
|
|
29
|
+
if (module.default) {
|
|
30
|
+
const def = module.default.__API_ENDPOINT_DEFINITION__ as
|
|
31
|
+
| ApiEndpointDefinition<
|
|
32
|
+
string,
|
|
33
|
+
HttpMethod,
|
|
34
|
+
z.ZodType | undefined,
|
|
35
|
+
z.ZodType | undefined,
|
|
36
|
+
GenericResponseSchemaMap
|
|
37
|
+
>
|
|
38
|
+
| undefined;
|
|
39
|
+
|
|
40
|
+
if (hasNoValue(def)) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return def;
|
|
45
|
+
} else {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
} catch (error) {
|
|
49
|
+
console.error(`Error importing ${file}:`, error);
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}),
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
return definitions.filter(hasValue);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function extractPathAndParameters(path: string): {
|
|
59
|
+
openApiPath: string;
|
|
60
|
+
parameters: OpenAPIV3.ParameterObject[];
|
|
61
|
+
} {
|
|
62
|
+
const parameters: OpenAPIV3.ParameterObject[] =
|
|
63
|
+
path.match(/:([a-zA-Z0-9_]+)/g)?.map((param) => {
|
|
64
|
+
return {
|
|
65
|
+
name: param.substring(1),
|
|
66
|
+
in: "path",
|
|
67
|
+
required: true,
|
|
68
|
+
schema: { type: "string" },
|
|
69
|
+
description: `Path parameter ${param}`,
|
|
70
|
+
};
|
|
71
|
+
}) ?? [];
|
|
72
|
+
|
|
73
|
+
const openApiPath = path.replace(/:([a-zA-Z0-9_]+)/g, (_, paramName) => {
|
|
74
|
+
return `{${paramName}}`;
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
return { openApiPath, parameters };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function extractQueryParameters(
|
|
81
|
+
querySchema: ZodType,
|
|
82
|
+
): OpenAPIV3.ParameterObject[] {
|
|
83
|
+
const querySchemaObject = z.toJSONSchema(querySchema);
|
|
84
|
+
|
|
85
|
+
if (querySchemaObject.properties) {
|
|
86
|
+
return Object.entries(querySchemaObject.properties).map(
|
|
87
|
+
([name, schema]) => ({
|
|
88
|
+
name: name,
|
|
89
|
+
in: "query",
|
|
90
|
+
required: querySchemaObject.required?.includes(name) || false,
|
|
91
|
+
schema: schema as OpenAPIV3.SchemaObject,
|
|
92
|
+
}),
|
|
93
|
+
);
|
|
94
|
+
} else {
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function translateToOpenAPIPathItem(
|
|
100
|
+
definition: ApiEndpointDefinition<
|
|
101
|
+
string,
|
|
102
|
+
HttpMethod,
|
|
103
|
+
ZodType | undefined,
|
|
104
|
+
ZodType | undefined,
|
|
105
|
+
GenericResponseSchemaMap
|
|
106
|
+
>,
|
|
107
|
+
): [string, OpenAPIV3.PathItemObject] {
|
|
108
|
+
const {
|
|
109
|
+
meta,
|
|
110
|
+
path,
|
|
111
|
+
method,
|
|
112
|
+
requestBodySchema,
|
|
113
|
+
querySchema,
|
|
114
|
+
responseSchemas,
|
|
115
|
+
} = definition;
|
|
116
|
+
|
|
117
|
+
// 1. Path and Parameter extraction
|
|
118
|
+
const { openApiPath, parameters: pathParameters } =
|
|
119
|
+
extractPathAndParameters(path);
|
|
120
|
+
|
|
121
|
+
const queryParameters = hasValue(querySchema)
|
|
122
|
+
? extractQueryParameters(querySchema)
|
|
123
|
+
: [];
|
|
124
|
+
|
|
125
|
+
const operationParameters = [...pathParameters, ...queryParameters];
|
|
126
|
+
|
|
127
|
+
const requestBody = hasValue(requestBodySchema)
|
|
128
|
+
? {
|
|
129
|
+
requestBody: {
|
|
130
|
+
description: `${meta.name} Request Body`,
|
|
131
|
+
required: true,
|
|
132
|
+
content: {
|
|
133
|
+
"application/json": {
|
|
134
|
+
schema: z.toJSONSchema(
|
|
135
|
+
requestBodySchema,
|
|
136
|
+
) as OpenAPIV3.SchemaObject, // Type assertion
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
}
|
|
141
|
+
: {};
|
|
142
|
+
|
|
143
|
+
// 4. Response Schema Translation
|
|
144
|
+
const responses = Object.entries(responseSchemas)
|
|
145
|
+
.map(([statusCode, responseDef]) => {
|
|
146
|
+
if (isJsonResponseSchema(responseDef)) {
|
|
147
|
+
const zodSchema = responseDef.dataSchema as ZodType;
|
|
148
|
+
const responseSchema = z.toJSONSchema(zodSchema);
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
[statusCode]: {
|
|
152
|
+
description: `Response for status code ${statusCode}`,
|
|
153
|
+
content: {
|
|
154
|
+
[responseDef.dataType]: {
|
|
155
|
+
schema: responseSchema as OpenAPIV3.SchemaObject,
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
} as OpenAPIV3.ResponseObject,
|
|
159
|
+
};
|
|
160
|
+
} else {
|
|
161
|
+
return {
|
|
162
|
+
[statusCode]: {
|
|
163
|
+
description: `Response for status code ${statusCode}`,
|
|
164
|
+
} as OpenAPIV3.ResponseObject,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
})
|
|
168
|
+
.reduce((acc, resp) => {
|
|
169
|
+
return { ...acc, ...resp };
|
|
170
|
+
}, {});
|
|
171
|
+
|
|
172
|
+
const operation: OpenAPIV3.OperationObject = {
|
|
173
|
+
operationId: meta.name,
|
|
174
|
+
summary: meta.description,
|
|
175
|
+
tags: [meta.group],
|
|
176
|
+
description: meta.description,
|
|
177
|
+
parameters: operationParameters,
|
|
178
|
+
...requestBody,
|
|
179
|
+
responses,
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const pathItem: OpenAPIV3.PathItemObject = {
|
|
183
|
+
[method.toLowerCase()]: operation,
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
return [openApiPath, pathItem];
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export async function generateOpenApiDoc(targetPath: string) {
|
|
190
|
+
const definitions = await findEndpointDefinitions(targetPath);
|
|
191
|
+
|
|
192
|
+
const paths = definitions.reduce<OpenAPIV3.PathsObject>((acc, def) => {
|
|
193
|
+
const [openApiPath, pathItem] = translateToOpenAPIPathItem(def);
|
|
194
|
+
|
|
195
|
+
if (acc[openApiPath]) {
|
|
196
|
+
acc[openApiPath] = {
|
|
197
|
+
...acc[openApiPath],
|
|
198
|
+
...pathItem,
|
|
199
|
+
};
|
|
200
|
+
} else {
|
|
201
|
+
acc[openApiPath] = pathItem;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return acc;
|
|
205
|
+
}, {});
|
|
206
|
+
|
|
207
|
+
const openApiDocument: OpenAPIV3.Document = {
|
|
208
|
+
openapi: "3.0.0",
|
|
209
|
+
info: {
|
|
210
|
+
title: "Generated API",
|
|
211
|
+
version: "1.0.0",
|
|
212
|
+
},
|
|
213
|
+
paths: paths,
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
return openApiDocument;
|
|
217
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export const SuccessStatusCodes = {
|
|
2
|
+
Ok_200: 200,
|
|
3
|
+
Created_201: 201,
|
|
4
|
+
NoContent_204: 204,
|
|
5
|
+
} as const;
|
|
6
|
+
|
|
7
|
+
export type SuccessStatusCode =
|
|
8
|
+
(typeof SuccessStatusCodes)[keyof typeof SuccessStatusCodes];
|
|
9
|
+
|
|
10
|
+
export const ClientErrorStatusCodes = {
|
|
11
|
+
BadRequest_400: 400,
|
|
12
|
+
Unauthorized_401: 401,
|
|
13
|
+
Forbidden_403: 403,
|
|
14
|
+
NotFound_404: 404,
|
|
15
|
+
Conflict_409: 409,
|
|
16
|
+
} as const;
|
|
17
|
+
|
|
18
|
+
export type ClientErrorStatusCode =
|
|
19
|
+
(typeof ClientErrorStatusCodes)[keyof typeof ClientErrorStatusCodes];
|
|
20
|
+
|
|
21
|
+
export const ServerErrorStatusCodes = {
|
|
22
|
+
InternalServerError_500: 500,
|
|
23
|
+
NotImplemented_501: 501,
|
|
24
|
+
BadGateway_502: 502,
|
|
25
|
+
ServiceUnavailable_503: 503,
|
|
26
|
+
} as const;
|
|
27
|
+
|
|
28
|
+
export const ErrorStatusCodes = {
|
|
29
|
+
...ClientErrorStatusCodes,
|
|
30
|
+
...ServerErrorStatusCodes,
|
|
31
|
+
} as const;
|
|
32
|
+
|
|
33
|
+
export type ErrorStatusCode =
|
|
34
|
+
(typeof ErrorStatusCodes)[keyof typeof ErrorStatusCodes];
|
|
35
|
+
|
|
36
|
+
export type ServerErrorStatusCode =
|
|
37
|
+
(typeof ServerErrorStatusCodes)[keyof typeof ServerErrorStatusCodes];
|
|
38
|
+
|
|
39
|
+
export const HttpStatusCodes = {
|
|
40
|
+
...SuccessStatusCodes,
|
|
41
|
+
...ClientErrorStatusCodes,
|
|
42
|
+
...ServerErrorStatusCodes,
|
|
43
|
+
} as const;
|
|
44
|
+
|
|
45
|
+
export type HttpStatusCode =
|
|
46
|
+
(typeof HttpStatusCodes)[keyof typeof HttpStatusCodes];
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import z from "zod";
|
|
2
|
+
import { HttpMethod } from "../../constants/HttpMethods";
|
|
3
|
+
import { GenericResponseSchemaMap } from "./responses/index";
|
|
4
|
+
|
|
5
|
+
export interface ApiEndpointMeta {
|
|
6
|
+
name: string;
|
|
7
|
+
group: string;
|
|
8
|
+
description: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type ApiEndpointDefinition<
|
|
12
|
+
Path extends string,
|
|
13
|
+
Method extends HttpMethod,
|
|
14
|
+
RequestBody extends z.ZodType | undefined,
|
|
15
|
+
Query extends z.ZodType | undefined,
|
|
16
|
+
ResponseMap extends GenericResponseSchemaMap,
|
|
17
|
+
> = {
|
|
18
|
+
meta: ApiEndpointMeta;
|
|
19
|
+
path: Path;
|
|
20
|
+
method: Method;
|
|
21
|
+
requestBodySchema?: RequestBody;
|
|
22
|
+
querySchema?: Query;
|
|
23
|
+
responseSchemas: ResponseMap;
|
|
24
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Request } from "express";
|
|
2
|
+
|
|
3
|
+
import { HttpStatusCodes } from "../../constants/HttpStatusCodes";
|
|
4
|
+
import { GenericResponse } from "./responses";
|
|
5
|
+
|
|
6
|
+
export type ApiEndpointHandler<
|
|
7
|
+
PathParams extends Record<string, string> | undefined = {},
|
|
8
|
+
RequestBody = unknown,
|
|
9
|
+
Query = unknown,
|
|
10
|
+
Responses extends GenericResponse = never,
|
|
11
|
+
> = (
|
|
12
|
+
request: Request<
|
|
13
|
+
PathParams,
|
|
14
|
+
unknown,
|
|
15
|
+
RequestBody,
|
|
16
|
+
Query,
|
|
17
|
+
Record<string, unknown>
|
|
18
|
+
>,
|
|
19
|
+
) => Promise<
|
|
20
|
+
| Responses
|
|
21
|
+
| {
|
|
22
|
+
code: (typeof HttpStatusCodes)["InternalServerError_500"];
|
|
23
|
+
}
|
|
24
|
+
>;
|