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/.env.example +1 -0
- package/README.md +50 -0
- package/file.ts +246 -73
- package/index.test.ts +45 -3
- package/index.ts +442 -349
- package/package.json +2 -1
- package/utils.ts +174 -20
- package/utils.server.ts +0 -79
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "inibase",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
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 = (
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
export const isArrayOfArrays = (
|
|
7
|
-
|
|
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
|
|
37
|
-
|
|
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) =>
|
|
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):
|
|
75
|
-
return
|
|
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 (
|
|
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
|
-
}
|