inibase 3.4.0 → 3.5.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.
Files changed (2) hide show
  1. package/dist/file.js +36 -12
  2. package/package.json +2 -2
package/dist/file.js CHANGED
@@ -377,7 +377,33 @@ export const isExists = async (path) => {
377
377
  }
378
378
  };
379
379
  /**
380
- * Secures input by encoding/escaping characters.
380
+ * Marks a cell written with the lossless string escaping below. It is a
381
+ * control character that never starts a legacy cell in practice, is not
382
+ * `[`/`{` (so the value is never mistaken for Inison) and is not numeric.
383
+ */
384
+ const ESCAPED_STRING_PREFIX = "\u0001";
385
+ /**
386
+ * Lossless escaping for strings that the legacy newline-only escaping cannot
387
+ * round-trip: `\` becomes `\\`, CR becomes `\r`, LF becomes `\n`.
388
+ */
389
+ const escapeString = (input) => ESCAPED_STRING_PREFIX +
390
+ input.replace(/[\\\r\n]/g, (char) => char === "\\" ? "\\\\" : char === "\r" ? "\\r" : "\\n");
391
+ /** Reverses {@link escapeString} (input includes the prefix). */
392
+ const unescapeString = (input) => input
393
+ .slice(ESCAPED_STRING_PREFIX.length)
394
+ .replace(/\\([\\rn])/g, (_match, char) => char === "\\" ? "\\" : char === "r" ? "\r" : "\n");
395
+ /**
396
+ * Secures input by encoding/escaping characters so a value fits on one line
397
+ * of a column file and reads back exactly as it was written.
398
+ *
399
+ * - The string is stored as given: it is NOT URI-decoded (`%20`, `50%`,
400
+ * `%D8%A7` are data, not transport encoding).
401
+ * - Strings without `\` or CR keep the legacy format (LF stored as `\n`), so
402
+ * existing files, native whole-line search and most new writes are
403
+ * byte-identical to before.
404
+ * - Strings containing `\` or CR (or starting with the marker) use the
405
+ * lossless format, because in the legacy format a literal `\n` typed by the
406
+ * user is indistinguishable from an escaped line break.
381
407
  *
382
408
  * @param input - String, number, boolean, or null.
383
409
  * @returns Encoded string for true/false, special characters in strings, or original input.
@@ -390,15 +416,11 @@ const secureString = (input) => {
390
416
  return "";
391
417
  return input;
392
418
  }
393
- let decodedInput = null;
394
- try {
395
- decodedInput = decodeURIComponent(input);
396
- }
397
- catch (_error) {
398
- decodedInput = decodeURIComponent(input.replace(/%(?![0-9][0-9a-fA-F]+)/g, ""));
399
- }
400
- // Replace characters using a single regular expression.
401
- return decodedInput.replace(/\r\n|\r|\n/g, "\\n");
419
+ if (input.includes("\\") ||
420
+ input.includes("\r") ||
421
+ input.startsWith(ESCAPED_STRING_PREFIX))
422
+ return escapeString(input);
423
+ return input.replace(/\n/g, "\\n");
402
424
  };
403
425
  /**
404
426
  * Encodes the input using 'secureString' and 'Inison.stringify' functions.
@@ -425,8 +447,10 @@ export const encode = (input) => Array.isArray(input)
425
447
  const unSecureString = (input) => {
426
448
  if (isNumber(input))
427
449
  return String(input).at(0) === "0" ? input : Number(input);
428
- // Fast path: the common case has no `\n` escape sequence, so avoid
429
- // allocating a replacement string (and a fresh RegExp) per cell.
450
+ if (typeof input === "string" && input.startsWith(ESCAPED_STRING_PREFIX))
451
+ return unescapeString(input);
452
+ // Legacy format. Fast path: the common case has no `\n` escape sequence,
453
+ // so avoid allocating a replacement string (and a fresh RegExp) per cell.
430
454
  if (typeof input === "string")
431
455
  return input.includes("\\n")
432
456
  ? input.replaceAll("\\n", "\n") || null
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "inibase",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
4
4
  "type": "module",
5
5
  "author": {
6
6
  "name": "Karim Amahtil",
@@ -88,7 +88,7 @@
88
88
  "benchmark": "./benchmark/run.js",
89
89
  "benchmark:durability": "tsx ./benchmark/durability.ts",
90
90
  "benchmark:computed": "tsx ./benchmark/computed.ts",
91
- "test": "tsx ./tests/inibase.test.ts && tsx ./tests/inibase.advanced.test.ts && tsx ./tests/transaction.test.ts && tsx ./tests/durability.test.ts && tsx ./tests/expression.test.ts && tsx ./tests/utils.test.ts && tsx ./tests/sum.nested.test.ts && tsx ./tests/computed.child.test.ts && tsx ./tests/hardening.test.ts",
91
+ "test": "tsx ./tests/inibase.test.ts && tsx ./tests/inibase.advanced.test.ts && tsx ./tests/transaction.test.ts && tsx ./tests/durability.test.ts && tsx ./tests/expression.test.ts && tsx ./tests/utils.test.ts && tsx ./tests/sum.nested.test.ts && tsx ./tests/computed.child.test.ts && tsx ./tests/hardening.test.ts && tsx ./tests/string.roundtrip.test.ts",
92
92
  "test:durability": "tsx ./tests/durability.test.ts",
93
93
  "test:transaction": "tsx ./tests/transaction.test.ts",
94
94
  "test:utils": "tsx ./tests/utils.test.ts",