inibase 1.0.0-rc.39 → 1.0.0-rc.40
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 +5 -2
- package/dist/config.d.ts +1 -0
- package/dist/config.js +1 -0
- package/dist/file.d.ts +4 -4
- package/dist/file.js +38 -29
- package/dist/index.d.ts +2 -0
- package/dist/index.js +80 -60
- package/dist/utils.d.ts +1 -1
- package/dist/utils.server.d.ts +2 -2
- package/dist/utils.server.js +2 -30
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -519,9 +519,9 @@ await Promise.all(
|
|
|
519
519
|
|
|
520
520
|
</details>
|
|
521
521
|
|
|
522
|
-
## Config
|
|
522
|
+
## Config (.env)
|
|
523
523
|
|
|
524
|
-
The `.env` file supports the following parameters (make sure to run
|
|
524
|
+
The `.env` file supports the following parameters (make sure to run commands with flag --env-file=.env)
|
|
525
525
|
|
|
526
526
|
```ini
|
|
527
527
|
# Auto generated secret key, will be using for encrypting the IDs
|
|
@@ -529,6 +529,9 @@ INIBASE_SECRET=
|
|
|
529
529
|
|
|
530
530
|
INIBASE_COMPRESSION=true
|
|
531
531
|
INIBASE_CACHE=true
|
|
532
|
+
|
|
533
|
+
# Prepend new items to the beginning of file
|
|
534
|
+
INIBASE_REVERSE=true
|
|
532
535
|
```
|
|
533
536
|
|
|
534
537
|
## License
|
package/dist/config.d.ts
CHANGED
package/dist/config.js
CHANGED
package/dist/file.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/// <reference types="node" resolution-mode="require"/>
|
|
2
2
|
import { ComparisonOperator, FieldType } from "./index.js";
|
|
3
|
-
export declare const lock: (folderPath: string) => Promise<void>;
|
|
4
|
-
export declare const unlock: (folderPath: string) => Promise<void>;
|
|
3
|
+
export declare const lock: (folderPath: string, prefix?: string) => Promise<void>;
|
|
4
|
+
export declare const unlock: (folderPath: string, prefix?: string) => Promise<void>;
|
|
5
5
|
export declare const write: (filePath: string, data: any, disableCompression?: boolean) => Promise<void>;
|
|
6
6
|
export declare const read: (filePath: string, disableCompression?: boolean) => Promise<string>;
|
|
7
7
|
/**
|
|
@@ -170,7 +170,7 @@ export default class File {
|
|
|
170
170
|
static count: (filePath: string) => Promise<number>;
|
|
171
171
|
static write: (filePath: string, data: any, disableCompression?: boolean) => Promise<void>;
|
|
172
172
|
static read: (filePath: string, disableCompression?: boolean) => Promise<string>;
|
|
173
|
-
static lock: (folderPath: string) => Promise<void>;
|
|
174
|
-
static unlock: (folderPath: string) => Promise<void>;
|
|
173
|
+
static lock: (folderPath: string, prefix?: string | undefined) => Promise<void>;
|
|
174
|
+
static unlock: (folderPath: string, prefix?: string | undefined) => Promise<void>;
|
|
175
175
|
static createWorker: typeof createWorker;
|
|
176
176
|
}
|
package/dist/file.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { open, access, writeFile, readFile, constants as fsConstants, unlink, } from "node:fs/promises";
|
|
1
|
+
import { open, access, writeFile, readFile, constants as fsConstants, unlink, copyFile, appendFile, } 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";
|
|
@@ -11,23 +11,23 @@ import { encodeID, comparePassword } from "./utils.server.js";
|
|
|
11
11
|
import Config from "./config.js";
|
|
12
12
|
const gzip = promisify(gzipAsync);
|
|
13
13
|
const gunzip = promisify(gunzipAsync);
|
|
14
|
-
export const lock = async (folderPath) => {
|
|
15
|
-
let lockFile, lockFilePath = join(folderPath, "locked
|
|
14
|
+
export const lock = async (folderPath, prefix) => {
|
|
15
|
+
let lockFile, lockFilePath = join(folderPath, `${prefix ?? ""}.locked`);
|
|
16
16
|
try {
|
|
17
17
|
lockFile = await open(lockFilePath, "wx");
|
|
18
18
|
return;
|
|
19
19
|
}
|
|
20
20
|
catch ({ message }) {
|
|
21
21
|
if (message.split(":")[0] === "EEXIST")
|
|
22
|
-
return await new Promise((resolve, reject) => setTimeout(() => resolve(lock(folderPath)), 13));
|
|
22
|
+
return await new Promise((resolve, reject) => setTimeout(() => resolve(lock(folderPath, prefix)), 13));
|
|
23
23
|
}
|
|
24
24
|
finally {
|
|
25
25
|
await lockFile?.close();
|
|
26
26
|
}
|
|
27
27
|
};
|
|
28
|
-
export const unlock = async (folderPath) => {
|
|
28
|
+
export const unlock = async (folderPath, prefix) => {
|
|
29
29
|
try {
|
|
30
|
-
await unlink(join(folderPath, "locked
|
|
30
|
+
await unlink(join(folderPath, `${prefix ?? ""}.locked`));
|
|
31
31
|
}
|
|
32
32
|
catch { }
|
|
33
33
|
};
|
|
@@ -184,7 +184,10 @@ const unSecureString = (input) => {
|
|
|
184
184
|
"\\r": "\r",
|
|
185
185
|
};
|
|
186
186
|
// Replace encoded characters with their original symbols using the defined mapping.
|
|
187
|
-
|
|
187
|
+
const decodedString = input.replace(/%(2C|7C|26|24|23|40|5E|3A|21|3B|\\n|\\r)/g, (match) => replacements[match]) || null;
|
|
188
|
+
if (decodedString === null)
|
|
189
|
+
return null;
|
|
190
|
+
return isNumber(decodedString) ? Number(decodedString) : decodedString;
|
|
188
191
|
};
|
|
189
192
|
/**
|
|
190
193
|
* Reverses the process of 'joinMultidimensionalArray', splitting a string back into a multidimensional array.
|
|
@@ -370,29 +373,35 @@ export const replace = async (filePath, replacements) => {
|
|
|
370
373
|
export const append = async (filePath, data) => {
|
|
371
374
|
const fileTempPath = filePath.replace(/([^/]+)\/?$/, `.tmp/$1`);
|
|
372
375
|
if (await isExists(filePath)) {
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
fileTempHandle = await open(fileTempPath, "w");
|
|
377
|
-
rl = readLineInternface(fileHandle);
|
|
378
|
-
let isAppended = false;
|
|
379
|
-
await _pipeline(rl, fileTempHandle.createWriteStream(), new Transform({
|
|
380
|
-
transform(line, encoding, callback) {
|
|
381
|
-
if (!isAppended) {
|
|
382
|
-
isAppended = true;
|
|
383
|
-
return callback(null, `${Array.isArray(data) ? data.join("\n") : data}\n` +
|
|
384
|
-
(line.length ? `${line}\n` : ""));
|
|
385
|
-
}
|
|
386
|
-
else
|
|
387
|
-
return callback(null, `${line}\n`);
|
|
388
|
-
},
|
|
389
|
-
}));
|
|
376
|
+
if (!Config.isReverseEnabled && !Config.isCompressionEnabled) {
|
|
377
|
+
await copyFile(filePath, fileTempPath);
|
|
378
|
+
await appendFile(fileTempPath, `${Array.isArray(data) ? data.join("\n") : data}\n`);
|
|
390
379
|
}
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
380
|
+
else {
|
|
381
|
+
let fileHandle, fileTempHandle, rl;
|
|
382
|
+
try {
|
|
383
|
+
fileHandle = await open(filePath, "r");
|
|
384
|
+
fileTempHandle = await open(fileTempPath, "w");
|
|
385
|
+
rl = readLineInternface(fileHandle);
|
|
386
|
+
let isAppended = false;
|
|
387
|
+
await _pipeline(rl, fileTempHandle.createWriteStream(), new Transform({
|
|
388
|
+
transform(line, encoding, callback) {
|
|
389
|
+
if (!isAppended) {
|
|
390
|
+
isAppended = true;
|
|
391
|
+
return callback(null, `${Array.isArray(data) ? data.join("\n") : data}\n` +
|
|
392
|
+
(line.length ? `${line}\n` : ""));
|
|
393
|
+
}
|
|
394
|
+
else
|
|
395
|
+
return callback(null, `${line}\n`);
|
|
396
|
+
},
|
|
397
|
+
}));
|
|
398
|
+
}
|
|
399
|
+
finally {
|
|
400
|
+
// Ensure that file handles are closed, even if an error occurred
|
|
401
|
+
rl?.close();
|
|
402
|
+
await fileHandle?.close();
|
|
403
|
+
await fileTempHandle?.close();
|
|
404
|
+
}
|
|
396
405
|
}
|
|
397
406
|
}
|
|
398
407
|
else
|
package/dist/index.d.ts
CHANGED
|
@@ -57,6 +57,8 @@ declare global {
|
|
|
57
57
|
entries<T extends object>(o: T): Entries<T>;
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
|
+
export type ErrorCodes = "FIELD_REQUIRED" | "NO_SCHEMA" | "NO_ITEMS" | "NO_RESULTS" | "INVALID_ID" | "INVALID_TYPE" | "INVALID_PARAMETERS";
|
|
61
|
+
export type ErrorLang = "en";
|
|
60
62
|
export default class Inibase {
|
|
61
63
|
folder: string;
|
|
62
64
|
database: string;
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import File from "./file.js";
|
|
|
7
7
|
import Utils from "./utils.js";
|
|
8
8
|
import UtilsServer from "./utils.server.js";
|
|
9
9
|
import Config from "./config.js";
|
|
10
|
+
import { inspect } from "node:util";
|
|
10
11
|
export default class Inibase {
|
|
11
12
|
folder;
|
|
12
13
|
database;
|
|
@@ -32,27 +33,24 @@ export default class Inibase {
|
|
|
32
33
|
throwError(code, variable, language = "en") {
|
|
33
34
|
const errorMessages = {
|
|
34
35
|
en: {
|
|
35
|
-
FIELD_REQUIRED: "
|
|
36
|
-
NO_SCHEMA: "
|
|
37
|
-
NO_ITEMS: "
|
|
38
|
-
|
|
39
|
-
INVALID_ID: "
|
|
40
|
-
INVALID_TYPE: "
|
|
41
|
-
|
|
42
|
-
INVALID_PARAMETERS: "PARAMETERS: {variable}",
|
|
36
|
+
FIELD_REQUIRED: "Field {variable} is required",
|
|
37
|
+
NO_SCHEMA: "Table {variable} does't have a schema",
|
|
38
|
+
NO_ITEMS: "Table {variable} is empty",
|
|
39
|
+
NO_RESULTS: "No results found for table {variable}",
|
|
40
|
+
INVALID_ID: "The given ID(s) is/are not valid(s)",
|
|
41
|
+
INVALID_TYPE: "Expect {variable} to be {variable}, got {variable} instead",
|
|
42
|
+
INVALID_PARAMETERS: "The given parameters are not valid",
|
|
43
43
|
},
|
|
44
44
|
// Add more languages and error messages as needed
|
|
45
45
|
};
|
|
46
|
-
let errorMessage = errorMessages[language][code]
|
|
47
|
-
if (
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
return new Error(errorMessage);
|
|
46
|
+
let errorMessage = errorMessages[language][code];
|
|
47
|
+
if (!errorMessage)
|
|
48
|
+
return new Error("ERR");
|
|
49
|
+
return new Error(variable
|
|
50
|
+
? Array.isArray(variable)
|
|
51
|
+
? errorMessage.replace(/\{variable\}/g, () => variable.shift()?.toString() ?? "")
|
|
52
|
+
: errorMessage.replaceAll(`{variable}`, `'${variable.toString()}'`)
|
|
53
|
+
: errorMessage.replaceAll(`{variable}`, ""));
|
|
56
54
|
}
|
|
57
55
|
async createWorker(functionName, arg) {
|
|
58
56
|
return new Promise((resolve, reject) => {
|
|
@@ -105,6 +103,8 @@ export default class Inibase {
|
|
|
105
103
|
await mkdir(tablePath, { recursive: true });
|
|
106
104
|
if (!(await File.isExists(join(tablePath, ".tmp"))))
|
|
107
105
|
await mkdir(join(tablePath, ".tmp"));
|
|
106
|
+
if (!(await File.isExists(join(tablePath, ".cache"))))
|
|
107
|
+
await mkdir(join(tablePath, ".cache"));
|
|
108
108
|
if (await File.isExists(tableSchemaPath)) {
|
|
109
109
|
// update columns files names based on field id
|
|
110
110
|
const replaceOldPathes = Utils.findChangedProperties(this._schemaToIdsPath((await this.getTableSchema(tableName)) ?? []), this._schemaToIdsPath(schema));
|
|
@@ -180,7 +180,11 @@ export default class Inibase {
|
|
|
180
180
|
!Utils.isArrayOfObjects(field.children)
|
|
181
181
|
? field.children
|
|
182
182
|
: undefined))
|
|
183
|
-
throw this.throwError("INVALID_TYPE", [
|
|
183
|
+
throw this.throwError("INVALID_TYPE", [
|
|
184
|
+
field.key,
|
|
185
|
+
field.type,
|
|
186
|
+
typeof data[field.key],
|
|
187
|
+
]);
|
|
184
188
|
if ((field.type === "array" || field.type === "object") &&
|
|
185
189
|
field.children &&
|
|
186
190
|
Utils.isArrayOfObjects(field.children))
|
|
@@ -642,9 +646,9 @@ export default class Inibase {
|
|
|
642
646
|
.filter((i) => i);
|
|
643
647
|
}
|
|
644
648
|
async clearCache(tablePath) {
|
|
645
|
-
await Promise.all((await readdir(join(tablePath, ".
|
|
646
|
-
?.filter((fileName) =>
|
|
647
|
-
.map(async (file) => unlink(join(tablePath, ".
|
|
649
|
+
await Promise.all((await readdir(join(tablePath, ".cache")))
|
|
650
|
+
?.filter((fileName) => fileName !== "pagination.inib")
|
|
651
|
+
.map(async (file) => unlink(join(tablePath, ".cache", file))));
|
|
648
652
|
}
|
|
649
653
|
async get(tableName, where, options = {
|
|
650
654
|
page: 1,
|
|
@@ -676,15 +680,15 @@ export default class Inibase {
|
|
|
676
680
|
index +
|
|
677
681
|
1), options));
|
|
678
682
|
if (Config.isCacheEnabled &&
|
|
679
|
-
(await File.isExists(join(tablePath, ".
|
|
680
|
-
this.totalItems[tableName + "-*"] = Number((await File.read(join(tablePath, ".
|
|
683
|
+
(await File.isExists(join(tablePath, ".cache", "pagination.inib"))))
|
|
684
|
+
this.totalItems[tableName + "-*"] = Number((await File.read(join(tablePath, ".cache", "pagination.inib"), true)).split(",")[1]);
|
|
681
685
|
else {
|
|
682
686
|
let [lastId, totalItems] = await File.get(join(tablePath, "id.inib"), -1, "number", undefined, this.salt, true);
|
|
683
687
|
if (lastId)
|
|
684
688
|
lastId = Number(Object.keys(lastId)[0] ?? 0);
|
|
685
689
|
this.totalItems[tableName + "-*"] = totalItems;
|
|
686
690
|
if (Config.isCacheEnabled)
|
|
687
|
-
await File.write(join(tablePath, ".
|
|
691
|
+
await File.write(join(tablePath, ".cache", "pagination.inib"), `${lastId},${totalItems}`, true);
|
|
688
692
|
}
|
|
689
693
|
}
|
|
690
694
|
else if ((Array.isArray(where) && where.every(Utils.isNumber)) ||
|
|
@@ -708,7 +712,7 @@ export default class Inibase {
|
|
|
708
712
|
Ids = [Ids];
|
|
709
713
|
const [lineNumbers, countItems] = await File.search(join(tablePath, "id.inib"), "[]", Ids.map((id) => Utils.isNumber(id) ? Number(id) : UtilsServer.decodeID(id, this.salt)), undefined, "number", undefined, Ids.length, 0, !this.totalItems[tableName + "-*"], this.salt);
|
|
710
714
|
if (!lineNumbers)
|
|
711
|
-
throw this.throwError("
|
|
715
|
+
throw this.throwError("NO_RESULTS", tableName);
|
|
712
716
|
if (onlyLinesNumbers)
|
|
713
717
|
return Object.keys(lineNumbers).length
|
|
714
718
|
? Object.keys(lineNumbers).map(Number)
|
|
@@ -723,7 +727,7 @@ export default class Inibase {
|
|
|
723
727
|
let cachedFilePath = "";
|
|
724
728
|
// Criteria
|
|
725
729
|
if (Config.isCacheEnabled)
|
|
726
|
-
cachedFilePath = join(tablePath, ".
|
|
730
|
+
cachedFilePath = join(tablePath, ".cache", `${UtilsServer.hashString(inspect(where, { sorted: true }))}.inib`);
|
|
727
731
|
if (Config.isCacheEnabled && (await File.isExists(cachedFilePath))) {
|
|
728
732
|
const cachedItems = (await File.read(cachedFilePath, true)).split(",");
|
|
729
733
|
this.totalItems[tableName + "-*"] = cachedItems.length;
|
|
@@ -775,13 +779,14 @@ export default class Inibase {
|
|
|
775
779
|
let RETURN;
|
|
776
780
|
if (!schema)
|
|
777
781
|
throw this.throwError("NO_SCHEMA", tableName);
|
|
782
|
+
const keys = UtilsServer.hashString(Object.keys(Array.isArray(data) ? data[0] : data).join("."));
|
|
778
783
|
let lastId = 0, totalItems = 0, renameList = [];
|
|
779
784
|
try {
|
|
780
|
-
await File.lock(join(tablePath, ".tmp"));
|
|
785
|
+
await File.lock(join(tablePath, ".tmp"), keys);
|
|
781
786
|
if (await File.isExists(join(tablePath, "id.inib"))) {
|
|
782
787
|
if (Config.isCacheEnabled &&
|
|
783
|
-
(await File.isExists(join(tablePath, ".
|
|
784
|
-
[lastId, totalItems] = (await File.read(join(tablePath, ".
|
|
788
|
+
(await File.isExists(join(tablePath, ".cache", "pagination.inib"))))
|
|
789
|
+
[lastId, totalItems] = (await File.read(join(tablePath, ".cache", "pagination.inib"), true))
|
|
785
790
|
.split(",")
|
|
786
791
|
.map(Number);
|
|
787
792
|
else {
|
|
@@ -803,10 +808,12 @@ export default class Inibase {
|
|
|
803
808
|
...rest,
|
|
804
809
|
createdAt: Date.now(),
|
|
805
810
|
}))(data);
|
|
806
|
-
if (!RETURN)
|
|
807
|
-
throw this.throwError("NO_DATA");
|
|
808
811
|
RETURN = this.formatData(RETURN, schema);
|
|
809
|
-
const pathesContents = this.joinPathesContents(
|
|
812
|
+
const pathesContents = this.joinPathesContents(tablePath, Config.isReverseEnabled
|
|
813
|
+
? Array.isArray(RETURN)
|
|
814
|
+
? RETURN.toReversed()
|
|
815
|
+
: RETURN
|
|
816
|
+
: RETURN);
|
|
810
817
|
await Promise.all(Object.entries(pathesContents).map(async ([path, content]) => renameList.push(this.isThreadEnabled
|
|
811
818
|
? await File.createWorker("append", [path, content])
|
|
812
819
|
: await File.append(path, content))));
|
|
@@ -814,7 +821,7 @@ export default class Inibase {
|
|
|
814
821
|
renameList = [];
|
|
815
822
|
if (Config.isCacheEnabled) {
|
|
816
823
|
await this.clearCache(tablePath);
|
|
817
|
-
await File.write(join(tablePath, ".
|
|
824
|
+
await File.write(join(tablePath, ".cache", "pagination.inib"), `${lastId},${totalItems + (Array.isArray(RETURN) ? RETURN.length : 1)}`, true);
|
|
818
825
|
}
|
|
819
826
|
if (returnPostedData)
|
|
820
827
|
return this.get(tableName, Array.isArray(RETURN) ? RETURN.map((_, index) => index + 1) : 1, options, !Utils.isArrayOfObjects(data), // return only one item if data is not array of objects
|
|
@@ -823,7 +830,7 @@ export default class Inibase {
|
|
|
823
830
|
finally {
|
|
824
831
|
if (renameList.length)
|
|
825
832
|
await Promise.allSettled(renameList.map(async ([tempPath, _]) => unlink(tempPath)));
|
|
826
|
-
await File.unlock(join(tablePath, ".tmp"));
|
|
833
|
+
await File.unlock(join(tablePath, ".tmp"), keys);
|
|
827
834
|
}
|
|
828
835
|
}
|
|
829
836
|
async put(tableName, data, where, options = {
|
|
@@ -852,23 +859,33 @@ export default class Inibase {
|
|
|
852
859
|
return this.put(tableName, data, UtilsServer.decodeID(data.id, this.salt));
|
|
853
860
|
}
|
|
854
861
|
else {
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
862
|
+
let totalItems;
|
|
863
|
+
if (Config.isCacheEnabled &&
|
|
864
|
+
(await File.isExists(join(tablePath, ".cache", "pagination.inib"))))
|
|
865
|
+
totalItems = (await File.read(join(tablePath, ".cache", "pagination.inib"), true))
|
|
866
|
+
.split(",")
|
|
867
|
+
.map(Number)[1];
|
|
868
|
+
else
|
|
869
|
+
totalItems = await File.count(join(tablePath, "id.inib"));
|
|
870
|
+
const pathesContents = this.joinPathesContents(tablePath, {
|
|
871
|
+
...(({ id, ...restOfData }) => restOfData)(data),
|
|
872
|
+
updatedAt: Date.now(),
|
|
873
|
+
});
|
|
864
874
|
try {
|
|
865
875
|
await File.lock(join(tablePath, ".tmp"));
|
|
866
876
|
await Promise.all(Object.entries(pathesContents).map(async ([path, content]) => renameList.push(this.isThreadEnabled
|
|
867
|
-
? await File.createWorker("replace", [
|
|
868
|
-
|
|
877
|
+
? await File.createWorker("replace", [
|
|
878
|
+
path,
|
|
879
|
+
Utils.combineObjects([...Array(totalItems)].map((_, i) => ({
|
|
880
|
+
[`${i + 1}`]: content,
|
|
881
|
+
}))),
|
|
882
|
+
])
|
|
883
|
+
: await File.replace(path, Utils.combineObjects([...Array(totalItems)].map((_, i) => ({
|
|
884
|
+
[`${i + 1}`]: content,
|
|
885
|
+
})))))));
|
|
869
886
|
await Promise.all(renameList.map(async ([tempPath, filePath]) => rename(tempPath, filePath)));
|
|
870
887
|
if (Config.isCacheEnabled)
|
|
871
|
-
await this.clearCache(join(tablePath, ".
|
|
888
|
+
await this.clearCache(join(tablePath, ".cache"));
|
|
872
889
|
if (returnPostedData)
|
|
873
890
|
return this.get(tableName, where, options, undefined, undefined, schema);
|
|
874
891
|
}
|
|
@@ -891,7 +908,7 @@ export default class Inibase {
|
|
|
891
908
|
else if ((Array.isArray(where) && where.every(Utils.isNumber)) ||
|
|
892
909
|
Utils.isNumber(where)) {
|
|
893
910
|
// "where" in this case, is the line(s) number(s) and not id(s)
|
|
894
|
-
const pathesContents = Object.fromEntries(Object.entries(this.joinPathesContents(
|
|
911
|
+
const pathesContents = Object.fromEntries(Object.entries(this.joinPathesContents(tablePath, Utils.isArrayOfObjects(data)
|
|
895
912
|
? data.map((item) => ({
|
|
896
913
|
...item,
|
|
897
914
|
updatedAt: Date.now(),
|
|
@@ -903,8 +920,11 @@ export default class Inibase {
|
|
|
903
920
|
[lineNum]: Array.isArray(content) ? content[index] : content,
|
|
904
921
|
}), {}),
|
|
905
922
|
]));
|
|
923
|
+
const keys = UtilsServer.hashString(Object.keys(pathesContents)
|
|
924
|
+
.map((path) => path.replaceAll(".inib", ""))
|
|
925
|
+
.join("."));
|
|
906
926
|
try {
|
|
907
|
-
await File.lock(join(tablePath, ".tmp"));
|
|
927
|
+
await File.lock(join(tablePath, ".tmp"), keys);
|
|
908
928
|
await Promise.all(Object.entries(pathesContents).map(async ([path, content]) => renameList.push(this.isThreadEnabled
|
|
909
929
|
? await File.createWorker("replace", [path, content])
|
|
910
930
|
: await File.replace(path, content))));
|
|
@@ -918,18 +938,18 @@ export default class Inibase {
|
|
|
918
938
|
finally {
|
|
919
939
|
if (renameList.length)
|
|
920
940
|
await Promise.allSettled(renameList.map(async ([tempPath, _]) => unlink(tempPath)));
|
|
921
|
-
await File.unlock(join(tablePath, ".tmp"));
|
|
941
|
+
await File.unlock(join(tablePath, ".tmp"), keys);
|
|
922
942
|
}
|
|
923
943
|
}
|
|
924
944
|
}
|
|
925
945
|
else if (Utils.isObject(where)) {
|
|
926
946
|
const lineNumbers = await this.get(tableName, where, undefined, undefined, true, schema);
|
|
927
947
|
if (!lineNumbers || !lineNumbers.length)
|
|
928
|
-
throw this.throwError("
|
|
948
|
+
throw this.throwError("NO_RESULTS", tableName);
|
|
929
949
|
return this.put(tableName, data, lineNumbers);
|
|
930
950
|
}
|
|
931
951
|
else
|
|
932
|
-
throw this.throwError("INVALID_PARAMETERS"
|
|
952
|
+
throw this.throwError("INVALID_PARAMETERS");
|
|
933
953
|
}
|
|
934
954
|
async delete(tableName, where, _id) {
|
|
935
955
|
let renameList = [];
|
|
@@ -942,7 +962,7 @@ export default class Inibase {
|
|
|
942
962
|
if (!where) {
|
|
943
963
|
try {
|
|
944
964
|
await File.lock(join(tablePath, ".tmp"));
|
|
945
|
-
await Promise.all((await readdir(
|
|
965
|
+
await Promise.all((await readdir(tablePath))
|
|
946
966
|
?.filter((fileName) => fileName.endsWith(".inib"))
|
|
947
967
|
.map(async (file) => unlink(join(tablePath, file))));
|
|
948
968
|
if (Config.isCacheEnabled)
|
|
@@ -967,12 +987,12 @@ export default class Inibase {
|
|
|
967
987
|
else if ((Array.isArray(where) && where.every(Utils.isNumber)) ||
|
|
968
988
|
Utils.isNumber(where)) {
|
|
969
989
|
// "where" in this case, is the line(s) number(s) and not id(s)
|
|
970
|
-
const files = (await readdir(
|
|
990
|
+
const files = (await readdir(tablePath))?.filter((fileName) => fileName.endsWith(".inib"));
|
|
971
991
|
if (files.length) {
|
|
972
992
|
if (!_id)
|
|
973
993
|
_id = Object.entries((await File.get(join(tablePath, "id.inib"), where, "number", undefined, this.salt)) ?? {}).map(([_key, id]) => UtilsServer.encodeID(Number(id), this.salt));
|
|
974
994
|
if (!_id.length)
|
|
975
|
-
throw this.throwError("
|
|
995
|
+
throw this.throwError("NO_RESULTS", tableName);
|
|
976
996
|
try {
|
|
977
997
|
await File.lock(join(tablePath, ".tmp"));
|
|
978
998
|
await Promise.all(files.map(async (file) => renameList.push(this.isThreadEnabled
|
|
@@ -984,11 +1004,11 @@ export default class Inibase {
|
|
|
984
1004
|
await Promise.all(renameList.map(async ([tempPath, filePath]) => rename(tempPath, filePath)));
|
|
985
1005
|
if (Config.isCacheEnabled) {
|
|
986
1006
|
await this.clearCache(tablePath);
|
|
987
|
-
if (await File.isExists(join(tablePath, ".
|
|
988
|
-
let [lastId, totalItems] = (await File.read(join(tablePath, ".
|
|
1007
|
+
if (await File.isExists(join(tablePath, ".cache", "pagination.inib"))) {
|
|
1008
|
+
let [lastId, totalItems] = (await File.read(join(tablePath, ".cache", "pagination.inib"), true))
|
|
989
1009
|
.split(",")
|
|
990
1010
|
.map(Number);
|
|
991
|
-
await File.write(join(tablePath, ".
|
|
1011
|
+
await File.write(join(tablePath, ".cache", "pagination.inib"), `${lastId},${totalItems - (Array.isArray(where) ? where.length : 1)}`, true);
|
|
992
1012
|
}
|
|
993
1013
|
}
|
|
994
1014
|
return Array.isArray(_id) && _id.length === 1 ? _id[0] : _id;
|
|
@@ -1004,11 +1024,11 @@ export default class Inibase {
|
|
|
1004
1024
|
else if (Utils.isObject(where)) {
|
|
1005
1025
|
const lineNumbers = await this.get(tableName, where, undefined, undefined, true, schema);
|
|
1006
1026
|
if (!lineNumbers || !lineNumbers.length)
|
|
1007
|
-
throw this.throwError("
|
|
1027
|
+
throw this.throwError("NO_RESULTS", tableName);
|
|
1008
1028
|
return this.delete(tableName, lineNumbers);
|
|
1009
1029
|
}
|
|
1010
1030
|
else
|
|
1011
|
-
throw this.throwError("INVALID_PARAMETERS"
|
|
1031
|
+
throw this.throwError("INVALID_PARAMETERS");
|
|
1012
1032
|
return null;
|
|
1013
1033
|
}
|
|
1014
1034
|
async sum(tableName, columns, where) {
|
package/dist/utils.d.ts
CHANGED
package/dist/utils.server.d.ts
CHANGED
|
@@ -70,7 +70,7 @@ export declare const addIdToSchema: (schema: Schema, oldIndex: number | undefine
|
|
|
70
70
|
type: "array";
|
|
71
71
|
children: "string" | "number" | "boolean" | "object" | "id" | "url" | "html" | "table" | "email" | "json" | "date" | "password" | "ip" | Schema | ("string" | "number" | "boolean" | "object" | "id" | "url" | "html" | "table" | "email" | "json" | "date" | "password" | "ip")[];
|
|
72
72
|
}))[];
|
|
73
|
-
export declare const
|
|
73
|
+
export declare const hashString: (str: string) => string;
|
|
74
74
|
export default class UtilsServer {
|
|
75
75
|
static encodeID: (id: string | number, secretKeyOrSalt: string | number | Buffer) => string;
|
|
76
76
|
static decodeID: (input: string, secretKeyOrSalt: string | number | Buffer) => number;
|
|
@@ -99,5 +99,5 @@ export default class UtilsServer {
|
|
|
99
99
|
type: "array";
|
|
100
100
|
children: "string" | "number" | "boolean" | "object" | "id" | "url" | "html" | "table" | "email" | "json" | "date" | "password" | "ip" | Schema | ("string" | "number" | "boolean" | "object" | "id" | "url" | "html" | "table" | "email" | "json" | "date" | "password" | "ip")[];
|
|
101
101
|
}))[];
|
|
102
|
-
static
|
|
102
|
+
static hashString: (str: string) => string;
|
|
103
103
|
}
|
package/dist/utils.server.js
CHANGED
|
@@ -110,35 +110,7 @@ export const addIdToSchema = (schema, oldIndex = 0, secretKeyOrSalt, encodeIDs)
|
|
|
110
110
|
}
|
|
111
111
|
return field;
|
|
112
112
|
});
|
|
113
|
-
|
|
114
|
-
if (typeof obj !== "object" || obj === null)
|
|
115
|
-
return obj;
|
|
116
|
-
if (Array.isArray(obj))
|
|
117
|
-
return obj.toSorted().map(sortObject);
|
|
118
|
-
const sorted = {};
|
|
119
|
-
const keys = Object.keys(obj).sort();
|
|
120
|
-
const length = keys.length;
|
|
121
|
-
for (let i = 0; i < length; i++) {
|
|
122
|
-
const key = keys[i];
|
|
123
|
-
sorted[key] = sortObject(obj[key]);
|
|
124
|
-
}
|
|
125
|
-
return sorted;
|
|
126
|
-
}
|
|
127
|
-
function stringifyObject(obj) {
|
|
128
|
-
if (typeof obj !== "object" || obj === null)
|
|
129
|
-
return String(obj);
|
|
130
|
-
if (Array.isArray(obj))
|
|
131
|
-
return "[" + obj.map((value) => stringifyObject(value)).join(",") + "]";
|
|
132
|
-
return ("{" +
|
|
133
|
-
Object.keys(obj)
|
|
134
|
-
.toSorted()
|
|
135
|
-
.map((key) => `${key}:${stringifyObject(obj[key])}`)
|
|
136
|
-
.join(",") +
|
|
137
|
-
"}");
|
|
138
|
-
}
|
|
139
|
-
export const hashObject = (obj) => createHash("sha256")
|
|
140
|
-
.update(stringifyObject(sortObject(obj)))
|
|
141
|
-
.digest("hex");
|
|
113
|
+
export const hashString = (str) => createHash("sha256").update(str).digest("hex");
|
|
142
114
|
export default class UtilsServer {
|
|
143
115
|
static encodeID = encodeID;
|
|
144
116
|
static decodeID = decodeID;
|
|
@@ -146,5 +118,5 @@ export default class UtilsServer {
|
|
|
146
118
|
static comparePassword = comparePassword;
|
|
147
119
|
static findLastIdNumber = findLastIdNumber;
|
|
148
120
|
static addIdToSchema = addIdToSchema;
|
|
149
|
-
static
|
|
121
|
+
static hashString = hashString;
|
|
150
122
|
}
|
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.40",
|
|
4
4
|
"author": {
|
|
5
5
|
"name": "Karim Amahtil",
|
|
6
6
|
"email": "karim.amahtil@gmail.com"
|
|
@@ -75,12 +75,14 @@
|
|
|
75
75
|
},
|
|
76
76
|
"devDependencies": {
|
|
77
77
|
"@types/node": "^20.10.6",
|
|
78
|
+
"tinybench": "^2.6.0",
|
|
78
79
|
"typescript": "^5.3.3"
|
|
79
80
|
},
|
|
80
81
|
"scripts": {
|
|
81
82
|
"build": "npx tsc",
|
|
82
83
|
"test": "npx tsx watch --expose-gc --env-file=.env ./index.test",
|
|
83
|
-
"benchmark
|
|
84
|
-
"benchmark:
|
|
84
|
+
"benchmark": "npx tsx watch --env-file=.env ./benchmark/index",
|
|
85
|
+
"benchmark:single": "npx tsx --expose-gc --env-file=.env ./benchmark/single",
|
|
86
|
+
"benchmark:bulk": "npx tsx --expose-gc --env-file=.env ./benchmark/bulk"
|
|
85
87
|
}
|
|
86
88
|
}
|