qhttpx 2.1.0 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -21
- package/README.md +117 -221
- package/dist/chunk-QW72SEAS.mjs +98 -0
- package/dist/{src/cli/index.d.ts → cli.d.mts} +0 -1
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +287 -0
- package/dist/cli.mjs +209 -0
- package/dist/index.d.mts +433 -0
- package/dist/index.d.ts +433 -0
- package/dist/index.js +1955 -0
- package/dist/index.mjs +1863 -0
- package/dist/qhttpx-core-new.linux-x64-gnu.node +0 -0
- package/dist/qhttpx-core-new.node +0 -0
- package/dist/qhttpx-core-new.win32-x64-msvc.node +0 -0
- package/examples/benchmark.ts +67 -0
- package/examples/body_demo.ts +32 -0
- package/examples/chat_client.ts +36 -0
- package/examples/chat_demo.ts +41 -0
- package/examples/cluster_demo.ts +26 -0
- package/examples/cors_demo.ts +25 -0
- package/examples/db_auth_demo.ts +66 -0
- package/examples/demo.ts +52 -0
- package/examples/headers_demo.ts +24 -0
- package/examples/hello.ts +7 -0
- package/examples/http2_demo.ts +52 -0
- package/examples/magic-dev.ts +15 -0
- package/examples/middleware_demo.ts +34 -0
- package/examples/mongo_demo.ts +40 -0
- package/examples/native_policy_demo.ts +33 -0
- package/examples/observability_demo.ts +24 -0
- package/examples/public/1mb.dat +1 -0
- package/examples/query_demo.ts +29 -0
- package/examples/response_demo.ts +21 -0
- package/examples/routing_demo.ts +24 -0
- package/examples/server.ts +51 -0
- package/examples/static_demo.ts +29 -0
- package/examples/test_middleware_client.ts +33 -0
- package/examples/test_query_client.ts +34 -0
- package/examples/test_response_client.ts +30 -0
- package/examples/tls_demo.ts +32 -0
- package/examples/upload_demo.ts +43 -0
- package/examples/uploads/test.txt +1 -0
- package/examples/verify_upload.ts +69 -0
- package/examples/ws_client.ts +26 -0
- package/examples/ws_demo.ts +21 -0
- package/package.json +65 -81
- package/CHANGELOG.md +0 -285
- package/dist/examples/api-server.d.ts +0 -1
- package/dist/examples/api-server.js +0 -80
- package/dist/examples/basic.d.ts +0 -1
- package/dist/examples/basic.js +0 -9
- package/dist/examples/compression.d.ts +0 -1
- package/dist/examples/compression.js +0 -15
- package/dist/examples/cors.d.ts +0 -1
- package/dist/examples/cors.js +0 -18
- package/dist/examples/errors.d.ts +0 -1
- package/dist/examples/errors.js +0 -26
- package/dist/examples/file-upload.d.ts +0 -1
- package/dist/examples/file-upload.js +0 -22
- package/dist/examples/fusion.d.ts +0 -1
- package/dist/examples/fusion.js +0 -21
- package/dist/examples/rate-limiting.d.ts +0 -1
- package/dist/examples/rate-limiting.js +0 -17
- package/dist/examples/validation.d.ts +0 -1
- package/dist/examples/validation.js +0 -22
- package/dist/examples/websockets.d.ts +0 -1
- package/dist/examples/websockets.js +0 -19
- package/dist/package.json +0 -107
- package/dist/src/benchmarks/quantam-users.d.ts +0 -1
- package/dist/src/benchmarks/quantam-users.js +0 -56
- package/dist/src/benchmarks/quick-bench.d.ts +0 -1
- package/dist/src/benchmarks/quick-bench.js +0 -57
- package/dist/src/benchmarks/simple-json.d.ts +0 -1
- package/dist/src/benchmarks/simple-json.js +0 -171
- package/dist/src/benchmarks/ultra-mode.d.ts +0 -1
- package/dist/src/benchmarks/ultra-mode.js +0 -64
- package/dist/src/cli/index.js +0 -222
- package/dist/src/client/index.d.ts +0 -17
- package/dist/src/client/index.js +0 -72
- package/dist/src/core/batch.d.ts +0 -24
- package/dist/src/core/batch.js +0 -97
- package/dist/src/core/body-parser.d.ts +0 -15
- package/dist/src/core/body-parser.js +0 -121
- package/dist/src/core/buffer-pool.d.ts +0 -41
- package/dist/src/core/buffer-pool.js +0 -70
- package/dist/src/core/config.d.ts +0 -7
- package/dist/src/core/config.js +0 -50
- package/dist/src/core/context-pool.d.ts +0 -12
- package/dist/src/core/context-pool.js +0 -34
- package/dist/src/core/errors.d.ts +0 -34
- package/dist/src/core/errors.js +0 -70
- package/dist/src/core/fusion.d.ts +0 -20
- package/dist/src/core/fusion.js +0 -191
- package/dist/src/core/logger.d.ts +0 -22
- package/dist/src/core/logger.js +0 -49
- package/dist/src/core/metrics.d.ts +0 -50
- package/dist/src/core/metrics.js +0 -123
- package/dist/src/core/resources.d.ts +0 -9
- package/dist/src/core/resources.js +0 -25
- package/dist/src/core/scheduler.d.ts +0 -38
- package/dist/src/core/scheduler.js +0 -126
- package/dist/src/core/scope.d.ts +0 -41
- package/dist/src/core/scope.js +0 -107
- package/dist/src/core/serializer.d.ts +0 -10
- package/dist/src/core/serializer.js +0 -82
- package/dist/src/core/server.d.ts +0 -179
- package/dist/src/core/server.js +0 -1511
- package/dist/src/core/stream.d.ts +0 -15
- package/dist/src/core/stream.js +0 -71
- package/dist/src/core/tasks.d.ts +0 -29
- package/dist/src/core/tasks.js +0 -87
- package/dist/src/core/timer.d.ts +0 -11
- package/dist/src/core/timer.js +0 -29
- package/dist/src/core/types.d.ts +0 -225
- package/dist/src/core/types.js +0 -19
- package/dist/src/core/websocket.d.ts +0 -25
- package/dist/src/core/websocket.js +0 -86
- package/dist/src/core/worker-queue.d.ts +0 -41
- package/dist/src/core/worker-queue.js +0 -73
- package/dist/src/database/adapters/memory.d.ts +0 -21
- package/dist/src/database/adapters/memory.js +0 -90
- package/dist/src/database/adapters/mongo.d.ts +0 -11
- package/dist/src/database/adapters/mongo.js +0 -141
- package/dist/src/database/adapters/postgres.d.ts +0 -10
- package/dist/src/database/adapters/postgres.js +0 -111
- package/dist/src/database/adapters/sqlite.d.ts +0 -10
- package/dist/src/database/adapters/sqlite.js +0 -42
- package/dist/src/database/coalescer.d.ts +0 -14
- package/dist/src/database/coalescer.js +0 -134
- package/dist/src/database/manager.d.ts +0 -35
- package/dist/src/database/manager.js +0 -87
- package/dist/src/database/types.d.ts +0 -20
- package/dist/src/database/types.js +0 -2
- package/dist/src/index.d.ts +0 -52
- package/dist/src/index.js +0 -92
- package/dist/src/middleware/compression.d.ts +0 -2
- package/dist/src/middleware/compression.js +0 -133
- package/dist/src/middleware/cors.d.ts +0 -2
- package/dist/src/middleware/cors.js +0 -66
- package/dist/src/middleware/presets.d.ts +0 -15
- package/dist/src/middleware/presets.js +0 -52
- package/dist/src/middleware/rate-limit.d.ts +0 -14
- package/dist/src/middleware/rate-limit.js +0 -83
- package/dist/src/middleware/security.d.ts +0 -10
- package/dist/src/middleware/security.js +0 -74
- package/dist/src/middleware/static.d.ts +0 -11
- package/dist/src/middleware/static.js +0 -191
- package/dist/src/openapi/generator.d.ts +0 -19
- package/dist/src/openapi/generator.js +0 -149
- package/dist/src/router/radix-router.d.ts +0 -18
- package/dist/src/router/radix-router.js +0 -89
- package/dist/src/router/radix-tree.d.ts +0 -21
- package/dist/src/router/radix-tree.js +0 -175
- package/dist/src/router/router.d.ts +0 -37
- package/dist/src/router/router.js +0 -203
- package/dist/src/testing/index.d.ts +0 -25
- package/dist/src/testing/index.js +0 -84
- package/dist/src/utils/cookies.d.ts +0 -3
- package/dist/src/utils/cookies.js +0 -59
- package/dist/src/utils/logger.d.ts +0 -2
- package/dist/src/utils/logger.js +0 -45
- package/dist/src/utils/signals.d.ts +0 -6
- package/dist/src/utils/signals.js +0 -31
- package/dist/src/utils/sse.d.ts +0 -6
- package/dist/src/utils/sse.js +0 -32
- package/dist/src/validation/index.d.ts +0 -3
- package/dist/src/validation/index.js +0 -19
- package/dist/src/validation/simple.d.ts +0 -5
- package/dist/src/validation/simple.js +0 -102
- package/dist/src/validation/types.d.ts +0 -32
- package/dist/src/validation/types.js +0 -12
- package/dist/src/validation/zod.d.ts +0 -4
- package/dist/src/validation/zod.js +0 -18
- package/dist/src/views/index.d.ts +0 -1
- package/dist/src/views/index.js +0 -17
- package/dist/src/views/types.d.ts +0 -3
- package/dist/src/views/types.js +0 -2
- package/dist/tests/adapters.test.d.ts +0 -1
- package/dist/tests/adapters.test.js +0 -106
- package/dist/tests/batch.test.d.ts +0 -1
- package/dist/tests/batch.test.js +0 -117
- package/dist/tests/body-parser.test.d.ts +0 -1
- package/dist/tests/body-parser.test.js +0 -52
- package/dist/tests/compression-sse.test.d.ts +0 -1
- package/dist/tests/compression-sse.test.js +0 -87
- package/dist/tests/cookies.test.d.ts +0 -1
- package/dist/tests/cookies.test.js +0 -63
- package/dist/tests/cors.test.d.ts +0 -1
- package/dist/tests/cors.test.js +0 -55
- package/dist/tests/database.test.d.ts +0 -1
- package/dist/tests/database.test.js +0 -80
- package/dist/tests/dx.test.d.ts +0 -1
- package/dist/tests/dx.test.js +0 -114
- package/dist/tests/ecosystem.test.d.ts +0 -1
- package/dist/tests/ecosystem.test.js +0 -133
- package/dist/tests/features.test.d.ts +0 -1
- package/dist/tests/features.test.js +0 -47
- package/dist/tests/fusion.test.d.ts +0 -1
- package/dist/tests/fusion.test.js +0 -92
- package/dist/tests/http-basic.test.d.ts +0 -1
- package/dist/tests/http-basic.test.js +0 -124
- package/dist/tests/logger.test.d.ts +0 -1
- package/dist/tests/logger.test.js +0 -33
- package/dist/tests/middleware.test.d.ts +0 -1
- package/dist/tests/middleware.test.js +0 -109
- package/dist/tests/observability.test.d.ts +0 -1
- package/dist/tests/observability.test.js +0 -59
- package/dist/tests/openapi.test.d.ts +0 -1
- package/dist/tests/openapi.test.js +0 -64
- package/dist/tests/plugin.test.d.ts +0 -1
- package/dist/tests/plugin.test.js +0 -65
- package/dist/tests/plugins.test.d.ts +0 -1
- package/dist/tests/plugins.test.js +0 -71
- package/dist/tests/rate-limit.test.d.ts +0 -1
- package/dist/tests/rate-limit.test.js +0 -77
- package/dist/tests/resources.test.d.ts +0 -1
- package/dist/tests/resources.test.js +0 -47
- package/dist/tests/scheduler.test.d.ts +0 -1
- package/dist/tests/scheduler.test.js +0 -46
- package/dist/tests/schema-routes.test.d.ts +0 -1
- package/dist/tests/schema-routes.test.js +0 -79
- package/dist/tests/security.test.d.ts +0 -1
- package/dist/tests/security.test.js +0 -83
- package/dist/tests/server-db.test.d.ts +0 -1
- package/dist/tests/server-db.test.js +0 -72
- package/dist/tests/smoke.test.d.ts +0 -1
- package/dist/tests/smoke.test.js +0 -10
- package/dist/tests/sqlite-fusion.test.d.ts +0 -1
- package/dist/tests/sqlite-fusion.test.js +0 -92
- package/dist/tests/static.test.d.ts +0 -1
- package/dist/tests/static.test.js +0 -102
- package/dist/tests/stream.test.d.ts +0 -1
- package/dist/tests/stream.test.js +0 -44
- package/dist/tests/task-metrics.test.d.ts +0 -1
- package/dist/tests/task-metrics.test.js +0 -53
- package/dist/tests/tasks.test.d.ts +0 -1
- package/dist/tests/tasks.test.js +0 -62
- package/dist/tests/testing.test.d.ts +0 -1
- package/dist/tests/testing.test.js +0 -47
- package/dist/tests/validation.test.d.ts +0 -1
- package/dist/tests/validation.test.js +0 -107
- package/dist/tests/websocket.test.d.ts +0 -1
- package/dist/tests/websocket.test.js +0 -146
- package/dist/vitest.config.d.ts +0 -2
- package/dist/vitest.config.js +0 -9
- package/docs/AEGIS.md +0 -91
- package/docs/API_REFERENCE.md +0 -749
- package/docs/BENCHMARKS.md +0 -39
- package/docs/CAPABILITIES.md +0 -70
- package/docs/CLI.md +0 -43
- package/docs/DATABASE.md +0 -142
- package/docs/ECOSYSTEM.md +0 -146
- package/docs/ERRORS.md +0 -112
- package/docs/FUSION.md +0 -87
- package/docs/MIDDLEWARE.md +0 -65
- package/docs/MIGRATION_1.9_TO_2.0.md +0 -495
- package/docs/NEXT_STEPS.md +0 -99
- package/docs/OPENAPI.md +0 -99
- package/docs/PLUGINS.md +0 -59
- package/docs/PRODUCTION_DEPLOYMENT.md +0 -798
- package/docs/REAL_WORLD_EXAMPLES.md +0 -109
- package/docs/ROADMAP.md +0 -366
- package/docs/ROUTING.md +0 -78
- package/docs/SECURITY.md +0 -876
- package/docs/STATIC.md +0 -61
- package/docs/VALIDATION.md +0 -114
- package/docs/WEBSOCKETS.md +0 -76
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/tests/tasks.test.js
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const vitest_1 = require("vitest");
|
|
4
|
-
const index_1 = require("../src/index");
|
|
5
|
-
(0, vitest_1.describe)('Task engine', () => {
|
|
6
|
-
(0, vitest_1.it)('registers and executes a task', async () => {
|
|
7
|
-
const app = new index_1.QHTTPX();
|
|
8
|
-
const seen = [];
|
|
9
|
-
app.task('echo', async (payload) => {
|
|
10
|
-
seen.push(payload);
|
|
11
|
-
});
|
|
12
|
-
await app.enqueue('echo', { hello: 'world' });
|
|
13
|
-
(0, vitest_1.expect)(seen).toHaveLength(1);
|
|
14
|
-
(0, vitest_1.expect)(seen[0]).toEqual({ hello: 'world' });
|
|
15
|
-
});
|
|
16
|
-
(0, vitest_1.it)('retries failing tasks according to maxRetries', async () => {
|
|
17
|
-
const app = new index_1.QHTTPX();
|
|
18
|
-
const spy = vitest_1.vi.fn();
|
|
19
|
-
let attempts = 0;
|
|
20
|
-
app.task('sometimes-fails', () => {
|
|
21
|
-
spy();
|
|
22
|
-
attempts += 1;
|
|
23
|
-
if (attempts < 3) {
|
|
24
|
-
throw new Error('fail');
|
|
25
|
-
}
|
|
26
|
-
}, { maxRetries: 5 });
|
|
27
|
-
await app.enqueue('sometimes-fails', null);
|
|
28
|
-
(0, vitest_1.expect)(spy).toHaveBeenCalledTimes(3);
|
|
29
|
-
});
|
|
30
|
-
(0, vitest_1.it)('propagates errors after exceeding retries', async () => {
|
|
31
|
-
const app = new index_1.QHTTPX();
|
|
32
|
-
app.task('always-fails', () => {
|
|
33
|
-
throw new Error('boom');
|
|
34
|
-
}, { maxRetries: 1 });
|
|
35
|
-
await (0, vitest_1.expect)(app.enqueue('always-fails', null)).rejects.toThrow('boom');
|
|
36
|
-
});
|
|
37
|
-
(0, vitest_1.it)('integrates with HTTP route that enqueues a job', async () => {
|
|
38
|
-
const app = new index_1.QHTTPX();
|
|
39
|
-
const seen = [];
|
|
40
|
-
app.task('job', async (payload) => {
|
|
41
|
-
seen.push(payload);
|
|
42
|
-
});
|
|
43
|
-
app.post('/enqueue', async (ctx) => {
|
|
44
|
-
const body = ctx.body;
|
|
45
|
-
await app.enqueue('job', body.value);
|
|
46
|
-
ctx.json({ ok: true });
|
|
47
|
-
});
|
|
48
|
-
const { port } = await app.listen(0, '127.0.0.1');
|
|
49
|
-
const response = await fetch(`http://127.0.0.1:${port}/enqueue`, {
|
|
50
|
-
method: 'POST',
|
|
51
|
-
headers: {
|
|
52
|
-
'content-type': 'application/json',
|
|
53
|
-
},
|
|
54
|
-
body: JSON.stringify({ value: 'hello' }),
|
|
55
|
-
});
|
|
56
|
-
(0, vitest_1.expect)(response.status).toBe(200);
|
|
57
|
-
const json = (await response.json());
|
|
58
|
-
(0, vitest_1.expect)(json.ok).toBe(true);
|
|
59
|
-
(0, vitest_1.expect)(seen).toEqual(['hello']);
|
|
60
|
-
await app.close();
|
|
61
|
-
});
|
|
62
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const vitest_1 = require("vitest");
|
|
4
|
-
const src_1 = require("../src");
|
|
5
|
-
(0, vitest_1.describe)('Testing utilities', () => {
|
|
6
|
-
(0, vitest_1.it)('creates a test client and handles requests', async () => {
|
|
7
|
-
const app = new src_1.QHTTPX();
|
|
8
|
-
app.get('/hello', (ctx) => {
|
|
9
|
-
ctx.json({ message: 'world' });
|
|
10
|
-
});
|
|
11
|
-
app.post('/echo', (ctx) => {
|
|
12
|
-
ctx.json(ctx.body);
|
|
13
|
-
});
|
|
14
|
-
const client = (0, src_1.createTestClient)(app);
|
|
15
|
-
try {
|
|
16
|
-
const res1 = await client.get('/hello');
|
|
17
|
-
(0, vitest_1.expect)(res1.status).toBe(200);
|
|
18
|
-
(0, vitest_1.expect)(await res1.json()).toEqual({ message: 'world' });
|
|
19
|
-
const res2 = await client.post('/echo', { foo: 'bar' });
|
|
20
|
-
(0, vitest_1.expect)(res2.status).toBe(200);
|
|
21
|
-
(0, vitest_1.expect)(await res2.json()).toEqual({ foo: 'bar' });
|
|
22
|
-
}
|
|
23
|
-
finally {
|
|
24
|
-
await client.stop();
|
|
25
|
-
}
|
|
26
|
-
});
|
|
27
|
-
(0, vitest_1.it)('handles custom headers and raw body', async () => {
|
|
28
|
-
const app = new src_1.QHTTPX();
|
|
29
|
-
app.post('/raw', (ctx) => {
|
|
30
|
-
ctx.send(ctx.req.headers['x-custom']);
|
|
31
|
-
});
|
|
32
|
-
const client = (0, src_1.createTestClient)(app);
|
|
33
|
-
try {
|
|
34
|
-
const res = await client.post('/raw', 'raw-body', {
|
|
35
|
-
headers: {
|
|
36
|
-
'x-custom': 'custom-value',
|
|
37
|
-
'content-type': 'text/plain',
|
|
38
|
-
},
|
|
39
|
-
});
|
|
40
|
-
(0, vitest_1.expect)(res.status).toBe(200);
|
|
41
|
-
(0, vitest_1.expect)(await res.text()).toBe('custom-value');
|
|
42
|
-
}
|
|
43
|
-
finally {
|
|
44
|
-
await client.stop();
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const vitest_1 = require("vitest");
|
|
4
|
-
const server_1 = require("../src/core/server");
|
|
5
|
-
(0, vitest_1.describe)('Validation System', () => {
|
|
6
|
-
let app;
|
|
7
|
-
(0, vitest_1.afterEach)(async () => {
|
|
8
|
-
if (app)
|
|
9
|
-
await app.close();
|
|
10
|
-
});
|
|
11
|
-
(0, vitest_1.it)('should validate request body and return 400 on failure', async () => {
|
|
12
|
-
app = new server_1.QHTTPX();
|
|
13
|
-
const schema = {
|
|
14
|
-
body: {
|
|
15
|
-
type: 'object',
|
|
16
|
-
properties: {
|
|
17
|
-
name: { type: 'string', min: 3 },
|
|
18
|
-
age: { type: 'number', min: 18 }
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
};
|
|
22
|
-
app.post('/user', {
|
|
23
|
-
schema,
|
|
24
|
-
handler: (ctx) => {
|
|
25
|
-
ctx.json({ status: 'ok', body: ctx.body });
|
|
26
|
-
}
|
|
27
|
-
});
|
|
28
|
-
const { port } = await app.listen(0);
|
|
29
|
-
// Invalid request (age too low)
|
|
30
|
-
const res1 = await fetch(`http://localhost:${port}/user`, {
|
|
31
|
-
method: 'POST',
|
|
32
|
-
headers: { 'Content-Type': 'application/json' },
|
|
33
|
-
body: JSON.stringify({ name: 'Alice', age: 10 })
|
|
34
|
-
});
|
|
35
|
-
(0, vitest_1.expect)(res1.status).toBe(400);
|
|
36
|
-
const data1 = await res1.json();
|
|
37
|
-
(0, vitest_1.expect)(data1.error).toBe('Validation Error');
|
|
38
|
-
(0, vitest_1.expect)(data1.details).toContain('age');
|
|
39
|
-
// Valid request
|
|
40
|
-
const res2 = await fetch(`http://localhost:${port}/user`, {
|
|
41
|
-
method: 'POST',
|
|
42
|
-
headers: { 'Content-Type': 'application/json' },
|
|
43
|
-
body: JSON.stringify({ name: 'Alice', age: 20 })
|
|
44
|
-
});
|
|
45
|
-
(0, vitest_1.expect)(res2.status).toBe(200);
|
|
46
|
-
const data2 = await res2.json();
|
|
47
|
-
(0, vitest_1.expect)(data2.body).toEqual({ name: 'Alice', age: 20 });
|
|
48
|
-
});
|
|
49
|
-
(0, vitest_1.it)('should validate and coerce query parameters', async () => {
|
|
50
|
-
app = new server_1.QHTTPX();
|
|
51
|
-
const schema = {
|
|
52
|
-
query: {
|
|
53
|
-
type: 'object',
|
|
54
|
-
properties: {
|
|
55
|
-
page: { type: 'number', min: 1 },
|
|
56
|
-
sort: { type: 'string', enum: ['asc', 'desc'] }
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
app.get('/items', {
|
|
61
|
-
schema,
|
|
62
|
-
handler: (ctx) => {
|
|
63
|
-
// ctx.query should have numbers now
|
|
64
|
-
ctx.json({
|
|
65
|
-
page: ctx.query.page,
|
|
66
|
-
pageType: typeof ctx.query.page,
|
|
67
|
-
sort: ctx.query.sort
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
});
|
|
71
|
-
const { port } = await app.listen(0);
|
|
72
|
-
// Valid query
|
|
73
|
-
const res1 = await fetch(`http://localhost:${port}/items?page=5&sort=desc`);
|
|
74
|
-
(0, vitest_1.expect)(res1.status).toBe(200);
|
|
75
|
-
const data1 = await res1.json();
|
|
76
|
-
(0, vitest_1.expect)(data1.page).toBe(5);
|
|
77
|
-
(0, vitest_1.expect)(data1.pageType).toBe('number'); // Coercion worked
|
|
78
|
-
(0, vitest_1.expect)(data1.sort).toBe('desc');
|
|
79
|
-
// Invalid query
|
|
80
|
-
const res2 = await fetch(`http://localhost:${port}/items?page=0&sort=foo`);
|
|
81
|
-
(0, vitest_1.expect)(res2.status).toBe(400);
|
|
82
|
-
});
|
|
83
|
-
(0, vitest_1.it)('should support legacy response schema (no validation)', async () => {
|
|
84
|
-
app = new server_1.QHTTPX();
|
|
85
|
-
// Legacy schema: just a JSON schema at root
|
|
86
|
-
const schema = {
|
|
87
|
-
type: 'object',
|
|
88
|
-
properties: {
|
|
89
|
-
hello: { type: 'string' }
|
|
90
|
-
}
|
|
91
|
-
};
|
|
92
|
-
app.get('/legacy', {
|
|
93
|
-
schema,
|
|
94
|
-
handler: (ctx) => {
|
|
95
|
-
// If validation ran, it might fail because ctx.body is undefined or not matching
|
|
96
|
-
// But here we check if it IGNORES validation and uses it for serialization
|
|
97
|
-
ctx.json({ hello: 'world', extra: 'hidden' });
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
|
-
const { port } = await app.listen(0);
|
|
101
|
-
const res = await fetch(`http://localhost:${port}/legacy`);
|
|
102
|
-
(0, vitest_1.expect)(res.status).toBe(200);
|
|
103
|
-
const data = await res.json();
|
|
104
|
-
(0, vitest_1.expect)(data.hello).toBe('world');
|
|
105
|
-
(0, vitest_1.expect)(data.extra).toBeUndefined(); // fast-json-stringify filters extra fields
|
|
106
|
-
});
|
|
107
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
-
};
|
|
38
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
const vitest_1 = require("vitest");
|
|
40
|
-
const net = __importStar(require("net"));
|
|
41
|
-
const ws_1 = __importDefault(require("ws"));
|
|
42
|
-
const src_1 = require("../src");
|
|
43
|
-
(0, vitest_1.describe)('WebSocket upgrade', () => {
|
|
44
|
-
(0, vitest_1.it)('handles websocket upgrade for registered path', async () => {
|
|
45
|
-
const app = new src_1.QHTTPX();
|
|
46
|
-
let upgraded = false;
|
|
47
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
48
|
-
app.upgrade('/ws', (ws, req) => {
|
|
49
|
-
upgraded = true;
|
|
50
|
-
ws.close();
|
|
51
|
-
});
|
|
52
|
-
const { port } = await app.listen(0, '127.0.0.1');
|
|
53
|
-
const ws = new ws_1.default(`ws://127.0.0.1:${port}/ws`);
|
|
54
|
-
await new Promise((resolve, reject) => {
|
|
55
|
-
ws.on('open', () => {
|
|
56
|
-
resolve();
|
|
57
|
-
});
|
|
58
|
-
ws.on('error', (err) => {
|
|
59
|
-
reject(err);
|
|
60
|
-
});
|
|
61
|
-
});
|
|
62
|
-
(0, vitest_1.expect)(upgraded).toBe(true);
|
|
63
|
-
ws.close();
|
|
64
|
-
await app.close();
|
|
65
|
-
});
|
|
66
|
-
(0, vitest_1.it)('destroys socket when no upgrade handler is registered for path', async () => {
|
|
67
|
-
const app = new src_1.QHTTPX();
|
|
68
|
-
const { port } = await app.listen(0, '127.0.0.1');
|
|
69
|
-
const socket = net.createConnection({ port, host: '127.0.0.1' });
|
|
70
|
-
const done = new Promise((resolve) => {
|
|
71
|
-
let finished = false;
|
|
72
|
-
const finish = () => {
|
|
73
|
-
if (!finished) {
|
|
74
|
-
finished = true;
|
|
75
|
-
resolve();
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
|
-
socket.on('end', finish);
|
|
79
|
-
socket.on('close', finish);
|
|
80
|
-
socket.on('error', () => {
|
|
81
|
-
finish();
|
|
82
|
-
});
|
|
83
|
-
setTimeout(finish, 500);
|
|
84
|
-
});
|
|
85
|
-
const requestLines = [
|
|
86
|
-
'GET /no-ws HTTP/1.1',
|
|
87
|
-
'Host: 127.0.0.1',
|
|
88
|
-
'Upgrade: websocket',
|
|
89
|
-
'Connection: Upgrade',
|
|
90
|
-
'',
|
|
91
|
-
'',
|
|
92
|
-
];
|
|
93
|
-
socket.write(requestLines.join('\r\n'));
|
|
94
|
-
await done;
|
|
95
|
-
await app.close();
|
|
96
|
-
});
|
|
97
|
-
(0, vitest_1.it)('supports rooms and broadcasting', async () => {
|
|
98
|
-
const app = new src_1.QHTTPX();
|
|
99
|
-
app.upgrade('/chat', (ws, req) => {
|
|
100
|
-
const url = new URL(req.url || '', 'http://localhost');
|
|
101
|
-
const room = url.searchParams.get('room');
|
|
102
|
-
if (room) {
|
|
103
|
-
ws.join(room);
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
app.post('/broadcast', (ctx) => {
|
|
107
|
-
const { room, message } = ctx.body;
|
|
108
|
-
app.websocket.to(room).emit(message);
|
|
109
|
-
ctx.json({ success: true });
|
|
110
|
-
});
|
|
111
|
-
const { port } = await app.listen(0, '127.0.0.1');
|
|
112
|
-
const baseUrl = `ws://127.0.0.1:${port}/chat`;
|
|
113
|
-
// Client A in room1
|
|
114
|
-
const wsA = new ws_1.default(`${baseUrl}?room=room1`);
|
|
115
|
-
const msgsA = [];
|
|
116
|
-
wsA.on('message', (data) => msgsA.push(data.toString()));
|
|
117
|
-
// Client B in room1
|
|
118
|
-
const wsB = new ws_1.default(`${baseUrl}?room=room1`);
|
|
119
|
-
const msgsB = [];
|
|
120
|
-
wsB.on('message', (data) => msgsB.push(data.toString()));
|
|
121
|
-
// Client C in room2
|
|
122
|
-
const wsC = new ws_1.default(`${baseUrl}?room=room2`);
|
|
123
|
-
const msgsC = [];
|
|
124
|
-
wsC.on('message', (data) => msgsC.push(data.toString()));
|
|
125
|
-
await Promise.all([
|
|
126
|
-
new Promise((resolve) => wsA.on('open', () => resolve())),
|
|
127
|
-
new Promise((resolve) => wsB.on('open', () => resolve())),
|
|
128
|
-
new Promise((resolve) => wsC.on('open', () => resolve())),
|
|
129
|
-
]);
|
|
130
|
-
// Broadcast to room1
|
|
131
|
-
// We need to make a POST request.
|
|
132
|
-
// Since we are inside test, we can use fetch or http.request.
|
|
133
|
-
// Or just use app.websocket directly if we can access it?
|
|
134
|
-
// We can access app.websocket directly here since we have app instance.
|
|
135
|
-
app.websocket.to('room1').emit('hello room1');
|
|
136
|
-
// Wait a bit for messages
|
|
137
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
138
|
-
(0, vitest_1.expect)(msgsA).toContain('hello room1');
|
|
139
|
-
(0, vitest_1.expect)(msgsB).toContain('hello room1');
|
|
140
|
-
(0, vitest_1.expect)(msgsC).not.toContain('hello room1');
|
|
141
|
-
wsA.close();
|
|
142
|
-
wsB.close();
|
|
143
|
-
wsC.close();
|
|
144
|
-
await app.close();
|
|
145
|
-
});
|
|
146
|
-
});
|
package/dist/vitest.config.d.ts
DELETED
package/dist/vitest.config.js
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const config_1 = require("vitest/config");
|
|
4
|
-
exports.default = (0, config_1.defineConfig)({
|
|
5
|
-
test: {
|
|
6
|
-
include: ['tests/**/*.test.ts'],
|
|
7
|
-
exclude: ['dist/**', 'node_modules/**'],
|
|
8
|
-
},
|
|
9
|
-
});
|
package/docs/AEGIS.md
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
# Aegis Rate Limiter
|
|
2
|
-
|
|
3
|
-
**Aegis** is a high-performance, Token Bucket-based rate limiter built directly into QHTTPX. It is designed to protect your application from DDoS attacks, brute-force attempts, and API abuse while maintaining ultra-low latency.
|
|
4
|
-
|
|
5
|
-
## Features
|
|
6
|
-
|
|
7
|
-
- **Token Bucket Algorithm**: Smooths out traffic spikes while enforcing strict limits.
|
|
8
|
-
- **Zero Dependencies**: Uses an efficient in-memory `Map` by default.
|
|
9
|
-
- **Standard Compliance**: Automatically sets `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `Retry-After` headers.
|
|
10
|
-
- **Flexible Keys**: Limit by IP, API Key, User ID, or any custom logic.
|
|
11
|
-
- **Extensible Storage**: Interface-ready for Redis or other distributed stores.
|
|
12
|
-
|
|
13
|
-
## Usage
|
|
14
|
-
|
|
15
|
-
### Zero-Config (Recommended)
|
|
16
|
-
|
|
17
|
-
The easiest way to enable Aegis is via the new fluent API:
|
|
18
|
-
|
|
19
|
-
```typescript
|
|
20
|
-
import { app } from 'qhttpx';
|
|
21
|
-
|
|
22
|
-
// 1. Presets
|
|
23
|
-
app.rateLimit('standard'); // 100 req / 15 min
|
|
24
|
-
|
|
25
|
-
// 2. Custom Fluent API
|
|
26
|
-
app.allow(500).per('minute');
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
### Manual Middleware
|
|
30
|
-
|
|
31
|
-
For specific routes or more control:
|
|
32
|
-
|
|
33
|
-
```typescript
|
|
34
|
-
import { app, rateLimit } from 'qhttpx';
|
|
35
|
-
|
|
36
|
-
// Global
|
|
37
|
-
app.use(rateLimit({
|
|
38
|
-
windowMs: 60 * 1000,
|
|
39
|
-
max: 100
|
|
40
|
-
}));
|
|
41
|
-
|
|
42
|
-
// Per Route
|
|
43
|
-
app.post('/login', rateLimit({ max: 5 }), async () => { ... });
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
## Proxy Support
|
|
47
|
-
|
|
48
|
-
If your app sits behind a reverse proxy (Nginx, AWS ALB, Cloudflare), you must configure **Proxy Trust** so Aegis sees the real user IP instead of the load balancer's IP.
|
|
49
|
-
|
|
50
|
-
```typescript
|
|
51
|
-
app.security({
|
|
52
|
-
cors: true,
|
|
53
|
-
rateLimit: { trustProxy: true }
|
|
54
|
-
});
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
## Options
|
|
58
|
-
|
|
59
|
-
| Option | Type | Default | Description |
|
|
60
|
-
|Params|---|---|---|
|
|
61
|
-
| `windowMs` | `number` | `60000` | Time frame for which requests are checked/remembered. |
|
|
62
|
-
| `max` | `number` | `100` | Max number of connections during `windowMs` before sending a 429 response. |
|
|
63
|
-
| `message` | `string` \| `object` | `"Too many requests..."` | Error message returned to the client. |
|
|
64
|
-
| `statusCode` | `number` | `429` | HTTP status code returned. |
|
|
65
|
-
| `headers` | `boolean` | `true` | Enable/disable `X-RateLimit-*` headers. |
|
|
66
|
-
| `keyGenerator` | `(ctx) => string` | `IP Address` | Function to generate a unique key for the client. |
|
|
67
|
-
| `skip` | `(ctx) => boolean` | `undefined` | Function to bypass the limiter. |
|
|
68
|
-
| `store` | `RateLimitStore` | `MemoryStore` | Custom storage backend (e.g., Redis). |
|
|
69
|
-
|
|
70
|
-
## Custom Storage (Redis Example)
|
|
71
|
-
|
|
72
|
-
You can easily swap the in-memory store for Redis to share limits across a cluster:
|
|
73
|
-
|
|
74
|
-
```typescript
|
|
75
|
-
import { RateLimitStore } from 'qhttpx/middleware';
|
|
76
|
-
|
|
77
|
-
class RedisStore implements RateLimitStore {
|
|
78
|
-
constructor(private redis: RedisClient) {}
|
|
79
|
-
|
|
80
|
-
async increment(key: string, windowMs: number) {
|
|
81
|
-
// Implement Redis INCR + EXPIRE logic
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
async decrement(key: string) { ... }
|
|
85
|
-
async reset(key: string) { ... }
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
app.use(rateLimit({
|
|
89
|
-
store: new RedisStore(redisClient)
|
|
90
|
-
}));
|
|
91
|
-
```
|