cacheable 2.2.0 → 2.3.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.
package/README.md CHANGED
@@ -547,6 +547,45 @@ const cache = new Cacheable({
547
547
  });
548
548
  ```
549
549
 
550
+ ## Namespace Isolation with Sync
551
+
552
+ When multiple services share the same Redis instance (or other message provider), you can use namespaces to isolate cache synchronization events between services. This prevents one service's cache updates from affecting another service's cache.
553
+
554
+ ```javascript
555
+ import { Cacheable } from 'cacheable';
556
+ import { RedisMessageProvider } from '@qified/redis';
557
+
558
+ const provider = new RedisMessageProvider({
559
+ connection: { host: 'localhost', port: 6379 }
560
+ });
561
+
562
+ // Service 1 with namespace
563
+ const serviceA = new Cacheable({
564
+ namespace: 'service-a',
565
+ sync: { qified: provider }
566
+ });
567
+
568
+ // Service 2 with different namespace
569
+ const serviceB = new Cacheable({
570
+ namespace: 'service-b',
571
+ sync: { qified: provider }
572
+ });
573
+
574
+ // Set value in service A
575
+ await serviceA.set('config', { timeout: 5000 });
576
+
577
+ // Service B won't receive this update because it has a different namespace
578
+ const value = await serviceB.get('config'); // undefined
579
+ ```
580
+
581
+ **How Namespace Isolation Works:**
582
+ - Without namespaces, sync events use channel names like `cache:set` and `cache:delete`
583
+ - With namespaces, events are prefixed: `service-a::cache:set`, `service-b::cache:set`
584
+ - Services only subscribe to events matching their namespace, ensuring complete isolation
585
+ - Namespaces can be static strings or functions that return strings
586
+
587
+ **Note:** The namespace is automatically passed from Cacheable to CacheableSync, so you only need to set it once in the Cacheable options.
588
+
550
589
  ## How Sync Works
551
590
 
552
591
  1. **SET Operations**: When you call `cache.set()` or `cache.setMany()`, the cache:
package/dist/index.cjs CHANGED
@@ -78,12 +78,16 @@ var CacheableSyncEvents = /* @__PURE__ */ ((CacheableSyncEvents2) => {
78
78
  })(CacheableSyncEvents || {});
79
79
  var CacheableSync = class extends import_hookified.Hookified {
80
80
  _qified = new import_qified.Qified();
81
+ _namespace;
82
+ _storage;
83
+ _cacheId;
81
84
  /**
82
85
  * Creates an instance of CacheableSync
83
86
  * @param options - Configuration options for CacheableSync
84
87
  */
85
88
  constructor(options) {
86
89
  super(options);
90
+ this._namespace = options.namespace;
87
91
  this._qified = this.createQified(options.qified);
88
92
  }
89
93
  /**
@@ -100,12 +104,36 @@ var CacheableSync = class extends import_hookified.Hookified {
100
104
  set qified(value) {
101
105
  this._qified = this.createQified(value);
102
106
  }
107
+ /**
108
+ * Gets the namespace for sync events
109
+ * @returns The namespace or undefined if not set
110
+ */
111
+ get namespace() {
112
+ return this._namespace;
113
+ }
114
+ /**
115
+ * Sets the namespace for sync events and resubscribes if needed
116
+ * @param namespace - The namespace string or function
117
+ */
118
+ set namespace(namespace) {
119
+ if (this._storage && this._cacheId) {
120
+ const oldSetEvent = this.getPrefixedEvent("cache:set" /* SET */);
121
+ const oldDeleteEvent = this.getPrefixedEvent("cache:delete" /* DELETE */);
122
+ void this._qified.unsubscribe(oldSetEvent);
123
+ void this._qified.unsubscribe(oldDeleteEvent);
124
+ }
125
+ this._namespace = namespace;
126
+ if (this._storage && this._cacheId) {
127
+ this.subscribe(this._storage, this._cacheId);
128
+ }
129
+ }
103
130
  /**
104
131
  * Publishes a cache event to all the cache instances
105
132
  * @param data - The cache item data containing cacheId, key, value, and optional ttl
106
133
  */
107
134
  async publish(event, data) {
108
- await this._qified.publish(event, {
135
+ const eventName = this.getPrefixedEvent(event);
136
+ await this._qified.publish(eventName, {
109
137
  id: crypto.randomUUID(),
110
138
  data
111
139
  });
@@ -116,7 +144,11 @@ var CacheableSync = class extends import_hookified.Hookified {
116
144
  * @param cacheId - The cache ID to identify this instance
117
145
  */
118
146
  subscribe(storage, cacheId) {
119
- this._qified.subscribe("cache:set" /* SET */, {
147
+ this._storage = storage;
148
+ this._cacheId = cacheId;
149
+ const setEvent = this.getPrefixedEvent("cache:set" /* SET */);
150
+ const deleteEvent = this.getPrefixedEvent("cache:delete" /* DELETE */);
151
+ this._qified.subscribe(setEvent, {
120
152
  handler: async (message) => {
121
153
  const data = message.data;
122
154
  if (data.cacheId !== cacheId) {
@@ -124,7 +156,7 @@ var CacheableSync = class extends import_hookified.Hookified {
124
156
  }
125
157
  }
126
158
  });
127
- this._qified.subscribe("cache:delete" /* DELETE */, {
159
+ this._qified.subscribe(deleteEvent, {
128
160
  handler: async (message) => {
129
161
  const data = message.data;
130
162
  if (data.cacheId !== cacheId) {
@@ -145,6 +177,25 @@ var CacheableSync = class extends import_hookified.Hookified {
145
177
  const providers = Array.isArray(value) ? value : [value];
146
178
  return new import_qified.Qified({ messageProviders: providers });
147
179
  }
180
+ /**
181
+ * Gets the namespace prefix to use for event names
182
+ * @returns The resolved namespace string or undefined
183
+ */
184
+ getNamespace() {
185
+ if (typeof this._namespace === "function") {
186
+ return this._namespace();
187
+ }
188
+ return this._namespace;
189
+ }
190
+ /**
191
+ * Prefixes an event name with the namespace if one is set
192
+ * @param event - The event to prefix
193
+ * @returns The prefixed event name or the original event
194
+ */
195
+ getPrefixedEvent(event) {
196
+ const ns = this.getNamespace();
197
+ return ns ? `${ns}::${event}` : event;
198
+ }
148
199
  };
149
200
 
150
201
  // src/index.ts
@@ -192,7 +243,10 @@ var Cacheable = class extends import_hookified2.Hookified {
192
243
  }
193
244
  }
194
245
  if (options?.sync) {
195
- this._sync = options.sync instanceof CacheableSync ? options.sync : new CacheableSync(options.sync);
246
+ this._sync = options.sync instanceof CacheableSync ? options.sync : new CacheableSync({
247
+ ...options.sync,
248
+ namespace: options.namespace
249
+ });
196
250
  this._sync.subscribe(this._primary, this._cacheId);
197
251
  }
198
252
  }
@@ -214,6 +268,9 @@ var Cacheable = class extends import_hookified2.Hookified {
214
268
  if (this._secondary) {
215
269
  this._secondary.namespace = this.getNameSpace();
216
270
  }
271
+ if (this._sync) {
272
+ this._sync.namespace = namespace;
273
+ }
217
274
  }
218
275
  /**
219
276
  * The statistics for the cacheable instance
package/dist/index.d.cts CHANGED
@@ -22,6 +22,11 @@ type CacheableSyncOptions = {
22
22
  * Qified instance or message provider(s) for synchronization
23
23
  */
24
24
  qified: Qified | MessageProvider | MessageProvider[];
25
+ /**
26
+ * The namespace for sync events. It can be a string or a function that returns a string.
27
+ * When set, event names will be prefixed with the namespace (e.g., "my-namespace::cache:set")
28
+ */
29
+ namespace?: string | (() => string);
25
30
  } & HookifiedOptions;
26
31
  type CacheableSyncItem = {
27
32
  cacheId: string;
@@ -35,6 +40,9 @@ type CacheableSyncItem = {
35
40
  */
36
41
  declare class CacheableSync extends Hookified {
37
42
  private _qified;
43
+ private _namespace?;
44
+ private _storage?;
45
+ private _cacheId?;
38
46
  /**
39
47
  * Creates an instance of CacheableSync
40
48
  * @param options - Configuration options for CacheableSync
@@ -50,6 +58,16 @@ declare class CacheableSync extends Hookified {
50
58
  * @param value - Either an existing Qified instance or MessageProvider(s)
51
59
  */
52
60
  set qified(value: Qified | MessageProvider | MessageProvider[]);
61
+ /**
62
+ * Gets the namespace for sync events
63
+ * @returns The namespace or undefined if not set
64
+ */
65
+ get namespace(): string | (() => string) | undefined;
66
+ /**
67
+ * Sets the namespace for sync events and resubscribes if needed
68
+ * @param namespace - The namespace string or function
69
+ */
70
+ set namespace(namespace: string | (() => string) | undefined);
53
71
  /**
54
72
  * Publishes a cache event to all the cache instances
55
73
  * @param data - The cache item data containing cacheId, key, value, and optional ttl
@@ -67,6 +85,17 @@ declare class CacheableSync extends Hookified {
67
85
  * @returns A Qified instance configured with the provided message provider(s)
68
86
  */
69
87
  createQified(value: Qified | MessageProvider | MessageProvider[]): Qified;
88
+ /**
89
+ * Gets the namespace prefix to use for event names
90
+ * @returns The resolved namespace string or undefined
91
+ */
92
+ private getNamespace;
93
+ /**
94
+ * Prefixes an event name with the namespace if one is set
95
+ * @param event - The event to prefix
96
+ * @returns The prefixed event name or the original event
97
+ */
98
+ private getPrefixedEvent;
70
99
  }
71
100
 
72
101
  type CacheableOptions = {
package/dist/index.d.ts CHANGED
@@ -22,6 +22,11 @@ type CacheableSyncOptions = {
22
22
  * Qified instance or message provider(s) for synchronization
23
23
  */
24
24
  qified: Qified | MessageProvider | MessageProvider[];
25
+ /**
26
+ * The namespace for sync events. It can be a string or a function that returns a string.
27
+ * When set, event names will be prefixed with the namespace (e.g., "my-namespace::cache:set")
28
+ */
29
+ namespace?: string | (() => string);
25
30
  } & HookifiedOptions;
26
31
  type CacheableSyncItem = {
27
32
  cacheId: string;
@@ -35,6 +40,9 @@ type CacheableSyncItem = {
35
40
  */
36
41
  declare class CacheableSync extends Hookified {
37
42
  private _qified;
43
+ private _namespace?;
44
+ private _storage?;
45
+ private _cacheId?;
38
46
  /**
39
47
  * Creates an instance of CacheableSync
40
48
  * @param options - Configuration options for CacheableSync
@@ -50,6 +58,16 @@ declare class CacheableSync extends Hookified {
50
58
  * @param value - Either an existing Qified instance or MessageProvider(s)
51
59
  */
52
60
  set qified(value: Qified | MessageProvider | MessageProvider[]);
61
+ /**
62
+ * Gets the namespace for sync events
63
+ * @returns The namespace or undefined if not set
64
+ */
65
+ get namespace(): string | (() => string) | undefined;
66
+ /**
67
+ * Sets the namespace for sync events and resubscribes if needed
68
+ * @param namespace - The namespace string or function
69
+ */
70
+ set namespace(namespace: string | (() => string) | undefined);
53
71
  /**
54
72
  * Publishes a cache event to all the cache instances
55
73
  * @param data - The cache item data containing cacheId, key, value, and optional ttl
@@ -67,6 +85,17 @@ declare class CacheableSync extends Hookified {
67
85
  * @returns A Qified instance configured with the provided message provider(s)
68
86
  */
69
87
  createQified(value: Qified | MessageProvider | MessageProvider[]): Qified;
88
+ /**
89
+ * Gets the namespace prefix to use for event names
90
+ * @returns The resolved namespace string or undefined
91
+ */
92
+ private getNamespace;
93
+ /**
94
+ * Prefixes an event name with the namespace if one is set
95
+ * @param event - The event to prefix
96
+ * @returns The prefixed event name or the original event
97
+ */
98
+ private getPrefixedEvent;
70
99
  }
71
100
 
72
101
  type CacheableOptions = {
package/dist/index.js CHANGED
@@ -48,12 +48,16 @@ var CacheableSyncEvents = /* @__PURE__ */ ((CacheableSyncEvents2) => {
48
48
  })(CacheableSyncEvents || {});
49
49
  var CacheableSync = class extends Hookified {
50
50
  _qified = new Qified();
51
+ _namespace;
52
+ _storage;
53
+ _cacheId;
51
54
  /**
52
55
  * Creates an instance of CacheableSync
53
56
  * @param options - Configuration options for CacheableSync
54
57
  */
55
58
  constructor(options) {
56
59
  super(options);
60
+ this._namespace = options.namespace;
57
61
  this._qified = this.createQified(options.qified);
58
62
  }
59
63
  /**
@@ -70,12 +74,36 @@ var CacheableSync = class extends Hookified {
70
74
  set qified(value) {
71
75
  this._qified = this.createQified(value);
72
76
  }
77
+ /**
78
+ * Gets the namespace for sync events
79
+ * @returns The namespace or undefined if not set
80
+ */
81
+ get namespace() {
82
+ return this._namespace;
83
+ }
84
+ /**
85
+ * Sets the namespace for sync events and resubscribes if needed
86
+ * @param namespace - The namespace string or function
87
+ */
88
+ set namespace(namespace) {
89
+ if (this._storage && this._cacheId) {
90
+ const oldSetEvent = this.getPrefixedEvent("cache:set" /* SET */);
91
+ const oldDeleteEvent = this.getPrefixedEvent("cache:delete" /* DELETE */);
92
+ void this._qified.unsubscribe(oldSetEvent);
93
+ void this._qified.unsubscribe(oldDeleteEvent);
94
+ }
95
+ this._namespace = namespace;
96
+ if (this._storage && this._cacheId) {
97
+ this.subscribe(this._storage, this._cacheId);
98
+ }
99
+ }
73
100
  /**
74
101
  * Publishes a cache event to all the cache instances
75
102
  * @param data - The cache item data containing cacheId, key, value, and optional ttl
76
103
  */
77
104
  async publish(event, data) {
78
- await this._qified.publish(event, {
105
+ const eventName = this.getPrefixedEvent(event);
106
+ await this._qified.publish(eventName, {
79
107
  id: crypto.randomUUID(),
80
108
  data
81
109
  });
@@ -86,7 +114,11 @@ var CacheableSync = class extends Hookified {
86
114
  * @param cacheId - The cache ID to identify this instance
87
115
  */
88
116
  subscribe(storage, cacheId) {
89
- this._qified.subscribe("cache:set" /* SET */, {
117
+ this._storage = storage;
118
+ this._cacheId = cacheId;
119
+ const setEvent = this.getPrefixedEvent("cache:set" /* SET */);
120
+ const deleteEvent = this.getPrefixedEvent("cache:delete" /* DELETE */);
121
+ this._qified.subscribe(setEvent, {
90
122
  handler: async (message) => {
91
123
  const data = message.data;
92
124
  if (data.cacheId !== cacheId) {
@@ -94,7 +126,7 @@ var CacheableSync = class extends Hookified {
94
126
  }
95
127
  }
96
128
  });
97
- this._qified.subscribe("cache:delete" /* DELETE */, {
129
+ this._qified.subscribe(deleteEvent, {
98
130
  handler: async (message) => {
99
131
  const data = message.data;
100
132
  if (data.cacheId !== cacheId) {
@@ -115,6 +147,25 @@ var CacheableSync = class extends Hookified {
115
147
  const providers = Array.isArray(value) ? value : [value];
116
148
  return new Qified({ messageProviders: providers });
117
149
  }
150
+ /**
151
+ * Gets the namespace prefix to use for event names
152
+ * @returns The resolved namespace string or undefined
153
+ */
154
+ getNamespace() {
155
+ if (typeof this._namespace === "function") {
156
+ return this._namespace();
157
+ }
158
+ return this._namespace;
159
+ }
160
+ /**
161
+ * Prefixes an event name with the namespace if one is set
162
+ * @param event - The event to prefix
163
+ * @returns The prefixed event name or the original event
164
+ */
165
+ getPrefixedEvent(event) {
166
+ const ns = this.getNamespace();
167
+ return ns ? `${ns}::${event}` : event;
168
+ }
118
169
  };
119
170
 
120
171
  // src/index.ts
@@ -177,7 +228,10 @@ var Cacheable = class extends Hookified2 {
177
228
  }
178
229
  }
179
230
  if (options?.sync) {
180
- this._sync = options.sync instanceof CacheableSync ? options.sync : new CacheableSync(options.sync);
231
+ this._sync = options.sync instanceof CacheableSync ? options.sync : new CacheableSync({
232
+ ...options.sync,
233
+ namespace: options.namespace
234
+ });
181
235
  this._sync.subscribe(this._primary, this._cacheId);
182
236
  }
183
237
  }
@@ -199,6 +253,9 @@ var Cacheable = class extends Hookified2 {
199
253
  if (this._secondary) {
200
254
  this._secondary.namespace = this.getNameSpace();
201
255
  }
256
+ if (this._sync) {
257
+ this._sync.namespace = namespace;
258
+ }
202
259
  }
203
260
  /**
204
261
  * The statistics for the cacheable instance
package/package.json CHANGED
@@ -1,15 +1,21 @@
1
1
  {
2
2
  "name": "cacheable",
3
- "version": "2.2.0",
3
+ "version": "2.3.1",
4
4
  "description": "High Performance Layer 1 / Layer 2 Caching with Keyv Storage",
5
5
  "type": "module",
6
- "main": "./dist/index.cjs",
6
+ "main": "./dist/index.js",
7
7
  "module": "./dist/index.js",
8
8
  "types": "./dist/index.d.ts",
9
9
  "exports": {
10
10
  ".": {
11
- "require": "./dist/index.cjs",
12
- "import": "./dist/index.js"
11
+ "import": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "require": {
16
+ "types": "./dist/index.d.cts",
17
+ "default": "./dist/index.cjs"
18
+ }
13
19
  }
14
20
  },
15
21
  "repository": {
@@ -21,25 +27,25 @@
21
27
  "license": "MIT",
22
28
  "private": false,
23
29
  "devDependencies": {
24
- "@biomejs/biome": "^2.3.5",
30
+ "@biomejs/biome": "^2.3.8",
25
31
  "@faker-js/faker": "^10.1.0",
26
- "@keyv/redis": "^5.1.4",
32
+ "@keyv/redis": "^5.1.5",
27
33
  "@keyv/valkey": "^1.0.11",
28
- "@qified/redis": "^0.5.2",
29
- "@types/node": "^24.10.1",
30
- "@vitest/coverage-v8": "^4.0.9",
31
- "lru-cache": "^11.2.2",
32
- "rimraf": "^6.1.0",
34
+ "@qified/redis": "^0.5.3",
35
+ "@types/node": "^25.0.2",
36
+ "@vitest/coverage-v8": "^4.0.15",
37
+ "lru-cache": "^11.2.4",
38
+ "rimraf": "^6.1.2",
33
39
  "tsup": "^8.5.1",
34
40
  "typescript": "^5.9.3",
35
- "vitest": "^4.0.9"
41
+ "vitest": "^4.0.15"
36
42
  },
37
43
  "dependencies": {
38
- "hookified": "^1.13.0",
39
- "keyv": "^5.5.4",
40
- "qified": "^0.5.2",
41
- "@cacheable/memory": "^2.0.5",
42
- "@cacheable/utils": "^2.3.0"
44
+ "hookified": "^1.14.0",
45
+ "keyv": "^5.5.5",
46
+ "qified": "^0.5.3",
47
+ "@cacheable/memory": "^2.0.6",
48
+ "@cacheable/utils": "^2.3.2"
43
49
  },
44
50
  "keywords": [
45
51
  "cacheable",