@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/index.js
ADDED
package/src/jdb.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/*
|
|
2
|
+
@jnode/db/jdb.js
|
|
3
|
+
|
|
4
|
+
Simple database package for Node.js.
|
|
5
|
+
|
|
6
|
+
JDB (.jdb) is the format of all the database files.
|
|
7
|
+
|
|
8
|
+
by JustApple
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// dependencies
|
|
12
|
+
const fs = require('fs/promises');
|
|
13
|
+
|
|
14
|
+
// base jdb file manager
|
|
15
|
+
class JDBFile {
|
|
16
|
+
constructor(handle) {
|
|
17
|
+
this.handle = handle;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static async load(path) {
|
|
21
|
+
const jdb = new JDBFile(await fs.open(path, 'r+'));
|
|
22
|
+
await jdb._loadHead();
|
|
23
|
+
|
|
24
|
+
return jdb;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
static async create(path, options = {}) {
|
|
28
|
+
const jdb = new JDBFile(await fs.open(path, 'wx+'));
|
|
29
|
+
await jdb._init(options);
|
|
30
|
+
|
|
31
|
+
return jdb;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
static async forceCreate(path, options = {}) {
|
|
35
|
+
const jdb = new JDBFile(await fs.open(path, 'w+'));
|
|
36
|
+
await jdb._init(options);
|
|
37
|
+
|
|
38
|
+
return jdb;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// init the file
|
|
42
|
+
async _init(options = {}) {
|
|
43
|
+
const headBuf = Buffer.alloc(16);
|
|
44
|
+
headBuf.writeUInt32BE(0x4A444244, 0); // magic string
|
|
45
|
+
headBuf[4] = 1; // jdb file version
|
|
46
|
+
headBuf.writeUInt32BE(this.dbType = (options.dbType || 0), 8); // db type
|
|
47
|
+
headBuf[12] = this.dbVersion = (options.dbVersion || 0); // db version
|
|
48
|
+
await this.handle.write(headBuf, 0, 16, 0);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// load and verify the file head
|
|
52
|
+
async _loadHead() {
|
|
53
|
+
const headBuf = Buffer.allocUnsafe(16);
|
|
54
|
+
const { bytesRead } = await this.handle.read(headBuf, 0, 16, 0);
|
|
55
|
+
|
|
56
|
+
if (bytesRead !== 16) throw new Error('JDB: File length not enough to be a JDB file.'); // basic length check
|
|
57
|
+
if (headBuf.readUInt32BE(0) !== 0x4A444244) throw new Error('JDB: Magic string doesn\'t match "JDBD".'); // magic string
|
|
58
|
+
if (headBuf[4] !== 1) throw new Error('JDB: Unsupported JDB file version.'); // file version
|
|
59
|
+
|
|
60
|
+
// load db type and version
|
|
61
|
+
this.dbType = headBuf.readUInt32BE(8);
|
|
62
|
+
this.dbVersion = headBuf[12];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
close() {
|
|
66
|
+
return this.handle.close();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
sync() {
|
|
70
|
+
return this.handle.sync();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
datasync() {
|
|
74
|
+
return this.handle.datasync();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// export
|
|
79
|
+
module.exports = {
|
|
80
|
+
JDBFile
|
|
81
|
+
};
|
package/src/ljson.js
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/*
|
|
2
|
+
@jnode/db/ljson.js
|
|
3
|
+
|
|
4
|
+
Simple database package for Node.js.
|
|
5
|
+
|
|
6
|
+
LineJSON (LJSON) is a simple new line-splitted JSON array database format with some special features.
|
|
7
|
+
Some place also called this JSONL or NDJSON.
|
|
8
|
+
This format is best for small, append-only data.
|
|
9
|
+
This format follows text-based JLD (JustLineData) format instead of binary-based JDB format.
|
|
10
|
+
|
|
11
|
+
by JustApple
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
// dependencies
|
|
15
|
+
const fs = require('fs/promises');
|
|
16
|
+
|
|
17
|
+
class LJSONFile {
|
|
18
|
+
constructor(path, data, options = {}) {
|
|
19
|
+
this.path = path;
|
|
20
|
+
this.options = options;
|
|
21
|
+
|
|
22
|
+
this._loadData(data);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
static async forceCreate(file, options) {
|
|
26
|
+
const data = '@ JLD 01 LJSO 01\n';
|
|
27
|
+
await fs.writeFile(file, data);
|
|
28
|
+
return new LJSONFile(file, data, options);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
static async load(file, options = {}) {
|
|
32
|
+
let data;
|
|
33
|
+
try {
|
|
34
|
+
data = (await fs.readFile(file)).toString('utf8');
|
|
35
|
+
} catch {
|
|
36
|
+
data = '@ JLD 01 LJSO 01\n';
|
|
37
|
+
await fs.writeFile(file, data);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// check header
|
|
41
|
+
if (!data.startsWith('@ JLD 01 LJSO 01\n')) {
|
|
42
|
+
throw new Error('JDB (LJSON): File header incorrect.')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const ljson = new LJSONFile(file, data, options);
|
|
46
|
+
|
|
47
|
+
if (!options.disableCleanupOnStart) {
|
|
48
|
+
await ljson.cleanUp();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return ljson;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
_loadData(data) {
|
|
55
|
+
const lines = data.split('\n');
|
|
56
|
+
this.lines = [];
|
|
57
|
+
this._requireCleanup = false;
|
|
58
|
+
|
|
59
|
+
for (let i of lines) {
|
|
60
|
+
if (i.startsWith('@')) continue; // header line
|
|
61
|
+
else if (i.startsWith('//')) this._requireCleanup = true; // comment line
|
|
62
|
+
else if (i === '') this._requireCleanup = true; // empty line
|
|
63
|
+
else if (i.startsWith('+ ')) { // update line
|
|
64
|
+
this._requireCleanup = true;
|
|
65
|
+
const split = i.indexOf(' ', 2);
|
|
66
|
+
const index = i.substring(2, split);
|
|
67
|
+
const json = JSON.parse(i.substring(split));
|
|
68
|
+
|
|
69
|
+
// check type
|
|
70
|
+
const lineType = _typeof(this.lines[index]);
|
|
71
|
+
if (lineType === _typeof(json)) {
|
|
72
|
+
switch (lineType) {
|
|
73
|
+
case 'number':
|
|
74
|
+
case 'string':
|
|
75
|
+
this.lines[index] += json;
|
|
76
|
+
break;
|
|
77
|
+
case 'boolean':
|
|
78
|
+
this.lines[index] = json !== this.lines[index];
|
|
79
|
+
break;
|
|
80
|
+
case 'array':
|
|
81
|
+
this.lines[index].push(...json);
|
|
82
|
+
break;
|
|
83
|
+
case 'object':
|
|
84
|
+
Object.assign(this.lines[index], json);
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
} else if (i.startsWith('= ')) { // overwrite line
|
|
89
|
+
this._requireCleanup = true;
|
|
90
|
+
const split = i.indexOf(' ', 2);
|
|
91
|
+
const index = i.substring(2, split);
|
|
92
|
+
const json = JSON.parse(i.substring(split));
|
|
93
|
+
|
|
94
|
+
this.lines[index] = json;
|
|
95
|
+
} else if (i.startsWith('- ')) { // delete line
|
|
96
|
+
this._requireCleanup = true;
|
|
97
|
+
const index = i.substring(2);
|
|
98
|
+
|
|
99
|
+
this.lines.splice(index, 1);
|
|
100
|
+
} else { // data
|
|
101
|
+
this.lines.push(JSON.parse(i));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// clean up the file
|
|
107
|
+
async cleanUp(force) {
|
|
108
|
+
if (this._requireCleanup || force) {
|
|
109
|
+
await fs.writeFile(this.path + '.tmp', '@ JLD 01 LJSO 01\n' + this.lines.map(JSON.stringify).join('\n') + '\n');
|
|
110
|
+
await fs.rename(this.path + '.tmp', this.path);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
update(index, json) {
|
|
115
|
+
// check type
|
|
116
|
+
const lineType = _typeof(this.lines[index]);
|
|
117
|
+
if (lineType === _typeof(json)) {
|
|
118
|
+
this._requireCleanup = true;
|
|
119
|
+
|
|
120
|
+
switch (lineType) {
|
|
121
|
+
case 'number':
|
|
122
|
+
case 'string':
|
|
123
|
+
this.lines[index] += json;
|
|
124
|
+
break;
|
|
125
|
+
case 'boolean':
|
|
126
|
+
this.lines[index] = json !== this.lines[index];
|
|
127
|
+
break;
|
|
128
|
+
case 'array':
|
|
129
|
+
this.lines[index].push(...json);
|
|
130
|
+
break;
|
|
131
|
+
case 'object':
|
|
132
|
+
Object.assign(this.lines[index], json);
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return fs.appendFile(this.path, `+ ${index} ${JSON.stringify(json)}\n`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
overwrite(index, json) {
|
|
141
|
+
this._requireCleanup = true;
|
|
142
|
+
this.lines[index] = json;
|
|
143
|
+
return fs.appendFile(this.path, `= ${index} ${JSON.stringify(json)}\n`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
delete(index) {
|
|
147
|
+
this._requireCleanup = true;
|
|
148
|
+
this.lines.splice(index, 1);
|
|
149
|
+
return fs.appendFile(this.path, `- ${index}\n`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
push(json) {
|
|
153
|
+
this.lines.push(json);
|
|
154
|
+
return fs.appendFile(this.path, `${JSON.stringify(json)}\n`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// get type with array support
|
|
159
|
+
function _typeof(value) {
|
|
160
|
+
return Array.isArray(value) ? 'array' : typeof value;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
module.exports = {
|
|
164
|
+
LJSONFile
|
|
165
|
+
};
|