UNPKG

key-hierarchy

Version:

A tiny TypeScript library for managing key hierarchies. The perfect companion for TanStack Query.

159 lines 7.37 kB
//#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