@buenojs/bueno 0.8.4 → 0.8.6

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 (234) hide show
  1. package/README.md +264 -17
  2. package/dist/cli/{index.js → bin.js} +413 -332
  3. package/dist/container/index.js +273 -0
  4. package/dist/context/index.js +219 -0
  5. package/dist/database/index.js +493 -0
  6. package/dist/frontend/index.js +7697 -0
  7. package/dist/graphql/index.js +2156 -0
  8. package/dist/health/index.js +364 -0
  9. package/dist/i18n/index.js +345 -0
  10. package/dist/index.js +9694 -5047
  11. package/dist/jobs/index.js +819 -0
  12. package/dist/lock/index.js +367 -0
  13. package/dist/logger/index.js +281 -0
  14. package/dist/metrics/index.js +289 -0
  15. package/dist/middleware/index.js +77 -0
  16. package/dist/migrations/index.js +571 -0
  17. package/dist/modules/index.js +3411 -0
  18. package/dist/notification/index.js +484 -0
  19. package/dist/observability/index.js +331 -0
  20. package/dist/openapi/index.js +795 -0
  21. package/dist/orm/index.js +1356 -0
  22. package/dist/router/index.js +886 -0
  23. package/dist/rpc/index.js +691 -0
  24. package/dist/schema/index.js +400 -0
  25. package/dist/telemetry/index.js +595 -0
  26. package/dist/template/index.js +640 -0
  27. package/dist/templates/index.js +640 -0
  28. package/dist/testing/index.js +1111 -0
  29. package/dist/types/index.js +60 -0
  30. package/llms.txt +231 -0
  31. package/package.json +125 -27
  32. package/src/cache/index.ts +2 -1
  33. package/src/cli/ARCHITECTURE.md +3 -3
  34. package/src/cli/bin.ts +2 -2
  35. package/src/cli/commands/build.ts +183 -165
  36. package/src/cli/commands/dev.ts +96 -89
  37. package/src/cli/commands/generate.ts +142 -111
  38. package/src/cli/commands/help.ts +20 -16
  39. package/src/cli/commands/index.ts +3 -6
  40. package/src/cli/commands/migration.ts +124 -105
  41. package/src/cli/commands/new.ts +294 -232
  42. package/src/cli/commands/start.ts +81 -79
  43. package/src/cli/core/args.ts +68 -50
  44. package/src/cli/core/console.ts +89 -95
  45. package/src/cli/core/index.ts +4 -4
  46. package/src/cli/core/prompt.ts +65 -62
  47. package/src/cli/core/spinner.ts +23 -20
  48. package/src/cli/index.ts +46 -38
  49. package/src/cli/templates/database/index.ts +37 -18
  50. package/src/cli/templates/database/mysql.ts +3 -3
  51. package/src/cli/templates/database/none.ts +2 -2
  52. package/src/cli/templates/database/postgresql.ts +3 -3
  53. package/src/cli/templates/database/sqlite.ts +3 -3
  54. package/src/cli/templates/deploy.ts +29 -26
  55. package/src/cli/templates/docker.ts +41 -30
  56. package/src/cli/templates/frontend/index.ts +33 -15
  57. package/src/cli/templates/frontend/none.ts +2 -2
  58. package/src/cli/templates/frontend/react.ts +18 -18
  59. package/src/cli/templates/frontend/solid.ts +15 -15
  60. package/src/cli/templates/frontend/svelte.ts +17 -17
  61. package/src/cli/templates/frontend/vue.ts +15 -15
  62. package/src/cli/templates/generators/index.ts +29 -29
  63. package/src/cli/templates/generators/types.ts +21 -21
  64. package/src/cli/templates/index.ts +6 -6
  65. package/src/cli/templates/project/api.ts +37 -36
  66. package/src/cli/templates/project/default.ts +25 -25
  67. package/src/cli/templates/project/fullstack.ts +28 -26
  68. package/src/cli/templates/project/index.ts +55 -16
  69. package/src/cli/templates/project/minimal.ts +17 -12
  70. package/src/cli/templates/project/types.ts +10 -5
  71. package/src/cli/templates/project/website.ts +15 -15
  72. package/src/cli/utils/fs.ts +55 -41
  73. package/src/cli/utils/index.ts +3 -3
  74. package/src/cli/utils/strings.ts +47 -33
  75. package/src/cli/utils/version.ts +14 -8
  76. package/src/config/env-validation.ts +100 -0
  77. package/src/config/env.ts +169 -41
  78. package/src/config/index.ts +28 -20
  79. package/src/config/loader.ts +25 -16
  80. package/src/config/merge.ts +21 -10
  81. package/src/config/types.ts +566 -25
  82. package/src/config/validation.ts +215 -7
  83. package/src/container/forward-ref.ts +22 -22
  84. package/src/container/index.ts +34 -12
  85. package/src/context/index.ts +11 -1
  86. package/src/database/index.ts +7 -190
  87. package/src/database/orm/builder.ts +457 -0
  88. package/src/database/orm/casts/index.ts +130 -0
  89. package/src/database/orm/casts/types.ts +25 -0
  90. package/src/database/orm/compiler.ts +304 -0
  91. package/src/database/orm/hooks/index.ts +114 -0
  92. package/src/database/orm/index.ts +61 -0
  93. package/src/database/orm/model-registry.ts +59 -0
  94. package/src/database/orm/model.ts +821 -0
  95. package/src/database/orm/relationships/base.ts +146 -0
  96. package/src/database/orm/relationships/belongs-to-many.ts +179 -0
  97. package/src/database/orm/relationships/belongs-to.ts +56 -0
  98. package/src/database/orm/relationships/has-many.ts +45 -0
  99. package/src/database/orm/relationships/has-one.ts +41 -0
  100. package/src/database/orm/relationships/index.ts +11 -0
  101. package/src/database/orm/scopes/index.ts +55 -0
  102. package/src/events/__tests__/event-system.test.ts +235 -0
  103. package/src/events/config.ts +238 -0
  104. package/src/events/example-usage.ts +185 -0
  105. package/src/events/index.ts +278 -0
  106. package/src/events/manager.ts +385 -0
  107. package/src/events/registry.ts +182 -0
  108. package/src/events/types.ts +124 -0
  109. package/src/frontend/api-routes.ts +65 -23
  110. package/src/frontend/bundler.ts +76 -34
  111. package/src/frontend/console-client.ts +2 -2
  112. package/src/frontend/console-stream.ts +94 -38
  113. package/src/frontend/dev-server.ts +94 -46
  114. package/src/frontend/file-router.ts +61 -19
  115. package/src/frontend/frameworks/index.ts +37 -10
  116. package/src/frontend/frameworks/react.ts +10 -8
  117. package/src/frontend/frameworks/solid.ts +11 -9
  118. package/src/frontend/frameworks/svelte.ts +15 -9
  119. package/src/frontend/frameworks/vue.ts +13 -11
  120. package/src/frontend/hmr-client.ts +12 -10
  121. package/src/frontend/hmr.ts +146 -103
  122. package/src/frontend/index.ts +14 -5
  123. package/src/frontend/islands.ts +41 -22
  124. package/src/frontend/isr.ts +59 -37
  125. package/src/frontend/layout.ts +36 -21
  126. package/src/frontend/ssr/react.ts +74 -27
  127. package/src/frontend/ssr/solid.ts +54 -20
  128. package/src/frontend/ssr/svelte.ts +48 -14
  129. package/src/frontend/ssr/vue.ts +50 -18
  130. package/src/frontend/ssr.ts +83 -39
  131. package/src/frontend/types.ts +91 -56
  132. package/src/graphql/built-in-engine.ts +598 -0
  133. package/src/graphql/context-builder.ts +110 -0
  134. package/src/graphql/decorators.ts +358 -0
  135. package/src/graphql/execution-pipeline.ts +227 -0
  136. package/src/graphql/graphql-module.ts +563 -0
  137. package/src/graphql/index.ts +101 -0
  138. package/src/graphql/metadata.ts +237 -0
  139. package/src/graphql/schema-builder.ts +319 -0
  140. package/src/graphql/subscription-handler.ts +283 -0
  141. package/src/graphql/types.ts +324 -0
  142. package/src/health/index.ts +21 -9
  143. package/src/i18n/engine.ts +305 -0
  144. package/src/i18n/index.ts +38 -0
  145. package/src/i18n/loader.ts +218 -0
  146. package/src/i18n/middleware.ts +164 -0
  147. package/src/i18n/negotiator.ts +162 -0
  148. package/src/i18n/types.ts +158 -0
  149. package/src/index.ts +182 -27
  150. package/src/jobs/drivers/memory.ts +315 -0
  151. package/src/jobs/drivers/redis.ts +459 -0
  152. package/src/jobs/index.ts +30 -0
  153. package/src/jobs/queue.ts +281 -0
  154. package/src/jobs/types.ts +295 -0
  155. package/src/jobs/worker.ts +380 -0
  156. package/src/logger/index.ts +1 -3
  157. package/src/logger/transports/index.ts +62 -22
  158. package/src/metrics/index.ts +25 -16
  159. package/src/migrations/index.ts +9 -0
  160. package/src/modules/filters.ts +13 -17
  161. package/src/modules/guards.ts +49 -26
  162. package/src/modules/index.ts +457 -299
  163. package/src/modules/interceptors.ts +58 -20
  164. package/src/modules/lazy.ts +11 -19
  165. package/src/modules/lifecycle.ts +15 -7
  166. package/src/modules/metadata.ts +15 -5
  167. package/src/modules/pipes.ts +94 -72
  168. package/src/notification/channels/base.ts +68 -0
  169. package/src/notification/channels/email.ts +105 -0
  170. package/src/notification/channels/push.ts +104 -0
  171. package/src/notification/channels/sms.ts +105 -0
  172. package/src/notification/channels/whatsapp.ts +104 -0
  173. package/src/notification/index.ts +48 -0
  174. package/src/notification/service.ts +354 -0
  175. package/src/notification/types.ts +344 -0
  176. package/src/observability/__tests__/observability.test.ts +483 -0
  177. package/src/observability/breadcrumbs.ts +114 -0
  178. package/src/observability/index.ts +136 -0
  179. package/src/observability/interceptor.ts +85 -0
  180. package/src/observability/service.ts +303 -0
  181. package/src/observability/trace.ts +37 -0
  182. package/src/observability/types.ts +196 -0
  183. package/src/openapi/__tests__/decorators.test.ts +335 -0
  184. package/src/openapi/__tests__/document-builder.test.ts +285 -0
  185. package/src/openapi/__tests__/route-scanner.test.ts +334 -0
  186. package/src/openapi/__tests__/schema-generator.test.ts +275 -0
  187. package/src/openapi/decorators.ts +328 -0
  188. package/src/openapi/document-builder.ts +274 -0
  189. package/src/openapi/index.ts +112 -0
  190. package/src/openapi/metadata.ts +112 -0
  191. package/src/openapi/route-scanner.ts +289 -0
  192. package/src/openapi/schema-generator.ts +256 -0
  193. package/src/openapi/swagger-module.ts +166 -0
  194. package/src/openapi/types.ts +398 -0
  195. package/src/orm/index.ts +10 -0
  196. package/src/rpc/index.ts +3 -1
  197. package/src/schema/index.ts +9 -0
  198. package/src/security/index.ts +15 -6
  199. package/src/ssg/index.ts +9 -8
  200. package/src/telemetry/index.ts +76 -22
  201. package/src/template/index.ts +7 -0
  202. package/src/templates/engine.ts +224 -0
  203. package/src/templates/index.ts +9 -0
  204. package/src/templates/loader.ts +331 -0
  205. package/src/templates/renderers/markdown.ts +212 -0
  206. package/src/templates/renderers/simple.ts +269 -0
  207. package/src/templates/types.ts +154 -0
  208. package/src/testing/index.ts +100 -27
  209. package/src/types/optional-deps.d.ts +347 -187
  210. package/src/validation/index.ts +92 -2
  211. package/src/validation/schemas.ts +536 -0
  212. package/tests/integration/cli.test.ts +19 -19
  213. package/tests/integration/fullstack.test.ts +4 -4
  214. package/tests/unit/cli.test.ts +1 -1
  215. package/tests/unit/database.test.ts +2 -72
  216. package/tests/unit/env-validation.test.ts +166 -0
  217. package/tests/unit/events.test.ts +910 -0
  218. package/tests/unit/graphql.test.ts +991 -0
  219. package/tests/unit/i18n.test.ts +455 -0
  220. package/tests/unit/jobs.test.ts +493 -0
  221. package/tests/unit/notification.test.ts +988 -0
  222. package/tests/unit/observability.test.ts +453 -0
  223. package/tests/unit/orm/builder.test.ts +323 -0
  224. package/tests/unit/orm/casts.test.ts +179 -0
  225. package/tests/unit/orm/compiler.test.ts +220 -0
  226. package/tests/unit/orm/eager-loading.test.ts +285 -0
  227. package/tests/unit/orm/hooks.test.ts +191 -0
  228. package/tests/unit/orm/model.test.ts +373 -0
  229. package/tests/unit/orm/relationships.test.ts +303 -0
  230. package/tests/unit/orm/scopes.test.ts +74 -0
  231. package/tests/unit/templates-simple.test.ts +53 -0
  232. package/tests/unit/templates.test.ts +454 -0
  233. package/tests/unit/validation.test.ts +18 -24
  234. package/tsconfig.json +11 -3
