You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Every HOCON leaf this project ships is kebab-case — except three blocks. actor-ts.http.client, actor-ts.http.websocket and actor-ts.cache.in-memory (with its per-instance sibling actor-ts.cache.<name>.in-memory) publish camelCase leaves that mirror their TypeScript options fields verbatim, and src/config/Reference.ts:431, :443 and :467 say so in as many words: "Leaf names match the HttpClientOptions fields (camelCase)".
That was a defensible local choice and it does not survive contact with the rest of the file. An operator reading reference.conf top to bottom meets gossip-interval, hand-off-timeout, max-frame-bytes, shutdown-grace-period — and then maxResponseBytes, acceptTimeoutMs, cleanupMs, with actor-ts.remote.max-frame-bytes and actor-ts.http.websocket.maxFrameBytes sitting forty lines apart spelling the same concept two ways. The convention is not discoverable from the file, so the file teaches both.
It is also about to get worse rather than settle. #876 proposes time-to-live / time-to-idle / initial-capacityinside actor-ts.cache.in-memory, and #870 proposes keep-alive-mode / receive-idle-timeoutinside actor-ts.http.websocket. Both land kebab leaves directly beside camelCase ones, in the same block, in this wave. Converting is cheaper now than after two more issues have doubled the mixed surface.
Pre-1.0, AGENTS.md permits the hard cut, so this converts rather than grandfathers.
Proposed keys
actor-ts.http.client {
max-response-bytes = 8M# was maxResponseBytesdefault-timeout = 30s# was defaultTimeoutMsredirect = "follow"# unchangedmax-redirects = 5# was maxRedirects
}
actor-ts.http.websocket {
max-frame-bytes = 1M# was maxFrameByteson-oversize-frame = "close"on-invalid-message = "close"max-buffered-bytes = 4Mon-backpressure = "drop"# max-connections (comment-only; was maxConnections)max-pre-attach-frames = 256max-pre-attach-bytes = 4Maccept-timeout = 10s# was acceptTimeoutMs
}
actor-ts.cache.in-memory {
max-entries = 10000# was maxEntriescleanup-interval = 60s# was cleanupMs = 60000# prefix-quotas { "rsp:" = 7000 } (comment-only; was prefixQuotas)
}
# and the same three leaves under actor-ts.cache.<name>.in-memory
Thirteen renames. redirect is already a single lowercase word and does not move.
The Ms suffix goes. The kebab side of the tree already decided this and the readers show it: src/cluster/ClusterOptions.ts:650 reads leaf gossip-interval into field gossipIntervalMs, :658 reads tombstone.time-to-live into tombstoneTtlMs, and ShardCoordinatorOptions reads hand-off-timeout into handOffTimeoutMs. The unit lives on the TypeScript field, never on the leaf, because HOCON carries its own units. default-timeout and accept-timeout already ship duration values (30s, 10s), so those are pure key renames.
cleanupMs = 60000 is the one leaf that is also a value decision. It becomes cleanup-interval = 60s. parseDuration accepts a bare number as milliseconds (src/config/Duration.ts:57), so cleanup-interval = 60000 keeps working for anyone who prefers it and 0 still disables the sweep — but the published default stops needing the trailing # …, ms comment to be legible. cleanup-interval rather than a mechanical cleanup because the field is a sweep interval and the file already names those *-interval.
This bends AGENTS.md's withX ⇔ x ⇔ leaf x lockstep, which the Ms suffix already bends in both directions today. The rule the tree actually follows, and which this issue writes down, is: the HOCON leaf is the kebab-case of the field with any unit suffix dropped. The builder methods and field names do not change — withCleanupMs(...), maxResponseBytes, acceptTimeoutMs all stay exactly as they are. Nothing in user code migrates.
Old spellings are rejected, not ignored. There is no unknown-key detection anywhere in src/config/, and mergeOptions treats an absent leaf as "not set" (src/util/OptionsMerge.ts:15-18), so without a rejection list a stale maxResponseBytes silently reverts to the built-in default. Six of the thirteen leaves are security caps an operator lowers on purpose — docs/…/http/security.mdx:189 tells them to lower maxFrameBytes for semi-trusted networks — and a rename that quietly restores the framework default on those is a security regression delivered as housekeeping. Each reader gets a retired-key list and throws a ConfigError naming both spellings, the way actor-ts.http.backend already does for an unknown backend (tests/unit/http/HttpConfigDefaults.test.ts:69-76).
Acceptance
All 13 leaves are kebab-case in src/config/Reference.ts, and the three comments at :431-432, :443 and :467 that currently document the camelCase convention state the new rule instead.
The two comment-only keys convert with the rest: maxConnections → max-connections (Reference.ts:452, :458) and prefixQuotas → prefix-quotas (Reference.ts:481-491), including the worked example. Both are read (WebsocketPolicy.ts:149, CacheExtension.ts:87) and neither is a parsed leaf, so no existing guard covers them.
The per-instance path actor-ts.cache.<name>.in-memory.* renames with the global block — one edit in inMemoryCacheLeaves() (src/cache/CacheExtension.ts:78) covers both, and a test drives the per-name path specifically.
Setting any retired spelling fails ActorSystem construction (or first cache(name) / first websocket() route resolve) with a ConfigError whose message contains both the old and the new key. One test per block.
cleanup-interval accepts 60s and a bare 60000 identically, and 0 still disables the sweep. The DocumentedDefaults entry moves from kind: 'int' to kind: 'duration' (tests/unit/docs/DocumentedDefaults.test.ts:271) — getInt rejects "60s" at src/config/Config.ts:143-148, so leaving the kind alone fails as a wrong value, which reads like a wrong default.
ConfigKeys names each renamed leaf as a full path and the three readers address leaves through those constants, so tests/unit/config/NoDeadConfigKeys.test.ts checks the leaf rather than the block root. Today it checks the root (coveringAccessor, :95-104, falls back to "a config root above it"), which is why renaming Reference.ts alone and leaving the readers on the old spelling passes the guard.
A new tests/unit/docs/HoconFenceKeys.test.ts fails on any camelCase key inside any hocon fence under docs/src/content/docs/, both languages, with an allow-list seeded only by the out-of-scope actor-ts.io.* blocks and naming the issue that empties it. Without this the docs half of the conversion is ungated: scripts/check-doc-samples.mjs:734 compiles only ts/typescript fences, and nothing reads configuration.mdx at all.
Both reference-conf.mdx pages are byte-identical to REFERENCE_CONF and both configuration.mdx key tables carry the new names (13 rows EN + 13 DE). The eight content pages with HOCON samples are converted in both languages: cache/in-memory, http/overview, http/websocket, http/security, http/middleware/{idempotency-key,rate-limit,response-cache}.
The TypeScript half of those pages is untouched: cache/in-memory.mdx:35-36 is the field/builder table and stays cleanupMs / withCleanupMs, and configuration.mdx:342-343 lists per-call request options, not leaves.
CHANGELOG.md carries a BREAKING entry with the full old → new table, the note that field and builder names are unchanged, and the statement that old keys throw rather than being ignored.
bun run typecheck, bun run typecheck:dev and bun test green; bun run test:coverage:gate unchanged.
Depends on ordering with #876 and #870, both of which add kebab leaves to the two blocks this converts — landing this first means neither has to file a mixed-casing block. The ~55 remaining camelCase leaves under actor-ts.io.broker.* (51 hasPath sites across 15 files under src/io/, plus 4 in src/http/websocket/WebsocketClientActor.ts:196-199) are deliberately not in scope here; they ship no reference.conf leaves and want their own issue.
Part of the reference.conf expansion batch — tracked in #887.
Use case
Every HOCON leaf this project ships is kebab-case — except three blocks.
actor-ts.http.client,actor-ts.http.websocketandactor-ts.cache.in-memory(with its per-instance siblingactor-ts.cache.<name>.in-memory) publish camelCase leaves that mirror their TypeScript options fields verbatim, andsrc/config/Reference.ts:431,:443and:467say so in as many words: "Leaf names match the HttpClientOptions fields (camelCase)".That was a defensible local choice and it does not survive contact with the rest of the file. An operator reading
reference.conftop to bottom meetsgossip-interval,hand-off-timeout,max-frame-bytes,shutdown-grace-period— and thenmaxResponseBytes,acceptTimeoutMs,cleanupMs, withactor-ts.remote.max-frame-bytesandactor-ts.http.websocket.maxFrameBytessitting forty lines apart spelling the same concept two ways. The convention is not discoverable from the file, so the file teaches both.It is also about to get worse rather than settle. #876 proposes
time-to-live/time-to-idle/initial-capacityinsideactor-ts.cache.in-memory, and #870 proposeskeep-alive-mode/receive-idle-timeoutinsideactor-ts.http.websocket. Both land kebab leaves directly beside camelCase ones, in the same block, in this wave. Converting is cheaper now than after two more issues have doubled the mixed surface.Pre-1.0, AGENTS.md permits the hard cut, so this converts rather than grandfathers.
Proposed keys
Thirteen renames.
redirectis already a single lowercase word and does not move.The
Mssuffix goes. The kebab side of the tree already decided this and the readers show it:src/cluster/ClusterOptions.ts:650reads leafgossip-intervalinto fieldgossipIntervalMs,:658readstombstone.time-to-liveintotombstoneTtlMs, andShardCoordinatorOptionsreadshand-off-timeoutintohandOffTimeoutMs. The unit lives on the TypeScript field, never on the leaf, because HOCON carries its own units.default-timeoutandaccept-timeoutalready ship duration values (30s,10s), so those are pure key renames.cleanupMs = 60000is the one leaf that is also a value decision. It becomescleanup-interval = 60s.parseDurationaccepts a bare number as milliseconds (src/config/Duration.ts:57), socleanup-interval = 60000keeps working for anyone who prefers it and0still disables the sweep — but the published default stops needing the trailing# …, mscomment to be legible.cleanup-intervalrather than a mechanicalcleanupbecause the field is a sweep interval and the file already names those*-interval.This bends AGENTS.md's
withX⇔x⇔ leafxlockstep, which theMssuffix already bends in both directions today. The rule the tree actually follows, and which this issue writes down, is: the HOCON leaf is the kebab-case of the field with any unit suffix dropped. The builder methods and field names do not change —withCleanupMs(...),maxResponseBytes,acceptTimeoutMsall stay exactly as they are. Nothing in user code migrates.Old spellings are rejected, not ignored. There is no unknown-key detection anywhere in
src/config/, andmergeOptionstreats an absent leaf as "not set" (src/util/OptionsMerge.ts:15-18), so without a rejection list a stalemaxResponseBytessilently reverts to the built-in default. Six of the thirteen leaves are security caps an operator lowers on purpose —docs/…/http/security.mdx:189tells them to lowermaxFrameBytesfor semi-trusted networks — and a rename that quietly restores the framework default on those is a security regression delivered as housekeeping. Each reader gets a retired-key list and throws aConfigErrornaming both spellings, the wayactor-ts.http.backendalready does for an unknown backend (tests/unit/http/HttpConfigDefaults.test.ts:69-76).Acceptance
src/config/Reference.ts, and the three comments at:431-432,:443and:467that currently document the camelCase convention state the new rule instead.maxConnections→max-connections(Reference.ts:452,:458) andprefixQuotas→prefix-quotas(Reference.ts:481-491), including the worked example. Both are read (WebsocketPolicy.ts:149,CacheExtension.ts:87) and neither is a parsed leaf, so no existing guard covers them.actor-ts.cache.<name>.in-memory.*renames with the global block — one edit ininMemoryCacheLeaves()(src/cache/CacheExtension.ts:78) covers both, and a test drives the per-name path specifically.ActorSystemconstruction (or firstcache(name)/ firstwebsocket()route resolve) with aConfigErrorwhose message contains both the old and the new key. One test per block.cleanup-intervalaccepts60sand a bare60000identically, and0still disables the sweep. TheDocumentedDefaultsentry moves fromkind: 'int'tokind: 'duration'(tests/unit/docs/DocumentedDefaults.test.ts:271) —getIntrejects"60s"atsrc/config/Config.ts:143-148, so leaving the kind alone fails as a wrong value, which reads like a wrong default.ConfigKeysnames each renamed leaf as a full path and the three readers address leaves through those constants, sotests/unit/config/NoDeadConfigKeys.test.tschecks the leaf rather than the block root. Today it checks the root (coveringAccessor,:95-104, falls back to "a config root above it"), which is why renamingReference.tsalone and leaving the readers on the old spelling passes the guard.tests/unit/docs/HoconFenceKeys.test.tsfails on any camelCase key inside anyhoconfence underdocs/src/content/docs/, both languages, with an allow-list seeded only by the out-of-scopeactor-ts.io.*blocks and naming the issue that empties it. Without this the docs half of the conversion is ungated:scripts/check-doc-samples.mjs:734compiles onlyts/typescriptfences, and nothing readsconfiguration.mdxat all.reference-conf.mdxpages are byte-identical toREFERENCE_CONFand bothconfiguration.mdxkey tables carry the new names (13 rows EN + 13 DE). The eight content pages with HOCON samples are converted in both languages:cache/in-memory,http/overview,http/websocket,http/security,http/middleware/{idempotency-key,rate-limit,response-cache}.cache/in-memory.mdx:35-36is the field/builder table and stayscleanupMs/withCleanupMs, andconfiguration.mdx:342-343lists per-call request options, not leaves.CHANGELOG.mdcarries aBREAKINGentry with the full old → new table, the note that field and builder names are unchanged, and the statement that old keys throw rather than being ignored.bun run typecheck,bun run typecheck:devandbun testgreen;bun run test:coverage:gateunchanged.Depends on ordering with #876 and #870, both of which add kebab leaves to the two blocks this converts — landing this first means neither has to file a mixed-casing block. The ~55 remaining camelCase leaves under
actor-ts.io.broker.*(51hasPathsites across 15 files undersrc/io/, plus 4 insrc/http/websocket/WebsocketClientActor.ts:196-199) are deliberately not in scope here; they ship noreference.confleaves and want their own issue.Part of the reference.conf expansion batch — tracked in #887.