@voltx/server 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,92 @@
1
+ <p align="center">
2
+ <strong>@voltx/server</strong><br/>
3
+ <em>Hono-based HTTP server with file-based routing and SSE streaming</em>
4
+ </p>
5
+
6
+ <p align="center">
7
+ <a href="https://www.npmjs.com/package/@voltx/server"><img src="https://img.shields.io/npm/v/@voltx/server?color=blue" alt="npm" /></a>
8
+ <a href="https://www.npmjs.com/package/@voltx/server"><img src="https://img.shields.io/npm/dm/@voltx/server" alt="downloads" /></a>
9
+ <a href="https://github.com/codewithshail/voltx/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/@voltx/server" alt="license" /></a>
10
+ </p>
11
+
12
+ ---
13
+
14
+ The HTTP layer of the [VoltX](https://github.com/codewithshail/voltx) framework. Built on [Hono](https://hono.dev) with file-based routing (Next.js-style), CORS, logging, error handling, and static file serving out of the box.
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install @voltx/server
20
+ ```
21
+
22
+ ## Quick Start
23
+
24
+ ```ts
25
+ import { createServer } from "@voltx/server";
26
+
27
+ const server = createServer({
28
+ port: 3000,
29
+ routesDir: "src/routes",
30
+ cors: true,
31
+ logger: true,
32
+ });
33
+
34
+ await server.start();
35
+ // ⚡ VoltX server running at http://localhost:3000
36
+ ```
37
+
38
+ ## File-Based Routing
39
+
40
+ Drop files in `src/routes/` and they become API endpoints automatically:
41
+
42
+ ```
43
+ src/routes/index.ts → GET /
44
+ src/routes/api/chat.ts → POST /api/chat
45
+ src/routes/api/users/[id].ts → GET /api/users/:id
46
+ src/routes/api/[...slug].ts → /api/* (catch-all)
47
+ ```
48
+
49
+ Each file exports HTTP method handlers:
50
+
51
+ ```ts
52
+ // src/routes/api/chat.ts
53
+ import type { Context } from "@voltx/server";
54
+
55
+ export async function POST(c: Context) {
56
+ const body = await c.req.json();
57
+ return c.json({ message: "Hello!" });
58
+ }
59
+
60
+ export function GET(c: Context) {
61
+ return c.json({ status: "ok" });
62
+ }
63
+ ```
64
+
65
+ ## Features
66
+
67
+ - **File-based routing** — Next.js-style, zero config
68
+ - **Dynamic routes** — `[param]` → `:param`, `[...slug]` → catch-all
69
+ - **Built-in middleware** — CORS, request logging, error handling
70
+ - **Static file serving** — Serves from `public/` directory
71
+ - **Per-route middleware** — Export `middleware` from any route file
72
+ - **Graceful shutdown** — Clean server stop with `server.stop()`
73
+ - **Full Hono access** — `server.app` gives you the raw Hono instance
74
+
75
+ ## Configuration
76
+
77
+ | Option | Type | Default | Description |
78
+ |--------|------|---------|-------------|
79
+ | `port` | `number` | `3000` | Server port |
80
+ | `hostname` | `string` | `"0.0.0.0"` | Bind address |
81
+ | `routesDir` | `string` | `"src/routes"` | Routes directory |
82
+ | `staticDir` | `string` | `"public"` | Static files directory |
83
+ | `cors` | `boolean \| object` | `true` | CORS configuration |
84
+ | `logger` | `boolean` | `true` (dev) | Request logging |
85
+
86
+ ## Part of VoltX
87
+
88
+ This package is part of the [VoltX](https://github.com/codewithshail/voltx) framework. See the [monorepo](https://github.com/codewithshail/voltx) for full documentation.
89
+
90
+ ## License
91
+
92
+ [MIT](https://github.com/codewithshail/voltx/blob/main/LICENSE) — Made by the [Promptly AI Team](https://buymeacoffee.com/promptlyai)
package/dist/index.cjs ADDED
@@ -0,0 +1,286 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ Hono: () => import_hono2.Hono,
24
+ VERSION: () => VERSION,
25
+ createCorsMiddleware: () => createCorsMiddleware,
26
+ createErrorHandler: () => createErrorHandler,
27
+ createLoggerMiddleware: () => createLoggerMiddleware,
28
+ createServer: () => createServer,
29
+ filePathToUrlPath: () => filePathToUrlPath,
30
+ registerStaticFiles: () => registerStaticFiles,
31
+ scanAndRegisterRoutes: () => scanAndRegisterRoutes
32
+ });
33
+ module.exports = __toCommonJS(index_exports);
34
+
35
+ // src/server.ts
36
+ var import_hono = require("hono");
37
+ var import_node_server = require("@hono/node-server");
38
+ var import_node_path2 = require("path");
39
+
40
+ // src/middleware/cors.ts
41
+ var import_cors = require("hono/cors");
42
+ function createCorsMiddleware(config) {
43
+ if (config === false) return null;
44
+ const opts = typeof config === "object" ? config : {};
45
+ return (0, import_cors.cors)({
46
+ origin: opts.origin ?? "*",
47
+ allowMethods: opts.allowMethods ?? ["GET", "HEAD", "PUT", "POST", "DELETE", "PATCH"],
48
+ allowHeaders: opts.allowHeaders ?? ["Content-Type", "Authorization"],
49
+ exposeHeaders: opts.exposeHeaders ?? [],
50
+ maxAge: opts.maxAge ?? 600,
51
+ credentials: opts.credentials ?? false
52
+ });
53
+ }
54
+
55
+ // src/middleware/logger.ts
56
+ var import_logger = require("hono/logger");
57
+ function createLoggerMiddleware() {
58
+ return (0, import_logger.logger)((message, ...rest) => {
59
+ console.log(`[voltx] ${message}`, ...rest);
60
+ });
61
+ }
62
+
63
+ // src/middleware/error-handler.ts
64
+ function createErrorHandler(customHandler) {
65
+ return async (err, c) => {
66
+ if (customHandler) {
67
+ try {
68
+ return await customHandler(err, c);
69
+ } catch {
70
+ }
71
+ }
72
+ const status = extractStatus(err);
73
+ const isDev = process.env.NODE_ENV !== "production";
74
+ console.error(`[voltx] Error ${status}:`, err.message);
75
+ if (isDev && err.stack) {
76
+ console.error(err.stack);
77
+ }
78
+ return c.json(
79
+ {
80
+ error: {
81
+ message: isDev ? err.message : "Internal Server Error",
82
+ status,
83
+ ...isDev && err.stack ? { stack: err.stack } : {}
84
+ }
85
+ },
86
+ status
87
+ );
88
+ };
89
+ }
90
+ function extractStatus(err) {
91
+ if (err && typeof err === "object") {
92
+ if ("status" in err && typeof err.status === "number") {
93
+ return err.status;
94
+ }
95
+ if ("statusCode" in err && typeof err.statusCode === "number") {
96
+ return err.statusCode;
97
+ }
98
+ }
99
+ return 500;
100
+ }
101
+
102
+ // src/router.ts
103
+ var import_promises = require("fs/promises");
104
+ var import_node_path = require("path");
105
+ var import_node_url = require("url");
106
+ var HTTP_METHODS = ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"];
107
+ var ROUTE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".js", ".mjs", ".mts"]);
108
+ async function scanAndRegisterRoutes(app, routesDir) {
109
+ const entries = [];
110
+ const files = await collectRouteFiles(routesDir);
111
+ files.sort((a, b) => {
112
+ const pathA = filePathToUrlPath(a, routesDir);
113
+ const pathB = filePathToUrlPath(b, routesDir);
114
+ const scoreA = pathA.includes("*") ? 2 : pathA.includes(":") ? 1 : 0;
115
+ const scoreB = pathB.includes("*") ? 2 : pathB.includes(":") ? 1 : 0;
116
+ if (scoreA !== scoreB) return scoreA - scoreB;
117
+ return pathA.localeCompare(pathB);
118
+ });
119
+ for (const filePath of files) {
120
+ const urlPath = filePathToUrlPath(filePath, routesDir);
121
+ const routeModule = await importRouteModule(filePath);
122
+ if (!routeModule) continue;
123
+ if (routeModule.middleware) {
124
+ const middlewares = Array.isArray(routeModule.middleware) ? routeModule.middleware : [routeModule.middleware];
125
+ for (const mw of middlewares) {
126
+ app.use(urlPath, mw);
127
+ }
128
+ }
129
+ for (const method of HTTP_METHODS) {
130
+ const handler = routeModule[method];
131
+ if (typeof handler !== "function") continue;
132
+ app.on(method, urlPath, handler);
133
+ entries.push({ method, path: urlPath, handler, filePath });
134
+ }
135
+ }
136
+ return entries;
137
+ }
138
+ async function collectRouteFiles(dir) {
139
+ const files = [];
140
+ let dirEntries;
141
+ try {
142
+ dirEntries = await (0, import_promises.readdir)(dir);
143
+ } catch {
144
+ return files;
145
+ }
146
+ for (const name of dirEntries) {
147
+ const fullPath = (0, import_node_path.join)(dir, name);
148
+ const info = await (0, import_promises.stat)(fullPath);
149
+ if (info.isDirectory()) {
150
+ const nested = await collectRouteFiles(fullPath);
151
+ files.push(...nested);
152
+ } else if (info.isFile() && ROUTE_EXTENSIONS.has((0, import_node_path.extname)(name))) {
153
+ if (name.startsWith("_") || name.startsWith(".")) continue;
154
+ files.push(fullPath);
155
+ }
156
+ }
157
+ return files;
158
+ }
159
+ function filePathToUrlPath(filePath, routesDir) {
160
+ let rel = (0, import_node_path.relative)(routesDir, filePath);
161
+ const ext = (0, import_node_path.extname)(rel);
162
+ rel = rel.slice(0, -ext.length);
163
+ rel = rel.replace(/\\/g, "/");
164
+ if (rel === "index") return "/";
165
+ if (rel.endsWith("/index")) {
166
+ rel = rel.slice(0, -"/index".length);
167
+ }
168
+ rel = rel.replace(/\[([^\]\.]+)\]/g, ":$1");
169
+ rel = rel.replace(/\[\.\.\.([^\]]+)\]/g, "*");
170
+ return "/" + rel;
171
+ }
172
+ async function importRouteModule(filePath) {
173
+ try {
174
+ const mod = await import((0, import_node_url.pathToFileURL)(filePath).href);
175
+ return mod;
176
+ } catch (err) {
177
+ console.error(`[voltx] Failed to import route: ${filePath}`, err);
178
+ return null;
179
+ }
180
+ }
181
+
182
+ // src/static.ts
183
+ var import_serve_static = require("@hono/node-server/serve-static");
184
+ function registerStaticFiles(app, staticDir = "public") {
185
+ app.use("/*", (0, import_serve_static.serveStatic)({ root: `./${staticDir}` }));
186
+ }
187
+
188
+ // src/server.ts
189
+ function createServer(config = {}) {
190
+ const {
191
+ port = Number(process.env.PORT) || 3e3,
192
+ hostname = "0.0.0.0",
193
+ routesDir = "src/routes",
194
+ staticDir = "public",
195
+ cors = true,
196
+ logger: enableLogger = process.env.NODE_ENV !== "production",
197
+ onError,
198
+ onStart
199
+ } = config;
200
+ const app = new import_hono.Hono();
201
+ const registeredRoutes = [];
202
+ let httpServer = null;
203
+ if (enableLogger) {
204
+ app.use("*", createLoggerMiddleware());
205
+ }
206
+ const corsMiddleware = createCorsMiddleware(cors);
207
+ if (corsMiddleware) {
208
+ app.use("*", corsMiddleware);
209
+ }
210
+ app.onError(createErrorHandler(onError));
211
+ app.notFound((c) => {
212
+ return c.json(
213
+ { error: { message: "Not Found", status: 404 } },
214
+ 404
215
+ );
216
+ });
217
+ const server = {
218
+ app,
219
+ async start() {
220
+ const absRoutesDir = (0, import_node_path2.resolve)(process.cwd(), routesDir);
221
+ const routes = await scanAndRegisterRoutes(app, absRoutesDir);
222
+ registeredRoutes.push(...routes);
223
+ registerStaticFiles(app, staticDir);
224
+ const info = {
225
+ port,
226
+ hostname,
227
+ url: `http://${hostname === "0.0.0.0" ? "localhost" : hostname}:${port}`
228
+ };
229
+ httpServer = (0, import_node_server.serve)({
230
+ fetch: app.fetch,
231
+ port,
232
+ hostname
233
+ });
234
+ console.log(`
235
+ \u26A1 VoltX server running at ${info.url}
236
+ `);
237
+ if (registeredRoutes.length > 0) {
238
+ console.log(` Routes (${registeredRoutes.length}):`);
239
+ for (const route of registeredRoutes) {
240
+ console.log(` ${route.method.padEnd(7)} ${route.path}`);
241
+ }
242
+ console.log();
243
+ }
244
+ onStart?.(info);
245
+ return info;
246
+ },
247
+ async stop() {
248
+ if (httpServer) {
249
+ await new Promise((resolve2, reject) => {
250
+ httpServer.close((err) => {
251
+ if (err) reject(err);
252
+ else resolve2();
253
+ });
254
+ });
255
+ httpServer = null;
256
+ console.log("\n \u26A1 VoltX server stopped.\n");
257
+ }
258
+ },
259
+ async registerRoutes(dir) {
260
+ const absDir = (0, import_node_path2.resolve)(process.cwd(), dir);
261
+ const routes = await scanAndRegisterRoutes(app, absDir);
262
+ registeredRoutes.push(...routes);
263
+ return routes;
264
+ },
265
+ routes() {
266
+ return [...registeredRoutes];
267
+ }
268
+ };
269
+ return server;
270
+ }
271
+
272
+ // src/index.ts
273
+ var import_hono2 = require("hono");
274
+ var VERSION = "0.3.0";
275
+ // Annotate the CommonJS export names for ESM import in node:
276
+ 0 && (module.exports = {
277
+ Hono,
278
+ VERSION,
279
+ createCorsMiddleware,
280
+ createErrorHandler,
281
+ createLoggerMiddleware,
282
+ createServer,
283
+ filePathToUrlPath,
284
+ registerStaticFiles,
285
+ scanAndRegisterRoutes
286
+ });
@@ -0,0 +1,150 @@
1
+ import * as hono from 'hono';
2
+ import { Context, Hono } from 'hono';
3
+ export { Context, Hono } from 'hono';
4
+
5
+ interface ServerConfig {
6
+ /** Port to listen on (default: 3000) */
7
+ port?: number;
8
+ /** Hostname to bind to (default: "0.0.0.0") */
9
+ hostname?: string;
10
+ /** Directory to scan for file-based routes (default: "src/routes") */
11
+ routesDir?: string;
12
+ /** Directory for static files (default: "public") */
13
+ staticDir?: string;
14
+ /** Enable CORS (default: true in dev) */
15
+ cors?: boolean | CorsConfig;
16
+ /** Enable request logging (default: true in dev) */
17
+ logger?: boolean;
18
+ /** Custom error handler */
19
+ onError?: (err: Error, c: Context) => Response | Promise<Response>;
20
+ /** Called when server starts */
21
+ onStart?: (info: ServerInfo) => void;
22
+ }
23
+ interface CorsConfig {
24
+ /** Allowed origins (default: "*") */
25
+ origin?: string | string[];
26
+ /** Allowed HTTP methods */
27
+ allowMethods?: string[];
28
+ /** Allowed headers */
29
+ allowHeaders?: string[];
30
+ /** Exposed headers */
31
+ exposeHeaders?: string[];
32
+ /** Max age for preflight cache (seconds) */
33
+ maxAge?: number;
34
+ /** Allow credentials */
35
+ credentials?: boolean;
36
+ }
37
+ interface ServerInfo {
38
+ port: number;
39
+ hostname: string;
40
+ url: string;
41
+ }
42
+ /** HTTP methods that route files can export */
43
+ type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "HEAD" | "OPTIONS";
44
+ /** A route handler function — receives Hono Context, returns Response */
45
+ type RouteHandler = (c: Context) => Response | Promise<Response>;
46
+ /** What a route file can export */
47
+ interface RouteModule {
48
+ GET?: RouteHandler;
49
+ POST?: RouteHandler;
50
+ PUT?: RouteHandler;
51
+ DELETE?: RouteHandler;
52
+ PATCH?: RouteHandler;
53
+ HEAD?: RouteHandler;
54
+ OPTIONS?: RouteHandler;
55
+ /** Middleware that runs before all handlers in this route */
56
+ middleware?: MiddlewareHandler | MiddlewareHandler[];
57
+ }
58
+ /** Middleware handler (Hono-style) */
59
+ type MiddlewareHandler = (c: Context, next: () => Promise<void>) => Promise<void | Response>;
60
+ /** A registered route entry */
61
+ interface RouteEntry {
62
+ /** HTTP method */
63
+ method: HttpMethod;
64
+ /** URL path pattern (e.g., "/api/users/:id") */
65
+ path: string;
66
+ /** Handler function */
67
+ handler: RouteHandler;
68
+ /** Source file path (for debugging) */
69
+ filePath: string;
70
+ }
71
+ interface VoltxServer {
72
+ /** The underlying Hono app instance */
73
+ app: Hono;
74
+ /** Start listening for requests */
75
+ start(): Promise<ServerInfo>;
76
+ /** Stop the server */
77
+ stop(): Promise<void>;
78
+ /** Register routes from a directory (file-based routing) */
79
+ registerRoutes(routesDir: string): Promise<RouteEntry[]>;
80
+ /** Get all registered routes */
81
+ routes(): RouteEntry[];
82
+ }
83
+
84
+ /**
85
+ * Create a VoltX server instance.
86
+ *
87
+ * @example
88
+ * ```ts
89
+ * import { createServer } from "@voltx/server";
90
+ *
91
+ * const server = createServer({
92
+ * port: 3000,
93
+ * routesDir: "src/routes",
94
+ * cors: true,
95
+ * logger: true,
96
+ * });
97
+ *
98
+ * await server.start();
99
+ * ```
100
+ */
101
+ declare function createServer(config?: ServerConfig): VoltxServer;
102
+
103
+ /**
104
+ * Scan a directory for route files and register them on a Hono app.
105
+ *
106
+ * @param app - Hono app instance
107
+ * @param routesDir - Absolute path to the routes directory
108
+ * @returns Array of registered route entries
109
+ */
110
+ declare function scanAndRegisterRoutes(app: Hono, routesDir: string): Promise<RouteEntry[]>;
111
+ /**
112
+ * Convert a file path to a URL path.
113
+ *
114
+ * Examples:
115
+ * routes/index.ts → /
116
+ * routes/api/chat.ts → /api/chat
117
+ * routes/api/users/[id].ts → /api/users/:id
118
+ * routes/api/[...slug].ts → /api/*
119
+ */
120
+ declare function filePathToUrlPath(filePath: string, routesDir: string): string;
121
+
122
+ /**
123
+ * Register static file serving on a Hono app.
124
+ *
125
+ * @param app - Hono app instance
126
+ * @param staticDir - Relative path to the static files directory (default: "public")
127
+ */
128
+ declare function registerStaticFiles(app: Hono, staticDir?: string): void;
129
+
130
+ /**
131
+ * Create CORS middleware from VoltX config.
132
+ * Wraps Hono's built-in CORS middleware with sensible defaults.
133
+ */
134
+ declare function createCorsMiddleware(config?: boolean | CorsConfig): hono.MiddlewareHandler | null;
135
+
136
+ /**
137
+ * Create request logger middleware.
138
+ * Wraps Hono's built-in logger with VoltX prefix.
139
+ */
140
+ declare function createLoggerMiddleware(): hono.MiddlewareHandler;
141
+
142
+ /**
143
+ * Default error handler for VoltX server.
144
+ * Returns JSON error responses with appropriate status codes.
145
+ */
146
+ declare function createErrorHandler(customHandler?: (err: Error, c: Context) => Response | Promise<Response>): (err: Error, c: Context) => Promise<Response>;
147
+
148
+ declare const VERSION = "0.3.0";
149
+
150
+ export { type CorsConfig, type HttpMethod, type MiddlewareHandler, type RouteEntry, type RouteHandler, type RouteModule, type ServerConfig, type ServerInfo, VERSION, type VoltxServer, createCorsMiddleware, createErrorHandler, createLoggerMiddleware, createServer, filePathToUrlPath, registerStaticFiles, scanAndRegisterRoutes };
@@ -0,0 +1,150 @@
1
+ import * as hono from 'hono';
2
+ import { Context, Hono } from 'hono';
3
+ export { Context, Hono } from 'hono';
4
+
5
+ interface ServerConfig {
6
+ /** Port to listen on (default: 3000) */
7
+ port?: number;
8
+ /** Hostname to bind to (default: "0.0.0.0") */
9
+ hostname?: string;
10
+ /** Directory to scan for file-based routes (default: "src/routes") */
11
+ routesDir?: string;
12
+ /** Directory for static files (default: "public") */
13
+ staticDir?: string;
14
+ /** Enable CORS (default: true in dev) */
15
+ cors?: boolean | CorsConfig;
16
+ /** Enable request logging (default: true in dev) */
17
+ logger?: boolean;
18
+ /** Custom error handler */
19
+ onError?: (err: Error, c: Context) => Response | Promise<Response>;
20
+ /** Called when server starts */
21
+ onStart?: (info: ServerInfo) => void;
22
+ }
23
+ interface CorsConfig {
24
+ /** Allowed origins (default: "*") */
25
+ origin?: string | string[];
26
+ /** Allowed HTTP methods */
27
+ allowMethods?: string[];
28
+ /** Allowed headers */
29
+ allowHeaders?: string[];
30
+ /** Exposed headers */
31
+ exposeHeaders?: string[];
32
+ /** Max age for preflight cache (seconds) */
33
+ maxAge?: number;
34
+ /** Allow credentials */
35
+ credentials?: boolean;
36
+ }
37
+ interface ServerInfo {
38
+ port: number;
39
+ hostname: string;
40
+ url: string;
41
+ }
42
+ /** HTTP methods that route files can export */
43
+ type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "HEAD" | "OPTIONS";
44
+ /** A route handler function — receives Hono Context, returns Response */
45
+ type RouteHandler = (c: Context) => Response | Promise<Response>;
46
+ /** What a route file can export */
47
+ interface RouteModule {
48
+ GET?: RouteHandler;
49
+ POST?: RouteHandler;
50
+ PUT?: RouteHandler;
51
+ DELETE?: RouteHandler;
52
+ PATCH?: RouteHandler;
53
+ HEAD?: RouteHandler;
54
+ OPTIONS?: RouteHandler;
55
+ /** Middleware that runs before all handlers in this route */
56
+ middleware?: MiddlewareHandler | MiddlewareHandler[];
57
+ }
58
+ /** Middleware handler (Hono-style) */
59
+ type MiddlewareHandler = (c: Context, next: () => Promise<void>) => Promise<void | Response>;
60
+ /** A registered route entry */
61
+ interface RouteEntry {
62
+ /** HTTP method */
63
+ method: HttpMethod;
64
+ /** URL path pattern (e.g., "/api/users/:id") */
65
+ path: string;
66
+ /** Handler function */
67
+ handler: RouteHandler;
68
+ /** Source file path (for debugging) */
69
+ filePath: string;
70
+ }
71
+ interface VoltxServer {
72
+ /** The underlying Hono app instance */
73
+ app: Hono;
74
+ /** Start listening for requests */
75
+ start(): Promise<ServerInfo>;
76
+ /** Stop the server */
77
+ stop(): Promise<void>;
78
+ /** Register routes from a directory (file-based routing) */
79
+ registerRoutes(routesDir: string): Promise<RouteEntry[]>;
80
+ /** Get all registered routes */
81
+ routes(): RouteEntry[];
82
+ }
83
+
84
+ /**
85
+ * Create a VoltX server instance.
86
+ *
87
+ * @example
88
+ * ```ts
89
+ * import { createServer } from "@voltx/server";
90
+ *
91
+ * const server = createServer({
92
+ * port: 3000,
93
+ * routesDir: "src/routes",
94
+ * cors: true,
95
+ * logger: true,
96
+ * });
97
+ *
98
+ * await server.start();
99
+ * ```
100
+ */
101
+ declare function createServer(config?: ServerConfig): VoltxServer;
102
+
103
+ /**
104
+ * Scan a directory for route files and register them on a Hono app.
105
+ *
106
+ * @param app - Hono app instance
107
+ * @param routesDir - Absolute path to the routes directory
108
+ * @returns Array of registered route entries
109
+ */
110
+ declare function scanAndRegisterRoutes(app: Hono, routesDir: string): Promise<RouteEntry[]>;
111
+ /**
112
+ * Convert a file path to a URL path.
113
+ *
114
+ * Examples:
115
+ * routes/index.ts → /
116
+ * routes/api/chat.ts → /api/chat
117
+ * routes/api/users/[id].ts → /api/users/:id
118
+ * routes/api/[...slug].ts → /api/*
119
+ */
120
+ declare function filePathToUrlPath(filePath: string, routesDir: string): string;
121
+
122
+ /**
123
+ * Register static file serving on a Hono app.
124
+ *
125
+ * @param app - Hono app instance
126
+ * @param staticDir - Relative path to the static files directory (default: "public")
127
+ */
128
+ declare function registerStaticFiles(app: Hono, staticDir?: string): void;
129
+
130
+ /**
131
+ * Create CORS middleware from VoltX config.
132
+ * Wraps Hono's built-in CORS middleware with sensible defaults.
133
+ */
134
+ declare function createCorsMiddleware(config?: boolean | CorsConfig): hono.MiddlewareHandler | null;
135
+
136
+ /**
137
+ * Create request logger middleware.
138
+ * Wraps Hono's built-in logger with VoltX prefix.
139
+ */
140
+ declare function createLoggerMiddleware(): hono.MiddlewareHandler;
141
+
142
+ /**
143
+ * Default error handler for VoltX server.
144
+ * Returns JSON error responses with appropriate status codes.
145
+ */
146
+ declare function createErrorHandler(customHandler?: (err: Error, c: Context) => Response | Promise<Response>): (err: Error, c: Context) => Promise<Response>;
147
+
148
+ declare const VERSION = "0.3.0";
149
+
150
+ export { type CorsConfig, type HttpMethod, type MiddlewareHandler, type RouteEntry, type RouteHandler, type RouteModule, type ServerConfig, type ServerInfo, VERSION, type VoltxServer, createCorsMiddleware, createErrorHandler, createLoggerMiddleware, createServer, filePathToUrlPath, registerStaticFiles, scanAndRegisterRoutes };
package/dist/index.js ADDED
@@ -0,0 +1,251 @@
1
+ // src/server.ts
2
+ import { Hono } from "hono";
3
+ import { serve } from "@hono/node-server";
4
+ import { resolve } from "path";
5
+
6
+ // src/middleware/cors.ts
7
+ import { cors as honoCors } from "hono/cors";
8
+ function createCorsMiddleware(config) {
9
+ if (config === false) return null;
10
+ const opts = typeof config === "object" ? config : {};
11
+ return honoCors({
12
+ origin: opts.origin ?? "*",
13
+ allowMethods: opts.allowMethods ?? ["GET", "HEAD", "PUT", "POST", "DELETE", "PATCH"],
14
+ allowHeaders: opts.allowHeaders ?? ["Content-Type", "Authorization"],
15
+ exposeHeaders: opts.exposeHeaders ?? [],
16
+ maxAge: opts.maxAge ?? 600,
17
+ credentials: opts.credentials ?? false
18
+ });
19
+ }
20
+
21
+ // src/middleware/logger.ts
22
+ import { logger as honoLogger } from "hono/logger";
23
+ function createLoggerMiddleware() {
24
+ return honoLogger((message, ...rest) => {
25
+ console.log(`[voltx] ${message}`, ...rest);
26
+ });
27
+ }
28
+
29
+ // src/middleware/error-handler.ts
30
+ function createErrorHandler(customHandler) {
31
+ return async (err, c) => {
32
+ if (customHandler) {
33
+ try {
34
+ return await customHandler(err, c);
35
+ } catch {
36
+ }
37
+ }
38
+ const status = extractStatus(err);
39
+ const isDev = process.env.NODE_ENV !== "production";
40
+ console.error(`[voltx] Error ${status}:`, err.message);
41
+ if (isDev && err.stack) {
42
+ console.error(err.stack);
43
+ }
44
+ return c.json(
45
+ {
46
+ error: {
47
+ message: isDev ? err.message : "Internal Server Error",
48
+ status,
49
+ ...isDev && err.stack ? { stack: err.stack } : {}
50
+ }
51
+ },
52
+ status
53
+ );
54
+ };
55
+ }
56
+ function extractStatus(err) {
57
+ if (err && typeof err === "object") {
58
+ if ("status" in err && typeof err.status === "number") {
59
+ return err.status;
60
+ }
61
+ if ("statusCode" in err && typeof err.statusCode === "number") {
62
+ return err.statusCode;
63
+ }
64
+ }
65
+ return 500;
66
+ }
67
+
68
+ // src/router.ts
69
+ import { readdir, stat } from "fs/promises";
70
+ import { join, relative, extname } from "path";
71
+ import { pathToFileURL } from "url";
72
+ var HTTP_METHODS = ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"];
73
+ var ROUTE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".js", ".mjs", ".mts"]);
74
+ async function scanAndRegisterRoutes(app, routesDir) {
75
+ const entries = [];
76
+ const files = await collectRouteFiles(routesDir);
77
+ files.sort((a, b) => {
78
+ const pathA = filePathToUrlPath(a, routesDir);
79
+ const pathB = filePathToUrlPath(b, routesDir);
80
+ const scoreA = pathA.includes("*") ? 2 : pathA.includes(":") ? 1 : 0;
81
+ const scoreB = pathB.includes("*") ? 2 : pathB.includes(":") ? 1 : 0;
82
+ if (scoreA !== scoreB) return scoreA - scoreB;
83
+ return pathA.localeCompare(pathB);
84
+ });
85
+ for (const filePath of files) {
86
+ const urlPath = filePathToUrlPath(filePath, routesDir);
87
+ const routeModule = await importRouteModule(filePath);
88
+ if (!routeModule) continue;
89
+ if (routeModule.middleware) {
90
+ const middlewares = Array.isArray(routeModule.middleware) ? routeModule.middleware : [routeModule.middleware];
91
+ for (const mw of middlewares) {
92
+ app.use(urlPath, mw);
93
+ }
94
+ }
95
+ for (const method of HTTP_METHODS) {
96
+ const handler = routeModule[method];
97
+ if (typeof handler !== "function") continue;
98
+ app.on(method, urlPath, handler);
99
+ entries.push({ method, path: urlPath, handler, filePath });
100
+ }
101
+ }
102
+ return entries;
103
+ }
104
+ async function collectRouteFiles(dir) {
105
+ const files = [];
106
+ let dirEntries;
107
+ try {
108
+ dirEntries = await readdir(dir);
109
+ } catch {
110
+ return files;
111
+ }
112
+ for (const name of dirEntries) {
113
+ const fullPath = join(dir, name);
114
+ const info = await stat(fullPath);
115
+ if (info.isDirectory()) {
116
+ const nested = await collectRouteFiles(fullPath);
117
+ files.push(...nested);
118
+ } else if (info.isFile() && ROUTE_EXTENSIONS.has(extname(name))) {
119
+ if (name.startsWith("_") || name.startsWith(".")) continue;
120
+ files.push(fullPath);
121
+ }
122
+ }
123
+ return files;
124
+ }
125
+ function filePathToUrlPath(filePath, routesDir) {
126
+ let rel = relative(routesDir, filePath);
127
+ const ext = extname(rel);
128
+ rel = rel.slice(0, -ext.length);
129
+ rel = rel.replace(/\\/g, "/");
130
+ if (rel === "index") return "/";
131
+ if (rel.endsWith("/index")) {
132
+ rel = rel.slice(0, -"/index".length);
133
+ }
134
+ rel = rel.replace(/\[([^\]\.]+)\]/g, ":$1");
135
+ rel = rel.replace(/\[\.\.\.([^\]]+)\]/g, "*");
136
+ return "/" + rel;
137
+ }
138
+ async function importRouteModule(filePath) {
139
+ try {
140
+ const mod = await import(pathToFileURL(filePath).href);
141
+ return mod;
142
+ } catch (err) {
143
+ console.error(`[voltx] Failed to import route: ${filePath}`, err);
144
+ return null;
145
+ }
146
+ }
147
+
148
+ // src/static.ts
149
+ import { serveStatic } from "@hono/node-server/serve-static";
150
+ function registerStaticFiles(app, staticDir = "public") {
151
+ app.use("/*", serveStatic({ root: `./${staticDir}` }));
152
+ }
153
+
154
+ // src/server.ts
155
+ function createServer(config = {}) {
156
+ const {
157
+ port = Number(process.env.PORT) || 3e3,
158
+ hostname = "0.0.0.0",
159
+ routesDir = "src/routes",
160
+ staticDir = "public",
161
+ cors = true,
162
+ logger: enableLogger = process.env.NODE_ENV !== "production",
163
+ onError,
164
+ onStart
165
+ } = config;
166
+ const app = new Hono();
167
+ const registeredRoutes = [];
168
+ let httpServer = null;
169
+ if (enableLogger) {
170
+ app.use("*", createLoggerMiddleware());
171
+ }
172
+ const corsMiddleware = createCorsMiddleware(cors);
173
+ if (corsMiddleware) {
174
+ app.use("*", corsMiddleware);
175
+ }
176
+ app.onError(createErrorHandler(onError));
177
+ app.notFound((c) => {
178
+ return c.json(
179
+ { error: { message: "Not Found", status: 404 } },
180
+ 404
181
+ );
182
+ });
183
+ const server = {
184
+ app,
185
+ async start() {
186
+ const absRoutesDir = resolve(process.cwd(), routesDir);
187
+ const routes = await scanAndRegisterRoutes(app, absRoutesDir);
188
+ registeredRoutes.push(...routes);
189
+ registerStaticFiles(app, staticDir);
190
+ const info = {
191
+ port,
192
+ hostname,
193
+ url: `http://${hostname === "0.0.0.0" ? "localhost" : hostname}:${port}`
194
+ };
195
+ httpServer = serve({
196
+ fetch: app.fetch,
197
+ port,
198
+ hostname
199
+ });
200
+ console.log(`
201
+ \u26A1 VoltX server running at ${info.url}
202
+ `);
203
+ if (registeredRoutes.length > 0) {
204
+ console.log(` Routes (${registeredRoutes.length}):`);
205
+ for (const route of registeredRoutes) {
206
+ console.log(` ${route.method.padEnd(7)} ${route.path}`);
207
+ }
208
+ console.log();
209
+ }
210
+ onStart?.(info);
211
+ return info;
212
+ },
213
+ async stop() {
214
+ if (httpServer) {
215
+ await new Promise((resolve2, reject) => {
216
+ httpServer.close((err) => {
217
+ if (err) reject(err);
218
+ else resolve2();
219
+ });
220
+ });
221
+ httpServer = null;
222
+ console.log("\n \u26A1 VoltX server stopped.\n");
223
+ }
224
+ },
225
+ async registerRoutes(dir) {
226
+ const absDir = resolve(process.cwd(), dir);
227
+ const routes = await scanAndRegisterRoutes(app, absDir);
228
+ registeredRoutes.push(...routes);
229
+ return routes;
230
+ },
231
+ routes() {
232
+ return [...registeredRoutes];
233
+ }
234
+ };
235
+ return server;
236
+ }
237
+
238
+ // src/index.ts
239
+ import { Hono as Hono2 } from "hono";
240
+ var VERSION = "0.3.0";
241
+ export {
242
+ Hono2 as Hono,
243
+ VERSION,
244
+ createCorsMiddleware,
245
+ createErrorHandler,
246
+ createLoggerMiddleware,
247
+ createServer,
248
+ filePathToUrlPath,
249
+ registerStaticFiles,
250
+ scanAndRegisterRoutes
251
+ };
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@voltx/server",
3
+ "version": "0.3.0",
4
+ "description": "VoltX Server — Hono-based HTTP server with file-based routing, SSE streaming, and static file serving",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": ["dist"],
17
+ "scripts": {
18
+ "build": "tsup src/index.ts --format cjs,esm --dts --clean",
19
+ "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
20
+ "clean": "rm -rf dist"
21
+ },
22
+ "dependencies": {
23
+ "hono": "^4.7.0",
24
+ "@hono/node-server": "^1.14.0"
25
+ },
26
+ "devDependencies": {
27
+ "tsup": "^8.0.0",
28
+ "typescript": "^5.7.0"
29
+ },
30
+ "keywords": ["voltx", "server", "hono", "http", "routing", "file-based", "sse", "streaming", "middleware"],
31
+ "license": "MIT",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "https://github.com/codewithshail/voltx.git",
35
+ "directory": "packages/server"
36
+ },
37
+ "homepage": "https://voltx.co.in",
38
+ "author": "Promptly AI Team"
39
+ }