Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
# TLQ User Guide

## TL;DR

TLQ is an in-memory message queue where you add messages via `/add` (returns a UUID), retrieve them via `/get` (which locks them in "Processing" state, making them invisible to other consumers), then either `/delete` them after successful processing or `/retry` them on failure (which returns them to "Ready" state with an incremented retry count) - all messages are lost on server restart since there's no persistence.

## Overview

TLQ (Tiny Little Queue) is an in-memory message queue that provides simple, reliable message processing with automatic state management. Messages are stored in memory only - there is no persistence across server restarts.

## Installation

### Using Cargo

Install TLQ directly from crates.io:

```bash
cargo install tlq
```

After installation, ensure `~/.cargo/bin` is in your PATH. You can verify the installation with:

```bash
tlq
```

If the command is not found, you can run it directly with:
```bash
~/.cargo/bin/tlq
```

### Using Docker

Run TLQ using the official Docker image:

```bash
docker run -p 1337:1337 nebojsa/tlq
```

### Building from Source

```bash
git clone https://github.com/nebjak/tlq.git
cd tlq
cargo build --release
./target/release/tlq
```

## Running the Server

Start TLQ with default settings:

```bash
tlq
```

The server will start on `http://localhost:1337`. You can verify it's running:

```bash
curl http://localhost:1337/hello
# Returns: "Hello World"
```

## Client Libraries

Official clients are available for:
- [Rust]https://crates.io/crates/tlq-client
- [Node.js]https://www.npmjs.com/package/tlq-client
- [Python]https://pypi.org/project/tlq-client/
- [Go]https://pkg.go.dev/github.com/skyaktech/tlq-client-go

Use whatever language your project needs - all clients provide the same functionality with idiomatic APIs for each language.

## Core Concepts

### Message Lifecycle

Messages in TLQ move through distinct states:

- **Ready** - Available for consumers to retrieve
- **Processing** - Locked by a consumer, invisible to others

### Message Structure

Every message contains:
- `id` - UUID v7 (time-ordered unique identifier)
- `body` - Message content (max 64KB)
- `state` - Current message state ("Ready", "Processing")
- `retry_count` - Number of retry attempts

## Operations

### Adding Messages

**POST /add**
```json
{"body": "Your message content"}
```

Returns the complete message object with generated UUID:
```json
{
  "id": "01234567-89ab-cdef-0123-456789abcdef",
  "body": "Your message content",
  "state": "Ready",
  "retry_count": 0
}
```

Messages are immediately available for consumption after being added.

### Retrieving Messages

**POST /get**
```json
{"count": 5}
```
Optional: `count` defaults to 1 if not specified.

Returns an array of messages. Retrieved messages:
- Automatically transition to **Processing** state
- Become invisible to other consumers
- Must be explicitly deleted or retried

```json
[
  {
    "id": "01234567-89ab-cdef-0123-456789abcdef",
    "body": "Message content",
    "state": "Processing",
    "retry_count": 0
  }
]
```

### Deleting Messages

**POST /delete**
```json
{"ids": ["uuid1", "uuid2"]}
```

Permanently removes messages from the queue. Use this after successful message processing. Returns "Success" on completion.

### Retrying Messages

**POST /retry**
```json
{"ids": ["uuid1", "uuid2"]}
```

Returns messages to the queue when processing fails:
- Changes state back to **Ready**
- Increments `retry_count`
- Makes message available for retrieval again
- Returns "Success" on completion

### Purging Queue

**POST /purge**
```json
{}
```

Removes all messages from the queue, including those being processed.

**⚠️ Warning:** This operation:
- Immediately deletes ALL messages in the queue
- Includes messages in "Processing" state
- Cannot be undone
- Returns "Success" on completion

Use cases:
- Clearing test data during development
- Emergency reset when queue is corrupted
- Starting fresh after configuration changes

### Health Check

**GET /hello**

Returns `"Hello World"` to verify server availability.

## Message Processing Pattern

1. **Consumer retrieves** message via `/get`
2. **Message locks** automatically (Processing state)
3. **Consumer processes** the message
4. **On success**: Delete message via `/delete`
5. **On failure**: Return to queue via `/retry`

## Important Notes

- **No persistence** - All messages lost on server restart
- **No TTL** - Messages remain until explicitly deleted
- **No dead letter queue** - Retry count increments but messages retry indefinitely
- **Single node only** - No clustering or replication
- **64KB limit** - Maximum message body size

## Examples

### Basic Workflow

