@kb-labs/core-state-daemon 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/README.md +503 -0
- package/dist/bin.cjs +333 -0
- package/dist/bin.cjs.map +1 -0
- package/dist/index.js +173 -0
- package/dist/index.js.map +1 -0
- package/package.json +54 -0
package/README.md
ADDED
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
# @kb-labs/state-daemon
|
|
2
|
+
|
|
3
|
+
HTTP daemon server for persistent cross-invocation state in KB Labs.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
State Daemon provides a lightweight HTTP server that maintains persistent state across CLI command invocations, enabling fast in-memory caching with automatic TTL cleanup.
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
- **Zero external dependencies**: Pure Node.js HTTP server
|
|
12
|
+
- **In-memory storage**: Fast key-value operations (~1ms)
|
|
13
|
+
- **Automatic TTL cleanup**: Background cleanup every 30s
|
|
14
|
+
- **HTTP REST API**: Simple GET/PUT/DELETE endpoints
|
|
15
|
+
- **Namespace isolation**: Per-plugin namespaces with statistics
|
|
16
|
+
- **Health monitoring**: `/health` and `/stats` endpoints
|
|
17
|
+
- **Graceful shutdown**: SIGTERM/SIGINT handling
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pnpm add @kb-labs/state-daemon
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
### Start Daemon
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# Default (localhost:7777)
|
|
31
|
+
kb-state-daemon
|
|
32
|
+
|
|
33
|
+
# Custom port
|
|
34
|
+
KB_STATE_DAEMON_PORT=8888 kb-state-daemon
|
|
35
|
+
|
|
36
|
+
# Custom host (be careful with security!)
|
|
37
|
+
KB_STATE_DAEMON_HOST=0.0.0.0 KB_STATE_DAEMON_PORT=7777 kb-state-daemon
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Environment Variables
|
|
41
|
+
|
|
42
|
+
| Variable | Default | Description |
|
|
43
|
+
|----------|---------|-------------|
|
|
44
|
+
| `KB_STATE_DAEMON_PORT` | `7777` | HTTP server port |
|
|
45
|
+
| `KB_STATE_DAEMON_HOST` | `localhost` | HTTP server host (use `0.0.0.0` for network access) |
|
|
46
|
+
|
|
47
|
+
### Programmatic Usage
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
import { StateDaemonServer } from '@kb-labs/state-daemon';
|
|
51
|
+
|
|
52
|
+
const server = new StateDaemonServer({
|
|
53
|
+
port: 7777,
|
|
54
|
+
host: 'localhost',
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
await server.start();
|
|
58
|
+
console.log('State daemon running on localhost:7777');
|
|
59
|
+
|
|
60
|
+
// Graceful shutdown
|
|
61
|
+
process.on('SIGTERM', () => server.shutdown());
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## HTTP API
|
|
65
|
+
|
|
66
|
+
### Health Check
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
GET /health
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**Response:**
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"status": "ok",
|
|
77
|
+
"version": "0.1.0",
|
|
78
|
+
"stats": {
|
|
79
|
+
"uptime": 123456,
|
|
80
|
+
"totalEntries": 42,
|
|
81
|
+
"totalSize": 1024,
|
|
82
|
+
"hitRate": 0.85,
|
|
83
|
+
"missRate": 0.15,
|
|
84
|
+
"evictions": 5,
|
|
85
|
+
"namespaces": {
|
|
86
|
+
"mind": {
|
|
87
|
+
"entries": 30,
|
|
88
|
+
"sizeBytes": 768,
|
|
89
|
+
"lastAccess": 1638360000000
|
|
90
|
+
},
|
|
91
|
+
"workflow": {
|
|
92
|
+
"entries": 12,
|
|
93
|
+
"sizeBytes": 256,
|
|
94
|
+
"lastAccess": 1638360000000
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Get Statistics
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
GET /stats
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**Response:** Same as health stats.
|
|
108
|
+
|
|
109
|
+
### Get Value
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
GET /state/:key
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**Example:**
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
curl http://localhost:7777/state/mind:query-123
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**Response (200 OK):**
|
|
122
|
+
|
|
123
|
+
```json
|
|
124
|
+
{
|
|
125
|
+
"queryId": "Q-abc123",
|
|
126
|
+
"result": { ... },
|
|
127
|
+
"createdAt": "2025-11-29T12:00:00Z"
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**Response (404 Not Found):**
|
|
132
|
+
|
|
133
|
+
```json
|
|
134
|
+
{
|
|
135
|
+
"error": "NOT_FOUND",
|
|
136
|
+
"message": "Key not found or expired"
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Set Value
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
PUT /state/:key
|
|
144
|
+
Content-Type: application/json
|
|
145
|
+
|
|
146
|
+
{
|
|
147
|
+
"value": { ... },
|
|
148
|
+
"ttl": 60000 // Optional TTL in milliseconds
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**Example:**
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
curl -X PUT http://localhost:7777/state/mind:query-123 \
|
|
156
|
+
-H "Content-Type: application/json" \
|
|
157
|
+
-d '{
|
|
158
|
+
"value": {"result": "cached data"},
|
|
159
|
+
"ttl": 60000
|
|
160
|
+
}'
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**Response (204 No Content)**
|
|
164
|
+
|
|
165
|
+
### Delete Value
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
DELETE /state/:key
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**Example:**
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
curl -X DELETE http://localhost:7777/state/mind:query-123
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
**Response (204 No Content)**
|
|
178
|
+
|
|
179
|
+
### Clear Values
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
POST /state/clear?pattern=<pattern>
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
**Examples:**
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
# Clear all entries
|
|
189
|
+
curl -X POST http://localhost:7777/state/clear
|
|
190
|
+
|
|
191
|
+
# Clear by namespace
|
|
192
|
+
curl -X POST http://localhost:7777/state/clear?pattern=mind:*
|
|
193
|
+
|
|
194
|
+
# Clear by prefix
|
|
195
|
+
curl -X POST http://localhost:7777/state/clear?pattern=mind:query-*
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
**Response (204 No Content)**
|
|
199
|
+
|
|
200
|
+
## Architecture
|
|
201
|
+
|
|
202
|
+
### In-Memory Storage
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
// Stored as:
|
|
206
|
+
Map<string, CacheEntry>
|
|
207
|
+
|
|
208
|
+
interface CacheEntry {
|
|
209
|
+
value: unknown;
|
|
210
|
+
expiresAt: number;
|
|
211
|
+
namespace: string;
|
|
212
|
+
sizeBytes: number;
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### TTL Cleanup
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
// Every 30 seconds
|
|
220
|
+
setInterval(() => {
|
|
221
|
+
const now = Date.now();
|
|
222
|
+
for (const [key, entry] of store.entries()) {
|
|
223
|
+
if (now > entry.expiresAt) {
|
|
224
|
+
store.delete(key);
|
|
225
|
+
evictions++;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}, 30000);
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Namespace Extraction
|
|
232
|
+
|
|
233
|
+
Keys are automatically parsed into namespaces:
|
|
234
|
+
|
|
235
|
+
- `mind:query-123` → namespace: `mind`
|
|
236
|
+
- `workflow:job-456` → namespace: `workflow`
|
|
237
|
+
- `cache:session-789` → namespace: `cache`
|
|
238
|
+
|
|
239
|
+
Statistics are tracked per namespace.
|
|
240
|
+
|
|
241
|
+
## Performance
|
|
242
|
+
|
|
243
|
+
### Benchmarks
|
|
244
|
+
|
|
245
|
+
| Operation | Latency | Throughput |
|
|
246
|
+
|-----------|---------|------------|
|
|
247
|
+
| GET | ~1ms | ~1000 ops/s |
|
|
248
|
+
| PUT | ~1ms | ~1000 ops/s |
|
|
249
|
+
| DELETE | ~0.5ms | ~2000 ops/s |
|
|
250
|
+
|
|
251
|
+
**Note:** Localhost performance. Network latency adds ~0.1-0.5ms.
|
|
252
|
+
|
|
253
|
+
### Memory Usage
|
|
254
|
+
|
|
255
|
+
- **Overhead per entry:** ~100 bytes (key + metadata)
|
|
256
|
+
- **Default limit:** None (controlled by plugin quotas)
|
|
257
|
+
- **10,000 entries:** ~1 MB + data size
|
|
258
|
+
|
|
259
|
+
### Comparison with File I/O
|
|
260
|
+
|
|
261
|
+
| Operation | Daemon | File I/O | Improvement |
|
|
262
|
+
|-----------|--------|----------|-------------|
|
|
263
|
+
| Cache read | 1ms | 10-50ms | 10-50x faster |
|
|
264
|
+
| Cache write | 1ms | 10-50ms | 10-50x faster |
|
|
265
|
+
|
|
266
|
+
## Monitoring
|
|
267
|
+
|
|
268
|
+
### Health Endpoint
|
|
269
|
+
|
|
270
|
+
```bash
|
|
271
|
+
# Quick health check
|
|
272
|
+
curl -s http://localhost:7777/health | jq '.status'
|
|
273
|
+
# "ok"
|
|
274
|
+
|
|
275
|
+
# Full stats
|
|
276
|
+
curl -s http://localhost:7777/health | jq '.stats'
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### Statistics
|
|
280
|
+
|
|
281
|
+
```json
|
|
282
|
+
{
|
|
283
|
+
"uptime": 3600000, // 1 hour
|
|
284
|
+
"totalEntries": 1234,
|
|
285
|
+
"totalSize": 5242880, // ~5 MB
|
|
286
|
+
"hitRate": 0.85, // 85% cache hits
|
|
287
|
+
"missRate": 0.15, // 15% cache misses
|
|
288
|
+
"evictions": 42, // 42 entries evicted (expired)
|
|
289
|
+
"namespaces": {
|
|
290
|
+
"mind": {
|
|
291
|
+
"entries": 1000,
|
|
292
|
+
"sizeBytes": 4194304, // ~4 MB
|
|
293
|
+
"lastAccess": 1638360000000
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
## Lifecycle Management
|
|
300
|
+
|
|
301
|
+
### Manual Start/Stop
|
|
302
|
+
|
|
303
|
+
```bash
|
|
304
|
+
# Start
|
|
305
|
+
kb-state-daemon &
|
|
306
|
+
DAEMON_PID=$!
|
|
307
|
+
|
|
308
|
+
# Stop
|
|
309
|
+
kill $DAEMON_PID
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### Systemd Service (Linux)
|
|
313
|
+
|
|
314
|
+
Create `/etc/systemd/system/kb-state-daemon.service`:
|
|
315
|
+
|
|
316
|
+
```ini
|
|
317
|
+
[Unit]
|
|
318
|
+
Description=KB Labs State Daemon
|
|
319
|
+
After=network.target
|
|
320
|
+
|
|
321
|
+
[Service]
|
|
322
|
+
Type=simple
|
|
323
|
+
User=youruser
|
|
324
|
+
Environment="KB_STATE_DAEMON_PORT=7777"
|
|
325
|
+
Environment="KB_STATE_DAEMON_HOST=localhost"
|
|
326
|
+
ExecStart=/usr/local/bin/kb-state-daemon
|
|
327
|
+
Restart=on-failure
|
|
328
|
+
RestartSec=5s
|
|
329
|
+
|
|
330
|
+
[Install]
|
|
331
|
+
WantedBy=multi-user.target
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
```bash
|
|
335
|
+
sudo systemctl enable kb-state-daemon
|
|
336
|
+
sudo systemctl start kb-state-daemon
|
|
337
|
+
sudo systemctl status kb-state-daemon
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### Launchd Service (macOS)
|
|
341
|
+
|
|
342
|
+
Create `~/Library/LaunchAgents/com.kb-labs.state-daemon.plist`:
|
|
343
|
+
|
|
344
|
+
```xml
|
|
345
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
346
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
347
|
+
<plist version="1.0">
|
|
348
|
+
<dict>
|
|
349
|
+
<key>Label</key>
|
|
350
|
+
<string>com.kb-labs.state-daemon</string>
|
|
351
|
+
<key>ProgramArguments</key>
|
|
352
|
+
<array>
|
|
353
|
+
<string>/usr/local/bin/kb-state-daemon</string>
|
|
354
|
+
</array>
|
|
355
|
+
<key>EnvironmentVariables</key>
|
|
356
|
+
<dict>
|
|
357
|
+
<key>KB_STATE_DAEMON_PORT</key>
|
|
358
|
+
<string>7777</string>
|
|
359
|
+
<key>KB_STATE_DAEMON_HOST</key>
|
|
360
|
+
<string>localhost</string>
|
|
361
|
+
</dict>
|
|
362
|
+
<key>RunAtLoad</key>
|
|
363
|
+
<true/>
|
|
364
|
+
<key>KeepAlive</key>
|
|
365
|
+
<true/>
|
|
366
|
+
<key>StandardOutPath</key>
|
|
367
|
+
<string>/tmp/kb-state-daemon.log</string>
|
|
368
|
+
<key>StandardErrorPath</key>
|
|
369
|
+
<string>/tmp/kb-state-daemon.error.log</string>
|
|
370
|
+
</dict>
|
|
371
|
+
</plist>
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
```bash
|
|
375
|
+
launchctl load ~/Library/LaunchAgents/com.kb-labs.state-daemon.plist
|
|
376
|
+
launchctl start com.kb-labs.state-daemon
|
|
377
|
+
launchctl list | grep kb-state-daemon
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
## Security Considerations
|
|
381
|
+
|
|
382
|
+
### Network Access
|
|
383
|
+
|
|
384
|
+
**⚠️ Default: localhost only**
|
|
385
|
+
|
|
386
|
+
The daemon binds to `localhost` by default, preventing network access.
|
|
387
|
+
|
|
388
|
+
**❌ DO NOT expose to network without authentication:**
|
|
389
|
+
|
|
390
|
+
```bash
|
|
391
|
+
# INSECURE - allows network access without auth
|
|
392
|
+
KB_STATE_DAEMON_HOST=0.0.0.0 kb-state-daemon
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
### Authentication
|
|
396
|
+
|
|
397
|
+
Currently, no authentication is implemented. The daemon should only be used on trusted localhost.
|
|
398
|
+
|
|
399
|
+
**Future improvements:**
|
|
400
|
+
- API key authentication
|
|
401
|
+
- JWT tokens
|
|
402
|
+
- TLS/SSL support
|
|
403
|
+
- Rate limiting
|
|
404
|
+
|
|
405
|
+
### Namespace Isolation
|
|
406
|
+
|
|
407
|
+
Namespace isolation is enforced at the runtime level, not the daemon level. The daemon itself does not enforce permissions - it's a dumb key-value store.
|
|
408
|
+
|
|
409
|
+
**Permission enforcement:** Handled by `@kb-labs/plugin-runtime` via `createStateAPI()`.
|
|
410
|
+
|
|
411
|
+
## Troubleshooting
|
|
412
|
+
|
|
413
|
+
### Daemon Not Starting
|
|
414
|
+
|
|
415
|
+
```bash
|
|
416
|
+
# Check if port is already in use
|
|
417
|
+
lsof -i :7777
|
|
418
|
+
|
|
419
|
+
# Try custom port
|
|
420
|
+
KB_STATE_DAEMON_PORT=8888 kb-state-daemon
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
### Connection Refused
|
|
424
|
+
|
|
425
|
+
```bash
|
|
426
|
+
# Check if daemon is running
|
|
427
|
+
curl http://localhost:7777/health
|
|
428
|
+
|
|
429
|
+
# Check logs (if using systemd)
|
|
430
|
+
sudo journalctl -u kb-state-daemon -f
|
|
431
|
+
|
|
432
|
+
# Check logs (if using launchd)
|
|
433
|
+
tail -f /tmp/kb-state-daemon.log
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
### High Memory Usage
|
|
437
|
+
|
|
438
|
+
```bash
|
|
439
|
+
# Check stats
|
|
440
|
+
curl -s http://localhost:7777/stats | jq '.totalSize'
|
|
441
|
+
|
|
442
|
+
# Clear specific namespace
|
|
443
|
+
curl -X POST http://localhost:7777/state/clear?pattern=mind:*
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
### Slow Performance
|
|
447
|
+
|
|
448
|
+
Daemon performance should be ~1ms per operation on localhost. If slower:
|
|
449
|
+
|
|
450
|
+
1. **Network latency**: Check if using remote host instead of localhost
|
|
451
|
+
2. **Large payloads**: Check `totalSize` in stats (>100 MB may cause slowdown)
|
|
452
|
+
3. **System load**: Check CPU/memory usage
|
|
453
|
+
|
|
454
|
+
## API Client Libraries
|
|
455
|
+
|
|
456
|
+
### JavaScript/TypeScript
|
|
457
|
+
|
|
458
|
+
```typescript
|
|
459
|
+
import { HTTPStateBroker } from '@kb-labs/state-broker';
|
|
460
|
+
|
|
461
|
+
const client = new HTTPStateBroker('http://localhost:7777');
|
|
462
|
+
await client.set('key', 'value', 60000);
|
|
463
|
+
const value = await client.get('key');
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
### Shell (curl)
|
|
467
|
+
|
|
468
|
+
```bash
|
|
469
|
+
# Helper functions
|
|
470
|
+
kb_state_get() {
|
|
471
|
+
curl -s "http://localhost:7777/state/$1" | jq -r '.value'
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
kb_state_set() {
|
|
475
|
+
curl -s -X PUT "http://localhost:7777/state/$1" \
|
|
476
|
+
-H "Content-Type: application/json" \
|
|
477
|
+
-d "{\"value\":\"$2\",\"ttl\":$3}"
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
kb_state_delete() {
|
|
481
|
+
curl -s -X DELETE "http://localhost:7777/state/$1"
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
# Usage
|
|
485
|
+
kb_state_set "my-key" "my-value" 60000
|
|
486
|
+
kb_state_get "my-key"
|
|
487
|
+
kb_state_delete "my-key"
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
## Related Packages
|
|
491
|
+
|
|
492
|
+
- **@kb-labs/state-broker** - Client library for state daemon
|
|
493
|
+
- **@kb-labs/plugin-runtime** - Runtime integration with permissions
|
|
494
|
+
- **@kb-labs/plugin-manifest** - Permission type definitions
|
|
495
|
+
|
|
496
|
+
## License
|
|
497
|
+
|
|
498
|
+
MIT
|
|
499
|
+
|
|
500
|
+
## See Also
|
|
501
|
+
|
|
502
|
+
- [ADR-0037: State Broker for Persistent Cache](../../kb-labs-mind/docs/adr/0037-state-broker-persistent-cache.md)
|
|
503
|
+
- [State Broker README](../state-broker/README.md)
|
package/dist/bin.cjs
ADDED
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var http = require('http');
|
|
5
|
+
|
|
6
|
+
// ../core-state-broker/dist/index.js
|
|
7
|
+
var InMemoryStateBroker = class {
|
|
8
|
+
constructor(cleanupIntervalMs = 3e4) {
|
|
9
|
+
this.cleanupIntervalMs = cleanupIntervalMs;
|
|
10
|
+
this.startTime = Date.now();
|
|
11
|
+
this.cleanupInterval = setInterval(() => this.cleanup(), cleanupIntervalMs);
|
|
12
|
+
}
|
|
13
|
+
store = /* @__PURE__ */ new Map();
|
|
14
|
+
cleanupInterval;
|
|
15
|
+
startTime;
|
|
16
|
+
hits = 0;
|
|
17
|
+
misses = 0;
|
|
18
|
+
evictions = 0;
|
|
19
|
+
async get(key) {
|
|
20
|
+
const entry = this.store.get(key);
|
|
21
|
+
if (!entry) {
|
|
22
|
+
this.misses++;
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
if (Date.now() > entry.expiresAt) {
|
|
26
|
+
this.store.delete(key);
|
|
27
|
+
this.misses++;
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
this.hits++;
|
|
31
|
+
return entry.value;
|
|
32
|
+
}
|
|
33
|
+
async set(key, value, ttl = 3e5) {
|
|
34
|
+
const namespace = this.extractNamespace(key);
|
|
35
|
+
const size = this.estimateSize(value);
|
|
36
|
+
this.store.set(key, {
|
|
37
|
+
value,
|
|
38
|
+
expiresAt: Date.now() + ttl,
|
|
39
|
+
size,
|
|
40
|
+
namespace
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
async delete(key) {
|
|
44
|
+
this.store.delete(key);
|
|
45
|
+
}
|
|
46
|
+
async clear(pattern) {
|
|
47
|
+
if (!pattern) {
|
|
48
|
+
this.store.clear();
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const prefix = pattern.replace("*", "");
|
|
52
|
+
for (const key of this.store.keys()) {
|
|
53
|
+
if (key.startsWith(prefix)) {
|
|
54
|
+
this.store.delete(key);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async getStats() {
|
|
59
|
+
const namespaces = {};
|
|
60
|
+
const byTenant = {};
|
|
61
|
+
let totalSize = 0;
|
|
62
|
+
const now = Date.now();
|
|
63
|
+
for (const [key, entry] of this.store.entries()) {
|
|
64
|
+
if (!namespaces[entry.namespace]) {
|
|
65
|
+
namespaces[entry.namespace] = { entries: 0, size: 0, oldestEntry: now };
|
|
66
|
+
}
|
|
67
|
+
const ns = namespaces[entry.namespace];
|
|
68
|
+
ns.entries++;
|
|
69
|
+
ns.size += entry.size;
|
|
70
|
+
totalSize += entry.size;
|
|
71
|
+
const entryAge = entry.expiresAt - now;
|
|
72
|
+
if (entryAge < ns.oldestEntry) {
|
|
73
|
+
ns.oldestEntry = entryAge;
|
|
74
|
+
}
|
|
75
|
+
const tenant = this.extractTenant(key);
|
|
76
|
+
if (!byTenant[tenant]) {
|
|
77
|
+
byTenant[tenant] = { entries: 0, size: 0, lastAccess: now };
|
|
78
|
+
}
|
|
79
|
+
byTenant[tenant].entries++;
|
|
80
|
+
byTenant[tenant].size += entry.size;
|
|
81
|
+
if (entry.expiresAt > byTenant[tenant].lastAccess) {
|
|
82
|
+
byTenant[tenant].lastAccess = entry.expiresAt;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const totalRequests = this.hits + this.misses;
|
|
86
|
+
const hitRate = totalRequests > 0 ? this.hits / totalRequests : 0;
|
|
87
|
+
const missRate = totalRequests > 0 ? this.misses / totalRequests : 0;
|
|
88
|
+
return {
|
|
89
|
+
uptime: Date.now() - this.startTime,
|
|
90
|
+
totalEntries: this.store.size,
|
|
91
|
+
totalSize,
|
|
92
|
+
hitRate,
|
|
93
|
+
missRate,
|
|
94
|
+
evictions: this.evictions,
|
|
95
|
+
namespaces,
|
|
96
|
+
byTenant
|
|
97
|
+
// ← New: stats by tenant
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
async getHealth() {
|
|
101
|
+
const stats = await this.getStats();
|
|
102
|
+
return {
|
|
103
|
+
status: "ok",
|
|
104
|
+
version: "0.1.0",
|
|
105
|
+
stats
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
async stop() {
|
|
109
|
+
clearInterval(this.cleanupInterval);
|
|
110
|
+
this.store.clear();
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Cleanup expired entries
|
|
114
|
+
*/
|
|
115
|
+
cleanup() {
|
|
116
|
+
const now = Date.now();
|
|
117
|
+
for (const [key, entry] of this.store.entries()) {
|
|
118
|
+
if (now > entry.expiresAt) {
|
|
119
|
+
this.store.delete(key);
|
|
120
|
+
this.evictions++;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Extract namespace from key (format: namespace:key or tenant:tenantId:namespace:key)
|
|
126
|
+
*/
|
|
127
|
+
extractNamespace(key) {
|
|
128
|
+
const parts = key.split(":");
|
|
129
|
+
if (parts[0] === "tenant" && parts.length >= 3) {
|
|
130
|
+
return parts[2] || "default";
|
|
131
|
+
}
|
|
132
|
+
return parts[0] || "default";
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Extract tenant ID from key (format: tenant:tenantId:namespace:key)
|
|
136
|
+
* For backward compatibility, returns 'default' if no tenant prefix
|
|
137
|
+
*/
|
|
138
|
+
extractTenant(key) {
|
|
139
|
+
const parts = key.split(":");
|
|
140
|
+
if (parts[0] === "tenant" && parts.length >= 2) {
|
|
141
|
+
return parts[1] || "default";
|
|
142
|
+
}
|
|
143
|
+
return "default";
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Estimate size of value in bytes
|
|
147
|
+
*/
|
|
148
|
+
estimateSize(value) {
|
|
149
|
+
try {
|
|
150
|
+
return JSON.stringify(value).length;
|
|
151
|
+
} catch {
|
|
152
|
+
return 0;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// src/server.ts
|
|
158
|
+
var StateDaemonServer = class {
|
|
159
|
+
constructor(config = {}) {
|
|
160
|
+
this.config = config;
|
|
161
|
+
this.broker = new InMemoryStateBroker();
|
|
162
|
+
}
|
|
163
|
+
broker;
|
|
164
|
+
jobsManager = null;
|
|
165
|
+
// DISABLED: JobsManager has missing dependencies
|
|
166
|
+
server = null;
|
|
167
|
+
isShuttingDown = false;
|
|
168
|
+
async start() {
|
|
169
|
+
const port2 = this.config.port ?? 7777;
|
|
170
|
+
const host2 = this.config.host ?? "localhost";
|
|
171
|
+
if (this.jobsManager) {
|
|
172
|
+
await this.jobsManager.initialize();
|
|
173
|
+
}
|
|
174
|
+
this.server = http.createServer((req, res) => this.handleRequest(req, res));
|
|
175
|
+
process.on("SIGTERM", () => this.shutdown());
|
|
176
|
+
process.on("SIGINT", () => this.shutdown());
|
|
177
|
+
return new Promise((resolve, reject) => {
|
|
178
|
+
this.server.listen(port2, host2, () => {
|
|
179
|
+
console.log(`State daemon listening on ${host2}:${port2}`);
|
|
180
|
+
if (this.jobsManager) {
|
|
181
|
+
console.log("Jobs manager enabled - HTTP endpoints available at /jobs");
|
|
182
|
+
}
|
|
183
|
+
resolve();
|
|
184
|
+
});
|
|
185
|
+
this.server.on("error", reject);
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
async stop() {
|
|
189
|
+
if (this.jobsManager) {
|
|
190
|
+
await this.jobsManager.dispose();
|
|
191
|
+
}
|
|
192
|
+
await this.broker.stop();
|
|
193
|
+
if (this.server) {
|
|
194
|
+
return new Promise((resolve) => {
|
|
195
|
+
this.server.close(() => resolve());
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
async shutdown() {
|
|
200
|
+
this.isShuttingDown = true;
|
|
201
|
+
console.log("Shutting down state daemon...");
|
|
202
|
+
await this.stop();
|
|
203
|
+
process.exit(0);
|
|
204
|
+
}
|
|
205
|
+
async handleRequest(req, res) {
|
|
206
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
207
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, PUT, DELETE, POST, OPTIONS");
|
|
208
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
209
|
+
if (req.method === "OPTIONS") {
|
|
210
|
+
res.writeHead(204);
|
|
211
|
+
res.end();
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
215
|
+
try {
|
|
216
|
+
if (req.method === "GET" && url.pathname === "/health") {
|
|
217
|
+
const health = await this.broker.getHealth();
|
|
218
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
219
|
+
res.end(JSON.stringify(health));
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
if (req.method === "GET" && url.pathname === "/stats") {
|
|
223
|
+
const stats = await this.broker.getStats();
|
|
224
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
225
|
+
res.end(JSON.stringify(stats));
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
if (req.method === "GET" && url.pathname.startsWith("/state/")) {
|
|
229
|
+
const key = decodeURIComponent(url.pathname.slice(7));
|
|
230
|
+
const value = await this.broker.get(key);
|
|
231
|
+
if (value === null) {
|
|
232
|
+
res.writeHead(404);
|
|
233
|
+
res.end();
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
237
|
+
res.end(JSON.stringify(value));
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
if (req.method === "PUT" && url.pathname.startsWith("/state/")) {
|
|
241
|
+
const key = decodeURIComponent(url.pathname.slice(7));
|
|
242
|
+
const body = await this.readBody(req);
|
|
243
|
+
const { value, ttl } = JSON.parse(body);
|
|
244
|
+
await this.broker.set(key, value, ttl);
|
|
245
|
+
res.writeHead(204);
|
|
246
|
+
res.end();
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
if (req.method === "DELETE" && url.pathname.startsWith("/state/")) {
|
|
250
|
+
const key = decodeURIComponent(url.pathname.slice(7));
|
|
251
|
+
await this.broker.delete(key);
|
|
252
|
+
res.writeHead(204);
|
|
253
|
+
res.end();
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
if (req.method === "POST" && url.pathname === "/state/clear") {
|
|
257
|
+
const pattern = url.searchParams.get("pattern") || void 0;
|
|
258
|
+
await this.broker.clear(pattern);
|
|
259
|
+
res.writeHead(204);
|
|
260
|
+
res.end();
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
if (req.method === "GET" && url.pathname === "/jobs") {
|
|
264
|
+
if (!this.jobsManager) {
|
|
265
|
+
res.writeHead(503, { "Content-Type": "application/json" });
|
|
266
|
+
res.end(JSON.stringify({ error: "Jobs manager not enabled" }));
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
const jobs = this.jobsManager.listJobs();
|
|
270
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
271
|
+
res.end(JSON.stringify({ jobs }));
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
if (req.method === "POST" && url.pathname.startsWith("/jobs/") && url.pathname.endsWith("/trigger")) {
|
|
275
|
+
if (!this.jobsManager) {
|
|
276
|
+
res.writeHead(503, { "Content-Type": "application/json" });
|
|
277
|
+
res.end(JSON.stringify({ error: "Jobs manager not enabled" }));
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
const id = decodeURIComponent(url.pathname.slice(6, -8));
|
|
281
|
+
try {
|
|
282
|
+
await this.jobsManager.triggerJob(id);
|
|
283
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
284
|
+
res.end(JSON.stringify({ ok: true, message: `Job ${id} triggered successfully` }));
|
|
285
|
+
} catch (error) {
|
|
286
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
287
|
+
res.end(JSON.stringify({
|
|
288
|
+
error: error instanceof Error ? error.message : "Job not found"
|
|
289
|
+
}));
|
|
290
|
+
}
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
if (req.method === "GET" && url.pathname === "/jobs/stats") {
|
|
294
|
+
if (!this.jobsManager) {
|
|
295
|
+
res.writeHead(503, { "Content-Type": "application/json" });
|
|
296
|
+
res.end(JSON.stringify({ error: "Jobs manager not enabled" }));
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
const stats = this.jobsManager.getStats();
|
|
300
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
301
|
+
res.end(JSON.stringify(stats));
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
305
|
+
res.end(JSON.stringify({ error: "Not Found" }));
|
|
306
|
+
} catch (error) {
|
|
307
|
+
console.error("Request error:", error);
|
|
308
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
309
|
+
res.end(JSON.stringify({
|
|
310
|
+
error: error instanceof Error ? error.message : "Internal Server Error"
|
|
311
|
+
}));
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
async readBody(req) {
|
|
315
|
+
return new Promise((resolve, reject) => {
|
|
316
|
+
const chunks = [];
|
|
317
|
+
req.on("data", (chunk) => chunks.push(chunk));
|
|
318
|
+
req.on("end", () => resolve(Buffer.concat(chunks).toString()));
|
|
319
|
+
req.on("error", reject);
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
// src/bin.ts
|
|
325
|
+
var port = process.env.KB_STATE_DAEMON_PORT ? parseInt(process.env.KB_STATE_DAEMON_PORT, 10) : 7777;
|
|
326
|
+
var host = process.env.KB_STATE_DAEMON_HOST ?? "localhost";
|
|
327
|
+
var server = new StateDaemonServer({ port, host });
|
|
328
|
+
server.start().catch((error) => {
|
|
329
|
+
console.error("Failed to start state daemon:", error);
|
|
330
|
+
process.exit(1);
|
|
331
|
+
});
|
|
332
|
+
//# sourceMappingURL=bin.cjs.map
|
|
333
|
+
//# sourceMappingURL=bin.cjs.map
|
package/dist/bin.cjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../core-state-broker/src/backends/in-memory.ts","../src/server.ts","../src/bin.ts"],"names":["port","host","createServer"],"mappings":";;;;;;AAaO,IAAM,sBAAN,MAAiD;AAQtD,EAAA,WAAA,CACU,oBAAoB,GAAA,EAC5B;AADQ,IAAA,IAAA,CAAA,iBAAA,GAAA,iBAAA;AAER,IAAA,IAAA,CAAK,SAAA,GAAY,KAAK,GAAA,EAAA;AACtB,IAAA,IAAA,CAAK,kBAAkB,WAAA,CAAY,MAAM,IAAA,CAAK,OAAA,IAAW,iBAAiB,CAAA;AAC5E,EAAA;AAZQ,EAAA,KAAA,uBAAY,GAAA,EAAA;AACZ,EAAA,eAAA;AACA,EAAA,SAAA;EACA,IAAA,GAAO,CAAA;EACP,MAAA,GAAS,CAAA;EACT,SAAA,GAAY,CAAA;AASpB,EAAA,MAAM,IAAO,GAAA,EAAgC;AAC3C,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,GAAG,CAAA;AAEhC,IAAA,IAAI,CAAC,KAAA,EAAO;AACV,MAAA,IAAA,CAAK,MAAA,EAAA;AACL,MAAA,OAAO,IAAA;AACT,IAAA;AAGA,IAAA,IAAI,IAAA,CAAK,GAAA,EAAA,GAAQ,KAAA,CAAM,SAAA,EAAW;AAChC,MAAA,IAAA,CAAK,KAAA,CAAM,OAAO,GAAG,CAAA;AACrB,MAAA,IAAA,CAAK,MAAA,EAAA;AACL,MAAA,OAAO,IAAA;AACT,IAAA;AAEA,IAAA,IAAA,CAAK,IAAA,EAAA;AACL,IAAA,OAAO,KAAA,CAAM,KAAA;AACf,EAAA;AAEA,EAAA,MAAM,GAAA,CAAO,GAAA,EAAa,KAAA,EAAU,GAAA,GAAM,GAAA,EAAwB;AAChE,IAAA,MAAM,SAAA,GAAY,IAAA,CAAK,gBAAA,CAAiB,GAAG,CAAA;AAC3C,IAAA,MAAM,IAAA,GAAO,IAAA,CAAK,YAAA,CAAa,KAAK,CAAA;AAEpC,IAAA,IAAA,CAAK,KAAA,CAAM,IAAI,GAAA,EAAK;AAClB,MAAA,KAAA;MACA,SAAA,EAAW,IAAA,CAAK,KAAA,GAAQ,GAAA;AACxB,MAAA,IAAA;AACA,MAAA;KACD,CAAA;AACH,EAAA;AAEA,EAAA,MAAM,OAAO,GAAA,EAA4B;AACvC,IAAA,IAAA,CAAK,KAAA,CAAM,OAAO,GAAG,CAAA;AACvB,EAAA;AAEA,EAAA,MAAM,MAAM,OAAA,EAAiC;AAC3C,IAAA,IAAI,CAAC,OAAA,EAAS;AACZ,MAAA,IAAA,CAAK,MAAM,KAAA,EAAA;AACX,MAAA;AACF,IAAA;AAGA,IAAA,MAAM,MAAA,GAAS,OAAA,CAAQ,OAAA,CAAQ,GAAA,EAAK,EAAE,CAAA;AACtC,IAAA,KAAA,MAAW,GAAA,IAAO,IAAA,CAAK,KAAA,CAAM,IAAA,EAAA,EAAQ;AACnC,MAAA,IAAI,GAAA,CAAI,UAAA,CAAW,MAAM,CAAA,EAAG;AAC1B,QAAA,IAAA,CAAK,KAAA,CAAM,OAAO,GAAG,CAAA;AACvB,MAAA;AACF,IAAA;AACF,EAAA;AAEA,EAAA,MAAM,QAAA,GAAiC;AACrC,IAAA,MAAM,aAAqF,EAAA;AAC3F,IAAA,MAAM,WAAkF,EAAA;AACxF,IAAA,IAAI,SAAA,GAAY,CAAA;AAChB,IAAA,MAAM,GAAA,GAAM,KAAK,GAAA,EAAA;AAEjB,IAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,KAAK,IAAA,CAAK,KAAA,CAAM,SAAA,EAAW;AAE/C,MAAA,IAAI,CAAC,UAAA,CAAW,KAAA,CAAM,SAAS,CAAA,EAAG;AAChC,QAAA,UAAA,CAAW,KAAA,CAAM,SAAS,CAAA,GAAI,EAAE,SAAS,CAAA,EAAG,IAAA,EAAM,CAAA,EAAG,WAAA,EAAa,GAAA,EAAA;AACpE,MAAA;AAEA,MAAA,MAAM,EAAA,GAAK,UAAA,CAAW,KAAA,CAAM,SAAS,CAAA;AACrC,MAAA,EAAA,CAAG,OAAA,EAAA;AACH,MAAA,EAAA,CAAG,QAAQ,KAAA,CAAM,IAAA;AACjB,MAAA,SAAA,IAAa,KAAA,CAAM,IAAA;AAEnB,MAAA,MAAM,QAAA,GAAW,MAAM,SAAA,GAAY,GAAA;AACnC,MAAA,IAAI,QAAA,GAAW,GAAG,WAAA,EAAa;AAC7B,QAAA,EAAA,CAAG,WAAA,GAAc,QAAA;AACnB,MAAA;AAGA,MAAA,MAAM,MAAA,GAAS,IAAA,CAAK,aAAA,CAAc,GAAG,CAAA;AACrC,MAAA,IAAI,CAAC,QAAA,CAAS,MAAM,CAAA,EAAG;AACrB,QAAA,QAAA,CAAS,MAAM,IAAI,EAAE,OAAA,EAAS,GAAG,IAAA,EAAM,CAAA,EAAG,YAAY,GAAA,EAAA;AACxD,MAAA;AACA,MAAA,QAAA,CAAS,MAAM,CAAA,CAAE,OAAA,EAAA;AACjB,MAAA,QAAA,CAAS,MAAM,CAAA,CAAE,IAAA,IAAQ,KAAA,CAAM,IAAA;AAC/B,MAAA,IAAI,KAAA,CAAM,SAAA,GAAY,QAAA,CAAS,MAAM,EAAE,UAAA,EAAY;AACjD,QAAA,QAAA,CAAS,MAAM,CAAA,CAAE,UAAA,GAAa,KAAA,CAAM,SAAA;AACtC,MAAA;AACF,IAAA;AAEA,IAAA,MAAM,aAAA,GAAgB,IAAA,CAAK,IAAA,GAAO,IAAA,CAAK,MAAA;AACvC,IAAA,MAAM,OAAA,GAAU,aAAA,GAAgB,CAAA,GAAI,IAAA,CAAK,OAAO,aAAA,GAAgB,CAAA;AAChE,IAAA,MAAM,QAAA,GAAW,aAAA,GAAgB,CAAA,GAAI,IAAA,CAAK,SAAS,aAAA,GAAgB,CAAA;AAEnE,IAAA,OAAO;MACL,MAAA,EAAQ,IAAA,CAAK,GAAA,EAAA,GAAQ,IAAA,CAAK,SAAA;AAC1B,MAAA,YAAA,EAAc,KAAK,KAAA,CAAM,IAAA;AACzB,MAAA,SAAA;AACA,MAAA,OAAA;AACA,MAAA,QAAA;AACA,MAAA,SAAA,EAAW,IAAA,CAAK,SAAA;AAChB,MAAA,UAAA;AACA,MAAA;;AAAA,KAAA;AAEJ,EAAA;AAEA,EAAA,MAAM,SAAA,GAAmC;AACvC,IAAA,MAAM,KAAA,GAAQ,MAAM,IAAA,CAAK,QAAA,EAAA;AACzB,IAAA,OAAO;MACL,MAAA,EAAQ,IAAA;MACR,OAAA,EAAS,OAAA;AACT,MAAA;AAAA,KAAA;AAEJ,EAAA;AAEA,EAAA,MAAM,IAAA,GAAsB;AAC1B,IAAA,aAAA,CAAc,KAAK,eAAe,CAAA;AAClC,IAAA,IAAA,CAAK,MAAM,KAAA,EAAA;AACb,EAAA;;;;EAKQ,OAAA,GAAU;AAChB,IAAA,MAAM,GAAA,GAAM,KAAK,GAAA,EAAA;AACjB,IAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,KAAK,IAAA,CAAK,KAAA,CAAM,SAAA,EAAW;AAC/C,MAAA,IAAI,GAAA,GAAM,MAAM,SAAA,EAAW;AACzB,QAAA,IAAA,CAAK,KAAA,CAAM,OAAO,GAAG,CAAA;AACrB,QAAA,IAAA,CAAK,SAAA,EAAA;AACP,MAAA;AACF,IAAA;AACF,EAAA;;;;AAKQ,EAAA,gBAAA,CAAiB,GAAA,EAAqB;AAC5C,IAAA,MAAM,KAAA,GAAQ,GAAA,CAAI,KAAA,CAAM,GAAG,CAAA;AAG3B,IAAA,IAAI,MAAM,CAAC,CAAA,KAAM,QAAA,IAAY,KAAA,CAAM,UAAU,CAAA,EAAG;AAC9C,MAAA,OAAO,KAAA,CAAM,CAAC,CAAA,IAAK,SAAA;AACrB,IAAA;AACA,IAAA,OAAO,KAAA,CAAM,CAAC,CAAA,IAAK,SAAA;AACrB,EAAA;;;;;AAMQ,EAAA,aAAA,CAAc,GAAA,EAAqB;AACzC,IAAA,MAAM,KAAA,GAAQ,GAAA,CAAI,KAAA,CAAM,GAAG,CAAA;AAG3B,IAAA,IAAI,MAAM,CAAC,CAAA,KAAM,QAAA,IAAY,KAAA,CAAM,UAAU,CAAA,EAAG;AAC9C,MAAA,OAAO,KAAA,CAAM,CAAC,CAAA,IAAK,SAAA;AACrB,IAAA;AACA,IAAA,OAAO,SAAA;AACT,EAAA;;;;AAKQ,EAAA,YAAA,CAAa,KAAA,EAAwB;AAC3C,IAAA,IAAI;AACF,MAAA,OAAO,IAAA,CAAK,SAAA,CAAU,KAAK,CAAA,CAAE,MAAA;IAC/B,CAAA,CAAA,MAAQ;AACN,MAAA,OAAO,CAAA;AACT,IAAA;AACF,EAAA;AACF,CAAA;;;AClLO,IAAM,oBAAN,MAAwB;AAAA,EAM7B,WAAA,CAAoB,MAAA,GAA4B,EAAC,EAAG;AAAhC,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AAClB,IAAA,IAAA,CAAK,MAAA,GAAS,IAAI,mBAAA,EAAoB;AAAA,EAkBxC;AAAA,EAxBQ,MAAA;AAAA,EACA,WAAA,GAA0B,IAAA;AAAA;AAAA,EAC1B,MAAA,GAAwB,IAAA;AAAA,EACxB,cAAA,GAAiB,KAAA;AAAA,EAuBzB,MAAM,KAAA,GAAuB;AAC3B,IAAA,MAAMA,KAAAA,GAAO,IAAA,CAAK,MAAA,CAAO,IAAA,IAAQ,IAAA;AACjC,IAAA,MAAMC,KAAAA,GAAO,IAAA,CAAK,MAAA,CAAO,IAAA,IAAQ,WAAA;AAGjC,IAAA,IAAI,KAAK,WAAA,EAAa;AACpB,MAAA,MAAM,IAAA,CAAK,YAAY,UAAA,EAAW;AAAA,IACpC;AAEA,IAAA,IAAA,CAAK,MAAA,GAASC,kBAAa,CAAC,GAAA,EAAK,QAAQ,IAAA,CAAK,aAAA,CAAc,GAAA,EAAK,GAAG,CAAC,CAAA;AAGrE,IAAA,OAAA,CAAQ,EAAA,CAAG,SAAA,EAAW,MAAM,IAAA,CAAK,UAAU,CAAA;AAC3C,IAAA,OAAA,CAAQ,EAAA,CAAG,QAAA,EAAU,MAAM,IAAA,CAAK,UAAU,CAAA;AAE1C,IAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,OAAA,EAAS,MAAA,KAAW;AACtC,MAAA,IAAA,CAAK,MAAA,CAAQ,MAAA,CAAOF,KAAAA,EAAMC,KAAAA,EAAM,MAAM;AACpC,QAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,0BAAA,EAA6BA,KAAI,CAAA,CAAA,EAAID,KAAI,CAAA,CAAE,CAAA;AACvD,QAAA,IAAI,KAAK,WAAA,EAAa;AACpB,UAAA,OAAA,CAAQ,IAAI,0DAA0D,CAAA;AAAA,QACxE;AACA,QAAA,OAAA,EAAQ;AAAA,MACV,CAAC,CAAA;AAED,MAAA,IAAA,CAAK,MAAA,CAAQ,EAAA,CAAG,OAAA,EAAS,MAAM,CAAA;AAAA,IACjC,CAAC,CAAA;AAAA,EACH;AAAA,EAEA,MAAM,IAAA,GAAsB;AAE1B,IAAA,IAAI,KAAK,WAAA,EAAa;AACpB,MAAA,MAAM,IAAA,CAAK,YAAY,OAAA,EAAQ;AAAA,IACjC;AAEA,IAAA,MAAM,IAAA,CAAK,OAAO,IAAA,EAAK;AAEvB,IAAA,IAAI,KAAK,MAAA,EAAQ;AACf,MAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,OAAA,KAAY;AAC9B,QAAA,IAAA,CAAK,MAAA,CAAQ,KAAA,CAAM,MAAM,OAAA,EAAS,CAAA;AAAA,MACpC,CAAC,CAAA;AAAA,IACH;AAAA,EACF;AAAA,EAEA,MAAc,QAAA,GAA0B;AACtC,IAAA,IAAA,CAAK,cAAA,GAAiB,IAAA;AACtB,IAAA,OAAA,CAAQ,IAAI,+BAA+B,CAAA;AAC3C,IAAA,MAAM,KAAK,IAAA,EAAK;AAChB,IAAA,OAAA,CAAQ,KAAK,CAAC,CAAA;AAAA,EAChB;AAAA,EAEA,MAAc,aAAA,CAAc,GAAA,EAAsB,GAAA,EAAoC;AAEpF,IAAA,GAAA,CAAI,SAAA,CAAU,+BAA+B,GAAG,CAAA;AAChD,IAAA,GAAA,CAAI,SAAA,CAAU,gCAAgC,iCAAiC,CAAA;AAC/E,IAAA,GAAA,CAAI,SAAA,CAAU,gCAAgC,cAAc,CAAA;AAG5D,IAAA,IAAI,GAAA,CAAI,WAAW,SAAA,EAAW;AAC5B,MAAA,GAAA,CAAI,UAAU,GAAG,CAAA;AACjB,MAAA,GAAA,CAAI,GAAA,EAAI;AACR,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,GAAA,GAAM,IAAI,GAAA,CAAI,GAAA,CAAI,KAAM,CAAA,OAAA,EAAU,GAAA,CAAI,OAAA,CAAQ,IAAI,CAAA,CAAE,CAAA;AAE1D,IAAA,IAAI;AAEF,MAAA,IAAI,GAAA,CAAI,MAAA,KAAW,KAAA,IAAS,GAAA,CAAI,aAAa,SAAA,EAAW;AACtD,QAAA,MAAM,MAAA,GAAS,MAAM,IAAA,CAAK,MAAA,CAAO,SAAA,EAAU;AAC3C,QAAA,GAAA,CAAI,SAAA,CAAU,GAAA,EAAK,EAAE,cAAA,EAAgB,oBAAoB,CAAA;AACzD,QAAA,GAAA,CAAI,GAAA,CAAI,IAAA,CAAK,SAAA,CAAU,MAAM,CAAC,CAAA;AAC9B,QAAA;AAAA,MACF;AAGA,MAAA,IAAI,GAAA,CAAI,MAAA,KAAW,KAAA,IAAS,GAAA,CAAI,aAAa,QAAA,EAAU;AACrD,QAAA,MAAM,KAAA,GAAQ,MAAM,IAAA,CAAK,MAAA,CAAO,QAAA,EAAS;AACzC,QAAA,GAAA,CAAI,SAAA,CAAU,GAAA,EAAK,EAAE,cAAA,EAAgB,oBAAoB,CAAA;AACzD,QAAA,GAAA,CAAI,GAAA,CAAI,IAAA,CAAK,SAAA,CAAU,KAAK,CAAC,CAAA;AAC7B,QAAA;AAAA,MACF;AAGA,MAAA,IAAI,IAAI,MAAA,KAAW,KAAA,IAAS,IAAI,QAAA,CAAS,UAAA,CAAW,SAAS,CAAA,EAAG;AAC9D,QAAA,MAAM,MAAM,kBAAA,CAAmB,GAAA,CAAI,QAAA,CAAS,KAAA,CAAM,CAAC,CAAC,CAAA;AACpD,QAAA,MAAM,KAAA,GAAQ,MAAM,IAAA,CAAK,MAAA,CAAO,IAAI,GAAG,CAAA;AAEvC,QAAA,IAAI,UAAU,IAAA,EAAM;AAClB,UAAA,GAAA,CAAI,UAAU,GAAG,CAAA;AACjB,UAAA,GAAA,CAAI,GAAA,EAAI;AACR,UAAA;AAAA,QACF;AAEA,QAAA,GAAA,CAAI,SAAA,CAAU,GAAA,EAAK,EAAE,cAAA,EAAgB,oBAAoB,CAAA;AACzD,QAAA,GAAA,CAAI,GAAA,CAAI,IAAA,CAAK,SAAA,CAAU,KAAK,CAAC,CAAA;AAC7B,QAAA;AAAA,MACF;AAGA,MAAA,IAAI,IAAI,MAAA,KAAW,KAAA,IAAS,IAAI,QAAA,CAAS,UAAA,CAAW,SAAS,CAAA,EAAG;AAC9D,QAAA,MAAM,MAAM,kBAAA,CAAmB,GAAA,CAAI,QAAA,CAAS,KAAA,CAAM,CAAC,CAAC,CAAA;AACpD,QAAA,MAAM,IAAA,GAAO,MAAM,IAAA,CAAK,QAAA,CAAS,GAAG,CAAA;AACpC,QAAA,MAAM,EAAE,KAAA,EAAO,GAAA,EAAI,GAAI,IAAA,CAAK,MAAM,IAAI,CAAA;AAEtC,QAAA,MAAM,IAAA,CAAK,MAAA,CAAO,GAAA,CAAI,GAAA,EAAK,OAAO,GAAG,CAAA;AAErC,QAAA,GAAA,CAAI,UAAU,GAAG,CAAA;AACjB,QAAA,GAAA,CAAI,GAAA,EAAI;AACR,QAAA;AAAA,MACF;AAGA,MAAA,IAAI,IAAI,MAAA,KAAW,QAAA,IAAY,IAAI,QAAA,CAAS,UAAA,CAAW,SAAS,CAAA,EAAG;AACjE,QAAA,MAAM,MAAM,kBAAA,CAAmB,GAAA,CAAI,QAAA,CAAS,KAAA,CAAM,CAAC,CAAC,CAAA;AACpD,QAAA,MAAM,IAAA,CAAK,MAAA,CAAO,MAAA,CAAO,GAAG,CAAA;AAE5B,QAAA,GAAA,CAAI,UAAU,GAAG,CAAA;AACjB,QAAA,GAAA,CAAI,GAAA,EAAI;AACR,QAAA;AAAA,MACF;AAGA,MAAA,IAAI,GAAA,CAAI,MAAA,KAAW,MAAA,IAAU,GAAA,CAAI,aAAa,cAAA,EAAgB;AAC5D,QAAA,MAAM,OAAA,GAAU,GAAA,CAAI,YAAA,CAAa,GAAA,CAAI,SAAS,CAAA,IAAK,KAAA,CAAA;AACnD,QAAA,MAAM,IAAA,CAAK,MAAA,CAAO,KAAA,CAAM,OAAO,CAAA;AAE/B,QAAA,GAAA,CAAI,UAAU,GAAG,CAAA;AACjB,QAAA,GAAA,CAAI,GAAA,EAAI;AACR,QAAA;AAAA,MACF;AAGA,MAAA,IAAI,GAAA,CAAI,MAAA,KAAW,KAAA,IAAS,GAAA,CAAI,aAAa,OAAA,EAAS;AACpD,QAAA,IAAI,CAAC,KAAK,WAAA,EAAa;AACrB,UAAA,GAAA,CAAI,SAAA,CAAU,GAAA,EAAK,EAAE,cAAA,EAAgB,oBAAoB,CAAA;AACzD,UAAA,GAAA,CAAI,IAAI,IAAA,CAAK,SAAA,CAAU,EAAE,KAAA,EAAO,0BAAA,EAA4B,CAAC,CAAA;AAC7D,UAAA;AAAA,QACF;AAEA,QAAA,MAAM,IAAA,GAAO,IAAA,CAAK,WAAA,CAAY,QAAA,EAAS;AACvC,QAAA,GAAA,CAAI,SAAA,CAAU,GAAA,EAAK,EAAE,cAAA,EAAgB,oBAAoB,CAAA;AACzD,QAAA,GAAA,CAAI,IAAI,IAAA,CAAK,SAAA,CAAU,EAAE,IAAA,EAAM,CAAC,CAAA;AAChC,QAAA;AAAA,MACF;AAGA,MAAA,IAAI,GAAA,CAAI,MAAA,KAAW,MAAA,IAAU,GAAA,CAAI,QAAA,CAAS,UAAA,CAAW,QAAQ,CAAA,IAAK,GAAA,CAAI,QAAA,CAAS,QAAA,CAAS,UAAU,CAAA,EAAG;AACnG,QAAA,IAAI,CAAC,KAAK,WAAA,EAAa;AACrB,UAAA,GAAA,CAAI,SAAA,CAAU,GAAA,EAAK,EAAE,cAAA,EAAgB,oBAAoB,CAAA;AACzD,UAAA,GAAA,CAAI,IAAI,IAAA,CAAK,SAAA,CAAU,EAAE,KAAA,EAAO,0BAAA,EAA4B,CAAC,CAAA;AAC7D,UAAA;AAAA,QACF;AAEA,QAAA,MAAM,KAAK,kBAAA,CAAmB,GAAA,CAAI,SAAS,KAAA,CAAM,CAAA,EAAG,EAAE,CAAC,CAAA;AAEvD,QAAA,IAAI;AACF,UAAA,MAAM,IAAA,CAAK,WAAA,CAAY,UAAA,CAAW,EAAE,CAAA;AACpC,UAAA,GAAA,CAAI,SAAA,CAAU,GAAA,EAAK,EAAE,cAAA,EAAgB,oBAAoB,CAAA;AACzD,UAAA,GAAA,CAAI,GAAA,CAAI,IAAA,CAAK,SAAA,CAAU,EAAE,EAAA,EAAI,IAAA,EAAM,OAAA,EAAS,CAAA,IAAA,EAAO,EAAE,CAAA,uBAAA,CAAA,EAA2B,CAAC,CAAA;AAAA,QACnF,SAAS,KAAA,EAAO;AACd,UAAA,GAAA,CAAI,SAAA,CAAU,GAAA,EAAK,EAAE,cAAA,EAAgB,oBAAoB,CAAA;AACzD,UAAA,GAAA,CAAI,GAAA,CAAI,KAAK,SAAA,CAAU;AAAA,YACrB,KAAA,EAAO,KAAA,YAAiB,KAAA,GAAQ,KAAA,CAAM,OAAA,GAAU;AAAA,WACjD,CAAC,CAAA;AAAA,QACJ;AACA,QAAA;AAAA,MACF;AAGA,MAAA,IAAI,GAAA,CAAI,MAAA,KAAW,KAAA,IAAS,GAAA,CAAI,aAAa,aAAA,EAAe;AAC1D,QAAA,IAAI,CAAC,KAAK,WAAA,EAAa;AACrB,UAAA,GAAA,CAAI,SAAA,CAAU,GAAA,EAAK,EAAE,cAAA,EAAgB,oBAAoB,CAAA;AACzD,UAAA,GAAA,CAAI,IAAI,IAAA,CAAK,SAAA,CAAU,EAAE,KAAA,EAAO,0BAAA,EAA4B,CAAC,CAAA;AAC7D,UAAA;AAAA,QACF;AAEA,QAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,WAAA,CAAY,QAAA,EAAS;AACxC,QAAA,GAAA,CAAI,SAAA,CAAU,GAAA,EAAK,EAAE,cAAA,EAAgB,oBAAoB,CAAA;AACzD,QAAA,GAAA,CAAI,GAAA,CAAI,IAAA,CAAK,SAAA,CAAU,KAAK,CAAC,CAAA;AAC7B,QAAA;AAAA,MACF;AAGA,MAAA,GAAA,CAAI,SAAA,CAAU,GAAA,EAAK,EAAE,cAAA,EAAgB,oBAAoB,CAAA;AACzD,MAAA,GAAA,CAAI,IAAI,IAAA,CAAK,SAAA,CAAU,EAAE,KAAA,EAAO,WAAA,EAAa,CAAC,CAAA;AAAA,IAChD,SAAS,KAAA,EAAO;AACd,MAAA,OAAA,CAAQ,KAAA,CAAM,kBAAkB,KAAK,CAAA;AACrC,MAAA,GAAA,CAAI,SAAA,CAAU,GAAA,EAAK,EAAE,cAAA,EAAgB,oBAAoB,CAAA;AACzD,MAAA,GAAA,CAAI,GAAA,CAAI,KAAK,SAAA,CAAU;AAAA,QACrB,KAAA,EAAO,KAAA,YAAiB,KAAA,GAAQ,KAAA,CAAM,OAAA,GAAU;AAAA,OACjD,CAAC,CAAA;AAAA,IACJ;AAAA,EACF;AAAA,EAEA,MAAc,SAAS,GAAA,EAAuC;AAC5D,IAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,OAAA,EAAS,MAAA,KAAW;AACtC,MAAA,MAAM,SAAmB,EAAC;AAC1B,MAAA,GAAA,CAAI,GAAG,MAAA,EAAQ,CAAC,UAAU,MAAA,CAAO,IAAA,CAAK,KAAK,CAAC,CAAA;AAC5C,MAAA,GAAA,CAAI,EAAA,CAAG,KAAA,EAAO,MAAM,OAAA,CAAQ,MAAA,CAAO,OAAO,MAAM,CAAA,CAAE,QAAA,EAAU,CAAC,CAAA;AAC7D,MAAA,GAAA,CAAI,EAAA,CAAG,SAAS,MAAM,CAAA;AAAA,IACxB,CAAC,CAAA;AAAA,EACH;AACF,CAAA;;;AC7OA,IAAM,IAAA,GAAO,QAAQ,GAAA,CAAI,oBAAA,GACrB,SAAS,OAAA,CAAQ,GAAA,CAAI,oBAAA,EAAsB,EAAE,CAAA,GAC7C,IAAA;AAEJ,IAAM,IAAA,GAAO,OAAA,CAAQ,GAAA,CAAI,oBAAA,IAAwB,WAAA;AAEjD,IAAM,SAAS,IAAI,iBAAA,CAAkB,EAAE,IAAA,EAAM,MAAM,CAAA;AAEnD,MAAA,CAAO,KAAA,EAAM,CAAE,KAAA,CAAM,CAAC,KAAA,KAAU;AAC9B,EAAA,OAAA,CAAQ,KAAA,CAAM,iCAAiC,KAAK,CAAA;AACpD,EAAA,OAAA,CAAQ,KAAK,CAAC,CAAA;AAChB,CAAC,CAAA","file":"bin.cjs","sourcesContent":["/**\n * In-memory state broker with TTL cleanup\n */\n\nimport type { StateBroker, BrokerStats, HealthStatus } from '../index';\n\ninterface CacheEntry {\n value: unknown;\n expiresAt: number;\n size: number; // estimated size in bytes\n namespace: string;\n}\n\nexport class InMemoryStateBroker implements StateBroker {\n private store = new Map<string, CacheEntry>();\n private cleanupInterval: NodeJS.Timeout;\n private startTime: number;\n private hits = 0;\n private misses = 0;\n private evictions = 0;\n\n constructor(\n private cleanupIntervalMs = 30_000 // cleanup every 30s\n ) {\n this.startTime = Date.now();\n this.cleanupInterval = setInterval(() => this.cleanup(), cleanupIntervalMs);\n }\n\n async get<T>(key: string): Promise<T | null> {\n const entry = this.store.get(key);\n\n if (!entry) {\n this.misses++;\n return null;\n }\n\n // Check expiration\n if (Date.now() > entry.expiresAt) {\n this.store.delete(key);\n this.misses++;\n return null;\n }\n\n this.hits++;\n return entry.value as T;\n }\n\n async set<T>(key: string, value: T, ttl = 300_000): Promise<void> {\n const namespace = this.extractNamespace(key);\n const size = this.estimateSize(value);\n\n this.store.set(key, {\n value,\n expiresAt: Date.now() + ttl,\n size,\n namespace,\n });\n }\n\n async delete(key: string): Promise<void> {\n this.store.delete(key);\n }\n\n async clear(pattern?: string): Promise<void> {\n if (!pattern) {\n this.store.clear();\n return;\n }\n\n // Simple pattern matching (namespace prefix)\n const prefix = pattern.replace('*', '');\n for (const key of this.store.keys()) {\n if (key.startsWith(prefix)) {\n this.store.delete(key);\n }\n }\n }\n\n async getStats(): Promise<BrokerStats> {\n const namespaces: Record<string, { entries: number; size: number; oldestEntry: number }> = {};\n const byTenant: Record<string, { entries: number; size: number; lastAccess: number }> = {};\n let totalSize = 0;\n const now = Date.now();\n\n for (const [key, entry] of this.store.entries()) {\n // Namespace stats (existing)\n if (!namespaces[entry.namespace]) {\n namespaces[entry.namespace] = { entries: 0, size: 0, oldestEntry: now };\n }\n\n const ns = namespaces[entry.namespace]!;\n ns.entries++;\n ns.size += entry.size;\n totalSize += entry.size;\n\n const entryAge = entry.expiresAt - now;\n if (entryAge < ns.oldestEntry) {\n ns.oldestEntry = entryAge;\n }\n\n // Tenant stats (new - multi-tenancy support)\n const tenant = this.extractTenant(key);\n if (!byTenant[tenant]) {\n byTenant[tenant] = { entries: 0, size: 0, lastAccess: now };\n }\n byTenant[tenant].entries++;\n byTenant[tenant].size += entry.size;\n if (entry.expiresAt > byTenant[tenant].lastAccess) {\n byTenant[tenant].lastAccess = entry.expiresAt;\n }\n }\n\n const totalRequests = this.hits + this.misses;\n const hitRate = totalRequests > 0 ? this.hits / totalRequests : 0;\n const missRate = totalRequests > 0 ? this.misses / totalRequests : 0;\n\n return {\n uptime: Date.now() - this.startTime,\n totalEntries: this.store.size,\n totalSize,\n hitRate,\n missRate,\n evictions: this.evictions,\n namespaces,\n byTenant, // ← New: stats by tenant\n };\n }\n\n async getHealth(): Promise<HealthStatus> {\n const stats = await this.getStats();\n return {\n status: 'ok',\n version: '0.1.0',\n stats,\n };\n }\n\n async stop(): Promise<void> {\n clearInterval(this.cleanupInterval);\n this.store.clear();\n }\n\n /**\n * Cleanup expired entries\n */\n private cleanup() {\n const now = Date.now();\n for (const [key, entry] of this.store.entries()) {\n if (now > entry.expiresAt) {\n this.store.delete(key);\n this.evictions++;\n }\n }\n }\n\n /**\n * Extract namespace from key (format: namespace:key or tenant:tenantId:namespace:key)\n */\n private extractNamespace(key: string): string {\n const parts = key.split(':');\n // New format: tenant:default:mind:key → namespace: mind\n // Old format: mind:key → namespace: mind\n if (parts[0] === 'tenant' && parts.length >= 3) {\n return parts[2] || 'default';\n }\n return parts[0] || 'default';\n }\n\n /**\n * Extract tenant ID from key (format: tenant:tenantId:namespace:key)\n * For backward compatibility, returns 'default' if no tenant prefix\n */\n private extractTenant(key: string): string {\n const parts = key.split(':');\n // New format: tenant:default:mind:key → tenant: default\n // Old format: mind:key → tenant: default (backward compatible)\n if (parts[0] === 'tenant' && parts.length >= 2) {\n return parts[1] || 'default';\n }\n return 'default';\n }\n\n /**\n * Estimate size of value in bytes\n */\n private estimateSize(value: unknown): number {\n try {\n return JSON.stringify(value).length;\n } catch {\n return 0;\n }\n }\n}\n","/**\n * State daemon HTTP server\n */\n\nimport { createServer, type Server, type IncomingMessage, type ServerResponse } from 'node:http';\nimport { InMemoryStateBroker } from '@kb-labs/core-state-broker';\n// import { JobsManager } from './jobs-manager'; // DISABLED: missing dependencies\n\nexport interface StateDaemonConfig {\n port?: number;\n host?: string;\n enableJobs?: boolean;\n}\n\nexport class StateDaemonServer {\n private broker: InMemoryStateBroker;\n private jobsManager: any | null = null; // DISABLED: JobsManager has missing dependencies\n private server: Server | null = null;\n private isShuttingDown = false;\n\n constructor(private config: StateDaemonConfig = {}) {\n this.broker = new InMemoryStateBroker();\n\n // Initialize jobs manager if enabled\n // DISABLED: JobsManager has missing dependencies\n // if (this.config.enableJobs !== false) {\n // const createLogger = (prefix: string = '[jobs]'): import('@kb-labs/core-platform').ILogger => ({\n // trace: (msg: string, meta?: Record<string, unknown>) => console.debug(prefix, '[trace]', msg, meta),\n // debug: (msg: string, meta?: Record<string, unknown>) => console.debug(prefix, msg, meta),\n // info: (msg: string, meta?: Record<string, unknown>) => console.log(prefix, msg, meta),\n // warn: (msg: string, meta?: Record<string, unknown>) => console.warn(prefix, msg, meta),\n // error: (msg: string, error?: Error, meta?: Record<string, unknown>) => console.error(prefix, msg, error, meta),\n // child: (bindings: Record<string, unknown>) => createLogger(`${prefix}:${JSON.stringify(bindings)}`),\n // });\n\n // this.jobsManager = new JobsManager({\n // logger: createLogger('[jobs]'),\n // });\n // }\n }\n\n async start(): Promise<void> {\n const port = this.config.port ?? 7777;\n const host = this.config.host ?? 'localhost';\n\n // Initialize jobs manager if enabled\n if (this.jobsManager) {\n await this.jobsManager.initialize();\n }\n\n this.server = createServer((req, res) => this.handleRequest(req, res));\n\n // Handle shutdown signals\n process.on('SIGTERM', () => this.shutdown());\n process.on('SIGINT', () => this.shutdown());\n\n return new Promise((resolve, reject) => {\n this.server!.listen(port, host, () => {\n console.log(`State daemon listening on ${host}:${port}`);\n if (this.jobsManager) {\n console.log('Jobs manager enabled - HTTP endpoints available at /jobs');\n }\n resolve();\n });\n\n this.server!.on('error', reject);\n });\n }\n\n async stop(): Promise<void> {\n // Dispose jobs manager\n if (this.jobsManager) {\n await this.jobsManager.dispose();\n }\n\n await this.broker.stop();\n\n if (this.server) {\n return new Promise((resolve) => {\n this.server!.close(() => resolve());\n });\n }\n }\n\n private async shutdown(): Promise<void> {\n this.isShuttingDown = true;\n console.log('Shutting down state daemon...');\n await this.stop();\n process.exit(0);\n }\n\n private async handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {\n // CORS headers\n res.setHeader('Access-Control-Allow-Origin', '*');\n res.setHeader('Access-Control-Allow-Methods', 'GET, PUT, DELETE, POST, OPTIONS');\n res.setHeader('Access-Control-Allow-Headers', 'Content-Type');\n\n // Handle OPTIONS (preflight)\n if (req.method === 'OPTIONS') {\n res.writeHead(204);\n res.end();\n return;\n }\n\n const url = new URL(req.url!, `http://${req.headers.host}`);\n\n try {\n // GET /health\n if (req.method === 'GET' && url.pathname === '/health') {\n const health = await this.broker.getHealth();\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(health));\n return;\n }\n\n // GET /stats\n if (req.method === 'GET' && url.pathname === '/stats') {\n const stats = await this.broker.getStats();\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(stats));\n return;\n }\n\n // GET /state/:key\n if (req.method === 'GET' && url.pathname.startsWith('/state/')) {\n const key = decodeURIComponent(url.pathname.slice(7));\n const value = await this.broker.get(key);\n\n if (value === null) {\n res.writeHead(404);\n res.end();\n return;\n }\n\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(value));\n return;\n }\n\n // PUT /state/:key\n if (req.method === 'PUT' && url.pathname.startsWith('/state/')) {\n const key = decodeURIComponent(url.pathname.slice(7));\n const body = await this.readBody(req);\n const { value, ttl } = JSON.parse(body);\n\n await this.broker.set(key, value, ttl);\n\n res.writeHead(204);\n res.end();\n return;\n }\n\n // DELETE /state/:key\n if (req.method === 'DELETE' && url.pathname.startsWith('/state/')) {\n const key = decodeURIComponent(url.pathname.slice(7));\n await this.broker.delete(key);\n\n res.writeHead(204);\n res.end();\n return;\n }\n\n // POST /state/clear\n if (req.method === 'POST' && url.pathname === '/state/clear') {\n const pattern = url.searchParams.get('pattern') || undefined;\n await this.broker.clear(pattern);\n\n res.writeHead(204);\n res.end();\n return;\n }\n\n // GET /jobs - List all registered jobs\n if (req.method === 'GET' && url.pathname === '/jobs') {\n if (!this.jobsManager) {\n res.writeHead(503, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Jobs manager not enabled' }));\n return;\n }\n\n const jobs = this.jobsManager.listJobs();\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ jobs }));\n return;\n }\n\n // POST /jobs/:id/trigger - Manually trigger a job\n if (req.method === 'POST' && url.pathname.startsWith('/jobs/') && url.pathname.endsWith('/trigger')) {\n if (!this.jobsManager) {\n res.writeHead(503, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Jobs manager not enabled' }));\n return;\n }\n\n const id = decodeURIComponent(url.pathname.slice(6, -8)); // Remove \"/jobs/\" and \"/trigger\"\n\n try {\n await this.jobsManager.triggerJob(id);\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ ok: true, message: `Job ${id} triggered successfully` }));\n } catch (error) {\n res.writeHead(404, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({\n error: error instanceof Error ? error.message : 'Job not found'\n }));\n }\n return;\n }\n\n // GET /jobs/stats - Get jobs statistics\n if (req.method === 'GET' && url.pathname === '/jobs/stats') {\n if (!this.jobsManager) {\n res.writeHead(503, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Jobs manager not enabled' }));\n return;\n }\n\n const stats = this.jobsManager.getStats();\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(stats));\n return;\n }\n\n // 404 Not Found\n res.writeHead(404, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Not Found' }));\n } catch (error) {\n console.error('Request error:', error);\n res.writeHead(500, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({\n error: error instanceof Error ? error.message : 'Internal Server Error',\n }));\n }\n }\n\n private async readBody(req: IncomingMessage): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on('data', (chunk) => chunks.push(chunk));\n req.on('end', () => resolve(Buffer.concat(chunks).toString()));\n req.on('error', reject);\n });\n }\n}\n","/**\n * State daemon CLI\n */\n\nimport { StateDaemonServer } from './server';\n\nconst port = process.env.KB_STATE_DAEMON_PORT\n ? parseInt(process.env.KB_STATE_DAEMON_PORT, 10)\n : 7777;\n\nconst host = process.env.KB_STATE_DAEMON_HOST ?? 'localhost';\n\nconst server = new StateDaemonServer({ port, host });\n\nserver.start().catch((error) => {\n console.error('Failed to start state daemon:', error);\n process.exit(1);\n});\n"]}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { createServer } from 'http';
|
|
2
|
+
import { InMemoryStateBroker } from '@kb-labs/core-state-broker';
|
|
3
|
+
|
|
4
|
+
// src/server.ts
|
|
5
|
+
var StateDaemonServer = class {
|
|
6
|
+
constructor(config = {}) {
|
|
7
|
+
this.config = config;
|
|
8
|
+
this.broker = new InMemoryStateBroker();
|
|
9
|
+
}
|
|
10
|
+
broker;
|
|
11
|
+
jobsManager = null;
|
|
12
|
+
// DISABLED: JobsManager has missing dependencies
|
|
13
|
+
server = null;
|
|
14
|
+
isShuttingDown = false;
|
|
15
|
+
async start() {
|
|
16
|
+
const port = this.config.port ?? 7777;
|
|
17
|
+
const host = this.config.host ?? "localhost";
|
|
18
|
+
if (this.jobsManager) {
|
|
19
|
+
await this.jobsManager.initialize();
|
|
20
|
+
}
|
|
21
|
+
this.server = createServer((req, res) => this.handleRequest(req, res));
|
|
22
|
+
process.on("SIGTERM", () => this.shutdown());
|
|
23
|
+
process.on("SIGINT", () => this.shutdown());
|
|
24
|
+
return new Promise((resolve, reject) => {
|
|
25
|
+
this.server.listen(port, host, () => {
|
|
26
|
+
console.log(`State daemon listening on ${host}:${port}`);
|
|
27
|
+
if (this.jobsManager) {
|
|
28
|
+
console.log("Jobs manager enabled - HTTP endpoints available at /jobs");
|
|
29
|
+
}
|
|
30
|
+
resolve();
|
|
31
|
+
});
|
|
32
|
+
this.server.on("error", reject);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
async stop() {
|
|
36
|
+
if (this.jobsManager) {
|
|
37
|
+
await this.jobsManager.dispose();
|
|
38
|
+
}
|
|
39
|
+
await this.broker.stop();
|
|
40
|
+
if (this.server) {
|
|
41
|
+
return new Promise((resolve) => {
|
|
42
|
+
this.server.close(() => resolve());
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async shutdown() {
|
|
47
|
+
this.isShuttingDown = true;
|
|
48
|
+
console.log("Shutting down state daemon...");
|
|
49
|
+
await this.stop();
|
|
50
|
+
process.exit(0);
|
|
51
|
+
}
|
|
52
|
+
async handleRequest(req, res) {
|
|
53
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
54
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, PUT, DELETE, POST, OPTIONS");
|
|
55
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
56
|
+
if (req.method === "OPTIONS") {
|
|
57
|
+
res.writeHead(204);
|
|
58
|
+
res.end();
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
62
|
+
try {
|
|
63
|
+
if (req.method === "GET" && url.pathname === "/health") {
|
|
64
|
+
const health = await this.broker.getHealth();
|
|
65
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
66
|
+
res.end(JSON.stringify(health));
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (req.method === "GET" && url.pathname === "/stats") {
|
|
70
|
+
const stats = await this.broker.getStats();
|
|
71
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
72
|
+
res.end(JSON.stringify(stats));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (req.method === "GET" && url.pathname.startsWith("/state/")) {
|
|
76
|
+
const key = decodeURIComponent(url.pathname.slice(7));
|
|
77
|
+
const value = await this.broker.get(key);
|
|
78
|
+
if (value === null) {
|
|
79
|
+
res.writeHead(404);
|
|
80
|
+
res.end();
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
84
|
+
res.end(JSON.stringify(value));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (req.method === "PUT" && url.pathname.startsWith("/state/")) {
|
|
88
|
+
const key = decodeURIComponent(url.pathname.slice(7));
|
|
89
|
+
const body = await this.readBody(req);
|
|
90
|
+
const { value, ttl } = JSON.parse(body);
|
|
91
|
+
await this.broker.set(key, value, ttl);
|
|
92
|
+
res.writeHead(204);
|
|
93
|
+
res.end();
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (req.method === "DELETE" && url.pathname.startsWith("/state/")) {
|
|
97
|
+
const key = decodeURIComponent(url.pathname.slice(7));
|
|
98
|
+
await this.broker.delete(key);
|
|
99
|
+
res.writeHead(204);
|
|
100
|
+
res.end();
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (req.method === "POST" && url.pathname === "/state/clear") {
|
|
104
|
+
const pattern = url.searchParams.get("pattern") || void 0;
|
|
105
|
+
await this.broker.clear(pattern);
|
|
106
|
+
res.writeHead(204);
|
|
107
|
+
res.end();
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (req.method === "GET" && url.pathname === "/jobs") {
|
|
111
|
+
if (!this.jobsManager) {
|
|
112
|
+
res.writeHead(503, { "Content-Type": "application/json" });
|
|
113
|
+
res.end(JSON.stringify({ error: "Jobs manager not enabled" }));
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const jobs = this.jobsManager.listJobs();
|
|
117
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
118
|
+
res.end(JSON.stringify({ jobs }));
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (req.method === "POST" && url.pathname.startsWith("/jobs/") && url.pathname.endsWith("/trigger")) {
|
|
122
|
+
if (!this.jobsManager) {
|
|
123
|
+
res.writeHead(503, { "Content-Type": "application/json" });
|
|
124
|
+
res.end(JSON.stringify({ error: "Jobs manager not enabled" }));
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const id = decodeURIComponent(url.pathname.slice(6, -8));
|
|
128
|
+
try {
|
|
129
|
+
await this.jobsManager.triggerJob(id);
|
|
130
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
131
|
+
res.end(JSON.stringify({ ok: true, message: `Job ${id} triggered successfully` }));
|
|
132
|
+
} catch (error) {
|
|
133
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
134
|
+
res.end(JSON.stringify({
|
|
135
|
+
error: error instanceof Error ? error.message : "Job not found"
|
|
136
|
+
}));
|
|
137
|
+
}
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
if (req.method === "GET" && url.pathname === "/jobs/stats") {
|
|
141
|
+
if (!this.jobsManager) {
|
|
142
|
+
res.writeHead(503, { "Content-Type": "application/json" });
|
|
143
|
+
res.end(JSON.stringify({ error: "Jobs manager not enabled" }));
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const stats = this.jobsManager.getStats();
|
|
147
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
148
|
+
res.end(JSON.stringify(stats));
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
152
|
+
res.end(JSON.stringify({ error: "Not Found" }));
|
|
153
|
+
} catch (error) {
|
|
154
|
+
console.error("Request error:", error);
|
|
155
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
156
|
+
res.end(JSON.stringify({
|
|
157
|
+
error: error instanceof Error ? error.message : "Internal Server Error"
|
|
158
|
+
}));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
async readBody(req) {
|
|
162
|
+
return new Promise((resolve, reject) => {
|
|
163
|
+
const chunks = [];
|
|
164
|
+
req.on("data", (chunk) => chunks.push(chunk));
|
|
165
|
+
req.on("end", () => resolve(Buffer.concat(chunks).toString()));
|
|
166
|
+
req.on("error", reject);
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
export { StateDaemonServer };
|
|
172
|
+
//# sourceMappingURL=index.js.map
|
|
173
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * State daemon HTTP server\n */\n\nimport { createServer, type Server, type IncomingMessage, type ServerResponse } from 'node:http';\nimport { InMemoryStateBroker } from '@kb-labs/core-state-broker';\n// import { JobsManager } from './jobs-manager'; // DISABLED: missing dependencies\n\nexport interface StateDaemonConfig {\n port?: number;\n host?: string;\n enableJobs?: boolean;\n}\n\nexport class StateDaemonServer {\n private broker: InMemoryStateBroker;\n private jobsManager: any | null = null; // DISABLED: JobsManager has missing dependencies\n private server: Server | null = null;\n private isShuttingDown = false;\n\n constructor(private config: StateDaemonConfig = {}) {\n this.broker = new InMemoryStateBroker();\n\n // Initialize jobs manager if enabled\n // DISABLED: JobsManager has missing dependencies\n // if (this.config.enableJobs !== false) {\n // const createLogger = (prefix: string = '[jobs]'): import('@kb-labs/core-platform').ILogger => ({\n // trace: (msg: string, meta?: Record<string, unknown>) => console.debug(prefix, '[trace]', msg, meta),\n // debug: (msg: string, meta?: Record<string, unknown>) => console.debug(prefix, msg, meta),\n // info: (msg: string, meta?: Record<string, unknown>) => console.log(prefix, msg, meta),\n // warn: (msg: string, meta?: Record<string, unknown>) => console.warn(prefix, msg, meta),\n // error: (msg: string, error?: Error, meta?: Record<string, unknown>) => console.error(prefix, msg, error, meta),\n // child: (bindings: Record<string, unknown>) => createLogger(`${prefix}:${JSON.stringify(bindings)}`),\n // });\n\n // this.jobsManager = new JobsManager({\n // logger: createLogger('[jobs]'),\n // });\n // }\n }\n\n async start(): Promise<void> {\n const port = this.config.port ?? 7777;\n const host = this.config.host ?? 'localhost';\n\n // Initialize jobs manager if enabled\n if (this.jobsManager) {\n await this.jobsManager.initialize();\n }\n\n this.server = createServer((req, res) => this.handleRequest(req, res));\n\n // Handle shutdown signals\n process.on('SIGTERM', () => this.shutdown());\n process.on('SIGINT', () => this.shutdown());\n\n return new Promise((resolve, reject) => {\n this.server!.listen(port, host, () => {\n console.log(`State daemon listening on ${host}:${port}`);\n if (this.jobsManager) {\n console.log('Jobs manager enabled - HTTP endpoints available at /jobs');\n }\n resolve();\n });\n\n this.server!.on('error', reject);\n });\n }\n\n async stop(): Promise<void> {\n // Dispose jobs manager\n if (this.jobsManager) {\n await this.jobsManager.dispose();\n }\n\n await this.broker.stop();\n\n if (this.server) {\n return new Promise((resolve) => {\n this.server!.close(() => resolve());\n });\n }\n }\n\n private async shutdown(): Promise<void> {\n this.isShuttingDown = true;\n console.log('Shutting down state daemon...');\n await this.stop();\n process.exit(0);\n }\n\n private async handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {\n // CORS headers\n res.setHeader('Access-Control-Allow-Origin', '*');\n res.setHeader('Access-Control-Allow-Methods', 'GET, PUT, DELETE, POST, OPTIONS');\n res.setHeader('Access-Control-Allow-Headers', 'Content-Type');\n\n // Handle OPTIONS (preflight)\n if (req.method === 'OPTIONS') {\n res.writeHead(204);\n res.end();\n return;\n }\n\n const url = new URL(req.url!, `http://${req.headers.host}`);\n\n try {\n // GET /health\n if (req.method === 'GET' && url.pathname === '/health') {\n const health = await this.broker.getHealth();\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(health));\n return;\n }\n\n // GET /stats\n if (req.method === 'GET' && url.pathname === '/stats') {\n const stats = await this.broker.getStats();\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(stats));\n return;\n }\n\n // GET /state/:key\n if (req.method === 'GET' && url.pathname.startsWith('/state/')) {\n const key = decodeURIComponent(url.pathname.slice(7));\n const value = await this.broker.get(key);\n\n if (value === null) {\n res.writeHead(404);\n res.end();\n return;\n }\n\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(value));\n return;\n }\n\n // PUT /state/:key\n if (req.method === 'PUT' && url.pathname.startsWith('/state/')) {\n const key = decodeURIComponent(url.pathname.slice(7));\n const body = await this.readBody(req);\n const { value, ttl } = JSON.parse(body);\n\n await this.broker.set(key, value, ttl);\n\n res.writeHead(204);\n res.end();\n return;\n }\n\n // DELETE /state/:key\n if (req.method === 'DELETE' && url.pathname.startsWith('/state/')) {\n const key = decodeURIComponent(url.pathname.slice(7));\n await this.broker.delete(key);\n\n res.writeHead(204);\n res.end();\n return;\n }\n\n // POST /state/clear\n if (req.method === 'POST' && url.pathname === '/state/clear') {\n const pattern = url.searchParams.get('pattern') || undefined;\n await this.broker.clear(pattern);\n\n res.writeHead(204);\n res.end();\n return;\n }\n\n // GET /jobs - List all registered jobs\n if (req.method === 'GET' && url.pathname === '/jobs') {\n if (!this.jobsManager) {\n res.writeHead(503, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Jobs manager not enabled' }));\n return;\n }\n\n const jobs = this.jobsManager.listJobs();\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ jobs }));\n return;\n }\n\n // POST /jobs/:id/trigger - Manually trigger a job\n if (req.method === 'POST' && url.pathname.startsWith('/jobs/') && url.pathname.endsWith('/trigger')) {\n if (!this.jobsManager) {\n res.writeHead(503, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Jobs manager not enabled' }));\n return;\n }\n\n const id = decodeURIComponent(url.pathname.slice(6, -8)); // Remove \"/jobs/\" and \"/trigger\"\n\n try {\n await this.jobsManager.triggerJob(id);\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ ok: true, message: `Job ${id} triggered successfully` }));\n } catch (error) {\n res.writeHead(404, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({\n error: error instanceof Error ? error.message : 'Job not found'\n }));\n }\n return;\n }\n\n // GET /jobs/stats - Get jobs statistics\n if (req.method === 'GET' && url.pathname === '/jobs/stats') {\n if (!this.jobsManager) {\n res.writeHead(503, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Jobs manager not enabled' }));\n return;\n }\n\n const stats = this.jobsManager.getStats();\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(stats));\n return;\n }\n\n // 404 Not Found\n res.writeHead(404, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Not Found' }));\n } catch (error) {\n console.error('Request error:', error);\n res.writeHead(500, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({\n error: error instanceof Error ? error.message : 'Internal Server Error',\n }));\n }\n }\n\n private async readBody(req: IncomingMessage): Promise<string> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on('data', (chunk) => chunks.push(chunk));\n req.on('end', () => resolve(Buffer.concat(chunks).toString()));\n req.on('error', reject);\n });\n }\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kb-labs/core-state-daemon",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "State daemon server for persistent cross-invocation state",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"kb-state-daemon": "./dist/bin.cjs"
|
|
10
|
+
},
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.js",
|
|
15
|
+
"require": "./dist/index.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist"
|
|
20
|
+
],
|
|
21
|
+
"sideEffects": false,
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@kb-labs/core-state-broker": "link:../core-state-broker",
|
|
24
|
+
"@kb-labs/core-runtime": "link:../core-runtime",
|
|
25
|
+
"@kb-labs/core-platform": "link:../core-platform",
|
|
26
|
+
"@kb-labs/plugin-runtime": "link:../../../kb-labs-plugin/packages/plugin-runtime",
|
|
27
|
+
"@kb-labs/shared-command-kit": "link:../../../kb-labs-shared/packages/shared-command-kit"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@kb-labs/devkit": "link:../../../kb-labs-devkit",
|
|
31
|
+
"@types/node": "^24.3.3",
|
|
32
|
+
"rimraf": "^6.0.1",
|
|
33
|
+
"tsup": "^8.5.0",
|
|
34
|
+
"typescript": "^5.6.3",
|
|
35
|
+
"vitest": "^3.2.4"
|
|
36
|
+
},
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=20.0.0",
|
|
39
|
+
"pnpm": ">=9.0.0"
|
|
40
|
+
},
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public"
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"clean": "rimraf dist",
|
|
46
|
+
"build": "pnpm clean && tsup --config tsup.bin.config.ts && tsup --config tsup.lib.config.ts",
|
|
47
|
+
"dev": "tsup --config tsup.config.ts --watch",
|
|
48
|
+
"type-check": "tsc --noEmit",
|
|
49
|
+
"test": "vitest run --passWithNoTests",
|
|
50
|
+
"test:watch": "vitest",
|
|
51
|
+
"lint": "eslint src --ext .ts",
|
|
52
|
+
"lint:fix": "eslint . --fix"
|
|
53
|
+
}
|
|
54
|
+
}
|