qhttpx 1.8.5 → 1.8.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (161) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +72 -52
  3. package/binding.gyp +18 -0
  4. package/dist/examples/api-server.js +29 -8
  5. package/dist/examples/basic.d.ts +1 -0
  6. package/dist/examples/basic.js +10 -0
  7. package/dist/examples/compression.d.ts +1 -0
  8. package/dist/examples/compression.js +17 -0
  9. package/dist/examples/cors.d.ts +1 -0
  10. package/dist/examples/cors.js +19 -0
  11. package/dist/examples/errors.d.ts +1 -0
  12. package/dist/examples/errors.js +25 -0
  13. package/dist/examples/file-upload.d.ts +1 -0
  14. package/dist/examples/file-upload.js +24 -0
  15. package/dist/examples/fusion.d.ts +1 -0
  16. package/dist/examples/fusion.js +21 -0
  17. package/dist/examples/rate-limiting.d.ts +1 -0
  18. package/dist/examples/rate-limiting.js +17 -0
  19. package/dist/examples/validation.d.ts +1 -0
  20. package/dist/examples/validation.js +23 -0
  21. package/dist/examples/websockets.d.ts +1 -0
  22. package/dist/examples/websockets.js +20 -0
  23. package/dist/package.json +11 -1
  24. package/dist/src/benchmarks/simple-json.js +6 -4
  25. package/dist/src/cli/index.js +33 -11
  26. package/dist/src/core/errors.d.ts +34 -0
  27. package/dist/src/core/errors.js +70 -0
  28. package/dist/src/core/native-adapter.d.ts +11 -0
  29. package/dist/src/core/native-adapter.js +211 -0
  30. package/dist/src/core/server.d.ts +52 -4
  31. package/dist/src/core/server.js +389 -261
  32. package/dist/src/core/types.d.ts +37 -0
  33. package/dist/src/index.d.ts +6 -1
  34. package/dist/src/index.js +19 -3
  35. package/dist/src/middleware/compression.d.ts +1 -5
  36. package/dist/src/middleware/cors.d.ts +1 -10
  37. package/dist/src/middleware/presets.d.ts +4 -1
  38. package/dist/src/middleware/presets.js +22 -3
  39. package/dist/src/middleware/rate-limit.d.ts +1 -19
  40. package/dist/src/middleware/rate-limit.js +6 -0
  41. package/dist/src/middleware/security.d.ts +1 -2
  42. package/dist/src/native/index.d.ts +29 -0
  43. package/dist/src/native/index.js +64 -0
  44. package/dist/src/router/radix-tree.d.ts +2 -0
  45. package/dist/src/router/radix-tree.js +54 -4
  46. package/dist/src/router/router.d.ts +1 -0
  47. package/dist/src/router/router.js +42 -2
  48. package/dist/tests/native-adapter.test.d.ts +1 -0
  49. package/dist/tests/native-adapter.test.js +71 -0
  50. package/dist/tests/resources.test.js +3 -0
  51. package/dist/tests/security.test.js +2 -2
  52. package/docs/AEGIS.md +34 -9
  53. package/docs/BENCHMARKS.md +8 -7
  54. package/docs/ERRORS.md +112 -0
  55. package/docs/FUSION.md +68 -0
  56. package/docs/MIDDLEWARE.md +65 -0
  57. package/docs/ROUTING.md +70 -0
  58. package/docs/STATIC.md +61 -0
  59. package/docs/WEBSOCKETS.md +76 -0
  60. package/package.json +11 -1
  61. package/src/native/README.md +31 -0
  62. package/src/native/addon.cc +8 -0
  63. package/src/native/index.ts +78 -0
  64. package/src/native/picohttpparser.c +608 -0
  65. package/src/native/picohttpparser.h +71 -0
  66. package/src/native/server.cc +262 -0
  67. package/src/native/server.h +30 -0
  68. package/.eslintrc.json +0 -22
  69. package/.github/workflows/ci.yml +0 -32
  70. package/.github/workflows/npm-publish.yml +0 -37
  71. package/.github/workflows/release.yml +0 -21
  72. package/.prettierrc +0 -7
  73. package/assets/logo.svg +0 -25
  74. package/eslint.config.cjs +0 -26
  75. package/examples/api-server.ts +0 -62
  76. package/src/benchmarks/quantam-users.ts +0 -70
  77. package/src/benchmarks/simple-json.ts +0 -71
  78. package/src/benchmarks/ultra-mode.ts +0 -127
  79. package/src/cli/index.ts +0 -214
  80. package/src/client/index.ts +0 -93
  81. package/src/core/batch.ts +0 -110
  82. package/src/core/body-parser.ts +0 -151
  83. package/src/core/buffer-pool.ts +0 -96
  84. package/src/core/config.ts +0 -60
  85. package/src/core/fusion.ts +0 -210
  86. package/src/core/logger.ts +0 -70
  87. package/src/core/metrics.ts +0 -166
  88. package/src/core/resources.ts +0 -38
  89. package/src/core/scheduler.ts +0 -126
  90. package/src/core/scope.ts +0 -87
  91. package/src/core/serializer.ts +0 -41
  92. package/src/core/server.ts +0 -1234
  93. package/src/core/stream.ts +0 -111
  94. package/src/core/tasks.ts +0 -138
  95. package/src/core/types.ts +0 -192
  96. package/src/core/websocket.ts +0 -112
  97. package/src/core/worker-queue.ts +0 -90
  98. package/src/database/adapters/memory.ts +0 -99
  99. package/src/database/adapters/mongo.ts +0 -116
  100. package/src/database/adapters/postgres.ts +0 -86
  101. package/src/database/adapters/sqlite.ts +0 -44
  102. package/src/database/coalescer.ts +0 -153
  103. package/src/database/manager.ts +0 -97
  104. package/src/database/types.ts +0 -24
  105. package/src/index.ts +0 -58
  106. package/src/middleware/compression.ts +0 -147
  107. package/src/middleware/cors.ts +0 -98
  108. package/src/middleware/presets.ts +0 -50
  109. package/src/middleware/rate-limit.ts +0 -106
  110. package/src/middleware/security.ts +0 -109
  111. package/src/middleware/static.ts +0 -216
  112. package/src/openapi/generator.ts +0 -167
  113. package/src/router/radix-router.ts +0 -119
  114. package/src/router/radix-tree.ts +0 -106
  115. package/src/router/router.ts +0 -190
  116. package/src/testing/index.ts +0 -104
  117. package/src/utils/cookies.ts +0 -67
  118. package/src/utils/logger.ts +0 -59
  119. package/src/utils/signals.ts +0 -45
  120. package/src/utils/sse.ts +0 -41
  121. package/src/validation/index.ts +0 -3
  122. package/src/validation/simple.ts +0 -93
  123. package/src/validation/types.ts +0 -38
  124. package/src/validation/zod.ts +0 -14
  125. package/src/views/index.ts +0 -1
  126. package/src/views/types.ts +0 -4
  127. package/tests/adapters.test.ts +0 -120
  128. package/tests/batch.test.ts +0 -139
  129. package/tests/body-parser.test.ts +0 -83
  130. package/tests/compression-sse.test.ts +0 -98
  131. package/tests/cookies.test.ts +0 -74
  132. package/tests/cors.test.ts +0 -79
  133. package/tests/database.test.ts +0 -90
  134. package/tests/dx.test.ts +0 -130
  135. package/tests/ecosystem.test.ts +0 -156
  136. package/tests/features.test.ts +0 -51
  137. package/tests/fusion.test.ts +0 -121
  138. package/tests/http-basic.test.ts +0 -161
  139. package/tests/logger.test.ts +0 -48
  140. package/tests/middleware.test.ts +0 -137
  141. package/tests/observability.test.ts +0 -91
  142. package/tests/openapi.test.ts +0 -74
  143. package/tests/plugin.test.ts +0 -85
  144. package/tests/plugins.test.ts +0 -93
  145. package/tests/rate-limit.test.ts +0 -97
  146. package/tests/resources.test.ts +0 -64
  147. package/tests/scheduler.test.ts +0 -71
  148. package/tests/schema-routes.test.ts +0 -89
  149. package/tests/security.test.ts +0 -128
  150. package/tests/server-db.test.ts +0 -72
  151. package/tests/smoke.test.ts +0 -9
  152. package/tests/sqlite-fusion.test.ts +0 -106
  153. package/tests/static.test.ts +0 -111
  154. package/tests/stream.test.ts +0 -58
  155. package/tests/task-metrics.test.ts +0 -78
  156. package/tests/tasks.test.ts +0 -90
  157. package/tests/testing.test.ts +0 -53
  158. package/tests/validation.test.ts +0 -126
  159. package/tests/websocket.test.ts +0 -132
  160. package/tsconfig.json +0 -17
  161. package/vitest.config.ts +0 -9
