@keyv/mongo 3.0.5 → 6.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +521 -0
- package/dist/index.cjs +491 -134
- package/dist/index.d.cts +199 -14
- package/dist/index.d.ts +199 -14
- package/dist/index.js +489 -134
- package/package.json +21 -11
package/dist/index.js
CHANGED
|
@@ -1,109 +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
|
-
MongoServerError,
|
|
7
7
|
MongoClient as mongoClient
|
|
8
8
|
} from "mongodb";
|
|
9
|
-
var
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
+
*/
|
|
23
46
|
connect;
|
|
24
|
-
|
|
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
|
+
*/
|
|
25
138
|
constructor(url, options) {
|
|
26
139
|
super();
|
|
27
|
-
|
|
140
|
+
let mergedOptions = {};
|
|
28
141
|
if (typeof url === "string") {
|
|
29
|
-
|
|
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;
|
|
30
150
|
}
|
|
31
|
-
if (
|
|
32
|
-
|
|
151
|
+
if (mergedOptions.uri !== void 0) {
|
|
152
|
+
this._url = mergedOptions.uri;
|
|
33
153
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
await client.connect();
|
|
55
|
-
const database = client.db(this.opts.db);
|
|
56
|
-
if (this.opts.useGridFS) {
|
|
57
|
-
const bucket = new GridFSBucket(database, {
|
|
58
|
-
readPreference: this.opts.readPreference,
|
|
59
|
-
bucketName: this.opts.collection
|
|
60
|
-
});
|
|
61
|
-
const store = database.collection(`${this.opts.collection}.files`);
|
|
62
|
-
await store.createIndex({ uploadDate: -1 });
|
|
63
|
-
await store.createIndex({ "metadata.expiresAt": 1 });
|
|
64
|
-
await store.createIndex({ "metadata.lastAccessed": 1 });
|
|
65
|
-
await store.createIndex({ "metadata.filename": 1 });
|
|
66
|
-
resolve({
|
|
67
|
-
bucket,
|
|
68
|
-
store,
|
|
69
|
-
db: database,
|
|
70
|
-
mongoClient: client
|
|
71
|
-
});
|
|
72
|
-
} else {
|
|
73
|
-
let collection = "keyv";
|
|
74
|
-
if (this.opts.collection) {
|
|
75
|
-
collection = this.opts.collection;
|
|
76
|
-
}
|
|
77
|
-
const store = database.collection(collection);
|
|
78
|
-
await store.createIndex(
|
|
79
|
-
{ key: 1 },
|
|
80
|
-
{ unique: true, background: true }
|
|
81
|
-
);
|
|
82
|
-
await store.createIndex(
|
|
83
|
-
{ expiresAt: 1 },
|
|
84
|
-
{ expireAfterSeconds: 0, background: true }
|
|
85
|
-
);
|
|
86
|
-
resolve({ store, mongoClient: client });
|
|
87
|
-
}
|
|
88
|
-
} catch (error) {
|
|
89
|
-
this.emit("error", error);
|
|
90
|
-
}
|
|
91
|
-
});
|
|
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();
|
|
92
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
|
+
*/
|
|
93
180
|
async get(key) {
|
|
94
181
|
const client = await this.connect;
|
|
95
|
-
|
|
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
|
+
}
|
|
96
192
|
await client.store.updateOne(
|
|
97
|
-
{
|
|
98
|
-
filename: String(key)
|
|
99
|
-
},
|
|
193
|
+
{ _id: { $eq: file._id } },
|
|
100
194
|
{
|
|
101
195
|
$set: {
|
|
102
196
|
"metadata.lastAccessed": /* @__PURE__ */ new Date()
|
|
103
197
|
}
|
|
104
198
|
}
|
|
105
199
|
);
|
|
106
|
-
const stream = client.bucket.
|
|
200
|
+
const stream = client.bucket.openDownloadStream(file._id);
|
|
107
201
|
return new Promise((resolve) => {
|
|
108
202
|
const resp = [];
|
|
109
203
|
stream.on("error", () => {
|
|
@@ -118,14 +212,23 @@ var KeyvMongo = class extends EventEmitter {
|
|
|
118
212
|
});
|
|
119
213
|
});
|
|
120
214
|
}
|
|
121
|
-
const document = await client.store.findOne({
|
|
215
|
+
const document = await client.store.findOne({
|
|
216
|
+
key: { $eq: strippedKey },
|
|
217
|
+
namespace: { $eq: ns }
|
|
218
|
+
});
|
|
122
219
|
if (!document) {
|
|
123
220
|
return void 0;
|
|
124
221
|
}
|
|
125
222
|
return document.value;
|
|
126
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
|
+
*/
|
|
127
230
|
async getMany(keys) {
|
|
128
|
-
if (this.
|
|
231
|
+
if (this._useGridFS) {
|
|
129
232
|
const promises = [];
|
|
130
233
|
for (const key of keys) {
|
|
131
234
|
promises.push(this.get(key));
|
|
@@ -138,30 +241,38 @@ var KeyvMongo = class extends EventEmitter {
|
|
|
138
241
|
return data;
|
|
139
242
|
}
|
|
140
243
|
const connect = await this.connect;
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
const
|
|
146
|
-
let i = 0;
|
|
147
|
-
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) {
|
|
148
249
|
const rowIndex = values.findIndex(
|
|
149
250
|
(row) => row.key === key
|
|
150
251
|
);
|
|
151
|
-
results
|
|
152
|
-
|
|
252
|
+
results.push(
|
|
253
|
+
rowIndex > -1 ? values[rowIndex].value : void 0
|
|
254
|
+
);
|
|
153
255
|
}
|
|
154
256
|
return results;
|
|
155
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
|
+
*/
|
|
156
264
|
// biome-ignore lint/suspicious/noExplicitAny: type format
|
|
157
265
|
async set(key, value, ttl) {
|
|
158
266
|
const expiresAt = typeof ttl === "number" ? new Date(Date.now() + ttl) : null;
|
|
159
|
-
|
|
267
|
+
const strippedKey = this.removeKeyPrefix(key);
|
|
268
|
+
const ns = this.getNamespaceValue();
|
|
269
|
+
if (this._useGridFS) {
|
|
160
270
|
const client2 = await this.connect;
|
|
161
|
-
const stream = client2.bucket.openUploadStream(
|
|
271
|
+
const stream = client2.bucket.openUploadStream(strippedKey, {
|
|
162
272
|
metadata: {
|
|
163
273
|
expiresAt,
|
|
164
|
-
lastAccessed: /* @__PURE__ */ new Date()
|
|
274
|
+
lastAccessed: /* @__PURE__ */ new Date(),
|
|
275
|
+
namespace: ns
|
|
165
276
|
}
|
|
166
277
|
});
|
|
167
278
|
return new Promise((resolve) => {
|
|
@@ -173,40 +284,96 @@ var KeyvMongo = class extends EventEmitter {
|
|
|
173
284
|
}
|
|
174
285
|
const client = await this.connect;
|
|
175
286
|
await client.store.updateOne(
|
|
176
|
-
{ key: { $eq:
|
|
177
|
-
{ $set: { key, value, expiresAt } },
|
|
287
|
+
{ key: { $eq: strippedKey }, namespace: { $eq: ns } },
|
|
288
|
+
{ $set: { key: strippedKey, value, namespace: ns, expiresAt } },
|
|
178
289
|
{ upsert: true }
|
|
179
290
|
);
|
|
180
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
|
+
*/
|
|
181
326
|
async delete(key) {
|
|
182
327
|
if (typeof key !== "string") {
|
|
183
328
|
return false;
|
|
184
329
|
}
|
|
185
330
|
const client = await this.connect;
|
|
186
|
-
|
|
331
|
+
const strippedKey = this.removeKeyPrefix(key);
|
|
332
|
+
const ns = this.getNamespaceValue();
|
|
333
|
+
if (this._useGridFS) {
|
|
187
334
|
try {
|
|
188
335
|
const connection = client.db;
|
|
189
336
|
const bucket = new GridFSBucket(connection, {
|
|
190
|
-
bucketName: this.
|
|
337
|
+
bucketName: this._collection
|
|
191
338
|
});
|
|
192
|
-
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
|
+
}
|
|
193
346
|
await client.bucket.delete(files[0]._id);
|
|
194
347
|
return true;
|
|
195
348
|
} catch {
|
|
196
349
|
return false;
|
|
197
350
|
}
|
|
198
351
|
}
|
|
199
|
-
const object = await client.store.deleteOne({
|
|
352
|
+
const object = await client.store.deleteOne({
|
|
353
|
+
key: { $eq: strippedKey },
|
|
354
|
+
namespace: { $eq: ns }
|
|
355
|
+
});
|
|
200
356
|
return object.deletedCount > 0;
|
|
201
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
|
+
*/
|
|
202
364
|
async deleteMany(keys) {
|
|
203
365
|
const client = await this.connect;
|
|
204
|
-
|
|
366
|
+
const strippedKeys = keys.map((k) => this.removeKeyPrefix(k));
|
|
367
|
+
const ns = this.getNamespaceValue();
|
|
368
|
+
if (this._useGridFS) {
|
|
205
369
|
const connection = client.db;
|
|
206
370
|
const bucket = new GridFSBucket(connection, {
|
|
207
|
-
bucketName: this.
|
|
371
|
+
bucketName: this._collection
|
|
208
372
|
});
|
|
209
|
-
const files = await bucket.find({
|
|
373
|
+
const files = await bucket.find({
|
|
374
|
+
filename: { $in: strippedKeys },
|
|
375
|
+
"metadata.namespace": { $eq: ns }
|
|
376
|
+
}).toArray();
|
|
210
377
|
if (files.length === 0) {
|
|
211
378
|
return false;
|
|
212
379
|
}
|
|
@@ -216,37 +383,56 @@ var KeyvMongo = class extends EventEmitter {
|
|
|
216
383
|
);
|
|
217
384
|
return true;
|
|
218
385
|
}
|
|
219
|
-
const object = await client.store.deleteMany({
|
|
386
|
+
const object = await client.store.deleteMany({
|
|
387
|
+
key: { $in: strippedKeys },
|
|
388
|
+
namespace: { $eq: ns }
|
|
389
|
+
});
|
|
220
390
|
return object.deletedCount > 0;
|
|
221
391
|
}
|
|
392
|
+
/**
|
|
393
|
+
* Delete all keys in the current namespace.
|
|
394
|
+
*/
|
|
222
395
|
async clear() {
|
|
223
396
|
const client = await this.connect;
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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;
|
|
232
411
|
}
|
|
233
412
|
await client.store.deleteMany({
|
|
234
|
-
|
|
413
|
+
namespace: { $eq: ns }
|
|
235
414
|
});
|
|
236
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
|
+
*/
|
|
237
421
|
async clearExpired() {
|
|
238
|
-
if (!this.
|
|
422
|
+
if (!this._useGridFS) {
|
|
239
423
|
return false;
|
|
240
424
|
}
|
|
425
|
+
const ns = this.getNamespaceValue();
|
|
241
426
|
return this.connect.then(async (client) => {
|
|
242
427
|
const connection = client.db;
|
|
243
428
|
const bucket = new GridFSBucket(connection, {
|
|
244
|
-
bucketName: this.
|
|
429
|
+
bucketName: this._collection
|
|
245
430
|
});
|
|
246
431
|
return bucket.find({
|
|
247
432
|
"metadata.expiresAt": {
|
|
248
433
|
$lte: new Date(Date.now())
|
|
249
|
-
}
|
|
434
|
+
},
|
|
435
|
+
"metadata.namespace": { $eq: ns }
|
|
250
436
|
}).toArray().then(
|
|
251
437
|
async (expiredFiles) => Promise.all(
|
|
252
438
|
// biome-ignore lint/style/noNonNullAssertion: need to fix
|
|
@@ -255,19 +441,27 @@ var KeyvMongo = class extends EventEmitter {
|
|
|
255
441
|
);
|
|
256
442
|
});
|
|
257
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
|
+
*/
|
|
258
450
|
async clearUnusedFor(seconds) {
|
|
259
|
-
if (!this.
|
|
451
|
+
if (!this._useGridFS) {
|
|
260
452
|
return false;
|
|
261
453
|
}
|
|
454
|
+
const ns = this.getNamespaceValue();
|
|
262
455
|
const client = await this.connect;
|
|
263
456
|
const connection = client.db;
|
|
264
457
|
const bucket = new GridFSBucket(connection, {
|
|
265
|
-
bucketName: this.
|
|
458
|
+
bucketName: this._collection
|
|
266
459
|
});
|
|
267
460
|
const lastAccessedFiles = await bucket.find({
|
|
268
461
|
"metadata.lastAccessed": {
|
|
269
462
|
$lte: new Date(Date.now() - seconds * 1e3)
|
|
270
|
-
}
|
|
463
|
+
},
|
|
464
|
+
"metadata.namespace": { $eq: ns }
|
|
271
465
|
}).toArray();
|
|
272
466
|
await Promise.all(
|
|
273
467
|
// biome-ignore lint/style/noNonNullAssertion: need to fix
|
|
@@ -275,32 +469,193 @@ var KeyvMongo = class extends EventEmitter {
|
|
|
275
469
|
);
|
|
276
470
|
return true;
|
|
277
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
|
+
*/
|
|
278
477
|
async *iterator(namespace) {
|
|
279
478
|
const client = await this.connect;
|
|
280
|
-
const
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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
|
+
});
|
|
289
505
|
yield* iterator;
|
|
290
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
|
+
*/
|
|
291
512
|
async has(key) {
|
|
292
513
|
const client = await this.connect;
|
|
293
|
-
const
|
|
294
|
-
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
|
+
});
|
|
295
527
|
return document !== 0;
|
|
296
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
|
+
*/
|
|
297
556
|
async disconnect() {
|
|
298
557
|
const client = await this.connect;
|
|
299
558
|
await client.mongoClient.close();
|
|
300
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 });
|
|
301
654
|
};
|
|
302
655
|
var index_default = KeyvMongo;
|
|
303
656
|
export {
|
|
304
657
|
KeyvMongo,
|
|
658
|
+
createKeyv,
|
|
305
659
|
index_default as default
|
|
306
660
|
};
|
|
661
|
+
/* v8 ignore next -- @preserve */
|