sorla-db 1.1.0 → 1.3.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,165 +0,0 @@
1
- /**
2
- * Compares find() against a straightforward reference implementation of the query semantics,
3
- * over hand written queries and hundreds of randomly generated nested ones (seeded, so a failure
4
- * always reproduces). This catches logic bugs, not just crashes.
5
- */
6
- const chai = require('chai')
7
- const expect = chai.expect
8
- const sorla = require('../src/sorla.js')
9
-
10
- // deterministic PRNG (mulberry32)
11
- function prng(seed) {
12
- return () => {
13
- seed |= 0; seed = seed + 0x6D2B79F5 | 0;
14
- let t = Math.imul(seed ^ seed >>> 15, 1 | seed);
15
- t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
16
- return ((t ^ t >>> 14) >>> 0) / 4294967296;
17
- };
18
- }
19
-
20
- const NAMES = ['Ana', 'Bruno', 'Carla', 'Davi', 'Eva', 'Fábio'];
21
- const CITIES = ['A', 'B', 'C', 'D'];
22
- const TAGS = ['admin', 'dev', 'ops'];
23
-
24
- function buildDocuments(random, count) {
25
- const pick = list => list[Math.floor(random() * list.length)];
26
- return Array.from({ length: count }, (_, n) => {
27
- const doc = { n, name: pick(NAMES), city: pick(CITIES), score: Math.round(random() * 1000) / 10, active: random() > 0.5 };
28
- if(random() > 0.15) doc.age = 18 + Math.floor(random() * 50); // sometimes missing
29
- if(random() > 0.3) doc.tags = TAGS.filter(() => random() > 0.5); // array, sometimes missing
30
- // nickname is often missing; null values cannot be stored yet (see feature_insert.test.js)
31
- if(random() > 0.5) doc.nickname = pick(NAMES);
32
- return doc;
33
- });
34
- }
35
-
36
- // ---- reference semantics, written independently of src/
37
- const compare = {
38
- $eq: (v, q) => !Array.isArray(q) && v === q,
39
- $ne: (v, q) => !Array.isArray(q) && v !== q,
40
- $gt: (v, q) => !Array.isArray(q) && v > q,
41
- $gte: (v, q) => !Array.isArray(q) && v >= q,
42
- $lt: (v, q) => !Array.isArray(q) && v < q,
43
- $lte: (v, q) => !Array.isArray(q) && v <= q,
44
- $in: (v, q) => Array.isArray(v) ? v.some(item => q.includes(item)) : q.includes(v),
45
- $nin: (v, q) => !(Array.isArray(v) ? v.some(item => q.includes(item)) : q.includes(v))
46
- };
47
-
48
- function refCondition(value, condition) {
49
- const isOps = condition !== null && typeof condition === 'object' && !Array.isArray(condition)
50
- && Object.keys(condition).length > 0 && Object.keys(condition).every(k => k.startsWith('$'));
51
- if(!isOps) return value === condition;
52
- return Object.entries(condition).every(([op, arg]) => op === '$not' ? !refCondition(value, arg) : compare[op](value, arg));
53
- }
54
-
55
- function refMatch(doc, query) {
56
- return Object.entries(query).every(([key, value]) => {
57
- if(key === '$and') return value.every(q => refMatch(doc, q));
58
- if(key === '$or') return value.some(q => refMatch(doc, q));
59
- if(key === '$nor') return !value.some(q => refMatch(doc, q));
60
- return refCondition(doc[key], value);
61
- });
62
- }
63
-
64
- // ---- random nested queries
65
- function randomQueryGenerator(random) {
66
- const pick = list => list[Math.floor(random() * list.length)];
67
- const fields = {
68
- name: () => pick(NAMES),
69
- city: () => pick(CITIES),
70
- age: () => 18 + Math.floor(random() * 50),
71
- score: () => Math.round(random() * 1000) / 10,
72
- active: () => random() > 0.5,
73
- tags: () => pick(TAGS),
74
- nickname: () => random() > 0.3 ? pick(NAMES) : null
75
- };
76
-
77
- function condition(field) {
78
- const value = fields[field];
79
- const roll = random();
80
- if(roll < 0.2) return value(); // plain equality
81
- const ops = {};
82
- const count = 1 + Math.floor(random() * 2);
83
- for(let i = 0; i < count; i++) {
84
- const op = pick(['$eq', '$ne', '$gt', '$gte', '$lt', '$lte', '$in', '$nin']);
85
- ops[op] = op === '$in' || op === '$nin' ? [value(), value()] : value();
86
- }
87
- return roll > 0.85 ? { $not: ops } : ops;
88
- }
89
-
90
- function node(depth) {
91
- const query = {};
92
- const fieldNames = Object.keys(fields).sort(() => random() - 0.5).slice(0, 1 + Math.floor(random() * 2));
93
- for(const field of fieldNames) query[field] = condition(field);
94
- if(depth < 3 && random() > 0.4) {
95
- const logical = pick(['$and', '$or', '$nor']);
96
- query[logical] = Array.from({ length: 1 + Math.floor(random() * 3) }, () => node(depth + 1));
97
- }
98
- return query;
99
- }
100
-
101
- return () => node(0);
102
- }
103
-
104
- describe('SORLA find() matches the reference query semantics', () => {
105
- let collection;
106
- let documents;
107
-
108
- beforeAll(() => {
109
- documents = buildDocuments(prng(2026), 120);
110
- const srla = new sorla();
111
- srla.useDb('equivalence');
112
- srla.db.createCollection('people');
113
- // insertOne mutates the object (adds _id), keep the reference documents clean
114
- for(const doc of documents) srla.db.people.insertOne(structuredClone(doc));
115
- collection = srla.db.people;
116
- });
117
-
118
- const found = query => {
119
- const cursor = collection.find(query);
120
- const ids = [];
121
- while(cursor.hasNext()) ids.push(cursor.next()._data.n);
122
- return ids;
123
- };
124
- const expected = query => documents.filter(doc => refMatch(doc, query)).map(doc => doc.n);
125
-
126
- const handWritten = [
127
- {},
128
- { city: 'A' },
129
- { age: { $gte: 30, $lt: 40 } },
130
- { tags: { $in: ['ops'] } },
131
- { nickname: null },
132
- { nickname: { $ne: null } },
133
- { age: { $nin: [20, 21, 22] }, active: true },
134
- { $or: [{ city: 'A' }, { city: 'B', age: { $gt: 50 } }] },
135
- { $and: [{ age: { $gte: 30 } }, { $or: [{ name: 'Ana' }, { city: { $in: ['A', 'B'] } }] }] },
136
- { $nor: [{ active: true }, { score: { $lt: 50 } }] },
137
- { score: { $not: { $gte: 20, $lte: 80 } } },
138
- { $or: [{ $and: [{ city: 'C' }, { $nor: [{ age: { $lt: 30 } }] }] }, { tags: { $in: ['admin'] }, active: false }] }
139
- ];
140
-
141
- handWritten.forEach((query, i) => {
142
- it(`hand written query #${i + 1}: ${JSON.stringify(query)}`, () => {
143
- expect(found(query)).to.deep.equal(expected(query));
144
- });
145
- });
146
-
147
- it('500 random nested queries give the same documents as the reference', () => {
148
- const next = randomQueryGenerator(prng(42));
149
- let nonEmpty = 0;
150
- for(let i = 0; i < 500; i++) {
151
- const query = next();
152
- const want = expected(query);
153
- if(want.length) nonEmpty++;
154
- expect(found(query), JSON.stringify(query)).to.deep.equal(want);
155
- }
156
- // the generator must produce meaningful queries, not only empty results
157
- expect(nonEmpty).to.be.greaterThan(100);
158
- });
159
-
160
- it('findOne returns the first matching document', () => {
161
- const query = { $and: [{ city: 'B' }, { $or: [{ active: true }, { age: { $gt: 40 } }] }] };
162
- const first = expected(query)[0];
163
- expect(collection.findOne(query).n).to.equal(first);
164
- });
165
- });
@@ -1,36 +0,0 @@
1
- const chai = require('chai')
2
- const expect = chai.expect
3
- const faker = require('faker')
4
- const sorla = require('../src/sorla.js')
5
- const collection = require('../src/collections/collection.js');
6
-
7
- describe('SORLA Database Unit Test', () => {
8
- beforeAll(() => {
9
- this.srla = new sorla();
10
- this.dbname = faker.random.word();
11
- this.srla.createDb(this.dbname);
12
- this.srla.useDb(this.dbname);
13
- });
14
-
15
- it('Should create a new collection with specified name', () => {
16
- this.srla.db.createCollection('collection1');
17
- expect(this.srla.db.countCollections()).to.equal(1);
18
- expect(this.srla.db.collection1).to.not.be.undefined;
19
- expect(this.srla.db.collection1).to.be.an.instanceof(collection);
20
- });
21
-
22
- it('Should create a new collection with specified name, second collection', () => {
23
- this.srla.db.createCollection('collection2');
24
- expect(this.srla.db.countCollections()).to.equal(2);
25
- expect(this.srla.db.collection2).to.not.be.undefined;
26
- expect(this.srla.db.collection2).to.be.an.instanceof(collection);
27
- });
28
-
29
- it('Should return a collection undefined when not exists', () => {
30
- expect(this.srla.db.collection3).to.be.undefined;
31
- });
32
-
33
- it('Should avoid create a collection with name that exists in database class', () => {
34
- expect(() => this.srla.db.createCollection('_name')).to.throw();
35
- });
36
- });
@@ -1,39 +0,0 @@
1
- const chai = require('chai');
2
- const expect = chai.expect;
3
- const faker = require('faker');
4
- const documents = require('../src/documents/document.js');
5
-
6
- describe('testing document capabilities', () => {
7
- it('checking capabilities of getting a field inside a object', () => {
8
- const doc = new documents({
9
- name: 'Anderson',
10
- test: {
11
- inscription: 123
12
- }
13
- });
14
-
15
- expect(doc.name).to.equal('Anderson');
16
- });
17
-
18
- it('checking capabilities of getting a field inside a object', () => {
19
- const doc = new documents({
20
- name: 'Anderson',
21
- test: {
22
- inscription: 123
23
- }
24
- });
25
-
26
- expect(doc.test.inscription).to.equal(123);
27
- });
28
-
29
- it('checking capabilities of getting a field inside a object', () => {
30
- const doc = new documents({
31
- name: 'Anderson',
32
- test: {
33
- inscription: 123
34
- }
35
- });
36
-
37
- expect(doc['test.inscription']).to.equal(123);
38
- });
39
- });
@@ -1,78 +0,0 @@
1
- const chai = require('chai')
2
- const expect = chai.expect
3
- const operators = require('../src/query_language/operators.js')
4
-
5
- describe('SORLA comparison operators', () => {
6
- const op = new operators();
7
-
8
- it('recognizes logical and comparison operators', () => {
9
- for(const name of ['$and', '$or', '$not', '$nor'])
10
- expect(op.isLogicalOperator(name)).to.equal(true);
11
- for(const name of ['$eq', '$ne', '$gt', '$gte', '$lt', '$lte', '$in', '$nin'])
12
- expect(op.isOperator(name)).to.equal(true);
13
- expect(op.isAnyOperator('$regex')).to.equal(false);
14
- expect(op.isAnyOperator('name')).to.equal(false);
15
- });
16
-
17
- it('$eq is strict', () => {
18
- expect(op.$eq(1, 1)).to.equal(true);
19
- expect(op.$eq('1', 1)).to.equal(false);
20
- expect(op.$eq(null, null)).to.equal(true);
21
- expect(op.$eq(undefined, null)).to.equal(false);
22
- expect(op.$eq(true, 1)).to.equal(false);
23
- expect(op.$eq([1], [1]), 'arrays are not compared').to.equal(false);
24
- });
25
-
26
- it('$ne is the negation of $eq for scalars', () => {
27
- expect(op.$ne(1, 2)).to.equal(true);
28
- expect(op.$ne(1, 1)).to.equal(false);
29
- expect(op.$ne(undefined, 1), 'missing field').to.equal(true);
30
- });
31
-
32
- it('range operators compare numbers, strings and ISO dates', () => {
33
- expect(op.$gt(5, 3)).to.equal(true);
34
- expect(op.$gte(3, 3)).to.equal(true);
35
- expect(op.$lt(2, 3)).to.equal(true);
36
- expect(op.$lte(3, 3)).to.equal(true);
37
- expect(op.$gt('b', 'a')).to.equal(true);
38
- expect(op.$lt('2026-01-31', '2026-02-01')).to.equal(true);
39
- expect(op.$gt(undefined, 0), 'missing field').to.equal(false);
40
- expect(op.$lt(undefined, 10), 'missing field').to.equal(false);
41
- });
42
-
43
- it('range operators reject arrays as the operand', () => {
44
- for(const name of ['$gt', '$gte', '$lt', '$lte', '$eq', '$ne'])
45
- expect(op[name](1, [1, 2]), name).to.equal(false);
46
- });
47
-
48
- it('$in and $nin work with scalar and array fields', () => {
49
- expect(op.$in('a', ['a', 'b'])).to.equal(true);
50
- expect(op.$in('c', ['a', 'b'])).to.equal(false);
51
- expect(op.$in(['x', 'b'], ['a', 'b']), 'array field shares a value').to.equal(true);
52
- expect(op.$in(['x'], ['a', 'b'])).to.equal(false);
53
- expect(op.$in(1, ['1']), 'strict').to.equal(false);
54
- expect(op.$nin('c', ['a', 'b'])).to.equal(true);
55
- expect(op.$nin(undefined, ['a']), 'missing field').to.equal(true);
56
- });
57
-
58
- it('$in and $nin require an array', () => {
59
- expect(() => op.$in('a', 'a')).to.throw('$in operator must be an array');
60
- expect(() => op.$nin('a', 'a')).to.throw('$nin operator must be an array');
61
- });
62
-
63
- it('matchesCondition treats plain values as equality and objects of operators as conditions', () => {
64
- expect(op.matchesCondition(5, 5)).to.equal(true);
65
- expect(op.matchesCondition(5, { $gt: 1, $lt: 10 })).to.equal(true);
66
- expect(op.matchesCondition(5, { $gt: 1, $lt: 4 })).to.equal(false);
67
- expect(op.matchesCondition(5, { $not: { $gt: 10 } })).to.equal(true);
68
- expect(op.matchesCondition({ a: 1 }, { a: 1 }), 'objects are not deep compared').to.equal(false);
69
- });
70
-
71
- it('isOperatorObject only accepts non empty objects made of operators', () => {
72
- expect(op.isOperatorObject({ $gt: 1 })).to.equal(true);
73
- expect(op.isOperatorObject({ $gt: 1, name: 2 })).to.equal(false);
74
- expect(op.isOperatorObject({})).to.equal(false);
75
- expect(op.isOperatorObject(null)).to.equal(false);
76
- expect(op.isOperatorObject([1])).to.equal(false);
77
- });
78
- });
@@ -1,42 +0,0 @@
1
- const chai = require('chai')
2
- const expect = chai.expect
3
- const faker = require('faker')
4
-
5
- const sorla = require('../src/sorla.js')
6
-
7
- describe('SORLA Unit Test', () => {
8
- it('Should create a new database with random name', () => {
9
- const srla = new sorla();
10
- const dbname = faker.random.word();
11
- srla.createDb(dbname);
12
- srla.useDb(dbname);
13
-
14
- expect(srla.countDb()).to.equal(1);
15
- expect(srla.dbExists(dbname)).to.equal(true);
16
- expect(srla.db.name).to.equal(dbname);
17
- });
18
-
19
- it('Should not create a new database with the same name', () => {
20
- const srla = new sorla();
21
- const dbname = faker.random.word();
22
- srla.createDb(dbname);
23
-
24
- expect(srla.createDb(dbname)).to.equal(false);
25
- });
26
-
27
- it('Should created more than one database', () => {
28
- const srla = new sorla();
29
- for(let i=0; i<2; i++)
30
- {
31
- const dbname = faker.random.word();
32
- srla.createDb(dbname);
33
- }
34
-
35
- expect(srla.countDb()).to.equal(2);
36
- });
37
-
38
- it('Should not have any database created', () => {
39
- const srla = new sorla();
40
- expect(srla.countDb()).to.equal(0);
41
- });
42
- });
@@ -1,25 +0,0 @@
1
- const chai = require('chai');
2
- const expect = chai.expect;
3
-
4
- const sorla = require('../src/sorla.js');
5
-
6
- describe('Sorla Class', () => {
7
- it('should create a new database', () => {
8
- const srla = new sorla();
9
- srla.createDb('test');
10
- expect(srla.countDb()).to.equal(1);
11
- });
12
-
13
- it('should not create a new database with the same name', () => {
14
- const srla = new sorla();
15
- srla.createDb('test');
16
- expect(srla.createDb('test')).to.equal(false);
17
- });
18
-
19
- it('should created a new database and select it', () => {
20
- const srla = new sorla();
21
- const dbname = 'test';
22
- srla.useDb(dbname);
23
- expect(srla.db.name).to.equal(dbname);
24
- });
25
- });
package/webpack.config.js DELETED
@@ -1,11 +0,0 @@
1
- const path = require('path');
2
-
3
- module.exports = {
4
- mode: "production",
5
- target: "web",
6
- entry: './src/sorla.js',
7
- output: {
8
- path: path.resolve(__dirname, 'dist'),
9
- filename: 'sorla.min.js'
10
- }
11
- }