inibase 1.0.0-rc.12 → 1.0.0-rc.13

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,54 @@
1
+ /// <reference types="node" />
2
+ import { FieldType, Schema } from ".";
3
+ export declare const isArrayOfObjects: (input: any) => input is Record<any, any>[];
4
+ export declare const isArrayOfArrays: (input: any) => input is any[][];
5
+ export declare const isObject: (obj: any) => boolean;
6
+ export declare const deepMerge: (target: any, source: any) => any;
7
+ export declare const combineObjects: (arr: Record<string, any>[]) => Record<string, any>;
8
+ export declare const isNumber: (input: any) => input is number;
9
+ export declare const isEmail: (input: any) => boolean;
10
+ export declare const isURL: (input: any) => boolean;
11
+ export declare const isHTML: (input: any) => boolean;
12
+ export declare const isString: (input: any) => input is string;
13
+ export declare const isIP: (input: any) => boolean;
14
+ export declare const isBoolean: (input: any) => input is boolean;
15
+ export declare const isPassword: (input: any) => input is string;
16
+ export declare const isDate: (input: any) => boolean;
17
+ export declare const isValidID: (input: any) => input is string;
18
+ export declare const findChangedProperties: (obj1: Record<string, string>, obj2: Record<string, string>) => Record<string, string> | null;
19
+ export declare const detectFieldType: (input: any, availableTypes: FieldType[]) => FieldType | undefined;
20
+ export declare const validateFieldType: (value: any, fieldType: FieldType | FieldType[], fieldChildrenType?: FieldType | FieldType[]) => boolean;
21
+ export declare const hashPassword: (password: string) => string;
22
+ export declare const comparePassword: (hashedPassword: string, inputPassword: string) => boolean;
23
+ export declare const encodeID: (id: number, secretKeyOrSalt: string | number | Buffer) => string;
24
+ export declare const decodeID: (input: string, secretKeyOrSalt: string | number | Buffer) => number;
25
+ export declare const findLastIdNumber: (schema: Schema, secretKeyOrSalt: string | number | Buffer) => number;
26
+ export declare const addIdToSchema: (schema: Schema, oldIndex: number, secretKeyOrSalt: string | number | Buffer) => import(".").Field[];
27
+ export declare const objectToDotNotation: (input: Record<string, any>) => Record<string, string | number | (string | number)[]>;
28
+ export default class Utils {
29
+ static isNumber: (input: any) => input is number;
30
+ static isObject: (obj: any) => boolean;
31
+ static isEmail: (input: any) => boolean;
32
+ static isDate: (input: any) => boolean;
33
+ static isURL: (input: any) => boolean;
34
+ static isValidID: (input: any) => input is string;
35
+ static isPassword: (input: any) => input is string;
36
+ static deepMerge: (target: any, source: any) => any;
37
+ static combineObjects: (arr: Record<string, any>[]) => Record<string, any>;
38
+ static isArrayOfObjects: (input: any) => input is Record<any, any>[];
39
+ static findChangedProperties: (obj1: Record<string, string>, obj2: Record<string, string>) => Record<string, string>;
40
+ static detectFieldType: (input: any, availableTypes: FieldType[]) => FieldType;
41
+ static isArrayOfArrays: (input: any) => input is any[][];
42
+ static isBoolean: (input: any) => input is boolean;
43
+ static isString: (input: any) => input is string;
44
+ static isHTML: (input: any) => boolean;
45
+ static isIP: (input: any) => boolean;
46
+ static validateFieldType: (value: any, fieldType: FieldType | FieldType[], fieldChildrenType?: FieldType | FieldType[]) => boolean;
47
+ static encodeID: (id: number, secretKeyOrSalt: string | number | Buffer) => string;
48
+ static decodeID: (input: string, secretKeyOrSalt: string | number | Buffer) => number;
49
+ static hashPassword: (password: string) => string;
50
+ static comparePassword: (hashedPassword: string, inputPassword: string) => boolean;
51
+ static findLastIdNumber: (schema: Schema, secretKeyOrSalt: string | number | Buffer) => number;
52
+ static addIdToSchema: (schema: Schema, oldIndex: number, secretKeyOrSalt: string | number | Buffer) => import(".").Field[];
53
+ static objectToDotNotation: (input: Record<string, any>) => Record<string, string | number | (string | number)[]>;
54
+ }
package/dist/utils.js ADDED
@@ -0,0 +1,301 @@
1
+ import { createCipheriv, createDecipheriv, randomBytes, scryptSync, createHash, } from "node:crypto";
2
+ export const isArrayOfObjects = (input) => Array.isArray(input) && (input.length === 0 || input.every(isObject));
3
+ export const isArrayOfArrays = (input) => Array.isArray(input) && (input.length === 0 || input.every(Array.isArray));
4
+ export const isObject = (obj) => obj != null &&
5
+ (obj.constructor.name === "Object" ||
6
+ (typeof obj === "object" && !Array.isArray(obj)));
7
+ export const deepMerge = (target, source) => {
8
+ for (const key in source) {
9
+ if (source.hasOwnProperty(key)) {
10
+ if (source[key] instanceof Object && target[key] instanceof Object)
11
+ target[key] = deepMerge(target[key], source[key]);
12
+ else
13
+ target[key] = source[key];
14
+ }
15
+ }
16
+ return target;
17
+ };
18
+ export const combineObjects = (arr) => {
19
+ const result = {};
20
+ for (const obj of arr) {
21
+ for (const key in obj) {
22
+ if (obj.hasOwnProperty(key)) {
23
+ const existingValue = result[key];
24
+ const newValue = obj[key];
25
+ if (typeof existingValue === "object" &&
26
+ typeof newValue === "object" &&
27
+ existingValue !== null &&
28
+ newValue !== null) {
29
+ // If both values are objects, recursively combine them
30
+ result[key] = combineObjects([existingValue, newValue]);
31
+ }
32
+ else {
33
+ // If one or both values are not objects, overwrite the existing value
34
+ result[key] = newValue;
35
+ }
36
+ }
37
+ }
38
+ }
39
+ return result;
40
+ };
41
+ export const isNumber = (input) => !isNaN(parseFloat(input)) && !isNaN(input - 0);
42
+ export const isEmail = (input) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(String(input));
43
+ export const isURL = (input) => {
44
+ if (typeof input !== "string")
45
+ return false;
46
+ if (input[0] === "#" ||
47
+ input.startsWith("tel:") ||
48
+ input.startsWith("mailto:"))
49
+ return true;
50
+ else if ("canParse" in URL)
51
+ return URL.canParse(input);
52
+ else {
53
+ var pattern = new RegExp("^(https?:\\/\\/)?" + // protocol
54
+ "((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|" + // domain name
55
+ "((\\d{1,3}\\.){3}\\d{1,3}))" + // OR ip (v4) address
56
+ "(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*" + // port and path
57
+ "(\\?[;&a-z\\d%_.~+=-]*)?" + // query string
58
+ "(\\#[-a-z\\d_]*)?$", "i"); // fragment locator
59
+ return !!pattern.test(input);
60
+ }
61
+ };
62
+ export const isHTML = (input) => /<\/?\s*[a-z-][^>]*\s*>|(\&(?:[\w\d]+|#\d+|#x[a-f\d]+);)/g.test(input);
63
+ export const isString = (input) => Object.prototype.toString.call(input) === "[object String]" &&
64
+ !isNumber(input) &&
65
+ !isBoolean(input) &&
66
+ !isEmail(input) &&
67
+ !isDate(input) &&
68
+ !isURL(input) &&
69
+ !isIP(input) &&
70
+ !isHTML(input);
71
+ export const isIP = (input) => /^(?:(?:^|\.)(?:2(?:5[0-5]|[0-4]\d)|1?\d?\d)){4}$/.test(input);
72
+ export const isBoolean = (input) => typeof input === "boolean" ||
73
+ input === "true" ||
74
+ input === "false" ||
75
+ input === true ||
76
+ input === false;
77
+ export const isPassword = (input) => input.length === 161;
78
+ export const isDate = (input) => !isNaN(Date.parse(String(input))) && Date.parse(String(input)) >= 0;
79
+ export const isValidID = (input) => {
80
+ return typeof input === "string" && input.length === 32;
81
+ };
82
+ export const findChangedProperties = (obj1, obj2) => {
83
+ const result = {};
84
+ for (const key1 in obj1)
85
+ if (obj2.hasOwnProperty(key1) && obj1[key1] !== obj2[key1])
86
+ result[obj1[key1]] = obj2[key1];
87
+ return Object.keys(result).length ? result : null;
88
+ };
89
+ export const detectFieldType = (input, availableTypes) => {
90
+ if (!Array.isArray(input)) {
91
+ if ((input === "0" ||
92
+ input === "1" ||
93
+ input === "true" ||
94
+ input === "false") &&
95
+ availableTypes.includes("boolean"))
96
+ return "boolean";
97
+ else if (isNumber(input)) {
98
+ if (availableTypes.includes("table"))
99
+ return "table";
100
+ else if (availableTypes.includes("date"))
101
+ return "date";
102
+ else if (availableTypes.includes("number"))
103
+ return "number";
104
+ }
105
+ else if (availableTypes.includes("table") && isValidID(input))
106
+ return "table";
107
+ else if (input.includes(",") && availableTypes.includes("array"))
108
+ return "array";
109
+ else if (availableTypes.includes("email") && isEmail(input))
110
+ return "email";
111
+ else if (availableTypes.includes("url") && isURL(input))
112
+ return "url";
113
+ else if (availableTypes.includes("password") && isPassword(input))
114
+ return "password";
115
+ else if (availableTypes.includes("date") && isDate(input))
116
+ return "date";
117
+ else if (availableTypes.includes("string") && isString(input))
118
+ return "string";
119
+ else if (availableTypes.includes("ip") && isIP(input))
120
+ return "ip";
121
+ }
122
+ else
123
+ return "array";
124
+ return undefined;
125
+ };
126
+ export const validateFieldType = (value, fieldType, fieldChildrenType) => {
127
+ if (value === null)
128
+ return true;
129
+ if (Array.isArray(fieldType))
130
+ return detectFieldType(value, fieldType) !== undefined;
131
+ if (fieldType === "array" && fieldChildrenType && Array.isArray(value))
132
+ return value.some((v) => detectFieldType(v, Array.isArray(fieldChildrenType)
133
+ ? fieldChildrenType
134
+ : [fieldChildrenType]) !== undefined);
135
+ switch (fieldType) {
136
+ case "string":
137
+ return isString(value);
138
+ case "password":
139
+ return isNumber(value) || isString(value) || isPassword(value);
140
+ case "number":
141
+ return isNumber(value);
142
+ case "html":
143
+ return isHTML(value);
144
+ case "ip":
145
+ return isIP(value);
146
+ case "boolean":
147
+ return isBoolean(value);
148
+ case "date":
149
+ return isDate(value);
150
+ case "object":
151
+ return isObject(value);
152
+ case "array":
153
+ return Array.isArray(value);
154
+ case "email":
155
+ return isEmail(value);
156
+ case "url":
157
+ return isURL(value);
158
+ case "table":
159
+ // feat: check if id exists
160
+ if (Array.isArray(value))
161
+ return ((isArrayOfObjects(value) &&
162
+ value.every((element) => element.hasOwnProperty("id") &&
163
+ (isValidID(element.id) || isNumber(element.id)))) ||
164
+ value.every(isNumber) ||
165
+ isValidID(value));
166
+ else if (isObject(value))
167
+ return (value.hasOwnProperty("id") &&
168
+ (isValidID(value.id) || isNumber(value.id)));
169
+ else
170
+ return isNumber(value) || isValidID(value);
171
+ case "id":
172
+ return isNumber(value) || isValidID(value);
173
+ default:
174
+ return false;
175
+ }
176
+ };
177
+ export const hashPassword = (password) => {
178
+ const salt = randomBytes(16).toString("hex");
179
+ const hash = createHash("sha256")
180
+ .update(password + salt)
181
+ .digest("hex");
182
+ return `${salt}:${hash}`;
183
+ };
184
+ export const comparePassword = (hashedPassword, inputPassword) => {
185
+ const [salt, originalHash] = hashedPassword.split(":");
186
+ const inputHash = createHash("sha256")
187
+ .update(inputPassword + salt)
188
+ .digest("hex");
189
+ return inputHash === originalHash;
190
+ };
191
+ export const encodeID = (id, secretKeyOrSalt) => {
192
+ let cipher, ret;
193
+ if (Buffer.isBuffer(secretKeyOrSalt))
194
+ cipher = createCipheriv("aes-256-cbc", secretKeyOrSalt, secretKeyOrSalt.subarray(0, 16));
195
+ else {
196
+ const salt = scryptSync(secretKeyOrSalt.toString(), (process.env.INIBASE_SECRET ?? "inibase") + "_salt", 32);
197
+ cipher = createCipheriv("aes-256-cbc", salt, salt.subarray(0, 16));
198
+ }
199
+ return cipher.update(id.toString(), "utf8", "hex") + cipher.final("hex");
200
+ };
201
+ export const decodeID = (input, secretKeyOrSalt) => {
202
+ let decipher;
203
+ if (Buffer.isBuffer(secretKeyOrSalt))
204
+ decipher = createDecipheriv("aes-256-cbc", secretKeyOrSalt, secretKeyOrSalt.subarray(0, 16));
205
+ else {
206
+ const salt = scryptSync(secretKeyOrSalt.toString(), (process.env.INIBASE_SECRET ?? "inibase") + "_salt", 32);
207
+ decipher = createDecipheriv("aes-256-cbc", salt, salt.subarray(0, 16));
208
+ }
209
+ return Number(decipher.update(input, "hex", "utf8") + decipher.final("utf8"));
210
+ };
211
+ export const findLastIdNumber = (schema, secretKeyOrSalt) => {
212
+ const lastField = schema[schema.length - 1];
213
+ if (lastField) {
214
+ if ((lastField.type === "array" || lastField.type === "object") &&
215
+ isArrayOfObjects(lastField.children))
216
+ return findLastIdNumber(lastField.children, secretKeyOrSalt);
217
+ else if (lastField.id)
218
+ return isValidID(lastField.id)
219
+ ? decodeID(lastField.id, secretKeyOrSalt)
220
+ : lastField.id;
221
+ }
222
+ return 0;
223
+ };
224
+ export const addIdToSchema = (schema, oldIndex = 0, secretKeyOrSalt) => schema.map((field) => {
225
+ if (!field.id) {
226
+ oldIndex++;
227
+ field.id = encodeID(oldIndex, secretKeyOrSalt);
228
+ }
229
+ else {
230
+ if (!isNumber(field.id))
231
+ oldIndex = decodeID(field.id, secretKeyOrSalt);
232
+ else {
233
+ oldIndex = field.id;
234
+ field.id = encodeID(field.id, secretKeyOrSalt);
235
+ }
236
+ }
237
+ if ((field.type === "array" || field.type === "object") &&
238
+ isArrayOfObjects(field.children)) {
239
+ field.children = addIdToSchema(field.children, oldIndex, secretKeyOrSalt);
240
+ oldIndex += field.children.length;
241
+ }
242
+ return field;
243
+ });
244
+ export const objectToDotNotation = (input) => {
245
+ const result = {};
246
+ const stack = [
247
+ { obj: input },
248
+ ];
249
+ while (stack.length > 0) {
250
+ const { obj, parentKey } = stack.pop();
251
+ for (const key in obj) {
252
+ if (obj.hasOwnProperty(key)) {
253
+ const newKey = parentKey ? `${parentKey}.${key}` : key;
254
+ const value = obj[key];
255
+ const isArray = Array.isArray(value);
256
+ const isStringOrNumberArray = isArray &&
257
+ value.every((item) => typeof item === "string" || typeof item === "number");
258
+ if (isStringOrNumberArray) {
259
+ // If the property is an array of strings or numbers, keep the array as is
260
+ result[newKey] = value;
261
+ }
262
+ else if (typeof value === "object" && value !== null) {
263
+ // If the property is an object, push it onto the stack for further processing
264
+ stack.push({ obj: value, parentKey: newKey });
265
+ }
266
+ else {
267
+ // Otherwise, assign the value to the dot notation key
268
+ result[newKey] = value;
269
+ }
270
+ }
271
+ }
272
+ }
273
+ return result;
274
+ };
275
+ export default class Utils {
276
+ static isNumber = isNumber;
277
+ static isObject = isObject;
278
+ static isEmail = isEmail;
279
+ static isDate = isDate;
280
+ static isURL = isURL;
281
+ static isValidID = isValidID;
282
+ static isPassword = isPassword;
283
+ static deepMerge = deepMerge;
284
+ static combineObjects = combineObjects;
285
+ static isArrayOfObjects = isArrayOfObjects;
286
+ static findChangedProperties = findChangedProperties;
287
+ static detectFieldType = detectFieldType;
288
+ static isArrayOfArrays = isArrayOfArrays;
289
+ static isBoolean = isBoolean;
290
+ static isString = isString;
291
+ static isHTML = isHTML;
292
+ static isIP = isIP;
293
+ static validateFieldType = validateFieldType;
294
+ static encodeID = encodeID;
295
+ static decodeID = decodeID;
296
+ static hashPassword = hashPassword;
297
+ static comparePassword = comparePassword;
298
+ static findLastIdNumber = findLastIdNumber;
299
+ static addIdToSchema = addIdToSchema;
300
+ static objectToDotNotation = objectToDotNotation;
301
+ }
package/package.json CHANGED
@@ -1,16 +1,20 @@
1
1
  {
2
2
  "name": "inibase",
3
- "version": "1.0.0-rc.12",
3
+ "version": "1.0.0-rc.13",
4
4
  "description": "File-based Relational Database for large data",
5
- "main": "index.ts",
5
+ "files": [
6
+ "dist"
7
+ ],
8
+ "types": "./dist",
6
9
  "type": "module",
7
10
  "scripts": {
8
- "test": "npx tsx watch ./index.test.ts"
9
- },
10
- "repository": {
11
- "type": "git",
12
- "url": "git+https://github.com/inicontent/inibase.git"
11
+ "build": "npx tsc",
12
+ "test": "npx tsx watch ./index.test",
13
+ "benchmark": "npx tsx watch ./benchmark.ts",
14
+ "prepublishOnly": "npm run build",
15
+ "postversion": "git push --follow-tags && npm publish"
13
16
  },
17
+ "repository": "inicontent/inibase",
14
18
  "keywords": [
15
19
  "db",
16
20
  "nosql",
@@ -27,13 +31,21 @@
27
31
  "pocketbase",
28
32
  "firebase"
29
33
  ],
30
- "author": "Inicontent",
34
+ "author": {
35
+ "name": "Karim Amahtil",
36
+ "email": "karim.amahtil@gmail.com"
37
+ },
38
+ "funding": "https://github.com/sponsors/inicontent",
31
39
  "license": "MIT",
32
40
  "bugs": {
33
41
  "url": "https://github.com/inicontent/inibase/issues"
34
42
  },
35
43
  "homepage": "https://github.com/inicontent/inibase#readme",
36
44
  "devDependencies": {
37
- "@types/node": "^20.8.6"
45
+ "@types/node": "^20.8.6",
46
+ "typescript": "^5.3.2"
47
+ },
48
+ "engines": {
49
+ "node": ">=16"
38
50
  }
39
51
  }
package/.env.example DELETED
@@ -1 +0,0 @@
1
- INIBASE_SECRET=