Skip to content

Commit c21ca07

Browse files
ytkimirtiCahidArda
andauthored
feat: use uncrypto instead of crypto-js (#1375)
* feat: use uncrypto instead of crypto-js * fix: bump node versions in cf tests * fix: flaky test --------- Co-authored-by: CahidArda <cahidardaooz@hotmail.com>
1 parent 38747a9 commit c21ca07

File tree

7 files changed

+72
-21
lines changed

7 files changed

+72
-21
lines changed

.github/workflows/tests.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ jobs:
244244
- name: Setup nodejs
245245
uses: actions/setup-node@v3
246246
with:
247-
node-version: 18
247+
node-version: 20
248248

249249
- name: Setup Bun
250250
uses: oven-sh/setup-bun@v1
@@ -298,7 +298,7 @@ jobs:
298298
- name: Setup nodejs
299299
uses: actions/setup-node@v3
300300
with:
301-
node-version: 18
301+
node-version: 20
302302

303303
- name: Setup Bun
304304
uses: oven-sh/setup-bun@v1
@@ -336,7 +336,7 @@ jobs:
336336
- name: Setup nodejs
337337
uses: actions/setup-node@v3
338338
with:
339-
node-version: 18
339+
node-version: 20
340340

341341
- name: Setup Bun
342342
uses: oven-sh/setup-bun@v1
@@ -391,7 +391,7 @@ jobs:
391391
- name: Setup nodejs
392392
uses: actions/setup-node@v3
393393
with:
394-
node-version: 18
394+
node-version: 20
395395

396396
- name: Setup Bun
397397
uses: oven-sh/setup-bun@v1

bun.lockb

-1 KB
Binary file not shown.

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,6 @@
7676
"@biomejs/biome": "latest",
7777
"@commitlint/cli": "^19.3.0",
7878
"@commitlint/config-conventional": "^19.2.2",
79-
"@types/crypto-js": "^4.1.3",
8079
"@typescript-eslint/eslint-plugin": "8.4.0",
8180
"@typescript-eslint/parser": "8.4.0",
8281
"bun-types": "1.0.33",
@@ -88,6 +87,6 @@
8887
"typescript": "latest"
8988
},
9089
"dependencies": {
91-
"crypto-js": "^4.2.0"
90+
"uncrypto": "^0.1.3"
9291
}
9392
}

pkg/auto-pipeline.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,11 +172,12 @@ describe("Auto pipeline", () => {
172172
latencyLogging: false,
173173
enableAutoPipelining: true,
174174
});
175+
await redis.flushdb();
175176
// @ts-expect-error pipelineCounter is not in type but accessible
176177
expect(redis.pipelineCounter).toBe(0);
177178

178179
// following five commands are added to the pipeline
179-
void redis.flushdb();
180+
void redis.del("baz");
180181
void redis.incr("baz");
181182
void redis.incr("baz");
182183
void redis.set("foo", "bar");

pkg/script.test.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,23 @@ describe("create a new script", () => {
2121
});
2222

