@rexeus/typeweaver-hono 0.0.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/README.md ADDED
@@ -0,0 +1,61 @@
1
+ # @rexeus/typeweaver-hono
2
+
3
+ TypeWeaver plugin for generating type-safe Hono routers from HTTP operation definitions.
4
+
5
+ ## Overview
6
+
7
+ This plugin generates Hono router classes that automatically handle request validation, type-safe
8
+ routing, and error responses. Each entity gets its own router class extending `TypeweaverHono` with
9
+ full TypeScript type inference.
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ npm install @rexeus/typeweaver-hono
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ```bash
20
+ # Via CLI
21
+ npx typeweaver generate --plugins hono --input ./definitions --output ./generated
22
+
23
+ # Via config file
24
+ npx typeweaver generate --config ./typeweaver.config.js
25
+ ```
26
+
27
+ ```javascript
28
+ // typeweaver.config.js
29
+ export default {
30
+ input: "./api/definitions",
31
+ output: "./api/generated",
32
+ plugins: ["hono"],
33
+ };
34
+ ```
35
+
36
+ ## Example
37
+
38
+ ## Features
39
+
40
+ - **Type-safe route handlers** - Full TypeScript inference for requests and responses
41
+ - **Automatic request validation** - Built-in validation using generated validators
42
+ - **Configurable error handling** - Customize validation and error responses
43
+ - **Pure Hono compatibility** - Works with all Hono middleware and features
44
+
45
+ ## Configuration Options
46
+
47
+ ```typescript
48
+ new ProjectHono({
49
+ requestHandlers: handlers,
50
+
51
+ // Optional configurations, for example:
52
+ validateRequests: false, // Disable automatic validation
53
+ handleValidationErrors: false, // Let validation errors bubble up
54
+ handleHttpResponseErrors: true, // Handle thrown HttpResponse errors
55
+ handleUnknownErrors: customHandler, // Custom error handler function
56
+ });
57
+ ```
58
+
59
+ ## License
60
+
61
+ ISC © Dennis Wentzien 2025
@@ -0,0 +1,8 @@
1
+ import { BasePlugin, GeneratorContext } from '@rexeus/typeweaver-gen';
2
+
3
+ declare class HonoPlugin extends BasePlugin {
4
+ name: string;
5
+ generate(context: GeneratorContext): void;
6
+ }
7
+
8
+ export { HonoPlugin as default };
package/dist/index.js ADDED
@@ -0,0 +1,55 @@
1
+ import { BasePlugin } from '@rexeus/typeweaver-gen';
2
+ import Case from 'case';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { fileURLToPath as fileURLToPath$1 } from 'url';
6
+ import path$1 from 'path';
7
+
8
+ class HonoRouterGenerator {
9
+ static generate(context) {
10
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
11
+ const templateFile = path.join(__dirname, "templates", "HonoRouter.ejs");
12
+ for (const [entityName, operationResources] of Object.entries(
13
+ context.resources.entityResources
14
+ )) {
15
+ this.writeHonoRouter(entityName, templateFile, operationResources, context);
16
+ }
17
+ }
18
+ static writeHonoRouter(entityName, templateFile, operationResources, context) {
19
+ const pascalCaseEntityName = Case.pascal(entityName);
20
+ const outputDir = path.join(context.outputDir, entityName);
21
+ const outputPath = path.join(outputDir, `${pascalCaseEntityName}Hono.ts`);
22
+ const operations = operationResources.map((resource) => this.createOperationData(resource));
23
+ const content = context.renderTemplate(templateFile, {
24
+ coreDir: path.relative(outputDir, context.outputDir),
25
+ entityName,
26
+ pascalCaseEntityName,
27
+ operations
28
+ });
29
+ const relativePath = path.relative(context.outputDir, outputPath);
30
+ context.writeFile(relativePath, content);
31
+ }
32
+ static createOperationData(resource) {
33
+ const operationId = resource.definition.operationId;
34
+ const className = Case.pascal(operationId);
35
+ const handlerName = `handle${className}Request`;
36
+ return {
37
+ className,
38
+ handlerName,
39
+ method: resource.definition.method,
40
+ path: resource.definition.path
41
+ };
42
+ }
43
+ }
44
+
45
+ const __dirname = path$1.dirname(fileURLToPath$1(import.meta.url));
46
+ class HonoPlugin extends BasePlugin {
47
+ name = "hono";
48
+ generate(context) {
49
+ const libSourceDir = path$1.join(__dirname, "lib");
50
+ this.copyLibFiles(context, libSourceDir, this.name);
51
+ HonoRouterGenerator.generate(context);
52
+ }
53
+ }
54
+
55
+ export { HonoPlugin as default };
@@ -0,0 +1,161 @@
1
+ import type {
2
+ HttpMethod,
3
+ IHttpRequest,
4
+ IHttpResponse,
5
+ IHttpHeader,
6
+ IHttpQuery,
7
+ IHttpBody,
8
+ } from "@rexeus/typeweaver-core";
9
+ import { HttpAdapter } from "./HttpAdapter";
10
+ import { normalizeHeaders } from "./normalizeHeaders";
11
+
12
+ /**
13
+ * Adapter for converting between Fetch API Request/Response objects
14
+ * and the framework-agnostic HTTP types.
15
+ *
16
+ * This adapter works with the Request and Response objects from the Fetch API
17
+ * specification, which are available in modern JavaScript runtimes (browsers,
18
+ * Node.js 18+, Deno, Bun, Cloudflare Workers, etc.).
19
+ */
20
+ export class FetchApiAdapter extends HttpAdapter<Request, Response> {
21
+ /**
22
+ * Converts a Fetch API Request to an IHttpRequest.
23
+ * Extracts headers, query parameters, and body from the Request object.
24
+ *
25
+ * @param request - The Fetch API Request object
26
+ * @param pathParams - Optional path parameters (not available in Fetch API Request)
27
+ * @returns Promise resolving to an IHttpRequest
28
+ */
29
+ public async toRequest(
30
+ request: Request,
31
+ pathParams?: Record<string, string>
32
+ ): Promise<IHttpRequest> {
33
+ const url = new URL(request.url);
34
+
35
+ return {
36
+ method: request.method.toUpperCase() as HttpMethod,
37
+ path: url.pathname,
38
+ header: this.extractHeaders(request.headers),
39
+ query: this.extractQueryParams(url),
40
+ param:
41
+ pathParams && Object.keys(pathParams).length > 0
42
+ ? pathParams
43
+ : undefined,
44
+ body: await this.parseRequestBody(request),
45
+ };
46
+ }
47
+
48
+ /**
49
+ * Converts an IHttpResponse to a Fetch API Response.
50
+ * Creates a Response object with the appropriate status, headers, and body.
51
+ *
52
+ * @param response - The IHttpResponse to convert
53
+ * @returns A Fetch API Response object
54
+ */
55
+ public toResponse(response: IHttpResponse): Response {
56
+ const { statusCode, body, header } = response;
57
+
58
+ return new Response(this.buildResponseBody(body), {
59
+ status: statusCode,
60
+ headers: this.buildResponseHeaders(header),
61
+ });
62
+ }
63
+
64
+ private addMultiValue(
65
+ record: Record<string, string | string[]>,
66
+ key: string,
67
+ value: string
68
+ ): void {
69
+ const existing = record[key];
70
+ if (existing) {
71
+ if (Array.isArray(existing)) {
72
+ existing.push(value);
73
+ } else {
74
+ record[key] = [existing, value];
75
+ }
76
+ } else {
77
+ record[key] = value;
78
+ }
79
+ }
80
+
81
+ private extractHeaders(headers: Headers): IHttpHeader {
82
+ const result: Record<string, string | string[]> = {};
83
+ headers.forEach((value, key) => {
84
+ this.addMultiValue(result, key, value);
85
+ });
86
+ return normalizeHeaders(result);
87
+ }
88
+
89
+ private extractQueryParams(url: URL): IHttpQuery {
90
+ const result: Record<string, string | string[]> = {};
91
+ url.searchParams.forEach((value, key) => {
92
+ this.addMultiValue(result, key, value);
93
+ });
94
+ return Object.keys(result).length > 0 ? result : undefined;
95
+ }
96
+
97
+ private async parseRequestBody(request: Request): Promise<IHttpBody> {
98
+ if (!request.body) return undefined;
99
+
100
+ const contentType = request.headers.get("content-type");
101
+
102
+ if (contentType?.includes("application/json")) {
103
+ try {
104
+ return await request.json();
105
+ } catch {
106
+ return undefined;
107
+ }
108
+ }
109
+
110
+ if (contentType?.includes("text/")) {
111
+ return await request.text();
112
+ }
113
+
114
+ if (contentType?.includes("application/x-www-form-urlencoded")) {
115
+ const text = await request.text();
116
+ const formData = new URLSearchParams(text);
117
+ const formObject: Record<string, string | string[]> = {};
118
+ formData.forEach((value, key) => {
119
+ this.addMultiValue(formObject, key, value);
120
+ });
121
+ return formObject;
122
+ }
123
+
124
+ const rawBody = await request.text();
125
+ return rawBody || undefined;
126
+ }
127
+
128
+ private buildResponseBody(body: any): string | ArrayBuffer | Blob | null {
129
+ if (body === undefined) {
130
+ return null;
131
+ }
132
+
133
+ if (
134
+ typeof body === "string" ||
135
+ body instanceof Blob ||
136
+ body instanceof ArrayBuffer
137
+ ) {
138
+ return body;
139
+ }
140
+
141
+ return JSON.stringify(body);
142
+ }
143
+
144
+ private buildResponseHeaders(header?: IHttpHeader): Headers {
145
+ const headers = new Headers();
146
+
147
+ if (header) {
148
+ Object.entries(header).forEach(([key, value]) => {
149
+ if (value !== undefined) {
150
+ if (Array.isArray(value)) {
151
+ value.forEach(v => headers.append(key, v));
152
+ } else {
153
+ headers.set(key, String(value));
154
+ }
155
+ }
156
+ });
157
+ }
158
+
159
+ return headers;
160
+ }
161
+ }
@@ -0,0 +1,44 @@
1
+ import type { Context } from "hono";
2
+ import type { IHttpRequest, IHttpResponse } from "@rexeus/typeweaver-core";
3
+ import { FetchApiAdapter } from "./FetchApiAdapter";
4
+ import { HttpAdapter } from "./HttpAdapter";
5
+
6
+ /**
7
+ * Adapter for converting between Hono's Context and the core HTTP types.
8
+ *
9
+ * This adapter extends the FetchApiAdapter functionality by adding
10
+ * Hono-specific features like path parameter extraction.
11
+ */
12
+ export class HonoAdapter extends HttpAdapter<Context, Response> {
13
+ private fetchAdapter = new FetchApiAdapter();
14
+
15
+ /**
16
+ * Converts a Hono Context to an IHttpRequest.
17
+ *
18
+ * Leverages the FetchApiAdapter for standard Request processing
19
+ * and adds Hono-specific path parameter extraction.
20
+ *
21
+ * @param c - The Hono context
22
+ * @returns Promise resolving to an IHttpRequest
23
+ */
24
+ public async toRequest(c: Context): Promise<IHttpRequest> {
25
+ const pathParams = c.req.param();
26
+ const request = await this.fetchAdapter.toRequest(c.req.raw, pathParams);
27
+
28
+ // Override the path with Hono's path (which may differ from raw URL path)
29
+ request.path = c.req.path;
30
+
31
+ return request;
32
+ }
33
+
34
+ /**
35
+ * Converts an IHttpResponse to a Hono Response.
36
+ * Delegates to FetchApiAdapter since Hono uses standard Fetch API Response objects.
37
+ *
38
+ * @param response - The IHttpResponse to convert
39
+ * @returns A Hono-compatible Response object
40
+ */
41
+ public toResponse(response: IHttpResponse): Response {
42
+ return this.fetchAdapter.toResponse(response);
43
+ }
44
+ }
@@ -0,0 +1,7 @@
1
+ import type { IHttpRequest, IHttpResponse } from "@rexeus/typeweaver-core";
2
+ import type { Context } from "hono";
3
+
4
+ export type HonoRequestHandler<
5
+ Request extends IHttpRequest,
6
+ Response extends IHttpResponse,
7
+ > = (request: Request, context: Context) => Promise<Response>;
@@ -0,0 +1,29 @@
1
+ import type { IHttpRequest, IHttpResponse } from "@rexeus/typeweaver-core";
2
+
3
+ /**
4
+ * Abstract base class for HTTP adapters.
5
+ *
6
+ * Provides a common interface for converting between different HTTP request/response
7
+ * formats and the framework-agnostic IHttpRequest/IHttpResponse types.
8
+ */
9
+ export abstract class HttpAdapter<TRequest = any, TResponse = any> {
10
+ /**
11
+ * Converts a framework-specific request to an IHttpRequest.
12
+ *
13
+ * @param request - The framework-specific request object
14
+ * @param context - Optional additional context needed for conversion
15
+ * @returns Promise resolving to an IHttpRequest
16
+ */
17
+ public abstract toRequest(
18
+ request: TRequest,
19
+ context?: any
20
+ ): Promise<IHttpRequest>;
21
+
22
+ /**
23
+ * Converts an IHttpResponse to a framework-specific response.
24
+ *
25
+ * @param response - The IHttpResponse to convert
26
+ * @returns The framework-specific response object
27
+ */
28
+ public abstract toResponse(response: IHttpResponse): TResponse;
29
+ }
@@ -0,0 +1,295 @@
1
+ import { Hono, type Context } from "hono";
2
+ import {
3
+ HttpResponse,
4
+ RequestValidationError,
5
+ type IHttpRequest,
6
+ type IHttpResponse,
7
+ type IRequestValidator,
8
+ } from "@rexeus/typeweaver-core";
9
+ import { HonoAdapter } from "./HonoAdapter";
10
+ import type { HonoRequestHandler } from "./HonoRequestHandler";
11
+ import type { HonoOptions } from "hono/hono-base";
12
+ import type { BlankEnv, BlankSchema, Env, Schema } from "hono/types";
13
+
14
+ /**
15
+ * Handles HTTP response errors thrown by request handlers.
16
+ * @param error - The HTTP response error that was thrown
17
+ * @param context - The Hono context for the current request
18
+ * @returns The HTTP response to send to the client
19
+ */
20
+ export type HttpResponseErrorHandler = (
21
+ error: HttpResponse,
22
+ context: Context
23
+ ) => Promise<IHttpResponse> | IHttpResponse;
24
+
25
+ /**
26
+ * Handles request validation errors.
27
+ * @param error - The validation error containing field-specific issues
28
+ * @param context - The Hono context for the current request
29
+ * @returns The HTTP response to send to the client
30
+ */
31
+ export type ValidationErrorHandler = (
32
+ error: RequestValidationError,
33
+ context: Context
34
+ ) => Promise<IHttpResponse> | IHttpResponse;
35
+
36
+ /**
37
+ * Handles any unknown errors not caught by other handlers.
38
+ * @param error - The unknown error (could be anything)
39
+ * @param context - The Hono context for the current request
40
+ * @returns The HTTP response to send to the client
41
+ */
42
+ export type UnknownErrorHandler = (
43
+ error: unknown,
44
+ context: Context
45
+ ) => Promise<IHttpResponse> | IHttpResponse;
46
+
47
+ /**
48
+ * Configuration options for TypeweaverHono routers.
49
+ * @template RequestHandlers - Type containing all request handler methods
50
+ * @template HonoEnv - Hono environment type for middleware context
51
+ */
52
+ export type TypeweaverHonoOptions<
53
+ RequestHandlers,
54
+ HonoEnv extends Env = BlankEnv,
55
+ > = HonoOptions<HonoEnv> & {
56
+ /**
57
+ * Request handler methods for each operation.
58
+ * Each handler receives a request (validated if `validateRequests` is true) and Hono context.
59
+ */
60
+ requestHandlers: RequestHandlers;
61
+
62
+ /**
63
+ * Enable request validation using generated validators.
64
+ * When false, requests are passed through without validation.
65
+ * @default true
66
+ */
67
+ validateRequests?: boolean;
68
+
69
+ /**
70
+ * Configure handling of HttpResponse errors thrown by handlers.
71
+ * - `true`: Use default handler (returns the error as-is)
72
+ * - `false`: Let errors bubble up to Hono
73
+ * - `function`: Use custom error handler
74
+ * @default true
75
+ */
76
+ handleHttpResponseErrors?: HttpResponseErrorHandler | boolean;
77
+
78
+ /**
79
+ * Configure handling of request validation errors.
80
+ * - `true`: Use default handler (400 with error details)
81
+ * - `false`: Let errors bubble up to Hono
82
+ * - `function`: Use custom error handler
83
+ * @default true
84
+ */
85
+ handleValidationErrors?: ValidationErrorHandler | boolean;
86
+
87
+ /**
88
+ * Configure handling of unknown errors.
89
+ * - `true`: Use default handler (500 Internal Server Error)
90
+ * - `false`: Let errors bubble up to Hono
91
+ * - `function`: Use custom error handler
92
+ * @default true
93
+ */
94
+ handleUnknownErrors?: UnknownErrorHandler | boolean;
95
+ };
96
+
97
+ /**
98
+ * Abstract base class for TypeWeaver-generated Hono routers.
99
+ *
100
+ * Extends Hono with TypeWeaver-specific features:
101
+ * - Automatic request validation using generated validators
102
+ * - Configurable error handling for validation, HTTP, and unknown errors
103
+ * - Type-safe request/response handling with adapters
104
+ *
105
+ * @template RequestHandlers - Object containing typed request handler methods
106
+ * @template HonoEnv - Hono environment type (default: BlankEnv)
107
+ * @template HonoSchema - Hono schema type (default: BlankSchema)
108
+ * @template HonoBasePath - Base path for routes (default: "/")
109
+ */
110
+ export abstract class TypeweaverHono<
111
+ RequestHandlers,
112
+ HonoEnv extends Env = BlankEnv,
113
+ HonoSchema extends Schema = BlankSchema,
114
+ HonoBasePath extends string = "/",
115
+ > extends Hono<HonoEnv, HonoSchema, HonoBasePath> {
116
+ /**
117
+ * Adapter for converting between Hono and TypeWeaver request/response formats.
118
+ */
119
+ protected readonly adapter = new HonoAdapter();
120
+
121
+ /**
122
+ * Request handlers provided during construction.
123
+ */
124
+ protected readonly requestHandlers: RequestHandlers;
125
+
126
+ /**
127
+ * Resolved configuration for validation and error handling.
128
+ */
129
+ private readonly config: {
130
+ validateRequests: boolean;
131
+ errorHandlers: {
132
+ validation: ValidationErrorHandler | undefined;
133
+ httpResponse: HttpResponseErrorHandler | undefined;
134
+ unknown: UnknownErrorHandler | undefined;
135
+ };
136
+ };
137
+
138
+ /**
139
+ * Default error handlers used when custom handlers are not provided.
140
+ */
141
+ private readonly defaultHandlers = {
142
+ validation: (error: RequestValidationError): IHttpResponse => ({
143
+ statusCode: 400,
144
+ body: {
145
+ error: {
146
+ code: "VALIDATION_ERROR",
147
+ message: error.message,
148
+ details: {
149
+ headers: error.headerIssues,
150
+ body: error.bodyIssues,
151
+ query: error.queryIssues,
152
+ params: error.pathParamIssues,
153
+ },
154
+ },
155
+ },
156
+ }),
157
+
158
+ httpResponse: (error: HttpResponse): IHttpResponse => error,
159
+
160
+ unknown: (): IHttpResponse => ({
161
+ statusCode: 500,
162
+ body: {
163
+ error: {
164
+ code: "INTERNAL_SERVER_ERROR",
165
+ message: "An unexpected error occurred.",
166
+ },
167
+ },
168
+ }),
169
+ };
170
+
171
+ /**
172
+ * Creates a new TypeweaverHono router instance.
173
+ *
174
+ * @param options - Configuration options including request handlers and error handling
175
+ * @param options.requestHandlers - Object containing all request handler methods
176
+ * @param options.validateRequests - Whether to validate requests (default: true)
177
+ * @param options.handleHttpResponseErrors - Handler or boolean for HTTP errors (default: true)
178
+ * @param options.handleValidationErrors - Handler or boolean for validation errors (default: true)
179
+ * @param options.handleUnknownErrors - Handler or boolean for unknown errors (default: true)
180
+ */
181
+ public constructor(options: TypeweaverHonoOptions<RequestHandlers, HonoEnv>) {
182
+ const {
183
+ requestHandlers,
184
+ validateRequests = true,
185
+ handleHttpResponseErrors,
186
+ handleValidationErrors,
187
+ handleUnknownErrors,
188
+ ...honoOptions
189
+ } = options;
190
+
191
+ super(honoOptions);
192
+
193
+ this.requestHandlers = requestHandlers;
194
+
195
+ // Resolve configuration
196
+ this.config = {
197
+ validateRequests,
198
+ errorHandlers: {
199
+ validation: this.resolveErrorHandler(handleValidationErrors, error =>
200
+ this.defaultHandlers.validation(error)
201
+ ),
202
+ httpResponse: this.resolveErrorHandler(
203
+ handleHttpResponseErrors,
204
+ error => this.defaultHandlers.httpResponse(error)
205
+ ),
206
+ unknown: this.resolveErrorHandler(handleUnknownErrors, () =>
207
+ this.defaultHandlers.unknown()
208
+ ),
209
+ },
210
+ };
211
+
212
+ this.registerErrorHandler();
213
+ }
214
+
215
+ /**
216
+ * Resolves error handler configuration to a handler function or undefined.
217
+ *
218
+ * @param option - Boolean to enable/disable or custom handler function
219
+ * @param defaultHandler - Default handler to use when option is true
220
+ * @returns Resolved handler function or undefined if disabled
221
+ */
222
+ private resolveErrorHandler<T extends Function>(
223
+ option: T | boolean | undefined,
224
+ defaultHandler: T
225
+ ): T | undefined {
226
+ if (option === false) return undefined;
227
+ if (option === true || option === undefined) return defaultHandler;
228
+ return option;
229
+ }
230
+
231
+ /**
232
+ * Registers the global error handler with Hono.
233
+ * Processes errors in order: validation, HTTP response, unknown.
234
+ */
235
+ protected registerErrorHandler(): void {
236
+ this.onError(async (error, context) => {
237
+ // Handle validation errors
238
+ if (
239
+ error instanceof RequestValidationError &&
240
+ this.config.errorHandlers.validation
241
+ ) {
242
+ return this.adapter.toResponse(
243
+ await this.config.errorHandlers.validation(error, context)
244
+ );
245
+ }
246
+
247
+ // Handle HTTP response errors
248
+ if (
249
+ error instanceof HttpResponse &&
250
+ this.config.errorHandlers.httpResponse
251
+ ) {
252
+ return this.adapter.toResponse(
253
+ await this.config.errorHandlers.httpResponse(error, context)
254
+ );
255
+ }
256
+
257
+ // Handle unknown errors
258
+ if (this.config.errorHandlers.unknown) {
259
+ return this.adapter.toResponse(
260
+ await this.config.errorHandlers.unknown(error, context)
261
+ );
262
+ }
263
+
264
+ // Default: re-throw
265
+ throw error;
266
+ });
267
+ }
268
+
269
+ /**
270
+ * Handles a request with validation and type-safe response conversion.
271
+ *
272
+ * @param context - Hono context for the current request
273
+ * @param validator - Request validator for the specific operation
274
+ * @param handler - Type-safe request handler function
275
+ * @returns Hono-compatible Response object
276
+ */
277
+ protected async handleRequest<
278
+ TRequest extends IHttpRequest,
279
+ TResponse extends IHttpResponse,
280
+ >(
281
+ context: Context,
282
+ validator: IRequestValidator,
283
+ handler: HonoRequestHandler<TRequest, TResponse>
284
+ ): Promise<Response> {
285
+ const httpRequest = await this.adapter.toRequest(context);
286
+
287
+ // Conditionally validate
288
+ const validatedRequest = this.config.validateRequests
289
+ ? (validator.validate(httpRequest) as TRequest)
290
+ : (httpRequest as TRequest);
291
+
292
+ const httpResponse = await handler(validatedRequest, context);
293
+ return this.adapter.toResponse(httpResponse);
294
+ }
295
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./TypeweaverHono";
2
+ export * from "./HonoRequestHandler";
3
+ export * from "./HonoAdapter";
@@ -0,0 +1,36 @@
1
+ import Case from "case";
2
+ import type { IHttpHeader } from "@rexeus/typeweaver-core";
3
+
4
+ export interface NormalizedHeaders {
5
+ [key: string]: string | string[];
6
+ }
7
+
8
+ export const normalizeHeaders = (
9
+ headers: Record<string, string | string[] | undefined> | null
10
+ ): IHttpHeader => {
11
+ if (!headers) return undefined;
12
+
13
+ const result: NormalizedHeaders = {};
14
+
15
+ for (const [key, value] of Object.entries(headers)) {
16
+ if (value === undefined) continue;
17
+
18
+ const processedValue =
19
+ typeof value === "string" ? value.split(",").map(v => v.trim()) : value;
20
+
21
+ const filteredValue = processedValue.filter(v => v !== "");
22
+ if (filteredValue.length === 0) continue;
23
+
24
+ const lowerKey = key.toLowerCase();
25
+ const headerKey = Case.header(key);
26
+
27
+ const finalValue: string | string[] =
28
+ filteredValue.length === 1 ? filteredValue[0]! : filteredValue;
29
+
30
+ result[lowerKey] = finalValue;
31
+ result[headerKey] = finalValue;
32
+ result[key] = finalValue;
33
+ }
34
+
35
+ return Object.keys(result).length > 0 ? result : undefined;
36
+ };
@@ -0,0 +1,32 @@
1
+ import type { Context } from "hono";
2
+ import { TypeweaverHono, type HonoRequestHandler } from "<%- coreDir %>/lib/hono";
3
+ <% for (const operation of operations) { %>
4
+ import type { I<%- operation.className %>Request } from "./<%- operation.className %>Request";
5
+ import { <%- operation.className %>RequestValidator } from "./<%- operation.className %>RequestValidator";
6
+ import type { <%- operation.className %>Response } from "./<%- operation.className %>Response";
7
+ <% } %>
8
+
9
+ export type <%- pascalCaseEntityName %>ApiHandler = {
10
+ <% for (const operation of operations) { %>
11
+ <%- operation.handlerName %>: HonoRequestHandler<I<%- operation.className %>Request, <%- operation.className %>Response>;
12
+ <% } %>
13
+ };
14
+
15
+ export class <%- pascalCaseEntityName %>Hono extends TypeweaverHono<<%- pascalCaseEntityName %>ApiHandler> {
16
+ public constructor(handlers: <%- pascalCaseEntityName %>ApiHandler) {
17
+ super({ requestHandlers: handlers });
18
+ this.setupRoutes();
19
+ }
20
+
21
+ protected setupRoutes(): void {
22
+ <% for (const operation of operations) { %>
23
+ this.<%- operation.method.toLowerCase() %>('<%- operation.path %>', async (context: Context) =>
24
+ this.handleRequest(
25
+ context,
26
+ new <%- operation.className %>RequestValidator(),
27
+ this.requestHandlers.<%- operation.handlerName %>
28
+ ));
29
+
30
+ <% } %>
31
+ }
32
+ }
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@rexeus/typeweaver-hono",
3
+ "version": "0.0.1",
4
+ "description": "Hono router generator plugin for TypeWeaver",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "files": [
9
+ "dist",
10
+ "package.json",
11
+ "README.md"
12
+ ],
13
+ "keywords": [
14
+ "typeweaver",
15
+ "plugin",
16
+ "hono",
17
+ "router",
18
+ "api",
19
+ "http"
20
+ ],
21
+ "author": "Dennis Wentzien <dennis@rexeus.com>",
22
+ "license": "ISC",
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/rexeus/typeweaver.git",
26
+ "directory": "packages/hono"
27
+ },
28
+ "bugs": {
29
+ "url": "https://github.com/rexeus/typeweaver/issues"
30
+ },
31
+ "homepage": "https://github.com/rexeus/typeweaver#readme",
32
+ "peerDependencies": {
33
+ "@rexeus/typeweaver-core": "*",
34
+ "@rexeus/typeweaver-gen": "*"
35
+ },
36
+ "devDependencies": {
37
+ "@rexeus/typeweaver-core": "0.0.1",
38
+ "@rexeus/typeweaver-gen": "0.0.1"
39
+ },
40
+ "dependencies": {
41
+ "case": "^1.6.3",
42
+ "hono": "^4.0.0"
43
+ },
44
+ "scripts": {
45
+ "typecheck": "tsc --noEmit",
46
+ "format": "prettier --write .",
47
+ "build": "pkgroll --clean-dist && cp -r ./src/templates ./dist/templates && cp -r ./src/lib ./dist/lib",
48
+ "preversion": "npm run build"
49
+ }
50
+ }