@soederpop/luca 0.0.31 → 0.0.34
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 +241 -36
- package/bun.lock +24 -5
- package/commands/build-python-bridge.ts +43 -0
- package/docs/apis/clients/rest.md +7 -7
- package/docs/apis/clients/websocket.md +23 -10
- package/docs/apis/features/agi/assistant.md +155 -8
- package/docs/apis/features/agi/assistants-manager.md +90 -22
- package/docs/apis/features/agi/auto-assistant.md +377 -0
- package/docs/apis/features/agi/browser-use.md +802 -0
- package/docs/apis/features/agi/claude-code.md +6 -1
- package/docs/apis/features/agi/conversation-history.md +7 -6
- package/docs/apis/features/agi/conversation.md +111 -38
- package/docs/apis/features/agi/docs-reader.md +35 -57
- package/docs/apis/features/agi/file-tools.md +163 -0
- package/docs/apis/features/agi/openapi.md +2 -2
- package/docs/apis/features/agi/skills-library.md +227 -0
- package/docs/apis/features/node/content-db.md +125 -4
- package/docs/apis/features/node/disk-cache.md +11 -11
- package/docs/apis/features/node/downloader.md +1 -1
- package/docs/apis/features/node/file-manager.md +15 -15
- package/docs/apis/features/node/fs.md +78 -21
- package/docs/apis/features/node/git.md +50 -10
- package/docs/apis/features/node/google-calendar.md +3 -0
- package/docs/apis/features/node/google-docs.md +10 -1
- package/docs/apis/features/node/google-drive.md +3 -0
- package/docs/apis/features/node/google-mail.md +214 -0
- package/docs/apis/features/node/google-sheets.md +3 -0
- package/docs/apis/features/node/ink.md +10 -10
- package/docs/apis/features/node/ipc-socket.md +83 -93
- package/docs/apis/features/node/networking.md +5 -5
- package/docs/apis/features/node/os.md +7 -7
- package/docs/apis/features/node/package-finder.md +14 -14
- package/docs/apis/features/node/proc.md +2 -1
- package/docs/apis/features/node/process-manager.md +70 -3
- package/docs/apis/features/node/python.md +265 -9
- package/docs/apis/features/node/redis.md +380 -0
- package/docs/apis/features/node/ui.md +13 -13
- package/docs/apis/servers/express.md +35 -7
- package/docs/apis/servers/mcp.md +3 -3
- package/docs/apis/servers/websocket.md +51 -8
- package/docs/bootstrap/CLAUDE.md +1 -1
- package/docs/bootstrap/SKILL.md +93 -7
- package/docs/examples/feature-as-tool-provider.md +143 -0
- package/docs/examples/python.md +42 -1
- package/docs/introspection.md +15 -5
- package/docs/tutorials/00-bootstrap.md +3 -3
- package/docs/tutorials/02-container.md +2 -2
- package/docs/tutorials/10-creating-features.md +5 -0
- package/docs/tutorials/13-introspection.md +12 -2
- package/docs/tutorials/19-python-sessions.md +401 -0
- package/package.json +8 -4
- package/src/agi/container.server.ts +8 -0
- package/src/agi/features/assistant.ts +19 -0
- package/src/agi/features/autonomous-assistant.ts +435 -0
- package/src/agi/features/conversation.ts +58 -6
- package/src/agi/features/file-tools.ts +286 -0
- package/src/agi/features/luca-coder.ts +643 -0
- package/src/bootstrap/generated.ts +705 -17
- package/src/cli/build-info.ts +2 -2
- package/src/cli/cli.ts +22 -13
- package/src/commands/bootstrap.ts +49 -6
- package/src/commands/code.ts +369 -0
- package/src/commands/describe.ts +7 -2
- package/src/commands/index.ts +1 -0
- package/src/commands/sandbox-mcp.ts +7 -7
- package/src/commands/save-api-docs.ts +1 -1
- package/src/container-describer.ts +4 -4
- package/src/container.ts +10 -19
- package/src/helper.ts +24 -33
- package/src/introspection/generated.agi.ts +2499 -63
- package/src/introspection/generated.node.ts +1625 -688
- package/src/introspection/generated.web.ts +15 -57
- package/src/node/container.ts +5 -0
- package/src/node/features/figlet-fonts.ts +597 -0
- package/src/node/features/fs.ts +3 -9
- package/src/node/features/helpers.ts +20 -0
- package/src/node/features/python.ts +429 -16
- package/src/node/features/redis.ts +446 -0
- package/src/node/features/ui.ts +4 -11
- package/src/python/bridge.py +220 -0
- package/src/python/generated.ts +227 -0
- package/src/scaffolds/generated.ts +1 -1
- package/test/python-session.test.ts +105 -0
- package/assistants/lucaExpert/CORE.md +0 -37
- package/assistants/lucaExpert/hooks.ts +0 -9
- package/assistants/lucaExpert/tools.ts +0 -177
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Python (features.python)
|
|
2
2
|
|
|
3
|
-
The Python VM feature provides Python virtual machine capabilities for executing Python code. This feature automatically detects Python environments (uv, conda, venv, system) and provides methods to install dependencies and execute Python scripts. It can manage project-specific Python environments and maintain context between executions.
|
|
3
|
+
The Python VM feature provides Python virtual machine capabilities for executing Python code. This feature automatically detects Python environments (uv, conda, venv, system) and provides methods to install dependencies and execute Python scripts. It can manage project-specific Python environments and maintain context between executions. Supports two modes: - **Stateless** (default): `execute()` and `executeFile()` spawn a fresh process per call - **Persistent session**: `startSession()` spawns a long-lived bridge process that maintains state across `run()` calls, enabling real codebase interaction with imports and session variables
|
|
4
4
|
|
|
5
5
|
## Usage
|
|
6
6
|
|
|
@@ -136,6 +136,154 @@ Gets information about the current Python environment.
|
|
|
136
136
|
|
|
137
137
|
|
|
138
138
|
|
|
139
|
+
### startSession
|
|
140
|
+
|
|
141
|
+
Starts a persistent Python session by spawning the bridge process. The bridge sets up sys.path for the project directory, then enters a JSON-line REPL loop. State (variables, imports) persists across run() calls until stopSession() or resetSession() is called.
|
|
142
|
+
|
|
143
|
+
**Returns:** `Promise<void>`
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
const python = container.feature('python', { dir: '/path/to/project' })
|
|
147
|
+
await python.enable()
|
|
148
|
+
await python.startSession()
|
|
149
|
+
await python.run('x = 42')
|
|
150
|
+
const result = await python.run('print(x)')
|
|
151
|
+
console.log(result.stdout) // '42\n'
|
|
152
|
+
await python.stopSession()
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
### stopSession
|
|
158
|
+
|
|
159
|
+
Stops the persistent Python session and cleans up the bridge process.
|
|
160
|
+
|
|
161
|
+
**Returns:** `Promise<void>`
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
await python.stopSession()
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
### run
|
|
170
|
+
|
|
171
|
+
Executes Python code in the persistent session. Variables and imports survive across calls. This is the session equivalent of execute().
|
|
172
|
+
|
|
173
|
+
**Parameters:**
|
|
174
|
+
|
|
175
|
+
| Name | Type | Required | Description |
|
|
176
|
+
|------|------|----------|-------------|
|
|
177
|
+
| `code` | `string` | ✓ | Python code to execute |
|
|
178
|
+
| `variables` | `Record<string, any>` | | Variables to inject into the namespace before execution |
|
|
179
|
+
|
|
180
|
+
**Returns:** `Promise<RunResult>`
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
await python.startSession()
|
|
184
|
+
|
|
185
|
+
// State persists across calls
|
|
186
|
+
await python.run('x = 42')
|
|
187
|
+
const result = await python.run('print(x * 2)')
|
|
188
|
+
console.log(result.stdout) // '84\n'
|
|
189
|
+
|
|
190
|
+
// Inject variables from JS
|
|
191
|
+
const result2 = await python.run('print(f"Hello {name}!")', { name: 'World' })
|
|
192
|
+
console.log(result2.stdout) // 'Hello World!\n'
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
### eval
|
|
198
|
+
|
|
199
|
+
Evaluates a Python expression in the persistent session and returns its value.
|
|
200
|
+
|
|
201
|
+
**Parameters:**
|
|
202
|
+
|
|
203
|
+
| Name | Type | Required | Description |
|
|
204
|
+
|------|------|----------|-------------|
|
|
205
|
+
| `expression` | `string` | ✓ | Python expression to evaluate |
|
|
206
|
+
|
|
207
|
+
**Returns:** `Promise<any>`
|
|
208
|
+
|
|
209
|
+
```ts
|
|
210
|
+
await python.run('x = 42')
|
|
211
|
+
const result = await python.eval('x * 2')
|
|
212
|
+
console.log(result) // 84
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
### importModule
|
|
218
|
+
|
|
219
|
+
Imports a Python module into the persistent session namespace.
|
|
220
|
+
|
|
221
|
+
**Parameters:**
|
|
222
|
+
|
|
223
|
+
| Name | Type | Required | Description |
|
|
224
|
+
|------|------|----------|-------------|
|
|
225
|
+
| `moduleName` | `string` | ✓ | Dotted module path (e.g. 'myapp.models') |
|
|
226
|
+
| `alias` | `string` | | Optional alias for the import (defaults to the last segment) |
|
|
227
|
+
|
|
228
|
+
**Returns:** `Promise<void>`
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
await python.importModule('json')
|
|
232
|
+
await python.importModule('myapp.models', 'models')
|
|
233
|
+
const result = await python.eval('models.User')
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
### call
|
|
239
|
+
|
|
240
|
+
Calls a function by dotted path in the persistent session namespace.
|
|
241
|
+
|
|
242
|
+
**Parameters:**
|
|
243
|
+
|
|
244
|
+
| Name | Type | Required | Description |
|
|
245
|
+
|------|------|----------|-------------|
|
|
246
|
+
| `funcPath` | `string` | ✓ | Dotted path to the function (e.g. 'json.dumps' or 'my_func') |
|
|
247
|
+
| `args` | `any[]` | | Positional arguments |
|
|
248
|
+
| `kwargs` | `Record<string, any>` | | Keyword arguments |
|
|
249
|
+
|
|
250
|
+
**Returns:** `Promise<any>`
|
|
251
|
+
|
|
252
|
+
```ts
|
|
253
|
+
await python.importModule('json')
|
|
254
|
+
const result = await python.call('json.dumps', [{ a: 1 }], { indent: 2 })
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
### getLocals
|
|
260
|
+
|
|
261
|
+
Returns all non-dunder variables from the persistent session namespace.
|
|
262
|
+
|
|
263
|
+
**Returns:** `Promise<Record<string, any>>`
|
|
264
|
+
|
|
265
|
+
```ts
|
|
266
|
+
await python.run('x = 42\ny = "hello"')
|
|
267
|
+
const locals = await python.getLocals()
|
|
268
|
+
console.log(locals) // { x: 42, y: 'hello' }
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
### resetSession
|
|
274
|
+
|
|
275
|
+
Clears all variables and imports from the persistent session namespace. The session remains active — you can continue calling run() after reset.
|
|
276
|
+
|
|
277
|
+
**Returns:** `Promise<void>`
|
|
278
|
+
|
|
279
|
+
```ts
|
|
280
|
+
await python.run('x = 42')
|
|
281
|
+
await python.resetSession()
|
|
282
|
+
// x is now undefined
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
|
|
139
287
|
## Getters
|
|
140
288
|
|
|
141
289
|
| Property | Type | Description |
|
|
@@ -194,6 +342,24 @@ Event emitted by Python
|
|
|
194
342
|
|
|
195
343
|
|
|
196
344
|
|
|
345
|
+
### sessionError
|
|
346
|
+
|
|
347
|
+
Event emitted by Python
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
### sessionStarted
|
|
352
|
+
|
|
353
|
+
Event emitted by Python
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
### sessionStopped
|
|
358
|
+
|
|
359
|
+
Event emitted by Python
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
|
|
197
363
|
## State (Zod v4 schema)
|
|
198
364
|
|
|
199
365
|
| Property | Type | Description |
|
|
@@ -204,25 +370,27 @@ Event emitted by Python
|
|
|
204
370
|
| `environmentType` | `any` | Detected Python environment type (uv, conda, venv, or system) |
|
|
205
371
|
| `isReady` | `boolean` | Whether the Python environment is ready for execution |
|
|
206
372
|
| `lastExecutedScript` | `any` | Path to the last executed Python script |
|
|
373
|
+
| `sessionActive` | `boolean` | Whether a persistent Python session is currently active |
|
|
374
|
+
| `sessionId` | `any` | Unique ID of the current persistent session |
|
|
207
375
|
|
|
208
376
|
## Examples
|
|
209
377
|
|
|
210
378
|
**features.python**
|
|
211
379
|
|
|
212
380
|
```ts
|
|
213
|
-
const python = container.feature('python', {
|
|
381
|
+
const python = container.feature('python', {
|
|
214
382
|
dir: "/path/to/python/project",
|
|
215
|
-
contextScript: "/path/to/setup-context.py"
|
|
216
383
|
})
|
|
217
384
|
|
|
218
|
-
//
|
|
219
|
-
await python.installDependencies()
|
|
220
|
-
|
|
221
|
-
// Execute Python code
|
|
385
|
+
// Stateless execution
|
|
222
386
|
const result = await python.execute('print("Hello from Python!")')
|
|
223
387
|
|
|
224
|
-
//
|
|
225
|
-
|
|
388
|
+
// Persistent session
|
|
389
|
+
await python.startSession()
|
|
390
|
+
await python.run('import myapp.models')
|
|
391
|
+
await python.run('users = myapp.models.User.objects.all()')
|
|
392
|
+
const result = await python.run('print(len(users))')
|
|
393
|
+
await python.stopSession()
|
|
226
394
|
```
|
|
227
395
|
|
|
228
396
|
|
|
@@ -276,3 +444,91 @@ const result = await python.executeFile('/path/to/script.py')
|
|
|
276
444
|
console.log(result.stdout)
|
|
277
445
|
```
|
|
278
446
|
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
**startSession**
|
|
450
|
+
|
|
451
|
+
```ts
|
|
452
|
+
const python = container.feature('python', { dir: '/path/to/project' })
|
|
453
|
+
await python.enable()
|
|
454
|
+
await python.startSession()
|
|
455
|
+
await python.run('x = 42')
|
|
456
|
+
const result = await python.run('print(x)')
|
|
457
|
+
console.log(result.stdout) // '42\n'
|
|
458
|
+
await python.stopSession()
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
**stopSession**
|
|
464
|
+
|
|
465
|
+
```ts
|
|
466
|
+
await python.stopSession()
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
**run**
|
|
472
|
+
|
|
473
|
+
```ts
|
|
474
|
+
await python.startSession()
|
|
475
|
+
|
|
476
|
+
// State persists across calls
|
|
477
|
+
await python.run('x = 42')
|
|
478
|
+
const result = await python.run('print(x * 2)')
|
|
479
|
+
console.log(result.stdout) // '84\n'
|
|
480
|
+
|
|
481
|
+
// Inject variables from JS
|
|
482
|
+
const result2 = await python.run('print(f"Hello {name}!")', { name: 'World' })
|
|
483
|
+
console.log(result2.stdout) // 'Hello World!\n'
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
**eval**
|
|
489
|
+
|
|
490
|
+
```ts
|
|
491
|
+
await python.run('x = 42')
|
|
492
|
+
const result = await python.eval('x * 2')
|
|
493
|
+
console.log(result) // 84
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
**importModule**
|
|
499
|
+
|
|
500
|
+
```ts
|
|
501
|
+
await python.importModule('json')
|
|
502
|
+
await python.importModule('myapp.models', 'models')
|
|
503
|
+
const result = await python.eval('models.User')
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
**call**
|
|
509
|
+
|
|
510
|
+
```ts
|
|
511
|
+
await python.importModule('json')
|
|
512
|
+
const result = await python.call('json.dumps', [{ a: 1 }], { indent: 2 })
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
**getLocals**
|
|
518
|
+
|
|
519
|
+
```ts
|
|
520
|
+
await python.run('x = 42\ny = "hello"')
|
|
521
|
+
const locals = await python.getLocals()
|
|
522
|
+
console.log(locals) // { x: 42, y: 'hello' }
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
**resetSession**
|
|
528
|
+
|
|
529
|
+
```ts
|
|
530
|
+
await python.run('x = 42')
|
|
531
|
+
await python.resetSession()
|
|
532
|
+
// x is now undefined
|
|
533
|
+
```
|
|
534
|
+
|
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
# RedisFeature (features.redis)
|
|
2
|
+
|
|
3
|
+
Redis feature for shared state and pub/sub communication between container instances. Wraps ioredis with a focused API for the primitives that matter most: key/value state, pub/sub messaging, and cross-instance coordination. Uses a dedicated subscriber connection for pub/sub (ioredis requirement), created lazily on first subscribe call.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
container.feature('redis', {
|
|
9
|
+
// Redis connection URL, e.g. redis://localhost:6379. Defaults to redis://localhost:6379
|
|
10
|
+
url,
|
|
11
|
+
// Key prefix applied to all get/set/del operations for namespace isolation
|
|
12
|
+
prefix,
|
|
13
|
+
// If true, connection is deferred until first command
|
|
14
|
+
lazyConnect,
|
|
15
|
+
})
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Options (Zod v4 schema)
|
|
19
|
+
|
|
20
|
+
| Property | Type | Description |
|
|
21
|
+
|----------|------|-------------|
|
|
22
|
+
| `url` | `string` | Redis connection URL, e.g. redis://localhost:6379. Defaults to redis://localhost:6379 |
|
|
23
|
+
| `prefix` | `string` | Key prefix applied to all get/set/del operations for namespace isolation |
|
|
24
|
+
| `lazyConnect` | `boolean` | If true, connection is deferred until first command |
|
|
25
|
+
|
|
26
|
+
## Methods
|
|
27
|
+
|
|
28
|
+
### set
|
|
29
|
+
|
|
30
|
+
Set a key to a string value with optional TTL.
|
|
31
|
+
|
|
32
|
+
**Parameters:**
|
|
33
|
+
|
|
34
|
+
| Name | Type | Required | Description |
|
|
35
|
+
|------|------|----------|-------------|
|
|
36
|
+
| `key` | `string` | ✓ | The key name |
|
|
37
|
+
| `value` | `string` | ✓ | The string value to store |
|
|
38
|
+
| `ttl` | `number` | | Optional time-to-live in seconds |
|
|
39
|
+
|
|
40
|
+
**Returns:** `Promise<void>`
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
### get
|
|
45
|
+
|
|
46
|
+
Get a key's value. Returns null if the key doesn't exist.
|
|
47
|
+
|
|
48
|
+
**Parameters:**
|
|
49
|
+
|
|
50
|
+
| Name | Type | Required | Description |
|
|
51
|
+
|------|------|----------|-------------|
|
|
52
|
+
| `key` | `string` | ✓ | The key name |
|
|
53
|
+
|
|
54
|
+
**Returns:** `Promise<string | null>`
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
### del
|
|
59
|
+
|
|
60
|
+
Delete one or more keys.
|
|
61
|
+
|
|
62
|
+
**Parameters:**
|
|
63
|
+
|
|
64
|
+
| Name | Type | Required | Description |
|
|
65
|
+
|------|------|----------|-------------|
|
|
66
|
+
| `keys` | `string[]` | ✓ | One or more key names to delete |
|
|
67
|
+
|
|
68
|
+
**Returns:** `Promise<number>`
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
### exists
|
|
73
|
+
|
|
74
|
+
Check if a key exists.
|
|
75
|
+
|
|
76
|
+
**Parameters:**
|
|
77
|
+
|
|
78
|
+
| Name | Type | Required | Description |
|
|
79
|
+
|------|------|----------|-------------|
|
|
80
|
+
| `key` | `string` | ✓ | The key name |
|
|
81
|
+
|
|
82
|
+
**Returns:** `Promise<boolean>`
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
### expire
|
|
87
|
+
|
|
88
|
+
Set a key's TTL in seconds.
|
|
89
|
+
|
|
90
|
+
**Parameters:**
|
|
91
|
+
|
|
92
|
+
| Name | Type | Required | Description |
|
|
93
|
+
|------|------|----------|-------------|
|
|
94
|
+
| `key` | `string` | ✓ | The key name |
|
|
95
|
+
| `seconds` | `number` | ✓ | TTL in seconds |
|
|
96
|
+
|
|
97
|
+
**Returns:** `Promise<boolean>`
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
### keys
|
|
102
|
+
|
|
103
|
+
Find keys matching a glob pattern (respects prefix).
|
|
104
|
+
|
|
105
|
+
**Parameters:**
|
|
106
|
+
|
|
107
|
+
| Name | Type | Required | Description |
|
|
108
|
+
|------|------|----------|-------------|
|
|
109
|
+
| `pattern` | `string` | | Glob pattern, e.g. "worker:*" |
|
|
110
|
+
|
|
111
|
+
**Returns:** `Promise<string[]>`
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
### setJSON
|
|
116
|
+
|
|
117
|
+
Store a value as JSON.
|
|
118
|
+
|
|
119
|
+
**Parameters:**
|
|
120
|
+
|
|
121
|
+
| Name | Type | Required | Description |
|
|
122
|
+
|------|------|----------|-------------|
|
|
123
|
+
| `key` | `string` | ✓ | The key name |
|
|
124
|
+
| `value` | `unknown` | ✓ | Any JSON-serializable value |
|
|
125
|
+
| `ttl` | `number` | | Optional TTL in seconds |
|
|
126
|
+
|
|
127
|
+
**Returns:** `Promise<void>`
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
### getJSON
|
|
132
|
+
|
|
133
|
+
Retrieve and parse a JSON value.
|
|
134
|
+
|
|
135
|
+
**Parameters:**
|
|
136
|
+
|
|
137
|
+
| Name | Type | Required | Description |
|
|
138
|
+
|------|------|----------|-------------|
|
|
139
|
+
| `key` | `string` | ✓ | The key name |
|
|
140
|
+
|
|
141
|
+
**Returns:** `Promise<T | null>`
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
### hset
|
|
146
|
+
|
|
147
|
+
Set fields on a hash.
|
|
148
|
+
|
|
149
|
+
**Parameters:**
|
|
150
|
+
|
|
151
|
+
| Name | Type | Required | Description |
|
|
152
|
+
|------|------|----------|-------------|
|
|
153
|
+
| `key` | `string` | ✓ | The hash key |
|
|
154
|
+
| `fields` | `Record<string, string>` | ✓ | Object of field/value pairs |
|
|
155
|
+
|
|
156
|
+
**Returns:** `Promise<void>`
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
### hgetall
|
|
161
|
+
|
|
162
|
+
Get all fields from a hash.
|
|
163
|
+
|
|
164
|
+
**Parameters:**
|
|
165
|
+
|
|
166
|
+
| Name | Type | Required | Description |
|
|
167
|
+
|------|------|----------|-------------|
|
|
168
|
+
| `key` | `string` | ✓ | The hash key |
|
|
169
|
+
|
|
170
|
+
**Returns:** `Promise<Record<string, string>>`
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
### hget
|
|
175
|
+
|
|
176
|
+
Get a single field from a hash.
|
|
177
|
+
|
|
178
|
+
**Parameters:**
|
|
179
|
+
|
|
180
|
+
| Name | Type | Required | Description |
|
|
181
|
+
|------|------|----------|-------------|
|
|
182
|
+
| `key` | `string` | ✓ | The hash key |
|
|
183
|
+
| `field` | `string` | ✓ | The field name |
|
|
184
|
+
|
|
185
|
+
**Returns:** `Promise<string | null>`
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
### subscribe
|
|
190
|
+
|
|
191
|
+
Subscribe to one or more channels. Optionally pass a handler that fires only for these channels. The feature also emits a `message` event for all messages.
|
|
192
|
+
|
|
193
|
+
**Parameters:**
|
|
194
|
+
|
|
195
|
+
| Name | Type | Required | Description |
|
|
196
|
+
|------|------|----------|-------------|
|
|
197
|
+
| `channels` | `string | string[]` | ✓ | Channel name(s) to subscribe to |
|
|
198
|
+
| `handler` | `MessageHandler` | | Optional per-channel message handler |
|
|
199
|
+
|
|
200
|
+
**Returns:** `Promise<void>`
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
await redis.subscribe('tasks', (channel, msg) => {
|
|
204
|
+
console.log(`Got ${msg} on ${channel}`)
|
|
205
|
+
})
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
### unsubscribe
|
|
211
|
+
|
|
212
|
+
Unsubscribe from one or more channels.
|
|
213
|
+
|
|
214
|
+
**Parameters:**
|
|
215
|
+
|
|
216
|
+
| Name | Type | Required | Description |
|
|
217
|
+
|------|------|----------|-------------|
|
|
218
|
+
| `channels` | `string[]` | ✓ | Channel name(s) to unsubscribe from |
|
|
219
|
+
|
|
220
|
+
**Returns:** `Promise<void>`
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
### publish
|
|
225
|
+
|
|
226
|
+
Publish a message to a channel.
|
|
227
|
+
|
|
228
|
+
**Parameters:**
|
|
229
|
+
|
|
230
|
+
| Name | Type | Required | Description |
|
|
231
|
+
|------|------|----------|-------------|
|
|
232
|
+
| `channel` | `string` | ✓ | The channel to publish to |
|
|
233
|
+
| `message` | `string` | ✓ | The message string (use JSON.stringify for objects) |
|
|
234
|
+
|
|
235
|
+
**Returns:** `Promise<number>`
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
### ensureLocalDocker
|
|
240
|
+
|
|
241
|
+
Spin up a local Redis instance via Docker. Checks if a container with the given name already exists and starts it if stopped, or creates a new one from redis:alpine. Requires the docker feature to be available on the container.
|
|
242
|
+
|
|
243
|
+
**Parameters:**
|
|
244
|
+
|
|
245
|
+
| Name | Type | Required | Description |
|
|
246
|
+
|------|------|----------|-------------|
|
|
247
|
+
| `options` | `{ name?: string; port?: number; image?: string }` | | Container name and host port |
|
|
248
|
+
|
|
249
|
+
**Returns:** `Promise<string>`
|
|
250
|
+
|
|
251
|
+
```ts
|
|
252
|
+
const redis = container.feature('redis', { url: 'redis://localhost:6379', lazyConnect: true })
|
|
253
|
+
await redis.ensureLocalDocker()
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
### close
|
|
259
|
+
|
|
260
|
+
Close all redis connections (main client + subscriber).
|
|
261
|
+
|
|
262
|
+
**Returns:** `Promise<this>`
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
## Getters
|
|
267
|
+
|
|
268
|
+
| Property | Type | Description |
|
|
269
|
+
|----------|------|-------------|
|
|
270
|
+
| `client` | `Redis` | The underlying ioredis client for advanced operations. |
|
|
271
|
+
| `subscriber` | `Redis | null` | The dedicated subscriber connection, if pub/sub is active. |
|
|
272
|
+
|
|
273
|
+
## Events (Zod v4 schema)
|
|
274
|
+
|
|
275
|
+
### message
|
|
276
|
+
|
|
277
|
+
When a message is received on a subscribed channel
|
|
278
|
+
|
|
279
|
+
**Event Arguments:**
|
|
280
|
+
|
|
281
|
+
| Name | Type | Description |
|
|
282
|
+
|------|------|-------------|
|
|
283
|
+
| `arg0` | `string` | The channel name |
|
|
284
|
+
| `arg1` | `string` | The message payload |
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
### error
|
|
289
|
+
|
|
290
|
+
When a redis operation fails
|
|
291
|
+
|
|
292
|
+
**Event Arguments:**
|
|
293
|
+
|
|
294
|
+
| Name | Type | Description |
|
|
295
|
+
|------|------|-------------|
|
|
296
|
+
| `arg0` | `string` | The error message |
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
### subscribed
|
|
301
|
+
|
|
302
|
+
When successfully subscribed to a channel
|
|
303
|
+
|
|
304
|
+
**Event Arguments:**
|
|
305
|
+
|
|
306
|
+
| Name | Type | Description |
|
|
307
|
+
|------|------|-------------|
|
|
308
|
+
| `arg0` | `string` | The channel name |
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
### unsubscribed
|
|
313
|
+
|
|
314
|
+
When unsubscribed from a channel
|
|
315
|
+
|
|
316
|
+
**Event Arguments:**
|
|
317
|
+
|
|
318
|
+
| Name | Type | Description |
|
|
319
|
+
|------|------|-------------|
|
|
320
|
+
| `arg0` | `string` | The channel name |
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
### closed
|
|
325
|
+
|
|
326
|
+
When the redis connection is closed
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
## State (Zod v4 schema)
|
|
331
|
+
|
|
332
|
+
| Property | Type | Description |
|
|
333
|
+
|----------|------|-------------|
|
|
334
|
+
| `enabled` | `boolean` | Whether this feature is currently enabled |
|
|
335
|
+
| `connected` | `boolean` | Whether the redis connection is currently open |
|
|
336
|
+
| `url` | `string` | Connection URL used for this redis feature instance |
|
|
337
|
+
| `subscriberConnected` | `boolean` | Whether the dedicated subscriber connection is open |
|
|
338
|
+
| `subscribedChannels` | `array` | List of channels currently subscribed to |
|
|
339
|
+
| `lastError` | `string` | Most recent redis error message, if any |
|
|
340
|
+
|
|
341
|
+
## Examples
|
|
342
|
+
|
|
343
|
+
**features.redis**
|
|
344
|
+
|
|
345
|
+
```ts
|
|
346
|
+
const redis = container.feature('redis', { url: 'redis://localhost:6379' })
|
|
347
|
+
|
|
348
|
+
// Shared state
|
|
349
|
+
await redis.set('worker:status', 'active')
|
|
350
|
+
const status = await redis.get('worker:status')
|
|
351
|
+
|
|
352
|
+
// Pub/sub between instances
|
|
353
|
+
redis.on('message', (channel, msg) => console.log(`${channel}: ${msg}`))
|
|
354
|
+
await redis.subscribe('tasks')
|
|
355
|
+
await redis.publish('tasks', JSON.stringify({ type: 'ping' }))
|
|
356
|
+
|
|
357
|
+
// JSON helpers
|
|
358
|
+
await redis.setJSON('config', { workers: 4, debug: true })
|
|
359
|
+
const config = await redis.getJSON<{ workers: number }>('config')
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
**subscribe**
|
|
365
|
+
|
|
366
|
+
```ts
|
|
367
|
+
await redis.subscribe('tasks', (channel, msg) => {
|
|
368
|
+
console.log(`Got ${msg} on ${channel}`)
|
|
369
|
+
})
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
**ensureLocalDocker**
|
|
375
|
+
|
|
376
|
+
```ts
|
|
377
|
+
const redis = container.feature('redis', { url: 'redis://localhost:6379', lazyConnect: true })
|
|
378
|
+
await redis.ensureLocalDocker()
|
|
379
|
+
```
|
|
380
|
+
|