inibase 1.0.0-rc.4 → 1.0.0-rc.6

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/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  # Inibase :pencil:
4
4
 
5
- [![npmjs](https://img.shields.io/npm/dm/inibase.svg?style=flat)](https://www.npmjs.org/package/inibase) [![Node.js Version](https://img.shields.io/badge/node-18.11.0-blue)](https://nodejs.org/) [![License](https://img.shields.io/github/license/inicontent/inibase.svg?style=flat&colorA=18181B&colorB=28CF8D)](./LICENSE) [![Activity](https://img.shields.io/github/commit-activity/m/inicontent/inibase)](https://github.com/inicontent/inibase/pulse) [![GitHub stars](https://img.shields.io/github/stars/inicontent/inibase?style=social)](https://github.com/inicontent/inibase)
5
+ [![npmjs](https://img.shields.io/npm/dm/inibase.svg?style=flat)](https://www.npmjs.org/package/inibase) [![License](https://img.shields.io/github/license/inicontent/inibase.svg?style=flat&colorA=18181B&colorB=28CF8D)](./LICENSE) [![Activity](https://img.shields.io/github/commit-activity/m/inicontent/inibase)](https://github.com/inicontent/inibase/pulse) [![GitHub stars](https://img.shields.io/github/stars/inicontent/inibase?style=social)](https://github.com/inicontent/inibase)
6
6
 
7
7
  > File-based relational database, simple to use and can handle large data :fire:
8
8
 
@@ -21,7 +21,7 @@
21
21
 
22
22
  ```js
23
23
  import Inibase from "inibase";
24
- const db = new Inibase("/database_name");
24
+ const db = new Inibase("database_name");
25
25
 
26
26
  // Get all items from "user" table
27
27
  const users = await db.get("user");
@@ -30,12 +30,12 @@ const users = await db.get("user");
30
30
  const users = await db.get("user", undefined, { page: 2, per_page: 15 });
31
31
 
32
32
  // Get only required columns to improve speed
33
- const users = await InibaseDB.get("user", undefined, {
33
+ const users = await db.get("user", undefined, {
34
34
  columns: ["username", "address.street", "hobbies.*.name"],
35
35
  });
36
36
 
37
37
  // Get items from "user" table where "favoriteFoods" does not includes "Pizza"
38
- const users = await InibaseDB.get("user", { favoriteFoods: "![]Pizza" });
38
+ const users = await db.get("user", { favoriteFoods: "![]Pizza" });
39
39
  ```
40
40
 
41
41
  If you like Inibase, please sponsor: [GitHub Sponsors](https://github.com/sponsors/inicontent) || [Paypal](https://paypal.me/KarimAmahtil).
@@ -50,11 +50,7 @@ Become a sponsor and have your company logo here 👉 [GitHub Sponsors](https://
50
50
  ## Install
51
51
 
52
52
  ```js
53
- // npm
54
- npm install inibase
55
-
56
- // pnpm
57
- pnpm add inibase
53
+ <npm|pnpm> install inibase
58
54
  ```
59
55
 
60
56
  ## How it works?
@@ -342,6 +338,11 @@ const users = await db.get("user", { favoriteFoods: "[]Pizza" });
342
338
  // },
343
339
  // ...
344
340
  // ]
341
+
342
+ // Get all "user" columns except "username" & "address.street"
343
+ const users = await db.get("user", undefined, {
344
+ columns: ["!username", "!address.street"],
345
+ });
345
346
  ```
346
347
 
347
348
  </details>
@@ -445,7 +446,7 @@ type Data = {
445
446
  - [x] Pagination
446
447
  - [x] Criteria
447
448
  - [x] Columns
448
- - [ ] Order
449
+ - [ ] Order By
449
450
  - [x] POST
450
451
  - [x] PUT
451
452
  - [x] DELETE
@@ -460,7 +461,12 @@ type Data = {
460
461
  - [x] Object
461
462
  - [x] Array
462
463
  - [x] Password
463
- - [ ] IP
464
+ - [x] IP
465
+ - [x] HTML
466
+ - [x] Id
467
+ - [ ] TO-DO:
468
+ - [ ] Improve caching
469
+ - [ ] Commenting the code
464
470
  - [ ] Features:
465
471
  - [ ] Encryption
466
472
  - [ ] Compress data
package/file.ts CHANGED
@@ -1,9 +1,23 @@
1
- import { createWriteStream, unlinkSync, renameSync, existsSync } from "fs";
2
- import { open } from "fs/promises";
3
- import { parse } from "path";
1
+ import { createWriteStream, createReadStream, WriteStream } from "node:fs";
2
+ import { open, unlink, rename, stat } from "node:fs/promises";
3
+ import { Interface, createInterface } from "node:readline";
4
+ import { parse } from "node:path";
4
5
  import { ComparisonOperator, FieldType } from ".";
5
6
  import Utils from "./utils";
6
7
 
8
+ const doesSupportReadLines = () => {
9
+ const [major, minor, patch] = process.versions.node.split(".").map(Number);
10
+ return major >= 18 && minor >= 11;
11
+ };
12
+
13
+ export const isExists = async (path: string) => {
14
+ try {
15
+ await stat(path);
16
+ return true;
17
+ } catch {
18
+ return false;
19
+ }
20
+ };
7
21
  export const encodeFileName = (fileName: string, extension?: string) => {
8
22
  return (
9
23
  fileName.replaceAll("%", "%25").replaceAll("*", "%") +
@@ -15,12 +29,99 @@ export const decodeFileName = (fileName: string) => {
15
29
  return fileName.replaceAll("%", "*").replaceAll("*25", "%");
16
30
  };
17
31
 
32
+ export const encode = (
33
+ input: string | number | boolean | null | (string | number | boolean | null)[]
34
+ ) => {
35
+ const secureString = (input: string | number | boolean | null) => {
36
+ if (["true", "false"].includes(String(input))) return input ? 1 : 0;
37
+ return typeof input === "string"
38
+ ? decodeURIComponent(input)
39
+ .replaceAll("<", "&lt;")
40
+ .replaceAll(">", "&gt;")
41
+ .replaceAll(",", "%2C")
42
+ .replaceAll("|", "%7C")
43
+ .replaceAll("\n", "\\n")
44
+ .replaceAll("\r", "\\r")
45
+ : input;
46
+ };
47
+ return Array.isArray(input)
48
+ ? Utils.isArrayOfArrays(input)
49
+ ? (input as any[])
50
+ .map((_input) => _input.map(secureString).join(","))
51
+ .join("|")
52
+ : input.map(secureString).join(",")
53
+ : secureString(input);
54
+ };
55
+
56
+ export const decode = (
57
+ input: string | null | number,
58
+ fieldType?: FieldType | FieldType[],
59
+ fieldChildrenType?: FieldType | FieldType[]
60
+ ): string | number | boolean | null | (string | number | null | boolean)[] => {
61
+ if (!fieldType) return null;
62
+ const unSecureString = (input: string) =>
63
+ decodeURIComponent(input)
64
+ .replaceAll("&lt;", "<")
65
+ .replaceAll("&gt;", ">")
66
+ .replaceAll("%2C", ",")
67
+ .replaceAll("%7C", "|")
68
+ .replaceAll("\\n", "\n")
69
+ .replaceAll("\\r", "\r") || null;
70
+ if (input === null || input === "") return null;
71
+ if (Array.isArray(fieldType))
72
+ fieldType = Utils.detectFieldType(String(input), fieldType);
73
+ const decodeHelper = (value: string | number | any[]) => {
74
+ if (Array.isArray(value) && fieldType !== "array")
75
+ return value.map(decodeHelper);
76
+ switch (fieldType as FieldType) {
77
+ case "table":
78
+ case "number":
79
+ return isNaN(Number(value)) ? null : Number(value);
80
+ case "boolean":
81
+ return typeof value === "string" ? value === "true" : Boolean(value);
82
+ case "array":
83
+ if (!Array.isArray(value)) return [value];
84
+ if (fieldChildrenType)
85
+ return value.map(
86
+ (v) =>
87
+ decode(
88
+ v,
89
+ Array.isArray(fieldChildrenType)
90
+ ? Utils.detectFieldType(v, fieldChildrenType)
91
+ : fieldChildrenType
92
+ ) as string | number | boolean | null
93
+ );
94
+ else return value;
95
+ default:
96
+ return value;
97
+ }
98
+ };
99
+ return decodeHelper(
100
+ typeof input === "string"
101
+ ? input.includes(",")
102
+ ? input.includes("|")
103
+ ? input
104
+ .split("|")
105
+ .map((_input) => _input.split(",").map(unSecureString))
106
+ : input.split(",").map(unSecureString)
107
+ : unSecureString(input)
108
+ : input
109
+ );
110
+ };
111
+
18
112
  export const get = async (
19
113
  filePath: string,
20
- fieldType?: FieldType,
21
- lineNumbers?: number | number[]
114
+ lineNumbers?: number | number[],
115
+ fieldType?: FieldType | FieldType[],
116
+ fieldChildrenType?: FieldType | FieldType[]
22
117
  ) => {
23
- const file = await open(filePath);
118
+ let rl: Interface;
119
+ if (doesSupportReadLines()) rl = (await open(filePath)).readLines();
120
+ else
121
+ rl = createInterface({
122
+ input: createReadStream(filePath),
123
+ crlfDelay: Infinity,
124
+ });
24
125
  let lines: Record<
25
126
  number,
26
127
  | string
@@ -32,21 +133,23 @@ export const get = async (
32
133
  lineCount = 0;
33
134
 
34
135
  if (!lineNumbers) {
35
- for await (const line of file.readLines())
36
- lineCount++, (lines[lineCount] = Utils.decode(line, fieldType));
136
+ for await (const line of rl)
137
+ lineCount++,
138
+ (lines[lineCount] = decode(line, fieldType, fieldChildrenType));
37
139
  } else if (lineNumbers === -1) {
38
- let lastLine;
39
- for await (const line of file.readLines()) lineCount++, (lastLine = line);
40
- if (lastLine) lines = { [lineCount]: Utils.decode(lastLine, fieldType) };
140
+ let lastLine: string;
141
+ for await (const line of rl) lineCount++, (lastLine = line);
142
+ if (lastLine)
143
+ lines = { [lineCount]: decode(lastLine, fieldType, fieldChildrenType) };
41
144
  } else {
42
145
  let lineNumbersArray = [
43
146
  ...(Array.isArray(lineNumbers) ? lineNumbers : [lineNumbers]),
44
147
  ];
45
- for await (const line of file.readLines()) {
148
+ for await (const line of rl) {
46
149
  lineCount++;
47
150
  if (!lineNumbersArray.includes(lineCount)) continue;
48
151
  const indexOfLineCount = lineNumbersArray.indexOf(lineCount);
49
- lines[lineCount] = Utils.decode(line, fieldType);
152
+ lines[lineCount] = decode(line, fieldType, fieldChildrenType);
50
153
  lineNumbersArray[indexOfLineCount] = 0;
51
154
  if (!lineNumbersArray.filter((lineN) => lineN !== 0).length) break;
52
155
  }
@@ -68,34 +171,44 @@ export const replace = async (
68
171
  string | boolean | number | null | (string | boolean | number | null)[]
69
172
  >
70
173
  ) => {
71
- if (existsSync(filePath)) {
72
- const file = await open(filePath, "w+"),
174
+ if (await isExists(filePath)) {
175
+ let rl: Interface, writeStream: WriteStream;
176
+ if (doesSupportReadLines()) {
177
+ const file = await open(filePath, "w+");
178
+ rl = file.readLines();
73
179
  writeStream = file.createWriteStream();
180
+ } else {
181
+ rl = createInterface({
182
+ input: createReadStream(filePath),
183
+ crlfDelay: Infinity,
184
+ });
185
+ writeStream = createWriteStream(filePath);
186
+ }
74
187
  if (typeof replacements === "object" && !Array.isArray(replacements)) {
75
188
  let lineCount = 0;
76
- for await (const line of file.readLines()) {
189
+ for await (const line of rl) {
77
190
  lineCount++;
78
191
  writeStream.write(
79
- (lineCount in replacements
80
- ? Utils.encode(replacements[lineCount])
81
- : line) + "\n"
192
+ (lineCount in replacements ? encode(replacements[lineCount]) : line) +
193
+ "\n"
82
194
  );
83
195
  }
84
196
  } else
85
- for await (const _line of file.readLines())
86
- writeStream.write(Utils.encode(replacements) + "\n");
197
+ for await (const _line of rl)
198
+ writeStream.write(encode(replacements) + "\n");
87
199
 
88
200
  writeStream.end();
89
201
  } else if (typeof replacements === "object" && !Array.isArray(replacements)) {
90
- const file = await open(filePath, "w"),
91
- writeStream = file.createWriteStream(),
92
- largestLinesNumbers =
93
- Math.max(...Object.keys(replacements).map(Number)) + 1;
202
+ let writeStream: WriteStream;
203
+ if (doesSupportReadLines())
204
+ writeStream = (await open(filePath, "w")).createWriteStream();
205
+ else writeStream = createWriteStream(filePath);
206
+ const largestLinesNumbers =
207
+ Math.max(...Object.keys(replacements).map(Number)) + 1;
94
208
  for (let lineCount = 1; lineCount < largestLinesNumbers; lineCount++) {
95
209
  writeStream.write(
96
- (lineCount in replacements
97
- ? Utils.encode(replacements[lineCount])
98
- : "") + "\n"
210
+ (lineCount in replacements ? encode(replacements[lineCount]) : "") +
211
+ "\n"
99
212
  );
100
213
  }
101
214
  writeStream.end();
@@ -108,39 +221,52 @@ export const remove = async (
108
221
  ): Promise<void> => {
109
222
  let lineCount = 0;
110
223
 
111
- const tempFilePath = `${filePath}.tmp`,
224
+ const tempFilePath = `${filePath}-${Date.now()}.tmp`,
112
225
  linesToDeleteArray = [
113
226
  ...(Array.isArray(linesToDelete) ? linesToDelete : [linesToDelete]),
114
- ],
115
- writeStream = createWriteStream(tempFilePath),
116
- file = await open(filePath);
227
+ ];
228
+
229
+ let rl: Interface, writeStream: WriteStream;
230
+ if (doesSupportReadLines()) {
231
+ rl = (await open(filePath)).readLines();
232
+ writeStream = (await open(tempFilePath, "w+")).createWriteStream();
233
+ } else {
234
+ rl = createInterface({
235
+ input: createReadStream(filePath),
236
+ crlfDelay: Infinity,
237
+ });
238
+ writeStream = createWriteStream(tempFilePath);
239
+ }
117
240
 
118
- for await (const line of file.readLines()) {
241
+ for await (const line of rl) {
119
242
  lineCount++;
120
243
  if (!linesToDeleteArray.includes(lineCount)) {
121
244
  writeStream.write(`${line}\n`);
122
245
  }
123
246
  }
124
- writeStream.end();
125
- writeStream.on("finish", () => {
126
- unlinkSync(filePath); // Remove the original file
127
- renameSync(tempFilePath, filePath); // Rename the temp file to the original file name
247
+ writeStream.end(async () => {
248
+ await unlink(filePath); // Remove the original file
249
+ await rename(tempFilePath, filePath); // Rename the temp file to the original file name
128
250
  });
129
251
  };
130
252
 
131
253
  export const count = async (filePath: string): Promise<number> => {
132
- let lineCount = 0;
133
-
134
- const file = await open(filePath);
254
+ let lineCount = 0,
255
+ rl: Interface;
256
+ if (doesSupportReadLines()) rl = (await open(filePath)).readLines();
257
+ else
258
+ rl = createInterface({
259
+ input: createReadStream(filePath),
260
+ crlfDelay: Infinity,
261
+ });
135
262
 
136
- for await (const line of file.readLines()) lineCount++;
263
+ for await (const line of rl) lineCount++;
137
264
 
138
265
  return lineCount;
139
266
  };
140
267
 
141
268
  export const search = async (
142
269
  filePath: string,
143
- fieldType: FieldType,
144
270
  operator: ComparisonOperator | ComparisonOperator[],
145
271
  comparedAtValue:
146
272
  | string
@@ -149,9 +275,12 @@ export const search = async (
149
275
  | null
150
276
  | (string | number | boolean | null)[],
151
277
  logicalOperator?: "and" | "or",
278
+ fieldType?: FieldType | FieldType[],
279
+ fieldChildrenType?: FieldType | FieldType[],
152
280
  limit?: number,
153
281
  offset?: number,
154
- readWholeFile?: boolean
282
+ readWholeFile?: boolean,
283
+ secretKey?: string
155
284
  ): Promise<
156
285
  [
157
286
  Record<
@@ -166,7 +295,7 @@ export const search = async (
166
295
  > => {
167
296
  const handleComparisonOperator = (
168
297
  operator: ComparisonOperator,
169
- value:
298
+ originalValue:
170
299
  | string
171
300
  | number
172
301
  | boolean
@@ -178,77 +307,85 @@ export const search = async (
178
307
  | boolean
179
308
  | null
180
309
  | (string | number | boolean | null)[],
181
- fieldType: FieldType
310
+ fieldType?: FieldType | FieldType[],
311
+ fieldChildrenType?: FieldType | FieldType[]
182
312
  ): boolean => {
183
- // check if not array or object
313
+ if (Array.isArray(fieldType))
314
+ fieldType = Utils.detectFieldType(String(originalValue), fieldType);
315
+ if (Array.isArray(comparedAtValue) && !["[]", "![]"].includes(operator))
316
+ return comparedAtValue.some((comparedAtValueSingle) =>
317
+ handleComparisonOperator(
318
+ operator,
319
+ originalValue,
320
+ comparedAtValueSingle,
321
+ fieldType
322
+ )
323
+ );
324
+ // check if not array or object // it can't be array or object!
184
325
  switch (operator) {
185
326
  case "=":
186
327
  switch (fieldType) {
187
328
  case "password":
188
- return typeof value === "string" &&
329
+ return typeof originalValue === "string" &&
189
330
  typeof comparedAtValue === "string"
190
- ? Utils.comparePassword(value, comparedAtValue)
331
+ ? Utils.comparePassword(originalValue, comparedAtValue)
191
332
  : false;
192
333
  case "boolean":
193
- return Number(value) - Number(comparedAtValue) === 0;
334
+ return Number(originalValue) - Number(comparedAtValue) === 0;
335
+ case "id":
336
+ return secretKey && typeof comparedAtValue === "string"
337
+ ? Utils.decodeID(comparedAtValue as string, secretKey) ===
338
+ originalValue
339
+ : comparedAtValue === originalValue;
194
340
  default:
195
- return value === comparedAtValue;
341
+ return originalValue === comparedAtValue;
196
342
  }
197
343
  case "!=":
198
344
  return !handleComparisonOperator(
199
345
  "=",
200
- value,
346
+ originalValue,
201
347
  comparedAtValue,
202
348
  fieldType
203
349
  );
204
350
  case ">":
205
- return (
206
- value !== null && comparedAtValue !== null && value > comparedAtValue
207
- );
351
+ return originalValue > comparedAtValue;
208
352
  case "<":
209
- return (
210
- value !== null && comparedAtValue !== null && value < comparedAtValue
211
- );
353
+ return originalValue < comparedAtValue;
212
354
  case ">=":
213
- return (
214
- value !== null && comparedAtValue !== null && value >= comparedAtValue
215
- );
355
+ return originalValue >= comparedAtValue;
216
356
  case "<=":
217
- return (
218
- value !== null && comparedAtValue !== null && value <= comparedAtValue
219
- );
357
+ return originalValue <= comparedAtValue;
220
358
  case "[]":
221
359
  return (
222
- (Array.isArray(value) &&
360
+ (Array.isArray(originalValue) &&
223
361
  Array.isArray(comparedAtValue) &&
224
- value.some(comparedAtValue.includes)) ||
225
- (Array.isArray(value) &&
362
+ originalValue.some(comparedAtValue.includes)) ||
363
+ (Array.isArray(originalValue) &&
226
364
  !Array.isArray(comparedAtValue) &&
227
- value.includes(comparedAtValue)) ||
228
- (!Array.isArray(value) &&
365
+ originalValue.includes(comparedAtValue)) ||
366
+ (!Array.isArray(originalValue) &&
229
367
  Array.isArray(comparedAtValue) &&
230
- comparedAtValue.includes(value))
368
+ comparedAtValue.includes(originalValue))
231
369
  );
232
370
  case "![]":
233
371
  return !handleComparisonOperator(
234
372
  "[]",
235
- value,
373
+ originalValue,
236
374
  comparedAtValue,
237
375
  fieldType
238
376
  );
239
377
  case "*":
240
- return (
241
- value !== null &&
242
- comparedAtValue !== null &&
243
- new RegExp(
244
- `^${comparedAtValue.toString().replace(/%/g, ".*")}$`,
245
- "i"
246
- ).test(value.toString())
247
- );
378
+ return new RegExp(
379
+ `^${(String(comparedAtValue).includes("%")
380
+ ? String(comparedAtValue)
381
+ : "%" + String(comparedAtValue) + "%"
382
+ ).replace(/%/g, ".*")}$`,
383
+ "i"
384
+ ).test(String(originalValue));
248
385
  case "!*":
249
386
  return !handleComparisonOperator(
250
387
  "*",
251
- value,
388
+ originalValue,
252
389
  comparedAtValue,
253
390
  fieldType
254
391
  );
@@ -266,13 +403,19 @@ export const search = async (
266
403
  > = {},
267
404
  lineCount = 0,
268
405
  foundItems = 0;
406
+ let rl: Interface;
407
+ if (doesSupportReadLines()) rl = (await open(filePath)).readLines();
408
+ else
409
+ rl = createInterface({
410
+ input: createReadStream(filePath),
411
+ crlfDelay: Infinity,
412
+ });
269
413
 
270
- const file = await open(filePath),
271
- columnName = decodeFileName(parse(filePath).name);
414
+ const columnName = decodeFileName(parse(filePath).name);
272
415
 
273
- for await (const line of file.readLines()) {
416
+ for await (const line of rl) {
274
417
  lineCount++;
275
- const decodedLine = Utils.decode(line, fieldType);
418
+ const decodedLine = decode(line, fieldType, fieldChildrenType);
276
419
  if (
277
420
  (Array.isArray(operator) &&
278
421
  Array.isArray(comparedAtValue) &&
@@ -318,10 +461,13 @@ export const search = async (
318
461
 
319
462
  export default class File {
320
463
  static get = get;
321
- static count = count;
322
464
  static remove = remove;
323
465
  static search = search;
324
466
  static replace = replace;
467
+ static count = count;
468
+ static encode = encode;
469
+ static decode = decode;
325
470
  static encodeFileName = encodeFileName;
326
471
  static decodeFileName = decodeFileName;
472
+ static isExists = isExists;
327
473
  }