axios-cache-nats-adapter 1.0.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Yasser Ouaftouh
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,257 @@
1
+ # axios-cache-nats-adapter
2
+
3
+ A NATS storage adapter for [axios-cache-interceptor](https://axios-cache-interceptor.js.org/), enabling distributed caching using NATS JetStream Key-Value store.
4
+
5
+ ## Features
6
+
7
+ - ๐Ÿš€ **NATS JetStream KV Store** - Persistent, distributed caching
8
+ - ๐Ÿ“ฆ **Dual Module Support** - Works with both ESM and CommonJS
9
+ - ๐Ÿ”ง **Flexible Configuration** - Customizable bucket, TTL, and serialization
10
+ - ๐Ÿ”‘ **Key Prefixing** - Namespace support for multiple applications
11
+ - โšก **TypeScript** - Full type safety and IntelliSense support
12
+ - ๐Ÿงช **Well Tested** - Comprehensive test coverage
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install axios-cache-nats-adapter axios-cache-interceptor nats
18
+ ```
19
+
20
+ or
21
+
22
+ ```bash
23
+ yarn add axios-cache-nats-adapter axios-cache-interceptor nats
24
+ ```
25
+
26
+ or
27
+
28
+ ```bash
29
+ pnpm add axios-cache-nats-adapter axios-cache-interceptor nats
30
+ ```
31
+
32
+ ## Quick Start
33
+
34
+ ### ESM
35
+
36
+ ```typescript
37
+ import axios from 'axios';
38
+ import { setupCache } from 'axios-cache-interceptor';
39
+ import { NatsAdapter } from 'axios-cache-nats-adapter';
40
+
41
+ // Create the NATS adapter
42
+ const adapter = new NatsAdapter({
43
+ natsOptions: {
44
+ servers: 'nats://localhost:4222',
45
+ },
46
+ bucket: 'axios-cache',
47
+ ttl: 60000, // 1 minute default TTL
48
+ });
49
+
50
+ // Setup axios with cache
51
+ const cachedAxios = setupCache(axios, {
52
+ storage: adapter,
53
+ });
54
+
55
+ // Use it like normal axios
56
+ const response = await cachedAxios.get('https://api.example.com/data');
57
+ console.log(response.cached); // false on first request, true on subsequent
58
+
59
+ // Clean up when done
60
+ await adapter.close();
61
+ ```
62
+
63
+ ### CommonJS
64
+
65
+ ```javascript
66
+ const axios = require('axios');
67
+ const { setupCache } = require('axios-cache-interceptor');
68
+ const { NatsAdapter } = require('axios-cache-nats-adapter');
69
+
70
+ const adapter = new NatsAdapter({
71
+ natsOptions: {
72
+ servers: 'nats://localhost:4222',
73
+ },
74
+ bucket: 'axios-cache',
75
+ });
76
+
77
+ const cachedAxios = setupCache(axios, {
78
+ storage: adapter,
79
+ });
80
+
81
+ // Use it...
82
+ ```
83
+
84
+ ## Configuration Options
85
+
86
+ ### NatsAdapterOptions
87
+
88
+ ```typescript
89
+ interface NatsAdapterOptions {
90
+ /**
91
+ * NATS connection options
92
+ * @default { servers: 'nats://localhost:4222' }
93
+ */
94
+ natsOptions?: ConnectionOptions;
95
+
96
+ /**
97
+ * Name of the JetStream KV bucket to use for caching
98
+ * @default 'axios-cache'
99
+ */
100
+ bucket?: string;
101
+
102
+ /**
103
+ * Prefix to prepend to all cache keys for namespacing
104
+ * @default ''
105
+ */
106
+ keyPrefix?: string;
107
+
108
+ /**
109
+ * Default TTL (time-to-live) in milliseconds for cached values
110
+ * Individual cache entries can override this
111
+ * @default 0 (no expiration)
112
+ */
113
+ ttl?: number;
114
+
115
+ /**
116
+ * Custom serializer for encoding/decoding cached values
117
+ * @default JsonSerializer
118
+ */
119
+ serializer?: Serializer;
120
+
121
+ /**
122
+ * Maximum number of history entries to keep in the KV bucket
123
+ * @default 1
124
+ */
125
+ maxHistory?: number;
126
+
127
+ /**
128
+ * Whether to automatically create the KV bucket if it doesn't exist
129
+ * @default true
130
+ */
131
+ autoCreateBucket?: boolean;
132
+ }
133
+ ```
134
+
135
+ ## Advanced Usage
136
+
137
+ ### Custom Serializer
138
+
139
+ You can provide a custom serializer for advanced use cases:
140
+
141
+ ```typescript
142
+ import { NatsAdapter, Serializer } from 'axios-cache-nats-adapter';
143
+ import type { StorageValue } from 'axios-cache-interceptor';
144
+
145
+ class CustomSerializer implements Serializer {
146
+ serialize(value: StorageValue): Uint8Array {
147
+ // Your custom serialization logic
148
+ return new TextEncoder().encode(JSON.stringify(value));
149
+ }
150
+
151
+ deserialize(data: Uint8Array): StorageValue {
152
+ // Your custom deserialization logic
153
+ return JSON.parse(new TextDecoder().decode(data));
154
+ }
155
+ }
156
+
157
+ const adapter = new NatsAdapter({
158
+ serializer: new CustomSerializer(),
159
+ });
160
+ ```
161
+
162
+ ### Multiple Applications
163
+
164
+ Use key prefixes to isolate caches for different applications:
165
+
166
+ ```typescript
167
+ // Application 1
168
+ const adapter1 = new NatsAdapter({
169
+ bucket: 'shared-cache',
170
+ keyPrefix: 'app1:',
171
+ });
172
+
173
+ // Application 2
174
+ const adapter2 = new NatsAdapter({
175
+ bucket: 'shared-cache',
176
+ keyPrefix: 'app2:',
177
+ });
178
+ ```
179
+
180
+ ### Per-Request TTL
181
+
182
+ You can override the default TTL per request:
183
+
184
+ ```typescript
185
+ const cachedAxios = setupCache(axios, {
186
+ storage: adapter,
187
+ });
188
+
189
+ // Cache for 5 minutes
190
+ await cachedAxios.get('https://api.example.com/data', {
191
+ cache: {
192
+ ttl: 5 * 60 * 1000,
193
+ },
194
+ });
195
+ ```
196
+
197
+ ### Connection Management
198
+
199
+ Always clean up resources when done:
200
+
201
+ ```typescript
202
+ const adapter = new NatsAdapter({ /* options */ });
203
+
204
+ try {
205
+ // Use the adapter...
206
+ } finally {
207
+ await adapter.close();
208
+ }
209
+ ```
210
+
211
+ ## Requirements
212
+
213
+ - Node.js >= 16.0.0
214
+ - A running NATS server with JetStream enabled
215
+
216
+ ## Running NATS Server
217
+
218
+ ### Using Docker
219
+
220
+ ```bash
221
+ docker run -p 4222:4222 nats:latest -js
222
+ ```
223
+
224
+ ### Using NATS CLI
225
+
226
+ ```bash
227
+ nats-server -js
228
+ ```
229
+
230
+ ## API Reference
231
+
232
+ ### NatsAdapter
233
+
234
+ #### Methods
235
+
236
+ - `get(key: string): Promise<StorageValue | undefined>` - Get a cached value
237
+ - `set(key: string, value: StorageValue, requestConfig?: CacheProperties): Promise<void>` - Set a cached value
238
+ - `remove(key: string): Promise<void>` - Remove a cached value
239
+ - `find(predicate: (key: string, value: StorageValue) => boolean): Promise<StorageValue[]>` - Find cached values matching a predicate
240
+ - `close(): Promise<void>` - Close the NATS connection
241
+
242
+ ## Contributing
243
+
244
+ Contributions are welcome! Please feel free to submit a Pull Request.
245
+
246
+ ## License
247
+
248
+ MIT ยฉ [Yasser Ouaftouh](https://github.com/youaftouh)
249
+
250
+ ## Related Projects
251
+
252
+ - [axios-cache-interceptor](https://github.com/arthurfiorette/axios-cache-interceptor) - The cache interceptor this adapter is built for
253
+ - [nats.js](https://github.com/nats-io/nats.js) - NATS client for Node.js
254
+
255
+ ## Support
256
+
257
+ If you encounter any issues or have questions, please [open an issue](https://github.com/youaftouh/axios-cache-nats-adapter/issues) on GitHub.
@@ -0,0 +1,45 @@
1
+ import { StorageValue, AxiosStorage, CacheRequestConfig, NotEmptyStorageValue } from 'axios-cache-interceptor';
2
+ import { ConnectionOptions, KV } from 'nats';
3
+
4
+ interface Serializer {
5
+ serialize(value: StorageValue): Uint8Array;
6
+ deserialize(data: Uint8Array): StorageValue;
7
+ }
8
+ interface NatsAdapterOptions {
9
+ natsOptions?: ConnectionOptions;
10
+ bucket?: string;
11
+ keyPrefix?: string;
12
+ ttl?: number;
13
+ serializer?: Serializer;
14
+ maxHistory?: number;
15
+ autoCreateBucket?: boolean;
16
+ }
17
+ interface NatsAdapterState {
18
+ kv: KV | null;
19
+ connected: boolean;
20
+ }
21
+
22
+ declare class NatsAdapter implements AxiosStorage {
23
+ private connection;
24
+ private state;
25
+ private readonly options;
26
+ private readonly serializer;
27
+ private initPromise;
28
+ constructor(options?: NatsAdapterOptions);
29
+ private initialize;
30
+ private _doInitialize;
31
+ private buildKey;
32
+ get(key: string, _currentRequest?: CacheRequestConfig): Promise<StorageValue>;
33
+ set(key: string, value: NotEmptyStorageValue, currentRequest?: CacheRequestConfig): Promise<void>;
34
+ remove(key: string): Promise<void>;
35
+ find(predicate: (key: string, value: StorageValue) => boolean): Promise<StorageValue[]>;
36
+ close(): Promise<void>;
37
+ }
38
+
39
+ declare class JsonSerializer implements Serializer {
40
+ serialize(value: StorageValue): Uint8Array;
41
+ deserialize(data: Uint8Array): StorageValue;
42
+ }
43
+ declare function getDefaultSerializer(): Serializer;
44
+
45
+ export { JsonSerializer, NatsAdapter, type NatsAdapterOptions, type NatsAdapterState, type Serializer, getDefaultSerializer };
@@ -0,0 +1,45 @@
1
+ import { StorageValue, AxiosStorage, CacheRequestConfig, NotEmptyStorageValue } from 'axios-cache-interceptor';
2
+ import { ConnectionOptions, KV } from 'nats';
3
+
4
+ interface Serializer {
5
+ serialize(value: StorageValue): Uint8Array;
6
+ deserialize(data: Uint8Array): StorageValue;
7
+ }
8
+ interface NatsAdapterOptions {
9
+ natsOptions?: ConnectionOptions;
10
+ bucket?: string;
11
+ keyPrefix?: string;
12
+ ttl?: number;
13
+ serializer?: Serializer;
14
+ maxHistory?: number;
15
+ autoCreateBucket?: boolean;
16
+ }
17
+ interface NatsAdapterState {
18
+ kv: KV | null;
19
+ connected: boolean;
20
+ }
21
+
22
+ declare class NatsAdapter implements AxiosStorage {
23
+ private connection;
24
+ private state;
25
+ private readonly options;
26
+ private readonly serializer;
27
+ private initPromise;
28
+ constructor(options?: NatsAdapterOptions);
29
+ private initialize;
30
+ private _doInitialize;
31
+ private buildKey;
32
+ get(key: string, _currentRequest?: CacheRequestConfig): Promise<StorageValue>;
33
+ set(key: string, value: NotEmptyStorageValue, currentRequest?: CacheRequestConfig): Promise<void>;
34
+ remove(key: string): Promise<void>;
35
+ find(predicate: (key: string, value: StorageValue) => boolean): Promise<StorageValue[]>;
36
+ close(): Promise<void>;
37
+ }
38
+
39
+ declare class JsonSerializer implements Serializer {
40
+ serialize(value: StorageValue): Uint8Array;
41
+ deserialize(data: Uint8Array): StorageValue;
42
+ }
43
+ declare function getDefaultSerializer(): Serializer;
44
+
45
+ export { JsonSerializer, NatsAdapter, type NatsAdapterOptions, type NatsAdapterState, type Serializer, getDefaultSerializer };
package/dist/index.js ADDED
@@ -0,0 +1,235 @@
1
+ 'use strict';
2
+
3
+ var nats = require('nats');
4
+
5
+ // src/adapter.ts
6
+
7
+ // src/serializer.ts
8
+ var JsonSerializer = class {
9
+ /**
10
+ * Serialize a StorageValue to a Uint8Array using JSON
11
+ */
12
+ serialize(value) {
13
+ try {
14
+ const json = JSON.stringify(value);
15
+ return new TextEncoder().encode(json);
16
+ } catch (error) {
17
+ throw new Error(
18
+ `Failed to serialize value: ${error instanceof Error ? error.message : String(error)}`
19
+ );
20
+ }
21
+ }
22
+ /**
23
+ * Deserialize a Uint8Array to a StorageValue using JSON
24
+ */
25
+ deserialize(data) {
26
+ try {
27
+ const json = new TextDecoder().decode(data);
28
+ return JSON.parse(json);
29
+ } catch (error) {
30
+ throw new Error(
31
+ `Failed to deserialize value: ${error instanceof Error ? error.message : String(error)}`
32
+ );
33
+ }
34
+ }
35
+ };
36
+ function getDefaultSerializer() {
37
+ return new JsonSerializer();
38
+ }
39
+
40
+ // src/adapter.ts
41
+ var NatsAdapter = class {
42
+ constructor(options = {}) {
43
+ this.connection = null;
44
+ this.state = {
45
+ kv: null,
46
+ connected: false
47
+ };
48
+ this.initPromise = null;
49
+ this.options = {
50
+ natsOptions: options.natsOptions || {},
51
+ bucket: options.bucket || "axios-cache",
52
+ keyPrefix: options.keyPrefix || "",
53
+ ttl: options.ttl || 0,
54
+ serializer: options.serializer || getDefaultSerializer(),
55
+ maxHistory: options.maxHistory || 1,
56
+ autoCreateBucket: options.autoCreateBucket !== false
57
+ };
58
+ this.serializer = this.options.serializer;
59
+ }
60
+ /**
61
+ * Initialize the NATS connection and KV bucket
62
+ */
63
+ async initialize() {
64
+ if (this.state.connected && this.state.kv) {
65
+ return;
66
+ }
67
+ if (this.initPromise) {
68
+ return this.initPromise;
69
+ }
70
+ this.initPromise = this._doInitialize();
71
+ return this.initPromise;
72
+ }
73
+ async _doInitialize() {
74
+ try {
75
+ this.connection = await nats.connect(this.options.natsOptions);
76
+ const jsm = await this.connection.jetstreamManager();
77
+ let kvExists = false;
78
+ try {
79
+ await jsm.streams.info(`KV_${this.options.bucket}`);
80
+ kvExists = true;
81
+ } catch (error) {
82
+ kvExists = false;
83
+ }
84
+ if (!kvExists) {
85
+ if (this.options.autoCreateBucket) {
86
+ await jsm.streams.add({
87
+ name: `KV_${this.options.bucket}`,
88
+ subjects: [`$KV.${this.options.bucket}.>`],
89
+ max_msgs_per_subject: this.options.maxHistory
90
+ });
91
+ } else {
92
+ throw new Error(
93
+ `KV bucket '${this.options.bucket}' does not exist and autoCreateBucket is disabled`
94
+ );
95
+ }
96
+ }
97
+ const js = this.connection.jetstream();
98
+ this.state.kv = await js.views.kv(this.options.bucket);
99
+ this.state.connected = true;
100
+ } catch (error) {
101
+ this.initPromise = null;
102
+ throw new Error(
103
+ `Failed to initialize NATS adapter: ${error instanceof Error ? error.message : String(error)}`
104
+ );
105
+ }
106
+ }
107
+ /**
108
+ * Build the full key with prefix
109
+ */
110
+ buildKey(key) {
111
+ return this.options.keyPrefix ? `${this.options.keyPrefix}${key}` : key;
112
+ }
113
+ /**
114
+ * Get a value from the cache
115
+ */
116
+ async get(key, _currentRequest) {
117
+ try {
118
+ await this.initialize();
119
+ if (!this.state.kv) {
120
+ throw new Error("KV store not initialized");
121
+ }
122
+ const fullKey = this.buildKey(key);
123
+ const entry = await this.state.kv.get(fullKey);
124
+ if (!entry || entry.operation === "DEL" || entry.operation === "PURGE") {
125
+ return void 0;
126
+ }
127
+ const value = this.serializer.deserialize(entry.value);
128
+ return value;
129
+ } catch (error) {
130
+ console.error(`Error getting key '${key}' from NATS:`, error);
131
+ return void 0;
132
+ }
133
+ }
134
+ /**
135
+ * Set a value in the cache
136
+ */
137
+ async set(key, value, currentRequest) {
138
+ try {
139
+ await this.initialize();
140
+ if (!this.state.kv) {
141
+ throw new Error("KV store not initialized");
142
+ }
143
+ const fullKey = this.buildKey(key);
144
+ const serialized = this.serializer.serialize(value);
145
+ let ttl = this.options.ttl;
146
+ if (currentRequest?.cache && typeof currentRequest.cache === "object") {
147
+ const cacheConfig = currentRequest.cache;
148
+ if (typeof cacheConfig.ttl === "number") {
149
+ ttl = cacheConfig.ttl;
150
+ }
151
+ }
152
+ if (ttl > 0) {
153
+ const ttlNanos = ttl * 1e6;
154
+ await this.state.kv.put(fullKey, serialized, { ttl: ttlNanos });
155
+ } else {
156
+ await this.state.kv.put(fullKey, serialized);
157
+ }
158
+ } catch (error) {
159
+ console.error(`Error setting key '${key}' in NATS:`, error);
160
+ throw error;
161
+ }
162
+ }
163
+ /**
164
+ * Remove a value from the cache
165
+ */
166
+ async remove(key) {
167
+ try {
168
+ await this.initialize();
169
+ if (!this.state.kv) {
170
+ throw new Error("KV store not initialized");
171
+ }
172
+ const fullKey = this.buildKey(key);
173
+ await this.state.kv.delete(fullKey);
174
+ } catch (error) {
175
+ console.error(`Error removing key '${key}' from NATS:`, error);
176
+ }
177
+ }
178
+ /**
179
+ * Find entries matching a given criteria
180
+ */
181
+ async find(predicate) {
182
+ try {
183
+ await this.initialize();
184
+ if (!this.state.kv) {
185
+ throw new Error("KV store not initialized");
186
+ }
187
+ const results = [];
188
+ const keys = await this.state.kv.keys();
189
+ for await (const key of keys) {
190
+ if (this.options.keyPrefix && !key.startsWith(this.options.keyPrefix)) {
191
+ continue;
192
+ }
193
+ try {
194
+ const entry = await this.state.kv.get(key);
195
+ if (entry && entry.operation !== "DEL" && entry.operation !== "PURGE") {
196
+ const value = this.serializer.deserialize(entry.value);
197
+ const originalKey = this.options.keyPrefix ? key.substring(this.options.keyPrefix.length) : key;
198
+ if (predicate(originalKey, value)) {
199
+ results.push(value);
200
+ }
201
+ }
202
+ } catch (error) {
203
+ console.error(`Error reading key '${key}' during find:`, error);
204
+ }
205
+ }
206
+ return results;
207
+ } catch (error) {
208
+ console.error("Error finding keys in NATS:", error);
209
+ return [];
210
+ }
211
+ }
212
+ /**
213
+ * Close the NATS connection
214
+ */
215
+ async close() {
216
+ try {
217
+ if (this.connection) {
218
+ await this.connection.drain();
219
+ await this.connection.close();
220
+ this.connection = null;
221
+ this.state.kv = null;
222
+ this.state.connected = false;
223
+ this.initPromise = null;
224
+ }
225
+ } catch (error) {
226
+ console.error("Error closing NATS connection:", error);
227
+ }
228
+ }
229
+ };
230
+
231
+ exports.JsonSerializer = JsonSerializer;
232
+ exports.NatsAdapter = NatsAdapter;
233
+ exports.getDefaultSerializer = getDefaultSerializer;
234
+ //# sourceMappingURL=index.js.map
235
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/serializer.ts","../src/adapter.ts"],"names":["connect"],"mappings":"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","file":"index.js","sourcesContent":["import type { StorageValue } from 'axios-cache-interceptor';\nimport type { Serializer } from './types';\n\n/**\n * Default JSON-based serializer for cache values\n */\nexport class JsonSerializer implements Serializer {\n /**\n * Serialize a StorageValue to a Uint8Array using JSON\n */\n serialize(value: StorageValue): Uint8Array {\n try {\n const json = JSON.stringify(value);\n return new TextEncoder().encode(json);\n } catch (error) {\n throw new Error(\n `Failed to serialize value: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n\n /**\n * Deserialize a Uint8Array to a StorageValue using JSON\n */\n deserialize(data: Uint8Array): StorageValue {\n try {\n const json = new TextDecoder().decode(data);\n return JSON.parse(json) as StorageValue;\n } catch (error) {\n throw new Error(\n `Failed to deserialize value: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n}\n\n/**\n * Get the default serializer instance\n */\nexport function getDefaultSerializer(): Serializer {\n return new JsonSerializer();\n}\n","import { connect, NatsConnection } from 'nats';\nimport type { AxiosStorage, StorageValue, NotEmptyStorageValue, CacheRequestConfig } from 'axios-cache-interceptor';\nimport type { NatsAdapterOptions, NatsAdapterState, Serializer } from './types';\nimport { getDefaultSerializer } from './serializer';\n\n/**\n * NATS storage adapter for axios-cache-interceptor\n * Uses NATS JetStream Key-Value store for persistent caching\n */\nexport class NatsAdapter implements AxiosStorage {\n private connection: NatsConnection | null = null;\n private state: NatsAdapterState = {\n kv: null,\n connected: false,\n };\n private readonly options: Required<NatsAdapterOptions>;\n private readonly serializer: Serializer;\n private initPromise: Promise<void> | null = null;\n\n constructor(options: NatsAdapterOptions = {}) {\n this.options = {\n natsOptions: options.natsOptions || {},\n bucket: options.bucket || 'axios-cache',\n keyPrefix: options.keyPrefix || '',\n ttl: options.ttl || 0,\n serializer: options.serializer || getDefaultSerializer(),\n maxHistory: options.maxHistory || 1,\n autoCreateBucket: options.autoCreateBucket !== false,\n };\n this.serializer = this.options.serializer;\n }\n\n /**\n * Initialize the NATS connection and KV bucket\n */\n private async initialize(): Promise<void> {\n if (this.state.connected && this.state.kv) {\n return;\n }\n\n if (this.initPromise) {\n return this.initPromise;\n }\n\n this.initPromise = this._doInitialize();\n return this.initPromise;\n }\n\n private async _doInitialize(): Promise<void> {\n try {\n // Connect to NATS\n this.connection = await connect(this.options.natsOptions);\n\n // Get JetStream manager\n const jsm = await this.connection.jetstreamManager();\n\n // Check if bucket exists\n let kvExists = false;\n try {\n await jsm.streams.info(`KV_${this.options.bucket}`);\n kvExists = true;\n } catch (error) {\n // Bucket doesn't exist\n kvExists = false;\n }\n\n // Create bucket if it doesn't exist and autoCreateBucket is true\n if (!kvExists) {\n if (this.options.autoCreateBucket) {\n await jsm.streams.add({\n name: `KV_${this.options.bucket}`,\n subjects: [`$KV.${this.options.bucket}.>`],\n max_msgs_per_subject: this.options.maxHistory,\n });\n } else {\n throw new Error(\n `KV bucket '${this.options.bucket}' does not exist and autoCreateBucket is disabled`\n );\n }\n }\n\n // Get KV handle\n const js = this.connection.jetstream();\n this.state.kv = await js.views.kv(this.options.bucket);\n this.state.connected = true;\n } catch (error) {\n this.initPromise = null;\n throw new Error(\n `Failed to initialize NATS adapter: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n\n /**\n * Build the full key with prefix\n */\n private buildKey(key: string): string {\n return this.options.keyPrefix ? `${this.options.keyPrefix}${key}` : key;\n }\n\n /**\n * Get a value from the cache\n */\n async get(key: string, _currentRequest?: CacheRequestConfig): Promise<StorageValue> {\n try {\n await this.initialize();\n\n if (!this.state.kv) {\n throw new Error('KV store not initialized');\n }\n\n const fullKey = this.buildKey(key);\n\n const entry = await this.state.kv.get(fullKey);\n\n if (!entry || entry.operation === 'DEL' || entry.operation === 'PURGE') {\n return undefined as unknown as StorageValue;\n }\n\n const value = this.serializer.deserialize(entry.value);\n return value;\n } catch (error) {\n // Key doesn't exist or other error\n console.error(`Error getting key '${key}' from NATS:`, error);\n return undefined as unknown as StorageValue;\n }\n }\n\n /**\n * Set a value in the cache\n */\n async set(key: string, value: NotEmptyStorageValue, currentRequest?: CacheRequestConfig): Promise<void> {\n try {\n await this.initialize();\n\n if (!this.state.kv) {\n throw new Error('KV store not initialized');\n }\n\n const fullKey = this.buildKey(key);\n const serialized = this.serializer.serialize(value);\n\n // Determine TTL - use from currentRequest if available, otherwise use default\n let ttl = this.options.ttl;\n if (currentRequest?.cache && typeof currentRequest.cache === 'object') {\n const cacheConfig = currentRequest.cache;\n if (typeof cacheConfig.ttl === 'number') {\n ttl = cacheConfig.ttl;\n }\n }\n\n // Convert milliseconds to nanoseconds for NATS\n // NATS KV ttl is in nanoseconds\n if (ttl > 0) {\n const ttlNanos = ttl * 1_000_000;\n await this.state.kv.put(fullKey, serialized, { ttl: ttlNanos } as any);\n } else {\n await this.state.kv.put(fullKey, serialized);\n }\n } catch (error) {\n console.error(`Error setting key '${key}' in NATS:`, error);\n throw error;\n }\n }\n\n /**\n * Remove a value from the cache\n */\n async remove(key: string): Promise<void> {\n try {\n await this.initialize();\n\n if (!this.state.kv) {\n throw new Error('KV store not initialized');\n }\n\n const fullKey = this.buildKey(key);\n await this.state.kv.delete(fullKey);\n } catch (error) {\n console.error(`Error removing key '${key}' from NATS:`, error);\n }\n }\n\n /**\n * Find entries matching a given criteria\n */\n async find(predicate: (key: string, value: StorageValue) => boolean): Promise<StorageValue[]> {\n try {\n await this.initialize();\n\n if (!this.state.kv) {\n throw new Error('KV store not initialized');\n }\n\n const results: StorageValue[] = [];\n const keys = await this.state.kv.keys();\n\n for await (const key of keys) {\n // Filter by prefix if set\n if (this.options.keyPrefix && !key.startsWith(this.options.keyPrefix)) {\n continue;\n }\n\n try {\n const entry = await this.state.kv.get(key);\n if (entry && entry.operation !== 'DEL' && entry.operation !== 'PURGE') {\n const value = this.serializer.deserialize(entry.value);\n // Remove prefix from key for predicate\n const originalKey = this.options.keyPrefix\n ? key.substring(this.options.keyPrefix.length)\n : key;\n if (predicate(originalKey, value)) {\n results.push(value);\n }\n }\n } catch (error) {\n console.error(`Error reading key '${key}' during find:`, error);\n }\n }\n\n return results;\n } catch (error) {\n console.error('Error finding keys in NATS:', error);\n return [];\n }\n }\n\n /**\n * Close the NATS connection\n */\n async close(): Promise<void> {\n try {\n if (this.connection) {\n await this.connection.drain();\n await this.connection.close();\n this.connection = null;\n this.state.kv = null;\n this.state.connected = false;\n this.initPromise = null;\n }\n } catch (error) {\n console.error('Error closing NATS connection:', error);\n }\n }\n}\n"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,231 @@
1
+ import { connect } from 'nats';
2
+
3
+ // src/adapter.ts
4
+
5
+ // src/serializer.ts
6
+ var JsonSerializer = class {
7
+ /**
8
+ * Serialize a StorageValue to a Uint8Array using JSON
9
+ */
10
+ serialize(value) {
11
+ try {
12
+ const json = JSON.stringify(value);
13
+ return new TextEncoder().encode(json);
14
+ } catch (error) {
15
+ throw new Error(
16
+ `Failed to serialize value: ${error instanceof Error ? error.message : String(error)}`
17
+ );
18
+ }
19
+ }
20
+ /**
21
+ * Deserialize a Uint8Array to a StorageValue using JSON
22
+ */
23
+ deserialize(data) {
24
+ try {
25
+ const json = new TextDecoder().decode(data);
26
+ return JSON.parse(json);
27
+ } catch (error) {
28
+ throw new Error(
29
+ `Failed to deserialize value: ${error instanceof Error ? error.message : String(error)}`
30
+ );
31
+ }
32
+ }
33
+ };
34
+ function getDefaultSerializer() {
35
+ return new JsonSerializer();
36
+ }
37
+
38
+ // src/adapter.ts
39
+ var NatsAdapter = class {
40
+ constructor(options = {}) {
41
+ this.connection = null;
42
+ this.state = {
43
+ kv: null,
44
+ connected: false
45
+ };
46
+ this.initPromise = null;
47
+ this.options = {
48
+ natsOptions: options.natsOptions || {},
49
+ bucket: options.bucket || "axios-cache",
50
+ keyPrefix: options.keyPrefix || "",
51
+ ttl: options.ttl || 0,
52
+ serializer: options.serializer || getDefaultSerializer(),
53
+ maxHistory: options.maxHistory || 1,
54
+ autoCreateBucket: options.autoCreateBucket !== false
55
+ };
56
+ this.serializer = this.options.serializer;
57
+ }
58
+ /**
59
+ * Initialize the NATS connection and KV bucket
60
+ */
61
+ async initialize() {
62
+ if (this.state.connected && this.state.kv) {
63
+ return;
64
+ }
65
+ if (this.initPromise) {
66
+ return this.initPromise;
67
+ }
68
+ this.initPromise = this._doInitialize();
69
+ return this.initPromise;
70
+ }
71
+ async _doInitialize() {
72
+ try {
73
+ this.connection = await connect(this.options.natsOptions);
74
+ const jsm = await this.connection.jetstreamManager();
75
+ let kvExists = false;
76
+ try {
77
+ await jsm.streams.info(`KV_${this.options.bucket}`);
78
+ kvExists = true;
79
+ } catch (error) {
80
+ kvExists = false;
81
+ }
82
+ if (!kvExists) {
83
+ if (this.options.autoCreateBucket) {
84
+ await jsm.streams.add({
85
+ name: `KV_${this.options.bucket}`,
86
+ subjects: [`$KV.${this.options.bucket}.>`],
87
+ max_msgs_per_subject: this.options.maxHistory
88
+ });
89
+ } else {
90
+ throw new Error(
91
+ `KV bucket '${this.options.bucket}' does not exist and autoCreateBucket is disabled`
92
+ );
93
+ }
94
+ }
95
+ const js = this.connection.jetstream();
96
+ this.state.kv = await js.views.kv(this.options.bucket);
97
+ this.state.connected = true;
98
+ } catch (error) {
99
+ this.initPromise = null;
100
+ throw new Error(
101
+ `Failed to initialize NATS adapter: ${error instanceof Error ? error.message : String(error)}`
102
+ );
103
+ }
104
+ }
105
+ /**
106
+ * Build the full key with prefix
107
+ */
108
+ buildKey(key) {
109
+ return this.options.keyPrefix ? `${this.options.keyPrefix}${key}` : key;
110
+ }
111
+ /**
112
+ * Get a value from the cache
113
+ */
114
+ async get(key, _currentRequest) {
115
+ try {
116
+ await this.initialize();
117
+ if (!this.state.kv) {
118
+ throw new Error("KV store not initialized");
119
+ }
120
+ const fullKey = this.buildKey(key);
121
+ const entry = await this.state.kv.get(fullKey);
122
+ if (!entry || entry.operation === "DEL" || entry.operation === "PURGE") {
123
+ return void 0;
124
+ }
125
+ const value = this.serializer.deserialize(entry.value);
126
+ return value;
127
+ } catch (error) {
128
+ console.error(`Error getting key '${key}' from NATS:`, error);
129
+ return void 0;
130
+ }
131
+ }
132
+ /**
133
+ * Set a value in the cache
134
+ */
135
+ async set(key, value, currentRequest) {
136
+ try {
137
+ await this.initialize();
138
+ if (!this.state.kv) {
139
+ throw new Error("KV store not initialized");
140
+ }
141
+ const fullKey = this.buildKey(key);
142
+ const serialized = this.serializer.serialize(value);
143
+ let ttl = this.options.ttl;
144
+ if (currentRequest?.cache && typeof currentRequest.cache === "object") {
145
+ const cacheConfig = currentRequest.cache;
146
+ if (typeof cacheConfig.ttl === "number") {
147
+ ttl = cacheConfig.ttl;
148
+ }
149
+ }
150
+ if (ttl > 0) {
151
+ const ttlNanos = ttl * 1e6;
152
+ await this.state.kv.put(fullKey, serialized, { ttl: ttlNanos });
153
+ } else {
154
+ await this.state.kv.put(fullKey, serialized);
155
+ }
156
+ } catch (error) {
157
+ console.error(`Error setting key '${key}' in NATS:`, error);
158
+ throw error;
159
+ }
160
+ }
161
+ /**
162
+ * Remove a value from the cache
163
+ */
164
+ async remove(key) {
165
+ try {
166
+ await this.initialize();
167
+ if (!this.state.kv) {
168
+ throw new Error("KV store not initialized");
169
+ }
170
+ const fullKey = this.buildKey(key);
171
+ await this.state.kv.delete(fullKey);
172
+ } catch (error) {
173
+ console.error(`Error removing key '${key}' from NATS:`, error);
174
+ }
175
+ }
176
+ /**
177
+ * Find entries matching a given criteria
178
+ */
179
+ async find(predicate) {
180
+ try {
181
+ await this.initialize();
182
+ if (!this.state.kv) {
183
+ throw new Error("KV store not initialized");
184
+ }
185
+ const results = [];
186
+ const keys = await this.state.kv.keys();
187
+ for await (const key of keys) {
188
+ if (this.options.keyPrefix && !key.startsWith(this.options.keyPrefix)) {
189
+ continue;
190
+ }
191
+ try {
192
+ const entry = await this.state.kv.get(key);
193
+ if (entry && entry.operation !== "DEL" && entry.operation !== "PURGE") {
194
+ const value = this.serializer.deserialize(entry.value);
195
+ const originalKey = this.options.keyPrefix ? key.substring(this.options.keyPrefix.length) : key;
196
+ if (predicate(originalKey, value)) {
197
+ results.push(value);
198
+ }
199
+ }
200
+ } catch (error) {
201
+ console.error(`Error reading key '${key}' during find:`, error);
202
+ }
203
+ }
204
+ return results;
205
+ } catch (error) {
206
+ console.error("Error finding keys in NATS:", error);
207
+ return [];
208
+ }
209
+ }
210
+ /**
211
+ * Close the NATS connection
212
+ */
213
+ async close() {
214
+ try {
215
+ if (this.connection) {
216
+ await this.connection.drain();
217
+ await this.connection.close();
218
+ this.connection = null;
219
+ this.state.kv = null;
220
+ this.state.connected = false;
221
+ this.initPromise = null;
222
+ }
223
+ } catch (error) {
224
+ console.error("Error closing NATS connection:", error);
225
+ }
226
+ }
227
+ };
228
+
229
+ export { JsonSerializer, NatsAdapter, getDefaultSerializer };
230
+ //# sourceMappingURL=index.mjs.map
231
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/serializer.ts","../src/adapter.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["import type { StorageValue } from 'axios-cache-interceptor';\nimport type { Serializer } from './types';\n\n/**\n * Default JSON-based serializer for cache values\n */\nexport class JsonSerializer implements Serializer {\n /**\n * Serialize a StorageValue to a Uint8Array using JSON\n */\n serialize(value: StorageValue): Uint8Array {\n try {\n const json = JSON.stringify(value);\n return new TextEncoder().encode(json);\n } catch (error) {\n throw new Error(\n `Failed to serialize value: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n\n /**\n * Deserialize a Uint8Array to a StorageValue using JSON\n */\n deserialize(data: Uint8Array): StorageValue {\n try {\n const json = new TextDecoder().decode(data);\n return JSON.parse(json) as StorageValue;\n } catch (error) {\n throw new Error(\n `Failed to deserialize value: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n}\n\n/**\n * Get the default serializer instance\n */\nexport function getDefaultSerializer(): Serializer {\n return new JsonSerializer();\n}\n","import { connect, NatsConnection } from 'nats';\nimport type { AxiosStorage, StorageValue, NotEmptyStorageValue, CacheRequestConfig } from 'axios-cache-interceptor';\nimport type { NatsAdapterOptions, NatsAdapterState, Serializer } from './types';\nimport { getDefaultSerializer } from './serializer';\n\n/**\n * NATS storage adapter for axios-cache-interceptor\n * Uses NATS JetStream Key-Value store for persistent caching\n */\nexport class NatsAdapter implements AxiosStorage {\n private connection: NatsConnection | null = null;\n private state: NatsAdapterState = {\n kv: null,\n connected: false,\n };\n private readonly options: Required<NatsAdapterOptions>;\n private readonly serializer: Serializer;\n private initPromise: Promise<void> | null = null;\n\n constructor(options: NatsAdapterOptions = {}) {\n this.options = {\n natsOptions: options.natsOptions || {},\n bucket: options.bucket || 'axios-cache',\n keyPrefix: options.keyPrefix || '',\n ttl: options.ttl || 0,\n serializer: options.serializer || getDefaultSerializer(),\n maxHistory: options.maxHistory || 1,\n autoCreateBucket: options.autoCreateBucket !== false,\n };\n this.serializer = this.options.serializer;\n }\n\n /**\n * Initialize the NATS connection and KV bucket\n */\n private async initialize(): Promise<void> {\n if (this.state.connected && this.state.kv) {\n return;\n }\n\n if (this.initPromise) {\n return this.initPromise;\n }\n\n this.initPromise = this._doInitialize();\n return this.initPromise;\n }\n\n private async _doInitialize(): Promise<void> {\n try {\n // Connect to NATS\n this.connection = await connect(this.options.natsOptions);\n\n // Get JetStream manager\n const jsm = await this.connection.jetstreamManager();\n\n // Check if bucket exists\n let kvExists = false;\n try {\n await jsm.streams.info(`KV_${this.options.bucket}`);\n kvExists = true;\n } catch (error) {\n // Bucket doesn't exist\n kvExists = false;\n }\n\n // Create bucket if it doesn't exist and autoCreateBucket is true\n if (!kvExists) {\n if (this.options.autoCreateBucket) {\n await jsm.streams.add({\n name: `KV_${this.options.bucket}`,\n subjects: [`$KV.${this.options.bucket}.>`],\n max_msgs_per_subject: this.options.maxHistory,\n });\n } else {\n throw new Error(\n `KV bucket '${this.options.bucket}' does not exist and autoCreateBucket is disabled`\n );\n }\n }\n\n // Get KV handle\n const js = this.connection.jetstream();\n this.state.kv = await js.views.kv(this.options.bucket);\n this.state.connected = true;\n } catch (error) {\n this.initPromise = null;\n throw new Error(\n `Failed to initialize NATS adapter: ${error instanceof Error ? error.message : String(error)}`\n );\n }\n }\n\n /**\n * Build the full key with prefix\n */\n private buildKey(key: string): string {\n return this.options.keyPrefix ? `${this.options.keyPrefix}${key}` : key;\n }\n\n /**\n * Get a value from the cache\n */\n async get(key: string, _currentRequest?: CacheRequestConfig): Promise<StorageValue> {\n try {\n await this.initialize();\n\n if (!this.state.kv) {\n throw new Error('KV store not initialized');\n }\n\n const fullKey = this.buildKey(key);\n\n const entry = await this.state.kv.get(fullKey);\n\n if (!entry || entry.operation === 'DEL' || entry.operation === 'PURGE') {\n return undefined as unknown as StorageValue;\n }\n\n const value = this.serializer.deserialize(entry.value);\n return value;\n } catch (error) {\n // Key doesn't exist or other error\n console.error(`Error getting key '${key}' from NATS:`, error);\n return undefined as unknown as StorageValue;\n }\n }\n\n /**\n * Set a value in the cache\n */\n async set(key: string, value: NotEmptyStorageValue, currentRequest?: CacheRequestConfig): Promise<void> {\n try {\n await this.initialize();\n\n if (!this.state.kv) {\n throw new Error('KV store not initialized');\n }\n\n const fullKey = this.buildKey(key);\n const serialized = this.serializer.serialize(value);\n\n // Determine TTL - use from currentRequest if available, otherwise use default\n let ttl = this.options.ttl;\n if (currentRequest?.cache && typeof currentRequest.cache === 'object') {\n const cacheConfig = currentRequest.cache;\n if (typeof cacheConfig.ttl === 'number') {\n ttl = cacheConfig.ttl;\n }\n }\n\n // Convert milliseconds to nanoseconds for NATS\n // NATS KV ttl is in nanoseconds\n if (ttl > 0) {\n const ttlNanos = ttl * 1_000_000;\n await this.state.kv.put(fullKey, serialized, { ttl: ttlNanos } as any);\n } else {\n await this.state.kv.put(fullKey, serialized);\n }\n } catch (error) {\n console.error(`Error setting key '${key}' in NATS:`, error);\n throw error;\n }\n }\n\n /**\n * Remove a value from the cache\n */\n async remove(key: string): Promise<void> {\n try {\n await this.initialize();\n\n if (!this.state.kv) {\n throw new Error('KV store not initialized');\n }\n\n const fullKey = this.buildKey(key);\n await this.state.kv.delete(fullKey);\n } catch (error) {\n console.error(`Error removing key '${key}' from NATS:`, error);\n }\n }\n\n /**\n * Find entries matching a given criteria\n */\n async find(predicate: (key: string, value: StorageValue) => boolean): Promise<StorageValue[]> {\n try {\n await this.initialize();\n\n if (!this.state.kv) {\n throw new Error('KV store not initialized');\n }\n\n const results: StorageValue[] = [];\n const keys = await this.state.kv.keys();\n\n for await (const key of keys) {\n // Filter by prefix if set\n if (this.options.keyPrefix && !key.startsWith(this.options.keyPrefix)) {\n continue;\n }\n\n try {\n const entry = await this.state.kv.get(key);\n if (entry && entry.operation !== 'DEL' && entry.operation !== 'PURGE') {\n const value = this.serializer.deserialize(entry.value);\n // Remove prefix from key for predicate\n const originalKey = this.options.keyPrefix\n ? key.substring(this.options.keyPrefix.length)\n : key;\n if (predicate(originalKey, value)) {\n results.push(value);\n }\n }\n } catch (error) {\n console.error(`Error reading key '${key}' during find:`, error);\n }\n }\n\n return results;\n } catch (error) {\n console.error('Error finding keys in NATS:', error);\n return [];\n }\n }\n\n /**\n * Close the NATS connection\n */\n async close(): Promise<void> {\n try {\n if (this.connection) {\n await this.connection.drain();\n await this.connection.close();\n this.connection = null;\n this.state.kv = null;\n this.state.connected = false;\n this.initPromise = null;\n }\n } catch (error) {\n console.error('Error closing NATS connection:', error);\n }\n }\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "axios-cache-nats-adapter",
3
+ "version": "1.0.0",
4
+ "description": "NATS storage adapter for axios-cache-interceptor",
5
+ "keywords": [
6
+ "axios",
7
+ "cache",
8
+ "nats",
9
+ "jetstream",
10
+ "storage",
11
+ "adapter",
12
+ "interceptor"
13
+ ],
14
+ "main": "./dist/index.cjs",
15
+ "module": "./dist/index.mjs",
16
+ "types": "./dist/index.d.ts",
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/index.d.ts",
20
+ "import": "./dist/index.mjs",
21
+ "require": "./dist/index.cjs"
22
+ }
23
+ },
24
+ "files": [
25
+ "dist"
26
+ ],
27
+ "scripts": {
28
+ "build": "tsup",
29
+ "dev": "tsup --watch",
30
+ "test": "vitest run",
31
+ "test:watch": "vitest",
32
+ "lint": "eslint src --ext .ts",
33
+ "format": "prettier --write \"src/**/*.ts\"",
34
+ "typecheck": "tsc --noEmit",
35
+ "prepublishOnly": "npm run build"
36
+ },
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "https://github.com/youaftouh/axios-cache-nats-adapter.git"
40
+ },
41
+ "author": "Youssef Aftouh",
42
+ "license": "MIT",
43
+ "dependencies": {
44
+ "nats": "^2.28.2"
45
+ },
46
+ "peerDependencies": {
47
+ "axios-cache-interceptor": "^1.0.0"
48
+ },
49
+ "devDependencies": {
50
+ "@types/node": "^20.11.5",
51
+ "@typescript-eslint/eslint-plugin": "^6.19.0",
52
+ "@typescript-eslint/parser": "^6.19.0",
53
+ "axios-cache-interceptor": "^1.5.3",
54
+ "eslint": "^8.56.0",
55
+ "prettier": "^3.2.4",
56
+ "tsup": "^8.0.1",
57
+ "typescript": "^5.3.3",
58
+ "vitest": "^1.2.1"
59
+ },
60
+ "engines": {
61
+ "node": ">=16.0.0"
62
+ }
63
+ }