dataflux 1.1.2 → 1.2.3

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
@@ -48,7 +48,7 @@ export default store;
48
48
 
49
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
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)
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
52
 
53
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
54
  **All the objects are indexed in the store.**
@@ -219,39 +219,78 @@ A model can be simply created with:
219
219
  const book = new Model("book", `https://rest.example.net/api/v1/books`);
220
220
  ```
221
221
 
222
- However, in many cases more complex APIs require different settings for the various operations.
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.
223
+
224
+ ```js
225
+ const book = new Model("book", options);
226
+ ```
227
+
228
+ All the possible options for a model creation are (they are all optional):
229
+
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 |
223
240
 
224
- Instead of an url, you can pass options to perform more elaborated Model's initializations.
241
+
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.
244
+
245
+ #### Operation object
246
+
247
+ An operation object is an object like follows:
248
+
249
+ ```json
250
+ {
251
+ "method": "get",
252
+ "url": "https://api.example.com",
253
+ "headers": {"Authorization": "bearer XXXX"}
254
+ }
255
+ ```
256
+
257
+ Usage example:
225
258
 
226
259
  ```js
227
260
  const options = {
261
+
228
262
  retrieve: {
229
263
  method: "get",
230
- 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
231
266
  },
267
+
232
268
  insert: {
233
269
  method: "post",
234
270
  url: "https://rest.example.net/api/v1/books"
235
271
  },
272
+
236
273
  update: {
237
274
  method: "put",
238
275
  url: "https://rest.example.net/api/v1/books"
239
276
  },
277
+
240
278
  delete: {
241
279
  method: "delete",
242
280
  url: "https://rest.example.net/api/v1/books"
243
- }
281
+ },
282
+
283
+ headers: {"Authorization": "bearer XXXX"} // Globally defined headers
244
284
  };
245
285
 
246
286
  const book = new Model("book", options);
247
287
  ```
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.
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.
249
289
 
250
- For example, if you want to perform both inserts and updates with `PUT`, you can do:
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:
251
291
  ```js
