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