@open-mercato/cache 0.3.2

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.
Files changed (44) hide show
  1. package/ENV.md +173 -0
  2. package/README.md +177 -0
  3. package/dist/errors.d.ts +7 -0
  4. package/dist/errors.d.ts.map +1 -0
  5. package/dist/errors.js +9 -0
  6. package/dist/index.d.ts +8 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +7 -0
  9. package/dist/service.d.ts +40 -0
  10. package/dist/service.d.ts.map +1 -0
  11. package/dist/service.js +321 -0
  12. package/dist/strategies/jsonfile.d.ts +10 -0
  13. package/dist/strategies/jsonfile.d.ts.map +1 -0
  14. package/dist/strategies/jsonfile.js +205 -0
  15. package/dist/strategies/memory.d.ts +9 -0
  16. package/dist/strategies/memory.d.ts.map +1 -0
  17. package/dist/strategies/memory.js +166 -0
  18. package/dist/strategies/redis.d.ts +5 -0
  19. package/dist/strategies/redis.d.ts.map +1 -0
  20. package/dist/strategies/redis.js +388 -0
  21. package/dist/strategies/sqlite.d.ts +13 -0
  22. package/dist/strategies/sqlite.d.ts.map +1 -0
  23. package/dist/strategies/sqlite.js +217 -0
  24. package/dist/tenantContext.d.ts +4 -0
  25. package/dist/tenantContext.d.ts.map +1 -0
  26. package/dist/tenantContext.js +9 -0
  27. package/dist/types.d.ts +86 -0
  28. package/dist/types.d.ts.map +1 -0
  29. package/dist/types.js +1 -0
  30. package/jest.config.js +19 -0
  31. package/package.json +39 -0
  32. package/src/__tests__/memory.strategy.test.ts +245 -0
  33. package/src/__tests__/service.test.ts +189 -0
  34. package/src/errors.ts +14 -0
  35. package/src/index.ts +7 -0
  36. package/src/service.ts +367 -0
  37. package/src/strategies/jsonfile.ts +249 -0
  38. package/src/strategies/memory.ts +185 -0
  39. package/src/strategies/redis.ts +443 -0
  40. package/src/strategies/sqlite.ts +285 -0
  41. package/src/tenantContext.ts +13 -0
  42. package/src/types.ts +100 -0
  43. package/tsconfig.build.json +5 -0
  44. package/tsconfig.json +12 -0
