Skip to content

Conversation

@taeold
Copy link
Contributor

@taeold taeold commented Dec 4, 2025

This PR resolves two critical issues caused by the "Dual-Package Hazard" when using firebase-functions in ESM projects:

  1. Separate instances of the declaredParams in firebase-functions/params for CJS and ESM led to params not being detected when used in ESM modules.

To fix, we implement a global singleton pattern for declaredParams using globalThis and a version-scoped Symbol.for key. This ensures both builds share the same global storage for collecting param API uses.

  1. instanceof checks failed because the CJS Expression/ResetValue instances differed from the ESM instances.

To fix, we implement a custom [Symbol.hasInstance] on Expression and ResetValue classes using branded tags to identify instances across package boundaries.

Fixes #1775, #1777, #1779

We resolve two critical issues caused by the "Dual-Package Hazard" when using `firebase-functions` in ESM projects:

1.   Separate instances of the `declaredParams` in `firebase-functions/params` led to params not being detected when used in ESM modules.

To fix, we implement a Global Singleton pattern for `declaredParams` using `globalThis` and a version-scoped `Symbol.for` key. This ensures both builds share the same global storage for collecting param API uses.

1. `instanceof` checks failed because the CJS `Expression`/`ResetValue` instances deferred from the ESM ones.

To fix, we implement a custom `[Symbol.hasInstance]` on `Expression` and `ResetValue` classes using specfic branded tags to identify instances across package boundaries.
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @taeold, 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 provides crucial fixes for the "Dual-Package Hazard" within the firebase-functions library, specifically targeting issues that arise when mixing CommonJS and ESM modules. The changes ensure consistent behavior for parameter declarations and instanceof checks, regardless of the module system used, thereby enhancing the stability and predictability of the developer experience for Firebase Functions users, particularly those adopting ESM.

Highlights

  • Dual-Package Hazard Resolution for Parameters: Implemented a global singleton pattern for declaredParams using globalThis and a version-scoped Symbol.for key. This ensures that parameter declarations are correctly shared and detected across both CommonJS (CJS) and ECMAScript Module (ESM) environments, preventing issues where parameters were not recognized in ESM modules.
  • Cross-Module instanceof Compatibility: Added custom [Symbol.hasInstance] implementations to the Expression and ResetValue classes. These use specific branded global symbols to allow instanceof checks to correctly identify instances of these classes, even when they originate from different CJS and ESM builds of the firebase-functions package.
  • Enhanced Testing for Module Compatibility: Introduced new test cases for both CommonJS and ESM parameter usage within the scripts/bin-test/test.ts suite. These tests validate that parameters are correctly processed and detected in functions.yaml across different module systems.
  • Build-Time Version Injection: Configured the build process (tsdown.config.mts) to read the package's major version and inject it as a global constant (__FIREBASE_FUNCTIONS_MAJOR_VERSION__). This constant is used to version-scope the global symbols, ensuring compatibility and preventing conflicts across different major versions of the library.
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 effectively addresses the dual-package hazard for parameters in ESM projects by implementing two robust patterns: a global singleton for declaredParams and custom Symbol.hasInstance checks for Expression and ResetValue classes. The use of version-scoped global symbols is a thoughtful touch for ensuring forward compatibility. The changes are well-implemented and include corresponding test cases to validate the fix. I have one minor suggestion to improve the maintainability of the build configuration by reducing some code duplication.

This commit resolves two critical issues caused by the "Dual-Package Hazard" when using `firebase-functions` in ESM projects:

1.  **Split State (Missing Params)**:
    The SDK bootstrap code (CJS) and User Code (ESM) were using separate instances of the `declaredParams` array.
    *   **Fix**: Implemented a Global Singleton pattern for `declaredParams` using `globalThis` and a version-scoped `Symbol.for` key. This ensures both builds share the same storage.

2.  **Identity Mismatch (Deployment Error)**:
    `instanceof Expression` checks in the SDK bootstrap code failed because the user's `Expression` instance (ESM) differed from the bootstrap's class (CJS).
    *   **Fix**: Implemented `[Symbol.hasInstance]` on `Expression` and `ResetValue` classes.
    *   **Mechanism**: Uses `Symbol.for` tags (`firebase-functions:Expression:Tag`) to robustly identify instances across package boundaries, avoiding fragile duck-typing or strict class identity checks.

This ensures that parameterized configuration works correctly regardless of the module system used.
@taeold
Copy link
Contributor Author

taeold commented Dec 4, 2025

/gemini review

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 effectively resolves the "Dual-Package Hazard" for parameterized configurations in ESM projects. The approach of using a global singleton for declaredParams via globalThis and a version-scoped Symbol.for key is a robust solution to share state between CJS and ESM builds. Similarly, the use of a custom [Symbol.hasInstance] on Expression and ResetValue classes is a clever and correct way to fix instanceof checks across different module instances. The changes are well-implemented and include new tests to cover the fixes. Overall, this is a high-quality contribution that addresses a tricky problem. I have one minor suggestion regarding code style.

Copy link
Contributor

@jhuleatt jhuleatt left a comment

Choose a reason for hiding this comment

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

gnarly!

Once Node 20 is EOL in the spring, we should be able to stop publishing the cjs package entirely: https://nodejs.org/api/modules.html#loading-ecmascript-modules-using-require. Hopefully that will simplify things!

@taeold taeold added this pull request to the merge queue Dec 4, 2025
github-merge-queue bot pushed a commit that referenced this pull request Dec 4, 2025
* fix: Resolve dual-package hazard for params in ESM

We resolve two critical issues caused by the "Dual-Package Hazard" when using `firebase-functions` in ESM projects:

1.   Separate instances of the `declaredParams` in `firebase-functions/params` led to params not being detected when used in ESM modules.

To fix, we implement a Global Singleton pattern for `declaredParams` using `globalThis` and a version-scoped `Symbol.for` key. This ensures both builds share the same global storage for collecting param API uses.

1. `instanceof` checks failed because the CJS `Expression`/`ResetValue` instances deferred from the ESM ones.

To fix, we implement a custom `[Symbol.hasInstance]` on `Expression` and `ResetValue` classes using specfic branded tags to identify instances across package boundaries.

* fix: Resolve dual-package hazard for params in ESM

This commit resolves two critical issues caused by the "Dual-Package Hazard" when using `firebase-functions` in ESM projects:

1.  **Split State (Missing Params)**:
    The SDK bootstrap code (CJS) and User Code (ESM) were using separate instances of the `declaredParams` array.
    *   **Fix**: Implemented a Global Singleton pattern for `declaredParams` using `globalThis` and a version-scoped `Symbol.for` key. This ensures both builds share the same storage.

2.  **Identity Mismatch (Deployment Error)**:
    `instanceof Expression` checks in the SDK bootstrap code failed because the user's `Expression` instance (ESM) differed from the bootstrap's class (CJS).
    *   **Fix**: Implemented `[Symbol.hasInstance]` on `Expression` and `ResetValue` classes.
    *   **Mechanism**: Uses `Symbol.for` tags (`firebase-functions:Expression:Tag`) to robustly identify instances across package boundaries, avoiding fragile duck-typing or strict class identity checks.

This ensures that parameterized configuration works correctly regardless of the module system used.

* fix changelog

* clarify comments.

* formatter
@taeold taeold removed this pull request from the merge queue due to a manual request Dec 4, 2025
@taeold taeold added this pull request to the merge queue Dec 4, 2025
Merged via the queue into master with commit fdeacc3 Dec 4, 2025
32 checks passed
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.

Using parameters for minInstances runtime option fails during deployment with ESM (v7.0.0)

2 participants