Skip to content

Commit 4c70dec

Browse files
committed
Add types and tslint
1 parent 7b95d31 commit 4c70dec

File tree

9 files changed

+233
-93
lines changed

9 files changed

+233
-93
lines changed

.eslintrc.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,16 @@ module.exports = {
1313
'no-console': ["error", { allow: ['warn'] }]
1414
},
1515
overrides: [
16+
// TypeScript files
17+
{
18+
files: ['addon/**/*.ts'],
19+
parser: '@typescript-eslint/parser',
20+
plugins: ['@typescript-eslint'],
21+
rules: {
22+
'no-undef': 'off',
23+
'no-unused-var': 'off'
24+
}
25+
},
1626
// node files
1727
{
1828
files: [

addon/mixins/adapter-fetch.ts

Lines changed: 92 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,65 @@
11
import Mixin from '@ember/object/mixin';
2-
import { assign } from '@ember/polyfills'
3-
import RSVP from 'rsvp';
2+
import { assign } from '@ember/polyfills';
3+
import RSVP, { reject } from 'rsvp';
44
import fetch from 'fetch';
55
import mungOptionsForFetch from '../utils/mung-options-for-fetch';
66
import determineBodyPromise from '../utils/determine-body-promise';
7+
import DS from 'ember-data';
8+
import Mix from '@ember/polyfills/types';
9+
import { get } from '@ember/object';
10+
import {
11+
PlainObject,
12+
PlainHeaders,
13+
Method,
14+
FetchOptions,
15+
Nullable
16+
} from 'ember-fetch/types';
717

818
/**
919
* Helper function to create a plain object from the response's Headers.
1020
* Consumed by the adapter's `handleResponse`.
11-
* @param {Headers} headers
12-
* @returns {Object}
1321
*/
14-
export function headersToObject(headers) {
15-
let headersObject = {};
22+
export function headersToObject(headers: Headers): PlainObject {
23+
let headersObject: PlainObject = {};
1624

1725
if (headers) {
18-
headers.forEach((value, key) => headersObject[key] = value);
26+
headers.forEach((value, key) => (headersObject[key] = value));
1927
}
2028

2129
return headersObject;
2230
}
2331

2432
export default Mixin.create({
2533
/**
26-
* @param {String} url
27-
* @param {String} type
28-
* @param {Object} _options
29-
* @returns {Object}
34+
* @property {PlainHeaders} headers
35+
* @public
36+
*/
37+
headers: undefined,
38+
39+
/**
3040
* @override
31-
*/
32-
ajaxOptions(url, type, options = {}) {
41+
*/
42+
ajaxOptions(
43+
url: string,
44+
type: Method,
45+
options: JQueryAjaxSettings = {}
46+
): FetchOptions {
3347
options.url = url;
3448
options.type = type;
3549

3650
// Add headers set on the Adapter
37-
let adapterHeaders = this.get('headers');
51+
let adapterHeaders = get(this, 'headers');
3852
if (adapterHeaders) {
39-
options.headers = assign(options.headers || {}, adapterHeaders);
53+
options.headers = assign(
54+
options.headers || {},
55+
adapterHeaders as PlainHeaders
56+
);
4057
}
4158

42-
const mungedOptions = mungOptionsForFetch(options);
59+
const mungedOptions = mungOptionsForFetch(options as Mix<
60+
JQueryAjaxSettings,
61+
{ url: string; type: Method }
62+
>);
4363

4464
// Mimics the default behavior in Ember Data's `ajaxOptions`, namely to set the
4565
// 'Content-Type' header to application/json if it is not a GET request and it has a body.
@@ -60,65 +80,73 @@ export default Mixin.create({
6080
},
6181

6282
/**
63-
* @param {String} url
64-
* @param {String} type
65-
* @param {Object} options
6683
* @override
6784
*/
68-
ajax(url, type, options) {
85+
ajax(url: string, type: Method, options: object) {
6986
const requestData = {
7087
url,
71-
method: type,
88+
method: type
7289
};
7390

7491
const hash = this.ajaxOptions(url, type, options);
7592

76-
return this._ajaxRequest(hash)
77-
.catch((error, response, requestData) => {
78-
throw this.ajaxError(this, response, null, requestData, error);
79-
})
80-
.then((response) => {
81-
return RSVP.hash({
82-
response,
83-
payload: determineBodyPromise(response, requestData)
84-
});
85-
})
86-
.then(({ response, payload }) => {
87-
if (response.ok) {
88-
return this.ajaxSuccess(this, response, payload, requestData);
89-
} else {
90-
throw this.ajaxError(this, response, payload, requestData);
91-
}
92-
});
93+
return (
94+
this._ajaxRequest(hash)
95+
// @ts-ignore
96+
.catch((error, response, requestData) => {
97+
throw this.ajaxError(this, response, null, requestData, error);
98+
})
99+
.then((response: Response) => {
100+
return RSVP.hash({
101+
response,
102+
payload: determineBodyPromise(response, requestData)
103+
});
104+
})
105+
.then(
106+
({
107+
response,
108+
payload
109+
}: {
110+
response: Response;
111+
payload: string | object | undefined;
112+
}) => {
113+
if (response.ok) {
114+
return this.ajaxSuccess(this, response, payload, requestData);
115+
} else {
116+
throw this.ajaxError(this, response, payload, requestData);
117+
}
118+
}
119+
)
120+
);
93121
},
94122

95123
/**
96124
* Overrides the `_ajaxRequest` method to use `fetch` instead of jQuery.ajax
97-
* @param {Object} options
98125
* @override
99126
*/
100-
_ajaxRequest(options) {
127+
_ajaxRequest(
128+
options: Mix<RequestInit, { url: string }>
129+
): RSVP.Promise<Response> {
101130
return this._fetchRequest(options.url, options);
102131
},
103132

104133
/**
105134
* A hook into where `fetch` is called.
106135
* Useful if you want to override this behavior, for example to multiplex requests.
107-
* @param {String} url
108-
* @param {Object} options
109136
*/
110-
_fetchRequest(url, options) {
137+
_fetchRequest(url: string, options: RequestInit): RSVP.Promise<Response> {
111138
return fetch(url, options);
112139
},
113140

114141
/**
115-
* @param {Object} adapter
116-
* @param {Object} response
117-
* @param {Object} payload
118-
* @param {Object} requestData
119142
* @override
120143
*/
121-
ajaxSuccess(adapter, response, payload, requestData) {
144+
ajaxSuccess(
145+
adapter: any,
146+
response: Response,
147+
payload: Nullable<string | object>,
148+
requestData: { url: string; method: string }
149+
): object | DS.AdapterError | RSVP.Promise<never> {
122150
const returnResponse = adapter.handleResponse(
123151
response.status,
124152
headersToObject(response.headers),
@@ -127,36 +155,40 @@ export default Mixin.create({
127155
);
128156

129157
if (returnResponse && returnResponse.isAdapterError) {
130-
return RSVP.Promise.reject(returnResponse);
158+
return reject(returnResponse);
131159
} else {
132160
return returnResponse;
133161
}
134162
},
135163

136-
137164
/**
138165
* Allows for the error to be selected from either the
139166
* response object, or the response data.
140-
* @param {Object} response
141-
* @param {Object} payload
142167
*/
143-
parseFetchResponseForError(response, payload) {
168+
parseFetchResponseForError(
169+
response: Response,
170+
payload: object | string
171+
): object | string {
144172
return payload || response.statusText;
145173
},
146174

147175
/**
148-
* @param {Object} adapter
149-
* @param {Object} response
150-
* @param {String|Object} payload
151-
* @param {Object} requestData
152-
* @param {Error} error
153176
* @override
154177
*/
155-
ajaxError(adapter, response, payload, requestData, error) {
178+
ajaxError(
179+
adapter: any,
180+
response: Response,
181+
payload: Nullable<string | object>,
182+
requestData: object,
183+
error?: Error
184+
): Error | object | DS.AdapterError {
156185
if (error) {
157186
return error;
158187
} else {
159-
const parsedResponse = adapter.parseFetchResponseForError(response, payload);
188+
const parsedResponse = adapter.parseFetchResponseForError(
189+
response,
190+
payload
191+
);
160192
return adapter.handleResponse(
161193
response.status,
162194
headersToObject(response.headers),

addon/types.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import Mix from '@ember/polyfills/types';
2+
3+
export type Nullable<T> = T | null | undefined;
4+
5+
export interface PlainObject {
6+
[key: string]: string | PlainObject | PlainObject[];
7+
}
8+
9+
export interface PlainHeaders {
10+
[key: string]: string | undefined | null;
11+
}
12+
13+
export type Method =
14+
| 'HEAD'
15+
| 'GET'
16+
| 'POST'
17+
| 'PUT'
18+
| 'PATCH'
19+
| 'DELETE'
20+
| 'OPTIONS';
21+
22+
export type FetchOptions = Mix<
23+
JQueryAjaxSettings,
24+
{ body?: BodyInit | null; url: string; method: Method }
25+
>;

addon/utils/determine-body-promise.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,29 @@
22
* Function that always attempts to parse the response as json, and if an error is thrown,
33
* returns `undefined` if the response is successful and has a status code of 204 (No Content),
44
* or 205 (Reset Content) or if the request method was 'HEAD', and the plain payload otherwise.
5-
* @param {Response} response
6-
* @param {Object} requestData
7-
* @returns {Promise}
85
*/
9-
export default function determineBodyPromise(response, requestData) {
6+
export default function determineBodyPromise(
7+
response: Response,
8+
requestData: JQueryAjaxSettings
9+
): Promise<object | string | undefined> {
1010
return response.text().then(function(payload) {
11+
let ret: string | object | undefined = payload;
1112
try {
12-
payload = JSON.parse(payload);
13-
} catch(error) {
13+
ret = JSON.parse(payload);
14+
} catch (error) {
1415
if (!(error instanceof SyntaxError)) {
1516
throw error;
1617
}
1718
const status = response.status;
18-
if (response.ok && (status === 204 || status === 205 || requestData.method === 'HEAD')) {
19-
payload = undefined;
19+
if (
20+
response.ok &&
21+
(status === 204 || status === 205 || requestData.method === 'HEAD')
22+
) {
23+
ret = undefined;
2024
} else {
2125
console.warn('This response was unable to be parsed as json.', payload);
2226
}
2327
}
24-
return payload;
28+
return ret;
2529
});
2630
}

addon/utils/mung-options-for-fetch.ts

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,44 @@
1-
import { assign } from '@ember/polyfills'
1+
import { assign } from '@ember/polyfills';
22
import { serializeQueryParams } from './serialize-query-params';
3+
import Mix from '@ember/polyfills/types';
4+
import { Method, FetchOptions } from 'ember-fetch/types';
35

46
/**
57
* Helper function that translates the options passed to `jQuery.ajax` into a format that `fetch` expects.
6-
* @param {Object} _options
7-
* @returns {Object}
88
*/
9-
export default function mungOptionsForFetch(_options) {
10-
const options = assign({
11-
credentials: 'same-origin',
12-
}, _options);
9+
export default function mungOptionsForFetch(
10+
_options: Mix<JQueryAjaxSettings, {url: string, type: Method}>
11+
): FetchOptions {
12+
const options = assign(
13+
{
14+
credentials: 'same-origin'
15+
},
16+
_options
17+
) as FetchOptions;
1318

1419
// Default to 'GET' in case `type` is not passed in (mimics jQuery.ajax).
15-
options.method = (options.method || options.type || 'GET').toUpperCase();
20+
options.method = (options.method || options.type || 'GET').toUpperCase() as Method;
1621

1722
if (options.data) {
1823
// GET and HEAD requests can't have a `body`
19-
if ((options.method === 'GET' || options.method === 'HEAD')) {
24+
if (options.method === 'GET' || options.method === 'HEAD') {
2025
// If no options are passed, Ember Data sets `data` to an empty object, which we test for.
2126
if (Object.keys(options.data).length) {
2227
// Test if there are already query params in the url (mimics jQuey.ajax).
2328
const queryParamDelimiter = options.url.indexOf('?') > -1 ? '&' : '?';
24-
options.url += `${queryParamDelimiter}${serializeQueryParams(options.data)}`;
29+
options.url += `${queryParamDelimiter}${serializeQueryParams(
30+
options.data
31+
)}`;
2532
}
2633
} else {
2734
// NOTE: a request's body cannot be a POJO, so we stringify it if it is.
2835
// JSON.stringify removes keys with values of `undefined` (mimics jQuery.ajax).
2936
// If the data is not a POJO (it's a String, FormData, etc), we just set it.
3037
// If the data is a string, we assume it's a stringified object.
31-
if (Object.prototype.toString.call(options.data) === "[object Object]") {
38+
if (Object.prototype.toString.call(options.data) === '[object Object]') {
3239
options.body = JSON.stringify(options.data);
3340
} else {
34-
options.body = options.data;
41+
options.body = options.data as BodyInit;
3542
}
3643
}
3744
}

0 commit comments

Comments
 (0)