hmpo-model 3.2.1 → 4.0.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.
@@ -0,0 +1,21 @@
1
+ # This config is equivalent to both the '.circleci/extended/orb-free.yml' and the base '.circleci/config.yml'
2
+ version: 2.1
3
+
4
+ # Orbs are reusable packages of CircleCI configuration that you may share across projects, enabling you to create encapsulated, parameterized commands, jobs, and executors that can be used across multiple projects.
5
+ # See: https://circleci.com/docs/2.0/orb-intro/
6
+ orbs:
7
+ node: circleci/node@4.7
8
+
9
+ # Invoke jobs via workflows
10
+ # See: https://circleci.com/docs/2.0/configuration-reference/#workflows
11
+ workflows:
12
+ sample: # This is the name of the workflow, feel free to change it to better match your workflow.
13
+ # Inside the workflow, you define the jobs you want to run.
14
+ jobs:
15
+ - node/test:
16
+ # This is the node version to use for the `cimg/node` tag
17
+ # Relevant tags can be found on the CircleCI Developer Hub
18
+ # https://circleci.com/developer/images/image/cimg/node
19
+ version: '16.10'
20
+ # If you are using yarn, change the line below from "npm" to "yarn"
21
+ pkg-manager: npm
package/README.md CHANGED
@@ -2,88 +2,134 @@
2
2
  * localModel - Simple model for data persistance
3
3
  * remoteModel - Simple model for interacting with http/rest apis.
4
4
 
5
+ ## Upgrading
6
+
7
+ The deprecated `request` library has been replaced with `got`. The API is very similar, and some args are translated, like auth, and proxy.
8
+ The new `got` library doesn't automativally use the proxy environment variables so you would need to use something like `global-agent` in your
9
+ app if you need to specify proxies by environment arguments.
10
+
11
+ The `request` method no longer takes a body. This should be inserted as `json`, `body`, or `form` into the `requestConfig` method.
12
+
5
13
  ## Local Model Usage
6
- ### get
14
+ ### `get(name)`
7
15
  * gets a model property via a key
8
16
 
9
- ### set
17
+ ### `set(name, value)` or `set({ name: value })`
10
18
  * sets a property on the model to a value and dispatches events
11
19
 
12
- ### unset
20
+ ### `unset(name)`
13
21
  * unsets a property
14
22
 
15
- ### reset
23
+ ### `reset([options])`
16
24
  * resets a model
17
25
  * suppresses `change` event notifications if `options.silent` is set
18
26
 
19
- ### increment
27
+ ### `increment(name)`
20
28
  * Increments a property
21
29
 
22
- ### toJSON
30
+ ### `toJSON()`
23
31
  * returns a JSON representation of the data in the model
24
32
 
25
33
  ## Remote Model Usage
26
34
 
27
35
  Normally this would be used as an abstract class and extended with your own implementation.
28
36
 
29
- Implementations would normally define at least a `url` method to define the target of API calls.
30
-
31
- There are three methods for API interaction corresponding to GET, POST, and DELETE http methods:
32
-
33
- ### `fetch`
37
+ Implementations would normally define at least a `url():url` method to define the target of API calls.
34
38
 
39
+ Example implimentation:
35
40
  ```javascript
36
- var model = new Model();
37
- model.fetch(function (err, data, responseTime) {
41
+ class MyModel extends HmpoModel {
42
+ url() {
43
+ return super.url('https://my.example.com/url')
44
+ }
45
+
46
+ auth() {
47
+ return super.auth('username:password');
48
+ }
49
+
50
+ requestConfig(config) {
51
+ config.proxy = 'http://proxy.example.com:3128'
52
+ return super.requestConfig(config);
53
+ }
54
+
55
+ // add data to JSON post body
56
+ prepare(callback) {
57
+ super.prepare((err, data) => {
58
+ if (err) return callback(err);
59
+ data.foo = 'bar';
60
+ callback(null, data);
61
+ });
62
+ }
63
+
64
+ // transform returned data
65
+ parse(data) {
66
+ data.additionalItem = true;
67
+ return super.parse(data);
68
+ }
69
+ }
70
+
71
+ const model = new MyModel();
72
+ model.set('boo', 'baz');
73
+ model.save((err, data, responseTime) => {
74
+ if (err) return console.error(err);
38
75
  console.log(data);
39
76
  });
40
77
  ```
41
78
 
42
- ### `save`
79
+ There are three methods for API interaction corresponding to GET, POST, and DELETE http methods:
80
+
81
+ ### `fetch([args, ][callback])`
82
+
83
+ `fetch` performs a `GET` request on the url
43
84
 
