@buenojs/bueno 0.8.3 → 0.8.5

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 (218) hide show
  1. package/README.md +136 -16
  2. package/dist/cli/{index.js → bin.js} +3036 -1421
  3. package/dist/container/index.js +250 -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/health/index.js +364 -0
  8. package/dist/i18n/index.js +345 -0
  9. package/dist/index.js +11043 -6482
  10. package/dist/jobs/index.js +819 -0
  11. package/dist/lock/index.js +367 -0
  12. package/dist/logger/index.js +281 -0
  13. package/dist/metrics/index.js +289 -0
  14. package/dist/middleware/index.js +77 -0
  15. package/dist/migrations/index.js +571 -0
  16. package/dist/modules/index.js +3346 -0
  17. package/dist/notification/index.js +484 -0
  18. package/dist/observability/index.js +331 -0
  19. package/dist/openapi/index.js +776 -0
  20. package/dist/orm/index.js +1356 -0
  21. package/dist/router/index.js +886 -0
  22. package/dist/rpc/index.js +691 -0
  23. package/dist/schema/index.js +400 -0
  24. package/dist/telemetry/index.js +595 -0
  25. package/dist/template/index.js +640 -0
  26. package/dist/templates/index.js +640 -0
  27. package/dist/testing/index.js +1111 -0
  28. package/dist/types/index.js +60 -0
  29. package/package.json +121 -27
  30. package/src/cache/index.ts +2 -1
  31. package/src/cli/bin.ts +2 -2
  32. package/src/cli/commands/build.ts +183 -165
  33. package/src/cli/commands/dev.ts +96 -89
  34. package/src/cli/commands/generate.ts +142 -111
  35. package/src/cli/commands/help.ts +20 -16
  36. package/src/cli/commands/index.ts +3 -6
  37. package/src/cli/commands/migration.ts +124 -105
  38. package/src/cli/commands/new.ts +392 -438
  39. package/src/cli/commands/start.ts +81 -79
  40. package/src/cli/core/args.ts +68 -50
  41. package/src/cli/core/console.ts +89 -95
  42. package/src/cli/core/index.ts +4 -4
  43. package/src/cli/core/prompt.ts +65 -62
  44. package/src/cli/core/spinner.ts +23 -20
  45. package/src/cli/index.ts +46 -38
  46. package/src/cli/templates/database/index.ts +61 -0
  47. package/src/cli/templates/database/mysql.ts +14 -0
  48. package/src/cli/templates/database/none.ts +16 -0
  49. package/src/cli/templates/database/postgresql.ts +14 -0
  50. package/src/cli/templates/database/sqlite.ts +14 -0
  51. package/src/cli/templates/deploy.ts +29 -26
  52. package/src/cli/templates/docker.ts +41 -30
  53. package/src/cli/templates/frontend/index.ts +63 -0
  54. package/src/cli/templates/frontend/none.ts +17 -0
  55. package/src/cli/templates/frontend/react.ts +140 -0
  56. package/src/cli/templates/frontend/solid.ts +134 -0
  57. package/src/cli/templates/frontend/svelte.ts +131 -0
  58. package/src/cli/templates/frontend/vue.ts +130 -0
  59. package/src/cli/templates/generators/index.ts +339 -0
  60. package/src/cli/templates/generators/types.ts +56 -0
  61. package/src/cli/templates/index.ts +35 -2
  62. package/src/cli/templates/project/api.ts +81 -0
  63. package/src/cli/templates/project/default.ts +140 -0
  64. package/src/cli/templates/project/fullstack.ts +111 -0
  65. package/src/cli/templates/project/index.ts +95 -0
  66. package/src/cli/templates/project/minimal.ts +45 -0
  67. package/src/cli/templates/project/types.ts +94 -0
  68. package/src/cli/templates/project/website.ts +263 -0
  69. package/src/cli/utils/fs.ts +55 -41
  70. package/src/cli/utils/index.ts +3 -2
  71. package/src/cli/utils/strings.ts +47 -33
  72. package/src/cli/utils/version.ts +47 -0
  73. package/src/config/env-validation.ts +100 -0
  74. package/src/config/env.ts +169 -41
  75. package/src/config/index.ts +28 -20
  76. package/src/config/loader.ts +25 -16
  77. package/src/config/merge.ts +21 -10
  78. package/src/config/types.ts +545 -25
  79. package/src/config/validation.ts +215 -7
  80. package/src/container/forward-ref.ts +22 -22
  81. package/src/container/index.ts +34 -12
  82. package/src/context/index.ts +11 -1
  83. package/src/database/index.ts +7 -190
  84. package/src/database/orm/builder.ts +457 -0
  85. package/src/database/orm/casts/index.ts +130 -0
  86. package/src/database/orm/casts/types.ts +25 -0
  87. package/src/database/orm/compiler.ts +304 -0
  88. package/src/database/orm/hooks/index.ts +114 -0
  89. package/src/database/orm/index.ts +61 -0
  90. package/src/database/orm/model-registry.ts +59 -0
  91. package/src/database/orm/model.ts +821 -0
  92. package/src/database/orm/relationships/base.ts +146 -0
  93. package/src/database/orm/relationships/belongs-to-many.ts +179 -0
  94. package/src/database/orm/relationships/belongs-to.ts +56 -0
  95. package/src/database/orm/relationships/has-many.ts +45 -0
  96. package/src/database/orm/relationships/has-one.ts +41 -0
  97. package/src/database/orm/relationships/index.ts +11 -0
  98. package/src/database/orm/scopes/index.ts +55 -0
  99. package/src/events/__tests__/event-system.test.ts +235 -0
  100. package/src/events/config.ts +238 -0
  101. package/src/events/example-usage.ts +185 -0
  102. package/src/events/index.ts +278 -0
  103. package/src/events/manager.ts +385 -0
  104. package/src/events/registry.ts +182 -0
  105. package/src/events/types.ts +124 -0
  106. package/src/frontend/api-routes.ts +65 -23
  107. package/src/frontend/bundler.ts +76 -34
  108. package/src/frontend/console-client.ts +2 -2
  109. package/src/frontend/console-stream.ts +94 -38
  110. package/src/frontend/dev-server.ts +94 -46
  111. package/src/frontend/file-router.ts +61 -19
  112. package/src/frontend/frameworks/index.ts +37 -10
  113. package/src/frontend/frameworks/react.ts +10 -8
  114. package/src/frontend/frameworks/solid.ts +11 -9
  115. package/src/frontend/frameworks/svelte.ts +15 -9
  116. package/src/frontend/frameworks/vue.ts +13 -11
  117. package/src/frontend/hmr-client.ts +12 -10
  118. package/src/frontend/hmr.ts +146 -103
  119. package/src/frontend/index.ts +14 -5
  120. package/src/frontend/islands.ts +41 -22
  121. package/src/frontend/isr.ts +59 -37
  122. package/src/frontend/layout.ts +36 -21
  123. package/src/frontend/ssr/react.ts +74 -27
  124. package/src/frontend/ssr/solid.ts +54 -20
  125. package/src/frontend/ssr/svelte.ts +48 -14
  126. package/src/frontend/ssr/vue.ts +50 -18
  127. package/src/frontend/ssr.ts +83 -39
  128. package/src/frontend/types.ts +91 -56
  129. package/src/health/index.ts +21 -9
  130. package/src/i18n/engine.ts +305 -0
  131. package/src/i18n/index.ts +38 -0
  132. package/src/i18n/loader.ts +218 -0
  133. package/src/i18n/middleware.ts +164 -0
  134. package/src/i18n/negotiator.ts +162 -0
  135. package/src/i18n/types.ts +158 -0
  136. package/src/index.ts +179 -27
  137. package/src/jobs/drivers/memory.ts +315 -0
  138. package/src/jobs/drivers/redis.ts +459 -0
  139. package/src/jobs/index.ts +30 -0
  140. package/src/jobs/queue.ts +281 -0
  141. package/src/jobs/types.ts +295 -0
  142. package/src/jobs/worker.ts +380 -0
  143. package/src/logger/index.ts +1 -3
  144. package/src/logger/transports/index.ts +62 -22
  145. package/src/metrics/index.ts +25 -16
  146. package/src/migrations/index.ts +9 -0
  147. package/src/modules/filters.ts +13 -17
  148. package/src/modules/guards.ts +49 -26
  149. package/src/modules/index.ts +409 -298
  150. package/src/modules/interceptors.ts +58 -20
  151. package/src/modules/lazy.ts +11 -19
  152. package/src/modules/lifecycle.ts +15 -7
  153. package/src/modules/metadata.ts +15 -5
  154. package/src/modules/pipes.ts +94 -72
  155. package/src/notification/channels/base.ts +68 -0
  156. package/src/notification/channels/email.ts +105 -0
  157. package/src/notification/channels/push.ts +104 -0
  158. package/src/notification/channels/sms.ts +105 -0
  159. package/src/notification/channels/whatsapp.ts +104 -0
  160. package/src/notification/index.ts +48 -0
  161. package/src/notification/service.ts +354 -0
  162. package/src/notification/types.ts +344 -0
  163. package/src/observability/__tests__/observability.test.ts +483 -0
  164. package/src/observability/breadcrumbs.ts +114 -0
  165. package/src/observability/index.ts +136 -0
  166. package/src/observability/interceptor.ts +85 -0
  167. package/src/observability/service.ts +303 -0
  168. package/src/observability/trace.ts +37 -0
  169. package/src/observability/types.ts +196 -0
  170. package/src/openapi/__tests__/decorators.test.ts +335 -0
  171. package/src/openapi/__tests__/document-builder.test.ts +285 -0
  172. package/src/openapi/__tests__/route-scanner.test.ts +334 -0
  173. package/src/openapi/__tests__/schema-generator.test.ts +275 -0
  174. package/src/openapi/decorators.ts +328 -0
  175. package/src/openapi/document-builder.ts +274 -0
  176. package/src/openapi/index.ts +112 -0
  177. package/src/openapi/metadata.ts +112 -0
  178. package/src/openapi/route-scanner.ts +289 -0
  179. package/src/openapi/schema-generator.ts +256 -0
  180. package/src/openapi/swagger-module.ts +166 -0
  181. package/src/openapi/types.ts +398 -0
  182. package/src/orm/index.ts +10 -0
  183. package/src/rpc/index.ts +3 -1
  184. package/src/schema/index.ts +9 -0
  185. package/src/security/index.ts +15 -6
  186. package/src/ssg/index.ts +9 -8
  187. package/src/telemetry/index.ts +76 -22
  188. package/src/template/index.ts +7 -0
  189. package/src/templates/engine.ts +224 -0
  190. package/src/templates/index.ts +9 -0
  191. package/src/templates/loader.ts +331 -0
  192. package/src/templates/renderers/markdown.ts +212 -0
  193. package/src/templates/renderers/simple.ts +269 -0
  194. package/src/templates/types.ts +154 -0
  195. package/src/testing/index.ts +100 -27
  196. package/src/types/optional-deps.d.ts +347 -187
  197. package/src/validation/index.ts +92 -2
  198. package/src/validation/schemas.ts +536 -0
  199. package/tests/integration/fullstack.test.ts +4 -4
  200. package/tests/unit/database.test.ts +2 -72
  201. package/tests/unit/env-validation.test.ts +166 -0
  202. package/tests/unit/events.test.ts +910 -0
  203. package/tests/unit/i18n.test.ts +455 -0
  204. package/tests/unit/jobs.test.ts +493 -0
  205. package/tests/unit/notification.test.ts +988 -0
  206. package/tests/unit/observability.test.ts +453 -0
  207. package/tests/unit/orm/builder.test.ts +323 -0
  208. package/tests/unit/orm/casts.test.ts +179 -0
  209. package/tests/unit/orm/compiler.test.ts +220 -0
  210. package/tests/unit/orm/eager-loading.test.ts +285 -0
  211. package/tests/unit/orm/hooks.test.ts +191 -0
  212. package/tests/unit/orm/model.test.ts +373 -0
  213. package/tests/unit/orm/relationships.test.ts +303 -0
  214. package/tests/unit/orm/scopes.test.ts +74 -0
  215. package/tests/unit/templates-simple.test.ts +53 -0
  216. package/tests/unit/templates.test.ts +454 -0
  217. package/tests/unit/validation.test.ts +18 -24
  218. package/tsconfig.json +11 -3
