Skip to content

Instantly share code, notes, and snippets.

@tyteen4a03
Last active January 24, 2025 00:31
Show Gist options
  • Save tyteen4a03/57e89cf64380f29928acb46b17e8ae67 to your computer and use it in GitHub Desktop.
Save tyteen4a03/57e89cf64380f29928acb46b17e8ae67 to your computer and use it in GitHub Desktop.
Coerce Postgres columns schema types by @ShilohFox (License: MIT)
import { bigint, integer, serial, smallint } from "drizzle-orm/pg-core";
import type { Field } from "payload";
import toSnakeCase from "to-snake-case";
/*
* Iterates through the list of fields and prepares changes using Payload's Drizzle exposure.
* The fields marked with `numberType: "smallint" | "integer" | "bigint"` in their `custom`
* field will use their respective column type in the schema, so postgres can coerce them.
*
* fields: The fields to comb through recursively.
* namePrefix: The camelCase name to prefix column field names, if the fields are from a group.
* slugPrefix: The snake_case name to prefix db column slugs, if the fields are from a group.
*
* returns: The modified columns if there were any modifications made.
*/
export function combFields(
fields: Field[],
ignoreId = false,
namePrefix = "",
slugPrefix = "",
// biome-ignore lint/suspicious/noExplicitAny: generic
): { modified: false } | { modified: true; columns: { [name: string]: any } } {
// Tracks whether or not there were any changes made, and thus "extending" the table
let extending = false;
// Stores changes made to any columns
// biome-ignore lint/suspicious/noExplicitAny: generic
let columns: { [name: string]: any } = {};
// Use the field definition to deduce whether we want to change its numeric type to a specific numeric type
for (const field of fields) {
// Recurse into field groups and append applicable changes
if (field.type === "group") {
const combedFields = combFields(
field.fields,
true,
`${namePrefix}${field.name}_`,
`${slugPrefix}${toSnakeCase(field.name)}_`,
);
if (!combedFields.modified) continue;
extending = true;
columns = { ...columns, ...combedFields.columns };
continue
}
if (field.type === "collapsible" || field.type === "row") {
const combedFields = combFields(
field.fields
);
if (!combedFields.modified) continue;
extending = true;
columns = { ...columns, ...combedFields.columns };
continue
}
if (field.type === "tabs") {
for (const tab of field.tabs) {
const combedFields = combFields(
tab.fields
);
if (!combedFields.modified) continue;
extending = true;
columns = { ...columns, ...combedFields.columns };
}
continue;
}
// TODO: Handle blocks correctly
if (field.type === "blocks") continue;
if (field.type !== "number" || field.custom?.numberType === undefined) continue;
// At this point, the field qualifies for the schema change
extending = true;
const fieldName = namePrefix + field.name;
const fieldSlug = slugPrefix + toSnakeCase(field.name);
// Generate column type from numberType value
switch (field.custom.numberType) {
case "serial":
columns[fieldName] = serial(fieldSlug);
break;
case "integer":
columns[fieldName] = integer(fieldSlug);
break;
case "smallint":
columns[fieldName] = smallint(fieldSlug);
break;
case "bigint":
columns[fieldName] = bigint(fieldSlug, { mode: "bigint" });
break;
default:
throw new Error(`Unsupported numberType ${field.custom.numberType} in field ${field.name}`);
}
// Generate any necessary modifiers for the column
if (field.defaultValue !== undefined) {
columns[fieldName].default(field.defaultValue);
}
if (field.required !== undefined && field.required) {
columns[fieldName].notNull();
}
if (field.unique !== undefined && field.unique) {
columns[fieldName].unique();
}
if (!ignoreId && field.name === "id") {
columns[fieldName] = serial(fieldSlug).primaryKey();
}
}
if (!extending) return { modified: false };
return {
modified: extending,
columns,
};
} // combFields()
...
db: postgresAdapter({
pool: {
connectionString: process.env.DATABASE_URI || "",
},
afterSchemaInit: [
({ schema, extendTable, adapter }) => {
// Use our collection definitions to generate the int field changes we want to see in the world
for (const collection of allCollections) {
const combedFields = combFields(collection.fields);
if (combedFields.modified) {
extendTable({
table: schema.tables[toSnakeCase(collection.slug)],
columns: combedFields.columns,
});
}
}
return schema;
},
],
}),
...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment