Skip to content

Commit e12419b

Browse files
committed
refactor: improve type safety by replacing as-any casts with typed error helpers
1 parent 69baa1a commit e12419b

File tree

7 files changed

+64
-38
lines changed

7 files changed

+64
-38
lines changed

cli/src/commands/publish.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,8 @@ export async function handlePublish(agentIds: string[]): Promise<PublishResult>
129129
// Find the specific agent
130130
const matchingTemplate = loadedDefinitions.find(
131131
(template) =>
132-
template.id === agentId || (template as any).displayName === agentId,
132+
template.id === agentId ||
133+
(template as { displayName?: string }).displayName === agentId,
133134
)
134135

135136
if (!matchingTemplate) {

common/src/testing/errors.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/**
2+
* Shared error helper utilities for tests.
3+
* These provide typed ways to create errors with additional properties
4+
* like `code` (for Node.js filesystem errors) and `constraint` (for Postgres errors).
5+
*/
6+
7+
export interface NodeError extends Error {
8+
code?: string
9+
}
10+
11+
export interface PostgresError extends Error {
12+
code: string
13+
constraint?: string
14+
}
15+
16+
export const createNodeError = (message: string, code: string): NodeError => {
17+
const error: NodeError = new Error(message)
18+
error.code = code
19+
return error
20+
}
21+
22+
export const createPostgresError = (
23+
message: string,
24+
code: string,
25+
constraint?: string,
26+
): PostgresError => {
27+
const error = new Error(message) as PostgresError
28+
error.code = code
29+
if (constraint !== undefined) {
30+
error.constraint = constraint
31+
}
32+
return error
33+
}

packages/billing/src/__tests__/org-billing.test.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
clearMockedModules,
33
mockModule,
44
} from '@codebuff/common/testing/mock-modules'
5+
import { createPostgresError } from '@codebuff/common/testing/errors'
56
import { afterEach, beforeEach, describe, expect, it } from 'bun:test'
67

