@spfn/core 0.1.0-alpha.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.
Files changed (37) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +580 -0
  3. package/dist/auto-loader-C44TcLmM.d.ts +125 -0
  4. package/dist/bind-pssq1NRT.d.ts +34 -0
  5. package/dist/client/index.d.ts +174 -0
  6. package/dist/client/index.js +179 -0
  7. package/dist/client/index.js.map +1 -0
  8. package/dist/codegen/index.d.ts +126 -0
  9. package/dist/codegen/index.js +970 -0
  10. package/dist/codegen/index.js.map +1 -0
  11. package/dist/db/index.d.ts +83 -0
  12. package/dist/db/index.js +2099 -0
  13. package/dist/db/index.js.map +1 -0
  14. package/dist/index.d.ts +379 -0
  15. package/dist/index.js +13042 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/postgres-errors-CY_Es8EJ.d.ts +1703 -0
  18. package/dist/route/index.d.ts +72 -0
  19. package/dist/route/index.js +442 -0
  20. package/dist/route/index.js.map +1 -0
  21. package/dist/scripts/index.d.ts +24 -0
  22. package/dist/scripts/index.js +1157 -0
  23. package/dist/scripts/index.js.map +1 -0
  24. package/dist/scripts/templates/api-index.template.txt +10 -0
  25. package/dist/scripts/templates/api-tag.template.txt +11 -0
  26. package/dist/scripts/templates/contract.template.txt +87 -0
  27. package/dist/scripts/templates/entity-type.template.txt +31 -0
  28. package/dist/scripts/templates/entity.template.txt +19 -0
  29. package/dist/scripts/templates/index.template.txt +10 -0
  30. package/dist/scripts/templates/repository.template.txt +37 -0
  31. package/dist/scripts/templates/routes-id.template.txt +59 -0
  32. package/dist/scripts/templates/routes-index.template.txt +44 -0
  33. package/dist/server/index.d.ts +303 -0
  34. package/dist/server/index.js +12923 -0
  35. package/dist/server/index.js.map +1 -0
  36. package/dist/types-SlzTr8ZO.d.ts +143 -0
  37. package/package.json +119 -0
