Skip to content

Commit 1e6023b

Browse files
committed
Allow functions as override values
1 parent fd88cc0 commit 1e6023b

1 file changed

Lines changed: 127 additions & 31 deletions

File tree

main.ts

Lines changed: 127 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,37 @@ export type Options = {
1313
output: Writable | string;
1414

1515
/**
16-
* Name overrides for enums, classes, and fields.
16+
* The name of the exported enum (Which encapsulates all tables)
17+
* Default: "Table"
18+
*/
19+
tablesEnumName?: string;
20+
21+
/**
22+
* Name overrides for enums, schemas, tables and columns.
23+
* Check tests for more info.
1724
*
1825
* @example
1926
* overrides: {
2027
* "identity_provider.linkedin": "LinkedIn"
2128
* }
29+
*
30+
* @example
31+
* Override a table name with a function
32+
*
33+
* overrides: {
34+
* // Overwrite the 'user' table name
35+
* user: (x, type, defaultValue) => UserTable
36+
* }
37+
*
38+
* @example
39+
* Tag all schemas, tables and columns
40+
*
41+
* overrides: {
42+
* // Append "Table" to all tables and TitleCase the name
43+
* "*": (x, type, defaultValue) => type === "table" ? "Table" + upperFirst(camelCase(x.table)) : defaultValue
44+
* }
2245
*/
23-
overrides?: Record<string, string>;
46+
overrides?: Record<string, OverrideStringFunction>;
2447

2548
/**
2649
* Overrides of column types.
@@ -153,7 +176,7 @@ export type Options = {
153176
* Generates TypeScript definitions (types) from a PostgreSQL database schema.
154177
*/
155178
export async function updateTypes(db: Knex, options: Options): Promise<void> {
156-
const overrides: Record<string, string> = options.overrides ?? {};
179+
const overrides = options.overrides ?? {};
157180
const output: Writable =
158181
typeof options.output === "string"
159182
? fs.createWriteStream(options.output, { encoding: "utf-8" })
@@ -218,7 +241,7 @@ export async function updateTypes(db: Knex, options: Options): Promise<void> {
218241
const enumsMap = new Map(
219242
enums.map((x) => [
220243
x.key,
221-
overrides[x.key] ?? upperFirst(camelCase(x.key)),
244+
(overrides[x.key] as string) ?? upperFirst(camelCase(x.key)),
222245
])
223246
);
224247

@@ -243,17 +266,33 @@ export async function updateTypes(db: Knex, options: Options): Promise<void> {
243266
);
244267

245268
// The list of database tables as enum
246-
output.write("export enum Table {\n");
247-
const tableSet = new Set(
248-
columns.map((x) => {
249-
const schema = x.schema !== "public" ? `${x.schema}.` : "";
250-
return `${schema}${x.table}`;
251-
})
252-
);
253-
Array.from(tableSet).forEach((value) => {
254-
const key = overrides[value] ?? upperFirst(camelCase(value));
269+
output.write(`export enum ${options.tablesEnumName || "Table"} {\n`);
270+
271+
// Unique schema / table combination array
272+
const tables: { table: string; schema: string }[] = [
273+
...new Set(
274+
columns.map((x) => JSON.stringify({ table: x.table, schema: x.schema }))
275+
),
276+
].map((t) => JSON.parse(t));
277+
278+
// Write enum tables
279+
for (const table of tables) {
280+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
281+
const x = columns.find(
282+
(x) => x.table === table.table && x.schema === table.schema
283+
)!;
284+
285+
const tableName =
286+
overrideName(x, "table", overrides) ?? upperFirst(camelCase(x.table));
287+
let schemaName =
288+
x.schema !== "public" ? upperFirst(camelCase(x.schema)) : "";
289+
schemaName = overrideName(x, "schema", overrides) ?? schemaName;
290+
const key = `${schemaName}${tableName}`;
291+
292+
const schema = x.schema !== "public" ? `${x.schema}.` : "";
293+
const value = `${schema}${x.table}`;
255294
output.write(` ${key} = "${value}",\n`);
256-
});
295+
}
257296
output.write("}\n\n");
258297

259298
// Construct TypeScript db record types
@@ -262,15 +301,21 @@ export async function updateTypes(db: Knex, options: Options): Promise<void> {
262301
columns[i - 1] && columns[i - 1].table === x.table
263302
);
264303

265-
// Export table type
304+
// Export schema & table type
266305
if (isTableFirstColumn) {
267-
const tableName = overrides[x.table] ?? upperFirst(camelCase(x.table));
268-
const schemaName =
306+
const tableName =
307+
overrideName(x, "table", overrides) ?? upperFirst(camelCase(x.table));
308+
309+
// Doing it this way because I need to separate the trinary expression from the ?? operator (doesn't work).
310+
let schemaName =
269311
x.schema !== "public" ? upperFirst(camelCase(x.schema)) : "";
312+
schemaName = overrideName(x, "schema", overrides) ?? schemaName;
313+
270314
output.write(`export type ${schemaName}${tableName} = {\n`);
271315
}
272316

273317
// Set column type
318+
const columnName = overrideName(x, "column", overrides) ?? x.column;
274319
const isArrayType = x.type === "ARRAY";
275320
let type = overrideType(x, options) ?? getType(x, enumsMap);
276321

@@ -280,7 +325,7 @@ export async function updateTypes(db: Knex, options: Options): Promise<void> {
280325
// Process the "*" type override if provided
281326
type = typePostProcessor(x, type, options);
282327

283-
output.write(` ${x.column}: ${type};\n`);
328+
output.write(` ${columnName}: ${type};\n`);
284329

285330
if (!(columns[i + 1] && columns[i + 1].table === x.table)) {
286331
output.write("};\n\n");
@@ -297,7 +342,7 @@ export async function updateTypes(db: Knex, options: Options): Promise<void> {
297342
}
298343
}
299344

300-
type Enum = {
345+
export type Enum = {
301346
key: string;
302347
value: string;
303348
};
@@ -312,17 +357,24 @@ export type Column = {
312357
udt: string;
313358
};
314359

315-
type TypeOverride = Record<
316-
string,
317-
string | ((x: Column, defaultType?: string) => string)
318-
>;
319-
type TypePostProcessor = Record<
360+
export type NameOverrideCategory = keyof Column &
361+
("table" | "schema" | "column");
362+
export type OverrideStringFunction =
363+
| string
364+
| ((
365+
x: Column,
366+
category: NameOverrideCategory,
367+
defaultValue: string | null
368+
) => string | null);
369+
370+
export type TypeOverride = Record<string, string | ((x: Column) => string)>;
371+
export type TypePostProcessor = Record<
320372
"*",
321373
string | ((x: Column, defaultType: string) => string)
322374
>;
323375

324376
export function getType(x: Column, customTypes: Map<string, string>): string {
325-
const udt = x.type === "ARRAY" ? x.udt.substring(1) : x.udt;
377+
const udt: string = x.type === "ARRAY" ? x.udt.substring(1) : x.udt;
326378

327379
switch (udt) {
328380
case "bool":
@@ -390,24 +442,22 @@ export function overrideType(x: Column, options: Options): string | null {
390442
const overrideTableColumnTypes = options.overrideTableColumnTypes ?? true;
391443
if (overrideTableColumnTypes && `${x.table}.${x.column}` in typeOverrides) {
392444
const tableColumnType = typeOverrides[`${x.table}.${x.column}`];
393-
return typeof tableColumnType === "function"
394-
? tableColumnType(x)
395-
: tableColumnType;
445+
return isFunction(tableColumnType) ? tableColumnType(x) : tableColumnType;
396446
}
397447

398448
// Override all matching columns type
399449
const overrideColumnTypes = options.overrideColumnTypes ?? true;
400450
if (overrideColumnTypes && x.column in typeOverrides) {
401451
const columnType = typeOverrides[x.column];
402-
return typeof columnType === "function" ? columnType(x) : columnType;
452+
return isFunction(columnType) ? columnType(x) : columnType;
403453
}
404454

405455
// Override the database's default type if provided.
406456
const overrideDefaultTypes = options.overrideDefaultTypes ?? true;
407457
const udt = x.type === "ARRAY" ? x.udt.substring(1) : x.udt;
408458
if (overrideDefaultTypes && udt in typeOverrides) {
409459
const type = typeOverrides[udt];
410-
return typeof type === "function" ? type(x) : type;
460+
return isFunction(type) ? type(x) : type;
411461
}
412462

413463
return null;
@@ -428,11 +478,57 @@ export function typePostProcessor(
428478

429479
// If the "*" has been provided, return its value.
430480
if ("*" in typeOverrides) {
431-
return typeof typeOverrides["*"] === "function"
481+
return isFunction(typeOverrides["*"])
432482
? typeOverrides["*"](x, type)
433483
: typeOverrides["*"];
434484
}
435485

436486
// Return the default type
437487
return type;
438488
}
489+
490+
// eslint-disable-next-line @typescript-eslint/ban-types
491+
function isFunction(value: unknown): value is Function {
492+
return typeof value === "function";
493+
}
494+
495+
export function overrideName(
496+
x: Column,
497+
category: NameOverrideCategory,
498+
overrides: Record<string, OverrideStringFunction>
499+
): string | null {
500+
let name: string | null = null;
501+
const defaultValue = x[category];
502+
503+
if (category === "column") {
504+
// Run override for specific table column
505+
if (`${x.table}.${x.column}` in overrides) {
506+
const override = overrides[`${x.table}.${x.column}`];
507+
name = isFunction(override)
508+
? override(x, category, defaultValue)
509+
: override;
510+
} else if (x.column in overrides) {
511+
// Run override for all columns with same name
512+
const override = overrides[x.column];
513+
name = isFunction(override)
514+
? override(x, category, defaultValue)
515+
: override;
516+
}
517+
} else {
518+
// Run override for specific name/key
519+
if (x[category] in overrides) {
520+
const override = overrides[x[category]];
521+
name = isFunction(override)
522+
? override(x, category, defaultValue)
523+
: override;
524+
}
525+
}
526+
527+
if ("*" in overrides) {
528+
name = isFunction(overrides["*"])
529+
? overrides["*"](x, category, name)
530+
: overrides["*"];
531+
}
532+
533+
return name;
534+
}

0 commit comments

Comments
 (0)