mikcar 0.1.1

Sidecar infrastructure services for mik (storage, kv, sql, queue)
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
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
# mikcar

Sidecar infrastructure services for mik.

## Overview

mikcar provides HTTP-based infrastructure services for WASM handlers running in mik:

- **Storage** - Object storage (S3, GCS, MinIO, local filesystem)
- **KV** - Embedded key-value store (redb - pure Rust, no external dependencies)
- **SQL** - Database proxy (Postgres, SQLite)
- **Queue** - Message queues (Redis Streams, RabbitMQ)
- **Secrets** - Secret managers (Vault, AWS Secrets Manager, GCP Secret Manager)
- **Email** - SMTP email (works with any provider)

Same API works locally and in production. Your WASM handlers don't know or care if they're talking to MinIO or AWS S3.

## Docker

Two optimized images available:

| Image            | Size  | Queue Support   | Base                  |
| ---------------- | ----- | --------------- | --------------------- |
| `mikcar`         | ~21MB | No              | scratch (musl static) |
| `mikcar-all`     | ~65MB | Redis, RabbitMQ | distroless            |

### Pull from GitHub Container Registry

```bash
# Minimal image (no queue) - recommended for most use cases
docker pull ghcr.io/dufeut/mikcar:latest

# Full image with queue support
docker pull ghcr.io/dufeut/mikcar-all:latest

# Pull specific version
docker pull ghcr.io/dufeut/mikcar:0.1.0
```

### Run

```bash
docker run -p 3001:3001 \
  -e SIDECAR_TOKEN=secret \
  -e KV_URL=memory:// \
  -e STORAGE_URL=memory:// \
  ghcr.io/dufeut/mikcar:latest --kv --storage
```

### Build locally

```bash
# Minimal image (no queue)
docker build -t mikcar .

# Full image with queue support
docker build -f Dockerfile.all -t mikcar:all .
```

## Installation

```bash
# Install with default services (storage, kv, sql, secrets, email)
cargo install mikcar

# Install with queue support
cargo install mikcar --features all

# Install specific services only
cargo install mikcar --features storage,kv
```

## Usage

```bash
# Run storage service
STORAGE_URL=file:///data mikcar --storage --port 3001

# Run KV service (embedded redb, no external dependencies)
KV_URL=file:///data/kv.redb mikcar --kv --port 3002

# Run SQL service
DATABASE_URL=postgres://user:pass@localhost/db mikcar --sql --port 3003

# Run secrets service
SECRETS_URL=vault://localhost:8200 mikcar --secrets --port 3004

# Run email service
EMAIL_URL=smtp://localhost:1025 mikcar --email --port 3005

# Supercar mode (multiple services on one port)
mikcar --kv --storage --sql --email --port 3001
```

## API

### Storage (`/storage/*`)

```
GET    /object/{path}    Get object
PUT    /object/{path}    Put object (body = content)
DELETE /object/{path}    Delete object
HEAD   /object/{path}    Check exists + metadata
GET    /list/{prefix}    List objects
```

### Key-Value (`/kv/*`)

```
GET    /get/{key}        Get value
POST   /get/batch        Get multiple (body = {keys: [...]})
POST   /set/{key}?ttl=   Set value (body = value)
POST   /set/batch        Set multiple (body = {key: value, ...})
DELETE /del/{key}        Delete key
GET    /keys/{pattern}   List keys matching pattern
POST   /increment/{key}  Increment (atomic)
GET    /exists/{key}     Check if key exists
GET    /ttl/{key}        Get TTL (-1 = no TTL, -2 = not found)
POST   /expire/{key}     Set expiration (body = {seconds: N})
```

### SQL (`/sql/*`)

```
POST   /query            Execute SELECT (body = {sql, params})
POST   /execute          Execute INSERT/UPDATE/DELETE
POST   /batch            Execute multiple statements in transaction
POST   /script           Execute JavaScript with SQL in transaction
```

#### SQL Script Endpoint

The `/sql/script` endpoint executes JavaScript code with SQL operations within a single database transaction. This enables complex conditional logic, validation, and multi-step operations with automatic rollback on failure.

**Request:**
```json
{
  "script": "var user = sql.query('SELECT * FROM users WHERE id = $1', [input.userId]); if (!user.length) throw new Error('User not found'); return { user: user[0] };",
  "input": { "userId": 42 }
}
```

**Response:**
```json
{
  "result": { "user": { "id": 42, "name": "Alice" } },
  "queries_executed": 1
}
```

**Script API:**
- `sql.query(sql, params)` - Execute SELECT, returns array of row objects
- `sql.execute(sql, params)` - Execute INSERT/UPDATE/DELETE, returns rows affected
- `input` - The input object from the request
- `return` - Specify the response value

