dataflux 1.2.3 → 1.4.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/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2022 Massimo Candela
3
+ Copyright (c) 2022-present Massimo Candela
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -30,23 +30,34 @@ Create your global store by creating a file (e.g., named `store.js`) containing
30
30
  Consider the following hypothetical store/model declaration common to all the examples below:
31
31
 
32
32
  ```js
33
+ // Content of your store.js
33
34
  import {Store, Model} from "dataflux";
34
35
 
36
+ // We create a new Store
35
37
  const store = new Store();
36
- const author = new Model("author", `https://rest.example.net/api/v1/authors`);
37
- const book = new Model("book", `https://rest.example.net/api/v1/books`);
38
38
 
39
- store.addModel(author);
39
+ // We now create two models, "author" and "book".
40
+ // Both of them are auto generated based on the output of a REST API.
41
+ // The REST API does NOT need to provide a specific format.
42
+ // E.g., /books returns [{"title": "Hamlet", "year": 1600}, ...].
43
+ // See "REST API format" below for more info.
44
+ const book = new Model("book", `https://api.example.net/books`);
45
+ const author = new Model("author", `https://api.example.net/authors`);
46
+
47
+ // We add the models to the store
40
48
  store.addModel(book);
49
+ store.addModel(author);
41
50
 
42
- // An object relation between author.id and book.authorId as follows
51
+ // Optionally, we can declare relations among models.
52
+ // E.g., we can declare that an author has one or more books.
43
53
  author.addRelation(book, "id", "authorId");
54
+ // The relation will provide all the books where author.id = book.authorId
44
55
 
45
56
  export default store;
46
57
  ```
47
58
 
48
59
 
49
- The store can be initialized with [various options](#configuration). You need only one store for the entire application, that's why you should declare it in its own file and import it in multiple places.
60
+ The store can be initialized with [various options](#configuration). You need only one store for the entire application, that's why you should declare it in its own file (store.js in this case) and import it in multiple places.
50
61
 
51
62
  The creation of a model requires at least a name and a url. GET, POST, PUT, and DELETE operations are going to be performed against the same url. [Models can be created with considerably more advanced options.](#models-creation)
52
63
 
@@ -55,28 +66,31 @@ A JS object is automatically created for each item returned by the API, for each
55
66
 
56
67
  ### Example 1
57
68
 
58
- Retrieve and edit an author not knowing the ID:
69
+ Retrieve and edit an author by name and surname:
59
70
 
60
71
  ```js
61
- import store from "./store";
72
+ import store from "./store"; // Import our store.js
62
73
 
63
74
  // Find the author Dante Alighieri
64
75
  store.find("author", ({name, surname}) => name == "Dante" && surname == "Alighieri")
65
76
  .then(([author]) => {
77
+
78
+ // We got the author, let's now edit it
66
79
  author.set("country", "Italy");
67
80
  author.set("type", "poet");
68
- // Nothing else to do, the store does a single PUT request to the model's API about the edited object
69
81
  });
70
82
  ```
71
83
 
72
- > You don't necessarily need to use `object.set` to edit an object attribute. You could do `author.country = "Italy"`. However, this approach relies on a periodic detection of changes (while `.set` triggers an update immediately). Check the `autoSave` option for more information
84
+ Nothing else to do! After your edit, the store will do a single PUT request to the model's API to save the edited object. This behavior can be disabled, see next example.
85
+
86
+ > You don't necessarily need to use `object.set` to edit an object attribute. You could do `author.country = "Italy"`. However, this approach has disadvantages, read [editing objects](#editing-objects) for more information
73
87
 
74
88
  ### Example 2
75
89
 
76
- Operations without autoSave:
90
+ DataFlux automatically sends the edited objects back to the API to be saved. However, you can disable this behavior and manually instruct the store when to save.
77
91
 
78
92
  ```js
79
- // To disable autoSave you must declare the store as follows
93
+ // To disable autoSave you must declare the store (in store.js) as follows
80
94
  const store = new Store({autoSave: false});
