2024-11-13 13:34:43 -06:00
import { Expectation } from "../types/common" ;
import { Context , type HydratedContext } from "../types/context" ;
import { Message , type HydratedMessage } from "../types/message" ;
import { exists } from "../utils" ;
import type { API } from "./api" ;
import type { Client } from "./client" ;
export type GatewayObject = Message ;
export type Hydrateable = Context | GatewayObject
2024-11-15 13:49:33 -06:00
export type Hydrated < T extends Hydrateable , K extends Array < Expectation > > =
2024-11-13 13:34:43 -06:00
T extends Message ? HydratedMessage < K > :
T extends Context ? HydratedContext < K > : never ;
2024-11-15 13:49:33 -06:00
/**
* Hydrates a given data object based on the provided expectations.
*
* @template T - The type of the data object to be hydrated. Must extend `Hydrateable`.
* @template K - An array of `Expectation` types that specify what properties should be hydrated.
*
* @param data - The data object to be hydrated. Can be of type `Context` or `Message`.
* @param expectations - An array of expectations that specify what properties should be hydrated.
* @param client - The client instance used to fetch additional data required for hydration.
* @param api - The API instance used to fetch additional data required for hydration.
*
* @returns A promise that resolves to the hydrated data object.
*
* @throws Will throw an error if an expectation cannot be resolved.
*/
export async function hydrate < T extends Hydrateable | Hydrated < any , any > , K extends Array < Expectation > > (
data : T ,
expectations : K ,
client : Client ,
api : API
) : Promise < T extends Hydrated < infer U , infer H > ? Hydrated < T & U , [ ...H , ...K ] > : Hydrated < T , K > > {
// TODO: add clone trait. Make it a requirement for hydrateable objects.
// Then clone the object and return the hydrated object.
let hydrated : Hydrated < T , K > ;
2024-11-13 13:34:43 -06:00
if ( data instanceof Context ) {
for ( const expectation of expectations ) {
2024-12-16 12:48:18 -06:00
if ( expectation === Expectation . Message && exists ( data . message_id ) ) {
let context = data as unknown as HydratedContext < [ Expectation . Message , . . . Expectation [ ] ] > ;
context . message = await client . messages . get ( data . channel_id , data . message_id ) ;
} else if ( expectation === Expectation . Guild && exists ( data . guild_id ) ) {
2024-11-13 13:34:43 -06:00
let context = data as unknown as HydratedContext < [ Expectation . Guild , . . . Expectation [ ] ] > ;
context . guild = await client . guilds . get ( data . guild_id ) ;
} else {
2024-12-16 12:48:18 -06:00
throw new Error ( ` Invalid expectation requested for Context: ${ expectation } ` ) ;
2024-11-13 13:34:43 -06:00
}
}
} else if ( data instanceof Message ) {
for ( const expectation of expectations ) {
if ( expectation === Expectation . Message && exists ( data . message_reference ? . id ) ) {
let message = data as unknown as HydratedMessage < [ Expectation . Message , . . . Expectation [ ] ] > ;
message . reference = await client . messages . get ( data . message_reference . channel_id , data . message_reference . id ) ;
} else if ( expectation === Expectation . Guild && exists ( data . guild_id ) ) {
let message = data as unknown as HydratedMessage < [ Expectation . Guild , . . . Expectation [ ] ] > ;
message . guild = await client . guilds . get ( data . guild_id ) ;
} else {
2024-12-16 12:48:18 -06:00
throw new Error ( ` Invalid expectation requested for Message: ${ expectation } ` ) ;
2024-11-13 13:34:43 -06:00
}
}
}
2024-11-15 13:49:33 -06:00
return data as unknown as ( T extends Hydrated < infer U , infer H > ? Hydrated < T & U , [ ...H , ...K ] > : Hydrated < T , K > ) ;
}
/**
* A wrapper function that attempts to hydrate a given data object and returns a type guard function.
*
* @template T - The type of the data object to be hydrated. Must extend `Hydrateable`.
* @template K - An array of `Expectation` types that specify what properties should be hydrated.
*
* @param data - The data object to be hydrated. Can be of type `Context` or `Message`.
* @param expectations - An array of expectations that specify what properties should be hydrated.
* @param client - The client instance used to fetch additional data required for hydration.
* @param api - The API instance used to fetch additional data required for hydration.
*
* @returns A promise that resolves to a type guard function. The type guard function returns `true` if the data object was successfully hydrated, otherwise `false`.
*/
export async function hydrator < T extends Hydrateable | Hydrated < any , any > , K extends Array < Expectation > > (
data : T ,
expectations : K ,
client : Client ,
api : API
) : Promise < ( data : unknown ) = > data is T extends Hydrated < infer U , infer H > ? Hydrated < T & U , [ ...H , ...K ] > : Hydrated < T , K > > {
let hydrated = true ;
try {
// TODO: When clone is implemented we will receive a clone of the object here
// So in hydrator we will need to copy the properties from the clone to the original object
await hydrate ( data , expectations , client , api ) ;
} catch ( error ) {
hydrated = false ;
}
// BUG: Resolves to never after hydrations on an already hydrated object
return function ( data : unknown ) : data is T extends Hydrated < infer U , infer H > ? Hydrated < T & U , [ ...H , ...K ] > : Hydrated < T , K > {
2024-11-13 13:34:43 -06:00
return hydrated ;
}
}