@warlock.js/cascade
Version:
ORM for managing databases
216 lines (215 loc) • 7.38 kB
JavaScript
import {areEqual}from'@mongez/reinforcements';import {getDatabaseConfig}from'../config.js';class ModelSync {
model;
columns;
embedMethod;
/**
* What do do when model is deleted
*/
whenDelete = getDatabaseConfig("model")?.cascadeOnDelete || "unset";
/**
* Embed on create
*/
embedOnCreate = "";
/**
* Sync mode
*/
syncMode = "single";
/**
* Query runner using `when` method
*/
queryRunner;
/**
* Define when should the synced model starts to update when any of the given columns is updated in the original model
*/
_updateWhenChange;
/**
* Constructor
*/
constructor(model, columns, embedMethod = "embedData") {
this.model = model;
this.columns = columns;
this.embedMethod = embedMethod;
//
}
/**
* Define when should the synced model starts to update when any of the given columns is updated in the original model
*/
updateWhenChange(columns) {
this._updateWhenChange = Array.isArray(columns) ? columns : [columns];
return this;
}
/**
* Set query runner
*/
where(queryRunner) {
this.queryRunner = queryRunner;
return this;
}
/**
* Unset on delete
*/
unsetOnDelete() {
this.whenDelete = "unset";
return this;
}
/**
* Ignore on delete
*/
ignoreOnDelete() {
this.whenDelete = "ignore";
return this;
}
/**
* Remove all matched documents on delete to be fully deleted
*/
removeOnDelete() {
this.whenDelete = "remove";
return this;
}
/**
* Embed on create to injected model one the original model is created
*/
embedOnCreateFrom(column) {
this.embedOnCreate = column;
return this;
}
/**
* Mark as many sync
*/
syncMany() {
this.syncMode = "many";
return this;
}
/**
* Start syncing the model
*/
async sync(model, saveMode, oldModel) {
if (saveMode === "update") {
return this.syncUpdate(model, oldModel);
}
if (!this.embedOnCreate)
return;
const columns = Array.isArray(this.columns) ? this.columns : [this.columns];
const syncedModel = await this.model.first({
id: model.get(this.embedOnCreate + ".id"),
});
if (!syncedModel)
return;
try {
const modelData = typeof model[this.embedMethod] !== "undefined"
? model[this.embedMethod]
: model.data;
for (const column of columns) {
if (this.syncMode === "single") {
syncedModel.set(column, modelData);
}
else {
syncedModel.associate(column, modelData);
}
}
await syncedModel.save();
}
catch (error) {
console.log("Error in Sync", error.message);
console.log(model);
throw error;
}
}
/**
* Sync update
*/
async syncUpdate(model, oldModel) {
if (this._updateWhenChange && oldModel) {
// now check if any of the columns has changed
// if all of them are the same, then we don't need to update
if (this._updateWhenChange.every(column => areEqual(model.get(column), oldModel.get(column)))) {
return;
}
}
const columns = Array.isArray(this.columns) ? this.columns : [this.columns];
const whereOptions = {};
for (const column of columns) {
whereOptions[column + ".id"] = model.id;
}
const query = this.model.aggregate().orWhere(whereOptions);
if (this.queryRunner) {
this.queryRunner(query);
}
const models = await query.get();
try {
const modelData = typeof model[this.embedMethod] !== "undefined"
? model[this.embedMethod]
: model.embeddedData;
for (const updatingModel of models) {
for (const column of columns) {
if (this.syncMode === "single") {
updatingModel.set(column, modelData);
}
else {
if (column.includes(".")) {
// if column includes dot, then it's a nested column
// so we need to get the top document key as it should be an array.
const [topKey, nestedKey] = column.split(".");
const documentsList = updatingModel.get(topKey) || [];
// as we're updating, so there should be at least one document
if (documentsList?.length === 0)
continue;
// now we need to find the document that has the same id as the model we're updating
const documentIndex = documentsList.findIndex((document) => document[nestedKey]?.id === model.get("id"));
// if document is not found, then we don't need to update
if (documentIndex === -1)
continue;
// now we need to update the document
documentsList[documentIndex][nestedKey] = modelData;
// and finally set the updated documents list
updatingModel.set(topKey, documentsList);
}
else {
// otherwise, it is a direct column update so we can just set it
updatingModel.reassociate(column, modelData);
}
}
}
// disable casting so we can save the data as it is
await updatingModel.save(undefined, {
cast: false,
});
}
}
catch (error) {
console.log("Error in Sync", error.message);
console.log(model);
throw error;
}
}
/**
* Sync model destruction
*/
async syncDestruction(model) {
const columns = Array.isArray(this.columns) ? this.columns : [this.columns];
const query = {};
for (const column of columns) {
query[column + ".id"] = model.get("id");
}
const models = await this.model
.aggregate()
.orWhere(query)
.get();
for (const currentModel of models) {
if (this.whenDelete === "unset") {
for (const column of columns) {
if (this.syncMode === "single") {
currentModel.unset(column);
}
else {
currentModel.disassociate(column, model);
}
}
await currentModel.save();
}
else if (this.whenDelete === "remove") {
await currentModel.destroy();
}
}
}
}export{ModelSync};//# sourceMappingURL=ModelSync.js.map