78
import {
@@ -255,10 +256,11 @@ describe('Organization Billing', () => {
255256
default: createDbMock({
256257
insert: () => ({
257258
values: () => {
258-
const error = new Error('Duplicate key')
259-
;(error as any).code = '23505'
260-
;(error as any).constraint = 'credit_ledger_pkey'
261-
throw error
259+
throw createPostgresError(
260+
'Duplicate key',
261+
'23505',
262+
'credit_ledger_pkey',
263+
)
262264
},
263265
}),
264266
}),

packages/internal/src/db/__tests__/transaction.test.ts

Lines changed: 10 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { afterEach, beforeEach, describe, expect, it, mock, spyOn } from 'bun:test'
2+
import { createPostgresError } from '@codebuff/common/testing/errors'
23

34
import {
45
getRetryableErrorDescription,
@@ -204,8 +205,7 @@ describe('transaction error handling', () => {
204205
})
205206

206207
it('should handle Error object with code property', () => {
207-
const error = new Error('Connection failed')
208-
;(error as Error & { code: string }).code = '08006'
208+
const error = createPostgresError('Connection failed', '08006')
209209
expect(getRetryableErrorDescription(error)).toBe('connection_failure')
210210
})
211211
})
@@ -352,9 +352,7 @@ describe('withSerializableTransaction', () => {
352352
async (callback) => {
353353
attempts++
354354
if (attempts === 1) {
355-
const error = new Error('serialization failure')
356-
;(error as Error & { code: string }).code = '40001'
357-
throw error
355+
throw createPostgresError('serialization failure', '40001')
358356
}
359357
return callback({} as Parameters<typeof callback>[0])
360358
},
@@ -377,9 +375,7 @@ describe('withSerializableTransaction', () => {
377375
async (callback) => {
378376
attempts++
379377
if (attempts <= 2) {
380-
const error = new Error('connection failure')
381-
;(error as Error & { code: string }).code = '08006'
382-
throw error
378+
throw createPostgresError('connection failure', '08006')
383379
}
384380
return callback({} as Parameters<typeof callback>[0])
385381
},
@@ -401,9 +397,7 @@ describe('withSerializableTransaction', () => {
401397
async (callback) => {
402398
attempts++
403399
if (attempts === 1) {
404-
const error = new Error('deadlock detected')
405-
;(error as Error & { code: string }).code = '40P01'
406-
throw error
400+
throw createPostgresError('deadlock detected', '40P01')
407401
}
408402
return callback({} as Parameters<typeof callback>[0])
409403
},
@@ -425,9 +419,7 @@ describe('withSerializableTransaction', () => {
425419
async (callback) => {
426420
attempts++
427421
if (attempts === 1) {
428-
const error = new Error('serialization failure')
429-
;(error as Error & { code: string }).code = '40001'
430-
throw error
422+
throw createPostgresError('serialization failure', '40001')
431423
}
432424
return callback({} as Parameters<typeof callback>[0])
433425
},
@@ -460,9 +452,7 @@ describe('withSerializableTransaction', () => {
460452
transactionSpy = spyOn(dbModule.db, 'transaction').mockImplementation(
461453
async () => {
462454
attempts++
463-
const error = new Error('unique violation')
464-
;(error as Error & { code: string }).code = '23505'
465-
throw error
455+
throw createPostgresError('unique violation', '23505')
466456
},
467457
)
468458

@@ -482,9 +472,7 @@ describe('withSerializableTransaction', () => {
482472
transactionSpy = spyOn(dbModule.db, 'transaction').mockImplementation(
483473
async () => {
484474
attempts++
485-
const error = new Error('syntax error')
486-
;(error as Error & { code: string }).code = '42601'
487-
throw error
475+
throw createPostgresError('syntax error', '42601')
488476
},
489477
)
490478

@@ -504,9 +492,7 @@ describe('withSerializableTransaction', () => {
504492
transactionSpy = spyOn(dbModule.db, 'transaction').mockImplementation(
505493
async () => {
506494
attempts++
507-
const error = new Error('foreign key violation')
508-
;(error as Error & { code: string }).code = '23503'
509-
throw error
495+
throw createPostgresError('foreign key violation', '23503')
510496
},
511497
)
512498

@@ -545,9 +531,7 @@ describe('withSerializableTransaction', () => {
545531
transactionSpy = spyOn(dbModule.db, 'transaction').mockImplementation(
546532
async () => {
547533
attempts++
548-
const error = new Error('persistent serialization failure')
549-
;(error as Error & { code: string }).code = '40001'
550-
throw error
534+
throw createPostgresError('persistent serialization failure', '40001')
551535
},
552536
)
553537

web/src/app/profile/components/api-keys-section.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ export function ApiKeysSection() {
9999
toast({
100100
title: 'Creation failed',
101101
description: e.message ?? String(e),
102-
variant: 'destructive' as any,
102+
variant: 'destructive',
103103
})
104104
},
105105
})
@@ -124,7 +124,7 @@ export function ApiKeysSection() {
124124
toast({
125125
title: 'Revoke failed',
126126
description: e.message ?? String(e),
127-
variant: 'destructive' as any,
127+
variant: 'destructive',
128128
})
129129
},
130130
})
@@ -179,7 +179,9 @@ export function ApiKeysSection() {
179179
<div className="rounded-md border border-destructive/50 bg-destructive/10 text-destructive px-3 py-2 flex items-center justify-between mb-4">
180180
<span className="text-sm">
181181
Error loading API keys:{' '}
182-
{(tokensError as any)?.message ?? 'Please try again.'}
182+
{tokensError instanceof Error
183+
? tokensError.message
184+
: 'Please try again.'}
183185
</span>
184186
<Button
185187
variant="outline"

web/src/app/profile/components/referrals-section.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,9 @@ export function ReferralsSection() {
7272
</CardHeader>
7373
<CardContent>
7474
<p>We couldn't fetch your referral data.</p>
75-
<code className="text-sm">{(error as any).message}</code>
75+
<code className="text-sm">
76+
{error instanceof Error ? error.message : 'Unknown error'}
77+
</code>
7678
</CardContent>
7779
</Card>
7880
</ProfileSection>

web/src/app/profile/components/security-section.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ export function SecuritySection() {
106106
toast({
107107
title: 'Revoke failed',
108108
description: e.message ?? String(e),
109-
variant: 'destructive' as any,
109+
variant: 'destructive',
110110
})
111111
},
112112
})
@@ -139,7 +139,7 @@ export function SecuritySection() {
139139
toast({
140140
title: 'Logout failed',
141141
description: e?.message ?? String(e),
142-
variant: 'destructive' as any,
142+
variant: 'destructive',
143143
})
144144
} finally {
145145
setIsBulkLoggingOut(false)
@@ -174,7 +174,9 @@ export function SecuritySection() {
174174
<div className="rounded-md border border-destructive/50 bg-destructive/10 text-destructive px-3 py-2 flex items-center justify-between">
175175
<span className="text-sm">
176176
Error loading sessions:{' '}
177-
{(sessionsError as any)?.message ?? 'Please try again.'}
177+
{sessionsError instanceof Error
178+
? sessionsError.message
179+
: 'Please try again.'}
178180
</span>
179181
<Button
180182
variant="outline"

0 commit comments

Comments
 (0)