Skip to content

feat: improved manual #9940

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
May 11, 2025
Prev Previous commit
Next Next commit
updates to support newer idioms
  • Loading branch information
runspired committed May 10, 2025
commit 2241c8e8bcbc2da8f343f0674b9f9f3eea761895
27 changes: 14 additions & 13 deletions guides/cookbook/incremental-adoption-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,8 @@ Here is how you do it:
```js
// eslint-disable-next-line ember/use-ember-data-rfc-395-imports
import Store from 'ember-data/store';
import { service } from '@ember/service';

export default class MyStore extends Store {
@service requestManager;
}
export default class AppStore extends Store {}

```

Expand All @@ -30,15 +27,19 @@ Notice we still want to import the `Store` class from `ember-data/store` package

> Note: Because we are extending `ember-data/store`, it is still v1 addon, so things might not work for you if you are using typescript. We recommend to have `store.js` file for now.

## Step 3: Add `RequestManager` service to your application
## Step 3: Add `RequestManager` to your application

Now let's create our very own `RequestManager` service. It is a service that is responsible for sending requests to the server. It is a composable class, which means you can add your own request handlers to it.
Now let's configure a `RequestManager` for our store. The RequestManager is responsible for sending requests to the server. It fulfills requests using a chain-of-responsibility pipeline, which means you can add your own request handlers to it.

