shyft
Version:
Model driven GraphQL API framework
570 lines (569 loc) • 28.5 kB
JavaScript
"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;