58 lines
2.1 KiB
TypeScript
58 lines
2.1 KiB
TypeScript
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<typeof ekklesiaUserIdSchema>;
|
|
|
|
export function parseEkklesiaUserId(input: unknown): EkklesiaUserId {
|
|
return ekklesiaUserIdSchema.parse(input);
|
|
}
|
|
|
|
export function safeParseEkklesiaUserId(input: unknown): z.ZodSafeParseResult<EkklesiaUserId> {
|
|
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";
|
|
}
|