dataflux 1.0.4 → 1.2.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/README.md +270 -127
- package/dist/Model.js +241 -57
- package/dist/ObserverStore.js +178 -131
- package/dist/PersistentStore.js +104 -75
- package/dist/PubSub.js +59 -0
- package/dist/ReactStore.js +22 -2
- package/dist/Store.js +295 -151
- package/dist/StoreObject.js +33 -0
- package/dist/fingerprint.js +8 -12
- package/dist/modelHooksUtils.js +61 -19
- package/package.json +6 -5
package/README.md
CHANGED
|
@@ -13,13 +13,22 @@ DataFlux is a JavaScript library that automatically interfaces with your REST AP
|
|
|
13
13
|
|
|
14
14
|
## Installation
|
|
15
15
|
|
|
16
|
+
Using npm:
|
|
16
17
|
```sh
|
|
17
18
|
npm install dataflux
|
|
18
19
|
```
|
|
19
20
|
|
|
21
|
+
Using jsDelivr CDN:
|
|
22
|
+
```html
|
|
23
|
+
<script src="https://cdn.jsdelivr.net/npm/dataflux/dist/index.js"></script>
|
|
24
|
+
```
|
|
25
|
+
|
|
20
26
|
## Examples
|
|
21
27
|
|
|
22
|
-
|
|
28
|
+
Create your global store by creating a file (e.g., named `store.js`) containing the model declaration.
|
|
29
|
+
|
|
30
|
+
Consider the following hypothetical store/model declaration common to all the examples below:
|
|
31
|
+
|
|
23
32
|
```js
|
|
24
33
|
import {Store, Model} from "dataflux";
|
|
25
34
|
|
|
@@ -30,16 +39,27 @@ const book = new Model("book", `https://rest.example.net/api/v1/books`);
|
|
|
30
39
|
store.addModel(author);
|
|
31
40
|
store.addModel(book);
|
|
32
41
|
|
|
33
|
-
|
|
42
|
+
// An object relation between author.id and book.authorId as follows
|
|
43
|
+
author.addRelation(book, "id", "authorId");
|
|
44
|
+
|
|
45
|
+
export default store;
|
|
34
46
|
```
|
|
35
|
-
|
|
36
|
-
|
|
47
|
+
|
|
48
|
+
|
|
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.
|
|
50
|
+
|
|
51
|
+
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
|
+
|
|
53
|
+
A JS object is automatically created for each item returned by the API, for each model. The object has the same properties of the JSON item plus some high-level method (see [objects methods](#objects-methods)).
|
|
54
|
+
**All the objects are indexed in the store.**
|
|
37
55
|
|
|
38
56
|
### Example 1
|
|
39
57
|
|
|
40
58
|
Retrieve and edit an author not knowing the ID:
|
|
41
59
|
|
|
42
60
|
```js
|
|
61
|
+
import store from "./store";
|
|
62
|
+
|
|
43
63
|
// Find the author Dante Alighieri
|
|
44
64
|
store.find("author", ({name, surname}) => name == "Dante" && surname == "Alighieri")
|
|
45
65
|
.then(([author]) => {
|
|
@@ -148,9 +168,9 @@ class MyComponent extends React.Component {
|
|
|
148
168
|
componentDidMount() {
|
|
149
169
|
// Get all books with a price < 20
|
|
150
170
|
store.findAll("book", "books", this, ({price}) => price < 20);
|
|
151
|
-
// Every time the dataset changes, a setState will be automatically
|
|
152
|
-
// An attribute "books" will be added/updated in the
|
|
153
|
-
// state remains unchanged).
|
|
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).
|
|
154
174
|
|
|
155
175
|
// findAll is a syntactic sugar for:
|
|
156
176
|
// const callback = (books) => {this.setState({...this.state, books})};
|
|
@@ -160,19 +180,23 @@ class MyComponent extends React.Component {
|
|
|
160
180
|
render(){
|
|
161
181
|
const {books} = this.state;
|
|
162
182
|
|
|
163
|
-
return books.map(book =>
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
183
|
+
return books.map(book =>
|
|
184
|
+
<Book
|
|
185
|
+
onTitleChange={(title) => book.set("title", title)}
|
|
186
|
+
// onTitleChange will alter the book and so the current
|
|
187
|
+
// state of "books" (a setState will be performed).
|
|
188
|
+
//
|
|
189
|
+
// Alternatively:
|
|
190
|
+
// onTitleChange={store.handleChange(book, "title")}
|
|
191
|
+
// is a syntactic sugar of the function above
|
|
192
|
+
/>);
|
|
171
193
|
}
|
|
172
194
|
}
|
|
173
195
|
```
|
|
174
196
|
|
|
175
|
-
|
|
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).
|
|
198
|
+
|
|
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.
|
|
176
200
|
|
|
177
201
|
## Configuration
|
|
178
202
|
|
|
@@ -187,147 +211,86 @@ The store can be configured with the following options:
|
|
|
187
211
|
|
|
188
212
|
|
|
189
213
|
|
|
190
|
-
##
|
|
191
|
-
The option `autoSave` can be `true`, `false`, a number (milliseconds).
|
|
192
|
-
|
|
193
|
-
* When `autoSave` is set to `false`, the following operations are equivalent:
|
|
194
|
-
```js
|
|
195
|
-
object.set("name", "Dante");
|
|
196
|
-
|
|
197
|
-
object.name = "Dante";
|
|
198
|
-
```
|
|
199
|
-
No matter which of the two approaches you use, the command `store.save()` must be invoked to sync the changes with the server.
|
|
200
|
-
|
|
201
|
-
> The command `store.save()` is always able to recognize changed objects that need to be persisted.
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
* **When `autoSave` is set to `true`, the above operations are NOT equivalent.**
|
|
205
|
-
|
|
206
|
-
Using `.set(attribute, value)` informs the store that an object changed, while changing directly a property the object (`object.name = "Dante"`) does not. Since the store is not aware of the changes, they will not be synced with the server. **To avoid this, always use `.set(attribute, value)`.**
|
|
214
|
+
## Models creation
|
|
207
215
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
* **When `autoSave` is set to an amount of milliseconds, the above operations are still NOT equivalent, but...**
|
|
211
|
-
|
|
212
|
-
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 a property of the object (`object.name = "Dante"`) are synced.
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
## API interaction
|
|
216
|
-
DataFlux is able to identify three sets of objects: inserted, updated, deleted.
|
|
217
|
-
Each of these set is synced to the server with POST, PUT, and DELETE REST operations, respectively.
|
|
218
|
-
|
|
219
|
-
The interaction with the API is handled automatically, multiple requests are prevented and operations are bundled as much as possible.
|
|
216
|
+
A model can be simply created with:
|
|
220
217
|
|
|
221
|
-
For example (with autoSave):
|
|
222
218
|
```js
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
// The commands above will correspond to 1 single query to the REST API.
|
|
226
|
-
|
|
227
|
-
author1.set("name", "Dante");
|
|
228
|
-
author2.set("name", "Italo");
|
|
229
|
-
author3.set("name", "Umberto");
|
|
230
|
-
author4.name = "Primo";
|
|
231
|
-
// The commands above will correspond to 1 single query to the REST API,
|
|
232
|
-
// no matter how many editing operations.
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
author1.set("name", "Dante");
|
|
236
|
-
setTimeout(() => author2.set("name", "Italo"), 10000); // To "emulate" a user interaction.
|
|
237
|
-
// The commands above will correspond to 2 queries to the REST API
|
|
219
|
+
const book = new Model("book", `https://rest.example.net/api/v1/books`);
|
|
220
|
+
```
|
|
238
221
|
|
|
239
|
-
|
|
240
|
-
store.insert(author1);
|
|
241
|
-
author1.set("name", "Dante");
|
|
242
|
-
author1.delete(author1);
|
|
243
|
-
// The commands above will not produce any query to the REST API since
|
|
244
|
-
// the initial and final states of the store are the same (object created and removed).
|
|
222
|
+
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.
|
|
245
223
|
|
|
224
|
+
```js
|
|
225
|
+
const book = new Model("book", options);
|
|
246
226
|
```
|
|
247
227
|
|
|
248
|
-
|
|
228
|
+
All the possible options for a model creation are (they are all optional):
|
|
249
229
|
|
|
250
|
-
|
|
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 |
|
|
251
240
|
|
|
252
|
-
The following format is automatically accepted, and it will create two objects.
|
|
253
241
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
{
|
|
257
|
-
"name": "Dante",
|
|
258
|
-
"surname": "Alighieri",
|
|
259
|
-
"reviews": [...]
|
|
260
|
-
},
|
|
261
|
-
{
|
|
262
|
-
"name": "Giovanni",
|
|
263
|
-
"surname": "Boccaccio",
|
|
264
|
-
"reviews": [...]
|
|
265
|
-
}
|
|
266
|
-
]
|
|
267
|
-
```
|
|
242
|
+
### Operations
|
|
243
|
+
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.
|
|
268
244
|
|
|
269
|
-
|
|
245
|
+
#### Operation object
|
|
270
246
|
|
|
271
|
-
|
|
272
|
-
{
|
|
273
|
-
"username": "Massimo",
|
|
274
|
-
"website": "https://massimocandela.com",
|
|
275
|
-
"otherParameters": {
|
|
276
|
-
...
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
```
|
|
280
|
-
|
|
281
|
-
The following format will create a single object, which probably you don't want. Use a function in [model creation](#model-creation) to unwrap the data.
|
|
247
|
+
An operation object is an object like follows:
|
|
282
248
|
|
|
283
249
|
```json
|
|
284
250
|
{
|
|
285
|
-
"
|
|
286
|
-
"
|
|
251
|
+
"method": "get",
|
|
252
|
+
"url": "https://api.example.com",
|
|
253
|
+
"headers": {"Authorization": "bearer XXXX"}
|
|
287
254
|
}
|
|
288
255
|
```
|
|
289
256
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
A model can be simply created with:
|
|
293
|
-
|
|
294
|
-
```js
|
|
295
|
-
const book = new Model("book", `https://rest.example.net/api/v1/books`);
|
|
296
|
-
```
|
|
297
|
-
|
|
298
|
-
However, in many cases more complex APIs require different settings for the various operations.
|
|
299
|
-
|
|
300
|
-
Instead of an url, you can pass options to perform more elaborated Model's initialization.
|
|
257
|
+
Usage example:
|
|
301
258
|
|
|
302
259
|
```js
|
|
303
260
|
const options = {
|
|
261
|
+
|
|
304
262
|
retrieve: {
|
|
305
263
|
method: "get",
|
|
306
|
-
url: "https://rest.example.net/api/v1/books"
|
|
264
|
+
url: "https://rest.example.net/api/v1/books",
|
|
265
|
+
headers: {} // Headers can be define per operation or globally
|
|
307
266
|
},
|
|
267
|
+
|
|
308
268
|
insert: {
|
|
309
269
|
method: "post",
|
|
310
270
|
url: "https://rest.example.net/api/v1/books"
|
|
311
271
|
},
|
|
272
|
+
|
|
312
273
|
update: {
|
|
313
274
|
method: "put",
|
|
314
275
|
url: "https://rest.example.net/api/v1/books"
|
|
315
276
|
},
|
|
277
|
+
|
|
316
278
|
delete: {
|
|
317
279
|
method: "delete",
|
|
318
280
|
url: "https://rest.example.net/api/v1/books"
|
|
319
|
-
}
|
|
281
|
+
},
|
|
282
|
+
|
|
283
|
+
headers: {"Authorization": "bearer XXXX"} // Globally defined headers
|
|
320
284
|
};
|
|
321
285
|
|
|
322
286
|
const book = new Model("book", options);
|
|
323
287
|
```
|
|
324
|
-
You don't
|
|
288
|
+
You don't need to specify all the attributes for each operation, only the ones you want to variate from the defaults (see table above). If a url is not specified for an operation, the url defined for the `GET` operation is used.
|
|
325
289
|
|
|
326
|
-
For example, if you want to perform inserts and updates with
|
|
290
|
+
For example, if you are ok with the default behaviour except you want to perform both inserts and updates with `PUT` (instead of post/put), you can do:
|
|
327
291
|
```js
|
|
328
292
|
const options = {
|
|
329
293
|
retrieve: {
|
|
330
|
-
method: "get",
|
|
331
294
|
url: "https://rest.example.net/api/v1/books"
|
|
332
295
|
},
|
|
333
296
|
insert: {
|
|
@@ -338,7 +301,9 @@ const options = {
|
|
|
338
301
|
const book = new Model("book", options);
|
|
339
302
|
```
|
|
340
303
|
|
|
341
|
-
|
|
304
|
+
#### Operation function
|
|
305
|
+
|
|
306
|
+
To be even more flexible, you can pass functions and handle yourself the operations. An operation function must return a promise, the promise must return an array of JSON objects when these are ready.
|
|
342
307
|
|
|
343
308
|
|
|
344
309
|
```js
|
|
@@ -352,7 +317,7 @@ const options = {
|
|
|
352
317
|
insert: (data) => {
|
|
353
318
|
// 1) recieve the data from the store
|
|
354
319
|
// 2) transform the data however you like
|
|
355
|
-
// 3) send data to server
|
|
320
|
+
// 3) send data to server and resolve empty
|
|
356
321
|
return Promise.resolve();
|
|
357
322
|
}
|
|
358
323
|
};
|
|
@@ -360,7 +325,51 @@ const options = {
|
|
|
360
325
|
const book = new Model("book", options);
|
|
361
326
|
```
|
|
362
327
|
|
|
363
|
-
|
|
328
|
+
#### Object enrichment
|
|
329
|
+
|
|
330
|
+
DataFlux objects can have a `load()` method which enables you to load extra attributes of an object.
|
|
331
|
+
|
|
332
|
+
Example of usage of `load()`:
|
|
333
|
+
|
|
334
|
+
```js
|
|
335
|
+
console.log(book);
|
|
336
|
+
// {title: "The little prince"}
|
|
337
|
+
|
|
338
|
+
book.load();
|
|
339
|
+
// The book object will be updated and it will contain
|
|
340
|
+
// {id: 23, title: "The little prince", price: 9.99, year: 1943}
|
|
341
|
+
//
|
|
342
|
+
// If you are using React, book.load() will automatically update your state
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
To enable such a method, you have to define the `load` option during model creation. The load option accepts a function that returns the complete object of a url. The function receives in input the current JSON object.
|
|
348
|
+
|
|
349
|
+
Example of creation of a model with `load` support:
|
|
350
|
+
```js
|
|
351
|
+
const book = new Model("book", {
|
|
352
|
+
retrieve: {
|
|
353
|
+
url: "https://rest.example.net/api/v1/books/"
|
|
354
|
+
},
|
|
355
|
+
fields: ["title"], // By default the books will contain only the title
|
|
356
|
+
load: (object) => { // "object" contains the current object to be enriched
|
|
357
|
+
|
|
358
|
+
// Return the url where to retrieve the object
|
|
359
|
+
return "https://rest.example.net/api/v1/books/" + object.id;
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
```
|
|
363
|
+
Alternatively, the `load` function can return directly the enriched object.
|
|
364
|
+
```js
|
|
365
|
+
const book = new Model("book", {
|
|
366
|
+
load: (object) => {
|
|
367
|
+
return axios({...}).then(raw => raw.data);
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### Model relations
|
|
364
373
|
|
|
365
374
|
Optionally, you can create relations among models.
|
|
366
375
|
|
|
@@ -374,15 +383,30 @@ author.addRelation(book, "id", "authorId");
|
|
|
374
383
|
```
|
|
375
384
|
In this example, we added an explicit relation between `author.id` and `book.authorId`. This means that the store will return as books belonging to the author, all the books having `authorId` equals to the id of the author.
|
|
376
385
|
|
|
377
|
-
|
|
386
|
+
|
|
387
|
+
Other ways to declare relations:
|
|
388
|
+
|
|
389
|
+
* `account.addRelation("user", "userId")`
|
|
390
|
+
|
|
391
|
+
When the third parameter is missing, it defaults to "id" (i.e., it is the shorter version of `account.addRelation("user", "userId", "id")`). This means that the store will return as user of the account, the user having `id` equals to `account.userId`.
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
* `author.addRelation("book", filterFunction)`
|
|
395
|
+
|
|
396
|
+
When the second parameter is a function, the function will be used by the store to filter the objects of the connected model. The `filterFunction` receives two parameters `(parentObject, possibleChildObject)` and returns a boolean. In this way you can create complex relations; e.g., a `filterFunction` equal to `(author, book) => author.name == book.authorName && author.surname == book.authorSurname` creates a relation based on two attributes.
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
#### Accessing model relations
|
|
400
|
+
|
|
401
|
+
Once the relation between the author and the book models is declared, all the author objects will expose a method `getRelation(type, filterFunction)` that can be used to retrieve a relation associated with the author. The `type` defines the model type (in our case, 'book'), the `filterFunction` is an optional parameter that can be passed in case the output needs an additional filtering.
|
|
378
402
|
|
|
379
403
|
For example, imagine you have the `author1` object defined in the examples above (Dante Alighieri):
|
|
380
404
|
|
|
381
405
|
```js
|
|
382
406
|
author1.getRelation("book")
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
407
|
+
.then(dantesBooks => {
|
|
408
|
+
// Do something with Dante's books
|
|
409
|
+
});
|
|
386
410
|
|
|
387
411
|
// Or..
|
|
388
412
|
author1.getRelation("book", (book) => book.price < 20)
|
|
@@ -390,14 +414,133 @@ author1.getRelation("book", (book) => book.price < 20)
|
|
|
390
414
|
// Do something with Dante's books cheaper than 20
|
|
391
415
|
});
|
|
392
416
|
```
|
|
417
|
+
## Store methods
|
|
418
|
+
|
|
419
|
+
The store has the following method.
|
|
420
|
+
|
|
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. |
|
|
429
|
+
|
|
430
|
+
## Objects methods
|
|
431
|
+
Each object created is enriched with the following methods.
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
| Method | Description |
|
|
435
|
+
|------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
436
|
+
| 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). |
|
|
438
|
+
| save() | Method to save the object. You can do `store.save()` instead. |
|
|
439
|
+
| destroy() | Method to delete the object. You can do `store.delete()` instead. |
|
|
440
|
+
| 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`). |
|
|
441
|
+
| getRelation(model, filterFunction) | To get all the objects respecting a specific relation with this object (see [model relations](#model-relations)). |
|
|
442
|
+
| toJSON() | It returns a pure JSON representation of the object. |
|
|
443
|
+
| toString() | It returns a string representation of the object. |
|
|
444
|
+
| 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. |
|
|
445
|
+
| 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. |
|
|
393
446
|
|
|
394
|
-
|
|
447
|
+
## Editing objects
|
|
448
|
+
The option `autoSave` can be `true`, `false`, or a number (milliseconds).
|
|
395
449
|
|
|
396
|
-
* `
|
|
397
|
-
|
|
398
|
-
|
|
450
|
+
* When `autoSave` is set to `false`, the following operations are equivalent:
|
|
451
|
+
```js
|
|
452
|
+
object.set("name", "Dante");
|
|
453
|
+
|
|
454
|
+
object.name = "Dante";
|
|
455
|
+
```
|
|
456
|
+
No matter which of the two approaches you use, the command `store.save()` must be invoked to sync the changes with the server.
|
|
399
457
|
|
|
458
|
+
> The command `store.save()` is always able to recognize changed objects that need to be persisted.
|
|
400
459
|
|
|
401
|
-
* `author.addRelation("book", filterFunction)`
|
|
402
460
|
|
|
403
|
-
|
|
461
|
+
* **When `autoSave` is set to `true`, the above operations are NOT equivalent.**
|
|
462
|
+
|
|
463
|
+
Using `.set(attribute, value)` informs the store that an object changed, while changing directly an attribute of the object (`object.name = "Dante"`) does not. Since the store is not aware of the changes, they will not be synced with the server. **To avoid this, always use `.set(attribute, value)`.**
|
|
464
|
+
|
|
465
|
+
> The commands `store.insert()`, `store.delete()`, and `object.destroy()` are always visible to the store, and so syncing is always performed when `autoSave` is `true`.
|
|
466
|
+
|
|
467
|
+
* **When `autoSave` is set to an amount of milliseconds, the above operations are still NOT equivalent, but...**
|
|
468
|
+
|
|
469
|
+
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
|
+
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
## API interaction
|
|
474
|
+
DataFlux is able to identify three sets of objects: inserted, updated, deleted.
|
|
475
|
+
Each of these set is synced with the server with POST, PUT, and DELETE REST operations, respectively.
|
|
476
|
+
|
|
477
|
+
The interaction with the API is handled automatically, multiple requests are prevented and operations are bundled as much as possible.
|
|
478
|
+
|
|
479
|
+
For example (with autoSave):
|
|
480
|
+
```js
|
|
481
|
+
store.find('book', (book) => book.price < 20);
|
|
482
|
+
store.find('book', (book) => book.price > 60);
|
|
483
|
+
// The commands above will correspond to 1 single query to the REST API.
|
|
484
|
+
|
|
485
|
+
author1.set("name", "Dante");
|
|
486
|
+
author2.set("name", "Italo");
|
|
487
|
+
author3.set("name", "Umberto");
|
|
488
|
+
author4.name = "Primo";
|
|
489
|
+
// The commands above will correspond to 1 single query to the REST API,
|
|
490
|
+
// no matter how many editing operations.
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
author1.set("name", "Dante");
|
|
494
|
+
setTimeout(() => author2.set("name", "Italo"), 10000); // To "emulate" a user interaction.
|
|
495
|
+
// The commands above will correspond to 2 queries to the REST API
|
|
496
|
+
|
|
497
|
+
const author1 = {surname: "Alighieri"};
|
|
498
|
+
store.insert(author1);
|
|
499
|
+
author1.set("name", "Dante");
|
|
500
|
+
store.delete(author1);
|
|
501
|
+
// The commands above will not produce any query to the REST API since
|
|
502
|
+
// the initial and final states of the store are the same (object created and removed).
|
|
503
|
+
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
### REST API format
|
|
507
|
+
|
|
508
|
+
The APIs must return/accept an array of JSON objects or a single object. If your API uses a different format, use a function in the [models creation](#models-creation) to transform the data.
|
|
509
|
+
|
|
510
|
+
The following format is automatically accepted, and it will create two objects.
|
|
511
|
+
|
|
512
|
+
```json
|
|
513
|
+
[
|
|
514
|
+
{
|
|
515
|
+
"name": "Dante",
|
|
516
|
+
"surname": "Alighieri",
|
|
517
|
+
"reviews": [...]
|
|
518
|
+
},
|
|
519
|
+
{
|
|
520
|
+
"name": "Giovanni",
|
|
521
|
+
"surname": "Boccaccio",
|
|
522
|
+
"reviews": [...]
|
|
523
|
+
}
|
|
524
|
+
]
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
The following format is automatically accepted, and it will create one object.
|
|
528
|
+
|
|
529
|
+
```json
|
|
530
|
+
{
|
|
531
|
+
"username": "Massimo",
|
|
532
|
+
"website": "https://massimocandela.com",
|
|
533
|
+
"otherParameters": {
|
|
534
|
+
...
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
The following format will create a single object, which probably you don't want. Use a function in [models creation](#models-creation) to unwrap the data.
|
|
540
|
+
|
|
541
|
+
```json
|
|
542
|
+
{
|
|
543
|
+
"books": [],
|
|
544
|
+
"authors": []
|
|
545
|
+
}
|
|
546
|
+
```
|