#Best practices for a singleton service class to inject?

9 messages · Page 1 of 1 (latest)

gritty dawn
#

Hello! I'm currently reworking some database code with new tooling, and I wanted a better structure for injection. Right now, I'm using a static class singleton with an asyncronous init method. However, I'm running into an issue trying to apply the class when I inject it.

The singleton:

// drizzle.db.ts
import { NodePgDatabase, drizzle } from 'drizzle-orm/node-postgres';
import pg from 'pg';
import * as schema from '../drizzle/schema.js';

export type dbType = NodePgDatabase<typeof schema>;

export class DrizzleDb {
    static db: dbType;
    static schema = schema;
    static isInit = false;
    static async init(connectionString: string) {
        //don't double init
        if (this.isInit) return;
        this.isInit = true;
        const client = new pg.Pool({
            connectionString,
        });

        await client.connect();
        this.db = drizzle(client, { schema });
    }
}

All well and good up through there. This works so far. Here's the container for my ORM that takes a DrizzleDb as a parameter:

// Orm.ts
import { NodePgDatabase } from 'drizzle-orm/node-postgres';
import { DrizzleDb } from '../../drizzle.db.js';

export class Orm {
    constructor(private dbService: typeof DrizzleDb) {}
    public get schema() {
        return this.dbService.schema;
    }
    public get db() {
        return this.dbService.db;
    }
}

This seems fine. But the signs of the upcoming error are present when I look at the type of the dbservice properties. The type of this.dbService.schema is DrizzleDb.schema: typeof import("/Users/kobold/Documents/kobold/src/drizzle/schema")

Here's where the issue comes in:

// character.model.ts
import { Orm } from '../Orm.js';
import { zCharacter } from './character.zod.js';
import 'drizzle-orm/pg-core';

export class dCharacter {
    public z: typeof zCharacter;
    // public query:
    constructor(private orm: Orm) {
        this.z = zCharacter;
    }
    public get query() {
        return this.orm.db.query.character;
    }
}

Here, I get an error on the type of public get query()

The inferred type of 'query' cannot be named without a reference to '../../../../../node_modules/drizzle-orm/pg-core/query-builders/query.js'. This is likely not portable. A type annotation is necessary.

I assume that this is because I'm hoisting an "imported type" through the Orm. I imagine it calls back to the Orm's typing of typeof DrizzleDb for the dependency in its constructor. However, I'm not entirely sure how to type that better. Is there some typescript trick here I'm not used to? Is this just a problem of using a static class singleton? Is there a way to do an instantiated version that doesn't have a possible undefined parameter after instantiation and then the init call? I would have gone the non-static method if I was more confident in my types and the ability to call .db without an undefined check every time.

#

I think I figured it out! It was an issue with using typeof schmea with export * as schema from '[...]' and then exporting something with that type to another file. I fixed this by creating a defined export in the file, and exporting a type of that defined export as well

#

hmmm, nope! Never mind. That made the type clearer, but I still get the exact same issue

timber holly
#

FWIW i don't think this is related to your dependency injection setup at all. you'll see the same error with just this:

import { drizzle } from 'drizzle-orm/node-postgres'
import { pgSchema } from 'drizzle-orm/pg-core'
import pg from 'pg'

const schema = { character: pgSchema('whatever').table('character', {}) }
const client = new pg.Pool({ connectionString: '...' })
const db = drizzle(client, { schema })

export class dCharacter {
  public get query() {
    return db.query.character
  }
}
#

hmm, doesn't seem like drizzle exports the RelationalQueryBuilder type that you'd need to explicitly annotate this

#

i guess you can do this, though it's kind of lame:

public get query(): typeof db.query.character {
  return db.query.character
}
#

or with your fancy version:

public get query(): typeof this.orm.db.query.character {
  return this.orm.db.query.character
}
gritty dawn
#

Yup, that fixes it. I don't like doing this much typeof. I hope drizzle releases better helper types soon