> For the complete documentation index, see [llms.txt](https://docs.pyck.cloud/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.pyck.cloud/development/migrations/relay-connections.md).

# Relay Connections

Migration guide for the breaking change from simple list fields to Relay-style Connection objects

Audience: Programmer

## Overview

This guide covers a breaking change in the pyck GraphQL API where all entity relationship fields now return Relay-style Connection objects instead of simple lists. This change enables consistent cursor-based pagination, filtering, and ordering across all relationships.

## What Changed

**Before**: Relationship fields returned simple arrays.

```graphql
type Group {
  users: [User!]
  roles: [Role!]
}
```

**After**: Relationship fields return Connection objects with edges and nodes.

```graphql
type Group {
  users(
    after: Cursor
    first: Int
    before: Cursor
    last: Int
    orderBy: UserOrder
    where: UserWhereInput
  ): UserConnection!
  roles(
    after: Cursor
    first: Int
    before: Cursor
    last: Int
    orderBy: RoleOrder
    where: RoleWhereInput
  ): RoleConnection!
}
```

## Affected Relationship Fields

The following relationship fields have been changed to return Connection objects:

### Inventory Service

| Type                 | Field                                | Connection Type                    |
| -------------------- | ------------------------------------ | ---------------------------------- |
| `InventoryItemSet`   | `items`                              | `InventoryItemConnection`          |
| `ReplenishmentOrder` | `replenishmentOrderItems`            | `ReplenishmentOrderItemConnection` |
| `Repository`         | `itemMovementToRepositories`         | `ItemMovementConnection`           |
| `Repository`         | `itemMovementFromRepositories`       | `ItemMovementConnection`           |
| `Repository`         | `repositoryMovementToRepositories`   | `RepositoryMovementConnection`     |
| `Repository`         | `repositoryMovementFromRepositories` | `RepositoryMovementConnection`     |
| `Repository`         | `repositoryMovementRepositories`     | `RepositoryMovementConnection`     |
| `Repository`         | `repositoryTransactions`             | `TransactionConnection`            |
| `Repository`         | `repositoryStocks`                   | `StockConnection`                  |
| `Repository`         | `children`                           | `RepositoryConnection`             |

### Management Service

| Type       | Field                     | Connection Type            |
| ---------- | ------------------------- | -------------------------- |
| `Device`   | `deviceLocationsDevice`   | `DeviceLocationConnection` |
| `Device`   | `deviceUsersDevice`       | `DeviceUserConnection`     |
| `Group`    | `users`                   | `UserConnection`           |
| `Group`    | `roles`                   | `RoleConnection`           |
| `Location` | `deviceLocationsLocation` | `DeviceLocationConnection` |
| `Role`     | `users`                   | `UserConnection`           |
| `Role`     | `groups`                  | `GroupConnection`          |
| `Role`     | `policies`                | `AccessPolicyConnection`   |
| `Tenant`   | `tenantUsers`             | `UserConnection`           |
| `User`     | `roles`                   | `RoleConnection`           |
| `User`     | `groups`                  | `GroupConnection`          |
| `User`     | `deviceUsersUsers`        | `DeviceUserConnection`     |

### Receiving Service

| Type               | Field          | Connection Type                  |
| ------------------ | -------------- | -------------------------------- |
| `ReceivingInbound` | `inboundItems` | `ReceivingInboundItemConnection` |

### Workflow Service

| Type       | Field             | Connection Type            |
| ---------- | ----------------- | -------------------------- |
| `Workflow` | `workflowSignals` | `WorkflowSignalConnection` |

## Connection Object Structure

All Connection types follow the Relay specification with this structure:

```graphql
type ExampleConnection {
  """A list of edges."""
  edges: [ExampleEdge]

  """Information to aid in pagination."""
  pageInfo: PageInfo!

  """Identifies the total count of items in the connection."""
  totalCount: Int!
}

type ExampleEdge {
  """The item at the end of the edge."""
  node: Example

  """A cursor for use in pagination."""
  cursor: Cursor!
}

type PageInfo {
  """When paginating forwards, are there more items?"""
  hasNextPage: Boolean!

  """When paginating backwards, are there more items?"""
  hasPreviousPage: Boolean!

  """When paginating backwards, the cursor to continue."""
  startCursor: Cursor

  """When paginating forwards, the cursor to continue."""
  endCursor: Cursor
}
```

## Migration Steps

### Step 1: Update Query Structure

Change your queries to use the `edges { node { ... } }` pattern.

**Before:**

```graphql
query GetGroupUsers {
  group(id: "group-uuid") {
    id
    name
    users {
      id
      username
      email
    }
  }
}
```

**After:**

```graphql
query GetGroupUsers {
  group(id: "group-uuid") {
    id
    name
    users {
      edges {
        node {
          id
          username
          email
        }
      }
      totalCount
    }
  }
}
```

### Step 2: Update Response Handling

Update your client code to handle the new response structure.

**Before (JavaScript):**

```javascript
const users = response.data.group.users;
users.forEach(user => {
  console.log(user.username);
});
```

**After (JavaScript):**

```javascript
const usersConnection = response.data.group.users;
const users = usersConnection.edges.map(edge => edge.node);
users.forEach(user => {
  console.log(user.username);
});

// Or access totalCount
console.log(`Total users: ${usersConnection.totalCount}`);
```

### Step 3: Implement Pagination (Optional)

Take advantage of the new pagination capabilities.

```graphql
query GetGroupUsersWithPagination($groupId: ID!, $first: Int, $after: Cursor) {
  group(id: $groupId) {
    id
    name
    users(first: $first, after: $after) {
      edges {
        node {
          id
          username
          email
        }
        cursor
      }
      pageInfo {
        hasNextPage
        endCursor
      }
      totalCount
    }
  }
}
```

**Pagination helper (JavaScript):**

```javascript
async function fetchAllUsers(groupId) {
  const allUsers = [];
  let hasNextPage = true;
  let cursor = null;

  while (hasNextPage) {
    const response = await client.query({
      query: GET_GROUP_USERS,
      variables: { groupId, first: 50, after: cursor }
    });

    const connection = response.data.group.users;
    allUsers.push(...connection.edges.map(e => e.node));

    hasNextPage = connection.pageInfo.hasNextPage;
    cursor = connection.pageInfo.endCursor;
  }

  return allUsers;
}
```

### Step 4: Add Filtering and Ordering (Optional)

Use the new filtering and ordering capabilities on relationship fields.

```graphql
query GetGroupActiveUsers($groupId: ID!) {
  group(id: $groupId) {
    users(
      first: 10
      orderBy: { direction: ASC, field: USERNAME }
      where: { isAdmin: false }
    ) {
      edges {
        node {
          id
          username
          email
        }
      }
      totalCount
    }
  }
}
```

## Complete Migration Examples

### Example 1: InventoryItemSet with Items

**Before:**

```graphql
query {
  inventoryItemSets(first: 10) {
    edges {
      node {
        id
        sku
        items {
          id
          sku
          data
        }
      }
    }
  }
}
```

**After:**

```graphql
query {
  inventoryItemSets(first: 10) {
    edges {
      node {
        id
        sku
        items(first: 100) {
          edges {
            node {
              id
              sku
              data
            }
          }
          totalCount
        }
      }
    }
  }
}
```

### Example 2: ReceivingInbound with InboundItems

**Before:**

```graphql
query {
  receivingInbounds(first: 10) {
    edges {
      node {
        id
        orderID
        inboundItems {
          sku
          quantity
        }
      }
    }
  }
}
```

**After:**

```graphql
query {
  receivingInbounds(first: 10) {
    edges {
      node {
        id
        orderID
        inboundItems {
          edges {
            node {
              sku
              quantity
            }
          }
          totalCount
        }
      }
    }
  }
}
```

### Example 3: Workflow with Signals

**Before:**

```graphql
query {
  workflows(first: 10) {
    edges {
      node {
        id
        name
        workflowSignals {
          id
          natsTopic
          temporalSignalType
        }
      }
    }
  }
}
```

**After:**

```graphql
query {
  workflows(first: 10) {
    edges {
      node {
        id
        name
        workflowSignals {
          edges {
            node {
              id
              natsTopic
              temporalSignalType
            }
          }
          totalCount
        }
      }
    }
  }
}
```

### Example 4: Role with Users, Groups, and Policies

**Before:**

```graphql
query {
  roles(first: 10) {
    edges {
      node {
        id
        name
        users {
          id
          username
        }
        groups {
          id
          name
        }
        policies {
          id
          resource
          action
        }
      }
    }
  }
}
```

**After:**

```graphql
query {
  roles(first: 10) {
    edges {
      node {
        id
        name
        users {
          edges {
            node {
              id
              username
            }
          }
          totalCount
        }
        groups {
          edges {
            node {
              id
              name
            }
          }
          totalCount
        }
        policies {
          edges {
            node {
              id
              resource
              action
            }
          }
          totalCount
        }
      }
    }
  }
}
```

## Benefits of This Change

1. **Consistent Pagination**: All relationship fields now support cursor-based pagination with `first`, `last`, `before`, and `after` arguments.
2. **Filtering on Relationships**: Apply WhereInput filters directly on relationship fields to reduce data transfer.
3. **Ordering on Relationships**: Order related items using OrderBy inputs on the relationship itself.
4. **Total Count**: Always know how many related items exist with the `totalCount` field.
5. **Relay Compliance**: Full compliance with the [Relay Cursor Connections Specification](https://relay.dev/graphql/connections.htm).

## Troubleshooting

### Error: Cannot query field "edges" on type "\[User!]"

This error indicates you are using the old query structure. Update your query to use the Connection pattern:

```graphql
# Wrong
users { id }

# Correct
users { edges { node { id } } }
```

### Error: Cannot query field "totalCount"

Ensure you are querying `totalCount` at the Connection level, not inside edges or node:

```graphql
# Wrong
users { edges { totalCount } }

# Correct
users {
  edges { node { id } }
  totalCount
}
```

### Performance Considerations

When fetching nested relationships, always use the `first` argument to limit results:

```graphql
# Recommended: Limit nested connections
inventoryItemSets(first: 10) {
  edges {
    node {
      items(first: 50) {  # Limit nested items
        edges { node { id } }
      }
    }
  }
}
```

## Related Documentation

* [PageInfo Type](https://github.com/pyck-ai/docs/blob/main/development/graphql/types/PageInfo.md)
* [Relay Cursor Connections Specification](https://relay.dev/graphql/connections.htm)
* [GraphQL Getting Started](https://github.com/pyck-ai/docs/blob/main/development/graphql/getting-started.md)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.pyck.cloud/development/migrations/relay-connections.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
