qhttpx 1.8.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/.eslintrc.json +22 -0
- package/.github/workflows/ci.yml +32 -0
- package/.github/workflows/npm-publish.yml +37 -0
- package/.github/workflows/release.yml +21 -0
- package/.prettierrc +7 -0
- package/CHANGELOG.md +145 -0
- package/LICENSE +21 -0
- package/README.md +343 -0
- package/dist/package.json +61 -0
- package/dist/src/benchmarks/compare-frameworks.js +119 -0
- package/dist/src/benchmarks/quantam-users.js +56 -0
- package/dist/src/benchmarks/simple-json.js +58 -0
- package/dist/src/benchmarks/ultra-mode.js +122 -0
- package/dist/src/cli/index.js +200 -0
- package/dist/src/client/index.js +72 -0
- package/dist/src/core/batch.js +97 -0
- package/dist/src/core/body-parser.js +121 -0
- package/dist/src/core/buffer-pool.js +70 -0
- package/dist/src/core/config.js +50 -0
- package/dist/src/core/fusion.js +183 -0
- package/dist/src/core/logger.js +49 -0
- package/dist/src/core/metrics.js +111 -0
- package/dist/src/core/resources.js +25 -0
- package/dist/src/core/scheduler.js +85 -0
- package/dist/src/core/scope.js +68 -0
- package/dist/src/core/serializer.js +44 -0
- package/dist/src/core/server.js +905 -0
- package/dist/src/core/stream.js +71 -0
- package/dist/src/core/tasks.js +87 -0
- package/dist/src/core/types.js +19 -0
- package/dist/src/core/websocket.js +86 -0
- package/dist/src/core/worker-queue.js +73 -0
- package/dist/src/database/adapters/memory.js +90 -0
- package/dist/src/database/adapters/mongo.js +141 -0
- package/dist/src/database/adapters/postgres.js +111 -0
- package/dist/src/database/adapters/sqlite.js +42 -0
- package/dist/src/database/coalescer.js +134 -0
- package/dist/src/database/manager.js +87 -0
- package/dist/src/database/types.js +2 -0
- package/dist/src/index.js +61 -0
- package/dist/src/middleware/compression.js +133 -0
- package/dist/src/middleware/cors.js +66 -0
- package/dist/src/middleware/presets.js +33 -0
- package/dist/src/middleware/rate-limit.js +77 -0
- package/dist/src/middleware/security.js +69 -0
- package/dist/src/middleware/static.js +191 -0
- package/dist/src/openapi/generator.js +149 -0
- package/dist/src/router/radix-router.js +89 -0
- package/dist/src/router/radix-tree.js +81 -0
- package/dist/src/router/router.js +146 -0
- package/dist/src/testing/index.js +84 -0
- package/dist/src/utils/cookies.js +59 -0
- package/dist/src/utils/logger.js +45 -0
- package/dist/src/utils/signals.js +31 -0
- package/dist/src/utils/sse.js +32 -0
- package/dist/src/validation/index.js +19 -0
- package/dist/src/validation/simple.js +102 -0
- package/dist/src/validation/types.js +12 -0
- package/dist/src/validation/zod.js +18 -0
- package/dist/src/views/index.js +17 -0
- package/dist/src/views/types.js +2 -0
- package/dist/tests/adapters.test.js +106 -0
- package/dist/tests/batch.test.js +117 -0
- package/dist/tests/body-parser.test.js +52 -0
- package/dist/tests/compression-sse.test.js +87 -0
- package/dist/tests/cookies.test.js +63 -0
- package/dist/tests/cors.test.js +55 -0
- package/dist/tests/database.test.js +80 -0
- package/dist/tests/dx.test.js +64 -0
- package/dist/tests/ecosystem.test.js +133 -0
- package/dist/tests/features.test.js +47 -0
- package/dist/tests/fusion.test.js +92 -0
- package/dist/tests/http-basic.test.js +124 -0
- package/dist/tests/logger.test.js +33 -0
- package/dist/tests/middleware.test.js +109 -0
- package/dist/tests/observability.test.js +59 -0
- package/dist/tests/openapi.test.js +64 -0
- package/dist/tests/plugin.test.js +65 -0
- package/dist/tests/plugins.test.js +71 -0
- package/dist/tests/rate-limit.test.js +77 -0
- package/dist/tests/resources.test.js +44 -0
- package/dist/tests/scheduler.test.js +46 -0
- package/dist/tests/schema-routes.test.js +77 -0
- package/dist/tests/security.test.js +83 -0
- package/dist/tests/server-db.test.js +72 -0
- package/dist/tests/smoke.test.js +10 -0
- package/dist/tests/sqlite-fusion.test.js +92 -0
- package/dist/tests/static.test.js +102 -0
- package/dist/tests/stream.test.js +44 -0
- package/dist/tests/task-metrics.test.js +53 -0
- package/dist/tests/tasks.test.js +62 -0
- package/dist/tests/testing.test.js +47 -0
- package/dist/tests/validation.test.js +107 -0
- package/dist/tests/websocket.test.js +146 -0
- package/dist/vitest.config.js +9 -0
- package/docs/AEGIS.md +76 -0
- package/docs/BENCHMARKS.md +36 -0
- package/docs/CAPABILITIES.md +70 -0
- package/docs/CLI.md +43 -0
- package/docs/DATABASE.md +142 -0
- package/docs/ECOSYSTEM.md +146 -0
- package/docs/NEXT_STEPS.md +99 -0
- package/docs/OPENAPI.md +99 -0
- package/docs/PLUGINS.md +59 -0
- package/docs/REAL_WORLD_EXAMPLES.md +109 -0
- package/docs/ROADMAP.md +366 -0
- package/docs/VALIDATION.md +136 -0
- package/eslint.config.cjs +26 -0
- package/examples/api-server.ts +254 -0
- package/package.json +61 -0
- package/src/benchmarks/compare-frameworks.ts +149 -0
- package/src/benchmarks/quantam-users.ts +70 -0
- package/src/benchmarks/simple-json.ts +71 -0
- package/src/benchmarks/ultra-mode.ts +159 -0
- package/src/cli/index.ts +214 -0
- package/src/client/index.ts +93 -0
- package/src/core/batch.ts +110 -0
- package/src/core/body-parser.ts +151 -0
- package/src/core/buffer-pool.ts +96 -0
- package/src/core/config.ts +60 -0
- package/src/core/fusion.ts +210 -0
- package/src/core/logger.ts +70 -0
- package/src/core/metrics.ts +166 -0
- package/src/core/resources.ts +38 -0
- package/src/core/scheduler.ts +126 -0
- package/src/core/scope.ts +87 -0
- package/src/core/serializer.ts +41 -0
- package/src/core/server.ts +1113 -0
- package/src/core/stream.ts +111 -0
- package/src/core/tasks.ts +138 -0
- package/src/core/types.ts +178 -0
- package/src/core/websocket.ts +112 -0
- package/src/core/worker-queue.ts +90 -0
- package/src/database/adapters/memory.ts +99 -0
- package/src/database/adapters/mongo.ts +116 -0
- package/src/database/adapters/postgres.ts +86 -0
- package/src/database/adapters/sqlite.ts +44 -0
- package/src/database/coalescer.ts +153 -0
- package/src/database/manager.ts +97 -0
- package/src/database/types.ts +24 -0
- package/src/index.ts +42 -0
- package/src/middleware/compression.ts +147 -0
- package/src/middleware/cors.ts +98 -0
- package/src/middleware/presets.ts +50 -0
- package/src/middleware/rate-limit.ts +106 -0
- package/src/middleware/security.ts +109 -0
- package/src/middleware/static.ts +216 -0
- package/src/openapi/generator.ts +167 -0
- package/src/router/radix-router.ts +119 -0
- package/src/router/radix-tree.ts +106 -0
- package/src/router/router.ts +190 -0
- package/src/testing/index.ts +104 -0
- package/src/utils/cookies.ts +67 -0
- package/src/utils/logger.ts +59 -0
- package/src/utils/signals.ts +45 -0
- package/src/utils/sse.ts +41 -0
- package/src/validation/index.ts +3 -0
- package/src/validation/simple.ts +93 -0
- package/src/validation/types.ts +38 -0
- package/src/validation/zod.ts +14 -0
- package/src/views/index.ts +1 -0
- package/src/views/types.ts +4 -0
- package/tests/adapters.test.ts +120 -0
- package/tests/batch.test.ts +139 -0
- package/tests/body-parser.test.ts +83 -0
- package/tests/compression-sse.test.ts +98 -0
- package/tests/cookies.test.ts +74 -0
- package/tests/cors.test.ts +79 -0
- package/tests/database.test.ts +90 -0
- package/tests/dx.test.ts +78 -0
- package/tests/ecosystem.test.ts +156 -0
- package/tests/features.test.ts +51 -0
- package/tests/fusion.test.ts +121 -0
- package/tests/http-basic.test.ts +161 -0
- package/tests/logger.test.ts +48 -0
- package/tests/middleware.test.ts +137 -0
- package/tests/observability.test.ts +91 -0
- package/tests/openapi.test.ts +74 -0
- package/tests/plugin.test.ts +85 -0
- package/tests/plugins.test.ts +93 -0
- package/tests/rate-limit.test.ts +97 -0
- package/tests/resources.test.ts +64 -0
- package/tests/scheduler.test.ts +71 -0
- package/tests/schema-routes.test.ts +89 -0
- package/tests/security.test.ts +128 -0
- package/tests/server-db.test.ts +72 -0
- package/tests/smoke.test.ts +9 -0
- package/tests/sqlite-fusion.test.ts +106 -0
- package/tests/static.test.ts +111 -0
- package/tests/stream.test.ts +58 -0
- package/tests/task-metrics.test.ts +78 -0
- package/tests/tasks.test.ts +90 -0
- package/tests/testing.test.ts +53 -0
- package/tests/validation.test.ts +126 -0
- package/tests/websocket.test.ts +132 -0
- package/tsconfig.json +16 -0
- package/vitest.config.ts +9 -0
package/docs/DATABASE.md
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# Database Engine & Query Fusion
|
|
2
|
+
|
|
3
|
+
QHTTPX includes a powerful, built-in database engine designed for high performance and flexibility. It features a multi-database adapter system, connection pooling, and a unique **Query Fusion** engine that automatically optimizes high-concurrency workloads.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Multi-Database Support**: Connect to PostgreSQL, MongoDB, SQLite, and more using a unified API.
|
|
8
|
+
- **Dynamic Adapters**: Drivers (`pg`, `mongodb`) are optional. The framework only loads what you use.
|
|
9
|
+
- **Query Fusion**: Automatically merges identical queries (e.g., `SELECT * FROM users WHERE id = ?`) into single batched queries (e.g., `WHERE id IN (...)`) to reduce network roundtrips.
|
|
10
|
+
- **Batch Execution**: Send multiple operations in a single HTTP request.
|
|
11
|
+
- **Context Integration**: Access database connections directly from your route handlers via `ctx.db`.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
The core framework is lightweight. Install the driver for the database you intend to use:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# For PostgreSQL
|
|
19
|
+
npm install pg
|
|
20
|
+
|
|
21
|
+
# For MongoDB
|
|
22
|
+
npm install mongodb
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Basic Usage
|
|
26
|
+
|
|
27
|
+
### 1. Configure the Server
|
|
28
|
+
|
|
29
|
+
Initialize the `DatabaseManager` and pass it to your QHTTPX application.
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { createHttpApp } from 'qhttpx';
|
|
33
|
+
import { DatabaseManager } from 'qhttpx/database';
|
|
34
|
+
|
|
35
|
+
const db = new DatabaseManager({
|
|
36
|
+
default: 'main',
|
|
37
|
+
connections: {
|
|
38
|
+
main: {
|
|
39
|
+
type: 'postgres',
|
|
40
|
+
host: 'localhost',
|
|
41
|
+
port: 5432,
|
|
42
|
+
username: 'user',
|
|
43
|
+
password: 'password',
|
|
44
|
+
database: 'mydb'
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const app = createHttpApp({ db });
|
|
50
|
+
|
|
51
|
+
// Connect during startup
|
|
52
|
+
await app.listen(3000);
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### 2. Accessing the Database in Routes
|
|
56
|
+
|
|
57
|
+
Use `ctx.db.get()` to retrieve the active connection.
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
app.get('/users/:id', async (ctx) => {
|
|
61
|
+
const db = ctx.db.get('main');
|
|
62
|
+
const userId = ctx.params.id;
|
|
63
|
+
|
|
64
|
+
// Standard Query
|
|
65
|
+
const users = await db.query('SELECT * FROM users WHERE id = $1', [userId]);
|
|
66
|
+
|
|
67
|
+
return ctx.json(users[0]);
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Bring Your Own Connection
|
|
72
|
+
|
|
73
|
+
If you have a pre-initialized database connection (e.g., from an external library or legacy code), you can register it manually without using the built-in configuration.
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
import { Client } from 'pg';
|
|
77
|
+
import { PostgresAdapter } from 'qhttpx/database';
|
|
78
|
+
|
|
79
|
+
// 1. Initialize your connection externally
|
|
80
|
+
const client = new Client({ connectionString: process.env.DATABASE_URL });
|
|
81
|
+
await client.connect();
|
|
82
|
+
|
|
83
|
+
// 2. Wrap it in an adapter
|
|
84
|
+
const adapter = new PostgresAdapter({});
|
|
85
|
+
// Note: You'll need to set the internal pool/client manually if bypassing connect()
|
|
86
|
+
// Or simply let QHTTPX manage the connection for you.
|
|
87
|
+
|
|
88
|
+
// 3. Register it
|
|
89
|
+
db.registerConnection('legacy_db', adapter);
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Query Fusion & Batching
|
|
93
|
+
|
|
94
|
+
The "Killer Feature" of QHTTPX is its ability to coalesce multiple individual requests into a single database query.
|
|
95
|
+
|
|
96
|
+
### How it works
|
|
97
|
+
|
|
98
|
+
1. **Client** sends a batch of operations:
|
|
99
|
+
```json
|
|
100
|
+
POST /batch
|
|
101
|
+
{
|
|
102
|
+
"ops": [
|
|
103
|
+
{ "id": "req1", "type": "getUser", "args": [1] },
|
|
104
|
+
{ "id": "req2", "type": "getUser", "args": [2] },
|
|
105
|
+
{ "id": "req3", "type": "getUser", "args": [3] }
|
|
106
|
+
]
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
2. **Server** defines a coalescing resolver:
|
|
111
|
+
```typescript
|
|
112
|
+
import { QueryCoalescer } from 'qhttpx/database';
|
|
113
|
+
|
|
114
|
+
const userCoalescer = new QueryCoalescer(async (ids) => {
|
|
115
|
+
// This runs ONCE for the entire batch
|
|
116
|
+
return await db.query('SELECT * FROM users WHERE id = ANY($1)', [ids]);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
app.op('getUser', async (id) => {
|
|
120
|
+
return await userCoalescer.execute(id);
|
|
121
|
+
});
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
3. **Result**: Instead of 3 separate DB queries, QHTTPX executes **1 query**. The results are automatically distributed back to the correct original requests.
|
|
125
|
+
|
|
126
|
+
## Supported Databases
|
|
127
|
+
|
|
128
|
+
| Database | Adapter Type | Driver Required |
|
|
129
|
+
| :--- | :--- | :--- |
|
|
130
|
+
| **PostgreSQL** | `postgres` | `npm install pg` |
|
|
131
|
+
| **MongoDB** | `mongo` | `npm install mongodb` |
|
|
132
|
+
| **Memory** | `memory` | *(Built-in)* |
|
|
133
|
+
|
|
134
|
+
## API Reference
|
|
135
|
+
|
|
136
|
+
### `DatabaseManager`
|
|
137
|
+
|
|
138
|
+
- `constructor(config: DatabaseEngineOptions)`
|
|
139
|
+
- `registerAdapter(type: string, adapter: AdapterConstructor)`: Register custom adapters.
|
|
140
|
+
- `registerConnection(name: string, adapter: DatabaseAdapter)`: Register initialized connections.
|
|
141
|
+
- `connect(name?: string)`: Connect to a specific or default DB.
|
|
142
|
+
- `get(name?: string)`: Get an active adapter instance.
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# SaaS & Ecosystem Integration Guide
|
|
2
|
+
|
|
3
|
+
QHTTPX is designed as a **Universal Runtime**. It provides a high-performance core but imposes **zero restrictions** on the libraries you choose to build your application.
|
|
4
|
+
|
|
5
|
+
If you are building a SaaS (e.g., a Notion clone), you will likely need specialized tools for Authentication, Caching, and Database management. QHTTPX integrates seamlessly with the standard Node.js ecosystem.
|
|
6
|
+
|
|
7
|
+
## 🔓 Authentication (JWT / OAuth)
|
|
8
|
+
QHTTPX does not ship with a built-in auth strategy (like Passport) to keep the core lightweight. Instead, it exposes the `ctx` object, allowing you to use libraries like `jose`, `jsonwebtoken`, or `oslo` easily.
|
|
9
|
+
|
|
10
|
+
### Example: JWT Auth with `jose`
|
|
11
|
+
You can install `jose` (`npm install jose`) and write a simple middleware:
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { createRemoteJWKSet, jwtVerify } from 'jose';
|
|
15
|
+
import { QHTTPXMiddleware } from 'qhttpx';
|
|
16
|
+
|
|
17
|
+
const JWKS = createRemoteJWKSet(new URL('https://your-auth-provider/.well-known/jwks.json'));
|
|
18
|
+
|
|
19
|
+
export const authMiddleware: QHTTPXMiddleware = async (ctx, next) => {
|
|
20
|
+
const authHeader = ctx.req.headers['authorization'];
|
|
21
|
+
|
|
22
|
+
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
23
|
+
ctx.res.statusCode = 401;
|
|
24
|
+
ctx.res.end('Unauthorized');
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const token = authHeader.split(' ')[1];
|
|
30
|
+
const { payload } = await jwtVerify(token, JWKS);
|
|
31
|
+
|
|
32
|
+
// Attach user to context state
|
|
33
|
+
ctx.state.user = payload;
|
|
34
|
+
|
|
35
|
+
await next();
|
|
36
|
+
} catch (err) {
|
|
37
|
+
ctx.res.statusCode = 403;
|
|
38
|
+
ctx.res.end('Forbidden');
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Usage
|
|
43
|
+
app.use(authMiddleware);
|
|
44
|
+
app.get('/dashboard', (ctx) => {
|
|
45
|
+
const userId = ctx.state.user.sub;
|
|
46
|
+
ctx.json({ message: `Hello ${userId}` });
|
|
47
|
+
});
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## ⚡ Caching (Redis / Memcached)
|
|
51
|
+
For high-scale SaaS, you often need a shared cache layer. QHTTPX's middleware pipeline allows you to intercept requests and serve from cache before hitting your database.
|
|
52
|
+
|
|
53
|
+
### Example: Redis Caching with `ioredis`
|
|
54
|
+
Install `ioredis` (`npm install ioredis`):
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
import Redis from 'ioredis';
|
|
58
|
+
import { QHTTPXMiddleware } from 'qhttpx';
|
|
59
|
+
|
|
60
|
+
const redis = new Redis(process.env.REDIS_URL);
|
|
61
|
+
|
|
62
|
+
export const redisCache: QHTTPXMiddleware = async (ctx, next) => {
|
|
63
|
+
if (ctx.req.method !== 'GET') return next();
|
|
64
|
+
|
|
65
|
+
const key = `cache:${ctx.url.pathname}`;
|
|
66
|
+
const cached = await redis.get(key);
|
|
67
|
+
|
|
68
|
+
if (cached) {
|
|
69
|
+
ctx.res.setHeader('X-Cache', 'HIT');
|
|
70
|
+
ctx.res.setHeader('Content-Type', 'application/json');
|
|
71
|
+
ctx.res.end(cached);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Hook into response to cache the result
|
|
76
|
+
const originalJson = ctx.json;
|
|
77
|
+
ctx.json = (body, status) => {
|
|
78
|
+
// Save to Redis (expire in 60s)
|
|
79
|
+
redis.setex(key, 60, JSON.stringify(body));
|
|
80
|
+
originalJson(body, status);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
await next();
|
|
84
|
+
};
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## 🗄️ Database (Prisma / Drizzle / TypeORM)
|
|
88
|
+
QHTTPX is database-agnostic. You can use any ORM or Query Builder.
|
|
89
|
+
|
|
90
|
+
### Example: Using Prisma
|
|
91
|
+
1. `npm install prisma @prisma/client`
|
|
92
|
+
2. `npx prisma init`
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
import { PrismaClient } from '@prisma/client';
|
|
96
|
+
import { QHTTPX } from 'qhttpx';
|
|
97
|
+
|
|
98
|
+
const prisma = new PrismaClient();
|
|
99
|
+
const app = new QHTTPX();
|
|
100
|
+
|
|
101
|
+
// Inject prisma into context (optional, or just import global)
|
|
102
|
+
app.use(async (ctx, next) => {
|
|
103
|
+
ctx.state.db = prisma;
|
|
104
|
+
await next();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
app.get('/users', async (ctx) => {
|
|
108
|
+
const users = await prisma.user.findMany();
|
|
109
|
+
ctx.json(users);
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## 📦 File Uploads to S3
|
|
114
|
+
While QHTTPX supports `multipart/form-data` parsing locally via `ctx.files`, for a SaaS you typically stream directly to S3.
|
|
115
|
+
|
|
116
|
+
You can use `@aws-sdk/client-s3` inside a handler:
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
|
|
120
|
+
|
|
121
|
+
const s3 = new S3Client({ region: 'us-east-1' });
|
|
122
|
+
|
|
123
|
+
app.post('/upload', async (ctx) => {
|
|
124
|
+
// Use busboy stream from QHTTPX (advanced) or just buffer
|
|
125
|
+
const file = ctx.files?.['avatar'];
|
|
126
|
+
|
|
127
|
+
await s3.send(new PutObjectCommand({
|
|
128
|
+
Bucket: 'my-saas-bucket',
|
|
129
|
+
Key: `avatars/${Date.now()}_${file.name}`,
|
|
130
|
+
Body: file.data
|
|
131
|
+
}));
|
|
132
|
+
|
|
133
|
+
ctx.json({ success: true });
|
|
134
|
+
});
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Summary
|
|
138
|
+
| Requirement | Recommended Tool | QHTTPX Integration |
|
|
139
|
+
| :--- | :--- | :--- |
|
|
140
|
+
| **Auth** | `jose`, `jsonwebtoken`, `lucia` | Middleware + `ctx.state.user` |
|
|
141
|
+
| **Database** | `prisma`, `drizzle-orm`, `mongoose` | Import globally or attach to `ctx` |
|
|
142
|
+
| **Caching** | `ioredis`, `node-cache` | Middleware interceptor |
|
|
143
|
+
| **Validation** | `zod`, `valibot` | Native `ctx.validate(schema)` support |
|
|
144
|
+
| **Email** | `resend`, `nodemailer` | Call directly in handlers |
|
|
145
|
+
|
|
146
|
+
**Verdict**: Yes, you have full freedom to install and use any NPM package. QHTTPX acts as the high-performance glue holding your stack together.
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# The Path to "The Best Framework"
|
|
2
|
+
|
|
3
|
+
To compete with frameworks like Fastify, Hono, and NestJS, QHTTPX needs to master **Type Safety**, **Performance (Serialization)**, and **Observability**.
|
|
4
|
+
|
|
5
|
+
Here is the vision for the final version.
|
|
6
|
+
|
|
7
|
+
## 1. Type-Safe Validation (Zod Integration)
|
|
8
|
+
|
|
9
|
+
Currently, we use simple strings. The "Best" version integrates **Zod** for TypeScript inference.
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
import { z } from 'zod';
|
|
13
|
+
|
|
14
|
+
const UserSchema = z.object({
|
|
15
|
+
username: z.string().min(3),
|
|
16
|
+
email: z.string().email(),
|
|
17
|
+
age: z.number().optional()
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
app.post('/users', async (ctx) => {
|
|
21
|
+
// 1. Validates body against Zod schema
|
|
22
|
+
// 2. Throws 400 if invalid
|
|
23
|
+
// 3. 'body' is fully typed as { username: string, email: string, ... }
|
|
24
|
+
const body = await ctx.validate(UserSchema);
|
|
25
|
+
|
|
26
|
+
// No need for "as string" casting
|
|
27
|
+
console.log(body.email);
|
|
28
|
+
});
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## 2. JIT Response Serialization (Performance)
|
|
32
|
+
|
|
33
|
+
`JSON.stringify` is slow. The "Best" frameworks compile schemas to code (JIT). This makes responses 2x-3x faster.
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
const ResponseSchema = z.object({
|
|
37
|
+
id: z.number(),
|
|
38
|
+
name: z.string(),
|
|
39
|
+
// Exclude 'password' automatically
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
app.get('/users/:id', async (ctx) => {
|
|
43
|
+
const user = await db.getUser(ctx.params.id);
|
|
44
|
+
|
|
45
|
+
// Uses a pre-compiled serializer function
|
|
46
|
+
// Faster than JSON.stringify and safer (strips sensitive fields)
|
|
47
|
+
ctx.schema(ResponseSchema).json(user);
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## 3. Automatic OpenAPI (Swagger) Generation
|
|
52
|
+
|
|
53
|
+
Since we have schemas, we can generate documentation automatically.
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
// Auto-generate OpenAPI v3 spec from routes and Zod schemas
|
|
57
|
+
app.get('/openapi.json', (ctx) => {
|
|
58
|
+
ctx.json(app.getOpenAPISpec());
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Serve Swagger UI
|
|
62
|
+
app.get('/docs', swaggerUI({ url: '/openapi.json' }));
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## 4. End-to-End Type Safety (RPC Client)
|
|
66
|
+
|
|
67
|
+
Share types between Backend and Frontend.
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
// Shared Types (libs/types.ts)
|
|
71
|
+
export type AppType = typeof app;
|
|
72
|
+
|
|
73
|
+
// Frontend (client.ts)
|
|
74
|
+
import { hc } from 'qhttpx/client';
|
|
75
|
+
import type { AppType } from './server';
|
|
76
|
+
|
|
77
|
+
const client = hc<AppType>('http://localhost:3000');
|
|
78
|
+
|
|
79
|
+
// Fully typed!
|
|
80
|
+
// TypeScript knows that res.json() returns { id: number, name: string }
|
|
81
|
+
const res = await client.users[':id'].$get({
|
|
82
|
+
param: { id: '123' }
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## 5. Structured Logging (Observability)
|
|
87
|
+
|
|
88
|
+
Replace `console.log` with a high-performance JSON logger (like Pino) for production observability.
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
// Logs: {"level":"info","time":1630000000,"reqId":"req-1","msg":"Request completed","duration":12}
|
|
92
|
+
app.log.info({ userId: 1 }, 'User logged in');
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Summary of Remaining Work
|
|
96
|
+
|
|
97
|
+
1. **Validator Upgrade**: Replace `SimpleValidator` with a `ZodValidator`.
|
|
98
|
+
2. **Serializer Compiler**: Implement `fast-json-stringify` logic.
|
|
99
|
+
3. **Type Extraction**: Build the `AppType` export logic.
|
package/docs/OPENAPI.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# OpenAPI Generation
|
|
2
|
+
|
|
3
|
+
QHTTPX can automatically generate an OpenAPI 3.0 specification from your route definitions and schemas. This allows you to easily create interactive documentation (Swagger UI) for your API.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Auto-Discovery**: Scans your router to find all registered endpoints.
|
|
8
|
+
- **Schema Mapping**: Converts your `SimpleValidator` schemas into OpenAPI Schema Objects.
|
|
9
|
+
- **Parameter Inference**: Automatically detects URL parameters (e.g., `/users/:id`) and query parameters.
|
|
10
|
+
- **Zero Configuration**: Just call `app.getOpenAPI()`.
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
### 1. Define Routes with Schemas
|
|
15
|
+
|
|
16
|
+
To get the most out of OpenAPI, define schemas for your routes.
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
app.post('/users/:id', {
|
|
20
|
+
schema: {
|
|
21
|
+
params: {
|
|
22
|
+
type: 'object',
|
|
23
|
+
properties: {
|
|
24
|
+
id: { type: 'string' }
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
body: {
|
|
28
|
+
type: 'object',
|
|
29
|
+
properties: {
|
|
30
|
+
name: { type: 'string', min: 3 },
|
|
31
|
+
email: { type: 'string', pattern: '^\\S+@\\S+\\.\\S+$' }
|
|
32
|
+
},
|
|
33
|
+
required: true
|
|
34
|
+
},
|
|
35
|
+
response: {
|
|
36
|
+
type: 'object',
|
|
37
|
+
properties: {
|
|
38
|
+
success: { type: 'boolean' },
|
|
39
|
+
id: { type: 'string' }
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
handler: (ctx) => { /* ... */ }
|
|
44
|
+
});
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### 2. Expose the Spec
|
|
48
|
+
|
|
49
|
+
Create a route to serve the JSON specification.
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
app.get('/swagger.json', (ctx) => {
|
|
53
|
+
const spec = app.getOpenAPI({
|
|
54
|
+
info: {
|
|
55
|
+
title: 'My Awesome API',
|
|
56
|
+
version: '1.0.0',
|
|
57
|
+
description: 'Powered by QHTTPX'
|
|
58
|
+
},
|
|
59
|
+
servers: [
|
|
60
|
+
{ url: 'http://localhost:3000', description: 'Local Server' }
|
|
61
|
+
]
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
ctx.json(spec);
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 3. (Optional) Serve Swagger UI
|
|
69
|
+
|
|
70
|
+
You can easily serve a Swagger UI page that points to your spec.
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
app.get('/docs', (ctx) => {
|
|
74
|
+
ctx.html(`
|
|
75
|
+
<!DOCTYPE html>
|
|
76
|
+
<html lang="en">
|
|
77
|
+
<head>
|
|
78
|
+
<meta charset="utf-8" />
|
|
79
|
+
<title>API Documentation</title>
|
|
80
|
+
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5.9.0/swagger-ui.css" />
|
|
81
|
+
</head>
|
|
82
|
+
<body>
|
|
83
|
+
<div id="swagger-ui"></div>
|
|
84
|
+
<script src="https://unpkg.com/swagger-ui-dist@5.9.0/swagger-ui-bundle.js"></script>
|
|
85
|
+
<script>
|
|
86
|
+
window.onload = () => {
|
|
87
|
+
window.ui = SwaggerUIBundle({
|
|
88
|
+
url: '/swagger.json',
|
|
89
|
+
dom_id: '#swagger-ui',
|
|
90
|
+
});
|
|
91
|
+
};
|
|
92
|
+
</script>
|
|
93
|
+
</body>
|
|
94
|
+
</html>
|
|
95
|
+
`);
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Now open `http://localhost:3000/docs` to see your interactive API documentation!
|
package/docs/PLUGINS.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# QHTTPX Plugin System
|
|
2
|
+
|
|
3
|
+
The QHTTPX Plugin System allows you to encapsulate and share functionality, routes, and middleware. Plugins are fully typed and support scoped routing.
|
|
4
|
+
|
|
5
|
+
## Creating a Plugin
|
|
6
|
+
|
|
7
|
+
A plugin is simply a function that accepts a `QHTTPXScope` and an options object.
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
import { QHTTPXPlugin, QHTTPXScope } from 'qhttpx';
|
|
11
|
+
|
|
12
|
+
type MyPluginOptions = {
|
|
13
|
+
secret: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const myPlugin: QHTTPXPlugin<MyPluginOptions> = async (scope, options) => {
|
|
17
|
+
// Register routes within this scope
|
|
18
|
+
scope.get('/info', (ctx) => {
|
|
19
|
+
ctx.json({ secret: options.secret });
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// Register sub-plugins
|
|
23
|
+
// await scope.register(otherPlugin);
|
|
24
|
+
};
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Registering a Plugin
|
|
28
|
+
|
|
29
|
+
Use `app.register()` to load a plugin. You can optionally provide a `prefix` to namespace all routes within the plugin.
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { createHttpApp } from 'qhttpx';
|
|
33
|
+
import { myPlugin } from './my-plugin';
|
|
34
|
+
|
|
35
|
+
const app = createHttpApp();
|
|
36
|
+
|
|
37
|
+
// Routes will be available at /api/v1/info
|
|
38
|
+
await app.register(myPlugin, {
|
|
39
|
+
prefix: '/api/v1',
|
|
40
|
+
secret: 'super-secret'
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Scope API
|
|
45
|
+
|
|
46
|
+
The `scope` object passed to the plugin exposes standard routing methods:
|
|
47
|
+
|
|
48
|
+
- `scope.get(path, handler)`
|
|
49
|
+
- `scope.post(path, handler)`
|
|
50
|
+
- `scope.put(path, handler)`
|
|
51
|
+
- `scope.delete(path, handler)`
|
|
52
|
+
- `scope.use(middleware)` (Currently global, scoped middleware coming soon)
|
|
53
|
+
- `scope.register(plugin, options)`
|
|
54
|
+
|
|
55
|
+
## Best Practices
|
|
56
|
+
|
|
57
|
+
1. **Encapsulation**: Keep plugins self-contained. Avoid relying on global state.
|
|
58
|
+
2. **Options**: Use the `options` object to pass configuration (database connections, secrets, etc.).
|
|
59
|
+
3. **Async**: Plugins can be async. QHTTPX will await them during registration.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Real World Usage Examples
|
|
2
|
+
|
|
3
|
+
Here is how QHTTPX looks in a production application.
|
|
4
|
+
|
|
5
|
+
## 1. The Entry Point (`src/index.ts`)
|
|
6
|
+
|
|
7
|
+
Clean setup with built-in clustering and rate limiting.
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
import { createHttpApp } from 'qhttpx';
|
|
11
|
+
import { rateLimit } from 'qhttpx/middleware';
|
|
12
|
+
|
|
13
|
+
const app = createHttpApp({
|
|
14
|
+
// Enable built-in request coalescing for high traffic
|
|
15
|
+
enableRequestFusion: true,
|
|
16
|
+
// Auto-scale to CPU cores
|
|
17
|
+
workers: 'auto'
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// Global Rate Limiter (Aegis)
|
|
21
|
+
app.use(rateLimit({
|
|
22
|
+
windowMs: 60 * 1000,
|
|
23
|
+
max: 1000,
|
|
24
|
+
keyGenerator: (req) => req.headers['x-api-key'] || req.ip
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
// Start server
|
|
28
|
+
app.listen(3000).then(({ port }) => {
|
|
29
|
+
console.log(`🚀 Server running on port ${port}`);
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## 2. Modular Features (`src/modules/users.ts`)
|
|
34
|
+
|
|
35
|
+
Using the Plugin System to organize code.
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import { QHTTPXPlugin, QHTTPXScope } from 'qhttpx';
|
|
39
|
+
|
|
40
|
+
export const userModule: QHTTPXPlugin = async (scope: QHTTPXScope) => {
|
|
41
|
+
|
|
42
|
+
// GET /api/v1/users/:id
|
|
43
|
+
scope.get('/:id', async (ctx) => {
|
|
44
|
+
const userId = ctx.params.id;
|
|
45
|
+
// Auto-fused database query
|
|
46
|
+
const user = await ctx.db.query('SELECT * FROM users WHERE id = ?', [userId]);
|
|
47
|
+
|
|
48
|
+
if (!user) return ctx.notFound();
|
|
49
|
+
ctx.json(user);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// POST /api/v1/users
|
|
53
|
+
scope.post('/', async (ctx) => {
|
|
54
|
+
// Built-in validation
|
|
55
|
+
const body = await ctx.validate({
|
|
56
|
+
email: 'string|required',
|
|
57
|
+
age: 'number|min:18'
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// ... save user logic
|
|
61
|
+
ctx.json({ success: true });
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## 3. Registering Modules (`src/app.ts`)
|
|
67
|
+
|
|
68
|
+
Bringing it all together with prefixes.
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import { userModule } from './modules/users';
|
|
72
|
+
import { authModule } from './modules/auth';
|
|
73
|
+
|
|
74
|
+
// Register plugins with prefixes
|
|
75
|
+
await app.register(userModule, { prefix: '/api/v1/users' });
|
|
76
|
+
await app.register(authModule, { prefix: '/api/v1/auth' });
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## 4. High-Performance Query Fusion
|
|
80
|
+
|
|
81
|
+
How the fusion engine simplifies code while boosting performance.
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
// In your controller/handler:
|
|
85
|
+
// You write simple queries:
|
|
86
|
+
const user = await db.query('SELECT * FROM users WHERE id = ?', [1]);
|
|
87
|
+
|
|
88
|
+
// QHTTPX automatically batches them into:
|
|
89
|
+
// SELECT * FROM users WHERE id IN (1, 2, 3, ...)
|
|
90
|
+
// And distributes the results back to each request.
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## 5. Middleware & Guards
|
|
94
|
+
|
|
95
|
+
Protecting routes is simple.
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
const adminGuard = (ctx, next) => {
|
|
99
|
+
if (ctx.req.headers['x-role'] !== 'admin') {
|
|
100
|
+
return ctx.status(403).json({ error: 'Forbidden' });
|
|
101
|
+
}
|
|
102
|
+
return next();
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// Apply to specific route
|
|
106
|
+
app.delete('/db/drop', adminGuard, (ctx) => {
|
|
107
|
+
// ...
|
|
108
|
+
});
|
|
109
|
+
```
|