@salespark/toolkit 2.1.14 → 2.1.15

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
@@ -32,7 +32,7 @@ npm i @salespark/toolkit
32
32
  - **Defer utilities**: post-return microtask scheduling, non-critical timers, after-response hooks.
33
33
  - **Boolean utilities**: safe boolean conversion with common representations
34
34
  - **Validation utilities**: IBAN validator (ISO 13616), Portuguese tax ID validator
35
- - **Security utilities**: Markdown XSS protection, content sanitization, risk assessment
35
+ - **Security utilities**: Markdown XSS protection, content sanitization, risk assessment, obfuscation helpers
36
36
  - **Environment detection**: `isBrowser`, `isNode` runtime checks
37
37
 
38
38
  ---
@@ -135,7 +135,7 @@ uniqBy(
135
135
  { id: 2, name: "Jane" },
136
136
  { id: 1, name: "John" },
137
137
  ],
138
- (x) => x.id
138
+ (x) => x.id,
139
139
  );
140
140
  // Result: [{id: 1, name: 'John'}, {id: 2, name: 'Jane'}]
141
141
  ```
@@ -177,7 +177,7 @@ groupBy(
177
177
  { type: "fruit", name: "banana" },
178
178
  { type: "veggie", name: "carrot" },
179
179
  ],
180
- "type"
180
+ "type",
181
181
  );
182
182
  // Result: {fruit: [{type: 'fruit', name: 'apple'}, {type: 'fruit', name: 'banana'}], veggie: [{type: 'veggie', name: 'carrot'}]}
183
183
  ```
@@ -218,7 +218,7 @@ pluck(
218
218
  { name: "John", age: 30 },
219
219
  { name: "Jane", age: 25 },
220
220
  ],
221
- "name"
221
+ "name",
222
222
  );
223
223
  // Result: ['John', 'Jane']
224
224
  ```
@@ -290,7 +290,7 @@ cleanObject(
290
290
  note: "",
291
291
  tags: ["", "ok"],
292
292
  },
293
- true
293
+ true,
294
294
  );
295
295
  // Result: {name: 'John', tags: ['ok']}
296
296
  ```
@@ -651,6 +651,26 @@ formatCurrency(null);
651
651
  // Result: "0,00 €"
652
652
  ```
653
653
 
654
+ **`formatCurrencyPro(value: number | string | null | undefined, options?: { withoutCurrencySymbol?: boolean; currency?: string; locale?: string; redact?: boolean; redactChar?: string }): string`** — Same as `formatCurrency`, but uses a single options object and supports redaction.
655
+
656
+ ```javascript
657
+ // Same defaults as formatCurrency
658
+ formatCurrencyPro(1234.56);
659
+ // Result: "1234,56 €"
660
+
661
+ // Options object
662
+ formatCurrencyPro(1234.56, { currency: "USD", locale: "en-US" });
663
+ // Result: "$1,234.56"
664
+
665
+ // Redacted output (mask digits)
666
+ formatCurrencyPro(1234.56, { redact: true });
667
+ // Result: "****,** €"
668
+
669
+ // Custom redaction character
670
+ formatCurrencyPro(1234.56, { redact: true, redactChar: "•" });
671
+ // Result: "••••,•• €"
672
+ ```
673
+
654
674
  **`parseName(name: string | null | undefined): {firstName: string, lastName: string}`** — Extracts first and last name from a full name string. Handles single names, empty inputs, and multi-word names intelligently.
655
675
 
656
676
  ```javascript
@@ -767,7 +787,7 @@ checkMarkdownSecurity('<script>alert("xss")</script>');
767
787
 
768
788
  // Content with multiple threats
769
789
  checkMarkdownSecurity(
770
- '<iframe src="evil.com"></iframe><div onclick="bad()">test</div>'
790
+ '<iframe src="evil.com"></iframe><div onclick="bad()">test</div>',
771
791
  );
772
792
  // Result: Multiple risks detected, sorted by severity
773
793
  ```
@@ -816,6 +836,30 @@ assessSecurityRisks([]);
816
836
  // Result: { score: 0, level: "safe", recommendations: ["Content appears safe to use"] }
817
837
  ```
818
838
 
839
+ **`scrambleString(value: string, secret: string): string`** — Scrambles a string using a repeating secret (XOR) and Base64. Reversible obfuscation only (not crypto).
840
+
841
+ ```javascript
842
+ const scrambled = scrambleString("Hello", "secret");
843
+ // Result: "..." (base64)
844
+
845
+ const original = descrambleString(scrambled, "secret");
846
+ // Result: "Hello"
847
+ ```
848
+
849
+ **`descrambleString(value: string, secret: string): string`** — Reverses `scrambleString` using the same secret.
850
+
851
+ **`encodeObject(input: object, secret: string): string`** — JSON-stringifies an object, Base64-encodes it, and scrambles the result (obfuscation only).
852
+
853
+ ```javascript
854
+ const encoded = encodeObject({ id: 1, name: "Ana" }, "secret");
855
+ // Result: "..." (base64)
856
+
857
+ const decoded = decodeObject(encoded, "secret");
858
+ // Result: { id: 1, name: "Ana" }
859
+ ```
860
+
861
+ **`decodeObject(encoded: string, secret: string): object`** — Reverses `encodeObject` using the same secret.
862
+
819
863
  ### ✅ Validation Utilities
