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.
Files changed (78) hide show
  1. package/.github/workflows/ci.yml +73 -0
  2. package/.github/workflows/release.yml +37 -0
  3. package/README.md +1 -0
  4. package/dist/cli/cli.d.ts +1 -0
  5. package/dist/cli/cli.js +20 -0
  6. package/dist/cli/generateOpenApi.d.ts +2 -0
  7. package/dist/cli/generateOpenApi.js +152 -0
  8. package/dist/server/constants/HttpMethods.d.ts +10 -0
  9. package/dist/server/constants/HttpMethods.js +9 -0
  10. package/dist/server/constants/HttpStatusCodes.d.ts +48 -0
  11. package/dist/server/constants/HttpStatusCodes.js +27 -0
  12. package/dist/server/handlers/api/EndpointDefinition.d.ts +16 -0
  13. package/dist/server/handlers/api/EndpointDefinition.js +1 -0
  14. package/dist/server/handlers/api/EndpointHandler.d.ts +6 -0
  15. package/dist/server/handlers/api/EndpointHandler.js +1 -0
  16. package/dist/server/handlers/api/HandlerFormDefinition.spec.d.ts +1 -0
  17. package/dist/server/handlers/api/HandlerFormDefinition.spec.js +19 -0
  18. package/dist/server/handlers/api/HandlerFromDefinition.d.ts +11 -0
  19. package/dist/server/handlers/api/HandlerFromDefinition.js +1 -0
  20. package/dist/server/handlers/api/PathParameters.d.ts +5 -0
  21. package/dist/server/handlers/api/PathParameters.js +1 -0
  22. package/dist/server/handlers/api/PathParameters.spec.d.ts +1 -0
  23. package/dist/server/handlers/api/PathParameters.spec.js +12 -0
  24. package/dist/server/handlers/api/createApiHandler.d.ts +12 -0
  25. package/dist/server/handlers/api/createApiHandler.js +20 -0
  26. package/dist/server/handlers/api/responses/emptyResponse.d.ts +7 -0
  27. package/dist/server/handlers/api/responses/emptyResponse.js +1 -0
  28. package/dist/server/handlers/api/responses/index.d.ts +7 -0
  29. package/dist/server/handlers/api/responses/index.js +1 -0
  30. package/dist/server/handlers/api/responses/jsonResponse.d.ts +14 -0
  31. package/dist/server/handlers/api/responses/jsonResponse.js +13 -0
  32. package/dist/server/index.d.ts +4 -0
  33. package/dist/server/index.js +3 -0
  34. package/dist/server/middleware/logging.d.ts +4 -0
  35. package/dist/server/middleware/logging.js +25 -0
  36. package/dist/server/middleware/validation.d.ts +4 -0
  37. package/dist/server/middleware/validation.js +27 -0
  38. package/dist/server/server.d.ts +21 -0
  39. package/dist/server/server.js +43 -0
  40. package/dist/server/utils/funcs.d.ts +1 -0
  41. package/dist/server/utils/funcs.js +3 -0
  42. package/dist/server/utils/logging.d.ts +3 -0
  43. package/dist/server/utils/logging.js +7 -0
  44. package/dist/server/utils/typeGuards.d.ts +2 -0
  45. package/dist/server/utils/typeGuards.js +6 -0
  46. package/dist/server/utils/types.d.ts +3 -0
  47. package/dist/server/utils/types.js +1 -0
  48. package/eslint-configs/eslint.base.config.js +30 -0
  49. package/eslint-configs/eslint.node.config.js +23 -0
  50. package/eslint-configs/eslint.test.config.js +15 -0
  51. package/eslint.config.ts +6 -0
  52. package/package.json +46 -0
  53. package/prettier.config.js +14 -0
  54. package/src/cli/cli.ts +37 -0
  55. package/src/cli/generateOpenApi.ts +217 -0
  56. package/src/server/constants/HttpMethods.ts +11 -0
  57. package/src/server/constants/HttpStatusCodes.ts +46 -0
  58. package/src/server/handlers/api/EndpointDefinition.ts +24 -0
  59. package/src/server/handlers/api/EndpointHandler.ts +24 -0
  60. package/src/server/handlers/api/HandlerFormDefinition.spec.ts +120 -0
  61. package/src/server/handlers/api/HandlerFromDefinition.ts +33 -0
  62. package/src/server/handlers/api/PathParameters.spec.ts +28 -0
  63. package/src/server/handlers/api/PathParameters.ts +10 -0
  64. package/src/server/handlers/api/createApiHandler.ts +44 -0
  65. package/src/server/handlers/api/responses/emptyResponse.ts +12 -0
  66. package/src/server/handlers/api/responses/index.ts +15 -0
  67. package/src/server/handlers/api/responses/jsonResponse.ts +44 -0
  68. package/src/server/index.ts +4 -0
  69. package/src/server/middleware/logging.ts +41 -0
  70. package/src/server/middleware/validation.ts +35 -0
  71. package/src/server/server.ts +90 -0
  72. package/src/server/utils/funcs.ts +3 -0
  73. package/src/server/utils/logging.ts +10 -0
  74. package/src/server/utils/typeGuards.ts +9 -0
  75. package/src/server/utils/types.ts +3 -0
  76. package/transitKit.code-workspace +36 -0
  77. package/tsconfig.json +15 -0
  78. package/vitest.config.ts +22 -0
