sorla-db 1.1.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/README.md +67 -9
- package/dist/sorla.min.js +1 -1
- package/index.d.ts +89 -0
- package/index.mjs +5 -0
- package/package.json +27 -3
- package/src/documents/cursor.js +80 -16
- package/src/documents/document.js +26 -30
- package/src/query_language/manipulation.js +84 -47
- package/src/utils.js +9 -8
- 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 -75
- package/test/feature_collection_queries.test.js +0 -224
- package/test/feature_cursor.test.js +0 -84
- package/test/feature_database.test.js +0 -62
- package/test/feature_insert.test.js +0 -116
- package/test/feature_nested_queries.test.js +0 -94
- package/test/feature_query_equivalence.test.js +0 -165
- package/test/unit_database.test.js +0 -36
- package/test/unit_document.test.js +0 -39
- package/test/unit_operators.test.js +0 -78
- package/test/unit_sorla.test.js +0 -42
- package/test/unit_test.test.js +0 -25
- package/webpack.config.js +0 -11
package/index.d.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// Type declarations for sorla-db
|
|
2
|
+
|
|
3
|
+
export type Document = Record<string, unknown> & { _id?: unknown };
|
|
4
|
+
|
|
5
|
+
export type ComparisonOperators = {
|
|
6
|
+
$eq?: unknown;
|
|
7
|
+
$ne?: unknown;
|
|
8
|
+
$gt?: unknown;
|
|
9
|
+
$gte?: unknown;
|
|
10
|
+
$lt?: unknown;
|
|
11
|
+
$lte?: unknown;
|
|
12
|
+
$in?: unknown[];
|
|
13
|
+
$nin?: unknown[];
|
|
14
|
+
$not?: ComparisonOperators;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/** A query: field conditions (value or operators, "a.b" paths allowed) and nested $and / $or / $nor. */
|
|
18
|
+
export type Query = {
|
|
19
|
+
$and?: Query[];
|
|
20
|
+
$or?: Query[];
|
|
21
|
+
$nor?: Query[];
|
|
22
|
+
[field: string]: unknown;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export type Projection = Record<string, 0>;
|
|
26
|
+
export type SortSpec = Record<string, 1 | -1>;
|
|
27
|
+
|
|
28
|
+
export interface SortOptions {
|
|
29
|
+
/** Intl.Collator options plus the locale, e.g. { locale: 'pt-BR', sensitivity: 'base', numeric: true } */
|
|
30
|
+
collation?: Intl.CollatorOptions & { locale?: string };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface StoredDocument {
|
|
34
|
+
readonly _data: Document;
|
|
35
|
+
readonly [field: string]: unknown;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export declare class Cursor {
|
|
39
|
+
readonly length: number;
|
|
40
|
+
currIndex: number;
|
|
41
|
+
hasNext(): boolean;
|
|
42
|
+
next(): StoredDocument;
|
|
43
|
+
reset(): void;
|
|
44
|
+
/** Data of the first document, null when empty. */
|
|
45
|
+
first(): Document | null;
|
|
46
|
+
/** Data of the last document, null when empty. */
|
|
47
|
+
last(): Document | null;
|
|
48
|
+
/** Plain copies of the documents. */
|
|
49
|
+
toArray(): Document[];
|
|
50
|
+
sort(spec: SortSpec, options?: SortOptions): this;
|
|
51
|
+
projection(projection: Projection): void;
|
|
52
|
+
push(element: object): void;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export declare class Collection {
|
|
56
|
+
readonly first: Document;
|
|
57
|
+
readonly last: Document;
|
|
58
|
+
readonly all: StoredDocument[];
|
|
59
|
+
count(): number;
|
|
60
|
+
/** Inserts a copy of the document and returns its _id. */
|
|
61
|
+
insertOne(document: Document, callback?: (id: unknown) => void): unknown;
|
|
62
|
+
/** Validates every document first, then inserts them and returns their ids. */
|
|
63
|
+
insertMany(documents: Document[], callback?: (ids: unknown[]) => void): unknown[];
|
|
64
|
+
find(query: Query, projection?: Projection | null): Cursor;
|
|
65
|
+
/** Data of the first matching document, null when nothing matches. */
|
|
66
|
+
findOne(query: Query, projection?: Projection | null): Document | null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export declare class Database {
|
|
70
|
+
readonly name: string;
|
|
71
|
+
createCollection(name: string, validationSchema?: unknown): void;
|
|
72
|
+
countCollections(): number;
|
|
73
|
+
[collection: string]: unknown;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
declare class sorla {
|
|
77
|
+
constructor();
|
|
78
|
+
/** The selected database. Collections are properties of it: sorla.db.people */
|
|
79
|
+
readonly db: Database & Record<string, Collection>;
|
|
80
|
+
dbExists(name: string): boolean;
|
|
81
|
+
/** Position of the new database, or false when the name exists. */
|
|
82
|
+
createDb(name: string): number | false;
|
|
83
|
+
/** Selects a database, creating it when needed. */
|
|
84
|
+
useDb(name: string): void;
|
|
85
|
+
countDb(): number;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export { sorla };
|
|
89
|
+
export default sorla;
|
package/index.mjs
ADDED
package/package.json
CHANGED
|
@@ -1,11 +1,32 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sorla-db",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "A simple and temporary database focused to use in browser environment that will bring capabilities for other tools that will be launched as soon as possible.",
|
|
5
|
-
"main": "
|
|
5
|
+
"main": "src/sorla.js",
|
|
6
|
+
"module": "index.mjs",
|
|
7
|
+
"types": "index.d.ts",
|
|
8
|
+
"unpkg": "dist/sorla.min.js",
|
|
9
|
+
"jsdelivr": "dist/sorla.min.js",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./index.d.ts",
|
|
13
|
+
"import": "./index.mjs",
|
|
14
|
+
"require": "./src/sorla.js"
|
|
15
|
+
},
|
|
16
|
+
"./dist/sorla.min.js": "./dist/sorla.min.js",
|
|
17
|
+
"./src/*": "./src/*",
|
|
18
|
+
"./package.json": "./package.json"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"src",
|
|
22
|
+
"!src/helper.js",
|
|
23
|
+
"dist",
|
|
24
|
+
"index.mjs",
|
|
25
|
+
"index.d.ts"
|
|
26
|
+
],
|
|
6
27
|
"scripts": {
|
|
28
|
+
"build": "node index.js",
|
|
7
29
|
"mini": "node index.js",
|
|
8
|
-
"build": "webpack",
|
|
9
30
|
"test": "jest"
|
|
10
31
|
},
|
|
11
32
|
"repository": {
|
|
@@ -38,5 +59,8 @@
|
|
|
38
59
|
"jsdom-global": "^3.0.2",
|
|
39
60
|
"mocha": "^10.2.0",
|
|
40
61
|
"terser": "^5.27.0"
|
|
62
|
+
},
|
|
63
|
+
"engines": {
|
|
64
|
+
"node": ">=18"
|
|
41
65
|
}
|
|
42
66
|
}
|
package/src/documents/cursor.js
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
* @description Cursor class is used to represent the cursor in the document, adding some extra capabilities to object.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
const documents = require('../documents/document.js');
|
|
7
6
|
|
|
8
7
|
class cursor {
|
|
9
8
|
/**
|
|
@@ -45,7 +44,8 @@ class cursor {
|
|
|
45
44
|
* @return array
|
|
46
45
|
*/
|
|
47
46
|
toArray() {
|
|
48
|
-
|
|
47
|
+
// the documents are proxies, which structuredClone cannot copy: clone their data
|
|
48
|
+
return this._data.map(doc => structuredClone(doc._data));
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
/**
|
|
@@ -63,7 +63,7 @@ class cursor {
|
|
|
63
63
|
* @return mixed
|
|
64
64
|
*/
|
|
65
65
|
first() {
|
|
66
|
-
return this._data[0]._data;
|
|
66
|
+
return this._data.length ? this._data[0]._data : null;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
/**
|
|
@@ -72,30 +72,45 @@ class cursor {
|
|
|
72
72
|
* @return mixed
|
|
73
73
|
*/
|
|
74
74
|
last() {
|
|
75
|
-
return this._data[this._data.length - 1];
|
|
75
|
+
return this._data.length ? this._data[this._data.length - 1]._data : null;
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
/**
|
|
79
79
|
* @method sort
|
|
80
|
-
* @description Sort the cursor
|
|
81
|
-
*
|
|
82
|
-
*
|
|
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
|
|
83
86
|
*/
|
|
84
|
-
sort(sortQuery) {
|
|
85
|
-
|
|
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
|
+
|
|
86
102
|
this._data.sort((a, b) => {
|
|
87
|
-
for(const key of
|
|
103
|
+
for(const key of keys)
|
|
88
104
|
{
|
|
89
|
-
const
|
|
90
|
-
if(
|
|
91
|
-
return
|
|
92
|
-
|
|
93
|
-
if(a[key] < b[key])
|
|
94
|
-
return ascending ? -1 : 1;
|
|
105
|
+
const result = compareValues(a[key], b[key], collation);
|
|
106
|
+
if(result !== 0)
|
|
107
|
+
return result * sortQuery[key];
|
|
95
108
|
}
|
|
96
109
|
|
|
97
110
|
return 0;
|
|
98
111
|
});
|
|
112
|
+
|
|
113
|
+
return this;
|
|
99
114
|
}
|
|
100
115
|
|
|
101
116
|
/**
|
|
@@ -141,4 +156,53 @@ class cursor {
|
|
|
141
156
|
}
|
|
142
157
|
}
|
|
143
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,7 +1,10 @@
|
|
|
1
1
|
const utils = require('../utils');
|
|
2
|
-
const cursor = require('./cursor');
|
|
3
|
-
const manipulation = require('../query_language/manipulation');
|
|
4
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
|
+
*/
|
|
5
8
|
class documents
|
|
6
9
|
{
|
|
7
10
|
constructor(object)
|
|
@@ -9,24 +12,23 @@ class documents
|
|
|
9
12
|
this._data = object;
|
|
10
13
|
this._utils = new utils();
|
|
11
14
|
this._cloned = false;
|
|
12
|
-
this._deleteReceiver = null;
|
|
13
15
|
|
|
14
16
|
return new Proxy(this, {
|
|
15
|
-
deleteProperty: (target, prop
|
|
16
|
-
if(target._cloned && target.
|
|
17
|
+
deleteProperty: (target, prop) => {
|
|
18
|
+
if(target._cloned && prop in target._data)
|
|
17
19
|
{
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
console.log('2', prop);
|
|
21
|
-
delete target._data[prop];
|
|
22
|
-
return true;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
return false;
|
|
20
|
+
delete target._data[prop];
|
|
21
|
+
return true;
|
|
26
22
|
}
|
|
23
|
+
|
|
24
|
+
return false;
|
|
27
25
|
},
|
|
28
26
|
|
|
29
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
|
+
|
|
30
32
|
if(prop.indexOf('.') > -1)
|
|
31
33
|
return target._utils.getDotData(target._data, prop);
|
|
32
34
|
|
|
@@ -36,19 +38,15 @@ class documents
|
|
|
36
38
|
return target[prop];
|
|
37
39
|
},
|
|
38
40
|
|
|
39
|
-
set: (target, prop, value
|
|
40
|
-
if(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if(target.hasOwnProperty(prop) && ['_data', '_utils'].indexOf(prop) === -1){
|
|
44
|
-
target[prop] = value;
|
|
41
|
+
set: (target, prop, value) => {
|
|
42
|
+
if(prop === '_cloned')
|
|
43
|
+
{
|
|
44
|
+
target._cloned = value;
|
|
45
45
|
return true;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
if(
|
|
49
|
-
|
|
50
|
-
return true;
|
|
51
|
-
}
|
|
48
|
+
if(typeof prop === 'string' && prop in target._data)
|
|
49
|
+
throw new Error(`Document fields are read-only ("${prop}"), change documents through the collection`);
|
|
52
50
|
|
|
53
51
|
return false;
|
|
54
52
|
}
|
|
@@ -69,18 +67,16 @@ class documents
|
|
|
69
67
|
|
|
70
68
|
/**
|
|
71
69
|
* @method delete prop
|
|
72
|
-
* @description Delete a property from
|
|
70
|
+
* @description Delete a property from a copy returned by find() (used by projection)
|
|
73
71
|
* @param string prop
|
|
74
|
-
* @param cursor receiver
|
|
75
72
|
* @return boolean
|
|
76
73
|
*/
|
|
77
|
-
deleteProp(prop
|
|
74
|
+
deleteProp(prop)
|
|
78
75
|
{
|
|
79
|
-
this.
|
|
80
|
-
|
|
81
|
-
return Reflect.deleteProperty(this, prop);
|
|
76
|
+
if(!this._cloned)
|
|
77
|
+
return false;
|
|
82
78
|
|
|
83
|
-
return
|
|
79
|
+
return Reflect.deleteProperty(this, prop);
|
|
84
80
|
}
|
|
85
81
|
}
|
|
86
82
|
|
|
@@ -52,106 +52,143 @@ class manipulation {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
/**
|
|
55
|
-
*
|
|
56
|
-
* @param
|
|
55
|
+
* Whether a value is a document: an object that is not null nor an array
|
|
56
|
+
* @param mixed value
|
|
57
57
|
* @return boolean
|
|
58
58
|
*/
|
|
59
|
-
|
|
59
|
+
_isDocument(value)
|
|
60
60
|
{
|
|
61
|
-
|
|
62
|
-
return typeof doc !== 'object' ? ++acc : acc;
|
|
63
|
-
}, 0);
|
|
64
|
-
|
|
65
|
-
return count;
|
|
61
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
66
62
|
}
|
|
67
63
|
|
|
68
64
|
/**
|
|
69
|
-
* Verify
|
|
65
|
+
* Verify that no key, at any depth, has prohibited characters
|
|
70
66
|
* @param object document
|
|
71
67
|
* @return boolean
|
|
72
68
|
*/
|
|
73
69
|
_validateKeys(document)
|
|
74
70
|
{
|
|
75
|
-
const keys = Object.keys(document);
|
|
76
71
|
const operators = new operatorsCheck();
|
|
77
|
-
for(
|
|
72
|
+
for(const key of Object.keys(document))
|
|
78
73
|
{
|
|
79
|
-
const hasProhibitedCharacters = this._prohibitedCharacters.
|
|
80
|
-
|
|
81
|
-
}, 0);
|
|
82
|
-
|
|
83
|
-
if(hasProhibitedCharacters > 0 && !operators.isOperator(key))
|
|
74
|
+
const hasProhibitedCharacters = this._prohibitedCharacters.some(char => key.indexOf(char) > -1);
|
|
75
|
+
if(hasProhibitedCharacters && !operators.isOperator(key))
|
|
84
76
|
return false;
|
|
85
77
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if(Array.isArray(document[key]))
|
|
78
|
+
const value = document[key];
|
|
79
|
+
const nested = Array.isArray(value) ? value : [value];
|
|
80
|
+
for(const item of nested)
|
|
90
81
|
{
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
return this._validateKeys(value);
|
|
95
|
-
}
|
|
82
|
+
// null is a valid value, only objects have keys to check
|
|
83
|
+
if(item !== null && typeof item === 'object' && !this._validateKeys(item))
|
|
84
|
+
return false;
|
|
96
85
|
}
|
|
97
86
|
}
|
|
98
87
|
|
|
99
88
|
return true;
|
|
100
89
|
}
|
|
101
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Set of the _id values of the collection. It is rebuilt when its size no longer matches the
|
|
93
|
+
* documents, so removals done directly on the documents array are handled too.
|
|
94
|
+
* @return Set
|
|
95
|
+
*/
|
|
96
|
+
_ids()
|
|
97
|
+
{
|
|
98
|
+
if(!this._idSet || this._idSet.size !== this._documents.length)
|
|
99
|
+
this._idSet = new Set(this._documents.map(doc => doc._data._id));
|
|
100
|
+
|
|
101
|
+
return this._idSet;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Copies a document, so the object given by the caller is never changed
|
|
106
|
+
* @param object document
|
|
107
|
+
* @return object
|
|
108
|
+
*/
|
|
109
|
+
_copy(document)
|
|
110
|
+
{
|
|
111
|
+
try {
|
|
112
|
+
return structuredClone(document);
|
|
113
|
+
} catch(error) {
|
|
114
|
+
throw new Error('Documents can only contain cloneable values (no functions or DOM nodes)');
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
102
118
|
/**
|
|
103
119
|
* Inserts one document to the collection
|
|
104
120
|
* @author Anderson Arruda < anderson@sysborg.com.br >
|
|
105
|
-
* @version 1.
|
|
121
|
+
* @version 1.2.0
|
|
106
122
|
* @param object document
|
|
107
|
-
* @param function callback -
|
|
108
|
-
* @return
|
|
123
|
+
* @param function callback - receives the inserted _id
|
|
124
|
+
* @return mixed the _id of the inserted document
|
|
109
125
|
**/
|
|
110
126
|
insertOne(document, callback)
|
|
111
127
|
{
|
|
112
|
-
if(
|
|
128
|
+
if(!this._isDocument(document))
|
|
113
129
|
throw new Error('Invalid document type');
|
|
114
130
|
|
|
115
131
|
if(!this._validateKeys(document))
|
|
116
132
|
throw new Error('Invalid document keys');
|
|
117
133
|
|
|
118
|
-
|
|
134
|
+
const data = this._copy(document);
|
|
135
|
+
if(typeof data._id === 'undefined')
|
|
119
136
|
{
|
|
120
|
-
let _idKey = '';
|
|
121
137
|
do {
|
|
122
|
-
|
|
123
|
-
} while(this.
|
|
124
|
-
|
|
125
|
-
|
|
138
|
+
data._id = this._generateKey();
|
|
139
|
+
} while(this._ids().has(data._id));
|
|
140
|
+
}
|
|
141
|
+
else if(this._ids().has(data._id))
|
|
142
|
+
{
|
|
143
|
+
throw new Error(`Duplicate _id "${data._id}"`);
|
|
126
144
|
}
|
|
127
145
|
|
|
128
|
-
if(!this._validate(
|
|
146
|
+
if(!this._validate(data))
|
|
129
147
|
throw new Error('Invalid document');
|
|
130
148
|
|
|
131
|
-
this._documents.push(new documents(
|
|
149
|
+
this._documents.push(new documents(data));
|
|
150
|
+
this._ids().add(data._id);
|
|
132
151
|
|
|
133
152
|
if(typeof callback === 'function')
|
|
134
|
-
callback();
|
|
153
|
+
callback(data._id);
|
|
154
|
+
|
|
155
|
+
return data._id;
|
|
135
156
|
}
|
|
136
157
|
|
|
137
158
|
/**
|
|
138
|
-
* Insert many documents
|
|
159
|
+
* Insert many documents. Every document is validated before the first one is inserted,
|
|
160
|
+
* so an invalid document never leaves the collection with only part of the batch.
|
|
139
161
|
* @author Anderson Arruda < anderson@sysborg.com.br >
|
|
140
|
-
* @version 1.
|
|
162
|
+
* @version 1.2.0
|
|
141
163
|
* @param array documents
|
|
142
|
-
* @param function callback
|
|
143
|
-
* @return
|
|
164
|
+
* @param function callback - receives the inserted ids
|
|
165
|
+
* @return array the _id of each inserted document
|
|
144
166
|
*/
|
|
145
167
|
insertMany(documents, callback) {
|
|
146
|
-
|
|
147
|
-
if(!Array.isArray(documents) || this._validateDocumentsType(documents) > 0)
|
|
168
|
+
if(!Array.isArray(documents) || !documents.every(document => this._isDocument(document)))
|
|
148
169
|
throw new Error('Documents must be an array of objects');
|
|
149
170
|
|
|
150
|
-
|
|
151
|
-
|
|
171
|
+
const given = new Set();
|
|
172
|
+
for(const document of documents)
|
|
173
|
+
{
|
|
174
|
+
if(!this._validateKeys(document))
|
|
175
|
+
throw new Error('Invalid document keys');
|
|
176
|
+
|
|
177
|
+
if(typeof document._id === 'undefined')
|
|
178
|
+
continue;
|
|
179
|
+
|
|
180
|
+
if(given.has(document._id) || this._ids().has(document._id))
|
|
181
|
+
throw new Error(`Duplicate _id "${document._id}"`);
|
|
182
|
+
|
|
183
|
+
given.add(document._id);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const ids = documents.map(document => this.insertOne(document));
|
|
152
187
|
|
|
153
188
|
if(typeof callback === 'function')
|
|
154
|
-
callback();
|
|
189
|
+
callback(ids);
|
|
190
|
+
|
|
191
|
+
return ids;
|
|
155
192
|
}
|
|
156
193
|
|
|
157
194
|
//create index
|
package/src/utils.js
CHANGED
|
@@ -25,16 +25,17 @@ class utils
|
|
|
25
25
|
*/
|
|
26
26
|
getDotData(data, field)
|
|
27
27
|
{
|
|
28
|
-
let
|
|
28
|
+
let value = data;
|
|
29
|
+
for(const part of field.split('.'))
|
|
30
|
+
{
|
|
31
|
+
// a missing step means the field does not exist (it used to return the parent object)
|
|
32
|
+
if(value === null || typeof value !== 'object' || !(part in value))
|
|
33
|
+
return undefined;
|
|
29
34
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
{
|
|
33
|
-
data = data[field];
|
|
34
|
-
}
|
|
35
|
-
});
|
|
35
|
+
value = value[part];
|
|
36
|
+
}
|
|
36
37
|
|
|
37
|
-
return
|
|
38
|
+
return value;
|
|
38
39
|
}
|
|
39
40
|
}
|
|
40
41
|
|
package/Brainstorming.md
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
For the first query version needs only to see if has some non logical operator or non logical comparison to resend to find as a recursive method.
|
|
2
|
-
|
|
3
|
-
- Preparing sort of data using new capabilities
|
|
4
|
-
- improving find behavior
|
|
5
|
-
|
|
6
|
-
For comparison operators and deny operator like not
|
|
7
|
-
adding nor https://www.mongodb.com/docs/manual/reference/operator/query/nor/
|
|
8
|
-
|
|
9
|
-
https://www.mongodb.com/docs/manual/reference/operator/query-comparison/
|
|
10
|
-
https://www.mongodb.com/docs/manual/reference/operator/query/expr/
|
|
11
|
-
https://www.mongodb.com/docs/manual/reference/operator/query/regex/
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
Preparing a bunch of misc tests to search data, add sort data and create test sortable data.
|
|
15
|
-
|
|
16
|
-
//erros and improvements
|
|
17
|
-
Bugs appears after resolving not, need to check and test why
|
|
18
|
-
npm run test
|
|
19
|
-
|
|
20
|
-
needs to improve projection to use cursor
|
|
21
|
-
|
|
22
|
-
fix new bugs in test
|
package/debug-not.js
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
const sorla = require('./src/sorla.js');
|
|
2
|
-
const helper = require('./src/helper.js');
|
|
3
|
-
|
|
4
|
-
const numberOfObjects = 20;
|
|
5
|
-
const sdb = new sorla();
|
|
6
|
-
sdb.createDb('test');
|
|
7
|
-
sdb.useDb('test');
|
|
8
|
-
|
|
9
|
-
sdb.db.createCollection('collection_test');
|
|
10
|
-
|
|
11
|
-
const objects = helper.createFakeCollection(sdb, 'collection_test', numberOfObjects);
|
|
12
|
-
const [itemIndex1, ] = helper.getRandomComparisonData(objects);
|
|
13
|
-
|
|
14
|
-
const finded = sdb.db.collection_test.find({
|
|
15
|
-
uuid: {
|
|
16
|
-
$not: {
|
|
17
|
-
$eq: itemIndex1.uuid
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
console.log(finded.length, numberOfObjects);
|
|
23
|
-
|
|
24
|
-
//console.log(finded);
|