Releases: jaredwray/keyv
v6.0.0-beta.3
Keyv v6.0.0-beta.3
This release rolls up the entire v6 beta cycle — everything merged since v6.0.0-alpha.3. v6 is a major, ground-up modernization of Keyv: a leaner core, a new raw-data API, first-class capability detection, telemetry, two new encryption packages, a dependency-free etcd adapter, and a wave of breaking dependency upgrades across the monorepo.
Heads up: v6 contains a number of breaking changes vs. v5. If you are upgrading from v5, read the v5 → v6 migration guide alongside these notes.
Highlights
- 🔐 Two new encryption packages —
@keyv/encrypt-node(Node.jscrypto) and@keyv/encrypt-web(Web Crypto API), with a shared wire format so they're cross-compatible. - 🧩 Raw data API —
getRaw/getManyRaw/setRaw/setManyRawfor working directly with the stored{ value, expires }envelope. - 🔎 Capability detection —
detectKeyv,detectKeyvStorage,detectKeyvCompression,detectKeyvSerialization,detectKeyvEncryptionhelpers. - 📊 Stats / telemetry overhaul — aggregate counters plus LRU-bounded per-key frequency maps.
- 🧼 Key sanitization — opt-in protection against SQL/Mongo/path-traversal/control-character injection in keys and namespaces.
- 🪝 Hookified everywhere — unified events + pre/post hooks across the core and every adapter, now including
setMany,deleteMany,clear, anddisconnect. - 🌐 Dependency-free etcd adapter — talks to etcd v3 over its HTTP/JSON gateway via an in-house client (no
etcd3). - ⚙️ Optional serialization and no key-prefixing in core for a smaller, faster default path.
- 🏗️ Build & toolchain modernization — moved to
tsdown, TypeScript 6, and a refreshed release pipeline (OIDC publishing). - 📦 Major dependency upgrades across Redis, MongoDB, MySQL, Postgres, DynamoDB, msgpackr, hookified, hashery, and more.
⚠️ Breaking Changes
Core
StoredDataandStoredDataRawtypes removed. Use theKeyvValue<T>envelope ({ value, expires? }) and the new raw API instead. (#1929)- Keys are no longer prefixed in core. Namespacing/prefixing is now handled by the storage adapters that need it, not the core. (#1899)
getno longer checks expiry by default. Expiration is evaluated lazily/where appropriate to keep the hot path fast; expired entries still resolve toundefinedthrough the normal read paths. To re-enable core-level expiry checks, set thecheckExpired: trueoption. (#1923)- Moved to Hookified for events + hooks. Replaces the old
EventEmitterbase across core and adapters. (#1900) .set()now returns a boolean instead of the instance. (#1904)- Iterator API simplified and various method signatures cleaned up. (#1902)
setRaw/setManyRawno longer take attlargument — setexpireson the value envelope instead. (#1905)optsremoved fromKeyvStorageAdapterand theoptsproperty removed from theKeyvclass. (#1906)
Adapters & tooling (dependency bumps that change minimums)
@redis/clientupgraded to v6 (breaking). (#1954)- hookified upgraded to v3 (breaking) across the monorepo. (#1957)
- hashery upgraded to v2 in BigMap (breaking). (#1956)
- msgpackr upgraded to v2 in
@keyv/serialize-msgpackr(breaking). (#1958) - bignumber.js upgraded to v11 in the test-suite (breaking). (#1955)
- docula upgraded to v2 for the website (breaking). (#1947)
- Code-quality deps, GitHub Actions, and build tooling upgraded (some breaking minimum versions). (#1944, #1946, #1945)
- TypeScript 6 is now used to build the monorepo. (#1933)
- test-suite v6 overhaul — compliance tests rewritten for the v6 API. If you maintain a third-party adapter against
@keyv/test-suite, expect changes. (#1931)
🔐 New: Encryption Packages
Two new adapters let you encrypt values transparently. They share a wire format, so data written by one can be read by the other (same key + algorithm).
@keyv/encrypt-node — Node.js crypto
Supports AES-GCM (default), AES-CCM, ChaCha20-Poly1305, AES-CBC, and any cipher available in your Node install. (#1927)
import Keyv from 'keyv';
import KeyvEncryptNode from '@keyv/encrypt-node';
const encryption = new KeyvEncryptNode({ key: 'your-secret-key' });
const keyv = new Keyv({ encryption });
await keyv.set('foo', 'bar');
const value = await keyv.get('foo'); // 'bar' (decrypted automatically)// Pick an algorithm and output encoding
const encryption = new KeyvEncryptNode({
key: 'your-secret-key',
algorithm: 'chacha20-poly1305',
encoding: 'hex',
});@keyv/encrypt-web — Web Crypto API
Works in browsers, Deno, Cloudflare Workers, and Node.js 18+ with no Node-specific dependencies. Supports AES-GCM (recommended) and AES-CBC. (#1928)
import Keyv from 'keyv';
import KeyvEncryptWeb from '@keyv/encrypt-web';
const encryption = new KeyvEncryptWeb({ key: 'your-secret-key' });
const keyv = new Keyv({ encryption });
await keyv.set('foo', 'bar');
const value = await keyv.get('foo'); // 'bar'Cross-compatibility wire format (same for both packages):
- AES-GCM:
base64([IV (12 bytes) || AuthTag (16 bytes) || Ciphertext]) - AES-CBC:
base64([IV (16 bytes) || Ciphertext])
🧩 Core: Raw Data API
Work directly with the stored envelope ({ value, expires? }) — useful for replication, cache warming, and moving data between stores. (#1897, #1929)
import Keyv from 'keyv';
const keyv = new Keyv();
// Write a raw envelope with an absolute expiry timestamp
await keyv.setRaw('foo', { value: 'bar', expires: Date.now() + 60_000 });
// No expiry
await keyv.setRaw('foo', { value: 'bar' });
// Read the raw envelope back
const raw = await keyv.getRaw('foo'); // { value: 'bar', expires: 1234567890 }
// Copy between instances without unwrapping/rewrapping
if (raw) {
await other.setRaw('foo', raw);
}
// Batch variants
await keyv.setManyRaw([
{ key: 'a', value: { value: 1 } },
{ key: 'b', value: { value: 2, expires: Date.now() + 60_000 } },
]);
const many = await keyv.getManyRaw(['a', 'b']);The store-level TTL is derived automatically from
value.expires, so you no longer pass a separatettlto the raw setters.
🔎 Core: Capability Detection
New helpers report exactly which parts of an interface an object implements. Each returns a compatible flag — true only when the full interface is satisfied — plus a methods map describing whether each method exists and its methodType ("sync" / "async" / "none"). (#1909, #1930)
import Keyv, {
detectKeyv,
detectKeyvStorage,
detectKeyvCompression,
detectKeyvSerialization,
detectKeyvEncryption,
} from 'keyv';
detectKeyv(new Keyv()).compatible; // true (only when ALL capabilities are present)
detectKeyv(new Map()).compatible; // false — but methods.get.exists is still true
// Storage detection reports the detected store type plus sync/async per method
const r = detectKeyvStorage(new Map());
r.compatible; // true
r.store; // "mapLike" ("keyvStorage" | "mapLike" | "asyncMap" | "none")
r.methods.get.methodType; // "sync"
detectKeyvSerialization(JSON).compatible; // true
detectKeyvCompression({ compress: d => d, decompress: d => d }).compatible; // true
detectKeyvEncryption({ encrypt: d => d, decrypt: d => d }).compatible; // true📊 Core: Stats / Telemetry
Opt-in statistics with aggregate counters and LRU-bounded per-key frequency maps. (#1912)
const keyv = new Keyv({ stats: true });
await keyv.set('foo', 'bar');
await keyv.get('foo'); // hit
await keyv.get('nonexistent'); // miss
await keyv.delete('foo');
keyv.stats.hits; // 1
keyv.stats.misses; // 1
keyv.stats.sets; // 1
keyv.stats.deletes; // 1
// Per-key frequency (each map capped at maxEntries, default 1000)
keyv.stats.hitKeys.get('foo'); // 1
keyv.stats.missKeys.get('nonexistent'); // 1
keyv.stats.reset(); // clears counters and maps
keyv.stats.enabled = false; // disable at runtime (auto-unsubscribes)🧼 Core: Key Sanitization
Opt-in detection that strips dangerous patterns (not harmless characters) from keys and namespaces — guarding against SQL injection, MongoDB operator injection, path traversal, and control-character/CRLF attacks. Results are LRU-cached for speed.
const keyv = new Keyv({ sanitize: true });
// or fine-grained:
const keyv2 = new Keyv({ sanitize: { sql: true, mongo: true, path: true, escape: true } });Applied to every key-accepting method (get, set, delete, has, the *Many variants, and the raw variants), plus namespaces at construction and on the namespace setter.
🪝 Core: Events, Hooks & Error Handling
-
Hooks for more operations — added pre/post hooks for
setMany,deleteMany,clear, anddisconnect, in addition to the existing single-key hooks. (#1918, #1924) -
throwOnErrors— make operations throw instead of emitting'error', so you cantry/catch(great with@keyv/redisconnection handling). (#1910)import Keyv from 'keyv'; import KeyvRedis from '@keyv/redis'; const keyv = new Keyv({ store: new KeyvRedis('redis://localhost:6379'), throwOnErrors: true }); try { await keyv.set('foo', 'bar'); } catch (error) { // handle connection/timeout errors yourself }
-
Key prefixing moved to adapters. Prefixing/namespacing is now handled by the storage adapters that need it rather than the core. (#1899)
-
Encode/decode now propagate errors instead of swallowing ...
v6.0.0-beta.1
What's Changed
- keyv - feat (breaking) stats / telemetry overhaul by @jaredwray in #1912
- keyv - feat: (breaking) memory adapter, bridge adapter, keyv overhaul by @jaredwray in #1913
- bigmap - feat: Optimize BigMap hash function and hot path performance by @jaredwray in #1915
- dynamo - feat: Add disconnect and iterator methods to KeyvDynamo by @jaredwray in #1914
- keyv - fix: handling has and hasMany better by @jaredwray in #1916
- keyv - fix: adding in decode expiring to has by @jaredwray in #1917
- keyv - feat: adding hooks for setMany and deleteMany by @jaredwray in #1918
- keyv - fix: dead code on getManyRaw by @jaredwray in #1919
- keyv - fix: on set with no result do not send telemetry STAT_SET by @jaredwray in #1920
- keyv - fix: telemetry issue on setRaw by @jaredwray in #1921
- keyv - fix: having encode / decode propegate errors by @jaredwray in #1922
- keyv - feat: (breaking) by default keyv no longer checks expires by @jaredwray in #1923
- keyv - feat: adding in hooks for clear and disconnect by @jaredwray in #1924
- keyv - fix: minor bug fixes on memory, ttl, etc by @jaredwray in #1925
- mono - feat: moving to tsdown for build by @jaredwray in #1926
- encryption-node - feat: Add Node.js encryption adapter for Keyv by @jaredwray in #1927
- encrypt-web - feat: adding new web crypto module by @jaredwray in #1928
- keyv - feat: (breaking) removing StoredData and StoredDataRaw types by @jaredwray in #1929
- keyv - feat: enhancing capabilities by @jaredwray in #1930
- test-suite - feat (breaking) overhaul based on v6 changes by @jaredwray in #1931
Full Changelog: v6.0.0-alpha.4...v6.0.0-beta.1
v6.0.0-alpha.4
What's Changed
- keyv - feat: (breaking) moving to Hookified by @jaredwray in #1900
- compression - feat: moving to KeyvCompressionAdapter standard by @jaredwray in #1901
- keyv - feat: (breaking) api changes and iterator simplification by @jaredwray in #1902
- keyv - feat: moving storage setMany to use KeyvEntry by @jaredwray in #1903
- keyv - feat: (breaking) moving to boolean return on set by @jaredwray in #1904
- keyv - fix: (breaking) setRaw and setMany raw do not need ttl param by @jaredwray in #1905
- keyv - feat: (breaking) removing opts from KeyvStorageAdapter by @jaredwray in #1906
- keyv - feat: browser compatability by @jaredwray in #1907
- keyv - fix: updating checks for browser tests by @jaredwray in #1908
- feat: updating capability helper functions by @jaredwray in #1909
- keyv - feat: clean up of code with fixes and helpers by @jaredwray in #1910
Full Changelog: v6.0.0-alpha.3...v6.0.0-alpha.4
v6.0.0-alpha.3
What's Changed
- sqlite - feat: moving to namespace and modern adapter methods by @jaredwray in #1884
- sqlite - feat: adding in benchmarks by @jaredwray in #1885
- sqlite - feat: adding in driver for sqlite3 for legacy by @jaredwray in #1886
- sqlite - feat: major clean up of drivers and code base by @jaredwray in #1887
- memcache - feat: moving to using faker for unit tests by @jaredwray in #1888
- redis - chore: updating tests with faker by @jaredwray in #1889
- dynamo - chore: moving to using faker for tests by @jaredwray in #1890
- etcd - chore: moving to faker for tests by @jaredwray in #1891
- bigmap - feat: moving this to core folder as it Map interface by @jaredwray in #1892
- etcd - feat: moving to v6 functionality by @jaredwray in #1893
- dynamo - feat: moving to v6 requirements many functions and namespace by @jaredwray in #1894
- mono - fix: working to speed up actions by @jaredwray in #1895
- memcache - fix: handling namespace support better by @jaredwray in #1896
- keyv - feat: adding raw set features by @jaredwray in #1897
- keyv - feat: moving to serialization optional by @jaredwray in #1898
- keyv - feat: (breaking) no longer supporting key prefixing in core by @jaredwray in #1899
Full Changelog: v6.0.0-alpha.2...v6.0.0-alpha.3
v6.0.0-alpha.2
What's Changed
- valkey - feat: moving options to properties by @jaredwray in #1876
- valkey - feat: adding in getMany, setMany, deleteMany, etc by @jaredwray in #1877
- valkey - feat: (breaking) moving to client instead of redis name by @jaredwray in #1878
- mono - feat: migrating to hookified v2 by @jaredwray in #1879
- mysql - chore: moving to faker-js for tests by @jaredwray in #1881
- test-suite - feat: moving to using faker-js by @jaredwray in #1882
Full Changelog: 6.0.0-alpha.1...v6.0.0-alpha.2
6.0.0-alpha.1
What's Changed
- redis - feat: exporting RedisSentinelOptions by @jaredwray in #1823
- redis - feat: exporting redis sentinel options with updated documentation by @jaredwray in #1824
- mono - chore: upgrading vitest to 4.0.18 by @jaredwray in #1827
- keyv - chore: upgrading vitest and supporting modules to latest by @jaredwray in #1828
- serialize - chore: upgrading vitest to 4.0.18 and supporting packages by @jaredwray in #1829
- sqlite - feat: Add WAL (Write-Ahead Logging) mode support by @snomiao in #1826
- compress-brotli - chore: upgrading vitest to 4.0.18 by @jaredwray in #1830
- compress-gzip - chore: upgrading vitest to 4.0.18 by @jaredwray in #1831
- sqlite - feat: Add warning for WAL mode with in-memory SQLite databases by @jaredwray in #1832
- docs: Add v5 to v6 migration guide by @jaredwray in #1833
- memcache - docs: Add memjs to memcache migration to v5-to-v6 guide by @jaredwray in #1836
- mono - chore: moving keyv to better mono structure by @jaredwray in #1838
- mono - feat: adding in version sync across all packages by @jaredwray in #1839
- mono - fix: tsconfig moving to esnext by @jaredwray in #1840
- compress-brotli - feat: moving to native zlib brotli engine by @jaredwray in #1841
- mono - refactor: remove ttlSupport property from all storage adapters by @jaredwray in #1842
- keyv - refactor: remove opts property from Keyv class by @jaredwray in #1843
- keyv - chore: remove cyclic workspace dependencies by @jaredwray in #1844
- keyv - chore: adding tests for code coverage by @jaredwray in #1845
- memcache - feat: migrating from memjs to memcache by @jaredwray in #1846
- memcache - feat: adding in createKeyv helper function by @jaredwray in #1847
- memcache - fix: removing serialization as not needed by @jaredwray in #1848
- memcache - feat: (breaking) moving to nodes based on memcache options by @jaredwray in #1849
- memcache - feat: moving eventemitter to hookified by @jaredwray in #1850
- memcache - feat: adding constructor documentation by @jaredwray in #1851
- keyv - docs: Move Bun Support section lower in README by @jaredwray in #1853
- postgres - feat: adding in table of contents by @jaredwray in #1856
- postgres - feat: migrating to hookified and advanced functions by @jaredwray in #1857
- postgres - feat: moving to properties by @jaredwray in #1858
- postgres - feat: adding in native namespace support by @jaredwray in #1860
- postgres - feat: better ttl support by @jaredwray in #1861
- mysql - chore: adding table of contents by @jaredwray in #1862
- mysql - feat: adding setMany and hasMany by @jaredwray in #1863
- mysql - feat: moving to native namespacing by @jaredwray in #1864
- mysql - feat: removing dialect as no longer needed by @jaredwray in #1865
- mysql - feat: (breaking) moving from keySize to keyLength by @jaredwray in #1866
- mysql - feat: moving from eventemitter to hookified by @jaredwray in #1867
- mysql - feat: createKeyv helper and options cleanup by @jaredwray in #1868
- mongo - feat: migrating to hookified from eventemitter by @jaredwray in #1869
- mongo - feat: adding createKeyv helper by @jaredwray in #1871
- mongo - feat: moving to more robust options and properties by @jaredwray in #1872
- mongo - fix: moving gridFS property to read only by @jaredwray in #1873
- mongo - feat: adding hasMany and setMany by @jaredwray in #1874
- mono - feat: adding in release action by @jaredwray in #1875
New Contributors
Full Changelog: 2026-01-20...6.0.0-alpha.1
2026-01-20
What's Changed
- website - feat: centralizing third party storage adapters by @jaredwray in #1789
- keyv - feat: validating bun support by @jaredwray in #1790
- keyv - feat: adding readme on bun support by @jaredwray in #1792
- keyv - feat: adding in jsDoc to KeyvGenericStore by @jaredwray in #1793
- keyv - feat: implementing generic iterator by @jaredwray in #1794
- keyv - chore: moving to nodejs24 tests by @jaredwray in #1796
- keyv - feat: adding in integration tests with generic store by @jaredwray in #1797
- redis - fix: adding in error event from redis by @jaredwray in #1799
- redis - fix: adding in the error from redis client attempt 2 by @jaredwray in #1801
- redis - chore: upgrading redis to 4.0.16 by @jaredwray in #1802
- redis - chore: upgrading @faker-js/faker to 10.2.0 by @jaredwray in #1803
- redis - fix: move client initialization to constructor to prevent connection on module load (#1805) by @jaredwray in #1806
- mono - feat: adding in agents.md by @jaredwray in #1808
- mysql - chore: upgrading vitest to 4.0.17 by @jaredwray in #1809
- mysql - chore: upgrading mysql2 to 3.16.1 by @jaredwray in #1810
- mysql - fix: iterator with cursor by @jaredwray in #1818
- mysql - fix: documentation and error checking fixes by @jaredwray in #1819
- sqlite - chore: upgrading vitest to 4.0.17 by @jaredwray in #1811
- postgres - fix: iteration with more resiliency by @jaredwray in #1784
- postgres - fix: fixing tests typing issue by @jaredwray in #1795
- postgres - chore: upgrading vitest to 4.0.17 by @jaredwray in #1812
- postgres - chore: upgrading pg to 8.17.1 by @jaredwray in #1813
- postgres - fix: moving to faker for unique tests by @jaredwray in #1814
- bigmap - chore: upgrading vitest to 4.0.17 by @jaredwray in #1815
- bigmap - chore: upgrading hashery to 1.4.0 by @jaredwray in #1816
- bigmap - chore: upgrading hookified to 1.15.0 by @jaredwray in #1817
Full Changelog: 2025-12-26...2025-01-20
2025-12-26
What's Changed
- mysql - chore: upgrading @biomejs/biome to 2.3.9 by @jaredwray in #1763
- mysql - chore: upgrading vitest to 4.0.15 by @jaredwray in #1764
- sqlite - chore: upgrading @biomejs/biome to 2.3.9 by @jaredwray in #1765
- sqlite - chore: upgrading vitest to 4.0.15 by @jaredwray in #1766
- postgres - chore: upgrading vitest to 4.0.15 by @jaredwray in #1767
- bigmap - chore: upgrading vitest to 4.0.15 by @jaredwray in #1768
- bigmap - chore: upgrading hashery to 1.3.0 by @jaredwray in #1769
- bigmap - chore: upgrading hookified to 1.14.0 by @jaredwray in #1770
- keyv - fix: prevent duplicate namespace prefixing and add iterator test for delete stats by @jorovipe97 in #1772
- keyv - fix: updating to return when more specific by @jaredwray in #1773
- chore: upgrading vitest to 4.0.16 by @jaredwray in #1774
- chore: upgrading @biomejs/biome to 2.3.1 by @jaredwray in #1775
- etcd - chore: upgrading vitest to 4.0.16 by @jaredwray in #1776
- etcd - chore: upgrading @biomejs/biome to 2.3.10 by @jaredwray in #1777
- dynamo - chore: upgrading vitest to 4.0.16 by @jaredwray in #1778
- dynamo - chore: upgrading @biomejs/biome to 2.3.10 by @jaredwray in #1779
- dynamo - chore: upgrading @aws-sdk to 3.958.0 by @jaredwray in #1780
- mongo - chore: upgrading vitest to 4.0.16 by @jaredwray in #1781
- mongo - chore: upgrading @biomejs/biome to 2.3.10 by @jaredwray in #1782
- mongo - chore: upgrading mongodb to 7.0.0 by @jaredwray in #1783
New Contributors
- @jorovipe97 made their first contribution in #1772
Full Changelog: 2025-12-10...2025-12-26
2025-12-10
What's Changed
- redis - feat: updating readme on why namespaces cause memory bloat by @jaredwray in #1726
- memcache - chore: upgrading @biomejs/biome to 2.3.7 by @jaredwray in #1728
- memcache - chore: upgrading tsup to 8.5.1 by @jaredwray in #1729
- memcache - chore: upgrading vitest to 4.0.14 by @jaredwray in #1730
- etcd - chore: upgrading @biomejs/biome to 2.3.7 by @jaredwray in #1731
- etcd - chore: upgrading vitest to 4.0.14 by @jaredwray in #1732
- dynamo - chore: upgrading vitest to 4.0.14 by @jaredwray in #1733
- dynamo - chore: upgrading aws-sdk to 3.940.0 by @jaredwray in #1734
- mono - feat: moving to minimum release ago to 5 days for updating mod… by @jaredwray in #1736
- serialize - chore: upgrading @biomejs/biome to 2.3.8 by @jaredwray in #1737
- serialize - chore: upgrading vitest to 4.0.14 by @jaredwray in #1738
- keyv - chore: upgrading @biomejs/biome to 2.3.8 by @jaredwray in #1739
- keyv - chore: upgrading vitest to 4.0.14 by @jaredwray in #1740
- compress-brotli - chore: upgrading @biomejs/biome to 2.3.8 by @jaredwray in #1741
- compress-brotli - chore: upgrading vitest to 4.0.14 by @jaredwray in #1742
- compress-gzip - chore: upgrading @biomejs/biome to 2.3.8 by @jaredwray in #1743
- compress-gzip - chore: upgrading vitest to 4.0.14 by @jaredwray in #1744
- compress-lz4 - chore: upgrading @biomejs/biome to 2.3.8 by @jaredwray in #1745
- compress-lz4 - chore: upgrading vitest to 4.0.14 by @jaredwray in #1746
- mono - chore: upgrading tsup to 8.5.1 by @jaredwray in #1747
- mono - chore: upgrading vitest to 4.0.14 by @jaredwray in #1748
- keyv - fix: updating namespace adapter interface to be more permissive by @jaredwray in #1749
- redis - chore: upgrading @biomejs/biome to 2.3.8 by @jaredwray in #1752
- redis - chore: upgrading vitest to 4.0.15 by @jaredwray in #1753
- redis - upgrading hookified to 1.13.0 by @jaredwray in #1754
- redis - chore: upgrading @redis/client to 5.10.0 by @jaredwray in #1755
- valkey - chore: upgrading @biomejs/biome to 2.3.8 by @jaredwray in #1756
- valkey - chore: upgrading vitest to 4.0.15 by @jaredwray in #1757
- test-suite - chore: upgrading vitest to 4.0.15 by @jaredwray in #1758
- test-suite - chore: upgrading @biomejs/biome to 2.3.8 by @jaredwray in #1759
- website - chore: upgrading docula to 0.31.1 by @jaredwray in #1760
Full Changelog: 2025-11-20...2025-12-10
2025-11-20
What's Changed
- postgres - feat: setMany support by @darky in #1723
- mysql - fix: resolving security concern with has method by @jaredwray in #1724
- mysql - feat: adding in jsDocs by @jaredwray in #1725
Full Changelog: 2025-11-17...2025-11-20