inibase 1.0.0-rc.56 → 1.0.0-rc.58
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/README.md +37 -47
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +98 -0
- package/dist/file.d.ts +0 -1
- package/dist/file.js +68 -70
- package/dist/index.d.ts +4 -2
- package/dist/index.js +141 -156
- package/package.json +12 -17
- package/dist/file.thread.d.ts +0 -1
- package/dist/file.thread.js +0 -5
- package/dist/index.thread.d.ts +0 -1
- package/dist/index.thread.js +0 -6
package/README.md
CHANGED
|
@@ -10,10 +10,11 @@
|
|
|
10
10
|
|
|
11
11
|
- **Lightweight** 🪶
|
|
12
12
|
- **Minimalist** :white_circle: (but powerful)
|
|
13
|
-
- **TypeScript** :large_blue_diamond:
|
|
13
|
+
- **100% TypeScript** :large_blue_diamond:
|
|
14
14
|
- **Super-Fast** :zap: (built-in caching system)
|
|
15
|
+
- **ATOMIC** :lock: File lock for writing
|
|
15
16
|
- **Built-in form-validation** included :sunglasses:
|
|
16
|
-
- **Suitable for large data** :page_with_curl: (tested with
|
|
17
|
+
- **Suitable for large data** :page_with_curl: (tested with 4M records)
|
|
17
18
|
- **Support Compression** :eight_spoked_asterisk: (using built-in nodejs zlib)
|
|
18
19
|
- **Support Table Joins** :link:
|
|
19
20
|
- **Low memory-usage** :chart_with_downwards_trend: (3-5mb)
|
|
@@ -25,7 +26,7 @@
|
|
|
25
26
|
|
|
26
27
|
```js
|
|
27
28
|
import Inibase from "inibase";
|
|
28
|
-
const db = new Inibase("
|
|
29
|
+
const db = new Inibase("databaseName");
|
|
29
30
|
|
|
30
31
|
// Get all items from "user" table
|
|
31
32
|
const users = await db.get("user");
|
|
@@ -54,6 +55,21 @@ If you like Inibase, please sponsor: [GitHub Sponsors](https://github.com/sponso
|
|
|
54
55
|
|
|
55
56
|
To simplify the idea, each database has tables, each table has columns, each column will be stored in a seperated file. When **POST**ing new data, it will be appended to the _head_ of each file as new line. When **GET**ing data, the file will be readed line-by-line so it can handle large data (without consuming a lot of resources), when **PUT**ing(updating) in a specific column, only one file will be opened and updated
|
|
56
57
|
|
|
58
|
+
## Config (.env)
|
|
59
|
+
|
|
60
|
+
The `.env` file supports the following parameters (make sure to run commands with flag --env-file=.env)
|
|
61
|
+
|
|
62
|
+
```ini
|
|
63
|
+
# Auto generated secret key, will be using for encrypting the IDs
|
|
64
|
+
INIBASE_SECRET=
|
|
65
|
+
|
|
66
|
+
INIBASE_COMPRESSION=true
|
|
67
|
+
INIBASE_CACHE=true
|
|
68
|
+
|
|
69
|
+
# Prepend new items to the beginning of file
|
|
70
|
+
INIBASE_REVERSE=true
|
|
71
|
+
```
|
|
72
|
+
|
|
57
73
|
## Benchmark
|
|
58
74
|
|
|
59
75
|
### Bulk
|
|
@@ -76,7 +92,6 @@ To simplify the idea, each database has tables, each table has columns, each col
|
|
|
76
92
|
|
|
77
93
|
Ps: Testing by default with `user` table, with username, email, password fields _so results include password encryption process_
|
|
78
94
|
|
|
79
|
-
|
|
80
95
|
## Roadmap
|
|
81
96
|
|
|
82
97
|
- [x] Actions:
|
|
@@ -84,7 +99,7 @@ Ps: Testing by default with `user` table, with username, email, password fields
|
|
|
84
99
|
- [x] Pagination
|
|
85
100
|
- [x] Criteria
|
|
86
101
|
- [x] Columns
|
|
87
|
-
- [x]
|
|
102
|
+
- [x] Sort (using UNIX commands)
|
|
88
103
|
- [x] POST
|
|
89
104
|
- [x] PUT
|
|
90
105
|
- [x] DELETE
|
|
@@ -126,7 +141,7 @@ Ps: Testing by default with `user` table, with username, email, password fields
|
|
|
126
141
|
|
|
127
142
|
```js
|
|
128
143
|
import Inibase from "inibase";
|
|
129
|
-
const db = new Inibase("/
|
|
144
|
+
const db = new Inibase("/databaseName");
|
|
130
145
|
|
|
131
146
|
const user_schema = [
|
|
132
147
|
{
|
|
@@ -271,7 +286,7 @@ Link two tables: "product" with "user"
|
|
|
271
286
|
|
|
272
287
|
```js
|
|
273
288
|
import Inibase from "inibase";
|
|
274
|
-
const db = new Inibase("/
|
|
289
|
+
const db = new Inibase("/databaseName");
|
|
275
290
|
|
|
276
291
|
const product_schema = [
|
|
277
292
|
{
|
|
@@ -337,7 +352,7 @@ const product = await db.post("product", product_data);
|
|
|
337
352
|
|
|
338
353
|
```js
|
|
339
354
|
import Inibase from "inibase";
|
|
340
|
-
const db = new Inibase("/
|
|
355
|
+
const db = new Inibase("/databaseName");
|
|
341
356
|
|
|
342
357
|
// Get "user" by id
|
|
343
358
|
const user = await db.get("user", "1d88385d4b1581f8fb059334dec30f4c");
|
|
@@ -415,7 +430,7 @@ const users = await db.get("user", undefined, {
|
|
|
415
430
|
|
|
416
431
|
```js
|
|
417
432
|
import Inibase from "inibase";
|
|
418
|
-
const db = new Inibase("/
|
|
433
|
+
const db = new Inibase("/databaseName");
|
|
419
434
|
|
|
420
435
|
// set "isActive" to "false" for all items in table "user"
|
|
421
436
|
await db.put("user", { isActive: false });
|
|
@@ -434,7 +449,7 @@ await db.put("user", { isActive: false }, { isActive: true });
|
|
|
434
449
|
|
|
435
450
|
```js
|
|
436
451
|
import Inibase from "inibase";
|
|
437
|
-
const db = new Inibase("/
|
|
452
|
+
const db = new Inibase("/databaseName");
|
|
438
453
|
|
|
439
454
|
// delete all items in "user" table
|
|
440
455
|
await db.delete("user");
|
|
@@ -453,7 +468,7 @@ await db.put("user", { isActive: false });
|
|
|
453
468
|
|
|
454
469
|
```js
|
|
455
470
|
import Inibase from "inibase";
|
|
456
|
-
const db = new Inibase("/
|
|
471
|
+
const db = new Inibase("/databaseName");
|
|
457
472
|
|
|
458
473
|
// get the sum of column "age" in "user" table
|
|
459
474
|
await db.sum("user", "age");
|
|
@@ -469,7 +484,7 @@ await db.sum("user", ["age", ...], { isActive: false });
|
|
|
469
484
|
|
|
470
485
|
```js
|
|
471
486
|
import Inibase from "inibase";
|
|
472
|
-
const db = new Inibase("/
|
|
487
|
+
const db = new Inibase("/databaseName");
|
|
473
488
|
|
|
474
489
|
// get the biggest number of column "age" in "user" table
|
|
475
490
|
await db.max("user", "age");
|
|
@@ -485,7 +500,7 @@ await db.max("user", ["age", ...], { isActive: false });
|
|
|
485
500
|
|
|
486
501
|
```js
|
|
487
502
|
import Inibase from "inibase";
|
|
488
|
-
const db = new Inibase("/
|
|
503
|
+
const db = new Inibase("/databaseName");
|
|
489
504
|
|
|
490
505
|
// get the smallest number of column "age" in "user" table
|
|
491
506
|
await db.min("user", "age");
|
|
@@ -497,47 +512,22 @@ await db.min("user", ["age", ...], { isActive: false });
|
|
|
497
512
|
</details>
|
|
498
513
|
|
|
499
514
|
<details>
|
|
500
|
-
<summary>
|
|
515
|
+
<summary>SORT</summary>
|
|
501
516
|
|
|
502
517
|
```js
|
|
503
518
|
import Inibase from "inibase";
|
|
504
|
-
const db = new Inibase("/
|
|
505
|
-
|
|
506
|
-
// POST 10,000 USER
|
|
507
|
-
await Promise.all(
|
|
508
|
-
[...Array(10)]
|
|
509
|
-
.map((x, i) => i)
|
|
510
|
-
.map(
|
|
511
|
-
(_index) =>
|
|
512
|
-
db.createWorker("post", [
|
|
513
|
-
"user",
|
|
514
|
-
[...Array(1000)].map((_, i) => ({
|
|
515
|
-
username: `username_${i + 1}`,
|
|
516
|
-
email: `email_${i + 1}@test.com`,
|
|
517
|
-
password: `password_${i + 1}`,
|
|
518
|
-
})),
|
|
519
|
-
])
|
|
520
|
-
)
|
|
521
|
-
)
|
|
522
|
-
```
|
|
519
|
+
const db = new Inibase("/databaseName");
|
|
523
520
|
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
## Config (.env)
|
|
527
|
-
|
|
528
|
-
The `.env` file supports the following parameters (make sure to run commands with flag --env-file=.env)
|
|
529
|
-
|
|
530
|
-
```ini
|
|
531
|
-
# Auto generated secret key, will be using for encrypting the IDs
|
|
532
|
-
INIBASE_SECRET=
|
|
533
|
-
|
|
534
|
-
INIBASE_COMPRESSION=true
|
|
535
|
-
INIBASE_CACHE=true
|
|
521
|
+
// order users by the age column
|
|
522
|
+
await db.sort("user", "age");
|
|
536
523
|
|
|
537
|
-
|
|
538
|
-
|
|
524
|
+
// order users by the age and username columns
|
|
525
|
+
await db.sort("user", ["age","username"]);
|
|
526
|
+
await db.sort("user", {age: -1, username: "asc"});
|
|
539
527
|
```
|
|
540
528
|
|
|
529
|
+
</details>
|
|
530
|
+
|
|
541
531
|
## License
|
|
542
532
|
|
|
543
533
|
[MIT](./LICENSE)
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "dotenv/config";
|
|
3
|
+
import { createInterface } from "node:readline/promises";
|
|
4
|
+
import { parseArgs } from "node:util";
|
|
5
|
+
import Inibase from "./index.js";
|
|
6
|
+
import { basename } from "node:path";
|
|
7
|
+
import { isJSON, isNumber } from "./utils.js";
|
|
8
|
+
import Inison from "inison";
|
|
9
|
+
const { path } = parseArgs({
|
|
10
|
+
options: {
|
|
11
|
+
path: { type: "string", short: "p" },
|
|
12
|
+
},
|
|
13
|
+
}).values;
|
|
14
|
+
if (!path)
|
|
15
|
+
throw new Error("Please specify database folder path --path <databasePath> or -p <databasePath>");
|
|
16
|
+
const db = new Inibase(basename(path));
|
|
17
|
+
const rl = createInterface({
|
|
18
|
+
input: process.stdin,
|
|
19
|
+
output: process.stdout,
|
|
20
|
+
});
|
|
21
|
+
rl.prompt();
|
|
22
|
+
rl.on("line", async (input) => {
|
|
23
|
+
const trimedInput = input.trim();
|
|
24
|
+
if (trimedInput === "clear") {
|
|
25
|
+
console.clear();
|
|
26
|
+
rl.prompt();
|
|
27
|
+
}
|
|
28
|
+
if (trimedInput === "info") {
|
|
29
|
+
console.warn("war");
|
|
30
|
+
console.error("err");
|
|
31
|
+
}
|
|
32
|
+
const splitedInput = trimedInput.match(/[^\s"']+|"([^"]*)"|'([^']*)'/g);
|
|
33
|
+
if (["get", "post", "delete", "put"].includes(splitedInput[0].toLocaleLowerCase())) {
|
|
34
|
+
const table = splitedInput[1];
|
|
35
|
+
if (!table)
|
|
36
|
+
throw new Error("Please specify table name");
|
|
37
|
+
let { where, page, perPage, columns, data, returnData } = parseArgs({
|
|
38
|
+
args: splitedInput.toSpliced(0, 2),
|
|
39
|
+
options: {
|
|
40
|
+
where: { type: "string", short: "w" },
|
|
41
|
+
page: { type: "string", short: "p" },
|
|
42
|
+
perPage: { type: "string", short: "l" },
|
|
43
|
+
columns: { type: "string", short: "c", multiple: true },
|
|
44
|
+
data: { type: "string", short: "d" },
|
|
45
|
+
returnData: { type: "boolean", short: "r" },
|
|
46
|
+
},
|
|
47
|
+
}).values;
|
|
48
|
+
if (where) {
|
|
49
|
+
if (isNumber(where))
|
|
50
|
+
where = Number(where);
|
|
51
|
+
else if (isJSON(where))
|
|
52
|
+
where = Inison.unstringify(where);
|
|
53
|
+
}
|
|
54
|
+
if (data) {
|
|
55
|
+
if (isJSON(data))
|
|
56
|
+
where = Inison.unstringify(data);
|
|
57
|
+
else
|
|
58
|
+
data = undefined;
|
|
59
|
+
}
|
|
60
|
+
switch (splitedInput[0].toLocaleLowerCase()) {
|
|
61
|
+
case "get":
|
|
62
|
+
console.log(await db.get(table, where, {
|
|
63
|
+
page: Number(page) ?? 1,
|
|
64
|
+
perPage: Number(perPage) ?? 15,
|
|
65
|
+
columns,
|
|
66
|
+
}));
|
|
67
|
+
break;
|
|
68
|
+
case "post":
|
|
69
|
+
{
|
|
70
|
+
const postReturn = await db.post(table, data, {
|
|
71
|
+
page: Number(page) ?? 1,
|
|
72
|
+
perPage: Number(perPage) ?? 15,
|
|
73
|
+
columns,
|
|
74
|
+
}, returnData);
|
|
75
|
+
console.log(postReturn !== null && typeof postReturn === "object"
|
|
76
|
+
? "Item(s) Posted Successfully"
|
|
77
|
+
: postReturn);
|
|
78
|
+
}
|
|
79
|
+
break;
|
|
80
|
+
case "put": {
|
|
81
|
+
const putReturn = await db.put(table, data, where, {
|
|
82
|
+
page: Number(page) ?? 1,
|
|
83
|
+
perPage: Number(perPage) ?? 15,
|
|
84
|
+
columns,
|
|
85
|
+
}, returnData);
|
|
86
|
+
console.log(putReturn !== null && typeof putReturn === "object"
|
|
87
|
+
? "Item(s) Updated Successfully"
|
|
88
|
+
: putReturn);
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
case "delete":
|
|
92
|
+
console.log(await db.delete(table, where));
|
|
93
|
+
break;
|
|
94
|
+
default:
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
});
|
package/dist/file.d.ts
CHANGED
|
@@ -142,4 +142,3 @@ export declare const max: (filePath: string, lineNumbers?: number | number[]) =>
|
|
|
142
142
|
* Note: Decodes each line as a number using the 'decode' function. Considers only numerical values for determining the minimum.
|
|
143
143
|
*/
|
|
144
144
|
export declare const min: (filePath: string, lineNumbers?: number | number[]) => Promise<number>;
|
|
145
|
-
export declare function createWorker(functionName: "get" | "remove" | "search" | "replace" | "sum" | "min" | "max" | "append" | "count", arg: any[]): Promise<any>;
|
package/dist/file.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import { open, access, writeFile, readFile, constants as fsConstants, unlink, copyFile, appendFile, } from "node:fs/promises";
|
|
1
|
+
import { open, access, writeFile, readFile, constants as fsConstants, unlink, copyFile, appendFile, mkdir, } from "node:fs/promises";
|
|
2
2
|
import { createInterface } from "node:readline";
|
|
3
3
|
import { Transform } from "node:stream";
|
|
4
4
|
import { pipeline } from "node:stream/promises";
|
|
5
5
|
import { createGzip, createGunzip, gunzipSync, gzipSync } from "node:zlib";
|
|
6
|
-
import { join } from "node:path";
|
|
7
|
-
import { Worker } from "node:worker_threads";
|
|
6
|
+
import { dirname, join } from "node:path";
|
|
8
7
|
import { detectFieldType, isArrayOfObjects, isJSON, isNumber, isObject, } from "./utils.js";
|
|
9
|
-
import { encodeID, compare } from "./utils.server.js";
|
|
8
|
+
import { encodeID, compare, exec } from "./utils.server.js";
|
|
10
9
|
import * as Config from "./config.js";
|
|
11
10
|
import Inison from "inison";
|
|
12
11
|
export const lock = async (folderPath, prefix) => {
|
|
@@ -31,15 +30,14 @@ export const unlock = async (folderPath, prefix) => {
|
|
|
31
30
|
catch { }
|
|
32
31
|
};
|
|
33
32
|
export const write = async (filePath, data, disableCompression = false) => {
|
|
33
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
34
34
|
await writeFile(filePath, Config.isCompressionEnabled && !disableCompression
|
|
35
35
|
? gzipSync(String(data))
|
|
36
36
|
: String(data));
|
|
37
37
|
};
|
|
38
|
-
export const read = async (filePath, disableCompression = false) =>
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
: (await readFile(filePath)).toString();
|
|
42
|
-
};
|
|
38
|
+
export const read = async (filePath, disableCompression = false) => Config.isCompressionEnabled && !disableCompression
|
|
39
|
+
? gunzipSync(await readFile(filePath)).toString()
|
|
40
|
+
: (await readFile(filePath)).toString();
|
|
43
41
|
const _pipeline = async (rl, writeStream, transform) => {
|
|
44
42
|
if (Config.isCompressionEnabled)
|
|
45
43
|
await pipeline(rl, transform, createGzip(), writeStream);
|
|
@@ -52,18 +50,12 @@ const _pipeline = async (rl, writeStream, transform) => {
|
|
|
52
50
|
* @param fileHandle - The file handle from which to create a read stream.
|
|
53
51
|
* @returns A readline.Interface instance configured with the provided file stream.
|
|
54
52
|
*/
|
|
55
|
-
const readLineInternface = (fileHandle) => {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
input: Config.isCompressionEnabled
|
|
62
|
-
? fileHandle.createReadStream().pipe(createGunzip())
|
|
63
|
-
: fileHandle.createReadStream(),
|
|
64
|
-
crlfDelay: Number.POSITIVE_INFINITY,
|
|
65
|
-
});
|
|
66
|
-
};
|
|
53
|
+
const readLineInternface = (fileHandle) => createInterface({
|
|
54
|
+
input: Config.isCompressionEnabled
|
|
55
|
+
? fileHandle.createReadStream().pipe(createGunzip())
|
|
56
|
+
: fileHandle.createReadStream(),
|
|
57
|
+
crlfDelay: Number.POSITIVE_INFINITY,
|
|
58
|
+
});
|
|
67
59
|
/**
|
|
68
60
|
* Checks if a file or directory exists at the specified path.
|
|
69
61
|
*
|
|
@@ -212,18 +204,28 @@ export async function get(filePath, lineNumbers, fieldType, fieldChildrenType, s
|
|
|
212
204
|
lines[linesCount] = decode(lastLine, fieldType, fieldChildrenType, secretKey);
|
|
213
205
|
}
|
|
214
206
|
else {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
207
|
+
lineNumbers = Array.isArray(lineNumbers) ? lineNumbers : [lineNumbers];
|
|
208
|
+
if (readWholeFile) {
|
|
209
|
+
const lineNumbersArray = new Set(lineNumbers);
|
|
210
|
+
for await (const line of rl) {
|
|
211
|
+
linesCount++;
|
|
212
|
+
if (!lineNumbersArray.has(linesCount))
|
|
213
|
+
continue;
|
|
214
|
+
lines[linesCount] = decode(line, fieldType, fieldChildrenType, secretKey);
|
|
215
|
+
lineNumbersArray.delete(linesCount);
|
|
216
|
+
}
|
|
217
|
+
return [lines, linesCount];
|
|
218
|
+
}
|
|
219
|
+
const command = Config.isCompressionEnabled
|
|
220
|
+
? `zcat ${filePath} | sed -n '${lineNumbers.join("p;")}p'`
|
|
221
|
+
: `sed -n '${lineNumbers.join("p;")}p' ${filePath}`, foundedLines = (await exec(command)).stdout.trim().split(/\r?\n/);
|
|
222
|
+
let index = 0;
|
|
223
|
+
for (const line of foundedLines) {
|
|
224
|
+
lines[lineNumbers[index]] = decode(line, fieldType, fieldChildrenType, secretKey);
|
|
225
|
+
index++;
|
|
224
226
|
}
|
|
225
227
|
}
|
|
226
|
-
return
|
|
228
|
+
return lines;
|
|
227
229
|
}
|
|
228
230
|
finally {
|
|
229
231
|
// Ensure that file handles are closed, even if an error occurred
|
|
@@ -312,7 +314,7 @@ export const append = async (filePath, data) => {
|
|
|
312
314
|
rl = readLineInternface(fileHandle);
|
|
313
315
|
let isAppended = false;
|
|
314
316
|
await _pipeline(rl, fileTempHandle.createWriteStream(), new Transform({
|
|
315
|
-
transform(line,
|
|
317
|
+
transform(line, _, callback) {
|
|
316
318
|
if (!isAppended) {
|
|
317
319
|
isAppended = true;
|
|
318
320
|
return callback(null, `${Array.isArray(data) ? data.join("\n") : data}\n${line.length ? `${line}\n` : ""}`);
|
|
@@ -343,32 +345,41 @@ export const append = async (filePath, data) => {
|
|
|
343
345
|
* Note: Creates a temporary file during the process and replaces the original file with it after removing lines.
|
|
344
346
|
*/
|
|
345
347
|
export const remove = async (filePath, linesToDelete) => {
|
|
346
|
-
|
|
347
|
-
let deletedCount = 0;
|
|
348
|
-
const fileHandle = await open(filePath, "r");
|
|
349
|
-
const fileTempPath = filePath.replace(/([^/]+)\/?$/, ".tmp/$1");
|
|
350
|
-
const fileTempHandle = await open(fileTempPath, "w");
|
|
351
|
-
const linesToDeleteArray = new Set(Array.isArray(linesToDelete)
|
|
348
|
+
linesToDelete = Array.isArray(linesToDelete)
|
|
352
349
|
? linesToDelete.map(Number)
|
|
353
|
-
: [Number(linesToDelete)]
|
|
354
|
-
const
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
350
|
+
: [Number(linesToDelete)];
|
|
351
|
+
const fileTempPath = filePath.replace(/([^/]+)\/?$/, ".tmp/$1");
|
|
352
|
+
if (linesToDelete.length < 1000) {
|
|
353
|
+
const command = Config.isCompressionEnabled
|
|
354
|
+
? `zcat ${filePath} | sed "${linesToDelete.join("d;")}d" | gzip > ${fileTempPath}`
|
|
355
|
+
: `sed "${linesToDelete.join("d;")}d" ${filePath} > ${fileTempPath}`;
|
|
356
|
+
await exec(command);
|
|
357
|
+
}
|
|
358
|
+
else {
|
|
359
|
+
let linesCount = 0;
|
|
360
|
+
let deletedCount = 0;
|
|
361
|
+
const fileHandle = await open(filePath, "r");
|
|
362
|
+
const fileTempHandle = await open(fileTempPath, "w");
|
|
363
|
+
const linesToDeleteArray = new Set(linesToDelete);
|
|
364
|
+
const rl = readLineInternface(fileHandle);
|
|
365
|
+
await _pipeline(rl, fileTempHandle.createWriteStream(), new Transform({
|
|
366
|
+
transform(line, _, callback) {
|
|
367
|
+
linesCount++;
|
|
368
|
+
if (linesToDeleteArray.has(linesCount)) {
|
|
369
|
+
deletedCount++;
|
|
370
|
+
return callback();
|
|
371
|
+
}
|
|
372
|
+
return callback(null, `${line}\n`);
|
|
373
|
+
},
|
|
374
|
+
final(callback) {
|
|
375
|
+
if (deletedCount === linesCount)
|
|
376
|
+
this.push("\n");
|
|
360
377
|
return callback();
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
this.push("\n");
|
|
367
|
-
return callback();
|
|
368
|
-
},
|
|
369
|
-
}));
|
|
370
|
-
await fileTempHandle.close();
|
|
371
|
-
await fileHandle.close();
|
|
378
|
+
},
|
|
379
|
+
}));
|
|
380
|
+
await fileTempHandle.close();
|
|
381
|
+
await fileHandle.close();
|
|
382
|
+
}
|
|
372
383
|
return [fileTempPath, filePath];
|
|
373
384
|
};
|
|
374
385
|
/**
|
|
@@ -456,7 +467,7 @@ export const search = async (filePath, operator, comparedAtValue, logicalOperato
|
|
|
456
467
|
* Note: Reads through the file line by line to count the total number of lines.
|
|
457
468
|
*/
|
|
458
469
|
export const count = async (filePath) => {
|
|
459
|
-
//
|
|
470
|
+
// Number((await exec(`wc -l < ${filePath}`)).stdout.trim());
|
|
460
471
|
let linesCount = 0;
|
|
461
472
|
if (await isExists(filePath)) {
|
|
462
473
|
let fileHandle = null;
|
|
@@ -580,16 +591,3 @@ export const min = async (filePath, lineNumbers) => {
|
|
|
580
591
|
await fileHandle.close();
|
|
581
592
|
return min;
|
|
582
593
|
};
|
|
583
|
-
export function createWorker(functionName, arg) {
|
|
584
|
-
return new Promise((resolve, reject) => {
|
|
585
|
-
const worker = new Worker("./dist/file.thread.js", {
|
|
586
|
-
workerData: { functionName, arg },
|
|
587
|
-
});
|
|
588
|
-
worker.on("message", (data) => {
|
|
589
|
-
resolve(data);
|
|
590
|
-
});
|
|
591
|
-
worker.on("error", (msg) => {
|
|
592
|
-
reject(`An error ocurred: ${msg}`);
|
|
593
|
-
});
|
|
594
|
-
});
|
|
595
|
-
}
|
package/dist/index.d.ts
CHANGED
|
@@ -47,13 +47,13 @@ export default class Inibase {
|
|
|
47
47
|
database: string;
|
|
48
48
|
table: string | null;
|
|
49
49
|
pageInfo: Record<string, pageInfo>;
|
|
50
|
+
private fileExtension;
|
|
50
51
|
private checkIFunique;
|
|
51
|
-
private isThreadEnabled;
|
|
52
52
|
private totalItems;
|
|
53
53
|
salt: Buffer;
|
|
54
54
|
constructor(database: string, mainFolder?: string, _table?: string | null, _totalItems?: Record<string, number>, _pageInfo?: Record<string, pageInfo>, _isThreadEnabled?: boolean);
|
|
55
55
|
private throwError;
|
|
56
|
-
|
|
56
|
+
private getFileExtension;
|
|
57
57
|
private _schemaToIdsPath;
|
|
58
58
|
setTableSchema(tableName: string, schema: Schema): Promise<void>;
|
|
59
59
|
getTableSchema(tableName: string, encodeIDs?: boolean): Promise<Schema | undefined>;
|
|
@@ -82,6 +82,8 @@ export default class Inibase {
|
|
|
82
82
|
put(tableName: string, data: Data[], where: number | string | (number | string)[] | Criteria | undefined, options: Options | undefined, returnUpdatedData: true): Promise<Data[] | null>;
|
|
83
83
|
delete(tableName: string, where?: number | string, _id?: string | string[]): Promise<string | null>;
|
|
84
84
|
delete(tableName: string, where?: (number | string)[] | Criteria, _id?: string | string[]): Promise<string[] | null>;
|
|
85
|
+
delete(tableName: string, where?: number, _id?: string | string[]): Promise<number | null>;
|
|
86
|
+
delete(tableName: string, where?: number[], _id?: string | string[]): Promise<number[] | null>;
|
|
85
87
|
sum(tableName: string, columns: string, where?: number | string | (number | string)[] | Criteria): Promise<number>;
|
|
86
88
|
sum(tableName: string, columns: string[], where?: number | string | (number | string)[] | Criteria): Promise<Record<string, number>>;
|
|
87
89
|
max(tableName: string, columns: string, where?: number | string | (number | string)[] | Criteria): Promise<number>;
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,6 @@ import { unlink, rename, mkdir, readdir } from "node:fs/promises";
|
|
|
2
2
|
import { existsSync, appendFileSync, readFileSync } from "node:fs";
|
|
3
3
|
import { join, parse } from "node:path";
|
|
4
4
|
import { scryptSync, randomBytes } from "node:crypto";
|
|
5
|
-
import { Worker } from "node:worker_threads";
|
|
6
5
|
import { inspect } from "node:util";
|
|
7
6
|
import Inison from "inison";
|
|
8
7
|
import * as File from "./file.js";
|
|
@@ -14,8 +13,8 @@ export default class Inibase {
|
|
|
14
13
|
database;
|
|
15
14
|
table;
|
|
16
15
|
pageInfo;
|
|
16
|
+
fileExtension = ".txt";
|
|
17
17
|
checkIFunique;
|
|
18
|
-
isThreadEnabled = false;
|
|
19
18
|
totalItems;
|
|
20
19
|
salt;
|
|
21
20
|
constructor(database, mainFolder = ".", _table = null, _totalItems = {}, _pageInfo = {}, _isThreadEnabled = false) {
|
|
@@ -24,7 +23,6 @@ export default class Inibase {
|
|
|
24
23
|
this.table = _table;
|
|
25
24
|
this.totalItems = _totalItems;
|
|
26
25
|
this.pageInfo = _pageInfo;
|
|
27
|
-
this.isThreadEnabled = _isThreadEnabled;
|
|
28
26
|
this.checkIFunique = {};
|
|
29
27
|
if (!process.env.INIBASE_SECRET) {
|
|
30
28
|
if (existsSync(".env") &&
|
|
@@ -35,6 +33,19 @@ export default class Inibase {
|
|
|
35
33
|
}
|
|
36
34
|
else
|
|
37
35
|
this.salt = Buffer.from(process.env.INIBASE_SECRET, "hex");
|
|
36
|
+
// try {
|
|
37
|
+
// if (Config.isCompressionEnabled)
|
|
38
|
+
// execSync(
|
|
39
|
+
// `gzip -r ${join(this.folder, this.database)}/*/*.txt 2>/dev/null`,
|
|
40
|
+
// );
|
|
41
|
+
// else
|
|
42
|
+
// execSync(
|
|
43
|
+
// `gunzip -r ${join(
|
|
44
|
+
// this.folder,
|
|
45
|
+
// this.database,
|
|
46
|
+
// )}/*/*.txt.gz 2>/dev/null`,
|
|
47
|
+
// );
|
|
48
|
+
// } catch {}
|
|
38
49
|
}
|
|
39
50
|
throwError(code, variable, language = "en") {
|
|
40
51
|
const errorMessages = {
|
|
@@ -62,26 +73,15 @@ export default class Inibase {
|
|
|
62
73
|
: errorMessage.replaceAll("{variable}", `'${variable.toString()}'`)
|
|
63
74
|
: errorMessage.replaceAll("{variable}", ""));
|
|
64
75
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
this.pageInfo,
|
|
75
|
-
true, // enable Thread
|
|
76
|
-
],
|
|
77
|
-
functionName,
|
|
78
|
-
arg,
|
|
79
|
-
},
|
|
80
|
-
});
|
|
81
|
-
worker.on("message", resolve);
|
|
82
|
-
worker.on("error", reject);
|
|
83
|
-
});
|
|
84
|
-
}
|
|
76
|
+
getFileExtension = () => {
|
|
77
|
+
let mainExtension = this.fileExtension;
|
|
78
|
+
// TODO: ADD ENCRYPTION
|
|
79
|
+
// if(Config.isEncryptionEnabled)
|
|
80
|
+
// mainExtension += ".enc"
|
|
81
|
+
if (Config.isCompressionEnabled)
|
|
82
|
+
mainExtension += ".gz";
|
|
83
|
+
return mainExtension;
|
|
84
|
+
};
|
|
85
85
|
_schemaToIdsPath = (schema, prefix = "") => {
|
|
86
86
|
const RETURN = {};
|
|
87
87
|
for (const field of schema)
|
|
@@ -91,7 +91,7 @@ export default class Inibase {
|
|
|
91
91
|
Utils.deepMerge(RETURN, this._schemaToIdsPath(field.children, `${(prefix ?? "") + field.key}.`));
|
|
92
92
|
}
|
|
93
93
|
else if (field.id)
|
|
94
|
-
RETURN[field.id] = `${(prefix ?? "") + field.key}.
|
|
94
|
+
RETURN[field.id] = `${(prefix ?? "") + field.key}${this.getFileExtension()}`;
|
|
95
95
|
return RETURN;
|
|
96
96
|
};
|
|
97
97
|
async setTableSchema(tableName, schema) {
|
|
@@ -161,7 +161,7 @@ export default class Inibase {
|
|
|
161
161
|
schema = await this.getTableSchema(tableName);
|
|
162
162
|
if (!schema)
|
|
163
163
|
throw this.throwError("NO_SCHEMA", tableName);
|
|
164
|
-
if (!(await File.isExists(join(tablePath,
|
|
164
|
+
if (!(await File.isExists(join(tablePath, `id${this.getFileExtension()}`))))
|
|
165
165
|
throw this.throwError("NO_ITEMS", tableName);
|
|
166
166
|
return schema;
|
|
167
167
|
}
|
|
@@ -289,7 +289,7 @@ export default class Inibase {
|
|
|
289
289
|
const field = Utils.getField(key, schema);
|
|
290
290
|
if (!field)
|
|
291
291
|
continue;
|
|
292
|
-
const [searchResult, totalLines] = await File.search(join(tablePath, `${key}.
|
|
292
|
+
const [searchResult, totalLines] = await File.search(join(tablePath, `${key}${this.getFileExtension()}`), Array.isArray(values) ? "=" : "[]", values, undefined, field.type, field.children, 1, undefined, false, this.salt);
|
|
293
293
|
if (searchResult && totalLines > 0)
|
|
294
294
|
throw this.throwError("FIELD_UNIQUE", [
|
|
295
295
|
field.key,
|
|
@@ -378,7 +378,7 @@ export default class Inibase {
|
|
|
378
378
|
_addPathToKeys = (obj, path) => {
|
|
379
379
|
const newObject = {};
|
|
380
380
|
for (const key in obj)
|
|
381
|
-
newObject[join(path, `${key}.
|
|
381
|
+
newObject[join(path, `${key}${this.getFileExtension()}`)] = obj[key];
|
|
382
382
|
return newObject;
|
|
383
383
|
};
|
|
384
384
|
joinPathesContents(mainPath, data) {
|
|
@@ -428,7 +428,7 @@ export default class Inibase {
|
|
|
428
428
|
async getItemsFromSchema(tableName, schema, linesNumber, options, prefix) {
|
|
429
429
|
const tablePath = join(this.folder, this.database, tableName);
|
|
430
430
|
let RETURN = {};
|
|
431
|
-
await
|
|
431
|
+
for await (const field of schema) {
|
|
432
432
|
if ((field.type === "array" ||
|
|
433
433
|
(Array.isArray(field.type) && field.type.includes("array"))) &&
|
|
434
434
|
field.children) {
|
|
@@ -436,83 +436,86 @@ export default class Inibase {
|
|
|
436
436
|
if (field.children.filter((children) => children.type === "array" &&
|
|
437
437
|
Utils.isArrayOfObjects(children.children)).length) {
|
|
438
438
|
// one of children has array field type and has children array of object = Schema
|
|
439
|
-
|
|
440
|
-
Utils.isArrayOfObjects(children.children)), linesNumber, options, `${(prefix ?? "") + field.key}.`)
|
|
441
|
-
|
|
442
|
-
|
|
439
|
+
const childItems = await this.getItemsFromSchema(tableName, field.children.filter((children) => children.type === "array" &&
|
|
440
|
+
Utils.isArrayOfObjects(children.children)), linesNumber, options, `${(prefix ?? "") + field.key}.`);
|
|
441
|
+
if (childItems)
|
|
442
|
+
for (const [index, item] of Object.entries(childItems))
|
|
443
|
+
this._getItemsFromSchemaHelper(RETURN, item, index, field);
|
|
443
444
|
field.children = field.children.filter((children) => children.type !== "array" ||
|
|
444
445
|
!Utils.isArrayOfObjects(children.children));
|
|
445
446
|
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
for (let _i = 0; _i < value.length; _i++) {
|
|
475
|
-
if (value[_i] === null ||
|
|
476
|
-
(Array.isArray(value[_i]) &&
|
|
477
|
-
Utils.isArrayOfNulls(value[_i])))
|
|
478
|
-
continue;
|
|
479
|
-
if (!RETURN[index][field.key][_i])
|
|
480
|
-
RETURN[index][field.key][_i] = {};
|
|
481
|
-
RETURN[index][field.key][_i][key] = value[_i];
|
|
447
|
+
const fieldItems = await this.getItemsFromSchema(tableName, field.children, linesNumber, options, `${(prefix ?? "") + field.key}.`);
|
|
448
|
+
if (fieldItems)
|
|
449
|
+
for (const [index, item] of Object.entries(fieldItems)) {
|
|
450
|
+
if (!RETURN[index])
|
|
451
|
+
RETURN[index] = {};
|
|
452
|
+
if (Utils.isObject(item)) {
|
|
453
|
+
if (!Utils.isArrayOfNulls(Object.values(item))) {
|
|
454
|
+
if (RETURN[index][field.key])
|
|
455
|
+
Object.entries(item).forEach(([key, value], _index) => {
|
|
456
|
+
for (let _index = 0; _index < value.length; _index++)
|
|
457
|
+
if (RETURN[index][field.key][_index])
|
|
458
|
+
Object.assign(RETURN[index][field.key][_index], {
|
|
459
|
+
[key]: value[_index],
|
|
460
|
+
});
|
|
461
|
+
else
|
|
462
|
+
RETURN[index][field.key][_index] = {
|
|
463
|
+
[key]: value[_index],
|
|
464
|
+
};
|
|
465
|
+
});
|
|
466
|
+
else if (Object.values(item).every((_i) => Utils.isArrayOfArrays(_i) || Array.isArray(_i)) &&
|
|
467
|
+
prefix)
|
|
468
|
+
RETURN[index][field.key] = item;
|
|
469
|
+
else {
|
|
470
|
+
RETURN[index][field.key] = [];
|
|
471
|
+
Object.entries(item).forEach(([key, value], _ind) => {
|
|
472
|
+
if (!Array.isArray(value)) {
|
|
473
|
+
RETURN[index][field.key][_ind] = {};
|
|
474
|
+
RETURN[index][field.key][_ind][key] = value;
|
|
482
475
|
}
|
|
483
|
-
|
|
476
|
+
else
|
|
477
|
+
for (let _i = 0; _i < value.length; _i++) {
|
|
478
|
+
if (value[_i] === null ||
|
|
479
|
+
(Array.isArray(value[_i]) &&
|
|
480
|
+
Utils.isArrayOfNulls(value[_i])))
|
|
481
|
+
continue;
|
|
482
|
+
if (!RETURN[index][field.key][_i])
|
|
483
|
+
RETURN[index][field.key][_i] = {};
|
|
484
|
+
RETURN[index][field.key][_i][key] = value[_i];
|
|
485
|
+
}
|
|
486
|
+
});
|
|
487
|
+
}
|
|
484
488
|
}
|
|
489
|
+
else
|
|
490
|
+
RETURN[index][field.key] = null;
|
|
485
491
|
}
|
|
486
492
|
else
|
|
487
|
-
RETURN[index][field.key] =
|
|
493
|
+
RETURN[index][field.key] = item;
|
|
488
494
|
}
|
|
489
|
-
else
|
|
490
|
-
RETURN[index][field.key] = item;
|
|
491
|
-
}
|
|
492
495
|
}
|
|
493
496
|
else if (field.children === "table" ||
|
|
494
497
|
(Array.isArray(field.type) && field.type.includes("table")) ||
|
|
495
498
|
(Array.isArray(field.children) && field.children.includes("table"))) {
|
|
496
499
|
if (field.table &&
|
|
497
500
|
(await File.isExists(join(this.folder, this.database, field.table))) &&
|
|
498
|
-
(await File.isExists(join(tablePath, `${(prefix ?? "") + field.key}.
|
|
501
|
+
(await File.isExists(join(tablePath, `${(prefix ?? "") + field.key}${this.getFileExtension()}`)))) {
|
|
499
502
|
if (options.columns)
|
|
500
503
|
options.columns = options.columns
|
|
501
504
|
.filter((column) => column.includes(`${field.key}.`))
|
|
502
505
|
.map((column) => column.replace(`${field.key}.`, ""));
|
|
503
|
-
const items = await File.get(join(tablePath, `${(prefix ?? "") + field.key}.
|
|
506
|
+
const items = await File.get(join(tablePath, `${(prefix ?? "") + field.key}${this.getFileExtension()}`), linesNumber, field.type, field.children, this.salt);
|
|
504
507
|
if (items)
|
|
505
|
-
await
|
|
508
|
+
for await (const [index, item] of Object.entries(items)) {
|
|
506
509
|
if (!RETURN[index])
|
|
507
510
|
RETURN[index] = {};
|
|
508
511
|
RETURN[index][field.key] = item
|
|
509
512
|
? await this.get(field.table, item, options)
|
|
510
513
|
: this.getDefaultValue(field);
|
|
511
|
-
}
|
|
514
|
+
}
|
|
512
515
|
}
|
|
513
516
|
}
|
|
514
|
-
else if (await File.isExists(join(tablePath, `${(prefix ?? "") + field.key}.
|
|
515
|
-
const items = await File.get(join(tablePath, `${(prefix ?? "") + field.key}.
|
|
517
|
+
else if (await File.isExists(join(tablePath, `${(prefix ?? "") + field.key}${this.getFileExtension()}`))) {
|
|
518
|
+
const items = await File.get(join(tablePath, `${(prefix ?? "") + field.key}${this.getFileExtension()}`), linesNumber, field.type, field.children, this.salt);
|
|
516
519
|
if (items)
|
|
517
520
|
for (const [index, item] of Object.entries(items)) {
|
|
518
521
|
if (!RETURN[index])
|
|
@@ -522,40 +525,42 @@ export default class Inibase {
|
|
|
522
525
|
}
|
|
523
526
|
}
|
|
524
527
|
else if (field.type === "object") {
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
528
|
+
const fieldItems = await this.getItemsFromSchema(tableName, field.children, linesNumber, options, `${(prefix ?? "") + field.key}.`);
|
|
529
|
+
if (fieldItems)
|
|
530
|
+
for (const [index, item] of Object.entries(fieldItems)) {
|
|
531
|
+
if (!RETURN[index])
|
|
532
|
+
RETURN[index] = {};
|
|
533
|
+
if (Utils.isObject(item)) {
|
|
534
|
+
if (!Object.values(item).every((i) => i === null))
|
|
535
|
+
RETURN[index][field.key] = item;
|
|
536
|
+
else
|
|
537
|
+
RETURN[index][field.key] = null;
|
|
538
|
+
}
|
|
531
539
|
else
|
|
532
540
|
RETURN[index][field.key] = null;
|
|
533
541
|
}
|
|
534
|
-
else
|
|
535
|
-
RETURN[index][field.key] = null;
|
|
536
|
-
}
|
|
537
542
|
}
|
|
538
543
|
else if (field.type === "table") {
|
|
539
544
|
if (field.table &&
|
|
540
545
|
(await File.isExists(join(this.folder, this.database, field.table))) &&
|
|
541
|
-
(await File.isExists(join(tablePath, `${(prefix ?? "") + field.key}.
|
|
546
|
+
(await File.isExists(join(tablePath, `${(prefix ?? "") + field.key}${this.getFileExtension()}`)))) {
|
|
542
547
|
if (options.columns)
|
|
543
548
|
options.columns = options.columns
|
|
544
549
|
.filter((column) => column.includes(`${field.key}.`))
|
|
545
550
|
.map((column) => column.replace(`${field.key}.`, ""));
|
|
546
|
-
const items = await File.get(join(tablePath, `${(prefix ?? "") + field.key}.
|
|
551
|
+
const items = await File.get(join(tablePath, `${(prefix ?? "") + field.key}${this.getFileExtension()}`), linesNumber, "number", undefined, this.salt);
|
|
547
552
|
if (items)
|
|
548
|
-
await
|
|
553
|
+
for await (const [index, item] of Object.entries(items)) {
|
|
549
554
|
if (!RETURN[index])
|
|
550
555
|
RETURN[index] = {};
|
|
551
556
|
RETURN[index][field.key] = item
|
|
552
557
|
? await this.get(field.table, item, options)
|
|
553
558
|
: this.getDefaultValue(field);
|
|
554
|
-
}
|
|
559
|
+
}
|
|
555
560
|
}
|
|
556
561
|
}
|
|
557
|
-
else if (await File.isExists(join(tablePath, `${(prefix ?? "") + field.key}.
|
|
558
|
-
const items = await File.get(join(tablePath, `${(prefix ?? "") + field.key}.
|
|
562
|
+
else if (await File.isExists(join(tablePath, `${(prefix ?? "") + field.key}${this.getFileExtension()}`))) {
|
|
563
|
+
const items = await File.get(join(tablePath, `${(prefix ?? "") + field.key}${this.getFileExtension()}`), linesNumber, field.type, field.children, this.salt);
|
|
559
564
|
if (items)
|
|
560
565
|
for (const [index, item] of Object.entries(items)) {
|
|
561
566
|
if (!RETURN[index])
|
|
@@ -568,7 +573,7 @@ export default class Inibase {
|
|
|
568
573
|
{ ...data, [field.key]: this.getDefaultValue(field) },
|
|
569
574
|
]));
|
|
570
575
|
}
|
|
571
|
-
}
|
|
576
|
+
}
|
|
572
577
|
return RETURN;
|
|
573
578
|
}
|
|
574
579
|
async applyCriteria(tableName, schema, options, criteria, allTrue) {
|
|
@@ -656,7 +661,7 @@ export default class Inibase {
|
|
|
656
661
|
searchOperator = "=";
|
|
657
662
|
searchComparedAtValue = value;
|
|
658
663
|
}
|
|
659
|
-
const [searchResult, totalLines, linesNumbers] = await File.search(join(tablePath, `${key}.
|
|
664
|
+
const [searchResult, totalLines, linesNumbers] = await File.search(join(tablePath, `${key}${this.getFileExtension()}`), searchOperator ?? "=", searchComparedAtValue ?? null, searchLogicalOperator, field?.type, field?.children, options.perPage, options.page - 1 * options.perPage + 1, true, this.salt);
|
|
660
665
|
if (searchResult) {
|
|
661
666
|
RETURN = Utils.deepMerge(RETURN, Object.fromEntries(Object.entries(searchResult).map(([id, value]) => [
|
|
662
667
|
id,
|
|
@@ -701,7 +706,7 @@ export default class Inibase {
|
|
|
701
706
|
}
|
|
702
707
|
async clearCache(tablePath) {
|
|
703
708
|
await Promise.all((await readdir(join(tablePath, ".cache")))
|
|
704
|
-
?.filter((fileName) => fileName !==
|
|
709
|
+
?.filter((fileName) => fileName !== `pagination${this.fileExtension}`)
|
|
705
710
|
.map(async (file) => unlink(join(tablePath, ".cache", file))));
|
|
706
711
|
}
|
|
707
712
|
async get(tableName, where, options = {
|
|
@@ -735,16 +740,14 @@ export default class Inibase {
|
|
|
735
740
|
RETURN = Object.values(await this.getItemsFromSchema(tableName, schema, Array.from({ length: options.perPage }, (_, index) => (options.page - 1) * options.perPage +
|
|
736
741
|
index +
|
|
737
742
|
1), options));
|
|
738
|
-
if (
|
|
739
|
-
(await File.
|
|
740
|
-
this.totalItems[`${tableName}-*`] = Number((await File.read(join(tablePath, ".cache", "pagination.inib"), true)).split(",")[1]);
|
|
743
|
+
if (await File.isExists(join(tablePath, ".cache", `pagination${this.fileExtension}`)))
|
|
744
|
+
this.totalItems[`${tableName}-*`] = Number((await File.read(join(tablePath, ".cache", `pagination${this.fileExtension}`), true)).split(",")[1]);
|
|
741
745
|
else {
|
|
742
|
-
let [lastId, totalItems] = await File.get(join(tablePath,
|
|
746
|
+
let [lastId, totalItems] = await File.get(join(tablePath, `id${this.getFileExtension()}`), -1, "number", undefined, this.salt, true);
|
|
743
747
|
if (lastId)
|
|
744
748
|
lastId = Number(Object.keys(lastId)?.[0] ?? 0);
|
|
745
749
|
this.totalItems[`${tableName}-*`] = totalItems;
|
|
746
|
-
|
|
747
|
-
await File.write(join(tablePath, ".cache", "pagination.inib"), `${lastId},${totalItems}`, true);
|
|
750
|
+
await File.write(join(tablePath, ".cache", `pagination${this.fileExtension}`), `${lastId},${totalItems}`, true);
|
|
748
751
|
}
|
|
749
752
|
}
|
|
750
753
|
else if ((Array.isArray(where) && where.every(Utils.isNumber)) ||
|
|
@@ -767,7 +770,7 @@ export default class Inibase {
|
|
|
767
770
|
let Ids = where;
|
|
768
771
|
if (!Array.isArray(Ids))
|
|
769
772
|
Ids = [Ids];
|
|
770
|
-
const [lineNumbers, countItems] = await File.search(join(tablePath,
|
|
773
|
+
const [lineNumbers, countItems] = await File.search(join(tablePath, `id${this.getFileExtension()}`), "[]", Ids.map((id) => Utils.isNumber(id) ? Number(id) : UtilsServer.decodeID(id, this.salt)), undefined, "number", undefined, Ids.length, 0, !this.totalItems[`${tableName}-*`], this.salt);
|
|
771
774
|
if (!lineNumbers)
|
|
772
775
|
throw this.throwError("NO_RESULTS", tableName);
|
|
773
776
|
if (onlyLinesNumbers)
|
|
@@ -784,7 +787,7 @@ export default class Inibase {
|
|
|
784
787
|
let cachedFilePath = "";
|
|
785
788
|
// Criteria
|
|
786
789
|
if (Config.isCacheEnabled)
|
|
787
|
-
cachedFilePath = join(tablePath, ".cache", `${UtilsServer.hashString(inspect(where, { sorted: true }))}.
|
|
790
|
+
cachedFilePath = join(tablePath, ".cache", `${UtilsServer.hashString(inspect(where, { sorted: true }))}${this.fileExtension}`);
|
|
788
791
|
if (Config.isCacheEnabled && (await File.isExists(cachedFilePath))) {
|
|
789
792
|
const cachedItems = (await File.read(cachedFilePath, true)).split(",");
|
|
790
793
|
this.totalItems[`${tableName}-*`] = cachedItems.length;
|
|
@@ -837,15 +840,14 @@ export default class Inibase {
|
|
|
837
840
|
let lastId = 0, totalItems = 0, renameList = [];
|
|
838
841
|
try {
|
|
839
842
|
await File.lock(join(tablePath, ".tmp"), keys);
|
|
840
|
-
if (await File.isExists(join(tablePath,
|
|
841
|
-
if (
|
|
842
|
-
(await File.
|
|
843
|
-
[lastId, totalItems] = (await File.read(join(tablePath, ".cache", "pagination.inib"), true))
|
|
843
|
+
if (await File.isExists(join(tablePath, `id${this.getFileExtension()}`))) {
|
|
844
|
+
if (await File.isExists(join(tablePath, ".cache", `pagination${this.fileExtension}`)))
|
|
845
|
+
[lastId, totalItems] = (await File.read(join(tablePath, ".cache", `pagination${this.fileExtension}`), true))
|
|
844
846
|
.split(",")
|
|
845
847
|
.map(Number);
|
|
846
848
|
else {
|
|
847
849
|
let lastIdObj = null;
|
|
848
|
-
[lastIdObj, totalItems] = await File.get(join(tablePath,
|
|
850
|
+
[lastIdObj, totalItems] = await File.get(join(tablePath, `id${this.getFileExtension()}`), -1, "number", undefined, this.salt, true);
|
|
849
851
|
if (lastIdObj)
|
|
850
852
|
lastId = Number(Object.keys(lastIdObj)?.[0] ?? 0);
|
|
851
853
|
}
|
|
@@ -870,16 +872,13 @@ export default class Inibase {
|
|
|
870
872
|
? RETURN.toReversed()
|
|
871
873
|
: RETURN
|
|
872
874
|
: RETURN);
|
|
873
|
-
await Promise.all(Object.entries(pathesContents).map(async ([path, content]) => renameList.push(
|
|
874
|
-
? await File.createWorker("append", [path, content])
|
|
875
|
-
: await File.append(path, content))));
|
|
875
|
+
await Promise.all(Object.entries(pathesContents).map(async ([path, content]) => renameList.push(await File.append(path, content))));
|
|
876
876
|
await Promise.all(renameList.map(async ([tempPath, filePath]) => rename(tempPath, filePath)));
|
|
877
877
|
renameList = [];
|
|
878
878
|
totalItems += Array.isArray(RETURN) ? RETURN.length : 1;
|
|
879
|
-
if (Config.isCacheEnabled)
|
|
879
|
+
if (Config.isCacheEnabled)
|
|
880
880
|
await this.clearCache(tablePath);
|
|
881
|
-
|
|
882
|
-
}
|
|
881
|
+
await File.write(join(tablePath, ".cache", `pagination${this.fileExtension}`), `${lastId},${totalItems}`, true);
|
|
883
882
|
if (returnPostedData)
|
|
884
883
|
return this.get(tableName, Config.isReverseEnabled
|
|
885
884
|
? Array.isArray(RETURN)
|
|
@@ -919,13 +918,12 @@ export default class Inibase {
|
|
|
919
918
|
return this.put(tableName, data, data.id);
|
|
920
919
|
}
|
|
921
920
|
let totalItems;
|
|
922
|
-
if (
|
|
923
|
-
(await File.
|
|
924
|
-
totalItems = (await File.read(join(tablePath, ".cache", "pagination.inib"), true))
|
|
921
|
+
if (await File.isExists(join(tablePath, ".cache", `pagination${this.fileExtension}`)))
|
|
922
|
+
totalItems = (await File.read(join(tablePath, ".cache", `pagination${this.fileExtension}`), true))
|
|
925
923
|
.split(",")
|
|
926
924
|
.map(Number)[1];
|
|
927
925
|
else
|
|
928
|
-
totalItems = await File.count(join(tablePath,
|
|
926
|
+
totalItems = await File.count(join(tablePath, `id${this.getFileExtension()}`));
|
|
929
927
|
const pathesContents = this.joinPathesContents(tablePath, {
|
|
930
928
|
...(({ id, ...restOfData }) => restOfData)(data),
|
|
931
929
|
updatedAt: Date.now(),
|
|
@@ -936,9 +934,7 @@ export default class Inibase {
|
|
|
936
934
|
const replacementObject = {};
|
|
937
935
|
for (const index of [...Array(totalItems)].keys())
|
|
938
936
|
replacementObject[`${index + 1}`] = content;
|
|
939
|
-
renameList.push(
|
|
940
|
-
? await File.createWorker("replace", [path, replacementObject])
|
|
941
|
-
: await File.replace(path, replacementObject));
|
|
937
|
+
renameList.push(await File.replace(path, replacementObject));
|
|
942
938
|
}));
|
|
943
939
|
await Promise.all(renameList.map(async ([tempPath, filePath]) => rename(tempPath, filePath)));
|
|
944
940
|
if (Config.isCacheEnabled)
|
|
@@ -976,9 +972,7 @@ export default class Inibase {
|
|
|
976
972
|
.join("."));
|
|
977
973
|
try {
|
|
978
974
|
await File.lock(join(tablePath, ".tmp"), keys);
|
|
979
|
-
await Promise.all(Object.entries(pathesContents).map(async ([path, content]) => renameList.push(
|
|
980
|
-
? await File.createWorker("replace", [path, content])
|
|
981
|
-
: await File.replace(path, content))));
|
|
975
|
+
await Promise.all(Object.entries(pathesContents).map(async ([path, content]) => renameList.push(await File.replace(path, content))));
|
|
982
976
|
await Promise.all(renameList.map(async ([tempPath, filePath]) => rename(tempPath, filePath)));
|
|
983
977
|
renameList = [];
|
|
984
978
|
if (Config.isCacheEnabled)
|
|
@@ -1026,31 +1020,23 @@ export default class Inibase {
|
|
|
1026
1020
|
if ((Array.isArray(where) && where.every(Utils.isNumber)) ||
|
|
1027
1021
|
Utils.isNumber(where)) {
|
|
1028
1022
|
// "where" in this case, is the line(s) number(s) and not id(s)
|
|
1029
|
-
const files = (await readdir(tablePath))?.filter((fileName) => fileName.endsWith(
|
|
1023
|
+
const files = (await readdir(tablePath))?.filter((fileName) => fileName.endsWith(this.getFileExtension()));
|
|
1030
1024
|
if (files.length) {
|
|
1031
|
-
if (!_id)
|
|
1032
|
-
_id = Object.entries((await File.get(join(tablePath, "id.inib"), where, "number", undefined, this.salt)) ?? {}).map(([_, id]) => UtilsServer.encodeID(id, this.salt));
|
|
1033
|
-
if (!_id.length)
|
|
1034
|
-
throw this.throwError("NO_RESULTS", tableName);
|
|
1035
1025
|
try {
|
|
1036
1026
|
await File.lock(join(tablePath, ".tmp"));
|
|
1037
|
-
await Promise.all(files.map(async (file) => renameList.push(
|
|
1038
|
-
? await File.createWorker("remove", [
|
|
1039
|
-
join(tablePath, file),
|
|
1040
|
-
where,
|
|
1041
|
-
])
|
|
1042
|
-
: await File.remove(join(tablePath, file), where))));
|
|
1027
|
+
await Promise.all(files.map(async (file) => renameList.push(await File.remove(join(tablePath, file), where))));
|
|
1043
1028
|
await Promise.all(renameList.map(async ([tempPath, filePath]) => rename(tempPath, filePath)));
|
|
1044
|
-
if (Config.isCacheEnabled)
|
|
1029
|
+
if (Config.isCacheEnabled)
|
|
1045
1030
|
await this.clearCache(tablePath);
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
}
|
|
1031
|
+
if (await File.isExists(join(tablePath, ".cache", `pagination${this.fileExtension}`))) {
|
|
1032
|
+
const [lastId, totalItems] = (await File.read(join(tablePath, ".cache", `pagination${this.fileExtension}`), true))
|
|
1033
|
+
.split(",")
|
|
1034
|
+
.map(Number);
|
|
1035
|
+
await File.write(join(tablePath, ".cache", `pagination${this.fileExtension}`), `${lastId},${totalItems - (Array.isArray(where) ? where.length : 1)}`, true);
|
|
1052
1036
|
}
|
|
1053
|
-
|
|
1037
|
+
if (_id)
|
|
1038
|
+
return Array.isArray(_id) && _id.length === 1 ? _id[0] : _id;
|
|
1039
|
+
return where;
|
|
1054
1040
|
}
|
|
1055
1041
|
finally {
|
|
1056
1042
|
if (renameList.length)
|
|
@@ -1073,7 +1059,7 @@ export default class Inibase {
|
|
|
1073
1059
|
if (!Array.isArray(columns))
|
|
1074
1060
|
columns = [columns];
|
|
1075
1061
|
for await (const column of columns) {
|
|
1076
|
-
const columnPath = join(tablePath, `${column}.
|
|
1062
|
+
const columnPath = join(tablePath, `${column}${this.getFileExtension()}`);
|
|
1077
1063
|
if (await File.isExists(columnPath)) {
|
|
1078
1064
|
if (where) {
|
|
1079
1065
|
const lineNumbers = await this.get(tableName, where, undefined, undefined, true, schema);
|
|
@@ -1093,7 +1079,7 @@ export default class Inibase {
|
|
|
1093
1079
|
if (!Array.isArray(columns))
|
|
1094
1080
|
columns = [columns];
|
|
1095
1081
|
for await (const column of columns) {
|
|
1096
|
-
const columnPath = join(tablePath, `${column}.
|
|
1082
|
+
const columnPath = join(tablePath, `${column}${this.getFileExtension()}`);
|
|
1097
1083
|
if (await File.isExists(columnPath)) {
|
|
1098
1084
|
if (where) {
|
|
1099
1085
|
const lineNumbers = await this.get(tableName, where, undefined, undefined, true, schema);
|
|
@@ -1113,7 +1099,7 @@ export default class Inibase {
|
|
|
1113
1099
|
if (!Array.isArray(columns))
|
|
1114
1100
|
columns = [columns];
|
|
1115
1101
|
for await (const column of columns) {
|
|
1116
|
-
const columnPath = join(tablePath, `${column}.
|
|
1102
|
+
const columnPath = join(tablePath, `${column}${this.getFileExtension()}`);
|
|
1117
1103
|
if (await File.isExists(columnPath)) {
|
|
1118
1104
|
if (where) {
|
|
1119
1105
|
const lineNumbers = await this.get(tableName, where, undefined, undefined, true, schema);
|
|
@@ -1131,7 +1117,6 @@ export default class Inibase {
|
|
|
1131
1117
|
page: 1,
|
|
1132
1118
|
perPage: 15,
|
|
1133
1119
|
}) {
|
|
1134
|
-
// TO-DO: Cache Results based on "Columns and Sort Direction"
|
|
1135
1120
|
const tablePath = join(this.folder, this.database, tableName), schema = await this.getSchemaWhenTableNotEmpty(tableName);
|
|
1136
1121
|
// Default values for page and perPage
|
|
1137
1122
|
options.page = options.page || 1;
|
|
@@ -1155,7 +1140,7 @@ export default class Inibase {
|
|
|
1155
1140
|
cacheKey = UtilsServer.hashString(inspect(sortArray, { sorted: true }));
|
|
1156
1141
|
if (where) {
|
|
1157
1142
|
const lineNumbers = await this.get(tableName, where, undefined, undefined, true, schema);
|
|
1158
|
-
keepItems = Object.values((await File.get(join(tablePath,
|
|
1143
|
+
keepItems = Object.values((await File.get(join(tablePath, `id${this.getFileExtension()}`), lineNumbers, "number", undefined, this.salt)) ?? {}).map(Number);
|
|
1159
1144
|
isLineNumbers = false;
|
|
1160
1145
|
if (!keepItems.length)
|
|
1161
1146
|
throw this.throwError("NO_RESULTS", tableName);
|
|
@@ -1165,7 +1150,7 @@ export default class Inibase {
|
|
|
1165
1150
|
keepItems = Array.from({ length: options.perPage }, (_, index) => (options.page - 1) * options.perPage +
|
|
1166
1151
|
index +
|
|
1167
1152
|
1);
|
|
1168
|
-
const filesPathes = [["id", true], ...sortArray].map((column) => join(tablePath, `${column[0]}.
|
|
1153
|
+
const filesPathes = [["id", true], ...sortArray].map((column) => join(tablePath, `${column[0]}${this.getFileExtension()}`));
|
|
1169
1154
|
// Construct the paste command to merge files and filter lines by IDs
|
|
1170
1155
|
const pasteCommand = `paste ${filesPathes.join(" ")}`;
|
|
1171
1156
|
// Construct the sort command dynamically based on the number of files for sorting
|
|
@@ -1190,10 +1175,10 @@ export default class Inibase {
|
|
|
1190
1175
|
await File.lock(join(tablePath, ".tmp"), cacheKey);
|
|
1191
1176
|
// Combine the commands
|
|
1192
1177
|
// Execute the command synchronously
|
|
1193
|
-
const { stdout
|
|
1194
|
-
? (await File.isExists(join(tablePath, ".cache", `${cacheKey}.
|
|
1195
|
-
? `${awkCommand} ${join(tablePath, ".cache", `${cacheKey}.
|
|
1196
|
-
: `${pasteCommand} | ${sortCommand} -o ${join(tablePath, ".cache", `${cacheKey}.
|
|
1178
|
+
const { stdout } = await UtilsServer.exec(Config.isCacheEnabled
|
|
1179
|
+
? (await File.isExists(join(tablePath, ".cache", `${cacheKey}${this.fileExtension}`)))
|
|
1180
|
+
? `${awkCommand} ${join(tablePath, ".cache", `${cacheKey}${this.fileExtension}`)}`
|
|
1181
|
+
: `${pasteCommand} | ${sortCommand} -o ${join(tablePath, ".cache", `${cacheKey}${this.fileExtension}`)} && ${awkCommand} ${join(tablePath, ".cache", `${cacheKey}${this.fileExtension}`)}`
|
|
1197
1182
|
: `${pasteCommand} | ${sortCommand} | ${awkCommand}`, {
|
|
1198
1183
|
encoding: "utf-8",
|
|
1199
1184
|
});
|
|
@@ -1202,7 +1187,7 @@ export default class Inibase {
|
|
|
1202
1187
|
const outputArray = lines.map((line) => {
|
|
1203
1188
|
const splitedFileColumns = line.split("\t"); // Assuming tab-separated columns
|
|
1204
1189
|
const outputObject = {};
|
|
1205
|
-
// Extract values for each file, including
|
|
1190
|
+
// Extract values for each file, including `id${this.getFileExtension()}`
|
|
1206
1191
|
filesPathes.forEach((fileName, index) => {
|
|
1207
1192
|
const Field = Utils.getField(parse(fileName).name, schema);
|
|
1208
1193
|
if (Field)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "inibase",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
3
|
+
"version": "1.0.0-rc.58",
|
|
4
4
|
"author": {
|
|
5
5
|
"name": "Karim Amahtil",
|
|
6
6
|
"email": "karim.amahtil@gmail.com"
|
|
@@ -9,9 +9,7 @@
|
|
|
9
9
|
"main": "./dist/index.js",
|
|
10
10
|
"exports": {
|
|
11
11
|
".": "./dist/index.js",
|
|
12
|
-
"./thread": "./dist/index.thread.js",
|
|
13
12
|
"./file": "./dist/file.js",
|
|
14
|
-
"./file.thread": "./dist/file.thread.js",
|
|
15
13
|
"./config": "./dist/config.js",
|
|
16
14
|
"./utils": "./dist/utils.js",
|
|
17
15
|
"./utils.server": "./dist/utils.server.js"
|
|
@@ -49,6 +47,9 @@
|
|
|
49
47
|
"pocketbase"
|
|
50
48
|
],
|
|
51
49
|
"license": "MIT",
|
|
50
|
+
"bin": {
|
|
51
|
+
"inibase": "./dist/cli.js"
|
|
52
|
+
},
|
|
52
53
|
"type": "module",
|
|
53
54
|
"types": "./dist",
|
|
54
55
|
"typesVersions": {
|
|
@@ -56,15 +57,9 @@
|
|
|
56
57
|
".": [
|
|
57
58
|
"./dist/index.d.ts"
|
|
58
59
|
],
|
|
59
|
-
"thread": [
|
|
60
|
-
"./dist/index.thread.d.ts"
|
|
61
|
-
],
|
|
62
60
|
"file": [
|
|
63
61
|
"./dist/file.d.ts"
|
|
64
62
|
],
|
|
65
|
-
"file.thread": [
|
|
66
|
-
"./dist/file.thread.d.ts"
|
|
67
|
-
],
|
|
68
63
|
"utils": [
|
|
69
64
|
"./dist/utils.d.ts"
|
|
70
65
|
],
|
|
@@ -77,18 +72,18 @@
|
|
|
77
72
|
}
|
|
78
73
|
},
|
|
79
74
|
"devDependencies": {
|
|
80
|
-
"@types/node": "^20.
|
|
81
|
-
"tinybench": "^2.6.0"
|
|
82
|
-
"typescript": "^5.3.3"
|
|
75
|
+
"@types/node": "^20.12.11",
|
|
76
|
+
"tinybench": "^2.6.0"
|
|
83
77
|
},
|
|
84
78
|
"dependencies": {
|
|
79
|
+
"commander": "^12.0.0",
|
|
80
|
+
"dotenv": "^16.4.5",
|
|
85
81
|
"inison": "^1.0.0-rc.2"
|
|
86
82
|
},
|
|
87
83
|
"scripts": {
|
|
88
|
-
"build": "
|
|
89
|
-
"
|
|
90
|
-
"benchmark": "
|
|
91
|
-
"benchmark:
|
|
92
|
-
"benchmark:bulk": "npx tsx --expose-gc --env-file=.env ./benchmark/bulk"
|
|
84
|
+
"build": "tsc",
|
|
85
|
+
"benchmark": "tsx --env-file=.env ./benchmark/index",
|
|
86
|
+
"benchmark:single": "tsx --expose-gc --env-file=.env ./benchmark/single",
|
|
87
|
+
"benchmark:bulk": "tsx --expose-gc --env-file=.env ./benchmark/bulk"
|
|
93
88
|
}
|
|
94
89
|
}
|
package/dist/file.thread.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/file.thread.js
DELETED
package/dist/index.thread.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/index.thread.js
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import Inibase from "./index.js";
|
|
2
|
-
import { parentPort, workerData } from "node:worker_threads";
|
|
3
|
-
const { _constructor, functionName, arg } = workerData;
|
|
4
|
-
// @ts-ignore
|
|
5
|
-
new Inibase(..._constructor)[functionName](...arg)
|
|
6
|
-
.then((res) => parentPort?.postMessage(res));
|