inibase 1.0.0-rc.4 → 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 +159 -91
- package/dist/config.d.ts +5 -0
- package/dist/config.js +5 -0
- package/dist/file.d.ts +176 -0
- package/dist/file.js +818 -0
- package/dist/file.thread.d.ts +1 -0
- package/dist/file.thread.js +5 -0
- package/dist/index.d.ts +109 -0
- package/dist/index.js +1106 -0
- package/dist/index.thread.d.ts +1 -0
- package/dist/index.thread.js +6 -0
- package/dist/utils.d.ts +195 -0
- package/dist/utils.js +427 -0
- package/dist/utils.server.d.ts +103 -0
- package/dist/utils.server.js +122 -0
- package/package.json +69 -18
- package/file.ts +0 -327
- package/index.ts +0 -1280
- package/tsconfig.json +0 -6
- package/utils.ts +0 -110
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
+
import { type Schema } from "./index.js";
|
|
3
|
+
/**
|
|
4
|
+
* Generates a hashed password using SHA-256.
|
|
5
|
+
*
|
|
6
|
+
* @param password - The plain text password to hash.
|
|
7
|
+
* @returns A string containing the salt and the hashed password, separated by a colon.
|
|
8
|
+
*/
|
|
9
|
+
export declare const hashPassword: (password: string) => string;
|
|
10
|
+
/**
|
|
11
|
+
* Compares a hashed password with an input password to verify a match.
|
|
12
|
+
*
|
|
13
|
+
* @param hashedPassword - The hashed password, containing both the salt and the hash, separated by a colon.
|
|
14
|
+
* @param inputPassword - The plain text input password to compare against the hashed password.
|
|
15
|
+
* @returns A boolean indicating whether the input password matches the hashed password.
|
|
16
|
+
*/
|
|
17
|
+
export declare const comparePassword: (hashedPassword: string, inputPassword: string) => boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Encodes an ID using AES-256-CBC encryption.
|
|
20
|
+
*
|
|
21
|
+
* @param id - The ID to encode, either a number or a string.
|
|
22
|
+
* @param secretKeyOrSalt - The secret key or salt for encryption, can be a string, number, or Buffer.
|
|
23
|
+
* @returns The encoded ID as a hexadecimal string.
|
|
24
|
+
*/
|
|
25
|
+
export declare const encodeID: (id: number | string, secretKeyOrSalt: string | number | Buffer) => string;
|
|
26
|
+
/**
|
|
27
|
+
* Decodes an encrypted ID using AES-256-CBC decryption.
|
|
28
|
+
*
|
|
29
|
+
* @param input - The encrypted ID as a hexadecimal string.
|
|
30
|
+
* @param secretKeyOrSalt - The secret key or salt used for decryption, can be a string, number, or Buffer.
|
|
31
|
+
* @returns The decoded ID as a number.
|
|
32
|
+
*/
|
|
33
|
+
export declare const decodeID: (input: string, secretKeyOrSalt: string | number | Buffer) => number;
|
|
34
|
+
/**
|
|
35
|
+
* Finds the last ID number in a schema, potentially decoding it if encrypted.
|
|
36
|
+
*
|
|
37
|
+
* @param schema - The schema to search, defined as an array of schema objects.
|
|
38
|
+
* @param secretKeyOrSalt - The secret key or salt for decoding an encrypted ID, can be a string, number, or Buffer.
|
|
39
|
+
* @returns The last ID number in the schema, decoded if necessary.
|
|
40
|
+
*/
|
|
41
|
+
export declare const findLastIdNumber: (schema: Schema, secretKeyOrSalt: string | number | Buffer) => number;
|
|
42
|
+
/**
|
|
43
|
+
* Adds or updates IDs in a schema, encoding them using a provided secret key or salt.
|
|
44
|
+
*
|
|
45
|
+
* @param schema - The schema to update, defined as an array of schema objects.
|
|
46
|
+
* @param oldIndex - The starting index for generating new IDs, defaults to 0.
|
|
47
|
+
* @param secretKeyOrSalt - The secret key or salt for encoding IDs, can be a string, number, or Buffer.
|
|
48
|
+
* @param encodeIDs - If true, IDs will be encoded, else they will remain as numbers.
|
|
49
|
+
* @returns The updated schema with encoded IDs.
|
|
50
|
+
*/
|
|
51
|
+
export declare const addIdToSchema: (schema: Schema, oldIndex: number | undefined, secretKeyOrSalt: string | number | Buffer, encodeIDs?: boolean) => (({
|
|
52
|
+
id?: string | number | undefined;
|
|
53
|
+
key: string;
|
|
54
|
+
required?: boolean | undefined;
|
|
55
|
+
} & {
|
|
56
|
+
type: "string" | "number" | "boolean" | "id" | "url" | "html" | "table" | "email" | "json" | "date" | "password" | "ip";
|
|
57
|
+
children?: undefined;
|
|
58
|
+
}) | ({
|
|
59
|
+
id?: string | number | undefined;
|
|
60
|
+
key: string;
|
|
61
|
+
required?: boolean | undefined;
|
|
62
|
+
} & {
|
|
63
|
+
type: "object";
|
|
64
|
+
children: Schema;
|
|
65
|
+
}) | ({
|
|
66
|
+
id?: string | number | undefined;
|
|
67
|
+
key: string;
|
|
68
|
+
required?: boolean | undefined;
|
|
69
|
+
} & {
|
|
70
|
+
type: "array";
|
|
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
|
+
}))[];
|
|
73
|
+
export declare const hashString: (str: string) => string;
|
|
74
|
+
export default class UtilsServer {
|
|
75
|
+
static encodeID: (id: string | number, secretKeyOrSalt: string | number | Buffer) => string;
|
|
76
|
+
static decodeID: (input: string, secretKeyOrSalt: string | number | Buffer) => number;
|
|
77
|
+
static hashPassword: (password: string) => string;
|
|
78
|
+
static comparePassword: (hashedPassword: string, inputPassword: string) => boolean;
|
|
79
|
+
static findLastIdNumber: (schema: Schema, secretKeyOrSalt: string | number | Buffer) => number;
|
|
80
|
+
static addIdToSchema: (schema: Schema, oldIndex: number | undefined, secretKeyOrSalt: string | number | Buffer, encodeIDs?: boolean | undefined) => (({
|
|
81
|
+
id?: string | number | undefined;
|
|
82
|
+
key: string;
|
|
83
|
+
required?: boolean | undefined;
|
|
84
|
+
} & {
|
|
85
|
+
type: "string" | "number" | "boolean" | "id" | "url" | "html" | "table" | "email" | "json" | "date" | "password" | "ip";
|
|
86
|
+
children?: undefined;
|
|
87
|
+
}) | ({
|
|
88
|
+
id?: string | number | undefined;
|
|
89
|
+
key: string;
|
|
90
|
+
required?: boolean | undefined;
|
|
91
|
+
} & {
|
|
92
|
+
type: "object";
|
|
93
|
+
children: Schema;
|
|
94
|
+
}) | ({
|
|
95
|
+
id?: string | number | undefined;
|
|
96
|
+
key: string;
|
|
97
|
+
required?: boolean | undefined;
|
|
98
|
+
} & {
|
|
99
|
+
type: "array";
|
|
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
|
+
}))[];
|
|
102
|
+
static hashString: (str: string) => string;
|
|
103
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { createCipheriv, createDecipheriv, randomBytes, scryptSync, createHash, } from "node:crypto";
|
|
2
|
+
import { isArrayOfObjects, isNumber, isValidID } from "./utils.js";
|
|
3
|
+
/**
|
|
4
|
+
* Generates a hashed password using SHA-256.
|
|
5
|
+
*
|
|
6
|
+
* @param password - The plain text password to hash.
|
|
7
|
+
* @returns A string containing the salt and the hashed password, separated by a colon.
|
|
8
|
+
*/
|
|
9
|
+
export const hashPassword = (password) => {
|
|
10
|
+
const salt = randomBytes(16).toString("hex");
|
|
11
|
+
const hash = createHash("sha256")
|
|
12
|
+
.update(password + salt)
|
|
13
|
+
.digest("hex");
|
|
14
|
+
return `${salt}:${hash}`;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Compares a hashed password with an input password to verify a match.
|
|
18
|
+
*
|
|
19
|
+
* @param hashedPassword - The hashed password, containing both the salt and the hash, separated by a colon.
|
|
20
|
+
* @param inputPassword - The plain text input password to compare against the hashed password.
|
|
21
|
+
* @returns A boolean indicating whether the input password matches the hashed password.
|
|
22
|
+
*/
|
|
23
|
+
export const comparePassword = (hashedPassword, inputPassword) => {
|
|
24
|
+
const [salt, originalHash] = hashedPassword.split(":");
|
|
25
|
+
const inputHash = createHash("sha256")
|
|
26
|
+
.update(inputPassword + salt)
|
|
27
|
+
.digest("hex");
|
|
28
|
+
return inputHash === originalHash;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Encodes an ID using AES-256-CBC encryption.
|
|
32
|
+
*
|
|
33
|
+
* @param id - The ID to encode, either a number or a string.
|
|
34
|
+
* @param secretKeyOrSalt - The secret key or salt for encryption, can be a string, number, or Buffer.
|
|
35
|
+
* @returns The encoded ID as a hexadecimal string.
|
|
36
|
+
*/
|
|
37
|
+
export const encodeID = (id, secretKeyOrSalt) => {
|
|
38
|
+
let cipher;
|
|
39
|
+
if (Buffer.isBuffer(secretKeyOrSalt))
|
|
40
|
+
cipher = createCipheriv("aes-256-cbc", secretKeyOrSalt, secretKeyOrSalt.subarray(0, 16));
|
|
41
|
+
else {
|
|
42
|
+
const salt = scryptSync(secretKeyOrSalt.toString(), (process.env.INIBASE_SECRET ?? "inibase") + "_salt", 32);
|
|
43
|
+
cipher = createCipheriv("aes-256-cbc", salt, salt.subarray(0, 16));
|
|
44
|
+
}
|
|
45
|
+
return cipher.update(id.toString(), "utf8", "hex") + cipher.final("hex");
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Decodes an encrypted ID using AES-256-CBC decryption.
|
|
49
|
+
*
|
|
50
|
+
* @param input - The encrypted ID as a hexadecimal string.
|
|
51
|
+
* @param secretKeyOrSalt - The secret key or salt used for decryption, can be a string, number, or Buffer.
|
|
52
|
+
* @returns The decoded ID as a number.
|
|
53
|
+
*/
|
|
54
|
+
export const decodeID = (input, secretKeyOrSalt) => {
|
|
55
|
+
let decipher;
|
|
56
|
+
if (Buffer.isBuffer(secretKeyOrSalt))
|
|
57
|
+
decipher = createDecipheriv("aes-256-cbc", secretKeyOrSalt, secretKeyOrSalt.subarray(0, 16));
|
|
58
|
+
else {
|
|
59
|
+
const salt = scryptSync(secretKeyOrSalt.toString(), (process.env.INIBASE_SECRET ?? "inibase") + "_salt", 32);
|
|
60
|
+
decipher = createDecipheriv("aes-256-cbc", salt, salt.subarray(0, 16));
|
|
61
|
+
}
|
|
62
|
+
return Number(decipher.update(input, "hex", "utf8") + decipher.final("utf8"));
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Finds the last ID number in a schema, potentially decoding it if encrypted.
|
|
66
|
+
*
|
|
67
|
+
* @param schema - The schema to search, defined as an array of schema objects.
|
|
68
|
+
* @param secretKeyOrSalt - The secret key or salt for decoding an encrypted ID, can be a string, number, or Buffer.
|
|
69
|
+
* @returns The last ID number in the schema, decoded if necessary.
|
|
70
|
+
*/
|
|
71
|
+
export const findLastIdNumber = (schema, secretKeyOrSalt) => {
|
|
72
|
+
const lastField = schema[schema.length - 1];
|
|
73
|
+
if (lastField) {
|
|
74
|
+
if ((lastField.type === "array" || lastField.type === "object") &&
|
|
75
|
+
isArrayOfObjects(lastField.children))
|
|
76
|
+
return findLastIdNumber(lastField.children, secretKeyOrSalt);
|
|
77
|
+
else if (lastField.id)
|
|
78
|
+
return isValidID(lastField.id)
|
|
79
|
+
? decodeID(lastField.id, secretKeyOrSalt)
|
|
80
|
+
: lastField.id;
|
|
81
|
+
}
|
|
82
|
+
return 0;
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Adds or updates IDs in a schema, encoding them using a provided secret key or salt.
|
|
86
|
+
*
|
|
87
|
+
* @param schema - The schema to update, defined as an array of schema objects.
|
|
88
|
+
* @param oldIndex - The starting index for generating new IDs, defaults to 0.
|
|
89
|
+
* @param secretKeyOrSalt - The secret key or salt for encoding IDs, can be a string, number, or Buffer.
|
|
90
|
+
* @param encodeIDs - If true, IDs will be encoded, else they will remain as numbers.
|
|
91
|
+
* @returns The updated schema with encoded IDs.
|
|
92
|
+
*/
|
|
93
|
+
export const addIdToSchema = (schema, oldIndex = 0, secretKeyOrSalt, encodeIDs) => schema.map((field) => {
|
|
94
|
+
if (!field.id) {
|
|
95
|
+
oldIndex++;
|
|
96
|
+
field.id = encodeIDs ? encodeID(oldIndex, secretKeyOrSalt) : oldIndex;
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
if (!isNumber(field.id))
|
|
100
|
+
oldIndex = decodeID(field.id, secretKeyOrSalt);
|
|
101
|
+
else {
|
|
102
|
+
oldIndex = field.id;
|
|
103
|
+
field.id = encodeIDs ? encodeID(field.id, secretKeyOrSalt) : field.id;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if ((field.type === "array" || field.type === "object") &&
|
|
107
|
+
isArrayOfObjects(field.children)) {
|
|
108
|
+
field.children = addIdToSchema(field.children, oldIndex, secretKeyOrSalt, encodeIDs);
|
|
109
|
+
oldIndex += field.children.length;
|
|
110
|
+
}
|
|
111
|
+
return field;
|
|
112
|
+
});
|
|
113
|
+
export const hashString = (str) => createHash("sha256").update(str).digest("hex");
|
|
114
|
+
export default class UtilsServer {
|
|
115
|
+
static encodeID = encodeID;
|
|
116
|
+
static decodeID = decodeID;
|
|
117
|
+
static hashPassword = hashPassword;
|
|
118
|
+
static comparePassword = comparePassword;
|
|
119
|
+
static findLastIdNumber = findLastIdNumber;
|
|
120
|
+
static addIdToSchema = addIdToSchema;
|
|
121
|
+
static hashString = hashString;
|
|
122
|
+
}
|
package/package.json
CHANGED
|
@@ -1,37 +1,88 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "inibase",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
4
|
-
"
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
"type": "git",
|
|
8
|
-
"url": "git+https://github.com/inicontent/inibase.git"
|
|
3
|
+
"version": "1.0.0-rc.40",
|
|
4
|
+
"author": {
|
|
5
|
+
"name": "Karim Amahtil",
|
|
6
|
+
"email": "karim.amahtil@gmail.com"
|
|
9
7
|
},
|
|
8
|
+
"repository": "inicontent/inibase",
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": "./dist/index.js",
|
|
12
|
+
"./thread": "./dist/index.thread.js",
|
|
13
|
+
"./file": "./dist/file.js",
|
|
14
|
+
"./file.thread": "./dist/file.thread.js",
|
|
15
|
+
"./config": "./dist/config.js",
|
|
16
|
+
"./utils": "./dist/utils.js",
|
|
17
|
+
"./utils.server": "./dist/utils.server.js"
|
|
18
|
+
},
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/inicontent/inibase/issues"
|
|
21
|
+
},
|
|
22
|
+
"description": "A file-based & memory-efficient, serverless, ACID compliant, relational database management system",
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=16"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"/dist"
|
|
28
|
+
],
|
|
29
|
+
"funding": "https://github.com/sponsors/inicontent",
|
|
30
|
+
"homepage": "https://github.com/inicontent/inibase#readme",
|
|
10
31
|
"keywords": [
|
|
11
|
-
"db",
|
|
12
32
|
"nosql",
|
|
13
|
-
"
|
|
33
|
+
"sqlite",
|
|
34
|
+
"sql",
|
|
35
|
+
"supabase",
|
|
36
|
+
"better-sqlite",
|
|
14
37
|
"mongodb",
|
|
38
|
+
"firebase",
|
|
39
|
+
"postgresql",
|
|
40
|
+
"rdms",
|
|
15
41
|
"database",
|
|
42
|
+
"db",
|
|
43
|
+
"mongoose",
|
|
16
44
|
"relational",
|
|
17
45
|
"local",
|
|
46
|
+
"file",
|
|
18
47
|
"storage",
|
|
19
48
|
"json",
|
|
20
|
-
"
|
|
21
|
-
"sqlite",
|
|
22
|
-
"supabase",
|
|
23
|
-
"firebase"
|
|
49
|
+
"pocketbase"
|
|
24
50
|
],
|
|
25
|
-
"author": "Inicontent",
|
|
26
51
|
"license": "MIT",
|
|
27
|
-
"
|
|
28
|
-
|
|
52
|
+
"type": "module",
|
|
53
|
+
"types": "./dist",
|
|
54
|
+
"typesVersions": {
|
|
55
|
+
"*": {
|
|
56
|
+
"thread": [
|
|
57
|
+
"./dist/index.thread.d.ts"
|
|
58
|
+
],
|
|
59
|
+
"file": [
|
|
60
|
+
"./dist/file.d.ts"
|
|
61
|
+
],
|
|
62
|
+
"file.thread": [
|
|
63
|
+
"./dist/file.thread.d.ts"
|
|
64
|
+
],
|
|
65
|
+
"utils": [
|
|
66
|
+
"./dist/utils.d.ts"
|
|
67
|
+
],
|
|
68
|
+
"config": [
|
|
69
|
+
"./dist/config.d.ts"
|
|
70
|
+
],
|
|
71
|
+
"utils.server": [
|
|
72
|
+
"./dist/utils.server.d.ts"
|
|
73
|
+
]
|
|
74
|
+
}
|
|
29
75
|
},
|
|
30
|
-
"homepage": "https://github.com/inicontent/inibase#readme",
|
|
31
76
|
"devDependencies": {
|
|
32
|
-
"@types/node": "^20.6
|
|
77
|
+
"@types/node": "^20.10.6",
|
|
78
|
+
"tinybench": "^2.6.0",
|
|
79
|
+
"typescript": "^5.3.3"
|
|
33
80
|
},
|
|
34
81
|
"scripts": {
|
|
35
|
-
"
|
|
82
|
+
"build": "npx tsc",
|
|
83
|
+
"test": "npx tsx watch --expose-gc --env-file=.env ./index.test",
|
|
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"
|
|
36
87
|
}
|
|
37
88
|
}
|
package/file.ts
DELETED
|
@@ -1,327 +0,0 @@
|
|
|
1
|
-
import { createWriteStream, unlinkSync, renameSync, existsSync } from "fs";
|
|
2
|
-
import { open } from "fs/promises";
|
|
3
|
-
import { parse } from "path";
|
|
4
|
-
import { ComparisonOperator, FieldType } from ".";
|
|
5
|
-
import Utils from "./utils";
|
|
6
|
-
|
|
7
|
-
export const encodeFileName = (fileName: string, extension?: string) => {
|
|
8
|
-
return (
|
|
9
|
-
fileName.replaceAll("%", "%25").replaceAll("*", "%") +
|
|
10
|
-
(extension ? `.${extension}` : "")
|
|
11
|
-
);
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export const decodeFileName = (fileName: string) => {
|
|
15
|
-
return fileName.replaceAll("%", "*").replaceAll("*25", "%");
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
export const get = async (
|
|
19
|
-
filePath: string,
|
|
20
|
-
fieldType?: FieldType,
|
|
21
|
-
lineNumbers?: number | number[]
|
|
22
|
-
) => {
|
|
23
|
-
const file = await open(filePath);
|
|
24
|
-
let lines: Record<
|
|
25
|
-
number,
|
|
26
|
-
| string
|
|
27
|
-
| number
|
|
28
|
-
| boolean
|
|
29
|
-
| (string | number | boolean | (string | number | boolean)[] | null)[]
|
|
30
|
-
| null
|
|
31
|
-
> = {},
|
|
32
|
-
lineCount = 0;
|
|
33
|
-
|
|
34
|
-
if (!lineNumbers) {
|
|
35
|
-
for await (const line of file.readLines())
|
|
36
|
-
lineCount++, (lines[lineCount] = Utils.decode(line, fieldType));
|
|
37
|
-
} else if (lineNumbers === -1) {
|
|
38
|
-
let lastLine;
|
|
39
|
-
for await (const line of file.readLines()) lineCount++, (lastLine = line);
|
|
40
|
-
if (lastLine) lines = { [lineCount]: Utils.decode(lastLine, fieldType) };
|
|
41
|
-
} else {
|
|
42
|
-
let lineNumbersArray = [
|
|
43
|
-
...(Array.isArray(lineNumbers) ? lineNumbers : [lineNumbers]),
|
|
44
|
-
];
|
|
45
|
-
for await (const line of file.readLines()) {
|
|
46
|
-
lineCount++;
|
|
47
|
-
if (!lineNumbersArray.includes(lineCount)) continue;
|
|
48
|
-
const indexOfLineCount = lineNumbersArray.indexOf(lineCount);
|
|
49
|
-
lines[lineCount] = Utils.decode(line, fieldType);
|
|
50
|
-
lineNumbersArray[indexOfLineCount] = 0;
|
|
51
|
-
if (!lineNumbersArray.filter((lineN) => lineN !== 0).length) break;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return lines ?? null;
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
export const replace = async (
|
|
59
|
-
filePath: string,
|
|
60
|
-
replacements:
|
|
61
|
-
| string
|
|
62
|
-
| number
|
|
63
|
-
| boolean
|
|
64
|
-
| null
|
|
65
|
-
| (string | number | boolean | null)[]
|
|
66
|
-
| Record<
|
|
67
|
-
number,
|
|
68
|
-
string | boolean | number | null | (string | boolean | number | null)[]
|
|
69
|
-
>
|
|
70
|
-
) => {
|
|
71
|
-
if (existsSync(filePath)) {
|
|
72
|
-
const file = await open(filePath, "w+"),
|
|
73
|
-
writeStream = file.createWriteStream();
|
|
74
|
-
if (typeof replacements === "object" && !Array.isArray(replacements)) {
|
|
75
|
-
let lineCount = 0;
|
|
76
|
-
for await (const line of file.readLines()) {
|
|
77
|
-
lineCount++;
|
|
78
|
-
writeStream.write(
|
|
79
|
-
(lineCount in replacements
|
|
80
|
-
? Utils.encode(replacements[lineCount])
|
|
81
|
-
: line) + "\n"
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
|
-
} else
|
|
85
|
-
for await (const _line of file.readLines())
|
|
86
|
-
writeStream.write(Utils.encode(replacements) + "\n");
|
|
87
|
-
|
|
88
|
-
writeStream.end();
|
|
89
|
-
} else if (typeof replacements === "object" && !Array.isArray(replacements)) {
|
|
90
|
-
const file = await open(filePath, "w"),
|
|
91
|
-
writeStream = file.createWriteStream(),
|
|
92
|
-
largestLinesNumbers =
|
|
93
|
-
Math.max(...Object.keys(replacements).map(Number)) + 1;
|
|
94
|
-
for (let lineCount = 1; lineCount < largestLinesNumbers; lineCount++) {
|
|
95
|
-
writeStream.write(
|
|
96
|
-
(lineCount in replacements
|
|
97
|
-
? Utils.encode(replacements[lineCount])
|
|
98
|
-
: "") + "\n"
|
|
99
|
-
);
|
|
100
|
-
}
|
|
101
|
-
writeStream.end();
|
|
102
|
-
}
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
export const remove = async (
|
|
106
|
-
filePath: string,
|
|
107
|
-
linesToDelete: number | number[]
|
|
108
|
-
): Promise<void> => {
|
|
109
|
-
let lineCount = 0;
|
|
110
|
-
|
|
111
|
-
const tempFilePath = `${filePath}.tmp`,
|
|
112
|
-
linesToDeleteArray = [
|
|
113
|
-
...(Array.isArray(linesToDelete) ? linesToDelete : [linesToDelete]),
|
|
114
|
-
],
|
|
115
|
-
writeStream = createWriteStream(tempFilePath),
|
|
116
|
-
file = await open(filePath);
|
|
117
|
-
|
|
118
|
-
for await (const line of file.readLines()) {
|
|
119
|
-
lineCount++;
|
|
120
|
-
if (!linesToDeleteArray.includes(lineCount)) {
|
|
121
|
-
writeStream.write(`${line}\n`);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
writeStream.end();
|
|
125
|
-
writeStream.on("finish", () => {
|
|
126
|
-
unlinkSync(filePath); // Remove the original file
|
|
127
|
-
renameSync(tempFilePath, filePath); // Rename the temp file to the original file name
|
|
128
|
-
});
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
export const count = async (filePath: string): Promise<number> => {
|
|
132
|
-
let lineCount = 0;
|
|
133
|
-
|
|
134
|
-
const file = await open(filePath);
|
|
135
|
-
|
|
136
|
-
for await (const line of file.readLines()) lineCount++;
|
|
137
|
-
|
|
138
|
-
return lineCount;
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
export const search = async (
|
|
142
|
-
filePath: string,
|
|
143
|
-
fieldType: FieldType,
|
|
144
|
-
operator: ComparisonOperator | ComparisonOperator[],
|
|
145
|
-
comparedAtValue:
|
|
146
|
-
| string
|
|
147
|
-
| number
|
|
148
|
-
| boolean
|
|
149
|
-
| null
|
|
150
|
-
| (string | number | boolean | null)[],
|
|
151
|
-
logicalOperator?: "and" | "or",
|
|
152
|
-
limit?: number,
|
|
153
|
-
offset?: number,
|
|
154
|
-
readWholeFile?: boolean
|
|
155
|
-
): Promise<
|
|
156
|
-
[
|
|
157
|
-
Record<
|
|
158
|
-
number,
|
|
159
|
-
Record<
|
|
160
|
-
string,
|
|
161
|
-
string | number | boolean | (string | number | boolean | null)[] | null
|
|
162
|
-
>
|
|
163
|
-
> | null,
|
|
164
|
-
number
|
|
165
|
-
]
|
|
166
|
-
> => {
|
|
167
|
-
const handleComparisonOperator = (
|
|
168
|
-
operator: ComparisonOperator,
|
|
169
|
-
value:
|
|
170
|
-
| string
|
|
171
|
-
| number
|
|
172
|
-
| boolean
|
|
173
|
-
| null
|
|
174
|
-
| (string | number | boolean | null)[],
|
|
175
|
-
comparedAtValue:
|
|
176
|
-
| string
|
|
177
|
-
| number
|
|
178
|
-
| boolean
|
|
179
|
-
| null
|
|
180
|
-
| (string | number | boolean | null)[],
|
|
181
|
-
fieldType: FieldType
|
|
182
|
-
): boolean => {
|
|
183
|
-
// check if not array or object
|
|
184
|
-
switch (operator) {
|
|
185
|
-
case "=":
|
|
186
|
-
switch (fieldType) {
|
|
187
|
-
case "password":
|
|
188
|
-
return typeof value === "string" &&
|
|
189
|
-
typeof comparedAtValue === "string"
|
|
190
|
-
? Utils.comparePassword(value, comparedAtValue)
|
|
191
|
-
: false;
|
|
192
|
-
case "boolean":
|
|
193
|
-
return Number(value) - Number(comparedAtValue) === 0;
|
|
194
|
-
default:
|
|
195
|
-
return value === comparedAtValue;
|
|
196
|
-
}
|
|
197
|
-
case "!=":
|
|
198
|
-
return !handleComparisonOperator(
|
|
199
|
-
"=",
|
|
200
|
-
value,
|
|
201
|
-
comparedAtValue,
|
|
202
|
-
fieldType
|
|
203
|
-
);
|
|
204
|
-
case ">":
|
|
205
|
-
return (
|
|
206
|
-
value !== null && comparedAtValue !== null && value > comparedAtValue
|
|
207
|
-
);
|
|
208
|
-
case "<":
|
|
209
|
-
return (
|
|
210
|
-
value !== null && comparedAtValue !== null && value < comparedAtValue
|
|
211
|
-
);
|
|
212
|
-
case ">=":
|
|
213
|
-
return (
|
|
214
|
-
value !== null && comparedAtValue !== null && value >= comparedAtValue
|
|
215
|
-
);
|
|
216
|
-
case "<=":
|
|
217
|
-
return (
|
|
218
|
-
value !== null && comparedAtValue !== null && value <= comparedAtValue
|
|
219
|
-
);
|
|
220
|
-
case "[]":
|
|
221
|
-
return (
|
|
222
|
-
(Array.isArray(value) &&
|
|
223
|
-
Array.isArray(comparedAtValue) &&
|
|
224
|
-
value.some(comparedAtValue.includes)) ||
|
|
225
|
-
(Array.isArray(value) &&
|
|
226
|
-
!Array.isArray(comparedAtValue) &&
|
|
227
|
-
value.includes(comparedAtValue)) ||
|
|
228
|
-
(!Array.isArray(value) &&
|
|
229
|
-
Array.isArray(comparedAtValue) &&
|
|
230
|
-
comparedAtValue.includes(value))
|
|
231
|
-
);
|
|
232
|
-
case "![]":
|
|
233
|
-
return !handleComparisonOperator(
|
|
234
|
-
"[]",
|
|
235
|
-
value,
|
|
236
|
-
comparedAtValue,
|
|
237
|
-
fieldType
|
|
238
|
-
);
|
|
239
|
-
case "*":
|
|
240
|
-
return (
|
|
241
|
-
value !== null &&
|
|
242
|
-
comparedAtValue !== null &&
|
|
243
|
-
new RegExp(
|
|
244
|
-
`^${comparedAtValue.toString().replace(/%/g, ".*")}$`,
|
|
245
|
-
"i"
|
|
246
|
-
).test(value.toString())
|
|
247
|
-
);
|
|
248
|
-
case "!*":
|
|
249
|
-
return !handleComparisonOperator(
|
|
250
|
-
"*",
|
|
251
|
-
value,
|
|
252
|
-
comparedAtValue,
|
|
253
|
-
fieldType
|
|
254
|
-
);
|
|
255
|
-
default:
|
|
256
|
-
throw new Error(operator);
|
|
257
|
-
}
|
|
258
|
-
};
|
|
259
|
-
|
|
260
|
-
let RETURN: Record<
|
|
261
|
-
number,
|
|
262
|
-
Record<
|
|
263
|
-
string,
|
|
264
|
-
string | number | boolean | null | (string | number | boolean | null)[]
|
|
265
|
-
>
|
|
266
|
-
> = {},
|
|
267
|
-
lineCount = 0,
|
|
268
|
-
foundItems = 0;
|
|
269
|
-
|
|
270
|
-
const file = await open(filePath),
|
|
271
|
-
columnName = decodeFileName(parse(filePath).name);
|
|
272
|
-
|
|
273
|
-
for await (const line of file.readLines()) {
|
|
274
|
-
lineCount++;
|
|
275
|
-
const decodedLine = Utils.decode(line, fieldType);
|
|
276
|
-
if (
|
|
277
|
-
(Array.isArray(operator) &&
|
|
278
|
-
Array.isArray(comparedAtValue) &&
|
|
279
|
-
((logicalOperator &&
|
|
280
|
-
logicalOperator === "or" &&
|
|
281
|
-
operator.some((single_operator, index) =>
|
|
282
|
-
handleComparisonOperator(
|
|
283
|
-
single_operator,
|
|
284
|
-
decodedLine,
|
|
285
|
-
comparedAtValue[index],
|
|
286
|
-
fieldType
|
|
287
|
-
)
|
|
288
|
-
)) ||
|
|
289
|
-
operator.every((single_operator, index) =>
|
|
290
|
-
handleComparisonOperator(
|
|
291
|
-
single_operator,
|
|
292
|
-
decodedLine,
|
|
293
|
-
comparedAtValue[index],
|
|
294
|
-
fieldType
|
|
295
|
-
)
|
|
296
|
-
))) ||
|
|
297
|
-
(!Array.isArray(operator) &&
|
|
298
|
-
handleComparisonOperator(
|
|
299
|
-
operator,
|
|
300
|
-
decodedLine,
|
|
301
|
-
comparedAtValue,
|
|
302
|
-
fieldType
|
|
303
|
-
))
|
|
304
|
-
) {
|
|
305
|
-
foundItems++;
|
|
306
|
-
if (offset && foundItems < offset) continue;
|
|
307
|
-
if (limit && foundItems > limit)
|
|
308
|
-
if (readWholeFile) continue;
|
|
309
|
-
else break;
|
|
310
|
-
if (!RETURN[lineCount]) RETURN[lineCount] = {};
|
|
311
|
-
RETURN[lineCount][columnName] = decodedLine;
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
if (foundItems) {
|
|
315
|
-
return [RETURN, readWholeFile ? foundItems : foundItems - 1];
|
|
316
|
-
} else return [null, 0];
|
|
317
|
-
};
|
|
318
|
-
|
|
319
|
-
export default class File {
|
|
320
|
-
static get = get;
|
|
321
|
-
static count = count;
|
|
322
|
-
static remove = remove;
|
|
323
|
-
static search = search;
|
|
324
|
-
static replace = replace;
|
|
325
|
-
static encodeFileName = encodeFileName;
|
|
326
|
-
static decodeFileName = decodeFileName;
|
|
327
|
-
}
|