Skip to content

Deprecating Subscription-Based Request State Proposed ​

Summary ​

The imperative request-state API — store.getRequestStateService() and the subscription and lookup methods it exposes (subscribeForRecord, getPendingRequestsForRecord, getLastRequestForRecord) — is deprecated in favor of the signals-backed reactive request state introduced by RFC 0001, Transactional Notification Delivery and Reactive Request State. Consumers read request state and autotrack it instead of registering callbacks. Removal from publishing is targeted for 6.0.

This deprecation is deliberately separate from, and sequenced after, RFC 0001. RFC 0001 proposes the replacement; this RFC deprecates the old surface, and its deprecation flag is only enabled once that replacement has shipped and reached the Recommended stage — the point at which the reactive API is the one we steer people toward. Until then this RFC documents the intent and the migration, but nothing is deprecated.

Motivation ​

RFC 0001 rebuilds request state as lazily-created, signals-backed data written inside the same transaction that applies a response's cache changes. Reactive consumers read that state directly and autotrack it; there is no callback to register and no flush to coordinate against. That makes the subscription API redundant, and redundant in a way that is actively worse for correctness:

  • Subscriptions observe a different timeline than the data they describe. The subscription API fires on promise hops, stitched to the notification flush by a one-shot internal callback. RFC 0001 removes that coupling; keeping the subscription API would mean maintaining the exact data/request-state skew RFC 0001 exists to eliminate.
  • Callbacks are strictly more error-prone than autotracking. A subscriber must be unsubscribed to avoid leaks and must reconcile its own view of "is this request still current." Reading signals-backed state has neither hazard: consumers read the current value and the framework invalidates them.
  • One surface, one contract. Leaving two ways to observe request state — one reactive, one callback-based, with different timing — is the kind of accidental multiplicity the reactive layer is trying to retire.

Detailed design ​

What is deprecated ​

The following become deprecated once the flag below is enabled:

  • store.getRequestStateService() and the service instance it returns.
  • That service's subscribeForRecord(identifier, callback).
  • Its imperative lookups getPendingRequestsForRecord(identifier) and getLastRequestForRecord(identifier).

Deprecation flag ​

  • id: warp-drive:deprecate-request-state-subscriptions
  • since: the first 5.x minor in which RFC 0001's reactive request state is Recommended (not merely shipped). This ordering is a hard precondition — see below.
  • until: 6.0
  • Gated by a build-config flag following the existing ENABLE_LEGACY_* pattern in deprecations.ts, so apps can opt into the removed-code build once migrated.

Sequencing constraint ​

The flag must not be enabled before the reactive replacement is Recommended. Enabling a deprecation whose replacement is not yet the recommended path would warn users toward an API that is still stabilizing. Concretely, this RFC may be Accepted independently, but the PR that flips since from a placeholder to a concrete version, and enables the warning, lands only after RFC 0001's reactive request state has reached Recommended. This is the general rule captured in the Writing and Implementing RFCs skill: deprecations follow the features that replace them.

Migration ​

The mapping is mechanical for the common cases:

ts
// Before: imperative subscription
const requests = store.getRequestStateService();
const token = requests.subscribeForRecord(identifier, (state) => {
  if (state.type === 'mutation' && state.state === 'fulfilled') {
    // react to save completion
  }
});
// ...later
// (consumer is responsible for teardown)
ts
// After: read signals-backed request state and autotrack it
import { getRequestState } from '@warp-drive/core/reactive'; // exact import per RFC 0001

const state = getRequestState(identifier);
// read state.status / state.isMutation / state.error in a tracked context;
// the framework invalidates readers when it changes. No token, no teardown.

The precise public read API is being settled in RFC 0001 (an open question there); this RFC's migration guidance will name the final import and shape once RFC 0001 resolves it, before this deprecation's warning is enabled.

Ecosystem implications ​

  • Addon ecosystem: addons that consume getRequestStateService are the affected surface. They migrate to the reactive read API on the same timeline.
  • Ember Inspector / DevTools: any request-state panel that reads via the subscription API moves to the reactive source; behavior for users is unchanged.
  • Lint rules: an eslint-plugin-warp-drive rule flagging getRequestStateService usage, with an autofix to the reactive API where the callback body maps cleanly, is a possible follow-up but is not required by this RFC.
  • Blueprints, Engines, SSR, IDE support: no changes.

How we teach this ​

  • A deprecation guide entry for warp-drive:deprecate-request-state-subscriptions with the before/after above and the teardown-hazard explanation, cross-linked from RFC 0001's teaching guide (which introduces the reactive request state this points at).
  • The API docs for getRequestStateService and its methods gain a deprecation notice pointing at the reactive read API.
  • No guide reorganization: the reactive request-state guide is introduced by RFC 0001; this entry only adds the "migrating off the old subscription API" section to it.

Drawbacks ​

  • Churn for an intimate-but-used API. getRequestStateService is public and has real consumers despite its low profile; the deprecation window and migration carry a maintenance cost until 6.0.
  • Two-RFC coordination. Splitting this out of RFC 0001 (per review feedback) means the deprecation's concrete since cannot be fixed until RFC 0001 is Recommended, so this RFC carries a placeholder version longer than a self-contained deprecation would.

Alternatives ​

  • Keep the subscription API indefinitely as a thin shim over the reactive state. RFC 0001 already describes a compatibility shim (an internal reactive subscriber replaying transitions to old callbacks) to avoid a hard break. This RFC's position is that the shim is a migration aid with an end date, not a permanent second API; keeping it forever preserves the dual-surface problem this deprecation exists to remove.
  • Fold this back into RFC 0001. Rejected: it couples the deprecation timeline to the feature and mixes two independent decisions in one proposal — the specific concern raised in review of emberjs/rfcs#1236.

Unresolved questions ​

  1. The concrete since version, which cannot be fixed until RFC 0001's reactive request state reaches Recommended.
  2. Whether an eslint-plugin-warp-drive autofix rule ships alongside the deprecation or as a later convenience.
  3. The final import path and read-API shape referenced in the migration examples, inherited from RFC 0001's corresponding open question.

Released under the MIT License.