2323
describe("sha1", () => {
24-
test("calculates the correct sha1", () => {
24+
test("calculates the correct sha1", async () => {
2525
const redis = new Redis(client);
2626
const script = redis.createScript("The quick brown fox jumps over the lazy dog");
2727

28+
// Wait one tick
29+
await new Promise((resolve) => setTimeout(resolve, 0));
30+
2831
expect(script.sha1).toEqual("2fd4e1c67a2d28fced849ee1bb76e7391b93eb12");
2932
});
3033

31-
test("calculates the correct sha1 for empty string", () => {
34+
test("calculates the correct sha1 for empty string", async () => {
3235
const redis = new Redis(client);
3336
const script = redis.createScript("");
3437

38+
// Wait one tick
39+
await new Promise((resolve) => setTimeout(resolve, 0));
40+
3541
expect(script.sha1).toEqual("da39a3ee5e6b4b0d3255bfef95601890afd80709");
3642
});
3743
});

pkg/script.ts

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import Hex from "crypto-js/enc-hex.js";
2-
import sha1 from "crypto-js/sha1.js";
1+
import { subtle } from "uncrypto";
32
import type { Redis } from "./redis";
43
/**
54
* Creates a new script.
@@ -19,26 +18,46 @@ import type { Redis } from "./redis";
1918
*/
2019
export class Script<TResult = unknown> {
2120
public readonly script: string;
22-
public readonly sha1: string;
21+
/**
22+
* @deprecated This property is initialized to an empty string and will be set in the init method
23+
* asynchronously. Do not use this property immidiately after the constructor.
24+
*
25+
* This property is only exposed for backwards compatibility and will be removed in the
26+
* future major release.
27+
*/
28+
public sha1: string;
2329
private readonly redis: Redis;
2430

2531
constructor(redis: Redis, script: string) {
2632
this.redis = redis;
27-
this.sha1 = this.digest(script);
2833
this.script = script;
34+
this.sha1 = "";
35+
void this.init(script);
36+
}
37+
38+
/**
39+
* Initialize the script by computing its SHA-1 hash.
40+
*/
41+
private async init(script: string): Promise<void> {
42+
if (this.sha1) return;
43+
this.sha1 = await this.digest(script);
2944
}
3045

3146
/**
3247
* Send an `EVAL` command to redis.
3348
*/
3449
public async eval(keys: string[], args: string[]): Promise<TResult> {
50+
await this.init(this.script);
51+
3552
return await this.redis.eval(this.script, keys, args);
3653
}
3754

3855
/**
3956
* Calculates the sha1 hash of the script and then calls `EVALSHA`.
4057
*/
4158
public async evalsha(keys: string[], args: string[]): Promise<TResult> {
59+
await this.init(this.script);
60+
4261
return await this.redis.evalsha(this.sha1, keys, args);
4362
}
4463

@@ -49,6 +68,8 @@ export class Script<TResult = unknown> {
4968
* Following calls will be able to use the cached script
5069
*/
5170
public async exec(keys: string[], args: string[]): Promise<TResult> {
71+
await this.init(this.script);
72+
5273
const res = await this.redis.evalsha(this.sha1, keys, args).catch(async (error) => {
5374
if (error instanceof Error && error.message.toLowerCase().includes("noscript")) {
5475
return await this.redis.eval(this.script, keys, args);
@@ -61,7 +82,10 @@ export class Script<TResult = unknown> {
6182
/**
6283
* Compute the sha1 hash of the script and return its hex representation.
6384
*/
64-
private digest(s: string): string {
65-
return Hex.stringify(sha1(s));
85+
private async digest(s: string): Promise<string> {
86+
const data = new TextEncoder().encode(s);
87+
const hashBuffer = await subtle.digest("SHA-1", data);
88+
const hashArray = [...new Uint8Array(hashBuffer)];
89+
return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
6690
}
6791
}

pkg/scriptRo.ts

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import Hex from "crypto-js/enc-hex.js";
2-
import sha1 from "crypto-js/sha1.js";
1+
import { subtle } from "uncrypto";
32
import type { Redis } from "./redis";
43

54
/**
@@ -20,26 +19,43 @@ import type { Redis } from "./redis";
2019
*/
2120
export class ScriptRO<TResult = unknown> {
2221
public readonly script: string;
23-
public readonly sha1: string;
22+
/**
23+
* @deprecated This property is initialized to an empty string and will be set in the init method
24+
* asynchronously. Do not use this property immidiately after the constructor.
25+
*
26+
* This property is only exposed for backwards compatibility and will be removed in the
27+
* future major release.
28+
*/
29+
public sha1: string;
2430
private readonly redis: Redis;
2531

2632
constructor(redis: Redis, script: string) {
2733
this.redis = redis;
28-
this.sha1 = this.digest(script);
34+
this.sha1 = "";
2935
this.script = script;
36+
void this.init(script);
37+
}
38+
39+
private async init(script: string): Promise<void> {
40+
if (this.sha1) return;
41+
this.sha1 = await this.digest(script);
3042
}
3143

3244
/**
3345
* Send an `EVAL_RO` command to redis.
3446
*/
3547
public async evalRo(keys: string[], args: string[]): Promise<TResult> {
48+
await this.init(this.script);
49+
3650
return await this.redis.evalRo(this.script, keys, args);
3751
}
3852

3953
/**
4054
* Calculates the sha1 hash of the script and then calls `EVALSHA_RO`.
4155
*/
4256
public async evalshaRo(keys: string[], args: string[]): Promise<TResult> {
57+
await this.init(this.script);
58+
4359
return await this.redis.evalshaRo(this.sha1, keys, args);
4460
}
4561

@@ -50,6 +66,8 @@ export class ScriptRO<TResult = unknown> {
5066
* Following calls will be able to use the cached script
5167
*/
5268
public async exec(keys: string[], args: string[]): Promise<TResult> {
69+
await this.init(this.script);
70+
5371
const res = await this.redis.evalshaRo(this.sha1, keys, args).catch(async (error) => {
5472
if (error instanceof Error && error.message.toLowerCase().includes("noscript")) {
5573
return await this.redis.evalRo(this.script, keys, args);
@@ -62,7 +80,10 @@ export class ScriptRO<TResult = unknown> {
6280
/**
6381
* Compute the sha1 hash of the script and return its hex representation.
6482
*/
65-
private digest(s: string): string {
66-
return Hex.stringify(sha1(s));
83+
private async digest(s: string): Promise<string> {
84+
const data = new TextEncoder().encode(s);
85+
const hashBuffer = await subtle.digest("SHA-1", data);
86+
const hashArray = [...new Uint8Array(hashBuffer)];
87+
return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
6788
}
6889
}

0 commit comments

Comments
 (0)