@objectstack/service-queue 9.4.0 → 9.5.1

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 CHANGED
@@ -1,17 +1,26 @@
1
1
  # @objectstack/service-queue
2
2
 
3
- Queue Service for ObjectStack — implements `IQueueService` with in-memory and BullMQ adapters.
3
+ Queue Service for ObjectStack — implements `IQueueService` with an in-memory
4
+ adapter and a durable, database-backed adapter (`sys_job_queue`).
4
5
 
5
- ## Features
6
+ ## Adapters
6
7
 
7
- - **Multiple Adapters**: In-memory (development) and BullMQ/Redis (production)
8
- - **Job Queues**: Organize work into named queues with priorities
9
- - **Worker Pools**: Process jobs concurrently with configurable workers
10
- - **Retry Logic**: Automatic retry with exponential backoff
11
- - **Job Scheduling**: Delay job execution or schedule for future
12
- - **Progress Tracking**: Track job progress and completion
13
- - **Job Events**: Listen to job lifecycle events (active, completed, failed)
14
- - **Rate Limiting**: Control job processing rate
8
+ | Adapter | Durable | Multi-node | Use |
9
+ | --- | --- | --- | --- |
10
+ | `memory` | No (in-process) | No | dev / test / ephemeral work |
11
+ | `db` | Yes (`sys_job_queue`) | Yes (lease-based claim) | **production default** |
12
+ | `auto` *(default)* | | | `db` when an ObjectQL engine is present, else `memory` |
13
+
14
+ The `db` adapter persists messages, retries, and the dead-letter queue to the
15
+ `sys_job_queue` object. Multiple worker processes claim messages from the
16
+ shared table with a lease (`leaseMs`), so it works across a multi-node
17
+ deployment **without any external broker** — no Redis required. Studio can list
18
+ and replay the DLQ because `sys_job_queue` is a first-class object.
19
+
20
+ > A BullMQ/Redis adapter is **not** shipped. The durable path is the DB adapter;
21
+ > it rides on the same datasource the runtime already uses. If you genuinely
22
+ > need a Redis-backed broker, register a custom `IQueueService` via
23
+ > `ctx.registerService('queue', myAdapter)`.
15
24
 
16
25
  ## Installation
17
26
 
@@ -19,435 +28,79 @@ Queue Service for ObjectStack — implements `IQueueService` with in-memory and
19
28
  pnpm add @objectstack/service-queue
20
29
  ```
21
30
 
22
- For BullMQ adapter (production):
23
- ```bash
24
- pnpm add bullmq ioredis
25
- ```
26
-
27
- ## Basic Usage
28
-
29
- ```typescript
30
- import { defineStack } from '@objectstack/spec';
31
- import { ServiceQueue } from '@objectstack/service-queue';
32
-
33
- const stack = defineStack({
34
- services: [
35
- ServiceQueue.configure({
36
- adapter: 'memory', // or 'bullmq'
37
- defaultQueue: 'default',
38
- }),
39
- ],
40
- });
41
- ```
42
-
43
- ## Configuration
44
-
45
- ### In-Memory Adapter (Development)
46
-
47
- ```typescript
48
- ServiceQueue.configure({
49
- adapter: 'memory',
50
- concurrency: 5, // Max concurrent jobs
51
- });
52
- ```
53
-
54
- ### BullMQ Adapter (Production)
55
-
56
- ```typescript
57
- ServiceQueue.configure({
58
- adapter: 'bullmq',
59
- redis: {
60
- host: 'localhost',
61
- port: 6379,
62
- password: process.env.REDIS_PASSWORD,
63
- },
64
- queues: {
65
- default: { concurrency: 10 },
66
- email: { concurrency: 5, rateLimit: { max: 100, duration: 60000 } },
67
- reports: { concurrency: 2 },
68
- },
69
- });
70
- ```
71
-
72
- ## Service API
73
-
74
- ```typescript
75
- // Get queue service
76
- const queue = kernel.getService<IQueueService>('queue');
77
- ```
78
-
79
- ### Adding Jobs
80
-
81
- ```typescript
82
- // Add a simple job
83
- await queue.add('email', 'send_welcome', {
84
- to: 'user@example.com',
85
- template: 'welcome',
86
- });
87
-
88
- // Add job with options
89
- await queue.add('reports', 'generate_monthly', {
90
- month: '2024-01',
91
- format: 'pdf',
92
- }, {
93
- priority: 1, // Higher number = higher priority
94
- attempts: 3, // Retry up to 3 times
95
- backoff: {
96
- type: 'exponential',
97
- delay: 1000,
98
- },
99
- });
100
-
101
- // Add delayed job (runs in 1 hour)
102
- await queue.add('notifications', 'reminder', {
103
- userId: '123',
104
- message: 'Don't forget!',
105
- }, {
106
- delay: 3600000, // 1 hour in milliseconds
107
- });
108
-
109
- // Schedule job for specific time
110
- await queue.add('cleanup', 'old_files', {}, {
111
- timestamp: new Date('2024-12-31T23:59:59Z').getTime(),
112
- });
113
- ```
114
-
115
- ### Processing Jobs
116
-
117
- ```typescript
118
- // Register a job processor
119
- queue.process('email', async (job) => {
120
- console.log('Processing email job:', job.data);
121
-
122
- // Access job data
123
- const { to, template } = job.data;
124
-
125
- // Update progress
126
- await job.updateProgress(25);
127
-
128
- // Send email
129
- await sendEmail(to, template);
130
-
131
- await job.updateProgress(100);
132
-
133
- // Return result
134
- return { sent: true, messageId: 'msg_123' };
135
- });
136
-
137
- // Process with concurrency
138
- queue.process('reports', 5, async (job) => {
139
- // Up to 5 reports generated concurrently
140
- return await generateReport(job.data);
141
- });
142
-
143
- // Process with named handler
144
- queue.process('default', 'calculate_metrics', async (job) => {
145
- return await calculateMetrics(job.data);
146
- });
147
- ```
148
-
149
- ### Job Management
150
-
151
- ```typescript
152
- // Get job by ID
153
- const job = await queue.getJob('email', 'job_abc123');
154
-
155
- // Get job status
156
- const status = await job.getState();
157
- // 'waiting' | 'active' | 'completed' | 'failed' | 'delayed'
158
-
159
- // Remove job
160
- await queue.removeJob('email', 'job_abc123');
161
-
162
- // Retry failed job
163
- await queue.retryJob('email', 'job_abc123');
164
-
165
- // Get job result
166
- const result = await job.returnvalue;
167
- ```
168
-
169
- ### Queue Operations
170
-
171
- ```typescript
172
- // Pause queue (stop processing new jobs)
173
- await queue.pause('email');
174
-
175
- // Resume queue
176
- await queue.resume('email');
177
-
178
- // Clear all jobs in queue
179
- await queue.clear('email');
180
-
181
- // Get queue statistics
182
- const stats = await queue.getStats('email');
183
- // {
184
- // waiting: 45,
185
- // active: 5,
186
- // completed: 1250,
187
- // failed: 12,
188
- // delayed: 3
189
- // }
190
- ```
191
-
192
- ## Advanced Features
193
-
194
- ### Job Events
195
-
196
- ```typescript
197
- // Listen to job lifecycle events
198
- queue.on('email', 'completed', async (job, result) => {
199
- console.log(`Email sent: ${result.messageId}`);
200
- });
201
-
202
- queue.on('email', 'failed', async (job, error) => {
203
- console.error(`Email failed: ${error.message}`);
204
- // Send alert to admin
205
- });
206
-
207
- queue.on('email', 'progress', async (job, progress) => {
208
- console.log(`Email progress: ${progress}%`);
209
- });
210
-
211
- queue.on('email', 'active', async (job) => {
212
- console.log(`Email job started: ${job.id}`);
213
- });
214
- ```
215
-
216
- ### Bulk Operations
217
-
218
- ```typescript
219
- // Add multiple jobs at once
220
- await queue.addBulk('email', [
221
- { name: 'send_welcome', data: { to: 'user1@example.com' } },
222
- { name: 'send_welcome', data: { to: 'user2@example.com' } },
223
- { name: 'send_welcome', data: { to: 'user3@example.com' } },
224
- ]);
225
-
226
- // Get multiple jobs
227
- const jobs = await queue.getJobs('email', ['waiting', 'active']);
228
- ```
229
-
230
- ### Job Patterns
231
-
232
- #### Worker Pattern
233
-
234
- ```typescript
235
- // Dedicated worker process
236
- queue.process('heavy_processing', async (job) => {
237
- // CPU-intensive work
238
- const result = await processLargeDataset(job.data);
239
- return result;
240
- });
241
- ```
242
-
243
- #### Fan-Out Pattern
244
-
245
- ```typescript
246
- // Split work across multiple jobs
247
- await queue.add('orchestrator', 'process_batch', { batchId: '123' });
248
-
249
- queue.process('orchestrator', async (job) => {
250
- const items = await loadBatchItems(job.data.batchId);
251
-
252
- // Create sub-jobs for each item
253
- for (const item of items) {
254
- await queue.add('worker', 'process_item', { item });
255
- }
256
- });
257
-
258
- queue.process('worker', async (job) => {
259
- return await processItem(job.data.item);
260
- });
261
- ```
262
-
263
- #### Priority Queues
264
-
265
- ```typescript
266
- // High priority
267
- await queue.add('tasks', 'urgent', data, { priority: 10 });
268
-
269
- // Normal priority
270
- await queue.add('tasks', 'normal', data, { priority: 5 });
271
-
272
- // Low priority
273
- await queue.add('tasks', 'background', data, { priority: 1 });
274
- ```
275
-
276
- ### Rate Limiting
277
-
278
- ```typescript
279
- // Limit queue to 100 jobs per minute
280
- ServiceQueue.configure({
281
- adapter: 'bullmq',
282
- queues: {
283
- api_calls: {
284
- concurrency: 5,
285
- rateLimit: {
286
- max: 100,
287
- duration: 60000, // 1 minute
288
- },
289
- },
290
- },
291
- });
292
- ```
293
-
294
- ### Repeatable Jobs
31
+ ## Usage
295
32
 
296
33
  ```typescript
