Problem
"Did this insert fail because the unique constraint tripped?" is detected in four places, each slightly differently. The members route in particular only matches `unique` and misses the `duplicate key` substring postgres emits on its main error message — so a unique-violation under that wording becomes a 500 to the user instead of a friendly 409.
| File |
Pattern |
| `src/routes/(admin)/admin/organizations/+page.server.ts:104` |
helper `isDuplicateKeyError` — checks `duplicate key` OR `unique` on message + cause |
| `src/routes/(admin)/admin/organizations/[id]/+page.server.ts:184` |
inline regex `/duplicate key|unique/i` on message + cause |
| `src/routes/(portal)/portal/members/+page.server.ts:34` |
`err.message.includes('unique')` only — misses `duplicate key` path |
| `src/routes/(marketing)/register/+page.server.ts:81-83` |
inline includes on message + cause |
Suggested fix
Extract a single helper (e.g. `src/lib/server/services/_errors.ts`):
```ts
export function isUniqueViolation(err: unknown): boolean {
if (!(err instanceof Error)) return false;
const msg = err.message ?? '';
const cause = (err as { cause?: { message?: string } }).cause?.message ?? '';
const hay = `${msg}\n${cause}`.toLowerCase();
return hay.includes('duplicate key') || hay.includes('unique');
}
```
Then replace the four call sites. The members route gets correct `duplicate key` coverage for free.
Why this matters
The members-route gap is the user-visible part: portal users adding a duplicate member email today may see an unexpected 500 instead of the intended 409 message, depending on which postgres error path fires.
Test plan
- Unit test the helper against shaped postgres error objects (both `duplicate key` wording and `unique constraint` wording on `message` and on `cause.message`).
- Verify all four call sites still produce the same form-error shape.
Problem
"Did this insert fail because the unique constraint tripped?" is detected in four places, each slightly differently. The members route in particular only matches `unique` and misses the `duplicate key` substring postgres emits on its main error message — so a unique-violation under that wording becomes a 500 to the user instead of a friendly 409.
Suggested fix
Extract a single helper (e.g. `src/lib/server/services/_errors.ts`):
```ts
export function isUniqueViolation(err: unknown): boolean {
if (!(err instanceof Error)) return false;
const msg = err.message ?? '';
const cause = (err as { cause?: { message?: string } }).cause?.message ?? '';
const hay = `${msg}\n${cause}`.toLowerCase();
return hay.includes('duplicate key') || hay.includes('unique');
}
```
Then replace the four call sites. The members route gets correct `duplicate key` coverage for free.
Why this matters
The members-route gap is the user-visible part: portal users adding a duplicate member email today may see an unexpected 500 instead of the intended 409 message, depending on which postgres error path fires.
Test plan