TypeScript
Every @inglorious package ships its own type declarations, so there is nothing to install. To start a TypeScript project, pick the ts template of create-app.
The approach: describe your entities, say which ones the app has, and let satisfies check each type against them. No casts.
Entities
Each entity is an interface extending BaseEntity, with its type as a literal:
import { type BaseEntity, type EntitiesState } from "@inglorious/store"
import { type RouterEntity } from "@inglorious/web/router"
interface CounterEntity extends BaseEntity {
type: "Counter"
value: number
}
interface AppRouterEntity extends RouterEntity {
type: "Router"
}
type AppEntity = CounterEntity | AppRouterEntity
interface AppState extends EntitiesState<AppEntity> {
counter: CounterEntity
router: AppRouterEntity
}Types
Api<AppEntity, AppState> is the api every handler and render receives. satisfies EntityType<…> checks a type's handlers against its entity while keeping their own inferred signatures:
import { type EntityType } from "@inglorious/store"
import { type Api, html } from "@inglorious/web"
type AppApi = Api<AppEntity, AppState>
const Counter = {
increment(entity) {
entity.value++
},
render(entity, api) {
const route = api.getEntity("router")?.route
return html`<button @click=${() => api.notify(`#${entity.id}:increment`)}>
${entity.value} on ${route}
</button>`
},
} satisfies EntityType<CounterEntity, AppApi>entity.value++ is checked against CounterEntity, and api.getEntity("router") returns an AppRouterEntity, so a misspelled property is an error.
The store
import { createStore } from "@inglorious/store"
import { Router } from "@inglorious/web/router"
export const store = createStore<AppEntity, AppState>({
types: { Counter, Router },
entities: {
counter: { type: "Counter", value: 0 },
router: { type: "Router" },
},
})Featured types such as Form, Router and the sensors export their entity interfaces (FormEntity, RouterEntity, MediaQueryEntity…): extend them as above.
Events
Each type's events are its handlers, so their declarations can tell the api which events exist and what they carry. List them with type aliases, read them with EventsOf, and give them to the api:
import { type EventsOf } from "@inglorious/store"
import { type RouterType } from "@inglorious/web/router"
type CounterType = {
increment: (entity: CounterEntity) => void
add: (entity: CounterEntity, amount: number) => void
render: (entity: CounterEntity, api: AppApi) => TemplateResult
}
type AppEvents = EventsOf<CounterType | RouterType>
type AppApi = Api<AppEntity, AppState, AppEvents>Now api.notify(`#${entity.id}:incremnet`) is an error, and so is api.notify("#counter:add", "2"). Renderers are not events, and the store's own (add, remove, pause…) are always allowed. Pass AppEvents to createStore<AppEntity, AppState, AppEvents> as well: mount then gives the root render the same checks.
Templates with props
Every render method takes props first: render(props, api), where a type's props are its entity, and sub-renderers renderX(props, ownProps, api), which get the component's props, then the item they draw. EntityType reads every member as an event handler, which takes the entity, so it rejects sub-renderers whose props are not the entity; defineType checks each member by its name instead:
import { defineType } from "@inglorious/store"
export const Scoreboard = defineType<ScoreboardEntity, AppApi>()({
...template, // renderScoreboard(props, api), renderContender(props, speaker, api), ...
render(props, api) {
const memory = api.getEntity("debate")?.memory
return this.renderScoreboard({ ...props, memory }, api)
},
})render still takes the entity as its props, sub-renderers keep the types they declare, every other member is a handler with its entity inferred, and this is the component itself.
Checking your types
The packages check their own declarations with type tests: small .ts files compiled with tsc --noEmit that must pass, plus @ts-expect-error lines that must fail. The same works for an app's own types: see packages/web/type-tests/app.ts in the repository for a complete example.
