lucia-auth / lucia

Authentication, simple and clean
https://lucia-auth.com
MIT License
8.42k stars 449 forks source link

[Bug]: `userId: number` cannot be inferred by DrizzlePostgreSQLAdapter #1491

Closed Polluxs closed 3 months ago

Polluxs commented 3 months ago

Package

​@lucia-auth/session-drizzle

Describe the bug

Package

​@lucia-auth/adapter-postgresql

Version using

3.1.1

Impact

Low: TS can just be ignored

Explanation

When changing the UserId type to a number that is possible since https://github.com/lucia-auth/lucia/pull/1472. And then changing the correlating types:

declare module 'lucia' {
    interface Register {
        Lucia: typeof lucia;
        UserId: number; // <- number instead of string
        DatabaseUserAttributes: Omit<SelectUser, 'id'>;
    }
}
export const user_table = pgTable('auth_user', {
    id: serial('id').primaryKey(),
    email: varchar('email', { length: 320 }).notNull().unique(),
    email_verified: boolean('email_verified').notNull().default(false)
});

export const session_table = pgTable('auth_session', {
    id: varchar('id', {
        length: 255
    }).primaryKey(),
    userId: integer('user_id')
        .notNull()
        .references(() => user_table.id),
    expiresAt: timestamp('expires_at', {
        withTimezone: true,
        mode: 'date'
    }).notNull()
});

Creates a typescript error on:

const adapter = new DrizzlePostgreSQLAdapter(db, session_table, user_table);

image

Changing the type back from

userId: integer("user_id")

To

userId: varchar("user_id")

clears the error message, but then I can't use a number as userId.

Workaround

// @ts-expect-error: drizzle adapter expects a { userId: number }
const adapter = new DrizzlePostgreSQLAdapter(db, session_table, user_table);

Solution

I'm not that good with typescript so there probably is a way to infer this, otherwise, I think changing this should do it:

export type PostgreSQLSessionTable = PgTableWithColumns<{
    dialect: "pg";
    columns: {
        id: PgColumn<{
            dataType: any;
            notNull: true;
            enumValues: any;
            tableName: any;
            columnType: any;
            data: string;
            driverParam: any;
            hasDefault: false;
            name: any;
        }, object>;
        expiresAt: PgColumn<{
            dataType: any;
            notNull: true;
            enumValues: any;
            tableName: any;
            columnType: any;
            data: Date;
            driverParam: any;
            hasDefault: false;
            name: any;
        }, object>;
        userId: PgColumn<{
            dataType: any;
            notNull: true;
            enumValues: any;
            tableName: any;
            columnType: any;
            data: string; // <- NEW: data: string | number;
            driverParam: any;
            hasDefault: false;
            name: any;
        }, object>;
    };
    schema: any;
    name: any;
}>;

// also for user table
export type PostgreSQLUserTable = PgTableWithColumns<{
    dialect: "pg";
    columns: {
        id: PgColumn<{
            name: any;
            tableName: any;
            dataType: any;
            columnType: any;
            data: string; // <- NEW: data: string | number;
            driverParam: any;
            notNull: true;
            hasDefault: boolean;
            enumValues: any;
            baseColumn: any;
        }, object>;
    };
    schema: any;
    name: any;
}>;

Let me know if I can help :)


