UNPKG

drizzle-orm

Version:

Drizzle ORM package for SQL databases

316 lines • 8.93 kB
import { getTableUniqueName, Table } from "./table.js"; import { Column } from "./column.js"; import { entityKind, is } from "./entity.js"; import { PrimaryKeyBuilder } from "./pg-core/primary-keys.js"; import { and, asc, between, desc, eq, exists, gt, gte, ilike, inArray, isNotNull, isNull, like, lt, lte, ne, not, notBetween, notExists, notIlike, notInArray, notLike, or } from "./sql/expressions/index.js"; import { SQL, sql } from "./sql/sql.js"; class Relation { constructor(sourceTable, referencedTable, relationName) { this.sourceTable = sourceTable; this.referencedTable = referencedTable; this.relationName = relationName; this.referencedTableName = referencedTable[Table.Symbol.Name]; } static [entityKind] = "Relation"; referencedTableName; fieldName; } class Relations { constructor(table, config) { this.table = table; this.config = config; } static [entityKind] = "Relations"; } class One extends Relation { constructor(sourceTable, referencedTable, config, isNullable) { super(sourceTable, referencedTable, config?.relationName); this.config = config; this.isNullable = isNullable; } static [entityKind] = "One"; withFieldName(fieldName) { const relation = new One( this.sourceTable, this.referencedTable, this.config, this.isNullable ); relation.fieldName = fieldName; return relation; } } class Many extends Relation { constructor(sourceTable, referencedTable, config) { super(sourceTable, referencedTable, config?.relationName); this.config = config; } static [entityKind] = "Many"; withFieldName(fieldName) { const relation = new Many( this.sourceTable, this.referencedTable, this.config ); relation.fieldName = fieldName; return relation; } } function getOperators() { return { and, between, eq, exists, gt, gte, ilike, inArray, isNull, isNotNull, like, lt, lte, ne, not, notBetween, notExists, notLike, notIlike, notInArray, or, sql }; } function getOrderByOperators() { return { sql, asc, desc }; } function extractTablesRelationalConfig(schema, configHelpers) { if (Object.keys(schema).length === 1 && "default" in schema && !is(schema["default"], Table)) { schema = schema["default"]; } const tableNamesMap = {}; const relationsBuffer = {}; const tablesConfig = {}; for (const [key, value] of Object.entries(schema)) { if (is(value, Table)) { const dbName = getTableUniqueName(value); const bufferedRelations = relationsBuffer[dbName]; tableNamesMap[dbName] = key; tablesConfig[key] = { tsName: key, dbName: value[Table.Symbol.Name], schema: value[Table.Symbol.Schema], columns: value[Table.Symbol.Columns], relations: bufferedRelations?.relations ?? {}, primaryKey: bufferedRelations?.primaryKey ?? [] }; for (const column of Object.values( value[Table.Symbol.Columns] )) { if (column.primary) { tablesConfig[key].primaryKey.push(column); } } const extraConfig = value[Table.Symbol.ExtraConfigBuilder]?.(value[Table.Symbol.ExtraConfigColumns]); if (extraConfig) { for (const configEntry of Object.values(extraConfig)) { if (is(configEntry, PrimaryKeyBuilder)) { tablesConfig[key].primaryKey.push(...configEntry.columns); } } } } else if (is(value, Relations)) { const dbName = getTableUniqueName(value.table); const tableName = tableNamesMap[dbName]; const relations2 = value.config( configHelpers(value.table) ); let primaryKey; for (const [relationName, relation] of Object.entries(relations2)) { if (tableName) { const tableConfig = tablesConfig[tableName]; tableConfig.relations[relationName] = relation; if (primaryKey) { tableConfig.primaryKey.push(...primaryKey); } } else { if (!(dbName in relationsBuffer)) { relationsBuffer[dbName] = { relations: {}, primaryKey }; } relationsBuffer[dbName].relations[relationName] = relation; } } } } return { tables: tablesConfig, tableNamesMap }; } function relations(table, relations2) { return new Relations( table, (helpers) => Object.fromEntries( Object.entries(relations2(helpers)).map(([key, value]) => [ key, value.withFieldName(key) ]) ) ); } function createOne(sourceTable) { return function one(table, config) { return new One( sourceTable, table, config, config?.fields.reduce((res, f) => res && f.notNull, true) ?? false ); }; } function createMany(sourceTable) { return function many(referencedTable, config) { return new Many(sourceTable, referencedTable, config); }; } function normalizeRelation(schema, tableNamesMap, relation) { if (is(relation, One) && relation.config) { return { fields: relation.config.fields, references: relation.config.references }; } const referencedTableTsName = tableNamesMap[getTableUniqueName(relation.referencedTable)]; if (!referencedTableTsName) { throw new Error( `Table "${relation.referencedTable[Table.Symbol.Name]}" not found in schema` ); } const referencedTableConfig = schema[referencedTableTsName]; if (!referencedTableConfig) { throw new Error(`Table "${referencedTableTsName}" not found in schema`); } const sourceTable = relation.sourceTable; const sourceTableTsName = tableNamesMap[getTableUniqueName(sourceTable)]; if (!sourceTableTsName) { throw new Error( `Table "${sourceTable[Table.Symbol.Name]}" not found in schema` ); } const reverseRelations = []; for (const referencedTableRelation of Object.values( referencedTableConfig.relations )) { if (relation.relationName && relation !== referencedTableRelation && referencedTableRelation.relationName === relation.relationName || !relation.relationName && referencedTableRelation.referencedTable === relation.sourceTable) { reverseRelations.push(referencedTableRelation); } } if (reverseRelations.length > 1) { throw relation.relationName ? new Error( `There are multiple relations with name "${relation.relationName}" in table "${referencedTableTsName}"` ) : new Error( `There are multiple relations between "${referencedTableTsName}" and "${relation.sourceTable[Table.Symbol.Name]}". Please specify relation name` ); } if (reverseRelations[0] && is(reverseRelations[0], One) && reverseRelations[0].config) { return { fields: reverseRelations[0].config.references, references: reverseRelations[0].config.fields }; } throw new Error( `There is not enough information to infer relation "${sourceTableTsName}.${relation.fieldName}"` ); } function createTableRelationsHelpers(sourceTable) { return { one: createOne(sourceTable), many: createMany(sourceTable) }; } function mapRelationalRow(tablesConfig, tableConfig, row, buildQueryResultSelection, mapColumnValue = (value) => value) { const result = {}; for (const [ selectionItemIndex, selectionItem ] of buildQueryResultSelection.entries()) { if (selectionItem.isJson) { const relation = tableConfig.relations[selectionItem.tsKey]; const rawSubRows = row[selectionItemIndex]; const subRows = typeof rawSubRows === "string" ? JSON.parse(rawSubRows) : rawSubRows; result[selectionItem.tsKey] = is(relation, One) ? subRows && mapRelationalRow( tablesConfig, tablesConfig[selectionItem.relationTableTsKey], subRows, selectionItem.selection, mapColumnValue ) : subRows.map( (subRow) => mapRelationalRow( tablesConfig, tablesConfig[selectionItem.relationTableTsKey], subRow, selectionItem.selection, mapColumnValue ) ); } else { const value = mapColumnValue(row[selectionItemIndex]); const field = selectionItem.field; let decoder; if (is(field, Column)) { decoder = field; } else if (is(field, SQL)) { decoder = field.decoder; } else { decoder = field.sql.decoder; } result[selectionItem.tsKey] = value === null ? null : decoder.mapFromDriverValue(value); } } return result; } export { Many, One, Relation, Relations, createMany, createOne, createTableRelationsHelpers, extractTablesRelationalConfig, getOperators, getOrderByOperators, mapRelationalRow, normalizeRelation, relations }; //# sourceMappingURL=relations.js.map