inibase 1.0.0-rc.8 → 1.0.0-rc.80

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.
@@ -0,0 +1,260 @@
1
+ import { exec as execAsync, execFile as execFileAsync, } from "node:child_process";
2
+ import { createCipheriv, createDecipheriv, createHash, randomBytes, scryptSync, } from "node:crypto";
3
+ import { promisify } from "node:util";
4
+ import { gunzip as gunzipAsync, gzip as gzipAsync } from "node:zlib";
5
+ import { detectFieldType, isArrayOfObjects, isNumber, isPassword, isValidID, } from "./utils.js";
6
+ export const exec = promisify(execAsync);
7
+ export const execFile = promisify(execFileAsync);
8
+ export const gzip = promisify(gzipAsync);
9
+ export const gunzip = promisify(gunzipAsync);
10
+ /**
11
+ * Generates a hashed password using SHA-256.
12
+ *
13
+ * @param password - The plain text password to hash.
14
+ * @returns A string containing the salt and the hashed password, separated by a colon.
15
+ */
16
+ export const hashPassword = (password) => {
17
+ const salt = randomBytes(16).toString("hex");
18
+ const hash = createHash("sha256")
19
+ .update(password + salt)
20
+ .digest("hex");
21
+ return `${salt}:${hash}`;
22
+ };
23
+ /**
24
+ * Compares a hashed password with an input password to verify a match.
25
+ *
26
+ * @param hashedPassword - The hashed password, containing both the salt and the hash, separated by a colon.
27
+ * @param inputPassword - The plain text input password to compare against the hashed password.
28
+ * @returns A boolean indicating whether the input password matches the hashed password.
29
+ */
30
+ export const comparePassword = (hashedPassword, inputPassword) => {
31
+ const [salt, originalHash] = hashedPassword.split(":");
32
+ const inputHash = createHash("sha256")
33
+ .update(inputPassword + salt)
34
+ .digest("hex");
35
+ return inputHash === originalHash;
36
+ };
37
+ /**
38
+ * Encodes an ID using AES-256-CBC encryption.
39
+ *
40
+ * @param id - The ID to encode, either a number or a string.
41
+ * @param secretKeyOrSalt - The secret key or salt for encryption, can be a string, number, or Buffer.
42
+ * @returns The encoded ID as a hexadecimal string.
43
+ */
44
+ export const encodeID = (id, secretKeyOrSalt) => {
45
+ let cipher;
46
+ if (Buffer.isBuffer(secretKeyOrSalt))
47
+ cipher = createCipheriv("aes-256-cbc", secretKeyOrSalt, secretKeyOrSalt.subarray(0, 16));
48
+ else {
49
+ const salt = scryptSync(secretKeyOrSalt.toString(), `${process.env.INIBASE_SECRET ?? "inibase"}_salt`, 32);
50
+ cipher = createCipheriv("aes-256-cbc", salt, salt.subarray(0, 16));
51
+ }
52
+ return cipher.update(id.toString(), "utf8", "hex") + cipher.final("hex");
53
+ };
54
+ /**
55
+ * Decodes an encrypted ID using AES-256-CBC decryption.
56
+ *
57
+ * @param input - The encrypted ID as a hexadecimal string.
58
+ * @param secretKeyOrSalt - The secret key or salt used for decryption, can be a string, number, or Buffer.
59
+ * @returns The decoded ID as a number.
60
+ */
61
+ export const decodeID = (input, secretKeyOrSalt) => {
62
+ let decipher;
63
+ if (Buffer.isBuffer(secretKeyOrSalt))
64
+ decipher = createDecipheriv("aes-256-cbc", secretKeyOrSalt, secretKeyOrSalt.subarray(0, 16));
65
+ else {
66
+ const salt = scryptSync(secretKeyOrSalt.toString(), `${process.env.INIBASE_SECRET ?? "inibase"}_salt`, 32);
67
+ decipher = createDecipheriv("aes-256-cbc", salt, salt.subarray(0, 16));
68
+ }
69
+ return Number(decipher.update(input, "hex", "utf8") + decipher.final("utf8"));
70
+ };
71
+ // Function to recursively flatten an array of objects and their nested children
72
+ export const extractIdsFromSchema = (schema, secretKeyOrSalt) => {
73
+ const result = [];
74
+ for (const field of schema) {
75
+ if (field.id)
76
+ result.push(typeof field.id === "number"
77
+ ? field.id
78
+ : decodeID(field.id, secretKeyOrSalt));
79
+ if (field.children && isArrayOfObjects(field.children))
80
+ result.push(...extractIdsFromSchema(field.children, secretKeyOrSalt));
81
+ }
82
+ return result;
83
+ };
84
+ /**
85
+ * Finds the last ID number in a schema, potentially decoding it if encrypted.
86
+ *
87
+ * @param schema - The schema to search, defined as an array of schema objects.
88
+ * @param secretKeyOrSalt - The secret key or salt for decoding an encrypted ID, can be a string, number, or Buffer.
89
+ * @returns The last ID number in the schema, decoded if necessary.
90
+ */
91
+ export const findLastIdNumber = (schema, secretKeyOrSalt) => Math.max(...extractIdsFromSchema(schema, secretKeyOrSalt));
92
+ /**
93
+ * Adds or updates IDs in a schema, encoding them using a provided secret key or salt.
94
+ *
95
+ * @param schema - The schema to update, defined as an array of schema objects.
96
+ * @param oldIndex - The starting index for generating new IDs, defaults to 0.
97
+ * @param secretKeyOrSalt - The secret key or salt for encoding IDs, can be a string, number, or Buffer.
98
+ * @param encodeIDs - If true, IDs will be encoded, else they will remain as numbers.
99
+ * @returns The updated schema with encoded IDs.
100
+ */
101
+ export const addIdToSchema = (schema, startWithID, secretKeyOrSalt, encodeIDs) => {
102
+ function _addIdToField(field) {
103
+ if (!field.id) {
104
+ startWithID++;
105
+ field.id = encodeIDs
106
+ ? encodeID(startWithID, secretKeyOrSalt)
107
+ : startWithID;
108
+ }
109
+ else {
110
+ if (isValidID(field.id)) {
111
+ if (!encodeIDs)
112
+ field.id = decodeID(field.id, secretKeyOrSalt);
113
+ }
114
+ else if (encodeIDs)
115
+ field.id = encodeID(field.id, secretKeyOrSalt);
116
+ }
117
+ if ((field.type === "array" || field.type === "object") &&
118
+ isArrayOfObjects(field.children))
119
+ field.children = _addIdToSchema(field.children);
120
+ return field;
121
+ }
122
+ const _addIdToSchema = (schema) => schema.map(_addIdToField);
123
+ return _addIdToSchema(schema);
124
+ };
125
+ export const encodeSchemaID = (schema, secretKeyOrSalt) => schema.map((field) => ({
126
+ ...field,
127
+ id: isNumber(field.id) ? encodeID(field.id, secretKeyOrSalt) : field.id,
128
+ ...(field.children
129
+ ? isArrayOfObjects(field.children)
130
+ ? {
131
+ children: encodeSchemaID(field.children, secretKeyOrSalt),
132
+ }
133
+ : { children: field.children }
134
+ : {}),
135
+ }));
136
+ export const hashString = (str) => createHash("sha256").update(str).digest("hex");
137
+ /**
138
+ * Evaluates a comparison between two values based on a specified operator and field types.
139
+ *
140
+ * @param operator - The comparison operator (e.g., '=', '!=', '>', '<', '>=', '<=', '[]', '![]', '*', '!*').
141
+ * @param originalValue - The value to compare, can be a single value or an array of values.
142
+ * @param comparedAtValue - The value or values to compare against.
143
+ * @param fieldType - Optional type of the field to guide comparison (e.g., 'password', 'boolean').
144
+ * @param fieldChildrenType - Optional type for child elements in array inputs.
145
+ * @returns boolean - Result of the comparison operation.
146
+ *
147
+ * Note: Handles various data types and comparison logic, including special handling for passwords and regex patterns.
148
+ */
149
+ export const compare = (operator, originalValue, comparedAtValue, fieldType, fieldChildrenType) => {
150
+ // Determine the field type if it's an array of potential types.
151
+ if (Array.isArray(fieldType)) {
152
+ fieldType = detectFieldType(String(originalValue), fieldType);
153
+ }
154
+ // Handle comparisons involving arrays.
155
+ if (Array.isArray(comparedAtValue) && !["[]", "![]"].includes(operator)) {
156
+ return comparedAtValue.some((comparedAtValueSingle) => compare(operator, originalValue, comparedAtValueSingle, fieldType));
157
+ }
158
+ // Switch statement for different comparison operators.
159
+ switch (operator) {
160
+ // Equal (Case Insensitive for strings, specific handling for passwords and booleans).
161
+ case "=":
162
+ return isEqual(originalValue, comparedAtValue, fieldType);
163
+ // Not Equal.
164
+ case "!=":
165
+ return !isEqual(originalValue, comparedAtValue, fieldType);
166
+ // Greater Than.
167
+ case ">":
168
+ return (originalValue !== null &&
169
+ comparedAtValue !== null &&
170
+ originalValue > comparedAtValue);
171
+ // Less Than.
172
+ case "<":
173
+ return (originalValue !== null &&
174
+ comparedAtValue !== null &&
175
+ originalValue < comparedAtValue);
176
+ // Greater Than or Equal.
177
+ case ">=":
178
+ return (originalValue !== null &&
179
+ comparedAtValue !== null &&
180
+ originalValue >= comparedAtValue);
181
+ // Less Than or Equal.
182
+ case "<=":
183
+ return (originalValue !== null &&
184
+ comparedAtValue !== null &&
185
+ originalValue <= comparedAtValue);
186
+ // Array Contains (equality check for arrays).
187
+ case "[]":
188
+ return isArrayEqual(originalValue, comparedAtValue);
189
+ // Array Does Not Contain.
190
+ case "![]":
191
+ return !isArrayEqual(originalValue, comparedAtValue);
192
+ // Wildcard Match (using regex pattern).
193
+ case "*":
194
+ return isWildcardMatch(originalValue, comparedAtValue);
195
+ // Not Wildcard Match.
196
+ case "!*":
197
+ return !isWildcardMatch(originalValue, comparedAtValue);
198
+ // Unsupported operator.
199
+ default:
200
+ throw new Error(`Unsupported operator: ${operator}`);
201
+ }
202
+ };
203
+ /**
204
+ * Helper function to check equality based on the field type.
205
+ *
206
+ * @param originalValue - The original value.
207
+ * @param comparedAtValue - The value to compare against.
208
+ * @param fieldType - Type of the field.
209
+ * @returns boolean - Result of the equality check.
210
+ */
211
+ export const isEqual = (originalValue, comparedAtValue, fieldType) => {
212
+ // Switch based on the field type for specific handling.
213
+ switch (fieldType) {
214
+ // Password comparison.
215
+ case "password":
216
+ return isPassword(originalValue) && typeof comparedAtValue === "string"
217
+ ? comparePassword(originalValue, comparedAtValue)
218
+ : false;
219
+ // Boolean comparison.
220
+ case "boolean":
221
+ return Number(originalValue) === Number(comparedAtValue);
222
+ // Default comparison.
223
+ default:
224
+ return originalValue == comparedAtValue;
225
+ }
226
+ };
227
+ /**
228
+ * Helper function to check array equality.
229
+ *
230
+ * @param originalValue - The original value.
231
+ * @param comparedAtValue - The value to compare against.
232
+ * @returns boolean - Result of the array equality check.
233
+ */
234
+ export const isArrayEqual = (originalValue, comparedAtValue) => {
235
+ return ((Array.isArray(originalValue) &&
236
+ Array.isArray(comparedAtValue) &&
237
+ originalValue.some((v) => comparedAtValue.includes(v))) ||
238
+ (Array.isArray(originalValue) &&
239
+ !Array.isArray(comparedAtValue) &&
240
+ originalValue.includes(comparedAtValue)) ||
241
+ (!Array.isArray(originalValue) &&
242
+ Array.isArray(comparedAtValue) &&
243
+ comparedAtValue.includes(originalValue)) ||
244
+ (!Array.isArray(originalValue) &&
245
+ !Array.isArray(comparedAtValue) &&
246
+ comparedAtValue === originalValue));
247
+ };
248
+ /**
249
+ * Helper function to check wildcard pattern matching using regex.
250
+ *
251
+ * @param originalValue - The original value.
252
+ * @param comparedAtValue - The value with wildcard pattern.
253
+ * @returns boolean - Result of the wildcard pattern matching.
254
+ */
255
+ export const isWildcardMatch = (originalValue, comparedAtValue) => {
256
+ const wildcardPattern = `^${(String(comparedAtValue).includes("%")
257
+ ? String(comparedAtValue)
258
+ : `%${String(comparedAtValue)}%`).replace(/%/g, ".*")}$`;
259
+ return new RegExp(wildcardPattern, "i").test(String(originalValue));
260
+ };
package/package.json CHANGED
@@ -1,38 +1,85 @@
1
1
  {
2
2
  "name": "inibase",
3
- "version": "1.0.0-rc.8",
4
- "description": "File-based Relational Database for large data",
5
- "main": "index.ts",
3
+ "version": "1.0.0-rc.80",
6
4
  "type": "module",
7
- "scripts": {
8
- "test": "npx tsx watch ./index.test.ts"
5
+ "author": {
6
+ "name": "Karim Amahtil",
7
+ "email": "karim.amahtil@gmail.com"
8
+ },
9
+ "repository": "inicontent/inibase",
10
+ "main": "./dist/index.js",
11
+ "exports": {
12
+ ".": "./dist/index.js",
13
+ "./file": "./dist/file.js",
14
+ "./utils": "./dist/utils.js",
15
+ "./utils.server": "./dist/utils.server.js"
16
+ },
17
+ "bugs": {
18
+ "url": "https://github.com/inicontent/inibase/issues"
9
19
  },
10
- "repository": {
11
- "type": "git",
12
- "url": "git+https://github.com/inicontent/inibase.git"
20
+ "description": "A file-based & memory-efficient, serverless, ACID compliant, relational database management system",
21
+ "engines": {
22
+ "node": ">=16"
13
23
  },
24
+ "files": [
25
+ "/dist"
26
+ ],
27
+ "funding": "https://github.com/sponsors/inicontent",
28
+ "homepage": "https://github.com/inicontent/inibase#readme",
14
29
  "keywords": [
15
- "db",
16
30
  "nosql",
17
- "mongoose",
18
- "mongodb",
31
+ "rdms",
19
32
  "database",
33
+ "db",
34
+ "mongoose",
20
35
  "relational",
21
36
  "local",
37
+ "file",
22
38
  "storage",
23
39
  "json",
24
- "sql",
25
40
  "sqlite",
41
+ "sql",
26
42
  "supabase",
27
- "firebase"
43
+ "better-sqlite",
44
+ "mongodb",
45
+ "firebase",
46
+ "postgresql",
47
+ "pocketbase"
28
48
  ],
29
- "author": "Inicontent",
30
49
  "license": "MIT",
31
- "bugs": {
32
- "url": "https://github.com/inicontent/inibase/issues"
50
+ "bin": {
51
+ "inibase": "./dist/cli.js"
52
+ },
53
+ "types": "./dist",
54
+ "typesVersions": {
55
+ "*": {
56
+ ".": [
57
+ "./dist/index.d.ts"
58
+ ],
59
+ "file": [
60
+ "./dist/file.d.ts"
61
+ ],
62
+ "utils": [
63
+ "./dist/utils.d.ts"
64
+ ],
65
+ "utils.server": [
66
+ "./dist/utils.server.d.ts"
67
+ ]
68
+ }
33
69
  },
34
- "homepage": "https://github.com/inicontent/inibase#readme",
35
70
  "devDependencies": {
36
- "@types/node": "^20.8.6"
71
+ "@types/node": "^20.12.11",
72
+ "tinybench": "^2.6.0",
73
+ "typescript": "^5.4.5"
74
+ },
75
+ "dependencies": {
76
+ "dotenv": "^16.4.5",
77
+ "inison": "1.0.0-rc.3"
78
+ },
79
+ "scripts": {
80
+ "build": "npx tsc",
81
+ "benchmark": "npx tsx ./benchmark/index",
82
+ "benchmark:single": "npx tsx --expose-gc ./benchmark/single",
83
+ "benchmark:bulk": "npx tsx --expose-gc ./benchmark/bulk"
37
84
  }
38
- }
85
+ }