Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(kysely): interface mismatch with default schema and kysely schema rules #10449

Closed
wants to merge 3 commits into from

Conversation

AchalS-iglu
Copy link

☕️ Reasoning

The adapter was using core types for verifying database schema. Kysely schema doesn't follow standard typing rules, it uses things like GeneratedAlways, ColumnType, etc.

The new types automatically align with the adapter types thanks to how kysely works.

The Database interface has been replaced with DatabaseSchema interface that is pretty much the same thing.

This is also more in line with the documentation

I had also opened an issue for the same - #10441

🧢 Checklist

  • [ Y] Documentation
  • [ N] Tests
  • [ Y] Ready to be merged

🎫 Affected issues

#10441

📌 Resources

Copy link

vercel bot commented Mar 31, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
auth-docs ✅ Ready (Inspect) Visit Preview 💬 Add feedback Apr 8, 2024 9:47pm
1 Ignored Deployment
Name Status Preview Comments Updated (UTC)
next-auth-docs ⬜️ Ignored (Inspect) Visit Preview Apr 8, 2024 9:47pm

Copy link

vercel bot commented Mar 31, 2024

@AchalS-iglu is attempting to deploy a commit to the authjs Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions github-actions bot added adapters Changes related to the core code concerning database adapters kysely labels Mar 31, 2024
@AchalS-iglu
Copy link
Author

UPDATE: Still some errors.

@AchalS-iglu
Copy link
Author

Yeaah,,, I cannot figure it out, I must be doing something wrong, If I type the KyselyAuth to use Schema it breaks the KyselyAdapter

@ndom91 ndom91 changed the title Fix(Adapter): Database interface mismatch with provided schema in docs and kysely schema rules fix(kysely): interface mismatch with default schema and kysely schema rules Apr 8, 2024
@AchalS-iglu
Copy link
Author

AYO, don't merge undo it, this will break the existing systems, I will send a new commit fixing a few issues soon @ndom91 I am sorry I am not used to PRs I should've closed this or something

@AchalS-iglu
Copy link
Author

I have given up on figuring out how to work with modules and packages for now, I tried running tests but it was taking too long. I have chosen to make a custom adapter instead since I have to quickly finish my project. Here is the adapter for reference, I am closing this PR.

/* eslint-disable @typescript-eslint/no-unnecessary-type-assertion */
/* eslint-disable @typescript-eslint/unbound-method */
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unused-vars */
import { Database, KyselyAuth } from "@auth/kysely-adapter";
import { Generated, GeneratedAlways, SqliteAdapter } from "kysely";
import { Kysely } from "kysely";
import { Adapter } from "next-auth/adapters";

const isoDateRE =
  /(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))/;
function isDate(value: any) {
  return value && isoDateRE.test(value) && !isNaN(Date.parse(value));
}

const format = {
  from<T>(object?: Record<string, any>): T {
    const newObject: Record<string, unknown> = {};
    for (const key in object) {
      const value = object[key];
      if (isDate(value)) newObject[key] = new Date(value);
      else newObject[key] = value;
    }
    return newObject as T;
  },
  to<T>(object: Record<string, any>): T {
    const newObject: Record<string, unknown> = {};
    for (const [key, value] of Object.entries(object))
      newObject[key] = value instanceof Date ? value.toISOString() : value;
    return newObject as T;
  },
};

