@@ -19,6 +19,15 @@ import { fetchVisibleTermCounts } from "../../taxonomies/term-counts.js";
1919import type { ApiResult } from "../types.js" ;
2020
2121const NAME_PATTERN = / ^ [ a - z ] [ a - z 0 - 9 _ ] * $ / ;
22+ const MAX_GENERATED_TERM_SLUG_ATTEMPTS = 16 ;
23+
24+ function isTermSlugUniqueViolation ( error : unknown ) : boolean {
25+ const message = error instanceof Error ? error . message . toLowerCase ( ) : "" ;
26+ return (
27+ ( message . includes ( "unique constraint failed" ) || message . includes ( "duplicate key" ) ) &&
28+ message . includes ( "slug" )
29+ ) ;
30+ }
2231
2332// ---------------------------------------------------------------------------
2433// Response types
@@ -820,6 +829,7 @@ export async function handleTermCreate(
820829 translationOf ?: string ;
821830 } ,
822831) : Promise < ApiResult < TermResponse > > {
832+ let attemptedSlug = input . slug ;
823833 try {
824834 const locale = resolveConfiguredLocale ( input . locale ?? getI18nConfig ( ) ?. defaultLocale ?? "en" ) ;
825835 // Taxonomy definitions are per-locale, but terms can exist in any locale
@@ -829,21 +839,20 @@ export async function handleTermCreate(
829839 if ( ! lookup . success ) return lookup ;
830840
831841 const repo = new TaxonomyRepository ( db ) ;
832- const generatedSlug = input . slug === undefined ;
833- const slug = input . slug ?? ( await repo . generateUniqueSlug ( taxonomyName , input . label , locale ) ) ;
834842
835843 // Coerce empty-string parentId to undefined (treat as "no parent").
836844 const parentId =
837845 input . parentId === "" || input . parentId === undefined ? undefined : input . parentId ;
838846
839847 // Conflict check is scoped to locale (per-locale slugs are unique).
840- const existing = generatedSlug ? null : await repo . findBySlug ( taxonomyName , slug , locale ) ;
848+ const existing =
849+ input . slug === undefined ? null : await repo . findBySlug ( taxonomyName , input . slug , locale ) ;
841850 if ( existing ) {
842851 return {
843852 success : false ,
844853 error : {
845854 code : "CONFLICT" ,
846- message : `Term '${ slug } ' already exists in '${ taxonomyName } ' (${ locale } )` ,
855+ message : `Term '${ input . slug } ' already exists in '${ taxonomyName } ' (${ locale } )` ,
847856 } ,
848857 } ;
849858 }
@@ -875,15 +884,33 @@ export async function handleTermCreate(
875884 return { success : false , error : parentError } ;
876885 }
877886
878- const term = await repo . create ( {
879- name : taxonomyName ,
880- slug,
881- label : input . label ,
882- parentId : parentId ?? undefined ,
883- data : input . description ? { description : input . description } : undefined ,
884- locale,
885- translationOf : input . translationOf ,
886- } ) ;
887+ const create = ( slug : string ) =>
888+ repo . create ( {
889+ name : taxonomyName ,
890+ slug,
891+ label : input . label ,
892+ parentId : parentId ?? undefined ,
893+ data : input . description ? { description : input . description } : undefined ,
894+ locale,
895+ translationOf : input . translationOf ,
896+ } ) ;
897+ let term : Awaited < ReturnType < typeof create > > | undefined ;
898+ let lastSlugConflict : unknown ;
899+ if ( input . slug !== undefined ) {
900+ term = await create ( input . slug ) ;
901+ } else {
902+ for ( let attempt = 0 ; attempt < MAX_GENERATED_TERM_SLUG_ATTEMPTS ; attempt ++ ) {
903+ attemptedSlug = await repo . generateUniqueSlug ( taxonomyName , input . label , locale ) ;
904+ try {
905+ term = await create ( attemptedSlug ) ;
906+ break ;
907+ } catch ( error ) {
908+ if ( ! isTermSlugUniqueViolation ( error ) ) throw error ;
909+ lastSlugConflict = error ;
910+ }
911+ }
912+ }
913+ if ( ! term ) throw lastSlugConflict ?? new Error ( "Failed to create taxonomy term" ) ;
887914
888915 invalidateTermCache ( ) ;
889916
@@ -903,7 +930,16 @@ export async function handleTermCreate(
903930 } ,
904931 } ,
905932 } ;
906- } catch {
933+ } catch ( error ) {
934+ if ( isTermSlugUniqueViolation ( error ) ) {
935+ return {
936+ success : false ,
937+ error : {
938+ code : "CONFLICT" ,
939+ message : `Term with slug '${ attemptedSlug ?? "(generated)" } ' already exists in taxonomy '${ taxonomyName } '` ,
940+ } ,
941+ } ;
942+ }
907943 return {
908944 success : false ,
909945 error : { code : "TERM_CREATE_ERROR" , message : "Failed to create term" } ,
0 commit comments