44
85
  ```javascript
45
- var model = new Model();
46
- model.set({
47
- property: 'properties are sent as JSON request body by default'
48
- });
49
- model.save(function (err, data, responseTime) {
86
+ const model = new Model();
87
+ model.fetch((err, data, responseTime) => {
50
88
  console.log(data);
51
89
  });
52
90
  ```
91
+ #### Request
92
+ - Request args for the `got` library, can be set by overriding the `requestConfig({}):{}` method.
93
+
94
+ - The `url` can be configured either by setting a default in the model options or `requestConfig()` data, or by overriding the `url(default, args):url` method.
95
+
96
+ - `proxy`, `timeout`, and basic `auth` can be set in the same way, using model options, setting in `requestConfig()`, or by overriding a method.
97
+ - Specifying a `proxy` will set up a proxy tunneling `agent` for the request.
98
+ - Specifying a numeric `timeout` will set the same timeout for all `got` timeout values.
99
+ - Basic `auth` can be a colon separated string, or a `{username, password}` or `{user, pass}` object.
100
+
101
+ #### Response
102
+ - The returned body will be expected to be in JSON format.
103
+ - If `statusCode < 400` the JSON response will be set to the model.
104
+ This behaviour can be changed by overriding the `parse(data):data` method.
105
+ - If `statusCode >= 400` the data will be passed to the `parseError(statusCode, data):error` method, and the `fetch` callback will be called with the returned error.
106
+ - If response statuses need to be treated differently than the above, the `parseResponse(statusCode, data, cb)` method can be overridden.
107
+ - If the response body is not going to be JSON, the `handleResponse(response, cb)` method can be overridden.
108
+
109
+ ### `save([args, ][callback])`
53
110
 
54
- The method can also be overwritten by passing options
111
+ `save` performs a `POST` request on the url
55
112
 
56
113
  ```javascript
57
- var model = new Model();
114
+ const model = new Model();
58
115
  model.set({
59
- property: 'this will be sent as a PUT request'
116
+ property: 'properties are sent as JSON request body by default'
60
117
  });
61
- model.save({ method: 'PUT' }, function (err, data, responseTime) {
118
+ model.save((err, data, responseTime) => {
62
119
  console.log(data);
63
120
  });
64
121
  ```
65
122
 
66
- ### `delete`
123
+ - By default the post body will be a JSON encoded object containing all attributes set to the model using, extracted using `model.toJSON()`. This behaviour can be changed by overriding the `prepare(callback(err, data))` method.
124
+ - The response and body will be treated the same way as the `fetch` request above.
67
125
 
68
- ```javascript
69
- var model = new Model();
70
- model.delete(function (err, data) {
71
- console.log(data);
72
- });
73
- ```
126
+ ### `delete([args, ][callback])`
74
127
 
