saas-backend-kit 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.
Files changed (67) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/PUBLISHING.md +133 -0
  3. package/README.md +459 -0
  4. package/copy-dts.js +255 -0
  5. package/dist/auth/index.d.ts +58 -0
  6. package/dist/auth/index.js +584 -0
  7. package/dist/auth/index.js.map +1 -0
  8. package/dist/auth/index.mjs +569 -0
  9. package/dist/auth/index.mjs.map +1 -0
  10. package/dist/config/index.d.ts +22 -0
  11. package/dist/config/index.js +106 -0
  12. package/dist/config/index.js.map +1 -0
  13. package/dist/config/index.mjs +100 -0
  14. package/dist/config/index.mjs.map +1 -0
  15. package/dist/index.d.ts +25 -0
  16. package/dist/index.js +1303 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/index.mjs +1281 -0
  19. package/dist/index.mjs.map +1 -0
  20. package/dist/logger/index.d.ts +18 -0
  21. package/dist/logger/index.js +188 -0
  22. package/dist/logger/index.js.map +1 -0
  23. package/dist/logger/index.mjs +178 -0
  24. package/dist/logger/index.mjs.map +1 -0
  25. package/dist/notifications/index.d.ts +35 -0
  26. package/dist/notifications/index.js +339 -0
  27. package/dist/notifications/index.js.map +1 -0
  28. package/dist/notifications/index.mjs +328 -0
  29. package/dist/notifications/index.mjs.map +1 -0
  30. package/dist/queue/index.d.ts +33 -0
  31. package/dist/queue/index.js +306 -0
  32. package/dist/queue/index.js.map +1 -0
  33. package/dist/queue/index.mjs +293 -0
  34. package/dist/queue/index.mjs.map +1 -0
  35. package/dist/rate-limit/index.d.ts +11 -0
  36. package/dist/rate-limit/index.js +290 -0
  37. package/dist/rate-limit/index.js.map +1 -0
  38. package/dist/rate-limit/index.mjs +286 -0
  39. package/dist/rate-limit/index.mjs.map +1 -0
  40. package/dist/response/index.d.ts +29 -0
  41. package/dist/response/index.js +120 -0
  42. package/dist/response/index.js.map +1 -0
  43. package/dist/response/index.mjs +114 -0
  44. package/dist/response/index.mjs.map +1 -0
  45. package/examples/express/.env.example +41 -0
  46. package/examples/express/app.ts +203 -0
  47. package/package.json +109 -0
  48. package/src/auth/express.ts +250 -0
  49. package/src/auth/fastify.ts +65 -0
  50. package/src/auth/index.ts +6 -0
  51. package/src/auth/jwt.ts +47 -0
  52. package/src/auth/oauth.ts +117 -0
  53. package/src/auth/rbac.ts +82 -0
  54. package/src/auth/types.ts +69 -0
  55. package/src/config/index.ts +120 -0
  56. package/src/index.ts +16 -0
  57. package/src/logger/index.ts +110 -0
  58. package/src/notifications/index.ts +262 -0
  59. package/src/plugin.ts +192 -0
  60. package/src/queue/index.ts +208 -0
  61. package/src/rate-limit/express.ts +144 -0
  62. package/src/rate-limit/fastify.ts +47 -0
  63. package/src/rate-limit/index.ts +2 -0
  64. package/src/response/index.ts +197 -0
  65. package/src/utils/index.ts +180 -0
  66. package/tsconfig.json +30 -0
  67. package/tsup.config.ts +24 -0
