import { objectFreeze } from './utilities'; import { createInterface, instanceRegistration } from './utilities-di'; import { isFunction, type IContainer, type IRegistry, type Key, type Resolved } from '@aurelia/kernel'; export type TaskSlot = | 'creating' | 'hydrating' | 'hydrated' | 'activating' | 'activated' | 'deactivating' | 'deactivated'; export const IAppTask = /*@__PURE__*/createInterface('IAppTask'); export interface IAppTask { readonly slot: TaskSlot; register(c: IContainer): IContainer; run(): void | Promise; } class $AppTask implements IAppTask { public readonly slot: TaskSlot; /** @internal */ private c: IContainer = (void 0)!; /** @internal */ private readonly k: K | null; /** @internal */ private readonly cb: AppTaskCallback | AppTaskCallbackNoArg; public constructor( slot: TaskSlot, key: K | null, cb: AppTaskCallback | AppTaskCallbackNoArg, ) { this.slot = slot; this.k = key; this.cb = cb; } public register(container: IContainer): IContainer { return this.c = container.register(instanceRegistration(IAppTask, this)); } public run(): void | Promise { const key = this.k; const cb = this.cb; return (key === null ? (cb as AppTaskCallbackNoArg)() : cb(this.c.get(key))) as Promise; } } export const AppTask = objectFreeze({ /** * Returns a task that will run just before the root component is created by DI */ creating: createAppTaskSlotHook('creating'), /** * Returns a task that will run after instantiating the root controller, * but before compiling its view (thus means before instantiating the child elements inside it) * * good chance for a router to do some initial work, or initial routing related in general */ hydrating: createAppTaskSlotHook('hydrating'), /** * Return a task that will run after the hydration of the root controller, * but before hydrating the child element inside * * good chance for a router to do some initial work, or initial routing related in general */ hydrated: createAppTaskSlotHook('hydrated'), /** * Return a task that will run right before the root component is activated. * In this phase, scope hierarchy is formed, and bindings are getting bound */ activating: createAppTaskSlotHook('activating'), /** * Return a task that will run right after the root component is activated - the app is now running */ activated: createAppTaskSlotHook('activated'), /** * Return a task that will runs right before the root component is deactivated. * In this phase, scope hierarchy is unlinked, and bindings are getting unbound */ deactivating: createAppTaskSlotHook('deactivating'), /** * Return a task that will run right after the root component is deactivated */ deactivated: createAppTaskSlotHook('deactivated'), }); // unknown as the return of an app task will be ignored // only cares whether it's a promise or not // the benefit of unknown is that application can avoid having to write () => { doThingsThatDoesNotReturnVoid() } export type AppTaskCallbackNoArg = () => unknown; export type AppTaskCallback = (arg: Resolved) => unknown; function createAppTaskSlotHook(slotName: TaskSlot) { // eslint-disable-next-line @typescript-eslint/no-unused-vars function appTaskFactory(callback: AppTaskCallbackNoArg): IRegistry; function appTaskFactory(key: T, callback: AppTaskCallback): IRegistry; function appTaskFactory(keyOrCallback: T | AppTaskCallback | AppTaskCallbackNoArg, callback?: AppTaskCallback): IRegistry { if (isFunction(callback)) { return new $AppTask(slotName, keyOrCallback as T, callback); } return new $AppTask(slotName, null, keyOrCallback as Exclude); } return appTaskFactory; }