@@ -0,0 +1,72 @@
1
+ export { A as AutoRouteLoader, R as RouteInfo, a as RouteStats, l as loadRoutes } from '../auto-loader-C44TcLmM.js';
2
+ export { b as bind } from '../bind-pssq1NRT.js';
3
+ import { Hono, MiddlewareHandler } from 'hono';
4
+ import { R as RouteContract, a as RouteHandler } from '../types-SlzTr8ZO.js';
5
+ export { H as HeaderRecord, d as HttpMethod, I as InferContract, c as RouteContext, b as RouteMeta, i as isHttpMethod } from '../types-SlzTr8ZO.js';
6
+ import 'hono/utils/http-status';
7
+ import '@sinclair/typebox';
8
+
9
+ /**
10
+ * Create App - Hono Wrapper for Contract-based Routing
11
+ *
12
+ * Provides a cleaner API for registering routes with contracts
13
+ */
14
+
15
+ /**
16
+ * Extended Hono app with bind() method
17
+ */
18
+ type SPFNApp = Hono & {
19
+ /**
20
+ * Bind a contract to a handler with optional middlewares
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * // Handler only
25
+ * app.bind(getUserContract, async (c) => {
26
+ * return c.json({ id: c.params.id });
27
+ * });
28
+ *
29
+ * // With middlewares
30
+ * app.bind(createUserContract, [authMiddleware, logMiddleware], async (c) => {
31
+ * const body = await c.data();
32
+ * return c.json({ id: '123' });
33
+ * });
34
+ * ```
35
+ */
36
+ bind<TContract extends RouteContract>(contract: TContract, handler: RouteHandler): void;
37
+ bind<TContract extends RouteContract>(contract: TContract, middlewares: MiddlewareHandler[], handler: RouteHandler): void;
38
+ /**
39
+ * Contract metadata storage
40
+ * Map<"METHOD /path", RouteMeta>
41
+ * @internal
42
+ */
43
+ _contractMetas?: Map<string, RouteContract['meta']>;
44
+ };
45
+ /**
46
+ * Create SPFN app instance
47
+ *
48
+ * Wraps Hono with contract-based routing support
49
+ *
50
+ * @example
51
+ * ```ts
52
+ * import { createApp } from '@spfn/core/route';
53
+ * import { getUserContract, createUserContract } from '@/server/contracts/users';
54
+ *
55
+ * const app = createApp();
56
+ *
57
+ * // Register routes using contracts
58
+ * app.bind(getUserContract, async (c) => {
59
+ * return c.json({ id: c.params.id });
60
+ * });
61
+ *
62
+ * app.bind(createUserContract, [authMiddleware], async (c) => {
63
+ * const body = await c.data();
64
+ * return c.json({ id: '123' });
65
+ * });
66
+ *
67
+ * export default app;
68
+ * ```
69
+ */
70
+ declare function createApp(): SPFNApp;
71
+
72
+ export { RouteContract, RouteHandler, type SPFNApp, createApp };
@@ -0,0 +1,442 @@
1
+ import { readdir, stat } from 'fs/promises';
2
+ import { relative, join } from 'path';
3
+ import { Value } from '@sinclair/typebox/value';
4
+ import { Hono } from 'hono';
5
+
6
+ // src/route/auto-loader.ts
7
+ var AutoRouteLoader = class {
8
+ constructor(routesDir, debug = false, middlewares = []) {
9
+ this.routesDir = routesDir;
10
+ this.debug = debug;
11
+ this.middlewares = middlewares;
12
+ }
13
+ routes = [];
14
+ registeredRoutes = /* @__PURE__ */ new Map();
15
+ // normalized path → file
16
+ debug;
17
+ middlewares;
18
+ /**
19
+ * Load all routes from directory
20
+ */
21
+ async load(app) {
22
+ const startTime = Date.now();
23
+ const files = await this.scanFiles(this.routesDir);
24
+ if (files.length === 0) {
25
+ console.warn("\u26A0\uFE0F No route files found");
26
+ return this.getStats();
27
+ }
28
+ const filesWithPriority = files.map((file) => ({
29
+ path: file,
30
+ priority: this.calculatePriority(relative(this.routesDir, file))
31
+ }));
32
+ filesWithPriority.sort((a, b) => a.priority - b.priority);
33
+ if (this.debug) {
34
+ console.log(`
35
+ \u{1F4CB} Route Registration Order:`);
36
+ console.log(` Priority 1 (Static): ${filesWithPriority.filter((f) => f.priority === 1).length} routes`);
37
+ console.log(` Priority 2 (Dynamic): ${filesWithPriority.filter((f) => f.priority === 2).length} routes`);
38
+ console.log(` Priority 3 (Catch-all): ${filesWithPriority.filter((f) => f.priority === 3).length} routes
39
+ `);
40
+ }
41
+ let failureCount = 0;
42
+ for (const { path } of filesWithPriority) {
43
+ const success = await this.loadRoute(app, path);
44
+ if (success) ; else {
45
+ failureCount++;
46
+ }
47
+ }
48
+ const elapsed = Date.now() - startTime;
49
+ const stats = this.getStats();
50
+ if (this.debug) {
51
+ this.logStats(stats, elapsed);
52
+ }
53
+ if (failureCount > 0) {
54
+ console.warn(`\u26A0\uFE0F ${failureCount} route(s) failed to load`);
55
+ }
56
+ return stats;
57
+ }
58
+ /**
59
+ * Get route statistics
60
+ */
61
+ getStats() {
62
+ const stats = {
63
+ total: this.routes.length,
64
+ byPriority: { static: 0, dynamic: 0, catchAll: 0 },
65
+ byTag: {},
66
+ routes: this.routes
67
+ };
68
+ for (const route of this.routes) {
69
+ if (route.priority === 1) stats.byPriority.static++;
70
+ else if (route.priority === 2) stats.byPriority.dynamic++;
71
+ else if (route.priority === 3) stats.byPriority.catchAll++;
72
+ if (route.meta?.tags) {
73
+ for (const tag of route.meta.tags) {
74
+ stats.byTag[tag] = (stats.byTag[tag] || 0) + 1;
75
+ }
76
+ }
77
+ }
78
+ return stats;
79
+ }
80
+ // ========================================================================
81
+ // Private Methods
82
+ // ========================================================================
83
+ /**
84
+ * Recursively scan directory for .ts files
85
+ */
86
+ async scanFiles(dir, files = []) {
87
+ const entries = await readdir(dir);
88
+ for (const entry of entries) {
89
+ const fullPath = join(dir, entry);
90
+ const fileStat = await stat(fullPath);
91
+ if (fileStat.isDirectory()) {
92
+ await this.scanFiles(fullPath, files);
93
+ } else if (this.isValidRouteFile(entry)) {
94
+ files.push(fullPath);
95
+ }
96
+ }
97
+ return files;
98
+ }
99
+ /**
100
+ * Check if file is a valid route file
101
+ */
102
+ isValidRouteFile(fileName) {
103
+ return fileName.endsWith(".ts") && !fileName.endsWith(".test.ts") && !fileName.endsWith(".spec.ts") && !fileName.endsWith(".d.ts") && fileName !== "contract.ts";
104
+ }
105
+ /**
106
+ * Load and register a single route
107
+ * Returns true if successful, false if failed
108
+ */
109
+ async loadRoute(app, absolutePath) {
110
+ const relativePath = relative(this.routesDir, absolutePath);
111
+ try {
112
+ const module = await import(absolutePath);
113
+ if (!module.default) {
114
+ console.error(`\u274C ${relativePath}: Must export Hono instance as default`);
115
+ return false;
116
+ }
117
+ if (typeof module.default.route !== "function") {
118
+ console.error(`\u274C ${relativePath}: Default export is not a Hono instance`);
119
+ return false;
120
+ }
121
+ const urlPath = this.fileToPath(relativePath);
122
+ const priority = this.calculatePriority(relativePath);
123
+ const normalizedPath = this.normalizePath(urlPath);
124
+ const existingFile = this.registeredRoutes.get(normalizedPath);
125
+ if (existingFile) {
126
+ console.warn(`\u26A0\uFE0F Route conflict detected:`);
127
+ console.warn(` Path: ${urlPath} (normalized: ${normalizedPath})`);
128
+ console.warn(` Already registered by: ${existingFile}`);
129
+ console.warn(` Attempted by: ${relativePath}`);
130
+ console.warn(` \u2192 Skipping duplicate registration`);
131
+ return false;
132
+ }
133
+ this.registeredRoutes.set(normalizedPath, relativePath);
134
+ const hasContractMetas = module.default._contractMetas && module.default._contractMetas.size > 0;
135
+ if (hasContractMetas) {
136
+ const middlewarePath = urlPath === "/" ? "/*" : `${urlPath}/*`;
137
+ app.use(middlewarePath, (c, next) => {
138
+ const method = c.req.method;
139
+ const requestPath = new URL(c.req.url).pathname;
140
+ const relativePath2 = requestPath.startsWith(urlPath) ? requestPath.slice(urlPath.length) || "/" : requestPath;
141
+ const key = `${method} ${relativePath2}`;
142
+ const meta = module.default._contractMetas?.get(key);
143
+ if (meta?.skipMiddlewares) {
144
+ c.set("_skipMiddlewares", meta.skipMiddlewares);
145
+ }
146
+ return next();
147
+ });
148
+ for (const middleware of this.middlewares) {
149
+ app.use(middlewarePath, async (c, next) => {
150
+ const skipList = c.get("_skipMiddlewares") || [];
151
+ if (skipList.includes(middleware.name)) {
152
+ return next();
153
+ }
154
+ return middleware.handler(c, next);
155
+ });
156
+ }
157
+ } else {
158
+ const skipList = module.meta?.skipMiddlewares || [];
159
+ const activeMiddlewares = this.middlewares.filter((m) => !skipList.includes(m.name));
160
+ for (const middleware of activeMiddlewares) {
161
+ app.use(urlPath, middleware.handler);
162
+ }
163
+ }
164
+ app.route(urlPath, module.default);
165
+ this.routes.push({
166
+ path: urlPath,
167
+ file: relativePath,
168
+ meta: module.meta,
169
+ priority
170
+ });
171
+ if (this.debug) {
172
+ const icon = priority === 1 ? "\u{1F539}" : priority === 2 ? "\u{1F538}" : "\u2B50";
173
+ console.log(` ${icon} ${urlPath.padEnd(40)} \u2192 ${relativePath}`);
174
+ }
175
+ return true;
176
+ } catch (error) {
177
+ const err = error;
178
+ if (err.message.includes("Cannot find module") || err.message.includes("MODULE_NOT_FOUND")) {
179
+ console.error(`\u274C ${relativePath}: Missing dependency`);
180
+ console.error(` ${err.message}`);
181
+ console.error(` \u2192 Run: npm install`);
182
+ } else if (err.message.includes("SyntaxError") || err.stack?.includes("SyntaxError")) {
183
+ console.error(`\u274C ${relativePath}: Syntax error`);
184
+ console.error(` ${err.message}`);
185
+ if (this.debug && err.stack) {
186
+ console.error(` Stack trace (first 5 lines):`);
187
+ const stackLines = err.stack.split("\n").slice(0, 5);
188
+ stackLines.forEach((line) => console.error(` ${line}`));
189
+ }
190
+ } else if (err.message.includes("Unexpected token")) {
191
+ console.error(`\u274C ${relativePath}: Parse error`);
192
+ console.error(` ${err.message}`);
193
+ console.error(` \u2192 Check for syntax errors or invalid TypeScript`);
194
+ } else {
195
+ console.error(`\u274C ${relativePath}: ${err.message}`);
196
+ if (this.debug && err.stack) {
197
+ console.error(` Stack: ${err.stack}`);
198
+ }
199
+ }
200
+ return false;
201
+ }
202
+ }
203
+ /**
204
+ * Convert file path to URL path
205
+ *
206
+ * Examples:
207
+ * - users/index.ts → /users
208
+ * - users/[id].ts → /users/:id
209
+ * - posts/[...slug].ts → /posts/*
210
+ */
211
+ fileToPath(filePath) {
212
+ let path = filePath.replace(/\.ts$/, "");
213
+ const segments = path.split("/");
214
+ if (segments[segments.length - 1] === "index") {
215
+ segments.pop();
216
+ }
217
+ const transformed = segments.map((seg) => {
218
+ if (/^\[\.\.\.[\w-]+]$/.test(seg)) {
219
+ return "*";
220
+ }
221
+ if (/^\[[\w-]+]$/.test(seg)) {
222
+ return ":" + seg.slice(1, -1);
223
+ }
224
+ if (seg === "index") {
225
+ return null;
226
+ }
227
+ return seg;
228
+ }).filter((seg) => seg !== null);
229
+ const result = "/" + transformed.join("/");
230
+ return result.replace(/\/+/g, "/").replace(/\/$/, "") || "/";
231
+ }
232
+ /**
233
+ * Calculate route priority
234
+ * 1 = static, 2 = dynamic, 3 = catch-all
235
+ */
236
+ calculatePriority(path) {
237
+ if (/\[\.\.\.[\w-]+]/.test(path)) return 3;
238
+ if (/\[[\w-]+]/.test(path)) return 2;
239
+ return 1;
240
+ }
241
+ /**
242
+ * Normalize path for conflict detection
243
+ *
244
+ * Converts dynamic parameter names to generic placeholders:
245
+ * - /users/:id → /users/:param
246
+ * - /users/:userId → /users/:param (conflict!)
247
+ * - /posts/* → /posts/* (unchanged)
248
+ *
249
+ * This allows detection of routes with different param names
250
+ * that would match the same URL patterns.
251
+ */
252
+ normalizePath(path) {
253
+ return path.replace(/:\w+/g, ":param");
254
+ }
255
+ /**
256
+ * Log statistics
257
+ */
258
+ logStats(stats, elapsed) {
259
+ console.log(`
260
+ \u{1F4CA} Route Statistics:`);
261
+ console.log(` Total: ${stats.total} routes`);
262
+ console.log(
263
+ ` Priority: ${stats.byPriority.static} static, ${stats.byPriority.dynamic} dynamic, ${stats.byPriority.catchAll} catch-all`
264
+ );
265
+ if (Object.keys(stats.byTag).length > 0) {
266
+ const tagCounts = Object.entries(stats.byTag).map(([tag, count]) => `${tag}(${count})`).join(", ");
267
+ console.log(` Tags: ${tagCounts}`);
268
+ }
269
+ console.log(`
270
+ \u2705 Routes loaded in ${elapsed}ms
271
+ `);
272
+ }
273
+ };
274
+ async function loadRoutes(app, options) {
275
+ const routesDir = options?.routesDir ?? join(process.cwd(), "src", "server", "routes");
276
+ const debug = options?.debug ?? false;
277
+ const middlewares = options?.middlewares ?? [];
278
+ const loader = new AutoRouteLoader(routesDir, debug, middlewares);
279
+ return loader.load(app);
280
+ }
281
+
282
+ // src/errors/database-errors.ts
283
+ var DatabaseError = class extends Error {
284
+ statusCode;
285
+ details;
286
+ timestamp;
287
+ constructor(message, statusCode = 500, details) {
288
+ super(message);
289
+ this.name = "DatabaseError";
290
+ this.statusCode = statusCode;
291
+ this.details = details;
292
+ this.timestamp = /* @__PURE__ */ new Date();
293
+ Error.captureStackTrace(this, this.constructor);
294
+ }
295
+ /**
296
+ * Serialize error for API response
297
+ */
298
+ toJSON() {
299
+ return {
300
+ name: this.name,
301
+ message: this.message,
302
+ statusCode: this.statusCode,
303
+ details: this.details,
304
+ timestamp: this.timestamp.toISOString()
305
+ };
306
+ }
307
+ };
308
+ var QueryError = class extends DatabaseError {
309
+ constructor(message, statusCode = 500, details) {
310
+ super(message, statusCode, details);
311
+ this.name = "QueryError";
312
+ }
313
+ };
314
+ var ValidationError = class extends QueryError {
315
+ constructor(message, details) {
316
+ super(message, 400, details);
317
+ this.name = "ValidationError";
318
+ }
319
+ };
320
+
321
+ // src/route/bind.ts
322
+ function bind(contract, handler) {
323
+ return async (rawContext) => {
324
+ const params = rawContext.req.param();
325
+ if (contract.params) {
326
+ const errors = [...Value.Errors(contract.params, params)];
327
+ if (errors.length > 0) {
328
+ throw new ValidationError(
329
+ "Invalid path parameters",
330
+ {
331
+ fields: errors.map((e) => ({
332
+ path: e.path,
333
+ message: e.message,
334
+ value: e.value
335
+ }))
336
+ }
337
+ );
338
+ }
339
+ }
340
+ const url = new URL(rawContext.req.url);
341
+ const query = {};
342
+ url.searchParams.forEach((v, k) => {
343
+ const existing = query[k];
344
+ if (existing) {
345
+ query[k] = Array.isArray(existing) ? [...existing, v] : [existing, v];
346
+ } else {
347
+ query[k] = v;
348
+ }
349
+ });
350
+ if (contract.query) {
351
+ const errors = [...Value.Errors(contract.query, query)];
352
+ if (errors.length > 0) {
353
+ throw new ValidationError(
354
+ "Invalid query parameters",
355
+ {
356
+ fields: errors.map((e) => ({
357
+ path: e.path,
358
+ message: e.message,
359
+ value: e.value
360
+ }))
361
+ }
362
+ );
363
+ }
364
+ }
365
+ const routeContext = {
366
+ params,
367
+ query,
368
+ // data() - validates and returns body
369
+ data: async () => {
370
+ const body = await rawContext.req.json();
371
+ if (contract.body) {
372
+ const errors = [...Value.Errors(contract.body, body)];
373
+ if (errors.length > 0) {
374
+ throw new ValidationError(
375
+ "Invalid request body",
376
+ {
377
+ fields: errors.map((e) => ({
378
+ path: e.path,
379
+ message: e.message,
380
+ value: e.value
381
+ }))
382
+ }
383
+ );
384
+ }
385
+ }
386
+ return body;
387
+ },
388
+ // json() - returns typed response
389
+ json: (data, status, headers) => {
390
+ return rawContext.json(data, status, headers);
391
+ },
392
+ // raw Hono context for advanced usage
393
+ raw: rawContext
394
+ };
395
+ return handler(routeContext);
396
+ };
397
+ }
398
+ function createApp() {
399
+ const hono = new Hono();
400
+ const app = hono;
401
+ app._contractMetas = /* @__PURE__ */ new Map();
402
+ app.bind = function(contract, ...args) {
403
+ const method = contract.method.toLowerCase();
404
+ const path = contract.path;
405
+ const [middlewares, handler] = args.length === 1 ? [[], args[0]] : [args[0], args[1]];
406
+ if (contract.meta) {
407
+ const key = `${contract.method} ${path}`;
408
+ app._contractMetas.set(key, contract.meta);
409
+ }
410
+ const boundHandler = bind(contract, handler);
411
+ const handlers = middlewares.length > 0 ? [...middlewares, boundHandler] : [boundHandler];
412
+ switch (method) {
413
+ case "get":
414
+ hono.get(path, ...handlers);
415
+ break;
416
+ case "post":
417
+ hono.post(path, ...handlers);
418
+ break;
419
+ case "put":
420
+ hono.put(path, ...handlers);
421
+ break;
422
+ case "patch":
423
+ hono.patch(path, ...handlers);
424
+ break;
425
+ case "delete":
426
+ hono.delete(path, ...handlers);
427
+ break;
428
+ default:
429
+ throw new Error(`Unsupported HTTP method: ${contract.method}`);
430
+ }
431
+ };
432
+ return app;
433
+ }
434
+
435
+ // src/route/types.ts
436
+ function isHttpMethod(value) {
437
+ return typeof value === "string" && ["GET", "POST", "PUT", "PATCH", "DELETE"].includes(value);
438
+ }
439
+
440
+ export { AutoRouteLoader, bind, createApp, isHttpMethod, loadRoutes };
441
+ //# sourceMappingURL=index.js.map
442
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/route/auto-loader.ts","../../src/errors/database-errors.ts","../../src/route/bind.ts","../../src/route/create-app.ts","../../src/route/types.ts"],"names":["relativePath"],"mappings":"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","file":"index.js","sourcesContent":["import { readdir, stat } from 'fs/promises';\nimport { join, relative } from 'path';\nimport type { Hono } from 'hono';\n\n/**\n * Extend Hono Context to support skipMiddlewares metadata\n */\ndeclare module 'hono' {\n interface ContextVariableMap {\n _skipMiddlewares?: string[];\n }\n}\n\n/**\n * AutoRouteLoader: Simplified File-based Routing System\n *\n * ## Features\n * - šŸ“ Auto-discovery: Scans routes directory and auto-registers\n * - šŸ”„ Dynamic routes: [id] → :id, [...slug] → *\n * - šŸ“Š Statistics: Route registration stats for dashboard\n * - šŸ·ļø Grouping: Natural grouping by directory structure\n *\n * ## Usage\n * ```typescript\n * const app = new Hono();\n * await loadRoutes(app);\n * ```\n */\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type RouteInfo = {\n /** URL path (e.g., /users/:id) */\n path: string;\n /** File path relative to routes dir */\n file: string;\n /** Route metadata from export */\n meta?: {\n description?: string;\n tags?: string[];\n auth?: boolean;\n [key: string]: unknown;\n };\n /** Priority (1=static, 2=dynamic, 3=catch-all) */\n priority: number;\n};\n\nexport type RouteStats = {\n total: number;\n byPriority: {\n static: number;\n dynamic: number;\n catchAll: number;\n };\n byTag: Record<string, number>;\n routes: RouteInfo[];\n};\n\n// ============================================================================\n// Main Loader Class\n// ============================================================================\n\nexport class AutoRouteLoader\n{\n private routes: RouteInfo[] = [];\n private registeredRoutes = new Map<string, string>(); // normalized path → file\n private debug: boolean;\n private readonly middlewares: Array<{ name: string; handler: any }>;\n\n constructor(\n private routesDir: string,\n debug = false,\n middlewares: Array<{ name: string; handler: any }> = []\n )\n {\n this.debug = debug;\n this.middlewares = middlewares;\n }\n\n /**\n * Load all routes from directory\n */\n async load(app: Hono): Promise<RouteStats>\n {\n const startTime = Date.now();\n\n // 1. Scan files\n const files = await this.scanFiles(this.routesDir);\n\n if (files.length === 0)\n {\n console.warn('āš ļø No route files found');\n return this.getStats();\n }\n\n // 2. Calculate priorities for all files\n const filesWithPriority = files.map(file => ({\n path: file,\n priority: this.calculatePriority(relative(this.routesDir, file)),\n }));\n\n // 3. Sort by priority (static=1, dynamic=2, catch-all=3)\n filesWithPriority.sort((a, b) => a.priority - b.priority);\n\n if (this.debug)\n {\n console.log(`\\nšŸ“‹ Route Registration Order:`);\n console.log(` Priority 1 (Static): ${filesWithPriority.filter(f => f.priority === 1).length} routes`);\n console.log(` Priority 2 (Dynamic): ${filesWithPriority.filter(f => f.priority === 2).length} routes`);\n console.log(` Priority 3 (Catch-all): ${filesWithPriority.filter(f => f.priority === 3).length} routes\\n`);\n }\n\n // 4. Load and register routes in priority order\n let successCount = 0;\n let failureCount = 0;\n\n for (const { path } of filesWithPriority)\n {\n const success = await this.loadRoute(app, path);\n if (success)\n {\n successCount++;\n }\n else\n {\n failureCount++;\n }\n }\n\n // 5. Log stats\n const elapsed = Date.now() - startTime;\n const stats = this.getStats();\n\n if (this.debug)\n {\n this.logStats(stats, elapsed);\n }\n\n if (failureCount > 0)\n {\n console.warn(`āš ļø ${failureCount} route(s) failed to load`);\n }\n\n return stats;\n }\n\n /**\n * Get route statistics\n */\n getStats(): RouteStats\n {\n const stats: RouteStats = {\n total: this.routes.length,\n byPriority: { static: 0, dynamic: 0, catchAll: 0 },\n byTag: {},\n routes: this.routes,\n };\n\n for (const route of this.routes)\n {\n // Count by priority\n if (route.priority === 1) stats.byPriority.static++;\n else if (route.priority === 2) stats.byPriority.dynamic++;\n else if (route.priority === 3) stats.byPriority.catchAll++;\n\n // Count by tag\n if (route.meta?.tags)\n {\n for (const tag of route.meta.tags)\n {\n stats.byTag[tag] = (stats.byTag[tag] || 0) + 1;\n }\n }\n }\n\n return stats;\n }\n\n // ========================================================================\n // Private Methods\n // ========================================================================\n\n /**\n * Recursively scan directory for .ts files\n */\n private async scanFiles(dir: string, files: string[] = []): Promise<string[]>\n {\n const entries = await readdir(dir);\n\n for (const entry of entries)\n {\n const fullPath = join(dir, entry);\n const fileStat = await stat(fullPath);\n\n if (fileStat.isDirectory())\n {\n // Recurse into subdirectories\n await this.scanFiles(fullPath, files);\n }\n else if (this.isValidRouteFile(entry))\n {\n files.push(fullPath);\n }\n }\n\n return files;\n }\n\n /**\n * Check if file is a valid route file\n */\n private isValidRouteFile(fileName: string): boolean\n {\n return (\n fileName.endsWith('.ts') &&\n !fileName.endsWith('.test.ts') &&\n !fileName.endsWith('.spec.ts') &&\n !fileName.endsWith('.d.ts') &&\n fileName !== 'contract.ts'\n );\n }\n\n /**\n * Load and register a single route\n * Returns true if successful, false if failed\n */\n private async loadRoute(app: Hono, absolutePath: string): Promise<boolean>\n {\n const relativePath = relative(this.routesDir, absolutePath);\n\n try\n {\n // Import module\n const module = await import(absolutePath);\n\n if (!module.default)\n {\n console.error(`āŒ ${relativePath}: Must export Hono instance as default`);\n return false;\n }\n\n // Validate that it's actually a Hono instance\n if (typeof module.default.route !== 'function')\n {\n console.error(`āŒ ${relativePath}: Default export is not a Hono instance`);\n return false;\n }\n\n // Convert file path to URL path\n const urlPath = this.fileToPath(relativePath);\n const priority = this.calculatePriority(relativePath);\n\n // Check for route conflicts\n const normalizedPath = this.normalizePath(urlPath);\n const existingFile = this.registeredRoutes.get(normalizedPath);\n\n if (existingFile)\n {\n console.warn(`āš ļø Route conflict detected:`);\n console.warn(` Path: ${urlPath} (normalized: ${normalizedPath})`);\n console.warn(` Already registered by: ${existingFile}`);\n console.warn(` Attempted by: ${relativePath}`);\n console.warn(` → Skipping duplicate registration`);\n return false;\n }\n\n // Track registration\n this.registeredRoutes.set(normalizedPath, relativePath);\n\n // Check if module uses contract-based routing (createApp)\n const hasContractMetas = module.default._contractMetas && module.default._contractMetas.size > 0;\n\n if (hasContractMetas)\n {\n // Contract-based routing: method-level skipMiddlewares\n // Use wildcard pattern to match all sub-paths (e.g., /test/* matches /test/public, /test/private)\n const middlewarePath = urlPath === '/' ? '/*' : `${urlPath}/*`;\n\n // 1. Register meta-setting middleware first\n app.use(middlewarePath, (c, next) =>\n {\n const method = c.req.method;\n const requestPath = new URL(c.req.url).pathname;\n\n // Calculate relative path by removing the route base path\n // E.g., if urlPath = '/test' and requestPath = '/test/public', then relativePath = '/public'\n const relativePath = requestPath.startsWith(urlPath)\n ? requestPath.slice(urlPath.length) || '/'\n : requestPath;\n\n const key = `${method} ${relativePath}`;\n const meta = module.default._contractMetas?.get(key);\n\n if (meta?.skipMiddlewares)\n {\n c.set('_skipMiddlewares', meta.skipMiddlewares);\n }\n\n return next();\n });\n\n // 2. Wrap global middlewares to check skipMiddlewares\n for (const middleware of this.middlewares)\n {\n app.use(middlewarePath, async (c, next) =>\n {\n const skipList = c.get('_skipMiddlewares') || [];\n\n if (skipList.includes(middleware.name))\n {\n return next(); // Skip this middleware\n }\n\n return middleware.handler(c, next); // Execute middleware\n });\n }\n }\n else\n {\n // File-based routing: file-level skipMiddlewares (fallback)\n const skipList = module.meta?.skipMiddlewares || [];\n const activeMiddlewares = this.middlewares\n .filter(m => !skipList.includes(m.name));\n\n for (const middleware of activeMiddlewares)\n {\n app.use(urlPath, middleware.handler);\n }\n }\n\n // Register route\n app.route(urlPath, module.default);\n\n // Store route info\n this.routes.push({\n path: urlPath,\n file: relativePath,\n meta: module.meta,\n priority,\n });\n\n if (this.debug)\n {\n const icon = priority === 1 ? 'šŸ”¹' : priority === 2 ? 'šŸ”ø' : '⭐';\n console.log(` ${icon} ${urlPath.padEnd(40)} → ${relativePath}`);\n }\n\n return true;\n }\n catch (error)\n {\n const err = error as Error;\n\n // Categorize error types and provide helpful messages\n if (err.message.includes('Cannot find module') || err.message.includes('MODULE_NOT_FOUND'))\n {\n console.error(`āŒ ${relativePath}: Missing dependency`);\n console.error(` ${err.message}`);\n console.error(` → Run: npm install`);\n }\n else if (err.message.includes('SyntaxError') || err.stack?.includes('SyntaxError'))\n {\n console.error(`āŒ ${relativePath}: Syntax error`);\n console.error(` ${err.message}`);\n\n if (this.debug && err.stack)\n {\n console.error(` Stack trace (first 5 lines):`);\n const stackLines = err.stack.split('\\n').slice(0, 5);\n stackLines.forEach(line => console.error(` ${line}`));\n }\n }\n else if (err.message.includes('Unexpected token'))\n {\n console.error(`āŒ ${relativePath}: Parse error`);\n console.error(` ${err.message}`);\n console.error(` → Check for syntax errors or invalid TypeScript`);\n }\n else\n {\n console.error(`āŒ ${relativePath}: ${err.message}`);\n\n if (this.debug && err.stack)\n {\n console.error(` Stack: ${err.stack}`);\n }\n }\n\n return false;\n }\n }\n\n /**\n * Convert file path to URL path\n *\n * Examples:\n * - users/index.ts → /users\n * - users/[id].ts → /users/:id\n * - posts/[...slug].ts → /posts/*\n */\n private fileToPath(filePath: string): string\n {\n // Remove .ts extension\n let path = filePath.replace(/\\.ts$/, '');\n\n // Split into segments\n const segments = path.split('/');\n\n // Remove 'index' if it's the last segment\n if (segments[segments.length - 1] === 'index')\n {\n segments.pop();\n }\n\n // Transform segments: [id] → :id, [...slug] → *\n const transformed = segments.map(seg =>\n {\n // Catch-all: [...slug] → *\n if (/^\\[\\.\\.\\.[\\w-]+]$/.test(seg))\n {\n return '*';\n }\n // Dynamic: [id] → :id\n if (/^\\[[\\w-]+]$/.test(seg))\n {\n return ':' + seg.slice(1, -1);\n }\n // Skip 'index' segments (index/index.ts → /, posts/index/index.ts → /posts)\n if (seg === 'index')\n {\n return null;\n }\n // Static: users → users\n return seg;\n }).filter(seg => seg !== null);\n\n // Join and ensure leading slash\n const result = '/' + transformed.join('/');\n return result.replace(/\\/+/g, '/').replace(/\\/$/, '') || '/';\n }\n\n /**\n * Calculate route priority\n * 1 = static, 2 = dynamic, 3 = catch-all\n */\n private calculatePriority(path: string): number\n {\n if (/\\[\\.\\.\\.[\\w-]+]/.test(path)) return 3; // Catch-all\n if (/\\[[\\w-]+]/.test(path)) return 2; // Dynamic\n return 1; // Static\n }\n\n /**\n * Normalize path for conflict detection\n *\n * Converts dynamic parameter names to generic placeholders:\n * - /users/:id → /users/:param\n * - /users/:userId → /users/:param (conflict!)\n * - /posts/* → /posts/* (unchanged)\n *\n * This allows detection of routes with different param names\n * that would match the same URL patterns.\n */\n private normalizePath(path: string): string\n {\n // Replace all dynamic params (:xxx) with :param\n // This allows us to detect conflicts like /users/:id and /users/:userId\n return path.replace(/:\\w+/g, ':param');\n }\n\n /**\n * Log statistics\n */\n private logStats(stats: RouteStats, elapsed: number): void\n {\n console.log(`\\nšŸ“Š Route Statistics:`);\n console.log(` Total: ${stats.total} routes`);\n console.log(\n ` Priority: ${stats.byPriority.static} static, ` +\n `${stats.byPriority.dynamic} dynamic, ` +\n `${stats.byPriority.catchAll} catch-all`\n );\n\n if (Object.keys(stats.byTag).length > 0)\n {\n const tagCounts = Object.entries(stats.byTag)\n .map(([tag, count]) => `${tag}(${count})`)\n .join(', ');\n console.log(` Tags: ${tagCounts}`);\n }\n\n console.log(`\\nāœ… Routes loaded in ${elapsed}ms\\n`);\n }\n}\n\n// ============================================================================\n// Convenience Function\n// ============================================================================\n\n/**\n * Load routes from default location (src/server/routes)\n */\nexport async function loadRoutes(\n app: Hono,\n options?: {\n routesDir?: string;\n debug?: boolean;\n middlewares?: Array<{ name: string; handler: any }>;\n }\n): Promise<RouteStats>\n{\n const routesDir = options?.routesDir ?? join(process.cwd(), 'src', 'server', 'routes');\n const debug = options?.debug ?? false;\n const middlewares = options?.middlewares ?? [];\n\n const loader = new AutoRouteLoader(routesDir, debug, middlewares);\n return loader.load(app);\n}","/**\n * Database Error Classes\n *\n * Type-safe error handling with custom error class hierarchy\n * Mapped to HTTP status codes for API responses\n */\n\n/**\n * Base Database Error\n *\n * Base class for all database-related errors\n */\nexport class DatabaseError<TDetails extends Record<string, unknown> = Record<string, unknown>> extends Error\n{\n public readonly statusCode: number;\n public readonly details?: TDetails;\n public readonly timestamp: Date;\n\n constructor(\n message: string,\n statusCode: number = 500,\n details?: TDetails\n )\n {\n super(message);\n this.name = 'DatabaseError';\n this.statusCode = statusCode;\n this.details = details;\n this.timestamp = new Date();\n Error.captureStackTrace(this, this.constructor);\n }\n\n /**\n * Serialize error for API response\n */\n toJSON()\n {\n return {\n name: this.name,\n message: this.message,\n statusCode: this.statusCode,\n details: this.details,\n timestamp: this.timestamp.toISOString()\n };\n }\n}\n\n/**\n * Connection Error (503 Service Unavailable)\n *\n * Database connection failure, connection pool exhaustion, etc.\n */\nexport class ConnectionError extends DatabaseError\n{\n constructor(message: string, details?: Record<string, any>)\n {\n super(message, 503, details);\n this.name = 'ConnectionError';\n }\n}\n\n/**\n * Query Error (500 Internal Server Error)\n *\n * SQL query execution failure, syntax errors, etc.\n */\nexport class QueryError extends DatabaseError\n{\n constructor(message: string, statusCode: number = 500, details?: Record<string, any>)\n {\n super(message, statusCode, details);\n this.name = 'QueryError';\n }\n}\n\n/**\n * Not Found Error (404 Not Found)\n *\n * Requested resource does not exist\n */\nexport class NotFoundError extends QueryError\n{\n constructor(resource: string, id: string | number)\n {\n super(`${resource} with id ${id} not found`, 404, { resource, id });\n this.name = 'NotFoundError';\n }\n}\n\n/**\n * Validation Error (400 Bad Request)\n *\n * Input data validation failure\n */\nexport class ValidationError extends QueryError\n{\n constructor(message: string, details?: Record<string, any>)\n {\n super(message, 400, details);\n this.name = 'ValidationError';\n }\n}\n\n/**\n * Transaction Error (500 Internal Server Error)\n *\n * Transaction start/commit/rollback failure\n */\nexport class TransactionError extends DatabaseError\n{\n constructor(message: string, statusCode: number = 500, details?: Record<string, any>)\n {\n super(message, statusCode, details);\n this.name = 'TransactionError';\n }\n}\n\n/**\n * Deadlock Error (409 Conflict)\n *\n * Database deadlock detected\n */\nexport class DeadlockError extends TransactionError\n{\n constructor(message: string, details?: Record<string, any>)\n {\n super(message, 409, details);\n this.name = 'DeadlockError';\n }\n}\n\n/**\n * Duplicate Entry Error (409 Conflict)\n *\n * Unique constraint violation (e.g., duplicate email)\n */\nexport class DuplicateEntryError extends QueryError\n{\n constructor(field: string, value: string | number)\n {\n super(`${field} '${value}' already exists`, 409, { field, value });\n this.name = 'DuplicateEntryError';\n }\n}","import type { Context } from 'hono';\nimport { Value } from '@sinclair/typebox/value';\nimport type { RouteContract, RouteContext, InferContract } from './types.js';\nimport { ValidationError } from '../errors/database-errors.js';\n\n/**\n * Contract-based Route Handler Wrapper\n *\n * Binds a contract to a route handler, providing automatic validation\n * and type-safe context creation.\n *\n * ## Features\n * - āœ… Automatic params/query/body validation using TypeBox\n * - āœ… Type-safe RouteContext with contract-based inference\n * - āœ… Clean separation: bind() for validation, Hono for middleware\n *\n * ## Usage\n *\n * ```typescript\n * // Basic usage\n * export const GET = bind(contract, async (c) => {\n * return c.json({ data: 'public' });\n * });\n *\n * // For middleware, use Hono's app-level or route-level middleware:\n * // app.use('/api/*', authMiddleware);\n * // app.get('/users/:id', authMiddleware, bind(contract, handler));\n * ```\n *\n * @param contract - Route contract defining params, query, body, response schemas\n * @param handler - Route handler function\n * @returns Hono-compatible handler function\n */\nexport function bind<TContract extends RouteContract>(\n contract: TContract,\n handler: (c: RouteContext<TContract>) => Response | Promise<Response>\n)\n{\n return async (rawContext: Context) =>\n {\n // ============================================================\n // 1. Validate params\n // ============================================================\n const params = rawContext.req.param();\n if (contract.params)\n {\n const errors = [...Value.Errors(contract.params, params)];\n if (errors.length > 0)\n {\n throw new ValidationError(\n 'Invalid path parameters',\n {\n fields: errors.map(e => ({\n path: e.path,\n message: e.message,\n value: e.value,\n }))\n }\n );\n }\n }\n\n // ============================================================\n // 2. Validate query\n // ============================================================\n const url = new URL(rawContext.req.url);\n const query: Record<string, string | string[]> = {};\n url.searchParams.forEach((v, k) =>\n {\n const existing = query[k];\n if (existing)\n {\n // Convert to array or append to existing array\n query[k] = Array.isArray(existing) ? [...existing, v] : [existing, v];\n }\n else\n {\n query[k] = v;\n }\n });\n\n if (contract.query)\n {\n const errors = [...Value.Errors(contract.query, query)];\n if (errors.length > 0)\n {\n throw new ValidationError(\n 'Invalid query parameters',\n {\n fields: errors.map(e => ({\n path: e.path,\n message: e.message,\n value: e.value,\n }))\n }\n );\n }\n }\n\n // ============================================================\n // 3. Create RouteContext\n // ============================================================\n const routeContext: RouteContext<TContract> = {\n params: params as InferContract<TContract>['params'],\n query: query as InferContract<TContract>['query'],\n\n // data() - validates and returns body\n data: async () =>\n {\n const body = await rawContext.req.json();\n if (contract.body)\n {\n const errors = [...Value.Errors(contract.body, body)];\n if (errors.length > 0)\n {\n throw new ValidationError(\n 'Invalid request body',\n {\n fields: errors.map(e => ({\n path: e.path,\n message: e.message,\n value: e.value,\n }))\n }\n );\n }\n }\n return body as InferContract<TContract>['body'];\n },\n\n // json() - returns typed response\n json: (data, status, headers) =>\n {\n return rawContext.json(data, status, headers);\n },\n\n // raw Hono context for advanced usage\n raw: rawContext,\n };\n\n // ============================================================\n // 4. Execute handler\n // ============================================================\n return handler(routeContext);\n };\n}","/**\n * Create App - Hono Wrapper for Contract-based Routing\n *\n * Provides a cleaner API for registering routes with contracts\n */\n\nimport { Hono } from 'hono';\nimport type { MiddlewareHandler } from 'hono';\nimport { bind } from './bind.js';\nimport type { RouteContract, RouteHandler } from './types.js';\n\n/**\n * Extended Hono app with bind() method\n */\nexport type SPFNApp = Hono & {\n /**\n * Bind a contract to a handler with optional middlewares\n *\n * @example\n * ```ts\n * // Handler only\n * app.bind(getUserContract, async (c) => {\n * return c.json({ id: c.params.id });\n * });\n *\n * // With middlewares\n * app.bind(createUserContract, [authMiddleware, logMiddleware], async (c) => {\n * const body = await c.data();\n * return c.json({ id: '123' });\n * });\n * ```\n */\n bind<TContract extends RouteContract>(\n contract: TContract,\n handler: RouteHandler\n ): void;\n\n bind<TContract extends RouteContract>(\n contract: TContract,\n middlewares: MiddlewareHandler[],\n handler: RouteHandler\n ): void;\n\n /**\n * Contract metadata storage\n * Map<\"METHOD /path\", RouteMeta>\n * @internal\n */\n _contractMetas?: Map<string, RouteContract['meta']>;\n};\n\n/**\n * Create SPFN app instance\n *\n * Wraps Hono with contract-based routing support\n *\n * @example\n * ```ts\n * import { createApp } from '@spfn/core/route';\n * import { getUserContract, createUserContract } from '@/server/contracts/users';\n *\n * const app = createApp();\n *\n * // Register routes using contracts\n * app.bind(getUserContract, async (c) => {\n * return c.json({ id: c.params.id });\n * });\n *\n * app.bind(createUserContract, [authMiddleware], async (c) => {\n * const body = await c.data();\n * return c.json({ id: '123' });\n * });\n *\n * export default app;\n * ```\n */\nexport function createApp(): SPFNApp\n{\n const hono = new Hono();\n\n // Add bind method\n const app = hono as SPFNApp;\n\n // Initialize contract metadata storage\n app._contractMetas = new Map();\n\n app.bind = function <TContract extends RouteContract>(\n contract: TContract,\n ...args: [RouteHandler] | [MiddlewareHandler[], RouteHandler]\n )\n {\n const method = contract.method.toLowerCase();\n const path = contract.path;\n\n // Extract middlewares and handler\n const [middlewares, handler] = args.length === 1\n ? [[], args[0]]\n : [args[0], args[1]];\n\n // Store contract metadata for auto-loader\n if (contract.meta)\n {\n const key = `${contract.method} ${path}`;\n app._contractMetas!.set(key, contract.meta);\n }\n\n // Register with Hono using bind() for validation\n const boundHandler = bind(contract, handler);\n\n // Build handler array\n const handlers = middlewares.length > 0\n ? [...middlewares, boundHandler]\n : [boundHandler];\n\n // Register based on HTTP method\n switch (method)\n {\n case 'get':\n hono.get(path, ...handlers);\n break;\n case 'post':\n hono.post(path, ...handlers);\n break;\n case 'put':\n hono.put(path, ...handlers);\n break;\n case 'patch':\n hono.patch(path, ...handlers);\n break;\n case 'delete':\n hono.delete(path, ...handlers);\n break;\n default:\n throw new Error(`Unsupported HTTP method: ${contract.method}`);\n }\n };\n\n return app;\n}","import type { Context } from 'hono';\nimport type { ContentfulStatusCode } from 'hono/utils/http-status';\nimport type { TSchema, Static } from '@sinclair/typebox';\n\n/**\n * Header record type compatible with Hono's c.json headers parameter\n */\nexport type HeaderRecord = Record<string, string | string[]>;\n\n/**\n * File-based Routing System Type Definitions\n *\n * ## Type Flow\n * ```\n * RouteFile (scan)\n * ↓\n * RouteModule (dynamic import + contracts)\n * ↓\n * RouteDefinition (transformation + validation)\n * ↓\n * Hono App (registration)\n * ```\n *\n * ## Core Types\n * 1. **RouteContract**: TypeBox-based contract definition for type safety and validation\n * 2. **RouteContext**: Extended Context for route handlers (params, query, data)\n * 3. **RouteHandler**: Next.js App Router style handler function type\n * 4. **RouteFile**: File system scan result (Scanner output)\n * 5. **RouteModule**: Dynamic import result (Mapper input)\n * 6. **RouteDefinition**: Transformed route definition (Mapper output, Registry storage)\n * 7. **RouteMeta**: Route metadata (auth, tags, description, etc.)\n * 8. **RoutePriority**: Priority enum (STATIC, DYNAMIC, CATCH_ALL)\n * 9. **ScanOptions**: Scanner configuration options\n *\n * ## Applied Improvements\n * āœ… **HTTP Method Types**: Added HttpMethod union type\n * āœ… **Route Grouping**: Added RouteGroup, RouteStats types\n * āœ… **Type Guards**: isRouteFile, isRouteDefinition, isHttpMethod, hasHttpMethodHandlers\n * āœ… **Contract-based Types**: RouteContract, InferContract for end-to-end type safety\n * āœ… **Generic RouteContext**: RouteContext<TContract> for typed params, query, body, response\n */\n\n// ============================================================================\n// Contract Types\n// ============================================================================\n\n/**\n * Route metadata for additional configuration\n */\nexport type RouteMeta = {\n /** Public route (skip authentication) - default: false */\n public?: boolean;\n /** Skip specific global middlewares by name */\n skipMiddlewares?: string[];\n /** OpenAPI tags for grouping */\n tags?: string[];\n /** Route description for documentation */\n description?: string;\n /** Deprecated flag */\n deprecated?: boolean;\n};\n\n/**\n * Route Contract: TypeBox-based type-safe route definition\n *\n * Defines the shape of request/response for a route endpoint\n *\n * Note: params and query are always Record<string, string> from URL,\n * but can be validated and transformed via TypeBox schemas\n */\nexport type RouteContract = {\n /** HTTP method (GET, POST, PUT, etc.) */\n method: HttpMethod;\n /** Route path (e.g., /users/:id) */\n path: string;\n /** Path parameters schema (optional) - input is always Record<string, string> */\n params?: TSchema;\n /** Query parameters schema (optional) - input is always Record<string, string | string[]> */\n query?: TSchema;\n /** Request body schema (optional) */\n body?: TSchema;\n /** Response schema (required) */\n response: TSchema;\n /** Route metadata (optional) */\n meta?: RouteMeta;\n};\n\n/**\n * Infer types from RouteContract\n *\n * Extracts TypeScript types from TypeBox schemas\n */\nexport type InferContract<TContract extends RouteContract> = {\n params: TContract['params'] extends TSchema\n ? Static<TContract['params']>\n : Record<string, never>;\n query: TContract['query'] extends TSchema\n ? Static<TContract['query']>\n : Record<string, never>;\n body: TContract['body'] extends TSchema\n ? Static<TContract['body']>\n : Record<string, never>;\n response: TContract['response'] extends TSchema\n ? Static<TContract['response']>\n : unknown;\n};\n\n/**\n * RouteContext: Route Handler Dedicated Context\n *\n * Generic version with contract-based type inference\n *\n * Convenience methods provided:\n * - params: Path parameters (typed via contract)\n * - query: Query parameters (typed via contract)\n * - data(): Request Body parsing helper (typed via contract)\n * - json(): JSON response helper (typed via contract)\n * - raw: Original Hono Context (advanced features: raw.req, raw.get(), raw.set(), etc.)\n */\nexport type RouteContext<TContract extends RouteContract = any> = {\n /**\n * Path parameters (typed via contract)\n */\n params: InferContract<TContract>['params'];\n\n /**\n * Query parameters (typed via contract)\n */\n query: InferContract<TContract>['query'];\n\n /**\n * Request Body parsing helper (typed via contract)\n */\n data(): Promise<InferContract<TContract>['body']>;\n\n /**\n * JSON response helper (typed via contract)\n */\n json(\n data: InferContract<TContract>['response'],\n status?: ContentfulStatusCode,\n headers?: HeaderRecord\n ): Response;\n\n /**\n * Original Hono Context (for advanced features when needed)\n * - raw.req: Request object (headers, cookies, etc.)\n * - raw.get(): Read context variables (middleware data)\n * - raw.set(): Set context variables\n */\n raw: Context;\n};\n\n/**\n * HTTP method type (common REST API methods)\n */\nexport type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\n\n/**\n * Next.js App Router Style Route Handler\n *\n * Function that receives RouteContext and returns Response\n */\nexport type RouteHandler = (c: RouteContext) => Response | Promise<Response>;\n\n/**\n * HttpMethod type guard\n */\nexport function isHttpMethod(value: unknown): value is HttpMethod\n{\n return (\n typeof value === 'string' &&\n ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'].includes(value)\n );\n}"]}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Generate Client CLI Command
3
+ *
4
+ * Generates type-safe API client from route contracts
5
+ *
6
+ * Usage:
7
+ * node dist/scripts/generate-client.js
8
+ * node dist/scripts/generate-client.js --watch
9
+ */
10
+ /**
11
+ * CLI options
12
+ */
13
+ interface GenerateClientOptions {
14
+ contractsDir?: string;
15
+ output?: string;
16
+ baseUrl?: string;
17
+ watch?: boolean;
18
+ }
19
+ /**
20
+ * Main generate client command
21
+ */
22
+ declare function generateClientCommand(options?: GenerateClientOptions): Promise<void>;
23
+
24
+ export { generateClientCommand };