inibase 1.0.0-rc.101 → 1.0.0-rc.102

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/dist/file.d.ts CHANGED
@@ -58,7 +58,7 @@ export declare function get(filePath: string, lineNumbers: undefined | number |
58
58
  *
59
59
  * Note: If the file doesn't exist and replacements is an object, it creates a new file with the specified replacements.
60
60
  */
61
- export declare const replace: (filePath: string, replacements: string | number | boolean | null | (string | number | boolean | null)[] | Record<number, string | boolean | number | null | (string | boolean | number | null)[]>) => Promise<string[]>;
61
+ export declare const replace: (filePath: string, replacements: string | number | boolean | null | (string | number | boolean | null)[] | Record<number, string | boolean | number | null | (string | boolean | number | null)[]>, totalItems?: number) => Promise<string[]>;
62
62
  /**
63
63
  * Asynchronously appends data to the end of a file.
64
64
  *
package/dist/file.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { createWriteStream } from "node:fs";
1
2
  import { access, appendFile, copyFile, constants as fsConstants, open, readFile, unlink, writeFile, } from "node:fs/promises";
2
3
  import { join } from "node:path";
3
4
  import { createInterface } from "node:readline";
@@ -7,6 +8,7 @@ import { createGunzip, createGzip } from "node:zlib";
7
8
  import Inison from "inison";
8
9
  import { detectFieldType, isArrayOfObjects, isStringified, isNumber, isObject, } from "./utils.js";
9
10
  import { compare, encodeID, exec, gunzip, gzip } from "./utils.server.js";
11
+ import { spawn } from "node:child_process";
10
12
  export const lock = async (folderPath, prefix) => {
11
13
  let lockFile = null;
12
14
  const lockFilePath = join(folderPath, `${prefix ?? ""}.locked`);
@@ -239,48 +241,93 @@ export async function get(filePath, lineNumbers, fieldType, fieldChildrenType, s
239
241
  *
240
242
  * Note: If the file doesn't exist and replacements is an object, it creates a new file with the specified replacements.
241
243
  */
242
- export const replace = async (filePath, replacements) => {
244
+ export const replace = async (filePath, replacements, totalItems) => {
243
245
  const fileTempPath = filePath.replace(/([^/]+)\/?$/, ".tmp/$1");
246
+ const isReplacementsObject = isObject(replacements);
247
+ const isReplacementsObjectHasLineNumbers = isReplacementsObject && !Number.isNaN(Number(Object.keys(replacements)[0]));
244
248
  if (await isExists(filePath)) {
245
- let fileHandle = null;
246
- let fileTempHandle = null;
249
+ if (isReplacementsObjectHasLineNumbers) {
250
+ let fileHandle = null;
251
+ let fileTempHandle = null;
252
+ try {
253
+ let linesCount = 0;
254
+ fileHandle = await open(filePath, "r");
255
+ fileTempHandle = await open(fileTempPath, "w");
256
+ const rl = createReadLineInternface(filePath, fileHandle);
257
+ await _pipeline(filePath, rl, fileTempHandle.createWriteStream(), new Transform({
258
+ transform(line, _, callback) {
259
+ linesCount++;
260
+ const replacement = isReplacementsObject
261
+ ? Object.hasOwn(replacements, linesCount)
262
+ ? replacements[linesCount]
263
+ : line
264
+ : replacements;
265
+ return callback(null, `${replacement}\n`);
266
+ },
267
+ }));
268
+ return [fileTempPath, filePath];
269
+ }
270
+ catch {
271
+ return [fileTempPath, null];
272
+ }
273
+ finally {
274
+ // Ensure that file handles are closed, even if an error occurred
275
+ await fileHandle?.close();
276
+ await fileTempHandle?.close();
277
+ }
278
+ }
279
+ else {
280
+ return new Promise((resolve) => {
281
+ const sedProcess = spawn("sed", [
282
+ "-e",
283
+ `s/.*/${replacements}/`,
284
+ "-e",
285
+ `/^$/s/^/${replacements}/`,
286
+ filePath,
287
+ ]);
288
+ const outputStream = createWriteStream(fileTempPath); // Temp file for output
289
+ // Pipe sed output to the temporary file
290
+ sedProcess.stdout.pipe(outputStream);
291
+ // Handle the process close
292
+ sedProcess.on("close", (code) => {
293
+ if (code === 0)
294
+ resolve([fileTempPath, filePath]);
295
+ else
296
+ resolve([fileTempPath, null]);
297
+ });
298
+ // Handle errors in spawning the sed process
299
+ sedProcess.on("error", () => {
300
+ resolve([fileTempPath, null]);
301
+ });
302
+ });
303
+ }
304
+ }
305
+ else if (isReplacementsObject) {
247
306
  try {
248
- let linesCount = 0;
249
- fileHandle = await open(filePath, "r");
250
- fileTempHandle = await open(fileTempPath, "w");
251
- const rl = createReadLineInternface(filePath, fileHandle);
252
- await _pipeline(filePath, rl, fileTempHandle.createWriteStream(), new Transform({
253
- transform(line, _, callback) {
254
- linesCount++;
255
- const replacement = isObject(replacements)
256
- ? Object.hasOwn(replacements, linesCount)
257
- ? replacements[linesCount]
258
- : line
259
- : replacements;
260
- return callback(null, `${replacement}\n`);
261
- },
262
- }));
307
+ if (isReplacementsObjectHasLineNumbers) {
308
+ const replacementsKeys = Object.keys(replacements)
309
+ .map(Number)
310
+ .toSorted((a, b) => a - b);
311
+ await write(fileTempPath, `${"\n".repeat(replacementsKeys[0] - 1) +
312
+ replacementsKeys
313
+ .map((lineNumber, index) => index === 0 ||
314
+ lineNumber - replacementsKeys[index - 1] - 1 === 0
315
+ ? replacements[lineNumber]
316
+ : "\n".repeat(lineNumber - replacementsKeys[index - 1] - 1) +
317
+ replacements[lineNumber])
318
+ .join("\n")}\n`);
319
+ }
320
+ else {
321
+ if (!totalItems)
322
+ throw new Error("INVALID_PARAMETERS");
323
+ await write(fileTempPath, `${`${replacements}\n`.repeat(totalItems)}\n`);
324
+ }
263
325
  return [fileTempPath, filePath];
264
326
  }
265
- finally {
266
- // Ensure that file handles are closed, even if an error occurred
267
- await fileHandle?.close();
268
- await fileTempHandle?.close();
327
+ catch {
328
+ return [fileTempPath, null];
269
329
  }
270
330
  }
271
- else if (isObject(replacements)) {
272
- const replacementsKeys = Object.keys(replacements)
273
- .map(Number)
274
- .toSorted((a, b) => a - b);
275
- await write(fileTempPath, `${"\n".repeat(replacementsKeys[0] - 1) +
276
- replacementsKeys
277
- .map((lineNumber, index) => index === 0 || lineNumber - replacementsKeys[index - 1] - 1 === 0
278
- ? replacements[lineNumber]
279
- : "\n".repeat(lineNumber - replacementsKeys[index - 1] - 1) +
280
- replacements[lineNumber])
281
- .join("\n")}\n`);
282
- return [fileTempPath, filePath];
283
- }
284
331
  return [];
285
332
  };
286
333
  /**
@@ -293,20 +340,25 @@ export const replace = async (filePath, replacements) => {
293
340
  */
294
341
  export const append = async (filePath, data) => {
295
342
  const fileTempPath = filePath.replace(/([^/]+)\/?$/, ".tmp/$1");
296
- if (await isExists(filePath)) {
297
- await copyFile(filePath, fileTempPath);
298
- if (!filePath.endsWith(".gz")) {
299
- await appendFile(fileTempPath, `${Array.isArray(data) ? data.join("\n") : data}\n`);
300
- }
301
- else {
302
- await exec(`echo $'${(Array.isArray(data) ? data.join("\n") : data)
303
- .toString()
304
- .replace(/'/g, "\\'")}' | gzip - >> ${fileTempPath}`);
343
+ try {
344
+ if (await isExists(filePath)) {
345
+ await copyFile(filePath, fileTempPath);
346
+ if (!filePath.endsWith(".gz")) {
347
+ await appendFile(fileTempPath, `${Array.isArray(data) ? data.join("\n") : data}\n`);
348
+ }
349
+ else {
350
+ await exec(`echo $'${(Array.isArray(data) ? data.join("\n") : data)
351
+ .toString()
352
+ .replace(/'/g, "\\'")}' | gzip - >> ${fileTempPath}`);
353
+ }
305
354
  }
355
+ else
356
+ await write(fileTempPath, `${Array.isArray(data) ? data.join("\n") : data}\n`);
357
+ return [fileTempPath, filePath];
358
+ }
359
+ catch {
360
+ return [fileTempPath, null];
306
361
  }
307
- else
308
- await write(fileTempPath, `${Array.isArray(data) ? data.join("\n") : data}\n`);
309
- return [fileTempPath, filePath];
310
362
  };
311
363
  /**
312
364
  * Asynchronously prepends data to the beginning of a file.
@@ -337,6 +389,9 @@ export const prepend = async (filePath, data) => {
337
389
  },
338
390
  }));
339
391
  }
392
+ catch {
393
+ return [fileTempPath, null];
394
+ }
340
395
  finally {
341
396
  // Ensure that file handles are closed, even if an error occurred
342
397
  await fileHandle?.close();
@@ -349,13 +404,22 @@ export const prepend = async (filePath, data) => {
349
404
  await write(fileChildTempPath, `${Array.isArray(data) ? data.join("\n") : data}\n`);
350
405
  await exec(`cat ${fileChildTempPath} ${filePath} > ${fileTempPath}`);
351
406
  }
407
+ catch {
408
+ return [fileTempPath, null];
409
+ }
352
410
  finally {
353
411
  await unlink(fileChildTempPath);
354
412
  }
355
413
  }
356
414
  }
357
- else
358
- await write(fileTempPath, `${Array.isArray(data) ? data.join("\n") : data}\n`);
415
+ else {
416
+ try {
417
+ await write(fileTempPath, `${Array.isArray(data) ? data.join("\n") : data}\n`);
418
+ }
419
+ catch {
420
+ return [fileTempPath, null];
421
+ }
422
+ }
359
423
  return [fileTempPath, filePath];
360
424
  };
361
425
  /**
@@ -374,11 +438,16 @@ export const remove = async (filePath, linesToDelete) => {
374
438
  if (linesToDelete.some(Number.isNaN))
375
439
  throw new Error("UNVALID_LINE_NUMBERS");
376
440
  const fileTempPath = filePath.replace(/([^/]+)\/?$/, ".tmp/$1");
377
- const command = filePath.endsWith(".gz")
378
- ? `zcat ${filePath} | sed "${linesToDelete.join("d;")}d" | gzip > ${fileTempPath}`
379
- : `sed "${linesToDelete.join("d;")}d" ${filePath} > ${fileTempPath}`;
380
- await exec(command);
381
- return [fileTempPath, filePath];
441
+ try {
442
+ const command = filePath.endsWith(".gz")
443
+ ? `zcat ${filePath} | sed "${linesToDelete.join("d;")}d" | gzip > ${fileTempPath}`
444
+ : `sed "${linesToDelete.join("d;")}d" ${filePath} > ${fileTempPath}`;
445
+ await exec(command);
446
+ return [fileTempPath, filePath];
447
+ }
448
+ catch {
449
+ return [fileTempPath, null];
450
+ }
382
451
  };
383
452
  /**
384
453
  * Asynchronously searches a file for lines matching specified criteria, using comparison and logical operators.
package/dist/index.d.ts CHANGED
@@ -49,7 +49,7 @@ declare global {
49
49
  entries<T extends object>(o: T): Entries<T>;
50
50
  }
51
51
  }
52
- export type ErrorCodes = "FIELD_UNIQUE" | "FIELD_REQUIRED" | "NO_SCHEMA" | "NO_ITEMS" | "INVALID_ID" | "INVALID_TYPE" | "INVALID_PARAMETERS" | "NO_ENV" | "TABLE_EXISTS" | "TABLE_NOT_EXISTS";
52
+ export type ErrorCodes = "FIELD_UNIQUE" | "FIELD_REQUIRED" | "NO_SCHEMA" | "TABLE_EMPTY" | "INVALID_ID" | "INVALID_TYPE" | "INVALID_PARAMETERS" | "NO_ENV" | "TABLE_EXISTS" | "TABLE_NOT_EXISTS";
53
53
  export type ErrorLang = "en";
54
54
  export default class Inibase {
55
55
  pageInfo: Record<string, pageInfo>;
@@ -72,7 +72,6 @@ export default class Inibase {
72
72
  */
73
73
  createTable(tableName: string, schema?: Schema, config?: Config): Promise<void>;
74
74
  private replaceStringInFile;
75
- private replaceStringInSchemas;
76
75
  /**
77
76
  * Update table schema or config
78
77
  *
package/dist/index.js CHANGED
@@ -1,13 +1,15 @@
1
1
  import "dotenv/config";
2
2
  import { randomBytes, scryptSync } from "node:crypto";
3
3
  import { appendFileSync, existsSync, readFileSync } from "node:fs";
4
- import { mkdir, readFile, readdir, rename, stat, unlink, writeFile, } from "node:fs/promises";
4
+ import { glob, mkdir, readFile, readdir, rename, rm, unlink, writeFile, } from "node:fs/promises";
5
5
  import { join, parse } from "node:path";
6
6
  import { inspect } from "node:util";
7
7
  import Inison from "inison";
8
8
  import * as File from "./file.js";
9
9
  import * as Utils from "./utils.js";
10
10
  import * as UtilsServer from "./utils.server.js";
11
+ // hide ExperimentalWarning glob()
12
+ process.removeAllListeners("warning");
11
13
  export default class Inibase {
12
14
  pageInfo;
13
15
  salt;
@@ -35,12 +37,12 @@ export default class Inibase {
35
37
  Error(code, variable, language = "en") {
36
38
  const errorMessages = {
37
39
  en: {
38
- FIELD_UNIQUE: "Field {variable} should be unique, got {variable} instead",
39
- FIELD_REQUIRED: "Field {variable} is required",
40
+ TABLE_EMPTY: "Table {variable} is empty",
40
41
  TABLE_EXISTS: "Table {variable} already exists",
41
42
  TABLE_NOT_EXISTS: "Table {variable} doesn't exist",
42
43
  NO_SCHEMA: "Table {variable} does't have a schema",
43
- NO_ITEMS: "Table {variable} is empty",
44
+ FIELD_UNIQUE: "Field {variable} should be unique, got {variable} instead",
45
+ FIELD_REQUIRED: "Field {variable} is required",
44
46
  INVALID_ID: "The given ID(s) is/are not valid(s)",
45
47
  INVALID_TYPE: "Expect {variable} to be {variable}, got {variable} instead",
46
48
  INVALID_PARAMETERS: "The given parameters are not valid",
@@ -108,8 +110,14 @@ export default class Inibase {
108
110
  if (config.prepend)
109
111
  await writeFile(join(tablePath, ".prepend.config"), "");
110
112
  }
111
- if (schema)
112
- await writeFile(join(tablePath, "schema.json"), JSON.stringify(UtilsServer.addIdToSchema(schema, 0, this.salt), null, 2));
113
+ if (schema) {
114
+ const lastSchemaId = 0;
115
+ await writeFile(join(tablePath, "schema.json"), JSON.stringify(UtilsServer.addIdToSchema(schema, lastSchemaId, this.salt), null, 2));
116
+ await writeFile(join(tablePath, `${lastSchemaId}.schema`), "");
117
+ }
118
+ else
119
+ await writeFile(join(tablePath, "0.schema"), "");
120
+ await writeFile(join(tablePath, "0-0.pagination"), "");
113
121
  }
114
122
  // Function to replace the string in one schema.json file
115
123
  async replaceStringInFile(filePath, targetString, replaceString) {
@@ -119,20 +127,6 @@ export default class Inibase {
119
127
  await writeFile(filePath, updatedContent, "utf8");
120
128
  }
121
129
  }
122
- // Function to process schema files one by one (sequentially)
123
- async replaceStringInSchemas(directoryPath, targetString, replaceString) {
124
- const files = await readdir(directoryPath);
125
- for (const file of files) {
126
- const fullPath = join(directoryPath, file);
127
- const fileStat = await stat(fullPath);
128
- if (fileStat.isDirectory()) {
129
- await this.replaceStringInSchemas(fullPath, targetString, replaceString);
130
- }
131
- else if (file === "schema.json") {
132
- await this.replaceStringInFile(fullPath, targetString, replaceString);
133
- }
134
- }
135
- }
136
130
  /**
137
131
  * Update table schema or config
138
132
  *
@@ -145,11 +139,13 @@ export default class Inibase {
145
139
  if (schema) {
146
140
  // remove id from schema
147
141
  schema = schema.filter(({ key }) => !["id", "createdAt", "updatedAt"].includes(key));
142
+ let schemaIdFilePath;
143
+ for await (const filePath of glob("*.schema", { cwd: this.databasePath }))
144
+ schemaIdFilePath = filePath;
145
+ const lastSchemaId = Number(parse(schemaIdFilePath).name);
148
146
  if (await File.isExists(join(tablePath, "schema.json"))) {
149
147
  // update columns files names based on field id
150
- schema = UtilsServer.addIdToSchema(schema, table.schema?.length
151
- ? UtilsServer.findLastIdNumber(table.schema, this.salt)
152
- : 0, this.salt);
148
+ schema = UtilsServer.addIdToSchema(schema, lastSchemaId, this.salt);
153
149
  if (table.schema?.length) {
154
150
  const replaceOldPathes = Utils.findChangedProperties(this._schemaToIdsPath(tableName, table.schema), this._schemaToIdsPath(tableName, schema));
155
151
  if (replaceOldPathes)
@@ -160,8 +156,9 @@ export default class Inibase {
160
156
  }
161
157
  }
162
158
  else
163
- schema = UtilsServer.addIdToSchema(schema, 0, this.salt);
159
+ schema = UtilsServer.addIdToSchema(schema, lastSchemaId, this.salt);
164
160
  await writeFile(join(tablePath, "schema.json"), JSON.stringify(schema, null, 2));
161
+ await rename(schemaIdFilePath, join(tablePath, `${lastSchemaId}.schema`));
165
162
  }
166
163
  if (config) {
167
164
  if (config.compression !== undefined &&
@@ -186,8 +183,10 @@ export default class Inibase {
186
183
  if (config.cache !== undefined && config.cache !== table.config.cache) {
187
184
  if (config.cache)
188
185
  await writeFile(join(tablePath, ".cache.config"), "");
189
- else
186
+ else {
187
+ await this.clearCache(tableName);
190
188
  await unlink(join(tablePath, ".cache.config"));
189
+ }
191
190
  }
192
191
  if (config.prepend !== undefined &&
193
192
  config.prepend !== table.config.prepend) {
@@ -213,8 +212,12 @@ export default class Inibase {
213
212
  await unlink(join(tablePath, ".prepend.config"));
214
213
  }
215
214
  if (config.name) {
216
- await this.replaceStringInSchemas(this.databasePath, `"table": "${tableName}"`, `"table": "${config.name}"`);
217
215
  await rename(tablePath, join(this.databasePath, config.name));
216
+ // replace table name in other linked tables (relationship)
217
+ for await (const schemaPath of glob("**/schema.json", {
218
+ cwd: this.databasePath,
219
+ }))
220
+ await this.replaceStringInFile(schemaPath, `"table": "${tableName}"`, `"table": "${config.name}"`);
218
221
  }
219
222
  }