@@ -0,0 +1,73 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main, develop]
6
+ pull_request:
7
+ branches: [main, develop]
8
+
9
+ jobs:
10
+ lint:
11
+ name: 🔍 Lint
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - name: Checkout code
15
+ uses: actions/checkout@v4
16
+
17
+ - name: Setup Node.js
18
+ uses: actions/setup-node@v4
19
+ with:
20
+ node-version: "20"
21
+ cache: "npm"
22
+
23
+ - name: 📦 Install dependencies
24
+ run: npm ci
25
+
26
+ - name: ✨ Run linter
27
+ run: npm run lint
28
+
29
+ test:
30
+ name: 🧪 Test
31
+ runs-on: ubuntu-latest
32
+ steps:
33
+ - name: 📥 Checkout code
34
+ uses: actions/checkout@v4
35
+
36
+ - name: ⚙️ Setup Node.js
37
+ uses: actions/setup-node@v4
38
+ with:
39
+ node-version: "22"
40
+ cache: "npm"
41
+
42
+ - name: 📦 Install dependencies
43
+ run: npm ci
44
+
45
+ - name: 🧪 Run tests
46
+ run: npm run test -- --run
47
+
48
+ build:
49
+ name: 🏗️ Build
50
+ runs-on: ubuntu-latest
51
+ needs: [lint, test]
52
+ steps:
53
+ - name: 📥 Checkout code
54
+ uses: actions/checkout@v4
55
+
56
+ - name: ⚙️ Setup Node.js
57
+ uses: actions/setup-node@v4
58
+ with:
59
+ node-version: "20"
60
+ cache: "npm"
61
+
62
+ - name: 📦 Install dependencies
63
+ run: npm ci
64
+
65
+ - name: 🔨 Build package
66
+ run: npm run build
67
+
68
+ - name: 📤 Upload build artifacts
69
+ uses: actions/upload-artifact@v4
70
+ with:
71
+ name: dist
72
+ path: dist/
73
+ retention-days: 7
@@ -0,0 +1,37 @@
1
+ name: Release
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ publish:
9
+ name: 🚀 Publish to npm
10
+ runs-on: ubuntu-latest
11
+ permissions:
12
+ contents: read
13
+ id-token: write
14
+ steps:
15
+ - name: 📥 Checkout code
16
+ uses: actions/checkout@v4
17
+
18
+ - name: ⚙️ Setup Node.js
19
+ uses: actions/setup-node@v4
20
+ with:
21
+ node-version: "20"
22
+ registry-url: "https://registry.npmjs.org"
23
+ cache: "npm"
24
+
25
+ - name: 📦 Install dependencies
26
+ run: npm ci
27
+
28
+ - name: 🧪 Run tests
29
+ run: npm run test -- --run
30
+
31
+ - name: 🔨 Build package
32
+ run: npm run build
33
+
34
+ - name: 🚀 Publish to npm
35
+ run: npm publish --provenance
36
+ env:
37
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
package/README.md ADDED
@@ -0,0 +1 @@
1
+ # declarative-server
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,20 @@
1
+ import { Command } from "commander";
2
+ import fs from "fs";
3
+ import { generateOpenApiDoc } from "./generateOpenApi";
4
+ const program = new Command();
5
+ program
6
+ .name("transit-kit")
7
+ .description("CLI of the transitKit backend framework")
8
+ .version("1.0.0");
9
+ program
10
+ .command("generate-openapi")
11
+ .option("-o, --output <path>", "Output path for the generated OpenAPI document", "openapi.json")
12
+ .action(async (options) => {
13
+ const { output } = options;
14
+ const generatedDoc = await generateOpenApiDoc();
15
+ fs.writeFileSync(output, JSON.stringify(generatedDoc, null, 2), {
16
+ encoding: "utf-8",
17
+ });
18
+ console.log(`OpenAPI document generated at: ${output}`);
19
+ });
20
+ program.parse();
@@ -0,0 +1,2 @@
1
+ import { OpenAPIV3 } from "openapi-types";
2
+ export declare function generateOpenApiDoc(): Promise<OpenAPIV3.Document<{}>>;
@@ -0,0 +1,152 @@
1
+ import { glob } from "glob";
2
+ import path from "path";
3
+ import z from "zod";
4
+ import { hasNoValue, hasValue } from "../server/utils/typeGuards";
5
+ import { isJsonResponseSchema } from "../server/handlers/api/responses/jsonResponse";
6
+ async function findEndpointDefinitions() {
7
+ const files = await glob("**/*.ts", {
8
+ cwd: process.cwd(),
9
+ ignore: ["**/node_modules/**", "**/*.spec.ts"],
10
+ });
11
+ const definitions = await Promise.all(files.map(async (file) => {
12
+ const absolutePath = path.resolve(file);
13
+ const fileUrl = path.toNamespacedPath(absolutePath).startsWith("/")
14
+ ? `file://${absolutePath}`
15
+ : `file:///${absolutePath}`;
16
+ try {
17
+ const module = await import(fileUrl);
18
+ if (module.default) {
19
+ const def = module.default.__API_ENDPOINT_DEFINITION__;
20
+ if (hasNoValue(def)) {
21
+ return null;
22
+ }
23
+ return def;
24
+ }
25
+ else {
26
+ return null;
27
+ }
28
+ }
29
+ catch (error) {
30
+ console.error(`Error importing ${file}:`, error);
31
+ return null;
32
+ }
33
+ }));
34
+ return definitions.filter(hasValue);
35
+ }
36
+ function extractPathAndParameters(path) {
37
+ const parameters = path.match(/:([a-zA-Z0-9_]+)/g)?.map((param) => {
38
+ return {
39
+ name: param.substring(1),
40
+ in: "path",
41
+ required: true,
42
+ schema: { type: "string" },
43
+ description: `Path parameter ${param}`,
44
+ };
45
+ }) ?? [];
46
+ const openApiPath = path.replace(/:([a-zA-Z0-9_]+)/g, (_, paramName) => {
47
+ return `{${paramName}}`;
48
+ });
49
+ return { openApiPath, parameters };
50
+ }
51
+ function extractQueryParameters(querySchema) {
52
+ const querySchemaObject = z.toJSONSchema(querySchema);
53
+ if (querySchemaObject.properties) {
54
+ return Object.entries(querySchemaObject.properties).map(([name, schema]) => ({
55
+ name: name,
56
+ in: "query",
57
+ required: querySchemaObject.required?.includes(name) || false,
58
+ schema: schema,
59
+ }));
60
+ }
61
+ else {
62
+ return [];
63
+ }
64
+ }
65
+ function translateToOpenAPIPathItem(definition) {
66
+ const { meta, path, method, requestBodySchema, querySchema, responseSchemas, } = definition;
67
+ // 1. Path and Parameter extraction
68
+ const { openApiPath, parameters: pathParameters } = extractPathAndParameters(path);
69
+ const queryParameters = hasValue(querySchema)
70
+ ? extractQueryParameters(querySchema)
71
+ : [];
72
+ const operationParameters = [...pathParameters, ...queryParameters];
73
+ const requestBody = hasValue(requestBodySchema)
74
+ ? {
75
+ requestBody: {
76
+ description: `${meta.name} Request Body`,
77
+ required: true,
78
+ content: {
79
+ "application/json": {
80
+ schema: z.toJSONSchema(requestBodySchema), // Type assertion
81
+ },
82
+ },
83
+ },
84
+ }
85
+ : {};
86
+ // 4. Response Schema Translation
87
+ const responses = Object.entries(responseSchemas)
88
+ .map(([statusCode, responseDef]) => {
89
+ if (isJsonResponseSchema(responseDef)) {
90
+ const zodSchema = responseDef.dataSchema;
91
+ const responseSchema = z.toJSONSchema(zodSchema);
92
+ return {
93
+ [statusCode]: {
94
+ description: `Response for status code ${statusCode}`,
95
+ content: {
96
+ [responseDef.dataType]: {
97
+ schema: responseSchema,
98
+ },
99
+ },
100
+ },
101
+ };
102
+ }
103
+ else {
104
+ return {
105
+ [statusCode]: {
106
+ description: `Response for status code ${statusCode}`,
107
+ },
108
+ };
109
+ }
110
+ })
111
+ .reduce((acc, resp) => {
112
+ return { ...acc, ...resp };
113
+ }, {});
114
+ const operation = {
115
+ operationId: meta.name,
116
+ summary: meta.description,
117
+ tags: [meta.group],
118
+ description: meta.description,
119
+ parameters: operationParameters,
120
+ ...requestBody,
121
+ responses,
122
+ };
123
+ const pathItem = {
124
+ [method.toLowerCase()]: operation,
125
+ };
126
+ return [openApiPath, pathItem];
127
+ }
128
+ export async function generateOpenApiDoc() {
129
+ const definitions = await findEndpointDefinitions();
130
+ const paths = definitions.reduce((acc, def) => {
131
+ const [openApiPath, pathItem] = translateToOpenAPIPathItem(def);
132
+ if (acc[openApiPath]) {
133
+ acc[openApiPath] = {
134
+ ...acc[openApiPath],
135
+ ...pathItem,
136
+ };
137
+ }
138
+ else {
139
+ acc[openApiPath] = pathItem;
140
+ }
141
+ return acc;
142
+ }, {});
143
+ const openApiDocument = {
144
+ openapi: "3.0.0",
145
+ info: {
146
+ title: "Generated API",
147
+ version: "1.0.0",
148
+ },
149
+ paths: paths,
150
+ };
151
+ return openApiDocument;
152
+ }
@@ -0,0 +1,10 @@
1
+ export declare const HttpMethods: {
2
+ readonly get: "get";
3
+ readonly post: "post";
4
+ readonly put: "put";
5
+ readonly delete: "delete";
6
+ readonly patch: "patch";
7
+ readonly head: "head";
8
+ readonly options: "options";
9
+ };
10
+ export type HttpMethod = (typeof HttpMethods)[keyof typeof HttpMethods];
@@ -0,0 +1,9 @@
1
+ export const HttpMethods = {
2
+ get: "get",
3
+ post: "post",
4
+ put: "put",
5
+ delete: "delete",
6
+ patch: "patch",
7
+ head: "head",
8
+ options: "options",
9
+ };
@@ -0,0 +1,48 @@
1
+ export declare const SuccessStatusCodes: {
2
+ readonly Ok_200: 200;
3
+ readonly Created_201: 201;
4
+ readonly NoContent_204: 204;
5
+ };
6
+ export type SuccessStatusCode = (typeof SuccessStatusCodes)[keyof typeof SuccessStatusCodes];
7
+ export declare const ClientErrorStatusCodes: {
8
+ readonly BadRequest_400: 400;
9
+ readonly Unauthorized_401: 401;
10
+ readonly Forbidden_403: 403;
11
+ readonly NotFound_404: 404;
12
+ readonly Conflict_409: 409;
13
+ };
14
+ export type ClientErrorStatusCode = (typeof ClientErrorStatusCodes)[keyof typeof ClientErrorStatusCodes];
15
+ export declare const ServerErrorStatusCodes: {
16
+ readonly InternalServerError_500: 500;
17
+ readonly NotImplemented_501: 501;
18
+ readonly BadGateway_502: 502;
19
+ readonly ServiceUnavailable_503: 503;
20
+ };
21
+ export declare const ErrorStatusCodes: {
22
+ readonly InternalServerError_500: 500;
23
+ readonly NotImplemented_501: 501;
24
+ readonly BadGateway_502: 502;
25
+ readonly ServiceUnavailable_503: 503;
26
+ readonly BadRequest_400: 400;
27
+ readonly Unauthorized_401: 401;
28
+ readonly Forbidden_403: 403;
29
+ readonly NotFound_404: 404;
30
+ readonly Conflict_409: 409;
31
+ };
32
+ export type ErrorStatusCode = (typeof ErrorStatusCodes)[keyof typeof ErrorStatusCodes];
33
+ export type ServerErrorStatusCode = (typeof ServerErrorStatusCodes)[keyof typeof ServerErrorStatusCodes];
34
+ export declare const HttpStatusCodes: {
35
+ readonly InternalServerError_500: 500;
36
+ readonly NotImplemented_501: 501;
37
+ readonly BadGateway_502: 502;
38
+ readonly ServiceUnavailable_503: 503;
39
+ readonly BadRequest_400: 400;
40
+ readonly Unauthorized_401: 401;
41
+ readonly Forbidden_403: 403;
42
+ readonly NotFound_404: 404;
43
+ readonly Conflict_409: 409;
44
+ readonly Ok_200: 200;
45
+ readonly Created_201: 201;
46
+ readonly NoContent_204: 204;
47
+ };
48
+ export type HttpStatusCode = (typeof HttpStatusCodes)[keyof typeof HttpStatusCodes];
@@ -0,0 +1,27 @@
1
+ export const SuccessStatusCodes = {
2
+ Ok_200: 200,
3
+ Created_201: 201,
4
+ NoContent_204: 204,
5
+ };
6
+ export const ClientErrorStatusCodes = {
7
+ BadRequest_400: 400,
8
+ Unauthorized_401: 401,
9
+ Forbidden_403: 403,
10
+ NotFound_404: 404,
11
+ Conflict_409: 409,
12
+ };
13
+ export const ServerErrorStatusCodes = {
14
+ InternalServerError_500: 500,
15
+ NotImplemented_501: 501,
16
+ BadGateway_502: 502,
17
+ ServiceUnavailable_503: 503,
18
+ };
19
+ export const ErrorStatusCodes = {
20
+ ...ClientErrorStatusCodes,
21
+ ...ServerErrorStatusCodes,
22
+ };
23
+ export const HttpStatusCodes = {
24
+ ...SuccessStatusCodes,
25
+ ...ClientErrorStatusCodes,
26
+ ...ServerErrorStatusCodes,
27
+ };
@@ -0,0 +1,16 @@
1
+ import z from "zod";
2
+ import { HttpMethod } from "../../constants/HttpMethods";
3
+ import { GenericResponseSchemaMap } from "./responses/index";
4
+ export interface ApiEndpointMeta {
5
+ name: string;
6
+ group: string;
7
+ description: string;
8
+ }
9
+ export type ApiEndpointDefinition<Path extends string, Method extends HttpMethod, RequestBody extends z.ZodType | undefined, Query extends z.ZodType | undefined, ResponseMap extends GenericResponseSchemaMap> = {
10
+ meta: ApiEndpointMeta;
11
+ path: Path;
12
+ method: Method;
13
+ requestBodySchema?: RequestBody;
14
+ querySchema?: Query;
15
+ responseSchemas: ResponseMap;
16
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,6 @@
1
+ import { Request } from "express";
2
+ import { HttpStatusCodes } from "../../constants/HttpStatusCodes";
3
+ import { GenericResponse } from "./responses";
4
+ export type ApiEndpointHandler<PathParams extends Record<string, string> | undefined = {}, RequestBody = unknown, Query = unknown, Responses extends GenericResponse = never> = (request: Request<PathParams, unknown, RequestBody, Query, Record<string, unknown>>) => Promise<Responses | {
5
+ code: (typeof HttpStatusCodes)["InternalServerError_500"];
6
+ }>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,19 @@
1
+ import { describe, expectTypeOf, it } from "vitest";
2
+ import z from "zod";
3
+ describe("HandlerFromDefinition", () => {
4
+ it("can infer handler responses correctly (Empty)", () => {
5
+ expectTypeOf().toEqualTypeOf();
6
+ });
7
+ it("can infer handler responses correctly (Json)", () => {
8
+ const _data = z.object({
9
+ a: z.number(),
10
+ });
11
+ expectTypeOf().toEqualTypeOf();
12
+ });
13
+ it("can infer handler responses correctly (Multiple)", () => {
14
+ const _data = z.object({
15
+ a: z.number(),
16
+ });
17
+ expectTypeOf().toEqualTypeOf();
18
+ });
19
+ });
@@ -0,0 +1,11 @@
1
+ import z from "zod";
2
+ import { HttpStatusCode } from "../../constants/HttpStatusCodes";
3
+ import { Prettify } from "../../utils/types";
4
+ import { ApiEndpointHandler } from "./EndpointHandler";
5
+ import { ExtractPathParams } from "./PathParameters";
6
+ import { EmptyResponse, EmptyResponseSchema } from "./responses/emptyResponse";
7
+ import { GenericResponseSchemaMap } from "./responses/index";
8
+ import { JsonResponseSchema, JsonResponseSchemaToResponseType } from "./responses/jsonResponse";
9
+ export type HandlerForDefinition<Path extends string, RequestBody extends z.ZodType | undefined, Query extends z.ZodType | undefined, ResponsesMap extends GenericResponseSchemaMap> = ApiEndpointHandler<ExtractPathParams<Path>, RequestBody extends undefined ? undefined : z.infer<RequestBody>, Query extends undefined ? undefined : z.infer<Query>, Prettify<{
10
+ [K in keyof ResponsesMap]: K extends HttpStatusCode ? ResponsesMap[K] extends JsonResponseSchema ? JsonResponseSchemaToResponseType<K, ResponsesMap[K]> : ResponsesMap[K] extends EmptyResponseSchema ? EmptyResponse<K> : never : never;
11
+ }[keyof ResponsesMap]>>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,5 @@
1
+ export type ExtractPathParams<Path extends string> = Path extends `${string}:${infer Param}/${infer Rest}` ? {
2
+ [K in Param | keyof ExtractPathParams<Rest>]: string;
3
+ } : Path extends `${string}:${infer Param}` ? {
4
+ [K in Param]: string;
5
+ } : undefined;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,12 @@
1
+ import { describe, expectTypeOf, it } from "vitest";
2
+ describe("ExtractPathParameters", () => {
3
+ it("can infer the parameter types of a path", () => {
4
+ expectTypeOf().toEqualTypeOf();
5
+ });
6
+ it("can infer multiple params", () => {
7
+ expectTypeOf().toEqualTypeOf();
8
+ });
9
+ it("can infer correctly if no param is present", () => {
10
+ expectTypeOf().toEqualTypeOf();
11
+ });
12
+ });
@@ -0,0 +1,12 @@
1
+ import z from "zod";
2
+ import { HttpMethod } from "../../constants/HttpMethods";
3
+ import { ApiEndpointDefinition } from "./EndpointDefinition";
4
+ import { ApiEndpointHandler } from "./EndpointHandler";
5
+ import { HandlerForDefinition } from "./HandlerFromDefinition";
6
+ import { GenericResponseSchemaMap } from "./responses";
7
+ export declare function createApiEndpointHandler<const ResponsesMap extends GenericResponseSchemaMap, const Path extends string, const Method extends HttpMethod, const RequestBody extends z.ZodType | undefined = undefined, const Query extends z.ZodType | undefined = undefined>(definition: ApiEndpointDefinition<Path, Method, RequestBody, Query, ResponsesMap>, handler: HandlerForDefinition<Path, RequestBody, Query, ResponsesMap>): {
8
+ __API_ENDPOINT_DEFINITION__: ApiEndpointDefinition<Path, Method, RequestBody, Query, ResponsesMap>;
9
+ definition: ApiEndpointDefinition<Path, Method, RequestBody, Query, ResponsesMap>;
10
+ handler: HandlerForDefinition<Path, RequestBody, Query, ResponsesMap>;
11
+ };
12
+ export declare function buildApiEndpointHandler(handler: ApiEndpointHandler): import("express").RequestHandler<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
@@ -0,0 +1,20 @@
1
+ import expressAsyncHandler from "express-async-handler";
2
+ import { isJsonResponse } from "./responses/jsonResponse";
3
+ export function createApiEndpointHandler(definition, handler) {
4
+ return {
5
+ __API_ENDPOINT_DEFINITION__: definition,
6
+ definition,
7
+ handler,
8
+ };
9
+ }
10
+ export function buildApiEndpointHandler(handler) {
11
+ return expressAsyncHandler(async (request, response) => {
12
+ const result = await handler(request);
13
+ if (isJsonResponse(result)) {
14
+ response.status(result.code).json(result.json);
15
+ }
16
+ else {
17
+ response.status(result.code).send();
18
+ }
19
+ });
20
+ }
@@ -0,0 +1,7 @@
1
+ import { HttpStatusCode } from "../../../constants/HttpStatusCodes";
2
+ export interface EmptyResponseSchema {
3
+ }
4
+ export interface EmptyResponse<Code extends HttpStatusCode = HttpStatusCode> {
5
+ code: Code;
6
+ }
7
+ export type EmptyResponseSchemaToResponseType<Code extends HttpStatusCode, _ extends EmptyResponseSchema> = EmptyResponse<Code>;
@@ -0,0 +1,7 @@
1
+ import { ClientErrorStatusCode, SuccessStatusCode } from "../../../constants/HttpStatusCodes";
2
+ import { EmptyResponse, EmptyResponseSchema } from "./emptyResponse";
3
+ import { JsonResponse, JsonResponseSchema } from "./jsonResponse";
4
+ export type GenericResponseSchemaMap = {
5
+ [K in SuccessStatusCode | ClientErrorStatusCode]?: EmptyResponseSchema | JsonResponseSchema | undefined;
6
+ };
7
+ export type GenericResponse = EmptyResponse | JsonResponse;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,14 @@
1
+ import z from "zod";
2
+ import { HttpStatusCode } from "../../../constants/HttpStatusCodes";
3
+ export interface JsonResponseSchema<DataSchema extends z.ZodType = z.ZodType> {
4
+ dataType: "application/json";
5
+ dataSchema: DataSchema;
6
+ }
7
+ export interface JsonResponse<Code extends HttpStatusCode = HttpStatusCode, Data = unknown> {
8
+ code: Code;
9
+ dataType: "application/json";
10
+ json: Data;
11
+ }
12
+ export type JsonResponseSchemaToResponseType<Code extends HttpStatusCode, Schema extends JsonResponseSchema> = Schema extends JsonResponseSchema<infer DataSchema> ? JsonResponse<Code, z.infer<DataSchema>> : never;
13
+ export declare function isJsonResponseSchema(value: unknown): value is JsonResponseSchema;
14
+ export declare function isJsonResponse(value: unknown): value is JsonResponse;
@@ -0,0 +1,13 @@
1
+ import { hasValue } from "../../../utils/typeGuards";
2
+ export function isJsonResponseSchema(value) {
3
+ return (typeof value === "object" &&
4
+ hasValue(value) &&
5
+ "dataType" in value &&
6
+ value.dataType === "application/json");
7
+ }
8
+ export function isJsonResponse(value) {
9
+ return (typeof value === "object" &&
10
+ value !== null &&
11
+ "dataType" in value &&
12
+ value.dataType === "application/json");
13
+ }
@@ -0,0 +1,4 @@
1
+ export { createApiEndpointHandler } from "./handlers/api/createApiHandler";
2
+ export { createServer, type Server, type ServerOptions } from "./server";
3
+ declare const _default: {};
4
+ export default _default;
@@ -0,0 +1,3 @@
1
+ export { createApiEndpointHandler } from "./handlers/api/createApiHandler";
2
+ export { createServer } from "./server";
3
+ export default {};
@@ -0,0 +1,4 @@
1
+ import { NextFunction, Request, Response } from "express";
2
+ import { Logger } from "../utils/logging";
3
+ export declare function buildRequestLogger(logger: Logger, isInDevMode: boolean): (req: Request, res: Response, next: NextFunction) => void;
4
+ export declare function buildResponseLogger(logger: Logger, isInDevMode: boolean): (req: Request, res: Response, next: NextFunction) => void;
@@ -0,0 +1,25 @@
1
+ import colors from "colors/safe";
2
+ export function buildRequestLogger(logger, isInDevMode) {
3
+ return (req, res, next) => {
4
+ logger.info(`[Request] ${colors.cyan(req.method)} - ${colors.cyan(req.path)}`);
5
+ if (isInDevMode) {
6
+ logger.info(`[Request - Dev] Headers: ${JSON.stringify(req.headers)}`);
7
+ logger.info(`[Request - Dev] Query: ${JSON.stringify(req.query)}`);
8
+ logger.info(`[Request - Dev] Body: ${JSON.stringify(req.body)}`);
9
+ }
10
+ next();
11
+ };
12
+ }
13
+ export function buildResponseLogger(logger, isInDevMode) {
14
+ return (req, res, next) => {
15
+ const originalSend = res.send;
16
+ res.json = function (body) {
17
+ logger.info(`[Response] ${colors.cyan(req.method)} - ${colors.cyan(req.path)} - Status: ${res.statusCode > 299 && res.statusCode < 599 ? colors.red(res.statusCode.toString()) : colors.green(res.statusCode.toString())}`);
18
+ if (isInDevMode) {
19
+ logger.info(`[Response - Dev] Body: ${JSON.stringify(body)}`);
20
+ }
21
+ return originalSend.call(this, body);
22
+ };
23
+ next();
24
+ };
25
+ }
@@ -0,0 +1,4 @@
1
+ import { NextFunction, Request, Response } from "express";
2
+ import { ZodType } from "zod";
3
+ export declare function buildBodyValidatorMiddleware<Schema extends ZodType>(schema: Schema): (request: Request, response: Response, next: NextFunction) => void;
4
+ export declare function buildQueryValidatorMiddleware<Schema extends ZodType>(schema: Schema): (request: Request, response: Response, next: NextFunction) => void;
@@ -0,0 +1,27 @@
1
+ import { HttpStatusCodes } from "../constants/HttpStatusCodes";
2
+ export function buildBodyValidatorMiddleware(schema) {
3
+ return (request, response, next) => {
4
+ const validationResult = schema.safeParse(request.body);
5
+ if (!validationResult.success) {
6
+ response.status(HttpStatusCodes.BadRequest_400).json({
7
+ message: `Body invalid`,
8
+ error: validationResult.error,
9
+ });
10
+ return;
11
+ }
12
+ next();
13
+ };
14
+ }
15
+ export function buildQueryValidatorMiddleware(schema) {
16
+ return (request, response, next) => {
17
+ const validationResult = schema.safeParse(request.query);
18
+ if (!validationResult.success) {
19
+ response.status(HttpStatusCodes.BadRequest_400).json({
20
+ message: `Query parameters invalid`,
21
+ error: validationResult.error,
22
+ });
23
+ return;
24
+ }
25
+ next();
26
+ };
27
+ }