Skip to content

Optional relations #8

@allardmuis

Description

@allardmuis

Issue type:

[X] Question
[ ] Bug report
[X] Feature request
[X] Documentation issue

Database system/driver:

[X] Postgres

typed-knex version:

[X] 2.8.1 (or put your version here)

Knex.js version:
0.20.3

Steps to reproduce or a small repository showing the problem:

Hi Wouter,

I'm (once more) looking into the Typed Knex project. The current state looks quite nice and I like the new short and concise syntax.
During my first test I ran into a problem, which I think is not solvable with the current version, but might just need a better explanation in the docs.

What I'm trying to achieve is to get optional relations working properly. Suppose the following knex migration:

    await knex.schema.createTable('groups', table => {
        table.uuid('id').primary();
        table.string('name');
    });

    await knex.schema.createTable('users', table => {
        table.uuid('id').primary();
        table.string('name');
        table.uuid('groupId')
            .nullable() // <-- Note the 'nullable()'
            .references('id')
            .inTable('groups');
    });

    const group1 = { id: uuidv4(), name: 'group 1' };
    const user1 = { id: uuidv4(), name: 'user 1', groupId: group1.id };
    const user2 = { id: uuidv4(), name: 'user 2', groupId: null };  // <-- this user does not have a group

    await knex.insert(group1).into('groups');
    await knex.insert(user1).into('users');
    await knex.insert(user2).into('users');

The simplest Type Knex implementation of this model would be:

@Entity('groups')
export class Group {
    @Column({ primary: true })
    public id: string;
    @Column()
    public name: string;
}

@Entity('users')
export class User {
    @Column({ primary: true })
    public id: string;
    @Column()
    public name: string;
    @Column({ name: 'groupId' })
    public group: Group;
}

But then, performing queries on this:

const user1 = await typedKnex.query(User)
    .where(i => i.name, 'user 1')
    .leftOuterJoinColumn(i => i.group)
    .select(i => [i.id, i.name, i.group.id, i.group.name])
    .getFirst();
console.log(user1);
/*
{ id: '3c152f1a-c0d5-4343-984c-c829909db133',
  name: 'user 1',
  group:
   { id: '0663bcc3-fddb-4e18-976e-ae90e12fc3c9', name: 'group 1' } }
Ok, that's fine!
*/

const user2 = await typedKnex.query(User)
    .where(i => i.name, 'user 2')
    .leftOuterJoinColumn(i => i.group)
    .select(i => [i.id, i.name, i.group.id, i.group.name])
    .getFirst();
console.log(user2);
/*
{ id: 'c40bb9b6-12b1-4021-a7fb-44b19495e72c',
  name: 'user 2',
  group: { id: null, name: null } }
Hmm....
*/

The result of user2 is really awkward. The fact that user2.group is an object instead of null is strange, and the typing of this object is incorrect because both fields in this object should be non-nullable.

Is there a way around this? The following does not work:

@Column({ name: 'groupId' })
public group: Group | null; // correct typing, but crashes the query builder

@Column({ name: 'groupId' })
public group?: Group; // somewhat correct typing, but crashes the query builder

Thanks!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions