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 +21 -0
- package/README.md +257 -0
- package/dist/index.d.mts +45 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.js +235 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +231 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +63 -0
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.
|
package/dist/index.d.mts
ADDED
|
@@ -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.d.ts
ADDED
|
@@ -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
|
+
}
|