ember-m3
Advanced tools
Comparing version 4.1.3 to 4.2.0
@@ -23,9 +23,2 @@ import require, { has } from 'require'; | ||
function m3FlagState(flagName) { | ||
if (window && window.M3ENV && window.M3ENV.FEATURES) { | ||
return window.M3ENV.FEATURES[flagName]; | ||
} | ||
} | ||
export const CUSTOM_MODEL_CLASS = dataFlagState('CUSTOM_MODEL_CLASS'); | ||
export const PROXY_MODEL_CLASS = m3FlagState('PROXY_MODEL_CLASS'); |
@@ -17,6 +17,5 @@ import { get } from '@ember/object'; | ||
} from './utils/notify-changes'; | ||
import { CUSTOM_MODEL_CLASS, PROXY_MODEL_CLASS } from 'ember-m3/-infra/features'; | ||
import { CUSTOM_MODEL_CLASS } from 'ember-m3/-infra/features'; | ||
import { recordDataToRecordMap, recordToRecordArrayMap } from './utils/caches'; | ||
import { recordIdentifierFor } from '@ember-data/store'; | ||
import { assert } from '@ember/debug'; | ||
@@ -31,3 +30,3 @@ /** | ||
if (PROXY_MODEL_CLASS) { | ||
if (CUSTOM_MODEL_CLASS) { | ||
const convertToInt = (prop) => { | ||
@@ -58,3 +57,3 @@ if (typeof prop === 'symbol') return null; | ||
if (index !== null) { | ||
receiver.replaceAt(index, value); | ||
receiver.replace(index, 1, [value]); | ||
} else { | ||
@@ -78,2 +77,4 @@ Reflect.set(target, key, value, receiver); | ||
BaseRecordArray = class BaseRecordArray extends EmberObject.extend(MutableArray) { | ||
[Symbol.iterator] = Array.prototype.values; | ||
// public RecordArray API | ||
@@ -83,7 +84,3 @@ static create(...args) { | ||
if (PROXY_MODEL_CLASS) { | ||
return new Proxy(instance, baseRecordArrayProxyHandler); | ||
} | ||
return instance; | ||
return new Proxy(instance, baseRecordArrayProxyHandler); | ||
} | ||
@@ -226,4 +223,2 @@ | ||
assert('CUSTOM_MODEL_CLASS must be enabled to use PROXY_MODEL_CLASS', !PROXY_MODEL_CLASS); | ||
return instance; | ||
@@ -369,5 +364,14 @@ } | ||
if (PROXY_MODEL_CLASS) { | ||
if (CUSTOM_MODEL_CLASS) { | ||
// Add native array methods here | ||
Object.assign(BaseRecordArray.prototype, { | ||
values: Array.prototype.values, | ||
keys: Array.prototype.keys, | ||
entries: Array.prototype.entries, | ||
copyWithin: Array.prototype.copyWithin, | ||
fill: Array.prototype.fill, | ||
findIndex: Array.prototype.findIndex, | ||
at: Array.prototype.at, | ||
join: Array.prototype.join, | ||
push(...values) { | ||
@@ -393,5 +397,23 @@ return this.pushObjects(values); | ||
some(callback) { | ||
return this.any(callback); | ||
}, | ||
concat(values) { | ||
return this.toArray().concat(...values); | ||
}, | ||
reverse() { | ||
return this.reverseObjects(); | ||
let reversed = this.toArray().reverse(); | ||
this.replace(0, this.length, reversed); | ||
}, | ||
reduceRight(callback, init) { | ||
return this.toArray().reduceRight(callback, init); | ||
}, | ||
sort(callback) { | ||
let sorted = this.toArray().sort(callback); | ||
this.replace(0, this.length, sorted); | ||
}, | ||
}); | ||
@@ -398,0 +420,0 @@ } |
@@ -6,3 +6,3 @@ // This lint error disables "this.attrs" everywhere. What could go wrong? | ||
import { isArray } from '@ember/array'; | ||
import { assert, warn } from '@ember/debug'; | ||
import { assert, warn, deprecate } from '@ember/debug'; | ||
import { readOnly } from '@ember/object/computed'; | ||
@@ -21,3 +21,3 @@ import { recordDataToRecordMap } from './utils/caches'; | ||
import { DEBUG } from '@glimmer/env'; | ||
import { CUSTOM_MODEL_CLASS, PROXY_MODEL_CLASS } from 'ember-m3/-infra/features'; | ||
import { CUSTOM_MODEL_CLASS } from 'ember-m3/-infra/features'; | ||
import { RootState, Errors as StoreErrors } from '@ember-data/store/-private'; | ||
@@ -81,9 +81,5 @@ import { Errors as ModelErrors } from '@ember-data/model/-private'; | ||
let megamorphicModelProxyHandler; | ||
let megamorphicModelProxyHandler, megamorphicNativeDeprecationHandler; | ||
if (PROXY_MODEL_CLASS) { | ||
console.warn( | ||
'You have enabled experimental M3 proxy support. This is a canary feature that is still under development, and can only be used in development mode for testing purposes. It will NOT work in production builds' | ||
); | ||
if (CUSTOM_MODEL_CLASS) { | ||
const MegamorphicModelProxyHandler = class { | ||
@@ -121,3 +117,34 @@ get(target, key, receiver) { | ||
megamorphicModelProxyHandler = new MegamorphicModelProxyHandler(); | ||
if (DEBUG) { | ||
megamorphicModelProxyHandler = new MegamorphicModelProxyHandler(); | ||
const MegamorphicNativeDeprecationProxyHandler = class { | ||
// Need to implement the getter for the Ember Proxy assertions to work | ||
get(target, key) { | ||
return Reflect.get(target, key); | ||
} | ||
set(target, key, value, receiver) { | ||
Reflect.set(target, key, value, receiver); | ||
if (!(key in MegamorphicModel.prototype)) { | ||
deprecate( | ||
`You set the property '${key}' on a '${target._modelName}' with id '${target.id}'. In order to migrate to using native property access for m3 fields, you need to migrate away from setting other values on the model.`, | ||
false, | ||
{ | ||
id: 'm3.model.native-property', | ||
until: '5.0', | ||
for: 'ember-m3', | ||
since: { | ||
available: '4.2.0', | ||
enabled: '4.2.0', | ||
}, | ||
} | ||
); | ||
} | ||
return true; | ||
} | ||
}; | ||
megamorphicNativeDeprecationHandler = new MegamorphicNativeDeprecationProxyHandler(); | ||
} | ||
} | ||
@@ -128,15 +155,28 @@ | ||
let instance = super.create(...args); | ||
let value = instance; | ||
if (CUSTOM_MODEL_CLASS) { | ||
let useNative = instance._schema.useNativeProperties(instance._modelName); | ||
if (PROXY_MODEL_CLASS) { | ||
assert('CUSTOM_MODEL_CLASS must be enabled to use PROXY_MODEL_CLASS', CUSTOM_MODEL_CLASS); | ||
if (useNative === true) { | ||
let proxy = new Proxy(instance, megamorphicModelProxyHandler); | ||
let proxy = new Proxy(instance, megamorphicModelProxyHandler); | ||
// Update the mapping to point to the proxy instead of the instance | ||
recordDataToRecordMap.set(instance._recordData, proxy); | ||
value = proxy; | ||
} | ||
if (DEBUG) { | ||
if (useNative === false) { | ||
let proxy = new Proxy(instance, megamorphicNativeDeprecationHandler); | ||
// Update the mapping to point to the proxy instead of the instance | ||
recordDataToRecordMap.set(instance._recordData, proxy); | ||
return proxy; | ||
// Update the mapping to point to the proxy instead of the instance | ||
recordDataToRecordMap.set(instance._recordData, proxy); | ||
value = proxy; | ||
} | ||
} | ||
} | ||
return instance; | ||
if (!value._topModel) { | ||
value._topModel = value; | ||
} | ||
value._flushInitProperties(); | ||
return value; | ||
} | ||
@@ -155,4 +195,2 @@ | ||
this._invalidRequests = []; | ||
this._errorRequests = []; | ||
this._lastErrorRequest = null; | ||
} | ||
@@ -162,4 +200,2 @@ this._store = properties.store; | ||
this._schema = get(properties.store, '_schemaManager'); | ||
this._topModel = this._topModel || this; | ||
this._parentModel = this._parentModel || null; | ||
@@ -171,4 +207,2 @@ this._errors = null; | ||
} | ||
this._flushInitProperties(); | ||
} | ||
@@ -181,6 +215,5 @@ | ||
if (request.state === 'rejected') { | ||
// TODO filter out queries | ||
this._lastErrorRequest = request; | ||
if (!(request.result && isInvalidError(request.result.error))) { | ||
this._errorRequests.push(request); | ||
if (!(request.response && isInvalidError(request.response.data))) { | ||
this.set('isError', true); | ||
this.set('adapterError', request.response && request.response.data); | ||
} else { | ||
@@ -190,5 +223,5 @@ this._invalidRequests.push(request); | ||
} else if (request.state === 'fulfilled') { | ||
this.set('isError', false); | ||
this.set('adapterError', null); | ||
this._invalidRequests = []; | ||
this._errorRequests = []; | ||
this._lastErrorRequest = null; | ||
} | ||
@@ -294,3 +327,3 @@ this._notifyNetworkChanges(); | ||
// in our subscription to the notificationManager | ||
if (['isNew', 'isDeleted'].indexOf(key) !== -1) { | ||
if (['isNew', 'isDeleted', 'isDirty'].indexOf(key) !== -1) { | ||
super.notifyPropertyChange(key); | ||
@@ -626,4 +659,11 @@ return; | ||
_removeError(key) { | ||
// Skip if `useUnderlyingErrorsValue` returns true meaning we are treating `errors` as an | ||
// attribute from the payload | ||
if (this._schema.useUnderlyingErrorsValue(this._modelName)) { | ||
return; | ||
} | ||
// Remove errors for the property | ||
this.errors.remove(key); | ||
if (CUSTOM_MODEL_CLASS) { | ||
@@ -677,4 +717,2 @@ if (get(this.errors, 'length') === 0) { | ||
MegamorphicModel.prototype._invalidRequests = null; | ||
MegamorphicModel.prototype._errorRequests = null; | ||
MegamorphicModel.prototype._lastErrorRequest = null; | ||
MegamorphicModel.prototype.currentState = null; | ||
@@ -723,3 +761,3 @@ MegamorphicModel.prototype.isError = null; | ||
isDirty = computed('_topModel.isDirty', function () { | ||
if (this._topModel !== this) { | ||
if (this !== this._topModel) { | ||
return this._topModel.get('isDirty'); | ||
@@ -870,2 +908,12 @@ } | ||
if (CUSTOM_MODEL_CLASS) { | ||
defineProperty( | ||
EmbeddedMegamorphicModel.prototype, | ||
'isSaving', | ||
computed('_topModel.isSaving', function () { | ||
return this._topModel.isSaving; | ||
}).readOnly() | ||
); | ||
} | ||
export class EmbeddedSnapshot { | ||
@@ -872,0 +920,0 @@ constructor(record) { |
@@ -5,2 +5,3 @@ import Service, { inject } from '@ember/service'; | ||
import DefaultSchema from './m3-schema'; | ||
import { CUSTOM_MODEL_CLASS } from 'ember-m3/-infra/features'; | ||
let useComputeAttributeCache = new WeakMap(); | ||
@@ -81,2 +82,18 @@ | ||
/* | ||
* Whether we should instantiate a record that has native proxy access | ||
*/ | ||
useNativeProperties(modelName) { | ||
if (this.get('schema').useNativeProperties) { | ||
if (!CUSTOM_MODEL_CLASS) { | ||
throw new Error( | ||
'In order to use nativeProperties you need to be on a version of Ember Data 3.28 or higher' | ||
); | ||
} | ||
return this.get('schema').useNativeProperties(modelName); | ||
} else { | ||
return undefined; | ||
} | ||
} | ||
/** | ||
@@ -83,0 +100,0 @@ * If the model name is a projection over some base type, return that base |
@@ -160,2 +160,3 @@ import Store from '@ember-data/store'; | ||
record.notifyPropertyChange('isDeleted'); | ||
record.notifyPropertyChange('isDirty'); | ||
} else if (value === 'identity') { | ||
@@ -162,0 +163,0 @@ record.notifyPropertyChange('id'); |
@@ -40,3 +40,24 @@ import { dasherize } from '@ember/string'; | ||
if (reference.type) { | ||
return store.peekRecord(dasherize(reference.type), reference.id); | ||
let normalizedType = dasherize(reference.type); | ||
let record = store.peekRecord(normalizedType, reference.id); | ||
if (record) { | ||
return record; | ||
} | ||
// If we have a cached recordData with the same id, but we have not seen a record with the same { type, id } pair | ||
// We could be a projection, in which case we want to push in a projected record with the new type | ||
let cachedRD = store._globalM3RecordDataCache[reference.id]; | ||
if (cachedRD) { | ||
let baseTypeName = dasherize(store._schemaManager.computeBaseModelName(normalizedType)); | ||
// We are a projection | ||
if (baseTypeName) { | ||
// Our projection matches the cached one | ||
if ( | ||
baseTypeName === cachedRD.modelName || | ||
baseTypeName === store._schemaManager.computeBaseModelName(cachedRD.modelName) | ||
) | ||
return store.push({ | ||
data: { type: normalizedType, id: reference.id, attributes: {} }, | ||
}); | ||
} | ||
} | ||
} else { | ||
@@ -43,0 +64,0 @@ let rd = store._globalM3RecordDataCache[reference.id]; |
## v4.2.0 (2021-08-02) | ||
#### :rocket: Enhancement | ||
* [#1238](https://github.com/hjdivad/ember-m3/pull/1238) Add nattive property access for arrays ([@igorT](https://github.com/igorT)) | ||
* [#1232](https://github.com/hjdivad/ember-m3/pull/1232) Add native property access for models ([@igorT](https://github.com/igorT)) | ||
#### :bug: Bug Fix | ||
* [#1241](https://github.com/hjdivad/ember-m3/pull/1241) fix: skip this.errors.remove if useUnderlyingErrorsValue ([@spham92](https://github.com/spham92)) | ||
* [#1243](https://github.com/hjdivad/ember-m3/pull/1243) Fix isError and adapterError with CUSTOM_MODEL_CLASSES ([@igorT](https://github.com/igorT)) | ||
* [#1240](https://github.com/hjdivad/ember-m3/pull/1240) Fix for projected models resolving in projected arrays, when CUSTOM_MODEL_CLASS is on ([@igorT](https://github.com/igorT)) | ||
* [#1249](https://github.com/hjdivad/ember-m3/pull/1249) Fix isSaving for embedded records when CUSTOM MODEL CLASS is on ([@igorT](https://github.com/igorT)) | ||
* [#1242](https://github.com/hjdivad/ember-m3/pull/1242) Fix isDirty for inflight records and set _topModel to the proxy value ([@igorT](https://github.com/igorT)) | ||
#### :memo: Documentation | ||
* [#1252](https://github.com/hjdivad/ember-m3/pull/1252) Add documentation for native property access ([@igorT](https://github.com/igorT)) | ||
#### :house: Internal | ||
* [#1251](https://github.com/hjdivad/ember-m3/pull/1251) Cleanup the invalid errors test ([@igorT](https://github.com/igorT)) | ||
#### Committers: 2 | ||
- Igor Terzic ([@igorT](https://github.com/igorT)) | ||
- Steven Pham ([@spham92](https://github.com/spham92)) | ||
## v4.1.3 (2021-07-27) | ||
@@ -94,2 +119,13 @@ | ||
## v3.0.8 (2021-07-28) | ||
#### :bug: Bug Fix | ||
* [#1131](https://github.com/hjdivad/ember-m3/pull/1131) Ensure we do not add custom babel plugins multiple times ([@rwjblue](https://github.com/rwjblue)) | ||
* [#1121](https://github.com/hjdivad/ember-m3/pull/1121) Avoid invalid imports in production app tree ([@rwjblue](https://github.com/rwjblue)) | ||
* [#1099](https://github.com/hjdivad/ember-m3/pull/1099) Fix state notifications when CUSTOM_MODEL_CLASS is active ([@runspired](https://github.com/runspired)) | ||
#### :house: Internal | ||
* [#1047](https://github.com/hjdivad/ember-m3/pull/1047) Refactor warning capturing and testing. ([@rwjblue](https://github.com/rwjblue)) | ||
## v3.0.7 (2021-03-17) | ||
@@ -96,0 +132,0 @@ |
{ | ||
"name": "ember-m3", | ||
"version": "4.1.3", | ||
"version": "4.2.0", | ||
"isCanary": false, | ||
@@ -76,3 +76,3 @@ "description": "Alternative to @ember-data/model in which attributes and relationships are derived from API Payloads", | ||
"@ember/optional-features": "^2.0.0", | ||
"@ember/test-helpers": "^2.2.8", | ||
"@ember/test-helpers": "^2.2.9", | ||
"@malleatus/nyx": "^0.2.0", | ||
@@ -82,3 +82,3 @@ "@octokit/rest": "^18.6.7", | ||
"broccoli-asset-rev": "^3.0.0", | ||
"ember-cli": "~3.26.1", | ||
"ember-cli": "~3.27.0", | ||
"ember-cli-dependency-checker": "^3.0.0", | ||
@@ -103,3 +103,3 @@ "ember-cli-htmlbars": "^5.7.1", | ||
"ember-try": "^1.4.0", | ||
"eslint": "^7.31.0", | ||
"eslint": "^7.32.0", | ||
"eslint-config-prettier": "^8.3.0", | ||
@@ -113,4 +113,4 @@ "eslint-plugin-ember": "^7.11.1", | ||
"moment": "^2.29.1", | ||
"pretender": "^3.4.5", | ||
"prettier": "^2.3.1", | ||
"pretender": "^3.4.6", | ||
"prettier": "^2.3.2", | ||
"qunit": "^2.16.0", | ||
@@ -117,0 +117,0 @@ "release-it": "^14.10.1", |
@@ -505,6 +505,11 @@ # ember-m3 ![CI](https://github.com/hjdivad/ember-m3/workflows/CI/badge.svg) | ||
- `useUnderlyingErrorsValue(modelName)` Helps the `model.js` determine whether to treat the `errors` attribute | ||
- `useUnderlyingErrorsValue(modelName)` Helps the `model.js` determine whether the `errors` attribute | ||
should be read from the underlying data payload. The default return is false which creates an object compatible with | ||
how Ember Data treats `errors` property. Return true to read from the data payload for the model. | ||
- `useNativeProperties(modelName)` If `true` is returned, removes the need to use `.set` and `.get` on m3 record of a given type. | ||
Instead of `model.get('someAttribute')` and `model.set('someAttribute)`, you can do `model.someAttribute` and | ||
`model.someAttribute = value`. When set to `false` deprecates your current `.` access to aid in the migration. For migration | ||
and deprecation guide see the [deprecations guide](DEPRECATIONS.md). | ||
## Serializer / Adapter | ||
@@ -530,2 +535,6 @@ | ||
## Deprecations | ||
For help with migrating deprecations refer to the [deprecations guide](DEPRECATIONS.md) | ||
## Customizing Store | ||
@@ -532,0 +541,0 @@ |
@@ -39,5 +39,3 @@ /* eslint-env node */ | ||
const M3_FEATURES = { | ||
PROXY_MODEL_CLASS: null, | ||
}; | ||
const M3_FEATURES = {}; | ||
@@ -44,0 +42,0 @@ function getM3Features(isProd) { |
222402
4791
807