UNPKG

@grouparoo/core

Version:
501 lines (500 loc) 20.4 kB
"use strict"; 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 __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var Source_1; Object.defineProperty(exports, "__esModule", { value: true }); exports.Source = void 0; const sequelize_1 = require("sequelize"); const sequelize_typescript_1 = require("sequelize-typescript"); const apiData_1 = require("../modules/apiData"); const lockableHelper_1 = require("../modules/lockableHelper"); const source_1 = require("../modules/ops/source"); const plugin_1 = require("../modules/plugin"); const configWriter_1 = require("./../modules/configWriter"); const mappingHelper_1 = require("./../modules/mappingHelper"); const optionHelper_1 = require("./../modules/optionHelper"); const stateMachine_1 = require("./../modules/stateMachine"); const App_1 = require("./App"); const Mapping_1 = require("./Mapping"); const Option_1 = require("./Option"); const Schedule_1 = require("./Schedule"); const Property_1 = require("./Property"); const GrouparooModel_1 = require("./GrouparooModel"); const modelGuard_1 = require("../modules/modelGuard"); const commonModel_1 = require("../classes/commonModel"); const propertiesCache_1 = require("../modules/caches/propertiesCache"); const sourcesCache_1 = require("../modules/caches/sourcesCache"); const cls_1 = require("../modules/cls"); const actionhero_1 = require("actionhero"); const STATES = ["draft", "ready", "deleted"]; const STATE_TRANSITIONS = [ { from: "draft", to: "ready", checks: [ (instance) => instance.validateOptions(), (instance) => instance.validateMapping(), ], }, { from: "draft", to: "deleted", checks: [] }, { from: "ready", to: "deleted", checks: [] }, { from: "deleted", to: "ready", checks: [ (instance) => instance.validateOptions(), (instance) => instance.validateMapping(), ], }, ]; let Source = Source_1 = class Source extends commonModel_1.CommonModel { idPrefix() { return "src"; } async getOptions(sourceFromEnvironment = true) { return optionHelper_1.OptionHelper.getOptions(this, sourceFromEnvironment); } async setOptions(options, externallyValidate = true) { return optionHelper_1.OptionHelper.setOptions(this, options, externallyValidate); } async afterSetOptions(hasChanges) { if (hasChanges) await Source_1.invalidateCache(); } async validateOptions(options, externallyValidate = true) { if (!options) options = await this.getOptions(true); const { pluginConnection } = await this.getPlugin(); if (!pluginConnection) { throw new Error(`cannot find a pluginConnection for type ${this.type}`); } const connectionOptions = externallyValidate ? await this.sourceConnectionOptions(options) : {}; const optionsSpec = pluginConnection.options.map((opt) => { var _a, _b; return ({ ...opt, options: (_b = (_a = connectionOptions[opt.key]) === null || _a === void 0 ? void 0 : _a.options) !== null && _b !== void 0 ? _b : [], }); }); return optionHelper_1.OptionHelper.validateOptions(this, options, optionsSpec); } async getPlugin() { return optionHelper_1.OptionHelper.getPlugin(this); } async parameterizedOptions(run) { const parameterizedOptions = {}; const options = await this.getOptions(); const keys = Object.keys(options); for (const i in keys) { const k = keys[i]; parameterizedOptions[k] = typeof options[k] === "string" ? await plugin_1.plugin.replaceTemplateRunVariables(options[k].toString(), run) : options[k]; } return parameterizedOptions; } async getMapping() { return mappingHelper_1.MappingHelper.getMapping(this); } async setMapping(mappings, externallyValidate = true) { return mappingHelper_1.MappingHelper.setMapping(this, mappings, externallyValidate); } async afterSetMapping() { await Source_1.determinePrimaryKeyProperty(this); await Source_1.invalidateCache(); } async validateMapping() { const previewAvailable = await this.previewAvailable(); if (!previewAvailable) return true; const { pluginConnection } = await this.getPlugin(); if (pluginConnection.skipSourceMapping) return true; const mapping = await this.getMapping(); if (Object.keys(mapping).length === 1) { return true; } else { throw new Error("mapping not set"); } } async sourceConnectionOptions(sourceOptions = {}) { return source_1.SourceOps.sourceConnectionOptions(this, sourceOptions); } async sourcePreview(sourceOptions) { return source_1.SourceOps.sourcePreview(this, sourceOptions); } async defaultPropertyOptions() { return source_1.SourceOps.defaultPropertyOptions(this); } async apiData() { const model = await this.$get("model"); const app = await this.$get("app", { scope: null, include: [Option_1.Option] }); const schedule = await this.$get("schedule", { scope: null }); const options = await this.getOptions(null); const { pluginConnection } = await this.getPlugin(); const scheduleAvailable = await this.scheduleAvailable(); const previewAvailable = await this.previewAvailable(); const mapping = await this.getMapping(); return { id: this.id, name: this.name, type: this.type, state: this.state, mapping, app: app ? await app.apiData() : undefined, appId: this.appId, modelName: model.name, modelId: this.modelId, scheduleAvailable, schedule: schedule ? await schedule.apiData() : undefined, previewAvailable, locked: this.locked, options, connection: pluginConnection, createdAt: apiData_1.APIData.formatDate(this.createdAt), updatedAt: apiData_1.APIData.formatDate(this.updatedAt), }; } async scheduleAvailable() { var _a; const { pluginConnection } = await this.getPlugin(); if (typeof ((_a = pluginConnection === null || pluginConnection === void 0 ? void 0 : pluginConnection.methods) === null || _a === void 0 ? void 0 : _a.importRecords) !== "function") { return false; } const mapping = await this.getMapping(); if (Object.values(mapping).length === 0) { return true; } else { const propertyMappingKey = Object.values(mapping)[0]; const property = (await propertiesCache_1.PropertiesCache.findAllWithCache(this.modelId, "ready")).find((p) => p.key === propertyMappingKey); if (!property) return false; if (!property.unique) return false; return true; } } async previewAvailable() { var _a; const { pluginConnection } = await this.getPlugin(); if (typeof ((_a = pluginConnection === null || pluginConnection === void 0 ? void 0 : pluginConnection.methods) === null || _a === void 0 ? void 0 : _a.sourcePreview) === "function") { return true; } return false; } async importRecordProperty(record, property, propertyOptionsOverride, propertyFiltersOverride) { return source_1.SourceOps.importRecordProperty(this, record, property, propertyOptionsOverride, propertyFiltersOverride); } async importRecordProperties(records, properties, propertyOptionsOverride, propertyFiltersOverride) { return source_1.SourceOps.importRecordProperties(this, records, properties, propertyOptionsOverride, propertyFiltersOverride); } async import(record) { return source_1.SourceOps._import(this, record); } async bootstrapUniqueProperty(params) { return source_1.SourceOps.bootstrapUniqueProperty(this, params); } getConfigId() { return this.idIsDefault() ? configWriter_1.ConfigWriter.generateId(this.name) : this.id; } async getConfigObject() { var _a, _b; const { name, type } = this; this.app = await this.$get("app"); this.model = await this.$get("model"); const appId = (_a = this.app) === null || _a === void 0 ? void 0 : _a.getConfigId(); const modelId = (_b = this.model) === null || _b === void 0 ? void 0 : _b.getConfigId(); const options = await this.getOptions(false); if (!appId || !modelId || !name) { return; } let configObject = { class: "Source", id: this.getConfigId(), modelId, name, type, appId, options, }; const mapping = await mappingHelper_1.MappingHelper.getConfigMapping(this); if (Object.keys(mapping).length > 0) { configObject.mapping = mapping; } const setSchedule = async () => { if (!this.schedule) return; const scheduleConfigObject = await this.schedule.getConfigObject(); if (scheduleConfigObject === null || scheduleConfigObject === void 0 ? void 0 : scheduleConfigObject.id) { configObject = [{ ...configObject }, { ...scheduleConfigObject }]; } }; this.schedule = await this.$get("schedule"); await setSchedule(); return configObject; } // --- Class Methods --- // static async ensureModel(instance) { return modelGuard_1.ModelGuard.check(instance); } static async ensurePluginConnection(instance) { await instance.getPlugin(); // will throw if not found } static async ensureAppReady(instance) { const app = await App_1.App.findById(instance.appId); if (app.state !== "ready") throw new Error(`app ${app.id} is not ready`); } static async ensureSupportedAppType(instance) { const app = await App_1.App.findById(instance.appId); const { pluginConnection } = await instance.getPlugin(); if (!pluginConnection.apps.includes(app.type)) throw new Error(`Source of type "${instance.type}" does not support the App \`${app.name}\` (${app.id}) of type "${app.type}". Supported App types: ${pluginConnection.apps.join(", ")}.`); } static async updateState(instance) { await stateMachine_1.StateMachine.transition(instance, STATE_TRANSITIONS); } static async noUpdateIfLocked(instance) { await lockableHelper_1.LockableHelper.beforeSave(instance, ["state"]); } static async determinePrimaryKeyProperty(instance) { if (instance.state === "deleted") return; const otherSourcePrimaryKey = await Property_1.Property.findOne({ include: { model: Source_1, required: true, where: { modelId: instance.modelId, }, }, where: { isPrimaryKey: true, sourceId: { [sequelize_1.Op.ne]: instance.id, }, }, }); // Do nothing unless we own the current primary key if (otherSourcePrimaryKey) { return; } // Assign the primary key to a property in this source const properties = await instance.$get("properties"); if (!properties.length) return; const mapping = await instance.getMapping(); const mappingValues = Object.values(mapping); for (const property of properties) { const isPrimaryKey = mappingValues.includes(property.key); if (property.isPrimaryKey !== isPrimaryKey) { await property.update({ isPrimaryKey }); } } } static async ensureNotInUse(instance) { const schedule = await instance.$get("schedule", { scope: null }); if (schedule) { throw new Error("cannot delete a source that has a schedule"); } const properties = await instance.$get("properties", { scope: null, where: { isPrimaryKey: false }, }); if (properties.length > 0) { throw new Error("cannot delete a source that has a property"); } } static async ensurePrimaryKeyPropertyNotInUse(instance) { const primaryKeyProperty = await Property_1.Property.findOne({ where: { sourceId: instance.id, isPrimaryKey: true }, }); if (primaryKeyProperty) { await Property_1.Property.ensureNotInUse(primaryKeyProperty, [instance.id]); } } static async noDestroyIfLocked(instance) { await lockableHelper_1.LockableHelper.beforeDestroy(instance); } static async destroyOptions(instance) { return Option_1.Option.destroy({ where: { ownerId: instance.id, ownerType: "source" }, }); } static async destroyMappings(instance) { return Mapping_1.Mapping.destroy({ where: { ownerId: instance.id }, }); } static async destroyPrimaryKeyProperty(instance) { const primaryKeyProperty = await Property_1.Property.findOne({ where: { sourceId: instance.id, isPrimaryKey: true }, }); if (primaryKeyProperty) { await primaryKeyProperty.destroy(); } } static async invalidateCache() { sourcesCache_1.SourcesCache.invalidate(); await cls_1.CLS.afterCommit(async () => await actionhero_1.redis.doCluster("api.rpc.source.invalidateCache")); } }; __decorate([ (0, sequelize_typescript_1.AllowNull)(false), (0, sequelize_typescript_1.ForeignKey)(() => App_1.App), sequelize_typescript_1.Column, __metadata("design:type", String) ], Source.prototype, "appId", void 0); __decorate([ (0, sequelize_typescript_1.Length)({ min: 0, max: 191 }), (0, sequelize_typescript_1.Default)(""), sequelize_typescript_1.Column, __metadata("design:type", String) ], Source.prototype, "name", void 0); __decorate([ (0, sequelize_typescript_1.AllowNull)(false), sequelize_typescript_1.Column, __metadata("design:type", String) ], Source.prototype, "type", 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) ], Source.prototype, "state", void 0); __decorate([ sequelize_typescript_1.Column, __metadata("design:type", String) ], Source.prototype, "locked", void 0); __decorate([ (0, sequelize_typescript_1.AllowNull)(false), (0, sequelize_typescript_1.ForeignKey)(() => GrouparooModel_1.GrouparooModel), sequelize_typescript_1.Column, __metadata("design:type", String) ], Source.prototype, "modelId", void 0); __decorate([ (0, sequelize_typescript_1.BelongsTo)(() => App_1.App), __metadata("design:type", App_1.App) ], Source.prototype, "app", void 0); __decorate([ (0, sequelize_typescript_1.HasOne)(() => Schedule_1.Schedule), __metadata("design:type", Schedule_1.Schedule) ], Source.prototype, "schedule", void 0); __decorate([ (0, sequelize_typescript_1.HasMany)(() => Mapping_1.Mapping), __metadata("design:type", Array) ], Source.prototype, "mappings", void 0); __decorate([ (0, sequelize_typescript_1.HasMany)(() => Property_1.Property), __metadata("design:type", Array) ], Source.prototype, "properties", void 0); __decorate([ (0, sequelize_typescript_1.HasMany)(() => Option_1.Option, { foreignKey: "ownerId", scope: { ownerType: "source" }, }), __metadata("design:type", Array) ], Source.prototype, "__options", void 0); __decorate([ (0, sequelize_typescript_1.BelongsTo)(() => GrouparooModel_1.GrouparooModel), __metadata("design:type", GrouparooModel_1.GrouparooModel) ], Source.prototype, "model", void 0); __decorate([ sequelize_typescript_1.BeforeCreate, sequelize_typescript_1.BeforeSave, __metadata("design:type", Function), __metadata("design:paramtypes", [Source]), __metadata("design:returntype", Promise) ], Source, "ensureModel", null); __decorate([ sequelize_typescript_1.BeforeCreate, __metadata("design:type", Function), __metadata("design:paramtypes", [Source]), __metadata("design:returntype", Promise) ], Source, "ensurePluginConnection", null); __decorate([ sequelize_typescript_1.BeforeCreate, __metadata("design:type", Function), __metadata("design:paramtypes", [Source]), __metadata("design:returntype", Promise) ], Source, "ensureAppReady", null); __decorate([ sequelize_typescript_1.BeforeCreate, __metadata("design:type", Function), __metadata("design:paramtypes", [Source]), __metadata("design:returntype", Promise) ], Source, "ensureSupportedAppType", null); __decorate([ sequelize_typescript_1.BeforeSave, __metadata("design:type", Function), __metadata("design:paramtypes", [Source]), __metadata("design:returntype", Promise) ], Source, "updateState", null); __decorate([ sequelize_typescript_1.BeforeSave, __metadata("design:type", Function), __metadata("design:paramtypes", [Source]), __metadata("design:returntype", Promise) ], Source, "noUpdateIfLocked", null); __decorate([ sequelize_typescript_1.BeforeDestroy, __metadata("design:type", Function), __metadata("design:paramtypes", [Source]), __metadata("design:returntype", Promise) ], Source, "ensureNotInUse", null); __decorate([ sequelize_typescript_1.BeforeDestroy, __metadata("design:type", Function), __metadata("design:paramtypes", [Source]), __metadata("design:returntype", Promise) ], Source, "ensurePrimaryKeyPropertyNotInUse", null); __decorate([ sequelize_typescript_1.BeforeDestroy, __metadata("design:type", Function), __metadata("design:paramtypes", [Source]), __metadata("design:returntype", Promise) ], Source, "noDestroyIfLocked", null); __decorate([ sequelize_typescript_1.AfterDestroy, __metadata("design:type", Function), __metadata("design:paramtypes", [Source]), __metadata("design:returntype", Promise) ], Source, "destroyOptions", null); __decorate([ sequelize_typescript_1.AfterDestroy, __metadata("design:type", Function), __metadata("design:paramtypes", [Source]), __metadata("design:returntype", Promise) ], Source, "destroyMappings", null); __decorate([ sequelize_typescript_1.AfterDestroy, __metadata("design:type", Function), __metadata("design:paramtypes", [Source]), __metadata("design:returntype", Promise) ], Source, "destroyPrimaryKeyProperty", null); __decorate([ sequelize_typescript_1.AfterSave, sequelize_typescript_1.AfterDestroy, __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", Promise) ], Source, "invalidateCache", null); Source = Source_1 = __decorate([ (0, sequelize_typescript_1.DefaultScope)(() => ({ where: { state: "ready" }, })), (0, sequelize_typescript_1.Table)({ tableName: "sources", paranoid: false }) ], Source); exports.Source = Source;