Skip to content

fix: load entity files through import when require cannot parse them - #12796

Open
Gauravtiwari31 wants to merge 1 commit into
typeorm:masterfrom
Gauravtiwari31:fix-issue-11570
Open

fix: load entity files through import when require cannot parse them#12796
Gauravtiwari31 wants to merge 1 commit into
typeorm:masterfrom
Gauravtiwari31:fix-issue-11570

Conversation

@Gauravtiwari31

Copy link
Copy Markdown

Description of change

Entities, migrations and subscribers passed as file globs (entities: ["./src/entity/*.ts"]) fail to load under Vitest with SyntaxError: Invalid or unexpected token. Listing every class by hand (entities: [User]) is currently the only workaround, which is reported as still broken in 0.3.25 through 0.3.28.

Cause: importOrRequireFile picks require for .ts/.js whenever the nearest package.json is not "type": "module". Vite based runners transform TypeScript for import(), but they leave require pointing at Node's plain CommonJS loader, which cannot parse TypeScript — so the file blows up before Vitest ever gets to transform it. Under ts-node/tsx this works only because those tools patch require itself.

Change: when require throws a SyntaxError, retry the file through import(). Vite based runners intercept dynamic imports, so the file is transformed and loads normally.

Two deliberate details:

  • Only SyntaxError triggers the retry. A module that requires fine, or that fails for any other reason (a missing dependency, a throwing side effect), behaves exactly as before — this cannot mask a runtime failure as a load failure.
  • If the retry also fails, the original require error is rethrown, not the one from import(). Otherwise a genuine syntax error in a user's entity file would surface on plain Node as ERR_UNKNOWN_FILE_EXTENSION ".ts", which says nothing about the actual mistake.

moduleType is reported as "esm" when the retry succeeds, which is what ConnectionOptionsReader needs in order to unwrap default off the module namespace.

Verification. Reproduced the runner's exact conditions — CommonJS loader unable to parse the file, import() able to — and ran the current and patched importOrRequireFile against it:

=== BEFORE ===
require()             -> SyntaxError: Invalid or unexpected token
importOrRequireFile() -> FAILED: SyntaxError: Invalid or unexpected token

=== AFTER ===
require()             -> SyntaxError: Invalid or unexpected token
importOrRequireFile() -> OK  moduleType=esm  exports=["default","value"]

Two tests were added to test/unit/util/import-utils.test.ts. The first stubs the CommonJS loader for one fixture file to throw the way Vitest does and asserts the module still loads as ESM — it fails on master and passes with this change. The second asserts the original require error survives when the retry cannot help; it guards the error-quality behaviour above rather than reproducing the bug, so it passes either way by design.

Full suite: 3046 passing, 0 failing on the default sqljs config. No documented behaviour changes, so no docs update.

Closes #11570

