UNPKG

cap-cds-mongoose

Version:

MongoDB (Mongoose) persistence adapter & deployer for SAP CAP (works like @cap-js/hana / @cap-js/sqlite)

129 lines (109 loc) 5.37 kB
// lib/adapter.js const cds = require("@sap/cds"); const { connect } = require("./connection"); const { getModel } = require("./mapper"); class MongooseAdapter extends cds.Service { constructor(csn, options = {}) { super(csn); this.csn = csn; this.options = options || {}; } async init() { // Connect to MongoDB first await connect(process.env.MONGO_URL); // register generic handlers this.on("READ", this._onRead.bind(this)); this.on("CREATE", this._onCreate.bind(this)); this.on("UPDATE", this._onUpdate.bind(this)); this.on("DELETE", this._onDelete.bind(this)); return super.init && super.init(); } // Helper to resolve model from request target _resolveModel(req) { const target = req.target || req._target || req.event && req.event.target; // target may be entity object or string let entityName = (typeof target === "string") ? target : (target && target.name); if (!entityName && req.path && req.path.length) { // fallback: try first path element entityName = req.path[0].id || req.path[0].ref && req.path[0].ref[0]; } // Try to find entity in csn model definitions if needed const entity = this.csn.definitions && this.csn.definitions[entityName] ? this.csn.definitions[entityName] : req.target; return getModel(this.csn, entity); } async _onRead(req) { const model = this._resolveModel(req); // Build a Mongoose query using minimal parts of req.query (where, limit, select) let q = model.find(); // Where clause: translate simple filters (if CDS query present) if (req.query && req.query.SELECT && req.query.SELECT.where) { // naive conversion: let CAP generate filter object from req.query // fallback: if req.data provides direct where, use it // For complex filter translation you'd need a dedicated parser - keep basic support here // If req.data is an object, treat it as where clause // Many CAP requests will be processed by the framework before hitting adapter. We'll allow req.query to be used if it's object-like. } if (req.query && req.query.SELECT && req.query.SELECT.limit) { q = q.limit(req.query.SELECT.limit); } if (req.query && req.query.SELECT && req.query.SELECT.offset) { q = q.skip(req.query.SELECT.offset); } // Handle projections (SELECT columns) if (req.query && req.query.SELECT && req.query.SELECT.columns) { // build projection string const cols = req.query.SELECT.columns.map(c => (c.ref ? c.ref.join(".") : c.as || c)) /* best-effort */; q = q.select(cols.join(" ")); } // Handle expand -> use populate if (req.query && req.query.SELECT && req.query.SELECT.expand) { // req.query.SELECT.expand is object keyed by association name in CDS AST const expands = Object.keys(req.query.SELECT.expand); for (const exp of expands) { q = q.populate(exp); } } else if (req._query && req._query.SELECT && req._query.SELECT.expand) { // sometimes framework uses _query const expands = Object.keys(req._query.SELECT.expand); for (const exp of expands) q = q.populate(exp); } // If client provided req.data as filter (simple use-case) if (req.data && typeof req.data === "object" && Object.keys(req.data).length) { q = model.find(req.data); } const res = await q.lean().exec(); return res; } async _onCreate(req) { const model = this._resolveModel(req); // allow single or bulk const payload = Array.isArray(req.data) ? req.data : [req.data]; const docs = await model.insertMany(payload); // return inserted docs (lean) return Array.isArray(req.data) ? docs : docs[0]; } async _onUpdate(req) { const model = this._resolveModel(req); // CAP passes keys in req.data (or req.params) depending on call site. // We'll support: // - req.data: object with key/fields to change and optional where // - req.params[0] if CAP used action-like calls. const where = req.query && req.query.SELECT && req.query.SELECT.where ? req.query.SELECT.where : req.data && req.data.where ? req.data.where : req.params && req.params.length ? req.params[0] : {}; const patch = req.data && req.data._patch ? req.data._patch : req.data && !req.data.where ? req.data : (req.data && req.data.patch) || {}; // If no explicit where, try to use keys from req.data (best-effort) if ((!where || Object.keys(where).length === 0) && patch && Object.keys(patch).length === 0) { // Use req.data as patch, remove keys which look like selectors? best-effort } // Use updateMany for multiple rows or findOneAndUpdate for single if keys provided // Simpler approach: attempt updateMany with where and patch await model.updateMany(where, { $set: patch }).exec(); return {}; // CAP expects some confirmation; adapt if more specific return needed } async _onDelete(req) { const model = this._resolveModel(req); const where = (req.query && req.query.SELECT && req.query.SELECT.where) || req.data || (req.params && req.params[0]) || {}; await model.deleteMany(where).exec(); return {}; } } module.exports = MongooseAdapter;