diff --git a/app/server/lib/auth/helpers/__tests__/create-default-org.test.ts b/app/server/lib/auth/helpers/__tests__/create-default-org.test.ts index fb93cd97..4c6a0dbc 100644 --- a/app/server/lib/auth/helpers/__tests__/create-default-org.test.ts +++ b/app/server/lib/auth/helpers/__tests__/create-default-org.test.ts @@ -1,9 +1,11 @@ import { beforeEach, describe, expect, test } from "bun:test"; import { eq } from "drizzle-orm"; -import { db } from "~/server/db/db"; +import { db, sqlite } from "~/server/db/db"; import { account, invitation, member, organization, usersTable } from "~/server/db/schema"; import { ensureDefaultOrg } from "../create-default-org"; +const CREATE_DEFAULT_ORG_ROLLBACK_TRIGGER = "create_default_org_member_abort"; + function randomId() { return Bun.randomUUIDv7(); } @@ -12,6 +14,14 @@ function randomSlug(prefix: string) { return `${prefix}-${Math.random().toString(36).slice(2, 8)}`; } +function escapeSqlLiteral(value: string) { + return value.replaceAll("'", "''"); +} + +function dropTrigger(name: string) { + sqlite.exec(`DROP TRIGGER IF EXISTS ${name};`); +} + async function createUser(email: string, username: string) { const userId = randomId(); await db.insert(usersTable).values({ @@ -25,6 +35,7 @@ async function createUser(email: string, username: string) { describe("ensureDefaultOrg", () => { beforeEach(async () => { + dropTrigger(CREATE_DEFAULT_ORG_ROLLBACK_TRIGGER); await db.delete(member); await db.delete(account); await db.delete(invitation); @@ -71,4 +82,29 @@ describe("ensureDefaultOrg", () => { expect(membership.role).toBe("owner"); expect(membership.organization.name).toContain("Workspace"); }); + + test("rolls back organization creation when membership insertion fails", async () => { + const userId = await createUser("rollback-user@example.com", randomSlug("rollback-user")); + + sqlite.exec(` + CREATE TRIGGER ${CREATE_DEFAULT_ORG_ROLLBACK_TRIGGER} + BEFORE INSERT ON member + WHEN NEW.user_id = '${escapeSqlLiteral(userId)}' + BEGIN + SELECT RAISE(ABORT, 'forced createDefaultOrg rollback'); + END; + `); + + try { + await expect(ensureDefaultOrg(userId)).rejects.toThrow("forced createDefaultOrg rollback"); + } finally { + dropTrigger(CREATE_DEFAULT_ORG_ROLLBACK_TRIGGER); + } + + const memberships = await db.select().from(member).where(eq(member.userId, userId)); + const organizations = await db.select().from(organization); + + expect(memberships).toHaveLength(0); + expect(organizations).toHaveLength(0); + }); }); diff --git a/app/server/lib/auth/helpers/create-default-org.ts b/app/server/lib/auth/helpers/create-default-org.ts index ea2349fd..87eb986a 100644 --- a/app/server/lib/auth/helpers/create-default-org.ts +++ b/app/server/lib/auth/helpers/create-default-org.ts @@ -57,7 +57,7 @@ async function createDefaultOrganizationMembership(user: User) { logger.debug("Creating default organization for user", { userId: user.id }); const organizationData = await buildDefaultOrganizationData(user); - await db.transaction(async (tx) => { + db.transaction((tx) => { tx.insert(organization).values(organizationData).run(); tx.insert(member) diff --git a/app/server/modules/auth/__tests__/auth.account-and-membership.test.ts b/app/server/modules/auth/__tests__/auth.account-and-membership.test.ts index f620fd68..f26b367b 100644 --- a/app/server/modules/auth/__tests__/auth.account-and-membership.test.ts +++ b/app/server/modules/auth/__tests__/auth.account-and-membership.test.ts @@ -1,8 +1,11 @@ import { beforeEach, describe, expect, test } from "bun:test"; -import { db } from "~/server/db/db"; +import { db, sqlite } from "~/server/db/db"; import { account, member, organization, sessionsTable, usersTable } from "~/server/db/schema"; import { authService } from "../auth.service"; +const DELETE_USER_ACCOUNT_ROLLBACK_TRIGGER = "delete_user_account_abort"; +const REMOVE_ORG_MEMBER_ROLLBACK_TRIGGER = "remove_org_member_reassign_abort"; + function randomId() { return Bun.randomUUIDv7(); } @@ -11,6 +14,14 @@ function randomSlug(prefix: string) { return `${prefix}-${Math.random().toString(36).slice(2, 8)}`; } +function escapeSqlLiteral(value: string) { + return value.replaceAll("'", "''"); +} + +function dropTrigger(name: string) { + sqlite.exec(`DROP TRIGGER IF EXISTS ${name};`); +} + async function createUser(email: string) { const id = randomId(); @@ -83,6 +94,8 @@ async function createAccount({ userId, providerId }: { userId: string; providerI describe("authService account and membership management", () => { beforeEach(async () => { + dropTrigger(DELETE_USER_ACCOUNT_ROLLBACK_TRIGGER); + dropTrigger(REMOVE_ORG_MEMBER_ROLLBACK_TRIGGER); await db.delete(account); await db.delete(member); await db.delete(sessionsTable); @@ -145,6 +158,46 @@ describe("authService account and membership management", () => { expect(existingAccount).toEqual({ id: onlyAccountId }); }); + test("deleteUserAccount leaves accounts untouched when deletion fails", async () => { + const userId = await createUser(`${randomSlug("user")}@example.com`); + const credentialAccountId = await createAccount({ + userId, + providerId: "credential", + }); + const oidcAccountId = await createAccount({ userId, providerId: "oidc-acme" }); + await createSession({ userId, activeOrganizationId: null }); + + sqlite.exec(` + CREATE TRIGGER ${DELETE_USER_ACCOUNT_ROLLBACK_TRIGGER} + BEFORE DELETE ON account + WHEN OLD.id = '${escapeSqlLiteral(credentialAccountId)}' + BEGIN + SELECT RAISE(ABORT, 'forced deleteUserAccount rollback'); + END; + `); + + try { + await expect(authService.deleteUserAccount(userId, credentialAccountId)).rejects.toThrow( + "forced deleteUserAccount rollback", + ); + } finally { + dropTrigger(DELETE_USER_ACCOUNT_ROLLBACK_TRIGGER); + } + + const remainingAccounts = await db.query.account.findMany({ + where: { userId }, + columns: { id: true }, + }); + const remainingSessions = await db.query.sessionsTable.findMany({ + where: { userId }, + columns: { id: true }, + }); + + expect(remainingAccounts).toHaveLength(2); + expect(remainingAccounts).toEqual(expect.arrayContaining([{ id: credentialAccountId }, { id: oidcAccountId }])); + expect(remainingSessions).toHaveLength(1); + }); + test("removeOrgMember rehomes active sessions to another organization membership", async () => { const userId = await createUser(`${randomSlug("user")}@example.com`); const removedOrgId = await createOrganization("Removed Org"); @@ -205,6 +258,53 @@ describe("authService account and membership management", () => { expect(removedMembership).toBeUndefined(); }); + test("removeOrgMember rolls back membership removal when session reassignment fails", async () => { + const userId = await createUser(`${randomSlug("user")}@example.com`); + const removedOrgId = await createOrganization("Removed Org"); + const fallbackOrgId = await createOrganization("Fallback Org"); + const membershipId = await createMembership({ + userId, + organizationId: removedOrgId, + role: "member", + }); + await createMembership({ + userId, + organizationId: fallbackOrgId, + role: "member", + }); + await createSession({ userId, activeOrganizationId: removedOrgId }); + + sqlite.exec(` + CREATE TRIGGER ${REMOVE_ORG_MEMBER_ROLLBACK_TRIGGER} + BEFORE UPDATE OF active_organization_id ON sessions_table + WHEN OLD.user_id = '${escapeSqlLiteral(userId)}' + AND NEW.active_organization_id = '${escapeSqlLiteral(fallbackOrgId)}' + BEGIN + SELECT RAISE(ABORT, 'forced removeOrgMember rollback'); + END; + `); + + try { + await expect(authService.removeOrgMember(membershipId, removedOrgId)).rejects.toThrow( + "forced removeOrgMember rollback", + ); + } finally { + dropTrigger(REMOVE_ORG_MEMBER_ROLLBACK_TRIGGER); + } + + const session = await db.query.sessionsTable.findFirst({ + where: { userId }, + columns: { activeOrganizationId: true }, + }); + const removedMembership = await db.query.member.findFirst({ + where: { id: membershipId }, + columns: { id: true }, + }); + + expect(session?.activeOrganizationId).toBe(removedOrgId); + expect(removedMembership).toEqual({ id: membershipId }); + }); + test("removeOrgMember returns isOwner=true and does not remove owner membership", async () => { const userId = await createUser(`${randomSlug("user")}@example.com`); const orgId = await createOrganization("Owner Org"); diff --git a/app/server/modules/auth/__tests__/auth.cleanup-user-organizations.test.ts b/app/server/modules/auth/__tests__/auth.cleanup-user-organizations.test.ts index c41a2486..754945b6 100644 --- a/app/server/modules/auth/__tests__/auth.cleanup-user-organizations.test.ts +++ b/app/server/modules/auth/__tests__/auth.cleanup-user-organizations.test.ts @@ -1,8 +1,10 @@ import { beforeEach, describe, expect, test } from "bun:test"; -import { db } from "~/server/db/db"; +import { db, sqlite } from "~/server/db/db"; import { member, organization, sessionsTable, usersTable } from "~/server/db/schema"; import { authService } from "../auth.service"; +const CLEANUP_USER_ORGS_ROLLBACK_TRIGGER = "cleanup_user_orgs_final_session_abort"; + function randomId() { return Bun.randomUUIDv7(); } @@ -11,6 +13,14 @@ function randomSlug(prefix: string) { return `${prefix}-${Math.random().toString(36).slice(2, 8)}`; } +function escapeSqlLiteral(value: string) { + return value.replaceAll("'", "''"); +} + +function dropTrigger(name: string) { + sqlite.exec(`DROP TRIGGER IF EXISTS ${name};`); +} + async function createUser(email: string) { const id = randomId(); @@ -77,6 +87,7 @@ async function createSession({ describe("authService.cleanupUserOrganizations", () => { beforeEach(async () => { + dropTrigger(CLEANUP_USER_ORGS_ROLLBACK_TRIGGER); await db.delete(member); await db.delete(sessionsTable); await db.delete(organization); @@ -158,4 +169,95 @@ describe("authService.cleanupUserOrganizations", () => { expect(membership).toBeUndefined(); }); + + test("sets active organization to null for affected members without a fallback organization", async () => { + const deletedUserId = await createUser(`${randomSlug("deleted")}@example.com`); + const affectedUserId = await createUser(`${randomSlug("affected")}@example.com`); + const deletedWorkspaceId = await createOrganization("Deleted Workspace"); + + await createMembership({ userId: deletedUserId, organizationId: deletedWorkspaceId, role: "owner" }); + await createMembership({ userId: affectedUserId, organizationId: deletedWorkspaceId, role: "member" }); + + const affectedSessionId = await createSession({ + userId: affectedUserId, + activeOrganizationId: deletedWorkspaceId, + }); + + await authService.cleanupUserOrganizations(deletedUserId); + + const updatedSession = await db.query.sessionsTable.findFirst({ + where: { id: affectedSessionId }, + columns: { activeOrganizationId: true }, + }); + const deletedWorkspace = await db.query.organization.findFirst({ + where: { id: deletedWorkspaceId }, + columns: { id: true }, + }); + const removedMembership = await db.query.member.findFirst({ + where: { AND: [{ organizationId: deletedWorkspaceId }, { userId: affectedUserId }] }, + columns: { id: true }, + }); + + expect(deletedWorkspace).toBeUndefined(); + expect(updatedSession?.activeOrganizationId).toBeNull(); + expect(removedMembership).toBeUndefined(); + }); + + test("rolls back organization cleanup when session nulling fails", async () => { + const affectedUserId = await createUser(`${randomSlug("admin")}@example.com`); + const deletedUserId = await createUser(`${randomSlug("deleted")}@example.com`); + + const fallbackWorkspaceId = await createOrganization("Fallback Workspace"); + const deletedWorkspaceId = await createOrganization("Deleted Workspace"); + + await createMembership({ userId: affectedUserId, organizationId: fallbackWorkspaceId, role: "owner" }); + await createMembership({ userId: deletedUserId, organizationId: deletedWorkspaceId, role: "owner" }); + await createMembership({ userId: affectedUserId, organizationId: deletedWorkspaceId, role: "member" }); + + const affectedSessionId = await createSession({ + userId: affectedUserId, + activeOrganizationId: deletedWorkspaceId, + }); + const deletedUserSessionId = await createSession({ + userId: deletedUserId, + activeOrganizationId: deletedWorkspaceId, + }); + + sqlite.exec(` + CREATE TRIGGER ${CLEANUP_USER_ORGS_ROLLBACK_TRIGGER} + BEFORE UPDATE OF active_organization_id ON sessions_table + WHEN OLD.user_id = '${escapeSqlLiteral(deletedUserId)}' AND NEW.active_organization_id IS NULL + BEGIN + SELECT RAISE(ABORT, 'forced cleanup rollback'); + END; + `); + + try { + await expect(authService.cleanupUserOrganizations(deletedUserId)).rejects.toThrow("forced cleanup rollback"); + } finally { + dropTrigger(CLEANUP_USER_ORGS_ROLLBACK_TRIGGER); + } + + const deletedWorkspace = await db.query.organization.findFirst({ + where: { id: deletedWorkspaceId }, + columns: { id: true }, + }); + const affectedSession = await db.query.sessionsTable.findFirst({ + where: { id: affectedSessionId }, + columns: { activeOrganizationId: true }, + }); + const deletedUserSession = await db.query.sessionsTable.findFirst({ + where: { id: deletedUserSessionId }, + columns: { activeOrganizationId: true }, + }); + const affectedMembership = await db.query.member.findFirst({ + where: { AND: [{ organizationId: deletedWorkspaceId }, { userId: affectedUserId }] }, + columns: { id: true }, + }); + + expect(deletedWorkspace).toEqual({ id: deletedWorkspaceId }); + expect(affectedSession?.activeOrganizationId).toBe(deletedWorkspaceId); + expect(deletedUserSession?.activeOrganizationId).toBe(deletedWorkspaceId); + expect(affectedMembership).not.toBeUndefined(); + }); }); diff --git a/app/server/modules/auth/auth.service.ts b/app/server/modules/auth/auth.service.ts index 59b7629c..7b8e29fd 100644 --- a/app/server/modules/auth/auth.service.ts +++ b/app/server/modules/auth/auth.service.ts @@ -87,22 +87,26 @@ export class AuthService { return; } - await db.transaction(async (tx) => { - const membersInDeletedOrgs = await tx - .select({ userId: member.userId }) - .from(member) - .where(and(inArray(member.organizationId, orgIds), ne(member.userId, userId))); + db.transaction((tx) => { + const membersInDeletedOrgs = tx.query.member + .findMany({ + where: { + AND: [{ organizationId: { in: orgIds } }, { userId: { ne: userId } }], + }, + columns: { userId: true }, + }) + .sync(); const affectedUserIds = [...new Set(membersInDeletedOrgs.map((r) => r.userId))]; if (affectedUserIds.length > 0) { - const memberships = await tx - .select({ - userId: member.userId, - organizationId: member.organizationId, + const memberships = tx.query.member + .findMany({ + where: { + userId: { in: affectedUserIds }, + }, }) - .from(member) - .where(inArray(member.userId, affectedUserIds)); + .sync(); const orgIdSet = new Set(orgIds); const fallbackOrgByUser = new Map(affectedUserIds.map((id) => [id, null])); @@ -114,19 +118,19 @@ export class AuthService { } for (const [affectedUserId, fallbackOrgId] of fallbackOrgByUser) { - await tx - .update(sessionsTable) + tx.update(sessionsTable) .set({ activeOrganizationId: fallbackOrgId }) - .where(and(eq(sessionsTable.userId, affectedUserId), inArray(sessionsTable.activeOrganizationId, orgIds))); + .where(and(eq(sessionsTable.userId, affectedUserId), inArray(sessionsTable.activeOrganizationId, orgIds))) + .run(); } } - await tx.delete(organization).where(inArray(organization.id, orgIds)); + tx.delete(organization).where(inArray(organization.id, orgIds)).run(); - await tx - .update(sessionsTable) + tx.update(sessionsTable) .set({ activeOrganizationId: null }) - .where(inArray(sessionsTable.activeOrganizationId, orgIds)); + .where(inArray(sessionsTable.activeOrganizationId, orgIds)) + .run(); }); } @@ -216,29 +220,31 @@ export class AuthService { return { found: true, isOwner: true } as const; } - await db.transaction(async (tx) => { - const fallbackMembership = await tx.query.member.findFirst({ - where: { - AND: [{ userId: targetMember.userId }, { organizationId: { ne: organizationId } }], - }, - columns: { organizationId: true }, - }); + db.transaction((tx) => { + const fallbackMembership = tx.query.member + .findFirst({ + where: { + AND: [{ userId: targetMember.userId }, { organizationId: { ne: organizationId } }], + }, + columns: { organizationId: true }, + }) + .sync(); - await tx.delete(member).where(eq(member.id, memberId)); + tx.delete(member).where(eq(member.id, memberId)).run(); if (fallbackMembership?.organizationId) { - await tx - .update(sessionsTable) + tx.update(sessionsTable) .set({ activeOrganizationId: fallbackMembership.organizationId, }) .where( and(eq(sessionsTable.userId, targetMember.userId), eq(sessionsTable.activeOrganizationId, organizationId)), - ); + ) + .run(); return; } - await tx.delete(sessionsTable).where(eq(sessionsTable.userId, targetMember.userId)); + tx.delete(sessionsTable).where(eq(sessionsTable.userId, targetMember.userId)).run(); }); return { found: true, isOwner: false } as const; @@ -261,29 +267,33 @@ export class AuthService { * Delete a single account for a user, refusing if it is the last one */ async deleteUserAccount(userId: string, accountId: string) { - return db.transaction(async (tx) => { - const [targetAccount] = await tx - .select({ id: account.id }) - .from(account) - .where(and(eq(account.id, accountId), eq(account.userId, userId))) - .limit(1); + return db.transaction((tx) => { + const targetAccount = tx.query.account + .findFirst({ + where: { + AND: [{ id: accountId }, { userId }], + }, + }) + .sync(); if (!targetAccount) { return { lastAccount: false, notFound: true }; } - const userAccounts = await tx.query.account.findMany({ - where: { userId }, - columns: { id: true }, - }); + const userAccounts = tx.query.account + .findMany({ + where: { userId }, + columns: { id: true }, + }) + .sync(); if (userAccounts.length <= 1) { return { lastAccount: true, notFound: false }; } - await tx.delete(account).where(and(eq(account.id, accountId), eq(account.userId, userId))); - // Sessions cannot be tied to a specific account/provider with the current schema, - // so keep active sessions intact when unlinking a single account. + tx.delete(account) + .where(and(eq(account.id, accountId), eq(account.userId, userId))) + .run(); return { lastAccount: false, notFound: false }; }); diff --git a/app/server/modules/sso/__tests__/sso.service.test.ts b/app/server/modules/sso/__tests__/sso.service.test.ts index dd3ae8c4..126df6d3 100644 --- a/app/server/modules/sso/__tests__/sso.service.test.ts +++ b/app/server/modules/sso/__tests__/sso.service.test.ts @@ -1,8 +1,10 @@ import { beforeEach, describe, expect, test } from "bun:test"; -import { db } from "~/server/db/db"; +import { db, sqlite } from "~/server/db/db"; import { account, invitation, member, organization, sessionsTable, ssoProvider, usersTable } from "~/server/db/schema"; import { ssoService } from "../sso.service"; +const DELETE_SSO_PROVIDER_ROLLBACK_TRIGGER = "delete_sso_provider_sessions_abort"; + function randomId() { return Bun.randomUUIDv7(); } @@ -11,6 +13,14 @@ function randomSlug(prefix: string) { return `${prefix}-${Math.random().toString(36).slice(2, 8)}`; } +function escapeSqlLiteral(value: string) { + return value.replaceAll("'", "''"); +} + +function dropTrigger(name: string) { + sqlite.exec(`DROP TRIGGER IF EXISTS ${name};`); +} + async function createUser(email: string) { const id = randomId(); @@ -48,6 +58,7 @@ async function createSession(userId: string) { describe("ssoService.deleteSsoProvider", () => { beforeEach(async () => { + dropTrigger(DELETE_SSO_PROVIDER_ROLLBACK_TRIGGER); await db.delete(member); await db.delete(account); await db.delete(sessionsTable); @@ -191,4 +202,61 @@ describe("ssoService.deleteSsoProvider", () => { expect(remainingProvider).toBeUndefined(); expect(remainingAccounts).toHaveLength(1); }); + + test("deleteSsoProvider rolls back account and provider deletion when session revocation fails", async () => { + const org = await createOrganization("Org A"); + const providerOwner = await createUser(`${randomSlug("owner")}@example.com`); + const accountUser = await createUser(`${randomSlug("member")}@example.com`); + + const providerId = `oidc-${randomSlug("provider")}`; + + await db.insert(ssoProvider).values({ + id: randomId(), + providerId, + organizationId: org, + userId: providerOwner, + issuer: "https://issuer.example.com", + domain: "example.com", + }); + + await db.insert(account).values({ + id: randomId(), + accountId: randomSlug("acct"), + providerId, + userId: accountUser, + }); + await createSession(accountUser); + + sqlite.exec(` + CREATE TRIGGER ${DELETE_SSO_PROVIDER_ROLLBACK_TRIGGER} + BEFORE DELETE ON sessions_table + WHEN OLD.user_id = '${escapeSqlLiteral(accountUser)}' + BEGIN + SELECT RAISE(ABORT, 'forced deleteSsoProvider rollback'); + END; + `); + + try { + await expect(ssoService.deleteSsoProvider(providerId, org)).rejects.toThrow("forced deleteSsoProvider rollback"); + } finally { + dropTrigger(DELETE_SSO_PROVIDER_ROLLBACK_TRIGGER); + } + + const remainingProvider = await db.query.ssoProvider.findFirst({ + where: { providerId }, + columns: { id: true }, + }); + const remainingAccounts = await db.query.account.findMany({ + where: { providerId }, + columns: { id: true }, + }); + const remainingSessions = await db.query.sessionsTable.findMany({ + where: { userId: accountUser }, + columns: { id: true }, + }); + + expect(remainingProvider).not.toBeUndefined(); + expect(remainingAccounts).toHaveLength(1); + expect(remainingSessions).toHaveLength(1); + }); }); diff --git a/app/server/modules/sso/sso.service.ts b/app/server/modules/sso/sso.service.ts index 996041fd..ec56f678 100644 --- a/app/server/modules/sso/sso.service.ts +++ b/app/server/modules/sso/sso.service.ts @@ -68,32 +68,36 @@ export class SsoService { * Delete an SSO provider and its associated accounts */ async deleteSsoProvider(providerId: string, organizationId: string) { - return db.transaction(async (tx) => { - const provider = await tx.query.ssoProvider.findFirst({ - where: { AND: [{ providerId }, { organizationId }] }, - columns: { id: true, providerId: true }, - }); + return db.transaction((tx) => { + const provider = tx.query.ssoProvider + .findFirst({ + where: { AND: [{ providerId }, { organizationId }] }, + columns: { id: true, providerId: true }, + }) + .sync(); if (!provider) { return false; } if (isReservedSsoProviderId(provider.providerId)) { - await tx.delete(ssoProvider).where(eq(ssoProvider.id, provider.id)); + tx.delete(ssoProvider).where(eq(ssoProvider.id, provider.id)).run(); return true; } - const affectedAccounts = await tx.query.account.findMany({ - where: { providerId: provider.providerId }, - columns: { userId: true }, - }); + const affectedAccounts = tx.query.account + .findMany({ + where: { providerId: provider.providerId }, + columns: { userId: true }, + }) + .sync(); const affectedUserIds = [...new Set(affectedAccounts.map((row) => row.userId))]; - await tx.delete(account).where(eq(account.providerId, provider.providerId)); - await tx.delete(ssoProvider).where(eq(ssoProvider.id, provider.id)); + tx.delete(account).where(eq(account.providerId, provider.providerId)).run(); + tx.delete(ssoProvider).where(eq(ssoProvider.id, provider.id)).run(); if (affectedUserIds.length > 0) { - await tx.delete(sessionsTable).where(inArray(sessionsTable.userId, affectedUserIds)); + tx.delete(sessionsTable).where(inArray(sessionsTable.userId, affectedUserIds)).run(); } return true; diff --git a/bun.lock b/bun.lock index 6d2cb7a3..a1ddb948 100644 --- a/bun.lock +++ b/bun.lock @@ -45,7 +45,7 @@ "date-fns": "^4.1.0", "dither-plugin": "^1.1.1", "dotenv": "^17.3.1", - "drizzle-orm": "^1.0.0-beta.12-a5629fb", + "drizzle-orm": "^1.0.0-beta.16-ea816b6", "es-toolkit": "^1.45.0", "hono": "^4.12.4", "hono-openapi": "^1.3.0", @@ -92,7 +92,7 @@ "@vitejs/plugin-react": "^5.1.4", "babel-plugin-react-compiler": "^1.0.0", "dotenv-cli": "^11.0.0", - "drizzle-kit": "^1.0.0-beta.15-859cf75", + "drizzle-kit": "^1.0.0-beta.16-ea816b6", "lefthook": "^2.1.2", "lightningcss": "^1.31.1", "nitro": "^3.0.1-alpha.2", @@ -1166,9 +1166,9 @@ "dotenv-expand": ["dotenv-expand@12.0.3", "", { "dependencies": { "dotenv": "^16.4.5" } }, "sha512-uc47g4b+4k/M/SeaW1y4OApx+mtLWl92l5LMPP0GNXctZqELk+YGgOPIIC5elYmUH4OuoK3JLhuRUYegeySiFA=="], - "drizzle-kit": ["drizzle-kit@1.0.0-beta.9-e89174b", "", { "dependencies": { "@drizzle-team/brocli": "^0.11.0", "@js-temporal/polyfill": "^0.5.1", "esbuild": "^0.25.10", "tsx": "^4.20.6" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-Xrw3k8E2CbSZr+kqe3k5W4oxd2fbEyczjKtyGIkAq0x9Wqpa/VtAT6Mkh83sIzqG4OSN7lOoUafsDxSE/AR7RA=="], + "drizzle-kit": ["drizzle-kit@1.0.0-beta.16-ea816b6", "", { "dependencies": { "@drizzle-team/brocli": "^0.11.0", "@js-temporal/polyfill": "^0.5.1", "esbuild": "^0.25.10", "jiti": "^2.6.1" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-GiJQqCNPZP8Kk+i7/sFa3rtXbq26tLDNi3LbMx9aoLuwF2ofk8CS7cySUGdI+r4J3q0a568quC8FZeaFTCw4IA=="], - "drizzle-orm": ["drizzle-orm@1.0.0-beta.9-e89174b", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@effect/sql": "^0.48.5", "@effect/sql-pg": "^0.49.7", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@sqlitecloud/drivers": ">=1.0.653", "@tidbcloud/serverless": "*", "@tursodatabase/database": ">=0.2.1", "@tursodatabase/database-common": ">=0.2.1", "@tursodatabase/database-wasm": ">=0.2.1", "@types/better-sqlite3": "*", "@types/mssql": "^9.1.4", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=9.3.0", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "mssql": "^11.0.1", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@effect/sql", "@effect/sql-pg", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@sqlitecloud/drivers", "@tidbcloud/serverless", "@tursodatabase/database", "@tursodatabase/database-common", "@tursodatabase/database-wasm", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-B5KR/qYMZ0JMOurK+0xi1ObpOQcgrjaC9wHUiU2eTJjLemuh2CoQIw6yur68NxZG6Xcd0b9qghUNC/78/bEfbg=="], + "drizzle-orm": ["drizzle-orm@1.0.0-beta.16-ea816b6", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@effect/sql": "^0.48.5", "@effect/sql-pg": "^0.49.7", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@sinclair/typebox": ">=0.34.8", "@sqlitecloud/drivers": ">=1.0.653", "@tidbcloud/serverless": "*", "@tursodatabase/database": ">=0.2.1", "@tursodatabase/database-common": ">=0.2.1", "@tursodatabase/database-wasm": ">=0.2.1", "@types/better-sqlite3": "*", "@types/mssql": "^9.1.4", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "arktype": ">=2.0.0", "better-sqlite3": ">=9.3.0", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "mssql": "^11.0.1", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5", "typebox": ">=1.0.0", "valibot": ">=1.0.0-beta.7", "zod": "^3.25.0 || ^4.0.0" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@effect/sql", "@effect/sql-pg", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@sinclair/typebox", "@sqlitecloud/drivers", "@tidbcloud/serverless", "@tursodatabase/database", "@tursodatabase/database-common", "@tursodatabase/database-wasm", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "arktype", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "mysql2", "pg", "postgres", "sql.js", "sqlite3", "typebox", "valibot", "zod"] }, "sha512-k9gT4f0O9Qvah5YK/zL+FZonQ8TPyVxcG/ojN4dzO0fHP8hs8tBno8lqmJo53g0JLWv3Q2nsTUoyBRKM2TljFw=="], "ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="], diff --git a/package.json b/package.json index a7013718..2c5c1aca 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "date-fns": "^4.1.0", "dither-plugin": "^1.1.1", "dotenv": "^17.3.1", - "drizzle-orm": "^1.0.0-beta.12-a5629fb", + "drizzle-orm": "^1.0.0-beta.16-ea816b6", "es-toolkit": "^1.45.0", "hono": "^4.12.4", "hono-openapi": "^1.3.0", @@ -113,7 +113,7 @@ "@vitejs/plugin-react": "^5.1.4", "babel-plugin-react-compiler": "^1.0.0", "dotenv-cli": "^11.0.0", - "drizzle-kit": "^1.0.0-beta.15-859cf75", + "drizzle-kit": "^1.0.0-beta.16-ea816b6", "lefthook": "^2.1.2", "lightningcss": "^1.31.1", "nitro": "^3.0.1-alpha.2",