@@ -0,0 +1,281 @@
1
+ /**
2
+ * Job Queue Factory and API
3
+ *
4
+ * Main public interface for enqueueing and managing background jobs.
5
+ * Supports both Redis and in-memory drivers.
6
+ */
7
+
8
+ import { MemoryJobQueueDriver } from "./drivers/memory";
9
+ import { RedisJobQueueDriver } from "./drivers/redis";
10
+ import type {
11
+ HandlerRegistryEntry,
12
+ Job,
13
+ JobEvent,
14
+ JobEventType,
15
+ JobHandler,
16
+ JobQueueConfig,
17
+ JobQueueDriver,
18
+ QueueMetrics,
19
+ QueueOptions,
20
+ } from "./types";
21
+
22
+ // ============= Job Queue Class =============
23
+
24
+ export class JobQueue {
25
+ private driver: JobQueueDriver;
26
+ private handlers: Map<string, JobHandler<unknown>> = new Map();
27
+ private handlerRegistry: HandlerRegistryEntry[] = [];
28
+ private eventListeners: Map<JobEventType, Set<(event: JobEvent) => void>> =
29
+ new Map();
30
+ private isRunning = false;
31
+
32
+ constructor(config: JobQueueConfig = {}) {
33
+ // Instantiate appropriate driver
34
+ const driver = config.driver ?? "memory";
35
+
36
+ if (driver === "redis") {
37
+ if (!config.url) {
38
+ throw new Error("Redis URL is required for Redis driver");
39
+ }
40
+ this.driver = new RedisJobQueueDriver(config);
41
+ } else {
42
+ this.driver = new MemoryJobQueueDriver(config);
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Initialize the queue (connect to backend)
48
+ */
49
+ async init(): Promise<void> {
50
+ await this.driver.connect();
51
+ }
52
+
53
+ /**
54
+ * Shutdown the queue gracefully
55
+ */
56
+ async shutdown(): Promise<void> {
57
+ this.isRunning = false;
58
+ await this.driver.disconnect();
59
+ }
60
+
61
+ /**
62
+ * Enqueue a job
63
+ * @param name - Job name/type (e.g., "email.welcome")
64
+ * @param data - Job payload
65
+ * @param options - Queue options (delay, priority, timeout)
66
+ * @returns Job ID
67
+ */
68
+ async enqueue<T = unknown>(
69
+ name: string,
70
+ data: T,
71
+ options?: QueueOptions,
72
+ ): Promise<string> {
73
+ const jobId = await this.driver.enqueue(name, data, options);
74
+ this._emitEvent("enqueued", { id: jobId, name } as unknown as Job);
75
+ return jobId;
76
+ }
77
+
78
+ /**
79
+ * Register a handler for a job type
80
+ * Supports wildcards: "email.*" matches "email.welcome", "email.reset", etc.
81
+ */
82
+ on(pattern: string, handler: JobHandler<unknown>): void {
83
+ this.handlers.set(pattern, handler);
84
+
85
+ // Calculate specificity for wildcard matching (longer = more specific)
86
+ const specificity = pattern.split(".").length;
87
+ const entry: HandlerRegistryEntry = { pattern, handler, specificity };
88
+
89
+ // Insert in order of specificity (highest first)
90
+ const index = this.handlerRegistry.findIndex(
91
+ (e) => e.specificity < specificity,
92
+ );
93
+ if (index >= 0) {
94
+ this.handlerRegistry.splice(index, 0, entry);
95
+ } else {
96
+ this.handlerRegistry.push(entry);
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Remove a handler
102
+ */
103
+ off(pattern: string): void {
104
+ this.handlers.delete(pattern);
105
+ const index = this.handlerRegistry.findIndex((e) => e.pattern === pattern);
106
+ if (index >= 0) {
107
+ this.handlerRegistry.splice(index, 1);
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Find the best matching handler for a job
113
+ */
114
+ private findHandler(jobName: string): JobHandler<unknown> | null {
115
+ for (const entry of this.handlerRegistry) {
116
+ if (this._patternMatches(entry.pattern, jobName)) {
117
+ return entry.handler;
118
+ }
119
+ }
120
+ return null;
121
+ }
122
+
123
+ /**
124
+ * Check if a pattern matches a job name (supports wildcards)
125
+ */
126
+ private _patternMatches(pattern: string, jobName: string): boolean {
127
+ if (pattern === jobName) return true;
128
+
129
+ // Handle wildcards: "email.*" matches "email.welcome"
130
+ if (pattern.endsWith(".*")) {
131
+ const prefix = pattern.slice(0, -2); // Remove ".*"
132
+ return jobName.startsWith(prefix + ".");
133
+ }
134
+
135
+ return false;
136
+ }
137
+
138
+ /**
139
+ * Listen for queue events
140
+ */
141
+ onEvent(event: JobEventType, listener: (event: JobEvent) => void): void {
142
+ if (!this.eventListeners.has(event)) {
143
+ this.eventListeners.set(event, new Set());
144
+ }
145
+ this.eventListeners.get(event)!.add(listener);
146
+ }
147
+
148
+ /**
149
+ * Stop listening for queue events
150
+ */
151
+ offEvent(event: JobEventType, listener: (event: JobEvent) => void): void {
152
+ this.eventListeners.get(event)?.delete(listener);
153
+ }
154
+
155
+ /**
156
+ * Emit a queue event
157
+ */
158
+ private _emitEvent(eventType: JobEventType, job: Job): void {
159
+ const listeners = this.eventListeners.get(eventType);
160
+ if (!listeners) return;
161
+
162
+ const event: JobEvent = {
163
+ type: eventType,
164
+ job,
165
+ timestamp: new Date(),
166
+ };
167
+
168
+ for (const listener of listeners) {
169
+ try {
170
+ listener(event);
171
+ } catch (error) {
172
+ console.error(`Error in ${eventType} listener:`, error);
173
+ }
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Start the polling loop (for tests/manual control)
179
+ * Note: In production, use a separate worker process
180
+ */
181
+ async start(
182
+ options: { pollInterval?: number; concurrency?: number } = {},
183
+ ): Promise<void> {
184
+ this.isRunning = true;
185
+ const pollInterval = options.pollInterval ?? 1000;
186
+ const concurrency = options.concurrency ?? 10;
187
+
188
+ // This is a basic polling loop for simple use cases
189
+ // For production, use a dedicated worker process with JobWorker class
190
+ while (this.isRunning) {
191
+ try {
192
+ const jobs = await this.driver.claim(concurrency, 30000);
193
+
194
+ for (const job of jobs) {
195
+ if (!this.isRunning) break;
196
+
197
+ try {
198
+ const handler = this.findHandler(job.name);
199
+ if (!handler) {
200
+ console.warn(`No handler found for job type: ${job.name}`);
201
+ await this.driver.complete(job.id);
202
+ continue;
203
+ }
204
+
205
+ this._emitEvent("started", job);
206
+
207
+ await handler(job);
208
+
209
+ this._emitEvent("completed", job);
210
+ await this.driver.complete(job.id);
211
+ } catch (error) {
212
+ const errorMsg =
213
+ error instanceof Error ? error.message : String(error);
214
+ const stackTrace = error instanceof Error ? error.stack : undefined;
215
+
216
+ if (job.attempts < job.maxRetries) {
217
+ const backoffMs = Math.pow(2, job.attempts) * 1000; // Exponential backoff
218
+ this._emitEvent("retried", job);
219
+ await this.driver.scheduleRetry(job.id, backoffMs, errorMsg);
220
+ } else {
221
+ this._emitEvent("failed", job);
222
+ await this.driver.fail(job.id, errorMsg, stackTrace);
223
+ }
224
+ }
225
+ }
226
+
227
+ // Sleep before next poll
228
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
229
+ } catch (error) {
230
+ console.error("Error in job queue polling loop:", error);
231
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
232
+ }
233
+ }
234
+ }
235
+
236
+ /**
237
+ * Stop the polling loop
238
+ */
239
+ async stop(): Promise<void> {
240
+ this.isRunning = false;
241
+ // Wait a bit for current jobs to finish
242
+ await new Promise((resolve) => setTimeout(resolve, 1000));
243
+ }
244
+
245
+ /**
246
+ * Get a job by ID
247
+ */
248
+ async getJob(jobId: string): Promise<Job | null> {
249
+ return this.driver.getJob(jobId);
250
+ }
251
+
252
+ /**
253
+ * Get queue metrics
254
+ */
255
+ async getMetrics(): Promise<QueueMetrics> {
256
+ return this.driver.getMetrics();
257
+ }
258
+
259
+ /**
260
+ * Clear all jobs (use with caution!)
261
+ */
262
+ async clear(): Promise<void> {
263
+ return this.driver.clear();
264
+ }
265
+
266
+ /**
267
+ * Check if queue is connected
268
+ */
269
+ async isConnected(): Promise<boolean> {
270
+ return this.driver.isConnected();
271
+ }
272
+ }
273
+
274
+ // ============= Factory Function =============
275
+
276
+ /**
277
+ * Create a new job queue instance
278
+ */
279
+ export function createJobQueue(config?: JobQueueConfig): JobQueue {
280
+ return new JobQueue(config);
281
+ }
@@ -0,0 +1,295 @@
1
+ /**
2
+ * Background Jobs / Task Queue
3
+ *
4
+ * Type definitions for the job queue system.
5
+ * Supports Redis (production) and in-memory (development) drivers.
6
+ */
7
+
8
+ // ============= Job Status =============
9
+
10
+ export type JobStatus =
11
+ | "pending"
12
+ | "processing"
13
+ | "completed"
14
+ | "failed"
15
+ | "delayed";
16
+
17
+ // ============= Job Data Structure =============
18
+
19
+ /**
20
+ * Represents a background job
21
+ */
22
+ export interface Job<T = unknown> {
23
+ /** Unique job identifier */
24
+ id: string;
25
+
26
+ /** Job name/type (e.g., "email.welcome", "image.resize") */
27
+ name: string;
28
+
29
+ /** Job payload data */
30
+ data: T;
31
+
32
+ /** Current status of the job */
33
+ status: JobStatus;
34
+
35
+ /** Number of times this job has been attempted */
36
+ attempts: number;
37
+
38
+ /** Maximum number of retry attempts */
39
+ maxRetries: number;
40
+
41
+ /** Error message if job failed */
42
+ error?: string;
43
+
44
+ /** Stack trace if job failed */
45
+ stackTrace?: string;
46
+
47
+ /** When the job was created (ISO 8601) */
48
+ createdAt: string;
49
+
50
+ /** When the job was last updated (ISO 8601) */
51
+ updatedAt: string;
52
+
53
+ /** When the job should start (ISO 8601, for delayed jobs) */
54
+ scheduledFor?: string;
55
+
56
+ /** When job processing started (ISO 8601) */
57
+ startedAt?: string;
58
+
59
+ /** When job processing completed (ISO 8601) */
60
+ completedAt?: string;
61
+
62
+ /** How long the job took to complete in milliseconds */
63
+ duration?: number;
64
+
65
+ /** Job priority (higher = process sooner) */
66
+ priority?: number;
67
+
68
+ /** Custom metadata attached to the job */
69
+ metadata?: Record<string, unknown>;
70
+
71
+ /** Worker that claimed this job */
72
+ workerId?: string;
73
+
74
+ /** When the job claim expires (for dead letter handling) */
75
+ claimExpiresAt?: string;
76
+ }
77
+
78
+ // ============= Queue Configuration =============
79
+
80
+ export interface JobQueueConfig {
81
+ /** Driver type: 'redis' for production, 'memory' for development */
82
+ driver?: "redis" | "memory";
83
+
84
+ /** Redis connection URL (required if driver is 'redis') */
85
+ url?: string;
86
+
87
+ /** Key prefix for all jobs (default: 'jobs:') */
88
+ keyPrefix?: string;
89
+
90
+ /** Maximum number of jobs to process concurrently (default: 10) */
91
+ concurrency?: number;
92
+
93
+ /** Maximum number of retry attempts (default: 3) */
94
+ maxRetries?: number;
95
+
96
+ /** Base delay in seconds for exponential backoff (default: 1) */
97
+ retryDelay?: number;
98
+
99
+ /** Number of jobs to claim in a single poll (default: 10) */
100
+ batchSize?: number;
101
+
102
+ /** Polling interval in milliseconds (default: 1000) */
103
+ pollInterval?: number;
104
+
105
+ /** Job timeout in milliseconds (default: 300000 / 5 minutes) */
106
+ jobTimeout?: number;
107
+
108
+ /** Enable metrics collection (default: true) */
109
+ enableMetrics?: boolean;
110
+ }
111
+
112
+ // ============= Enqueue Options =============
113
+
114
+ export interface QueueOptions {
115
+ /** Schedule job for later execution (ISO 8601 or Date) */
116
+ delay?: number | Date;
117
+
118
+ /** Job priority (default: 0) */
119
+ priority?: number;
120
+
121
+ /** Custom metadata */
122
+ metadata?: Record<string, unknown>;
123
+
124
+ /** Override default job timeout in milliseconds */
125
+ timeout?: number;
126
+
127
+ /** Maximum retries for this specific job */
128
+ maxRetries?: number;
129
+ }
130
+
131
+ // ============= Handler Type =============
132
+
133
+ /**
134
+ * Async handler function for processing jobs
135
+ */
136
+ export type JobHandler<T = unknown> = (job: Job<T>) => Promise<void>;
137
+
138
+ // ============= Queue Metrics =============
139
+
140
+ /**
141
+ * Metrics for job queue observability
142
+ */
143
+ export interface QueueMetrics {
144
+ /** Total jobs enqueued */
145
+ enqueued: number;
146
+
147
+ /** Jobs successfully completed */
148
+ processed: number;
149
+
150
+ /** Jobs that failed (exceeded max retries) */
151
+ failed: number;
152
+
153
+ /** Jobs currently in pending state */
154
+ pending: number;
155
+
156
+ /** Jobs currently being processed */
157
+ processing: number;
158
+
159
+ /** Average job processing time in milliseconds */
160
+ avgLatency: number;
161
+
162
+ /** Total processing time in milliseconds */
163
+ totalLatency: number;
164
+
165
+ /** Number of job retries that occurred */
166
+ retried: number;
167
+
168
+ /** Success rate (0.0 to 1.0) */
169
+ successRate: number;
170
+
171
+ /** Average number of attempts per job */
172
+ avgAttempts: number;
173
+ }
174
+
175
+ // ============= Events =============
176
+
177
+ export type JobEventType =
178
+ | "enqueued"
179
+ | "started"
180
+ | "completed"
181
+ | "failed"
182
+ | "retried";
183
+
184
+ export interface JobEvent<T = unknown> {
185
+ type: JobEventType;
186
+ job: Job<T>;
187
+ timestamp: Date;
188
+ metadata?: Record<string, unknown>;
189
+ }
190
+
191
+ // ============= Lock Handle for Job Claims =============
192
+
193
+ export interface JobClaimHandle {
194
+ /** Whether the claim was successfully acquired */
195
+ acquired: boolean;
196
+
197
+ /** Release the claim (job goes back to pending) */
198
+ release: () => Promise<boolean>;
199
+
200
+ /** Extend the claim TTL for long-running jobs */
201
+ extend: (ttl?: number) => Promise<boolean>;
202
+
203
+ /** Check if claim is still valid */
204
+ isValid: () => Promise<boolean>;
205
+
206
+ /** Get remaining TTL in milliseconds */
207
+ getRemainingTTL: () => Promise<number>;
208
+
209
+ /** The claim key */
210
+ key: string;
211
+
212
+ /** The claim value (unique identifier) */
213
+ value: string;
214
+ }
215
+
216
+ // ============= Driver Interface =============
217
+
218
+ /**
219
+ * Interface that both Redis and Memory drivers must implement
220
+ */
221
+ export interface JobQueueDriver {
222
+ /**
223
+ * Initialize the driver (e.g., connect to Redis)
224
+ */
225
+ connect(): Promise<void>;
226
+
227
+ /**
228
+ * Disconnect from the driver
229
+ */
230
+ disconnect(): Promise<void>;
231
+
232
+ /**
233
+ * Check if connected
234
+ */
235
+ isConnected(): Promise<boolean>;
236
+
237
+ /**
238
+ * Enqueue a new job
239
+ */
240
+ enqueue<T = unknown>(
241
+ name: string,
242
+ data: T,
243
+ options?: QueueOptions,
244
+ ): Promise<string>;
245
+
246
+ /**
247
+ * Claim a batch of pending jobs for processing
248
+ */
249
+ claim(count: number, timeout: number): Promise<Job[]>;
250
+
251
+ /**
252
+ * Mark a job as completed
253
+ */
254
+ complete(jobId: string): Promise<void>;
255
+
256
+ /**
257
+ * Mark a job as failed
258
+ */
259
+ fail(jobId: string, error: string, stackTrace?: string): Promise<void>;
260
+
261
+ /**
262
+ * Schedule a job for retry
263
+ */
264
+ scheduleRetry(jobId: string, delayMs: number, error: string): Promise<void>;
265
+
266
+ /**
267
+ * Get a job by ID
268
+ */
269
+ getJob(jobId: string): Promise<Job | null>;
270
+
271
+ /**
272
+ * Get queue metrics
273
+ */
274
+ getMetrics(): Promise<QueueMetrics>;
275
+
276
+ /**
277
+ * Clear all jobs from the queue (use with caution!)
278
+ */
279
+ clear(): Promise<void>;
280
+ }
281
+
282
+ // ============= Handler Registry Entry =============
283
+
284
+ export interface HandlerRegistryEntry {
285
+ pattern: string;
286
+ handler: JobHandler<unknown>;
287
+ specificity: number; // For wildcard matching priority
288
+ }
289
+
290
+ // ============= Configuration Validation =============
291
+
292
+ export interface JobConfigValidationResult {
293
+ valid: boolean;
294
+ errors: string[];
295
+ }