**Supported script formats:**

```javascript
// 1. Export default function (recommended)
export default function(input) {
  var users = sql.query("SELECT * FROM users WHERE org = $1", [input.org]);
  return { count: users.length };
}

// 2. Raw code with return (simple scripts)
var users = sql.query("SELECT * FROM users");
return { count: users.length };

// 3. Raw expression (simplest)
sql.query("SELECT COUNT(*) FROM users")
```

**Transaction semantics:**
- All SQL operations run in a single transaction
- If the script throws an error, the transaction is rolled back
- If any SQL operation fails, the transaction is rolled back
- Only on successful completion is the transaction committed

**Example: Fund transfer with balance check**
```javascript
export default function(input) {
  // Check source account
  var from = sql.query("SELECT id, balance FROM accounts WHERE id = $1", [input.from]);
  if (!from.length) throw new Error("Source account not found");
  if (parseFloat(from[0].balance) < input.amount) throw new Error("Insufficient funds");

  // Check destination account
  var to = sql.query("SELECT id FROM accounts WHERE id = $1", [input.to]);
  if (!to.length) throw new Error("Destination account not found");

  // Perform transfer (atomic - both succeed or both fail)
  sql.execute("UPDATE accounts SET balance = balance - $1 WHERE id = $2", [input.amount, input.from]);
  sql.execute("UPDATE accounts SET balance = balance + $1 WHERE id = $2", [input.amount, input.to]);

  return { success: true, transferred: input.amount };
}
```

### Queue (`/queue/*`)

```
POST   /publish/{topic}  Publish message
GET    /subscribe/{topic} Long-poll for messages
POST   /ack/{topic}/{id} Acknowledge message
POST   /push/{queue}     Push to work queue
GET    /pop/{queue}      Pop from work queue
```

### Email (`/email/*`)

```
POST   /send             Send single email
POST   /send/batch       Send multiple emails
```

**Request body (POST /send):**
```json
{
  "from": "sender@example.com",
  "to": ["recipient@example.com"],
  "cc": ["cc@example.com"],
  "bcc": ["bcc@example.com"],
  "subject": "Hello",
  "text": "Plain text body",
  "html": "<p>HTML body</p>",
  "reply_to": "reply@example.com"
}
```

**Response:**
```json
{"id": "message-id", "success": true}
```

**Batch request (POST /send/batch):**
```json
{
  "emails": [
    {"from": "...", "to": ["..."], "subject": "...", "text": "..."},
    {"from": "...", "to": ["..."], "subject": "...", "text": "..."}
  ]
}
```

## Authentication

Set `SIDECAR_TOKEN` or `AUTH_TOKEN` environment variable. Requests must include:

```
Authorization: Bearer <token>
```

The `/health` endpoint is always accessible without authentication.

## Configuration

| Variable        | Service | Example                                                  |
| --------------- | ------- | -------------------------------------------------------- |
| `STORAGE_URL`   | storage | `file:///data`, `s3://bucket`, `memory://`               |
| `KV_URL`        | kv      | `file:///data/kv.redb`, `memory://`                      |
| `DATABASE_URL`  | sql     | `postgres://user:pass@host/db`                           |
| `QUEUE_URL`     | queue   | `redis://host:port`, `amqp://user:pass@host:port`        |
| `SECRETS_URL`   | secrets | `vault://host:port`, `awssm://region`, `gcpsm://project` |
| `EMAIL_URL`     | email   | `smtp://host:port`, `smtps://user:pass@host:465`         |
| `SIDECAR_TOKEN` | auth    | Bearer token for API authentication                      |

## Queue Backends

mikcar acts as an HTTP proxy to queue infrastructure. Supported backends:

| Backend       | URL Format                   | Platform    |
| ------------- | ---------------------------- | ----------- |
| In-memory     | `memory://`                  | All         |
| Redis Streams | `redis://host:port`          | Linux/macOS |
| RabbitMQ      | `amqp://user:pass@host:port` | Linux/macOS |

**Auto-creation:** Queues are automatically created on first push:
- Redis: Creates stream + consumer group (`XGROUP CREATE ... MKSTREAM`)
- RabbitMQ: Declares durable queue

No manual setup required - just push and pop.

**Note:** Queue requires the `all` feature or `Dockerfile.all`. Windows builds only support `memory://`.

```bash
# Redis
QUEUE_URL=redis://localhost:6379 mikcar --queue

# RabbitMQ
QUEUE_URL=amqp://guest:guest@localhost:5672 mikcar --queue

# In-memory (dev only)
QUEUE_URL=memory:// mikcar --queue
```

