@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
|
@@ -1,736 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
@module @ember-data/store
|
|
3
|
-
*/
|
|
4
|
-
import { assert } from '@ember/debug';
|
|
5
|
-
|
|
6
|
-
/*
|
|
7
|
-
This file encapsulates the various states that a record can transition
|
|
8
|
-
through during its lifecycle.
|
|
9
|
-
*/
|
|
10
|
-
/**
|
|
11
|
-
### State
|
|
12
|
-
|
|
13
|
-
Each record has a `currentState` property that explicitly tracks what
|
|
14
|
-
state a record is in at any given time. For instance, if a record is
|
|
15
|
-
newly created and has not yet been sent to the adapter to be saved,
|
|
16
|
-
it would be in the `root.loaded.created.uncommitted` state. If a
|
|
17
|
-
record has had local modifications made to it that are in the
|
|
18
|
-
process of being saved, the record would be in the
|
|
19
|
-
`root.loaded.updated.inFlight` state. (This state path will be
|
|
20
|
-
explained in more detail below.)
|
|
21
|
-
|
|
22
|
-
Events are sent by the record or its store to the record's
|
|
23
|
-
`currentState` property. How the state reacts to these events is
|
|
24
|
-
dependent on which state it is in. In some states, certain events
|
|
25
|
-
will be invalid and will cause an exception to be raised.
|
|
26
|
-
|
|
27
|
-
States are hierarchical and every state is a sub-state of the
|
|
28
|
-
`RootState`. For example, a record can be in the
|
|
29
|
-
`root.deleted.uncommitted` state then transitions into the
|
|
30
|
-
`root.deleted.inFlight` state. If a child state does not implement
|
|
31
|
-
an event handler, the state manager will attempt to invoke the event
|
|
32
|
-
on all parent states until the root state is reached. The state
|
|
33
|
-
hierarchy of a record is described in terms of a path string. You
|
|
34
|
-
can determine a record's current state by getting the state's
|
|
35
|
-
`stateName` property:
|
|
36
|
-
|
|
37
|
-
```javascript
|
|
38
|
-
record.get('currentState.stateName');
|
|
39
|
-
//=> "root.created.uncommitted"
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
The hierarchy of valid states that ship with ember data looks like
|
|
43
|
-
this:
|
|
44
|
-
|
|
45
|
-
```text
|
|
46
|
-
* root
|
|
47
|
-
* deleted
|
|
48
|
-
* saved
|
|
49
|
-
* uncommitted
|
|
50
|
-
* inFlight
|
|
51
|
-
* empty
|
|
52
|
-
* loaded
|
|
53
|
-
* created
|
|
54
|
-
* uncommitted
|
|
55
|
-
* inFlight
|
|
56
|
-
* saved
|
|
57
|
-
* updated
|
|
58
|
-
* uncommitted
|
|
59
|
-
* inFlight
|
|
60
|
-
* loading
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
The `Model` states are themselves stateless. What that means is
|
|
64
|
-
that, the hierarchical states that each of *those* points to is a
|
|
65
|
-
shared data structure. For performance reasons, instead of each
|
|
66
|
-
record getting its own copy of the hierarchy of states, each record
|
|
67
|
-
points to this global, immutable shared instance. How does a state
|
|
68
|
-
know which record it should be acting on? We pass the record
|
|
69
|
-
instance into the state's event handlers as the first argument.
|
|
70
|
-
|
|
71
|
-
The record passed as the first parameter is where you should stash
|
|
72
|
-
state about the record if needed; you should never store data on the state
|
|
73
|
-
object itself.
|
|
74
|
-
|
|
75
|
-
### Events and Flags
|
|
76
|
-
|
|
77
|
-
A state may implement zero or more events and flags.
|
|
78
|
-
|
|
79
|
-
#### Events
|
|
80
|
-
|
|
81
|
-
Events are named functions that are invoked when sent to a record. The
|
|
82
|
-
record will first look for a method with the given name on the
|
|
83
|
-
current state. If no method is found, it will search the current
|
|
84
|
-
state's parent, and then its grandparent, and so on until reaching
|
|
85
|
-
the top of the hierarchy. If the root is reached without an event
|
|
86
|
-
handler being found, an exception will be raised. This can be very
|
|
87
|
-
helpful when debugging new features.
|
|
88
|
-
|
|
89
|
-
Here's an example implementation of a state with a `myEvent` event handler:
|
|
90
|
-
|
|
91
|
-
```javascript
|
|
92
|
-
aState: State.create({
|
|
93
|
-
myEvent: function(manager, param) {
|
|
94
|
-
console.log("Received myEvent with", param);
|
|
95
|
-
}
|
|
96
|
-
})
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
To trigger this event:
|
|
100
|
-
|
|
101
|
-
```javascript
|
|
102
|
-
record.send('myEvent', 'foo');
|
|
103
|
-
//=> "Received myEvent with foo"
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
Note that an optional parameter can be sent to a record's `send()` method,
|
|
107
|
-
which will be passed as the second parameter to the event handler.
|
|
108
|
-
|
|
109
|
-
Events should transition to a different state if appropriate. This can be
|
|
110
|
-
done by calling the record's `transitionTo()` method with a path to the
|
|
111
|
-
desired state. The state manager will attempt to resolve the state path
|
|
112
|
-
relative to the current state. If no state is found at that path, it will
|
|
113
|
-
attempt to resolve it relative to the current state's parent, and then its
|
|
114
|
-
parent, and so on until the root is reached. For example, imagine a hierarchy
|
|
115
|
-
like this:
|
|
116
|
-
|
|
117
|
-
* created
|
|
118
|
-
* uncommitted <-- currentState
|
|
119
|
-
* inFlight
|
|
120
|
-
* updated
|
|
121
|
-
* inFlight
|
|
122
|
-
|
|
123
|
-
If we are currently in the `uncommitted` state, calling
|
|
124
|
-
`transitionTo('inFlight')` would transition to the `created.inFlight` state,
|
|
125
|
-
while calling `transitionTo('updated.inFlight')` would transition to
|
|
126
|
-
the `updated.inFlight` state.
|
|
127
|
-
|
|
128
|
-
Remember that *only events* should ever cause a state transition. You should
|
|
129
|
-
never call `transitionTo()` from outside a state's event handler. If you are
|
|
130
|
-
tempted to do so, create a new event and send that to the state manager.
|
|
131
|
-
|
|
132
|
-
#### Flags
|
|
133
|
-
|
|
134
|
-
Flags are Boolean values that can be used to introspect a record's current
|
|
135
|
-
state in a more user-friendly way than examining its state path. For example,
|
|
136
|
-
instead of doing this:
|
|
137
|
-
|
|
138
|
-
```javascript
|
|
139
|
-
var statePath = record.get('stateManager.currentPath');
|
|
140
|
-
if (statePath === 'created.inFlight') {
|
|
141
|
-
doSomething();
|
|
142
|
-
}
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
You can say:
|
|
146
|
-
|
|
147
|
-
```javascript
|
|
148
|
-
if (record.get('isNew') && record.get('isSaving')) {
|
|
149
|
-
doSomething();
|
|
150
|
-
}
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
If your state does not set a value for a given flag, the value will
|
|
154
|
-
be inherited from its parent (or the first place in the state hierarchy
|
|
155
|
-
where it is defined).
|
|
156
|
-
|
|
157
|
-
The current set of flags are defined below. If you want to add a new flag,
|
|
158
|
-
in addition to the area below, you will also need to declare it in the
|
|
159
|
-
`Model` class.
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
* [isEmpty](Model/properties/isEmpty?anchor=isEmpty)
|
|
163
|
-
* [isLoading](Model/properties/isLoading?anchor=isLoading)
|
|
164
|
-
* [isLoaded](Model/properties/isLoaded?anchor=isLoaded)
|
|
165
|
-
* [hasDirtyAttributes](Model/properties/hasDirtyAttributes?anchor=hasDirtyAttributes)
|
|
166
|
-
* [isSaving](Model/properties/isSaving?anchor=isSaving)
|
|
167
|
-
* [isDeleted](Model/properties/isDeleted?anchor=isDeleted)
|
|
168
|
-
* [isNew](Model/properties/isNew?anchor=isNew)
|
|
169
|
-
* [isValid](Model/properties/isValid?anchor=isValid)
|
|
170
|
-
|
|
171
|
-
@class RootState
|
|
172
|
-
@internal
|
|
173
|
-
*/
|
|
174
|
-
|
|
175
|
-
function didSetProperty(internalModel, context) {
|
|
176
|
-
if (context.isDirty) {
|
|
177
|
-
internalModel.send('becomeDirty');
|
|
178
|
-
} else {
|
|
179
|
-
internalModel.send('propertyWasReset');
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Implementation notes:
|
|
184
|
-
//
|
|
185
|
-
// Each state has a boolean value for all of the following flags:
|
|
186
|
-
//
|
|
187
|
-
// * isLoaded: The record has a populated `data` property. When a
|
|
188
|
-
// record is loaded via `store.find`, `isLoaded` is false
|
|
189
|
-
// until the adapter sets it. When a record is created locally,
|
|
190
|
-
// its `isLoaded` property is always true.
|
|
191
|
-
// * isDirty: The record has local changes that have not yet been
|
|
192
|
-
// saved by the adapter. This includes records that have been
|
|
193
|
-
// created (but not yet saved) or deleted.
|
|
194
|
-
// * isSaving: The record has been committed, but
|
|
195
|
-
// the adapter has not yet acknowledged that the changes have
|
|
196
|
-
// been persisted to the backend.
|
|
197
|
-
// * isDeleted: The record was marked for deletion. When `isDeleted`
|
|
198
|
-
// is true and `isDirty` is true, the record is deleted locally
|
|
199
|
-
// but the deletion was not yet persisted. When `isSaving` is
|
|
200
|
-
// true, the change is in-flight. When both `isDirty` and
|
|
201
|
-
// `isSaving` are false, the change has persisted.
|
|
202
|
-
// * isNew: The record was created on the client and the adapter
|
|
203
|
-
// did not yet report that it was successfully saved.
|
|
204
|
-
// * isValid: The adapter did not report any server-side validation
|
|
205
|
-
// failures.
|
|
206
|
-
|
|
207
|
-
// The dirty state is a abstract state whose functionality is
|
|
208
|
-
// shared between the `created` and `updated` states.
|
|
209
|
-
//
|
|
210
|
-
// The deleted state shares the `isDirty` flag with the
|
|
211
|
-
// subclasses of `DirtyState`, but with a very different
|
|
212
|
-
// implementation.
|
|
213
|
-
//
|
|
214
|
-
// Dirty states have three child states:
|
|
215
|
-
//
|
|
216
|
-
// `uncommitted`: the store has not yet handed off the record
|
|
217
|
-
// to be saved.
|
|
218
|
-
// `inFlight`: the store has handed off the record to be saved,
|
|
219
|
-
// but the adapter has not yet acknowledged success.
|
|
220
|
-
// `invalid`: the record has invalid information and cannot be
|
|
221
|
-
// sent to the adapter yet.
|
|
222
|
-
const DirtyState = {
|
|
223
|
-
initialState: 'uncommitted',
|
|
224
|
-
|
|
225
|
-
// FLAGS
|
|
226
|
-
isDirty: true,
|
|
227
|
-
|
|
228
|
-
// SUBSTATES
|
|
229
|
-
|
|
230
|
-
// When a record first becomes dirty, it is `uncommitted`.
|
|
231
|
-
// This means that there are local pending changes, but they
|
|
232
|
-
// have not yet begun to be saved, and are not invalid.
|
|
233
|
-
uncommitted: {
|
|
234
|
-
// EVENTS
|
|
235
|
-
didSetProperty,
|
|
236
|
-
|
|
237
|
-
//TODO(Igor) reloading now triggers a
|
|
238
|
-
//loadingData event, though it seems fine?
|
|
239
|
-
loadingData() {},
|
|
240
|
-
|
|
241
|
-
propertyWasReset(internalModel, name) {
|
|
242
|
-
if (!internalModel.hasChangedAttributes()) {
|
|
243
|
-
internalModel.send('rolledBack');
|
|
244
|
-
}
|
|
245
|
-
},
|
|
246
|
-
|
|
247
|
-
pushedData(internalModel) {
|
|
248
|
-
if (!internalModel.hasChangedAttributes()) {
|
|
249
|
-
internalModel.transitionTo('loaded.saved');
|
|
250
|
-
}
|
|
251
|
-
},
|
|
252
|
-
|
|
253
|
-
becomeDirty() {},
|
|
254
|
-
|
|
255
|
-
willCommit(internalModel) {
|
|
256
|
-
internalModel.transitionTo('inFlight');
|
|
257
|
-
},
|
|
258
|
-
|
|
259
|
-
reloadRecord(internalModel, { resolve, options }) {
|
|
260
|
-
resolve(internalModel.store._reloadRecord(internalModel, options));
|
|
261
|
-
},
|
|
262
|
-
|
|
263
|
-
rolledBack(internalModel) {
|
|
264
|
-
internalModel.transitionTo('loaded.saved');
|
|
265
|
-
},
|
|
266
|
-
|
|
267
|
-
becameInvalid(internalModel) {
|
|
268
|
-
internalModel.transitionTo('invalid');
|
|
269
|
-
},
|
|
270
|
-
|
|
271
|
-
rollback(internalModel) {
|
|
272
|
-
internalModel.rollbackAttributes();
|
|
273
|
-
},
|
|
274
|
-
},
|
|
275
|
-
|
|
276
|
-
// Once a record has been handed off to the adapter to be
|
|
277
|
-
// saved, it is in the 'in flight' state. Changes to the
|
|
278
|
-
// record cannot be made during this window.
|
|
279
|
-
inFlight: {
|
|
280
|
-
// FLAGS
|
|
281
|
-
isSaving: true,
|
|
282
|
-
|
|
283
|
-
// EVENTS
|
|
284
|
-
didSetProperty,
|
|
285
|
-
becomeDirty() {},
|
|
286
|
-
pushedData() {},
|
|
287
|
-
|
|
288
|
-
unloadRecord: assertAgainstUnloadRecord,
|
|
289
|
-
|
|
290
|
-
// TODO: More robust semantics around save-while-in-flight
|
|
291
|
-
willCommit() {},
|
|
292
|
-
|
|
293
|
-
didCommit(internalModel) {
|
|
294
|
-
internalModel.transitionTo('saved');
|
|
295
|
-
internalModel.send('invokeLifecycleCallbacks', this.dirtyType);
|
|
296
|
-
},
|
|
297
|
-
|
|
298
|
-
rolledBack() {},
|
|
299
|
-
|
|
300
|
-
becameInvalid(internalModel) {
|
|
301
|
-
internalModel.transitionTo('invalid');
|
|
302
|
-
internalModel.send('invokeLifecycleCallbacks');
|
|
303
|
-
},
|
|
304
|
-
|
|
305
|
-
becameError(internalModel) {
|
|
306
|
-
internalModel.transitionTo('uncommitted');
|
|
307
|
-
},
|
|
308
|
-
},
|
|
309
|
-
|
|
310
|
-
// A record is in the `invalid` if the adapter has indicated
|
|
311
|
-
// the the record failed server-side invalidations.
|
|
312
|
-
invalid: {
|
|
313
|
-
// FLAGS
|
|
314
|
-
isValid: false,
|
|
315
|
-
|
|
316
|
-
// EVENTS
|
|
317
|
-
deleteRecord(internalModel) {
|
|
318
|
-
internalModel.transitionTo('deleted.uncommitted');
|
|
319
|
-
},
|
|
320
|
-
|
|
321
|
-
didSetProperty(internalModel, context) {
|
|
322
|
-
internalModel.getRecord().errors._remove(context.name);
|
|
323
|
-
|
|
324
|
-
didSetProperty(internalModel, context);
|
|
325
|
-
|
|
326
|
-
if (!internalModel.hasErrors()) {
|
|
327
|
-
this.becameValid(internalModel);
|
|
328
|
-
}
|
|
329
|
-
},
|
|
330
|
-
|
|
331
|
-
becameInvalid() {},
|
|
332
|
-
becomeDirty() {},
|
|
333
|
-
pushedData() {},
|
|
334
|
-
|
|
335
|
-
willCommit(internalModel) {
|
|
336
|
-
clearErrorMessages(internalModel);
|
|
337
|
-
internalModel.transitionTo('inFlight');
|
|
338
|
-
},
|
|
339
|
-
|
|
340
|
-
rolledBack(internalModel) {
|
|
341
|
-
clearErrorMessages(internalModel);
|
|
342
|
-
internalModel.transitionTo('loaded.saved');
|
|
343
|
-
},
|
|
344
|
-
|
|
345
|
-
becameValid(internalModel) {
|
|
346
|
-
internalModel.transitionTo('uncommitted');
|
|
347
|
-
},
|
|
348
|
-
|
|
349
|
-
invokeLifecycleCallbacks() {},
|
|
350
|
-
},
|
|
351
|
-
};
|
|
352
|
-
|
|
353
|
-
// The created and updated states are created outside the state
|
|
354
|
-
// chart so we can reopen their substates and add mixins as
|
|
355
|
-
// necessary.
|
|
356
|
-
|
|
357
|
-
function deepClone(object) {
|
|
358
|
-
const clone = {};
|
|
359
|
-
let value;
|
|
360
|
-
|
|
361
|
-
for (let prop in object) {
|
|
362
|
-
value = object[prop];
|
|
363
|
-
if (value && typeof value === 'object') {
|
|
364
|
-
clone[prop] = deepClone(value);
|
|
365
|
-
} else {
|
|
366
|
-
clone[prop] = value;
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
return clone;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
function mixin(original, hash) {
|
|
374
|
-
for (let prop in hash) {
|
|
375
|
-
original[prop] = hash[prop];
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
return original;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
function dirtyState(options) {
|
|
382
|
-
var newState = deepClone(DirtyState);
|
|
383
|
-
return mixin(newState, options);
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
const createdState = dirtyState({
|
|
387
|
-
dirtyType: 'created',
|
|
388
|
-
// FLAGS
|
|
389
|
-
isNew: true,
|
|
390
|
-
|
|
391
|
-
setup(internalModel) {
|
|
392
|
-
internalModel.store.recordArrayManager.recordDidChange(internalModel.identifier);
|
|
393
|
-
},
|
|
394
|
-
});
|
|
395
|
-
|
|
396
|
-
createdState.invalid.rolledBack = function (internalModel) {
|
|
397
|
-
internalModel.transitionTo('deleted.saved');
|
|
398
|
-
};
|
|
399
|
-
|
|
400
|
-
createdState.uncommitted.rolledBack = function (internalModel) {
|
|
401
|
-
internalModel.transitionTo('deleted.saved');
|
|
402
|
-
};
|
|
403
|
-
|
|
404
|
-
const updatedState = dirtyState({
|
|
405
|
-
dirtyType: 'updated',
|
|
406
|
-
});
|
|
407
|
-
|
|
408
|
-
function createdStateDeleteRecord(internalModel) {
|
|
409
|
-
internalModel.transitionTo('deleted.saved');
|
|
410
|
-
internalModel.send('invokeLifecycleCallbacks');
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
createdState.uncommitted.deleteRecord = createdStateDeleteRecord;
|
|
414
|
-
|
|
415
|
-
createdState.invalid.deleteRecord = createdStateDeleteRecord;
|
|
416
|
-
|
|
417
|
-
createdState.uncommitted.rollback = function (internalModel) {
|
|
418
|
-
DirtyState.uncommitted.rollback.apply(this, arguments);
|
|
419
|
-
internalModel.transitionTo('deleted.saved');
|
|
420
|
-
};
|
|
421
|
-
|
|
422
|
-
createdState.uncommitted.pushedData = function (internalModel) {
|
|
423
|
-
// TODO @runspired consider where to do this once we kill off state machine
|
|
424
|
-
internalModel.store._notificationManager.notify(internalModel.identifier, 'identity');
|
|
425
|
-
internalModel.transitionTo('loaded.updated.uncommitted');
|
|
426
|
-
};
|
|
427
|
-
|
|
428
|
-
createdState.uncommitted.propertyWasReset = function () {};
|
|
429
|
-
|
|
430
|
-
function assertAgainstUnloadRecord(internalModel) {
|
|
431
|
-
assert('You can only unload a record which is not inFlight. `' + internalModel + '`', false);
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
updatedState.invalid.becameValid = function (internalModel) {
|
|
435
|
-
// we're eagerly transition into the loaded.saved state, even though we could
|
|
436
|
-
// be still dirty; but the setup hook of the loaded.saved state checks for
|
|
437
|
-
// dirty attributes and transitions into the corresponding dirty state
|
|
438
|
-
internalModel.transitionTo('loaded.saved');
|
|
439
|
-
};
|
|
440
|
-
|
|
441
|
-
updatedState.inFlight.unloadRecord = assertAgainstUnloadRecord;
|
|
442
|
-
|
|
443
|
-
updatedState.uncommitted.deleteRecord = function (internalModel) {
|
|
444
|
-
internalModel.transitionTo('deleted.uncommitted');
|
|
445
|
-
};
|
|
446
|
-
|
|
447
|
-
updatedState.invalid.rolledBack = function (internalModel) {
|
|
448
|
-
clearErrorMessages(internalModel);
|
|
449
|
-
internalModel.transitionTo('loaded.saved');
|
|
450
|
-
};
|
|
451
|
-
|
|
452
|
-
const RootState = {
|
|
453
|
-
// FLAGS
|
|
454
|
-
isEmpty: false,
|
|
455
|
-
isLoading: false,
|
|
456
|
-
isLoaded: false,
|
|
457
|
-
isDirty: false,
|
|
458
|
-
isSaving: false,
|
|
459
|
-
isDeleted: false,
|
|
460
|
-
isNew: false,
|
|
461
|
-
isValid: true,
|
|
462
|
-
|
|
463
|
-
// DEFAULT EVENTS
|
|
464
|
-
|
|
465
|
-
// Trying to roll back if you're not in the dirty state
|
|
466
|
-
// doesn't change your state. For example, if you're in the
|
|
467
|
-
// in-flight state, rolling back the record doesn't move
|
|
468
|
-
// you out of the in-flight state.
|
|
469
|
-
rolledBack() {},
|
|
470
|
-
unloadRecord(internalModel) {},
|
|
471
|
-
|
|
472
|
-
propertyWasReset() {},
|
|
473
|
-
|
|
474
|
-
// SUBSTATES
|
|
475
|
-
|
|
476
|
-
// A record begins its lifecycle in the `empty` state.
|
|
477
|
-
// If its data will come from the adapter, it will
|
|
478
|
-
// transition into the `loading` state. Otherwise, if
|
|
479
|
-
// the record is being created on the client, it will
|
|
480
|
-
// transition into the `created` state.
|
|
481
|
-
empty: {
|
|
482
|
-
isEmpty: true,
|
|
483
|
-
|
|
484
|
-
// EVENTS
|
|
485
|
-
loadingData(internalModel, promise) {
|
|
486
|
-
internalModel.transitionTo('loading');
|
|
487
|
-
},
|
|
488
|
-
|
|
489
|
-
loadedData(internalModel) {
|
|
490
|
-
internalModel.transitionTo('loaded.created.uncommitted');
|
|
491
|
-
},
|
|
492
|
-
|
|
493
|
-
pushedData(internalModel) {
|
|
494
|
-
internalModel.transitionTo('loaded.saved');
|
|
495
|
-
},
|
|
496
|
-
|
|
497
|
-
// Record is already in an empty state, triggering transition to empty here
|
|
498
|
-
// produce an error.
|
|
499
|
-
notFound() {},
|
|
500
|
-
},
|
|
501
|
-
|
|
502
|
-
// A record enters this state when the store asks
|
|
503
|
-
// the adapter for its data. It remains in this state
|
|
504
|
-
// until the adapter provides the requested data.
|
|
505
|
-
//
|
|
506
|
-
// Usually, this process is asynchronous, using an
|
|
507
|
-
// XHR to retrieve the data.
|
|
508
|
-
loading: {
|
|
509
|
-
// FLAGS
|
|
510
|
-
isLoading: true,
|
|
511
|
-
|
|
512
|
-
exit(internalModel) {
|
|
513
|
-
internalModel._promiseProxy = null;
|
|
514
|
-
},
|
|
515
|
-
|
|
516
|
-
loadingData() {},
|
|
517
|
-
|
|
518
|
-
// EVENTS
|
|
519
|
-
pushedData(internalModel) {
|
|
520
|
-
internalModel.transitionTo('loaded.saved');
|
|
521
|
-
//TODO this seems out of place here
|
|
522
|
-
internalModel.didCleanError();
|
|
523
|
-
},
|
|
524
|
-
|
|
525
|
-
becameError() {},
|
|
526
|
-
|
|
527
|
-
notFound(internalModel) {
|
|
528
|
-
internalModel.transitionTo('empty');
|
|
529
|
-
},
|
|
530
|
-
},
|
|
531
|
-
|
|
532
|
-
// A record enters this state when its data is populated.
|
|
533
|
-
// Most of a record's lifecycle is spent inside substates
|
|
534
|
-
// of the `loaded` state.
|
|
535
|
-
loaded: {
|
|
536
|
-
initialState: 'saved',
|
|
537
|
-
|
|
538
|
-
// FLAGS
|
|
539
|
-
isLoaded: true,
|
|
540
|
-
|
|
541
|
-
//TODO(Igor) Reloading now triggers a loadingData event,
|
|
542
|
-
//but it should be ok?
|
|
543
|
-
loadingData() {},
|
|
544
|
-
|
|
545
|
-
// SUBSTATES
|
|
546
|
-
|
|
547
|
-
// If there are no local changes to a record, it remains
|
|
548
|
-
// in the `saved` state.
|
|
549
|
-
saved: {
|
|
550
|
-
setup(internalModel) {
|
|
551
|
-
if (internalModel.hasChangedAttributes()) {
|
|
552
|
-
internalModel.adapterDidDirty();
|
|
553
|
-
}
|
|
554
|
-
},
|
|
555
|
-
|
|
556
|
-
// EVENTS
|
|
557
|
-
didSetProperty,
|
|
558
|
-
|
|
559
|
-
pushedData() {},
|
|
560
|
-
|
|
561
|
-
becomeDirty(internalModel) {
|
|
562
|
-
internalModel.transitionTo('updated.uncommitted');
|
|
563
|
-
},
|
|
564
|
-
|
|
565
|
-
willCommit(internalModel) {
|
|
566
|
-
internalModel.transitionTo('updated.inFlight');
|
|
567
|
-
},
|
|
568
|
-
|
|
569
|
-
reloadRecord() {},
|
|
570
|
-
|
|
571
|
-
deleteRecord(internalModel) {
|
|
572
|
-
internalModel.transitionTo('deleted.uncommitted');
|
|
573
|
-
},
|
|
574
|
-
|
|
575
|
-
unloadRecord(internalModel) {},
|
|
576
|
-
|
|
577
|
-
didCommit() {},
|
|
578
|
-
|
|
579
|
-
// loaded.saved.notFound would be triggered by a failed
|
|
580
|
-
// `reload()` on an unchanged record
|
|
581
|
-
notFound() {},
|
|
582
|
-
},
|
|
583
|
-
|
|
584
|
-
// A record is in this state after it has been locally
|
|
585
|
-
// created but before the adapter has indicated that
|
|
586
|
-
// it has been saved.
|
|
587
|
-
created: createdState,
|
|
588
|
-
|
|
589
|
-
// A record is in this state if it has already been
|
|
590
|
-
// saved to the server, but there are new local changes
|
|
591
|
-
// that have not yet been saved.
|
|
592
|
-
updated: updatedState,
|
|
593
|
-
},
|
|
594
|
-
|
|
595
|
-
// A record is in this state if it was deleted from the store.
|
|
596
|
-
deleted: {
|
|
597
|
-
initialState: 'uncommitted',
|
|
598
|
-
dirtyType: 'deleted',
|
|
599
|
-
|
|
600
|
-
// FLAGS
|
|
601
|
-
isDeleted: true,
|
|
602
|
-
isLoaded: true,
|
|
603
|
-
isDirty: true,
|
|
604
|
-
|
|
605
|
-
// TRANSITIONS
|
|
606
|
-
setup(internalModel) {
|
|
607
|
-
internalModel.store.recordArrayManager.recordDidChange(internalModel.identifier);
|
|
608
|
-
},
|
|
609
|
-
|
|
610
|
-
// SUBSTATES
|
|
611
|
-
|
|
612
|
-
// When a record is deleted, it enters the `start`
|
|
613
|
-
// state. It will exit this state when the record
|
|
614
|
-
// starts to commit.
|
|
615
|
-
uncommitted: {
|
|
616
|
-
// EVENTS
|
|
617
|
-
|
|
618
|
-
willCommit(internalModel) {
|
|
619
|
-
internalModel.transitionTo('inFlight');
|
|
620
|
-
},
|
|
621
|
-
|
|
622
|
-
rollback(internalModel) {
|
|
623
|
-
internalModel.rollbackAttributes();
|
|
624
|
-
},
|
|
625
|
-
|
|
626
|
-
pushedData() {},
|
|
627
|
-
becomeDirty() {},
|
|
628
|
-
deleteRecord() {},
|
|
629
|
-
|
|
630
|
-
rolledBack(internalModel) {
|
|
631
|
-
internalModel.transitionTo('loaded.saved');
|
|
632
|
-
},
|
|
633
|
-
},
|
|
634
|
-
|
|
635
|
-
// After a record starts committing, but
|
|
636
|
-
// before the adapter indicates that the deletion
|
|
637
|
-
// has saved to the server, a record is in the
|
|
638
|
-
// `inFlight` substate of `deleted`.
|
|
639
|
-
inFlight: {
|
|
640
|
-
// FLAGS
|
|
641
|
-
isSaving: true,
|
|
642
|
-
|
|
643
|
-
// EVENTS
|
|
644
|
-
|
|
645
|
-
unloadRecord: assertAgainstUnloadRecord,
|
|
646
|
-
|
|
647
|
-
// TODO: More robust semantics around save-while-in-flight
|
|
648
|
-
willCommit() {},
|
|
649
|
-
didCommit(internalModel) {
|
|
650
|
-
internalModel.transitionTo('saved');
|
|
651
|
-
|
|
652
|
-
internalModel.send('invokeLifecycleCallbacks');
|
|
653
|
-
},
|
|
654
|
-
|
|
655
|
-
becameError(internalModel) {
|
|
656
|
-
internalModel.transitionTo('uncommitted');
|
|
657
|
-
},
|
|
658
|
-
|
|
659
|
-
becameInvalid(internalModel) {
|
|
660
|
-
internalModel.transitionTo('invalid');
|
|
661
|
-
},
|
|
662
|
-
},
|
|
663
|
-
|
|
664
|
-
// Once the adapter indicates that the deletion has
|
|
665
|
-
// been saved, the record enters the `saved` substate
|
|
666
|
-
// of `deleted`.
|
|
667
|
-
saved: {
|
|
668
|
-
// FLAGS
|
|
669
|
-
isDirty: false,
|
|
670
|
-
|
|
671
|
-
setup(internalModel) {
|
|
672
|
-
internalModel.removeFromInverseRelationships();
|
|
673
|
-
},
|
|
674
|
-
|
|
675
|
-
invokeLifecycleCallbacks() {},
|
|
676
|
-
|
|
677
|
-
willCommit() {},
|
|
678
|
-
didCommit() {},
|
|
679
|
-
pushedData() {},
|
|
680
|
-
},
|
|
681
|
-
|
|
682
|
-
invalid: {
|
|
683
|
-
isValid: false,
|
|
684
|
-
|
|
685
|
-
didSetProperty(internalModel, context) {
|
|
686
|
-
internalModel.getRecord().errors._remove(context.name);
|
|
687
|
-
|
|
688
|
-
didSetProperty(internalModel, context);
|
|
689
|
-
|
|
690
|
-
if (!internalModel.hasErrors()) {
|
|
691
|
-
this.becameValid(internalModel);
|
|
692
|
-
}
|
|
693
|
-
},
|
|
694
|
-
|
|
695
|
-
becameInvalid() {},
|
|
696
|
-
becomeDirty() {},
|
|
697
|
-
deleteRecord() {},
|
|
698
|
-
willCommit() {},
|
|
699
|
-
|
|
700
|
-
rolledBack(internalModel) {
|
|
701
|
-
clearErrorMessages(internalModel);
|
|
702
|
-
internalModel.transitionTo('loaded.saved');
|
|
703
|
-
},
|
|
704
|
-
|
|
705
|
-
becameValid(internalModel) {
|
|
706
|
-
internalModel.transitionTo('uncommitted');
|
|
707
|
-
},
|
|
708
|
-
},
|
|
709
|
-
},
|
|
710
|
-
|
|
711
|
-
invokeLifecycleCallbacks() {},
|
|
712
|
-
};
|
|
713
|
-
|
|
714
|
-
function wireState(object, parent, name) {
|
|
715
|
-
// TODO: Use Object.create and copy instead
|
|
716
|
-
object = mixin(parent ? Object.create(parent) : {}, object);
|
|
717
|
-
object.parentState = parent;
|
|
718
|
-
object.stateName = name;
|
|
719
|
-
|
|
720
|
-
for (let prop in object) {
|
|
721
|
-
if (!Object.prototype.hasOwnProperty.call(object, prop) || prop === 'parentState' || prop === 'stateName') {
|
|
722
|
-
continue;
|
|
723
|
-
}
|
|
724
|
-
if (typeof object[prop] === 'object') {
|
|
725
|
-
object[prop] = wireState(object[prop], object, name + '.' + prop);
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
return object;
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
function clearErrorMessages(internalModel) {
|
|
733
|
-
internalModel.getRecord().errors._clear();
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
export default wireState(RootState, null, 'root');
|