@rexeus/typeweaver-hono 0.6.4 → 0.7.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/dist/index.cjs CHANGED
@@ -58,6 +58,7 @@ var HonoRouterGenerator = class {
58
58
  const operationId = resource.definition.operationId;
59
59
  const className = case$1.default.pascal(operationId);
60
60
  return {
61
+ operationId,
61
62
  className,
62
63
  handlerName: `handle${className}Request`,
63
64
  method: resource.definition.method,
package/dist/index.mjs CHANGED
@@ -29,6 +29,7 @@ var HonoRouterGenerator = class {
29
29
  const operationId = resource.definition.operationId;
30
30
  const className = Case.pascal(operationId);
31
31
  return {
32
+ operationId,
32
33
  className,
33
34
  handlerName: `handle${className}Request`,
34
35
  method: resource.definition.method,
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/HonoRouterGenerator.ts","../src/index.ts"],"sourcesContent":["import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { HttpMethod } from \"@rexeus/typeweaver-core\";\nimport type {\n GeneratorContext,\n OperationResource,\n} from \"@rexeus/typeweaver-gen\";\nimport Case from \"case\";\n\nexport class HonoRouterGenerator {\n public static generate(context: GeneratorContext): void {\n const moduleDir = path.dirname(fileURLToPath(import.meta.url));\n const templateFile = path.join(moduleDir, \"templates\", \"HonoRouter.ejs\");\n\n for (const [entityName, entityResource] of Object.entries(\n context.resources.entityResources\n )) {\n this.writeHonoRouter(\n entityName,\n templateFile,\n entityResource.operations,\n context\n );\n }\n }\n\n private static writeHonoRouter(\n entityName: string,\n templateFile: string,\n operationResources: OperationResource[],\n context: GeneratorContext\n ): void {\n const pascalCaseEntityName = Case.pascal(entityName);\n const outputDir = path.join(context.outputDir, entityName);\n const outputPath = path.join(outputDir, `${pascalCaseEntityName}Hono.ts`);\n\n const operations = operationResources\n // Hono handles HEAD requests automatically, so we skip them\n .filter(resource => resource.definition.method !== HttpMethod.HEAD)\n .map(resource => this.createOperationData(resource))\n .sort((a, b) => this.compareRoutes(a, b));\n\n const content = context.renderTemplate(templateFile, {\n coreDir: path.relative(outputDir, context.outputDir),\n entityName,\n pascalCaseEntityName,\n operations,\n });\n\n const relativePath = path.relative(context.outputDir, outputPath);\n context.writeFile(relativePath, content);\n }\n\n private static createOperationData(resource: OperationResource) {\n const operationId = resource.definition.operationId;\n const className = Case.pascal(operationId);\n const handlerName = `handle${className}Request`;\n\n return {\n className,\n handlerName,\n method: resource.definition.method,\n path: resource.definition.path,\n };\n }\n\n private static compareRoutes(\n a: ReturnType<typeof HonoRouterGenerator.createOperationData>,\n b: ReturnType<typeof HonoRouterGenerator.createOperationData>\n ): number {\n const aSegments = a.path.split(\"/\").filter(s => s);\n const bSegments = b.path.split(\"/\").filter(s => s);\n\n // 1. Compare by depth first (shallow to deep)\n if (aSegments.length !== bSegments.length) {\n return aSegments.length - bSegments.length;\n }\n\n // 2. Compare segment by segment\n for (let i = 0; i < aSegments.length; i++) {\n const aSegment = aSegments[i]!;\n const bSegment = bSegments[i]!;\n\n const aIsParam = aSegment.startsWith(\":\");\n const bIsParam = bSegment.startsWith(\":\");\n\n // Static segments before parameters\n if (aIsParam !== bIsParam) {\n return aIsParam ? 1 : -1;\n }\n\n // Within same type, alphabetical order\n if (aSegment !== bSegment) {\n return aSegment.localeCompare(bSegment);\n }\n }\n\n // 3. Same path = sort by HTTP method priority\n return this.getMethodPriority(a.method) - this.getMethodPriority(b.method);\n }\n\n private static getMethodPriority(method: string): number {\n const priorities: Record<string, number> = {\n GET: 1,\n POST: 2,\n PUT: 3,\n PATCH: 4,\n DELETE: 5,\n OPTIONS: 6,\n HEAD: 7,\n };\n return priorities[method] ?? 999;\n }\n}\n","import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { BasePlugin } from \"@rexeus/typeweaver-gen\";\nimport type { GeneratorContext } from \"@rexeus/typeweaver-gen\";\nimport { HonoRouterGenerator } from \"./HonoRouterGenerator\";\n\nconst moduleDir = path.dirname(fileURLToPath(import.meta.url));\n\nexport default class HonoPlugin extends BasePlugin {\n public name = \"hono\";\n\n public override generate(context: GeneratorContext): void {\n const libSourceDir = path.join(moduleDir, \"lib\");\n this.copyLibFiles(context, libSourceDir, this.name);\n\n HonoRouterGenerator.generate(context);\n }\n}\n"],"mappings":";;;;;;;AASA,IAAa,sBAAb,MAAiC;CAC/B,OAAc,SAAS,SAAiC;EACtD,MAAM,YAAY,KAAK,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC;EAC9D,MAAM,eAAe,KAAK,KAAK,WAAW,aAAa,iBAAiB;AAExE,OAAK,MAAM,CAAC,YAAY,mBAAmB,OAAO,QAChD,QAAQ,UAAU,gBACnB,CACC,MAAK,gBACH,YACA,cACA,eAAe,YACf,QACD;;CAIL,OAAe,gBACb,YACA,cACA,oBACA,SACM;EACN,MAAM,uBAAuB,KAAK,OAAO,WAAW;EACpD,MAAM,YAAY,KAAK,KAAK,QAAQ,WAAW,WAAW;EAC1D,MAAM,aAAa,KAAK,KAAK,WAAW,GAAG,qBAAqB,SAAS;EAEzE,MAAM,aAAa,mBAEhB,QAAO,aAAY,SAAS,WAAW,WAAW,WAAW,KAAK,CAClE,KAAI,aAAY,KAAK,oBAAoB,SAAS,CAAC,CACnD,MAAM,GAAG,MAAM,KAAK,cAAc,GAAG,EAAE,CAAC;EAE3C,MAAM,UAAU,QAAQ,eAAe,cAAc;GACnD,SAAS,KAAK,SAAS,WAAW,QAAQ,UAAU;GACpD;GACA;GACA;GACD,CAAC;EAEF,MAAM,eAAe,KAAK,SAAS,QAAQ,WAAW,WAAW;AACjE,UAAQ,UAAU,cAAc,QAAQ;;CAG1C,OAAe,oBAAoB,UAA6B;EAC9D,MAAM,cAAc,SAAS,WAAW;EACxC,MAAM,YAAY,KAAK,OAAO,YAAY;AAG1C,SAAO;GACL;GACA,aAJkB,SAAS,UAAU;GAKrC,QAAQ,SAAS,WAAW;GAC5B,MAAM,SAAS,WAAW;GAC3B;;CAGH,OAAe,cACb,GACA,GACQ;EACR,MAAM,YAAY,EAAE,KAAK,MAAM,IAAI,CAAC,QAAO,MAAK,EAAE;EAClD,MAAM,YAAY,EAAE,KAAK,MAAM,IAAI,CAAC,QAAO,MAAK,EAAE;AAGlD,MAAI,UAAU,WAAW,UAAU,OACjC,QAAO,UAAU,SAAS,UAAU;AAItC,OAAK,IAAI,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK;GACzC,MAAM,WAAW,UAAU;GAC3B,MAAM,WAAW,UAAU;GAE3B,MAAM,WAAW,SAAS,WAAW,IAAI;AAIzC,OAAI,aAHa,SAAS,WAAW,IAAI,CAIvC,QAAO,WAAW,IAAI;AAIxB,OAAI,aAAa,SACf,QAAO,SAAS,cAAc,SAAS;;AAK3C,SAAO,KAAK,kBAAkB,EAAE,OAAO,GAAG,KAAK,kBAAkB,EAAE,OAAO;;CAG5E,OAAe,kBAAkB,QAAwB;AAUvD,SAT2C;GACzC,KAAK;GACL,MAAM;GACN,KAAK;GACL,OAAO;GACP,QAAQ;GACR,SAAS;GACT,MAAM;GACP,CACiB,WAAW;;;;;;ACzGjC,MAAM,YAAY,KAAK,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC;AAE9D,IAAqB,aAArB,cAAwC,WAAW;CACjD,AAAO,OAAO;CAEd,AAAgB,SAAS,SAAiC;EACxD,MAAM,eAAe,KAAK,KAAK,WAAW,MAAM;AAChD,OAAK,aAAa,SAAS,cAAc,KAAK,KAAK;AAEnD,sBAAoB,SAAS,QAAQ"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/HonoRouterGenerator.ts","../src/index.ts"],"sourcesContent":["import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { HttpMethod } from \"@rexeus/typeweaver-core\";\nimport type {\n GeneratorContext,\n OperationResource,\n} from \"@rexeus/typeweaver-gen\";\nimport Case from \"case\";\n\nexport class HonoRouterGenerator {\n public static generate(context: GeneratorContext): void {\n const moduleDir = path.dirname(fileURLToPath(import.meta.url));\n const templateFile = path.join(moduleDir, \"templates\", \"HonoRouter.ejs\");\n\n for (const [entityName, entityResource] of Object.entries(\n context.resources.entityResources\n )) {\n this.writeHonoRouter(\n entityName,\n templateFile,\n entityResource.operations,\n context\n );\n }\n }\n\n private static writeHonoRouter(\n entityName: string,\n templateFile: string,\n operationResources: OperationResource[],\n context: GeneratorContext\n ): void {\n const pascalCaseEntityName = Case.pascal(entityName);\n const outputDir = path.join(context.outputDir, entityName);\n const outputPath = path.join(outputDir, `${pascalCaseEntityName}Hono.ts`);\n\n const operations = operationResources\n // Hono handles HEAD requests automatically, so we skip them\n .filter(resource => resource.definition.method !== HttpMethod.HEAD)\n .map(resource => this.createOperationData(resource))\n .sort((a, b) => this.compareRoutes(a, b));\n\n const content = context.renderTemplate(templateFile, {\n coreDir: path.relative(outputDir, context.outputDir),\n entityName,\n pascalCaseEntityName,\n operations,\n });\n\n const relativePath = path.relative(context.outputDir, outputPath);\n context.writeFile(relativePath, content);\n }\n\n private static createOperationData(resource: OperationResource) {\n const operationId = resource.definition.operationId;\n const className = Case.pascal(operationId);\n const handlerName = `handle${className}Request`;\n\n return {\n operationId,\n className,\n handlerName,\n method: resource.definition.method,\n path: resource.definition.path,\n };\n }\n\n private static compareRoutes(\n a: ReturnType<typeof HonoRouterGenerator.createOperationData>,\n b: ReturnType<typeof HonoRouterGenerator.createOperationData>\n ): number {\n const aSegments = a.path.split(\"/\").filter(s => s);\n const bSegments = b.path.split(\"/\").filter(s => s);\n\n // 1. Compare by depth first (shallow to deep)\n if (aSegments.length !== bSegments.length) {\n return aSegments.length - bSegments.length;\n }\n\n // 2. Compare segment by segment\n for (let i = 0; i < aSegments.length; i++) {\n const aSegment = aSegments[i]!;\n const bSegment = bSegments[i]!;\n\n const aIsParam = aSegment.startsWith(\":\");\n const bIsParam = bSegment.startsWith(\":\");\n\n // Static segments before parameters\n if (aIsParam !== bIsParam) {\n return aIsParam ? 1 : -1;\n }\n\n // Within same type, alphabetical order\n if (aSegment !== bSegment) {\n return aSegment.localeCompare(bSegment);\n }\n }\n\n // 3. Same path = sort by HTTP method priority\n return this.getMethodPriority(a.method) - this.getMethodPriority(b.method);\n }\n\n private static getMethodPriority(method: string): number {\n const priorities: Record<string, number> = {\n GET: 1,\n POST: 2,\n PUT: 3,\n PATCH: 4,\n DELETE: 5,\n OPTIONS: 6,\n HEAD: 7,\n };\n return priorities[method] ?? 999;\n }\n}\n","import path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { BasePlugin } from \"@rexeus/typeweaver-gen\";\nimport type { GeneratorContext } from \"@rexeus/typeweaver-gen\";\nimport { HonoRouterGenerator } from \"./HonoRouterGenerator\";\n\nconst moduleDir = path.dirname(fileURLToPath(import.meta.url));\n\nexport default class HonoPlugin extends BasePlugin {\n public name = \"hono\";\n\n public override generate(context: GeneratorContext): void {\n const libSourceDir = path.join(moduleDir, \"lib\");\n this.copyLibFiles(context, libSourceDir, this.name);\n\n HonoRouterGenerator.generate(context);\n }\n}\n"],"mappings":";;;;;;;AASA,IAAa,sBAAb,MAAiC;CAC/B,OAAc,SAAS,SAAiC;EACtD,MAAM,YAAY,KAAK,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC;EAC9D,MAAM,eAAe,KAAK,KAAK,WAAW,aAAa,iBAAiB;AAExE,OAAK,MAAM,CAAC,YAAY,mBAAmB,OAAO,QAChD,QAAQ,UAAU,gBACnB,CACC,MAAK,gBACH,YACA,cACA,eAAe,YACf,QACD;;CAIL,OAAe,gBACb,YACA,cACA,oBACA,SACM;EACN,MAAM,uBAAuB,KAAK,OAAO,WAAW;EACpD,MAAM,YAAY,KAAK,KAAK,QAAQ,WAAW,WAAW;EAC1D,MAAM,aAAa,KAAK,KAAK,WAAW,GAAG,qBAAqB,SAAS;EAEzE,MAAM,aAAa,mBAEhB,QAAO,aAAY,SAAS,WAAW,WAAW,WAAW,KAAK,CAClE,KAAI,aAAY,KAAK,oBAAoB,SAAS,CAAC,CACnD,MAAM,GAAG,MAAM,KAAK,cAAc,GAAG,EAAE,CAAC;EAE3C,MAAM,UAAU,QAAQ,eAAe,cAAc;GACnD,SAAS,KAAK,SAAS,WAAW,QAAQ,UAAU;GACpD;GACA;GACA;GACD,CAAC;EAEF,MAAM,eAAe,KAAK,SAAS,QAAQ,WAAW,WAAW;AACjE,UAAQ,UAAU,cAAc,QAAQ;;CAG1C,OAAe,oBAAoB,UAA6B;EAC9D,MAAM,cAAc,SAAS,WAAW;EACxC,MAAM,YAAY,KAAK,OAAO,YAAY;AAG1C,SAAO;GACL;GACA;GACA,aALkB,SAAS,UAAU;GAMrC,QAAQ,SAAS,WAAW;GAC5B,MAAM,SAAS,WAAW;GAC3B;;CAGH,OAAe,cACb,GACA,GACQ;EACR,MAAM,YAAY,EAAE,KAAK,MAAM,IAAI,CAAC,QAAO,MAAK,EAAE;EAClD,MAAM,YAAY,EAAE,KAAK,MAAM,IAAI,CAAC,QAAO,MAAK,EAAE;AAGlD,MAAI,UAAU,WAAW,UAAU,OACjC,QAAO,UAAU,SAAS,UAAU;AAItC,OAAK,IAAI,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK;GACzC,MAAM,WAAW,UAAU;GAC3B,MAAM,WAAW,UAAU;GAE3B,MAAM,WAAW,SAAS,WAAW,IAAI;AAIzC,OAAI,aAHa,SAAS,WAAW,IAAI,CAIvC,QAAO,WAAW,IAAI;AAIxB,OAAI,aAAa,SACf,QAAO,SAAS,cAAc,SAAS;;AAK3C,SAAO,KAAK,kBAAkB,EAAE,OAAO,GAAG,KAAK,kBAAkB,EAAE,OAAO;;CAG5E,OAAe,kBAAkB,QAAwB;AAUvD,SAT2C;GACzC,KAAK;GACL,MAAM;GACN,KAAK;GACL,OAAO;GACP,QAAQ;GACR,SAAS;GACT,MAAM;GACP,CACiB,WAAW;;;;;;AC1GjC,MAAM,YAAY,KAAK,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC;AAE9D,IAAqB,aAArB,cAAwC,WAAW;CACjD,AAAO,OAAO;CAEd,AAAgB,SAAS,SAAiC;EACxD,MAAM,eAAe,KAAK,KAAK,WAAW,MAAM;AAChD,OAAK,aAAa,SAAS,cAAc,KAAK,KAAK;AAEnD,sBAAoB,SAAS,QAAQ"}
@@ -24,7 +24,7 @@ import type { BlankEnv, BlankSchema, Env, Schema } from "hono/types";
24
24
  * @param context - The Hono context for the current request
25
25
  * @returns The HTTP response to send to the client
26
26
  */
27
- export type HttpResponseErrorHandler = (
27
+ export type HonoHttpResponseErrorHandler = (
28
28
  error: HttpResponse,
29
29
  context: Context
30
30
  ) => Promise<IHttpResponse> | IHttpResponse;
@@ -35,7 +35,7 @@ export type HttpResponseErrorHandler = (
35
35
  * @param context - The Hono context for the current request
36
36
  * @returns The HTTP response to send to the client
37
37
  */
38
- export type ValidationErrorHandler = (
38
+ export type HonoValidationErrorHandler = (
39
39
  error: RequestValidationError,
40
40
  context: Context
41
41
  ) => Promise<IHttpResponse> | IHttpResponse;
@@ -46,7 +46,7 @@ export type ValidationErrorHandler = (
46
46
  * @param context - The Hono context for the current request
47
47
  * @returns The HTTP response to send to the client
48
48
  */
49
- export type UnknownErrorHandler = (
49
+ export type HonoUnknownErrorHandler = (
50
50
  error: unknown,
51
51
  context: Context
52
52
  ) => Promise<IHttpResponse> | IHttpResponse;
@@ -80,7 +80,7 @@ export type TypeweaverHonoOptions<
80
80
  * - `function`: Use custom error handler
81
81
  * @default true
82
82
  */
83
- handleHttpResponseErrors?: HttpResponseErrorHandler | boolean;
83
+ handleHttpResponseErrors?: HonoHttpResponseErrorHandler | boolean;
84
84
 
85
85
  /**
86
86
  * Configure handling of request validation errors.
@@ -89,7 +89,7 @@ export type TypeweaverHonoOptions<
89
89
  * - `function`: Use custom error handler
90
90
  * @default true
91
91
  */
92
- handleValidationErrors?: ValidationErrorHandler | boolean;
92
+ handleValidationErrors?: HonoValidationErrorHandler | boolean;
93
93
 
94
94
  /**
95
95
  * Configure handling of unknown errors.
@@ -98,7 +98,7 @@ export type TypeweaverHonoOptions<
98
98
  * - `function`: Use custom error handler
99
99
  * @default true
100
100
  */
101
- handleUnknownErrors?: UnknownErrorHandler | boolean;
101
+ handleUnknownErrors?: HonoUnknownErrorHandler | boolean;
102
102
  };
103
103
 
104
104
  /**
@@ -136,9 +136,9 @@ export abstract class TypeweaverHono<
136
136
  private readonly config: {
137
137
  validateRequests: boolean;
138
138
  errorHandlers: {
139
- validation: ValidationErrorHandler | undefined;
140
- httpResponse: HttpResponseErrorHandler | undefined;
141
- unknown: UnknownErrorHandler | undefined;
139
+ validation: HonoValidationErrorHandler | undefined;
140
+ httpResponse: HonoHttpResponseErrorHandler | undefined;
141
+ unknown: HonoUnknownErrorHandler | undefined;
142
142
  };
143
143
  };
144
144
 
@@ -303,6 +303,7 @@ export abstract class TypeweaverHono<
303
303
  * Handles a request with validation and type-safe response conversion.
304
304
  *
305
305
  * @param context - Hono context for the current request
306
+ * @param operationId - Unique operation identifier from the API definition
306
307
  * @param validator - Request validator for the specific operation
307
308
  * @param handler - Type-safe request handler function
308
309
  * @returns Hono-compatible Response object
@@ -312,10 +313,13 @@ export abstract class TypeweaverHono<
312
313
  TResponse extends IHttpResponse,
313
314
  >(
314
315
  context: Context,
316
+ operationId: string,
315
317
  validator: IRequestValidator,
316
318
  handler: HonoRequestHandler<TRequest, TResponse>
317
319
  ): Promise<Response> {
318
320
  try {
321
+ context.set("operationId", operationId);
322
+
319
323
  const httpRequest = await this.adapter.toRequest(context);
320
324
 
321
325
  // Conditionally validate
@@ -31,6 +31,7 @@ export class <%- pascalCaseEntityName %>Hono extends TypeweaverHono<Hono<%- pasc
31
31
  this.<%- operation.method.toLowerCase() %>('<%- operation.path %>', async (context: Context) =>
32
32
  this.handleRequest(
33
33
  context,
34
+ '<%- operation.operationId %>',
34
35
  new <%- operation.className %>RequestValidator(),
35
36
  this.requestHandlers.<%- operation.handlerName %>.bind(this.requestHandlers)
36
37
  ));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rexeus/typeweaver-hono",
3
- "version": "0.6.4",
3
+ "version": "0.7.0",
4
4
  "description": "Generates Hono routers and handlers straight from your API definitions. Powered by Typeweaver 🧵✨",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -47,14 +47,14 @@
47
47
  "homepage": "https://github.com/rexeus/typeweaver#readme",
48
48
  "peerDependencies": {
49
49
  "hono": "^4.11.0",
50
- "@rexeus/typeweaver-core": "^0.6.4",
51
- "@rexeus/typeweaver-gen": "^0.6.4"
50
+ "@rexeus/typeweaver-core": "^0.7.0",
51
+ "@rexeus/typeweaver-gen": "^0.7.0"
52
52
  },
53
53
  "devDependencies": {
54
54
  "hono": "^4.11.3",
55
55
  "test-utils": "file:../test-utils",
56
- "@rexeus/typeweaver-core": "^0.6.4",
57
- "@rexeus/typeweaver-gen": "^0.6.4"
56
+ "@rexeus/typeweaver-core": "^0.7.0",
57
+ "@rexeus/typeweaver-gen": "^0.7.0"
58
58
  },
59
59
  "dependencies": {
60
60
  "case": "^1.6.3"