inibase 1.0.0-rc.4 → 1.0.0-rc.5

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).
@@ -342,6 +342,11 @@ const users = await db.get("user", { favoriteFoods: "[]Pizza" });
342
342
  // },
343
343
  // ...
344
344
  // ]
345
+
346
+ // Get all "user" columns except "username" & "address.street"
347
+ const users = await db.get("user", undefined, {
348
+ columns: ["!username", "!address.street"],
349
+ });
345
350
  ```
346
351
 
347
352
  </details>
@@ -445,7 +450,7 @@ type Data = {
445
450
  - [x] Pagination
446
451
  - [x] Criteria
447
452
  - [x] Columns
448
- - [ ] Order
453
+ - [ ] Order By
449
454
  - [x] POST
450
455
  - [x] PUT
451
456
  - [x] DELETE
@@ -461,6 +466,11 @@ type Data = {
461
466
  - [x] Array
462
467
  - [x] Password
463
468
  - [ ] IP
469
+ - [ ] HTML
470
+ - [ ] Markdown
471
+ - [ ] TO-DO:
472
+ - [ ] Improve caching
473
+ - [ ] Commenting the code
464
474
  - [ ] Features:
465
475
  - [ ] Encryption
466
476
  - [ ] Compress data
package/file.ts CHANGED
@@ -1,9 +1,22 @@
1
- import { createWriteStream, unlinkSync, renameSync, existsSync } from "fs";
1
+ import {
2
+ createWriteStream,
3
+ unlinkSync,
4
+ renameSync,
5
+ existsSync,
6
+ createReadStream,
7
+ WriteStream,
8
+ } from "fs";
2
9
  import { open } from "fs/promises";
10
+ import { Interface, createInterface } from "readline";
3
11
  import { parse } from "path";
4
12
  import { ComparisonOperator, FieldType } from ".";
5
13
  import Utils from "./utils";
6
14
 
15
+ const doesSupportReadLines = () => {
16
+ const [major, minor, patch] = process.versions.node.split(".").map(Number);
17
+ return major >= 18 && minor >= 11;
18
+ };
19
+
7
20
  export const encodeFileName = (fileName: string, extension?: string) => {
8
21
  return (
9
22
  fileName.replaceAll("%", "%25").replaceAll("*", "%") +
@@ -15,12 +28,99 @@ export const decodeFileName = (fileName: string) => {
15
28
  return fileName.replaceAll("%", "*").replaceAll("*25", "%");
16
29
  };
17
30
 
31
+ export const encode = (
32
+ input: string | number | boolean | null | (string | number | boolean | null)[]
33
+ ) => {
34
+ const secureString = (input: string | number | boolean | null) => {
35
+ if (["true", "false"].includes(String(input))) return input ? 1 : 0;
36
+ return typeof input === "string"
37
+ ? decodeURIComponent(input)
38
+ .replaceAll("<", "&lt;")
39
+ .replaceAll(">", "&gt;")
40
+ .replaceAll(",", "%2C")
41
+ .replaceAll("|", "%7C")
42
+ .replaceAll("\n", "\\n")
43
+ .replaceAll("\r", "\\r")
44
+ : input;
45
+ };
46
+ return Array.isArray(input)
47
+ ? Utils.isArrayOfArrays(input)
48
+ ? (input as any[])
49
+ .map((_input) => _input.map(secureString).join(","))
50
+ .join("|")
51
+ : input.map(secureString).join(",")
52
+ : secureString(input);
53
+ };
54
+
55
+ export const decode = (
56
+ input: string | null | number,
57
+ fieldType?: FieldType | FieldType[],
58
+ fieldChildrenType?: FieldType | FieldType[]
59
+ ): string | number | boolean | null | (string | number | null | boolean)[] => {
60
+ if (!fieldType) return null;
61
+ const unSecureString = (input: string) =>
62
+ decodeURIComponent(input)
63
+ .replaceAll("&lt;", "<")
64
+ .replaceAll("&gt;", ">")
65
+ .replaceAll("%2C", ",")
66
+ .replaceAll("%7C", "|")
67
+ .replaceAll("\\n", "\n")
68
+ .replaceAll("\\r", "\r") || null;
69
+ if (input === null || input === "") return null;
70
+ if (Array.isArray(fieldType))
71
+ fieldType = Utils.detectFieldType(String(input), fieldType);
72
+ const decodeHelper = (value: string | number | any[]) => {
73
+ if (Array.isArray(value) && fieldType !== "array")
74
+ return value.map(decodeHelper);
75
+ switch (fieldType as FieldType) {
76
+ case "table":
77
+ case "number":
78
+ return isNaN(Number(value)) ? null : Number(value);
79
+ case "boolean":
80
+ return typeof value === "string" ? value === "true" : Boolean(value);
81
+ case "array":
82
+ if (!Array.isArray(value)) return null;
83
+ if (fieldChildrenType)
84
+ return value.map(
85
+ (v) =>
86
+ decode(
87
+ v,
88
+ Array.isArray(fieldChildrenType)
89
+ ? Utils.detectFieldType(v, fieldChildrenType)
90
+ : fieldChildrenType
91
+ ) as string | number | boolean | null
92
+ );
93
+ else return value;
94
+ default:
95
+ return value;
96
+ }
97
+ };
98
+ return decodeHelper(
99
+ typeof input === "string"
100
+ ? input.includes(",")
101
+ ? input.includes("|")
102
+ ? input
103
+ .split("|")
104
+ .map((_input) => _input.split(",").map(unSecureString))
105
+ : input.split(",").map(unSecureString)
106
+ : unSecureString(input)
107
+ : input
108
+ );
109
+ };
110
+
18
111
  export const get = async (
19
112
  filePath: string,
20
- fieldType?: FieldType,
21
- lineNumbers?: number | number[]
113
+ lineNumbers?: number | number[],
114
+ fieldType?: FieldType | FieldType[],
115
+ fieldChildrenType?: FieldType | FieldType[]
22
116
  ) => {
23
- const file = await open(filePath);
117
+ let rl: Interface;
118
+ if (doesSupportReadLines()) rl = (await open(filePath)).readLines();
119
+ else
120
+ rl = createInterface({
121
+ input: createReadStream(filePath),
122
+ crlfDelay: Infinity,
123
+ });
24
124
  let lines: Record<
25
125
  number,
26
126
  | string
@@ -32,21 +132,23 @@ export const get = async (
32
132
  lineCount = 0;
33
133
 
34
134
  if (!lineNumbers) {
35
- for await (const line of file.readLines())
36
- lineCount++, (lines[lineCount] = Utils.decode(line, fieldType));
135
+ for await (const line of rl)
136
+ lineCount++,
137
+ (lines[lineCount] = decode(line, fieldType, fieldChildrenType));
37
138
  } 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) };
139
+ let lastLine: string;
140
+ for await (const line of rl) lineCount++, (lastLine = line);
141
+ if (lastLine)
142
+ lines = { [lineCount]: decode(lastLine, fieldType, fieldChildrenType) };
41
143
  } else {
42
144
  let lineNumbersArray = [
43
145
  ...(Array.isArray(lineNumbers) ? lineNumbers : [lineNumbers]),
44
146
  ];
45
- for await (const line of file.readLines()) {
147
+ for await (const line of rl) {
46
148
  lineCount++;
47
149
  if (!lineNumbersArray.includes(lineCount)) continue;
48
150
  const indexOfLineCount = lineNumbersArray.indexOf(lineCount);
49
- lines[lineCount] = Utils.decode(line, fieldType);
151
+ lines[lineCount] = decode(line, fieldType, fieldChildrenType);
50
152
  lineNumbersArray[indexOfLineCount] = 0;
51
153
  if (!lineNumbersArray.filter((lineN) => lineN !== 0).length) break;
52
154
  }
@@ -69,33 +171,43 @@ export const replace = async (
69
171
  >
70
172
  ) => {
71
173
  if (existsSync(filePath)) {
72
- const file = await open(filePath, "w+"),
174
+ let rl: Interface, writeStream: WriteStream;
175
+ if (doesSupportReadLines()) {
176
+ const file = await open(filePath, "w+");
177
+ rl = file.readLines();
73
178
  writeStream = file.createWriteStream();
179
+ } else {
180
+ rl = createInterface({
181
+ input: createReadStream(filePath),
182
+ crlfDelay: Infinity,
183
+ });
184
+ writeStream = createWriteStream(filePath);
185
+ }
74
186
  if (typeof replacements === "object" && !Array.isArray(replacements)) {
75
187
  let lineCount = 0;
76
- for await (const line of file.readLines()) {
188
+ for await (const line of rl) {
77
189
  lineCount++;
78
190
  writeStream.write(
79
- (lineCount in replacements
80
- ? Utils.encode(replacements[lineCount])
81
- : line) + "\n"
191
+ (lineCount in replacements ? encode(replacements[lineCount]) : line) +
192
+ "\n"
82
193
  );
83
194
  }
84
195
  } else
85
- for await (const _line of file.readLines())
86
- writeStream.write(Utils.encode(replacements) + "\n");
196
+ for await (const _line of rl)
197
+ writeStream.write(encode(replacements) + "\n");
87
198
 
88
199
  writeStream.end();
89
200
  } 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;
201
+ let writeStream: WriteStream;
202
+ if (doesSupportReadLines())
203
+ writeStream = (await open(filePath, "w")).createWriteStream();
204
+ else writeStream = createWriteStream(filePath);
205
+ const largestLinesNumbers =
206
+ Math.max(...Object.keys(replacements).map(Number)) + 1;
94
207
  for (let lineCount = 1; lineCount < largestLinesNumbers; lineCount++) {
95
208
  writeStream.write(
96
- (lineCount in replacements
97
- ? Utils.encode(replacements[lineCount])
98
- : "") + "\n"
209
+ (lineCount in replacements ? encode(replacements[lineCount]) : "") +
210
+ "\n"
99
211
  );
100
212
  }
101
213
  writeStream.end();
@@ -111,11 +223,21 @@ export const remove = async (
111
223
  const tempFilePath = `${filePath}.tmp`,
112
224
  linesToDeleteArray = [
113
225
  ...(Array.isArray(linesToDelete) ? linesToDelete : [linesToDelete]),
114
- ],
115
- writeStream = createWriteStream(tempFilePath),
116
- file = await open(filePath);
226
+ ];
227
+
228
+ let rl: Interface, writeStream: WriteStream;
229
+ if (doesSupportReadLines()) {
230
+ rl = (await open(filePath)).readLines();
231
+ writeStream = (await open(tempFilePath, "w+")).createWriteStream();
232
+ } else {
233
+ rl = createInterface({
234
+ input: createReadStream(filePath),
235
+ crlfDelay: Infinity,
236
+ });
237
+ writeStream = createWriteStream(tempFilePath);
238
+ }
117
239
 
118
- for await (const line of file.readLines()) {
240
+ for await (const line of rl) {
119
241
  lineCount++;
120
242
  if (!linesToDeleteArray.includes(lineCount)) {
121
243
  writeStream.write(`${line}\n`);
@@ -129,18 +251,22 @@ export const remove = async (
129
251
  };
130
252
 
131
253
  export const count = async (filePath: string): Promise<number> => {
132
- let lineCount = 0;
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
+ });
133
262
 
134
- const file = await open(filePath);
135
-
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,6 +275,8 @@ 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
282
  readWholeFile?: boolean
@@ -166,7 +294,7 @@ export const search = async (
166
294
  > => {
167
295
  const handleComparisonOperator = (
168
296
  operator: ComparisonOperator,
169
- value:
297
+ originalValue:
170
298
  | string
171
299
  | number
172
300
  | boolean
@@ -178,77 +306,91 @@ export const search = async (
178
306
  | boolean
179
307
  | null
180
308
  | (string | number | boolean | null)[],
181
- fieldType: FieldType
309
+ fieldType?: FieldType | FieldType[],
310
+ fieldChildrenType?: FieldType | FieldType[]
182
311
  ): boolean => {
183
- // check if not array or object
312
+ if (Array.isArray(fieldType))
313
+ fieldType = Utils.detectFieldType(String(originalValue), fieldType);
314
+ // check if not array or object // it can't be array or object!
184
315
  switch (operator) {
185
316
  case "=":
186
317
  switch (fieldType) {
187
318
  case "password":
188
- return typeof value === "string" &&
319
+ return typeof originalValue === "string" &&
189
320
  typeof comparedAtValue === "string"
190
- ? Utils.comparePassword(value, comparedAtValue)
321
+ ? Utils.comparePassword(originalValue, comparedAtValue)
191
322
  : false;
192
323
  case "boolean":
193
- return Number(value) - Number(comparedAtValue) === 0;
324
+ return Number(originalValue) - Number(comparedAtValue) === 0;
194
325
  default:
195
- return value === comparedAtValue;
326
+ return Array.isArray(comparedAtValue)
327
+ ? comparedAtValue.some((value) => originalValue === value)
328
+ : originalValue === comparedAtValue;
196
329
  }
197
330
  case "!=":
198
331
  return !handleComparisonOperator(
199
332
  "=",
200
- value,
333
+ originalValue,
201
334
  comparedAtValue,
202
335
  fieldType
203
336
  );
204
337
  case ">":
205
- return (
206
- value !== null && comparedAtValue !== null && value > comparedAtValue
207
- );
338
+ return Array.isArray(comparedAtValue)
339
+ ? comparedAtValue.some((value) => originalValue > value)
340
+ : originalValue > comparedAtValue;
208
341
  case "<":
209
- return (
210
- value !== null && comparedAtValue !== null && value < comparedAtValue
211
- );
342
+ return Array.isArray(comparedAtValue)
343
+ ? comparedAtValue.some((value) => originalValue < value)
344
+ : originalValue < comparedAtValue;
212
345
  case ">=":
213
- return (
214
- value !== null && comparedAtValue !== null && value >= comparedAtValue
215
- );
346
+ return Array.isArray(comparedAtValue)
347
+ ? comparedAtValue.some((value) => originalValue >= value)
348
+ : originalValue >= comparedAtValue;
216
349
  case "<=":
217
- return (
218
- value !== null && comparedAtValue !== null && value <= comparedAtValue
219
- );
350
+ return Array.isArray(comparedAtValue)
351
+ ? comparedAtValue.some((value) => originalValue <= value)
352
+ : originalValue <= comparedAtValue;
220
353
  case "[]":
221
354
  return (
222
- (Array.isArray(value) &&
355
+ (Array.isArray(originalValue) &&
223
356
  Array.isArray(comparedAtValue) &&
224
- value.some(comparedAtValue.includes)) ||
225
- (Array.isArray(value) &&
357
+ originalValue.some(comparedAtValue.includes)) ||
358
+ (Array.isArray(originalValue) &&
226
359
  !Array.isArray(comparedAtValue) &&
227
- value.includes(comparedAtValue)) ||
228
- (!Array.isArray(value) &&
360
+ originalValue.includes(comparedAtValue)) ||
361
+ (!Array.isArray(originalValue) &&
229
362
  Array.isArray(comparedAtValue) &&
230
- comparedAtValue.includes(value))
363
+ comparedAtValue.includes(originalValue))
231
364
  );
232
365
  case "![]":
233
366
  return !handleComparisonOperator(
234
367
  "[]",
235
- value,
368
+ originalValue,
236
369
  comparedAtValue,
237
370
  fieldType
238
371
  );
239
372
  case "*":
240
- return (
241
- value !== null &&
242
- comparedAtValue !== null &&
243
- new RegExp(
244
- `^${comparedAtValue.toString().replace(/%/g, ".*")}$`,
245
- "i"
246
- ).test(value.toString())
247
- );
373
+ return Array.isArray(comparedAtValue)
374
+ ? comparedAtValue.some((value) =>
375
+ new RegExp(
376
+ `^${(String(value).includes("%")
377
+ ? String(value)
378
+ : "%" + String(value) + "%"
379
+ ).replace(/%/g, ".*")}$`,
380
+ "i"
381
+ ).test(String(originalValue))
382
+ )
383
+ : new RegExp(
384
+ `^${(String(comparedAtValue).includes("%")
385
+ ? String(comparedAtValue)
386
+ : "%" + String(comparedAtValue) + "%"
387
+ ).replace(/%/g, ".*")}$`,
388
+ "i"
389
+ ).test(String(originalValue));
248
390
  case "!*":
249
391
  return !handleComparisonOperator(
250
392
  "*",
251
- value,
393
+ originalValue,
252
394
  comparedAtValue,
253
395
  fieldType
254
396
  );
@@ -266,13 +408,19 @@ export const search = async (
266
408
  > = {},
267
409
  lineCount = 0,
268
410
  foundItems = 0;
411
+ let rl: Interface;
412
+ if (doesSupportReadLines()) rl = (await open(filePath)).readLines();
413
+ else
414
+ rl = createInterface({
415
+ input: createReadStream(filePath),
416
+ crlfDelay: Infinity,
417
+ });
269
418
 
270
- const file = await open(filePath),
271
- columnName = decodeFileName(parse(filePath).name);
419
+ const columnName = decodeFileName(parse(filePath).name);
272
420
 
273
- for await (const line of file.readLines()) {
421
+ for await (const line of rl) {
274
422
  lineCount++;
275
- const decodedLine = Utils.decode(line, fieldType);
423
+ const decodedLine = decode(line, fieldType, fieldChildrenType);
276
424
  if (
277
425
  (Array.isArray(operator) &&
278
426
  Array.isArray(comparedAtValue) &&
@@ -318,10 +466,12 @@ export const search = async (
318
466
 
319
467
  export default class File {
320
468
  static get = get;
321
- static count = count;
322
469
  static remove = remove;
323
470
  static search = search;
324
471
  static replace = replace;
472
+ static count = count;
473
+ static encode = encode;
474
+ static decode = decode;
325
475
  static encodeFileName = encodeFileName;
326
476
  static decodeFileName = decodeFileName;
327
477
  }