package/ENV.md ADDED
@@ -0,0 +1,173 @@
1
+ # Cache Environment Variables
2
+
3
+ This document describes the environment variables used to configure the cache service.
4
+
5
+ ## Configuration Variables
6
+
7
+ ### `CACHE_STRATEGY`
8
+
9
+ **Description:** Specifies which cache storage strategy to use.
10
+
11
+ **Values:** `memory` | `redis` | `sqlite` | `jsonfile`
12
+
13
+ **Default:** `memory`
14
+
15
+ **Example:**
16
+ ```bash
17
+ CACHE_STRATEGY=redis
18
+ ```
19
+
20
+ ### `CACHE_TTL`
21
+
22
+ **Description:** Default Time To Live (TTL) for cache entries in milliseconds. If not set, cache entries don't expire by default unless specified per-operation.
23
+
24
+ **Type:** Number (milliseconds)
25
+
26
+ **Optional:** Yes
27
+
28
+ **Example:**
29
+ ```bash
30
+ CACHE_TTL=300000 # 5 minutes
31
+ ```
32
+
33
+ ### `CACHE_REDIS_URL`
34
+
35
+ **Description:** Redis connection URL. Required when using `redis` strategy. Falls back to `REDIS_URL` if not set.
36
+
37
+ **Type:** String (URL)
38
+
39
+ **Required:** Only for `redis` strategy
40
+
41
+ **Example:**
42
+ ```bash
43
+ CACHE_REDIS_URL=redis://localhost:6379
44
+ # or
45
+ REDIS_URL=redis://localhost:6379
46
+ ```
47
+
48
+ ### `CACHE_SQLITE_PATH`
49
+
50
+ **Description:** Path to the SQLite database file. Required when using `sqlite` strategy.
51
+
52
+ **Type:** String (file path)
53
+
54
+ **Default:** `.cache.db`
55
+
56
+ **Required:** Only for `sqlite` strategy
57
+
58
+ **Example:**
59
+ ```bash
60
+ CACHE_SQLITE_PATH=./data/.cache.db
61
+ ```
62
+
63
+ ### `CACHE_JSON_FILE_PATH`
64
+
65
+ **Description:** Path to the JSON cache file. Required when using `jsonfile` strategy.
66
+
67
+ **Type:** String (file path)
68
+
69
+ **Default:** `.cache.json`
70
+
71
+ **Required:** Only for `jsonfile` strategy
72
+
73
+ **Example:**
74
+ ```bash
75
+ CACHE_JSON_FILE_PATH=./data/.cache.json
76
+ ```
77
+
78
+ ## Example Configurations
79
+
80
+ ### Development (In-Memory)
81
+
82
+ Fast, not shared across instances, data lost on restart.
83
+
84
+ ```bash
85
+ CACHE_STRATEGY=memory
86
+ CACHE_TTL=300000
87
+ ```
88
+
89
+ ### Production (Redis)
90
+
91
+ Shared across instances, persistent, requires Redis server.
92
+
93
+ ```bash
94
+ CACHE_STRATEGY=redis
95
+ CACHE_REDIS_URL=redis://localhost:6379
96
+ CACHE_TTL=600000
97
+ ```
98
+
99
+ ### Production (SQLite)
100
+
101
+ Persistent, file-based, good for single instance deployments.
102
+
103
+ ```bash
104
+ CACHE_STRATEGY=sqlite
105
+ CACHE_SQLITE_PATH=./data/.cache.db
106
+ CACHE_TTL=3600000
107
+ ```
108
+
109
+ ### Development/Testing (JSON File)
110
+
111
+ Persistent, human-readable, slow, good for debugging.
112
+
113
+ ```bash
114
+ CACHE_STRATEGY=jsonfile
115
+ CACHE_JSON_FILE_PATH=./data/.cache.json
116
+ CACHE_TTL=600000
117
+ ```
118
+
119
+ ## Strategy Comparison
120
+
121
+ | Strategy | Speed | Persistent | Shared | Dependencies | Use Case |
122
+ |------------|------------|------------|--------|------------------|---------------------------------|
123
+ | `memory` | Fastest | No | No | None | Development, single instance |
124
+ | `redis` | Fast | Yes | Yes | ioredis | Production, multi-instance |
125
+ | `sqlite` | Medium | Yes | No* | better-sqlite3 | Production, single instance |
126
+ | `jsonfile` | Slowest | Yes | No | None | Development, debugging |
127
+
128
+ *Not recommended for concurrent access from multiple processes
129
+
130
+ ## TTL Values Reference
131
+
132
+ Common TTL values in milliseconds:
133
+
134
+ ```bash
135
+ # 1 minute
136
+ CACHE_TTL=60000
137
+
138
+ # 5 minutes
139
+ CACHE_TTL=300000
140
+
141
+ # 10 minutes
142
+ CACHE_TTL=600000
143
+
144
+ # 30 minutes
145
+ CACHE_TTL=1800000
146
+
147
+ # 1 hour
148
+ CACHE_TTL=3600000
149
+
150
+ # 24 hours
151
+ CACHE_TTL=86400000
152
+ ```
153
+
154
+ ## Installation Requirements
155
+
156
+ Depending on your chosen strategy, you may need to install additional dependencies:
157
+
158
+ ### Redis Strategy
159
+
160
+ ```bash
161
+ yarn add ioredis
162
+ ```
163
+
164
+ ### SQLite Strategy
165
+
166
+ ```bash
167
+ yarn add better-sqlite3
168
+ ```
169
+
170
+ ### Memory and JSON File Strategies
171
+
172
+ No additional dependencies required.
173
+
package/README.md ADDED
@@ -0,0 +1,177 @@
1
+ # @open-mercato/cache
2
+
3
+ > Tag-aware, strategy-pluggable cache service extracted from the Open Mercato platform—now packaged for general-purpose Node.js apps.
4
+
5
+ - 🔁 **Swappable storage engines**: in-memory, Redis, SQLite, or JSON-file without touching your call-sites.
6
+ - 🏷️ **Tag-based invalidation**: expire related keys with one operation (`deleteByTags`).
7
+ - ⏱️ **TTL + stats tooling**: per-entry TTLs, wildcard key lookups, stats, and cleanup helpers.
8
+ - 🧩 **DI friendly**: use the functional factory or the `CacheService` class.
9
+ - 🧾 **Pure TypeScript**: strict typings + generated declaration files (emitted to `dist/`).
10
+ - 🪶 **Zero hard deps**: optional peers (`ioredis`, `better-sqlite3`) only when you activate those strategies.
11
+
12
+ ---
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ yarn add @open-mercato/cache
18
+
19
+ # add peers when needed
20
+ yarn add ioredis # for the Redis strategy
21
+ yarn add better-sqlite3 # for the SQLite strategy
22
+ ```
23
+
24
+ > Runtime: Node 18+. The library is compiled with the repo’s root `tsconfig` and ships JS + d.ts files in `dist/`.
25
+
26
+ ---
27
+
28
+ ## Quick start
29
+
30
+ ```ts
31
+ import { createCacheService } from '@open-mercato/cache'
32
+
33
+ const cache = createCacheService({
34
+ strategy: process.env.CACHE_STRATEGY || 'memory',
35
+ redisUrl: process.env.CACHE_REDIS_URL,
36
+ sqlitePath: './data/cache.db',
37
+ jsonFilePath: './data/cache.json',
38
+ defaultTtl: 5 * 60_000,
39
+ })
40
+
41
+ await cache.set('user:123', { name: 'Ada' }, { ttl: 10 * 60_000, tags: ['users', 'user:123'] })
42
+
43
+ const result = await cache.get('user:123')
44
+ await cache.deleteByTags(['users']) // bust related entries
45
+ await cache.cleanup() // sweep expired rows (mainly for sqlite/json)
46
+ await cache.close() // dispose connections on shutdown
47
+ ```
48
+
49
+ ---
50
+
51
+ ## Strategy matrix
52
+
53
+ | Strategy | Persistence | Concurrency | Extra deps | Typical use case |
54
+ |------------|-------------|-------------|------------------|------------------------------------------------|
55
+ | `memory` | ❌ process | single node | – | Unit tests, light CLIs, temporary caches |
56
+ | `redis` | ✅ external | multi-node | `ioredis` | Horizontal APIs, queues, distributed workers |
57
+ | `sqlite` | ✅ local | single node | `better-sqlite3` | Edge workers, self-hosted admin tooling |
58
+ | `jsonfile` | ✅ local | single node | – | Debugging snapshots, very small deployments |
59
+
60
+ Switch the strategy via:
61
+
62
+ - `CACHE_STRATEGY` env var (`memory` default), or
63
+ - Passing `strategy`, `redisUrl`, `sqlitePath`, `jsonFilePath` to `createCacheService`.
64
+
65
+ ---
66
+
67
+ ## Configuration
68
+
69
+ ### Environment variables
70
+
71
+ ```bash
72
+ CACHE_STRATEGY=memory|redis|sqlite|jsonfile
73
+ CACHE_TTL=300000
74
+ CACHE_REDIS_URL=redis://localhost:6379
75
+ CACHE_SQLITE_PATH=.cache/cache.db
76
+ CACHE_JSON_FILE_PATH=.cache/cache.json
77
+ ```
78
+
79
+ ### Programmatic
80
+
81
+ ```ts
82
+ const sqliteCache = createCacheService({
83
+ strategy: 'sqlite',
84
+ sqlitePath: './data/cache.db',
85
+ defaultTtl: 30 * 60_000,
86
+ })
87
+ ```
88
+
89
+ ---
90
+
91
+ ## API reference (all strategies implement these)
92
+
93
+ | Method | Description |
94
+ |--------|-------------|
95
+ | `get(key, { returnExpired? })` | Retrieves a value, optionally returning expired records for debugging. |
96
+ | `set(key, value, { ttl?, tags? })` | Stores a value with a TTL and tag metadata. |
97
+ | `has(key)` | Boolean existence check (ignores expired rows). |
98
+ | `delete(key)` | Removes a single key. |
99
+ | `deleteByTags(tags[])` | Removes every entry that matches _any_ of the provided tags. |
100
+ | `clear()` | Clears the current scope (all tenant-prefixed entries when used inside Open Mercato). |
101
+ | `keys(pattern?)` | Lists logical keys using glob syntax (`*`, `?`). |
102
+ | `stats()` | Returns `{ size, expired }` counters. |
103
+ | `cleanup()` | Sweeps expired entries (primarily for sqlite/json backends). |
104
+ | `close()` | Disposes the underlying client (important for Redis). |
105
+
106
+ Prefer the functional factory (`createCacheService`) for most cases. If you need to integrate with Awilix or another DI container, use the `CacheService` class wrapper—its public methods mirror the table above.
107
+
108
+ ---
109
+
110
+ ## Advanced usage
111
+
112
+ ### Tag-based invalidation
113
+
114
+ ```ts
115
+ await cache.set(`product:${id}`, payload, {
116
+ tags: ['products', `catalog:${catalogId}`, `product:${id}`],
117
+ })
118
+
119
+ // Later…
120
+ await cache.deleteByTags([`catalog:${catalogId}`]) // bust only that catalog
121
+ ```
122
+
123
+ Tags behave like sets. A single delete request can invalidate thousands of keys without scanning the entire store, which keeps admin actions and background jobs snappy.
124
+
125
+ ### Pattern matching & diagnostics
126
+
127
+ ```ts
128
+ const staleKeys = await cache.keys('report:*:2024-*')
129
+ const stats = await cache.stats()
130
+
131
+ console.log(`Cache size: ${stats.size}, expired entries waiting: ${stats.expired}`)
132
+ ```
133
+
134
+ The glob-matching happens on logical keys, so you can keep friendly names for troubleshooting while the implementation hashes/filters keys under the hood.
135
+
136
+ ---
137
+
138
+ ## Building & publishing
139
+
140
+ This package lives inside the monorepo but publishes independently. The emitted JS and declaration files live in `dist/` (ignored by git).
141
+
142
+ ```bash
143
+ # Install dependencies at the repo root
144
+ yarn install
145
+
146
+ # Build the cache package
147
+ npx tsc -p packages/cache/tsconfig.build.json
148
+
149
+ # Run the targeted test suite (optional)
150
+ npm run test -- --runTestsByPath packages/cache/src/__tests__/service.test.ts
151
+
152
+ # Inspect the tarball before publishing
153
+ cd packages/cache
154
+ npm pack
155
+
156
+ # Publish
157
+ npm publish --access public
158
+ ```
159
+
160
+ The `package.json` already points `main`/`types` at `dist/index.*`, so consumers always receive compiled artifacts.
161
+
162
+ ---
163
+
164
+ ## Contributing
165
+
166
+ 1. Edit the TypeScript sources in `packages/cache/src`.
167
+ 2. Run the focused Jest suite (`npm run test -- --runTestsByPath packages/cache/src/__tests__/service.test.ts`).
168
+ 3. Rebuild (`npx tsc -p packages/cache/tsconfig.build.json`) to refresh `dist/`.
169
+ 4. Open a PR and describe the strategy/feature you touched.
170
+
171
+ Bug reports & feature requests: open an issue in the main Open Mercato repository and tag it with `cache`.
172
+
173
+ ---
174
+
175
+ ## License
176
+
177
+ MIT © Open Mercato
@@ -0,0 +1,7 @@
1
+ export declare class CacheDependencyUnavailableError extends Error {
2
+ readonly strategy: string;
3
+ readonly dependency: string;
4
+ readonly originalError?: unknown;
5
+ constructor(strategy: string, dependency: string, originalError?: unknown);
6
+ }
7
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,qBAAa,+BAAgC,SAAQ,KAAK;IACxD,SAAgB,QAAQ,EAAE,MAAM,CAAA;IAChC,SAAgB,UAAU,EAAE,MAAM,CAAA;IAClC,SAAgB,aAAa,CAAC,EAAE,OAAO,CAAA;gBAE3B,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,OAAO;CAO1E"}
package/dist/errors.js ADDED
@@ -0,0 +1,9 @@
1
+ export class CacheDependencyUnavailableError extends Error {
2
+ constructor(strategy, dependency, originalError) {
3
+ super(`Cache strategy "${strategy}" requires dependency "${dependency}" which is not available`);
4
+ this.name = 'CacheDependencyUnavailableError';
5
+ this.strategy = strategy;
6
+ this.dependency = dependency;
7
+ this.originalError = originalError;
8
+ }
9
+ }
@@ -0,0 +1,8 @@
1
+ export * from './types';
2
+ export * from './service';
3
+ export { createMemoryStrategy } from './strategies/memory';
4
+ export { createRedisStrategy } from './strategies/redis';
5
+ export { createSqliteStrategy } from './strategies/sqlite';
6
+ export { createJsonFileStrategy } from './strategies/jsonfile';
7
+ export { runWithCacheTenant, getCurrentCacheTenant } from './tenantContext';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAA;AACvB,cAAc,WAAW,CAAA;AACzB,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAA;AAC1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AACxD,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAA;AAC1D,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAA;AAC9D,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ export * from './types';
2
+ export * from './service';
3
+ export { createMemoryStrategy } from './strategies/memory';
4
+ export { createRedisStrategy } from './strategies/redis';
5
+ export { createSqliteStrategy } from './strategies/sqlite';
6
+ export { createJsonFileStrategy } from './strategies/jsonfile';
7
+ export { runWithCacheTenant, getCurrentCacheTenant } from './tenantContext';
@@ -0,0 +1,40 @@
1
+ import type { CacheStrategy, CacheServiceOptions, CacheGetOptions, CacheSetOptions, CacheValue } from './types';
2
+ /**
3
+ * Cache service that provides a unified interface to different cache strategies
4
+ *
5
+ * Configuration via environment variables:
6
+ * - CACHE_STRATEGY: 'memory' | 'redis' | 'sqlite' | 'jsonfile' (default: 'memory')
7
+ * - CACHE_TTL: Default TTL in milliseconds (optional)
8
+ * - CACHE_REDIS_URL: Redis connection URL (for redis strategy)
9
+ * - CACHE_SQLITE_PATH: SQLite database file path (for sqlite strategy)
10
+ * - CACHE_JSON_FILE_PATH: JSON file path (for jsonfile strategy)
11
+ *
12
+ * @example
13
+ * const cache = createCacheService({ strategy: 'memory', defaultTtl: 60000 })
14
+ * await cache.set('user:123', { name: 'John' }, { tags: ['users', 'user:123'] })
15
+ * const user = await cache.get('user:123')
16
+ * await cache.deleteByTags(['users']) // Invalidate all user-related cache
17
+ */
18
+ export declare function createCacheService(options?: CacheServiceOptions): CacheStrategy;
19
+ /**
20
+ * CacheService class wrapper for DI integration
21
+ * Provides the same interface as the functional API but as a class
22
+ */
23
+ export declare class CacheService implements CacheStrategy {
24
+ private strategy;
25
+ constructor(options?: CacheServiceOptions);
26
+ get(key: string, options?: CacheGetOptions): Promise<CacheValue | null>;
27
+ set(key: string, value: CacheValue, options?: CacheSetOptions): Promise<void>;
28
+ has(key: string): Promise<boolean>;
29
+ delete(key: string): Promise<boolean>;
30
+ deleteByTags(tags: string[]): Promise<number>;
31
+ clear(): Promise<number>;
32
+ keys(pattern?: string): Promise<string[]>;
33
+ stats(): Promise<{
34
+ size: number;
35
+ expired: number;
36
+ }>;
37
+ cleanup(): Promise<number>;
38
+ close(): Promise<void>;
39
+ }
40
+ //# sourceMappingURL=service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,mBAAmB,EAAE,eAAe,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AA6M/G;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,CAAC,EAAE,mBAAmB,GAAG,aAAa,CAc/E;AAED;;;GAGG;AACH,qBAAa,YAAa,YAAW,aAAa;IAChD,OAAO,CAAC,QAAQ,CAAe;gBAEnB,OAAO,CAAC,EAAE,mBAAmB;IAInC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAIvE,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAI7E,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAIlC,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAIrC,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAI7C,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC;IAIxB,IAAI,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAIzC,KAAK,IAAI,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAInD,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC;IAO1B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAK7B"}