package/src/index.ts CHANGED
@@ -53,42 +53,16 @@ export {
53
53
  createApp,
54
54
  } from "./modules";
55
55
 
56
- // Database
56
+ // Database (connection layer)
57
57
  export {
58
58
  Database,
59
59
  createConnection,
60
60
  detectDriver,
61
- QueryBuilder,
62
- table,
63
61
  ReservedConnection,
64
62
  type DatabaseConfig as DatabaseConfigType,
65
63
  type DatabaseDriver,
66
64
  type QueryResult,
67
65
  type Transaction,
68
- // Schema
69
- SchemaBuilder,
70
- createSchema,
71
- defineTable,
72
- generateCreateTable,
73
- generateDropTable,
74
- generateCreateIndex,
75
- type ColumnType,
76
- type ColumnOptions,
77
- type TableSchema,
78
- type IndexDefinition,
79
- type ConstraintDefinition,
80
- type TypeScriptType,
81
- type InferType,
82
- type InferInsertType,
83
- // Migrations
84
- MigrationRunner,
85
- MigrationBuilder,
86
- createMigration,
87
- createMigrationRunner,
88
- generateMigrationId,
89
- type Migration,
90
- type MigrationRecord,
91
- type MigrationOptions,
92
66
  } from "./database";
93
67
 
