jexidb 1.1.0 → 2.0.1
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 +2 -2
- package/README.md +459 -63
- package/dist/FileHandler.js +688 -0
- package/dist/IndexManager.js +353 -0
- package/dist/IntegrityChecker.js +364 -0
- package/dist/JSONLDatabase.js +1194 -0
- package/dist/index.js +608 -0
- package/package.json +65 -59
- package/src/FileHandler.js +674 -0
- package/src/IndexManager.js +363 -0
- package/src/IntegrityChecker.js +379 -0
- package/src/JSONLDatabase.js +1248 -0
- package/src/index.js +604 -0
- package/.gitattributes +0 -2
- package/babel.config.json +0 -5
- package/dist/Database.cjs +0 -1161
- package/src/Database.mjs +0 -376
- package/src/FileHandler.mjs +0 -202
- package/src/IndexManager.mjs +0 -230
- package/src/Serializer.mjs +0 -120
- package/test/README.md +0 -13
- package/test/test-json-compressed.jdb +0 -0
- package/test/test-json.jdb +0 -0
- package/test/test-v8-compressed.jdb +0 -0
- package/test/test-v8.jdb +0 -0
- package/test/test.mjs +0 -173
package/src/IndexManager.mjs
DELETED
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
export default class IndexManager {
|
|
2
|
-
constructor(opts) {
|
|
3
|
-
this.opts = Object.assign({}, opts)
|
|
4
|
-
this.index = Object.assign({data: {}}, this.opts.index)
|
|
5
|
-
Object.keys(this.opts.indexes).forEach(field => {
|
|
6
|
-
this.index.data[field] = {}
|
|
7
|
-
})
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
add(row, lineNumber) {
|
|
11
|
-
if (typeof row !== 'object' || !row) {
|
|
12
|
-
throw new Error('Invalid \'row\' parameter, it must be an object')
|
|
13
|
-
}
|
|
14
|
-
if (typeof lineNumber !== 'number') {
|
|
15
|
-
throw new Error('Invalid line number')
|
|
16
|
-
}
|
|
17
|
-
for (const field in this.index.data) {
|
|
18
|
-
if (row[field]) {
|
|
19
|
-
const values = Array.isArray(row[field]) ? row[field] : [row[field]]
|
|
20
|
-
for (const value of values) {
|
|
21
|
-
if (!this.index.data[field][value]) {
|
|
22
|
-
this.index.data[field][value] = new Set()
|
|
23
|
-
}
|
|
24
|
-
if (!this.index.data[field][value].has(lineNumber)) {
|
|
25
|
-
this.index.data[field][value].add(lineNumber)
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
dryRemove(ln) { // remove line numbers from index without adjusting the rest
|
|
33
|
-
for (const field in this.index.data) {
|
|
34
|
-
for (const value in this.index.data[field]) {
|
|
35
|
-
if (this.index.data[field][value].has(ln)) {
|
|
36
|
-
this.index.data[field][value].delete(ln)
|
|
37
|
-
}
|
|
38
|
-
if (this.index.data[field][value].size === 0) {
|
|
39
|
-
delete this.index.data[field][value]
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
remove(lineNumbers) { // remove line numbers from index and adjust the rest
|
|
46
|
-
lineNumbers.sort((a, b) => a - b) // Sort ascending to make calculations easier
|
|
47
|
-
for (const field in this.index.data) {
|
|
48
|
-
for (const value in this.index.data[field]) {
|
|
49
|
-
const newSet = new Set()
|
|
50
|
-
for (const ln of this.index.data[field][value]) {
|
|
51
|
-
let offset = 0
|
|
52
|
-
for (const lineNumber of lineNumbers) {
|
|
53
|
-
if (lineNumber < ln) {
|
|
54
|
-
offset++
|
|
55
|
-
} else if (lineNumber === ln) {
|
|
56
|
-
offset = -1 // Marca para remoção
|
|
57
|
-
break
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
if (offset >= 0) {
|
|
61
|
-
newSet.add(ln - offset) // Atualiza o valor
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
if (newSet.size > 0) {
|
|
65
|
-
this.index.data[field][value] = newSet
|
|
66
|
-
} else {
|
|
67
|
-
delete this.index.data[field][value]
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
replace(map) {
|
|
74
|
-
for (const field in this.index.data) {
|
|
75
|
-
for (const value in this.index.data[field]) {
|
|
76
|
-
for(const lineNumber of this.index.data[field][value]) {
|
|
77
|
-
if (map.has(lineNumber)) {
|
|
78
|
-
this.index.data[field][value].delete(lineNumber)
|
|
79
|
-
this.index.data[field][value].add(map.get(lineNumber))
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
query(criteria, options = {}) {
|
|
87
|
-
if (typeof options === 'boolean') {
|
|
88
|
-
options = { matchAny: options };
|
|
89
|
-
}
|
|
90
|
-
const { matchAny = false, caseInsensitive = false } = options;
|
|
91
|
-
if (!criteria) throw new Error('No query criteria provided');
|
|
92
|
-
const fields = Object.keys(criteria);
|
|
93
|
-
if (!fields.length) throw new Error('No valid query criteria provided');
|
|
94
|
-
let matchingLines = matchAny ? new Set() : null;
|
|
95
|
-
|
|
96
|
-
for (const field of fields) {
|
|
97
|
-
if (typeof this.index.data[field] === 'undefined') continue;
|
|
98
|
-
const criteriaValue = criteria[field];
|
|
99
|
-
let lineNumbersForField = new Set();
|
|
100
|
-
const isNumericField = this.opts.indexes[field] === 'number';
|
|
101
|
-
|
|
102
|
-
if (typeof criteriaValue === 'object' && !Array.isArray(criteriaValue)) {
|
|
103
|
-
const fieldIndex = this.index.data[field];
|
|
104
|
-
for (const value in fieldIndex) {
|
|
105
|
-
let includeValue = true;
|
|
106
|
-
if (isNumericField) {
|
|
107
|
-
const numericValue = parseFloat(value);
|
|
108
|
-
if (!isNaN(numericValue)) {
|
|
109
|
-
if (criteriaValue['>'] !== undefined && numericValue <= criteriaValue['>']) {
|
|
110
|
-
includeValue = false;
|
|
111
|
-
}
|
|
112
|
-
if (criteriaValue['>='] !== undefined && numericValue < criteriaValue['>=']) {
|
|
113
|
-
includeValue = false;
|
|
114
|
-
}
|
|
115
|
-
if (criteriaValue['<'] !== undefined && numericValue >= criteriaValue['<']) {
|
|
116
|
-
includeValue = false;
|
|
117
|
-
}
|
|
118
|
-
if (criteriaValue['<='] !== undefined && numericValue > criteriaValue['<=']) {
|
|
119
|
-
includeValue = false;
|
|
120
|
-
}
|
|
121
|
-
if (criteriaValue['!='] !== undefined) {
|
|
122
|
-
const excludeValues = Array.isArray(criteriaValue['!='])
|
|
123
|
-
? criteriaValue['!=']
|
|
124
|
-
: [criteriaValue['!=']];
|
|
125
|
-
if (excludeValues.includes(numericValue)) {
|
|
126
|
-
includeValue = false;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
} else {
|
|
131
|
-
if (criteriaValue['contains'] !== undefined && typeof value === 'string') {
|
|
132
|
-
const term = String(criteriaValue['contains']);
|
|
133
|
-
if (caseInsensitive) {
|
|
134
|
-
if (!value.toLowerCase().includes(term.toLowerCase())) {
|
|
135
|
-
includeValue = false;
|
|
136
|
-
}
|
|
137
|
-
} else {
|
|
138
|
-
if (!value.includes(term)) {
|
|
139
|
-
includeValue = false;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
if (criteriaValue['regex'] !== undefined) {
|
|
144
|
-
let regex;
|
|
145
|
-
if (typeof criteriaValue['regex'] === 'string') {
|
|
146
|
-
regex = new RegExp(criteriaValue['regex'], caseInsensitive ? 'i' : '');
|
|
147
|
-
} else if (criteriaValue['regex'] instanceof RegExp) {
|
|
148
|
-
if (caseInsensitive && !criteriaValue['regex'].ignoreCase) {
|
|
149
|
-
const flags = criteriaValue['regex'].flags.includes('i')
|
|
150
|
-
? criteriaValue['regex'].flags
|
|
151
|
-
: criteriaValue['regex'].flags + 'i';
|
|
152
|
-
regex = new RegExp(criteriaValue['regex'].source, flags);
|
|
153
|
-
} else {
|
|
154
|
-
regex = criteriaValue['regex'];
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
if (regex && !regex.test(value)) {
|
|
158
|
-
includeValue = false;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
if (criteriaValue['!='] !== undefined) {
|
|
162
|
-
const excludeValues = Array.isArray(criteriaValue['!='])
|
|
163
|
-
? criteriaValue['!=']
|
|
164
|
-
: [criteriaValue['!=']];
|
|
165
|
-
if (excludeValues.includes(value)) {
|
|
166
|
-
includeValue = false;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
if (includeValue) {
|
|
172
|
-
for (const lineNumber of fieldIndex[value]) {
|
|
173
|
-
lineNumbersForField.add(lineNumber);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
} else {
|
|
178
|
-
// Comparação simples de igualdade
|
|
179
|
-
const values = Array.isArray(criteriaValue) ? criteriaValue : [criteriaValue];
|
|
180
|
-
const fieldData = this.index.data[field];
|
|
181
|
-
for (const searchValue of values) {
|
|
182
|
-
for (const key in fieldData) {
|
|
183
|
-
let match = false;
|
|
184
|
-
if (isNumericField) {
|
|
185
|
-
// Converter ambas as partes para número
|
|
186
|
-
match = Number(key) === Number(searchValue);
|
|
187
|
-
} else {
|
|
188
|
-
match = caseInsensitive
|
|
189
|
-
? key.toLowerCase() === String(searchValue).toLowerCase()
|
|
190
|
-
: key === searchValue;
|
|
191
|
-
}
|
|
192
|
-
if (match) {
|
|
193
|
-
for (const lineNumber of fieldData[key]) {
|
|
194
|
-
lineNumbersForField.add(lineNumber);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// Consolida os resultados de cada campo
|
|
202
|
-
if (matchAny) {
|
|
203
|
-
matchingLines = new Set([...matchingLines, ...lineNumbersForField]);
|
|
204
|
-
} else {
|
|
205
|
-
if (matchingLines === null) {
|
|
206
|
-
matchingLines = lineNumbersForField;
|
|
207
|
-
} else {
|
|
208
|
-
matchingLines = new Set([...matchingLines].filter(n => lineNumbersForField.has(n)));
|
|
209
|
-
}
|
|
210
|
-
if (!matchingLines.size) {
|
|
211
|
-
return new Set();
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
return matchingLines || new Set();
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
load(index) {
|
|
219
|
-
for(const field in index.data) {
|
|
220
|
-
for(const term in index.data[field]) {
|
|
221
|
-
index.data[field][term] = new Set(index.data[field][term]) // set to array
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
this.index = index
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
readColumnIndex(column) {
|
|
228
|
-
return new Set((this.index.data && this.index.data[column]) ? Object.keys(this.index.data[column]) : [])
|
|
229
|
-
}
|
|
230
|
-
}
|
package/src/Serializer.mjs
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import { EventEmitter } from 'events'
|
|
2
|
-
import zlib from 'zlib'
|
|
3
|
-
import v8 from 'v8'
|
|
4
|
-
|
|
5
|
-
export default class Serializer extends EventEmitter {
|
|
6
|
-
constructor(opts = {}) {
|
|
7
|
-
super()
|
|
8
|
-
this.opts = Object.assign({}, opts)
|
|
9
|
-
this.brotliOptions = {
|
|
10
|
-
params: {
|
|
11
|
-
[zlib.constants.BROTLI_PARAM_QUALITY]: 4
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
async serialize(data, opts={}) {
|
|
17
|
-
let line
|
|
18
|
-
let header = 0x00 // 1 byte de header
|
|
19
|
-
const useV8 = (this.opts.v8 && opts.json !== true) || opts.v8 === true
|
|
20
|
-
const compress = (this.opts.compress && opts.compress !== false) || opts.compress === true
|
|
21
|
-
const addLinebreak = opts.linebreak !== false || (!useV8 && !compress && opts.linebreak !== false)
|
|
22
|
-
if (useV8) {
|
|
23
|
-
header |= 0x02 // set V8
|
|
24
|
-
line = v8.serialize(data)
|
|
25
|
-
} else {
|
|
26
|
-
const json = JSON.stringify(data)
|
|
27
|
-
line = Buffer.from(json, 'utf-8')
|
|
28
|
-
}
|
|
29
|
-
if (compress) {
|
|
30
|
-
let err
|
|
31
|
-
const compressionType = useV8 ? 'deflate' : 'brotli'
|
|
32
|
-
const buffer = await this.compress(line, compressionType).catch(e => err = e)
|
|
33
|
-
if(!err && buffer.length && buffer.length < line.length) {
|
|
34
|
-
header |= 0x01
|
|
35
|
-
line = buffer
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
const totalLength = 1 + line.length + (addLinebreak ? 1 : 0)
|
|
39
|
-
const result = Buffer.alloc(totalLength)
|
|
40
|
-
result[0] = header
|
|
41
|
-
line.copy(result, 1, 0, line.length)
|
|
42
|
-
if(addLinebreak) {
|
|
43
|
-
result[result.length - 1] = 0x0A
|
|
44
|
-
}
|
|
45
|
-
return result
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
async deserialize(data) {
|
|
49
|
-
if(data.length === 0) {
|
|
50
|
-
return null
|
|
51
|
-
}
|
|
52
|
-
let line, isCompressed, isV8
|
|
53
|
-
const header = data.readUInt8(0)
|
|
54
|
-
const valid = header === 0x00 || header === 0x01 || header === 0x02 || header === 0x03
|
|
55
|
-
if(valid) {
|
|
56
|
-
isCompressed = (header & 0x01) === 0x01
|
|
57
|
-
isV8 = (header & 0x02) === 0x02
|
|
58
|
-
line = data.subarray(1) // remove byte header
|
|
59
|
-
} else {
|
|
60
|
-
isCompressed = isV8 = false
|
|
61
|
-
try {
|
|
62
|
-
return JSON.parse(data.toString('utf-8').trim())
|
|
63
|
-
} catch (e) {
|
|
64
|
-
throw new Error('Failed to deserialize JSON data')
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
if (isCompressed) {
|
|
68
|
-
const compressionType = isV8 ? 'deflate' : 'brotli'
|
|
69
|
-
line = await this.decompress(line, compressionType).catch(e => err = e)
|
|
70
|
-
}
|
|
71
|
-
if (isV8) {
|
|
72
|
-
try {
|
|
73
|
-
return v8.deserialize(line)
|
|
74
|
-
} catch (e) {
|
|
75
|
-
throw new Error('Failed to deserialize V8 data')
|
|
76
|
-
}
|
|
77
|
-
} else {
|
|
78
|
-
try {
|
|
79
|
-
return JSON.parse(line.toString('utf-8').trim())
|
|
80
|
-
} catch (e) {
|
|
81
|
-
throw new Error('Failed to deserialize JSON data')
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
compress(data, type) {
|
|
87
|
-
return new Promise((resolve, reject) => {
|
|
88
|
-
const callback = (err, buffer) => {
|
|
89
|
-
if (err) {
|
|
90
|
-
reject(err)
|
|
91
|
-
} else {
|
|
92
|
-
resolve(buffer)
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
if(type === 'brotli') {
|
|
96
|
-
zlib.brotliCompress(data, this.brotliOptions, callback)
|
|
97
|
-
} else {
|
|
98
|
-
zlib.deflate(data, callback)
|
|
99
|
-
}
|
|
100
|
-
})
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
decompress(data, type) {
|
|
104
|
-
return new Promise((resolve, reject) => {
|
|
105
|
-
const callback = (err, buffer) => {
|
|
106
|
-
if (err) {
|
|
107
|
-
reject(err)
|
|
108
|
-
} else {
|
|
109
|
-
resolve(buffer)
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
if(type === 'brotli') {
|
|
113
|
-
zlib.brotliDecompress(data, callback)
|
|
114
|
-
} else {
|
|
115
|
-
zlib.inflate(data, callback)
|
|
116
|
-
}
|
|
117
|
-
})
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
}
|
package/test/README.md
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
## Test Results
|
|
2
|
-
The following are the results of the automated tests conducted on my PC for different serialization formats and compression methods.
|
|
3
|
-
|
|
4
|
-
| Format | Size (bytes) | Time elapsed (ms) |
|
|
5
|
-
|-------------------------------|--------------|--------------------|
|
|
6
|
-
| JSON | 1117 | 21 |
|
|
7
|
-
| V8 Serialization | 1124 | 12 🏆 |
|
|
8
|
-
| JSON with Brotli Compression | 1038 🏆 | 20 |
|
|
9
|
-
| V8 with Brotli Compression | 1052 | 15 |
|
|
10
|
-
|
|
11
|
-
When using V8 for serialization, be aware that serialized data may not deserialize correctly across different versions of V8 (which powers Chromium, Node.js, and Electron), leading to potential data loss or corruption. This issue is specific to V8 serialization; however, when using JSON, the data remains universal and compatible across environments.
|
|
12
|
-
|
|
13
|
-
To avoid this issue, always use your V8 database with the same version of Node.js.
|
|
Binary file
|
package/test/test-json.jdb
DELETED
|
Binary file
|
|
Binary file
|
package/test/test-v8.jdb
DELETED
|
Binary file
|
package/test/test.mjs
DELETED
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { Database } from '../src/Database.mjs';
|
|
4
|
-
import { fileURLToPath } from 'url';
|
|
5
|
-
|
|
6
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
-
const __dirname = path.dirname(__filename);
|
|
8
|
-
const benchmarks = {}
|
|
9
|
-
|
|
10
|
-
// Array of objects containing the name and specific messages for each character
|
|
11
|
-
const characters = [
|
|
12
|
-
{
|
|
13
|
-
name: 'Scorpion',
|
|
14
|
-
missingMessage: 'Did Scorpion pull a "GET OVER HERE" on the missing entries?',
|
|
15
|
-
updateMessage: 'Scorpion refuses to update. Maybe he’s stuck in the Netherrealm?',
|
|
16
|
-
deleteMessage: 'I thought Scorpion was gone, but he’s still here! Must be that "Hellfire Resurrection."',
|
|
17
|
-
signatureMove: 'Spear', powerType: 'Hellfire'
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
name: 'Scarlet',
|
|
21
|
-
missingMessage: 'Did Scarlet drain the life from the missing entries with her blood magic?',
|
|
22
|
-
updateMessage: 'Scarlet refuses to update. Maybe her blood magic is causing interference?',
|
|
23
|
-
deleteMessage: 'I thought Scarlet was gone, but she’s still here! Must be that blood regeneration ability.',
|
|
24
|
-
signatureMove: 'Blood Tentacle', powerType: 'Blood Manipulation'
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
name: 'Frost',
|
|
28
|
-
missingMessage: 'Did Frost freeze the missing entries?',
|
|
29
|
-
updateMessage: 'Frost refuses to update. Maybe she’s stuck in an ice block?',
|
|
30
|
-
deleteMessage: 'I thought Frost was gone, but she’s still here! Must be that "Ice Shield."',
|
|
31
|
-
signatureMove: 'Ice Daggers',
|
|
32
|
-
powerType: 'Cryomancy'
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
name: 'Frost',
|
|
36
|
-
missingMessage: 'Did Frost freeze the missing entries?',
|
|
37
|
-
updateMessage: 'Frost refuses to update. Maybe she’s stuck in an ice block?',
|
|
38
|
-
deleteMessage: 'I thought Frost was gone, but she’s still here! Must be that "Ice Shield."',
|
|
39
|
-
signatureMove: 'Ice Daggers',
|
|
40
|
-
powerType: 'Cryomancy'
|
|
41
|
-
}
|
|
42
|
-
];
|
|
43
|
-
|
|
44
|
-
// Function to run the tests
|
|
45
|
-
const runTests = async (id, name, format, opts) => {
|
|
46
|
-
|
|
47
|
-
// Define the character for this battle based on the battle ID
|
|
48
|
-
const character = characters[(id - 1) % characters.length];
|
|
49
|
-
|
|
50
|
-
// Path to the test file
|
|
51
|
-
const testFilePath = path.join(__dirname, 'test-' + name + '.jdb');
|
|
52
|
-
|
|
53
|
-
console.log('Battle #' + id + ' (' + format + ') is starting...\n');
|
|
54
|
-
fs.writeFileSync(testFilePath, '', { encoding: null }); // Clear the file before starting the tests
|
|
55
|
-
const start = Date.now()
|
|
56
|
-
const db = new Database(testFilePath, opts); // Instantiate the database
|
|
57
|
-
|
|
58
|
-
// 1. Test if the instance was created correctly
|
|
59
|
-
await db.init(); // Call init() right after the instance is created
|
|
60
|
-
console.assert(db.initialized === true, `Test failed: Database didn't initialize. Looks like Raiden needs to give it a shock!`);
|
|
61
|
-
|
|
62
|
-
// 2. Test data insertion with Mortal Kombat characters
|
|
63
|
-
await db.insert({ id: 1, name: character.name, signatureMove: character.signatureMove, powerType: character.powerType });
|
|
64
|
-
await db.insert({ id: 2, name: 'Sub-Zero', signatureMove: 'Ice Ball', powerType: 'Cryomancy' });
|
|
65
|
-
await db.insert({ id: 3, name: 'Raiden', signatureMove: 'Electric Fly', powerType: 'Lightning' });
|
|
66
|
-
await db.insert({ id: 4, name: 'Jax', signatureMove: 'Ground Pound', powerType: 'Strength' });
|
|
67
|
-
await db.insert({ id: 5, name: 'Sindel', signatureMove: 'Sonic Scream', powerType: 'Sound' });
|
|
68
|
-
await db.insert({ id: 6, name: 'Ermac', signatureMove: 'Telekinesis', powerType: 'Telekinesis' });
|
|
69
|
-
await db.insert({ id: 7, name: 'Mileena', signatureMove: 'Sai Throw', powerType: 'Teleportation' });
|
|
70
|
-
await db.insert({ id: 8, name: 'Kenshi', signatureMove: 'Telekinetic Slash', powerType: 'Telekinesis' });
|
|
71
|
-
await db.insert({ id: 9, name: 'D\'Vorah', signatureMove: 'Swarm', powerType: 'Insects' });
|
|
72
|
-
await db.insert({ id: 10, name: 'Sonya Blade', signatureMove: 'Energy Rings', powerType: 'Special Forces Technology' });
|
|
73
|
-
await db.insert({ id: 11, name: 'Kotal Kahn', signatureMove: 'Sunstone', powerType: 'Osh-Tekk Strength' });
|
|
74
|
-
|
|
75
|
-
// "Flawless Victory" if the insertion is successful
|
|
76
|
-
console.log('Round 1 - CREATE: Flawless Victory! All characters inserted successfully.');
|
|
77
|
-
|
|
78
|
-
// 3. Test if the data was inserted correctly
|
|
79
|
-
let results = await db.query({ id: { '<=': 5 } });
|
|
80
|
-
const pass1 = results.length === 5;
|
|
81
|
-
const pass2 = results[0].name === character.name;
|
|
82
|
-
console.assert(pass1, `Round 2 - READ: Test failed: Where is everyone? ${character.missingMessage}`);
|
|
83
|
-
console.assert(pass2, `Round 2 - READ: Test failed: ${character.name} seems to have been teleported out of the database!`);
|
|
84
|
-
if(pass1 && pass2) console.log(`Round 2 - READ: Flawless Victory! All characters inserted successfully, led by ${character.name}.`);
|
|
85
|
-
|
|
86
|
-
// 4. Test indexes
|
|
87
|
-
const pass3 = await db.indexManager.readColumnIndex('name').has(character.name)
|
|
88
|
-
console.assert(pass3, `Round 3 - INDEX: Test failed: ${character.name} is not in the index.`);
|
|
89
|
-
if(pass3) console.log(`Round 3 - INDEX: Flawless Victory! ${character.name} is in the index.`);
|
|
90
|
-
|
|
91
|
-
// 5. Test data update
|
|
92
|
-
await db.update({ id: 1 }, { name: character.name + ' Updated' });
|
|
93
|
-
results = await db.query({ id: 1 });
|
|
94
|
-
const pass4 = results.length === 1 && results[0].name === character.name + ' Updated';
|
|
95
|
-
console.assert(pass4, `Round 3 - UPDATE: Test failed: ${character.updateMessage}`);
|
|
96
|
-
if(pass4) console.log(`Round 3 - UPDATE: Flawless Victory! ${character.name} has been updated successfully.`);
|
|
97
|
-
|
|
98
|
-
// 6. Test data deletion
|
|
99
|
-
await db.delete({ name: character.name + ' Updated' });
|
|
100
|
-
results = await db.query({ id: { '<=': 2 } });
|
|
101
|
-
const pass5 = results.length === 1;
|
|
102
|
-
const pass6 = results[0].name === 'Sub-Zero';
|
|
103
|
-
console.assert(pass5, `Round 4 - DELETE: Test failed: ${character.deleteMessage}`);
|
|
104
|
-
console.assert(pass6, `Round 4 - DELETE: Test failed: Sub-Zero is nowhere to be seen. Did he freeze the system?`);
|
|
105
|
-
if(pass5 && pass6) console.log(`Round 4 - DELETE: Flawless Victory! ${character.name} has been eliminated successfully.`);
|
|
106
|
-
|
|
107
|
-
// End the battle and log the result
|
|
108
|
-
if(pass1 && pass2 && pass4 && pass5 && pass6) {
|
|
109
|
-
let err, elapsed = Date.now() - start;
|
|
110
|
-
const { size } = await fs.promises.stat(testFilePath);
|
|
111
|
-
if(!benchmarks[format]) {
|
|
112
|
-
benchmarks[format] = { elapsed, size }
|
|
113
|
-
} else {
|
|
114
|
-
benchmarks[format].elapsed = (elapsed + benchmarks[format].elapsed)
|
|
115
|
-
benchmarks[format].size = (size + benchmarks[format].size)
|
|
116
|
-
}
|
|
117
|
-
console.log(`\nBattle #${id} ended: All tests with format "${format}" ran successfully! Fatality avoided this time.\n\n`);
|
|
118
|
-
global.gc()
|
|
119
|
-
} else {
|
|
120
|
-
benchmarks[format] = { elapsed: 'Error', size: 'Error' };
|
|
121
|
-
global.gc();
|
|
122
|
-
throw `\nBattle #${id} ended: Some tests failed with format "${format}"! Time to train harder.\n\n`;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
async function runAllTests() {
|
|
127
|
-
const depth = 10
|
|
128
|
-
let err, i = 1
|
|
129
|
-
let tests = [
|
|
130
|
-
['json', 'JSON', { indexes: { id: 'number', name: 'string' }, v8: false, compress: false, compressIndex: false }],
|
|
131
|
-
['v8', 'V8 serialization', { indexes: { id: 'number', name: 'string' }, v8: true, compress: false, compressIndex: false }],
|
|
132
|
-
['json-compressed', 'JSON with Brotli compression', { indexes: { id: 'number', name: 'string' }, v8: false, compress: false, compressIndex: true }],
|
|
133
|
-
['v8-compressed', 'V8 with Deflate compression', { indexes: { id: 'number', name: 'string' }, v8: true, compress: false, compressIndex: true }]
|
|
134
|
-
]
|
|
135
|
-
tests = Array(depth).fill(tests).flat()
|
|
136
|
-
tests = tests.map(value => ({ value, sort: Math.random() })).sort((a, b) => a.sort - b.sort).map(({ value }) => value)
|
|
137
|
-
for(const test of tests) {
|
|
138
|
-
await runTests(i++, test[0], test[1], test[2]).catch(e => {
|
|
139
|
-
benchmarks[test[1]] = { elapsed: 'Error', size: 'Error' };
|
|
140
|
-
console.error(e)
|
|
141
|
-
err = e
|
|
142
|
-
})
|
|
143
|
-
}
|
|
144
|
-
const winners = {}
|
|
145
|
-
for (const [format, result] of Object.entries(benchmarks)) {
|
|
146
|
-
if (result.elapsed !== 'Error' && result.size !== 'Error') {
|
|
147
|
-
if (typeof(winners.elapsed) === 'undefined' || result.elapsed < winners.elapsed) {
|
|
148
|
-
winners.elapsed = result.elapsed
|
|
149
|
-
winners.format = format
|
|
150
|
-
}
|
|
151
|
-
if (typeof(winners.size) === 'undefined' || result.size < winners.size) {
|
|
152
|
-
winners.size = result.size
|
|
153
|
-
winners.format = format
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
for(const format in benchmarks) {
|
|
158
|
-
for(const prop of ['elapsed', 'size']) {
|
|
159
|
-
if(benchmarks[format][prop] === winners[prop]) {
|
|
160
|
-
benchmarks[format][prop] += ' \uD83C\uDFC6'
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
console.log('Benchmarks results after '+ tests.length +' battles:')
|
|
165
|
-
console.table(benchmarks)
|
|
166
|
-
// setInterval(() => {}, 1000)
|
|
167
|
-
// global.Database = Database
|
|
168
|
-
// global.__dirname = __dirname
|
|
169
|
-
process.exit(err ? 1 : 0)
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// Run the tests
|
|
173
|
-
runAllTests().catch(error => console.error('Error during tests:', error));
|