@@ -33,16 +33,32 @@ export class DestinationsApi {
3333 }
3434
3535 /**
36- * Find the first destination whose slug or name loosely matches `query`.
36+ * Find the first destination whose slug or name matches `query`.
3737 *
3838 * Matching is case-insensitive and ignores non-alphanumeric characters on
39- * both sides, then checks substring containment. For example, the query
40- * `"waltdisneyworld"` matches a destination with slug
41- * `"walt-disney-world"`. Returns `undefined` when nothing matches.
39+ * both sides. An exact normalized match on either `slug` or `name` wins
40+ * over a substring match — so a query of `"walt-disney-world"` returns the
41+ * destination with that exact slug even when another entry's slug/name
42+ * contains it as a substring (e.g. `"walt-disney-world-extra"`). If no
43+ * exact match is found, the first substring containment match is
44+ * returned. Returns `undefined` when nothing matches.
4245 */
4346 async find ( query : string ) : Promise < Destination | undefined > {
4447 const res = await this . list ( ) ;
4548 const needle = normalize ( query ) ;
46- return ( res . destinations as Destination [ ] ) . find ( ( d ) => matches ( d , needle ) ) ;
49+ const destinations = ( res . destinations ?? [ ] ) as Destination [ ] ;
50+
51+ // Pass 1: exact normalized match on slug or name.
52+ for ( const d of destinations ) {
53+ const slug = normalize ( d . slug ?? '' ) ;
54+ const name = normalize ( d . name ?? '' ) ;
55+ if ( slug === needle || name === needle ) return d ;
56+ }
57+
58+ // Pass 2: substring containment.
59+ for ( const d of destinations ) {
60+ if ( matches ( d , needle ) ) return d ;
61+ }
62+ return undefined ;
4763 }
4864}
0 commit comments