Pull-Request Checklist

  • Code is up-to-date with the master branch
  • This pull request links a relevant issue using a closing keyword:
    Fixes #NNNN, Closes #NNNN, or Resolves #NNNN
  • There are new or updated tests validating the change (tests/**.test.ts)
  • Documentation has been updated to reflect this change (docs/docs/**.md) — N/A, bug fix with no API or behaviour change to document

Entities, migrations and subscribers given as file globs are loaded with
`require` whenever the nearest package.json is not `"type": "module"`. Vite based
runners such as Vitest transform TypeScript for `import()` but leave `require` on
the plain CommonJS loader, so requiring a `.ts` file throws
`SyntaxError: Invalid or unexpected token` and the data source fails to start.
Listing every entity class by hand was the only workaround.

Retry with `import()` when `require` fails to parse the file. Runtimes that can
already require the file are unaffected, and when the retry does not help the
original require error is thrown instead of the less useful one from `import()`,
so a genuine syntax error in a user file stays readable.

Closes typeorm#11570
@github-actions github-actions Bot added the linked-issue PR references an issue label Aug 20, 2026
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Flaky ESM fallback test 🐞 Bug ☼ Reliability
Description
The new test creates a .js file containing ESM export syntax under a directory whose
package.json is {} (CommonJS), yet asserts it can be loaded via import() as ESM. This
contradicts the suite’s earlier assumption that ESM .js requires type: "module", so the test may
be Node-version dependent and not reliably validate the intended Vitest scenario.
Code

test/unit/util/import-utils.test.ts[R194-197]

+        const jsFileContent = `
+            export default function test() {}
+            export const number = 6;
+        `
Evidence
The suite’s own ESM .js test sets type: "module" before using export syntax, but the new
fallback test uses export syntax with an empty package.json (CommonJS). importOrRequireFile also
uses nearest package.json to decide CJS vs ESM for .js, so this setup is inconsistent and risks
failing outside Vite-like transforms.

test/unit/util/import-utils.test.ts[12-48]
test/unit/util/import-utils.test.ts[185-212]
src/util/ImportUtils.ts[54-63]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
The test `should fall back to import when the CommonJS loader cannot parse the file` writes ESM syntax into `file.js` while also writing `package.json` as `{}`. Elsewhere in this same test suite, ESM `.js` loading is only expected to work when `package.json` has `type: "module"`.

This makes the new test brittle: in many Node configurations, `import()` of `.js` in a CommonJS package will still treat it as CommonJS and fail to parse `export` syntax, so the test may fail or not exercise the intended behavior.

### Issue Context
You want a fixture where:
1) `require()` fails with a `SyntaxError` (simulating Vitest’s CJS loader behavior), and
2) `import()` succeeds.

In plain Node, a reliable way to satisfy (2) is to make the fixture a **valid CommonJS module** (since `import()` can load CJS), while still forcing (1) via the `_extensions['.js']` stub.

### Fix Focus Areas
- test/unit/util/import-utils.test.ts[185-223]
- test/unit/util/import-utils.test.ts[12-48]
- src/util/ImportUtils.ts[54-63]

### Suggested fix
- Change `jsFileContent` in the fallback test to valid CommonJS that still yields `default` + a named export under `import()`; for example:
 - `module.exports = function test() {}; module.exports.number = 6;`
 - (Then assert `exports.default` is a function and `exports.number === 6`.)
- Keep `package.json` as `{}` to ensure `importOrRequireFile` chooses the require-first path.
- Optionally, add a comment clarifying that the fixture is intentionally CommonJS so `import()` works in Node without Vite.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. SyntaxError fallback masks runtime 🐞 Bug ≡ Correctness
Description
importOrRequireFile retries import() on any caught SyntaxError from require(), which can change
behavior by swallowing a real runtime-thrown SyntaxError from the module and loading successfully
via import(). This can mask legitimate failures (e.g., modules that intentionally throw SyntaxError)
and make test/prod behavior diverge depending on the runtime’s import handling.
Code

src/util/ImportUtils.ts[R38-45]

+        } catch (error) {
+            if (!(error instanceof SyntaxError)) throw error
+
+            try {
+                return await tryToImport()
+            } catch {
+                throw error
+            }
Evidence
The new helper retries on any SyntaxError, with no attempt to distinguish loader parse failures from
user-thrown runtime SyntaxErrors, so a runtime SyntaxError can be masked if import() succeeds.

src/util/ImportUtils.ts[29-46]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`tryToRequireOrImport` currently retries `import()` for **any** `SyntaxError` thrown by `require()`. This can unintentionally treat runtime-thrown `SyntaxError`s (thrown by user code) as “parse errors” and load the module via `import()`, changing behavior and potentially hiding failures.

### Issue Context
The intent is to retry only when the CommonJS loader fails to *parse* the file (e.g., TS tokens under Vitest). You can reduce behavior changes by gating the retry on heuristics that indicate a loader/parser syntax failure (message patterns, stack markers), and otherwise rethrow the original error.

### Fix Focus Areas
- src/util/ImportUtils.ts[29-46]
- src/util/ImportUtils.ts[49-63]

### Suggested fix
- Replace the broad `error instanceof SyntaxError` check with a stricter predicate, e.g.:
 - check `error instanceof SyntaxError` **and** message matches known loader parse errors (`Invalid or unexpected token`, `Unexpected token`, `Cannot use import statement outside a module`, etc.), and/or
 - check stack includes CJS loader compilation frames.
- Add/adjust a unit test that demonstrates a module that throws `new SyntaxError("...")` at runtime is **not** retried (or, if you want to support retrying it, document that behavior explicitly).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


  • Author self-review: I have reviewed the code review findings, and addressed the relevant ones.

Grey Divider

Tip of the day
💡 Did you know, you can copy the agent prompt from any finding and feed it to your IDE agent

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

@alumni alumni assigned alumni and unassigned alumni Aug 26, 2026
@alumni
alumni self-requested a review August 26, 2026 07:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

linked-issue PR references an issue

Development

Successfully merging this pull request may close these issues.

Vitest / SyntaxError: Invalid or unexpected token

2 participants