model-redis 0.1.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 +21 -0
- package/README.md +6 -0
- package/index.js +0 -0
- package/package.json +24 -0
- package/src/object_validate.js +90 -0
- package/src/redis_model.js +202 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 William Mantly
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Model Redis
|
|
2
|
+
|
|
3
|
+
Simple ORM model for redis in NodsJS. The only external dependence is `redis`.
|
|
4
|
+
This provides a simple ORM interface, with schema, for redis. This is not meant
|
|
5
|
+
for large data sets and is geared more for small, internal infrastructure based
|
|
6
|
+
projects that do not require complex data model.
|
package/index.js
ADDED
|
File without changes
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "model-redis",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Simple ORM model for redis in NodsJS",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/wmantly/model-redis.git"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"nodeJS",
|
|
15
|
+
"redis",
|
|
16
|
+
"orm"
|
|
17
|
+
],
|
|
18
|
+
"author": "William Mantly",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/wmantly/model-redis/issues"
|
|
22
|
+
},
|
|
23
|
+
"homepage": "https://github.com/wmantly/model-redis#readme"
|
|
24
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const process_type = {
|
|
4
|
+
number: function(key, value){
|
|
5
|
+
if(key.min && value < key.min) return `is to small, min ${key.min}.`
|
|
6
|
+
if(key.max && value > key.max) return `is to large, max ${key.max}.`
|
|
7
|
+
},
|
|
8
|
+
string: function(key, value){
|
|
9
|
+
if(key.min && value.length < key.min) return `is too short, min ${key.min}.`
|
|
10
|
+
if(key.max && value.length > key.max) return `is too short, max ${key.max}.`
|
|
11
|
+
},
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function returnOrCall(value){
|
|
15
|
+
return typeof(value) === 'function' ? value() : value;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function processKeys(map, data, partial){
|
|
19
|
+
let errors = [];
|
|
20
|
+
let out = {};
|
|
21
|
+
|
|
22
|
+
for(let key of Object.keys(map)){
|
|
23
|
+
|
|
24
|
+
if(!map[key].always && partial && !data.hasOwnProperty(key)) continue;
|
|
25
|
+
|
|
26
|
+
if(!partial && map[key].isRequired && !data.hasOwnProperty(key)){
|
|
27
|
+
errors.push({key, message:`${key} is required.`});
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if(data.hasOwnProperty(key) && map[key].type && typeof(data[key]) !== map[key].type){
|
|
32
|
+
errors.push({key, message:`${key} is not ${map[key].type} type.`});
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
out[key] = data.hasOwnProperty(key) && data[key] !== undefined ? data[key] : returnOrCall(map[key].default);
|
|
37
|
+
|
|
38
|
+
if(data.hasOwnProperty(key) && process_type[map[key].type]){
|
|
39
|
+
let typeError = process_type[map[key].type](map[key], data[key]);
|
|
40
|
+
if(typeError){
|
|
41
|
+
errors.push({key, message:`${key} ${typeError}`});
|
|
42
|
+
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if(errors.length !== 0){
|
|
49
|
+
throw new ObjectValidateError(errors);
|
|
50
|
+
return {__errors__: errors};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function parseFromString(map, data){
|
|
57
|
+
let types = {
|
|
58
|
+
boolean: function(value){ return value === 'false' ? false : true },
|
|
59
|
+
number: Number,
|
|
60
|
+
string: String,
|
|
61
|
+
object: JSON.parse
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
for(let key of Object.keys(data)){
|
|
65
|
+
if(map[key] && map[key].type){
|
|
66
|
+
data[key] = types[map[key].type](data[key]);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return data;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function parseToString(data){
|
|
74
|
+
let types = {
|
|
75
|
+
object: JSON.stringify
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return (types[typeof(data)] || String)(data);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function ObjectValidateError(message) {
|
|
82
|
+
this.name = 'ObjectValidateError';
|
|
83
|
+
this.message = (message || {});
|
|
84
|
+
this.status = 422;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
ObjectValidateError.prototype = Error.prototype;
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
module.exports = {processKeys, parseFromString, ObjectValidateError, parseToString};
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const objValidate = require('./object_validate');
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
function setUpTable(client){
|
|
7
|
+
|
|
8
|
+
class Table{
|
|
9
|
+
static _indexed = [];
|
|
10
|
+
|
|
11
|
+
constructor(data){
|
|
12
|
+
for(let key in data){
|
|
13
|
+
this[key] = data[key];
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
static async get(index){
|
|
18
|
+
try{
|
|
19
|
+
|
|
20
|
+
if(typeof index === 'object'){
|
|
21
|
+
index = index[this._key]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let result = await client.HGETALL(`${this.prototype.constructor.name}_${index}`);
|
|
25
|
+
|
|
26
|
+
if(Object.keys(result).length === 0){
|
|
27
|
+
let error = new Error('EntryNotFound');
|
|
28
|
+
error.name = 'EntryNotFound';
|
|
29
|
+
error.message = `${this.prototype.constructor.name}:${index} does not exists`;
|
|
30
|
+
error.status = 404;
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Redis always returns strings, use the keyMap schema to turn them
|
|
35
|
+
// back to native values.
|
|
36
|
+
result = objValidate.parseFromString(this._keyMap, result);
|
|
37
|
+
|
|
38
|
+
return new this.prototype.constructor(result)
|
|
39
|
+
|
|
40
|
+
}catch(error){
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
static async exists(data){
|
|
47
|
+
try{
|
|
48
|
+
await this.get(data);
|
|
49
|
+
|
|
50
|
+
return true
|
|
51
|
+
}catch(error){
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
static async list(index_key, value){
|
|
57
|
+
// return a list of all the index keys for this table.
|
|
58
|
+
try{
|
|
59
|
+
|
|
60
|
+
if(index_key && !this._indexed.includes(index_key)) return [];
|
|
61
|
+
|
|
62
|
+
if(index_key && this._indexed.includes(index_key)){
|
|
63
|
+
return await client.SMEMBERS(`${this.prototype.constructor.name}_${index_key}_${value}`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return await client.SMEMBERS(this.prototype.constructor.name);
|
|
67
|
+
|
|
68
|
+
}catch(error){
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
static async listDetail(index_key, value){
|
|
74
|
+
// Return a list of the entries as instances.
|
|
75
|
+
let out = [];
|
|
76
|
+
|
|
77
|
+
for(let entry of await this.list(index_key, value)){
|
|
78
|
+
out.push(await this.get(entry));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return out;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
static async add(data){
|
|
85
|
+
// Add a entry to this redis table.
|
|
86
|
+
try{
|
|
87
|
+
// Validate the passed data by the keyMap schema.
|
|
88
|
+
|
|
89
|
+
data = objValidate.processKeys(this._keyMap, data);
|
|
90
|
+
|
|
91
|
+
// Do not allow the caller to overwrite an existing index key,
|
|
92
|
+
if(data[this._key] && await this.exists(data)){
|
|
93
|
+
let error = new Error('EntryNameUsed');
|
|
94
|
+
error.name = 'EntryNameUsed';
|
|
95
|
+
error.message = `${this.prototype.constructor.name}:${data[this._key]} already exists`;
|
|
96
|
+
error.status = 409;
|
|
97
|
+
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Add the key to the members for this redis table
|
|
102
|
+
await client.SADD(this.prototype.constructor.name, String(data[this._key]));
|
|
103
|
+
|
|
104
|
+
// Create index keys lists
|
|
105
|
+
for(let index of this._indexed){
|
|
106
|
+
if(data[index]) await client.SADD(
|
|
107
|
+
`${this.prototype.constructor.name}_${index}_${data[index]}`,
|
|
108
|
+
String(data[this._key]
|
|
109
|
+
));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Add the values for this entry.
|
|
113
|
+
for(let key of Object.keys(data)){
|
|
114
|
+
await client.HSET(`${this.prototype.constructor.name}_${data[this._key]}`, key, objValidate.parseToString(data[key]));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// return the created redis entry as entry instance.
|
|
118
|
+
return await this.get(data[this._key]);
|
|
119
|
+
} catch(error){
|
|
120
|
+
throw error;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async update(data, key){
|
|
125
|
+
// Update an existing entry.
|
|
126
|
+
try{
|
|
127
|
+
// Check to see if entry name changed.
|
|
128
|
+
if(data[this.constructor._key] && data[this.constructor._key] !== this[this.constructor._key]){
|
|
129
|
+
|
|
130
|
+
// Merge the current data into with the updated data
|
|
131
|
+
let newData = Object.assign({}, this, data);
|
|
132
|
+
|
|
133
|
+
// Remove the updated failed so it doesnt keep it
|
|
134
|
+
delete newData.updated;
|
|
135
|
+
|
|
136
|
+
// Create a new record for the updated entry. If that succeeds,
|
|
137
|
+
// delete the old recored
|
|
138
|
+
if(await this.add(newData)) await this.remove();
|
|
139
|
+
|
|
140
|
+
}else{
|
|
141
|
+
// Update what ever fields that where passed.
|
|
142
|
+
|
|
143
|
+
// Validate the passed data, ignoring required fields.
|
|
144
|
+
data = objValidate.processKeys(this.constructor._keyMap, data, true);
|
|
145
|
+
|
|
146
|
+
// Update the index keys
|
|
147
|
+
for(let index of this.constructor._indexed){
|
|
148
|
+
if(data[index]){
|
|
149
|
+
await client.SREM(
|
|
150
|
+
`${this.constructor.name}_${index}_${this[index]}`,
|
|
151
|
+
String(this[this.constructor._key])
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
await client.SADD(
|
|
155
|
+
`${this.constructor.name}_${index}_${data[index]}`,
|
|
156
|
+
String(data[this.constructor._key] || this[this.constructor._key])
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
}
|
|
161
|
+
// Loop over the data fields and apply them to redis
|
|
162
|
+
for(let key of Object.keys(data)){
|
|
163
|
+
this[key] = data[key];
|
|
164
|
+
await client.HSET(`${this.constructor.name}_${this[this.constructor._key]}`, key, data[key]);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return this;
|
|
169
|
+
|
|
170
|
+
} catch(error){
|
|
171
|
+
// Pass any error to the calling function
|
|
172
|
+
throw error;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async remove(data){
|
|
177
|
+
// Remove an entry from this table.
|
|
178
|
+
|
|
179
|
+
try{
|
|
180
|
+
// Remove the index key from the tables members list.
|
|
181
|
+
|
|
182
|
+
await client.SREM(this.constructor.name, this[this.constructor._key]);
|
|
183
|
+
|
|
184
|
+
for(let index of this.constructor._indexed){
|
|
185
|
+
await client.SREM(`${this.constructor.name}_${index}_${data[value]}`, data[this.constructor._key]);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Remove the entries hash values.
|
|
189
|
+
let count = await client.DEL(`${this.constructor.name}_${this[this.constructor._key]}`);
|
|
190
|
+
|
|
191
|
+
// Return the number of removed values to the caller.
|
|
192
|
+
return count;
|
|
193
|
+
|
|
194
|
+
} catch(error) {
|
|
195
|
+
throw error;
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
module.exports = setUpTable;
|