key-hierarchy
Version:
A tiny TypeScript library for managing key hierarchies. The perfect companion for TanStack Query.
159 lines • 7.37 kB
TypeScript
//#region src/types.d.ts
/**
* Represents a key hierarchy for a given object type.
* Dynamically derived based on a provided {@link KeyHierarchyConfig}.
*/
type KeyHierarchy<T, Path extends readonly unknown[] = []> = { readonly [K in keyof T as K extends InternalKeys ? never : K extends number ? `${K}` : K]: T[K] extends DynamicExtendedSegment<infer Arg, infer U> ? (arg: Arg) => KeyHierarchy<U, readonly [...Path, readonly [K extends number ? `${K}` : K, DeepReadonly<Arg>]]> & {
readonly __key: readonly [...Path, readonly [K extends number ? `${K}` : K, DeepReadonly<Arg>]];
} : T[K] extends DynamicSegment<infer Arg> ? (arg: Arg) => readonly [...Path, readonly [K extends number ? `${K}` : K, DeepReadonly<Arg>]] : T[K] extends object ? KeyHierarchy<T[K], readonly [...Path, K extends number ? `${K}` : K]> & {
readonly __key: readonly [...Path, K extends number ? `${K}` : K];
} : readonly [...Path, K extends number ? `${K}` : K] };
declare const DYNAMIC_SEGMENT: unique symbol;
interface DynamicSegment<T> {
readonly [DYNAMIC_SEGMENT]: T;
}
declare const DYNAMIC_EXTENDED_SEGMENT: unique symbol;
type DynamicExtendedSegment<T, U$1 extends KeyHierarchyConfig<U$1>> = U$1 & {
readonly [DYNAMIC_EXTENDED_SEGMENT]: T;
};
type DynamicExtendableSegment<T> = DynamicSegment<T> & {
/**
* Extends the dynamic segment with a nested configuration.
* @param config - The nested configuration to extend the dynamic segment with.
* @returns The extended dynamic segment.
*/
extend: <U$1 extends KeyHierarchyConfig<U$1>>(config: U$1) => DynamicExtendedSegment<T, U$1>;
};
type InternalKeys = "__key" | typeof DYNAMIC_SEGMENT | typeof DYNAMIC_EXTENDED_SEGMENT;
/**
* Declarative configuration of a key hierarchy.
* @remarks **Note:** May not contain `__key` as a property at any place.
*/
type KeyHierarchyConfig<T> = "__key" extends keyof T ? never : { [K in keyof T as K extends InternalKeys ? never : K]: T[K] extends DynamicSegment<infer Arg> ? DynamicExtendableSegment<Arg> : T[K] extends DynamicExtendedSegment<infer Arg, infer U> ? DynamicExtendedSegment<Arg, U> : T[K] extends Function ? never : T[K] extends object ? KeyHierarchyConfig<T[K]> : true };
/**
* Options for a key hierarchy.
*/
interface KeyHierarchyOptions {
/**
* If enabled, all key arrays and their elements will be deeply frozen.
* While already present at the type level, due to {@link DeepReadonly}, this ensures true immutability during runtime.
* @remarks **Note:** Function arguments will be cloned with {@link structuredClone} to not affect the original values.
* @remarks **Warning:** Does not work with values that are not supported by {@link structuredClone}.
* @remarks **Warning:** Do not enable this for `queryKey` or `mutationKey` with `@tanstack/vue-query` as it will cause issues with reactivity.
* @defaultValue false
*/
freeze?: boolean;
/**
* The method to use for key hierarchy computation.
* `proxy` uses a {@link Proxy} to dynamically resolve keys on demand.
* `precompute` computes the key hierarchy upfront.
* @defaultValue 'proxy'
*/
method?: "proxy" | "precompute";
}
/**
* Represents a deeply readonly version of a type.
*/
type DeepReadonly<T> = T extends (infer R)[] ? DeepReadonlyArray<R> : T extends Function ? T : T extends object ? DeepReadonlyObject<T> : T;
/**
* Represents a deeply readonly version of an array.
*/
interface DeepReadonlyArray<T> extends ReadonlyArray<DeepReadonly<T>> {}
/**
* Represents a deeply readonly version of an object.
*/
type DeepReadonlyObject<T> = { readonly [P in keyof T]: DeepReadonly<T[P]> };
//#endregion
//#region src/index.d.ts
/**
* Represents a configuration or a builder function for a key hierarchy.
*/
type ConfigOrBuilder<T extends KeyHierarchyConfig<T>> = T | ((dynamic: typeof dynamicHelper) => T);
/**
* Defines a key hierarchy based on the provided configuration and options.
* @remarks **Note:** This function uses {@link Proxy} objects by default. See {@link KeyHierarchyOptions.method} for other options.
* @param config - The declarative {@link KeyHierarchyConfig} of the key hierarchy.
* @param options - The {@link KeyHierarchyOptions} for the key hierarchy.
* @returns The {@link KeyHierarchy} derived from the given config and options.
* @example
* ```ts
* const keys = defineKeyHierarchy((dynamic) => ({
* users: {
* getAll: true,
* create: true,
* byId: dynamic<number>().extend({
* get: true,
* update: true,
* delete: true,
* }),
* },
* posts: {
* byUserId: dynamic<number>(),
* byMonth: dynamic<number>().extend({
* byDay: dynamic<number>(),
* }),
* byAuthorAndYear: dynamic<{ authorId: string, year: number }>(),
* byTags: dynamic<{ tags: string[], filter?: PostFilter }>(),
* }
* }))
* console.log(keys.users.getAll) // readonly ['users', 'getAll']
* console.log(keys.users.byId(42).update) // readonly ['users', ['byId', 42], 'update']
* console.log(keys.users.byId(42).__key) // readonly ['users', ['byId', 42]]
* console.log(keys.posts.byUserId(42)) // readonly ['posts', ['byUserId', 42]]
* console.log(keys.posts.byMonth(3).byDay(15)) // readonly ['posts', ['byMonth', 3], ['byDay', 15]]
* console.log(keys.posts.byAuthorAndYear({ authorId: 'id', year: 2023 })) // readonly ['posts', ['byAuthorAndYear', { authorId: 'id', year: 2023 }]]
* ```
*/
declare function defineKeyHierarchy<T extends KeyHierarchyConfig<T>>(configOrBuilder: ConfigOrBuilder<T>, options?: KeyHierarchyOptions): KeyHierarchy<T>;
/**
* Defines a key hierarchy module.
* @remarks This function is a no-op and is used for type inference only.
* @param config - The declarative {@link KeyHierarchyConfig} of the key hierarchy module.
* @returns The {@link KeyHierarchyConfig} of the key hierarchy module.
* @example
* ```ts
* const userKeyModule = defineKeyHierarchyModule((dynamic) => ({
* getAll: true,
* create: true,
* byId: dynamic<number>().extend({
* get: true,
* update: true,
* delete: true,
* })
* }))
*
* const postKeyModule = defineKeyHierarchyModule((dynamic) => ({
* byUserId: dynamic<number>(),
* byMonth: dynamic<number>().extend({
* byDay: dynamic<number>(),
* })
* })
*
* const keys = defineKeyHierarchy({
* users: userKeyModule,
* posts: postKeyModule
* })
* ```
*/
declare function defineKeyHierarchyModule<T extends KeyHierarchyConfig<T>>(configOrBuilder: ConfigOrBuilder<T>): T;
/**
* Helper function to define dynamic key segments with a single argument.
* @returns A dynamic key segment that can optionally be extended with a nested configuration.
* @example
* ```ts
* defineKeyHierarchy({
* getAll: true,
* byGroup: dynamic<{ groupId: string }>(),
* byId: dynamic<string>().extend({ get: true, update: true }),
* byMonth: dynamic<number>().extend({
* byDay: dynamic<number>(),
* }),
* byAuthorAndYear: dynamic<{ authorId: string, year: number }>(),
* byTags: dynamic<{ tags: string[], filter?: PostFilter }>(),
* })
* ```
*/
declare function dynamicHelper<T>(): DynamicExtendableSegment<T>;
//#endregion
export { ConfigOrBuilder, DYNAMIC_EXTENDED_SEGMENT, DYNAMIC_SEGMENT, DeepReadonly, DeepReadonlyArray, DeepReadonlyObject, DynamicExtendableSegment, DynamicExtendedSegment, DynamicSegment, KeyHierarchy, KeyHierarchyConfig, KeyHierarchyOptions, defineKeyHierarchy, defineKeyHierarchyModule };
//# sourceMappingURL=index.d.ts.map