import { z } from "zod"; /** * Opaque at the type level via Zod's nominal `.brand()`: a plain `string` is not assignable to * `EkklesiaUserId` without passing through `parseEkklesiaUserId`/`safeParseEkklesiaUserId`. The * brand exists only in the type system — it adds no runtime tag, wrapper, or field, so a value * still serializes (JSON.stringify, string concatenation, equality) as a plain opaque string. * Ekklesia does not assume any particular format (in particular, not UUID): Phase 4A's identity * schema stores Better Auth's default application-generated id, an opaque base62-ish string, not * a database-generated UUID. Only the constraints Ekklesia itself owns are validated: non-empty * after trimming, and a generous upper bound against unreasonably large values. */ export const ekklesiaUserIdSchema = z.string().trim().min(1).max(128).brand<"EkklesiaUserId">(); export type EkklesiaUserId = z.infer; export function parseEkklesiaUserId(input: unknown): EkklesiaUserId { return ekklesiaUserIdSchema.parse(input); } export function safeParseEkklesiaUserId(input: unknown): z.ZodSafeParseResult { return ekklesiaUserIdSchema.safeParse(input); } export interface EkklesiaAnonymousPrincipal { readonly kind: "anonymous"; } export interface EkklesiaAuthenticatedPrincipal { readonly kind: "authenticated"; readonly userId: EkklesiaUserId; } export type EkklesiaPrincipal = EkklesiaAnonymousPrincipal | EkklesiaAuthenticatedPrincipal; export const ANONYMOUS_PRINCIPAL: EkklesiaAnonymousPrincipal = Object.freeze({ kind: "anonymous", }); export function createAuthenticatedPrincipal(userId: unknown): EkklesiaAuthenticatedPrincipal { return Object.freeze({ kind: "authenticated", userId: parseEkklesiaUserId(userId), }); } export function isAnonymousPrincipal( principal: EkklesiaPrincipal, ): principal is EkklesiaAnonymousPrincipal { return principal.kind === "anonymous"; } export function isAuthenticatedPrincipal( principal: EkklesiaPrincipal, ): principal is EkklesiaAuthenticatedPrincipal { return principal.kind === "authenticated"; }