Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 87 additions & 12 deletions resourcerer.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,19 @@ type GetModelOptions<C> =
: never;

declare module "resourcerer" {
type WithModelSuffix<K extends ResourceKeys, C> =
type WithModelSuffix<K extends string, C> =
C extends Collection<any, any> ? `${K}Collection` : `${K}Model`;
export type LoadingStates = "error" | "loading" | "loaded" | "pending";

// empty interface for declaration merging - users extend this with their model definitions
// @ts-ignore
export interface ModelMap {}
export type ResourceKeys = Extract<keyof ModelMap, string>;

// Resource config for a specific resource key
// K represents the ResourceKey, which can be either:
// - The key itself (when the key IS a ResourceKey)
// - The resourceKey field value (when using a custom key with resourceKey property)
export type ResourceConfigObj<K extends ResourceKeys> = {
data?: Partial<GetModelOptions<InstanceType<ModelMap[K]>>[0]>;
dependsOn?: boolean;
Expand All @@ -27,15 +37,69 @@ declare module "resourcerer" {
prefetches?: { [key: string]: any }[];
provides?: (
model: InstanceType<ModelMap[K]>,
props: Record<string, any>
props: Record<string, any>,
) => { [key: string]: any };
};

export interface ModelMap {}
export type ResourceKeys = Extract<keyof ModelMap, string>;
// Helper type to create a config object with resourceKey field
// Used when the executor key is not a ResourceKey and needs a resourceKey property
type ResourceConfigWithKey<RK extends ResourceKeys> = ResourceConfigObj<RK> & {
resourceKey: RK;
};

// Helper to extract resourceKey from a config object
// If the config has a resourceKey property, extract it
// Otherwise, if the config is ResourceConfigObj<K>, K is the resourceKey
type ExtractResourceKey<Config> =
Config extends { resourceKey: infer RK } ?
RK extends ResourceKeys ?
RK
: never
: never;

// Helper to get the resource key for a given key using the executor return type
// If the key is a ResourceKeys, use it directly
// Otherwise, extract the resourceKey from the config
type GetResourceKey<K extends string, R extends Record<string, any>> =
K extends ResourceKeys ? K
: K extends keyof R ?
R[K] extends infer Config ?
Config extends undefined ?
never
: ExtractResourceKey<Config>
: never
: never;

// Helper to get the model constructor type for a given key
type GetModelConstructor<K extends string, R extends Record<string, any>> =
GetResourceKey<K, R> extends keyof ModelMap ? ModelMap[GetResourceKey<K, R>] : never;

// Helper to get the model instance type for a given key
type GetModelType<K extends string, R extends Record<string, any>> =
GetModelConstructor<K, R> extends new (...args: any[]) => infer ModelType ? ModelType : never;

// Union type of all possible configs with resourceKey
// TypeScript will narrow this discriminated union based on the literal resourceKey value
// When you use resourceKey: "energySource", provides will be typed for energySourceModel
type ConfigWithResourceKey = {
[RK in ResourceKeys]: ResourceConfigWithKey<RK>;
}[ResourceKeys];

export type ExecutorFunction<T extends ResourceKeys, O = Record<string, never>> = (props: O) => {
[Key in T]?: ResourceConfigObj<Key>;
// ExecutorFunction with explicit resourceKey mapping
// M maps executor keys to their resourceKeys (e.g., { gridEnergySource: "energySource" })
// When a custom key is used with resourceKey property, TypeScript will narrow the provides type
// based on the literal resourceKey value provided
export type ExecutorFunction<
T extends string,
O = Record<string, never>,
M extends Partial<Record<T, ResourceKeys>> = Partial<Record<T, ResourceKeys>>,
> = (props: O) => {
[Key in T]?: Key extends ResourceKeys ? ResourceConfigObj<Key>
: Key extends keyof M ?
M[Key] extends ResourceKeys ?
ResourceConfigWithKey<M[Key]>
: ConfigWithResourceKey
: ConfigWithResourceKey;
};

export type UseResourcesResponse = {
Expand All @@ -48,14 +112,25 @@ declare module "resourcerer" {
setResourceState(newState: { [key: string]: any }): void;
};

export function useResources<T extends ResourceKeys, O extends Record<string, any>>(
getResources: ExecutorFunction<T, O>,
_props: O
export function useResources<
O extends Record<string, any>,
F extends (props: O) => Record<string, any>,
T extends string = Extract<keyof ReturnType<F>, string>,
R extends ReturnType<F> = ReturnType<F>,
>(
getResources: F,
_props: O,
): UseResourcesResponse & {
[Key in T as WithModelSuffix<Key, InstanceType<ModelMap[Key]>>]: InstanceType<ModelMap[Key]>;
[Key in T as GetResourceKey<Key, R> extends ResourceKeys ?
GetModelType<Key, R> extends infer C ?
C extends Collection<any, any> ?
`${Key}Collection`
: `${Key}Model`
: `${Key}Model`
: never]: GetModelType<Key, R>;
} & {
[Key in T as `${T}LoadingState`]: LoadingStates;
[Key in T as `${Key}LoadingState`]: LoadingStates;
} & {
[Key in T as `${T}Status`]: number;
[Key in T as `${Key}Status`]: number;
};
}