Typed Engine Surface
The FactoryEngineTyped type lets consumers opt into a closed, autocomplete-rich view of the engine. It catches typoed method names at compile time and — for the methods listed in MethodSignatures — gives real param and return types lifted from the source declarations.
import { tournamentEngine } from 'tods-competition-factory';
import type { FactoryEngineTyped } from 'tods-competition-factory';
const engine = tournamentEngine as FactoryEngineTyped;
// typed param + return
const { events = [] } = engine.getEvents({ tournamentRecord });
// events: Event[]
// IDE flags typos at compile time
// @ts-expect-error — `getEent` is not in FactoryEngineMethod
engine.getEent({ tournamentRecord });
How it's composed
type FactoryEngineTyped =
// 1. methods with real param + return types
MethodSignatures &
// 2. fallback for every remaining FactoryEngineMethod
Record<Exclude<FactoryEngineMethod, keyof MethodSignatures>, (...args: any[]) => any> &
// 3. the ergonomic facades
{ q: QueryFacade; on: ...; once: ...; off: ...; waitFor: ...; build: BuildFacade };
MethodSignaturescarries the highest-traffic methods. Each entry is atypeof <source-fn>, so the type reflects exactly what the implementation accepts and returns — no drift.- The fallback
Recordkeeps every other registered method nameable on the typed engine. As more methods migrate intoMethodSignatures, the fallback set shrinks. Adding a method is purely additive — it never breaks existing callers. - The facades —
q,on/once/off/waitFor,build— live alongside the methods so consumers reach them with the sameengine.prefix.
What's typed today
542 of 600 engine methods (~90 %) carry real param + return types via the generated MethodSignatures interface. The remaining ~10 % fall through to the open-shape fallback — typically methods that aren't exposed through a governor barrel (some are mounted from engine parts, some are dynamic). They still type-check as (...args: any[]) => any, so calls don't fail compile; they just lack inference.
The list is regenerated by walking governor barrels (src/assemblies/governors/) and engineStart.ts's lambda mount block. Adding a new governor export — or refactoring an existing one — is picked up automatically by the next pnpm build (the generator runs in prebuild).
engine.q.* also returns real TODS types (no more any[] / any):
const events: Event[] = engine.q.events();
const event: Event | undefined = engine.q.event({ eventId });
const matchUps: HydratedMatchUp[] = engine.q.matchUps();
const tournament: Tournament | undefined = engine.q.tournament();
Extending the typed surface
src/types/methodSignatures.ts is generated. Do not hand-edit it. Coverage grows automatically as new governor exports land — no per-method maintenance burden.
To add a method:
-
Re-export it from the right governor barrel (the convention already in use across
src/assemblies/governors/):// src/assemblies/governors/eventGovernor/query.tsexport { getEventTimeItem } from '@Query/base/timeItems'; -
Regenerate signatures (this also runs automatically in
prebuild):pnpm gen:method-signatures
That's it. FactoryEngineTyped picks the new method up via the regenerated MethodSignatures, the Exclude<FactoryEngineMethod, keyof MethodSignatures> step drops it from the open-shape fallback, and consumers get real types at the call site.
CI runs pnpm check:method-signatures to catch drift — if a generated signature would change but wasn't committed, the build fails.
What's NOT auto-generated
A few special engine surfaces are declared explicitly on FactoryEngineTyped rather than via the generator:
- The forge facades —
engine.q,engine.on/once/off/waitFor,engine.inspect,engine.build— have per-facade types inline. They're excluded from the generator via theSKIPset inscripts/generateMethodSignatures.mjs. engine.executionQueue,engine.execute,engine.importMethods— varargs-style engine internals that hand-curation models better thantypeof. Same skip mechanism.
Edge cases the generator handles automatically
- Renamed governor exports.
export { publicFindMatchUp as findMatchUp } from '@Acquire/findMatchUp'producesfindMatchUp: typeof publicFindMatchUpin the generated file, with the rightimport { publicFindMatchUp } from '@Acquire/findMatchUp'. - Methods with multiple parameter shapes.
typeofcarries unions and overloads through unchanged. - Private inline param types.
typeoflifts the structural signature into the dist.d.ts— no need to export the param type from the source file. - Relative
frompaths in governor barrels. Re-resolved against the governor's location and emitted relative tomethodSignatures.ts.
Why this exists
Before: engine.getEvents had the signature (...args: any[]) => any. Every consumer cast the return, every typo compiled cleanly. Today, the same call returns the real { events?: Event[]; … } shape, autocompletes in the IDE, and a misspelled method name is a compile error.
See src/types/methodSignatures.ts (generated), scripts/generateMethodSignatures.mjs (generator), and src/types/factoryTypes.ts (composition) for the implementation.