# @theorchard/utils-dataloader

Utilities used for building dataloaders.

### `groupFetchAndUngroup`

This utility is useful for implementing complex dataloaders where there are additional parameters that are likely to apply across an entire group of requests at once, and where they simplify the underlying endpoint implementation.

For example, if you are implementing an endpoint that supports filtering or ordering for the results it may be beneficial to use this utility.

It is particularly of use where the fields that are per-batch are inputs to a field in GraphQL.

For example, if we have the following GraphQL schema:

```graphql
type Query {
    as: [A!]!
}

type A {
    id: ID!
    b(orderBy: string): [B!]!
}

type B {
    id: ID!
}
```

The `A.b` field is likely to be suitable candidate for this dataloader.

For example, if we have an underlying dataloader endpoint that works something along the lines of:

```
POST /a/bs-dataloaded

{
    "orderBy": "id",
    "ids": [1, 2, 3, 4]
}
```

And responds with

```
HTTP/1.1 200 OK

[
    [2, 3, 4],
    [5, 6, 7],
    [1, 8, 9],
    []
]
```

It may look like we can write the dataloader for `A.b` to do something like:

```ts
interface Input {
    orderBy: string;
    id: number;
}

const bIdsDataLoader = new DataLoader<Input, number[]>(
    async (batch) => {
        const orderBy = batch[0].orderBy;
        const ids = batch.map(({ id }) => id);
        const data = // call the dataloader endpoint
        return data;
    }
);

async bIds(id, orderBy) {
    return bIdsDataLoader.load({
        orderBy,
        id,
    });
}
```

But, this assumes that all members of the group have the same `orderBy` key.

This may not always be the case. In particular, it is possible to write a client query that uses the same field twice with different input arguments:

```graphql
query {
    as {
        bsById: b(orderBy: "id") {
            id
        }
        bsByName: b(orderBy: "name") {
            id
        }
    }
}
```

This utility helps in this case by grouping the dataloader batch in to smaller batches that share the same common parameters. In this case, you can do something like:

```ts

interface Input {
    orderBy: string;
    id: number;
}

const bIdsDataLoader = new DataLoader<Input, number[]>(
    (batch) => groupFetchAndUngroup(
        batch,
        ({ orderBy, id }) => [{ orderBy }, id],
        async ({ orderBy }, ids) => {
            const data = // call the dataloader endpoint
            return data;
        },
    )
);

async bIds(id, orderBy) {
    return bIdsDataLoader.load({
        orderBy,
        id,
    });
}
```
