@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.
- package/README.md +136 -16
- package/dist/cli/{index.js → bin.js} +3036 -1421
- package/dist/container/index.js +250 -0
- package/dist/context/index.js +219 -0
- package/dist/database/index.js +493 -0
- package/dist/frontend/index.js +7697 -0
- package/dist/health/index.js +364 -0
- package/dist/i18n/index.js +345 -0
- package/dist/index.js +11043 -6482
- package/dist/jobs/index.js +819 -0
- package/dist/lock/index.js +367 -0
- package/dist/logger/index.js +281 -0
- package/dist/metrics/index.js +289 -0
- package/dist/middleware/index.js +77 -0
- package/dist/migrations/index.js +571 -0
- package/dist/modules/index.js +3346 -0
- package/dist/notification/index.js +484 -0
- package/dist/observability/index.js +331 -0
- package/dist/openapi/index.js +776 -0
- package/dist/orm/index.js +1356 -0
- package/dist/router/index.js +886 -0
- package/dist/rpc/index.js +691 -0
- package/dist/schema/index.js +400 -0
- package/dist/telemetry/index.js +595 -0
- package/dist/template/index.js +640 -0
- package/dist/templates/index.js +640 -0
- package/dist/testing/index.js +1111 -0
- package/dist/types/index.js +60 -0
- package/package.json +121 -27
- package/src/cache/index.ts +2 -1
- package/src/cli/bin.ts +2 -2
- package/src/cli/commands/build.ts +183 -165
- package/src/cli/commands/dev.ts +96 -89
- package/src/cli/commands/generate.ts +142 -111
- package/src/cli/commands/help.ts +20 -16
- package/src/cli/commands/index.ts +3 -6
- package/src/cli/commands/migration.ts +124 -105
- package/src/cli/commands/new.ts +392 -438
- package/src/cli/commands/start.ts +81 -79
- package/src/cli/core/args.ts +68 -50
- package/src/cli/core/console.ts +89 -95
- package/src/cli/core/index.ts +4 -4
- package/src/cli/core/prompt.ts +65 -62
- package/src/cli/core/spinner.ts +23 -20
- package/src/cli/index.ts +46 -38
- package/src/cli/templates/database/index.ts +61 -0
- package/src/cli/templates/database/mysql.ts +14 -0
- package/src/cli/templates/database/none.ts +16 -0
- package/src/cli/templates/database/postgresql.ts +14 -0
- package/src/cli/templates/database/sqlite.ts +14 -0
- package/src/cli/templates/deploy.ts +29 -26
- package/src/cli/templates/docker.ts +41 -30
- package/src/cli/templates/frontend/index.ts +63 -0
- package/src/cli/templates/frontend/none.ts +17 -0
- package/src/cli/templates/frontend/react.ts +140 -0
- package/src/cli/templates/frontend/solid.ts +134 -0
- package/src/cli/templates/frontend/svelte.ts +131 -0
- package/src/cli/templates/frontend/vue.ts +130 -0
- package/src/cli/templates/generators/index.ts +339 -0
- package/src/cli/templates/generators/types.ts +56 -0
- package/src/cli/templates/index.ts +35 -2
- package/src/cli/templates/project/api.ts +81 -0
- package/src/cli/templates/project/default.ts +140 -0
- package/src/cli/templates/project/fullstack.ts +111 -0
- package/src/cli/templates/project/index.ts +95 -0
- package/src/cli/templates/project/minimal.ts +45 -0
- package/src/cli/templates/project/types.ts +94 -0
- package/src/cli/templates/project/website.ts +263 -0
- package/src/cli/utils/fs.ts +55 -41
- package/src/cli/utils/index.ts +3 -2
- package/src/cli/utils/strings.ts +47 -33
- package/src/cli/utils/version.ts +47 -0
- package/src/config/env-validation.ts +100 -0
- package/src/config/env.ts +169 -41
- package/src/config/index.ts +28 -20
- package/src/config/loader.ts +25 -16
- package/src/config/merge.ts +21 -10
- package/src/config/types.ts +545 -25
- package/src/config/validation.ts +215 -7
- package/src/container/forward-ref.ts +22 -22
- package/src/container/index.ts +34 -12
- package/src/context/index.ts +11 -1
- package/src/database/index.ts +7 -190
- package/src/database/orm/builder.ts +457 -0
- package/src/database/orm/casts/index.ts +130 -0
- package/src/database/orm/casts/types.ts +25 -0
- package/src/database/orm/compiler.ts +304 -0
- package/src/database/orm/hooks/index.ts +114 -0
- package/src/database/orm/index.ts +61 -0
- package/src/database/orm/model-registry.ts +59 -0
- package/src/database/orm/model.ts +821 -0
- package/src/database/orm/relationships/base.ts +146 -0
- package/src/database/orm/relationships/belongs-to-many.ts +179 -0
- package/src/database/orm/relationships/belongs-to.ts +56 -0
- package/src/database/orm/relationships/has-many.ts +45 -0
- package/src/database/orm/relationships/has-one.ts +41 -0
- package/src/database/orm/relationships/index.ts +11 -0
- package/src/database/orm/scopes/index.ts +55 -0
- package/src/events/__tests__/event-system.test.ts +235 -0
- package/src/events/config.ts +238 -0
- package/src/events/example-usage.ts +185 -0
- package/src/events/index.ts +278 -0
- package/src/events/manager.ts +385 -0
- package/src/events/registry.ts +182 -0
- package/src/events/types.ts +124 -0
- package/src/frontend/api-routes.ts +65 -23
- package/src/frontend/bundler.ts +76 -34
- package/src/frontend/console-client.ts +2 -2
- package/src/frontend/console-stream.ts +94 -38
- package/src/frontend/dev-server.ts +94 -46
- package/src/frontend/file-router.ts +61 -19
- package/src/frontend/frameworks/index.ts +37 -10
- package/src/frontend/frameworks/react.ts +10 -8
- package/src/frontend/frameworks/solid.ts +11 -9
- package/src/frontend/frameworks/svelte.ts +15 -9
- package/src/frontend/frameworks/vue.ts +13 -11
- package/src/frontend/hmr-client.ts +12 -10
- package/src/frontend/hmr.ts +146 -103
- package/src/frontend/index.ts +14 -5
- package/src/frontend/islands.ts +41 -22
- package/src/frontend/isr.ts +59 -37
- package/src/frontend/layout.ts +36 -21
- package/src/frontend/ssr/react.ts +74 -27
- package/src/frontend/ssr/solid.ts +54 -20
- package/src/frontend/ssr/svelte.ts +48 -14
- package/src/frontend/ssr/vue.ts +50 -18
- package/src/frontend/ssr.ts +83 -39
- package/src/frontend/types.ts +91 -56
- package/src/health/index.ts +21 -9
- package/src/i18n/engine.ts +305 -0
- package/src/i18n/index.ts +38 -0
- package/src/i18n/loader.ts +218 -0
- package/src/i18n/middleware.ts +164 -0
- package/src/i18n/negotiator.ts +162 -0
- package/src/i18n/types.ts +158 -0
- package/src/index.ts +179 -27
- package/src/jobs/drivers/memory.ts +315 -0
- package/src/jobs/drivers/redis.ts +459 -0
- package/src/jobs/index.ts +30 -0
- package/src/jobs/queue.ts +281 -0
- package/src/jobs/types.ts +295 -0
- package/src/jobs/worker.ts +380 -0
- package/src/logger/index.ts +1 -3
- package/src/logger/transports/index.ts +62 -22
- package/src/metrics/index.ts +25 -16
- package/src/migrations/index.ts +9 -0
- package/src/modules/filters.ts +13 -17
- package/src/modules/guards.ts +49 -26
- package/src/modules/index.ts +409 -298
- package/src/modules/interceptors.ts +58 -20
- package/src/modules/lazy.ts +11 -19
- package/src/modules/lifecycle.ts +15 -7
- package/src/modules/metadata.ts +15 -5
- package/src/modules/pipes.ts +94 -72
- package/src/notification/channels/base.ts +68 -0
- package/src/notification/channels/email.ts +105 -0
- package/src/notification/channels/push.ts +104 -0
- package/src/notification/channels/sms.ts +105 -0
- package/src/notification/channels/whatsapp.ts +104 -0
- package/src/notification/index.ts +48 -0
- package/src/notification/service.ts +354 -0
- package/src/notification/types.ts +344 -0
- package/src/observability/__tests__/observability.test.ts +483 -0
- package/src/observability/breadcrumbs.ts +114 -0
- package/src/observability/index.ts +136 -0
- package/src/observability/interceptor.ts +85 -0
- package/src/observability/service.ts +303 -0
- package/src/observability/trace.ts +37 -0
- package/src/observability/types.ts +196 -0
- package/src/openapi/__tests__/decorators.test.ts +335 -0
- package/src/openapi/__tests__/document-builder.test.ts +285 -0
- package/src/openapi/__tests__/route-scanner.test.ts +334 -0
- package/src/openapi/__tests__/schema-generator.test.ts +275 -0
- package/src/openapi/decorators.ts +328 -0
- package/src/openapi/document-builder.ts +274 -0
- package/src/openapi/index.ts +112 -0
- package/src/openapi/metadata.ts +112 -0
- package/src/openapi/route-scanner.ts +289 -0
- package/src/openapi/schema-generator.ts +256 -0
- package/src/openapi/swagger-module.ts +166 -0
- package/src/openapi/types.ts +398 -0
- package/src/orm/index.ts +10 -0
- package/src/rpc/index.ts +3 -1
- package/src/schema/index.ts +9 -0
- package/src/security/index.ts +15 -6
- package/src/ssg/index.ts +9 -8
- package/src/telemetry/index.ts +76 -22
- package/src/template/index.ts +7 -0
- package/src/templates/engine.ts +224 -0
- package/src/templates/index.ts +9 -0
- package/src/templates/loader.ts +331 -0
- package/src/templates/renderers/markdown.ts +212 -0
- package/src/templates/renderers/simple.ts +269 -0
- package/src/templates/types.ts +154 -0
- package/src/testing/index.ts +100 -27
- package/src/types/optional-deps.d.ts +347 -187
- package/src/validation/index.ts +92 -2
- package/src/validation/schemas.ts +536 -0
- package/tests/integration/fullstack.test.ts +4 -4
- package/tests/unit/database.test.ts +2 -72
- package/tests/unit/env-validation.test.ts +166 -0
- package/tests/unit/events.test.ts +910 -0
- package/tests/unit/i18n.test.ts +455 -0
- package/tests/unit/jobs.test.ts +493 -0
- package/tests/unit/notification.test.ts +988 -0
- package/tests/unit/observability.test.ts +453 -0
- package/tests/unit/orm/builder.test.ts +323 -0
- package/tests/unit/orm/casts.test.ts +179 -0
- package/tests/unit/orm/compiler.test.ts +220 -0
- package/tests/unit/orm/eager-loading.test.ts +285 -0
- package/tests/unit/orm/hooks.test.ts +191 -0
- package/tests/unit/orm/model.test.ts +373 -0
- package/tests/unit/orm/relationships.test.ts +303 -0
- package/tests/unit/orm/scopes.test.ts +74 -0
- package/tests/unit/templates-simple.test.ts +53 -0
- package/tests/unit/templates.test.ts +454 -0
- package/tests/unit/validation.test.ts +18 -24
- 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
|
+
}
|