@@ -4,30 +4,29 @@ import { getCurrentMessageContext } from "./message-context";
44import { hasValueBeenUsed , markValueAsUsed } from "./unique-selection" ;
55import { random } from "./utils" ;
66
7- type PrimitiveKey = string | number | boolean ;
8- type UniqueIdentifierResolver < T > =
9- | keyof any
10- | ( ( value : T ) => PrimitiveKey | null | undefined ) ;
11-
12- export interface OneOfUniqueBy < T > {
7+ export interface OneOfUniqueBy {
138 collection : string ;
14- itemId ?: UniqueIdentifierResolver < T > ;
159}
1610
1711export interface OneOfOptions < T > {
18- unique ?: boolean ;
19- uniqueBy ?: OneOfUniqueBy < T > ;
12+ uniqueBy ?: OneOfUniqueBy ;
2013}
2114
2215type NormalizedWeightedOption < T > = {
2316 value : T ;
2417 weight ?: number ;
18+ id ?: string | number | boolean ;
2519 uniqueKey ?: string ;
2620} ;
2721
28- type ResolvedUniqueBy < T > = {
22+ type ResolvedUniqueBy = {
2923 collection : string ;
30- itemId : UniqueIdentifierResolver < T > ;
24+ } ;
25+
26+ export type OneOfOptionWithId < T > = {
27+ value : T ;
28+ id : string | number | boolean ;
29+ weight ?: number ;
3130} ;
3231
3332export function optional < T > ( message : T ) : T | ( ( ) => [ ] ) {
@@ -59,6 +58,15 @@ export function randomSample<T>(n: number, array: T[]): T[] {
5958
6059 return result ;
6160}
61+
62+ export function oneOf < T > (
63+ options : Array < OneOfOptionWithId < T > > ,
64+ config : { uniqueBy : OneOfUniqueBy }
65+ ) : T ;
66+ export function oneOf < T > (
67+ options : Array < WeightedOneOfOption < T > > ,
68+ config ?: { uniqueBy ?: never }
69+ ) : T ;
6270export function oneOf < T > (
6371 options : Array < WeightedOneOfOption < T > > ,
6472 config ?: OneOfOptions < T >
@@ -67,16 +75,8 @@ export function oneOf<T>(
6775 throw new Error ( "oneOf requires at least one option" ) ;
6876 }
6977
70- const rawUniqueBy = config ?. uniqueBy ;
71- const enforceUnique = Boolean ( config ?. unique ?? rawUniqueBy ) ;
72-
73- if ( enforceUnique && ! rawUniqueBy ) {
74- throw new Error (
75- "oneOf unique mode requires a uniqueBy option with a collection name"
76- ) ;
77- }
78-
79- const uniqueBy = resolveUniqueBy ( rawUniqueBy ) ;
78+ const uniqueBy = resolveUniqueBy ( config ?. uniqueBy ) ;
79+ const enforceUnique = Boolean ( uniqueBy ) ;
8080
8181 const normalized = options . map ( ( option ) =>
8282 isWeightedOption ( option ) ? { ...option } : { value : option }
@@ -123,18 +123,21 @@ export function oneOf<T>(
123123 }
124124 }
125125
126- const candidateBase = normalized . map < NormalizedWeightedOption < T > > ( ( option ) => ( {
127- value : option . value ,
128- weight : option . weight ,
129- } ) ) ;
126+ const candidateBase = normalized . map < NormalizedWeightedOption < T > > (
127+ ( option ) => ( {
128+ value : option . value ,
129+ weight : option . weight ,
130+ id : option . id ,
131+ } )
132+ ) ;
130133
131134 let candidateOptions = candidateBase ;
132135
133136 if ( enforceUnique && uniqueBy ) {
134137 candidateOptions = candidateBase
135138 . map ( ( option ) => ( {
136139 ...option ,
137- uniqueKey : buildUniqueKey ( option . value , uniqueBy ) ,
140+ uniqueKey : buildUniqueKey ( option . id ) ,
138141 } ) )
139142 . filter (
140143 ( option ) => ! hasValueBeenUsed ( uniqueBy . collection , option . uniqueKey ! )
@@ -185,7 +188,7 @@ export function oneOf<T>(
185188
186189function recordUniqueSelection < T > (
187190 option : NormalizedWeightedOption < T > ,
188- uniqueBy : ResolvedUniqueBy < T > | undefined ,
191+ uniqueBy : ResolvedUniqueBy | undefined ,
189192 enforceUnique : boolean
190193) : void {
191194 if ( ! enforceUnique || ! uniqueBy || ! option . uniqueKey ) {
@@ -200,51 +203,26 @@ function recordUniqueSelection<T>(
200203 markValueAsUsed ( uniqueBy . collection , option . uniqueKey ) ;
201204}
202205
203- function buildUniqueKey < T > (
204- value : T ,
205- uniqueBy : ResolvedUniqueBy < T >
206- ) : string {
207- const rawValue =
208- typeof uniqueBy . itemId === "function"
209- ? uniqueBy . itemId ( value )
210- : readProperty ( value , uniqueBy . itemId ) ;
211-
212- if (
213- rawValue === undefined ||
214- rawValue === null ||
215- ( typeof rawValue !== "string" &&
216- typeof rawValue !== "number" &&
217- typeof rawValue !== "boolean" )
218- ) {
219- throw new Error (
220- `oneOf uniqueBy.itemId "${ String (
221- uniqueBy . itemId
222- ) } " must resolve to a string, number, or boolean`
223- ) ;
224- }
225-
226- const prefix = typeof rawValue ;
227- return `${ prefix } :${ String ( rawValue ) } ` ;
228- }
229-
230- function readProperty < T > ( value : T , key : keyof any ) : unknown {
206+ function buildUniqueKey ( id : unknown ) : string {
231207 if (
232- value === null ||
233- ( typeof value !== "object" && typeof value !== "function" )
208+ id === undefined ||
209+ id === null ||
210+ ( typeof id !== "string" &&
211+ typeof id !== "number" &&
212+ typeof id !== "boolean" )
234213 ) {
235214 throw new Error (
236- `oneOf uniqueBy.itemId "${ String (
237- key
238- ) } " requires the option value to be an object or function`
215+ `oneOf uniqueBy requires options to be objects with a valid "id" property (string, number, or boolean)`
239216 ) ;
240217 }
241218
242- return ( value as any ) [ key ] ;
219+ const prefix = typeof id ;
220+ return `${ prefix } :${ String ( id ) } ` ;
243221}
244222
245- function resolveUniqueBy < T > (
246- uniqueBy ?: OneOfUniqueBy < T >
247- ) : ResolvedUniqueBy < T > | undefined {
223+ function resolveUniqueBy (
224+ uniqueBy ?: OneOfUniqueBy
225+ ) : ResolvedUniqueBy | undefined {
248226 if ( ! uniqueBy ) {
249227 return undefined ;
250228 }
@@ -258,10 +236,7 @@ function resolveUniqueBy<T>(
258236 throw new Error ( "oneOf uniqueBy.collection must be a non-empty string" ) ;
259237 }
260238
261- const itemId = uniqueBy . itemId ?? ( "id" as UniqueIdentifierResolver < T > ) ;
262-
263239 return {
264240 collection,
265- itemId,
266241 } ;
267242}
0 commit comments