@ember-data/store 4.5.0-beta.0 → 4.5.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/{system/backburner.js → backburner.js} +0 -0
- package/addon/-private/{system/coerce-id.ts → coerce-id.ts} +0 -0
- package/addon/-private/{system/store/common.js → common.js} +0 -0
- package/addon/-private/{system/core-store.ts → core-store.ts} +467 -1253
- package/addon/-private/{system/errors-utils.js → errors-utils.js} +7 -6
- package/addon/-private/{system/fetch-manager.ts → fetch-manager.ts} +72 -42
- package/addon/-private/finders.js +107 -0
- package/addon/-private/identifer-debug-consts.ts +3 -0
- package/addon/-private/{identifiers/cache.ts → identifier-cache.ts} +26 -14
- package/addon/-private/{system/identity-map.ts → identity-map.ts} +2 -1
- package/addon/-private/index.ts +17 -17
- package/addon/-private/instance-cache.ts +387 -0
- package/addon/-private/{system/store/internal-model-factory.ts → internal-model-factory.ts} +25 -19
- package/addon/-private/{system/internal-model-map.ts → internal-model-map.ts} +9 -5
- package/addon/-private/model/internal-model.ts +602 -0
- package/addon/-private/{system/references/record.ts → model/record-reference.ts} +23 -36
- package/addon/-private/{system/model → model}/shim-model-class.ts +19 -14
- package/addon/-private/{system/normalize-model-name.ts → normalize-model-name.ts} +0 -0
- package/addon/-private/{system/promise-proxies.ts → promise-proxies.ts} +12 -5
- package/addon/-private/{system/promise-proxy-base.js → promise-proxy-base.js} +0 -0
- package/addon/-private/{system/record-array-manager.ts → record-array-manager.ts} +19 -18
- package/addon/-private/{system/record-arrays → record-arrays}/adapter-populated-record-array.ts +11 -10
- package/addon/-private/{system/record-arrays → record-arrays}/record-array.ts +37 -19
- package/addon/-private/record-data-for.ts +39 -0
- package/addon/-private/{system/store/record-data-store-wrapper.ts → record-data-store-wrapper.ts} +21 -26
- package/addon/-private/{system/record-notification-manager.ts → record-notification-manager.ts} +8 -3
- package/addon/-private/{system/request-cache.ts → request-cache.ts} +5 -6
- package/addon/-private/{system/schema-definition-service.ts → schema-definition-service.ts} +30 -14
- package/addon/-private/{system/store/serializer-response.ts → serializer-response.ts} +7 -6
- package/addon/-private/{system/snapshot-record-array.ts → snapshot-record-array.ts} +27 -8
- package/addon/-private/{system/snapshot.ts → snapshot.ts} +54 -39
- package/addon/-private/utils/construct-resource.ts +7 -3
- package/addon/-private/utils/promise-record.ts +9 -18
- package/addon/-private/{system/weak-cache.ts → weak-cache.ts} +2 -2
- package/addon/index.ts +1 -0
- package/package.json +21 -20
- package/addon/-private/identifiers/is-stable-identifier.ts +0 -18
- package/addon/-private/identifiers/utils/uuid-v4.ts +0 -80
- package/addon/-private/system/ds-model-store.ts +0 -136
- package/addon/-private/system/model/internal-model.ts +0 -1303
- package/addon/-private/system/model/states.js +0 -736
- package/addon/-private/system/record-arrays.ts +0 -8
- package/addon/-private/system/record-data-for.ts +0 -54
- package/addon/-private/system/references/belongs-to.ts +0 -406
- package/addon/-private/system/references/has-many.ts +0 -487
- package/addon/-private/system/references/reference.ts +0 -205
- package/addon/-private/system/references.js +0 -9
- package/addon/-private/system/store/finders.js +0 -412
- package/addon/-private/ts-interfaces/ds-model.ts +0 -50
- package/addon/-private/ts-interfaces/ember-data-json-api.ts +0 -145
- package/addon/-private/ts-interfaces/fetch-manager.ts +0 -44
- package/addon/-private/ts-interfaces/identifier.ts +0 -246
- package/addon/-private/ts-interfaces/minimum-adapter-interface.ts +0 -584
- package/addon/-private/ts-interfaces/minimum-serializer-interface.ts +0 -257
- package/addon/-private/ts-interfaces/promise-proxies.ts +0 -3
- package/addon/-private/ts-interfaces/record-data-json-api.ts +0 -29
- package/addon/-private/ts-interfaces/record-data-record-wrapper.ts +0 -46
- package/addon/-private/ts-interfaces/record-data-schemas.ts +0 -45
- package/addon/-private/ts-interfaces/record-data-store-wrapper.ts +0 -56
- package/addon/-private/ts-interfaces/record-data.ts +0 -72
- package/addon/-private/ts-interfaces/record-instance.ts +0 -18
- package/addon/-private/ts-interfaces/schema-definition-service.ts +0 -12
- package/addon/-private/ts-interfaces/store.ts +0 -10
- package/addon/-private/ts-interfaces/utils.ts +0 -6
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
import { assert } from '@ember/debug';
|
|
2
|
+
import { DEBUG } from '@glimmer/env';
|
|
3
|
+
|
|
4
|
+
import { resolve } from 'rsvp';
|
|
5
|
+
|
|
6
|
+
import type { ExistingResourceObject, ResourceIdentifierObject } from '@ember-data/types/q/ember-data-json-api';
|
|
7
|
+
import type {
|
|
8
|
+
RecordIdentifier,
|
|
9
|
+
StableExistingRecordIdentifier,
|
|
10
|
+
StableRecordIdentifier,
|
|
11
|
+
} from '@ember-data/types/q/identifier';
|
|
12
|
+
import type { RecordData } from '@ember-data/types/q/record-data';
|
|
13
|
+
import type { RecordInstance } from '@ember-data/types/q/record-instance';
|
|
14
|
+
import type { FindOptions } from '@ember-data/types/q/store';
|
|
15
|
+
|
|
16
|
+
import coerceId, { ensureStringId } from './coerce-id';
|
|
17
|
+
import type { CreateRecordProperties } from './core-store';
|
|
18
|
+
import type Store from './core-store';
|
|
19
|
+
import { assertIdentifierHasId } from './core-store';
|
|
20
|
+
import { internalModelFactoryFor, setRecordIdentifier } from './internal-model-factory';
|
|
21
|
+
import InternalModel from './model/internal-model';
|
|
22
|
+
import RecordReference from './model/record-reference';
|
|
23
|
+
import normalizeModelName from './normalize-model-name';
|
|
24
|
+
import recordDataFor, { setRecordDataFor } from './record-data-for';
|
|
25
|
+
import RecordDataStoreWrapper from './record-data-store-wrapper';
|
|
26
|
+
import Snapshot from './snapshot';
|
|
27
|
+
import constructResource from './utils/construct-resource';
|
|
28
|
+
import WeakCache from './weak-cache';
|
|
29
|
+
|
|
30
|
+
const RECORD_REFERENCES = new WeakCache<StableRecordIdentifier, RecordReference>(DEBUG ? 'reference' : '');
|
|
31
|
+
export const StoreMap = new WeakCache<RecordInstance, Store>(DEBUG ? 'store' : '');
|
|
32
|
+
|
|
33
|
+
export function storeFor(record: RecordInstance): Store | undefined {
|
|
34
|
+
const store = StoreMap.get(record);
|
|
35
|
+
|
|
36
|
+
assert(
|
|
37
|
+
`A record in a disconnected state cannot utilize the store. This typically means the record has been destroyed, most commonly by unloading it.`,
|
|
38
|
+
store
|
|
39
|
+
);
|
|
40
|
+
return store;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
type Caches = {
|
|
44
|
+
record: WeakMap<StableRecordIdentifier, RecordInstance>;
|
|
45
|
+
recordData: WeakMap<StableRecordIdentifier, RecordData>;
|
|
46
|
+
};
|
|
47
|
+
export class InstanceCache {
|
|
48
|
+
declare store: Store;
|
|
49
|
+
declare _storeWrapper: RecordDataStoreWrapper;
|
|
50
|
+
|
|
51
|
+
#instances: Caches = {
|
|
52
|
+
record: new WeakMap<StableRecordIdentifier, RecordInstance>(),
|
|
53
|
+
recordData: new WeakMap<StableRecordIdentifier, RecordData>(),
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
constructor(store: Store) {
|
|
57
|
+
this.store = store;
|
|
58
|
+
|
|
59
|
+
this._storeWrapper = new RecordDataStoreWrapper(this.store);
|
|
60
|
+
this.__recordDataFor = this.__recordDataFor.bind(this);
|
|
61
|
+
|
|
62
|
+
RECORD_REFERENCES._generator = (identifier) => {
|
|
63
|
+
return new RecordReference(this.store, identifier);
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
peek({ identifier, bucket }: { identifier: StableRecordIdentifier; bucket: 'record' }): RecordInstance | undefined;
|
|
67
|
+
peek({ identifier, bucket }: { identifier: StableRecordIdentifier; bucket: 'recordData' }): RecordData | undefined;
|
|
68
|
+
peek({
|
|
69
|
+
identifier,
|
|
70
|
+
bucket,
|
|
71
|
+
}: {
|
|
72
|
+
identifier: StableRecordIdentifier;
|
|
73
|
+
bucket: 'record' | 'recordData';
|
|
74
|
+
}): RecordData | RecordInstance | undefined {
|
|
75
|
+
return this.#instances[bucket]?.get(identifier);
|
|
76
|
+
}
|
|
77
|
+
set({
|
|
78
|
+
identifier,
|
|
79
|
+
bucket,
|
|
80
|
+
value,
|
|
81
|
+
}: {
|
|
82
|
+
identifier: StableRecordIdentifier;
|
|
83
|
+
bucket: 'record';
|
|
84
|
+
value: RecordInstance;
|
|
85
|
+
}): void {
|
|
86
|
+
this.#instances[bucket].set(identifier, value);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
getRecord(identifier: StableRecordIdentifier, properties?: CreateRecordProperties): RecordInstance {
|
|
90
|
+
let record = this.peek({ identifier, bucket: 'record' });
|
|
91
|
+
|
|
92
|
+
if (!record) {
|
|
93
|
+
// TODO store this state somewhere better
|
|
94
|
+
const internalModel = this.getInternalModel(identifier);
|
|
95
|
+
|
|
96
|
+
if (internalModel._isDematerializing) {
|
|
97
|
+
// TODO this should be an assertion, this likely means
|
|
98
|
+
// we have a bug to find wherein our own store is calling this
|
|
99
|
+
// with an identifier that should have already been disconnected.
|
|
100
|
+
// the destroy + fetch again case is likely either preserving the
|
|
101
|
+
// identifier for re-use or failing to unload it.
|
|
102
|
+
return null as unknown as RecordInstance;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// TODO store this state somewhere better
|
|
106
|
+
internalModel.hasRecord = true;
|
|
107
|
+
|
|
108
|
+
if (properties && 'id' in properties) {
|
|
109
|
+
assert(`expected id to be a string or null`, properties.id !== undefined);
|
|
110
|
+
internalModel.setId(properties.id);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
record = this._instantiateRecord(this.getRecordData(identifier), identifier, properties);
|
|
114
|
+
this.set({ identifier, bucket: 'record', value: record });
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return record;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
getReference(identifier: StableRecordIdentifier) {
|
|
121
|
+
return RECORD_REFERENCES.lookup(identifier);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
_fetchDataIfNeededForIdentifier(
|
|
125
|
+
identifier: StableRecordIdentifier,
|
|
126
|
+
options: FindOptions = {}
|
|
127
|
+
): Promise<StableRecordIdentifier> {
|
|
128
|
+
const internalModel = this.getInternalModel(identifier);
|
|
129
|
+
|
|
130
|
+
// pre-loading will change the isEmpty value
|
|
131
|
+
// TODO stpre this state somewhere other than InternalModel
|
|
132
|
+
const { isEmpty, isLoading } = internalModel;
|
|
133
|
+
|
|
134
|
+
if (options.preload) {
|
|
135
|
+
this.store._backburner.join(() => {
|
|
136
|
+
internalModel.preloadData(options.preload);
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
let promise: Promise<StableRecordIdentifier>;
|
|
141
|
+
if (isEmpty) {
|
|
142
|
+
assertIdentifierHasId(identifier);
|
|
143
|
+
|
|
144
|
+
promise = this.store._fetchManager.scheduleFetch(identifier, options);
|
|
145
|
+
} else if (isLoading) {
|
|
146
|
+
promise = this.store._fetchManager.getPendingFetch(identifier, options)!;
|
|
147
|
+
assert(`Expected to find a pending request for a record in the loading state, but found none`, promise);
|
|
148
|
+
} else {
|
|
149
|
+
promise = resolve(identifier);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return promise;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
_instantiateRecord(
|
|
156
|
+
recordData: RecordData,
|
|
157
|
+
identifier: StableRecordIdentifier,
|
|
158
|
+
properties?: { [key: string]: unknown }
|
|
159
|
+
) {
|
|
160
|
+
// assert here
|
|
161
|
+
if (properties !== undefined) {
|
|
162
|
+
assert(
|
|
163
|
+
`You passed '${typeof properties}' as properties for record creation instead of an object.`,
|
|
164
|
+
typeof properties === 'object' && properties !== null
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
const { type } = identifier;
|
|
168
|
+
|
|
169
|
+
// convert relationship Records to RecordDatas before passing to RecordData
|
|
170
|
+
let defs = this.store.getSchemaDefinitionService().relationshipsDefinitionFor({ type });
|
|
171
|
+
|
|
172
|
+
if (defs !== null) {
|
|
173
|
+
let keys = Object.keys(properties);
|
|
174
|
+
let relationshipValue;
|
|
175
|
+
|
|
176
|
+
for (let i = 0; i < keys.length; i++) {
|
|
177
|
+
let prop = keys[i];
|
|
178
|
+
let def = defs[prop];
|
|
179
|
+
|
|
180
|
+
if (def !== undefined) {
|
|
181
|
+
if (def.kind === 'hasMany') {
|
|
182
|
+
if (DEBUG) {
|
|
183
|
+
assertRecordsPassedToHasMany(properties[prop] as RecordInstance[]);
|
|
184
|
+
}
|
|
185
|
+
relationshipValue = extractRecordDatasFromRecords(properties[prop] as RecordInstance[]);
|
|
186
|
+
} else {
|
|
187
|
+
relationshipValue = extractRecordDataFromRecord(properties[prop] as RecordInstance);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
properties[prop] = relationshipValue;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// TODO guard against initRecordOptions no being there
|
|
197
|
+
let createOptions = recordData._initRecordCreateOptions(properties);
|
|
198
|
+
//TODO Igor pass a wrapper instead of RD
|
|
199
|
+
let record = this.store.instantiateRecord(
|
|
200
|
+
identifier,
|
|
201
|
+
createOptions,
|
|
202
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
203
|
+
this.__recordDataFor,
|
|
204
|
+
this.store._notificationManager
|
|
205
|
+
);
|
|
206
|
+
setRecordIdentifier(record, identifier);
|
|
207
|
+
setRecordDataFor(record, recordData);
|
|
208
|
+
StoreMap.set(record, this.store);
|
|
209
|
+
return record;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
_teardownRecord(record: RecordInstance) {
|
|
213
|
+
StoreMap.delete(record);
|
|
214
|
+
// TODO remove identifier:record cache link
|
|
215
|
+
this.store.teardownRecord(record);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
removeRecord(identifier: StableRecordIdentifier): boolean {
|
|
219
|
+
let record = this.peek({ identifier, bucket: 'record' });
|
|
220
|
+
|
|
221
|
+
if (record) {
|
|
222
|
+
this.#instances.record.delete(identifier);
|
|
223
|
+
this._teardownRecord(record);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return !!record;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// TODO move RecordData Cache into InstanceCache
|
|
230
|
+
getRecordData(identifier: StableRecordIdentifier) {
|
|
231
|
+
let recordData = this.peek({ identifier, bucket: 'recordData' });
|
|
232
|
+
|
|
233
|
+
if (!recordData) {
|
|
234
|
+
recordData = this._createRecordData(identifier);
|
|
235
|
+
this.#instances.recordData.set(identifier, recordData);
|
|
236
|
+
this.getInternalModel(identifier).hasRecordData = true;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return recordData;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// TODO move InternalModel cache into InstanceCache
|
|
243
|
+
getInternalModel(identifier: StableRecordIdentifier) {
|
|
244
|
+
return this._internalModelForResource(identifier);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
createSnapshot(identifier: StableRecordIdentifier, options: FindOptions = {}): Snapshot {
|
|
248
|
+
return new Snapshot(options, identifier, this.store);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
__recordDataFor(resource: RecordIdentifier) {
|
|
252
|
+
const identifier = this.store.identifierCache.getOrCreateRecordIdentifier(resource);
|
|
253
|
+
return this.recordDataFor(identifier, false);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// TODO move this to InstanceCache
|
|
257
|
+
_createRecordData(identifier: StableRecordIdentifier): RecordData {
|
|
258
|
+
const recordData = this.store.createRecordDataFor(
|
|
259
|
+
identifier.type,
|
|
260
|
+
identifier.id,
|
|
261
|
+
identifier.lid,
|
|
262
|
+
this._storeWrapper
|
|
263
|
+
);
|
|
264
|
+
setRecordDataFor(identifier, recordData);
|
|
265
|
+
// TODO this is invalid for v2 recordData but required
|
|
266
|
+
// for v1 recordData. Remember to remove this once the
|
|
267
|
+
// RecordData manager handles converting recordData to identifier
|
|
268
|
+
setRecordIdentifier(recordData, identifier);
|
|
269
|
+
return recordData;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// TODO string candidate for early elimination
|
|
273
|
+
_internalModelForResource(resource: ResourceIdentifierObject): InternalModel {
|
|
274
|
+
return internalModelFactoryFor(this.store).getByResource(resource);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
setRecordId(modelName: string, newId: string, clientId: string) {
|
|
278
|
+
internalModelFactoryFor(this.store).setRecordId(modelName, newId, clientId);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
_load(data: ExistingResourceObject): StableExistingRecordIdentifier {
|
|
282
|
+
// TODO type should be pulled from the identifier for debug
|
|
283
|
+
let modelName = data.type;
|
|
284
|
+
assert(
|
|
285
|
+
`You must include an 'id' for ${modelName} in an object passed to 'push'`,
|
|
286
|
+
data.id !== null && data.id !== undefined && data.id !== ''
|
|
287
|
+
);
|
|
288
|
+
assert(
|
|
289
|
+
`You tried to push data with a type '${modelName}' but no model could be found with that name.`,
|
|
290
|
+
this.store.getSchemaDefinitionService().doesTypeExist(modelName)
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
// TODO this should determine identifier via the cache before making assumptions
|
|
294
|
+
const resource = constructResource(normalizeModelName(data.type), ensureStringId(data.id), coerceId(data.lid));
|
|
295
|
+
const maybeIdentifier = this.store.identifierCache.peekRecordIdentifier(resource);
|
|
296
|
+
|
|
297
|
+
let internalModel = internalModelFactoryFor(this.store).lookup(resource, data);
|
|
298
|
+
|
|
299
|
+
// store.push will be from empty
|
|
300
|
+
// findRecord will be from root.loading
|
|
301
|
+
// this cannot be loading state if we do not already have an identifier
|
|
302
|
+
// all else will be updates
|
|
303
|
+
const isLoading = internalModel.isLoading || (!internalModel.isLoaded && maybeIdentifier);
|
|
304
|
+
const isUpdate = internalModel.isEmpty === false && !isLoading;
|
|
305
|
+
|
|
306
|
+
// exclude store.push (root.empty) case
|
|
307
|
+
let identifier = internalModel.identifier;
|
|
308
|
+
if (isUpdate || isLoading) {
|
|
309
|
+
let updatedIdentifier = this.store.identifierCache.updateRecordIdentifier(identifier, data);
|
|
310
|
+
|
|
311
|
+
if (updatedIdentifier !== identifier) {
|
|
312
|
+
// we encountered a merge of identifiers in which
|
|
313
|
+
// two identifiers (and likely two internalModels)
|
|
314
|
+
// existed for the same resource. Now that we have
|
|
315
|
+
// determined the correct identifier to use, make sure
|
|
316
|
+
// that we also use the correct internalModel.
|
|
317
|
+
identifier = updatedIdentifier;
|
|
318
|
+
internalModel = internalModelFactoryFor(this.store).lookup(identifier);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
internalModel.setupData(data);
|
|
323
|
+
|
|
324
|
+
if (!isUpdate) {
|
|
325
|
+
this.store.recordArrayManager.recordDidChange(identifier);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return identifier as StableExistingRecordIdentifier;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
recordDataFor(identifier: StableRecordIdentifier | { type: string }, isCreate: boolean): RecordData {
|
|
332
|
+
let recordData: RecordData;
|
|
333
|
+
if (isCreate === true) {
|
|
334
|
+
// TODO remove once InternalModel is no longer essential to internal state
|
|
335
|
+
// and just build a new identifier directly
|
|
336
|
+
let internalModel = internalModelFactoryFor(this.store).build({ type: identifier.type, id: null });
|
|
337
|
+
let stableIdentifier = internalModel.identifier;
|
|
338
|
+
recordData = this.getRecordData(stableIdentifier);
|
|
339
|
+
recordData.clientDidCreate();
|
|
340
|
+
this.store.recordArrayManager.recordDidChange(stableIdentifier);
|
|
341
|
+
} else {
|
|
342
|
+
// TODO remove once InternalModel is no longer essential to internal state
|
|
343
|
+
internalModelFactoryFor(this.store).lookup(identifier as StableRecordIdentifier);
|
|
344
|
+
recordData = this.getRecordData(identifier as StableRecordIdentifier);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return recordData;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function assertRecordsPassedToHasMany(records: RecordInstance[]) {
|
|
352
|
+
assert(`You must pass an array of records to set a hasMany relationship`, Array.isArray(records));
|
|
353
|
+
assert(
|
|
354
|
+
`All elements of a hasMany relationship must be instances of Model, you passed ${records
|
|
355
|
+
.map((r) => `${typeof r}`)
|
|
356
|
+
.join(', ')}`,
|
|
357
|
+
(function () {
|
|
358
|
+
return records.every((record) => Object.prototype.hasOwnProperty.call(record, '_internalModel') === true);
|
|
359
|
+
})()
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function extractRecordDatasFromRecords(records: RecordInstance[]): RecordData[] {
|
|
364
|
+
return records.map(extractRecordDataFromRecord) as RecordData[];
|
|
365
|
+
}
|
|
366
|
+
type PromiseProxyRecord = { then(): void; get(str: 'content'): RecordInstance | null | undefined };
|
|
367
|
+
|
|
368
|
+
function extractRecordDataFromRecord(recordOrPromiseRecord: PromiseProxyRecord | RecordInstance | null) {
|
|
369
|
+
if (!recordOrPromiseRecord) {
|
|
370
|
+
return null;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (isPromiseRecord(recordOrPromiseRecord)) {
|
|
374
|
+
let content = recordOrPromiseRecord.get && recordOrPromiseRecord.get('content');
|
|
375
|
+
assert(
|
|
376
|
+
'You passed in a promise that did not originate from an EmberData relationship. You can only pass promises that come from a belongsTo or hasMany relationship to the get call.',
|
|
377
|
+
content !== undefined
|
|
378
|
+
);
|
|
379
|
+
return content ? recordDataFor(content) : null;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return recordDataFor(recordOrPromiseRecord);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function isPromiseRecord(record: PromiseProxyRecord | RecordInstance): record is PromiseProxyRecord {
|
|
386
|
+
return !!record.then;
|
|
387
|
+
}
|
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
import { assert, warn } from '@ember/debug';
|
|
2
|
-
import { isNone } from '@ember/utils';
|
|
3
2
|
import { DEBUG } from '@glimmer/env';
|
|
4
3
|
|
|
5
|
-
import type { IdentifierCache } from '../../identifiers/cache';
|
|
6
4
|
import type {
|
|
7
5
|
ExistingResourceObject,
|
|
8
6
|
NewResourceIdentifierObject,
|
|
9
7
|
ResourceIdentifierObject,
|
|
10
|
-
} from '
|
|
11
|
-
import type { StableRecordIdentifier } from '
|
|
12
|
-
import type { RecordData } from '
|
|
13
|
-
import type { RecordInstance } from '
|
|
14
|
-
|
|
15
|
-
import type
|
|
16
|
-
import
|
|
17
|
-
import
|
|
18
|
-
import
|
|
19
|
-
import
|
|
8
|
+
} from '@ember-data/types/q/ember-data-json-api';
|
|
9
|
+
import type { StableRecordIdentifier } from '@ember-data/types/q/identifier';
|
|
10
|
+
import type { RecordData } from '@ember-data/types/q/record-data';
|
|
11
|
+
import type { RecordInstance } from '@ember-data/types/q/record-instance';
|
|
12
|
+
|
|
13
|
+
import type Store from './core-store';
|
|
14
|
+
import type { IdentifierCache } from './identifier-cache';
|
|
15
|
+
import IdentityMap from './identity-map';
|
|
16
|
+
import type InternalModelMap from './internal-model-map';
|
|
17
|
+
import InternalModel from './model/internal-model';
|
|
18
|
+
import constructResource from './utils/construct-resource';
|
|
19
|
+
import WeakCache from './weak-cache';
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
@module @ember-data/store
|
|
23
23
|
*/
|
|
24
|
-
const FactoryCache = new WeakCache<
|
|
25
|
-
FactoryCache._generator = (store:
|
|
24
|
+
const FactoryCache = new WeakCache<Store, InternalModelFactory>(DEBUG ? 'internal-model-factory' : '');
|
|
25
|
+
FactoryCache._generator = (store: Store) => {
|
|
26
26
|
return new InternalModelFactory(store);
|
|
27
27
|
};
|
|
28
28
|
type NewResourceInfo = { type: string; id: string | null };
|
|
@@ -66,7 +66,7 @@ export function recordIdentifierFor(record: RecordInstance | RecordData): Stable
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
export function setRecordIdentifier(record: RecordInstance | RecordData, identifier: StableRecordIdentifier): void {
|
|
69
|
-
if (DEBUG && RecordCache.has(record)) {
|
|
69
|
+
if (DEBUG && RecordCache.has(record) && RecordCache.get(record) !== identifier) {
|
|
70
70
|
throw new Error(`${record} was already assigned an identifier`);
|
|
71
71
|
}
|
|
72
72
|
|
|
@@ -81,7 +81,7 @@ export function setRecordIdentifier(record: RecordInstance | RecordData, identif
|
|
|
81
81
|
RecordCache.set(record, identifier);
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
export function internalModelFactoryFor(store:
|
|
84
|
+
export function internalModelFactoryFor(store: Store): InternalModelFactory {
|
|
85
85
|
return FactoryCache.lookup(store);
|
|
86
86
|
}
|
|
87
87
|
|
|
@@ -96,9 +96,9 @@ export function internalModelFactoryFor(store: CoreStore): InternalModelFactory
|
|
|
96
96
|
export default class InternalModelFactory {
|
|
97
97
|
declare _identityMap: IdentityMap;
|
|
98
98
|
declare identifierCache: IdentifierCache;
|
|
99
|
-
declare store:
|
|
99
|
+
declare store: Store;
|
|
100
100
|
|
|
101
|
-
constructor(store:
|
|
101
|
+
constructor(store: Store) {
|
|
102
102
|
this.store = store;
|
|
103
103
|
this.identifierCache = store.identifierCache;
|
|
104
104
|
this.identifierCache.__configureMerge((identifier, matchedIdentifier, resourceData) => {
|
|
@@ -119,6 +119,10 @@ export default class InternalModelFactory {
|
|
|
119
119
|
// we cannot merge internalModels when both have records
|
|
120
120
|
// (this may not be strictly true, we could probably swap the internalModel the record points at)
|
|
121
121
|
if (im && otherIm && im.hasRecord && otherIm.hasRecord) {
|
|
122
|
+
// TODO we probably don't need to throw these errors anymore
|
|
123
|
+
// once InternalModel is fully removed, as we can just "swap"
|
|
124
|
+
// what data source the abandoned record points at so long as
|
|
125
|
+
// it itself is not retained by the store in any way.
|
|
122
126
|
if ('id' in resourceData) {
|
|
123
127
|
throw new Error(
|
|
124
128
|
`Failed to update the 'id' for the RecordIdentifier '${identifier.type}:${identifier.id} (${identifier.lid})' to '${resourceData.id}', because that id is already in use by '${matchedIdentifier.type}:${matchedIdentifier.id} (${matchedIdentifier.lid})'`
|
|
@@ -150,6 +154,7 @@ export default class InternalModelFactory {
|
|
|
150
154
|
im = otherIm;
|
|
151
155
|
// TODO do we need to notify the id change?
|
|
152
156
|
im._id = intendedIdentifier.id;
|
|
157
|
+
im.identifier = intendedIdentifier;
|
|
153
158
|
map.add(im, intendedIdentifier.lid);
|
|
154
159
|
|
|
155
160
|
// just use im
|
|
@@ -268,10 +273,11 @@ export default class InternalModelFactory {
|
|
|
268
273
|
|
|
269
274
|
assert(
|
|
270
275
|
`'${modelName}' was saved to the server, but the response returned the new id '${id}', which has already been used with another record.'`,
|
|
271
|
-
|
|
276
|
+
!existingInternalModel || existingInternalModel === internalModel
|
|
272
277
|
);
|
|
273
278
|
|
|
274
279
|
if (identifier.id === null) {
|
|
280
|
+
// TODO potentially this needs to handle merged result
|
|
275
281
|
this.identifierCache.updateRecordIdentifier(identifier, { type, id });
|
|
276
282
|
}
|
|
277
283
|
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { assert } from '@ember/debug';
|
|
2
2
|
|
|
3
|
-
import type { StableRecordIdentifier } from '
|
|
4
|
-
import type { ConfidentDict } from '
|
|
3
|
+
import type { StableRecordIdentifier } from '@ember-data/types/q/identifier';
|
|
4
|
+
import type { ConfidentDict } from '@ember-data/types/q/utils';
|
|
5
|
+
|
|
5
6
|
import InternalModel from './model/internal-model';
|
|
6
7
|
|
|
7
8
|
/**
|
|
@@ -19,10 +20,13 @@ import InternalModel from './model/internal-model';
|
|
|
19
20
|
@internal
|
|
20
21
|
*/
|
|
21
22
|
export default class InternalModelMap {
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
_idToModel: ConfidentDict<InternalModel> = Object.create(null);
|
|
24
|
+
_models: InternalModel[] = [];
|
|
25
|
+
modelName: string;
|
|
24
26
|
|
|
25
|
-
constructor(
|
|
27
|
+
constructor(modelName: string) {
|
|
28
|
+
this.modelName = modelName;
|
|
29
|
+
}
|
|
26
30
|
|
|
27
31
|
get(id: string): InternalModel | null {
|
|
28
32
|
return this._idToModel[id] || null;
|