dataflux 1.1.2 → 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 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
@@ -69,6 +69,8 @@ var _batchSize = /*#__PURE__*/new WeakMap();
69
69
 
70
70
  var _axios = /*#__PURE__*/new WeakMap();
71
71
 
72
+ var _loadFunction = /*#__PURE__*/new WeakMap();
73
+
72
74
  var _addRelationByField = /*#__PURE__*/new WeakMap();
73
75
 
74
76
  var _addRelationByFilter = /*#__PURE__*/new WeakMap();
@@ -140,6 +142,11 @@ var Model = /*#__PURE__*/_createClass(function Model(name) {
140
142
  value: void 0
141
143
  });
142
144
 
145
+ _classPrivateFieldInitSpec(this, _loadFunction, {
146
+ writable: true,
147
+ value: void 0
148
+ });
149
+
143
150
  _defineProperty(this, "getStore", function () {
144
151
  return _classPrivateFieldGet(_this, _store);
145
152
  });
@@ -152,6 +159,38 @@ var Model = /*#__PURE__*/_createClass(function Model(name) {
152
159
  }
153
160
  });
154
161
 
162
+ _defineProperty(this, "load", function (obj) {
163
+ return new Promise(function (resolve, reject) {
164
+ var applyData = function applyData(data) {
165
+ for (var att in data) {
166
+ if (att !== "id" || obj.id === undefined || att === "id" && obj.id === data.id) {
167
+ obj[att] = data[att];
168
+ } else {
169
+ return Promise.reject("The loading function cannot change the id of the object.");
170
+ }
171
+ }
172
+ };
173
+
174
+ if (_classPrivateFieldGet(_this, _loadFunction)) {
175
+ var res = _classPrivateFieldGet(_this, _loadFunction).call(_this, obj.toJson());
176
+
177
+ if (typeof res === "string") {
178
+ _classPrivateFieldGet(_this, _axios).call(_this, {
179
+ method: "get",
180
+ url: res,
181
+ responseType: "json"
182
+ }).then(function (data) {
183
+ return applyData(data.data);
184
+ }).then(resolve);
185
+ } else {
186
+ res.then(applyData).then(resolve);
187
+ }
188
+ } else {
189
+ reject("You must define a loading function in the model to enable load().");
190
+ }
191
+ });
192
+ });
193
+
155
194
  _defineProperty(this, "addRelation", function (model, param2, param3) {
156
195
  if (model) {
157
196
  _this.getStore().validateModel(model);
@@ -285,10 +324,16 @@ var Model = /*#__PURE__*/_createClass(function Model(name) {
285
324
 
286
325
  _classPrivateFieldSet(this, _axios, options.axios || _axios2["default"]);
287
326
 
327
+ _classPrivateFieldSet(this, _loadFunction, options.load || null);
328
+
288
329
  if (!name || !options) {
289
330
  throw new Error("A Model requires at least a name and a hook");
290
331
  }
291
332
 
333
+ if (_classPrivateFieldGet(this, _loadFunction) && typeof _classPrivateFieldGet(this, _loadFunction) !== "function") {
334
+ throw new Error("The load option must be a function");
335
+ }
336
+
292
337
  var _ref = _typeof(options) === "object" ? (0, _modelHooksUtils.getHooksFromOptions)(options) : (0, _modelHooksUtils.getHooksFromUrl)(options),
293
338
  _ref2 = _slicedToArray(_ref, 4),
294
339
  retrieveHook = _ref2[0],
@@ -19,11 +19,44 @@ 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
+ 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
+
22
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
+ return _this.getModel().load(_this).then(function () {
53
+ _classPrivateFieldSet(_this, _loaded, true);
54
+
55
+ return _this;
56
+ });
57
+ }
58
+ });
59
+
27
60
  _defineProperty(this, "getFingerprint", function () {
28
61
  return (0, _fingerprint["default"])(_this.toJson());
29
62
  });
@@ -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.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",
@@ -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
  },