Skip to content

Commit 2093cde

Browse files
committed
update rfc text
1 parent 817283f commit 2093cde

File tree

1 file changed

+108
-41
lines changed

1 file changed

+108
-41
lines changed

text/0860-ember-data-request-service.md

Lines changed: 108 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -55,15 +55,16 @@ to handle, modify, or pass-along a request.
5555

5656
```ts
5757
interface RequestManager {
58-
async request<T>(req: RequestInfo): Future<T>;
58+
request<T>(req: RequestInfo): Future<T>;
5959
}
6060
```
6161

6262

6363
For example:
6464

6565
```ts
66-
import RequestManager, { Fetch } from '@ember-data/request';
66+
import { RequestManager } from '@ember-data/request';
67+
import { Fetch } from '@ember/data/request/fetch';
6768
import Auth from 'ember-simple-auth/ember-data-handler';
6869
import Config from './config';
6970

@@ -99,42 +100,100 @@ interface Future<T> extends Promise<StructuredDocument<T>> {
99100
}
100101
```
101102

102-
The `StructuredDocument` interface is the same as is proposed in RFC 854 and copied below:
103+
The `StructuredDocument` interface is the same as is proposed in emberjs/rfcs#854 but is shown here in richer detail.
103104

104105
```ts
105-
interface StructuredDocument<T> {
106-
request: {
107-
url: string;
108-
cache?: { key?: string, reload?: boolean, backgroundReload?: boolean };
109-
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
110-
data?: Record<string, unknown>;
111-
options?: Record<string, unknown>;
112-
headers: Record<string, string>;
113-
}
114-
response: {
115-
status: HTTPStatusCode;
116-
headers: Record<string, string>;
117-
}
106+
interface RequestInfo {
107+
/**
108+
* data that a handler should convert into
109+
* the query (GET) or body (POST)
110+
*/
111+
data?: Record<string, unknown>;
112+
/**
113+
* options specifically intended for handlers
114+
* to utilize to process the request
115+
*/
116+
options?: Record<string, unknown>;
117+
/**
118+
* Allows supplying a custom AbortController for
119+
* the request, if none is supplied one is generated
120+
* for the request. When calling `next` if none is
121+
* provided the primary controller for the request
122+
* is used.
123+
*
124+
* controller will not be passed through onto the immutable
125+
* request on the context supplied to handlers.
126+
*/
127+
controller?: AbortController;
128+
129+
// the below options perfectly mirror the
130+
// native Request interface
131+
cache?: RequestCache;
132+
credentials?: RequestCredentials;
133+
destination?: RequestDestination;
134+
/**
135+
* Once a request has been made it becomes immutable, this
136+
* includes Headers. To modify headers you may copy existing
137+
* headers using `new Headers([...headers.entries()])`.
138+
*
139+
* Immutable headers instances have an additional method `clone`
140+
* to allow this to be done swiftly.
141+
*/
142+
headers?: Headers;
143+
integrity?: string;
144+
keepalive?: boolean;
145+
method?: string;
146+
mode?: RequestMode;
147+
redirect?: RequestRedirect;
148+
referrer?: string;
149+
referrerPolicy?: ReferrerPolicy;
150+
/**
151+
* Typically you should not set this, though you may choose to curry
152+
* a received signal if calling next. signal will automatically be set
153+
* to the associated controller's signal if none is supplied.
154+
*/
155+
signal?: AbortSignal;
156+
url?: string;
157+
}
158+
interface ResponseInfo {
159+
headers: Headers;
160+
ok: boolean;
161+
redirected: boolean;
162+
status: number;
163+
statusText: string;
164+
type: string;
165+
url: string;
166+
}
167+
168+
interface StructuredDataDocument<T> {
169+
request: RequestInfo;
170+
response: ResponseInfo;
118171
data: T;
119-
error?: Error;
120172
}
173+
interface StructuredErrorDocument extends Error {
174+
request: RequestInfo;
175+
response: ResponseInfo;
176+
error: string | object;
177+
}
178+
type StructuredDocument<T> = StructuredDataDocument<T> | StructuredErrorDocument;
121179
```
122180

181+
A `Future` resolves with a StructuredDataDocument or rejects with a StructuredErrorDocument.
182+
123183
**Request Handlers**
124184

125185
Requests are fulfilled by handlers. A handler receives the request context
126186
as well as a `next` function with which to pass along a request to the next
127187
handler if it so chooses.
128188

129-
If a handler calls `next`, it receives a `Future` which resolves to a `StructuredDocument`
189+
If a handler calls `next`, it receives a `Future` which fuulfills to a `StructuredDocument`
130190
that it can then compose how it sees fit with its own response.
131191

