inibase 1.0.0-rc.6 → 1.0.0-rc.61

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