saas-backend-kit 1.0.2 → 1.0.4

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 (70) hide show
  1. package/README.md +170 -1
  2. package/dist/auth/index.js +1 -0
  3. package/dist/auth/index.js.map +1 -1
  4. package/dist/auth/index.mjs +1 -0
  5. package/dist/auth/index.mjs.map +1 -1
  6. package/dist/config/index.js +1 -0
  7. package/dist/config/index.js.map +1 -1
  8. package/dist/config/index.mjs +1 -0
  9. package/dist/config/index.mjs.map +1 -1
  10. package/dist/index.js +75 -0
  11. package/dist/index.js.map +1 -1
  12. package/dist/index.mjs +73 -1
  13. package/dist/index.mjs.map +1 -1
  14. package/dist/logger/index.js +1 -0
  15. package/dist/logger/index.js.map +1 -1
  16. package/dist/logger/index.mjs +1 -0
  17. package/dist/logger/index.mjs.map +1 -1
  18. package/dist/notifications/index.js +1 -0
  19. package/dist/notifications/index.js.map +1 -1
  20. package/dist/notifications/index.mjs +1 -0
  21. package/dist/notifications/index.mjs.map +1 -1
  22. package/dist/queue/index.js +1 -0
  23. package/dist/queue/index.js.map +1 -1
  24. package/dist/queue/index.mjs +1 -0
  25. package/dist/queue/index.mjs.map +1 -1
  26. package/dist/rate-limit/index.js +1 -0
  27. package/dist/rate-limit/index.js.map +1 -1
  28. package/dist/rate-limit/index.mjs +1 -0
  29. package/dist/rate-limit/index.mjs.map +1 -1
  30. package/dist/upload/index.js +1 -0
  31. package/dist/upload/index.js.map +1 -1
  32. package/dist/upload/index.mjs +1 -0
  33. package/dist/upload/index.mjs.map +1 -1
  34. package/package.json +18 -3
  35. package/CHANGELOG.md +0 -31
  36. package/PUBLISHING.md +0 -133
  37. package/copy-dts.js +0 -314
  38. package/examples/express/.env.example +0 -41
  39. package/examples/express/app.ts +0 -203
  40. package/jest-output.json +0 -72
  41. package/jest.config.js +0 -19
  42. package/saas-banner.svg +0 -239
  43. package/src/auth/express.ts +0 -250
  44. package/src/auth/fastify.ts +0 -65
  45. package/src/auth/index.ts +0 -6
  46. package/src/auth/jwt.ts +0 -47
  47. package/src/auth/oauth.ts +0 -117
  48. package/src/auth/rbac.ts +0 -82
  49. package/src/auth/types.ts +0 -69
  50. package/src/config/index.ts +0 -125
  51. package/src/index.ts +0 -18
  52. package/src/logger/index.ts +0 -110
  53. package/src/notifications/index.ts +0 -262
  54. package/src/plugin.ts +0 -192
  55. package/src/queue/index.ts +0 -208
  56. package/src/rate-limit/express.ts +0 -145
  57. package/src/rate-limit/fastify.ts +0 -47
  58. package/src/rate-limit/index.ts +0 -2
  59. package/src/response/index.ts +0 -206
  60. package/src/upload/index.ts +0 -268
  61. package/src/utils/index.ts +0 -180
  62. package/tests/auth.test.ts +0 -134
  63. package/tests/config.test.ts +0 -36
  64. package/tests/logger.test.ts +0 -47
  65. package/tests/notifications.test.ts +0 -19
  66. package/tests/rate-limit.test.ts +0 -50
  67. package/tests/upload.test.ts +0 -33
  68. package/tsconfig.json +0 -30
  69. package/tsconfig.test.json +0 -14
  70. package/tsup.config.ts +0 -25
