Table factory typescript problem

I have a basic table schema that all tables share (id, timestamps and more) and I'm trying to write an helper function that returns a pgTable with these fields already added. Right now it looks like this:
const baseTable: PgTableFn = (tableName, fields, extraConfig) =>
pgTable(
tableName,
{
id: uuid("id").primaryKey(),
...fields,
createdAt: timestamp("created_at"),
updatedAt: timestamp("updated_at")
},
extraConfig
);

const testTable = baseTable(
"test_table",
{ test: text("test") },
table => ({
testIdx: index().on(table.test), // <- This is ok
errIdx: index().on(table.createdAt) // <- This gives me an error
})
);
const baseTable: PgTableFn = (tableName, fields, extraConfig) =>
pgTable(
tableName,
{
id: uuid("id").primaryKey(),
...fields,
createdAt: timestamp("created_at"),
updatedAt: timestamp("updated_at")
},
extraConfig
);

const testTable = baseTable(
"test_table",
{ test: text("test") },
table => ({
testIdx: index().on(table.test), // <- This is ok
errIdx: index().on(table.createdAt) // <- This gives me an error
})
);
This is wrong though because testTable is not typed correctly and it is missing all the shared fields. How would I go about correcting this?
2 Replies
A Dapper Raccoon
I'm not great with typescript and I really hope someone else might provide a better solution, but I couldn't see any obvious way to shim the extra columns into TColumnsMap while using the *TableFn interface for the wrapper, since it's explicitly inferred from the fields parameter. I ended up typing my function similar to:
const baseColumns = { /* ... */ };

const baseTable = <
TTableName extends string,
TColumnsMap extends Record<string, SQLiteColumnBuilderBase>
>(
name: TTableName,
fields: TColumnsMap,
extraConfig?: (self: BuildColumns<TTableName, TColumnsMap & typeof baseColumns, 'sqlite'>) => SQLiteTableExtraConfig
) => {
const {id, ...baseColumnsRest} = baseColumns;

return sqliteTable(
name,
{
id,
...fields,
...baseColumnsRest,
},
extraConfig
);
}
const baseColumns = { /* ... */ };

const baseTable = <
TTableName extends string,
TColumnsMap extends Record<string, SQLiteColumnBuilderBase>
>(
name: TTableName,
fields: TColumnsMap,
extraConfig?: (self: BuildColumns<TTableName, TColumnsMap & typeof baseColumns, 'sqlite'>) => SQLiteTableExtraConfig
) => {
const {id, ...baseColumnsRest} = baseColumns;

return sqliteTable(
name,
{
id,
...fields,
...baseColumnsRest,
},
extraConfig
);
}
I did try my hand at a generic interface to serve as a type for various "table factory functions" using whatever default columns - and it did work - but it uses a lot of copy & paste from the Drizzle ORM implementation, which kind of scared me away given that Drizzle is still fairly unstable. I would love to know if there's a more canonical solution to typing these sorts of functions.
icomad
icomad4mo ago
Yeah, at the end I came up with something exactly like your implementation, it will do for now, thank you!
Want results from more Discord servers?
Add your server