Skip to content

refactor: remove shajs#11864

Merged
gioboa merged 12 commits intotypeorm:masterfrom
G0maa:refactor/remvoe-shajs
Dec 27, 2025
Merged

refactor: remove shajs#11864
gioboa merged 12 commits intotypeorm:masterfrom
G0maa:refactor/remvoe-shajs

Conversation

@G0maa
Copy link
Copy Markdown
Collaborator

@G0maa G0maa commented Dec 21, 2025

Description of change

Remove sha.js dependency, replace with built-in crypto module in node environments, and our RandomGenerator in other environments, internally RandomGenerator uses implementation from Locutus.

There was a commented function call in RandomGenerator that is unescape(), this line was replaced entirely with TextEncoder which is supported by both browsers and Hermes (RN/Expo). To be honest it shouldn't matter anyway the only case where the older implementation would fail (i.e. behave differently that proper SHA1 implementations) is if we use non-ASCII characters, I've looked into where we use RandomGenerator or hash function:

  1. DefaultNamingStrategy
  2. PathUtils
  3. DriverUtils

I don't think any of these instances should accept non-ASCII characters to begin with.

Such discrepancy in where we use RandomGenerator vs hash is up to the reviewer. Maybe I should refactor all instances of hash to use RandomGenerator and update RandomGenerator to use built-in crypto if existent, otherwise use Locutus implementation? let me know what you think.

I fixed some linting warnings along the way.

