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.
- package/LICENSE +21 -21
- package/README.md +274 -31
- package/dist/sorla.min.js +1 -1
- package/index.d.ts +89 -0
- package/index.mjs +5 -0
- package/package.json +66 -42
- package/src/collections/collection.js +46 -46
- package/src/dbs/database.js +71 -71
- package/src/documents/cursor.js +207 -143
- package/src/documents/document.js +83 -87
- package/src/query_language/manipulation.js +197 -160
- package/src/query_language/operators.js +242 -288
- package/src/query_language/query.js +73 -103
- package/src/sorla.js +122 -122
- package/src/utils.js +41 -40
- package/Brainstorming.md +0 -22
- package/debug-not.js +0 -24
- package/example/simple_data_example.html +0 -196
- package/index.js +0 -62
- package/src/helper.js +0 -41
- package/test/feature_collection.test.js +0 -73
- package/test/feature_collection_queries.test.js +0 -269
- package/test/unit_database.test.js +0 -36
- package/test/unit_document_test.js +0 -39
- package/test/unit_sorla.test.js +0 -42
- package/test/unit_test.test.js +0 -25
- package/webpack.config.js +0 -11
package/src/documents/cursor.js
CHANGED
|
@@ -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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
* @
|
|
11
|
-
* @
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
this.
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
* @
|
|
23
|
-
* @
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
* @
|
|
32
|
-
* @
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
* @
|
|
44
|
-
* @
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
return structuredClone(
|
|
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
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
this.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
target
|
|
45
|
-
return true;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
* @
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
* @
|
|
72
|
-
* @
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
this
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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;
|