@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 +92 -0
- package/dist/index.cjs +286 -0
- package/dist/index.d.cts +150 -0
- package/dist/index.d.ts +150 -0
- package/dist/index.js +251 -0
- package/package.json +39 -0
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
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -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.d.ts
ADDED
|
@@ -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
|
+
}
|