payload
Version:
Node, React, Headless CMS and Application Framework built on Next.js
295 lines (294 loc) • 9.05 kB
JavaScript
// @ts-check
/**
* THIS FILE IS COPIED FROM:
* https://github.com/rocicorp/fractional-indexing/blob/main/src/index.js
*
* I AM NOT INSTALLING THAT LIBRARY BECAUSE JEST COMPLAINS ABOUT THE ESM MODULE AND THE TESTS FAIL.
* DO NOT MODIFY IT
*/ // License: CC0 (no rights reserved).
// This is based on https://observablehq.com/@dgreensp/implementing-fractional-indexing
export const BASE_62_DIGITS = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
// `a` may be empty string, `b` is null or non-empty string.
// `a < b` lexicographically if `b` is non-null.
// no trailing zeros allowed.
// digits is a string such as '0123456789' for base 10. Digits must be in
// ascending character code order!
/**
* @param {string} a
* @param {string | null | undefined} b
* @param {string} digits
* @returns {string}
*/ function midpoint(a, b, digits) {
const zero = digits[0];
if (b != null && a >= b) {
throw new Error(a + ' >= ' + b);
}
if (a.slice(-1) === zero || b && b.slice(-1) === zero) {
throw new Error('trailing zero');
}
if (b) {
// remove longest common prefix. pad `a` with 0s as we
// go. note that we don't need to pad `b`, because it can't
// end before `a` while traversing the common prefix.
let n = 0;
while((a[n] || zero) === b[n]){
n++;
}
if (n > 0) {
return b.slice(0, n) + midpoint(a.slice(n), b.slice(n), digits);
}
}
// first digits (or lack of digit) are different
const digitA = a ? digits.indexOf(a[0]) : 0;
const digitB = b != null ? digits.indexOf(b[0]) : digits.length;
if (digitB - digitA > 1) {
const midDigit = Math.round(0.5 * (digitA + digitB));
return digits[midDigit];
} else {
// first digits are consecutive
if (b && b.length > 1) {
return b.slice(0, 1);
} else {
// `b` is null or has length 1 (a single digit).
// the first digit of `a` is the previous digit to `b`,
// or 9 if `b` is null.
// given, for example, midpoint('49', '5'), return
// '4' + midpoint('9', null), which will become
// '4' + '9' + midpoint('', null), which is '495'
return digits[digitA] + midpoint(a.slice(1), null, digits);
}
}
}
/**
* @param {string} int
* @return {void}
*/ function validateInteger(int) {
if (int.length !== getIntegerLength(int[0])) {
throw new Error('invalid integer part of order key: ' + int);
}
}
/**
* @param {string} head
* @return {number}
*/ function getIntegerLength(head) {
if (head >= 'a' && head <= 'z') {
return head.charCodeAt(0) - 'a'.charCodeAt(0) + 2;
} else if (head >= 'A' && head <= 'Z') {
return 'Z'.charCodeAt(0) - head.charCodeAt(0) + 2;
} else {
throw new Error('invalid order key head: ' + head);
}
}
/**
* @param {string} key
* @return {string}
*/ function getIntegerPart(key) {
const integerPartLength = getIntegerLength(key[0]);
if (integerPartLength > key.length) {
throw new Error('invalid order key: ' + key);
}
return key.slice(0, integerPartLength);
}
/**
* @param {string} key
* @param {string} digits
* @return {void}
*/ function validateOrderKey(key, digits) {
if (key === 'A' + digits[0].repeat(26)) {
throw new Error('invalid order key: ' + key);
}
// getIntegerPart will throw if the first character is bad,
// or the key is too short. we'd call it to check these things
// even if we didn't need the result
const i = getIntegerPart(key);
const f = key.slice(i.length);
if (f.slice(-1) === digits[0]) {
throw new Error('invalid order key: ' + key);
}
}
// note that this may return null, as there is a largest integer
/**
* @param {string} x
* @param {string} digits
* @return {string | null}
*/ function incrementInteger(x, digits) {
validateInteger(x);
const [head, ...digs] = x.split('');
let carry = true;
for(let i = digs.length - 1; carry && i >= 0; i--){
const d = digits.indexOf(digs[i]) + 1;
if (d === digits.length) {
digs[i] = digits[0];
} else {
digs[i] = digits[d];
carry = false;
}
}
if (carry) {
if (head === 'Z') {
return 'a' + digits[0];
}
if (head === 'z') {
return null;
}
const h = String.fromCharCode(head.charCodeAt(0) + 1);
if (h > 'a') {
digs.push(digits[0]);
} else {
digs.pop();
}
return h + digs.join('');
} else {
return head + digs.join('');
}
}
// note that this may return null, as there is a smallest integer
/**
* @param {string} x
* @param {string} digits
* @return {string | null}
*/ function decrementInteger(x, digits) {
validateInteger(x);
const [head, ...digs] = x.split('');
let borrow = true;
for(let i = digs.length - 1; borrow && i >= 0; i--){
const d = digits.indexOf(digs[i]) - 1;
if (d === -1) {
digs[i] = digits.slice(-1);
} else {
digs[i] = digits[d];
borrow = false;
}
}
if (borrow) {
if (head === 'a') {
return 'Z' + digits.slice(-1);
}
if (head === 'A') {
return null;
}
const h = String.fromCharCode(head.charCodeAt(0) - 1);
if (h < 'Z') {
digs.push(digits.slice(-1));
} else {
digs.pop();
}
return h + digs.join('');
} else {
return head + digs.join('');
}
}
// `a` is an order key or null (START).
// `b` is an order key or null (END).
// `a < b` lexicographically if both are non-null.
// digits is a string such as '0123456789' for base 10. Digits must be in
// ascending character code order!
/**
* @param {string | null | undefined} a
* @param {string | null | undefined} b
* @param {string=} digits
* @return {string}
*/ export function generateKeyBetween(a, b, digits = BASE_62_DIGITS) {
if (a != null) {
validateOrderKey(a, digits);
}
if (b != null) {
validateOrderKey(b, digits);
}
if (a != null && b != null && a >= b) {
throw new Error(a + ' >= ' + b);
}
if (a == null) {
if (b == null) {
return 'a' + digits[0];
}
const ib = getIntegerPart(b);
const fb = b.slice(ib.length);
if (ib === 'A' + digits[0].repeat(26)) {
return ib + midpoint('', fb, digits);
}
if (ib < b) {
return ib;
}
const res = decrementInteger(ib, digits);
if (res == null) {
throw new Error('cannot decrement any more');
}
return res;
}
if (b == null) {
const ia = getIntegerPart(a);
const fa = a.slice(ia.length);
const i = incrementInteger(ia, digits);
return i == null ? ia + midpoint(fa, null, digits) : i;
}
const ia = getIntegerPart(a);
const fa = a.slice(ia.length);
const ib = getIntegerPart(b);
const fb = b.slice(ib.length);
if (ia === ib) {
return ia + midpoint(fa, fb, digits);
}
const i = incrementInteger(ia, digits);
if (i == null) {
throw new Error('cannot increment any more');
}
if (i < b) {
return i;
}
return ia + midpoint(fa, null, digits);
}
/**
* same preconditions as generateKeysBetween.
* n >= 0.
* Returns an array of n distinct keys in sorted order.
* If a and b are both null, returns [a0, a1, ...]
* If one or the other is null, returns consecutive "integer"
* keys. Otherwise, returns relatively short keys between
* a and b.
* @param {string | null | undefined} a
* @param {string | null | undefined} b
* @param {number} n
* @param {string} digits
* @return {string[]}
*/ export function generateNKeysBetween(a, b, n, digits = BASE_62_DIGITS) {
if (n === 0) {
return [];
}
if (n === 1) {
return [
generateKeyBetween(a, b, digits)
];
}
if (b == null) {
let c = generateKeyBetween(a, b, digits);
const result = [
c
];
for(let i = 0; i < n - 1; i++){
c = generateKeyBetween(c, b, digits);
result.push(c);
}
return result;
}
if (a == null) {
let c = generateKeyBetween(a, b, digits);
const result = [
c
];
for(let i = 0; i < n - 1; i++){
c = generateKeyBetween(a, c, digits);
result.push(c);
}
result.reverse();
return result;
}
const mid = Math.floor(n / 2);
const c = generateKeyBetween(a, b, digits);
return [
...generateNKeysBetween(a, c, mid, digits),
c,
...generateNKeysBetween(c, b, n - mid - 1, digits)
];
}
//# sourceMappingURL=fractional-indexing.js.map