Replace void returns with { success: true } across all API endpoints

- Add successResponseSchema to common.ts for explicit success responses
- Update all auth, me, orgs, and admin procedures to return { success: true }
- Update contract.ts to use successResponseSchema instead of z.void()
- Add ast-grep rule to prevent future z.void() usage in contracts
- Add build:packages script to root package.json
- Fix test file lint errors with eslint-disable comments

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
RevIQ
2026-01-10 16:30:22 +08:00
parent 5e13809c0e
commit 1bf05465c3
31 changed files with 179 additions and 53 deletions

View File

@@ -22,7 +22,11 @@ import {
signupInputSchema,
verifyEmailInputSchema,
} from "./schemas/auth.js";
import { emailSchema, slugSchema } from "./schemas/common.js";
import {
emailSchema,
slugSchema,
successResponseSchema,
} from "./schemas/common.js";
import {
createInviteInputSchema,
createOrgInputSchema,
@@ -51,26 +55,32 @@ import {
export const contract = oc.router({
auth: oc.router({
// Signup and verification
signup: oc.input(signupInputSchema).output(z.void()),
verifyEmail: oc.input(verifyEmailInputSchema).output(z.void()),
resendVerificationEmail: oc.output(z.void()),
signup: oc.input(signupInputSchema).output(successResponseSchema),
verifyEmail: oc.input(verifyEmailInputSchema).output(successResponseSchema),
resendVerificationEmail: oc.output(successResponseSchema),
// Login flow
createLoginRequest: oc
.input(loginRequestInputSchema)
.output(loginRequestOutputSchema),
loginPassword: oc.input(loginPasswordInputSchema).output(z.void()),
loginPassword: oc
.input(loginPasswordInputSchema)
.output(successResponseSchema),
loginPasswordConfirm: oc
.input(z.object({ token: z.string() }))
.output(z.void()),
.output(successResponseSchema),
loginIfRequestIsCompleted: oc.output(loginStatusOutputSchema),
// Password reset
forgotPassword: oc.input(forgotPasswordInputSchema).output(z.void()),
resetPassword: oc.input(resetPasswordInputSchema).output(z.void()),
forgotPassword: oc
.input(forgotPasswordInputSchema)
.output(successResponseSchema),
resetPassword: oc
.input(resetPasswordInputSchema)
.output(successResponseSchema),
// Logout
logout: oc.output(z.void()),
logout: oc.output(successResponseSchema),
// WebAuthn procedures
webauthn: oc.router({
@@ -103,45 +113,53 @@ export const contract = oc.router({
response: z.custom<AuthenticationResponseJSON>(),
}),
)
.output(z.void()),
.output(successResponseSchema),
}),
}),
me: oc.router({
// Profile
get: oc.output(userProfileSchema),
setupProfile: oc.input(setupProfileInputSchema).output(z.void()),
updateProfile: oc.input(updateProfileInputSchema).output(z.void()),
delete: oc.input(z.object({ password: z.string() })).output(z.void()),
setupProfile: oc
.input(setupProfileInputSchema)
.output(successResponseSchema),
updateProfile: oc
.input(updateProfileInputSchema)
.output(successResponseSchema),
delete: oc
.input(z.object({ password: z.string() }))
.output(successResponseSchema),
// Auth status (for CLI and debugging)
authStatus: oc.output(authStatusOutputSchema),
// Authentication settings
setPassword: oc.input(setPasswordInputSchema).output(z.void()),
setPassword: oc.input(setPasswordInputSchema).output(successResponseSchema),
// Passkeys
passkeys: oc.router({
list: oc.output(z.array(passkeyOutputSchema)),
rename: oc
.input(z.object({ passkeyId: z.number(), name: z.string() }))
.output(z.void()),
delete: oc.input(z.object({ passkeyId: z.number() })).output(z.void()),
.output(successResponseSchema),
delete: oc
.input(z.object({ passkeyId: z.number() }))
.output(successResponseSchema),
}),
// Sessions & devices
listSessions: oc.output(z.array(sessionOutputSchema)),
revokeSession: oc
.input(z.object({ sessionId: z.number() }))
.output(z.void()),
revokeAllSessions: oc.output(z.void()),
.output(successResponseSchema),
revokeAllSessions: oc.output(successResponseSchema),
getDeviceInfo: oc.output(deviceOutputSchema),
trustDevice: oc.input(trustDeviceInputSchema).output(z.void()),
trustDevice: oc.input(trustDeviceInputSchema).output(successResponseSchema),
listTrustedDevices: oc.output(z.array(deviceOutputSchema)),
untrustDevice: oc
.input(z.object({ deviceId: z.number() }))
.output(z.void()),
revokeAllTrustedDevices: oc.output(z.void()),
.output(successResponseSchema),
revokeAllTrustedDevices: oc.output(successResponseSchema),
}),
orgs: oc.router({
@@ -159,19 +177,25 @@ export const contract = oc.router({
logoUrl: z.string().optional(),
}),
)
.output(z.void()),
delete: oc.input(z.object({ slug: slugSchema })).output(z.void()),
leave: oc.input(z.object({ slug: slugSchema })).output(z.void()),
.output(successResponseSchema),
delete: oc
.input(z.object({ slug: slugSchema }))
.output(successResponseSchema),
leave: oc
.input(z.object({ slug: slugSchema }))
.output(successResponseSchema),
// Members
members: oc.router({
list: oc
.input(z.object({ slug: slugSchema }))
.output(z.array(orgMemberOutputSchema)),
updateRole: oc.input(updateMemberRoleInputSchema).output(z.void()),
updateRole: oc
.input(updateMemberRoleInputSchema)
.output(successResponseSchema),
remove: oc
.input(z.object({ slug: slugSchema, userId: z.number() }))
.output(z.void()),
.output(successResponseSchema),
}),
// Invites
@@ -179,11 +203,13 @@ export const contract = oc.router({
list: oc
.input(z.object({ slug: slugSchema }))
.output(z.array(orgInviteOutputSchema)),
create: oc.input(createInviteInputSchema).output(z.void()),
create: oc.input(createInviteInputSchema).output(successResponseSchema),
cancel: oc
.input(z.object({ slug: slugSchema, inviteId: z.number() }))
.output(z.void()),
accept: oc.input(z.object({ token: z.string() })).output(z.void()),
.output(successResponseSchema),
accept: oc
.input(z.object({ token: z.string() }))
.output(successResponseSchema),
}),
// Sites
@@ -210,31 +236,39 @@ export const contract = oc.router({
logoUrl: z.string().optional(),
}),
)
.output(z.void()),
delete: oc.input(z.object({ slug: slugSchema })).output(z.void()),
.output(successResponseSchema),
delete: oc
.input(z.object({ slug: slugSchema }))
.output(successResponseSchema),
listSites: oc
.input(z.object({ slug: slugSchema }))
.output(z.array(orgSiteOutputSchema)),
addSite: oc.input(adminAddSiteInputSchema).output(z.void()),
addSite: oc.input(adminAddSiteInputSchema).output(successResponseSchema),
removeSite: oc
.input(z.object({ slug: slugSchema, domain: z.string() }))
.output(z.void()),
.output(successResponseSchema),
}),
// Admin user management
users: oc.router({
list: oc.output(z.array(userProfileSchema)),
get: oc.input(z.object({ email: emailSchema })).output(userProfileSchema),
create: oc.input(adminCreateUserInputSchema).output(z.void()),
update: oc.input(adminUpdateUserInputSchema).output(z.void()),
confirmEmail: oc.input(z.object({ email: emailSchema })).output(z.void()),
create: oc
.input(adminCreateUserInputSchema)
.output(successResponseSchema),
update: oc
.input(adminUpdateUserInputSchema)
.output(successResponseSchema),
confirmEmail: oc
.input(z.object({ email: emailSchema }))
.output(successResponseSchema),
}),
// Admin auth management
auth: oc.router({
completeLogin: oc
.input(z.object({ email: emailSchema }))
.output(z.void()),
.output(successResponseSchema),
}),
}),
});

View File

@@ -58,3 +58,9 @@ export const phoneSchema = z
.refine((val) => !val || isValidPhoneNumber(val), {
message: "Invalid phone number",
});
/**
* Success response schema for operations that don't return data
* Use instead of void to make responses more explicit
*/
export const successResponseSchema = z.object({ success: z.literal(true) });