@adonisjs/inertia
Version:
Official Inertia.js adapter for AdonisJS
394 lines (393 loc) • 14.4 kB
TypeScript
import type { HttpContext } from '@adonisjs/core/http';
import { type ContainerResolver } from '@adonisjs/core/container';
import type { JSONDataTypes } from '@adonisjs/core/types/transformers';
import type { AsyncOrSync, DeepPartial, Prettify } from '@adonisjs/core/types/common';
import { type DEEP_MERGE, type ALWAYS_PROP, type OPTIONAL_PROP, type TO_BE_MERGED, type DEFERRED_PROP } from './symbols.ts';
/**
* Representation of a resource item, collection and paginator that can be resolved to
* get normalized objects
*
* @template T - The type that the resource resolves to
*/
export type ResolvableOf<T> = {
resolve(container: ContainerResolver<any>, depth: number, maxDepth?: number): Promise<T>;
};
/**
* Union type representing unpacked page prop values that can be either JSON data or serializable objects
*
* @template T - The JSON data type, defaults to JSONDataTypes
*/
export type UnPackedPageProps<T extends JSONDataTypes = JSONDataTypes> = T | ResolvableOf<T>;
/**
* Utility type that extracts the resolved type from a SerializableOf wrapper
* If the type is already unwrapped, returns it as-is
*
* @template T - The type to unwrap, potentially wrapped in SerializableOf
*/
export type UnpackProp<T> = T extends ResolvableOf<infer A> ? A : T;
/**
* Information extracted from Inertia request headers
* Contains metadata about the current request type and filtering preferences
*/
export type RequestInfo = {
/** Asset version sent by the client for cache busting */
version?: string;
/** Whether this is an Inertia AJAX request */
isInertiaRequest: boolean;
/** Whether this is a partial data request */
isPartialRequest: boolean;
/** Component name for partial reloads */
partialComponent?: string;
/** Props to include in partial requests */
onlyProps?: string[];
/** Props to exclude in partial requests */
exceptProps?: string[];
/** Props to reset during merging */
resetProps?: string[];
/** Error bag identifier for validation errors */
errorBag?: string;
};
/**
* Represents a prop that is always included in responses and cannot be removed during cherry-picking
*
* @template T - The type of the prop value
*/
export type AlwaysProp<T extends UnPackedPageProps> = {
/** The actual value of the prop */
value: T;
/** Brand symbol to identify this as an always prop */
[ALWAYS_PROP]: true;
};
/**
* Represents a prop that is never included in standard visits but can be explicitly requested
* The prop value is computed lazily when requested
*
* @template T - The type of the computed prop value
*/
export type OptionalProp<T extends UnPackedPageProps> = {
/** Function that computes the prop value when requested */
compute: () => AsyncOrSync<T>;
/** Brand symbol to identify this as an optional prop */
[OPTIONAL_PROP]: true;
};
/**
* Represents a deferred prop that is never included in standard visits but must be shared with
* the client during standard visits. Can be explicitly requested and supports merging
*
* @template T - The type of the computed prop value
*/
export type DeferProp<T extends UnPackedPageProps> = {
group: string;
/** Function that computes the prop value when requested */
compute: () => AsyncOrSync<T>;
/** Creates a mergeable version of this deferred prop */
merge(): MergeableProp<DeferProp<T>>;
/** Brand symbol to identify this as a deferred prop */
[DEFERRED_PROP]: true;
};
/**
* Represents a prop that should be merged with existing props on the page rather than replaced
*
* @template T - The type of the prop value to be merged
*/
export type MergeableProp<T extends UnPackedPageProps | DeferProp<UnPackedPageProps>> = {
/** The prop value to be merged */
value: T;
/** Brand symbol to identify this prop for merging */
[TO_BE_MERGED]: true;
[DEEP_MERGE]: boolean;
};
/**
* Lazy props are never included during standard Inertia visits
* These props must be explicitly requested by the client
*
* @template T - The data type of the prop value
*/
type PagePropsLazyDataTypes<T extends JSONDataTypes> =
/**
* - Never included on standard visit
* - Must be shared with the client during standard visit
* - Can be explicitly requested for
* - Can be dropped during cherry-picking
*/
DeferProp<T | ResolvableOf<T>>
/**
* - Never included on standard visit
* - Can be explicitly requested for
* - Can be dropped during cherry-picking
*/
| OptionalProp<T | ResolvableOf<T>>;
/**
* Eager props are always included during standard Inertia visits, but
* can be removed via cherry-picking when only specific props are requested
*
* @template T - The data type of the prop value
*/
type PagePropsEagerDataTypes<T extends JSONDataTypes> =
/**
* - Always included on standard visit.
* - Can be dropped during cherry-picking
*/
T
/**
* - Always included on standard visit.
* - Can be dropped during cherry-picking
*/
| ResolvableOf<T>
/**
* - Always included on standard visit.
* - Can be dropped during cherry-picking
*/
| (() => AsyncOrSync<T | ResolvableOf<T>>)
/**
* - Always included on standard visit
* - Cannot be dropped during cherry-picking
*/
| AlwaysProp<T | ResolvableOf<T>>;
/**
* Following is the list of acceptable Page props data types
* Combines both eager and lazy prop data types for comprehensive prop handling
*
* @template T - The data type extending JSONDataTypes, defaults to JSONDataTypes
*/
export type PagePropsDataTypes<T extends JSONDataTypes = JSONDataTypes> = PagePropsEagerDataTypes<T> | PagePropsLazyDataTypes<T>;
/**
* Record type representing all page props that can be passed to an Inertia page
* Maps prop names to their corresponding data types, including branded types for special behavior
*/
export type PageProps = Record<string, PagePropsDataTypes | MergeableProp<UnPackedPageProps | DeferProp<UnPackedPageProps>>>;
/**
* Record type representing component props as they appear on the frontend after serialization
* Maps prop names to JSON-serializable values that components can consume directly
*/
export type ComponentProps = Record<string, JSONDataTypes>;
/**
* Utility type to extract optional and deferred prop keys from a props object
* Identifies props that are not required and may not be present in the component
*
* @template Props - The page props object type to analyze
*/
export type GetOptionalProps<Props> = {
[K in keyof Props]: Props[K] extends OptionalProp<any> ? K : Props[K] extends DeferProp<any> ? K : [undefined] extends [Props[K]] ? K : Props[K] extends MergeableProp<infer A> ? A extends DeferProp<any> ? K : never : never;
}[keyof Props];
/**
* Utility type to extract required prop keys from a props object
* Identifies props that are always present and required by the component
*
* @template Props - The page props object type to analyze
*/
export type GetRequiredProps<Props> = {
[K in keyof Props]: Props[K] extends OptionalProp<any> ? never : Props[K] extends DeferProp<any> ? never : [undefined] extends [Props[K]] ? never : Props[K] extends MergeableProp<infer A> ? A extends DeferProp<any> ? never : K : K;
}[keyof Props];
/**
* Utility type to simplify value of a required prop by unwrapping branded types
* Extracts the actual value type from wrapped prop types like AlwaysProp, functions, etc.
*
* @template Value - The prop value type to unwrap
*/
export type GetRequiredPropValue<Value> = Value extends AlwaysProp<infer A> ? UnpackProp<A> : Value extends MergeableProp<infer B> ? UnpackProp<B> : Value extends () => AsyncOrSync<infer C> ? UnpackProp<C> : UnpackProp<Value>;
/**
* Utility type to simplify value of an optional prop by unwrapping branded types
* Extracts the actual value type from wrapped optional prop types like DeferProp, OptionalProp, etc.
*
* @template Value - The optional prop value type to unwrap
*/
export type GetOptionalPropValue<Value> = Value extends DeferProp<infer A> ? UnpackProp<A> : Value extends MergeableProp<infer B> ? B extends DeferProp<infer BA> ? UnpackProp<BA> : UnpackProp<B> : Value extends OptionalProp<infer C> ? UnpackProp<C> : Value extends () => AsyncOrSync<infer D> ? UnpackProp<D> : UnpackProp<Value>;
/**
* Converts the Page props to Component props that will be available to the frontend
* app after serialization. Maps server-side prop definitions to client-side prop types
*
* @template Props - The page props object with branded prop types
*/
export type ToComponentProps<Props extends PageProps> = Prettify<{
[K in GetRequiredProps<Props>]: GetRequiredPropValue<Props[K]>;
} & {
[K in GetOptionalProps<Props>]?: GetOptionalPropValue<Props[K]>;
}>;
/**
* Converts the Component props to Page props to allow computing the same values
* via branded types and lazy evaluated callbacks and promises
* Maps client-side prop types back to server-side prop definitions
*
* @template Props - The component props object with JSON data types
*/
export type AsPageProps<Props extends ComponentProps> = Prettify<{
[K in {
[O in keyof Props]: [undefined] extends [Props[O]] ? O : never;
}[keyof Props]]?: PagePropsDataTypes<Props[K]> | MergeableProp<UnPackedPageProps<Props[K]> | DeferProp<UnPackedPageProps<Props[K]>>>;
} & {
[K in {
[O in keyof Props]: [undefined] extends [Props[O]] ? never : O;
}[keyof Props]]: PagePropsEagerDataTypes<Props[K]> | MergeableProp<UnPackedPageProps<Props[K]>>;
}>;
/**
* Allowed values for the assets version used for cache busting
* Can be a string, number, or undefined for auto-detection
*/
export type AssetsVersion = string | number | undefined;
/**
* Resolved configuration returned by the `defineConfig` helper
* Contains all settings needed to configure Inertia.js integration
*/
export type InertiaConfig = {
/**
* Root Edge template to use for rendering the shell for the inertia
* application
*/
rootView: string | ((ctx: HttpContext) => string);
/**
* A fixed asset version value to use. Otherwise, it will be read from the
* Vite manifest file.
*/
assetsVersion?: AssetsVersion;
/**
* History encryption settings. https://inertiajs.com/history-encryption
*/
encryptHistory: boolean;
/**
* Configuration settings for server-side rendering of the frontend
* app
*/
ssr: {
/**
* Enable/disable the SSR. Disabled by default
*/
enabled: boolean;
/**
* Cherry pick the pages you want to render server side
*/
pages?: string[] | ((ctx: HttpContext, page: string) => AsyncOrSync<boolean>);
/**
* The entrypoint file to load in order to boot the frontend application on
* the server
*/
entrypoint: string;
/**
* The SSR bundle output to load during production. This bundle is created
* using Vite
*/
bundle: string;
};
};
/**
* Input configuration type allowing partial configuration objects
* Used when defining configuration where all properties are optional and can be deeply partial
*/
export type InertiaConfigInput = DeepPartial<InertiaConfig>;
/**
* Represents a page object that is passed between server and client
*
* @template Props - The props type for the page component
*/
export type PageObject<Props> = {
/**
* The name/path of the component to render
*/
component: string;
/**
* Version identifier sent to the client with every request. Inertia
* will trigger a full page refresh (in case of version mis-match)
*/
version: string | number;
/**
* Props data to pass to the component. These should be JSON values
*/
props: Props;
/**
* Current URL of the page
*/
url: string;
/**
* Grouped deferred props that can be loaded after the initial page
* load
*/
deferredProps?: {
[group: string]: string[];
};
/**
* An array with the keys of props that should be merged with the
* existing props on the page
*/
mergeProps?: string[];
/**
* An array with the keys of props that should be deeply merged with the
* existing props on the page
*/
deepMergeProps?: string[];
/**
* Encrypt history flag to be sent to the client with every request.
*/
encryptHistory?: boolean;
/**
* Optionally clear the browser history
*/
clearHistory?: boolean;
};
/**
* The shared props inferred from the user-land
* Should be augmented in the host application to define globally available props
*
* @example
* ```typescript
* declare module '@adonisjs/inertia/types' {
* interface SharedProps {
* user: { id: number; name: string } | null
* flash: { success?: string; error?: string }
* }
* }
* ```
*/
export interface SharedProps {
}
/**
* Discovered known pages with their props
* Should be augmented in the host application to define page-specific prop types
*
* @example
* ```typescript
* declare module '@adonisjs/inertia/types' {
* interface InertiaPages {
* 'users/index': { users: User[] }
* 'users/show': { user: User }
* }
* }
* ```
*/
export interface InertiaPages {
}
/**
* Function signature for the SSR render method that should be exported
* from the SSR entrypoint file to render Inertia pages on the server
*
* @param page - The page object containing component and props data
* @returns Promise resolving to an object with head tags and body HTML
*/
export type RenderInertiaSsrApp = (page: PageObject<any>) => Promise<{
head: string[];
body: string;
}>;
/**
* Type helper to infer the return type of InertiaMiddleware.share method
* and augment the SharedProps interface automatically
*
* @template T - The middleware class type that extends BaseInertiaMiddleware
*
* @example
* ```typescript
* class InertiaMiddleware extends BaseInertiaMiddleware {
* async share() {
* return {
* user: { id: 1, name: 'John' },
* flash: { success: 'Welcome!' }
* }
* }
* }
*
* // Automatically infer and augment SharedProps
* type InferredSharedProps = InferSharedProps<InertiaMiddleware>
* ```
*/
export type InferSharedProps<T> = T extends {
share(...args: any[]): infer R;
} ? Awaited<R> extends PageProps ? ToComponentProps<Awaited<R>> : never : never;
export {};