94
68
  // Validation
@@ -156,6 +130,159 @@ export {
156
130
  type LockHandle,
157
131
  } from "./lock";
158
132
 
133
+ // Background Jobs
134
+ export {
135
+ JobQueue,
136
+ JobWorker,
137
+ createJobQueue,
138
+ startWorker,
139
+ MemoryJobQueueDriver,
140
+ RedisJobQueueDriver,
141
+ type Job,
142
+ type JobStatus,
143
+ type JobQueueConfig,
144
+ type QueueOptions,
145
+ type JobHandler,
146
+ type QueueMetrics,
147
+ type JobEvent,
148
+ type JobEventType,
149
+ type JobClaimHandle,
150
+ type JobQueueDriver,
151
+ type HandlerRegistryEntry,
152
+ type JobConfigValidationResult,
153
+ } from "./jobs";
154
+
155
+ // Templates
156
+ export {
157
+ TemplateEngine,
158
+ TemplateLoader,
159
+ SimpleRenderer,
160
+ MarkdownRenderer,
161
+ type Template,
162
+ type TemplateMetadata,
163
+ type TemplateData,
164
+ type RenderOptions,
165
+ type TemplateEngineConfig,
166
+ type TemplateEngineMetrics,
167
+ type IRenderer,
168
+ type FilterRegistry,
169
+ type TemplateLoaderOptions,
170
+ type TemplateLoadedEvent,
171
+ } from "./templates";
172
+
173
+ // Notification
174
+ export {
175
+ NotificationService,
176
+ createNotificationService,
177
+ BaseChannelService,
178
+ EmailChannelService,
179
+ SMSChannelService,
180
+ WhatsAppChannelService,
181
+ PushNotificationChannelService,
182
+ type NotificationChannel,
183
+ type NotificationMessage,
184
+ type EmailRecipient,
185
+ type EmailRecipients,
186
+ type EmailAttachment,
187
+ type EmailMessage,
188
+ type SMSMessage,
189
+ type WhatsAppMessage,
190
+ type PushNotificationMessage,
191
+ type BuiltInNotification,
192
+ type Notifiable,
193
+ type ChannelHealth,
194
+ type ChannelService,
195
+ type BaseChannelConfig,
196
+ type EmailChannelConfig,
197
+ type SMSChannelConfig,
198
+ type WhatsAppChannelConfig,
199
+ type PushChannelConfig,
200
+ type ChannelConfig,
201
+ type NotificationServiceConfig,
202
+ type ChannelMetrics,
203
+ type NotificationEvent,
204
+ type NotificationEventType,
205
+ type NotificationValidationResult,
206
+ } from "./notification";
207
+
208
+ // OpenAPI
209
+ export {
210
+ DocumentBuilder,
211
+ SchemaGenerator,
212
+ RouteScanner,
213
+ SwaggerModule,
214
+ ApiTags,
215
+ ApiBearerAuth,
216
+ ApiBasicAuth,
217
+ ApiApiKey,
218
+ ApiExcludeController,
219
+ ApiOperation,
220
+ ApiResponse,
221
+ ApiParam,
222
+ ApiQuery,
223
+ ApiHeader,
224
+ ApiBody,
225
+ ApiExcludeEndpoint,
226
+ ApiProperty,
227
+ ApiPropertyOptional,
228
+ type OpenAPIDocument,
229
+ type OpenAPIInfo,
230
+ type OpenAPIContact,
231
+ type OpenAPILicense,
232
+ type OpenAPIServer,
233
+ type OpenAPIPaths,
234
+ type OpenAPIPath,
235
+ type OpenAPIOperation,
236
+ type OpenAPIExternalDocs,
237
+ type OpenAPIParameter,
238
+ type OpenAPIRequestBody,
239
+ type OpenAPIMediaType,
240
+ type OpenAPIExample,
241
+ type OpenAPIResponse,
242
+ type OpenAPIHeader,
243
+ type OpenAPIResponses,
244
+ type OpenAPISchema,
245
+ type OpenAPIDiscriminator,
246
+ type OpenAPIXML,
247
+ type OpenAPIComponents,
248
+ type OpenAPISecurityScheme,
249
+ type OpenAPIOAuthFlows,
250
+ type OpenAPIOAuthFlow,
251
+ type OpenAPISecurity,
252
+ type OpenAPITag,
253
+ type SwaggerOptions,
254
+ type ApiOperationOptions,
255
+ type ApiResponseOptions,
256
+ type ApiParamOptions,
257
+ type ApiQueryOptions,
258
+ type ApiHeaderOptions,
259
+ type ApiBodyOptions,
260
+ type ApiPropertyOptions,
261
+ type SecuritySchemeOptions,
262
+ type ApiKeySecurityOptions,
263
+ } from "./openapi";
264
+
265
+ // i18n
266
+ export {
267
+ I18n,
268
+ createI18n,
269
+ TranslationLoader,
270
+ LocaleNegotiator,
271
+ parseAcceptLanguage,
272
+ normaliseLocale,
273
+ i18nMiddleware,
274
+ getLocale,
275
+ getT,
276
+ type PluralKey,
277
+ type TranslationParams,
278
+ type TranslationFunction,
279
+ type LocaleMatch,
280
+ type I18nContext,
281
+ type LocaleBundle,
282
+ type I18nMetrics,
283
+ type I18nMiddlewareOptions,
284
+ } from "./i18n";
285
+
159
286
  // SSG
