inibase 3.6.0 → 3.7.0

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
@@ -88,6 +88,20 @@ export declare function get(filePath: string, lineNumbers: undefined | number |
88
88
  Record<number, string | number | boolean | null | undefined | (string | number | boolean | (string | number | boolean)[] | null)[]> | null,
89
89
  number
90
90
  ]>;
91
+ /**
92
+ * Reads only the requested line numbers of one column file and decodes each
93
+ * with `field`. Unlike the shell `sed -n` reader this stops as soon as the last
94
+ * requested line has been seen, so a point lookup never scans the column to
95
+ * EOF, and it decodes each column independently (no tab-delimited `paste`
96
+ * alignment to break on values that contain a literal tab). Compression is
97
+ * handled transparently: `.gz` columns are gunzipped on the fly.
98
+ *
99
+ * Every resource (readline, gunzip/source stream, file handle) is released on
100
+ * both the normal and the early-exit path.
101
+ */
102
+ export declare const getLines: (filePath: string, lineNumbers: number[], field?: Field & {
103
+ databasePath?: string;
104
+ }) => Promise<Record<number, ReturnType<typeof decode>>>;
91
105
  /**
92
106
  * Asynchronously replaces specific lines in a file based on the provided replacements map or string.
93
107
  *
@@ -146,7 +160,7 @@ export declare const remove: (filePath: string, linesToDelete: number | number[]
146
160
  */
147
161
  export declare const search: (filePath: string, operator: ComparisonOperator | ComparisonOperator[], comparedAtValue: string | number | boolean | null | (string | number | boolean | null)[], logicalOperator?: "and" | "or", searchIn?: Set<number>, field?: Field & {
148
162
  databasePath?: string;
149
- }, limit?: number, offset?: number, readWholeFile?: boolean) => Promise<[Record<number, string | number | boolean | null | undefined | (string | number | boolean | null)[]> | null, number, Set<number> | null]>;
163
+ }, limit?: number, offset?: number, readWholeFile?: boolean, earlyStop?: boolean) => Promise<[Record<number, string | number | boolean | null | undefined | (string | number | boolean | null)[]> | null, number, Set<number> | null]>;
150
164
  export declare const sum: (fp: string, ln?: number | number[]) => Promise<number>;
151
165
  export declare const avg: (fp: string, ln?: number | number[]) => Promise<number>;
152
166
  export declare const min: (fp: string, ln?: number | number[]) => Promise<number>;
