transit-kit 0.1.1 → 0.2.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.
package/dist/cli/cli.js CHANGED
@@ -9,9 +9,10 @@ program
9
9
  program
10
10
  .command("generate-openapi")
11
11
  .option("-o, --output <path>", "Output path for the generated OpenAPI document", "openapi.json")
12
+ .option("-t, --target <path>", "Target path to search for endpoint definitions", ".")
12
13
  .action(async (options) => {
13
- const { output } = options;
14
- const generatedDoc = await generateOpenApiDoc();
14
+ const { output, target } = options;
15
+ const generatedDoc = await generateOpenApiDoc(target);
15
16
  fs.writeFileSync(output, JSON.stringify(generatedDoc, null, 2), {
16
17
  encoding: "utf-8",
17
18
  });
@@ -1,2 +1,2 @@
1
1
  import { OpenAPIV3 } from "openapi-types";
2
- export declare function generateOpenApiDoc(): Promise<OpenAPIV3.Document<{}>>;
2
+ export declare function generateOpenApiDoc(targetPath: string): Promise<OpenAPIV3.Document<{}>>;
@@ -1,38 +1,7 @@
1
- import { glob } from "glob";
2
- import path from "path";
3
1
  import z from "zod";
4
- import { hasNoValue, hasValue } from "../server/utils/typeGuards";
2
+ import { hasValue } from "../server/utils/typeGuards";
3
+ import path from "path";
5
4
  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
5
  function extractPathAndParameters(path) {
37
6
  const parameters = path.match(/:([a-zA-Z0-9_]+)/g)?.map((param) => {
38
7
  return {
@@ -125,28 +94,37 @@ function translateToOpenAPIPathItem(definition) {
125
94
  };
126
95
  return [openApiPath, pathItem];
127
96
  }
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;
97
+ export async function generateOpenApiDoc(targetPath) {
98
+ const serverModule = await import(path.resolve(process.cwd(), targetPath));
99
+ const server = serverModule.default;
100
+ if (hasValue(server) &&
101
+ hasValue(server.endpointDefinitions) &&
102
+ Array.isArray(server.endpointDefinitions)) {
103
+ const endpointDefinitions = server.endpointDefinitions;
104
+ const paths = endpointDefinitions.reduce((acc, def) => {
105
+ const [openApiPath, pathItem] = translateToOpenAPIPathItem(def);
106
+ if (acc[openApiPath]) {
107
+ acc[openApiPath] = {
108
+ ...acc[openApiPath],
109
+ ...pathItem,
110
+ };
111
+ }
112
+ else {
113
+ acc[openApiPath] = pathItem;
114
+ }
115
+ return acc;
116
+ }, {});
117
+ const openApiDocument = {
118
+ openapi: "3.0.0",
119
+ info: {
120
+ title: "Generated API",
121
+ version: "1.0.0",
122
+ },
123
+ paths: paths,
124
+ };
125
+ return openApiDocument;
126
+ }
127
+ else {
128
+ throw new Error("The specified module does not export a valid server instance.");
129
+ }
152
130
  }
@@ -1,6 +1,6 @@
1
1
  import { Request } from "express";
2
2
  import { HttpStatusCodes } from "../../constants/HttpStatusCodes";
3
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 | {
4
+ export type ApiEndpointHandler<PathParams extends Record<string, string> = {}, RequestBody = unknown, Query = unknown, Responses extends GenericResponse = never> = (request: Request<PathParams, unknown, RequestBody, Query, Record<string, unknown>>) => Promise<Responses | {
5
5
  code: (typeof HttpStatusCodes)["InternalServerError_500"];
6
6
  }>;
@@ -2,4 +2,4 @@ export type ExtractPathParams<Path extends string> = Path extends `${string}:${i
2
2
  [K in Param | keyof ExtractPathParams<Rest>]: string;
3
3
  } : Path extends `${string}:${infer Param}` ? {
4
4
  [K in Param]: string;
5
- } : undefined;
5
+ } : {};
@@ -3,10 +3,9 @@ import { HttpMethod } from "../../constants/HttpMethods";
3
3
  import { ApiEndpointDefinition } from "./EndpointDefinition";
4
4
  import { ApiEndpointHandler } from "./EndpointHandler";
5
5
  import { HandlerForDefinition } from "./HandlerFromDefinition";
6
- import { GenericResponseSchemaMap } from "./responses";
6
+ import { GenericResponse, GenericResponseSchemaMap } from "./responses";
7
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
8
  definition: ApiEndpointDefinition<Path, Method, RequestBody, Query, ResponsesMap>;
10
9
  handler: HandlerForDefinition<Path, RequestBody, Query, ResponsesMap>;
11
10
  };
12
- export declare function buildApiEndpointHandler(handler: ApiEndpointHandler): import("express").RequestHandler<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
11
+ export declare function buildApiEndpointHandler(handler: ApiEndpointHandler<Record<string, string>, unknown, unknown, GenericResponse>): import("express").RequestHandler<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
@@ -2,7 +2,6 @@ import expressAsyncHandler from "express-async-handler";
2
2
  import { isJsonResponse } from "./responses/jsonResponse";
3
3
  export function createApiEndpointHandler(definition, handler) {
4
4
  return {
5
- __API_ENDPOINT_DEFINITION__: definition,
6
5
  definition,
7
6
  handler,
8
7
  };
@@ -0,0 +1,31 @@
1
+ import { describe, it } from "vitest";
2
+ import { createServer } from "../../server";
3
+ import { createApiEndpointHandler } from "./createApiHandler";
4
+ import testRequest from "supertest";
5
+ describe("createApiHandler", () => {
6
+ it("can create an API handler", () => {
7
+ const endpoint = createApiEndpointHandler({
8
+ meta: {
9
+ name: "",
10
+ description: "",
11
+ group: "",
12
+ },
13
+ method: "get",
14
+ path: "/test",
15
+ responseSchemas: {
16
+ 200: {},
17
+ },
18
+ }, async () => {
19
+ return {
20
+ code: 200,
21
+ };
22
+ });
23
+ const server = createServer({
24
+ inDevMode: true,
25
+ port: 3000,
26
+ logger: false,
27
+ endpoints: [endpoint],
28
+ });
29
+ testRequest(server.expressApp).get("/test").expect(200);
30
+ });
31
+ });
@@ -1,4 +1,4 @@
1
1
  export { createApiEndpointHandler } from "./handlers/api/createApiHandler";
2
- export { createServer, type Server, type ServerOptions } from "./server";
2
+ export { createServer, type Server, type ServerConfig } from "./server";
3
3
  declare const _default: {};
4
4
  export default _default;
@@ -3,19 +3,21 @@ import z from "zod";
3
3
  import { HttpMethod } from "./constants/HttpMethods";
4
4
  import { ApiEndpointDefinition } from "./handlers/api/EndpointDefinition";
5
5
  import { ApiEndpointHandler } from "./handlers/api/EndpointHandler";
6
+ import { GenericResponse, GenericResponseSchemaMap } from "./handlers/api/responses";
6
7
  import { Logger } from "./utils/logging";
7
- export interface ServerOptions {
8
- inDevMode?: boolean;
9
- port?: number;
10
- logger?: Logger | boolean;
8
+ export interface ServerConfig {
9
+ inDevMode: boolean;
10
+ port: number;
11
+ logger: Logger | boolean;
12
+ endpoints: Array<{
13
+ handler: ApiEndpointHandler<Record<string, string>, any, any, GenericResponse>;
14
+ definition: ApiEndpointDefinition<string, HttpMethod, z.ZodType | undefined, z.ZodType | undefined, GenericResponseSchemaMap>;
15
+ }>;
11
16
  }
12
17
  export interface Server {
13
- _expressApp: Application;
14
- _logger: Logger | boolean;
18
+ expressApp: Application;
19
+ logger: Logger | boolean;
20
+ endpointDefinitions: ApiEndpointDefinition<string, HttpMethod, z.ZodType | undefined, z.ZodType | undefined, GenericResponseSchemaMap>[];
15
21
  start: () => void;
16
- registerApiEndpoint: (endpoint: {
17
- endpointHandler: ApiEndpointHandler;
18
- endpointDefinition: ApiEndpointDefinition<string, HttpMethod, z.ZodType, z.ZodType, {}>;
19
- }) => void;
20
22
  }
21
- export declare function createServer(options: ServerOptions): Server;
23
+ export declare function createServer(config: ServerConfig): Server;
@@ -5,39 +5,40 @@ import { buildRequestLogger, buildResponseLogger } from "./middleware/logging";
5
5
  import { buildBodyValidatorMiddleware, buildQueryValidatorMiddleware, } from "./middleware/validation";
6
6
  import { NoOpLogger } from "./utils/logging";
7
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
8
+ function registerApiEndpoint(expressApp, endpointDefinition, endpointHandler) {
9
+ const handlerStack = [
10
+ hasValue(endpointDefinition.querySchema)
11
+ ? buildQueryValidatorMiddleware(endpointDefinition.querySchema)
12
+ : null,
13
+ hasValue(endpointDefinition.requestBodySchema)
14
+ ? buildBodyValidatorMiddleware(endpointDefinition.requestBodySchema)
15
+ : null,
16
+ buildApiEndpointHandler(endpointHandler),
17
+ ].filter(hasValue);
18
+ expressApp[endpointDefinition.method](endpointDefinition.path, handlerStack);
19
+ }
20
+ export function createServer(config) {
21
+ const { port, inDevMode, endpoints } = config;
22
+ const logger = config.logger === true
11
23
  ? console
12
- : options.logger === false || hasNoValue(options.logger)
24
+ : config.logger === false || hasNoValue(config.logger)
13
25
  ? NoOpLogger
14
- : options.logger;
26
+ : config.logger;
15
27
  const app = express();
16
28
  app.use(express.json());
17
29
  app.use(express.urlencoded({ extended: true }));
18
30
  app.use(cookieParser());
19
31
  app.use(buildRequestLogger(logger, inDevMode));
20
32
  app.use(buildResponseLogger(logger, inDevMode));
33
+ endpoints.forEach(({ definition, handler }) => {
34
+ registerApiEndpoint(app, definition, handler);
35
+ });
21
36
  return {
22
- _expressApp: app,
23
- _logger: logger,
37
+ expressApp: app,
38
+ logger: logger,
39
+ endpointDefinitions: endpoints.map((e) => e.definition),
24
40
  start: () => {
25
41
  app.listen(port);
26
42
  },
27
- registerApiEndpoint: ({ endpointDefinition, endpointHandler }) => {
28
- registerApiEndpoint(app, endpointDefinition, endpointHandler);
29
- },
30
43
  };
31
44
  }
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
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "transit-kit",
3
- "version": "0.1.1",
3
+ "version": "0.2.1",
4
4
  "description": "A declarative TypeScript framework for building type-safe Express.js APIs with automatic OpenAPI generation",
5
5
  "keywords": [
6
6
  "express",
@@ -17,11 +17,11 @@
17
17
  "author": "D4rkr34lm",
18
18
  "repository": {
19
19
  "type": "git",
20
- "url": "https://github.com/D4rkr34lm/declarative-server.git"
20
+ "url": "https://github.com/D4rkr34lm/transit-kit.git"
21
21
  },
22
- "homepage": "https://github.com/D4rkr34lm/declarative-server#readme",
22
+ "homepage": "https://github.com/D4rkr34lm/transit-kit#readme",
23
23
  "bugs": {
24
- "url": "https://github.com/D4rkr34lm/declarative-server/issues"
24
+ "url": "https://github.com/D4rkr34lm/transit-kit/issues"
25
25
  },
26
26
  "files": [
27
27
  "dist"
@@ -42,6 +42,7 @@
42
42
  "devDependencies": {
43
43
  "@types/cookie-parser": "^1.4.9",
44
44
  "@types/express": "^5.0.0",
45
+ "@types/supertest": "^6.0.3",
45
46
  "@vitest/coverage-v8": "^4.0.15",
46
47
  "@vitest/eslint-plugin": "^1.5.2",
47
48
  "eslint": "^9.39.1",
@@ -49,6 +50,7 @@
49
50
  "eslint-plugin-prettier": "^5.5.4",
50
51
  "jiti": "^2.6.1",
51
52
  "prettier": "^3.7.4",
53
+ "supertest": "^7.1.4",
52
54
  "tslib": "2.8.1",
53
55
  "typescript": "^5.9.3",
54
56
  "typescript-eslint": "^8.49.0",
@@ -61,7 +63,6 @@
61
63
  "cookie-parser": "^1.4.7",
62
64
  "express": "^4.21.1",
63
65
  "express-async-handler": "^1.2.0",
64
- "glob": "^13.0.0",
65
66
  "openapi-types": "^12.1.3",
66
67
  "zod": "^4.1.13"
67
68
  }