@@ -1,125 +0,0 @@
1
- import { z } from 'zod';
2
-
3
- export const envSchema = z.object({
4
- NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
5
- PORT: z.string().default('3000'),
6
- DATABASE_URL: z.string().optional(),
7
- REDIS_URL: z.string().default('redis://localhost:6379'),
8
- JWT_SECRET: z.string().min(32).optional(),
9
- JWT_EXPIRES_IN: z.string().default('7d'),
10
- JWT_REFRESH_SECRET: z.string().min(32).optional(),
11
- JWT_REFRESH_EXPIRES_IN: z.string().default('30d'),
12
- GOOGLE_CLIENT_ID: z.string().optional(),
13
- GOOGLE_CLIENT_SECRET: z.string().optional(),
14
- GOOGLE_REDIRECT_URI: z.string().optional(),
15
- SMTP_HOST: z.string().optional(),
16
- SMTP_PORT: z.string().default('587'),
17
- SMTP_USER: z.string().optional(),
18
- SMTP_PASS: z.string().optional(),
19
- SMTP_FROM: z.string().optional(),
20
- TWILIO_ACCOUNT_SID: z.string().optional(),
21
- TWILIO_AUTH_TOKEN: z.string().optional(),
22
- TWILIO_PHONE_NUMBER: z.string().optional(),
23
- SLACK_WEBHOOK_URL: z.string().optional(),
24
- RATE_LIMIT_WINDOW: z.string().default('1m'),
25
- RATE_LIMIT_LIMIT: z.string().default('100'),
26
- LOG_LEVEL: z.enum(['fatal', 'error', 'warn', 'info', 'debug', 'trace']).default('info'),
27
- AWS_REGION: z.string().default('us-east-1'),
28
- AWS_ACCESS_KEY_ID: z.string().optional(),
29
- AWS_SECRET_ACCESS_KEY: z.string().optional(),
30
- AWS_S3_BUCKET: z.string().optional(),
31
- AWS_ENDPOINT: z.string().optional(),
32
- });
33
-
34
- export type EnvConfig = z.infer<typeof envSchema>;
35
-
36
- export interface ConfigOptions {
37
- schema?: z.ZodSchema;
38
- envPath?: string;
39
- validate?: boolean;
40
- }
41
-
42
- class ConfigManager {
43
- private config: EnvConfig | null = null;
44
- private schema: z.ZodSchema;
45
- private validate: boolean;
46
-
47
- constructor(options: ConfigOptions = {}) {
48
- this.schema = options.schema || envSchema;
49
- this.validate = options.validate ?? true;
50
- }
51
-
52
- load(): EnvConfig {
53
- if (this.config) return this.config;
54
-
55
- const env: Record<string, string | undefined> = {};
56
-
57
- for (const key of Object.keys(this.schema.shape)) {
58
- env[key] = process.env[key];
59
- }
60
-
61
- if (this.validate) {
62
- const result = this.schema.safeParse(env);
63
- if (!result.success) {
64
- const errors = result.error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', ');
65
- throw new Error(`Config validation failed: ${errors}`);
66
- }
67
- this.config = result.data;
68
- } else {
69
- this.config = env as EnvConfig;
70
- }
71
-
72
- return this.config;
73
- }
74
-
75
- get<K extends keyof EnvConfig>(key: K): EnvConfig[K] {
76
- if (!this.config) this.load();
77
- return this.config![key];
78
- }
79
-
80
- int(key: keyof EnvConfig): number {
81
- const value = this.get(key);
82
- if (typeof value === 'string') return parseInt(value, 10);
83
- return Number(value);
84
- }
85
-
86
- bool(key: keyof EnvConfig): boolean {
87
- const value = this.get(key);
88
- if (typeof value === 'boolean') return value;
89
- if (typeof value === 'string') return value.toLowerCase() === 'true';
90
- return Boolean(value);
91
- }
92
-
93
- isProduction(): boolean {
94
- return this.get('NODE_ENV') === 'production';
95
- }
96
-
97
- isDevelopment(): boolean {
98
- return this.get('NODE_ENV') === 'development';
99
- }
100
-
101
- isTest(): boolean {
102
- return this.get('NODE_ENV') === 'test';
103
- }
104
-
105
- getAll(): EnvConfig {
106
- if (!this.config) this.load();
107
- return this.config!;
108
- }
109
- }
110
-
111
- const globalConfig = new ConfigManager();
112
-
113
- export const config = {
114
- load: () => globalConfig.load(),
115
- get: <K extends keyof EnvConfig>(key: K) => globalConfig.get(key),
116
- int: (key: keyof EnvConfig) => globalConfig.int(key),
117
- bool: (key: keyof EnvConfig) => globalConfig.bool(key),
118
- isProduction: () => globalConfig.isProduction(),
119
- isDevelopment: () => globalConfig.isDevelopment(),
120
- isTest: () => globalConfig.isTest(),
121
- getAll: () => globalConfig.getAll(),
122
- create: (options?: ConfigOptions) => new ConfigManager(options),
123
- };
124
-
125
- export default config;
package/src/index.ts DELETED
@@ -1,18 +0,0 @@
1
- export { AuthService, createAuth, auth, Auth } from './auth';
2
- export { QueueManager, createQueue, queue } from './queue';
3
- export { notify, notification } from './notifications';
4
- export { logger } from './logger';
5
- export { rateLimit, createRateLimiter } from './rate-limit';
6
- export { config } from './config';
7
- export { ResponseHelper, response } from './response';
8
- export { upload, s3Service, S3Service } from './upload';
9
- export { createApp, createExpressApp, SaaSAppBuilder, PluginManager, Plugin, AppOptions } from './plugin';
10
-
11
- export { AuthOptions, User, JWTPayload, TokenPair, LoginCredentials, RegisterData, Role, Permission, RolePermissions } from './auth/types';
12
- export { QueueOptions, JobData, JobProcessor } from './queue';
13
- export { EmailOptions, SMSOptions, WebhookOptions, SlackOptions } from './notifications';
14
- export { LoggerConfig, LogLevel } from './logger';
15
- export { RateLimitOptions } from './rate-limit';
16
- export { EnvConfig, ConfigOptions } from './config';
17
- export { ApiResponse, PaginatedResponse, ErrorResponse } from './response';
18
- export { S3Config, UploadOptions, UploadResult, SignedUrlOptions, FileObject } from './upload';
@@ -1,110 +0,0 @@
1
- import pino, { Logger, LoggerOptions, Bindings } from 'pino';
2
- import { config } from '../config';
3
-
4
- export type LogLevel = 'fatal' | 'error' | 'warn' | 'info' | 'debug' | 'trace';
5
-
6
- export interface LoggerConfig extends Partial<LoggerOptions> {
7
- level?: LogLevel;
8
- name?: string;
9
- prettyPrint?: boolean;
10
- }
11
-
12
- export interface RequestLoggerOptions {
13
- logLevel?: LogLevel;
14
- autoLogging?: boolean;
15
- }
16
-
17
- class LoggerManager {
18
- private loggers: Map<string, Logger> = new Map();
19
- private defaultLogger: Logger;
20
-
21
- constructor() {
22
- const level = (config.get('LOG_LEVEL') as LogLevel) || 'info';
23
- this.defaultLogger = pino({
24
- level,
25
- name: 'saas-backend-kit',
26
- formatters: {
27
- bindings: (bindings: Bindings) => ({
28
- ...bindings,
29
- service: 'saas-backend-kit',
30
- }),
31
- },
32
- });
33
- }
34
-
35
- createLogger(options: LoggerConfig = {}): Logger {
36
- const name = options.name || 'default';
37
-
38
- if (this.loggers.has(name)) {
39
- return this.loggers.get(name)!;
40
- }
41
-
42
- const level = options.level || (config.get('LOG_LEVEL') as LogLevel) || 'info';
43
-
44
- const logger = pino({
45
- level,
46
- name: options.name,
47
- ...options,
48
- });
49
-
50
- this.loggers.set(name, logger);
51
- return logger;
52
- }
53
-
54
- getLogger(name?: string): Logger {
55
- if (name) {
56
- return this.loggers.get(name) || this.defaultLogger;
57
- }
58
- return this.defaultLogger;
59
- }
60
-
61
- child(bindings: Bindings, options?: { name?: string }): Logger {
62
- const name = options?.name || 'child';
63
- const parent = options?.name ? this.getLogger(name) : this.defaultLogger;
64
- return parent.child(bindings);
65
- }
66
- }
67
-
68
- const loggerManager = new LoggerManager();
69
-
70
- export const logger = {
71
- info: (message: string, ...args: unknown[]) => loggerManager.getLogger().info(message, ...args),
72
- warn: (message: string, ...args: unknown[]) => loggerManager.getLogger().warn(message, ...args),
73
- error: (message: string, ...args: unknown[]) => loggerManager.getLogger().error(message, ...args),
74
- debug: (message: string, ...args: unknown[]) => loggerManager.getLogger().debug(message, ...args),
75
- trace: (message: string, ...args: unknown[]) => loggerManager.getLogger().trace(message, ...args),
76
- fatal: (message: string, ...args: unknown[]) => loggerManager.getLogger().fatal(message, ...args),
77
- child: (bindings: Bindings, options?: { name?: string }) => loggerManager.child(bindings, options),
78
- create: (options?: LoggerConfig) => loggerManager.createLogger(options),
79
- get: (name?: string) => loggerManager.getLogger(name),
80
- };
81
-
82
- export function createRequestLogger(options: RequestLoggerOptions = {}) {
83
- const logLevel = options.logLevel || 'info';
84
- const logger = loggerManager.getLogger('http');
85
-
86
- return function requestLogger(
87
- req: { method: string; url: string; headers: Record<string, string | string[] | undefined> },
88
- res: { statusCode: number; statusMessage?: string },
89
- elapsed: number
90
- ) {
91
- const log = logger.child({
92
- method: req.method,
93
- url: req.url,
94
- status: res.statusCode,
95
- responseTime: elapsed,
96
- ip: req.headers['x-forwarded-for'] || req.headers['x-real-ip'] || 'unknown',
97
- userAgent: req.headers['user-agent'],
98
- });
99
-
100
- if (res.statusCode >= 500) {
101
- log.error(`Request completed`);
102
- } else if (res.statusCode >= 400) {
103
- log.warn(`Request completed`);
104
- } else {
105
- log.info(`Request completed`);
106
- }
107
- };
108
- }
109
-
110
- export default logger;
@@ -1,262 +0,0 @@
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 DELETED
@@ -1,192 +0,0 @@
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;