@@ -0,0 +1,262 @@
1
+ import nodemailer, { Transporter, SendMailOptions } from 'nodemailer';
2
+ import { config } from '../config';
3
+ import { logger } from '../logger';
4
+
5
+ export interface EmailOptions {
6
+ to: string | string[];
7
+ subject: string;
8
+ text?: string;
9
+ html?: string;
10
+ template?: string;
11
+ templateData?: Record<string, unknown>;
12
+ from?: string;
13
+ cc?: string | string[];
14
+ bcc?: string | string[];
15
+ attachments?: Array<{
16
+ filename: string;
17
+ content?: Buffer | string;
18
+ path?: string;
19
+ contentType?: string;
20
+ }>;
21
+ }
22
+
23
+ export interface SMSOptions {
24
+ to: string;
25
+ message: string;
26
+ from?: string;
27
+ }
28
+
29
+ export interface WebhookOptions {
30
+ url: string;
31
+ method?: 'GET' | 'POST' | 'PUT' | 'PATCH';
32
+ headers?: Record<string, string>;
33
+ body?: unknown;
34
+ timeout?: number;
35
+ }
36
+
37
+ export interface SlackOptions {
38
+ text?: string;
39
+ blocks?: Array<{
40
+ type: string;
41
+ text?: { type: string; text: string; emoji?: boolean };
42
+ elements?: Array<{ type: string; text?: { type: string; text: string } }>;
43
+ accessory?: { type: string; image_url?: string; alt_text?: string };
44
+ }>;
45
+ channel?: string;
46
+ username?: string;
47
+ iconEmoji?: string;
48
+ }
49
+
50
+ export interface NotificationTransporter {
51
+ email: Transporter;
52
+ twilio?: {
53
+ accountSid: string;
54
+ authToken: string;
55
+ phoneNumber: string;
56
+ };
57
+ slack?: {
58
+ webhookUrl: string;
59
+ };
60
+ }
61
+
62
+ class EmailService {
63
+ private transporter: Transporter | null = null;
64
+ private from: string;
65
+
66
+ constructor() {
67
+ this.from = config.get('SMTP_FROM') || 'noreply@example.com';
68
+ this.initialize();
69
+ }
70
+
71
+ private initialize(): void {
72
+ const host = config.get('SMTP_HOST');
73
+ const port = parseInt(config.get('SMTP_PORT') || '587', 10);
74
+ const user = config.get('SMTP_USER');
75
+ const pass = config.get('SMTP_PASS');
76
+
77
+ if (host && user && pass) {
78
+ this.transporter = nodemailer.createTransport({
79
+ host,
80
+ port,
81
+ secure: port === 465,
82
+ auth: {
83
+ user,
84
+ pass,
85
+ },
86
+ });
87
+ logger.info('Email service initialized');
88
+ }
89
+ }
90
+
91
+ setTransporter(transporter: Transporter): void {
92
+ this.transporter = transporter;
93
+ }
94
+
95
+ async send(options: EmailOptions): Promise<{ messageId: string }> {
96
+ if (!this.transporter) {
97
+ logger.warn('Email transporter not configured, skipping email');
98
+ return { messageId: 'mock-message-id' };
99
+ }
100
+
101
+ const mailOptions: SendMailOptions = {
102
+ from: options.from || this.from,
103
+ to: Array.isArray(options.to) ? options.to.join(', ') : options.to,
104
+ subject: options.subject,
105
+ text: options.text,
106
+ html: options.html || this.renderTemplate(options.template, options.templateData),
107
+ cc: options.cc,
108
+ bcc: options.bcc,
109
+ attachments: options.attachments,
110
+ };
111
+
112
+ const result = await this.transporter.sendMail(mailOptions);
113
+ logger.info(`Email sent to ${options.to}`, { messageId: result.messageId });
114
+ return { messageId: result.messageId };
115
+ }
116
+
117
+ private renderTemplate(templateName?: string, data?: Record<string, unknown>): string | undefined {
118
+ if (!templateName || !data) return undefined;
119
+
120
+ const templates: Record<string, (data: Record<string, unknown>) => string> = {
121
+ welcome: (data) => `
122
+ <h1>Welcome, ${data.name || 'User'}!</h1>
123
+ <p>Thank you for joining ${data.appName || 'our platform'}.</p>
124
+ <p>Get started by verifying your email.</p>
125
+ `,
126
+ passwordReset: (data) => `
127
+ <h1>Password Reset</h1>
128
+ <p>Click <a href="${data.resetUrl}">here</a> to reset your password.</p>
129
+ <p>This link expires in ${data.expiry || '1 hour'}.</p>
130
+ `,
131
+ verification: (data) => `
132
+ <h1>Verify Your Email</h1>
133
+ <p>Click <a href="${data.verifyUrl}">here</a> to verify your email.</p>
134
+ `,
135
+ };
136
+
137
+ const template = templates[templateName];
138
+ return template ? template(data) : undefined;
139
+ }
140
+ }
141
+
142
+ class SMSService {
143
+ private accountSid: string;
144
+ private authToken: string;
145
+ private phoneNumber: string;
146
+ private initialized: boolean = false;
147
+
148
+ constructor() {
149
+ this.accountSid = config.get('TWILIO_ACCOUNT_SID') || '';
150
+ this.authToken = config.get('TWILIO_AUTH_TOKEN') || '';
151
+ this.phoneNumber = config.get('TWILIO_PHONE_NUMBER') || '';
152
+ this.initialized = !!(this.accountSid && this.authToken && this.phoneNumber);
153
+ }
154
+
155
+ async send(options: SMSOptions): Promise<{ sid: string }> {
156
+ if (!this.initialized) {
157
+ logger.warn('Twilio not configured, skipping SMS');
158
+ return { sid: 'mock-sid' };
159
+ }
160
+
161
+ try {
162
+ const twilio = await import('twilio');
163
+ const client = twilio.default(this.accountSid, this.authToken);
164
+
165
+ const result = await client.messages.create({
166
+ body: options.message,
167
+ from: options.from || this.phoneNumber,
168
+ to: options.to,
169
+ });
170
+
171
+ logger.info(`SMS sent to ${options.to}`, { sid: result.sid });
172
+ return { sid: result.sid };
173
+ } catch (error) {
174
+ logger.error('Failed to send SMS', { error });
175
+ throw error;
176
+ }
177
+ }
178
+ }
179
+
180
+ class WebhookService {
181
+ async send(options: WebhookOptions): Promise<{ status: number; body: unknown }> {
182
+ const response = await fetch(options.url, {
183
+ method: options.method || 'POST',
184
+ headers: {
185
+ 'Content-Type': 'application/json',
186
+ ...options.headers,
187
+ },
188
+ body: options.body ? JSON.stringify(options.body) : undefined,
189
+ signal: options.timeout ? AbortSignal.timeout(options.timeout) : undefined,
190
+ });
191
+
192
+ const body = await response.json().catch(() => null);
193
+ logger.info(`Webhook sent to ${options.url}`, { status: response.status });
194
+ return { status: response.status, body };
195
+ }
196
+ }
197
+
198
+ class SlackService {
199
+ private webhookUrl: string;
200
+
201
+ constructor() {
202
+ this.webhookUrl = config.get('SLACK_WEBHOOK_URL') || '';
203
+ }
204
+
205
+ setWebhookUrl(url: string): void {
206
+ this.webhookUrl = url;
207
+ }
208
+
209
+ async send(options: SlackOptions): Promise<{ ok: boolean }> {
210
+ if (!this.webhookUrl) {
211
+ logger.warn('Slack webhook not configured, skipping message');
212
+ return { ok: false };
213
+ }
214
+
215
+ const payload = {
216
+ text: options.text,
217
+ blocks: options.blocks,
218
+ channel: options.channel,
219
+ username: options.username,
220
+ icon_emoji: options.iconEmoji,
221
+ };
222
+
223
+ const response = await fetch(this.webhookUrl, {
224
+ method: 'POST',
225
+ headers: { 'Content-Type': 'application/json' },
226
+ body: JSON.stringify(payload),
227
+ });
228
+
229
+ const ok = response.ok;
230
+ if (ok) {
231
+ logger.info('Slack message sent');
232
+ } else {
233
+ logger.error('Failed to send Slack message');
234
+ }
235
+ return { ok };
236
+ }
237
+ }
238
+
239
+ class NotificationService {
240
+ email: EmailService;
241
+ sms: SMSService;
242
+ webhook: WebhookService;
243
+ slack: SlackService;
244
+
245
+ constructor() {
246
+ this.email = new EmailService();
247
+ this.sms = new SMSService();
248
+ this.webhook = new WebhookService();
249
+ this.slack = new SlackService();
250
+ }
251
+ }
252
+
253
+ export const notify = new NotificationService();
254
+
255
+ export const notification = {
256
+ email: (options: EmailOptions) => notify.email.send(options),
257
+ sms: (options: SMSOptions) => notify.sms.send(options),
258
+ webhook: (options: WebhookOptions) => notify.webhook.send(options),
259
+ slack: (options: SlackOptions) => notify.slack.send(options),
260
+ };
261
+
262
+ export default notification;
package/src/plugin.ts ADDED
@@ -0,0 +1,192 @@
1
+ import { Application, Request, Response, NextFunction, RequestHandler } from 'express';
2
+ import { FastifyInstance } from 'fastify';
3
+ import { createAuth, AuthService } from './auth';
4
+ import { createQueue, QueueManager } from './queue';
5
+ import { notify, notification } from './notifications';
6
+ import { logger } from './logger';
7
+ import { rateLimit } from './rate-limit';
8
+ import { config } from './config';
9
+ import { ResponseHelper } from './response';
10
+
11
+ export interface AppOptions {
12
+ framework?: 'express' | 'fastify';
13
+ auth?: boolean | {
14
+ jwtSecret?: string;
15
+ jwtExpiresIn?: string;
16
+ refreshSecret?: string;
17
+ refreshExpiresIn?: string;
18
+ googleClientId?: string;
19
+ googleClientSecret?: string;
20
+ googleRedirectUri?: string;
21
+ };
22
+ queue?: boolean | {
23
+ redisUrl?: string;
24
+ };
25
+ notifications?: boolean;
26
+ rateLimit?: boolean | {
27
+ window?: string;
28
+ limit?: number;
29
+ };
30
+ logger?: boolean | {
31
+ level?: string;
32
+ prettyPrint?: boolean;
33
+ };
34
+ config?: boolean;
35
+ }
36
+
37
+ export interface Plugin {
38
+ name: string;
39
+ initialize: (app: Application | FastifyInstance) => Promise<void> | void;
40
+ middleware?: RequestHandler[];
41
+ }
42
+
43
+ class PluginManager {
44
+ private plugins: Map<string, Plugin> = new Map();
45
+ private app: Application | FastifyInstance | null = null;
46
+ private authService: AuthService | null = null;
47
+ private queueManager: QueueManager | null = null;
48
+
49
+ register(plugin: Plugin): void {
50
+ this.plugins.set(plugin.name, plugin);
51
+ logger.info(`Plugin "${plugin.name}" registered`);
52
+ }
53
+
54
+ unregister(name: string): void {
55
+ this.plugins.delete(name);
56
+ logger.info(`Plugin "${name}" unregistered`);
57
+ }
58
+
59
+ get(name: string): Plugin | undefined {
60
+ return this.plugins.get(name);
61
+ }
62
+
63
+ getAll(): Plugin[] {
64
+ return Array.from(this.plugins.values());
65
+ }
66
+
67
+ has(name: string): boolean {
68
+ return this.plugins.has(name);
69
+ }
70
+
71
+ async initializeAll(app: Application | FastifyInstance): Promise<void> {
72
+ this.app = app;
73
+
74
+ for (const plugin of this.plugins.values()) {
75
+ logger.info(`Initializing plugin "${plugin.name}"`);
76
+ await plugin.initialize(app);
77
+
78
+ if (plugin.middleware && 'use' in app) {
79
+ for (const mw of plugin.middleware) {
80
+ (app as Application).use(mw);
81
+ }
82
+ }
83
+ }
84
+ }
85
+ }
86
+
87
+ export class SaaSAppBuilder {
88
+ private options: AppOptions;
89
+ private pluginManager: PluginManager;
90
+ private initialized: boolean = false;
91
+
92
+ constructor(options: AppOptions = {}) {
93
+ this.options = options;
94
+ this.pluginManager = new PluginManager();
95
+ }
96
+
97
+ use(plugin: Plugin): this {
98
+ this.pluginManager.register(plugin);
99
+ return this;
100
+ }
101
+
102
+ async initialize(app: Application | FastifyInstance): Promise<void> {
103
+ if (this.initialized) {
104
+ throw new Error('App already initialized');
105
+ }
106
+
107
+ config.load();
108
+
109
+ if (this.options.logger !== false) {
110
+ const loggerOptions = typeof this.options.logger === 'object' ? this.options.logger : {};
111
+ logger.create(loggerOptions);
112
+ }
113
+
114
+ if (this.options.auth !== false) {
115
+ const authOptions = typeof this.options.auth === 'object' ? this.options.auth : {};
116
+ this.authService = createAuth(authOptions);
117
+ await this.authService.initialize();
118
+
119
+ this.pluginManager.register({
120
+ name: 'auth',
121
+ initialize: (app) => {
122
+ if ('use' in app) {
123
+ (app as Application).use(this.authService!.getMiddleware());
124
+ }
125
+ },
126
+ middleware: [this.authService!.getMiddleware()],
127
+ });
128
+ }
129
+
130
+ if (this.options.queue !== false) {
131
+ const queueOptions = typeof this.options.queue === 'object' ? this.options.queue : {};
132
+ if (queueOptions.redisUrl) {
133
+ (await import('./queue')).queue.setRedisOptions({ url: queueOptions.redisUrl });
134
+ }
135
+
136
+ this.pluginManager.register({
137
+ name: 'queue',
138
+ initialize: () => {},
139
+ });
140
+ }
141
+
142
+ if (this.options.rateLimit !== false) {
143
+ const rateLimitOptions = typeof this.options.rateLimit === 'object' ? this.options.rateLimit : {};
144
+
145
+ this.pluginManager.register({
146
+ name: 'rate-limit',
147
+ initialize: (app) => {
148
+ if ('use' in app) {
149
+ (app as Application).use(rateLimit(rateLimitOptions));
150
+ }
151
+ },
152
+ middleware: [rateLimit(rateLimitOptions)],
153
+ });
154
+ }
155
+
156
+ if (this.options.notifications !== false) {
157
+ this.pluginManager.register({
158
+ name: 'notifications',
159
+ initialize: () => {},
160
+ });
161
+ }
162
+
163
+ await this.pluginManager.initializeAll(app);
164
+ this.initialized = true;
165
+ logger.info('SaaS App initialized');
166
+ }
167
+
168
+ getAuth(): AuthService | null {
169
+ return this.authService;
170
+ }
171
+
172
+ getPluginManager(): PluginManager {
173
+ return this.pluginManager;
174
+ }
175
+ }
176
+
177
+ export function createApp(options: AppOptions = {}): SaaSAppBuilder {
178
+ return new SaaSAppBuilder(options);
179
+ }
180
+
181
+ export function createExpressApp(options: AppOptions = {}): Application {
182
+ const express = require('express');
183
+ const app = express();
184
+
185
+ const builder = createApp({ ...options, framework: 'express' });
186
+ builder.initialize(app);
187
+
188
+ return app;
189
+ }
190
+
191
+ export { PluginManager, Plugin };
192
+ export default createApp;
@@ -0,0 +1,208 @@
1
+ import { Queue, Worker, Job, JobsOptions, WorkerOptions } from 'bullmq';
2
+ import { config } from '../config';
3
+ import { logger } from '../logger';
4
+
5
+ export interface QueueOptions {
6
+ name: string;
7
+ defaultJobOptions?: JobsOptions;
8
+ }
9
+
10
+ export interface RedisOptions {
11
+ host?: string;
12
+ port?: number;
13
+ password?: string;
14
+ db?: number;
15
+ url?: string;
16
+ }
17
+
18
+ export interface JobData {
19
+ [key: string]: unknown;
20
+ }
21
+
22
+ export type JobProcessor = (job: Job<JobData>) => Promise<unknown>;
23
+
24
+ class QueueManager {
25
+ private queues: Map<string, Queue> = new Map();
26
+ private workers: Map<string, Worker> = new Map();
27
+ private redisOptions: RedisOptions;
28
+
29
+ constructor() {
30
+ const redisUrl = config.get('REDIS_URL');
31
+ if (redisUrl) {
32
+ this.redisOptions = { url: redisUrl };
33
+ } else {
34
+ this.redisOptions = {
35
+ host: 'localhost',
36
+ port: 6379,
37
+ };
38
+ }
39
+ }
40
+
41
+ setRedisOptions(options: RedisOptions): void {
42
+ this.redisOptions = options;
43
+ }
44
+
45
+ createQueue(name: string, options?: Partial<QueueOptions>): Queue {
46
+ if (this.queues.has(name)) {
47
+ return this.queues.get(name)!;
48
+ }
49
+
50
+ const queue = new Queue(name, {
51
+ connection: this.redisOptions as any,
52
+ defaultJobOptions: options?.defaultJobOptions || {
53
+ removeOnComplete: 100,
54
+ removeOnFail: 100,
55
+ },
56
+ });
57
+
58
+ this.queues.set(name, queue);
59
+ logger.info(`Queue "${name}" created`);
60
+
61
+ return queue;
62
+ }
63
+
64
+ getQueue(name: string): Queue | undefined {
65
+ return this.queues.get(name);
66
+ }
67
+
68
+ async addJob(
69
+ queueName: string,
70
+ jobName: string,
71
+ data: JobData,
72
+ options?: JobsOptions
73
+ ): Promise<Job> {
74
+ const queue = this.getQueue(queueName) || this.createQueue(queueName);
75
+ const job = await queue.add(jobName, data, options);
76
+ logger.debug(`Job "${jobName}" added to queue "${queueName}"`, { jobId: job.id });
77
+ return job;
78
+ }
79
+
80
+ async addBulkJobs(
81
+ queueName: string,
82
+ jobs: Array<{ name: string; data: JobData; options?: JobsOptions }>
83
+ ): Promise<Job[]> {
84
+ const queue = this.getQueue(queueName) || this.createQueue(queueName);
85
+ const bulkJobs = jobs.map(job => ({
86
+ name: job.name,
87
+ data: job.data,
88
+ ...job.options,
89
+ }));
90
+ const result = await queue.addBulk(bulkJobs);
91
+ logger.debug(`${jobs.length} jobs added to queue "${queueName}"`);
92
+ return result;
93
+ }
94
+
95
+ processJob(
96
+ queueName: string,
97
+ processor: JobProcessor,
98
+ options?: WorkerOptions
99
+ ): Worker {
100
+ const queue = this.getQueue(queueName) || this.createQueue(queueName);
101
+
102
+ const worker = new Worker(queueName, async (job) => {
103
+ logger.debug(`Processing job "${job.name}"`, { jobId: job.id, queue: queueName });
104
+ return await processor(job);
105
+ }, {
106
+ connection: this.redisOptions as any,
107
+ concurrency: options?.concurrency || 1,
108
+ ...options,
109
+ });
110
+
111
+ worker.on('completed', (job) => {
112
+ logger.debug(`Job completed`, { jobId: job.id, queue: queueName });
113
+ });
114
+
115
+ worker.on('failed', (job, err) => {
116
+ logger.error(`Job failed`, { jobId: job?.id, queue: queueName, error: err.message });
117
+ });
118
+
119
+ worker.on('error', (err) => {
120
+ logger.error(`Worker error`, { queue: queueName, error: err.message });
121
+ });
122
+
123
+ this.workers.set(queueName, worker);
124
+ logger.info(`Worker started for queue "${queueName}"`);
125
+
126
+ return worker;
127
+ }
128
+
129
+ async getJobCounts(queueName: string): Promise<Record<string, number>> {
130
+ const queue = this.getQueue(queueName);
131
+ if (!queue) {
132
+ return { waiting: 0, active: 0, completed: 0, failed: 0, delayed: 0 };
133
+ }
134
+ return await queue.getJobCounts();
135
+ }
136
+
137
+ async getJobs(queueName: string, start: number = 0, end: number = 10): Promise<Job[]> {
138
+ const queue = this.getQueue(queueName);
139
+ if (!queue) return [];
140
+ return await queue.getJobs(['waiting', 'active', 'completed', 'failed'], start, end);
141
+ }
142
+
143
+ async closeQueue(name: string): Promise<void> {
144
+ const queue = this.queues.get(name);
145
+ if (queue) {
146
+ await queue.close();
147
+ this.queues.delete(name);
148
+ logger.info(`Queue "${name}" closed`);
149
+ }
150
+ }
151
+
152
+ async closeWorker(name: string): Promise<void> {
153
+ const worker = this.workers.get(name);
154
+ if (worker) {
155
+ await worker.close();
156
+ this.workers.delete(name);
157
+ logger.info(`Worker for queue "${name}" closed`);
158
+ }
159
+ }
160
+
161
+ async closeAll(): Promise<void> {
162
+ await Promise.all([
163
+ ...Array.from(this.queues.values()).map(q => q.close()),
164
+ ...Array.from(this.workers.values()).map(w => w.close()),
165
+ ]);
166
+ this.queues.clear();
167
+ this.workers.clear();
168
+ logger.info('All queues and workers closed');
169
+ }
170
+ }
171
+
172
+ const queueManager = new QueueManager();
173
+
174
+ export const createQueue = (name: string, options?: Partial<QueueOptions>): Queue => {
175
+ return queueManager.createQueue(name, options);
176
+ };
177
+
178
+ export const addJob = (
179
+ queueName: string,
180
+ jobName: string,
181
+ data: JobData,
182
+ options?: JobsOptions
183
+ ): Promise<Job> => {
184
+ return queueManager.addJob(queueName, jobName, data, options);
185
+ };
186
+
187
+ export const processJob = (
188
+ queueName: string,
189
+ processor: JobProcessor,
190
+ options?: WorkerOptions
191
+ ): Worker => {
192
+ return queueManager.processJob(queueName, processor, options);
193
+ };
194
+
195
+ export const queue = {
196
+ create: createQueue,
197
+ add: addJob,
198
+ process: processJob,
199
+ get: (name: string) => queueManager.getQueue(name),
200
+ getJobCounts: (name: string) => queueManager.getJobCounts(name),
201
+ getJobs: (name: string, start?: number, end?: number) => queueManager.getJobs(name, start, end),
202
+ close: (name: string) => queueManager.closeQueue(name),
203
+ closeAll: () => queueManager.closeAll(),
204
+ setRedisOptions: (options: RedisOptions) => queueManager.setRedisOptions(options),
205
+ };
206
+
207
+ export { QueueManager };
208
+ export default queue;