Skip to content

fix: cache plan eagerly to support self-referential responses#32

Open
hodfords-quang-th-backend wants to merge 1 commit into
hodfords-solutions:mainfrom
hodfords-quang-th-backend:fix/plan-cache-cycle-handling
Open

fix: cache plan eagerly to support self-referential responses#32
hodfords-quang-th-backend wants to merge 1 commit into
hodfords-solutions:mainfrom
hodfords-quang-th-backend:fix/plan-cache-cycle-handling

Conversation

@hodfords-quang-th-backend
Copy link
Copy Markdown
Contributor

Summary

  • Cache the in-progress plan in planCache before recursing into nested classes so that self-referential response types resolve to a stable, shared plan instead of being dropped as empty placeholders.
  • Removes the visiting set / empty-plan short-circuit in getOrBuildPlan, and the if (nestedPlan.propertyTransforms.length > 0 || nestedPlan.nestedPlans.length > 0) filter in collectNestedPlans that was filtering out the cycle.

Why

Caught in employer-backend chat tests after upgrading to @hodfords/nestjs-response@11.1.0. A ChatMessageResponse whose referencedMessage is itself a ChatMessageResponse would lose its property transforms — the recursive call returned { propertyTransforms: [], nestedPlans: [] }, then the empty plan was filtered out, so transforms attached to the referenced message were silently dropped (7 e2e failures).

After this fix, the plan inserted before recursion is the same reference returned to the inner call. As subsequent loop iterations mutate plan.propertyTransforms and plan.nestedPlans, the inner reference sees the updates. The cycle in the plan structure mirrors the cycle in the data; runPlan terminates naturally when the data graph terminates.

Test plan

  • npm run build — clean tsc build, generated JS is byte-identical to the patched JS we are running in production.
  • Verified in employer-backend chat e2e: 7 previously-failing referencedMessage assertions now pass with the patched JS this PR produces.
  • Add a regression unit test for self-referential response types (left for follow-up unless you'd like it bundled).

🤖 Generated with Claude Code

Cache the in-progress plan before recursing into nested classes so that
self-referential response types (e.g. ChatMessageResponse with a
referencedMessage of the same type) resolve to a stable, shared plan
instead of being dropped as an empty placeholder.

Previously, getOrBuildPlan tracked the current target with a visiting
set and returned an empty plan when a cycle was detected. The empty
plan was then filtered out in collectNestedPlans, so transforms on the
self-referenced field were never applied.

Now the plan object is inserted into planCache before its children are
walked. Recursive lookups receive the same object reference and any
property transforms attached to the cycle are honored. The cycle in the
plan mirrors the cycle in the data, and runPlan terminates naturally
when the data graph terminates.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@hodfords-quang-th-backend hodfords-quang-th-backend force-pushed the fix/plan-cache-cycle-handling branch from a2ed446 to 94e731a Compare May 22, 2026 02:28
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