@jnode/db 1.0.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 +201 -0
- package/README.md +386 -0
- package/package.json +35 -0
- package/src/dble.js +1039 -0
- package/src/index.js +14 -0
- package/src/jdb.js +81 -0
- package/src/ljson.js +165 -0
package/src/dble.js
ADDED
|
@@ -0,0 +1,1039 @@
|
|
|
1
|
+
/*
|
|
2
|
+
@jnode/db/dble.js
|
|
3
|
+
|
|
4
|
+
Simple database package for Node.js.
|
|
5
|
+
|
|
6
|
+
JDB Lite Extended (DBLE) is a simple, fast and extensiable database format.
|
|
7
|
+
|
|
8
|
+
by JustApple (format design, programming, testing) &
|
|
9
|
+
Google Gemini (code review, small bug fix, completion of default types)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// load class
|
|
13
|
+
const { JDBFile } = require('./jdb.js');
|
|
14
|
+
|
|
15
|
+
// dble file manager
|
|
16
|
+
class DBLEFile {
|
|
17
|
+
constructor(jdb, options = {}) {
|
|
18
|
+
this.jdb = jdb;
|
|
19
|
+
this.options = options;
|
|
20
|
+
this._lineCache = new Map();
|
|
21
|
+
this.types = Object.assign({}, defaultDBLETypes, options.types);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
static async load(path, options = {}) {
|
|
25
|
+
const jdb = await JDBFile.load(path);
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
if (jdb.dbType !== 0x44424C45) throw new Error('JDB (DBLE): DBType is not "DBLE".'); // db type
|
|
29
|
+
if (jdb.dbVersion !== 1) throw new Error('JDB (DBLE): Unsupported DBLE DBVersion.'); // db version
|
|
30
|
+
|
|
31
|
+
const dble = new DBLEFile(jdb, options);
|
|
32
|
+
await dble._loadHead();
|
|
33
|
+
await dble._loadIndices();
|
|
34
|
+
|
|
35
|
+
return dble;
|
|
36
|
+
} catch (e) {
|
|
37
|
+
await jdb.close();
|
|
38
|
+
throw e;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
static async create(path, options = {}) {
|
|
43
|
+
const jdb = await JDBFile.create(path, {
|
|
44
|
+
dbType: 0x44424C45,
|
|
45
|
+
dbVersion: 1
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const dble = new DBLEFile(jdb, options);
|
|
49
|
+
try {
|
|
50
|
+
await dble._init(options);
|
|
51
|
+
return dble;
|
|
52
|
+
} catch (e) {
|
|
53
|
+
await jdb.close();
|
|
54
|
+
throw e;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
static async forceCreate(path, options = {}) {
|
|
59
|
+
const jdb = await JDBFile.forceCreate(path, {
|
|
60
|
+
dbType: 0x44424C45,
|
|
61
|
+
dbVersion: 1
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const dble = new DBLEFile(jdb, options);
|
|
65
|
+
try {
|
|
66
|
+
await dble._init(options);
|
|
67
|
+
return dble;
|
|
68
|
+
} catch (e) {
|
|
69
|
+
await jdb.close();
|
|
70
|
+
throw e;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async _init(options = {}) {
|
|
75
|
+
if (!options.fields) throw new Error('`options.fields` is required.');
|
|
76
|
+
|
|
77
|
+
this.fields = options.fields;
|
|
78
|
+
this.fieldsMap = {};
|
|
79
|
+
this.fieldOffsets = {};
|
|
80
|
+
this.indices = {};
|
|
81
|
+
this.relatives = {};
|
|
82
|
+
this.relativeOffsets = {};
|
|
83
|
+
|
|
84
|
+
// generate definitions
|
|
85
|
+
const dbHeadBufs = [];
|
|
86
|
+
let dbHeadLength = 22;
|
|
87
|
+
let dbLineLength = 6;
|
|
88
|
+
for (let i = 0; i < options.fields.length; i++) {
|
|
89
|
+
const def = options.fields[i];
|
|
90
|
+
|
|
91
|
+
if (this.fieldOffsets[def.name]) throw new Error('Could not have two fields in the same name.');
|
|
92
|
+
this.fieldOffsets[def.name] = dbLineLength;
|
|
93
|
+
this.fieldsMap[def.name] = def;
|
|
94
|
+
if (def.isKey) this.indices[def.name] = new Map(); // set index
|
|
95
|
+
|
|
96
|
+
const definitionBuf = def.toDefinitionBuffer(i === options.fields.length - 1);
|
|
97
|
+
dbHeadBufs.push(definitionBuf);
|
|
98
|
+
dbHeadLength += definitionBuf.length;
|
|
99
|
+
dbLineLength += def.length;
|
|
100
|
+
if (def.isRelative) { // set relatives
|
|
101
|
+
this.relatives[def.name] = this._getDefaultOf(def);
|
|
102
|
+
this.relativeOffsets[def.name] = dbHeadLength - def.length - 6;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
this.extOffset = dbLineLength;
|
|
107
|
+
|
|
108
|
+
// calculate ext field length dynamically as a power of 2
|
|
109
|
+
let n = dbLineLength + 1 + (options.extBaseLength ?? 0);
|
|
110
|
+
n |= n >> 1;
|
|
111
|
+
n |= n >> 2;
|
|
112
|
+
n |= n >> 4;
|
|
113
|
+
n |= n >> 8;
|
|
114
|
+
n |= n >> 16;
|
|
115
|
+
n++;
|
|
116
|
+
|
|
117
|
+
let extBaseLength = n - dbLineLength;
|
|
118
|
+
this.extBaseLength = extBaseLength;
|
|
119
|
+
this.lineLength = n;
|
|
120
|
+
|
|
121
|
+
// padding
|
|
122
|
+
const dbHeadPadBuf = Buffer.alloc(((dbHeadLength + n - 1) & ~(n - 1)) - dbHeadLength + 6);
|
|
123
|
+
dbHeadPadBuf.writeUInt16LE(extBaseLength); // extend field base length
|
|
124
|
+
dbHeadPadBuf.writeUInt32LE(0xFFFFFFFF, 2); // last empty line
|
|
125
|
+
dbHeadBufs.push(dbHeadPadBuf);
|
|
126
|
+
this.lastEmptyLine = 0xFFFFFFFF;
|
|
127
|
+
this.lastEmptyLineOffset = dbHeadLength - 4;
|
|
128
|
+
|
|
129
|
+
// write to file
|
|
130
|
+
const dbHeadBuf = Buffer.concat(dbHeadBufs);
|
|
131
|
+
this.bodyOffset = 16 + dbHeadBuf.length;
|
|
132
|
+
await this.jdb.handle.write(dbHeadBuf, 0, undefined, 16);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async _loadHead() {
|
|
136
|
+
const handle = this.jdb.handle;
|
|
137
|
+
|
|
138
|
+
this.fields = [];
|
|
139
|
+
this.fieldsMap = {};
|
|
140
|
+
this.fieldOffsets = {};
|
|
141
|
+
this.indices = {};
|
|
142
|
+
this.bodyOffset = 16;
|
|
143
|
+
this.relatives = {};
|
|
144
|
+
this.relativeOffsets = {};
|
|
145
|
+
|
|
146
|
+
// parse definitions
|
|
147
|
+
let fieldIsLast = false;
|
|
148
|
+
let fieldIsKey = false;
|
|
149
|
+
let fieldIsRelative = false;
|
|
150
|
+
let fieldLength = 0;
|
|
151
|
+
let fieldType = 'null';
|
|
152
|
+
let fieldName = 'null';
|
|
153
|
+
|
|
154
|
+
let dbLineLength = 6;
|
|
155
|
+
|
|
156
|
+
const readBytes = async (len) => {
|
|
157
|
+
const buf = Buffer.allocUnsafe(len);
|
|
158
|
+
const { bytesRead } = await handle.read(buf, 0, len, this.bodyOffset);
|
|
159
|
+
if (bytesRead !== len) throw new Error('JDB (DBLE): Fail to parse the file.');
|
|
160
|
+
this.bodyOffset += len;
|
|
161
|
+
return buf;
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
while (!fieldIsLast) {
|
|
165
|
+
// load flags and field length
|
|
166
|
+
const flagLenBuf = await readBytes(4);
|
|
167
|
+
const fieldFlags = flagLenBuf.readUInt16BE();
|
|
168
|
+
fieldIsLast = fieldFlags & 1;
|
|
169
|
+
fieldIsKey = fieldFlags & (1 << 1);
|
|
170
|
+
fieldIsRelative = fieldFlags & (1 << 2);
|
|
171
|
+
fieldLength = flagLenBuf.readUInt16LE(2);
|
|
172
|
+
|
|
173
|
+
// load type
|
|
174
|
+
const typeLenBuf = await readBytes(1);
|
|
175
|
+
fieldType = (await readBytes(typeLenBuf[0])).toString('utf8');
|
|
176
|
+
|
|
177
|
+
// load name
|
|
178
|
+
const nameLenBuf = await readBytes(1);
|
|
179
|
+
fieldName = (await readBytes(nameLenBuf[0])).toString('utf8');
|
|
180
|
+
|
|
181
|
+
// save fields
|
|
182
|
+
const FieldType = this.types[fieldType] || DBLEAnyField;
|
|
183
|
+
const field = new FieldType;
|
|
184
|
+
field.isKey = !!fieldIsKey;
|
|
185
|
+
field.isRelative = !!fieldIsRelative;
|
|
186
|
+
field.length = fieldLength;
|
|
187
|
+
field.type = fieldType;
|
|
188
|
+
field.name = fieldName;
|
|
189
|
+
this.fields.push(field);
|
|
190
|
+
|
|
191
|
+
if (this.fieldOffsets[fieldName]) throw new Error('JDB (DBLE): Fail to parse the file, could not have two fields in the same name.');
|
|
192
|
+
this.fieldOffsets[fieldName] = dbLineLength;
|
|
193
|
+
this.fieldsMap[fieldName] = field;
|
|
194
|
+
if (fieldIsKey) this.indices[fieldName] = new Map(); // set index
|
|
195
|
+
|
|
196
|
+
// set relatives
|
|
197
|
+
if (fieldIsRelative) {
|
|
198
|
+
this.relatives[fieldName] = field.parse(await readBytes(fieldLength), 0);
|
|
199
|
+
this.relativeOffsets[fieldName] = this.bodyOffset - fieldLength;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
dbLineLength += fieldLength;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const eblLelBuf = await readBytes(6);
|
|
206
|
+
this.extBaseLength = eblLelBuf.readUInt16LE();
|
|
207
|
+
this.extOffset = dbLineLength;
|
|
208
|
+
this.lineLength = dbLineLength += this.extBaseLength;
|
|
209
|
+
this.lastEmptyLine = eblLelBuf.readUInt32LE(2);
|
|
210
|
+
this.lastEmptyLineOffset = this.bodyOffset - 4;
|
|
211
|
+
let dbHeadLength = this.bodyOffset;
|
|
212
|
+
|
|
213
|
+
// calculate padding
|
|
214
|
+
this.bodyOffset += ((dbHeadLength + dbLineLength - 1) & ~(dbLineLength - 1)) - dbHeadLength;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
_getDefaultOf(type, relative) {
|
|
218
|
+
if (typeof type.default === 'function') {
|
|
219
|
+
return type.default(relative);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// safely retrieve normalized index mapping target mapping
|
|
224
|
+
_getIndexKey(val) {
|
|
225
|
+
return Buffer.isBuffer(val) ? val.toString('hex') : val;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// close file handle safely through the inner task queue
|
|
229
|
+
close() {
|
|
230
|
+
return this._doTask(async () => {
|
|
231
|
+
return await this.jdb.close();
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// load indices
|
|
236
|
+
async _loadIndices() {
|
|
237
|
+
for await (let { chunk, line } of this._readlines()) {
|
|
238
|
+
for (let i in this.indices) {
|
|
239
|
+
this.indices[i].set(this._getIndexKey(this._getFieldFromLine(chunk, i)), line);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// simple task queue
|
|
245
|
+
_doTask(func, skipQueue) {
|
|
246
|
+
if (skipQueue) return func();
|
|
247
|
+
return this._task = (async () => {
|
|
248
|
+
try { await this._task; } catch { }
|
|
249
|
+
return await func();
|
|
250
|
+
})();
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
_setLineCache(line, buf) {
|
|
254
|
+
// enforce LRU replacement mechanic mapping
|
|
255
|
+
this._lineCache.delete(line);
|
|
256
|
+
this._lineCache.set(line, buf);
|
|
257
|
+
|
|
258
|
+
// remove oldest added cache
|
|
259
|
+
if (this._lineCache.size > (this.options.maxLineCache || (this.options.maxLineCache = Math.ceil((this.options.maxLineCacheSize || 1048576) / this.lineLength)))) {
|
|
260
|
+
this._lineCache.delete(this._lineCache.keys().next().value);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// get a line's buffer
|
|
265
|
+
async _getLineBuffer(line) {
|
|
266
|
+
if (typeof line !== 'number') throw new TypeError('line must be an integer.');
|
|
267
|
+
|
|
268
|
+
// already in cache
|
|
269
|
+
if (this._lineCache.has(line)) return this._lineCache.get(line);
|
|
270
|
+
|
|
271
|
+
// read line
|
|
272
|
+
const lineBuf = Buffer.allocUnsafe(this.lineLength);
|
|
273
|
+
const { bytesRead } = await this.jdb.handle.read(lineBuf, 0, this.lineLength, this.bodyOffset + line * this.lineLength);
|
|
274
|
+
if (bytesRead !== this.lineLength) throw new Error('JDB (DBLE): Unexpected end of file.');
|
|
275
|
+
|
|
276
|
+
// add to cache
|
|
277
|
+
this._setLineCache(line, lineBuf);
|
|
278
|
+
|
|
279
|
+
return lineBuf;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// write a buffer to line
|
|
283
|
+
async _writeLineBuffer(line, buf, offset = 0) {
|
|
284
|
+
if (typeof line !== 'number') throw new TypeError('line must be an integer.');
|
|
285
|
+
if (!Buffer.isBuffer(buf)) throw new TypeError('buf must be a buffer.');
|
|
286
|
+
|
|
287
|
+
const writeBuf = offset === 0 && buf.length === this.lineLength ? buf : buf.subarray(offset, offset + this.lineLength);
|
|
288
|
+
|
|
289
|
+
// write line
|
|
290
|
+
await this.jdb.handle.write(writeBuf, 0, this.lineLength, this.bodyOffset + line * this.lineLength);
|
|
291
|
+
|
|
292
|
+
// add to cache
|
|
293
|
+
this._setLineCache(line, writeBuf);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
_getRelative(field) {
|
|
297
|
+
return this.relatives[field];
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
async _setRelative(field, value) {
|
|
301
|
+
const type = this.fieldsMap[field];
|
|
302
|
+
const buf = Buffer.alloc(type.length);
|
|
303
|
+
type.write(value, buf, 0);
|
|
304
|
+
|
|
305
|
+
// write line
|
|
306
|
+
await this.jdb.handle.write(buf, 0, buf.length, this.relativeOffsets[field]);
|
|
307
|
+
|
|
308
|
+
this.relatives[field] = value;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
_getDefaultByField(field, relative) {
|
|
312
|
+
return this._getDefaultOf(this.fieldsMap[field], relative);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// append a line
|
|
316
|
+
async _appendLineBuffer(buf, offset = 0) {
|
|
317
|
+
if (!Buffer.isBuffer(buf)) throw new TypeError('buf must be a buffer.');
|
|
318
|
+
|
|
319
|
+
const writeBuf = offset === 0 && buf.length === this.lineLength ? buf : buf.subarray(offset, offset + this.lineLength);
|
|
320
|
+
let targetLine = this.lastEmptyLine;
|
|
321
|
+
|
|
322
|
+
if (targetLine === 0xFFFFFFFF) { // no empty lines
|
|
323
|
+
const { size } = await this.jdb.handle.stat();
|
|
324
|
+
|
|
325
|
+
// write line
|
|
326
|
+
await this.jdb.handle.write(writeBuf, 0, this.lineLength, size);
|
|
327
|
+
|
|
328
|
+
// add to cache
|
|
329
|
+
targetLine = (size - this.bodyOffset) / this.lineLength;
|
|
330
|
+
this._setLineCache(targetLine, writeBuf);
|
|
331
|
+
} else { // empty line
|
|
332
|
+
const lineBuf = await this._getLineBuffer(targetLine);
|
|
333
|
+
this.lastEmptyLine = lineBuf.readUInt32LE(2);
|
|
334
|
+
await this._writeLineBuffer(targetLine, writeBuf);
|
|
335
|
+
|
|
336
|
+
// write back the last empty line
|
|
337
|
+
const lastEmptyLineBuf = Buffer.allocUnsafe(4);
|
|
338
|
+
lastEmptyLineBuf.writeUInt32LE(this.lastEmptyLine);
|
|
339
|
+
await this.jdb.handle.write(lastEmptyLineBuf, 0, 4, this.lastEmptyLineOffset);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// update for start line
|
|
343
|
+
if ((writeBuf[0] & 0xC0) === (0b00 << 6)) {
|
|
344
|
+
// update indecies
|
|
345
|
+
for (let i in this.indices) {
|
|
346
|
+
this.indices[i].set(this._getIndexKey(this._getFieldFromLine(writeBuf, i)), targetLine);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// update relatives
|
|
350
|
+
for (let i in this.relatives) {
|
|
351
|
+
await this._setRelative(i, this._getFieldFromLine(writeBuf, i));
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return targetLine;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
async _getLastEmptyLine(after) {
|
|
359
|
+
if ((typeof after !== 'number') && (this.lastEmptyLine !== 0xFFFFFFFF)) return this.lastEmptyLine;
|
|
360
|
+
|
|
361
|
+
const { size } = await this.jdb.handle.stat();
|
|
362
|
+
const newLine = (size - this.bodyOffset) / this.lineLength;
|
|
363
|
+
|
|
364
|
+
if (typeof after === 'number') {
|
|
365
|
+
if (after >= newLine) return after + 1;
|
|
366
|
+
const lineBuf = await this._getLineBuffer(after);
|
|
367
|
+
const next = lineBuf.readUInt32LE(2);
|
|
368
|
+
if (next !== 0xFFFFFFFF) return next;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return newLine;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
_getFieldFromLine(buf, field) {
|
|
375
|
+
return this.fieldsMap[field].parse(buf, this.fieldOffsets[field]);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
_setFieldsToLine(buf, fields) {
|
|
379
|
+
for (let field in fields) {
|
|
380
|
+
if (this.fieldsMap[field]) {
|
|
381
|
+
if (fields[field] === undefined) continue;
|
|
382
|
+
this.fieldsMap[field].write(fields[field], buf, this.fieldOffsets[field]);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
return buf;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
async _clearLinesFrom(line) {
|
|
389
|
+
if (line === 0xFFFFFFFF) return;
|
|
390
|
+
|
|
391
|
+
let lineBuf;
|
|
392
|
+
let nextLine = line;
|
|
393
|
+
|
|
394
|
+
while (nextLine !== 0xFFFFFFFF) {
|
|
395
|
+
lineBuf = await this._getLineBuffer(nextLine);
|
|
396
|
+
const actualNext = lineBuf.readUInt32LE(2);
|
|
397
|
+
|
|
398
|
+
const type = lineBuf[0] & 0xC0;
|
|
399
|
+
if (type === (0b00 << 6) || type === (0b11 << 6)) { // remove from index mapping
|
|
400
|
+
for (let i in this.indices) {
|
|
401
|
+
const key = this._getIndexKey(this._getFieldFromLine(lineBuf, i));
|
|
402
|
+
if (this.indices[i].get(key) === nextLine) this.indices[i].delete(key);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const clearedBuf = Buffer.alloc(this.lineLength);
|
|
407
|
+
clearedBuf[0] = (lineBuf[0] & 0x3F) | (0b10 << 6); // Preserve 14-bit flag, toggle 'Empty' state safely
|
|
408
|
+
clearedBuf.writeUInt32LE(this.lastEmptyLine, 2);
|
|
409
|
+
|
|
410
|
+
this.lastEmptyLine = nextLine;
|
|
411
|
+
await this._writeLineBuffer(nextLine, clearedBuf);
|
|
412
|
+
this._lineCache.delete(nextLine);
|
|
413
|
+
|
|
414
|
+
nextLine = actualNext;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// write back the last empty line
|
|
418
|
+
const lastEmptyLineBuf = Buffer.allocUnsafe(4);
|
|
419
|
+
lastEmptyLineBuf.writeUInt32LE(this.lastEmptyLine);
|
|
420
|
+
await this.jdb.handle.write(lastEmptyLineBuf, 0, 4, this.lastEmptyLineOffset);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
async * _readlines(start = 0, end, startLineOnly = true) {
|
|
424
|
+
let line = start;
|
|
425
|
+
const bufSize = this.options.scanBufferLines || (this.options.scanBufferLines = Math.ceil((this.options.scanBufferSize || 1048576) / this.lineLength));
|
|
426
|
+
let chunkLine = 0;
|
|
427
|
+
let chunk = null;
|
|
428
|
+
let chunkSize = 0;
|
|
429
|
+
|
|
430
|
+
while (true) {
|
|
431
|
+
if (end !== undefined && line >= end) return;
|
|
432
|
+
if (chunkLine === chunkSize) {
|
|
433
|
+
chunk = Buffer.allocUnsafe(bufSize * this.lineLength);
|
|
434
|
+
const { bytesRead } = await this.jdb.handle.read(chunk, 0, chunk.length, this.bodyOffset + line * this.lineLength);
|
|
435
|
+
chunkSize = bytesRead / this.lineLength;
|
|
436
|
+
if (bytesRead % this.lineLength !== 0) throw new Error('JDB (DBLE): File broken. Reading non-aligned chunk blocks.');
|
|
437
|
+
if (bytesRead === 0) return;
|
|
438
|
+
chunkLine = 0;
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const lineChunk = chunk.subarray(chunkLine * this.lineLength, (chunkLine + 1) * this.lineLength);
|
|
443
|
+
|
|
444
|
+
line++;
|
|
445
|
+
chunkLine++;
|
|
446
|
+
|
|
447
|
+
if (startLineOnly && ((lineChunk[0] & 0xC0) !== (0b00 << 6))) continue;
|
|
448
|
+
yield { chunk: lineChunk, line: line - 1 };
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
_parseLine(buf) {
|
|
453
|
+
const lineType = (buf[0] & 0xC0) >> 6;
|
|
454
|
+
const nextLine = buf.readUInt32LE(2);
|
|
455
|
+
const line = { type: lineType };
|
|
456
|
+
|
|
457
|
+
if (line.type === 0b00) { // normal start
|
|
458
|
+
line.nextLine = nextLine;
|
|
459
|
+
|
|
460
|
+
line.fields = {};
|
|
461
|
+
for (let i in this.fieldsMap) {
|
|
462
|
+
line.fields[i] = this._getFieldFromLine(buf, i);
|
|
463
|
+
}
|
|
464
|
+
} else if (line.type === 0b01) { // extended (this function does not parse extended lines)
|
|
465
|
+
line.nextLine = nextLine;
|
|
466
|
+
} else if (line.type === 0b10) { // empty
|
|
467
|
+
line.lastEmptyLine = nextLine;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
return line;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// get field from line
|
|
474
|
+
getField(line, field, skipQueue) {
|
|
475
|
+
return this._doTask(async () => {
|
|
476
|
+
return this._getFieldFromLine(await this._getLineBuffer(line), field);
|
|
477
|
+
}, skipQueue);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
getExt(line, skipQueue) {
|
|
481
|
+
return this._doTask(async () => {
|
|
482
|
+
let extBufs = [];
|
|
483
|
+
let lineBuf;
|
|
484
|
+
let nextLine = line;
|
|
485
|
+
let length;
|
|
486
|
+
let lengthRead = 0;
|
|
487
|
+
|
|
488
|
+
while (nextLine !== 0xFFFFFFFF) {
|
|
489
|
+
if (length === lengthRead) break;
|
|
490
|
+
|
|
491
|
+
lineBuf = await this._getLineBuffer(nextLine);
|
|
492
|
+
nextLine = lineBuf.readUInt32LE(2);
|
|
493
|
+
|
|
494
|
+
if (extBufs.length === 0) {
|
|
495
|
+
length = lineBuf.readUInt16LE(this.extOffset);
|
|
496
|
+
const extBufPart = lineBuf.subarray(this.extOffset + 2, Math.min(this.extOffset + 2 + length, lineBuf.length));
|
|
497
|
+
lengthRead += extBufPart.length;
|
|
498
|
+
extBufs.push(extBufPart);
|
|
499
|
+
} else {
|
|
500
|
+
const extBufPart = lineBuf.subarray(6, Math.min(6 + length - lengthRead, lineBuf.length));
|
|
501
|
+
lengthRead += extBufPart.length;
|
|
502
|
+
extBufs.push(extBufPart);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
return Buffer.concat(extBufs);
|
|
507
|
+
}, skipQueue);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
getLineByField(field, query, skipQueue) {
|
|
511
|
+
return this._doTask(async () => {
|
|
512
|
+
if (this.indices[field]) {
|
|
513
|
+
return this.indices[field].get(this._getIndexKey(query));
|
|
514
|
+
} else {
|
|
515
|
+
for await (let { chunk, line } of this._readlines()) {
|
|
516
|
+
let val = this._getFieldFromLine(chunk, field);
|
|
517
|
+
if (Buffer.isBuffer(val) && Buffer.isBuffer(query)) {
|
|
518
|
+
if (val.equals(query)) return line;
|
|
519
|
+
} else if (val === query) {
|
|
520
|
+
return line;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}, skipQueue);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
readLine(line, skipQueue) {
|
|
528
|
+
return this._doTask(async () => {
|
|
529
|
+
return this._parseLine(await this._getLineBuffer(line));
|
|
530
|
+
}, skipQueue);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
readLineByField(field, query, skipQueue) {
|
|
534
|
+
return this._doTask(async () => {
|
|
535
|
+
const line = await this.getLineByField(field, query, true);
|
|
536
|
+
if (line === undefined) return;
|
|
537
|
+
return this.readLine(line, true);
|
|
538
|
+
}, skipQueue);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
findLine(fn, skipQueue) {
|
|
542
|
+
return this._doTask(async () => {
|
|
543
|
+
for await (let { chunk, line } of this._readlines()) {
|
|
544
|
+
const parsed = this._parseLine(chunk);
|
|
545
|
+
if (await fn(parsed)) return line;
|
|
546
|
+
}
|
|
547
|
+
}, skipQueue);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
forEachLine(fn, start, to, skipQueue) {
|
|
551
|
+
return this._doTask(async () => {
|
|
552
|
+
for await (let { chunk, line } of this._readlines(start, to)) {
|
|
553
|
+
const parsed = this._parseLine(chunk);
|
|
554
|
+
await fn(parsed, line);
|
|
555
|
+
}
|
|
556
|
+
}, skipQueue);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
setLine(line, fields = {}, skipQueue) {
|
|
560
|
+
return this._doTask(async () => {
|
|
561
|
+
if (typeof line === 'number') { // edit a line
|
|
562
|
+
const buf = await this._getLineBuffer(line);
|
|
563
|
+
buf[0] = (buf[0] & 0x3F) | (0b00 << 6); // set type appropriately
|
|
564
|
+
|
|
565
|
+
// check index mappings accurately
|
|
566
|
+
const oldValues = {};
|
|
567
|
+
for (let i in this.indices) {
|
|
568
|
+
if (fields[i] !== undefined) oldValues[i] = this._getFieldFromLine(buf, i);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
this._setFieldsToLine(buf, fields);
|
|
572
|
+
|
|
573
|
+
for (let i in this.indices) {
|
|
574
|
+
if (fields[i] !== undefined) {
|
|
575
|
+
const oldVal = oldValues[i];
|
|
576
|
+
const newVal = this._getFieldFromLine(buf, i);
|
|
577
|
+
|
|
578
|
+
let isDifferent = oldVal !== newVal;
|
|
579
|
+
if (Buffer.isBuffer(oldVal) && Buffer.isBuffer(newVal)) {
|
|
580
|
+
isDifferent = !oldVal.equals(newVal);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
if (isDifferent) {
|
|
584
|
+
const oldKey = this._getIndexKey(oldVal);
|
|
585
|
+
if (this.indices[i].get(oldKey) === line) this.indices[i].delete(oldKey);
|
|
586
|
+
this.indices[i].set(this._getIndexKey(newVal), line);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
await this._writeLineBuffer(line, buf);
|
|
592
|
+
return line;
|
|
593
|
+
} else { // create a new line
|
|
594
|
+
return await this.appendLine(fields, true);
|
|
595
|
+
}
|
|
596
|
+
}, skipQueue);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
setLineByField(field, query, fields = {}, skipQueue) {
|
|
600
|
+
return this._doTask(async () => {
|
|
601
|
+
const line = await this.getLineByField(field, query, true);
|
|
602
|
+
if (fields[field] === undefined) fields[field] = query;
|
|
603
|
+
await this.setLine(line, fields, true);
|
|
604
|
+
}, skipQueue);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
appendLine(fields = {}, skipQueue) {
|
|
608
|
+
return this._doTask(async () => {
|
|
609
|
+
const buf = Buffer.alloc(this.lineLength);
|
|
610
|
+
buf[0] = (buf[0] & 0x3F) | (0b00 << 6); // set type
|
|
611
|
+
|
|
612
|
+
// set relatives
|
|
613
|
+
for (let i in this.relatives) {
|
|
614
|
+
fields[i] = fields[i] ?? this._getDefaultByField(i, this.relatives[i]);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
this._setFieldsToLine(buf, fields);
|
|
618
|
+
buf.writeUInt32LE(0xFFFFFFFF, 2);
|
|
619
|
+
|
|
620
|
+
return await this._appendLineBuffer(buf);
|
|
621
|
+
}, skipQueue);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
deleteLine(line, releaseSpace, skipQueue) {
|
|
625
|
+
return this._doTask(async () => {
|
|
626
|
+
if (releaseSpace) {
|
|
627
|
+
await this._clearLinesFrom(line);
|
|
628
|
+
} else {
|
|
629
|
+
const buf = await this._getLineBuffer(line);
|
|
630
|
+
if ((buf[0] & 0xC0) === (0b00 << 6)) {
|
|
631
|
+
for (let i in this.indices) {
|
|
632
|
+
this.indices[i].delete(this._getIndexKey(this._getFieldFromLine(buf, i)));
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
buf[0] = (buf[0] & 0x3F) | (0b11 << 6); // preserve flags safely, set deleted type
|
|
636
|
+
await this._writeLineBuffer(line, buf);
|
|
637
|
+
}
|
|
638
|
+
}, skipQueue);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
deleteLineByField(field, query, releaseSpace, skipQueue) {
|
|
642
|
+
return this._doTask(async () => {
|
|
643
|
+
const line = await this.getLineByField(field, query, true);
|
|
644
|
+
if (line !== undefined) await this.deleteLine(line, releaseSpace, true);
|
|
645
|
+
}, skipQueue);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
cleanUp(skipQueue) {
|
|
649
|
+
return this._doTask(async () => {
|
|
650
|
+
for await (let { chunk, line } of this._readlines(0, undefined, false)) {
|
|
651
|
+
if ((chunk[0] & 0xC0) === (0b11 << 6)) await this._clearLinesFrom(line);
|
|
652
|
+
}
|
|
653
|
+
}, skipQueue);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
setExt(line, ext = Buffer.alloc(0), skipQueue) {
|
|
657
|
+
return this._doTask(async () => {
|
|
658
|
+
if (!Buffer.isBuffer(ext)) ext = Buffer.from(ext);
|
|
659
|
+
|
|
660
|
+
const linesNeeded = ext.length <= this.extBaseLength - 2 ? 1 : 1 + Math.ceil((ext.length - this.extBaseLength + 2) / (this.lineLength - 6));
|
|
661
|
+
let offset = this.extBaseLength - 2;
|
|
662
|
+
let lineBuf = await this._getLineBuffer(line);
|
|
663
|
+
await this._clearLinesFrom(lineBuf.readUInt32LE(2));
|
|
664
|
+
|
|
665
|
+
// write length and first line
|
|
666
|
+
let nextLine = await this._getLastEmptyLine();
|
|
667
|
+
lineBuf.writeUInt32LE((linesNeeded > 1) ? nextLine : 0xFFFFFFFF, 2);
|
|
668
|
+
lineBuf.writeUInt16LE(ext.length, this.extOffset);
|
|
669
|
+
|
|
670
|
+
const baseWriteLen = Math.min(ext.length, this.extBaseLength - 2);
|
|
671
|
+
ext.copy(lineBuf, this.extOffset + 2, 0, baseWriteLen);
|
|
672
|
+
lineBuf.fill(0, this.extOffset + 2 + baseWriteLen, this.extOffset + this.extBaseLength); // clean trailing ghosts
|
|
673
|
+
|
|
674
|
+
await this._writeLineBuffer(line, lineBuf);
|
|
675
|
+
|
|
676
|
+
for (let i = 1; i < linesNeeded; i++) {
|
|
677
|
+
nextLine = await this._getLastEmptyLine(nextLine);
|
|
678
|
+
const extBuf = Buffer.alloc(this.lineLength);
|
|
679
|
+
extBuf[0] = (extBuf[0] & 0x3F) | (0b01 << 6); // extended type toggle preserving 14-bit flag bounds
|
|
680
|
+
extBuf.writeUInt32LE((linesNeeded - 1 > i) ? nextLine : 0xFFFFFFFF, 2);
|
|
681
|
+
|
|
682
|
+
const partLen = Math.min(ext.length - offset, this.lineLength - 6);
|
|
683
|
+
ext.copy(extBuf, 6, offset, offset + partLen);
|
|
684
|
+
offset += partLen;
|
|
685
|
+
|
|
686
|
+
await this._appendLineBuffer(extBuf);
|
|
687
|
+
}
|
|
688
|
+
}, skipQueue);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
setExtByField(field, query, ext, skipQueue) {
|
|
692
|
+
return this._doTask(async () => {
|
|
693
|
+
const line = await this.getLineByField(field, query, true);
|
|
694
|
+
if (line !== undefined) await this.setExt(line, ext, true);
|
|
695
|
+
else throw new Error(`JDB (DBLE): Line not found by field "${field}".`);
|
|
696
|
+
}, skipQueue);
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
async sync(skipQueue) {
|
|
700
|
+
return this._doTask(async () => {
|
|
701
|
+
await this.jdb.sync();
|
|
702
|
+
}, skipQueue);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
async datasync(skipQueue) {
|
|
706
|
+
return this._doTask(async () => {
|
|
707
|
+
await this.jdb.datasync();
|
|
708
|
+
}, skipQueue);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// DBLE field
|
|
713
|
+
class DBLEField {
|
|
714
|
+
constructor(length = 0, type = 'null', name = 'null', isKey = false, isRelative = false) {
|
|
715
|
+
this.length = length;
|
|
716
|
+
this.type = type;
|
|
717
|
+
this.name = name;
|
|
718
|
+
this.isKey = isKey;
|
|
719
|
+
this.isRelative = isRelative;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
parse(buf, offset) {
|
|
723
|
+
return buf.subarray(offset, offset + this.length);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
write(data = Buffer.alloc(this.length), buf = Buffer.alloc(this.length), offset = 0) {
|
|
727
|
+
data.copy(buf, offset, 0, this.length);
|
|
728
|
+
return buf;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
default(relative) {
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
toDefinitionBuffer(isLast) {
|
|
736
|
+
const typeBuf = Buffer.from(this.type, 'utf8');
|
|
737
|
+
const nameBuf = Buffer.from(this.name, 'utf8');
|
|
738
|
+
|
|
739
|
+
// safety check
|
|
740
|
+
if (typeBuf.length > 255) throw new Error('Field type length must not over 255 bytes.');
|
|
741
|
+
if (nameBuf.length > 255) throw new Error('Field name length must not over 255 bytes.');
|
|
742
|
+
|
|
743
|
+
const definitionBuf = Buffer.alloc(6 + typeBuf.length + nameBuf.length + (this.isRelative ? this.length : 0));
|
|
744
|
+
|
|
745
|
+
definitionBuf.writeUInt16BE(
|
|
746
|
+
(isLast ? 1 : 0) | // isLast
|
|
747
|
+
(this.isKey ? 1 << 1 : 0) | // isKey
|
|
748
|
+
(this.isRelative ? 1 << 2 : 0) // isRelative
|
|
749
|
+
); // flag
|
|
750
|
+
definitionBuf.writeUInt16LE(this.length, 2); // field length
|
|
751
|
+
definitionBuf.writeUInt8(typeBuf.length, 4); // field type length
|
|
752
|
+
typeBuf.copy(definitionBuf, 5); // field type
|
|
753
|
+
definitionBuf.writeUInt8(nameBuf.length, 5 + typeBuf.length); // field name length
|
|
754
|
+
nameBuf.copy(definitionBuf, 6 + typeBuf.length); // field name
|
|
755
|
+
|
|
756
|
+
return definitionBuf;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// DBLE field types
|
|
761
|
+
|
|
762
|
+
// Int8 (1 byte)
|
|
763
|
+
class DBLEInt8Field extends DBLEField {
|
|
764
|
+
constructor(name, isKey, isRelative) {
|
|
765
|
+
super(1, 'Int8', name, isKey, isRelative);
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
parse(buf, offset) {
|
|
769
|
+
return buf.readInt8(offset);
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
write(data, buf = Buffer.alloc(this.length), offset = 0) {
|
|
773
|
+
buf.writeInt8(data, offset);
|
|
774
|
+
return buf;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
default(relative = -1) {
|
|
778
|
+
return relative + 1;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
// UInt8 (1 byte)
|
|
783
|
+
class DBLEUInt8Field extends DBLEField {
|
|
784
|
+
constructor(name, isKey, isRelative) {
|
|
785
|
+
super(1, 'UInt8', name, isKey, isRelative);
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
parse(buf, offset) {
|
|
789
|
+
return buf.readUInt8(offset);
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
write(data, buf = Buffer.alloc(this.length), offset = 0) {
|
|
793
|
+
buf.writeUInt8(data, offset);
|
|
794
|
+
return buf;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
default(relative = -1) {
|
|
798
|
+
return relative + 1;
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
// Int16 (2 bytes)
|
|
803
|
+
class DBLEInt16Field extends DBLEField {
|
|
804
|
+
constructor(name, isKey, isRelative) {
|
|
805
|
+
super(2, 'Int16', name, isKey, isRelative);
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
parse(buf, offset) {
|
|
809
|
+
return buf.readInt16LE(offset);
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
write(data, buf = Buffer.alloc(this.length), offset = 0) {
|
|
813
|
+
buf.writeInt16LE(data, offset);
|
|
814
|
+
return buf;
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
default(relative = -1) {
|
|
818
|
+
return relative + 1;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
// UInt16 (2 bytes)
|
|
823
|
+
class DBLEUInt16Field extends DBLEField {
|
|
824
|
+
constructor(name, isKey, isRelative) {
|
|
825
|
+
super(2, 'UInt16', name, isKey, isRelative);
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
parse(buf, offset) {
|
|
829
|
+
return buf.readUInt16LE(offset);
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
write(data, buf = Buffer.alloc(this.length), offset = 0) {
|
|
833
|
+
buf.writeUInt16LE(data, offset);
|
|
834
|
+
return buf;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
default(relative = -1) {
|
|
838
|
+
return relative + 1;
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
// Int32 (4 bytes)
|
|
843
|
+
class DBLEInt32Field extends DBLEField {
|
|
844
|
+
constructor(name, isKey, isRelative) {
|
|
845
|
+
super(4, 'Int32', name, isKey, isRelative);
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
parse(buf, offset) {
|
|
849
|
+
return buf.readInt32LE(offset);
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
write(data, buf = Buffer.alloc(this.length), offset = 0) {
|
|
853
|
+
buf.writeInt32LE(data, offset);
|
|
854
|
+
return buf;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
default(relative = -1) {
|
|
858
|
+
return relative + 1;
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
// UInt32 (4 bytes)
|
|
863
|
+
class DBLEUInt32Field extends DBLEField {
|
|
864
|
+
constructor(name, isKey, isRelative) {
|
|
865
|
+
super(4, 'UInt32', name, isKey, isRelative);
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
parse(buf, offset) {
|
|
869
|
+
return buf.readUInt32LE(offset);
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
write(data, buf = Buffer.alloc(this.length), offset = 0) {
|
|
873
|
+
buf.writeUInt32LE(data, offset);
|
|
874
|
+
return buf;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
default(relative = -1) {
|
|
878
|
+
return relative + 1;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
// BigInt64 (8 bytes)
|
|
883
|
+
class DBLEBigInt64Field extends DBLEField {
|
|
884
|
+
constructor(name, isKey, isRelative) {
|
|
885
|
+
super(8, 'BigInt64', name, isKey, isRelative);
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
parse(buf, offset) {
|
|
889
|
+
return buf.readBigInt64LE(offset);
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
write(data, buf = Buffer.alloc(this.length), offset = 0) {
|
|
893
|
+
buf.writeBigInt64LE(BigInt(data), offset);
|
|
894
|
+
return buf;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
default(relative = -1n) {
|
|
898
|
+
return relative + 1n;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
// BigUInt64 (8 bytes)
|
|
903
|
+
class DBLEBigUInt64Field extends DBLEField {
|
|
904
|
+
constructor(name, isKey, isRelative) {
|
|
905
|
+
super(8, 'BigUInt64', name, isKey, isRelative);
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
parse(buf, offset) {
|
|
909
|
+
return buf.readBigUInt64LE(offset);
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
write(data, buf = Buffer.alloc(this.length), offset = 0) {
|
|
913
|
+
buf.writeBigUInt64LE(BigInt(data), offset);
|
|
914
|
+
return buf;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
default(relative = -1n) {
|
|
918
|
+
return relative + 1n;
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
// Float (4 bytes)
|
|
923
|
+
class DBLEFloatField extends DBLEField {
|
|
924
|
+
constructor(name, isKey, isRelative) {
|
|
925
|
+
super(4, 'Float', name, isKey, isRelative);
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
parse(buf, offset) {
|
|
929
|
+
return buf.readFloatLE(offset);
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
write(data, buf = Buffer.alloc(this.length), offset = 0) {
|
|
933
|
+
buf.writeFloatLE(data, offset);
|
|
934
|
+
return buf;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
default(relative = -1) {
|
|
938
|
+
return relative + 1;
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
// Double (8 bytes)
|
|
943
|
+
class DBLEDoubleField extends DBLEField {
|
|
944
|
+
constructor(name, isKey, isRelative) {
|
|
945
|
+
super(8, 'Double', name, isKey, isRelative);
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
parse(buf, offset) {
|
|
949
|
+
return buf.readDoubleLE(offset);
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
write(data, buf = Buffer.alloc(this.length), offset = 0) {
|
|
953
|
+
buf.writeDoubleLE(data, offset);
|
|
954
|
+
return buf;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
default(relative = -1) {
|
|
958
|
+
return relative + 1;
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
// String (2 + n Bytes)
|
|
963
|
+
class DBLEStringField extends DBLEField {
|
|
964
|
+
constructor(maxLength, name, isKey, isRelative) {
|
|
965
|
+
super(2 + maxLength, 'String', name, isKey, isRelative);
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
parse(buf, offset) {
|
|
969
|
+
const len = buf.readUInt16LE(offset);
|
|
970
|
+
return buf.toString('utf8', offset + 2, offset + 2 + len);
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
write(data, buf = Buffer.allocUnsafe(this.length), offset = 0) {
|
|
974
|
+
const strBuf = Buffer.from(data, 'utf8');
|
|
975
|
+
const len = Math.min(strBuf.length, this.length - 2);
|
|
976
|
+
|
|
977
|
+
buf.writeUInt16LE(len, offset);
|
|
978
|
+
strBuf.copy(buf, offset + 2, 0, len);
|
|
979
|
+
buf.fill(0, offset + 2 + len, offset + this.length);
|
|
980
|
+
return buf;
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
// Buffer (2 + n Bytes)
|
|
985
|
+
class DBLEBufferField extends DBLEField {
|
|
986
|
+
constructor(maxLength, name, isKey, isRelative) {
|
|
987
|
+
super(2 + maxLength, 'Buffer', name, isKey, isRelative);
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
parse(buf, offset) {
|
|
991
|
+
const len = buf.readUInt16LE(offset);
|
|
992
|
+
return buf.subarray(offset + 2, offset + 2 + len);
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
write(data, buf = Buffer.allocUnsafe(this.length), offset = 0) {
|
|
996
|
+
const source = Buffer.isBuffer(data) ? data : Buffer.from(data);
|
|
997
|
+
const len = Math.min(source.length, this.length - 2);
|
|
998
|
+
|
|
999
|
+
buf.writeUInt16LE(len, offset);
|
|
1000
|
+
source.copy(buf, offset + 2, 0, len);
|
|
1001
|
+
buf.fill(0, offset + 2 + len, offset + this.length);
|
|
1002
|
+
return buf;
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
class DBLEAnyField extends DBLEField {
|
|
1007
|
+
constructor(length, name, isKey, isRelative) {
|
|
1008
|
+
super(length, 'Any', name, isKey, isRelative);
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
// dble default types
|
|
1013
|
+
const defaultDBLETypes = {
|
|
1014
|
+
Int8: DBLEInt8Field, i8: DBLEInt8Field,
|
|
1015
|
+
UInt8: DBLEUInt8Field, u8: DBLEUInt8Field, byte: DBLEUInt8Field,
|
|
1016
|
+
Int16: DBLEInt16Field, i16: DBLEInt16Field,
|
|
1017
|
+
UInt16: DBLEUInt16Field, u16: DBLEUInt16Field,
|
|
1018
|
+
Int32: DBLEInt32Field, i32: DBLEInt32Field,
|
|
1019
|
+
UInt32: DBLEUInt32Field, u32: DBLEUInt32Field,
|
|
1020
|
+
BigInt64: DBLEBigInt64Field, i64: DBLEBigInt64Field,
|
|
1021
|
+
BigUInt64: DBLEBigUInt64Field, u64: DBLEBigUInt64Field,
|
|
1022
|
+
Float: DBLEFloatField, f32: DBLEFloatField,
|
|
1023
|
+
Double: DBLEDoubleField, f64: DBLEDoubleField,
|
|
1024
|
+
String: DBLEStringField, str: DBLEStringField,
|
|
1025
|
+
Buffer: DBLEBufferField, buf: DBLEBufferField,
|
|
1026
|
+
Any: DBLEAnyField, any: DBLEBufferField
|
|
1027
|
+
};
|
|
1028
|
+
|
|
1029
|
+
// export
|
|
1030
|
+
module.exports = {
|
|
1031
|
+
DBLEFile,
|
|
1032
|
+
DBLEField,
|
|
1033
|
+
DBLEInt8Field, DBLEUInt8Field, DBLEInt16Field, DBLEUInt16Field, DBLEInt32Field, DBLEUInt32Field,
|
|
1034
|
+
DBLEBigInt64Field, DBLEBigUInt64Field,
|
|
1035
|
+
DBLEFloatField, DBLEDoubleField,
|
|
1036
|
+
DBLEStringField, DBLEBufferField,
|
|
1037
|
+
DBLEAnyField,
|
|
1038
|
+
defaultDBLETypes
|
|
1039
|
+
};
|