160
287
  export {
161
288
  SSG,
@@ -269,6 +396,28 @@ export {
269
396
  type CacheLike,
270
397
  } from "./health";
271
398
 
399
+ // Observability
400
+ export {
401
+ ObservabilityModule,
402
+ ObservabilityService,
403
+ ObservabilityInterceptor,
404
+ BreadcrumbCollector,
405
+ httpBreadcrumb,
406
+ logBreadcrumb,
407
+ extractTraceContext,
408
+ generateTraceId,
409
+ generateSpanId,
410
+ buildTraceparent,
411
+ type BreadcrumbEntry,
412
+ type ErrorEvent,
413
+ type ErrorRequestContext,
414
+ type ErrorUserContext,
415
+ type MessageEvent,
416
+ type ErrorReporter,
417
+ type ObservabilityOptions,
418
+ type ObservabilityConfig,
419
+ } from "./observability";
420
+
272
421
  // Types
273
422
  export type {
274
423
  HTTPMethod,
@@ -355,6 +504,9 @@ export type {
355
504
  MetricsConfig,
356
505
  TelemetryConfig,
357
506
  FrontendConfig,
507
+ JobsConfig,
508
+ NotificationConfig,
509
+ I18nConfig,
358
510
  DeepPartial,
359
511
  UserConfig,
360
512
  UserConfigFn,
@@ -0,0 +1,315 @@
1
+ /**
2
+ * In-Memory Job Queue Driver
3
+ *
4
+ * Used for development and testing. No external dependencies.
5
+ * Jobs are stored in memory and lost on process restart.
6
+ */
7
+
8
+ import type {
9
+ Job,
10
+ JobQueueConfig,
11
+ JobQueueDriver,
12
+ QueueMetrics,
13
+ QueueOptions,
14
+ } from "../types";
15
+
16
+ // ============= In-Memory Store =============
17
+
18
+ interface StoredJob {
19
+ job: Job;
20
+ expiresAt: number; // Timestamp when job expires (for cleanup)
21
+ }
22
+
23
+ // ============= Memory Job Queue Driver =============
24
+
25
+ export class MemoryJobQueueDriver implements JobQueueDriver {
26
+ private jobStore = new Map<string, StoredJob>();
27
+ private pendingQueue: string[] = []; // Job IDs in FIFO order
28
+ private processingSet = new Set<string>(); // Currently claimed jobs
29
+ private failedQueue: string[] = [];
30
+ private cleanupInterval: ReturnType<typeof setInterval> | null = null;
31
+ private jobCounter = 0; // For generating sequential IDs
32
+ private metrics: QueueMetrics = {
33
+ enqueued: 0,
34
+ processed: 0,
35
+ failed: 0,
36
+ pending: 0,
37
+ processing: 0,
38
+ avgLatency: 0,
39
+ totalLatency: 0,
40
+ retried: 0,
41
+ successRate: 0,
42
+ avgAttempts: 0,
43
+ };
44
+
45
+ constructor(private config: JobQueueConfig = {}) {
46
+ this._startCleanup();
47
+ }
48
+
49
+ /**
50
+ * Generate a unique job ID
51
+ */
52
+ private generateJobId(): string {
53
+ this.jobCounter++;
54
+ return `job:memory:${Date.now()}:${this.jobCounter}`;
55
+ }
56
+
57
+ /**
58
+ * Start periodic cleanup of expired jobs
59
+ */
60
+ private _startCleanup(): void {
61
+ this.cleanupInterval = setInterval(() => {
62
+ const now = Date.now();
63
+ for (const [jobId, stored] of this.jobStore.entries()) {
64
+ if (now > stored.expiresAt) {
65
+ this.jobStore.delete(jobId);
66
+ }
67
+ }
68
+ }, 30000); // Run every 30 seconds
69
+ }
70
+
71
+ async connect(): Promise<void> {
72
+ // No-op for in-memory driver
73
+ }
74
+
75
+ async disconnect(): Promise<void> {
76
+ if (this.cleanupInterval) {
77
+ clearInterval(this.cleanupInterval);
78
+ this.cleanupInterval = null;
79
+ }
80
+ this.jobStore.clear();
81
+ this.pendingQueue = [];
82
+ this.processingSet.clear();
83
+ this.failedQueue = [];
84
+ }
85
+
86
+ async isConnected(): Promise<boolean> {
87
+ return true; // Always connected for in-memory
88
+ }
89
+
90
+ async enqueue<T = unknown>(
91
+ name: string,
92
+ data: T,
93
+ options?: QueueOptions,
94
+ ): Promise<string> {
95
+ const jobId = this.generateJobId();
96
+ const now = new Date();
97
+
98
+ const job: Job<T> = {
99
+ id: jobId,
100
+ name,
101
+ data,
102
+ status: "pending",
103
+ attempts: 0,
104
+ maxRetries: options?.maxRetries ?? this.config.maxRetries ?? 3,
105
+ createdAt: now.toISOString(),
106
+ updatedAt: now.toISOString(),
107
+ priority: options?.priority ?? 0,
108
+ metadata: options?.metadata,
109
+ };
110
+
111
+ // Handle delayed jobs
112
+ if (options?.delay) {
113
+ const delayMs =
114
+ options.delay instanceof Date
115
+ ? options.delay.getTime() - Date.now()
116
+ : options.delay;
117
+
118
+ if (delayMs > 0) {
119
+ job.status = "delayed";
120
+ job.scheduledFor = new Date(Date.now() + delayMs).toISOString();
121
+ }
122
+ }
123
+
124
+ // Store job with expiry (7 days by default)
125
+ const expiryMs = 7 * 24 * 60 * 60 * 1000;
126
+ this.jobStore.set(jobId, {
127
+ job,
128
+ expiresAt: Date.now() + expiryMs,
129
+ });
130
+
131
+ // Add to pending queue if not delayed
132
+ if (job.status === "pending") {
133
+ this.pendingQueue.push(jobId);
134
+ }
135
+
136
+ this.metrics.enqueued++;
137
+ this.metrics.pending = this.pendingQueue.length;
138
+
139
+ return jobId;
140
+ }
141
+
142
+ async claim(count: number, timeout: number): Promise<Job[]> {
143
+ const now = Date.now();
144
+ const claimed: Job[] = [];
145
+
146
+ // Move delayed jobs to pending if their time has come
147
+ const toRemove: number[] = [];
148
+ this.jobStore.forEach((stored, jobId) => {
149
+ if (
150
+ stored.job.status === "delayed" &&
151
+ stored.job.scheduledFor &&
152
+ new Date(stored.job.scheduledFor).getTime() <= now
153
+ ) {
154
+ stored.job.status = "pending";
155
+ if (!this.pendingQueue.includes(jobId)) {
156
+ this.pendingQueue.push(jobId);
157
+ }
158
+ }
159
+ });
160
+
161
+ // Claim up to 'count' pending jobs
162
+ while (claimed.length < count && this.pendingQueue.length > 0) {
163
+ const jobId = this.pendingQueue.shift();
164
+ if (!jobId) break;
165
+
166
+ const stored = this.jobStore.get(jobId);
167
+ if (!stored) continue;
168
+
169
+ const job = stored.job;
170
+ job.status = "processing";
171
+ job.attempts++;
172
+ job.startedAt = new Date().toISOString();
173
+ job.claimExpiresAt = new Date(now + timeout).toISOString();
174
+ job.updatedAt = new Date().toISOString();
175
+
176
+ this.processingSet.add(jobId);
177
+ claimed.push(job);
178
+ }
179
+
180
+ this.metrics.processing = this.processingSet.size;
181
+ this.metrics.pending = this.pendingQueue.length;
182
+
183
+ return claimed;
184
+ }
185
+
186
+ async complete(jobId: string): Promise<void> {
187
+ const stored = this.jobStore.get(jobId);
188
+ if (!stored) return;
189
+
190
+ const job = stored.job;
191
+ const now = new Date();
192
+
193
+ job.status = "completed";
194
+ job.completedAt = now.toISOString();
195
+ job.updatedAt = now.toISOString();
196
+
197
+ // Calculate duration
198
+ if (job.startedAt) {
199
+ job.duration =
200
+ new Date(job.completedAt).getTime() - new Date(job.startedAt).getTime();
201
+ this.metrics.totalLatency += job.duration;
202
+ }
203
+
204
+ this.processingSet.delete(jobId);
205
+ this.metrics.processed++;
206
+ this.metrics.processing = this.processingSet.size;
207
+
208
+ // Update average latency
209
+ if (this.metrics.processed > 0) {
210
+ this.metrics.avgLatency =
211
+ this.metrics.totalLatency / this.metrics.processed;
212
+ }
213
+
214
+ // Update success rate
215
+ const total =
216
+ this.metrics.processed + this.metrics.failed + this.metrics.pending;
217
+ if (total > 0) {
218
+ this.metrics.successRate = this.metrics.processed / total;
219
+ }
220
+
221
+ // Calculate average attempts
222
+ const allJobs = Array.from(this.jobStore.values())
223
+ .map((s) => s.job)
224
+ .filter((j) => j.status === "completed" || j.status === "failed");
225
+ if (allJobs.length > 0) {
226
+ const totalAttempts = allJobs.reduce(
227
+ (sum, j) => sum + (j.attempts || 1),
228
+ 0,
229
+ );
230
+ this.metrics.avgAttempts = totalAttempts / allJobs.length;
231
+ }
232
+ }
233
+
234
+ async fail(jobId: string, error: string, stackTrace?: string): Promise<void> {
235
+ const stored = this.jobStore.get(jobId);
236
+ if (!stored) return;
237
+
238
+ const job = stored.job;
239
+ const now = new Date();
240
+
241
+ job.error = error;
242
+ job.stackTrace = stackTrace;
243
+ job.status = "failed";
244
+ job.updatedAt = now.toISOString();
245
+
246
+ this.processingSet.delete(jobId);
247
+ this.failedQueue.push(jobId);
248
+
249
+ this.metrics.failed++;
250
+ this.metrics.processing = this.processingSet.size;
251
+
252
+ // Update success rate
253
+ const total =
254
+ this.metrics.processed + this.metrics.failed + this.metrics.pending;
255
+ if (total > 0) {
256
+ this.metrics.successRate = this.metrics.processed / total;
257
+ }
258
+ }
259
+
260
+ async scheduleRetry(
261
+ jobId: string,
262
+ delayMs: number,
263
+ error: string,
264
+ ): Promise<void> {
265
+ const stored = this.jobStore.get(jobId);
266
+ if (!stored) return;
267
+
268
+ const job = stored.job;
269
+ const now = new Date();
270
+
271
+ job.error = error;
272
+ job.status = "delayed";
273
+ job.scheduledFor = new Date(now.getTime() + delayMs).toISOString();
274
+ job.updatedAt = now.toISOString();
275
+
276
+ // Remove from processing and failed queues
277
+ this.processingSet.delete(jobId);
278
+ const failedIndex = this.failedQueue.indexOf(jobId);
279
+ if (failedIndex >= 0) {
280
+ this.failedQueue.splice(failedIndex, 1);
281
+ }
282
+
283
+ // Don't add back to pending yet - it will be added when delay expires
284
+ this.metrics.retried++;
285
+ this.metrics.processing = this.processingSet.size;
286
+ }
287
+
288
+ async getJob(jobId: string): Promise<Job | null> {
289
+ const stored = this.jobStore.get(jobId);
290
+ return stored ? stored.job : null;
291
+ }
292
+
293
+ async getMetrics(): Promise<QueueMetrics> {
294
+ return { ...this.metrics };
295
+ }
296
+
297
+ async clear(): Promise<void> {
298
+ this.jobStore.clear();
299
+ this.pendingQueue = [];
300
+ this.processingSet.clear();
301
+ this.failedQueue = [];
302
+ this.metrics = {
303
+ enqueued: 0,
304
+ processed: 0,
305
+ failed: 0,
306
+ pending: 0,
307
+ processing: 0,
308
+ avgLatency: 0,
309
+ totalLatency: 0,
310
+ retried: 0,
311
+ successRate: 0,
312
+ avgAttempts: 0,
313
+ };
314
+ }
315
+ }