bunsane 0.2.2 → 0.2.4

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/README.md CHANGED
@@ -21,7 +21,7 @@
21
21
  - Zod-friendly GraphQL error helper
22
22
 
23
23
 
24
- Full documentation visit: [Documentation](https://yaaruu.github.io/bunsane/#/)
24
+ Full documentation visit: [Documentation](https://yaaruu.github.io/bunsane-docs/#/)
25
25
 
26
26
 
27
27
  ---
@@ -15,6 +15,8 @@ export interface CacheConfig {
15
15
  db?: number;
16
16
  keyPrefix?: string;
17
17
  retryStrategy?: (times: number) => number | void;
18
+ connectTimeout?: number;
19
+ commandTimeout?: number;
18
20
  };
19
21
 
20
22
  entity?: {
@@ -66,7 +66,9 @@ export class CacheFactory {
66
66
  retryStrategy: config.redis.retryStrategy,
67
67
  maxRetriesPerRequest: 3,
68
68
  lazyConnect: false,
69
- enableReadyCheck: true
69
+ enableReadyCheck: true,
70
+ connectTimeout: config.redis.connectTimeout,
71
+ commandTimeout: config.redis.commandTimeout
70
72
  };
71
73
 
72
74
  const { password: _pw, ...safeConfig } = redisConfig;
@@ -1,5 +1,7 @@
1
1
  import { CacheManager } from './CacheManager.js';
2
2
  import { SchedulerManager } from '../SchedulerManager.js';
3
+ import { Entity } from '../Entity.js';
4
+ import { logger } from '../Logger.js';
3
5
 
4
6
  /**
5
7
  * CacheWarmer preloads frequently accessed data into the cache to improve
@@ -34,7 +36,7 @@ export class CacheWarmer {
34
36
  let warmed = 0;
35
37
  let failed = 0;
36
38
 
37
- console.log(`Starting entity cache warming for ${entityIds.length} ${entityType} entities`);
39
+ logger.info({ msg: `Starting entity cache warming`, count: entityIds.length, entityType });
38
40
 
39
41
  // Process entities in batches to avoid overwhelming the database
40
42
  const batchSize = 10;
@@ -46,7 +48,7 @@ export class CacheWarmer {
46
48
  const entities = await this.loadEntitiesBatch(batch, entityType);
47
49
  warmed += entities.length;
48
50
  } catch (error) {
49
- console.warn(`Failed to warm batch of entities:`, error);
51
+ logger.warn({ msg: 'Failed to warm batch of entities', error });
50
52
  failed += batch.length;
51
53
  }
52
54
 
@@ -55,7 +57,7 @@ export class CacheWarmer {
55
57
  }
56
58
 
57
59
  const duration = Date.now() - startTime;
58
- console.log(`Entity cache warming completed: ${warmed} warmed, ${failed} failed in ${duration}ms`);
60
+ logger.info({ msg: 'Entity cache warming completed', warmed, failed, duration });
59
61
 
60
62
  return { success: failed === 0, warmed, failed, duration };
61
63
  }
@@ -71,7 +73,7 @@ export class CacheWarmer {
71
73
  enabled?: boolean;
72
74
  }): void {
73
75
  if (!config.enabled) {
74
- console.log(`Cache warming job "${config.name}" is disabled`);
76
+ logger.debug({ msg: 'Cache warming job disabled', name: config.name });
75
77
  return;
76
78
  }
77
79
 
@@ -80,18 +82,18 @@ export class CacheWarmer {
80
82
 
81
83
  const job = this.scheduler.scheduleJob(config.name, config.cronExpression, async () => {
82
84
  try {
83
- console.log(`Running scheduled cache warming: ${config.name}`);
85
+ logger.info({ msg: 'Running scheduled cache warming', name: config.name });
84
86
 
85
87
  if (config.type === 'entity') {
86
88
  await this.warmEntityCache(config.config.entityIds, config.config.entityType);
87
89
  }
88
90
  } catch (error) {
89
- console.error(`Scheduled cache warming failed for "${config.name}":`, error);
91
+ logger.error({ msg: 'Scheduled cache warming failed', name: config.name, error });
90
92
  }
91
93
  });
92
94
 
93
95
  this.warmingJobs.set(config.name, job);
94
- console.log(`Scheduled cache warming job "${config.name}" with cron: ${config.cronExpression}`);
96
+ logger.info({ msg: 'Scheduled cache warming job', name: config.name, cron: config.cronExpression });
95
97
  }
96
98
 
97
99
  /**
@@ -102,7 +104,7 @@ export class CacheWarmer {
102
104
  if (job) {
103
105
  job.cancel();
104
106
  this.warmingJobs.delete(name);
105
- console.log(`Cancelled cache warming job: ${name}`);
107
+ logger.info({ msg: 'Cancelled cache warming job', name });
106
108
  return true;
107
109
  }
108
110
  return false;
@@ -126,11 +128,17 @@ export class CacheWarmer {
126
128
  }> {
127
129
  const startTime = Date.now();
128
130
 
129
- // Warm entities
130
- const firstEntity = config.entities?.[0];
131
- const entityResults = firstEntity
132
- ? await this.warmEntityCache(firstEntity.entityIds, firstEntity.entityType)
133
- : { success: true, warmed: 0, failed: 0, duration: 0 };
131
+ // Warm all entity groups
132
+ let entityResults = { success: true, warmed: 0, failed: 0, duration: 0 };
133
+ if (config.entities) {
134
+ for (const entry of config.entities) {
135
+ const result = await this.warmEntityCache(entry.entityIds, entry.entityType);
136
+ entityResults.warmed += result.warmed;
137
+ entityResults.failed += result.failed;
138
+ entityResults.duration += result.duration;
139
+ if (!result.success) entityResults.success = false;
140
+ }
141
+ }
134
142
 
135
143
  const totalDuration = Date.now() - startTime;
136
144
 
@@ -141,17 +149,31 @@ export class CacheWarmer {
141
149
  }
142
150
 
143
151
  /**
144
- * Loads a batch of entities (placeholder - would need actual entity loading logic)
152
+ * Loads a batch of entities from the database and populates the cache.
153
+ * Uses Entity.FindById to load each entity with all its components,
154
+ * then writes the entity and its components into cache via CacheManager.
145
155
  */
146
- private async loadEntitiesBatch(entityIds: string[], entityType: string): Promise<any[]> {
147
- // This is a placeholder - in a real implementation, this would load entities
148
- // from the database using the appropriate entity manager or query system
149
- console.log(`Loading batch of ${entityIds.length} ${entityType} entities: ${entityIds.slice(0, 3).join(', ')}...`);
150
-
151
- // Simulate loading delay
152
- await new Promise(resolve => setTimeout(resolve, 10));
156
+ private async loadEntitiesBatch(entityIds: string[], entityType: string): Promise<Entity[]> {
157
+ const loaded: Entity[] = [];
158
+
159
+ const results = await Promise.allSettled(
160
+ entityIds.map(id => Entity.FindById(id))
161
+ );
162
+
163
+ for (const result of results) {
164
+ if (result.status === 'fulfilled' && result.value) {
165
+ const entity = result.value;
166
+ loaded.push(entity);
167
+
168
+ await this.cacheManager.setEntityWriteThrough(entity);
169
+ const components = entity.componentList();
170
+ if (components.length > 0) {
171
+ await this.cacheManager.setComponentWriteThrough(entity.id, components);
172
+ }
173
+ }
174
+ }
153
175
 
154
- // Return mock entities - in real implementation, this would be actual entity data
155
- return entityIds.map(id => ({ id, type: entityType, loaded: true }));
176
+ logger.debug({ msg: 'Loaded entity batch', entityType, requested: entityIds.length, loaded: loaded.length });
177
+ return loaded;
156
178
  }
157
179
  }
@@ -27,6 +27,8 @@ export interface RedisCacheConfig {
27
27
  maxRetriesPerRequest?: number;
28
28
  lazyConnect?: boolean;
29
29
  enableReadyCheck?: boolean;
30
+ connectTimeout?: number;
31
+ commandTimeout?: number;
30
32
  }
31
33
 
32
34
  /**
@@ -60,7 +62,8 @@ export class RedisCache implements CacheProvider {
60
62
  maxRetriesPerRequest: config.maxRetriesPerRequest || 3,
61
63
  lazyConnect: config.lazyConnect || false,
62
64
  enableReadyCheck: config.enableReadyCheck || false,
63
- // Connection pooling settings
65
+ connectTimeout: config.connectTimeout ?? 5000,
66
+ commandTimeout: config.commandTimeout ?? 3000,
64
67
  enableOfflineQueue: true,
65
68
  };
66
69
 
@@ -194,19 +197,20 @@ export class RedisCache implements CacheProvider {
194
197
  const prefixedKeys = keys.map(k => this.prefixKey(k));
195
198
  const values = await this.client.mget(...prefixedKeys);
196
199
 
197
- return values.map((value, index) => {
200
+ return await Promise.all(values.map(async (value, index) => {
198
201
  if (value === null) {
199
202
  this.stats.misses++;
200
203
  return null;
201
204
  }
202
205
  this.stats.hits++;
203
206
  try {
204
- return JSON.parse(value) as T;
207
+ const parsed = JSON.parse(value);
208
+ return await CompressionUtils.decompress(parsed) as T;
205
209
  } catch (parseError) {
206
210
  logger.error({ error: parseError, key: keys[index], msg: 'Failed to parse cached value' });
207
211
  return null;
208
212
  }
209
- });
213
+ }));
210
214
  } catch (error) {
211
215
  logger.error({ error, msg: 'Redis getMany error' });
212
216
  return new Array(keys.length).fill(null);
@@ -222,7 +226,8 @@ export class RedisCache implements CacheProvider {
222
226
 
223
227
  for (const entry of entries) {
224
228
  const prefixedKey = this.prefixKey(entry.key);
225
- const serializedValue = JSON.stringify(entry.value);
229
+ const compressedValue = await CompressionUtils.compress(entry.value);
230
+ const serializedValue = JSON.stringify(compressedValue);
226
231
 
227
232
  if (entry.ttl) {
228
233
  pipeline.setex(prefixedKey, Math.floor(entry.ttl / 1000), serializedValue);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bunsane",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "author": {
5
5
  "name": "yaaruu"
6
6
  },