servcraft 0.1.0 → 0.1.3

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 (217) hide show
  1. package/.claude/settings.local.json +30 -0
  2. package/.github/CODEOWNERS +18 -0
  3. package/.github/PULL_REQUEST_TEMPLATE.md +46 -0
  4. package/.github/dependabot.yml +59 -0
  5. package/.github/workflows/ci.yml +188 -0
  6. package/.github/workflows/release.yml +195 -0
  7. package/AUDIT.md +602 -0
  8. package/LICENSE +21 -0
  9. package/README.md +1102 -1
  10. package/dist/cli/index.cjs +2026 -2168
  11. package/dist/cli/index.cjs.map +1 -1
  12. package/dist/cli/index.js +2026 -2168
  13. package/dist/cli/index.js.map +1 -1
  14. package/dist/index.cjs +595 -616
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.d.cts +114 -52
  17. package/dist/index.d.ts +114 -52
  18. package/dist/index.js +595 -616
  19. package/dist/index.js.map +1 -1
  20. package/docs/CLI-001_MULTI_DB_PLAN.md +546 -0
  21. package/docs/DATABASE_MULTI_ORM.md +399 -0
  22. package/docs/PHASE1_BREAKDOWN.md +346 -0
  23. package/docs/PROGRESS.md +550 -0
  24. package/docs/modules/ANALYTICS.md +226 -0
  25. package/docs/modules/API-VERSIONING.md +252 -0
  26. package/docs/modules/AUDIT.md +192 -0
  27. package/docs/modules/AUTH.md +431 -0
  28. package/docs/modules/CACHE.md +346 -0
  29. package/docs/modules/EMAIL.md +254 -0
  30. package/docs/modules/FEATURE-FLAG.md +291 -0
  31. package/docs/modules/I18N.md +294 -0
  32. package/docs/modules/MEDIA-PROCESSING.md +281 -0
  33. package/docs/modules/MFA.md +266 -0
  34. package/docs/modules/NOTIFICATION.md +311 -0
  35. package/docs/modules/OAUTH.md +237 -0
  36. package/docs/modules/PAYMENT.md +804 -0
  37. package/docs/modules/QUEUE.md +540 -0
  38. package/docs/modules/RATE-LIMIT.md +339 -0
  39. package/docs/modules/SEARCH.md +288 -0
  40. package/docs/modules/SECURITY.md +327 -0
  41. package/docs/modules/SESSION.md +382 -0
  42. package/docs/modules/SWAGGER.md +305 -0
  43. package/docs/modules/UPLOAD.md +296 -0
  44. package/docs/modules/USER.md +505 -0
  45. package/docs/modules/VALIDATION.md +294 -0
  46. package/docs/modules/WEBHOOK.md +270 -0
  47. package/docs/modules/WEBSOCKET.md +691 -0
  48. package/package.json +53 -38
  49. package/prisma/schema.prisma +395 -1
  50. package/src/cli/commands/add-module.ts +520 -87
  51. package/src/cli/commands/db.ts +3 -4
  52. package/src/cli/commands/docs.ts +256 -6
  53. package/src/cli/commands/generate.ts +12 -19
  54. package/src/cli/commands/init.ts +384 -214
  55. package/src/cli/index.ts +0 -4
  56. package/src/cli/templates/repository.ts +6 -1
  57. package/src/cli/templates/routes.ts +6 -21
  58. package/src/cli/utils/docs-generator.ts +6 -7
  59. package/src/cli/utils/env-manager.ts +717 -0
  60. package/src/cli/utils/field-parser.ts +16 -7
  61. package/src/cli/utils/interactive-prompt.ts +223 -0
  62. package/src/cli/utils/template-manager.ts +346 -0
  63. package/src/config/database.config.ts +183 -0
  64. package/src/config/env.ts +0 -10
  65. package/src/config/index.ts +0 -14
  66. package/src/core/server.ts +1 -1
  67. package/src/database/adapters/mongoose.adapter.ts +132 -0
  68. package/src/database/adapters/prisma.adapter.ts +118 -0
  69. package/src/database/connection.ts +190 -0
  70. package/src/database/interfaces/database.interface.ts +85 -0
  71. package/src/database/interfaces/index.ts +7 -0
  72. package/src/database/interfaces/repository.interface.ts +129 -0
  73. package/src/database/models/mongoose/index.ts +7 -0
  74. package/src/database/models/mongoose/payment.schema.ts +347 -0
  75. package/src/database/models/mongoose/user.schema.ts +154 -0
  76. package/src/database/prisma.ts +1 -4
  77. package/src/database/redis.ts +101 -0
  78. package/src/database/repositories/mongoose/index.ts +7 -0
  79. package/src/database/repositories/mongoose/payment.repository.ts +380 -0
  80. package/src/database/repositories/mongoose/user.repository.ts +255 -0
  81. package/src/database/seed.ts +6 -1
  82. package/src/index.ts +9 -20
  83. package/src/middleware/security.ts +2 -6
  84. package/src/modules/analytics/analytics.routes.ts +80 -0
  85. package/src/modules/analytics/analytics.service.ts +364 -0
  86. package/src/modules/analytics/index.ts +18 -0
  87. package/src/modules/analytics/types.ts +180 -0
  88. package/src/modules/api-versioning/index.ts +15 -0
  89. package/src/modules/api-versioning/types.ts +86 -0
  90. package/src/modules/api-versioning/versioning.middleware.ts +120 -0
  91. package/src/modules/api-versioning/versioning.routes.ts +54 -0
  92. package/src/modules/api-versioning/versioning.service.ts +189 -0
  93. package/src/modules/audit/audit.repository.ts +206 -0
  94. package/src/modules/audit/audit.service.ts +27 -59
  95. package/src/modules/auth/auth.controller.ts +2 -2
  96. package/src/modules/auth/auth.middleware.ts +3 -9
  97. package/src/modules/auth/auth.routes.ts +10 -107
  98. package/src/modules/auth/auth.service.ts +126 -23
  99. package/src/modules/auth/index.ts +3 -4
  100. package/src/modules/cache/cache.service.ts +367 -0
  101. package/src/modules/cache/index.ts +10 -0
  102. package/src/modules/cache/types.ts +44 -0
  103. package/src/modules/email/email.service.ts +3 -10
  104. package/src/modules/email/templates.ts +2 -8
  105. package/src/modules/feature-flag/feature-flag.repository.ts +303 -0
  106. package/src/modules/feature-flag/feature-flag.routes.ts +247 -0
  107. package/src/modules/feature-flag/feature-flag.service.ts +566 -0
  108. package/src/modules/feature-flag/index.ts +20 -0
  109. package/src/modules/feature-flag/types.ts +192 -0
  110. package/src/modules/i18n/i18n.middleware.ts +186 -0
  111. package/src/modules/i18n/i18n.routes.ts +191 -0
  112. package/src/modules/i18n/i18n.service.ts +456 -0
  113. package/src/modules/i18n/index.ts +18 -0
  114. package/src/modules/i18n/types.ts +118 -0
  115. package/src/modules/media-processing/index.ts +17 -0
  116. package/src/modules/media-processing/media-processing.routes.ts +111 -0
  117. package/src/modules/media-processing/media-processing.service.ts +245 -0
  118. package/src/modules/media-processing/types.ts +156 -0
  119. package/src/modules/mfa/index.ts +20 -0
  120. package/src/modules/mfa/mfa.repository.ts +206 -0
  121. package/src/modules/mfa/mfa.routes.ts +595 -0
  122. package/src/modules/mfa/mfa.service.ts +572 -0
  123. package/src/modules/mfa/totp.ts +150 -0
  124. package/src/modules/mfa/types.ts +57 -0
  125. package/src/modules/notification/index.ts +20 -0
  126. package/src/modules/notification/notification.repository.ts +356 -0
  127. package/src/modules/notification/notification.service.ts +483 -0
  128. package/src/modules/notification/types.ts +119 -0
  129. package/src/modules/oauth/index.ts +20 -0
  130. package/src/modules/oauth/oauth.repository.ts +219 -0
  131. package/src/modules/oauth/oauth.routes.ts +446 -0
  132. package/src/modules/oauth/oauth.service.ts +293 -0
  133. package/src/modules/oauth/providers/apple.provider.ts +250 -0
  134. package/src/modules/oauth/providers/facebook.provider.ts +181 -0
  135. package/src/modules/oauth/providers/github.provider.ts +248 -0
  136. package/src/modules/oauth/providers/google.provider.ts +189 -0
  137. package/src/modules/oauth/providers/twitter.provider.ts +214 -0
  138. package/src/modules/oauth/types.ts +94 -0
  139. package/src/modules/payment/index.ts +19 -0
  140. package/src/modules/payment/payment.repository.ts +733 -0
  141. package/src/modules/payment/payment.routes.ts +390 -0
  142. package/src/modules/payment/payment.service.ts +354 -0
  143. package/src/modules/payment/providers/mobile-money.provider.ts +274 -0
  144. package/src/modules/payment/providers/paypal.provider.ts +190 -0
  145. package/src/modules/payment/providers/stripe.provider.ts +215 -0
  146. package/src/modules/payment/types.ts +140 -0
  147. package/src/modules/queue/cron.ts +438 -0
  148. package/src/modules/queue/index.ts +87 -0
  149. package/src/modules/queue/queue.routes.ts +600 -0
  150. package/src/modules/queue/queue.service.ts +842 -0
  151. package/src/modules/queue/types.ts +222 -0
  152. package/src/modules/queue/workers.ts +366 -0
  153. package/src/modules/rate-limit/index.ts +59 -0
  154. package/src/modules/rate-limit/rate-limit.middleware.ts +134 -0
  155. package/src/modules/rate-limit/rate-limit.routes.ts +269 -0
  156. package/src/modules/rate-limit/rate-limit.service.ts +348 -0
  157. package/src/modules/rate-limit/stores/memory.store.ts +165 -0
  158. package/src/modules/rate-limit/stores/redis.store.ts +322 -0
  159. package/src/modules/rate-limit/types.ts +153 -0
  160. package/src/modules/search/adapters/elasticsearch.adapter.ts +326 -0
  161. package/src/modules/search/adapters/meilisearch.adapter.ts +261 -0
  162. package/src/modules/search/adapters/memory.adapter.ts +278 -0
  163. package/src/modules/search/index.ts +21 -0
  164. package/src/modules/search/search.service.ts +234 -0
  165. package/src/modules/search/types.ts +214 -0
  166. package/src/modules/security/index.ts +40 -0
  167. package/src/modules/security/sanitize.ts +223 -0
  168. package/src/modules/security/security-audit.service.ts +388 -0
  169. package/src/modules/security/security.middleware.ts +398 -0
  170. package/src/modules/session/index.ts +3 -0
  171. package/src/modules/session/session.repository.ts +159 -0
  172. package/src/modules/session/session.service.ts +340 -0
  173. package/src/modules/session/types.ts +38 -0
  174. package/src/modules/swagger/index.ts +7 -1
  175. package/src/modules/swagger/schema-builder.ts +16 -4
  176. package/src/modules/swagger/swagger.service.ts +9 -10
  177. package/src/modules/swagger/types.ts +0 -2
  178. package/src/modules/upload/index.ts +14 -0
  179. package/src/modules/upload/types.ts +83 -0
  180. package/src/modules/upload/upload.repository.ts +199 -0
  181. package/src/modules/upload/upload.routes.ts +311 -0
  182. package/src/modules/upload/upload.service.ts +448 -0
  183. package/src/modules/user/index.ts +3 -3
  184. package/src/modules/user/user.controller.ts +15 -9
  185. package/src/modules/user/user.repository.ts +237 -113
  186. package/src/modules/user/user.routes.ts +39 -164
  187. package/src/modules/user/user.service.ts +4 -3
  188. package/src/modules/validation/validator.ts +12 -17
  189. package/src/modules/webhook/index.ts +91 -0
  190. package/src/modules/webhook/retry.ts +196 -0
  191. package/src/modules/webhook/signature.ts +135 -0
  192. package/src/modules/webhook/types.ts +181 -0
  193. package/src/modules/webhook/webhook.repository.ts +358 -0
  194. package/src/modules/webhook/webhook.routes.ts +442 -0
  195. package/src/modules/webhook/webhook.service.ts +457 -0
  196. package/src/modules/websocket/features.ts +504 -0
  197. package/src/modules/websocket/index.ts +106 -0
  198. package/src/modules/websocket/middlewares.ts +298 -0
  199. package/src/modules/websocket/types.ts +181 -0
  200. package/src/modules/websocket/websocket.service.ts +692 -0
  201. package/src/utils/errors.ts +7 -0
  202. package/src/utils/pagination.ts +4 -1
  203. package/tests/helpers/db-check.ts +79 -0
  204. package/tests/integration/auth-redis.test.ts +94 -0
  205. package/tests/integration/cache-redis.test.ts +387 -0
  206. package/tests/integration/mongoose-repositories.test.ts +410 -0
  207. package/tests/integration/payment-prisma.test.ts +637 -0
  208. package/tests/integration/queue-bullmq.test.ts +417 -0
  209. package/tests/integration/user-prisma.test.ts +441 -0
  210. package/tests/integration/websocket-socketio.test.ts +552 -0
  211. package/tests/setup.ts +11 -9
  212. package/vitest.config.ts +3 -8
  213. package/npm-cache/_cacache/content-v2/sha512/1c/d0/03440d500a0487621aad1d6402978340698976602046db8e24fa03c01ee6c022c69b0582f969042d9442ee876ac35c038e960dd427d1e622fa24b8eb7dba +0 -0
  214. package/npm-cache/_cacache/content-v2/sha512/42/55/28b493ca491833e5aab0e9c3108d29ab3f36c248ca88f45d4630674fce9130959e56ae308797ac2b6328fa7f09a610b9550ed09cb971d039876d293fc69d +0 -0
  215. package/npm-cache/_cacache/content-v2/sha512/e0/12/f360dc9315ee5f17844a0c8c233ee6bf7c30837c4a02ea0d56c61c7f7ab21c0e958e50ed2c57c59f983c762b93056778c9009b2398ffc26def0183999b13 +0 -0
  216. package/npm-cache/_cacache/content-v2/sha512/ed/b0/fae1161902898f4c913c67d7f6cdf6be0665aec3b389b9c4f4f0a101ca1da59badf1b59c4e0030f5223023b8d63cfe501c46a32c20c895d4fb3f11ca2232 +0 -0
  217. package/npm-cache/_cacache/index-v5/58/94/c2cba79e0f16b4c10e95a87e32255741149e8222cc314a476aab67c39cc0 +0 -5
@@ -0,0 +1,183 @@
1
+ /**
2
+ * Database Configuration
3
+ * Centralized database configuration management
4
+ */
5
+
6
+ import type { DatabaseConfig } from '../database/interfaces/index.js';
7
+
8
+ /**
9
+ * Load database configuration from environment variables
10
+ */
11
+ export function loadDatabaseConfig(): DatabaseConfig {
12
+ const orm = (process.env.DATABASE_ORM || 'prisma') as DatabaseConfig['orm'];
13
+ const database = (process.env.DATABASE_TYPE || 'postgresql') as DatabaseConfig['database'];
14
+
15
+ // Primary configuration: DATABASE_URL
16
+ if (process.env.DATABASE_URL) {
17
+ return {
18
+ orm,
19
+ database,
20
+ url: process.env.DATABASE_URL,
21
+ logging: process.env.DATABASE_LOGGING === 'true',
22
+ ssl: process.env.DATABASE_SSL === 'true',
23
+ };
24
+ }
25
+
26
+ // Fallback: individual environment variables
27
+ const config: DatabaseConfig = {
28
+ orm,
29
+ database,
30
+ host: process.env.DATABASE_HOST || 'localhost',
31
+ port: process.env.DATABASE_PORT ? parseInt(process.env.DATABASE_PORT) : undefined,
32
+ username: process.env.DATABASE_USER,
33
+ password: process.env.DATABASE_PASSWORD,
34
+ databaseName: process.env.DATABASE_NAME,
35
+ logging: process.env.DATABASE_LOGGING === 'true',
36
+ ssl: process.env.DATABASE_SSL === 'true',
37
+ };
38
+
39
+ // MongoDB specific URL fallback
40
+ if (database === 'mongodb' && process.env.MONGODB_URI) {
41
+ config.url = process.env.MONGODB_URI;
42
+ }
43
+
44
+ // Set default ports if not specified
45
+ if (!config.port && !config.url) {
46
+ switch (database) {
47
+ case 'postgresql':
48
+ config.port = 5432;
49
+ break;
50
+ case 'mysql':
51
+ case 'mariadb':
52
+ config.port = 3306;
53
+ break;
54
+ case 'mongodb':
55
+ config.port = 27017;
56
+ break;
57
+ }
58
+ }
59
+
60
+ return config;
61
+ }
62
+
63
+ /**
64
+ * Validate database configuration
65
+ * @throws Error if configuration is invalid
66
+ */
67
+ export function validateDatabaseConfig(config: DatabaseConfig): void {
68
+ // Check required fields
69
+ if (!config.orm) {
70
+ throw new Error('DATABASE_ORM is required');
71
+ }
72
+
73
+ if (!config.database) {
74
+ throw new Error('DATABASE_TYPE is required');
75
+ }
76
+
77
+ // Validate ORM compatibility with database
78
+ const compatibility: Record<DatabaseConfig['orm'], DatabaseConfig['database'][]> = {
79
+ prisma: ['postgresql', 'mysql', 'sqlite', 'mongodb'],
80
+ mongoose: ['mongodb'],
81
+ sequelize: ['postgresql', 'mysql', 'sqlite', 'mariadb'],
82
+ typeorm: ['postgresql', 'mysql', 'sqlite', 'mongodb', 'mariadb'],
83
+ };
84
+
85
+ if (!compatibility[config.orm]?.includes(config.database)) {
86
+ throw new Error(
87
+ `ORM '${config.orm}' does not support database '${config.database}'. ` +
88
+ `Supported: ${compatibility[config.orm]?.join(', ') || 'none'}`
89
+ );
90
+ }
91
+
92
+ // Check connection details
93
+ if (!config.url) {
94
+ if (config.database !== 'sqlite') {
95
+ if (!config.host) {
96
+ throw new Error('DATABASE_HOST or DATABASE_URL is required');
97
+ }
98
+ if (!config.databaseName) {
99
+ throw new Error('DATABASE_NAME or DATABASE_URL is required');
100
+ }
101
+ } else {
102
+ // SQLite needs at least a database name/path
103
+ if (!config.databaseName) {
104
+ config.databaseName = './dev.db';
105
+ }
106
+ }
107
+ }
108
+
109
+ // Validate Mongoose specific
110
+ if (config.orm === 'mongoose' && config.database !== 'mongodb') {
111
+ throw new Error('Mongoose can only be used with MongoDB');
112
+ }
113
+
114
+ // Validate MongoDB specific
115
+ if (
116
+ config.database === 'mongodb' &&
117
+ config.orm !== 'mongoose' &&
118
+ config.orm !== 'prisma' &&
119
+ config.orm !== 'typeorm'
120
+ ) {
121
+ throw new Error(`MongoDB requires Mongoose, Prisma, or TypeORM (got ${config.orm})`);
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Get recommended ORM for a database
127
+ */
128
+ export function getRecommendedOrm(database: DatabaseConfig['database']): DatabaseConfig['orm'] {
129
+ switch (database) {
130
+ case 'mongodb':
131
+ return 'mongoose';
132
+ case 'postgresql':
133
+ case 'mysql':
134
+ case 'sqlite':
135
+ return 'prisma';
136
+ case 'mariadb':
137
+ return 'sequelize';
138
+ default:
139
+ return 'prisma';
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Get supported databases for an ORM
145
+ */
146
+ export function getSupportedDatabases(orm: DatabaseConfig['orm']): DatabaseConfig['database'][] {
147
+ const support: Record<DatabaseConfig['orm'], DatabaseConfig['database'][]> = {
148
+ prisma: ['postgresql', 'mysql', 'sqlite', 'mongodb'],
149
+ mongoose: ['mongodb'],
150
+ sequelize: ['postgresql', 'mysql', 'sqlite', 'mariadb'],
151
+ typeorm: ['postgresql', 'mysql', 'sqlite', 'mongodb', 'mariadb'],
152
+ };
153
+
154
+ return support[orm] || [];
155
+ }
156
+
157
+ /**
158
+ * Example .env template generator
159
+ */
160
+ export function generateEnvTemplate(config: DatabaseConfig): string {
161
+ const lines: string[] = [
162
+ '# Database Configuration',
163
+ `DATABASE_ORM=${config.orm}`,
164
+ `DATABASE_TYPE=${config.database}`,
165
+ '',
166
+ ];
167
+
168
+ if (config.url) {
169
+ lines.push(`DATABASE_URL=${config.url}`);
170
+ } else {
171
+ lines.push(
172
+ `DATABASE_HOST=${config.host || 'localhost'}`,
173
+ `DATABASE_PORT=${config.port || ''}`,
174
+ `DATABASE_USER=${config.username || ''}`,
175
+ `DATABASE_PASSWORD=${config.password || ''}`,
176
+ `DATABASE_NAME=${config.databaseName || 'mydb'}`
177
+ );
178
+ }
179
+
180
+ lines.push('', '# Optional', 'DATABASE_LOGGING=false', 'DATABASE_SSL=false');
181
+
182
+ return lines.join('\n');
183
+ }
package/src/config/env.ts CHANGED
@@ -34,16 +34,6 @@ const envSchema = z.object({
34
34
  // Redis (optional)
35
35
  REDIS_URL: z.string().optional(),
36
36
 
37
- // Swagger/OpenAPI
38
- SWAGGER_ENABLED: z
39
- .union([z.literal('true'), z.literal('false')])
40
- .default('true')
41
- .transform((val) => val === 'true'),
42
- SWAGGER_ROUTE: z.string().default('/docs'),
43
- SWAGGER_TITLE: z.string().default('Servcraft API'),
44
- SWAGGER_DESCRIPTION: z.string().default('API documentation'),
45
- SWAGGER_VERSION: z.string().default('1.0.0'),
46
-
47
37
  // Logging
48
38
  LOG_LEVEL: z.enum(['fatal', 'error', 'warn', 'info', 'debug', 'trace']).default('info'),
49
39
  });
@@ -32,13 +32,6 @@ export interface AppConfig {
32
32
  redis: {
33
33
  url?: string;
34
34
  };
35
- swagger: {
36
- enabled: boolean;
37
- route: string;
38
- title: string;
39
- description: string;
40
- version: string;
41
- };
42
35
  }
43
36
 
44
37
  function parseCorsOrigin(origin: string): string | string[] {
@@ -81,13 +74,6 @@ export function createConfig(): AppConfig {
81
74
  redis: {
82
75
  url: env.REDIS_URL,
83
76
  },
84
- swagger: {
85
- enabled: env.SWAGGER_ENABLED,
86
- route: env.SWAGGER_ROUTE,
87
- title: env.SWAGGER_TITLE,
88
- description: env.SWAGGER_DESCRIPTION,
89
- version: env.SWAGGER_VERSION,
90
- },
91
77
  };
92
78
  }
93
79
 
@@ -1,6 +1,6 @@
1
1
  import Fastify from 'fastify';
2
2
  import type { FastifyInstance, FastifyServerOptions } from 'fastify';
3
- import { logger, createLogger } from './logger.js';
3
+ import { logger } from './logger.js';
4
4
  import type { Logger } from './logger.js';
5
5
 
6
6
  export interface ServerConfig {
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Mongoose Database Adapter
3
+ * Implements IDatabaseAdapter for Mongoose ORM (MongoDB)
4
+ */
5
+
6
+ import mongoose from 'mongoose';
7
+ import { logger } from '../../core/logger.js';
8
+ import type { IDatabaseAdapter, DatabaseConfig } from '../interfaces/index.js';
9
+ import { getDatabaseUrl } from '../connection.js';
10
+
11
+ export class MongooseAdapter implements IDatabaseAdapter {
12
+ private connection: typeof mongoose | null = null;
13
+ private config: DatabaseConfig;
14
+ private connected: boolean = false;
15
+
16
+ constructor(config: DatabaseConfig) {
17
+ this.config = config;
18
+
19
+ if (config.database !== 'mongodb') {
20
+ throw new Error('MongooseAdapter only supports MongoDB');
21
+ }
22
+ }
23
+
24
+ /**
25
+ * Connect to MongoDB using Mongoose
26
+ */
27
+ async connect(): Promise<void> {
28
+ if (this.connected && this.connection) {
29
+ logger.warn('Mongoose already connected');
30
+ return;
31
+ }
32
+
33
+ try {
34
+ const url = this.config.url || getDatabaseUrl(this.config);
35
+
36
+ this.connection = await mongoose.connect(url, {
37
+ // Modern Mongoose options
38
+ maxPoolSize: this.config.pool?.max || 10,
39
+ minPoolSize: this.config.pool?.min || 2,
40
+ socketTimeoutMS: 45000,
41
+ serverSelectionTimeoutMS: 5000,
42
+ });
43
+
44
+ this.connected = true;
45
+
46
+ logger.info({ host: this.connection.connection.host }, 'Mongoose connected to MongoDB');
47
+ } catch (error) {
48
+ logger.error({ err: error }, 'Failed to connect Mongoose');
49
+ throw new Error(
50
+ `Mongoose connection failed: ${error instanceof Error ? error.message : 'Unknown error'}`
51
+ );
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Disconnect from MongoDB
57
+ */
58
+ async disconnect(): Promise<void> {
59
+ if (!this.connection) {
60
+ return;
61
+ }
62
+
63
+ try {
64
+ await mongoose.disconnect();
65
+ this.connection = null;
66
+ this.connected = false;
67
+
68
+ logger.info('Mongoose disconnected from MongoDB');
69
+ } catch (error) {
70
+ logger.error({ err: error }, 'Error disconnecting Mongoose');
71
+ throw error;
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Health check
77
+ */
78
+ async healthCheck(): Promise<boolean> {
79
+ if (!this.connection || !this.connected) {
80
+ return false;
81
+ }
82
+
83
+ try {
84
+ // Check connection state
85
+ return mongoose.connection.readyState === 1; // 1 = connected
86
+ } catch {
87
+ return false;
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Get ORM type
93
+ */
94
+ getType(): 'mongoose' {
95
+ return 'mongoose';
96
+ }
97
+
98
+ /**
99
+ * Get database type
100
+ */
101
+ getDatabaseType(): 'mongodb' {
102
+ return 'mongodb';
103
+ }
104
+
105
+ /**
106
+ * Get raw Mongoose instance
107
+ * Use with caution - breaks abstraction
108
+ */
109
+ getRawClient(): typeof mongoose {
110
+ if (!this.connection) {
111
+ throw new Error('Mongoose not initialized. Call connect() first.');
112
+ }
113
+ return this.connection;
114
+ }
115
+
116
+ /**
117
+ * Get Mongoose instance (typed helper)
118
+ */
119
+ getMongoose(): typeof mongoose {
120
+ return this.getRawClient();
121
+ }
122
+
123
+ /**
124
+ * Get native MongoDB connection
125
+ */
126
+ getConnection(): mongoose.Connection {
127
+ if (!this.connection) {
128
+ throw new Error('Mongoose not initialized. Call connect() first.');
129
+ }
130
+ return this.connection.connection;
131
+ }
132
+ }
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Prisma Database Adapter
3
+ * Implements IDatabaseAdapter for Prisma ORM
4
+ */
5
+
6
+ import { PrismaClient } from '@prisma/client';
7
+ import { logger } from '../../core/logger.js';
8
+ import type { IDatabaseAdapter, DatabaseConfig } from '../interfaces/index.js';
9
+
10
+ export class PrismaAdapter implements IDatabaseAdapter {
11
+ private client: PrismaClient | null = null;
12
+ private config: DatabaseConfig;
13
+ private connected: boolean = false;
14
+
15
+ constructor(config: DatabaseConfig) {
16
+ this.config = config;
17
+ }
18
+
19
+ /**
20
+ * Connect to database using Prisma
21
+ */
22
+ async connect(): Promise<void> {
23
+ if (this.connected && this.client) {
24
+ logger.warn('Prisma client already connected');
25
+ return;
26
+ }
27
+
28
+ try {
29
+ this.client = new PrismaClient({
30
+ datasources: {
31
+ db: {
32
+ url: this.config.url,
33
+ },
34
+ },
35
+ log: this.config.logging ? ['query', 'info', 'warn', 'error'] : ['error'],
36
+ });
37
+
38
+ await this.client.$connect();
39
+ this.connected = true;
40
+
41
+ logger.info({ database: this.config.database }, 'Prisma connected successfully');
42
+ } catch (error) {
43
+ logger.error({ err: error }, 'Failed to connect Prisma');
44
+ throw new Error(
45
+ `Prisma connection failed: ${error instanceof Error ? error.message : 'Unknown error'}`
46
+ );
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Disconnect from database
52
+ */
53
+ async disconnect(): Promise<void> {
54
+ if (!this.client) {
55
+ return;
56
+ }
57
+
58
+ try {
59
+ await this.client.$disconnect();
60
+ this.client = null;
61
+ this.connected = false;
62
+
63
+ logger.info('Prisma disconnected');
64
+ } catch (error) {
65
+ logger.error({ err: error }, 'Error disconnecting Prisma');
66
+ throw error;
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Health check
72
+ */
73
+ async healthCheck(): Promise<boolean> {
74
+ if (!this.client || !this.connected) {
75
+ return false;
76
+ }
77
+
78
+ try {
79
+ // Try a simple query
80
+ await this.client.$queryRaw`SELECT 1`;
81
+ return true;
82
+ } catch {
83
+ return false;
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Get ORM type
89
+ */
90
+ getType(): 'prisma' {
91
+ return 'prisma';
92
+ }
93
+
94
+ /**
95
+ * Get database type
96
+ */
97
+ getDatabaseType(): DatabaseConfig['database'] {
98
+ return this.config.database;
99
+ }
100
+
101
+ /**
102
+ * Get raw Prisma client
103
+ * Use with caution - breaks abstraction
104
+ */
105
+ getRawClient(): PrismaClient {
106
+ if (!this.client) {
107
+ throw new Error('Prisma client not initialized. Call connect() first.');
108
+ }
109
+ return this.client;
110
+ }
111
+
112
+ /**
113
+ * Get Prisma client (typed helper)
114
+ */
115
+ getClient(): PrismaClient {
116
+ return this.getRawClient();
117
+ }
118
+ }
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Database Connection Factory
3
+ * Creates appropriate adapter based on configuration
4
+ */
5
+
6
+ import { logger } from '../core/logger.js';
7
+ import type { IDatabaseAdapter, DatabaseConfig } from './interfaces/index.js';
8
+
9
+ // Lazy imports to avoid loading unused ORMs
10
+ let PrismaAdapter: any;
11
+ let MongooseAdapter: any;
12
+
13
+ /**
14
+ * Database Factory
15
+ * Singleton factory for creating and managing database adapters
16
+ */
17
+ export class DatabaseFactory {
18
+ private static adapter: IDatabaseAdapter | null = null;
19
+ private static config: DatabaseConfig | null = null;
20
+
21
+ /**
22
+ * Create database adapter based on configuration
23
+ * @param config - Database configuration
24
+ * @returns Database adapter instance
25
+ */
26
+ static async createAdapter(config: DatabaseConfig): Promise<IDatabaseAdapter> {
27
+ this.config = config;
28
+
29
+ logger.info({ orm: config.orm, database: config.database }, 'Creating database adapter');
30
+
31
+ switch (config.orm) {
32
+ case 'prisma':
33
+ return this.createPrismaAdapter(config);
34
+
35
+ case 'mongoose':
36
+ return this.createMongooseAdapter(config);
37
+
38
+ case 'sequelize':
39
+ throw new Error('Sequelize support coming soon! Use Prisma or Mongoose for now.');
40
+
41
+ case 'typeorm':
42
+ throw new Error('TypeORM support coming soon! Use Prisma or Mongoose for now.');
43
+
44
+ default:
45
+ throw new Error(`Unsupported ORM: ${config.orm}`);
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Create Prisma adapter
51
+ */
52
+ private static async createPrismaAdapter(config: DatabaseConfig): Promise<IDatabaseAdapter> {
53
+ // Validate Prisma supports this database
54
+ if (!['postgresql', 'mysql', 'sqlite', 'mongodb'].includes(config.database)) {
55
+ throw new Error(
56
+ `Prisma does not support ${config.database}. Use PostgreSQL, MySQL, SQLite, or MongoDB.`
57
+ );
58
+ }
59
+
60
+ // Lazy load Prisma adapter
61
+ if (!PrismaAdapter) {
62
+ const module = await import('./adapters/prisma.adapter.js');
63
+ PrismaAdapter = module.PrismaAdapter;
64
+ }
65
+
66
+ return new PrismaAdapter(config);
67
+ }
68
+
69
+ /**
70
+ * Create Mongoose adapter
71
+ */
72
+ private static async createMongooseAdapter(config: DatabaseConfig): Promise<IDatabaseAdapter> {
73
+ // Validate Mongoose only supports MongoDB
74
+ if (config.database !== 'mongodb') {
75
+ throw new Error('Mongoose only supports MongoDB database.');
76
+ }
77
+
78
+ // Lazy load Mongoose adapter
79
+ if (!MongooseAdapter) {
80
+ const module = await import('./adapters/mongoose.adapter.js');
81
+ MongooseAdapter = module.MongooseAdapter;
82
+ }
83
+
84
+ return new MongooseAdapter(config);
85
+ }
86
+
87
+ /**
88
+ * Get current adapter instance (singleton)
89
+ * @throws Error if no adapter initialized
90
+ */
91
+ static getAdapter(): IDatabaseAdapter {
92
+ if (!this.adapter) {
93
+ throw new Error('Database adapter not initialized. Call DatabaseFactory.initialize() first.');
94
+ }
95
+ return this.adapter;
96
+ }
97
+
98
+ /**
99
+ * Initialize database connection
100
+ * @param config - Database configuration
101
+ */
102
+ static async initialize(config: DatabaseConfig): Promise<IDatabaseAdapter> {
103
+ if (this.adapter) {
104
+ logger.warn('Database adapter already initialized. Disconnecting previous connection.');
105
+ await this.disconnect();
106
+ }
107
+
108
+ this.adapter = await this.createAdapter(config);
109
+ await this.adapter.connect();
110
+
111
+ logger.info({ orm: config.orm, database: config.database }, 'Database initialized');
112
+
113
+ return this.adapter;
114
+ }
115
+
116
+ /**
117
+ * Disconnect from database
118
+ */
119
+ static async disconnect(): Promise<void> {
120
+ if (this.adapter) {
121
+ await this.adapter.disconnect();
122
+ this.adapter = null;
123
+ logger.info('Database disconnected');
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Check database health
129
+ */
130
+ static async healthCheck(): Promise<boolean> {
131
+ if (!this.adapter) {
132
+ return false;
133
+ }
134
+ return this.adapter.healthCheck();
135
+ }
136
+
137
+ /**
138
+ * Get current configuration
139
+ */
140
+ static getConfig(): DatabaseConfig | null {
141
+ return this.config;
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Helper function to get database connection URL from config
147
+ */
148
+ export function getDatabaseUrl(config: DatabaseConfig): string {
149
+ if (config.url) {
150
+ return config.url;
151
+ }
152
+
153
+ // Build URL from components
154
+ const { database, host, port, username, password, databaseName } = config;
155
+
156
+ switch (database) {
157
+ case 'postgresql':
158
+ return `postgresql://${username}:${password}@${host}:${port || 5432}/${databaseName}`;
159
+
160
+ case 'mysql':
161
+ case 'mariadb':
162
+ return `mysql://${username}:${password}@${host}:${port || 3306}/${databaseName}`;
163
+
164
+ case 'sqlite':
165
+ return `file:${databaseName || './dev.db'}`;
166
+
167
+ case 'mongodb':
168
+ return `mongodb://${host}:${port || 27017}/${databaseName}`;
169
+
170
+ default:
171
+ throw new Error(`Cannot build URL for database: ${database}`);
172
+ }
173
+ }
174
+
175
+ /**
176
+ * Validate ORM and database compatibility
177
+ */
178
+ export function validateCompatibility(
179
+ orm: DatabaseConfig['orm'],
180
+ database: DatabaseConfig['database']
181
+ ): boolean {
182
+ const compatibility: Record<DatabaseConfig['orm'], DatabaseConfig['database'][]> = {
183
+ prisma: ['postgresql', 'mysql', 'sqlite', 'mongodb'],
184
+ mongoose: ['mongodb'],
185
+ sequelize: ['postgresql', 'mysql', 'sqlite', 'mariadb'],
186
+ typeorm: ['postgresql', 'mysql', 'sqlite', 'mongodb', 'mariadb'],
187
+ };
188
+
189
+ return compatibility[orm]?.includes(database) || false;
190
+ }