transit-kit 0.3.0 → 0.4.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 CHANGED
@@ -2,3 +2,344 @@
2
2
 
3
3
  [![CI](https://github.com/D4rkr34lm/transit-kit/actions/workflows/ci.yml/badge.svg)](https://github.com/D4rkr34lm/transit-kit/actions/workflows/ci.yml)
4
4
  [![Coverage Status](https://coveralls.io/repos/github/D4rkr34lm/transit-kit/badge.svg?branch=main)](https://coveralls.io/github/D4rkr34lm/transit-kit?branch=main)
5
+
6
+ A declarative TypeScript framework for building type-safe Express.js APIs with automatic OpenAPI generation.
7
+
8
+ ## Features
9
+
10
+ - 🔒 **Type-Safe**: End-to-end type safety with TypeScript and Zod validation
11
+ - 📝 **Declarative API Definition**: Define your endpoints with clear, declarative syntax
12
+ - 🔄 **Automatic Validation**: Request body and query parameter validation using Zod schemas
13
+ - 📚 **OpenAPI Generation**: Automatically generate OpenAPI documentation from your endpoint definitions
14
+ - ⚡ **Express.js Powered**: Built on top of the battle-tested Express.js framework
15
+ - 🪵 **Built-in Logging**: Request and response logging out of the box
16
+ - 🎯 **Path Parameters**: Full support for path parameters with type safety
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ npm install transit-kit
22
+ ```
23
+
24
+ ## Quick Start
25
+
26
+ ### 1. Create a Server
27
+
28
+ ```typescript
29
+ import { createServer } from "transit-kit/server";
30
+
31
+ const server = createServer({
32
+ port: 3000,
33
+ inDevMode: true,
34
+ logger: true, // or pass a custom logger (console-like interface)
35
+ });
36
+ ```
37
+
38
+ ### 2. Define an API Endpoint
39
+
40
+ ```typescript
41
+ import { createApiEndpointHandler } from "transit-kit/server";
42
+ import z from "zod";
43
+
44
+ // Define a simple GET endpoint
45
+ const getUserEndpoint = createApiEndpointHandler(
46
+ {
47
+ meta: {
48
+ name: "Get User",
49
+ description: "Retrieves a user by ID",
50
+ group: "Users",
51
+ },
52
+ method: "get",
53
+ path: "/users/:userId",
54
+ responseSchemas: {
55
+ 200: {
56
+ dataType: "application/json",
57
+ dataSchema: z.object({
58
+ id: z.string(),
59
+ name: z.string(),
60
+ email: z.string().email(),
61
+ }),
62
+ },
63
+ 404: {}, // Empty response
64
+ },
65
+ },
66
+ async (request) => {
67
+ const { userId } = request.params;
68
+
69
+ // Simulate fetching user
70
+ const user = await fetchUser(userId);
71
+
72
+ if (!user) {
73
+ return {
74
+ code: 404,
75
+ };
76
+ }
77
+
78
+ return {
79
+ code: 200,
80
+ dataType: "application/json",
81
+ json: user,
82
+ };
83
+ },
84
+ );
85
+ ```
86
+
87
+ ### 3. Define an Endpoint with Request Body and Query Parameters
88
+
89
+ ```typescript
90
+ const createUserEndpoint = createApiEndpointHandler(
91
+ {
92
+ meta: {
93
+ name: "Create User",
94
+ description: "Creates a new user",
95
+ group: "Users",
96
+ },
97
+ method: "post",
98
+ path: "/users",
99
+ requestBodySchema: z.object({
100
+ name: z.string().min(1),
101
+ email: z.string().email(),
102
+ age: z.number().min(18),
103
+ }),
104
+ querySchema: z.object({
105
+ notify: z.boolean().optional(),
106
+ }),
107
+ responseSchemas: {
108
+ 201: {
109
+ dataType: "application/json",
110
+ dataSchema: z.object({
111
+ id: z.string(),
112
+ name: z.string(),
113
+ email: z.string().email(),
114
+ }),
115
+ },
116
+ 400: {
117
+ dataType: "application/json",
118
+ dataSchema: z.object({
119
+ error: z.string(),
120
+ }),
121
+ },
122
+ },
123
+ },
124
+ async (request) => {
125
+ // Request body is automatically validated and typed
126
+ const { name, email, age } = request.body;
127
+ const { notify } = request.query;
128
+
129
+ try {
130
+ const newUser = await createUser({ name, email, age });
131
+
132
+ if (notify) {
133
+ await sendNotification(email);
134
+ }
135
+
136
+ return {
137
+ code: 201,
138
+ dataType: "application/json",
139
+ json: newUser,
140
+ };
141
+ } catch (error) {
142
+ return {
143
+ code: 400,
144
+ dataType: "application/json",
145
+ json: { error: error.message },
146
+ };
147
+ }
148
+ },
149
+ );
150
+ ```
151
+
152
+ ### 4. Register Endpoints and Start Server
153
+
154
+ ```typescript
155
+ server.registerApiEndpoint(getUserEndpoint);
156
+ server.registerApiEndpoint(createUserEndpoint);
157
+
158
+ server.start();
159
+ ```
160
+
161
+ ## Response Types
162
+
163
+ ### JSON Response
164
+
165
+ For endpoints that return JSON data:
166
+
167
+ ```typescript
168
+ return {
169
+ code: 200,
170
+ dataType: "application/json",
171
+ json: { message: "Success", data: myData },
172
+ };
173
+ ```
174
+
175
+ ### Empty Response
176
+
177
+ For endpoints that return no content (e.g., 204 No Content):
178
+
179
+ ```typescript
180
+ return {
181
+ code: 204,
182
+ };
183
+ ```
184
+
185
+ ## OpenAPI Generation
186
+
187
+ Transit-kit can automatically generate OpenAPI documentation from your endpoint definitions.
188
+
189
+ ### Using the CLI
190
+
191
+ ```bash
192
+ npx transit-kit generate-openapi --output openapi.json --target ./src
193
+ ```
194
+
195
+ Options:
196
+
197
+ - `-o, --output <path>`: Output path for the generated OpenAPI document (default: `openapi.json`)
198
+ - `-t, --target <path>`: Target path to search for endpoint definitions (default: `.`)
199
+
200
+ ### Programmatic Usage
201
+
202
+ ```typescript
203
+ import { generateOpenApiDoc } from "transit-kit/cli";
204
+
205
+ const openApiDoc = await generateOpenApiDoc("./src");
206
+ ```
207
+
208
+ The generated OpenAPI document will include:
209
+
210
+ - All registered endpoints
211
+ - Request/response schemas
212
+ - Path parameters
213
+ - Query parameters
214
+ - Request body schemas
215
+ - Response schemas for all status codes
216
+
217
+ ## Configuration
218
+
219
+ ### Server Configuration
220
+
221
+ ```typescript
222
+ interface ServerConfig {
223
+ inDevMode: boolean; // Enable development mode features
224
+ port: number; // Port to listen on
225
+ logger: Logger | boolean; // Logger instance or boolean to enable/disable
226
+ }
227
+ ```
228
+
229
+ ### Custom Logger
230
+
231
+ You can provide a custom logger with a console-like interface:
232
+
233
+ ```typescript
234
+ const customLogger = {
235
+ log: (message: string) => {
236
+ /* custom logging */
237
+ },
238
+ error: (message: string) => {
239
+ /* custom error logging */
240
+ },
241
+ // ... other console methods
242
+ };
243
+
244
+ const server = createServer({
245
+ port: 3000,
246
+ inDevMode: false,
247
+ logger: customLogger,
248
+ });
249
+ ```
250
+
251
+ ## API Reference
252
+
253
+ ### `createServer(config: ServerConfig): Server`
254
+
255
+ Creates a new server instance with the specified configuration.
256
+
257
+ ### `createApiEndpointHandler(definition, handler)`
258
+
259
+ Creates an API endpoint handler with type-safe request/response handling.
260
+
261
+ **Definition properties:**
262
+
263
+ - `meta`: Metadata about the endpoint (name, description, group)
264
+ - `method`: HTTP method (`get`, `post`, `put`, `patch`, `delete`)
265
+ - `path`: Express-style path with optional parameters (e.g., `/users/:userId`)
266
+ - `requestBodySchema`: (Optional) Zod schema for request body validation
267
+ - `querySchema`: (Optional) Zod schema for query parameter validation
268
+ - `responseSchemas`: Map of status codes to response schemas
269
+
270
+ ### `server.registerApiEndpoint(endpoint)`
271
+
272
+ Registers an endpoint with the server.
273
+
274
+ ### `server.start()`
275
+
276
+ Starts the Express server on the configured port.
277
+
278
+ ## Examples
279
+
280
+ ### Complete Example
281
+
282
+ ```typescript
283
+ import { createServer, createApiEndpointHandler } from "transit-kit/server";
284
+ import z from "zod";
285
+
286
+ // Create server
287
+ const server = createServer({
288
+ port: 3000,
289
+ inDevMode: true,
290
+ logger: true,
291
+ });
292
+
293
+ // Define endpoints
294
+ const listUsersEndpoint = createApiEndpointHandler(
295
+ {
296
+ meta: {
297
+ name: "List Users",
298
+ description: "Get a list of all users",
299
+ group: "Users",
300
+ },
301
+ method: "get",
302
+ path: "/users",
303
+ querySchema: z.object({
304
+ page: z.number().optional(),
305
+ limit: z.number().optional(),
306
+ }),
307
+ responseSchemas: {
308
+ 200: {
309
+ dataType: "application/json",
310
+ dataSchema: z.array(
311
+ z.object({
312
+ id: z.string(),
313
+ name: z.string(),
314
+ }),
315
+ ),
316
+ },
317
+ },
318
+ },
319
+ async (request) => {
320
+ const { page = 1, limit = 10 } = request.query;
321
+ const users = await fetchUsers(page, limit);
322
+
323
+ return {
324
+ code: 200,
325
+ dataType: "application/json",
326
+ json: users,
327
+ };
328
+ },
329
+ );
330
+
331
+ server.registerApiEndpoint(listUsersEndpoint);
332
+ server.start();
333
+ ```
334
+
335
+ ## License
336
+
337
+ MIT
338
+
339
+ ## Contributing
340
+
341
+ Contributions are welcome! Please feel free to submit a Pull Request.
342
+
343
+ ## Author
344
+
345
+ D4rkr34lm
@@ -31,6 +31,24 @@ function extractQueryParameters(querySchema) {
31
31
  return [];
32
32
  }
33
33
  }
34
+ function extractRequestSecuritySchemes(definition) {
35
+ const securitySchemes = definition.securitySchemes;
36
+ if (hasValue(securitySchemes)) {
37
+ return securitySchemes.map((schema) => {
38
+ switch (schema.type) {
39
+ case "http":
40
+ return {
41
+ [schema.name]: [],
42
+ };
43
+ default:
44
+ throw new Error(`Unsupported security scheme type: ${schema.type}`);
45
+ }
46
+ });
47
+ }
48
+ else {
49
+ return [];
50
+ }
51
+ }
34
52
  function translateToOpenAPIPathItem(definition) {
35
53
  const { meta, path, method, requestBodySchema, querySchema, responseSchemas, } = definition;
36
54
  // 1. Path and Parameter extraction
@@ -80,6 +98,8 @@ function translateToOpenAPIPathItem(definition) {
80
98
  .reduce((acc, resp) => {
81
99
  return { ...acc, ...resp };
82
100
  }, {});
101
+ // 5. Security Requirements
102
+ const securityRequirements = extractRequestSecuritySchemes(definition);
83
103
  const operation = {
84
104
  operationId: meta.name,
85
105
  summary: meta.description,
@@ -88,12 +108,35 @@ function translateToOpenAPIPathItem(definition) {
88
108
  parameters: operationParameters,
89
109
  ...requestBody,
90
110
  responses,
111
+ security: securityRequirements,
91
112
  };
92
113
  const pathItem = {
93
114
  [method.toLowerCase()]: operation,
94
115
  };
95
116
  return [openApiPath, pathItem];
96
117
  }
118
+ function extractSecuritySchemes(endpointDefinitions) {
119
+ const securitySchemes = Array.from(new Set(endpointDefinitions
120
+ .map((def) => def.securitySchemes)
121
+ .filter(hasValue)
122
+ .flat()));
123
+ const openApiSecuritySchemes = securitySchemes.map((scheme) => {
124
+ switch (scheme.type) {
125
+ case "http":
126
+ return {
127
+ [scheme.name]: {
128
+ type: "http",
129
+ scheme: scheme.scheme,
130
+ },
131
+ };
132
+ default:
133
+ throw new Error(`Unsupported security scheme type: ${scheme.type}`);
134
+ }
135
+ });
136
+ return openApiSecuritySchemes.reduce((acc, scheme) => {
137
+ return { ...acc, ...scheme };
138
+ }, {});
139
+ }
97
140
  export async function generateOpenApiDoc(targetPath) {
98
141
  const serverModule = await import(path.resolve(process.cwd(), targetPath));
99
142
  const server = serverModule.default;
@@ -114,6 +157,7 @@ export async function generateOpenApiDoc(targetPath) {
114
157
  }
115
158
  return acc;
116
159
  }, {});
160
+ const securitySchemes = extractSecuritySchemes(endpointDefinitions);
117
161
  const openApiDocument = {
118
162
  openapi: "3.0.0",
119
163
  info: {
@@ -121,6 +165,9 @@ export async function generateOpenApiDoc(targetPath) {
121
165
  version: "1.0.0",
122
166
  },
123
167
  paths: paths,
168
+ components: {
169
+ securitySchemes,
170
+ },
124
171
  };
125
172
  return openApiDocument;
126
173
  }
@@ -47,6 +47,7 @@ describe("translateToOpenAPIPathItem", () => {
47
47
  schema: { type: "string" },
48
48
  },
49
49
  ],
50
+ security: [],
50
51
  responses: {
51
52
  "200": {
52
53
  description: "Response for status code 200",
@@ -1,16 +1,18 @@
1
1
  import z from "zod";
2
2
  import { HttpMethod } from "../../constants/HttpMethods";
3
+ import { SecurityScheme } from "../../security/SecuritySchema";
3
4
  import { GenericResponseSchemaMap } from "./responses/index";
4
5
  export interface ApiEndpointMeta {
5
6
  name: string;
6
7
  group: string;
7
8
  description: string;
8
9
  }
9
- export type ApiEndpointDefinition<Path extends string = string, Method extends HttpMethod = HttpMethod, RequestBody extends z.ZodType | undefined = z.ZodType | undefined, Query extends z.ZodType | undefined = z.ZodType | undefined, ResponseMap extends GenericResponseSchemaMap = GenericResponseSchemaMap> = {
10
+ export type ApiEndpointDefinition<Path extends string = string, Method extends HttpMethod = HttpMethod, RequestBody extends z.ZodType | undefined = z.ZodType | undefined, Query extends z.ZodType | undefined = z.ZodType | undefined, ResponseMap extends GenericResponseSchemaMap = GenericResponseSchemaMap, SecuritySchemes extends SecurityScheme<unknown>[] = SecurityScheme<unknown>[]> = {
10
11
  meta: ApiEndpointMeta;
11
12
  path: Path;
12
13
  method: Method;
13
14
  requestBodySchema?: RequestBody;
14
15
  querySchema?: Query;
15
16
  responseSchemas: ResponseMap;
17
+ securitySchemes?: SecuritySchemes;
16
18
  };
@@ -1,6 +1,11 @@
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> = {}, 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, Caller = unknown> = (request: Request<PathParams, unknown, RequestBody, Query, Record<string, unknown>>, extractedRequestData: {
5
+ parameters: PathParams;
6
+ query: Query;
7
+ body: RequestBody;
8
+ caller: Caller;
9
+ }) => Promise<Responses | {
5
10
  code: (typeof HttpStatusCodes)["InternalServerError_500"];
6
11
  }>;
@@ -1,11 +1,12 @@
1
1
  import z from "zod";
2
2
  import { HttpStatusCode } from "../../constants/HttpStatusCodes";
3
+ import { SecurityScheme } from "../../security/SecuritySchema";
3
4
  import { Prettify } from "../../utils/types";
4
5
  import { ApiEndpointHandler } from "./EndpointHandler";
5
6
  import { ExtractPathParams } from "./PathParameters";
6
7
  import { EmptyResponse, EmptyResponseSchema } from "./responses/emptyResponse";
7
8
  import { GenericResponse, GenericResponseSchemaMap } from "./responses/index";
8
9
  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>, Exclude<Prettify<{
10
+ export type HandlerForDefinition<Path extends string, RequestBody extends z.ZodType | undefined, Query extends z.ZodType | undefined, ResponsesMap extends GenericResponseSchemaMap, SecuritySchemas extends SecurityScheme<unknown>[] = []> = ApiEndpointHandler<ExtractPathParams<Path>, RequestBody extends undefined ? undefined : z.infer<RequestBody>, Query extends undefined ? undefined : z.infer<Query>, Exclude<Prettify<{
10
11
  [K in keyof ResponsesMap]: K extends HttpStatusCode ? ResponsesMap[K] extends JsonResponseSchema ? JsonResponseSchemaToResponseType<K, ResponsesMap[K]> : ResponsesMap[K] extends EmptyResponseSchema ? EmptyResponse<K> : ResponsesMap[K] extends undefined ? never : GenericResponse : never;
11
- }[keyof ResponsesMap]>, undefined>>;
12
+ }[keyof ResponsesMap]>, undefined>, SecuritySchemas extends SecurityScheme<infer Caller>[] ? Caller : unknown>;
@@ -1,11 +1,12 @@
1
1
  import z from "zod";
2
2
  import { HttpMethod } from "../../constants/HttpMethods";
3
+ import { SecurityScheme } from "../../security/SecuritySchema";
3
4
  import { Prettify } from "../../utils/types";
4
5
  import { ApiEndpointDefinition } from "./EndpointDefinition";
5
6
  import { ApiEndpointHandler } from "./EndpointHandler";
6
7
  import { HandlerForDefinition } from "./HandlerFromDefinition";
7
8
  import { GenericResponse, GenericResponseSchemaMap } from "./responses";
8
- 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: Prettify<ApiEndpointDefinition<Path, Method, RequestBody, Query, ResponsesMap>>, handler: HandlerForDefinition<Path, RequestBody, Query, ResponsesMap>): {
9
+ 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, const SecuritySchemas extends SecurityScheme<unknown>[] = []>(definition: Prettify<ApiEndpointDefinition<Path, Method, RequestBody, Query, ResponsesMap, SecuritySchemas>>, handler: HandlerForDefinition<Path, RequestBody, Query, ResponsesMap>): {
9
10
  definition: {
10
11
  meta: import("./EndpointDefinition").ApiEndpointMeta;
11
12
  path: Path;
@@ -13,6 +14,7 @@ export declare function createApiEndpointHandler<const ResponsesMap extends Gene
13
14
  requestBodySchema?: RequestBody | undefined;
14
15
  querySchema?: Query | undefined;
15
16
  responseSchemas: ResponsesMap;
17
+ securitySchemes?: SecuritySchemas | undefined;
16
18
  };
17
19
  handler: HandlerForDefinition<Path, RequestBody, Query, ResponsesMap>;
18
20
  };
@@ -8,7 +8,14 @@ export function createApiEndpointHandler(definition, handler) {
8
8
  }
9
9
  export function buildApiEndpointHandler(handler) {
10
10
  return expressAsyncHandler(async (request, response) => {
11
- const result = await handler(request);
11
+ const caller = response.locals.caller;
12
+ const extractedRequestData = {
13
+ parameters: request.params,
14
+ query: request.query,
15
+ body: request.body,
16
+ caller,
17
+ };
18
+ const result = await handler(request, extractedRequestData);
12
19
  if (isJsonResponse(result)) {
13
20
  response.status(result.code).json(result.json);
14
21
  }
@@ -1,4 +1,5 @@
1
1
  export { createApiEndpointHandler } from "./handlers/api/createApiHandler";
2
+ export { buildAuthenticationMiddleware } from "./middleware/auth";
2
3
  export { createServer, type Server, type ServerConfig } from "./server";
3
4
  declare const _default: {};
4
5
  export default _default;
@@ -1,3 +1,4 @@
1
1
  export { createApiEndpointHandler } from "./handlers/api/createApiHandler";
2
+ export { buildAuthenticationMiddleware } from "./middleware/auth";
2
3
  export { createServer } from "./server";
3
4
  export default {};
@@ -0,0 +1,2 @@
1
+ import { SecurityScheme } from "../security/SecuritySchema";
2
+ export declare function buildAuthenticationMiddleware<Caller>(schemes: SecurityScheme<Caller>[]): import("express").RequestHandler<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
@@ -0,0 +1,13 @@
1
+ import expressAsyncHandler from "express-async-handler";
2
+ import { authenticate } from "../security/SecuritySchema";
3
+ export function buildAuthenticationMiddleware(schemes) {
4
+ return expressAsyncHandler(async (request, response, next) => {
5
+ const caller = await authenticate(schemes, request);
6
+ if (caller == null) {
7
+ response.status(401).json({ message: "Unauthorized" });
8
+ return;
9
+ }
10
+ response.locals.caller = caller;
11
+ next();
12
+ });
13
+ }
@@ -0,0 +1,5 @@
1
+ import { Request } from "express";
2
+ import { BasicAuthScheme } from "./basicAuth";
3
+ import { BearerAuthScheme } from "./bearerAuth";
4
+ export type SecurityScheme<Caller> = BasicAuthScheme<Caller> | BearerAuthScheme<Caller>;
5
+ export declare function authenticate<Caller>(schemes: SecurityScheme<Caller>[], request: Request): Promise<Caller | null>;
@@ -0,0 +1,14 @@
1
+ import { buildBasicAuthenticator } from "./basicAuth";
2
+ import { buildBearerAuthenticator } from "./bearerAuth";
3
+ export async function authenticate(schemes, request) {
4
+ const authenticationResults = await Promise.all(schemes.map((scheme) => {
5
+ switch (scheme.scheme) {
6
+ case "basic":
7
+ return buildBasicAuthenticator(scheme)(request);
8
+ case "bearer":
9
+ return buildBearerAuthenticator(scheme)(request);
10
+ }
11
+ }));
12
+ const successfulAuthentication = authenticationResults.find((result) => result !== null);
13
+ return successfulAuthentication ?? null;
14
+ }
@@ -0,0 +1,9 @@
1
+ import { Request } from "express";
2
+ export interface BasicAuthScheme<Caller = unknown> {
3
+ name: string;
4
+ type: "http";
5
+ scheme: "basic";
6
+ validateCaller: (username: string, password: string) => Promise<Caller | null>;
7
+ }
8
+ export declare function createBasicAuthSchema<Caller>(name: string, validateCaller: (username: string, password: string) => Promise<Caller | null>): BasicAuthScheme<Caller>;
9
+ export declare function buildBasicAuthenticator<Caller>(scheme: BasicAuthScheme<Caller>): (request: Request) => Promise<Caller | null>;
@@ -0,0 +1,21 @@
1
+ import { hasNoValue } from "../utils/typeGuards";
2
+ export function createBasicAuthSchema(name, validateCaller) {
3
+ return {
4
+ name,
5
+ type: "http",
6
+ scheme: "basic",
7
+ validateCaller,
8
+ };
9
+ }
10
+ export function buildBasicAuthenticator(scheme) {
11
+ return async (request) => {
12
+ const authHeader = request.headers.authorization;
13
+ if (hasNoValue(authHeader) || !authHeader.startsWith("Basic ")) {
14
+ return null;
15
+ }
16
+ const base64Credentials = authHeader.slice("Basic ".length);
17
+ const credentials = Buffer.from(base64Credentials, "base64").toString("utf-8");
18
+ const [username, password] = credentials.split(":", 2);
19
+ return scheme.validateCaller(username, password);
20
+ };
21
+ }
@@ -0,0 +1,9 @@
1
+ import { Request } from "express";
2
+ export interface BearerAuthScheme<Caller = unknown> {
3
+ name: string;
4
+ type: "http";
5
+ scheme: "bearer";
6
+ validateCaller: (token: string) => Promise<Caller | null>;
7
+ }
8
+ export declare function createBearerAuthSchema<Caller>(name: string, validateCaller: (token: string) => Promise<Caller | null>): BearerAuthScheme<Caller>;
9
+ export declare function buildBearerAuthenticator<Caller>(scheme: BearerAuthScheme<Caller>): (request: Request) => Promise<Caller | null>;
@@ -0,0 +1,18 @@
1
+ export function createBearerAuthSchema(name, validateCaller) {
2
+ return {
3
+ name,
4
+ type: "http",
5
+ scheme: "bearer",
6
+ validateCaller,
7
+ };
8
+ }
9
+ export function buildBearerAuthenticator(scheme) {
10
+ return async (request) => {
11
+ const authHeader = request.headers.authorization;
12
+ if (authHeader == null || !authHeader.startsWith("Bearer ")) {
13
+ return null;
14
+ }
15
+ const token = authHeader.slice("Bearer ".length);
16
+ return scheme.validateCaller(token);
17
+ };
18
+ }
@@ -1,13 +1,18 @@
1
1
  import cookieParser from "cookie-parser";
2
2
  import express from "express";
3
3
  import { buildApiEndpointHandler } from "./handlers/api/createApiHandler";
4
+ import { buildAuthenticationMiddleware } from "./middleware/auth";
4
5
  import { buildRequestLogger, buildResponseLogger } from "./middleware/logging";
5
6
  import { buildBodyValidatorMiddleware, buildQueryValidatorMiddleware, } from "./middleware/validation";
7
+ import { isEmpty } from "./utils/funcs";
6
8
  import { NoOpLogger } from "./utils/logging";
7
9
  import { hasNoValue, hasValue } from "./utils/typeGuards";
8
10
  function registerApiEndpoint(expressApp, endpoint) {
9
11
  const { definition, handler } = endpoint;
10
12
  const handlerStack = [
13
+ hasValue(definition.securitySchemes) && !isEmpty(definition.securitySchemes)
14
+ ? buildAuthenticationMiddleware(definition.securitySchemes)
15
+ : null,
11
16
  hasValue(definition.querySchema)
12
17
  ? buildQueryValidatorMiddleware(definition.querySchema)
13
18
  : null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "transit-kit",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "A declarative TypeScript framework for building type-safe Express.js APIs with automatic OpenAPI generation",
5
5
  "keywords": [
6
6
  "express",