UNPKG

@dataql/mongodb-adapter

Version:

MongoDB adapter for DataQL with zero API changes

558 lines 20.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.BSON = exports.MongoClient = exports.Db = exports.Collection = exports.FindCursor = exports.ObjectId = void 0; exports.connect = connect; const core_1 = require("@dataql/core"); // ObjectId class for MongoDB compatibility class ObjectId { constructor(id) { if (id instanceof ObjectId) { this._id = id.toString(); } else if (typeof id === "string") { this._id = id; } else { // Generate a random ObjectId-like string this._id = this._generateObjectId(); } } _generateObjectId() { const timestamp = Math.floor(Date.now() / 1000).toString(16); const randomBytes = Math.random().toString(16).substr(2, 16); return (timestamp + randomBytes).substr(0, 24); } toString() { return this._id; } toHexString() { return this._id; } equals(other) { if (other instanceof ObjectId) { return this._id === other._id; } return this._id === other; } getTimestamp() { const timestamp = parseInt(this._id.substr(0, 8), 16); return new Date(timestamp * 1000); } static isValid(id) { if (typeof id === "string") { return /^[0-9a-fA-F]{24}$/.test(id); } return id instanceof ObjectId; } static createFromHexString(hexString) { return new ObjectId(hexString); } } exports.ObjectId = ObjectId; // Cursor class for query results class FindCursor { constructor(data, collectionName, filter = {}, options = {}) { this._data = data; this._collectionName = collectionName; this._filter = filter; this._options = options; } get _collection() { return this._data.collection(this._collectionName, this._getDefaultSchema()); } _getDefaultSchema() { return { _id: { type: "ID", required: true }, createdAt: { type: "Date", default: "now" }, updatedAt: { type: "Date", default: "now" }, }; } async toArray() { if (this._results) { return this._results; } let results = await this._collection.find(this._filter); // Apply sorting if (this._options.sort) { results = this._applySorting(results, this._options.sort); } // Apply skip if (this._options.skip) { results = results.slice(this._options.skip); } // Apply limit if (this._options.limit) { results = results.slice(0, this._options.limit); } // Apply projection if (this._options.projection) { results = this._applyProjection(results, this._options.projection); } this._results = results; return results; } _applySorting(results, sort) { return results.sort((a, b) => { for (const [field, direction] of Object.entries(sort)) { const aVal = a[field]; const bVal = b[field]; const sortDir = direction === -1 ? -1 : 1; if (aVal < bVal) return -1 * sortDir; if (aVal > bVal) return 1 * sortDir; } return 0; }); } _applyProjection(results, projection) { const isInclusion = Object.values(projection).some((val) => val === 1); return results.map((doc) => { if (isInclusion) { const projected = {}; for (const [field, include] of Object.entries(projection)) { if (include === 1) { projected[field] = doc[field]; } } return projected; } else { const projected = { ...doc }; for (const [field, exclude] of Object.entries(projection)) { if (exclude === 0) { delete projected[field]; } } return projected; } }); } async next() { const results = await this.toArray(); return results.length > 0 ? results.shift() : null; } async hasNext() { const results = await this.toArray(); return results.length > 0; } async forEach(fn) { const results = await this.toArray(); results.forEach(fn); } map(fn) { // Return a new cursor with mapped results const newCursor = new FindCursor(this._data, this._collectionName, this._filter, this._options); return newCursor; } filter(fn) { // Return a new cursor with filtered results return new FindCursor(this._data, this._collectionName, this._filter, this._options); } limit(count) { const newCursor = new FindCursor(this._data, this._collectionName, this._filter, { ...this._options, limit: count }); return newCursor; } skip(count) { const newCursor = new FindCursor(this._data, this._collectionName, this._filter, { ...this._options, skip: count }); return newCursor; } sort(sort) { const newCursor = new FindCursor(this._data, this._collectionName, this._filter, { ...this._options, sort }); return newCursor; } project(projection) { const newCursor = new FindCursor(this._data, this._collectionName, this._filter, { ...this._options, projection }); return newCursor; } async count() { const results = await this._collection.find(this._filter); return results.length; } async *[Symbol.asyncIterator]() { const results = await this.toArray(); for (const result of results) { yield result; } } } exports.FindCursor = FindCursor; // Collection class class Collection { constructor(_data, _db, collectionName) { this._data = _data; this._db = _db; this.collectionName = collectionName; } get _collection() { return this._data.collection(this.collectionName, this._getDefaultSchema()); } _getDefaultSchema() { return { _id: { type: "ID", required: true }, createdAt: { type: "Date", default: "now" }, updatedAt: { type: "Date", default: "now" }, }; } // Insert operations async insertOne(doc, options) { const docWithId = { _id: new ObjectId(), ...doc, }; const result = await this._collection.create(docWithId); return { acknowledged: true, insertedId: docWithId._id, }; } async insertMany(docs, options) { const docsWithIds = docs.map((doc, index) => ({ _id: new ObjectId(), ...doc, })); const results = await this._collection.create(docsWithIds); const insertedIds = {}; docsWithIds.forEach((doc, index) => { insertedIds[index] = doc._id; }); return { acknowledged: true, insertedCount: docsWithIds.length, insertedIds, }; } // Find operations find(filter = {}, options) { return new FindCursor(this._data, this.collectionName, filter, options); } async findOne(filter = {}, options) { const cursor = this.find(filter, { ...options, limit: 1 }); const results = await cursor.toArray(); return results.length > 0 ? results[0] : null; } async findOneAndUpdate(filter, update, options) { const existing = await this.findOne(filter); if (!existing && !(options === null || options === void 0 ? void 0 : options.upsert)) { return { value: null }; } const updateResult = await this._collection.update(filter, update, options === null || options === void 0 ? void 0 : options.upsert); if ((options === null || options === void 0 ? void 0 : options.returnDocument) === "before") { return { value: existing }; } else { const updated = await this.findOne(filter); return { value: updated }; } } async findOneAndDelete(filter) { const existing = await this.findOne(filter); if (existing) { await this._collection.delete(filter); } return { value: existing }; } async findOneAndReplace(filter, replacement, options) { const existing = await this.findOne(filter); if (!existing && !(options === null || options === void 0 ? void 0 : options.upsert)) { return { value: null }; } await this._collection.update(filter, replacement, options === null || options === void 0 ? void 0 : options.upsert); if ((options === null || options === void 0 ? void 0 : options.returnDocument) === "before") { return { value: existing }; } else { const updated = await this.findOne(filter); return { value: updated }; } } // Update operations async updateOne(filter, update, options) { const result = await this._collection.update(filter, update, options === null || options === void 0 ? void 0 : options.upsert); return { acknowledged: true, matchedCount: result ? 1 : 0, modifiedCount: result ? 1 : 0, upsertedId: (options === null || options === void 0 ? void 0 : options.upsert) && !result ? new ObjectId() : undefined, upsertedCount: (options === null || options === void 0 ? void 0 : options.upsert) && !result ? 1 : 0, }; } async updateMany(filter, update, options) { // DataQL doesn't have updateMany, so we'll find and update individually const existing = await this._collection.find(filter); let modifiedCount = 0; for (const doc of existing) { const result = await this._collection.update({ _id: doc._id }, update); if (result) modifiedCount++; } return { acknowledged: true, matchedCount: existing.length, modifiedCount, upsertedCount: 0, }; } async replaceOne(filter, replacement, options) { const result = await this._collection.update(filter, replacement, options === null || options === void 0 ? void 0 : options.upsert); return { acknowledged: true, matchedCount: result ? 1 : 0, modifiedCount: result ? 1 : 0, upsertedId: (options === null || options === void 0 ? void 0 : options.upsert) && !result ? new ObjectId() : undefined, upsertedCount: (options === null || options === void 0 ? void 0 : options.upsert) && !result ? 1 : 0, }; } // Delete operations async deleteOne(filter, options) { const existing = await this.findOne(filter); if (existing) { await this._collection.delete(filter); return { acknowledged: true, deletedCount: 1, }; } return { acknowledged: true, deletedCount: 0, }; } async deleteMany(filter, options) { const existing = await this._collection.find(filter); let deletedCount = 0; for (const doc of existing) { await this._collection.delete({ _id: doc._id }); deletedCount++; } return { acknowledged: true, deletedCount, }; } // Bulk operations async bulkWrite(operations, options) { let insertedCount = 0; let matchedCount = 0; let modifiedCount = 0; let deletedCount = 0; let upsertedCount = 0; const insertedIds = {}; const upsertedIds = {}; for (let i = 0; i < operations.length; i++) { const operation = operations[i]; try { if ("insertOne" in operation) { const result = await this.insertOne(operation.insertOne.document); insertedCount++; insertedIds[i] = result.insertedId; } else if ("updateOne" in operation) { const result = await this.updateOne(operation.updateOne.filter, operation.updateOne.update, { upsert: operation.updateOne.upsert }); matchedCount += result.matchedCount; modifiedCount += result.modifiedCount; if (result.upsertedId) { upsertedCount++; upsertedIds[i] = result.upsertedId; } } else if ("updateMany" in operation) { const result = await this.updateMany(operation.updateMany.filter, operation.updateMany.update, { upsert: operation.updateMany.upsert }); matchedCount += result.matchedCount; modifiedCount += result.modifiedCount; upsertedCount += result.upsertedCount; } else if ("deleteOne" in operation) { const result = await this.deleteOne(operation.deleteOne.filter); deletedCount += result.deletedCount; } else if ("deleteMany" in operation) { const result = await this.deleteMany(operation.deleteMany.filter); deletedCount += result.deletedCount; } else if ("replaceOne" in operation) { const result = await this.replaceOne(operation.replaceOne.filter, operation.replaceOne.replacement, { upsert: operation.replaceOne.upsert }); matchedCount += result.matchedCount; modifiedCount += result.modifiedCount; if (result.upsertedId) { upsertedCount++; upsertedIds[i] = result.upsertedId; } } } catch (error) { if (!(options === null || options === void 0 ? void 0 : options.ordered)) { continue; // Continue with next operation if not ordered } throw error; // Stop on first error if ordered } } return { acknowledged: true, insertedCount, matchedCount, modifiedCount, deletedCount, upsertedCount, insertedIds, upsertedIds, }; } // Count operations async countDocuments(filter = {}) { const results = await this._collection.find(filter); return results.length; } async estimatedDocumentCount() { return this.countDocuments(); } // Aggregation aggregate(pipeline) { // Basic aggregation support - would need more sophisticated implementation return { toArray: async () => { // For now, just return find results const results = await this._collection.find({}); return results; }, }; } // Index operations (no-op for DataQL) async createIndex(fieldOrSpec, options) { return "index_created"; } async createIndexes(indexSpecs) { return indexSpecs.map((_, i) => `index_${i}_created`); } async dropIndex(indexName) { return { ok: 1 }; } async dropIndexes() { return { ok: 1 }; } async listIndexes() { return []; } // Utility methods async distinct(field, filter = {}) { const results = await this._collection.find(filter); const values = results.map((doc) => doc[field]); return [...new Set(values)]; } // Drop collection async drop() { // DataQL doesn't have explicit drop - return true for compatibility return true; } } exports.Collection = Collection; // Database class class Db { constructor(_data, databaseName) { this._data = _data; this.databaseName = databaseName; } collection(name) { return new Collection(this._data, this, name); } async listCollections() { // DataQL doesn't track collections separately return []; } async dropCollection(name) { // DataQL doesn't have explicit drop - return true for compatibility return true; } async dropDatabase() { // DataQL doesn't have explicit drop - return ok for compatibility return { ok: 1 }; } async stats() { return { db: this.databaseName, collections: 0, views: 0, objects: 0, avgObjSize: 0, dataSize: 0, storageSize: 0, totalSize: 0, indexes: 0, indexSize: 0, scaleFactor: 1, }; } } exports.Db = Db; // MongoClient class class MongoClient { constructor(_url, _options) { var _a, _b, _c, _d; this._url = _url; this._options = _options; this._connected = false; // Extract database name from MongoDB URL for DataQL database isolation this._databaseName = this._extractDatabaseName(_url); // Configure DataQL to work through its infrastructure (Client → Worker → Lambda → MongoDB) const dataqlOptions = { // Pass database name for DataQL's per-client database isolation dbName: this._databaseName || "mongodb_app", // MongoDB URL will be handled by DataQL's infrastructure routing appToken: ((_a = _options === null || _options === void 0 ? void 0 : _options.dataql) === null || _a === void 0 ? void 0 : _a.appToken) || "mongodb_adapter", env: ((_b = _options === null || _options === void 0 ? void 0 : _options.dataql) === null || _b === void 0 ? void 0 : _b.env) || "prod", devPrefix: ((_c = _options === null || _options === void 0 ? void 0 : _options.dataql) === null || _c === void 0 ? void 0 : _c.devPrefix) || "mongodb_", customConnection: (_d = _options === null || _options === void 0 ? void 0 : _options.dataql) === null || _d === void 0 ? void 0 : _d.customConnection, }; this._data = new core_1.Data(dataqlOptions); } _extractDatabaseName(url) { try { // Extract database name from MongoDB connection string // mongodb://host:port/database or mongodb+srv://host/database const match = url.match(/\/([^/?]+)(?:\?|$)/); return match ? match[1] : undefined; } catch (_a) { return undefined; } } async connect() { // Connection is handled by DataQL infrastructure this._connected = true; return this; } async close() { // Cleanup handled by DataQL this._connected = false; } db(name) { if (!this._connected) { throw new Error("MongoClient must be connected before running operations"); } // Database operations go through DataQL infrastructure const dbName = name || this._databaseName || "default"; return new Db(this._data, dbName); } isConnected() { return this._connected; } // Static connect method static async connect(url, options) { const client = new MongoClient(url, options); await client.connect(); return client; } } exports.MongoClient = MongoClient; // Default export exports.default = MongoClient; // Additional MongoDB utilities exports.BSON = { ObjectId, ObjectID: ObjectId, // Legacy alias }; // Connection helper async function connect(url, options) { return MongoClient.connect(url, options); } //# sourceMappingURL=index.js.map