@hiliosai/sdk 0.1.0 → 0.1.1

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.
@@ -1,293 +0,0 @@
1
- /**
2
- * Example of how to use the cache middleware in a Moleculer service
3
- * This file demonstrates various caching patterns and best practices
4
- */
5
-
6
- import type {AppContext} from '../types';
7
- import {CacheMiddleware} from '../middlewares/cache.middleware';
8
- import {REDIS_URL} from '../env';
9
-
10
- // Initialize cache on application start
11
- export function initializeCache() {
12
- CacheMiddleware.init({
13
- redisUrl: REDIS_URL, // Falls back to in-memory if not set
14
- ttl: 10 * 60 * 1000, // 10 minutes default TTL
15
- namespace: 'app', // Namespace for cache keys
16
- });
17
- }
18
-
19
- /**
20
- * Example 1: Simple get/set pattern
21
- */
22
- export async function getUserWithCache(ctx: AppContext, userId: string) {
23
- const cacheKey = `user:${userId}`;
24
-
25
- // Try to get from cache
26
- const cached = await ctx.cache.get(cacheKey);
27
- if (cached) {
28
- return cached;
29
- }
30
-
31
- // Cache miss - fetch from database
32
- const user = await fetchUserFromDatabase(userId);
33
-
34
- // Store in cache for 5 minutes
35
- await ctx.cache.set(cacheKey, user, 5 * 60 * 1000);
36
-
37
- return user;
38
- }
39
-
40
- /**
41
- * Example 2: Using the wrap pattern (recommended)
42
- * Automatically handles get/set logic
43
- */
44
- export async function getUserWithWrap(ctx: AppContext, userId: string) {
45
- return ctx.cache.wrap(
46
- `user:${userId}`,
47
- async () => {
48
- // This function only runs if cache miss
49
- return fetchUserFromDatabase(userId);
50
- },
51
- 5 * 60 * 1000 // TTL
52
- );
53
- }
54
-
55
- /**
56
- * Example 3: Caching complex calculations
57
- */
58
- export async function getUserPermissions(ctx: AppContext, userId: string) {
59
- return ctx.cache.wrap(
60
- `permissions:${userId}`,
61
- async () => {
62
- // Complex permission calculation
63
- const user = await fetchUserFromDatabase(userId);
64
- const roles = await fetchUserRoles(userId);
65
- const permissions = calculatePermissions(user, roles);
66
- return permissions;
67
- },
68
- 10 * 60 * 1000 // Cache for 10 minutes
69
- );
70
- }
71
-
72
- /**
73
- * Example 4: Batch operations
74
- */
75
- export async function getUsersBatch(ctx: AppContext, userIds: string[]) {
76
- // Generate cache keys
77
- const cacheKeys = userIds.map((id) => `user:${id}`);
78
-
79
- // Get all from cache at once
80
- const cachedUsers = await ctx.cache.mget(...cacheKeys);
81
-
82
- // Find missing users
83
- const result: Array<Record<string, unknown>> = [];
84
- const missingIds: string[] = [];
85
-
86
- cachedUsers.forEach((user, index) => {
87
- if (user !== undefined && user !== null) {
88
- result[index] = user as Record<string, unknown>;
89
- } else {
90
- const userId = userIds[index];
91
- if (userId !== undefined) {
92
- missingIds.push(userId);
93
- }
94
- }
95
- });
96
-
97
- // Fetch missing users from database
98
- if (missingIds.length > 0) {
99
- const missingUsers = await fetchUsersFromDatabase(missingIds);
100
-
101
- // Prepare batch cache update
102
- const cacheEntries: Record<string, unknown> = {};
103
- missingUsers.forEach((user) => {
104
- cacheEntries[`user:${user.id}`] = user;
105
- // Add to result
106
- const index = userIds.indexOf(user.id);
107
- if (index !== -1) {
108
- result[index] = user;
109
- }
110
- });
111
-
112
- // Set all at once
113
- await ctx.cache.mset(cacheEntries, 5 * 60 * 1000);
114
- }
115
-
116
- return result;
117
- }
118
-
119
- /**
120
- * Example 5: Cache invalidation after update
121
- */
122
- export async function updateUser(
123
- ctx: AppContext,
124
- userId: string,
125
- updates: Record<string, unknown>
126
- ) {
127
- // Update in database
128
- const updatedUser = await updateUserInDatabase(userId, updates);
129
-
130
- // Invalidate all related caches
131
- await ctx.cache.mdel(
132
- `user:${userId}`,
133
- `permissions:${userId}`,
134
- `profile:${userId}`
135
- );
136
-
137
- // Optionally, pre-populate cache with new data
138
- await ctx.cache.set(`user:${userId}`, updatedUser, 5 * 60 * 1000);
139
-
140
- return updatedUser;
141
- }
142
-
143
- /**
144
- * Example 6: Check if key exists
145
- */
146
- export async function isUserCached(ctx: AppContext, userId: string) {
147
- return ctx.cache.has(`user:${userId}`);
148
- }
149
-
150
- /**
151
- * Example 7: Clear entire cache namespace
152
- */
153
- export async function clearAllCache(ctx: AppContext) {
154
- await ctx.cache.clear();
155
- }
156
-
157
- /**
158
- * Example 8: Cache with dynamic TTL based on data
159
- */
160
- interface CacheableData {
161
- type: 'static' | 'volatile' | 'normal';
162
- [key: string]: unknown;
163
- }
164
-
165
- export async function cacheWithDynamicTTL(
166
- ctx: AppContext,
167
- key: string,
168
- data: CacheableData
169
- ) {
170
- // Determine TTL based on data characteristics
171
- let ttl = 5 * 60 * 1000; // 5 minutes default
172
-
173
- if (data.type === 'static') {
174
- ttl = 60 * 60 * 1000; // 1 hour for static data
175
- } else if (data.type === 'volatile') {
176
- ttl = 60 * 1000; // 1 minute for volatile data
177
- }
178
-
179
- await ctx.cache.set(key, data, ttl);
180
- }
181
-
182
- /**
183
- * Example 9: Cache status check
184
- */
185
- export async function checkCacheHealth(ctx: AppContext) {
186
- const testKey = 'health:check';
187
- const testValue = {timestamp: Date.now(), random: Math.random()};
188
-
189
- try {
190
- // Test set
191
- const setResult = await ctx.cache.set(testKey, testValue, 1000);
192
-
193
- // Test get
194
- const getResult = await ctx.cache.get(testKey);
195
-
196
- // Test has
197
- const hasResult = await ctx.cache.has(testKey);
198
-
199
- // Test delete
200
- const deleteResult = await ctx.cache.delete(testKey);
201
-
202
- return {
203
- healthy: true,
204
- type: REDIS_URL ? 'Redis' : 'In-Memory',
205
- operations: {
206
- set: setResult,
207
- get: JSON.stringify(getResult) === JSON.stringify(testValue),
208
- has: hasResult,
209
- delete: deleteResult,
210
- },
211
- };
212
- } catch (error) {
213
- return {
214
- healthy: false,
215
- error: error instanceof Error ? error.message : 'Unknown error',
216
- };
217
- }
218
- }
219
-
220
- // Mock database functions for example purposes
221
- async function fetchUserFromDatabase(userId: string) {
222
- return {
223
- id: userId,
224
- name: `User ${userId}`,
225
- email: `user${userId}@example.com`,
226
- createdAt: new Date(),
227
- };
228
- }
229
-
230
- async function fetchUsersFromDatabase(userIds: string[]) {
231
- return userIds.map((id) => ({
232
- id,
233
- name: `User ${id}`,
234
- email: `user${id}@example.com`,
235
- createdAt: new Date(),
236
- }));
237
- }
238
-
239
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
240
- async function fetchUserRoles(_userId: string) {
241
- return ['user', 'member'];
242
- }
243
-
244
- interface User {
245
- id: string;
246
- name: string;
247
- email: string;
248
- createdAt: Date;
249
- }
250
-
251
- function calculatePermissions(_user: User, roles: string[]) {
252
- return {
253
- canRead: true,
254
- canWrite: roles.includes('admin'),
255
- canDelete: roles.includes('admin'),
256
- };
257
- }
258
-
259
- async function updateUserInDatabase(
260
- userId: string,
261
- updates: Record<string, unknown>
262
- ) {
263
- return {
264
- id: userId,
265
- ...updates,
266
- updatedAt: new Date(),
267
- };
268
- }
269
-
270
- /**
271
- * Usage in a Moleculer service:
272
- *
273
- * export default {
274
- * name: 'users',
275
- * middlewares: [CacheMiddleware],
276
- *
277
- * started() {
278
- * initializeCache();
279
- * },
280
- *
281
- * stopped() {
282
- * return CacheMiddleware.stopped();
283
- * },
284
- *
285
- * actions: {
286
- * get: {
287
- * async handler(ctx) {
288
- * return getUserWithWrap(ctx, ctx.params.id);
289
- * }
290
- * }
291
- * }
292
- * }
293
- */
@@ -1,278 +0,0 @@
1
- import KeyvRedis from '@keyv/redis';
2
- import Keyv from 'keyv';
3
- import {REDIS_URL} from '../env';
4
- import type {AppContext} from '../types';
5
-
6
- export interface CacheOptions {
7
- redisUrl?: string;
8
- ttl?: number; // Default TTL in milliseconds
9
- namespace?: string;
10
- serialize?: (value: unknown) => string;
11
- deserialize?: (value: string) => unknown;
12
- }
13
-
14
- export interface CacheHelpers {
15
- get<T = unknown>(key: string): Promise<T | undefined>;
16
- set<T = unknown>(key: string, value: T, ttl?: number): Promise<boolean>;
17
- delete(key: string): Promise<boolean>;
18
- clear(): Promise<void>;
19
- has(key: string): Promise<boolean>;
20
- mget<T = unknown>(...keys: string[]): Promise<Array<T | undefined>>;
21
- mset(entries: Record<string, unknown>, ttl?: number): Promise<boolean>;
22
- mdel(...keys: string[]): Promise<boolean>;
23
- wrap<T = unknown>(
24
- key: string,
25
- factory: () => Promise<T> | T,
26
- ttl?: number
27
- ): Promise<T>;
28
- }
29
-
30
- export class CacheService {
31
- private keyv: Keyv;
32
- private defaultTtl: number;
33
-
34
- constructor(options: CacheOptions = {}) {
35
- const {
36
- redisUrl = REDIS_URL,
37
- ttl = 5 * 60 * 1000, // 5 minutes default
38
- namespace = 'cache',
39
- } = options;
40
-
41
- this.defaultTtl = ttl;
42
-
43
- // Configure store based on Redis availability
44
- const store = redisUrl ? new KeyvRedis(redisUrl) : new Map(); // In-memory fallback
45
-
46
- this.keyv = new Keyv({
47
- store,
48
- namespace,
49
- ttl,
50
- });
51
-
52
- // Error handling
53
- this.keyv.on('error', () => {
54
- // Silently handle errors - could be logged to a logger if available
55
- });
56
- }
57
-
58
- /**
59
- * Get a value from cache
60
- */
61
- async get<T = unknown>(key: string): Promise<T | undefined> {
62
- try {
63
- return await this.keyv.get<T>(key);
64
- } catch {
65
- return undefined;
66
- }
67
- }
68
-
69
- /**
70
- * Set a value in cache
71
- */
72
- async set<T = unknown>(
73
- key: string,
74
- value: T,
75
- ttl?: number
76
- ): Promise<boolean> {
77
- try {
78
- return await this.keyv.set(key, value, ttl);
79
- } catch {
80
- return false;
81
- }
82
- }
83
-
84
- /**
85
- * Delete a key from cache
86
- */
87
- async delete(key: string): Promise<boolean> {
88
- try {
89
- return await this.keyv.delete(key);
90
- } catch {
91
- return false;
92
- }
93
- }
94
-
95
- /**
96
- * Clear all keys with the namespace
97
- */
98
- async clear(): Promise<void> {
99
- try {
100
- await this.keyv.clear();
101
- } catch {
102
- // Silently handle errors
103
- }
104
- }
105
-
106
- /**
107
- * Check if a key exists
108
- */
109
- async has(key: string): Promise<boolean> {
110
- try {
111
- return await this.keyv.has(key);
112
- } catch {
113
- return false;
114
- }
115
- }
116
-
117
- /**
118
- * Get multiple values at once
119
- */
120
- async mget<T = unknown>(...keys: string[]): Promise<Array<T | undefined>> {
121
- try {
122
- const promises = keys.map((key) => this.get<T>(key));
123
- return await Promise.all(promises);
124
- } catch {
125
- return keys.map(() => undefined);
126
- }
127
- }
128
-
129
- /**
130
- * Set multiple values at once
131
- */
132
- async mset(entries: Record<string, unknown>, ttl?: number): Promise<boolean> {
133
- try {
134
- const promises = Object.entries(entries).map(([key, value]) =>
135
- this.set(key, value, ttl)
136
- );
137
- const results = await Promise.all(promises);
138
- return results.every(Boolean);
139
- } catch {
140
- return false;
141
- }
142
- }
143
-
144
- /**
145
- * Delete multiple keys at once
146
- */
147
- async mdel(...keys: string[]): Promise<boolean> {
148
- try {
149
- const promises = keys.map((key) => this.delete(key));
150
- const results = await Promise.all(promises);
151
- return results.every(Boolean);
152
- } catch {
153
- return false;
154
- }
155
- }
156
-
157
- /**
158
- * Wrap pattern: Get from cache or calculate and cache
159
- */
160
- async wrap<T = unknown>(
161
- key: string,
162
- factory: () => Promise<T> | T,
163
- ttl?: number
164
- ): Promise<T> {
165
- try {
166
- // Try to get from cache
167
- const cached = await this.get<T>(key);
168
- if (cached !== undefined) {
169
- return cached;
170
- }
171
-
172
- // Calculate value
173
- const value = await factory();
174
-
175
- // Cache the value
176
- await this.set(key, value, ttl ?? this.defaultTtl);
177
-
178
- return value;
179
- } catch {
180
- // In case of error, still try to return the factory value
181
- return await factory();
182
- }
183
- }
184
-
185
- /**
186
- * Disconnect the cache (for cleanup)
187
- */
188
- async disconnect(): Promise<void> {
189
- try {
190
- await this.keyv.disconnect();
191
- } catch {
192
- // Silently handle errors
193
- }
194
- }
195
- }
196
-
197
- /**
198
- * Create cache middleware with specific options for a service
199
- */
200
- export function createCacheMiddleware(options: CacheOptions = {}) {
201
- let cacheInstance: CacheService | null = null;
202
-
203
- return {
204
- /**
205
- * Initialize cache on service start
206
- */
207
- started() {
208
- cacheInstance = new CacheService(options);
209
- },
210
-
211
- /**
212
- * Add cache helpers to context
213
- */
214
- localAction(handler: (...args: unknown[]) => unknown) {
215
- return function CacheMiddlewareWrapper(this: unknown, ctx: AppContext) {
216
- cacheInstance ??= new CacheService(options);
217
-
218
- const instance = cacheInstance;
219
-
220
- // Create scoped cache helpers
221
- const cache: CacheHelpers = {
222
- get: <T = unknown>(key: string) => instance.get<T>(key),
223
-
224
- set: <T = unknown>(key: string, value: T, ttl?: number) =>
225
- instance.set(key, value, ttl),
226
-
227
- delete: (key: string) => instance.delete(key),
228
-
229
- clear: () => instance.clear(),
230
-
231
- has: (key: string) => instance.has(key),
232
-
233
- mget: <T = unknown>(...keys: string[]) => instance.mget<T>(...keys),
234
-
235
- mset: (entries: Record<string, unknown>, ttl?: number) =>
236
- instance.mset(entries, ttl),
237
-
238
- mdel: (...keys: string[]) => instance.mdel(...keys),
239
-
240
- wrap: <T = unknown>(
241
- key: string,
242
- factory: () => Promise<T> | T,
243
- ttl?: number
244
- ) => instance.wrap<T>(key, factory, ttl),
245
- };
246
-
247
- // Add cache to context
248
- ctx.cache = cache;
249
-
250
- // Call the original handler
251
- return handler.call(this, ctx);
252
- };
253
- },
254
-
255
- /**
256
- * Cleanup on service stop
257
- */
258
- stopped() {
259
- if (cacheInstance) {
260
- return cacheInstance.disconnect();
261
- }
262
- },
263
- };
264
- }
265
-
266
- // Default cache middleware (backwards compatibility)
267
- export const CacheMiddleware = createCacheMiddleware();
268
-
269
- // Singleton instance for direct usage
270
- let globalCacheInstance: CacheService | null = null;
271
-
272
- export const getCacheInstance = (options?: CacheOptions) => {
273
- if (options) {
274
- return new CacheService(options);
275
- }
276
- globalCacheInstance ??= new CacheService();
277
- return globalCacheInstance;
278
- };