220
223
  delete this.tables[tableName];
@@ -241,14 +244,13 @@ export default class Inibase {
241
244
  return this.tables[tableName];
242
245
  }
243
246
  async getTableSchema(tableName, encodeIDs = true) {
244
- const tableSchemaPath = join(this.databasePath, tableName, "schema.json");
245
- if (!(await File.isExists(tableSchemaPath)))
247
+ const tablePath = join(this.databasePath, tableName);
248
+ if (!(await File.isExists(join(tablePath, "schema.json"))))
246
249
  return undefined;
247
- const schemaFile = await readFile(tableSchemaPath, "utf8");
250
+ const schemaFile = await readFile(join(tablePath, "schema.json"), "utf8");
248
251
  if (!schemaFile)
249
252
  return undefined;
250
253
  let schema = JSON.parse(schemaFile);
251
- const lastIdNumber = UtilsServer.findLastIdNumber(schema, this.salt);
252
254
  schema = [
253
255
  {
254
256
  id: 0,
@@ -256,19 +258,19 @@ export default class Inibase {
256
258
  type: "id",
257
259
  required: true,
258
260
  },
259
- ...schema,
260
261
  {
261
- id: lastIdNumber + 1,
262
+ id: -1,
262
263
  key: "createdAt",
263
264
  type: "date",
264
265
  required: true,
265
266
  },
266
267
  {
267
- id: lastIdNumber + 2,
268
+ id: -2,
268
269
  key: "updatedAt",
269
270
  type: "date",
270
271
  required: false,
271
272
  },
273
+ ...schema,
272
274
  ];
273
275
  if (!encodeIDs)
274
276
  return schema;
@@ -279,7 +281,7 @@ export default class Inibase {
279
281
  if (!table.schema)
280
282
  throw this.Error("NO_SCHEMA", tableName);
281
283
  if (!(await File.isExists(join(this.databasePath, tableName, `id${this.getFileExtension(tableName)}`))))
282
- throw this.Error("NO_ITEMS", tableName);
284
+ throw this.Error("TABLE_EMPTY", tableName);
283
285
  return table;
284
286
  }
285
287
  validateData(data, schema, skipRequiredField = false) {
@@ -455,15 +457,17 @@ export default class Inibase {
455
457
  return null;
456
458
  }
457
459
  }
458
- _combineObjectsToArray = (input) => input.reduce((result, current) => {
459
- for (const [key, value] of Object.entries(current))
460
- if (Object.hasOwn(result, key) && Array.isArray(result[key]))
461
- result[key].push(value);
462
- else
463
- result[key] = [value];
464
- return result;
465
- }, {});
466
- _CombineData = (data, prefix) => {
460
+ _combineObjectsToArray(input) {
461
+ return input.reduce((result, current) => {
462
+ for (const [key, value] of Object.entries(current))
463
+ if (Object.hasOwn(result, key) && Array.isArray(result[key]))
464
+ result[key].push(value);
465
+ else
466
+ result[key] = [value];
467
+ return result;
468
+ }, {});
469
+ }
470
+ _CombineData(data, prefix) {
467
471
  if (Utils.isArrayOfObjects(data))
468
472
  return this._combineObjectsToArray(data.map((single_data) => this._CombineData(single_data)));
469
473
  const RETURN = {};
@@ -480,7 +484,7 @@ export default class Inibase {
480
484
  RETURN[(prefix ?? "") + key] = File.encode(value);
481
485
  }
482
486
  return RETURN;
483
- };
487
+ }
484
488
  joinPathesContents(tableName, data) {
485
489
  const tablePath = join(this.databasePath, tableName), combinedData = this._CombineData(data);
486
490
  const newCombinedData = {};
@@ -834,9 +838,8 @@ export default class Inibase {
834
838
  */
835
839
  async clearCache(tableName) {
836
840
  const cacheFolderPath = join(this.databasePath, tableName, ".cache");
837
- await Promise.all((await readdir(cacheFolderPath))
838
- .filter((file) => file !== ".pagination")
839
- .map((file) => unlink(join(cacheFolderPath, file))));
841
+ await rm(cacheFolderPath, { recursive: true, force: true });
842
+ await mkdir(cacheFolderPath);
840
843
  }
841
844
  async get(tableName, where, options = {
842
845
  page: 1,
@@ -858,7 +861,12 @@ export default class Inibase {
858
861
  let schema = (await this.getTable(tableName)).schema;
859
862
  if (!schema)
860
863
  throw this.Error("NO_SCHEMA", tableName);
861
- if (!(await File.isExists(join(tablePath, `id${this.getFileExtension(tableName)}`))))
864
+ let pagination;
865
+ for await (const paginationFilePath of glob("*.pagination", {
866
+ cwd: tablePath,
867
+ }))
868
+ pagination = parse(paginationFilePath).name.split("-").map(Number);
869
+ if (!pagination[1])
862
870
  return null;
863
871
  if (options.columns?.length)
864
872
  schema = this._filterSchemaByColumns(schema, options.columns);
@@ -931,7 +939,7 @@ export default class Inibase {
931
939
  if (where)
932
940
  lines = lines.slice((options.page - 1) * options.perPage, options.page * options.perPage);
933
941
  else if (!this.totalItems[`${tableName}-*`])
934
- this.totalItems[`${tableName}-*`] = await File.count(join(tablePath, `id${this.getFileExtension(tableName)}`));
942
+ this.totalItems[`${tableName}-*`] = pagination[1];
935
943
  if (!lines.length)
936
944
  return null;
937
945
  // Parse the result and extract the specified lines
@@ -964,16 +972,8 @@ export default class Inibase {
964
972
  RETURN = Object.values(await this.getItemsFromSchema(tableName, schema, Array.from({ length: options.perPage }, (_, index) => (options.page - 1) * options.perPage +
965
973
  index +
966
974
  1), options));
967
- if (await File.isExists(join(tablePath, ".cache", ".pagination"))) {
968
- if (!this.totalItems[`${tableName}-*`])
969
- this.totalItems[`${tableName}-*`] = Number((await readFile(join(tablePath, ".cache", ".pagination"), "utf8")).split(",")[1]);
970
- }
971
- else {
972
- const lastId = Number(Object.keys((await File.get(join(tablePath, `id${this.getFileExtension(tableName)}`), -1, "number", undefined, this.salt, true))?.[0] ?? 0));
973
- if (!this.totalItems[`${tableName}-*`])
974
- this.totalItems[`${tableName}-*`] = await File.count(join(tablePath, `id${this.getFileExtension(tableName)}`));
975
- await writeFile(join(tablePath, ".cache", ".pagination"), `${lastId},${this.totalItems[`${tableName}-*`]}`);
976
- }
975
+ if (!this.totalItems[`${tableName}-*`])
976
+ this.totalItems[`${tableName}-*`] = pagination[1];
977
977
  }
978
978
  else if ((Array.isArray(where) && where.every(Utils.isNumber)) ||
979
979
  Utils.isNumber(where)) {
@@ -1076,23 +1076,17 @@ export default class Inibase {
1076
1076
  returnPostedData = false;
1077
1077
  const keys = UtilsServer.hashString(Object.keys(Array.isArray(data) ? data[0] : data).join("."));
1078
1078
  // Skip Id and (created|updated)At
1079
- this.validateData(data, schema.slice(1, -2));
1080
- let lastId = 0, renameList = [];
1079
+ this.validateData(data, schema.slice(3));
1080
+ let lastId = 0;
1081
+ const renameList = [];
1081
1082
  try {
1082
1083
  await File.lock(join(tablePath, ".tmp"), keys);
1083
- if (await File.isExists(join(tablePath, `id${this.getFileExtension(tableName)}`))) {
1084
- if (await File.isExists(join(tablePath, ".cache", ".pagination")))
1085
- [lastId, this.totalItems[`${tableName}-*`]] = (await readFile(join(tablePath, ".cache", ".pagination"), "utf8"))
1086
- .split(",")
1087
- .map(Number);
1088
- else {
1089
- lastId = Number(Object.keys((await File.get(join(tablePath, `id${this.getFileExtension(tableName)}`), -1, "number", undefined, this.salt, true))?.[0] ?? 0));
1090
- if (!this.totalItems[`${tableName}-*`])
1091
- this.totalItems[`${tableName}-*`] = await File.count(join(tablePath, `id${this.getFileExtension(tableName)}`));
1092
- }
1093
- }
1094
- else
1095
- this.totalItems[`${tableName}-*`] = 0;
1084
+ let paginationFilePath;
1085
+ for await (const filePath of glob("*.pagination", { cwd: tablePath }))
1086
+ paginationFilePath = filePath;
1087
+ [lastId, this.totalItems[`${tableName}-*`]] = parse(paginationFilePath)
1088
+ .name.split("-")
1089
+ .map(Number);
1096
1090
  if (Utils.isArrayOfObjects(data))
1097
1091
  for (let index = 0; index < data.length; index++) {
1098
1092
  const element = data[index];
@@ -1112,17 +1106,18 @@ export default class Inibase {
1112
1106
  ? data.toReversed()
1113
1107
  : data
1114
1108
  : data);
1115
- await Promise.all(Object.entries(pathesContents).map(async ([path, content]) => renameList.push(this.tables[tableName].config.prepend
1109
+ await Promise.allSettled(Object.entries(pathesContents).map(async ([path, content]) => renameList.push(this.tables[tableName].config.prepend
1116
1110
  ? await File.prepend(path, content)
1117
1111
  : await File.append(path, content))));
1118
- await Promise.all(renameList.map(async ([tempPath, filePath]) => rename(tempPath, filePath)));
1119
- renameList = [];
1112
+ await Promise.allSettled(renameList
1113
+ .filter(([_, filePath]) => filePath)
1114
+ .map(async ([tempPath, filePath]) => rename(tempPath, filePath)));
1120
1115
  if (this.tables[tableName].config.cache)
1121
1116
  await this.clearCache(tableName);
1122
1117
  this.totalItems[`${tableName}-*`] += Array.isArray(data)
1123
1118
  ? data.length
1124
1119
  : 1;
1125
- await writeFile(join(tablePath, ".cache", ".pagination"), `${lastId},${this.totalItems[`${tableName}-*`]}`);
1120
+ await rename(join(tablePath, paginationFilePath), join(tablePath, `${lastId}-${this.totalItems[`${tableName}-*`]}.pagination`));
1126
1121
  if (returnPostedData)
1127
1122
  return this.get(tableName, this.tables[tableName].config.prepend
1128
1123
  ? Array.isArray(data)
@@ -1144,7 +1139,7 @@ export default class Inibase {
1144
1139
  page: 1,
1145
1140
  perPage: 15,
1146
1141
  }, returnUpdatedData) {
1147
- let renameList = [];
1142
+ const renameList = [];
1148
1143
  const tablePath = join(this.databasePath, tableName);
1149
1144
  const schema = (await this.throwErrorIfTableEmpty(tableName))
1150
1145
  .schema;
@@ -1159,13 +1154,6 @@ export default class Inibase {
1159
1154
  throw this.Error("INVALID_ID", data.id);
1160
1155
  return this.put(tableName, data, data.id, options, returnUpdatedData || undefined);
1161
1156
  }
1162
- let totalItems;
1163
- if (await File.isExists(join(tablePath, ".cache", ".pagination")))
1164
- totalItems = (await readFile(join(tablePath, ".cache", ".pagination"), "utf8"))
1165
- .split(",")
1166
- .map(Number)[1];
1167
- else
1168
- totalItems = await File.count(join(tablePath, `id${this.getFileExtension(tableName)}`));
1169
1157
  this.validateData(data, schema, true);
1170
1158
  await this.checkUnique(tableName, schema);
1171
1159
  data = this.formatData(data, schema, true);
@@ -1175,13 +1163,16 @@ export default class Inibase {
1175
1163
  });
1176
1164
  try {
1177
1165
  await File.lock(join(tablePath, ".tmp"));
1178
- await Promise.all(Object.entries(pathesContents).map(async ([path, content]) => {
1179
- const replacementObject = {};
1180
- for (const index of [...Array(totalItems)].keys())
1181
- replacementObject[`${index + 1}`] = content;
1182
- renameList.push(await File.replace(path, replacementObject));
1183
- }));
1184
- await Promise.all(renameList.map(async ([tempPath, filePath]) => rename(tempPath, filePath)));
1166
+ for await (const paginationFilePath of glob("*.pagination", {
1167
+ cwd: tablePath,
1168
+ }))
1169
+ this.totalItems[`${tableName}-*`] = parse(paginationFilePath)
1170
+ .name.split("-")
1171
+ .map(Number)[1];
1172
+ await Promise.allSettled(Object.entries(pathesContents).map(async ([path, content]) => renameList.push(await File.replace(path, content, this.totalItems[`${tableName}-*`]))));
1173
+ await Promise.allSettled(renameList
1174
+ .filter(([_, filePath]) => filePath)
1175
+ .map(async ([tempPath, filePath]) => rename(tempPath, filePath)));
1185
1176
  if (this.tables[tableName].config.cache)
1186
1177
  await this.clearCache(join(tablePath, ".cache"));
1187
1178
  if (returnUpdatedData)
@@ -1220,9 +1211,10 @@ export default class Inibase {
1220
1211
  .join("."));
1221
1212
  try {
1222
1213
  await File.lock(join(tablePath, ".tmp"), keys);
1223
- await Promise.all(Object.entries(pathesContents).map(async ([path, content]) => renameList.push(await File.replace(path, content))));
1224
- await Promise.all(renameList.map(async ([tempPath, filePath]) => rename(tempPath, filePath)));
1225
- renameList = [];
1214
+ await Promise.allSettled(Object.entries(pathesContents).map(async ([path, content]) => renameList.push(await File.replace(path, content))));
1215
+ await Promise.allSettled(renameList
1216
+ .filter(([_, filePath]) => filePath)
1217
+ .map(async ([tempPath, filePath]) => rename(tempPath, filePath)));
1226
1218
  if (this.tables[tableName].config.cache)
1227
1219
  await this.clearCache(tableName);
1228
1220
  if (returnUpdatedData)
@@ -1236,7 +1228,8 @@ export default class Inibase {
1236
1228
  }
1237
1229
  else if (Utils.isObject(where)) {
1238
1230
  const lineNumbers = await this.get(tableName, where, undefined, undefined, true);
1239
- return this.put(tableName, data, lineNumbers, options, returnUpdatedData || undefined);
1231
+ if (lineNumbers)
1232
+ return this.put(tableName, data, lineNumbers, options, returnUpdatedData || undefined);
1240
1233
  }
1241
1234
  else
1242
1235
  throw this.Error("INVALID_PARAMETERS");
@@ -1254,19 +1247,20 @@ export default class Inibase {
1254
1247
  if (!where) {
1255
1248
  try {
1256
1249
  await File.lock(join(tablePath, ".tmp"));
1257
- let lastId;
1258
- if (await File.isExists(join(tablePath, ".cache", ".pagination")))
1259
- lastId = (await readFile(join(tablePath, ".cache", ".pagination"), "utf8"))
1260
- .split(",")
1261
- .map(Number)[0];
1262
- else
1263
- lastId = Number(Object.keys((await File.get(join(tablePath, `id${this.getFileExtension(tableName)}`), -1, "number", undefined, this.salt, true))?.[0] ?? 0));
1250
+ let paginationFilePath;
1251
+ let pagination;
1252
+ for await (const filePath of glob("*.pagination", {
1253
+ cwd: tablePath,
1254
+ })) {
1255
+ paginationFilePath = filePath;
1256
+ pagination = parse(filePath).name.split("-").map(Number);
1257
+ }
1264
1258
  await Promise.all((await readdir(tablePath))
1265
1259
  ?.filter((fileName) => fileName.endsWith(this.getFileExtension(tableName)))
1266
1260
  .map(async (file) => unlink(join(tablePath, file))));
1267
1261
  if (this.tables[tableName].config.cache)
1268
1262
  await this.clearCache(tableName);
1269
- await writeFile(join(tablePath, ".cache", ".pagination"), `${lastId},0`);
1263
+ await rename(join(tablePath, paginationFilePath), join(tablePath, `${pagination[0]}-0.pagination`));
1270
1264
  return true;
1271
1265
  }
1272
1266
  finally {
@@ -1286,22 +1280,20 @@ export default class Inibase {
1286
1280
  const renameList = [];
1287
1281
  try {
1288
1282
  await File.lock(join(tablePath, ".tmp"));
1289
- let lastId;
1290
- if (await File.isExists(join(tablePath, ".cache", ".pagination")))
1291
- [lastId, this.totalItems[`${tableName}-*`]] = (await readFile(join(tablePath, ".cache", ".pagination"), "utf8"))
1292
- .split(",")
1293
- .map(Number);
1294
- else {
1295
- lastId = Number(Object.keys((await File.get(join(tablePath, `id${this.getFileExtension(tableName)}`), -1, "number", undefined, this.salt, true))?.[0] ?? 0));
1296
- if (!this.totalItems[`${tableName}-*`])
1297
- this.totalItems[`${tableName}-*`] = await File.count(join(tablePath, `id${this.getFileExtension(tableName)}`));
1283
+ let paginationFilePath;
1284
+ let pagination;
1285
+ for await (const filePath of glob("*.pagination", {
1286
+ cwd: tablePath,
1287
+ })) {
1288
+ paginationFilePath = filePath;
1289
+ pagination = parse(filePath).name.split("-").map(Number);
1298
1290
  }
1299
- if (this.totalItems[`${tableName}-*`] &&
1300
- this.totalItems[`${tableName}-*`] -
1301
- (Array.isArray(where) ? where.length : 1) >
1302
- 0) {
1291
+ if (pagination[1] &&
1292
+ pagination[1] - (Array.isArray(where) ? where.length : 1) > 0) {
1303
1293
  await Promise.all(files.map(async (file) => renameList.push(await File.remove(join(tablePath, file), where))));
1304
- await Promise.all(renameList.map(async ([tempPath, filePath]) => rename(tempPath, filePath)));
1294
+ await Promise.all(renameList
1295
+ .filter(([_, filePath]) => filePath)
1296
+ .map(async ([tempPath, filePath]) => rename(tempPath, filePath)));
1305
1297
  }
1306
1298
  else
1307
1299
  await Promise.all((await readdir(tablePath))
@@ -1309,8 +1301,7 @@ export default class Inibase {
1309
1301
  .map(async (file) => unlink(join(tablePath, file))));
1310
1302
  if (this.tables[tableName].config.cache)
1311
1303
  await this.clearCache(tableName);
1312
- await writeFile(join(tablePath, ".cache", ".pagination"), `${lastId},${this.totalItems[`${tableName}-*`] -
1313
- (Array.isArray(where) ? where.length : 1)}`);
1304
+ await rename(join(tablePath, paginationFilePath), join(tablePath, `${pagination[0]}-${pagination[1] - (Array.isArray(where) ? where.length : 1)}.pagination`));
1314
1305
  return true;
1315
1306
  }
1316
1307
  finally {
@@ -1322,7 +1313,8 @@ export default class Inibase {
1322
1313
  }
1323
1314
  else if (Utils.isObject(where)) {
1324
1315
  const lineNumbers = await this.get(tableName, where, undefined, undefined, true);
1325
- return this.delete(tableName, lineNumbers);
1316
+ if (lineNumbers)
1317
+ return this.delete(tableName, lineNumbers);
1326
1318
  }
1327
1319
  else
1328
1320
  throw this.Error("INVALID_PARAMETERS");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "inibase",
3
- "version": "1.0.0-rc.101",
3
+ "version": "1.0.0-rc.102",
4
4
  "type": "module",
5
5
  "author": {
6
6
  "name": "Karim Amahtil",