316 lines
8.7 KiB
JavaScript
316 lines
8.7 KiB
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
|