BrunoLM
BrunoLM

Reputation: 100361

How to use TypeScript to map database schema and models?

I have a database table called student.

id | name | createdAt  | deleted
--------------------------------
1  | foo  | 2017-01-13 | false

When I retrieve values from the database I will get values mapped to the correct type. I have an interface for this table:

interface Student {
  id: number;
  name: string;
  createdAt: Date;
  deleted: boolean;
}

To grab values from the database I'm doing something like:

await knex('student').where('deleted', false);

I'm thinking how to replace the hard-coded strings to refer to the table/columns, so it will be possible to rename/remove columns and detect issues in compile time rather then runtime. I created an object like:

const tables = { student: 'student' };
const cols = {
  id: 'id',
  name: 'name',
  createdAt: 'createdAt',
  deleted: 'deleted',
};

await knex(tables.student).where(cols.deleted, false);

It works. But the problem with this approach is that if someone change the model interface (Student) and forget to change the cols object it is still going to work on compile time.

If I do const cols: Student it would validate all columns, but the type for all columns on the cols object should be a string.

Is there a way I can do this? Maybe from this line of though or maybe in a completely different approach?

Upvotes: 1

Views: 3388

Answers (2)

8192K
8192K

Reputation: 5290

I can imagine a map-like structure like the following

interface MetaDataDescription {
    dbName: string;
    type: any;
}

class StudentMetaData {
    static ID: MetaDataDescription = {dbName: 'id', type: number};
    static NAME: MetaDataDescription = {dbName: 'name', type: string};
    //etc...
}

Then you would at runtime generate your interface Student from the data (dbName for the field name and typefor the field type) given in StudentMetaData. (and likewise for all other models)

Accessing data in this generic interface would be done like genericInstance[StudentMetaData.ID.dbName] keeping compile time integrity.

In the query you would then write await knex(tables.student).where(StudentMetaData.DELETED.dbName, false);, also keeping compile time checks..

Any change to the whole structure would be in a single point in the metadata class(es),

Upvotes: 1

Simon Meskens
Simon Meskens

Reputation: 968

Really easy to fix with the new 2.1.0 features.

const tables = { student: 'student' };
const cols: { [P in keyof Student]: string } = {
  id: 'id',
  name: 'name',
  createdAt: 'createdAt',
  deleted: 'deleted',
};

await knex(tables.student).where(cols.deleted, false);

This will complain if you change Student, but not the cols object.

Upvotes: 2

Related Questions