K
Kysely•15mo ago
NazCodeland

Making Transactions with KyselyAdapter for auth.js

Hey everyone, I am using this KyselyAdapter package for auth.js https://github.com/nextauthjs/next-auth/pull/5464 I made slight changes since I am in a SvetleKit project and it works! This package returns and object of functions, each function creates, updates, deletes a record in the database. I am not sure how I would go about using these functions to create a transaction. Prior to using this package, I wrote my own KyselyAdapter for auth.js but in ( for it wasn't working in some situation, so I switched ) but this is how I wrote a transaction using my own KyselyAdapter
export async function createAccount(
user: NewUser,
profile: NewProfile,
address: NewAddress,
business: NewBusiness
) {
try {
const result = await DB.transaction().execute(async (trx) => {
const newUser = await createUser(user, trx);
const newProfile = await createProfile({ ...profile, userId: newUser.id }, trx);
// prettier-ignore
const newBusiness = await createBusiness({ ...business, userId: newUser.id }, trx);

// prettier-ignore
const newBusinessAddress = await createAddress({ ...address, businessId: newBusiness.id}, trx);
// prettier-ignore
const newProfileAddress = await createAddress({ profileId: newProfile.id, }, trx);
return { newUser, newProfile, newBusiness, newBusinessAddress, newProfileAddress };
});
return result;
} catch (error) {
// Handle the error here
console.error(error);
}
}
export async function createAccount(
user: NewUser,
profile: NewProfile,
address: NewAddress,
business: NewBusiness
) {
try {
const result = await DB.transaction().execute(async (trx) => {
const newUser = await createUser(user, trx);
const newProfile = await createProfile({ ...profile, userId: newUser.id }, trx);
// prettier-ignore
const newBusiness = await createBusiness({ ...business, userId: newUser.id }, trx);

// prettier-ignore
const newBusinessAddress = await createAddress({ ...address, businessId: newBusiness.id}, trx);
// prettier-ignore
const newProfileAddress = await createAddress({ profileId: newProfile.id, }, trx);
return { newUser, newProfile, newBusiness, newBusinessAddress, newProfileAddress };
});
return result;
} catch (error) {
// Handle the error here
console.error(error);
}
}
I realize the purpose of https://github.com/nextauthjs/next-auth/pull/5464 is to be used with auth.js - and it serves that objective But for me to write a transaction like the above code snippet, I would have to modify that packages code - which I don't want to do
5 Replies
NazCodeland
NazCodeland•15mo ago
ideally, I would like to have transactions that use the exact create method defined inside of KyselyAdapter.ts
async createUser(data) {
const userData = coerceInputData(data, 'emailVerified');
userData.role = 'client';
const query = db.insertInto('User').values(userData);
const result = supportsReturning
? await query.returningAll().executeTakeFirstOrThrow()
: await query.executeTakeFirstOrThrow().then(async () => {
return await db
.selectFrom('User')
.selectAll()
.where('email', '=', `${userData.email}`)
.executeTakeFirstOrThrow();
});
return coerceReturnData(result, 'emailVerified');
},
async createUser(data) {
const userData = coerceInputData(data, 'emailVerified');
userData.role = 'client';
const query = db.insertInto('User').values(userData);
const result = supportsReturning
? await query.returningAll().executeTakeFirstOrThrow()
: await query.executeTakeFirstOrThrow().then(async () => {
return await db
.selectFrom('User')
.selectAll()
.where('email', '=', `${userData.email}`)
.executeTakeFirstOrThrow();
});
return coerceReturnData(result, 'emailVerified');
},
this way, I don't have to have that code in two places. If that's not possible, I am thinking of defining another file that contains all those functions and then imports it into KyselyAdapter > which exports an object of all those functions and that way I use those same individual functions to create transactions or should I make a transactions file and import KyselyAdapter in there and define all the transction functions in that file using the KyselyAdapter functions
Igal
Igal•15mo ago
Hey 👋 If you find something missing in that PR, worth adding your review comments. I have low hopes for that PR ever being merged at this point, but its a good reference point for others. I see the authjs adapter is a function that receives a Kysely instance and returns that object that performs queries on given Kysely instance. Well, a Transaction extends Kysely, so you should be able to pass it to the adapter function, and receive an object bound to the transaction. Something like:
export async function createAccount(
user: NewUser,
profile: NewProfile,
address: NewAddress,
business: NewBusiness
) {
try {
const result = await DB.transaction().execute(async (trx) => {
const AuthJsAdapter = KyselyAdapter(trx);

const newUser = await AuthJsAdapter.createUser(user);
const newProfile = await createProfile({ ...profile, userId: newUser.id }, trx);
// prettier-ignore
const newBusiness = await createBusiness({ ...business, userId: newUser.id }, trx);

// prettier-ignore
const newBusinessAddress = await createAddress({ ...address, businessId: newBusiness.id}, trx);
// prettier-ignore
const newProfileAddress = await createAddress({ profileId: newProfile.id, }, trx);
return { newUser, newProfile, newBusiness, newBusinessAddress, newProfileAddress };
});
return result;
} catch (error) {
// Handle the error here
console.error(error);
}
}
export async function createAccount(
user: NewUser,
profile: NewProfile,
address: NewAddress,
business: NewBusiness
) {
try {
const result = await DB.transaction().execute(async (trx) => {
const AuthJsAdapter = KyselyAdapter(trx);

const newUser = await AuthJsAdapter.createUser(user);
const newProfile = await createProfile({ ...profile, userId: newUser.id }, trx);
// prettier-ignore
const newBusiness = await createBusiness({ ...business, userId: newUser.id }, trx);

// prettier-ignore
const newBusinessAddress = await createAddress({ ...address, businessId: newBusiness.id}, trx);
// prettier-ignore
const newProfileAddress = await createAddress({ profileId: newProfile.id, }, trx);
return { newUser, newProfile, newBusiness, newBusinessAddress, newProfileAddress };
});
return result;
} catch (error) {
// Handle the error here
console.error(error);
}
}
NazCodeland
NazCodeland•15mo ago
Hey! 🫡 I haven't tried it yet but that code makes sense to me. I was thinking of using those functions and putting them in a file and exporting them individually and that way i can import them inside the KyselyAdapter and use them there (or use some of those functions with my own adapter I had) - Individual Funcitons - KyselyAdataper or as you suggested, I can just make a new instance of the KyselyAdapter
const AuthJsAdapter = KyselyAdapter(trx);
const AuthJsAdapter = KyselyAdapter(trx);
and use that for the transaction and if the transaction fails I just need to make sure the instance is cleaned out of memory I was slightly against that at first because this would import the whole module for the KyselyAdatper even if I only want to use 1 function from all the availabe methods 1) single functions Pros: it gives more modularity but, is it useful to my case? ( I don't think this extra flexibility provides additional usefulness to my case )
Cons: Little bit of redundancy will be created inside of KyselyAdapter as it will contain functions to just call the equivalant individual functions
2) creating an instance of KyselyAdapter Pros: one line of code!
Cons: Have to make sure KyselyAdapter is removed from memory ( or if the transaction fails, does the memory space where the instance of the KyselyAdapter live get cleaned up too? ) ya, gonna go with 2 thank you for the input
Igal
Igal•15mo ago
the GC will handle it, just gotta make sure nothing references it as soon as its not of use to you the usual stuff, if its assigned to a long-lived state variable, assign undefined. if its stored in a Map or Set, use WeakMap or WeakSet, etc.
NazCodeland
NazCodeland•15mo ago
ok perfect once again, thank you @Igal
Want results from more Discord servers?
Add your server