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
@@ -1,8 +1,8 @@
1
1
  import { z } from 'zod';
2
- import type { ZodError, ZodTypeAny } from 'zod';
2
+ import type { ZodSchema, ZodError } from 'zod';
3
3
  import { ValidationError } from '../../utils/errors.js';
4
4
 
5
- export function validateBody<T extends ZodTypeAny>(schema: T, data: unknown): z.infer<T> {
5
+ export function validateBody<T>(schema: ZodSchema<T>, data: unknown): T {
6
6
  const result = schema.safeParse(data);
7
7
 
8
8
  if (!result.success) {
@@ -12,7 +12,7 @@ export function validateBody<T extends ZodTypeAny>(schema: T, data: unknown): z.
12
12
  return result.data;
13
13
  }
14
14
 
15
- export function validateQuery<T extends ZodTypeAny>(schema: T, data: unknown): z.infer<T> {
15
+ export function validateQuery<T extends z.ZodTypeAny>(schema: T, data: unknown): z.infer<T> {
16
16
  const result = schema.safeParse(data);
17
17
 
18
18
  if (!result.success) {
@@ -22,7 +22,7 @@ export function validateQuery<T extends ZodTypeAny>(schema: T, data: unknown): z
22
22
  return result.data;
23
23
  }
24
24
 
25
- export function validateParams<T extends ZodTypeAny>(schema: T, data: unknown): z.infer<T> {
25
+ export function validateParams<T>(schema: ZodSchema<T>, data: unknown): T {
26
26
  const result = schema.safeParse(data);
27
27
 
28
28
  if (!result.success) {
@@ -32,7 +32,7 @@ export function validateParams<T extends ZodTypeAny>(schema: T, data: unknown):
32
32
  return result.data;
33
33
  }
34
34
 
35
- export function validate<T extends ZodTypeAny>(schema: T, data: unknown): z.infer<T> {
35
+ export function validate<T>(schema: ZodSchema<T>, data: unknown): T {
36
36
  return validateBody(schema, data);
37
37
  }
38
38
 
@@ -83,21 +83,16 @@ export const passwordSchema = z
83
83
  export const urlSchema = z.string().url('Invalid URL format');
84
84
 
85
85
  // Phone validation (basic international format)
86
- export const phoneSchema = z.string().regex(
87
- /^\+?[1-9]\d{1,14}$/,
88
- 'Invalid phone number format'
89
- );
86
+ export const phoneSchema = z.string().regex(/^\+?[1-9]\d{1,14}$/, 'Invalid phone number format');
90
87
 
91
88
  // Date validation
92
89
  export const dateSchema = z.coerce.date();
93
- export const futureDateSchema = z.coerce.date().refine(
94
- (date) => date > new Date(),
95
- 'Date must be in the future'
96
- );
97
- export const pastDateSchema = z.coerce.date().refine(
98
- (date) => date < new Date(),
99
- 'Date must be in the past'
100
- );
90
+ export const futureDateSchema = z.coerce
91
+ .date()
92
+ .refine((date) => date > new Date(), 'Date must be in the future');
93
+ export const pastDateSchema = z.coerce
94
+ .date()
95
+ .refine((date) => date < new Date(), 'Date must be in the past');
101
96
 
102
97
  // Type exports
103
98
  export type IdParam = z.infer<typeof idParamSchema>;
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Webhook Module
3
+ *
4
+ * Provides outgoing webhook functionality with:
5
+ * - HMAC signature verification
6
+ * - Automatic retry with exponential backoff
7
+ * - Delivery tracking and monitoring
8
+ * - Multiple retry strategies
9
+ * - Event publishing and subscription
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * import { WebhookService, createWebhookRoutes } from './modules/webhook';
14
+ *
15
+ * // Create service
16
+ * const webhookService = new WebhookService({
17
+ * maxRetries: 5,
18
+ * timeout: 10000,
19
+ * enableSignature: true
20
+ * });
21
+ *
22
+ * // Create endpoint
23
+ * const endpoint = await webhookService.createEndpoint({
24
+ * url: 'https://example.com/webhook',
25
+ * events: ['user.created', 'order.completed'],
26
+ * description: 'Production webhook'
27
+ * });
28
+ *
29
+ * // Publish event
30
+ * await webhookService.publishEvent('user.created', {
31
+ * userId: '123',
32
+ * email: 'user@example.com',
33
+ * name: 'John Doe'
34
+ * });
35
+ *
36
+ * // Add routes to your app
37
+ * app.use('/api/webhooks', authMiddleware, createWebhookRoutes(webhookService));
38
+ * ```
39
+ *
40
+ * ## Signature Verification
41
+ *
42
+ * Webhooks are signed with HMAC-SHA256. Recipients should verify:
43
+ *
44
+ * ```typescript
45
+ * import { verifyWebhookSignature } from './modules/webhook';
46
+ *
47
+ * app.post('/webhook', (req, res) => {
48
+ * const signature = req.headers['x-webhook-signature'];
49
+ * const secret = 'your-webhook-secret';
50
+ *
51
+ * if (!verifyWebhookSignature(req.body, signature, secret)) {
52
+ * return res.status(401).json({ error: 'Invalid signature' });
53
+ * }
54
+ *
55
+ * // Process webhook
56
+ * res.json({ received: true });
57
+ * });
58
+ * ```
59
+ */
60
+
61
+ // Types
62
+ export * from './types.js';
63
+
64
+ // Service
65
+ export { WebhookService } from './webhook.service.js';
66
+
67
+ // Routes
68
+ export { createWebhookRoutes } from './webhook.routes.js';
69
+
70
+ // Signature utilities
71
+ export {
72
+ generateSignature,
73
+ verifySignature,
74
+ verifyWebhookSignature,
75
+ parseSignatureHeader,
76
+ formatSignatureHeader,
77
+ generateWebhookSecret,
78
+ } from './signature.js';
79
+
80
+ // Retry strategies
81
+ export {
82
+ ExponentialBackoffStrategy,
83
+ LinearBackoffStrategy,
84
+ FixedDelayStrategy,
85
+ CustomDelayStrategy,
86
+ createDefaultRetryStrategy,
87
+ calculateNextRetryTime,
88
+ shouldRetryDelivery,
89
+ getExponentialBackoff,
90
+ parseRetryAfter,
91
+ } from './retry.js';
@@ -0,0 +1,196 @@
1
+ import type { WebhookRetryStrategy } from './types.js';
2
+
3
+ /**
4
+ * Exponential backoff retry strategy
5
+ */
6
+ export class ExponentialBackoffStrategy implements WebhookRetryStrategy {
7
+ constructor(
8
+ private initialDelay: number = 1000, // 1 second
9
+ private maxDelay: number = 60000, // 1 minute
10
+ private multiplier: number = 2,
11
+ private maxRetries: number = 5
12
+ ) {}
13
+
14
+ getNextRetryDelay(attempt: number): number {
15
+ if (attempt >= this.maxRetries) {
16
+ return -1; // No more retries
17
+ }
18
+
19
+ // Calculate exponential backoff: initialDelay * (multiplier ^ attempt)
20
+ const delay = this.initialDelay * Math.pow(this.multiplier, attempt);
21
+
22
+ // Add jitter (±25% randomization) to prevent thundering herd
23
+ const jitter = delay * 0.25 * (Math.random() * 2 - 1);
24
+
25
+ // Cap at maxDelay
26
+ return Math.min(delay + jitter, this.maxDelay);
27
+ }
28
+
29
+ shouldRetry(attempt: number, error?: Error): boolean {
30
+ // Don't retry if max attempts reached
31
+ if (attempt >= this.maxRetries) {
32
+ return false;
33
+ }
34
+
35
+ // Don't retry client errors (4xx), except specific cases
36
+ if (error && 'statusCode' in error) {
37
+ const statusCode = (error as { statusCode: number }).statusCode;
38
+
39
+ // Retry on server errors (5xx) and specific client errors
40
+ if (statusCode >= 500) {
41
+ return true;
42
+ }
43
+
44
+ // Retry on specific client errors
45
+ if (statusCode === 408 || statusCode === 429) {
46
+ return true; // Request Timeout or Too Many Requests
47
+ }
48
+
49
+ // Don't retry other client errors
50
+ if (statusCode >= 400 && statusCode < 500) {
51
+ return false;
52
+ }
53
+ }
54
+
55
+ return true;
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Linear backoff retry strategy
61
+ */
62
+ export class LinearBackoffStrategy implements WebhookRetryStrategy {
63
+ constructor(
64
+ private delay: number = 5000, // 5 seconds
65
+ private maxRetries: number = 3
66
+ ) {}
67
+
68
+ getNextRetryDelay(attempt: number): number {
69
+ if (attempt >= this.maxRetries) {
70
+ return -1;
71
+ }
72
+
73
+ // Linear increase: delay * (attempt + 1)
74
+ return this.delay * (attempt + 1);
75
+ }
76
+
77
+ shouldRetry(attempt: number): boolean {
78
+ return attempt < this.maxRetries;
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Fixed delay retry strategy
84
+ */
85
+ export class FixedDelayStrategy implements WebhookRetryStrategy {
86
+ constructor(
87
+ private delay: number = 10000, // 10 seconds
88
+ private maxRetries: number = 3
89
+ ) {}
90
+
91
+ getNextRetryDelay(attempt: number): number {
92
+ if (attempt >= this.maxRetries) {
93
+ return -1;
94
+ }
95
+
96
+ return this.delay;
97
+ }
98
+
99
+ shouldRetry(attempt: number): boolean {
100
+ return attempt < this.maxRetries;
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Custom retry delays strategy
106
+ */
107
+ export class CustomDelayStrategy implements WebhookRetryStrategy {
108
+ constructor(private delays: number[]) {}
109
+
110
+ getNextRetryDelay(attempt: number): number {
111
+ if (attempt >= this.delays.length) {
112
+ return -1;
113
+ }
114
+
115
+ return this.delays[attempt] ?? -1;
116
+ }
117
+
118
+ shouldRetry(attempt: number): boolean {
119
+ return attempt < this.delays.length;
120
+ }
121
+ }
122
+
123
+ /**
124
+ * Retry utility functions
125
+ */
126
+
127
+ /**
128
+ * Calculate next retry time
129
+ */
130
+ export function calculateNextRetryTime(
131
+ attempt: number,
132
+ strategy: WebhookRetryStrategy
133
+ ): Date | null {
134
+ const delay = strategy.getNextRetryDelay(attempt);
135
+
136
+ if (delay < 0) {
137
+ return null;
138
+ }
139
+
140
+ return new Date(Date.now() + delay);
141
+ }
142
+
143
+ /**
144
+ * Check if delivery should be retried
145
+ */
146
+ export function shouldRetryDelivery(
147
+ attempts: number,
148
+ strategy: WebhookRetryStrategy,
149
+ error?: Error
150
+ ): boolean {
151
+ return strategy.shouldRetry(attempts, error);
152
+ }
153
+
154
+ /**
155
+ * Get retry delay with exponential backoff
156
+ */
157
+ export function getExponentialBackoff(
158
+ attempt: number,
159
+ initialDelay = 1000,
160
+ maxDelay = 60000,
161
+ multiplier = 2
162
+ ): number {
163
+ const delay = initialDelay * Math.pow(multiplier, attempt);
164
+ const jitter = delay * 0.25 * (Math.random() * 2 - 1);
165
+ return Math.min(delay + jitter, maxDelay);
166
+ }
167
+
168
+ /**
169
+ * Create default retry strategy
170
+ */
171
+ export function createDefaultRetryStrategy(): WebhookRetryStrategy {
172
+ return new ExponentialBackoffStrategy(1000, 60000, 2, 5);
173
+ }
174
+
175
+ /**
176
+ * Parse retry-after header
177
+ */
178
+ export function parseRetryAfter(retryAfter: string | number): number {
179
+ if (typeof retryAfter === 'number') {
180
+ return retryAfter * 1000; // seconds to milliseconds
181
+ }
182
+
183
+ // Try parsing as number (seconds)
184
+ const seconds = parseInt(retryAfter, 10);
185
+ if (!isNaN(seconds)) {
186
+ return seconds * 1000;
187
+ }
188
+
189
+ // Try parsing as HTTP date
190
+ const date = new Date(retryAfter);
191
+ if (!isNaN(date.getTime())) {
192
+ return Math.max(0, date.getTime() - Date.now());
193
+ }
194
+
195
+ return 0;
196
+ }
@@ -0,0 +1,135 @@
1
+ import { createHmac, timingSafeEqual } from 'crypto';
2
+ import type { WebhookSignature } from './types.js';
3
+
4
+ /**
5
+ * Generate HMAC signature for webhook payload
6
+ */
7
+ export function generateSignature(
8
+ payload: string | Record<string, unknown>,
9
+ secret: string,
10
+ timestamp?: number
11
+ ): WebhookSignature {
12
+ const ts = timestamp || Date.now();
13
+ const payloadString = typeof payload === 'string' ? payload : JSON.stringify(payload);
14
+
15
+ // Format: timestamp.payload
16
+ const signedPayload = `${ts}.${payloadString}`;
17
+
18
+ // Generate HMAC SHA256 signature
19
+ const signature = createHmac('sha256', secret).update(signedPayload).digest('hex');
20
+
21
+ return {
22
+ signature,
23
+ timestamp: ts,
24
+ version: 'v1',
25
+ };
26
+ }
27
+
28
+ /**
29
+ * Verify webhook signature
30
+ */
31
+ export function verifySignature(
32
+ payload: string | Record<string, unknown>,
33
+ signature: string,
34
+ secret: string,
35
+ timestamp: number,
36
+ options: {
37
+ toleranceSeconds?: number;
38
+ } = {}
39
+ ): boolean {
40
+ const { toleranceSeconds = 300 } = options; // 5 minutes default tolerance
41
+
42
+ // Check timestamp tolerance
43
+ const now = Date.now();
44
+ const timeDiff = Math.abs(now - timestamp);
45
+
46
+ if (timeDiff > toleranceSeconds * 1000) {
47
+ return false;
48
+ }
49
+
50
+ // Generate expected signature
51
+ const expected = generateSignature(payload, secret, timestamp);
52
+
53
+ // Timing-safe comparison
54
+ try {
55
+ const receivedBuffer = Buffer.from(signature, 'hex');
56
+ const expectedBuffer = Buffer.from(expected.signature, 'hex');
57
+
58
+ if (receivedBuffer.length !== expectedBuffer.length) {
59
+ return false;
60
+ }
61
+
62
+ return timingSafeEqual(receivedBuffer, expectedBuffer);
63
+ } catch {
64
+ return false;
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Parse signature header
70
+ * Format: "t=<timestamp>,v1=<signature>"
71
+ */
72
+ export function parseSignatureHeader(header: string): {
73
+ timestamp: number;
74
+ signature: string;
75
+ } | null {
76
+ try {
77
+ const parts = header.split(',');
78
+ const result: { timestamp?: number; signature?: string } = {};
79
+
80
+ for (const part of parts) {
81
+ const [key, value] = part.split('=');
82
+ if (key === 't' && value) {
83
+ result.timestamp = parseInt(value, 10);
84
+ } else if (key === 'v1' && value) {
85
+ result.signature = value;
86
+ }
87
+ }
88
+
89
+ if (result.timestamp && result.signature) {
90
+ return {
91
+ timestamp: result.timestamp,
92
+ signature: result.signature,
93
+ };
94
+ }
95
+
96
+ return null;
97
+ } catch {
98
+ return null;
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Format signature for header
104
+ * Format: "t=<timestamp>,v1=<signature>"
105
+ */
106
+ export function formatSignatureHeader(sig: WebhookSignature): string {
107
+ return `t=${sig.timestamp},v1=${sig.signature}`;
108
+ }
109
+
110
+ /**
111
+ * Generate webhook secret
112
+ */
113
+ export function generateWebhookSecret(): string {
114
+ return createHmac('sha256', Date.now().toString()).update(Math.random().toString()).digest('hex');
115
+ }
116
+
117
+ /**
118
+ * Verify webhook signature from headers
119
+ */
120
+ export function verifyWebhookSignature(
121
+ payload: string | Record<string, unknown>,
122
+ signatureHeader: string,
123
+ secret: string,
124
+ options: {
125
+ toleranceSeconds?: number;
126
+ } = {}
127
+ ): boolean {
128
+ const parsed = parseSignatureHeader(signatureHeader);
129
+
130
+ if (!parsed) {
131
+ return false;
132
+ }
133
+
134
+ return verifySignature(payload, parsed.signature, secret, parsed.timestamp, options);
135
+ }
@@ -0,0 +1,181 @@
1
+ export type WebhookEventType =
2
+ | 'user.created'
3
+ | 'user.updated'
4
+ | 'user.deleted'
5
+ | 'payment.succeeded'
6
+ | 'payment.failed'
7
+ | 'order.created'
8
+ | 'order.completed'
9
+ | 'order.cancelled'
10
+ | string; // Allow custom event types
11
+
12
+ export type WebhookStatus = 'pending' | 'delivered' | 'failed' | 'disabled';
13
+
14
+ export type WebhookDeliveryStatus = 'pending' | 'success' | 'failed' | 'retrying';
15
+
16
+ export interface WebhookEndpoint {
17
+ /** Unique identifier */
18
+ id: string;
19
+ /** Endpoint URL */
20
+ url: string;
21
+ /** Secret for HMAC signature */
22
+ secret: string;
23
+ /** Events to listen for */
24
+ events: WebhookEventType[];
25
+ /** Whether the endpoint is active */
26
+ enabled: boolean;
27
+ /** Optional description */
28
+ description?: string;
29
+ /** Custom headers to send */
30
+ headers?: Record<string, string>;
31
+ /** Metadata */
32
+ metadata?: Record<string, unknown>;
33
+ /** Creation date */
34
+ createdAt: Date;
35
+ /** Last update date */
36
+ updatedAt: Date;
37
+ }
38
+
39
+ export interface WebhookEvent {
40
+ /** Unique identifier */
41
+ id: string;
42
+ /** Event type */
43
+ type: WebhookEventType;
44
+ /** Event payload */
45
+ payload: Record<string, unknown>;
46
+ /** When the event occurred */
47
+ occurredAt: Date;
48
+ /** Endpoints that should receive this event */
49
+ endpoints?: string[];
50
+ }
51
+
52
+ export interface WebhookDelivery {
53
+ /** Unique identifier */
54
+ id: string;
55
+ /** Webhook endpoint ID */
56
+ endpointId: string;
57
+ /** Event ID */
58
+ eventId: string;
59
+ /** Event type */
60
+ eventType: WebhookEventType;
61
+ /** Delivery status */
62
+ status: WebhookDeliveryStatus;
63
+ /** Number of attempts */
64
+ attempts: number;
65
+ /** Maximum attempts */
66
+ maxAttempts: number;
67
+ /** Next retry time */
68
+ nextRetryAt?: Date;
69
+ /** HTTP response status */
70
+ responseStatus?: number;
71
+ /** Response body */
72
+ responseBody?: string;
73
+ /** Error message if failed */
74
+ error?: string;
75
+ /** Request payload */
76
+ payload: Record<string, unknown>;
77
+ /** Creation date */
78
+ createdAt: Date;
79
+ /** Last attempt date */
80
+ lastAttemptAt?: Date;
81
+ /** Delivery completion date */
82
+ deliveredAt?: Date;
83
+ }
84
+
85
+ export interface WebhookConfig {
86
+ /** Default maximum retry attempts */
87
+ maxRetries?: number;
88
+ /** Initial retry delay in ms */
89
+ initialRetryDelay?: number;
90
+ /** Maximum retry delay in ms */
91
+ maxRetryDelay?: number;
92
+ /** Backoff multiplier */
93
+ backoffMultiplier?: number;
94
+ /** Request timeout in ms */
95
+ timeout?: number;
96
+ /** Enable signature verification */
97
+ enableSignature?: boolean;
98
+ /** Signature header name */
99
+ signatureHeader?: string;
100
+ /** Timestamp header name */
101
+ timestampHeader?: string;
102
+ }
103
+
104
+ export interface WebhookSignature {
105
+ /** Signature value */
106
+ signature: string;
107
+ /** Timestamp */
108
+ timestamp: number;
109
+ /** Version */
110
+ version: string;
111
+ }
112
+
113
+ export interface WebhookDeliveryAttempt {
114
+ /** Attempt number */
115
+ attempt: number;
116
+ /** HTTP status code */
117
+ statusCode?: number;
118
+ /** Response body */
119
+ responseBody?: string;
120
+ /** Error message */
121
+ error?: string;
122
+ /** Attempt timestamp */
123
+ timestamp: Date;
124
+ /** Duration in ms */
125
+ duration?: number;
126
+ }
127
+
128
+ export interface WebhookStats {
129
+ /** Total events sent */
130
+ totalEvents: number;
131
+ /** Successful deliveries */
132
+ successfulDeliveries: number;
133
+ /** Failed deliveries */
134
+ failedDeliveries: number;
135
+ /** Pending deliveries */
136
+ pendingDeliveries: number;
137
+ /** Average delivery time in ms */
138
+ averageDeliveryTime: number;
139
+ /** Success rate (0-100) */
140
+ successRate: number;
141
+ }
142
+
143
+ export interface WebhookFilter {
144
+ /** Filter by endpoint ID */
145
+ endpointId?: string;
146
+ /** Filter by event type */
147
+ eventType?: WebhookEventType;
148
+ /** Filter by status */
149
+ status?: WebhookDeliveryStatus;
150
+ /** Filter by date range */
151
+ startDate?: Date;
152
+ endDate?: Date;
153
+ /** Limit results */
154
+ limit?: number;
155
+ /** Offset for pagination */
156
+ offset?: number;
157
+ }
158
+
159
+ export interface CreateWebhookEndpointData {
160
+ url: string;
161
+ events: WebhookEventType[];
162
+ description?: string;
163
+ headers?: Record<string, string>;
164
+ metadata?: Record<string, unknown>;
165
+ }
166
+
167
+ export interface UpdateWebhookEndpointData {
168
+ url?: string;
169
+ events?: WebhookEventType[];
170
+ enabled?: boolean;
171
+ description?: string;
172
+ headers?: Record<string, string>;
173
+ metadata?: Record<string, unknown>;
174
+ }
175
+
176
+ export interface WebhookRetryStrategy {
177
+ /** Calculate next retry delay */
178
+ getNextRetryDelay(attempt: number): number;
179
+ /** Check if should retry */
180
+ shouldRetry(attempt: number, error?: Error): boolean;
181
+ }