MemberJunction / MemberJunction/MJ

ClassFactory: TaskEntity and TaskDependencyEntity register twice on every MJAPI start — last-wins, and the warning does not name either registrant

Open
#3,976 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
TSQL
Stars
29
Forks
6
Avg merge
2d 1h
Merged PRs (30d)
323

Description

## Summary

Two entities register twice in `ClassFactory` on every MJAPI start, and MJ's own warning states the consequence: the last registration wins. Reporting it because it is **live in the supported topology**, not theoretical.

```
*** ClassFactory.Register: Registering class TaskEntity for base class BaseEntity and key/priority
MJ_BizApps_Tasks: Tasks/1. 1 registrations already exist for that combination. While this is allowed it
is not desired and when matching class requests occur, we will simply use the LAST registration we
happen to have which can lead to unintended behavior. ***

*** ClassFactory.Register: Registering class TaskDependencyEntity for base class BaseEntity and
key/priority MJ_BizApps_Tasks: Task Dependencies/1. 1 registrations already exist … ***
```

Reproduces two ways, so it is not an artefact of how the API boots:

- every `mjdev run api` (MJAPI startup, after `Loading Open App server packages...`)
- a bare `await import('@mj-biz-apps/tasks-server')` in the MJAPI package — the warning fires during module evaluation, before anything else runs

**Environment:** MJ `next` @ `e45fde5b2c` (`6.1.0-edge.2`), Node 24.16.0, pnpm 10.33.0, macOS. Workspace holding MJ + `bizapps-{common,tasks,accounting,orders,contracts}`, all five registered as Open Apps. Single-copy census green (one physical `@memberjunction/core`, realpath-verified, shared by MJAPI and every app package), so this is **not** a duplicated-package problem — the same key is genuinely registered twice from one module graph.

## Why it matters

The warning is accurate about the risk: with two registrations for one key, `ClassFactory` resolves to whichever happened to register last. That is load-order-dependent, so it can differ between a dev boot and a production boot, and nothing fails — you get the wrong subclass silently.

## ⚠️ Please do not fix this by making registration idempotent by name

Stating this up front because it is the obvious fix and we believe it makes things worse. With `type-graphql@2.0.0-beta.3`'s storage anchored on `global`, two registrations of the same name currently produce one of two outcomes:

- a **loud, correct failure** — `Schema must contain uniquely named types` — when both are resolver-reachable, or
- a **silently wrong schema** when only one is.

De-duplicating by name converts the first case into the second: the loud failure disappears and you are left with the silent one. The shape that is safe is **"idempotent only if structurally identical, loud otherwise"** — same key + same class is a no-op, same key + different class is an error naming both registrants and their packages.

## What would help most, in order

1. **Name both registrants in the warning.** Today it says a registration already exists but not what registered it. The module/package of the incumbent and of the newcomer would make this self-diagnosing.
2. **Find out why `tasks-server` double-registers** — worth a look on its own, since a single import triggers it. Likely candidates: the same entity subclass reachable through two barrel exports, or a generated registration plus a hand-written one for the same key. If it turns out to be app-side we will take it to `bizapps-tasks`, but the double-register is only visible from MJ's side today.
3. **Consider structural idempotency** (per above) so the same-class case stops warning at all and the different-class case fails loudly instead of last-wins.

## Credit where it is due

The same startup produced genuinely useful telemetry, which is the standard this warning could meet:

```
💡 [Telemetry/Performance] Sequential Queries Could Be Parallelized
3 RunView calls executed sequentially: MJ: AI Agent Sessions, MJ: Task Types, MJ: Task Types
💡 Use RunViews (batch) to execute these queries in parallel for better performance
⚠️ [Telemetry/Redundancy] Duplicate RunView Detected
Identical RunView (MJ: Task Types, same filter/orderBy) called 2 times
```

Both name the entity and the count. The `ClassFactory` warning naming its two registrants the same way would have closed this in one read.

Contributor guide

Open the contributing guide

Research direction

Start at the ClassFactory.Register entry point and reproduce the issue with a bare import of @mj-biz-apps/tasks-server, then with mjdev run api. Trace both TaskEntity and TaskDependencyEntity registrations during module evaluation; done means the warning identifies both registrants and the duplicate-registration behavior has a verified outcome without masking distinct classes.

Written by the indexing model from the issue text.

Assessment

Tech stack
nodejs, typescript
Domain
api, backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.