graphile-settings
Version:
graphile settings
120 lines (119 loc) • 6.07 kB
JavaScript
import { safeInflection } from './inflection-utils';
import { buildForeignKeyConstraint, } from './constraint-meta-builders';
import { buildFieldList } from './table-meta-context';
import { getRelation, getResourceCodec, getUniques, sameAttributes, } from './table-resource-utils';
function isRecord(value) {
return typeof value === 'object' && value !== null;
}
export function buildBelongsToRelations(codec, attributes, uniques, relations, context) {
const belongsTo = [];
for (const [relationName, relation] of Object.entries(relations)) {
// PostGraphile only sets isReferencee when true (reverse FK);
// forward relations have isReferencee undefined, not false.
if (relation.isReferencee)
continue;
const localAttributes = relation.localAttributes || [];
const isUnique = uniques.some((unique) => sameAttributes(unique.attributes, localAttributes));
const remoteCodec = relation.remoteResource?.codec;
belongsTo.push({
fieldName: relationName,
isUnique,
type: remoteCodec?.name || null,
keys: buildFieldList(localAttributes, codec, attributes, context),
references: { name: remoteCodec?.name || 'unknown' },
});
}
return belongsTo;
}
export function buildReverseRelations(codec, attributes, relations, context) {
const hasOne = [];
const hasMany = [];
for (const [relationName, relation] of Object.entries(relations)) {
if (relation.isReferencee !== true)
continue;
const remoteUniques = getUniques(relation.remoteResource || {});
const remoteAttributes = relation.remoteAttributes || [];
const isUnique = remoteUniques.some((unique) => sameAttributes(unique.attributes, remoteAttributes));
const remoteCodec = relation.remoteResource?.codec;
const meta = {
fieldName: relationName,
isUnique,
type: remoteCodec?.name || null,
keys: buildFieldList(relation.localAttributes || [], codec, attributes, context),
referencedBy: { name: remoteCodec?.name || 'unknown' },
};
if (isUnique) {
hasOne.push(meta);
}
else {
hasMany.push(meta);
}
}
return { hasOne, hasMany };
}
function isManyToManyDetails(value) {
if (!isRecord(value))
return false;
return (isRecord(value.leftTable) &&
isRecord(value.junctionTable) &&
isRecord(value.rightTable) &&
typeof value.leftRelationName === 'string' &&
typeof value.rightRelationName === 'string');
}
function parseManyToManyRelationships(value) {
if (!Array.isArray(value))
return [];
return value.filter(isManyToManyDetails);
}
function getManyToManyRelationships(build, tableResource, codec) {
const relationshipsByResource = build.pgManyToManyRealtionshipsByResource;
if (!(relationshipsByResource instanceof Map))
return [];
const direct = parseManyToManyRelationships(relationshipsByResource.get(tableResource));
if (direct.length > 0)
return direct;
for (const [leftTable, relationships] of relationshipsByResource.entries()) {
const details = parseManyToManyRelationships(relationships);
if (details.length === 0 || !isRecord(leftTable))
continue;
if (leftTable.codec === codec) {
return details;
}
}
return [];
}
function buildManyToManyRelation(details, context) {
const leftCodec = getResourceCodec(details.leftTable);
const junctionCodec = getResourceCodec(details.junctionTable);
const rightCodec = getResourceCodec(details.rightTable);
if (!leftCodec || !junctionCodec || !rightCodec)
return null;
const leftRelation = getRelation(details.leftTable, details.leftRelationName);
const junctionRightRelation = getRelation(details.junctionTable, details.rightRelationName);
if (!leftRelation || !junctionRightRelation)
return null;
const leftJunctionAttributes = leftRelation.remoteAttributes || [];
const leftTableAttributes = leftRelation.localAttributes || [];
const rightJunctionAttributes = junctionRightRelation.localAttributes || [];
const rightTableAttributes = junctionRightRelation.remoteAttributes || [];
const relationFieldName = safeInflection(() => context.build.inflection._manyToManyRelation?.(details), details.rightRelationName || rightCodec.name || null);
const junctionLeftConstraint = buildForeignKeyConstraint(details.leftRelationName || `${junctionCodec.name}_${leftCodec.name}_fkey`, junctionCodec, junctionCodec.attributes, leftJunctionAttributes, leftCodec, leftCodec.attributes, leftTableAttributes, context);
const junctionRightConstraint = buildForeignKeyConstraint(details.rightRelationName || `${junctionCodec.name}_${rightCodec.name}_fkey`, junctionCodec, junctionCodec.attributes, rightJunctionAttributes, rightCodec, rightCodec.attributes, rightTableAttributes, context);
return {
fieldName: relationFieldName,
type: rightCodec.name || null,
junctionTable: { name: junctionCodec.name || 'unknown' },
junctionLeftConstraint,
junctionLeftKeyAttributes: buildFieldList(leftJunctionAttributes, junctionCodec, junctionCodec.attributes, context),
junctionRightConstraint,
junctionRightKeyAttributes: buildFieldList(rightJunctionAttributes, junctionCodec, junctionCodec.attributes, context),
leftKeyAttributes: buildFieldList(leftTableAttributes, leftCodec, leftCodec.attributes, context),
rightKeyAttributes: buildFieldList(rightTableAttributes, rightCodec, rightCodec.attributes, context),
rightTable: { name: rightCodec.name || 'unknown' },
};
}
export function buildManyToManyRelations(resource, codec, context) {
return getManyToManyRelationships(context.build, resource, codec)
.map((details) => buildManyToManyRelation(details, context))
.filter((relation) => relation !== null);
}