export default function KyselyAdapter(db: Kysely<Database>): Adapter {
  const { adapter } = db.getExecutor();
  const { supportsReturning } = adapter;
  const isSqlite = adapter instanceof SqliteAdapter;
  /** If the database is SQLite, turn dates into an ISO string  */
  const to = isSqlite ? format.to : <T>(x: T) => x as T;
  /** If the database is SQLite, turn ISO strings into dates */
  const from = isSqlite ? format.from : <T>(x: T) => x as T;
  return {
    async createUser(data) {
      const user = { ...data, id: crypto.randomUUID() };
      await db.insertInto("User").values(to(user)).executeTakeFirstOrThrow();
      return user;
    },
    async getUser(id) {
      const result = await db
        .selectFrom("User")
        .selectAll()
        .where("id", "=", id)
        .executeTakeFirst();
      if (!result) return null;
      return from(result);
    },
    async getUserByEmail(email) {
      const result = await db
        .selectFrom("User")
        .selectAll()
        .where("email", "=", email)
        .executeTakeFirst();
      if (!result) return null;
      return from(result);
    },
    async getUserByAccount({ providerAccountId, provider }) {
      const result = await db
        .selectFrom("User")
        .innerJoin("Account", "User.id", "Account.userId")
        .selectAll("User")
        .where("Account.providerAccountId", "=", providerAccountId)
        .where("Account.provider", "=", provider)
        .executeTakeFirst();
      if (!result) return null;
      return from(result);
    },
    async updateUser({ id, ...user }) {
      const userData = to(user);
      const query = db.updateTable("User").set(userData).where("id", "=", id);
      const result = supportsReturning
        ? query.returningAll().executeTakeFirstOrThrow()
        : query
            .executeTakeFirstOrThrow()
            .then(() =>
              db
                .selectFrom("User")
                .selectAll()
                .where("id", "=", id)
                .executeTakeFirstOrThrow(),
            );
      return from(await result);
    },
    async deleteUser(userId) {
      await db
        .deleteFrom("User")
        .where("User.id", "=", userId)
        .executeTakeFirst();
    },
    async linkAccount(account) {
      await db
        .insertInto("Account")
        .values(to(account))
        .executeTakeFirstOrThrow();
      return account;
    },
    async unlinkAccount({ providerAccountId, provider }) {
      await db
        .deleteFrom("Account")
        .where("Account.providerAccountId", "=", providerAccountId)
        .where("Account.provider", "=", provider)
        .executeTakeFirstOrThrow();
    },
    async createSession(session) {
      await db.insertInto("Session").values(to(session)).execute();
      return session;
    },
    async getSessionAndUser(sessionToken) {
      const result = await db
        .selectFrom("Session")
        .innerJoin("User", "User.id", "Session.userId")
        .selectAll("User")
        .select(["Session.expires", "Session.userId"])
        .where("Session.sessionToken", "=", sessionToken)
        .executeTakeFirst();
      if (!result) return null;
      const { userId, expires, ...user } = result;
      const session = { sessionToken, userId, expires };
      return { user: from(user), session: from(session) };
    },
    async updateSession(session) {
      const sessionData = to(session);
      const query = db
        .updateTable("Session")
        .set(sessionData)
        .where("Session.sessionToken", "=", session.sessionToken);
      const result = supportsReturning
        ? await query.returningAll().executeTakeFirstOrThrow()
        : await query.executeTakeFirstOrThrow().then(async () => {
            return await db
              .selectFrom("Session")
              .selectAll()
              .where("Session.sessionToken", "=", sessionData.sessionToken)
              .executeTakeFirstOrThrow();
          });
      return from(result);
    },
    async deleteSession(sessionToken) {
      await db
        .deleteFrom("Session")
        .where("Session.sessionToken", "=", sessionToken)
        .executeTakeFirstOrThrow();
    },
    async createVerificationToken(data) {
      await db.insertInto("VerificationToken").values(to(data)).execute();
      return data;
    },
    async useVerificationToken({ identifier, token }) {
      const query = db
        .deleteFrom("VerificationToken")
        .where("VerificationToken.token", "=", token)
        .where("VerificationToken.identifier", "=", identifier);

      const result = supportsReturning
        ? await query.returningAll().executeTakeFirst()
        : await db
            .selectFrom("VerificationToken")
            .selectAll()
            .where("token", "=", token)
            .executeTakeFirst()
            .then(async (res) => {
              await query.executeTakeFirst();
              return res;
            });
      if (!result) return null;
      return from(result);
    },
  };
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
adapters Changes related to the core code concerning database adapters kysely
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants