diff --git a/.ember-cli b/.ember-cli
index ee64cfe..465c405 100644
--- a/.ember-cli
+++ b/.ember-cli
@@ -1,9 +1,7 @@
{
/**
- Ember CLI sends analytics information by default. The data is completely
- anonymous, but there are times when you might want to disable this behavior.
-
- Setting `disableAnalytics` to true will prevent any data from being sent.
+ Setting `isTypeScriptProject` to true will force the blueprint generators to generate TypeScript
+ rather than JavaScript by default, when a TypeScript version of a given blueprint is available.
*/
- "disableAnalytics": false
+ "isTypeScriptProject": false
}
diff --git a/.eslintignore b/.eslintignore
index 6437276..4c74aec 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -1,6 +1,5 @@
# unconventional js
/blueprints/*/files/
-/vendor/
# compiled output
/dist/
@@ -16,3 +15,9 @@
/.node_modules.ember-try/
/bower.json.ember-try
/package.json.ember-try
+
+# misc
+/coverage/
+!.*
+.*/
+
diff --git a/.eslintrc.js b/.eslintrc.js
index 4c662f0..688756d 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -1,51 +1,56 @@
+'use strict';
+const path = require('path');
+
module.exports = {
root: true,
+ parser: '@babel/eslint-parser',
parserOptions: {
- ecmaVersion: 2017,
- sourceType: 'module'
+ ecmaVersion: 'latest',
+ sourceType: 'module',
+ requireConfigFile: false,
+ babelOptions: { configFile: path.join(__dirname, 'babel.config.json') },
},
- plugins: [
- 'ember'
- ],
- extends: [
- 'eslint:recommended',
- 'plugin:ember/recommended'
- ],
+ plugins: ['ember'],
+ extends: ['eslint:recommended', 'plugin:ember/recommended'],
env: {
- browser: true
+ browser: true,
},
rules: {
+ 'ember/no-computed-properties-in-native-classes': 'warn',
},
overrides: [
// node files
{
files: [
- '.template-lintrc.js',
- 'ember-cli-build.js',
- 'index.js',
- 'testem.js',
- 'blueprints/*/index.js',
- 'config/**/*.js',
- 'tests/dummy/config/**/*.js'
- ],
- excludedFiles: [
- 'addon/**',
- 'addon-test-support/**',
- 'app/**',
- 'tests/dummy/app/**'
+ './.eslintrc.js',
+ './.prettierrc.js',
+ './.stylelintrc.js',
+ './.template-lintrc.js',
+ './ember-cli-build.js',
+ './index.js',
+ './testem.js',
+ './blueprints/*/index.js',
+ './config/**/*.js',
+ './tests/dummy/config/**/*.js',
],
parserOptions: {
sourceType: 'script',
- ecmaVersion: 2015
+ ecmaVersion: 2015,
},
env: {
browser: false,
- node: true
+ node: true,
},
- plugins: ['node'],
- rules: Object.assign({}, require('eslint-plugin-node').configs.recommended.rules, {
- // add your custom rules and overrides for node files here
- })
- }
- ]
+ extends: ['plugin:n/recommended'],
+ },
+ {
+ // test files
+ files: ['tests/**/*-test.{js,ts}'],
+ extends: ['plugin:qunit/recommended'],
+
+ rules: {
+ 'qunit/require-expect': 0,
+ },
+ },
+ ],
};
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..57a6bf2
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,78 @@
+name: CI
+
+on:
+ push:
+ branches:
+ - main
+ - master
+ pull_request: {}
+
+concurrency:
+ group: ci-${{ github.head_ref || github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ test:
+ name: "Tests"
+ runs-on: ubuntu-latest
+ timeout-minutes: 10
+
+ steps:
+ - uses: actions/checkout@v3
+ - name: Install Node
+ uses: actions/setup-node@v3
+ with:
+ node-version: 18
+ cache: npm
+ - name: Install Dependencies
+ run: npm ci
+ - name: Lint
+ run: npm run lint
+ - name: Run Tests
+ run: npm run test:ember
+
+ floating:
+ name: "Floating Dependencies"
+ runs-on: ubuntu-latest
+ timeout-minutes: 10
+
+ steps:
+ - uses: actions/checkout@v3
+ - uses: actions/setup-node@v3
+ with:
+ node-version: 18
+ cache: npm
+ - name: Install Dependencies
+ run: npm install --no-shrinkwrap
+ - name: Run Tests
+ run: npm run test:ember
+
+ try-scenarios:
+ name: ${{ matrix.try-scenario }}
+ runs-on: ubuntu-latest
+ needs: "test"
+ timeout-minutes: 10
+
+ strategy:
+ fail-fast: false
+ matrix:
+ try-scenario:
+ - ember-lts-4.8
+ - ember-lts-4.12
+ - ember-release
+ - ember-beta
+ - ember-canary
+ - embroider-safe
+ - embroider-optimized
+
+ steps:
+ - uses: actions/checkout@v3
+ - name: Install Node
+ uses: actions/setup-node@v3
+ with:
+ node-version: 18
+ cache: npm
+ - name: Install Dependencies
+ run: npm ci
+ - name: Run Tests
+ run: ./node_modules/.bin/ember try:one ${{ matrix.try-scenario }}
diff --git a/.gitignore b/.gitignore
index 29c9bc6..07c1ada 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,18 +1,17 @@
-# See https://help.github.com/ignore-files/ for more about ignoring files.
-
# compiled output
/dist/
-/tmp/
+/declarations/
# dependencies
-/bower_components/
/node_modules/
# misc
/.sass-cache
/connect.lock
+/.env*
+/.pnp*
+/.eslintcache
/coverage/
-/libpeerconnection.log
/npm-debug.log*
/testem.log
/yarn-error.log
@@ -20,4 +19,8 @@
# ember-try
/.node_modules.ember-try/
/bower.json.ember-try
+/npm-shrinkwrap.json.ember-try
/package.json.ember-try
+
+# broccoli-debug
+/DEBUG/
diff --git a/.npmignore b/.npmignore
index 2f20afe..216c739 100644
--- a/.npmignore
+++ b/.npmignore
@@ -2,20 +2,22 @@
/dist/
/tmp/
-# dependencies
-/bower_components/
-
# misc
-/.bowerrc
/.editorconfig
/.ember-cli
/.eslintignore
/.eslintrc.js
/.gitignore
+/.prettierignore
+/.prettierrc.js
+/.stylelintignore
+/.stylelintrc.js
+/.template-lintrc.js
+/.travis.yml
/.watchmanconfig
/.travis.yml
/bower.json
-/config/ember-try.js
+/CONTRIBUTING.md
/ember-cli-build.js
/testem.js
/tests/
@@ -25,4 +27,5 @@
# ember-try
/.node_modules.ember-try/
/bower.json.ember-try
+/npm-shrinkwrap.json.ember-try
/package.json.ember-try
diff --git a/.prettierrc.js b/.prettierrc.js
new file mode 100644
index 0000000..e5f7b6d
--- /dev/null
+++ b/.prettierrc.js
@@ -0,0 +1,12 @@
+'use strict';
+
+module.exports = {
+ overrides: [
+ {
+ files: '*.{js,ts}',
+ options: {
+ singleQuote: true,
+ },
+ },
+ ],
+};
diff --git a/.stylelintignore b/.stylelintignore
new file mode 100644
index 0000000..a0cf71c
--- /dev/null
+++ b/.stylelintignore
@@ -0,0 +1,8 @@
+# unconventional files
+/blueprints/*/files/
+
+# compiled output
+/dist/
+
+# addons
+/.node_modules.ember-try/
diff --git a/.stylelintrc.js b/.stylelintrc.js
new file mode 100644
index 0000000..021c539
--- /dev/null
+++ b/.stylelintrc.js
@@ -0,0 +1,5 @@
+'use strict';
+
+module.exports = {
+ extends: ['stylelint-config-standard', 'stylelint-prettier/recommended'],
+};
diff --git a/.travis.yml b/.travis.yml
index f904298..7d1d1af 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -40,10 +40,11 @@ jobs:
- stage: "Additional Tests"
env: EMBER_TRY_SCENARIO=ember-lts-2.16
- env: EMBER_TRY_SCENARIO=ember-lts-2.18
+ - env: EMBER_TRY_SCENARIO=ember-lts-3.24
+ - env: EMBER_TRY_SCENARIO=ember-lts-4.0
- env: EMBER_TRY_SCENARIO=ember-release
- env: EMBER_TRY_SCENARIO=ember-beta
- env: EMBER_TRY_SCENARIO=ember-canary
- - env: EMBER_TRY_SCENARIO=ember-default-with-jquery
before_install:
- npm config set spin false
diff --git a/.watchmanconfig b/.watchmanconfig
index e7834e3..f9c3d8f 100644
--- a/.watchmanconfig
+++ b/.watchmanconfig
@@ -1,3 +1,3 @@
{
- "ignore_dirs": ["tmp", "dist"]
+ "ignore_dirs": ["dist"]
}
diff --git a/MODULE_REPORT.md b/MODULE_REPORT.md
deleted file mode 100644
index f1d70e5..0000000
--- a/MODULE_REPORT.md
+++ /dev/null
@@ -1,14 +0,0 @@
-## Module Report
-### Unknown Global
-
-**Global**: `Ember.Logger`
-
-**Location**: `addon/mixins/with-logger.js` at line 13
-
-```js
-
-LEVELS.forEach(function (level) {
- methods[level] = bind(Ember.Logger, levelMap[level] || level, '[ed-sails]');
-});
-
-```
diff --git a/README.md b/README.md
index 793ccdd..8219dc5 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,12 @@
-<<<<<<< HEAD
-# ember-data-sails
+# @voll/ember-data-sails (ember-data-sails fork)
+
+
+## The goal of this fork is to enable usage with Ember 4.
+
+
+
+
+## The following text is the same as the original repo https://github.com/huafu/ember-data-sails:
Adapters and tools for Ember to work well with Sails. Provides `SailsSocketService`, `SailsRESTAdapter`, `SailsSocketAdapter`, `SailsSerializer` and extends the store so that you can subscribe for records while pushing a payload.
@@ -14,9 +21,9 @@ Adapters and tools for Ember to work well with Sails. Provides `SailsSocketServi
// do something with the response
});
```
-
+
It'll use by default the `sails.io.js` located at `:1337/js/dependencies/sails.io.js`, but you can change this using configuration in `config/environment.js` file:
-
+
```js
ENV.APP = {
// if you want some useful debug information related to sails
@@ -29,22 +36,28 @@ Adapters and tools for Ember to work well with Sails. Provides `SailsSocketServi
}
}
```
-
+
Also don't forget to add the rules for CSP:
-
+
```js
// allow to fetch the script
ENV.contentSecurityPolicy['script-src'] += ' http://localhost:1337';
// allow the websocket to connect
ENV.contentSecurityPolicy['connect-src'] += ' http://localhost:1337 ws://localhost:1337';
```
-
-
-* `DS.SailsSocketAdapter`: use this adapter when you want to use sockets for your model(s)
-* `DS.SailsRESTAdapter`: use this adapter when you want to use sockets for your model(s)
-* `DS.SailsSerializer`: used by default when you use a Sails adapter, you shouldn't need to access it but it's there in case
-* `DS.Store.pushPayload([type], payload, [subscribe=false])`: as the original one from Ember Data, except it accepts an additional parameter which, when set to `true`, will tell the socket adapter to subscribe to the pushed records (see below)
-* `DS.Store.subscribe(type, ids)`: tells the sails socket adapter to subscribe to those models (see below)
+
+
+* `SailsSocketAdapter`: use this adapter when you want to use sockets for your model(s)
+* `SailsRESTAdapter`: use this adapter when you want to use sockets for your model(s)
+* `SailsSerializer`: used by default when you use a Sails adapter, you shouldn't need to access it but it's there in case
+* `Store.pushPayload([type], payload, [subscribe=false])`: as the original one from Ember Data, except it accepts an additional parameter which, when set to `true`, will tell the socket adapter to subscribe to the pushed records (see below)
+* `Store.subscribe(type, ids)`: tells the sails socket adapter to subscribe to those models (see below)
+
+## Compatibility
+
+* Ember.js v4.8 or above
+* Ember CLI v4.8 or above
+* Node.js v18 or above
## Installation
@@ -68,7 +81,7 @@ Adapters and tools for Ember to work well with Sails. Provides `SailsSocketServi
//at the very bottom if you want to server other routes through Sails, because they are matched in order
- '/*': { controller: 'App', action: 'serve', skipAssets: true, skipRegex: /^\/api\/.*$/ }
+ '/*': { controller: 'App', action: 'serve', skipAssets: true, skipRegex: /^\/(api\/.*|__getcookie|csrfToken)$/ }
-
+
//You could also just serve the index view directly if you want
//'/*': { view: 'index', skipAssets: true, skipRegex: /^\/api\/.*$/ }
```
@@ -77,7 +90,7 @@ Adapters and tools for Ember to work well with Sails. Provides `SailsSocketServi
```js
// file: app/adapters/application.js
import SailsSocketAdapter from 'ember-data-sails/adapters/sails-socket';
-
+
export default SailsSocketAdapter.extend({
/**
* Whether to use CSRF tokens or not
@@ -102,7 +115,7 @@ Adapters and tools for Ember to work well with Sails. Provides `SailsSocketServi
```js
// file: app/adapters/application.js
import SailsRESTAdapter from 'ember-data-sails/adapters/sails-rest';
-
+
export default SailsRESTAdapter.extend({
/**
* The host of your API
@@ -134,7 +147,7 @@ properties of the adapter to do a request on the API.
* `subscribeMethod`: `get`, `post`, ... defaults to `post`
* `subscribeEndpoint`: the endpoint to do the request on, defaults to `/socket/subscribe`
* Of course you'll need to create a basic controller in your Sails API. Here is an example:
-
+
```js
// api/controllers/SocketController.js
module.exports = {
@@ -174,56 +187,3 @@ properties of the adapter to do a request on the API.
_While this was first inspired from [ember-data-sails-adapter](https://github.com/bmac/ember-data-sails-adapter), it has now been fully re-written, with a totally different approach, and, as of the day this was written, with more features._
*
[Huafu Gandon](http://huafu.github.com) - [@huafu_g](https://twitter.com/huafu_g)
-=======
-my-addon
-==============================================================================
-
-[Short description of the addon.]
-
-Installation
-------------------------------------------------------------------------------
-
-```
-ember install my-addon
-```
-
-
-Usage
-------------------------------------------------------------------------------
-
-[Longer description of how to use the addon in apps.]
-
-
-Contributing
-------------------------------------------------------------------------------
-
-### Installation
-
-* `git clone `
-* `cd my-addon`
-* `npm install`
-
-### Linting
-
-* `npm run lint:hbs`
-* `npm run lint:js`
-* `npm run lint:js -- --fix`
-
-### Running tests
-
-* `ember test` – Runs the test suite on the current Ember version
-* `ember test --server` – Runs the test suite in "watch mode"
-* `ember try:each` – Runs the test suite against multiple Ember versions
-
-### Running the dummy application
-
-* `ember serve`
-* Visit the dummy application at [http://localhost:4200](http://localhost:4200).
-
-For more information on using ember-cli, visit [https://ember-cli.com/](https://ember-cli.com/).
-
-License
-------------------------------------------------------------------------------
-
-This project is licensed under the [MIT License](LICENSE.md).
->>>>>>> da52bfe... message
diff --git a/addon/adapters/sails-base.js b/addon/adapters/sails-base.js
index 609bbf7..c5feeae 100644
--- a/addon/adapters/sails-base.js
+++ b/addon/adapters/sails-base.js
@@ -1,258 +1,340 @@
-import Evented from '@ember/object/evented';
-import $ from 'jquery';
-import RSVP from 'rsvp';
+import RESTAdapter from '@ember-data/adapter/rest';
+import { debug, warn } from '@ember/debug';
+import { action, set } from '@ember/object';
+import {
+ addListener,
+ hasListeners,
+ removeListener,
+ sendEvent,
+} from '@ember/object/events';
import { bind, schedule } from '@ember/runloop';
import { camelize } from '@ember/string';
-import { set, get } from '@ember/object';
-import DS from 'ember-data';
-import WithLoggerMixin from '../mixins/with-logger';
+import { cached } from '@glimmer/tracking';
import { pluralize } from 'ember-inflector';
-import { bool } from '@ember/object/computed'
-import { debug, warn } from '@ember/debug';
/**
* Base adapter for SailsJS adapters
*
* @since 0.0.1
* @class SailsBaseAdapter
- * @extends DS.RESTAdapter
- * @uses Ember.Evented
- * @uses WithLoggerMixin
+ * @extends RESTAdapter
* @constructor
*/
-export default DS.RESTAdapter.extend(Evented, WithLoggerMixin, {
- /**
- * @inheritDoc
- */
- defaultSerializer: 'sails',
-
- /**
- * Whether to use CSRF
- * @since 0.0.1
- * @property useCSRF
- * @type Boolean
- */
- useCSRF: false,
-
- /**
- * Path where to GET the CSRF
- * @since 0.0.15
- * @property csrfTokenPath
- * @type String
- */
- csrfTokenPath: '/csrfToken',
-
- /**
- * The csrfToken
- * @since 0.0.1
- * @property csrfToken
- * @type String
- */
- csrfToken: null,
-
- /**
- * Are we loading CSRF token?
- * @since 0.0.7
- * @property isLoadingCSRF
- * @type Boolean
- */
- isLoadingCSRF: bool('_csrfTokenLoadingPromise'),
-
- /**
- * The promise responsible of the current CSRF token fetch
- * @since 0.0.15
- * @property _csrfTokenLoadingPromise
- * @type Promise
- * @private
- */
- _csrfTokenLoadingPromise: null,
-
-
- /**
- * @since 0.0.4
- * @method init
- * @inheritDoc
- */
- init: function () {
- this._super();
- set(this, 'csrfToken', null);
- },
-
- /**
- * Send a message using `_request` of extending class
- *
- * @since 0.0.11
- * @method ajax
- * @inheritDoc
- */
- ajax: function (url, method, options) {
- const out = {};
- method = method.toUpperCase();
- if (!options) {
- options = {};
- }
- if (!options.data && method !== 'GET') {
- // so that we can add our CSRF token
- options.data = {};
- }
- const processRequest = bind(this, function () {
- return this._request(out, url, method, options)
- .then(bind(this, function (response) {
- debug(`${out.protocol} ${method} request on ${url}: SUCCESS`);
- debug(' → request:', options.data);
- debug(' ← response:', response);
- if (this.isErrorObject(response)) {
- if (response.errors) {
- return RSVP.reject(new DS.InvalidError(this.formatError(response)));
- }
- return RSVP.reject(response);
- }
- return response;
- }))
- .catch(bind(this, function (error) {
- warn(`${out.protocol} ${method} request on ${url}: ERROR`, false, { id: 'ember-data-sails.failed-request' });
- debug(' → request:', options.data);
- debug(' ← error:', error);
- return RSVP.reject(error);
- }));
- });
- if (method !== 'GET') {
- return this.fetchCSRFToken()
- .then(bind(this, function () {
- this.checkCSRF(options.data);
- return processRequest();
- }));
- }
- else {
- return processRequest();
- }
- },
-
- /**
- * @since 0.0.1
- * @method ajaxError
- * @inheritDoc
- */
- ajaxError: function (jqXHR) {
- const error = this._super(jqXHR);
- let data;
-
- try {
- data = $.parseJSON(jqXHR.responseText);
- }
- catch (err) {
- data = jqXHR.responseText;
- }
-
- if (data.errors) {
- this.error('error returned from Sails', data);
- return new DS.InvalidError(this.formatError(data));
- }
- else if (data) {
- return new Error(data);
- }
- else {
- return error;
- }
- },
-
- /**
- * Fetches the CSRF token if needed
- *
- * @since 0.0.3
- * @method fetchCSRFToken
- * @param {Boolean} [force] If `true`, the token will be fetched even if it has already been fetched
- * @return {RSVP.Promise}
- */
- fetchCSRFToken: function (force) {
- let promise;
- if (get(this, 'useCSRF') && (force || !get(this, 'csrfToken'))) {
- if (!(promise = get(this, '_csrfTokenLoadingPromise'))) {
- this.set('csrfToken', null);
- debug('fetching CSRF token...');
- promise = this._fetchCSRFToken()
- // handle success response
- .then(token => {
- if (!token) {
- this.error('Got an empty CSRF token from the server.');
- return RSVP.reject('Got an empty CSRF token from the server!');
- }
- debug('got a new CSRF token:', token);
- this.set('csrfToken', token);
- schedule('actions', this, 'trigger', 'didLoadCSRF', token);
- return token;
- })
- // handle errors
- .catch(error => {
- this.error('error trying to get new CSRF token:', error);
- schedule('actions', this, 'trigger', 'didLoadCSRF', null, error);
- return error;
- })
- // reset the loading promise
- .finally(bind(this, 'set', '_csrfTokenLoadingPromise', null));
- this.set('_csrfTokenLoadingPromise', promise);
- }
- // return the loading promise
- return promise;
- }
- return RSVP.resolve(null);
- },
-
- /**
- * Format an error coming from Sails
- *
- * @since 0.0.1
- * @method formatError
- * @param {Object} error The error to format
- * @return {Object}
- */
- formatError: function (error) {
- return Object.keys(error.invalidAttributes).reduce(function (memo, property) {
- memo[property] = error.invalidAttributes[property].map(function (err) {
- return err.message;
- });
- return memo;
- }, {});
- },
-
- /**
- * @since 0.0.1
- * @method pathForType
- * @inheritDoc
- */
- pathForType: function (type) {
- return pluralize(camelize(type));
- },
-
- /**
- * Is the given result a Sails error object?
- *
- * @since 0.0.1
- * @method isErrorObject
- * @param {Object} data The object to test
- * @return {Boolean} Returns `true` if it's an error object, else `false`
- */
- isErrorObject: function (data) {
- return !!(data && data.error && data.model && data.summary && data.status);
- },
-
- /**
- * Check if we have a CSRF and include it in the given data to be sent
- *
- * @since 0.0.1
- * @method checkCSRF
- * @param {Object} [data] The data on which to attach the CSRF token
- * @return {Object} data The given data
- */
- checkCSRF: function (data) {
- if (!this.useCSRF) {
- return data;
- }
- debug('adding CSRF token');
- if (!this.csrfToken) {
- throw new Error("CSRF Token not fetched yet.");
- }
- data._csrf = this.csrfToken;
- return data;
- }
-});
+
+export default class SailsBase extends RESTAdapter {
+ @cached
+ get SAILS_LOG_LEVEL() {
+ return this.appConfig?.SAILS_LOG_LEVEL || 'error';
+ }
+
+ /**
+ * @inheritDoc
+ */
+ defaultSerializer = 'sails';
+
+ /**
+ * Whether to use CSRF
+ * @since 0.0.1
+ * @property useCSRF
+ * @type Boolean
+ */
+ useCSRF = false;
+
+ /**
+ * Path where to GET the CSRF
+ * @since 0.0.15
+ * @property csrfTokenPath
+ * @type String
+ */
+ csrfTokenPath = '/csrfToken';
+
+ /**
+ * The csrfToken
+ * @since 0.0.1
+ * @property csrfToken
+ * @type String
+ */
+ csrfToken = null;
+
+ /**
+ * Are we loading CSRF token?
+ * @since 0.0.7
+ * @property isLoadingCSRF
+ * @type Boolean
+ */
+ get isLoadingCSRF() {
+ return Boolean(this._csrfTokenLoadingPromise);
+ }
+
+ /**
+ * The promise responsible of the current CSRF token fetch
+ * @since 0.0.15
+ * @property _csrfTokenLoadingPromise
+ * @type Promise
+ * @private
+ */
+ _csrfTokenLoadingPromise = null;
+
+ /**
+ * Send a message using `_request` of extending class
+ *
+ * @since 0.0.11
+ * @method ajax
+ * @inheritDoc
+ */
+ @action
+ ajax(url, method, options) {
+ const out = {};
+ method = method.toUpperCase();
+ if (!options) {
+ options = {};
+ }
+ if (!options.data && method !== 'GET') {
+ // so that we can add our CSRF token
+ options.data = {};
+ }
+
+ const processRequest = bind(this, function () {
+ return this._request(out, url, method, options)
+ .then(
+ bind(this, function (response) {
+ debug(`${out.protocol} ${method} request on ${url}: SUCCESS`);
+ debug(' → request:', options.data);
+ debug(' ← response:', response);
+ if (this.isErrorObject(response)) {
+ if (response.errors) {
+ return Promise.reject(new Error(this.formatError(response)));
+ }
+ return Promise.reject(response);
+ }
+ return response;
+ }),
+ )
+ .catch(
+ bind(this, function (error) {
+ warn(`${out.protocol} ${method} request on ${url}: ERROR`, false, {
+ id: 'ember-data-sails.failed-request',
+ });
+ debug(' → request:', options.data);
+ debug(' ← error:', error);
+ return Promise.reject(error);
+ }),
+ );
+ });
+ if (method !== 'GET') {
+ return this.fetchCSRFToken().then(
+ bind(this, function () {
+ this.checkCSRF(options.data);
+ return processRequest();
+ }),
+ );
+ } else {
+ return processRequest();
+ }
+ }
+
+ /**
+ * @since 0.0.1
+ * @method ajaxError
+ * @inheritDoc
+ */
+ @action
+ ajaxError(jqXHR) {
+ const error = super.ajaxError(jqXHR);
+ let data;
+
+ try {
+ data = JSON.parse(jqXHR.responseText);
+ } catch (err) {
+ data = jqXHR.responseText;
+ }
+
+ if (data.errors) {
+ this.error('error returned from Sails', data);
+ return new Error(this.formatError(data));
+ } else if (data) {
+ return new Error(data);
+ } else {
+ return error;
+ }
+ }
+
+ /**
+ * Fetches the CSRF token if needed
+ *
+ * @since 0.0.3
+ * @method fetchCSRFToken
+ * @param {Boolean} [force] If `true`, the token will be fetched even if it has already been fetched
+ * @return {Promise}
+ */
+ @action
+ fetchCSRFToken(force) {
+ let promise;
+ if (this.useCSRF && (force || !this.csrfToken)) {
+ if (!(promise = this._csrfTokenLoadingPromise)) {
+ set(this, 'csrfToken', null);
+ debug('fetching CSRF token...');
+ promise = this._fetchCSRFToken()
+ // handle success response
+ .then((token) => {
+ if (!token) {
+ this.error('Got an empty CSRF token from the server.');
+ return Promise.reject('Got an empty CSRF token from the server!');
+ }
+ debug('got a new CSRF token:', token);
+ set(this, 'csrfToken', token);
+ schedule('actions', this, 'trigger', 'didLoadCSRF', token);
+ return token;
+ })
+ // handle errors
+ .catch((error) => {
+ this.error('error trying to get new CSRF token:', error);
+ schedule('actions', this, 'trigger', 'didLoadCSRF', null, error);
+ return error;
+ })
+ // reset the loading promise
+ .finally(bind(this, 'set', '_csrfTokenLoadingPromise', null));
+ set(this, '_csrfTokenLoadingPromise', promise);
+ }
+ // return the loading promise
+ return promise;
+ }
+ return Promise.resolve(null);
+ }
+
+ /**
+ * Format an error coming from Sails
+ *
+ * @since 0.0.1
+ * @method formatError
+ * @param {Object} error The error to format
+ * @return {Object}
+ */
+ formatError(error) {
+ return Object.keys(error.invalidAttributes).reduce(function (
+ memo,
+ property,
+ ) {
+ memo[property] = error.invalidAttributes[property].map(function (err) {
+ return err.message;
+ });
+ return memo;
+ }, {});
+ }
+
+ /**
+ * @since 0.0.1
+ * @method pathForType
+ * @inheritDoc
+ */
+ pathForType(type) {
+ return pluralize(camelize(type));
+ }
+
+ /**
+ * Is the given result a Sails error object?
+ *
+ * @since 0.0.1
+ * @method isErrorObject
+ * @param {Object} data The object to test
+ * @return {Boolean} Returns `true` if it's an error object, else `false`
+ */
+ isErrorObject(data) {
+ return !!(data && data.error && data.model && data.summary && data.status);
+ }
+
+ /**
+ * Check if we have a CSRF and include it in the given data to be sent
+ *
+ * @since 0.0.1
+ * @method checkCSRF
+ * @param {Object} [data] The data on which to attach the CSRF token
+ * @return {Object} data The given data
+ */
+ @action
+ checkCSRF(data) {
+ if (!this.useCSRF) {
+ return data;
+ }
+ debug('adding CSRF token');
+ if (!this.csrfToken) {
+ throw new Error('CSRF Token not fetched yet.');
+ }
+ data._csrf = this.csrfToken;
+ return data;
+ }
+
+ @action
+ log(type) {
+ const LEVELS = 'debug info notice warn error'.split(' ');
+ const levelMap = { notice: 'log' };
+ const minLevel = this.SAILS_LOG_LEVEL;
+
+ let shouldLog = false;
+ const isLevelLoggable = LEVELS.some((level) => {
+ if (level === minLevel) shouldLog = true;
+
+ return shouldLog && type === level;
+ });
+
+ if (isLevelLoggable) {
+ return console[levelMap[type] || type].bind(this, '[ember-data-sails]');
+ } else {
+ return () => {};
+ }
+ }
+
+ @action
+ debug() {
+ return this.log('debug');
+ }
+
+ @action
+ info() {
+ return this.log('info');
+ }
+
+ @action
+ notice() {
+ return this.log('notice');
+ }
+
+ @action
+ warn() {
+ return this.log('warn');
+ }
+
+ @action
+ error() {
+ return this.log('error');
+ }
+
+ @action
+ trigger(event, ...args) {
+ debug(`triggering event ${event}`);
+ return sendEvent(this, event, args);
+ }
+
+ @action
+ on(name, target, method) {
+ addListener(this, name, target, method);
+ return this;
+ }
+
+ @action
+ one(name, target, method) {
+ addListener(this, name, target, method, true);
+ return this;
+ }
+
+ @action
+ off(name, target, method) {
+ removeListener(this, name, target, method);
+ return this;
+ }
+
+ @action
+ has(name) {
+ return hasListeners(this, name);
+ }
+}
diff --git a/addon/adapters/sails-rest.js b/addon/adapters/sails-rest.js
index 02963f3..9dcd9c9 100644
--- a/addon/adapters/sails-rest.js
+++ b/addon/adapters/sails-rest.js
@@ -1,6 +1,7 @@
import { A } from '@ember/array';
+import RESTAdapter from '@ember-data/adapter/rest';
+// eslint-disable-next-line ember/no-computed-properties-in-native-classes
import { computed } from '@ember/object';
-import DS from 'ember-data';
import SailsBaseAdapter from './sails-base';
/**
@@ -11,78 +12,79 @@ import SailsBaseAdapter from './sails-base';
* @extends SailsBaseAdapter
* @constructor
*/
-export default SailsBaseAdapter.extend({
- /**
- * The full URL to the CSRF token
- * @since 0.0.15
- * @property csrfTokenUrl
- * @type String
- */
- csrfTokenUrl: computed('host', 'namespace', 'csrfTokenPath', function (key, value) {
- let csrfTokenUrl, csrfTokenPath;
- if (arguments.length > 1) {
- this._csrfTokenUrl = csrfTokenUrl = value;
- }
- else if (this._csrfTokenUrl !== undefined) {
- csrfTokenUrl = this._csrfTokenUrl;
- }
- else {
- csrfTokenPath = this.get('csrfTokenPath');
- csrfTokenUrl = A([
- this.get('host'),
- csrfTokenPath.charAt(0) === '/' ? null : this.get('namespace'),
- csrfTokenPath.replace(/^\//, '')
- ]).filter(Boolean).join('/');
- if (!/^(https?:)?\/\//.test(csrfTokenUrl)) {
- csrfTokenUrl = '/' + csrfTokenUrl;
- }
- }
- return csrfTokenUrl;
- }),
+export default class SailsRest extends SailsBaseAdapter {
+ /**
+ * The full URL to the CSRF token
+ * @since 0.0.15
+ * @property csrfTokenUrl
+ * @type String
+ */
+ @computed('host', 'namespace', 'csrfTokenPath')
+ csrfTokenUrl(key, value) {
+ let csrfTokenUrl, csrfTokenPath;
+ if (arguments.length > 1) {
+ this._csrfTokenUrl = csrfTokenUrl = value;
+ } else if (this._csrfTokenUrl !== undefined) {
+ csrfTokenUrl = this._csrfTokenUrl;
+ } else {
+ csrfTokenPath = this.csrfTokenPath;
+ csrfTokenUrl = A([
+ this.host,
+ csrfTokenPath.charAt(0) === '/' ? null : this.namespace,
+ csrfTokenPath.replace(/^\//, ''),
+ ])
+ .filter(Boolean)
+ .join('/');
+ if (!/^(https?:)?\/\//.test(csrfTokenUrl)) {
+ csrfTokenUrl = '/' + csrfTokenUrl;
+ }
+ }
+ return csrfTokenUrl;
+ }
+ /**
+ * Sends a request over HTTP
+ *
+ * @since 0.0.11
+ * @method _request
+ * @param {Object} out
+ * @param {String} url
+ * @param {String} method
+ * @param {Object} options
+ * @returns {Promise}
+ * @private
+ */
+ _request(out, url, method, options) {
+ out.protocol = 'http';
+ return this._restAdapter_ajax.call(this, url, method, options);
+ }
- /**
- * Sends a request over HTTP
- *
- * @since 0.0.11
- * @method _request
- * @param {Object} out
- * @param {String} url
- * @param {String} method
- * @param {Object} options
- * @returns {Ember.RSVP.Promise}
- * @private
- */
- _request: function (out, url, method, options) {
- out.protocol = 'http';
- return this._restAdapter_ajax.call(this, url, method, options);
- },
+ /**
+ * Fetches the CSRF token
+ *
+ * @since 0.0.4
+ * @method _fetchCSRFToken
+ * @return {Promise} Returns the promise resolving the CSRF token
+ * @private
+ */
+ _fetchCSRFToken() {
+ return this._restAdapter_ajax
+ .call(this, this.csrfTokenUrl, 'get')
+ .then(function (tokenObject) {
+ return tokenObject._csrf;
+ });
+ }
- /**
- * Fetches the CSRF token
- *
- * @since 0.0.4
- * @method _fetchCSRFToken
- * @return {Ember.RSVP.Promise} Returns the promise resolving the CSRF token
- * @private
- */
- _fetchCSRFToken: function () {
- return this._restAdapter_ajax.call(this, this.get('csrfTokenUrl'), 'get')
- .then(function (tokenObject) {
- return tokenObject._csrf;
- });
- },
-
- /**
- * We need to copy the original `ajax` method to be able to use it inside our own `_request`
- *
- * @since 0.0.8
- * @method _restAdapter_ajax
- * @private
- * @param {String} url
- * @param {String} type The request type GET, POST, PUT, DELETE etc.
- * @param {Object} hash
- * @return {Ember.RSVP.Promise} promise
- */
- _restAdapter_ajax: DS.RESTAdapter.proto().ajax
-});
+ /**
+ * We need to copy the original `ajax` method to be able to use it inside our own `_request`
+ *
+ * @since 0.0.8
+ * @method _restAdapter_ajax
+ * @private
+ * @param {String} url
+ * @param {String} type The request type GET, POST, PUT, DELETE etc.
+ * @param {Object} hash
+ * @return {Promise} promise
+ */
+ _restAdapter_ajax = RESTAdapter.proto().ajax;
+}
diff --git a/addon/adapters/sails-socket.js b/addon/adapters/sails-socket.js
index 46ed10d..a24bd79 100644
--- a/addon/adapters/sails-socket.js
+++ b/addon/adapters/sails-socket.js
@@ -1,10 +1,9 @@
-import { debounce, bind } from '@ember/runloop';
-import { camelize } from '@ember/string';
+import { debug, warn } from '@ember/debug';
+import { bind, debounce } from '@ember/runloop';
import { inject as service } from '@ember/service';
-import { get, aliasMethod } from '@ember/object';
-import SailsBaseAdapter from './sails-base';
+import { camelize } from '@ember/string';
import { pluralize } from 'ember-inflector';
-import { warn, debug } from '@ember/debug';
+import SailsBaseAdapter from './sails-base';
/**
* Adapter for SailsJS sockets
@@ -14,230 +13,252 @@ import { warn, debug } from '@ember/debug';
* @extends SailsBaseAdapter
* @constructor
*/
-export default SailsBaseAdapter.extend({
- store: service(),
- sailsSocket: service(),
- /**
- * Holds the scheduled subscriptions
- * @since 0.0.11
- * @property _scheduledSubscriptions
- * @type Object
- */
- _scheduledSubscriptions: null,
- /**
- * The method used when sending a request over the socket to update/setup subscriptions
- * Set this or subscribeEndpoint to `null` will disable this feature
- * @since 0.0.11
- * @property subscribeMethod
- * @type String
- */
- subscribeMethod: 'POST',
- /**
- * The path to send a request over the socket to update/setup subscriptions
- * Set this or subscribeMethod to `null` will disable this feature
- * @since 0.0.11
- * @property subscribeEndpoint
- * @type String
- */
- subscribeEndpoint: '/socket/subscribe',
+export default class App extends SailsBaseAdapter {
+ @service store;
+ @service sailsSocket;
+ /**
+ * Holds the scheduled subscriptions
+ * @since 0.0.11
+ * @property _scheduledSubscriptions
+ * @type Object
+ */
+ _scheduledSubscriptions = null;
+ /**
+ * The method used when sending a request over the socket to update/setup subscriptions
+ * Set this or subscribeEndpoint to `null` will disable this feature
+ * @since 0.0.11
+ * @property subscribeMethod
+ * @type String
+ */
+ subscribeMethod = 'POST';
+ /**
+ * The path to send a request over the socket to update/setup subscriptions
+ * Set this or subscribeMethod to `null` will disable this feature
+ * @since 0.0.11
+ * @property subscribeEndpoint
+ * @type String
+ */
+ subscribeEndpoint = '/socket/subscribe';
- /**
- * @since 0.0.1
- * @method init
- * @inheritDoc
- */
- init: function () {
- this._super();
- get(this, 'sailsSocket').on('didConnect', this, 'fetchCSRFToken', true);
- },
+ /**
+ * @since 1.0.0
+ * @method constructor
+ * @inheritDoc
+ */
+ constructor() {
+ super(...arguments);
+ this.sailsSocket.on('didConnect', this, 'fetchCSRFToken', true);
+ }
- /**
- * Sends a request over the socket
- *
- * @since 0.0.11
- * @method _request
- * @param {Object} out
- * @param {String} url
- * @param {String} method
- * @param {Object} options
- * @returns {Ember.RSVP.Promise}
- * @private
- */
- _request: function (out, url, method, options) {
- out.protocol = 'socket';
- return get(this, 'sailsSocket').request(method, url, options.data);
- },
+ /**
+ * Sends a request over the socket
+ *
+ * @since 0.0.11
+ * @method _request
+ * @param {Object} out
+ * @param {String} url
+ * @param {String} method
+ * @param {Object} options
+ * @returns {Promise}
+ * @private
+ */
+ _request(out, url, method, options) {
+ out.protocol = 'socket';
+ return this.sailsSocket.request(method, url, options.data);
+ }
- /**
- * @since 0.0.11
- * @method buildURL
- * @inheritDoc
- */
- buildURL: function (type, id, record) {
- return this._super(type, id, record);
- },
+ /**
+ * @since 0.0.11
+ * @method buildURL
+ * @inheritDoc
+ */
+ buildURL(type, id, record) {
+ return super.buildURL(type, id, record);
+ }
- /**
- * Whether we should subscribe to a given model or not
- * By default it subscribe to any model, tho it's better to optimize by setting up a filter here
- * so that it does not ask the server for subscription on unneeded stuff
- *
- * @since 0.0.11
- * @method shouldSubscribe
- * @param {subclass of DS.Model} type The type of the record
- * @param {Object} recordJson The json of the record (DO NOT ALTER IT!)
- * @returns {Boolean} If `false` then the record isn't subscribed for, else it is
- */
- shouldSubscribe: function (/*type, recordJson*/) {
- return true;
- },
+ /**
+ * Whether we should subscribe to a given model or not
+ * By default it subscribe to any model, tho it's better to optimize by setting up a filter here
+ * so that it does not ask the server for subscription on unneeded stuff
+ *
+ * @since 0.0.11
+ * @method shouldSubscribe
+ * @param {subclass of Model} type The type of the record
+ * @param {Object} recordJson The json of the record (DO NOT ALTER IT!)
+ * @returns {Boolean} If `false` then the record isn't subscribed for, else it is
+ */
+ shouldSubscribe(/*type, recordJson*/) {
+ return true;
+ }
- /**
- * Fetches the CSRF token
- *
- * @since 0.0.4
- * @method _fetchCSRFToken
- * @return {Ember.RSVP.Promise} Returns the promise resolving the CSRF token
- * @private
- */
- _fetchCSRFToken: function () {
- return get(this, 'sailsSocket').request('get', this.get('csrfTokenPath').replace(/^\/?/, '/'))
- .then(function (tokenObject) {
- return tokenObject._csrf;
- });
- },
+ /**
+ * Fetches the CSRF token
+ *
+ * @since 0.0.4
+ * @method _fetchCSRFToken
+ * @return {Promise} Returns the promise resolving the CSRF token
+ * @private
+ */
+ _fetchCSRFToken() {
+ return this.sailsSocket
+ .request('get', this.csrfTokenPath.replace(/^\/?/, '/'))
+ .then(function (tokenObject) {
+ return tokenObject._csrf;
+ });
+ }
- /**
- * Handle a created record message
- *
- * @since 0.0.1
- * @method _handleSocketRecordCreated
- * @param {DS.Store} store The store to be used
- * @param {subclass of DS.Model} type The type to push
- * @param {Object} message The message received
- * @private
- */
- _handleSocketRecordCreated: function (store, type, message) {
- const record = message.data;
- const payload = {};
- if (!record.id && message.id) {
- record.id = message.id;
- }
- payload[pluralize(camelize(type.modelName))] = [record];
- store.pushPayload(type.modelName, payload);
- },
+ /**
+ * Handle a created record message
+ *
+ * @since 0.0.1
+ * @method _handleSocketRecordCreated
+ * @param {Store} store The store to be used
+ * @param {subclass of Model} type The type to push
+ * @param {Object} message The message received
+ * @private
+ */
+ _handleSocketRecordCreated(store, type, message) {
+ const record = message.data;
+ const payload = {};
+ if (!record.id && message.id) {
+ record.id = message.id;
+ }
+ payload[pluralize(camelize(type.modelName))] = [record];
+ store.pushPayload(type.modelName, payload);
+ }
- /**
- * Handle a updated record message
- *
- * @since 0.0.1
- * @method _handleSocketRecordUpdated
- * @param {DS.Store} store The store to be used
- * @param {subclass of DS.Model} type The type to push
- * @param {Object} message The message received
- * @private
- */
- _handleSocketRecordUpdated: aliasMethod('_handleSocketRecordCreated'),
+ /**
+ * Handle a updated record message
+ *
+ * @since 0.0.1
+ * @method _handleSocketRecordUpdated
+ * @param {Store} store The store to be used
+ * @param {subclass of Model} type The type to push
+ * @param {Object} message The message received
+ * @private
+ */
+ _handleSocketRecordUpdated() {
+ this._handleSocketRecordCreated(...arguments);
+ }
- /**
- * Handle a destroyed record message
- *
- * @since 0.0.1
- * @method _handleSocketRecordDeleted
- * @param {DS.Store} store The store to be used
- * @param {subclass of DS.Model} type The type to push
- * @param {Object} message The message received
- * @private
- */
- _handleSocketRecordDeleted: function (store, type, message) {
- const record = store.peekRecord(type.modelName, message.id);
- if (record && typeof record.get('dirtyType') === 'undefined') {
- record.unloadRecord();
- }
- },
+ /**
+ * Handle a destroyed record message
+ *
+ * @since 0.0.1
+ * @method _handleSocketRecordDeleted
+ * @param {Store} store The store to be used
+ * @param {subclass of Model} type The type to push
+ * @param {Object} message The message received
+ * @private
+ */
+ _handleSocketRecordDeleted(store, type, message) {
+ const record = store.peekRecord(type.modelName, message.id);
+ if (record && typeof record.get('dirtyType') === 'undefined') {
+ record.unloadRecord();
+ }
+ }
- /**
- * Listen to socket message for a given model
- *
- * @since 0.0.1
- * @method _listenToSocket
- * @param {String} model The model name to listen for events
- * @private
- */
- _listenToSocket: function (model) {
- const eventName = camelize(model).toLowerCase();
- const socket = get(this, 'sailsSocket');
- if (socket.listenFor(eventName, true)) {
- this.notice(`setting up adapter to listen for ${model} messages`);
- const store = get(this, 'store');
- const type = store.modelFor(model);
- socket.on(eventName + '.created', bind(this, '_handleSocketRecordCreated', store, type));
- socket.on(eventName + '.updated', bind(this, '_handleSocketRecordUpdated', store, type));
- socket.on(eventName + '.destroyed', bind(this, '_handleSocketRecordDeleted', store, type));
- }
- },
+ /**
+ * Listen to socket message for a given model
+ *
+ * @since 0.0.1
+ * @method _listenToSocket
+ * @param {String} model The model name to listen for events
+ * @private
+ */
+ _listenToSocket(model) {
+ const eventName = camelize(model).toLowerCase();
+ const socket = this.sailsSocket;
+ if (socket.listenFor(eventName, true)) {
+ this.notice(`setting up adapter to listen for ${model} messages`);
+ const store = this.store;
+ const type = store.modelFor(model);
+ socket.on(
+ eventName + '.created',
+ bind(this, '_handleSocketRecordCreated', store, type),
+ );
+ socket.on(
+ eventName + '.updated',
+ bind(this, '_handleSocketRecordUpdated', store, type),
+ );
+ socket.on(
+ eventName + '.destroyed',
+ bind(this, '_handleSocketRecordDeleted', store, type),
+ );
+ }
+ }
- /**
- * Schedule a record subscription
- *
- * @since 0.0.11
- * @method _scheduleSubscribe
- * @param {subclass of DS.Model} type
- * @param {String|Number} id
- * @private
- */
- _scheduleSubscribe: function (type, id) {
- if (id && this.shouldSubscribe(type, id)) {
- if (!this._scheduledSubscriptions) {
- this._scheduledSubscriptions = {};
- }
- // use an object and keys so that we don't have duplicate IDs
- let key = camelize(type.modelName);
- if (!this._scheduledSubscriptions[key]) {
- this._scheduledSubscriptions[key] = {};
- }
- id = '' + id;
- if (!this._scheduledSubscriptions[key][id]) {
- this._scheduledSubscriptions[key][id] = 0;
- debounce(this, '_subscribeScheduled', 50);
- }
- }
- },
+ /**
+ * Schedule a record subscription
+ *
+ * @since 0.0.11
+ * @method _scheduleSubscribe
+ * @param {subclass of Model} type
+ * @param {String|Number} id
+ * @private
+ */
+ _scheduleSubscribe(type, id) {
+ if (id && this.shouldSubscribe(type, id)) {
+ if (!this._scheduledSubscriptions) {
+ this._scheduledSubscriptions = {};
+ }
+ // use an object and keys so that we don't have duplicate IDs
+ let key = camelize(type.modelName);
+ if (!this._scheduledSubscriptions[key]) {
+ this._scheduledSubscriptions[key] = {};
+ }
+ id = '' + id;
+ if (!this._scheduledSubscriptions[key][id]) {
+ this._scheduledSubscriptions[key][id] = 0;
+ debounce(this, '_subscribeScheduled', 50);
+ }
+ }
+ }
- /**
- * Ask the API to subscribe
- *
- * @since 0.0.11
- * @method _subscribeScheduled
- * @private
- */
- _subscribeScheduled: function () {
- if (this._scheduledSubscriptions) {
- // grab and delete our scheduled subscriptions
- let opt = this.getProperties('subscribeMethod', 'subscribeEndpoint');
- let data = this._scheduledSubscriptions;
- this._scheduledSubscriptions = null;
- const payload = {};
- // the IDs are the keys so that set both the same will not duplicate them, we need to reduce them
- for (let k in data) {
- payload[k] = Object.keys(data[k]);
- this._listenToSocket(k);
- }
+ /**
+ * Ask the API to subscribe
+ *
+ * @since 0.0.11
+ * @method _subscribeScheduled
+ * @private
+ */
+ _subscribeScheduled() {
+ if (this._scheduledSubscriptions) {
+ // grab and delete our scheduled subscriptions
+ let opt = {
+ subscribeMethod: this.subscribeMethod,
+ subscribeEndpoint: this.subscribeEndpoint,
+ };
+ let data = this._scheduledSubscriptions;
+ this._scheduledSubscriptions = null;
+ const payload = {};
+ // the IDs are the keys so that set both the same will not duplicate them, we need to reduce them
+ for (let k in data) {
+ payload[k] = Object.keys(data[k]);
+ this._listenToSocket(k);
+ }
- if (opt.subscribeEndpoint && opt.subscribeMethod) {
- debug(`asking the API to subscribe to some records of type ${Object.keys(data).join(', ')}`);
- // ask the API to subscribe to those records
- this.fetchCSRFToken().then(() => {
- this.checkCSRF(payload);
- get(this, 'sailsSocket').request(opt.subscribeMethod, opt.subscribeEndpoint, payload)
- .then((result) => {
- debug('subscription successful, result:', result);
- })
- .catch((/* jwr */) => {
- warn('error when trying to subscribe to some model(s)', false, { id: 'ember-data-sails.subscribe' });
- });
- });
- }
- }
- }
-});
+ if (opt.subscribeEndpoint && opt.subscribeMethod) {
+ debug(
+ `asking the API to subscribe to some records of type ${Object.keys(
+ data,
+ ).join(', ')}`,
+ );
+ // ask the API to subscribe to those records
+ this.fetchCSRFToken().then(() => {
+ this.checkCSRF(payload);
+ this.sailsSocket
+ .request(opt.subscribeMethod, opt.subscribeEndpoint, payload)
+ .then((result) => {
+ debug('subscription successful, result:', result);
+ })
+ .catch((/* jwr */) => {
+ warn('error when trying to subscribe to some model(s)', false, {
+ id: 'ember-data-sails.subscribe',
+ });
+ });
+ });
+ }
+ }
+ }
+}
diff --git a/addon/initializers/ember-data-sails.js b/addon/initializers/ember-data-sails.js
index 619f738..bb9a39e 100644
--- a/addon/initializers/ember-data-sails.js
+++ b/addon/initializers/ember-data-sails.js
@@ -1,39 +1,17 @@
-import { get } from '@ember/object';
-import DS from 'ember-data';
-import WithLoggerMixin from '../mixins/with-logger';
-import { LEVELS } from '../mixins/with-logger';
-import StoreMixin from '../mixins/store';
import SailsSocketService from '../services/sails-socket';
-DS.Store.reopen(StoreMixin);
-
export function initialize(application) {
- let methods = {};
- let shouldLog = false;
- const minLevel = get(application, 'SAILS_LOG_LEVEL');
- LEVELS.forEach(function (level) {
- if (level === minLevel) {
- shouldLog = true;
- }
- if (!shouldLog) {
- methods[level] = [];
- }
- });
- WithLoggerMixin.reopen(methods);
-
- application.register('service:sails-socket', SailsSocketService);
- application.register('config:ember-data-sails', get(application, 'emberDataSails') || {}, {instantiate: false});
-
- // setup injections
- application.inject('adapter', 'sailsSocket', 'service:sails-socket');
- application.inject('serializer', 'config', 'config:ember-data-sails');
- application.inject('route', 'sailsSocket', 'service:sails-socket');
- application.inject('controller', 'sailsSocket', 'service:sails-socket');
+ application.register('service:sails-socket', SailsSocketService);
+ application.register(
+ 'config:ember-data-sails',
+ application.emberDataSails || {},
+ { instantiate: false },
+ );
}
export default {
- name: 'ember-data-sails',
- before: 'ember-data',
+ name: 'ember-data-sails',
+ before: 'ember-data',
- initialize: initialize
+ initialize: initialize,
};
diff --git a/addon/instance-initializers/ember-data-sails.js b/addon/instance-initializers/ember-data-sails.js
new file mode 100644
index 0000000..847cdff
--- /dev/null
+++ b/addon/instance-initializers/ember-data-sails.js
@@ -0,0 +1,13 @@
+export function initialize(application) {
+ application.inject('adapter', 'sailsSocket', 'service:sails-socket');
+ application.inject('serializer', 'config', 'config:ember-data-sails');
+ application.inject('route', 'sailsSocket', 'service:sails-socket');
+ application.inject('controller', 'sailsSocket', 'service:sails-socket');
+}
+
+export default {
+ name: 'ember-data-sails',
+ before: 'ember-data',
+
+ initialize: initialize,
+};
diff --git a/addon/mixins/store.js b/addon/mixins/store.js
deleted file mode 100644
index c8d4774..0000000
--- a/addon/mixins/store.js
+++ /dev/null
@@ -1,71 +0,0 @@
-import { typeOf } from '@ember/utils';
-import Mixin from '@ember/object/mixin';
-import SailsSocketAdapter from '../adapters/sails-socket';
-
-
-const StoreMixin = Mixin.create({
- /**
- * @since 0.0.11
- * @inheritDoc
- * @method pushPayload
- * @param {String|subclass of DS.Model} [type]
- * @param {Object} payload
- * @param {Boolean} [subscribe] Whether to subscribe to pushed models or not (Sails socket)
- */
- pushPayload: function (/*type, payload, subscribe*/) {
- let sub = false;
- const args = [].slice.call(arguments);
- const old = this._pushSubscribes;
- if (typeOf(args[args.length - 1]) === 'boolean') {
- sub = args.pop();
- }
- this._pushSubscribes = sub;
- this._super.apply(this, args);
- this._pushSubscribes = old;
- },
-
-
- /**
- * @since 0.0.11
- * @method push
- * @inheritDoc
- */
- push: function (/*results, data, _partial*/) {
- const res = this._super.apply(this, arguments);
- const resArray = Array.isArray(res) ? res : [res];
- let id;
-
- resArray.forEach(res => {
- if (res && (id = res.get('id'))) {
- const type = this.modelFor(res.constructor.modelName);
- const adapter = this.adapterFor(res.constructor.modelName);
- if (adapter instanceof SailsSocketAdapter) {
- adapter._scheduleSubscribe(type, id);
- }
- }
- });
-
- return res;
- },
-
- /**
- * Schedule a subscription to the given model
- *
- * @since 0.0.11
- * @method subscribe
- * @param {String|subclass of DS.Model} type
- * @param {Array|String|Number} ids
- */
- subscribe: function (type, ids) {
- if (typeOf(ids) !== 'array') {
- ids = [ids];
- }
- type = this.modelFor(type);
- const adapter = this.adapterFor(type);
- for (let i = 0; i < ids.length; i++) {
- adapter._scheduleSubscribe(type, ids[i]);
- }
- }
-});
-
-export default StoreMixin;
diff --git a/addon/mixins/with-logger.js b/addon/mixins/with-logger.js
deleted file mode 100644
index a20e351..0000000
--- a/addon/mixins/with-logger.js
+++ /dev/null
@@ -1,23 +0,0 @@
-import Mixin from '@ember/object/mixin';
-import { bind } from '@ember/runloop';
-import Ember from 'ember';
-
-export var LEVELS = 'debug info notice warn error'.split(' ');
-
-const levelMap = {
- notice: 'log'
-};
-let methods = {};
-
-LEVELS.forEach(function (level) {
- methods[level] = bind(Ember.Logger, levelMap[level] || level, '[ed-sails]');
-});
-
-/**
- * Mix logging methods in our class depending on the configured log level
- * @since 0.0.10
- * @class WithLoggerMixin
- * @extends Ember.Logger
- * @extensionFor Ember.Object
- */
-export default Mixin.create(methods);
diff --git a/addon/serializers/sails.js b/addon/serializers/sails.js
index 5462498..b2942ae 100644
--- a/addon/serializers/sails.js
+++ b/addon/serializers/sails.js
@@ -1,159 +1,182 @@
+import RESTSerializer from '@ember-data/serializer/rest';
+import { debug, warn } from '@ember/debug';
import { readOnly } from '@ember/object/computed';
import { typeOf } from '@ember/utils';
-import $ from 'jquery';
-import { get } from '@ember/object';
-import DS from 'ember-data';
-import WithLogger from '../mixins/with-logger';
-import SailsSocketAdapter from '@brickclick/ember-data-sails/adapters/sails-socket';
+import SailsSocketAdapter from '@voll/ember-data-sails/adapters/sails-socket';
import { pluralize } from 'ember-inflector';
-import { warn, debug } from '@ember/debug';
-import { computed } from '@ember/object';
+import _ from 'lodash';
-
-function blueprintsWrapMethod(method) {
- return function () {
- return (get(this, 'useSailsEmberBlueprints') ? this._super : method).apply(this, arguments);
- };
+function blueprintsWrapMethod(superMethod, method) {
+ return function () {
+ return (this.useSailsEmberBlueprints ? superMethod : method).apply(
+ this,
+ arguments,
+ );
+ };
}
-
/**
* @class SailsSerializer
- * @extends DS.RESTSerializer
+ * @extends RESTSerializer
*/
-const SailsSerializer = DS.RESTSerializer.extend(WithLogger, {
- /**
- * The config of the addon will be set here by the initializer
- * @since 0.0.17
- * @property config
- * @type Object
- */
- config: computed(function() { return {} }),
+export default class SailsSerializer extends RESTSerializer {
+ /**
+ * The config of the addon will be set here by the initializer
+ * @since 0.0.17
+ * @property config
+ * @type Object
+ */
+ config = {};
+
+ /**
+ * Whether to use `sails-generate-ember-blueprints` or not
+ * @since 0.0.15
+ * @property useSailsEmberBlueprints
+ * @type Boolean
+ */
+ @readOnly('config.useSailsEmberBlueprints') useSailsEmberBlueprints;
- /**
- * Whether to use `sails-generate-ember-blueprints` or not
- * @since 0.0.15
- * @property useSailsEmberBlueprints
- * @type Boolean
- */
- useSailsEmberBlueprints: readOnly('config.useSailsEmberBlueprints'),
+ /**
+ * @since 0.0.11
+ * @method extractArray
+ * @inheritDoc
+ */
+ normalizeArrayResponse() {
+ const superMethod = super.normalizeArrayResponse;
- /**
- * @since 0.0.11
- * @method extractArray
- * @inheritDoc
- */
- normalizeArrayResponse: blueprintsWrapMethod(function (store, primaryType, payload) {
- let newPayload = {};
- newPayload[pluralize(primaryType.modelName)] = payload;
- return this._super(...arguments);
- }),
+ return blueprintsWrapMethod(
+ superMethod,
+ function (store, primaryType, payload) {
+ let newPayload = {};
+ newPayload[pluralize(primaryType.modelName)] = payload;
+ return superMethod(...arguments);
+ },
+ );
+ }
- /**
- * @since 0.0.11
- * @method extractSingle
- * @inheritDoc
- */
- normalizeSingleResponse: blueprintsWrapMethod(function (store, primaryType, payload) {
- if (payload === null) {
- return this._super.apply(this, arguments);
- }
- let newPayload = {};
- newPayload[pluralize(primaryType.modelName)] = [payload];
- return this._super(...arguments);
- }),
+ /**
+ * @since 0.0.11
+ * @method extractSingle
+ * @inheritDoc
+ */
+ normalizeSingleResponse() {
+ const superMethod = super.normalizeSingleResponse;
- /**
- * @since 0.0.11
- * @method serializeIntoHash
- * @inheritDoc
- */
- serializeIntoHash: blueprintsWrapMethod(function (data, type, record, options) {
- if (Object.keys(data).length > 0) {
- this.error(
- `trying to serialize multiple records in one hash for type ${type.modelName}`,
- data
- );
- throw new Error('Sails does not accept putting multiple records in one hash');
- }
- const json = this.serialize(record, options);
- $.extend(data, json);
- }),
+ return blueprintsWrapMethod(
+ superMethod,
+ function (store, primaryType, payload) {
+ if (payload === null) {
+ return superMethod.apply(this, arguments);
+ }
+ let newPayload = {};
+ newPayload[pluralize(primaryType.modelName)] = [payload];
+ return superMethod(...arguments);
+ },
+ );
+ }
- /**
- * @since 0.0.11
- * @method normalize
- * @inheritDoc
- */
- normalize: blueprintsWrapMethod(function (type) {
- const normalized = this._super(...arguments);
- return this._extractEmbeddedRecords(this, this.store, type, normalized);
- }),
+ /**
+ * @since 0.0.11
+ * @method serializeIntoHash
+ * @inheritDoc
+ */
+ serializeIntoHash() {
+ const superMethod = super.serializeIntoHash;
- /**
- * @since 0.0.15
- * @method extract
- * @inheritDoc
- */
- normalizeResponse: function (store, primaryModelClass/*, payload, id, requestType*/) {
- // this is the only place we have access to the store, so that we can get the adapter and check
- // if it is an instance of sails socket adapter, and so register for events if necessary on that
- // model. We keep a cache here to avoid too many calls
- if (!this._modelsUsingSailsSocketAdapter) {
- this._modelsUsingSailsSocketAdapter = Object.create(null);
- }
- const modelName = primaryModelClass.modelName;
- if (this._modelsUsingSailsSocketAdapter[modelName] === undefined) {
- const adapter = store.adapterFor(modelName);
- this._modelsUsingSailsSocketAdapter[modelName] = adapter instanceof SailsSocketAdapter;
- adapter._listenToSocket(modelName);
- }
- return this._super.apply(this, arguments);
- },
+ return blueprintsWrapMethod(
+ superMethod,
+ function (data, type, record, options) {
+ if (Object.keys(data).length > 0) {
+ this.error(
+ `trying to serialize multiple records in one hash for type ${type.modelName}`,
+ data,
+ );
+ throw new Error(
+ 'Sails does not accept putting multiple records in one hash',
+ );
+ }
+ const json = this.serialize(record, options);
+ _.merge(data, json);
+ },
+ );
+ }
+ /**
+ * @since 0.0.11
+ * @method normalize
+ * @inheritDoc
+ */
+ normalize() {
+ const superMethod = super.normalize;
- /**
- * Extract the embedded records and create them
- *
- * @since 0.0.11
- * @method _extractEmbeddedRecords
- * @param {subclass of DS.Model} type
- * @param {Object} hash
- * @returns {Object}
- * @private
- */
- _extractEmbeddedRecords: function (serializer, store, type, hash) {
- type.eachRelationship((key, rel) => {
- const modelName = rel.type.modelName;
- const data = hash[key];
- const serializer = store.serializerFor(modelName);
- if (data) {
- if (rel.kind === 'belongsTo') {
- if (typeOf(hash[key]) === 'object') {
- debug(`found 1 embedded ${modelName} record:`, hash[key]);
- delete hash[key];
- store.push(rel.type, serializer.normalize(rel.type, data, null));
- hash[key] = data.id;
- }
- }
- else if (rel.kind === 'hasMany') {
- hash[key] = data.map(function (item) {
- if (typeOf(item) === 'object') {
- debug(`found 1 embedded ${modelName} record:`, item);
- store.push(rel.type, serializer.normalize(rel.type, item, null));
- return item.id;
- }
- return item;
- });
- }
- else {
- warn(`unknown relationship kind ${rel.kind}: ${rel}`, false, { id: 'ember-data-sails.relationship' });
- throw new Error('Unknown relationship kind ' + rel.kind);
- }
- }
- });
- return hash;
- }
-});
+ return blueprintsWrapMethod(superMethod, function (type) {
+ const normalized = superMethod(...arguments);
+ return this._extractEmbeddedRecords(this, this.store, type, normalized);
+ });
+ }
-export default SailsSerializer;
\ No newline at end of file
+ /**
+ * @since 0.0.15
+ * @method extract
+ * @inheritDoc
+ */
+ normalizeResponse(store, primaryModelClass /*, payload, id, requestType*/) {
+ // this is the only place we have access to the store, so that we can get the adapter and check
+ // if it is an instance of sails socket adapter, and so register for events if necessary on that
+ // model. We keep a cache here to avoid too many calls
+ if (!this._modelsUsingSailsSocketAdapter) {
+ this._modelsUsingSailsSocketAdapter = Object.create(null);
+ }
+ const modelName = primaryModelClass.modelName;
+ if (this._modelsUsingSailsSocketAdapter[modelName] === undefined) {
+ const adapter = store.adapterFor(modelName);
+ this._modelsUsingSailsSocketAdapter[modelName] =
+ adapter instanceof SailsSocketAdapter;
+ adapter._listenToSocket(modelName);
+ }
+ return super.normalizeResponse.apply(this, arguments);
+ }
+
+ /**
+ * Extract the embedded records and create them
+ *
+ * @since 0.0.11
+ * @method _extractEmbeddedRecords
+ * @param {subclass of Model} type
+ * @param {Object} hash
+ * @returns {Object}
+ * @private
+ */
+ _extractEmbeddedRecords(serializer, store, type, hash) {
+ type.eachRelationship((key, rel) => {
+ const modelName = rel.type.modelName;
+ const data = hash[key];
+ const serializer = store.serializerFor(modelName);
+ if (data) {
+ if (rel.kind === 'belongsTo') {
+ if (typeOf(hash[key]) === 'object') {
+ debug(`found 1 embedded ${modelName} record:`, hash[key]);
+ delete hash[key];
+ store.push(rel.type, serializer.normalize(rel.type, data, null));
+ hash[key] = data.id;
+ }
+ } else if (rel.kind === 'hasMany') {
+ hash[key] = data.map(function (item) {
+ if (typeOf(item) === 'object') {
+ debug(`found 1 embedded ${modelName} record:`, item);
+ store.push(rel.type, serializer.normalize(rel.type, item, null));
+ return item.id;
+ }
+ return item;
+ });
+ } else {
+ warn(`unknown relationship kind ${rel.kind}: ${rel}`, false, {
+ id: 'ember-data-sails.relationship',
+ });
+ throw new Error('Unknown relationship kind ' + rel.kind);
+ }
+ }
+ });
+ return hash;
+ }
+}
diff --git a/addon/services/sails-socket.js b/addon/services/sails-socket.js
index 871655d..1547004 100644
--- a/addon/services/sails-socket.js
+++ b/addon/services/sails-socket.js
@@ -1,13 +1,15 @@
/* global io */
-import { later, next, bind } from '@ember/runloop';
-
-import RSVP from 'rsvp';
-import Service from '@ember/service';
-import Evented from '@ember/object/evented';
-import Error from '@ember/error';
-import { set, get, computed } from '@ember/object';
-import WithLoggerMixin from '../mixins/with-logger';
import { debug, warn } from '@ember/debug';
+import { action, set, setProperties } from '@ember/object';
+import {
+ addListener,
+ hasListeners,
+ removeListener,
+ sendEvent,
+} from '@ember/object/events';
+import { bind, later, next } from '@ember/runloop';
+import Service from '@ember/service';
+import { tracked } from '@glimmer/tracking';
/**
* Shortcut to know if an object is alive or not
@@ -18,7 +20,7 @@ import { debug, warn } from '@ember/debug';
* @private
*/
function isAlive(obj) {
- return !(!obj || obj.isDestroying || obj.isDestroyed);
+ return !(!obj || obj.isDestroying || obj.isDestroyed);
}
/**
@@ -26,406 +28,459 @@ function isAlive(obj) {
*
* @since 0.0.4
* @class SailsSocketService
- * @extends Ember.Object
- * @uses Ember.Evented
+ * @extends Service
* @uses WithLoggerMixin
* @constructor
*/
-const SailsSocketService = Service.extend(Evented, WithLoggerMixin, {
- /**
- * Holds our sails socket
- * @since 0.0.4
- * @property _sailsSocket
- * @type SailsSocket
- * @private
- */
- _sailsSocket: null,
+export default class SailsSocketService extends Service {
+ /**
+ * Holds our sails socket
+ * @since 0.0.4
+ * @property _sailsSocket
+ * @type SailsSocket
+ * @private
+ */
+ _sailsSocket = null;
- /**
- * Holds the events we are listening on the socket for later re-binding
- * @since 0.0.4
- * @property _listeners
- * @type Object