Size / Priority
- Size: M — many APIs touched; not a single PR.
- Category: C.2 Simplifications & DRY (consistency).
- Risk: medium — public-API-shape change for some types.
Affected files
- Across the codebase — public APIs that return one of:
X | null, X | undefined, or Option<X>.
Background
The codebase mixes three "no value" idioms:
null — explicit "nothing".
undefined — absent / not set.
Option<X> — custom Maybe-like wrapper.
Inconsistent across APIs. Same conceptual "find me X" returns each variant at different call sites. Some examples:
ActorContext.child(name): Option<ActorRef> — uses Option.
Cache.get<V>(key): Promise<V | undefined> — uses undefined.
Cluster.currentLeader(): Option<Member> — uses Option.
LookupResult.value: T | null — uses null.
This is a recurring point of confusion in the README + onboarding.
Target — codebase rule
Decide one shape per use case:
Option<X> for cases where "absent" is a frequent + meaningful result that callers should explicitly handle (e.g., resolving a path).
X | undefined for optional values where the caller will often skip if missing (e.g., cache lookup).
- Avoid
null — pick one of the above per case.
Document the rule. Migrate inconsistent sites.
Migration plan
This is a multi-PR cleanup:
- Audit — every public API returning one of these shapes. Decide target shape per API.
- Per-PR — migrate ~5-10 APIs at a time.
- Deprecation period — keep old signatures with
@deprecated for a release.
- Removal — remove deprecated alternatives.
Integration / risk
- Public API changes — semver caveat. Pre-1.0 is acceptable; document in CHANGELOG.
Option polyfill — keep the existing Option type; expand documentation.
- Type signature ripple — callers that destructure or pattern-match may need updates.
Test plan
- Per-API: regression on type + value.
- TypeScript strict:
noUncheckedIndexedAccess + strictNullChecks should compile.
- Documentation: each API's JSDoc states the chosen shape.
Acceptance criteria
Pre-implementation note
This is the biggest C.2 item — significant cleanup. Worth doing before 1.0, painful after.
Size / Priority
Affected files
X | null,X | undefined, orOption<X>.Background
The codebase mixes three "no value" idioms:
null— explicit "nothing".undefined— absent / not set.Option<X>— custom Maybe-like wrapper.Inconsistent across APIs. Same conceptual "find me X" returns each variant at different call sites. Some examples:
ActorContext.child(name): Option<ActorRef>— uses Option.Cache.get<V>(key): Promise<V | undefined>— uses undefined.Cluster.currentLeader(): Option<Member>— uses Option.LookupResult.value: T | null— uses null.This is a recurring point of confusion in the README + onboarding.
Target — codebase rule
Decide one shape per use case:
Option<X>for cases where "absent" is a frequent + meaningful result that callers should explicitly handle (e.g., resolving a path).X | undefinedfor optional values where the caller will often skip if missing (e.g., cache lookup).null— pick one of the above per case.Document the rule. Migrate inconsistent sites.
Migration plan
This is a multi-PR cleanup:
@deprecatedfor a release.Integration / risk
Optionpolyfill — keep the existingOptiontype; expand documentation.Test plan
noUncheckedIndexedAccess+strictNullChecksshould compile.Acceptance criteria
CONTRIBUTING.mdor equivalent).@deprecated+ migration note.Pre-implementation note
This is the biggest C.2 item — significant cleanup. Worth doing before 1.0, painful after.