@salespark/toolkit 2.1.17 → 2.1.19

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,8 @@ 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, obfuscation helpers
35
+ - **Security utilities**: Markdown XSS protection, content sanitization, risk assessment, obfuscation helpers, reversible base36 code encoding/decoding
36
+ - **UUID utilities**: uuidv4 generator (RFC 4122)
36
37
  - **Environment detection**: `isBrowser`, `isNode` runtime checks
37
38
 
38
39
  ---
@@ -52,6 +53,7 @@ import {
52
53
  clamp,
53
54
  isBrowser,
54
55
  toBool,
56
+ uuidv4,
55
57
  } from "@salespark/toolkit";
56
58
 
57
59
  // Debounce a function
@@ -74,6 +76,9 @@ const safe = clamp(15, 0, 10); // 10
74
76
  // Convert to boolean
75
77
  const bool = toBool("yes"); // true
76
78
 
79
+ // Generate UUID v4
80
+ const id = uuidv4();
81
+
77
82
  // Check environment
78
83
  if (isBrowser) {
79
84
  /* browser-specific code */
@@ -102,6 +107,9 @@ const safe = _.clamp(15, 0, 10); // 10
102
107
  // Convert to boolean
103
108
  const bool = _.toBool("yes"); // true
104
109
 
110
+ // Generate UUID v4
111
+ const id = _.uuidv4();
112
+
105
113
  // Check environment
106
114
  if (_.isBrowser) {
107
115
  /* browser-specific code */
@@ -876,6 +884,50 @@ const decoded = decodeObject(encoded.data, "secret");
876
884
 
877
885
  **`decodeObject(encoded: string, secret: string): SalesParkContract<object>`** — Reverses `encodeObject` using the same secret.
878
886
 
887
+ **`encodeBase36Code(identifier: string, config: EncodeDecodeConfig): SalesParkContract<{ code: string }>`** — Encodes a base36 identifier into a reversible lower-case base36 code using secret-based XOR + rotation.
888
+
889
+ ```typescript
890
+ import { encodeBase36Code, decodeBase36Code } from "@salespark/toolkit";
891
+
892
+ const config = {
893
+ secret: "my-super-secret-key",
894
+ bitSize: 80,
895
+ rotateBits: 17,
896
+ addConstant: "0x1fd0a5b7c3",
897
+ };
898
+
899
+ const encoded = encodeBase36Code("AB12CD34", config);
900
+ // Result: { status: true, data: { code: "..." } }
901
+
902
+ const decoded = decodeBase36Code(encoded.data.code, config);
903
+ // Result: { status: true, data: { identifier: "AB12CD34" } }
904
+ ```
905
+
906
+ **`decodeBase36Code(code: string, config: EncodeDecodeConfig): SalesParkContract<{ identifier: string }>`** — Decodes a previously encoded base36 code back to the original identifier (upper-case).
907
+
908
+ ```typescript
909
+ const bad = encodeBase36Code("AB-12", {
910
+ secret: "my-super-secret-key",
911
+ });
912
+ // Result: { status: false, data: { message: "Identifier must be base36 (0-9, A-Z)" } }
913
+
914
+ const weakSecret = decodeBase36Code("abc123", {
915
+ secret: "short",
916
+ });
917
+ // Result: { status: false, data: { message: "Missing or weak secret" } }
918
+ ```
919
+
920
+ **`EncodeDecodeConfig`** — Configuration object used by `encodeBase36Code` and `decodeBase36Code`.
921
+
922
+ ```typescript
923
+ type EncodeDecodeConfig = {
924
+ secret: string; // required, minimum 12 chars
925
+ bitSize?: number; // default: 80
926
+ rotateBits?: number; // default: 17
927
+ addConstant?: string; // default: "0x1fd0a5b7c3"
928
+ };
929
+ ```
930
+
879
931
  ### ✅ Validation Utilities
880
932
 
881
933
  **`isPTTaxId(value: string | number): boolean`** — Validates Portuguese Tax ID (NIF) with MOD-11 algorithm and format checking.
@@ -1009,5 +1061,5 @@ MIT © [SalesPark](https://salespark.io)
1009
1061
 
1010
1062
  ---
1011
1063
 
1012
- _Document version: 14_
1013
- _Last update: 06-02-2026_
1064
+ _Document version: 16_
1065
+ _Last update: 14-03-2026_
package/dist/index.cjs CHANGED
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ var uuid = require('uuid');
4
+
3
5
  // src/utils/array.ts
4
6
  function uniqBy(arr, key) {
5
7
  const seen = /* @__PURE__ */ new Set();
@@ -2031,10 +2033,203 @@ var deferAfterResponseNonCritical = (res, fn) => {
2031
2033
  }
2032
2034
  };
2033
2035
 
2036
+ // src/utils/base36.ts
2037
+ var DEFAULTS = {
2038
+ bitSize: 80,
2039
+ rotateBits: 17,
2040
+ addConstant: "0x1fd0a5b7c3"
2041
+ };
2042
+ var isValidBase36 = (value) => /^[a-z0-9]+$/i.test(value);
2043
+ var normalizeInput = (value) => (value || "").trim();
2044
+ var assertSecret = (secret) => {
2045
+ if (typeof secret !== "string" || secret.trim().length < 12) {
2046
+ return { status: false, data: { message: "Missing or weak secret" } };
2047
+ }
2048
+ return { status: true, data: true };
2049
+ };
2050
+ var parseBase36BigInt = (value) => {
2051
+ try {
2052
+ const normalizedValue = normalizeInput(value);
2053
+ if (!normalizedValue) {
2054
+ return { status: false, data: { message: "Empty input" } };
2055
+ }
2056
+ if (!isValidBase36(normalizedValue)) {
2057
+ return { status: false, data: { message: "Invalid base36 input" } };
2058
+ }
2059
+ const safeValue = normalizedValue.toLowerCase();
2060
+ let output = 0n;
2061
+ for (let i = 0; i < safeValue.length; i++) {
2062
+ const char = safeValue[i];
2063
+ const digit = char >= "0" && char <= "9" ? BigInt(char.charCodeAt(0) - 48) : BigInt(char.charCodeAt(0) - 87);
2064
+ output = output * 36n + digit;
2065
+ }
2066
+ return { status: true, data: output };
2067
+ } catch (error) {
2068
+ return {
2069
+ status: false,
2070
+ data: { message: "Failed to parse base36 input", error }
2071
+ };
2072
+ }
2073
+ };
2074
+ var toBase36Lower = (value) => {
2075
+ try {
2076
+ if (value < 0n) {
2077
+ return {
2078
+ status: false,
2079
+ data: { message: "Negative values are not supported" }
2080
+ };
2081
+ }
2082
+ return { status: true, data: value.toString(36) };
2083
+ } catch (error) {
2084
+ return {
2085
+ status: false,
2086
+ data: { message: "Failed to convert to base36", error }
2087
+ };
2088
+ }
2089
+ };
2090
+ var getParams = (config) => {
2091
+ try {
2092
+ const bitSizeRaw = BigInt(config.bitSize ?? DEFAULTS.bitSize);
2093
+ if (bitSizeRaw <= 0n) {
2094
+ return {
2095
+ status: false,
2096
+ data: { message: "bitSize must be greater than 0" }
2097
+ };
2098
+ }
2099
+ const rotateRaw = BigInt(config.rotateBits ?? DEFAULTS.rotateBits);
2100
+ const rotateBits = (rotateRaw % bitSizeRaw + bitSizeRaw) % bitSizeRaw;
2101
+ const addConstant = BigInt(config.addConstant ?? DEFAULTS.addConstant);
2102
+ const mask = (1n << bitSizeRaw) - 1n;
2103
+ return {
2104
+ status: true,
2105
+ data: { bitSize: bitSizeRaw, rotateBits, addConstant, mask }
2106
+ };
2107
+ } catch (error) {
2108
+ return { status: false, data: { message: "Invalid configuration", error } };
2109
+ }
2110
+ };
2111
+ var secretToKey = (secret, mask) => {
2112
+ let key = 0n;
2113
+ const safeSecret = secret.trim();
2114
+ for (let i = 0; i < safeSecret.length; i++) {
2115
+ key = key * 131n + BigInt(safeSecret.charCodeAt(i)) & mask;
2116
+ }
2117
+ return key;
2118
+ };
2119
+ var rotl = (x, r, bitSize, mask) => {
2120
+ if (r === 0n) return x & mask;
2121
+ return (x << r | x >> bitSize - r) & mask;
2122
+ };
2123
+ var rotr = (x, r, bitSize, mask) => {
2124
+ if (r === 0n) return x & mask;
2125
+ return (x >> r | x << bitSize - r) & mask;
2126
+ };
2127
+ var encodeBase36Code = (identifier, config) => {
2128
+ try {
2129
+ const secretCheck = assertSecret(config?.secret);
2130
+ if (!secretCheck.status) {
2131
+ return { status: false, data: secretCheck.data };
2132
+ }
2133
+ const input = normalizeInput(identifier);
2134
+ if (!input) {
2135
+ return { status: false, data: { message: "Identifier is required" } };
2136
+ }
2137
+ if (!isValidBase36(input)) {
2138
+ return {
2139
+ status: false,
2140
+ data: { message: "Identifier must be base36 (0-9, A-Z)" }
2141
+ };
2142
+ }
2143
+ const parsed = parseBase36BigInt(input);
2144
+ if (!parsed.status) {
2145
+ return { status: false, data: parsed.data };
2146
+ }
2147
+ const params = getParams(config);
2148
+ if (!params.status) {
2149
+ return { status: false, data: params.data };
2150
+ }
2151
+ const {
2152
+ bitSize,
2153
+ rotateBits,
2154
+ addConstant,
2155
+ mask
2156
+ } = params.data;
2157
+ const key = secretToKey(config.secret, mask);
2158
+ let value = parsed.data & mask;
2159
+ value = value ^ key;
2160
+ value = value + addConstant & mask;
2161
+ value = rotl(value, rotateBits, bitSize, mask);
2162
+ const codeResult = toBase36Lower(value);
2163
+ if (!codeResult.status) {
2164
+ return { status: false, data: codeResult.data };
2165
+ }
2166
+ return { status: true, data: { code: codeResult.data } };
2167
+ } catch (error) {
2168
+ return {
2169
+ status: false,
2170
+ data: { message: "Failed to encode code", error }
2171
+ };
2172
+ }
2173
+ };
2174
+ var decodeBase36Code = (code, config) => {
2175
+ try {
2176
+ const secretCheck = assertSecret(config?.secret);
2177
+ if (!secretCheck.status) {
2178
+ return { status: false, data: secretCheck.data };
2179
+ }
2180
+ const input = normalizeInput(code);
2181
+ if (!input) {
2182
+ return { status: false, data: { message: "Code is required" } };
2183
+ }
2184
+ if (!isValidBase36(input)) {
2185
+ return {
2186
+ status: false,
2187
+ data: { message: "Code must be base36 (0-9, a-z)" }
2188
+ };
2189
+ }
2190
+ const parsed = parseBase36BigInt(input);
2191
+ if (!parsed.status) {
2192
+ return { status: false, data: parsed.data };
2193
+ }
2194
+ const params = getParams(config);
2195
+ if (!params.status) {
2196
+ return { status: false, data: params.data };
2197
+ }
2198
+ const {
2199
+ bitSize,
2200
+ rotateBits,
2201
+ addConstant,
2202
+ mask
2203
+ } = params.data;
2204
+ const key = secretToKey(config.secret, mask);
2205
+ let value = parsed.data & mask;
2206
+ value = rotr(value, rotateBits, bitSize, mask);
2207
+ value = value - addConstant & mask;
2208
+ value = value ^ key;
2209
+ const identifierResult = toBase36Lower(value);
2210
+ if (!identifierResult.status) {
2211
+ return { status: false, data: identifierResult.data };
2212
+ }
2213
+ return {
2214
+ status: true,
2215
+ data: { identifier: identifierResult.data.toUpperCase() }
2216
+ };
2217
+ } catch (error) {
2218
+ return {
2219
+ status: false,
2220
+ data: { message: "Failed to decode code", error }
2221
+ };
2222
+ }
2223
+ };
2224
+
2034
2225
  // src/index.ts
2035
2226
  var isBrowser = typeof globalThis !== "undefined" && typeof globalThis.document !== "undefined";
2036
2227
  var isNode = typeof process !== "undefined" && !!process.versions?.node;
2037
2228
 
2229
+ Object.defineProperty(exports, "uuidv4", {
2230
+ enumerable: true,
2231
+ get: function () { return uuid.v4; }
2232
+ });
2038
2233
  exports.addSpaceBetweenNumbers = addSpaceBetweenNumbers;
2039
2234
  exports.addThousandsSpace = addThousandsSpace;
2040
2235
  exports.areArraysDeepEqualUnordered = areArraysDeepEqualUnordered;
@@ -2051,6 +2246,7 @@ exports.compact = compact;
2051
2246
  exports.currencyToSymbol = currencyToSymbol;
2052
2247
  exports.debounce = debounce;
2053
2248
  exports.deburr = deburr;
2249
+ exports.decodeBase36Code = decodeBase36Code;
2054
2250
  exports.decodeObject = decodeObject;
2055
2251
  exports.decodeString = decodeString;
2056
2252
  exports.deferAfterResponse = deferAfterResponse;
@@ -2059,6 +2255,7 @@ exports.deferNonCritical = deferNonCritical;
2059
2255
  exports.deferPostReturn = deferPostReturn;
2060
2256
  exports.delay = delay;
2061
2257
  exports.difference = difference;
2258
+ exports.encodeBase36Code = encodeBase36Code;
2062
2259
  exports.encodeObject = encodeObject;
2063
2260
  exports.encodeString = encodeString;
2064
2261
  exports.fill = fill;