@ember-data/model 4.8.0-alpha.3 → 4.8.0-alpha.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,19 +11,6 @@ import { computedMacroWithOptionalParams } from './util';
11
11
  @module @ember-data/model
12
12
  */
13
13
 
14
- function getDefaultValue(record, options, key) {
15
- if (typeof options.defaultValue === 'function') {
16
- return options.defaultValue.apply(null, arguments);
17
- } else {
18
- let defaultValue = options.defaultValue;
19
- assert(
20
- `Non primitive defaultValues are not supported because they are shared between all instances. If you would like to use a complex object as a default value please provide a function that returns the complex object.`,
21
- typeof defaultValue !== 'object' || defaultValue === null
22
- );
23
- return defaultValue;
24
- }
25
- }
26
-
27
14
  /**
28
15
  `attr` defines an attribute on a [Model](/ember-data/release/classes/Model).
29
16
  By default, attributes are passed through as-is, however you can specify an
@@ -123,7 +110,6 @@ function attr(type, options) {
123
110
  let meta = {
124
111
  type: type,
125
112
  isAttribute: true,
126
- kind: 'attribute',
127
113
  options: options,
128
114
  };
129
115
 
@@ -139,20 +125,7 @@ function attr(type, options) {
139
125
  if (this.isDestroyed || this.isDestroying) {
140
126
  return;
141
127
  }
142
- let recordData = recordDataFor(this);
143
- // TODO hasAttr is not spec'd
144
- // essentially this is needed because
145
- // there is a difference between "undefined" meaning never set
146
- // and "undefined" meaning set to "undefined". In the "key present"
147
- // case we want to return undefined. In the "key absent" case
148
- // we want to return getDefaultValue. RecordDataV2 can fix this
149
- // by providing the attributes blob such that we can make our
150
- // own determination.
151
- if (recordData.hasAttr(key)) {
152
- return recordData.getAttr(key);
153
- } else {
154
- return getDefaultValue(this, options, key);
155
- }
128
+ return recordDataFor(this).getAttr(recordIdentifierFor(this), key);
156
129
  },
157
130
  set(key, value) {
158
131
  if (DEBUG) {
@@ -166,10 +139,11 @@ function attr(type, options) {
166
139
  `Attempted to set '${key}' on the deleted record ${recordIdentifierFor(this)}`,
167
140
  !this.currentState.isDeleted
168
141
  );
169
- const recordData = storeFor(this)._instanceCache.getRecordData(recordIdentifierFor(this));
170
- let currentValue = recordData.getAttr(key);
142
+ const identifier = recordIdentifierFor(this);
143
+ const recordData = storeFor(this)._instanceCache.getRecordData(identifier);
144
+ let currentValue = recordData.getAttr(identifier, key);
171
145
  if (currentValue !== value) {
172
- recordData.setDirtyAttribute(key, value);
146
+ recordData.setAttr(identifier, key, value);
173
147
 
174
148
  if (!this.isValid) {
175
149
  const { errors } = this;
@@ -1,10 +1,24 @@
1
- import { assert, inspect, warn } from '@ember/debug';
1
+ import { assert, deprecate, warn } from '@ember/debug';
2
2
  import { computed } from '@ember/object';
3
+ import { dasherize } from '@ember/string';
3
4
  import { DEBUG } from '@glimmer/env';
4
5
 
5
- import { LEGACY_SUPPORT } from './model';
6
+ import {
7
+ DEPRECATE_RELATIONSHIPS_WITHOUT_ASYNC,
8
+ DEPRECATE_RELATIONSHIPS_WITHOUT_INVERSE,
9
+ DEPRECATE_RELATIONSHIPS_WITHOUT_TYPE,
10
+ } from '@ember-data/private-build-infra/deprecations';
11
+
12
+ import { lookupLegacySupport } from './model';
6
13
  import { computedMacroWithOptionalParams } from './util';
7
14
 
15
+ function normalizeType(type) {
16
+ if (DEPRECATE_RELATIONSHIPS_WITHOUT_TYPE && !type) {
17
+ return;
18
+ }
19
+
20
+ return dasherize(type);
21
+ }
8
22
  /**
9
23
  @module @ember-data/model
10
24
  */
