inibase 1.0.0-rc.10 → 1.0.0-rc.12

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "inibase",
3
- "version": "1.0.0-rc.10",
3
+ "version": "1.0.0-rc.12",
4
4
  "description": "File-based Relational Database for large data",
5
5
  "main": "index.ts",
6
6
  "type": "module",
@@ -24,6 +24,7 @@
24
24
  "sql",
25
25
  "sqlite",
26
26
  "supabase",
27
+ "pocketbase",
27
28
  "firebase"
28
29
  ],
29
30
  "author": "Inicontent",
package/utils.ts CHANGED
@@ -1,11 +1,19 @@
1
- import { FieldType, Data } from ".";
1
+ import { FieldType, Data, Schema } from ".";
2
+ import {
3
+ createCipheriv,
4
+ createDecipheriv,
5
+ randomBytes,
6
+ scryptSync,
7
+ timingSafeEqual,
8
+ type Cipher,
9
+ type Decipher,
10
+ } from "node:crypto";
2
11
 
3
- export const isArrayOfObjects = (arr: any) => {
4
- return Array.isArray(arr) && (arr.length === 0 || arr.every(isObject));
5
- };
6
- export const isArrayOfArrays = (arr: any) => {
7
- return Array.isArray(arr) && (arr.length === 0 || arr.every(Array.isArray));
8
- };
12
+ export const isArrayOfObjects = (input: any): input is Record<any, any>[] =>
13
+ Array.isArray(input) && (input.length === 0 || input.every(isObject));
14
+
15
+ export const isArrayOfArrays = (input: any): input is any[][] =>
16
+ Array.isArray(input) && (input.length === 0 || input.every(Array.isArray));
9
17
 
10
18
  export const isObject = (obj: any) =>
11
19
  obj != null &&
@@ -33,20 +41,39 @@ export const combineObjects = (objectArray: Record<string, any>[]) => {
33
41
  return combinedValues;
34
42
  };
35
43
 
36
- export const isNumber = (input: any | any[]): boolean =>
37
- Array.isArray(input)
38
- ? input.every(isNumber)
39
- : !isNaN(parseFloat(input)) && !isNaN(input - 0);
44
+ export const isNumber = (input: any): input is number =>
45
+ !isNaN(parseFloat(input)) && !isNaN(input - 0);
40
46
 
41
47
  export const isEmail = (input: any) =>
42
48
  /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(String(input));
43
49
 
44
- export const isURL = (input: any) => input[0] === "#" || URL.canParse(input);
50
+ export const isURL = (input: any) => {
51
+ if (typeof input !== "string") return false;
52
+ if (
53
+ input[0] === "#" ||
54
+ input.startsWith("tel:") ||
55
+ input.startsWith("mailto:")
56
+ )
57
+ return true;
58
+ else if ("canParse" in URL) return URL.canParse(input);
59
+ else {
60
+ var pattern = new RegExp(
61
+ "^(https?:\\/\\/)?" + // protocol
62
+ "((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|" + // domain name
63
+ "((\\d{1,3}\\.){3}\\d{1,3}))" + // OR ip (v4) address
64
+ "(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*" + // port and path
65
+ "(\\?[;&a-z\\d%_.~+=-]*)?" + // query string
66
+ "(\\#[-a-z\\d_]*)?$",
67
+ "i"
68
+ ); // fragment locator
69
+ return !!pattern.test(input);
70
+ }
71
+ };
45
72
 
46
73
  export const isHTML = (input: any) =>
