Rxova
Skip to content

Function: defineWork()

@rxova/journey-core


@rxova/journey-core / defineWork

function defineWork<TBag, TType>(): <TResult>(config) => GraphOnEntry<TBag["context"], TBag["stepId"], TBag["events"], HandlersOf<TBag>, MetaOf<TBag>, ResultOf<TBag, TType>, Extract<TBag["events"], {
type: TType;
}>>;

Defined in: packages/core/src/graph/work.ts:100

Declares an event’s async so that run’s return type flows into commit and into the candidates’ guards.

Written straight into a definition, run sits at a property position, which is not an inference site — so result is unknown unless the type is restated in the bag’s results. Pinning it there works, but it is a second place to keep in sync, and it is one result type per event name rather than per (step, event) pair. Passing the same config through a generic function call puts TResult back at an inference site, so it is read off run and nothing needs restating:

on: {
SUBMIT: defineWork<AuthBag, "SUBMIT">()({
run: ({ handlers }) => handlers.login(), // TResult inferred here
commit: ({ result, updateContext }) => // ...and typed here
updateContext((c) => ({ ...c, token: result.token })),
timeoutMs: 10_000,
candidates: [
{ to: "twofa", label: "needs-2fa", when: ({ result }) => result?.twoFactor === true },
{ to: "home", label: "logged-in" }
]
})
}

The call is curried because TypeScript infers all of a call’s type arguments or none: naming the bag and the event inline would opt TResult out of inference too, which is the problem being solved. The empty second call is the price of pinning the first two and inferring the third.

Pairs with a bag, so reach for it through withGraphTypes<TBag>() rather than plain createGraphJourney: the bag is where stepId, events and handlers come from, and a definition that infers those itself will not line up with them.

At runtime this returns its argument unchanged. The cast is what erases the narrowed event and the inferred TResult back down to the slot’s wider shape — a guard’s result sits at a contravariant position, so a pinned config cannot be assigned into an unknown slot directly. That is the same erasure the v1 builder did, and it is sound because the config has already been checked, against sharper types, at this call site.

Type ParameterDefault type
TBag extends Bag-
TType extends stringTBag["events"]["type"]

<TResult>(config) => GraphOnEntry<TBag["context"], TBag["stepId"], TBag["events"], HandlersOf<TBag>, MetaOf<TBag>, ResultOf<TBag, TType>, Extract<TBag["events"], { type: TType; }>>