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,96 +0,0 @@
1
- /**
2
- * Buffer pool for response bodies (small, medium, large).
3
- * Reuses allocated buffers to reduce GC pressure.
4
- */
5
-
6
- export type BufferPoolConfig = {
7
- smallSize?: number; // Default: 4KB
8
- mediumSize?: number; // Default: 64KB
9
- largeSize?: number; // Default: 256KB
10
- smallPoolSize?: number; // Number of small buffers to maintain
11
- mediumPoolSize?: number; // Number of medium buffers to maintain
12
- largePoolSize?: number; // Number of large buffers to maintain
13
- };
14
-
15
- export class BufferPool {
16
- private readonly smallSize: number;
17
- private readonly mediumSize: number;
18
- private readonly largeSize: number;
19
-
20
- private readonly smallBuffers: Buffer[] = [];
21
- private readonly mediumBuffers: Buffer[] = [];
22
- private readonly largeBuffers: Buffer[] = [];
23
-
24
- private readonly smallPoolSize: number;
25
- private readonly mediumPoolSize: number;
26
- private readonly largePoolSize: number;
27
-
28
- constructor(config: BufferPoolConfig = {}) {
29
- this.smallSize = config.smallSize ?? 4096; // 4KB
30
- this.mediumSize = config.mediumSize ?? 65536; // 64KB
31
- this.largeSize = config.largeSize ?? 262144; // 256KB
32
-
33
- this.smallPoolSize = config.smallPoolSize ?? 32;
34
- this.mediumPoolSize = config.mediumPoolSize ?? 8;
35
- this.largePoolSize = config.largePoolSize ?? 2;
36
-
37
- // Preallocate buffers
38
- for (let i = 0; i < this.smallPoolSize; i += 1) {
39
- this.smallBuffers.push(Buffer.allocUnsafe(this.smallSize));
40
- }
41
- for (let i = 0; i < this.mediumPoolSize; i += 1) {
42
- this.mediumBuffers.push(Buffer.allocUnsafe(this.mediumSize));
43
- }
44
- for (let i = 0; i < this.largePoolSize; i += 1) {
45
- this.largeBuffers.push(Buffer.allocUnsafe(this.largeSize));
46
- }
47
- }
48
-
49
- /**
50
- * Acquire a buffer suitable for the given size.
51
- * Returns a buffer from the appropriate pool.
52
- */
53
- acquire(size: number): Buffer {
54
- if (size <= this.smallSize) {
55
- return this.smallBuffers.pop() || Buffer.allocUnsafe(this.smallSize);
56
- }
57
- if (size <= this.mediumSize) {
58
- return this.mediumBuffers.pop() || Buffer.allocUnsafe(this.mediumSize);
59
- }
60
- return this.largeBuffers.pop() || Buffer.allocUnsafe(this.largeSize);
61
- }
62
-
63
- /**
64
- * Release a buffer back to the appropriate pool.
65
- */
66
- release(buffer: Buffer): void {
67
- if (buffer.length === this.smallSize && this.smallBuffers.length < this.smallPoolSize) {
68
- this.smallBuffers.push(buffer);
69
- } else if (
70
- buffer.length === this.mediumSize &&
71
- this.mediumBuffers.length < this.mediumPoolSize
72
- ) {
73
- this.mediumBuffers.push(buffer);
74
- } else if (
75
- buffer.length === this.largeSize &&
76
- this.largeBuffers.length < this.largePoolSize
77
- ) {
78
- this.largeBuffers.push(buffer);
79
- }
80
- }
81
-
82
- /**
83
- * Get the number of available buffers in each pool.
84
- */
85
- getPoolStatus(): {
86
- small: number;
87
- medium: number;
88
- large: number;
89
- } {
90
- return {
91
- small: this.smallBuffers.length,
92
- medium: this.mediumBuffers.length,
93
- large: this.largeBuffers.length,
94
- };
95
- }
96
- }
@@ -1,60 +0,0 @@
1
- import type { QHTTPXOptions } from './types';
2
-
3
- export type LoadConfigOptions = {
4
- env?: NodeJS.ProcessEnv;
5
- defaults?: QHTTPXOptions;
6
- prefix?: string;
7
- };
8
-
9
- export function loadConfig(options: LoadConfigOptions = {}): QHTTPXOptions {
10
- const env = options.env ?? process.env;
11
- const defaults = options.defaults ?? {};
12
- const prefix = options.prefix ?? 'QHTTPX_';
13
-
14
- const getNumber = (key: string): number | undefined => {
15
- const value = env[prefix + key];
16
- if (!value) {
17
- return undefined;
18
- }
19
- const n = Number(value);
20
- if (!Number.isFinite(n) || n <= 0) {
21
- return undefined;
22
- }
23
- return n;
24
- };
25
-
26
- const getBoolean = (key: string): boolean | undefined => {
27
- const value = env[prefix + key];
28
- if (!value) {
29
- return undefined;
30
- }
31
- const lower = value.toLowerCase();
32
- if (lower === 'true' || lower === '1') {
33
- return true;
34
- }
35
- if (lower === 'false' || lower === '0') {
36
- return false;
37
- }
38
- return undefined;
39
- };
40
-
41
- const maxConcurrency = getNumber('MAX_CONCURRENCY');
42
- const requestTimeoutMs = getNumber('REQUEST_TIMEOUT_MS');
43
- const maxMemoryBytes = getNumber('MAX_MEMORY_BYTES');
44
- const maxBodyBytes = getNumber('MAX_BODY_BYTES');
45
- const keepAliveTimeoutMs = getNumber('KEEP_ALIVE_TIMEOUT_MS');
46
- const headersTimeoutMs = getNumber('HEADERS_TIMEOUT_MS');
47
- const metricsEnabled = getBoolean('METRICS_ENABLED');
48
-
49
- return {
50
- ...defaults,
51
- maxConcurrency: maxConcurrency ?? defaults.maxConcurrency,
52
- requestTimeoutMs: requestTimeoutMs ?? defaults.requestTimeoutMs,
53
- maxMemoryBytes: maxMemoryBytes ?? defaults.maxMemoryBytes,
54
- maxBodyBytes: maxBodyBytes ?? defaults.maxBodyBytes,
55
- keepAliveTimeoutMs: keepAliveTimeoutMs ?? defaults.keepAliveTimeoutMs,
56
- headersTimeoutMs: headersTimeoutMs ?? defaults.headersTimeoutMs,
57
- metricsEnabled: metricsEnabled ?? defaults.metricsEnabled,
58
- };
59
- }
60
-
@@ -1,210 +0,0 @@
1
- import type { QHTTPXContext } from './types';
2
- import { createHash } from 'crypto';
3
-
4
- export interface RequestFusionOptions {
5
- windowMs?: number;
6
- vary?: string[];
7
- }
8
-
9
- interface FusionResult {
10
- type: 'json' | 'send' | 'html' | 'redirect';
11
- payload: unknown;
12
- status: number;
13
- contentType?: string;
14
- }
15
-
16
- export class RequestFusion {
17
- private inflight: Map<string, Promise<FusionResult>> = new Map();
18
- private cache: Map<string, { result: FusionResult; expires: number }> = new Map();
19
- private options: RequestFusionOptions;
20
-
21
- constructor(options: boolean | RequestFusionOptions = {}) {
22
- this.options = typeof options === 'boolean' ? {} : options;
23
- if (!this.options.vary) {
24
- this.options.vary = ['authorization', 'cookie'];
25
- }
26
- }
27
-
28
- public async coalesce(ctx: QHTTPXContext, next: (ctx: QHTTPXContext) => Promise<void> | void): Promise<void> {
29
- const key = this.getKey(ctx);
30
-
31
- // 1. Check Cache (Short-lived "Micro-TTL")
32
- if (this.options.windowMs && this.options.windowMs > 0) {
33
- const cached = this.cache.get(key);
34
- if (cached) {
35
- if (Date.now() < cached.expires) {
36
- this.applyResult(ctx, cached.result);
37
- return;
38
- } else {
39
- this.cache.delete(key);
40
- }
41
- }
42
- }
43
-
44
- // 2. Check In-Flight
45
- if (this.inflight.has(key)) {
46
- try {
47
- const result = await this.inflight.get(key)!;
48
- this.applyResult(ctx, result);
49
- return;
50
- } catch (err) {
51
- // If leader failed, we should probably fail too or retry?
52
- // For now, let's propagate the error.
53
- throw err;
54
- }
55
- }
56
-
57
- // 3. Be the Leader
58
- let resolve: (value: FusionResult) => void;
59
- let reject: (reason?: unknown) => void;
60
- const promise = new Promise<FusionResult>((res, rej) => {
61
- resolve = res;
62
- reject = rej;
63
- });
64
-
65
- this.inflight.set(key, promise);
66
-
67
- // Hijack context methods to capture result
68
- // Cast to any to allow overwriting readonly methods
69
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
70
- const mutableCtx = ctx as any;
71
-
72
- const originalJson = mutableCtx.json;
73
- const originalSend = mutableCtx.send;
74
- const originalHtml = mutableCtx.html;
75
- const originalRedirect = mutableCtx.redirect;
76
-
77
- let captured = false;
78
-
79
- // Helper to cleanup and resolve
80
- const finish = (result: FusionResult) => {
81
- if (captured) return;
82
- captured = true;
83
- resolve(result);
84
-
85
- // Cache if needed
86
- if (this.options.windowMs && this.options.windowMs > 0) {
87
- this.cache.set(key, {
88
- result,
89
- expires: Date.now() + this.options.windowMs
90
- });
91
- // Cleanup cache later
92
- setTimeout(() => this.cache.delete(key), this.options.windowMs);
93
- }
94
-
95
- // Remove from inflight immediately (unless we rely on cache for window)
96
- // Actually, if we have windowMs, we rely on cache.
97
- // If windowMs=0, we remove immediately.
98
- this.inflight.delete(key);
99
- };
100
-
101
- mutableCtx.json = (payload: unknown, status = 200) => {
102
- finish({ type: 'json', payload, status });
103
- return originalJson.call(ctx, payload, status);
104
- };
105
-
106
- mutableCtx.send = (payload: string | Buffer, status = 200) => {
107
- finish({ type: 'send', payload, status });
108
- return originalSend.call(ctx, payload, status);
109
- };
110
-
111
- mutableCtx.html = (payload: string, status = 200) => {
112
- finish({ type: 'html', payload, status });
113
- return originalHtml.call(ctx, payload, status);
114
- };
115
-
116
- mutableCtx.redirect = (url: string, status = 302) => {
117
- finish({ type: 'redirect', payload: url, status });
118
- return originalRedirect.call(ctx, url, status);
119
- };
120
-
121
- try {
122
- await next(ctx);
123
- // If next() completes but no response method was called,
124
- // it implies the handler might be async and forgot to await,
125
- // or it's a 404/middleware issue.
126
- // If not captured yet, we can't resolve the followers properly.
127
- // They will hang unless we reject or resolve with something.
128
- // However, typical QHTTPX usage implies ctx.json/send is called.
129
- } catch (err) {
130
- reject!(err);
131
- this.inflight.delete(key);
132
- // Restore methods
133
- mutableCtx.json = originalJson;
134
- mutableCtx.send = originalSend;
135
- mutableCtx.html = originalHtml;
136
- mutableCtx.redirect = originalRedirect;
137
- throw err;
138
- }
139
- }
140
-
141
- private applyResult(ctx: QHTTPXContext, result: FusionResult) {
142
- switch (result.type) {
143
- case 'json':
144
- ctx.json(result.payload, result.status);
145
- break;
146
- case 'send': {
147
- if (typeof result.payload === 'string' || Buffer.isBuffer(result.payload)) {
148
- ctx.send(result.payload as string | Buffer, result.status);
149
- } else {
150
- ctx.send(String(result.payload), result.status);
151
- }
152
- break;
153
- }
154
- case 'html': {
155
- if (typeof result.payload === 'string') {
156
- ctx.html(result.payload as string, result.status);
157
- } else {
158
- ctx.html(String(result.payload), result.status);
159
- }
160
- break;
161
- }
162
- case 'redirect': {
163
- if (typeof result.payload === 'string') {
164
- ctx.redirect(result.payload as string, result.status);
165
- } else {
166
- ctx.redirect(String(result.payload), result.status);
167
- }
168
- break;
169
- }
170
- }
171
- }
172
-
173
- private getKey(ctx: QHTTPXContext): string {
174
- // Base: Method + Path
175
- let data = `${ctx.req.method}|${ctx.url?.pathname}|`;
176
-
177
- // Query
178
- if (ctx.query) {
179
- // Deterministic sort
180
- const keys = Object.keys(ctx.query).sort();
181
- for (const k of keys) {
182
- data += `${k}=${ctx.query[k]}|`;
183
- }
184
- }
185
-
186
- // Body (if parsed)
187
- if (ctx.body) {
188
- // We assume body is simple JSON.
189
- // For objects, order matters for hashing.
190
- // fast-json-stringify or JSON.stringify isn't always deterministic key order.
191
- // But for performance, JSON.stringify is often "good enough" if keys aren't shuffled.
192
- // For strict correctness, we should use a canonical stringify, but that's slow.
193
- // Let's use JSON.stringify for now.
194
- data += JSON.stringify(ctx.body);
195
- }
196
-
197
- // Vary Headers
198
- if (this.options.vary) {
199
- for (const header of this.options.vary) {
200
- const val = ctx.req.headers[header.toLowerCase()];
201
- if (val) {
202
- data += `|${header}:${val}`;
203
- }
204
- }
205
- }
206
-
207
- // We hash it to keep the key size manageable
208
- return createHash('sha1').update(data).digest('hex');
209
- }
210
- }
@@ -1,70 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import pino from 'pino';
3
-
4
- export type LogLevel = 'fatal' | 'error' | 'warn' | 'info' | 'debug' | 'trace';
5
-
6
- export interface LoggerOptions {
7
- level?: LogLevel;
8
- pretty?: boolean;
9
- name?: string;
10
- }
11
-
12
- export class Logger {
13
- private pino: pino.Logger;
14
-
15
- constructor(options: LoggerOptions = {}) {
16
- const isDev = process.env.NODE_ENV !== 'production';
17
- const pretty = options.pretty ?? isDev;
18
-
19
- this.pino = pino({
20
- name: options.name || 'qhttpx',
21
- level: options.level || 'info',
22
- transport: pretty
23
- ? {
24
- target: 'pino-pretty',
25
- options: {
26
- colorize: true,
27
- translateTime: 'HH:MM:ss Z',
28
- ignore: 'pid,hostname',
29
- },
30
- }
31
- : undefined,
32
- });
33
- }
34
-
35
- info(msg: string, ...args: any[]): void;
36
- info(obj: object, msg?: string, ...args: any[]): void;
37
- info(arg1: string | object, ...args: any[]): void {
38
- this.pino.info(arg1 as any, ...args);
39
- }
40
-
41
- error(msg: string, ...args: any[]): void;
42
- error(obj: object, msg?: string, ...args: any[]): void;
43
- error(arg1: string | object, ...args: any[]): void {
44
- this.pino.error(arg1 as any, ...args);
45
- }
46
-
47
- warn(msg: string, ...args: any[]): void;
48
- warn(obj: object, msg?: string, ...args: any[]): void;
49
- warn(arg1: string | object, ...args: any[]): void {
50
- this.pino.warn(arg1 as any, ...args);
51
- }
52
-
53
- debug(msg: string, ...args: any[]): void;
54
- debug(obj: object, msg?: string, ...args: any[]): void;
55
- debug(arg1: string | object, ...args: any[]): void {
56
- this.pino.debug(arg1 as any, ...args);
57
- }
58
-
59
- fatal(msg: string, ...args: any[]): void;
60
- fatal(obj: object, msg?: string, ...args: any[]): void;
61
- fatal(arg1: string | object, ...args: any[]): void {
62
- this.pino.fatal(arg1 as any, ...args);
63
- }
64
-
65
- child(bindings: pino.Bindings): Logger {
66
- const child = new Logger();
67
- child.pino = this.pino.child(bindings);
68
- return child;
69
- }
70
- }
@@ -1,166 +0,0 @@
1
- import type { TaskEngine, TaskMetrics } from './tasks';
2
- import { Scheduler } from './scheduler';
3
-
4
- export type LatencySnapshot = {
5
- p50: number | null;
6
- p95: number | null;
7
- p99: number | null;
8
- };
9
-
10
- export type MetricsSnapshot = {
11
- totalRequests: number;
12
- inFlightRequests: number;
13
- totalErrors: number;
14
- totalTimeouts: number;
15
- requestsPerSecond: number;
16
- latency: LatencySnapshot;
17
- scheduler: {
18
- inFlight: number;
19
- };
20
- tasks?: TaskMetrics;
21
- memory: {
22
- rssBytes: number;
23
- heapUsedBytes: number;
24
- };
25
- };
26
-
27
- export class Metrics {
28
- private readonly scheduler: Scheduler;
29
-
30
- private readonly taskEngine?: TaskEngine;
31
-
32
- private readonly latencies: number[] = [];
33
-
34
- private readonly maxLatencies: number;
35
-
36
- private readonly enabled: boolean;
37
-
38
- private totalRequests = 0;
39
-
40
- private inFlightRequests = 0;
41
-
42
- private totalErrors = 0;
43
-
44
- private totalTimeouts = 0;
45
-
46
- constructor(
47
- scheduler: Scheduler,
48
- options: { maxLatencies?: number; enabled?: boolean } = {},
49
- taskEngine?: TaskEngine,
50
- ) {
51
- this.scheduler = scheduler;
52
- this.taskEngine = taskEngine;
53
- this.maxLatencies = options.maxLatencies ?? 1000;
54
- this.enabled = options.enabled ?? true;
55
- }
56
-
57
- onRequestStart(): void {
58
- if (!this.enabled) {
59
- return;
60
- }
61
- this.inFlightRequests += 1;
62
- }
63
-
64
- onRequestEnd(durationMs: number, statusCode: number): void {
65
- if (!this.enabled) {
66
- return;
67
- }
68
- this.totalRequests += 1;
69
- if (this.inFlightRequests > 0) {
70
- this.inFlightRequests -= 1;
71
- }
72
- if (statusCode >= 500) {
73
- this.totalErrors += 1;
74
- }
75
- this.recordLatency(durationMs);
76
- }
77
-
78
- onTimeout(): void {
79
- if (!this.enabled) {
80
- return;
81
- }
82
- this.totalTimeouts += 1;
83
- }
84
-
85
- snapshot(): MetricsSnapshot {
86
- const uptime = process.uptime();
87
- const requestsPerSecond =
88
- uptime > 0 ? this.totalRequests / uptime : this.totalRequests;
89
-
90
- const latency = this.latencySnapshot();
91
-
92
- const memoryUsage = process.memoryUsage();
93
-
94
- let tasks: TaskMetrics | undefined;
95
- if (this.taskEngine) {
96
- tasks = this.taskEngine.getMetrics();
97
- }
98
-
99
- return {
100
- totalRequests: this.totalRequests,
101
- inFlightRequests: this.inFlightRequests,
102
- totalErrors: this.totalErrors,
103
- totalTimeouts: this.totalTimeouts,
104
- requestsPerSecond,
105
- latency,
106
- scheduler: {
107
- inFlight: this.scheduler.getCurrentInFlight(),
108
- },
109
- tasks,
110
- memory: {
111
- rssBytes: memoryUsage.rss,
112
- heapUsedBytes: memoryUsage.heapUsed,
113
- },
114
- };
115
- }
116
-
117
- private recordLatency(durationMs: number): void {
118
- if (!this.enabled) {
119
- return;
120
- }
121
- if (!Number.isFinite(durationMs) || durationMs < 0) {
122
- return;
123
- }
124
- this.latencies.push(durationMs);
125
- if (this.latencies.length > this.maxLatencies) {
126
- this.latencies.shift();
127
- }
128
- }
129
-
130
- private latencySnapshot(): LatencySnapshot {
131
- if (this.latencies.length === 0) {
132
- return {
133
- p50: null,
134
- p95: null,
135
- p99: null,
136
- };
137
- }
138
-
139
- const sorted = [...this.latencies].sort((a, b) => a - b);
140
-
141
- const p50 = this.percentile(sorted, 0.5);
142
- const p95 = this.percentile(sorted, 0.95);
143
- const p99 = this.percentile(sorted, 0.99);
144
-
145
- return {
146
- p50,
147
- p95,
148
- p99,
149
- };
150
- }
151
-
152
- private percentile(sorted: number[], p: number): number {
153
- if (sorted.length === 0) {
154
- return 0;
155
- }
156
- if (p <= 0) {
157
- return sorted[0];
158
- }
159
- if (p >= 1) {
160
- return sorted[sorted.length - 1];
161
- }
162
- const index = Math.floor(p * (sorted.length - 1));
163
- return sorted[index];
164
- }
165
- }
166
-
@@ -1,38 +0,0 @@
1
- import os from 'os';
2
-
3
- export type WorkerSetting = 'auto' | number;
4
-
5
- export function calculateWorkerCount(setting: WorkerSetting): number {
6
- if (typeof setting === 'number') {
7
- if (!Number.isFinite(setting) || setting <= 0) {
8
- return 1;
9
- }
10
- return Math.floor(setting);
11
- }
12
-
13
- const cpuCount = os.cpus().length || 1;
14
- return Math.max(1, cpuCount);
15
- }
16
-
17
- export type ResourceThresholds = {
18
- maxRssBytes?: number;
19
- };
20
-
21
- export type ResourceSample = {
22
- rssBytes: number;
23
- };
24
-
25
- export function isResourceOverloaded(
26
- sample: ResourceSample,
27
- thresholds: ResourceThresholds,
28
- ): boolean {
29
- if (
30
- thresholds.maxRssBytes !== undefined &&
31
- sample.rssBytes > thresholds.maxRssBytes
32
- ) {
33
- return true;
34
- }
35
-
36
- return false;
37
- }
38
-