Last active
May 17, 2025 15:27
-
-
Save ahmedrowaihi/715f3b43d786d4bd200c6f932848cb58 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// incomplete - better-auth letta identity based adapter | |
import type { LettaClient } from "@letta-ai/letta-client"; | |
import { Identity, IdentityProperty } from "@letta-ai/letta-client/api"; | |
import { generateId } from "better-auth"; | |
import type { | |
Account, | |
Adapter, | |
BetterAuthOptions, | |
Session, | |
User, | |
Verification, | |
Where, | |
} from "better-auth/types"; | |
const getIdentity = async ( | |
letta: LettaClient, | |
where: Where[], | |
type: string = "email" | |
): Promise<Identity | undefined> => { | |
const value = where.find((clause) => clause.field === type)?.value; | |
if (!value) return undefined; | |
if (type === "email") { | |
return letta.identities | |
.list({ | |
identityType: "user", | |
limit: 1, | |
identifierKey: String(value), | |
}) | |
.then((results) => results[0]); | |
} | |
if (type === "userId") { | |
return letta.identities.retrieve(String(value)); | |
} | |
return undefined; | |
}; | |
const updateIdentityProperties = async ( | |
letta: LettaClient, | |
identityId: string, | |
key: string, | |
value: any | |
): Promise<void> => { | |
await letta.identities.modify(identityId, { | |
properties: [ | |
{ | |
key, | |
value: | |
typeof value === "object" ? JSON.stringify(value) : String(value), | |
type: | |
typeof value === "object" | |
? "json" | |
: typeof value === "boolean" | |
? "boolean" | |
: "string", | |
}, | |
], | |
}); | |
}; | |
const transformers = { | |
user: { | |
transform: (identity: Identity): User => ({ | |
id: identity.id ?? "", | |
email: parseIdentityProperty<string>(identity, "email") ?? "", | |
name: parseIdentityProperty<string>(identity, "name") ?? "", | |
image: parseIdentityProperty<string | null>(identity, "image") ?? null, | |
createdAt: new Date( | |
parseIdentityProperty<string>(identity, "createdAt") ?? | |
new Date().toISOString() | |
), | |
updatedAt: new Date( | |
parseIdentityProperty<string>(identity, "updatedAt") ?? | |
new Date().toISOString() | |
), | |
emailVerified: | |
parseIdentityProperty<boolean>(identity, "emailVerified") ?? false, | |
}), | |
parse: (identity: Identity) => [identity], | |
update: async ( | |
letta: LettaClient, | |
identity: Identity, | |
data: Partial<User> | |
) => { | |
if (!identity.id) throw new Error("Identity not found"); | |
await updateIdentityProperties(letta, identity.id, "user", data); | |
}, | |
}, | |
account: { | |
transform: (account: Account, userId: string): Account => ({ | |
id: account.id, | |
userId, | |
providerId: account.providerId, | |
accountId: account.accountId, | |
password: account.password, | |
createdAt: account.createdAt, | |
updatedAt: account.updatedAt, | |
}), | |
parse: (identity: Identity) => | |
parseIdentityProperty<Account[]>(identity, "accounts") ?? [], | |
update: async ( | |
letta: LettaClient, | |
identity: Identity, | |
accounts: Account[] | |
) => { | |
if (!identity.id) throw new Error("Identity not found"); | |
await updateIdentityProperties(letta, identity.id, "accounts", accounts); | |
}, | |
}, | |
session: { | |
transform: (session: Session, userId: string): Session => ({ | |
id: session.id, | |
userId, | |
expiresAt: session.expiresAt, | |
token: session.token, | |
ipAddress: session.ipAddress, | |
userAgent: session.userAgent, | |
createdAt: session.createdAt, | |
updatedAt: session.updatedAt, | |
}), | |
parse: (identity: Identity) => | |
parseIdentityProperty<Session[]>(identity, "sessions") ?? [], | |
update: async ( | |
letta: LettaClient, | |
identity: Identity, | |
sessions: Session[] | |
) => { | |
if (!identity.id) throw new Error("Identity not found"); | |
await updateIdentityProperties(letta, identity.id, "sessions", sessions); | |
}, | |
}, | |
verification: { | |
transform: (verification: Verification): Verification => ({ | |
id: verification.id, | |
identifier: verification.identifier, | |
value: verification.value, | |
expiresAt: verification.expiresAt, | |
createdAt: verification.createdAt, | |
updatedAt: verification.updatedAt, | |
}), | |
parse: (identity: Identity) => | |
parseIdentityProperty<Verification[]>(identity, "verification") ?? [], | |
update: async ( | |
letta: LettaClient, | |
identity: Identity, | |
verifications: Verification[] | |
) => { | |
if (!identity.id) throw new Error("Identity not found"); | |
await updateIdentityProperties( | |
letta, | |
identity.id, | |
"verification", | |
verifications | |
); | |
}, | |
}, | |
}; | |
const parseIdentityProperty = <T>( | |
identity: Identity, | |
key: string | |
): T | undefined => { | |
const property = identity.properties?.find((p) => p.key === key); | |
try { | |
if (!property?.value && property?.key !== "image") return undefined; | |
if (property?.type === "json") { | |
return JSON.parse(property.value as string) as T; | |
} | |
return property.value as T; | |
} catch (e) { | |
return undefined; | |
} | |
}; | |
const generateInitialProperties = ( | |
data: Omit<User, "id"> | |
): IdentityProperty[] => [ | |
{ key: "email", value: data.email, type: "string" }, | |
{ key: "name", value: data.name || data.email, type: "string" }, | |
{ key: "image", value: data.image || "", type: "string" }, | |
{ key: "emailVerified", value: data.emailVerified, type: "boolean" }, | |
{ key: "createdAt", value: data.createdAt.toISOString(), type: "string" }, | |
{ key: "updatedAt", value: data.updatedAt.toISOString(), type: "string" }, | |
{ key: "accounts", value: JSON.stringify([]), type: "json" }, | |
{ key: "sessions", value: JSON.stringify([]), type: "json" }, | |
{ key: "verification", value: JSON.stringify([]), type: "json" }, | |
]; | |
type AuthModel = "user" | "account" | "session" | "verification"; | |
const isValidModel = (model: string): model is AuthModel => { | |
return ["user", "account", "session", "verification"].includes(model); | |
}; | |
export const lettaAdapter = | |
(letta: LettaClient) => (options: BetterAuthOptions) => { | |
const debug = (message: string, ...args: any[]) => { | |
if (options.logger?.log) { | |
options.logger.log("info", message, ...args); | |
} else { | |
console.log(message, ...args); | |
} | |
}; | |
if (!letta) { | |
throw new Error("Letta adapter requires a Letta client"); | |
} | |
const genId = () => | |
options.advanced?.generateId | |
? options.advanced.generateId({ model: "user" }) | |
: generateId(); | |
const createUserIdentity = async ( | |
data: Omit<User, "id"> | |
): Promise<User> => { | |
const identity = await letta.identities.create({ | |
identifierKey: data.email, | |
name: data.name || data.email, | |
identityType: "user", | |
properties: generateInitialProperties(data), | |
}); | |
return transformers.user.transform(identity); | |
}; | |
const createAccount = async ( | |
userId: string, | |
data: Omit<Account, "id"> | |
): Promise<Account> => { | |
debug("[LettaAdapter] Creating account with userId:", userId); | |
if (!userId) { | |
throw new Error("userId is required"); | |
} | |
try { | |
const identity = await letta.identities.retrieve(userId); | |
debug("[LettaAdapter] Retrieved identity:", { id: identity?.id }); | |
if (!identity || !identity.id) { | |
debug("[LettaAdapter] Identity not found for userId:", userId); | |
throw new Error("Identity not found"); | |
} | |
const identityProperties = identity.properties; | |
if (!identityProperties) { | |
debug( | |
"[LettaAdapter] Identity properties not found for:", | |
identity.id | |
); | |
throw new Error("Identity properties not found"); | |
} | |
const accounts = transformers.account.parse(identity) || []; | |
debug("[LettaAdapter] Existing accounts:", accounts.length); | |
const account: Account = { | |
id: genId(), | |
...data, | |
userId: identity.id, | |
createdAt: new Date(), | |
updatedAt: new Date(), | |
}; | |
debug("[LettaAdapter] Created new account:", { id: account.id }); | |
const existingAccountIndex = accounts.findIndex( | |
(a) => | |
a.providerId === account.providerId && | |
a.accountId === account.accountId | |
); | |
if (existingAccountIndex >= 0) { | |
accounts[existingAccountIndex] = account; | |
debug("[LettaAdapter] Updated existing account"); | |
} else { | |
accounts.push(account); | |
debug("[LettaAdapter] Added new account"); | |
} | |
await transformers.account.update(letta, identity, accounts); | |
debug("[LettaAdapter] Updated identity with new accounts"); | |
return account; | |
} catch (error) { | |
debug("[LettaAdapter] Error creating account:", error); | |
throw error; | |
} | |
}; | |
const createVerification = async ( | |
userId: string, | |
data: Omit<Verification, "id"> | |
): Promise<Verification> => { | |
if (!userId) { | |
throw new Error("userId is required"); | |
} | |
const identity = await getIdentity(letta, [ | |
{ field: "userId", value: userId }, | |
]); | |
if (!identity || !identity.id) { | |
throw new Error("Identity not found"); | |
} | |
const identityProperties = identity.properties; | |
if (!identityProperties) { | |
throw new Error("Identity properties not found"); | |
} | |
const verifications = transformers.verification.parse(identity) || []; | |
const verification: Verification = { | |
id: genId(), | |
value: data.value, | |
identifier: data.identifier, | |
expiresAt: data.expiresAt, | |
createdAt: new Date(), | |
updatedAt: new Date(), | |
}; | |
const existingVerificationIndex = verifications.findIndex( | |
(v) => v.identifier === verification.identifier | |
); | |
if (existingVerificationIndex >= 0) { | |
verifications[existingVerificationIndex] = verification; | |
} else { | |
verifications.push(verification); | |
} | |
await transformers.verification.update(letta, identity, verifications); | |
return verification; | |
}; | |
const createSession = async ( | |
userId: string, | |
data: Omit<Session, "id"> | |
): Promise<Session> => { | |
debug("[LettaAdapter] Creating session with userId:", userId); | |
if (!userId) { | |
throw new Error("userId is required"); | |
} | |
try { | |
const identity = await letta.identities.retrieve(userId); | |
debug("[LettaAdapter] Retrieved identity:", { id: identity?.id }); | |
if (!identity || !identity.id) { | |
debug("[LettaAdapter] Identity not found for userId:", userId); | |
throw new Error("Identity not found"); | |
} | |
const identityProperties = identity.properties; | |
if (!identityProperties) { | |
debug( | |
"[LettaAdapter] Identity properties not found for:", | |
identity.id | |
); | |
throw new Error("Identity properties not found"); | |
} | |
const sessions = transformers.session.parse(identity) || []; | |
debug("[LettaAdapter] Existing sessions:", sessions.length); | |
const ip = | |
data.ipAddress || | |
(data as any).headers?.["x-forwarded-for"]?.split(",")[0]?.trim() || | |
(data as any).headers?.["x-real-ip"] || | |
""; | |
const session: Session = { | |
id: genId(), | |
token: data.token, | |
userId: identity.id, | |
expiresAt: data.expiresAt, | |
ipAddress: ip, | |
userAgent: data.userAgent, | |
createdAt: new Date(), | |
updatedAt: new Date(), | |
}; | |
debug("[LettaAdapter] Created new session:", { id: session.id }); | |
const existingSessionIndex = sessions.findIndex( | |
(s) => s.token === session.token | |
); | |
if (existingSessionIndex >= 0) { | |
sessions[existingSessionIndex] = session; | |
debug("[LettaAdapter] Updated existing session"); | |
} else { | |
sessions.push(session); | |
debug("[LettaAdapter] Added new session"); | |
} | |
await transformers.session.update(letta, identity, sessions); | |
debug("[LettaAdapter] Updated identity with new sessions"); | |
return session; | |
} catch (error) { | |
debug("[LettaAdapter] Error creating session:", error); | |
throw error; | |
} | |
}; | |
return { | |
id: "letta", | |
create: async <T extends Record<string, any>, R = T>({ | |
model, | |
data, | |
select, | |
}: { | |
model: string; | |
data: Omit<T, "id">; | |
select?: string[]; | |
}): Promise<R> => { | |
debug(`[LettaAdapter] create called for model: ${model}`, { | |
data, | |
select, | |
}); | |
if (!isValidModel(model)) { | |
debug(`[LettaAdapter] Invalid model: ${model}`); | |
throw new Error(`Invalid model: ${model}`); | |
} | |
try { | |
if (model === "user") { | |
const user = await createUserIdentity( | |
data as unknown as Omit<User, "id"> | |
); | |
debug(`[LettaAdapter] User created:`, { id: user.id }); | |
return { id: user.id } as R; | |
} else if (model === "account") { | |
const accountData = { | |
...data, | |
createdAt: new Date(), | |
updatedAt: new Date(), | |
providerId: (data as any).providerId, | |
accountId: (data as any).accountId, | |
userId: (data as any).userId, | |
} as Omit<Account, "id">; | |
if (!accountData.userId) { | |
debug(`[LettaAdapter] userId is required for account creation`); | |
throw new Error("userId is required for account creation"); | |
} | |
const account = await createAccount( | |
accountData.userId, | |
accountData | |
); | |
debug(`[LettaAdapter] Account created:`, { id: account.id }); | |
return { id: account.id } as R; | |
} else if (model === "verification") { | |
const verificationData = { | |
value: (data as any).value, | |
identifier: (data as any).identifier, | |
expiresAt: (data as any).expiresAt, | |
createdAt: new Date(), | |
updatedAt: new Date(), | |
} as Omit<Verification, "id">; | |
if (!verificationData.identifier) { | |
debug( | |
`[LettaAdapter] identifier is required for verification creation` | |
); | |
throw new Error( | |
"identifier is required for verification creation" | |
); | |
} | |
const verification = await createVerification( | |
(data as any).userId, | |
verificationData | |
); | |
debug(`[LettaAdapter] Verification created:`, { | |
id: verification.id, | |
}); | |
return { id: verification.id } as R; | |
} else if (model === "session") { | |
const sessionData = { | |
token: (data as any).token, | |
userId: (data as any).userId, | |
expiresAt: (data as any).expiresAt, | |
ipAddress: (data as any).ipAddress, | |
userAgent: (data as any).userAgent, | |
createdAt: new Date(), | |
updatedAt: new Date(), | |
} as Omit<Session, "id">; | |
if (!sessionData.userId) { | |
debug(`[LettaAdapter] userId is required for session creation`); | |
throw new Error("userId is required for session creation"); | |
} | |
const session = await createSession( | |
sessionData.userId, | |
sessionData | |
); | |
debug(`[LettaAdapter] Session created:`, { id: session.id }); | |
return { id: session.id } as R; | |
} | |
return { id: genId() } as R; | |
} catch (error) { | |
debug(`[LettaAdapter] Error in create for model ${model}:`, error); | |
throw error; | |
} | |
}, | |
findOne: async <T>({ | |
model, | |
where, | |
select = [], | |
}: { | |
model: string; | |
where: Where[]; | |
select?: string[]; | |
}): Promise<T | null> => { | |
debug(`[LettaAdapter] findOne called for model: ${model}`, { | |
where, | |
select, | |
}); | |
if (!isValidModel(model)) { | |
debug(`[LettaAdapter] Invalid model: ${model}`); | |
throw new Error(`Invalid model: ${model}`); | |
} | |
try { | |
if (model === "user") { | |
const emailClause = where.find( | |
(clause: Where) => clause.field === "email" | |
); | |
if (!emailClause?.value) { | |
debug(`[LettaAdapter] No email provided for user lookup`); | |
return null; | |
} | |
const identity = await getIdentity(letta, where, "email"); | |
if (!identity) { | |
debug( | |
`[LettaAdapter] No user found for email: ${emailClause.value}` | |
); | |
return null; | |
} | |
const user = transformers.user.transform(identity); | |
debug(`[LettaAdapter] User found:`, { id: user.id }); | |
return user as T; | |
} | |
if (model === "account") { | |
const userIdClause = where.find( | |
(clause: Where) => clause.field === "userId" | |
); | |
if (!userIdClause?.value) { | |
debug(`[LettaAdapter] No userId provided for account lookup`); | |
return null; | |
} | |
const identity = await getIdentity(letta, where, "userId"); | |
if (!identity) { | |
debug( | |
`[LettaAdapter] No identity found for userId: ${userIdClause.value}` | |
); | |
return null; | |
} | |
const accounts = transformers.account.parse(identity) || []; | |
const accountIdClause = where.find( | |
(clause: Where) => clause.field === "accountId" | |
); | |
if (accountIdClause?.value) { | |
const account = accounts.find( | |
(acc) => acc.accountId === accountIdClause.value | |
); | |
if (!account) return null; | |
debug(`[LettaAdapter] Account found:`, { id: account.id }); | |
return transformers.account.transform(account, identity.id!) as T; | |
} | |
const firstAccount = accounts[0]; | |
if (!firstAccount) return null; | |
debug(`[LettaAdapter] First account found:`, { | |
id: firstAccount.id, | |
}); | |
return transformers.account.transform( | |
firstAccount, | |
identity.id! | |
) as T; | |
} | |
if (model === "session") { | |
const userIdClause = where.find( | |
(clause: Where) => clause.field === "userId" | |
); | |
if (!userIdClause?.value) { | |
debug(`[LettaAdapter] No userId provided for session lookup`); | |
return null; | |
} | |
const identity = await getIdentity(letta, where, "id"); | |
if (!identity) { | |
debug( | |
`[LettaAdapter] No identity found for userId: ${userIdClause.value}` | |
); | |
return null; | |
} | |
const sessions = transformers.session.parse(identity) || []; | |
const sessionTokenClause = where.find( | |
(clause: Where) => clause.field === "token" | |
); | |
if (sessionTokenClause?.value) { | |
const session = sessions.find( | |
(s) => s.token === sessionTokenClause.value | |
); | |
if (!session) return null; | |
debug(`[LettaAdapter] Session found:`, { id: session.id }); | |
return transformers.session.transform(session, identity.id!) as T; | |
} | |
const firstSession = sessions[0]; | |
if (!firstSession) return null; | |
debug(`[LettaAdapter] First session found:`, { | |
id: firstSession.id, | |
}); | |
return transformers.session.transform( | |
firstSession, | |
identity.id! | |
) as T; | |
} | |
if (model === "verification") { | |
const identifierClause = where.find( | |
(clause: Where) => clause.field === "identifier" | |
); | |
if (!identifierClause?.value) { | |
debug( | |
`[LettaAdapter] No identifier provided for verification lookup` | |
); | |
return null; | |
} | |
const identity = await getIdentity(letta, where, "id"); | |
if (!identity) { | |
debug( | |
`[LettaAdapter] No identity found for identifier: ${identifierClause.value}` | |
); | |
return null; | |
} | |
const verifications = | |
transformers.verification.parse(identity) || []; | |
const verification = verifications.find( | |
(v) => v.identifier === identifierClause.value | |
); | |
if (!verification) return null; | |
debug(`[LettaAdapter] Verification found:`, { | |
id: verification.id, | |
}); | |
return transformers.verification.transform(verification) as T; | |
} | |
return null; | |
} catch (error) { | |
debug(`[LettaAdapter] Error in findOne for model ${model}:`, error); | |
throw error; | |
} | |
}, | |
findMany: async <T>({ | |
model, | |
where = [], | |
sortBy, | |
limit, | |
offset, | |
}: { | |
model: string; | |
where?: Where[]; | |
sortBy?: { field: string; direction: "asc" | "desc" }; | |
limit?: number; | |
offset?: number; | |
}): Promise<T[]> => { | |
debug(`[LettaAdapter] findMany called for model: ${model}`, { | |
where, | |
sortBy, | |
limit, | |
offset, | |
}); | |
if (!isValidModel(model)) { | |
debug(`[LettaAdapter] Invalid model: ${model}`); | |
throw new Error(`Invalid model: ${model}`); | |
} | |
try { | |
if (model === "user") { | |
const results = await letta.identities.list({ | |
identityType: "user", | |
limit, | |
before: offset ? offset.toString() : undefined, | |
}); | |
const users = results.map((identity) => | |
transformers.user.transform(identity) | |
); | |
debug(`[LettaAdapter] Found ${users.length} users`); | |
return users as T[]; | |
} else if (model === "account") { | |
const userIdClause = where.find( | |
(clause: Where) => clause.field === "userId" | |
); | |
if (!userIdClause?.value) { | |
debug(`[LettaAdapter] No userId provided for account lookup`); | |
return []; | |
} | |
const identity = await getIdentity( | |
letta, | |
where, | |
userIdClause.field | |
); | |
if (!identity) return []; | |
const accounts = transformers.account.parse(identity) || []; | |
debug(`[LettaAdapter] Found ${accounts.length} accounts`); | |
const mappedAccounts = accounts.map((account) => | |
transformers.account.transform(account, identity.id!) | |
); | |
return mappedAccounts as T[]; | |
} else if (model === "session") { | |
const userIdClause = where.find( | |
(clause: Where) => clause.field === "userId" | |
); | |
if (!userIdClause?.value) { | |
debug(`[LettaAdapter] No userId provided for session lookup`); | |
return []; | |
} | |
const identity = await getIdentity( | |
letta, | |
where, | |
userIdClause.field | |
); | |
if (!identity) return []; | |
const sessions = transformers.session.parse(identity) || []; | |
debug(`[LettaAdapter] Found ${sessions.length} sessions`); | |
const mappedSessions = sessions.map((session) => | |
transformers.session.transform(session, identity.id!) | |
); | |
return mappedSessions as T[]; | |
} else if (model === "verification") { | |
const identifierClause = where.find( | |
(clause: Where) => clause.field === "identifier" | |
); | |
if (!identifierClause?.value) { | |
debug( | |
`[LettaAdapter] No identifier provided for verification lookup` | |
); | |
return []; | |
} | |
const identity = await getIdentity( | |
letta, | |
where, | |
identifierClause.field | |
); | |
if (!identity) return []; | |
const verifications = | |
transformers.verification.parse(identity) || []; | |
debug(`[LettaAdapter] Found ${verifications.length} verifications`); | |
const mappedVerifications = verifications.map((verification) => | |
transformers.verification.transform(verification) | |
); | |
return mappedVerifications as T[]; | |
} | |
debug(`[LettaAdapter] No model found for ${model}`); | |
return []; | |
} catch (error) { | |
debug(`[LettaAdapter] Error in findMany for model ${model}:`, error); | |
throw error; | |
} | |
}, | |
count: async ({ | |
model, | |
where = [], | |
}: { | |
model: string; | |
where?: Where[]; | |
}): Promise<number> => { | |
debug(`[LettaAdapter] count called for model: ${model}`, { | |
where, | |
}); | |
if (!isValidModel(model)) { | |
debug(`[LettaAdapter] Invalid model: ${model}`); | |
throw new Error(`Invalid model: ${model}`); | |
} | |
try { | |
if (model === "user") { | |
const results = await letta.identities.list({ | |
identityType: "user", | |
}); | |
debug(`[LettaAdapter] Counted ${results.length} users`); | |
return results.length; | |
} | |
return 0; | |
} catch (error) { | |
debug(`[LettaAdapter] Error in count for model ${model}:`, error); | |
throw error; | |
} | |
}, | |
update: async <T>({ | |
model, | |
where, | |
update, | |
}: { | |
model: string; | |
where: Where[]; | |
update: Record<string, any>; | |
}): Promise<T | null> => { | |
debug(`[LettaAdapter] update called for model: ${model}`, { | |
where, | |
update, | |
}); | |
if (!isValidModel(model)) { | |
debug(`[LettaAdapter] Invalid model: ${model}`); | |
throw new Error(`Invalid model: ${model}`); | |
} | |
try { | |
if (model === "user") { | |
const emailClause = where.find( | |
(clause: Where) => clause.field === "email" | |
); | |
if (!emailClause?.value) { | |
debug(`[LettaAdapter] No email provided for update`); | |
throw new Error("No email provided for update"); | |
} | |
const identity = await getIdentity(letta, where, emailClause.field); | |
if (!identity) { | |
debug( | |
`[LettaAdapter] No user found for email: ${emailClause.value}` | |
); | |
throw new Error("User not found"); | |
} | |
await transformers.user.update( | |
letta, | |
identity, | |
update as Partial<User> | |
); | |
const user = transformers.user.transform(identity); | |
debug(`[LettaAdapter] User updated:`, { id: user.id }); | |
return user as T; | |
} | |
throw new Error(`Update not implemented for model: ${model}`); | |
} catch (error) { | |
debug(`[LettaAdapter] Error in update for model ${model}:`, error); | |
throw error; | |
} | |
}, | |
delete: async <T>({ | |
model, | |
where, | |
}: { | |
model: string; | |
where: Where[]; | |
}): Promise<void> => { | |
debug(`[LettaAdapter] delete called for model: ${model}`, { | |
where, | |
}); | |
if (!isValidModel(model)) { | |
debug(`[LettaAdapter] Invalid model: ${model}`); | |
throw new Error(`Invalid model: ${model}`); | |
} | |
try { | |
if (model === "user") { | |
const emailClause = where.find( | |
(clause: Where) => clause.field === "email" | |
); | |
if (!emailClause?.value) { | |
debug(`[LettaAdapter] No email provided for delete`); | |
throw new Error("No email provided for delete"); | |
} | |
const identity = await getIdentity(letta, where, emailClause.field); | |
if (!identity) { | |
debug( | |
`[LettaAdapter] No user found for email: ${emailClause.value}` | |
); | |
throw new Error("User not found"); | |
} | |
await letta.identities.delete(identity.id!); | |
debug(`[LettaAdapter] User deleted:`, { | |
email: emailClause.value, | |
}); | |
return; | |
} | |
throw new Error(`Delete not implemented for model: ${model}`); | |
} catch (error) { | |
debug(`[LettaAdapter] Error in delete for model ${model}:`, error); | |
throw error; | |
} | |
}, | |
deleteMany: async ({ | |
model, | |
where = [], | |
}: { | |
model: string; | |
where: Where[]; | |
}): Promise<number> => { | |
debug(`[LettaAdapter] deleteMany called for model: ${model}`, { | |
where, | |
}); | |
if (!isValidModel(model)) { | |
debug(`[LettaAdapter] Invalid model: ${model}`); | |
throw new Error(`Invalid model: ${model}`); | |
} | |
try { | |
if (model === "user") { | |
const results = await letta.identities.list({ | |
identityType: "user", | |
}); | |
const count = results.length; | |
for (const result of results) { | |
if (!result.id) continue; | |
await letta.identities.delete(result.id); | |
} | |
debug(`[LettaAdapter] Deleted ${count} users`); | |
return count; | |
} | |
return 0; | |
} catch (error) { | |
debug( | |
`[LettaAdapter] Error in deleteMany for model ${model}:`, | |
error | |
); | |
throw error; | |
} | |
}, | |
updateMany: async ({ | |
model, | |
where, | |
update, | |
}: { | |
model: string; | |
where: Where[]; | |
update: Record<string, any>; | |
}): Promise<number> => { | |
debug(`[LettaAdapter] updateMany called for model: ${model}`, { | |
where, | |
update, | |
}); | |
if (!isValidModel(model)) { | |
debug(`[LettaAdapter] Invalid model: ${model}`); | |
throw new Error(`Invalid model: ${model}`); | |
} | |
try { | |
if (model === "user") { | |
const results = await letta.identities.list({ | |
identityType: "user", | |
}); | |
let count = 0; | |
for (const result of results) { | |
if (!result.id) continue; | |
await transformers.user.update( | |
letta, | |
result, | |
update as Partial<User> | |
); | |
count++; | |
} | |
debug(`[LettaAdapter] Updated ${count} users`); | |
return count; | |
} | |
return 0; | |
} catch (error) { | |
debug( | |
`[LettaAdapter] Error in updateMany for model ${model}:`, | |
error | |
); | |
throw error; | |
} | |
}, | |
} satisfies Adapter; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment