Skip to content

Conversation

@akitaSummer
Copy link
Contributor

@akitaSummer akitaSummer commented Dec 19, 2025

Checklist
  • npm test passes
  • tests and/or benchmarks are included
  • documentation is changed or added
  • commit message follows commit guidelines
Affected core subsystem(s)
Description of change

Summary by CodeRabbit

  • New Features

    • Added a decorator to restrict factory-based dependency injection to specified implementation ranges
    • Runtime enforcement preventing requests for implementations outside the allowed dynamic range
    • New demo endpoints and services showcasing qualified factory injection
  • Tests

    • Added tests covering dynamic range validation, qualified factory behavior, and related route interactions

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Dec 19, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds factory-qualifier metadata, a decorator and utility to declare allowed dynamic qualifiers, enforces those ranges at runtime for eggObjectFactory injections, and includes unit and integration tests and fixtures exercising the new dynamic-range behavior.

Changes

Cohort / File(s) Summary
Core Decorator & Type Support
core/types/core-decorator/enum/Qualifier.ts
Adds DYNAMIC_RANGE_META_DATA symbol constant for storing factory qualifier metadata.
Dynamic Inject Public API
core/dynamic-inject/index.ts
Re-exports FactoryQualifier and FactoryQualifierUtil from the new source files.
Dynamic Inject Utilities
core/dynamic-inject/src/FactoryQualifier.ts, core/dynamic-inject/src/FactoryQualifierUtil.ts
Implements FactoryQualifier decorator factory and FactoryQualifierUtil for recording and retrieving per-property/parameter allowed qualifier types.
Runtime Integration
core/runtime/src/impl/EggObjectImpl.ts
When encountering eggObjectFactory injections, queries allowed qualifiers via FactoryQualifierUtil and wraps the factory to restrict dynamic qualifier requests (throws on disallowed qualifiers). Adds relevant imports and typing.
Unit Test Fixtures & Cases
core/dynamic-inject/test/fixtures/modules/dynamicRange/HelloService.ts, core/dynamic-inject/test/typing.test.ts
Adds HelloService fixture using @FactoryQualifier and a unit test verifying FactoryQualifierUtil.getProperQualifiers returns the expected qualifier.
End-to-End Test & App Fixtures
plugin/tegg/test/DynamicInject.test.ts, plugin/tegg/test/fixtures/apps/dynamic-inject-app/app/controller/app.ts, plugin/tegg/test/fixtures/apps/dynamic-inject-app/app/router.ts, plugin/tegg/test/fixtures/apps/dynamic-inject-app/modules/dynamic-inject-module/FactoryQualifierService.ts
Adds integration test asserting dynamic-range enforcement and singleton behavior; new controller route and service fixture that call into the factory-qualifier-backed service.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Pay extra attention to:
    • core/runtime/src/impl/EggObjectImpl.ts — wrapper/proxy correctness and error messages.
    • core/dynamic-inject/src/FactoryQualifierUtil.ts — parameter-index handling and metadata keying.
    • Tests and fixtures — ensure assertions align with runtime behavior and error cases.

Possibly related PRs

Suggested reviewers

  • killagu

Poem

🐰 I hop through code with ribbon and quill,
Marking factories with a careful will.
Qualifiers set the hopping gate,
Only allowed friends may pass that state.
Hooray — dynamic ranges snug and still!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add FactoryQualifier' accurately reflects the main change: adding the FactoryQualifier decorator and related utilities for dynamic range support in dependency injection.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/add_dynamic_range

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8ba4dcf and f1cf27d.

📒 Files selected for processing (1)
  • core/dynamic-inject/test/typing.test.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • core/dynamic-inject/test/typing.test.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Runner-ubuntu (18)
  • GitHub Check: Runner-macos (16)
  • GitHub Check: Runner-macos (18)
  • GitHub Check: Runner-macos (20)
  • GitHub Check: Runner-ubuntu (20)
  • GitHub Check: Runner-ubuntu (16)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @akitaSummer, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request enhances the dependency injection system by introducing a 'dynamic range' mechanism. This feature allows developers to explicitly define a whitelist of abstract classes that an EggObjectFactory can instantiate for a specific injection point. This provides finer-grained control over dynamic injections, improving type safety and preventing unintended object resolutions, ultimately leading to more robust and predictable application behavior.

Highlights

  • New Dynamic Range Feature: Introduced a new @DynamicRange decorator and DynamicRangeUtil to control the types of objects that can be dynamically injected by an EggObjectFactory instance.
  • Runtime Enforcement: Modified EggObjectImpl to enforce the DynamicRange constraints at runtime, ensuring that only allowed types are resolved by the EggObjectFactory.
  • Metadata Introduction: Added DYNAMIC_RANGE_META_DATA to store the dynamic range information for properties and constructor parameters.
  • Comprehensive Testing: Included new test fixtures and test cases to validate the functionality and correctness of the dynamic range feature, covering both successful injections and constraint violations.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a DynamicRange decorator to restrict the types that can be dynamically injected using EggObjectFactory. The implementation involves metadata storage for the range and runtime checks during dependency injection. The changes are well-tested with unit and integration tests. My review includes suggestions to improve type safety in the decorator implementation, and to refactor a complex part of the injection logic for better readability and maintainability, for instance by avoiding magic strings.

Comment on lines 5 to 7
return function(target: any, propertyKey?: PropertyKey, parameterIndex?: number) {
DynamicRangeUtil.addProperQualifier(target as EggProtoImplClass, propertyKey, parameterIndex, dynamics);
};
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

To align with the suggested changes in DynamicRangeUtil.addProperQualifier for improved type safety, the decorator implementation should be updated. This involves changing the target type from any to object and passing it directly to addProperQualifier without casting.

Suggested change
return function(target: any, propertyKey?: PropertyKey, parameterIndex?: number) {
DynamicRangeUtil.addProperQualifier(target as EggProtoImplClass, propertyKey, parameterIndex, dynamics);
};
return function(target: object, propertyKey?: PropertyKey, parameterIndex?: number) {
DynamicRangeUtil.addProperQualifier(target, propertyKey, parameterIndex, dynamics);
};

Comment on lines +8 to +18
static addProperQualifier(clazz: EggProtoImplClass, property: PropertyKey | undefined, parameterIndex: number | undefined, value: EggAbstractClazz | EggAbstractClazz[]) {
if (typeof parameterIndex === 'number') {
const argNames = ObjectUtils.getConstructorArgNameList(clazz);
const argName = argNames[parameterIndex];
const properQualifiers = MetadataUtil.initOwnMapMetaData(DYNAMIC_RANGE_META_DATA, clazz, new Map<PropertyKey, EggAbstractClazz | EggAbstractClazz[]>());
MapUtil.getOrStore(properQualifiers, argName, value);
} else {
const properQualifiers = MetadataUtil.initOwnMapMetaData(DYNAMIC_RANGE_META_DATA, (clazz as any).constructor, new Map<PropertyKey, EggAbstractClazz | EggAbstractClazz[]>());
MapUtil.getOrStore(properQualifiers, property, value);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The type of clazz is declared as EggProtoImplClass, which is a constructor type. However, when used with a property decorator, this method receives the class's prototype, not the constructor. The code currently handles this with (clazz as any).constructor, but the type signature is misleading and could lead to bugs if this code is refactored.

To improve type safety and clarity, the signature and implementation could be adjusted to correctly reflect the different target types from property and parameter decorators. This change would also require a small adjustment in DynamicRange.ts.

  static addProperQualifier(target: object, property: PropertyKey | undefined, parameterIndex: number | undefined, value: EggAbstractClazz | EggAbstractClazz[]) {
    if (typeof parameterIndex === 'number') {
      // Parameter decorator, target is the constructor
      const clazz = target as EggProtoImplClass;
      const argNames = ObjectUtils.getConstructorArgNameList(clazz);
      const argName = argNames[parameterIndex];
      const properQualifiers = MetadataUtil.initOwnMapMetaData(DYNAMIC_RANGE_META_DATA, clazz, new Map<PropertyKey, EggAbstractClazz | EggAbstractClazz[]>());
      MapUtil.getOrStore(properQualifiers, argName, value);
    } else {
      // Property decorator, target is the prototype
      const clazz = (target as any).constructor as EggProtoImplClass;
      const properQualifiers = MetadataUtil.initOwnMapMetaData(DYNAMIC_RANGE_META_DATA, clazz, new Map<PropertyKey, EggAbstractClazz | EggAbstractClazz[]>());
      MapUtil.getOrStore(properQualifiers, property, value);
    }
  }

Comment on lines 65 to 87
if (injectObj.proto.name === 'eggObjectFactory') {
const dynamics = DynamicRangeUtil.getProperQualifiers(this._obj.constructor as unknown as EggProtoImplClass, injectObject.refName);
if (dynamics.length > 0) {
const obj: any = {
dynamics,
};
const originalGetEggObject = (injectObj.obj as any).getEggObject;
Object.setPrototypeOf(obj, injectObj.obj);
obj.getEggObject = function(...args: any[]) {
if (!this.dynamics.includes(args[0])) {
throw new Error(`${args[0].name} is not in dynamic range: ${this.dynamics.map(item => item.name).join(', ')}`);
}
return originalGetEggObject.apply(this, args);
};
injectObj = Object.create(injectObj);
Object.defineProperty(injectObj, 'obj', {
value: obj,
writable: true,
enumerable: true,
configurable: true,
});
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This block of logic for wrapping eggObjectFactory has a few areas for improvement:

  1. Hardcoded String: injectObj.proto.name === 'eggObjectFactory' uses a magic string. It would be more robust to use a shared constant to avoid potential issues if the name changes.
  2. Object Creation: Using Object.setPrototypeOf can have performance implications. It's generally better to use Object.create() to establish the prototype chain when creating a new object.
  3. Complexity: The logic is quite dense and nested within the map callback, which harms readability. Consider extracting this logic into a separate private helper method to make initWithInjectProperty easier to understand and maintain.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (6)
core/dynamic-inject/test/typing.test.ts (1)

31-34: Test is synchronous but marked as async.

The test body contains no await expressions, so the async keyword is unnecessary. Consider removing it for clarity.

🔎 Suggested fix
-  it('should dynamic range run', async () => {
+  it('should dynamic range run', () => {
     const dynamics = DynamicRangeUtil.getProperQualifiers(HelloService, 'eggObjectFactory');
     assert(dynamics.includes(AbstractContextHello));
   });
core/dynamic-inject/src/DynamicRange.ts (1)

4-8: Consider adding explicit return type annotations.

The decorator factory and its returned function lack explicit type annotations. Adding them would improve type safety and documentation.

🔎 Suggested improvement
-export function DynamicRange(dynamics: EggAbstractClazz | EggAbstractClazz[]) {
-  return function(target: any, propertyKey?: PropertyKey, parameterIndex?: number) {
+export function DynamicRange(dynamics: EggAbstractClazz | EggAbstractClazz[]): PropertyDecorator & ParameterDecorator {
+  return function(target: any, propertyKey?: PropertyKey, parameterIndex?: number): void {
     DynamicRangeUtil.addProperQualifier(target as EggProtoImplClass, propertyKey, parameterIndex, dynamics);
   };
 }
core/runtime/src/impl/EggObjectImpl.ts (2)

73-78: Consider adding defensive checks for getEggObject arguments.

The override assumes args[0] is defined and has a .name property. If getEggObject is called without arguments or with an unexpected type, this could throw an unclear error.

🔎 Suggested improvement
               obj.getEggObject = function(...args: any[]) {
+                const clazz = args[0];
+                if (!clazz) {
+                  throw new Error('getEggObject requires a class argument');
+                }
-                if (!this.dynamics.includes(args[0])) {
-                  throw new Error(`${args[0].name} is not in dynamic range: ${this.dynamics.map(item => item.name).join(', ')}`);
+                if (!this.dynamics.includes(clazz)) {
+                  throw new Error(`${clazz.name ?? clazz} is not in dynamic range: ${this.dynamics.map(item => item.name).join(', ')}`);
                 }
                 return originalGetEggObject.apply(this, args);
               };

64-87: Dynamic range enforcement missing in constructor injection path.

The initWithInjectConstructor method (lines 113-184) processes injected objects without checking dynamic range restrictions, unlike the property injection path (lines 64-87) which enforces them. The @DynamicRange decorator architecturally supports both field and constructor parameter injection via the decorator's parameterIndex parameter, and DynamicRangeUtil stores constructor parameter metadata. For architectural consistency, if constructor injection with eggObjectFactory should enforce dynamic ranges, add similar validation in initWithInjectConstructor when injectObject.proto.name === 'eggObjectFactory'.

plugin/tegg/test/fixtures/apps/dynamic-inject-app/modules/dynamic-inject-module/DynamicRangeService.ts (2)

19-20: Ensure safe access to error message.

Accessing err.message assumes err is an Error object. Consider using optional chaining or String coercion for robustness.

🔎 Safer error handling
     } catch (err) {
-      return [ err.message ];
+      return [ err?.message ?? String(err) ];
     }

10-12: The @dynamicrange constraint mismatch is intentional and tests error handling.

The decorator specifies AbstractSingletonHello, but line 16 calls getEggObject(AbstractContextHello, ...), which intentionally violates the dynamic range constraint. The test confirms this is expected behavior: the framework throws "AbstractContextHello is not in dynamic range: AbstractSingletonHello" at runtime. Consider adding a comment like // Test that dynamic range constraint is enforced at runtime to clarify the intent.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d237adb and c01f726.

📒 Files selected for processing (11)
  • core/dynamic-inject/index.ts (1 hunks)
  • core/dynamic-inject/src/DynamicRange.ts (1 hunks)
  • core/dynamic-inject/src/DynamicRangeUtil.ts (1 hunks)
  • core/dynamic-inject/test/fixtures/modules/dynamicRange/HelloService.ts (1 hunks)
  • core/dynamic-inject/test/typing.test.ts (2 hunks)
  • core/runtime/src/impl/EggObjectImpl.ts (3 hunks)
  • core/types/core-decorator/enum/Qualifier.ts (1 hunks)
  • plugin/tegg/test/DynamicInject.test.ts (1 hunks)
  • plugin/tegg/test/fixtures/apps/dynamic-inject-app/app/controller/app.ts (1 hunks)
  • plugin/tegg/test/fixtures/apps/dynamic-inject-app/app/router.ts (1 hunks)
  • plugin/tegg/test/fixtures/apps/dynamic-inject-app/modules/dynamic-inject-module/DynamicRangeService.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (8)
plugin/tegg/test/DynamicInject.test.ts (1)
benchmark/http/app/router.js (1)
  • app (4-4)
plugin/tegg/test/fixtures/apps/dynamic-inject-app/modules/dynamic-inject-module/DynamicRangeService.ts (2)
core/dynamic-inject/test/fixtures/modules/dynamicRange/HelloService.ts (1)
  • ContextProto (7-21)
core/dynamic-inject/src/DynamicRange.ts (1)
  • DynamicRange (4-8)
core/dynamic-inject/src/DynamicRangeUtil.ts (4)
core/types/core-decorator/model/EggPrototypeInfo.ts (1)
  • EggProtoImplClass (5-5)
core/types/dynamic-inject.ts (1)
  • EggAbstractClazz (3-3)
core/common-util/src/ObjectUtils.ts (1)
  • ObjectUtils (3-54)
core/types/core-decorator/enum/Qualifier.ts (1)
  • DYNAMIC_RANGE_META_DATA (13-13)
core/dynamic-inject/src/DynamicRange.ts (3)
core/types/dynamic-inject.ts (1)
  • EggAbstractClazz (3-3)
core/dynamic-inject/src/DynamicRangeUtil.ts (1)
  • DynamicRangeUtil (6-31)
core/types/core-decorator/model/EggPrototypeInfo.ts (1)
  • EggProtoImplClass (5-5)
core/dynamic-inject/test/fixtures/modules/dynamicRange/HelloService.ts (2)
plugin/tegg/test/fixtures/apps/dynamic-inject-app/modules/dynamic-inject-module/DynamicRangeService.ts (1)
  • ContextProto (6-23)
core/dynamic-inject/src/DynamicRange.ts (1)
  • DynamicRange (4-8)
core/dynamic-inject/test/typing.test.ts (1)
core/dynamic-inject/src/DynamicRangeUtil.ts (1)
  • DynamicRangeUtil (6-31)
plugin/tegg/test/fixtures/apps/dynamic-inject-app/app/router.ts (1)
benchmark/http/app/router.js (1)
  • app (4-4)
core/runtime/src/impl/EggObjectImpl.ts (4)
core/runtime/src/factory/EggContainerFactory.ts (1)
  • EggContainerFactory (18-88)
core/dynamic-inject/src/DynamicRangeUtil.ts (1)
  • DynamicRangeUtil (6-31)
core/types/core-decorator/model/EggPrototypeInfo.ts (1)
  • EggProtoImplClass (5-5)
plugin/tegg/lib/EggCompatibleObject.ts (1)
  • obj (32-40)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Runner-ubuntu (16)
  • GitHub Check: Runner-macos (20)
  • GitHub Check: Runner-macos (16)
  • GitHub Check: Runner-macos (18)
  • GitHub Check: Runner-ubuntu (20)
  • GitHub Check: Runner-ubuntu (18)
🔇 Additional comments (8)
plugin/tegg/test/fixtures/apps/dynamic-inject-app/app/router.ts (1)

6-6: LGTM!

The new route registration follows the established pattern used for the other dynamic inject routes in this file.

plugin/tegg/test/fixtures/apps/dynamic-inject-app/app/controller/app.ts (1)

19-24: LGTM!

The new controller method follows the established pattern. The type annotation HelloService for the resolved dynamicRangeService is consistent with how dynamicInject handles its service resolution.

core/dynamic-inject/index.ts (1)

4-5: LGTM!

The new exports for DynamicRange and DynamicRangeUtil follow the existing export pattern and properly expose the public API surface for the dynamic range feature.

core/types/core-decorator/enum/Qualifier.ts (1)

13-14: LGTM!

The new metadata symbol follows the established naming convention and uses Symbol.for for cross-module identity consistency.

plugin/tegg/test/DynamicInject.test.ts (1)

52-67: LGTM!

The test case effectively validates:

  1. Dynamic range enforcement by checking the expected error message when requesting a dynamic outside the allowed range
  2. State continuity by verifying the singleton counter incremented as expected

The test structure is consistent with the existing test cases in this file.

core/dynamic-inject/test/fixtures/modules/dynamicRange/HelloService.ts (2)

9-11: LGTM! Consistent DynamicRange usage.

The @DynamicRange([ AbstractContextHello ]) decorator correctly matches the AbstractContextHello type retrieved in the hello() method, establishing a clear constraint-to-usage pattern.


13-20: LGTM! Efficient concurrent retrieval.

Using Promise.all for concurrent object retrieval is appropriate. Note that unlike DynamicRangeService.ts, this test fixture doesn't include error handling, which is acceptable for test code but means failures will propagate.

core/dynamic-inject/src/DynamicRangeUtil.ts (1)

20-30: LGTM! Proper null safety and normalization.

The method correctly:

  • Uses optional chaining to handle undefined metadata
  • Returns an empty array when no qualifiers are found
  • Normalizes single values to arrays for consistent return type

The method expects clazz to be the class constructor (not prototype), which aligns with all call sites: EggObjectImpl.ts passes this._obj.constructor and tests pass the class directly.

Comment on lines +8 to +13
static addProperQualifier(clazz: EggProtoImplClass, property: PropertyKey | undefined, parameterIndex: number | undefined, value: EggAbstractClazz | EggAbstractClazz[]) {
if (typeof parameterIndex === 'number') {
const argNames = ObjectUtils.getConstructorArgNameList(clazz);
const argName = argNames[parameterIndex];
const properQualifiers = MetadataUtil.initOwnMapMetaData(DYNAMIC_RANGE_META_DATA, clazz, new Map<PropertyKey, EggAbstractClazz | EggAbstractClazz[]>());
MapUtil.getOrStore(properQualifiers, argName, value);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add bounds checking for parameterIndex.

Line 11 accesses argNames[parameterIndex] without verifying that parameterIndex is within bounds. If parameterIndex >= argNames.length, argName will be undefined, which could lead to incorrect metadata storage or silent failures.

🔎 Add validation
   static addProperQualifier(clazz: EggProtoImplClass, property: PropertyKey | undefined, parameterIndex: number | undefined, value: EggAbstractClazz | EggAbstractClazz[]) {
     if (typeof parameterIndex === 'number') {
       const argNames = ObjectUtils.getConstructorArgNameList(clazz);
+      if (parameterIndex >= argNames.length) {
+        throw new Error(`Parameter index ${parameterIndex} is out of bounds for constructor with ${argNames.length} arguments`);
+      }
       const argName = argNames[parameterIndex];
       const properQualifiers = MetadataUtil.initOwnMapMetaData(DYNAMIC_RANGE_META_DATA, clazz, new Map<PropertyKey, EggAbstractClazz | EggAbstractClazz[]>());
       MapUtil.getOrStore(properQualifiers, argName, value);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
static addProperQualifier(clazz: EggProtoImplClass, property: PropertyKey | undefined, parameterIndex: number | undefined, value: EggAbstractClazz | EggAbstractClazz[]) {
if (typeof parameterIndex === 'number') {
const argNames = ObjectUtils.getConstructorArgNameList(clazz);
const argName = argNames[parameterIndex];
const properQualifiers = MetadataUtil.initOwnMapMetaData(DYNAMIC_RANGE_META_DATA, clazz, new Map<PropertyKey, EggAbstractClazz | EggAbstractClazz[]>());
MapUtil.getOrStore(properQualifiers, argName, value);
static addProperQualifier(clazz: EggProtoImplClass, property: PropertyKey | undefined, parameterIndex: number | undefined, value: EggAbstractClazz | EggAbstractClazz[]) {
if (typeof parameterIndex === 'number') {
const argNames = ObjectUtils.getConstructorArgNameList(clazz);
if (parameterIndex >= argNames.length) {
throw new Error(`Parameter index ${parameterIndex} is out of bounds for constructor with ${argNames.length} arguments`);
}
const argName = argNames[parameterIndex];
const properQualifiers = MetadataUtil.initOwnMapMetaData(DYNAMIC_RANGE_META_DATA, clazz, new Map<PropertyKey, EggAbstractClazz | EggAbstractClazz[]>());
MapUtil.getOrStore(properQualifiers, argName, value);
🤖 Prompt for AI Agents
In core/dynamic-inject/src/DynamicRangeUtil.ts around lines 8 to 13,
parameterIndex is used to index argNames without bounds checking which can yield
undefined argName; add a guard that validates parameterIndex is a number within
0 <= parameterIndex < argNames.length before using it, and if it is out of range
either throw a clear RangeError (including clazz.name and the invalid index) or
return early (no-op) depending on expected behavior; ensure argName is only
read/used after this validation so metadata is not stored under an undefined
key.

Comment on lines +14 to +17
} else {
const properQualifiers = MetadataUtil.initOwnMapMetaData(DYNAMIC_RANGE_META_DATA, (clazz as any).constructor, new Map<PropertyKey, EggAbstractClazz | EggAbstractClazz[]>());
MapUtil.getOrStore(properQualifiers, property, value);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Clarify type handling and validate property parameter.

Two concerns:

  1. Type inconsistency: The parameter is typed as clazz: EggProtoImplClass (a class constructor), but when parameterIndex is undefined (property decorator case), clazz is actually the prototype, and line 15 uses (clazz as any).constructor to get the class. This makes the API unclear. Consider either:

    • Typing the parameter as target: any to reflect the actual decorator contract
    • Normalizing the input at the function start to always work with the class
    • Adding a JSDoc comment explaining the dual nature
  2. Undefined property: When parameterIndex is undefined, property could also be undefined (per its type PropertyKey | undefined), but line 16 uses it directly as a Map key without validation.

🔎 Add property validation
     } else {
+      if (property === undefined) {
+        throw new Error('Property key is required when parameterIndex is undefined');
+      }
       const properQualifiers = MetadataUtil.initOwnMapMetaData(DYNAMIC_RANGE_META_DATA, (clazz as any).constructor, new Map<PropertyKey, EggAbstractClazz | EggAbstractClazz[]>());
       MapUtil.getOrStore(properQualifiers, property, value);
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} else {
const properQualifiers = MetadataUtil.initOwnMapMetaData(DYNAMIC_RANGE_META_DATA, (clazz as any).constructor, new Map<PropertyKey, EggAbstractClazz | EggAbstractClazz[]>());
MapUtil.getOrStore(properQualifiers, property, value);
}
} else {
if (property === undefined) {
throw new Error('Property key is required when parameterIndex is undefined');
}
const properQualifiers = MetadataUtil.initOwnMapMetaData(DYNAMIC_RANGE_META_DATA, (clazz as any).constructor, new Map<PropertyKey, EggAbstractClazz | EggAbstractClazz[]>());
MapUtil.getOrStore(properQualifiers, property, value);
}
🤖 Prompt for AI Agents
In core/dynamic-inject/src/DynamicRangeUtil.ts around lines 14 to 17, the
function treats its first argument as both a class constructor and a prototype
and uses property as a Map key without checking for undefined; normalize the
target at the top (e.g., detect if the passed value is a prototype and replace
it with its constructor) or change the parameter name/type to reflect it accepts
either prototype or constructor and add a brief JSDoc explaining the dual
nature, then validate that property is not undefined when parameterIndex is
undefined (throw or handle the error) before calling
MetadataUtil.initOwnMapMetaData/MapUtil.getOrStore so you never use an undefined
key.

@akitaSummer akitaSummer changed the title feat: add dynamic range feat: add FactoryQualifier Dec 20, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (3)
core/runtime/src/impl/EggObjectImpl.ts (1)

65-87: Refactor to address complexity, hardcoded string, and performance concerns.

This block has several areas for improvement that align with previous review feedback:

  1. Hardcoded string: injectObj.proto.name === 'eggObjectFactory' should use a shared constant
  2. Performance: Object.setPrototypeOf (line 72) can have performance implications; prefer Object.create() for establishing prototype chains
  3. Complexity: The nested logic within the map callback harms readability; extract into a separate helper method

Based on previous review feedback at lines 65-87.

core/dynamic-inject/src/FactoryQualifierUtil.ts (2)

8-13: Add bounds checking for parameterIndex.

Line 11 accesses argNames[parameterIndex] without verifying that parameterIndex is within bounds. If parameterIndex >= argNames.length, argName will be undefined, leading to incorrect metadata storage or silent failures.

This issue was previously identified in past review comments.

🔎 Add validation
 static addProperQualifier(clazz: EggProtoImplClass, property: PropertyKey | undefined, parameterIndex: number | undefined, value: EggAbstractClazz | EggAbstractClazz[]) {
   if (typeof parameterIndex === 'number') {
     const argNames = ObjectUtils.getConstructorArgNameList(clazz);
+    if (parameterIndex < 0 || parameterIndex >= argNames.length) {
+      throw new Error(`Parameter index ${parameterIndex} is out of bounds for constructor with ${argNames.length} arguments`);
+    }
     const argName = argNames[parameterIndex];
     const properQualifiers = MetadataUtil.initOwnMapMetaData(DYNAMIC_RANGE_META_DATA, clazz, new Map<PropertyKey, EggAbstractClazz | EggAbstractClazz[]>());
     MapUtil.getOrStore(properQualifiers, argName, value);

14-17: Clarify type handling and validate property parameter.

Two concerns previously identified:

  1. Type inconsistency: The parameter is typed as clazz: EggProtoImplClass (a class constructor), but in the property decorator case (line 15), clazz is actually the prototype, requiring (clazz as any).constructor. This makes the API unclear.

  2. Undefined property: When parameterIndex is undefined, property could also be undefined, but line 16 uses it directly as a Map key without validation.

These issues were previously identified in past review comments.

🔎 Add property validation
   } else {
+    if (property === undefined) {
+      throw new Error('Property key is required when parameterIndex is undefined');
+    }
     const properQualifiers = MetadataUtil.initOwnMapMetaData(DYNAMIC_RANGE_META_DATA, (clazz as any).constructor, new Map<PropertyKey, EggAbstractClazz | EggAbstractClazz[]>());
     MapUtil.getOrStore(properQualifiers, property, value);
   }
🧹 Nitpick comments (3)
core/dynamic-inject/package.json (1)

49-49: Remove unused cross-env dependency.

The cross-env package is no longer used after simplifying the test script but remains in devDependencies. Consider removing it to keep dependencies clean.

🔎 Proposed fix
    "coffee": "^5.4.0",
-   "cross-env": "^7.0.3",
    "mocha": "^10.2.0",
plugin/tegg/package.json (2)

65-65: Consider removing unused cross-env dependency.

Since the test script no longer uses cross-env, this dependency can be removed to keep the dependency list clean.

🔎 Proposed fix
-    "cross-env": "^7.0.3",

29-29: Remove unused cross-env dependency from devDependencies.

The test script has been simplified to just mocha, which no longer requires cross-env. Since cross-env is not used anywhere else in the codebase, it should be removed from devDependencies at line 65 as part of this cleanup.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c01f726 and 9040cfe.

📒 Files selected for processing (13)
  • core/dynamic-inject/index.ts (1 hunks)
  • core/dynamic-inject/package.json (1 hunks)
  • core/dynamic-inject/src/FactoryQualifier.ts (1 hunks)
  • core/dynamic-inject/src/FactoryQualifierUtil.ts (1 hunks)
  • core/dynamic-inject/test/fixtures/modules/dynamicRange/HelloService.ts (1 hunks)
  • core/dynamic-inject/test/typing.test.ts (2 hunks)
  • core/runtime/src/impl/EggObjectImpl.ts (3 hunks)
  • core/types/core-decorator/enum/Qualifier.ts (1 hunks)
  • plugin/tegg/package.json (1 hunks)
  • plugin/tegg/test/DynamicInject.test.ts (1 hunks)
  • plugin/tegg/test/fixtures/apps/dynamic-inject-app/app/controller/app.ts (1 hunks)
  • plugin/tegg/test/fixtures/apps/dynamic-inject-app/app/router.ts (1 hunks)
  • plugin/tegg/test/fixtures/apps/dynamic-inject-app/modules/dynamic-inject-module/FactoryQualifierService.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
  • core/dynamic-inject/index.ts
  • core/dynamic-inject/test/fixtures/modules/dynamicRange/HelloService.ts
  • core/types/core-decorator/enum/Qualifier.ts
  • plugin/tegg/test/DynamicInject.test.ts
  • plugin/tegg/test/fixtures/apps/dynamic-inject-app/app/controller/app.ts
  • core/dynamic-inject/test/typing.test.ts
🧰 Additional context used
🧬 Code graph analysis (5)
core/dynamic-inject/src/FactoryQualifier.ts (3)
core/types/dynamic-inject.ts (1)
  • EggAbstractClazz (3-3)
core/dynamic-inject/src/FactoryQualifierUtil.ts (1)
  • FactoryQualifierUtil (6-31)
core/types/core-decorator/model/EggPrototypeInfo.ts (1)
  • EggProtoImplClass (5-5)
core/runtime/src/impl/EggObjectImpl.ts (3)
core/runtime/src/factory/EggContainerFactory.ts (1)
  • EggContainerFactory (18-88)
core/dynamic-inject/src/FactoryQualifierUtil.ts (1)
  • FactoryQualifierUtil (6-31)
core/types/core-decorator/model/EggPrototypeInfo.ts (1)
  • EggProtoImplClass (5-5)
plugin/tegg/test/fixtures/apps/dynamic-inject-app/app/router.ts (1)
benchmark/http/app/router.js (1)
  • app (4-4)
core/dynamic-inject/src/FactoryQualifierUtil.ts (5)
core/types/core-decorator/model/EggPrototypeInfo.ts (1)
  • EggProtoImplClass (5-5)
core/types/dynamic-inject.ts (1)
  • EggAbstractClazz (3-3)
core/common-util/src/ObjectUtils.ts (1)
  • ObjectUtils (3-54)
core/core-decorator/src/util/MetadataUtil.ts (1)
  • MetadataUtil (5-97)
core/types/core-decorator/enum/Qualifier.ts (1)
  • DYNAMIC_RANGE_META_DATA (13-13)
plugin/tegg/test/fixtures/apps/dynamic-inject-app/modules/dynamic-inject-module/FactoryQualifierService.ts (2)
core/dynamic-inject/test/fixtures/modules/dynamicRange/HelloService.ts (1)
  • ContextProto (7-21)
core/dynamic-inject/src/FactoryQualifier.ts (1)
  • FactoryQualifier (4-8)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Socket Security: Pull Request Alerts
  • GitHub Check: Runner-macos (16)
  • GitHub Check: Runner-macos (18)
🔇 Additional comments (5)
plugin/tegg/test/fixtures/apps/dynamic-inject-app/app/router.ts (1)

6-6: LGTM!

The new route registration follows the established pattern and correctly maps the endpoint to the controller method.

core/runtime/src/impl/EggObjectImpl.ts (1)

7-7: LGTM!

The new imports are necessary for the dynamic range functionality and are properly typed.

Also applies to: 16-16

core/dynamic-inject/src/FactoryQualifier.ts (1)

4-8: LGTM!

The decorator implementation is clean and follows the standard TypeScript decorator pattern by delegating to the utility class.

core/dynamic-inject/src/FactoryQualifierUtil.ts (1)

20-30: LGTM!

The getProperQualifiers method correctly retrieves and normalizes dynamic qualifiers, handling both single values and arrays, with appropriate fallback to an empty array.

plugin/tegg/test/fixtures/apps/dynamic-inject-app/modules/dynamic-inject-module/FactoryQualifierService.ts (1)

10-16: The qualifier mismatch is intentional for testing dynamic range validation errors.

The @FactoryQualifier(AbstractSingletonHello) decorator on line 11 intentionally restricts the factory to a different qualifier than the AbstractContextHello requested on line 16. The error handling at lines 19-20 captures this expected mismatch and returns the error message. The test in DynamicInject.test.ts confirms this is a negative test case, expecting the error: "AbstractContextHello is not in dynamic range: AbstractSingletonHello".

],
"scripts": {
"test": "cross-env NODE_ENV=test NODE_OPTIONS='--no-deprecation' mocha",
"test": "mocha",
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check package.json for test script and dependencies
echo "=== Checking core/dynamic-inject/package.json ==="
cat core/dynamic-inject/package.json | jq '.scripts.test, .devDependencies | has("cross-env")'

# Look for mocha config files in core/dynamic-inject
echo -e "\n=== Checking for mocha configuration files ==="
fd -e json -e js -e yml -e yaml 'mocharc|mocha.opts' core/dynamic-inject 2>/dev/null || echo "No mocha config files found"

# Check if mocha config exists in package.json
echo -e "\n=== Checking for mocha property in package.json ==="
cat core/dynamic-inject/package.json | jq '.mocha' 2>/dev/null || echo "No mocha property in package.json"

# Show test script and relevant dependencies
echo -e "\n=== Full test script and devDependencies ==="
cat core/dynamic-inject/package.json | jq '.scripts.test, .devDependencies'

Repository: eggjs/tegg

Length of output: 531


🏁 Script executed:

#!/bin/bash
cd core/dynamic-inject || exit 1

echo "=== Test script ==="
cat package.json | jq -r '.scripts.test'

echo -e "\n=== cross-env in devDependencies? ==="
cat package.json | jq 'has("devDependencies") and .devDependencies | has("cross-env")'

echo -e "\n=== Mocha config files ==="
ls -la .mocharc* 2>/dev/null || echo "No .mocharc files found"
ls -la mocha.opts 2>/dev/null || echo "No mocha.opts found"

echo -e "\n=== Mocha property in package.json ==="
cat package.json | jq '.mocha // "No mocha property"'

Repository: eggjs/tegg

Length of output: 340


Simplification of test script removes critical environment configuration without proper migration.

The test script has been simplified from cross-env NODE_ENV=test NODE_OPTIONS='--no-deprecation' mocha to just mocha. This removes explicit environment variable configuration:

  • NODE_ENV=test: Required for tests that check the environment mode
  • NODE_OPTIONS='--no-deprecation': Suppresses Node.js deprecation warnings

No mocha configuration file (.mocharc.json, .mocharc.js, etc.) or mocha property in package.json was created to preserve these settings. Additionally, cross-env remains in devDependencies but is now unused.

Verify that tests pass without these settings, or configure them via a mocha config file or the package.json test script. Remove the unused cross-env dependency.

🤖 Prompt for AI Agents
In core/dynamic-inject/package.json around line 18, the test script was
simplified to "mocha" which removed required environment setup (NODE_ENV=test
and NODE_OPTIONS='--no-deprecation') and left cross-env unused; restore the
environment configuration by either: 1) reverting the script to use cross-env
(e.g. cross-env NODE_ENV=test NODE_OPTIONS='--no-deprecation' mocha), or 2)
create a Mocha config file (e.g. .mocharc.json/.mocharc.js) and set NODE_OPTIONS
via an npm script that exports NODE_ENV=test (or use cross-env only for NODE_ENV
while moving NODE_OPTIONS into the Mocha config), then remove cross-env from
devDependencies if you choose the config-file approach and verify all tests pass
locally and in CI before committing.

Comment on lines +73 to +77
obj.getEggObject = function(...args: any[]) {
if (!this.dynamics.includes(args[0])) {
throw new Error(`${args[0].name} is not in dynamic range: ${this.dynamics.map(item => item.name).join(', ')}`);
}
return originalGetEggObject.apply(this, args);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Add validation and fix reference equality check.

Two issues:

  1. Missing argument validation: Line 73 accesses args[0] without checking if arguments were provided, which could cause runtime errors.

  2. Reference equality issue: Line 74 uses this.dynamics.includes(args[0]) which performs reference equality checks. If the abstract class passed at runtime is a different reference to the same class (e.g., re-imported or loaded from different module context), the check will fail even though it logically represents the same type.

🔎 Proposed fix
 obj.getEggObject = function(...args: any[]) {
+  if (args.length === 0 || !args[0]) {
+    throw new Error('Dynamic class argument is required');
+  }
-  if (!this.dynamics.includes(args[0])) {
+  const requestedDynamic = args[0];
+  const isAllowed = this.dynamics.some((allowedDynamic: any) => 
+    allowedDynamic === requestedDynamic || 
+    allowedDynamic.name === requestedDynamic.name
+  );
+  if (!isAllowed) {
     throw new Error(`${args[0].name} is not in dynamic range: ${this.dynamics.map(item => item.name).join(', ')}`);
   }
   return originalGetEggObject.apply(this, args);
 };
🤖 Prompt for AI Agents
In core/runtime/src/impl/EggObjectImpl.ts around lines 73 to 77, add argument
validation and replace the strict reference equality check: first ensure args[0]
exists and is a valid constructor/type (throw a clear error if missing or
invalid), then determine membership in this.dynamics by comparing logical
identity rather than reference — e.g., search this.dynamics for an entry that
either is the same reference or has the same name (or other stable identifier)
as args[0]; if no match is found throw the existing error message using the
provided argument name, and only then call originalGetEggObject.apply(this,
args).

@akitaSummer akitaSummer force-pushed the feat/add_dynamic_range branch from 9040cfe to 8ba4dcf Compare December 20, 2025 18:14
} else {
const injectObj = await EggContainerFactory.getOrCreateEggObject(proto, injectObject.objName);
let injectObj = await EggContainerFactory.getOrCreateEggObject(proto, injectObject.objName);
if (injectObj.proto.name === 'eggObjectFactory') {
Copy link
Contributor

Choose a reason for hiding this comment

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

这个逻辑应该写在 eggObjectFactory 内部,不应该写在 EggObjectImpl。

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.

3 participants