@objectstack/service-cache 4.0.3 → 4.0.5

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 ADDED
@@ -0,0 +1,294 @@
1
+ # @objectstack/service-cache
2
+
3
+ Cache Service for ObjectStack — implements `ICacheService` with in-memory and Redis adapters.
4
+
5
+ ## Features
6
+
7
+ - **Multiple Adapters**: In-memory (development) and Redis (production) support
8
+ - **Type-Safe**: Full TypeScript support with generic value types
9
+ - **TTL Support**: Automatic expiration with time-to-live
10
+ - **Namespace Support**: Organize cache keys by namespace
11
+ - **Pattern Matching**: Delete keys by pattern (e.g., `user:*`)
12
+ - **Statistics**: Track hit/miss rates and memory usage
13
+ - **JSON Serialization**: Automatic serialization of complex objects
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ pnpm add @objectstack/service-cache
19
+ ```
20
+
21
+ For Redis adapter:
22
+ ```bash
23
+ pnpm add ioredis
24
+ ```
25
+
26
+ ## Basic Usage
27
+
28
+ ```typescript
29
+ import { defineStack } from '@objectstack/spec';
30
+ import { ServiceCache } from '@objectstack/service-cache';
31
+
32
+ const stack = defineStack({
33
+ services: [
34
+ ServiceCache.configure({
35
+ adapter: 'memory', // or 'redis'
36
+ defaultTTL: 300, // 5 minutes
37
+ }),
38
+ ],
39
+ });
40
+ ```
41
+
42
+ ## Configuration
43
+
44
+ ### In-Memory Adapter (Development)
45
+
46
+ ```typescript
47
+ ServiceCache.configure({
48
+ adapter: 'memory',
49
+ defaultTTL: 300,
50
+ maxSize: 1000, // Maximum number of entries
51
+ });
52
+ ```
53
+
54
+ ### Redis Adapter (Production)
55
+
56
+ ```typescript
57
+ ServiceCache.configure({
58
+ adapter: 'redis',
59
+ redis: {
60
+ host: 'localhost',
61
+ port: 6379,
62
+ password: process.env.REDIS_PASSWORD,
63
+ db: 0,
64
+ },
65
+ defaultTTL: 600,
66
+ });
67
+ ```
68
+
69
+ ## Service API
70
+
71
+ ```typescript
72
+ // Get cache service
73
+ const cache = kernel.getService<ICacheService>('cache');
74
+ ```
75
+
76
+ ### Set/Get Operations
77
+
78
+ ```typescript
79
+ // Set a value
80
+ await cache.set('user:123', { name: 'John', email: 'john@example.com' });
81
+
82
+ // Set with custom TTL (in seconds)
83
+ await cache.set('session:abc', sessionData, { ttl: 3600 }); // 1 hour
84
+
85
+ // Get a value
86
+ const user = await cache.get('user:123');
87
+
88
+ // Get with type safety
89
+ const user = await cache.get<User>('user:123');
90
+
91
+ // Get multiple keys
92
+ const users = await cache.mget(['user:123', 'user:456']);
93
+ ```
94
+
95
+ ### Existence & Deletion
96
+
97
+ ```typescript
98
+ // Check if key exists
99
+ const exists = await cache.has('user:123');
100
+
101
+ // Delete a key
102
+ await cache.del('user:123');
103
+
104
+ // Delete multiple keys
105
+ await cache.del(['session:abc', 'session:def']);
106
+
107
+ // Delete by pattern
108
+ await cache.delPattern('user:*');
109
+ ```
110
+
111
+ ### Namespaced Operations
112
+
113
+ ```typescript
114
+ // Create a namespaced cache instance
115
+ const userCache = cache.namespace('user');
116
+
117
+ // Set in namespace (key becomes 'user:123')
118
+ await userCache.set('123', userData);
119
+
120
+ // Get from namespace
121
+ const user = await userCache.get('123');
122
+
123
+ // Clear entire namespace
124
+ await userCache.clear();
125
+ ```
126
+
127
+ ### TTL Management
128
+
129
+ ```typescript
130
+ // Get remaining TTL (in seconds)
131
+ const ttl = await cache.ttl('session:abc');
132
+
133
+ // Update TTL
134
+ await cache.expire('session:abc', 7200); // 2 hours
135
+
136
+ // Make key permanent (remove expiration)
137
+ await cache.persist('user:123');
138
+ ```
139
+
140
+ ### Atomic Operations
141
+
142
+ ```typescript
143
+ // Increment (useful for counters)
144
+ await cache.incr('page:views:123'); // Returns new value
145
+
146
+ // Increment by amount
147
+ await cache.incrby('score:user:123', 10);
148
+
149
+ // Decrement
150
+ await cache.decr('inventory:product:456');
151
+ ```
152
+
153
+ ### Batch Operations
154
+
155
+ ```typescript
156
+ // Set multiple keys at once
157
+ await cache.mset({
158
+ 'user:123': user1Data,
159
+ 'user:456': user2Data,
160
+ 'user:789': user3Data,
161
+ });
162
+
163
+ // Get multiple keys
164
+ const users = await cache.mget(['user:123', 'user:456', 'user:789']);
165
+ ```
166
+
167
+ ## Advanced Features
168
+
169
+ ### Cache Aside Pattern
170
+
171
+ ```typescript
172
+ async function getUser(id: string): Promise<User> {
173
+ // Try cache first
174
+ const cached = await cache.get<User>(`user:${id}`);
175
+ if (cached) return cached;
176
+
177
+ // Load from database
178
+ const user = await db.findUser(id);
179
+
180
+ // Store in cache
181
+ await cache.set(`user:${id}`, user, { ttl: 600 });
182
+
183
+ return user;
184
+ }
185
+ ```
186
+
187
+ ### Cache-Through Pattern
188
+
189
+ ```typescript
190
+ async function getUserCacheThrough(id: string): Promise<User> {
191
+ return cache.getOrSet(`user:${id}`, async () => {
192
+ return await db.findUser(id);
193
+ }, { ttl: 600 });
194
+ }
195
+ ```
196
+
197
+ ### Invalidation on Write
198
+
199
+ ```typescript
200
+ async function updateUser(id: string, data: Partial<User>) {
201
+ // Update database
202
+ await db.updateUser(id, data);
203
+
204
+ // Invalidate cache
205
+ await cache.del(`user:${id}`);
206
+
207
+ // Or update cache immediately
208
+ const updated = await db.findUser(id);
209
+ await cache.set(`user:${id}`, updated);
210
+ }
211
+ ```
212
+
213
+ ### Tagging & Invalidation
214
+
215
+ ```typescript
216
+ // Tag cache entries
217
+ await cache.set('product:123', productData, {
218
+ ttl: 600,
219
+ tags: ['products', 'category:electronics'],
220
+ });
221
+
222
+ // Invalidate by tag
223
+ await cache.invalidateTag('category:electronics');
224
+ ```
225
+
226
+ ## Statistics & Monitoring
227
+
228
+ ```typescript
229
+ // Get cache statistics
230
+ const stats = await cache.stats();
231
+ // {
232
+ // hits: 1250,
233
+ // misses: 325,
234
+ // hitRate: 0.794,
235
+ // keys: 450,
236
+ // memoryUsage: 1024000 // bytes
237
+ // }
238
+
239
+ // Reset statistics
240
+ await cache.resetStats();
241
+ ```
242
+
243
+ ## REST API Endpoints
244
+
245
+ ```
246
+ GET /api/v1/cache/stats # Get cache statistics
247
+ POST /api/v1/cache/clear # Clear cache
248
+ DELETE /api/v1/cache/:key # Delete specific key
249
+ DELETE /api/v1/cache/pattern/:pattern # Delete by pattern
250
+ ```
251
+
252
+ ## Best Practices
253
+
254
+ 1. **Use Namespaces**: Organize cache keys with namespaces
255
+ 2. **Set Appropriate TTLs**: Don't cache data longer than necessary
256
+ 3. **Handle Misses**: Always have fallback logic when cache misses
257
+ 4. **Invalidate on Write**: Clear stale cache after updates
258
+ 5. **Monitor Hit Rates**: Track cache effectiveness with statistics
259
+ 6. **Serialize Carefully**: Be mindful of what you serialize (avoid circular references)
260
+ 7. **Use Redis in Production**: In-memory adapter is for development only
261
+
262
+ ## Performance Considerations
263
+
264
+ - **In-Memory Adapter**: Fast but limited by server memory, not shared across instances
265
+ - **Redis Adapter**: Shared across instances, persistent, but network latency
266
+ - **TTL Strategy**: Balance between freshness and cache hit rate
267
+ - **Key Patterns**: Use consistent naming conventions for easier invalidation
268
+
269
+ ## Contract Implementation
270
+
271
+ Implements `ICacheService` from `@objectstack/spec/contracts`:
272
+
273
+ ```typescript
274
+ interface ICacheService {
275
+ get<T>(key: string): Promise<T | null>;
276
+ set<T>(key: string, value: T, options?: CacheOptions): Promise<void>;
277
+ del(key: string | string[]): Promise<void>;
278
+ has(key: string): Promise<boolean>;
279
+ ttl(key: string): Promise<number>;
280
+ expire(key: string, ttl: number): Promise<void>;
281
+ clear(): Promise<void>;
282
+ namespace(name: string): ICacheService;
283
+ }
284
+ ```
285
+
286
+ ## License
287
+
288
+ Apache-2.0
289
+
290
+ ## See Also
291
+
292
+ - [Redis Documentation](https://redis.io/documentation)
293
+ - [@objectstack/spec/contracts](../../spec/src/contracts/)
294
+ - [Caching Best Practices](/content/docs/guides/caching/)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@objectstack/service-cache",
3
- "version": "4.0.3",
3
+ "version": "4.0.5",
4
4
  "license": "Apache-2.0",
5
5
  "description": "Cache Service for ObjectStack — implements ICacheService with in-memory and Redis adapters",
6
6
  "type": "module",
@@ -14,13 +14,37 @@
14
14
  }
15
15
  },
16
16
  "dependencies": {
17
- "@objectstack/core": "4.0.3",
18
- "@objectstack/spec": "4.0.3"
17
+ "@objectstack/core": "4.0.5",
18
+ "@objectstack/spec": "4.0.5"
19
19
  },
20
20
  "devDependencies": {
21
- "@types/node": "^25.6.0",
22
- "typescript": "^6.0.2",
23
- "vitest": "^4.1.4"
21
+ "@types/node": "^25.6.2",
22
+ "typescript": "^6.0.3",
23
+ "vitest": "^4.1.5"
24
+ },
25
+ "keywords": [
26
+ "objectstack",
27
+ "service",
28
+ "cache",
29
+ "redis"
30
+ ],
31
+ "author": "ObjectStack",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "https://github.com/objectstack-ai/framework.git",
35
+ "directory": "packages/services/service-cache"
36
+ },
37
+ "homepage": "https://objectstack.ai/docs",
38
+ "bugs": "https://github.com/objectstack-ai/framework/issues",
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "files": [
43
+ "dist",
44
+ "README.md"
45
+ ],
46
+ "engines": {
47
+ "node": ">=18.0.0"
24
48
  },
25
49
  "scripts": {
26
50
  "build": "tsup --config ../../../tsup.config.ts",
@@ -1,22 +0,0 @@
1
-
2
- > @objectstack/service-cache@4.0.3 build /home/runner/work/framework/framework/packages/services/service-cache
3
- > tsup --config ../../../tsup.config.ts
4
-
5
- CLI Building entry: src/index.ts
6
- CLI Using tsconfig: tsconfig.json
7
- CLI tsup v8.5.1
8
- CLI Using tsup config: /home/runner/work/framework/framework/tsup.config.ts
9
- CLI Target: es2020
10
- CLI Cleaning output folder
11
- ESM Build start
12
- CJS Build start
13
- CJS dist/index.cjs 4.09 KB
14
- CJS dist/index.cjs.map 9.50 KB
15
- CJS ⚡️ Build success in 94ms
16
- ESM dist/index.js 2.98 KB
17
- ESM dist/index.js.map 8.94 KB
18
- ESM ⚡️ Build success in 99ms
19
- DTS Build start
20
- DTS ⚡️ Build success in 12340ms
21
- DTS dist/index.d.ts 3.56 KB
22
- DTS dist/index.d.cts 3.56 KB
package/CHANGELOG.md DELETED
@@ -1,169 +0,0 @@
1
- # @objectstack/service-cache
2
-
3
- ## 4.0.3
4
-
5
- ### Patch Changes
6
-
7
- - @objectstack/spec@4.0.3
8
- - @objectstack/core@4.0.3
9
-
10
- ## 4.0.2
11
-
12
- ### Patch Changes
13
-
14
- - Updated dependencies [5f659e9]
15
- - @objectstack/spec@4.0.2
16
- - @objectstack/core@4.0.2
17
-
18
- ## 4.0.0
19
-
20
- ### Patch Changes
21
-
22
- - Updated dependencies [f08ffc3]
23
- - Updated dependencies [e0b0a78]
24
- - @objectstack/spec@4.0.0
25
- - @objectstack/core@4.0.0
26
-
27
- ## 3.3.1
28
-
29
- ### Patch Changes
30
-
31
- - @objectstack/spec@3.3.1
32
- - @objectstack/core@3.3.1
33
-
34
- ## 3.3.0
35
-
36
- ### Patch Changes
37
-
38
- - @objectstack/spec@3.3.0
39
- - @objectstack/core@3.3.0
40
-
41
- ## 3.2.9
42
-
43
- ### Patch Changes
44
-
45
- - @objectstack/spec@3.2.9
46
- - @objectstack/core@3.2.9
47
-
48
- ## 3.2.8
49
-
50
- ### Patch Changes
51
-
52
- - @objectstack/spec@3.2.8
53
- - @objectstack/core@3.2.8
54
-
55
- ## 3.2.7
56
-
57
- ### Patch Changes
58
-
59
- - @objectstack/spec@3.2.7
60
- - @objectstack/core@3.2.7
61
-
62
- ## 3.2.6
63
-
64
- ### Patch Changes
65
-
66
- - @objectstack/spec@3.2.6
67
- - @objectstack/core@3.2.6
68
-
69
- ## 3.2.5
70
-
71
- ### Patch Changes
72
-
73
- - @objectstack/spec@3.2.5
74
- - @objectstack/core@3.2.5
75
-
76
- ## 3.2.4
77
-
78
- ### Patch Changes
79
-
80
- - @objectstack/spec@3.2.4
81
- - @objectstack/core@3.2.4
82
-
83
- ## 3.2.3
84
-
85
- ### Patch Changes
86
-
87
- - @objectstack/spec@3.2.3
88
- - @objectstack/core@3.2.3
89
-
90
- ## 3.2.2
91
-
92
- ### Patch Changes
93
-
94
- - Updated dependencies [46defbb]
95
- - @objectstack/spec@3.2.2
96
- - @objectstack/core@3.2.2
97
-
98
- ## 3.2.1
99
-
100
- ### Patch Changes
101
-
102
- - Updated dependencies [850b546]
103
- - @objectstack/spec@3.2.1
104
- - @objectstack/core@3.2.1
105
-
106
- ## 3.2.0
107
-
108
- ### Patch Changes
109
-
110
- - Updated dependencies [5901c29]
111
- - @objectstack/spec@3.2.0
112
- - @objectstack/core@3.2.0
113
-
114
- ## 3.1.1
115
-
116
- ### Patch Changes
117
-
118
- - Updated dependencies [953d667]
119
- - @objectstack/spec@3.1.1
120
- - @objectstack/core@3.1.1
121
-
122
- ## 3.1.0
123
-
124
- ### Patch Changes
125
-
126
- - Updated dependencies [0088830]
127
- - @objectstack/spec@3.1.0
128
- - @objectstack/core@3.1.0
129
-
130
- ## 3.0.11
131
-
132
- ### Patch Changes
133
-
134
- - Updated dependencies [92d9d99]
135
- - @objectstack/spec@3.0.11
136
- - @objectstack/core@3.0.11
137
-
138
- ## 3.0.10
139
-
140
- ### Patch Changes
141
-
142
- - Updated dependencies [d1e5d31]
143
- - @objectstack/spec@3.0.10
144
- - @objectstack/core@3.0.10
145
-
146
- ## 3.0.9
147
-
148
- ### Patch Changes
149
-
150
- - Updated dependencies [15e0df6]
151
- - @objectstack/spec@3.0.9
152
- - @objectstack/core@3.0.9
153
-
154
- ## 3.0.8
155
-
156
- ### Patch Changes
157
-
158
- - Updated dependencies [5a968a2]
159
- - @objectstack/spec@3.0.8
160
- - @objectstack/core@3.0.8
161
-
162
- ## 3.0.7
163
-
164
- ### Patch Changes
165
-
166
- - Updated dependencies [0119bd7]
167
- - Updated dependencies [5426bdf]
168
- - @objectstack/spec@3.0.7
169
- - @objectstack/core@3.0.7
@@ -1,63 +0,0 @@
1
- // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2
-
3
- import type { Plugin, PluginContext } from '@objectstack/core';
4
- import { MemoryCacheAdapter } from './memory-cache-adapter.js';
5
- import type { MemoryCacheAdapterOptions } from './memory-cache-adapter.js';
6
-
7
- /**
8
- * Configuration options for the CacheServicePlugin.
9
- */
10
- export interface CacheServicePluginOptions {
11
- /** Cache adapter type (default: 'memory') */
12
- adapter?: 'memory' | 'redis';
13
- /** Options for the memory cache adapter */
14
- memory?: MemoryCacheAdapterOptions;
15
- /** Redis connection URL (used when adapter is 'redis') */
16
- redisUrl?: string;
17
- }
18
-
19
- /**
20
- * CacheServicePlugin — Production ICacheService implementation.
21
- *
22
- * Registers a cache service with the kernel during the init phase.
23
- * Supports in-memory and Redis adapters.
24
- *
25
- * @example
26
- * ```ts
27
- * import { ObjectKernel } from '@objectstack/core';
28
- * import { CacheServicePlugin } from '@objectstack/service-cache';
29
- *
30
- * const kernel = new ObjectKernel();
31
- * kernel.use(new CacheServicePlugin({ adapter: 'memory', memory: { maxSize: 1000 } }));
32
- * await kernel.bootstrap();
33
- *
34
- * const cache = kernel.getService('cache');
35
- * await cache.set('key', 'value', 60);
36
- * ```
37
- */
38
- export class CacheServicePlugin implements Plugin {
39
- name = 'com.objectstack.service.cache';
40
- version = '1.0.0';
41
- type = 'standard';
42
-
43
- private readonly options: CacheServicePluginOptions;
44
-
45
- constructor(options: CacheServicePluginOptions = {}) {
46
- this.options = { adapter: 'memory', ...options };
47
- }
48
-
49
- async init(ctx: PluginContext): Promise<void> {
50
- const adapter = this.options.adapter;
51
- if (adapter === 'redis') {
52
- // Redis adapter is a skeleton — throw an informative error for now
53
- throw new Error(
54
- 'Redis cache adapter is not yet implemented. ' +
55
- 'Use adapter: "memory" or provide a custom ICacheService via ctx.registerService("cache", impl).'
56
- );
57
- }
58
-
59
- const cache = new MemoryCacheAdapter(this.options.memory);
60
- ctx.registerService('cache', cache);
61
- ctx.logger.info('CacheServicePlugin: registered memory cache adapter');
62
- }
63
- }
package/src/index.ts DELETED
@@ -1,8 +0,0 @@
1
- // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2
-
3
- export { CacheServicePlugin } from './cache-service-plugin.js';
4
- export type { CacheServicePluginOptions } from './cache-service-plugin.js';
5
- export { MemoryCacheAdapter } from './memory-cache-adapter.js';
6
- export type { MemoryCacheAdapterOptions } from './memory-cache-adapter.js';
7
- export { RedisCacheAdapter } from './redis-cache-adapter.js';
8
- export type { RedisCacheAdapterOptions } from './redis-cache-adapter.js';
@@ -1,107 +0,0 @@
1
- // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2
-
3
- import { describe, it, expect } from 'vitest';
4
- import { MemoryCacheAdapter } from './memory-cache-adapter';
5
- import type { ICacheService } from '@objectstack/spec/contracts';
6
-
7
- describe('MemoryCacheAdapter', () => {
8
- it('should implement ICacheService contract', () => {
9
- const cache: ICacheService = new MemoryCacheAdapter();
10
- expect(typeof cache.get).toBe('function');
11
- expect(typeof cache.set).toBe('function');
12
- expect(typeof cache.delete).toBe('function');
13
- expect(typeof cache.has).toBe('function');
14
- expect(typeof cache.clear).toBe('function');
15
- expect(typeof cache.stats).toBe('function');
16
- });
17
-
18
- it('should set and get a value', async () => {
19
- const cache = new MemoryCacheAdapter();
20
- await cache.set('key1', 'value1');
21
- expect(await cache.get('key1')).toBe('value1');
22
- });
23
-
24
- it('should return undefined for missing key', async () => {
25
- const cache = new MemoryCacheAdapter();
26
- expect(await cache.get('nonexistent')).toBeUndefined();
27
- });
28
-
29
- it('should delete a key', async () => {
30
- const cache = new MemoryCacheAdapter();
31
- await cache.set('key1', 'value1');
32
- expect(await cache.delete('key1')).toBe(true);
33
- expect(await cache.get('key1')).toBeUndefined();
34
- });
35
-
36
- it('should return false when deleting missing key', async () => {
37
- const cache = new MemoryCacheAdapter();
38
- expect(await cache.delete('missing')).toBe(false);
39
- });
40
-
41
- it('should check if a key exists with has()', async () => {
42
- const cache = new MemoryCacheAdapter();
43
- expect(await cache.has('key1')).toBe(false);
44
- await cache.set('key1', 'value1');
45
- expect(await cache.has('key1')).toBe(true);
46
- });
47
-
48
- it('should clear all entries', async () => {
49
- const cache = new MemoryCacheAdapter();
50
- await cache.set('a', 1);
51
- await cache.set('b', 2);
52
- await cache.clear();
53
- expect(await cache.has('a')).toBe(false);
54
- expect(await cache.has('b')).toBe(false);
55
- });
56
-
57
- it('should expire entries based on TTL', async () => {
58
- const cache = new MemoryCacheAdapter();
59
- await cache.set('temp', 'data', 0.001); // 1ms TTL
60
- await new Promise(r => setTimeout(r, 20));
61
- expect(await cache.get('temp')).toBeUndefined();
62
- });
63
-
64
- it('should track hit/miss stats', async () => {
65
- const cache = new MemoryCacheAdapter();
66
- await cache.set('key1', 'value1');
67
- await cache.get('key1'); // hit
68
- await cache.get('missing'); // miss
69
- const stats = await cache.stats();
70
- expect(stats.hits).toBe(1);
71
- expect(stats.misses).toBe(1);
72
- expect(stats.keyCount).toBe(1);
73
- });
74
-
75
- it('should apply defaultTtl when no TTL is provided', async () => {
76
- const cache = new MemoryCacheAdapter({ defaultTtl: 0.001 });
77
- await cache.set('key', 'value');
78
- await new Promise(r => setTimeout(r, 20));
79
- expect(await cache.get('key')).toBeUndefined();
80
- });
81
-
82
- it('should evict oldest entry when maxSize is reached', async () => {
83
- const cache = new MemoryCacheAdapter({ maxSize: 2 });
84
- await cache.set('a', 1);
85
- await cache.set('b', 2);
86
- await cache.set('c', 3); // should evict 'a'
87
- expect(await cache.has('a')).toBe(false);
88
- expect(await cache.get('b')).toBe(2);
89
- expect(await cache.get('c')).toBe(3);
90
- });
91
-
92
- it('should not evict when updating existing key at maxSize', async () => {
93
- const cache = new MemoryCacheAdapter({ maxSize: 2 });
94
- await cache.set('a', 1);
95
- await cache.set('b', 2);
96
- await cache.set('a', 10); // update, not new entry
97
- expect(await cache.get('a')).toBe(10);
98
- expect(await cache.get('b')).toBe(2);
99
- });
100
-
101
- it('should handle has() with expired TTL', async () => {
102
- const cache = new MemoryCacheAdapter();
103
- await cache.set('expiring', 'val', 0.001);
104
- await new Promise(r => setTimeout(r, 20));
105
- expect(await cache.has('expiring')).toBe(false);
106
- });
107
- });
@@ -1,90 +0,0 @@
1
- // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2
-
3
- import type { ICacheService, CacheStats } from '@objectstack/spec/contracts';
4
-
5
- /**
6
- * In-memory cache entry with optional TTL expiry.
7
- */
8
- interface CacheEntry<T = unknown> {
9
- value: T;
10
- expires?: number;
11
- }
12
-
13
- /**
14
- * Configuration options for MemoryCacheAdapter.
15
- */
16
- export interface MemoryCacheAdapterOptions {
17
- /** Maximum number of entries before eviction (0 = unlimited) */
18
- maxSize?: number;
19
- /** Default TTL in seconds (0 = no expiry) */
20
- defaultTtl?: number;
21
- }
22
-
23
- /**
24
- * In-memory cache adapter implementing ICacheService.
25
- *
26
- * Uses a Map-backed store with TTL-based expiry and LRU-style eviction.
27
- * Suitable for single-process environments, development, and testing.
28
- */
29
- export class MemoryCacheAdapter implements ICacheService {
30
- private readonly store = new Map<string, CacheEntry>();
31
- private hits = 0;
32
- private misses = 0;
33
- private readonly maxSize: number;
34
- private readonly defaultTtl: number;
35
-
36
- constructor(options: MemoryCacheAdapterOptions = {}) {
37
- this.maxSize = options.maxSize ?? 0;
38
- this.defaultTtl = options.defaultTtl ?? 0;
39
- }
40
-
41
- async get<T = unknown>(key: string): Promise<T | undefined> {
42
- const entry = this.store.get(key);
43
- if (!entry || (entry.expires && Date.now() > entry.expires)) {
44
- if (entry) this.store.delete(key);
45
- this.misses++;
46
- return undefined;
47
- }
48
- this.hits++;
49
- return entry.value as T;
50
- }
51
-
52
- async set<T = unknown>(key: string, value: T, ttl?: number): Promise<void> {
53
- const effectiveTtl = ttl ?? this.defaultTtl;
54
- if (this.maxSize > 0 && !this.store.has(key) && this.store.size >= this.maxSize) {
55
- // Evict oldest entry (first key in Map insertion order)
56
- const firstKey = this.store.keys().next().value;
57
- if (firstKey !== undefined) this.store.delete(firstKey);
58
- }
59
- this.store.set(key, {
60
- value,
61
- expires: effectiveTtl > 0 ? Date.now() + effectiveTtl * 1000 : undefined,
62
- });
63
- }
64
-
65
- async delete(key: string): Promise<boolean> {
66
- return this.store.delete(key);
67
- }
68
-
69
- async has(key: string): Promise<boolean> {
70
- const entry = this.store.get(key);
71
- if (!entry) return false;
72
- if (entry.expires && Date.now() > entry.expires) {
73
- this.store.delete(key);
74
- return false;
75
- }
76
- return true;
77
- }
78
-
79
- async clear(): Promise<void> {
80
- this.store.clear();
81
- }
82
-
83
- async stats(): Promise<CacheStats> {
84
- return {
85
- hits: this.hits,
86
- misses: this.misses,
87
- keyCount: this.store.size,
88
- };
89
- }
90
- }
@@ -1,63 +0,0 @@
1
- // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2
-
3
- import type { ICacheService, CacheStats } from '@objectstack/spec/contracts';
4
-
5
- /**
6
- * Configuration for the Redis cache adapter.
7
- */
8
- export interface RedisCacheAdapterOptions {
9
- /** Redis connection URL (e.g. 'redis://localhost:6379') */
10
- url: string;
11
- /** Key prefix for namespacing (default: 'os:') */
12
- keyPrefix?: string;
13
- /** Default TTL in seconds (0 = no expiry) */
14
- defaultTtl?: number;
15
- }
16
-
17
- /**
18
- * Redis cache adapter skeleton implementing ICacheService.
19
- *
20
- * This is a placeholder for future Redis integration.
21
- * Concrete implementation will use `ioredis` or `redis` client.
22
- *
23
- * @example
24
- * ```ts
25
- * const cache = new RedisCacheAdapter({ url: 'redis://localhost:6379' });
26
- * await cache.set('user:1', { name: 'Alice' }, 3600);
27
- * ```
28
- */
29
- export class RedisCacheAdapter implements ICacheService {
30
- private readonly url: string;
31
- private readonly keyPrefix: string;
32
- private readonly defaultTtl: number;
33
-
34
- constructor(options: RedisCacheAdapterOptions) {
35
- this.url = options.url;
36
- this.keyPrefix = options.keyPrefix ?? 'os:';
37
- this.defaultTtl = options.defaultTtl ?? 0;
38
- }
39
-
40
- async get<T = unknown>(_key: string): Promise<T | undefined> {
41
- throw new Error(`RedisCacheAdapter not yet implemented (url: ${this.url}, prefix: ${this.keyPrefix}, ttl: ${this.defaultTtl})`);
42
- }
43
-
44
- async set<T = unknown>(_key: string, _value: T, _ttl?: number): Promise<void> {
45
- throw new Error('RedisCacheAdapter not yet implemented');
46
- }
47
-
48
- async delete(_key: string): Promise<boolean> {
49
- throw new Error('RedisCacheAdapter not yet implemented');
50
- }
51
-
52
- async has(_key: string): Promise<boolean> {
53
- throw new Error('RedisCacheAdapter not yet implemented');
54
- }
55
-
56
- async clear(): Promise<void> {
57
- throw new Error('RedisCacheAdapter not yet implemented');
58
- }
59
-
60
- async stats(): Promise<CacheStats> {
61
- throw new Error('RedisCacheAdapter not yet implemented');
62
- }
63
- }
package/tsconfig.json DELETED
@@ -1,17 +0,0 @@
1
- {
2
- "extends": "../../../tsconfig.json",
3
- "compilerOptions": {
4
- "outDir": "dist",
5
- "rootDir": "src",
6
- "types": [
7
- "node"
8
- ]
9
- },
10
- "include": [
11
- "src"
12
- ],
13
- "exclude": [
14
- "node_modules",
15
- "dist"
16
- ]
17
- }