```bash
# Add message
curl -X POST localhost:1337/add \
  -H "Content-Type: application/json" \
  -d '{"body":"Process this task"}'

# Get and process
curl -X POST localhost:1337/get \
  -H "Content-Type: application/json" \
  -d '{"count":1}'
# Returns message with ID

# Success - delete it
curl -X POST localhost:1337/delete \
  -H "Content-Type: application/json" \
  -d '{"ids":["returned-uuid-here"]}'

# OR Failure - retry it
curl -X POST localhost:1337/retry \
  -H "Content-Type: application/json" \
  -d '{"ids":["returned-uuid-here"]}'
```

### Batch Processing

```bash
# Add multiple messages
curl -X POST localhost:1337/add \
  -H "Content-Type: application/json" \
  -d '{"body":"Task 1"}'

curl -X POST localhost:1337/add \
  -H "Content-Type: application/json" \
  -d '{"body":"Task 2"}'

# Get multiple messages at once
curl -X POST localhost:1337/get \
  -H "Content-Type: application/json" \
  -d '{"count":10}'

# Delete multiple messages
curl -X POST localhost:1337/delete \
  -H "Content-Type: application/json" \
  -d '{"ids":["uuid1", "uuid2", "uuid3"]}'
```

### Using Client Libraries

#### Node.js
```javascript
const { TLQClient, AddMessageCommand, GetMessagesCommand, 
        DeleteMessagesCommand, RetryMessagesCommand, 
        PurgeQueueCommand } = require('tlq-client');

const client = new TLQClient({
  host: 'localhost',
  port: 1337
});

// Add message
const message = await client.send(new AddMessageCommand({
  body: JSON.stringify({ task: 'Process this' })
}));
console.log('Added message:', message.id);

// Get and process messages
const result = await client.send(new GetMessagesCommand({ count: 5 }));
for (const msg of result.messages) {
  try {
    // Process message
    await processTask(JSON.parse(msg.body));
    await client.send(new DeleteMessagesCommand({ ids: [msg.id] }));
  } catch (error) {
    await client.send(new RetryMessagesCommand({ ids: [msg.id] }));
  }
}

// Purge queue if needed
await client.send(new PurgeQueueCommand());
```

#### Python
```python
from tlq_client import TLQClient

# Using context manager for automatic cleanup
with TLQClient(host="localhost", port=1337) as client:
    # Add message
    message_id = client.add_message("Process this task")
    print(f'Added message: {message_id}')
    
    # Get and process messages
    messages = client.get_messages(count=5)
    for msg in messages:
        try:
            # Process message
            process_task(msg.body)
            client.delete_messages(msg.id)
        except Exception:
            client.retry_messages(msg.id)
    
    # Purge queue if needed (careful!)
    # client.purge_queue()
```

#### Rust
```rust
use tlq_client::TlqClient;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = TlqClient::builder()
        .host("localhost")
        .port(1337)
        .build();
    
    // Add message
    let message = client.add_message("Process this task").await?;
    println!("Added message: {}", message.id);
    
    // Get and process messages
    let messages = client.get_messages(5).await?;
    for msg in messages {
        match process_task(&msg.body).await {
            Ok(_) => client.delete_message(msg.id).await?,
            Err(_) => client.retry_message(msg.id).await?,
        }
    }
    
    // Purge queue if needed
    // client.purge_queue().await?;
    
    Ok(())
}
```

#### Go
```go
package main

import (
    "context"
    "fmt"
    "time"
    "github.com/skyaktech/tlq-client-go"
)

func main() {
    // Create configured client
    client := tlq.NewClient(
        tlq.WithHost("localhost"),
        tlq.WithPort(1337),
        tlq.WithTimeout(30 * time.Second),
    )
    
    ctx := context.Background()
    
    // Add message
    message, err := client.AddMessage(ctx, "Process this task")
    if err != nil {
        panic(err)
    }
    fmt.Printf("Added message: %s\n", message.ID)
    
    // Get and process messages
    messages, err := client.GetMessages(ctx, 5)
    if err != nil {
        panic(err)
    }
    
    for _, msg := range messages {
        if err := processTask(msg.Body); err != nil {
            client.RetryMessages(ctx, []string{msg.ID})
        } else {
            client.DeleteMessage(ctx, msg.ID)
        }
    }
    
    // Purge queue if needed
    // err = client.PurgeQueue(ctx)
}
```

### Emergency Operations

```bash
# Check server health
curl http://localhost:1337/hello
# Returns: "Hello World"

# Purge all messages (use with caution!)
curl -X POST localhost:1337/purge \
  -H "Content-Type: application/json" \
  -d '{}'
# Returns: "Success"
```

**When to use purge:**
- Development/testing: Clear test data between runs
- Emergency: Queue contains corrupted or invalid messages
- Reset: Starting fresh after major changes

**⚠️ Never use purge in production unless absolutely necessary** - there's no way to recover purged messages.