util.ts53 lines · main
1/**
2 * LLMs sometimes emit MySQL-style `\'` escapes in SQL. PostgreSQL doesn't
3 * treat backslash as an escape character, so replace `\'` → `''`.
4 * Dollar-quoted strings (e.g. `$$...$$`) are left untouched.
5 */
6export function fixSqlBackslashEscapes(sql: string): string {
7 return sql.replace(/\$([^$]*)\$[\s\S]*?\$\1\$|\\'/g, (match, dollarTag) =>
8 dollarTag !== undefined ? match : "''"
9 )
10}
11
12/**
13 * Selects a key from weighted choices using consistent hashing
14 * on an input string.
15 *
16 * The same input always returns the same key, with distribution
17 * proportional to the provided weights.
18 *
19 * @example
20 * const region = await selectWeightedKey('my-unique-id', {
21 * use1: 40,
22 * use2: 10,
23 * usw2: 10,
24 * euc1: 10,
25 * })
26 * // Returns one of the keys based on the input and weights
27 */
28export async function selectWeightedKey<T extends string>(
29 input: string,
30 weights: Record<T, number>
31): Promise<T> {
32 const keys = Object.keys(weights) as T[]
33 const encoder = new TextEncoder()
34 const data = encoder.encode(input)
35 const hashBuffer = await crypto.subtle.digest('SHA-256', data)
36
37 // Use first 4 bytes (32 bit integer)
38 const hashInt = new DataView(hashBuffer).getUint32(0)
39
40 const totalWeight = keys.reduce((sum, key) => sum + weights[key], 0)
41
42 let cumulativeWeight = 0
43 const targetWeight = hashInt % totalWeight
44
45 for (const key of keys) {
46 cumulativeWeight += weights[key]
47 if (cumulativeWeight > targetWeight) {
48 return key
49 }
50 }
51
52 return keys[0]
53}