|
| 1 | +import { Config } from '@graphprotocol/hypergraph'; |
| 2 | +import { request } from 'graphql-request'; |
| 3 | +import type { RelationsListWithNodes } from '../../utils/convert-relations.js'; |
| 4 | +import type { RelationTypeIdInfo } from '../../utils/get-relation-type-ids.js'; |
| 5 | +import { getRelationAlias } from '../../utils/relation-query-helpers.js'; |
| 6 | + |
| 7 | +type QueryEntityWithRelations = { |
| 8 | + id: string; |
| 9 | +} & Partial<Record<`relations_${string}`, RelationsListWithNodes | undefined>>; |
| 10 | + |
| 11 | +type ProposalRelationRef = { |
| 12 | + proposalId: string; |
| 13 | + relationId: string; |
| 14 | +}; |
| 15 | + |
| 16 | +type ProposalQueryResult = { |
| 17 | + id: string; |
| 18 | +} & Record<string, unknown>; |
| 19 | + |
| 20 | +type ProposalsByIdsQueryResult = { |
| 21 | + proposals: ProposalQueryResult[]; |
| 22 | +}; |
| 23 | + |
| 24 | +const proposalsByIdsQueryDocument = ` |
| 25 | +query proposalsByIds($ids: [UUID!]!) { |
| 26 | + proposals(filter: { id: { in: $ids } }) { |
| 27 | + id |
| 28 | + proposedBy |
| 29 | + executedAt |
| 30 | + spaceId |
| 31 | + votingMode |
| 32 | + startTime |
| 33 | + endTime |
| 34 | + quorum |
| 35 | + threshold |
| 36 | + name |
| 37 | + createdAt |
| 38 | + noCount |
| 39 | + yesCount |
| 40 | + createdAtBlock |
| 41 | + } |
| 42 | +} |
| 43 | +`; |
| 44 | + |
| 45 | +const getProposalBacklinkInfo = (relationInfo: RelationTypeIdInfo[]) => |
| 46 | + relationInfo.filter((info) => info.includeNodes && info.resolutionStrategy === 'proposalBacklink'); |
| 47 | + |
| 48 | +const collectProposalBacklinkRefs = (queryEntities: QueryEntityWithRelations[], relationInfo: RelationTypeIdInfo[]) => { |
| 49 | + const backlinkInfo = getProposalBacklinkInfo(relationInfo); |
| 50 | + const refsByParentId = new Map<string, Map<string, ProposalRelationRef[]>>(); |
| 51 | + const proposalIds = new Set<string>(); |
| 52 | + |
| 53 | + for (const queryEntity of queryEntities) { |
| 54 | + const refsByPropertyName = new Map<string, ProposalRelationRef[]>(); |
| 55 | + |
| 56 | + for (const info of backlinkInfo) { |
| 57 | + const alias = getRelationAlias(info.typeId, info.targetTypeIds); |
| 58 | + const relationNodes = queryEntity[alias]?.nodes ?? []; |
| 59 | + const relationRefs: ProposalRelationRef[] = []; |
| 60 | + |
| 61 | + for (const relationNode of relationNodes) { |
| 62 | + if (!relationNode?.toEntity?.id || !relationNode.id) { |
| 63 | + continue; |
| 64 | + } |
| 65 | + relationRefs.push({ |
| 66 | + proposalId: relationNode.toEntity.id, |
| 67 | + relationId: relationNode.id, |
| 68 | + }); |
| 69 | + proposalIds.add(relationNode.toEntity.id); |
| 70 | + } |
| 71 | + |
| 72 | + refsByPropertyName.set(info.propertyName, relationRefs); |
| 73 | + } |
| 74 | + |
| 75 | + refsByParentId.set(queryEntity.id, refsByPropertyName); |
| 76 | + } |
| 77 | + |
| 78 | + return { |
| 79 | + refsByParentId, |
| 80 | + proposalIds: Array.from(proposalIds), |
| 81 | + }; |
| 82 | +}; |
| 83 | + |
| 84 | +const fetchProposalsByIds = async (proposalIds: readonly string[]) => { |
| 85 | + if (proposalIds.length === 0) { |
| 86 | + return new Map<string, ProposalQueryResult>(); |
| 87 | + } |
| 88 | + |
| 89 | + const response = await request<ProposalsByIdsQueryResult>( |
| 90 | + `${Config.getApiOrigin()}/graphql`, |
| 91 | + proposalsByIdsQueryDocument, |
| 92 | + { |
| 93 | + ids: proposalIds, |
| 94 | + }, |
| 95 | + ); |
| 96 | + |
| 97 | + return new Map(response.proposals.map((proposal) => [proposal.id, proposal])); |
| 98 | +}; |
| 99 | + |
| 100 | +export const hydrateProposalBacklinks = async <T extends { id: string }>( |
| 101 | + queryEntities: QueryEntityWithRelations[], |
| 102 | + entities: T[], |
| 103 | + relationInfo: RelationTypeIdInfo[], |
| 104 | +) => { |
| 105 | + const proposalBacklinkInfo = getProposalBacklinkInfo(relationInfo); |
| 106 | + if (proposalBacklinkInfo.length === 0 || entities.length === 0) { |
| 107 | + return; |
| 108 | + } |
| 109 | + |
| 110 | + const { refsByParentId, proposalIds } = collectProposalBacklinkRefs(queryEntities, relationInfo); |
| 111 | + const proposalsById = await fetchProposalsByIds(proposalIds); |
| 112 | + |
| 113 | + for (const entity of entities) { |
| 114 | + const refsByPropertyName = refsByParentId.get(entity.id); |
| 115 | + if (!refsByPropertyName) { |
| 116 | + continue; |
| 117 | + } |
| 118 | + |
| 119 | + const entityRecord = entity as Record<string, unknown>; |
| 120 | + |
| 121 | + for (const info of proposalBacklinkInfo) { |
| 122 | + const refs = refsByPropertyName.get(info.propertyName) ?? []; |
| 123 | + entityRecord[info.propertyName] = refs.flatMap((ref) => { |
| 124 | + const proposal = proposalsById.get(ref.proposalId); |
| 125 | + if (!proposal) { |
| 126 | + return []; |
| 127 | + } |
| 128 | + |
| 129 | + return [ |
| 130 | + { |
| 131 | + ...proposal, |
| 132 | + _relation: { |
| 133 | + id: ref.relationId, |
| 134 | + }, |
| 135 | + }, |
| 136 | + ]; |
| 137 | + }); |
| 138 | + } |
| 139 | + } |
| 140 | +}; |
0 commit comments