inibase 1.0.0-rc.12 → 1.0.0-rc.15

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/index.ts DELETED
@@ -1,1587 +0,0 @@
1
- import {
2
- unlink,
3
- rename,
4
- readFile,
5
- writeFile,
6
- mkdir,
7
- readdir,
8
- } from "node:fs/promises";
9
- import { join, parse } from "node:path";
10
- import { scryptSync } from "node:crypto";
11
- import File from "./file";
12
- import Utils from "./utils";
13
-
14
- export interface Data {
15
- id?: number | string;
16
- [key: string]: any;
17
- createdAt?: Date;
18
- updatedAt?: Date;
19
- }
20
-
21
- export type FieldType =
22
- | "string"
23
- | "number"
24
- | "boolean"
25
- | "date"
26
- | "email"
27
- | "url"
28
- | "table"
29
- | "object"
30
- | "array"
31
- | "password"
32
- | "html"
33
- | "ip"
34
- | "id";
35
- type FieldDefault = {
36
- id?: string | number | null | undefined;
37
- key: string;
38
- required?: boolean;
39
- children?: any;
40
- };
41
- type FieldStringType = {
42
- type: Exclude<FieldType, "array" | "object">;
43
- };
44
- type FieldStringArrayType = {
45
- type: Exclude<FieldType, "array" | "object">[];
46
- };
47
- type FieldArrayType = {
48
- type: "array";
49
- children: FieldType | FieldType[] | Schema;
50
- };
51
- type FieldArrayArrayType = {
52
- type: ["array", ...FieldType[]];
53
- children: FieldType | FieldType[];
54
- };
55
- type FieldObjectType = {
56
- type: "object";
57
- children: Schema;
58
- };
59
- // if "type" is array, make "array" at first place, and "number" & "string" at last place of the array
60
- export type Field = FieldDefault &
61
- (
62
- | FieldStringType
63
- | FieldStringArrayType
64
- | FieldObjectType
65
- | FieldArrayType
66
- | FieldArrayArrayType
67
- );
68
-
69
- export type Schema = Field[];
70
-
71
- export interface Options {
72
- page?: number;
73
- per_page?: number;
74
- columns?: string[] | string;
75
- order?: Record<string, "asc" | "desc">;
76
- }
77
-
78
- export type ComparisonOperator =
79
- | "="
80
- | "!="
81
- | ">"
82
- | "<"
83
- | ">="
84
- | "<="
85
- | "*"
86
- | "!*"
87
- | "[]"
88
- | "![]";
89
-
90
- type pageInfo = {
91
- total?: number;
92
- total_pages?: number;
93
- } & Options;
94
-
95
- export type Criteria =
96
- | ({
97
- [logic in "and" | "or"]?: Criteria | (string | number | boolean | null)[];
98
- } & {
99
- [key: string]: string | number | boolean | Criteria;
100
- })
101
- | null;
102
-
103
- declare global {
104
- type Entries<T> = {
105
- [K in keyof T]: [K, T[K]];
106
- }[keyof T][];
107
-
108
- interface ObjectConstructor {
109
- entries<T extends object>(o: T): Entries<T>;
110
- }
111
- }
112
-
113
- export default class Inibase {
114
- public folder: string;
115
- public database: string;
116
- public table: string;
117
- public pageInfo: pageInfo;
118
- private cache: Map<string, string>;
119
- private totalItems: Record<string, number>;
120
- public salt: Buffer;
121
-
122
- constructor(database: string, mainFolder: string = ".") {
123
- this.database = database;
124
- this.folder = mainFolder;
125
- this.table = null;
126
- this.cache = new Map<string, any>();
127
- this.totalItems = {};
128
- this.pageInfo = { page: 1, per_page: 15 };
129
- this.salt = scryptSync(
130
- process.env.INIBASE_SECRET ?? "inibase",
131
- (process.env.INIBASE_SECRET ?? "inibase") + "_salt",
132
- 32
133
- );
134
- }
135
-
136
- private throwError(
137
- code: string,
138
- variable?:
139
- | string
140
- | number
141
- | (string | number)[]
142
- | Record<string, string | number>,
143
- language: string = "en"
144
- ): Error {
145
- const errorMessages: Record<string, Record<string, string>> = {
146
- en: {
147
- FIELD_REQUIRED: "REQUIRED: {variable}",
148
- NO_SCHEMA: "NO_SCHEMA: {variable}",
149
- NO_ITEMS: "NO_ITEMS: {variable}",
150
- NO_DATA: "NO_DATA: {variable}",
151
- INVALID_ID: "INVALID_ID: {variable}",
152
- INVALID_TYPE: "INVALID_TYPE: {variable}",
153
- INVALID_OPERATOR: "INVALID_OPERATOR: {variable}",
154
- INVALID_PARAMETERS: "PARAMETERS: {variable}",
155
- },
156
- // Add more languages and error messages as needed
157
- };
158
-
159
- let errorMessage = errorMessages[language][code] || code;
160
- if (variable) {
161
- if (
162
- typeof variable === "string" ||
163
- typeof variable === "number" ||
164
- Array.isArray(variable)
165
- )
166
- errorMessage = errorMessage.replaceAll(
167
- `{variable}`,
168
- Array.isArray(variable) ? variable.join(", ") : (variable as string)
169
- );
170
- else
171
- Object.keys(variable).forEach(
172
- (variableKey) =>
173
- (errorMessage = errorMessage.replaceAll(
174
- `{${variableKey}}`,
175
- variable[variableKey].toString()
176
- ))
177
- );
178
- }
179
- return new Error(errorMessage);
180
- }
181
-
182
- public async setTableSchema(
183
- tableName: string,
184
- schema: Schema
185
- ): Promise<void> {
186
- const decodeIdFromSchema = (schema: Schema) =>
187
- schema.map((field) => {
188
- if (field.children && Utils.isArrayOfObjects(field.children))
189
- field.children = decodeIdFromSchema(field.children as Schema);
190
- if (!Utils.isNumber(field.id))
191
- field.id = Utils.decodeID(field.id, this.salt);
192
- return field;
193
- });
194
- // remove id from schema
195
- schema = schema.filter(
196
- ({ key }) => !["id", "createdAt", "updatedAt"].includes(key)
197
- );
198
- schema = Utils.addIdToSchema(
199
- schema,
200
- Utils.findLastIdNumber(schema, this.salt),
201
- this.salt
202
- );
203
- const TablePath = join(this.folder, this.database, tableName),
204
- TableSchemaPath = join(TablePath, "schema.json");
205
- if (!(await File.isExists(TablePath)))
206
- await mkdir(TablePath, { recursive: true });
207
- if (await File.isExists(TableSchemaPath)) {
208
- // update columns files names based on field id
209
- const schemaToIdsPath = (schema: Schema, prefix = "") => {
210
- let RETURN: any = {};
211
- for (const field of schema)
212
- if (field.children && Utils.isArrayOfObjects(field.children)) {
213
- Utils.deepMerge(
214
- RETURN,
215
- schemaToIdsPath(
216
- field.children as Schema,
217
- (prefix ?? "") + field.key + "."
218
- )
219
- );
220
- } else if (Utils.isValidID(field.id))
221
- RETURN[Utils.decodeID(field.id, this.salt)] =
222
- (prefix ?? "") + field.key + ".inib";
223
-
224
- return RETURN;
225
- },
226
- replaceOldPathes = Utils.findChangedProperties(
227
- schemaToIdsPath(await this.getTableSchema(tableName)),
228
- schemaToIdsPath(schema)
229
- );
230
- if (replaceOldPathes)
231
- for (const [oldPath, newPath] of Object.entries(replaceOldPathes))
232
- if (await File.isExists(join(TablePath, oldPath)))
233
- await rename(join(TablePath, oldPath), join(TablePath, newPath));
234
- }
235
-
236
- await writeFile(
237
- join(TablePath, "schema.json"),
238
- JSON.stringify(decodeIdFromSchema(schema))
239
- );
240
- }
241
-
242
- public async getTableSchema(tableName: string): Promise<Schema | undefined> {
243
- const TableSchemaPath = join(
244
- this.folder,
245
- this.database,
246
- tableName,
247
- "schema.json"
248
- );
249
- if (!(await File.isExists(TableSchemaPath))) return undefined;
250
-
251
- if (!this.cache.has(TableSchemaPath))
252
- this.cache.set(TableSchemaPath, await readFile(TableSchemaPath, "utf8"));
253
-
254
- if (!this.cache.get(TableSchemaPath)) return undefined;
255
- const schema = JSON.parse(this.cache.get(TableSchemaPath)),
256
- lastIdNumber = Utils.findLastIdNumber(schema, this.salt);
257
-
258
- return [
259
- {
260
- id: Utils.encodeID(0, this.salt),
261
- key: "id",
262
- type: "id",
263
- required: true,
264
- },
265
- ...Utils.addIdToSchema(schema, lastIdNumber, this.salt),
266
- {
267
- id: Utils.encodeID(lastIdNumber + 1, this.salt),
268
- key: "createdAt",
269
- type: "date",
270
- required: true,
271
- },
272
- {
273
- id: Utils.encodeID(lastIdNumber + 2, this.salt),
274
- key: "updatedAt",
275
- type: "date",
276
- required: false,
277
- },
278
- ];
279
- }
280
-
281
- public getField<Property extends keyof Field | "children">(
282
- keyPath: string,
283
- schema: Schema | Field,
284
- property?: Property
285
- ) {
286
- const keyPathSplited = keyPath.split(".");
287
- for (const [index, key] of keyPathSplited.entries()) {
288
- if (key === "*") continue;
289
- const foundItem = (schema as Schema).find((item) => item.key === key);
290
- if (!foundItem) return null;
291
- if (index === keyPathSplited.length - 1) schema = foundItem;
292
- if (
293
- (foundItem.type === "array" || foundItem.type === "object") &&
294
- foundItem.children &&
295
- Utils.isArrayOfObjects(foundItem.children)
296
- )
297
- schema = foundItem.children as Schema;
298
- }
299
- if (property) {
300
- switch (property) {
301
- case "type":
302
- return (schema as Field).type;
303
- case "children":
304
- return (
305
- schema as
306
- | (Field & FieldObjectType)
307
- | FieldArrayType
308
- | FieldArrayArrayType
309
- ).children;
310
-
311
- default:
312
- return (schema as Field)[property as keyof Field];
313
- }
314
- } else return schema as Field;
315
- }
316
-
317
- public validateData(
318
- data: Data | Data[],
319
- schema: Schema,
320
- skipRequiredField: boolean = false
321
- ): void {
322
- if (Utils.isArrayOfObjects(data))
323
- for (const single_data of data as Data[])
324
- this.validateData(single_data, schema, skipRequiredField);
325
- else if (Utils.isObject(data)) {
326
- for (const { key, type, required, children } of schema) {
327
- if (!data.hasOwnProperty(key) && required && !skipRequiredField)
328
- throw this.throwError("FIELD_REQUIRED", key);
329
- if (
330
- data.hasOwnProperty(key) &&
331
- !Utils.validateFieldType(
332
- data[key],
333
- type,
334
- children && !Utils.isArrayOfObjects(children) ? children : undefined
335
- )
336
- )
337
- throw this.throwError("INVALID_TYPE", key);
338
- if (
339
- (type === "array" || type === "object") &&
340
- children &&
341
- Utils.isArrayOfObjects(children)
342
- )
343
- this.validateData(data[key], children as Schema, skipRequiredField);
344
- }
345
- }
346
- }
347
-
348
- public formatData<dataType extends Data | Data[]>(
349
- data: dataType,
350
- schema: Schema,
351
- formatOnlyAvailiableKeys?: boolean
352
- ): dataType extends Data ? Data : Data[] {
353
- this.validateData(data, schema, formatOnlyAvailiableKeys);
354
-
355
- const formatField = (
356
- value: Data | number | string | (number | string | Data)[],
357
- field: Field
358
- ): Data | Data[] | number | string => {
359
- if (Array.isArray(field.type))
360
- field.type = Utils.detectFieldType(value, field.type);
361
- switch (field.type) {
362
- case "array":
363
- if (!Array.isArray(value)) value = [value];
364
- if (typeof field.children === "string") {
365
- if (field.type === "array" && field.children === "table") {
366
- if (Array.isArray(value)) {
367
- if (Utils.isArrayOfObjects(value)) {
368
- if (
369
- value.every(
370
- (item: any): item is Data =>
371
- item.hasOwnProperty("id") &&
372
- (Utils.isValidID(item.id) || Utils.isNumber(item.id))
373
- )
374
- )
375
- value.map((item) =>
376
- Utils.isNumber(item.id)
377
- ? Number(item.id)
378
- : Utils.decodeID(item.id, this.salt)
379
- );
380
- } else if (
381
- (value as (number | string)[]).every(Utils.isValidID) ||
382
- (value as (number | string)[]).every(Utils.isNumber)
383
- )
384
- return (value as (number | string)[]).map((item) =>
385
- Utils.isNumber(item)
386
- ? Number(item)
387
- : Utils.decodeID(item, this.salt)
388
- );
389
- } else if (Utils.isValidID(value))
390
- return [Utils.decodeID(value, this.salt)];
391
- else if (Utils.isNumber(value)) return [Number(value)];
392
- } else if (data.hasOwnProperty(field.key)) return value;
393
- } else if (Utils.isArrayOfObjects(field.children))
394
- return this.formatData(
395
- value as Data[],
396
- field.children as Schema,
397
- formatOnlyAvailiableKeys
398
- );
399
- else if (Array.isArray(field.children))
400
- return Array.isArray(value) ? value : [value];
401
- break;
402
- case "object":
403
- if (Utils.isArrayOfObjects(field.children))
404
- return this.formatData(
405
- value as Data,
406
- field.children as Schema,
407
- formatOnlyAvailiableKeys
408
- );
409
- break;
410
- case "table":
411
- if (Array.isArray(value)) value = value[0];
412
- if (Utils.isObject(value)) {
413
- if (
414
- (value as Data).hasOwnProperty("id") &&
415
- (Utils.isValidID((value as Data).id) ||
416
- Utils.isNumber((value as Data).id))
417
- )
418
- return Utils.isNumber((value as Data).id)
419
- ? Number((value as Data).id)
420
- : Utils.decodeID((value as Data).id as string, this.salt);
421
- } else if (Utils.isValidID(value) || Utils.isNumber(value))
422
- return Utils.isNumber(value)
423
- ? Number(value)
424
- : Utils.decodeID(value, this.salt);
425
- break;
426
- case "password":
427
- if (Array.isArray(value)) value = value[0];
428
- return typeof value === "string" && value.length === 161
429
- ? value
430
- : Utils.hashPassword(String(value));
431
- case "number":
432
- if (Array.isArray(value)) value = value[0];
433
- return Utils.isNumber(value) ? Number(value) : null;
434
- case "id":
435
- if (Array.isArray(value)) value = value[0];
436
- return Utils.isNumber(value)
437
- ? value
438
- : Utils.decodeID(value as string, this.salt);
439
- default:
440
- return value;
441
- }
442
- return null;
443
- };
444
-
445
- if (Utils.isArrayOfObjects(data))
446
- return data.map((single_data: Data) =>
447
- this.formatData(single_data, schema, formatOnlyAvailiableKeys)
448
- );
449
- else if (Utils.isObject(data)) {
450
- let RETURN: Data = {};
451
- for (const field of schema) {
452
- if (!data.hasOwnProperty(field.key)) {
453
- if (formatOnlyAvailiableKeys || !field.required) continue;
454
- RETURN[field.key] = this.getDefaultValue(field);
455
- continue;
456
- }
457
- RETURN[field.key] = formatField(data[field.key], field);
458
- }
459
- return RETURN;
460
- } else return [];
461
- }
462
-
463
- private getDefaultValue(field: Field): any {
464
- if (Array.isArray(field.type))
465
- return this.getDefaultValue({
466
- ...field,
467
- type: field.type.sort(
468
- (a: FieldType, b: FieldType) =>
469
- Number(b === "array") - Number(a === "array") ||
470
- Number(a === "string") - Number(b === "string") ||
471
- Number(a === "number") - Number(b === "number")
472
- )[0],
473
- } as Field);
474
-
475
- switch (field.type) {
476
- case "array":
477
- return Utils.isArrayOfObjects(field.children)
478
- ? [
479
- this.getDefaultValue({
480
- ...field,
481
- type: "object",
482
- children: field.children as Schema,
483
- }),
484
- ]
485
- : [];
486
- case "object":
487
- return Utils.combineObjects(
488
- field.children.map((f: Field) => ({
489
- [f.key]: this.getDefaultValue(f),
490
- }))
491
- );
492
- case "boolean":
493
- return false;
494
- default:
495
- return null;
496
- }
497
- }
498
-
499
- public joinPathesContents(
500
- mainPath: string,
501
- data: Data | Data[]
502
- ): { [key: string]: string[] } {
503
- const CombineData = (_data: Data | Data[], prefix?: string) => {
504
- let RETURN: Record<
505
- string,
506
- string | boolean | number | null | (string | boolean | number | null)[]
507
- > = {};
508
- const combineObjectsToArray = (input: any[]) =>
509
- input.reduce(
510
- (r, c) => (
511
- Object.keys(c).map((k) => (r[k] = [...(r[k] || []), c[k]])), r
512
- ),
513
- {}
514
- );
515
- if (Utils.isArrayOfObjects(_data))
516
- RETURN = combineObjectsToArray(
517
- (_data as Data[]).map((single_data) => CombineData(single_data))
518
- );
519
- else {
520
- for (const [key, value] of Object.entries(_data as Data)) {
521
- if (Utils.isObject(value))
522
- Object.assign(RETURN, CombineData(value, `${key}.`));
523
- else if (Utils.isArrayOfObjects(value)) {
524
- Object.assign(
525
- RETURN,
526
- CombineData(
527
- combineObjectsToArray(value),
528
- (prefix ?? "") + key + "."
529
- )
530
- );
531
- } else if (
532
- Utils.isArrayOfArrays(value) &&
533
- value.every(Utils.isArrayOfObjects)
534
- )
535
- Object.assign(
536
- RETURN,
537
- CombineData(
538
- combineObjectsToArray(value.map(combineObjectsToArray)),
539
- (prefix ?? "") + key + "."
540
- )
541
- );
542
- else
543
- RETURN[(prefix ?? "") + key] = File.encode(value) as
544
- | boolean
545
- | number
546
- | string
547
- | null;
548
- }
549
- }
550
- return RETURN;
551
- };
552
- const addPathToKeys = (obj: Record<string, any>, path: string) => {
553
- const newObject: Record<string, any> = {};
554
-
555
- for (const key in obj) newObject[join(path, key + ".inib")] = obj[key];
556
-
557
- return newObject;
558
- };
559
- return addPathToKeys(CombineData(data), mainPath);
560
- }
561
-
562
- public async get<O extends boolean = false, N extends boolean = false>(
563
- tableName: string,
564
- where?: string | number | (string | number)[] | Criteria,
565
- options: Options = {
566
- page: 1,
567
- per_page: 15,
568
- },
569
- onlyOne?: O,
570
- onlyLinesNumbers?: N
571
- ): Promise<
572
- (N extends true ? number[] : O extends true ? Data : Data[]) | null
573
- > {
574
- if (!options.columns) options.columns = [];
575
- else if (!Array.isArray(options.columns))
576
- options.columns = [options.columns];
577
- if (options.columns.length && !(options.columns as string[]).includes("id"))
578
- options.columns.push("id");
579
- if (!options.page) options.page = 1;
580
- if (!options.per_page) options.per_page = 15;
581
- let RETURN!: Data | Data[] | null;
582
- let schema = await this.getTableSchema(tableName);
583
- if (!schema) throw this.throwError("NO_SCHEMA", tableName);
584
- const idFilePath = join(this.folder, this.database, tableName, "id.inib");
585
- if (!(await File.isExists(idFilePath))) return null;
586
- const filterSchemaByColumns = (schema: Schema, columns: string[]): Schema =>
587
- schema
588
- .map((field) => {
589
- if (columns.some((column) => column.startsWith("!")))
590
- return columns.includes("!" + field.key) ? null : field;
591
- if (columns.includes(field.key) || columns.includes("*"))
592
- return field;
593
-
594
- if (
595
- (field.type === "array" || field.type === "object") &&
596
- Utils.isArrayOfObjects(field.children) &&
597
- columns.filter(
598
- (column) =>
599
- column.startsWith(field.key + ".") ||
600
- column.startsWith("!" + field.key + ".")
601
- ).length
602
- ) {
603
- field.children = filterSchemaByColumns(
604
- field.children as Schema,
605
- columns
606
- .filter(
607
- (column) =>
608
- column.startsWith(field.key + ".") ||
609
- column.startsWith("!" + field.key + ".")
610
- )
611
- .map((column) => column.replace(field.key + ".", ""))
612
- );
613
- return field;
614
- }
615
- return null;
616
- })
617
- .filter((i) => i) as Schema;
618
- if (options.columns.length)
619
- schema = filterSchemaByColumns(schema, options.columns);
620
-
621
- const getItemsFromSchema = async (
622
- path: string,
623
- schema: Schema,
624
- linesNumber: number[],
625
- prefix?: string
626
- ) => {
627
- let RETURN: Record<number, Data> = {};
628
- for (const field of schema) {
629
- if (
630
- (field.type === "array" ||
631
- (Array.isArray(field.type) &&
632
- (field.type as any).includes("array"))) &&
633
- (field as FieldDefault & (FieldArrayType | FieldArrayArrayType))
634
- .children
635
- ) {
636
- if (
637
- Utils.isArrayOfObjects(
638
- (field as FieldDefault & (FieldArrayType | FieldArrayArrayType))
639
- .children
640
- )
641
- ) {
642
- if (
643
- (
644
- (field as FieldDefault & (FieldArrayType | FieldArrayArrayType))
645
- .children as Schema
646
- ).filter(
647
- (children) =>
648
- children.type === "array" &&
649
- Utils.isArrayOfObjects(children.children)
650
- ).length
651
- ) {
652
- // one of children has array field type and has children array of object = Schema
653
- Object.entries(
654
- (await getItemsFromSchema(
655
- path,
656
- (
657
- (
658
- field as FieldDefault &
659
- (FieldArrayType | FieldArrayArrayType)
660
- ).children as Schema
661
- ).filter(
662
- (children) =>
663
- children.type === "array" &&
664
- Utils.isArrayOfObjects(children.children)
665
- ),
666
- linesNumber,
667
- (prefix ?? "") + field.key + "."
668
- )) ?? {}
669
- ).forEach(([index, item]) => {
670
- if (Utils.isObject(item)) {
671
- if (!RETURN[index]) RETURN[index] = {};
672
- if (!RETURN[index][field.key]) RETURN[index][field.key] = [];
673
- for (const child_field of (
674
- (
675
- field as FieldDefault &
676
- (FieldArrayType | FieldArrayArrayType)
677
- ).children as Schema
678
- ).filter(
679
- (children) =>
680
- children.type === "array" &&
681
- Utils.isArrayOfObjects(children.children)
682
- )) {
683
- if (Utils.isObject(item[child_field.key])) {
684
- Object.entries(item[child_field.key]).forEach(
685
- ([key, value]) => {
686
- for (let _i = 0; _i < value.length; _i++) {
687
- if (!RETURN[index][field.key][_i])
688
- RETURN[index][field.key][_i] = {};
689
- if (!RETURN[index][field.key][_i][child_field.key])
690
- RETURN[index][field.key][_i][child_field.key] =
691
- [];
692
- value[_i].forEach(
693
- (_element: any, _index: string | number) => {
694
- if (
695
- !RETURN[index][field.key][_i][
696
- child_field.key
697
- ][_index]
698
- )
699
- RETURN[index][field.key][_i][child_field.key][
700
- _index
701
- ] = {};
702
- RETURN[index][field.key][_i][child_field.key][
703
- _index
704
- ][key] = _element;
705
- }
706
- );
707
- }
708
- }
709
- );
710
- }
711
- }
712
- }
713
- });
714
- (
715
- field as FieldDefault & (FieldArrayType | FieldArrayArrayType)
716
- ).children = (
717
- (field as FieldDefault & (FieldArrayType | FieldArrayArrayType))
718
- .children as Schema
719
- ).filter(
720
- (children) =>
721
- children.type !== "array" ||
722
- !Utils.isArrayOfObjects(children.children)
723
- );
724
- }
725
-
726
- Object.entries(
727
- (await getItemsFromSchema(
728
- path,
729
- (field as FieldDefault & (FieldArrayType | FieldArrayArrayType))
730
- .children as Schema,
731
- linesNumber,
732
- (prefix ?? "") + field.key + "."
733
- )) ?? {}
734
- ).forEach(([index, item]) => {
735
- if (!RETURN[index]) RETURN[index] = {};
736
- if (Utils.isObject(item)) {
737
- if (!Object.values(item).every((i) => i === null)) {
738
- if (RETURN[index][field.key])
739
- Object.entries(item).forEach(([key, value], _index) => {
740
- RETURN[index][field.key] = RETURN[index][field.key].map(
741
- (_obj: any, _i: string | number) => ({
742
- ..._obj,
743
- [key]: value[_i],
744
- })
745
- );
746
- });
747
- else if (Object.values(item).every(Utils.isArrayOfArrays))
748
- RETURN[index][field.key] = item;
749
- else {
750
- RETURN[index][field.key] = [];
751
- Object.entries(item).forEach(([key, value]) => {
752
- for (let _i = 0; _i < value.length; _i++) {
753
- if (!RETURN[index][field.key][_i])
754
- RETURN[index][field.key][_i] = {};
755
- RETURN[index][field.key][_i][key] = value[_i];
756
- }
757
- });
758
- }
759
- } else RETURN[index][field.key] = null;
760
- } else RETURN[index][field.key] = item;
761
- });
762
- } else if (
763
- (field as FieldDefault & (FieldArrayType | FieldArrayArrayType))
764
- .children === "table" ||
765
- (Array.isArray(
766
- (field as FieldDefault & (FieldArrayType | FieldArrayArrayType))
767
- .children
768
- ) &&
769
- (
770
- (field as FieldDefault & (FieldArrayType | FieldArrayArrayType))
771
- .children as FieldType[]
772
- ).includes("table"))
773
- ) {
774
- if (options.columns)
775
- options.columns = (options.columns as string[])
776
- .filter((column) => column.includes(`${field.key}.`))
777
- .map((column) => column.replace(`${field.key}.`, ""));
778
- const [items, total_lines] = await File.get(
779
- join(path, (prefix ?? "") + field.key + ".inib"),
780
- linesNumber,
781
- field.type,
782
- (field as FieldDefault & (FieldArrayType | FieldArrayArrayType))
783
- .children as FieldType | FieldType[],
784
- this.salt
785
- );
786
-
787
- this.totalItems[tableName + "-" + field.key] = total_lines;
788
- for (const [index, item] of Object.entries(items)) {
789
- if (!RETURN[index]) RETURN[index] = {};
790
- RETURN[index][field.key] = item
791
- ? await this.get(field.key, item as number, options)
792
- : this.getDefaultValue(field);
793
- }
794
- } else if (
795
- await File.isExists(
796
- join(path, (prefix ?? "") + field.key + ".inib")
797
- )
798
- ) {
799
- const [items, total_lines] = await File.get(
800
- join(path, (prefix ?? "") + field.key + ".inib"),
801
- linesNumber,
802
- field.type,
803
- (field as any)?.children,
804
- this.salt
805
- );
806
-
807
- this.totalItems[tableName + "-" + field.key] = total_lines;
808
- for (const [index, item] of Object.entries(items)) {
809
- if (!RETURN[index]) RETURN[index] = {};
810
- RETURN[index][field.key] = item ?? this.getDefaultValue(field);
811
- }
812
- }
813
- } else if (field.type === "object") {
814
- for (const [index, item] of Object.entries(
815
- (await getItemsFromSchema(
816
- path,
817
- field.children as Schema,
818
- linesNumber,
819
- (prefix ?? "") + field.key + "."
820
- )) ?? {}
821
- )) {
822
- if (!RETURN[index]) RETURN[index] = {};
823
- if (Utils.isObject(item)) {
824
- if (!Object.values(item).every((i) => i === null))
825
- RETURN[index][field.key] = item;
826
- else RETURN[index][field.key] = null;
827
- } else RETURN[index][field.key] = null;
828
- }
829
- } else if (field.type === "table") {
830
- if (
831
- (await File.isExists(
832
- join(this.folder, this.database, field.key)
833
- )) &&
834
- (await File.isExists(
835
- join(path, (prefix ?? "") + field.key + ".inib")
836
- ))
837
- ) {
838
- if (options.columns)
839
- options.columns = (options.columns as string[])
840
- .filter(
841
- (column) =>
842
- column.includes(`${field.key}.`) &&
843
- !column.includes(`${field.key}.`)
844
- )
845
- .map((column) => column.replace(`${field.key}.`, ""));
846
- const [items, total_lines] = await File.get(
847
- join(path, (prefix ?? "") + field.key + ".inib"),
848
- linesNumber,
849
- "number",
850
- undefined,
851
- this.salt
852
- );
853
- this.totalItems[tableName + "-" + field.key] = total_lines;
854
- for (const [index, item] of Object.entries(items)) {
855
- if (!RETURN[index]) RETURN[index] = {};
856
- RETURN[index][field.key] = item
857
- ? await this.get(field.key, item as number, options)
858
- : this.getDefaultValue(field);
859
- }
860
- }
861
- } else if (
862
- await File.isExists(join(path, (prefix ?? "") + field.key + ".inib"))
863
- ) {
864
- const [items, total_lines] = await File.get(
865
- join(path, (prefix ?? "") + field.key + ".inib"),
866
- linesNumber,
867
- field.type,
868
- (field as any)?.children,
869
- this.salt
870
- );
871
-
872
- this.totalItems[tableName + "-" + field.key] = total_lines;
873
- for (const [index, item] of Object.entries(items)) {
874
- if (!RETURN[index]) RETURN[index] = {};
875
- RETURN[index][field.key] = item ?? this.getDefaultValue(field);
876
- }
877
- }
878
- }
879
- return RETURN;
880
- };
881
- if (!where) {
882
- // Display all data
883
- RETURN = Object.values(
884
- await getItemsFromSchema(
885
- join(this.folder, this.database, tableName),
886
- schema,
887
- Array.from(
888
- { length: options.per_page },
889
- (_, index) =>
890
- ((options.page as number) - 1) * (options.per_page as number) +
891
- index +
892
- 1
893
- )
894
- )
895
- );
896
- } else if (
897
- (Array.isArray(where) &&
898
- (where.every(Utils.isValidID) || where.every(Utils.isNumber))) ||
899
- Utils.isValidID(where) ||
900
- Utils.isNumber(where)
901
- ) {
902
- let Ids = where as string | number | (string | number)[];
903
- if (!Array.isArray(Ids)) Ids = [Ids];
904
- const [lineNumbers, countItems] = await File.search(
905
- idFilePath,
906
- "[]",
907
- Ids.map((id) =>
908
- Utils.isNumber(id) ? Number(id) : Utils.decodeID(id, this.salt)
909
- ),
910
- undefined,
911
- "number",
912
- undefined,
913
- Ids.length,
914
- 0,
915
- false,
916
- this.salt
917
- );
918
- if (!lineNumbers || !Object.keys(lineNumbers).length)
919
- throw this.throwError(
920
- "INVALID_ID",
921
- where as number | string | (number | string)[]
922
- );
923
-
924
- if (onlyLinesNumbers) return Object.keys(lineNumbers).map(Number) as any;
925
-
926
- RETURN = Object.values(
927
- (await getItemsFromSchema(
928
- join(this.folder, this.database, tableName),
929
- schema,
930
- Object.keys(lineNumbers).map(Number)
931
- )) ?? {}
932
- );
933
- if (RETURN.length && !Array.isArray(where)) RETURN = RETURN[0];
934
- } else if (Utils.isObject(where)) {
935
- // Criteria
936
- const FormatObjectCriteriaValue = (
937
- value: string,
938
- isParentArray: boolean = false
939
- ): [ComparisonOperator, string | number | boolean | null] => {
940
- switch (value[0]) {
941
- case ">":
942
- case "<":
943
- case "[":
944
- return ["=", "]", "*"].includes(value[1])
945
- ? [
946
- value.slice(0, 2) as ComparisonOperator,
947
- value.slice(2) as string | number,
948
- ]
949
- : [
950
- value.slice(0, 1) as ComparisonOperator,
951
- value.slice(1) as string | number,
952
- ];
953
- case "!":
954
- return ["=", "*"].includes(value[1])
955
- ? [
956
- value.slice(0, 2) as ComparisonOperator,
957
- value.slice(2) as string | number,
958
- ]
959
- : value[1] === "["
960
- ? [
961
- value.slice(0, 3) as ComparisonOperator,
962
- value.slice(3) as string | number,
963
- ]
964
- : [
965
- (value.slice(0, 1) + "=") as ComparisonOperator,
966
- value.slice(1) as string | number,
967
- ];
968
- case "=":
969
- return isParentArray
970
- ? [
971
- value.slice(0, 1) as ComparisonOperator,
972
- value.slice(1) as string | number,
973
- ]
974
- : [
975
- value.slice(0, 1) as ComparisonOperator,
976
- (value.slice(1) + ",") as string,
977
- ];
978
- case "*":
979
- return [
980
- value.slice(0, 1) as ComparisonOperator,
981
- value.slice(1) as string | number,
982
- ];
983
- default:
984
- return ["=", value];
985
- }
986
- };
987
-
988
- const applyCriteria = async (
989
- criteria?: Criteria,
990
- allTrue?: boolean
991
- ): Promise<Record<number, Data> | null> => {
992
- let RETURN: Record<number, Data> = {};
993
- if (!criteria) return null;
994
- if (criteria.and && Utils.isObject(criteria.and)) {
995
- const searchResult = await applyCriteria(
996
- criteria.and as Criteria,
997
- true
998
- );
999
- if (searchResult) {
1000
- RETURN = Utils.deepMerge(
1001
- RETURN,
1002
- Object.fromEntries(
1003
- Object.entries(searchResult).filter(
1004
- ([_k, v], _i) =>
1005
- Object.keys(v).length ===
1006
- Object.keys(criteria.and ?? {}).length
1007
- )
1008
- )
1009
- );
1010
- delete criteria.and;
1011
- } else return null;
1012
- }
1013
-
1014
- if (criteria.or && Utils.isObject(criteria.or)) {
1015
- const searchResult = await applyCriteria(
1016
- criteria.or as Criteria,
1017
- false
1018
- );
1019
- delete criteria.or;
1020
- if (searchResult) RETURN = Utils.deepMerge(RETURN, searchResult);
1021
- }
1022
-
1023
- if (Object.keys(criteria).length > 0) {
1024
- if (allTrue === undefined) allTrue = true;
1025
-
1026
- let index = -1;
1027
- for (const [key, value] of Object.entries(criteria)) {
1028
- const field = this.getField(key, schema as Schema) as Field;
1029
- index++;
1030
- let searchOperator:
1031
- | ComparisonOperator
1032
- | ComparisonOperator[]
1033
- | undefined = undefined,
1034
- searchComparedAtValue:
1035
- | string
1036
- | number
1037
- | boolean
1038
- | null
1039
- | (string | number | boolean | null)[]
1040
- | undefined = undefined,
1041
- searchLogicalOperator: "and" | "or" | undefined = undefined;
1042
- if (Utils.isObject(value)) {
1043
- if (
1044
- (value as Criteria)?.or &&
1045
- Array.isArray((value as Criteria).or)
1046
- ) {
1047
- const searchCriteria = (
1048
- (value as Criteria).or as (string | number | boolean)[]
1049
- )
1050
- .map(
1051
- (
1052
- single_or
1053
- ): [ComparisonOperator, string | number | boolean | null] =>
1054
- typeof single_or === "string"
1055
- ? FormatObjectCriteriaValue(single_or)
1056
- : ["=", single_or]
1057
- )
1058
- .filter((a) => a) as [ComparisonOperator, string | number][];
1059
- if (searchCriteria.length > 0) {
1060
- searchOperator = searchCriteria.map(
1061
- (single_or) => single_or[0]
1062
- );
1063
- searchComparedAtValue = searchCriteria.map(
1064
- (single_or) => single_or[1]
1065
- );
1066
- searchLogicalOperator = "or";
1067
- }
1068
- delete (value as Criteria).or;
1069
- }
1070
- if (
1071
- (value as Criteria)?.and &&
1072
- Array.isArray((value as Criteria).and)
1073
- ) {
1074
- const searchCriteria = (
1075
- (value as Criteria).and as (string | number | boolean)[]
1076
- )
1077
- .map(
1078
- (
1079
- single_and
1080
- ): [ComparisonOperator, string | number | boolean | null] =>
1081
- typeof single_and === "string"
1082
- ? FormatObjectCriteriaValue(single_and)
1083
- : ["=", single_and]
1084
- )
1085
- .filter((a) => a) as [ComparisonOperator, string | number][];
1086
- if (searchCriteria.length > 0) {
1087
- searchOperator = searchCriteria.map(
1088
- (single_and) => single_and[0]
1089
- );
1090
- searchComparedAtValue = searchCriteria.map(
1091
- (single_and) => single_and[1]
1092
- );
1093
- searchLogicalOperator = "and";
1094
- }
1095
- delete (value as Criteria).and;
1096
- }
1097
- } else if (Array.isArray(value)) {
1098
- const searchCriteria = value
1099
- .map(
1100
- (
1101
- single
1102
- ): [ComparisonOperator, string | number | boolean | null] =>
1103
- typeof single === "string"
1104
- ? FormatObjectCriteriaValue(single)
1105
- : ["=", single]
1106
- )
1107
- .filter((a) => a) as [ComparisonOperator, string | number][];
1108
- if (searchCriteria.length > 0) {
1109
- searchOperator = searchCriteria.map((single) => single[0]);
1110
- searchComparedAtValue = searchCriteria.map(
1111
- (single) => single[1]
1112
- );
1113
- searchLogicalOperator = "and";
1114
- }
1115
- } else if (typeof value === "string") {
1116
- const ComparisonOperatorValue = FormatObjectCriteriaValue(value);
1117
- if (ComparisonOperatorValue) {
1118
- searchOperator = ComparisonOperatorValue[0];
1119
- searchComparedAtValue = ComparisonOperatorValue[1];
1120
- }
1121
- } else {
1122
- searchOperator = "=";
1123
- searchComparedAtValue = value as number | boolean;
1124
- }
1125
- const [searchResult, total_lines] = await File.search(
1126
- join(this.folder, this.database, tableName, key + ".inib"),
1127
- searchOperator,
1128
- searchComparedAtValue,
1129
- searchLogicalOperator,
1130
- field?.type,
1131
- (field as any)?.children,
1132
- options.per_page,
1133
- (options.page as number) - 1 * (options.per_page as number) + 1,
1134
- true,
1135
- this.salt
1136
- );
1137
- if (searchResult) {
1138
- RETURN = Utils.deepMerge(RETURN, searchResult);
1139
- this.totalItems[tableName + "-" + key] = total_lines;
1140
- }
1141
-
1142
- if (allTrue && index > 0) {
1143
- if (!Object.keys(RETURN).length) RETURN = {};
1144
- RETURN = Object.fromEntries(
1145
- Object.entries(RETURN).filter(
1146
- ([_index, item]) => Object.keys(item).length > index
1147
- )
1148
- );
1149
- if (!Object.keys(RETURN).length) RETURN = {};
1150
- }
1151
- }
1152
- }
1153
-
1154
- return Object.keys(RETURN).length ? RETURN : null;
1155
- };
1156
-
1157
- RETURN = await applyCriteria(where as Criteria);
1158
- if (RETURN) {
1159
- if (onlyLinesNumbers) return Object.keys(RETURN).map(Number) as any;
1160
- const alreadyExistsColumns = Object.keys(Object.values(RETURN)[0]).map(
1161
- (key) => parse(key).name
1162
- );
1163
- RETURN = Object.values(
1164
- Utils.deepMerge(
1165
- RETURN,
1166
- await getItemsFromSchema(
1167
- join(this.folder, this.database, tableName),
1168
- schema.filter(
1169
- (field) => !alreadyExistsColumns.includes(field.key)
1170
- ),
1171
- Object.keys(RETURN).map(Number)
1172
- )
1173
- )
1174
- );
1175
- }
1176
- }
1177
- if (
1178
- !RETURN ||
1179
- (Utils.isObject(RETURN) && !Object.keys(RETURN).length) ||
1180
- (Array.isArray(RETURN) && !RETURN.length)
1181
- )
1182
- return null;
1183
-
1184
- const greatestTotalItems = Math.max(
1185
- ...Object.entries(this.totalItems)
1186
- .filter(([k]) => k.startsWith(tableName + "-"))
1187
- .map(([, v]) => v)
1188
- );
1189
- this.pageInfo = {
1190
- ...(({ columns, ...restOfOptions }) => restOfOptions)(options),
1191
- total_pages: Math.ceil(greatestTotalItems / options.per_page),
1192
- total: greatestTotalItems,
1193
- };
1194
- return onlyOne ? RETURN[0] : RETURN;
1195
- }
1196
-
1197
- public async post<DataType extends Data | Data[]>(
1198
- tableName: string,
1199
- data: DataType,
1200
- options: Options = {
1201
- page: 1,
1202
- per_page: 15,
1203
- },
1204
- returnPostedData: boolean = true
1205
- ): Promise<
1206
- DataType extends Data ? Data | null | void : Data[] | null | void
1207
- > {
1208
- const schema = await this.getTableSchema(tableName);
1209
- let RETURN: Data | Data[] | null | undefined;
1210
- if (!schema) throw this.throwError("NO_SCHEMA", tableName);
1211
- const idFilePath = join(this.folder, this.database, tableName, "id.inib");
1212
-
1213
- let [last_line_number, last_id] = (await File.isExists(idFilePath))
1214
- ? (Object.entries(
1215
- (await File.get(idFilePath, -1, "number", undefined, this.salt))[0]
1216
- )[0] as [number, number] | undefined) ?? [1, 0]
1217
- : [1, 0];
1218
- if (Utils.isArrayOfObjects(data))
1219
- data.forEach((single_data: any, index: string | number) => {
1220
- if (!RETURN) RETURN = [];
1221
- RETURN[index] = (({ id, updatedAt, createdAt, ...rest }) => ({
1222
- id: ++last_id,
1223
- ...rest,
1224
- createdAt: new Date(),
1225
- }))(single_data);
1226
- });
1227
- else
1228
- RETURN = (({ id, updatedAt, createdAt, ...rest }) => ({
1229
- id: ++last_id,
1230
- ...rest,
1231
- createdAt: new Date(),
1232
- }))(data as Data);
1233
- if (!RETURN) throw this.throwError("NO_DATA");
1234
-
1235
- RETURN = this.formatData(RETURN, schema);
1236
- const pathesContents = this.joinPathesContents(
1237
- join(this.folder, this.database, tableName),
1238
- RETURN
1239
- );
1240
- for await (const [path, content] of Object.entries(pathesContents))
1241
- await File.replace(path, { [last_line_number]: content });
1242
-
1243
- if (returnPostedData)
1244
- return this.get(
1245
- tableName,
1246
- Utils.isArrayOfObjects(RETURN)
1247
- ? RETURN.map((data: Data) => data.id)
1248
- : ((RETURN as Data).id as number),
1249
- options,
1250
- !Utils.isArrayOfObjects(data) // return only one item if data is not array of objects
1251
- );
1252
- }
1253
-
1254
- public async put<returnPostedDataType extends boolean = true>(
1255
- tableName: string,
1256
- data: Data | Data[],
1257
- where?: number | string | (number | string)[] | Criteria,
1258
- options: Options = {
1259
- page: 1,
1260
- per_page: 15,
1261
- },
1262
- returnPostedData?: returnPostedDataType
1263
- ): Promise<
1264
- (returnPostedDataType extends true ? Data | Data[] : void) | null
1265
- > {
1266
- const schema = await this.getTableSchema(tableName);
1267
- if (!schema) throw this.throwError("NO_SCHEMA", tableName);
1268
- const idFilePath = join(this.folder, this.database, tableName, "id.inib");
1269
- if (!(await File.isExists(idFilePath)))
1270
- throw this.throwError("NO_ITEMS", tableName);
1271
- data = this.formatData(data, schema, true);
1272
- if (!where) {
1273
- if (Utils.isArrayOfObjects(data)) {
1274
- if (
1275
- !data.every(
1276
- (item: any) => item.hasOwnProperty("id") && Utils.isValidID(item.id)
1277
- )
1278
- )
1279
- throw this.throwError("INVALID_ID");
1280
- return this.put(
1281
- tableName,
1282
- data,
1283
- data.map((item: { id: any }) => item.id)
1284
- );
1285
- } else if (data.hasOwnProperty("id")) {
1286
- if (!Utils.isValidID((data as Data).id))
1287
- throw this.throwError("INVALID_ID", (data as Data).id);
1288
- return this.put(
1289
- tableName,
1290
- data,
1291
- Utils.decodeID((data as Data).id as string, this.salt)
1292
- );
1293
- } else {
1294
- const pathesContents = this.joinPathesContents(
1295
- join(this.folder, this.database, tableName),
1296
- Utils.isArrayOfObjects(data)
1297
- ? data.map((item: any) => ({
1298
- ...(({ id, ...restOfData }) => restOfData)(item),
1299
- updatedAt: new Date(),
1300
- }))
1301
- : {
1302
- ...(({ id, ...restOfData }) => restOfData)(data as Data),
1303
- updatedAt: new Date(),
1304
- }
1305
- );
1306
- for await (const [path, content] of Object.entries(pathesContents))
1307
- await File.replace(path, content);
1308
-
1309
- if (returnPostedData) return this.get(tableName, where, options) as any;
1310
- }
1311
- } else if (
1312
- (Array.isArray(where) &&
1313
- (where.every(Utils.isValidID) || where.every(Utils.isNumber))) ||
1314
- Utils.isValidID(where) ||
1315
- Utils.isNumber(where)
1316
- ) {
1317
- if (
1318
- (Array.isArray(where) && where.every(Utils.isValidID)) ||
1319
- Utils.isValidID(where)
1320
- ) {
1321
- const lineNumbers = await this.get(
1322
- tableName,
1323
- where,
1324
- undefined,
1325
- undefined,
1326
- true
1327
- );
1328
- return this.put(tableName, data, lineNumbers);
1329
- } else if (
1330
- (Array.isArray(where) && where.every(Utils.isNumber)) ||
1331
- Utils.isNumber(where)
1332
- ) {
1333
- // "where" in this case, is the line(s) number(s) and not id(s)
1334
- const pathesContents = Object.fromEntries(
1335
- Object.entries(
1336
- this.joinPathesContents(
1337
- join(this.folder, this.database, tableName),
1338
- Utils.isArrayOfObjects(data)
1339
- ? data.map((item: any) => ({
1340
- ...item,
1341
- updatedAt: new Date(),
1342
- }))
1343
- : { ...data, updatedAt: new Date() }
1344
- )
1345
- ).map(([key, value]) => [
1346
- key,
1347
- ([...(Array.isArray(where) ? where : [where])] as number[]).reduce(
1348
- (obj, key, index) => ({
1349
- ...obj,
1350
- [key]: Array.isArray(value) ? value[index] : value,
1351
- }),
1352
- {}
1353
- ),
1354
- ])
1355
- );
1356
- for await (const [path, content] of Object.entries(pathesContents))
1357
- await File.replace(path, content);
1358
-
1359
- if (returnPostedData)
1360
- return this.get(
1361
- tableName,
1362
- where,
1363
- options,
1364
- !Array.isArray(where)
1365
- ) as any;
1366
- }
1367
- } else if (typeof where === "object") {
1368
- const lineNumbers = this.get(
1369
- tableName,
1370
- where,
1371
- undefined,
1372
- undefined,
1373
- true
1374
- );
1375
- if (!lineNumbers || !Array.isArray(lineNumbers) || !lineNumbers.length)
1376
- throw this.throwError("NO_ITEMS", tableName);
1377
- return this.put(tableName, data, lineNumbers);
1378
- } else throw this.throwError("INVALID_PARAMETERS", tableName);
1379
- }
1380
-
1381
- public async delete(
1382
- tableName: string,
1383
- where?: number | string | (number | string)[] | Criteria,
1384
- _id?: string | string[]
1385
- ): Promise<string | string[] | null> {
1386
- const schema = await this.getTableSchema(tableName);
1387
- if (!schema) throw this.throwError("NO_SCHEMA", tableName);
1388
- const idFilePath = join(this.folder, this.database, tableName, "id.inib");
1389
- if (!(await File.isExists(idFilePath)))
1390
- throw this.throwError("NO_ITEMS", tableName);
1391
- if (!where) {
1392
- const files = await readdir(join(this.folder, this.database, tableName));
1393
- if (files.length) {
1394
- for (const file in files.filter(
1395
- (fileName: string) => fileName !== "schema.json"
1396
- ))
1397
- await unlink(join(this.folder, this.database, tableName, file));
1398
- }
1399
- return "*";
1400
- } else if (
1401
- (Array.isArray(where) &&
1402
- (where.every(Utils.isValidID) || where.every(Utils.isNumber))) ||
1403
- Utils.isValidID(where) ||
1404
- Utils.isNumber(where)
1405
- ) {
1406
- if (
1407
- (Array.isArray(where) && where.every(Utils.isValidID)) ||
1408
- Utils.isValidID(where)
1409
- ) {
1410
- const lineNumbers = await this.get(
1411
- tableName,
1412
- where,
1413
- undefined,
1414
- undefined,
1415
- true
1416
- );
1417
- return this.delete(tableName, lineNumbers, where as string | string[]);
1418
- } else if (
1419
- (Array.isArray(where) && where.every(Utils.isNumber)) ||
1420
- Utils.isNumber(where)
1421
- ) {
1422
- // "where" in this case, is the line(s) number(s) and not id(s)
1423
- const files = await readdir(
1424
- join(this.folder, this.database, tableName)
1425
- );
1426
- if (files.length) {
1427
- if (!_id)
1428
- _id = Object.values(
1429
- (
1430
- await File.get(
1431
- join(this.folder, this.database, tableName, "id.inib"),
1432
- where,
1433
- "number",
1434
- undefined,
1435
- this.salt
1436
- )
1437
- )[0]
1438
- ).map((id) => Utils.encodeID(Number(id), this.salt));
1439
- for (const file of files.filter((fileName: string) =>
1440
- fileName.endsWith(".inib")
1441
- ))
1442
- await File.remove(
1443
- join(this.folder, this.database, tableName, file),
1444
- where as number | number[]
1445
- );
1446
- return Array.isArray(_id) && _id.length === 1 ? _id[0] : _id;
1447
- }
1448
- }
1449
- } else if (typeof where === "object" && !Array.isArray(where)) {
1450
- const lineNumbers = this.get(
1451
- tableName,
1452
- where,
1453
- undefined,
1454
- undefined,
1455
- true
1456
- );
1457
- if (!lineNumbers || !Array.isArray(lineNumbers) || !lineNumbers.length)
1458
- throw this.throwError("NO_ITEMS", tableName);
1459
- return this.delete(tableName, lineNumbers);
1460
- } else throw this.throwError("INVALID_PARAMETERS", tableName);
1461
- return null;
1462
- }
1463
-
1464
- public async sum(
1465
- tableName: string,
1466
- columns: string,
1467
- where?: number | string | (number | string)[] | Criteria
1468
- ): Promise<number>;
1469
- public async sum(
1470
- tableName: string,
1471
- columns: string[],
1472
- where?: number | string | (number | string)[] | Criteria
1473
- ): Promise<Record<string, number>>;
1474
- public async sum(
1475
- tableName: string,
1476
- columns: string | string[],
1477
- where?: number | string | (number | string)[] | Criteria
1478
- ): Promise<number | Record<string, number>> {
1479
- let RETURN: Record<string, number>;
1480
- const schema = await this.getTableSchema(tableName);
1481
- if (!schema) throw this.throwError("NO_SCHEMA", tableName);
1482
- if (
1483
- !(await File.isExists(
1484
- join(this.folder, this.database, tableName, "id.inib")
1485
- ))
1486
- )
1487
- throw this.throwError("NO_ITEMS", tableName);
1488
- if (!Array.isArray(columns)) columns = [columns];
1489
- for await (const column of columns) {
1490
- const columnPath = join(
1491
- this.folder,
1492
- this.database,
1493
- tableName,
1494
- column + ".inib"
1495
- );
1496
- if (await File.isExists(columnPath)) {
1497
- if (where) {
1498
- const lineNumbers = await this.get(
1499
- tableName,
1500
- where,
1501
- undefined,
1502
- undefined,
1503
- true
1504
- );
1505
- RETURN[column] = await File.sum(columnPath, lineNumbers);
1506
- } else RETURN[column] = await File.sum(columnPath);
1507
- }
1508
- }
1509
- return Array.isArray(columns) ? RETURN : Object.values(RETURN)[0];
1510
- }
1511
-
1512
- public async max(
1513
- tableName: string,
1514
- columns: string | string[],
1515
- where?: number | string | (number | string)[] | Criteria
1516
- ) {
1517
- let RETURN: Record<string, number>;
1518
- const schema = await this.getTableSchema(tableName);
1519
- if (!schema) throw this.throwError("NO_SCHEMA", tableName);
1520
- if (
1521
- !(await File.isExists(
1522
- join(this.folder, this.database, tableName, "id.inib")
1523
- ))
1524
- )
1525
- throw this.throwError("NO_ITEMS", tableName);
1526
- if (!Array.isArray(columns)) columns = [columns];
1527
- for await (const column of columns) {
1528
- const columnPath = join(
1529
- this.folder,
1530
- this.database,
1531
- tableName,
1532
- column + ".inib"
1533
- );
1534
- if (await File.isExists(columnPath)) {
1535
- if (where) {
1536
- const lineNumbers = await this.get(
1537
- tableName,
1538
- where,
1539
- undefined,
1540
- undefined,
1541
- true
1542
- );
1543
- RETURN[column] = await File.max(columnPath, lineNumbers);
1544
- } else RETURN[column] = await File.max(columnPath);
1545
- }
1546
- }
1547
- return RETURN;
1548
- }
1549
-
1550
- public async min(
1551
- tableName: string,
1552
- columns: string | string[],
1553
- where?: number | string | (number | string)[] | Criteria
1554
- ) {
1555
- let RETURN: Record<string, number>;
1556
- const schema = await this.getTableSchema(tableName);
1557
- if (!schema) throw this.throwError("NO_SCHEMA", tableName);
1558
- if (
1559
- !(await File.isExists(
1560
- join(this.folder, this.database, tableName, "id.inib")
1561
- ))
1562
- )
1563
- throw this.throwError("NO_ITEMS", tableName);
1564
- if (!Array.isArray(columns)) columns = [columns];
1565
- for await (const column of columns) {
1566
- const columnPath = join(
1567
- this.folder,
1568
- this.database,
1569
- tableName,
1570
- column + ".inib"
1571
- );
1572
- if (await File.isExists(columnPath)) {
1573
- if (where) {
1574
- const lineNumbers = await this.get(
1575
- tableName,
1576
- where,
1577
- undefined,
1578
- undefined,
1579
- true
1580
- );
1581
- RETURN[column] = await File.min(columnPath, lineNumbers);
1582
- } else RETURN[column] = await File.min(columnPath);
1583
- }
1584
- }
1585
- return RETURN;
1586
- }
1587
- }