Intent
An Intent is a declarative mapping from a user action to one or more commands. It is what you call from your UI — the entry point into Hurum’s data flow.
import { Intents, Intent } from '@hurum/core'
const PurchaseIntents = Intents('Purchase', { submitButtonClicked: Intent(ValidateCommand, SavePurchaseCommand), pageOpened: Intent(LoadPurchaseCommand), cancelClicked: Intent(CancelCommand),})
// Send from your UIPurchaseStore.send.submitButtonClicked({ id: '123' })How it works
Section titled “How it works”Intent(Command1, Command2, ...) creates a sequential intent. When triggered, it runs each command’s executor in order. If one throws, the rest are skipped.
Intents() groups related intents under a namespace, similar to how Events() groups events. Each key becomes a method on store.send:
const CounterIntents = Intents('Counter', { plusClicked: Intent(IncrementCommand), minusClicked: Intent(DecrementCommand), resetClicked: Intent(ResetCommand),})
// After registering with a Store:CounterStore.send.plusClicked({ amount: 1 })CounterStore.send.resetClicked({})The payload you pass to send is forwarded to every command in the intent. TypeScript checks that the payload type is compatible with all commands at compile time.
Execution strategies
Section titled “Execution strategies”Hurum provides three strategies for running multiple commands:
const AppIntents = Intents('App', { // Sequential (default) -- run one after another, stop on first error submit: Intent(ValidateCommand, SaveCommand),
// Parallel fail-fast -- run all at once, abort others on first error quickSubmit: Intent.all(ValidateCommand, SaveCommand),
// Parallel independent -- run all at once, each completes regardless initialize: Intent.allSettled( LoadCurrencyCommand, LoadVATCommand, LoadPurchaseCommand, ),})| Strategy | Execution | On error |
|---|---|---|
Intent(A, B, C) | A, then B, then C | Stop. B and C don’t run. |
Intent.all(A, B, C) | A, B, C simultaneously | Abort the others. |
Intent.allSettled(A, B, C) | A, B, C simultaneously | Others continue. |
Cancellation
Section titled “Cancellation”Intents support automatic and manual cancellation:
Auto-cancel on re-execution. Sending the same intent again aborts the previous run. This is useful for search-as-you-type: each keystroke cancels the previous search.
// The first call starts runningPurchaseStore.send.submitButtonClicked({ id: '123' })
// This aborts the first call and starts a new onePurchaseStore.send.submitButtonClicked({ id: '456' })Manual cancel. Use store.cancel(ref) for a specific intent or store.cancelAll() for everything:
// Cancel a specific intentconst ref = PurchaseStore.send.submitButtonClicked({ id: '123' })PurchaseStore.cancel(ref)
// Cancel all running executorsPurchaseStore.cancelAll()When an executor is cancelled, its signal.aborted becomes true and any events emitted after that point are silently ignored.
Common patterns
Section titled “Common patterns”Page lifecycle intents
Section titled “Page lifecycle intents”const PageIntents = Intents('ProductPage', { // Load everything the page needs opened: Intent.allSettled( LoadProductCommand, LoadReviewsCommand, LoadRecommendationsCommand, ),
// Clean up on leave closed: Intent(CleanupCommand),})Form submission
Section titled “Form submission”const FormIntents = Intents('ContactForm', { // Validate first, then submit submitted: Intent(ValidateFormCommand, SubmitFormCommand),
// Individual field changes fieldChanged: Intent(UpdateFieldCommand),})Search with debounce
Section titled “Search with debounce”The auto-cancel behavior means you don’t need a separate debounce utility for basic cases:
const SearchIntents = Intents('Search', { queryChanged: Intent(SearchCommand),})
// Each call cancels the previous oneSearchStore.send.queryChanged({ query: 'h' })SearchStore.send.queryChanged({ query: 'hu' })SearchStore.send.queryChanged({ query: 'hur' }) // only this one completesFor true debounce (waiting N ms before executing), handle it in the executor itself.
- Name intents after user actions.
plusClicked,formSubmitted,pageOpened— notincrementCounterorsaveData. The intent describes what the user did, not what the system should do. - Use
Intent.allSettledfor independent loads. If loading currencies, VAT rates, and purchase data are independent, don’t let one failure block the others. - One intent per user action. If a button click should validate then save, that’s one intent with two commands, not two intents.
- Intents belong to exactly one Store. You register intents with
.intents()in the Store builder. The same intent namespace cannot be shared across stores.