inibase 1.0.0-rc.9 → 1.0.0-rc.90

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,248 @@
1
+ import { exec as execSync, execFile as execFileSync } from "node:child_process";
2
+ import { createCipheriv, createDecipheriv, createHash, randomBytes, scryptSync, } from "node:crypto";
3
+ import { gunzip as gunzipSync, gzip as gzipSync } from "node:zlib";
4
+ import { promisify } from "node:util";
5
+ import { detectFieldType, isArrayOfObjects, isNumber, isPassword, isValidID, } from "./utils.js";
6
+ export const exec = promisify(execSync);
7
+ export const execFile = promisify(execFileSync);
8
+ export const gzip = promisify(gzipSync);
9
+ export const gunzip = promisify(gunzipSync);
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 = (hash, password) => {
31
+ const [salt, originalHash] = hash.split(":");
32
+ const inputHash = createHash("sha256")
33
+ .update(password + salt)
34
+ .digest("hex");
35
+ return inputHash === originalHash;
36
+ };
37
+ // Cache for derived keys if using scrypt
38
+ const derivedKeyCache = new Map();
39
+ // Helper function to create cipher or decipher
40
+ const getKeyAndIv = (secretKeyOrSalt) => {
41
+ if (Buffer.isBuffer(secretKeyOrSalt)) {
42
+ return { key: secretKeyOrSalt, iv: secretKeyOrSalt.subarray(0, 16) };
43
+ }
44
+ const cacheKey = secretKeyOrSalt.toString();
45
+ let key = derivedKeyCache.get(cacheKey);
46
+ if (!key) {
47
+ key = scryptSync(cacheKey, `${INIBASE_SECRET}`, 32);
48
+ derivedKeyCache.set(cacheKey, key); // Cache the derived key
49
+ }
50
+ return { key, iv: key.subarray(0, 16) };
51
+ };
52
+ // Ensure the environment variable is read once
53
+ const INIBASE_SECRET = process.env.INIBASE_SECRET ?? "inibase";
54
+ // Optimized encodeID
55
+ export const encodeID = (id, secretKeyOrSalt) => {
56
+ const { key, iv } = getKeyAndIv(secretKeyOrSalt);
57
+ const cipher = createCipheriv("aes-256-cbc", key, iv);
58
+ return cipher.update(id.toString(), "utf8", "hex") + cipher.final("hex");
59
+ };
60
+ // Optimized decodeID
61
+ export const decodeID = (input, secretKeyOrSalt) => {
62
+ const { key, iv } = getKeyAndIv(secretKeyOrSalt);
63
+ const decipher = createDecipheriv("aes-256-cbc", key, iv);
64
+ return Number(decipher.update(input, "hex", "utf8") + decipher.final("utf8"));
65
+ };
66
+ // Function to recursively flatten an array of objects and their nested children
67
+ export const extractIdsFromSchema = (schema, secretKeyOrSalt) => {
68
+ const result = [];
69
+ for (const field of schema) {
70
+ if (field.id)
71
+ result.push(typeof field.id === "number"
72
+ ? field.id
73
+ : decodeID(field.id, secretKeyOrSalt));
74
+ if (field.children && isArrayOfObjects(field.children))
75
+ result.push(...extractIdsFromSchema(field.children, secretKeyOrSalt));
76
+ }
77
+ return result;
78
+ };
79
+ /**
80
+ * Finds the last ID number in a schema, potentially decoding it if encrypted.
81
+ *
82
+ * @param schema - The schema to search, defined as an array of schema objects.
83
+ * @param secretKeyOrSalt - The secret key or salt for decoding an encrypted ID, can be a string, number, or Buffer.
84
+ * @returns The last ID number in the schema, decoded if necessary.
85
+ */
86
+ export const findLastIdNumber = (schema, secretKeyOrSalt) => Math.max(...extractIdsFromSchema(schema, secretKeyOrSalt));
87
+ /**
88
+ * Adds or updates IDs in a schema, encoding them using a provided secret key or salt.
89
+ *
90
+ * @param schema - The schema to update, defined as an array of schema objects.
91
+ * @param oldIndex - The starting index for generating new IDs, defaults to 0.
92
+ * @param secretKeyOrSalt - The secret key or salt for encoding IDs, can be a string, number, or Buffer.
93
+ * @param encodeIDs - If true, IDs will be encoded, else they will remain as numbers.
94
+ * @returns The updated schema with encoded IDs.
95
+ */
96
+ export const addIdToSchema = (schema, startWithID, secretKeyOrSalt, encodeIDs) => {
97
+ function _addIdToField(field) {
98
+ if (!field.id) {
99
+ startWithID++;
100
+ field.id = encodeIDs
101
+ ? encodeID(startWithID, secretKeyOrSalt)
102
+ : startWithID;
103
+ }
104
+ else {
105
+ if (isValidID(field.id)) {
106
+ if (!encodeIDs)
107
+ field.id = decodeID(field.id, secretKeyOrSalt);
108
+ }
109
+ else if (encodeIDs)
110
+ field.id = encodeID(field.id, secretKeyOrSalt);
111
+ }
112
+ if ((field.type === "array" || field.type === "object") &&
113
+ isArrayOfObjects(field.children))
114
+ field.children = _addIdToSchema(field.children);
115
+ return field;
116
+ }
117
+ const _addIdToSchema = (schema) => schema.map(_addIdToField);
118
+ return _addIdToSchema(schema);
119
+ };
120
+ export const encodeSchemaID = (schema, secretKeyOrSalt) => schema.map((field) => ({
121
+ ...field,
122
+ id: isNumber(field.id) ? encodeID(field.id, secretKeyOrSalt) : field.id,
123
+ ...(field.children
124
+ ? isArrayOfObjects(field.children)
125
+ ? {
126
+ children: encodeSchemaID(field.children, secretKeyOrSalt),
127
+ }
128
+ : { children: field.children }
129
+ : {}),
130
+ }));
131
+ export const hashString = (str) => createHash("sha256").update(str).digest("hex");
132
+ /**
133
+ * Evaluates a comparison between two values based on a specified operator and field types.
134
+ *
135
+ * @param operator - The comparison operator (e.g., '=', '!=', '>', '<', '>=', '<=', '[]', '![]', '*', '!*').
136
+ * @param originalValue - The value to compare, can be a single value or an array of values.
137
+ * @param comparedValue - The value or values to compare against.
138
+ * @param fieldType - Optional type of the field to guide comparison (e.g., 'password', 'boolean').
139
+ * @param fieldChildrenType - Optional type for child elements in array inputs.
140
+ * @returns boolean - Result of the comparison operation.
141
+ *
142
+ * Note: Handles various data types and comparison logic, including special handling for passwords and regex patterns.
143
+ */
144
+ export const compare = (operator, originalValue, comparedValue, fieldType) => {
145
+ // Determine the field type if it's an array of potential types.
146
+ if (Array.isArray(fieldType))
147
+ fieldType = detectFieldType(String(originalValue), fieldType);
148
+ // Handle comparisons involving arrays.
149
+ if (Array.isArray(comparedValue) && !["[]", "![]"].includes(operator))
150
+ return comparedValue.some((value) => compare(operator, originalValue, value, fieldType));
151
+ // Switch statement for different comparison operators.
152
+ switch (operator) {
153
+ // Equal (Case Insensitive for strings, specific handling for passwords and booleans).
154
+ case "=":
155
+ return isEqual(originalValue, comparedValue, fieldType);
156
+ // Not Equal.
157
+ case "!=":
158
+ return !isEqual(originalValue, comparedValue, fieldType);
159
+ // Greater Than.
160
+ case ">":
161
+ return compareNonNullValues(originalValue, comparedValue, (a, b) => a > b);
162
+ // Less Than.
163
+ case "<":
164
+ return compareNonNullValues(originalValue, comparedValue, (a, b) => a < b);
165
+ // Greater Than or Equal.
166
+ case ">=":
167
+ return compareNonNullValues(originalValue, comparedValue, (a, b) => a >= b);
168
+ // Less Than or Equal.
169
+ case "<=":
170
+ return compareNonNullValues(originalValue, comparedValue, (a, b) => a <= b);
171
+ // Array Contains (equality check for arrays).
172
+ case "[]":
173
+ return isArrayEqual(originalValue, comparedValue);
174
+ // Array Does Not Contain.
175
+ case "![]":
176
+ return !isArrayEqual(originalValue, comparedValue);
177
+ // Wildcard Match (using regex pattern).
178
+ case "*":
179
+ return isWildcardMatch(originalValue, comparedValue);
180
+ // Not Wildcard Match.
181
+ case "!*":
182
+ return !isWildcardMatch(originalValue, comparedValue);
183
+ // Unsupported operator.
184
+ default:
185
+ throw new Error(`Unsupported operator: ${operator}`);
186
+ }
187
+ };
188
+ /**
189
+ * Helper function to handle non-null comparisons.
190
+ */
191
+ const compareNonNullValues = (originalValue, comparedValue, comparator) => {
192
+ return (originalValue !== null &&
193
+ comparedValue !== null &&
194
+ comparator(originalValue, comparedValue));
195
+ };
196
+ /**
197
+ * Helper function to check equality based on the field type.
198
+ *
199
+ * @param originalValue - The original value.
200
+ * @param comparedValue - The value to compare against.
201
+ * @param fieldType - Type of the field.
202
+ * @returns boolean - Result of the equality check.
203
+ */
204
+ export const isEqual = (originalValue, comparedValue, fieldType) => {
205
+ switch (fieldType) {
206
+ case "password":
207
+ return isPassword(originalValue) && typeof comparedValue === "string"
208
+ ? comparePassword(originalValue, comparedValue)
209
+ : false;
210
+ case "boolean":
211
+ return Number(originalValue) === Number(comparedValue);
212
+ default:
213
+ return originalValue == comparedValue;
214
+ }
215
+ };
216
+ /**
217
+ * Helper function to check array equality.
218
+ *
219
+ * @param originalValue - The original value.
220
+ * @param comparedValue - The value to compare against.
221
+ * @returns boolean - Result of the array equality check.
222
+ */
223
+ export const isArrayEqual = (originalValue, comparedValue) => {
224
+ if (Array.isArray(originalValue) && Array.isArray(comparedValue))
225
+ return originalValue.some((v) => comparedValue.includes(v));
226
+ if (Array.isArray(originalValue))
227
+ return originalValue.includes(comparedValue);
228
+ if (Array.isArray(comparedValue))
229
+ return comparedValue.includes(originalValue);
230
+ return originalValue == comparedValue;
231
+ };
232
+ /**
233
+ * Helper function to check wildcard pattern matching using regex.
234
+ *
235
+ * @param originalValue - The original value.
236
+ * @param comparedValue - The value with wildcard pattern.
237
+ * @returns boolean - Result of the wildcard pattern matching.
238
+ */
239
+ export const isWildcardMatch = (originalValue, comparedValue) => {
240
+ const comparedValueStr = String(comparedValue);
241
+ const originalValueStr = String(originalValue);
242
+ if (!comparedValueStr.includes("%") &&
243
+ (comparedValueStr === originalValueStr ||
244
+ comparedValueStr.toLowerCase() === originalValueStr.toLowerCase()))
245
+ return true;
246
+ const wildcardPattern = `^${(comparedValueStr.includes("%") ? comparedValueStr : `%${comparedValueStr}%`).replace(/%/g, ".*")}$`;
247
+ return new RegExp(wildcardPattern, "i").test(originalValueStr);
248
+ };
package/package.json CHANGED
@@ -1,38 +1,85 @@
1
1
  {
2
2
  "name": "inibase",
3
- "version": "1.0.0-rc.9",
4
- "description": "File-based Relational Database for large data",
5
- "main": "index.ts",
3
+ "version": "1.0.0-rc.90",
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/bun": "^1.1.10",
72
+ "@types/node": "^22.7.4",
73
+ "tinybench": "^2.6.0",
74
+ "typescript": "^5.6.2"
75
+ },
76
+ "dependencies": {
77
+ "dotenv": "^16.4.5",
78
+ "inison": "1.0.0-rc.4"
79
+ },
80
+ "scripts": {
81
+ "prepublish": "npx tsc",
82
+ "build": "npx tsc",
83
+ "benchmark": "./benchmark/run.js"
37
84
  }
38
- }
85
+ }