@furystack/mongodb-store
Version:
MongoDB Store implementation for FuryStack
150 lines • 5.34 kB
JavaScript
import { EventHub } from '@furystack/utils';
import { ObjectId } from 'mongodb';
import { Lock } from 'semaphore-async-await';
const hasObjectId = (value) => value._id instanceof ObjectId;
/**
* MongoDB Store implementation for FuryStack
*/
export class MongodbStore extends EventHub {
options;
primaryKey;
model;
initLock = new Lock();
collection;
createIdFilter(...values) {
return {
[this.primaryKey]: {
$in: this.primaryKey === '_id' ? values.map((value) => new ObjectId(value)) : values,
},
};
}
stringifyResultId(item) {
if (this.primaryKey === '_id' && hasObjectId(item)) {
return {
...item,
_id: item._id.toHexString(),
};
}
return item;
}
parseFilter(filter) {
if (!filter) {
return {};
}
if (Object.keys(filter).includes('_id')) {
const f = { ...filter };
if (typeof f._id === 'string') {
return {
...f,
_id: new ObjectId(f._id),
};
}
if (typeof f._id === 'object') {
if (f._id.$eq) {
f._id.$eq = new ObjectId(f._id.$eq);
}
if (f._id.$in && f._id.$in instanceof Array) {
f._id.$in = f._id.$in.map((id) => new ObjectId(id));
}
if (f._id.$nin && f._id.$nin instanceof Array) {
f._id.$nin = f._id.$nin.map((id) => new ObjectId(id));
}
}
return f;
}
return filter;
}
async getCollection() {
if (this.collection) {
return this.collection;
}
await this.initLock.acquire();
if (this.collection) {
return this.collection;
}
try {
const client = this.options.mongoClient();
const collection = client.db(this.options.db).collection(this.options.collection);
if (this.primaryKey !== '_id') {
await collection.createIndex({ [this.primaryKey]: 1 }, { unique: true });
}
this.collection = collection;
return collection;
}
finally {
this.initLock.release();
}
}
constructor(options) {
super();
this.options = options;
this.primaryKey = options.primaryKey;
this.model = options.model;
}
async add(...entries) {
const collection = await this.getCollection();
const result = await collection.insertMany(entries.map((e) => ({ ...e })));
const created = this.primaryKey === '_id'
? Object.values(result.insertedIds).map((insertedId, index) => this.stringifyResultId({ _id: insertedId, ...entries[index] }))
: Object.values(result.insertedIds).map((insertedId, index) => {
const entity = { _id: insertedId, ...entries[index] };
const { _id, ...r } = entity;
return r;
});
created.forEach((entity) => this.emit('onEntityAdded', { entity }));
return {
created,
};
}
async update(id, data) {
const collection = await this.getCollection();
const updateResult = await collection.updateOne(this.createIdFilter(id), { $set: data });
if (updateResult.matchedCount < 1) {
throw Error(`Entity not found with id '${id}', cannot update!`);
}
this.emit('onEntityUpdated', { id, change: data });
}
async count(filter) {
const collection = await this.getCollection();
return await collection.countDocuments(this.parseFilter(filter), {});
}
async find(filter) {
const collection = await this.getCollection();
const sort = filter.order
? Object.fromEntries([
...Object.keys(filter.order).map((key) => [
key,
filter.order[key] === 'ASC' ? 1 : -1,
]),
])
: {};
const result = await collection
.find(this.parseFilter(filter.filter))
.project(this.getProjection(filter.select))
.skip(filter.skip || 0)
.limit(filter.top || Number.MAX_SAFE_INTEGER)
.sort(sort)
.toArray();
return result.map((entity) => this.stringifyResultId(entity));
}
getProjection(fields) {
return {
...(this.primaryKey !== '_id' ? { _id: 0 } : {}),
...(fields ? Object.fromEntries(fields.map((field) => [field, 1])) : {}),
};
}
async get(key, select) {
const collection = await this.getCollection();
const projection = this.getProjection(select);
const result = await collection.findOne(this.createIdFilter(key), {
projection,
});
return result ? this.stringifyResultId(result) : undefined;
}
async remove(...keys) {
const collection = await this.getCollection();
await collection.deleteMany(this.createIdFilter(...keys));
keys.forEach((key) => this.emit('onEntityRemoved', { key }));
}
}
//# sourceMappingURL=mongodb-store.js.map