@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2024 OwlMeans Common — Fullstack typescript framework
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
- The **@owlmeans/redis-resource** package provides Redis-based resource storage implementation for OwlMeans Common Libraries, designed for fullstack microservices and microclients development with focus on security and proper authentication and authorization.
3
+ Redis-backed resource with pub/sub and streaming support for real-time features.
4
4
 
5
- ## Purpose
5
+ ## Overview
6
6
 
7
- This package serves as a Redis integration layer for the OwlMeans resource system that:
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
- - **Provides Redis-backed storage** for resources with high performance and scalability
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
- ## Core Concepts
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
- ### Context Integration
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
- // Configure and initialize context
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
- const orderResource = makeRedisResource<Order>('orders')
335
-
336
- // Subscribe to order updates
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
- ### TTL and Expiration
30
+ Define typed resource interfaces:
361
31
 
362
32
  ```typescript
363
- const sessionResource = makeRedisResource<Session>('sessions')
33
+ import type { RedisResource } from '@owlmeans/redis-resource'
364
34
 
365
- // Create session with TTL
366
- await sessionResource.create({
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
- ### Real-time Synchronization
39
+ Publish and subscribe:
380
40
 
381
41
  ```typescript
382
- // Service A - Updates user data
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
- ### Error Handling
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
- try {
425
- await userResource.create({ id: 'user-1', name: 'Alice', email: 'alice@example.com' })
426
- // This will throw RecordExists
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
- ## Integration Patterns
457
-
458
- ### Microservices Architecture
53
+ Stream events (Redis Streams):
459
54
 
460
55
  ```typescript
461
- // Product Service
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
- // Shared event bus
468
- const eventResource = makeRedisResource<Event>('events', 'redis-events')
58
+ // Producer
59
+ await stream.stream('thinking-key', { step: 'reasoning', content: text })
469
60
 
470
- // Cross-service communication
471
- await productResource.subscribe<ProductEvent>(
472
- async (event) => {
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
- ## Performance Considerations
67
+ ## API
518
68
 
519
- ### Connection Management
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
- ### Data Organization
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
- ### Best Practices
73
+ ### `RedisResource<T>`
530
74
 
531
- 1. **Use appropriate TTL** - Set TTL for temporary data to prevent memory bloat
532
- 2. **Monitor Redis memory** - Keep track of Redis memory usage and key counts
533
- 3. **Implement error handling** - Handle Redis connection failures gracefully
534
- 4. **Use pub/sub wisely** - Avoid creating too many subscriptions that could impact performance
535
- 5. **Batch operations** - Use pipelines for multiple operations when possible
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
- ## Error Handling
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
- This package depends on:
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/resource`](../resource) - Base resource interface and utilities
558
- - [`@owlmeans/mongo-resource`](../mongo-resource) - MongoDB resource implementation
559
- - [`@owlmeans/static-resource`](../static-resource) - In-memory resource implementation
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.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.1",
28
- "@owlmeans/context": "^0.1.1",
29
- "@owlmeans/resource": "^0.1.1",
30
- "@owlmeans/server-context": "^0.1.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": "^5.8.3"
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
- "../tsconfig.default.json",
3
+ "@owlmeans/dep-config/tsconfig.base.json",
4
+ "@owlmeans/dep-config/tsconfig.node.json"
4
5
  ],
5
6
  "compilerOptions": {
6
- "rootDir": "./src/", /* Specify the root folder within your source files. */
7
- "outDir": "./build/", /* Specify an output folder for all emitted files. */
7
+ "rootDir": "./src/",
8
+ "outDir": "./build/"
8
9
  },
9
10
  "exclude": [
10
11
  "./dist/**/*",
11
12
  "./build/**/*",
12
13
  "./*.ts"
13
14
  ]
14
- }
15
+ }