UNPKG

cosmos-db-repositories

Version:
268 lines (267 loc) 12.9 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.mergeEntities = void 0; const cosmos_1 = require("@azure/cosmos"); const entity_utils_1 = __importDefault(require("./entity.utils")); const uuid_1 = require("uuid"); const time_utils_1 = require("./time.utils"); const audit_client_1 = __importStar(require("./audit.client")); const query_builder_1 = __importDefault(require("./query-builder")); let container; const containerInstance = () => __awaiter(void 0, void 0, void 0, function* () { if (!container) { const endpoint = process.env.AZURE_DATASOURCE_URL; const key = process.env.AZURE_DATASOURCE_KEY; const databaseId = process.env.AZURE_DATASOURCE_DATABASE_ID; const containerId = process.env.AZURE_DATASOURCE_CONTAINER_ID; container = new cosmos_1.CosmosClient({ endpoint, key }).database(databaseId).container(containerId); } return container; }); function chunk(array, size) { return Array.from({ length: Math.ceil(array.length / size) }, (_, i) => array.slice(i * size, i * size + size)); } const bulk = (operations, chunkSize = 100) => __awaiter(void 0, void 0, void 0, function* () { const partitionKey = process.env.AZURE_DATASOURCE_PARTITION_KEY; const failed = []; const token = (0, uuid_1.v4)(); const startDate = new Date(); let importDataCount = 0; let importDataFailedCount = 0; console.log('Start migrate (' + operations.length + ') users at ', startDate.toISOString()); // Group by partition key const groups = new Map(); for (const item of operations) { const pk = item.resourceBody[partitionKey]; item.partitionKey = pk; if (!groups.has(pk)) { groups.set(pk, []); } groups.get(pk).push(item); } const container = yield containerInstance(); for (const [partitionKey, groupItems] of groups) { const chunks = chunk(groupItems, chunkSize); let groupImportDataCount = 0; let groupImportDataFailedCount = 0; for (const chunkItems of chunks) { const response = yield container.items.bulk(chunkItems); let chunkImportDataCount = 0; let chunkImportDataFailedCount = 0; response.forEach((res, i) => { var _a, _b; if (res.statusCode >= 200 && res.statusCode < 300) { chunkImportDataCount++; groupImportDataCount++; importDataCount++; } else { chunkImportDataFailedCount++; groupImportDataFailedCount++; importDataFailedCount++; failed.push(chunkItems[i].resourceBody); console.error(`Failed upsert for item [${(_b = (_a = chunkItems[i]) === null || _a === void 0 ? void 0 : _a.resourceBody) === null || _b === void 0 ? void 0 : _b.id}] with statusCode: ${res.statusCode}`); } }); console.log(`Partition [${partitionKey}] Summary: ${groupImportDataCount} succeeded, ${groupImportDataFailedCount} failed of ${groupItems.length} total items`); } } const now = new Date(); const duration = (0, time_utils_1.timeDuration)(startDate.getTime(), now.getTime()); return { total: operations.length, imported: importDataCount, failed, duration, token, message: 'Data import [' + importDataCount + '] successfully completed and [' + importDataFailedCount + '] failed' }; }); const get = (id, partitionKey) => __awaiter(void 0, void 0, void 0, function* () { const container = yield containerInstance(); const { resource } = yield container.item(id, partitionKey).read(); return entity_utils_1.default.cleanUp(resource); }); const save = (item) => __awaiter(void 0, void 0, void 0, function* () { const container = yield containerInstance(); const { createdBy: userId, id, [process.env.AZURE_DATASOURCE_PARTITION_KEY]: partitionKey } = item; const { resource } = yield container.items.create(item); const entity = entity_utils_1.default.cleanUp(resource); yield audit_client_1.default.publishEvent(audit_client_1.AuditOperation.INSERT, userId, { rowId: id, partitionKey, name: process.env.AZURE_DATASOURCE_PARTITION_KEY }, entity); return entity; }); const update = (updatedData) => __awaiter(void 0, void 0, void 0, function* () { const { updatedBy, id, markAsDeleted, [process.env.AZURE_DATASOURCE_PARTITION_KEY]: partitionKey } = updatedData; if (markAsDeleted) { return markAsDeletedImpl(id, partitionKey, updatedBy); } const container = yield containerInstance(); const { resource: existingItem } = yield container.item(id, partitionKey).read(); // Merge the updates into the existing item const updatedItem = Object.assign(Object.assign({}, existingItem), updatedData); const { resource } = yield container.item(id, partitionKey).replace(updatedItem); const entity = entity_utils_1.default.cleanUp(resource); const before = entity_utils_1.default.cleanUp(existingItem); yield audit_client_1.default.publishEvent(audit_client_1.AuditOperation.UPDATE, updatedBy, { rowId: id, partitionKey, name: process.env.AZURE_DATASOURCE_PARTITION_KEY }, before, entity); return entity; }); const markAsDeletedImpl = (id, partitionKey, updatedBy) => __awaiter(void 0, void 0, void 0, function* () { const container = yield containerInstance(); const { resource: existingItem } = yield container.item(id, partitionKey).read(); const updatedData = { updatedBy, updatedDate: new Date(), markAsDeleted: true }; // Merge the updates into the existing item const updatedItem = Object.assign(Object.assign({}, existingItem), updatedData // Override with new data ); const { resource } = yield container.item(id, partitionKey).replace(updatedItem); const entity = entity_utils_1.default.cleanUp(resource); const before = entity_utils_1.default.cleanUp(existingItem); yield audit_client_1.default.publishEvent(audit_client_1.AuditOperation.SOFT_DELETE, updatedBy, { rowId: id, partitionKey, name: process.env.AZURE_DATASOURCE_PARTITION_KEY }, before, entity); return entity; }); const remove = (id, partitionKey, userId) => __awaiter(void 0, void 0, void 0, function* () { const container = yield containerInstance(); const { resource } = yield container.item(id, partitionKey).delete(); const entity = entity_utils_1.default.cleanUp(resource); yield audit_client_1.default.publishEvent(audit_client_1.AuditOperation.DELETE, userId, { rowId: id, partitionKey, name: process.env.AZURE_DATASOURCE_PARTITION_KEY }, entity); return entity; }); const query = (querySpec, pageRequest) => __awaiter(void 0, void 0, void 0, function* () { const pageable = Object.assign({ pageIndex: 1, rowsPerPage: 25 }, (pageRequest || {})); const { pageIndex, rowsPerPage } = pageable; const container = yield containerInstance(); const totalItems = yield fetchTotalCount(querySpec, container); // Total item count // Iterate through pages until the desired pageIndex is reached const { results, continuationToken } = yield fetchResults(querySpec, pageable, container); const data = { pageIndex, rowsPerPage, totalItems, continuationToken, totalPages: Math.ceil(totalItems / rowsPerPage), content: results.map(entity_utils_1.default.cleanUp), map: (converter) => data.content = data.content.map(converter) }; return data; }); const nativeQuery = (query, options) => __awaiter(void 0, void 0, void 0, function* () { const container = yield containerInstance(); const queryIterator = container.items.query(query, options); // Fetch results for the current page const { resources: results } = yield queryIterator.fetchAll(); return results || []; }); const fetchTotalCount = (querySpec, container) => __awaiter(void 0, void 0, void 0, function* () { const { query, parameters } = querySpec; const tokens = query.split("WHERE"); let totalQuery = 'SELECT VALUE COUNT(1) FROM c '; if (tokens.length > 1) { totalQuery += 'WHERE' + tokens[1]; } const totalQuerySpec = { query: totalQuery.trim(), parameters }; const { resources } = yield container.items.query(totalQuerySpec).fetchAll(); return resources ? resources[0] : 0; // Single value for the total count }); const fetchPaginatedResults = (querySpec, rowsPerPage, continuationToken = null, container) => __awaiter(void 0, void 0, void 0, function* () { // Calculate the offset based on pageIndex and rowsPerPage const options = { maxItemCount: rowsPerPage, continuationToken, // Token for the next page }; const queryIterator = container.items.query(querySpec, options); // Fetch results for the current page const { resources: results, continuationToken: nextToken } = yield queryIterator.fetchNext(); return { results: results || [], nextToken }; }); const fetchResults = (querySpec, pageable, container) => __awaiter(void 0, void 0, void 0, function* () { const { pageIndex, rowsPerPage } = pageable; let continuationToken = null; let currentPageIndex = 1; // 1 base page index let pageResults = []; // Iterate through pages until the desired pageIndex is reached do { const { results, nextToken } = yield fetchPaginatedResults(querySpec, rowsPerPage, continuationToken, container); continuationToken = nextToken; if (currentPageIndex === pageIndex) { pageResults = results; break; } currentPageIndex++; } while (continuationToken); return { results: pageResults, continuationToken }; }); const updateEntityField = (fieldName, entity, settings) => __awaiter(void 0, void 0, void 0, function* () { const querySpec = query_builder_1.default.build(settings); const { content } = yield query(querySpec, null); const operations = content.map((it) => ({ operationType: 'Upsert', resourceBody: Object.assign(Object.assign({ updatedBy: 'system' }, it), { [fieldName]: (0, exports.mergeEntities)(it[fieldName], entity), updatedDate: new Date(), token: entity.id }) })); const { message } = yield bulk(operations); return message; }); const mergeEntities = (entities, entity) => { const list = (entities || []); const entries = list.filter(it => it.id === entity.id); if (entries.length) { list.forEach(it => Object.assign(it, entity)); } else { list.push(entity); } return list; }; exports.mergeEntities = mergeEntities; exports.default = { get, query, save, update, remove, markAsDeleted: markAsDeletedImpl, bulk, nativeQuery, updateEntityField, containerInstance };