UNPKG

shyft

Version:

Model driven GraphQL API framework

570 lines (569 loc) 28.5 kB
"use strict"; var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (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 __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 __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (_) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.disconnectStorage = exports.connectStorage = exports.installStorageScripts = exports.generateMockData = exports.loadModels = void 0; var StorageTypePostgres_1 = __importDefault(require("./StorageTypePostgres")); var _ = __importStar(require("lodash")); var toposort_1 = __importDefault(require("toposort")); var util_1 = require("./util"); var __1 = require(".."); var json_shaper_1 = require("json-shaper"); var typeorm_1 = require("typeorm"); var StoragePostgresConfiguration_1 = require("./StoragePostgresConfiguration"); var filterOperatorMap = { $ne: '$ne', $in: '$in', $notIn: '$not_in', $lt: '$lt', $lte: '$lte', $gt: '$gt', $gte: '$gte', $startsWith: '$starts_with', $contains: '$contains', $endsWith: '$ends_with', $notStartsWith: '$not_starts_with', $notContains: '$not_contains', $notEndsWith: '$not_ends_with', $includes: '$includes', $notIncludes: '$not_includes', $isNull: '$is_null', $noResult: '$noResult', $not: '$not', $or: '$or', $and: '$and', }; var loadModels = function (configuration) { if (!__1.isConfiguration(configuration)) { throw new Error('Invalid configuration object provided to loadModels()'); } var schema = configuration.getSchema(); var storageConfiguration = configuration.getStorageConfiguration(); if (!StoragePostgresConfiguration_1.isStoragePostgresConfiguration(storageConfiguration)) { throw new Error('Invalid storage configuration object provided to loadModels()'); } StorageTypePostgres_1.default.setStorageConfiguration(storageConfiguration); var modelRegistry = {}; _.map(schema.getEntities(), function (entity, entityName) { if (String(entity.storageType) !== String(StorageTypePostgres_1.default)) { return; } var attributes = {}; var references = []; var dataShaperMap = {}; var filterShaperMap = {}; var foreignKeyIndices = []; function Skeleton() { // empty } Object.defineProperty(Skeleton, 'name', { value: entityName, }); _.map(entity.getAttributes(), function (attribute) { // skip for computed values if (attribute.resolve) { return; } // skip for mutation inputs if (attribute.mutationInput) { return; } // TODO: add name to AttributeBase type // eslint-disable-next-line dot-notation var attributeName = attribute['name']; var storageAttributeName; if (attribute.meta && attribute.meta.storageAttributeName) { storageAttributeName = attribute.meta.storageAttributeName; } else if (__1.isViewEntity(entity)) { // TODO: add name to AttributeBase type // eslint-disable-next-line dot-notation storageAttributeName = attribute['name']; } else { // TODO: add name to AttributeBase type // eslint-disable-next-line dot-notation storageAttributeName = _.snakeCase(attribute['name']); } dataShaperMap[attributeName] = storageAttributeName; filterShaperMap[attributeName] = attributeName; var storageDataType; // it's a reference if (__1.isEntity(attribute.type) || __1.isShadowEntity(attribute.type)) { var primaryAttribute = attribute.type.getPrimaryAttribute(); storageDataType = StorageTypePostgres_1.default.convertToStorageDataType(primaryAttribute.type); var targetEntityName_1 = attribute.type.name; // TODO: add targetAttributesMap to AttributeBase type // eslint-disable-next-line dot-notation if (attribute['targetAttributesMap']) { // TODO: check composite foreign keys support __1.mapOverProperties( // TODO: add targetAttributesMap to AttributeBase type // eslint-disable-next-line dot-notation attribute['targetAttributesMap'], function (targetAttribute, sourceAttributeName) { references.push({ sourceAttributeName: sourceAttributeName, targetAttributeName: targetAttribute.name, targetEntityName: targetEntityName_1, }); }); } else { references.push({ sourceAttributeName: attributeName, targetAttributeName: primaryAttribute.name, targetEntityName: targetEntityName_1, }); var foreignKeyIndexName = util_1.generateIndexName(_.snakeCase(entityName), [_.snakeCase(attributeName)], 'key'); var unique = false; // checking if there is a defined index for the foreign key if (__1.isEntity(entity) && entity.indexes) { var definedForeignKeyIndex = entity.indexes.filter(function (index) { return _.isEqual(index.attributes, [attributeName]); }); if (definedForeignKeyIndex && definedForeignKeyIndex.length > 0) { unique = definedForeignKeyIndex[0].type === __1.INDEX_UNIQUE; } } typeorm_1.Index(foreignKeyIndexName, [attributeName], { unique: unique })(Skeleton); foreignKeyIndices.push(foreignKeyIndexName); } typeorm_1.ManyToOne(function () { return modelRegistry[targetEntityName_1].model; }, { onDelete: 'CASCADE', })(Skeleton.prototype, storageAttributeName); // why ts-ignore? name is not a valid key as function param // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore typeorm_1.JoinColumn({ name: storageAttributeName, cascadeAll: true, })(Skeleton.prototype, storageAttributeName); } // it's a regular attribute else { storageDataType = StorageTypePostgres_1.default.convertToStorageDataType(attribute.type); } attributes[attributeName] = { name: storageAttributeName, type: storageDataType.nativeDataType, primary: attribute.primary, nullable: !attribute.required, }; if (attribute.primary) { var primaryGenerator = storageDataType.name === 'StorageDataTypeUUID' ? 'uuid' : 'increment'; // why ts-ignore? PrimaryGeneratedColumn expects 'rowid' as first param // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore typeorm_1.PrimaryGeneratedColumn(primaryGenerator, attributes[attributeName])(Skeleton.prototype, attributeName); } else if (__1.isEntity(entity) || __1.isShadowEntity(entity)) { typeorm_1.Column(attributes[attributeName])(Skeleton.prototype, attributeName); } else if (__1.isViewEntity(entity)) { typeorm_1.ViewColumn(attributes[attributeName])(Skeleton.prototype, attributeName); } }); var constraints = { unique: {}, }; if (__1.isEntity(entity) && entity.indexes) { entity.indexes.map(function (index) { if (index.type === __1.INDEX_UNIQUE || index.type === __1.INDEX_GENERIC) { var indexAttributes = index.attributes.map(_.snakeCase); var indexName = util_1.generateIndexName(_.snakeCase(entityName), indexAttributes, 'key'); var unique = index.type === __1.INDEX_UNIQUE; if (!foreignKeyIndices.includes(indexName)) { typeorm_1.Index(indexName, index.attributes, { unique: unique })(Skeleton); } constraints.unique[indexName] = { attributes: index.attributes, }; } }); } var storageTableName = _.snakeCase(entityName); entity.storageTableName = storageTableName; if (__1.isEntity(entity)) { typeorm_1.Entity({ name: storageTableName })(Skeleton); } else if (__1.isShadowEntity(entity)) { typeorm_1.Entity({ name: storageTableName, synchronize: false })(Skeleton); } else if (__1.isViewEntity(entity)) { typeorm_1.ViewEntity({ name: storageTableName, expression: entity.viewExpression, })(Skeleton); } modelRegistry[entityName] = { model: Skeleton, storageTableName: storageTableName, dataShaperMap: dataShaperMap, reverseDataShaperMap: _.invert(dataShaperMap), dataShaper: json_shaper_1.shaper(dataShaperMap), reverseDataShaper: json_shaper_1.shaper(_.invert(dataShaperMap)), filterShaper: json_shaper_1.shaper(_.extend(filterShaperMap, filterOperatorMap)), references: references, constraints: constraints, }; }); return modelRegistry; }; exports.loadModels = loadModels; var generateMockData = function (configuration) { return __awaiter(void 0, void 0, void 0, function () { var schema, languages, storageConfiguration, storageInstance, modelRegistry, edges, entities, mockInstancesMemory, dependencySortedEntityNames, e, entityName, entity, mockItemCount, i, err_1; return __generator(this, function (_a) { switch (_a.label) { case 0: if (!__1.isConfiguration(configuration)) { throw new Error('Invalid configuration object provided to generateMockData()'); } schema = configuration.getSchema(); languages = configuration.getLanguages(); storageConfiguration = configuration.getStorageConfiguration(); storageInstance = storageConfiguration.getStorageInstance(); modelRegistry = storageConfiguration.getStorageModels(); edges = []; entities = schema.getEntities(); mockInstancesMemory = {}; _.map(entities, function (entity) { _.map(entity.getAttributes(), function (attribute) { if (__1.isEntity(attribute.type)) { // skip cyclic dependencies if (entity.name === attribute.type.name) { return; } edges.push([attribute.type.name, entity.name]); } }); }); dependencySortedEntityNames = toposort_1.default(edges); // add independent entities _.map(entities, function (entity) { if (dependencySortedEntityNames.indexOf(entity.name) === -1) { dependencySortedEntityNames.push(entity.name); } }); e = 0; _a.label = 1; case 1: if (!(e < dependencySortedEntityNames.length)) return [3 /*break*/, 9]; entityName = dependencySortedEntityNames[e]; entity = entities[entityName]; if (String(entity.storageType) !== String(StorageTypePostgres_1.default)) { return [2 /*return*/]; } _a.label = 2; case 2: _a.trys.push([2, 7, , 8]); mockItemCount = entity.meta && entity.meta.mockItemsCount >= 0 ? entity.meta.mockItemsCount : _.random(10, 100); i = 0; _a.label = 3; case 3: if (!(i < mockItemCount)) return [3 /*break*/, 6]; return [4 /*yield*/, generateItem(storageInstance, entity, mockInstancesMemory, modelRegistry[entityName], languages)]; case 4: _a.sent(); _a.label = 5; case 5: i++; return [3 /*break*/, 3]; case 6: return [3 /*break*/, 8]; case 7: err_1 = _a.sent(); console.error(err_1); return [3 /*break*/, 8]; case 8: e++; return [3 /*break*/, 1]; case 9: return [2 /*return*/]; } }); }); }; exports.generateMockData = generateMockData; var getExistingData = function (storageInstance, mockInstancesMemory, entity) { return __awaiter(void 0, void 0, void 0, function () { var entityName, storageTableName, manager, result; return __generator(this, function (_a) { switch (_a.label) { case 0: entityName = entity.name, storageTableName = entity.storageTableName; manager = storageInstance.manager; return [4 /*yield*/, manager .createQueryBuilder(storageTableName, 't') .select(['t.id']) .getMany()]; case 1: result = _a.sent(); if (result.length) { mockInstancesMemory[entityName] = result.map(function (_a) { var id = _a.id; return id; }); } else { mockInstancesMemory[entityName] = []; } return [2 /*return*/]; } }); }); }; function generateItem(storageInstance, entity, mockInstancesMemory, model, languages) { return __awaiter(this, void 0, void 0, function () { var entityName, storageTableName, item, manager, instance, result, primaryAttributeName; var _this = this; return __generator(this, function (_a) { switch (_a.label) { case 0: entityName = entity.name, storageTableName = entity.storageTableName; item = {}; return [4 /*yield*/, Promise.all(_.map(entity.getAttributes(), function (_a, name) { var type = _a.type, primary = _a.primary, required = _a.required, mock = _a.mock; return __awaiter(_this, void 0, void 0, function () { var mockDataGenerator; return __generator(this, function (_b) { switch (_b.label) { case 0: if (primary) { return [2 /*return*/]; } if (!required) { if (!entity.meta || (entity.meta && !entity.meta.mockNoNulls)) { if (Math.random() > 0.5) { return [2 /*return*/]; } } } if (!__1.isEntity(type)) return [3 /*break*/, 3]; if (!!mockInstancesMemory[type.name]) return [3 /*break*/, 2]; return [4 /*yield*/, getExistingData(storageInstance, mockInstancesMemory, type)]; case 1: _b.sent(); _b.label = 2; case 2: item[name] = _.sample(mockInstancesMemory[type.name]); _b.label = 3; case 3: if (__1.isComplexDataType(type)) { // TODO: generate mocks based on complex types item[name] = {}; } else { mockDataGenerator = mock || type.mock; if (mockDataGenerator) { item[name] = mockDataGenerator(entity, name, model, languages); } } return [2 /*return*/]; } }); }); }))]; case 1: _a.sent(); manager = storageInstance.manager; return [4 /*yield*/, manager .createQueryBuilder() .insert() .into(storageTableName) .values(item) .returning('*') .execute()]; case 2: result = _a.sent(); if (result.raw.length) { instance = result.raw[0]; primaryAttributeName = entity.getPrimaryAttribute().name; mockInstancesMemory[entityName] = mockInstancesMemory[entityName] || []; mockInstancesMemory[entityName].push(instance[primaryAttributeName]); } return [2 /*return*/]; } }); }); } var installStorageScripts = function (configuration, synchronize) { if (synchronize === void 0) { synchronize = false; } return __awaiter(void 0, void 0, void 0, function () { var storageConfiguration, languages, storageInstance, manager, modelFeatures, schema; return __generator(this, function (_a) { switch (_a.label) { case 0: storageConfiguration = configuration.getStorageConfiguration(); languages = configuration.getLanguages(); storageInstance = storageConfiguration.getStorageInstance(); manager = storageInstance.manager; modelFeatures = { language: false, i18n: false, state: false, }; schema = configuration.getSchema(); if (languages.length > 1) { modelFeatures.language = true; } _.forEach(schema.getEntities(), function (entity) { if (__1.isEntity(entity) && (entity === null || entity === void 0 ? void 0 : entity.getI18nAttributeNames())) { modelFeatures.i18n = true; } if (__1.isEntity(entity) && (entity === null || entity === void 0 ? void 0 : entity.hasStates())) { modelFeatures.state = true; } }); if (!modelFeatures.state) return [3 /*break*/, 5]; return [4 /*yield*/, manager.query(storageConfiguration.generateGetStateIdFunction(configuration))]; case 1: _a.sent(); return [4 /*yield*/, manager.query(storageConfiguration.generateGetStateIdsFunction(configuration))]; case 2: _a.sent(); return [4 /*yield*/, manager.query(storageConfiguration.generateGetStateMapFunction(configuration))]; case 3: _a.sent(); return [4 /*yield*/, manager.query(storageConfiguration.generateGetStateNameFunction(configuration))]; case 4: _a.sent(); _a.label = 5; case 5: if (!modelFeatures.i18n) return [3 /*break*/, 10]; return [4 /*yield*/, manager.query(storageConfiguration.generateGetAttributeTranslationFunction(configuration))]; case 6: _a.sent(); return [4 /*yield*/, manager.query(storageConfiguration.generateGetAttributeTranslationsFunction(configuration))]; case 7: _a.sent(); return [4 /*yield*/, manager.query(storageConfiguration.generateMergeTranslationsFunction(configuration))]; case 8: _a.sent(); if (!synchronize) return [3 /*break*/, 10]; return [4 /*yield*/, manager.query(storageConfiguration.createI18nIndices(configuration))]; case 9: _a.sent(); _a.label = 10; case 10: return [2 /*return*/]; } }); }); }; exports.installStorageScripts = installStorageScripts; var connectStorage = function (configuration, synchronize, dropSchema, onConnect) { if (synchronize === void 0) { synchronize = false; } if (dropSchema === void 0) { dropSchema = false; } return __awaiter(void 0, void 0, void 0, function () { var storageConfiguration, connectionConfig, modelRegistry, entities, connection, loggerOptions; return __generator(this, function (_a) { switch (_a.label) { case 0: storageConfiguration = configuration.getStorageConfiguration(); connectionConfig = storageConfiguration.getConnectionConfig(); modelRegistry = exports.loadModels(configuration); entities = Object.keys(modelRegistry).map(function (entityName) { return modelRegistry[entityName].model; }); return [4 /*yield*/, typeorm_1.createConnection(__assign(__assign({}, connectionConfig), { type: 'postgres', synchronize: synchronize, dropSchema: dropSchema, entities: entities }))]; case 1: connection = _a.sent(); if (onConnect) { onConnect(connection); } storageConfiguration.setStorageInstance(connection); storageConfiguration.setStorageModels(modelRegistry); loggerOptions = connection.logger.options; // @ts-expect-error untyped property connection.logger.options = false; return [4 /*yield*/, exports.installStorageScripts(configuration, synchronize)]; case 2: _a.sent(); // bring back logger options // @ts-expect-error untyped property connection.logger.options = loggerOptions; return [2 /*return*/, connection]; } }); }); }; exports.connectStorage = connectStorage; var disconnectStorage = function (connection) { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: if (!connection) return [3 /*break*/, 2]; return [4 /*yield*/, connection.close()]; case 1: _a.sent(); _a.label = 2; case 2: return [2 /*return*/]; } }); }); }; exports.disconnectStorage = disconnectStorage;