@@ -1,111 +0,0 @@
1
- import { Readable } from 'stream';
2
- import { QHTTPXContext } from './types';
3
-
4
- export type SseOptions = {
5
- retryMs?: number;
6
- };
7
-
8
- export type SseStream = {
9
- send: (data: unknown, event?: string) => void;
10
- close: () => void;
11
- };
12
-
13
- export function createSseStream(
14
- ctx: QHTTPXContext,
15
- options: SseOptions = {},
16
- ): SseStream {
17
- const res = ctx.res;
18
-
19
- if (!res.headersSent) {
20
- res.statusCode = 200;
21
- res.setHeader('content-type', 'text/event-stream; charset=utf-8');
22
- res.setHeader('cache-control', 'no-cache');
23
- res.setHeader('connection', 'keep-alive');
24
- }
25
-
26
- const anyRes = res as unknown as {
27
- flushHeaders?: () => void;
28
- flush?: () => void;
29
- };
30
- if (anyRes.flushHeaders) {
31
- anyRes.flushHeaders();
32
- }
33
-
34
- if (typeof options.retryMs === 'number') {
35
- res.write(`retry: ${options.retryMs}\n\n`);
36
- }
37
-
38
- const send = (data: unknown, event?: string) => {
39
- if (res.writableEnded) {
40
- return;
41
- }
42
-
43
- const payload =
44
- typeof data === 'string' ? data : JSON.stringify(data);
45
-
46
- let chunk = '';
47
- if (event) {
48
- chunk += `event: ${event}\n`;
49
- }
50
- chunk += `data: ${payload}\n\n`;
51
-
52
- res.write(chunk);
53
-
54
- if (anyRes.flush) {
55
- anyRes.flush();
56
- }
57
- };
58
-
59
- const close = () => {
60
- if (!res.writableEnded) {
61
- res.end();
62
- }
63
- };
64
-
65
- return { send, close };
66
- }
67
-
68
- export type StreamOptions = {
69
- contentType?: string;
70
- status?: number;
71
- };
72
-
73
- export function sendStream(
74
- ctx: QHTTPXContext,
75
- stream: Readable,
76
- options: StreamOptions = {},
77
- ): Promise<void> {
78
- const res = ctx.res;
79
-
80
- if (!res.headersSent) {
81
- if (options.status !== undefined) {
82
- res.statusCode = options.status;
83
- }
84
- if (options.contentType) {
85
- res.setHeader('content-type', options.contentType);
86
- }
87
- }
88
-
89
- return new Promise((resolve, reject) => {
90
- stream.on('error', (err) => {
91
- if (!res.headersSent) {
92
- res.statusCode = 500;
93
- res.setHeader('content-type', 'text/plain; charset=utf-8');
94
- }
95
- if (!res.writableEnded) {
96
- res.end('Internal Server Error');
97
- }
98
- reject(err);
99
- });
100
-
101
- stream.on('end', () => {
102
- if (!res.writableEnded) {
103
- res.end();
104
- }
105
- resolve();
106
- });
107
-
108
- stream.pipe(res, { end: false });
109
- });
110
- }
111
-
package/src/core/tasks.ts DELETED
@@ -1,138 +0,0 @@
1
- import { Scheduler } from './scheduler';
2
-
3
- export type QHTTPXTaskHandler = (payload: unknown) => void | Promise<void>;
4
-
5
- export type QHTTPXTaskOptions = {
6
- maxRetries?: number;
7
- backoffMs?: number;
8
- };
9
-
10
- export type TaskMetrics = {
11
- registeredTasks: number;
12
- totalEnqueued: number;
13
- totalCompleted: number;
14
- totalFailed: number;
15
- totalOverloaded: number;
16
- totalRetried: number;
17
- };
18
-
19
- type TaskDefinition = {
20
- name: string;
21
- handler: QHTTPXTaskHandler;
22
- options: QHTTPXTaskOptions;
23
- };
24
-
25
- export class TaskEngine {
26
- private readonly scheduler: Scheduler;
27
-
28
- private readonly tasks = new Map<string, TaskDefinition>();
29
-
30
- private registeredTasksCount = 0;
31
-
32
- private totalEnqueued = 0;
33
-
34
- private totalCompleted = 0;
35
-
36
- private totalFailed = 0;
37
-
38
- private totalOverloaded = 0;
39
-
40
- private totalRetried = 0;
41
-
42
- constructor(scheduler: Scheduler) {
43
- this.scheduler = scheduler;
44
- }
45
-
46
- register(
47
- name: string,
48
- handler: QHTTPXTaskHandler,
49
- options: QHTTPXTaskOptions = {},
50
- ): void {
51
- if (!this.tasks.has(name)) {
52
- this.registeredTasksCount += 1;
53
- }
54
- this.tasks.set(name, {
55
- name,
56
- handler,
57
- options,
58
- });
59
- }
60
-
61
- async enqueue(name: string, payload: unknown): Promise<void> {
62
- const def = this.tasks.get(name);
63
- if (!def) {
64
- throw new Error(`Task "${name}" is not registered`);
65
- }
66
-
67
- this.totalEnqueued += 1;
68
- await this.executeWithRetry(def, payload);
69
- }
70
-
71
- getMetrics(): TaskMetrics {
72
- return {
73
- registeredTasks: this.registeredTasksCount,
74
- totalEnqueued: this.totalEnqueued,
75
- totalCompleted: this.totalCompleted,
76
- totalFailed: this.totalFailed,
77
- totalOverloaded: this.totalOverloaded,
78
- totalRetried: this.totalRetried,
79
- };
80
- }
81
-
82
- private async executeWithRetry(
83
- def: TaskDefinition,
84
- payload: unknown,
85
- ): Promise<void> {
86
- const maxRetries = def.options.maxRetries ?? 0;
87
- const backoffMs = def.options.backoffMs ?? 0;
88
-
89
- let attempt = 0;
90
-
91
- for (;;) {
92
- let overloaded = false;
93
- let error: unknown;
94
-
95
- await this.scheduler.run(
96
- async () => {
97
- try {
98
- await def.handler(payload);
99
- } catch (err) {
100
- error = err;
101
- }
102
- },
103
- {
104
- onOverloaded: () => {
105
- overloaded = true;
106
- },
107
- },
108
- );
109
-
110
- if (!overloaded && !error) {
111
- this.totalCompleted += 1;
112
- return;
113
- }
114
-
115
- if (attempt >= maxRetries) {
116
- if (error) {
117
- this.totalFailed += 1;
118
- throw error;
119
- }
120
- if (overloaded) {
121
- this.totalOverloaded += 1;
122
- throw new Error(`Task "${def.name}" overloaded`);
123
- }
124
- return;
125
- }
126
-
127
- attempt += 1;
128
- this.totalRetried += 1;
129
-
130
- if (backoffMs > 0) {
131
- await new Promise<void>((resolve) => {
132
- setTimeout(resolve, backoffMs);
133
- });
134
- }
135
- }
136
- }
137
- }
138
-
package/src/core/types.ts DELETED
@@ -1,192 +0,0 @@
1
- import { IncomingMessage, ServerResponse, IncomingHttpHeaders } from 'http';
2
- import { URL } from 'url';
3
- import type { BufferPool, BufferPoolConfig } from './buffer-pool';
4
- import type { DatabaseManager } from '../database/manager';
5
- import type { RouteSchema, Validator } from '../validation/types';
6
- import type { ViewEngine } from '../views/types';
7
- import type { RequestFusionOptions } from './fusion';
8
-
9
- export type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS';
10
-
11
- export enum RoutePriority {
12
- CRITICAL = 'critical',
13
- STANDARD = 'standard',
14
- BEST_EFFORT = 'best-effort',
15
- }
16
-
17
- export type RouteOptions = {
18
- priority?: RoutePriority;
19
- };
20
-
21
- export class HttpError extends Error {
22
- status: number;
23
-
24
- code?: string;
25
-
26
- details?: unknown;
27
-
28
- constructor(
29
- status: number,
30
- message?: string,
31
- options: { code?: string; details?: unknown } = {},
32
- ) {
33
- super(message ?? 'HTTP Error');
34
- this.status = status;
35
- this.code = options.code;
36
- this.details = options.details;
37
- Object.setPrototypeOf(this, new.target.prototype);
38
- }
39
- }
40
-
41
- export type CookieOptions = {
42
- domain?: string;
43
- path?: string;
44
- expires?: Date;
45
- maxAge?: number;
46
- httpOnly?: boolean;
47
- secure?: boolean;
48
- sameSite?: 'lax' | 'strict' | 'none';
49
- };
50
-
51
- export type QHTTPXFile = {
52
- filename: string;
53
- encoding: string;
54
- mimeType: string;
55
- data: Buffer;
56
- size: number;
57
- };
58
-
59
- export type QHTTPXContext = {
60
- readonly req: IncomingMessage;
61
- readonly res: ServerResponse;
62
- readonly headers: IncomingHttpHeaders;
63
- readonly url: URL;
64
- readonly params: Record<string, string>;
65
- readonly query: Record<string, string | string[]>;
66
- body: unknown;
67
- files?: Record<string, QHTTPXFile | QHTTPXFile[]>;
68
- readonly cookies: Record<string, string>;
69
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
70
- state: Record<string, any>;
71
- readonly bufferPool: BufferPool;
72
- requestId: string | undefined;
73
- requestStart: number | undefined;
74
- // Serializer for the current route
75
- serializer?: (value: unknown) => string;
76
- readonly json: (body: unknown, status?: number) => void;
77
- readonly send: (body: string | Buffer, status?: number) => void;
78
- readonly html: (html: string, status?: number) => void;
79
- readonly redirect: (url: string, status?: number) => void;
80
- readonly setCookie: (name: string, value: string, options?: CookieOptions) => void;
81
- readonly db?: DatabaseManager;
82
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
83
- readonly call?: (op: string, params: any) => Promise<any>;
84
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
85
- readonly render: (view: string, locals?: Record<string, any>) => Promise<void>;
86
- readonly validate: <T = unknown>(schema: unknown, data?: unknown) => Promise<T>;
87
- disableAutoEnd?: boolean;
88
- readonly path: string;
89
- readonly next?: () => Promise<void>;
90
- };
91
-
92
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
93
- export type QHTTPXOpHandler = (params: any, ctx: QHTTPXContext) => any | Promise<any>;
94
-
95
- export type QHTTPXHandler = (ctx: QHTTPXContext) => void | Promise<void>;
96
-
97
- export type QHTTPXRouteOptions = {
98
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
99
- schema?: RouteSchema | Record<string, any>;
100
- handler: QHTTPXHandler;
101
- priority?: RoutePriority;
102
- };
103
-
104
- export type QHTTPXRouteConfig = Omit<QHTTPXRouteOptions, 'handler'>;
105
-
106
-
107
- export type QHTTPXMiddleware = (
108
- ctx: QHTTPXContext,
109
- next: () => Promise<void>,
110
- ) => void | Promise<void>;
111
-
112
- export type QHTTPXErrorContext = QHTTPXContext & { error: unknown };
113
-
114
- export type QHTTPXErrorHandler = (
115
- ctx: QHTTPXErrorContext,
116
- ) => void | Promise<void>;
117
-
118
- export type QHTTPXNotFoundHandler = (
119
- ctx: QHTTPXContext,
120
- ) => void | Promise<void>;
121
-
122
- export type QHTTPXMethodNotAllowedHandler = (
123
- ctx: QHTTPXContext,
124
- allowedMethods: HTTPMethod[],
125
- ) => void | Promise<void>;
126
-
127
- export type QHTTPXTraceEvent =
128
- | {
129
- type: 'request_start';
130
- method: string;
131
- path: string;
132
- requestId?: string;
133
- }
134
- | {
135
- type: 'request_end';
136
- method: string;
137
- path: string;
138
- statusCode: number;
139
- durationMs: number;
140
- requestId?: string;
141
- };
142
-
143
- export type QHTTPXTracer = (
144
- event: QHTTPXTraceEvent,
145
- ) => void | Promise<void>;
146
-
147
- export type QHTTPXTaskHandler = (payload: unknown) => void | Promise<void>;
148
-
149
- export type QHTTPXTaskOptions = {
150
- maxRetries?: number;
151
- backoffMs?: number;
152
- };
153
-
154
- export type PerformanceMode = 'balanced' | 'ultra';
155
-
156
- export type QHTTPXOptions = {
157
- name?: string;
158
- workers?: 'auto' | number;
159
- maxConcurrency?: number;
160
- requestTimeoutMs?: number;
161
- maxMemoryBytes?: number;
162
- maxBodyBytes?: number;
163
- keepAliveTimeoutMs?: number;
164
- headersTimeoutMs?: number;
165
- metricsEnabled?: boolean;
166
- jsonSerializer?: (value: unknown) => string | Buffer;
167
- bufferPoolConfig?: BufferPoolConfig;
168
- errorHandler?: QHTTPXErrorHandler;
169
- notFoundHandler?: QHTTPXNotFoundHandler;
170
- methodNotAllowedHandler?: QHTTPXMethodNotAllowedHandler;
171
- tracer?: QHTTPXTracer;
172
- performanceMode?: PerformanceMode;
173
- database?: DatabaseManager;
174
- enableBatching?: boolean | { endpoint: string };
175
- validator?: Validator;
176
- enableRequestFusion?: boolean | RequestFusionOptions;
177
- viewEngine?: ViewEngine;
178
- viewsPath?: string;
179
- };
180
-
181
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
182
- export type QHTTPXPlugin<Options = any> = (
183
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
184
- app: any, // We use 'any' here to avoid circular dependency with QHTTPX class, or we could use an interface
185
- options: Options
186
- ) => void | Promise<void>;
187
-
188
- export type QHTTPXPluginOptions = {
189
- prefix?: string;
190
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
191
- [key: string]: any;
192
- };
@@ -1,112 +0,0 @@
1
- import { IncomingMessage } from 'http';
2
- import { Duplex } from 'stream';
3
- import { URL } from 'url';
4
- import { WebSocket, WebSocketServer } from 'ws';
5
-
6
- export interface QHTTPXWebSocket extends WebSocket {
7
- join(room: string): void;
8
- leave(room: string): void;
9
- id: string;
10
- }
11
-
12
- export type WSHandler = (ws: QHTTPXWebSocket, req: IncomingMessage) => void;
13
-
14
- export class WebSocketManager {
15
- private readonly wss: WebSocketServer;
16
- private readonly handlers: { path: string; handler: WSHandler }[] = [];
17
- private readonly rooms: Map<string, Set<QHTTPXWebSocket>> = new Map();
18
-
19
- constructor(private readonly requestIdGenerator: () => string) {
20
- this.wss = new WebSocketServer({ noServer: true });
21
- }
22
-
23
- register(path: string, handler: WSHandler): void {
24
- this.handlers.push({ path, handler });
25
- }
26
-
27
- async handleUpgrade(
28
- req: IncomingMessage,
29
- socket: Duplex,
30
- head: Buffer,
31
- ): Promise<void> {
32
- const rawUrl = req.url || '/';
33
- const host = req.headers.host || 'localhost';
34
- const urlObj = new URL(rawUrl, `http://${host}`);
35
- const path = urlObj.pathname;
36
-
37
- const handlerEntry = this.handlers.find((entry) => entry.path === path);
38
-
39
- if (!handlerEntry) {
40
- socket.destroy();
41
- return;
42
- }
43
-
44
- this.wss.handleUpgrade(req, socket, head, (ws) => {
45
- const qws = ws as QHTTPXWebSocket;
46
- qws.id = this.requestIdGenerator();
47
-
48
- qws.join = (room: string) => this.join(room, qws);
49
- qws.leave = (room: string) => this.leave(room, qws);
50
-
51
- qws.on('close', () => {
52
- this.leaveAll(qws);
53
- });
54
-
55
- handlerEntry.handler(qws, req);
56
- });
57
- }
58
-
59
- private join(room: string, ws: QHTTPXWebSocket) {
60
- if (!this.rooms.has(room)) {
61
- this.rooms.set(room, new Set());
62
- }
63
- this.rooms.get(room)!.add(ws);
64
- }
65
-
66
- private leave(room: string, ws: QHTTPXWebSocket) {
67
- const set = this.rooms.get(room);
68
- if (set) {
69
- set.delete(ws);
70
- if (set.size === 0) {
71
- this.rooms.delete(room);
72
- }
73
- }
74
- }
75
-
76
- private leaveAll(ws: QHTTPXWebSocket) {
77
- for (const [room, set] of this.rooms) {
78
- if (set.has(ws)) {
79
- set.delete(ws);
80
- if (set.size === 0) {
81
- this.rooms.delete(room);
82
- }
83
- }
84
- }
85
- }
86
-
87
- public to(room: string) {
88
- return {
89
- emit: (data: unknown) => {
90
- const set = this.rooms.get(room);
91
- if (set) {
92
- const payload =
93
- typeof data === 'string' ? data : JSON.stringify(data);
94
- for (const client of set) {
95
- if (client.readyState === WebSocket.OPEN) {
96
- client.send(payload);
97
- }
98
- }
99
- }
100
- },
101
- };
102
- }
103
-
104
- public broadcast(data: unknown) {
105
- const payload = typeof data === 'string' ? data : JSON.stringify(data);
106
- for (const client of this.wss.clients) {
107
- if (client.readyState === WebSocket.OPEN) {
108
- client.send(payload);
109
- }
110
- }
111
- }
112
- }
@@ -1,90 +0,0 @@
1
- /**
2
- * Lock-free (or lock-minimal) work queue for per-worker task distribution.
3
- * Uses a simple ring buffer for high throughput.
4
- */
5
-
6
- export type WorkItem<T> = {
7
- id: number;
8
- task: T;
9
- priority: number;
10
- };
11
-
12
- export class WorkerQueue<T> {
13
- private readonly capacity: number;
14
- private readonly buffer: (WorkItem<T> | undefined)[];
15
-
16
- private writeIndex = 0;
17
- private readIndex = 0;
18
- private size = 0;
19
-
20
- constructor(capacity: number = 1024) {
21
- if (capacity <= 0 || !Number.isInteger(capacity)) {
22
- throw new Error('Capacity must be a positive integer');
23
- }
24
- // Ensure capacity is a power of 2 for efficient modulo with bitmask
25
- this.capacity = Math.pow(2, Math.ceil(Math.log2(capacity)));
26
- this.buffer = new Array(this.capacity);
27
- }
28
-
29
- /**
30
- * Enqueue a work item. Returns true if successful, false if queue is full.
31
- */
32
- enqueue(item: WorkItem<T>): boolean {
33
- if (this.size >= this.capacity) {
34
- return false;
35
- }
36
-
37
- this.buffer[this.writeIndex] = item;
38
- this.writeIndex = (this.writeIndex + 1) & (this.capacity - 1);
39
- this.size += 1;
40
-
41
- return true;
42
- }
43
-
44
- /**
45
- * Dequeue a work item. Returns undefined if queue is empty.
46
- */
47
- dequeue(): WorkItem<T> | undefined {
48
- if (this.size <= 0) {
49
- return undefined;
50
- }
51
-
52
- const item = this.buffer[this.readIndex];
53
- this.buffer[this.readIndex] = undefined;
54
- this.readIndex = (this.readIndex + 1) & (this.capacity - 1);
55
- this.size -= 1;
56
-
57
- return item;
58
- }
59
-
60
- /**
61
- * Peek at the next item without removing it.
62
- */
63
- peek(): WorkItem<T> | undefined {
64
- if (this.size <= 0) {
65
- return undefined;
66
- }
67
- return this.buffer[this.readIndex];
68
- }
69
-
70
- /**
71
- * Check if the queue is empty.
72
- */
73
- isEmpty(): boolean {
74
- return this.size === 0;
75
- }
76
-
77
- /**
78
- * Get the current size of the queue.
79
- */
80
- getSize(): number {
81
- return this.size;
82
- }
83
-
84
- /**
85
- * Get the capacity of the queue.
86
- */
87
- getCapacity(): number {
88
- return this.capacity;
89
- }
90
- }