inibase 1.0.0-rc.3 → 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
@@ -1,8 +1,8 @@
1
- ![Inibase banner](./.github/assets/banner.jpg)
1
+ [![Inibase banner](./.github/assets/banner.jpg)](https://github.com/inicontent/inibase)
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;
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,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,71 +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
- return fieldType === "password" &&
187
- typeof value === "string" &&
188
- typeof comparedAtValue === "string"
189
- ? Utils.comparePassword(value, comparedAtValue)
190
- : value === comparedAtValue;
317
+ switch (fieldType) {
318
+ case "password":
319
+ return typeof originalValue === "string" &&
320
+ typeof comparedAtValue === "string"
321
+ ? Utils.comparePassword(originalValue, comparedAtValue)
322
+ : false;
323
+ case "boolean":
324
+ return Number(originalValue) - Number(comparedAtValue) === 0;
325
+ default:
326
+ return Array.isArray(comparedAtValue)
327
+ ? comparedAtValue.some((value) => originalValue === value)
328
+ : originalValue === comparedAtValue;
329
+ }
191
330
  case "!=":
192
331
  return !handleComparisonOperator(
193
332
  "=",
194
- value,
333
+ originalValue,
195
334
  comparedAtValue,
196
335
  fieldType
197
336
  );
198
337
  case ">":
199
- return (
200
- value !== null && comparedAtValue !== null && value > comparedAtValue
201
- );
338
+ return Array.isArray(comparedAtValue)
339
+ ? comparedAtValue.some((value) => originalValue > value)
340
+ : originalValue > comparedAtValue;
202
341
  case "<":
203
- return (
204
- value !== null && comparedAtValue !== null && value < comparedAtValue
205
- );
342
+ return Array.isArray(comparedAtValue)
343
+ ? comparedAtValue.some((value) => originalValue < value)
344
+ : originalValue < comparedAtValue;
206
345
  case ">=":
207
- return (
208
- value !== null && comparedAtValue !== null && value >= comparedAtValue
209
- );
346
+ return Array.isArray(comparedAtValue)
347
+ ? comparedAtValue.some((value) => originalValue >= value)
348
+ : originalValue >= comparedAtValue;
210
349
  case "<=":
211
- return (
212
- value !== null && comparedAtValue !== null && value <= comparedAtValue
213
- );
350
+ return Array.isArray(comparedAtValue)
351
+ ? comparedAtValue.some((value) => originalValue <= value)
352
+ : originalValue <= comparedAtValue;
214
353
  case "[]":
215
354
  return (
216
- (Array.isArray(value) &&
355
+ (Array.isArray(originalValue) &&
217
356
  Array.isArray(comparedAtValue) &&
218
- value.some(comparedAtValue.includes)) ||
219
- (Array.isArray(value) &&
357
+ originalValue.some(comparedAtValue.includes)) ||
358
+ (Array.isArray(originalValue) &&
220
359
  !Array.isArray(comparedAtValue) &&
221
- value.includes(comparedAtValue)) ||
222
- (!Array.isArray(value) &&
360
+ originalValue.includes(comparedAtValue)) ||
361
+ (!Array.isArray(originalValue) &&
223
362
  Array.isArray(comparedAtValue) &&
224
- comparedAtValue.includes(value))
363
+ comparedAtValue.includes(originalValue))
225
364
  );
226
365
  case "![]":
227
366
  return !handleComparisonOperator(
228
367
  "[]",
229
- value,
368
+ originalValue,
230
369
  comparedAtValue,
231
370
  fieldType
232
371
  );
233
372
  case "*":
234
- return (
235
- value !== null &&
236
- comparedAtValue !== null &&
237
- new RegExp(
238
- `^${comparedAtValue.toString().replace(/%/g, ".*")}$`,
239
- "i"
240
- ).test(value.toString())
241
- );
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));
242
390
  case "!*":
243
391
  return !handleComparisonOperator(
244
392
  "*",
245
- value,
393
+ originalValue,
246
394
  comparedAtValue,
247
395
  fieldType
248
396
  );
@@ -260,16 +408,21 @@ export const search = async (
260
408
  > = {},
261
409
  lineCount = 0,
262
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
+ });
263
418
 
264
- const file = await open(filePath),
265
- columnName = decodeFileName(parse(filePath).name);
419
+ const columnName = decodeFileName(parse(filePath).name);
266
420
 
267
- for await (const line of file.readLines()) {
421
+ for await (const line of rl) {
268
422
  lineCount++;
269
- const decodedLine = Utils.decode(line, fieldType);
423
+ const decodedLine = decode(line, fieldType, fieldChildrenType);
270
424
  if (
271
- decodedLine &&
272
- ((Array.isArray(operator) &&
425
+ (Array.isArray(operator) &&
273
426
  Array.isArray(comparedAtValue) &&
274
427
  ((logicalOperator &&
275
428
  logicalOperator === "or" &&
@@ -289,13 +442,13 @@ export const search = async (
289
442
  fieldType
290
443
  )
291
444
  ))) ||
292
- (!Array.isArray(operator) &&
293
- handleComparisonOperator(
294
- operator,
295
- decodedLine,
296
- comparedAtValue,
297
- fieldType
298
- )))
445
+ (!Array.isArray(operator) &&
446
+ handleComparisonOperator(
447
+ operator,
448
+ decodedLine,
449
+ comparedAtValue,
450
+ fieldType
451
+ ))
299
452
  ) {
300
453
  foundItems++;
301
454
  if (offset && foundItems < offset) continue;
@@ -313,10 +466,12 @@ export const search = async (
313
466
 
314
467
  export default class File {
315
468
  static get = get;
316
- static count = count;
317
469
  static remove = remove;
318
470
  static search = search;
319
471
  static replace = replace;
472
+ static count = count;
473
+ static encode = encode;
474
+ static decode = decode;
320
475
  static encodeFileName = encodeFileName;
321
476
  static decodeFileName = decodeFileName;
322
477
  }