UNPKG

@grouparoo/core

Version:
604 lines (603 loc) 24.5 kB
"use strict"; 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;