nest-scheduler-engine 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +374 -0
- package/dist/decorators/event-handler.decorator.d.ts +2 -0
- package/dist/decorators/event-handler.decorator.js +12 -0
- package/dist/decorators/event-handler.decorator.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/interfaces/cache-adapter.interface.d.ts +7 -0
- package/dist/interfaces/cache-adapter.interface.js +3 -0
- package/dist/interfaces/cache-adapter.interface.js.map +1 -0
- package/dist/interfaces/config.interface.d.ts +43 -0
- package/dist/interfaces/config.interface.js +3 -0
- package/dist/interfaces/config.interface.js.map +1 -0
- package/dist/interfaces/database-adapter.interface.d.ts +8 -0
- package/dist/interfaces/database-adapter.interface.js +3 -0
- package/dist/interfaces/database-adapter.interface.js.map +1 -0
- package/dist/interfaces/event-handler.interface.d.ts +15 -0
- package/dist/interfaces/event-handler.interface.js +3 -0
- package/dist/interfaces/event-handler.interface.js.map +1 -0
- package/dist/interfaces/index.d.ts +6 -0
- package/dist/interfaces/index.js +23 -0
- package/dist/interfaces/index.js.map +1 -0
- package/dist/interfaces/logger-adapter.interface.d.ts +6 -0
- package/dist/interfaces/logger-adapter.interface.js +3 -0
- package/dist/interfaces/logger-adapter.interface.js.map +1 -0
- package/dist/interfaces/queue-adapter.interface.d.ts +14 -0
- package/dist/interfaces/queue-adapter.interface.js +3 -0
- package/dist/interfaces/queue-adapter.interface.js.map +1 -0
- package/dist/migrations/index.d.ts +9 -0
- package/dist/migrations/index.js +142 -0
- package/dist/migrations/index.js.map +1 -0
- package/dist/models/dead-letter.model.d.ts +8 -0
- package/dist/models/dead-letter.model.js +10 -0
- package/dist/models/dead-letter.model.js.map +1 -0
- package/dist/models/event-execution.model.d.ts +13 -0
- package/dist/models/event-execution.model.js +19 -0
- package/dist/models/event-execution.model.js.map +1 -0
- package/dist/models/event-instance.model.d.ts +22 -0
- package/dist/models/event-instance.model.js +22 -0
- package/dist/models/event-instance.model.js.map +1 -0
- package/dist/models/event-type.model.d.ts +14 -0
- package/dist/models/event-type.model.js +17 -0
- package/dist/models/event-type.model.js.map +1 -0
- package/dist/models/index.d.ts +4 -0
- package/dist/models/index.js +21 -0
- package/dist/models/index.js.map +1 -0
- package/dist/registry/handler.registry.d.ts +18 -0
- package/dist/registry/handler.registry.js +69 -0
- package/dist/registry/handler.registry.js.map +1 -0
- package/dist/scheduler.module.d.ts +10 -0
- package/dist/scheduler.module.js +183 -0
- package/dist/scheduler.module.js.map +1 -0
- package/dist/services/dispatcher.service.d.ts +26 -0
- package/dist/services/dispatcher.service.js +250 -0
- package/dist/services/dispatcher.service.js.map +1 -0
- package/dist/services/event-manager.service.d.ts +31 -0
- package/dist/services/event-manager.service.js +319 -0
- package/dist/services/event-manager.service.js.map +1 -0
- package/dist/services/polling-engine.service.d.ts +23 -0
- package/dist/services/polling-engine.service.js +162 -0
- package/dist/services/polling-engine.service.js.map +1 -0
- package/dist/services/scheduler.service.d.ts +24 -0
- package/dist/services/scheduler.service.js +67 -0
- package/dist/services/scheduler.service.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/types/enums.d.ts +21 -0
- package/dist/types/enums.js +29 -0
- package/dist/types/enums.js.map +1 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.js +19 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/inputs.d.ts +33 -0
- package/dist/types/inputs.js +3 -0
- package/dist/types/inputs.js.map +1 -0
- package/dist/utils/console-logger.d.ts +7 -0
- package/dist/utils/console-logger.js +19 -0
- package/dist/utils/console-logger.js.map +1 -0
- package/dist/utils/retry-helper.d.ts +3 -0
- package/dist/utils/retry-helper.js +16 -0
- package/dist/utils/retry-helper.js.map +1 -0
- package/dist/utils/rrule-helper.d.ts +2 -0
- package/dist/utils/rrule-helper.js +24 -0
- package/dist/utils/rrule-helper.js.map +1 -0
- package/package.json +50 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Your Organization
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
# @your-org/scheduler-engine
|
|
2
|
+
|
|
3
|
+
A generic, event-type-driven scheduling engine for NestJS applications. This package provides a flexible and scalable solution for scheduling and executing recurring and one-time events with built-in retry logic, dead-letter handling, and RRULE support (RFC 5545).
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Zero Infrastructure Ownership** - Package never creates DB connections, queues, or caches. All are injected via adapters.
|
|
8
|
+
- **Adapter Pattern** - Works with any PostgreSQL client (pg, TypeORM, Knex, Prisma) and queue system (SQS, BullMQ, RabbitMQ).
|
|
9
|
+
- **Ships Database Migrations** - Includes migration files that consumers run against their own database.
|
|
10
|
+
- **Data-Driven Scheduling** - All scheduling decisions from DB, no hardcoded schedules.
|
|
11
|
+
- **RRULE Support** - RFC 5545 compliant recurrence rules for complex recurring schedules.
|
|
12
|
+
- **Built-in Retry & Dead-Letter** - Configurable retry policies with exponential backoff and dead-letter queue.
|
|
13
|
+
- **Framework-Agnostic Core** - Core logic is framework-agnostic with a NestJS wrapper module.
|
|
14
|
+
- **Horizontal Scalability** - Multiple instances can run concurrently with database-level locking.
|
|
15
|
+
- **Observability Hooks** - Lifecycle hooks for monitoring, alerting, and audit logging.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @your-org/scheduler-engine
|
|
21
|
+
|
|
22
|
+
# Peer dependencies (consumer must install)
|
|
23
|
+
npm install @nestjs/core @nestjs/common rxjs
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
### 1. Run Migrations
|
|
29
|
+
|
|
30
|
+
First, export and run the migrations against your database:
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import { getMigrations } from '@your-org/scheduler-engine';
|
|
34
|
+
|
|
35
|
+
const migrations = getMigrations({ tablePrefix: 'scheduler_' });
|
|
36
|
+
|
|
37
|
+
// Use your migration tool (Knex, TypeORM, Flyway, etc.)
|
|
38
|
+
// migrations is an array of { version, up, down }
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Or use the CLI helper:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npx scheduler-engine migrations:export --output ./migrations/
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### 2. Implement Adapters
|
|
48
|
+
|
|
49
|
+
Create adapter implementations for your infrastructure:
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
// adapters/pg-database.adapter.ts
|
|
53
|
+
import { Injectable } from '@nestjs/common';
|
|
54
|
+
import { Pool } from 'pg';
|
|
55
|
+
import { IDatabaseAdapter, ITransactionClient } from '@your-org/scheduler-engine';
|
|
56
|
+
|
|
57
|
+
@Injectable()
|
|
58
|
+
export class PgDatabaseAdapter implements IDatabaseAdapter {
|
|
59
|
+
constructor(private readonly pool: Pool) {}
|
|
60
|
+
|
|
61
|
+
async query<T = any>(sql: string, params?: any[]): Promise<T[]> {
|
|
62
|
+
const result = await this.pool.query(sql, params);
|
|
63
|
+
return result.rows;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async transaction<T>(fn: (trx: ITransactionClient) => Promise<T>): Promise<T> {
|
|
67
|
+
const client = await this.pool.connect();
|
|
68
|
+
try {
|
|
69
|
+
await client.query('BEGIN');
|
|
70
|
+
const trx = {
|
|
71
|
+
query: async <T = any>(sql: string, params?: any[]) => {
|
|
72
|
+
const result = await client.query(sql, params);
|
|
73
|
+
return result.rows;
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
const result = await fn(trx);
|
|
77
|
+
await client.query('COMMIT');
|
|
78
|
+
return result;
|
|
79
|
+
} catch (error) {
|
|
80
|
+
await client.query('ROLLBACK');
|
|
81
|
+
throw error;
|
|
82
|
+
} finally {
|
|
83
|
+
client.release();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async ping(): Promise<boolean> {
|
|
88
|
+
try {
|
|
89
|
+
await this.pool.query('SELECT 1');
|
|
90
|
+
return true;
|
|
91
|
+
} catch {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
// adapters/sqs-queue.adapter.ts
|
|
100
|
+
import { Injectable } from '@nestjs/common';
|
|
101
|
+
import { SQSClient, SendMessageCommand } from '@aws-sdk/client-sqs';
|
|
102
|
+
import { IQueueAdapter, QueueMessage } from '@your-org/scheduler-engine';
|
|
103
|
+
|
|
104
|
+
@Injectable()
|
|
105
|
+
export class SqsQueueAdapter implements IQueueAdapter {
|
|
106
|
+
constructor(
|
|
107
|
+
private readonly sqs: SQSClient,
|
|
108
|
+
private readonly queueUrl: string,
|
|
109
|
+
) {}
|
|
110
|
+
|
|
111
|
+
async publish(message: QueueMessage): Promise<void> {
|
|
112
|
+
await this.sqs.send(new SendMessageCommand({
|
|
113
|
+
QueueUrl: this.queueUrl,
|
|
114
|
+
MessageBody: JSON.stringify(message),
|
|
115
|
+
}));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async subscribe(handler: (msg: QueueMessage) => Promise<void>): Promise<void> {
|
|
119
|
+
// Implement SQS polling and handler invocation
|
|
120
|
+
// This is typically done in a separate worker process
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async ack(messageId: string): Promise<void> {
|
|
124
|
+
// Delete message from SQS
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async nack(messageId: string): Promise<void> {
|
|
128
|
+
// Change message visibility or move to DLQ
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### 3. Register the Module
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
// app.module.ts
|
|
137
|
+
import { Module } from '@nestjs/common';
|
|
138
|
+
import { SchedulerModule } from '@your-org/scheduler-engine';
|
|
139
|
+
import { PgDatabaseAdapter } from './adapters/pg-database.adapter';
|
|
140
|
+
import { SqsQueueAdapter } from './adapters/sqs-queue.adapter';
|
|
141
|
+
import { Pool } from 'pg';
|
|
142
|
+
|
|
143
|
+
@Module({
|
|
144
|
+
imports: [
|
|
145
|
+
SchedulerModule.forRoot({
|
|
146
|
+
// Required adapters
|
|
147
|
+
database: {
|
|
148
|
+
useFactory: (pool: Pool) => new PgDatabaseAdapter(pool),
|
|
149
|
+
inject: [Pool],
|
|
150
|
+
},
|
|
151
|
+
queue: {
|
|
152
|
+
useFactory: (sqs: SQSClient) => new SqsQueueAdapter(sqs, process.env.QUEUE_URL),
|
|
153
|
+
inject: [SQSClient],
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
// Optional configuration
|
|
157
|
+
config: {
|
|
158
|
+
pollingIntervalMs: 5000,
|
|
159
|
+
lockDurationMs: 30000,
|
|
160
|
+
batchSize: 50,
|
|
161
|
+
concurrency: 10,
|
|
162
|
+
deadLetterEnabled: true,
|
|
163
|
+
tablePrefix: 'scheduler_',
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
// Optional hooks
|
|
167
|
+
hooks: {
|
|
168
|
+
onEventScheduled: async (event) => {
|
|
169
|
+
console.log('Event scheduled:', event.id);
|
|
170
|
+
},
|
|
171
|
+
onEventFailed: async (event, error, retryCount) => {
|
|
172
|
+
console.error('Event failed:', event.id, error);
|
|
173
|
+
},
|
|
174
|
+
onEventDeadLettered: async (event) => {
|
|
175
|
+
// Send alert to oncall
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
}),
|
|
179
|
+
],
|
|
180
|
+
providers: [
|
|
181
|
+
// Your event handlers
|
|
182
|
+
ReminderHandler,
|
|
183
|
+
DataSyncHandler,
|
|
184
|
+
ReportHandler,
|
|
185
|
+
],
|
|
186
|
+
})
|
|
187
|
+
export class AppModule {}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### 4. Create Event Handlers
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
// handlers/reminder.handler.ts
|
|
194
|
+
import { Injectable } from '@nestjs/common';
|
|
195
|
+
import { EventHandler, IEventHandler, ExecutionContext, HandlerResult } from '@your-org/scheduler-engine';
|
|
196
|
+
|
|
197
|
+
@Injectable()
|
|
198
|
+
@EventHandler('REMINDER_NOTIFICATION')
|
|
199
|
+
export class ReminderHandler implements IEventHandler {
|
|
200
|
+
readonly eventType = 'REMINDER_NOTIFICATION';
|
|
201
|
+
|
|
202
|
+
constructor(
|
|
203
|
+
private readonly notificationService: NotificationService,
|
|
204
|
+
) {}
|
|
205
|
+
|
|
206
|
+
async handle(payload: Record<string, any>, ctx: ExecutionContext): Promise<HandlerResult> {
|
|
207
|
+
try {
|
|
208
|
+
await this.notificationService.send(payload.userId, payload.message);
|
|
209
|
+
return { success: true };
|
|
210
|
+
} catch (error) {
|
|
211
|
+
return {
|
|
212
|
+
success: false,
|
|
213
|
+
error: error instanceof Error ? error.message : String(error),
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### 5. Schedule Events
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
// some.service.ts
|
|
224
|
+
import { Injectable } from '@nestjs/common';
|
|
225
|
+
import { SchedulerService, ScheduleType } from '@your-org/scheduler-engine';
|
|
226
|
+
|
|
227
|
+
@Injectable()
|
|
228
|
+
export class NotificationService {
|
|
229
|
+
constructor(private readonly scheduler: SchedulerService) {}
|
|
230
|
+
|
|
231
|
+
async scheduleReminder(userId: string, message: string, sendAt: Date) {
|
|
232
|
+
// First, create the event type (usually done once on app startup)
|
|
233
|
+
await this.scheduler.createEventType({
|
|
234
|
+
name: 'REMINDER_NOTIFICATION',
|
|
235
|
+
description: 'Send reminder notifications to users',
|
|
236
|
+
retryPolicy: {
|
|
237
|
+
maxRetries: 3,
|
|
238
|
+
delayMs: 5000,
|
|
239
|
+
backoff: 'exponential',
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// Schedule a one-time event
|
|
244
|
+
const event = await this.scheduler.scheduleEvent({
|
|
245
|
+
eventTypeName: 'REMINDER_NOTIFICATION',
|
|
246
|
+
payload: { userId, message },
|
|
247
|
+
scheduleType: ScheduleType.ONE_TIME,
|
|
248
|
+
scheduledAt: sendAt,
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
return event;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async scheduleRecurringReport() {
|
|
255
|
+
// Schedule a recurring event using RRULE
|
|
256
|
+
const event = await this.scheduler.scheduleEvent({
|
|
257
|
+
eventTypeName: 'WEEKLY_REPORT',
|
|
258
|
+
payload: { reportType: 'sales' },
|
|
259
|
+
scheduleType: ScheduleType.RECURRING,
|
|
260
|
+
rrule: 'FREQ=WEEKLY;BYDAY=MO;BYHOUR=9;BYMINUTE=0', // Every Monday at 9:00 AM
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
return event;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## API Reference
|
|
269
|
+
|
|
270
|
+
### SchedulerService
|
|
271
|
+
|
|
272
|
+
Main service for interacting with the scheduler.
|
|
273
|
+
|
|
274
|
+
#### Event Type Management
|
|
275
|
+
|
|
276
|
+
- `createEventType(input: CreateEventTypeInput): Promise<EventType>`
|
|
277
|
+
- `getEventType(id: string): Promise<EventType | null>`
|
|
278
|
+
- `getEventTypeByName(name: string): Promise<EventType | null>`
|
|
279
|
+
- `listEventTypes(): Promise<EventType[]>`
|
|
280
|
+
- `deleteEventType(id: string): Promise<void>`
|
|
281
|
+
|
|
282
|
+
#### Event Instance Management
|
|
283
|
+
|
|
284
|
+
- `scheduleEvent(input: ScheduleEventInput): Promise<EventInstance>`
|
|
285
|
+
- `getEvent(id: string): Promise<EventInstance | null>`
|
|
286
|
+
- `listEvents(filters?: EventFilters): Promise<PaginatedResult<EventInstance>>`
|
|
287
|
+
- `pauseEvent(id: string): Promise<EventInstance>`
|
|
288
|
+
- `resumeEvent(id: string): Promise<EventInstance>`
|
|
289
|
+
- `cancelEvent(id: string): Promise<void>`
|
|
290
|
+
|
|
291
|
+
#### Execution History
|
|
292
|
+
|
|
293
|
+
- `getExecutions(eventId: string): Promise<EventExecution[]>`
|
|
294
|
+
|
|
295
|
+
#### Dead Letters
|
|
296
|
+
|
|
297
|
+
- `listDeadLetters(): Promise<DeadLetter[]>`
|
|
298
|
+
- `retryDeadLetter(id: string): Promise<EventInstance>`
|
|
299
|
+
|
|
300
|
+
## Deployment Modes
|
|
301
|
+
|
|
302
|
+
The package supports three deployment modes:
|
|
303
|
+
|
|
304
|
+
### Combined Mode (Default)
|
|
305
|
+
|
|
306
|
+
Polls + dispatches + consumes in one process.
|
|
307
|
+
|
|
308
|
+
```typescript
|
|
309
|
+
config: {
|
|
310
|
+
workerMode: true, // default
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### Poller Only
|
|
315
|
+
|
|
316
|
+
Only polls and dispatches to queue. Separate workers consume.
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
config: {
|
|
320
|
+
pollerOnly: true,
|
|
321
|
+
}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### Worker Only
|
|
325
|
+
|
|
326
|
+
Only consumes from queue. Separate poller dispatches.
|
|
327
|
+
|
|
328
|
+
```typescript
|
|
329
|
+
config: {
|
|
330
|
+
workerMode: true,
|
|
331
|
+
pollerOnly: false,
|
|
332
|
+
}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
## RRULE Examples
|
|
336
|
+
|
|
337
|
+
```typescript
|
|
338
|
+
// Every day at 10:00 AM
|
|
339
|
+
rrule: 'FREQ=DAILY;BYHOUR=10;BYMINUTE=0'
|
|
340
|
+
|
|
341
|
+
// Every Monday and Friday at 9:00 AM
|
|
342
|
+
rrule: 'FREQ=WEEKLY;BYDAY=MO,FR;BYHOUR=9;BYMINUTE=0'
|
|
343
|
+
|
|
344
|
+
// Last day of every month at 11:59 PM
|
|
345
|
+
rrule: 'FREQ=MONTHLY;BYMONTHDAY=-1;BYHOUR=23;BYMINUTE=59'
|
|
346
|
+
|
|
347
|
+
// Every 2 hours
|
|
348
|
+
rrule: 'FREQ=HOURLY;INTERVAL=2'
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
See [RFC 5545](https://tools.ietf.org/html/rfc5545) for full RRULE specification.
|
|
352
|
+
|
|
353
|
+
## Database Schema
|
|
354
|
+
|
|
355
|
+
The package creates the following tables:
|
|
356
|
+
|
|
357
|
+
- `event_types` - Event type definitions with retry policies
|
|
358
|
+
- `event_instances` - Scheduled event instances
|
|
359
|
+
- `event_executions` - Execution history for audit and debugging
|
|
360
|
+
- `dead_letters` - Failed events that exhausted retries
|
|
361
|
+
|
|
362
|
+
All tables support an optional prefix via `tablePrefix` config option.
|
|
363
|
+
|
|
364
|
+
## License
|
|
365
|
+
|
|
366
|
+
MIT
|
|
367
|
+
|
|
368
|
+
## Contributing
|
|
369
|
+
|
|
370
|
+
Contributions are welcome! Please see CONTRIBUTING.md for details.
|
|
371
|
+
|
|
372
|
+
## Support
|
|
373
|
+
|
|
374
|
+
For issues and feature requests, please use the GitHub issue tracker.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EVENT_HANDLER_METADATA = void 0;
|
|
4
|
+
exports.EventHandler = EventHandler;
|
|
5
|
+
const common_1 = require("@nestjs/common");
|
|
6
|
+
exports.EVENT_HANDLER_METADATA = "scheduler:event_handler";
|
|
7
|
+
function EventHandler(eventType) {
|
|
8
|
+
return (target) => {
|
|
9
|
+
(0, common_1.SetMetadata)(exports.EVENT_HANDLER_METADATA, eventType)(target);
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=event-handler.decorator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-handler.decorator.js","sourceRoot":"","sources":["../../src/decorators/event-handler.decorator.ts"],"names":[],"mappings":";;;AAWA,oCAIC;AAfD,2CAA6C;AAKhC,QAAA,sBAAsB,GAAG,yBAAyB,CAAC;AAMhE,SAAgB,YAAY,CAAC,SAAiB;IAC5C,OAAO,CAAC,MAAW,EAAE,EAAE;QACrB,IAAA,oBAAW,EAAC,8BAAsB,EAAE,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC;IACzD,CAAC,CAAC;AACJ,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export { SchedulerModule } from './scheduler.module';
|
|
2
|
+
export { SchedulerService } from './services/scheduler.service';
|
|
3
|
+
export { EventHandler } from './decorators/event-handler.decorator';
|
|
4
|
+
export type { IDatabaseAdapter, ITransactionClient, } from './interfaces/database-adapter.interface';
|
|
5
|
+
export type { IQueueAdapter, QueueMessage, } from './interfaces/queue-adapter.interface';
|
|
6
|
+
export type { ILoggerAdapter } from './interfaces/logger-adapter.interface';
|
|
7
|
+
export type { ICacheAdapter } from './interfaces/cache-adapter.interface';
|
|
8
|
+
export type { IEventHandler, HandlerResult, ExecutionContext, } from './interfaces/event-handler.interface';
|
|
9
|
+
export type { SchedulerConfig, SchedulerHooks, SchedulerModuleOptions, SchedulerModuleAsyncOptions, AdapterProvider, } from './interfaces/config.interface';
|
|
10
|
+
export { EventStatus, ScheduleType, RetryBackoff, ExecutionStatus, } from './types/enums';
|
|
11
|
+
export type { RetryPolicy, CreateEventTypeInput, ScheduleEventInput, EventFilters, PaginatedResult, } from './types/inputs';
|
|
12
|
+
export { EventType } from './models/event-type.model';
|
|
13
|
+
export { EventInstance } from './models/event-instance.model';
|
|
14
|
+
export { EventExecution } from './models/event-execution.model';
|
|
15
|
+
export { DeadLetter } from './models/dead-letter.model';
|
|
16
|
+
export { getMigrations, Migration, MigrationOptions } from './migrations';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getMigrations = exports.DeadLetter = exports.EventExecution = exports.EventInstance = exports.EventType = exports.ExecutionStatus = exports.RetryBackoff = exports.ScheduleType = exports.EventStatus = exports.EventHandler = exports.SchedulerService = exports.SchedulerModule = void 0;
|
|
4
|
+
var scheduler_module_1 = require("./scheduler.module");
|
|
5
|
+
Object.defineProperty(exports, "SchedulerModule", { enumerable: true, get: function () { return scheduler_module_1.SchedulerModule; } });
|
|
6
|
+
var scheduler_service_1 = require("./services/scheduler.service");
|
|
7
|
+
Object.defineProperty(exports, "SchedulerService", { enumerable: true, get: function () { return scheduler_service_1.SchedulerService; } });
|
|
8
|
+
var event_handler_decorator_1 = require("./decorators/event-handler.decorator");
|
|
9
|
+
Object.defineProperty(exports, "EventHandler", { enumerable: true, get: function () { return event_handler_decorator_1.EventHandler; } });
|
|
10
|
+
var enums_1 = require("./types/enums");
|
|
11
|
+
Object.defineProperty(exports, "EventStatus", { enumerable: true, get: function () { return enums_1.EventStatus; } });
|
|
12
|
+
Object.defineProperty(exports, "ScheduleType", { enumerable: true, get: function () { return enums_1.ScheduleType; } });
|
|
13
|
+
Object.defineProperty(exports, "RetryBackoff", { enumerable: true, get: function () { return enums_1.RetryBackoff; } });
|
|
14
|
+
Object.defineProperty(exports, "ExecutionStatus", { enumerable: true, get: function () { return enums_1.ExecutionStatus; } });
|
|
15
|
+
var event_type_model_1 = require("./models/event-type.model");
|
|
16
|
+
Object.defineProperty(exports, "EventType", { enumerable: true, get: function () { return event_type_model_1.EventType; } });
|
|
17
|
+
var event_instance_model_1 = require("./models/event-instance.model");
|
|
18
|
+
Object.defineProperty(exports, "EventInstance", { enumerable: true, get: function () { return event_instance_model_1.EventInstance; } });
|
|
19
|
+
var event_execution_model_1 = require("./models/event-execution.model");
|
|
20
|
+
Object.defineProperty(exports, "EventExecution", { enumerable: true, get: function () { return event_execution_model_1.EventExecution; } });
|
|
21
|
+
var dead_letter_model_1 = require("./models/dead-letter.model");
|
|
22
|
+
Object.defineProperty(exports, "DeadLetter", { enumerable: true, get: function () { return dead_letter_model_1.DeadLetter; } });
|
|
23
|
+
var migrations_1 = require("./migrations");
|
|
24
|
+
Object.defineProperty(exports, "getMigrations", { enumerable: true, get: function () { return migrations_1.getMigrations; } });
|
|
25
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AACA,uDAAqD;AAA5C,mHAAA,eAAe,OAAA;AAGxB,kEAAgE;AAAvD,qHAAA,gBAAgB,OAAA;AAGzB,gFAAoE;AAA3D,uHAAA,YAAY,OAAA;AAgCrB,uCAKuB;AAJrB,oGAAA,WAAW,OAAA;AACX,qGAAA,YAAY,OAAA;AACZ,qGAAA,YAAY,OAAA;AACZ,wGAAA,eAAe,OAAA;AAYjB,8DAAsD;AAA7C,6GAAA,SAAS,OAAA;AAClB,sEAA8D;AAArD,qHAAA,aAAa,OAAA;AACtB,wEAAgE;AAAvD,uHAAA,cAAc,OAAA;AACvB,gEAAwD;AAA/C,+GAAA,UAAU,OAAA;AAGnB,2CAA0E;AAAjE,2GAAA,aAAa,OAAA"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface ICacheAdapter {
|
|
2
|
+
get(key: string): Promise<any | null>;
|
|
3
|
+
set(key: string, value: any, ttlMs?: number): Promise<void>;
|
|
4
|
+
del(key: string): Promise<void>;
|
|
5
|
+
acquireLock(key: string, ttlMs: number): Promise<boolean>;
|
|
6
|
+
releaseLock(key: string): Promise<void>;
|
|
7
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache-adapter.interface.js","sourceRoot":"","sources":["../../src/interfaces/cache-adapter.interface.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { ModuleMetadata, Type } from "@nestjs/common";
|
|
2
|
+
import { IDatabaseAdapter } from "./database-adapter.interface";
|
|
3
|
+
import { IQueueAdapter } from "./queue-adapter.interface";
|
|
4
|
+
import { ILoggerAdapter } from "./logger-adapter.interface";
|
|
5
|
+
import { ICacheAdapter } from "./cache-adapter.interface";
|
|
6
|
+
import { HandlerResult, ExecutionContext } from "./event-handler.interface";
|
|
7
|
+
export interface SchedulerConfig {
|
|
8
|
+
pollingIntervalMs?: number;
|
|
9
|
+
lockDurationMs?: number;
|
|
10
|
+
batchSize?: number;
|
|
11
|
+
concurrency?: number;
|
|
12
|
+
deadLetterEnabled?: boolean;
|
|
13
|
+
tablePrefix?: string;
|
|
14
|
+
workerMode?: boolean;
|
|
15
|
+
pollerOnly?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export interface SchedulerHooks {
|
|
18
|
+
onEventScheduled?: (event: any) => Promise<void>;
|
|
19
|
+
onEventExecuting?: (event: any, ctx: ExecutionContext) => Promise<void>;
|
|
20
|
+
onEventCompleted?: (event: any, result: HandlerResult) => Promise<void>;
|
|
21
|
+
onEventFailed?: (event: any, error: any, retryCount: number) => Promise<void>;
|
|
22
|
+
onEventDeadLettered?: (event: any) => Promise<void>;
|
|
23
|
+
onEventPaused?: (event: any) => Promise<void>;
|
|
24
|
+
onEventResumed?: (event: any) => Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
export interface AdapterProvider<T = any> {
|
|
27
|
+
useClass?: Type<T>;
|
|
28
|
+
useFactory?: (...args: any[]) => T | Promise<T>;
|
|
29
|
+
useExisting?: Type<T>;
|
|
30
|
+
inject?: any[];
|
|
31
|
+
}
|
|
32
|
+
export interface SchedulerModuleOptions {
|
|
33
|
+
database: AdapterProvider<IDatabaseAdapter>;
|
|
34
|
+
queue: AdapterProvider<IQueueAdapter>;
|
|
35
|
+
logger?: AdapterProvider<ILoggerAdapter>;
|
|
36
|
+
cache?: AdapterProvider<ICacheAdapter>;
|
|
37
|
+
config?: SchedulerConfig;
|
|
38
|
+
hooks?: SchedulerHooks;
|
|
39
|
+
}
|
|
40
|
+
export interface SchedulerModuleAsyncOptions extends Pick<ModuleMetadata, "imports"> {
|
|
41
|
+
useFactory?: (...args: any[]) => Promise<SchedulerModuleOptions> | SchedulerModuleOptions;
|
|
42
|
+
inject?: any[];
|
|
43
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.interface.js","sourceRoot":"","sources":["../../src/interfaces/config.interface.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface ITransactionClient {
|
|
2
|
+
query<T = any>(sql: string, params?: any[]): Promise<T[]>;
|
|
3
|
+
}
|
|
4
|
+
export interface IDatabaseAdapter {
|
|
5
|
+
query<T = any>(sql: string, params?: any[]): Promise<T[]>;
|
|
6
|
+
transaction<T>(fn: (trx: ITransactionClient) => Promise<T>): Promise<T>;
|
|
7
|
+
ping(): Promise<boolean>;
|
|
8
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"database-adapter.interface.js","sourceRoot":"","sources":["../../src/interfaces/database-adapter.interface.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface ExecutionContext {
|
|
2
|
+
executionId: string;
|
|
3
|
+
eventInstanceId: string;
|
|
4
|
+
attempt: number;
|
|
5
|
+
scheduledAt: Date;
|
|
6
|
+
}
|
|
7
|
+
export interface HandlerResult {
|
|
8
|
+
success: boolean;
|
|
9
|
+
data?: any;
|
|
10
|
+
error?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface IEventHandler {
|
|
13
|
+
readonly eventType: string;
|
|
14
|
+
handle(payload: Record<string, any>, ctx: ExecutionContext): Promise<HandlerResult>;
|
|
15
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-handler.interface.js","sourceRoot":"","sources":["../../src/interfaces/event-handler.interface.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,23 @@
|
|
|
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 __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./database-adapter.interface"), exports);
|
|
18
|
+
__exportStar(require("./queue-adapter.interface"), exports);
|
|
19
|
+
__exportStar(require("./logger-adapter.interface"), exports);
|
|
20
|
+
__exportStar(require("./cache-adapter.interface"), exports);
|
|
21
|
+
__exportStar(require("./event-handler.interface"), exports);
|
|
22
|
+
__exportStar(require("./config.interface"), exports);
|
|
23
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/interfaces/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,+DAA6C;AAC7C,4DAA0C;AAC1C,6DAA2C;AAC3C,4DAA0C;AAC1C,4DAA0C;AAC1C,qDAAmC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export interface ILoggerAdapter {
|
|
2
|
+
info(message: string, meta?: Record<string, any>): void;
|
|
3
|
+
warn(message: string, meta?: Record<string, any>): void;
|
|
4
|
+
error(message: string, meta?: Record<string, any>): void;
|
|
5
|
+
debug(message: string, meta?: Record<string, any>): void;
|
|
6
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger-adapter.interface.js","sourceRoot":"","sources":["../../src/interfaces/logger-adapter.interface.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface QueueMessage {
|
|
2
|
+
id: string;
|
|
3
|
+
eventInstanceId: string;
|
|
4
|
+
eventType: string;
|
|
5
|
+
payload: Record<string, any>;
|
|
6
|
+
attempt: number;
|
|
7
|
+
scheduledAt: Date;
|
|
8
|
+
}
|
|
9
|
+
export interface IQueueAdapter {
|
|
10
|
+
publish(message: QueueMessage): Promise<void>;
|
|
11
|
+
subscribe(handler: (msg: QueueMessage) => Promise<void>): Promise<void>;
|
|
12
|
+
ack(messageId: string): Promise<void>;
|
|
13
|
+
nack(messageId: string): Promise<void>;
|
|
14
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue-adapter.interface.js","sourceRoot":"","sources":["../../src/interfaces/queue-adapter.interface.ts"],"names":[],"mappings":""}
|