81
95
  ```
82
96
 
@@ -86,11 +100,13 @@ The same example above now becomes:
86
100
  // Find the author Dante Alighieri
87
101
  store.find("author", ({name, surname}) => name == "Dante" && surname == "Alighieri")
88
102
  .then(([author]) => {
89
- // When autoSave = false, you can still use author.set, but there is no actual benefit
103
+
104
+ // When autoSave is false, author.set("country", "Italy") and
105
+ // author.country = "Italy" are equivalent
90
106
  author.country = "Italy"
91
107
  author.type = "poet"
92
108
 
93
- store.save(); // Even if we changed only one author, prefer always store.save() to author.save()
109
+ store.save(); // Instruct the store to save
94
110
  });
95
111
  ```
96
112
 
@@ -129,6 +145,7 @@ author.getRelation("book");
129
145
 
130
146
  If you use `subscribe` instead of `find`, you can provide a callback to be invoked when data is ready or there is a change in the data.
131
147
 
148
+ _**DataFlux remembers your query and calls your callback every time any change is affecting the result of your query!**_
132
149
 
133
150
  ```js
134
151
  const drawBooksCallback = (books) => {
@@ -139,7 +156,7 @@ const drawBooksCallback = (books) => {
139
156
  store.subscribe("book", drawBooks, ({price}) => price < 20);
140
157
  ```
141
158
 
142
- If now somewhere a book is inserted/deleted/edited:
159
+ If now a book is inserted/deleted/edited:
143
160
  * if the book has `price < 20`, `drawBooksCallback` will be called again with the new dataset;
144
161
  * if the book has `price > 20`, `drawBooksCallback` will NOT be called again (because the new book doesn't impact our selection).
145
162
 
@@ -151,14 +168,31 @@ const subKey = store.subscribe("book", drawBooks, ({price}) => price < 20); // S
151
168
  store.unsubscribe(subKey); // Unsubscribe
152
169
  ```
153
170
 
171
+ You can also do multiple subscriptions at once:
172
+
173
+ ```js
174
+ const subscriptions = [
175
+ ["book", ({title}) => title === "The little prince"], // Model name and filter function
176
+ ["author"], // No filter function, all objects returned
177
+ ];
178
+
179
+ const callback = ([books, authors]) => {
180
+ // Objects are ready
181
+ };
182
+
183
+ const subKey = store.multipleSubscribe(requests, callback); // Subscribe
184
+
185
+ store.unsubscribe(subKey); // Unsubscribe
186
+ ```
187
+
154
188
  ### Example 6 - Observability + React
155
189
 
156
190
  The integration with React is offered transparently when using the store inside a `React.Component`.
157
- You can use two methods: `findOne`, and `findAll`.
191
+ You can use two methods: `findOne`, and `findAll` (which are a react-specific syntactic sugar over `subscribe`).
158
192
 
159
- > Since the store is able to detect changes deep in a nested structure, you will not have to worry about the component not re-rendering. Also, the setState will only be triggered when the next change of the dataset is really impacting your selection.
193
+ **_Since the store is able to detect changes deep in a nested structure, you will not have to worry about the component not re-rendering. Also, the setState will be triggered ONLY when the next change of the dataset is impacting your selection._**
160
194
 
161
- React Component example
195
+ React Component example:
162
196
  ```jsx
163
197
  class MyComponent extends React.Component {
164
198
  constructor(props) {
@@ -168,9 +202,9 @@ class MyComponent extends React.Component {
168
202
  componentDidMount() {
169
203
  // Get all books with a price < 20
170
204
  store.findAll("book", "books", this, ({price}) => price < 20);
171
- // Every time the dataset changes, a setState will be automatically
172
- // performed. An attribute "books" will be added/updated in the
173
- // state (the rest of the state remains unchanged).
205
+ // An attribute "books" will be added/updated in the
206
+ // state (the rest of the state remains unchanged) every time
207
+ // a book in our selection is inserted/deleted/edited.
174
208
 
175
209
  // findAll is a syntactic sugar for:
176
210
  // const callback = (books) => {this.setState({...this.state, books})};
@@ -185,7 +219,7 @@ class MyComponent extends React.Component {
185
219
  onTitleChange={(title) => book.set("title", title)}
186
220
  // onTitleChange will alter the book and so the current
187
221
  // state of "books" (a setState will be performed).
188
- //
222
+
189
223
  // Alternatively:
190
224
  // onTitleChange={store.handleChange(book, "title")}
191
225
  // is a syntactic sugar of the function above
@@ -194,9 +228,9 @@ class MyComponent extends React.Component {
194
228
  }
195
229
  ```
196
230
 
197
- The method `findAll` returns always an array. The method `findOne` returns a single object (if multiple objects satisfy the search, the first is returned).
231
+ The method `findAll` returns always an array. The method `findOne` returns a single object (if multiple objects satisfy the query, the first is returned).
198
232
 
199
- When the component will unmount, the `findAll` subscription will be automatically terminated without the need to unsubscribe. Be aware, `store.findAll` injects the unsubscribe call inside `componentWillUnmount`. If your component already implements `componentWillUnmount()`, then you will have to use `store.subscribe` and `store.unsubscribe` instead of `store.findAll`, to avoid side effects when the component is unmounted.
233
+ When the component will unmount, the `findAll` subscription will be automatically terminated without the need to unsubscribe. Be aware, `store.findAll()` injects the unsubscribe call inside `componentWillUnmount()`. If your component already implements `componentWillUnmount()`, then you will have to use `store.subscribe()` and `store.unsubscribe()` instead of `store.findAll()`, to avoid side effects when the component is unmounted.
200
234
 
201
235
  ## Configuration
202
236
 
@@ -205,7 +239,7 @@ The store can be configured with the following options:
205
239
 
206
240
  | Option | Description | Default |
207
241
  |-----------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|
208
- | 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. | 3000 |
242
+ | 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 |
209
243
  | 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 |
210
244
  | 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 |
211
245
 
@@ -216,7 +250,7 @@ The store can be configured with the following options:
216
250
  A model can be simply created with:
217
251
 
218
252
  ```js
219
- const book = new Model("book", `https://rest.example.net/api/v1/books`);
253
+ const book = new Model("book", `https://api.example.net/books`);
220
254
  ```
221
255
 
222
256
  However, sometimes you may want to define a more complex interaction with the API. In such cases you can pass options to perform more elaborated model's initializations.
@@ -227,17 +261,18 @@ const book = new Model("book", options);
227
261
 
228
262
  All the possible options for a model creation are (they are all optional):
229
263
 
230
- | Name | Description | Default |
231
- |----------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------|
232
- | retrieve | Describes the operation to retrieve the collection of objects from the REST API. It can be an operation object or a function. See [operations](#operations). | `{method: "get"}` |
233
- | insert | Describes the operation to insert a new object in the collection. It can be an operation object or a function. See [operations](#operations). | `{method: "post"}` |
234
- | update | Describes the operation to update objects of the collection. It can be an operation object or a function. See [operations](#operations). | `{method: "put"}` |
235
- | delete | Describes the operation to remove objects from the collection. It can be an operation object or a function. See [operations](#operations). | `{method: "delete"}` |
236
- | fields | An array of strings defining which attributes the retrieved objects should have. Essentially, it allows you to contemporarily specify the [X-Fields header](https://flask-restplus.readthedocs.io/en/stable/mask.html) and the [fields GET parameter](https://developers.google.com/slides/api/guides/performance#partial). This reduces transfer size and memory usage. E.g., if you have a collection of books, of which you are interested only in the name, you can define `fields: ["name"]`. In combination with `load` it allows for partial lazy load of the objects. | All the fields |
237
- | headers | A dictionary of headers for the HTTP request. E.g., `{"Authorization": "bearer XXXX"}`. | No headers |
238
- | load | A function that allows to enrich the objects on demand. E.g., you can use `fields` to download only the titles of a collection of books, and `load` to load completely the object. See [object enrichment](#object-enrichment). |
239
- | axios | It allows to specify an axios instance to be used for the queries. If not specified, a new one will be used. | A new axios instance |
240
-
264
+ | Name | Description | Default |
265
+ |--------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------|
266
+ | retrieve | Describes the operation to retrieve the collection of objects from the REST API. It can be an operation object or a function. See [operations](#operations). | `{method: "get"}` |
267
+ | insert | Describes the operation to insert a new object in the collection. It can be an operation object or a function. See [operations](#operations). | `{method: "post"}` |
268
+ | update | Describes the operation to update objects of the collection. It can be an operation object or a function. See [operations](#operations). | `{method: "put"}` |
269
+ | delete | Describes the operation to remove objects from the collection. It can be an operation object or a function. See [operations](#operations). | `{method: "delete"}` |
270
+ | fields | An array of strings defining which attributes the retrieved objects should have. Essentially, it allows you to contemporarily specify the [X-Fields header](https://flask-restplus.readthedocs.io/en/stable/mask.html) and the [fields GET parameter](https://developers.google.com/slides/api/guides/performance#partial). This reduces transfer size and memory usage. E.g., if you have a collection of books, of which you are interested only in the name, you can define `fields: ["name"]`. In combination with `load` it allows for partial lazy load of the objects. | All the fields |
271
+ | headers | A dictionary of headers for the HTTP request. E.g., `{"Authorization": "bearer XXXX"}`. | No headers |
272
+ | load | A function that allows to enrich the objects on demand. E.g., you can use `fields` to download only the titles of a collection of books, and `load` to load completely the object. See [object enrichment](#object-enrichment). |
273
+ | axios | It allows to specify an axios instance to be used for the queries. If not specified, a new one will be used. | A new axios instance |
274
+ | parseMoment | Automatically creates Moment.js objects out of ISO8601 strings. E.g., if an object has a property `createdAt: "2022-01-07T21:38:50.295Z"`, this will be transformed to a moment object. | |
275
+ | 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. |
241
276
 
242
277
  ### Operations
243
278
  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.
@@ -418,14 +453,29 @@ author1.getRelation("book", (book) => book.price < 20)
418
453
 
419
454
  The store has the following method.
420
455
 
421
- | Method | Description |
422
- |------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------|
423
- | addModel(model) | Introduce a new model to the store. If lazyLoad = false (default), the model is populated with the objects coming from the API. |
424
- | get(type, id) | It allows to retrieve an object based on its type and store's ID (see `getId()` in [objects methods](#objects-methods). The type is the name of the model. |
425
- | find(type,filterFunction) | The promise-oriented method to access objects given a type and a filter function. See [example 1](#example-1). |
426
- | delete(objects) | It deletes an array of objects. See [example 1](#example-3). |
427
- | delete(type, filterFunction) | It deleted objects given an array and a filter function. See [example 1](#example-3). |
428
- | insert(type, object) | It creates a new object of a given type and inserts it in the store. |
456
+ | Method | Description |
457
+ |--------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
458
+ | on(event, callback) | Method to subscribe to the events emitted by the store. See [events](#store-events) below. |
459
+ | addModel(model) | Introduce a new model to the store. If lazyLoad = false (default), the model is populated with the objects coming from the API. |
460
+ | get(type, id) | It allows to retrieve an object based on its type and store's ID (see `getId()` in [objects methods](#objects-methods). The type is the name of the model. |
461
+ | 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). |
462
+ | delete(objects) | It deletes an array of objects. See [example 1](#example-3). |
463
+ | delete(type, filterFunction) | It deleted objects given an array and a filter function. See [example 1](#example-3). |
464
+ | insert(type, object) | It creates a new object of a given type and inserts it in the store. |
465
+ | 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). |
466
+ | 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). |
467
+ | unsubscribe(key) | Method to terminate a subscription given a subscription key. See [example 5](#example-5---observability). |
468
+ | 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). |
469
+ | 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). |
470
+
471
+ ## Store events
472
+ The store emits the following events:
473
+
474
+ | Name | Description |
475
+ |---------|---------------------------------------------------------------------------------------------------------------------------------------|
476
+ | error | To listen the errors emitted by the store. |
477
+ | save | Possible emitted values are `start` and `end`. They are emitted when the store starts/finishes to persist the data (API interaction). |
478
+ | loading | The event is emitted while a new model is loaded. The value contains something like `{status: "start", model: "book"}` |
429
479
 
430
480
  ## Objects methods
431
481
  Each object created is enriched with the following methods.
@@ -434,7 +484,7 @@ Each object created is enriched with the following methods.
434
484
  | Method | Description |
435
485
  |------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
436
486
  | 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. |
437
- | set(attribute, value) | 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). |
487
+ | 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). |
438
488
  | save() | Method to save the object. You can do `store.save()` instead. |
439
489
  | destroy() | Method to delete the object. You can do `store.delete()` instead. |
440
490
  | get(attribute) | If you like symmetry and since you do `.set()` you would like to do also `.get()` of the attributes. It does not provide any advantage compared to accessing directly the attribute (e.g., `author.name`). |
@@ -468,7 +518,7 @@ The option `autoSave` can be `true`, `false`, or a number (milliseconds).
468
518
 
469
519
  The store will perform as if the `autoSave` was set to `true`; hence, changes performed with `.set(attribute, value)` are synced. However, it will periodically attempt also a `store.save()`. Since `store.save()` is always able to recognize edited objects, also changes directly operated on an attribute of the object (`object.name = "Dante"`) are synced.
470
520
 
471
-
521
+ > The method set takes 3 parameters in input, "attribute, value, hidden". The "hidden" parameter allows you to set an attribute to the object that will not trigger autoSave. However, hidden attributes cannot be persisted (they act like "hiddenFields" specified during model creation).
472
522
 
473
523
  ## API interaction
474
524
  DataFlux is able to identify three sets of objects: inserted, updated, deleted.
package/dist/Model.js CHANGED
@@ -19,26 +19,32 @@ function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArra
19
19
 
20
20
  function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
21
21
 
22
- function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
23
-
24
- function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
25
-
26
22
  function _iterableToArrayLimit(arr, i) { var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"]; if (_i == null) return; var _arr = []; var _n = true; var _d = false; var _s, _e; try { for (_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; }
27
23
 
28
24
  function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
29
25
 
26
+ function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e2) { throw _e2; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e3) { didErr = true; err = _e3; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; }
27
+
28
+ function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
29
+
30
+ function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
31
+
30
32
  function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
31
33
 
32
34
  function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
33
35
 
34
36
  function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
35
37
 
38
+ function _classPrivateMethodInitSpec(obj, privateSet) { _checkPrivateRedeclaration(obj, privateSet); privateSet.add(obj); }
39
+
36
40
  function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
37
41
 
38
42
  function _classPrivateFieldInitSpec(obj, privateMap, value) { _checkPrivateRedeclaration(obj, privateMap); privateMap.set(obj, value); }
39
43
 
40
44
  function _checkPrivateRedeclaration(obj, privateCollection) { if (privateCollection.has(obj)) { throw new TypeError("Cannot initialize the same private elements twice on an object"); } }
41
45
 
46
+ function _classPrivateMethodGet(receiver, privateSet, fn) { if (!privateSet.has(receiver)) { throw new TypeError("attempted to get private field on non-instance"); } return fn; }
47
+
42
48
  function _classPrivateFieldGet(receiver, privateMap) { var descriptor = _classExtractFieldDescriptor(receiver, privateMap, "get"); return _classApplyDescriptorGet(receiver, descriptor); }
43
49
 
44
50
  function _classApplyDescriptorGet(receiver, descriptor) { if (descriptor.get) { return descriptor.get.call(receiver); } return descriptor.value; }
@@ -83,10 +89,16 @@ var _axios = /*#__PURE__*/new WeakMap();
83
89
 
84
90
  var _loadFunction = /*#__PURE__*/new WeakMap();
85
91
 
92
+ var _hiddenFields = /*#__PURE__*/new WeakMap();
93
+
94
+ var _error = /*#__PURE__*/new WeakSet();
95
+
86
96
  var _addRelationByField = /*#__PURE__*/new WeakMap();
87
97
 
88
98
  var _addRelationByFilter = /*#__PURE__*/new WeakMap();
89
99
 
100
+ var _removeHiddenFields = /*#__PURE__*/new WeakMap();
101
+
90
102
  var _bulkOperation = /*#__PURE__*/new WeakMap();
91
103
 
92
104
  var _toArray = /*#__PURE__*/new WeakMap();
@@ -104,6 +116,8 @@ var Model = /*#__PURE__*/_createClass(function Model(name) {
104
116
 
105
117
  _classCallCheck(this, Model);
106
118
 
119
+ _classPrivateMethodInitSpec(this, _error);
120
+
107
121
  _classPrivateFieldInitSpec(this, _type, {
108
122
  writable: true,
109
123
  value: void 0
@@ -159,6 +173,11 @@ var Model = /*#__PURE__*/_createClass(function Model(name) {
159
173
  value: void 0
160
174
  });
161
175
 
176
+ _classPrivateFieldInitSpec(this, _hiddenFields, {
177
+ writable: true,
178
+ value: void 0
179
+ });
180
+
162
181
  _defineProperty(this, "getStore", function () {
163
182
  return _classPrivateFieldGet(_this, _store);
164
183
  });
@@ -173,23 +192,29 @@ var Model = /*#__PURE__*/_createClass(function Model(name) {
173
192
 
174
193
  _defineProperty(this, "load", function (obj) {
175
194
  if (_classPrivateFieldGet(_this, _loadFunction)) {
176
- var res = _classPrivateFieldGet(_this, _loadFunction).call(_this, obj.toJSON());
177
-
178
- if (typeof res === "string") {
179
- return _classPrivateFieldGet(_this, _axios).call(_this, {
180
- method: "get",
181
- url: res,
182
- responseType: "json"
183
- }).then(function (data) {
184
- return applyData(obj, data.data);
185
- });
186
- } else {
187
- return res.then(function (data) {
188
- return applyData(obj, data);
189
- });
190
- }
195
+ return _this.getStore().whenSaved(_this.getType())["catch"](function () {
196
+ throw new Error("You cannot perform load() on an unsaved object.");
197
+ }).then(function () {
198
+ var res = _classPrivateFieldGet(_this, _loadFunction).call(_this, obj.toJSON());
199
+
200
+ if (typeof res === "string") {
201
+ return _classPrivateFieldGet(_this, _axios).call(_this, {
202
+ method: "get",
203
+ url: res,
204
+ responseType: "json"
205
+ }).then(function (data) {
206
+ return data.data;
207
+ });
208
+ } else {
209
+ return res;
210
+ }
211
+ }).then(function (data) {
212
+ return applyData(obj, data);
213
+ })["catch"](function (error) {
214
+ return _classPrivateMethodGet(_this, _error, _error2).call(_this, error);
215
+ });
191
216
  } else {
192
- return Promise.reject("You must define a loading function in the model to enable load().");
217
+ return _classPrivateMethodGet(_this, _error, _error2).call(_this, "You must define a loading function in the model to enable load().");
193
218
  }
194
219
  });
195
220
 
@@ -218,17 +243,15 @@ var Model = /*#__PURE__*/_createClass(function Model(name) {
218
243
  var filterRelation = _classPrivateFieldGet(_this, _includes)[includedType];
219
244
 
220
245
  if (filterRelation) {
221
- return _this.getStore().find(includedType, function (item) {
222
- return filterRelation(parentObject, item);
223
- }).then(function (data) {
224
- if (filterFunction) {
225
- return data.filter(filterFunction);
226
- }
227
-
228
- return data;
246
+ return parentObject.load()["catch"](function () {}).then(function () {
247
+ return _this.getStore().find(includedType, function (item) {
248
+ return filterRelation(parentObject, item);
249
+ }).then(function (data) {
250
+ return filterFunction ? data.filter(filterFunction) : data;
251
+ });
229
252
  });
230
253
  } else {
231
- return Promise.reject("The relation doesn't exist");
254
+ return _classPrivateMethodGet(_this, _error, _error2).call(_this, "The relation doesn't exist");
232
255
  }
233
256
  });
234
257
 
@@ -237,7 +260,15 @@ var Model = /*#__PURE__*/_createClass(function Model(name) {
237
260
  });
238
261
 
239
262
  _defineProperty(this, "retrieveAll", function () {
240
- return (0, _modelHooksUtils.executeHook)("retrieve", _classPrivateFieldGet(_this, _retrieveHook), null, _classPrivateFieldGet(_this, _axios)).then(_classPrivateFieldGet(_this, _toArray));
263
+ return (0, _modelHooksUtils.executeHook)("retrieve", _classPrivateFieldGet(_this, _retrieveHook), null, _classPrivateFieldGet(_this, _axios)).then(function (data) {
264
+ if (Array.isArray(data)) {
265
+ return data;
266
+ } else {
267
+ _classPrivateFieldSet(_this, _singleItemQuery, true);
268
+
269
+ return [data];
270
+ }
271
+ });
241
272
  });
242
273
 
243
274
  _defineProperty(this, "insertObjects", function (objects) {
@@ -273,13 +304,38 @@ var Model = /*#__PURE__*/_createClass(function Model(name) {
273
304
  }
274
305
  });
275
306
 
307
+ _classPrivateFieldInitSpec(this, _removeHiddenFields, {
308
+ writable: true,
309
+ value: function value(json) {
310
+ var _iterator = _createForOfIteratorHelper(_classPrivateFieldGet(_this, _hiddenFields)),
311
+ _step;
312
+
313
+ try {
314
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
315
+ var attribute = _step.value;
316
+ delete json[attribute];
317
+ }
318
+ } catch (err) {
319
+ _iterator.e(err);
320
+ } finally {
321
+ _iterator.f();
322
+ }
323
+
324
+ return json;
325
+ }
326
+ });
327
+
276
328
  _classPrivateFieldInitSpec(this, _bulkOperation, {
277
329
  writable: true,
278
330
  value: function value(objects, action) {
279
331
  if (_classPrivateFieldGet(_this, _singleItemQuery)) {
280
- return (0, _batchPromises["default"])(_classPrivateFieldGet(_this, _batchSize), objects, action);
332
+ return (0, _batchPromises["default"])(_classPrivateFieldGet(_this, _batchSize), objects.map(function (i) {
333
+ return _classPrivateFieldGet(_this, _removeHiddenFields).call(_this, i.toJSON());
334
+ }), action);
281
335
  } else {
282
- return action(objects);
336
+ return action(objects.map(function (i) {
337
+ return _classPrivateFieldGet(_this, _removeHiddenFields).call(_this, i.toJSON());
338
+ }));
283
339
  }
284
340
  }
285
341
  });
@@ -290,8 +346,6 @@ var Model = /*#__PURE__*/_createClass(function Model(name) {
290
346
  if (Array.isArray(data)) {
291
347
  return data;
292
348
  } else {
293
- _classPrivateFieldSet(_this, _singleItemQuery, true);
294
-
295
349
  return [data];
296
350
  }
297
351
  }
@@ -320,12 +374,18 @@ var Model = /*#__PURE__*/_createClass(function Model(name) {
320
374
 
321
375
  _classPrivateFieldSet(this, _type, name);
322
376
 
377
+ this.options = {
378
+ parseMoment: options.parseMoment
379
+ };
380
+
323
381
  _classPrivateFieldSet(this, _store, null);
324
382
 
325
383
  _classPrivateFieldSet(this, _includes, {});
326
384
 
327
385
  _classPrivateFieldSet(this, _axios, options.axios || _axios2["default"]);
328
386
 
387
+ _classPrivateFieldSet(this, _hiddenFields, options.hiddenFields || []);
388
+
329
389
  _classPrivateFieldSet(this, _loadFunction, options.load || null);
330
390
 
331
391
  if (!name || !options) {
@@ -358,4 +418,10 @@ var Model = /*#__PURE__*/_createClass(function Model(name) {
358
418
 
359
419
  });
360
420
 
361
- exports["default"] = Model;
421
+ exports["default"] = Model;
422
+
423
+ function _error2(error) {
424
+ error = error.message || error;
425
+ this.getStore().pubSub.publish("error", error);
426
+ return Promise.reject(error);
427
+ }