@owlmeans/redis-resource 0.1.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/LICENSE +21 -0
- package/README.md +560 -0
- package/build/.gitkeep +0 -0
- package/build/consts.d.ts +3 -0
- package/build/consts.d.ts.map +1 -0
- package/build/consts.js +3 -0
- package/build/consts.js.map +1 -0
- package/build/index.d.ts +4 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +3 -0
- package/build/index.js.map +1 -0
- package/build/resource.d.ts +4 -0
- package/build/resource.d.ts.map +1 -0
- package/build/resource.js +241 -0
- package/build/resource.js.map +1 -0
- package/build/types.d.ts +22 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +2 -0
- package/build/types.js.map +1 -0
- package/build/utils/index.d.ts +2 -0
- package/build/utils/index.d.ts.map +1 -0
- package/build/utils/index.js +2 -0
- package/build/utils/index.js.map +1 -0
- package/build/utils/options.d.ts +3 -0
- package/build/utils/options.d.ts.map +1 -0
- package/build/utils/options.js +17 -0
- package/build/utils/options.js.map +1 -0
- package/package.json +41 -0
- package/src/consts.ts +4 -0
- package/src/index.ts +3 -0
- package/src/resource.ts +294 -0
- package/src/types.ts +32 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/options.ts +17 -0
- package/tsconfig.json +14 -0
- package/tsconfig.tsbuildinfo +1 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 OwlMeans Common — Fullstack typescript framework
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
# @owlmeans/redis-resource
|
|
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.
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
This package serves as a Redis integration layer for the OwlMeans resource system that:
|
|
8
|
+
|
|
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
|
|
15
|
+
|
|
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)
|
|
304
|
+
```
|
|
305
|
+
|
|
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)
|
|
320
|
+
|
|
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
|
|
330
|
+
|
|
331
|
+
```typescript
|
|
332
|
+
import { makeRedisResource } from '@owlmeans/redis-resource'
|
|
333
|
+
|
|
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()
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### TTL and Expiration
|
|
361
|
+
|
|
362
|
+
```typescript
|
|
363
|
+
const sessionResource = makeRedisResource<Session>('sessions')
|
|
364
|
+
|
|
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
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
### Real-time Synchronization
|
|
380
|
+
|
|
381
|
+
```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
|
+
```
|
|
407
|
+
|
|
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
|
+
}
|
|
423
|
+
|
|
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 }
|
|
445
|
+
})
|
|
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
|
+
```
|
|
455
|
+
|
|
456
|
+
## Integration Patterns
|
|
457
|
+
|
|
458
|
+
### Microservices Architecture
|
|
459
|
+
|
|
460
|
+
```typescript
|
|
461
|
+
// Product Service
|
|
462
|
+
const productResource = makeRedisResource<Product>('products', 'redis-products')
|
|
463
|
+
|
|
464
|
+
// Order Service
|
|
465
|
+
const orderResource = makeRedisResource<Order>('orders', 'redis-orders')
|
|
466
|
+
|
|
467
|
+
// Shared event bus
|
|
468
|
+
const eventResource = makeRedisResource<Event>('events', 'redis-events')
|
|
469
|
+
|
|
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
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// Usage
|
|
514
|
+
const userData = await getCachedData(`user:${userId}`, () => fetchUserFromDB(userId))
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
## Performance Considerations
|
|
518
|
+
|
|
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
|
|
523
|
+
|
|
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
|
|
528
|
+
|
|
529
|
+
### Best Practices
|
|
530
|
+
|
|
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
|
|
536
|
+
|
|
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
|
|
549
|
+
|
|
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
|
|
554
|
+
|
|
555
|
+
## Related Packages
|
|
556
|
+
|
|
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
|
package/build/.gitkeep
ADDED
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consts.d.ts","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,gBAAgB,UAAU,CAAA;AAEvC,eAAO,MAAM,iBAAiB,KAAK,CAAA"}
|
package/build/consts.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consts.js","sourceRoot":"","sources":["../src/consts.ts"],"names":[],"mappings":"AACA,MAAM,CAAC,MAAM,gBAAgB,GAAG,OAAO,CAAA;AAEvC,MAAM,CAAC,MAAM,iBAAiB,GAAG,EAAE,CAAA"}
|
package/build/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,mBAAmB,YAAY,CAAA;AAC/B,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA"}
|
package/build/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ResourceMaker, ResourceRecord } from '@owlmeans/resource';
|
|
2
|
+
import type { RedisResource } from './types.js';
|
|
3
|
+
export declare const makeRedisResource: <R extends ResourceRecord, T extends RedisResource<R> = RedisResource<R>>(alias: string, dbAlias?: string, serviceAlias?: string, makeCustomResource?: ResourceMaker<R, T>) => T;
|
|
4
|
+
//# sourceMappingURL=resource.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../src/resource.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAEvE,OAAO,KAAK,EAA+B,aAAa,EAAE,MAAM,YAAY,CAAA;AAU5E,eAAO,MAAM,iBAAiB,GAC5B,CAAC,SAAS,cAAc,EAAE,CAAC,SAAS,aAAa,CAAC,CAAC,CAAC,4BAE7C,MAAM,gEACQ,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,KACvC,CAoRF,CAAA"}
|