@keyv/mongo 3.1.0 → 6.0.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +521 -0
- package/dist/index.cjs +491 -129
- package/dist/index.d.cts +199 -14
- package/dist/index.d.ts +199 -14
- package/dist/index.js +489 -128
- package/package.json +5 -3
package/dist/index.js
CHANGED
|
@@ -1,108 +1,203 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import { Buffer } from "buffer";
|
|
3
|
-
import
|
|
3
|
+
import { Hookified } from "hookified";
|
|
4
|
+
import Keyv from "keyv";
|
|
4
5
|
import {
|
|
5
6
|
GridFSBucket,
|
|
6
7
|
MongoClient as mongoClient
|
|
7
8
|
} from "mongodb";
|
|
8
|
-
var
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
9
|
+
var KeyvMongo = class extends Hookified {
|
|
10
|
+
/**
|
|
11
|
+
* The MongoDB connection URI.
|
|
12
|
+
* @default 'mongodb://127.0.0.1:27017'
|
|
13
|
+
*/
|
|
14
|
+
_url = "mongodb://127.0.0.1:27017";
|
|
15
|
+
/**
|
|
16
|
+
* The collection name used for storage.
|
|
17
|
+
* @default 'keyv'
|
|
18
|
+
*/
|
|
19
|
+
_collection = "keyv";
|
|
20
|
+
/**
|
|
21
|
+
* The namespace used to prefix keys for multi-tenant separation.
|
|
22
|
+
*/
|
|
23
|
+
_namespace;
|
|
24
|
+
/**
|
|
25
|
+
* Whether to use GridFS for storing values.
|
|
26
|
+
* @default false
|
|
27
|
+
*/
|
|
28
|
+
_useGridFS = false;
|
|
29
|
+
/**
|
|
30
|
+
* The database name for the MongoDB connection.
|
|
31
|
+
* @default undefined
|
|
32
|
+
*/
|
|
33
|
+
_db;
|
|
34
|
+
/**
|
|
35
|
+
* The MongoDB read preference for GridFS operations.
|
|
36
|
+
* @default undefined
|
|
37
|
+
*/
|
|
38
|
+
_readPreference;
|
|
39
|
+
/**
|
|
40
|
+
* Additional MongoClientOptions passed through to the MongoDB driver.
|
|
41
|
+
*/
|
|
42
|
+
_mongoOptions = {};
|
|
43
|
+
/**
|
|
44
|
+
* Promise that resolves to the MongoDB connection details.
|
|
45
|
+
*/
|
|
22
46
|
connect;
|
|
23
|
-
|
|
47
|
+
/**
|
|
48
|
+
* Get the MongoDB connection URI.
|
|
49
|
+
* @default 'mongodb://127.0.0.1:27017'
|
|
50
|
+
*/
|
|
51
|
+
get url() {
|
|
52
|
+
return this._url;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Set the MongoDB connection URI.
|
|
56
|
+
*/
|
|
57
|
+
set url(value) {
|
|
58
|
+
this._url = value;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Get the collection name used for storage.
|
|
62
|
+
* @default 'keyv'
|
|
63
|
+
*/
|
|
64
|
+
get collection() {
|
|
65
|
+
return this._collection;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Set the collection name used for storage.
|
|
69
|
+
*/
|
|
70
|
+
set collection(value) {
|
|
71
|
+
this._collection = value;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Get the namespace for the adapter. If undefined, no namespace prefix is applied.
|
|
75
|
+
*/
|
|
76
|
+
get namespace() {
|
|
77
|
+
return this._namespace;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Set the namespace for the adapter. Used for key prefixing and scoping operations like `clear()`.
|
|
81
|
+
*/
|
|
82
|
+
set namespace(value) {
|
|
83
|
+
this._namespace = value;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Get whether GridFS is used for storing values. This is read-only and can only be set via the constructor
|
|
87
|
+
* because the MongoDB connection shape differs between GridFS and standard modes.
|
|
88
|
+
* @default false
|
|
89
|
+
*/
|
|
90
|
+
get useGridFS() {
|
|
91
|
+
return this._useGridFS;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Get the database name for the MongoDB connection.
|
|
95
|
+
*/
|
|
96
|
+
get db() {
|
|
97
|
+
return this._db;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Set the database name for the MongoDB connection.
|
|
101
|
+
*/
|
|
102
|
+
set db(value) {
|
|
103
|
+
this._db = value;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Get the MongoDB read preference for GridFS operations.
|
|
107
|
+
*/
|
|
108
|
+
get readPreference() {
|
|
109
|
+
return this._readPreference;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Set the MongoDB read preference for GridFS operations.
|
|
113
|
+
*/
|
|
114
|
+
set readPreference(value) {
|
|
115
|
+
this._readPreference = value;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Get the options for the adapter. This is provided for backward compatibility.
|
|
119
|
+
*/
|
|
120
|
+
// biome-ignore lint/suspicious/noExplicitAny: type format
|
|
121
|
+
get opts() {
|
|
122
|
+
return {
|
|
123
|
+
url: this._url,
|
|
124
|
+
uri: this._url,
|
|
125
|
+
collection: this._collection,
|
|
126
|
+
useGridFS: this._useGridFS,
|
|
127
|
+
db: this._db,
|
|
128
|
+
readPreference: this._readPreference,
|
|
129
|
+
dialect: "mongo",
|
|
130
|
+
...this._mongoOptions
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Creates a new KeyvMongo instance.
|
|
135
|
+
* @param url - Configuration options, connection URI string, or undefined for defaults.
|
|
136
|
+
* @param options - Additional configuration options that override the first parameter.
|
|
137
|
+
*/
|
|
24
138
|
constructor(url, options) {
|
|
25
|
-
super();
|
|
26
|
-
|
|
139
|
+
super({ throwOnEmptyListeners: false });
|
|
140
|
+
let mergedOptions = {};
|
|
27
141
|
if (typeof url === "string") {
|
|
28
|
-
|
|
142
|
+
this._url = url;
|
|
143
|
+
if (options) {
|
|
144
|
+
mergedOptions = options;
|
|
145
|
+
}
|
|
146
|
+
} else if (url) {
|
|
147
|
+
mergedOptions = { ...url, ...options };
|
|
148
|
+
} else if (options) {
|
|
149
|
+
mergedOptions = options;
|
|
29
150
|
}
|
|
30
|
-
if (
|
|
31
|
-
|
|
151
|
+
if (mergedOptions.uri !== void 0) {
|
|
152
|
+
this._url = mergedOptions.uri;
|
|
32
153
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
await client.connect();
|
|
54
|
-
const database = client.db(this.opts.db);
|
|
55
|
-
if (this.opts.useGridFS) {
|
|
56
|
-
const bucket = new GridFSBucket(database, {
|
|
57
|
-
readPreference: this.opts.readPreference,
|
|
58
|
-
bucketName: this.opts.collection
|
|
59
|
-
});
|
|
60
|
-
const store = database.collection(`${this.opts.collection}.files`);
|
|
61
|
-
await store.createIndex({ uploadDate: -1 });
|
|
62
|
-
await store.createIndex({ "metadata.expiresAt": 1 });
|
|
63
|
-
await store.createIndex({ "metadata.lastAccessed": 1 });
|
|
64
|
-
await store.createIndex({ "metadata.filename": 1 });
|
|
65
|
-
resolve({
|
|
66
|
-
bucket,
|
|
67
|
-
store,
|
|
68
|
-
db: database,
|
|
69
|
-
mongoClient: client
|
|
70
|
-
});
|
|
71
|
-
} else {
|
|
72
|
-
let collection = "keyv";
|
|
73
|
-
if (this.opts.collection) {
|
|
74
|
-
collection = this.opts.collection;
|
|
75
|
-
}
|
|
76
|
-
const store = database.collection(collection);
|
|
77
|
-
await store.createIndex(
|
|
78
|
-
{ key: 1 },
|
|
79
|
-
{ unique: true, background: true }
|
|
80
|
-
);
|
|
81
|
-
await store.createIndex(
|
|
82
|
-
{ expiresAt: 1 },
|
|
83
|
-
{ expireAfterSeconds: 0, background: true }
|
|
84
|
-
);
|
|
85
|
-
resolve({ store, mongoClient: client });
|
|
86
|
-
}
|
|
87
|
-
} catch (error) {
|
|
88
|
-
this.emit("error", error);
|
|
89
|
-
}
|
|
90
|
-
});
|
|
154
|
+
if (mergedOptions.url !== void 0) {
|
|
155
|
+
this._url = mergedOptions.url;
|
|
156
|
+
}
|
|
157
|
+
if (mergedOptions.collection !== void 0) {
|
|
158
|
+
this._collection = mergedOptions.collection;
|
|
159
|
+
}
|
|
160
|
+
if (mergedOptions.namespace !== void 0) {
|
|
161
|
+
this._namespace = mergedOptions.namespace;
|
|
162
|
+
}
|
|
163
|
+
if (mergedOptions.useGridFS !== void 0) {
|
|
164
|
+
this._useGridFS = mergedOptions.useGridFS;
|
|
165
|
+
}
|
|
166
|
+
if (mergedOptions.db !== void 0) {
|
|
167
|
+
this._db = mergedOptions.db;
|
|
168
|
+
}
|
|
169
|
+
if (mergedOptions.readPreference !== void 0) {
|
|
170
|
+
this._readPreference = mergedOptions.readPreference;
|
|
171
|
+
}
|
|
172
|
+
this._mongoOptions = this.extractMongoOptions(mergedOptions);
|
|
173
|
+
this.connect = this.initConnection();
|
|
91
174
|
}
|
|
175
|
+
/**
|
|
176
|
+
* Get a value from the store by key. In GridFS mode, also updates the `lastAccessed` timestamp.
|
|
177
|
+
* @param key - The key to retrieve.
|
|
178
|
+
* @returns The stored value, or `undefined` if the key does not exist.
|
|
179
|
+
*/
|
|
92
180
|
async get(key) {
|
|
93
181
|
const client = await this.connect;
|
|
94
|
-
|
|
182
|
+
const strippedKey = this.removeKeyPrefix(key);
|
|
183
|
+
const ns = this.getNamespaceValue();
|
|
184
|
+
if (this._useGridFS) {
|
|
185
|
+
const file = await client.store.findOne({
|
|
186
|
+
filename: { $eq: strippedKey },
|
|
187
|
+
"metadata.namespace": { $eq: ns }
|
|
188
|
+
});
|
|
189
|
+
if (!file) {
|
|
190
|
+
return void 0;
|
|
191
|
+
}
|
|
95
192
|
await client.store.updateOne(
|
|
96
|
-
{
|
|
97
|
-
filename: String(key)
|
|
98
|
-
},
|
|
193
|
+
{ _id: { $eq: file._id } },
|
|
99
194
|
{
|
|
100
195
|
$set: {
|
|
101
196
|
"metadata.lastAccessed": /* @__PURE__ */ new Date()
|
|
102
197
|
}
|
|
103
198
|
}
|
|
104
199
|
);
|
|
105
|
-
const stream = client.bucket.
|
|
200
|
+
const stream = client.bucket.openDownloadStream(file._id);
|
|
106
201
|
return new Promise((resolve) => {
|
|
107
202
|
const resp = [];
|
|
108
203
|
stream.on("error", () => {
|
|
@@ -117,14 +212,23 @@ var KeyvMongo = class extends EventEmitter {
|
|
|
117
212
|
});
|
|
118
213
|
});
|
|
119
214
|
}
|
|
120
|
-
const document = await client.store.findOne({
|
|
215
|
+
const document = await client.store.findOne({
|
|
216
|
+
key: { $eq: strippedKey },
|
|
217
|
+
namespace: { $eq: ns }
|
|
218
|
+
});
|
|
121
219
|
if (!document) {
|
|
122
220
|
return void 0;
|
|
123
221
|
}
|
|
124
222
|
return document.value;
|
|
125
223
|
}
|
|
224
|
+
/**
|
|
225
|
+
* Get multiple values from the store at once. In standard mode, uses a single query with the `$in` operator.
|
|
226
|
+
* In GridFS mode, each key is fetched individually in parallel.
|
|
227
|
+
* @param keys - Array of keys to retrieve.
|
|
228
|
+
* @returns Array of values in the same order as the input keys. Missing keys return `undefined`.
|
|
229
|
+
*/
|
|
126
230
|
async getMany(keys) {
|
|
127
|
-
if (this.
|
|
231
|
+
if (this._useGridFS) {
|
|
128
232
|
const promises = [];
|
|
129
233
|
for (const key of keys) {
|
|
130
234
|
promises.push(this.get(key));
|
|
@@ -137,30 +241,38 @@ var KeyvMongo = class extends EventEmitter {
|
|
|
137
241
|
return data;
|
|
138
242
|
}
|
|
139
243
|
const connect = await this.connect;
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
const
|
|
145
|
-
let i = 0;
|
|
146
|
-
for (const key of keys) {
|
|
244
|
+
const strippedKeys = keys.map((k) => this.removeKeyPrefix(k));
|
|
245
|
+
const ns = this.getNamespaceValue();
|
|
246
|
+
const values = await connect.store.find({ key: { $in: strippedKeys }, namespace: { $eq: ns } }).project({ _id: 0, value: 1, key: 1 }).toArray();
|
|
247
|
+
const results = [];
|
|
248
|
+
for (const key of strippedKeys) {
|
|
147
249
|
const rowIndex = values.findIndex(
|
|
148
250
|
(row) => row.key === key
|
|
149
251
|
);
|
|
150
|
-
results
|
|
151
|
-
|
|
252
|
+
results.push(
|
|
253
|
+
rowIndex > -1 ? values[rowIndex].value : void 0
|
|
254
|
+
);
|
|
152
255
|
}
|
|
153
256
|
return results;
|
|
154
257
|
}
|
|
258
|
+
/**
|
|
259
|
+
* Set a value in the store.
|
|
260
|
+
* @param key - The key to set.
|
|
261
|
+
* @param value - The value to store.
|
|
262
|
+
* @param ttl - Time to live in milliseconds. If specified, the key will expire after this duration.
|
|
263
|
+
*/
|
|
155
264
|
// biome-ignore lint/suspicious/noExplicitAny: type format
|
|
156
265
|
async set(key, value, ttl) {
|
|
157
266
|
const expiresAt = typeof ttl === "number" ? new Date(Date.now() + ttl) : null;
|
|
158
|
-
|
|
267
|
+
const strippedKey = this.removeKeyPrefix(key);
|
|
268
|
+
const ns = this.getNamespaceValue();
|
|
269
|
+
if (this._useGridFS) {
|
|
159
270
|
const client2 = await this.connect;
|
|
160
|
-
const stream = client2.bucket.openUploadStream(
|
|
271
|
+
const stream = client2.bucket.openUploadStream(strippedKey, {
|
|
161
272
|
metadata: {
|
|
162
273
|
expiresAt,
|
|
163
|
-
lastAccessed: /* @__PURE__ */ new Date()
|
|
274
|
+
lastAccessed: /* @__PURE__ */ new Date(),
|
|
275
|
+
namespace: ns
|
|
164
276
|
}
|
|
165
277
|
});
|
|
166
278
|
return new Promise((resolve) => {
|
|
@@ -172,40 +284,96 @@ var KeyvMongo = class extends EventEmitter {
|
|
|
172
284
|
}
|
|
173
285
|
const client = await this.connect;
|
|
174
286
|
await client.store.updateOne(
|
|
175
|
-
{ key: { $eq:
|
|
176
|
-
{ $set: { key, value, expiresAt } },
|
|
287
|
+
{ key: { $eq: strippedKey }, namespace: { $eq: ns } },
|
|
288
|
+
{ $set: { key: strippedKey, value, namespace: ns, expiresAt } },
|
|
177
289
|
{ upsert: true }
|
|
178
290
|
);
|
|
179
291
|
}
|
|
292
|
+
/**
|
|
293
|
+
* Set multiple values in the store at once. In standard mode, uses a single `bulkWrite` operation.
|
|
294
|
+
* In GridFS mode, each entry is set individually in parallel.
|
|
295
|
+
* @param entries - Array of entries to set. Each entry has a `key`, `value`, and optional `ttl` in milliseconds.
|
|
296
|
+
*/
|
|
297
|
+
async setMany(entries) {
|
|
298
|
+
if (this._useGridFS) {
|
|
299
|
+
await Promise.all(
|
|
300
|
+
entries.map(async ({ key, value, ttl }) => this.set(key, value, ttl))
|
|
301
|
+
);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
const client = await this.connect;
|
|
305
|
+
const ns = this.getNamespaceValue();
|
|
306
|
+
const operations = entries.map(({ key, value, ttl }) => {
|
|
307
|
+
const strippedKey = this.removeKeyPrefix(key);
|
|
308
|
+
const expiresAt = typeof ttl === "number" ? new Date(Date.now() + ttl) : null;
|
|
309
|
+
return {
|
|
310
|
+
updateOne: {
|
|
311
|
+
filter: { key: { $eq: strippedKey }, namespace: { $eq: ns } },
|
|
312
|
+
update: {
|
|
313
|
+
$set: { key: strippedKey, value, namespace: ns, expiresAt }
|
|
314
|
+
},
|
|
315
|
+
upsert: true
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
});
|
|
319
|
+
await client.store.bulkWrite(operations);
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Delete a key from the store.
|
|
323
|
+
* @param key - The key to delete.
|
|
324
|
+
* @returns `true` if the key was deleted, `false` if the key was not found.
|
|
325
|
+
*/
|
|
180
326
|
async delete(key) {
|
|
181
327
|
if (typeof key !== "string") {
|
|
182
328
|
return false;
|
|
183
329
|
}
|
|
184
330
|
const client = await this.connect;
|
|
185
|
-
|
|
331
|
+
const strippedKey = this.removeKeyPrefix(key);
|
|
332
|
+
const ns = this.getNamespaceValue();
|
|
333
|
+
if (this._useGridFS) {
|
|
186
334
|
try {
|
|
187
335
|
const connection = client.db;
|
|
188
336
|
const bucket = new GridFSBucket(connection, {
|
|
189
|
-
bucketName: this.
|
|
337
|
+
bucketName: this._collection
|
|
190
338
|
});
|
|
191
|
-
const files = await bucket.find({
|
|
339
|
+
const files = await bucket.find({
|
|
340
|
+
filename: { $eq: strippedKey },
|
|
341
|
+
"metadata.namespace": { $eq: ns }
|
|
342
|
+
}).toArray();
|
|
343
|
+
if (files.length === 0) {
|
|
344
|
+
return false;
|
|
345
|
+
}
|
|
192
346
|
await client.bucket.delete(files[0]._id);
|
|
193
347
|
return true;
|
|
194
348
|
} catch {
|
|
195
349
|
return false;
|
|
196
350
|
}
|
|
197
351
|
}
|
|
198
|
-
const object = await client.store.deleteOne({
|
|
352
|
+
const object = await client.store.deleteOne({
|
|
353
|
+
key: { $eq: strippedKey },
|
|
354
|
+
namespace: { $eq: ns }
|
|
355
|
+
});
|
|
199
356
|
return object.deletedCount > 0;
|
|
200
357
|
}
|
|
358
|
+
/**
|
|
359
|
+
* Delete multiple keys from the store at once. In standard mode, uses a single query with the `$in` operator.
|
|
360
|
+
* In GridFS mode, all matching files are found and deleted in parallel.
|
|
361
|
+
* @param keys - Array of keys to delete.
|
|
362
|
+
* @returns `true` if any keys were deleted, `false` if none were found.
|
|
363
|
+
*/
|
|
201
364
|
async deleteMany(keys) {
|
|
202
365
|
const client = await this.connect;
|
|
203
|
-
|
|
366
|
+
const strippedKeys = keys.map((k) => this.removeKeyPrefix(k));
|
|
367
|
+
const ns = this.getNamespaceValue();
|
|
368
|
+
if (this._useGridFS) {
|
|
204
369
|
const connection = client.db;
|
|
205
370
|
const bucket = new GridFSBucket(connection, {
|
|
206
|
-
bucketName: this.
|
|
371
|
+
bucketName: this._collection
|
|
207
372
|
});
|
|
208
|
-
const files = await bucket.find({
|
|
373
|
+
const files = await bucket.find({
|
|
374
|
+
filename: { $in: strippedKeys },
|
|
375
|
+
"metadata.namespace": { $eq: ns }
|
|
376
|
+
}).toArray();
|
|
209
377
|
if (files.length === 0) {
|
|
210
378
|
return false;
|
|
211
379
|
}
|
|
@@ -215,31 +383,56 @@ var KeyvMongo = class extends EventEmitter {
|
|
|
215
383
|
);
|
|
216
384
|
return true;
|
|
217
385
|
}
|
|
218
|
-
const object = await client.store.deleteMany({
|
|
386
|
+
const object = await client.store.deleteMany({
|
|
387
|
+
key: { $in: strippedKeys },
|
|
388
|
+
namespace: { $eq: ns }
|
|
389
|
+
});
|
|
219
390
|
return object.deletedCount > 0;
|
|
220
391
|
}
|
|
392
|
+
/**
|
|
393
|
+
* Delete all keys in the current namespace.
|
|
394
|
+
*/
|
|
221
395
|
async clear() {
|
|
222
396
|
const client = await this.connect;
|
|
223
|
-
|
|
224
|
-
|
|
397
|
+
const ns = this.getNamespaceValue();
|
|
398
|
+
if (this._useGridFS) {
|
|
399
|
+
const connection = client.db;
|
|
400
|
+
const bucket = new GridFSBucket(connection, {
|
|
401
|
+
bucketName: this._collection
|
|
402
|
+
});
|
|
403
|
+
const files = await bucket.find({
|
|
404
|
+
"metadata.namespace": { $eq: ns }
|
|
405
|
+
}).toArray();
|
|
406
|
+
await Promise.all(
|
|
407
|
+
// biome-ignore lint/style/noNonNullAssertion: need to fix
|
|
408
|
+
files.map(async (file) => client.bucket.delete(file._id))
|
|
409
|
+
);
|
|
410
|
+
return;
|
|
225
411
|
}
|
|
226
412
|
await client.store.deleteMany({
|
|
227
|
-
|
|
413
|
+
namespace: { $eq: ns }
|
|
228
414
|
});
|
|
229
415
|
}
|
|
416
|
+
/**
|
|
417
|
+
* Remove all expired files from GridFS. This method only works in GridFS mode
|
|
418
|
+
* and is a no-op that returns `false` in standard mode.
|
|
419
|
+
* @returns `true` if running in GridFS mode, `false` otherwise.
|
|
420
|
+
*/
|
|
230
421
|
async clearExpired() {
|
|
231
|
-
if (!this.
|
|
422
|
+
if (!this._useGridFS) {
|
|
232
423
|
return false;
|
|
233
424
|
}
|
|
425
|
+
const ns = this.getNamespaceValue();
|
|
234
426
|
return this.connect.then(async (client) => {
|
|
235
427
|
const connection = client.db;
|
|
236
428
|
const bucket = new GridFSBucket(connection, {
|
|
237
|
-
bucketName: this.
|
|
429
|
+
bucketName: this._collection
|
|
238
430
|
});
|
|
239
431
|
return bucket.find({
|
|
240
432
|
"metadata.expiresAt": {
|
|
241
433
|
$lte: new Date(Date.now())
|
|
242
|
-
}
|
|
434
|
+
},
|
|
435
|
+
"metadata.namespace": { $eq: ns }
|
|
243
436
|
}).toArray().then(
|
|
244
437
|
async (expiredFiles) => Promise.all(
|
|
245
438
|
// biome-ignore lint/style/noNonNullAssertion: need to fix
|
|
@@ -248,19 +441,27 @@ var KeyvMongo = class extends EventEmitter {
|
|
|
248
441
|
);
|
|
249
442
|
});
|
|
250
443
|
}
|
|
444
|
+
/**
|
|
445
|
+
* Remove all GridFS files that have not been accessed for the specified duration. This method only works
|
|
446
|
+
* in GridFS mode and is a no-op that returns `false` in standard mode.
|
|
447
|
+
* @param seconds - The number of seconds of inactivity after which files should be removed.
|
|
448
|
+
* @returns `true` if running in GridFS mode, `false` otherwise.
|
|
449
|
+
*/
|
|
251
450
|
async clearUnusedFor(seconds) {
|
|
252
|
-
if (!this.
|
|
451
|
+
if (!this._useGridFS) {
|
|
253
452
|
return false;
|
|
254
453
|
}
|
|
454
|
+
const ns = this.getNamespaceValue();
|
|
255
455
|
const client = await this.connect;
|
|
256
456
|
const connection = client.db;
|
|
257
457
|
const bucket = new GridFSBucket(connection, {
|
|
258
|
-
bucketName: this.
|
|
458
|
+
bucketName: this._collection
|
|
259
459
|
});
|
|
260
460
|
const lastAccessedFiles = await bucket.find({
|
|
261
461
|
"metadata.lastAccessed": {
|
|
262
462
|
$lte: new Date(Date.now() - seconds * 1e3)
|
|
263
|
-
}
|
|
463
|
+
},
|
|
464
|
+
"metadata.namespace": { $eq: ns }
|
|
264
465
|
}).toArray();
|
|
265
466
|
await Promise.all(
|
|
266
467
|
// biome-ignore lint/style/noNonNullAssertion: need to fix
|
|
@@ -268,33 +469,193 @@ var KeyvMongo = class extends EventEmitter {
|
|
|
268
469
|
);
|
|
269
470
|
return true;
|
|
270
471
|
}
|
|
472
|
+
/**
|
|
473
|
+
* Iterate over all key-value pairs in the store matching the given namespace.
|
|
474
|
+
* @param namespace - The namespace to iterate over. When used through Keyv, this is passed automatically.
|
|
475
|
+
* @yields `[key, value]` pairs as an async generator.
|
|
476
|
+
*/
|
|
271
477
|
async *iterator(namespace) {
|
|
272
478
|
const client = await this.connect;
|
|
273
|
-
const
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
479
|
+
const namespaceValue = namespace ?? "";
|
|
480
|
+
if (this._useGridFS) {
|
|
481
|
+
const gridIterator = client.store.find({ "metadata.namespace": { $eq: namespaceValue } }).map(async (x) => {
|
|
482
|
+
const prefixedKey = namespace ? `${namespace}:${x.filename}` : x.filename;
|
|
483
|
+
const stream = client.bucket.openDownloadStream(x._id);
|
|
484
|
+
const data = await new Promise((resolve) => {
|
|
485
|
+
const resp = [];
|
|
486
|
+
stream.on("error", () => {
|
|
487
|
+
resolve(void 0);
|
|
488
|
+
});
|
|
489
|
+
stream.on("end", () => {
|
|
490
|
+
resolve(Buffer.concat(resp).toString("utf8"));
|
|
491
|
+
});
|
|
492
|
+
stream.on("data", (chunk) => {
|
|
493
|
+
resp.push(chunk);
|
|
494
|
+
});
|
|
495
|
+
});
|
|
496
|
+
return [prefixedKey, data];
|
|
497
|
+
});
|
|
498
|
+
yield* gridIterator;
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
const iterator = client.store.find({ namespace: { $eq: namespaceValue } }).map((x) => {
|
|
502
|
+
const prefixedKey = namespace ? `${namespace}:${x.key}` : x.key;
|
|
503
|
+
return [prefixedKey, x.value];
|
|
504
|
+
});
|
|
282
505
|
yield* iterator;
|
|
283
506
|
}
|
|
507
|
+
/**
|
|
508
|
+
* Check if a key exists in the store.
|
|
509
|
+
* @param key - The key to check.
|
|
510
|
+
* @returns `true` if the key exists, `false` otherwise.
|
|
511
|
+
*/
|
|
284
512
|
async has(key) {
|
|
285
513
|
const client = await this.connect;
|
|
286
|
-
const
|
|
287
|
-
const
|
|
514
|
+
const strippedKey = this.removeKeyPrefix(key);
|
|
515
|
+
const ns = this.getNamespaceValue();
|
|
516
|
+
if (this._useGridFS) {
|
|
517
|
+
const document2 = await client.store.count({
|
|
518
|
+
filename: { $eq: strippedKey },
|
|
519
|
+
"metadata.namespace": { $eq: ns }
|
|
520
|
+
});
|
|
521
|
+
return document2 !== 0;
|
|
522
|
+
}
|
|
523
|
+
const document = await client.store.count({
|
|
524
|
+
key: { $eq: strippedKey },
|
|
525
|
+
namespace: { $eq: ns }
|
|
526
|
+
});
|
|
288
527
|
return document !== 0;
|
|
289
528
|
}
|
|
529
|
+
/**
|
|
530
|
+
* Check if multiple keys exist in the store at once. Uses a single query with the `$in` operator.
|
|
531
|
+
* @param keys - Array of keys to check.
|
|
532
|
+
* @returns Array of booleans in the same order as the input keys.
|
|
533
|
+
*/
|
|
534
|
+
async hasMany(keys) {
|
|
535
|
+
const client = await this.connect;
|
|
536
|
+
const strippedKeys = keys.map((k) => this.removeKeyPrefix(k));
|
|
537
|
+
const ns = this.getNamespaceValue();
|
|
538
|
+
if (this._useGridFS) {
|
|
539
|
+
const files = await client.store.find({
|
|
540
|
+
filename: { $in: strippedKeys },
|
|
541
|
+
"metadata.namespace": { $eq: ns }
|
|
542
|
+
}).project({ filename: 1 }).toArray();
|
|
543
|
+
const existingKeys2 = new Set(files.map((f) => f.filename));
|
|
544
|
+
return strippedKeys.map((key) => existingKeys2.has(key));
|
|
545
|
+
}
|
|
546
|
+
const docs = await client.store.find({
|
|
547
|
+
key: { $in: strippedKeys },
|
|
548
|
+
namespace: { $eq: ns }
|
|
549
|
+
}).project({ key: 1 }).toArray();
|
|
550
|
+
const existingKeys = new Set(docs.map((d) => d.key));
|
|
551
|
+
return strippedKeys.map((key) => existingKeys.has(key));
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Close the MongoDB connection.
|
|
555
|
+
*/
|
|
290
556
|
async disconnect() {
|
|
291
557
|
const client = await this.connect;
|
|
292
558
|
await client.mongoClient.close();
|
|
293
559
|
}
|
|
560
|
+
/**
|
|
561
|
+
* Strips the namespace prefix from a key that was added by the Keyv core.
|
|
562
|
+
* For example, if namespace is "ns" and key is "ns:foo", returns "foo".
|
|
563
|
+
*/
|
|
564
|
+
removeKeyPrefix(key) {
|
|
565
|
+
if (this._namespace && key.startsWith(`${this._namespace}:`)) {
|
|
566
|
+
return key.slice(this._namespace.length + 1);
|
|
567
|
+
}
|
|
568
|
+
return key;
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Returns the namespace value for query filters.
|
|
572
|
+
* Returns empty string when no namespace is set.
|
|
573
|
+
*/
|
|
574
|
+
getNamespaceValue() {
|
|
575
|
+
return this._namespace ?? "";
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Extracts MongoDB driver options from the provided options, filtering out Keyv-specific properties.
|
|
579
|
+
*/
|
|
580
|
+
extractMongoOptions(options) {
|
|
581
|
+
const keyvKeys = /* @__PURE__ */ new Set([
|
|
582
|
+
"url",
|
|
583
|
+
"collection",
|
|
584
|
+
"namespace",
|
|
585
|
+
"serialize",
|
|
586
|
+
"deserialize",
|
|
587
|
+
"uri",
|
|
588
|
+
"useGridFS",
|
|
589
|
+
"dialect",
|
|
590
|
+
"db",
|
|
591
|
+
"readPreference",
|
|
592
|
+
"emitErrors"
|
|
593
|
+
]);
|
|
594
|
+
const mongoOptions = {};
|
|
595
|
+
for (const [key, value] of Object.entries(options)) {
|
|
596
|
+
if (!keyvKeys.has(key)) {
|
|
597
|
+
mongoOptions[key] = value;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
return mongoOptions;
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* Initializes the MongoDB connection and sets up indexes.
|
|
604
|
+
*/
|
|
605
|
+
initConnection() {
|
|
606
|
+
return new Promise(async (resolve, _reject) => {
|
|
607
|
+
try {
|
|
608
|
+
const client = new mongoClient(this._url, this._mongoOptions);
|
|
609
|
+
await client.connect();
|
|
610
|
+
const database = client.db(this._db);
|
|
611
|
+
if (this._useGridFS) {
|
|
612
|
+
const bucket = new GridFSBucket(database, {
|
|
613
|
+
readPreference: this._readPreference,
|
|
614
|
+
bucketName: this._collection
|
|
615
|
+
});
|
|
616
|
+
const store = database.collection(`${this._collection}.files`);
|
|
617
|
+
await store.createIndex({ uploadDate: -1 });
|
|
618
|
+
await store.createIndex({ "metadata.expiresAt": 1 });
|
|
619
|
+
await store.createIndex({ "metadata.lastAccessed": 1 });
|
|
620
|
+
await store.createIndex({ "metadata.filename": 1 });
|
|
621
|
+
await store.createIndex({ "metadata.namespace": 1 });
|
|
622
|
+
resolve({
|
|
623
|
+
bucket,
|
|
624
|
+
store,
|
|
625
|
+
db: database,
|
|
626
|
+
mongoClient: client
|
|
627
|
+
});
|
|
628
|
+
} else {
|
|
629
|
+
const store = database.collection(this._collection);
|
|
630
|
+
try {
|
|
631
|
+
await store.dropIndex("key_1");
|
|
632
|
+
} catch {
|
|
633
|
+
}
|
|
634
|
+
await store.createIndex(
|
|
635
|
+
{ key: 1, namespace: 1 },
|
|
636
|
+
{ unique: true, background: true }
|
|
637
|
+
);
|
|
638
|
+
await store.createIndex(
|
|
639
|
+
{ expiresAt: 1 },
|
|
640
|
+
{ expireAfterSeconds: 0, background: true }
|
|
641
|
+
);
|
|
642
|
+
resolve({ store, mongoClient: client });
|
|
643
|
+
}
|
|
644
|
+
} catch (error) {
|
|
645
|
+
this.emit("error", error);
|
|
646
|
+
}
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
};
|
|
650
|
+
var createKeyv = (options) => {
|
|
651
|
+
const store = new KeyvMongo(options);
|
|
652
|
+
const namespace = typeof options === "object" ? options?.namespace : void 0;
|
|
653
|
+
return new Keyv({ store, namespace });
|
|
294
654
|
};
|
|
295
655
|
var index_default = KeyvMongo;
|
|
296
656
|
export {
|
|
297
657
|
KeyvMongo,
|
|
658
|
+
createKeyv,
|
|
298
659
|
index_default as default
|
|
299
660
|
};
|
|
300
661
|
/* v8 ignore next -- @preserve */
|