Pull-Request Checklist

  • Code is up-to-date with the master branch
  • This pull request links relevant issues as Fixes #00000
  • There are new or updated tests validating the change (tests/**.test.ts)
  • Documentation has been updated to reflect this change (docs/docs/**.md)

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Dec 21, 2025

commit: 24ed7a3

@G0maa G0maa marked this pull request as ready for review December 21, 2025 11:51
@G0maa G0maa requested review from alumni, gioboa and sgarner December 21, 2025 11:51
@qodo-free-for-open-source-projects
Copy link
Copy Markdown

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Dynamic require

The hash function uses dynamic require("node:crypto") which may cause issues with bundlers and tree-shaking. Consider importing crypto at the top level with a conditional check, or using a try-catch block to handle environments where crypto is not available.

const crypto = require("node:crypto") as typeof import("node:crypto")
const hashFunction = crypto.createHash("sha1")
TextEncoder compatibility

The implementation uses TextEncoder and spread operator with String.fromCharCode(...bytes) which could fail with large strings due to call stack size limits. The spread operator has a maximum argument limit that varies by JavaScript engine. Consider processing the bytes in chunks for robustness.

const bytes = new TextEncoder().encode(str)
str = String.fromCharCode(...bytes)
Type annotations

Variables i, j, and v are declared at line 25-26 but used in nested scopes throughout the function. While TypeScript allows this, it may be clearer to declare them in the appropriate scopes or ensure they are properly initialized before use to avoid potential undefined behavior.

let i: number
let v: number

@G0maa G0maa requested review from OSA413 and naorpeled December 21, 2025 11:52
@qodo-free-for-open-source-projects
Copy link
Copy Markdown

qodo-free-for-open-source-projects bot commented Dec 21, 2025

PR Code Suggestions ✨

Latest suggestions up to 24ed7a3

CategorySuggestion                                                                                                                                    Impact
General
Avoid repeated module loading

Move the require("node:crypto") call out of the hash function to the module
scope to avoid repeated module loading on each function call.

src/util/StringUtils.ts [134-141]

-if (isNode()) {
-    const crypto = require("node:crypto") as typeof import("node:crypto")
-    const hashFunction = crypto.createHash("sha1")
+let nodeCrypto: typeof import("node:crypto") | undefined
+if (IS_NODE) {
+    nodeCrypto = require("node:crypto")
+}
+
+// In hash function:
+if (nodeCrypto) {
+    const hashFunction = nodeCrypto.createHash("sha1")
     hashFunction.update(input, "utf8")
     sha1 = hashFunction.digest("hex")
 } else {
     sha1 = RandomGenerator.sha1(input)
 }
  • Apply / Chat
Suggestion importance[1-10]: 5

__

Why: The suggestion correctly points out that require("node:crypto") inside the hash function leads to repeated module lookups, and suggests a better pattern of loading it once at the module level for improved performance.

Low
Cache environment detection result

Replace the isNode() function with a module-level constant to cache the
environment check result, avoiding redundant checks inside the hash function.

src/util/StringUtils.ts [117-119]

-function isNode(): boolean {
-    return typeof process !== "undefined" && !!process.versions?.node
-}
+const IS_NODE = typeof process !== "undefined" && !!process.versions?.node
  • Apply / Chat
Suggestion importance[1-10]: 4

__

Why: The suggestion correctly identifies a micro-optimization by caching the result of the environment check, which improves performance if hash() is called frequently.

Low
  • More

Previous suggestions

Suggestions up to commit 4625f29
CategorySuggestion                                                                                                                                    Impact
General
Use existing platform type check

Refactor the isNode() function to use the existing PlatformTools.type check for
consistency with the established platform abstraction pattern.

src/util/StringUtils.ts [118-120]

 function isNode(): boolean {
-    return typeof process !== "undefined" && !!process.versions?.node
+    return PlatformTools.type === "node"
 }
Suggestion importance[1-10]: 6

__

Why: This suggestion correctly points out that using PlatformTools.type aligns with the existing platform detection pattern in the codebase, improving consistency and maintainability.

Low
Add error handling for crypto module

Wrap the require("node:crypto") call in a try-catch block to gracefully handle
environments where the module may be unavailable, falling back to
RandomGenerator.sha1().

src/util/StringUtils.ts [135-142]

 if (isNode()) {
-    const crypto = require("node:crypto") as typeof import("node:crypto")
-    const hashFunction = crypto.createHash("sha1")
-    hashFunction.update(input, "utf8")
-    sha1 = hashFunction.digest("hex")
+    try {
+        const crypto = require("node:crypto") as typeof import("node:crypto")
+        const hashFunction = crypto.createHash("sha1")
+        hashFunction.update(input, "utf8")
+        sha1 = hashFunction.digest("hex")
+    } catch {
+        sha1 = RandomGenerator.sha1(input)
+    }
 } else {
     sha1 = RandomGenerator.sha1(input)
 }
Suggestion importance[1-10]: 5

__

Why: The suggestion improves robustness by adding error handling for require("node:crypto"), providing a fallback mechanism. However, this change introduces code duplication in the catch and else blocks.

Low
✅ Suggestions up to commit ed391a8
CategorySuggestion                                                                                                                                    Impact
Possible issue
Prevent stack overflow on large strings
Suggestion Impact:The suggestion's core issue was addressed, but through a better solution. Instead of converting bytes to a string and then using charCodeAt, the commit directly uses the bytes array throughout the code, completely eliminating the problematic String.fromCharCode(...bytes) line and avoiding the stack overflow issue entirely.

code diff:

         const bytes = new TextEncoder().encode(str)
-        str = String.fromCharCode(...bytes)
-
-        const strLen = str.length
+        const bytesLength = bytes.length
 
         const wordArray: number[] = []
-        for (i = 0; i < strLen - 3; i += 4) {
-            j =
-                (str.charCodeAt(i) << 24) |
-                (str.charCodeAt(i + 1) << 16) |
-                (str.charCodeAt(i + 2) << 8) |
-                str.charCodeAt(i + 3)
+        for (let i = 0; i < bytesLength - 3; i += 4) {
+            const j =
+                (bytes[i] << 24) |
+                (bytes[i + 1] << 16) |
+                (bytes[i + 2] << 8) |
+                bytes[i + 3]

To prevent a potential stack overflow with large strings, replace
String.fromCharCode(...bytes) with a loop that processes bytes iteratively.

src/util/RandomGenerator.ts [46-47]

 const bytes = new TextEncoder().encode(str)
-str = String.fromCharCode(...bytes)
+let encoded = ""
+for (let i = 0; i < bytes.length; i++) {
+    encoded += String.fromCharCode(bytes[i])
+}
+str = encoded

[Suggestion processed]

Suggestion importance[1-10]: 8

__

Why: This suggestion correctly identifies a critical "Maximum call stack size exceeded" error that will occur for large inputs, and the proposed fix resolves this potential runtime failure.

Medium
General
Improve Node.js environment detection
Suggestion Impact:The commit addressed the suggestion by removing the local isNode() function entirely and replacing it with PlatformTools.isNode(). This delegates the Node.js detection to a centralized platform utility, which is a better architectural approach than implementing the improved check inline. The suggestion's intent to improve Node.js detection reliability was implemented through refactoring to use existing platform detection infrastructure.

code diff:

@@ -1,3 +1,4 @@
+import { PlatformTools } from "../platform/PlatformTools"
 import { RandomGenerator } from "./RandomGenerator"
 
 /**
@@ -110,14 +111,6 @@
     return shortSegments.join(separator)
 }
 
-/**
- * Checks if the current environment is Node.js.
- * @returns `true` if the current environment is Node.js, `false` otherwise.
- */
-function isNode(): boolean {
-    return typeof process !== "undefined" && !!process.versions?.node
-}
-
 interface IHashOptions {
     length?: number
 }
@@ -131,7 +124,7 @@
  */
 export function hash(input: string, options: IHashOptions = {}): string {
     let sha1: string
-    if (isNode()) {
+    if (PlatformTools.isNode()) {

Improve the isNode() function's reliability by checking for process.release.name
=== 'node' to better distinguish a true Node.js environment.

src/util/StringUtils.ts [117-119]

 function isNode(): boolean {
-    return typeof process !== "undefined" && !!process.versions?.node
+    return typeof process !== "undefined" && 
+           process.release?.name === "node"
 }

[Suggestion processed]

Suggestion importance[1-10]: 6

__

Why: The suggestion correctly points out a way to make the Node.js environment check more robust, preventing potential runtime errors in environments that polyfill the process object.

Low
Suggestions up to commit aa95d53
CategorySuggestion                                                                                                                                    Impact
General
Add error handling for crypto module

Wrap the require("node:crypto") call in a try-catch block to handle cases where
the module is unavailable and fall back to the browser-based implementation.

src/util/StringUtils.ts [134-141]

 if (isNode()) {
-    const crypto = require("node:crypto") as typeof import("node:crypto")
-    const hashFunction = crypto.createHash("sha1")
-    hashFunction.update(input, "utf8")
-    sha1 = hashFunction.digest("hex")
+    try {
+        const crypto = require("node:crypto") as typeof import("node:crypto")
+        const hashFunction = crypto.createHash("sha1")
+        hashFunction.update(input, "utf8")
+        sha1 = hashFunction.digest("hex")
+    } catch {
+        sha1 = RandomGenerator.sha1(input)
+    }
 } else {
     sha1 = RandomGenerator.sha1(input)
 }
Suggestion importance[1-10]: 6

__

Why: This suggestion adds valuable defensive programming by handling potential runtime errors if the node:crypto module fails to load, even when isNode() returns true. This makes the hashing function more robust across different JavaScript runtimes or misconfigured environments.

Low
Improve Node.js environment detection
Suggestion Impact:The commit addressed the suggestion by removing the local isNode() function entirely and replacing it with PlatformTools.isNode(). This delegates the Node.js detection to a centralized platform utility, which is a better architectural approach than implementing the improved check inline. The suggestion's intent to improve Node.js detection reliability was implemented through refactoring to use existing platform detection infrastructure.

code diff:

@@ -1,3 +1,4 @@
+import { PlatformTools } from "../platform/PlatformTools"
 import { RandomGenerator } from "./RandomGenerator"
 
 /**
@@ -110,14 +111,6 @@
     return shortSegments.join(separator)
 }
 
-/**
- * Checks if the current environment is Node.js.
- * @returns `true` if the current environment is Node.js, `false` otherwise.
- */
-function isNode(): boolean {
-    return typeof process !== "undefined" && !!process.versions?.node
-}
-
 interface IHashOptions {
     length?: number
 }
@@ -131,7 +124,7 @@
  */
 export function hash(input: string, options: IHashOptions = {}): string {
     let sha1: string
-    if (isNode()) {
+    if (PlatformTools.isNode()) {

Improve the isNode() function by also checking for the existence of require to
more accurately detect a Node.js environment.

src/util/StringUtils.ts [117-119]

 function isNode(): boolean {
-    return typeof process !== "undefined" && !!process.versions?.node
+    return typeof process !== "undefined" && 
+           !!process.versions?.node && 
+           typeof require === "function"
 }
Suggestion importance[1-10]: 5

__

Why: This suggestion improves the robustness of the Node.js environment detection. While process.versions?.node is a strong indicator, adding a check for require makes the function more resilient against environments that might polyfill the process object.

Low
✅ Suggestions up to commit f26e205
CategorySuggestion                                                                                                                                    Impact
High-level
Consolidate hashing logic into one place

The suggestion is to consolidate the environment-specific hashing logic into
RandomGenerator.sha1. This would make it the single source for SHA-1 hashing,
preventing direct calls to the less performant browser-only implementation in
Node.js environments.

Examples:

src/util/StringUtils.ts [132-148]
export function hash(input: string, options: IHashOptions = {}): string {
    let sha1: string
    if (isNode()) {
        const crypto = require("node:crypto") as typeof import("node:crypto")
        const hashFunction = crypto.createHash("sha1")
        hashFunction.update(input, "utf8")
        sha1 = hashFunction.digest("hex")
    } else {
        sha1 = RandomGenerator.sha1(input)
    }

 ... (clipped 7 lines)
src/util/RandomGenerator.ts [16-168]
    static sha1(str: string) {
        const _rotLeft = function (n: number, s: number): number {
            const t4 = (n << s) | (n >>> (32 - s))
            return t4
        }

        const _cvtHex = function (val: number): string {
            let str = ""
            let i: number
            let v: number

 ... (clipped 143 lines)

Solution Walkthrough:

Before:

// src/util/StringUtils.ts
export function hash(input: string, options: IHashOptions = {}): string {
    let sha1: string
    if (isNode()) {
        const crypto = require("node:crypto")
        // ... use crypto to generate sha1
    } else {
        sha1 = RandomGenerator.sha1(input)
    }
    // ...
    return sha1
}

// src/util/RandomGenerator.ts
export class RandomGenerator {
    static sha1(str: string) {
        // ... pure JS SHA1 implementation ...
    }
}

After:

// src/util/StringUtils.ts
export function hash(input: string, options: IHashOptions = {}): string {
    const sha1 = RandomGenerator.sha1(input)
    if (options.length && options.length > 0) {
        return sha1.slice(0, options.length)
    }
    return sha1
}

// src/util/RandomGenerator.ts
export class RandomGenerator {
    static sha1(str: string) {
        if (isNode()) {
            const crypto = require("node:crypto")
            // ... use crypto to generate sha1
            return sha1
        } else {
            // ... pure JS SHA1 implementation ...
        }
    }
}
Suggestion importance[1-10]: 8

__

Why: This is a strong architectural suggestion that correctly identifies duplicated logic and potential for misuse, as the PR creates two separate hashing entry points. Centralizing the environment-aware logic into a single function improves maintainability and ensures optimal performance across all environments.

Medium
Possible issue
Prevent stack overflow on large inputs
Suggestion Impact:The commit addressed the stack overflow issue but took a more efficient approach. Instead of converting bytes to a string and then using charCodeAt, it directly uses the bytes array throughout the code, eliminating the need for String.fromCharCode entirely. This achieves the same goal of preventing stack overflow while also improving performance.

code diff:

         // utf8_encode
         const bytes = new TextEncoder().encode(str)
-        str = String.fromCharCode(...bytes)
-
-        const strLen = str.length
+        const bytesLength = bytes.length
 
         const wordArray: number[] = []
-        for (i = 0; i < strLen - 3; i += 4) {
-            j =
-                (str.charCodeAt(i) << 24) |
-                (str.charCodeAt(i + 1) << 16) |
-                (str.charCodeAt(i + 2) << 8) |
-                str.charCodeAt(i + 3)
+        for (let i = 0; i < bytesLength - 3; i += 4) {
+            const j =
+                (bytes[i] << 24) |
+                (bytes[i + 1] << 16) |
+                (bytes[i + 2] << 8) |
+                bytes[i + 3]
             wordArray.push(j)
         }
 
-        switch (strLen % 4) {
+        let i: number = 0
+        switch (bytesLength % 4) {
             case 0:
                 i = 0x080000000
                 break
             case 1:
-                i = (str.charCodeAt(strLen - 1) << 24) | 0x0800000
+                i = (bytes[bytesLength - 1] << 24) | 0x0800000
                 break
             case 2:
                 i =
-                    (str.charCodeAt(strLen - 2) << 24) |
-                    (str.charCodeAt(strLen - 1) << 16) |
+                    (bytes[bytesLength - 2] << 24) |
+                    (bytes[bytesLength - 1] << 16) |
                     0x08000
                 break
             case 3:
                 i =
-                    (str.charCodeAt(strLen - 3) << 24) |
-                    (str.charCodeAt(strLen - 2) << 16) |
-                    (str.charCodeAt(strLen - 1) << 8) |
+                    (bytes[bytesLength - 3] << 24) |
+                    (bytes[bytesLength - 2] << 16) |
+                    (bytes[bytesLength - 1] << 8) |

Replace String.fromCharCode(...bytes) with an iterative loop to build the
string, preventing a potential stack overflow error when hashing large strings.

src/util/RandomGenerator.ts [45-47]

 // utf8_encode
 const bytes = new TextEncoder().encode(str)
-str = String.fromCharCode(...bytes)
+let binary = ""
+for (let i = 0; i < bytes.length; i++) {
+    binary += String.fromCharCode(bytes[i])
+}
+str = binary
Suggestion importance[1-10]: 8

__

Why: This suggestion correctly identifies a potential "Maximum call stack size exceeded" error for large inputs and provides a robust solution, preventing a critical runtime failure.

Medium
General
Improve Node.js environment detection
Suggestion Impact:The commit addressed the suggestion by removing the local isNode() function entirely and replacing it with PlatformTools.isNode(). This delegates the Node.js detection to a centralized platform utility, which is a better architectural approach than implementing the improved check inline. The suggestion's intent to improve Node.js detection reliability was implemented through refactoring to use existing platform detection infrastructure.

code diff:

@@ -1,3 +1,4 @@
+import { PlatformTools } from "../platform/PlatformTools"
 import { RandomGenerator } from "./RandomGenerator"
 
 /**
@@ -110,14 +111,6 @@
     return shortSegments.join(separator)
 }
 
-/**
- * Checks if the current environment is Node.js.
- * @returns `true` if the current environment is Node.js, `false` otherwise.
- */
-function isNode(): boolean {
-    return typeof process !== "undefined" && !!process.versions?.node
-}
-
 interface IHashOptions {
     length?: number
 }
@@ -131,7 +124,7 @@
  */
 export function hash(input: string, options: IHashOptions = {}): string {
     let sha1: string
-    if (isNode()) {
+    if (PlatformTools.isNode()) {

Improve the isNode() function by checking for globalThis.process.release?.name
=== "node" to more reliably detect the Node.js environment and avoid issues with
bundler polyfills.

src/util/StringUtils.ts [117-119]

 function isNode(): boolean {
-    return typeof process !== "undefined" && !!process.versions?.node
+    return (
+        typeof globalThis.process !== "undefined" &&
+        globalThis.process.release?.name === "node"
+    )
 }
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly points out a potential fragility in the Node.js environment detection and proposes a more robust check, which improves the reliability of the new hashing logic.

Low

Copy link
Copy Markdown
Collaborator

@gioboa gioboa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @G0maa for your valuable comments.
It looks great to me, tests are passing 💪

@qodo-free-for-open-source-projects
Copy link
Copy Markdown

PR Code Suggestions ✨

No code suggestions found for the PR.

@G0maa G0maa requested a review from alumni December 27, 2025 10:01
@qodo-free-for-open-source-projects
Copy link
Copy Markdown

PR Code Suggestions ✨

No code suggestions found for the PR.

@coveralls
Copy link
Copy Markdown

coveralls commented Dec 27, 2025

Coverage Status

coverage: 80.91% (+0.001%) from 80.909%
when pulling 24ed7a3 on G0maa:refactor/remvoe-shajs
into f2547e1 on typeorm:master.

- Browser platform tools are in BrowserPlatformTools.template, putting it in PlatformTools may not work
@G0maa G0maa force-pushed the refactor/remvoe-shajs branch from 4625f29 to 24ed7a3 Compare December 27, 2025 12:04
@gioboa gioboa merged commit c52a5fd into typeorm:master Dec 27, 2025
64 checks passed
Cprakhar pushed a commit to Cprakhar/typeorm that referenced this pull request Dec 27, 2025
@alumni alumni mentioned this pull request Dec 30, 2025
19 tasks
Cprakhar pushed a commit to Cprakhar/typeorm that referenced this pull request Jan 7, 2026
mgohin pushed a commit to mgohin/typeorm that referenced this pull request Jan 15, 2026
@linbrandon4
Copy link
Copy Markdown

Nice cleanup overall. Dropping sha.js and leaning on node:crypto where it exists is a good move (fewer deps + less surface area). I also like that you kept behavior working outside Node by falling back to RandomGenerator / internal SHA1, and that you added more hashing tests instead of just refactoring and hoping nothing breaks.

A couple things I’d keep an eye on:

  • Environment handling / imports: the conditional or dynamic require("node:crypto") pattern can be touchy with bundlers. If this code path can ever be bundled for browser/RN, it’s worth making sure the detection is clean and doesn’t trigger crypto resolution at build time.
  • TextEncoder path: switching off unescape() is good, and TextEncoder is widely supported, but String.fromCharCode(...bytes) can hit argument limits if bytes ever gets large. Probably fine for naming/paths, but chunking would make it more robust.
  • Behavior consistency: you mention RandomGenerator vs hash usage across DefaultNamingStrategy, PathUtils, DriverUtils. I’d just make sure the same input produces the same hash everywhere (especially for migrations / naming), and that non-ASCII inputs won’t create surprising differences.

kranners pushed a commit to kranners/typeorm that referenced this pull request Mar 1, 2026
@pkuczynski pkuczynski added this to the 1.0 milestone Mar 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

7 participants