@martinmilo/verve
Version:
TypeScript domain modeling library with field-level authorization, business rule validation, and context-aware access control
206 lines • 7.83 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Field = void 0;
const constants_1 = require("../../constants");
const utils_1 = require("./utils");
const generator_1 = require("../utils/generator");
const validator_1 = require("../utils/validator");
const errors_1 = require("../../errors");
const FieldType_1 = require("./FieldType");
class Field {
constructor(metadata, options = {}) {
this.metadata = metadata;
this.options = options;
}
static setGlobalValidators(validators) {
Field.globalValidatorsMap.set(this, validators);
}
static getGlobalValidators() {
var _a;
return (_a = Field.globalValidatorsMap.get(this)) !== null && _a !== void 0 ? _a : [];
}
static getGlobalGenerator() {
var _a;
return (_a = Field.globalGeneratorsMap.get(this)) !== null && _a !== void 0 ? _a : undefined;
}
static getEagerGenerator(options) {
if (options.generator && (0, generator_1.isEagerFieldGenerator)(options.generator)) {
return options.generator;
}
return undefined;
}
static getEagerValidators(options) {
var _a, _b;
return (_b = (_a = options.validators) === null || _a === void 0 ? void 0 : _a.filter(validator_1.isEagerFieldValidator)) !== null && _b !== void 0 ? _b : [];
}
get(model, key) {
const state = model[constants_1.MODEL_STATE]();
if (!this.isReadable(model, key)) {
throw new errors_1.VerveError(errors_1.ErrorCode.FIELD_NOT_READABLE, { field: key, model: this.metadata.model });
}
const value = state[key];
if (value === undefined) {
throw new errors_1.VerveError(errors_1.ErrorCode.FIELD_NOT_INITIALIZED, { field: key, model: this.metadata.model });
}
const errors = this.validate(model, key);
if (errors.isPresent()) {
throw new errors_1.VerveError(errors_1.ErrorCode.FIELD_VALIDATORS_FAILED, {
field: key,
model: this.metadata.model,
errors: errors.toErrorMessagesWithCode().join('\n'),
});
}
return value;
}
unsafeGet(model, key) {
const state = model[constants_1.MODEL_STATE]();
return state[key];
}
set(model, key, value) {
const state = model[constants_1.MODEL_STATE]();
FieldType_1.FieldType.validate(this, value);
if (!this.isWritable(model, key)) {
throw new errors_1.VerveError(errors_1.ErrorCode.FIELD_NOT_WRITABLE, { field: key, model: this.metadata.model });
}
if (value === undefined) {
this.unset(model, key);
return;
}
try {
model[constants_1.MODEL_CHANGE_LOG]({
field: key,
currentValue: value,
previousValue: state[key],
timestamp: new Date(),
});
state[key] = value;
}
catch (error) {
throw new errors_1.VerveError(errors_1.ErrorCode.FIELD_SET_ERROR, {
field: key,
model: this.metadata.model,
error: error instanceof Error ? error.message : String(error),
});
}
}
unset(model, key) {
const state = model[constants_1.MODEL_STATE]();
try {
model[constants_1.MODEL_CHANGE_LOG]({
field: key,
currentValue: undefined,
previousValue: state[key],
timestamp: new Date(),
});
delete state[key];
delete model[key];
}
catch (error) {
throw new errors_1.VerveError(errors_1.ErrorCode.FIELD_UNSET_ERROR, {
field: key,
model: this.metadata.model,
error: error instanceof Error ? error.message : String(error),
});
}
}
is(model, key, value) {
const currentValue = this.unsafeGet(model, key);
if (typeof currentValue !== typeof value) {
return false;
}
if (Array.isArray(currentValue) && Array.isArray(value)) {
return (0, utils_1.isArrayEqual)(currentValue, value);
}
if (typeof currentValue === 'object' && typeof value === 'object') {
return (0, utils_1.isObjectEqual)(currentValue, value);
}
return currentValue === value;
}
isEmpty(model, key) {
const value = this.unsafeGet(model, key);
if (value === undefined || value === null) {
return true;
}
if (typeof value === 'string') {
return value.trim().length === 0;
}
if (Array.isArray(value)) {
return value.length === 0;
}
if (typeof value === 'object') {
return Object.keys(value).length === 0;
}
return false;
}
isPresent(model, key) {
return !this.isEmpty(model, key);
}
isValid(model, key) {
const value = this.unsafeGet(model, key);
const validators = this.options.validators || [];
return validators.every((validator) => validator(value));
}
generate(model, key) {
const generator = this.options.generator;
if (!generator) {
throw new errors_1.VerveError(errors_1.ErrorCode.FIELD_NO_GENERATOR, { field: key, model: this.metadata.model });
}
const value = this.unsafeGet(model, key);
if (value) {
throw new errors_1.VerveError(errors_1.ErrorCode.FIELD_ALREADY_GENERATED, { field: key, model: this.metadata.model });
}
if (model.isExisting()) {
throw new errors_1.VerveError(errors_1.ErrorCode.FIELD_CANNOT_GENERATE_EXISTING, { field: key, model: this.metadata.model });
}
this.set(model, key, generator());
}
compute(model, key) {
const compute = this.options.compute;
if (!compute) {
throw new errors_1.VerveError(errors_1.ErrorCode.FIELD_NO_COMPUTE, { field: key, model: this.metadata.model });
}
const proxy = model[constants_1.MODEL_PROXY]();
return compute(proxy);
}
validate(model, key) {
const value = this.unsafeGet(model, key);
const validators = this.options.validators || [];
const errors = errors_1.VerveErrorList.new();
if (value === undefined) {
return errors;
}
for (const validator of validators) {
const result = validator(value, model);
if (result !== true) {
errors.add(errors_1.ErrorCode.FIELD_VALIDATOR_FAILED, {
field: key,
validator: validator.name || 'anonymous',
model: this.metadata.model,
});
}
}
return errors;
}
isReadable(model, key) {
var _a;
const value = this.unsafeGet(model, key);
const proxy = model[constants_1.MODEL_PROXY]();
if (typeof this.options.readable === 'function') {
return this.options.readable(model.getContext(), proxy, value);
}
return (_a = this.options.readable) !== null && _a !== void 0 ? _a : true;
}
isWritable(model, key) {
var _a;
const value = this.unsafeGet(model, key);
const proxy = model[constants_1.MODEL_PROXY]();
if (typeof this.options.writable === 'function') {
return this.options.writable(model.getContext(), proxy, value);
}
return (_a = this.options.writable) !== null && _a !== void 0 ? _a : true;
}
}
exports.Field = Field;
Field.globalValidatorsMap = new Map();
Field.globalGeneratorsMap = new Map();
//# sourceMappingURL=Field.js.map