jexidb 1.0.2
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/.gitattributes +2 -0
- package/LICENSE +21 -0
- package/README.md +125 -0
- package/babel.config.json +5 -0
- package/dist/Database.cjs +983 -0
- package/package.json +58 -0
- package/src/Database.mjs +365 -0
- package/src/FileHandler.mjs +133 -0
- package/src/IndexManager.mjs +186 -0
- package/src/serializers/Advanced.mjs +120 -0
- package/src/serializers/Simple.mjs +21 -0
- package/test/test-json-compressed.jdb +0 -0
- package/test/test-json.jdb +12 -0
- package/test/test-v8-compressed.jdb +0 -0
- package/test/test-v8.jdb +0 -0
- package/test/test.mjs +152 -0
|
@@ -0,0 +1,186 @@
|
|
|
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, matchAny=false) {
|
|
87
|
+
if (!criteria) throw new Error('No query criteria provided')
|
|
88
|
+
const fields = Object.keys(criteria)
|
|
89
|
+
if (!fields.length) throw new Error('No valid query criteria provided')
|
|
90
|
+
let matchingLines = matchAny ? new Set() : null
|
|
91
|
+
for (const field of fields) {
|
|
92
|
+
if (typeof(this.index.data[field]) == 'undefined') continue
|
|
93
|
+
const criteriaValue = criteria[field]
|
|
94
|
+
let lineNumbersForField = new Set()
|
|
95
|
+
const isNumericField = this.opts.indexes[field] === 'number'
|
|
96
|
+
if (typeof(criteriaValue) === 'object' && !Array.isArray(criteriaValue)) {
|
|
97
|
+
const fieldIndex = this.index.data[field];
|
|
98
|
+
for (const value in fieldIndex) {
|
|
99
|
+
let includeValue = true
|
|
100
|
+
if (isNumericField) {
|
|
101
|
+
const numericValue = parseFloat(value);
|
|
102
|
+
if (!isNaN(numericValue)) {
|
|
103
|
+
if (criteriaValue['>'] !== undefined && numericValue <= criteriaValue['>']) {
|
|
104
|
+
includeValue = false;
|
|
105
|
+
}
|
|
106
|
+
if (criteriaValue['>='] !== undefined && numericValue < criteriaValue['>=']) {
|
|
107
|
+
includeValue = false;
|
|
108
|
+
}
|
|
109
|
+
if (criteriaValue['<'] !== undefined && numericValue >= criteriaValue['<']) {
|
|
110
|
+
includeValue = false;
|
|
111
|
+
}
|
|
112
|
+
if (criteriaValue['<='] !== undefined && numericValue > criteriaValue['<=']) {
|
|
113
|
+
includeValue = false;
|
|
114
|
+
}
|
|
115
|
+
if (criteriaValue['!='] !== undefined) {
|
|
116
|
+
const excludeValues = Array.isArray(criteriaValue['!=']) ? criteriaValue['!='] : [criteriaValue['!=']];
|
|
117
|
+
if (excludeValues.includes(numericValue)) {
|
|
118
|
+
includeValue = false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
} else {
|
|
123
|
+
if (criteriaValue['contains'] !== undefined && typeof value === 'string') {
|
|
124
|
+
if (!value.includes(criteriaValue['contains'])) {
|
|
125
|
+
includeValue = false;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (criteriaValue['regex'] !== undefined && typeof value === 'string') {
|
|
129
|
+
const regex = new RegExp(criteriaValue['regex']);
|
|
130
|
+
if (!regex.test(value)) {
|
|
131
|
+
includeValue = false;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (criteriaValue['!='] !== undefined) {
|
|
135
|
+
const excludeValues = Array.isArray(criteriaValue['!=']) ? criteriaValue['!='] : [criteriaValue['!=']];
|
|
136
|
+
if (excludeValues.includes(value)) {
|
|
137
|
+
includeValue = false;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (includeValue) {
|
|
143
|
+
for (const lineNumber of fieldIndex[value]) {
|
|
144
|
+
lineNumbersForField.add(lineNumber);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
} else {
|
|
149
|
+
const values = Array.isArray(criteriaValue) ? criteriaValue : [criteriaValue];
|
|
150
|
+
for (const value of values) {
|
|
151
|
+
if (this.index.data[field][value]) {
|
|
152
|
+
for (const lineNumber of this.index.data[field][value]) {
|
|
153
|
+
lineNumbersForField.add(lineNumber);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (matchAny) {
|
|
159
|
+
matchingLines = new Set([...matchingLines, ...lineNumbersForField]);
|
|
160
|
+
} else {
|
|
161
|
+
if (matchingLines === null) {
|
|
162
|
+
matchingLines = lineNumbersForField
|
|
163
|
+
} else {
|
|
164
|
+
matchingLines = new Set([...matchingLines].filter(n => lineNumbersForField.has(n)));
|
|
165
|
+
}
|
|
166
|
+
if (!matchingLines.size) {
|
|
167
|
+
return new Set()
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return matchingLines || new Set();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
load(index) {
|
|
175
|
+
for(const field in index.data) {
|
|
176
|
+
for(const term in index.data[field]) {
|
|
177
|
+
index.data[field][term] = new Set(index.data[field][term]) // set to array
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
this.index = index
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
readColumnIndex(column) {
|
|
184
|
+
return new Set((this.index.data && this.index.data[column]) ? Object.keys(this.index.data[column]) : [])
|
|
185
|
+
}
|
|
186
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
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.linebreak = Buffer.from([0x0A])
|
|
10
|
+
this.delimiter = Buffer.from([0xFF, 0xFF, 0xFF, 0xFF])
|
|
11
|
+
this.defaultBuffer = Buffer.alloc(4096)
|
|
12
|
+
this.brotliOptions = {
|
|
13
|
+
params: {
|
|
14
|
+
[zlib.constants.BROTLI_PARAM_QUALITY]: 4
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async serialize(data, opts={}) {
|
|
20
|
+
let line
|
|
21
|
+
let header = 0x00 // 1 byte de header
|
|
22
|
+
const useV8 = this.opts.v8 || opts.v8 === true
|
|
23
|
+
const compress = this.opts.compress || opts.compress === true
|
|
24
|
+
if (useV8) {
|
|
25
|
+
header |= 0x02 // set V8
|
|
26
|
+
line = v8.serialize(data)
|
|
27
|
+
} else {
|
|
28
|
+
if(compress) {
|
|
29
|
+
line = Buffer.from(JSON.stringify(data), 'utf-8')
|
|
30
|
+
} else {
|
|
31
|
+
return Buffer.from(JSON.stringify(data) + (opts.linebreak !== false ? '\n' : ''), 'utf-8')
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (compress) {
|
|
35
|
+
let err
|
|
36
|
+
const buffer = await this.compress(line).catch(e => err = e)
|
|
37
|
+
if(!err) {
|
|
38
|
+
header |= 0x01
|
|
39
|
+
line = buffer
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const totalLength = 1 + line.length + (opts.linebreak !== false ? 1 : 0)
|
|
43
|
+
const result = Buffer.alloc(totalLength)
|
|
44
|
+
result[0] = header
|
|
45
|
+
line.copy(result, 1)
|
|
46
|
+
if (opts.linebreak !== false) {
|
|
47
|
+
result[totalLength - 1] = 0x0A
|
|
48
|
+
}
|
|
49
|
+
return result
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async deserialize(data, opts={}) {
|
|
53
|
+
let line
|
|
54
|
+
const header = data.readUInt8(0)
|
|
55
|
+
const valid = header === 0x00 || header === 0x01 || header === 0x02 || header === 0x03
|
|
56
|
+
let isCompressed, isV8, decompresssed
|
|
57
|
+
if(valid) {
|
|
58
|
+
isCompressed = (header & 0x01) === 0x01
|
|
59
|
+
isV8 = (header & 0x02) === 0x02
|
|
60
|
+
line = data.subarray(1) // remove byte header
|
|
61
|
+
} else {
|
|
62
|
+
isCompressed = isV8 = false
|
|
63
|
+
line = data
|
|
64
|
+
}
|
|
65
|
+
if (isCompressed) {
|
|
66
|
+
let err
|
|
67
|
+
const buffer = await this.decompress(line).catch(e => err = e)
|
|
68
|
+
if(!err) {
|
|
69
|
+
decompresssed = true
|
|
70
|
+
line = buffer
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (isV8) {
|
|
74
|
+
try {
|
|
75
|
+
return v8.deserialize(line)
|
|
76
|
+
} catch (e) {
|
|
77
|
+
throw new Error('Failed to deserialize V8 data')
|
|
78
|
+
}
|
|
79
|
+
} else {
|
|
80
|
+
try {
|
|
81
|
+
return JSON.parse(line.toString('utf-8').trim())
|
|
82
|
+
} catch (e) {
|
|
83
|
+
console.error('Failed to deserialize', header, line.toString('utf-8').trim())
|
|
84
|
+
throw new Error('Failed to deserialize JSON data')
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
compress(data) {
|
|
90
|
+
return new Promise((resolve, reject) => {
|
|
91
|
+
zlib.brotliCompress(data, this.brotliOptions, (err, buffer) => {
|
|
92
|
+
if (err) {
|
|
93
|
+
reject(err)
|
|
94
|
+
} else {
|
|
95
|
+
resolve(buffer)
|
|
96
|
+
}
|
|
97
|
+
})
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
decompress(data) {
|
|
102
|
+
return new Promise((resolve, reject) => {
|
|
103
|
+
zlib.brotliDecompress(data, (err, buffer) => {
|
|
104
|
+
if (err) {
|
|
105
|
+
reject(err)
|
|
106
|
+
} else {
|
|
107
|
+
resolve(buffer)
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async safeDeserialize(json) {
|
|
114
|
+
try {
|
|
115
|
+
return await this.deserialize(json)
|
|
116
|
+
} catch (e) {
|
|
117
|
+
return null
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export default class Serializer {
|
|
2
|
+
|
|
3
|
+
constructor(opts={}) {
|
|
4
|
+
this.opts = Object.assign({}, opts)
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
async serialize(data, opts={}) {
|
|
8
|
+
return Buffer.from(JSON.stringify(data) + (opts.linebreak !== false ? '\n' : ''), 'utf-8')
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async deserialize(data, opts={}) {
|
|
12
|
+
const line = data.toString('utf-8')
|
|
13
|
+
try {
|
|
14
|
+
return JSON.parse(line)
|
|
15
|
+
} catch (e) {
|
|
16
|
+
console.error('Failed to deserialize', line)
|
|
17
|
+
throw new Error('Failed to deserialize JSON data')
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{"id":2,"name":"Sub-Zero","signatureMove":"Ice Ball","powerType":"Cryomancy"}
|
|
2
|
+
{"id":3,"name":"Raiden","signatureMove":"Electric Fly","powerType":"Lightning"}
|
|
3
|
+
{"id":4,"name":"Jax","signatureMove":"Ground Pound","powerType":"Strength"}
|
|
4
|
+
{"id":5,"name":"Sindel","signatureMove":"Sonic Scream","powerType":"Sound"}
|
|
5
|
+
{"id":6,"name":"Ermac","signatureMove":"Telekinesis","powerType":"Telekinesis"}
|
|
6
|
+
{"id":7,"name":"Mileena","signatureMove":"Sai Throw","powerType":"Teleportation"}
|
|
7
|
+
{"id":8,"name":"Kenshi","signatureMove":"Telekinetic Slash","powerType":"Telekinesis"}
|
|
8
|
+
{"id":9,"name":"D'Vorah","signatureMove":"Swarm","powerType":"Insects"}
|
|
9
|
+
{"id":10,"name":"Sonya Blade","signatureMove":"Energy Rings","powerType":"Special Forces Technology"}
|
|
10
|
+
{"id":11,"name":"Kotal Kahn","signatureMove":"Sunstone","powerType":"Osh-Tekk Strength"}
|
|
11
|
+
{"data":{"id":{"2":[0],"3":[1],"4":[2],"5":[3],"6":[4],"7":[5],"8":[6],"9":[7],"10":[8],"11":[9]},"name":{"Sub-Zero":[0],"Raiden":[1],"Jax":[2],"Sindel":[3],"Ermac":[4],"Mileena":[5],"Kenshi":[6],"D'Vorah":[7],"Sonya Blade":[8],"Kotal Kahn":[9]}}}
|
|
12
|
+
[0,78,158,234,310,390,472,559,631,733,822,1070]
|
|
Binary file
|
package/test/test-v8.jdb
ADDED
|
Binary file
|
package/test/test.mjs
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
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
|
+
// Function to clear the test file before each run
|
|
54
|
+
const clearTestFile = () => {
|
|
55
|
+
fs.writeFileSync(testFilePath, '', { encoding: null });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
console.log('Battle #' + id + ' (' + format + ') is starting...\n');
|
|
59
|
+
clearTestFile(); // Clear the file before starting the tests
|
|
60
|
+
const start = Date.now()
|
|
61
|
+
const db = new Database(testFilePath, opts); // Instantiate the database
|
|
62
|
+
|
|
63
|
+
// 1. Test if the instance was created correctly
|
|
64
|
+
await db.init(); // Call init() right after the instance is created
|
|
65
|
+
console.assert(db.initialized === true, `Test failed: Database didn't initialize. Looks like Raiden needs to give it a shock!`);
|
|
66
|
+
|
|
67
|
+
// 2. Test data insertion with Mortal Kombat characters
|
|
68
|
+
await db.insert({ id: 1, name: character.name, signatureMove: character.signatureMove, powerType: character.powerType });
|
|
69
|
+
await db.insert({ id: 2, name: 'Sub-Zero', signatureMove: 'Ice Ball', powerType: 'Cryomancy' });
|
|
70
|
+
await db.insert({ id: 3, name: 'Raiden', signatureMove: 'Electric Fly', powerType: 'Lightning' });
|
|
71
|
+
await db.insert({ id: 4, name: 'Jax', signatureMove: 'Ground Pound', powerType: 'Strength' });
|
|
72
|
+
await db.insert({ id: 5, name: 'Sindel', signatureMove: 'Sonic Scream', powerType: 'Sound' });
|
|
73
|
+
await db.insert({ id: 6, name: 'Ermac', signatureMove: 'Telekinesis', powerType: 'Telekinesis' });
|
|
74
|
+
await db.insert({ id: 7, name: 'Mileena', signatureMove: 'Sai Throw', powerType: 'Teleportation' });
|
|
75
|
+
await db.insert({ id: 8, name: 'Kenshi', signatureMove: 'Telekinetic Slash', powerType: 'Telekinesis' });
|
|
76
|
+
await db.insert({ id: 9, name: 'D\'Vorah', signatureMove: 'Swarm', powerType: 'Insects' });
|
|
77
|
+
await db.insert({ id: 10, name: 'Sonya Blade', signatureMove: 'Energy Rings', powerType: 'Special Forces Technology' });
|
|
78
|
+
await db.insert({ id: 11, name: 'Kotal Kahn', signatureMove: 'Sunstone', powerType: 'Osh-Tekk Strength' });
|
|
79
|
+
|
|
80
|
+
// "Flawless Victory" if the insertion is successful
|
|
81
|
+
console.log('Round 1 - CREATE: Flawless Victory! All characters inserted successfully.');
|
|
82
|
+
|
|
83
|
+
// 3. Test if the data was inserted correctly
|
|
84
|
+
let results = await db.query({ id: { '<=': 5 } });
|
|
85
|
+
const pass1 = results.length === 5;
|
|
86
|
+
const pass2 = results[0].name === character.name;
|
|
87
|
+
console.assert(pass1, `Round 2 - READ: Test failed: Where is everyone? ${character.missingMessage}`);
|
|
88
|
+
console.assert(pass2, `Round 2 - READ: Test failed: ${character.name} seems to have been teleported out of the database!`);
|
|
89
|
+
if(pass1 && pass2) console.log(`Round 2 - READ: Flawless Victory! All characters inserted successfully, led by ${character.name}.`);
|
|
90
|
+
|
|
91
|
+
// 4. 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
|
+
// 5. Test data deletion
|
|
99
|
+
await db.delete({ name: character.name + ' Updated' });
|
|
100
|
+
|
|
101
|
+
results = await db.query({ id: { '<=': 2 } });
|
|
102
|
+
const pass5 = results.length === 1;
|
|
103
|
+
const pass6 = results[0].name === 'Sub-Zero';
|
|
104
|
+
console.assert(pass5, `Round 4 - DELETE: Test failed: ${character.deleteMessage}`);
|
|
105
|
+
console.assert(pass6, `Round 4 - DELETE: Test failed: Sub-Zero is nowhere to be seen. Did he freeze the system?`);
|
|
106
|
+
if(pass5 && pass6) console.log(`Round 4 - DELETE: Flawless Victory! ${character.name} has been eliminated successfully.`);
|
|
107
|
+
|
|
108
|
+
// End the battle and log the result
|
|
109
|
+
if(pass1 && pass2 && pass4 && pass5 && pass6) {
|
|
110
|
+
let err, elapsed = Date.now() - start;
|
|
111
|
+
elapsed = elapsed < 1000 ? elapsed + 'ms' : (elapsed / 1000).toFixed(3) + 's';
|
|
112
|
+
const { size } = await fs.promises.stat(testFilePath);
|
|
113
|
+
benchmarks[format] = { elapsed, size };
|
|
114
|
+
console.log(`\nBattle #${id} ended: All tests with format "${format}" ran successfully! Fatality avoided this time.\n\n`);
|
|
115
|
+
} else {
|
|
116
|
+
benchmarks[format] = { elapsed: 'Error', size: 'Error' };
|
|
117
|
+
throw `\nBattle #${id} ended: Some tests failed with format "${format}"! Time to train harder.\n\n`;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function runAllTests() {
|
|
122
|
+
let err
|
|
123
|
+
await runTests(1, 'json', 'JSON', {
|
|
124
|
+
indexes: {id: 'number', name: 'string'},
|
|
125
|
+
v8: false,
|
|
126
|
+
compress: false,
|
|
127
|
+
compressIndex: false
|
|
128
|
+
}).catch(e => err = e)
|
|
129
|
+
await runTests(2, 'v8', 'V8 serialization', {
|
|
130
|
+
indexes: {id: 'number', name: 'string'},
|
|
131
|
+
v8: true,
|
|
132
|
+
compress: false,
|
|
133
|
+
compressIndex: false
|
|
134
|
+
}).catch(e => err = e)
|
|
135
|
+
await runTests(3, 'json-compressed', 'JSON with Brotli compression', {
|
|
136
|
+
indexes: {id: 'number', name: 'string'},
|
|
137
|
+
v8: false,
|
|
138
|
+
compress: false,
|
|
139
|
+
compressIndex: true
|
|
140
|
+
}).catch(e => err = e)
|
|
141
|
+
await runTests(3, 'v8-compressed', 'V8 with Brotli compression', {
|
|
142
|
+
indexes: {id: 'number', name: 'string'},
|
|
143
|
+
v8: true,
|
|
144
|
+
compress: false,
|
|
145
|
+
compressIndex: true
|
|
146
|
+
}).catch(e => err = e)
|
|
147
|
+
console.table(benchmarks)
|
|
148
|
+
process.exit(err ? 1 : 0)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Run the tests
|
|
152
|
+
runAllTests().catch(error => console.error('Error during tests:', error));
|