package/dist/file.js CHANGED
@@ -343,6 +343,16 @@ export function escapeShellPath(filePath) {
343
343
  */
344
344
  let tempSeq = 0;
345
345
  const tempPathFor = (filePath) => filePath.replace(/([^/]+)\/?$/, `.tmp/$1.${process.pid}-${(tempSeq++).toString(36)}`);
346
+ /**
347
+ * Cleanup-and-rethrow for a failed column write. A partial temp file must never
348
+ * be left behind, and the caller must observe the failure so it can abort the
349
+ * operation instead of publishing a column that is missing a line — which would
350
+ * desync the pagination row count from the column files.
351
+ */
352
+ const failWrite = async (fileTempPath, error) => {
353
+ await unlink(fileTempPath).catch(() => { });
354
+ throw error;
355
+ };
346
356
  const _pipeline = async (filePath, rl, writeStream, transform) => {
347
357
  if (filePath.endsWith(".gz"))
348
358
  await pipeline(rl, transform, createGzip(), writeStream);
@@ -361,6 +371,30 @@ const createReadLineInternface = (filePath, fileHandle) => createInterface({
361
371
  : fileHandle.createReadStream(),
362
372
  crlfDelay: Number.POSITIVE_INFINITY,
363
373
  });
374
+ /**
375
+ * Like {@link createReadLineInternface} but also hands back a disposer that
376
+ * tears down the readline interface and both the source and (for `.gz`
377
+ * columns) gunzip streams. Sparse readers call the disposer in their `finally`
378
+ * so an early `break` — the whole point of a point lookup — cannot leave a read
379
+ * stream (and its libuv handle) dangling, which would keep the process alive.
380
+ */
381
+ const openReadline = (filePath, fileHandle) => {
382
+ const source = fileHandle.createReadStream();
383
+ const input = filePath.endsWith(".gz") ? source.pipe(createGunzip()) : source;
384
+ const rl = createInterface({
385
+ input,
386
+ crlfDelay: Number.POSITIVE_INFINITY,
387
+ });
388
+ return {
389
+ rl,
390
+ destroy: () => {
391
+ rl.close();
392
+ input.destroy();
393
+ if (input !== source)
394
+ source.destroy();
395
+ },
396
+ };
397
+ };
364
398
  /**
365
399
  * Checks if a file or directory exists at the specified path.
366
400
  *
@@ -619,6 +653,45 @@ export async function get(filePath, lineNumbers, field, readWholeFile = false) {
619
653
  await fileHandle?.close();
620
654
  }
621
655
  }
656
+ /**
657
+ * Reads only the requested line numbers of one column file and decodes each
658
+ * with `field`. Unlike the shell `sed -n` reader this stops as soon as the last
659
+ * requested line has been seen, so a point lookup never scans the column to
660
+ * EOF, and it decodes each column independently (no tab-delimited `paste`
661
+ * alignment to break on values that contain a literal tab). Compression is
662
+ * handled transparently: `.gz` columns are gunzipped on the fly.
663
+ *
664
+ * Every resource (readline, gunzip/source stream, file handle) is released on
665
+ * both the normal and the early-exit path.
666
+ */
667
+ export const getLines = async (filePath, lineNumbers, field) => {
668
+ const lines = {};
669
+ if (!lineNumbers.length)
670
+ return lines;
671
+ const wanted = new Set(lineNumbers);
672
+ const last = Math.max(...lineNumbers);
673
+ const config = field ?? {
674
+ key: "BLABLA",
675
+ type: "string",
676
+ };
677
+ const fileHandle = await open(filePath, "r");
678
+ const { rl, destroy } = openReadline(filePath, fileHandle);
679
+ try {
680
+ let linesCount = 0;
681
+ for await (const line of rl) {
682
+ linesCount++;
683
+ if (wanted.has(linesCount))
684
+ lines[linesCount] = decode(line, config);
685
+ if (linesCount >= last)
686
+ break;
687
+ }
688
+ return lines;
689
+ }
690
+ finally {
691
+ destroy();
692
+ await fileHandle.close().catch(() => { });
693
+ }
694
+ };
622
695
  /**
623
696
  * Asynchronously replaces specific lines in a file based on the provided replacements map or string.
624
697
  *
@@ -675,8 +748,8 @@ export const replace = async (filePath, replacements, totalItems) => {
675
748
  }));
676
749
  return [fileTempPath, filePath];
677
750
  }
678
- catch {
679
- return [fileTempPath, null];
751
+ catch (error) {
752
+ return failWrite(fileTempPath, error);
680
753
  }
681
754
  finally {
682
755
  // Ensure that file handles are closed, even if an error occurred
@@ -695,8 +768,8 @@ export const replace = async (filePath, replacements, totalItems) => {
695
768
  await exec(command);
696
769
  return [fileTempPath, filePath];
697
770
  }
698
- catch {
699
- return [fileTempPath, null];
771
+ catch (error) {
772
+ return failWrite(fileTempPath, error);
700
773
  }
701
774
  }
702
775
  }
@@ -736,8 +809,8 @@ export const replace = async (filePath, replacements, totalItems) => {
736
809
  }
737
810
  return [fileTempPath, filePath];
738
811
  }
739
- catch {
740
- return [fileTempPath, null];
812
+ catch (error) {
813
+ return failWrite(fileTempPath, error);
741
814
  }
742
815
  }
743
816
  return [];
@@ -759,10 +832,12 @@ export const append = async (filePath, data) => {
759
832
  await appendFile(fileTempPath, `${Array.isArray(data) ? data.join("\n") : data}\n`);
760
833
  }
761
834
  else {
762
- const escapedFileTempPath = escapeShellPath(fileTempPath);
763
- await exec(`echo '${(Array.isArray(data) ? data.join("\n") : data)
764
- .toString()
765
- .replace(/'/g, "\\'")}' | gzip - >> ${escapedFileTempPath}`);
835
+ // Append a complete gzip member. Readers gunzip every member in
836
+ // sequence (multi-member stream), and this avoids the shell's
837
+ // argument-length limits and single-quote escaping the old
838
+ // `echo … | gzip - >> file` pipeline suffered from.
839
+ const text = `${Array.isArray(data) ? data.join("\n") : data}\n`;
840
+ await appendFile(fileTempPath, await gzip(text));
766
841
  }
767
842
  }
768
843
  else
@@ -775,8 +850,8 @@ export const append = async (filePath, data) => {
775
850
  : `${Array.isArray(data) ? data.join("\n") : data}\n`);
776
851
  return [fileTempPath, filePath];
777
852
  }
778
- catch {
779
- return [fileTempPath, null];
853
+ catch (error) {
854
+ return failWrite(fileTempPath, error);
780
855
  }
781
856
  };
782
857
  /**
@@ -808,8 +883,8 @@ export const prepend = async (filePath, data) => {
808
883
  },
809
884
  }));
810
885
  }
811
- catch {
812
- return [fileTempPath, null];
886
+ catch (error) {
887
+ return failWrite(fileTempPath, error);
813
888
  }
814
889
  finally {
815
890
  // Ensure that file handles are closed, even if an error occurred
@@ -826,8 +901,8 @@ export const prepend = async (filePath, data) => {
826
901
  const escapedFileChildTempPath = escapeShellPath(fileChildTempPath);
827
902
  await exec(`cat ${escapedFileChildTempPath} ${escapedFilePath} > ${escapedFileTempPath}`);
828
903
  }
829
- catch {
830
- return [fileTempPath, null];
904
+ catch (error) {
905
+ return failWrite(fileTempPath, error);
831
906
  }
832
907
  finally {
833
908
  await unlink(fileChildTempPath);
@@ -843,8 +918,8 @@ export const prepend = async (filePath, data) => {
843
918
  ? await gzip(`${Array.isArray(data) ? data.join("\n") : data}\n`)
844
919
  : `${Array.isArray(data) ? data.join("\n") : data}\n`);
845
920
  }
846
- catch {
847
- return [fileTempPath, null];
921
+ catch (error) {
922
+ return failWrite(fileTempPath, error);
848
923
  }
849
924
  }
850
925
  return [fileTempPath, filePath];
@@ -874,8 +949,8 @@ export const remove = async (filePath, linesToDelete) => {
874
949
  await exec(command);
875
950
  return [fileTempPath, filePath];
876
951
  }
877
- catch {
878
- return [fileTempPath, null];
952
+ catch (error) {
953
+ return failWrite(fileTempPath, error);
879
954
  }
880
955
  };
881
956
  /**
@@ -1094,7 +1169,7 @@ async function searchEqualsNative(filePath, patterns, field, searchIn, comparedA
1094
1169
  *
1095
1170
  * Note: Decodes each line for comparison and can handle complex queries with multiple conditions.
1096
1171
  */
1097
- export const search = async (filePath, operator, comparedAtValue, logicalOperator, searchIn, field, limit, offset, readWholeFile) => {
1172
+ export const search = async (filePath, operator, comparedAtValue, logicalOperator, searchIn, field, limit, offset, readWholeFile, earlyStop) => {
1098
1173
  // Native fast path for exact-equality searches (whole-line `grep -x -F`).
1099
1174
  if (operator === "=" &&
1100
1175
  !Array.isArray(operator) &&
@@ -1116,6 +1191,7 @@ export const search = async (filePath, operator, comparedAtValue, logicalOperato
1116
1191
  let linesCount = 0;
1117
1192
  const linesNumbers = new Set();
1118
1193
  let fileHandle = null;
1194
+ let destroyStreams = () => { };
1119
1195
  const config = field ?? {
1120
1196
  key: "BLABLA",
1121
1197
  type: "string",
@@ -1131,7 +1207,8 @@ export const search = async (filePath, operator, comparedAtValue, logicalOperato
1131
1207
  // Open the file for reading.
1132
1208
  fileHandle = await open(filePath, "r");
1133
1209
  // Create a Readline interface to read the file line by line.
1134
- const rl = createReadLineInternface(filePath, fileHandle);
1210
+ const { rl, destroy } = openReadline(filePath, fileHandle);
1211
+ destroyStreams = destroy;
1135
1212
  // Iterate through each line in the file.
1136
1213
  for await (const line of rl) {
1137
1214
  // Increment the line count for each line.
@@ -1162,6 +1239,10 @@ export const search = async (filePath, operator, comparedAtValue, logicalOperato
1162
1239
  }
1163
1240
  // Store the decoded line in the result object.
1164
1241
  matchingLines[linesCount] = decodedLine;
1242
+ // Every requested match is now in hand: stop instead of
1243
+ // scanning the rest of the column (id -> line resolution).
1244
+ if (earlyStop && limit && linesNumbers.size >= limit)
1245
+ break;
1165
1246
  }
1166
1247
  }
1167
1248
  // Convert the Map to an object using Object.fromEntries and return the result.
@@ -1173,7 +1254,9 @@ export const search = async (filePath, operator, comparedAtValue, logicalOperato
1173
1254
  return [null, 0, null];
1174
1255
  }
1175
1256
  finally {
1176
- // Close the file handle in the finally block to ensure it is closed even if an error occurs.
1257
+ // Tear down the read streams (an early `break` must not leave a handle
1258
+ // open) and then the file handle, even if an error occurred.
1259
+ destroyStreams();
1177
1260
  await fileHandle?.close();
1178
1261
  }
1179
1262
  };
package/dist/index.d.ts CHANGED
@@ -252,14 +252,6 @@ export default class Inibase {
252
252
  private sameCellValue;
253
253
  private isSimpleField;
254
254
  private processSimpleField;
255
- /**
256
- * Batched read for top-level simple columns: a single `paste | sed` child
257
- * process returns every requested line of every column at once instead of
258
- * spawning one `sed`/`gunzip|sed` child process per column. Returning null
259
- * (e.g. when any column file is missing, or the shell command fails) makes
260
- * the caller fall back to the existing per-column reads.
261
- */
262
- private processSimpleFieldsBatch;
263
255
  private isArrayField;
264
256
  private processArrayField;
265
257
  private isObjectField;
package/dist/index.js CHANGED
@@ -110,6 +110,18 @@ function astHasHops(node) {
110
110
  return astHasHops(node.arg);
111
111
  }
112
112
  }
113
+ /**
114
+ * Await a batch of column-write promises and surface the first rejection.
115
+ * `Promise.allSettled` never short-circuits, so a swallowed failure would let
116
+ * the caller publish a partial column set — the pagination row count and the
117
+ * column line counts would diverge. Every batch that fills a `renameList` must
118
+ * be inspected through here before the journal/pagination is touched.
119
+ */
120
+ async function settleWrites(results) {
121
+ const failed = results.find((result) => result.status === "rejected");
122
+ if (failed)
123
+ throw failed.reason;
124
+ }
113
125
  /**
114
126
  * @param {string} database - Database name
115
127
  * @param {string} [mainFolder="."] - Main folder path
@@ -982,19 +994,39 @@ export default class Inibase {
982
994
  }
983
995
  async processSchemaData(tableName, schema, linesNumber, options, prefix) {
984
996
  const RETURN = {};
985
- // Fast path: read every top-level simple column in one `paste | sed`
986
- // child process instead of spawning one child process per column.
987
- // Nested (prefixed) schemas keep the per-column path.
997
+ // Sparse path: for an explicit finite selection, stream each top-level
998
+ // simple column only up to the last requested line (never to EOF) and
999
+ // decode it independently, instead of pasting every column through a
1000
+ // shell pipeline that always scans the whole table and can misalign on
1001
+ // values containing a literal tab. Nested (prefixed) schemas keep the
1002
+ // per-column path below.
988
1003
  let batchedKeys = null;
989
1004
  if (!prefix && linesNumber?.length) {
990
1005
  const simpleFields = schema.filter((field) => this.isSimpleField(field.type));
991
- const batched = await this.processSimpleFieldsBatch(tableName, simpleFields, linesNumber);
992
- if (batched) {
993
- batchedKeys = new Set(simpleFields.map((field) => field.key));
994
- for (const [line, row] of Object.entries(batched)) {
1006
+ const decodeID = globalConfig[this.databasePath].tables?.get(tableName)?.config
1007
+ .decodeID === true;
1008
+ const extension = this.getFileExtension(tableName);
1009
+ const reads = await Promise.all(simpleFields.map(async (field) => {
1010
+ const fieldPath = join(this.databasePath, tableName, `${field.key}${extension}`);
1011
+ if (!(await File.isExists(fieldPath)))
1012
+ return null;
1013
+ const items = await File.getLines(fieldPath, linesNumber, {
1014
+ ...field,
1015
+ type: field.key === "id" && decodeID ? "number" : field.type,
1016
+ databasePath: this.databasePath,
1017
+ });
1018
+ return { key: field.key, items };
1019
+ }));
1020
+ batchedKeys = new Set(simpleFields.map((field) => field.key));
1021
+ for (const read of reads) {
1022
+ if (!read)
1023
+ continue;
1024
+ for (const [line, value] of Object.entries(read.items)) {
1025
+ if (typeof value === "undefined")
1026
+ continue;
995
1027
  if (!RETURN[line])
996
1028
  RETURN[line] = {};
997
- Object.assign(RETURN[line], row);
1029
+ Object.assign(RETURN[line], { [read.key]: value });
998
1030
  }
999
1031
  }
1000
1032
  }
@@ -1658,78 +1690,6 @@ export default class Inibase {
1658
1690
  }
1659
1691
  }
1660
1692
  }
1661
- /**
1662
- * Batched read for top-level simple columns: a single `paste | sed` child
1663
- * process returns every requested line of every column at once instead of
1664
- * spawning one `sed`/`gunzip|sed` child process per column. Returning null
1665
- * (e.g. when any column file is missing, or the shell command fails) makes
1666
- * the caller fall back to the existing per-column reads.
1667
- */
1668
- async processSimpleFieldsBatch(tableName, fields, linesNumber) {
1669
- if (!fields.length || !linesNumber.length)
1670
- return null;
1671
- const decodeID = globalConfig[this.databasePath].tables?.get(tableName)?.config
1672
- .decodeID === true;
1673
- // Decode configs are computed once per column (the per-column read path
1674
- // builds one config per file too), not once per cell.
1675
- const cols = [];
1676
- for (const field of fields) {
1677
- const path = join(this.databasePath, tableName, `${field.key}${this.getFileExtension(tableName)}`);
1678
- if (!(await File.isExists(path)))
1679
- return null;
1680
- cols.push({
1681
- path,
1682
- key: field.key,
1683
- config: {
1684
- ...field,
1685
- type: field.key === "id" && decodeID ? "number" : field.type,
1686
- databasePath: this.databasePath,
1687
- },
1688
- });
1689
- }
1690
- const sortedLines = [...linesNumber].sort((a, b) => a - b);
1691
- const range = File._groupIntoRanges(sortedLines);
1692
- const files = cols.map(({ path }) => File.escapeShellPath(path)).join(" ");
1693
- const isGz = this.getFileExtension(tableName).endsWith(".gz");
1694
- // Each compressed column needs its own process substitution so the
1695
- // streams stay aligned column-by-column inside `paste`.
1696
- const pasteInputs = isGz
1697
- ? cols
1698
- .map(({ path }) => `<(gunzip -c ${File.escapeShellPath(path)})`)
1699
- .join(" ")
1700
- : files;
1701
- const command = isGz
1702
- ? `bash -c 'paste -d "\\t" ${pasteInputs} | sed -n "${range}"'`
1703
- : `paste -d'\\t' ${files} | sed -n '${range}'`;
1704
- let output;
1705
- try {
1706
- output = (await UtilsServer.exec(command)).stdout;
1707
- }
1708
- catch {
1709
- return null;
1710
- }
1711
- const outLines = output.trimEnd().split("\n");
1712
- const RETURN = {};
1713
- for (let i = 0; i < outLines.length && i < sortedLines.length; i++) {
1714
- const lineNo = sortedLines[i];
1715
- const cells = outLines[i].split("\t");
1716
- const row = {};
1717
- let added = false;
1718
- for (let c = 0; c < cols.length; c++) {
1719
- const raw = cells[c];
1720
- if (raw === undefined)
1721
- continue;
1722
- const value = File.decode(raw, cols[c].config);
1723
- if (value !== undefined) {
1724
- row[cols[c].key] = value;
1725
- added = true;
1726
- }
1727
- }
1728
- if (added)
1729
- RETURN[lineNo] = row;
1730
- }
1731
- return Object.keys(RETURN).length ? RETURN : null;
1732
- }
1733
1693
  // Helper function to check if the field type is array
1734
1694
  isArrayField(fieldType) {
1735
1695
  return ((Array.isArray(fieldType) &&
@@ -2768,7 +2728,11 @@ export default class Inibase {
2768
2728
  }
2769
2729
  }
2770
2730
  if (!lineNumbers) {
2771
- [lineNumbers, countItems] = await File.search(join(tablePath, `id${this.getFileExtension(tableName)}`), "[]", Ids.map((id) => Utils.isNumber(id) ? Number(id) : UtilsServer.decodeID(id)), undefined, undefined, { key: "BLABLA", type: "number" }, Ids.length, 0, !this.totalItems.has(`${tableName}-id`));
2731
+ [lineNumbers, countItems] = await File.search(join(tablePath, `id${this.getFileExtension(tableName)}`), "[]", Ids.map((id) => Utils.isNumber(id) ? Number(id) : UtilsServer.decodeID(id)), undefined, undefined, { key: "BLABLA", type: "number" }, Ids.length, 0,
2732
+ // The caller only needs the matching line numbers, not a
2733
+ // whole-table count: stop as soon as every requested id has
2734
+ // been found instead of scanning the column to EOF.
2735
+ false, true);
2772
2736
  }
2773
2737
  if (!lineNumbers)
2774
2738
  return null;
@@ -2915,10 +2879,10 @@ export default class Inibase {
2915
2879
  ? clonedData.toReversed()
2916
2880
  : clonedData
2917
2881
  : clonedData);
2918
- await Promise.allSettled(Object.entries(pathesContents).map(async ([path, content]) => renameList.push(globalConfig[this.databasePath].tables?.get(tableName)?.config
2882
+ await settleWrites(await Promise.allSettled(Object.entries(pathesContents).map(async ([path, content]) => renameList.push(globalConfig[this.databasePath].tables?.get(tableName)?.config
2919
2883
  .prepend
2920
2884
  ? await File.prepend(path, content)
2921
- : await File.append(path, content))));
2885
+ : await File.append(path, content)))));
2922
2886
  const newTotal = _totalItems + (Array.isArray(data) ? data.length : 1);
2923
2887
  const pagination = {
2924
2888
  from: paginationFilePath,
@@ -3060,7 +3024,7 @@ export default class Inibase {
3060
3024
  const evaluated = await this.evaluateComputedRows(tableName, computedPlan, mergedRows);
3061
3025
  this.mergeComputedLineRecords(tableName, computedPlan, evaluated, pathesContents, existing);
3062
3026
  }
3063
- await Promise.allSettled(Object.entries(pathesContents).map(async ([path, content]) => renameList.push(await File.replace(path, content, total))));
3027
+ await settleWrites(await Promise.allSettled(Object.entries(pathesContents).map(async ([path, content]) => renameList.push(await File.replace(path, content, total)))));
3064
3028
  if (this.activeTransaction) {
3065
3029
  // Stage instead of publishing: row count is unchanged so
3066
3030
  // there is no pagination rename to journal.
@@ -3157,7 +3121,7 @@ export default class Inibase {
3157
3121
  }
3158
3122
  this.mergeComputedLineRecords(tableName, computedPlan, evaluated, pathesContents, existing);
3159
3123
  }
3160
- await Promise.allSettled(Object.entries(pathesContents).map(async ([path, content]) => renameList.push(await File.replace(path, content))));
3124
+ await settleWrites(await Promise.allSettled(Object.entries(pathesContents).map(async ([path, content]) => renameList.push(await File.replace(path, content)))));
3161
3125
  if (this.activeTransaction) {
3162
3126
  await this.stageTxnOp(tableName, renameList, null);
3163
3127
  txnStaged = true;
@@ -3330,7 +3294,7 @@ export default class Inibase {
3330
3294
  const remaining = total - (Array.isArray(where) ? where.length : 1);
3331
3295
  if (total && remaining > 0) {
3332
3296
  this.idDensity.set(tableName, false);
3333
- await Promise.allSettled(files.map(async (file) => renameList.push(await File.remove(join(tablePath, file), where))));
3297
+ await settleWrites(await Promise.allSettled(files.map(async (file) => renameList.push(await File.remove(join(tablePath, file), where)))));
3334
3298
  const pagination = {
3335
3299
  from: paginationFilePath,
3336
3300
  to: join(tablePath, `${lastId}-${remaining}.pagination`),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "inibase",
3
- "version": "3.6.0",
3
+ "version": "3.7.0",
4
4
  "type": "module",
5
5
  "author": {
6
6
  "name": "Karim Amahtil",