import {Collection, MongoClient, TransactionOptions} from "mongodb" import {down, downs, up, ups} from "./mongo/conversions.js" import {objectMap, objectMap3} from "../tools/object-map.js" import {orderToSort, prepareQuery} from "./mongo/queries.js" import {makeTableNameWithUnderscores} from "./utils/make-table-name-with-underscores.js" import {AmbiguousUpdate, MongoDatabase, Row, Schema, SchemaToShape, SchemaToTables, Shape, Table, Tables} from "../types.js" export function mongo({ dbName, client, shape, transactionOptions, makeTableName = makeTableNameWithUnderscores, }: { dbName: string client: MongoClient shape: SchemaToShape transactionOptions?: TransactionOptions makeTableName?: (path: string[]) => string }): MongoDatabase { const db = client.db(dbName) function makeTable(collection: Collection): Table { return { async create(...rows) { await collection.insertMany(ups(rows)) }, async read({conditions, order, offset, limit}) { const query = prepareQuery({conditions: conditions}) let cursor = collection.find(query, undefined) if (offset) cursor = cursor.skip(offset) if (order) cursor = cursor.sort(orderToSort(order)) if (limit) cursor = cursor.limit(limit) const rows = await cursor.toArray() return downs(rows) }, async update({ write, whole, upsert, ...conditional }: AmbiguousUpdate) { const query = prepareQuery(conditional) if (write) { await collection.updateMany(query, {$set: up(write)}, {upsert: false}) } else if (upsert) { await collection.updateOne(query, {$set: up(upsert)}, {upsert: true}) } else if (whole) { await collection.deleteMany(query) await collection.insertOne(up(whole)) } else throw new Error("invalid update") }, async delete(conditional) { const query = prepareQuery(conditional) await collection.deleteMany(query) }, async count(conditional) { const query = prepareQuery(conditional) return collection.countDocuments(query) }, async average({fields, ...conditional}) { const query = prepareQuery(conditional) const aggr = await collection.aggregate([ {$match: query}, {$group: { _id: null, ...objectMap3(fields, (value, key) => [key, {$avg: "$" + key}]), }}, ]).toArray() const [document] = aggr return objectMap3(fields, (value, key) => document[key]) }, async readOne(conditional) { const query = prepareQuery(conditional) const row = await collection.findOne(query) return down(row) }, } } function makeTables() { function recurse(shape: Shape, path: string[]): Tables { return objectMap(shape, (value, key) =>{ const currentPath = [...path, key] const storageKey = makeTableName(currentPath) const collection = db.collection(storageKey) return typeof value === "boolean" ? makeTable(collection) : recurse(value, [...path, key]) }) } return recurse(shape, []) } const tables = >makeTables() return { tables, async transaction(action, options = transactionOptions) { const session = client.startSession() let result: any try { result = await session.withTransaction(async() => action({ tables, async abort() { await session.abortTransaction() }, }), options) } catch (error) { console.error("transaction failed") } finally { await session.endSession() } return result }, } }