Skip to content

Fix schema lookups and repository resolution to be resilient to mangling#18

Open
kac-axelor wants to merge 2 commits intoaxelor:devfrom
kac-axelor:mangling
Open

Fix schema lookups and repository resolution to be resilient to mangling#18
kac-axelor wants to merge 2 commits intoaxelor:devfrom
kac-axelor:mangling

Conversation

@kac-axelor
Copy link
Copy Markdown
Contributor

@kac-axelor kac-axelor commented Mar 18, 2026

JavaScript bundlers (webpack, turbopack) mangle class and function names in production builds to reduce bundle size:

// Source
class AOSPartner extends AuditableModel { ... }

// After mangling
class e extends t { ... }

This is harmless for most code, but breaks any code that reads a class name at runtime and compares it against a string baked in at compile time. Goovee ORM has several such patterns.

TypeORM's EntityMetadata exposes two relevant properties:

  • metadata.name / metadata.targetName — the class constructor name ((EntityClass as Function).name). Gets mangled.
  • metadata.tableNameWithoutPrefix — the table name after applying the naming strategy, before any global entityPrefix. Never mangled and stable for schema lookups regardless of DataSource prefix configuration.

Similarly, RelationMetadata exposes .name (the class constructor name, mangled) and .target (the class constructor reference, unaffected by mangling).


Changes

1. src/client/parser/context.tsisStringField schema lookup

isStringField is called during query parsing to decide whether to wrap a field in lower() / unaccent() for case-insensitive and accent-insensitive search. It looks up the entity's schema definition to check if a field is of type String.

The lookup was using metadata.targetName (mangled) to find the matching entry in schemaDefs (which stores original names like "AOSPartner"). This silently failed in production, causing isStringField to always return false and disabling normalization entirely.

The fix resolves the schema entry using the active TypeORM naming strategy — the same logic TypeORM uses to derive the table name from @Entity() decorator arguments — so the lookup is both mangling-resilient and correct regardless of which naming strategy is configured.

// Before
const schemaDef = this.schema.find((x) => x.name === repo.metadata.targetName);

// After
const ns = repo.metadata.connection.namingStrategy;
const schemaDef = this.schema.find(
  (x) => ns.tableName(x.name, x.table) === repo.metadata.tableNameWithoutPrefix,
);

2. src/client/repository/repository.tsgetProtectedFields schema lookup

getProtectedFields enforces that fields marked internal (never writable) or readonly (not updatable after creation) are rejected on create and update operations. It also looks up schemaDefs by entity name.

The lookup used meta.name (mangled), so the entity was never found. getProtectedFields would return an empty set, silently bypassing all protected field checks.

The fix passes the repo directly so getProtectedFields can use the naming strategy from the live connection, matching entries the same way TypeORM resolves them.

// Before
function getProtectedFields(client, entityName, mode) {
  const entity = schema.find((e) => e.name === entityName);
  ...
}
getProtectedFields(this.#client, meta.name, "create");
getProtectedFields(this.#client, meta.name, "update");

// After
function getProtectedFields(repo, client, mode) {
  const ns = repo.metadata.connection.namingStrategy;
  const entity = schema.find(
    (e) => ns.tableName(e.name, e.table) === repo.metadata.tableNameWithoutPrefix,
  );
  ...
}
getProtectedFields(repo, this.#client, "create");
getProtectedFields(repo, this.#client, "update");

3. src/client/repository/repository.ts and src/client/repository/query/query.ts — string-based getRepository calls

Three call sites were passing a string (the class constructor name) to TypeORM's getRepository. Under mangling, this works incidentally — rMeta.name and the internally registered metadata name are both the same mangled value — but it is fragile. If two entity classes are ever assigned the same mangled name (possible across async chunks), getRepository(string) silently returns the wrong repository.

The fix uses the class constructor reference instead, consistent with the already-correct pattern in join-handler.ts (getRepository(relation.type)).

// Before — string lookup, fragile under mangling
// repository.ts
const rRepo = repo.manager.getRepository(rMeta.name);
// query.ts
const rr = repo.manager.getRepository(relation.inverseEntityMetadata.name);

// After — class reference, safe under mangling
// repository.ts
const rRepo = repo.manager.getRepository(rMeta.target);
// query.ts
const rr = repo.manager.getRepository(relation.inverseEntityMetadata.target);

Notes

The portal project also adds --no-mangling to its next build command as a short-term mitigation while these fixes are released. That flag can be removed once this ORM version is consumed.

@kac-axelor kac-axelor changed the title Fix schema lookups and repository resolution to be resilient to name … Fix schema lookups and repository resolution to be resilient to mangling Mar 18, 2026
@kac-axelor kac-axelor force-pushed the mangling branch 2 times, most recently from c056146 to 8fd5440 Compare March 20, 2026 06:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant