@excofy/utils 2.4.0 → 2.5.1

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/index.cjs CHANGED
@@ -36,6 +36,7 @@ __export(index_exports, {
36
36
  cryptoUtils: () => cryptoUtils,
37
37
  generateCode: () => generateCode,
38
38
  htmlEntityDecode: () => htmlEntityDecode,
39
+ htmlToPlainText: () => sanitizeValue,
39
40
  numberUtils: () => number_exports,
40
41
  slugUtils: () => slug_exports,
41
42
  stringUtils: () => stringUtils
@@ -809,6 +810,19 @@ async function verifyDeterministicHash(inputCode, storedHash, secret) {
809
810
  }
810
811
  var cryptoUtils = {
811
812
  uuidV4: () => crypto.randomUUID(),
813
+ ulid: () => {
814
+ function randomChar() {
815
+ const chars = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
816
+ return chars[Math.floor(Math.random() * chars.length)];
817
+ }
818
+ const timestamp = Date.now();
819
+ const timestampStr = timestamp.toString(36).toUpperCase().padStart(10, "0");
820
+ let randomPart = "";
821
+ for (let i = 0; i < 16; i++) {
822
+ randomPart += randomChar();
823
+ }
824
+ return timestampStr + randomPart;
825
+ },
812
826
  hash: async (password, salt = 10) => {
813
827
  const importedKey = await crypto.subtle.importKey(
814
828
  "raw",
@@ -960,6 +974,7 @@ var divide = (numerator, denominator) => {
960
974
  cryptoUtils,
961
975
  generateCode,
962
976
  htmlEntityDecode,
977
+ htmlToPlainText,
963
978
  numberUtils,
964
979
  slugUtils,
965
980
  stringUtils
package/dist/index.d.cts CHANGED
@@ -6,6 +6,7 @@ type AllowedTag = Tag | {
6
6
  tag: Tag;
7
7
  attributes?: Attributes[];
8
8
  };
9
+ declare function sanitizeValue(value: string, allowedTags?: AllowedTag[]): string;
9
10
  declare function htmlEntityDecode(str?: string): string;
10
11
 
11
12
  type TMessage = {
@@ -122,6 +123,7 @@ interface IValidateAccessToken {
122
123
  }
123
124
  interface ICrypto {
124
125
  uuidV4: () => string;
126
+ ulid: () => string;
125
127
  hash: (password: string, salt: number) => Promise<string>;
126
128
  isMatch: (password: string, hash: string, salt?: number) => Promise<boolean>;
127
129
  generateAccessToken: (data: IGenerateAccessToken) => Promise<string>;
@@ -240,4 +242,4 @@ declare class ExpiredTokenError extends CryptoError {
240
242
  constructor(message?: string);
241
243
  }
242
244
 
243
- export { ExpiredTokenError, InvalidTokenError, createValidator, cryptoUtils, generateCode, htmlEntityDecode, number as numberUtils, slug as slugUtils, stringUtils };
245
+ export { ExpiredTokenError, InvalidTokenError, createValidator, cryptoUtils, generateCode, htmlEntityDecode, sanitizeValue as htmlToPlainText, number as numberUtils, slug as slugUtils, stringUtils };
package/dist/index.d.ts CHANGED
@@ -6,6 +6,7 @@ type AllowedTag = Tag | {
6
6
  tag: Tag;
7
7
  attributes?: Attributes[];
8
8
  };
9
+ declare function sanitizeValue(value: string, allowedTags?: AllowedTag[]): string;
9
10
  declare function htmlEntityDecode(str?: string): string;
10
11
 
11
12
  type TMessage = {
@@ -122,6 +123,7 @@ interface IValidateAccessToken {
122
123
  }
123
124
  interface ICrypto {
124
125
  uuidV4: () => string;
126
+ ulid: () => string;
125
127
  hash: (password: string, salt: number) => Promise<string>;
126
128
  isMatch: (password: string, hash: string, salt?: number) => Promise<boolean>;
127
129
  generateAccessToken: (data: IGenerateAccessToken) => Promise<string>;
@@ -240,4 +242,4 @@ declare class ExpiredTokenError extends CryptoError {
240
242
  constructor(message?: string);
241
243
  }
242
244
 
243
- export { ExpiredTokenError, InvalidTokenError, createValidator, cryptoUtils, generateCode, htmlEntityDecode, number as numberUtils, slug as slugUtils, stringUtils };
245
+ export { ExpiredTokenError, InvalidTokenError, createValidator, cryptoUtils, generateCode, htmlEntityDecode, sanitizeValue as htmlToPlainText, number as numberUtils, slug as slugUtils, stringUtils };
package/dist/index.js CHANGED
@@ -771,6 +771,19 @@ async function verifyDeterministicHash(inputCode, storedHash, secret) {
771
771
  }
772
772
  var cryptoUtils = {
773
773
  uuidV4: () => crypto.randomUUID(),
774
+ ulid: () => {
775
+ function randomChar() {
776
+ const chars = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
777
+ return chars[Math.floor(Math.random() * chars.length)];
778
+ }
779
+ const timestamp = Date.now();
780
+ const timestampStr = timestamp.toString(36).toUpperCase().padStart(10, "0");
781
+ let randomPart = "";
782
+ for (let i = 0; i < 16; i++) {
783
+ randomPart += randomChar();
784
+ }
785
+ return timestampStr + randomPart;
786
+ },
774
787
  hash: async (password, salt = 10) => {
775
788
  const importedKey = await crypto.subtle.importKey(
776
789
  "raw",
@@ -921,6 +934,7 @@ export {
921
934
  cryptoUtils,
922
935
  generateCode,
923
936
  htmlEntityDecode,
937
+ sanitizeValue as htmlToPlainText,
924
938
  number_exports as numberUtils,
925
939
  slug_exports as slugUtils,
926
940
  stringUtils
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@excofy/utils",
3
- "version": "2.4.0",
3
+ "version": "2.5.1",
4
4
  "description": "Biblioteca de utilitários para o Excofy",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -32,7 +32,7 @@
32
32
  "devDependencies": {
33
33
  "@biomejs/biome": "^1.9.4",
34
34
  "@types/big.js": "^6.2.2",
35
- "@types/node": "^24.0.3",
35
+ "@types/node": "^24.10.13",
36
36
  "tsup": "^8.5.0",
37
37
  "tsx": "^4.20.4",
38
38
  "typescript": "^5.8.3"
@@ -17,6 +17,7 @@ interface IValidateAccessToken {
17
17
 
18
18
  interface ICrypto {
19
19
  uuidV4: () => string;
20
+ ulid: () => string;
20
21
  hash: (password: string, salt: number) => Promise<string>;
21
22
  isMatch: (password: string, hash: string, salt?: number) => Promise<boolean>;
22
23
  generateAccessToken: (data: IGenerateAccessToken) => Promise<string>;
@@ -34,7 +35,7 @@ interface ICrypto {
34
35
  verifyDeterministicHash: (
35
36
  inputCode: string,
36
37
  storedHash: string,
37
- secret: string
38
+ secret: string,
38
39
  ) => Promise<boolean>;
39
40
  }
40
41
 
@@ -54,7 +55,7 @@ const signatureKey = async (AUTH_SIGN_SECRET: string) =>
54
55
  encoder.encode(AUTH_SIGN_SECRET),
55
56
  { name: 'HMAC', hash: 'SHA-256' },
56
57
  false,
57
- ['sign', 'verify']
58
+ ['sign', 'verify'],
58
59
  );
59
60
 
60
61
  const payloadKey = async (AUTH_PAYLOAD_SECRET: string) =>
@@ -63,7 +64,7 @@ const payloadKey = async (AUTH_PAYLOAD_SECRET: string) =>
63
64
  encoder.encode(AUTH_PAYLOAD_SECRET.substring(0, 32)),
64
65
  { name: 'AES-GCM', length: 256 },
65
66
  true,
66
- ['encrypt', 'decrypt']
67
+ ['encrypt', 'decrypt'],
67
68
  );
68
69
 
69
70
  /* --------------------- Payload (encrypt/decrypt) --------------------- */
@@ -80,7 +81,7 @@ const encryptPayload = async ({
80
81
  const buffer = await crypto.subtle.encrypt(
81
82
  { name: 'AES-GCM', iv },
82
83
  key,
83
- encoder.encode(payload)
84
+ encoder.encode(payload),
84
85
  );
85
86
 
86
87
  return `${toBase64(iv)}.${toBase64(buffer)}`;
@@ -99,7 +100,7 @@ const decryptPayload = async ({
99
100
  const buffer = await crypto.subtle.decrypt(
100
101
  { name: 'AES-GCM', iv: fromBase64(header).buffer as ArrayBuffer },
101
102
  key,
102
- fromBase64(payload).buffer as ArrayBuffer
103
+ fromBase64(payload).buffer as ArrayBuffer,
103
104
  );
104
105
 
105
106
  return decoder.decode(buffer);
@@ -115,14 +116,14 @@ const decryptPayload = async ({
115
116
  */
116
117
  export async function generateDeterministicHash(
117
118
  code: string,
118
- secret: string
119
+ secret: string,
119
120
  ): Promise<string> {
120
121
  const key = await crypto.subtle.importKey(
121
122
  'raw',
122
123
  encoder.encode(secret),
123
124
  { name: 'HMAC', hash: 'SHA-256' },
124
125
  false,
125
- ['sign']
126
+ ['sign'],
126
127
  );
127
128
 
128
129
  const signature = await crypto.subtle.sign('HMAC', key, encoder.encode(code));
@@ -143,7 +144,7 @@ export async function generateDeterministicHash(
143
144
  export async function verifyDeterministicHash(
144
145
  inputCode: string,
145
146
  storedHash: string,
146
- secret: string
147
+ secret: string,
147
148
  ): Promise<boolean> {
148
149
  const hash = await generateDeterministicHash(inputCode, secret);
149
150
  return hash === storedHash;
@@ -153,13 +154,30 @@ export async function verifyDeterministicHash(
153
154
  export const cryptoUtils: ICrypto = {
154
155
  uuidV4: (): string => crypto.randomUUID(),
155
156
 
157
+ ulid: (): string => {
158
+ function randomChar() {
159
+ const chars = '0123456789ABCDEFGHJKMNPQRSTVWXYZ'; // Crockford's Base32
160
+ return chars[Math.floor(Math.random() * chars.length)];
161
+ }
162
+
163
+ const timestamp = Date.now();
164
+ const timestampStr = timestamp.toString(36).toUpperCase().padStart(10, '0');
165
+
166
+ let randomPart = '';
167
+ for (let i = 0; i < 16; i++) {
168
+ randomPart += randomChar();
169
+ }
170
+
171
+ return timestampStr + randomPart;
172
+ },
173
+
156
174
  hash: async (password: string, salt = 10): Promise<string> => {
157
175
  const importedKey = await crypto.subtle.importKey(
158
176
  'raw',
159
177
  encoder.encode(password),
160
178
  { name: 'PBKDF2' },
161
179
  false,
162
- ['deriveBits']
180
+ ['deriveBits'],
163
181
  );
164
182
 
165
183
  const derivedKey = await crypto.subtle.deriveBits(
@@ -170,7 +188,7 @@ export const cryptoUtils: ICrypto = {
170
188
  hash: 'SHA-256',
171
189
  },
172
190
  importedKey,
173
- 256
191
+ 256,
174
192
  );
175
193
 
176
194
  return Array.from(new Uint8Array(derivedKey))
@@ -200,7 +218,7 @@ export const cryptoUtils: ICrypto = {
200
218
  const signatureBuffer = await crypto.subtle.sign(
201
219
  'HMAC',
202
220
  key,
203
- encoder.encode(payload)
221
+ encoder.encode(payload),
204
222
  );
205
223
  const signature = toBase64(signatureBuffer);
206
224
 
@@ -227,7 +245,7 @@ export const cryptoUtils: ICrypto = {
227
245
  'HMAC',
228
246
  key,
229
247
  fromBase64(signature).buffer as ArrayBuffer,
230
- encoder.encode(payloadDecrypted)
248
+ encoder.encode(payloadDecrypted),
231
249
  );
232
250
 
233
251
  if (!valid) throw new Error('Invalid access token');
package/src/index.ts CHANGED
@@ -1,5 +1,8 @@
1
1
  import { createValidator } from './helpers/validator';
2
- import { htmlEntityDecode } from './helpers/sanitize';
2
+ import {
3
+ htmlEntityDecode,
4
+ sanitizeValue as htmlToPlainText,
5
+ } from './helpers/sanitize';
3
6
  import { generateCode } from './helpers/generateCode';
4
7
  import { stringUtils } from './helpers/string';
5
8
  import { cryptoUtils } from './helpers/crypto';
@@ -9,6 +12,7 @@ import * as numberUtils from './helpers/number';
9
12
  export {
10
13
  createValidator,
11
14
  htmlEntityDecode,
15
+ htmlToPlainText,
12
16
  slugUtils,
13
17
  numberUtils,
14
18
  stringUtils,
@@ -0,0 +1,75 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { createValidator } from '../../src/helpers/validator';
4
+
5
+ describe('validator', () => {
6
+ describe('asNumber', () => {
7
+ it('deve converter uma string "10" para o número 10', () => {
8
+ type InputRaw = { limit: string };
9
+ type InputParsed = { limit: number };
10
+
11
+ const v = createValidator<InputRaw, InputParsed>();
12
+ v.setInputs({ limit: '10' });
13
+ v.validate('limit')
14
+ .isRequired('Limite é obrigatório')
15
+ .type('string', 'Limite deve ser uma string')
16
+ .asNumber('Limite deve ser um número válido');
17
+
18
+ const inputs = v.getSanitizedInputs();
19
+
20
+ assert.equal(inputs.limit, 10);
21
+ assert.equal(typeof inputs.limit, 'number');
22
+ assert.equal(v.hasErrors(), false);
23
+ });
24
+
25
+ it('deve adicionar erro quando o valor não pode ser convertido para número', () => {
26
+ type InputRaw = { limit: string };
27
+ type InputParsed = { limit: number };
28
+
29
+ const v = createValidator<InputRaw, InputParsed>();
30
+ v.setInputs({ limit: 'abc' });
31
+ v.validate('limit')
32
+ .isRequired('Limite é obrigatório')
33
+ .type('string', 'Limite deve ser uma string')
34
+ .asNumber('Limite deve ser um número válido');
35
+
36
+ assert.equal(v.hasErrors(), true);
37
+ const errors = v.getFlatErrors();
38
+ assert.equal(errors.limit[0].message, 'Limite deve ser um número válido');
39
+ });
40
+
41
+ it('deve converter um número diretamente', () => {
42
+ type InputRaw = { limit: number };
43
+ type InputParsed = { limit: number };
44
+
45
+ const v = createValidator<InputRaw, InputParsed>();
46
+ v.setInputs({ limit: 25 });
47
+ v.validate('limit')
48
+ .isRequired('Limite é obrigatório')
49
+ .type('number', 'Limite deve ser um número')
50
+ .asNumber('Limite deve ser um número válido');
51
+
52
+ const inputs = v.getSanitizedInputs();
53
+
54
+ assert.equal(inputs.limit, 25);
55
+ assert.equal(typeof inputs.limit, 'number');
56
+ assert.equal(v.hasErrors(), false);
57
+ });
58
+
59
+ it('deve lidar com valores opcionais quando não fornecidos', () => {
60
+ type InputRaw = { limit?: string };
61
+ type InputParsed = { limit?: number };
62
+
63
+ const v = createValidator<InputRaw, InputParsed>();
64
+ v.setInputs({});
65
+ v.validate('limit')
66
+ .isNotRequired()
67
+ .type('string', 'Limite deve ser uma string')
68
+ .asNumber('Limite deve ser um número válido');
69
+
70
+ assert.equal(v.hasErrors(), false);
71
+ const inputs = v.getSanitizedInputs();
72
+ assert.equal(inputs.limit, undefined);
73
+ });
74
+ });
75
+ });
package/tsconfig.json CHANGED
@@ -11,7 +11,8 @@
11
11
  "strict": true,
12
12
  "skipLibCheck": true,
13
13
  "resolveJsonModule": true,
14
- "isolatedModules": true
14
+ "isolatedModules": true,
15
+ "types": ["node"]
15
16
  },
16
17
  "include": ["src", "tests"],
17
18
  "exclude": ["dist", "node_modules", "**/*.test.ts"]