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 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;