bunqueue 1.0.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/LICENSE +21 -0
- package/README.md +640 -0
- package/dist/application/dlqManager.d.ts +19 -0
- package/dist/application/dlqManager.d.ts.map +1 -0
- package/dist/application/dlqManager.js +44 -0
- package/dist/application/dlqManager.js.map +1 -0
- package/dist/application/eventsManager.d.ts +28 -0
- package/dist/application/eventsManager.d.ts.map +1 -0
- package/dist/application/eventsManager.js +59 -0
- package/dist/application/eventsManager.js.map +1 -0
- package/dist/application/jobLogsManager.d.ts +20 -0
- package/dist/application/jobLogsManager.d.ts.map +1 -0
- package/dist/application/jobLogsManager.js +28 -0
- package/dist/application/jobLogsManager.js.map +1 -0
- package/dist/application/metricsExporter.d.ts +24 -0
- package/dist/application/metricsExporter.d.ts.map +1 -0
- package/dist/application/metricsExporter.js +80 -0
- package/dist/application/metricsExporter.js.map +1 -0
- package/dist/application/operations/ack.d.ts +48 -0
- package/dist/application/operations/ack.d.ts.map +1 -0
- package/dist/application/operations/ack.js +109 -0
- package/dist/application/operations/ack.js.map +1 -0
- package/dist/application/operations/index.d.ts +10 -0
- package/dist/application/operations/index.d.ts.map +1 -0
- package/dist/application/operations/index.js +10 -0
- package/dist/application/operations/index.js.map +1 -0
- package/dist/application/operations/jobManagement.d.ts +32 -0
- package/dist/application/operations/jobManagement.d.ts.map +1 -0
- package/dist/application/operations/jobManagement.js +122 -0
- package/dist/application/operations/jobManagement.js.map +1 -0
- package/dist/application/operations/pull.d.ts +36 -0
- package/dist/application/operations/pull.d.ts.map +1 -0
- package/dist/application/operations/pull.js +109 -0
- package/dist/application/operations/pull.js.map +1 -0
- package/dist/application/operations/push.d.ts +36 -0
- package/dist/application/operations/push.d.ts.map +1 -0
- package/dist/application/operations/push.js +94 -0
- package/dist/application/operations/push.js.map +1 -0
- package/dist/application/operations/queryOperations.d.ts +33 -0
- package/dist/application/operations/queryOperations.d.ts.map +1 -0
- package/dist/application/operations/queryOperations.js +57 -0
- package/dist/application/operations/queryOperations.js.map +1 -0
- package/dist/application/operations/queueControl.d.ts +32 -0
- package/dist/application/operations/queueControl.d.ts.map +1 -0
- package/dist/application/operations/queueControl.js +72 -0
- package/dist/application/operations/queueControl.js.map +1 -0
- package/dist/application/queueManager.d.ts +113 -0
- package/dist/application/queueManager.d.ts.map +1 -0
- package/dist/application/queueManager.js +406 -0
- package/dist/application/queueManager.js.map +1 -0
- package/dist/application/webhookManager.d.ts +35 -0
- package/dist/application/webhookManager.d.ts.map +1 -0
- package/dist/application/webhookManager.js +109 -0
- package/dist/application/webhookManager.js.map +1 -0
- package/dist/application/workerManager.d.ts +48 -0
- package/dist/application/workerManager.d.ts.map +1 -0
- package/dist/application/workerManager.js +121 -0
- package/dist/application/workerManager.js.map +1 -0
- package/dist/domain/queue/index.d.ts +6 -0
- package/dist/domain/queue/index.d.ts.map +1 -0
- package/dist/domain/queue/index.js +6 -0
- package/dist/domain/queue/index.js.map +1 -0
- package/dist/domain/queue/priorityQueue.d.ts +45 -0
- package/dist/domain/queue/priorityQueue.d.ts.map +1 -0
- package/dist/domain/queue/priorityQueue.js +203 -0
- package/dist/domain/queue/priorityQueue.js.map +1 -0
- package/dist/domain/queue/shard.d.ts +98 -0
- package/dist/domain/queue/shard.d.ts.map +1 -0
- package/dist/domain/queue/shard.js +245 -0
- package/dist/domain/queue/shard.js.map +1 -0
- package/dist/domain/types/command.d.ts +270 -0
- package/dist/domain/types/command.d.ts.map +1 -0
- package/dist/domain/types/command.js +6 -0
- package/dist/domain/types/command.js.map +1 -0
- package/dist/domain/types/cron.d.ts +32 -0
- package/dist/domain/types/cron.d.ts.map +1 -0
- package/dist/domain/types/cron.js +31 -0
- package/dist/domain/types/cron.js.map +1 -0
- package/dist/domain/types/index.d.ts +9 -0
- package/dist/domain/types/index.d.ts.map +1 -0
- package/dist/domain/types/index.js +9 -0
- package/dist/domain/types/index.js.map +1 -0
- package/dist/domain/types/job.d.ts +94 -0
- package/dist/domain/types/job.d.ts.map +1 -0
- package/dist/domain/types/job.js +81 -0
- package/dist/domain/types/job.js.map +1 -0
- package/dist/domain/types/queue.d.ts +86 -0
- package/dist/domain/types/queue.d.ts.map +1 -0
- package/dist/domain/types/queue.js +84 -0
- package/dist/domain/types/queue.js.map +1 -0
- package/dist/domain/types/response.d.ts +158 -0
- package/dist/domain/types/response.d.ts.map +1 -0
- package/dist/domain/types/response.js +86 -0
- package/dist/domain/types/response.js.map +1 -0
- package/dist/domain/types/webhook.d.ts +33 -0
- package/dist/domain/types/webhook.d.ts.map +1 -0
- package/dist/domain/types/webhook.js +20 -0
- package/dist/domain/types/webhook.js.map +1 -0
- package/dist/domain/types/worker.d.ts +27 -0
- package/dist/domain/types/worker.d.ts.map +1 -0
- package/dist/domain/types/worker.js +27 -0
- package/dist/domain/types/worker.js.map +1 -0
- package/dist/infrastructure/persistence/index.d.ts +6 -0
- package/dist/infrastructure/persistence/index.d.ts.map +1 -0
- package/dist/infrastructure/persistence/index.js +6 -0
- package/dist/infrastructure/persistence/index.js.map +1 -0
- package/dist/infrastructure/persistence/schema.d.ts +14 -0
- package/dist/infrastructure/persistence/schema.d.ts.map +1 -0
- package/dist/infrastructure/persistence/schema.js +123 -0
- package/dist/infrastructure/persistence/schema.js.map +1 -0
- package/dist/infrastructure/persistence/sqlite.d.ts +42 -0
- package/dist/infrastructure/persistence/sqlite.d.ts.map +1 -0
- package/dist/infrastructure/persistence/sqlite.js +164 -0
- package/dist/infrastructure/persistence/sqlite.js.map +1 -0
- package/dist/infrastructure/persistence/statements.d.ts +55 -0
- package/dist/infrastructure/persistence/statements.d.ts.map +1 -0
- package/dist/infrastructure/persistence/statements.js +42 -0
- package/dist/infrastructure/persistence/statements.js.map +1 -0
- package/dist/infrastructure/scheduler/cronParser.d.ts +44 -0
- package/dist/infrastructure/scheduler/cronParser.d.ts.map +1 -0
- package/dist/infrastructure/scheduler/cronParser.js +90 -0
- package/dist/infrastructure/scheduler/cronParser.js.map +1 -0
- package/dist/infrastructure/scheduler/cronScheduler.d.ts +68 -0
- package/dist/infrastructure/scheduler/cronScheduler.d.ts.map +1 -0
- package/dist/infrastructure/scheduler/cronScheduler.js +172 -0
- package/dist/infrastructure/scheduler/cronScheduler.js.map +1 -0
- package/dist/infrastructure/scheduler/index.d.ts +6 -0
- package/dist/infrastructure/scheduler/index.d.ts.map +1 -0
- package/dist/infrastructure/scheduler/index.js +6 -0
- package/dist/infrastructure/scheduler/index.js.map +1 -0
- package/dist/infrastructure/server/handler.d.ts +13 -0
- package/dist/infrastructure/server/handler.d.ts.map +1 -0
- package/dist/infrastructure/server/handler.js +167 -0
- package/dist/infrastructure/server/handler.js.map +1 -0
- package/dist/infrastructure/server/handlers/advanced.d.ts +68 -0
- package/dist/infrastructure/server/handlers/advanced.d.ts.map +1 -0
- package/dist/infrastructure/server/handlers/advanced.js +99 -0
- package/dist/infrastructure/server/handlers/advanced.js.map +1 -0
- package/dist/infrastructure/server/handlers/core.d.ts +36 -0
- package/dist/infrastructure/server/handlers/core.d.ts.map +1 -0
- package/dist/infrastructure/server/handlers/core.js +82 -0
- package/dist/infrastructure/server/handlers/core.js.map +1 -0
- package/dist/infrastructure/server/handlers/cron.d.ts +20 -0
- package/dist/infrastructure/server/handlers/cron.d.ts.map +1 -0
- package/dist/infrastructure/server/handlers/cron.js +56 -0
- package/dist/infrastructure/server/handlers/cron.js.map +1 -0
- package/dist/infrastructure/server/handlers/dlq.d.ts +20 -0
- package/dist/infrastructure/server/handlers/dlq.d.ts.map +1 -0
- package/dist/infrastructure/server/handlers/dlq.js +31 -0
- package/dist/infrastructure/server/handlers/dlq.js.map +1 -0
- package/dist/infrastructure/server/handlers/index.d.ts +7 -0
- package/dist/infrastructure/server/handlers/index.d.ts.map +1 -0
- package/dist/infrastructure/server/handlers/index.js +7 -0
- package/dist/infrastructure/server/handlers/index.js.map +1 -0
- package/dist/infrastructure/server/handlers/management.d.ts +36 -0
- package/dist/infrastructure/server/handlers/management.d.ts.map +1 -0
- package/dist/infrastructure/server/handlers/management.js +75 -0
- package/dist/infrastructure/server/handlers/management.js.map +1 -0
- package/dist/infrastructure/server/handlers/monitoring.d.ts +18 -0
- package/dist/infrastructure/server/handlers/monitoring.d.ts.map +1 -0
- package/dist/infrastructure/server/handlers/monitoring.js +102 -0
- package/dist/infrastructure/server/handlers/monitoring.js.map +1 -0
- package/dist/infrastructure/server/handlers/query.d.ts +32 -0
- package/dist/infrastructure/server/handlers/query.d.ts.map +1 -0
- package/dist/infrastructure/server/handlers/query.js +61 -0
- package/dist/infrastructure/server/handlers/query.js.map +1 -0
- package/dist/infrastructure/server/http.d.ts +43 -0
- package/dist/infrastructure/server/http.d.ts.map +1 -0
- package/dist/infrastructure/server/http.js +344 -0
- package/dist/infrastructure/server/http.js.map +1 -0
- package/dist/infrastructure/server/index.d.ts +8 -0
- package/dist/infrastructure/server/index.d.ts.map +1 -0
- package/dist/infrastructure/server/index.js +8 -0
- package/dist/infrastructure/server/index.js.map +1 -0
- package/dist/infrastructure/server/protocol.d.ts +44 -0
- package/dist/infrastructure/server/protocol.d.ts.map +1 -0
- package/dist/infrastructure/server/protocol.js +129 -0
- package/dist/infrastructure/server/protocol.js.map +1 -0
- package/dist/infrastructure/server/rateLimiter.d.ts +31 -0
- package/dist/infrastructure/server/rateLimiter.d.ts.map +1 -0
- package/dist/infrastructure/server/rateLimiter.js +79 -0
- package/dist/infrastructure/server/rateLimiter.js.map +1 -0
- package/dist/infrastructure/server/tcp.d.ts +36 -0
- package/dist/infrastructure/server/tcp.d.ts.map +1 -0
- package/dist/infrastructure/server/tcp.js +101 -0
- package/dist/infrastructure/server/tcp.js.map +1 -0
- package/dist/infrastructure/server/types.d.ts +11 -0
- package/dist/infrastructure/server/types.d.ts.map +1 -0
- package/dist/infrastructure/server/types.js +5 -0
- package/dist/infrastructure/server/types.js.map +1 -0
- package/dist/main.d.ts +6 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +111 -0
- package/dist/main.js.map +1 -0
- package/dist/shared/hash.d.ts +30 -0
- package/dist/shared/hash.d.ts.map +1 -0
- package/dist/shared/hash.js +69 -0
- package/dist/shared/hash.js.map +1 -0
- package/dist/shared/index.d.ts +6 -0
- package/dist/shared/index.d.ts.map +1 -0
- package/dist/shared/index.js +6 -0
- package/dist/shared/index.js.map +1 -0
- package/dist/shared/lock.d.ts +70 -0
- package/dist/shared/lock.d.ts.map +1 -0
- package/dist/shared/lock.js +207 -0
- package/dist/shared/lock.js.map +1 -0
- package/dist/shared/logger.d.ts +38 -0
- package/dist/shared/logger.d.ts.map +1 -0
- package/dist/shared/logger.js +74 -0
- package/dist/shared/logger.js.map +1 -0
- package/dist/shared/serialization.d.ts +23 -0
- package/dist/shared/serialization.d.ts.map +1 -0
- package/dist/shared/serialization.js +63 -0
- package/dist/shared/serialization.js.map +1 -0
- package/package.json +82 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 egeominotti
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,640 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src=".github/banner.svg" alt="bunQ - High-performance job queue for Bun" width="700" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<a href="https://github.com/egeominotti/bunq/actions"><img src="https://github.com/egeominotti/bunq/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
7
|
+
<a href="https://github.com/egeominotti/bunq/releases"><img src="https://img.shields.io/github/v/release/egeominotti/bunq" alt="Release"></a>
|
|
8
|
+
<a href="https://github.com/egeominotti/bunq/blob/main/LICENSE"><img src="https://img.shields.io/github/license/egeominotti/bunq" alt="License"></a>
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
<a href="#features">Features</a> •
|
|
13
|
+
<a href="#sdk">SDK</a> •
|
|
14
|
+
<a href="#quick-start">Quick Start</a> •
|
|
15
|
+
<a href="#installation">Installation</a> •
|
|
16
|
+
<a href="#api-reference">API</a> •
|
|
17
|
+
<a href="#docker">Docker</a>
|
|
18
|
+
</p>
|
|
19
|
+
|
|
20
|
+
<p align="center">
|
|
21
|
+
<a href="https://www.npmjs.com/package/flashq"><img src="https://img.shields.io/npm/v/flashq" alt="npm"></a>
|
|
22
|
+
<a href="https://www.npmjs.com/package/flashq"><img src="https://img.shields.io/npm/dm/flashq" alt="npm downloads"></a>
|
|
23
|
+
</p>
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Features
|
|
28
|
+
|
|
29
|
+
- **Blazing Fast** — Built on Bun runtime with native SQLite, optimized for maximum throughput
|
|
30
|
+
- **Persistent Storage** — SQLite with WAL mode for durability and concurrent access
|
|
31
|
+
- **Priority Queues** — FIFO, LIFO, and priority-based job ordering
|
|
32
|
+
- **Delayed Jobs** — Schedule jobs to run at specific times
|
|
33
|
+
- **Cron Scheduling** — Recurring jobs with cron expressions or fixed intervals
|
|
34
|
+
- **Retry & Backoff** — Automatic retries with exponential backoff
|
|
35
|
+
- **Dead Letter Queue** — Failed jobs preserved for inspection and retry
|
|
36
|
+
- **Job Dependencies** — Define parent-child relationships and execution order
|
|
37
|
+
- **Progress Tracking** — Real-time progress updates for long-running jobs
|
|
38
|
+
- **Rate Limiting** — Per-queue rate limits and concurrency control
|
|
39
|
+
- **Webhooks** — HTTP callbacks on job events
|
|
40
|
+
- **Real-time Events** — WebSocket and Server-Sent Events (SSE) support
|
|
41
|
+
- **Prometheus Metrics** — Built-in metrics endpoint for monitoring
|
|
42
|
+
- **Authentication** — Token-based auth for secure access
|
|
43
|
+
- **Dual Protocol** — TCP (high performance) and HTTP/REST (compatibility)
|
|
44
|
+
|
|
45
|
+
## SDK
|
|
46
|
+
|
|
47
|
+
Install the official TypeScript SDK to use bunQ in your Bun applications.
|
|
48
|
+
|
|
49
|
+
> **Note:** The SDK requires Bun runtime and a running bunQ server.
|
|
50
|
+
|
|
51
|
+
### Install
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
bun add flashq
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Basic Usage
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
import { Queue, Worker } from 'flashq';
|
|
61
|
+
|
|
62
|
+
// Create a queue
|
|
63
|
+
const queue = new Queue('my-queue', {
|
|
64
|
+
connection: { host: 'localhost', port: 6789 }
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Add a job
|
|
68
|
+
await queue.add('process-data', { userId: 123, action: 'sync' });
|
|
69
|
+
|
|
70
|
+
// Add with options
|
|
71
|
+
await queue.add('send-email',
|
|
72
|
+
{ to: 'user@example.com', subject: 'Hello' },
|
|
73
|
+
{
|
|
74
|
+
priority: 10,
|
|
75
|
+
delay: 5000, // 5 seconds
|
|
76
|
+
attempts: 3,
|
|
77
|
+
backoff: { type: 'exponential', delay: 1000 }
|
|
78
|
+
}
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
// Create a worker
|
|
82
|
+
const worker = new Worker('my-queue', async (job) => {
|
|
83
|
+
console.log('Processing:', job.name, job.data);
|
|
84
|
+
|
|
85
|
+
// Update progress
|
|
86
|
+
await job.updateProgress(50);
|
|
87
|
+
|
|
88
|
+
// Do work...
|
|
89
|
+
|
|
90
|
+
return { success: true };
|
|
91
|
+
}, {
|
|
92
|
+
connection: { host: 'localhost', port: 6789 },
|
|
93
|
+
concurrency: 5
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// Handle events
|
|
97
|
+
worker.on('completed', (job, result) => {
|
|
98
|
+
console.log(`Job ${job.id} completed:`, result);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
worker.on('failed', (job, err) => {
|
|
102
|
+
console.error(`Job ${job.id} failed:`, err.message);
|
|
103
|
+
});
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Cron Jobs
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
import { Queue } from 'flashq';
|
|
110
|
+
|
|
111
|
+
const queue = new Queue('scheduled', {
|
|
112
|
+
connection: { host: 'localhost', port: 6789 }
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// Every hour
|
|
116
|
+
await queue.upsertJobScheduler('hourly-report',
|
|
117
|
+
{ pattern: '0 * * * *' },
|
|
118
|
+
{ name: 'generate-report', data: { type: 'hourly' } }
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
// Every 5 minutes
|
|
122
|
+
await queue.upsertJobScheduler('health-check',
|
|
123
|
+
{ every: 300000 },
|
|
124
|
+
{ name: 'ping', data: {} }
|
|
125
|
+
);
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Job Dependencies (Flows)
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
import { FlowProducer } from 'flashq';
|
|
132
|
+
|
|
133
|
+
const flow = new FlowProducer({
|
|
134
|
+
connection: { host: 'localhost', port: 6789 }
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// Create a flow with parent-child dependencies
|
|
138
|
+
await flow.add({
|
|
139
|
+
name: 'final-step',
|
|
140
|
+
queueName: 'pipeline',
|
|
141
|
+
data: { step: 'aggregate' },
|
|
142
|
+
children: [
|
|
143
|
+
{
|
|
144
|
+
name: 'step-1',
|
|
145
|
+
queueName: 'pipeline',
|
|
146
|
+
data: { step: 'fetch' }
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
name: 'step-2',
|
|
150
|
+
queueName: 'pipeline',
|
|
151
|
+
data: { step: 'transform' }
|
|
152
|
+
}
|
|
153
|
+
]
|
|
154
|
+
});
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Real-time Events
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
import { QueueEvents } from 'flashq';
|
|
161
|
+
|
|
162
|
+
const events = new QueueEvents('my-queue', {
|
|
163
|
+
connection: { host: 'localhost', port: 6789 }
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
events.on('completed', ({ jobId, returnvalue }) => {
|
|
167
|
+
console.log(`Job ${jobId} completed with:`, returnvalue);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
events.on('failed', ({ jobId, failedReason }) => {
|
|
171
|
+
console.error(`Job ${jobId} failed:`, failedReason);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
events.on('progress', ({ jobId, data }) => {
|
|
175
|
+
console.log(`Job ${jobId} progress:`, data);
|
|
176
|
+
});
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
For more examples, see the [SDK documentation](https://www.npmjs.com/package/flashq).
|
|
180
|
+
|
|
181
|
+
## Quick Start
|
|
182
|
+
|
|
183
|
+
### Start the Server
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
# Using Bun directly
|
|
187
|
+
bun run src/main.ts
|
|
188
|
+
|
|
189
|
+
# Or with Docker
|
|
190
|
+
docker run -p 6789:6789 -p 6790:6790 ghcr.io/egeominotti/bunq
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Push a Job (HTTP)
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
curl -X POST http://localhost:6790/queues/emails/jobs \
|
|
197
|
+
-H "Content-Type: application/json" \
|
|
198
|
+
-d '{"data": {"to": "user@example.com", "subject": "Hello"}}'
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Pull a Job (HTTP)
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
curl http://localhost:6790/queues/emails/jobs
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Acknowledge Completion
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
curl -X POST http://localhost:6790/jobs/1/ack \
|
|
211
|
+
-H "Content-Type: application/json" \
|
|
212
|
+
-d '{"result": {"sent": true}}'
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## Installation
|
|
216
|
+
|
|
217
|
+
### From Source
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
git clone https://github.com/egeominotti/bunq.git
|
|
221
|
+
cd bunq
|
|
222
|
+
bun install
|
|
223
|
+
bun run start
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Build Binary
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
bun run build
|
|
230
|
+
./dist/bunq
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Docker
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
docker pull ghcr.io/egeominotti/bunq
|
|
237
|
+
docker run -d \
|
|
238
|
+
-p 6789:6789 \
|
|
239
|
+
-p 6790:6790 \
|
|
240
|
+
-v bunq-data:/app/data \
|
|
241
|
+
ghcr.io/egeominotti/bunq
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Docker Compose
|
|
245
|
+
|
|
246
|
+
```yaml
|
|
247
|
+
version: "3.8"
|
|
248
|
+
services:
|
|
249
|
+
bunq:
|
|
250
|
+
image: ghcr.io/egeominotti/bunq
|
|
251
|
+
ports:
|
|
252
|
+
- "6789:6789"
|
|
253
|
+
- "6790:6790"
|
|
254
|
+
volumes:
|
|
255
|
+
- bunq-data:/app/data
|
|
256
|
+
environment:
|
|
257
|
+
- AUTH_TOKENS=your-secret-token
|
|
258
|
+
|
|
259
|
+
volumes:
|
|
260
|
+
bunq-data:
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
## Usage
|
|
264
|
+
|
|
265
|
+
### TCP Protocol (High Performance)
|
|
266
|
+
|
|
267
|
+
Connect via TCP for maximum throughput. Commands are newline-delimited JSON.
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
# Connect with netcat
|
|
271
|
+
nc localhost 6789
|
|
272
|
+
|
|
273
|
+
# Push a job
|
|
274
|
+
{"cmd":"PUSH","queue":"tasks","data":{"action":"process"}}
|
|
275
|
+
|
|
276
|
+
# Pull a job
|
|
277
|
+
{"cmd":"PULL","queue":"tasks"}
|
|
278
|
+
|
|
279
|
+
# Acknowledge
|
|
280
|
+
{"cmd":"ACK","id":"1"}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### HTTP REST API
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
# Push job
|
|
287
|
+
curl -X POST http://localhost:6790/queues/tasks/jobs \
|
|
288
|
+
-H "Content-Type: application/json" \
|
|
289
|
+
-d '{
|
|
290
|
+
"data": {"action": "process"},
|
|
291
|
+
"priority": 10,
|
|
292
|
+
"delay": 5000,
|
|
293
|
+
"maxAttempts": 5
|
|
294
|
+
}'
|
|
295
|
+
|
|
296
|
+
# Pull job (with timeout)
|
|
297
|
+
curl "http://localhost:6790/queues/tasks/jobs?timeout=30000"
|
|
298
|
+
|
|
299
|
+
# Get job by ID
|
|
300
|
+
curl http://localhost:6790/jobs/123
|
|
301
|
+
|
|
302
|
+
# Fail a job
|
|
303
|
+
curl -X POST http://localhost:6790/jobs/123/fail \
|
|
304
|
+
-H "Content-Type: application/json" \
|
|
305
|
+
-d '{"error": "Processing failed"}'
|
|
306
|
+
|
|
307
|
+
# Get stats
|
|
308
|
+
curl http://localhost:6790/stats
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### WebSocket (Real-time)
|
|
312
|
+
|
|
313
|
+
```javascript
|
|
314
|
+
const ws = new WebSocket('ws://localhost:6790/ws');
|
|
315
|
+
|
|
316
|
+
ws.onmessage = (event) => {
|
|
317
|
+
const job = JSON.parse(event.data);
|
|
318
|
+
console.log('Job event:', job);
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
// Subscribe to specific queue
|
|
322
|
+
const wsQueue = new WebSocket('ws://localhost:6790/ws/queues/emails');
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### Server-Sent Events (SSE)
|
|
326
|
+
|
|
327
|
+
```javascript
|
|
328
|
+
const events = new EventSource('http://localhost:6790/events');
|
|
329
|
+
|
|
330
|
+
events.onmessage = (event) => {
|
|
331
|
+
const data = JSON.parse(event.data);
|
|
332
|
+
console.log('Event:', data);
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
// Filter by queue
|
|
336
|
+
const queueEvents = new EventSource('http://localhost:6790/events/queues/emails');
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### Job Options
|
|
340
|
+
|
|
341
|
+
| Option | Type | Default | Description |
|
|
342
|
+
|--------|------|---------|-------------|
|
|
343
|
+
| `data` | any | required | Job payload |
|
|
344
|
+
| `priority` | number | 0 | Higher = processed first |
|
|
345
|
+
| `delay` | number | 0 | Delay in milliseconds |
|
|
346
|
+
| `maxAttempts` | number | 3 | Max retry attempts |
|
|
347
|
+
| `backoff` | number | 1000 | Initial backoff (ms), doubles each retry |
|
|
348
|
+
| `ttl` | number | null | Time-to-live in milliseconds |
|
|
349
|
+
| `timeout` | number | null | Job processing timeout |
|
|
350
|
+
| `uniqueKey` | string | null | Deduplication key |
|
|
351
|
+
| `jobId` | string | null | Custom job identifier |
|
|
352
|
+
| `dependsOn` | string[] | [] | Job IDs that must complete first |
|
|
353
|
+
| `tags` | string[] | [] | Tags for filtering |
|
|
354
|
+
| `groupId` | string | null | Group identifier |
|
|
355
|
+
| `lifo` | boolean | false | Last-in-first-out ordering |
|
|
356
|
+
| `removeOnComplete` | boolean | false | Auto-delete on completion |
|
|
357
|
+
| `removeOnFail` | boolean | false | Auto-delete on failure |
|
|
358
|
+
|
|
359
|
+
### Cron Jobs
|
|
360
|
+
|
|
361
|
+
```bash
|
|
362
|
+
# Cron expression (every hour)
|
|
363
|
+
curl -X POST http://localhost:6790/cron \
|
|
364
|
+
-d '{"cmd":"Cron","name":"hourly-cleanup","queue":"maintenance","data":{"task":"cleanup"},"schedule":"0 * * * *"}'
|
|
365
|
+
|
|
366
|
+
# Fixed interval (every 5 minutes)
|
|
367
|
+
curl -X POST http://localhost:6790/cron \
|
|
368
|
+
-d '{"cmd":"Cron","name":"health-check","queue":"monitoring","data":{"check":"ping"},"repeatEvery":300000}'
|
|
369
|
+
|
|
370
|
+
# With execution limit
|
|
371
|
+
curl -X POST http://localhost:6790/cron \
|
|
372
|
+
-d '{"cmd":"Cron","name":"one-time-migration","queue":"migrations","data":{},"repeatEvery":0,"maxLimit":1}'
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
## API Reference
|
|
376
|
+
|
|
377
|
+
### Core Operations
|
|
378
|
+
|
|
379
|
+
| Command | Description |
|
|
380
|
+
|---------|-------------|
|
|
381
|
+
| `PUSH` | Add a job to a queue |
|
|
382
|
+
| `PUSHB` | Batch push multiple jobs |
|
|
383
|
+
| `PULL` | Get the next job from a queue |
|
|
384
|
+
| `PULLB` | Batch pull multiple jobs |
|
|
385
|
+
| `ACK` | Mark job as completed |
|
|
386
|
+
| `ACKB` | Batch acknowledge jobs |
|
|
387
|
+
| `FAIL` | Mark job as failed |
|
|
388
|
+
|
|
389
|
+
### Query Operations
|
|
390
|
+
|
|
391
|
+
| Command | Description |
|
|
392
|
+
|---------|-------------|
|
|
393
|
+
| `GetJob` | Get job by ID |
|
|
394
|
+
| `GetState` | Get job state |
|
|
395
|
+
| `GetResult` | Get job result |
|
|
396
|
+
| `GetJobs` | List jobs with filters |
|
|
397
|
+
| `GetJobCounts` | Count jobs by state |
|
|
398
|
+
| `GetJobByCustomId` | Find job by custom ID |
|
|
399
|
+
| `GetProgress` | Get job progress |
|
|
400
|
+
| `GetLogs` | Get job logs |
|
|
401
|
+
|
|
402
|
+
### Job Management
|
|
403
|
+
|
|
404
|
+
| Command | Description |
|
|
405
|
+
|---------|-------------|
|
|
406
|
+
| `Cancel` | Cancel a pending job |
|
|
407
|
+
| `Progress` | Update job progress |
|
|
408
|
+
| `Update` | Update job data |
|
|
409
|
+
| `ChangePriority` | Change job priority |
|
|
410
|
+
| `Promote` | Move delayed job to waiting |
|
|
411
|
+
| `MoveToDelayed` | Delay a waiting job |
|
|
412
|
+
| `Discard` | Discard a job |
|
|
413
|
+
| `Heartbeat` | Send job heartbeat |
|
|
414
|
+
| `AddLog` | Add log entry to job |
|
|
415
|
+
|
|
416
|
+
### Queue Control
|
|
417
|
+
|
|
418
|
+
| Command | Description |
|
|
419
|
+
|---------|-------------|
|
|
420
|
+
| `Pause` | Pause queue processing |
|
|
421
|
+
| `Resume` | Resume queue processing |
|
|
422
|
+
| `IsPaused` | Check if queue is paused |
|
|
423
|
+
| `Drain` | Remove all waiting jobs |
|
|
424
|
+
| `Obliterate` | Remove all queue data |
|
|
425
|
+
| `Clean` | Remove old jobs |
|
|
426
|
+
| `ListQueues` | List all queues |
|
|
427
|
+
| `RateLimit` | Set queue rate limit |
|
|
428
|
+
| `SetConcurrency` | Set max concurrent jobs |
|
|
429
|
+
|
|
430
|
+
### Dead Letter Queue
|
|
431
|
+
|
|
432
|
+
| Command | Description |
|
|
433
|
+
|---------|-------------|
|
|
434
|
+
| `Dlq` | Get failed jobs |
|
|
435
|
+
| `RetryDlq` | Retry failed jobs |
|
|
436
|
+
| `PurgeDlq` | Clear failed jobs |
|
|
437
|
+
|
|
438
|
+
### Scheduling
|
|
439
|
+
|
|
440
|
+
| Command | Description |
|
|
441
|
+
|---------|-------------|
|
|
442
|
+
| `Cron` | Create/update cron job |
|
|
443
|
+
| `CronDelete` | Delete cron job |
|
|
444
|
+
| `CronList` | List all cron jobs |
|
|
445
|
+
|
|
446
|
+
### Workers & Webhooks
|
|
447
|
+
|
|
448
|
+
| Command | Description |
|
|
449
|
+
|---------|-------------|
|
|
450
|
+
| `RegisterWorker` | Register a worker |
|
|
451
|
+
| `UnregisterWorker` | Unregister a worker |
|
|
452
|
+
| `ListWorkers` | List active workers |
|
|
453
|
+
| `AddWebhook` | Add webhook endpoint |
|
|
454
|
+
| `RemoveWebhook` | Remove webhook |
|
|
455
|
+
| `ListWebhooks` | List webhooks |
|
|
456
|
+
|
|
457
|
+
### Monitoring
|
|
458
|
+
|
|
459
|
+
| Command | Description |
|
|
460
|
+
|---------|-------------|
|
|
461
|
+
| `Stats` | Get server statistics |
|
|
462
|
+
| `Metrics` | Get job metrics |
|
|
463
|
+
| `Prometheus` | Prometheus format metrics |
|
|
464
|
+
|
|
465
|
+
## Configuration
|
|
466
|
+
|
|
467
|
+
### Environment Variables
|
|
468
|
+
|
|
469
|
+
| Variable | Default | Description |
|
|
470
|
+
|----------|---------|-------------|
|
|
471
|
+
| `TCP_PORT` | 6789 | TCP protocol port |
|
|
472
|
+
| `HTTP_PORT` | 6790 | HTTP/WebSocket port |
|
|
473
|
+
| `HOST` | 0.0.0.0 | Bind address |
|
|
474
|
+
| `AUTH_TOKENS` | - | Comma-separated auth tokens |
|
|
475
|
+
| `DATA_PATH` | - | SQLite database path (in-memory if not set) |
|
|
476
|
+
| `CORS_ALLOW_ORIGIN` | * | Allowed CORS origins |
|
|
477
|
+
|
|
478
|
+
### Authentication
|
|
479
|
+
|
|
480
|
+
Enable authentication by setting `AUTH_TOKENS`:
|
|
481
|
+
|
|
482
|
+
```bash
|
|
483
|
+
AUTH_TOKENS=token1,token2 bun run start
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
**HTTP:**
|
|
487
|
+
```bash
|
|
488
|
+
curl -H "Authorization: Bearer token1" http://localhost:6790/stats
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
**TCP:**
|
|
492
|
+
```json
|
|
493
|
+
{"cmd":"Auth","token":"token1"}
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
**WebSocket:**
|
|
497
|
+
```json
|
|
498
|
+
{"cmd":"Auth","token":"token1"}
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
## Monitoring
|
|
502
|
+
|
|
503
|
+
### Health Check
|
|
504
|
+
|
|
505
|
+
```bash
|
|
506
|
+
curl http://localhost:6790/health
|
|
507
|
+
# {"ok":true,"status":"healthy"}
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
### Prometheus Metrics
|
|
511
|
+
|
|
512
|
+
```bash
|
|
513
|
+
curl http://localhost:6790/prometheus
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
Metrics include:
|
|
517
|
+
- `bunq_jobs_total{queue,state}` — Job counts by state
|
|
518
|
+
- `bunq_jobs_processed_total{queue}` — Total processed jobs
|
|
519
|
+
- `bunq_jobs_failed_total{queue}` — Total failed jobs
|
|
520
|
+
- `bunq_queue_latency_seconds{queue}` — Processing latency
|
|
521
|
+
|
|
522
|
+
### Statistics
|
|
523
|
+
|
|
524
|
+
```bash
|
|
525
|
+
curl http://localhost:6790/stats
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
```json
|
|
529
|
+
{
|
|
530
|
+
"ok": true,
|
|
531
|
+
"stats": {
|
|
532
|
+
"waiting": 150,
|
|
533
|
+
"active": 10,
|
|
534
|
+
"delayed": 25,
|
|
535
|
+
"completed": 10000,
|
|
536
|
+
"failed": 50,
|
|
537
|
+
"dlq": 5,
|
|
538
|
+
"totalPushed": 10235,
|
|
539
|
+
"totalPulled": 10085,
|
|
540
|
+
"totalCompleted": 10000,
|
|
541
|
+
"totalFailed": 50
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
## Docker
|
|
547
|
+
|
|
548
|
+
### Build
|
|
549
|
+
|
|
550
|
+
```bash
|
|
551
|
+
docker build -t bunq .
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
### Run
|
|
555
|
+
|
|
556
|
+
```bash
|
|
557
|
+
# Basic
|
|
558
|
+
docker run -p 6789:6789 -p 6790:6790 bunq
|
|
559
|
+
|
|
560
|
+
# With persistence
|
|
561
|
+
docker run -p 6789:6789 -p 6790:6790 \
|
|
562
|
+
-v bunq-data:/app/data \
|
|
563
|
+
-e DATA_PATH=/app/data/bunq.db \
|
|
564
|
+
bunq
|
|
565
|
+
|
|
566
|
+
# With authentication
|
|
567
|
+
docker run -p 6789:6789 -p 6790:6790 \
|
|
568
|
+
-e AUTH_TOKENS=secret1,secret2 \
|
|
569
|
+
bunq
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
### Docker Compose
|
|
573
|
+
|
|
574
|
+
```bash
|
|
575
|
+
# Production
|
|
576
|
+
docker compose up -d
|
|
577
|
+
|
|
578
|
+
# Development (hot reload)
|
|
579
|
+
docker compose --profile dev up bunq-dev
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
## Architecture
|
|
583
|
+
|
|
584
|
+
```
|
|
585
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
586
|
+
│ bunQ Server │
|
|
587
|
+
├─────────────────────────────────────────────────────────────┤
|
|
588
|
+
│ HTTP/WS (Bun.serve) │ TCP Protocol (Bun.listen) │
|
|
589
|
+
├─────────────────────────────────────────────────────────────┤
|
|
590
|
+
│ Core Engine │
|
|
591
|
+
│ ┌──────────┐ ┌──────────┐ ┌───────────┐ ┌──────────┐ │
|
|
592
|
+
│ │ Queues │ │ Workers │ │ Scheduler │ │ DLQ │ │
|
|
593
|
+
│ │ (32 shards) │ │ │ (Cron) │ │ │ │
|
|
594
|
+
│ └──────────┘ └──────────┘ └───────────┘ └──────────┘ │
|
|
595
|
+
├─────────────────────────────────────────────────────────────┤
|
|
596
|
+
│ SQLite (WAL mode, 256MB mmap) │
|
|
597
|
+
└─────────────────────────────────────────────────────────────┘
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
### Performance Optimizations
|
|
601
|
+
|
|
602
|
+
- **32 Shards** — Lock contention minimized with FNV-1a hash distribution
|
|
603
|
+
- **WAL Mode** — Concurrent reads during writes
|
|
604
|
+
- **Memory-mapped I/O** — 256MB mmap for fast access
|
|
605
|
+
- **Batch Operations** — Bulk inserts and updates
|
|
606
|
+
- **Bounded Collections** — Automatic memory cleanup
|
|
607
|
+
|
|
608
|
+
## Contributing
|
|
609
|
+
|
|
610
|
+
Contributions are welcome! Please read our contributing guidelines before submitting PRs.
|
|
611
|
+
|
|
612
|
+
```bash
|
|
613
|
+
# Install dependencies
|
|
614
|
+
bun install
|
|
615
|
+
|
|
616
|
+
# Run tests
|
|
617
|
+
bun test
|
|
618
|
+
|
|
619
|
+
# Run linter
|
|
620
|
+
bun run lint
|
|
621
|
+
|
|
622
|
+
# Format code
|
|
623
|
+
bun run format
|
|
624
|
+
|
|
625
|
+
# Type check
|
|
626
|
+
bun run typecheck
|
|
627
|
+
|
|
628
|
+
# Run all checks
|
|
629
|
+
bun run check
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
## License
|
|
633
|
+
|
|
634
|
+
MIT License — see [LICENSE](LICENSE) for details.
|
|
635
|
+
|
|
636
|
+
---
|
|
637
|
+
|
|
638
|
+
<p align="center">
|
|
639
|
+
Built with <a href="https://bun.sh">Bun</a> 🥟
|
|
640
|
+
</p>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DLQ Manager
|
|
3
|
+
* Dead Letter Queue operations
|
|
4
|
+
*/
|
|
5
|
+
import type { Job, JobId } from '../domain/types/job';
|
|
6
|
+
import type { JobLocation } from '../domain/types/queue';
|
|
7
|
+
import type { Shard } from '../domain/queue/shard';
|
|
8
|
+
/** Context for DLQ operations */
|
|
9
|
+
export interface DlqContext {
|
|
10
|
+
shards: Shard[];
|
|
11
|
+
jobIndex: Map<JobId, JobLocation>;
|
|
12
|
+
}
|
|
13
|
+
/** Get jobs from DLQ */
|
|
14
|
+
export declare function getDlqJobs(queue: string, ctx: DlqContext, count?: number): Job[];
|
|
15
|
+
/** Retry jobs from DLQ */
|
|
16
|
+
export declare function retryDlqJobs(queue: string, ctx: DlqContext, jobId?: JobId): number;
|
|
17
|
+
/** Purge all jobs from DLQ */
|
|
18
|
+
export declare function purgeDlqJobs(queue: string, ctx: DlqContext): number;
|
|
19
|
+
//# sourceMappingURL=dlqManager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dlqManager.d.ts","sourceRoot":"","sources":["../../src/application/dlqManager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAGnD,iCAAiC;AACjC,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,QAAQ,EAAE,GAAG,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;CACnC;AAED,wBAAwB;AACxB,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,GAAG,EAAE,CAIhF;AAED,0BAA0B;AAC1B,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,EAAE,KAAK,CAAC,EAAE,KAAK,GAAG,MAAM,CA6BlF;AAED,8BAA8B;AAC9B,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,GAAG,MAAM,CAGnE"}
|