inibase 1.0.0-rc.101 → 1.0.0-rc.103
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 +1 -1
- package/dist/file.js +140 -59
- package/dist/index.d.ts +1 -2
- package/dist/index.js +114 -122
- package/package.json +2 -2
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)[]
|
|
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,9 +1,11 @@
|
|
|
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";
|
|
4
5
|
import { Transform } from "node:stream";
|
|
5
6
|
import { pipeline } from "node:stream/promises";
|
|
6
7
|
import { createGunzip, createGzip } from "node:zlib";
|
|
8
|
+
import { spawn as spawnSync } from "node:child_process";
|
|
7
9
|
import Inison from "inison";
|
|
8
10
|
import { detectFieldType, isArrayOfObjects, isStringified, isNumber, isObject, } from "./utils.js";
|
|
9
11
|
import { compare, encodeID, exec, gunzip, gzip } from "./utils.server.js";
|
|
@@ -34,6 +36,10 @@ export const write = async (filePath, data) => {
|
|
|
34
36
|
export const read = async (filePath) => filePath.endsWith(".gz")
|
|
35
37
|
? (await gunzip(await readFile(filePath, "utf8"))).toString()
|
|
36
38
|
: await readFile(filePath, "utf8");
|
|
39
|
+
// Escaping function to safely handle special characters
|
|
40
|
+
function _escapeDoubleQuotes(arg) {
|
|
41
|
+
return arg.replace(/(["$`\\])/g, "\\$1"); // Escape double quotes, $, `, and \
|
|
42
|
+
}
|
|
37
43
|
const _pipeline = async (filePath, rl, writeStream, transform) => {
|
|
38
44
|
if (filePath.endsWith(".gz"))
|
|
39
45
|
await pipeline(rl, transform, createGzip(), writeStream);
|
|
@@ -192,9 +198,10 @@ export async function get(filePath, lineNumbers, fieldType, fieldChildrenType, s
|
|
|
192
198
|
}
|
|
193
199
|
}
|
|
194
200
|
else if (lineNumbers == -1) {
|
|
201
|
+
const escapedFilePath = `"${_escapeDoubleQuotes(filePath)}"`;
|
|
195
202
|
const command = filePath.endsWith(".gz")
|
|
196
|
-
? `zcat ${
|
|
197
|
-
: `sed -n '$p' ${
|
|
203
|
+
? `zcat ${escapedFilePath} | sed -n '$p'`
|
|
204
|
+
: `sed -n '$p' ${escapedFilePath}`, foundedLine = (await exec(command)).stdout.trimEnd();
|
|
198
205
|
if (foundedLine)
|
|
199
206
|
lines[linesCount] = decode(foundedLine, fieldType, fieldChildrenType, secretKey);
|
|
200
207
|
}
|
|
@@ -213,9 +220,10 @@ export async function get(filePath, lineNumbers, fieldType, fieldChildrenType, s
|
|
|
213
220
|
}
|
|
214
221
|
return [lines, linesCount];
|
|
215
222
|
}
|
|
223
|
+
const escapedFilePath = `"${_escapeDoubleQuotes(filePath)}"`;
|
|
216
224
|
const command = filePath.endsWith(".gz")
|
|
217
|
-
? `zcat ${
|
|
218
|
-
: `sed -n '${lineNumbers.join("p;")}p' ${
|
|
225
|
+
? `zcat "${escapedFilePath}" | sed -n '${lineNumbers.join("p;")}p'`
|
|
226
|
+
: `sed -n '${lineNumbers.join("p;")}p' "${escapedFilePath}"`, foundedLines = (await exec(command)).stdout.trimEnd().split("\n");
|
|
219
227
|
let index = 0;
|
|
220
228
|
for (const line of foundedLines) {
|
|
221
229
|
lines[lineNumbers[index]] = decode(line, fieldType, fieldChildrenType, secretKey);
|
|
@@ -239,48 +247,93 @@ export async function get(filePath, lineNumbers, fieldType, fieldChildrenType, s
|
|
|
239
247
|
*
|
|
240
248
|
* Note: If the file doesn't exist and replacements is an object, it creates a new file with the specified replacements.
|
|
241
249
|
*/
|
|
242
|
-
export const replace = async (filePath, replacements) => {
|
|
250
|
+
export const replace = async (filePath, replacements, totalItems) => {
|
|
243
251
|
const fileTempPath = filePath.replace(/([^/]+)\/?$/, ".tmp/$1");
|
|
252
|
+
const isReplacementsObject = isObject(replacements);
|
|
253
|
+
const isReplacementsObjectHasLineNumbers = isReplacementsObject && !Number.isNaN(Number(Object.keys(replacements)[0]));
|
|
244
254
|
if (await isExists(filePath)) {
|
|
245
|
-
|
|
246
|
-
|
|
255
|
+
if (isReplacementsObjectHasLineNumbers) {
|
|
256
|
+
let fileHandle = null;
|
|
257
|
+
let fileTempHandle = null;
|
|
258
|
+
try {
|
|
259
|
+
let linesCount = 0;
|
|
260
|
+
fileHandle = await open(filePath, "r");
|
|
261
|
+
fileTempHandle = await open(fileTempPath, "w");
|
|
262
|
+
const rl = createReadLineInternface(filePath, fileHandle);
|
|
263
|
+
await _pipeline(filePath, rl, fileTempHandle.createWriteStream(), new Transform({
|
|
264
|
+
transform(line, _, callback) {
|
|
265
|
+
linesCount++;
|
|
266
|
+
const replacement = isReplacementsObject
|
|
267
|
+
? Object.hasOwn(replacements, linesCount)
|
|
268
|
+
? replacements[linesCount]
|
|
269
|
+
: line
|
|
270
|
+
: replacements;
|
|
271
|
+
return callback(null, `${replacement}\n`);
|
|
272
|
+
},
|
|
273
|
+
}));
|
|
274
|
+
return [fileTempPath, filePath];
|
|
275
|
+
}
|
|
276
|
+
catch {
|
|
277
|
+
return [fileTempPath, null];
|
|
278
|
+
}
|
|
279
|
+
finally {
|
|
280
|
+
// Ensure that file handles are closed, even if an error occurred
|
|
281
|
+
await fileHandle?.close();
|
|
282
|
+
await fileTempHandle?.close();
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
return new Promise((resolve) => {
|
|
287
|
+
const sedProcess = spawnSync("sed", [
|
|
288
|
+
"-e",
|
|
289
|
+
`s/.*/${replacements}/`,
|
|
290
|
+
"-e",
|
|
291
|
+
`/^$/s/^/${replacements}/`,
|
|
292
|
+
filePath,
|
|
293
|
+
]);
|
|
294
|
+
const outputStream = createWriteStream(fileTempPath); // Temp file for output
|
|
295
|
+
// Pipe sed output to the temporary file
|
|
296
|
+
sedProcess.stdout.pipe(outputStream);
|
|
297
|
+
// Handle the process close
|
|
298
|
+
sedProcess.on("close", (code) => {
|
|
299
|
+
if (code === 0)
|
|
300
|
+
resolve([fileTempPath, filePath]);
|
|
301
|
+
else
|
|
302
|
+
resolve([fileTempPath, null]);
|
|
303
|
+
});
|
|
304
|
+
// Handle errors in spawning the sed process
|
|
305
|
+
sedProcess.on("error", () => {
|
|
306
|
+
resolve([fileTempPath, null]);
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
else if (isReplacementsObject) {
|
|
247
312
|
try {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
?
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
313
|
+
if (isReplacementsObjectHasLineNumbers) {
|
|
314
|
+
const replacementsKeys = Object.keys(replacements)
|
|
315
|
+
.map(Number)
|
|
316
|
+
.toSorted((a, b) => a - b);
|
|
317
|
+
await write(fileTempPath, `${"\n".repeat(replacementsKeys[0] - 1) +
|
|
318
|
+
replacementsKeys
|
|
319
|
+
.map((lineNumber, index) => index === 0 ||
|
|
320
|
+
lineNumber - replacementsKeys[index - 1] - 1 === 0
|
|
321
|
+
? replacements[lineNumber]
|
|
322
|
+
: "\n".repeat(lineNumber - replacementsKeys[index - 1] - 1) +
|
|
323
|
+
replacements[lineNumber])
|
|
324
|
+
.join("\n")}\n`);
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
if (!totalItems)
|
|
328
|
+
throw new Error("INVALID_PARAMETERS");
|
|
329
|
+
await write(fileTempPath, `${`${replacements}\n`.repeat(totalItems)}\n`);
|
|
330
|
+
}
|
|
263
331
|
return [fileTempPath, filePath];
|
|
264
332
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
await fileHandle?.close();
|
|
268
|
-
await fileTempHandle?.close();
|
|
333
|
+
catch {
|
|
334
|
+
return [fileTempPath, null];
|
|
269
335
|
}
|
|
270
336
|
}
|
|
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
337
|
return [];
|
|
285
338
|
};
|
|
286
339
|
/**
|
|
@@ -293,20 +346,26 @@ export const replace = async (filePath, replacements) => {
|
|
|
293
346
|
*/
|
|
294
347
|
export const append = async (filePath, data) => {
|
|
295
348
|
const fileTempPath = filePath.replace(/([^/]+)\/?$/, ".tmp/$1");
|
|
296
|
-
|
|
297
|
-
await
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
.
|
|
349
|
+
try {
|
|
350
|
+
if (await isExists(filePath)) {
|
|
351
|
+
await copyFile(filePath, fileTempPath);
|
|
352
|
+
if (!filePath.endsWith(".gz")) {
|
|
353
|
+
await appendFile(fileTempPath, `${Array.isArray(data) ? data.join("\n") : data}\n`);
|
|
354
|
+
}
|
|
355
|
+
else {
|
|
356
|
+
const escapedFileTempPath = `"${_escapeDoubleQuotes(fileTempPath)}"`;
|
|
357
|
+
await exec(`echo $'${(Array.isArray(data) ? data.join("\n") : data)
|
|
358
|
+
.toString()
|
|
359
|
+
.replace(/'/g, "\\'")}' | gzip - >> ${escapedFileTempPath}`);
|
|
360
|
+
}
|
|
305
361
|
}
|
|
362
|
+
else
|
|
363
|
+
await write(fileTempPath, `${Array.isArray(data) ? data.join("\n") : data}\n`);
|
|
364
|
+
return [fileTempPath, filePath];
|
|
365
|
+
}
|
|
366
|
+
catch {
|
|
367
|
+
return [fileTempPath, null];
|
|
306
368
|
}
|
|
307
|
-
else
|
|
308
|
-
await write(fileTempPath, `${Array.isArray(data) ? data.join("\n") : data}\n`);
|
|
309
|
-
return [fileTempPath, filePath];
|
|
310
369
|
};
|
|
311
370
|
/**
|
|
312
371
|
* Asynchronously prepends data to the beginning of a file.
|
|
@@ -337,6 +396,9 @@ export const prepend = async (filePath, data) => {
|
|
|
337
396
|
},
|
|
338
397
|
}));
|
|
339
398
|
}
|
|
399
|
+
catch {
|
|
400
|
+
return [fileTempPath, null];
|
|
401
|
+
}
|
|
340
402
|
finally {
|
|
341
403
|
// Ensure that file handles are closed, even if an error occurred
|
|
342
404
|
await fileHandle?.close();
|
|
@@ -347,15 +409,27 @@ export const prepend = async (filePath, data) => {
|
|
|
347
409
|
const fileChildTempPath = filePath.replace(/([^/]+)\/?$/, ".tmp/tmp_$1");
|
|
348
410
|
try {
|
|
349
411
|
await write(fileChildTempPath, `${Array.isArray(data) ? data.join("\n") : data}\n`);
|
|
350
|
-
|
|
412
|
+
const escapedFilePath = `"${_escapeDoubleQuotes(filePath)}"`;
|
|
413
|
+
const escapedFileTempPath = `"${_escapeDoubleQuotes(fileTempPath)}"`;
|
|
414
|
+
const escapedFileChildTempPath = `"${_escapeDoubleQuotes(fileChildTempPath)}"`;
|
|
415
|
+
await exec(`cat ${escapedFileChildTempPath} ${escapedFilePath} > ${escapedFileTempPath}`);
|
|
416
|
+
}
|
|
417
|
+
catch {
|
|
418
|
+
return [fileTempPath, null];
|
|
351
419
|
}
|
|
352
420
|
finally {
|
|
353
421
|
await unlink(fileChildTempPath);
|
|
354
422
|
}
|
|
355
423
|
}
|
|
356
424
|
}
|
|
357
|
-
else
|
|
358
|
-
|
|
425
|
+
else {
|
|
426
|
+
try {
|
|
427
|
+
await write(fileTempPath, `${Array.isArray(data) ? data.join("\n") : data}\n`);
|
|
428
|
+
}
|
|
429
|
+
catch {
|
|
430
|
+
return [fileTempPath, null];
|
|
431
|
+
}
|
|
432
|
+
}
|
|
359
433
|
return [fileTempPath, filePath];
|
|
360
434
|
};
|
|
361
435
|
/**
|
|
@@ -374,11 +448,18 @@ export const remove = async (filePath, linesToDelete) => {
|
|
|
374
448
|
if (linesToDelete.some(Number.isNaN))
|
|
375
449
|
throw new Error("UNVALID_LINE_NUMBERS");
|
|
376
450
|
const fileTempPath = filePath.replace(/([^/]+)\/?$/, ".tmp/$1");
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
451
|
+
try {
|
|
452
|
+
const escapedFilePath = `"${_escapeDoubleQuotes(filePath)}"`;
|
|
453
|
+
const escapedFileTempPath = `"${_escapeDoubleQuotes(fileTempPath)}"`;
|
|
454
|
+
const command = filePath.endsWith(".gz")
|
|
455
|
+
? `zcat ${escapedFilePath} | sed "${linesToDelete.join("d;")}d" | gzip > ${fileTempPath}`
|
|
456
|
+
: `sed "${linesToDelete.join("d;")}d" ${escapedFilePath} > ${escapedFileTempPath}`;
|
|
457
|
+
await exec(command);
|
|
458
|
+
return [fileTempPath, filePath];
|
|
459
|
+
}
|
|
460
|
+
catch {
|
|
461
|
+
return [fileTempPath, null];
|
|
462
|
+
}
|
|
382
463
|
};
|
|
383
464
|
/**
|
|
384
465
|
* 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" | "
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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
|
|
245
|
-
if (!(await File.isExists(
|
|
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(
|
|
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:
|
|
262
|
+
id: -1,
|
|
262
263
|
key: "createdAt",
|
|
263
264
|
type: "date",
|
|
264
265
|
required: true,
|
|
265
266
|
},
|
|
266
267
|
{
|
|
267
|
-
id:
|
|
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("
|
|
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
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
result[key]
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
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
|
|
838
|
-
|
|
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
|
-
|
|
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}-*`] =
|
|
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 (
|
|
968
|
-
|
|
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(
|
|
1080
|
-
let lastId = 0
|
|
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
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
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.
|
|
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.
|
|
1119
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
await Promise.
|
|
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.
|
|
1224
|
-
await Promise.
|
|
1225
|
-
|
|
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
|
-
|
|
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
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
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
|
|
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
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
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 (
|
|
1300
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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.
|
|
3
|
+
"version": "1.0.0-rc.103",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Karim Amahtil",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
},
|
|
20
20
|
"description": "A file-based & memory-efficient, serverless, ACID compliant, relational database management system",
|
|
21
21
|
"engines": {
|
|
22
|
-
"node": ">=
|
|
22
|
+
"node": ">=22"
|
|
23
23
|
},
|
|
24
24
|
"files": [
|
|
25
25
|
"/dist"
|