// SPDX-License-Identifier: LicenseRef-ConusAI-Template-1.0 // Private RPCs used only by the Next BFF's server-side ConvexHttpClient. They expose the narrow // global reads/writes kit-auth needs to maintain its sealed OIDC sessions and user rows. Domain // procedures never use this path: each of those executes through one generated public // query/mutation and therefore one real Convex transaction. import { apiKeys, sessions } from "@conusai-kit/auth/entities" import type { Id } from "@conusai-kit/core" import { createConvexDataSource } from "@conusai-kit/data-convex" import { executorFor } from "@conusai-kit/data-convex/runtime" import { type AppIdentityClaims, type AppUserClaims, appUserIdentities, resolveAppUser, users, } from "@conusai-kit/tenancy" import type { GenericDataModel, GenericMutationCtx, GenericQueryCtx } from "convex/server" import { v } from "convex/values" import { internalMutation, internalQuery } from "./_generated/server" import { convexCapabilities } from "./lib/runtime" const entities = [users, appUserIdentities, sessions, apiKeys] type FunctionContext = { db: GenericQueryCtx["db"] | GenericMutationCtx["db"] } const data = (ctx: FunctionContext) => createConvexDataSource({ entities, executor: executorFor(ctx) }) // One internal mutation is the whole first-login/backfill transaction. Separate BFF calls could // never provide the uniqueness and rollback guarantees of the resolver. export const appUserResolve = internalMutation({ args: { claims: v.object({ issuer: v.string(), subject: v.string(), email: v.optional(v.string()), displayName: v.optional(v.string()), locale: v.optional(v.string()), }), create: v.boolean(), }, handler: async (ctx, { claims, create }) => resolveAppUser(data(ctx), claims as AppUserClaims | AppIdentityClaims, { create }), }) export const sessionById = internalQuery({ args: { id: v.string() }, handler: async (ctx, { id }) => data(ctx) .global(sessions) .get(id as Id), }) export const sessionByHandleHash = internalQuery({ args: { handleHash: v.string() }, handler: async (ctx, { handleHash }) => { const page = await data(ctx) .global(sessions) .list({ where: [{ field: "handleHash", op: "eq", value: handleHash }], limit: 1, }) return page.items[0] ?? null }, }) export const sessionInsert = internalMutation({ args: { input: v.any() }, handler: async (ctx, { input }) => data(ctx).global(sessions).insert(input), }) export const sessionUpdate = internalMutation({ args: { id: v.string(), patch: v.any(), expectedVersion: v.number() }, handler: async (ctx, { id, patch, expectedVersion }) => data(ctx) .global(sessions) .update(id as Id, patch, expectedVersion), }) export const apiKeyByHash = internalQuery({ args: { tokenHash: v.string() }, handler: async (ctx, { tokenHash }) => { const source = data(ctx) const page = await source.acrossTenants(apiKeys).list({ where: [{ field: "tokenHash", op: "eq", value: tokenHash }], limit: 1, }) return page.items[0] ?? null }, }) export const ready = internalQuery({ args: {}, handler: async () => ({ ready: true, ...convexCapabilities }), })