@owlmeans/redis-resource 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +46 -517
- package/package.json +8 -6
- package/tsconfig.json +5 -4
package/LICENSE
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c)
|
|
3
|
+
Copyright (c) 2026 OwlMeans Common — Fullstack typescript framework
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
package/README.md
CHANGED
|
@@ -1,560 +1,89 @@
|
|
|
1
1
|
# @owlmeans/redis-resource
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Redis-backed resource with pub/sub and streaming support for real-time features.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Overview
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
- `makeRedisResource(alias, dbAlias?)` — creates a Redis resource
|
|
8
|
+
- `RedisResource<T>` — extends `Resource<T>` with `subscribe`, `publish`, `stream`, and `consume`
|
|
9
|
+
- Used for authentication caches, pub/sub channels, and async event streams
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
- **Supports pub/sub messaging** for real-time data synchronization across services
|
|
11
|
-
- **Implements full CRUD operations** with Redis-optimized performance
|
|
12
|
-
- **Offers TTL support** for automatic expiration of records
|
|
13
|
-
- **Enables horizontal scaling** through Redis clustering
|
|
14
|
-
- **Integrates with context system** for dependency injection and service management
|
|
11
|
+
## Installation
|
|
15
12
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
### Redis Resource
|
|
19
|
-
A Redis resource is a storage implementation that uses Redis as the backend, providing fast key-value storage with advanced features like pub/sub, TTL, and clustering support.
|
|
20
|
-
|
|
21
|
-
### Key Management
|
|
22
|
-
Records are stored in Redis using a configurable prefix and key structure, allowing for namespace separation and efficient data organization.
|
|
23
|
-
|
|
24
|
-
### Pub/Sub Integration
|
|
25
|
-
The package provides built-in pub/sub functionality for real-time data synchronization, enabling reactive updates across distributed systems.
|
|
26
|
-
|
|
27
|
-
### Database Service Integration
|
|
28
|
-
Integrates with the OwlMeans database service pattern, allowing for connection pooling and database management.
|
|
29
|
-
|
|
30
|
-
## API Reference
|
|
31
|
-
|
|
32
|
-
### Factory Functions
|
|
33
|
-
|
|
34
|
-
#### `makeRedisResource<R, T>(alias, dbAlias?, serviceAlias?, makeCustomResource?): T`
|
|
35
|
-
|
|
36
|
-
Creates a new Redis resource instance.
|
|
37
|
-
|
|
38
|
-
```typescript
|
|
39
|
-
import { makeRedisResource } from '@owlmeans/redis-resource'
|
|
40
|
-
|
|
41
|
-
interface User {
|
|
42
|
-
id: string
|
|
43
|
-
name: string
|
|
44
|
-
email: string
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const userResource = makeRedisResource<User>('users', 'redis-main')
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
**Parameters:**
|
|
51
|
-
- `alias`: string - Resource alias for context registration
|
|
52
|
-
- `dbAlias`: string (optional) - Database alias, defaults to `DEFAULT_DB_ALIAS` ('redis')
|
|
53
|
-
- `serviceAlias`: string (optional) - Service alias, defaults to `DEFAULT_DB_ALIAS`
|
|
54
|
-
- `makeCustomResource`: ResourceMaker (optional) - Custom resource factory function
|
|
55
|
-
|
|
56
|
-
**Returns:** RedisResource instance with full CRUD and pub/sub capabilities
|
|
57
|
-
|
|
58
|
-
### Resource Methods
|
|
59
|
-
|
|
60
|
-
The Redis resource implements the standard `Resource<ResourceRecord>` interface with additional Redis-specific methods:
|
|
61
|
-
|
|
62
|
-
#### Core CRUD Operations
|
|
63
|
-
|
|
64
|
-
#### `get<T>(id, field?, opts?): Promise<T>`
|
|
65
|
-
|
|
66
|
-
Retrieves a record by ID or field value. Throws `UnknownRecordError` if not found.
|
|
67
|
-
|
|
68
|
-
```typescript
|
|
69
|
-
const user = await userResource.get<User>('user-123')
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
#### `load<T>(id, field?, opts?): Promise<T | null>`
|
|
73
|
-
|
|
74
|
-
Loads a record by ID or field value. Returns `null` if not found.
|
|
75
|
-
|
|
76
|
-
```typescript
|
|
77
|
-
const user = await userResource.load<User>('user-123')
|
|
78
|
-
if (user) {
|
|
79
|
-
console.log('User found:', user.name)
|
|
80
|
-
}
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
#### `create<T>(record, opts?): Promise<T>`
|
|
84
|
-
|
|
85
|
-
Creates a new record in Redis storage.
|
|
86
|
-
|
|
87
|
-
```typescript
|
|
88
|
-
const user = await userResource.create({
|
|
89
|
-
id: 'user-123',
|
|
90
|
-
name: 'John Doe',
|
|
91
|
-
email: 'john@example.com'
|
|
92
|
-
}, { ttl: 3600 }) // TTL in seconds
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
**Options:**
|
|
96
|
-
- `ttl`: number | Date - Time to live in seconds (number) or expiration date (Date)
|
|
97
|
-
|
|
98
|
-
#### `update<T>(record, opts?): Promise<T>`
|
|
99
|
-
|
|
100
|
-
Updates an existing record.
|
|
101
|
-
|
|
102
|
-
```typescript
|
|
103
|
-
const updatedUser = await userResource.update({
|
|
104
|
-
id: 'user-123',
|
|
105
|
-
name: 'John Smith',
|
|
106
|
-
email: 'john.smith@example.com'
|
|
107
|
-
}, { ttl: 7200 })
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
#### `save<T>(record, opts?): Promise<T>`
|
|
111
|
-
|
|
112
|
-
Creates or updates a record (upsert operation).
|
|
113
|
-
|
|
114
|
-
```typescript
|
|
115
|
-
const user = await userResource.save({
|
|
116
|
-
id: 'user-123',
|
|
117
|
-
name: 'John Doe',
|
|
118
|
-
email: 'john@example.com'
|
|
119
|
-
})
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
#### `delete(id, opts?): Promise<T | null>`
|
|
123
|
-
|
|
124
|
-
Deletes a record and returns it, or `null` if not found.
|
|
125
|
-
|
|
126
|
-
```typescript
|
|
127
|
-
const deletedUser = await userResource.delete('user-123')
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
#### `pick<T>(id, opts?): Promise<T>`
|
|
131
|
-
|
|
132
|
-
Deletes a record and returns it. Throws `UnknownRecordError` if not found.
|
|
133
|
-
|
|
134
|
-
```typescript
|
|
135
|
-
const pickedUser = await userResource.pick('user-123')
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
#### `list<T>(criteria?, opts?): Promise<ListResult<T>>`
|
|
139
|
-
|
|
140
|
-
Lists records with pagination support.
|
|
141
|
-
|
|
142
|
-
```typescript
|
|
143
|
-
const result = await userResource.list<User>({}, {
|
|
144
|
-
pager: { page: 0, size: 20 }
|
|
145
|
-
})
|
|
146
|
-
console.log('Users:', result.items)
|
|
147
|
-
console.log('Total:', result.pager?.total)
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
### Redis-Specific Methods
|
|
151
|
-
|
|
152
|
-
#### `key(key?): string`
|
|
153
|
-
|
|
154
|
-
Generates a Redis key with the configured prefix.
|
|
155
|
-
|
|
156
|
-
```typescript
|
|
157
|
-
const redisKey = userResource.key('user-123')
|
|
158
|
-
console.log(redisKey) // "prefix:user-123"
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
#### `subscribe<Type>(handler, key?): Promise<() => Promise<void>>`
|
|
162
|
-
|
|
163
|
-
Subscribes to Redis pub/sub messages for real-time updates.
|
|
164
|
-
|
|
165
|
-
```typescript
|
|
166
|
-
const unsubscribe = await userResource.subscribe<User>(
|
|
167
|
-
async (user) => {
|
|
168
|
-
console.log('User updated:', user)
|
|
169
|
-
},
|
|
170
|
-
'user-updates'
|
|
171
|
-
)
|
|
172
|
-
|
|
173
|
-
// Later, unsubscribe
|
|
174
|
-
await unsubscribe()
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
**Parameters:**
|
|
178
|
-
- `handler`: (value: Type) => Promise<void> - Message handler function
|
|
179
|
-
- `key`: SubOpts (optional) - Subscription options or key
|
|
180
|
-
|
|
181
|
-
#### `publish<Type>(value, key?): Promise<void>`
|
|
182
|
-
|
|
183
|
-
Publishes a message to Redis pub/sub.
|
|
184
|
-
|
|
185
|
-
```typescript
|
|
186
|
-
await userResource.publish<User>({
|
|
187
|
-
id: 'user-123',
|
|
188
|
-
name: 'John Doe',
|
|
189
|
-
email: 'john@example.com'
|
|
190
|
-
}, 'user-updates')
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
### Types
|
|
194
|
-
|
|
195
|
-
#### `RedisResource<T>`
|
|
196
|
-
|
|
197
|
-
Main interface for Redis resources extending the base Resource interface.
|
|
198
|
-
|
|
199
|
-
```typescript
|
|
200
|
-
interface RedisResource<T extends ResourceRecord> extends Resource<T> {
|
|
201
|
-
name?: string
|
|
202
|
-
db: RedisDb
|
|
203
|
-
key: (key?: string) => string
|
|
204
|
-
subscribe: <Type extends T>(handler: (value: Type) => Promise<void>, key?: SubOpts) => Promise<() => Promise<void>>
|
|
205
|
-
publish: <Type extends T>(value: Type, key?: string) => Promise<void>
|
|
206
|
-
}
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
#### `RedisDb`
|
|
210
|
-
|
|
211
|
-
Redis database connection interface.
|
|
212
|
-
|
|
213
|
-
```typescript
|
|
214
|
-
interface RedisDb {
|
|
215
|
-
client: RedisCommander
|
|
216
|
-
prefix: string
|
|
217
|
-
}
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
#### `RedisDbService`
|
|
221
|
-
|
|
222
|
-
Service interface for Redis database management.
|
|
223
|
-
|
|
224
|
-
```typescript
|
|
225
|
-
interface RedisDbService extends ResourceDbService<RedisDb, RedisClient> {}
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
#### `RedisClient`
|
|
229
|
-
|
|
230
|
-
Type alias for Redis client (supports both single Redis and Cluster).
|
|
231
|
-
|
|
232
|
-
```typescript
|
|
233
|
-
type RedisClient = Redis | Cluster
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
#### `SubscriptionOptions`
|
|
237
|
-
|
|
238
|
-
Options for Redis subscriptions.
|
|
239
|
-
|
|
240
|
-
```typescript
|
|
241
|
-
interface SubscriptionOptions extends LifecycleOptions {
|
|
242
|
-
key?: string
|
|
243
|
-
once?: boolean
|
|
244
|
-
}
|
|
245
|
-
```
|
|
246
|
-
|
|
247
|
-
### Constants
|
|
248
|
-
|
|
249
|
-
#### `DEFAULT_DB_ALIAS`
|
|
250
|
-
|
|
251
|
-
Default database alias for Redis connections.
|
|
252
|
-
|
|
253
|
-
```typescript
|
|
254
|
-
const DEFAULT_DB_ALIAS = 'redis'
|
|
255
|
-
```
|
|
256
|
-
|
|
257
|
-
#### `DEFAULT_PAGE_SIZE`
|
|
258
|
-
|
|
259
|
-
Default page size for list operations.
|
|
260
|
-
|
|
261
|
-
```typescript
|
|
262
|
-
const DEFAULT_PAGE_SIZE = 10
|
|
263
|
-
```
|
|
264
|
-
|
|
265
|
-
## Usage Examples
|
|
266
|
-
|
|
267
|
-
### Basic Usage
|
|
268
|
-
|
|
269
|
-
```typescript
|
|
270
|
-
import { makeRedisResource } from '@owlmeans/redis-resource'
|
|
271
|
-
|
|
272
|
-
interface Product {
|
|
273
|
-
id: string
|
|
274
|
-
name: string
|
|
275
|
-
price: number
|
|
276
|
-
category: string
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// Create Redis resource
|
|
280
|
-
const productResource = makeRedisResource<Product>('products')
|
|
281
|
-
|
|
282
|
-
// Create products
|
|
283
|
-
await productResource.create({
|
|
284
|
-
id: 'prod-1',
|
|
285
|
-
name: 'Laptop',
|
|
286
|
-
price: 999.99,
|
|
287
|
-
category: 'electronics'
|
|
288
|
-
})
|
|
289
|
-
|
|
290
|
-
await productResource.create({
|
|
291
|
-
id: 'prod-2',
|
|
292
|
-
name: 'Book',
|
|
293
|
-
price: 19.99,
|
|
294
|
-
category: 'books'
|
|
295
|
-
}, { ttl: 86400 }) // Expires in 24 hours
|
|
296
|
-
|
|
297
|
-
// Retrieve products
|
|
298
|
-
const laptop = await productResource.get('prod-1')
|
|
299
|
-
console.log('Product:', laptop.name, laptop.price)
|
|
300
|
-
|
|
301
|
-
// List all products
|
|
302
|
-
const { items, pager } = await productResource.list()
|
|
303
|
-
console.log('Total products:', pager?.total)
|
|
13
|
+
```bash
|
|
14
|
+
bun add @owlmeans/redis-resource
|
|
304
15
|
```
|
|
305
16
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
```typescript
|
|
309
|
-
import { makeRedisResource } from '@owlmeans/redis-resource'
|
|
310
|
-
import { makeServerContext, makeServerConfig } from '@owlmeans/server-context'
|
|
311
|
-
import { AppType } from '@owlmeans/context'
|
|
312
|
-
|
|
313
|
-
// Create server context
|
|
314
|
-
const config = makeServerConfig(AppType.Backend, 'product-service')
|
|
315
|
-
const context = makeServerContext(config)
|
|
316
|
-
|
|
317
|
-
// Create and register Redis resource
|
|
318
|
-
const productResource = makeRedisResource<Product>('products', 'redis-main')
|
|
319
|
-
context.registerResource(productResource)
|
|
17
|
+
## Usage
|
|
320
18
|
|
|
321
|
-
|
|
322
|
-
context.configure()
|
|
323
|
-
await context.init()
|
|
324
|
-
|
|
325
|
-
// Use resource through context
|
|
326
|
-
const resource = context.resource<typeof productResource>('products')
|
|
327
|
-
```
|
|
328
|
-
|
|
329
|
-
### Pub/Sub Usage
|
|
19
|
+
Register Redis resources in context setup:
|
|
330
20
|
|
|
331
21
|
```typescript
|
|
332
22
|
import { makeRedisResource } from '@owlmeans/redis-resource'
|
|
23
|
+
import { AUTH_CACHE } from '@owlmeans/server-auth'
|
|
333
24
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
const unsubscribe = await orderResource.subscribe<Order>(
|
|
338
|
-
async (order) => {
|
|
339
|
-
console.log('Order updated:', order.id, order.status)
|
|
340
|
-
|
|
341
|
-
// Process order update
|
|
342
|
-
if (order.status === 'completed') {
|
|
343
|
-
await processCompletedOrder(order)
|
|
344
|
-
}
|
|
345
|
-
},
|
|
346
|
-
{ key: 'order-updates', once: false }
|
|
347
|
-
)
|
|
348
|
-
|
|
349
|
-
// Publish order updates
|
|
350
|
-
await orderResource.publish({
|
|
351
|
-
id: 'order-123',
|
|
352
|
-
status: 'completed',
|
|
353
|
-
total: 299.99
|
|
354
|
-
}, 'order-updates')
|
|
355
|
-
|
|
356
|
-
// Clean up subscription
|
|
357
|
-
await unsubscribe()
|
|
25
|
+
context.registerResource(makeRedisResource(AUTH_CACHE))
|
|
26
|
+
context.registerResource(makeRedisResource(AGENT_THINKING_STREAM))
|
|
27
|
+
context.registerResource(makeRedisResource(AGENT_WATCHER_PUBSUB))
|
|
358
28
|
```
|
|
359
29
|
|
|
360
|
-
|
|
30
|
+
Define typed resource interfaces:
|
|
361
31
|
|
|
362
32
|
```typescript
|
|
363
|
-
|
|
33
|
+
import type { RedisResource } from '@owlmeans/redis-resource'
|
|
364
34
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
id: 'session-abc123',
|
|
368
|
-
userId: 'user-456',
|
|
369
|
-
createdAt: new Date()
|
|
370
|
-
}, { ttl: 3600 }) // Expires in 1 hour
|
|
371
|
-
|
|
372
|
-
// Update with new expiration
|
|
373
|
-
await sessionResource.update({
|
|
374
|
-
id: 'session-abc123',
|
|
375
|
-
lastActivity: new Date()
|
|
376
|
-
}, { ttl: new Date(Date.now() + 7200000) }) // Expires in 2 hours
|
|
35
|
+
interface AgentThinkingStream extends RedisResource<AgentThinkingEvent> {}
|
|
36
|
+
interface AgentWatcherPubsub extends RedisResource<AgentWatcherEvent> {}
|
|
377
37
|
```
|
|
378
38
|
|
|
379
|
-
|
|
39
|
+
Publish and subscribe:
|
|
380
40
|
|
|
381
41
|
```typescript
|
|
382
|
-
|
|
383
|
-
const userResource = makeRedisResource<User>('users')
|
|
384
|
-
|
|
385
|
-
await userResource.update({
|
|
386
|
-
id: 'user-123',
|
|
387
|
-
lastLogin: new Date(),
|
|
388
|
-
status: 'online'
|
|
389
|
-
})
|
|
390
|
-
|
|
391
|
-
// Notify other services
|
|
392
|
-
await userResource.publish({
|
|
393
|
-
id: 'user-123',
|
|
394
|
-
event: 'status-change',
|
|
395
|
-
status: 'online'
|
|
396
|
-
}, 'user-events')
|
|
397
|
-
|
|
398
|
-
// Service B - Listens for user events
|
|
399
|
-
const unsubscribe = await userResource.subscribe<UserEvent>(
|
|
400
|
-
async (event) => {
|
|
401
|
-
console.log('User event:', event.event, event.id)
|
|
402
|
-
await updateLocalCache(event)
|
|
403
|
-
},
|
|
404
|
-
'user-events'
|
|
405
|
-
)
|
|
406
|
-
```
|
|
42
|
+
const pubsub = context.resource<AgentWatcherPubsub>(AGENT_WATCHER_PUBSUB)
|
|
407
43
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
```typescript
|
|
411
|
-
import { UnknownRecordError, RecordExists, MisshapedRecord } from '@owlmeans/resource'
|
|
412
|
-
|
|
413
|
-
const userResource = makeRedisResource<User>('users')
|
|
414
|
-
|
|
415
|
-
try {
|
|
416
|
-
// This will throw UnknownRecordError
|
|
417
|
-
await userResource.get('non-existent-user')
|
|
418
|
-
} catch (error) {
|
|
419
|
-
if (error instanceof UnknownRecordError) {
|
|
420
|
-
console.log('User not found:', error.message)
|
|
421
|
-
}
|
|
422
|
-
}
|
|
44
|
+
// Publisher side
|
|
45
|
+
await pubsub.publish({ type: 'update', payload: data })
|
|
423
46
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
await userResource.create({ id: 'user-1', name: 'Bob', email: 'bob@example.com' })
|
|
428
|
-
} catch (error) {
|
|
429
|
-
if (error instanceof RecordExists) {
|
|
430
|
-
console.log('User already exists:', error.message)
|
|
431
|
-
// Use save() for upsert instead
|
|
432
|
-
await userResource.save({ id: 'user-1', name: 'Bob', email: 'bob@example.com' })
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
```
|
|
436
|
-
|
|
437
|
-
### Advanced Filtering and Pagination
|
|
438
|
-
|
|
439
|
-
```typescript
|
|
440
|
-
const productResource = makeRedisResource<Product>('products')
|
|
441
|
-
|
|
442
|
-
// List with pagination
|
|
443
|
-
const page1 = await productResource.list({}, {
|
|
444
|
-
pager: { page: 0, size: 10 }
|
|
47
|
+
// Subscriber side
|
|
48
|
+
const unsubscribe = await pubsub.subscribe(async (event) => {
|
|
49
|
+
handleEvent(event)
|
|
445
50
|
})
|
|
446
|
-
|
|
447
|
-
const page2 = await productResource.list({}, {
|
|
448
|
-
pager: { page: 1, size: 10 }
|
|
449
|
-
})
|
|
450
|
-
|
|
451
|
-
console.log('Page 1:', page1.items.length)
|
|
452
|
-
console.log('Page 2:', page2.items.length)
|
|
453
|
-
console.log('Total products:', page1.pager?.total)
|
|
454
51
|
```
|
|
455
52
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
### Microservices Architecture
|
|
53
|
+
Stream events (Redis Streams):
|
|
459
54
|
|
|
460
55
|
```typescript
|
|
461
|
-
|
|
462
|
-
const productResource = makeRedisResource<Product>('products', 'redis-products')
|
|
463
|
-
|
|
464
|
-
// Order Service
|
|
465
|
-
const orderResource = makeRedisResource<Order>('orders', 'redis-orders')
|
|
56
|
+
const stream = context.resource<AgentThinkingStream>(AGENT_THINKING_STREAM)
|
|
466
57
|
|
|
467
|
-
//
|
|
468
|
-
|
|
58
|
+
// Producer
|
|
59
|
+
await stream.stream('thinking-key', { step: 'reasoning', content: text })
|
|
469
60
|
|
|
470
|
-
//
|
|
471
|
-
await
|
|
472
|
-
|
|
473
|
-
if (event.type === 'price-updated') {
|
|
474
|
-
await orderResource.publish({
|
|
475
|
-
type: 'product-price-changed',
|
|
476
|
-
productId: event.productId,
|
|
477
|
-
newPrice: event.price
|
|
478
|
-
}, 'order-events')
|
|
479
|
-
}
|
|
480
|
-
},
|
|
481
|
-
'product-events'
|
|
482
|
-
)
|
|
483
|
-
```
|
|
484
|
-
|
|
485
|
-
### Caching Layer
|
|
486
|
-
|
|
487
|
-
```typescript
|
|
488
|
-
// Use Redis resource as a caching layer
|
|
489
|
-
const cacheResource = makeRedisResource<CacheEntry>('cache')
|
|
490
|
-
|
|
491
|
-
const getCachedData = async <T>(key: string, fetcher: () => Promise<T>): Promise<T> => {
|
|
492
|
-
try {
|
|
493
|
-
const cached = await cacheResource.load(key)
|
|
494
|
-
if (cached && cached.expiresAt > Date.now()) {
|
|
495
|
-
return cached.data as T
|
|
496
|
-
}
|
|
497
|
-
} catch (error) {
|
|
498
|
-
// Cache miss, continue to fetch
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
const data = await fetcher()
|
|
502
|
-
|
|
503
|
-
// Cache for 5 minutes
|
|
504
|
-
await cacheResource.save({
|
|
505
|
-
id: key,
|
|
506
|
-
data,
|
|
507
|
-
expiresAt: Date.now() + 300000
|
|
508
|
-
}, { ttl: 300 })
|
|
509
|
-
|
|
510
|
-
return data
|
|
61
|
+
// Consumer (async generator)
|
|
62
|
+
for await (const event of stream.consume('thinking-key', 'workers', 'worker-1')) {
|
|
63
|
+
process(event)
|
|
511
64
|
}
|
|
512
|
-
|
|
513
|
-
// Usage
|
|
514
|
-
const userData = await getCachedData(`user:${userId}`, () => fetchUserFromDB(userId))
|
|
515
65
|
```
|
|
516
66
|
|
|
517
|
-
##
|
|
67
|
+
## API
|
|
518
68
|
|
|
519
|
-
###
|
|
520
|
-
- **Connection Pooling** - Use Redis connection pools for high-traffic applications
|
|
521
|
-
- **Cluster Support** - Leverage Redis clustering for horizontal scaling
|
|
522
|
-
- **Pipeline Operations** - Batch multiple operations for better performance
|
|
69
|
+
### `makeRedisResource(alias, dbAlias?): RedisResource<T>`
|
|
523
70
|
|
|
524
|
-
|
|
525
|
-
- **Key Prefixes** - Use meaningful prefixes to organize data and avoid conflicts
|
|
526
|
-
- **TTL Management** - Set appropriate TTL values to manage memory usage
|
|
527
|
-
- **Pagination** - Use pagination for large datasets to avoid memory issues
|
|
71
|
+
Creates a Redis resource. `dbAlias` defaults to `DEFAULT_DB_ALIAS` (`'redis'`).
|
|
528
72
|
|
|
529
|
-
###
|
|
73
|
+
### `RedisResource<T>`
|
|
530
74
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
75
|
+
Extends `Resource<T>` with:
|
|
76
|
+
- `subscribe<T>(handler, key?): Promise<() => Promise<void>>` — subscribe to pub/sub channel
|
|
77
|
+
- `publish<T>(value, key?): Promise<void>` — publish to pub/sub channel
|
|
78
|
+
- `stream<T>(key, data): Promise<void>` — append to a Redis Stream
|
|
79
|
+
- `consume<T>(key, group?, consumer?): AsyncGenerator<T>` — consume from a Redis Stream
|
|
536
80
|
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
The package uses standard error types from `@owlmeans/resource`:
|
|
540
|
-
|
|
541
|
-
- **`UnknownRecordError`** - Thrown when trying to access a non-existent record
|
|
542
|
-
- **`RecordExists`** - Thrown when trying to create a record with an existing ID
|
|
543
|
-
- **`MisshapedRecord`** - Thrown when record structure is invalid
|
|
544
|
-
- **`UnsupportedArgumentError`** - Thrown when unsupported arguments are provided
|
|
545
|
-
|
|
546
|
-
Additional Redis-specific errors may be thrown by the underlying ioredis client.
|
|
547
|
-
|
|
548
|
-
## Dependencies
|
|
81
|
+
### Constants
|
|
549
82
|
|
|
550
|
-
|
|
551
|
-
- `@owlmeans/context` - For contextual resource management
|
|
552
|
-
- `@owlmeans/resource` - For resource interface and error types
|
|
553
|
-
- `ioredis` - Redis client library for Node.js
|
|
83
|
+
- `DEFAULT_DB_ALIAS` — `'redis'`
|
|
554
84
|
|
|
555
85
|
## Related Packages
|
|
556
86
|
|
|
557
|
-
- [`@owlmeans/
|
|
558
|
-
- [`@owlmeans/
|
|
559
|
-
- [`@owlmeans/
|
|
560
|
-
- [`@owlmeans/server-context`](../server-context) - Server-side context management
|
|
87
|
+
- [`@owlmeans/redis`](../redis) — Redis connection service
|
|
88
|
+
- [`@owlmeans/resource`](../resource) — `Resource<T>` base interface
|
|
89
|
+
- [`@owlmeans/server-socket`](../server-socket) — WebSocket handlers often use Redis resources for streaming
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@owlmeans/redis-resource",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"license": "MIT",
|
|
4
5
|
"type": "module",
|
|
5
6
|
"scripts": {
|
|
6
7
|
"build": "tsc -b",
|
|
@@ -24,16 +25,17 @@
|
|
|
24
25
|
"ioredis": "*"
|
|
25
26
|
},
|
|
26
27
|
"dependencies": {
|
|
27
|
-
"@owlmeans/basic-ids": "^0.1.
|
|
28
|
-
"@owlmeans/context": "^0.1.
|
|
29
|
-
"@owlmeans/resource": "^0.1.
|
|
30
|
-
"@owlmeans/server-context": "^0.1.
|
|
28
|
+
"@owlmeans/basic-ids": "^0.1.3",
|
|
29
|
+
"@owlmeans/context": "^0.1.3",
|
|
30
|
+
"@owlmeans/resource": "^0.1.3",
|
|
31
|
+
"@owlmeans/server-context": "^0.1.3"
|
|
31
32
|
},
|
|
32
33
|
"devDependencies": {
|
|
34
|
+
"@owlmeans/dep-config": "workspace:*",
|
|
33
35
|
"@types/node": "^24.10.1",
|
|
34
36
|
"nodemon": "^3.1.11",
|
|
35
37
|
"npm-check": "^6.0.1",
|
|
36
|
-
"typescript": "^
|
|
38
|
+
"typescript": "^6.0.2"
|
|
37
39
|
},
|
|
38
40
|
"publishConfig": {
|
|
39
41
|
"access": "public"
|
package/tsconfig.json
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"extends": [
|
|
3
|
-
"
|
|
3
|
+
"@owlmeans/dep-config/tsconfig.base.json",
|
|
4
|
+
"@owlmeans/dep-config/tsconfig.node.json"
|
|
4
5
|
],
|
|
5
6
|
"compilerOptions": {
|
|
6
|
-
"rootDir": "./src/",
|
|
7
|
-
"outDir": "./build/"
|
|
7
|
+
"rootDir": "./src/",
|
|
8
|
+
"outDir": "./build/"
|
|
8
9
|
},
|
|
9
10
|
"exclude": [
|
|
10
11
|
"./dist/**/*",
|
|
11
12
|
"./build/**/*",
|
|
12
13
|
"./*.ts"
|
|
13
14
|
]
|
|
14
|
-
}
|
|
15
|
+
}
|