```ts
/**
 * Merges overlapping or touching booking spans into single spans, returning them ordered by start.
 * @param spans - Array of [start, end] pairs representing booking spans.
 * @returns Merged spans ordered by start.
 */
export function spanMerge(spans: number[][]): number[][] {
    if (spans.length === 0) return [];

    const sortedSpans = spans.sort((a, b) => a[0] - b[0]);
    const mergedSpans: number[][] = [sortedSpans[0]];

    for (let i = 1; i < sortedSpans.length; i++) {
        const current = sortedSpans[i];
        const lastMerged = mergedSpans[mergedSpans.length - 1];

        if (current[0] <= lastMerged[1]) {
            mergedSpans[mergedSpans.length - 1] = [lastMerged[0], Math.max(lastMerged[1], current[1])];
        } else {
            mergedSpans.push(current);
        }
    }

    return mergedSpans;
}
```