@dataql/mongodb-adapter
Version:
MongoDB adapter for DataQL with zero API changes
558 lines • 20.1 kB
JavaScript
"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