From cd66d17c3fdbcae0334fe3594ff3251842557699 Mon Sep 17 00:00:00 2001 From: Robert Jackson Date: Wed, 1 May 2019 14:46:38 -0400 Subject: [PATCH] [BUGFIX release] Remove ember-fetch dependency. This change removes ember-fetch as a direct dependency of ember-data, and allows for this dependency to be provided by the host application or for users to directly use native fetch. Having `ember-fetch` as a direct dependency of ember-data has proven troublesome. There are issues using ember-fetch as a nested dependency (these are documented in the ember-fetch README), but it is also quite difficult for ember-data to ensure that the versions of `ember-fetch` provided will actually include the correct version (duplicated addon merging in the ember-cli addon space is complicated and error prone). This change moves the `determineBodyPromise` and `serializeQueryParams` helper functions directly into `@ember-data/adapter`. A future version of ember-fetch will drop these utilities (as well as the ember-data adapter mixin). In addition, this also enables usage of the global `fetch` if a `fetch` module is not provided. --- packages/-ember-data/package.json | 2 +- packages/adapter/addon/-private/index.js | 3 + .../-private/utils/determine-body-promise.ts | 30 ++++++++ .../adapter/addon/-private/utils/fetch.ts | 17 +++++ .../-private/utils/serialize-query-params.ts | 69 +++++++++++++++++++ packages/adapter/addon/rest.js | 15 ++-- yarn.lock | 20 +++++- 7 files changed, 146 insertions(+), 10 deletions(-) create mode 100644 packages/adapter/addon/-private/utils/determine-body-promise.ts create mode 100644 packages/adapter/addon/-private/utils/fetch.ts create mode 100644 packages/adapter/addon/-private/utils/serialize-query-params.ts diff --git a/packages/-ember-data/package.json b/packages/-ember-data/package.json index 52c602320f5..9b292a2b546 100644 --- a/packages/-ember-data/package.json +++ b/packages/-ember-data/package.json @@ -40,7 +40,6 @@ "ember-cli-test-info": "^1.0.0", "ember-cli-typescript": "^2.0.1", "ember-cli-version-checker": "^3.1.2", - "ember-fetch": "^6.5.0", "ember-inflector": "^3.0.0", "inflection": "^1.12.0", "resolve": "^1.8.1", @@ -84,6 +83,7 @@ "ember-decorators-polyfill": "^1.0.4", "ember-disable-prototype-extensions": "*", "ember-export-application-global": "*", + "ember-fetch": "^6.5.0", "ember-load-initializers": "*", "ember-maybe-import-regenerator": "*", "ember-qunit": "*", diff --git a/packages/adapter/addon/-private/index.js b/packages/adapter/addon/-private/index.js index 04d26f85a6a..d38322fe42b 100644 --- a/packages/adapter/addon/-private/index.js +++ b/packages/adapter/addon/-private/index.js @@ -1 +1,4 @@ export { default as parseResponseHeaders } from './utils/parse-response-headers'; +export { determineBodyPromise } from './utils/determine-body-promise'; +export { serializeQueryParams } from './utils/serialize-query-params'; +export { default as fetch } from './utils/fetch'; diff --git a/packages/adapter/addon/-private/utils/determine-body-promise.ts b/packages/adapter/addon/-private/utils/determine-body-promise.ts new file mode 100644 index 00000000000..a1fba5c9fb9 --- /dev/null +++ b/packages/adapter/addon/-private/utils/determine-body-promise.ts @@ -0,0 +1,30 @@ +/** + * Function that always attempts to parse the response as json, and if an error is thrown, + * returns `undefined` if the response is successful and has a status code of 204 (No Content), + * or 205 (Reset Content) or if the request method was 'HEAD', and the plain payload otherwise. + */ +export function determineBodyPromise( + response: Response, + requestData: JQueryAjaxSettings +): Promise { + return response.text().then(function(payload) { + let ret: string | object | undefined = payload; + try { + ret = JSON.parse(payload); + } catch (error) { + if (!(error instanceof SyntaxError)) { + throw error; + } + const status = response.status; + if ( + response.ok && + (status === 204 || status === 205 || requestData.method === 'HEAD') + ) { + ret = undefined; + } else { + console.warn('This response was unable to be parsed as json.', payload); + } + } + return ret; + }); +} diff --git a/packages/adapter/addon/-private/utils/fetch.ts b/packages/adapter/addon/-private/utils/fetch.ts new file mode 100644 index 00000000000..1be5259f483 --- /dev/null +++ b/packages/adapter/addon/-private/utils/fetch.ts @@ -0,0 +1,17 @@ +import require, { has } from 'require'; + +type MaybeFetch = { + (input: RequestInfo, init?: RequestInit | undefined): Promise; +} | null; + +let _fetch: MaybeFetch = null; + +if (has('fetch')) { + // use `fetch` module by default, this is commonly provided by ember-fetch + _fetch = require('fetch').default; +} else if (typeof fetch === 'function') { + // fallback to using global fetch + _fetch = fetch; +} + +export default _fetch; diff --git a/packages/adapter/addon/-private/utils/serialize-query-params.ts b/packages/adapter/addon/-private/utils/serialize-query-params.ts new file mode 100644 index 00000000000..a2c12c76b7a --- /dev/null +++ b/packages/adapter/addon/-private/utils/serialize-query-params.ts @@ -0,0 +1,69 @@ +const RBRACKET = /\[\]$/; + +function isPlainObject(obj: any): boolean { + return Object.prototype.toString.call(obj) === '[object Object]'; +} + +/** + * Helper function that turns the data/body of a request into a query param string. + * This is directly copied from jQuery.param. + */ +export function serializeQueryParams( + queryParamsObject: object | string +): string { + var s: any[] = []; + + function buildParams(prefix: string, obj: any) { + var i, len, key; + + if (prefix) { + if (Array.isArray(obj)) { + for (i = 0, len = obj.length; i < len; i++) { + if (RBRACKET.test(prefix)) { + add(s, prefix, obj[i]); + } else { + buildParams( + prefix + '[' + (typeof obj[i] === 'object' ? i : '') + ']', + obj[i] + ); + } + } + } else if (isPlainObject(obj)) { + for (key in obj) { + buildParams(prefix + '[' + key + ']', obj[key]); + } + } else { + add(s, prefix, obj); + } + } else if (Array.isArray(obj)) { + for (i = 0, len = obj.length; i < len; i++) { + add(s, obj[i].name, obj[i].value); + } + } else { + for (key in obj) { + buildParams(key, obj[key]); + } + } + return s; + } + + return buildParams('', queryParamsObject) + .join('&') + .replace(/%20/g, '+'); +} + +/** + * Part of the `serializeQueryParams` helper function. + */ +function add(s: Array, k: string, v?: string | (() => string)) { + // Strip out keys with undefined value and replace null values with + // empty strings (mimics jQuery.ajax) + if (v === undefined) { + return; + } else if (v === null) { + v = ''; + } + + v = typeof v === 'function' ? v() : v; + s[s.length] = `${encodeURIComponent(k)}=${encodeURIComponent(v)}`; +} diff --git a/packages/adapter/addon/rest.js b/packages/adapter/addon/rest.js index aa2199fb905..ce5fc110e59 100644 --- a/packages/adapter/addon/rest.js +++ b/packages/adapter/addon/rest.js @@ -3,17 +3,18 @@ @module ember-data */ -import fetch from 'fetch'; -import serializeQueryParams from 'ember-fetch/utils/serialize-query-params'; -import determineBodyPromise from 'ember-fetch/utils/determine-body-promise'; - import RSVP, { Promise as EmberPromise } from 'rsvp'; import { get, computed } from '@ember/object'; import { getOwner } from '@ember/application'; import { run } from '@ember/runloop'; import Adapter, { BuildURLMixin } from '@ember-data/adapter'; import { assign } from '@ember/polyfills'; -import { parseResponseHeaders } from '@ember-data/adapter/-private'; +import { + determineBodyPromise, + fetch, + parseResponseHeaders, + serializeQueryParams, +} from './-private'; import { AdapterError, InvalidError, @@ -301,7 +302,9 @@ const RESTAdapter = Adapter.extend(BuildURLMixin, { useFetch: computed(function() { let ENV = getOwner(this).resolveRegistration('config:environment'); - return (ENV && ENV._JQUERY_INTEGRATION) === false || jQ === undefined; + let shouldUseFetch = (ENV && ENV._JQUERY_INTEGRATION) === false || jQ === undefined; + + return shouldUseFetch; }), /** diff --git a/yarn.lock b/yarn.lock index 87e89f95cfc..49932ff7192 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3377,7 +3377,16 @@ browserslist@^3.2.6: caniuse-lite "^1.0.30000844" electron-to-chromium "^1.3.47" -browserslist@^4.0.0, browserslist@^4.5.2, browserslist@^4.5.4: +browserslist@^4.0.0: + version "4.5.6" + resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.5.6.tgz#ea42e8581ca2513fa7f371d4dd66da763938163d" + integrity sha512-o/hPOtbU9oX507lIqon+UvPYqpx3mHc8cV3QemSBTXwkG8gSQSK6UKvXcE/DcleU3+A59XTUHyCvZ5qGy8xVAg== + dependencies: + caniuse-lite "^1.0.30000963" + electron-to-chromium "^1.3.127" + node-releases "^1.1.17" + +browserslist@^4.5.2, browserslist@^4.5.4: version "4.5.5" resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.5.5.tgz#fe1a352330d2490d5735574c149a85bc18ef9b82" integrity sha512-0QFO1r/2c792Ohkit5XI8Cm8pDtZxgNl2H6HU4mHrpYz7314pEYcsAVVatM0l/YmxPnEzh9VygXouj4gkFUTKA== @@ -3591,7 +3600,7 @@ caniuse-api@^3.0.0: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000844, caniuse-lite@^1.0.30000960: +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000844, caniuse-lite@^1.0.30000960, caniuse-lite@^1.0.30000963: version "1.0.30000963" resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000963.tgz#5be481d5292f22aff5ee0db4a6c049b65b5798b1" integrity sha512-n4HUiullc7Lw0LyzpeLa2ffP8KxFBGdxqD/8G3bSL6oB758hZ2UE2CVK+tQN958tJIi0/tfpjAc67aAtoHgnrQ== @@ -4577,6 +4586,11 @@ electron-to-chromium@^1.3.124, electron-to-chromium@^1.3.47: resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.127.tgz#9b34d3d63ee0f3747967205b953b25fe7feb0e10" integrity sha512-1o25iFRf/dbgauTWalEzmD1EmRN3a2CzP/K7UVpYLEBduk96LF0FyUdCcf4Ry2mAWJ1VxyblFjC93q6qlLwA2A== +electron-to-chromium@^1.3.127: + version "1.3.128" + resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.128.tgz#1f9ffa31397da2f220d583dbb2b763e365dfbbc5" + integrity sha512-1QK+KELj1mhC9iE7grSP9PP2f06F85UgTs0M9qjuvSuKwxbyifhyPB8dZ27IlYvzMBnLtO4oHNBKTQoN6Tg5mg== + ember-assign-polyfill@^2.6.0: version "2.6.0" resolved "https://registry.npmjs.org/ember-assign-polyfill/-/ember-assign-polyfill-2.6.0.tgz#07847e3357ee35b33f886a0b5fbec6873f6860eb" @@ -8618,7 +8632,7 @@ node-notifier@^5.0.1: shellwords "^0.1.1" which "^1.3.0" -node-releases@^1.1.14: +node-releases@^1.1.14, node-releases@^1.1.17: version "1.1.17" resolved "https://registry.npmjs.org/node-releases/-/node-releases-1.1.17.tgz#71ea4631f0a97d5cd4f65f7d04ecf9072eac711a" integrity sha512-/SCjetyta1m7YXLgtACZGDYJdCSIBAWorDWkGCGZlydP2Ll7J48l7j/JxNYZ+xsgSPbWfdulVS/aY+GdjUsQ7Q==