297
- // Add cron-based repeatable job
298
- await queue.addRepeatable('cleanup', 'old_sessions', {}, {
299
- cron: '0 2 * * *', // Daily at 2 AM
300
- });
301
-
302
- // Add interval-based repeatable job
303
- await queue.addRepeatable('sync', 'data', {}, {
304
- every: 300000, // Every 5 minutes
305
- });
306
-
307
- // Remove repeatable job
308
- await queue.removeRepeatable('cleanup', 'old_sessions');
309
- ```
34
+ import { ObjectKernel } from '@objectstack/core';
35
+ import { QueueServicePlugin } from '@objectstack/service-queue';
310
36
 
311
- ## Common Use Cases
37
+ const kernel = new ObjectKernel();
38
+ // 'auto' (default): durable DbQueueAdapter when ObjectQL is available, else memory
39
+ kernel.use(new QueueServicePlugin({ adapter: 'auto' }));
40
+ await kernel.bootstrap();
312
41
 
313
- ### Email Queue
42
+ const queue = kernel.getService('queue'); // IQueueService
314
43
 
315
- ```typescript
316
- queue.process('email', async (job) => {
317
- const { to, subject, body, template } = job.data;
318
-
319
- try {
320
- const result = await emailProvider.send({
321
- to,
322
- subject,
323
- html: renderTemplate(template, job.data),
324
- });
325
-
326
- return { messageId: result.id, sentAt: new Date() };
327
- } catch (error) {
328
- // Throw error to trigger retry
329
- throw new Error(`Failed to send email: ${error.message}`);
330
- }
44
+ // Publish a message
45
+ await queue.subscribe('email', async (msg) => {
46
+ await sendEmail(msg.data);
331
47
  });
332
48
 
333
- // Add email job
334
- await queue.add('email', 'welcome', {
335
- to: 'newuser@example.com',
336
- template: 'welcome',
337
- name: 'John Doe',
338
- }, {
49
+ await queue.publish('email', { to: 'user@example.com', template: 'welcome' }, {
50
+ // delay / priority / retries (see QueuePublishOptions)
339
51
  attempts: 3,
340
- backoff: { type: 'exponential', delay: 5000 },
341
- });
342
- ```
343
-
344
- ### Report Generation
345
-
346
- ```typescript
347
- queue.process('reports', async (job) => {
348
- const { reportType, userId, dateRange } = job.data;
349
-
350
- await job.updateProgress(10);
351
-
352
- // Fetch data
353
- const data = await fetchReportData(reportType, dateRange);
354
-
355
- await job.updateProgress(50);
356
-
357
- // Generate report
358
- const report = await generatePDF(data);
359
-
360
- await job.updateProgress(90);
361
-
362
- // Upload to storage
363
- const url = await uploadReport(report);
364
-
365
- await job.updateProgress(100);
366
-
367
- // Notify user
368
- await notifyUser(userId, { reportUrl: url });
369
-
370
- return { url, size: report.length };
371
52
  });
372
53
  ```
373
54
 
374
- ### Webhook Processing
55
+ ### Configuration
375
56
 
376
57
  ```typescript
377
- queue.process('webhooks', async (job) => {
378
- const { url, payload, headers } = job.data;
379
-
380
- const response = await fetch(url, {
381
- method: 'POST',
382
- headers,
383
- body: JSON.stringify(payload),
384
- });
385
-
386
- if (!response.ok) {
387
- throw new Error(`Webhook failed: ${response.status}`);
388
- }
389
-
390
- return { status: response.status, responseTime: Date.now() - job.timestamp };
58
+ // Force the durable DB adapter (requires an ObjectQL engine)
59
+ new QueueServicePlugin({
60
+ adapter: 'db',
61
+ db: {
62
+ pollIntervalMs: 1000, // worker poll cadence
63
+ batchSize: 10, // messages claimed per tick
64
+ leaseMs: 30000, // lease before another worker may reclaim
65
+ idempotencyWindowMs: 24 * 60 * 60 * 1000,
66
+ },
391
67
  });
392
- ```
393
68
 
394
- ## REST API Endpoints
395
-
396
- ```
397
- POST /api/v1/queues/:queue/jobs # Add job
398
- GET /api/v1/queues/:queue/jobs/:id # Get job
399
- DELETE /api/v1/queues/:queue/jobs/:id # Remove job
400
- POST /api/v1/queues/:queue/jobs/:id/retry # Retry failed job
401
- GET /api/v1/queues/:queue/stats # Get queue stats
402
- POST /api/v1/queues/:queue/pause # Pause queue
403
- POST /api/v1/queues/:queue/resume # Resume queue
404
- DELETE /api/v1/queues/:queue # Clear queue
69
+ // In-process only (non-durable) — dev / test
70
+ new QueueServicePlugin({ adapter: 'memory' });
405
71
  ```
406
72
 
407
- ## Best Practices
408
-
409
- 1. **Idempotent Jobs**: Design jobs to be safely retried
410
- 2. **Error Handling**: Always handle errors and throw to trigger retry
411
- 3. **Progress Updates**: Update progress for long-running jobs
412
- 4. **Resource Limits**: Set appropriate concurrency limits
413
- 5. **Job Data**: Keep job data small (< 1MB)
414
- 6. **Monitoring**: Track queue metrics and job failure rates
415
- 7. **Cleanup**: Remove completed jobs periodically
416
-
417
- ## Performance Considerations
418
-
419
- - **Concurrency**: Tune based on system resources and external API limits
420
- - **Rate Limiting**: Prevent overwhelming external services
421
- - **Job Size**: Keep job payloads small for faster serialization
422
- - **Redis Connection**: Use connection pooling for BullMQ
423
- - **Queue Organization**: Use separate queues for different job types
424
-
425
- ## Contract Implementation
73
+ ## Service API
426
74
 
427
75
  Implements `IQueueService` from `@objectstack/spec/contracts`:
428
76
 
429
77
  ```typescript
430
78
  interface IQueueService {
431
- add(queue: string, name: string, data: any, options?: JobOptions): Promise<Job>;
432
- addBulk(queue: string, jobs: JobDefinition[]): Promise<Job[]>;
433
- process(queue: string, handler: JobHandler): void;
434
- getJob(queue: string, jobId: string): Promise<Job | null>;
435
- removeJob(queue: string, jobId: string): Promise<void>;
436
- retryJob(queue: string, jobId: string): Promise<void>;
437
- getStats(queue: string): Promise<QueueStats>;
438
- pause(queue: string): Promise<void>;
439
- resume(queue: string): Promise<void>;
440
- clear(queue: string): Promise<void>;
441
- on(queue: string, event: JobEvent, handler: EventHandler): void;
79
+ publish<T>(queue: string, data: T, options?: QueuePublishOptions): Promise<string>;
80
+ subscribe<T>(queue: string, handler: QueueHandler<T>): Promise<void>;
81
+ unsubscribe(queue: string): Promise<void>;
82
+ getQueueSize?(queue: string): Promise<number>;
83
+ purge?(queue: string): Promise<void>;
84
+ // Dead-letter queue (db adapter)
85
+ listFailed?(queue?: string, options?: { limit?: number; offset?: number }): Promise<QueueMessageRecord[]>;
86
+ replay?(messageId: string): Promise<void>;
87
+ purgeFailed?(messageId: string): Promise<void>;
442
88
  }
443
89
  ```
444
90
 
91
+ ## Best Practices
92
+
93
+ 1. **Idempotent handlers** — messages may be re-delivered after a lease expires.
94
+ 2. **Small payloads** — keep message data compact for fast serialization.
95
+ 3. **Handle the DLQ** — monitor `listFailed()` and `replay()` poisoned messages.
96
+ 4. **Use `db` in production** — `memory` loses in-flight work on restart and does
97
+ not coordinate across nodes.
98
+
445
99
  ## License
446
100
 
447
101
  Apache-2.0. See [LICENSING.md](../../../LICENSING.md).
448
102
 
449
103
  ## See Also
450
104
 
451
- - [BullMQ Documentation](https://docs.bullmq.io/)
452
- - [@objectstack/spec/contracts](../../spec/src/contracts/)
453
- - [Queue Patterns Guide](/content/docs/guides/queues/)
105
+ - [@objectstack/spec/contracts](../../spec/src/contracts/queue-service.ts)
106
+ - [@objectstack/trigger-schedule](../../triggers/trigger-schedule) — cron/interval triggers on top of the job service
package/dist/index.cjs CHANGED
@@ -20,7 +20,6 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
- BullMQQueueAdapter: () => BullMQQueueAdapter,
24
23
  DbQueueAdapter: () => DbQueueAdapter,
25
24
  MemoryQueueAdapter: () => MemoryQueueAdapter,
26
25
  QueueServicePlugin: () => QueueServicePlugin
@@ -451,11 +450,6 @@ var QueueServicePlugin = class {
451
450
  ctx.logger.warn("QueueServicePlugin: manifest service unavailable; sys_job_queue not registered", err);
452
451
  }
453
452
  const choice = this.options.adapter ?? "auto";
454
- if (choice === "bullmq") {
455
- throw new Error(
456
- 'BullMQ queue adapter is not yet implemented (M10.43). Use adapter: "auto", "db", or "memory", or provide a custom IQueueService.'
457
- );
458
- }
459
453
  if (choice === "memory") {
460
454
  const q = new MemoryQueueAdapter(this.options.memory);
461
455
  ctx.registerService("queue", q);
@@ -499,31 +493,8 @@ var QueueServicePlugin = class {
499
493
  await this.dbAdapter?.stop();
500
494
  }
501
495
  };
502
-
503
- // src/bullmq-queue-adapter.ts
504
- var BullMQQueueAdapter = class {
505
- constructor(options) {
506
- this.redisUrl = options.redisUrl;
507
- }
508
- async publish(_queue, _data, _options) {
509
- throw new Error(`BullMQQueueAdapter not yet implemented (url: ${this.redisUrl})`);
510
- }
511
- async subscribe(_queue, _handler) {
512
- throw new Error("BullMQQueueAdapter not yet implemented");
513
- }
514
- async unsubscribe(_queue) {
515
- throw new Error("BullMQQueueAdapter not yet implemented");
516
- }
517
- async getQueueSize(_queue) {
518
- throw new Error("BullMQQueueAdapter not yet implemented");
519
- }
520
- async purge(_queue) {
521
- throw new Error("BullMQQueueAdapter not yet implemented");
522
- }
523
- };
524
496
  // Annotate the CommonJS export names for ESM import in node:
525
497
  0 && (module.exports = {
526
- BullMQQueueAdapter,
527
498
  DbQueueAdapter,
528
499
  MemoryQueueAdapter,
529
500
  QueueServicePlugin
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/queue-service-plugin.ts","../src/memory-queue-adapter.ts","../src/common.ts","../src/db-queue-adapter.ts","../src/bullmq-queue-adapter.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nexport { QueueServicePlugin } from './queue-service-plugin.js';\nexport type { QueueServicePluginOptions } from './queue-service-plugin.js';\nexport { MemoryQueueAdapter } from './memory-queue-adapter.js';\nexport type { MemoryQueueAdapterOptions } from './memory-queue-adapter.js';\nexport { BullMQQueueAdapter } from './bullmq-queue-adapter.js';\nexport type { BullMQQueueAdapterOptions } from './bullmq-queue-adapter.js';\nexport { DbQueueAdapter } from './db-queue-adapter.js';\nexport type { DbQueueAdapterOptions } from './db-queue-adapter.js';\nexport type { JobEngine, JobClock, JobLogger } from './common.js';\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { Plugin, PluginContext } from '@objectstack/core';\nimport { SysJobQueue } from '@objectstack/platform-objects/audit';\nimport { MemoryQueueAdapter } from './memory-queue-adapter.js';\nimport type { MemoryQueueAdapterOptions } from './memory-queue-adapter.js';\nimport { DbQueueAdapter } from './db-queue-adapter.js';\nimport type { DbQueueAdapterOptions } from './db-queue-adapter.js';\n\n/**\n * Configuration options for the QueueServicePlugin.\n */\nexport interface QueueServicePluginOptions {\n /**\n * Queue adapter type.\n * - 'auto' (default): use DbQueueAdapter when objectql engine available, else MemoryQueueAdapter\n * - 'db': require objectql; persists messages, retries, and DLQ to sys_job_queue\n * - 'memory': in-process MemoryQueueAdapter (non-durable, dev/test)\n * - 'bullmq': reserved for M10.43 (throws today)\n */\n adapter?: 'auto' | 'db' | 'memory' | 'bullmq';\n /** Options for the memory queue adapter */\n memory?: MemoryQueueAdapterOptions;\n /** Options for the DB adapter (polling, batch, lease, idempotency window…) */\n db?: DbQueueAdapterOptions;\n /** Redis connection URL (reserved for bullmq) */\n redisUrl?: string;\n}\n\n/**\n * QueueServicePlugin — Production IQueueService implementation.\n *\n * Default: registers MemoryQueueAdapter synchronously so producers can\n * publish during plugin init; upgrades to DbQueueAdapter on `kernel:ready`\n * when an ObjectQL engine is available. Subscribers registered against\n * the (now-replaced) memory queue must re-subscribe after upgrade — for\n * that reason most plugins register subscribers inside their own\n * `kernel:ready` hook, which fires after this one.\n */\nexport class QueueServicePlugin implements Plugin {\n name = 'com.objectstack.service.queue';\n version = '1.1.0';\n type = 'standard';\n\n private readonly options: QueueServicePluginOptions;\n private dbAdapter?: DbQueueAdapter;\n\n constructor(options: QueueServicePluginOptions = {}) {\n this.options = { adapter: 'auto', ...options };\n }\n\n async init(ctx: PluginContext): Promise<void> {\n // Register sys_job_queue (also serves as DLQ view) so Studio can list/replay.\n try {\n ctx.getService<{ register(m: any): void }>('manifest').register({\n id: 'com.objectstack.service.queue',\n name: 'Queue Service',\n version: '1.1.0',\n type: 'plugin',\n scope: 'system',\n defaultDatasource: 'cloud',\n namespace: 'sys',\n objects: [SysJobQueue],\n });\n } catch (err) {\n ctx.logger.warn('QueueServicePlugin: manifest service unavailable; sys_job_queue not registered', err as any);\n }\n\n const choice = this.options.adapter ?? 'auto';\n\n if (choice === 'bullmq') {\n throw new Error(\n 'BullMQ queue adapter is not yet implemented (M10.43). ' +\n 'Use adapter: \"auto\", \"db\", or \"memory\", or provide a custom IQueueService.',\n );\n }\n\n if (choice === 'memory') {\n const q = new MemoryQueueAdapter(this.options.memory);\n ctx.registerService('queue', q);\n ctx.logger.info('QueueServicePlugin: registered MemoryQueueAdapter');\n return;\n }\n\n // auto / db — register memory placeholder, upgrade on kernel:ready\n ctx.registerService('queue', new MemoryQueueAdapter(this.options.memory));\n\n ctx.hook('kernel:ready', async () => {\n let engine: any = null;\n try { engine = ctx.getService<any>('objectql'); }\n catch { try { engine = ctx.getService<any>('data'); } catch { /* ignore */ } }\n\n if (!engine) {\n if (choice === 'db') {\n ctx.logger.warn('QueueServicePlugin: db adapter requested but no ObjectQL engine — staying on MemoryQueueAdapter');\n } else {\n ctx.logger.info('QueueServicePlugin: no ObjectQL engine — staying on MemoryQueueAdapter');\n }\n return;\n }\n\n this.dbAdapter = new DbQueueAdapter({\n engine,\n logger: ctx.logger,\n options: this.options.db,\n });\n\n try {\n (ctx as any).replaceService?.('queue', this.dbAdapter);\n this.dbAdapter.start();\n ctx.logger.info('QueueServicePlugin: upgraded to DbQueueAdapter (sys_job_queue persistence)');\n } catch (err) {\n ctx.logger.warn('QueueServicePlugin: replaceService failed; staying on MemoryQueueAdapter', err as any);\n }\n });\n }\n\n async destroy(): Promise<void> {\n await this.dbAdapter?.stop();\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { IQueueService, QueuePublishOptions, QueueMessage, QueueHandler } from '@objectstack/spec/contracts';\n\n/**\n * Configuration options for MemoryQueueAdapter.\n */\nexport interface MemoryQueueAdapterOptions {\n /** Maximum number of messages retained per queue (0 = unlimited) */\n maxQueueSize?: number;\n}\n\n/**\n * In-memory queue adapter implementing IQueueService.\n *\n * Provides synchronous in-process pub/sub delivery.\n * Suitable for single-process environments, development, and testing.\n */\nexport class MemoryQueueAdapter implements IQueueService {\n private readonly handlers = new Map<string, QueueHandler[]>();\n private readonly deadLetters: QueueMessage[] = [];\n private msgCounter = 0;\n private readonly maxQueueSize: number;\n\n constructor(options: MemoryQueueAdapterOptions = {}) {\n this.maxQueueSize = options.maxQueueSize ?? 0;\n }\n\n async publish<T = unknown>(queue: string, data: T, options?: QueuePublishOptions): Promise<string> {\n const id = `msg-${++this.msgCounter}`;\n const msg: QueueMessage<T> = {\n id,\n data,\n attempts: 0,\n timestamp: Date.now(),\n };\n\n const fns = this.handlers.get(queue) ?? [];\n if (fns.length === 0) {\n // No subscribers — retain as dead letter if within limits\n if (this.maxQueueSize === 0 || this.deadLetters.length < this.maxQueueSize) {\n this.deadLetters.push(msg);\n }\n return id;\n }\n\n const maxRetries = options?.retries ?? 0;\n for (const handler of fns) {\n let attempt = 0;\n let success = false;\n while (!success && attempt <= maxRetries) {\n try {\n msg.attempts = attempt + 1;\n await handler(msg as QueueMessage);\n success = true;\n } catch {\n attempt++;\n }\n }\n }\n\n return id;\n }\n\n async subscribe<T = unknown>(queue: string, handler: QueueHandler<T>): Promise<void> {\n const existing = this.handlers.get(queue) ?? [];\n this.handlers.set(queue, [...existing, handler as QueueHandler]);\n }\n\n async unsubscribe(queue: string): Promise<void> {\n this.handlers.delete(queue);\n }\n\n async getQueueSize(_queue: string): Promise<number> {\n // In-memory: no persistent queue depth tracking\n return 0;\n }\n\n async purge(queue: string): Promise<void> {\n this.handlers.delete(queue);\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Narrow ObjectQL engine surface used by job/queue adapters.\n * Keeps the adapter testable without booting a real kernel.\n *\n * IMPORTANT: matches the canonical engine API:\n * - find: `where:` (NOT `filter:`)\n * - update: `(table, {id, ...patch}, opts)`\n */\nexport interface JobEngine {\n find(object: string, options?: any): Promise<any[]>;\n insert(object: string, data: any, options?: any): Promise<any>;\n update(object: string, idOrData: any, dataOrOptions?: any, options?: any): Promise<any>;\n delete(object: string, options?: any): Promise<any>;\n}\n\n/** Stamped only in tests to make `now` deterministic. */\nexport interface JobClock { now(): Date }\n\nexport interface JobLogger {\n info(msg: string, meta?: unknown): void;\n warn(msg: string, meta?: unknown): void;\n error?(msg: string, meta?: unknown): void;\n}\n\nexport const SYSTEM_CTX = { isSystem: true, roles: [], permissions: [] } as const;\n\nexport function uid(prefix: string): string {\n const g: any = globalThis as any;\n if (g.crypto?.randomUUID) return `${prefix}_${g.crypto.randomUUID()}`;\n return `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;\n}\n\nexport function nowIso(clock?: JobClock): string {\n return (clock?.now() ?? new Date()).toISOString();\n}\n\nexport function parseJson<T = unknown>(raw: unknown, fallback?: T): T | undefined {\n if (raw == null) return fallback;\n if (typeof raw === 'string') {\n try { return JSON.parse(raw) as T; } catch { return fallback; }\n }\n if (typeof raw === 'object') return raw as T;\n return fallback;\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type {\n IQueueService,\n QueuePublishOptions,\n QueueMessage,\n QueueMessageRecord,\n QueueHandler,\n} from '@objectstack/spec/contracts';\nimport {\n SYSTEM_CTX,\n uid,\n nowIso,\n parseJson,\n type JobEngine,\n type JobClock,\n type JobLogger,\n} from './common.js';\n\nconst QUEUE_TABLE = 'sys_job_queue';\n\nexport interface DbQueueAdapterOptions {\n /** Polling interval for the worker loop (ms, default 1000) */\n pollIntervalMs?: number;\n /** Max messages claimed per poll tick (default 10) */\n batchSize?: number;\n /** Lease duration before another worker may reclaim (ms, default 30000) */\n leaseMs?: number;\n /** Idempotency window — how long the same key blocks re-publish (ms, default 24h) */\n idempotencyWindowMs?: number;\n /** Default maxAttempts when publish doesn't specify (default 3) */\n defaultMaxAttempts?: number;\n /** Unique identifier for this worker (default: random) */\n workerId?: string;\n /** Whether to auto-start the polling worker (default true) */\n autoStart?: boolean;\n}\n\ninterface RegisteredHandler {\n queue: string;\n fn: QueueHandler;\n}\n\n/**\n * DbQueueAdapter — durable, polling, DB-backed IQueueService.\n *\n * Persists every message to `sys_job_queue`. A polling worker leases\n * pending messages (CAS update status pending→running with a lease),\n * invokes registered subscribers, and retries with backoff on failure.\n * Messages that exceed `max_attempts` land in `status='dlq'`.\n *\n * Idempotency: publish suppresses duplicates within a configurable\n * window when `(queue, idempotencyKey)` is non-null.\n *\n * Designed for SQLite and Postgres alike — uses CAS via WHERE-clauses,\n * not row-level locking.\n */\nexport class DbQueueAdapter implements IQueueService {\n private readonly engine: JobEngine;\n private readonly logger?: JobLogger;\n private readonly clock?: JobClock;\n private readonly opts: Required<Omit<DbQueueAdapterOptions, 'workerId'>> & { workerId: string };\n\n private readonly handlers = new Map<string, RegisteredHandler[]>();\n private timer?: ReturnType<typeof setInterval>;\n private running = false;\n\n constructor(args: {\n engine: JobEngine;\n logger?: JobLogger;\n clock?: JobClock;\n options?: DbQueueAdapterOptions;\n }) {\n this.engine = args.engine;\n this.logger = args.logger;\n this.clock = args.clock;\n const o = args.options ?? {};\n this.opts = {\n pollIntervalMs: o.pollIntervalMs ?? 1000,\n batchSize: o.batchSize ?? 10,\n leaseMs: o.leaseMs ?? 30_000,\n idempotencyWindowMs: o.idempotencyWindowMs ?? 24 * 60 * 60 * 1000,\n defaultMaxAttempts: o.defaultMaxAttempts ?? 3,\n autoStart: o.autoStart ?? true,\n workerId: o.workerId ?? uid('worker'),\n };\n }\n\n // ── IQueueService ────────────────────────────────────────────────\n\n async publish<T = unknown>(\n queue: string,\n data: T,\n options?: QueuePublishOptions,\n ): Promise<string> {\n const opts = options ?? {};\n const now = this.now();\n\n // Idempotency check\n if (opts.idempotencyKey) {\n const windowStart = new Date(now.getTime() - this.opts.idempotencyWindowMs).toISOString();\n const existing = await this.engine.find(QUEUE_TABLE, {\n where: {\n queue,\n idempotency_key: opts.idempotencyKey,\n // Only block if not yet terminal — completed/dlq dedup is by window via created_at\n },\n limit: 5,\n context: SYSTEM_CTX,\n });\n const blocking = (existing ?? []).find((row: any) => {\n if (row.status === 'pending' || row.status === 'running') return true;\n return String(row.created_at ?? '') >= windowStart;\n });\n if (blocking) return String(blocking.id);\n }\n\n const id = uid('msg');\n const scheduledFor = opts.scheduledFor\n ? new Date(opts.scheduledFor).toISOString()\n : opts.delay\n ? new Date(now.getTime() + opts.delay).toISOString()\n : now.toISOString();\n\n const maxAttempts = opts.maxAttempts\n ?? (opts.retries != null ? opts.retries + 1 : this.opts.defaultMaxAttempts);\n const backoff = opts.backoff ?? { type: 'exponential' as const, delayMs: 1000 };\n\n await this.engine.insert(QUEUE_TABLE, {\n id,\n queue,\n idempotency_key: opts.idempotencyKey ?? null,\n payload_json: JSON.stringify(data ?? null),\n metadata_json: opts.metadata ? JSON.stringify(opts.metadata) : null,\n status: 'pending',\n priority: opts.priority ?? 100,\n attempts: 0,\n max_attempts: maxAttempts,\n backoff_type: backoff.type,\n backoff_delay_ms: backoff.delayMs,\n backoff_max_delay_ms: backoff.maxDelayMs ?? null,\n scheduled_for: scheduledFor,\n created_at: now.toISOString(),\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n\n return id;\n }\n\n async subscribe<T = unknown>(queue: string, handler: QueueHandler<T>): Promise<void> {\n const existing = this.handlers.get(queue) ?? [];\n existing.push({ queue, fn: handler as QueueHandler });\n this.handlers.set(queue, existing);\n if (this.opts.autoStart) this.start();\n }\n\n async unsubscribe(queue: string): Promise<void> {\n this.handlers.delete(queue);\n }\n\n async getQueueSize(queue: string): Promise<number> {\n const rows = await this.engine.find(QUEUE_TABLE, {\n where: { queue, status: 'pending' },\n limit: 10_000,\n context: SYSTEM_CTX,\n });\n return rows?.length ?? 0;\n }\n\n async purge(queue: string): Promise<void> {\n const rows = await this.engine.find(QUEUE_TABLE, {\n where: { queue, status: 'pending' },\n limit: 10_000,\n context: SYSTEM_CTX,\n });\n for (const row of rows ?? []) {\n try { await this.engine.delete(QUEUE_TABLE, { id: row.id, context: SYSTEM_CTX }); }\n catch (err) { this.logger?.warn?.('DbQueueAdapter: purge delete failed', err as any); }\n }\n }\n\n async listFailed(\n queue?: string,\n options?: { limit?: number; offset?: number },\n ): Promise<QueueMessageRecord[]> {\n const where: any = { status: 'dlq' };\n if (queue) where.queue = queue;\n const rows = await this.engine.find(QUEUE_TABLE, {\n where,\n limit: options?.limit ?? 100,\n offset: options?.offset,\n orderBy: [{ field: 'created_at', order: 'desc' }],\n context: SYSTEM_CTX,\n });\n return (rows ?? []).map((r: any) => this.rowToRecord(r));\n }\n\n async replay(messageId: string): Promise<void> {\n const row = await this.loadById(messageId);\n if (!row) throw new Error(`MESSAGE_NOT_FOUND: ${messageId}`);\n if (row.status !== 'dlq' && row.status !== 'failed') {\n throw new Error(`INVALID_STATE: cannot replay message in status=${row.status}`);\n }\n const now = this.now();\n await this.engine.update(QUEUE_TABLE, {\n id: messageId,\n status: 'pending',\n attempts: 0,\n last_error: null,\n locked_by: null,\n locked_until: null,\n scheduled_for: now.toISOString(),\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n }\n\n async purgeFailed(messageId: string): Promise<void> {\n const row = await this.loadById(messageId);\n if (!row) return;\n if (row.status !== 'dlq' && row.status !== 'failed') {\n throw new Error(`INVALID_STATE: cannot purge message in status=${row.status}`);\n }\n await this.engine.delete(QUEUE_TABLE, { id: messageId, context: SYSTEM_CTX });\n }\n\n // ── Worker lifecycle ─────────────────────────────────────────────\n\n start(): void {\n if (this.timer) return;\n this.timer = setInterval(() => {\n if (this.running) return;\n this.running = true;\n this.pollOnce()\n .catch((err) => { this.logger?.warn?.('DbQueueAdapter: poll tick failed', err); })\n .finally(() => { this.running = false; });\n }, this.opts.pollIntervalMs);\n (this.timer as any)?.unref?.();\n }\n\n async stop(): Promise<void> {\n if (this.timer) { clearInterval(this.timer); this.timer = undefined; }\n }\n\n /** Test-friendly synchronous poll. */\n async pollOnce(): Promise<number> {\n const queues = [...this.handlers.keys()];\n if (queues.length === 0) return 0;\n\n let processed = 0;\n for (const queue of queues) {\n const claimed = await this.claimBatch(queue, this.opts.batchSize);\n for (const row of claimed) {\n await this.dispatch(row);\n processed++;\n }\n }\n return processed;\n }\n\n // ── Internals ────────────────────────────────────────────────────\n\n private async claimBatch(queue: string, max: number): Promise<any[]> {\n const now = this.now();\n const candidates = await this.engine.find(QUEUE_TABLE, {\n where: { queue, status: 'pending' },\n limit: max * 3, // over-fetch in case of CAS contention\n orderBy: [\n { field: 'priority', order: 'asc' },\n { field: 'scheduled_for', order: 'asc' },\n ],\n context: SYSTEM_CTX,\n });\n\n const out: any[] = [];\n for (const row of candidates ?? []) {\n if (out.length >= max) break;\n const sched = row.scheduled_for ? new Date(row.scheduled_for).getTime() : 0;\n if (sched > now.getTime()) continue;\n // Honor existing lease\n const lockedUntil = row.locked_until ? new Date(row.locked_until).getTime() : 0;\n if (row.locked_by && lockedUntil > now.getTime()) continue;\n\n // CAS — only update if still pending (best-effort with engine.update which\n // typically does row-level update by id; concurrent workers will overwrite\n // each other but the dispatcher tolerates duplicate delivery via attempts).\n try {\n await this.engine.update(QUEUE_TABLE, {\n id: row.id,\n status: 'running',\n locked_by: this.opts.workerId,\n locked_until: new Date(now.getTime() + this.opts.leaseMs).toISOString(),\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n out.push({ ...row, status: 'running' });\n } catch (err) {\n this.logger?.warn?.('DbQueueAdapter: claim CAS failed', err as any);\n }\n }\n return out;\n }\n\n private async dispatch(row: any): Promise<void> {\n const handlers = this.handlers.get(row.queue) ?? [];\n if (handlers.length === 0) {\n // No handler — release lease so another process can pick it up\n await this.releasePending(row.id);\n return;\n }\n\n const msg: QueueMessage = {\n id: String(row.id),\n data: parseJson(row.payload_json),\n attempts: (row.attempts ?? 0) + 1,\n timestamp: row.created_at ? new Date(row.created_at).getTime() : Date.now(),\n };\n\n let success = true;\n let lastError: string | undefined;\n for (const h of handlers) {\n try { await h.fn(msg); }\n catch (err) {\n success = false;\n lastError = err instanceof Error ? err.message : String(err);\n this.logger?.warn?.(`DbQueueAdapter: handler failed on ${row.queue}`, err as any);\n break;\n }\n }\n\n const now = this.now();\n if (success) {\n await this.engine.update(QUEUE_TABLE, {\n id: row.id,\n status: 'completed',\n attempts: msg.attempts,\n completed_at: now.toISOString(),\n locked_by: null,\n locked_until: null,\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n return;\n }\n\n const attempts = msg.attempts;\n const max = row.max_attempts ?? this.opts.defaultMaxAttempts;\n if (attempts >= max) {\n await this.engine.update(QUEUE_TABLE, {\n id: row.id,\n status: 'dlq',\n attempts,\n last_error: lastError ?? 'unknown error',\n completed_at: now.toISOString(),\n locked_by: null,\n locked_until: null,\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n return;\n }\n\n const backoffMs = this.computeBackoff(row, attempts);\n await this.engine.update(QUEUE_TABLE, {\n id: row.id,\n status: 'pending',\n attempts,\n last_error: lastError ?? 'unknown error',\n scheduled_for: new Date(now.getTime() + backoffMs).toISOString(),\n locked_by: null,\n locked_until: null,\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n }\n\n private computeBackoff(row: any, attempt: number): number {\n const base = row.backoff_delay_ms ?? 1000;\n const cap = row.backoff_max_delay_ms ?? undefined;\n if ((row.backoff_type ?? 'exponential') === 'fixed') return base;\n const exp = base * Math.pow(2, Math.max(0, attempt - 1));\n return cap ? Math.min(exp, cap) : exp;\n }\n\n private async releasePending(id: string): Promise<void> {\n const now = this.now();\n try {\n await this.engine.update(QUEUE_TABLE, {\n id,\n status: 'pending',\n locked_by: null,\n locked_until: null,\n scheduled_for: new Date(now.getTime() + this.opts.pollIntervalMs * 5).toISOString(),\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n } catch (err) {\n this.logger?.warn?.('DbQueueAdapter: release failed', err as any);\n }\n }\n\n private async loadById(id: string): Promise<any | null> {\n const rows = await this.engine.find(QUEUE_TABLE, {\n where: { id },\n limit: 1,\n context: SYSTEM_CTX,\n });\n return rows?.[0] ?? null;\n }\n\n private rowToRecord(r: any): QueueMessageRecord {\n return {\n id: String(r.id),\n queue: String(r.queue),\n data: parseJson(r.payload_json),\n status: r.status,\n attempts: r.attempts ?? 0,\n maxAttempts: r.max_attempts ?? this.opts.defaultMaxAttempts,\n scheduledFor: r.scheduled_for ?? undefined,\n lockedBy: r.locked_by ?? undefined,\n lockedUntil: r.locked_until ?? undefined,\n lastError: r.last_error ?? undefined,\n idempotencyKey: r.idempotency_key ?? undefined,\n metadata: parseJson(r.metadata_json),\n createdAt: r.created_at ?? nowIso(this.clock),\n updatedAt: r.updated_at ?? undefined,\n completedAt: r.completed_at ?? undefined,\n };\n }\n\n private now(): Date {\n return this.clock?.now() ?? new Date();\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { IQueueService, QueuePublishOptions, QueueHandler } from '@objectstack/spec/contracts';\n\n/**\n * Configuration for the BullMQ queue adapter.\n */\nexport interface BullMQQueueAdapterOptions {\n /** Redis connection URL (e.g. 'redis://localhost:6379') */\n redisUrl: string;\n /** Default job options */\n defaultJobOptions?: {\n /** Number of retry attempts */\n attempts?: number;\n /** Backoff strategy */\n backoff?: { type: 'fixed' | 'exponential'; delay: number };\n };\n}\n\n/**\n * BullMQ queue adapter skeleton implementing IQueueService.\n *\n * This is a placeholder for future BullMQ integration.\n * Concrete implementation will use the `bullmq` package.\n *\n * @example\n * ```ts\n * const queue = new BullMQQueueAdapter({ redisUrl: 'redis://localhost:6379' });\n * await queue.publish('orders', { orderId: 123 });\n * ```\n */\nexport class BullMQQueueAdapter implements IQueueService {\n private readonly redisUrl: string;\n\n constructor(options: BullMQQueueAdapterOptions) {\n this.redisUrl = options.redisUrl;\n }\n\n async publish<T = unknown>(_queue: string, _data: T, _options?: QueuePublishOptions): Promise<string> {\n throw new Error(`BullMQQueueAdapter not yet implemented (url: ${this.redisUrl})`);\n }\n\n async subscribe<T = unknown>(_queue: string, _handler: QueueHandler<T>): Promise<void> {\n throw new Error('BullMQQueueAdapter not yet implemented');\n }\n\n async unsubscribe(_queue: string): Promise<void> {\n throw new Error('BullMQQueueAdapter not yet implemented');\n }\n\n async getQueueSize(_queue: string): Promise<number> {\n throw new Error('BullMQQueueAdapter not yet implemented');\n }\n\n async purge(_queue: string): Promise<void> {\n throw new Error('BullMQQueueAdapter not yet implemented');\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/queue-service-plugin.ts","../src/memory-queue-adapter.ts","../src/common.ts","../src/db-queue-adapter.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nexport { QueueServicePlugin } from './queue-service-plugin.js';\nexport type { QueueServicePluginOptions } from './queue-service-plugin.js';\nexport { MemoryQueueAdapter } from './memory-queue-adapter.js';\nexport type { MemoryQueueAdapterOptions } from './memory-queue-adapter.js';\nexport { DbQueueAdapter } from './db-queue-adapter.js';\nexport type { DbQueueAdapterOptions } from './db-queue-adapter.js';\nexport type { JobEngine, JobClock, JobLogger } from './common.js';\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { Plugin, PluginContext } from '@objectstack/core';\nimport { SysJobQueue } from '@objectstack/platform-objects/audit';\nimport { MemoryQueueAdapter } from './memory-queue-adapter.js';\nimport type { MemoryQueueAdapterOptions } from './memory-queue-adapter.js';\nimport { DbQueueAdapter } from './db-queue-adapter.js';\nimport type { DbQueueAdapterOptions } from './db-queue-adapter.js';\n\n/**\n * Configuration options for the QueueServicePlugin.\n */\nexport interface QueueServicePluginOptions {\n /**\n * Queue adapter type.\n * - 'auto' (default): use DbQueueAdapter when objectql engine available, else MemoryQueueAdapter\n * - 'db': require objectql; persists messages, retries, and DLQ to sys_job_queue\n * - 'memory': in-process MemoryQueueAdapter (non-durable, dev/test)\n */\n adapter?: 'auto' | 'db' | 'memory';\n /** Options for the memory queue adapter */\n memory?: MemoryQueueAdapterOptions;\n /** Options for the DB adapter (polling, batch, lease, idempotency window…) */\n db?: DbQueueAdapterOptions;\n}\n\n/**\n * QueueServicePlugin — Production IQueueService implementation.\n *\n * Default: registers MemoryQueueAdapter synchronously so producers can\n * publish during plugin init; upgrades to DbQueueAdapter on `kernel:ready`\n * when an ObjectQL engine is available. Subscribers registered against\n * the (now-replaced) memory queue must re-subscribe after upgrade — for\n * that reason most plugins register subscribers inside their own\n * `kernel:ready` hook, which fires after this one.\n */\nexport class QueueServicePlugin implements Plugin {\n name = 'com.objectstack.service.queue';\n version = '1.1.0';\n type = 'standard';\n\n private readonly options: QueueServicePluginOptions;\n private dbAdapter?: DbQueueAdapter;\n\n constructor(options: QueueServicePluginOptions = {}) {\n this.options = { adapter: 'auto', ...options };\n }\n\n async init(ctx: PluginContext): Promise<void> {\n // Register sys_job_queue (also serves as DLQ view) so Studio can list/replay.\n try {\n ctx.getService<{ register(m: any): void }>('manifest').register({\n id: 'com.objectstack.service.queue',\n name: 'Queue Service',\n version: '1.1.0',\n type: 'plugin',\n scope: 'system',\n defaultDatasource: 'cloud',\n namespace: 'sys',\n objects: [SysJobQueue],\n });\n } catch (err) {\n ctx.logger.warn('QueueServicePlugin: manifest service unavailable; sys_job_queue not registered', err as any);\n }\n\n const choice = this.options.adapter ?? 'auto';\n\n if (choice === 'memory') {\n const q = new MemoryQueueAdapter(this.options.memory);\n ctx.registerService('queue', q);\n ctx.logger.info('QueueServicePlugin: registered MemoryQueueAdapter');\n return;\n }\n\n // auto / db — register memory placeholder, upgrade on kernel:ready\n ctx.registerService('queue', new MemoryQueueAdapter(this.options.memory));\n\n ctx.hook('kernel:ready', async () => {\n let engine: any = null;\n try { engine = ctx.getService<any>('objectql'); }\n catch { try { engine = ctx.getService<any>('data'); } catch { /* ignore */ } }\n\n if (!engine) {\n if (choice === 'db') {\n ctx.logger.warn('QueueServicePlugin: db adapter requested but no ObjectQL engine — staying on MemoryQueueAdapter');\n } else {\n ctx.logger.info('QueueServicePlugin: no ObjectQL engine — staying on MemoryQueueAdapter');\n }\n return;\n }\n\n this.dbAdapter = new DbQueueAdapter({\n engine,\n logger: ctx.logger,\n options: this.options.db,\n });\n\n try {\n (ctx as any).replaceService?.('queue', this.dbAdapter);\n this.dbAdapter.start();\n ctx.logger.info('QueueServicePlugin: upgraded to DbQueueAdapter (sys_job_queue persistence)');\n } catch (err) {\n ctx.logger.warn('QueueServicePlugin: replaceService failed; staying on MemoryQueueAdapter', err as any);\n }\n });\n }\n\n async destroy(): Promise<void> {\n await this.dbAdapter?.stop();\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { IQueueService, QueuePublishOptions, QueueMessage, QueueHandler } from '@objectstack/spec/contracts';\n\n/**\n * Configuration options for MemoryQueueAdapter.\n */\nexport interface MemoryQueueAdapterOptions {\n /** Maximum number of messages retained per queue (0 = unlimited) */\n maxQueueSize?: number;\n}\n\n/**\n * In-memory queue adapter implementing IQueueService.\n *\n * Provides synchronous in-process pub/sub delivery.\n * Suitable for single-process environments, development, and testing.\n */\nexport class MemoryQueueAdapter implements IQueueService {\n private readonly handlers = new Map<string, QueueHandler[]>();\n private readonly deadLetters: QueueMessage[] = [];\n private msgCounter = 0;\n private readonly maxQueueSize: number;\n\n constructor(options: MemoryQueueAdapterOptions = {}) {\n this.maxQueueSize = options.maxQueueSize ?? 0;\n }\n\n async publish<T = unknown>(queue: string, data: T, options?: QueuePublishOptions): Promise<string> {\n const id = `msg-${++this.msgCounter}`;\n const msg: QueueMessage<T> = {\n id,\n data,\n attempts: 0,\n timestamp: Date.now(),\n };\n\n const fns = this.handlers.get(queue) ?? [];\n if (fns.length === 0) {\n // No subscribers — retain as dead letter if within limits\n if (this.maxQueueSize === 0 || this.deadLetters.length < this.maxQueueSize) {\n this.deadLetters.push(msg);\n }\n return id;\n }\n\n const maxRetries = options?.retries ?? 0;\n for (const handler of fns) {\n let attempt = 0;\n let success = false;\n while (!success && attempt <= maxRetries) {\n try {\n msg.attempts = attempt + 1;\n await handler(msg as QueueMessage);\n success = true;\n } catch {\n attempt++;\n }\n }\n }\n\n return id;\n }\n\n async subscribe<T = unknown>(queue: string, handler: QueueHandler<T>): Promise<void> {\n const existing = this.handlers.get(queue) ?? [];\n this.handlers.set(queue, [...existing, handler as QueueHandler]);\n }\n\n async unsubscribe(queue: string): Promise<void> {\n this.handlers.delete(queue);\n }\n\n async getQueueSize(_queue: string): Promise<number> {\n // In-memory: no persistent queue depth tracking\n return 0;\n }\n\n async purge(queue: string): Promise<void> {\n this.handlers.delete(queue);\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Narrow ObjectQL engine surface used by job/queue adapters.\n * Keeps the adapter testable without booting a real kernel.\n *\n * IMPORTANT: matches the canonical engine API:\n * - find: `where:` (NOT `filter:`)\n * - update: `(table, {id, ...patch}, opts)`\n */\nexport interface JobEngine {\n find(object: string, options?: any): Promise<any[]>;\n insert(object: string, data: any, options?: any): Promise<any>;\n update(object: string, idOrData: any, dataOrOptions?: any, options?: any): Promise<any>;\n delete(object: string, options?: any): Promise<any>;\n}\n\n/** Stamped only in tests to make `now` deterministic. */\nexport interface JobClock { now(): Date }\n\nexport interface JobLogger {\n info(msg: string, meta?: unknown): void;\n warn(msg: string, meta?: unknown): void;\n error?(msg: string, meta?: unknown): void;\n}\n\nexport const SYSTEM_CTX = { isSystem: true, roles: [], permissions: [] } as const;\n\nexport function uid(prefix: string): string {\n const g: any = globalThis as any;\n if (g.crypto?.randomUUID) return `${prefix}_${g.crypto.randomUUID()}`;\n return `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;\n}\n\nexport function nowIso(clock?: JobClock): string {\n return (clock?.now() ?? new Date()).toISOString();\n}\n\nexport function parseJson<T = unknown>(raw: unknown, fallback?: T): T | undefined {\n if (raw == null) return fallback;\n if (typeof raw === 'string') {\n try { return JSON.parse(raw) as T; } catch { return fallback; }\n }\n if (typeof raw === 'object') return raw as T;\n return fallback;\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type {\n IQueueService,\n QueuePublishOptions,\n QueueMessage,\n QueueMessageRecord,\n QueueHandler,\n} from '@objectstack/spec/contracts';\nimport {\n SYSTEM_CTX,\n uid,\n nowIso,\n parseJson,\n type JobEngine,\n type JobClock,\n type JobLogger,\n} from './common.js';\n\nconst QUEUE_TABLE = 'sys_job_queue';\n\nexport interface DbQueueAdapterOptions {\n /** Polling interval for the worker loop (ms, default 1000) */\n pollIntervalMs?: number;\n /** Max messages claimed per poll tick (default 10) */\n batchSize?: number;\n /** Lease duration before another worker may reclaim (ms, default 30000) */\n leaseMs?: number;\n /** Idempotency window — how long the same key blocks re-publish (ms, default 24h) */\n idempotencyWindowMs?: number;\n /** Default maxAttempts when publish doesn't specify (default 3) */\n defaultMaxAttempts?: number;\n /** Unique identifier for this worker (default: random) */\n workerId?: string;\n /** Whether to auto-start the polling worker (default true) */\n autoStart?: boolean;\n}\n\ninterface RegisteredHandler {\n queue: string;\n fn: QueueHandler;\n}\n\n/**\n * DbQueueAdapter — durable, polling, DB-backed IQueueService.\n *\n * Persists every message to `sys_job_queue`. A polling worker leases\n * pending messages (CAS update status pending→running with a lease),\n * invokes registered subscribers, and retries with backoff on failure.\n * Messages that exceed `max_attempts` land in `status='dlq'`.\n *\n * Idempotency: publish suppresses duplicates within a configurable\n * window when `(queue, idempotencyKey)` is non-null.\n *\n * Designed for SQLite and Postgres alike — uses CAS via WHERE-clauses,\n * not row-level locking.\n */\nexport class DbQueueAdapter implements IQueueService {\n private readonly engine: JobEngine;\n private readonly logger?: JobLogger;\n private readonly clock?: JobClock;\n private readonly opts: Required<Omit<DbQueueAdapterOptions, 'workerId'>> & { workerId: string };\n\n private readonly handlers = new Map<string, RegisteredHandler[]>();\n private timer?: ReturnType<typeof setInterval>;\n private running = false;\n\n constructor(args: {\n engine: JobEngine;\n logger?: JobLogger;\n clock?: JobClock;\n options?: DbQueueAdapterOptions;\n }) {\n this.engine = args.engine;\n this.logger = args.logger;\n this.clock = args.clock;\n const o = args.options ?? {};\n this.opts = {\n pollIntervalMs: o.pollIntervalMs ?? 1000,\n batchSize: o.batchSize ?? 10,\n leaseMs: o.leaseMs ?? 30_000,\n idempotencyWindowMs: o.idempotencyWindowMs ?? 24 * 60 * 60 * 1000,\n defaultMaxAttempts: o.defaultMaxAttempts ?? 3,\n autoStart: o.autoStart ?? true,\n workerId: o.workerId ?? uid('worker'),\n };\n }\n\n // ── IQueueService ────────────────────────────────────────────────\n\n async publish<T = unknown>(\n queue: string,\n data: T,\n options?: QueuePublishOptions,\n ): Promise<string> {\n const opts = options ?? {};\n const now = this.now();\n\n // Idempotency check\n if (opts.idempotencyKey) {\n const windowStart = new Date(now.getTime() - this.opts.idempotencyWindowMs).toISOString();\n const existing = await this.engine.find(QUEUE_TABLE, {\n where: {\n queue,\n idempotency_key: opts.idempotencyKey,\n // Only block if not yet terminal — completed/dlq dedup is by window via created_at\n },\n limit: 5,\n context: SYSTEM_CTX,\n });\n const blocking = (existing ?? []).find((row: any) => {\n if (row.status === 'pending' || row.status === 'running') return true;\n return String(row.created_at ?? '') >= windowStart;\n });\n if (blocking) return String(blocking.id);\n }\n\n const id = uid('msg');\n const scheduledFor = opts.scheduledFor\n ? new Date(opts.scheduledFor).toISOString()\n : opts.delay\n ? new Date(now.getTime() + opts.delay).toISOString()\n : now.toISOString();\n\n const maxAttempts = opts.maxAttempts\n ?? (opts.retries != null ? opts.retries + 1 : this.opts.defaultMaxAttempts);\n const backoff = opts.backoff ?? { type: 'exponential' as const, delayMs: 1000 };\n\n await this.engine.insert(QUEUE_TABLE, {\n id,\n queue,\n idempotency_key: opts.idempotencyKey ?? null,\n payload_json: JSON.stringify(data ?? null),\n metadata_json: opts.metadata ? JSON.stringify(opts.metadata) : null,\n status: 'pending',\n priority: opts.priority ?? 100,\n attempts: 0,\n max_attempts: maxAttempts,\n backoff_type: backoff.type,\n backoff_delay_ms: backoff.delayMs,\n backoff_max_delay_ms: backoff.maxDelayMs ?? null,\n scheduled_for: scheduledFor,\n created_at: now.toISOString(),\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n\n return id;\n }\n\n async subscribe<T = unknown>(queue: string, handler: QueueHandler<T>): Promise<void> {\n const existing = this.handlers.get(queue) ?? [];\n existing.push({ queue, fn: handler as QueueHandler });\n this.handlers.set(queue, existing);\n if (this.opts.autoStart) this.start();\n }\n\n async unsubscribe(queue: string): Promise<void> {\n this.handlers.delete(queue);\n }\n\n async getQueueSize(queue: string): Promise<number> {\n const rows = await this.engine.find(QUEUE_TABLE, {\n where: { queue, status: 'pending' },\n limit: 10_000,\n context: SYSTEM_CTX,\n });\n return rows?.length ?? 0;\n }\n\n async purge(queue: string): Promise<void> {\n const rows = await this.engine.find(QUEUE_TABLE, {\n where: { queue, status: 'pending' },\n limit: 10_000,\n context: SYSTEM_CTX,\n });\n for (const row of rows ?? []) {\n try { await this.engine.delete(QUEUE_TABLE, { id: row.id, context: SYSTEM_CTX }); }\n catch (err) { this.logger?.warn?.('DbQueueAdapter: purge delete failed', err as any); }\n }\n }\n\n async listFailed(\n queue?: string,\n options?: { limit?: number; offset?: number },\n ): Promise<QueueMessageRecord[]> {\n const where: any = { status: 'dlq' };\n if (queue) where.queue = queue;\n const rows = await this.engine.find(QUEUE_TABLE, {\n where,\n limit: options?.limit ?? 100,\n offset: options?.offset,\n orderBy: [{ field: 'created_at', order: 'desc' }],\n context: SYSTEM_CTX,\n });\n return (rows ?? []).map((r: any) => this.rowToRecord(r));\n }\n\n async replay(messageId: string): Promise<void> {\n const row = await this.loadById(messageId);\n if (!row) throw new Error(`MESSAGE_NOT_FOUND: ${messageId}`);\n if (row.status !== 'dlq' && row.status !== 'failed') {\n throw new Error(`INVALID_STATE: cannot replay message in status=${row.status}`);\n }\n const now = this.now();\n await this.engine.update(QUEUE_TABLE, {\n id: messageId,\n status: 'pending',\n attempts: 0,\n last_error: null,\n locked_by: null,\n locked_until: null,\n scheduled_for: now.toISOString(),\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n }\n\n async purgeFailed(messageId: string): Promise<void> {\n const row = await this.loadById(messageId);\n if (!row) return;\n if (row.status !== 'dlq' && row.status !== 'failed') {\n throw new Error(`INVALID_STATE: cannot purge message in status=${row.status}`);\n }\n await this.engine.delete(QUEUE_TABLE, { id: messageId, context: SYSTEM_CTX });\n }\n\n // ── Worker lifecycle ─────────────────────────────────────────────\n\n start(): void {\n if (this.timer) return;\n this.timer = setInterval(() => {\n if (this.running) return;\n this.running = true;\n this.pollOnce()\n .catch((err) => { this.logger?.warn?.('DbQueueAdapter: poll tick failed', err); })\n .finally(() => { this.running = false; });\n }, this.opts.pollIntervalMs);\n (this.timer as any)?.unref?.();\n }\n\n async stop(): Promise<void> {\n if (this.timer) { clearInterval(this.timer); this.timer = undefined; }\n }\n\n /** Test-friendly synchronous poll. */\n async pollOnce(): Promise<number> {\n const queues = [...this.handlers.keys()];\n if (queues.length === 0) return 0;\n\n let processed = 0;\n for (const queue of queues) {\n const claimed = await this.claimBatch(queue, this.opts.batchSize);\n for (const row of claimed) {\n await this.dispatch(row);\n processed++;\n }\n }\n return processed;\n }\n\n // ── Internals ────────────────────────────────────────────────────\n\n private async claimBatch(queue: string, max: number): Promise<any[]> {\n const now = this.now();\n const candidates = await this.engine.find(QUEUE_TABLE, {\n where: { queue, status: 'pending' },\n limit: max * 3, // over-fetch in case of CAS contention\n orderBy: [\n { field: 'priority', order: 'asc' },\n { field: 'scheduled_for', order: 'asc' },\n ],\n context: SYSTEM_CTX,\n });\n\n const out: any[] = [];\n for (const row of candidates ?? []) {\n if (out.length >= max) break;\n const sched = row.scheduled_for ? new Date(row.scheduled_for).getTime() : 0;\n if (sched > now.getTime()) continue;\n // Honor existing lease\n const lockedUntil = row.locked_until ? new Date(row.locked_until).getTime() : 0;\n if (row.locked_by && lockedUntil > now.getTime()) continue;\n\n // CAS — only update if still pending (best-effort with engine.update which\n // typically does row-level update by id; concurrent workers will overwrite\n // each other but the dispatcher tolerates duplicate delivery via attempts).\n try {\n await this.engine.update(QUEUE_TABLE, {\n id: row.id,\n status: 'running',\n locked_by: this.opts.workerId,\n locked_until: new Date(now.getTime() + this.opts.leaseMs).toISOString(),\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n out.push({ ...row, status: 'running' });\n } catch (err) {\n this.logger?.warn?.('DbQueueAdapter: claim CAS failed', err as any);\n }\n }\n return out;\n }\n\n private async dispatch(row: any): Promise<void> {\n const handlers = this.handlers.get(row.queue) ?? [];\n if (handlers.length === 0) {\n // No handler — release lease so another process can pick it up\n await this.releasePending(row.id);\n return;\n }\n\n const msg: QueueMessage = {\n id: String(row.id),\n data: parseJson(row.payload_json),\n attempts: (row.attempts ?? 0) + 1,\n timestamp: row.created_at ? new Date(row.created_at).getTime() : Date.now(),\n };\n\n let success = true;\n let lastError: string | undefined;\n for (const h of handlers) {\n try { await h.fn(msg); }\n catch (err) {\n success = false;\n lastError = err instanceof Error ? err.message : String(err);\n this.logger?.warn?.(`DbQueueAdapter: handler failed on ${row.queue}`, err as any);\n break;\n }\n }\n\n const now = this.now();\n if (success) {\n await this.engine.update(QUEUE_TABLE, {\n id: row.id,\n status: 'completed',\n attempts: msg.attempts,\n completed_at: now.toISOString(),\n locked_by: null,\n locked_until: null,\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n return;\n }\n\n const attempts = msg.attempts;\n const max = row.max_attempts ?? this.opts.defaultMaxAttempts;\n if (attempts >= max) {\n await this.engine.update(QUEUE_TABLE, {\n id: row.id,\n status: 'dlq',\n attempts,\n last_error: lastError ?? 'unknown error',\n completed_at: now.toISOString(),\n locked_by: null,\n locked_until: null,\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n return;\n }\n\n const backoffMs = this.computeBackoff(row, attempts);\n await this.engine.update(QUEUE_TABLE, {\n id: row.id,\n status: 'pending',\n attempts,\n last_error: lastError ?? 'unknown error',\n scheduled_for: new Date(now.getTime() + backoffMs).toISOString(),\n locked_by: null,\n locked_until: null,\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n }\n\n private computeBackoff(row: any, attempt: number): number {\n const base = row.backoff_delay_ms ?? 1000;\n const cap = row.backoff_max_delay_ms ?? undefined;\n if ((row.backoff_type ?? 'exponential') === 'fixed') return base;\n const exp = base * Math.pow(2, Math.max(0, attempt - 1));\n return cap ? Math.min(exp, cap) : exp;\n }\n\n private async releasePending(id: string): Promise<void> {\n const now = this.now();\n try {\n await this.engine.update(QUEUE_TABLE, {\n id,\n status: 'pending',\n locked_by: null,\n locked_until: null,\n scheduled_for: new Date(now.getTime() + this.opts.pollIntervalMs * 5).toISOString(),\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n } catch (err) {\n this.logger?.warn?.('DbQueueAdapter: release failed', err as any);\n }\n }\n\n private async loadById(id: string): Promise<any | null> {\n const rows = await this.engine.find(QUEUE_TABLE, {\n where: { id },\n limit: 1,\n context: SYSTEM_CTX,\n });\n return rows?.[0] ?? null;\n }\n\n private rowToRecord(r: any): QueueMessageRecord {\n return {\n id: String(r.id),\n queue: String(r.queue),\n data: parseJson(r.payload_json),\n status: r.status,\n attempts: r.attempts ?? 0,\n maxAttempts: r.max_attempts ?? this.opts.defaultMaxAttempts,\n scheduledFor: r.scheduled_for ?? undefined,\n lockedBy: r.locked_by ?? undefined,\n lockedUntil: r.locked_until ?? undefined,\n lastError: r.last_error ?? undefined,\n idempotencyKey: r.idempotency_key ?? undefined,\n metadata: parseJson(r.metadata_json),\n createdAt: r.created_at ?? nowIso(this.clock),\n updatedAt: r.updated_at ?? undefined,\n completedAt: r.completed_at ?? undefined,\n };\n }\n\n private now(): Date {\n return this.clock?.now() ?? new Date();\n }\n}\n"],"mappings":"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","names":[]}
package/dist/index.d.cts CHANGED
@@ -128,15 +128,12 @@ interface QueueServicePluginOptions {
128
128
  * - 'auto' (default): use DbQueueAdapter when objectql engine available, else MemoryQueueAdapter
129
129
  * - 'db': require objectql; persists messages, retries, and DLQ to sys_job_queue
130
130
  * - 'memory': in-process MemoryQueueAdapter (non-durable, dev/test)
131
- * - 'bullmq': reserved for M10.43 (throws today)
132
131
  */
133
- adapter?: 'auto' | 'db' | 'memory' | 'bullmq';
132
+ adapter?: 'auto' | 'db' | 'memory';
134
133
  /** Options for the memory queue adapter */
135
134
  memory?: MemoryQueueAdapterOptions;
136
135
  /** Options for the DB adapter (polling, batch, lease, idempotency window…) */
137
136
  db?: DbQueueAdapterOptions;
138
- /** Redis connection URL (reserved for bullmq) */
139
- redisUrl?: string;
140
137
  }
141
138
  /**
142
139
  * QueueServicePlugin — Production IQueueService implementation.
@@ -159,43 +156,4 @@ declare class QueueServicePlugin implements Plugin {
159
156
  destroy(): Promise<void>;
160
157
  }
161
158
 
162
- /**
163
- * Configuration for the BullMQ queue adapter.
164
- */
165
- interface BullMQQueueAdapterOptions {
166
- /** Redis connection URL (e.g. 'redis://localhost:6379') */
167
- redisUrl: string;
168
- /** Default job options */
169
- defaultJobOptions?: {
170
- /** Number of retry attempts */
171
- attempts?: number;
172
- /** Backoff strategy */
173
- backoff?: {
174
- type: 'fixed' | 'exponential';
175
- delay: number;
176
- };
177
- };
178
- }
179
- /**
180
- * BullMQ queue adapter skeleton implementing IQueueService.
181
- *
182
- * This is a placeholder for future BullMQ integration.
183
- * Concrete implementation will use the `bullmq` package.
184
- *
185
- * @example
186
- * ```ts
187
- * const queue = new BullMQQueueAdapter({ redisUrl: 'redis://localhost:6379' });
188
- * await queue.publish('orders', { orderId: 123 });
189
- * ```
190
- */
191
- declare class BullMQQueueAdapter implements IQueueService {
192
- private readonly redisUrl;
193
- constructor(options: BullMQQueueAdapterOptions);
194
- publish<T = unknown>(_queue: string, _data: T, _options?: QueuePublishOptions): Promise<string>;
195
- subscribe<T = unknown>(_queue: string, _handler: QueueHandler<T>): Promise<void>;
196
- unsubscribe(_queue: string): Promise<void>;
197
- getQueueSize(_queue: string): Promise<number>;
198
- purge(_queue: string): Promise<void>;
199
- }
200
-
201
- export { BullMQQueueAdapter, type BullMQQueueAdapterOptions, DbQueueAdapter, type DbQueueAdapterOptions, type JobClock, type JobEngine, type JobLogger, MemoryQueueAdapter, type MemoryQueueAdapterOptions, QueueServicePlugin, type QueueServicePluginOptions };
159
+ export { DbQueueAdapter, type DbQueueAdapterOptions, type JobClock, type JobEngine, type JobLogger, MemoryQueueAdapter, type MemoryQueueAdapterOptions, QueueServicePlugin, type QueueServicePluginOptions };
package/dist/index.d.ts CHANGED
@@ -128,15 +128,12 @@ interface QueueServicePluginOptions {
128
128
  * - 'auto' (default): use DbQueueAdapter when objectql engine available, else MemoryQueueAdapter
129
129
  * - 'db': require objectql; persists messages, retries, and DLQ to sys_job_queue
130
130
  * - 'memory': in-process MemoryQueueAdapter (non-durable, dev/test)
131
- * - 'bullmq': reserved for M10.43 (throws today)
132
131
  */
133
- adapter?: 'auto' | 'db' | 'memory' | 'bullmq';
132
+ adapter?: 'auto' | 'db' | 'memory';
134
133
  /** Options for the memory queue adapter */
135
134
  memory?: MemoryQueueAdapterOptions;
136
135
  /** Options for the DB adapter (polling, batch, lease, idempotency window…) */
137
136
  db?: DbQueueAdapterOptions;
138
- /** Redis connection URL (reserved for bullmq) */
139
- redisUrl?: string;
140
137
  }
141
138
  /**
142
139
  * QueueServicePlugin — Production IQueueService implementation.
@@ -159,43 +156,4 @@ declare class QueueServicePlugin implements Plugin {
159
156
  destroy(): Promise<void>;
160
157
  }
161
158
 
162
- /**
163
- * Configuration for the BullMQ queue adapter.
164
- */
165
- interface BullMQQueueAdapterOptions {
166
- /** Redis connection URL (e.g. 'redis://localhost:6379') */
167
- redisUrl: string;
168
- /** Default job options */
169
- defaultJobOptions?: {
170
- /** Number of retry attempts */
171
- attempts?: number;
172
- /** Backoff strategy */
173
- backoff?: {
174
- type: 'fixed' | 'exponential';
175
- delay: number;
176
- };
177
- };
178
- }
179
- /**
180
- * BullMQ queue adapter skeleton implementing IQueueService.
181
- *
182
- * This is a placeholder for future BullMQ integration.
183
- * Concrete implementation will use the `bullmq` package.
184
- *
185
- * @example
186
- * ```ts
187
- * const queue = new BullMQQueueAdapter({ redisUrl: 'redis://localhost:6379' });
188
- * await queue.publish('orders', { orderId: 123 });
189
- * ```
190
- */
191
- declare class BullMQQueueAdapter implements IQueueService {
192
- private readonly redisUrl;
193
- constructor(options: BullMQQueueAdapterOptions);
194
- publish<T = unknown>(_queue: string, _data: T, _options?: QueuePublishOptions): Promise<string>;
195
- subscribe<T = unknown>(_queue: string, _handler: QueueHandler<T>): Promise<void>;
196
- unsubscribe(_queue: string): Promise<void>;
197
- getQueueSize(_queue: string): Promise<number>;
198
- purge(_queue: string): Promise<void>;
199
- }
200
-
201
- export { BullMQQueueAdapter, type BullMQQueueAdapterOptions, DbQueueAdapter, type DbQueueAdapterOptions, type JobClock, type JobEngine, type JobLogger, MemoryQueueAdapter, type MemoryQueueAdapterOptions, QueueServicePlugin, type QueueServicePluginOptions };
159
+ export { DbQueueAdapter, type DbQueueAdapterOptions, type JobClock, type JobEngine, type JobLogger, MemoryQueueAdapter, type MemoryQueueAdapterOptions, QueueServicePlugin, type QueueServicePluginOptions };
package/dist/index.js CHANGED
@@ -422,11 +422,6 @@ var QueueServicePlugin = class {
422
422
  ctx.logger.warn("QueueServicePlugin: manifest service unavailable; sys_job_queue not registered", err);
423
423
  }
424
424
  const choice = this.options.adapter ?? "auto";
425
- if (choice === "bullmq") {
426
- throw new Error(
427
- 'BullMQ queue adapter is not yet implemented (M10.43). Use adapter: "auto", "db", or "memory", or provide a custom IQueueService.'
428
- );
429
- }
430
425
  if (choice === "memory") {
431
426
  const q = new MemoryQueueAdapter(this.options.memory);
432
427
  ctx.registerService("queue", q);
@@ -470,30 +465,7 @@ var QueueServicePlugin = class {
470
465
  await this.dbAdapter?.stop();
471
466
  }
472
467
  };
473
-
474
- // src/bullmq-queue-adapter.ts
475
- var BullMQQueueAdapter = class {
476
- constructor(options) {
477
- this.redisUrl = options.redisUrl;
478
- }
479
- async publish(_queue, _data, _options) {
480
- throw new Error(`BullMQQueueAdapter not yet implemented (url: ${this.redisUrl})`);
481
- }
482
- async subscribe(_queue, _handler) {
483
- throw new Error("BullMQQueueAdapter not yet implemented");
484
- }
485
- async unsubscribe(_queue) {
486
- throw new Error("BullMQQueueAdapter not yet implemented");
487
- }
488
- async getQueueSize(_queue) {
489
- throw new Error("BullMQQueueAdapter not yet implemented");
490
- }
491
- async purge(_queue) {
492
- throw new Error("BullMQQueueAdapter not yet implemented");
493
- }
494
- };
495
468
  export {
496
- BullMQQueueAdapter,
497
469
  DbQueueAdapter,
498
470
  MemoryQueueAdapter,
499
471
  QueueServicePlugin
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/queue-service-plugin.ts","../src/memory-queue-adapter.ts","../src/common.ts","../src/db-queue-adapter.ts","../src/bullmq-queue-adapter.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { Plugin, PluginContext } from '@objectstack/core';\nimport { SysJobQueue } from '@objectstack/platform-objects/audit';\nimport { MemoryQueueAdapter } from './memory-queue-adapter.js';\nimport type { MemoryQueueAdapterOptions } from './memory-queue-adapter.js';\nimport { DbQueueAdapter } from './db-queue-adapter.js';\nimport type { DbQueueAdapterOptions } from './db-queue-adapter.js';\n\n/**\n * Configuration options for the QueueServicePlugin.\n */\nexport interface QueueServicePluginOptions {\n /**\n * Queue adapter type.\n * - 'auto' (default): use DbQueueAdapter when objectql engine available, else MemoryQueueAdapter\n * - 'db': require objectql; persists messages, retries, and DLQ to sys_job_queue\n * - 'memory': in-process MemoryQueueAdapter (non-durable, dev/test)\n * - 'bullmq': reserved for M10.43 (throws today)\n */\n adapter?: 'auto' | 'db' | 'memory' | 'bullmq';\n /** Options for the memory queue adapter */\n memory?: MemoryQueueAdapterOptions;\n /** Options for the DB adapter (polling, batch, lease, idempotency window…) */\n db?: DbQueueAdapterOptions;\n /** Redis connection URL (reserved for bullmq) */\n redisUrl?: string;\n}\n\n/**\n * QueueServicePlugin — Production IQueueService implementation.\n *\n * Default: registers MemoryQueueAdapter synchronously so producers can\n * publish during plugin init; upgrades to DbQueueAdapter on `kernel:ready`\n * when an ObjectQL engine is available. Subscribers registered against\n * the (now-replaced) memory queue must re-subscribe after upgrade — for\n * that reason most plugins register subscribers inside their own\n * `kernel:ready` hook, which fires after this one.\n */\nexport class QueueServicePlugin implements Plugin {\n name = 'com.objectstack.service.queue';\n version = '1.1.0';\n type = 'standard';\n\n private readonly options: QueueServicePluginOptions;\n private dbAdapter?: DbQueueAdapter;\n\n constructor(options: QueueServicePluginOptions = {}) {\n this.options = { adapter: 'auto', ...options };\n }\n\n async init(ctx: PluginContext): Promise<void> {\n // Register sys_job_queue (also serves as DLQ view) so Studio can list/replay.\n try {\n ctx.getService<{ register(m: any): void }>('manifest').register({\n id: 'com.objectstack.service.queue',\n name: 'Queue Service',\n version: '1.1.0',\n type: 'plugin',\n scope: 'system',\n defaultDatasource: 'cloud',\n namespace: 'sys',\n objects: [SysJobQueue],\n });\n } catch (err) {\n ctx.logger.warn('QueueServicePlugin: manifest service unavailable; sys_job_queue not registered', err as any);\n }\n\n const choice = this.options.adapter ?? 'auto';\n\n if (choice === 'bullmq') {\n throw new Error(\n 'BullMQ queue adapter is not yet implemented (M10.43). ' +\n 'Use adapter: \"auto\", \"db\", or \"memory\", or provide a custom IQueueService.',\n );\n }\n\n if (choice === 'memory') {\n const q = new MemoryQueueAdapter(this.options.memory);\n ctx.registerService('queue', q);\n ctx.logger.info('QueueServicePlugin: registered MemoryQueueAdapter');\n return;\n }\n\n // auto / db — register memory placeholder, upgrade on kernel:ready\n ctx.registerService('queue', new MemoryQueueAdapter(this.options.memory));\n\n ctx.hook('kernel:ready', async () => {\n let engine: any = null;\n try { engine = ctx.getService<any>('objectql'); }\n catch { try { engine = ctx.getService<any>('data'); } catch { /* ignore */ } }\n\n if (!engine) {\n if (choice === 'db') {\n ctx.logger.warn('QueueServicePlugin: db adapter requested but no ObjectQL engine — staying on MemoryQueueAdapter');\n } else {\n ctx.logger.info('QueueServicePlugin: no ObjectQL engine — staying on MemoryQueueAdapter');\n }\n return;\n }\n\n this.dbAdapter = new DbQueueAdapter({\n engine,\n logger: ctx.logger,\n options: this.options.db,\n });\n\n try {\n (ctx as any).replaceService?.('queue', this.dbAdapter);\n this.dbAdapter.start();\n ctx.logger.info('QueueServicePlugin: upgraded to DbQueueAdapter (sys_job_queue persistence)');\n } catch (err) {\n ctx.logger.warn('QueueServicePlugin: replaceService failed; staying on MemoryQueueAdapter', err as any);\n }\n });\n }\n\n async destroy(): Promise<void> {\n await this.dbAdapter?.stop();\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { IQueueService, QueuePublishOptions, QueueMessage, QueueHandler } from '@objectstack/spec/contracts';\n\n/**\n * Configuration options for MemoryQueueAdapter.\n */\nexport interface MemoryQueueAdapterOptions {\n /** Maximum number of messages retained per queue (0 = unlimited) */\n maxQueueSize?: number;\n}\n\n/**\n * In-memory queue adapter implementing IQueueService.\n *\n * Provides synchronous in-process pub/sub delivery.\n * Suitable for single-process environments, development, and testing.\n */\nexport class MemoryQueueAdapter implements IQueueService {\n private readonly handlers = new Map<string, QueueHandler[]>();\n private readonly deadLetters: QueueMessage[] = [];\n private msgCounter = 0;\n private readonly maxQueueSize: number;\n\n constructor(options: MemoryQueueAdapterOptions = {}) {\n this.maxQueueSize = options.maxQueueSize ?? 0;\n }\n\n async publish<T = unknown>(queue: string, data: T, options?: QueuePublishOptions): Promise<string> {\n const id = `msg-${++this.msgCounter}`;\n const msg: QueueMessage<T> = {\n id,\n data,\n attempts: 0,\n timestamp: Date.now(),\n };\n\n const fns = this.handlers.get(queue) ?? [];\n if (fns.length === 0) {\n // No subscribers — retain as dead letter if within limits\n if (this.maxQueueSize === 0 || this.deadLetters.length < this.maxQueueSize) {\n this.deadLetters.push(msg);\n }\n return id;\n }\n\n const maxRetries = options?.retries ?? 0;\n for (const handler of fns) {\n let attempt = 0;\n let success = false;\n while (!success && attempt <= maxRetries) {\n try {\n msg.attempts = attempt + 1;\n await handler(msg as QueueMessage);\n success = true;\n } catch {\n attempt++;\n }\n }\n }\n\n return id;\n }\n\n async subscribe<T = unknown>(queue: string, handler: QueueHandler<T>): Promise<void> {\n const existing = this.handlers.get(queue) ?? [];\n this.handlers.set(queue, [...existing, handler as QueueHandler]);\n }\n\n async unsubscribe(queue: string): Promise<void> {\n this.handlers.delete(queue);\n }\n\n async getQueueSize(_queue: string): Promise<number> {\n // In-memory: no persistent queue depth tracking\n return 0;\n }\n\n async purge(queue: string): Promise<void> {\n this.handlers.delete(queue);\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Narrow ObjectQL engine surface used by job/queue adapters.\n * Keeps the adapter testable without booting a real kernel.\n *\n * IMPORTANT: matches the canonical engine API:\n * - find: `where:` (NOT `filter:`)\n * - update: `(table, {id, ...patch}, opts)`\n */\nexport interface JobEngine {\n find(object: string, options?: any): Promise<any[]>;\n insert(object: string, data: any, options?: any): Promise<any>;\n update(object: string, idOrData: any, dataOrOptions?: any, options?: any): Promise<any>;\n delete(object: string, options?: any): Promise<any>;\n}\n\n/** Stamped only in tests to make `now` deterministic. */\nexport interface JobClock { now(): Date }\n\nexport interface JobLogger {\n info(msg: string, meta?: unknown): void;\n warn(msg: string, meta?: unknown): void;\n error?(msg: string, meta?: unknown): void;\n}\n\nexport const SYSTEM_CTX = { isSystem: true, roles: [], permissions: [] } as const;\n\nexport function uid(prefix: string): string {\n const g: any = globalThis as any;\n if (g.crypto?.randomUUID) return `${prefix}_${g.crypto.randomUUID()}`;\n return `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;\n}\n\nexport function nowIso(clock?: JobClock): string {\n return (clock?.now() ?? new Date()).toISOString();\n}\n\nexport function parseJson<T = unknown>(raw: unknown, fallback?: T): T | undefined {\n if (raw == null) return fallback;\n if (typeof raw === 'string') {\n try { return JSON.parse(raw) as T; } catch { return fallback; }\n }\n if (typeof raw === 'object') return raw as T;\n return fallback;\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type {\n IQueueService,\n QueuePublishOptions,\n QueueMessage,\n QueueMessageRecord,\n QueueHandler,\n} from '@objectstack/spec/contracts';\nimport {\n SYSTEM_CTX,\n uid,\n nowIso,\n parseJson,\n type JobEngine,\n type JobClock,\n type JobLogger,\n} from './common.js';\n\nconst QUEUE_TABLE = 'sys_job_queue';\n\nexport interface DbQueueAdapterOptions {\n /** Polling interval for the worker loop (ms, default 1000) */\n pollIntervalMs?: number;\n /** Max messages claimed per poll tick (default 10) */\n batchSize?: number;\n /** Lease duration before another worker may reclaim (ms, default 30000) */\n leaseMs?: number;\n /** Idempotency window — how long the same key blocks re-publish (ms, default 24h) */\n idempotencyWindowMs?: number;\n /** Default maxAttempts when publish doesn't specify (default 3) */\n defaultMaxAttempts?: number;\n /** Unique identifier for this worker (default: random) */\n workerId?: string;\n /** Whether to auto-start the polling worker (default true) */\n autoStart?: boolean;\n}\n\ninterface RegisteredHandler {\n queue: string;\n fn: QueueHandler;\n}\n\n/**\n * DbQueueAdapter — durable, polling, DB-backed IQueueService.\n *\n * Persists every message to `sys_job_queue`. A polling worker leases\n * pending messages (CAS update status pending→running with a lease),\n * invokes registered subscribers, and retries with backoff on failure.\n * Messages that exceed `max_attempts` land in `status='dlq'`.\n *\n * Idempotency: publish suppresses duplicates within a configurable\n * window when `(queue, idempotencyKey)` is non-null.\n *\n * Designed for SQLite and Postgres alike — uses CAS via WHERE-clauses,\n * not row-level locking.\n */\nexport class DbQueueAdapter implements IQueueService {\n private readonly engine: JobEngine;\n private readonly logger?: JobLogger;\n private readonly clock?: JobClock;\n private readonly opts: Required<Omit<DbQueueAdapterOptions, 'workerId'>> & { workerId: string };\n\n private readonly handlers = new Map<string, RegisteredHandler[]>();\n private timer?: ReturnType<typeof setInterval>;\n private running = false;\n\n constructor(args: {\n engine: JobEngine;\n logger?: JobLogger;\n clock?: JobClock;\n options?: DbQueueAdapterOptions;\n }) {\n this.engine = args.engine;\n this.logger = args.logger;\n this.clock = args.clock;\n const o = args.options ?? {};\n this.opts = {\n pollIntervalMs: o.pollIntervalMs ?? 1000,\n batchSize: o.batchSize ?? 10,\n leaseMs: o.leaseMs ?? 30_000,\n idempotencyWindowMs: o.idempotencyWindowMs ?? 24 * 60 * 60 * 1000,\n defaultMaxAttempts: o.defaultMaxAttempts ?? 3,\n autoStart: o.autoStart ?? true,\n workerId: o.workerId ?? uid('worker'),\n };\n }\n\n // ── IQueueService ────────────────────────────────────────────────\n\n async publish<T = unknown>(\n queue: string,\n data: T,\n options?: QueuePublishOptions,\n ): Promise<string> {\n const opts = options ?? {};\n const now = this.now();\n\n // Idempotency check\n if (opts.idempotencyKey) {\n const windowStart = new Date(now.getTime() - this.opts.idempotencyWindowMs).toISOString();\n const existing = await this.engine.find(QUEUE_TABLE, {\n where: {\n queue,\n idempotency_key: opts.idempotencyKey,\n // Only block if not yet terminal — completed/dlq dedup is by window via created_at\n },\n limit: 5,\n context: SYSTEM_CTX,\n });\n const blocking = (existing ?? []).find((row: any) => {\n if (row.status === 'pending' || row.status === 'running') return true;\n return String(row.created_at ?? '') >= windowStart;\n });\n if (blocking) return String(blocking.id);\n }\n\n const id = uid('msg');\n const scheduledFor = opts.scheduledFor\n ? new Date(opts.scheduledFor).toISOString()\n : opts.delay\n ? new Date(now.getTime() + opts.delay).toISOString()\n : now.toISOString();\n\n const maxAttempts = opts.maxAttempts\n ?? (opts.retries != null ? opts.retries + 1 : this.opts.defaultMaxAttempts);\n const backoff = opts.backoff ?? { type: 'exponential' as const, delayMs: 1000 };\n\n await this.engine.insert(QUEUE_TABLE, {\n id,\n queue,\n idempotency_key: opts.idempotencyKey ?? null,\n payload_json: JSON.stringify(data ?? null),\n metadata_json: opts.metadata ? JSON.stringify(opts.metadata) : null,\n status: 'pending',\n priority: opts.priority ?? 100,\n attempts: 0,\n max_attempts: maxAttempts,\n backoff_type: backoff.type,\n backoff_delay_ms: backoff.delayMs,\n backoff_max_delay_ms: backoff.maxDelayMs ?? null,\n scheduled_for: scheduledFor,\n created_at: now.toISOString(),\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n\n return id;\n }\n\n async subscribe<T = unknown>(queue: string, handler: QueueHandler<T>): Promise<void> {\n const existing = this.handlers.get(queue) ?? [];\n existing.push({ queue, fn: handler as QueueHandler });\n this.handlers.set(queue, existing);\n if (this.opts.autoStart) this.start();\n }\n\n async unsubscribe(queue: string): Promise<void> {\n this.handlers.delete(queue);\n }\n\n async getQueueSize(queue: string): Promise<number> {\n const rows = await this.engine.find(QUEUE_TABLE, {\n where: { queue, status: 'pending' },\n limit: 10_000,\n context: SYSTEM_CTX,\n });\n return rows?.length ?? 0;\n }\n\n async purge(queue: string): Promise<void> {\n const rows = await this.engine.find(QUEUE_TABLE, {\n where: { queue, status: 'pending' },\n limit: 10_000,\n context: SYSTEM_CTX,\n });\n for (const row of rows ?? []) {\n try { await this.engine.delete(QUEUE_TABLE, { id: row.id, context: SYSTEM_CTX }); }\n catch (err) { this.logger?.warn?.('DbQueueAdapter: purge delete failed', err as any); }\n }\n }\n\n async listFailed(\n queue?: string,\n options?: { limit?: number; offset?: number },\n ): Promise<QueueMessageRecord[]> {\n const where: any = { status: 'dlq' };\n if (queue) where.queue = queue;\n const rows = await this.engine.find(QUEUE_TABLE, {\n where,\n limit: options?.limit ?? 100,\n offset: options?.offset,\n orderBy: [{ field: 'created_at', order: 'desc' }],\n context: SYSTEM_CTX,\n });\n return (rows ?? []).map((r: any) => this.rowToRecord(r));\n }\n\n async replay(messageId: string): Promise<void> {\n const row = await this.loadById(messageId);\n if (!row) throw new Error(`MESSAGE_NOT_FOUND: ${messageId}`);\n if (row.status !== 'dlq' && row.status !== 'failed') {\n throw new Error(`INVALID_STATE: cannot replay message in status=${row.status}`);\n }\n const now = this.now();\n await this.engine.update(QUEUE_TABLE, {\n id: messageId,\n status: 'pending',\n attempts: 0,\n last_error: null,\n locked_by: null,\n locked_until: null,\n scheduled_for: now.toISOString(),\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n }\n\n async purgeFailed(messageId: string): Promise<void> {\n const row = await this.loadById(messageId);\n if (!row) return;\n if (row.status !== 'dlq' && row.status !== 'failed') {\n throw new Error(`INVALID_STATE: cannot purge message in status=${row.status}`);\n }\n await this.engine.delete(QUEUE_TABLE, { id: messageId, context: SYSTEM_CTX });\n }\n\n // ── Worker lifecycle ─────────────────────────────────────────────\n\n start(): void {\n if (this.timer) return;\n this.timer = setInterval(() => {\n if (this.running) return;\n this.running = true;\n this.pollOnce()\n .catch((err) => { this.logger?.warn?.('DbQueueAdapter: poll tick failed', err); })\n .finally(() => { this.running = false; });\n }, this.opts.pollIntervalMs);\n (this.timer as any)?.unref?.();\n }\n\n async stop(): Promise<void> {\n if (this.timer) { clearInterval(this.timer); this.timer = undefined; }\n }\n\n /** Test-friendly synchronous poll. */\n async pollOnce(): Promise<number> {\n const queues = [...this.handlers.keys()];\n if (queues.length === 0) return 0;\n\n let processed = 0;\n for (const queue of queues) {\n const claimed = await this.claimBatch(queue, this.opts.batchSize);\n for (const row of claimed) {\n await this.dispatch(row);\n processed++;\n }\n }\n return processed;\n }\n\n // ── Internals ────────────────────────────────────────────────────\n\n private async claimBatch(queue: string, max: number): Promise<any[]> {\n const now = this.now();\n const candidates = await this.engine.find(QUEUE_TABLE, {\n where: { queue, status: 'pending' },\n limit: max * 3, // over-fetch in case of CAS contention\n orderBy: [\n { field: 'priority', order: 'asc' },\n { field: 'scheduled_for', order: 'asc' },\n ],\n context: SYSTEM_CTX,\n });\n\n const out: any[] = [];\n for (const row of candidates ?? []) {\n if (out.length >= max) break;\n const sched = row.scheduled_for ? new Date(row.scheduled_for).getTime() : 0;\n if (sched > now.getTime()) continue;\n // Honor existing lease\n const lockedUntil = row.locked_until ? new Date(row.locked_until).getTime() : 0;\n if (row.locked_by && lockedUntil > now.getTime()) continue;\n\n // CAS — only update if still pending (best-effort with engine.update which\n // typically does row-level update by id; concurrent workers will overwrite\n // each other but the dispatcher tolerates duplicate delivery via attempts).\n try {\n await this.engine.update(QUEUE_TABLE, {\n id: row.id,\n status: 'running',\n locked_by: this.opts.workerId,\n locked_until: new Date(now.getTime() + this.opts.leaseMs).toISOString(),\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n out.push({ ...row, status: 'running' });\n } catch (err) {\n this.logger?.warn?.('DbQueueAdapter: claim CAS failed', err as any);\n }\n }\n return out;\n }\n\n private async dispatch(row: any): Promise<void> {\n const handlers = this.handlers.get(row.queue) ?? [];\n if (handlers.length === 0) {\n // No handler — release lease so another process can pick it up\n await this.releasePending(row.id);\n return;\n }\n\n const msg: QueueMessage = {\n id: String(row.id),\n data: parseJson(row.payload_json),\n attempts: (row.attempts ?? 0) + 1,\n timestamp: row.created_at ? new Date(row.created_at).getTime() : Date.now(),\n };\n\n let success = true;\n let lastError: string | undefined;\n for (const h of handlers) {\n try { await h.fn(msg); }\n catch (err) {\n success = false;\n lastError = err instanceof Error ? err.message : String(err);\n this.logger?.warn?.(`DbQueueAdapter: handler failed on ${row.queue}`, err as any);\n break;\n }\n }\n\n const now = this.now();\n if (success) {\n await this.engine.update(QUEUE_TABLE, {\n id: row.id,\n status: 'completed',\n attempts: msg.attempts,\n completed_at: now.toISOString(),\n locked_by: null,\n locked_until: null,\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n return;\n }\n\n const attempts = msg.attempts;\n const max = row.max_attempts ?? this.opts.defaultMaxAttempts;\n if (attempts >= max) {\n await this.engine.update(QUEUE_TABLE, {\n id: row.id,\n status: 'dlq',\n attempts,\n last_error: lastError ?? 'unknown error',\n completed_at: now.toISOString(),\n locked_by: null,\n locked_until: null,\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n return;\n }\n\n const backoffMs = this.computeBackoff(row, attempts);\n await this.engine.update(QUEUE_TABLE, {\n id: row.id,\n status: 'pending',\n attempts,\n last_error: lastError ?? 'unknown error',\n scheduled_for: new Date(now.getTime() + backoffMs).toISOString(),\n locked_by: null,\n locked_until: null,\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n }\n\n private computeBackoff(row: any, attempt: number): number {\n const base = row.backoff_delay_ms ?? 1000;\n const cap = row.backoff_max_delay_ms ?? undefined;\n if ((row.backoff_type ?? 'exponential') === 'fixed') return base;\n const exp = base * Math.pow(2, Math.max(0, attempt - 1));\n return cap ? Math.min(exp, cap) : exp;\n }\n\n private async releasePending(id: string): Promise<void> {\n const now = this.now();\n try {\n await this.engine.update(QUEUE_TABLE, {\n id,\n status: 'pending',\n locked_by: null,\n locked_until: null,\n scheduled_for: new Date(now.getTime() + this.opts.pollIntervalMs * 5).toISOString(),\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n } catch (err) {\n this.logger?.warn?.('DbQueueAdapter: release failed', err as any);\n }\n }\n\n private async loadById(id: string): Promise<any | null> {\n const rows = await this.engine.find(QUEUE_TABLE, {\n where: { id },\n limit: 1,\n context: SYSTEM_CTX,\n });\n return rows?.[0] ?? null;\n }\n\n private rowToRecord(r: any): QueueMessageRecord {\n return {\n id: String(r.id),\n queue: String(r.queue),\n data: parseJson(r.payload_json),\n status: r.status,\n attempts: r.attempts ?? 0,\n maxAttempts: r.max_attempts ?? this.opts.defaultMaxAttempts,\n scheduledFor: r.scheduled_for ?? undefined,\n lockedBy: r.locked_by ?? undefined,\n lockedUntil: r.locked_until ?? undefined,\n lastError: r.last_error ?? undefined,\n idempotencyKey: r.idempotency_key ?? undefined,\n metadata: parseJson(r.metadata_json),\n createdAt: r.created_at ?? nowIso(this.clock),\n updatedAt: r.updated_at ?? undefined,\n completedAt: r.completed_at ?? undefined,\n };\n }\n\n private now(): Date {\n return this.clock?.now() ?? new Date();\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { IQueueService, QueuePublishOptions, QueueHandler } from '@objectstack/spec/contracts';\n\n/**\n * Configuration for the BullMQ queue adapter.\n */\nexport interface BullMQQueueAdapterOptions {\n /** Redis connection URL (e.g. 'redis://localhost:6379') */\n redisUrl: string;\n /** Default job options */\n defaultJobOptions?: {\n /** Number of retry attempts */\n attempts?: number;\n /** Backoff strategy */\n backoff?: { type: 'fixed' | 'exponential'; delay: number };\n };\n}\n\n/**\n * BullMQ queue adapter skeleton implementing IQueueService.\n *\n * This is a placeholder for future BullMQ integration.\n * Concrete implementation will use the `bullmq` package.\n *\n * @example\n * ```ts\n * const queue = new BullMQQueueAdapter({ redisUrl: 'redis://localhost:6379' });\n * await queue.publish('orders', { orderId: 123 });\n * ```\n */\nexport class BullMQQueueAdapter implements IQueueService {\n private readonly redisUrl: string;\n\n constructor(options: BullMQQueueAdapterOptions) {\n this.redisUrl = options.redisUrl;\n }\n\n async publish<T = unknown>(_queue: string, _data: T, _options?: QueuePublishOptions): Promise<string> {\n throw new Error(`BullMQQueueAdapter not yet implemented (url: ${this.redisUrl})`);\n }\n\n async subscribe<T = unknown>(_queue: string, _handler: QueueHandler<T>): Promise<void> {\n throw new Error('BullMQQueueAdapter not yet implemented');\n }\n\n async unsubscribe(_queue: string): Promise<void> {\n throw new Error('BullMQQueueAdapter not yet implemented');\n }\n\n async getQueueSize(_queue: string): Promise<number> {\n throw new Error('BullMQQueueAdapter not yet implemented');\n }\n\n async purge(_queue: string): Promise<void> {\n throw new Error('BullMQQueueAdapter not yet implemented');\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/queue-service-plugin.ts","../src/memory-queue-adapter.ts","../src/common.ts","../src/db-queue-adapter.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { Plugin, PluginContext } from '@objectstack/core';\nimport { SysJobQueue } from '@objectstack/platform-objects/audit';\nimport { MemoryQueueAdapter } from './memory-queue-adapter.js';\nimport type { MemoryQueueAdapterOptions } from './memory-queue-adapter.js';\nimport { DbQueueAdapter } from './db-queue-adapter.js';\nimport type { DbQueueAdapterOptions } from './db-queue-adapter.js';\n\n/**\n * Configuration options for the QueueServicePlugin.\n */\nexport interface QueueServicePluginOptions {\n /**\n * Queue adapter type.\n * - 'auto' (default): use DbQueueAdapter when objectql engine available, else MemoryQueueAdapter\n * - 'db': require objectql; persists messages, retries, and DLQ to sys_job_queue\n * - 'memory': in-process MemoryQueueAdapter (non-durable, dev/test)\n */\n adapter?: 'auto' | 'db' | 'memory';\n /** Options for the memory queue adapter */\n memory?: MemoryQueueAdapterOptions;\n /** Options for the DB adapter (polling, batch, lease, idempotency window…) */\n db?: DbQueueAdapterOptions;\n}\n\n/**\n * QueueServicePlugin — Production IQueueService implementation.\n *\n * Default: registers MemoryQueueAdapter synchronously so producers can\n * publish during plugin init; upgrades to DbQueueAdapter on `kernel:ready`\n * when an ObjectQL engine is available. Subscribers registered against\n * the (now-replaced) memory queue must re-subscribe after upgrade — for\n * that reason most plugins register subscribers inside their own\n * `kernel:ready` hook, which fires after this one.\n */\nexport class QueueServicePlugin implements Plugin {\n name = 'com.objectstack.service.queue';\n version = '1.1.0';\n type = 'standard';\n\n private readonly options: QueueServicePluginOptions;\n private dbAdapter?: DbQueueAdapter;\n\n constructor(options: QueueServicePluginOptions = {}) {\n this.options = { adapter: 'auto', ...options };\n }\n\n async init(ctx: PluginContext): Promise<void> {\n // Register sys_job_queue (also serves as DLQ view) so Studio can list/replay.\n try {\n ctx.getService<{ register(m: any): void }>('manifest').register({\n id: 'com.objectstack.service.queue',\n name: 'Queue Service',\n version: '1.1.0',\n type: 'plugin',\n scope: 'system',\n defaultDatasource: 'cloud',\n namespace: 'sys',\n objects: [SysJobQueue],\n });\n } catch (err) {\n ctx.logger.warn('QueueServicePlugin: manifest service unavailable; sys_job_queue not registered', err as any);\n }\n\n const choice = this.options.adapter ?? 'auto';\n\n if (choice === 'memory') {\n const q = new MemoryQueueAdapter(this.options.memory);\n ctx.registerService('queue', q);\n ctx.logger.info('QueueServicePlugin: registered MemoryQueueAdapter');\n return;\n }\n\n // auto / db — register memory placeholder, upgrade on kernel:ready\n ctx.registerService('queue', new MemoryQueueAdapter(this.options.memory));\n\n ctx.hook('kernel:ready', async () => {\n let engine: any = null;\n try { engine = ctx.getService<any>('objectql'); }\n catch { try { engine = ctx.getService<any>('data'); } catch { /* ignore */ } }\n\n if (!engine) {\n if (choice === 'db') {\n ctx.logger.warn('QueueServicePlugin: db adapter requested but no ObjectQL engine — staying on MemoryQueueAdapter');\n } else {\n ctx.logger.info('QueueServicePlugin: no ObjectQL engine — staying on MemoryQueueAdapter');\n }\n return;\n }\n\n this.dbAdapter = new DbQueueAdapter({\n engine,\n logger: ctx.logger,\n options: this.options.db,\n });\n\n try {\n (ctx as any).replaceService?.('queue', this.dbAdapter);\n this.dbAdapter.start();\n ctx.logger.info('QueueServicePlugin: upgraded to DbQueueAdapter (sys_job_queue persistence)');\n } catch (err) {\n ctx.logger.warn('QueueServicePlugin: replaceService failed; staying on MemoryQueueAdapter', err as any);\n }\n });\n }\n\n async destroy(): Promise<void> {\n await this.dbAdapter?.stop();\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { IQueueService, QueuePublishOptions, QueueMessage, QueueHandler } from '@objectstack/spec/contracts';\n\n/**\n * Configuration options for MemoryQueueAdapter.\n */\nexport interface MemoryQueueAdapterOptions {\n /** Maximum number of messages retained per queue (0 = unlimited) */\n maxQueueSize?: number;\n}\n\n/**\n * In-memory queue adapter implementing IQueueService.\n *\n * Provides synchronous in-process pub/sub delivery.\n * Suitable for single-process environments, development, and testing.\n */\nexport class MemoryQueueAdapter implements IQueueService {\n private readonly handlers = new Map<string, QueueHandler[]>();\n private readonly deadLetters: QueueMessage[] = [];\n private msgCounter = 0;\n private readonly maxQueueSize: number;\n\n constructor(options: MemoryQueueAdapterOptions = {}) {\n this.maxQueueSize = options.maxQueueSize ?? 0;\n }\n\n async publish<T = unknown>(queue: string, data: T, options?: QueuePublishOptions): Promise<string> {\n const id = `msg-${++this.msgCounter}`;\n const msg: QueueMessage<T> = {\n id,\n data,\n attempts: 0,\n timestamp: Date.now(),\n };\n\n const fns = this.handlers.get(queue) ?? [];\n if (fns.length === 0) {\n // No subscribers — retain as dead letter if within limits\n if (this.maxQueueSize === 0 || this.deadLetters.length < this.maxQueueSize) {\n this.deadLetters.push(msg);\n }\n return id;\n }\n\n const maxRetries = options?.retries ?? 0;\n for (const handler of fns) {\n let attempt = 0;\n let success = false;\n while (!success && attempt <= maxRetries) {\n try {\n msg.attempts = attempt + 1;\n await handler(msg as QueueMessage);\n success = true;\n } catch {\n attempt++;\n }\n }\n }\n\n return id;\n }\n\n async subscribe<T = unknown>(queue: string, handler: QueueHandler<T>): Promise<void> {\n const existing = this.handlers.get(queue) ?? [];\n this.handlers.set(queue, [...existing, handler as QueueHandler]);\n }\n\n async unsubscribe(queue: string): Promise<void> {\n this.handlers.delete(queue);\n }\n\n async getQueueSize(_queue: string): Promise<number> {\n // In-memory: no persistent queue depth tracking\n return 0;\n }\n\n async purge(queue: string): Promise<void> {\n this.handlers.delete(queue);\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\n/**\n * Narrow ObjectQL engine surface used by job/queue adapters.\n * Keeps the adapter testable without booting a real kernel.\n *\n * IMPORTANT: matches the canonical engine API:\n * - find: `where:` (NOT `filter:`)\n * - update: `(table, {id, ...patch}, opts)`\n */\nexport interface JobEngine {\n find(object: string, options?: any): Promise<any[]>;\n insert(object: string, data: any, options?: any): Promise<any>;\n update(object: string, idOrData: any, dataOrOptions?: any, options?: any): Promise<any>;\n delete(object: string, options?: any): Promise<any>;\n}\n\n/** Stamped only in tests to make `now` deterministic. */\nexport interface JobClock { now(): Date }\n\nexport interface JobLogger {\n info(msg: string, meta?: unknown): void;\n warn(msg: string, meta?: unknown): void;\n error?(msg: string, meta?: unknown): void;\n}\n\nexport const SYSTEM_CTX = { isSystem: true, roles: [], permissions: [] } as const;\n\nexport function uid(prefix: string): string {\n const g: any = globalThis as any;\n if (g.crypto?.randomUUID) return `${prefix}_${g.crypto.randomUUID()}`;\n return `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;\n}\n\nexport function nowIso(clock?: JobClock): string {\n return (clock?.now() ?? new Date()).toISOString();\n}\n\nexport function parseJson<T = unknown>(raw: unknown, fallback?: T): T | undefined {\n if (raw == null) return fallback;\n if (typeof raw === 'string') {\n try { return JSON.parse(raw) as T; } catch { return fallback; }\n }\n if (typeof raw === 'object') return raw as T;\n return fallback;\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type {\n IQueueService,\n QueuePublishOptions,\n QueueMessage,\n QueueMessageRecord,\n QueueHandler,\n} from '@objectstack/spec/contracts';\nimport {\n SYSTEM_CTX,\n uid,\n nowIso,\n parseJson,\n type JobEngine,\n type JobClock,\n type JobLogger,\n} from './common.js';\n\nconst QUEUE_TABLE = 'sys_job_queue';\n\nexport interface DbQueueAdapterOptions {\n /** Polling interval for the worker loop (ms, default 1000) */\n pollIntervalMs?: number;\n /** Max messages claimed per poll tick (default 10) */\n batchSize?: number;\n /** Lease duration before another worker may reclaim (ms, default 30000) */\n leaseMs?: number;\n /** Idempotency window — how long the same key blocks re-publish (ms, default 24h) */\n idempotencyWindowMs?: number;\n /** Default maxAttempts when publish doesn't specify (default 3) */\n defaultMaxAttempts?: number;\n /** Unique identifier for this worker (default: random) */\n workerId?: string;\n /** Whether to auto-start the polling worker (default true) */\n autoStart?: boolean;\n}\n\ninterface RegisteredHandler {\n queue: string;\n fn: QueueHandler;\n}\n\n/**\n * DbQueueAdapter — durable, polling, DB-backed IQueueService.\n *\n * Persists every message to `sys_job_queue`. A polling worker leases\n * pending messages (CAS update status pending→running with a lease),\n * invokes registered subscribers, and retries with backoff on failure.\n * Messages that exceed `max_attempts` land in `status='dlq'`.\n *\n * Idempotency: publish suppresses duplicates within a configurable\n * window when `(queue, idempotencyKey)` is non-null.\n *\n * Designed for SQLite and Postgres alike — uses CAS via WHERE-clauses,\n * not row-level locking.\n */\nexport class DbQueueAdapter implements IQueueService {\n private readonly engine: JobEngine;\n private readonly logger?: JobLogger;\n private readonly clock?: JobClock;\n private readonly opts: Required<Omit<DbQueueAdapterOptions, 'workerId'>> & { workerId: string };\n\n private readonly handlers = new Map<string, RegisteredHandler[]>();\n private timer?: ReturnType<typeof setInterval>;\n private running = false;\n\n constructor(args: {\n engine: JobEngine;\n logger?: JobLogger;\n clock?: JobClock;\n options?: DbQueueAdapterOptions;\n }) {\n this.engine = args.engine;\n this.logger = args.logger;\n this.clock = args.clock;\n const o = args.options ?? {};\n this.opts = {\n pollIntervalMs: o.pollIntervalMs ?? 1000,\n batchSize: o.batchSize ?? 10,\n leaseMs: o.leaseMs ?? 30_000,\n idempotencyWindowMs: o.idempotencyWindowMs ?? 24 * 60 * 60 * 1000,\n defaultMaxAttempts: o.defaultMaxAttempts ?? 3,\n autoStart: o.autoStart ?? true,\n workerId: o.workerId ?? uid('worker'),\n };\n }\n\n // ── IQueueService ────────────────────────────────────────────────\n\n async publish<T = unknown>(\n queue: string,\n data: T,\n options?: QueuePublishOptions,\n ): Promise<string> {\n const opts = options ?? {};\n const now = this.now();\n\n // Idempotency check\n if (opts.idempotencyKey) {\n const windowStart = new Date(now.getTime() - this.opts.idempotencyWindowMs).toISOString();\n const existing = await this.engine.find(QUEUE_TABLE, {\n where: {\n queue,\n idempotency_key: opts.idempotencyKey,\n // Only block if not yet terminal — completed/dlq dedup is by window via created_at\n },\n limit: 5,\n context: SYSTEM_CTX,\n });\n const blocking = (existing ?? []).find((row: any) => {\n if (row.status === 'pending' || row.status === 'running') return true;\n return String(row.created_at ?? '') >= windowStart;\n });\n if (blocking) return String(blocking.id);\n }\n\n const id = uid('msg');\n const scheduledFor = opts.scheduledFor\n ? new Date(opts.scheduledFor).toISOString()\n : opts.delay\n ? new Date(now.getTime() + opts.delay).toISOString()\n : now.toISOString();\n\n const maxAttempts = opts.maxAttempts\n ?? (opts.retries != null ? opts.retries + 1 : this.opts.defaultMaxAttempts);\n const backoff = opts.backoff ?? { type: 'exponential' as const, delayMs: 1000 };\n\n await this.engine.insert(QUEUE_TABLE, {\n id,\n queue,\n idempotency_key: opts.idempotencyKey ?? null,\n payload_json: JSON.stringify(data ?? null),\n metadata_json: opts.metadata ? JSON.stringify(opts.metadata) : null,\n status: 'pending',\n priority: opts.priority ?? 100,\n attempts: 0,\n max_attempts: maxAttempts,\n backoff_type: backoff.type,\n backoff_delay_ms: backoff.delayMs,\n backoff_max_delay_ms: backoff.maxDelayMs ?? null,\n scheduled_for: scheduledFor,\n created_at: now.toISOString(),\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n\n return id;\n }\n\n async subscribe<T = unknown>(queue: string, handler: QueueHandler<T>): Promise<void> {\n const existing = this.handlers.get(queue) ?? [];\n existing.push({ queue, fn: handler as QueueHandler });\n this.handlers.set(queue, existing);\n if (this.opts.autoStart) this.start();\n }\n\n async unsubscribe(queue: string): Promise<void> {\n this.handlers.delete(queue);\n }\n\n async getQueueSize(queue: string): Promise<number> {\n const rows = await this.engine.find(QUEUE_TABLE, {\n where: { queue, status: 'pending' },\n limit: 10_000,\n context: SYSTEM_CTX,\n });\n return rows?.length ?? 0;\n }\n\n async purge(queue: string): Promise<void> {\n const rows = await this.engine.find(QUEUE_TABLE, {\n where: { queue, status: 'pending' },\n limit: 10_000,\n context: SYSTEM_CTX,\n });\n for (const row of rows ?? []) {\n try { await this.engine.delete(QUEUE_TABLE, { id: row.id, context: SYSTEM_CTX }); }\n catch (err) { this.logger?.warn?.('DbQueueAdapter: purge delete failed', err as any); }\n }\n }\n\n async listFailed(\n queue?: string,\n options?: { limit?: number; offset?: number },\n ): Promise<QueueMessageRecord[]> {\n const where: any = { status: 'dlq' };\n if (queue) where.queue = queue;\n const rows = await this.engine.find(QUEUE_TABLE, {\n where,\n limit: options?.limit ?? 100,\n offset: options?.offset,\n orderBy: [{ field: 'created_at', order: 'desc' }],\n context: SYSTEM_CTX,\n });\n return (rows ?? []).map((r: any) => this.rowToRecord(r));\n }\n\n async replay(messageId: string): Promise<void> {\n const row = await this.loadById(messageId);\n if (!row) throw new Error(`MESSAGE_NOT_FOUND: ${messageId}`);\n if (row.status !== 'dlq' && row.status !== 'failed') {\n throw new Error(`INVALID_STATE: cannot replay message in status=${row.status}`);\n }\n const now = this.now();\n await this.engine.update(QUEUE_TABLE, {\n id: messageId,\n status: 'pending',\n attempts: 0,\n last_error: null,\n locked_by: null,\n locked_until: null,\n scheduled_for: now.toISOString(),\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n }\n\n async purgeFailed(messageId: string): Promise<void> {\n const row = await this.loadById(messageId);\n if (!row) return;\n if (row.status !== 'dlq' && row.status !== 'failed') {\n throw new Error(`INVALID_STATE: cannot purge message in status=${row.status}`);\n }\n await this.engine.delete(QUEUE_TABLE, { id: messageId, context: SYSTEM_CTX });\n }\n\n // ── Worker lifecycle ─────────────────────────────────────────────\n\n start(): void {\n if (this.timer) return;\n this.timer = setInterval(() => {\n if (this.running) return;\n this.running = true;\n this.pollOnce()\n .catch((err) => { this.logger?.warn?.('DbQueueAdapter: poll tick failed', err); })\n .finally(() => { this.running = false; });\n }, this.opts.pollIntervalMs);\n (this.timer as any)?.unref?.();\n }\n\n async stop(): Promise<void> {\n if (this.timer) { clearInterval(this.timer); this.timer = undefined; }\n }\n\n /** Test-friendly synchronous poll. */\n async pollOnce(): Promise<number> {\n const queues = [...this.handlers.keys()];\n if (queues.length === 0) return 0;\n\n let processed = 0;\n for (const queue of queues) {\n const claimed = await this.claimBatch(queue, this.opts.batchSize);\n for (const row of claimed) {\n await this.dispatch(row);\n processed++;\n }\n }\n return processed;\n }\n\n // ── Internals ────────────────────────────────────────────────────\n\n private async claimBatch(queue: string, max: number): Promise<any[]> {\n const now = this.now();\n const candidates = await this.engine.find(QUEUE_TABLE, {\n where: { queue, status: 'pending' },\n limit: max * 3, // over-fetch in case of CAS contention\n orderBy: [\n { field: 'priority', order: 'asc' },\n { field: 'scheduled_for', order: 'asc' },\n ],\n context: SYSTEM_CTX,\n });\n\n const out: any[] = [];\n for (const row of candidates ?? []) {\n if (out.length >= max) break;\n const sched = row.scheduled_for ? new Date(row.scheduled_for).getTime() : 0;\n if (sched > now.getTime()) continue;\n // Honor existing lease\n const lockedUntil = row.locked_until ? new Date(row.locked_until).getTime() : 0;\n if (row.locked_by && lockedUntil > now.getTime()) continue;\n\n // CAS — only update if still pending (best-effort with engine.update which\n // typically does row-level update by id; concurrent workers will overwrite\n // each other but the dispatcher tolerates duplicate delivery via attempts).\n try {\n await this.engine.update(QUEUE_TABLE, {\n id: row.id,\n status: 'running',\n locked_by: this.opts.workerId,\n locked_until: new Date(now.getTime() + this.opts.leaseMs).toISOString(),\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n out.push({ ...row, status: 'running' });\n } catch (err) {\n this.logger?.warn?.('DbQueueAdapter: claim CAS failed', err as any);\n }\n }\n return out;\n }\n\n private async dispatch(row: any): Promise<void> {\n const handlers = this.handlers.get(row.queue) ?? [];\n if (handlers.length === 0) {\n // No handler — release lease so another process can pick it up\n await this.releasePending(row.id);\n return;\n }\n\n const msg: QueueMessage = {\n id: String(row.id),\n data: parseJson(row.payload_json),\n attempts: (row.attempts ?? 0) + 1,\n timestamp: row.created_at ? new Date(row.created_at).getTime() : Date.now(),\n };\n\n let success = true;\n let lastError: string | undefined;\n for (const h of handlers) {\n try { await h.fn(msg); }\n catch (err) {\n success = false;\n lastError = err instanceof Error ? err.message : String(err);\n this.logger?.warn?.(`DbQueueAdapter: handler failed on ${row.queue}`, err as any);\n break;\n }\n }\n\n const now = this.now();\n if (success) {\n await this.engine.update(QUEUE_TABLE, {\n id: row.id,\n status: 'completed',\n attempts: msg.attempts,\n completed_at: now.toISOString(),\n locked_by: null,\n locked_until: null,\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n return;\n }\n\n const attempts = msg.attempts;\n const max = row.max_attempts ?? this.opts.defaultMaxAttempts;\n if (attempts >= max) {\n await this.engine.update(QUEUE_TABLE, {\n id: row.id,\n status: 'dlq',\n attempts,\n last_error: lastError ?? 'unknown error',\n completed_at: now.toISOString(),\n locked_by: null,\n locked_until: null,\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n return;\n }\n\n const backoffMs = this.computeBackoff(row, attempts);\n await this.engine.update(QUEUE_TABLE, {\n id: row.id,\n status: 'pending',\n attempts,\n last_error: lastError ?? 'unknown error',\n scheduled_for: new Date(now.getTime() + backoffMs).toISOString(),\n locked_by: null,\n locked_until: null,\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n }\n\n private computeBackoff(row: any, attempt: number): number {\n const base = row.backoff_delay_ms ?? 1000;\n const cap = row.backoff_max_delay_ms ?? undefined;\n if ((row.backoff_type ?? 'exponential') === 'fixed') return base;\n const exp = base * Math.pow(2, Math.max(0, attempt - 1));\n return cap ? Math.min(exp, cap) : exp;\n }\n\n private async releasePending(id: string): Promise<void> {\n const now = this.now();\n try {\n await this.engine.update(QUEUE_TABLE, {\n id,\n status: 'pending',\n locked_by: null,\n locked_until: null,\n scheduled_for: new Date(now.getTime() + this.opts.pollIntervalMs * 5).toISOString(),\n updated_at: now.toISOString(),\n }, { context: SYSTEM_CTX });\n } catch (err) {\n this.logger?.warn?.('DbQueueAdapter: release failed', err as any);\n }\n }\n\n private async loadById(id: string): Promise<any | null> {\n const rows = await this.engine.find(QUEUE_TABLE, {\n where: { id },\n limit: 1,\n context: SYSTEM_CTX,\n });\n return rows?.[0] ?? null;\n }\n\n private rowToRecord(r: any): QueueMessageRecord {\n return {\n id: String(r.id),\n queue: String(r.queue),\n data: parseJson(r.payload_json),\n status: r.status,\n attempts: r.attempts ?? 0,\n maxAttempts: r.max_attempts ?? this.opts.defaultMaxAttempts,\n scheduledFor: r.scheduled_for ?? undefined,\n lockedBy: r.locked_by ?? undefined,\n lockedUntil: r.locked_until ?? undefined,\n lastError: r.last_error ?? undefined,\n idempotencyKey: r.idempotency_key ?? undefined,\n metadata: parseJson(r.metadata_json),\n createdAt: r.created_at ?? nowIso(this.clock),\n updatedAt: r.updated_at ?? undefined,\n completedAt: r.completed_at ?? undefined,\n };\n }\n\n private now(): Date {\n return this.clock?.now() ?? new Date();\n }\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@objectstack/service-queue",
3
- "version": "9.4.0",
3
+ "version": "9.5.1",
4
4
  "license": "Apache-2.0",
5
- "description": "Queue Service for ObjectStack — implements IQueueService with in-memory and BullMQ adapters",
5
+ "description": "Queue Service for ObjectStack — implements IQueueService with in-memory and durable DB-backed (sys_job_queue) adapters",
6
6
  "type": "module",
7
7
  "main": "dist/index.js",
8
8
  "types": "dist/index.d.ts",
@@ -14,9 +14,9 @@
14
14
  }
15
15
  },
16
16
  "dependencies": {
17
- "@objectstack/core": "9.4.0",
18
- "@objectstack/platform-objects": "9.4.0",
19
- "@objectstack/spec": "9.4.0"
17
+ "@objectstack/core": "9.5.1",
18
+ "@objectstack/platform-objects": "9.5.1",
19
+ "@objectstack/spec": "9.5.1"
20
20
  },
21
21
  "devDependencies": {
22
22
  "@types/node": "^25.9.2",