@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,380 @@
1
+ /**
2
+ * Job Worker
3
+ *
4
+ * Production-grade worker for processing background jobs.
5
+ * Designed to run in a separate process/worker.
6
+ * Supports graceful shutdown and exponential backoff polling.
7
+ */
8
+
9
+ import { MemoryJobQueueDriver } from "./drivers/memory";
10
+ import { RedisJobQueueDriver } from "./drivers/redis";
11
+ import type {
12
+ HandlerRegistryEntry,
13
+ Job,
14
+ JobEvent,
15
+ JobEventType,
16
+ JobHandler,
17
+ JobQueueConfig,
18
+ JobQueueDriver,
19
+ } from "./types";
20
+
21
+ // ============= Job Worker Class =============
22
+
23
+ export class JobWorker {
24
+ private driver: JobQueueDriver;
25
+ private handlers: Map<string, JobHandler<unknown>> = new Map();
26
+ private handlerRegistry: HandlerRegistryEntry[] = [];
27
+ private eventListeners: Map<JobEventType, Set<(event: JobEvent) => void>> =
28
+ new Map();
29
+ private isRunning = false;
30
+ private pollInterval: number;
31
+ private concurrency: number;
32
+ private jobTimeout: number;
33
+ private maxBackoffDelay = 30000; // 30 seconds max backoff
34
+ private currentBackoff = 0;
35
+ private inFlightJobs = new Set<string>();
36
+ private shutdownTimeout = 10000; // 10 seconds to drain
37
+
38
+ constructor(config: JobQueueConfig = {}) {
39
+ // Instantiate appropriate driver
40
+ const driver = config.driver ?? "memory";
41
+
42
+ if (driver === "redis") {
43
+ if (!config.url) {
44
+ throw new Error("Redis URL is required for Redis driver");
45
+ }
46
+ this.driver = new RedisJobQueueDriver(config);
47
+ } else {
48
+ this.driver = new MemoryJobQueueDriver(config);
49
+ }
50
+
51
+ this.pollInterval = config.pollInterval ?? 1000;
52
+ this.concurrency = config.concurrency ?? 10;
53
+ this.jobTimeout = config.jobTimeout ?? 300000; // 5 minutes default
54
+ }
55
+
56
+ /**
57
+ * Initialize the worker (connect to backend)
58
+ */
59
+ async init(): Promise<void> {
60
+ await this.driver.connect();
61
+ }
62
+
63
+ /**
64
+ * Register a handler for a job type
65
+ * Supports wildcards: "email.*" matches "email.welcome", "email.reset", etc.
66
+ */
67
+ handle(pattern: string, handler: JobHandler<unknown>): void {
68
+ this.handlers.set(pattern, handler);
69
+
70
+ // Calculate specificity for wildcard matching (longer = more specific)
71
+ const specificity = pattern.split(".").length;
72
+ const entry: HandlerRegistryEntry = { pattern, handler, specificity };
73
+
74
+ // Insert in order of specificity (highest first)
75
+ const index = this.handlerRegistry.findIndex(
76
+ (e) => e.specificity < specificity,
77
+ );
78
+ if (index >= 0) {
79
+ this.handlerRegistry.splice(index, 0, entry);
80
+ } else {
81
+ this.handlerRegistry.push(entry);
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Remove a handler
87
+ */
88
+ unhandle(pattern: string): void {
89
+ this.handlers.delete(pattern);
90
+ const index = this.handlerRegistry.findIndex((e) => e.pattern === pattern);
91
+ if (index >= 0) {
92
+ this.handlerRegistry.splice(index, 1);
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Listen for worker events
98
+ */
99
+ on(eventType: JobEventType, listener: (event: JobEvent) => void): void {
100
+ if (!this.eventListeners.has(eventType)) {
101
+ this.eventListeners.set(eventType, new Set());
102
+ }
103
+ this.eventListeners.get(eventType)!.add(listener);
104
+ }
105
+
106
+ /**
107
+ * Stop listening for events
108
+ */
109
+ off(eventType: JobEventType, listener: (event: JobEvent) => void): void {
110
+ this.eventListeners.get(eventType)?.delete(listener);
111
+ }
112
+
113
+ /**
114
+ * Emit a worker event
115
+ */
116
+ private _emitEvent(eventType: JobEventType, job: Job): void {
117
+ const listeners = this.eventListeners.get(eventType);
118
+ if (!listeners) return;
119
+
120
+ const event: JobEvent = {
121
+ type: eventType,
122
+ job,
123
+ timestamp: new Date(),
124
+ };
125
+
126
+ for (const listener of listeners) {
127
+ try {
128
+ listener(event);
129
+ } catch (error) {
130
+ console.error(`Error in ${eventType} listener:`, error);
131
+ }
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Find the best matching handler for a job
137
+ */
138
+ private findHandler(jobName: string): JobHandler<unknown> | null {
139
+ for (const entry of this.handlerRegistry) {
140
+ if (this._patternMatches(entry.pattern, jobName)) {
141
+ return entry.handler;
142
+ }
143
+ }
144
+ return null;
145
+ }
146
+
147
+ /**
148
+ * Check if a pattern matches a job name (supports wildcards)
149
+ */
150
+ private _patternMatches(pattern: string, jobName: string): boolean {
151
+ if (pattern === jobName) return true;
152
+
153
+ // Handle wildcards: "email.*" matches "email.welcome"
154
+ if (pattern.endsWith(".*")) {
155
+ const prefix = pattern.slice(0, -2); // Remove ".*"
156
+ return jobName.startsWith(prefix + ".");
157
+ }
158
+
159
+ return false;
160
+ }
161
+
162
+ /**
163
+ * Start the worker (blocks until stopped)
164
+ */
165
+ async start(): Promise<void> {
166
+ this.isRunning = true;
167
+
168
+ console.log("[JobWorker] Starting worker process");
169
+
170
+ // Handle signals for graceful shutdown
171
+ const handleSignal = async () => {
172
+ await this.stop();
173
+ };
174
+
175
+ process.on("SIGTERM", handleSignal);
176
+ process.on("SIGINT", handleSignal);
177
+
178
+ try {
179
+ await this._pollLoop();
180
+ } finally {
181
+ process.removeListener("SIGTERM", handleSignal);
182
+ process.removeListener("SIGINT", handleSignal);
183
+ }
184
+ }
185
+
186
+ /**
187
+ * Main polling loop
188
+ */
189
+ private async _pollLoop(): Promise<void> {
190
+ while (this.isRunning) {
191
+ try {
192
+ const availableSlots = this.concurrency - this.inFlightJobs.size;
193
+
194
+ if (availableSlots > 0) {
195
+ const jobs = await this.driver.claim(availableSlots, this.jobTimeout);
196
+
197
+ if (jobs.length > 0) {
198
+ // Reset backoff on successful claim
199
+ this.currentBackoff = 0;
200
+
201
+ // Process jobs concurrently
202
+ for (const job of jobs) {
203
+ this._processJob(job).catch((error) => {
204
+ console.error(
205
+ `[JobWorker] Unhandled error processing job ${job.id}:`,
206
+ error,
207
+ );
208
+ });
209
+ }
210
+ } else {
211
+ // No jobs, increase backoff
212
+ this.currentBackoff = Math.min(
213
+ (this.currentBackoff || this.pollInterval) * 1.5,
214
+ this.maxBackoffDelay,
215
+ );
216
+ }
217
+ }
218
+
219
+ // Sleep before next poll
220
+ const delay = this.currentBackoff || this.pollInterval;
221
+ await new Promise((resolve) => setTimeout(resolve, delay));
222
+ } catch (error) {
223
+ console.error("[JobWorker] Error in polling loop:", error);
224
+
225
+ // Backoff on error
226
+ this.currentBackoff = Math.min(
227
+ (this.currentBackoff || this.pollInterval) * 1.5,
228
+ this.maxBackoffDelay,
229
+ );
230
+
231
+ await new Promise((resolve) =>
232
+ setTimeout(resolve, this.currentBackoff),
233
+ );
234
+ }
235
+ }
236
+ }
237
+
238
+ /**
239
+ * Process a single job
240
+ */
241
+ private async _processJob(job: Job): Promise<void> {
242
+ this.inFlightJobs.add(job.id);
243
+
244
+ try {
245
+ const handler = this.findHandler(job.name);
246
+
247
+ if (!handler) {
248
+ console.warn(`[JobWorker] No handler found for job type: ${job.name}`);
249
+ await this.driver.complete(job.id);
250
+ return;
251
+ }
252
+
253
+ this._emitEvent("started", job);
254
+
255
+ // Execute handler with timeout
256
+ const timeoutPromise = new Promise((_, reject) =>
257
+ setTimeout(
258
+ () => reject(new Error(`Job timeout after ${this.jobTimeout}ms`)),
259
+ this.jobTimeout,
260
+ ),
261
+ );
262
+
263
+ const handlerPromise = handler(job);
264
+
265
+ await Promise.race([handlerPromise, timeoutPromise]);
266
+
267
+ this._emitEvent("completed", job);
268
+ await this.driver.complete(job.id);
269
+ } catch (error) {
270
+ const errorMsg = error instanceof Error ? error.message : String(error);
271
+ const stackTrace = error instanceof Error ? error.stack : undefined;
272
+
273
+ // Determine if we should retry
274
+ if (job.attempts < job.maxRetries) {
275
+ // Exponential backoff: 1s, 2s, 4s, 8s, etc. (capped at 1 hour)
276
+ const delaySeconds = Math.min(Math.pow(2, job.attempts), 3600);
277
+ const delayMs = delaySeconds * 1000;
278
+
279
+ this._emitEvent("retried", job);
280
+ await this.driver.scheduleRetry(job.id, delayMs, errorMsg);
281
+
282
+ console.warn(
283
+ `[JobWorker] Job ${job.id} failed (attempt ${job.attempts}/${job.maxRetries}): ${errorMsg}. Retrying in ${delaySeconds}s`,
284
+ );
285
+ } else {
286
+ this._emitEvent("failed", job);
287
+ await this.driver.fail(job.id, errorMsg, stackTrace);
288
+
289
+ console.error(
290
+ `[JobWorker] Job ${job.id} failed permanently: ${errorMsg}`,
291
+ );
292
+ }
293
+ } finally {
294
+ this.inFlightJobs.delete(job.id);
295
+ }
296
+ }
297
+
298
+ /**
299
+ * Stop the worker gracefully
300
+ * Waits for in-flight jobs to complete before shutdown
301
+ */
302
+ async stop(): Promise<void> {
303
+ if (!this.isRunning) return;
304
+
305
+ console.log("[JobWorker] Shutting down gracefully...");
306
+ this.isRunning = false;
307
+
308
+ // Wait for in-flight jobs to complete
309
+ const startTime = Date.now();
310
+ while (
311
+ this.inFlightJobs.size > 0 &&
312
+ Date.now() - startTime < this.shutdownTimeout
313
+ ) {
314
+ await new Promise((resolve) => setTimeout(resolve, 100));
315
+ }
316
+
317
+ if (this.inFlightJobs.size > 0) {
318
+ console.warn(
319
+ `[JobWorker] Force shutdown with ${this.inFlightJobs.size} jobs still in flight`,
320
+ );
321
+ } else {
322
+ console.log("[JobWorker] All jobs completed, shutting down");
323
+ }
324
+
325
+ await this.driver.disconnect();
326
+ }
327
+
328
+ /**
329
+ * Get the number of jobs currently being processed
330
+ */
331
+ getInFlightCount(): number {
332
+ return this.inFlightJobs.size;
333
+ }
334
+
335
+ /**
336
+ * Get queue metrics
337
+ */
338
+ async getMetrics() {
339
+ return this.driver.getMetrics();
340
+ }
341
+
342
+ /**
343
+ * Check if worker is running
344
+ */
345
+ isActive(): boolean {
346
+ return this.isRunning;
347
+ }
348
+ }
349
+
350
+ // ============= Worker CLI Entry Point =============
351
+
352
+ /**
353
+ * Create and start a worker from config
354
+ * Useful for CLI commands like: bueno queue:worker
355
+ */
356
+ export async function startWorker(config?: JobQueueConfig): Promise<void> {
357
+ const worker = new JobWorker(config);
358
+
359
+ // Log metrics periodically
360
+ const metricsInterval = setInterval(async () => {
361
+ if (worker.isActive()) {
362
+ const metrics = await worker.getMetrics();
363
+ console.log("[JobWorker] Metrics:", {
364
+ pending: metrics.pending,
365
+ processing: metrics.processing,
366
+ processed: metrics.processed,
367
+ failed: metrics.failed,
368
+ avgLatency: `${Math.round(metrics.avgLatency)}ms`,
369
+ });
370
+ }
371
+ }, 30000);
372
+
373
+ await worker.init();
374
+
375
+ process.on("exit", () => {
376
+ clearInterval(metricsInterval);
377
+ });
378
+
379
+ await worker.start();
380
+ }
@@ -372,9 +372,7 @@ export class PerformanceLogger {
372
372
  /**
373
373
  * Get metric statistics
374
374
  */
375
- stats(
376
- name: string,
377
- ): {
375
+ stats(name: string): {
378
376
  count: number;
379
377
  min: number;
380
378
  max: number;
@@ -12,7 +12,10 @@ import type { LogEntry, LogLevel, LoggerConfig } from "../index";
12
12
  /**
13
13
  * Error callback for transport errors
14
14
  */
15
- export type TransportErrorCallback = (error: Error, transport: LogTransport) => void;
15
+ export type TransportErrorCallback = (
16
+ error: Error,
17
+ transport: LogTransport,
18
+ ) => void;
16
19
 
17
20
  /**
18
21
  * Base interface for log transports
@@ -192,7 +195,7 @@ export class HTTPWebhookTransport implements LogTransport {
192
195
  await this.sleep(delay);
193
196
  delay = Math.min(
194
197
  delay * this.retryOptions.backoffMultiplier,
195
- this.retryOptions.maxDelay
198
+ this.retryOptions.maxDelay,
196
199
  );
197
200
  }
198
201
  }
@@ -259,7 +262,9 @@ export class HTTPWebhookTransport implements LogTransport {
259
262
  try {
260
263
  await this.flush();
261
264
  } catch (error) {
262
- this.handleError(error instanceof Error ? error : new Error(String(error)));
265
+ this.handleError(
266
+ error instanceof Error ? error : new Error(String(error)),
267
+ );
263
268
  }
264
269
  }
265
270
  }
@@ -337,7 +342,8 @@ export class DatadogTransport implements LogTransport {
337
342
  this.env = options.env ?? process.env.NODE_ENV ?? "development";
338
343
  this.hostname = options.hostname ?? this.getDefaultHostname();
339
344
  this.tags = options.tags ?? [];
340
- this.endpoint = options.endpoint ?? "https://http-intake.logs.datadoghq.com/v1/input";
345
+ this.endpoint =
346
+ options.endpoint ?? "https://http-intake.logs.datadoghq.com/v1/input";
341
347
  this.batchSize = options.batchSize ?? 100;
342
348
  this.flushInterval = options.flushInterval ?? 5000;
343
349
  this.timeout = options.timeout ?? 30000;
@@ -395,7 +401,7 @@ export class DatadogTransport implements LogTransport {
395
401
  */
396
402
  private toDatadogFormat(entry: LogEntry): DatadogLogEntry {
397
403
  const allTags = [...this.tags];
398
-
404
+
399
405
  // Add environment tag
400
406
  if (this.env) {
401
407
  allTags.push(`env:${this.env}`);
@@ -404,7 +410,11 @@ export class DatadogTransport implements LogTransport {
404
410
  // Add context as tags
405
411
  if (entry.context) {
406
412
  for (const [key, value] of Object.entries(entry.context)) {
407
- if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
413
+ if (
414
+ typeof value === "string" ||
415
+ typeof value === "number" ||
416
+ typeof value === "boolean"
417
+ ) {
408
418
  allTags.push(`${key}:${value}`);
409
419
  }
410
420
  }
@@ -437,7 +447,16 @@ export class DatadogTransport implements LogTransport {
437
447
 
438
448
  // Add any additional fields from the entry
439
449
  for (const [key, value] of Object.entries(entry)) {
440
- if (!["level", "message", "timestamp", "context", "error", "duration"].includes(key)) {
450
+ if (
451
+ ![
452
+ "level",
453
+ "message",
454
+ "timestamp",
455
+ "context",
456
+ "error",
457
+ "duration",
458
+ ].includes(key)
459
+ ) {
441
460
  datadogEntry[key] = value;
442
461
  }
443
462
  }
@@ -510,7 +529,7 @@ export class DatadogTransport implements LogTransport {
510
529
  await this.sleep(delay);
511
530
  delay = Math.min(
512
531
  delay * this.retryOptions.backoffMultiplier,
513
- this.retryOptions.maxDelay
532
+ this.retryOptions.maxDelay,
514
533
  );
515
534
  }
516
535
  }
@@ -540,7 +559,9 @@ export class DatadogTransport implements LogTransport {
540
559
  });
541
560
 
542
561
  if (!response.ok) {
543
- throw new Error(`Datadog API error: HTTP ${response.status}: ${response.statusText}`);
562
+ throw new Error(
563
+ `Datadog API error: HTTP ${response.status}: ${response.statusText}`,
564
+ );
544
565
  }
545
566
  } finally {
546
567
  clearTimeout(timeoutId);
@@ -579,7 +600,9 @@ export class DatadogTransport implements LogTransport {
579
600
  try {
580
601
  await this.flush();
581
602
  } catch (error) {
582
- this.handleError(error instanceof Error ? error : new Error(String(error)));
603
+ this.handleError(
604
+ error instanceof Error ? error : new Error(String(error)),
605
+ );
583
606
  }
584
607
  }
585
608
  }
@@ -680,7 +703,10 @@ export class ConsoleTransport implements LogTransport {
680
703
  this.output(formatted, entry.level);
681
704
  } catch (error) {
682
705
  if (this.onError) {
683
- this.onError(error instanceof Error ? error : new Error(String(error)), this);
706
+ this.onError(
707
+ error instanceof Error ? error : new Error(String(error)),
708
+ this,
709
+ );
684
710
  }
685
711
  }
686
712
  }
@@ -766,7 +792,10 @@ export class TransportManager {
766
792
  await transport.send(entry);
767
793
  } catch (error) {
768
794
  if (this.onError) {
769
- this.onError(error instanceof Error ? error : new Error(String(error)), transport);
795
+ this.onError(
796
+ error instanceof Error ? error : new Error(String(error)),
797
+ transport,
798
+ );
770
799
  }
771
800
  }
772
801
  });
@@ -783,7 +812,10 @@ export class TransportManager {
783
812
  await transport.sendBatch(entries);
784
813
  } catch (error) {
785
814
  if (this.onError) {
786
- this.onError(error instanceof Error ? error : new Error(String(error)), transport);
815
+ this.onError(
816
+ error instanceof Error ? error : new Error(String(error)),
817
+ transport,
818
+ );
787
819
  }
788
820
  }
789
821
  });
@@ -801,7 +833,10 @@ export class TransportManager {
801
833
  await transport.flush();
802
834
  } catch (error) {
803
835
  if (this.onError) {
804
- this.onError(error instanceof Error ? error : new Error(String(error)), transport);
836
+ this.onError(
837
+ error instanceof Error ? error : new Error(String(error)),
838
+ transport,
839
+ );
805
840
  }
806
841
  }
807
842
  }
@@ -820,7 +855,10 @@ export class TransportManager {
820
855
  await transport.close();
821
856
  } catch (error) {
822
857
  if (this.onError) {
823
- this.onError(error instanceof Error ? error : new Error(String(error)), transport);
858
+ this.onError(
859
+ error instanceof Error ? error : new Error(String(error)),
860
+ transport,
861
+ );
824
862
  }
825
863
  }
826
864
  }
@@ -853,10 +891,12 @@ export class LoggerWithTransports extends Logger {
853
891
 
854
892
  constructor(config: LoggerWithTransportsConfig = {}) {
855
893
  const { transports, onTransportError, ...loggerConfig } = config;
856
-
894
+
857
895
  // Create transport manager
858
- const transportManager = new TransportManager({ onError: onTransportError });
859
-
896
+ const transportManager = new TransportManager({
897
+ onError: onTransportError,
898
+ });
899
+
860
900
  // Add transports
861
901
  if (transports) {
862
902
  for (const transport of transports) {
@@ -931,7 +971,7 @@ export class LoggerWithTransports extends Logger {
931
971
  * Create a logger with transports
932
972
  */
933
973
  export function createLoggerWithTransports(
934
- config: LoggerWithTransportsConfig = {}
974
+ config: LoggerWithTransportsConfig = {},
935
975
  ): LoggerWithTransports {
936
976
  return new LoggerWithTransports(config);
937
977
  }
@@ -941,10 +981,10 @@ export function createLoggerWithTransports(
941
981
  */
942
982
  export function createTransportOutput(
943
983
  transports: LogTransport[],
944
- options?: { onError?: TransportErrorCallback }
984
+ options?: { onError?: TransportErrorCallback },
945
985
  ): (entry: LogEntry) => void {
946
986
  const manager = new TransportManager({ onError: options?.onError });
947
-
987
+
948
988
  for (const transport of transports) {
949
989
  manager.addTransport(transport);
950
990
  }
@@ -966,4 +1006,4 @@ export default {
966
1006
  LoggerWithTransports,
967
1007
  createLoggerWithTransports,
968
1008
  createTransportOutput,
969
- };
1009
+ };
@@ -167,9 +167,7 @@ export async function measureEventLoopLag(): Promise<number> {
167
167
  /**
168
168
  * Measure event loop lag multiple times and return average
169
169
  */
170
- export async function measureEventLoopLagAverage(
171
- samples: number = 5,
172
- ): Promise<number> {
170
+ export async function measureEventLoopLagAverage(samples = 5): Promise<number> {
173
171
  const measurements: number[] = [];
174
172
 
175
173
  for (let i = 0; i < samples; i++) {
@@ -243,7 +241,7 @@ export class MetricsCollector {
243
241
  * Start collecting metrics at regular intervals
244
242
  * @param intervalMs Interval in milliseconds (default: 5000)
245
243
  */
246
- startPeriodicCollection(intervalMs: number = 5000): void {
244
+ startPeriodicCollection(intervalMs = 5000): void {
247
245
  if (this.periodicTimer !== null) {
248
246
  throw new Error("Periodic collection is already running");
249
247
  }
@@ -310,7 +308,7 @@ export class MetricsCollector {
310
308
  let sumCpuUser = 0;
311
309
  let sumCpuSystem = 0;
312
310
  let sumEventLoopLag = 0;
313
- let minHeapUsed = Infinity;
311
+ let minHeapUsed = Number.POSITIVE_INFINITY;
314
312
  let maxHeapUsed = 0;
315
313
 
316
314
  for (const m of this.history) {
@@ -333,7 +331,8 @@ export class MetricsCollector {
333
331
  avgCpuUser: Math.round(sumCpuUser / count),
334
332
  avgCpuSystem: Math.round(sumCpuSystem / count),
335
333
  avgEventLoopLag: Math.round((sumEventLoopLag / count) * 100) / 100,
336
- minMemoryHeapUsed: minHeapUsed === Infinity ? 0 : minHeapUsed,
334
+ minMemoryHeapUsed:
335
+ minHeapUsed === Number.POSITIVE_INFINITY ? 0 : minHeapUsed,
337
336
  maxMemoryHeapUsed: maxHeapUsed,
338
337
  sampleCount: count,
339
338
  timeRange: {
@@ -377,7 +376,9 @@ export function toPrometheusFormat(metrics: RuntimeMetrics): string {
377
376
  `process_memory_heap_used_bytes ${metrics.memoryHeapUsed} ${timestamp}`,
378
377
  );
379
378
 
380
- lines.push("# HELP process_memory_heap_total_bytes Total heap memory in bytes");
379
+ lines.push(
380
+ "# HELP process_memory_heap_total_bytes Total heap memory in bytes",
381
+ );
381
382
  lines.push("# TYPE process_memory_heap_total_bytes gauge");
382
383
  lines.push(
383
384
  `process_memory_heap_total_bytes ${metrics.memoryHeapTotal} ${timestamp}`,
@@ -409,7 +410,9 @@ export function toPrometheusFormat(metrics: RuntimeMetrics): string {
409
410
  // Runtime metrics
410
411
  lines.push("# HELP process_uptime_seconds Process uptime in seconds");
411
412
  lines.push("# TYPE process_uptime_seconds gauge");
412
- lines.push(`process_uptime_seconds ${metrics.uptime.toFixed(2)} ${timestamp}`);
413
+ lines.push(
414
+ `process_uptime_seconds ${metrics.uptime.toFixed(2)} ${timestamp}`,
415
+ );
413
416
 
414
417
  lines.push("# HELP nodejs_eventloop_lag_ms Event loop lag in milliseconds");
415
418
  lines.push("# TYPE nodejs_eventloop_lag_ms gauge");
@@ -423,25 +426,29 @@ export function toPrometheusFormat(metrics: RuntimeMetrics): string {
423
426
  /**
424
427
  * Export averaged metrics in Prometheus format
425
428
  */
426
- export function averagedMetricsToPrometheus(
427
- averaged: AveragedMetrics,
428
- ): string {
429
+ export function averagedMetricsToPrometheus(averaged: AveragedMetrics): string {
429
430
  const timestamp = Date.now();
430
431
  const lines: string[] = [];
431
432
 
432
- lines.push("# HELP process_memory_heap_used_avg_bytes Average heap memory used");
433
+ lines.push(
434
+ "# HELP process_memory_heap_used_avg_bytes Average heap memory used",
435
+ );
433
436
  lines.push("# TYPE process_memory_heap_used_avg_bytes gauge");
434
437
  lines.push(
435
438
  `process_memory_heap_used_avg_bytes ${averaged.avgMemoryHeapUsed} ${timestamp}`,
436
439
  );
437
440
 
438
- lines.push("# HELP process_memory_heap_used_min_bytes Minimum heap memory used");
441
+ lines.push(
442
+ "# HELP process_memory_heap_used_min_bytes Minimum heap memory used",
443
+ );
439
444
  lines.push("# TYPE process_memory_heap_used_min_bytes gauge");
440
445
  lines.push(
441
446
  `process_memory_heap_used_min_bytes ${averaged.minMemoryHeapUsed} ${timestamp}`,
442
447
  );
443
448
 
444
- lines.push("# HELP process_memory_heap_used_max_bytes Maximum heap memory used");
449
+ lines.push(
450
+ "# HELP process_memory_heap_used_max_bytes Maximum heap memory used",
451
+ );
445
452
  lines.push("# TYPE process_memory_heap_used_max_bytes gauge");
446
453
  lines.push(
447
454
  `process_memory_heap_used_max_bytes ${averaged.maxMemoryHeapUsed} ${timestamp}`,
@@ -455,7 +462,9 @@ export function averagedMetricsToPrometheus(
455
462
 
456
463
  lines.push("# HELP process_metrics_sample_count Number of samples collected");
457
464
  lines.push("# TYPE process_metrics_sample_count gauge");
458
- lines.push(`process_metrics_sample_count ${averaged.sampleCount} ${timestamp}`);
465
+ lines.push(
466
+ `process_metrics_sample_count ${averaged.sampleCount} ${timestamp}`,
467
+ );
459
468
 
460
469
  return lines.join("\n");
461
470
  }
@@ -491,4 +500,4 @@ export async function collectMetrics(): Promise<RuntimeMetrics> {
491
500
  eventLoopLag,
492
501
  timestamp: new Date().toISOString(),
493
502
  };
494
- }
503
+ }