Uh oh!
There was an error while loading. Please reload this page.
feat(client): minimal response-cache substrate (ResponseCacheStore + aggregate-then-write list*()) - #2336
Conversation
🦋 Changeset detectedLatest commit: e987453 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
@modelcontextprotocol/client@modelcontextprotocol/codemod@modelcontextprotocol/server@modelcontextprotocol/server-legacy@modelcontextprotocol/express@modelcontextprotocol/fastify@modelcontextprotocol/hono@modelcontextprotocol/nodecommit: |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
4280d2d to
0e94d53CompareUh oh!
There was an error while loading. Please reload this page.
0e94d53 to
eab632cCompareUh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
eab632c to
d43e3ffCompareUh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
d43e3ff to
28e58b3CompareUh oh!
There was an error while loading. Please reload this page.
…e every page The legacy cacheToolMetadata / _cachedToolOutputValidators path is retired: callTool()'''s output-schema validation now reads the cached tools/list entry via ClientResponseCache.outputValidator (the substrate'''s first production caller), so the response cache is the single source for tool metadata. The name -> validator index is stamp-memoized against the tools/list entry and re-derives only when the backing entry changes; a list_changed eviction invalidates it via the stamp (no separate state to keep in sync). Validator-lifecycle behavior change (every era): compilation is now lazy (first callTool against the cached entry, not eagerly inside listTools) and non-throwing - an uncompilable outputSchema is console.warn-ed and validation is skipped for that tool only; listTools() no longer throws on it.
28e58b3 to
e987453Compare| for (const method of evicted) { | ||
| // `evict()` bumps the generation FIRST and unconditionally | ||
| // (the `_cacheListResult` race guard relies on the bump, not | ||
| // on the store's evict completing), then awaits the store. A | ||
| // custom store's `evict()` may throw or reject; route to | ||
| // `onerror` and proceed so dispatch (and the user's | ||
| // `listChanged` handler) runs regardless. Fire-and-forget — | ||
| // dispatch must not block on an async store. |
There was a problem hiding this comment.
🟡 The new eviction comment in _onnotification says "the _cacheListResult race guard relies on the bump", but no _cacheListResult method exists anywhere in the codebase — the generation race guard actually lives in _listAllPages (captureGeneration) and ClientResponseCache.write(). Update the reference so the comment points at code that exists.
Extended reasoning...
What the issue is. The new comment inside the LIST_CHANGED_EVICTIONS loop in _onnotification (packages/client/src/client/client.ts:1739-1741) reads: "evict() bumps the generation FIRST and unconditionally (the _cacheListResult race guard relies on the bump, not on the store's evict completing)". There is no _cacheListResult method, field, or test anywhere in the repository — a repo-wide grep finds exactly one hit: this comment itself at client.ts:1740.
Where the race guard actually lives. The mechanism the comment is describing is real, it just has a different name in the shipped code. _listAllPages captures the per-method eviction generation before page 1 via this._cache.captureGeneration(method) (client.ts:1428-1432), and ClientResponseCache.write() in responseCache.ts compares the captured generation against the current one and skips the store write when it has moved. So the substance of the comment is correct — evict() bumping the generation first and unconditionally is exactly what the write-skip relies on — but the named collaborator is from an earlier revision of this PR that presumably had a _cacheListResult helper, since superseded by the _listAllPages / ClientResponseCache.write() split.
Step-by-step proof. (1) grep -r _cacheListResult over the repo returns only packages/client/src/client/client.ts:1740 — the comment itself. (2) The diff shows the actual race-guard chain: _listAllPages → captureGeneration(method) → terminal this._cache.write(method, acc, generation) → write() checks (this._evictionGeneration.get(method) ?? 0) !== capturedGen and returns without writing. (3) A maintainer following the comment's reference would search for _cacheListResult and find nothing, then have to reverse-engineer which code the comment means.
Why nothing else covers it. The other doc-accuracy comments on this PR (the responseCacheStore JSDoc / _cache field doc / "output validators" parenthetical threads) target different sites; none of them mentions the _cacheListResult name, so fixing those does not fix this line.
Impact and fix. Zero runtime impact — this is purely a stale internal comment introduced by this PR. One-word fix: replace _cacheListResult with a reference to the actual guard, e.g. "the ClientResponseCache.write() race guard (against the generation captured in _listAllPages) relies on the bump, not on the store's evict completing".
| /** | ||
| * The response-cache store backing the client's derived views (the cached | ||
| * `tools/list` result that {@linkcode Client.callTool | callTool}'s output | ||
| * validation and SEP-2243 header mirroring will read once the stacked | ||
| * SEP-2243 PR lands; this commit ships only the seam). Defaults to a fresh | ||
| * {@linkcode InMemoryResponseCacheStore} per client. | ||
| * | ||
| * **Do not share one store across clients at all in v2.0.x** — entries | ||
| * are keyed by method + params only, so two clients connected to | ||
| * different servers (even under the same credential) collide on | ||
| * `tools/list`, and one client's `list_changed` evicts every co-tenant's | ||
| * entry. Supply your own only as a single-client backing store. | ||
| * Per-principal partitioning that enables safe sharing is #39. | ||
| */ | ||
| responseCacheStore?: ResponseCacheStore; |
There was a problem hiding this comment.
🟡 The ClientOptions.responseCacheStore JSDoc says the cached tools/list result is what callTool's output validation "will read once the stacked SEP-2243 PR lands; this commit ships only the seam", but in this revision callTool() already reads the cache via this._cache.outputValidator(...) (the _cache field doc even calls callTool "the substrate's first production caller"), while the changeset, docs/migration.md, and the listTools() JSDoc still describe SEP-2243 Mcp-Param-* mirroring in present tense even though that code is the not-yet-merged stacked PR. One consistency pass is needed: output validation = present tense everywhere, SEP-2243 mirroring = future tense everywhere.
Extended reasoning...
What the issue is. Two sets of prose in this diff are now inconsistent with the implementation, in opposite directions:
The
ClientOptions.responseCacheStoreJSDoc (packages/client/src/client/client.ts~273-288) says the store backs "the cachedtools/listresult thatcallTool's output validation and SEP-2243 header mirroring will read once the stacked SEP-2243 PR lands; this commit ships only the seam." That wording was accurate for the previous revision (wherecallToolstill used_cachedToolOutputValidators), but the current revision wirescallTool()through the cache: it callsawait this._cache.outputValidator(params.name, tool => this._compileOutputValidator(tool)), and the_cachefield comment in the same file explicitly callscallTool"the substrate's first production caller." The option JSDoc now understates what shipping code does.Conversely, the changeset (".../the derived
tools/listindex thatcallTool's output validation and SEP-2243Mcp-Param-*mirroring read"),docs/migration.md(~line 575: "the source forcallTool's output-schema validation and SEP-2243 header mirroring"), and thelistTools()JSDoc (~line 1928) describe the SEP-2243Mcp-Param-*mirroring in present tense — but a grep ofpackages/client/srcforMcp-Param/x-mcp-headerfinds only comments; the mirroring code is the stacked feat: SEP-2243 custom half — Mcp-Param header codec, client mirroring, server validation #2327, not this PR.
The code path that makes claim (1) wrong. Construct a Client with a custom responseCacheStore pre-seeded with a tools/list entry. (a) Call callTool('some-tool') — callTool reads this._cache.outputValidator(...), which derives the name → validator index from the seeded store entry's stamp and compiles validators via _compileOutputValidator. (b) The seeded store therefore directly controls which output validators callTool compiles and consults — today, in this PR. The new jsonSchemaValidatorOverride.test.ts even pins this lazy compile-on-first-callTool-from-the-cached-entry behavior. Yet a consumer reading the option's IDE-hover JSDoc would conclude that supplying or pre-seeding a store has no effect on callTool validation until the stacked PR merges — the opposite of what the code does — and the option JSDoc contradicts the _cache field comment a few hundred lines below it.
Why claim (2) is wrong in the other direction. The mirroring half genuinely is future work: nothing in this diff parses x-mcp-header declarations or emits Mcp-Param-* headers (only doc comments mention them, and ClientResponseCache.toolDefinition()'s own JSDoc correctly says "No production caller in the substrate commit — the stacked SEP-2243 PR wires callTool()'s Mcp-Param-* mirroring through it"). So the present-tense "...and SEP-2243 Mcp-Param-* mirroring read" phrasing in the changeset, migration guide, and listTools() JSDoc over-promises.
Why this isn't covered by the earlier review thread. The resolved 06-22 inline comment on this same JSDoc was written against the previous revision, where callTool still read the legacy _cachedToolOutputValidators map; it asked for the future-tense rewording that is exactly what is now wrong for the output-validation half, because the author both applied the rewording and rewired callTool through the cache. This finding is the residual inconsistency left after that pass.
Impact. Documentation/changeset accuracy only — no runtime effect — but responseCacheStore is a new public option and these are the surfaces consumers read first (IDE hover, the changelog entry, the migration guide).
How to fix. One consistency pass over the prose: (a) in the responseCacheStore JSDoc, state in present tense that the cached tools/list entry is what callTool's output-schema validation reads, and keep only the SEP-2243 mirroring half as "will read once the stacked SEP-2243 PR lands"; (b) in the changeset, docs/migration.md, and the listTools() JSDoc, split the same way — output validation in present tense, Mcp-Param-* mirroring in future tense ("will be the source for ... once #2327 lands").
Uh oh!
There was an error while loading. Please reload this page.
…st*/readResource The four list verbs and readResource now serve a still-fresh ResponseCacheStore entry without a round trip when the server-stamped ttlMs has not elapsed. Additive on the substrate (#2336): _listAllPages now stamps {expiresAt, scope} on the aggregate write; a _serveFromCache front gates each verb on freshness; readResource is newly cached (URI-keyed; only stored when ttl > 0, since the URI keyspace is unbounded and there is no derived index). Per-call CacheableRequestOptions.cacheMode ('use' | 'refresh' | 'bypass') maps to mcp.d's CacheMode. ClientOptions.cachePartition is the per-principal slot for 'private'-scoped entries (the spec's MUST-NOT-share-across-authz-contexts); 'public' entries always live at partition '' so a shared store serves them to every co-tenant. ClientResponseCache reads probe own-partition then '' (mcp.d's two-probe order — own-first because scope is only known after a fetch); the toolDefinition/outputValidator derived indices use the same probe so SEP-2243 mirroring works under partitioning. readResource applies the same partition derivation as the list verbs and treats absent cacheScope as 'private', so a shared store cannot serve one principal's resource body to another. ClientOptions.defaultCacheTtlMs (default 0) supplies the TTL when the result lacks one (e.g. a legacy-era response); an explicit server-sent ttlMs:0 is honoured as immediately stale. List aggregates are always stored regardless of TTL (mcp.d's retainForSchema posture) so callTool's mirroring/output-validation index keeps working at any TTL while the freshness gate never serves a stale entry. A list_changed eviction beats TTL (the existing partition-agnostic evict). Clock seam (now) injectable on ClientResponseCache for tests. New exports: CacheMode, CacheableRequestOptions.
…st*/readResource The four list verbs and readResource now serve a still-fresh ResponseCacheStore entry without a round trip when the server-stamped ttlMs has not elapsed. Additive on the substrate (#2336): _listAllPages now stamps {expiresAt, scope} on the aggregate write; a _serveFromCache front gates each verb on freshness; readResource is newly cached (URI-keyed; only stored when ttl > 0, since the URI keyspace is unbounded and there is no derived index). Per-call CacheableRequestOptions.cacheMode ('use' | 'refresh' | 'bypass') maps to mcp.d's CacheMode. ClientOptions.cachePartition is the per-principal slot for 'private'-scoped entries (the spec's MUST-NOT-share-across-authz-contexts); 'public' entries always live at partition '' so a shared store serves them to every co-tenant. ClientResponseCache reads probe own-partition then '' (mcp.d's two-probe order — own-first because scope is only known after a fetch); the toolDefinition/outputValidator derived indices use the same probe so SEP-2243 mirroring works under partitioning. readResource applies the same partition derivation as the list verbs and treats absent cacheScope as 'private', so a shared store cannot serve one principal's resource body to another. ClientOptions.defaultCacheTtlMs (default 0) supplies the TTL when the result lacks one (e.g. a legacy-era response); an explicit server-sent ttlMs:0 is honoured as immediately stale. List aggregates are always stored regardless of TTL (mcp.d's retainForSchema posture) so callTool's mirroring/output-validation index keeps working at any TTL while the freshness gate never serves a stale entry. A list_changed eviction beats TTL (the existing partition-agnostic evict). Clock seam (now) injectable on ClientResponseCache for tests. New exports: CacheMode, CacheableRequestOptions.
…st*/readResource The four list verbs and readResource now serve a still-fresh ResponseCacheStore entry without a round trip when the server-stamped ttlMs has not elapsed. Additive on the substrate (#2336): _listAllPages now stamps {expiresAt, scope} on the aggregate write; a _serveFromCache front gates each verb on freshness; readResource is newly cached (URI-keyed; only stored when ttl > 0, since the URI keyspace is unbounded and there is no derived index). Per-call CacheableRequestOptions.cacheMode ('use' | 'refresh' | 'bypass') maps to mcp.d's CacheMode. ClientOptions.cachePartition is the per-principal slot for 'private'-scoped entries (the spec's MUST-NOT-share-across-authz-contexts); 'public' entries always live at partition '' so a shared store serves them to every co-tenant. ClientResponseCache reads probe own-partition then '' (mcp.d's two-probe order — own-first because scope is only known after a fetch); the toolDefinition/outputValidator derived indices use the same probe so SEP-2243 mirroring works under partitioning. readResource applies the same partition derivation as the list verbs and treats absent cacheScope as 'private', so a shared store cannot serve one principal's resource body to another. ClientOptions.defaultCacheTtlMs (default 0) supplies the TTL when the result lacks one (e.g. a legacy-era response); an explicit server-sent ttlMs:0 is honoured as immediately stale. List aggregates are always stored regardless of TTL (mcp.d's retainForSchema posture) so callTool's mirroring/output-validation index keeps working at any TTL while the freshness gate never serves a stale entry. A list_changed eviction beats TTL (the existing partition-agnostic evict). Clock seam (now) injectable on ClientResponseCache for tests. New exports: CacheMode, CacheableRequestOptions.
…st*/readResource The four list verbs and readResource now serve a still-fresh ResponseCacheStore entry without a round trip when the server-stamped ttlMs has not elapsed. Additive on the substrate (#2336): _listAllPages now stamps {expiresAt, scope} on the aggregate write; a _serveFromCache front gates each verb on freshness; readResource is newly cached (URI-keyed; only stored when ttl > 0, since the URI keyspace is unbounded and there is no derived index). Per-call CacheableRequestOptions.cacheMode ('use' | 'refresh' | 'bypass') maps to mcp.d's CacheMode. ClientOptions.cachePartition is the per-principal slot for 'private'-scoped entries (the spec's MUST-NOT-share-across-authz-contexts); 'public' entries always live at partition '' so a shared store serves them to every co-tenant. ClientResponseCache reads probe own-partition then '' (mcp.d's two-probe order — own-first because scope is only known after a fetch); the toolDefinition/outputValidator derived indices use the same probe so SEP-2243 mirroring works under partitioning. readResource applies the same partition derivation as the list verbs and treats absent cacheScope as 'private', so a shared store cannot serve one principal's resource body to another. ClientOptions.defaultCacheTtlMs (default 0) supplies the TTL when the result lacks one (e.g. a legacy-era response); an explicit server-sent ttlMs:0 is honoured as immediately stale. List aggregates are always stored regardless of TTL (mcp.d's retainForSchema posture) so callTool's mirroring/output-validation index keeps working at any TTL while the freshness gate never serves a stale entry. A list_changed eviction beats TTL (the existing partition-agnostic evict). Clock seam (now) injectable on ClientResponseCache for tests. New exports: CacheMode, CacheableRequestOptions.
…st*/readResource The four list verbs and readResource now serve a still-fresh ResponseCacheStore entry without a round trip when the server-stamped ttlMs has not elapsed. Additive on the substrate (#2336): _listAllPages now stamps {expiresAt, scope} on the aggregate write; a _serveFromCache front gates each verb on freshness; readResource is newly cached (URI-keyed; only stored when ttl > 0, since the URI keyspace is unbounded and there is no derived index). Per-call CacheableRequestOptions.cacheMode ('use' | 'refresh' | 'bypass') maps to mcp.d's CacheMode. ClientOptions.cachePartition is the per-principal slot for 'private'-scoped entries (the spec's MUST-NOT-share-across-authz-contexts); 'public' entries always live at partition '' so a shared store serves them to every co-tenant. ClientResponseCache reads probe own-partition then '' (mcp.d's two-probe order — own-first because scope is only known after a fetch); the toolDefinition/outputValidator derived indices use the same probe so SEP-2243 mirroring works under partitioning. readResource applies the same partition derivation as the list verbs and treats absent cacheScope as 'private', so a shared store cannot serve one principal's resource body to another. ClientOptions.defaultCacheTtlMs (default 0) supplies the TTL when the result lacks one (e.g. a legacy-era response); an explicit server-sent ttlMs:0 is honoured as immediately stale. List aggregates are always stored regardless of TTL (mcp.d's retainForSchema posture) so callTool's mirroring/output-validation index keeps working at any TTL while the freshness gate never serves a stale entry. A list_changed eviction beats TTL (the existing partition-agnostic evict). Clock seam (now) injectable on ClientResponseCache for tests. New exports: CacheMode, CacheableRequestOptions.
…st*/readResource The four list verbs and readResource now serve a still-fresh ResponseCacheStore entry without a round trip when the server-stamped ttlMs has not elapsed. Additive on the substrate (#2336): _listAllPages now stamps {expiresAt, scope} on the aggregate write; a _serveFromCache front gates each verb on freshness; readResource is newly cached (URI-keyed; only stored when ttl > 0, since the URI keyspace is unbounded and there is no derived index). Per-call CacheableRequestOptions.cacheMode ('use' | 'refresh' | 'bypass') maps to mcp.d's CacheMode. ClientOptions.cachePartition is the per-principal slot for 'private'-scoped entries (the spec's MUST-NOT-share-across-authz-contexts); 'public' entries always live at partition '' so a shared store serves them to every co-tenant. ClientResponseCache reads probe own-partition then '' (mcp.d's two-probe order — own-first because scope is only known after a fetch); the toolDefinition/outputValidator derived indices use the same probe so SEP-2243 mirroring works under partitioning. readResource applies the same partition derivation as the list verbs and treats absent cacheScope as 'private', so a shared store cannot serve one principal's resource body to another. ClientOptions.defaultCacheTtlMs (default 0) supplies the TTL when the result lacks one (e.g. a legacy-era response); an explicit server-sent ttlMs:0 is honoured as immediately stale. List aggregates are always stored regardless of TTL (mcp.d's retainForSchema posture) so callTool's mirroring/output-validation index keeps working at any TTL while the freshness gate never serves a stale entry. A list_changed eviction beats TTL (the existing partition-agnostic evict). Clock seam (now) injectable on ClientResponseCache for tests. New exports: CacheMode, CacheableRequestOptions.
…st*/readResource The four list verbs and readResource now serve a still-fresh ResponseCacheStore entry without a round trip when the server-stamped ttlMs has not elapsed. Additive on the substrate (#2336): _listAllPages now stamps {expiresAt, scope} on the aggregate write; a _serveFromCache front gates each verb on freshness; readResource is newly cached (URI-keyed; only stored when ttl > 0, since the URI keyspace is unbounded and there is no derived index). Per-call CacheableRequestOptions.cacheMode ('use' | 'refresh' | 'bypass') maps to mcp.d's CacheMode. ClientOptions.cachePartition is the per-principal slot for 'private'-scoped entries (the spec's MUST-NOT-share-across-authz-contexts); 'public' entries always live at partition '' so a shared store serves them to every co-tenant. ClientResponseCache reads probe own-partition then '' (mcp.d's two-probe order — own-first because scope is only known after a fetch); the toolDefinition/outputValidator derived indices use the same probe so SEP-2243 mirroring works under partitioning. readResource applies the same partition derivation as the list verbs and treats absent cacheScope as 'private', so a shared store cannot serve one principal's resource body to another. ClientOptions.defaultCacheTtlMs (default 0) supplies the TTL when the result lacks one (e.g. a legacy-era response); an explicit server-sent ttlMs:0 is honoured as immediately stale. List aggregates are always stored regardless of TTL (mcp.d's retainForSchema posture) so callTool's mirroring/output-validation index keeps working at any TTL while the freshness gate never serves a stale entry. A list_changed eviction beats TTL (the existing partition-agnostic evict). Clock seam (now) injectable on ClientResponseCache for tests. New exports: CacheMode, CacheableRequestOptions.
…st*/readResource The four list verbs and readResource now serve a still-fresh ResponseCacheStore entry without a round trip when the server-stamped ttlMs has not elapsed. Additive on the substrate (#2336): _listAllPages now stamps {expiresAt, scope} on the aggregate write; a _serveFromCache front gates each verb on freshness; readResource is newly cached (URI-keyed; only stored when ttl > 0, since the URI keyspace is unbounded and there is no derived index). Per-call CacheableRequestOptions.cacheMode ('use' | 'refresh' | 'bypass') maps to mcp.d's CacheMode. ClientOptions.cachePartition is the per-principal slot for 'private'-scoped entries (the spec's MUST-NOT-share-across-authz-contexts); 'public' entries always live at partition '' so a shared store serves them to every co-tenant. ClientResponseCache reads probe own-partition then '' (mcp.d's two-probe order — own-first because scope is only known after a fetch); the toolDefinition/outputValidator derived indices use the same probe so SEP-2243 mirroring works under partitioning. readResource applies the same partition derivation as the list verbs and treats absent cacheScope as 'private', so a shared store cannot serve one principal's resource body to another. ClientOptions.defaultCacheTtlMs (default 0) supplies the TTL when the result lacks one (e.g. a legacy-era response); an explicit server-sent ttlMs:0 is honoured as immediately stale. List aggregates are always stored regardless of TTL (mcp.d's retainForSchema posture) so callTool's mirroring/output-validation index keeps working at any TTL while the freshness gate never serves a stale entry. A list_changed eviction beats TTL (the existing partition-agnostic evict). Clock seam (now) injectable on ClientResponseCache for tests. New exports: CacheMode, CacheableRequestOptions.
…aggregate-then-write list*()) (#2336)
Adds a minimal response-cache substrate to
Client— the foundation that SEP-2243 header mirroring (#2327) and the future client-sidecacheHintshonoring both build on.Motivation and Context
SEP-2243's client algorithm requires the client to know each tool's
inputSchema(to findx-mcp-headerdeclarations). #2327's first iteration built a SEP-2243-specific tool-definition cache with its own population/invalidation logic; review rounds repeatedly surfaced lifecycle edges (page-1 wipes, concurrent races, listChanged interactions). This PR moves to the right abstraction: a general response cache thatlistTools()/listPrompts()/listResources()write to, and_toolDefinition(name)reads as a stamp-memoized derived view. Lifecycle is solved once at the cache layer.How Has This Been Tested?
Client suite (550), full e2e matrix (2545p/205xf), conformance
client:all. NewresponseCache.test.tscovers store contract, partition keying,listMaxPagescap-throw, generation-guard, and theObject.prototypemethod-name guard.Breaking Changes
listTools()/listPrompts()/listResources()/listResourceTemplates()called without acursornow aggregate all pages internally and return the complete result. Callers that paginated manually should drop their loop. ExplicitlistTools({ cursor })keeps the per-page contract.ClientOptions.listMaxPages(default 64) now throws instead of returning a truncated result.Types of changes
Checklist
Additional context
Public surface added:
ResponseCacheStore(interface,MaybePromise<…>returns),InMemoryResponseCacheStore(default),CacheKey,CacheEntry,CacheScope,ClientOptions.responseCacheStore. The interface is shaped for the eventualcacheHintswork to be additive:set(key, { value, expiresAt?, scope? })carries the freshness slot now, even though nothing populates it yet.Do not share a store across
Clientinstances in v2.0.x — the store is keyed without a server-identity or principal partition, so cross-instance sharing causes server-identity confusion andclear()/evict()cross-talk. Per-principal partitioning that enables safe sharing is the follow-up work.Lifecycle model:
list_changednotifications evict the matching entry (they do not eagerly refetch). A per-method generation counter prevents an in-flight aggregation from caching a stale result after an eviction has fired._resetConnectionStateclears only the per-instance default store, never a user-supplied one.