UNPKG

node-cosmos

Version:

A light weight azure cosmosdb client aiming at ease of use for creating REST API. Supports json filter, sort and offset/limit

266 lines 10 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CosmosDatabaseImpl = void 0; const Condition_1 = require("../../condition/Condition"); const CosmosDatabase_1 = require("../../CosmosDatabase"); const CosmosContainer_1 = require("../../CosmosContainer"); const assert_1 = require("../../../util/assert"); const RetryUtil_1 = require("../../../util/RetryUtil"); const _partition = "_partition"; // Partition KeyName const reservedFields = new Set(["_ts", "_partition", "_etag"]); /** * Remove unused cosmosdb system properties(e.g. _self / _rid / _attachments) * @param item */ const removeUnusedProps = (item) => { if (item) { Object.keys(item) .filter((k) => k.startsWith("_") && !reservedFields.has(k)) .forEach((k) => delete item[k]); } return item; }; /** * check if id is valid * @param id */ const checkValidId = (id) => { if (!id) { throw new Error("id cannot be empty"); } if (id.includes("\t") || id.includes("\n") || id.includes("\r")) { throw new Error("id cannot contain \t or \n or \r"); } }; /** * class represents a Cosmos Database */ class CosmosDatabaseImpl { constructor(client, database) { this.collectionMap = new Map(); this.client = client; this.database = database; } /** * Create a collection if not exists * @param coll */ async createCollection(coll) { const { database } = this; const partitionKey = "/" + _partition; const conf = { id: coll, partitionKey, defaultTtl: -1 }; const { container } = await database.containers.createIfNotExists(conf); return new CosmosContainer_1.CosmosContainer(coll, container); } /** * Delete a collection if exists * @param coll */ async deleteCollection(coll) { const { database } = this; await database.container(coll).delete(); } /** * * @param coll */ async getCollection(coll) { const { collectionMap } = this; let collection = collectionMap.get(coll); if (!collection) { collection = await this.createCollection(coll); collectionMap.set(coll, collection); } return collection; } /** * Create an item. * @param coll * @param data * @param partition */ async create(coll, data, partition = coll) { const container = await this.getCollection(coll); if (data.id) { // if id is specified explicitly, check if a valid one. checkValidId(data.id); } const _data = {}; Object.assign(_data, data); Object.assign(_data, { [_partition]: partition }); const containerInstance = container.container; const { resource } = await (0, RetryUtil_1.executeWithRetry)(() => containerInstance.items.create(_data)); (0, assert_1.assertIsDefined)(resource, `item, coll:${coll}, data:${JSON.stringify(data)}, partition:${partition}`); console.info(`created. coll:${coll}, resource:${resource.id}, partition:${partition}`); return removeUnusedProps(resource); } /** * Read an item. Throw DocumentClientException(404 NotFound) if object not exist * * @param coll * @param id * @param partition */ async read(coll, id, partition = coll) { const container = await this.getCollection(coll); const containerInstance = container.container; const item = containerInstance.item(id, partition); const itemResponse = await (0, RetryUtil_1.executeWithRetry)(() => item.read()); const { statusCode, resource } = itemResponse; if (statusCode === 404) { throw new CosmosDatabase_1.CosmosError(itemResponse); } (0, assert_1.assertIsDefined)(resource); return resource; } /** * Read an item. return defaultValue if item not exist * * @param coll * @param id * @param partition * @param defaultValue defaultValue if item not exist */ async readOrDefault(coll, id, partition, defaultValue) { const container = await this.getCollection(coll); const containerInstance = container.container; const item = containerInstance.item(id, partition); try { const itemResponse = await (0, RetryUtil_1.executeWithRetry)(() => item.read()); const { statusCode, resource } = itemResponse; if (statusCode >= 400) { throw new CosmosDatabase_1.CosmosError(itemResponse); } (0, assert_1.assertIsDefined)(resource); return resource; } catch (e) { if (typeof e === "object" && e !== null && "code" in e && e.code === 404) { return defaultValue; } else { throw e; } } } /** * Upsert an item. Insert will be performed if not exist. Do not support partial update. * @param coll * @param data * @param partition */ async upsert(coll, data, partition = coll) { (0, assert_1.assertIsDefined)(data.id, "data.id"); checkValidId(data.id); const container = await this.getCollection(coll); const containerInstance = container.container; const _data = {}; Object.assign(_data, data); Object.assign(_data, { [_partition]: partition }); const { resource } = await (0, RetryUtil_1.executeWithRetry)(() => containerInstance.items.upsert(_data)); (0, assert_1.assertIsDefined)(resource, `item, coll:${coll}, id:${data.id}, partition:${partition}`); console.info(`upserted. coll:${coll}, id:${data.id}, partition:${partition}`); return removeUnusedProps(resource); } /** * Update an item. Supports partial update. Error will be throw if not exist. * @param coll * @param data * @param partition */ async update(coll, data, partition = coll) { (0, assert_1.assertIsDefined)(data.id, "data.id"); checkValidId(data.id); const container = await this.getCollection(coll); const containerInstance = container.container; const item = containerInstance.item(data.id, partition); const { resource: toUpdate } = await item.read(); (0, assert_1.assertIsDefined)(toUpdate, `toUpdate, ${coll}, ${data.id}, ${partition}`); Object.assign(toUpdate, data); const { resource: updated } = await (0, RetryUtil_1.executeWithRetry)(() => item.replace(toUpdate)); (0, assert_1.assertIsDefined)(updated, `item, coll:${coll}, id:${data.id}, partition:${partition}`); console.info(`updated. coll:${coll}, id:${data.id}, partition:${partition}`); return removeUnusedProps(updated); } /** * Delete an item. Return {id} if exist. Otherwise return undefined. * * @param coll * @param id * @param partition */ async delete(coll, id, partition = coll) { const container = await this.getCollection(coll); const containerInstance = container.container; const item = containerInstance.item(id, partition); try { await (0, RetryUtil_1.executeWithRetry)(() => item.delete()); console.info(`deleted coll:${coll}, id:${id}, partition:${partition}`); return { id }; } catch (e) { if (typeof e === "object" && e !== null && "code" in e && e.code === 404) { return undefined; } else { throw e; } } } /** * find data by condition * * @param coll * @param condition * @param partition */ async find(coll, condition, partition) { const container = await this.getCollection(coll); const containerInstance = container.container; const partitionKey = partition; const options = { partitionKey }; const querySpec = (0, Condition_1.toQuerySpec)(condition); const iter = await (0, RetryUtil_1.executeWithRetry)(async () => containerInstance.items.query(querySpec, options)); const response = await iter.fetchAll(); const ret = response.resources || []; return ret.map((item) => removeUnusedProps(item)); } /** * find data by SQL * using SQL-like syntax * https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/cosmosdb/cosmos/README.md#query-the-database * @param coll * @param query * @param partition */ async findBySQL(coll, query, partition) { const container = await this.getCollection(coll); const containerInstance = container.container; const partitionKey = partition; const options = { partitionKey }; const iter = await (0, RetryUtil_1.executeWithRetry)(async () => containerInstance.items.query(query, options)); const response = await iter.fetchAll(); const ret = response.resources || []; return ret.map((item) => removeUnusedProps(item)); } /** * count data by condition * * @param coll * @param condition * @param partition */ async count(coll, condition, partition) { const container = await this.getCollection(coll); const containerInstance = container.container; const partitionKey = partition; const options = { partitionKey }; const querySpec = (0, Condition_1.toQuerySpec)(condition, true); const iter = await (0, RetryUtil_1.executeWithRetry)(async () => containerInstance.items.query(querySpec, options)); const res = await (0, RetryUtil_1.executeWithRetry)(async () => iter.fetchNext()); const [{ $1: total }] = res.resources; return total; } } exports.CosmosDatabaseImpl = CosmosDatabaseImpl; //# sourceMappingURL=CosmosDatabaseImpl.js.map