cosmos-db-repositories
Version:
cosmos-db repositories
268 lines (267 loc) • 12.9 kB
JavaScript
;
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
};