UNPKG

@kauza/knex-types

Version:

Generates TypeScript definitions (types) from a (PostgreSQL) database schema.

219 lines (207 loc) 10.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getType = getType; exports.updateTypes = updateTypes; var _camelCase2 = _interopRequireDefault(require("lodash/camelCase")); var _upperFirst2 = _interopRequireDefault(require("lodash/upperFirst")); var _fs = _interopRequireDefault(require("fs")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /* SPDX-FileCopyrightText: 2016-present Kriasoft <hello@kriasoft.com> */ /* SPDX-License-Identifier: MIT */ /** * Generates TypeScript definitions (types) from a PostgreSQL database schema. */ async function updateTypes(db, options) { var _options$overrides, _ref, _ref2; const overrides = (_options$overrides = options.overrides) !== null && _options$overrides !== void 0 ? _options$overrides : {}; const output = typeof options.output === "string" ? _fs.default.createWriteStream(options.output, { encoding: "utf-8" }) : options.output; ["// The TypeScript definitions below are automatically generated.\n", "// Do not touch them, or risk, your modifications being lost.\n\n"].forEach(line => output.write(line)); const schema = (_ref = typeof options.schema === "string" ? options.schema.split(",").map(x => x.trim()) : options.schema) !== null && _ref !== void 0 ? _ref : ["public"]; // Schemas to include or exclude const [includeSchemas, excludeSchemas] = schema.reduce((acc, s) => acc[+s.startsWith("!")].push(s) && acc, [[], []]); // Tables to exclude const exclude = (_ref2 = typeof options.exclude === "string" ? options.exclude.split(",").map(x => x.trim()) : options.exclude) !== null && _ref2 !== void 0 ? _ref2 : []; if (options.prefix) { output.write(options.prefix); output.write("\n\n"); } try { // Fetch the list of custom enum types const enums = await db.table("pg_type").join("pg_enum", "pg_enum.enumtypid", "pg_type.oid").orderBy("pg_type.typname").orderBy("pg_enum.enumsortorder").select("pg_type.typname as key", "pg_enum.enumlabel as value"); // Construct TypeScript type union from enum enums.forEach((x, i) => { var _overrides$x$key; // The first line of enum declaration const enumName = (_overrides$x$key = overrides[x.key]) !== null && _overrides$x$key !== void 0 ? _overrides$x$key : (0, _upperFirst2.default)((0, _camelCase2.default)(x.key)); if (!(enums[i - 1] && enums[i - 1].key === x.key)) { output.write(`export const ${enumName} = {\n`); } // Enum body output.write(` "${x.value}": "${x.value}",\n`); // The closing line if (!(enums[i + 1] && enums[i + 1].key === x.key)) { output.write("};\n"); output.write(`export type ${enumName} = keyof typeof ${enumName};\n\n`); } }); const enumsMap = new Map(enums.map(x => { var _overrides$x$key2; return [x.key, (_overrides$x$key2 = overrides[x.key]) !== null && _overrides$x$key2 !== void 0 ? _overrides$x$key2 : (0, _upperFirst2.default)((0, _camelCase2.default)(x.key))]; })); // Fetch the list of tables/columns const columns = await db.withSchema("information_schema").table("columns").whereIn("table_schema", includeSchemas).whereNotIn("table_schema", excludeSchemas).whereNotIn("table_name", exclude).orderBy("table_schema").orderBy("table_name").orderBy("ordinal_position").select("table_schema as schema", "table_name as table", "column_name as column", db.raw("(is_nullable = 'YES') as nullable"), "column_default as default", "data_type as type", "udt_name as udt"); let keys = []; for (const schema of includeSchemas) { // as we can't join internal tables (for some reasons) we need to fetch the keys separately const keyConstraints = await db.withSchema("information_schema").table("table_constraints").where("table_schema", schema).whereIn("constraint_type", ["FOREIGN KEY", "UNIQUE", "PRIMARY KEY"]).select(["constraint_name", "constraint_type"]); const keyUsage = await db.withSchema("information_schema").table("key_column_usage").where("table_schema", schema).whereIn("constraint_name", keyConstraints.map(x => x.constraint_name)).select("table_schema as schema", "table_name as table", "column_name as column", "constraint_name"); const columnUsage = await db.withSchema("information_schema").table("constraint_column_usage").whereIn("constraint_name", keyConstraints.map(x => x.constraint_name)).select("table_schema as schema", "table_name as table", "column_name as column", "constraint_name"); keys = [...keys, ...keyUsage.map(key => { var _keyConstraints$find; // look up the constraint type const constraintType = (_keyConstraints$find = keyConstraints.find(constraint => constraint.constraint_name === key.constraint_name)) === null || _keyConstraints$find === void 0 ? void 0 : _keyConstraints$find.constraint_type; const refSchema = columnUsage.find(column => column.constraint_name === key.constraint_name); return { ...key, constraint_type: constraintType, refSchema }; })]; } const columnsWithKeyInfo = columns.map(column => { var _keys$find; return { ...column, is_foreign_key: keys.some(key => key.table === column.table && key.column === column.column && key.constraint_type === "FOREIGN KEY"), is_unique: keys.some(key => key.table === column.table && key.column === column.column && key.constraint_type === "UNIQUE"), is_primary_key: keys.some(key => key.table === column.table && key.column === column.column && key.constraint_type === "PRIMARY KEY"), ref_schema: (_keys$find = keys.find(key => key.table === column.table && key.column === column.column && key.constraint_type === "FOREIGN KEY")) === null || _keys$find === void 0 ? void 0 : _keys$find.refSchema }; }); // The list of database tables as enum output.write("export enum Table {\n"); const tableSet = new Set(columnsWithKeyInfo.map(x => { const schema = x.schema !== "public" ? `${x.schema}.` : ""; return `${schema}${x.table}`; })); Array.from(tableSet).forEach(value => { var _overrides$value; const key = (_overrides$value = overrides[value]) !== null && _overrides$value !== void 0 ? _overrides$value : (0, _upperFirst2.default)((0, _camelCase2.default)(value)); output.write(` ${key} = "${value}",\n`); }); output.write("}\n\n"); // The list of tables as type output.write("export type Tables = {\n"); Array.from(tableSet).forEach(key => { var _overrides$key; const value = (_overrides$key = overrides[key]) !== null && _overrides$key !== void 0 ? _overrides$key : (0, _upperFirst2.default)((0, _camelCase2.default)(key)); output.write(` "${key}": ${value},\n`); }); output.write("};\n\n"); // Construct TypeScript db record types columnsWithKeyInfo.forEach((x, i) => { var _overrides$x$table; const schemaName = x.schema !== "public" ? (0, _upperFirst2.default)((0, _camelCase2.default)(x.schema)) : ""; const tableName = (_overrides$x$table = overrides[x.table]) !== null && _overrides$x$table !== void 0 ? _overrides$x$table : (0, _upperFirst2.default)((0, _camelCase2.default)(x.table)); if (!(columnsWithKeyInfo[i - 1] && columnsWithKeyInfo[i - 1].table === x.table)) { output.write(`export type ${schemaName}${tableName} = {\n`); } let type = x.type === "ARRAY" ? `${getType(x.udt.substring(1), enumsMap, x.default)}[]` : getType(x.udt, enumsMap, x.default); // branding the id columns (unique and foreign keys) if (x.column === "id" && (x.is_unique || x.is_primary_key)) { const brandName = `${tableName}Id`; type += ` & { __flavor?: "${brandName}" }`; } else if (x.is_foreign_key && x.column.endsWith("id")) { var _x$ref_schema, _x$ref_schema2, _x$ref_schema3; const refSchemaName = ((_x$ref_schema = x.ref_schema) === null || _x$ref_schema === void 0 ? void 0 : _x$ref_schema.schema) !== "public" ? (0, _upperFirst2.default)((0, _camelCase2.default)((_x$ref_schema2 = x.ref_schema) === null || _x$ref_schema2 === void 0 ? void 0 : _x$ref_schema2.schema)) : ""; const refTableName = (0, _upperFirst2.default)((0, _camelCase2.default)((_x$ref_schema3 = x.ref_schema) === null || _x$ref_schema3 === void 0 ? void 0 : _x$ref_schema3.table)); const brandName = `${refTableName}Id`; type += ` & { __flavor?: "${brandName}" }`; } if (x.nullable) { type += " | null"; } output.write(` ${sanitize(x.column)}: ${type};\n`); if (!(columnsWithKeyInfo[i + 1] && columnsWithKeyInfo[i + 1].table === x.table)) { output.write("};\n\n"); } }); if (options.suffix) { output.write(options.suffix); output.write("\n"); } } finally { output.end(); db.destroy(); } } function getType(udt, customTypes, defaultValue) { var _customTypes$get; switch (udt) { case "bool": return "boolean"; case "text": case "citext": case "money": case "numeric": case "int8": case "char": case "character": case "bpchar": case "varchar": case "time": case "tsquery": case "tsvector": case "uuid": case "xml": case "cidr": case "inet": case "macaddr": return "string"; case "smallint": case "integer": case "int": case "int2": case "int4": case "real": case "float": case "float4": case "float8": return "number"; case "date": case "timestamp": case "timestamptz": return "Date"; case "json": case "jsonb": if (defaultValue) { if (defaultValue.startsWith("'{")) { return "Record<string, unknown>"; } if (defaultValue.startsWith("'[")) { return "unknown[]"; } } return "unknown"; case "bytea": return "Buffer"; case "interval": return "PostgresInterval"; default: return (_customTypes$get = customTypes.get(udt)) !== null && _customTypes$get !== void 0 ? _customTypes$get : "unknown"; } } /** * Wraps the target property identifier into quotes in case it contains any * invalid characters. * * @see https://developer.mozilla.org/docs/Glossary/Identifier */ function sanitize(name) { return /^[a-zA-Z$_][a-zA-Z$_0-9]*$/.test(name) ? name : JSON.stringify(name); } //# sourceMappingURL=main.js.map