Skip to content

App & Module

A Module is a scoped set of providers with a lifecycle. An App is the root module - the only one you construct yourself. The semantics are on Modules; this page is the API surface.

Signature

ts
class Module {
    readonly id: string
    readonly parent: Module | null
    readonly resolver: Resolver
    readonly traversal: ModuleTraversal

    get children(): ReadonlySet<Module>
    get status(): ModuleStatus

    init(): void
    mount(): void
    unmount(): void
    destroy(): Promise<void>
}

class App extends Module {
    constructor(params?: ModuleParams)
}

The four phase methods are called by AppProvider and ModuleProvider, not by you.

new App(params?)

App is the only module with a public constructor you use. Scoped modules are built for you by ModuleProvider and createModuleComponent, under the module already in context.

ModuleParams is the whole argument, and every key is optional:

KeyTypeWhat it is
idstringThe module's name in traversal lookups and error messages. Defaults to a generated id:<n>.
providersreadonly ProviderInput[]Providers and features this module owns.
onModuleInit(resolver: Resolver) => unknownRuns when the module is armed.
onModuleMount(resolver: Resolver) => unknownRuns when the module mounts.
onModuleUnmount(resolver: Resolver) => unknownRuns when the module unmounts.
onModuleDestroy(resolver: Resolver) => unknownRuns when the module is destroyed.
tsx
import { App, AppProvider } from "@remodulo/react"

export const app = new App({
    providers: [OrdersApi],
    onModuleInit: (resolver) => resolver.resolve(OrdersApi).warmUp(),
})

export function Root() {
    return (
        <AppProvider app={app}>
            <OrdersPage />
        </AppProvider>
    )
}

app is constructed before React renders, so OrdersApi is registered by the time AppProvider arms it. Each hook receives the module's own resolver - there is no other argument.

Public surface

Everything below is readable on any module, App included:

MemberTypeWhat it is
idstringThe id you passed, or the generated one.
resolverResolverThis module's resolver. The same object useResolver returns.
traversalModuleTraversalThe tree around this module.
parentModule | nullThe module this one forked from. null on an App.
childrenReadonlySet<Module>Direct children that have mounted, in attach order.
statusModuleStatusSee below.

What is not on a module: container, lifecycle, addChild and removeChild are internal and stripped from the published types. Reads go through resolver; the tree goes through traversal.

status

The eight ModuleStatus values - created, initializing, initialized, mounted, unmounted, destroying, destroyed, failed - exist and you can read them, but reading them is diagnostics, not a lifecycle API: drive work from the module hooks, not from a status check.

An App is a Module; a Module is not an App

App carries a private type-only brand, so the subtyping runs one way in TypeScript too. A Module is not assignable to App, and AppProvider's app prop accepts only something built with new App(...). The brand is declared and emits no runtime field.

Where to go next

Guides

Reference

MIT licensed.