820
864
 
821
865
  **`isPTTaxId(value: string | number): boolean`** — Validates Portuguese Tax ID (NIF) with MOD-11 algorithm and format checking.
package/dist/index.cjs CHANGED
@@ -566,7 +566,7 @@ var formatBytes = (bytes, si = false, dp = 1, noSpace = false) => {
566
566
  value /= thresh;
567
567
  ++u;
568
568
  } while (Math.round(Math.abs(value) * r) / r >= thresh && u < units.length - 1);
569
- return `${value.toFixed(dp)}${noSpace ? "" : " "}${units[u]}`;
569
+ return `${value.toFixed(dp)}${noSpace === true ? "" : " "}${units[u]}`;
570
570
  };
571
571
  var humanFileSize = formatBytes;
572
572
  var levenshtein = (a, b) => {
@@ -658,6 +658,35 @@ var formatCurrency = (value, withoutCurrencySymbol = false, currency = "EUR", lo
658
658
  return withoutCurrencySymbol ? formatted : `${formatted} \u20AC`;
659
659
  }
660
660
  };
661
+ var formatCurrencyPro = (value, options = {}) => {
662
+ const resolvedWithoutSymbol = options.withoutCurrencySymbol === true;
663
+ const resolvedCurrency = options.currency ?? "EUR";
664
+ const resolvedLocale = options.locale ?? "pt-PT";
665
+ const redact = options.redact === true;
666
+ const redactChar = options.redactChar ? options.redactChar : "*";
667
+ try {
668
+ const numValue = value === void 0 || value === null || value === "" ? 0 : Number(value);
669
+ if (isNaN(numValue) || !isFinite(numValue)) {
670
+ const fallback = resolvedWithoutSymbol ? "0,00" : "0,00 \u20AC";
671
+ return redact ? fallback.replace(/\d/g, redactChar) : fallback;
672
+ }
673
+ const intlOptions = {
674
+ style: resolvedWithoutSymbol ? "decimal" : "currency",
675
+ currency: resolvedCurrency,
676
+ minimumFractionDigits: 2,
677
+ maximumFractionDigits: 2
678
+ };
679
+ const formatted = new Intl.NumberFormat(resolvedLocale, intlOptions).format(
680
+ numValue
681
+ );
682
+ return redact ? formatted.replace(/\d/g, redactChar) : formatted;
683
+ } catch (error) {
684
+ const numValue = Number(value) || 0;
685
+ const formatted = numValue.toFixed(2).replace(".", ",");
686
+ const fallback = resolvedWithoutSymbol ? formatted : `${formatted} \u20AC`;
687
+ return redact ? fallback.replace(/\d/g, redactChar) : fallback;
688
+ }
689
+ };
661
690
  var parseName = (name) => {
662
691
  try {
663
692
  if (name === void 0 || name === null || name === "") {
@@ -1745,6 +1774,109 @@ var assessSecurityRisks = (risks) => {
1745
1774
  return { score, level, recommendations };
1746
1775
  };
1747
1776
 
1777
+ // src/utils/scramble.ts
1778
+ var hasBuffer = typeof Buffer !== "undefined" && typeof Buffer.from === "function";
1779
+ var base64EncodeBinary = (binary) => {
1780
+ if (hasBuffer) {
1781
+ return Buffer.from(binary, "binary").toString("base64");
1782
+ }
1783
+ if (typeof btoa === "function") {
1784
+ return btoa(binary);
1785
+ }
1786
+ throw new Error("Base64 encoder not available");
1787
+ };
1788
+ var base64DecodeToBinary = (base64) => {
1789
+ if (hasBuffer) {
1790
+ return Buffer.from(base64, "base64").toString("binary");
1791
+ }
1792
+ if (typeof atob === "function") {
1793
+ return atob(base64);
1794
+ }
1795
+ throw new Error("Base64 decoder not available");
1796
+ };
1797
+ var utf8ToBinary = (value) => {
1798
+ if (hasBuffer) {
1799
+ return Buffer.from(value, "utf8").toString("binary");
1800
+ }
1801
+ if (typeof TextEncoder !== "undefined") {
1802
+ const bytes = new TextEncoder().encode(value);
1803
+ let binary = "";
1804
+ for (const b of bytes) {
1805
+ binary += String.fromCharCode(b);
1806
+ }
1807
+ return binary;
1808
+ }
1809
+ return value;
1810
+ };
1811
+ var binaryToUtf8 = (binary) => {
1812
+ if (hasBuffer) {
1813
+ return Buffer.from(binary, "binary").toString("utf8");
1814
+ }
1815
+ if (typeof TextDecoder !== "undefined") {
1816
+ const bytes = new Uint8Array(binary.length);
1817
+ for (let i = 0; i < binary.length; i++) {
1818
+ bytes[i] = binary.charCodeAt(i) & 255;
1819
+ }
1820
+ return new TextDecoder().decode(bytes);
1821
+ }
1822
+ return binary;
1823
+ };
1824
+ var toBase64 = (value) => base64EncodeBinary(utf8ToBinary(value));
1825
+ var fromBase64 = (value) => binaryToUtf8(base64DecodeToBinary(value));
1826
+ var scrambleString = (value, secret) => {
1827
+ if (typeof value !== "string") {
1828
+ throw new TypeError("Value must be a string");
1829
+ }
1830
+ if (!secret || typeof secret !== "string") {
1831
+ throw new TypeError("Secret must be a non-empty string");
1832
+ }
1833
+ let result = "";
1834
+ for (let i = 0; i < value.length; i++) {
1835
+ const charCode = value.charCodeAt(i) & 255;
1836
+ const keyCode = secret.charCodeAt(i % secret.length) & 255;
1837
+ result += String.fromCharCode(charCode ^ keyCode);
1838
+ }
1839
+ return base64EncodeBinary(result);
1840
+ };
1841
+ var descrambleString = (value, secret) => {
1842
+ if (typeof value !== "string") {
1843
+ throw new TypeError("Value must be a string");
1844
+ }
1845
+ if (!secret || typeof secret !== "string") {
1846
+ throw new TypeError("Secret must be a non-empty string");
1847
+ }
1848
+ const decoded = base64DecodeToBinary(value);
1849
+ let result = "";
1850
+ for (let i = 0; i < decoded.length; i++) {
1851
+ const charCode = decoded.charCodeAt(i) & 255;
1852
+ const keyCode = secret.charCodeAt(i % secret.length) & 255;
1853
+ result += String.fromCharCode(charCode ^ keyCode);
1854
+ }
1855
+ return result;
1856
+ };
1857
+ var encodeObject = (input, secret) => {
1858
+ if (!input || typeof input !== "object") {
1859
+ throw new TypeError("Input must be an object");
1860
+ }
1861
+ if (!secret || typeof secret !== "string") {
1862
+ throw new TypeError("Secret must be a non-empty string");
1863
+ }
1864
+ const jsonString = JSON.stringify(input);
1865
+ const base64 = toBase64(jsonString);
1866
+ return scrambleString(base64, secret);
1867
+ };
1868
+ var decodeObject = (encoded, secret) => {
1869
+ if (typeof encoded !== "string") {
1870
+ throw new TypeError("Encoded value must be a string");
1871
+ }
1872
+ if (!secret || typeof secret !== "string") {
1873
+ throw new TypeError("Secret must be a non-empty string");
1874
+ }
1875
+ const descrambled = descrambleString(encoded, secret);
1876
+ const jsonString = fromBase64(descrambled);
1877
+ return JSON.parse(jsonString);
1878
+ };
1879
+
1748
1880
  // src/utils/defer.ts
1749
1881
  var swallow = (p) => p.catch(() => {
1750
1882
  });
@@ -1849,12 +1981,15 @@ exports.compact = compact;
1849
1981
  exports.currencyToSymbol = currencyToSymbol;
1850
1982
  exports.debounce = debounce;
1851
1983
  exports.deburr = deburr;
1984
+ exports.decodeObject = decodeObject;
1852
1985
  exports.deferAfterResponse = deferAfterResponse;
1853
1986
  exports.deferAfterResponseNonCritical = deferAfterResponseNonCritical;
1854
1987
  exports.deferNonCritical = deferNonCritical;
1855
1988
  exports.deferPostReturn = deferPostReturn;
1856
1989
  exports.delay = delay;
1990
+ exports.descrambleString = descrambleString;
1857
1991
  exports.difference = difference;
1992
+ exports.encodeObject = encodeObject;
1858
1993
  exports.fill = fill;
1859
1994
  exports.flatten = flatten;
1860
1995
  exports.flattenDepth = flattenDepth;
@@ -1862,6 +1997,7 @@ exports.flattenDepthBase = flattenDepthBase;
1862
1997
  exports.flattenOnce = flattenOnce;
1863
1998
  exports.formatBytes = formatBytes;
1864
1999
  exports.formatCurrency = formatCurrency;
2000
+ exports.formatCurrencyPro = formatCurrencyPro;
1865
2001
  exports.formatDecimalNumber = formatDecimalNumber;
1866
2002
  exports.getStringSimilarity = getStringSimilarity;
1867
2003
  exports.groupBy = groupBy;
@@ -1913,6 +2049,7 @@ exports.safeParseInt = safeParseInt;
1913
2049
  exports.safeSubtract = safeSubtract;
1914
2050
  exports.sanitize = sanitize;
1915
2051
  exports.sanitizeMarkdown = sanitizeMarkdown;
2052
+ exports.scrambleString = scrambleString;
1916
2053
  exports.sentenceCase = sentenceCase;
1917
2054
  exports.shuffle = shuffle;
1918
2055
  exports.slugify = slugify;