inibase 1.0.0-rc.75 → 1.0.0-rc.77

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -632,11 +632,11 @@ import Inibase from "inibase";
632
632
  const db = new Inibase("/databaseName");
633
633
 
634
634
  // order users by the age column
635
- await db.sort("user", "age");
635
+ await db.get("user", undefined, { sort: "age" });
636
636
 
637
637
  // order users by the age and username columns
638
- await db.sort("user", ["age","username"]);
639
- await db.sort("user", {age: -1, username: "asc"});
638
+ await db.get("user", undefined, { sort: ["age", "username"] });
639
+ await db.get("user", undefined, { sort: {age: -1, username: "asc"} });
640
640
  ```
641
641
  </blockquote>
642
642
  </details>
package/dist/cli.js CHANGED
@@ -196,7 +196,7 @@ rl.on("line", async (input) => {
196
196
  console.log(`${textRed(" Err:")} Please specify table name`);
197
197
  break;
198
198
  }
199
- let where = undefined, page = undefined, perPage = undefined, columns = undefined, data = undefined;
199
+ let where = undefined, page = undefined, perPage = undefined, columns = undefined, sort = undefined, data = undefined;
200
200
  if (splitedInput.toSpliced(0, 1).length) {
201
201
  const parsedArgs = parseArgs({
202
202
  args: splitedInput.toSpliced(0, table ? 1 : 2),
@@ -204,6 +204,7 @@ rl.on("line", async (input) => {
204
204
  where: { type: "string", short: "w" },
205
205
  page: { type: "string", short: "p" },
206
206
  perPage: { type: "string", short: "l" },
207
+ sort: { type: "string", short: "s" },
207
208
  columns: { type: "string", short: "c", multiple: true },
208
209
  data: { type: "string", short: "d" },
209
210
  },
@@ -216,6 +217,12 @@ rl.on("line", async (input) => {
216
217
  else if (isJSON(parsedArgs.where))
217
218
  where = Inison.unstringify(parsedArgs.where);
218
219
  }
220
+ if (parsedArgs.sort) {
221
+ if (isJSON(parsedArgs.sort))
222
+ sort = Inison.unstringify(parsedArgs.sort);
223
+ else
224
+ sort = parsedArgs.sort;
225
+ }
219
226
  page = Number(parsedArgs.page) ?? undefined;
220
227
  perPage = Number(parsedArgs.perPage) ?? undefined;
221
228
  columns = parsedArgs.columns;
@@ -229,6 +236,7 @@ rl.on("line", async (input) => {
229
236
  page: Number(page) ?? 1,
230
237
  perPage: Number(perPage) ?? 15,
231
238
  columns,
239
+ sort,
232
240
  }));
233
241
  break;
234
242
  case "p":
package/dist/index.d.ts CHANGED
@@ -21,7 +21,7 @@ export interface Options {
21
21
  page?: number;
22
22
  perPage?: number;
23
23
  columns?: string[] | string;
24
- order?: Record<string, "asc" | "desc">;
24
+ sort?: Record<string, 1 | -1 | "asc" | "ASC" | "desc" | "DESC"> | string[] | string;
25
25
  }
26
26
  export interface Config {
27
27
  compression?: boolean;
@@ -113,13 +113,14 @@ export default class Inibase {
113
113
  * @param {string} tableName
114
114
  * @param {(string | number | (string | number)[] | Criteria | undefined)} [where]
115
115
  * @param {(Options | undefined)} [options]
116
- * @param {true} [onlyOne]
117
- * @param {undefined} [onlyLinesNumbers]
118
- * @param {boolean} [_skipIdColumn]
116
+ * @param {boolean} [onlyOne]
117
+ * @param {boolean} [onlyLinesNumbers]
119
118
  * @return {*} {(Promise<Data[] | Data | number[] | null>)}
120
119
  */
121
- get(tableName: string, where?: string | number | (string | number)[] | Criteria | undefined, options?: Options | undefined, onlyOne?: true, onlyLinesNumbers?: undefined, _skipIdColumn?: boolean): Promise<Data | null>;
122
- get(tableName: string, where?: string | number | (string | number)[] | Criteria | undefined, options?: Options | undefined, onlyOne?: boolean | undefined, onlyLinesNumbers?: true, _skipIdColumn?: boolean): Promise<number[]>;
120
+ get(tableName: string, where: string | number | (string | number)[] | Criteria | undefined, options: Options | undefined, onlyOne: true, onlyLinesNumbers?: false): Promise<Data | null>;
121
+ get(tableName: string, where: string | number, options?: Options, onlyOne?: boolean, onlyLinesNumbers?: false): Promise<Data | null>;
122
+ get(tableName: string, where?: string | number | (string | number)[] | Criteria, options?: Options, onlyOne?: boolean, onlyLinesNumbers?: false): Promise<Data[] | null>;
123
+ get(tableName: string, where: string | number | (string | number)[] | Criteria | undefined, options: Options | undefined, onlyOne: boolean | undefined, onlyLinesNumbers: true): Promise<number[]>;
123
124
  /**
124
125
  * Create new item(s) in a table
125
126
  *
@@ -183,18 +184,4 @@ export default class Inibase {
183
184
  */
184
185
  min(tableName: string, columns: string, where?: number | string | (number | string)[] | Criteria): Promise<number>;
185
186
  min(tableName: string, columns: string[], where?: number | string | (number | string)[] | Criteria): Promise<Record<string, number>>;
186
- /**
187
- * Sort column(s) of a table
188
- *
189
- * @param {string} tableName
190
- * @param {(string
191
- * | string[]
192
- * | Record<string, 1 | -1 | "asc" | "ASC" | "desc" | "DESC">)} columns
193
- * @param {(string | number | (string | number)[] | Criteria)} [where]
194
- * @param {Options} [options={
195
- * page: 1,
196
- * perPage: 15,
197
- * }]
198
- */
199
- sort(tableName: string, columns: string | string[] | Record<string, 1 | -1 | "asc" | "ASC" | "desc" | "DESC">, where?: string | number | (string | number)[] | Criteria, options?: Options): Promise<any[]>;
200
187
  }
package/dist/index.js CHANGED
@@ -222,30 +222,32 @@ export default class Inibase {
222
222
  const schemaFile = await readFile(tableSchemaPath, "utf8");
223
223
  if (!schemaFile)
224
224
  return undefined;
225
- const schema = JSON.parse(schemaFile), lastIdNumber = UtilsServer.findLastIdNumber(schema, this.salt);
226
- if (!encodeIDs)
227
- return schema;
228
- return [
225
+ let schema = JSON.parse(schemaFile);
226
+ const lastIdNumber = UtilsServer.findLastIdNumber(schema, this.salt);
227
+ schema = [
229
228
  {
230
- id: UtilsServer.encodeID(0, this.salt),
229
+ id: 0,
231
230
  key: "id",
232
231
  type: "id",
233
232
  required: true,
234
233
  },
235
- ...UtilsServer.encodeSchemaID(schema, this.salt),
234
+ ...schema,
236
235
  {
237
- id: UtilsServer.encodeID(lastIdNumber + 1, this.salt),
236
+ id: lastIdNumber + 1,
238
237
  key: "createdAt",
239
238
  type: "date",
240
239
  required: true,
241
240
  },
242
241
  {
243
- id: UtilsServer.encodeID(lastIdNumber + 2, this.salt),
242
+ id: lastIdNumber + 2,
244
243
  key: "updatedAt",
245
244
  type: "date",
246
245
  required: false,
247
246
  },
248
247
  ];
248
+ if (!encodeIDs)
249
+ return schema;
250
+ return UtilsServer.encodeSchemaID(schema, this.salt);
249
251
  }
250
252
  async throwErrorIfTableEmpty(tableName) {
251
253
  const table = await this.getTable(tableName);
@@ -794,16 +796,14 @@ export default class Inibase {
794
796
  async get(tableName, where, options = {
795
797
  page: 1,
796
798
  perPage: 15,
797
- }, onlyOne, onlyLinesNumbers, _skipIdColumn) {
799
+ }, onlyOne, onlyLinesNumbers) {
798
800
  const tablePath = join(this.databasePath, tableName);
799
801
  // Ensure options.columns is an array
800
802
  if (options.columns) {
801
803
  options.columns = Array.isArray(options.columns)
802
804
  ? options.columns
803
805
  : [options.columns];
804
- if (!_skipIdColumn &&
805
- options.columns.length &&
806
- !options.columns.includes("id"))
806
+ if (options.columns.length && !options.columns.includes("id"))
807
807
  options.columns.push("id");
808
808
  }
809
809
  // Default values for page and perPage
@@ -821,6 +821,96 @@ export default class Inibase {
821
821
  ((Array.isArray(where) && !where.length) ||
822
822
  (Utils.isObject(where) && !Object.keys(where).length)))
823
823
  where = undefined;
824
+ if (options.sort) {
825
+ let sortArray, isLineNumbers = true, keepItems = [];
826
+ if (Utils.isObject(options.sort) && !Array.isArray(options.sort)) {
827
+ // {name: "ASC", age: "DESC"}
828
+ sortArray = Object.entries(options.sort).map(([key, value]) => [
829
+ key,
830
+ typeof value === "string" ? value.toLowerCase() === "asc" : value > 0,
831
+ ]);
832
+ }
833
+ else
834
+ sortArray = []
835
+ .concat(options.sort)
836
+ .map((column) => [column, true]);
837
+ let cacheKey = "";
838
+ // Criteria
839
+ if (this.tables[tableName].config.cache)
840
+ cacheKey = UtilsServer.hashString(inspect(sortArray, { sorted: true }));
841
+ if (where) {
842
+ const lineNumbers = await this.get(tableName, where, undefined, undefined, true);
843
+ keepItems = Object.values((await File.get(join(tablePath, `id${this.getFileExtension(tableName)}`), lineNumbers, "number", undefined, this.salt)) ?? {}).map(Number);
844
+ isLineNumbers = false;
845
+ if (!keepItems.length)
846
+ throw this.throwError("NO_RESULTS", tableName);
847
+ keepItems = keepItems.slice((options.page - 1) * options.perPage, options.page * options.perPage);
848
+ }
849
+ if (!keepItems.length)
850
+ keepItems = Array.from({ length: options.perPage }, (_, index) => (options.page - 1) * options.perPage +
851
+ index +
852
+ 1);
853
+ const filesPathes = [["id", true], ...sortArray].map((column) => join(tablePath, `${column[0]}${this.getFileExtension(tableName)}`));
854
+ for await (const path of filesPathes.slice(1))
855
+ if (!(await File.isExists(path)))
856
+ return null;
857
+ // Construct the paste command to merge files and filter lines by IDs
858
+ const pasteCommand = `paste ${filesPathes.join(" ")}`;
859
+ // Construct the sort command dynamically based on the number of files for sorting
860
+ const index = 2;
861
+ const sortColumns = sortArray
862
+ .map(([key, ascending], i) => {
863
+ const field = Utils.getField(key, schema);
864
+ if (field)
865
+ return `-k${i + index},${i + index}${Utils.isFieldType(["id", "number", "date"], field.type, field.children)
866
+ ? "n"
867
+ : ""}${!ascending ? "r" : ""}`;
868
+ return "";
869
+ })
870
+ .join(" ");
871
+ const sortCommand = `sort ${sortColumns} -T=${join(tablePath, ".tmp")}`;
872
+ // Construct the awk command to keep only the specified lines after sorting
873
+ const awkCommand = isLineNumbers
874
+ ? `awk '${keepItems.map((lineNumber) => `NR==${lineNumber}`).join(" || ")}'`
875
+ : `awk '${keepItems.map((id) => `$1 ~ /^${id} */`).join(" || ")}'`;
876
+ try {
877
+ if (cacheKey)
878
+ await File.lock(join(tablePath, ".tmp"), cacheKey);
879
+ // Combine && Execute the commands synchronously
880
+ const lines = (await UtilsServer.exec(this.tables[tableName].config.cache
881
+ ? (await File.isExists(join(tablePath, ".cache", `${cacheKey}${this.fileExtension}`)))
882
+ ? `${awkCommand} ${join(tablePath, ".cache", `${cacheKey}${this.fileExtension}`)}`
883
+ : `${pasteCommand} | ${sortCommand} -o ${join(tablePath, ".cache", `${cacheKey}${this.fileExtension}`)} && ${awkCommand} ${join(tablePath, ".cache", `${cacheKey}${this.fileExtension}`)}`
884
+ : `${pasteCommand} | ${sortCommand} | ${awkCommand}`, {
885
+ encoding: "utf-8",
886
+ })).stdout
887
+ .trim()
888
+ .split("\n");
889
+ // Parse the result and extract the specified lines
890
+ const outputArray = lines.map((line) => {
891
+ const splitedFileColumns = line.split("\t"); // Assuming tab-separated columns
892
+ const outputObject = {};
893
+ // Extract values for each file, including `id${this.getFileExtension(tableName)}`
894
+ filesPathes.forEach((fileName, index) => {
895
+ const field = Utils.getField(parse(fileName).name, schema);
896
+ if (field)
897
+ outputObject[field.key] = File.decode(splitedFileColumns[index], field?.type, field?.children, this.salt);
898
+ });
899
+ return outputObject;
900
+ });
901
+ const restOfColumns = await this.get(tableName, outputArray.map(({ id }) => id), (({ sort, ...rest }) => rest)(options));
902
+ return restOfColumns
903
+ ? outputArray.map((item) => ({
904
+ ...item,
905
+ ...restOfColumns.find(({ id }) => id === item.id),
906
+ }))
907
+ : outputArray;
908
+ }
909
+ finally {
910
+ if (cacheKey)
911
+ await File.unlock(join(tablePath, ".tmp"), cacheKey);
912
+ }
913
+ }
824
914
  if (!where) {
825
915
  // Display all data
826
916
  RETURN = Object.values(await this.getItemsFromSchema(tableName, schema, Array.from({ length: options.perPage }, (_, index) => (options.page - 1) * options.perPage +
@@ -861,6 +951,11 @@ export default class Inibase {
861
951
  return Object.keys(lineNumbers).length
862
952
  ? Object.keys(lineNumbers).map(Number)
863
953
  : null;
954
+ if (options.columns) {
955
+ options.columns = options.columns.filter((column) => column !== "id");
956
+ if (!options.columns?.length)
957
+ options.columns = undefined;
958
+ }
864
959
  RETURN = Object.values((await this.getItemsFromSchema(tableName, schema, Object.keys(lineNumbers).map(Number), options)) ?? {});
865
960
  if (!this.totalItems[`${tableName}-*`])
866
961
  this.totalItems[`${tableName}-*`] = countItems;
@@ -1214,113 +1309,4 @@ export default class Inibase {
1214
1309
  }
1215
1310
  return RETURN;
1216
1311
  }
1217
- /**
1218
- * Sort column(s) of a table
1219
- *
1220
- * @param {string} tableName
1221
- * @param {(string
1222
- * | string[]
1223
- * | Record<string, 1 | -1 | "asc" | "ASC" | "desc" | "DESC">)} columns
1224
- * @param {(string | number | (string | number)[] | Criteria)} [where]
1225
- * @param {Options} [options={
1226
- * page: 1,
1227
- * perPage: 15,
1228
- * }]
1229
- */
1230
- async sort(tableName, columns, where, options = {
1231
- page: 1,
1232
- perPage: 15,
1233
- }) {
1234
- const tablePath = join(this.databasePath, tableName), schema = (await this.throwErrorIfTableEmpty(tableName)).schema;
1235
- // Default values for page and perPage
1236
- options.page = options.page || 1;
1237
- options.perPage = options.perPage || 15;
1238
- let sortArray, isLineNumbers = true, keepItems = [];
1239
- if (Utils.isObject(columns) && !Array.isArray(columns)) {
1240
- // {name: "ASC", age: "DESC"}
1241
- sortArray = Object.entries(columns).map(([key, value]) => [
1242
- key,
1243
- typeof value === "string" ? value.toLowerCase() === "asc" : value > 0,
1244
- ]);
1245
- }
1246
- else {
1247
- if (!Array.isArray(columns))
1248
- columns = [columns];
1249
- sortArray = columns.map((column) => [column, true]);
1250
- }
1251
- let cacheKey = "";
1252
- // Criteria
1253
- if (this.tables[tableName].config.cache)
1254
- cacheKey = UtilsServer.hashString(inspect(sortArray, { sorted: true }));
1255
- if (where) {
1256
- const lineNumbers = await this.get(tableName, where, undefined, undefined, true);
1257
- keepItems = Object.values((await File.get(join(tablePath, `id${this.getFileExtension(tableName)}`), lineNumbers, "number", undefined, this.salt)) ?? {}).map(Number);
1258
- isLineNumbers = false;
1259
- if (!keepItems.length)
1260
- throw this.throwError("NO_RESULTS", tableName);
1261
- keepItems = keepItems.slice((options.page - 1) * options.perPage, options.page * options.perPage);
1262
- }
1263
- if (!keepItems.length)
1264
- keepItems = Array.from({ length: options.perPage }, (_, index) => (options.page - 1) * options.perPage +
1265
- index +
1266
- 1);
1267
- const filesPathes = [["id", true], ...sortArray].map((column) => join(tablePath, `${column[0]}${this.getFileExtension(tableName)}`));
1268
- // Construct the paste command to merge files and filter lines by IDs
1269
- const pasteCommand = `paste ${filesPathes.join(" ")}`;
1270
- // Construct the sort command dynamically based on the number of files for sorting
1271
- const index = 2;
1272
- const sortColumns = sortArray
1273
- .map(([key, ascending], i) => {
1274
- const field = Utils.getField(key, schema);
1275
- if (field)
1276
- return `-k${i + index},${i + index}${field.type === "number" ? "n" : ""}${!ascending ? "r" : ""}`;
1277
- return "";
1278
- })
1279
- .join(" ");
1280
- const sortCommand = `sort ${sortColumns}`;
1281
- // Construct the awk command to keep only the specified lines after sorting
1282
- const awkCommand = isLineNumbers
1283
- ? `awk '${keepItems.map((line) => `NR==${line}`).join(" || ")}'`
1284
- : `awk 'NR==${keepItems[0]}${keepItems
1285
- .map((num) => `||NR==${num}`)
1286
- .join("")}'`;
1287
- try {
1288
- if (cacheKey)
1289
- await File.lock(join(tablePath, ".tmp"), cacheKey);
1290
- // Combine the commands
1291
- // Execute the command synchronously
1292
- const lines = (await UtilsServer.exec(this.tables[tableName].config.cache
1293
- ? (await File.isExists(join(tablePath, ".cache", `${cacheKey}${this.fileExtension}`)))
1294
- ? `${awkCommand} ${join(tablePath, ".cache", `${cacheKey}${this.fileExtension}`)}`
1295
- : `${pasteCommand} | ${sortCommand} -o ${join(tablePath, ".cache", `${cacheKey}${this.fileExtension}`)} && ${awkCommand} ${join(tablePath, ".cache", `${cacheKey}${this.fileExtension}`)}`
1296
- : `${pasteCommand} | ${sortCommand} | ${awkCommand}`, {
1297
- encoding: "utf-8",
1298
- })).stdout
1299
- .trim()
1300
- .split("\n");
1301
- // Parse the result and extract the specified lines
1302
- const outputArray = lines.map((line) => {
1303
- const splitedFileColumns = line.split("\t"); // Assuming tab-separated columns
1304
- const outputObject = {};
1305
- // Extract values for each file, including `id${this.getFileExtension(tableName)}`
1306
- filesPathes.forEach((fileName, index) => {
1307
- const field = Utils.getField(parse(fileName).name, schema);
1308
- if (field)
1309
- outputObject[field.key] = File.decode(splitedFileColumns[index], field?.type, field?.children, this.salt);
1310
- });
1311
- return outputObject;
1312
- });
1313
- const restOfColumns = await this.get(tableName, outputArray.map(({ id }) => id), options, undefined, undefined, true);
1314
- return restOfColumns
1315
- ? outputArray.map((item, index) => ({
1316
- ...item,
1317
- ...restOfColumns[index],
1318
- }))
1319
- : outputArray;
1320
- }
1321
- finally {
1322
- if (cacheKey)
1323
- await File.unlock(join(tablePath, ".tmp"), cacheKey);
1324
- }
1325
- }
1326
1312
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "inibase",
3
- "version": "1.0.0-rc.75",
3
+ "version": "1.0.0-rc.77",
4
4
  "type": "module",
5
5
  "author": {
6
6
  "name": "Karim Amahtil",