97 lines
3.3 KiB
TypeScript
97 lines
3.3 KiB
TypeScript
// 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<DataModel extends GenericDataModel> = {
|
|
db: GenericQueryCtx<DataModel>["db"] | GenericMutationCtx<DataModel>["db"]
|
|
}
|
|
const data = <DataModel extends GenericDataModel>(ctx: FunctionContext<DataModel>) =>
|
|
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 }),
|
|
})
|