typescriptkysely

Is there a way to flatten many to many joins when using Kysely?


I have the following DB structure in Kysely:

export type User = {
    id: string;
    name: string;
};

export type UserToTeam = {
    userId: string;
    teamId: string;
};

export type Team = {
    id: string;
    name: string;
};

and I'm trying to create a query that returns users with their teams in a nested structure:

const result = await db
    .selectFrom("User")
    .select(["id", "name"])
    .select((eb) => [withTeams(eb)])
    .execute();

function withTeams(eb: ExpressionBuilder<DB, "User">) {
    return jsonArrayFrom(
        eb
            .selectFrom("UserToTeam")
            .select((eb) => [
                jsonArrayFrom(
                    eb
                        .selectFrom("Team")
                        .select(["id", "name"])
                        .whereRef("Team.id", "=", "UserToTeam.teamId"),
                ).as("teams"),
            ])
            .whereRef("UserToTeam.userId", "=", "User.id"),
    ).as("teams");
}

This works, but there is a redundant teams object in the result:

[
    {
        "id": "1fd0a910-8871-41b2-9a42-303e82a47713",
        "name": "Jane Doe",
        "teams": []
    },
    {
        "id": "c7d86c73-091f-44c1-b89a-6f6cb6ba9ed0",
        "name": "John Doe",
        "teams": [
            {
                "teams": [
                    {
                        "id": "07dc9a2c-52c1-4d1d-8c07-ba21dc98f733",
                        "name": "Team 1"
                    }
                ]
            }
        ]
    }
]

Is there a way to flatten this into a single teams object with an array of my selected Teams?


Solution

  • In this case, it looks like you want to select the Team data associated to each User, and not the association itself, which you only need so you're able to know how to join a user to it's teams. So, with that in mind, you could adjust withTeams like so:

    function withTeams(eb: ExpressionBuilder<DB, "User">) {
        return jsonArrayFrom(
            eb
                .selectFrom("Team")
                .innerJoin("UserToTeam", "UserToTeam.teamId", "Team.id")
                .select(["Team.id", "Team.name"])
                .whereRef("UserToTeam.userId", "=", "User.id")
        ).as("teams");
    }