sorla-db 1.0.0 → 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.
@@ -1,144 +1,208 @@
1
- /**
2
- * @class Cursor
3
- * @description Cursor class is used to represent the cursor in the document, adding some extra capabilities to object.
4
- */
5
-
6
- const documents = require('../documents/document.js');
7
-
8
- class cursor {
9
- /**
10
- * @constructor
11
- * @param array data
12
- * @description Create a new cursor instance
13
- */
14
- constructor(data, projection) {
15
- this._data = typeof data === 'undefined' ? [] : data;
16
- this.currIndex = 0;
17
- if(typeof projection === 'object')
18
- this.projection(projection);
19
- }
20
-
21
- /**
22
- * @method hasNext
23
- * @description Check if the cursor has more elements
24
- * @return boolean
25
- */
26
- hasNext() {
27
- return this.currIndex < this._data.length;
28
- }
29
-
30
- /**
31
- * @method next
32
- * @description Get the next element in the cursor
33
- * @return mixed
34
- */
35
- next() {
36
- if(!this.hasNext())
37
- throw new Error('No more item to the cursor');
38
-
39
- return this._data[this.currIndex++];
40
- }
41
-
42
- /**
43
- * @method toArray
44
- * @description Convert the cursor to an array
45
- * @return array
46
- */
47
- toArray() {
48
- return structuredClone(this._data);
49
- }
50
-
51
- /**
52
- * @methor reset
53
- * @description Reset the cursor to the first element
54
- * @return void
55
- */
56
- reset() {
57
- this.currIndex = 0;
58
- }
59
-
60
- /**
61
- * @method first
62
- * @description Get the first element in the cursor
63
- * @return mixed
64
- */
65
- first() {
66
- return this._data[0]._data;
67
- }
68
-
69
- /**
70
- * @method last
71
- * @description Get the last element in the cursor
72
- * @return mixed
73
- */
74
- last() {
75
- return this._data[this._data.length - 1];
76
- }
77
-
78
- /**
79
- * @method sort
80
- * @description Sort the cursor using collection
81
- * @param json sortQuery
82
- * @return void
83
- */
84
- sort(sortQuery) {
85
- const queryDoc = new documents(sortQuery);
86
- this._data.sort((a, b) => {
87
- for(const key of Object.keys(sortQuery))
88
- {
89
- const ascending = queryDoc[key] === 1;
90
- if(a[key] > b[key])
91
- return ascending ? 1 : -1;
92
-
93
- if(a[key] < b[key])
94
- return ascending ? -1 : 1;
95
- }
96
-
97
- return 0;
98
- });
99
- }
100
-
101
- /**
102
- * @get length
103
- * @description Get the number of elements in the cursor
104
- * @return number
105
- */
106
- get length() {
107
- return this._data.length;
108
- }
109
-
110
- /**
111
- * Projection of the cursor
112
- * @param json projection
113
- * @return cursor
114
- */
115
- projection(projection) {
116
- if(projection === null || typeof projection !== 'object')
117
- return;
118
-
119
- const self = this;
120
- this._data.forEach(doc => {
121
- for(const attribute of Object.keys(projection))
122
- {
123
- if(projection[attribute] === 0) {
124
- doc.deleteProp(attribute, self);
125
- }
126
- }
127
- });
128
- }
129
-
130
- /**
131
- * @method push
132
- * @description Push an element to the cursor
133
- * @param object element
134
- * @return void
135
- */
136
- push(element) {
137
- if(typeof element !== 'object')
138
- throw new Error('The element must be an object');
139
-
140
- this._data.push(element);
141
- }
142
- }
143
-
1
+ /**
2
+ * @class Cursor
3
+ * @description Cursor class is used to represent the cursor in the document, adding some extra capabilities to object.
4
+ */
5
+
6
+
7
+ class cursor {
8
+ /**
9
+ * @constructor
10
+ * @param array data
11
+ * @description Create a new cursor instance
12
+ */
13
+ constructor(data, projection) {
14
+ this._data = typeof data === 'undefined' ? [] : data;
15
+ this.currIndex = 0;
16
+ if(typeof projection === 'object')
17
+ this.projection(projection);
18
+ }
19
+
20
+ /**
21
+ * @method hasNext
22
+ * @description Check if the cursor has more elements
23
+ * @return boolean
24
+ */
25
+ hasNext() {
26
+ return this.currIndex < this._data.length;
27
+ }
28
+
29
+ /**
30
+ * @method next
31
+ * @description Get the next element in the cursor
32
+ * @return mixed
33
+ */
34
+ next() {
35
+ if(!this.hasNext())
36
+ throw new Error('No more item to the cursor');
37
+
38
+ return this._data[this.currIndex++];
39
+ }
40
+
41
+ /**
42
+ * @method toArray
43
+ * @description Convert the cursor to an array
44
+ * @return array
45
+ */
46
+ toArray() {
47
+ // the documents are proxies, which structuredClone cannot copy: clone their data
48
+ return this._data.map(doc => structuredClone(doc._data));
49
+ }
50
+
51
+ /**
52
+ * @methor reset
53
+ * @description Reset the cursor to the first element
54
+ * @return void
55
+ */
56
+ reset() {
57
+ this.currIndex = 0;
58
+ }
59
+
60
+ /**
61
+ * @method first
62
+ * @description Get the first element in the cursor
63
+ * @return mixed
64
+ */
65
+ first() {
66
+ return this._data.length ? this._data[0]._data : null;
67
+ }
68
+
69
+ /**
70
+ * @method last
71
+ * @description Get the last element in the cursor
72
+ * @return mixed
73
+ */
74
+ last() {
75
+ return this._data.length ? this._data[this._data.length - 1]._data : null;
76
+ }
77
+
78
+ /**
79
+ * @method sort
80
+ * @description Sort the documents of the cursor, like MongoDB: {age: -1, name: 1}
81
+ * Values of different types follow MongoDB's order: missing and null, numbers, strings,
82
+ * objects, arrays, booleans, dates. Strings compare by code point unless a collation is given.
83
+ * @param json sortQuery field => 1 (ascending) or -1 (descending), "a.b" paths allowed
84
+ * @param json options {collation: {locale, sensitivity, numeric...}} (Intl.Collator options)
85
+ * @return cursor
86
+ */
87
+ sort(sortQuery, options = {}) {
88
+ if(sortQuery === null || typeof sortQuery !== 'object' || Array.isArray(sortQuery))
89
+ throw new Error('The sort query must be an object like {field: 1}');
90
+
91
+ const keys = Object.keys(sortQuery);
92
+ for(const key of keys)
93
+ {
94
+ if(sortQuery[key] !== 1 && sortQuery[key] !== -1)
95
+ throw new Error(`Sort direction of "${key}" must be 1 or -1`);
96
+ }
97
+
98
+ const collation = options.collation
99
+ ? new Intl.Collator(options.collation.locale, options.collation)
100
+ : null;
101
+
102
+ this._data.sort((a, b) => {
103
+ for(const key of keys)
104
+ {
105
+ const result = compareValues(a[key], b[key], collation);
106
+ if(result !== 0)
107
+ return result * sortQuery[key];
108
+ }
109
+
110
+ return 0;
111
+ });
112
+
113
+ return this;
114
+ }
115
+
116
+ /**
117
+ * @get length
118
+ * @description Get the number of elements in the cursor
119
+ * @return number
120
+ */
121
+ get length() {
122
+ return this._data.length;
123
+ }
124
+
125
+ /**
126
+ * Projection of the cursor
127
+ * @param json projection
128
+ * @return cursor
129
+ */
130
+ projection(projection) {
131
+ if(projection === null || typeof projection !== 'object')
132
+ return;
133
+
134
+ const self = this;
135
+ this._data.forEach(doc => {
136
+ for(const attribute of Object.keys(projection))
137
+ {
138
+ if(projection[attribute] === 0) {
139
+ doc.deleteProp(attribute, self);
140
+ }
141
+ }
142
+ });
143
+ }
144
+
145
+ /**
146
+ * @method push
147
+ * @description Push an element to the cursor
148
+ * @param object element
149
+ * @return void
150
+ */
151
+ push(element) {
152
+ if(typeof element !== 'object')
153
+ throw new Error('The element must be an object');
154
+
155
+ this._data.push(element);
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Rank of a value in MongoDB's comparison order
161
+ * @param mixed value
162
+ * @return number
163
+ */
164
+ function typeRank(value) {
165
+ if(value === undefined || value === null) return 0;
166
+ if(typeof value === 'number') return 1;
167
+ if(typeof value === 'string') return 2;
168
+ // toString instead of instanceof: Dates from another realm (iframes, workers, vm) still count
169
+ if(Object.prototype.toString.call(value) === '[object Date]') return 6;
170
+ if(Array.isArray(value)) return 4;
171
+ if(typeof value === 'object') return 3;
172
+ if(typeof value === 'boolean') return 5;
173
+ return 7;
174
+ }
175
+
176
+ /**
177
+ * Compares two field values for sorting
178
+ * @param mixed a
179
+ * @param mixed b
180
+ * @param Intl.Collator|null collation
181
+ * @return number -1, 0 or 1
182
+ */
183
+ function compareValues(a, b, collation) {
184
+ const rankA = typeRank(a);
185
+ const rankB = typeRank(b);
186
+ if(rankA !== rankB) return rankA < rankB ? -1 : 1;
187
+
188
+ switch(rankA) {
189
+ case 0:
190
+ return 0;
191
+ case 2:
192
+ if(collation) return Math.sign(collation.compare(a, b));
193
+ break;
194
+ case 3:
195
+ case 4:
196
+ a = JSON.stringify(a);
197
+ b = JSON.stringify(b);
198
+ break;
199
+ case 6:
200
+ a = a.getTime();
201
+ b = b.getTime();
202
+ break;
203
+ }
204
+
205
+ return a < b ? -1 : a > b ? 1 : 0;
206
+ }
207
+
144
208
  module.exports = cursor;
@@ -1,88 +1,84 @@
1
- const utils = require('../utils');
2
- const cursor = require('./cursor');
3
- const manipulation = require('../query_language/manipulation');
4
-
5
- class documents
6
- {
7
- constructor(object)
8
- {
9
- this._data = object;
10
- this._utils = new utils();
11
- this._cloned = false;
12
- this._deleteReceiver = null;
13
-
14
- return new Proxy(this, {
15
- deleteProperty: (target, prop, receiver) => {
16
- if(target._cloned && target._deleteReceiver instanceof cursor)
17
- {
18
- if(prop in target._data)
19
- {
20
- console.log('2', prop);
21
- delete target._data[prop];
22
- return true;
23
- }
24
-
25
- return false;
26
- }
27
- },
28
-
29
- get: (target, prop) => {
30
- if(prop.indexOf('.') > -1)
31
- return target._utils.getDotData(target._data, prop);
32
-
33
- if(prop in target._data)
34
- return target._data[prop];
35
-
36
- return target[prop];
37
- },
38
-
39
- set: (target, prop, value, receiver) => {
40
- if(!(prop in target._data) && !target.hasOwnProperty(prop))
41
- return false;
42
-
43
- if(target.hasOwnProperty(prop) && ['_data', '_utils'].indexOf(prop) === -1){
44
- target[prop] = value;
45
- return true;
46
- }
47
-
48
- if(receiver instanceof manipulation || target._cloned && receiver instanceof cursor) {
49
- target._data[prop] = value;
50
- return true;
51
- }
52
-
53
- return false;
54
- }
55
- });
56
- }
57
-
58
- /**
59
- * @method clone
60
- * @description Clone the document
61
- * @return Document
62
- */
63
- clone()
64
- {
65
- const cloned = new documents(structuredClone(this._data));
66
- cloned._cloned = true;
67
- return cloned;
68
- }
69
-
70
- /**
71
- * @method delete prop
72
- * @description Delete a property from the document
73
- * @param string prop
74
- * @param cursor receiver
75
- * @return boolean
76
- */
77
- deleteProp(prop, receiver)
78
- {
79
- this._deleteReceiver = receiver;
80
- if(this._cloned)
81
- return Reflect.deleteProperty(this, prop);
82
-
83
- return false;
84
- }
85
- }
86
-
87
-
1
+ const utils = require('../utils');
2
+
3
+ /**
4
+ * A stored document. Fields read like a plain object, including "a.b" paths. Stored documents are
5
+ * read-only from the outside; find() returns copies (clone()) whose fields projection can remove.
6
+ * This module requires no other document or query module, which keeps the require graph acyclic.
7
+ */
8
+ class documents
9
+ {
10
+ constructor(object)
11
+ {
12
+ this._data = object;
13
+ this._utils = new utils();
14
+ this._cloned = false;
15
+
16
+ return new Proxy(this, {
17
+ deleteProperty: (target, prop) => {
18
+ if(target._cloned && prop in target._data)
19
+ {
20
+ delete target._data[prop];
21
+ return true;
22
+ }
23
+
24
+ return false;
25
+ },
26
+
27
+ get: (target, prop) => {
28
+ // symbols (Symbol.toPrimitive, Symbol.iterator...) belong to the wrapper, not to the data
29
+ if(typeof prop !== 'string')
30
+ return target[prop];
31
+
32
+ if(prop.indexOf('.') > -1)
33
+ return target._utils.getDotData(target._data, prop);
34
+
35
+ if(prop in target._data)
36
+ return target._data[prop];
37
+
38
+ return target[prop];
39
+ },
40
+
41
+ set: (target, prop, value) => {
42
+ if(prop === '_cloned')
43
+ {
44
+ target._cloned = value;
45
+ return true;
46
+ }
47
+
48
+ if(typeof prop === 'string' && prop in target._data)
49
+ throw new Error(`Document fields are read-only ("${prop}"), change documents through the collection`);
50
+
51
+ return false;
52
+ }
53
+ });
54
+ }
55
+
56
+ /**
57
+ * @method clone
58
+ * @description Clone the document
59
+ * @return Document
60
+ */
61
+ clone()
62
+ {
63
+ const cloned = new documents(structuredClone(this._data));
64
+ cloned._cloned = true;
65
+ return cloned;
66
+ }
67
+
68
+ /**
69
+ * @method delete prop
70
+ * @description Delete a property from a copy returned by find() (used by projection)
71
+ * @param string prop
72
+ * @return boolean
73
+ */
74
+ deleteProp(prop)
75
+ {
76
+ if(!this._cloned)
77
+ return false;
78
+
79
+ return Reflect.deleteProperty(this, prop);
80
+ }
81
+ }
82
+
83
+
88
84
  module.exports = documents;