@grouparoo/core
Version:
The Grouparoo Core
604 lines (603 loc) • 24.5 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
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;
};
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var _a;
var Property_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Property = exports.PropertyTypes = exports.propertyJSToSQLType = void 0;
const actionhero_1 = require("actionhero");
const sequelize_1 = __importStar(require("sequelize"));
const sequelize_typescript_1 = require("sequelize-typescript");
const apiData_1 = require("../modules/apiData");
const cls_1 = require("../modules/cls");
const configWriter_1 = require("../modules/configWriter");
const filterHelper_1 = require("../modules/filterHelper");
const lockableHelper_1 = require("../modules/lockableHelper");
const property_1 = require("../modules/ops/property");
const optionHelper_1 = require("../modules/optionHelper");
const plugin_1 = require("../modules/plugin");
const stateMachine_1 = require("../modules/stateMachine");
const topLevelGroupRules_1 = require("../modules/topLevelGroupRules");
const Filter_1 = require("./Filter");
const Group_1 = require("./Group");
const GroupRule_1 = require("./GroupRule");
const Mapping_1 = require("./Mapping");
const Option_1 = require("./Option");
const GrouparooRecord_1 = require("./GrouparooRecord");
const RecordProperty_1 = require("./RecordProperty");
const Run_1 = require("./Run");
const Source_1 = require("./Source");
const runMode_1 = require("../modules/runMode");
const commonModel_1 = require("../classes/commonModel");
const propertiesCache_1 = require("../modules/caches/propertiesCache");
const sourcesCache_1 = require("../modules/caches/sourcesCache");
const jsMap = {
boolean: ((_a = actionhero_1.config === null || actionhero_1.config === void 0 ? void 0 : actionhero_1.config.sequelize) === null || _a === void 0 ? void 0 : _a.dialect) === "sqlite" ? "text" : "boolean",
date: "bigint",
email: "text",
float: "float",
integer: "bigint",
phoneNumber: "text",
string: "text",
url: "text",
};
const propertyJSToSQLType = (jsType) => jsMap[jsType];
exports.propertyJSToSQLType = propertyJSToSQLType;
exports.PropertyTypes = [
"boolean",
"date",
"email",
"float",
"integer",
"phoneNumber",
"string",
"url",
];
const STATES = ["draft", "ready", "deleted"];
const STATE_TRANSITIONS = [
{
from: "draft",
to: "ready",
checks: [(instance) => instance.validateOptions()],
},
{ from: "draft", to: "deleted", checks: [] },
{ from: "ready", to: "deleted", checks: [] },
{
from: "deleted",
to: "ready",
checks: [(instance) => instance.validateOptions()],
},
];
let Property = Property_1 = class Property extends commonModel_1.CommonModel {
idPrefix() {
return "prp";
}
async parameterizedQueryFromRecord(q, record) {
return plugin_1.plugin.replaceTemplateRecordVariables(q, record);
}
async test(options) {
const record = await GrouparooRecord_1.GrouparooRecord.findOne({
order: [["id", "asc"]],
});
if (record) {
const source = await Source_1.Source.findById(this.sourceId);
return source.importRecordProperty(record, this, options);
}
}
async getOptions(sourceFromEnvironment = true) {
const options = await optionHelper_1.OptionHelper.getOptions(this, sourceFromEnvironment);
const source = await sourcesCache_1.SourcesCache.findOneWithCache(this.sourceId);
for (const i in options) {
options[i] =
await plugin_1.plugin.replaceTemplateRecordPropertyIdsWithRecordPropertyKeys(options[i].toString(), source.modelId);
}
return options;
}
async setOptions(options, test = true, externallyValidate = true) {
if (test)
await this.test(options);
const source = await sourcesCache_1.SourcesCache.findOneWithCache(this.sourceId);
for (const i in options) {
options[i] =
await plugin_1.plugin.replaceTemplateRecordPropertyKeysWithRecordPropertyId(options[i].toString(), source.modelId);
}
return optionHelper_1.OptionHelper.setOptions(this, options, externallyValidate);
}
async afterSetOptions(hasChanges) {
if (hasChanges) {
await Property_1.invalidateCache();
await property_1.PropertyOps.enqueueRuns(this);
}
}
async validateOptions(options, externallyValidate = true) {
if (!options)
options = await this.getOptions(true);
if (!externallyValidate)
return;
const pluginOptions = await this.pluginOptions(options);
const optionsSpec = pluginOptions.map((opt) => {
var _a;
return ({
...opt,
options: (_a = opt.options) === null || _a === void 0 ? void 0 : _a.map((o) => o.key),
});
});
await optionHelper_1.OptionHelper.validateOptions(this, options, optionsSpec);
}
async getPlugin() {
return optionHelper_1.OptionHelper.getPlugin(this);
}
async getFilters() {
return filterHelper_1.FilterHelper.getFilters(this);
}
async setFilters(filters, externallyValidate = true) {
return filterHelper_1.FilterHelper.setFilters(this, filters, externallyValidate);
}
async afterSetFilters(hasChanges) {
if (hasChanges) {
await Property_1.invalidateCache();
return property_1.PropertyOps.enqueueRuns(this);
}
}
async pluginOptions(propertyOptions) {
return property_1.PropertyOps.pluginOptions(this, propertyOptions);
}
async validateFilters(filters) {
return filterHelper_1.FilterHelper.validateFilters(this, filters);
}
async apiData() {
const options = await this.getOptions();
const filters = await this.getFilters();
return {
id: this.id,
sourceId: this.sourceId,
key: this.key,
type: this.type,
state: this.state,
unique: this.unique,
isPrimaryKey: this.isPrimaryKey,
locked: this.locked,
options,
filters,
isArray: this.isArray,
createdAt: apiData_1.APIData.formatDate(this.createdAt),
updatedAt: apiData_1.APIData.formatDate(this.updatedAt),
recordProperties: this.RecordProperties
? await Promise.all(this.RecordProperties.map((rp) => rp.apiData()))
: undefined,
};
}
getConfigId() {
return this.idIsDefault() ? configWriter_1.ConfigWriter.generateId(this.key) : this.id;
}
async getConfigObject() {
var _a;
const { key, type, unique, isArray } = this;
this.source = await this.$get("source");
const sourceId = (_a = this.source) === null || _a === void 0 ? void 0 : _a.getConfigId();
const options = await this.getOptions(false);
const filters = await this.getFilters();
if (!key || !sourceId)
return;
return {
class: "Property",
id: this.getConfigId(),
type,
key,
sourceId,
unique,
isArray,
options,
filters,
};
}
// --- Class Methods --- //
static async ensureUniquePrimaryKey(instance) {
if (instance.isPrimaryKey && !instance.unique) {
throw new Error(`Property "${instance.key}" must be unique because it‘s the model‘s Primary Key.`);
}
}
static async ensureOptions(instance) {
const source = await Source_1.Source.findById(instance.sourceId);
await source.validateOptions(null);
}
static async updateState(instance) {
await stateMachine_1.StateMachine.transition(instance, STATE_TRANSITIONS);
}
static async ensureNonArrayAndUnique(instance) {
if (instance.isArray && instance.unique) {
throw new Error("unique record properties cannot be arrays");
}
}
static async ensureUniqueProperties(instance) {
if (instance.changed("unique") && instance.unique) {
const valueCounts = await RecordProperty_1.RecordProperty.findAll({
attributes: [
"rawValue",
[sequelize_1.default.fn("COUNT", sequelize_1.default.col("rawValue")), "count"],
],
group: ["rawValue"],
where: { propertyId: instance.id },
having: sequelize_1.default.where(sequelize_1.default.fn("COUNT", sequelize_1.default.col("rawValue")), { [sequelize_1.Op.gt]: 1 }),
limit: 1,
raw: true,
});
if (valueCounts.length > 0) {
throw new Error(
//@ts-ignore
`cannot make this property unique as there are ${valueCounts[0]["count"]} records with the value \'${valueCounts[0]["rawValue"]}\'`);
}
}
}
static async ensureSourceReady(instance) {
const source = await Source_1.Source.findById(instance.sourceId);
if (source.state !== "ready") {
// allow the bootstrap for this source (first one and unique)
if (!instance.unique) {
throw new Error("source is not ready and property not unique");
}
const otherProperties = await Property_1.scope(null).count({
where: {
sourceId: instance.sourceId,
id: { [sequelize_1.Op.ne]: instance.id },
},
});
if (otherProperties > 0) {
// already has another property
throw new Error("source is not ready and property not first");
}
}
}
static async validateQuery(instance) {
await instance.test();
}
static async validateReservedKeys(instance) {
const reservedKeys = ["_meta"].concat(topLevelGroupRules_1.TopLevelGroupRules.map((tlgr) => tlgr.key));
if (reservedKeys.includes(instance.key)) {
throw new Error(`${instance.key} is a reserved key and cannot be used as a property`);
}
}
static async noUniqueOrArrayThroughNonUniqueMapping(instance) {
if (instance.state === "draft")
return;
const source = await Source_1.Source.findById(instance.sourceId);
if (source.state !== "ready")
return; // we are bootstrapping
const sourceMapping = await source.getMapping();
if (Object.keys(sourceMapping).length === 0)
return; // Query source
const mappedPropertyKey = Object.values(sourceMapping)[0];
const mappedProperty = await Property_1.findOne({
where: { key: mappedPropertyKey },
});
if (mappedProperty.unique)
return;
if (instance.unique)
throw new Error(`Unique Property ${instance.key} (${instance.id}) cannot be mapped through a non-unique Property - ${mappedProperty.key} (${mappedProperty.id})`);
}
static async noUpdateIfLocked(instance) {
await lockableHelper_1.LockableHelper.beforeSave(instance, ["state", "isPrimaryKey"]);
}
static async runPropertyWithNoOptionsOrFiltersWhenReady(instance) {
const changedState = instance.changed("state");
if (changedState && instance.state === "ready") {
const options = await instance.getOptions();
const filters = await instance.getFilters();
if (Object.keys(options).length === 0 &&
Object.keys(filters).length === 0) {
await property_1.PropertyOps.enqueueRuns(instance);
}
}
}
static async updateRecordPropertyUniqueness(instance) {
if (!instance.isNewRecord && instance.changed("unique")) {
cls_1.CLS.enqueueTask("property:updateRecordProperties", {
propertyId: instance.id,
});
}
}
static async ensureNotInUse(instance, excludeMappingOwnerIds) {
const groupRule = await GroupRule_1.GroupRule.findOne({
where: { propertyId: instance.id },
});
if (groupRule) {
const group = await Group_1.Group.findById(groupRule.groupId);
throw new Error(`cannot delete property "${instance.key}", group ${group.name} (${group.id}) is based on it`);
}
const mappingWhere = { propertyId: instance.id };
if (excludeMappingOwnerIds) {
mappingWhere.ownerId = { [sequelize_1.Op.notIn]: excludeMappingOwnerIds };
}
const mapping = await Mapping_1.Mapping.findOne({
where: mappingWhere,
});
if (mapping) {
throw new Error(`cannot delete property "${instance.key}" as ${mapping.ownerId} is using it in a mapping`);
}
}
static async updateRecordsOnTypeChange(instance) {
if (instance.changed("type") && instance.state === "ready") {
await property_1.PropertyOps.enqueueRuns(instance);
}
}
static async updateSampleRecords(instance) {
if ((0, runMode_1.getGrouparooRunMode)() !== "cli:config")
return;
if (instance.state !== "ready")
return;
const source = await instance.$get("source");
if (!source)
return;
const records = await GrouparooRecord_1.GrouparooRecord.findAll({
where: { modelId: source.modelId },
});
for (const record of records)
await record.buildNullProperties();
}
static async noDestroyIfLocked(instance) {
await lockableHelper_1.LockableHelper.beforeDestroy(instance);
}
static async destroyOptions(instance) {
await Option_1.Option.destroy({
where: { ownerId: instance.id, ownerType: "property" },
});
}
static async stopRuns(instance) {
const runs = await Run_1.Run.findAll({
where: { creatorId: instance.id, state: "running" },
});
for (const i in runs) {
await runs[i].update({ state: "stopped" });
}
}
static async destroyFilters(instance) {
await Filter_1.Filter.destroy({
where: { ownerId: instance.id, ownerType: "property" },
});
}
static async destroyRecordProperties(instance) {
await RecordProperty_1.RecordProperty.destroy({
where: { propertyId: instance.id },
});
}
static async invalidateCache() {
propertiesCache_1.PropertiesCache.invalidate();
await cls_1.CLS.afterCommit(async () => await actionhero_1.redis.doCluster("api.rpc.property.invalidateCache"));
}
};
__decorate([
(0, sequelize_typescript_1.Length)({ min: 0, max: 191 }),
(0, sequelize_typescript_1.Default)(""),
sequelize_typescript_1.Column,
__metadata("design:type", String)
], Property.prototype, "key", void 0);
__decorate([
(0, sequelize_typescript_1.AllowNull)(false),
(0, sequelize_typescript_1.Is)("properType", (value) => {
if (!exports.PropertyTypes.includes(value)) {
throw new Error(`${value} is not an allowed type`);
}
}),
(0, sequelize_typescript_1.Column)(sequelize_typescript_1.DataType.ENUM(...exports.PropertyTypes)),
__metadata("design:type", Object)
], Property.prototype, "type", void 0);
__decorate([
(0, sequelize_typescript_1.AllowNull)(false),
(0, sequelize_typescript_1.Default)(false),
sequelize_typescript_1.Column,
__metadata("design:type", Boolean)
], Property.prototype, "unique", void 0);
__decorate([
(0, sequelize_typescript_1.AllowNull)(false),
(0, sequelize_typescript_1.ForeignKey)(() => Source_1.Source),
sequelize_typescript_1.Column,
__metadata("design:type", String)
], Property.prototype, "sourceId", void 0);
__decorate([
(0, sequelize_typescript_1.AllowNull)(false),
(0, sequelize_typescript_1.Default)("draft"),
(0, sequelize_typescript_1.Column)(sequelize_typescript_1.DataType.ENUM(...STATES)),
__metadata("design:type", Object)
], Property.prototype, "state", void 0);
__decorate([
sequelize_typescript_1.Column,
__metadata("design:type", String)
], Property.prototype, "locked", void 0);
__decorate([
(0, sequelize_typescript_1.AllowNull)(false),
(0, sequelize_typescript_1.Default)(false),
sequelize_typescript_1.Column,
__metadata("design:type", Boolean)
], Property.prototype, "isPrimaryKey", void 0);
__decorate([
(0, sequelize_typescript_1.AllowNull)(false),
(0, sequelize_typescript_1.Default)(false),
sequelize_typescript_1.Column,
__metadata("design:type", Boolean)
], Property.prototype, "isArray", void 0);
__decorate([
(0, sequelize_typescript_1.BelongsTo)(() => Source_1.Source),
__metadata("design:type", Source_1.Source)
], Property.prototype, "source", void 0);
__decorate([
(0, sequelize_typescript_1.HasMany)(() => Option_1.Option, {
foreignKey: "ownerId",
scope: { ownerType: "property" },
}),
__metadata("design:type", Array)
], Property.prototype, "__options", void 0);
__decorate([
(0, sequelize_typescript_1.HasMany)(() => Filter_1.Filter, {
foreignKey: "ownerId",
scope: { ownerType: "property" },
}),
__metadata("design:type", Array)
], Property.prototype, "filters", void 0);
__decorate([
(0, sequelize_typescript_1.HasMany)(() => RecordProperty_1.RecordProperty),
__metadata("design:type", Array)
], Property.prototype, "RecordProperties", void 0);
__decorate([
sequelize_typescript_1.BeforeSave,
__metadata("design:type", Function),
__metadata("design:paramtypes", [Property]),
__metadata("design:returntype", Promise)
], Property, "ensureUniquePrimaryKey", null);
__decorate([
sequelize_typescript_1.BeforeSave,
__metadata("design:type", Function),
__metadata("design:paramtypes", [Property]),
__metadata("design:returntype", Promise)
], Property, "ensureOptions", null);
__decorate([
sequelize_typescript_1.BeforeSave,
__metadata("design:type", Function),
__metadata("design:paramtypes", [Property]),
__metadata("design:returntype", Promise)
], Property, "updateState", null);
__decorate([
sequelize_typescript_1.BeforeSave,
__metadata("design:type", Function),
__metadata("design:paramtypes", [Property]),
__metadata("design:returntype", Promise)
], Property, "ensureNonArrayAndUnique", null);
__decorate([
sequelize_typescript_1.BeforeSave,
__metadata("design:type", Function),
__metadata("design:paramtypes", [Property]),
__metadata("design:returntype", Promise)
], Property, "ensureUniqueProperties", null);
__decorate([
sequelize_typescript_1.BeforeSave,
__metadata("design:type", Function),
__metadata("design:paramtypes", [Property]),
__metadata("design:returntype", Promise)
], Property, "ensureSourceReady", null);
__decorate([
sequelize_typescript_1.BeforeSave,
__metadata("design:type", Function),
__metadata("design:paramtypes", [Property]),
__metadata("design:returntype", Promise)
], Property, "validateQuery", null);
__decorate([
sequelize_typescript_1.BeforeSave,
__metadata("design:type", Function),
__metadata("design:paramtypes", [Property]),
__metadata("design:returntype", Promise)
], Property, "validateReservedKeys", null);
__decorate([
sequelize_typescript_1.BeforeSave,
__metadata("design:type", Function),
__metadata("design:paramtypes", [Property]),
__metadata("design:returntype", Promise)
], Property, "noUniqueOrArrayThroughNonUniqueMapping", null);
__decorate([
sequelize_typescript_1.BeforeSave,
__metadata("design:type", Function),
__metadata("design:paramtypes", [Property]),
__metadata("design:returntype", Promise)
], Property, "noUpdateIfLocked", null);
__decorate([
sequelize_typescript_1.BeforeSave,
__metadata("design:type", Function),
__metadata("design:paramtypes", [Property]),
__metadata("design:returntype", Promise)
], Property, "runPropertyWithNoOptionsOrFiltersWhenReady", null);
__decorate([
sequelize_typescript_1.BeforeSave,
__metadata("design:type", Function),
__metadata("design:paramtypes", [Property]),
__metadata("design:returntype", Promise)
], Property, "updateRecordPropertyUniqueness", null);
__decorate([
sequelize_typescript_1.BeforeDestroy,
__metadata("design:type", Function),
__metadata("design:paramtypes", [Property, Array]),
__metadata("design:returntype", Promise)
], Property, "ensureNotInUse", null);
__decorate([
sequelize_typescript_1.AfterSave,
__metadata("design:type", Function),
__metadata("design:paramtypes", [Property]),
__metadata("design:returntype", Promise)
], Property, "updateRecordsOnTypeChange", null);
__decorate([
sequelize_typescript_1.AfterSave,
__metadata("design:type", Function),
__metadata("design:paramtypes", [Property]),
__metadata("design:returntype", Promise)
], Property, "updateSampleRecords", null);
__decorate([
sequelize_typescript_1.BeforeDestroy,
__metadata("design:type", Function),
__metadata("design:paramtypes", [Property]),
__metadata("design:returntype", Promise)
], Property, "noDestroyIfLocked", null);
__decorate([
sequelize_typescript_1.AfterDestroy,
__metadata("design:type", Function),
__metadata("design:paramtypes", [Property]),
__metadata("design:returntype", Promise)
], Property, "destroyOptions", null);
__decorate([
sequelize_typescript_1.AfterDestroy,
__metadata("design:type", Function),
__metadata("design:paramtypes", [Property]),
__metadata("design:returntype", Promise)
], Property, "stopRuns", null);
__decorate([
sequelize_typescript_1.AfterDestroy,
__metadata("design:type", Function),
__metadata("design:paramtypes", [Property]),
__metadata("design:returntype", Promise)
], Property, "destroyFilters", null);
__decorate([
sequelize_typescript_1.AfterDestroy,
__metadata("design:type", Function),
__metadata("design:paramtypes", [Property]),
__metadata("design:returntype", Promise)
], Property, "destroyRecordProperties", null);
__decorate([
sequelize_typescript_1.AfterSave,
sequelize_typescript_1.AfterDestroy,
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", Promise)
], Property, "invalidateCache", null);
Property = Property_1 = __decorate([
(0, sequelize_typescript_1.DefaultScope)(() => ({
where: { state: { [sequelize_1.Op.notIn]: ["draft"] } },
})),
(0, sequelize_typescript_1.Table)({ tableName: "properties", paranoid: false })
], Property);
exports.Property = Property;