dataflux 1.8.0 → 1.9.2

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/README.md CHANGED
@@ -258,11 +258,12 @@ function MyComponent() {
258
258
  The store can be configured with the following options:
259
259
 
260
260
 
261
- | Option | Description | Default |
262
- |-----------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|
263
- | autoSave | It can be `true`, `false`, or an amount of milliseconds (integer). If `false`, you will have to perform `store.save()` manually. If `true`, the store will automatically perform `save()` when objects change. If an amount of milliseconds is provided, the objects are saved periodically AND when a change is detected. See [Editing objects](#editing-objects) for more information. | true |
264
- | saveDelay | An amount of milliseconds used to defer synching operations with the server. It triggers `store.save()` milliseconds after the last change on the store's objects is detedect. This allows to bundle together multiple changes operated by an interacting user. See [Editing objects](#editing-objects) for more information. | 1000 |
265
- | lazyLoad | A boolean. If set to `false`, the store is pre-populated with all the models' objects. If set to `true`, models' objects are loaded only on first usage (e.g., 'find', 'subscribe', 'getRelation'). LazyLoad operates per model, only the objects of the used models are loaded. | false |
261
+ | Option | Description | Default |
262
+ |-------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|
263
+ | autoSave | It can be `true`, `false`, or an amount of milliseconds (integer). If `false`, you will have to perform `store.save()` manually. If `true`, the store will automatically perform `store.save()` when objects change. If an amount of milliseconds is provided, the objects are saved periodically AND when a change is detected. See [Editing objects](#editing-objects) for more information. | true |
264
+ | saveDelay | An amount of milliseconds used to defer synching operations with the server. It triggers `store.save()` milliseconds after the last change on the store's objects is detedect. This allows to bundle together multiple changes operated by an interacting user. See [Editing objects](#editing-objects) for more information. | 1000 |
265
+ | lazyLoad | A boolean. If set to `false`, the store is pre-populated with all the models' objects. If set to `true`, models' objects are loaded only on first usage (e.g., 'find', 'subscribe', 'getRelation'). LazyLoad operates per model, only the objects of the used models are loaded. | false |
266
+ | autoRefresh | It can be `true`, `false`, or an amount of milliseconds (integer). If `false`, you will have to perform `store.refresh()` manually. If `true`, the store will automatically perform `store.refresh()` every 2 minutes. If an amount of milliseconds is provided, the `store.refresh()` is performed periodically. See [store methods](#store-methods) for more information. | false |
266
267
 
267
268
 
268
269
 
@@ -296,6 +297,7 @@ All the possible options for a model creation are (they are all optional):
296
297
  | hiddenFields | An array of attribute names that will never be sent back to the API. E.g., if you set `hiddenFields: ["pages"]`, a book object can contain an attribute `pages` locally, but this will be stripped out in PUT/POST requests. |
297
298
  | deep | A boolean defining if nested objects should be enriched with the object methods. | true |
298
299
  | lazyLoad | A boolean defining if the model should be lazy loaded on the first use. This takes precedence over the lazyLoad declared during store initialization. | false |
300
+ | validate | A dictionary containing functions to validate the objects of this model. See [objects validation](#objects-validation) | no validation |
299
301
 
300
302
  ### Operations
301
303
  As described in the table above, there are four possible operations: **retrieve, insert, update,** and **delete**. An operation can be defined as an operation object or a function.
@@ -553,41 +555,73 @@ The store has the following method.
553
555
  | find(type, filterFunction) | The promise-oriented method to access objects given a type and a filter function. If the filter function is missing, all the objects are returned. See [example 1](#example-1). |
554
556
  | delete(objects) | It deletes an array of objects. See [example 1](#example-3). |
555
557
  | delete(type, filterFunction) | It deleted objects given an array and a filter function. See [example 1](#example-3). |
556
- | insert(type, object) | It creates a new object of a given type and inserts it in the store. |
558
+ | insert(type, object) | It creates a new object of a given type and inserts it in the store. The object inserted MUST be ready to be persisted, read `mock()` below. |
559
+ | mock(type, object) | It creates a mock object of a given type and inserts it in the store. The mock object behaves exactly like a real object, except that it is not persisted (sent to the API) as long as you don't call `object.insert()`. This is useful when you want to create an object but you need to be able to change some properties before to send it to the API. Read [insert vs. mock](#insert-vs-mock). |
557
560
  | subscribe(type, callback, filterFunction) | The callback-oriented method to access objects given a type and a filter function. It returns the key of the subscription, needed to unsubscribe. If the filter function is missing, all the objects are returned. **DataFlux remembers your query and calls the callback every time any change is affecting the result of your query.** See [example 5](#example-5---observability). |
558
561
  | multipleSubscribe(subscriptions, callback) | A method to subscribe to multiple models. The first parameter is an array of models' names and filterFunctions, the second parameter is the callback to be called when the cumulative dataset is ready. E.g., `multipleSubscribe([["book", filterFunction1], ["author", filterFunction2]], callback)`. It returns the key of the subscription. See [example 5](#example-5---observability). |
559
562
  | unsubscribe(key) | Method to terminate a subscription given a subscription key. See [example 5](#example-5---observability). |
560
563
  | findOne(type, stateAttribute, context, filterFunction) | This method automatically injects and updates the React state with the requested data. If multiple objects satisfy the query, only the first is selected. The `stateAttribute` is the name of the attribute that will be added/updated in the state, the `context` is the React.Component. It automatically unsubscribe when the React.Component will unmount. See [example 6](#example-6---observability--react). |
561
564
  | findAll(type, stateAttribute, context, filterFunction) | This method automatically injects and updates the React state with the requested data. The `stateAttribute` is the name of the attribute that will be added/updated in the state, the `context` is the React.Component. It automatically unsubscribe when the React.Component will unmount. If the filter function is missing, all the objects are returned. See [example 6](#example-6---observability--react). |
562
565
  | preload(type) | This method allows to preLoad all objects of a given model. If you initialize the store with `lazyLoad:true`, the objects of a model are retrieved from the API at the first query performed on that model (e.g., at the first `.find()`). However, sometimes you may want to speed up the first query by pre loading the objects of a specific model while keeping `lazyLoad:true` on the store; in such a case you can use `store.preload(type)`. |
566
+ | save() | Persist the changes. Edited local objects will be sent to the REST APIs (insert/update/delete). See also [editing objects](#editing-objects). |
567
+ | save() | Persist the changes (`local -> remote`). Edited local objects will be sent to the REST APIs (insert/update/delete). See also [editing objects](#editing-objects). |
568
+ | refresh() | This method syncs all the objects in the store with the remote version offered by the REST APIs (`remote -> local`). Remote changes are applied locally, including adding/removing objects. Objects edited locally but not yet persisted are preserved locally (tip: you can also create a store with the `autoRefresh` option). |
569
+ ### Insert vs. Mock
570
+
571
+ If you do:
572
+ ```js
573
+ store.insert("book", {title: "The little prince"});
574
+ ```
575
+ when the store will try to save, the object `{title: "The little prince"}` will be sent to the API in a post request. However, the API may require an attribute "price" for each book object, hence the post request will fail.
576
+ Of course, you can add the attribute directly in the `.insert()` call; however, the value of this attribute may not be known at that time. For example, the user may need to input the price in a text field.
577
+
578
+ You may think to delay the `.insert()` up to when all the required attributes are available; however, **this is a bad idea**, because as long as the object is not in the store, you will not benefit from the observability provided by DataFlux. _E.g., your React state will not update automatically and you will need to handle the changes triggered by the input fields yourself_ ([reinventing the wheel](https://en.wikipedia.org/wiki/Reinventing_the_wheel)).
579
+
580
+ Solution, use `.mock()` to create a mock object:
581
+
582
+ ```js
583
+ store.mock("book", {title: "The little prince"});
584
+ ```
585
+
586
+ The mock object behaves exactly like a normal object: you can retrieve it with `.find`/`.findAll`/`.findOne`/`.subscribe`.
587
+
588
+ However, the mock object is not sent to the API as long as you don't call `.insert()` on the object itself. When you call `.insert()`, the mock object is promoted to real object.
589
+
590
+ > Warning: to promote a mock object, you need to call `object.insert()` on the object itself (you must first retrieve it) and NOT `store.insert()`.
591
+
563
592
 
564
593
  ## Store events
565
594
  The store emits the following events:
566
595
 
567
- | Name | Description |
568
- |---------|---------------------------------------------------------------------------------------------------------------------------------------|
569
- | error | To listen the errors emitted by the store. |
570
- | save | Possible emitted values are `start` and `end`. They are emitted when the store starts/finishes to persist the data (API interaction). |
571
- | loading | The event is emitted while a new model is loaded. The value contains something like `{status: "start", model: "book"}` |
596
+ | Name | Description |
597
+ |------------|---------------------------------------------------------------------------------------------------------------------------------------|
598
+ | error | To listen the errors emitted by the store. |
599
+ | save | Possible emitted values are `start` and `end`. They are emitted when the store starts/finishes to persist the data (API interaction). |
600
+ | loading | The event is emitted while a new model is loaded. The value contains something like `{status: "start", model: "book"}` |
601
+ | refreshing | The event is emitted while a model is refreshed. The value contains something like `{status: "start", model: "book"}` |
572
602
 
573
603
  ## Objects methods
574
604
  Each object created is enriched with the following methods.
575
605
 
576
606
 
577
- | Method | Description |
578
- |------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
579
- | getId() | It returns a unique ID used by the store to identify the object. The ID is unique inside a single model. Be aware, `object.id` and `objet.getId()` may return different values, since store's IDs can be different from the one of the REST API. |
580
- | set(attribute, value, hidden) | A method to set an attribute to the object. It provides some advantages compared to doing `object.attribute = value`, these are discussed in [below](#editing-objects). The third parameter is optional, and when set to true will set the attribute as hidden (see [hiddenFields](#models-creation)). |
581
- | setConstant(attribute, value) | A method to set an unmodifiable hidden attribute on the object. Setting the attribute as a constant will not propagate an update. |
607
+ | Method | Description |
608
+ |------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
609
+ | getId() | It returns a unique ID used by the store to identify the object. The ID is unique inside a single model. Be aware, `object.id` and `objet.getId()` may return different values, since store's IDs can be different from the one of the REST API. |
610
+ | set(attribute, value, hidden) | A method to set an attribute to the object. It provides some advantages compared to doing `object.attribute = value`, these are discussed in [below](#editing-objects). The third parameter is optional, and when set to true will set the attribute as hidden (see [hiddenFields](#models-creation)). |
611
+ | setConstant(attribute, value) | A method to set an unmodifiable hidden attribute on the object. Setting the attribute as a constant will not propagate an update. |
582
612
  | get(attribute, defaultValue) | Method to retrieve the value of an attribute. It does not provide any advantage compared to accessing directly the attribute (e.g., `author.name`); except for hidden fields and constants, which can be retrieved only with the `.get` method. Additionally, you can provide a default value as a second parameter in case the object doesn't have that attribute. |
583
- | getRelation(model, filterFunction) | To get all the objects respecting a specific relation with this object (see [model relations](#model-relations)). |
584
- | save() | Method to save the object. You can do `store.save()` instead. |
585
- | destroy() | Method to delete the object. You can do `store.delete()` instead. |
586
- | toJSON() | It returns a pure JSON representation of the object. |
587
- | toString() | It returns a string representation of the object. |
588
- | getFingerprint() | It returns a hash of the object. The hash changes at every change of the object or of any nested object. Useful to detect object changes. |
589
- | getModel() | It returns the model of this object. Mostly useful to do `object.getModel().getType()` and obtain a string defining the type of the object. |
590
- ### Deeper Objects
613
+ | getRelation(model, filterFunction) | To get all the objects respecting a specific relation with this object (see [model relations](#model-relations)). |
614
+ | save() | Method to save the object. You can do `store.save()` instead. |
615
+ | destroy() | Method to delete the object. You can do `store.delete()` instead. |
616
+ | toJSON() | It returns a pure JSON representation of the object. |
617
+ | toString() | It returns a string representation of the object. |
618
+ | getFingerprint() | It returns a hash of the object. The hash changes at every change of the object or of any nested object. Useful to detect object changes. |
619
+ | getModel() | It returns the model of this object. Mostly useful to do `object.getModel().getType()` and obtain a string defining the type of the object. |
620
+ | getError() | If an operation on an object triggers an error, this error can be retrieved with `getError()`. This allows to observe specific objects' errors, instead of the generic `store.on("error", ...)`. |
621
+ | getError(attributeName) | This method allows you to check if the specificed attribute generated any error according to the validation property specified in the model. See [objects validation](#objects-validation). |
622
+ | setError(error) | Additionally to DataFlux's errors, you can trigger your own errors with this method. Other components observing this objet's error will be notified. |
623
+
624
+ ### Deep Objects
591
625
  When a model is declared with the option `deep: true` (default, see [model creation](#models-creation)), all the sub objects will also offer many of the methods above.
592
626
 
593
627
  Imagine the API returns:
@@ -624,8 +658,75 @@ store.find("book")
624
658
  });
625
659
  ```
626
660
 
661
+ ### Objects validation
662
+
663
+ DataFlux supports automatic validation of the objects. This is important for two reasons:
664
+ * Objects that contain invalid attributes' values are not sent back to the API;
665
+ * Validation errors can be used to automatically suggest errors in the UI.
666
+
667
+ To specify the validation of the objects for a specific model, you need to add a `validate` dictionary during model creation.
668
+
669
+ ```js
670
+ const book = new Model("book", {
671
+ retrieve: {
672
+ url: "https://rest.example.net/api/v1/books/"
673
+ },
674
+ validate: {
675
+ isbn: ({isbn}) => {
676
+ if (typeof(isbn) !== "number") {
677
+ throw new Error("The isbn must be a number");
678
+ }
679
+ },
680
+ title: ({title}) => {
681
+ if (!title) {
682
+ throw new Error("The title is mandatory");
683
+ }
684
+ }
685
+ }
686
+ });
687
+ ```
688
+
689
+ Each key of the `validate` dictionary is an attribute of the object (a field name), each value is a function receiving in input the object and throwing an error in case the field is not valid.
690
+
691
+ > Be aware: A validation function cannot return a boolean, you have to throw an error.
692
+
693
+ To validate a specific attribute of an object, you can do `object.getError(attributeName)` (e.g., `book.getError(isbn)`). In case of error, a string describing the error is returned, `false` otherwise.
694
+
695
+ Example of usage in a React component.
696
+
697
+ ```js
698
+ class MyComponent extends React.Component {
699
+ constructor(props) {
700
+ super(props);
701
+ }
702
+
703
+ render(){
704
+ const {book} = this.state;
705
+
706
+ // A textfield to edit the title of a book
707
+ return <TextField
708
+ value={book.title}
709
+ onChange={store.handleChange(book, "title")}
710
+ error={object.getError("title")}
711
+ // E.g., in material UI the test field will be red in case of errors
712
+ />;
713
+ }
714
+ }
715
+ ```
716
+
717
+ > Be aware: if you do `object.getError()` without specifying any attribute, you will receive object errors not associated with any field, such as API errors. This is similar to `store.on("error")`
718
+
627
719
 
628
720
  ## Editing objects
721
+
722
+ The preferred method to edit objects is using the `.set()` method that each object has, instead of editing directly the attributes. However, there are a few notions to keep in mind.
723
+
724
+ #### Client-side object validaion will not work when editing attributes directly.
725
+
726
+ You will be able to validate objects only if you use `.set()`. If you edit directly the attribute (e.g., `book.title = "test"`) errors will be discovered only by the API.
727
+
728
+ #### Auto save may not work when editing attributes directly.
729
+
629
730
  The option `autoSave` can be `true`, `false`, or a number (milliseconds).
630
731
 
631
732
  * When `autoSave` is set to `false`, the following operations are equivalent:
package/dist/BasicObj.js CHANGED
@@ -24,20 +24,21 @@ function _classPrivateFieldInitSpec(obj, privateMap, value) { _checkPrivateRedec
24
24
 
25
25
  function _checkPrivateRedeclaration(obj, privateCollection) { if (privateCollection.has(obj)) { throw new TypeError("Cannot initialize the same private elements twice on an object"); } }
26
26
 
27
- function _classPrivateFieldSet(receiver, privateMap, value) { var descriptor = _classExtractFieldDescriptor(receiver, privateMap, "set"); _classApplyDescriptorSet(receiver, descriptor, value); return value; }
27
+ function _classPrivateFieldGet(receiver, privateMap) { var descriptor = _classExtractFieldDescriptor(receiver, privateMap, "get"); return _classApplyDescriptorGet(receiver, descriptor); }
28
28
 
29
- function _classApplyDescriptorSet(receiver, descriptor, value) { if (descriptor.set) { descriptor.set.call(receiver, value); } else { if (!descriptor.writable) { throw new TypeError("attempted to set read only private field"); } descriptor.value = value; } }
29
+ function _classApplyDescriptorGet(receiver, descriptor) { if (descriptor.get) { return descriptor.get.call(receiver); } return descriptor.value; }
30
30
 
31
- function _classPrivateFieldGet(receiver, privateMap) { var descriptor = _classExtractFieldDescriptor(receiver, privateMap, "get"); return _classApplyDescriptorGet(receiver, descriptor); }
31
+ function _classPrivateFieldSet(receiver, privateMap, value) { var descriptor = _classExtractFieldDescriptor(receiver, privateMap, "set"); _classApplyDescriptorSet(receiver, descriptor, value); return value; }
32
32
 
33
33
  function _classExtractFieldDescriptor(receiver, privateMap, action) { if (!privateMap.has(receiver)) { throw new TypeError("attempted to " + action + " private field on non-instance"); } return privateMap.get(receiver); }
34
34
 
35
- function _classApplyDescriptorGet(receiver, descriptor) { if (descriptor.get) { return descriptor.get.call(receiver); } return descriptor.value; }
35
+ function _classApplyDescriptorSet(receiver, descriptor, value) { if (descriptor.set) { descriptor.set.call(receiver, value); } else { if (!descriptor.writable) { throw new TypeError("attempted to set read only private field"); } descriptor.value = value; } }
36
36
 
37
37
  function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
38
38
 
39
39
  var dateRegex = new RegExp("^[0-9][0-9][0-9][0-9]-[0-9].*T[0-9].*Z$");
40
40
  exports.dateRegex = dateRegex;
41
+ var globalError = "_object";
41
42
 
42
43
  function setValues(values, model, SubObj, parent, context) {
43
44
  Object.keys(values).forEach(function (key) {
@@ -64,6 +65,10 @@ var _setHidden = /*#__PURE__*/new WeakMap();
64
65
 
65
66
  var _id = /*#__PURE__*/new WeakMap();
66
67
 
68
+ var _error = /*#__PURE__*/new WeakMap();
69
+
70
+ var _model = /*#__PURE__*/new WeakMap();
71
+
67
72
  var BasicObj = /*#__PURE__*/_createClass(function BasicObj(values, model) {
68
73
  var _this = this;
69
74
 
@@ -79,10 +84,26 @@ var BasicObj = /*#__PURE__*/_createClass(function BasicObj(values, model) {
79
84
  value: null
80
85
  });
81
86
 
87
+ _classPrivateFieldInitSpec(this, _error, {
88
+ writable: true,
89
+ value: {}
90
+ });
91
+
92
+ _classPrivateFieldInitSpec(this, _model, {
93
+ writable: true,
94
+ value: void 0
95
+ });
96
+
97
+ _defineProperty(this, "setId", function (id) {
98
+ _classPrivateFieldSet(_this, _id, id);
99
+ });
100
+
82
101
  _defineProperty(this, "getId", function () {
83
102
  if (!_classPrivateFieldGet(_this, _id)) {
84
103
  if (_this.id && (typeof _this.id === "string" || typeof _this.id === "number")) {
85
104
  _classPrivateFieldSet(_this, _id, _this.id.toString());
105
+
106
+ delete _this.setId;
86
107
  } else {
87
108
  _classPrivateFieldSet(_this, _id, (0, _uuid.v4)());
88
109
  }
@@ -106,15 +127,39 @@ var BasicObj = /*#__PURE__*/_createClass(function BasicObj(values, model) {
106
127
  }
107
128
 
108
129
  _this[attribute] = value;
130
+
131
+ _classPrivateFieldGet(_this, _model).validateObjectAttribute(_this, attribute);
109
132
  }
110
133
 
111
134
  return _this.update();
112
135
  });
113
136
 
114
- _defineProperty(this, "setConstant", function (attribute, value) {
137
+ _defineProperty(this, "getError", function () {
115
138
  var _classPrivateFieldGet3;
116
139
 
117
- _classPrivateFieldGet(_this, _setHidden)[attribute] = (_classPrivateFieldGet3 = _classPrivateFieldGet(_this, _setHidden)[attribute]) !== null && _classPrivateFieldGet3 !== void 0 ? _classPrivateFieldGet3 : value;
140
+ var attribute = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
141
+ var key = attribute ? attribute : globalError;
142
+ return (_classPrivateFieldGet3 = _classPrivateFieldGet(_this, _error)[key]) !== null && _classPrivateFieldGet3 !== void 0 ? _classPrivateFieldGet3 : false;
143
+ });
144
+
145
+ _defineProperty(this, "setError", function (error) {
146
+ var attribute = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
147
+
148
+ if (error && attribute) {
149
+ _classPrivateFieldGet(_this, _error)[attribute] = error;
150
+ } else if (error && !attribute) {
151
+ _classPrivateFieldGet(_this, _error)[globalError] = error;
152
+ } else if (!error && attribute) {
153
+ delete _classPrivateFieldGet(_this, _error)[attribute];
154
+ } else {
155
+ _classPrivateFieldSet(_this, _error, {});
156
+ }
157
+ });
158
+
159
+ _defineProperty(this, "setConstant", function (attribute, value) {
160
+ var _classPrivateFieldGet4;
161
+
162
+ _classPrivateFieldGet(_this, _setHidden)[attribute] = (_classPrivateFieldGet4 = _classPrivateFieldGet(_this, _setHidden)[attribute]) !== null && _classPrivateFieldGet4 !== void 0 ? _classPrivateFieldGet4 : value;
118
163
  });
119
164
 
120
165
  _defineProperty(this, "toJSON", function () {
@@ -153,6 +198,8 @@ var BasicObj = /*#__PURE__*/_createClass(function BasicObj(values, model) {
153
198
  _defineProperty(this, "update", function () {
154
199
  return Promise.resolve();
155
200
  });
201
+
202
+ _classPrivateFieldSet(this, _model, model);
156
203
  });
157
204
 
158
205
  exports.BasicObj = BasicObj;
package/dist/Model.js CHANGED
@@ -93,22 +93,29 @@ var _addRelationByFilter = /*#__PURE__*/new WeakMap();
93
93
 
94
94
  var _removeHiddenFields = /*#__PURE__*/new WeakMap();
95
95
 
96
- var _bulkOperation = /*#__PURE__*/new WeakMap();
97
-
98
96
  var _toArray = /*#__PURE__*/new WeakMap();
99
97
 
100
98
  var _unWrap = /*#__PURE__*/new WeakMap();
101
99
 
102
100
  var _insertObjects = /*#__PURE__*/new WeakMap();
103
101
 
102
+ var _assignId = /*#__PURE__*/new WeakMap();
103
+
104
104
  var _updateObjects = /*#__PURE__*/new WeakMap();
105
105
 
106
106
  var _deleteObjects = /*#__PURE__*/new WeakMap();
107
107
 
108
+ var _hanldeApiError = /*#__PURE__*/new WeakMap();
109
+
110
+ var _cleanApiError = /*#__PURE__*/new WeakMap();
111
+
112
+ var _removeFromStoreSilentlyAfterFailure = /*#__PURE__*/new WeakMap();
113
+
108
114
  var Model = /*#__PURE__*/_createClass(function Model(name) {
109
115
  var _this = this,
110
116
  _options$deep,
111
- _options$parseMoment;
117
+ _options$parseMoment,
118
+ _options$validate;
112
119
 
113
120
  var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
114
121
 
@@ -176,6 +183,31 @@ var Model = /*#__PURE__*/_createClass(function Model(name) {
176
183
  value: void 0
177
184
  });
178
185
 
186
+ _defineProperty(this, "validateObjectAttribute", function (object, key) {
187
+ var validate = _this.options.validate;
188
+
189
+ if (validate && validate[key]) {
190
+ try {
191
+ validate[key](object);
192
+ object.setError(false, key);
193
+ } catch (error) {
194
+ object.setError(error.message, key);
195
+ }
196
+ }
197
+ });
198
+
199
+ _defineProperty(this, "isObjectValid", function (object) {
200
+ for (var key in object) {
201
+ if (typeof object[key] !== "function") {
202
+ if (object.getError(key)) {
203
+ return false;
204
+ }
205
+ }
206
+ }
207
+
208
+ return true;
209
+ });
210
+
179
211
  _defineProperty(this, "getStore", function () {
180
212
  return _classPrivateFieldGet(_this, _store);
181
213
  });
@@ -270,15 +302,15 @@ var Model = /*#__PURE__*/_createClass(function Model(name) {
270
302
  });
271
303
 
272
304
  _defineProperty(this, "insertObjects", function (objects) {
273
- return objects.length ? _classPrivateFieldGet(_this, _bulkOperation).call(_this, objects, _classPrivateFieldGet(_this, _insertObjects)) : Promise.resolve();
305
+ return objects.length ? _classPrivateFieldGet(_this, _insertObjects).call(_this, objects) : Promise.resolve();
274
306
  });
275
307
 
276
308
  _defineProperty(this, "updateObjects", function (objects) {
277
- return objects.length ? _classPrivateFieldGet(_this, _bulkOperation).call(_this, objects, _classPrivateFieldGet(_this, _updateObjects)) : Promise.resolve();
309
+ return objects.length ? _classPrivateFieldGet(_this, _updateObjects).call(_this, objects) : Promise.resolve();
278
310
  });
279
311
 
280
312
  _defineProperty(this, "deleteObjects", function (objects) {
281
- return objects.length ? _classPrivateFieldGet(_this, _bulkOperation).call(_this, objects, _classPrivateFieldGet(_this, _deleteObjects)) : Promise.resolve();
313
+ return objects.length ? _classPrivateFieldGet(_this, _deleteObjects).call(_this, objects) : Promise.resolve();
282
314
  });
283
315
 
284
316
  _defineProperty(this, "factory", function (params) {
@@ -337,15 +369,6 @@ var Model = /*#__PURE__*/_createClass(function Model(name) {
337
369
  }
338
370
  });
339
371
 
340
- _classPrivateFieldInitSpec(this, _bulkOperation, {
341
- writable: true,
342
- value: function value(objects, action) {
343
- return action(objects.map(function (i) {
344
- return _classPrivateFieldGet(_this, _removeHiddenFields).call(_this, i.toJSON());
345
- }));
346
- }
347
- });
348
-
349
372
  _classPrivateFieldInitSpec(this, _toArray, {
350
373
  writable: true,
351
374
  value: function value(data) {
@@ -373,7 +396,11 @@ var Model = /*#__PURE__*/_createClass(function Model(name) {
373
396
 
374
397
  _classPrivateFieldInitSpec(this, _unWrap, {
375
398
  writable: true,
376
- value: function value(data) {
399
+ value: function value(objects) {
400
+ var data = Object.values(objects).map(function (object) {
401
+ return _classPrivateFieldGet(_this, _removeHiddenFields).call(_this, object.toJSON());
402
+ });
403
+
377
404
  if (data.value != null && Object.keys(data).length === 1) {
378
405
  return data.value;
379
406
  } else if (Array.isArray(data) && data.length === 1 && data[0].value != null && Object.keys(data[0]).length === 1) {
@@ -386,22 +413,109 @@ var Model = /*#__PURE__*/_createClass(function Model(name) {
386
413
 
387
414
  _classPrivateFieldInitSpec(this, _insertObjects, {
388
415
  writable: true,
389
- value: function value(data) {
390
- return (0, _modelHooksUtils.executeHook)("insert", _classPrivateFieldGet(_this, _insertHook), _classPrivateFieldGet(_this, _unWrap).call(_this, data), _classPrivateFieldGet(_this, _axios)).then(_classPrivateFieldGet(_this, _toArray));
416
+ value: function value(objects) {
417
+ var operation = "insert";
418
+ return (0, _modelHooksUtils.executeHook)(operation, _classPrivateFieldGet(_this, _insertHook), _classPrivateFieldGet(_this, _unWrap).call(_this, objects), _classPrivateFieldGet(_this, _axios)).then(function (data) {
419
+ if (data) _classPrivateFieldGet(_this, _assignId).call(_this, data, objects);
420
+
421
+ _classPrivateFieldGet(_this, _cleanApiError).call(_this, objects);
422
+
423
+ return data;
424
+ }).then(_classPrivateFieldGet(_this, _toArray))["catch"](function (error) {
425
+ _classPrivateFieldGet(_this, _removeFromStoreSilentlyAfterFailure).call(_this, objects);
426
+
427
+ return _classPrivateFieldGet(_this, _hanldeApiError).call(_this, error, objects, operation);
428
+ });
429
+ }
430
+ });
431
+
432
+ _classPrivateFieldInitSpec(this, _assignId, {
433
+ writable: true,
434
+ value: function value(data, objects) {
435
+ if (data.length === 1) {
436
+ var newId = data[0].id;
437
+ objects[0].setId(newId);
438
+ delete objects[0].setId;
439
+ }
391
440
  }
392
441
  });
393
442
 
394
443
  _classPrivateFieldInitSpec(this, _updateObjects, {
395
444
  writable: true,
396
- value: function value(data) {
397
- return (0, _modelHooksUtils.executeHook)("update", _classPrivateFieldGet(_this, _updateHook), _classPrivateFieldGet(_this, _unWrap).call(_this, data), _classPrivateFieldGet(_this, _axios)).then(_classPrivateFieldGet(_this, _toArray));
445
+ value: function value(objects) {
446
+ var operation = "update";
447
+ return (0, _modelHooksUtils.executeHook)(operation, _classPrivateFieldGet(_this, _updateHook), _classPrivateFieldGet(_this, _unWrap).call(_this, objects), _classPrivateFieldGet(_this, _axios)).then(function (data) {
448
+ _classPrivateFieldGet(_this, _cleanApiError).call(_this, objects);
449
+
450
+ return data;
451
+ }).then(_classPrivateFieldGet(_this, _toArray))["catch"](function (error) {
452
+ return _classPrivateFieldGet(_this, _hanldeApiError).call(_this, error, objects, operation);
453
+ });
398
454
  }
399
455
  });
400
456
 
401
457
  _classPrivateFieldInitSpec(this, _deleteObjects, {
402
458
  writable: true,
403
- value: function value(data) {
404
- return (0, _modelHooksUtils.executeHook)("delete", _classPrivateFieldGet(_this, _deleteHook), _classPrivateFieldGet(_this, _unWrap).call(_this, data), _classPrivateFieldGet(_this, _axios)).then(_classPrivateFieldGet(_this, _toArray));
459
+ value: function value(objects) {
460
+ var operation = "delete";
461
+ return (0, _modelHooksUtils.executeHook)(operation, _classPrivateFieldGet(_this, _deleteHook), _classPrivateFieldGet(_this, _unWrap).call(_this, objects), _classPrivateFieldGet(_this, _axios)).then(function (data) {
462
+ _classPrivateFieldGet(_this, _cleanApiError).call(_this, objects);
463
+
464
+ return data;
465
+ }).then(_classPrivateFieldGet(_this, _toArray))["catch"](function (error) {
466
+ return _classPrivateFieldGet(_this, _hanldeApiError).call(_this, error, objects, operation);
467
+ });
468
+ }
469
+ });
470
+
471
+ _classPrivateFieldInitSpec(this, _hanldeApiError, {
472
+ writable: true,
473
+ value: function value(error, objects, operation) {
474
+ var _error$response$data, _error3, _error3$response, _ref, _error$message, _error4, _error5;
475
+
476
+ error = (_error$response$data = (_error3 = error) === null || _error3 === void 0 ? void 0 : (_error3$response = _error3.response) === null || _error3$response === void 0 ? void 0 : _error3$response.data) !== null && _error$response$data !== void 0 ? _error$response$data : error;
477
+ var targets = objects.map(function (object) {
478
+ return object.getId();
479
+ }); // Set errors
480
+
481
+ var strError = (_ref = (_error$message = (_error4 = error) === null || _error4 === void 0 ? void 0 : _error4.message) !== null && _error$message !== void 0 ? _error$message : (_error5 = error) === null || _error5 === void 0 ? void 0 : _error5.error) !== null && _ref !== void 0 ? _ref : error;
482
+ Object.values(objects).map(function (object) {
483
+ return object.setError(strError);
484
+ });
485
+ return Promise.reject(_objectSpread(_objectSpread({}, error), {}, {
486
+ targets: targets,
487
+ operation: operation
488
+ }));
489
+ }
490
+ });
491
+
492
+ _classPrivateFieldInitSpec(this, _cleanApiError, {
493
+ writable: true,
494
+ value: function value(objects) {
495
+ Object.values(objects).map(function (object) {
496
+ return object.setError(false);
497
+ });
498
+ }
499
+ });
500
+
501
+ _classPrivateFieldInitSpec(this, _removeFromStoreSilentlyAfterFailure, {
502
+ writable: true,
503
+ value: function value(objects) {
504
+ var _iterator2 = _createForOfIteratorHelper(objects.map(function (object) {
505
+ return object.getId();
506
+ })),
507
+ _step2;
508
+
509
+ try {
510
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
511
+ var target = _step2.value;
512
+ delete _this.getStore().models[_this.getType()].storedObjects[target];
513
+ }
514
+ } catch (err) {
515
+ _iterator2.e(err);
516
+ } finally {
517
+ _iterator2.f();
518
+ }
405
519
  }
406
520
  });
407
521
 
@@ -410,7 +524,8 @@ var Model = /*#__PURE__*/_createClass(function Model(name) {
410
524
  this.options = _objectSpread(_objectSpread({}, options), {}, {
411
525
  deep: (_options$deep = options.deep) !== null && _options$deep !== void 0 ? _options$deep : true,
412
526
  parseMoment: (_options$parseMoment = options.parseMoment) !== null && _options$parseMoment !== void 0 ? _options$parseMoment : false,
413
- lazyLoad: options.lazyLoad
527
+ lazyLoad: options.lazyLoad,
528
+ validate: (_options$validate = options.validate) !== null && _options$validate !== void 0 ? _options$validate : {}
414
529
  });
415
530
 
416
531
  _classPrivateFieldSet(this, _store, null);
@@ -431,12 +546,12 @@ var Model = /*#__PURE__*/_createClass(function Model(name) {
431
546
  throw new Error("The load option must be a function");
432
547
  }
433
548
 
434
- var _ref = _typeof(options) === "object" ? (0, _modelHooksUtils.getHooksFromOptions)(options) : (0, _modelHooksUtils.getHooksFromUrl)(options),
435
- _ref2 = _slicedToArray(_ref, 4),
436
- retrieveHook = _ref2[0],
437
- insertHook = _ref2[1],
438
- updateHook = _ref2[2],
439
- deleteHook = _ref2[3];
549
+ var _ref2 = _typeof(options) === "object" ? (0, _modelHooksUtils.getHooksFromOptions)(options) : (0, _modelHooksUtils.getHooksFromUrl)(options),
550
+ _ref3 = _slicedToArray(_ref2, 4),
551
+ retrieveHook = _ref3[0],
552
+ insertHook = _ref3[1],
553
+ updateHook = _ref3[2],
554
+ deleteHook = _ref3[3];
440
555
 
441
556
  _classPrivateFieldSet(this, _retrieveHook, retrieveHook);
442
557