Created
July 2, 2025 12:04
-
-
Save pozylon/41d76702c2184e19a599599e8c7db84c to your computer and use it in GitHub Desktop.
MongoProxy: JS Object with DB Backend
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| MIT LICENSE | |
| Copyright (c) 2023-2024, Pascal Kaufmann | |
| MongoProxy is a utility to create a proxy for MongoDB documents. | |
| It allows you to create a proxy object that syncs with a MongoDB collection. | |
| The proxy object can be used to read and write properties, and it will automatically | |
| update a MongoDB collection when properties are set. | |
| Benefits: | |
| - Non-blocking sync access to stateful data | |
| - Writes synchronize optimistically to MongoDB | |
| Use it like: | |
| import mongoProxy, { connect } from './mongo-proxy'; | |
| import { MongoClient } from 'mongodb'; | |
| const myDoc = mongoProxy({ name: 'John Doe', age: 30 }, import.meta.filename); | |
| await connect(MongoClient); | |
| await myDoc.sync(); | |
| myDoc.age = 31; | |
| console.log(myDoc.name); // 'John Doe' | |
| console.log(myDoc.age); // 31 | |
| */ | |
| const syncState = []; | |
| const updateQueue = {}; | |
| let collection: any; | |
| export default function mongoProxy<T extends Record<string, any>>( | |
| object: T, | |
| forcedDocumentId?: string, | |
| ): T & { | |
| sync: () => Promise<void>; | |
| } { | |
| const docId = forcedDocumentId; | |
| if (!updateQueue[docId]) updateQueue[docId] = []; | |
| const handler: ProxyHandler<any> & { injectProxy: any } = { | |
| injectProxy(path, obj) { | |
| if (typeof obj !== "object") return obj; | |
| obj[Symbol.for("proxy-path")] = path; | |
| return new Proxy(obj, this); | |
| }, | |
| get(obj, prop) { | |
| if (prop === "sync") { | |
| return async () => { | |
| try { | |
| await runUpdateQueue(); | |
| } catch { | |
| /* */ | |
| } | |
| if (syncState[docId] !== undefined) { | |
| Object.assign(obj, syncState[docId]); | |
| delete syncState[docId]; | |
| } | |
| }; | |
| } | |
| if (syncState[docId] !== undefined) { | |
| Object.assign(obj, syncState[docId]); | |
| delete syncState[docId]; | |
| } | |
| const currentPath = [obj[Symbol.for("proxy-path")], prop] | |
| .filter(Boolean) | |
| .join("."); | |
| return this.injectProxy(currentPath, obj[prop]); | |
| }, | |
| set(obj, prop, value) { | |
| const currentPath = [obj[Symbol.for("proxy-path")], prop] | |
| .filter(Boolean) | |
| .join("."); | |
| updateQueue[docId].push({ | |
| update: { | |
| [currentPath]: value, | |
| }, | |
| }); | |
| runUpdateQueue().catch((error) => { | |
| console.error(`Error updating MongoDB for docId ${docId}:`, error); | |
| }); | |
| obj[prop] = value; | |
| return true; | |
| }, | |
| }; | |
| updateQueue[docId].push({ | |
| sync: object, | |
| }); | |
| return new Proxy(object, handler); | |
| } | |
| const runUpdateQueue = async () => { | |
| if (!collection) return; | |
| for (const docId of Object.keys(updateQueue)) { | |
| while (updateQueue[docId].length) { | |
| const update = updateQueue[docId].shift(); | |
| if (update.sync) { | |
| const readRecord = await collection.findOne({ _id: docId }); | |
| if (readRecord) { | |
| syncState[docId] = readRecord || {}; | |
| } else { | |
| await collection.insertOne({ ...update.sync, _id: docId }); | |
| } | |
| } else { | |
| await collection.updateOne( | |
| { _id: docId }, | |
| { $set: update.update }, | |
| { upsert: true }, | |
| ); | |
| } | |
| } | |
| } | |
| }; | |
| export const connect = async ( | |
| mongodb, | |
| mongoURI = process.env.MONGO_URL, | |
| collectionName = "mongo-proxy-state", | |
| ) => { | |
| if (mongoURI) { | |
| try { | |
| const client = new mongodb.MongoClient(mongoURI); | |
| await client.connect(); | |
| const db = await client.db(); | |
| collection = await db.collection(collectionName); | |
| } catch (error) { | |
| console.error("Error connecting MongoProxy to MongoDB:", error); | |
| } | |
| } | |
| }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment