@salespark/toolkit 2.1.14 → 2.1.16

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): SalesParkContract<object>`** — 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: { status: true, data: "..." }
844
+
845
+ const original = descrambleString(scrambled.data, "secret");
846
+ // Result: { status: true, data: "Hello" }
847
+ ```
848
+
849
+ **`descrambleString(value: string, secret: string): SalesParkContract<object>`** — Reverses `scrambleString` using the same secret.
850
+
851
+ **`encodeObject(input: object, secret: string): SalesParkContract<object>`** — 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: { status: true, data: "..." }
856
+
857
+ const decoded = decodeObject(encoded.data, "secret");
858
+ // Result: { status: true, data: { id: 1, name: "Ana" } }
859
+ ```
860
+
861
+ **`decodeObject(encoded: string, secret: string): SalesParkContract<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,132 @@ 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
+ try {
1828
+ if (typeof value !== "string") {
1829
+ return { status: false, data: "Value must be a string" };
1830
+ }
1831
+ if (!secret || typeof secret !== "string") {
1832
+ return { status: false, data: "Secret must be a non-empty string" };
1833
+ }
1834
+ let result = "";
1835
+ for (let i = 0; i < value.length; i++) {
1836
+ const charCode = value.charCodeAt(i) & 255;
1837
+ const keyCode = secret.charCodeAt(i % secret.length) & 255;
1838
+ result += String.fromCharCode(charCode ^ keyCode);
1839
+ }
1840
+ return { status: true, data: base64EncodeBinary(result) };
1841
+ } catch (error) {
1842
+ return { status: false, data: error };
1843
+ }
1844
+ };
1845
+ var descrambleString = (value, secret) => {
1846
+ try {
1847
+ if (typeof value !== "string") {
1848
+ return { status: false, data: "Value must be a string" };
1849
+ }
1850
+ if (!secret || typeof secret !== "string") {
1851
+ return { status: false, data: "Secret must be a non-empty string" };
1852
+ }
1853
+ const decoded = base64DecodeToBinary(value);
1854
+ let result = "";
1855
+ for (let i = 0; i < decoded.length; i++) {
1856
+ const charCode = decoded.charCodeAt(i) & 255;
1857
+ const keyCode = secret.charCodeAt(i % secret.length) & 255;
1858
+ result += String.fromCharCode(charCode ^ keyCode);
1859
+ }
1860
+ return { status: true, data: result };
1861
+ } catch (error) {
1862
+ return { status: false, data: error };
1863
+ }
1864
+ };
1865
+ var encodeObject = (input, secret) => {
1866
+ try {
1867
+ if (!input || typeof input !== "object") {
1868
+ return { status: false, data: "Input must be an object" };
1869
+ }
1870
+ if (!secret || typeof secret !== "string") {
1871
+ return { status: false, data: "Secret must be a non-empty string" };
1872
+ }
1873
+ const jsonString = JSON.stringify(input);
1874
+ const base64 = toBase64(jsonString);
1875
+ const scrambledResponse = scrambleString(base64, secret);
1876
+ if (!scrambledResponse.status) {
1877
+ return { status: false, data: "Scrambling failed" };
1878
+ }
1879
+ return { status: true, data: scrambledResponse.data };
1880
+ } catch (error) {
1881
+ return { status: false, data: error };
1882
+ }
1883
+ };
1884
+ var decodeObject = (encoded, secret) => {
1885
+ try {
1886
+ if (typeof encoded !== "string") {
1887
+ return { status: false, data: "Encoded value must be a string" };
1888
+ }
1889
+ if (!secret || typeof secret !== "string") {
1890
+ return { status: false, data: "Secret must be a non-empty string" };
1891
+ }
1892
+ const descrambledResponse = descrambleString(encoded, secret);
1893
+ if (!descrambledResponse.status) {
1894
+ return { status: false, data: "Descrambling failed" };
1895
+ }
1896
+ const jsonString = fromBase64(descrambledResponse.data);
1897
+ return { status: true, data: JSON.parse(jsonString) };
1898
+ } catch (error) {
1899
+ return { status: false, data: error };
1900
+ }
1901
+ };
1902
+
1748
1903
  // src/utils/defer.ts
1749
1904
  var swallow = (p) => p.catch(() => {
1750
1905
  });
@@ -1849,12 +2004,15 @@ exports.compact = compact;
1849
2004
  exports.currencyToSymbol = currencyToSymbol;
1850
2005
  exports.debounce = debounce;
1851
2006
  exports.deburr = deburr;
2007
+ exports.decodeObject = decodeObject;
1852
2008
  exports.deferAfterResponse = deferAfterResponse;
1853
2009
  exports.deferAfterResponseNonCritical = deferAfterResponseNonCritical;
1854
2010
  exports.deferNonCritical = deferNonCritical;
1855
2011
  exports.deferPostReturn = deferPostReturn;
1856
2012
  exports.delay = delay;
2013
+ exports.descrambleString = descrambleString;
1857
2014
  exports.difference = difference;
2015
+ exports.encodeObject = encodeObject;
1858
2016
  exports.fill = fill;
1859
2017
  exports.flatten = flatten;
1860
2018
  exports.flattenDepth = flattenDepth;
@@ -1862,6 +2020,7 @@ exports.flattenDepthBase = flattenDepthBase;
1862
2020
  exports.flattenOnce = flattenOnce;
1863
2021
  exports.formatBytes = formatBytes;
1864
2022
  exports.formatCurrency = formatCurrency;
2023
+ exports.formatCurrencyPro = formatCurrencyPro;
1865
2024
  exports.formatDecimalNumber = formatDecimalNumber;
1866
2025
  exports.getStringSimilarity = getStringSimilarity;
1867
2026
  exports.groupBy = groupBy;
@@ -1913,6 +2072,7 @@ exports.safeParseInt = safeParseInt;
1913
2072
  exports.safeSubtract = safeSubtract;
1914
2073
  exports.sanitize = sanitize;
1915
2074
  exports.sanitizeMarkdown = sanitizeMarkdown;
2075
+ exports.scrambleString = scrambleString;
1916
2076
  exports.sentenceCase = sentenceCase;
1917
2077
  exports.shuffle = shuffle;
1918
2078
  exports.slugify = slugify;