75
- If no `url` method is defined then the model will use the options parameter and [Node's url.format method](https://nodejs.org/api/url.html#url_url_format_urlobj) to construct a URL.
128
+ `delete` performs a `DELETE` request on the url
76
129
 
77
130
  ```javascript
78
- var model = new Model();
79
-
80
- // make a GET request to http://example.com:3000/foo/bar
81
- model.fetch({
82
- protocol: 'http',
83
- hostname: 'example.com',
84
- port: 3000,
85
- path: '/foo/bar'
86
- }, function (err, data, responseTime) {
131
+ const model = new Model();
132
+ model.delete((err, data, responseTime) => {
87
133
  console.log(data);
88
134
  });
89
135
  ```
@@ -1,6 +1,5 @@
1
1
  'use strict';
2
2
 
3
- const _ = require('underscore');
4
3
  const EventEmitter = require('events').EventEmitter;
5
4
 
6
5
  class LocalModel extends EventEmitter {
@@ -9,8 +8,8 @@ class LocalModel extends EventEmitter {
9
8
  super();
10
9
 
11
10
  this.options = options || {};
12
- this.attributes = {};
13
- this.set(attributes, {silent: true});
11
+ this.attributes = Object.create(null);
12
+ if (attributes) this.set(attributes, {silent: true});
14
13
  }
15
14
 
16
15
  get(key) {
@@ -19,32 +18,35 @@ class LocalModel extends EventEmitter {
19
18
 
20
19
  set(key, value, options) {
21
20
 
22
- let attrs = {};
23
-
24
- if (typeof key === 'string') {
21
+ let attrs = Object.create(null);
22
+ if (key instanceof Map) {
23
+ Object.assign(attrs, Object.fromEntries(key));
24
+ options = value;
25
+ } else if (typeof key === 'string') {
25
26
  attrs[key] = value;
26
27
  } else {
27
- attrs = key;
28
+ Object.assign(attrs, key);
28
29
  options = value;
29
30
  }
30
- options = options || {};
31
31
 
32
- let old = this.toJSON(),
33
- changed = {};
32
+ // silent set
33
+ if (options && options.silent) {
34
+ Object.assign(this.attributes, attrs);
35
+ return this;
36
+ }
34
37
 
35
- _.each(attrs, (value, key) => {
36
- if (value !== old[key]) {
37
- changed[key] = value;
38
- }
39
- });
38
+ const changes = [];
39
+ for (let key in attrs) {
40
+ const value = attrs[key];
41
+ const old = this.attributes[key];
42
+ if (value !== old) changes.push([key, value, old]);
43
+ }
40
44
 
41
- _.extend(this.attributes, attrs);
45
+ Object.assign(this.attributes, attrs);
42
46
 
43
- if (!options.silent && !_.isEmpty(changed)) {
44
- _.each(changed, (value, key) => {
45
- this.emit('change:' + key, this.get(key), old[key]);
46
- });
47
- this.emit('change', changed);
47
+ if (changes.length) {
48
+ changes.forEach(([key, value, old]) => this.emit('change:' + key, value, old));
49
+ if (this.listenerCount('change')) this.emit('change', Object.fromEntries(changes));
48
50
  }
49
51
 
50
52
  return this;
@@ -52,38 +54,32 @@ class LocalModel extends EventEmitter {
52
54
 
53
55
 
54
56
  unset(fields, options) {
55
- options = options || {};
56
57
  if (typeof fields === 'string') {
57
58
  fields = [fields];
58
59
  }
59
- let old = this.toJSON(),
60
- changed = {};
61
60
 
62
- _.each(fields, (key) => {
63
- if (old[key] !== undefined) {
64
- changed[key] = undefined;
61
+ const changes = [];
62
+ for (let key of fields) {
63
+ const old = this.attributes[key];
64
+ if (old !== undefined) {
65
+ changes.push([key, undefined, old]);
65
66
  delete this.attributes[key];
66
67
  }
67
- });
68
+ }
68
69
 
69
- if (!options.silent && !_.isEmpty(changed)) {
70
- _.each(changed, (value, key) => {
71
- this.emit('change:' + key, undefined, old[key]);
72
- });
73
- this.emit('change', changed);
70
+ if ((!options || !options.silent) && changes.length) {
71
+ changes.forEach(([key, value, old]) => this.emit('change:' + key, value, old));
72
+ if (this.listenerCount('change')) this.emit('change', Object.fromEntries(changes));
74
73
  }
75
74
 
76
75
  return this;
77
76
  }
78
77
 
79
78
  reset(options) {
80
- options = options || {};
81
- let keys = Object.keys(this.attributes);
82
- this.attributes = {};
83
- if (!options.silent) {
84
- _.each(keys, (key) => {
85
- this.emit('change:' + key, undefined);
86
- });
79
+ const old = this.attributes;
80
+ this.attributes = Object.create(null);
81
+ if (!options || !options.silent) {
82
+ Object.keys(old).forEach(key => this.emit('change:' + key, undefined, old[key]));
87
83
  this.emit('reset');
88
84
  }
89
85
  }
@@ -97,8 +93,8 @@ class LocalModel extends EventEmitter {
97
93
  this.set(property, val + amount);
98
94
  }
99
95
 
100
- toJSON() {
101
- return _.clone(this.attributes);
96
+ toJSON(bare = false) {
97
+ return Object.assign(bare ? Object.create(null) : {}, this.attributes);
102
98
  }
103
99
  }
104
100
 
@@ -0,0 +1,51 @@
1
+
2
+ class ModelError extends Error {
3
+ constructor(e) {
4
+ super(e);
5
+ Object.defineProperties(this, {
6
+ original: {
7
+ value: e,
8
+ enumerable: false,
9
+ writable: false,
10
+ configurable: true,
11
+ },
12
+ name: {
13
+ value: e.name,
14
+ enumerable: false,
15
+ writable: true,
16
+ configurable: true,
17
+ },
18
+ code: {
19
+ value: e.code,
20
+ enumerable: false,
21
+ writable: true,
22
+ configurable: true,
23
+ },
24
+ errno: {
25
+ value: e.errno,
26
+ enumerable: false,
27
+ writable: true,
28
+ configurable: true,
29
+ },
30
+ info: {
31
+ get: () => this.original.info,
32
+ enumerable: false,
33
+ configurable: true
34
+ },
35
+ stack: {
36
+ get: () => this.original.stack,
37
+ enumerable: false,
38
+ configurable: true
39
+ },
40
+ });
41
+ this.code = e.code;
42
+
43
+ if (this.code === 'ETIMEDOUT') {
44
+ this.message = 'Connection timed out';
45
+ this.status = 504;
46
+ }
47
+ this.status = this.status || (e.response && e.response.statusCode) || 503;
48
+ }
49
+ }
50
+
51
+ module.exports = ModelError;