What you’ll learn

  • How to define computed properties alongside schema and actions
  • What computed is for — and what it must never do
  • How computed differs from actions
  • The boundary between computed and use-case logic

Defining computed

computed: {
  displayName(props) { return props.username; },
  isVerified(props)  { return props.emailVerified; },
  initials(props)    { return props.username.slice(0, 2).toUpperCase(); },
}

Usage:

user.displayName;  // 'alice' — not user.displayName()
user.isVerified;   // false
user.initials;     // 'AL'

They look like getters, work like getters, but are defined alongside your schema and actions.

What computed is FOR

  • Formatting: initials, fullName, abbreviatedAddress
  • Boolean checks: isVerified, isExpired, hasChildren
  • Aggregations: totalPrice = items.reduce(...), wordCount
  • Lookups: primaryEmail = emails[0]

Keep them synchronous, pure, and fast. No I/O, no async, no mutation.

What computed is NOT for

I/O in computed:

computed: {
  avatarUrl(props) {
    return s3.getSignedUrl(props.avatarKey); // ❌ runs on every access
  },
}

Computed runs on every access. A network call here means your entity is making HTTP requests when you read a property. Generate signed URLs at the use-case level.

Heavy computation:

computed: {
  recommendationScore(props) {
    return mlModel.predict(props.history); // ❌ CPU-heavy, runs every access
  },
}

If the value is expensive, compute it once in the use case.

Mutating state from computed:

computed: {
  nextId(props) {
    props.counter++;     // ❌ computed should never mutate
    return props.counter;
  },
}

Computed receives a read-only snapshot. Use an action for mutations.

Async computed — not supported:

computed: {
  async recommendations(props) { ... }
}

Computed vs. action

ComputedAction
Reads state, returns valueReads state, mutates state
No side effectsChanges entity state
Property access (user.isVerified)Method call (user.actions.verifyEmail())
Can’t reject or throwCan throw to reject mutation

Using an action when computed would do:

actions: {
  getDisplayName(state) { return state.username; }, // computed territory
}

Using computed to enforce validation:

computed: {
  isValid(props) {
    if (props.items.length === 0) return false; // silently returns false
    return true;
  },
}

Don’t use computed for validation. Invariants (on Aggregate) or guard clauses (in actions) actively reject invalid states. Computed is passive.


What you’ve learned

Computed properties derive values from state. Defined alongside schema and actions in the entity config. Accessed as properties — no function call syntax.

Computed is synchronous and pure. No I/O, no async, no mutation. If you need a database call or heavy computation, do it in the use case and attach the result separately.

Computed ≠ validation. Computed reports state. Actions and invariants enforce it. Don’t use isValid() to silently track broken state — throw in the action that would cause the invalid state.

What’s next?

Computed properties derive values. Invariants enforce them. Invariants covers cross-field consistency rules — where they live, when they run, and why they’re the backbone of domain integrity.

Invariants