nest-arango
Version:
ArangoDB driver module for NestJS with a built-in CLI tool for creating and running migration scripts
764 lines • 30.5 kB
JavaScript
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ArangoRepository = void 0;
const common_1 = require("@nestjs/common");
const arangojs_1 = require("arangojs");
const error_1 = require("arangojs/error");
const __1 = require("..");
const repository_types_1 = require("../interfaces/repository.types");
const event_metadata_storage_1 = require("../metadata/storages/event-metadata.storage");
const type_metadata_storage_1 = require("../metadata/storages/type-metadata.storage");
const listener_type_1 = require("../metadata/types/listener.type");
let ArangoRepository = class ArangoRepository {
arangoManager;
targetMetadata;
collection;
eventListeners;
get collectionName() {
return this.targetMetadata.collection;
}
constructor(arangoManager, target) {
this.arangoManager = arangoManager;
this.targetMetadata = type_metadata_storage_1.TypeMetadataStorage.getMetadata(target.constructor.name);
this.eventListeners = event_metadata_storage_1.EventListenerMetadataStorage.getMetadata(target.constructor.name);
this.collection = this.arangoManager.database.collection(this.collectionName);
}
getIdFor(_key) {
return `${this.collectionName}/${_key}`;
}
getKeyFrom(_id) {
return _id.split('/')[1];
}
async documentExists(key, documentExistsOptions = {}) {
if (documentExistsOptions.transaction) {
return await documentExistsOptions.transaction.step(() => this.collection.documentExists(key));
}
else {
return await this.collection.documentExists(key, documentExistsOptions);
}
}
async documentsExist(documents, documentsExistOptions = {}) {
const keys = documents.map((selector) => {
const curSelector = selector;
if (curSelector['_id'])
return curSelector._id.split('/')[1];
if (curSelector['_key'])
return curSelector._key;
const split = curSelector.split('/');
if (split.length > 1)
return split[1];
return curSelector;
});
const aqlQuery = `
FOR key IN
RETURN !IS_NULL(DOCUMENT(CONCAT('${this.collection.name}', '/', key)))
`;
const bindVars = { keys: keys };
if (documentsExistOptions.transaction) {
const cursor = await documentsExistOptions.transaction.step(() => this.arangoManager.query(aqlQuery, bindVars));
return await documentsExistOptions.transaction.step(() => cursor.all());
}
else {
const cursor = await this.arangoManager.query(aqlQuery, bindVars);
return await cursor.all();
}
}
async getDocumentCountBy(bindVars, findManyByOptions = {}) {
const filter = Object.entries(bindVars)
?.map(([k]) => `FILTER d.${k} == @${k}`)
.join(' ');
const aqlQuery = `WITH ${this.collection.name} FOR d IN ${this.collection.name} ${filter} COLLECT WITH COUNT INTO length RETURN length`;
if (findManyByOptions.transaction) {
const cursor = await findManyByOptions.transaction.step(() => this.arangoManager.query(aqlQuery, bindVars));
return ((await findManyByOptions.transaction.step(() => cursor.next())) ?? 0);
}
else {
const cursor = await this.arangoManager.query(aqlQuery, bindVars);
return (await cursor.next()) ?? 0;
}
}
async findOne(key, findOneOptions = {}) {
if (findOneOptions.transaction) {
return await findOneOptions.transaction.step(() => this.collection.document(key, { graceful: true }));
}
else {
return await this.collection.document(key, { graceful: true });
}
}
async findOneBy(bindVars, findOneByOptions = {}) {
const filter = Object.entries(bindVars)
?.map(([k]) => `FILTER d.${k} == @${k}`)
.join(' ');
const aqlQuery = `WITH ${this.collection.name} FOR d IN ${this.collection.name} ${filter} RETURN d`;
if (findOneByOptions.transaction) {
const cursor = await findOneByOptions.transaction.step(() => this.arangoManager.query(aqlQuery, bindVars));
return await findOneByOptions.transaction.step(() => cursor.next());
}
else {
const cursor = await this.arangoManager.query(aqlQuery, bindVars);
return await cursor.next();
}
}
async findMany(keys, findManyOptions = {}) {
if (findManyOptions.transaction) {
const docs = await findManyOptions.transaction.step(() => this.collection.documents(keys));
return this.findManyInternal(docs);
}
else {
return this.findManyInternal(await this.collection.documents(keys));
}
}
findManyInternal(docs) {
return docs.filter((doc) => {
if (doc.error) {
return doc.errorNum !== 1202;
}
else {
return true;
}
});
}
async findManyBy(bindVars, findManyByOptions = {}) {
const filter = Object.entries(bindVars)
?.map(([k]) => `FILTER d.${k} == @${k}`)
.join(' ');
let limit = '';
if (findManyByOptions.page !== undefined &&
findManyByOptions.pageSize !== undefined) {
limit = `LIMIT ${findManyByOptions.page * findManyByOptions.pageSize}, ${findManyByOptions.pageSize}`;
}
const sort = Object.entries(findManyByOptions.sort ?? {})
?.map(([k, v]) => `SORT d.${k} ${v}`)
.join(' ');
const aqlQuery = `WITH ${this.collection.name} FOR d IN ${this.collection.name} ${filter} ${limit} ${sort} RETURN d`;
if (findManyByOptions.transaction) {
const cursor = await findManyByOptions.transaction.step(() => this.arangoManager.query(aqlQuery, bindVars, {
fullCount: true,
}));
const results = await findManyByOptions.transaction.step(() => cursor.all());
return {
totalCount: cursor.extra.stats?.fullCount ?? results.length,
results: results,
};
}
else {
const cursor = await this.arangoManager.query(aqlQuery, bindVars, { fullCount: true });
const results = await cursor.all();
return {
totalCount: cursor.extra.stats?.fullCount ?? results.length,
results: results,
};
}
}
async findAll(findAllOptions = {}) {
let limit = '';
if (findAllOptions.page !== undefined &&
findAllOptions.pageSize !== undefined) {
limit = `LIMIT ${findAllOptions.page * findAllOptions.pageSize}, ${findAllOptions.pageSize}`;
}
const sort = Object.entries(findAllOptions.sort ?? {})
?.map(([k, v]) => `SORT d.${k} ${v}`)
.join(' ');
const aqlQuery = `WITH ${this.collection.name} FOR d IN ${this.collection.name} ${limit} ${sort} RETURN d`;
if (findAllOptions.transaction) {
const cursor = await findAllOptions.transaction.step(() => this.arangoManager.query(aqlQuery, {}, {
fullCount: true,
}));
const results = await findAllOptions.transaction.step(() => cursor.all());
return {
totalCount: cursor.extra.stats?.fullCount ?? results.length,
results: results,
};
}
else {
const cursor = await this.arangoManager.query(aqlQuery, {}, { fullCount: true });
const results = await cursor.all();
return {
totalCount: cursor.extra.stats?.fullCount ?? results.length,
results: results,
};
}
}
async save(document, saveOptions = {}) {
saveOptions = { emitEvents: true, ...saveOptions };
let context;
if (saveOptions?.emitEvents) {
context = {
database: this.arangoManager.database,
transaction: saveOptions?.transaction,
info: {
current: 0,
},
data: saveOptions?.data,
repository: this,
};
await this.eventListeners
?.get(listener_type_1.EventListenerType.BEFORE_SAVE)
?.call(document, context);
}
let newEntity;
if (saveOptions.transaction) {
newEntity = (await saveOptions.transaction.step(() => this.collection.save(document, {
returnNew: true,
}))).new;
}
else {
newEntity = (await this.collection.save(document, {
returnNew: true,
})).new;
}
if (saveOptions?.emitEvents) {
context.new = newEntity;
await this.eventListeners
?.get(listener_type_1.EventListenerType.AFTER_SAVE)
?.call(document, context);
}
return newEntity;
}
async saveAll(documents, saveAllOptions = {}) {
saveAllOptions = { emitEvents: true, ...saveAllOptions };
let context;
if (saveAllOptions?.emitEvents) {
context = {
database: this.arangoManager.database,
transaction: saveAllOptions?.transaction,
info: {
current: 0,
},
data: saveAllOptions?.data,
repository: this,
};
if (this.eventListeners?.get(listener_type_1.EventListenerType.BEFORE_SAVE)) {
await Promise.all(documents.map(async (document, index) => {
context.info.current = index;
await this.eventListeners
?.get(listener_type_1.EventListenerType.BEFORE_SAVE)
?.call(document, context);
}));
}
}
let result;
if (saveAllOptions.transaction) {
result = await saveAllOptions.transaction.step(() => {
return this.collection.saveAll(documents, {
returnNew: true,
});
});
}
else {
result = await this.collection.saveAll(documents, {
returnNew: true,
});
}
return Promise.all(result.map(async (document, index) => {
if (saveAllOptions?.emitEvents) {
context.info.current = index;
context.new = document.new;
await this.eventListeners
?.get(listener_type_1.EventListenerType.AFTER_SAVE)
?.call(document, context);
}
return document.new;
}));
}
async update(document, updateOptions = {}) {
updateOptions = {
returnOld: true,
emitEvents: true,
...updateOptions,
};
const { transaction, emitEvents, data, ...options } = updateOptions;
let context;
if (emitEvents) {
context = {
database: this.arangoManager.database,
transaction: transaction,
info: {
current: 0,
},
data: data,
repository: this,
};
await this.eventListeners
?.get(listener_type_1.EventListenerType.BEFORE_UPDATE)
?.call(document, context);
}
let result;
if (transaction) {
result = await transaction.step(() => this.collection.update(document, document, {
returnNew: true,
...updateOptions,
}));
}
else {
result = await this.collection.update(document, document, {
returnNew: true,
...updateOptions,
});
}
if (updateOptions?.emitEvents) {
context.new = result.new;
context.old = result.old;
await this.eventListeners
?.get(listener_type_1.EventListenerType.AFTER_UPDATE)
?.call(document, context);
}
return new repository_types_1.ArangoNewOldResult(result?.new, result?.old);
}
async updateWithAql(document, updateOptions = {}) {
updateOptions = {
returnOld: true,
emitEvents: true,
...updateOptions,
};
const { transaction, emitEvents, data, ...options } = updateOptions;
let context;
if (emitEvents) {
context = {
database: this.arangoManager.database,
transaction: transaction,
info: {
current: 0,
},
data: data,
repository: this,
};
await this.eventListeners
?.get(listener_type_1.EventListenerType.BEFORE_UPDATE)
?.call(document, context);
}
let result;
const _aql = (0, __1.aqlConcat)((0, __1.aqlPart) `WITH ${this.collection} `, `UPDATE ${JSON.stringify({
_key: document._key,
_id: document._id,
})} WITH `, (0, __1.documentAQLBuilder)(document), (0, __1.aqlPart) ` IN ${this.collection} `, `OPTIONS ${JSON.stringify(options)} RETURN { 'new': NEW, 'old': OLD }`);
const aqlQuery = (0, arangojs_1.aql)(_aql.templateStrings, ..._aql.args);
let cursor;
if (transaction) {
cursor = await transaction.step(() => this.arangoManager.query(aqlQuery));
result = await transaction.step(() => cursor.next());
}
else {
cursor = await this.arangoManager.query(aqlQuery);
result = await cursor.next();
}
if (!result) {
throw new error_1.ArangoError({
code: 404,
error: true,
errorMessage: 'document not found',
errorNum: 1202,
});
}
if (updateOptions?.emitEvents) {
context.new = result.new;
context.old = result.old;
await this.eventListeners
?.get(listener_type_1.EventListenerType.AFTER_UPDATE)
?.call(document, context);
}
return new repository_types_1.ArangoNewOldResult(result?.new, result?.old);
}
async updateAll(documents, updateAllOptions = {}) {
updateAllOptions = {
returnOld: true,
emitEvents: true,
...updateAllOptions,
};
let context;
if (updateAllOptions?.emitEvents) {
context = {
database: this.arangoManager.database,
transaction: updateAllOptions?.transaction,
info: {
current: 0,
},
data: updateAllOptions?.data,
repository: this,
};
if (this.eventListeners?.get(listener_type_1.EventListenerType.BEFORE_UPDATE)) {
await Promise.all(documents.map(async (document, index) => {
context.info.current = index;
await this.eventListeners
?.get(listener_type_1.EventListenerType.BEFORE_UPDATE)
?.call(document, context);
}));
}
}
let results;
if (updateAllOptions.transaction) {
results = await updateAllOptions.transaction.step(() => this.collection.updateAll(documents, {
returnNew: true,
...updateAllOptions,
}));
}
else {
results = await this.collection.updateAll(documents, {
returnNew: true,
...updateAllOptions,
});
}
return await Promise.all(results.map(async (document, index) => {
if (updateAllOptions?.emitEvents) {
context.new = document.new;
context.old = document.old;
context.info.current = index;
await this.eventListeners
?.get(listener_type_1.EventListenerType.AFTER_UPDATE)
?.call(document, context);
}
return new repository_types_1.ArangoNewOldResult(document.new, document.old);
}));
}
async replace(selector, document, replaceOptions = {}) {
replaceOptions = { returnOld: true, emitEvents: true, ...replaceOptions };
let context;
if (replaceOptions?.emitEvents) {
context = {
database: this.arangoManager.database,
transaction: replaceOptions?.transaction,
info: {
current: 0,
},
data: replaceOptions?.data,
repository: this,
};
await this.eventListeners
?.get(listener_type_1.EventListenerType.BEFORE_REPLACE)
?.call(document, context);
}
let result;
if (replaceOptions.transaction) {
result = await replaceOptions.transaction.step(() => this.collection.replace(selector, document, {
returnNew: true,
...replaceOptions,
}));
}
else {
result = await this.collection.replace(selector, document, {
returnNew: true,
...replaceOptions,
});
}
if (replaceOptions?.emitEvents) {
context.new = result.new;
context.old = result.old;
await this.eventListeners
?.get(listener_type_1.EventListenerType.AFTER_REPLACE)
?.call(document, context);
}
return new repository_types_1.ArangoNewOldResult(result.new, result.old);
}
async replaceAll(documents, replaceAllOptions = {}) {
replaceAllOptions = {
returnOld: true,
emitEvents: true,
...replaceAllOptions,
};
let context;
if (replaceAllOptions?.emitEvents) {
context = {
database: this.arangoManager.database,
transaction: replaceAllOptions?.transaction,
info: {
current: 0,
},
data: replaceAllOptions?.data,
repository: this,
};
if (this.eventListeners?.get(listener_type_1.EventListenerType.BEFORE_REPLACE)) {
await Promise.all(documents.map(async (document, index) => {
context.info.current = index;
await this.eventListeners
?.get(listener_type_1.EventListenerType.BEFORE_REPLACE)
?.call(document, context);
}));
}
}
let results;
if (replaceAllOptions.transaction) {
results = await replaceAllOptions.transaction.step(() => this.collection.replaceAll(documents, {
returnNew: true,
...replaceAllOptions,
}));
}
else {
results = await this.collection.replaceAll(documents, {
returnNew: true,
...replaceAllOptions,
});
}
return await Promise.all(results.map(async (document, index) => {
if (replaceAllOptions?.emitEvents) {
context.new = document.new;
context.old = document.old;
context.info.current = index;
await this.eventListeners
?.get(listener_type_1.EventListenerType.AFTER_REPLACE)
?.call(document, context);
}
return new repository_types_1.ArangoNewOldResult(document.new, document.old);
}));
}
async upsert(upsert, insert, update, upsertOptions = {}) {
upsertOptions = {
emitEvents: true,
...upsertOptions,
};
const { transaction, emitEvents, data, ...options } = upsertOptions;
let context;
if (emitEvents) {
context = {
database: this.arangoManager.database,
transaction: transaction,
info: {
current: 0,
},
data: data,
repository: this,
};
await this.eventListeners
?.get(listener_type_1.EventListenerType.BEFORE_SAVE)
?.call(insert, context);
await this.eventListeners
?.get(listener_type_1.EventListenerType.BEFORE_UPDATE)
?.call(update, context);
}
let result;
let cursor;
const _aql = (0, __1.aqlConcat)((0, __1.aqlPart) `WITH ${this.collection} `, (0, __1.aqlPart) `UPSERT ${upsert} `, (0, __1.aqlPart) `INSERT ${insert} `, (0, __1.aqlPart) `UPDATE ${update} `, (0, __1.aqlPart) `IN ${this.collection} `, `OPTIONS ${JSON.stringify(options)} `, `RETURN { 'new': NEW, 'old': OLD }`);
const aqlQuery = (0, arangojs_1.aql)(_aql.templateStrings, ..._aql.args);
if (transaction) {
cursor = await transaction.step(() => this.arangoManager.query(aqlQuery));
result = await transaction.step(() => cursor.next());
}
else {
cursor = await this.arangoManager.query(aqlQuery);
result = await cursor.next();
}
if (!result) {
throw new error_1.ArangoError({
code: 404,
error: true,
errorMessage: 'document not found',
errorNum: 1202,
});
}
if (emitEvents) {
context.new = result.new;
context.old = result.old;
if (result.old) {
await this.eventListeners
?.get(listener_type_1.EventListenerType.AFTER_UPDATE)
?.call(update, context);
}
else {
await this.eventListeners
?.get(listener_type_1.EventListenerType.AFTER_SAVE)
?.call(insert, context);
}
}
return new repository_types_1.ArangoNewOldResult(result.new, result.old);
}
async upsertWithAql(upsert, insert, update, upsertOptions = {}) {
upsertOptions = {
emitEvents: true,
...upsertOptions,
};
const { transaction, emitEvents, data, ...options } = upsertOptions;
let context;
if (emitEvents) {
context = {
database: this.arangoManager.database,
transaction: transaction,
info: {
current: 0,
},
data: data,
repository: this,
};
await this.eventListeners
?.get(listener_type_1.EventListenerType.BEFORE_SAVE)
?.call(insert, context);
await this.eventListeners
?.get(listener_type_1.EventListenerType.BEFORE_UPDATE)
?.call(update, context);
}
let result;
let cursor;
const _aql = (0, __1.aqlConcat)((0, __1.aqlPart) `WITH ${this.collection} `, (0, __1.aqlPart) `UPSERT ${upsert} `, (0, __1.aqlPart) `INSERT ${insert} `, `UPDATE `, (0, __1.documentAQLBuilder)(update), (0, __1.aqlPart) `IN ${this.collection} `, `OPTIONS ${JSON.stringify(options)} `, `RETURN { 'new': NEW, 'old': OLD }`);
const aqlQuery = (0, arangojs_1.aql)(_aql.templateStrings, ..._aql.args);
if (transaction) {
cursor = await transaction.step(() => this.arangoManager.query(aqlQuery));
result = await transaction.step(() => cursor.next());
}
else {
cursor = await this.arangoManager.query(aqlQuery);
result = await cursor.next();
}
if (!result) {
throw new error_1.ArangoError({
code: 404,
error: true,
errorMessage: 'document not found',
errorNum: 1202,
});
}
if (emitEvents) {
context.new = result.new;
context.old = result.old;
if (result.old) {
await this.eventListeners
?.get(listener_type_1.EventListenerType.AFTER_UPDATE)
?.call(update, context);
}
else {
await this.eventListeners
?.get(listener_type_1.EventListenerType.AFTER_SAVE)
?.call(insert, context);
}
}
return new repository_types_1.ArangoNewOldResult(result.new, result.old);
}
async remove(key, removeOptions = {}) {
removeOptions = {
emitEvents: true,
...removeOptions,
};
let context;
if (removeOptions?.emitEvents) {
context = {
database: this.arangoManager.database,
transaction: removeOptions?.transaction,
info: {
current: 0,
},
data: removeOptions?.data,
repository: this,
};
}
let result;
if (removeOptions.transaction) {
result = (await removeOptions.transaction.step(() => this.collection.remove(key, { returnOld: true }))).old;
}
else {
result = (await this.collection.remove(key, { returnOld: true })).old;
}
if (removeOptions?.emitEvents) {
context.old = result;
await this.eventListeners
?.get(listener_type_1.EventListenerType.AFTER_REMOVE)
?.call(result, context);
}
return result;
}
/**
* Removes documents containing matching fields with the provided bind variables.
*
* @remarks
* This method does NOT emit the event that invokes `@BeforeRemove` decorated methods.
*/
async removeBy(bindVars, removeByOptions = {}) {
removeByOptions = {
emitEvents: true,
...removeByOptions,
};
const entries = Object.entries(bindVars);
if (entries?.length < 1) {
throw new Error('No bindVars were specified!');
}
let context;
if (removeByOptions?.emitEvents) {
context = {
database: this.arangoManager.database,
transaction: removeByOptions?.transaction,
info: {
current: 0,
},
data: removeByOptions?.data,
repository: this,
};
}
let results;
const filter = entries
?.map(([k]) => `FILTER d.${k} == @${k}`)
.join(' ');
const aqlQuery = `WITH ${this.collection.name} FOR d IN ${this.collection.name} ${filter} REMOVE d IN ${this.collection.name} RETURN OLD`;
if (removeByOptions.transaction) {
const cursor = await removeByOptions.transaction.step(() => this.arangoManager.query(aqlQuery, bindVars));
results = await removeByOptions.transaction.step(() => cursor.all());
}
else {
const cursor = await this.arangoManager.query(aqlQuery, bindVars);
results = await cursor.all();
}
return await Promise.all(results.map(async (document, index) => {
if (removeByOptions?.emitEvents) {
context.old = document;
context.info.current = index;
await this.eventListeners
?.get(listener_type_1.EventListenerType.AFTER_REMOVE)
?.call(document, context);
}
return document;
}));
}
async removeAll(keys, removeAllOptions = {}) {
removeAllOptions = {
emitEvents: true,
...removeAllOptions,
};
let context;
if (removeAllOptions?.emitEvents) {
context = {
database: this.arangoManager.database,
transaction: removeAllOptions?.transaction,
info: {
current: 0,
},
data: removeAllOptions?.data,
repository: this,
};
}
let results;
if (removeAllOptions.transaction) {
results = (await removeAllOptions.transaction.step(() => this.collection.removeAll(keys, { returnOld: true }))).map((item) => {
return item.old;
});
}
else {
results = (await this.collection.removeAll(keys, { returnOld: true })).map((item) => {
return item.old;
});
}
return results.map((document, index) => {
if (removeAllOptions?.emitEvents) {
context.old = document;
context.info.current = index;
this.eventListeners
?.get(listener_type_1.EventListenerType.AFTER_REMOVE)
?.call(document, context);
}
return document;
});
}
async truncate(truncateOptions = {}) {
if (truncateOptions.transaction) {
await truncateOptions.transaction.step(() => this.collection.truncate());
}
else {
await this.collection.truncate();
}
}
};
exports.ArangoRepository = ArangoRepository;
exports.ArangoRepository = ArangoRepository = __decorate([
(0, common_1.Injectable)()
], ArangoRepository);
//# sourceMappingURL=arango.repository.js.map