mongodb-local-data-api
Version:
Run your own version of the MongoDB Atlas Data API.
124 lines (115 loc) • 4.2 kB
JavaScript
import { MongoClient } from 'mongodb'
const supportedActions = [
'aggregate',
'deleteOne',
'deleteMany',
'find',
'findOne',
'insertOne',
'insertMany',
'replaceOne',
'updateOne',
'updateMany',
]
const actionValidation = {
insertOne: ({ document }) => {
if (!document) return {
status: 400,
body: "Failed to insert document: must provide a 'document' field",
}
},
}
const specialAction = {
aggregate: (coll, params) => coll.aggregate(params.pipeline || []).toArray().then(documents => ({ documents })),
deleteMany: (coll, params) => {
const { filter, ...remaining } = params
return coll.deleteMany(filter || {}, remaining || {})
},
find: (coll, params) => {
const { filter, ...remaining } = params
return coll.find(filter, remaining || {}).toArray().then(documents => ({ documents }))
},
findOne: (coll, params) => {
const { filter, ...remaining } = params
return coll.findOne(filter, remaining).then(document => ({ document }))
},
insertMany: async (coll, params) => {
const { documents, ...remaining } = params
const { insertedIds } = await coll.insertMany(documents, remaining)
return { insertedIds: Object.values(insertedIds).map(o => o.toString()) }
},
insertOne: async (coll, params) => coll.insertOne(params.document),
replaceOne: async (coll, params) => {
const { filter, replacement, ...remaining } = params
const { matchedCount, modifiedCount, upsertedId } = await coll.replaceOne(filter, replacement, remaining)
const out = { matchedCount, modifiedCount }
if (upsertedId) out.upsertedId = upsertedId
return out
},
updateOne: async (coll, params) => {
const { filter, update, ...remaining } = params
const { matchedCount, modifiedCount, upsertedId } = await coll.updateOne(filter, update, remaining)
const out = { matchedCount, modifiedCount }
if (upsertedId) out.upsertedId = upsertedId
return out
},
updateMany: async (coll, params) => {
const { filter, update, ...remaining } = params
const { matchedCount, modifiedCount, upsertedId } = await coll.updateMany(filter, update, remaining)
const out = { matchedCount, modifiedCount }
if (upsertedId) out.upsertedId = upsertedId
return out
},
}
const tidyResults = (actionName, body) => {
if (body && body.acknowledged) delete body.acknowledged
if (body && body.insertedCount) delete body.insertedCount
return body
}
export const setup = ({ url, verbose, retryCount }) => {
const databaseToConnection = {}
return async (actionName, { dataSource, database, collection, ...params }) => {
if (dataSource && verbose) console.warn('The property `dataSource` is currently ignored for local databases.')
if (!supportedActions.includes(actionName)) return { status: 404, body: '' }
const validationError = actionValidation[actionName] && actionValidation[actionName](params)
if (validationError) return validationError
let running
const run = async () => {
if (!databaseToConnection[database]) {
console.log(`Attempting to connect to: ${database}`)
const client = new MongoClient(url)
await client.connect()
databaseToConnection[database] = client.db(database)
}
const coll = databaseToConnection[database].collection(collection)
const results = await (specialAction[actionName] ? specialAction[actionName](coll, params) : coll[actionName](params))
running = true
return {
count: results?.documents?.length,
status: actionName.startsWith('insert') ? 201 : 200,
body: tidyResults(actionName, results),
}
}
try {
return await run()
} catch (firstRunError) {
if (firstRunError.message.includes('ECONNREFUSED')) {
console.log('Connection to MongoDB was interrupted, trying again...')
let retries = 1
while (!running && (retries < retryCount || retryCount === undefined)) {
try {
return await run()
} catch (retryError) {
if (!retryError.message.includes('ECONNREFUSED')) throw retryError
console.log(`Reconnect retry ${++retries} of ${retryCount === undefined ? '∞' : retryCount}...`)
}
}
}
console.log('Error handling request:', firstRunError.message)
return {
status: 400,
body: firstRunError.message,
}
}
}
}