@@ -111,29 +125,71 @@ import { computedMacroWithOptionalParams } from './util';
111
125
  @return {Ember.computed} relationship
112
126
  */
113
127
  function belongsTo(modelName, options) {
114
- let opts, userEnteredModelName;
115
- if (typeof modelName === 'object') {
116
- opts = modelName;
117
- userEnteredModelName = undefined;
118
- } else {
119
- opts = options;
120
- userEnteredModelName = modelName;
128
+ let opts = options;
129
+ let userEnteredModelName = modelName;
130
+ if (DEPRECATE_RELATIONSHIPS_WITHOUT_TYPE && (typeof modelName !== 'string' || !modelName.length)) {
131
+ deprecate('belongsTo() must specify the string type of the related resource as the first parameter', false, {
132
+ id: 'ember-data:deprecate-non-strict-relationships',
133
+ for: 'ember-data',
134
+ until: '5.0',
135
+ since: { enabled: '4.8', available: '4.8' },
136
+ });
137
+
138
+ if (typeof modelName === 'object') {
139
+ opts = modelName;
140
+ userEnteredModelName = undefined;
141
+ } else {
142
+ opts = options;
143
+ userEnteredModelName = modelName;
144
+ }
145
+
146
+ assert(
147
+ 'The first argument to belongsTo must be a string representing a model type key, not an instance of ' +
148
+ typeof userEnteredModelName +
149
+ ". E.g., to define a relation to the Person model, use belongsTo('person')",
150
+ typeof userEnteredModelName === 'string' || typeof userEnteredModelName === 'undefined'
151
+ );
121
152
  }
122
153
 
123
- assert(
124
- 'The first argument to belongsTo must be a string representing a model type key, not an instance of ' +
125
- inspect(userEnteredModelName) +
126
- ". E.g., to define a relation to the Person model, use belongsTo('person')",
127
- typeof userEnteredModelName === 'string' || typeof userEnteredModelName === 'undefined'
128
- );
154
+ if (DEPRECATE_RELATIONSHIPS_WITHOUT_ASYNC && (!opts || typeof opts.async !== 'boolean')) {
155
+ opts = opts || {};
156
+ if (!('async' in opts)) {
157
+ opts.async = true;
158
+ }
159
+ deprecate('belongsTo(<type>, <options>) must specify options.async as either `true` or `false`.', false, {
160
+ id: 'ember-data:deprecate-non-strict-relationships',
161
+ for: 'ember-data',
162
+ until: '5.0',
163
+ since: { enabled: '4.8', available: '4.8' },
164
+ });
165
+ } else {
166
+ assert(`Expected belongsTo options.async to be a boolean`, opts && typeof opts.async === 'boolean');
167
+ }
129
168
 
130
- opts = opts || {};
131
- if (!('async' in opts)) {
132
- opts.async = true;
169
+ if (
170
+ DEPRECATE_RELATIONSHIPS_WITHOUT_INVERSE &&
171
+ opts.inverse !== null &&
172
+ (typeof opts.inverse !== 'string' || opts.inverse.length === 0)
173
+ ) {
174
+ deprecate(
175
+ 'belongsTo(<type>, <options>) must specify options.inverse as either `null` or string type of the related resource.',
176
+ false,
177
+ {
178
+ id: 'ember-data:deprecate-non-strict-relationships',
179
+ for: 'ember-data',
180
+ until: '5.0',
181
+ since: { enabled: '4.8', available: '4.8' },
182
+ }
183
+ );
184
+ } else {
185
+ assert(
186
+ `Expected belongsTo options.inverse to be either null or the string type of the related resource.`,
187
+ opts.inverse === null || (typeof opts.inverse === 'string' && opts.inverse.length > 0)
188
+ );
133
189
  }
134
190
 
135
191
  let meta = {
136
- type: userEnteredModelName,
192
+ type: normalizeType(userEnteredModelName),
137
193
  isRelationship: true,
138
194
  options: opts,
139
195
  kind: 'belongsTo',
@@ -149,7 +205,7 @@ function belongsTo(modelName, options) {
149
205
  if (this.isDestroying || this.isDestroyed) {
150
206
  return null;
151
207
  }
152
- const support = LEGACY_SUPPORT.lookup(this);
208
+ const support = lookupLegacySupport(this);
153
209
 
154
210
  if (DEBUG) {
155
211
  if (['currentState'].indexOf(key) !== -1) {
@@ -181,7 +237,7 @@ function belongsTo(modelName, options) {
181
237
  return support.getBelongsTo(key);
182
238
  },
183
239
  set(key, value) {
184
- const support = LEGACY_SUPPORT.lookup(this);
240
+ const support = lookupLegacySupport(this);
185
241
  if (DEBUG) {
186
242
  if (['currentState'].indexOf(key) !== -1) {
187
243
  throw new Error(
@@ -189,7 +245,7 @@ function belongsTo(modelName, options) {
189
245
  );
190
246
  }
191
247
  }
192
- this.store._backburner.join(() => {
248
+ this.store._join(() => {
193
249
  support.setDirtyBelongsTo(key, value);
194
250
  });
195
251
 
@@ -0,0 +1,54 @@
1
+ import { deprecate } from '@ember/debug';
2
+
3
+ import { resolve } from 'rsvp';
4
+
5
+ import { PromiseObject } from './promise-proxy-base';
6
+
7
+ function promiseObject<T>(promise: Promise<T>): PromiseObject<T> {
8
+ return PromiseObject.create({
9
+ promise: resolve(promise),
10
+ }) as PromiseObject<T>;
11
+ }
12
+
13
+ // constructor is accessed in some internals but not including it in the copyright for the deprecation
14
+ const ALLOWABLE_METHODS = ['constructor', 'then', 'catch', 'finally'];
15
+ const PROXIED_OBJECT_PROPS = ['content', 'isPending', 'isSettled', 'isRejected', 'isFulfilled', 'promise', 'reason'];
16
+
17
+ export function deprecatedPromiseObject<T>(promise: Promise<T>): PromiseObject<T> {
18
+ const promiseObjectProxy: PromiseObject<T> = promiseObject(promise);
19
+ const handler = {
20
+ get(target: object, prop: string, receiver?: object): unknown {
21
+ if (typeof prop === 'symbol') {
22
+ return Reflect.get(target, prop, receiver);
23
+ }
24
+ if (!ALLOWABLE_METHODS.includes(prop)) {
25
+ deprecate(
26
+ `Accessing ${prop} is deprecated. The return type is being changed fomr PromiseObjectProxy to a Promise. The only available methods to access on this promise are .then, .catch and .finally`,
27
+ false,
28
+ {
29
+ id: 'ember-data:model-save-promise',
30
+ until: '5.0',
31
+ for: '@ember-data/store',
32
+ since: {
33
+ available: '4.4',
34
+ enabled: '4.4',
35
+ },
36
+ }
37
+ );
38
+ }
39
+
40
+ const value: unknown = target[prop];
41
+ if (value && typeof value === 'function' && typeof value.bind === 'function') {
42
+ return value.bind(target);
43
+ }
44
+
45
+ if (PROXIED_OBJECT_PROPS.includes(prop)) {
46
+ return value;
47
+ }
48
+
49
+ return undefined;
50
+ },
51
+ };
52
+
53
+ return new Proxy(promiseObjectProxy, handler);
54
+ }
@@ -2,13 +2,30 @@
2
2
  @module @ember-data/model
3
3
  */
4
4
  import { A } from '@ember/array';
5
- import { assert, inspect } from '@ember/debug';
5
+ import { assert, deprecate, inspect } from '@ember/debug';
6
6
  import { computed } from '@ember/object';
7
+ import { dasherize } from '@ember/string';
7
8
  import { DEBUG } from '@glimmer/env';
8
9
 
9
- import { LEGACY_SUPPORT } from './model';
10
+ import { singularize } from 'ember-inflector';
11
+
12
+ import {
13
+ DEPRECATE_RELATIONSHIPS_WITHOUT_ASYNC,
14
+ DEPRECATE_RELATIONSHIPS_WITHOUT_INVERSE,
15
+ DEPRECATE_RELATIONSHIPS_WITHOUT_TYPE,
16
+ } from '@ember-data/private-build-infra/deprecations';
17
+
18
+ import { lookupLegacySupport } from './model';
10
19
  import { computedMacroWithOptionalParams } from './util';
11
20
 
21
+ function normalizeType(type) {
22
+ if (DEPRECATE_RELATIONSHIPS_WITHOUT_TYPE && !type) {
23
+ return;
24
+ }
25
+
26
+ return singularize(dasherize(type));
27
+ }
28
+
12
29
  /**
13
30
  `hasMany` is used to define One-To-Many and Many-To-Many
14
31
  relationships on a [Model](/ember-data/release/classes/Model).
@@ -151,21 +168,60 @@ import { computedMacroWithOptionalParams } from './util';
151
168
  @return {Ember.computed} relationship
152
169
  */
153
170
  function hasMany(type, options) {
154
- if (typeof type === 'object') {
155
- options = type;
156
- type = undefined;
171
+ if (DEPRECATE_RELATIONSHIPS_WITHOUT_TYPE && (typeof type !== 'string' || !type.length)) {
172
+ deprecate(
173
+ 'hasMany(<type>, <options>) must specify the string type of the related resource as the first parameter',
174
+ false,
175
+ {
176
+ id: 'ember-data:deprecate-non-strict-relationships',
177
+ for: 'ember-data',
178
+ until: '5.0',
179
+ since: { enabled: '4.8', available: '4.8' },
180
+ }
181
+ );
182
+ if (typeof type === 'object') {
183
+ options = type;
184
+ type = undefined;
185
+ }
186
+
187
+ assert(
188
+ `The first argument to hasMany must be a string representing a model type key, not an instance of ${inspect(
189
+ type
190
+ )}. E.g., to define a relation to the Comment model, use hasMany('comment')`,
191
+ typeof type === 'string' || typeof type === 'undefined'
192
+ );
157
193
  }
158
194
 
159
- assert(
160
- `The first argument to hasMany must be a string representing a model type key, not an instance of ${inspect(
161
- type
162
- )}. E.g., to define a relation to the Comment model, use hasMany('comment')`,
163
- typeof type === 'string' || typeof type === 'undefined'
164
- );
195
+ if (DEPRECATE_RELATIONSHIPS_WITHOUT_ASYNC && (!options || typeof options.async !== 'boolean')) {
196
+ options = options || {};
197
+ if (!('async' in options)) {
198
+ options.async = true;
199
+ }
200
+ deprecate('hasMany(<type>, <options>) must specify options.async as either `true` or `false`.', false, {
201
+ id: 'ember-data:deprecate-non-strict-relationships',
202
+ for: 'ember-data',
203
+ until: '5.0',
204
+ since: { enabled: '4.8', available: '4.8' },
205
+ });
206
+ } else {
207
+ assert(`Expected hasMany options.async to be a boolean`, options && typeof options.async === 'boolean');
208
+ }
165
209
 
166
- options = options || {};
167
- if (!('async' in options)) {
168
- options.async = true;
210
+ if (
211
+ DEPRECATE_RELATIONSHIPS_WITHOUT_INVERSE &&
212
+ options.inverse !== null &&
213
+ (typeof options.inverse !== 'string' || options.inverse.length === 0)
214
+ ) {
215
+ deprecate(
216
+ 'hasMany(<type>, <options>) must specify options.inverse as either `null` or string type of the related resource.',
217
+ false,
218
+ {
219
+ id: 'ember-data:deprecate-non-strict-relationships',
220
+ for: 'ember-data',
221
+ until: '5.0',
222
+ since: { enabled: '4.8', available: '4.8' },
223
+ }
224
+ );
169
225
  }
170
226
 
171
227
  // Metadata about relationships is stored on the meta of
@@ -173,7 +229,7 @@ function hasMany(type, options) {
173
229
  // serialization. Note that `key` is populated lazily
174
230
  // the first time the CP is called.
175
231
  let meta = {
176
- type,
232
+ type: normalizeType(type),
177
233
  options,
178
234
  isRelationship: true,
179
235
  kind: 'hasMany',
@@ -193,7 +249,7 @@ function hasMany(type, options) {
193
249
  if (this.isDestroying || this.isDestroyed) {
194
250
  return A();
195
251
  }
196
- return LEGACY_SUPPORT.lookup(this).getHasMany(key);
252
+ return lookupLegacySupport(this).getHasMany(key);
197
253
  },
198
254
  set(key, records) {
199
255
  if (DEBUG) {
@@ -203,9 +259,11 @@ function hasMany(type, options) {
203
259
  );
204
260
  }
205
261
  }
206
- const support = LEGACY_SUPPORT.lookup(this);
207
- this.store._backburner.join(() => {
208
- support.setDirtyHasMany(key, records);
262
+ const support = lookupLegacySupport(this);
263
+ const manyArray = support.getManyArray(key);
264
+ assert(`You must pass an array of records to set a hasMany relationship`, Array.isArray(records));
265
+ this.store._join(() => {
266
+ manyArray.splice(0, manyArray.length, ...records);
209
267
  });
210
268
 
211
269
  return support.getHasMany(key);
@@ -3,7 +3,10 @@ import { DEBUG } from '@glimmer/env';
3
3
 
4
4
  import { resolve } from 'rsvp';
5
5
 
6
- import { DEPRECATE_RSVP_PROMISE } from '@ember-data/private-build-infra/deprecations';
6
+ import {
7
+ DEPRECATE_RELATIONSHIPS_WITHOUT_INVERSE,
8
+ DEPRECATE_RSVP_PROMISE,
9
+ } from '@ember-data/private-build-infra/deprecations';
7
10
 
8
11
  import { iterateData, normalizeResponseHelper } from './legacy-data-utils';
9
12
 
@@ -231,19 +234,36 @@ function ensureRelationshipIsSetToParent(payload, parentIdentifier, store, paren
231
234
  }
232
235
  }
233
236
 
234
- function getInverse(store, parentIdentifier, parentRelationship, type) {
235
- return recordDataFindInverseRelationshipInfo(store, parentIdentifier, parentRelationship, type);
237
+ function metaIsRelationshipDefinition(meta) {
238
+ return typeof meta._inverseKey === 'function';
236
239
  }
237
240
 
238
- function recordDataFindInverseRelationshipInfo(store, parentIdentifier, parentRelationship, type) {
241
+ function inverseForRelationship(store, identifier, key) {
242
+ const definition = store.getSchemaDefinitionService().relationshipsDefinitionFor(identifier)[key];
243
+ if (!definition) {
244
+ return null;
245
+ }
246
+
247
+ if (DEPRECATE_RELATIONSHIPS_WITHOUT_INVERSE && metaIsRelationshipDefinition(definition)) {
248
+ const modelClass = store.modelFor(identifier.type);
249
+ return definition._inverseKey(store, modelClass);
250
+ }
251
+ assert(
252
+ `Expected the relationship defintion to specify the inverse type or null.`,
253
+ definition.options?.inverse === null ||
254
+ (typeof definition.options?.inverse === 'string' && definition.options.inverse.length > 0)
255
+ );
256
+ return definition.options.inverse;
257
+ }
258
+
259
+ function getInverse(store, parentIdentifier, parentRelationship, type) {
239
260
  let { name: lhs_relationshipName } = parentRelationship;
240
261
  let { type: parentType } = parentIdentifier;
241
- let inverseKey = store._instanceCache._storeWrapper.inverseForRelationship(parentType, lhs_relationshipName);
262
+ let inverseKey = inverseForRelationship(store, { type: parentType }, lhs_relationshipName);
242
263
 
243
264
  if (inverseKey) {
244
- let {
245
- meta: { kind },
246
- } = store._instanceCache._storeWrapper.relationshipsDefinitionFor(type)[inverseKey];
265
+ const definition = store.getSchemaDefinitionService().relationshipsDefinitionFor({ type });
266
+ let { kind } = definition[inverseKey];
247
267
  return {
248
268
  inverseKey,
249
269
  kind,