drizzle-orm
Version:
Drizzle ORM package for SQL databases
316 lines • 8.93 kB
JavaScript
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