WarpDrive 5.9
sincev5.9.1 2026-09-05WarpDrive 5.9 is out, and this post is for apps already on 5.x deciding whether and how to upgrade. The headline changes are experimental reactive pagination, a codemod that turns EmberData models into schemas, and a new package of WarpDrive knowledge for AI coding agents. Before upgrading, read Changes That May Need Attention: most apps won't hit any of them, but the two most likely to are that published types are now rolled up per entry point and that @warp-drive/holodeck now replays its fixtures in CI.
Install 5.9.1, not 5.9.0
The 5.9.0 release did not publish completely. Every package went out at 5.9.0 except @warp-drive/experiments, whose publish failed, so there is no @warp-drive/experiments that goes with 5.9.0. Version 5.9.1, released the same day, fixes the release tooling and publishes @warp-drive/experiments at 5.9.1 alongside everything else (#11023). It contains no other changes.
Install 5.9.1 for every WarpDrive package you use. If you already installed 5.9.0, move to 5.9.1; there is nothing else to do.
Starting with 5.9.1, @warp-drive/experiments is versioned with the rest of the 5.x packages instead of on its own 0.x line, so its version always matches theirs.
Changes That May Need Attention
Each of these can surface after upgrading, and each says what to do about it.
Types are rolled up per entry point
The @warp-drive/* packages now build with rolldown (via tsdown) instead of Vite (#10643). Imports and module format are unchanged, but declarations now live in dist/ next to the JavaScript and are rolled up into one file per public entry point (@warp-drive/core goes from 130 .d.ts files to 60). If you imported a type from an internal module path that isn't a package entry point, it no longer resolves: import it from its public entry point instead. As a bonus, editor import suggestions now only offer public paths, and packages ship sourcemaps.
Holodeck replays in CI
@warp-drive/holodeck was meant to record locally and replay from its .mock-cache fixtures when CI is set, but it always recorded, so a test whose fixture was never committed still passed in CI. It now replays as intended (#10698). If tests start failing in CI after upgrading, run them locally and commit the fixtures they write; set IS_RECORDING to record even when CI is set. Replay also now finds mocks whose URL includes a query string, such as GET(this, 'users?name=Chris', ...).
withArrayDefaults field types
withArrayDefaults from @warp-drive/legacy/model-fragments takes an optional second argument for the item type: withArrayDefaults('titles', 'string') produces a field of type array:string, matching array('string') in ember-data-model-fragments (#10421). Without it the field type is now array; it used to be derived from the singularized field name (array:title). If you relied on that, pass the item type explicitly.
No more ember barrel import
ember-data and @warp-drive/legacy no longer import the ember barrel module, removing a blocker for Ember 7 (#10513). As a result, ember-data no longer registers itself in Ember.libraries, and cacheFor on records using the EmberObjectExtension or EmberObjectArrayExtension from @warp-drive/legacy/compat/extensions now throws, since Ember removed it with no replacement.
Generators no longer write classic or pods output
The ember generate blueprints for models, adapters, serializers and transforms (and their unit tests) no longer generate classic Model.extend() syntax or pods layouts; they always write native classes at the default paths (#10866). See warp-drive generate below.
New Features
Reactive pagination (experimental)
getPaginationState(request) from @warp-drive/experiments/pagination returns a reactive pagination state for a request, built on the pagination links in WarpDrive response documents (#10014). It comes in two flavors: paged (activePage, totalPages, loadPage(url)) and infinite (data, hasNext, loadNext(), loadPrev()). Loaded pages are cached per collection, so every component paginating the same collection shares them while keeping its own navigation state.
import { getPaginationState } from '@warp-drive/experiments/pagination';
const request = store.request({ url: '/users', method: 'GET' });
const pages = getPaginationState(request);
await request;
await pages.loadNext();Ember apps also get <Paginate /> and <EachLink /> from @warp-drive/ember/experiments. <Paginate /> mirrors <Request />'s blocks and picks a flavor with @mode="paged" (the default) or @mode="infinite". This feature lives in @warp-drive/experiments, which is one more reason to install 5.9.1.
Migrate models to schemas with a codemod
@ember-data/codemods adds migrate-to-schema, which turns EmberData models and mixins into WarpDrive schemas: a LegacyResourceSchema built with withDefaults, a TypeScript type, an extension for computed properties and methods, and traits for mixins and intermediate base classes (#10466). It handles JavaScript and TypeScript models, leaves the original files in place, writes to app/data/ by default, and logs each file it skips and why. A JSON config covers custom transform types, base classes, monorepo sources, and where your app imports WarpDrive APIs from (warpDriveImports).
npx @ember-data/codemods apply migrate-to-schema --project-name my-appThe CLI now ships as a portable Node bundle that runs on macOS, Linux and Windows via npx, pnpm dlx or bunx. See Using Codemods.
Memory Alpha: WarpDrive knowledge for AI agents
@warp-drive/memory-alpha is a new package of WarpDrive knowledge for AI coding agents such as Claude Code, Codex, Copilot, Cursor and Gemini (#10665). It has no code: it ships a skills/ directory of small, task-focused markdown files, starting with defining a resource schema and fetching and caching data through the Store, plus a skills/index.md routing table that sends an agent straight to the one file matching its task.
To use it, install the package, copy the instruction file for your agent from the package README (CLAUDE.md, AGENTS.md, GEMINI.md, Copilot instructions or a Cursor rule), and point it at node_modules/@warp-drive/memory-alpha/skills/index.md, or serve the skills/ directory from an MCP server. The same skills are readable by humans in the Skills section of the docs site.
Stateful request handlers
The handlers option of useRecommendedStore (@warp-drive/core) and useLegacyStore (@warp-drive/legacy) also accepts a function that receives the store and returns the handler list (#10551). It runs once per store, the first time store.requestManager is read, so handlers can depend on the store or its owner, such as an Ember service.
export default useRecommendedStore({
cache: JSONAPICache,
handlers: (store) => {
const authHandler = new AuthHandler();
setOwner(authHandler, getOwner(store)!);
return [authHandler];
},
});Relationships
- LinksMode without related links.
linksModerelationships no longer require alinks.relatedlink when the payload is "fully linked": abelongsTowithdata: nullor a related resource inincluded, or ahasManywithdata: []or every member inincluded. A relationship with nodatakey still needs a related link. In PolarisMode, a synclinksModebelongsTocan now be set on an editable (checked-out) record, and resources built withwithDefaultsget a$keyfield that returns the record'sResourceKey. See LinksMode (#10524). - Abstract polymorphic types need no schema. For resources defined with schemas, when a concrete type's relationship declares
as: 'abstract-pet', registering that type's schema also builds the schema forabstract-pet; before, resolving the relationship threw. A real schema you register for the abstract type is merged with the fields its implementers add, and development builds now catch conflicting field shapes and point misconfiguration errors at the side that needs fixing. See Polymorphism (#10547).
Easier migration from Model
@attr, @belongsTo and @hasMany in @warp-drive/legacy/model accept a sourceKey option for when the API's field name differs from the property name: @attr('string', { sourceKey: 'first-name' }) firstName reads and writes first-name in the cache (#10549). The same release fixes several rough edges for apps that mix Models with schema-only resources while they migrate: JSONSerializer's shouldSerializeHasMany and the EmbeddedRecordsMixin work for resources with no Model class, store.modelFor() no longer asserts on stores created with useLegacyStore({ linksMode: true }), and looking up fields for a type with no registered model throws No model was found for '<type>' instead of recursing forever.
warp-drive generate
The warp-drive CLI adds warp-drive generate <type> <name> (aliases g and gen), which writes a model, adapter, serializer or transform, or a unit test for one, without going through ember-cli (#10866). The ember generate blueprints keep working and now produce the same output as the CLI.
npx warp-drive generate model taco filling:belongs-to:protein toppings:has-many:topping name:stringPayload validation in the cache log
When LOG_CACHE is on, the payload report JSONAPICache logs in development also checks the compound-document rules of the {json:api} spec (#10433). It flags a resource that appears more than once in the same payload, a relationship whose data points at a resource the payload doesn't include, and an included resource that no relationship reaches from the primary data. Duplicates are a common slip in hand-written mocks, and the report names every place the resource appears.
Linting
eslint-plugin-warp-drive gains its first template rule, template-always-use-request-content, which flags a <Request> whose result is never used, which usually means the data is being read some other way and bypasses the boundary <Request> sets up (#10613). A new eslint-plugin-warp-drive/recommended-templates config wires up ember-eslint-parser for .gjs/.gts files and enables it. The no-legacy-imports autofix now preserves type-only imports, and reports legacy imports it can't map safely instead of skipping them. See Linting.
Fetch improvements
The Fetch handler in @warp-drive/core now supports HEAD requests, resolving them with null content, and requests can pass the native priority fetch hint ('high', 'low' or 'auto') without failing development-mode validation (#10463).
In development builds, the fetch handler guesses whether Mirage (or another Pretender-based mock) is serving requests, and the guess can be wrong either way. Call globalThis.setWarpDriveIsMaybeMirage(true) or (false) to override it, including outside tests for Mirage in ember serve. A wrong guess also no longer throws when the response's headers are immutable (#10543).
Performance
When a push changes several fields on a record, JSONAPICache now hands the store's notification manager all of them in one call instead of one call per field, which saves work when you push large payloads. Subscribers see no difference: they are still called once per changed field (#10614, #10560). To support this, store.notifications.notify() accepts an array or Set of keys, as does a cache's notifyChange() for 'attributes'. @warp-drive/core also exports a new NotificationChannel type ('local' | 'remote') that custom Cache implementations can pass to notify(), subscribe() and notifyChange() to report local edits separately from remote state; leave it out to keep the old behavior.
Notable Fixes
- WarpDrive no longer crashes in environments without full browser globals, like React Native. Without
window.addEventListeneranddocument, the online/visibility listeners that drive<Request />auto-refresh are skipped, and abort errors fall back to a plainErrorwith the samename(#10612). - A sync
linksModehasMany on an immutable record no longer goes stale after repeated remote updates (#10560). - Reading a
hasManyafter a record on its inverse side was unloaded no longer throws "Expected localState to be present";splice(start)with no delete count now removes everything fromstartonward, likeArray.prototype.splice; and development builds now throw when two relationships on a type declare the same explicitinverse, instead of silently corrupting it (#10533). JSONAPISerializerskips resources of unknown types in a payload's primarydataarray instead of crashing, andRESTAdapterreturns anInvalidErrorinstead of throwing when a 422 response has anullbody (#10649).
Documentation
The Manual is reorganized into topic sections, and several pages that were placeholders in 5.8 are now written (#10519):
- Reactivity explains how WarpDrive uses signals as "gates" next to the cache rather than as storage, with new pages on Reactive Control Flow and Async as Reactive State.
- Debugging covers turning on instrumented logging at runtime or build time and what each log flag shows.
- The schema guides gain full pages on Derivations, Transformations, Traits and Complex Fields.
- The TypeScript guides' examples now import from
@warp-drive/coreand@warp-drive/legacyinstead of the pre-5.x@ember-data/*packages.
The API reference also fills in a large number of previously undocumented public types and members across @warp-drive/core, @warp-drive/legacy, @warp-drive/utilities, @warp-drive/ember and @warp-drive/react, with union members and related types now linked to each other (#10569).
Upgrading
Install 5.9.1, then check the Changes That May Need Attention above. If you're moving from Models to schemas, start with Using Codemods.
Thanks
Thank you to everyone who contributed to 5.9: Chris Thoburn (@runspired), Vaibhav Srivastava (@vaibhav8a), Sergey Astapov (@SergeAstapov), Bartlomiej Dudzik (@BobrImperator), Sam Van Campenhout (@Windvis), Krystan HuffMenne (@gitKrystan), Mehul Kiran Chaudhari (@MehulKChaudhari), Chris Manson (@mansona), Marine Dunstetter (@BlueCutOfficial), Markus Sanin (@mkszepp), Kirill Shaplyko (@Baltazore), Liam (@evoactivity), David Baker (@acorncom), Michal Bryxí (@MichalBryxi), Alex Raputa (@alexraputa), Leo Euclides (@leoeuclids), Thomas Gossmann (@gossi), PJ Carly (@pjcarly), @NullVoxPopuli-ai-agent, Rich Glazerman (@richgt) and @BoussonKarel.