fraci
Version:
Fractional indexing that's robust, performant, and secure, with first-class support for Drizzle ORM and Prisma ORM.
581 lines (577 loc) • 27.8 kB
text/typescript
/**
* Base configuration for binary fractional index implementations.
*
* Defines the configuration for fractional indices represented using binary encoding,
* where indices are stored as byte arrays (`Uint8Array`).
*
* Binary indices generally provide more compact storage and efficient comparison operations
* compared to string-based alternatives.
*
* @see {@link FractionalIndexBase} - Base configuration for fractional indices
* @see {@link AnyStringFractionalIndexBase} - Base configuration for string-based fractional indices
*/
type AnyBinaryFractionalIndexBase = {
/**
* The type discriminator identifying this as a binary fractional index configuration.
*/
readonly type: "binary";
};
/**
* Base configuration for string-based fractional index implementations.
*
* Defines the configuration for fractional indices represented using string encoding,
* where indices are stored as human-readable strings using specified character sets.
*
* String indices are useful when human readability or sortability in standard string
* contexts (like databases) is required.
*
* @see {@link FractionalIndexBase} - Base configuration for fractional indices
* @see {@link AnyBinaryFractionalIndexBase} - Base configuration for binary-based fractional indices
*/
type AnyStringFractionalIndexBase = {
/**
* The type discriminator identifying this as a string fractional index configuration.
*/
readonly type: "string";
/**
* The character set used for encoding the length of the integer part.
*
* This determines what characters are used to represent the length of the integer
* portion of the fractional index. Characters must be in ascending lexicographic order.
*
* The first character of a fractional index comes from this character set.
*
* @see {@link BASE10}, {@link BASE16L}, {@link BASE16U}, {@link BASE26L}, {@link BASE26U}, {@link BASE36L}, {@link BASE36U}, {@link BASE52}, {@link BASE62}, {@link BASE64URL}, {@link BASE88}, {@link BASE95}
*/
readonly lengthBase: string;
/**
* The character set used for representing digits in the fractional index.
*
* These characters form the ordered set used to encode the actual index values,
* and must be in ascending lexicographic order.
*
* The second and all subsequent characters of a fractional index come from this character set.
*
* @see {@link BASE10}, {@link BASE16L}, {@link BASE16U}, {@link BASE26L}, {@link BASE26U}, {@link BASE36L}, {@link BASE36U}, {@link BASE52}, {@link BASE62}, {@link BASE64URL}, {@link BASE88}, {@link BASE95}
*/
readonly digitBase: string;
};
/**
* Base configuration for fractional index implementations.
*
* This type serves as the base type for the `B` template parameter in the {@link FractionalIndex} type,
* defining the encoding strategy used by the index.
*
* @see {@link FractionalIndex} - The main type for fractional indices
*/
type FractionalIndexBase = AnyBinaryFractionalIndexBase | AnyStringFractionalIndexBase;
/**
* A branded type representing a fractional index with encoding strategy based on the base configuration.
*
* **WARNING: DO NOT cast untrusted values to `FractionalIndex` as this may cause runtime exceptions.**
*
* @template B - The base configuration defining the encoding strategy
* @template X - The brand type for type safety and identification
*
* @see {@link FractionalIndex} - The main type for fractional indices
* @see {@link AnyBinaryFractionalIndex} - A type representing any binary fractional index
* @see {@link AnyStringFractionalIndex} - A type representing any string fractional index
*/
type FractionalIndex<B extends FractionalIndexBase, X> = (B extends AnyBinaryFractionalIndexBase ? Uint8Array : string) & {
/**
* Branding information for the fractional index. Does not exist at runtime.
* @internal
*/
readonly __fraci__: {
readonly brand: X;
readonly base: B;
};
};
/**
* Represents any binary-encoded fractional index.
*
* Binary fractional indices are implemented as Uint8Array objects with additional
* type information. They provide compact representation and efficient comparison
* operations compared to string-based indices.
*
* This type is commonly used when you need to work with binary fractional indices
* without knowing their specific brand type.
*
* @see {@link AnyFractionalIndex} - A type representing any fractional index
* @see {@link AnyStringFractionalIndex} - A type representing any string fractional index
*/
type AnyBinaryFractionalIndex = FractionalIndex<AnyBinaryFractionalIndexBase, any>;
/**
* Represents any string-encoded fractional index.
*
* String fractional indices are implemented as string values with additional
* type information. They provide human-readable representation and natural
* lexicographic sorting in string-based storage systems.
*
* This type is commonly used when you need to work with string fractional indices
* without knowing their specific brand type.
*
* @see {@link AnyFractionalIndex} - A type representing any fractional index
* @see {@link AnyBinaryFractionalIndex} - A type representing any binary fractional index
*/
type AnyStringFractionalIndex = FractionalIndex<AnyStringFractionalIndexBase, any>;
/**
* Represents any fractional index regardless of encoding strategy.
*
* This union type encompasses both binary and string fractional indices,
* allowing operations that can work with any fractional index implementation.
*
* Use this type for generic algorithms and functions that should accept
* any fractional index without being tied to a specific encoding strategy.
*/
type AnyFractionalIndex = AnyBinaryFractionalIndex | AnyStringFractionalIndex;
/**
* Default maximum length for fractional index keys.
*/
declare const DEFAULT_MAX_LENGTH = 50;
/**
* Default maximum number of retry attempts when generating keys.
*/
declare const DEFAULT_MAX_RETRIES = 5;
type IndexPairs<T> = [T | null, T | null] | [T | null, T] | [T | null, null] | [T, T | null] | [T, T] | [T, null] | [null, T | null] | [null, T] | [null, null];
/**
* Fractional indexing utility that provides methods for generating ordered keys.
*
* @template B - The base configuration defining the encoding strategy
* @template X - The brand type for the fractional index
*
* @see {@link fraci} - The unified factory function for creating fractional indexing utilities
* @see {@link fraciBinary} - The factory function for creating binary-based fractional indexing utilities
* @see {@link fraciString} - The factory function for creating string-based fractional indexing utilities
*/
interface Fraci<B extends FractionalIndexBase, X> {
/**
* The character sets used for representing digits in the fractional index.
*/
readonly base: B;
/**
* The brand type for the fractional index. Does not exist at runtime.
*
* @internal
*/
readonly brand?: X | undefined;
/**
* Generates a key between two existing keys.
* Returns a generator that yields new unique keys between the provided bounds.
*
* @param a - The lower bound key, or null if there is no lower bound
* @param b - The upper bound key, or null if there is no upper bound
* @param skip - Number of conflict avoidance iterations to skip (default: 0)
* @returns A generator yielding fractional index keys
* @throws {FraciError} Throws a {@link FraciError} when invalid input is provided
* @throws {FraciError} Throws a {@link FraciError} when the generated key exceeds the maximum length
*
* @see {@link FraciError} - The custom error class for the Fraci library
*/
generateKeyBetween(a: FractionalIndex<B, X> | null, b: FractionalIndex<B, X> | null, skip?: number): Generator<FractionalIndex<B, X>, never, unknown>;
/**
* Generates a key between two existing keys.
* Returns a generator that yields new unique keys between the provided bounds.
*
* This is an overload to make the spread operator work with conditional tuples.
*
* @param a - The lower bound key, or null if there is no lower bound
* @param b - The upper bound key, or null if there is no upper bound
* @param skip - Number of conflict avoidance iterations to skip (default: 0)
* @returns A generator yielding fractional index keys
* @throws {FraciError} Throws a {@link FraciError} when invalid input is provided
* @throws {FraciError} Throws a {@link FraciError} when the generated key exceeds the maximum length
*
* @see {@link FraciError} - The custom error class for the Fraci library
*
* @ignore Documentation should be ignored for this overload but should not affect functionality and Intellisense
*/
generateKeyBetween(...[a, b, skip]: [...IndexPairs<FractionalIndex<B, X>>] | [...IndexPairs<FractionalIndex<B, X>>, number]): Generator<FractionalIndex<B, X>, never, unknown>;
/**
* Generates multiple keys evenly distributed between two existing keys.
* Returns a generator that yields arrays of new unique keys.
*
* @param a - The lower bound key, or null if there is no lower bound
* @param b - The upper bound key, or null if there is no upper bound
* @param n - Number of keys to generate
* @param skip - Number of conflict avoidance iterations to skip (default: 0)
* @returns A generator yielding arrays of fractional index keys
* @throws {FraciError} Throws a {@link FraciError} when invalid input is provided
* @throws {FraciError} Throws a {@link FraciError} when the generated keys would exceed the maximum length
*
* @see {@link FraciError} - The custom error class for the Fraci library
*/
generateNKeysBetween(a: FractionalIndex<B, X> | null, b: FractionalIndex<B, X> | null, n: number, skip?: number): Generator<FractionalIndex<B, X>[], never, unknown>;
/**
* Generates multiple keys evenly distributed between two existing keys.
* Returns a generator that yields arrays of new unique keys.
*
* This is an overload to make the spread operator work with conditional tuples.
*
* @param a - The lower bound key, or null if there is no lower bound
* @param b - The upper bound key, or null if there is no upper bound
* @param n - Number of keys to generate
* @param skip - Number of conflict avoidance iterations to skip (default: 0)
* @returns A generator yielding arrays of fractional index keys
* @throws {FraciError} Throws a {@link FraciError} when invalid input is provided
* @throws {FraciError} Throws a {@link FraciError} when the generated keys would exceed the maximum length
*
* @ignore Documentation should be ignored for this overload but should not affect functionality and Intellisense
*
* @see {@link FraciError} - The custom error class for the Fraci library
*/
generateNKeysBetween(...[a, b, n, skip]: [...IndexPairs<FractionalIndex<B, X>>, number] | [...IndexPairs<FractionalIndex<B, X>>, number, number]): Generator<FractionalIndex<B, X>[], never, unknown>;
}
/**
* Type alias for any {@link Fraci} instance with a binary digit base.
*
* @see {@link Fraci} - The main fractional indexing utility type
* @see {@link AnyFraci} - A union type of all fractional index types
* @see {@link AnyStringFraci} - The type of all string fractional index types
*/
type AnyBinaryFraci = Fraci<AnyBinaryFractionalIndexBase, any>;
/**
* Type alias for any {@link Fraci} instance with a string digit base.
*
* @see {@link Fraci} - The main fractional indexing utility type
* @see {@link AnyFraci} - A union type of all fractional index types
* @see {@link AnyBinaryFraci} - The type of all binary fractional index types
*/
type AnyStringFraci = Fraci<AnyStringFractionalIndexBase, any>;
/**
* Type alias for any {@link Fraci} instance with any digit base, length base, and brand.
* This is useful for cases where the specific parameters don't matter.
*
* @see {@link Fraci} - The main fractional indexing utility type
* @see {@link AnyBinaryFraci} - The type of all binary fractional index types
* @see {@link AnyStringFraci} - The type of all string fractional index types
*/
type AnyFraci = AnyBinaryFraci | AnyStringFraci;
/**
* Base options for fractional indexing.
*
* This type serves as the base type for the `B` template parameter in the {@link Fraci} type,
* defining the encoding strategy used by the index.
*
* @see {@link FraciOptions} - The main configuration options for fractional indexing
* @see {@link FraciOptionsBaseToBase} - The type alias for converting options to a more specific type
*/
type FraciOptionsBase = {
/**
* The type discriminator identifying this as a string fractional index configuration.
*
* Must be "string" or `undefined` for string fractional indices.
*/
readonly type?: "string" | undefined;
/**
* The character set used for encoding the length of the integer part.
*
* This determines what characters are used to represent the length of the integer
* portion of the fractional index. Characters must be in ascending lexicographic order.
*
* The first character of a fractional index comes from this character set.
*
* @see {@link BASE10}, {@link BASE16L}, {@link BASE16U}, {@link BASE26L}, {@link BASE26U}, {@link BASE36L}, {@link BASE36U}, {@link BASE52}, {@link BASE62}, {@link BASE64URL}, {@link BASE88}, {@link BASE95}
*/
readonly lengthBase: string;
/**
* The character set used for representing digits in the fractional index.
*
* These characters form the ordered set used to encode the actual index values,
* and must be in ascending lexicographic order.
*
* The second and all subsequent characters of a fractional index come from this character set.
*
* @see {@link BASE10}, {@link BASE16L}, {@link BASE16U}, {@link BASE26L}, {@link BASE26U}, {@link BASE36L}, {@link BASE36U}, {@link BASE52}, {@link BASE62}, {@link BASE64URL}, {@link BASE88}, {@link BASE95}
*/
readonly digitBase: string;
} | {
/**
* The type discriminator identifying this as a binary fractional index configuration.
*
* Must be "binary" for binary fractional indices.
*/
readonly type: "binary";
};
/**
* Type alias for converting the options to a more specific type.
*
* @template B - The base configuration defining the encoding strategy
*
* @see {@link FraciOptionsBase} - The base options for fractional indexing
* @see {@link FractionalIndexBase} - The base configuration for fractional indices
*/
type FraciOptionsBaseToBase<B extends FraciOptionsBase> = B extends {
readonly lengthBase: string;
readonly digitBase: string;
} ? {
/**
* The type discriminator identifying this as a string fractional index configuration.
*/
readonly type: "string";
/**
* The character set used for encoding the length of the integer part.
*
* This determines what characters are used to represent the length of the integer
* portion of the fractional index. Characters must be in ascending lexicographic order.
*
* The first character of a fractional index comes from this character set.
*
* @see {@link BASE10}, {@link BASE16L}, {@link BASE16U}, {@link BASE26L}, {@link BASE26U}, {@link BASE36L}, {@link BASE36U}, {@link BASE52}, {@link BASE62}, {@link BASE64URL}, {@link BASE88}, {@link BASE95}
*/
readonly lengthBase: B["lengthBase"];
/**
* The character set used for representing digits in the fractional index.
*
* These characters form the ordered set used to encode the actual index values,
* and must be in ascending lexicographic order.
*
* The second and all subsequent characters of a fractional index come from this character set.
*
* @see {@link BASE10}, {@link BASE16L}, {@link BASE16U}, {@link BASE26L}, {@link BASE26U}, {@link BASE36L}, {@link BASE36U}, {@link BASE52}, {@link BASE62}, {@link BASE64URL}, {@link BASE88}, {@link BASE95}
*/
readonly digitBase: B["digitBase"];
} : {
/**
* The type discriminator identifying this as a binary fractional index configuration.
*/
readonly type: "binary";
};
/**
* Configuration options for creating fractional indexing utilities.
*
* @template B - The base configuration defining the encoding strategy
*
* @see {@link fraci} - The unified factory function for creating fractional indexing utilities
* @see {@link FraciOptionsBase} - The base options for fractional indexing
* @see {@link BinaryFraciOptions} - The options for binary fractional indexing
* @see {@link StringFraciOptions} - The options for string fractional indexing
*/
type FraciOptions<B extends FraciOptionsBase> = B & {
/**
* Maximum allowed length for generated keys.
* @default DEFAULT_MAX_LENGTH (50)
*/
readonly maxLength?: number | undefined;
/**
* Maximum number of retry attempts when generating keys.
* @default DEFAULT_MAX_RETRIES (5)
*/
readonly maxRetries?: number | undefined;
};
/**
* Base configuration for creating string-based fractional indexing utilities.
*
* Defines the configuration for fractional indices represented using binary encoding,
* where indices are stored as byte arrays (`Uint8Array`).
*
* Binary indices generally provide more compact storage and efficient comparison operations
* compared to string-based alternatives.
*
* @see {@link FraciOptions} - The main configuration options for fractional indexing
* @see {@link StringFraciOptions} - The options for string fractional indexing
*/
type BinaryFraciOptions = FraciOptions<{
/**
* The type discriminator identifying this as a binary fractional index configuration.
*/
readonly type: "binary";
}>;
/**
* Base configuration for creating string-based fractional indexing utilities.
*
* Defines the configuration for fractional indices represented using string encoding,
* where indices are stored as human-readable strings using specified character sets.
*
* String indices are useful when human readability or sortability in standard string
* contexts (like databases) is required.
*
* @see {@link FraciOptions} - The main configuration options for fractional indexing
* @see {@link BinaryFraciOptions} - The options for binary fractional indexing
*/
type StringFraciOptions = FraciOptions<{
/**
* The type discriminator identifying this as a string fractional index configuration.
*/
readonly type?: "string" | undefined;
/**
* The character set used for encoding the length of the integer part.
*
* This determines what characters are used to represent the length of the integer
* portion of the fractional index. Characters must be in ascending lexicographic order.
*
* The first character of a fractional index comes from this character set.
*
* @see {@link BASE10}, {@link BASE16L}, {@link BASE16U}, {@link BASE26L}, {@link BASE26U}, {@link BASE36L}, {@link BASE36U}, {@link BASE52}, {@link BASE62}, {@link BASE64URL}, {@link BASE88}, {@link BASE95}
*/
readonly lengthBase: string;
/**
* The character set used for representing digits in the fractional index.
*
* These characters form the ordered set used to encode the actual index values,
* and must be in ascending lexicographic order.
*
* The second and all subsequent characters of a fractional index come from this character set.
*
* @see {@link BASE10}, {@link BASE16L}, {@link BASE16U}, {@link BASE26L}, {@link BASE26U}, {@link BASE36L}, {@link BASE36U}, {@link BASE52}, {@link BASE62}, {@link BASE64URL}, {@link BASE88}, {@link BASE95}
*/
readonly digitBase: string;
}>;
/**
* Type alias to represent options that can be branded.
*
* @template T - The base options type
* @template X - The brand type
*/
type BrandableOptions<T, X> = T & {
/**
* The brand type for the fractional index.
*/
readonly brand?: X | undefined;
};
/**
* Cache for storing computed values to improve performance.
* Uses a branded type pattern to prevent accidental misuse.
*
* @see {@link createFraciCache} - Function to create a new cache
*/
type FraciCache = Map<string, unknown> & {
readonly __fraci__: never;
};
/**
* Creates a new empty {@link FraciCache} for storing computed values in string-based fractional indexing operations.
* Using a cache can improve initialization performance when repeatedly using the same base configurations.
*
* @returns A new empty FraciCache instance
*
* @example
* ```typescript
* const cache = createFraciCache();
* const fraci1 = fraciString({ brand: "a", lengthBase: "abcdefghij", digitBase: "0123456789" }, cache);
* const fraci2 = fraciString({ brand: "b", lengthBase: "abcdefghij", digitBase: "0123456789" }, cache);
* // Both instances will share cached computations
* ```
*/
declare function createFraciCache(): FraciCache;
/**
* Creates a binary-based fractional indexing utility with the specified configuration.
*
* @template X - The brand type for the fractional index
*
* @param options - Configuration options for the fractional indexing utility
* @returns A binary-based fractional indexing utility instance
*
* @example
* ```typescript
* // Create a binary-based fractional indexing utility
* const binaryFraci = fraciBinary({ brand: "exampleIndex" });
*
* // Generate a key between null and null (first key)
* const [key1] = binaryFraci.generateKeyBetween(null, null);
* // Generate a key between key1 and null (key after key1)
* const [key2] = binaryFraci.generateKeyBetween(key1, null);
* // Generate a key between key1 and key2
* const [key3] = binaryFraci.generateKeyBetween(key1, key2);
* ```
*
* @see {@link Fraci} - The main fractional indexing utility type
* @see {@link BinaryFraciOptions} - The options for the binary fractional indexing utility
* @see {@link fraci} - The unified factory function for creating fractional indexing utilities
* @see {@link fraciString} - The factory function for creating string-based fractional indexing utilities
*/
declare function fraciBinary<const X = unknown>({ maxLength, maxRetries, }?: BrandableOptions<Omit<BinaryFraciOptions, "type"> & {
readonly type?: "binary" | undefined;
}, X>): Fraci<AnyBinaryFractionalIndexBase, X>;
/**
* Creates a string-based fractional indexing utility with the specified configuration.
*
* @template B - The base configuration defining the character sets
* @template X - The brand type for the fractional index
*
* @param options - Configuration options for the fractional indexing utility
* @param cache - Optional cache to improve performance by reusing computed values
* @returns A string-based fractional indexing utility instance
* @throws {FraciError} Throws a {@link FraciError} when the digit or length base strings are invalid
*
* @example
* ```typescript
* // Create a decimal-based fractional indexing utility
* const decimalFraci = fraciString({
* brand: "exampleIndex",
* lengthBase: "abcdefghij",
* digitBase: "0123456789",
* });
*
* // Generate a key between null and null (first key)
* const [key1] = decimalFraci.generateKeyBetween(null, null);
* // Generate a key between key1 and null (key after key1)
* const [key2] = decimalFraci.generateKeyBetween(key1, null);
* // Generate a key between key1 and key2
* const [key3] = decimalFraci.generateKeyBetween(key1, key2);
* ```
*
* @see {@link Fraci} - The main fractional indexing utility type
* @see {@link StringFraciOptions} - The options for the string fractional indexing utility
* @see {@link FraciCache} - The cache for storing computed values
* @see {@link fraci} - The unified factory function for creating fractional indexing utilities
* @see {@link fraciBinary} - The factory function for creating binary-based fractional indexing utilities
* @see {@link FraciError} - The custom error class for the Fraci library
*/
declare function fraciString<const B extends StringFraciOptions, const X = unknown>({ lengthBase, digitBase, maxLength, maxRetries, }: BrandableOptions<B, X>, cache?: FraciCache): Fraci<FraciOptionsBaseToBase<B>, X>;
/**
* **We recommend using {@link fraciBinary} or {@link fraciString} directly to reduce bundle size whenever possible.**
*
* Creates a fractional indexing utility with the specified configuration.
* This is the main factory function for creating a {@link Fraci} instance that can generate
* fractional indices between existing values.
*
* @template B - The base configuration defining the encoding strategy
* @template X - The brand type for the fractional index
*
* @param options - Configuration options for the fractional indexing utility
* @param cache - Optional cache to improve performance by reusing computed values
* @returns A fractional indexing utility instance
* @throws {FraciError} Throws a {@link FraciError} when the digit or length base strings are invalid
*
* @example
* ```typescript
* // Create a decimal-based fractional indexing utility
* const decimalFraci = fraci({
* brand: "exampleIndex",
* lengthBase: "abcdefghij",
* digitBase: "0123456789",
* });
*
* // Generate a key between null and null (first key)
* const [key1] = decimalFraci.generateKeyBetween(null, null);
* // Generate a key between key1 and null (key after key1)
* const [key2] = decimalFraci.generateKeyBetween(key1, null);
* // Generate a key between key1 and key2
* const [key3] = decimalFraci.generateKeyBetween(key1, key2);
* ```
*
* @see {@link Fraci} - The main fractional indexing utility type
* @see {@link fraciBinary} - The factory function for creating binary-based fractional indexing utilities
* @see {@link fraciString} - The factory function for creating string-based fractional indexing utilities
* @see {@link FraciError} - The custom error class for the Fraci library
*/
declare function fraci<const B extends FraciOptionsBase, const X = unknown>(options: FraciOptions<B> | (FraciOptions<B> & {
readonly brand: X;
}), cache?: FraciCache): Fraci<FraciOptionsBaseToBase<B>, X>;
/**
* Extracts the Fraci type from a fractional index type.
* This utility type infers the digit base, length base, and brand from a
* fractional index type and constructs the corresponding Fraci type.
*
* @template F - The fractional index type to extract from
*/
type FraciOf<F extends AnyFractionalIndex> = F extends FractionalIndex<infer B, infer X> ? Fraci<B, X> : never;
/**
* Extracts the fractional index type from a Fraci type.
* This utility type infers the digit base, length base, and brand from a
* Fraci type and constructs the corresponding fractional index type.
*
* @template F - The Fraci type to extract from
*/
type FractionalIndexOf<F extends AnyFraci> = F extends Fraci<infer B, infer X> ? FractionalIndex<B, X> : never;
export { type AnyFraci as A, type BinaryFraciOptions as B, DEFAULT_MAX_LENGTH as D, type FractionalIndexOf as F, type StringFraciOptions as S, type AnyFractionalIndex as a, type AnyBinaryFractionalIndex as b, DEFAULT_MAX_RETRIES as c, type Fraci as d, type AnyBinaryFraci as e, type AnyStringFraci as f, type FraciOptionsBase as g, type FraciOptionsBaseToBase as h, type FraciOptions as i, type FraciCache as j, createFraciCache as k, fraciBinary as l, fraciString as m, fraci as n, type FraciOf as o, type AnyBinaryFractionalIndexBase as p, type AnyStringFractionalIndexBase as q, type FractionalIndexBase as r, type FractionalIndex as s, type AnyStringFractionalIndex as t };