47
74
  /<\/?\s*[a-z-][^>]*\s*>|(\&(?:[\w\d]+|#\d+|#x[a-f\d]+);)/g.test(input);
48
75
 
49
- export const isString = (input: any) =>
76
+ export const isString = (input: any): input is string =>
50
77
  Object.prototype.toString.call(input) === "[object String]" &&
51
78
  !isNumber(input) &&
52
79
  !isBoolean(input) &&
@@ -59,22 +86,20 @@ export const isString = (input: any) =>
59
86
  export const isIP = (input: any) =>
60
87
  /^(?:(?:^|\.)(?:2(?:5[0-5]|[0-4]\d)|1?\d?\d)){4}$/.test(input);
61
88
 
62
- export const isBoolean = (input: any) =>
89
+ export const isBoolean = (input: any): input is boolean =>
63
90
  typeof input === "boolean" ||
64
91
  input === "true" ||
65
92
  input === "false" ||
66
93
  input === true ||
67
94
  input === false;
68
95
 
69
- export const isPassword = (input: any) => input.length === 161;
96
+ export const isPassword = (input: any): input is string => input.length === 161;
70
97
 
71
98
  export const isDate = (input: any) =>
72
99
  !isNaN(Date.parse(String(input))) && Date.parse(String(input)) >= 0;
73
100
 
74
- export const isValidID = (input: any): boolean => {
75
- return Array.isArray(input)
76
- ? input.every(isValidID)
77
- : typeof input === "string" && input.length === 32;
101
+ export const isValidID = (input: any): input is string => {
102
+ return typeof input === "string" && input.length === 32;
78
103
  };
79
104
 
80
105
  export const findChangedProperties = (
@@ -107,7 +132,9 @@ export const detectFieldType = (
107
132
  if (availableTypes.includes("table")) return "table";
108
133
  else if (availableTypes.includes("date")) return "date";
109
134
  else if (availableTypes.includes("number")) return "number";
110
- } else if (input.includes(",") && availableTypes.includes("array"))
135
+ } else if (availableTypes.includes("table") && isValidID(input))
136
+ return "table";
137
+ else if (input.includes(",") && availableTypes.includes("array"))
111
138
  return "array";
112
139
  else if (availableTypes.includes("email") && isEmail(input)) return "email";
113
140
  else if (availableTypes.includes("url") && isURL(input)) return "url";
@@ -190,6 +217,126 @@ export const validateFieldType = (
190
217
  }
191
218
  };
192
219
 
220
+ export const hashPassword = (password: string) => {
221
+ const salt = randomBytes(16).toString("hex");
222
+ const buf = scryptSync(password, salt, 64);
223
+ // return "161" length string
224
+ return `${buf.toString("hex")}.${salt}`;
225
+ };
226
+
227
+ export const comparePassword = (
228
+ storedPassword: string,
229
+ suppliedPassword: string
230
+ ) => {
231
+ // split() returns array
232
+ const [hashedPassword, salt] = storedPassword.split(".");
233
+ // we need to pass buffer values to timingSafeEqual
234
+ const hashedPasswordBuf = Buffer.from(hashedPassword, "hex");
235
+ // we hash the new sign-in password
236
+ const suppliedPasswordBuf = scryptSync(suppliedPassword, salt, 64);
237
+ // compare the new supplied password with the stored hashed password
238
+ return timingSafeEqual(hashedPasswordBuf, suppliedPasswordBuf);
239
+ };
240
+
241
+ export const encodeID = (
242
+ id: number,
243
+ secretKeyOrSalt: string | number | Buffer
244
+ ): string => {
245
+ let cipher: Cipher, ret: string;
246
+
247
+ if (Buffer.isBuffer(secretKeyOrSalt))
248
+ cipher = createCipheriv(
249
+ "aes-256-cbc",
250
+ secretKeyOrSalt,
251
+ secretKeyOrSalt.subarray(0, 16)
252
+ );
253
+ else {
254
+ const salt = scryptSync(
255
+ secretKeyOrSalt.toString(),
256
+ (process.env.INIBASE_SECRET ?? "inibase") + "_salt",
257
+ 32
258
+ );
259
+ cipher = createCipheriv("aes-256-cbc", salt, salt.subarray(0, 16));
260
+ }
261
+
262
+ return cipher.update(id.toString(), "utf8", "hex") + cipher.final("hex");
263
+ };
264
+
265
+ export const decodeID = (
266
+ input: string,
267
+ secretKeyOrSalt: string | number | Buffer
268
+ ): number => {
269
+ let decipher: Decipher;
270
+
271
+ if (Buffer.isBuffer(secretKeyOrSalt))
272
+ decipher = createDecipheriv(
273
+ "aes-256-cbc",
274
+ secretKeyOrSalt,
275
+ secretKeyOrSalt.subarray(0, 16)
276
+ );
277
+ else {
278
+ const salt = scryptSync(
279
+ secretKeyOrSalt.toString(),
280
+ (process.env.INIBASE_SECRET ?? "inibase") + "_salt",
281
+ 32
282
+ );
283
+ decipher = createDecipheriv("aes-256-cbc", salt, salt.subarray(0, 16));
284
+ }
285
+
286
+ return Number(
287
+ decipher.update(input as string, "hex", "utf8") + decipher.final("utf8")
288
+ );
289
+ };
290
+
291
+ export const findLastIdNumber = (
292
+ schema: Schema,
293
+ secretKeyOrSalt: string | number | Buffer
294
+ ): number => {
295
+ const lastField = schema[schema.length - 1];
296
+ if (lastField) {
297
+ if (
298
+ (lastField.type === "array" || lastField.type === "object") &&
299
+ isArrayOfObjects(lastField.children)
300
+ )
301
+ return findLastIdNumber(lastField.children as Schema, secretKeyOrSalt);
302
+ else if (lastField.id)
303
+ return isValidID(lastField.id)
304
+ ? decodeID(lastField.id as string, secretKeyOrSalt)
305
+ : lastField.id;
306
+ }
307
+ return 0;
308
+ };
309
+
310
+ export const addIdToSchema = (
311
+ schema: Schema,
312
+ oldIndex: number = 0,
313
+ secretKeyOrSalt: string | number | Buffer
314
+ ) =>
315
+ schema.map((field) => {
316
+ if (!field.id) {
317
+ oldIndex++;
318
+ field.id = encodeID(oldIndex, secretKeyOrSalt);
319
+ } else {
320
+ if (!isNumber(field.id)) oldIndex = decodeID(field.id, secretKeyOrSalt);
321
+ else {
322
+ oldIndex = field.id;
323
+ field.id = encodeID(field.id, secretKeyOrSalt);
324
+ }
325
+ }
326
+ if (
327
+ (field.type === "array" || field.type === "object") &&
328
+ isArrayOfObjects(field.children)
329
+ ) {
330
+ field.children = addIdToSchema(
331
+ field.children as Schema,
332
+ oldIndex,
333
+ secretKeyOrSalt
334
+ );
335
+ oldIndex += field.children.length;
336
+ }
337
+ return field;
338
+ });
339
+
193
340
  export default class Utils {
194
341
  static isNumber = isNumber;
195
342
  static isObject = isObject;
@@ -209,4 +356,11 @@ export default class Utils {
209
356
  static isHTML = isHTML;
210
357
  static isIP = isIP;
211
358
  static validateFieldType = validateFieldType;
359
+
360
+ static encodeID = encodeID;
361
+ static decodeID = decodeID;
362
+ static hashPassword = hashPassword;
363
+ static comparePassword = comparePassword;
364
+ static findLastIdNumber = findLastIdNumber;
365
+ static addIdToSchema = addIdToSchema;
212
366
  }
package/utils.server.ts DELETED
@@ -1,79 +0,0 @@
1
- import {
2
- scryptSync,
3
- randomBytes,
4
- timingSafeEqual,
5
- createDecipheriv,
6
- createCipheriv,
7
- Cipher,
8
- Decipher,
9
- } from "node:crypto";
10
-
11
- export const hashPassword = (password: string) => {
12
- const salt = randomBytes(16).toString("hex");
13
- const buf = scryptSync(password, salt, 64);
14
- // return "161" length string
15
- return `${buf.toString("hex")}.${salt}`;
16
- };
17
-
18
- export const comparePassword = (
19
- storedPassword: string,
20
- suppliedPassword: string
21
- ) => {
22
- // split() returns array
23
- const [hashedPassword, salt] = storedPassword.split(".");
24
- // we need to pass buffer values to timingSafeEqual
25
- const hashedPasswordBuf = Buffer.from(hashedPassword, "hex");
26
- // we hash the new sign-in password
27
- const suppliedPasswordBuf = scryptSync(suppliedPassword, salt, 64);
28
- // compare the new supplied password with the stored hashed password
29
- return timingSafeEqual(hashedPasswordBuf, suppliedPasswordBuf);
30
- };
31
-
32
- export const encodeID = (
33
- id: number,
34
- secretKey: string | number | Buffer
35
- ): string => {
36
- let cipher: Cipher, ret: string;
37
-
38
- if (Buffer.isBuffer(secretKey))
39
- cipher = createCipheriv(
40
- "aes-256-cbc",
41
- secretKey,
42
- secretKey.subarray(0, 16)
43
- );
44
- else {
45
- const salt = scryptSync(secretKey.toString(), "salt", 32);
46
- cipher = createCipheriv("aes-256-cbc", salt, salt.subarray(0, 16));
47
- }
48
-
49
- return cipher.update(id.toString(), "utf8", "hex") + cipher.final("hex");
50
- };
51
-
52
- export const decodeID = (
53
- input: string,
54
- secretKey: string | number | Buffer
55
- ): number => {
56
- let decipher: Decipher;
57
-
58
- if (Buffer.isBuffer(secretKey))
59
- decipher = createDecipheriv(
60
- "aes-256-cbc",
61
- secretKey,
62
- secretKey.subarray(0, 16)
63
- );
64
- else {
65
- const salt = scryptSync(secretKey.toString(), "salt", 32);
66
- decipher = createDecipheriv("aes-256-cbc", salt, salt.subarray(0, 16));
67
- }
68
-
69
- return Number(
70
- decipher.update(input as string, "hex", "utf8") + decipher.final("utf8")
71
- );
72
- };
73
-
74
- export default class Utils {
75
- static encodeID = encodeID;
76
- static decodeID = decodeID;
77
- static hashPassword = hashPassword;
78
- static comparePassword = comparePassword;
79
- }