dataflux 1.6.2 → 1.7.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 +1 -1
- package/README.md +63 -10
- package/dist/Model.js +10 -13
- package/dist/PubSub.js +23 -0
- package/dist/Store.js +57 -22
- package/dist/modelHooksUtils.js +2 -1
- package/package.json +1 -1
- package/CNAME +0 -1
- package/_config.yml +0 -1
package/LICENSE
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2022-present Massimo Candela
|
|
3
|
+
Copyright (c) 2022-present Massimo Candela <https://massimocandela.com>
|
|
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
|
@@ -276,6 +276,7 @@ All the possible options for a model creation are (they are all optional):
|
|
|
276
276
|
| 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. | |
|
|
277
277
|
| 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. |
|
|
278
278
|
| deep | A boolean defining if nested objects should be enriched with the object methods. | true |
|
|
279
|
+
| 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 |
|
|
279
280
|
|
|
280
281
|
### Operations
|
|
281
282
|
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.
|
|
@@ -341,28 +342,80 @@ const book = new Model("book", options);
|
|
|
341
342
|
|
|
342
343
|
#### Operation function
|
|
343
344
|
|
|
344
|
-
To be even more flexible, you can pass functions
|
|
345
|
+
To be even more flexible, you can pass functions to generate the API urls or retrieve the data. An operation function can return a url or a promise. If the function returns a promise, the promise must resolve in an array of JSON objects when these are ready.
|
|
345
346
|
|
|
346
347
|
|
|
348
|
+
Example of operation function returning from the API and submitting to the API an array of JSON objects.
|
|
349
|
+
|
|
347
350
|
```js
|
|
348
351
|
const options = {
|
|
349
352
|
retrieve: () => {
|
|
350
353
|
// 1) get the data from the API
|
|
351
354
|
// 2) tranforms the data
|
|
352
355
|
// 3) return the data to the store
|
|
353
|
-
return
|
|
356
|
+
return axios({ // Example with axios, but you can use whatever you prefer
|
|
357
|
+
url: "https://api.example.net/example",
|
|
358
|
+
method: "get"
|
|
359
|
+
});
|
|
354
360
|
},
|
|
355
361
|
insert: (data) => {
|
|
356
362
|
// 1) recieve the data from the store
|
|
357
363
|
// 2) transform the data however you like
|
|
358
|
-
// 3) send data to server
|
|
359
|
-
return
|
|
364
|
+
// 3) send data to server
|
|
365
|
+
return axios({
|
|
366
|
+
url: "https://api.example.net/example",
|
|
367
|
+
data,
|
|
368
|
+
method: "post"
|
|
369
|
+
});
|
|
360
370
|
}
|
|
361
371
|
};
|
|
362
372
|
|
|
363
373
|
const book = new Model("book", options);
|
|
364
374
|
```
|
|
365
375
|
|
|
376
|
+
#### Object factory
|
|
377
|
+
|
|
378
|
+
In the examples we saw above, objects are retrieved from an API returning one or more objects. However, sometimes object creation requires a more complex logic. This can be summarized as: the object must be created based on some input parameter
|
|
379
|
+
|
|
380
|
+
Typical examples are:
|
|
381
|
+
* There is no API returning all the objects of a given type, you can only access specific objects based on a parameter (e.g., based on the ID).
|
|
382
|
+
* It doesn't make sense to retrieve all the objects of a given type, since the client needs to access only to a subset of them.
|
|
383
|
+
* The APIs to get/post/put/delete objects are parametric (e.g., you need to specify the ID in the url).
|
|
384
|
+
* The model is polymorphic, and the final object's format is based on some parameter;
|
|
385
|
+
* The model is polymorphic, a different API is used to retrieve the objects based on some input parameter (e.g., they are all books, but there are different APIs by genre).
|
|
386
|
+
|
|
387
|
+
This is a well-know problem, described by the [factory design pattern](https://refactoring.guru/design-patterns/factory-method).
|
|
388
|
+
In DataFlux, the store provides for this use case the `.factory()` method that allows you to implement Factory.
|
|
389
|
+
|
|
390
|
+
To create a factory, you must declare a model as follows:
|
|
391
|
+
|
|
392
|
+
```js
|
|
393
|
+
const author = new Model("author", {
|
|
394
|
+
lazyLoad: true, // It MUST be lazyLoaded
|
|
395
|
+
retrieve: ({params}) => { // The retrieve function now takes some parameters
|
|
396
|
+
|
|
397
|
+
// You can return a URL or directly one or more JSON objects
|
|
398
|
+
return `https://api.example.net/authors/${params.id}`;
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
store.addModel(author);
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
It is important to notice in the example above, how `lazyLoad` must be set to `true` and how the retrieve function returns a URL based on an input parameter. As always, the operation function can return a URL (DataFlux will download the objects) or directly a collection of objects.
|
|
406
|
+
|
|
407
|
+
> If you don't specify the insert/update/delete operation functions, the same URL of the retrieve function will be used.
|
|
408
|
+
|
|
409
|
+
Once the parametric retrieve function is declared, you can instantiate the objects with the `store.factory()` method:
|
|
410
|
+
|
|
411
|
+
```js
|
|
412
|
+
store.factory("author", {id: 4});
|
|
413
|
+
```
|
|
414
|
+
Invoking `store.factory()` will create a new object in the "author" collection.
|
|
415
|
+
|
|
416
|
+
> store.factory() will not return the object. It just inserts the object in the collection. You will need to use any of the usual .find/.findOne/.findAll/.subscribe to retrieve it.
|
|
417
|
+
|
|
418
|
+
|
|
366
419
|
#### Object enrichment
|
|
367
420
|
|
|
368
421
|
DataFlux objects can have a `load()` method which enables you to load extra attributes of an object.
|
|
@@ -526,13 +579,13 @@ You can operate on the reviews similarly to how you operate on the main model's
|
|
|
526
579
|
|
|
527
580
|
```js
|
|
528
581
|
store.find("book")
|
|
529
|
-
|
|
530
|
-
|
|
582
|
+
.then(([book]) => {
|
|
583
|
+
const firstReview = book.reviews[0];
|
|
531
584
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
585
|
+
// Examples of what you can do:
|
|
586
|
+
firstReview.detroy(); // The first review is removed from the array book.reviews
|
|
587
|
+
firstReview.set("stars", 5); // Set the stars of the first review to 5
|
|
588
|
+
});
|
|
536
589
|
```
|
|
537
590
|
|
|
538
591
|
|
package/dist/Model.js
CHANGED
|
@@ -63,18 +63,6 @@ function _classExtractFieldDescriptor(receiver, privateMap, action) { if (!priva
|
|
|
63
63
|
|
|
64
64
|
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; } }
|
|
65
65
|
|
|
66
|
-
var applyData = function applyData(obj, data) {
|
|
67
|
-
for (var att in data) {
|
|
68
|
-
if (att !== "id" || obj.id === undefined || att === "id" && obj.id === data.id) {
|
|
69
|
-
obj[att] = data[att];
|
|
70
|
-
} else {
|
|
71
|
-
return Promise.reject("The loading function cannot change the id of the object.");
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return Promise.resolve(obj);
|
|
76
|
-
};
|
|
77
|
-
|
|
78
66
|
var _type = /*#__PURE__*/new WeakMap();
|
|
79
67
|
|
|
80
68
|
var _store = /*#__PURE__*/new WeakMap();
|
|
@@ -294,6 +282,14 @@ var Model = /*#__PURE__*/_createClass(function Model(name) {
|
|
|
294
282
|
return objects.length ? _classPrivateFieldGet(_this, _bulkOperation).call(_this, objects, _classPrivateFieldGet(_this, _deleteObjects)) : Promise.resolve();
|
|
295
283
|
});
|
|
296
284
|
|
|
285
|
+
_defineProperty(this, "factory", function (params) {
|
|
286
|
+
if (!_this.options.lazyLoad) {
|
|
287
|
+
return Promise.reject("Factory can be used only on a model declared with lazyLoad: true");
|
|
288
|
+
} else {
|
|
289
|
+
return (0, _modelHooksUtils.executeHook)("retrieve", _classPrivateFieldGet(_this, _retrieveHook), params, _classPrivateFieldGet(_this, _axios));
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
|
|
297
293
|
_classPrivateFieldInitSpec(this, _addRelationByField, {
|
|
298
294
|
writable: true,
|
|
299
295
|
value: function value(model, localField) {
|
|
@@ -387,7 +383,8 @@ var Model = /*#__PURE__*/_createClass(function Model(name) {
|
|
|
387
383
|
|
|
388
384
|
this.options = _objectSpread(_objectSpread({}, options), {}, {
|
|
389
385
|
deep: (_options$deep = options.deep) !== null && _options$deep !== void 0 ? _options$deep : true,
|
|
390
|
-
parseMoment: (_options$parseMoment = options.parseMoment) !== null && _options$parseMoment !== void 0 ? _options$parseMoment : false
|
|
386
|
+
parseMoment: (_options$parseMoment = options.parseMoment) !== null && _options$parseMoment !== void 0 ? _options$parseMoment : false,
|
|
387
|
+
lazyLoad: options.lazyLoad
|
|
391
388
|
});
|
|
392
389
|
|
|
393
390
|
_classPrivateFieldSet(this, _store, null);
|
package/dist/PubSub.js
CHANGED
|
@@ -19,6 +19,29 @@ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Cons
|
|
|
19
19
|
|
|
20
20
|
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; }
|
|
21
21
|
|
|
22
|
+
/*
|
|
23
|
+
* MIT License
|
|
24
|
+
*
|
|
25
|
+
* Copyright (c) 2022 Massimo Candela <https://massimocandela.com>
|
|
26
|
+
*
|
|
27
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
28
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
29
|
+
* in the Software without restriction, including without limitation the rights
|
|
30
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
31
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
32
|
+
* furnished to do so, subject to the following conditions:
|
|
33
|
+
*
|
|
34
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
35
|
+
* copies or substantial portions of the Software.
|
|
36
|
+
*
|
|
37
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
38
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
39
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
40
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
41
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
42
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
43
|
+
* SOFTWARE.
|
|
44
|
+
*/
|
|
22
45
|
var PubSub = /*#__PURE__*/_createClass(function PubSub() {
|
|
23
46
|
var _this = this;
|
|
24
47
|
|
package/dist/Store.js
CHANGED
|
@@ -120,16 +120,19 @@ var Store = /*#__PURE__*/function () {
|
|
|
120
120
|
var type = model.getType();
|
|
121
121
|
|
|
122
122
|
if (!_this2.models[type]) {
|
|
123
|
+
var _model$options$lazyLo;
|
|
124
|
+
|
|
123
125
|
_this2.models[type] = {
|
|
124
126
|
model: model,
|
|
125
127
|
storedObjects: {}
|
|
126
128
|
};
|
|
127
129
|
model.setStore(_this2);
|
|
130
|
+
var lazyLoad = (_model$options$lazyLo = model.options.lazyLoad) !== null && _model$options$lazyLo !== void 0 ? _model$options$lazyLo : _this2.options.lazyLoad;
|
|
128
131
|
|
|
129
|
-
if (
|
|
130
|
-
resolve(_classPrivateMethodGet(_this2, _loadObjects, _loadObjects2).call(_this2, type));
|
|
131
|
-
} else {
|
|
132
|
+
if (lazyLoad) {
|
|
132
133
|
resolve();
|
|
134
|
+
} else {
|
|
135
|
+
resolve(_classPrivateMethodGet(_this2, _loadObjects, _loadObjects2).call(_this2, type));
|
|
133
136
|
}
|
|
134
137
|
} else {
|
|
135
138
|
var error = "The model already exists";
|
|
@@ -296,6 +299,38 @@ var Store = /*#__PURE__*/function () {
|
|
|
296
299
|
};
|
|
297
300
|
});
|
|
298
301
|
}
|
|
302
|
+
}, {
|
|
303
|
+
key: "factory",
|
|
304
|
+
value: function factory(type, params) {
|
|
305
|
+
var _this8 = this;
|
|
306
|
+
|
|
307
|
+
var item = this.models[type];
|
|
308
|
+
this.pubSub.publish("loading", {
|
|
309
|
+
status: "start",
|
|
310
|
+
model: type
|
|
311
|
+
});
|
|
312
|
+
return item.promise = item.model.factory(params).then(function (items) {
|
|
313
|
+
var _iterator3 = _createForOfIteratorHelper(items),
|
|
314
|
+
_step3;
|
|
315
|
+
|
|
316
|
+
try {
|
|
317
|
+
for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
|
|
318
|
+
var _item = _step3.value;
|
|
319
|
+
|
|
320
|
+
_classPrivateMethodGet(_this8, _insertObject, _insertObject2).call(_this8, type, _item, false);
|
|
321
|
+
}
|
|
322
|
+
} catch (err) {
|
|
323
|
+
_iterator3.e(err);
|
|
324
|
+
} finally {
|
|
325
|
+
_iterator3.f();
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
_this8.pubSub.publish("loading", {
|
|
329
|
+
status: "end",
|
|
330
|
+
model: type
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
}
|
|
299
334
|
}]);
|
|
300
335
|
|
|
301
336
|
return Store;
|
|
@@ -310,25 +345,25 @@ function _error2(error) {
|
|
|
310
345
|
}
|
|
311
346
|
|
|
312
347
|
function _deleteByFilter2(type, filterFunction) {
|
|
313
|
-
var
|
|
348
|
+
var _this9 = this;
|
|
314
349
|
|
|
315
350
|
return _classPrivateMethodGet(this, _getPromise, _getPromise2).call(this, type).then(function () {
|
|
316
|
-
var deleted = Object.values(
|
|
351
|
+
var deleted = Object.values(_this9.models[type].storedObjects).filter(function (i) {
|
|
317
352
|
return filterFunction(i.object);
|
|
318
353
|
});
|
|
319
354
|
|
|
320
|
-
var
|
|
321
|
-
|
|
355
|
+
var _iterator4 = _createForOfIteratorHelper(deleted),
|
|
356
|
+
_step4;
|
|
322
357
|
|
|
323
358
|
try {
|
|
324
|
-
for (
|
|
325
|
-
var object =
|
|
359
|
+
for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
|
|
360
|
+
var object = _step4.value;
|
|
326
361
|
object.status = "deleted";
|
|
327
362
|
}
|
|
328
363
|
} catch (err) {
|
|
329
|
-
|
|
364
|
+
_iterator4.e(err);
|
|
330
365
|
} finally {
|
|
331
|
-
|
|
366
|
+
_iterator4.f();
|
|
332
367
|
}
|
|
333
368
|
|
|
334
369
|
return deleted.map(function (i) {
|
|
@@ -338,7 +373,7 @@ function _deleteByFilter2(type, filterFunction) {
|
|
|
338
373
|
}
|
|
339
374
|
|
|
340
375
|
function _getPromise2(type) {
|
|
341
|
-
var
|
|
376
|
+
var _this10 = this;
|
|
342
377
|
|
|
343
378
|
if (!this.models[type]) {
|
|
344
379
|
return Promise.reject("The model doesn't exist");
|
|
@@ -346,7 +381,7 @@ function _getPromise2(type) {
|
|
|
346
381
|
return Promise.reject("The model is not loaded");
|
|
347
382
|
} else if (!this.models[type].promise && this.options.lazyLoad) {
|
|
348
383
|
return _classPrivateMethodGet(this, _loadObjects, _loadObjects2).call(this, type).then(function () {
|
|
349
|
-
return
|
|
384
|
+
return _this10.models[type].promise;
|
|
350
385
|
});
|
|
351
386
|
} else {
|
|
352
387
|
return this.models[type].promise;
|
|
@@ -373,7 +408,7 @@ function _insertObject2(type, item) {
|
|
|
373
408
|
}
|
|
374
409
|
|
|
375
410
|
function _loadObjects2(type) {
|
|
376
|
-
var
|
|
411
|
+
var _this11 = this;
|
|
377
412
|
|
|
378
413
|
var item = this.models[type];
|
|
379
414
|
this.pubSub.publish("loading", {
|
|
@@ -381,22 +416,22 @@ function _loadObjects2(type) {
|
|
|
381
416
|
model: type
|
|
382
417
|
});
|
|
383
418
|
return item.promise = item.model.retrieveAll().then(function (items) {
|
|
384
|
-
var
|
|
385
|
-
|
|
419
|
+
var _iterator5 = _createForOfIteratorHelper(items),
|
|
420
|
+
_step5;
|
|
386
421
|
|
|
387
422
|
try {
|
|
388
|
-
for (
|
|
389
|
-
var
|
|
423
|
+
for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
|
|
424
|
+
var _item2 = _step5.value;
|
|
390
425
|
|
|
391
|
-
_classPrivateMethodGet(
|
|
426
|
+
_classPrivateMethodGet(_this11, _insertObject, _insertObject2).call(_this11, type, _item2, false);
|
|
392
427
|
}
|
|
393
428
|
} catch (err) {
|
|
394
|
-
|
|
429
|
+
_iterator5.e(err);
|
|
395
430
|
} finally {
|
|
396
|
-
|
|
431
|
+
_iterator5.f();
|
|
397
432
|
}
|
|
398
433
|
|
|
399
|
-
|
|
434
|
+
_this11.pubSub.publish("loading", {
|
|
400
435
|
status: "end",
|
|
401
436
|
model: type
|
|
402
437
|
});
|
package/dist/modelHooksUtils.js
CHANGED
|
@@ -114,7 +114,8 @@ var executeHook = function executeHook(type, hook, data, axios) {
|
|
|
114
114
|
return getDataStringHook(hook, data, axios);
|
|
115
115
|
|
|
116
116
|
case "function":
|
|
117
|
-
|
|
117
|
+
var res = hook(data);
|
|
118
|
+
return typeof res === "string" ? getDataStringHook(res, data, axios) : res;
|
|
118
119
|
|
|
119
120
|
default:
|
|
120
121
|
return Promise.reject("The ".concat(type, " hook must be a URL or a function returning a promise"));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dataflux",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "DataFlux, automatically interfaces with your REST APIs to create a 2-way-synced local data store. Transparently manages data propagation in the React state.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": "dist/index.js",
|
package/CNAME
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
dataflux.js.org
|
package/_config.yml
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
theme: jekyll-theme-cayman
|