@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/src/index.js ADDED
@@ -0,0 +1,14 @@
1
+ /*
2
+ @jnode/db
3
+
4
+ Simple database package for Node.js.
5
+
6
+ by JustApple
7
+ */
8
+
9
+ // export
10
+ module.exports = {
11
+ jdb: require('./jdb.js'),
12
+ dble: require('./dble.js'),
13
+ ljson: require('./ljson.js')
14
+ }
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
+ };