mirror of https://github.com/boxyhq/jackson.git
181 lines
4.8 KiB
TypeScript
181 lines
4.8 KiB
TypeScript
import { Collection, Db, MongoClient, UpdateOptions } from 'mongodb';
|
|
import { DatabaseDriver, DatabaseOption, Encrypted, Index, Records } from '../typings';
|
|
import * as dbutils from './utils';
|
|
|
|
type _Document = {
|
|
value: Encrypted;
|
|
expiresAt?: Date;
|
|
modifiedAt: string;
|
|
namespace: string;
|
|
indexes: string[];
|
|
};
|
|
|
|
class Mongo implements DatabaseDriver {
|
|
private options: DatabaseOption;
|
|
private client!: MongoClient;
|
|
private collection!: Collection;
|
|
private db!: Db;
|
|
|
|
constructor(options: DatabaseOption) {
|
|
this.options = options;
|
|
}
|
|
|
|
async init(): Promise<Mongo> {
|
|
const dbUrl = this.options.url as string;
|
|
try {
|
|
this.client = new MongoClient(dbUrl);
|
|
await this.client.connect();
|
|
} catch (err) {
|
|
console.error(`error connecting to engine: ${this.options.engine}, db: ${err}`);
|
|
throw err;
|
|
}
|
|
|
|
this.db = this.client.db();
|
|
this.collection = this.db.collection('jacksonStore');
|
|
|
|
await this.collection.createIndex({ indexes: 1 });
|
|
await this.collection.createIndex({ expiresAt: 1 }, { expireAfterSeconds: 1 });
|
|
await this.collection.createIndex({ namespace: 1 });
|
|
|
|
// eslint-disable-next-line no-constant-condition
|
|
while (true) {
|
|
try {
|
|
if (!this.options.manualMigration) {
|
|
await this.indexNamespace();
|
|
}
|
|
break;
|
|
} catch (err) {
|
|
console.error(
|
|
`error in index namespace execution for db engine: ${this.options.engine}, err: ${err}`
|
|
);
|
|
await dbutils.sleep(1000);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
async indexNamespace() {
|
|
const docs = await this.collection.find({ namespace: { $exists: false } }).toArray();
|
|
const searchTerm = ':';
|
|
|
|
for (const doc of docs || []) {
|
|
const tokens2 = doc._id.toString().split(searchTerm).slice(0, 2);
|
|
const namespace = tokens2.join(searchTerm);
|
|
await this.collection.updateOne({ _id: doc._id }, { $set: { namespace } });
|
|
}
|
|
}
|
|
|
|
async get(namespace: string, key: string): Promise<any> {
|
|
const res = await this.collection.findOne({
|
|
_id: dbutils.key(namespace, key) as any,
|
|
});
|
|
if (res && res.value) {
|
|
return res.value;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
async getAll(namespace: string, pageOffset?: number, pageLimit?: number, _?: string): Promise<Records> {
|
|
const docs = await this.collection
|
|
.find({ namespace: namespace }, { sort: { createdAt: -1 }, skip: pageOffset, limit: pageLimit })
|
|
.toArray();
|
|
|
|
if (docs) {
|
|
return { data: docs.map(({ value }) => value) };
|
|
}
|
|
return { data: [] };
|
|
}
|
|
|
|
async getByIndex(
|
|
namespace: string,
|
|
idx: Index,
|
|
offset?: number,
|
|
limit?: number,
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
_?: string
|
|
): Promise<Records> {
|
|
const docs =
|
|
dbutils.isNumeric(offset) && dbutils.isNumeric(limit)
|
|
? await this.collection
|
|
.find(
|
|
{
|
|
indexes: dbutils.keyForIndex(namespace, idx),
|
|
},
|
|
{ sort: { createdAt: -1 }, skip: offset, limit: limit }
|
|
)
|
|
.toArray()
|
|
: await this.collection
|
|
.find({
|
|
indexes: dbutils.keyForIndex(namespace, idx),
|
|
})
|
|
.toArray();
|
|
|
|
const ret: string[] = [];
|
|
for (const doc of docs || []) {
|
|
ret.push(doc.value);
|
|
}
|
|
|
|
return { data: ret };
|
|
}
|
|
|
|
async put(namespace: string, key: string, val: Encrypted, ttl = 0, ...indexes: any[]): Promise<void> {
|
|
const doc = <_Document>{
|
|
value: val,
|
|
};
|
|
|
|
if (ttl) {
|
|
doc.expiresAt = new Date(Date.now() + ttl * 1000);
|
|
}
|
|
doc.namespace = namespace;
|
|
// no ttl support for secondary indexes
|
|
for (const idx of indexes || []) {
|
|
const idxKey = dbutils.keyForIndex(namespace, idx);
|
|
|
|
if (!doc.indexes) {
|
|
doc.indexes = [];
|
|
}
|
|
doc.indexes.push(idxKey);
|
|
}
|
|
|
|
doc.modifiedAt = new Date().toISOString();
|
|
await this.collection.updateOne(
|
|
{ _id: dbutils.key(namespace, key) as any },
|
|
{
|
|
$set: doc,
|
|
$setOnInsert: {
|
|
createdAt: new Date().toISOString(),
|
|
},
|
|
},
|
|
{ upsert: true } as UpdateOptions
|
|
);
|
|
}
|
|
|
|
async delete(namespace: string, key: string): Promise<any> {
|
|
return await this.collection.deleteOne({
|
|
_id: dbutils.key(namespace, key) as any,
|
|
});
|
|
}
|
|
|
|
async deleteMany(namespace: string, keys: string[]): Promise<void> {
|
|
if (keys.length === 0) {
|
|
return;
|
|
}
|
|
|
|
const dbKeys = keys.map((key) => dbutils.key(namespace, key)) as any[];
|
|
|
|
await this.collection.deleteMany({
|
|
_id: { $in: dbKeys },
|
|
});
|
|
}
|
|
}
|
|
|
|
export default {
|
|
new: async (options: DatabaseOption): Promise<Mongo> => {
|
|
return await new Mongo(options).init();
|
|
},
|
|
};
|