util.ts 1.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253
  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. */
  6. export function fixSqlBackslashEscapes(sql: string): string {
  7. return sql.replace(/\$([^$]*)\$[\s\S]*?\$\1\$|\\'/g, (match, dollarTag) =>
  8. dollarTag !== undefined ? match : "''"
  9. )
  10. }
  11. /**
  12. * Selects a key from weighted choices using consistent hashing
  13. * on an input string.
  14. *
  15. * The same input always returns the same key, with distribution
  16. * proportional to the provided weights.
  17. *
  18. * @example
  19. * const region = await selectWeightedKey('my-unique-id', {
  20. * use1: 40,
  21. * use2: 10,
  22. * usw2: 10,
  23. * euc1: 10,
  24. * })
  25. * // Returns one of the keys based on the input and weights
  26. */
  27. export async function selectWeightedKey<T extends string>(
  28. input: string,
  29. weights: Record<T, number>
  30. ): Promise<T> {
  31. const keys = Object.keys(weights) as T[]
  32. const encoder = new TextEncoder()
  33. const data = encoder.encode(input)
  34. const hashBuffer = await crypto.subtle.digest('SHA-256', data)
  35. // Use first 4 bytes (32 bit integer)
  36. const hashInt = new DataView(hashBuffer).getUint32(0)
  37. const totalWeight = keys.reduce((sum, key) => sum + weights[key], 0)
  38. let cumulativeWeight = 0
  39. const targetWeight = hashInt % totalWeight
  40. for (const key of keys) {
  41. cumulativeWeight += weights[key]
  42. if (cumulativeWeight > targetWeight) {
  43. return key
  44. }
  45. }
  46. return keys[0]
  47. }