@clickup/ent-framework
Version:
A PostgreSQL graph-database-alike library with microsharding and row-level security
170 lines • 6.38 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Triggers = void 0;
exports.buildUpdateNewRow = buildUpdateNewRow;
const deepEqual_1 = require("../internal/deepEqual");
const misc_1 = require("../internal/misc");
class Triggers {
constructor(beforeInsert, beforeUpdate, beforeDelete, beforeMutation, afterInsert, afterUpdate, afterDelete, afterMutation) {
this.beforeInsert = beforeInsert;
this.beforeUpdate = beforeUpdate;
this.beforeDelete = beforeDelete;
this.beforeMutation = beforeMutation;
this.afterInsert = afterInsert;
this.afterUpdate = afterUpdate;
this.afterDelete = afterDelete;
this.afterMutation = afterMutation;
}
hasInsertTriggers() {
return (this.beforeInsert.length > 0 ||
this.beforeMutation.length > 0 ||
this.afterInsert.length > 0 ||
this.afterMutation.length > 0);
}
hasUpdateTriggers() {
return (this.beforeUpdate.length > 0 ||
this.beforeMutation.length > 0 ||
this.afterUpdate.length > 0 ||
this.afterMutation.length > 0);
}
async wrapInsert(func, vc, input) {
if (!this.hasInsertTriggers()) {
return func(input);
}
for (const triggerBeforeInsert of this.beforeInsert) {
// We clone the input to make different triggers calls independent: if the
// trigger e.g. stores input somewhere by reference, we don't want the
// next trigger to affect that place.
input = { ...input };
await triggerBeforeInsert(vc, { input });
}
for (const [_, triggerBeforeMutation] of this.beforeMutation) {
input = { ...input };
await triggerBeforeMutation(vc, {
op: "INSERT",
newOrOldRow: input,
input,
});
}
const output = await func(input);
if (!output) {
// Insert failed (unique key constraint failed); don't run after-triggers.
return output;
}
for (const triggerAfterInsert of this.afterInsert) {
await triggerAfterInsert(vc, { input });
}
for (const [_, triggerAfterMutation] of this.afterMutation) {
await triggerAfterMutation(vc, { op: "INSERT", newOrOldRow: input });
}
return output;
}
async wrapUpdate(func, vc, oldRow, input) {
if (!this.hasUpdateTriggers()) {
return func(input);
}
let newRow = buildUpdateNewRow(oldRow, input);
for (const [depsBuilder, triggerBeforeUpdate] of this.beforeUpdate) {
if (await depsBuilderApproves(depsBuilder, vc, oldRow, newRow)) {
await triggerBeforeUpdate(vc, { newRow, oldRow, input });
// Each call to triggerBefore() may potentially change the input, so we
// need to rebuild newRow each time to feed it to the next call of
// triggerBefore() and to the rest of triggerAfter.
newRow = buildUpdateNewRow(oldRow, input);
}
}
for (const [depsBuilder, triggerBeforeMutation] of this.beforeMutation) {
if (await depsBuilderApproves(depsBuilder, vc, oldRow, newRow)) {
await triggerBeforeMutation(vc, {
op: "UPDATE",
newOrOldRow: newRow,
input,
});
newRow = buildUpdateNewRow(oldRow, input);
}
}
const output = await func(input);
if (!output) {
// Update failed (no row with such ID); don't call after-triggers.
return output;
}
for (const [depsBuilder, triggerAfterUpdate] of this.afterUpdate) {
if (await depsBuilderApproves(depsBuilder, vc, oldRow, newRow)) {
await triggerAfterUpdate(vc, {
newRow,
oldRow: oldRow,
});
}
}
for (const [depsBuilder, triggerAfterMutation] of this.afterMutation) {
if (await depsBuilderApproves(depsBuilder, vc, oldRow, newRow)) {
await triggerAfterMutation(vc, {
op: "UPDATE",
newOrOldRow: newRow,
});
}
}
return output;
}
async wrapDelete(func, vc, oldRow) {
for (const triggerBeforeDelete of this.beforeDelete) {
await triggerBeforeDelete(vc, { oldRow });
}
for (const [_, triggerBeforeMutation] of this.beforeMutation) {
await triggerBeforeMutation(vc, {
op: "DELETE",
newOrOldRow: oldRow,
input: oldRow,
});
}
const output = await func();
if (!output) {
// Delete failed (no row with such ID); don't call after-triggers.
return output;
}
for (const triggerAfterDelete of this.afterDelete) {
await triggerAfterDelete(vc, { oldRow });
}
for (const [_, triggerAfterMutation] of this.afterMutation) {
await triggerAfterMutation(vc, {
op: "DELETE",
newOrOldRow: oldRow,
});
}
return output;
}
}
exports.Triggers = Triggers;
/**
* Simulates an update for a row, as if it's applied to the Ent.
* @ignore
*/
function buildUpdateNewRow(oldRow, input) {
const newRow = { ...oldRow };
for (const k of Object.getOwnPropertyNames(input)) {
if (input[k] !== undefined) {
newRow[k] = input[k];
}
}
for (const k of Object.getOwnPropertySymbols(input)) {
if (input[k] !== undefined) {
newRow[k] = input[k];
}
}
return newRow;
}
/**
* Returns true if depsBuilder approves running the trigger (i.e. the deps are
* changed, or no depsBuilder is presented at all).
*/
async function depsBuilderApproves(depsBuilder, vc, oldRow, newRow) {
if (!depsBuilder) {
return true;
}
const [depsOld, depsNew] = await (0, misc_1.join)([
depsBuilder(vc, oldRow),
depsBuilder(vc, newRow),
]);
return !(0, deepEqual_1.deepEqual)(depsOld, depsNew);
}
//# sourceMappingURL=Triggers.js.map