v0.12.1
The config honesty release. Every key reference.conf ships is now actually read by something — the sharding, cluster, remote, http, system, worker-cluster and coordinated-shutdown blocks were documented, shipped, and inert; explicit options > HOCON > built-in defaults now holds across all of them. A CI guard fails the build on the next key nothing reads, and a new docs page publishes the complete reference.conf verbatim, pinned to the source by a test. Alongside that, actors can reach two things they previously had to be handed: their own Cluster and their own entityId.
Numbered a patch, but read the breaking change below before upgrading: this window carries one, plus several HOCON key renames.
🚀 New features
- An actor can reach its own
Cluster(#833) —this.cluster(unwrapped, throwing when the system never joined one),this.context.clusterandsystem.cluster(bothOption<Cluster>). TheClusterwas the one runtime object that had to be threaded in by hand, and a framework-constructed actor — a sharded entity, a singleton — has no call site to thread it through at all.cluster.sharding/cluster.singletoncome along, so an actor can start a region or a singleton from the inside. All three read through to the system on every access, so an actor that outlived the join still sees the cluster, and a system that rejoined afterleave()resolves to the new instance rather than the dead one. Registration is a newClusterExtensionthatCluster.joinis the sole writer of; core keeps its runtime independence from the cluster layer. - A sharded entity can read its own
entityId(#832) — the routing id used to stop at theShardthat spawned it, recoverable only by slicing theentity-prefix off the actor path. That was boilerplate at every call site and lossy: actor names have a restricted alphabet, souser:42anduser/42both read back asuser_42(#568).Props.withEntity({ entityId, typeName, shardId })is the same doorClusterShardinguses, left public so an entity can be unit-tested without a cluster around it. actor-ts.sharding.max-entities— the per-node entity cap is configurable (#835) —maxEntitiesLRU-passivates the coldest entity at capacity, and it was the one passivation trigger with no HOCON form, leaving the time bound tunable per environment and the memory bound code-only. An entity count is exactly the value that differs between a laptop and a 64 GB production node. Reference value is0(no cap), so nothing changes for anyone who does not set it.
⚠️ Breaking changes (pre-1.0)
-
ReplicatedEventSourcedActorno longer takes aCluster, andreplicaIdhas a default (#833) — both existed only because the actor could not reach its own cluster.// before class Counter extends ReplicatedEventSourcedActor<Command, Event, State> { readonly persistenceId = 'counter-1'; readonly replicaId: string; constructor(cluster: Cluster) { super(cluster); this.replicaId = cluster.selfAddress.toString(); } } new Counter(cluster); // after class Counter extends ReplicatedEventSourcedActor<Command, Event, State> { readonly persistenceId = 'counter-1'; } new Counter();
Migration: drop the
clusterconstructor argument and thesuper(cluster)it fed — a subclass with no other dependencies can drop its constructor entirely.replicaIddefaults tothis.cluster.selfAddress.toString(), which is what every in-repo subclass set it to by hand. A customreplicaIdbecomes a getter, since as a field it now collides with the base-class accessor (TS2610):override get replicaId(): string { … }. -
HOCON keys renamed. All were inert before this release, so no working configuration changes meaning — but a file that named them was never doing anything:
actor-ts.remote.max-frame-size→remote.max-frame-bytes, and its published default moves1M→16M. Nothing read the key, so every cluster has always run at the 16 MiB code default; publishing16Mstates what the framework does. If you sized your deployment against the documented 1 MiB, setmax-frame-bytes = 1Mexplicitly — it now works.actor-ts.remote.tcp.hostname→remote.tcp.host, matchingClusterOptions.host.actor-ts.worker.*→actor-ts.worker-cluster.*, andcount→workers, in lockstep withWorkerClusterOptions.actor-ts.coordinated-shutdown.exit-jvm→exit-process— a JVM-ism in a TypeScript framework, and it now does something:process.exit(0)once the pipeline completes.
-
Two dead keys removed rather than wired:
cluster.leader-election(the leader is always the lowest-addressed up-member; there is no second strategy) andremote.transport(a custom transport is an object passed towithTransport(…), never a string). -
actor-ts.http.shutdown-grace-period's published default moves5s→0ms—unbind()has always been called with no grace period, so0is what every deployment has actually been running. Making the documented5slive would have cost real time: where a backend'sclose()cannot settle, the window is a deadline always reached, not an upper bound that resolves early. Raise it deliberately if you want in-flight requests to finish. -
Cluster.joinwithouthost/portno longer throws. Validation runs on the merged settings and the reference config supplies both, so it now binds0.0.0.0:2552. That is the point of the feature, but it turns a startup error into a running node — pin the address in config if you were relying on the throw.
🐛 Fixed
- The
actor-ts.sharding.*,cluster.*andremote.*blocks are actually read (part of #653; closes #754) — the keys shipped, the docs explained them, andCluster.jointook every value fromClusterOptionsalone. Env-var substitution (port = ${?ACTOR_TS_PORT}) is applied now too.failureDetectormerges per threshold, not per object, so setting onlydownAfterMsin code keepsheartbeat-intervalandunreachable-afterfrom the file. actor-ts.http.backendandhttp.shutdown-grace-periodare actually read (part of #653) —bind()hardcodednew FastifyBackend().useBackend(…)still wins; the config only decides whatbind()picks when the builder was given nothing. An unrecognised name now fails with aConfigErrornaming the key and the accepted values instead of silently falling back. The reference comment advertisedfastify | bun | express— abunbackend that has never existed, and no mention of the Hono backend that does; corrected tofastify | express | hono.actor-ts.system.name,worker-cluster.*andcoordinated-shutdown.*are actually read (part of #653) —ActorSystem.create()now takes an optional name, falling back toactor-ts.system.namethen"default";create('billing')still wins.coordinated-shutdown.default-phase-timeoutseeds the 12 canonical phases (was hardcoded to5_000), andterminate-actor-system = falsedrops the built-in terminator task while leaving the phase and any user tasks intact. An unknownworker-cluster.restart-policyis now rejected byWorkerClusterOptionsValidatorinstead of falling through the internalmatchand silently meaning "never restart".- A guard against the next dead config key (closes #653) —
tests/unit/config/NoDeadConfigKeys.test.tsasserts, for every leaf inREFERENCE_CONF, that it is reachable fromConfigKeysand referenced from somewhere undersrc/. Knowingly-unimplemented keys go inKNOWN_DEAD_KEYSwith the issue that will remove them — one entry today (remote.tls.enabled, #591) — and the guard checks each excused key still exists, so an exception cannot outlive its key. ShardedDaemonProcessno longer regex-parses its own actor name to find its daemon index, and the chat example's direct-messagepersistenceIdis built from the real|-separated pair id rather than the sanitized one.
📚 Documentation
- A new page publishes the complete
reference.conf— every setting the framework ships, verbatim, so "what can I configure?" has one exhaustive answer instead of a curated example. The Configuration page keeps explaining what each key does and links across. The copy is pinned to the source: a test compares the page's HOCON block toREFERENCE_CONFand fails on any drift, in both languages.