252
292
  const options = {
253
293
  retrieve: {
254
- method: "get",
255
294
  url: "https://rest.example.net/api/v1/books"
256
295
  },
257
296
  insert: {
@@ -262,7 +301,9 @@ const options = {
262
301
  const book = new Model("book", options);
263
302
  ```
264
303
 
265
- Or, even more flexible, you can pass functions and handle yourself the operations. The functions MUST return promises.
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.
266
307
 
267
308
 
268
309
  ```js
@@ -276,7 +317,7 @@ const options = {
276
317
  insert: (data) => {
277
318
  // 1) recieve the data from the store
278
319
  // 2) transform the data however you like
279
- // 3) send data to server
320
+ // 3) send data to server and resolve empty
280
321
  return Promise.resolve();
281
322
  }
282
323
  };
@@ -284,16 +325,49 @@ const options = {
284
325
  const book = new Model("book", options);
285
326
  ```
286
327
 
287
- All the possible options for a model creation are:
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
+
288
345
 
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
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
+ ```
297
371
 
298
372
  ### Model relations
299
373
 
package/dist/Model.js CHANGED
@@ -49,6 +49,18 @@ function _classExtractFieldDescriptor(receiver, privateMap, action) { if (!priva
49
49
 
50
50
  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; } }
51
51
 
52
+ var applyData = function applyData(obj, data) {
53
+ for (var att in data) {
54
+ if (att !== "id" || obj.id === undefined || att === "id" && obj.id === data.id) {
55
+ obj[att] = data[att];
56
+ } else {
57
+ return Promise.reject("The loading function cannot change the id of the object.");
58
+ }
59
+ }
60
+
61
+ return Promise.resolve(obj);
62
+ };
63
+
52
64
  var _type = /*#__PURE__*/new WeakMap();
53
65
 
54
66
  var _store = /*#__PURE__*/new WeakMap();
@@ -69,6 +81,8 @@ var _batchSize = /*#__PURE__*/new WeakMap();
69
81
 
70
82
  var _axios = /*#__PURE__*/new WeakMap();
71
83
 
84
+ var _loadFunction = /*#__PURE__*/new WeakMap();
85
+
72
86
  var _addRelationByField = /*#__PURE__*/new WeakMap();
73
87
 
74
88
  var _addRelationByFilter = /*#__PURE__*/new WeakMap();
@@ -140,6 +154,11 @@ var Model = /*#__PURE__*/_createClass(function Model(name) {
140
154
  value: void 0
141
155
  });
142
156
 
157
+ _classPrivateFieldInitSpec(this, _loadFunction, {
158
+ writable: true,
159
+ value: void 0
160
+ });
161
+
143
162
  _defineProperty(this, "getStore", function () {
144
163
  return _classPrivateFieldGet(_this, _store);
145
164
  });
@@ -152,6 +171,28 @@ var Model = /*#__PURE__*/_createClass(function Model(name) {
152
171
  }
153
172
  });
154
173
 
174
+ _defineProperty(this, "load", function (obj) {
175
+ 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
+ }
191
+ } else {
192
+ return Promise.reject("You must define a loading function in the model to enable load().");
193
+ }
194
+ });
195
+
155
196
  _defineProperty(this, "addRelation", function (model, param2, param3) {
156
197
  if (model) {
157
198
  _this.getStore().validateModel(model);
@@ -285,10 +326,16 @@ var Model = /*#__PURE__*/_createClass(function Model(name) {
285
326
 
286
327
  _classPrivateFieldSet(this, _axios, options.axios || _axios2["default"]);
287
328
 
329
+ _classPrivateFieldSet(this, _loadFunction, options.load || null);
330
+
288
331
  if (!name || !options) {
289
332
  throw new Error("A Model requires at least a name and a hook");
290
333
  }
291
334
 
335
+ if (_classPrivateFieldGet(this, _loadFunction) && typeof _classPrivateFieldGet(this, _loadFunction) !== "function") {
336
+ throw new Error("The load option must be a function");
337
+ }
338
+
292
339
  var _ref = _typeof(options) === "object" ? (0, _modelHooksUtils.getHooksFromOptions)(options) : (0, _modelHooksUtils.getHooksFromUrl)(options),
293
340
  _ref2 = _slicedToArray(_ref, 4),
294
341
  retrieveHook = _ref2[0],
@@ -108,7 +108,7 @@ var ReactStore = /*#__PURE__*/function (_ObserverStore) {
108
108
  _createClass(ReactStore, [{
109
109
  key: "findAll",
110
110
  value: function findAll(type, stateAttribute, context, filterFunction) {
111
- _classPrivateMethodGet(this, _fixState, _fixState2).call(this, stateAttribute, context);
111
+ _classPrivateMethodGet(this, _fixState, _fixState2).call(this, stateAttribute, context, false);
112
112
 
113
113
  var subKey = this.subscribe(type, function (data) {
114
114
  context.setState(_objectSpread(_objectSpread({}, context.state), {}, _defineProperty({}, stateAttribute, data || [])));
@@ -118,7 +118,7 @@ var ReactStore = /*#__PURE__*/function (_ObserverStore) {
118
118
  }, {
119
119
  key: "findOne",
120
120
  value: function findOne(type, stateAttribute, context, filterFunction) {
121
- _classPrivateMethodGet(this, _fixState, _fixState2).call(this, stateAttribute, context);
121
+ _classPrivateMethodGet(this, _fixState, _fixState2).call(this, stateAttribute, context, true);
122
122
 
123
123
  var subKey = this.subscribe(type, function (data) {
124
124
  context.setState(_objectSpread(_objectSpread({}, context.state), {}, _defineProperty({}, stateAttribute, data && data.length ? data[0] : null)));
@@ -132,8 +132,8 @@ var ReactStore = /*#__PURE__*/function (_ObserverStore) {
132
132
 
133
133
  exports["default"] = ReactStore;
134
134
 
135
- function _fixState2(stateAttribute, context) {
135
+ function _fixState2(stateAttribute, context, one) {
136
136
  if (!context[stateAttribute]) {
137
- context[stateAttribute] = []; // side effect on state
137
+ context[stateAttribute] = one ? null : []; // side effect on state
138
138
  }
139
139
  }
@@ -19,13 +19,50 @@ 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
- var Obj = /*#__PURE__*/_createClass(function Obj(values, model) {
22
+ function _classPrivateFieldInitSpec(obj, privateMap, value) { _checkPrivateRedeclaration(obj, privateMap); privateMap.set(obj, value); }
23
+
24
+ function _checkPrivateRedeclaration(obj, privateCollection) { if (privateCollection.has(obj)) { throw new TypeError("Cannot initialize the same private elements twice on an object"); } }
25
+
26
+ function _classPrivateFieldSet(receiver, privateMap, value) { var descriptor = _classExtractFieldDescriptor(receiver, privateMap, "set"); _classApplyDescriptorSet(receiver, descriptor, value); return value; }
27
+
28
+ function _classApplyDescriptorSet(receiver, descriptor, value) { if (descriptor.set) { descriptor.set.call(receiver, value); } else { if (!descriptor.writable) { throw new TypeError("attempted to set read only private field"); } descriptor.value = value; } }
29
+
30
+ function _classPrivateFieldGet(receiver, privateMap) { var descriptor = _classExtractFieldDescriptor(receiver, privateMap, "get"); return _classApplyDescriptorGet(receiver, descriptor); }
31
+
32
+ function _classExtractFieldDescriptor(receiver, privateMap, action) { if (!privateMap.has(receiver)) { throw new TypeError("attempted to " + action + " private field on non-instance"); } return privateMap.get(receiver); }
33
+
34
+ function _classApplyDescriptorGet(receiver, descriptor) { if (descriptor.get) { return descriptor.get.call(receiver); } return descriptor.value; }
35
+
36
+ var _loaded = /*#__PURE__*/new WeakMap();
37
+
38
+ var Obj = /*#__PURE__*/_createClass(function Obj(values, _model) {
23
39
  var _this = this;
24
40
 
25
41
  _classCallCheck(this, Obj);
26
42
 
43
+ _classPrivateFieldInitSpec(this, _loaded, {
44
+ writable: true,
45
+ value: false
46
+ });
47
+
48
+ _defineProperty(this, "load", function () {
49
+ if (_classPrivateFieldGet(_this, _loaded)) {
50
+ return Promise.resolve(_this);
51
+ } else {
52
+ var model = _this.getModel();
53
+
54
+ return model.load(_this).then(function () {
55
+ _classPrivateFieldSet(_this, _loaded, true);
56
+
57
+ return model.getStore().update([_this]); // Propagate update
58
+ }).then(function () {
59
+ return _this;
60
+ }); // return always this
61
+ }
62
+ });
63
+
27
64
  _defineProperty(this, "getFingerprint", function () {
28
- return (0, _fingerprint["default"])(_this.toJson());
65
+ return (0, _fingerprint["default"])(_this.toJSON());
29
66
  });
30
67
 
31
68
  _defineProperty(this, "get", function (attribute) {
@@ -54,7 +91,7 @@ var Obj = /*#__PURE__*/_createClass(function Obj(values, model) {
54
91
  return _this.getModel().getStore()["delete"]([_this]);
55
92
  });
56
93
 
57
- _defineProperty(this, "toJson", function () {
94
+ _defineProperty(this, "toJSON", function () {
58
95
  var attrs = Object.keys(_this);
59
96
  var out = {};
60
97
 
@@ -70,11 +107,11 @@ var Obj = /*#__PURE__*/_createClass(function Obj(values, model) {
70
107
  });
71
108
 
72
109
  _defineProperty(this, "toString", function () {
73
- return JSON.stringify(_this.toJson());
110
+ return JSON.stringify(_this.toJSON());
74
111
  });
75
112
 
76
113
  this.getModel = function () {
77
- return model;
114
+ return _model;
78
115
  };
79
116
 
80
117
  Object.keys(values).forEach(function (key) {
@@ -5,23 +5,46 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.getHooksFromUrl = exports.getHooksFromOptions = exports.executeHook = void 0;
7
7
 
8
+ var _brembo = _interopRequireDefault(require("brembo"));
9
+
10
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
11
+
8
12
  function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
9
13
 
10
- var getDataStringHook = function getDataStringHook(url) {
11
- var method = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "get";
12
- var data = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
13
- var axios = arguments.length > 3 ? arguments[3] : undefined;
14
- return axios({
15
- url: url,
16
- method: method,
14
+ var getDataStringHook = function getDataStringHook(hook) {
15
+ var data = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
16
+ var axios = arguments.length > 2 ? arguments[2] : undefined;
17
+ var options = {
18
+ url: hook.url,
19
+ method: hook.method || "get",
17
20
  data: data,
18
21
  reponseType: 'json'
19
- }).then(function (data) {
22
+ };
23
+
24
+ if (hook.headers) {
25
+ options.headers = hook.headers;
26
+ }
27
+
28
+ if (hook.fields) {
29
+ setFields(options, hook);
30
+ }
31
+
32
+ return axios(options).then(function (data) {
20
33
  return data.data;
21
34
  });
22
35
  };
23
36
 
24
- var createHookItem = function createHookItem(optionItem, defaultMethod, defaultUrl) {
37
+ var setFields = function setFields(options, hook) {
38
+ options.headers = options.headers || {};
39
+ options.headers['X-Fields'] = hook.fields;
40
+ options.url = _brembo["default"].build(options.url, {
41
+ params: {
42
+ fields: hook.fields.join(",")
43
+ }
44
+ });
45
+ };
46
+
47
+ var createHookItem = function createHookItem(optionItem, defaultMethod, defaultUrl, options) {
25
48
  switch (_typeof(optionItem)) {
26
49
  case "undefined":
27
50
  if (!defaultUrl) {
@@ -44,7 +67,9 @@ var createHookItem = function createHookItem(optionItem, defaultMethod, defaultU
44
67
  case "object":
45
68
  return {
46
69
  method: optionItem.method || defaultMethod,
47
- url: optionItem.url || defaultUrl
70
+ url: optionItem.url || defaultUrl,
71
+ fields: options.fields || [],
72
+ headers: optionItem.headers || options.headers || {}
48
73
  };
49
74
 
50
75
  default:
@@ -54,7 +79,7 @@ var createHookItem = function createHookItem(optionItem, defaultMethod, defaultU
54
79
 
55
80
  var getHooksFromOptions = function getHooksFromOptions(options) {
56
81
  var defaultUrl = typeof options.retrieve === "function" ? null : options.retrieve.url;
57
- return [createHookItem(options.retrieve, "get", defaultUrl), createHookItem(options.insert, "post", defaultUrl), createHookItem(options.update, "put", defaultUrl), createHookItem(options["delete"], "delete", defaultUrl)];
82
+ return [createHookItem(options.retrieve, "get", defaultUrl, options), createHookItem(options.insert, "post", defaultUrl, options), createHookItem(options.update, "put", defaultUrl, options), createHookItem(options["delete"], "delete", defaultUrl, options)];
58
83
  };
59
84
 
60
85
  exports.getHooksFromOptions = getHooksFromOptions;
@@ -82,7 +107,7 @@ var executeHook = function executeHook(type, hook, data, axios) {
82
107
 
83
108
  switch (hookType) {
84
109
  case "object":
85
- return getDataStringHook(hook.url, hook.method, data, axios);
110
+ return getDataStringHook(hook, data, axios);
86
111
 
87
112
  case "function":
88
113
  return hook(data);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dataflux",
3
- "version": "1.1.2",
3
+ "version": "1.2.3",
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",
@@ -78,13 +78,13 @@
78
78
  },
79
79
  "devDependencies": {
80
80
  "@babel/cli": "^7.16.8",
81
- "@babel/core": "^7.16.10",
81
+ "@babel/core": "^7.16.12",
82
82
  "@babel/node": "^7.16.8",
83
83
  "@babel/plugin-proposal-class-properties": "^7.16.7",
84
84
  "@babel/plugin-proposal-object-rest-spread": "^7.16.7",
85
85
  "@babel/plugin-transform-async-to-generator": "^7.16.8",
86
86
  "@babel/plugin-transform-runtime": "^7.16.10",
87
- "@babel/preset-env": "^7.16.10",
87
+ "@babel/preset-env": "^7.16.11",
88
88
  "@babel/preset-react": "^7.16.7",
89
89
  "dotenv-cli": "^4.1.1",
90
90
  "release-it": "^14.12.3"
@@ -92,6 +92,7 @@
92
92
  "dependencies": {
93
93
  "axios": "^0.25.0",
94
94
  "batch-promises": "^0.0.3",
95
+ "brembo": "^2.0.6",
95
96
  "crc-32": "^1.2.0",
96
97
  "uuid": "^8.3.2"
97
98
  },