UNPKG

pg-to-ts

Version:

Generate TypeScript-friendly interfaces and constants from (postgres) SQL database schema

347 lines 19.7 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 __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 (g && (g = 0, op[0] && (_ = 0)), _) 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.PostgresDatabase = exports.pgTypeToTsType = void 0; var pg_promise_1 = __importDefault(require("pg-promise")); var lodash_1 = __importDefault(require("lodash")); var pgp = (0, pg_promise_1.default)(); function pgTypeToTsType(column, customTypes, options) { var udtName = column.udtName; switch (udtName) { case 'bpchar': case 'char': case 'varchar': case 'text': case 'citext': case 'uuid': case 'bytea': case 'inet': case 'time': case 'timetz': case 'interval': case 'name': return 'string'; case 'int2': case 'int4': case 'int8': case 'float4': case 'float8': case 'numeric': case 'money': case 'oid': return 'number'; case 'bool': return 'boolean'; case 'json': case 'jsonb': return 'Json'; case 'date': case 'timestamp': case 'timestamptz': return options.options.datesAsStrings ? 'string' : 'Date'; case '_int2': case '_int4': case '_int8': case '_float4': case '_float8': case '_numeric': case '_money': return 'number[]'; case '_bool': return 'boolean[]'; case '_varchar': case '_text': case '_citext': case '_uuid': case '_bytea': return 'string[]'; case '_json': case '_jsonb': return 'Json[]'; case '_timestamptz': return 'Date[]'; default: if (customTypes.includes(udtName)) { return options.transformTypeName(udtName); } if (udtName.startsWith('_')) { var singularName = udtName.slice(1); if (customTypes.includes(singularName)) { return options.transformTypeName(singularName) + '[]'; } } console.log("Type [".concat(column.udtName, " has been mapped to [any] because no specific type has been found.")); return 'any'; } } exports.pgTypeToTsType = pgTypeToTsType; var PostgresDatabase = /** @class */ (function () { function PostgresDatabase(connectionString) { this.metadata = null; this.connectionString = connectionString; this.db = pgp(connectionString); } PostgresDatabase.mapTableDefinitionToType = function (tableDefinition, customTypes, options) { return __assign(__assign({}, tableDefinition), { columns: lodash_1.default.mapValues(tableDefinition.columns, function (column) { return (__assign(__assign({}, column), { tsType: pgTypeToTsType(column, customTypes, options) })); }) }); }; PostgresDatabase.prototype.query = function (queryString) { return this.db.query(queryString); }; /** Call this if you know the DB has changed underneath you, e.g. in a test. */ PostgresDatabase.prototype.reset = function () { this.metadata = null; }; PostgresDatabase.prototype.getEnumTypes = function (schema) { return __awaiter(this, void 0, void 0, function () { var enums, enumSchemaWhereClause; return __generator(this, function (_a) { switch (_a.label) { case 0: enums = {}; enumSchemaWhereClause = schema ? pgp.as.format("where n.nspname = $1", schema) : ''; return [4 /*yield*/, this.db.each('select n.nspname as schema, t.typname as name, e.enumlabel as value ' + 'from pg_type t ' + 'join pg_enum e on t.oid = e.enumtypid ' + 'join pg_catalog.pg_namespace n ON n.oid = t.typnamespace ' + "".concat(enumSchemaWhereClause, " ") + 'order by t.typname asc, e.enumlabel asc;', [], function (item) { if (!enums[item.name]) { enums[item.name] = []; } enums[item.name].push(item.value); })]; case 1: _a.sent(); return [2 /*return*/, enums]; } }); }); }; PostgresDatabase.prototype.getTableDefinition = function (tableName, tableSchema) { return __awaiter(this, void 0, void 0, function () { var _a, tableToKeys, columnComments, tableComments, foreignKeys, tableDefinition, tableComment, comments, fkeys; return __generator(this, function (_b) { switch (_b.label) { case 0: return [4 /*yield*/, this.getMeta(tableSchema)]; case 1: _a = _b.sent(), tableToKeys = _a.tableToKeys, columnComments = _a.columnComments, tableComments = _a.tableComments, foreignKeys = _a.foreignKeys; tableDefinition = { columns: {}, primaryKey: tableToKeys[tableName] || null, }; tableComment = tableComments[tableName]; if (tableComment) { tableDefinition.comment = tableComment; } comments = columnComments[tableName] || {}; fkeys = foreignKeys[tableName] || {}; return [4 /*yield*/, this.db.each('SELECT column_name, udt_name, is_nullable, column_default IS NOT NULL as has_default ' + 'FROM information_schema.columns ' + 'WHERE table_name = $1 and table_schema = $2', [tableName, tableSchema], function (schemaItem) { var column_name = schemaItem.column_name; var columnComment = comments[column_name]; var foreignKey = fkeys[column_name]; tableDefinition.columns[column_name] = __assign(__assign({ udtName: schemaItem.udt_name, nullable: schemaItem.is_nullable === 'YES', hasDefault: schemaItem.has_default === true }, (columnComment ? { comment: columnComment } : {})), (foreignKey ? { foreignKey: foreignKey } : {})); })]; case 2: _b.sent(); return [2 /*return*/, tableDefinition]; } }); }); }; PostgresDatabase.prototype.getTableTypes = function (tableName, tableSchema, options) { return __awaiter(this, void 0, void 0, function () { var enumTypes, customTypes, _a, _b; return __generator(this, function (_c) { switch (_c.label) { case 0: return [4 /*yield*/, this.getMeta(tableSchema)]; case 1: enumTypes = (_c.sent()).enumTypes; customTypes = lodash_1.default.keys(enumTypes); _b = (_a = PostgresDatabase).mapTableDefinitionToType; return [4 /*yield*/, this.getTableDefinition(tableName, tableSchema)]; case 2: return [2 /*return*/, _b.apply(_a, [_c.sent(), customTypes, options])]; } }); }); }; PostgresDatabase.prototype.getSchemaTables = function (schemaName) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { return [2 /*return*/, this.db.map('SELECT table_name ' + 'FROM information_schema.columns ' + 'WHERE table_schema = $1 ' + 'GROUP BY table_name ORDER BY lower(table_name)', [schemaName], function (schemaItem) { return schemaItem.table_name; })]; }); }); }; PostgresDatabase.prototype.getPrimaryKeys = function (schemaName) { return __awaiter(this, void 0, void 0, function () { var keys; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.db.query("\n SELECT\n kcu.table_name,\n tco.constraint_name,\n kcu.ordinal_position as position,\n kcu.column_name as key_column\n FROM information_schema.table_constraints tco\n JOIN information_schema.key_column_usage kcu\n on kcu.constraint_name = tco.constraint_name\n and kcu.constraint_schema = tco.constraint_schema\n and kcu.constraint_name = tco.constraint_name\n WHERE tco.constraint_type = 'PRIMARY KEY'\n AND kcu.table_schema = $1\n ORDER BY kcu.table_name,\n position;\n ", [schemaName])]; case 1: keys = _a.sent(); return [2 /*return*/, (0, lodash_1.default)(keys) .groupBy(function (k) { return k.table_name; }) .mapValues(function (keysForTable) { return keysForTable[0].key_column; }) .value()]; } }); }); }; PostgresDatabase.prototype.getColumnComments = function (schemaName) { return __awaiter(this, void 0, void 0, function () { var comments; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.db.query("\n SELECT\n c.table_name,\n c.column_name,\n pgd.description\n FROM pg_catalog.pg_statio_all_tables AS st\n INNER JOIN pg_catalog.pg_description pgd ON (pgd.objoid=st.relid)\n INNER JOIN information_schema.columns c ON (\n pgd.objsubid=c.ordinal_position AND\n c.table_schema=st.schemaname AND\n c.table_name=st.relname\n )\n WHERE c.table_schema = $1;\n ", [schemaName])]; case 1: comments = _a.sent(); return [2 /*return*/, (0, lodash_1.default)(comments) .groupBy(function (c) { return c.table_name; }) .mapValues(function (ct) { return lodash_1.default.fromPairs(ct.map(function (_a) { var column_name = _a.column_name, description = _a.description; return [column_name, description]; })); }) .value()]; } }); }); }; PostgresDatabase.prototype.getTableComments = function (schemaName) { return __awaiter(this, void 0, void 0, function () { var comments; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.db.query("\n SELECT\n t.table_name,\n pgd.description\n FROM pg_catalog.pg_statio_all_tables AS st\n INNER JOIN pg_catalog.pg_description pgd ON (pgd.objoid=st.relid)\n INNER JOIN information_schema.tables t ON (\n t.table_schema=st.schemaname AND\n t.table_name=st.relname\n )\n WHERE pgd.objsubid = 0\n AND t.table_schema = $1;\n ", [schemaName])]; case 1: comments = _a.sent(); return [2 /*return*/, lodash_1.default.fromPairs(comments.map(function (c) { return [c.table_name, c.description]; }))]; } }); }); }; PostgresDatabase.prototype.getForeignKeys = function (schemaName) { return __awaiter(this, void 0, void 0, function () { var fkeys, countKey, colCounts; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, this.db.query("\n SELECT\n cl2.relname AS table_name,\n att2.attname AS column_name,\n cl.relname AS foreign_table_name,\n att.attname AS foreign_column_name,\n conname\n FROM\n (SELECT\n unnest(con1.conkey) AS \"parent\",\n unnest(con1.confkey) AS \"child\",\n con1.confrelid,\n con1.conrelid,\n con1.conname\n FROM pg_class cl\n JOIN pg_namespace ns ON cl.relnamespace = ns.oid\n JOIN pg_constraint con1 ON con1.conrelid = cl.oid\n WHERE ns.nspname = $1 AND con1.contype = 'f'\n ) con\n JOIN pg_attribute att ON att.attrelid = con.confrelid and att.attnum = con.child\n JOIN pg_class cl ON cl.oid = con.confrelid\n JOIN pg_class cl2 ON cl2.oid = con.conrelid\n JOIN pg_attribute att2 ON att2.attrelid = con.conrelid AND att2.attnum = con.parent\n ", [schemaName])]; case 1: fkeys = _a.sent(); countKey = function (fk) { return "".concat(fk.table_name, ",").concat(fk.conname); }; colCounts = lodash_1.default.countBy(fkeys, countKey); return [2 /*return*/, (0, lodash_1.default)(fkeys) .filter(function (c) { return colCounts[countKey(c)] < 2; }) .groupBy(function (c) { return c.table_name; }) .mapValues(function (tks) { return lodash_1.default.fromPairs(tks.map(function (ck) { return [ ck.column_name, { table: ck.foreign_table_name, column: ck.foreign_column_name }, ]; })); }) .value()]; } }); }); }; PostgresDatabase.prototype.getMeta = function (schemaName) { return __awaiter(this, void 0, void 0, function () { var _a, enumTypes, tableToKeys, foreignKeys, columnComments, tableComments, metadata; return __generator(this, function (_b) { switch (_b.label) { case 0: if (this.metadata && schemaName === this.metadata.schema) { return [2 /*return*/, this.metadata]; } return [4 /*yield*/, Promise.all([ this.getEnumTypes(), this.getPrimaryKeys(schemaName), this.getForeignKeys(schemaName), this.getColumnComments(schemaName), this.getTableComments(schemaName), ])]; case 1: _a = _b.sent(), enumTypes = _a[0], tableToKeys = _a[1], foreignKeys = _a[2], columnComments = _a[3], tableComments = _a[4]; metadata = { schema: schemaName, enumTypes: enumTypes, tableToKeys: tableToKeys, foreignKeys: foreignKeys, columnComments: columnComments, tableComments: tableComments, }; this.metadata = metadata; return [2 /*return*/, metadata]; } }); }); }; PostgresDatabase.prototype.getDefaultSchema = function () { return 'public'; }; return PostgresDatabase; }()); exports.PostgresDatabase = PostgresDatabase; //# sourceMappingURL=schemaPostgres.js.map