/** * Copyright (c) 2026, Salesforce, Inc., * All rights reserved. * For full license text, see the LICENSE.txt file */ import { type GraphQLSchema } from "graphql"; import { type ChildRelationshipSpec } from "./types.js"; import { assertDottedGraphqlName, assertGraphqlName } from "../lib/graphql-name.js"; import { selectDottedFieldPath } from "../lib/path-selection.js"; import { deepSetArg, type QuerySession } from "../lib/session.js"; import { normalizeOrderBy, promoteArg } from "../lib/variable-promotion.js"; /** * Render a UIAPI child relationship onto an existing query session as * ` { edges { node { ... } } }`, with optional `first` / `filter` / * `orderBy` arguments on the connection. `$varName` leaves in `filter` and * `orderBy` promote to typed query variables (same machinery as the top-level * connection). * * Shared by `buildList` and `buildDetail`. Behavior is identical between * callers — the connection node path is the only thing that varies. */ export function selectChildRelationship( session: QuerySession, schema: GraphQLSchema, parentNodePath: string[], child: ChildRelationshipSpec, warnings?: string[], ): void { // `relationshipName` is appended to the path and rendered verbatim as the // connection field name (query-builder emits node.fieldName directly), so an // unconstrained value is a selection-set breakout — the same class as `fields` // below and `object`/`scope` on the top-level builders. Guard it here too: the // MCP + CLI-mirror boundaries already enforce the GraphQL Name charset via zod, // but the builders are also reachable directly (CLI, eval harness) (W-22735537). assertGraphqlName(child.relationshipName, "selectChildRelationship", "relationshipName"); const childConnectionPath = [...parentNodePath, child.relationshipName]; const childNodePath = [...childConnectionPath, "edges", "node"]; for (const field of child.fields) { assertDottedGraphqlName(field, "selectChildRelationship", "childRelationships fields entry"); selectDottedFieldPath(session, schema, childNodePath, field); } if (child.first !== undefined) { const firstRendered = promoteArg( session, schema, childConnectionPath, "first", child.first, ).rendered; deepSetArg(session, childConnectionPath, "first", [], firstRendered); } if (child.filter) { const { rendered } = promoteArg( session, schema, childConnectionPath, "where", child.filter, warnings, ); deepSetArg(session, childConnectionPath, "where", [], rendered); } const childOrderBy = normalizeOrderBy(child.orderBy); if (childOrderBy) { const { rendered } = promoteArg( session, schema, childConnectionPath, "orderBy", childOrderBy, warnings, ); deepSetArg(session, childConnectionPath, "orderBy", [], rendered); } }