@fjell/cache 4.6.7 → 4.6.11
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/dist/Aggregator.cjs.js +26 -20
- package/dist/Aggregator.d.ts +17 -1
- package/dist/Aggregator.es.js +26 -20
- package/dist/Cache.cjs.js +22 -345
- package/dist/Cache.d.ts +25 -20
- package/dist/Cache.es.js +22 -346
- package/dist/Instance.cjs.js +23 -0
- package/dist/Instance.d.ts +17 -0
- package/dist/Instance.es.js +18 -0
- package/dist/InstanceFactory.cjs.js +35 -0
- package/dist/InstanceFactory.d.ts +8 -0
- package/dist/InstanceFactory.es.js +31 -0
- package/dist/Operations.cjs.js +43 -0
- package/dist/Operations.d.ts +70 -0
- package/dist/Operations.es.js +39 -0
- package/dist/Registry.cjs.js +36 -0
- package/dist/Registry.d.ts +15 -0
- package/dist/Registry.es.js +31 -0
- package/dist/index.cjs +459 -408
- package/dist/index.cjs.js +12 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.es.js +6 -3
- package/dist/ops/action.cjs.js +28 -0
- package/dist/ops/action.d.ts +4 -0
- package/dist/ops/action.es.js +24 -0
- package/dist/ops/all.cjs.js +33 -0
- package/dist/ops/all.d.ts +4 -0
- package/dist/ops/all.es.js +29 -0
- package/dist/ops/allAction.cjs.js +35 -0
- package/dist/ops/allAction.d.ts +4 -0
- package/dist/ops/allAction.es.js +31 -0
- package/dist/ops/allFacet.cjs.js +22 -0
- package/dist/ops/allFacet.d.ts +4 -0
- package/dist/ops/allFacet.es.js +18 -0
- package/dist/ops/create.cjs.js +23 -0
- package/dist/ops/create.d.ts +4 -0
- package/dist/ops/create.es.js +19 -0
- package/dist/ops/facet.cjs.js +21 -0
- package/dist/ops/facet.d.ts +4 -0
- package/dist/ops/facet.es.js +17 -0
- package/dist/ops/find.cjs.js +26 -0
- package/dist/ops/find.d.ts +4 -0
- package/dist/ops/find.es.js +22 -0
- package/dist/ops/findOne.cjs.js +24 -0
- package/dist/ops/findOne.d.ts +4 -0
- package/dist/ops/findOne.es.js +20 -0
- package/dist/ops/get.cjs.js +38 -0
- package/dist/ops/get.d.ts +4 -0
- package/dist/ops/get.es.js +34 -0
- package/dist/ops/one.cjs.js +33 -0
- package/dist/ops/one.d.ts +4 -0
- package/dist/ops/one.es.js +29 -0
- package/dist/ops/remove.cjs.js +30 -0
- package/dist/ops/remove.d.ts +4 -0
- package/dist/ops/remove.es.js +26 -0
- package/dist/ops/reset.cjs.js +15 -0
- package/dist/ops/reset.d.ts +4 -0
- package/dist/ops/reset.es.js +11 -0
- package/dist/ops/retrieve.cjs.js +37 -0
- package/dist/ops/retrieve.d.ts +4 -0
- package/dist/ops/retrieve.es.js +33 -0
- package/dist/ops/set.cjs.js +71 -0
- package/dist/ops/set.d.ts +3 -0
- package/dist/ops/set.es.js +67 -0
- package/dist/ops/update.cjs.js +34 -0
- package/dist/ops/update.d.ts +4 -0
- package/dist/ops/update.es.js +30 -0
- package/examples/README.md +302 -0
- package/examples/aggregator-example.ts +328 -0
- package/examples/basic-cache-example.ts +270 -0
- package/examples/cache-map-example.ts +265 -0
- package/package.json +7 -6
- package/vitest.config.ts +2 -2
- package/dist/CacheRegistry.cjs.js +0 -66
- package/dist/CacheRegistry.d.ts +0 -10
- package/dist/CacheRegistry.es.js +0 -62
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
# Fjell-Cache Examples
|
|
2
|
+
|
|
3
|
+
This directory contains examples demonstrating how to use fjell-cache for caching data models and managing complex business relationships with different patterns and complexity levels.
|
|
4
|
+
|
|
5
|
+
## Examples
|
|
6
|
+
|
|
7
|
+
### 1. `basic-cache-example.ts` ⭐ **Start Here!**
|
|
8
|
+
**Perfect for beginners!** Demonstrates the fundamental way to use fjell-cache for data caching:
|
|
9
|
+
- **Basic cache operations** - Create caches with coordinates and registries, use operations API
|
|
10
|
+
- **Simple data models** - User and Task entities with mock storage
|
|
11
|
+
- **Cache-as-Instance architecture** - Caches extend Instance from fjell-registry
|
|
12
|
+
- **Cache hits vs misses** - Understand cache behavior and performance benefits
|
|
13
|
+
- **Cache management** - Updates, deletions, and data consistency through operations
|
|
14
|
+
|
|
15
|
+
Great for understanding the fundamentals of fjell-cache data management.
|
|
16
|
+
|
|
17
|
+
### 2. `aggregator-example.ts` 🏗️ **Advanced Business Relationships**
|
|
18
|
+
**Complete business relationship management!** Demonstrates advanced caching patterns with entity relationships:
|
|
19
|
+
- **Multiple interconnected models**: Customer, Order, Product, SupportTicket
|
|
20
|
+
- **Automatic reference population**: Orders automatically include customer data
|
|
21
|
+
- **Required vs optional aggregates**: Flexible relationship management
|
|
22
|
+
- **Complex business scenarios**: E-commerce platform with customer management
|
|
23
|
+
- **Performance optimization**: Cache efficiency through aggregated data
|
|
24
|
+
|
|
25
|
+
Shows how fjell-cache handles enterprise data relationship patterns.
|
|
26
|
+
|
|
27
|
+
### 3. `cache-map-example.ts` 🔧 **Low-Level Cache Operations**
|
|
28
|
+
**Direct cache management!** Demonstrates lower-level CacheMap functionality:
|
|
29
|
+
- **Direct CacheMap usage**: Create and manage cache maps without higher-level abstractions
|
|
30
|
+
- **Primary and composite keys**: Handle both simple and complex key structures
|
|
31
|
+
- **Location-based operations**: Filter contained items by location hierarchy
|
|
32
|
+
- **Performance characteristics**: Bulk operations and efficiency testing
|
|
33
|
+
- **Cache lifecycle**: Cloning, cleanup, and memory management
|
|
34
|
+
|
|
35
|
+
Perfect for understanding the underlying cache mechanisms and advanced use cases.
|
|
36
|
+
|
|
37
|
+
## Key Concepts Demonstrated
|
|
38
|
+
|
|
39
|
+
### Basic Caching Operations (basic-cache-example.ts)
|
|
40
|
+
```typescript
|
|
41
|
+
// Import fjell-cache functionality
|
|
42
|
+
import { createCache } from '@fjell/cache';
|
|
43
|
+
import { createCoordinate, createRegistry } from '@fjell/registry';
|
|
44
|
+
import { ClientApi } from '@fjell/client-api';
|
|
45
|
+
|
|
46
|
+
// Create a registry for cache management
|
|
47
|
+
const registry = createRegistry();
|
|
48
|
+
|
|
49
|
+
// Create a cache instance with API integration
|
|
50
|
+
const userApi = createUserApi(); // Your API implementation
|
|
51
|
+
const userCache = await createCache(userApi, createCoordinate('user'), registry);
|
|
52
|
+
|
|
53
|
+
// Cache is now an instance - no need for separate createInstance call
|
|
54
|
+
// Perform cache operations through the operations API
|
|
55
|
+
const [cacheMap, allUsers] = await userCache.operations.all();
|
|
56
|
+
const [, cachedUser] = await userCache.operations.get(userKey);
|
|
57
|
+
const [, retrievedUser] = await userCache.operations.retrieve(userKey); // Cache hit!
|
|
58
|
+
|
|
59
|
+
await userCache.operations.set(userKey, updatedUser);
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Advanced Aggregation (aggregator-example.ts)
|
|
63
|
+
```typescript
|
|
64
|
+
// Create aggregated cache with relationships
|
|
65
|
+
const orderAggregator = await createAggregator(orderCache, {
|
|
66
|
+
aggregates: {
|
|
67
|
+
customer: { cache: customerCache, optional: false }, // Required reference
|
|
68
|
+
product: { cache: productCache, optional: true }, // Optional reference
|
|
69
|
+
},
|
|
70
|
+
events: {}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Automatically populate related entities
|
|
74
|
+
const populatedOrder = await orderAggregator.populate(order);
|
|
75
|
+
if (populatedOrder.aggs?.customer?.item) {
|
|
76
|
+
const customer = populatedOrder.aggs.customer.item;
|
|
77
|
+
console.log(`Order for: ${customer.name} (${customer.email})`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Aggregator is now itself an instance - access operations directly
|
|
81
|
+
const [, orders] = await orderAggregator.all();
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Direct Cache Management (cache-map-example.ts)
|
|
85
|
+
```typescript
|
|
86
|
+
// Create CacheMap instances directly
|
|
87
|
+
const documentCacheMap = new CacheMap<Document, 'document'>(['document']);
|
|
88
|
+
const commentCacheMap = new CacheMap<Comment, 'comment', 'document'>(['comment', 'document']);
|
|
89
|
+
|
|
90
|
+
// Basic operations
|
|
91
|
+
documentCacheMap.set(documentKey, document);
|
|
92
|
+
const retrievedDoc = documentCacheMap.get(documentKey);
|
|
93
|
+
const hasDoc = documentCacheMap.includesKey(documentKey);
|
|
94
|
+
|
|
95
|
+
// Bulk operations
|
|
96
|
+
const allDocuments = documentCacheMap.allIn([]);
|
|
97
|
+
const allKeys = documentCacheMap.keys();
|
|
98
|
+
const allValues = documentCacheMap.values();
|
|
99
|
+
|
|
100
|
+
// Location-based filtering for contained items
|
|
101
|
+
const commentsInDoc = commentCacheMap.allIn([{ kt: 'document', lk: documentId }]);
|
|
102
|
+
|
|
103
|
+
// Performance operations
|
|
104
|
+
const clonedCache = documentCacheMap.clone();
|
|
105
|
+
documentCacheMap.delete(documentKey);
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Data Model Patterns
|
|
109
|
+
|
|
110
|
+
#### Primary Items
|
|
111
|
+
- Standalone entities (User, Customer, Document)
|
|
112
|
+
- No location hierarchy constraints
|
|
113
|
+
- Simple key structure: `Item<'keyType'>`
|
|
114
|
+
|
|
115
|
+
#### Contained Items
|
|
116
|
+
- Nested within other entities or locations
|
|
117
|
+
- Multi-level location keys for organization
|
|
118
|
+
- Complex key structure: `Item<'keyType', 'location1', 'location2', ...>`
|
|
119
|
+
|
|
120
|
+
#### Aggregated Items
|
|
121
|
+
- Items with automatic reference population
|
|
122
|
+
- Business relationships through cache aggregation
|
|
123
|
+
- Performance optimized through cached references
|
|
124
|
+
|
|
125
|
+
## Running Examples
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
# Start with the basic example (recommended)
|
|
129
|
+
npx tsx examples/basic-cache-example.ts
|
|
130
|
+
|
|
131
|
+
# Run the aggregator example
|
|
132
|
+
npx tsx examples/aggregator-example.ts
|
|
133
|
+
|
|
134
|
+
# Run the cache map example
|
|
135
|
+
npx tsx examples/cache-map-example.ts
|
|
136
|
+
|
|
137
|
+
# Or with Node.js
|
|
138
|
+
node -r esbuild-register examples/basic-cache-example.ts
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Integration with Real Applications
|
|
142
|
+
|
|
143
|
+
All examples use the actual fjell-cache functionality! In production applications:
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
import { createCache, createRegistry, createInstance, createAggregator } from '@fjell/cache';
|
|
147
|
+
import { ClientApi } from '@fjell/client-api';
|
|
148
|
+
|
|
149
|
+
// Basic cache setup
|
|
150
|
+
const registry = createRegistry();
|
|
151
|
+
|
|
152
|
+
const userCache = await createCache(userApi, 'user');
|
|
153
|
+
const userInstance = createInstance(registry, createCoordinate('user'), userCache);
|
|
154
|
+
|
|
155
|
+
// With aggregation for business relationships
|
|
156
|
+
const orderAggregator = await createAggregator(orderCache, {
|
|
157
|
+
aggregates: {
|
|
158
|
+
customer: { cache: customerCache, optional: false },
|
|
159
|
+
items: { cache: productCache, optional: true }
|
|
160
|
+
},
|
|
161
|
+
events: {
|
|
162
|
+
orderUpdated: async (key, item) => {
|
|
163
|
+
// Custom event handling
|
|
164
|
+
await notifyCustomer(item);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Advanced cache configuration
|
|
170
|
+
const options = {
|
|
171
|
+
cacheSize: 10000,
|
|
172
|
+
ttl: 3600000, // 1 hour
|
|
173
|
+
refreshThreshold: 0.8,
|
|
174
|
+
compression: true
|
|
175
|
+
};
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Cache Operation Types
|
|
179
|
+
|
|
180
|
+
### Basic Operations
|
|
181
|
+
- **all()**: Get all items and update cache
|
|
182
|
+
- **get()**: Get item by key, fetch from API if not cached
|
|
183
|
+
- **retrieve()**: Get item by key, return null if not cached
|
|
184
|
+
- **set()**: Store item in cache
|
|
185
|
+
- **one()**: Get single item
|
|
186
|
+
- **find()**: Search items with finder methods
|
|
187
|
+
|
|
188
|
+
### Aggregation Operations
|
|
189
|
+
- **populate()**: Automatically populate item with related entities
|
|
190
|
+
- **populateAggregate()**: Populate specific aggregate relationship
|
|
191
|
+
- **populateEvent()**: Handle population events
|
|
192
|
+
|
|
193
|
+
### Cache Management
|
|
194
|
+
- **allIn()**: Get items by location (for contained items)
|
|
195
|
+
- **queryIn()**: Query items by location with filtering
|
|
196
|
+
- **clone()**: Create independent cache copy
|
|
197
|
+
- **delete()**: Remove item from cache
|
|
198
|
+
- **clear()**: Clear all cache contents
|
|
199
|
+
|
|
200
|
+
### Business Operations
|
|
201
|
+
```typescript
|
|
202
|
+
// Cache with business logic integration
|
|
203
|
+
const cache = await createCache(api, 'order', {
|
|
204
|
+
hooks: {
|
|
205
|
+
beforeGet: async (key) => { /* validation */ },
|
|
206
|
+
afterSet: async (key, item) => { /* notifications */ }
|
|
207
|
+
},
|
|
208
|
+
validators: {
|
|
209
|
+
status: (status) => ['pending', 'shipped', 'delivered'].includes(status)
|
|
210
|
+
},
|
|
211
|
+
aggregates: {
|
|
212
|
+
customer: customerCache,
|
|
213
|
+
items: productCache
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## When to Use What
|
|
219
|
+
|
|
220
|
+
**Use `basic-cache-example.ts` approach when:**
|
|
221
|
+
- Learning fjell-cache fundamentals
|
|
222
|
+
- Building simple applications with caching needs
|
|
223
|
+
- Need basic get/set cache operations
|
|
224
|
+
- Working with independent data models
|
|
225
|
+
|
|
226
|
+
**Use `aggregator-example.ts` approach when:**
|
|
227
|
+
- Managing complex business relationships
|
|
228
|
+
- Need automatic population of related entities
|
|
229
|
+
- Building enterprise applications with interconnected data
|
|
230
|
+
- Require performance optimization through aggregated caching
|
|
231
|
+
- Working with customer/order/product type relationships
|
|
232
|
+
|
|
233
|
+
**Use `cache-map-example.ts` approach when:**
|
|
234
|
+
- Need direct control over cache operations
|
|
235
|
+
- Building custom caching solutions
|
|
236
|
+
- Working with contained items and location hierarchies
|
|
237
|
+
- Require maximum performance and minimal overhead
|
|
238
|
+
- Implementing cache-based data structures
|
|
239
|
+
|
|
240
|
+
## Advanced Features
|
|
241
|
+
|
|
242
|
+
### Cache Aggregation
|
|
243
|
+
```typescript
|
|
244
|
+
// Complex aggregation with optional and required references
|
|
245
|
+
const ticketAggregator = await createAggregator(ticketCache, {
|
|
246
|
+
aggregates: {
|
|
247
|
+
customer: { cache: customerCache, optional: false }, // Always populated
|
|
248
|
+
order: { cache: orderCache, optional: true }, // Only if orderId exists
|
|
249
|
+
assignee: { cache: userCache, optional: true } // Only if assigned
|
|
250
|
+
},
|
|
251
|
+
events: {
|
|
252
|
+
ticketAssigned: async (key, ticket) => {
|
|
253
|
+
await notifyAssignee(ticket);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
// Automatic population includes all available references
|
|
259
|
+
const populatedTicket = await ticketAggregator.populate(ticket);
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Performance Optimization
|
|
263
|
+
```typescript
|
|
264
|
+
// Cache with coordinate and registry
|
|
265
|
+
const cache = await createCache(api, createCoordinate('product'), registry);
|
|
266
|
+
|
|
267
|
+
// Bulk operations for efficiency
|
|
268
|
+
const [cacheMap, allProducts] = await cache.operations.all();
|
|
269
|
+
const productMap = new Map(allProducts.map(p => [p.id, p]));
|
|
270
|
+
|
|
271
|
+
// Access cache properties for optimization
|
|
272
|
+
console.log(`Cache coordinate: ${cache.coordinate.kta.join(', ')}`);
|
|
273
|
+
console.log(`Cached items: ${cache.cacheMap.size()}`);
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Storage Integration
|
|
277
|
+
Fjell-cache works with any storage backend through the ClientApi interface:
|
|
278
|
+
- SQL databases (PostgreSQL, MySQL, SQLite)
|
|
279
|
+
- NoSQL databases (MongoDB, DynamoDB, Redis)
|
|
280
|
+
- REST APIs and GraphQL endpoints
|
|
281
|
+
- In-memory stores and mock data
|
|
282
|
+
- File systems and cloud storage
|
|
283
|
+
- Custom data sources
|
|
284
|
+
|
|
285
|
+
### Error Handling and Resilience
|
|
286
|
+
```typescript
|
|
287
|
+
// Cache with proper error handling through the API layer
|
|
288
|
+
const resilientCache = await createCache(api, createCoordinate('user'), registry);
|
|
289
|
+
|
|
290
|
+
// Error handling in operations
|
|
291
|
+
try {
|
|
292
|
+
const [, user] = await resilientCache.operations.get(userKey);
|
|
293
|
+
return user;
|
|
294
|
+
} catch (error) {
|
|
295
|
+
// Handle cache errors gracefully
|
|
296
|
+
console.error('Cache operation failed:', error);
|
|
297
|
+
// Fallback to direct API call
|
|
298
|
+
return await api.get(userKey);
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
This provides the foundation for building scalable, maintainable applications with intelligent caching using fjell-cache.
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
2
|
+
/**
|
|
3
|
+
* Aggregator Example
|
|
4
|
+
*
|
|
5
|
+
* This example demonstrates advanced fjell-cache functionality using the Aggregator
|
|
6
|
+
* for managing related entities with automatic population of references.
|
|
7
|
+
*
|
|
8
|
+
* Shows how to:
|
|
9
|
+
* - Create aggregated caches with references between entities
|
|
10
|
+
* - Populate items with their related data automatically
|
|
11
|
+
* - Handle optional vs required aggregates
|
|
12
|
+
* - Manage complex business relationships through caching
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { createAggregator } from '../src/Aggregator';
|
|
16
|
+
import { createCache } from '../src/Cache';
|
|
17
|
+
import { createInstance } from '../src/Instance';
|
|
18
|
+
import { createRegistry } from '../src/Registry';
|
|
19
|
+
import { ClientApi } from '@fjell/client-api';
|
|
20
|
+
import { Item, PriKey } from '@fjell/core';
|
|
21
|
+
import { createCoordinate } from '@fjell/registry';
|
|
22
|
+
|
|
23
|
+
// Define our business models with relationships
|
|
24
|
+
interface Customer extends Item<'customer'> {
|
|
25
|
+
id: string;
|
|
26
|
+
name: string;
|
|
27
|
+
email: string;
|
|
28
|
+
company: string;
|
|
29
|
+
tier: 'bronze' | 'silver' | 'gold' | 'platinum';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface Order extends Item<'order'> {
|
|
33
|
+
id: string;
|
|
34
|
+
customerId: string;
|
|
35
|
+
total: number;
|
|
36
|
+
status: 'pending' | 'processing' | 'shipped' | 'delivered' | 'cancelled';
|
|
37
|
+
items: string[]; // Array of product IDs
|
|
38
|
+
orderDate: Date;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface Product extends Item<'product'> {
|
|
42
|
+
id: string;
|
|
43
|
+
name: string;
|
|
44
|
+
price: number;
|
|
45
|
+
category: string;
|
|
46
|
+
inStock: boolean;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
interface SupportTicket extends Item<'ticket'> {
|
|
50
|
+
id: string;
|
|
51
|
+
customerId: string;
|
|
52
|
+
orderId?: string; // Optional reference to order
|
|
53
|
+
subject: string;
|
|
54
|
+
priority: 'low' | 'medium' | 'high' | 'urgent';
|
|
55
|
+
status: 'open' | 'in-progress' | 'resolved' | 'closed';
|
|
56
|
+
description: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Mock storage
|
|
60
|
+
const mockCustomers = new Map<string, Customer>();
|
|
61
|
+
const mockOrders = new Map<string, Order>();
|
|
62
|
+
const mockProducts = new Map<string, Product>();
|
|
63
|
+
const mockTickets = new Map<string, SupportTicket>();
|
|
64
|
+
|
|
65
|
+
// Helper to create mock APIs
|
|
66
|
+
const createMockApi = <T extends Item<any>>(storage: Map<string, T>) => {
|
|
67
|
+
return {
|
|
68
|
+
async all(_query = {}) {
|
|
69
|
+
console.log(`📦 Fetching all items from ${storage.constructor.name}...`);
|
|
70
|
+
return Array.from(storage.values());
|
|
71
|
+
},
|
|
72
|
+
async one(query = {}) {
|
|
73
|
+
const items = await this.all!(query);
|
|
74
|
+
return items[0] || null;
|
|
75
|
+
},
|
|
76
|
+
async get(key: PriKey<any>) {
|
|
77
|
+
const item = storage.get(String(key.pk));
|
|
78
|
+
if (!item) {
|
|
79
|
+
throw new Error(`Item not found: ${key.pk}`);
|
|
80
|
+
}
|
|
81
|
+
return item;
|
|
82
|
+
},
|
|
83
|
+
async find(_finder = 'all') {
|
|
84
|
+
return await this.all!({});
|
|
85
|
+
}
|
|
86
|
+
} as Partial<ClientApi<T, any>>;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// Test data creation helpers
|
|
90
|
+
const createCustomer = (id: string, name: string, email: string, company: string, tier: 'bronze' | 'silver' | 'gold' | 'platinum'): Customer => {
|
|
91
|
+
const customer: Customer = {
|
|
92
|
+
id, name, email, company, tier,
|
|
93
|
+
key: { kt: 'customer', pk: id },
|
|
94
|
+
events: { created: { at: new Date() }, updated: { at: new Date() }, deleted: { at: null } }
|
|
95
|
+
};
|
|
96
|
+
mockCustomers.set(id, customer);
|
|
97
|
+
return customer;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const createProduct = (id: string, name: string, price: number, category: string, inStock: boolean): Product => {
|
|
101
|
+
const product: Product = {
|
|
102
|
+
id, name, price, category, inStock,
|
|
103
|
+
key: { kt: 'product', pk: id },
|
|
104
|
+
events: { created: { at: new Date() }, updated: { at: new Date() }, deleted: { at: null } }
|
|
105
|
+
};
|
|
106
|
+
mockProducts.set(id, product);
|
|
107
|
+
return product;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const createOrder = (id: string, customerId: string, total: number, status: 'pending' | 'processing' | 'shipped' | 'delivered' | 'cancelled', items: string[]): Order => {
|
|
111
|
+
const order: Order = {
|
|
112
|
+
id, customerId, total, status, items, orderDate: new Date(),
|
|
113
|
+
key: { kt: 'order', pk: id },
|
|
114
|
+
events: { created: { at: new Date() }, updated: { at: new Date() }, deleted: { at: null } },
|
|
115
|
+
// Add references for aggregation
|
|
116
|
+
refs: {
|
|
117
|
+
customer: { kt: 'customer', pk: customerId }
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
mockOrders.set(id, order);
|
|
121
|
+
return order;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const createSupportTicket = (
|
|
125
|
+
id: string,
|
|
126
|
+
customerId: string,
|
|
127
|
+
subject: string,
|
|
128
|
+
priority: 'low' | 'medium' | 'high' | 'urgent',
|
|
129
|
+
status: 'open' | 'in-progress' | 'resolved' | 'closed',
|
|
130
|
+
description: string,
|
|
131
|
+
orderId?: string): SupportTicket => {
|
|
132
|
+
const ticket: SupportTicket = {
|
|
133
|
+
id, customerId, subject, priority, status, description, orderId,
|
|
134
|
+
key: { kt: 'ticket', pk: id },
|
|
135
|
+
events: { created: { at: new Date() }, updated: { at: new Date() }, deleted: { at: null } },
|
|
136
|
+
// Add references for aggregation
|
|
137
|
+
refs: {
|
|
138
|
+
customer: { kt: 'customer', pk: customerId },
|
|
139
|
+
...(orderId && { order: { kt: 'order', pk: orderId } })
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
mockTickets.set(id, ticket);
|
|
143
|
+
return ticket;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
export const runAggregatorExample = async (): Promise<void> => {
|
|
147
|
+
console.log('\n🚀 Fjell-Cache Aggregator Example');
|
|
148
|
+
console.log('=================================\n');
|
|
149
|
+
|
|
150
|
+
console.log('This example demonstrates advanced caching with entity relationships and aggregation.\n');
|
|
151
|
+
|
|
152
|
+
// Step 1: Create test data
|
|
153
|
+
console.log('Step 1: Creating business entities');
|
|
154
|
+
console.log('----------------------------------');
|
|
155
|
+
|
|
156
|
+
const customer1 = createCustomer('cust-1', 'Acme Corp', 'contact@acme.com', 'Acme Corporation', 'gold');
|
|
157
|
+
const customer2 = createCustomer('cust-2', 'TechStart Inc', 'hello@techstart.io', 'TechStart Inc', 'silver');
|
|
158
|
+
|
|
159
|
+
const product1 = createProduct('prod-1', 'Premium Widget', 299.99, 'widgets', true);
|
|
160
|
+
const product2 = createProduct('prod-2', 'Standard Widget', 199.99, 'widgets', true);
|
|
161
|
+
const product3 = createProduct('prod-3', 'Budget Widget', 99.99, 'widgets', false);
|
|
162
|
+
|
|
163
|
+
const order1 = createOrder('order-1', customer1.id, 499.98, 'shipped', [product1.id, product2.id]);
|
|
164
|
+
const order2 = createOrder('order-2', customer2.id, 199.99, 'pending', [product2.id]);
|
|
165
|
+
|
|
166
|
+
const ticket1 = createSupportTicket('ticket-1', customer1.id, 'Widget not working', 'high', 'open', 'The premium widget stopped working after 2 days', order1.id);
|
|
167
|
+
const ticket2 = createSupportTicket('ticket-2', customer2.id, 'General inquiry', 'low', 'resolved', 'Question about widget compatibility');
|
|
168
|
+
|
|
169
|
+
console.log(`✅ Created ${mockCustomers.size} customers, ${mockProducts.size} products, ${mockOrders.size} orders, ${mockTickets.size} tickets\n`);
|
|
170
|
+
|
|
171
|
+
// Step 2: Set up cache infrastructure
|
|
172
|
+
console.log('Step 2: Setting up cache infrastructure');
|
|
173
|
+
console.log('--------------------------------------');
|
|
174
|
+
|
|
175
|
+
const registry = createRegistry();
|
|
176
|
+
console.log('✅ Created registry');
|
|
177
|
+
|
|
178
|
+
// Create individual caches for each entity type
|
|
179
|
+
const customerApi = createMockApi(mockCustomers) as ClientApi<Customer, 'customer'>;
|
|
180
|
+
const orderApi = createMockApi(mockOrders) as ClientApi<Order, 'order'>;
|
|
181
|
+
const productApi = createMockApi(mockProducts) as ClientApi<Product, 'product'>;
|
|
182
|
+
const ticketApi = createMockApi(mockTickets) as ClientApi<SupportTicket, 'ticket'>;
|
|
183
|
+
|
|
184
|
+
const customerCache = await createCache(customerApi, createCoordinate('customer'), registry);
|
|
185
|
+
const orderCache = await createCache(orderApi, createCoordinate('order'), registry);
|
|
186
|
+
const productCache = await createCache(productApi, createCoordinate('product'), registry);
|
|
187
|
+
const ticketCache = await createCache(ticketApi, createCoordinate('ticket'), registry);
|
|
188
|
+
|
|
189
|
+
console.log('✅ Created individual caches for each entity type');
|
|
190
|
+
|
|
191
|
+
// Step 3: Create aggregated caches
|
|
192
|
+
console.log('\nStep 3: Creating aggregated caches');
|
|
193
|
+
console.log('----------------------------------');
|
|
194
|
+
|
|
195
|
+
// Create order aggregator that automatically populates customer data
|
|
196
|
+
const orderAggregator = await createAggregator(orderCache, {
|
|
197
|
+
aggregates: {
|
|
198
|
+
customer: { cache: customerCache, optional: false }, // Required reference
|
|
199
|
+
},
|
|
200
|
+
events: {}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// Create support ticket aggregator with both customer and order references
|
|
204
|
+
const ticketAggregator = await createAggregator(ticketCache, {
|
|
205
|
+
aggregates: {
|
|
206
|
+
customer: { cache: customerCache, optional: false }, // Required reference
|
|
207
|
+
order: { cache: orderCache, optional: true }, // Optional reference (not all tickets relate to orders)
|
|
208
|
+
},
|
|
209
|
+
events: {}
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
console.log('✅ Created aggregated caches with relationship mappings (aggregators are now instances)\n');
|
|
213
|
+
|
|
214
|
+
// Step 4: Basic aggregation - Fetch orders with customer data
|
|
215
|
+
console.log('Step 4: Order aggregation with customer data');
|
|
216
|
+
console.log('--------------------------------------------');
|
|
217
|
+
|
|
218
|
+
const [, orders] = await orderAggregator.all();
|
|
219
|
+
console.log(`📋 Fetched ${orders.length} orders`);
|
|
220
|
+
|
|
221
|
+
for (const order of orders) {
|
|
222
|
+
console.log(`\n📦 Order ${order.id}:`);
|
|
223
|
+
console.log(` Amount: $${order.total}`);
|
|
224
|
+
console.log(` Status: ${order.status}`);
|
|
225
|
+
console.log(` Items: ${order.items.join(', ')}`);
|
|
226
|
+
|
|
227
|
+
// Populate the order with customer data
|
|
228
|
+
const populatedOrder = await orderAggregator.populate(order);
|
|
229
|
+
if (populatedOrder.aggs?.customer?.item) {
|
|
230
|
+
const customer = populatedOrder.aggs.customer.item;
|
|
231
|
+
console.log(` 👤 Customer: ${customer.name} (${customer.email})`);
|
|
232
|
+
console.log(` 🏢 Company: ${customer.company} - ${customer.tier} tier`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Step 5: Complex aggregation - Support tickets with multiple references
|
|
237
|
+
console.log('\n\nStep 5: Support ticket aggregation with multiple references');
|
|
238
|
+
console.log('----------------------------------------------------------');
|
|
239
|
+
|
|
240
|
+
const [, tickets] = await ticketAggregator.all();
|
|
241
|
+
console.log(`🎫 Fetched ${tickets.length} support tickets`);
|
|
242
|
+
|
|
243
|
+
for (const ticket of tickets) {
|
|
244
|
+
console.log(`\n🎫 Ticket ${ticket.id}:`);
|
|
245
|
+
console.log(` Subject: ${ticket.subject}`);
|
|
246
|
+
console.log(` Priority: ${ticket.priority}`);
|
|
247
|
+
console.log(` Status: ${ticket.status}`);
|
|
248
|
+
console.log(` Description: ${ticket.description}`);
|
|
249
|
+
|
|
250
|
+
// Populate the ticket with all related data
|
|
251
|
+
const populatedTicket = await ticketAggregator.populate(ticket);
|
|
252
|
+
|
|
253
|
+
// Customer data (required reference)
|
|
254
|
+
if (populatedTicket.aggs?.customer?.item) {
|
|
255
|
+
const customer = populatedTicket.aggs.customer.item;
|
|
256
|
+
console.log(` 👤 Customer: ${customer.name} (${customer.email}) - ${customer.tier} tier`);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Order data (optional reference)
|
|
260
|
+
if (populatedTicket.aggs?.order?.item) {
|
|
261
|
+
const order = populatedTicket.aggs.order.item;
|
|
262
|
+
console.log(` 📦 Related Order: ${order.id} - $${order.total} (${order.status})`);
|
|
263
|
+
} else {
|
|
264
|
+
console.log(` 📦 No related order`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Step 6: Individual item retrieval with aggregation
|
|
269
|
+
console.log('\n\nStep 6: Individual item retrieval with aggregation');
|
|
270
|
+
console.log('-------------------------------------------------');
|
|
271
|
+
|
|
272
|
+
const [, specificOrder] = await orderAggregator.get(order1.key);
|
|
273
|
+
if (specificOrder) {
|
|
274
|
+
console.log(`🔍 Retrieved specific order: ${specificOrder.id}`);
|
|
275
|
+
|
|
276
|
+
const populatedSpecificOrder = await orderAggregator.populate(specificOrder);
|
|
277
|
+
if (populatedSpecificOrder.aggs?.customer?.item) {
|
|
278
|
+
const customer = populatedSpecificOrder.aggs.customer.item;
|
|
279
|
+
console.log(` Automatically populated customer: ${customer.name}`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Step 7: Demonstrating cache efficiency with aggregation
|
|
284
|
+
console.log('\n\nStep 7: Cache efficiency demonstration');
|
|
285
|
+
console.log('-------------------------------------');
|
|
286
|
+
|
|
287
|
+
console.log('🎯 First population (will fetch from storage):');
|
|
288
|
+
const startTime1 = Date.now();
|
|
289
|
+
const populated1 = await orderAggregator.populate(order1);
|
|
290
|
+
const time1 = Date.now() - startTime1;
|
|
291
|
+
console.log(` Populated order with customer data in ${time1}ms`);
|
|
292
|
+
|
|
293
|
+
console.log('🎯 Second population (should use cached data):');
|
|
294
|
+
const startTime2 = Date.now();
|
|
295
|
+
const populated2 = await orderAggregator.populate(order1);
|
|
296
|
+
const time2 = Date.now() - startTime2;
|
|
297
|
+
console.log(` Populated same order in ${time2}ms (cached)`);
|
|
298
|
+
|
|
299
|
+
console.log(` 📊 Cache efficiency: ${Math.round(((time1 - time2) / time1) * 100)}% faster on second call`);
|
|
300
|
+
|
|
301
|
+
// Step 8: Aggregate management and statistics
|
|
302
|
+
console.log('\n\nStep 8: Aggregate management and statistics');
|
|
303
|
+
console.log('------------------------------------------');
|
|
304
|
+
|
|
305
|
+
console.log('📊 Cache Statistics:');
|
|
306
|
+
console.log(` 👥 Customers cached: ${mockCustomers.size}`);
|
|
307
|
+
console.log(` 📦 Orders cached: ${mockOrders.size}`);
|
|
308
|
+
console.log(` 📦 Products cached: ${mockProducts.size}`);
|
|
309
|
+
console.log(` 🎫 Tickets cached: ${mockTickets.size}`);
|
|
310
|
+
console.log(` 🔗 Order aggregator references: customer (required)`);
|
|
311
|
+
console.log(` 🔗 Ticket aggregator references: customer (required), order (optional)`);
|
|
312
|
+
|
|
313
|
+
console.log('\n🎉 Aggregator Example Complete!');
|
|
314
|
+
console.log('===============================\n');
|
|
315
|
+
|
|
316
|
+
console.log('Key concepts demonstrated:');
|
|
317
|
+
console.log('• Creating aggregated caches with entity relationships');
|
|
318
|
+
console.log('• Automatic population of referenced entities');
|
|
319
|
+
console.log('• Required vs optional aggregate references');
|
|
320
|
+
console.log('• Cache efficiency through aggregation');
|
|
321
|
+
console.log('• Complex business model relationships');
|
|
322
|
+
console.log('• Performance benefits of cached aggregates\n');
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
// Run the example if this file is executed directly
|
|
326
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
327
|
+
runAggregatorExample().catch(console.error);
|
|
328
|
+
}
|