kristiandupont / kanel

Generate Typescript types from Postgres
https://kristiandupont.github.io/kanel/
MIT License
829 stars 57 forks source link

Handle circular references between tables, fixes #402 #540

Closed Recursing closed 4 months ago

Recursing commented 4 months ago

Thanks for the great project!

I tried updating an old codebase to Kanel 3, and got hit by #402

Minimal reproducible case:

CREATE TABLE person (
    id BIGINT PRIMARY KEY,
    email TEXT
);

CREATE TABLE email_address (
    email TEXT PRIMARY KEY,
    person_id BIGINT NOT NULL REFERENCES person(id),
    CONSTRAINT email_fk_unique UNIQUE(person_id, email)
);

ALTER TABLE person ADD CONSTRAINT active_email_fkey
FOREIGN KEY (id, email) REFERENCES email_address(person_id, email);

This PR allows us to generate types and unblocks the migration by keeping track of visited columns and giving up on using the referenced type when detecting a circular reference.

The above schema will generate the files:

Person.ts

import { type EmailAddressEmail } from './EmailAddress';

/** Identifier type for public.person */
export type PersonId = number | string & { __brand: 'PersonId' };

/** Represents the table public.person */
export default interface Person {
  id: PersonId;

  email: EmailAddressEmail | null;
}

/** Represents the initializer for the table public.person */
export interface PersonInitializer {
  id: PersonId;

  email?: EmailAddressEmail | null;
}

/** Represents the mutator for the table public.person */
export interface PersonMutator {
  id?: PersonId;

  email?: EmailAddressEmail | null;
}

EmailAddress.ts

import { type PersonId } from './Person';

/** Identifier type for public.email_address */
export type EmailAddressEmail = string & { __brand: 'EmailAddressEmail' };

/** Represents the table public.email_address */
export default interface EmailAddress {
  email: EmailAddressEmail;

  person_id: PersonId;
}

/** Represents the initializer for the table public.email_address */
export interface EmailAddressInitializer {
  email: EmailAddressEmail;

  person_id: PersonId;
}

/** Represents the mutator for the table public.email_address */
export interface EmailAddressMutator {
  email?: EmailAddressEmail;

  person_id?: PersonId;
}
kristiandupont commented 4 months ago

This is awesome, thank you! The build issue is my own mistake, so I will ignore and check manually to make sure formatting and tests are ok. I hope later today or tomorrow, and I'll publish a new version.

kristiandupont commented 4 months ago

Published.

Recursing commented 4 months ago

That's an impressive response time, thank you!