brainy/src/utils/collation.ts
David Snelling bf4a333f9b docs: rename the native provider to @soulcraft/cor across public docs and JSDoc
The 3.0 native engine ships as @soulcraft/cor (brainy 8.x <-> cor 3.x are a
version-matched pair); the old @soulcraft/cortex package stays on the 2.x/7.x
line. Public docs and .d.ts-visible comments now name the correct package.
Historical 2.x contract references (e.g. the 2.3.1 read-side fallback) keep
the old name deliberately.
2026-07-02 15:11:41 -07:00

37 lines
1.7 KiB
TypeScript

/**
* @module utils/collation
* @description Deterministic string ordering for persisted indexes and any comparison
* that must agree with the native (Rust) column store / aggregation engine.
*
* Strings are compared by **UTF-8 byte order**, which equals Unicode code-point order
* and is byte-for-byte identical to Rust's `str::cmp` (`as_bytes().cmp()`). This is:
* - **deterministic** across environments (unlike `localeCompare`, whose default-locale
* ordering varies by OS / Node / ICU build — unsafe for a persisted sorted index), and
* - **cross-language consistent** with `@soulcraft/cor`'s native column store and
* aggregation sort, so query results are identical with or without the native plugin.
*
* Use this instead of `String.localeCompare` and the `<`/`>` operators (which compare
* UTF-16 code units and diverge from code-point order for supplementary-plane characters)
* wherever ordering is persisted or must match the native engine. See cor `docs/ADR-001`.
*/
const utf8 = new TextEncoder()
/**
* Compare two strings by UTF-8 byte order (== Unicode code-point order == Rust `str::cmp`).
*
* @param a - First string.
* @param b - Second string.
* @returns Negative if `a < b`, positive if `a > b`, `0` if equal — by UTF-8 byte order.
* @example
* ['b', 'A', 'a'].sort(compareCodePoints) // ['A', 'a', 'b'] (byte order: A=65, a=97, b=98)
*/
export function compareCodePoints(a: string, b: string): number {
const ea = utf8.encode(a)
const eb = utf8.encode(b)
const len = ea.length < eb.length ? ea.length : eb.length
for (let i = 0; i < len; i++) {
if (ea[i] !== eb[i]) return ea[i] - eb[i]
}
return ea.length - eb.length
}