@ember-data/store 4.6.1 → 4.7.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/-debug/index.js +35 -13
- package/addon/-private/{identifier-cache.ts → caches/identifier-cache.ts} +148 -73
- package/addon/-private/caches/instance-cache.ts +690 -0
- package/addon/-private/{record-data-for.ts → caches/record-data-for.ts} +2 -7
- package/addon/-private/index.ts +44 -24
- package/addon/-private/{model → legacy-model-support}/record-reference.ts +15 -13
- package/addon/-private/{schema-definition-service.ts → legacy-model-support/schema-definition-service.ts} +13 -9
- package/addon/-private/{model → legacy-model-support}/shim-model-class.ts +18 -11
- package/addon/-private/managers/record-array-manager.ts +377 -0
- package/addon/-private/managers/record-data-manager.ts +845 -0
- package/addon/-private/managers/record-data-store-wrapper.ts +421 -0
- package/addon/-private/managers/record-notification-manager.ts +109 -0
- package/addon/-private/network/fetch-manager.ts +567 -0
- package/addon/-private/{finders.js → network/finders.js} +14 -17
- package/addon/-private/{request-cache.ts → network/request-cache.ts} +21 -18
- package/addon/-private/{snapshot-record-array.ts → network/snapshot-record-array.ts} +14 -31
- package/addon/-private/{snapshot.ts → network/snapshot.ts} +40 -49
- package/addon/-private/{promise-proxies.ts → proxies/promise-proxies.ts} +76 -15
- package/addon/-private/{promise-proxy-base.js → proxies/promise-proxy-base.js} +0 -0
- package/addon/-private/record-arrays/identifier-array.ts +924 -0
- package/addon/-private/{core-store.ts → store-service.ts} +574 -215
- package/addon/-private/{coerce-id.ts → utils/coerce-id.ts} +1 -1
- package/addon/-private/{common.js → utils/common.js} +1 -2
- package/addon/-private/utils/construct-resource.ts +2 -2
- package/addon/-private/{identifer-debug-consts.ts → utils/identifer-debug-consts.ts} +0 -0
- package/addon/-private/utils/is-non-empty-string.ts +1 -1
- package/addon/-private/{normalize-model-name.ts → utils/normalize-model-name.ts} +1 -3
- package/addon/-private/utils/promise-record.ts +5 -6
- package/addon/-private/{serializer-response.ts → utils/serializer-response.ts} +2 -2
- package/addon/-private/utils/uuid-polyfill.ts +73 -0
- package/package.json +12 -8
- package/addon/-private/backburner.js +0 -25
- package/addon/-private/errors-utils.js +0 -146
- package/addon/-private/fetch-manager.ts +0 -597
- package/addon/-private/identity-map.ts +0 -54
- package/addon/-private/instance-cache.ts +0 -387
- package/addon/-private/internal-model-factory.ts +0 -359
- package/addon/-private/internal-model-map.ts +0 -121
- package/addon/-private/model/internal-model.ts +0 -602
- package/addon/-private/record-array-manager.ts +0 -444
- package/addon/-private/record-arrays/adapter-populated-record-array.ts +0 -130
- package/addon/-private/record-arrays/record-array.ts +0 -318
- package/addon/-private/record-data-store-wrapper.ts +0 -243
- package/addon/-private/record-notification-manager.ts +0 -73
- package/addon/-private/weak-cache.ts +0 -125
|
@@ -0,0 +1,845 @@
|
|
|
1
|
+
import { assert, deprecate } from '@ember/debug';
|
|
2
|
+
|
|
3
|
+
import type { LocalRelationshipOperation } from '@ember-data/record-data/-private/graph/-operations';
|
|
4
|
+
import type {
|
|
5
|
+
CollectionResourceRelationship,
|
|
6
|
+
SingleResourceRelationship,
|
|
7
|
+
} from '@ember-data/types/q/ember-data-json-api';
|
|
8
|
+
import type { StableRecordIdentifier } from '@ember-data/types/q/identifier';
|
|
9
|
+
import type { ChangedAttributesHash, MergeOperation, RecordData, RecordDataV1 } from '@ember-data/types/q/record-data';
|
|
10
|
+
import type { JsonApiResource, JsonApiValidationError } from '@ember-data/types/q/record-data-json-api';
|
|
11
|
+
import type { Dict } from '@ember-data/types/q/utils';
|
|
12
|
+
|
|
13
|
+
import { isStableIdentifier } from '../caches/identifier-cache';
|
|
14
|
+
import type Store from '../store-service';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The RecordDataManager wraps a RecordData cache
|
|
18
|
+
* enforcing that only the public API surface area
|
|
19
|
+
* is exposed.
|
|
20
|
+
*
|
|
21
|
+
* This class is the the return value of both the
|
|
22
|
+
* `recordDataFor` function supplied to the store
|
|
23
|
+
* hook `instantiateRecord`, and the `recordDataFor`
|
|
24
|
+
* method on the `RecordDataStoreWrapper`. It is not
|
|
25
|
+
* directly instantiatable.
|
|
26
|
+
*
|
|
27
|
+
* It handles translating between cache versions when
|
|
28
|
+
* necessary, for instance when a Store is configured
|
|
29
|
+
* to use both a v1 and a v2 cache depending on some
|
|
30
|
+
* heuristic.
|
|
31
|
+
*
|
|
32
|
+
* Starting with the v2 spec, the cache is designed such
|
|
33
|
+
* that it may be implemented as a singleton. However,
|
|
34
|
+
* because the v1 spec was not designed for this whenever
|
|
35
|
+
* we encounter any v1 cache we must wrap all caches, even
|
|
36
|
+
* singletons, in non-singleton managers to preserve v1
|
|
37
|
+
* compatibility.
|
|
38
|
+
*
|
|
39
|
+
* To avoid this performance penalty being paid by all
|
|
40
|
+
* applications, singleton behavior may be opted-in via
|
|
41
|
+
* the configuration supplied to your Ember application
|
|
42
|
+
* at build time. This effectively removes support for
|
|
43
|
+
* v1 caches.
|
|
44
|
+
*
|
|
45
|
+
* ```js
|
|
46
|
+
* let app = new EmberApp(defaults, {
|
|
47
|
+
* emberData: {
|
|
48
|
+
* useSingletonManager: true
|
|
49
|
+
* },
|
|
50
|
+
* });
|
|
51
|
+
* ```
|
|
52
|
+
*
|
|
53
|
+
* @class RecordDataManager
|
|
54
|
+
* @public
|
|
55
|
+
*/
|
|
56
|
+
export class NonSingletonRecordDataManager implements RecordData {
|
|
57
|
+
version: '2' = '2';
|
|
58
|
+
|
|
59
|
+
#store: Store;
|
|
60
|
+
#recordData: RecordData | RecordDataV1;
|
|
61
|
+
#identifier: StableRecordIdentifier;
|
|
62
|
+
|
|
63
|
+
get managedVersion() {
|
|
64
|
+
return this.#recordData.version || '1';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
constructor(store: Store, recordData: RecordData | RecordDataV1, identifier: StableRecordIdentifier) {
|
|
68
|
+
this.#store = store;
|
|
69
|
+
this.#recordData = recordData;
|
|
70
|
+
this.#identifier = identifier;
|
|
71
|
+
|
|
72
|
+
if (this.#isDeprecated(recordData)) {
|
|
73
|
+
deprecate(
|
|
74
|
+
`This RecordData uses the deprecated V1 RecordData Spec. Upgrade to V2 to maintain compatibility.`,
|
|
75
|
+
false,
|
|
76
|
+
{
|
|
77
|
+
id: 'ember-data:deprecate-v1-cache',
|
|
78
|
+
until: '5.0',
|
|
79
|
+
since: { available: '4.8', enabled: '4.8' },
|
|
80
|
+
for: 'ember-data',
|
|
81
|
+
}
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
#isDeprecated(recordData: RecordData | RecordDataV1): recordData is RecordDataV1 {
|
|
87
|
+
let version = recordData.version || '1';
|
|
88
|
+
return version !== this.version;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Cache
|
|
92
|
+
// =====
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Retrieve the identifier for this v1 recordData
|
|
96
|
+
*
|
|
97
|
+
* DEPRECATED Caches should not be assumed to be 1:1 with resources
|
|
98
|
+
*
|
|
99
|
+
* @method getResourceIdentifier
|
|
100
|
+
* @public
|
|
101
|
+
* @deprecated
|
|
102
|
+
*/
|
|
103
|
+
getResourceIdentifier(): StableRecordIdentifier {
|
|
104
|
+
return this.#identifier;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Push resource data from a remote source into the cache for this identifier
|
|
109
|
+
*
|
|
110
|
+
* @method pushData
|
|
111
|
+
* @public
|
|
112
|
+
* @param identifier
|
|
113
|
+
* @param data
|
|
114
|
+
* @param hasRecord
|
|
115
|
+
* @returns {void | string[]} if `hasRecord` is true then calculated key changes should be returned
|
|
116
|
+
*/
|
|
117
|
+
pushData(identifier: StableRecordIdentifier, data: JsonApiResource, hasRecord?: boolean): void | string[] {
|
|
118
|
+
const recordData = this.#recordData;
|
|
119
|
+
// called by something V1
|
|
120
|
+
if (!isStableIdentifier(identifier)) {
|
|
121
|
+
data = identifier as JsonApiResource;
|
|
122
|
+
hasRecord = data as boolean;
|
|
123
|
+
identifier = this.#identifier;
|
|
124
|
+
}
|
|
125
|
+
if (this.#isDeprecated(recordData)) {
|
|
126
|
+
return recordData.pushData(data, hasRecord);
|
|
127
|
+
}
|
|
128
|
+
return recordData.pushData(identifier, data, hasRecord);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Perform an operation on the cache to update the remote state.
|
|
133
|
+
*
|
|
134
|
+
* Note: currently the only valid operation is a MergeOperation
|
|
135
|
+
* which occurs when a collision of identifiers is detected.
|
|
136
|
+
*
|
|
137
|
+
* @method sync
|
|
138
|
+
* @public
|
|
139
|
+
* @param op the operation to perform
|
|
140
|
+
* @returns {void}
|
|
141
|
+
*/
|
|
142
|
+
sync(op: MergeOperation): void {
|
|
143
|
+
const recordData = this.#recordData;
|
|
144
|
+
if (this.#isDeprecated(recordData)) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
recordData.sync(op);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Update resource data with a local mutation. Currently supports operations
|
|
152
|
+
* on relationships only.
|
|
153
|
+
*
|
|
154
|
+
* @method update
|
|
155
|
+
* @public
|
|
156
|
+
* @param operation
|
|
157
|
+
*/
|
|
158
|
+
// isCollection is only needed for interop with v1 cache
|
|
159
|
+
update(operation: LocalRelationshipOperation, isResource?: boolean): void {
|
|
160
|
+
if (this.#isDeprecated(this.#recordData)) {
|
|
161
|
+
const cache = this.#store._instanceCache;
|
|
162
|
+
switch (operation.op) {
|
|
163
|
+
case 'addToRelatedRecords':
|
|
164
|
+
this.#recordData.addToHasMany(
|
|
165
|
+
operation.field,
|
|
166
|
+
(operation.value as StableRecordIdentifier[]).map((i) => cache.getRecordData(i)),
|
|
167
|
+
operation.index
|
|
168
|
+
);
|
|
169
|
+
return;
|
|
170
|
+
case 'removeFromRelatedRecords':
|
|
171
|
+
this.#recordData.removeFromHasMany(
|
|
172
|
+
operation.field,
|
|
173
|
+
(operation.value as StableRecordIdentifier[]).map((i) => cache.getRecordData(i))
|
|
174
|
+
);
|
|
175
|
+
return;
|
|
176
|
+
case 'replaceRelatedRecords':
|
|
177
|
+
this.#recordData.setDirtyHasMany(
|
|
178
|
+
operation.field,
|
|
179
|
+
operation.value.map((i) => cache.getRecordData(i))
|
|
180
|
+
);
|
|
181
|
+
return;
|
|
182
|
+
case 'replaceRelatedRecord':
|
|
183
|
+
if (isResource) {
|
|
184
|
+
this.#recordData.setDirtyBelongsTo(
|
|
185
|
+
operation.field,
|
|
186
|
+
operation.value ? cache.getRecordData(operation.value) : null
|
|
187
|
+
);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
this.#recordData.removeFromHasMany(operation.field, [cache.getRecordData(operation.prior!)]);
|
|
191
|
+
this.#recordData.addToHasMany(operation.field, [cache.getRecordData(operation.value!)], operation.index);
|
|
192
|
+
return;
|
|
193
|
+
case 'sortRelatedRecords':
|
|
194
|
+
return;
|
|
195
|
+
default:
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
} else {
|
|
199
|
+
this.#recordData.update(operation);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* [LIFECYLCE] Signal to the cache that a new record has been instantiated on the client
|
|
205
|
+
*
|
|
206
|
+
* It returns properties from options that should be set on the record during the create
|
|
207
|
+
* process. This return value behavior is deprecated.
|
|
208
|
+
*
|
|
209
|
+
* @method clientDidCreate
|
|
210
|
+
* @public
|
|
211
|
+
* @param identifier
|
|
212
|
+
* @param options
|
|
213
|
+
*/
|
|
214
|
+
clientDidCreate(identifier: StableRecordIdentifier, options?: Dict<unknown>): Dict<unknown> {
|
|
215
|
+
// called by something V1
|
|
216
|
+
if (!isStableIdentifier(identifier)) {
|
|
217
|
+
options = identifier;
|
|
218
|
+
identifier = this.#identifier;
|
|
219
|
+
}
|
|
220
|
+
let recordData = this.#recordData;
|
|
221
|
+
|
|
222
|
+
// TODO deprecate return value
|
|
223
|
+
if (this.#isDeprecated(recordData)) {
|
|
224
|
+
recordData.clientDidCreate();
|
|
225
|
+
// if a V2 is calling a V1 we need to call both methods
|
|
226
|
+
return recordData._initRecordCreateOptions(options);
|
|
227
|
+
} else {
|
|
228
|
+
return recordData.clientDidCreate(identifier, options);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Pass options to the cache that were supplied to a new record
|
|
234
|
+
* instantiated on the client.
|
|
235
|
+
*
|
|
236
|
+
* DEPRECATED: options are now passed via `clientDidCreate`
|
|
237
|
+
*
|
|
238
|
+
* @method clientDidCreate
|
|
239
|
+
* @public
|
|
240
|
+
* @deprecated
|
|
241
|
+
* @param options
|
|
242
|
+
*/
|
|
243
|
+
_initRecordCreateOptions(options?: Dict<unknown>) {
|
|
244
|
+
let recordData = this.#recordData;
|
|
245
|
+
|
|
246
|
+
if (this.#isDeprecated(recordData)) {
|
|
247
|
+
return recordData._initRecordCreateOptions(options);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* [LIFECYCLE] Signals to the cache that a resource
|
|
253
|
+
* will be part of a save transaction.
|
|
254
|
+
*
|
|
255
|
+
* @method willCommit
|
|
256
|
+
* @public
|
|
257
|
+
* @param identifier
|
|
258
|
+
*/
|
|
259
|
+
willCommit(identifier: StableRecordIdentifier): void {
|
|
260
|
+
this.#recordData.willCommit(identifier || this.#identifier);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* [LIFECYCLE] Signals to the cache that a resource
|
|
265
|
+
* was successfully updated as part of a save transaction.
|
|
266
|
+
*
|
|
267
|
+
* @method didCommit
|
|
268
|
+
* @public
|
|
269
|
+
* @param identifier
|
|
270
|
+
* @param data
|
|
271
|
+
*/
|
|
272
|
+
didCommit(identifier: StableRecordIdentifier, data: JsonApiResource | null): void {
|
|
273
|
+
// called by something V1
|
|
274
|
+
if (!isStableIdentifier(identifier)) {
|
|
275
|
+
data = identifier;
|
|
276
|
+
identifier = this.#identifier;
|
|
277
|
+
}
|
|
278
|
+
let recordData = this.#recordData;
|
|
279
|
+
this.#isDeprecated(recordData) ? recordData.didCommit(data) : recordData.didCommit(identifier, data);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* [LIFECYCLE] Signals to the cache that a resource
|
|
284
|
+
* was update via a save transaction failed.
|
|
285
|
+
*
|
|
286
|
+
* @method commitWasRejected
|
|
287
|
+
* @public
|
|
288
|
+
* @param identifier
|
|
289
|
+
* @param errors
|
|
290
|
+
*/
|
|
291
|
+
commitWasRejected(identifier: StableRecordIdentifier, errors?: JsonApiValidationError[]) {
|
|
292
|
+
this.#recordData.commitWasRejected(identifier || this.#identifier, errors);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* [LIFECYCLE] Signals to the cache that all data for a resource
|
|
297
|
+
* should be cleared.
|
|
298
|
+
*
|
|
299
|
+
* @method unloadRecord
|
|
300
|
+
* @public
|
|
301
|
+
* @param identifier
|
|
302
|
+
*/
|
|
303
|
+
unloadRecord(identifier: StableRecordIdentifier): void {
|
|
304
|
+
const recordData = this.#recordData;
|
|
305
|
+
if (this.#isDeprecated(recordData)) {
|
|
306
|
+
recordData.unloadRecord();
|
|
307
|
+
} else {
|
|
308
|
+
recordData.unloadRecord(identifier || this.#identifier);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Attrs
|
|
313
|
+
// =====
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Retrieve the data for an attribute from the cache
|
|
317
|
+
*
|
|
318
|
+
* @method getAttr
|
|
319
|
+
* @public
|
|
320
|
+
* @param identifier
|
|
321
|
+
* @param propertyName
|
|
322
|
+
* @returns {unknown}
|
|
323
|
+
*/
|
|
324
|
+
getAttr(identifier: StableRecordIdentifier, propertyName: string): unknown {
|
|
325
|
+
// called by something V1
|
|
326
|
+
if (!isStableIdentifier(identifier)) {
|
|
327
|
+
propertyName = identifier;
|
|
328
|
+
identifier = this.#identifier;
|
|
329
|
+
}
|
|
330
|
+
let recordData = this.#recordData;
|
|
331
|
+
return this.#isDeprecated(recordData)
|
|
332
|
+
? recordData.getAttr(propertyName)
|
|
333
|
+
: recordData.getAttr(identifier, propertyName);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Mutate the data for an attribute in the cache
|
|
338
|
+
*
|
|
339
|
+
* @method setAttr
|
|
340
|
+
* @public
|
|
341
|
+
* @param identifier
|
|
342
|
+
* @param propertyName
|
|
343
|
+
* @param value
|
|
344
|
+
*/
|
|
345
|
+
setAttr(identifier: StableRecordIdentifier, propertyName: string, value: unknown): void {
|
|
346
|
+
let recordData = this.#recordData;
|
|
347
|
+
|
|
348
|
+
this.#isDeprecated(recordData)
|
|
349
|
+
? recordData.setDirtyAttribute(propertyName, value)
|
|
350
|
+
: recordData.setAttr(identifier, propertyName, value);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Mutate the data for an attribute in the cache
|
|
355
|
+
*
|
|
356
|
+
* DEPRECATED use setAttr
|
|
357
|
+
*
|
|
358
|
+
* @method setDirtyAttribute
|
|
359
|
+
* @public
|
|
360
|
+
* @deprecated
|
|
361
|
+
* @param identifier
|
|
362
|
+
* @param propertyName
|
|
363
|
+
* @param value
|
|
364
|
+
*/
|
|
365
|
+
setDirtyAttribute(propertyName: string, value: unknown): void {
|
|
366
|
+
let recordData = this.#recordData;
|
|
367
|
+
|
|
368
|
+
this.#isDeprecated(recordData)
|
|
369
|
+
? recordData.setDirtyAttribute(propertyName, value)
|
|
370
|
+
: recordData.setAttr(this.#identifier, propertyName, value);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Query the cache for the changed attributes of a resource.
|
|
375
|
+
*
|
|
376
|
+
* DEPRECATED use changedAttrs
|
|
377
|
+
*
|
|
378
|
+
* @method changedAttributes
|
|
379
|
+
* @public
|
|
380
|
+
* @deprecated
|
|
381
|
+
* @param identifier
|
|
382
|
+
* @returns
|
|
383
|
+
*/
|
|
384
|
+
changedAttributes(): ChangedAttributesHash {
|
|
385
|
+
const recordData = this.#recordData;
|
|
386
|
+
if (this.#isDeprecated(recordData)) {
|
|
387
|
+
return recordData.changedAttributes();
|
|
388
|
+
}
|
|
389
|
+
return recordData.changedAttrs(this.#identifier);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Query the cache for the changed attributes of a resource.
|
|
394
|
+
*
|
|
395
|
+
* @method changedAttrs
|
|
396
|
+
* @public
|
|
397
|
+
* @deprecated
|
|
398
|
+
* @param identifier
|
|
399
|
+
* @returns
|
|
400
|
+
*/
|
|
401
|
+
changedAttrs(identifier: StableRecordIdentifier): ChangedAttributesHash {
|
|
402
|
+
const recordData = this.#recordData;
|
|
403
|
+
if (this.#isDeprecated(recordData)) {
|
|
404
|
+
return recordData.changedAttributes();
|
|
405
|
+
}
|
|
406
|
+
return recordData.changedAttrs(identifier);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Query the cache for whether any mutated attributes exist
|
|
411
|
+
*
|
|
412
|
+
* DEPRECATED use hasChangedAttrs
|
|
413
|
+
*
|
|
414
|
+
* @method hasChangedAttributes
|
|
415
|
+
* @public
|
|
416
|
+
* @deprecated
|
|
417
|
+
* @returns
|
|
418
|
+
*/
|
|
419
|
+
hasChangedAttributes(): boolean {
|
|
420
|
+
const recordData = this.#recordData;
|
|
421
|
+
return this.#isDeprecated(recordData)
|
|
422
|
+
? recordData.hasChangedAttributes()
|
|
423
|
+
: recordData.hasChangedAttrs(this.#identifier);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Query the cache for whether any mutated attributes exist
|
|
428
|
+
*
|
|
429
|
+
* @method hasChangedAttrs
|
|
430
|
+
* @public
|
|
431
|
+
* @param identifier
|
|
432
|
+
* @returns
|
|
433
|
+
*/
|
|
434
|
+
hasChangedAttrs(identifier: StableRecordIdentifier): boolean {
|
|
435
|
+
const recordData = this.#recordData;
|
|
436
|
+
return this.#isDeprecated(recordData) ? recordData.hasChangedAttributes() : recordData.hasChangedAttrs(identifier);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Tell the cache to discard any uncommitted mutations to attributes
|
|
441
|
+
*
|
|
442
|
+
* DEPRECATED use rollbackAttrs
|
|
443
|
+
*
|
|
444
|
+
* @method rollbackAttributes
|
|
445
|
+
* @public
|
|
446
|
+
* @deprecated
|
|
447
|
+
* @returns
|
|
448
|
+
*/
|
|
449
|
+
rollbackAttributes() {
|
|
450
|
+
const recordData = this.#recordData;
|
|
451
|
+
return this.#isDeprecated(recordData)
|
|
452
|
+
? recordData.rollbackAttributes()
|
|
453
|
+
: recordData.rollbackAttrs(this.#identifier);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Tell the cache to discard any uncommitted mutations to attributes
|
|
458
|
+
*
|
|
459
|
+
* @method rollbackAttrs
|
|
460
|
+
* @public
|
|
461
|
+
* @param identifier
|
|
462
|
+
* @returns the names of attributes that were restored
|
|
463
|
+
*/
|
|
464
|
+
rollbackAttrs(identifier: StableRecordIdentifier): string[] {
|
|
465
|
+
const recordData = this.#recordData;
|
|
466
|
+
return this.#isDeprecated(recordData) ? recordData.rollbackAttributes() : recordData.rollbackAttrs(identifier);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Relationships
|
|
470
|
+
// =============
|
|
471
|
+
|
|
472
|
+
// the third arg here is "private". In a world with only V2 it is not necessary
|
|
473
|
+
// but in one in which we must convert a call from V2 -> V1 it is required to do this
|
|
474
|
+
// or else to do nasty schema lookup things
|
|
475
|
+
// @runspired has implemented this concept in relationships spikes and is confident
|
|
476
|
+
// we do not need any signal about whether a relationship is a collection or not at this
|
|
477
|
+
// boundary
|
|
478
|
+
/**
|
|
479
|
+
* Query the cache for the current state of a relationship property
|
|
480
|
+
*
|
|
481
|
+
* @method getRelationship
|
|
482
|
+
* @public
|
|
483
|
+
* @param identifier
|
|
484
|
+
* @param propertyName
|
|
485
|
+
* @returns resource relationship object
|
|
486
|
+
*/
|
|
487
|
+
getRelationship(
|
|
488
|
+
identifier: StableRecordIdentifier,
|
|
489
|
+
propertyName: string,
|
|
490
|
+
isCollection = false
|
|
491
|
+
): SingleResourceRelationship | CollectionResourceRelationship {
|
|
492
|
+
let recordData = this.#recordData;
|
|
493
|
+
|
|
494
|
+
if (this.#isDeprecated(recordData)) {
|
|
495
|
+
let isBelongsTo = !isCollection;
|
|
496
|
+
return isBelongsTo ? recordData.getBelongsTo(propertyName) : recordData.getHasMany(propertyName);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
return recordData.getRelationship(identifier, propertyName);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Query the cache for the current state of a belongsTo field
|
|
504
|
+
*
|
|
505
|
+
* DEPRECATED use `getRelationship`
|
|
506
|
+
*
|
|
507
|
+
* @method getBelongsTo
|
|
508
|
+
* @public
|
|
509
|
+
* @deprecated
|
|
510
|
+
* @param propertyName
|
|
511
|
+
* @returns single resource relationship object
|
|
512
|
+
*/
|
|
513
|
+
getBelongsTo(propertyName: string): SingleResourceRelationship {
|
|
514
|
+
let recordData = this.#recordData;
|
|
515
|
+
|
|
516
|
+
if (this.#isDeprecated(recordData)) {
|
|
517
|
+
return recordData.getBelongsTo(propertyName);
|
|
518
|
+
} else {
|
|
519
|
+
let identifier = this.#identifier;
|
|
520
|
+
return recordData.getRelationship(identifier, propertyName) as SingleResourceRelationship;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Query the cache for the current state of a hasMany field
|
|
526
|
+
*
|
|
527
|
+
* DEPRECATED use `getRelationship`
|
|
528
|
+
*
|
|
529
|
+
* @method getHasMany
|
|
530
|
+
* @public
|
|
531
|
+
* @deprecated
|
|
532
|
+
* @param propertyName
|
|
533
|
+
* @returns single resource relationship object
|
|
534
|
+
*/
|
|
535
|
+
getHasMany(propertyName: string): CollectionResourceRelationship {
|
|
536
|
+
let recordData = this.#recordData;
|
|
537
|
+
|
|
538
|
+
if (this.#isDeprecated(recordData)) {
|
|
539
|
+
return recordData.getHasMany(propertyName);
|
|
540
|
+
} else {
|
|
541
|
+
let identifier = this.#identifier;
|
|
542
|
+
return recordData.getRelationship(identifier, propertyName) as CollectionResourceRelationship;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* Mutate the current state of a belongsTo relationship
|
|
548
|
+
*
|
|
549
|
+
* DEPRECATED use update
|
|
550
|
+
*
|
|
551
|
+
* @method setDirtyBelongsTo
|
|
552
|
+
* @public
|
|
553
|
+
* @deprecated
|
|
554
|
+
* @param propertyName
|
|
555
|
+
* @param value
|
|
556
|
+
*/
|
|
557
|
+
setDirtyBelongsTo(propertyName: string, value: NonSingletonRecordDataManager | null) {
|
|
558
|
+
const recordData = this.#recordData;
|
|
559
|
+
|
|
560
|
+
this.#isDeprecated(recordData)
|
|
561
|
+
? recordData.setDirtyBelongsTo(propertyName, value)
|
|
562
|
+
: recordData.update({
|
|
563
|
+
op: 'replaceRelatedRecord',
|
|
564
|
+
record: this.#identifier,
|
|
565
|
+
field: propertyName,
|
|
566
|
+
value: value ? value.getResourceIdentifier() : null,
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Mutate the current state of a hasMany relationship by adding values
|
|
572
|
+
* An index may optionally be specified which the cache should use for
|
|
573
|
+
* where in the list to insert the records
|
|
574
|
+
*
|
|
575
|
+
* DEPRECATED use update
|
|
576
|
+
*
|
|
577
|
+
* @method addToHasMany
|
|
578
|
+
* @deprecated
|
|
579
|
+
* @public
|
|
580
|
+
* @param propertyName
|
|
581
|
+
* @param value
|
|
582
|
+
* @param idx
|
|
583
|
+
*/
|
|
584
|
+
addToHasMany(propertyName: string, value: NonSingletonRecordDataManager[], idx?: number): void {
|
|
585
|
+
const identifier = this.#identifier;
|
|
586
|
+
const recordData = this.#recordData;
|
|
587
|
+
|
|
588
|
+
this.#isDeprecated(recordData)
|
|
589
|
+
? recordData.addToHasMany(propertyName, value, idx)
|
|
590
|
+
: recordData.update({
|
|
591
|
+
op: 'addToRelatedRecords',
|
|
592
|
+
field: propertyName,
|
|
593
|
+
record: identifier,
|
|
594
|
+
value: value.map((v) => v.getResourceIdentifier()),
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* Mutate the current state of a hasMany relationship by removing values.
|
|
600
|
+
*
|
|
601
|
+
* DEPRECATED use update
|
|
602
|
+
*
|
|
603
|
+
* @method removeFromHasMany
|
|
604
|
+
* @deprecated
|
|
605
|
+
* @public
|
|
606
|
+
* @param propertyName
|
|
607
|
+
* @param value
|
|
608
|
+
*/
|
|
609
|
+
removeFromHasMany(propertyName: string, value: RecordData[]): void {
|
|
610
|
+
const identifier = this.#identifier;
|
|
611
|
+
const recordData = this.#recordData;
|
|
612
|
+
|
|
613
|
+
this.#isDeprecated(recordData)
|
|
614
|
+
? recordData.removeFromHasMany(propertyName, value)
|
|
615
|
+
: recordData.update({
|
|
616
|
+
op: 'removeFromRelatedRecords',
|
|
617
|
+
record: identifier,
|
|
618
|
+
field: propertyName,
|
|
619
|
+
value: (value as unknown as NonSingletonRecordDataManager[]).map((v) => v.getResourceIdentifier()),
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* Mutate the current state of a hasMany relationship by replacing it entirely
|
|
625
|
+
*
|
|
626
|
+
* DEPRECATED use `setHasMany`
|
|
627
|
+
*
|
|
628
|
+
* @method setDirtyHasMany
|
|
629
|
+
* @public
|
|
630
|
+
* @deprecated
|
|
631
|
+
* @param propertyName
|
|
632
|
+
* @param value
|
|
633
|
+
*/
|
|
634
|
+
setDirtyHasMany(propertyName: string, value: NonSingletonRecordDataManager[]) {
|
|
635
|
+
let recordData = this.#recordData;
|
|
636
|
+
|
|
637
|
+
this.#isDeprecated(recordData)
|
|
638
|
+
? recordData.setDirtyHasMany(propertyName, value)
|
|
639
|
+
: recordData.update({
|
|
640
|
+
op: 'replaceRelatedRecords',
|
|
641
|
+
record: this.#identifier,
|
|
642
|
+
field: propertyName,
|
|
643
|
+
value: value.map((rd) => rd.getResourceIdentifier()),
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// State
|
|
648
|
+
// =============
|
|
649
|
+
|
|
650
|
+
/**
|
|
651
|
+
* Update the cache state for the given resource to be marked as locally deleted,
|
|
652
|
+
* or remove such a mark.
|
|
653
|
+
*
|
|
654
|
+
* @method setIsDeleted
|
|
655
|
+
* @public
|
|
656
|
+
* @param identifier
|
|
657
|
+
* @param isDeleted
|
|
658
|
+
*/
|
|
659
|
+
setIsDeleted(identifier: StableRecordIdentifier, isDeleted: boolean): void {
|
|
660
|
+
if (!isStableIdentifier(identifier)) {
|
|
661
|
+
isDeleted = identifier as boolean;
|
|
662
|
+
identifier = this.#identifier;
|
|
663
|
+
}
|
|
664
|
+
const recordData = this.#recordData;
|
|
665
|
+
this.#isDeprecated(recordData)
|
|
666
|
+
? recordData.setIsDeleted(isDeleted)
|
|
667
|
+
: recordData.setIsDeleted(identifier, isDeleted);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/**
|
|
671
|
+
* Query the cache for any validation errors applicable to the given resource.
|
|
672
|
+
*
|
|
673
|
+
* @method getErrors
|
|
674
|
+
* @public
|
|
675
|
+
* @param identifier
|
|
676
|
+
* @returns
|
|
677
|
+
*/
|
|
678
|
+
getErrors(identifier: StableRecordIdentifier): JsonApiValidationError[] {
|
|
679
|
+
return this.#recordData.getErrors(identifier || this.#identifier);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
/**
|
|
683
|
+
* Query the cache for whether a given resource has any available data
|
|
684
|
+
*
|
|
685
|
+
* @method isEmpty
|
|
686
|
+
* @public
|
|
687
|
+
* @param identifier
|
|
688
|
+
* @returns {boolean}
|
|
689
|
+
*/
|
|
690
|
+
isEmpty(identifier: StableRecordIdentifier): boolean {
|
|
691
|
+
const recordData = this.#recordData;
|
|
692
|
+
return this.#isDeprecated(recordData)
|
|
693
|
+
? recordData.isEmpty?.(identifier || this.#identifier) || false
|
|
694
|
+
: recordData.isEmpty(identifier || this.#identifier);
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
/**
|
|
698
|
+
* Query the cache for whether a given resource was created locally and not
|
|
699
|
+
* yet persisted.
|
|
700
|
+
*
|
|
701
|
+
* @method isNew
|
|
702
|
+
* @public
|
|
703
|
+
* @param identifier
|
|
704
|
+
* @returns {boolean}
|
|
705
|
+
*/
|
|
706
|
+
isNew(identifier: StableRecordIdentifier): boolean {
|
|
707
|
+
return this.#recordData.isNew(identifier || this.#identifier);
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
/**
|
|
711
|
+
* Query the cache for whether a given resource is marked as deleted (but not
|
|
712
|
+
* necessarily persisted yet).
|
|
713
|
+
*
|
|
714
|
+
* @method isDeleted
|
|
715
|
+
* @public
|
|
716
|
+
* @param identifier
|
|
717
|
+
* @returns {boolean}
|
|
718
|
+
*/
|
|
719
|
+
isDeleted(identifier: StableRecordIdentifier): boolean {
|
|
720
|
+
return this.#recordData.isDeleted(identifier || this.#identifier);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
/**
|
|
724
|
+
* Query the cache for whether a given resource has been deleted and that deletion
|
|
725
|
+
* has also been persisted.
|
|
726
|
+
*
|
|
727
|
+
* @method isDeletionCommitted
|
|
728
|
+
* @public
|
|
729
|
+
* @param identifier
|
|
730
|
+
* @returns {boolean}
|
|
731
|
+
*/
|
|
732
|
+
isDeletionCommitted(identifier: StableRecordIdentifier): boolean {
|
|
733
|
+
return this.#recordData.isDeletionCommitted(identifier || this.#identifier);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
export class SingletonRecordDataManager implements RecordData {
|
|
738
|
+
version: '2' = '2';
|
|
739
|
+
|
|
740
|
+
#recordDatas: Map<StableRecordIdentifier, RecordData>;
|
|
741
|
+
|
|
742
|
+
constructor() {
|
|
743
|
+
this.#recordDatas = new Map();
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
_addRecordData(identifier: StableRecordIdentifier, recordData: RecordData) {
|
|
747
|
+
this.#recordDatas.set(identifier, recordData);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
#recordData(identifier: StableRecordIdentifier): RecordData {
|
|
751
|
+
assert(`No RecordData Yet Exists!`, this.#recordDatas.has(identifier));
|
|
752
|
+
return this.#recordDatas.get(identifier)!;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
// Cache
|
|
756
|
+
// =====
|
|
757
|
+
|
|
758
|
+
pushData(identifier: StableRecordIdentifier, data: JsonApiResource, hasRecord?: boolean): void | string[] {
|
|
759
|
+
return this.#recordData(identifier).pushData(identifier, data, hasRecord);
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
sync(op: MergeOperation): void {
|
|
763
|
+
this.#recordData(op.record).sync(op);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
clientDidCreate(identifier: StableRecordIdentifier, options?: Dict<unknown>): Dict<unknown> {
|
|
767
|
+
return this.#recordData(identifier).clientDidCreate(identifier, options);
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
willCommit(identifier: StableRecordIdentifier): void {
|
|
771
|
+
this.#recordData(identifier).willCommit(identifier);
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
didCommit(identifier: StableRecordIdentifier, data: JsonApiResource | null): void {
|
|
775
|
+
this.#recordData(identifier).didCommit(identifier, data);
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
commitWasRejected(identifier: StableRecordIdentifier, errors?: JsonApiValidationError[]): void {
|
|
779
|
+
this.#recordData(identifier).commitWasRejected(identifier, errors);
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
unloadRecord(identifier: StableRecordIdentifier): void {
|
|
783
|
+
this.#recordData(identifier).unloadRecord(identifier);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// Attrs
|
|
787
|
+
// =====
|
|
788
|
+
|
|
789
|
+
getAttr(identifier: StableRecordIdentifier, propertyName: string): unknown {
|
|
790
|
+
return this.#recordData(identifier).getAttr(identifier, propertyName);
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
setAttr(identifier: StableRecordIdentifier, propertyName: string, value: unknown): void {
|
|
794
|
+
this.#recordData(identifier).setAttr(identifier, propertyName, value);
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
changedAttrs(identifier: StableRecordIdentifier): ChangedAttributesHash {
|
|
798
|
+
return this.#recordData(identifier).changedAttrs(identifier);
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
hasChangedAttrs(identifier: StableRecordIdentifier): boolean {
|
|
802
|
+
return this.#recordData(identifier).hasChangedAttrs(identifier);
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
rollbackAttrs(identifier: StableRecordIdentifier): string[] {
|
|
806
|
+
return this.#recordData(identifier).rollbackAttrs(identifier);
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
getRelationship(
|
|
810
|
+
identifier: StableRecordIdentifier,
|
|
811
|
+
propertyName: string
|
|
812
|
+
): SingleResourceRelationship | CollectionResourceRelationship {
|
|
813
|
+
return this.#recordData(identifier).getRelationship(identifier, propertyName);
|
|
814
|
+
}
|
|
815
|
+
update(operation: LocalRelationshipOperation): void {
|
|
816
|
+
this.#recordData(operation.record).update(operation);
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
// State
|
|
820
|
+
// =============
|
|
821
|
+
|
|
822
|
+
setIsDeleted(identifier: StableRecordIdentifier, isDeleted: boolean): void {
|
|
823
|
+
this.#recordData(identifier).setIsDeleted(identifier, isDeleted);
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
getErrors(identifier: StableRecordIdentifier): JsonApiValidationError[] {
|
|
827
|
+
return this.#recordData(identifier).getErrors(identifier);
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
isEmpty(identifier: StableRecordIdentifier): boolean {
|
|
831
|
+
return this.#recordData(identifier).isEmpty(identifier);
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
isNew(identifier: StableRecordIdentifier): boolean {
|
|
835
|
+
return this.#recordData(identifier).isNew(identifier);
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
isDeleted(identifier: StableRecordIdentifier): boolean {
|
|
839
|
+
return this.#recordData(identifier).isDeleted(identifier);
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
isDeletionCommitted(identifier: StableRecordIdentifier): boolean {
|
|
843
|
+
return this.#recordData(identifier).isDeletionCommitted(identifier);
|
|
844
|
+
}
|
|
845
|
+
}
|