Complete TypeScript type definitions for all tRPC procedures.
- Core Types
- System Types
- Auth Types
- County Types
- User Types
- Search Types
- Query Types
- Voice Types
- Feedback Types
- Contact Types
- Subscription Types
- Admin Types
- Agency Admin Types
- Integration Types
- Referral Types
interface User {
id: number;
supabaseId: string;
email: string | null;
name: string | null;
role: "user" | "admin";
tier: "free" | "pro" | "enterprise";
selectedCountyId: number | null;
stripeCustomerId: string | null;
subscriptionStatus: string | null;
subscriptionEndDate: Date | null;
createdAt: Date;
updatedAt: Date;
lastLoginAt: Date | null;
disclaimerAcknowledgedAt: Date | null;
}interface TrpcContext {
req: Request;
res: Response;
user: User | null;
}Input:
{
timestamp: number; // >= 0
}Output:
{
ok: boolean;
}Input:
{
title: string; // min 1 char
content: string; // min 1 char
}Output:
{
success: boolean;
}Input: None
Output:
User | nullInput: None
Output:
{
success: true;
}interface County {
id: number;
name: string;
state: string;
stateCode: string;
createdAt: Date;
updatedAt: Date;
}Input: None
Output:
{
counties: County[];
grouped: Record<string, County[]>; // Grouped by state
}Input:
{
id: number;
}Output:
County | nullInput: None
Output:
{
count: number; // Queries used today
limit: number; // Daily query limit
tier: "free" | "pro" | "enterprise";
}Input: None
Output:
{
acknowledgedAt: Date;
}Input: None
Output:
{
hasAcknowledged: boolean;
}Input:
{
countyId: number;
}Output:
{
success: boolean;
}Input:
{
limit?: number; // 1-100, default 10
}Output:
Array<{
id: number;
queryText: string;
responseText: string;
protocolRefs: string[];
createdAt: Date;
countyId: number;
}>Input: None
Output:
{
counties: County[];
canAdd: boolean;
currentCount: number;
maxAllowed: number;
tier: "free" | "pro" | "enterprise";
}Input:
{
countyId: number;
isPrimary?: boolean; // default false
}Output:
{
success: boolean;
error?: string;
}Input:
{
countyId: number;
}Output:
{
success: boolean;
}Input:
{
countyId: number;
}Output:
{
success: boolean;
}Input: None
Output:
County | nullinterface SearchResultItem {
id: number;
protocolNumber: string;
protocolTitle: string;
section: string | null;
content: string; // Truncated to 500 chars
fullContent: string; // Complete content
relevanceScore: number; // 0-1 similarity
countyId: number;
sourcePdfUrl: null;
protocolEffectiveDate: null;
lastVerifiedAt: null;
protocolYear: null;
}Input:
{
query: string; // 1-500 chars
countyId?: number; // Optional county filter
limit?: number; // 1-50, default 10
stateFilter?: string; // Two-letter state code
}Output:
{
results: SearchResultItem[];
totalFound: number;
query: string; // Original query
normalizedQuery: string; // Normalized query
fromCache: boolean;
latencyMs: number;
}Input:
{
id: number;
}Output:
{
id: number;
protocolNumber: string;
protocolTitle: string;
section: string | null;
content: string;
embedding: number[] | null;
agencyId: number;
createdAt: Date;
updatedAt: Date;
} | nullInput: None
Output:
{
totalProtocols: number;
totalAgencies: number;
lastUpdated: Date;
}Input: None
Output:
Array<{
state: string;
countyCount: number;
protocolCount: number;
}>Input: None
Output:
{
totalProtocols: number;
totalAgencies: number;
totalStates: number;
}Input:
{
state: string; // Two-letter code
}Output:
Array<{
id: number;
name: string;
state: string;
protocolCount: number;
}>Input:
{
state?: string; // Optional state filter
}Output: Same as AgenciesByState
Input:
{
query: string; // 1-500 chars
agencyId: number; // MySQL county ID
limit?: number; // 1-50, default 10
}Output: Same as Search.Semantic
Input:
{
countyId: number;
queryText: string; // 1-1000 chars
}Output:
{
success: boolean;
error: string | null;
response: {
text: string; // Claude's response
protocolRefs: string[]; // Referenced protocols
model: string; // Claude model used
tokens: {
input: number;
output: number;
};
responseTimeMs: number;
normalizedQuery: string; // Normalized query
queryIntent: string; // Detected intent
isComplexQuery: boolean;
} | null;
}Input:
{
limit?: number; // 1-100, default 50
}Output:
Array<{
id: number;
userId: number;
countyId: number;
queryText: string;
responseText: string;
protocolRefs: string[];
createdAt: Date;
}>interface SearchHistoryEntry {
id: number;
userId: number;
queryText: string;
countyId: number | null;
timestamp: Date;
deviceId: string | null;
synced: boolean;
}Input:
{
limit?: number; // 1-100, default 50
}Output:
SearchHistoryEntry[]Input:
{
localQueries: Array<{
queryText: string; // 1-500 chars
countyId?: number;
timestamp: string | Date;
deviceId?: string; // Max 64 chars
}>;
}Output:
{
success: boolean;
merged: number;
serverHistory: SearchHistoryEntry[];
}Input: None
Output:
{
success: boolean;
}Input:
{
entryId: number;
}Output:
{
success: boolean;
}Input:
{
audioUrl: string; // Must be from authorized domain
language?: string; // Optional language hint
}Output:
{
success: boolean;
error: string | null;
text: string | null;
}Input:
{
audioBase64: string; // Max 10MB
mimeType: string; // e.g., "audio/webm"
}Output:
{
url: string; // Storage URL
}Input:
{
category: "error" | "suggestion" | "general";
subject: string; // 1-255 chars
message: string; // min 1 char
protocolRef?: string; // max 255 chars
}Output:
{
success: boolean;
error: string | null;
}Input: None
Output:
Array<{
id: number;
category: "error" | "suggestion" | "general";
subject: string;
message: string;
status: "pending" | "reviewed" | "resolved" | "dismissed";
createdAt: Date;
adminNotes?: string;
}>Input:
{
name: string; // 1-255 chars
email: string; // Valid email, max 320 chars
message: string; // 10-5000 chars
}Output:
{
success: boolean;
error: string | null;
}Input:
{
plan: "monthly" | "annual";
successUrl: string; // Valid URL
cancelUrl: string; // Valid URL
}Output:
{
success: boolean;
error: string | null;
url: string | null; // Stripe checkout URL
}Input:
{
returnUrl: string; // Valid URL
}Output:
{
success: boolean;
error: string | null;
url: string | null; // Customer portal URL
}Input: None
Output:
{
tier: "free" | "pro" | "enterprise";
subscriptionStatus: string | null;
subscriptionEndDate: Date | null;
}Input:
{
agencyId: number;
tier: "starter" | "professional" | "enterprise";
seatCount: number; // 1-1000
interval: "monthly" | "annual";
successUrl: string;
cancelUrl: string;
}Output:
{
success: boolean;
error: string | null;
url: string | null;
}Input:
{
status?: "pending" | "reviewed" | "resolved" | "dismissed";
limit?: number; // 1-100, default 50
offset?: number; // default 0
}Output:
{
feedback: Array<Feedback>;
total: number;
}Input:
{
feedbackId: number;
status: "pending" | "reviewed" | "resolved" | "dismissed";
adminNotes?: string;
}Output:
{
success: boolean;
}Input:
{
tier?: "free" | "pro" | "enterprise";
role?: "user" | "admin";
limit?: number; // 1-100, default 50
offset?: number; // default 0
}Output:
{
users: User[];
total: number;
}Input:
{
userId: number;
role: "user" | "admin";
}Output:
{
success: boolean;
}Input:
{
status?: "pending" | "reviewed" | "resolved";
limit?: number; // 1-100, default 50
offset?: number; // default 0
}Output:
{
submissions: ContactSubmission[];
total: number;
}Input:
{
submissionId: number;
status: "pending" | "reviewed" | "resolved";
}Output:
{
success: boolean;
}interface AuditLog {
id: number;
userId: number;
action: string;
targetType: string;
targetId: string;
details: object;
createdAt: Date;
}Input:
{
limit?: number; // 1-100, default 50
offset?: number; // default 0
}Output:
{
logs: AuditLog[];
total: number;
}interface Agency {
id: number;
name: string;
state: string;
stateCode: string;
contactEmail: string | null;
contactPhone: string | null;
address: string | null;
settings: object | null;
tier: "starter" | "professional" | "enterprise" | null;
seatCount: number | null;
createdAt: Date;
updatedAt: Date;
}interface AgencyMember {
id: number;
agencyId: number;
userId: number;
role: "owner" | "admin" | "protocol_author" | "member";
joinedAt: Date;
user?: {
id: number;
name: string;
email: string;
} | null;
}Input: None
Output:
Array<{
id: number;
name: string;
role: "owner" | "admin" | "protocol_author" | "member";
// ...Agency fields
}>Input:
{
agencyId: number;
}Output:
AgencyInput:
{
agencyId: number;
name?: string; // 1-255 chars
contactEmail?: string; // Valid email, max 320
contactPhone?: string; // Max 20 chars
address?: string; // Max 500 chars
settings?: {
brandColor?: string;
allowSelfRegistration?: boolean;
requireEmailVerification?: boolean;
protocolApprovalRequired?: boolean;
};
}Output:
{
success: boolean;
}Input:
{
agencyId: number;
}Output:
AgencyMember[]Input:
{
agencyId: number;
email: string;
role?: "admin" | "protocol_author" | "member"; // default "member"
}Output:
{
success: boolean;
token: string;
}Input:
{
agencyId: number;
memberId: number;
role: "admin" | "protocol_author" | "member";
}Output:
{
success: boolean;
}Input:
{
agencyId: number;
memberId: number;
}Output:
{
success: boolean;
}interface ProtocolVersion {
id: number;
agencyId: number;
protocolNumber: string;
title: string;
version: string;
status: "draft" | "review" | "approved" | "published" | "archived";
sourceFileUrl: string;
effectiveDate: Date | null;
metadata: object | null;
createdBy: number;
approvedBy: number | null;
createdAt: Date;
updatedAt: Date;
}Input:
{
agencyId: number;
status?: "draft" | "review" | "approved" | "published" | "archived";
limit?: number; // 1-100, default 50
offset?: number; // default 0
}Output:
{
protocols: ProtocolVersion[];
total: number;
}Input:
{
agencyId: number;
fileName: string; // Max 255 chars
fileBase64: string; // Max 20MB
mimeType?: string; // default "application/pdf"
protocolNumber: string; // Max 50 chars
title: string; // Max 255 chars
version?: string; // Max 20 chars, default "1.0"
effectiveDate?: string; // ISO date string
}Output:
{
success: boolean;
uploadId: number;
versionId: number;
fileUrl: string;
}interface ProtocolUpload {
id: number;
agencyId: number;
userId: number;
fileName: string;
fileUrl: string;
fileSize: number;
mimeType: string;
status: "pending" | "processing" | "completed" | "failed";
progress: number | null;
error: string | null;
createdAt: Date;
updatedAt: Date;
}Input:
{
agencyId: number;
uploadId: number;
}Output:
ProtocolUploadInput:
{
agencyId: number;
versionId: number;
status: "draft" | "review" | "approved" | "published" | "archived";
}Output:
{
success: boolean;
}Input:
{
agencyId: number;
versionId: number;
}Output:
{
success: boolean;
}Input:
{
agencyId: number;
versionId: number;
}Output:
{
success: boolean;
}Input:
{
agencyId: number;
protocolNumber: string;
}Output:
ProtocolVersion[]Input:
{
agencyId: number;
fromVersionId: number;
newVersion: string; // Max 20 chars
changes?: string; // Change log
}Output:
{
success: boolean;
versionId: number;
}type IntegrationPartner = "imagetrend" | "esos" | "zoll" | "emscloud";Input:
{
partner: IntegrationPartner;
agencyId?: string; // Max 100 chars
agencyName?: string; // Max 255 chars
searchTerm?: string; // Max 500 chars
userAge?: number; // NOT STORED (HIPAA)
impression?: string; // NOT STORED (HIPAA)
responseTimeMs?: number;
resultCount?: number;
}Output:
{
success: boolean;
logged: boolean;
requestId: string;
}Input:
{
partner?: IntegrationPartner;
days?: number; // 1-365, default 30
}Output:
{
stats: Array<{
partner: string;
accessCount: number;
uniqueAgencies: number;
avgResponseTimeMs: number | null;
}>;
total: number;
periodDays: number;
}Input:
{
partner?: IntegrationPartner;
limit?: number; // 1-100, default 50
offset?: number; // default 0
}Output:
{
logs: IntegrationLog[];
total: number;
}Input:
{
partner?: IntegrationPartner;
days?: number; // 1-90, default 30
}Output:
{
dailyUsage: Array<{
date: string;
partner: string;
count: number;
}>;
}type ReferralTier = "bronze" | "silver" | "gold" | "platinum" | "ambassador";Input: None
Output:
{
code: string;
usesCount: number;
createdAt: Date;
}Input: None
Output:
{
totalReferrals: number;
successfulReferrals: number;
pendingReferrals: number;
proDaysEarned: number;
creditsEarned: number;
currentTier: ReferralTier;
rank: number | null;
nextTierProgress: number; // 0-100
nextTierName: string | null;
referralsToNextTier: number;
}Input:
{
limit?: number; // 1-50, default 20
offset?: number; // default 0
}Output:
{
referrals: Array<{
id: number;
redeemedAt: Date;
convertedToPaid: boolean;
conversionDate: Date | null;
reward: object;
referredUser: {
name: string;
email: string; // Masked
};
}>;
total: number;
}Input:
{
code: string; // 1-20 chars
}Output:
{
valid: boolean;
error?: string;
referrerName?: string;
benefits?: {
trialDays: number;
description: string;
};
}Input:
{
code: string; // 1-20 chars
}Output:
{
success: boolean;
benefit: string;
}Input: None
Output:
{
sms: string;
whatsapp: string;
email: {
subject: string;
body: string;
};
generic: string;
shareUrl: string;
code: string;
}Input:
{
limit?: number; // 1-100, default 25
timeframe?: "all_time" | "this_month" | "this_week"; // default "all_time"
}Output:
{
leaderboard: Array<{
rank: number;
userId: number;
userName: string;
totalReferrals: number;
successfulReferrals: number;
tier: string;
}>;
timeframe: string;
}type ViralEventType =
| "referral_code_generated"
| "referral_code_shared"
| "referral_code_copied"
| "share_button_tapped"
| "shift_share_shown"
| "shift_share_accepted"
| "shift_share_dismissed"
| "social_share_completed";Input:
{
eventType: ViralEventType;
metadata?: {
shareMethod?: "sms" | "whatsapp" | "email" | "copy" | "qr";
referralCode?: string;
platform?: string;
};
}Output:
{
tracked: boolean;
}import type { AppRouter } from "../server/routers";
import { inferRouterInputs } from "@trpc/server";
type RouterInput = inferRouterInputs<AppRouter>;
// Example: Get input type for any procedure
type QuerySubmitInput = RouterInput["query"]["submit"];
type SearchSemanticInput = RouterInput["search"]["semantic"];import type { AppRouter } from "../server/routers";
import { inferRouterOutputs } from "@trpc/server";
type RouterOutput = inferRouterOutputs<AppRouter>;
// Example: Get output type for any procedure
type QuerySubmitOutput = RouterOutput["query"]["submit"];
type SearchSemanticOutput = RouterOutput["search"]["semantic"];import type { AppRouter } from "../server/routers";
import { inferProcedureOutput, inferProcedureInput } from "@trpc/server";
// For specific procedures
type UserUsageOutput = inferProcedureOutput<AppRouter["user"]["usage"]>;
type UserUsageInput = inferProcedureInput<AppRouter["user"]["usage"]>;All input validation is handled by Zod schemas. Here are common validation patterns:
z.string()
.min(1) // Required
.max(255) // Max length
.email() // Email format
.url() // URL format
.optional() // Optional field
.default("value") // Default valuez.number()
.int() // Integer only
.min(0) // Minimum value
.max(100) // Maximum value
.default(10) // Default valuez.enum(["option1", "option2", "option3"])z.object({
field1: z.string(),
field2: z.number().optional(),
})z.array(z.object({
item: z.string()
}))For type-related questions or issues, contact: support@protocolguide.app