Skip to main content
Module

x/pasta/src/static-modules-generator.ts

Create and manipulate type-safe SQL for PostgreSQL
Go to Latest
File
function header() { return `// Automatically generated by PASTA`;}
function generatePgCatalog() { return `${header()}type UUIDFunctionCall = { returnType: "uuid" };type TimestampFunctionCall = { returnType: "timestamp" };type JSONValue = | string | number | boolean | { [x: string]: JSONValue } | Array<JSONValue>;
function uuid() { return { "type": "call", "function": { "name": "gen_random_uuid" }, "args": [], "returnType": "uuid", } as UUIDFunctionCall;}
function now() { return ( { "type": "call", "function": { "name": "now" }, "args": [], "returnType": "timestamp", } as TimestampFunctionCall );}
const functions = { now, uuid,};export type { JSONValue, TimestampFunctionCall };export { functions, now, uuid };`;}
function generateSchema() { return `${header()}import type { TableName, Tables } from "./custom-schema.ts";import { associations } from "./custom-schema.ts";
type KeysOf<T extends TableName> = Tables[T]["keys"];type ColumnsOf<T extends TableName> = Tables[T]["columns"];type ColumnNamesOf<T extends TableName> = (keyof ColumnsOf<T>)[];type AssociationsOf<T extends TableName> = Tables[T]["associations"];
type MxNAssociation = { kind: "MxN"; table: TableName; associativeTable: TableName; fks: Record<string, [string, string]>;};
type NAssociation = { kind: "1xN"; table: TableName; fks: Record<string, string>;};
type Association = | NAssociation | MxNAssociation;
type Associations = Record<TableName, Record<string, Association>>;
export type { Association, Associations, AssociationsOf, ColumnNamesOf, ColumnsOf, KeysOf, MxNAssociation, NAssociation, TableName, Tables,};export { associations };
`;}
function generateTypedStatementBuilder(pastaLib: string) { return `${header()}import { associations, AssociationsOf, ColumnNamesOf, ColumnsOf, KeysOf, MxNAssociation, NAssociation, TableName,} from "./schema.ts";
import { sql } from "${pastaLib}";
type ReturningBuilder<T extends TableName> = sql.SqlBuilder & { returning: (options: ColumnNamesOf<T>) => ReturningBuilder<T>;};type InsertBuilder<T extends TableName> = ReturningBuilder<T> & { associate: (associationMap: AssociationsOf<T>) => InsertBuilder<T>;};type SelectBuilder<T extends TableName> = ReturningBuilder<T> & { where: (whereMap: ColumnsOf<T>) => SelectBuilder<T>; unique: (whereMap: KeysOf<T>) => SelectBuilder<T>;};
function addReturning<T extends TableName>(builder: sql.SqlBuilder): ReturningBuilder<T> { return { ...builder, returning: function ( options: ColumnNamesOf<T>, ): ReturningBuilder<T> { return addReturning(sql.returning(builder, options.map(String))); }, };}
function addSelectReturning<T extends TableName>(builder: sql.SqlBuilder) { return { ...builder, returning: function (options: ColumnNamesOf<T>): ReturningBuilder<T> { return addSelectReturning(sql.selection(builder, options.map(String))); }, };}
function addWhere<T extends TableName>(builder: ReturningBuilder<T>) { return { ...builder, where: function (whereMap: ColumnsOf<T>): ReturningBuilder<T> { return addSelectReturning(sql.where(builder, whereMap)); }, } as SelectBuilder<T>;}
function addUnique<T extends TableName>(builder: ReturningBuilder<T>) { return { ...builder, unique: function (whereMap: KeysOf<T>): ReturningBuilder<T> { return addSelectReturning(sql.where(builder, whereMap)); }, } as SelectBuilder<T>;}
function addAssociate<T extends TableName>( table: T, builder: ReturningBuilder<T>,): InsertBuilder<T> { const builderWithMxNAssociation = <T extends TableName>( builder: ReturningBuilder<T>, association: MxNAssociation, associatedValues: Record<string, unknown>, ) => { const { fks, associativeTable } = association; const targetAssociationColumns = Object.keys(fks); const sourceColumns = targetAssociationColumns.map(( c, ) => (sql.column(...fks[c]))); const returningFksAssociation = Object.values(fks).filter(( [fkTable], ) => (fkTable == association.table)) .map(([_, fkColumn]) => (fkColumn));
const withStatement = sql.makeInsertWith( association.table, sql.returning( sql.makeInsert(association.table, associatedValues), returningFksAssociation, ), sql.makeInsertWith( table, builder, sql.makeInsertFrom(associativeTable, sourceColumns, targetAssociationColumns), ), ); return addReturning<T>(withStatement); };
const builderWith1xNAssociation = ( builder: ReturningBuilder<T>, association: NAssociation, associatedValues: Record<string, unknown>, ) => { const { fks, table: associedTable } = association;
const returningFksAssociation = Object.values(fks);
const sourceFkColumns = Object.keys(fks).map(( k, ) => (sql.column(table, fks[k])));
const sourceValueColumns = Object.keys(associatedValues).map(( k, ) => (sql.column(String(associatedValues[k]))));
const withStatement = sql.makeInsertWith( table, sql.returning(builder, returningFksAssociation), sql.makeInsertFrom( associedTable, [...sourceFkColumns, ...sourceValueColumns], [...Object.keys(fks), ...Object.keys(associatedValues)], ), ); return addReturning<T>(withStatement); };
const associate = (associationMap: AssociationsOf<T>) => { const newBuilder = Object.entries(associationMap).reduce( (previous, [associated, associatedValues]) => { const association = associations[table][associated]; const pks = association.kind === "1xN" ? Object.values(association.fks) : Object.values(association.fks).filter(( [associatedTable, _column], ) => associatedTable === table).map(([_table, column]) => column);
const returningPks = previous.returning(pks as ColumnNamesOf<T>);
return association.kind === "1xN" ? builderWith1xNAssociation(returningPks, association, associatedValues) : builderWithMxNAssociation(returningPks, association, associatedValues); }, builder, );
return addAssociate(table, newBuilder); }; return { ...builder, associate };}
// Public functions
function insert<T extends TableName>(table: T): (valueMap: ColumnsOf<T>) => InsertBuilder<T> { return function (valueMap) { return addAssociate<T>( table, addReturning<T>(sql.makeInsert(table, valueMap)), ); };}
function upsert<T extends TableName>( table: T,): (insertValues: ColumnsOf<T>, updateValues?: ColumnsOf<T>) => ReturningBuilder<T> { return (insertValues, updateValues) => addReturning(sql.makeUpsert(table, insertValues, updateValues));}
function update<T extends TableName>( table: T,): (keyValues: KeysOf<T>, setValues: ColumnsOf<T>) => ReturningBuilder<T> { return (keyValues, setValues) => addReturning(sql.makeUpdate(table, keyValues, setValues));}
function insertWith<T1 extends TableName>(contextTable: T1, context: ReturningBuilder<T1>) { return function <T2 extends TableName>(insert: ReturningBuilder<T2>) { return addReturning<T2>( sql.makeInsertWith(contextTable, context, insert), ); };}
function select<T extends TableName>(table: T): () => SelectBuilder<T> { return () => addUnique<T>(addWhere<T>(addSelectReturning<T>(sql.makeSelect(table))));}
export { insert, insertWith, select, update, upsert };export type { InsertBuilder, ReturningBuilder, SelectBuilder };`;}
function generateIndex(pastaLib: string) { return `${header()}export { db } from "${pastaLib}";export { tables } from "./builders.ts";export { functions } from "./pg-catalog.ts";`;}
export { generateIndex, generatePgCatalog, generateSchema, generateTypedStatementBuilder,};