dataflux 1.0.2 → 1.1.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
@@ -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
- Consider the following store/model declaration common to all the examples below:
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
- author.addRelation(book, "id", "authorId"); // Add an object relation between author.id and book.authorId
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
- The store can be initialized with [various options](#configuration).
36
- The creation of a model requires at least a name and an url. GET, POST, PUT, DELETE operations are going to be performed against the same url. [Models can be created with considerably more advanced options.](#model-creation)
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 an 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 performed.
152
- // An attribute "books" will be added/updated in the state (the rest of 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
- return <Book
165
- // onTitleChange will alter the book and so the current state of "books"
166
- onTitleChange={(title) => book.set("title", title)}
167
- // alternatively, onTitleChange={store.handleChange(book, "title")}
168
- // is a syntactic sugar of the function above
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
- When the component will unmount, the `findAll` subscription will be terminated.
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,107 +211,7 @@ The store can be configured with the following options:
187
211
 
188
212
 
189
213
 
190
- ## Editing objects
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)`.**
207
-
208
- > 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`.
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.
220
-
221
- For example (with autoSave):
222
- ```js
223
- store.find('book', (book) => book.price < 20);
224
- store.find('book', (book) => book.price > 60);
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
238
-
239
- const author1 = {surname: "Alighieri"};
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).
245
-
246
- ```
247
-
248
- ### REST API format
249
-
250
- 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 [model creation](#model-creation) to transform the data.
251
-
252
- The following format is automatically accepted, and it will create two objects.
253
-
254
- ```json
255
- [
256
- {
257
- "name": "Dante",
258
- "surname": "Alighieri",
259
- "reviews": [...]
260
- },
261
- {
262
- "name": "Giovanni",
263
- "surname": "Boccaccio",
264
- "reviews": [...]
265
- }
266
- ]
267
- ```
268
-
269
- The following format is automatically accepted, and it will create one object.
270
-
271
- ```json
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.
282
-
283
- ```json
284
- {
285
- "books": [],
286
- "authors": []
287
- }
288
- ```
289
-
290
- ## Model creation
214
+ ## Models creation
291
215
 
292
216
  A model can be simply created with:
293
217
 
@@ -297,7 +221,7 @@ const book = new Model("book", `https://rest.example.net/api/v1/books`);
297
221
 
298
222
  However, in many cases more complex APIs require different settings for the various operations.
299
223
 
300
- Instead of an url, you can pass options to perform more elaborated Model's initialization.
224
+ Instead of an url, you can pass options to perform more elaborated Model's initializations.
301
225
 
302
226
  ```js
303
227
  const options = {
@@ -323,7 +247,7 @@ const book = new Model("book", options);
323
247
  ```
324
248
  You don't necessarily need to specify a url for each operation. If a url is not specified for an operation, the url defined for the `GET` operation is used.
325
249
 
326
- For example, if you want to perform inserts and updates with just `PUT`, you can simply do:
250
+ For example, if you want to perform both inserts and updates with `PUT`, you can do:
327
251
  ```js
328
252
  const options = {
329
253
  retrieve: {
@@ -360,7 +284,18 @@ const options = {
360
284
  const book = new Model("book", options);
361
285
  ```
362
286
 
363
- ## Relations among models
287
+ All the possible options for a model creation are:
288
+
289
+ | Name | Description |
290
+ |----------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------|
291
+ | retrieve | Describes the operation to retrieve a collection of objects from a REST API. It can be an object containing `method` and `url` or a function. See examples above. |
292
+ | insert | Describes the operation to insert a new object in the collection. It can be an object containing `method` and `url` or a function. See examples above. |
293
+ | update | Describes the operation to update objects of the collection. It can be an object containing `method` and `url` or a function. See examples above. |
294
+ | delete | Describes the operation to remove objects from the collection. It can be an object containing `method` and `url` or a function. See examples above. |
295
+ | axios | It allows to specify an axios instance to be used for the queries. If not specified, a new one will be used. |
296
+
297
+
298
+ ### Model relations
364
299
 
365
300
  Optionally, you can create relations among models.
366
301
 
@@ -374,15 +309,30 @@ author.addRelation(book, "id", "authorId");
374
309
  ```
375
310
  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
311
 
377
- Now all the author objects will have the 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.
312
+
313
+ Other ways to declare relations:
314
+
315
+ * `account.addRelation("user", "userId")`
316
+
317
+ 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`.
318
+
319
+
320
+ * `author.addRelation("book", filterFunction)`
321
+
322
+ 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.
323
+
324
+
325
+ #### Accessing model relations
326
+
327
+ 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
328
 
379
329
  For example, imagine you have the `author1` object defined in the examples above (Dante Alighieri):
380
330
 
381
331
  ```js
382
332
  author1.getRelation("book")
383
- .then(dantesBooks => {
384
- // Do something with Dante's books
385
- });
333
+ .then(dantesBooks => {
334
+ // Do something with Dante's books
335
+ });
386
336
 
387
337
  // Or..
388
338
  author1.getRelation("book", (book) => book.price < 20)
@@ -390,14 +340,133 @@ author1.getRelation("book", (book) => book.price < 20)
390
340
  // Do something with Dante's books cheaper than 20
391
341
  });
392
342
  ```
343
+ ## Store methods
344
+
345
+ The store has the following method.
346
+
347
+ | Method | Description |
348
+ |------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------|
349
+ | addModel(model) | Introduce a new model to the store. If lazyLoad = false (default), the model is populated with the objects coming from the API. |
350
+ | 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. |
351
+ | find(type,filterFunction) | The promise-oriented method to access objects given a type and a filter function. See [example 1](#example-1). |
352
+ | delete(objects) | It deletes an array of objects. See [example 1](#example-3). |
353
+ | delete(type, filterFunction) | It deleted objects given an array and a filter function. See [example 1](#example-3). |
354
+ | insert(type, object) | It creates a new object of a given type and inserts it in the store. |
355
+
356
+ ## Objects methods
357
+ Each object created is enriched with the following methods.
358
+
359
+
360
+ | Method | Description |
361
+ |------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
362
+ | 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. |
363
+ | 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). |
364
+ | save() | Method to save the object. You can do `store.save()` instead. |
365
+ | destroy() | Method to delete the object. You can do `store.delete()` instead. |
366
+ | 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`). |
367
+ | getRelation(model, filterFunction) | To get all the objects respecting a specific relation with this object (see [model relations](#model-relations)). |
368
+ | toJSON() | It returns a pure JSON representation of the object. |
369
+ | toString() | It returns a string representation of the object. |
370
+ | 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. |
371
+ | 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
372
 
394
- Other ways to declare relations:
373
+ ## Editing objects
374
+ The option `autoSave` can be `true`, `false`, or a number (milliseconds).
395
375
 
396
- * `account.addRelation("user", "userId")`
397
-
398
- 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`.
376
+ * When `autoSave` is set to `false`, the following operations are equivalent:
377
+ ```js
378
+ object.set("name", "Dante");
379
+
380
+ object.name = "Dante";
381
+ ```
382
+ No matter which of the two approaches you use, the command `store.save()` must be invoked to sync the changes with the server.
383
+
384
+ > The command `store.save()` is always able to recognize changed objects that need to be persisted.
399
385
 
400
386
 
401
- * `author.addRelation("book", filterFunction)`
387
+ * **When `autoSave` is set to `true`, the above operations are NOT equivalent.**
388
+
389
+ 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)`.**
390
+
391
+ > 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`.
392
+
393
+ * **When `autoSave` is set to an amount of milliseconds, the above operations are still NOT equivalent, but...**
394
+
395
+ 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.
396
+
397
+
398
+
399
+ ## API interaction
400
+ DataFlux is able to identify three sets of objects: inserted, updated, deleted.
401
+ Each of these set is synced with the server with POST, PUT, and DELETE REST operations, respectively.
402
402
 
403
- 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 based on multiple fields/factors; e.g., a `filterFunction` equals to `(author, book) => author.name == book.authorName && author.surname == book.authorSurname`.
403
+ The interaction with the API is handled automatically, multiple requests are prevented and operations are bundled as much as possible.
404
+
405
+ For example (with autoSave):
406
+ ```js
407
+ store.find('book', (book) => book.price < 20);
408
+ store.find('book', (book) => book.price > 60);
409
+ // The commands above will correspond to 1 single query to the REST API.
410
+
411
+ author1.set("name", "Dante");
412
+ author2.set("name", "Italo");
413
+ author3.set("name", "Umberto");
414
+ author4.name = "Primo";
415
+ // The commands above will correspond to 1 single query to the REST API,
416
+ // no matter how many editing operations.
417
+
418
+
419
+ author1.set("name", "Dante");
420
+ setTimeout(() => author2.set("name", "Italo"), 10000); // To "emulate" a user interaction.
421
+ // The commands above will correspond to 2 queries to the REST API
422
+
423
+ const author1 = {surname: "Alighieri"};
424
+ store.insert(author1);
425
+ author1.set("name", "Dante");
426
+ store.delete(author1);
427
+ // The commands above will not produce any query to the REST API since
428
+ // the initial and final states of the store are the same (object created and removed).
429
+
430
+ ```
431
+
432
+ ### REST API format
433
+
434
+ 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.
435
+
436
+ The following format is automatically accepted, and it will create two objects.
437
+
438
+ ```json
439
+ [
440
+ {
441
+ "name": "Dante",
442
+ "surname": "Alighieri",
443
+ "reviews": [...]
444
+ },
445
+ {
446
+ "name": "Giovanni",
447
+ "surname": "Boccaccio",
448
+ "reviews": [...]
449
+ }
450
+ ]
451
+ ```
452
+
453
+ The following format is automatically accepted, and it will create one object.
454
+
455
+ ```json
456
+ {
457
+ "username": "Massimo",
458
+ "website": "https://massimocandela.com",
459
+ "otherParameters": {
460
+ ...
461
+ }
462
+ }
463
+ ```
464
+
465
+ 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.
466
+
467
+ ```json
468
+ {
469
+ "books": [],
470
+ "authors": []
471
+ }
472
+ ```