refactor(frontend): delete type system boilerplate

This commit is contained in:
Lewis
2026-03-19 14:26:26 +00:00
committed by Tangled
parent c2b8253abe
commit 7f66419036
8 changed files with 0 additions and 1090 deletions
-440
View File
@@ -1,440 +0,0 @@
import { z } from "zod";
import { err, ok, type Result } from "./types/result.ts";
import { ApiError } from "./api.ts";
import type {
AccessToken,
Did,
Nsid,
RefreshToken,
Rkey,
} from "./types/branded.ts";
import {
accountInfoSchema,
appPasswordSchema,
createdAppPasswordSchema,
createRecordResponseSchema,
didDocumentSchema,
enableTotpResponseSchema,
legacyLoginPreferenceSchema,
listPasskeysResponseSchema,
listRecordsResponseSchema,
listSessionsResponseSchema,
listTrustedDevicesResponseSchema,
notificationPrefsSchema,
passwordStatusSchema,
reauthStatusSchema,
recordResponseSchema,
repoDescriptionSchema,
searchAccountsResponseSchema,
serverConfigSchema,
serverDescriptionSchema,
serverStatsSchema,
sessionSchema,
successResponseSchema,
totpSecretSchema,
totpStatusSchema,
type ValidatedAccountInfo,
type ValidatedAppPassword,
type ValidatedCreatedAppPassword,
type ValidatedCreateRecordResponse,
type ValidatedDidDocument,
type ValidatedEnableTotpResponse,
type ValidatedLegacyLoginPreference,
type ValidatedListPasskeysResponse,
type ValidatedListRecordsResponse,
type ValidatedListSessionsResponse,
type ValidatedListTrustedDevicesResponse,
type ValidatedNotificationPrefs,
type ValidatedPasswordStatus,
type ValidatedReauthStatus,
type ValidatedRecordResponse,
type ValidatedRepoDescription,
type ValidatedSearchAccountsResponse,
type ValidatedServerConfig,
type ValidatedServerDescription,
type ValidatedServerStats,
type ValidatedSession,
type ValidatedSuccessResponse,
type ValidatedTotpSecret,
type ValidatedTotpStatus,
} from "./types/schemas.ts";
const API_BASE = "/xrpc";
interface XrpcOptions {
method?: "GET" | "POST";
params?: Record<string, string>;
body?: unknown;
token?: string;
}
class ValidationError extends Error {
constructor(
public issues: z.ZodIssue[],
message: string = "API response validation failed",
) {
super(message);
this.name = "ValidationError";
}
}
async function xrpcValidated<T>(
method: string,
schema: z.ZodType<T>,
options?: XrpcOptions,
): Promise<Result<T, ApiError | ValidationError>> {
const { method: httpMethod = "GET", params, body, token } = options ?? {};
let url = `${API_BASE}/${method}`;
if (params) {
const searchParams = new URLSearchParams(params);
url += `?${searchParams}`;
}
const headers: Record<string, string> = {};
if (token) {
headers["Authorization"] = `Bearer ${token}`;
}
if (body) {
headers["Content-Type"] = "application/json";
}
try {
const res = await fetch(url, {
method: httpMethod,
headers,
body: body ? JSON.stringify(body) : undefined,
});
if (!res.ok) {
const errData = await res.json().catch(() => ({
error: "Unknown",
message: res.statusText,
}));
return err(new ApiError(res.status, errData.error, errData.message));
}
const data = await res.json();
const parsed = schema.safeParse(data);
if (!parsed.success) {
return err(new ValidationError(parsed.error.issues));
}
return ok(parsed.data);
} catch (e) {
if (e instanceof ApiError || e instanceof ValidationError) {
return err(e);
}
return err(
new ApiError(0, "Unknown", e instanceof Error ? e.message : String(e)),
);
}
}
export const validatedApi = {
getSession(
token: AccessToken,
): Promise<Result<ValidatedSession, ApiError | ValidationError>> {
return xrpcValidated("com.atproto.server.getSession", sessionSchema, {
token,
});
},
refreshSession(
refreshJwt: RefreshToken,
): Promise<Result<ValidatedSession, ApiError | ValidationError>> {
return xrpcValidated("com.atproto.server.refreshSession", sessionSchema, {
method: "POST",
token: refreshJwt,
});
},
createSession(
identifier: string,
password: string,
): Promise<Result<ValidatedSession, ApiError | ValidationError>> {
return xrpcValidated("com.atproto.server.createSession", sessionSchema, {
method: "POST",
body: { identifier, password },
});
},
describeServer(): Promise<
Result<ValidatedServerDescription, ApiError | ValidationError>
> {
return xrpcValidated(
"com.atproto.server.describeServer",
serverDescriptionSchema,
);
},
listAppPasswords(
token: AccessToken,
): Promise<
Result<{ passwords: ValidatedAppPassword[] }, ApiError | ValidationError>
> {
return xrpcValidated(
"com.atproto.server.listAppPasswords",
z.object({ passwords: z.array(appPasswordSchema) }),
{ token },
);
},
createAppPassword(
token: AccessToken,
name: string,
scopes?: string,
): Promise<Result<ValidatedCreatedAppPassword, ApiError | ValidationError>> {
return xrpcValidated(
"com.atproto.server.createAppPassword",
createdAppPasswordSchema,
{
method: "POST",
token,
body: { name, scopes },
},
);
},
listSessions(
token: AccessToken,
): Promise<
Result<ValidatedListSessionsResponse, ApiError | ValidationError>
> {
return xrpcValidated("_account.listSessions", listSessionsResponseSchema, {
token,
});
},
getTotpStatus(
token: AccessToken,
): Promise<Result<ValidatedTotpStatus, ApiError | ValidationError>> {
return xrpcValidated("com.atproto.server.getTotpStatus", totpStatusSchema, {
token,
});
},
createTotpSecret(
token: AccessToken,
): Promise<Result<ValidatedTotpSecret, ApiError | ValidationError>> {
return xrpcValidated(
"com.atproto.server.createTotpSecret",
totpSecretSchema,
{
method: "POST",
token,
},
);
},
enableTotp(
token: AccessToken,
code: string,
): Promise<Result<ValidatedEnableTotpResponse, ApiError | ValidationError>> {
return xrpcValidated(
"com.atproto.server.enableTotp",
enableTotpResponseSchema,
{
method: "POST",
token,
body: { code },
},
);
},
listPasskeys(
token: AccessToken,
): Promise<
Result<ValidatedListPasskeysResponse, ApiError | ValidationError>
> {
return xrpcValidated(
"com.atproto.server.listPasskeys",
listPasskeysResponseSchema,
{ token },
);
},
listTrustedDevices(
token: AccessToken,
): Promise<
Result<ValidatedListTrustedDevicesResponse, ApiError | ValidationError>
> {
return xrpcValidated(
"_account.listTrustedDevices",
listTrustedDevicesResponseSchema,
{ token },
);
},
getReauthStatus(
token: AccessToken,
): Promise<Result<ValidatedReauthStatus, ApiError | ValidationError>> {
return xrpcValidated("_account.getReauthStatus", reauthStatusSchema, {
token,
});
},
getNotificationPrefs(
token: AccessToken,
): Promise<Result<ValidatedNotificationPrefs, ApiError | ValidationError>> {
return xrpcValidated(
"_account.getNotificationPrefs",
notificationPrefsSchema,
{ token },
);
},
getDidDocument(
token: AccessToken,
): Promise<Result<ValidatedDidDocument, ApiError | ValidationError>> {
return xrpcValidated("_account.getDidDocument", didDocumentSchema, {
token,
});
},
describeRepo(
token: AccessToken,
repo: Did,
): Promise<Result<ValidatedRepoDescription, ApiError | ValidationError>> {
return xrpcValidated(
"com.atproto.repo.describeRepo",
repoDescriptionSchema,
{
token,
params: { repo },
},
);
},
listRecords(
token: AccessToken,
repo: Did,
collection: Nsid,
options?: { limit?: number; cursor?: string; reverse?: boolean },
): Promise<Result<ValidatedListRecordsResponse, ApiError | ValidationError>> {
const params: Record<string, string> = { repo, collection };
if (options?.limit) params.limit = String(options.limit);
if (options?.cursor) params.cursor = options.cursor;
if (options?.reverse) params.reverse = "true";
return xrpcValidated(
"com.atproto.repo.listRecords",
listRecordsResponseSchema,
{
token,
params,
},
);
},
getRecord(
token: AccessToken,
repo: Did,
collection: Nsid,
rkey: Rkey,
): Promise<Result<ValidatedRecordResponse, ApiError | ValidationError>> {
return xrpcValidated("com.atproto.repo.getRecord", recordResponseSchema, {
token,
params: { repo, collection, rkey },
});
},
createRecord(
token: AccessToken,
repo: Did,
collection: Nsid,
record: unknown,
rkey?: Rkey,
): Promise<
Result<ValidatedCreateRecordResponse, ApiError | ValidationError>
> {
return xrpcValidated(
"com.atproto.repo.createRecord",
createRecordResponseSchema,
{
method: "POST",
token,
body: { repo, collection, record, rkey },
},
);
},
getServerStats(
token: AccessToken,
): Promise<Result<ValidatedServerStats, ApiError | ValidationError>> {
return xrpcValidated("_admin.getServerStats", serverStatsSchema, { token });
},
getServerConfig(): Promise<
Result<ValidatedServerConfig, ApiError | ValidationError>
> {
return xrpcValidated("_server.getConfig", serverConfigSchema);
},
getPasswordStatus(
token: AccessToken,
): Promise<Result<ValidatedPasswordStatus, ApiError | ValidationError>> {
return xrpcValidated("_account.getPasswordStatus", passwordStatusSchema, {
token,
});
},
changePassword(
token: AccessToken,
currentPassword: string,
newPassword: string,
): Promise<Result<ValidatedSuccessResponse, ApiError | ValidationError>> {
return xrpcValidated("_account.changePassword", successResponseSchema, {
method: "POST",
token,
body: { currentPassword, newPassword },
});
},
getLegacyLoginPreference(
token: AccessToken,
): Promise<
Result<ValidatedLegacyLoginPreference, ApiError | ValidationError>
> {
return xrpcValidated(
"_account.getLegacyLoginPreference",
legacyLoginPreferenceSchema,
{ token },
);
},
getAccountInfo(
token: AccessToken,
did: Did,
): Promise<Result<ValidatedAccountInfo, ApiError | ValidationError>> {
return xrpcValidated(
"com.atproto.admin.getAccountInfo",
accountInfoSchema,
{
token,
params: { did },
},
);
},
searchAccounts(
token: AccessToken,
options?: { handle?: string; cursor?: string; limit?: number },
): Promise<
Result<ValidatedSearchAccountsResponse, ApiError | ValidationError>
> {
const params: Record<string, string> = {};
if (options?.handle) params.handle = options.handle;
if (options?.cursor) params.cursor = options.cursor;
if (options?.limit) params.limit = String(options.limit);
return xrpcValidated(
"com.atproto.admin.searchAccounts",
searchAccountsResponseSchema,
{
token,
params,
},
);
},
};
export { ValidationError };
-39
View File
@@ -88,13 +88,6 @@ type SessionBase = {
export type Session = SessionBase & ContactState & AccountState;
export function hasEmail(
session: Session,
): session is Session & { email: EmailAddress } {
return session.contactKind === "email" ||
(session.contactKind === "channel" && session.email !== undefined);
}
export function getSessionEmail(session: Session): EmailAddress | undefined {
return session.contactKind === "email"
? session.email
@@ -103,24 +96,6 @@ export function getSessionEmail(session: Session): EmailAddress | undefined {
: undefined;
}
export function isEmailVerified(session: Session): boolean {
return session.contactKind === "email"
? session.emailConfirmed
: session.contactKind === "channel"
? session.preferredChannelVerified
: false;
}
export function isMigrated(
session: Session,
): session is Session & { accountKind: "migrated" } {
return session.accountKind === "migrated";
}
export function isDeactivated(session: Session): boolean {
return session.accountKind === "deactivated";
}
export function isActive(session: Session): boolean {
return session.accountKind === "active";
}
@@ -208,17 +183,6 @@ export interface ConfirmSignupResult {
preferredChannelVerified?: boolean;
}
export interface ListAppPasswordsResponse {
passwords: AppPassword[];
}
export interface AccountInviteCodesResponse {
codes: InviteCodeInfo[];
}
export interface CreateInviteCodeResponse {
code: InviteCodeBrand;
}
export interface ServerLinks {
privacyPolicy?: string;
@@ -317,9 +281,6 @@ export interface ListSessionsResponse {
sessions: SessionInfo[];
}
export interface RevokeAllSessionsResponse {
revokedCount: number;
}
export interface AccountSearchResult {
did: Did;
-131
View File
@@ -25,99 +25,6 @@ export type PublicKeyMultibase = Brand<string, "PublicKeyMultibase">;
export type DidKeyString = Brand<string, "DidKeyString">;
export type ScopeSet = Brand<string, "ScopeSet">;
const DID_PLC_REGEX = /^did:plc:[a-z2-7]{24}$/;
const DID_WEB_REGEX = /^did:web:.+$/;
const HANDLE_REGEX =
/^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/;
const AT_URI_REGEX = /^at:\/\/[^/]+\/[^/]+\/[^/]+$/;
const CID_REGEX = /^[a-z2-7]{59}$|^baf[a-z2-7]+$/;
const NSID_REGEX =
/^[a-z]([a-z0-9-]*[a-z0-9])?(\.[a-z]([a-z0-9-]*[a-z0-9])?)+$/;
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
const ISO_DATE_REGEX =
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$/;
export function isDid(s: string): s is Did {
return s.startsWith("did:plc:") || s.startsWith("did:web:");
}
export function isDidPlc(s: string): s is DidPlc {
return DID_PLC_REGEX.test(s);
}
export function isDidWeb(s: string): s is DidWeb {
return DID_WEB_REGEX.test(s);
}
export function isHandle(s: string): s is Handle {
return HANDLE_REGEX.test(s) && s.length <= 253;
}
export function isAtUri(s: string): s is AtUri {
return AT_URI_REGEX.test(s);
}
export function isCid(s: string): s is Cid {
return CID_REGEX.test(s);
}
export function isNsid(s: string): s is Nsid {
return NSID_REGEX.test(s);
}
export function isEmail(s: string): s is EmailAddress {
return EMAIL_REGEX.test(s);
}
export function isISODate(s: string): s is ISODateString {
return ISO_DATE_REGEX.test(s);
}
export function asDid(s: string): Did {
if (!isDid(s)) throw new TypeError(`Invalid DID: ${s}`);
return s;
}
export function asDidPlc(s: string): DidPlc {
if (!isDidPlc(s)) throw new TypeError(`Invalid DID:PLC: ${s}`);
return s as DidPlc;
}
export function asDidWeb(s: string): DidWeb {
if (!isDidWeb(s)) throw new TypeError(`Invalid DID:WEB: ${s}`);
return s as DidWeb;
}
export function asHandle(s: string): Handle {
if (!isHandle(s)) throw new TypeError(`Invalid handle: ${s}`);
return s;
}
export function asAtUri(s: string): AtUri {
if (!isAtUri(s)) throw new TypeError(`Invalid AT-URI: ${s}`);
return s;
}
export function asCid(s: string): Cid {
if (!isCid(s)) throw new TypeError(`Invalid CID: ${s}`);
return s;
}
export function asNsid(s: string): Nsid {
if (!isNsid(s)) throw new TypeError(`Invalid NSID: ${s}`);
return s;
}
export function asEmail(s: string): EmailAddress {
if (!isEmail(s)) throw new TypeError(`Invalid email: ${s}`);
return s;
}
export function asISODate(s: string): ISODateString {
if (!isISODate(s)) throw new TypeError(`Invalid ISO date: ${s}`);
return s;
}
export function unsafeAsDid(s: string): Did {
return s as Did;
}
@@ -134,26 +41,10 @@ export function unsafeAsRefreshToken(s: string): RefreshToken {
return s as RefreshToken;
}
export function unsafeAsServiceToken(s: string): ServiceToken {
return s as ServiceToken;
}
export function unsafeAsSetupToken(s: string): SetupToken {
return s as SetupToken;
}
export function unsafeAsCid(s: string): Cid {
return s as Cid;
}
export function unsafeAsRkey(s: string): Rkey {
return s as Rkey;
}
export function unsafeAsAtUri(s: string): AtUri {
return s as AtUri;
}
export function unsafeAsNsid(s: string): Nsid {
return s as Nsid;
}
@@ -172,29 +63,7 @@ export function unsafeAsInviteCode(s: string): InviteCode {
return s as InviteCode;
}
export function unsafeAsPublicKeyMultibase(s: string): PublicKeyMultibase {
return s as PublicKeyMultibase;
}
export function unsafeAsDidKey(s: string): DidKeyString {
return s as DidKeyString;
}
export function unsafeAsScopeSet(s: string): ScopeSet {
return s as ScopeSet;
}
export function parseAtUri(
uri: AtUri,
): { repo: Did; collection: Nsid; rkey: Rkey } {
const parts = uri.replace("at://", "").split("/");
return {
repo: unsafeAsDid(parts[0]),
collection: unsafeAsNsid(parts[1]),
rkey: unsafeAsRkey(parts[2]),
};
}
export function makeAtUri(repo: Did, collection: Nsid, rkey: Rkey): AtUri {
return `at://${repo}/${collection}/${rkey}` as AtUri;
}
-49
View File
@@ -1,49 +0,0 @@
export function assertNever(x: never, message?: string): never {
throw new Error(message ?? `Unexpected value: ${JSON.stringify(x)}`);
}
export function exhaustive<T extends string | number | symbol>(
value: T,
handlers: Record<T, () => void>,
): void {
const handler = handlers[value];
if (handler) {
handler();
} else {
assertNever(value as never, `Unhandled case: ${String(value)}`);
}
}
export function exhaustiveMap<T extends string | number | symbol, R>(
value: T,
handlers: Record<T, () => R>,
): R {
const handler = handlers[value];
if (handler) {
return handler();
}
return assertNever(value as never, `Unhandled case: ${String(value)}`);
}
export async function exhaustiveAsync<T extends string | number | symbol>(
value: T,
handlers: Record<T, () => Promise<void>>,
): Promise<void> {
const handler = handlers[value];
if (handler) {
await handler();
} else {
assertNever(value as never, `Unhandled case: ${String(value)}`);
}
}
export async function exhaustiveMapAsync<T extends string | number | symbol, R>(
value: T,
handlers: Record<T, () => Promise<R>>,
): Promise<R> {
const handler = handlers[value];
if (handler) {
return handler();
}
return assertNever(value as never, `Unhandled case: ${String(value)}`);
}
-5
View File
@@ -1,5 +0,0 @@
export * from "./result.ts";
export * from "./branded.ts";
export * from "./exhaustive.ts";
export * from "./api.ts";
export * from "./routes.ts";
-85
View File
@@ -22,90 +22,5 @@ export function isErr<T, E>(
return !result.ok;
}
export function map<T, U, E>(
result: Result<T, E>,
fn: (t: T) => U,
): Result<U, E> {
return result.ok ? ok(fn(result.value)) : result;
}
export function mapErr<T, E, F>(
result: Result<T, E>,
fn: (e: E) => F,
): Result<T, F> {
return result.ok ? result : err(fn(result.error));
}
export function flatMap<T, U, E>(
result: Result<T, E>,
fn: (t: T) => Result<U, E>,
): Result<U, E> {
return result.ok ? fn(result.value) : result;
}
export function unwrap<T, E>(result: Result<T, E>): T {
if (result.ok) return result.value;
throw result.error instanceof Error
? result.error
: new Error(String(result.error));
}
export function unwrapOr<T, E>(result: Result<T, E>, defaultValue: T): T {
return result.ok ? result.value : defaultValue;
}
export function unwrapOrElse<T, E>(result: Result<T, E>, fn: (e: E) => T): T {
return result.ok ? result.value : fn(result.error);
}
export function match<T, E, U>(
result: Result<T, E>,
handlers: { ok: (t: T) => U; err: (e: E) => U },
): U {
return result.ok ? handlers.ok(result.value) : handlers.err(result.error);
}
export async function tryAsync<T>(
fn: () => Promise<T>,
): Promise<Result<T, Error>> {
try {
return ok(await fn());
} catch (e) {
return err(e instanceof Error ? e : new Error(String(e)));
}
}
export async function tryAsyncWith<T, E>(
fn: () => Promise<T>,
mapError: (e: unknown) => E,
): Promise<Result<T, E>> {
try {
return ok(await fn());
} catch (e) {
return err(mapError(e));
}
}
export function fromNullable<T>(value: T | null | undefined): Result<T, null> {
return value != null ? ok(value) : err(null);
}
export function toNullable<T, E>(result: Result<T, E>): T | null {
return result.ok ? result.value : null;
}
export function collect<T, E>(results: Result<T, E>[]): Result<T[], E> {
const values: T[] = [];
for (const result of results) {
if (!result.ok) return result;
values.push(result.value);
}
return ok(values);
}
export async function collectAsync<T, E>(
results: Promise<Result<T, E>>[],
): Promise<Result<T[], E>> {
const settled = await Promise.all(results);
return collect(settled);
}
-329
View File
@@ -1,329 +0,0 @@
import { z } from "zod";
import {
unsafeAsAccessToken,
unsafeAsAtUri,
unsafeAsCid,
unsafeAsDid,
unsafeAsEmail,
unsafeAsHandle,
unsafeAsInviteCode,
unsafeAsISODate,
unsafeAsNsid,
unsafeAsPublicKeyMultibase,
unsafeAsRefreshToken,
unsafeAsRkey,
} from "./branded.ts";
const did = z.string().transform((s) => unsafeAsDid(s));
const handle = z.string().transform((s) => unsafeAsHandle(s));
const accessToken = z.string().transform((s) => unsafeAsAccessToken(s));
const refreshToken = z.string().transform((s) => unsafeAsRefreshToken(s));
const cid = z.string().transform((s) => unsafeAsCid(s));
const nsid = z.string().transform((s) => unsafeAsNsid(s));
const atUri = z.string().transform((s) => unsafeAsAtUri(s));
const _rkey = z.string().transform((s) => unsafeAsRkey(s));
const isoDate = z.string().transform((s) => unsafeAsISODate(s));
const email = z.string().transform((s) => unsafeAsEmail(s));
const inviteCode = z.string().transform((s) => unsafeAsInviteCode(s));
const publicKeyMultibase = z.string().transform((s) =>
unsafeAsPublicKeyMultibase(s)
);
export const verificationChannel = z.enum([
"email",
"discord",
"telegram",
"signal",
]);
export const didType = z.enum(["plc", "web", "web-external"]);
export const accountStatus = z.enum([
"active",
"deactivated",
"migrated",
"suspended",
"deleted",
]);
export const sessionType = z.enum(["oauth", "legacy", "app_password"]);
export const reauthMethod = z.enum(["password", "totp", "passkey"]);
export const sessionSchema = z.object({
did: did,
handle: handle,
email: email.optional(),
emailConfirmed: z.boolean().optional(),
preferredChannel: verificationChannel.optional(),
preferredChannelVerified: z.boolean().optional(),
isAdmin: z.boolean().optional(),
active: z.boolean().optional(),
status: accountStatus.optional(),
migratedToPds: z.string().optional(),
migratedAt: isoDate.optional(),
accessJwt: accessToken,
refreshJwt: refreshToken,
});
export const serverLinksSchema = z.object({
privacyPolicy: z.string().optional(),
termsOfService: z.string().optional(),
});
export const serverDescriptionSchema = z.object({
availableUserDomains: z.array(z.string()),
inviteCodeRequired: z.boolean(),
links: serverLinksSchema.optional(),
version: z.string().optional(),
availableCommsChannels: z.array(verificationChannel).optional(),
selfHostedDidWebEnabled: z.boolean().optional(),
});
export const appPasswordSchema = z.object({
name: z.string(),
createdAt: isoDate,
scopes: z.string().optional(),
createdByController: z.string().optional(),
});
export const createdAppPasswordSchema = z.object({
name: z.string(),
password: z.string(),
createdAt: isoDate,
scopes: z.string().optional(),
});
export const inviteCodeUseSchema = z.object({
usedBy: did,
usedByHandle: handle.optional(),
usedAt: isoDate,
});
export const inviteCodeInfoSchema = z.object({
code: inviteCode,
available: z.number(),
disabled: z.boolean(),
forAccount: did,
createdBy: did,
createdAt: isoDate,
uses: z.array(inviteCodeUseSchema),
});
export const sessionInfoSchema = z.object({
id: z.string(),
sessionType: sessionType,
clientName: z.string().nullable(),
createdAt: isoDate,
expiresAt: isoDate,
isCurrent: z.boolean(),
});
export const listSessionsResponseSchema = z.object({
sessions: z.array(sessionInfoSchema),
});
export const totpStatusSchema = z.object({
enabled: z.boolean(),
hasBackupCodes: z.boolean(),
});
export const totpSecretSchema = z.object({
uri: z.string(),
qrBase64: z.string(),
});
export const enableTotpResponseSchema = z.object({
success: z.boolean(),
backupCodes: z.array(z.string()),
});
export const passkeyInfoSchema = z.object({
id: z.string(),
credentialId: z.string(),
friendlyName: z.string().nullable(),
createdAt: isoDate,
lastUsed: isoDate.nullable(),
});
export const listPasskeysResponseSchema = z.object({
passkeys: z.array(passkeyInfoSchema),
});
export const trustedDeviceSchema = z.object({
id: z.string(),
userAgent: z.string().nullable(),
friendlyName: z.string().nullable(),
trustedAt: isoDate.nullable(),
trustedUntil: isoDate.nullable(),
lastSeenAt: isoDate,
});
export const listTrustedDevicesResponseSchema = z.object({
devices: z.array(trustedDeviceSchema),
});
export const reauthStatusSchema = z.object({
requiresReauth: z.boolean(),
lastReauthAt: isoDate.nullable(),
availableMethods: z.array(reauthMethod),
});
export const reauthResponseSchema = z.object({
success: z.boolean(),
reauthAt: isoDate,
});
export const notificationPrefsSchema = z.object({
preferredChannel: verificationChannel,
email: email,
discordUsername: z.string().nullable(),
discordVerified: z.boolean(),
telegramUsername: z.string().nullable(),
telegramVerified: z.boolean(),
signalUsername: z.string().nullable(),
signalVerified: z.boolean(),
});
export const verificationMethodSchema = z.object({
id: z.string(),
type: z.string(),
controller: z.string(),
publicKeyMultibase: publicKeyMultibase,
});
export const serviceEndpointSchema = z.object({
id: z.string(),
type: z.string(),
serviceEndpoint: z.string(),
});
export const didDocumentSchema = z.object({
"@context": z.array(z.string()),
id: did,
alsoKnownAs: z.array(z.string()),
verificationMethod: z.array(verificationMethodSchema),
service: z.array(serviceEndpointSchema),
});
export const repoDescriptionSchema = z.object({
handle: handle,
did: did,
didDoc: didDocumentSchema,
collections: z.array(nsid),
handleIsCorrect: z.boolean(),
});
export const recordInfoSchema = z.object({
uri: atUri,
cid: cid,
value: z.unknown(),
});
export const listRecordsResponseSchema = z.object({
records: z.array(recordInfoSchema),
cursor: z.string().optional(),
});
export const recordResponseSchema = z.object({
uri: atUri,
cid: cid,
value: z.unknown(),
});
export const createRecordResponseSchema = z.object({
uri: atUri,
cid: cid,
});
export const serverStatsSchema = z.object({
userCount: z.number(),
repoCount: z.number(),
recordCount: z.number(),
blobStorageBytes: z.number(),
});
export const serverConfigSchema = z.object({
serverName: z.string(),
primaryColor: z.string().nullable(),
primaryColorDark: z.string().nullable(),
secondaryColor: z.string().nullable(),
secondaryColorDark: z.string().nullable(),
logoCid: cid.nullable(),
});
export const passwordStatusSchema = z.object({
hasPassword: z.boolean(),
});
export const successResponseSchema = z.object({
success: z.boolean(),
});
export const legacyLoginPreferenceSchema = z.object({
allowLegacyLogin: z.boolean(),
hasMfa: z.boolean(),
});
export const accountInfoSchema = z.object({
did: did,
handle: handle,
email: email.optional(),
indexedAt: isoDate,
emailConfirmedAt: isoDate.optional(),
invitesDisabled: z.boolean().optional(),
deactivatedAt: isoDate.optional(),
});
export const searchAccountsResponseSchema = z.object({
cursor: z.string().optional(),
accounts: z.array(accountInfoSchema),
});
export type ValidatedSession = z.infer<typeof sessionSchema>;
export type ValidatedServerDescription = z.infer<
typeof serverDescriptionSchema
>;
export type ValidatedAppPassword = z.infer<typeof appPasswordSchema>;
export type ValidatedCreatedAppPassword = z.infer<
typeof createdAppPasswordSchema
>;
export type ValidatedInviteCodeInfo = z.infer<typeof inviteCodeInfoSchema>;
export type ValidatedSessionInfo = z.infer<typeof sessionInfoSchema>;
export type ValidatedListSessionsResponse = z.infer<
typeof listSessionsResponseSchema
>;
export type ValidatedTotpStatus = z.infer<typeof totpStatusSchema>;
export type ValidatedTotpSecret = z.infer<typeof totpSecretSchema>;
export type ValidatedEnableTotpResponse = z.infer<
typeof enableTotpResponseSchema
>;
export type ValidatedPasskeyInfo = z.infer<typeof passkeyInfoSchema>;
export type ValidatedListPasskeysResponse = z.infer<
typeof listPasskeysResponseSchema
>;
export type ValidatedTrustedDevice = z.infer<typeof trustedDeviceSchema>;
export type ValidatedListTrustedDevicesResponse = z.infer<
typeof listTrustedDevicesResponseSchema
>;
export type ValidatedReauthStatus = z.infer<typeof reauthStatusSchema>;
export type ValidatedReauthResponse = z.infer<typeof reauthResponseSchema>;
export type ValidatedNotificationPrefs = z.infer<
typeof notificationPrefsSchema
>;
export type ValidatedDidDocument = z.infer<typeof didDocumentSchema>;
export type ValidatedRepoDescription = z.infer<typeof repoDescriptionSchema>;
export type ValidatedListRecordsResponse = z.infer<
typeof listRecordsResponseSchema
>;
export type ValidatedRecordResponse = z.infer<typeof recordResponseSchema>;
export type ValidatedCreateRecordResponse = z.infer<
typeof createRecordResponseSchema
>;
export type ValidatedServerStats = z.infer<typeof serverStatsSchema>;
export type ValidatedServerConfig = z.infer<typeof serverConfigSchema>;
export type ValidatedPasswordStatus = z.infer<typeof passwordStatusSchema>;
export type ValidatedSuccessResponse = z.infer<typeof successResponseSchema>;
export type ValidatedLegacyLoginPreference = z.infer<
typeof legacyLoginPreferenceSchema
>;
export type ValidatedAccountInfo = z.infer<typeof accountInfoSchema>;
export type ValidatedSearchAccountsResponse = z.infer<
typeof searchAccountsResponseSchema
>;
-12
View File
@@ -61,18 +61,6 @@ export function finish(_state: TotpBackup): TotpIdle {
return idleState;
}
export function isIdle(state: TotpSetupState): state is TotpIdle {
return state.step === "idle";
}
export function isQr(state: TotpSetupState): state is TotpQr {
return state.step === "qr";
}
export function isVerify(state: TotpSetupState): state is TotpVerify {
return state.step === "verify";
}
export function isBackup(state: TotpSetupState): state is TotpBackup {
return state.step === "backup";
}