@objectstack/service-queue 9.3.0 → 9.5.0
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 +64 -411
- package/dist/index.cjs +0 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -44
- package/dist/index.d.ts +2 -44
- package/dist/index.js +0 -28
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
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
|
|
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
|
-
##
|
|
6
|
+
## Adapters
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
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
|
-
|
|
298
|
-
|
|
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
|
-
|
|
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
|
-
|
|
42
|
+
const queue = kernel.getService('queue'); // IQueueService
|
|
314
43
|
|
|
315
|
-
|
|
316
|
-
queue.
|
|
317
|
-
|
|
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
|
-
|
|
334
|
-
|
|
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
|
-
###
|
|
55
|
+
### Configuration
|
|
375
56
|
|
|
376
57
|
```typescript
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
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
|
-
- [
|
|
452
|
-
- [@objectstack/
|
|
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
|
package/dist/index.cjs.map
CHANGED
|
@@ -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":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACGA,mBAA4B;;;ACerB,IAAM,qBAAN,MAAkD;AAAA,EAMvD,YAAY,UAAqC,CAAC,GAAG;AALrD,SAAiB,WAAW,oBAAI,IAA4B;AAC5D,SAAiB,cAA8B,CAAC;AAChD,SAAQ,aAAa;AAInB,SAAK,eAAe,QAAQ,gBAAgB;AAAA,EAC9C;AAAA,EAEA,MAAM,QAAqB,OAAe,MAAS,SAAgD;AACjG,UAAM,KAAK,OAAO,EAAE,KAAK,UAAU;AACnC,UAAM,MAAuB;AAAA,MAC3B;AAAA,MACA;AAAA,MACA,UAAU;AAAA,MACV,WAAW,KAAK,IAAI;AAAA,IACtB;AAEA,UAAM,MAAM,KAAK,SAAS,IAAI,KAAK,KAAK,CAAC;AACzC,QAAI,IAAI,WAAW,GAAG;AAEpB,UAAI,KAAK,iBAAiB,KAAK,KAAK,YAAY,SAAS,KAAK,cAAc;AAC1E,aAAK,YAAY,KAAK,GAAG;AAAA,MAC3B;AACA,aAAO;AAAA,IACT;AAEA,UAAM,aAAa,SAAS,WAAW;AACvC,eAAW,WAAW,KAAK;AACzB,UAAI,UAAU;AACd,UAAI,UAAU;AACd,aAAO,CAAC,WAAW,WAAW,YAAY;AACxC,YAAI;AACF,cAAI,WAAW,UAAU;AACzB,gBAAM,QAAQ,GAAmB;AACjC,oBAAU;AAAA,QACZ,QAAQ;AACN;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,UAAuB,OAAe,SAAyC;AACnF,UAAM,WAAW,KAAK,SAAS,IAAI,KAAK,KAAK,CAAC;AAC9C,SAAK,SAAS,IAAI,OAAO,CAAC,GAAG,UAAU,OAAuB,CAAC;AAAA,EACjE;AAAA,EAEA,MAAM,YAAY,OAA8B;AAC9C,SAAK,SAAS,OAAO,KAAK;AAAA,EAC5B;AAAA,EAEA,MAAM,aAAa,QAAiC;AAElD,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,MAAM,OAA8B;AACxC,SAAK,SAAS,OAAO,KAAK;AAAA,EAC5B;AACF;;;ACvDO,IAAM,aAAa,EAAE,UAAU,MAAM,OAAO,CAAC,GAAG,aAAa,CAAC,EAAE;AAEhE,SAAS,IAAI,QAAwB;AAC1C,QAAM,IAAS;AACf,MAAI,EAAE,QAAQ,WAAY,QAAO,GAAG,MAAM,IAAI,EAAE,OAAO,WAAW,CAAC;AACnE,SAAO,GAAG,MAAM,IAAI,KAAK,IAAI,EAAE,SAAS,EAAE,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,GAAG,EAAE,CAAC;AACxF;AAEO,SAAS,OAAO,OAA0B;AAC/C,UAAQ,OAAO,IAAI,KAAK,oBAAI,KAAK,GAAG,YAAY;AAClD;AAEO,SAAS,UAAuB,KAAc,UAA6B;AAChF,MAAI,OAAO,KAAM,QAAO;AACxB,MAAI,OAAO,QAAQ,UAAU;AAC3B,QAAI;AAAE,aAAO,KAAK,MAAM,GAAG;AAAA,IAAQ,QAAQ;AAAE,aAAO;AAAA,IAAU;AAAA,EAChE;AACA,MAAI,OAAO,QAAQ,SAAU,QAAO;AACpC,SAAO;AACT;;;AC1BA,IAAM,cAAc;AAsCb,IAAM,iBAAN,MAA8C;AAAA,EAUnD,YAAY,MAKT;AATH,SAAiB,WAAW,oBAAI,IAAiC;AAEjE,SAAQ,UAAU;AAQhB,SAAK,SAAS,KAAK;AACnB,SAAK,SAAS,KAAK;AACnB,SAAK,QAAQ,KAAK;AAClB,UAAM,IAAI,KAAK,WAAW,CAAC;AAC3B,SAAK,OAAO;AAAA,MACV,gBAAgB,EAAE,kBAAkB;AAAA,MACpC,WAAW,EAAE,aAAa;AAAA,MAC1B,SAAS,EAAE,WAAW;AAAA,MACtB,qBAAqB,EAAE,uBAAuB,KAAK,KAAK,KAAK;AAAA,MAC7D,oBAAoB,EAAE,sBAAsB;AAAA,MAC5C,WAAW,EAAE,aAAa;AAAA,MAC1B,UAAU,EAAE,YAAY,IAAI,QAAQ;AAAA,IACtC;AAAA,EACF;AAAA;AAAA,EAIA,MAAM,QACJ,OACA,MACA,SACiB;AACjB,UAAM,OAAO,WAAW,CAAC;AACzB,UAAM,MAAM,KAAK,IAAI;AAGrB,QAAI,KAAK,gBAAgB;AACvB,YAAM,cAAc,IAAI,KAAK,IAAI,QAAQ,IAAI,KAAK,KAAK,mBAAmB,EAAE,YAAY;AACxF,YAAM,WAAW,MAAM,KAAK,OAAO,KAAK,aAAa;AAAA,QACnD,OAAO;AAAA,UACL;AAAA,UACA,iBAAiB,KAAK;AAAA;AAAA,QAExB;AAAA,QACA,OAAO;AAAA,QACP,SAAS;AAAA,MACX,CAAC;AACD,YAAM,YAAY,YAAY,CAAC,GAAG,KAAK,CAAC,QAAa;AACnD,YAAI,IAAI,WAAW,aAAa,IAAI,WAAW,UAAW,QAAO;AACjE,eAAO,OAAO,IAAI,cAAc,EAAE,KAAK;AAAA,MACzC,CAAC;AACD,UAAI,SAAU,QAAO,OAAO,SAAS,EAAE;AAAA,IACzC;AAEA,UAAM,KAAK,IAAI,KAAK;AACpB,UAAM,eAAe,KAAK,eACtB,IAAI,KAAK,KAAK,YAAY,EAAE,YAAY,IACxC,KAAK,QACH,IAAI,KAAK,IAAI,QAAQ,IAAI,KAAK,KAAK,EAAE,YAAY,IACjD,IAAI,YAAY;AAEtB,UAAM,cAAc,KAAK,gBACnB,KAAK,WAAW,OAAO,KAAK,UAAU,IAAI,KAAK,KAAK;AAC1D,UAAM,UAAU,KAAK,WAAW,EAAE,MAAM,eAAwB,SAAS,IAAK;AAE9E,UAAM,KAAK,OAAO,OAAO,aAAa;AAAA,MACpC;AAAA,MACA;AAAA,MACA,iBAAiB,KAAK,kBAAkB;AAAA,MACxC,cAAc,KAAK,UAAU,QAAQ,IAAI;AAAA,MACzC,eAAe,KAAK,WAAW,KAAK,UAAU,KAAK,QAAQ,IAAI;AAAA,MAC/D,QAAQ;AAAA,MACR,UAAU,KAAK,YAAY;AAAA,MAC3B,UAAU;AAAA,MACV,cAAc;AAAA,MACd,cAAc,QAAQ;AAAA,MACtB,kBAAkB,QAAQ;AAAA,MAC1B,sBAAsB,QAAQ,cAAc;AAAA,MAC5C,eAAe;AAAA,MACf,YAAY,IAAI,YAAY;AAAA,MAC5B,YAAY,IAAI,YAAY;AAAA,IAC9B,GAAG,EAAE,SAAS,WAAW,CAAC;AAE1B,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,UAAuB,OAAe,SAAyC;AACnF,UAAM,WAAW,KAAK,SAAS,IAAI,KAAK,KAAK,CAAC;AAC9C,aAAS,KAAK,EAAE,OAAO,IAAI,QAAwB,CAAC;AACpD,SAAK,SAAS,IAAI,OAAO,QAAQ;AACjC,QAAI,KAAK,KAAK,UAAW,MAAK,MAAM;AAAA,EACtC;AAAA,EAEA,MAAM,YAAY,OAA8B;AAC9C,SAAK,SAAS,OAAO,KAAK;AAAA,EAC5B;AAAA,EAEA,MAAM,aAAa,OAAgC;AACjD,UAAM,OAAO,MAAM,KAAK,OAAO,KAAK,aAAa;AAAA,MAC/C,OAAO,EAAE,OAAO,QAAQ,UAAU;AAAA,MAClC,OAAO;AAAA,MACP,SAAS;AAAA,IACX,CAAC;AACD,WAAO,MAAM,UAAU;AAAA,EACzB;AAAA,EAEA,MAAM,MAAM,OAA8B;AACxC,UAAM,OAAO,MAAM,KAAK,OAAO,KAAK,aAAa;AAAA,MAC/C,OAAO,EAAE,OAAO,QAAQ,UAAU;AAAA,MAClC,OAAO;AAAA,MACP,SAAS;AAAA,IACX,CAAC;AACD,eAAW,OAAO,QAAQ,CAAC,GAAG;AAC5B,UAAI;AAAE,cAAM,KAAK,OAAO,OAAO,aAAa,EAAE,IAAI,IAAI,IAAI,SAAS,WAAW,CAAC;AAAA,MAAG,SAC3E,KAAK;AAAE,aAAK,QAAQ,OAAO,uCAAuC,GAAU;AAAA,MAAG;AAAA,IACxF;AAAA,EACF;AAAA,EAEA,MAAM,WACJ,OACA,SAC+B;AAC/B,UAAM,QAAa,EAAE,QAAQ,MAAM;AACnC,QAAI,MAAO,OAAM,QAAQ;AACzB,UAAM,OAAO,MAAM,KAAK,OAAO,KAAK,aAAa;AAAA,MAC/C;AAAA,MACA,OAAO,SAAS,SAAS;AAAA,MACzB,QAAQ,SAAS;AAAA,MACjB,SAAS,CAAC,EAAE,OAAO,cAAc,OAAO,OAAO,CAAC;AAAA,MAChD,SAAS;AAAA,IACX,CAAC;AACD,YAAQ,QAAQ,CAAC,GAAG,IAAI,CAAC,MAAW,KAAK,YAAY,CAAC,CAAC;AAAA,EACzD;AAAA,EAEA,MAAM,OAAO,WAAkC;AAC7C,UAAM,MAAM,MAAM,KAAK,SAAS,SAAS;AACzC,QAAI,CAAC,IAAK,OAAM,IAAI,MAAM,sBAAsB,SAAS,EAAE;AAC3D,QAAI,IAAI,WAAW,SAAS,IAAI,WAAW,UAAU;AACnD,YAAM,IAAI,MAAM,kDAAkD,IAAI,MAAM,EAAE;AAAA,IAChF;AACA,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,KAAK,OAAO,OAAO,aAAa;AAAA,MACpC,IAAI;AAAA,MACJ,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,YAAY;AAAA,MACZ,WAAW;AAAA,MACX,cAAc;AAAA,MACd,eAAe,IAAI,YAAY;AAAA,MAC/B,YAAY,IAAI,YAAY;AAAA,IAC9B,GAAG,EAAE,SAAS,WAAW,CAAC;AAAA,EAC5B;AAAA,EAEA,MAAM,YAAY,WAAkC;AAClD,UAAM,MAAM,MAAM,KAAK,SAAS,SAAS;AACzC,QAAI,CAAC,IAAK;AACV,QAAI,IAAI,WAAW,SAAS,IAAI,WAAW,UAAU;AACnD,YAAM,IAAI,MAAM,iDAAiD,IAAI,MAAM,EAAE;AAAA,IAC/E;AACA,UAAM,KAAK,OAAO,OAAO,aAAa,EAAE,IAAI,WAAW,SAAS,WAAW,CAAC;AAAA,EAC9E;AAAA;AAAA,EAIA,QAAc;AACZ,QAAI,KAAK,MAAO;AAChB,SAAK,QAAQ,YAAY,MAAM;AAC7B,UAAI,KAAK,QAAS;AAClB,WAAK,UAAU;AACf,WAAK,SAAS,EACX,MAAM,CAAC,QAAQ;AAAE,aAAK,QAAQ,OAAO,oCAAoC,GAAG;AAAA,MAAG,CAAC,EAChF,QAAQ,MAAM;AAAE,aAAK,UAAU;AAAA,MAAO,CAAC;AAAA,IAC5C,GAAG,KAAK,KAAK,cAAc;AAC3B,IAAC,KAAK,OAAe,QAAQ;AAAA,EAC/B;AAAA,EAEA,MAAM,OAAsB;AAC1B,QAAI,KAAK,OAAO;AAAE,oBAAc,KAAK,KAAK;AAAG,WAAK,QAAQ;AAAA,IAAW;AAAA,EACvE;AAAA;AAAA,EAGA,MAAM,WAA4B;AAChC,UAAM,SAAS,CAAC,GAAG,KAAK,SAAS,KAAK,CAAC;AACvC,QAAI,OAAO,WAAW,EAAG,QAAO;AAEhC,QAAI,YAAY;AAChB,eAAW,SAAS,QAAQ;AAC1B,YAAM,UAAU,MAAM,KAAK,WAAW,OAAO,KAAK,KAAK,SAAS;AAChE,iBAAW,OAAO,SAAS;AACzB,cAAM,KAAK,SAAS,GAAG;AACvB;AAAA,MACF;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA;AAAA,EAIA,MAAc,WAAW,OAAe,KAA6B;AACnE,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,aAAa,MAAM,KAAK,OAAO,KAAK,aAAa;AAAA,MACrD,OAAO,EAAE,OAAO,QAAQ,UAAU;AAAA,MAClC,OAAO,MAAM;AAAA;AAAA,MACb,SAAS;AAAA,QACP,EAAE,OAAO,YAAY,OAAO,MAAM;AAAA,QAClC,EAAE,OAAO,iBAAiB,OAAO,MAAM;AAAA,MACzC;AAAA,MACA,SAAS;AAAA,IACX,CAAC;AAED,UAAM,MAAa,CAAC;AACpB,eAAW,OAAO,cAAc,CAAC,GAAG;AAClC,UAAI,IAAI,UAAU,IAAK;AACvB,YAAM,QAAQ,IAAI,gBAAgB,IAAI,KAAK,IAAI,aAAa,EAAE,QAAQ,IAAI;AAC1E,UAAI,QAAQ,IAAI,QAAQ,EAAG;AAE3B,YAAM,cAAc,IAAI,eAAe,IAAI,KAAK,IAAI,YAAY,EAAE,QAAQ,IAAI;AAC9E,UAAI,IAAI,aAAa,cAAc,IAAI,QAAQ,EAAG;AAKlD,UAAI;AACF,cAAM,KAAK,OAAO,OAAO,aAAa;AAAA,UACpC,IAAI,IAAI;AAAA,UACR,QAAQ;AAAA,UACR,WAAW,KAAK,KAAK;AAAA,UACrB,cAAc,IAAI,KAAK,IAAI,QAAQ,IAAI,KAAK,KAAK,OAAO,EAAE,YAAY;AAAA,UACtE,YAAY,IAAI,YAAY;AAAA,QAC9B,GAAG,EAAE,SAAS,WAAW,CAAC;AAC1B,YAAI,KAAK,EAAE,GAAG,KAAK,QAAQ,UAAU,CAAC;AAAA,MACxC,SAAS,KAAK;AACZ,aAAK,QAAQ,OAAO,oCAAoC,GAAU;AAAA,MACpE;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,SAAS,KAAyB;AAC9C,UAAM,WAAW,KAAK,SAAS,IAAI,IAAI,KAAK,KAAK,CAAC;AAClD,QAAI,SAAS,WAAW,GAAG;AAEzB,YAAM,KAAK,eAAe,IAAI,EAAE;AAChC;AAAA,IACF;AAEA,UAAM,MAAoB;AAAA,MACxB,IAAI,OAAO,IAAI,EAAE;AAAA,MACjB,MAAM,UAAU,IAAI,YAAY;AAAA,MAChC,WAAW,IAAI,YAAY,KAAK;AAAA,MAChC,WAAW,IAAI,aAAa,IAAI,KAAK,IAAI,UAAU,EAAE,QAAQ,IAAI,KAAK,IAAI;AAAA,IAC5E;AAEA,QAAI,UAAU;AACd,QAAI;AACJ,eAAW,KAAK,UAAU;AACxB,UAAI;AAAE,cAAM,EAAE,GAAG,GAAG;AAAA,MAAG,SAChB,KAAK;AACV,kBAAU;AACV,oBAAY,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC3D,aAAK,QAAQ,OAAO,qCAAqC,IAAI,KAAK,IAAI,GAAU;AAChF;AAAA,MACF;AAAA,IACF;AAEA,UAAM,MAAM,KAAK,IAAI;AACrB,QAAI,SAAS;AACX,YAAM,KAAK,OAAO,OAAO,aAAa;AAAA,QACpC,IAAI,IAAI;AAAA,QACR,QAAQ;AAAA,QACR,UAAU,IAAI;AAAA,QACd,cAAc,IAAI,YAAY;AAAA,QAC9B,WAAW;AAAA,QACX,cAAc;AAAA,QACd,YAAY,IAAI,YAAY;AAAA,MAC9B,GAAG,EAAE,SAAS,WAAW,CAAC;AAC1B;AAAA,IACF;AAEA,UAAM,WAAW,IAAI;AACrB,UAAM,MAAM,IAAI,gBAAgB,KAAK,KAAK;AAC1C,QAAI,YAAY,KAAK;AACnB,YAAM,KAAK,OAAO,OAAO,aAAa;AAAA,QACpC,IAAI,IAAI;AAAA,QACR,QAAQ;AAAA,QACR;AAAA,QACA,YAAY,aAAa;AAAA,QACzB,cAAc,IAAI,YAAY;AAAA,QAC9B,WAAW;AAAA,QACX,cAAc;AAAA,QACd,YAAY,IAAI,YAAY;AAAA,MAC9B,GAAG,EAAE,SAAS,WAAW,CAAC;AAC1B;AAAA,IACF;AAEA,UAAM,YAAY,KAAK,eAAe,KAAK,QAAQ;AACnD,UAAM,KAAK,OAAO,OAAO,aAAa;AAAA,MACpC,IAAI,IAAI;AAAA,MACR,QAAQ;AAAA,MACR;AAAA,MACA,YAAY,aAAa;AAAA,MACzB,eAAe,IAAI,KAAK,IAAI,QAAQ,IAAI,SAAS,EAAE,YAAY;AAAA,MAC/D,WAAW;AAAA,MACX,cAAc;AAAA,MACd,YAAY,IAAI,YAAY;AAAA,IAC9B,GAAG,EAAE,SAAS,WAAW,CAAC;AAAA,EAC5B;AAAA,EAEQ,eAAe,KAAU,SAAyB;AACxD,UAAM,OAAO,IAAI,oBAAoB;AACrC,UAAM,MAAM,IAAI,wBAAwB;AACxC,SAAK,IAAI,gBAAgB,mBAAmB,QAAS,QAAO;AAC5D,UAAM,MAAM,OAAO,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,UAAU,CAAC,CAAC;AACvD,WAAO,MAAM,KAAK,IAAI,KAAK,GAAG,IAAI;AAAA,EACpC;AAAA,EAEA,MAAc,eAAe,IAA2B;AACtD,UAAM,MAAM,KAAK,IAAI;AACrB,QAAI;AACF,YAAM,KAAK,OAAO,OAAO,aAAa;AAAA,QACpC;AAAA,QACA,QAAQ;AAAA,QACR,WAAW;AAAA,QACX,cAAc;AAAA,QACd,eAAe,IAAI,KAAK,IAAI,QAAQ,IAAI,KAAK,KAAK,iBAAiB,CAAC,EAAE,YAAY;AAAA,QAClF,YAAY,IAAI,YAAY;AAAA,MAC9B,GAAG,EAAE,SAAS,WAAW,CAAC;AAAA,IAC5B,SAAS,KAAK;AACZ,WAAK,QAAQ,OAAO,kCAAkC,GAAU;AAAA,IAClE;AAAA,EACF;AAAA,EAEA,MAAc,SAAS,IAAiC;AACtD,UAAM,OAAO,MAAM,KAAK,OAAO,KAAK,aAAa;AAAA,MAC/C,OAAO,EAAE,GAAG;AAAA,MACZ,OAAO;AAAA,MACP,SAAS;AAAA,IACX,CAAC;AACD,WAAO,OAAO,CAAC,KAAK;AAAA,EACtB;AAAA,EAEQ,YAAY,GAA4B;AAC9C,WAAO;AAAA,MACL,IAAI,OAAO,EAAE,EAAE;AAAA,MACf,OAAO,OAAO,EAAE,KAAK;AAAA,MACrB,MAAM,UAAU,EAAE,YAAY;AAAA,MAC9B,QAAQ,EAAE;AAAA,MACV,UAAU,EAAE,YAAY;AAAA,MACxB,aAAa,EAAE,gBAAgB,KAAK,KAAK;AAAA,MACzC,cAAc,EAAE,iBAAiB;AAAA,MACjC,UAAU,EAAE,aAAa;AAAA,MACzB,aAAa,EAAE,gBAAgB;AAAA,MAC/B,WAAW,EAAE,cAAc;AAAA,MAC3B,gBAAgB,EAAE,mBAAmB;AAAA,MACrC,UAAU,UAAU,EAAE,aAAa;AAAA,MACnC,WAAW,EAAE,cAAc,OAAO,KAAK,KAAK;AAAA,MAC5C,WAAW,EAAE,cAAc;AAAA,MAC3B,aAAa,EAAE,gBAAgB;AAAA,IACjC;AAAA,EACF;AAAA,EAEQ,MAAY;AAClB,WAAO,KAAK,OAAO,IAAI,KAAK,oBAAI,KAAK;AAAA,EACvC;AACF;;;AHpYO,IAAM,qBAAN,MAA2C;AAAA,EAQhD,YAAY,UAAqC,CAAC,GAAG;AAPrD,gBAAO;AACP,mBAAU;AACV,gBAAO;AAML,SAAK,UAAU,EAAE,SAAS,QAAQ,GAAG,QAAQ;AAAA,EAC/C;AAAA,EAEA,MAAM,KAAK,KAAmC;AAE5C,QAAI;AACF,UAAI,WAAuC,UAAU,EAAE,SAAS;AAAA,QAC9D,IAAI;AAAA,QACJ,MAAM;AAAA,QACN,SAAS;AAAA,QACT,MAAM;AAAA,QACN,OAAO;AAAA,QACP,mBAAmB;AAAA,QACnB,WAAW;AAAA,QACX,SAAS,CAAC,wBAAW;AAAA,MACvB,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,UAAI,OAAO,KAAK,kFAAkF,GAAU;AAAA,IAC9G;AAEA,UAAM,SAAS,KAAK,QAAQ,WAAW;AAEvC,QAAI,WAAW,UAAU;AACvB,YAAM,IAAI;AAAA,QACR;AAAA,MAEF;AAAA,IACF;AAEA,QAAI,WAAW,UAAU;AACvB,YAAM,IAAI,IAAI,mBAAmB,KAAK,QAAQ,MAAM;AACpD,UAAI,gBAAgB,SAAS,CAAC;AAC9B,UAAI,OAAO,KAAK,mDAAmD;AACnE;AAAA,IACF;AAGA,QAAI,gBAAgB,SAAS,IAAI,mBAAmB,KAAK,QAAQ,MAAM,CAAC;AAExE,QAAI,KAAK,gBAAgB,YAAY;AACnC,UAAI,SAAc;AAClB,UAAI;AAAE,iBAAS,IAAI,WAAgB,UAAU;AAAA,MAAG,QAC1C;AAAE,YAAI;AAAE,mBAAS,IAAI,WAAgB,MAAM;AAAA,QAAG,QAAQ;AAAA,QAAe;AAAA,MAAE;AAE7E,UAAI,CAAC,QAAQ;AACX,YAAI,WAAW,MAAM;AACnB,cAAI,OAAO,KAAK,sGAAiG;AAAA,QACnH,OAAO;AACL,cAAI,OAAO,KAAK,6EAAwE;AAAA,QAC1F;AACA;AAAA,MACF;AAEA,WAAK,YAAY,IAAI,eAAe;AAAA,QAClC;AAAA,QACA,QAAQ,IAAI;AAAA,QACZ,SAAS,KAAK,QAAQ;AAAA,MACxB,CAAC;AAED,UAAI;AACF,QAAC,IAAY,iBAAiB,SAAS,KAAK,SAAS;AACrD,aAAK,UAAU,MAAM;AACrB,YAAI,OAAO,KAAK,4EAA4E;AAAA,MAC9F,SAAS,KAAK;AACZ,YAAI,OAAO,KAAK,4EAA4E,GAAU;AAAA,MACxG;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,UAAyB;AAC7B,UAAM,KAAK,WAAW,KAAK;AAAA,EAC7B;AACF;;;AIzFO,IAAM,qBAAN,MAAkD;AAAA,EAGvD,YAAY,SAAoC;AAC9C,SAAK,WAAW,QAAQ;AAAA,EAC1B;AAAA,EAEA,MAAM,QAAqB,QAAgB,OAAU,UAAiD;AACpG,UAAM,IAAI,MAAM,gDAAgD,KAAK,QAAQ,GAAG;AAAA,EAClF;AAAA,EAEA,MAAM,UAAuB,QAAgB,UAA0C;AACrF,UAAM,IAAI,MAAM,wCAAwC;AAAA,EAC1D;AAAA,EAEA,MAAM,YAAY,QAA+B;AAC/C,UAAM,IAAI,MAAM,wCAAwC;AAAA,EAC1D;AAAA,EAEA,MAAM,aAAa,QAAiC;AAClD,UAAM,IAAI,MAAM,wCAAwC;AAAA,EAC1D;AAAA,EAEA,MAAM,MAAM,QAA+B;AACzC,UAAM,IAAI,MAAM,wCAAwC;AAAA,EAC1D;AACF;","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'
|
|
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'
|
|
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.
|
|
3
|
+
"version": "9.5.0",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
|
-
"description": "Queue Service for ObjectStack — implements IQueueService with in-memory and
|
|
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.
|
|
18
|
-
"@objectstack/platform-objects": "9.
|
|
19
|
-
"@objectstack/spec": "9.
|
|
17
|
+
"@objectstack/core": "9.5.0",
|
|
18
|
+
"@objectstack/platform-objects": "9.5.0",
|
|
19
|
+
"@objectstack/spec": "9.5.0"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"@types/node": "^25.9.2",
|