## Email Backends

SMTP is the universal email protocol - works with any provider.

| Provider            | URL Format                                                      |
| ------------------- | --------------------------------------------------------------- |
| Local dev (Mailpit) | `smtp://localhost:1025`                                         |
| Gmail               | `smtps://user:app-password@smtp.gmail.com:465`                  |
| SendGrid            | `smtps://apikey:SG.xxx@smtp.sendgrid.net:465`                   |
| AWS SES             | `smtps://AKIA...:secret@email-smtp.us-east-1.amazonaws.com:465` |
| Resend              | `smtps://resend:re_xxx@smtp.resend.com:465`                     |

**Port behavior:**
- Ports 25, 1025, 2525: Plain SMTP (no TLS) - for local testing
- Port 465: Implicit TLS (`smtps://`)
- Port 587: STARTTLS (`smtp://`)

```bash
# Local development with Mailpit
EMAIL_URL=smtp://localhost:1025 mikcar --email

# Production with SendGrid
EMAIL_URL=smtps://apikey:SG.xxx@smtp.sendgrid.net:465 mikcar --email
```

## Library Usage

mikcar can be used as a Rust library to embed sidecars in your own applications (e.g., Tauri, Axum, or custom servers).

### Add Dependencies

```toml
[dependencies]
mikcar = { git = "https://github.com/dufeut/mikcar", features = ["all"] }
tokio = { version = "1", features = ["full"] }
```

### Example: Single Service

```rust
use mikcar::{SidecarBuilder, StorageService};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let storage = StorageService::from_url("memory://")?;

    SidecarBuilder::new()
        .port(3001)
        .auth_token("secret")
        .serve(storage)
        .await
}
```

### Example: Multiple Services

```rust
use mikcar::{SidecarBuilder, KvService, StorageService, SqlService};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let kv = KvService::from_url("memory://")?;
    let storage = StorageService::from_url("memory://")?;
    let sql = SqlService::from_url("postgres://user:pass@localhost/db").await?;

    SidecarBuilder::new()
        .port(3001)
        .auth_token("secret")
        .add(kv)
        .add(storage)
        .add(sql)
        .serve_many()
        .await
}
```

### Example: Tauri Integration

```rust
use mikcar::{SidecarBuilder, KvService, StorageService};
use std::sync::Arc;
use tokio::sync::Mutex;

struct AppState {
    sidecars: Arc<Mutex<Option<tokio::task::JoinHandle<()>>>>,
}

#[tauri::command]
async fn start_sidecars(state: tauri::State<'_, AppState>) -> Result<(), String> {
    let kv = KvService::from_url("memory://").map_err(|e| e.to_string())?;
    let storage = StorageService::from_url("memory://").map_err(|e| e.to_string())?;

    let handle = tokio::spawn(async move {
        let _ = SidecarBuilder::new()
            .port(3001)
            .add(kv)
            .add(storage)
            .serve_many()
            .await;
    });

    *state.sidecars.lock().await = Some(handle);
    Ok(())
}
```

### Example: Combined with mik Runtime

```rust
use mik::runtime::HostBuilder;
use mikcar::{SidecarBuilder, KvService, StorageService};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    // Start sidecars on port 3001
    let kv = KvService::from_url("memory://")?;
    let storage = StorageService::from_url("memory://")?;

    tokio::spawn(async move {
        SidecarBuilder::new()
            .port(3001)
            .add(kv)
            .add(storage)
            .serve_many()
            .await
            .unwrap();
    });

    // Start WASM runtime on port 3000
    let host = HostBuilder::new()
        .port(3000)
        .modules_dir("./modules".into())
        .build()
        .await?;

    host.serve().await?;
    Ok(())
}
```

### Exported Types

| Type             | Description                                   |
| ---------------- | --------------------------------------------- |
| `SidecarBuilder` | Builder for configuring and starting sidecars |
| `StorageService` | S3/GCS/Azure/local filesystem storage         |
| `KvService`      | Embedded key-value store (redb)               |
| `SqlService`     | PostgreSQL/SQLite proxy                       |
| `QueueService`   | Redis Streams/RabbitMQ queues                 |
| `SecretsService` | Vault/AWS/GCP secret managers                 |
| `EmailService`   | SMTP email sending                            |
| `Sidecar` trait  | Implement custom sidecars                     |

### Re-exported Crates

mikcar re-exports commonly used crates for convenience:

```rust
use mikcar::axum;        // Web framework
use mikcar::tower;       // Service abstractions
use mikcar::tower_http;  // HTTP middleware
```

## License

MIT