Skip to content

v0.12.1

Choose a tag to compare

@pathosDev pathosDev released this 03 Aug 00:02
· 1479 commits to main since this release
67a3cec

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.cluster and system.cluster (both Option<Cluster>). The Cluster was 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.singleton come 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 after leave() resolves to the new instance rather than the dead one. Registration is a new ClusterExtension that Cluster.join is 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 the Shard that spawned it, recoverable only by slicing the entity- prefix off the actor path. That was boilerplate at every call site and lossy: actor names have a restricted alphabet, so user:42 and user/42 both read back as user_42 (#568). Props.withEntity({ entityId, typeName, shardId }) is the same door ClusterSharding uses, 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) — maxEntities LRU-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 is 0 (no cap), so nothing changes for anyone who does not set it.

⚠️ Breaking changes (pre-1.0)

  • ReplicatedEventSourcedActor no longer takes a Cluster, and replicaId has 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 cluster constructor argument and the super(cluster) it fed — a subclass with no other dependencies can drop its constructor entirely. replicaId defaults to this.cluster.selfAddress.toString(), which is what every in-repo subclass set it to by hand. A custom replicaId becomes 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-sizeremote.max-frame-bytes, and its published default moves 1M16M. Nothing read the key, so every cluster has always run at the 16 MiB code default; publishing 16M states what the framework does. If you sized your deployment against the documented 1 MiB, set max-frame-bytes = 1M explicitly — it now works.
    • actor-ts.remote.tcp.hostnameremote.tcp.host, matching ClusterOptions.host.
    • actor-ts.worker.*actor-ts.worker-cluster.*, and countworkers, in lockstep with WorkerClusterOptions.
    • actor-ts.coordinated-shutdown.exit-jvmexit-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) and remote.transport (a custom transport is an object passed to withTransport(…), never a string).

  • actor-ts.http.shutdown-grace-period's published default moves 5s0msunbind() has always been called with no grace period, so 0 is what every deployment has actually been running. Making the documented 5s live would have cost real time: where a backend's close() 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.join without host/port no longer throws. Validation runs on the merged settings and the reference config supplies both, so it now binds 0.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.* and remote.* blocks are actually read (part of #653; closes #754) — the keys shipped, the docs explained them, and Cluster.join took every value from ClusterOptions alone. Env-var substitution (port = ${?ACTOR_TS_PORT}) is applied now too. failureDetector merges per threshold, not per object, so setting only downAfterMs in code keeps heartbeat-interval and unreachable-after from the file.
  • actor-ts.http.backend and http.shutdown-grace-period are actually read (part of #653) — bind() hardcoded new FastifyBackend(). useBackend(…) still wins; the config only decides what bind() picks when the builder was given nothing. An unrecognised name now fails with a ConfigError naming the key and the accepted values instead of silently falling back. The reference comment advertised fastify | bun | express — a bun backend that has never existed, and no mention of the Hono backend that does; corrected to fastify | express | hono.
  • actor-ts.system.name, worker-cluster.* and coordinated-shutdown.* are actually read (part of #653) — ActorSystem.create() now takes an optional name, falling back to actor-ts.system.name then "default"; create('billing') still wins. coordinated-shutdown.default-phase-timeout seeds the 12 canonical phases (was hardcoded to 5_000), and terminate-actor-system = false drops the built-in terminator task while leaving the phase and any user tasks intact. An unknown worker-cluster.restart-policy is now rejected by WorkerClusterOptionsValidator instead of falling through the internal match and silently meaning "never restart".
  • A guard against the next dead config key (closes #653) — tests/unit/config/NoDeadConfigKeys.test.ts asserts, for every leaf in REFERENCE_CONF, that it is reachable from ConfigKeys and referenced from somewhere under src/. Knowingly-unimplemented keys go in KNOWN_DEAD_KEYS with 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.
  • ShardedDaemonProcess no longer regex-parses its own actor name to find its daemon index, and the chat example's direct-message persistenceId is 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 to REFERENCE_CONF and fails on any drift, in both languages.