moleculer-db-adapter-cosmos
Version:
Azure CosmosDB SQL native adapter for Moleculer DB service.
439 lines (384 loc) • 10.5 kB
JavaScript
/*
* moleculer-db-adapter-cosmosDB
* Copyright (c) 2019 MoleculerJS (https://github.com/moleculerjs/moleculer-db)
* MIT Licensed
*/
;
const _ = require("lodash");
const Promise = require("bluebird");
const { ServiceSchemaError } = require("moleculer").Errors;
const { CosmosClient } = require("@azure/cosmos");
class CosmosDbAdapter {
/**
* Creates an instance of CosmosDbAdapter.
* @param {Object} connection
* @param {string} dbName
* @param {string} containerName
*
* @memberof CosmosDbAdapter
*/
constructor(connection, dbName, containerName) {
this.connection = connection;
this.dbName = dbName;
this.containerName = containerName;
}
/**
* Initialize adapter
*
* @param {ServiceBroker} broker
* @param {Service} service
*
* @memberof CosmosDbAdapter
*/
init(broker, service) {
this.broker = broker;
this.service = service;
if (!this.dbName) {
/* istanbul ignore next */
throw new ServiceSchemaError(
"Missing `database` definition in DB adapter!"
);
}
if (!this.containerName) {
/* istanbul ignore next */
throw new ServiceSchemaError(
"Missing `container` definition in DB adapter!"
);
}
}
/**
* Connect to database
*
* @returns {Promise}
*
* @memberof CosmosDbAdapter
*/
async connect() {
try {
this.client = new CosmosClient(this.connection);
const dbResponse = await this.client.databases.createIfNotExists({
id: this.dbName
});
this.database = dbResponse.database;
const containerResponse = await this.database.containers.createIfNotExists(
{ id: this.containerName }
);
this.container = containerResponse.container;
return Promise.resolve();
} catch (error) {
throw error;
}
}
/**
* Disconnect from database
*
* @returns {Promise}
*
* @memberof CosmosDbAdapter
*/
disconnect() {
// We are making REST requests so just resolve
return Promise.resolve("Disconnected");
}
/**
* Find all entities by filters.
*
* - query
*
* @param {any} filters
* @returns {Promise}
*
* @memberof CosmosDbAdapter
*/
find(filters) {
// throw new Error("Method Not Implemented");
return this.createCursor(filters);
}
/**
* Find an entity by query
*
* @param {Object} query
* @returns {Promise}
*
* @memberof MemoryDbAdapter
*/
async findOne(query) {
// throw new Error("Method `findOne` Not Implemented");
try {
const response = await this.container.items.query(query).fetchAll();
return response.resources;
} catch (error) {
throw error;
}
}
/**
* Find an entities by ID
*
* @param {any} _id
* @returns {Promise}
*
* @memberof CosmosDbAdapter
*/
async findById(_id) {
// throw new Error("Method `findById` Not Implemented");
try {
const readResponse = await this.container.item(_id).read();
if (readResponse.statusCode >= 400) {
throw new Error(`Resource with id "${_id}" not found`);
}
return readResponse.resource;
} catch (error) {
throw error;
}
}
/**
* Find any entities by IDs
*
* @param {Array} idList
* @returns {Promise}
*
* @memberof CosmosDbAdapter
*/
async findByIds(idList) {
// throw new Error("Method `findByIds` Not Implemented");
const ids = idList.map(elem => `"${elem}"`).join(",");
const q = {
query: `SELECT * FROM c WHERE c.id IN (${ids})`,
parameters: []
};
const response = await this.container.items.query(q).fetchAll();
return response.resources;
}
/**
* Get count of filtered entities
*
* Available filter props:
* - query
*
* @param {Object} [filters={}]
* @returns {Promise}
*
* @memberof CosmosDbAdapter
*/
count(filters = {}) {
// throw new Error("Method `count` Not Implemented");
return this.createCursor(filters, true);
}
/**
* Insert an entity
*
* @param {Object} entity
* @returns {Promise}
*
* @memberof CosmosDbAdapter
*/
async insert(entity) {
try {
const response = await this.container.items.create(entity);
return response.resource;
} catch (error) {
throw error;
}
}
/**
* Insert many entities
*
* @param {Array} entities
* @returns {Promise}
*
* @memberof CosmosDbAdapter
*/
insertMany(entities) {
// At this moment there is no Bulk Executor Lib for Node
// More info: https://docs.microsoft.com/en-us/azure/cosmos-db/bulk-executor-overview
// ToDo: Check CosmosDB DB engine stored procedures
// More info: https://docs.microsoft.com/en-us/azure/cosmos-db/stored-procedures-triggers-udfs
this.broker.logger.warn(
"insertMany() method is not Request Units (RUs) friendly. Use it at your own risk. Consider using CosmosDB DB stored procedures"
);
const p = entities.map(entity => this.insert(entity));
try {
const response = Promise.all(p);
return response;
} catch (error) {
throw error;
}
}
/**
* Update an entity by ID and `update`
*
* @param {any} _id
* @param {Object} update
* @returns {Promise}
*
* @memberof CosmosDbAdapter
*/
async updateById(_id, update) {
// throw new Error("Method `updateById` Not Implemented");
try {
const newItem = { id: _id, ...update["$set"] };
const response = await this.container.item(_id).replace(newItem);
return response.resource;
} catch (error) {
throw error;
}
}
/**
* Update many entities
*
* @param {Array} update array of entities
* @returns {Promise}
*
* @memberof CosmosDbAdapter
*/
updateMany(entries) {
// At this moment there is no Bulk Executor Lib for Node
// More info: https://docs.microsoft.com/en-us/azure/cosmos-db/bulk-executor-overview
// ToDo: Check CosmosDB DB stored procedures
// More info: https://docs.microsoft.com/en-us/azure/cosmos-db/stored-procedures-triggers-udfs
this.broker.logger.warn(
"updateMany() method operator is not Request Units (RUs) friendly. Use it at your own risk. Consider using CosmosDB DB stored procedures"
);
try {
const p = entries.map(entry =>
this.container.item(entry.id).replace(entry)
);
const response = Promise.all(p);
return response.map(entry => entry.resource);
} catch (error) {
throw error;
}
}
/**
* Remove an entity by ID
*
* @param {any} _id
* @returns {Promise}
*
* @memberof CosmosDbAdapter
*/
async removeById(_id) {
try {
const readResponse = await this.container.item(_id).read();
if (readResponse.statusCode >= 400) {
throw new Error(`Resource with id "${_id}" not found`);
}
const delResponse = await this.container.item(_id).delete();
if (delResponse.statusCode >= 400) {
throw new Error(`Resource with id "${_id}" not found`);
}
return readResponse.resource;
} catch (error) {
throw error;
}
}
/**
* Remove many entities by their ID
*
* @param {Array<string>} idList
* @returns {Promise}
*
* @memberof CosmosDbAdapter
*/
removeMany(idList) {
// At this moment there is no Bulk Executor Lib for Node
// More info: https://docs.microsoft.com/en-us/azure/cosmos-db/bulk-executor-overview
// ToDo: Check CosmosDB DB engine stored procedures
// More info: https://docs.microsoft.com/en-us/azure/cosmos-db/stored-procedures-triggers-udfs
this.broker.logger.warn(
"removeMany() method is not Request Units (RUs) friendly. Use it at your own risk. Consider using CosmosDB DB stored procedures"
);
try {
const p = idList.map(id => this.removeById(id));
return Promise.all(p);
} catch (error) {
throw error;
}
}
/**
* Clear all entities from container
*
* @returns {Promise}
*
* @memberof CosmosDbAdapter
*/
async clear() {
// At this moment there is no Bulk Executor Lib for Node
// More info: https://docs.microsoft.com/en-us/azure/cosmos-db/bulk-executor-overview
// ToDo: Check CosmosDB DB engine stored procedures
// More info: https://docs.microsoft.com/en-us/azure/cosmos-db/stored-procedures-triggers-udfs
this.broker.logger.warn(
"clear() method is not Request Units (RUs) friendly. Use it at your own risk. Consider using CosmosDB DB stored procedures"
);
try {
const allEntries = await this.find();
const result = await this.removeMany(allEntries.map(entry => entry.id));
return result;
} catch (error) {
throw error;
}
}
/**
* Convert DB entity to JSON object
*
* @param {any} entity
* @returns {Object}
* @memberof CosmosDbAdapter
*/
entityToObject(entity) {
return entity;
}
/**
* Create a filtered query
* Available filters in `params`:
* - query
*
* @param {Object} params
* @param {Boolean} isCounting
* @returns {Promise}
*/
async createCursor(params = {}, isCounting) {
const q = {
query: params.query || "SELECT * from c", // By default get everything
parameters: []
};
if (isCounting) {
q.query = "SELECT VALUE COUNT(1) FROM c"; // Count all entries
}
const response = await this.container.items.query(q).fetchAll();
// Response is an array with the count number
if (isCounting) return response.resources[0];
return response.resources;
}
/**
* Convert the `sort` param to a `sort` object to Sequelize queries.
*
* @param {String|Array<String>|Object} paramSort
* @returns {Object} Return with a sort object like `[["votes", "ASC"], ["title", "DESC"]]`
* @memberof CosmosDbAdapter
*/
transformSort(paramSort) {
throw new Error("Method `transformSort` Not Implemented");
}
/**
* For compatibility only.
* @param {Object} entity
* @param {String} idField
* @memberof CosmosDbAdapter
* @returns {Object} Entity
*/
beforeSaveTransformID(entity, idField) {
return entity;
}
/**
* For compatibility only.
* @param {Object} entity
* @param {String} idField
* @memberof CosmosDbAdapter
* @returns {Object} Entity
*/
afterRetrieveTransformID(entity, idField) {
return entity;
}
}
module.exports = CosmosDbAdapter;