@engjts/nexus 0.1.7 → 0.1.9
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/dist/advanced/playground/generatePlaygroundHTML.d.ts.map +1 -1
- package/dist/advanced/playground/generatePlaygroundHTML.js +107 -0
- package/dist/advanced/playground/generatePlaygroundHTML.js.map +1 -1
- package/dist/advanced/playground/playground.d.ts +19 -0
- package/dist/advanced/playground/playground.d.ts.map +1 -1
- package/dist/advanced/playground/playground.js +70 -0
- package/dist/advanced/playground/playground.js.map +1 -1
- package/dist/advanced/playground/types.d.ts +20 -0
- package/dist/advanced/playground/types.d.ts.map +1 -1
- package/dist/core/application.d.ts +14 -0
- package/dist/core/application.d.ts.map +1 -1
- package/dist/core/application.js +173 -71
- package/dist/core/application.js.map +1 -1
- package/dist/core/context-pool.d.ts +2 -13
- package/dist/core/context-pool.d.ts.map +1 -1
- package/dist/core/context-pool.js +7 -45
- package/dist/core/context-pool.js.map +1 -1
- package/dist/core/context.d.ts +108 -5
- package/dist/core/context.d.ts.map +1 -1
- package/dist/core/context.js +449 -53
- package/dist/core/context.js.map +1 -1
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +9 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/middleware.d.ts +6 -0
- package/dist/core/middleware.d.ts.map +1 -1
- package/dist/core/middleware.js +83 -84
- package/dist/core/middleware.js.map +1 -1
- package/dist/core/performance/fast-json.d.ts +149 -0
- package/dist/core/performance/fast-json.d.ts.map +1 -0
- package/dist/core/performance/fast-json.js +473 -0
- package/dist/core/performance/fast-json.js.map +1 -0
- package/dist/core/router/file-router.d.ts +20 -7
- package/dist/core/router/file-router.d.ts.map +1 -1
- package/dist/core/router/file-router.js +41 -13
- package/dist/core/router/file-router.js.map +1 -1
- package/dist/core/router/index.d.ts +6 -0
- package/dist/core/router/index.d.ts.map +1 -1
- package/dist/core/router/index.js +33 -6
- package/dist/core/router/index.js.map +1 -1
- package/dist/core/router/radix-tree.d.ts +4 -1
- package/dist/core/router/radix-tree.d.ts.map +1 -1
- package/dist/core/router/radix-tree.js +7 -3
- package/dist/core/router/radix-tree.js.map +1 -1
- package/dist/core/serializer.d.ts +251 -0
- package/dist/core/serializer.d.ts.map +1 -0
- package/dist/core/serializer.js +290 -0
- package/dist/core/serializer.js.map +1 -0
- package/dist/core/types.d.ts +39 -1
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -2
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
- package/documentation/01-getting-started.md +0 -240
- package/documentation/02-context.md +0 -335
- package/documentation/03-routing.md +0 -397
- package/documentation/04-middleware.md +0 -483
- package/documentation/05-validation.md +0 -514
- package/documentation/06-error-handling.md +0 -465
- package/documentation/07-performance.md +0 -364
- package/documentation/08-adapters.md +0 -470
- package/documentation/09-api-reference.md +0 -548
- package/documentation/10-examples.md +0 -582
- package/documentation/11-deployment.md +0 -477
- package/documentation/12-sentry.md +0 -620
- package/documentation/13-sentry-data-storage.md +0 -996
- package/documentation/14-sentry-data-reference.md +0 -457
- package/documentation/15-sentry-summary.md +0 -409
- package/documentation/16-alerts-system.md +0 -745
- package/documentation/17-alert-adapters.md +0 -696
- package/documentation/18-alerts-implementation-summary.md +0 -385
- package/documentation/19-class-based-routing.md +0 -840
- package/documentation/20-websocket-realtime.md +0 -813
- package/documentation/21-cache-system.md +0 -510
- package/documentation/22-job-queue.md +0 -772
- package/documentation/23-sentry-plugin.md +0 -551
- package/documentation/24-testing-utilities.md +0 -1287
- package/documentation/25-api-versioning.md +0 -533
- package/documentation/26-context-store.md +0 -607
- package/documentation/27-dependency-injection.md +0 -329
- package/documentation/28-lifecycle-hooks.md +0 -521
- package/documentation/29-package-structure.md +0 -196
- package/documentation/30-plugin-system.md +0 -414
- package/documentation/31-jwt-authentication.md +0 -597
- package/documentation/32-cli.md +0 -268
- package/documentation/ALERTS-COMPLETE-SUMMARY.md +0 -429
- package/documentation/ALERTS-INDEX.md +0 -330
- package/documentation/ALERTS-QUICK-REFERENCE.md +0 -286
- package/documentation/README.md +0 -178
- package/documentation/index.html +0 -34
- package/modern_framework_paper.md +0 -1870
- package/public/css/style.css +0 -87
- package/public/index.html +0 -34
- package/public/js/app.js +0 -27
- package/src/advanced/cache/InMemoryCacheStore.ts +0 -68
- package/src/advanced/cache/MultiTierCache.ts +0 -194
- package/src/advanced/cache/RedisCacheStore.ts +0 -341
- package/src/advanced/cache/index.ts +0 -5
- package/src/advanced/cache/types.ts +0 -40
- package/src/advanced/graphql/SimpleDataLoader.ts +0 -42
- package/src/advanced/graphql/index.ts +0 -22
- package/src/advanced/graphql/server.ts +0 -252
- package/src/advanced/graphql/types.ts +0 -42
- package/src/advanced/jobs/InMemoryQueueStore.ts +0 -68
- package/src/advanced/jobs/JobQueue.ts +0 -556
- package/src/advanced/jobs/RedisQueueStore.ts +0 -367
- package/src/advanced/jobs/index.ts +0 -5
- package/src/advanced/jobs/types.ts +0 -70
- package/src/advanced/observability/APMManager.ts +0 -163
- package/src/advanced/observability/AlertManager.ts +0 -109
- package/src/advanced/observability/MetricRegistry.ts +0 -151
- package/src/advanced/observability/ObservabilityCenter.ts +0 -304
- package/src/advanced/observability/StructuredLogger.ts +0 -154
- package/src/advanced/observability/TracingManager.ts +0 -117
- package/src/advanced/observability/adapters.ts +0 -304
- package/src/advanced/observability/createObservabilityMiddleware.ts +0 -63
- package/src/advanced/observability/index.ts +0 -11
- package/src/advanced/observability/types.ts +0 -174
- package/src/advanced/playground/extractPathParams.ts +0 -6
- package/src/advanced/playground/generateFieldExample.ts +0 -31
- package/src/advanced/playground/generatePlaygroundHTML.ts +0 -1849
- package/src/advanced/playground/generateSummary.ts +0 -19
- package/src/advanced/playground/getTagFromPath.ts +0 -9
- package/src/advanced/playground/index.ts +0 -8
- package/src/advanced/playground/playground.ts +0 -170
- package/src/advanced/playground/types.ts +0 -20
- package/src/advanced/playground/zodToExample.ts +0 -16
- package/src/advanced/playground/zodToParams.ts +0 -15
- package/src/advanced/postman/buildAuth.ts +0 -31
- package/src/advanced/postman/buildBody.ts +0 -15
- package/src/advanced/postman/buildQueryParams.ts +0 -27
- package/src/advanced/postman/buildRequestItem.ts +0 -36
- package/src/advanced/postman/buildResponses.ts +0 -11
- package/src/advanced/postman/buildUrl.ts +0 -33
- package/src/advanced/postman/capitalize.ts +0 -4
- package/src/advanced/postman/generateCollection.ts +0 -59
- package/src/advanced/postman/generateEnvironment.ts +0 -34
- package/src/advanced/postman/generateExampleFromZod.ts +0 -21
- package/src/advanced/postman/generateFieldExample.ts +0 -45
- package/src/advanced/postman/generateName.ts +0 -20
- package/src/advanced/postman/generateUUID.ts +0 -11
- package/src/advanced/postman/getTagFromPath.ts +0 -10
- package/src/advanced/postman/index.ts +0 -28
- package/src/advanced/postman/postman.ts +0 -156
- package/src/advanced/postman/slugify.ts +0 -7
- package/src/advanced/postman/types.ts +0 -140
- package/src/advanced/realtime/index.ts +0 -18
- package/src/advanced/realtime/websocket.ts +0 -231
- package/src/advanced/sentry/index.ts +0 -1236
- package/src/advanced/sentry/types.ts +0 -355
- package/src/advanced/static/generateDirectoryListing.ts +0 -47
- package/src/advanced/static/generateETag.ts +0 -7
- package/src/advanced/static/getMimeType.ts +0 -9
- package/src/advanced/static/index.ts +0 -32
- package/src/advanced/static/isSafePath.ts +0 -13
- package/src/advanced/static/publicDir.ts +0 -21
- package/src/advanced/static/serveStatic.ts +0 -225
- package/src/advanced/static/spa.ts +0 -24
- package/src/advanced/static/types.ts +0 -159
- package/src/advanced/swagger/SwaggerGenerator.ts +0 -66
- package/src/advanced/swagger/buildOperation.ts +0 -61
- package/src/advanced/swagger/buildParameters.ts +0 -61
- package/src/advanced/swagger/buildRequestBody.ts +0 -21
- package/src/advanced/swagger/buildResponses.ts +0 -54
- package/src/advanced/swagger/capitalize.ts +0 -5
- package/src/advanced/swagger/convertPath.ts +0 -9
- package/src/advanced/swagger/createSwagger.ts +0 -12
- package/src/advanced/swagger/generateOperationId.ts +0 -21
- package/src/advanced/swagger/generateSpec.ts +0 -105
- package/src/advanced/swagger/generateSummary.ts +0 -24
- package/src/advanced/swagger/generateSwaggerUI.ts +0 -70
- package/src/advanced/swagger/generateThemeCss.ts +0 -53
- package/src/advanced/swagger/index.ts +0 -25
- package/src/advanced/swagger/swagger.ts +0 -237
- package/src/advanced/swagger/types.ts +0 -206
- package/src/advanced/swagger/zodFieldToOpenAPI.ts +0 -94
- package/src/advanced/swagger/zodSchemaToOpenAPI.ts +0 -50
- package/src/advanced/swagger/zodToOpenAPI.ts +0 -22
- package/src/advanced/testing/factory.ts +0 -509
- package/src/advanced/testing/harness.ts +0 -612
- package/src/advanced/testing/index.ts +0 -430
- package/src/advanced/testing/load-test.ts +0 -618
- package/src/advanced/testing/mock-server.ts +0 -498
- package/src/advanced/testing/mock.ts +0 -670
- package/src/cli/bin.ts +0 -9
- package/src/cli/cli.ts +0 -158
- package/src/cli/commands/add.ts +0 -178
- package/src/cli/commands/build.ts +0 -73
- package/src/cli/commands/create.ts +0 -166
- package/src/cli/commands/dev.ts +0 -85
- package/src/cli/commands/generate.ts +0 -99
- package/src/cli/commands/help.ts +0 -95
- package/src/cli/commands/init.ts +0 -91
- package/src/cli/commands/version.ts +0 -38
- package/src/cli/index.ts +0 -6
- package/src/cli/templates/generators.ts +0 -359
- package/src/cli/templates/index.ts +0 -680
- package/src/cli/utils/exec.ts +0 -52
- package/src/cli/utils/file-system.ts +0 -78
- package/src/cli/utils/logger.ts +0 -111
- package/src/core/adapter.ts +0 -88
- package/src/core/application.ts +0 -1335
- package/src/core/context-pool.ts +0 -127
- package/src/core/context.ts +0 -412
- package/src/core/index.ts +0 -80
- package/src/core/middleware.ts +0 -262
- package/src/core/performance/buffer-pool.ts +0 -108
- package/src/core/performance/middleware-optimizer.ts +0 -162
- package/src/core/plugin/PluginManager.ts +0 -435
- package/src/core/plugin/builder.ts +0 -358
- package/src/core/plugin/index.ts +0 -50
- package/src/core/plugin/types.ts +0 -214
- package/src/core/router/file-router.ts +0 -594
- package/src/core/router/index.ts +0 -227
- package/src/core/router/radix-tree.ts +0 -226
- package/src/core/store/index.ts +0 -30
- package/src/core/store/registry.ts +0 -178
- package/src/core/store/request-store.ts +0 -240
- package/src/core/store/types.ts +0 -233
- package/src/core/types.ts +0 -574
- package/src/database/adapter.ts +0 -35
- package/src/database/adapters/index.ts +0 -1
- package/src/database/adapters/mysql.ts +0 -669
- package/src/database/database.ts +0 -70
- package/src/database/dialect.ts +0 -388
- package/src/database/index.ts +0 -12
- package/src/database/migrations.ts +0 -86
- package/src/database/optimizer.ts +0 -125
- package/src/database/query-builder.ts +0 -404
- package/src/database/realtime.ts +0 -53
- package/src/database/schema.ts +0 -71
- package/src/database/transactions.ts +0 -56
- package/src/database/types.ts +0 -87
- package/src/deployment/cluster.ts +0 -471
- package/src/deployment/config.ts +0 -454
- package/src/deployment/docker.ts +0 -599
- package/src/deployment/graceful-shutdown.ts +0 -373
- package/src/deployment/index.ts +0 -56
- package/src/index.ts +0 -264
- package/src/security/adapter.ts +0 -318
- package/src/security/auth/JWTPlugin.ts +0 -234
- package/src/security/auth/JWTProvider.ts +0 -316
- package/src/security/auth/adapter.ts +0 -12
- package/src/security/auth/jwt.ts +0 -234
- package/src/security/auth/middleware.ts +0 -188
- package/src/security/csrf.ts +0 -220
- package/src/security/headers.ts +0 -108
- package/src/security/index.ts +0 -60
- package/src/security/rate-limit/adapter.ts +0 -7
- package/src/security/rate-limit/memory.ts +0 -108
- package/src/security/rate-limit/middleware.ts +0 -181
- package/src/security/sanitization.ts +0 -75
- package/src/security/types.ts +0 -240
- package/src/security/utils.ts +0 -52
- package/tsconfig.json +0 -39
|
@@ -1,367 +0,0 @@
|
|
|
1
|
-
import { QueueStore, Job, JobState, QueueStats } from './types';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Redis queue store configuration
|
|
5
|
-
*/
|
|
6
|
-
export interface RedisQueueConfig {
|
|
7
|
-
/**
|
|
8
|
-
* Redis connection URL
|
|
9
|
-
* @example 'redis://localhost:6379'
|
|
10
|
-
*/
|
|
11
|
-
url?: string;
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Redis host
|
|
15
|
-
* @default 'localhost'
|
|
16
|
-
*/
|
|
17
|
-
host?: string;
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Redis port
|
|
21
|
-
* @default 6379
|
|
22
|
-
*/
|
|
23
|
-
port?: number;
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Redis password
|
|
27
|
-
*/
|
|
28
|
-
password?: string;
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Redis database number
|
|
32
|
-
* @default 0
|
|
33
|
-
*/
|
|
34
|
-
db?: number;
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Key prefix for queue data
|
|
38
|
-
* @default 'nexus:queue:'
|
|
39
|
-
*/
|
|
40
|
-
prefix?: string;
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Poll interval for delayed jobs in milliseconds
|
|
44
|
-
* @default 1000
|
|
45
|
-
*/
|
|
46
|
-
pollInterval?: number;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Redis client interface (compatible with ioredis and node-redis)
|
|
51
|
-
*/
|
|
52
|
-
export interface RedisClientLike {
|
|
53
|
-
get(key: string): Promise<string | null>;
|
|
54
|
-
set(key: string, value: string): Promise<any>;
|
|
55
|
-
del(key: string | string[]): Promise<number>;
|
|
56
|
-
keys(pattern: string): Promise<string[]>;
|
|
57
|
-
lpush(key: string, ...values: string[]): Promise<number>;
|
|
58
|
-
rpop(key: string): Promise<string | null>;
|
|
59
|
-
lrange(key: string, start: number, stop: number): Promise<string[]>;
|
|
60
|
-
lrem(key: string, count: number, value: string): Promise<number>;
|
|
61
|
-
zadd(key: string, ...args: (string | number)[]): Promise<number>;
|
|
62
|
-
zrangebyscore(key: string, min: number | string, max: number | string): Promise<string[]>;
|
|
63
|
-
zrem(key: string, ...members: string[]): Promise<number>;
|
|
64
|
-
hset(key: string, field: string, value: string): Promise<number>;
|
|
65
|
-
hget(key: string, field: string): Promise<string | null>;
|
|
66
|
-
hgetall(key: string): Promise<Record<string, string>>;
|
|
67
|
-
hdel(key: string, ...fields: string[]): Promise<number>;
|
|
68
|
-
incr(key: string): Promise<number>;
|
|
69
|
-
expire(key: string, seconds: number): Promise<number>;
|
|
70
|
-
quit?(): Promise<any>;
|
|
71
|
-
disconnect?(): Promise<any>;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Redis-based queue store implementation
|
|
76
|
-
* Provides persistent job storage with support for distributed workers
|
|
77
|
-
*
|
|
78
|
-
* @example
|
|
79
|
-
* ```typescript
|
|
80
|
-
* import Redis from 'ioredis';
|
|
81
|
-
* import { JobQueue, RedisQueueStore } from 'nexus';
|
|
82
|
-
*
|
|
83
|
-
* const redis = new Redis();
|
|
84
|
-
* const store = new RedisQueueStore('emails', { client: redis });
|
|
85
|
-
* const queue = new JobQueue('emails', { store });
|
|
86
|
-
*
|
|
87
|
-
* // Add a job
|
|
88
|
-
* await queue.add('sendEmail', { to: 'user@example.com', subject: 'Hello' });
|
|
89
|
-
*
|
|
90
|
-
* // Process jobs
|
|
91
|
-
* queue.process('sendEmail', async (job) => {
|
|
92
|
-
* await sendEmail(job.data);
|
|
93
|
-
* return { sent: true };
|
|
94
|
-
* });
|
|
95
|
-
* ```
|
|
96
|
-
*/
|
|
97
|
-
export class RedisQueueStore<Data = any> implements QueueStore<Data> {
|
|
98
|
-
private client: RedisClientLike;
|
|
99
|
-
private prefix: string;
|
|
100
|
-
private queueName: string;
|
|
101
|
-
private pollInterval: number;
|
|
102
|
-
private pollTimer?: NodeJS.Timeout;
|
|
103
|
-
|
|
104
|
-
constructor(
|
|
105
|
-
queueName: string,
|
|
106
|
-
options: RedisQueueConfig & { client: RedisClientLike }
|
|
107
|
-
) {
|
|
108
|
-
this.queueName = queueName;
|
|
109
|
-
this.client = options.client;
|
|
110
|
-
this.prefix = options.prefix ?? 'nexus:queue:';
|
|
111
|
-
this.pollInterval = options.pollInterval ?? 1000;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Build Redis key with prefix
|
|
116
|
-
*/
|
|
117
|
-
private key(type: string): string {
|
|
118
|
-
return `${this.prefix}${this.queueName}:${type}`;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Serialize job to JSON
|
|
123
|
-
*/
|
|
124
|
-
private serialize(job: Job<Data>): string {
|
|
125
|
-
return JSON.stringify(job);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Deserialize job from JSON
|
|
130
|
-
*/
|
|
131
|
-
private deserialize(data: string): Job<Data> {
|
|
132
|
-
return JSON.parse(data);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Add a job to the queue
|
|
137
|
-
*/
|
|
138
|
-
async enqueue(job: Job<Data>): Promise<void> {
|
|
139
|
-
const serialized = this.serialize(job);
|
|
140
|
-
|
|
141
|
-
// Store job data in hash
|
|
142
|
-
await this.client.hset(this.key('jobs'), job.id, serialized);
|
|
143
|
-
|
|
144
|
-
if (job.state === 'delayed') {
|
|
145
|
-
// Add to delayed set with score = runAt timestamp
|
|
146
|
-
await this.client.zadd(this.key('delayed'), job.runAt, job.id);
|
|
147
|
-
} else if (job.state === 'waiting') {
|
|
148
|
-
// Add to waiting list (with priority handling)
|
|
149
|
-
await this.client.lpush(this.key(`waiting:${job.priority}`), job.id);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Increment total counter
|
|
153
|
-
await this.client.incr(this.key('total'));
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Get the next job to process
|
|
158
|
-
*/
|
|
159
|
-
async dequeue(): Promise<Job<Data> | undefined> {
|
|
160
|
-
// First, check delayed jobs that are now ready
|
|
161
|
-
const now = Date.now();
|
|
162
|
-
const readyDelayed = await this.client.zrangebyscore(
|
|
163
|
-
this.key('delayed'),
|
|
164
|
-
'-inf',
|
|
165
|
-
now
|
|
166
|
-
);
|
|
167
|
-
|
|
168
|
-
// Move ready delayed jobs to waiting
|
|
169
|
-
for (const jobId of readyDelayed) {
|
|
170
|
-
const jobData = await this.client.hget(this.key('jobs'), jobId);
|
|
171
|
-
if (jobData) {
|
|
172
|
-
const job = this.deserialize(jobData);
|
|
173
|
-
job.state = 'waiting';
|
|
174
|
-
await this.client.hset(this.key('jobs'), jobId, this.serialize(job));
|
|
175
|
-
await this.client.zrem(this.key('delayed'), jobId);
|
|
176
|
-
await this.client.lpush(this.key(`waiting:${job.priority}`), jobId);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// Check priority queues (high to low: 10, 5, 0, -5, -10, etc.)
|
|
181
|
-
const priorities = [10, 5, 0, -5, -10];
|
|
182
|
-
|
|
183
|
-
for (const priority of priorities) {
|
|
184
|
-
const jobId = await this.client.rpop(this.key(`waiting:${priority}`));
|
|
185
|
-
if (jobId) {
|
|
186
|
-
const jobData = await this.client.hget(this.key('jobs'), jobId);
|
|
187
|
-
if (jobData) {
|
|
188
|
-
const job = this.deserialize(jobData);
|
|
189
|
-
job.state = 'active';
|
|
190
|
-
job.updatedAt = Date.now();
|
|
191
|
-
await this.client.hset(this.key('jobs'), jobId, this.serialize(job));
|
|
192
|
-
return job;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// Fallback: check default priority (0)
|
|
198
|
-
const jobId = await this.client.rpop(this.key('waiting:0'));
|
|
199
|
-
if (jobId) {
|
|
200
|
-
const jobData = await this.client.hget(this.key('jobs'), jobId);
|
|
201
|
-
if (jobData) {
|
|
202
|
-
const job = this.deserialize(jobData);
|
|
203
|
-
job.state = 'active';
|
|
204
|
-
job.updatedAt = Date.now();
|
|
205
|
-
await this.client.hset(this.key('jobs'), jobId, this.serialize(job));
|
|
206
|
-
return job;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
return undefined;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* Update job state
|
|
215
|
-
*/
|
|
216
|
-
async update(job: Job<Data>): Promise<void> {
|
|
217
|
-
const serialized = this.serialize(job);
|
|
218
|
-
await this.client.hset(this.key('jobs'), job.id, serialized);
|
|
219
|
-
|
|
220
|
-
// Handle state-specific storage
|
|
221
|
-
if (job.state === 'delayed') {
|
|
222
|
-
await this.client.zadd(this.key('delayed'), job.runAt, job.id);
|
|
223
|
-
} else if (job.state === 'waiting') {
|
|
224
|
-
await this.client.lpush(this.key(`waiting:${job.priority}`), job.id);
|
|
225
|
-
} else if (job.state === 'completed') {
|
|
226
|
-
await this.client.lpush(this.key('completed'), job.id);
|
|
227
|
-
// Set expiry for completed jobs (24 hours)
|
|
228
|
-
await this.client.expire(this.key('completed'), 86400);
|
|
229
|
-
} else if (job.state === 'failed') {
|
|
230
|
-
await this.client.lpush(this.key('failed'), job.id);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* Get a job by ID
|
|
236
|
-
*/
|
|
237
|
-
async get(id: string): Promise<Job<Data> | undefined> {
|
|
238
|
-
const data = await this.client.hget(this.key('jobs'), id);
|
|
239
|
-
if (!data) return undefined;
|
|
240
|
-
return this.deserialize(data);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/**
|
|
244
|
-
* List jobs by state
|
|
245
|
-
*/
|
|
246
|
-
async list(state?: JobState): Promise<Job<Data>[]> {
|
|
247
|
-
const allJobs = await this.client.hgetall(this.key('jobs'));
|
|
248
|
-
const jobs = Object.values(allJobs).map(data => this.deserialize(data));
|
|
249
|
-
|
|
250
|
-
if (!state) {
|
|
251
|
-
return jobs;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
return jobs.filter(job => job.state === state);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Get queue statistics
|
|
259
|
-
*/
|
|
260
|
-
async stats(): Promise<QueueStats> {
|
|
261
|
-
const allJobs = await this.client.hgetall(this.key('jobs'));
|
|
262
|
-
const stats: QueueStats = {
|
|
263
|
-
waiting: 0,
|
|
264
|
-
active: 0,
|
|
265
|
-
completed: 0,
|
|
266
|
-
failed: 0,
|
|
267
|
-
delayed: 0
|
|
268
|
-
};
|
|
269
|
-
|
|
270
|
-
for (const data of Object.values(allJobs)) {
|
|
271
|
-
const job = this.deserialize(data);
|
|
272
|
-
if (job.state in stats) {
|
|
273
|
-
(stats as any)[job.state]++;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
return stats;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
/**
|
|
281
|
-
* Clean old completed/failed jobs
|
|
282
|
-
*/
|
|
283
|
-
async clean(state: 'completed' | 'failed', olderThanMs: number = 86400000): Promise<number> {
|
|
284
|
-
const cutoff = Date.now() - olderThanMs;
|
|
285
|
-
const jobs = await this.list(state);
|
|
286
|
-
let cleaned = 0;
|
|
287
|
-
|
|
288
|
-
for (const job of jobs) {
|
|
289
|
-
if (job.updatedAt < cutoff) {
|
|
290
|
-
await this.client.hdel(this.key('jobs'), job.id);
|
|
291
|
-
await this.client.lrem(this.key(state), 0, job.id);
|
|
292
|
-
cleaned++;
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
return cleaned;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
/**
|
|
300
|
-
* Disconnect from Redis
|
|
301
|
-
*/
|
|
302
|
-
async disconnect(): Promise<void> {
|
|
303
|
-
if (this.pollTimer) {
|
|
304
|
-
clearInterval(this.pollTimer);
|
|
305
|
-
}
|
|
306
|
-
if (this.client.quit) {
|
|
307
|
-
await this.client.quit();
|
|
308
|
-
} else if (this.client.disconnect) {
|
|
309
|
-
await this.client.disconnect();
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
/**
|
|
315
|
-
* Create a Redis queue store with automatic client creation
|
|
316
|
-
* Requires 'ioredis' or 'redis' package to be installed
|
|
317
|
-
*
|
|
318
|
-
* @example
|
|
319
|
-
* ```typescript
|
|
320
|
-
* const store = await createRedisQueueStore('emails', {
|
|
321
|
-
* host: 'localhost',
|
|
322
|
-
* port: 6379
|
|
323
|
-
* });
|
|
324
|
-
*
|
|
325
|
-
* const queue = new JobQueue('emails', { store });
|
|
326
|
-
* ```
|
|
327
|
-
*/
|
|
328
|
-
export async function createRedisQueueStore<Data = any>(
|
|
329
|
-
queueName: string,
|
|
330
|
-
config: RedisQueueConfig = {}
|
|
331
|
-
): Promise<RedisQueueStore<Data>> {
|
|
332
|
-
let client: RedisClientLike;
|
|
333
|
-
|
|
334
|
-
// Try ioredis first
|
|
335
|
-
try {
|
|
336
|
-
const Redis = require('ioredis');
|
|
337
|
-
client = new Redis({
|
|
338
|
-
host: config.host ?? 'localhost',
|
|
339
|
-
port: config.port ?? 6379,
|
|
340
|
-
password: config.password,
|
|
341
|
-
db: config.db ?? 0
|
|
342
|
-
});
|
|
343
|
-
} catch {
|
|
344
|
-
// Try node-redis
|
|
345
|
-
try {
|
|
346
|
-
const { createClient } = require('redis');
|
|
347
|
-
const url = config.url ?? `redis://${config.host ?? 'localhost'}:${config.port ?? 6379}`;
|
|
348
|
-
|
|
349
|
-
client = createClient({
|
|
350
|
-
url,
|
|
351
|
-
password: config.password,
|
|
352
|
-
database: config.db ?? 0
|
|
353
|
-
});
|
|
354
|
-
|
|
355
|
-
await (client as any).connect();
|
|
356
|
-
} catch {
|
|
357
|
-
throw new Error(
|
|
358
|
-
'Redis client not found. Please install either "ioredis" or "redis" package:\n' +
|
|
359
|
-
' npm install ioredis\n' +
|
|
360
|
-
' # or\n' +
|
|
361
|
-
' npm install redis'
|
|
362
|
-
);
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
return new RedisQueueStore<Data>(queueName, { ...config, client });
|
|
367
|
-
}
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
export { JobQueue, CronSchedule, ScheduledJobConfig } from './JobQueue';
|
|
2
|
-
export { InMemoryQueueStore } from './InMemoryQueueStore';
|
|
3
|
-
export { RedisQueueStore, createRedisQueueStore } from './RedisQueueStore';
|
|
4
|
-
export type { RedisQueueConfig, RedisClientLike } from './RedisQueueStore';
|
|
5
|
-
export * from './types';
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
export type JobState = 'waiting' | 'delayed' | 'active' | 'completed' | 'failed' | 'paused';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
export interface Job<Data = any, Result = any> {
|
|
5
|
-
id: string;
|
|
6
|
-
name: string;
|
|
7
|
-
data: Data;
|
|
8
|
-
result?: Result;
|
|
9
|
-
error?: { message: string; stack?: string };
|
|
10
|
-
state: JobState;
|
|
11
|
-
attemptsMade: number;
|
|
12
|
-
maxAttempts: number;
|
|
13
|
-
priority: number;
|
|
14
|
-
createdAt: number;
|
|
15
|
-
runAt: number;
|
|
16
|
-
updatedAt: number;
|
|
17
|
-
metadata?: Record<string, any>;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface RetryOptions {
|
|
21
|
-
attempts?: number;
|
|
22
|
-
backoff?: 'fixed' | 'exponential';
|
|
23
|
-
delay?: number;
|
|
24
|
-
maxDelay?: number;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export interface RateLimitOptions {
|
|
28
|
-
max: number;
|
|
29
|
-
duration: number;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface QueueHooks<Data = any> {
|
|
33
|
-
onComplete?: (job: Job<Data>, result: any) => void | Promise<void>;
|
|
34
|
-
onFailed?: (job: Job<Data>, error: Error) => void | Promise<void>;
|
|
35
|
-
onRetry?: (job: Job<Data>, attempt: number, delay: number) => void | Promise<void>;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export interface QueueOptions<Data = any> {
|
|
39
|
-
concurrency?: number;
|
|
40
|
-
retry?: RetryOptions;
|
|
41
|
-
limiter?: RateLimitOptions;
|
|
42
|
-
hooks?: QueueHooks<Data>;
|
|
43
|
-
store?: QueueStore<Data>;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export interface EnqueueOptions {
|
|
47
|
-
delay?: number;
|
|
48
|
-
priority?: number;
|
|
49
|
-
metadata?: Record<string, any>;
|
|
50
|
-
attempts?: number;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export type JobHandler<Data = any, Result = any> = (job: Job<Data>) => Promise<Result>;
|
|
54
|
-
|
|
55
|
-
export interface QueueStore<Data = any> {
|
|
56
|
-
enqueue(job: Job<Data>): Promise<void>;
|
|
57
|
-
dequeue(): Promise<Job<Data> | undefined>;
|
|
58
|
-
update(job: Job<Data>): Promise<void>;
|
|
59
|
-
get(id: string): Promise<Job<Data> | undefined>;
|
|
60
|
-
list(state?: JobState): Promise<Job<Data>[]>;
|
|
61
|
-
stats(): Promise<QueueStats>;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export interface QueueStats {
|
|
65
|
-
waiting: number;
|
|
66
|
-
active: number;
|
|
67
|
-
completed: number;
|
|
68
|
-
failed: number;
|
|
69
|
-
delayed: number;
|
|
70
|
-
}
|
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
import { APMOptions } from './types';
|
|
2
|
-
import { getSentry, SentryClient } from '../sentry';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* APM (Application Performance Monitoring) manager
|
|
6
|
-
*
|
|
7
|
-
* This is a lightweight wrapper that delegates to Sentry when available.
|
|
8
|
-
* For full APM features (memory leak detection, slow query tracking),
|
|
9
|
-
* use Sentry integration which has built-in APM support.
|
|
10
|
-
*
|
|
11
|
-
* @example
|
|
12
|
-
* ```typescript
|
|
13
|
-
* // Recommended: Use Sentry with APM
|
|
14
|
-
* import { initSentry } from 'nexus';
|
|
15
|
-
*
|
|
16
|
-
* initSentry({
|
|
17
|
-
* dsn: 'your-dsn',
|
|
18
|
-
* apm: {
|
|
19
|
-
* slowQueryThreshold: 500,
|
|
20
|
-
* memoryLeakDetection: { enabled: true }
|
|
21
|
-
* }
|
|
22
|
-
* });
|
|
23
|
-
*
|
|
24
|
-
* // Then use APMManager as facade
|
|
25
|
-
* const apm = new APMManager();
|
|
26
|
-
* apm.recordQuery('SELECT * FROM users', 1200);
|
|
27
|
-
* ```
|
|
28
|
-
*/
|
|
29
|
-
export class APMManager {
|
|
30
|
-
private options: APMOptions;
|
|
31
|
-
private sentryClient?: SentryClient | null;
|
|
32
|
-
|
|
33
|
-
// Fallback storage when Sentry is not available
|
|
34
|
-
private slowQueries: Array<{ query: string; duration: number; timestamp: number; }> = [];
|
|
35
|
-
private memorySnapshots: Array<{ timestamp: number; heapUsed: number; heapTotal: number; }> = [];
|
|
36
|
-
private memoryCheckInterval?: NodeJS.Timeout;
|
|
37
|
-
|
|
38
|
-
constructor(options: APMOptions = {}) {
|
|
39
|
-
this.options = options;
|
|
40
|
-
this.sentryClient = getSentry();
|
|
41
|
-
|
|
42
|
-
// If Sentry is not available and memory leak detection is enabled, use fallback
|
|
43
|
-
if (!this.sentryClient && options.memoryLeakDetection?.enabled) {
|
|
44
|
-
this.startMemoryMonitoring();
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Record a database/external query
|
|
50
|
-
* Delegates to Sentry when available
|
|
51
|
-
*/
|
|
52
|
-
recordQuery(query: string, durationMs: number, metadata?: Record<string, any>) {
|
|
53
|
-
// Delegate to Sentry if available
|
|
54
|
-
if (this.sentryClient) {
|
|
55
|
-
this.sentryClient.recordQuery(query, durationMs, metadata);
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Fallback: store locally
|
|
60
|
-
const threshold = this.options.slowQueryThreshold ?? 1000;
|
|
61
|
-
if (durationMs >= threshold) {
|
|
62
|
-
this.slowQueries.push({
|
|
63
|
-
query,
|
|
64
|
-
duration: durationMs,
|
|
65
|
-
timestamp: Date.now()
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
// Keep last 100 slow queries
|
|
69
|
-
if (this.slowQueries.length > 100) {
|
|
70
|
-
this.slowQueries.shift();
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Get slow queries
|
|
77
|
-
* Delegates to Sentry when available
|
|
78
|
-
*/
|
|
79
|
-
getSlowQueries() {
|
|
80
|
-
if (this.sentryClient) {
|
|
81
|
-
return this.sentryClient.getSlowQueries();
|
|
82
|
-
}
|
|
83
|
-
return [...this.slowQueries];
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
private startMemoryMonitoring() {
|
|
87
|
-
const interval = this.options.memoryLeakDetection?.interval ?? 60000;
|
|
88
|
-
|
|
89
|
-
this.memoryCheckInterval = setInterval(() => {
|
|
90
|
-
const usage = process.memoryUsage();
|
|
91
|
-
this.memorySnapshots.push({
|
|
92
|
-
timestamp: Date.now(),
|
|
93
|
-
heapUsed: usage.heapUsed,
|
|
94
|
-
heapTotal: usage.heapTotal
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
// Keep last 60 snapshots
|
|
98
|
-
if (this.memorySnapshots.length > 60) {
|
|
99
|
-
this.memorySnapshots.shift();
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Check for potential memory leak
|
|
103
|
-
this.checkMemoryLeak();
|
|
104
|
-
}, interval);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
private checkMemoryLeak() {
|
|
108
|
-
if (this.memorySnapshots.length < 10) return;
|
|
109
|
-
|
|
110
|
-
const recent = this.memorySnapshots.slice(-10);
|
|
111
|
-
const oldest = recent[0].heapUsed;
|
|
112
|
-
const newest = recent[recent.length - 1].heapUsed;
|
|
113
|
-
const growth = (newest - oldest) / oldest;
|
|
114
|
-
|
|
115
|
-
// Parse threshold
|
|
116
|
-
let threshold = 0.5;
|
|
117
|
-
const thresholdConfig = this.options.memoryLeakDetection?.threshold;
|
|
118
|
-
if (typeof thresholdConfig === 'string') {
|
|
119
|
-
threshold = parseFloat(thresholdConfig.replace('%', '')) / 100;
|
|
120
|
-
} else if (typeof thresholdConfig === 'number') {
|
|
121
|
-
threshold = thresholdConfig;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Alert if memory grew more than threshold in the monitoring window
|
|
125
|
-
if (growth > threshold) {
|
|
126
|
-
console.warn('[APM] Potential memory leak detected:', {
|
|
127
|
-
growth: `${(growth * 100).toFixed(1)}%`,
|
|
128
|
-
from: `${(oldest / 1024 / 1024).toFixed(1)}MB`,
|
|
129
|
-
to: `${(newest / 1024 / 1024).toFixed(1)}MB`
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Get memory statistics
|
|
136
|
-
* Delegates to Sentry when available
|
|
137
|
-
*/
|
|
138
|
-
getMemoryStats() {
|
|
139
|
-
if (this.sentryClient) {
|
|
140
|
-
return this.sentryClient.getMemoryStats();
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
const current = process.memoryUsage();
|
|
144
|
-
return {
|
|
145
|
-
current: {
|
|
146
|
-
heapUsed: current.heapUsed,
|
|
147
|
-
heapTotal: current.heapTotal,
|
|
148
|
-
external: current.external,
|
|
149
|
-
rss: current.rss
|
|
150
|
-
},
|
|
151
|
-
history: this.memorySnapshots
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Stop APM monitoring
|
|
157
|
-
*/
|
|
158
|
-
stop() {
|
|
159
|
-
if (this.memoryCheckInterval) {
|
|
160
|
-
clearInterval(this.memoryCheckInterval);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|