@ember-data/model 4.11.0 → 4.12.0-alpha.0
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.
- package/addon/{-private/diff-array.ts → -private.js} +41 -13
- package/addon/-private.js.map +1 -0
- package/addon/has-many-a275fe0d.js +6283 -0
- package/addon/has-many-a275fe0d.js.map +1 -0
- package/addon/index.js +1 -0
- package/addon/index.js.map +1 -0
- package/addon-main.js +90 -0
- package/package.json +43 -15
- package/addon/-private/attr.js +0 -162
- package/addon/-private/belongs-to.js +0 -268
- package/addon/-private/debug/assert-polymorphic-type.js +0 -71
- package/addon/-private/deprecated-promise-proxy.ts +0 -76
- package/addon/-private/errors.ts +0 -425
- package/addon/-private/has-many.js +0 -280
- package/addon/-private/index.ts +0 -14
- package/addon/-private/legacy-data-fetch.js +0 -395
- package/addon/-private/legacy-data-utils.ts +0 -92
- package/addon/-private/legacy-relationships-support.ts +0 -774
- package/addon/-private/many-array.ts +0 -401
- package/addon/-private/model-for-mixin.ts +0 -38
- package/addon/-private/model.js +0 -2516
- package/addon/-private/notify-changes.ts +0 -72
- package/addon/-private/promise-belongs-to.ts +0 -73
- package/addon/-private/promise-many-array.ts +0 -425
- package/addon/-private/promise-proxy-base.js +0 -4
- package/addon/-private/record-state.ts +0 -468
- package/addon/-private/references/belongs-to.ts +0 -624
- package/addon/-private/references/has-many.ts +0 -669
- package/addon/-private/relationship-meta.ts +0 -98
- package/addon/-private/util.ts +0 -31
- package/addon/index.ts +0 -39
- package/blueprints/model/HELP.md +0 -26
- package/blueprints/model/files/__root__/__path__/__name__.js +0 -5
- package/blueprints/model/index.js +0 -158
- package/blueprints/model/native-files/__root__/__path__/__name__.js +0 -5
- package/blueprints/model-test/index.js +0 -33
- package/blueprints/model-test/mocha-files/__root__/__path__/__test__.js +0 -18
- package/blueprints/model-test/mocha-rfc-232-files/__root__/__path__/__test__.js +0 -15
- package/blueprints/model-test/qunit-files/__root__/__path__/__test__.js +0 -14
- package/index.js +0 -46
|
@@ -1,468 +0,0 @@
|
|
|
1
|
-
import { assert } from '@ember/debug';
|
|
2
|
-
import { dependentKeyCompat } from '@ember/object/compat';
|
|
3
|
-
import { DEBUG } from '@glimmer/env';
|
|
4
|
-
import { cached, tracked } from '@glimmer/tracking';
|
|
5
|
-
|
|
6
|
-
import type Store from '@ember-data/store';
|
|
7
|
-
import { storeFor } from '@ember-data/store';
|
|
8
|
-
import { recordIdentifierFor } from '@ember-data/store/-private';
|
|
9
|
-
import type { NotificationType } from '@ember-data/store/-private/managers/record-notification-manager';
|
|
10
|
-
import type RequestCache from '@ember-data/store/-private/network/request-cache';
|
|
11
|
-
import { addToTransaction, subscribe } from '@ember-data/tracking/-private';
|
|
12
|
-
import type { StableRecordIdentifier } from '@ember-data/types/q/identifier';
|
|
13
|
-
import type { RecordData } from '@ember-data/types/q/record-data';
|
|
14
|
-
|
|
15
|
-
type Model = InstanceType<typeof import('./model')>;
|
|
16
|
-
|
|
17
|
-
const SOURCE_POINTER_REGEXP = /^\/?data\/(attributes|relationships)\/(.*)/;
|
|
18
|
-
const SOURCE_POINTER_PRIMARY_REGEXP = /^\/?data/;
|
|
19
|
-
const PRIMARY_ATTRIBUTE_KEY = 'base';
|
|
20
|
-
function isInvalidError(error) {
|
|
21
|
-
return error && error.isAdapterError === true && error.code === 'InvalidError';
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Tag provides a cache for a getter
|
|
26
|
-
* that recomputes only when a specific
|
|
27
|
-
* tracked property that it manages is dirtied.
|
|
28
|
-
*
|
|
29
|
-
* This allows us to bust the cache for a value
|
|
30
|
-
* that otherwise doesn't access anything tracked
|
|
31
|
-
* as well as control the timing of that notification.
|
|
32
|
-
*
|
|
33
|
-
* @internal
|
|
34
|
-
*/
|
|
35
|
-
class Tag {
|
|
36
|
-
declare rev: number;
|
|
37
|
-
declare isDirty: boolean;
|
|
38
|
-
declare value: any;
|
|
39
|
-
declare t: boolean;
|
|
40
|
-
|
|
41
|
-
constructor() {
|
|
42
|
-
this.rev = 1;
|
|
43
|
-
this.isDirty = true;
|
|
44
|
-
this.value = undefined;
|
|
45
|
-
this.t = false;
|
|
46
|
-
}
|
|
47
|
-
@tracked ref = null;
|
|
48
|
-
|
|
49
|
-
notify() {
|
|
50
|
-
this.isDirty = true;
|
|
51
|
-
addToTransaction(this);
|
|
52
|
-
this.rev++;
|
|
53
|
-
}
|
|
54
|
-
consume(v) {
|
|
55
|
-
this.isDirty = false;
|
|
56
|
-
this.value = v; // set cached value
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const Tags = new WeakMap();
|
|
61
|
-
function getTag(record, key) {
|
|
62
|
-
let tags = Tags.get(record);
|
|
63
|
-
if (!tags) {
|
|
64
|
-
tags = Object.create(null);
|
|
65
|
-
Tags.set(record, tags);
|
|
66
|
-
}
|
|
67
|
-
return (tags[key] = tags[key] || new Tag());
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export function peekTag(record, key) {
|
|
71
|
-
let tags = Tags.get(record);
|
|
72
|
-
return tags && tags[key];
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* A decorattor that caches a getter while
|
|
77
|
-
* providing the ability to bust that cache
|
|
78
|
-
* when we so choose in a way that notifies
|
|
79
|
-
* glimmer's tracking system.
|
|
80
|
-
*
|
|
81
|
-
* @internal
|
|
82
|
-
*/
|
|
83
|
-
export function tagged(_target, key, desc) {
|
|
84
|
-
const getter = desc.get;
|
|
85
|
-
const setter = desc.set;
|
|
86
|
-
desc.get = function () {
|
|
87
|
-
let tag = getTag(this, key);
|
|
88
|
-
subscribe(tag);
|
|
89
|
-
|
|
90
|
-
if (tag.isDirty) {
|
|
91
|
-
tag.consume(getter.call(this));
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return tag.value;
|
|
95
|
-
};
|
|
96
|
-
desc.set = function (v) {
|
|
97
|
-
getTag(this, key); // ensure tag is setup in case we want to use it.
|
|
98
|
-
// probably notify here but not yet.
|
|
99
|
-
setter.call(this, v);
|
|
100
|
-
};
|
|
101
|
-
dependentKeyCompat(desc);
|
|
102
|
-
return desc;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
Historically EmberData managed a state machine
|
|
107
|
-
for each record, the localState for which
|
|
108
|
-
was reflected onto Model.
|
|
109
|
-
|
|
110
|
-
This implements the flags and stateName for backwards compat
|
|
111
|
-
with the state tree that used to be possible (listed below).
|
|
112
|
-
|
|
113
|
-
stateName and dirtyType are candidates for deprecation.
|
|
114
|
-
|
|
115
|
-
root
|
|
116
|
-
empty
|
|
117
|
-
deleted // hidden from stateName
|
|
118
|
-
preloaded // hidden from stateName
|
|
119
|
-
|
|
120
|
-
loading
|
|
121
|
-
empty // hidden from stateName
|
|
122
|
-
preloaded // hidden from stateName
|
|
123
|
-
|
|
124
|
-
loaded
|
|
125
|
-
saved
|
|
126
|
-
updated
|
|
127
|
-
uncommitted
|
|
128
|
-
invalid
|
|
129
|
-
inFlight
|
|
130
|
-
created
|
|
131
|
-
uncommitted
|
|
132
|
-
invalid
|
|
133
|
-
inFlight
|
|
134
|
-
|
|
135
|
-
deleted
|
|
136
|
-
saved
|
|
137
|
-
new // hidden from stateName
|
|
138
|
-
uncommitted
|
|
139
|
-
invalid
|
|
140
|
-
inFlight
|
|
141
|
-
|
|
142
|
-
@internal
|
|
143
|
-
*/
|
|
144
|
-
export default class RecordState {
|
|
145
|
-
declare store: Store;
|
|
146
|
-
declare identifier: StableRecordIdentifier;
|
|
147
|
-
declare record: Model;
|
|
148
|
-
declare rs: RequestCache;
|
|
149
|
-
|
|
150
|
-
declare pendingCount: number;
|
|
151
|
-
declare fulfilledCount: number;
|
|
152
|
-
declare rejectedCount: number;
|
|
153
|
-
declare recordData: RecordData;
|
|
154
|
-
declare _errorRequests: any[];
|
|
155
|
-
declare _lastError: any;
|
|
156
|
-
declare handler: object;
|
|
157
|
-
|
|
158
|
-
constructor(record: Model) {
|
|
159
|
-
const store = storeFor(record)!;
|
|
160
|
-
const identity = recordIdentifierFor(record);
|
|
161
|
-
|
|
162
|
-
this.identifier = identity;
|
|
163
|
-
this.record = record;
|
|
164
|
-
this.recordData = store._instanceCache.getRecordData(identity);
|
|
165
|
-
|
|
166
|
-
this.pendingCount = 0;
|
|
167
|
-
this.fulfilledCount = 0;
|
|
168
|
-
this.rejectedCount = 0;
|
|
169
|
-
this._errorRequests = [];
|
|
170
|
-
this._lastError = null;
|
|
171
|
-
|
|
172
|
-
let requests = store.getRequestStateService();
|
|
173
|
-
let notifications = store._notificationManager;
|
|
174
|
-
|
|
175
|
-
const handleRequest = (req) => {
|
|
176
|
-
if (req.type === 'mutation') {
|
|
177
|
-
switch (req.state) {
|
|
178
|
-
case 'pending':
|
|
179
|
-
this.isSaving = true;
|
|
180
|
-
break;
|
|
181
|
-
case 'rejected':
|
|
182
|
-
this.isSaving = false;
|
|
183
|
-
this._lastError = req;
|
|
184
|
-
if (!(req.response && isInvalidError(req.response.data))) {
|
|
185
|
-
this._errorRequests.push(req);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
notifyErrorsStateChanged(this);
|
|
189
|
-
break;
|
|
190
|
-
case 'fulfilled':
|
|
191
|
-
this._errorRequests = [];
|
|
192
|
-
this._lastError = null;
|
|
193
|
-
this.isSaving = false;
|
|
194
|
-
notifyErrorsStateChanged(this);
|
|
195
|
-
break;
|
|
196
|
-
}
|
|
197
|
-
} else {
|
|
198
|
-
switch (req.state) {
|
|
199
|
-
case 'pending':
|
|
200
|
-
this.pendingCount++;
|
|
201
|
-
this.notify('isLoading');
|
|
202
|
-
break;
|
|
203
|
-
case 'rejected':
|
|
204
|
-
this.pendingCount--;
|
|
205
|
-
this._lastError = req;
|
|
206
|
-
if (!(req.response && isInvalidError(req.response.data))) {
|
|
207
|
-
this._errorRequests.push(req);
|
|
208
|
-
}
|
|
209
|
-
this.notify('isLoading');
|
|
210
|
-
notifyErrorsStateChanged(this);
|
|
211
|
-
break;
|
|
212
|
-
case 'fulfilled':
|
|
213
|
-
this.pendingCount--;
|
|
214
|
-
this.fulfilledCount++;
|
|
215
|
-
this.notify('isLoading');
|
|
216
|
-
this.notify('isDirty');
|
|
217
|
-
notifyErrorsStateChanged(this);
|
|
218
|
-
this._errorRequests = [];
|
|
219
|
-
this._lastError = null;
|
|
220
|
-
break;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
};
|
|
224
|
-
|
|
225
|
-
requests.subscribeForRecord(identity, handleRequest);
|
|
226
|
-
|
|
227
|
-
// we instantiate lazily
|
|
228
|
-
// so we grab anything we don't have yet
|
|
229
|
-
if (!DEBUG) {
|
|
230
|
-
const lastRequest = requests.getLastRequestForRecord(identity);
|
|
231
|
-
if (lastRequest) {
|
|
232
|
-
handleRequest(lastRequest);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
this.handler = notifications.subscribe(
|
|
237
|
-
identity,
|
|
238
|
-
(identifier: StableRecordIdentifier, type: NotificationType, key?: string) => {
|
|
239
|
-
switch (type) {
|
|
240
|
-
case 'state':
|
|
241
|
-
this.notify('isNew');
|
|
242
|
-
this.notify('isDeleted');
|
|
243
|
-
this.notify('isDirty');
|
|
244
|
-
break;
|
|
245
|
-
case 'attributes':
|
|
246
|
-
this.notify('isEmpty');
|
|
247
|
-
this.notify('isDirty');
|
|
248
|
-
break;
|
|
249
|
-
case 'errors':
|
|
250
|
-
this.updateInvalidErrors(this.record.errors);
|
|
251
|
-
this.notify('isValid');
|
|
252
|
-
break;
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
destroy() {
|
|
259
|
-
storeFor(this.record)!._notificationManager.unsubscribe(this.handler);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
notify(key) {
|
|
263
|
-
getTag(this, key).notify();
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
updateInvalidErrors(errors) {
|
|
267
|
-
assert(
|
|
268
|
-
`Expected the RecordData instance for ${this.identifier} to implement getErrors(identifier)`,
|
|
269
|
-
typeof this.recordData.getErrors === 'function'
|
|
270
|
-
);
|
|
271
|
-
let jsonApiErrors = this.recordData.getErrors(this.identifier);
|
|
272
|
-
|
|
273
|
-
errors.clear();
|
|
274
|
-
|
|
275
|
-
for (let i = 0; i < jsonApiErrors.length; i++) {
|
|
276
|
-
let error = jsonApiErrors[i];
|
|
277
|
-
|
|
278
|
-
if (error.source && error.source.pointer) {
|
|
279
|
-
let keyMatch = error.source.pointer.match(SOURCE_POINTER_REGEXP);
|
|
280
|
-
let key: string | undefined;
|
|
281
|
-
|
|
282
|
-
if (keyMatch) {
|
|
283
|
-
key = keyMatch[2];
|
|
284
|
-
} else if (error.source.pointer.search(SOURCE_POINTER_PRIMARY_REGEXP) !== -1) {
|
|
285
|
-
key = PRIMARY_ATTRIBUTE_KEY;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
if (key) {
|
|
289
|
-
let errMsg = error.detail || error.title;
|
|
290
|
-
errors.add(key, errMsg);
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
cleanErrorRequests() {
|
|
297
|
-
this.notify('isValid');
|
|
298
|
-
this.notify('isError');
|
|
299
|
-
this.notify('adapterError');
|
|
300
|
-
this._errorRequests = [];
|
|
301
|
-
this._lastError = null;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
@tracked isSaving = false;
|
|
305
|
-
|
|
306
|
-
@tagged
|
|
307
|
-
get isLoading() {
|
|
308
|
-
return !this.isLoaded && this.pendingCount > 0 && this.fulfilledCount === 0;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
@tagged
|
|
312
|
-
get isLoaded() {
|
|
313
|
-
if (this.isNew) {
|
|
314
|
-
return true;
|
|
315
|
-
}
|
|
316
|
-
return this.fulfilledCount > 0 || !this.isEmpty;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
@tagged
|
|
320
|
-
get isSaved() {
|
|
321
|
-
let rd = this.recordData;
|
|
322
|
-
if (this.isDeleted) {
|
|
323
|
-
assert(`Expected RecordData to implement isDeletionCommitted()`, rd.isDeletionCommitted);
|
|
324
|
-
return rd.isDeletionCommitted(this.identifier);
|
|
325
|
-
}
|
|
326
|
-
if (this.isNew || this.isEmpty || !this.isValid || this.isDirty || this.isLoading) {
|
|
327
|
-
return false;
|
|
328
|
-
}
|
|
329
|
-
return true;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
@tagged
|
|
333
|
-
get isEmpty() {
|
|
334
|
-
let rd = this.recordData;
|
|
335
|
-
// TODO this is not actually an RFC'd concept. Determine the
|
|
336
|
-
// correct heuristic to replace this with.
|
|
337
|
-
assert(`Expected RecordData to implement isEmpty()`, rd.isEmpty);
|
|
338
|
-
return !this.isNew && rd.isEmpty(this.identifier);
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
@tagged
|
|
342
|
-
get isNew() {
|
|
343
|
-
let rd = this.recordData;
|
|
344
|
-
assert(`Expected RecordData to implement isNew()`, rd.isNew);
|
|
345
|
-
return rd.isNew(this.identifier);
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
@tagged
|
|
349
|
-
get isDeleted() {
|
|
350
|
-
let rd = this.recordData;
|
|
351
|
-
assert(`Expected RecordData to implement isDeleted()`, rd.isDeleted);
|
|
352
|
-
return rd.isDeleted(this.identifier);
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
@tagged
|
|
356
|
-
get isValid() {
|
|
357
|
-
return this.record.errors.length === 0;
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
@tagged
|
|
361
|
-
get isDirty() {
|
|
362
|
-
let rd = this.recordData;
|
|
363
|
-
if (rd.isDeletionCommitted(this.identifier) || (this.isDeleted && this.isNew)) {
|
|
364
|
-
return false;
|
|
365
|
-
}
|
|
366
|
-
return this.isNew || rd.hasChangedAttrs(this.identifier);
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
@tagged
|
|
370
|
-
get isError() {
|
|
371
|
-
let errorReq = this._errorRequests[this._errorRequests.length - 1];
|
|
372
|
-
if (!errorReq) {
|
|
373
|
-
return false;
|
|
374
|
-
} else {
|
|
375
|
-
return true;
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
@tagged
|
|
380
|
-
get adapterError() {
|
|
381
|
-
let request = this._lastError;
|
|
382
|
-
if (!request) {
|
|
383
|
-
return null;
|
|
384
|
-
}
|
|
385
|
-
return request.state === 'rejected' && request.response.data;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
@cached
|
|
389
|
-
get isPreloaded() {
|
|
390
|
-
return !this.isEmpty && this.isLoading;
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
@cached
|
|
394
|
-
get stateName() {
|
|
395
|
-
// we might be empty while loading so check this first
|
|
396
|
-
if (this.isLoading) {
|
|
397
|
-
return 'root.loading';
|
|
398
|
-
|
|
399
|
-
// got nothing yet or were unloaded
|
|
400
|
-
} else if (this.isEmpty) {
|
|
401
|
-
return 'root.empty';
|
|
402
|
-
|
|
403
|
-
// deleted substates
|
|
404
|
-
} else if (this.isDeleted) {
|
|
405
|
-
if (this.isSaving) {
|
|
406
|
-
return 'root.deleted.inFlight';
|
|
407
|
-
} else if (this.isSaved) {
|
|
408
|
-
// TODO ensure isSaved isn't true from previous requests
|
|
409
|
-
return 'root.deleted.saved';
|
|
410
|
-
} else if (!this.isValid) {
|
|
411
|
-
return 'root.deleted.invalid';
|
|
412
|
-
} else {
|
|
413
|
-
return 'root.deleted.uncommitted';
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
// loaded.created substates
|
|
417
|
-
} else if (this.isNew) {
|
|
418
|
-
if (this.isSaving) {
|
|
419
|
-
return 'root.loaded.created.inFlight';
|
|
420
|
-
} else if (!this.isValid) {
|
|
421
|
-
return 'root.loaded.created.invalid';
|
|
422
|
-
}
|
|
423
|
-
return 'root.loaded.created.uncommitted';
|
|
424
|
-
|
|
425
|
-
// loaded.updated substates
|
|
426
|
-
} else if (this.isSaving) {
|
|
427
|
-
return 'root.loaded.updated.inFlight';
|
|
428
|
-
} else if (!this.isValid) {
|
|
429
|
-
return 'root.loaded.updated.invalid';
|
|
430
|
-
} else if (this.isDirty) {
|
|
431
|
-
return 'root.loaded.updated.uncommitted';
|
|
432
|
-
|
|
433
|
-
// if nothing remains, we are loaded saved!
|
|
434
|
-
} else {
|
|
435
|
-
return 'root.loaded.saved';
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
@cached
|
|
440
|
-
get dirtyType() {
|
|
441
|
-
// we might be empty while loading so check this first
|
|
442
|
-
if (this.isLoading || this.isEmpty) {
|
|
443
|
-
return '';
|
|
444
|
-
|
|
445
|
-
// deleted substates
|
|
446
|
-
} else if (this.isDeleted) {
|
|
447
|
-
return 'deleted';
|
|
448
|
-
|
|
449
|
-
// loaded.created substates
|
|
450
|
-
} else if (this.isNew) {
|
|
451
|
-
return 'created';
|
|
452
|
-
|
|
453
|
-
// loaded.updated substates
|
|
454
|
-
} else if (this.isSaving || !this.isValid || this.isDirty) {
|
|
455
|
-
return 'updated';
|
|
456
|
-
|
|
457
|
-
// if nothing remains, we are loaded saved!
|
|
458
|
-
} else {
|
|
459
|
-
return '';
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
function notifyErrorsStateChanged(state: RecordState) {
|
|
465
|
-
state.notify('isValid');
|
|
466
|
-
state.notify('isError');
|
|
467
|
-
state.notify('adapterError');
|
|
468
|
-
}
|