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.
- package/.circleci/config.yml +21 -0
- package/README.md +86 -40
- package/lib/local-model.js +38 -42
- package/lib/model-error.js +51 -0
- package/lib/remote-model.js +162 -97
- package/package.json +9 -11
- package/test/lib/spec.local-model.js +36 -18
- package/test/lib/spec.remote-model.js +377 -105
- package/.travis.yml +0 -8
|
@@ -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
|
-
|
|
37
|
-
|
|
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
|
-
|
|
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
|
-
|
|
46
|
-
model.
|
|
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
|
-
|
|
111
|
+
`save` performs a `POST` request on the url
|
|
55
112
|
|
|
56
113
|
```javascript
|
|
57
|
-
|
|
114
|
+
const model = new Model();
|
|
58
115
|
model.set({
|
|
59
|
-
property: '
|
|
116
|
+
property: 'properties are sent as JSON request body by default'
|
|
60
117
|
});
|
|
61
|
-
model.save(
|
|
118
|
+
model.save((err, data, responseTime) => {
|
|
62
119
|
console.log(data);
|
|
63
120
|
});
|
|
64
121
|
```
|
|
65
122
|
|
|
66
|
-
|
|
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
|
-
|
|
69
|
-
var model = new Model();
|
|
70
|
-
model.delete(function (err, data) {
|
|
71
|
-
console.log(data);
|
|
72
|
-
});
|
|
73
|
-
```
|
|
126
|
+
### `delete([args, ][callback])`
|
|
74
127
|
|
|
75
|
-
|
|
128
|
+
`delete` performs a `DELETE` request on the url
|
|
76
129
|
|
|
77
130
|
```javascript
|
|
78
|
-
|
|
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
|
```
|
package/lib/local-model.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
28
|
+
Object.assign(attrs, key);
|
|
28
29
|
options = value;
|
|
29
30
|
}
|
|
30
|
-
options = options || {};
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
// silent set
|
|
33
|
+
if (options && options.silent) {
|
|
34
|
+
Object.assign(this.attributes, attrs);
|
|
35
|
+
return this;
|
|
36
|
+
}
|
|
34
37
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
45
|
+
Object.assign(this.attributes, attrs);
|
|
42
46
|
|
|
43
|
-
if (
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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 &&
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
|
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;
|