From b3ef375af37410ee01180ab90a303683594408ad Mon Sep 17 00:00:00 2001 From: Darek Walczy-Antkowicz Date: Mon, 22 Nov 2021 01:50:10 +0100 Subject: [PATCH] Add formatters --- main.test.ts | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++-- main.ts | 46 ++++++++++++++++++++------- 2 files changed, 120 insertions(+), 15 deletions(-) diff --git a/main.test.ts b/main.test.ts index 5ca9a25..3d69419 100644 --- a/main.test.ts +++ b/main.test.ts @@ -2,12 +2,15 @@ /* SPDX-License-Identifier: MIT */ import { knex } from "knex"; +import { camelCase, snakeCase, upperFirst } from "lodash"; import { PassThrough } from "stream"; import { updateTypes } from "./main"; -const db = knex({ client: "pg", connection: { database: "update_types" } }); +let db: ReturnType; + +beforeEach(async function setup() { + db = knex({ client: "pg", connection: { database: "update_types" } }); -beforeAll(async function setup() { await createDatabase(); await db.raw(`CREATE DOMAIN short_id AS TEXT CHECK(VALUE ~ '^[0-9a-z]{6}$')`); @@ -74,7 +77,7 @@ beforeAll(async function setup() { }); }); -afterAll(async function teardown() { +afterEach(async function teardown() { await db.destroy(); }); @@ -165,6 +168,86 @@ test("updateTypes", async function () { `); }); +test("updateTypes with formatters", async function () { + const output = new PassThrough(); + + await updateTypes(db, { + output, + formatters: { + column: (name) => upperFirst(camelCase(name)), + enum: (name) => name, // disable default + enumEl: (name) => snakeCase(name).toUpperCase(), + table: (name) => `prefix${upperFirst(name)}`, + }, + }); + + expect(await toString(output)).toMatchInlineSnapshot(` +"// The TypeScript definitions below are automatically generated. +// Do not touch them, or risk, your modifications being lost. + +export enum identity_provider { + GOOGLE = \\"google\\", + FACEBOOK = \\"facebook\\", + LINKEDIN = \\"linkedin\\", +} + +export enum Table { + prefixLogin = \\"login\\", + prefixUser = \\"user\\", +} + +export type prefixLogin = { + Secret: number; +}; + +export type prefixUser = { + Int: number; + Provider: identity_provider; + ProviderNull: identity_provider | null; + ProviderArray: identity_provider[]; + IntArray: number[]; + ShortId: string; + Decimal: string; + DecimalArray: string[]; + Double: number; + DoubleArray: number[]; + Float: number; + FloatArray: number[]; + Money: string; + Bigint: string; + Binary: Buffer; + BinaryNull: Buffer | null; + BinaryArray: Buffer[]; + Uuid: string; + UuidNull: string | null; + UuidArray: string[]; + Text: string; + TextNull: string | null; + TextArray: string[]; + Citext: string; + CitextNull: string | null; + CitextArray: string[]; + Char: string; + Varchar: string; + Bool: boolean; + BoolNull: boolean | null; + BoolArray: boolean[]; + JsonbObject: Record; + JsonbObjectNull: Record | null; + JsonbArray: unknown[]; + JsonbArrayNull: unknown[] | null; + Timestamp: Date; + TimestampNull: Date | null; + Time: string; + TimeNull: string | null; + TimeArray: string[]; + Interval: PostgresInterval; +}; + +" +`); +}); + async function createDatabase(): Promise { try { await db.select(db.raw("version()")).first(); diff --git a/main.ts b/main.ts index 4313e7c..53a9897 100644 --- a/main.ts +++ b/main.ts @@ -51,12 +51,39 @@ export type Options = { * exclude: ["migration", "migration_lock"] */ exclude?: string[] | string; + + /** + * Custom formatters for specific type of data - enum, enumEl, table and column. + * + * If any type is null/not specified default formatter for this type will be used. + * + * @example + * formatters: { + * column: (name) => kebabCase(name), + * table: (name) => upperFirst(name), + * } + */ + formatters?: { + column?: (name: string) => string; + enum?: (name: string) => string; + enumEl?: (name: string) => string; + table?: (name: string) => string; + }; }; /** * Generates TypeScript definitions (types) from a PostgreSQL database schema. */ export async function updateTypes(db: Knex, options: Options): Promise { + // const formatter: Record string> = { + const formatter: Required = { + column: options.formatters?.column ?? ((name) => name), + enum: options.formatters?.enum ?? ((name) => upperFirst(camelCase(name))), + enumEl: + options.formatters?.enumEl ?? + ((name) => upperFirst(camelCase(name.replace(/[.-]/g, "_")))), + table: options.formatters?.table ?? ((name) => upperFirst(camelCase(name))), + }; const overrides: Record = options.overrides ?? {}; const output: Writable = typeof options.output === "string" @@ -103,14 +130,12 @@ export async function updateTypes(db: Knex, options: Options): Promise { enums.forEach((x, i) => { // The first line of enum declaration if (!(enums[i - 1] && enums[i - 1].key === x.key)) { - const enumName = overrides[x.key] ?? upperFirst(camelCase(x.key)); + const enumName = overrides[x.key] ?? formatter.enum(x.key); output.write(`export enum ${enumName} {\n`); } // Enum body - const key = - overrides[`${x.key}.${x.value}`] ?? - upperFirst(camelCase(x.value.replace(/[.-]/g, "_"))); + const key = overrides[`${x.key}.${x.value}`] ?? formatter.enumEl(x.value); output.write(` ${key} = "${x.value}",\n`); // The closing line @@ -120,10 +145,7 @@ export async function updateTypes(db: Knex, options: Options): Promise { }); const enumsMap = new Map( - enums.map((x) => [ - x.key, - overrides[x.key] ?? upperFirst(camelCase(x.key)), - ]) + enums.map((x) => [x.key, overrides[x.key] ?? formatter.enum(x.key)]) ); // Fetch the list of tables/columns @@ -155,7 +177,7 @@ export async function updateTypes(db: Knex, options: Options): Promise { }) ); Array.from(tableSet).forEach((value) => { - const key = overrides[value] ?? upperFirst(camelCase(value)); + const key = overrides[value] ?? formatter.table(value); output.write(` ${key} = "${value}",\n`); }); output.write("}\n\n"); @@ -163,9 +185,9 @@ export async function updateTypes(db: Knex, options: Options): Promise { // Construct TypeScript db record types columns.forEach((x, i) => { if (!(columns[i - 1] && columns[i - 1].table === x.table)) { - const tableName = overrides[x.table] ?? upperFirst(camelCase(x.table)); + const tableName = overrides[x.table] ?? formatter.table(x.table); const schemaName = - x.schema !== "public" ? upperFirst(camelCase(x.schema)) : ""; + x.schema !== "public" ? formatter.table(x.schema) : ""; output.write(`export type ${schemaName}${tableName} = {\n`); } @@ -178,7 +200,7 @@ export async function updateTypes(db: Knex, options: Options): Promise { type += " | null"; } - output.write(` ${x.column}: ${type};\n`); + output.write(` ${formatter.column(x.column)}: ${type};\n`); if (!(columns[i + 1] && columns[i + 1].table === x.table)) { output.write("};\n\n");