Full error: Argument of type 'PgTableWithColumns<{ name: "auth_session"; schema: undefined; columns: { id: PgColumn<{ name: "id"; tableName: "auth_session"; dataType: "string"; columnType: "PgVarchar"; data: string; driverParam: string; notNull: true; hasDefault: false; enumValues: [...]; baseColumn: never; }, {}, {}>; userId: PgColumn<...>; exp...' is not assignable to parameter of type 'PostgreSQLSessionTable'. Type 'PgTableWithColumns<{ name: "auth_session"; schema: undefined; columns: { id: PgColumn<{ name: "id"; tableName: "auth_session"; dataType: "string"; columnType: "PgVarchar"; data: string; driverParam: string; notNull: true; hasDefault: false; enumValues: [...]; baseColumn: never; }, {}, {}>; userId: PgColumn<...>; exp...' is not assignable to type 'PgTable<{ dialect: "pg"; columns: { id: PgColumn<{ dataType: any; notNull: true; enumValues: any; tableName: any; columnType: any; data: string; driverParam: any; hasDefault: false; name: any; }, object, {}>; expiresAt: PgColumn<...>; userId: PgColumn<...>; }; schema: any; name: any; }>'. The types of '_.config.columns.userId' are incompatible between these types. Type 'PgColumn<{ name: "user_id"; tableName: "auth_session"; dataType: "number"; columnType: "PgInteger"; data: number; driverParam: string | number; notNull: true; hasDefault: false; enumValues: undefined; baseColumn: never; }, {}, {}>' is not assignable to type 'PgColumn<{ dataType: any; notNull: true; enumValues: any; tableName: any; columnType: any; data: string; driverParam: any; hasDefault: false; name: any; }, object, {}>'. Type '{ name: "user_id"; tableName: "auth_session"; dataType: "number"; columnType: "PgInteger"; data: number; driverParam: string | number; notNull: true; hasDefault: false; enumValues: undefined; baseColumn: never; }' is not assignable to type '{ dataType: any; notNull: true; enumValues: any; tableName: any; columnType: any; data: string; driverParam: any; hasDefault: false; name: any; }'. Types of property 'data' are incompatible. Type 'number' is not assignable to type 'string'.

pilcrowOnPaper commented 3 months ago

What version of the adapter are you using?

oscarhermoso commented 3 months ago

@Polluxs - I don't think your suggestion is correct:

        userId: PgColumn<{
            dataType: any;
            notNull: true;
            enumValues: any;
            tableName: any;
            columnType: any;
            data: string; // <- NEW: data: string | number;
            driverParam: any;
            hasDefault: false;
            name: any;
        }, object>;

This is already updated in the latest source code:

https://github.com/lucia-auth/lucia/blob/5d1cfa8a2b4375f03c1f5dc9a5ecc0eb86299665/packages/adapter-drizzle/src/drivers/postgresql.ts#L143

Mine is working fine right now (although with bigint/bigserial instead of int/serial):

{
    "dependencies": {
        "oslo": "^1.1.3"
    },
    "devDependencies": {
        "arctic": "^1.2.1",
        "drizzle-orm": "^0.29.3",
        "@lucia-auth/adapter-drizzle": "^1.0.4",
        "lucia": "^3.1.1",
        "postgres": "^3.3.5",
    }
}
// schema.ts

export const userTable = pgTable(
    'auth_user',
    {
        id: bigserial('internal_id', { mode: 'number' }).primaryKey(),
        // other properties
    }
);

export const sessionTable = pgTable('user_session', {
    id: varchar('id', {
        length: 128,
    }).primaryKey(),

    userId: bigint('user_id', { mode: 'number' }).notNull().references(() => userTable.id)

    // other properties
});

Type definition of userId:

userId: PgColumn<{
            name: "user_id_2";
            tableName: "user_session";
            dataType: "number";
            columnType: "PgBigInt53";
            data: number;
            driverParam: string | number;
            notNull: true;
            hasDefault: false;
            enumValues: undefined;
            baseColumn: never;
        }, {}, {}>;
// auth.ts
interface DatabaseUserAttributes extends InferSelectModel<typeof userTable> {}
interface DatabaseSessionAttributes
    extends Omit<
        InferSelectModel<typeof sessionTable>,
        // Omit fields that are always present
        | 'id'
        | 'userId'
        | 'expiresAt'
    > {}

declare module 'lucia' {
    interface Register {
        Lucia: typeof auth;
        UserId: number;
        DatabaseUserAttributes: DatabaseUserAttributes;
        DatabaseSessionAttributes: DatabaseSessionAttributes;
    }
}
Polluxs commented 3 months ago

lucia/packages/adapter-drizzle/src/drivers/postgresql.ts

Thanks for pointing that out. I upgraded the @lucia-auth/adapter-drizzle from 1.0.3 to 1.0.4 + reload which now uses UserId. (and resolves it)

Thanks to everyone for looking!