132192
```ts
193+
type NextFn = <P>(req: RequestInfo) => Future<P>;
133194

134-
type NextFn<P> = (req: RequestInfo) => Future<P>;
135-
136-
interface Handler<T> {
137-
async request(context: RequestContext, next: NextFn<P>): T;
195+
interface Handler {
196+
request<T>(context: RequestContext, next: NextFn): T;
138197
}
139198
```
140199

@@ -146,17 +205,8 @@ interface Handler<T> {
146205
interface RequestContext<T> {
147206
readonly request: RequestInfo;
148207

149-
setStream(stream: ReadableStream | Promise<ReadableStream>): void;
150-
setResponse(response: Response): void;
151-
}
152-
153-
interface RequestInfo {
154-
url: string;
155-
method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
156-
data?: Record<string, unknown>;
157-
options?: Record<string, unknown>;
158-
headers: Record<string, string>;
159-
signal: AbortSignal;
208+
setStream(stream: ReadableStream | Promise<ReadableStream | null>): void;
209+
setResponse(response: ResponseInfo | Response | null): void;
160210
}
161211
```
162212

@@ -264,10 +314,9 @@ Similarly, if `next` is called only a single time and neither `setStream` nor `g
264314
called, we automatically curry the stream from the future returned by `next` onto the future returned by the handler.
265315

266316
Finally, if the return value of a handler is a `Future`, we curry the entire thing. This makes the
267-
following possible and ensures even `data` is curried when doing so: `return next(<req>)`.
317+
following possible and ensures even `data` and `error` is curried when doing so: `return next(<req>)`.
268318

269-
In the case of the `Future` being returned, `Stream` proxying is automatic and immediate and does
270-
not wait for the `Future` to resolve.
319+
In the case of the `Future` being returned from a handler not using `async/await`, `Stream` proxying is automatic and immediate and does not wait for the `Future` to resolve. If the handler uses `async/await` we have no ability to detect the Future until the handler has fully resolved. This means that if using `async/await` in your handler you should always pro-actively pipe the stream.
271320

272321
**Using as a Service**
273322

@@ -277,7 +326,8 @@ applications by exporting the manager as an Ember service.
277326

278327
*services/request.ts*
279328
```ts
280-
import RequestManager, { Fetch } from '@ember-data/request';
329+
import { RequestManager } from '@ember-data/request';
330+
import { Fetch } from '@ember/data/request/fetch';
281331
import Auth from 'ember-simple-auth/ember-data-handler';
282332

283333
export default class extends RequestManager {
@@ -306,7 +356,8 @@ Alternatively to have a request service unique to the store:
306356

307357
```ts
308358
import Store from '@ember-data/store';
309-
import RequestManager, { Fetch } from '@ember-data/request';
359+
import { RequestManager } from '@ember-data/request';
360+
import { Fetch } from '@ember/data/request/fetch';
310361

311362
export default class extends Store {
312363
requestManager = new RequestManager();
@@ -325,8 +376,8 @@ like the above would need to be done by the consuming application in order to ma
325376

326377
```ts
327378
import Store from '@ember-data/store';
328-
import RequestManager from '@ember-data/request';
329-
import LegacyHandler from '@ember-data/legacy-network-handler';
379+
import { RequestManager } from '@ember-data/request';
380+
import { LegacyHandler } from '@ember-data/legacy-network-handler';
330381

331382
export default class extends Store {
332383
requestManager = new RequestManager();
@@ -344,7 +395,7 @@ The `Store` will add support for using the `RequestManager` via `store.request(<
344395

345396
```ts
346397
class Store {
347-
async request<T>(req: RequestInfo): Future<Reified<T>>;
398+
request<T>(req: RequestInfo): Future<Reified<T>>;
348399
}
349400
```
350401

@@ -388,6 +439,13 @@ interface StoreRequestInfo extends RequestInfo {
388439
}
389440
```
390441

442+
**Background Reload Error Handling**
443+
444+
When an error occurs during a background request we will update the cache with the StructuredErrorDocument but will swallowed the Error at that point.
445+
446+
This prevents consuming applications from being required to catch the error unless
447+
they wish to via a handler.
448+
391449
### RequestStateService
392450

393451
We do not intend to make any adjustments to the RequestStateService at this time, though
@@ -549,6 +607,15 @@ and feature-set that this shift brings will –over the course of the few years
549607
removal– prove to users that the Adapter and Serializer world is no longer the best paradigm
550608
for their applications.
551609

610+
### Typescript Support
611+
612+
Although EmberData has not more broadly shipped support for Typescript, experimental types
613+
will be shipped specifically for the RequestManager package. We can do this because the lack
614+
of entanglement with the other packages affords us the ability to more safely ship this subset
615+
of types while the others are still incomplete.
616+
617+
Types for other packages will eventually be provided but we will not rush them at this time.
618+
552619
## How we teach this
553620

554621
- EmberData should create new documentation and guides to cover using the RequestManager.

0 commit comments

Comments
 (0)