@hiki9/rich-domain
Version:
Rich Domain is a library that provides a set of tools to help you build complex business logic in NodeJS using Domain Driven Design principles.
279 lines • 12.3 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.EntityMetaHistory = void 0;
const lodash_1 = __importDefault(require("lodash"));
const validator_1 = __importDefault(require("../../utils/validator"));
const errors_1 = require("../errors");
const history_snapshot_1 = require("./history-snapshot");
class EntityMetaHistory {
constructor(props, callbacks) {
this.onChange = [];
this.initialProps = lodash_1.default.cloneDeep(props);
this.snapshots = [];
this.callbacks = callbacks;
}
addSnapshot(data) {
const snapshot = new history_snapshot_1.Snapshot({
props: null, //lodash.cloneDeep(data.props),
trace: {
updatedAt: data.trace.updatedAt,
update: data.trace.update,
fieldKey: data.trace.fieldKey,
instanceKey: data.trace.instanceKey,
instanceId: data.trace.instanceId,
},
});
if (!data.trace.action) {
snapshot.trace.from = data.trace.from;
snapshot.trace.to = data.trace.to;
}
else {
snapshot.trace.action = data.trace.action;
}
if (typeof data.trace.position !== 'undefined') {
snapshot.trace.position = data.trace.position;
}
this.snapshots.push(snapshot);
if (this.callbacks?.onAddedSnapshot) {
this.callbacks.onAddedSnapshot(snapshot);
}
if (this.onChange.length) {
this.onChange.forEach((callback) => {
callback(snapshot);
});
}
}
deepWatch(rootEntity, onChangeCallback, childrenEntity) {
const entityTarget = childrenEntity ?? rootEntity;
const currentProps = entityTarget?.['props'];
Object.entries(currentProps).forEach(([key, _value]) => {
const value = _value;
if (Array.isArray(value)) {
value.forEach((currentValue) => {
if (currentValue?.isEntity && currentValue?.history) {
currentValue.history.onChange.push((snapshot) => {
const clonedSnapshot = lodash_1.default.cloneDeep(snapshot);
clonedSnapshot.fromDeepWatch = true;
clonedSnapshot.deepWatchPath = key;
onChangeCallback(rootEntity, clonedSnapshot);
});
entityTarget.history?.deepWatch(rootEntity, onChangeCallback, currentValue);
}
});
}
else if (value?.isEntity && value?.history) {
value.history.onChange.push((snapshot) => {
const clonedSnapshot = lodash_1.default.cloneDeep(snapshot);
clonedSnapshot.fromDeepWatch = true;
clonedSnapshot.deepWatchPath = key;
onChangeCallback(rootEntity, clonedSnapshot);
});
entityTarget.history?.deepWatch(rootEntity, onChangeCallback, value);
}
});
}
getSnapshotFromUpdatedKey(key) {
return this.snapshots.filter((snapshot) => {
if (snapshot.trace.update === key) {
return true;
}
const splitted = snapshot.trace.update.split('.');
return splitted.includes(key);
});
}
hasChange(key) {
return this.snapshots.some((snapshot) => {
if (snapshot.trace.update === key) {
return true;
}
const splitted = snapshot.trace.update.split('.');
return splitted.includes(key);
});
}
subs(entity, onChange, parents = [], result = []) {
const history = entity.history;
if (!(entity.isEntity)) {
throw new errors_1.DomainError('Entity is not an entity', entity);
}
if (!history) {
throw new errors_1.DomainError('History is not enabled for this entity', entity);
}
const currentResult = history.snapshots.map((snapshot) => onChange(snapshot, ...parents));
result.push(...currentResult);
Object.values(entity['props']).forEach((value) => {
if (Array.isArray(value)) {
value.forEach((possibleEntity) => {
if (possibleEntity?.isEntity) {
const partial = possibleEntity.history.subs(possibleEntity, onChange, [...parents, entity]);
result.push(...partial);
}
});
}
else if (value?.isEntity) {
const partial = value.history.subs(value, onChange, [...parents, entity]);
result.push(...partial);
}
});
return result;
}
subscribe(entity, subscribeProps, initialProps) {
if (!entity) {
throw new errors_1.DomainError('History is not enabled for this entity', entity);
}
if (typeof subscribeProps !== 'object') {
throw new errors_1.ApplicationLevelError('Subscribe props must be an object', subscribeProps);
}
const onChange = subscribeProps['onChange'];
if (typeof onChange === 'function') {
if (Array.isArray(entity)) {
const { toCreate, toDelete, toUpdate } = this.resolve(initialProps ?? [], entity);
const trace = entity.map(e => e.history.snapshots.map((snapshot) => snapshot.trace)).flat();
onChange({ entity, toCreate, toUpdate, toDelete }, trace);
}
else {
const trace = entity.history.snapshots.map((snapshot) => snapshot.trace);
onChange({ entity }, trace);
}
}
Object.entries(subscribeProps).forEach((entries) => {
const [key, value] = entries;
if (key === 'onChange')
return;
if (typeof value !== 'object')
return;
if (!Array.isArray(entity)) {
const nextEntity = entity['props'][key];
const nextInitialProps = entity.history.initialProps[key];
if (nextEntity?.isEntity) {
return this.subscribe(nextEntity, value, nextInitialProps);
}
if (nextEntity?.isValueObject) {
const trace = entity.history.snapshots.map((snapshot) => snapshot.trace);
const onChange = value?.['onChange'];
if (trace.length && typeof onChange === 'function') {
onChange({ entity: nextEntity }, trace);
}
return;
}
if (Array.isArray(nextEntity)) {
const everyPropIsEntity = nextEntity.every((prop) => prop?.isEntity);
if (everyPropIsEntity) {
return this.subscribe(nextEntity, value, nextInitialProps);
}
const everyPropIsValueObject = nextEntity.every((prop) => prop?.isValueObject);
if (everyPropIsValueObject) {
const { toCreate, toDelete, toUpdate } = this.resolve(nextInitialProps, nextEntity);
const trace = entity.history.snapshots.map((snapshot) => snapshot.trace);
const onChange = value?.['onChange'];
if (trace.length && typeof onChange === 'function') {
onChange({ entity: nextEntity, toCreate, toUpdate, toDelete }, trace);
}
}
}
}
else {
const nextEntity = entity.flatMap((entity) => entity['props'][key]);
const nextInitialProps = entity.flatMap((entity) => entity.history.initialProps[key]);
const isEntity = nextEntity.every((entity) => entity?.isEntity);
if (isEntity) {
return this.subscribe(nextEntity, value, nextInitialProps);
}
const isValueObject = nextEntity.every((entity) => entity?.isValueObject);
if (isValueObject) {
const { toCreate, toDelete, toUpdate } = this.resolve(nextInitialProps, nextEntity);
const trace = entity.map(e => e.history.snapshots.map((snapshot) => snapshot.trace)).flat();
const onChange = value?.['onChange'];
if (trace.length && typeof onChange === 'function') {
onChange({ entity: nextEntity, toCreate, toUpdate, toDelete }, trace);
return;
}
}
}
});
}
deepSearchSnapshots(entity, key) {
const propertyOfKey = entity?.['props']?.[key];
if (!propertyOfKey) {
return [];
}
if (Array.isArray(propertyOfKey)) {
const everyPropIsEntity = propertyOfKey.every((prop) => prop?.isEntity);
if (!everyPropIsEntity) {
return this.getSnapshotFromUpdatedKey(key);
}
return propertyOfKey.map((prop) => prop.history.snapshots).flat();
}
if (!propertyOfKey?.isEntity) {
return this.getSnapshotFromUpdatedKey(key);
}
const history = propertyOfKey.history;
if (!(history instanceof EntityMetaHistory)) {
throw new errors_1.DomainError('History is not enabled for this entity ->' + key?.toString());
}
return history.snapshots;
}
resolve(initialValues, currentValues) {
const { toCreate, toUpdate } = this.resolveEachPropsToUpsert(initialValues, currentValues);
return {
toCreate,
toUpdate,
toDelete: this.resolveEachPropsToDelete(initialValues, currentValues),
};
}
resolveEachPropsToDelete(initialValues, currentValues) {
return initialValues.reduce((acc, initialValue) => {
const found = currentValues.find((value) => {
if (validator_1.default.isValueObject(value)) {
return value.isEqual(initialValue);
}
else if (validator_1.default.isEntity(value) || validator_1.default.isAggregate(value)) {
return value.isEqual(initialValue)
|| value.id.isEqual(initialValue?.id);
}
else {
return value === initialValue;
}
});
if (!found) {
acc.push(initialValue);
}
return acc;
}, []);
}
resolveEachPropsToUpsert(initialValues, currentValues) {
return currentValues.reduce((acc, currentValue) => {
let shouldUpdate = false;
const found = initialValues.find((initialValue) => {
if (validator_1.default.isValueObject(initialValue)) {
return initialValue.isEqual(currentValue);
}
else if (validator_1.default.isEntity(initialValue) || validator_1.default.isAggregate(initialValue)) {
const sameID = initialValue.id.isEqual(currentValue.id);
const sameProps = initialValue.isEqual(currentValue);
if (sameID && !sameProps) {
shouldUpdate = true;
}
return sameID;
}
else {
return initialValue === currentValue;
}
});
if (found && shouldUpdate) {
acc.toUpdate.push(currentValue);
}
if (!found) {
acc.toCreate.push(currentValue);
}
return acc;
}, {
toCreate: [],
toUpdate: []
});
}
}
exports.EntityMetaHistory = EntityMetaHistory;
//# sourceMappingURL=history.js.map