UNPKG

@furystack/mongodb-store

Version:

MongoDB Store implementation for FuryStack

148 lines 5.64 kB
import { EventHub } from '@furystack/utils'; import { ObjectId } from 'mongodb'; import { Lock } from 'semaphore-async-await'; // Improved type safety for hasObjectId const hasObjectId = (value) => { return value && typeof value === 'object' && value._id instanceof ObjectId; }; /** * MongoDB Store implementation for FuryStack */ export class MongodbStore extends EventHub { options; primaryKey; model; initLock = new Lock(); collection; createIdFilter(...values) { // If primaryKey is _id, convert string values to ObjectId return { [this.primaryKey]: { $in: this.primaryKey === '_id' ? values.map((value) => new ObjectId(value)) : values, }, }; } stringifyResultId(item) { // If _id is ObjectId, convert to string if (this.primaryKey === '_id' && hasObjectId(item)) { return { ...item, _id: item._id.toHexString(), }; } return item; } parseFilter(filter) { if (!filter) { return {}; } // Only handle _id conversion if present if (Object.prototype.hasOwnProperty.call(filter, '_id')) { const f = { ...filter }; if (typeof f._id === 'string') { f._id = new ObjectId(f._id); } else if (typeof f._id === 'object' && f._id !== null) { const idObj = f._id; if (idObj.$eq && typeof idObj.$eq === 'string') { idObj.$eq = new ObjectId(idObj.$eq); } if (Array.isArray(idObj.$in)) { idObj.$in = idObj.$in.map((id) => new ObjectId(id)); } if (Array.isArray(idObj.$nin)) { idObj.$nin = idObj.$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) => // Use 'unknown' as intermediate cast to satisfy TypeScript this.stringifyResultId({ _id: insertedId, ...entries[index] })) : Object.values(result.insertedIds).map((insertedId, index) => { // Use 'unknown' as intermediate cast to satisfy TypeScript 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 '${String(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.entries(filter.order).map(([key, value]) => [key, value === '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