First you need to install [`@ember-data/request`](https://github.com/emberjs/data/tree/main/packages/request) and [`@ember-data/legacy-compat`](https://github.com/emberjs/data/tree/main/packages/legacy-compat) packages. First contains the `RequestManager` service and a few request handlers, second has `LegacyNetworkHandler` that gonna handle all old-style `this.store.*` calls.
First you need to install [`@ember-data/request`](https://github.com/emberjs/data/tree/main/packages/request) and [`@ember-data/legacy-compat`](https://github.com/emberjs/data/tree/main/packages/legacy-compat) packages. The first contains the `RequestManager` service and a few request handlers, while the second has `LegacyNetworkHandler` that will handle all old-style `this.store.*` calls.

Here is how your own `RequestManager` service may look like:

```ts
// eslint-disable-next-line ember/use-ember-data-rfc-395-imports
import Store from 'ember-data/store';

import { CacheHandler } from '@ember-data/store';
import { LegacyNetworkHandler } from '@ember-data/legacy-compat';
import type { Handler, NextFn, RequestContext } from '@ember-data/request';
import RequestManager from '@ember-data/request';
Expand All @@ -54,12 +55,12 @@ const TestHandler: Handler = {
},
};

export default class Requests extends RequestManager {
constructor(args?: Record<string | symbol, unknown>) {
super(args);
this.use([LegacyNetworkHandler, TestHandler, Fetch]);
}
export default class AppStore extends Store {
requestManager = new RequestManager()
.use([LegacyNetworkHandler, TestHandler, Fetch])
.useCache(CacheHandler);
}

```

Let's go over the code above:
Expand All @@ -70,7 +71,7 @@ Let's go over the code above:

3. Lastly `Fetch`. It is a handler that sends requests to the server using the `fetch` API. It expects responses to be JSON and when in use it should be the last handler you put in the chain. After finishing each request it will convert the response into json and pass it back to the handlers chain in reverse order as the request context's response. So `TestHandler` will receive `response` property first, and so on if we would have any.

> NOTE: Your `RequestManager` service should be exactly `app/services/request-manager.[js|ts]` file. It is a convention that Ember uses to find the service.
The CacheHandler is a special handler that enables requests to fulfill from and update the cache associated to this store.

You can read more about request manager in the [request manager guide](../requests/index.md).

Expand Down
35 changes: 12 additions & 23 deletions guides/requests/examples/1-auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,20 +49,15 @@ const AuthHandler: Handler = {
}
```

This handler would need to be added to request manager service configuration:
This handler would need to be added to the request manager configuration:

```ts
import RequestManager from '@ember-data/request';
import Fetch from '@ember-data/request/fetch';
import AuthHandler from './auth-handler.js';

export default class extends RequestManager {
constructor(args?: Record<string | symbol, unknown>) {
super(args);

this.use([AuthHandler, Fetch]);
}
}
const manager = new RequestManager()
.use([AuthHandler, Fetch]);
```

This way every request that was made using this request manager will have `Authorization` header added to it.
Expand Down Expand Up @@ -104,17 +99,16 @@ To use this handler we need to register it in our request manager service, but a
```ts
import RequestManager from '@ember-data/request';
import Fetch from '@ember-data/request/fetch';
import { getOwner, setOwner } from '@ember/application';
import { setOwner } from '@ember/owner';
import AuthHandler from './auth-handler';

export default class extends RequestManager {
constructor(args?: Record<string | symbol, unknown>) {
super(args);

export default {
create(owner) {
const authHandler = new AuthHandler();
setOwner(authHandler, getOwner(this));
setOwner(authHandler, owner);

this.use([authHandler, Fetch]);
return new RequestManager()
.use([authHandler, Fetch]);
}
}
```
Expand Down Expand Up @@ -168,20 +162,15 @@ const AuthHandler: Handler = {
}
```

This handler would need to be added to request manager service configuration:
This handler would need to be added to request manager configuration:

```ts
import RequestManager from '@ember-data/request';
import Fetch from '@ember-data/request/fetch';
import AuthHandler from './auth-handler';

export default class extends RequestManager {
constructor(args?: Record<string | symbol, unknown>) {
super(args);

this.use([AuthHandler, Fetch]);
}
}
const manager = new RequestManager()
.use([AuthHandler, Fetch]);
```

This way every request that was made using this request manager will have `X-CSRF-Token` header added to it when needed.
Expand Down
35 changes: 19 additions & 16 deletions guides/requests/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -293,39 +293,42 @@ In the case of the `Future` being returned, `Stream` proxying is automatic and i

---

### Using as a Service
#### Using with `@ember-data/store`

Most applications will desire to have a single `RequestManager` instance, which can be achieved using module-state patterns for singletons, or for [Ember](https://emberjs.com) applications by exporting the manager as a [service](https://guides.emberjs.com/release/services/).
To have a request service unique to a Store:

*services/request.ts*
```ts
import Store, { CacheHandler } from '@ember-data/store';
import RequestManager from '@ember-data/request';
import Fetch from '@ember-data/request/fetch';
import Auth from 'app/services/ember-data-handler';

export default class extends RequestManager {
constructor(args?: Record<string | symbol, unknown>) {
super(args);
this.use([Auth, Fetch]);
}
class extends Store {
requestManager = new RequestManager()
.use([Fetch])
.useCache(CacheHandler);
}
```

---

#### Using with `@ember-data/store`

To have a request service unique to a Store:
### Using as a Service

Some applications will desire to have direct service-level access to the `RequestManager`, which can be achieved using module-state patterns for singletons, or for [Ember](https://emberjs.com) applications by exporting the manager as a [service](https://guides.emberjs.com/release/services/).

*services/request.ts*
```ts
import Store, { CacheHandler } from '@ember-data/store';
import { CacheHandler } from '@ember-data/store';
import RequestManager from '@ember-data/request';
import Fetch from '@ember-data/request/fetch';
import Auth from 'app/services/ember-data-handler';

class extends Store {
requestManager = new RequestManager()
.use([Fetch])
.useCache(CacheHandler);
export default {
create() {
return new RequestManager()
.use([Auth, Fetch])
.useCache(CacheHandler);
}
}
```

Expand Down
34 changes: 18 additions & 16 deletions packages/request/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -411,39 +411,41 @@ In the case of the `Future` being returned, `Stream` proxying is automatic and i

---

### Using as a Service
#### Using with `@ember-data/store`

Most applications will desire to have a single `RequestManager` instance, which can be achieved using module-state patterns for singletons, or for [Ember](https://emberjs.com) applications by exporting the manager as a [service](https://guides.emberjs.com/release/services/).
To have a request service unique to a Store:

*services/request.ts*
```ts
import Store, { CacheHandler } from '@ember-data/store';
import RequestManager from '@ember-data/request';
import Fetch from '@ember-data/request/fetch';
import Auth from 'app/services/auth-handler';

export default class extends RequestManager {
constructor(createArgs) {
super(createArgs);
this.use([Auth, Fetch]);
}
class extends Store {
requestManager = new RequestManager()
.use([Fetch])
.useCache(CacheHandler);
}
```

---

#### Using with `@ember-data/store`
### Using as a Service

To have a request service unique to a Store:
Some applications will desire to have direct service-level access to the `RequestManager`, which can be achieved using module-state patterns for singletons, or for [Ember](https://emberjs.com) applications by exporting the manager as a [service](https://guides.emberjs.com/release/services/).

*services/request.ts*
```ts
import Store, { CacheHandler } from '@ember-data/store';
import { CacheHandler } from '@ember-data/store';
import RequestManager from '@ember-data/request';
import Fetch from '@ember-data/request/fetch';
import Auth from 'app/services/ember-data-handler';

class extends Store {
requestManager = new RequestManager()
.use([Fetch])
.useCache(CacheHandler);
export default {
create() {
return new RequestManager()
.use([Auth, Fetch])
.useCache(CacheHandler);
}
}
```

Expand Down
34 changes: 18 additions & 16 deletions packages/request/src/-private/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,39 +353,41 @@ In the case of the `Future` being returned, `Stream` proxying is automatic and i

---

### Using as a Service
#### Using with `@ember-data/store`

Most applications will desire to have a single `RequestManager` instance, which can be achieved using module-state patterns for singletons, or for [Ember](https://emberjs.com) applications by exporting the manager as a [service](https://guides.emberjs.com/release/services/).
To have a request service unique to a Store:

*services/request.ts*
```ts
import Store, { CacheHandler } from '@ember-data/store';
import RequestManager from '@ember-data/request';
import Fetch from '@ember-data/request/fetch';
import Auth from 'ember-simple-auth/ember-data-handler';

export default class extends RequestManager {
constructor(createArgs) {
super(createArgs);
this.use([Auth, Fetch]);
}
class extends Store {
requestManager = new RequestManager()
.use([Fetch])
.useCache(CacheHandler);
}
```

---

#### Using with `@ember-data/store`
### Using as a Service

To have a request service unique to a Store:
Some applications will desire to have a single `RequestManager` instance, which can be achieved using module-state patterns for singletons, or for [Ember](https://emberjs.com) applications by exporting the manager as a [service](https://guides.emberjs.com/release/services/).

*services/request.ts*
```ts
import Store, { CacheHandler } from '@ember-data/store';
import { CacheHandler } from '@ember-data/store';
import RequestManager from '@ember-data/request';
import Fetch from '@ember-data/request/fetch';
import Auth from 'ember-simple-auth/ember-data-handler';

class extends Store {
requestManager = new RequestManager()
.use([Fetch])
.useCache(CacheHandler);
export default {
create() {
return new RequestManager()
.use([Auth, Fetch])
.use(CacheHandler);
}
}
```

Expand Down
10 changes: 5 additions & 5 deletions packages/store/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,11 +124,11 @@ import RequestManager from '@ember-data/request';
import { CacheHandler } from '@ember-data/store';
import Fetch from '@ember-data/request/fetch';

export default class extends RequestManager {
constructor(createArgs) {
super(createArgs);
this.use([Fetch]);
this.useCache(CacheHandler);
export default {
create() {
return new RequestManager()
.use([Fetch])
.useCache(CacheHandler);
}
}
```
Expand Down
9 changes: 5 additions & 4 deletions packages/store/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,10 +96,11 @@
* import RequestManager from '@ember-data/request';
* import Fetch from '@ember-data/request/fetch';
*
* export default class extends RequestManager {
* constructor(createArgs) {
* super(createArgs);
* this.use([Fetch]);
* export default {
* create() {
* return new RequestManager()
* .use([Fetch])
* .useCache(CacheHandler);
* }
* }
* ```
Expand Down
26 changes: 25 additions & 1 deletion tests/ember-data__request/tests/integration/service-test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { getOwner } from '@ember/application';
import { getOwner, setOwner } from '@ember/application';
import type Owner from '@ember/owner';
import * as s from '@ember/service';
import type { TestContext } from '@ember/test-helpers';

Expand Down Expand Up @@ -33,4 +34,27 @@ module('RequestManager | Ember Service Setup', function (hooks) {
assert.ok(manager.cache instanceof Cache, 'We can utilize injections');
assert.equal(getOwner(manager), this.owner, 'The manager correctly sets owner');
});

test('We can use injections when registering the RequestManager as a service (create)', function (this: TestContext, assert) {
class CustomManager extends RequestManager {
@service cache;
}

const ManagerService = {
create(owner: Owner) {
const manager = new CustomManager();
setOwner(manager, owner);

return manager;
},
};
this.owner.register('service:request', ManagerService);
class Cache extends Service {}
this.owner.register('service:cache', Cache);
const manager = this.owner.lookup('service:request') as unknown as CustomManager;
assert.ok(manager instanceof RequestManager, 'We instantiated');
assert.ok(manager instanceof CustomManager, 'We instantiated');
assert.ok(manager.cache instanceof Cache, 'We can utilize injections');
assert.equal(getOwner(manager), this.owner, 'The manager correctly sets owner');
});
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { setOwner } from '@ember/owner';
import { setOwner } from '@ember/application';
import { service } from '@ember/service';
import type { TestContext } from '@ember/test-helpers';

Expand Down
11 changes: 5 additions & 6 deletions tests/example-json-api/app/services/request-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,8 @@ const TestHandler: Handler = {
},
};

export default class Requests extends RequestManager {
constructor(args?: Record<string | symbol, unknown>) {
super(args);
this.use([LegacyNetworkHandler, TestHandler, Fetch]);
}
}
export default {
create() {
return new RequestManager().use([LegacyNetworkHandler, TestHandler, Fetch]);
},
};
Loading