Skip to content

feat(openfeature): vendor the flagging provider instead of an optional peer dependency - #9570

Merged
rochdev merged 9 commits into
masterfrom
rochdev/openfeature-bundler-entrypoint
Aug 13, 2026
Merged

feat(openfeature): vendor the flagging provider instead of an optional peer dependency#9570
rochdev merged 9 commits into
masterfrom
rochdev/openfeature-bundler-entrypoint

Conversation

@rochdev

@rochdev rochdev commented Jul 28, 2026

Copy link
Copy Markdown
Member

What does this PR do?

Vendors @datadog/openfeature-node-server, Datadog's OpenFeature provider, directly into the tracer package (bundled via rspack into vendor/dist/) instead of requiring the application to install it as a peer dependency. tracer.openfeature is the single, lazy entrypoint for the provider — it can be accessed and registered with @openfeature/server-sdk in either order.

As of @datadog/openfeature-node-server 2.1.0, the vendored provider no longer depends on @openfeature/server-sdk at all — it ships its own event emitter (NodeProviderEventEmitter) instead of importing OpenFeatureEventEmitter/ProviderEvents from the SDK. That's the only reason this needed special handling: OpenFeature's own event wiring (provider.events.addHandler(eventType, handler)) is duck-typed on event name strings, not emitter class identity, so the vendored provider and the application's own copy of @openfeature/server-sdk interoperate correctly without any shared dependency, bridge module, or dedicated instrumentation.

This also removes the generic multi-feature "feature registry" abstraction (feature-registry.js, openfeature/register.js, openfeature/require-provider.js) and the esbuild/webpack requireOptionalPeer rewriting machinery it depended on, since a fully self-contained vendored dependency doesn't need either: tracer.openfeature is now wired directly as a lazy module on the proxy, the same way the tracer's other lazy modules are.

FlaggingProvider#initialize also unrefs the vendored provider's initialization timer (with a TODO to remove once fixed upstream), so a short script or serverless handler isn't kept alive for up to initializationTimeoutMs just because it's waiting on remote configuration.

Motivation

Requiring @datadog/openfeature-node-server as an optional peer dependency assumed dd-trace and the application shared the same node_modules tree. That doesn't hold for Single Step Instrumentation (SSI), where dd-trace is installed outside the application's own directory: resolving the peer via module.createRequire(__filename) walks up node_modules from dd-trace's own install location, so it could never reach the application's node_modules where the provider was actually installed. Vendoring the provider removes the dependency on the application installing anything at all, so it's available regardless of where dd-trace itself lives.

Treating @openfeature/server-sdk and tracer.openfeature as independent optional peer dependencies also required dedicated esbuild/webpack plugin support to keep bundlers from breaking the optional requires, and behavior depended on the order the two were accessed relative to each other. Vendoring removes those constraints too: it behaves the same whether or not the application is bundled, no longer depends on access order, and — since the provider no longer has any runtime dependency of its own to reconcile with the application's copy — needs no bundler-specific support at all.

Additional Notes

This PR was generated with Claude Code.

@dd-octo-sts

dd-octo-sts Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Overall package size

Self size: 8.07 MB
Deduped: 8.73 MB
No deduping: 8.73 MB

Dependency sizes | name | version | self size | total size | |------|---------|-----------|------------| | import-in-the-middle | 3.3.3 | 125.43 kB | 441.68 kB | | opentracing | 0.14.7 | 194.81 kB | 194.81 kB | | dc-polyfill | 0.1.11 | 25.74 kB | 25.74 kB |

🤖 This report was automatically generated by heaviest-objects-in-the-universe

@datadog-datadog-us1-prod

datadog-datadog-us1-prod Bot commented Jul 28, 2026

Copy link
Copy Markdown

Tests

🎉 All green!

🧪 All tests passed
❄️ No new flaky tests detected

🎯 Code Coverage (details)
Patch Coverage: 100.00%
Overall Coverage: 98.52% (-0.04%)

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: a64371e | Docs | Datadog PR Page | Give us feedback!

@pr-commenter

pr-commenter Bot commented Jul 28, 2026

Copy link
Copy Markdown

Benchmarks

Benchmark execution time: 2026-08-13 03:18:13

Comparing candidate commit a64371e in PR branch rochdev/openfeature-bundler-entrypoint with baseline commit ec16c78 in branch master.

📊 Benchmarking dashboard

Found 0 performance improvements and 0 performance regressions! Performance is the same for 2325 metrics, 33 unstable metrics.

Explanation

This is an A/B test comparing a candidate commit's performance against that of a baseline commit. Performance changes are noted in the tables below as:

  • 🟩 = significantly better candidate vs. baseline
  • 🟥 = significantly worse candidate vs. baseline

We compute a confidence interval (CI) over the relative difference of means between metrics from the candidate and baseline commits, considering the baseline as the reference.

If the CI is entirely outside the configured SIGNIFICANT_IMPACT_THRESHOLD (or the deprecated UNCONFIDENCE_THRESHOLD), the change is considered significant.

Feel free to reach out to #apm-benchmarking-platform on Slack if you have any questions.

More details about the CI and significant changes

You can imagine this CI as a range of values that is likely to contain the true difference of means between the candidate and baseline commits.

CIs of the difference of means are often centered around 0%, because often changes are not that big:

---------------------------------(------|---^--------)-------------------------------->
                              -0.6%    0%  0.3%     +1.2%
                                 |          |        |
         lower bound of the CI --'          |        |
sample mean (center of the CI) -------------'        |
         upper bound of the CI ----------------------'

As described above, a change is considered significant if the CI is entirely outside the configured SIGNIFICANT_IMPACT_THRESHOLD (or the deprecated UNCONFIDENCE_THRESHOLD).

For instance, for an execution time metric, this confidence interval indicates a significantly worse performance:

----------------------------------------|---------|---(---------^---------)---------->
                                       0%        1%  1.3%      2.2%      3.1%
                                                  |   |         |         |
       significant impact threshold --------------'   |         |         |
                      lower bound of CI --------------'         |         |
       sample mean (center of the CI) --------------------------'         |
                      upper bound of CI ----------------------------------'

Unstable benchmarks

These benchmarks have a confidence interval too wide to call a change; treat them as noise rather than signal.

scenario:appsec-appsec-enabled-24

  • unstable execution_time [-208.056ms; +212.198ms] or [-7.685%; +7.837%]

scenario:appsec-appsec-enabled-26

  • unstable execution_time [-234.411ms; +226.687ms] or [-9.066%; +8.767%]

scenario:appsec-appsec-enabled-with-attacks-24

  • unstable execution_time [-162.888ms; +167.943ms] or [-5.218%; +5.380%]

scenario:appsec-appsec-enabled-with-attacks-26

  • unstable execution_time [-190.037ms; +182.274ms] or [-6.471%; +6.206%]

scenario:appsec-control-20

  • unstable execution_time [-134.573ms; +127.271ms] or [-8.059%; +7.622%]

scenario:appsec-control-24

  • unstable execution_time [-109.137ms; +122.821ms] or [-8.740%; +9.836%]

scenario:appsec-control-26

  • unstable execution_time [-130.418ms; +133.200ms] or [-10.367%; +10.588%]

scenario:appsec-iast-no-vulnerability-iast-enabled-default-config-20

  • unstable execution_time [-15638.244µs; +15418.074µs] or [-6.038%; +5.953%]

scenario:debugger-line-probe-with-snapshot-default-24

  • unstable cpu_user_time [-887.178ms; +349.879ms] or [-11.324%; +4.466%]
  • unstable execution_time [-883.749ms; +388.179ms] or [-10.327%; +4.536%]
  • unstable instructions [-7.5G instructions; +2.9G instructions] or [-11.818%; +4.534%]
  • unstable throughput [-152.954op/s; +308.986op/s] or [-4.030%; +8.142%]

scenario:debugger-line-probe-with-snapshot-default-26

  • unstable cpu_user_time [-2303.381ms; +761.714ms] or [-24.102%; +7.970%]
  • unstable execution_time [-2345.050ms; +758.016ms] or [-22.821%; +7.377%]
  • unstable instructions [-20.6G instructions; +6.7G instructions] or [-25.901%; +8.436%]
  • unstable throughput [-149.465op/s; +458.351op/s] or [-4.628%; +14.192%]

scenario:debugger-line-probe-with-snapshot-minimal-24

  • unstable cpu_user_time [-1613.706ms; +523.381ms] or [-19.970%; +6.477%]
  • unstable execution_time [-1600.719ms; +545.608ms] or [-18.205%; +6.205%]
  • unstable instructions [-13.6G instructions; +4.6G instructions] or [-20.722%; +6.946%]
  • unstable throughput [-164.391op/s; +458.508op/s] or [-4.390%; +12.245%]

scenario:dogstatsd-with-tags-20

  • unstable cpu_user_time [-232.589ms; +481.576ms] or [-4.920%; +10.188%]
  • unstable execution_time [-232.584ms; +483.215ms] or [-4.843%; +10.062%]
  • unstable throughput [-163130.015op/s; +92092.919op/s] or [-9.371%; +5.290%]

scenario:plugin-claude-agent-sdk-compact-stream-scan-26

  • unstable cpu_usage_percentage [-4.560%; +5.543%]

scenario:plugin-graphql-long-with-depth-and-collapse-off-20

  • unstable max_rss_usage [-19.456MB; +35.595MB] or [-4.984%; +9.118%]

scenario:plugin-graphql-long-with-depth-off-26

  • unstable max_rss_usage [-37.727MB; +27.374MB] or [-19.440%; +14.106%]

scenario:plugin-graphql-long-with-depth-on-max-20

  • unstable cpu_user_time [-578.908ms; +596.411ms] or [-5.033%; +5.185%]
  • unstable execution_time [-598.528ms; +609.767ms] or [-5.098%; +5.194%]
  • unstable throughput [-3.574op/s; +3.535op/s] or [-5.215%; +5.159%]

scenario:plugin-pg-service-26

  • unstable cpu_usage_percentage [-10.592%; +6.578%]
  • unstable execution_time [-88.600ms; +176.808ms] or [-9.791%; +19.540%]
  • unstable throughput [-889644.607op/s; +389099.511op/s] or [-13.113%; +5.735%]

scenario:test-optimization-large-suite-20

  • unstable max_rss_usage [-4507.882KB; +6071.215KB] or [-5.647%; +7.606%]

@codecov

codecov Bot commented Jul 28, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.52%. Comparing base (ec16c78) to head (a64371e).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #9570      +/-   ##
==========================================
- Coverage   98.56%   98.52%   -0.05%     
==========================================
  Files         972      967       -5     
  Lines      140837   140644     -193     
  Branches    12982    12031     -951     
==========================================
- Hits       138821   138570     -251     
- Misses       2016     2074      +58     
Flag Coverage Δ
aiguard 57.43% <83.87%> (-0.10%) ⬇️
aiguard-integration 55.63% <83.87%> (-0.07%) ⬇️
apm-bucket-0 57.16% <83.87%> (-0.14%) ⬇️
apm-bucket-1 63.30% <83.87%> (-0.08%) ⬇️
apm-bucket-2 62.14% <83.87%> (-0.09%) ⬇️
apm-bucket-3 59.73% <83.87%> (-0.09%) ⬇️
apm-capabilities-tracing 62.27% <71.69%> (-0.24%) ⬇️
apm-integrations-aerospike 56.20% <83.87%> (-0.10%) ⬇️
apm-integrations-confluentinc-kafka-javascript 61.09% <83.87%> (-0.09%) ⬇️
apm-integrations-couchbase 56.64% <83.87%> (-0.10%) ⬇️
apm-integrations-http 61.84% <83.87%> (-0.08%) ⬇️
apm-integrations-kafkajs 61.65% <83.87%> (-0.09%) ⬇️
apm-integrations-next 59.34% <83.87%> (-0.09%) ⬇️
apm-integrations-prisma 58.45% <83.87%> (-0.09%) ⬇️
appsec 72.04% <83.87%> (-0.09%) ⬇️
appsec-express_fastify_graphql 69.38% <83.87%> (-0.06%) ⬇️
appsec-integration 50.04% <83.87%> (-0.09%) ⬇️
appsec-kafka_ldapjs_lodash 63.35% <83.87%> (-0.08%) ⬇️
appsec-mongodb-core_mongoose_mysql 66.82% <83.87%> (-0.06%) ⬇️
appsec-next 56.61% <83.87%> (-0.08%) ⬇️
appsec-node-serialize_passport_postgres 66.23% <83.87%> (-0.06%) ⬇️
appsec-sourcing_stripe_template 64.66% <83.87%> (-0.07%) ⬇️
debugger 64.18% <83.87%> (-0.06%) ⬇️
instrumentations-bucket-0 51.60% <83.87%> (-0.11%) ⬇️
instrumentations-bucket-1 59.59% <83.87%> (-0.09%) ⬇️
instrumentations-bucket-10 60.83% <83.87%> (-0.08%) ⬇️
instrumentations-bucket-11 61.48% <83.87%> (-0.08%) ⬇️
instrumentations-bucket-12 51.51% <83.87%> (-0.11%) ⬇️
instrumentations-bucket-13 52.36% <83.87%> (-0.10%) ⬇️
instrumentations-bucket-14 51.62% <83.87%> (-0.11%) ⬇️
instrumentations-bucket-2 52.84% <83.87%> (-0.10%) ⬇️
instrumentations-bucket-3 53.49% <83.87%> (-0.10%) ⬇️
instrumentations-bucket-4 58.65% <83.87%> (-0.09%) ⬇️
instrumentations-bucket-5 49.33% <83.87%> (-0.10%) ⬇️
instrumentations-bucket-6 60.21% <83.87%> (-0.09%) ⬇️
instrumentations-bucket-7 51.80% <83.87%> (-0.11%) ⬇️
instrumentations-bucket-8 58.33% <83.87%> (-0.09%) ⬇️
instrumentations-bucket-9 57.18% <83.87%> (-0.10%) ⬇️
instrumentations-instrumentation-couchbase 50.85% <83.87%> (-0.11%) ⬇️
instrumentations-integration-esbuild 34.06% <53.33%> (-0.15%) ⬇️
llmobs-ai_anthropic_bedrock 62.81% <83.87%> (-0.07%) ⬇️
llmobs-bucket-1 61.29% <83.87%> (-0.07%) ⬇️
llmobs-openai 61.69% <83.87%> (-0.08%) ⬇️
llmobs-openai-agents_vertex-ai 59.96% <83.87%> (-0.09%) ⬇️
llmobs-sdk 66.72% <83.87%> (-0.08%) ⬇️
master-coverage 98.52% <100.00%> (?)
openfeature 55.61% <100.00%> (-0.07%) ⬇️
openfeature-unit 53.23% <100.00%> (+<0.01%) ⬆️
platform-core_esbuild_instrumentations-misc 41.09% <83.87%> (-0.15%) ⬇️
platform-integration 60.36% <83.87%> (-0.06%) ⬇️
platform-shimmer_unit-guardrails_webpack 38.68% <83.87%> (-0.22%) ⬇️
plugins-bucket-0 56.87% <83.87%> (-0.09%) ⬇️
plugins-bucket-1 53.95% <83.87%> (-0.08%) ⬇️
plugins-bucket-11 61.41% <83.87%> (-0.09%) ⬇️
plugins-bucket-17 61.23% <83.87%> (-0.09%) ⬇️
plugins-bucket-18 61.87% <83.87%> (-0.08%) ⬇️
plugins-bucket-19 61.25% <83.87%> (-0.09%) ⬇️
plugins-bucket-20 63.66% <83.87%> (-0.08%) ⬇️
plugins-bucket-4 58.25% <83.87%> (-0.09%) ⬇️
plugins-bullmq_cassandra_cookie 61.31% <83.87%> (-0.09%) ⬇️
plugins-cookie-parser_crypto_dd-trace-api 56.29% <85.71%> (-0.10%) ⬇️
plugins-fetch_fs_generic-pool 58.15% <83.87%> (-0.13%) ⬇️
plugins-google-cloud-pubsub_grpc_handlebars 64.08% <83.87%> (-0.08%) ⬇️
plugins-hapi_hono_ioredis 59.84% <83.87%> (-0.09%) ⬇️
plugins-knex_langgraph_ldapjs 55.00% <83.87%> (-0.10%) ⬇️
plugins-light-my-request_limitd-client_lodash 58.31% <83.87%> (-0.08%) ⬇️
plugins-mariadb_memcached_mercurius 61.23% <83.87%> (-0.08%) ⬇️
plugins-mongodb_mongodb-core_mongoose 59.19% <83.87%> (-0.09%) ⬇️
plugins-multer_mysql_mysql2 58.78% <83.87%> (-0.09%) ⬇️
plugins-nats_node-serialize_opensearch 60.33% <83.87%> (-0.09%) ⬇️
plugins-passport-http_pino_postgres 58.53% <83.87%> (-0.09%) ⬇️
plugins-process_pug_redis 57.33% <83.87%> (-0.10%) ⬇️
plugins-undici_url_valkey 57.96% <83.87%> (-0.09%) ⬇️
plugins-vm_winston_ws 59.53% <83.87%> (-0.09%) ⬇️
profiling 61.45% <83.87%> (-0.08%) ⬇️
serverless-aws-sdk-aws-sdk 55.07% <83.87%> (-0.09%) ⬇️
serverless-aws-sdk-base-inject-field 50.83% <83.87%> (-0.11%) ⬇️
serverless-aws-sdk-bedrockruntime 54.60% <83.87%> (-0.09%) ⬇️
serverless-aws-sdk-client 56.16% <83.87%> (-0.09%) ⬇️
serverless-aws-sdk-dynamodb 55.43% <83.87%> (-0.09%) ⬇️
serverless-aws-sdk-eventbridge 49.64% <83.87%> (-0.10%) ⬇️
serverless-aws-sdk-kinesis 59.02% <83.87%> (-0.09%) ⬇️
serverless-aws-sdk-lambda 57.18% <83.87%> (-0.09%) ⬇️
serverless-aws-sdk-s3 55.53% <83.87%> (-0.09%) ⬇️
serverless-aws-sdk-serverless-peer-service 59.28% <83.87%> (-0.09%) ⬇️
serverless-aws-sdk-sns 59.83% <83.87%> (-0.08%) ⬇️
serverless-aws-sdk-sqs 60.24% <83.87%> (-0.08%) ⬇️
serverless-aws-sdk-stepfunctions 55.36% <83.87%> (-0.09%) ⬇️
serverless-aws-sdk-util 51.36% <83.87%> (-0.11%) ⬇️
serverless-bucket-0 54.00% <83.87%> (-0.08%) ⬇️
serverless-bucket-1 58.79% <83.87%> (-0.09%) ⬇️
test-optimization-cucumber 71.13% <83.33%> (-0.06%) ⬇️
test-optimization-cypress 65.00% <70.00%> (+0.03%) ⬆️
test-optimization-jest 72.49% <83.87%> (-0.05%) ⬇️
test-optimization-mocha 72.18% <83.87%> (+<0.01%) ⬆️
test-optimization-playwright-playwright-atr 59.87% <70.00%> (-0.06%) ⬇️
test-optimization-playwright-playwright-efd 60.03% <70.00%> (-0.05%) ⬇️
test-optimization-playwright-playwright-final-status 60.19% <70.00%> (-0.06%) ⬇️
test-optimization-playwright-playwright-impacted-tests 59.74% <70.00%> (+0.10%) ⬆️
test-optimization-playwright-playwright-reporting 61.05% <70.00%> (-0.12%) ⬇️
test-optimization-playwright-playwright-test-management 60.71% <70.00%> (-0.14%) ⬇️
test-optimization-playwright-playwright-test-span 59.92% <70.00%> (-0.11%) ⬇️
test-optimization-selenium 59.12% <70.00%> (-0.18%) ⬇️
test-optimization-testopt 57.72% <83.87%> (+0.03%) ⬆️
test-optimization-vitest 73.28% <83.33%> (+0.01%) ⬆️
test-optimization-vitest-browser 58.98% <70.00%> (-0.05%) ⬇️
test-optimization-webdriverio 65.37% <83.87%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@rochdev rochdev changed the title feat(openfeature): return a ready provider from the openfeature entrypoint feat(openfeature): vendor the flagging provider instead of an optional peer dependency Aug 1, 2026
@rochdev
rochdev force-pushed the rochdev/openfeature-bundler-entrypoint branch from 9faa31a to 8b21280 Compare August 1, 2026 01:08
@rochdev
rochdev marked this pull request as ready for review August 3, 2026 03:50
@rochdev
rochdev requested review from a team as code owners August 3, 2026 03:50
@rochdev
rochdev requested review from dd-oleksii, leoromanovsky and tlhunter and removed request for a team August 3, 2026 03:50

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e2db427c4c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread packages/dd-trace/src/openfeature/server-sdk-bridge.js Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: caa112c44a

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread packages/dd-trace/src/openfeature/server-sdk-bridge.js Outdated

@BridgeAR BridgeAR left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please keep the bundler tests, since they verify things are working as expected.

Comment thread scripts/check_licenses.js
if (!packagePath || dependency.link || (dependency.dev && !dependency.devOptional)) continue
// A peer dependency is supplied by the consumer, not shipped by this package, the same
// way addYarnProductionDependencies excludes peerDependencies from its graph walk.
if (!packagePath || dependency.link || dependency.peer || (dependency.dev && !dependency.devOptional)) continue

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We could actually still have a bundled part, even if it is a peer dependency

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Not sure I understand the above, please clarify.

@rochdev

rochdev commented Aug 3, 2026

Copy link
Copy Markdown
Member Author

Please keep the bundler tests, since they verify things are working as expected.

They were validating the old approach explicitly and were replaced by new tests that test the behaviour instead.

@tlhunter

tlhunter commented Aug 3, 2026

Copy link
Copy Markdown
Member

A new openfeature-server-sdk instrumentation fills that bridge from whichever copy of @openfeature/server-sdk the application itself requires, so the vendored provider and the application's copy share the same event emitter identity — including when the application is bundled, since the existing generic bundler-instrumentation mechanism (modulesOfInterest / dd-trace:bundler:load) already covers any addHook()-registered package.

I had a similar issue when I built dc-polyfill. I solved it by creating a global registry that can be shared across package versions. Is that something we can do here as well? Maybe not as every single version of dc-polyfill had such a concept but your package won't...

Comment thread vendor/rspack.config.js Outdated
tlhunter
tlhunter previously approved these changes Aug 3, 2026

@tlhunter tlhunter left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I didn't notice any issues.

@leoromanovsky leoromanovsky left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

  1. Fix the test command.

On the exact PR head, npm run test:openfeature reported:

Cannot find any files matching pattern "packages/datadog-instrumentations/test/openfeature-server-sdk.spec.{js,mjs}"

The suite reported 209 passing tests, but the newly added instrumentation spec did not run.

  1. Retain real black-box tests. The PR deletes the dedicated esbuild and Webpack

OpenFeature tests. I manually verified CJS, documented ESM loading, and esbuild at head caa112c, but unit-testing the bridge is not durable protection for the customer integration.

  1. Either add the dd-trace-api/SSI provider handoff or explicitly state that end-to-end FFE through pure SSI remains future work.

@greghuels

greghuels commented Aug 4, 2026

Copy link
Copy Markdown

@rochdev Let's assume we are able to remove @openfeature/server-sdk as a runtime dependency of @datadog/openfeature-node-server. In that situation, would there be any concerns with simply adding @datadog/openfeature-node-server as a patch-level range dependency (i.e. "@datadog/openfeature-node-server": "~1.2.3")?

That would allow for security updates in @datadog/openfeature-node-server to be automatically applied for non-SSI installs and would simplify the build and CI complexity in dd-trace-js.

But as a stop-gap, I'd be fine with moving this forward so long as @leoromanovsky's concerns are addressed.

@rochdev

rochdev commented Aug 7, 2026

Copy link
Copy Markdown
Member Author

I had a similar issue when I built dc-polyfill. I solved it by creating a global registry that can be shared across package versions. Is that something we can do here as well? Maybe not as every single version of dc-polyfill had such a concept but your package won't...

What would be the benefit of the registry over an instrumentation? (assuming it would be possible which it may not be as you pointed out because of version support)

@rochdev

rochdev commented Aug 7, 2026

Copy link
Copy Markdown
Member Author

@greghuels Assuming the dependency could be removed completely that would definitely be the best case scenario, but it's unclear whether that would be achievable. By vendoring and defining an external, we're basically removing the dependency by force externally and then replacing it with a placeholder that is then replaced by the real dependency from the user at import time, but if there was no dependency to being with that wouldn't be needed.

@rochdev

rochdev commented Aug 7, 2026

Copy link
Copy Markdown
Member Author

@leoromanovsky Everything should be addressed!

@greghuels

Copy link
Copy Markdown

@greghuels Assuming the dependency could be removed completely that would definitely be the best case scenario, but it's unclear whether that would be achievable. By vendoring and defining an external, we're basically removing the dependency by force externally and then replacing it with a placeholder that is then replaced by the real dependency from the user at import time, but if there was no dependency to being with that wouldn't be needed.

@rochdev Yeah, I think there needs to be an investigation on our end to see if we can do this without causing breaking changes. In the meantime, I think your change here makes sense.

greghuels added a commit to DataDog/openfeature-js-client that referenced this pull request Aug 7, 2026
Adds a pre-publish script that fails if @openfeature/server-sdk or
@openfeature/core appear in dependencies/peerDependencies, or if
the compiled .js files contain any runtime @openfeature imports.

This prevents accidentally reintroducing the runtime dependency
that would break SSI compatibility with dd-trace-js, where dd-trace
is installed outside the application's node_modules tree and would
get a separate copy of the SDK with a different event emitter
identity.

See DataDog/dd-trace-js#9570 for context.
greghuels added a commit to DataDog/openfeature-js-client that referenced this pull request Aug 7, 2026
* Custom EventEmitter

* Bundle type declarations to remove @openfeature devDependencies from .d.ts output

Add dts-bundle-generator to inline all @openfeature/core and
@openfeature/server-sdk types into a single bundled index.d.ts.
This eliminates unresolved import type references in the published
.d.ts files, which previously caused 'Cannot find module' errors for
TypeScript consumers since neither package is declared as a runtime
dependency or peerDependency.

Changes:
- package.json: types field now points to bundled index.d.ts
- package.json: build script runs build:types after tsc builds
- package.json: added dts-bundle-generator to devDependencies
- index.d.ts: generated bundled type declarations (642 lines, zero
  @openfeature references, node:diagnostics_channel imported externally)

* Fix yarn install: pin TypeScript to 5.9.3 via resolutions

dts-bundle-generator declares typescript >=5.0.2 as a regular
dependency, causing yarn to resolve TypeScript 7.0.2 for its
nested copy. The built-in compat/typescript patch then fails with
ENOENT trying to lstat an incorrect path. Adding a resolutions
field in the root package.json forces all TypeScript resolutions
to 5.9.3, matching the existing devDependency constraint.

Also includes yarn.lock update for dts-bundle-generator and
devDependencies reordering by yarn.

* Don't export inlined OpenFeature types from bundled declarations

Use --export-referenced-types false so inlined types like Provider,
ResolutionDetails, ServerProviderEvents, ProviderEventEmitter, etc.
are used in DatadogNodeServerProvider's method signatures but not
exported as standalone types. This prevents consumers from
accidentally importing our bundled copy of these types instead of
getting them from their own @openfeature/server-sdk installation.

The bundled index.d.ts now exports only:
- UniversalFlagConfigurationV1
- DatadogNodeServerProviderOptions
- DatadogNodeServerProvider

All OpenFeature types remain inlined (zero @openfeature references)
for SSI compatibility, but are invisible to consumers' import
statements. TypeScript still performs structural comparison when
the customer passes the provider to their own OpenFeature SDK.

* Fix dependency build metadata

* Fix build:types temp file cleanup and add index.d.ts to clean script

- Use trap EXIT in build:types to ensure src/.dts-entry.ts is removed
  even if dts-bundle-generator fails
- Add src/.dts-entry.ts to .gitignore as a safety net
- Add index.d.ts to the clean script so all generated artifacts are
  removed consistently

* Add prepack guard to prevent @openfeature/server-sdk runtime dependency

Adds a pre-publish script that fails if @openfeature/server-sdk or
@openfeature/core appear in dependencies/peerDependencies, or if
the compiled .js files contain any runtime @openfeature imports.

This prevents accidentally reintroducing the runtime dependency
that would break SSI compatibility with dd-trace-js, where dd-trace
is installed outside the application's node_modules tree and would
get a separate copy of the SDK with a different event emitter
identity.

See DataDog/dd-trace-js#9570 for context.

* Fix duplicate resolutions block in root package.json

The earlier TypeScript resolution was added as a second resolutions
block instead of merging with the existing one, causing yarn to
ignore the typescript@npm:>=5.0.2 pin and crash on TS 7.0.2's
compat patch. Merged into a single resolutions block.

* Add comment explaining why build:types is needed

* formatting

* test: add TypeScript consumer test for node-server package

The node-server package's bundled index.d.ts (generated by
dts-bundle-generator) had no TypeScript consumer test to verify it
compiles correctly from a consumer's perspective. The existing
test-app-node/test.js only tested runtime require() behavior.

This adds:
- test-app-node/tsconfig.json: consumer-style TypeScript config
- test-app-node/typecheck.ts: imports and exercises the public API
  (DatadogNodeServerProvider, UniversalFlagConfigurationV1, etc.)
- test-app-node/package.json: adds typescript + @types/node devDeps
- scripts/test-node-package-install.sh: runs tsc --noEmit before
  runtime tests

The type check runs in both scenarios:
1. Without @openfeature/* installed (SSI/dd-trace) — verifies the
   bundled index.d.ts is self-contained with no dangling references
2. With @openfeature/* installed (normal consumer) — verifies type
   compatibility with the real OpenFeature SDK

* test: add tests for NodeProviderEventEmitter

Cover emit/addHandler, handler error isolation (incl. async rejections
and a throwing logger), removeHandler LIFO semantics, removeAllHandlers
scoping, getHandlers, and setLogger chaining/current-logger routing.

* format

* test: verify OpenFeature.setProvider(provider) type-checks against minimum SDK version

Add Test 11 to typecheck.ts that calls OpenFeature.setProvider(provider)
to catch type drift between the bundled index.d.ts and the Provider
interface expected by @openfeature/server-sdk. Uses @ts-ignore on the
import so the file still compiles in the SSI/dd-trace scenario where
@openfeature/server-sdk is not installed.

Add CI step to run the node install test against the minimum supported
OpenFeature SDK versions (server-sdk 1.15.0, core 1.3.0) per
COMPATIBILITY.md, ensuring the type check catches regressions at the
lower bound of the supported range.

@greghuels greghuels left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@rochdev @datadog/openfeature-node-server should no longer have a dependency on @openfeature/server-sdk. The change to remove @openfeature/server-sdk as a dependency was merged here and released in version 2.1.0.

So it might make sense to close this PR out and simply add @datadog/openfeature-node-server as a patch-range dependency (i.e. "~2.1.0").

@rochdev

rochdev commented Aug 7, 2026

Copy link
Copy Markdown
Member Author

Updated to remove all the hackery around the OpenFeature SDK. Still vendoring our own library as ideally we would do that with everything for consistency, to avoid weird issues when mixing bundlers and ESM, to reduce package size, and reduce complexity and time of linking dependencies, and eventually be self-contained to maximize portability. Any library not done yet is because they have some incompatibility with being vendored, not by choice.

@leoromanovsky @greghuels Should be ready for re-review!

Comment thread package.json
"@babel/helpers": "^8.0.0",
"@eslint/eslintrc": "^3.3.5",
"@eslint/js": "^10.0.1",
"@datadog/openfeature-node-server": "2.1.0",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

exact version pin needed?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

We started doing this in #5995. Unfortunately the PR doesn't explain the reasoning, but I prefer to keep it the same so that it stays consistent. The good news is that it doesn't really change anything because the vendoring is done from the lockfile, so pin or no pin is effectively the same there, and it makes sense to keep the dev dependency in line with that. This is also automatically updated weekly by Dependabot.

@greghuels greghuels Aug 10, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The good news is that it doesn't really change anything because the vendoring is done from the lockfile

Agreed that with vendoring there's no difference. It might be worth documenting why vendoring is necessary though (or pointing us to existing documentation). Getting to a place where transient packages can be upgraded by customers without a version bump of dd-trace has the benefit of allowing customers to pull in hotfixes and security patches of transient dependencies via yarn upgrade or npm update, even if they are many versions behind latest dd-trace. If the idea is to mitigate zero-day supply chain exploits, then that's a legit reason that should at least be documented.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

It also does the opposite in that it allows zero-day exploits to actually land to begin with. So vendoring with a 5 days lag allows us to never ship the exploit to begin with. If the exploit is discovered more than 5 days later, then we can expedite a fix as well without waiting for Dependabot. Trade-offs all around, but given the other benefits of vendoring and the fact that we already do this for everything else (except native extensions because it's not possible to vendor them and we instead avoid installing entirely where not supported) I'd rather keep things consistent. Happy to revisit any past decisions later on but that's out of scope of this PR.

@greghuels greghuels Aug 11, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Yeah, that's what I was calling out by referencing zero day attacks. It's a good reason to keep vendoring. Like you said, tradeoffs either way. It's just unclear on my end if that was motivation behind vendoring. In any case, you should be good to go with this change.

greghuels
greghuels previously approved these changes Aug 7, 2026
@rochdev
rochdev force-pushed the rochdev/openfeature-bundler-entrypoint branch from 9ea2bf5 to f479b46 Compare August 10, 2026 02:59
leoromanovsky
leoromanovsky previously approved these changes Aug 11, 2026
rochdev and others added 9 commits August 12, 2026 23:03
…point

dd-trace/openfeature previously only existed as a side-effect require for
file tracers (#9324). Turn it into the real public entrypoint: it now
returns a usable FlaggingProvider instance after tracer.init(), and
tracer.openfeature is deprecated in its favor since it doesn't work in
bundled applications.

Extract the base provider class into a factory so it can be constructed
from either the bundler-opaque require-provider wrapper (legacy
tracer.openfeature) or a plain require (the new entrypoint) without
duplicating the class body.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
openfeature.js now pulls in the full tracer package to check
tracer.init() state, so nodeFileTrace has a much larger dependency
tree to walk. The default 5s mocha timeout was tight enough that CI
runners under load exceeded it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…l peer

`@datadog/openfeature-node-server` was an optional peer dependency resolved
through a bundler-opaque require, requiring dedicated esbuild/webpack plugin
code to keep the require invisible when the peer wasn't installed (#8635) and
to inline it so bundles survived relocation when it was (#8980). Vendoring the
provider into `vendor/dist/` removes the need for a peer at all, so all of
that machinery (`feature-registry.js`, `register.js`, `require-provider.js`,
the optional-peer esbuild/webpack loaders, and their integration tests) is
deleted in favor of a single lazy `tracer.openfeature` property.

The vendored provider's only remaining external dependency is
`@openfeature/server-sdk`, used solely for `OpenFeatureEventEmitter` and
`ProviderEvents`. Bundling our own copy would give those a different identity
than the customer's, so it's externalized to a small bridge module
(`server-sdk-bridge.js`) that a new `openfeature-server-sdk` instrumentation
fills in from the customer's own `require()`.

`FlaggingProvider#initialize` also unrefs the vendored provider's
initialization timer, which otherwise keeps an idle process (a short script, a
serverless handler) alive for up to `initializationTimeoutMs` while waiting
for configuration to arrive. TODO left to remove this once
`@datadog/openfeature-node-server` unrefs it upstream.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
scripts/check_licenses.js treated every non-dev entry in
vendor/package-lock.json as requiring license attribution, including
`peer: true` entries. Those record an unresolved peer dependency range
that npm never actually installs, so nothing is shipped for them. The
official dd-license-attribution tool used in CI already excludes these,
so the local script disagreed with CI and had two extraneous rows
(@openfeature/core, @openfeature/server-sdk) added to satisfy it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The prior fix's comment claimed npm never installs peer entries, which is
false under npm 7+ auto-install-peers - vendor/node_modules physically has
@openfeature/core and @openfeature/server-sdk installed. The real reason
they don't need attribution is that peer dependencies are supplied by the
consumer rather than shipped by this package, matching how the yarn.lock
walk already excludes peerDependencies. The pre-existing vendor-peer test
fixture assumed the opposite and was never validated against real CI
behavior for a peer case, so it broke once the npm-lock scan started
excluding peers too.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… deferred bridge emitter

@openfeature/core's transferListeners always calls oldProvider.events?.removeHandler(...)
on every subsequent OpenFeature.setProvider() call for a domain, regardless of whether the
app registered any handlers itself. The deferred emitter only implemented addHandler/emit,
so replacing the Datadog OpenFeature provider with another provider at runtime threw
"oldProvider.events.removeHandler is not a function", crashing the customer's app.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Fix test:openfeature script so the instrumentation spec actually runs:
  mocha treats a brace-only glob segment as a literal filename, so add
  a wildcard segment that both mocha and verify-exercised-tests resolve.
- Add real esbuild/webpack black-box tests that bundle
  @openfeature/server-sdk (not marked external) and assert dd-trace's
  bundler-instrumentation mechanism still bridges the real event
  emitter into the vendored provider.
- Correct comments in proxy.js and the dd-trace-api plugin: SSI itself
  is fixed by this PR's vendoring; the only remaining gap is that the
  dd-trace-api shim has no openfeature handoff.

Generated with Claude Code.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@datadog/openfeature-node-server 2.1.0 ships its own ProviderEventEmitter
and no longer depends on @openfeature/server-sdk at all (neither as a
dependency nor a peer), so the identity-preserving bridge this PR
introduced is no longer needed:

- Bump @datadog/openfeature-node-server to 2.1.0 everywhere it's pinned
  (vendor, root devDependencies, plugin versions matrix) and rebuild
  vendor/dist.
- Remove the @openfeature/server-sdk external redirect from
  vendor/rspack.config.js -- the vendored provider no longer references
  that package at all.
- Delete server-sdk-bridge.js, the openfeature-server-sdk require-hook
  instrumentation, and their specs.
- Remove the now-obsolete esbuild/webpack black-box tests that verified
  the bridge survived bundling -- there's no bridge left to verify.
- Update file-tracing.spec.js's expected traced files and loaded-modules
  list to match.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…erver

root package.json's devDependency was bumped to 2.1.0 but yarn.lock still
pinned 2.0.2, which made bun's yarn.lock-migration fail to resolve the
package in plugin test CI.
@rochdev
rochdev force-pushed the rochdev/openfeature-bundler-entrypoint branch from f479b46 to a64371e Compare August 13, 2026 03:04

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a64371e3f2

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread openfeature.js
@rochdev
rochdev merged commit 6ad8248 into master Aug 13, 2026
685 checks passed
@rochdev
rochdev deleted the rochdev/openfeature-bundler-entrypoint branch August 13, 2026 03:47
dd-octo-sts Bot pushed a commit that referenced this pull request Aug 13, 2026
…l peer dependency (#9570)

* feat(openfeature): return a ready provider from the openfeature entrypoint

dd-trace/openfeature previously only existed as a side-effect require for
file tracers (#9324). Turn it into the real public entrypoint: it now
returns a usable FlaggingProvider instance after tracer.init(), and
tracer.openfeature is deprecated in its favor since it doesn't work in
bundled applications.

Extract the base provider class into a factory so it can be constructed
from either the bundler-opaque require-provider wrapper (legacy
tracer.openfeature) or a plain require (the new entrypoint) without
duplicating the class body.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* test(openfeature): raise nft trace timeout for the entrypoint test

openfeature.js now pulls in the full tracer package to check
tracer.init() state, so nodeFileTrace has a much larger dependency
tree to walk. The default 5s mocha timeout was tight enough that CI
runners under load exceeded it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* feat(openfeature): vendor the flagging provider instead of an optional peer

`@datadog/openfeature-node-server` was an optional peer dependency resolved
through a bundler-opaque require, requiring dedicated esbuild/webpack plugin
code to keep the require invisible when the peer wasn't installed (#8635) and
to inline it so bundles survived relocation when it was (#8980). Vendoring the
provider into `vendor/dist/` removes the need for a peer at all, so all of
that machinery (`feature-registry.js`, `register.js`, `require-provider.js`,
the optional-peer esbuild/webpack loaders, and their integration tests) is
deleted in favor of a single lazy `tracer.openfeature` property.

The vendored provider's only remaining external dependency is
`@openfeature/server-sdk`, used solely for `OpenFeatureEventEmitter` and
`ProviderEvents`. Bundling our own copy would give those a different identity
than the customer's, so it's externalized to a small bridge module
(`server-sdk-bridge.js`) that a new `openfeature-server-sdk` instrumentation
fills in from the customer's own `require()`.

`FlaggingProvider#initialize` also unrefs the vendored provider's
initialization timer, which otherwise keeps an idle process (a short script, a
serverless handler) alive for up to `initializationTimeoutMs` while waiting
for configuration to arrive. TODO left to remove this once
`@datadog/openfeature-node-server` unrefs it upstream.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(build): exclude unresolved peer entries from license check

scripts/check_licenses.js treated every non-dev entry in
vendor/package-lock.json as requiring license attribution, including
`peer: true` entries. Those record an unresolved peer dependency range
that npm never actually installs, so nothing is shipped for them. The
official dd-license-attribution tool used in CI already excludes these,
so the local script disagreed with CI and had two extraneous rows
(@openfeature/core, @openfeature/server-sdk) added to satisfy it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* test(licenses): stop expecting npm peer dependencies to need attribution

The prior fix's comment claimed npm never installs peer entries, which is
false under npm 7+ auto-install-peers - vendor/node_modules physically has
@openfeature/core and @openfeature/server-sdk installed. The real reason
they don't need attribution is that peer dependencies are supplied by the
consumer rather than shipped by this package, matching how the yarn.lock
walk already excludes peerDependencies. The pre-existing vendor-peer test
fixture assumed the opposite and was never validated against real CI
behavior for a peer case, so it broke once the npm-lock scan started
excluding peers too.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(openfeature): implement full ProviderEventEmitter contract in the deferred bridge emitter

@openfeature/core's transferListeners always calls oldProvider.events?.removeHandler(...)
on every subsequent OpenFeature.setProvider() call for a domain, regardless of whether the
app registered any handlers itself. The deferred emitter only implemented addHandler/emit,
so replacing the Datadog OpenFeature provider with another provider at runtime threw
"oldProvider.events.removeHandler is not a function", crashing the customer's app.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(openfeature): address review feedback on tests and SSI scope

- Fix test:openfeature script so the instrumentation spec actually runs:
  mocha treats a brace-only glob segment as a literal filename, so add
  a wildcard segment that both mocha and verify-exercised-tests resolve.
- Add real esbuild/webpack black-box tests that bundle
  @openfeature/server-sdk (not marked external) and assert dd-trace's
  bundler-instrumentation mechanism still bridges the real event
  emitter into the vendored provider.
- Correct comments in proxy.js and the dd-trace-api plugin: SSI itself
  is fixed by this PR's vendoring; the only remaining gap is that the
  dd-trace-api shim has no openfeature handoff.

Generated with Claude Code.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* feat(openfeature): drop the server-sdk event-emitter bridge

@datadog/openfeature-node-server 2.1.0 ships its own ProviderEventEmitter
and no longer depends on @openfeature/server-sdk at all (neither as a
dependency nor a peer), so the identity-preserving bridge this PR
introduced is no longer needed:

- Bump @datadog/openfeature-node-server to 2.1.0 everywhere it's pinned
  (vendor, root devDependencies, plugin versions matrix) and rebuild
  vendor/dist.
- Remove the @openfeature/server-sdk external redirect from
  vendor/rspack.config.js -- the vendored provider no longer references
  that package at all.
- Delete server-sdk-bridge.js, the openfeature-server-sdk require-hook
  instrumentation, and their specs.
- Remove the now-obsolete esbuild/webpack black-box tests that verified
  the bridge survived bundling -- there's no bridge left to verify.
- Update file-tracing.spec.js's expected traced files and loaded-modules
  list to match.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(openfeature): update stale yarn.lock entry for openfeature-node-server

root package.json's devDependency was bumped to 2.1.0 but yarn.lock still
pinned 2.0.2, which made bun's yarn.lock-migration fail to resolve the
package in plugin test CI.

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
@dd-octo-sts dd-octo-sts Bot mentioned this pull request Aug 13, 2026
dd-octo-sts Bot pushed a commit that referenced this pull request Aug 13, 2026
…l peer dependency (#9570)

* feat(openfeature): return a ready provider from the openfeature entrypoint

dd-trace/openfeature previously only existed as a side-effect require for
file tracers (#9324). Turn it into the real public entrypoint: it now
returns a usable FlaggingProvider instance after tracer.init(), and
tracer.openfeature is deprecated in its favor since it doesn't work in
bundled applications.

Extract the base provider class into a factory so it can be constructed
from either the bundler-opaque require-provider wrapper (legacy
tracer.openfeature) or a plain require (the new entrypoint) without
duplicating the class body.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* test(openfeature): raise nft trace timeout for the entrypoint test

openfeature.js now pulls in the full tracer package to check
tracer.init() state, so nodeFileTrace has a much larger dependency
tree to walk. The default 5s mocha timeout was tight enough that CI
runners under load exceeded it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* feat(openfeature): vendor the flagging provider instead of an optional peer

`@datadog/openfeature-node-server` was an optional peer dependency resolved
through a bundler-opaque require, requiring dedicated esbuild/webpack plugin
code to keep the require invisible when the peer wasn't installed (#8635) and
to inline it so bundles survived relocation when it was (#8980). Vendoring the
provider into `vendor/dist/` removes the need for a peer at all, so all of
that machinery (`feature-registry.js`, `register.js`, `require-provider.js`,
the optional-peer esbuild/webpack loaders, and their integration tests) is
deleted in favor of a single lazy `tracer.openfeature` property.

The vendored provider's only remaining external dependency is
`@openfeature/server-sdk`, used solely for `OpenFeatureEventEmitter` and
`ProviderEvents`. Bundling our own copy would give those a different identity
than the customer's, so it's externalized to a small bridge module
(`server-sdk-bridge.js`) that a new `openfeature-server-sdk` instrumentation
fills in from the customer's own `require()`.

`FlaggingProvider#initialize` also unrefs the vendored provider's
initialization timer, which otherwise keeps an idle process (a short script, a
serverless handler) alive for up to `initializationTimeoutMs` while waiting
for configuration to arrive. TODO left to remove this once
`@datadog/openfeature-node-server` unrefs it upstream.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(build): exclude unresolved peer entries from license check

scripts/check_licenses.js treated every non-dev entry in
vendor/package-lock.json as requiring license attribution, including
`peer: true` entries. Those record an unresolved peer dependency range
that npm never actually installs, so nothing is shipped for them. The
official dd-license-attribution tool used in CI already excludes these,
so the local script disagreed with CI and had two extraneous rows
(@openfeature/core, @openfeature/server-sdk) added to satisfy it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* test(licenses): stop expecting npm peer dependencies to need attribution

The prior fix's comment claimed npm never installs peer entries, which is
false under npm 7+ auto-install-peers - vendor/node_modules physically has
@openfeature/core and @openfeature/server-sdk installed. The real reason
they don't need attribution is that peer dependencies are supplied by the
consumer rather than shipped by this package, matching how the yarn.lock
walk already excludes peerDependencies. The pre-existing vendor-peer test
fixture assumed the opposite and was never validated against real CI
behavior for a peer case, so it broke once the npm-lock scan started
excluding peers too.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(openfeature): implement full ProviderEventEmitter contract in the deferred bridge emitter

@openfeature/core's transferListeners always calls oldProvider.events?.removeHandler(...)
on every subsequent OpenFeature.setProvider() call for a domain, regardless of whether the
app registered any handlers itself. The deferred emitter only implemented addHandler/emit,
so replacing the Datadog OpenFeature provider with another provider at runtime threw
"oldProvider.events.removeHandler is not a function", crashing the customer's app.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(openfeature): address review feedback on tests and SSI scope

- Fix test:openfeature script so the instrumentation spec actually runs:
  mocha treats a brace-only glob segment as a literal filename, so add
  a wildcard segment that both mocha and verify-exercised-tests resolve.
- Add real esbuild/webpack black-box tests that bundle
  @openfeature/server-sdk (not marked external) and assert dd-trace's
  bundler-instrumentation mechanism still bridges the real event
  emitter into the vendored provider.
- Correct comments in proxy.js and the dd-trace-api plugin: SSI itself
  is fixed by this PR's vendoring; the only remaining gap is that the
  dd-trace-api shim has no openfeature handoff.

Generated with Claude Code.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* feat(openfeature): drop the server-sdk event-emitter bridge

@datadog/openfeature-node-server 2.1.0 ships its own ProviderEventEmitter
and no longer depends on @openfeature/server-sdk at all (neither as a
dependency nor a peer), so the identity-preserving bridge this PR
introduced is no longer needed:

- Bump @datadog/openfeature-node-server to 2.1.0 everywhere it's pinned
  (vendor, root devDependencies, plugin versions matrix) and rebuild
  vendor/dist.
- Remove the @openfeature/server-sdk external redirect from
  vendor/rspack.config.js -- the vendored provider no longer references
  that package at all.
- Delete server-sdk-bridge.js, the openfeature-server-sdk require-hook
  instrumentation, and their specs.
- Remove the now-obsolete esbuild/webpack black-box tests that verified
  the bridge survived bundling -- there's no bridge left to verify.
- Update file-tracing.spec.js's expected traced files and loaded-modules
  list to match.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(openfeature): update stale yarn.lock entry for openfeature-node-server

root package.json's devDependency was bumped to 2.1.0 but yarn.lock still
pinned 2.0.2, which made bun's yarn.lock-migration fail to resolve the
package in plugin test CI.

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
@dd-octo-sts dd-octo-sts Bot mentioned this pull request Aug 13, 2026
pabloerhard pushed a commit that referenced this pull request Aug 17, 2026
…l peer dependency (#9570)

* feat(openfeature): return a ready provider from the openfeature entrypoint

dd-trace/openfeature previously only existed as a side-effect require for
file tracers (#9324). Turn it into the real public entrypoint: it now
returns a usable FlaggingProvider instance after tracer.init(), and
tracer.openfeature is deprecated in its favor since it doesn't work in
bundled applications.

Extract the base provider class into a factory so it can be constructed
from either the bundler-opaque require-provider wrapper (legacy
tracer.openfeature) or a plain require (the new entrypoint) without
duplicating the class body.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* test(openfeature): raise nft trace timeout for the entrypoint test

openfeature.js now pulls in the full tracer package to check
tracer.init() state, so nodeFileTrace has a much larger dependency
tree to walk. The default 5s mocha timeout was tight enough that CI
runners under load exceeded it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* feat(openfeature): vendor the flagging provider instead of an optional peer

`@datadog/openfeature-node-server` was an optional peer dependency resolved
through a bundler-opaque require, requiring dedicated esbuild/webpack plugin
code to keep the require invisible when the peer wasn't installed (#8635) and
to inline it so bundles survived relocation when it was (#8980). Vendoring the
provider into `vendor/dist/` removes the need for a peer at all, so all of
that machinery (`feature-registry.js`, `register.js`, `require-provider.js`,
the optional-peer esbuild/webpack loaders, and their integration tests) is
deleted in favor of a single lazy `tracer.openfeature` property.

The vendored provider's only remaining external dependency is
`@openfeature/server-sdk`, used solely for `OpenFeatureEventEmitter` and
`ProviderEvents`. Bundling our own copy would give those a different identity
than the customer's, so it's externalized to a small bridge module
(`server-sdk-bridge.js`) that a new `openfeature-server-sdk` instrumentation
fills in from the customer's own `require()`.

`FlaggingProvider#initialize` also unrefs the vendored provider's
initialization timer, which otherwise keeps an idle process (a short script, a
serverless handler) alive for up to `initializationTimeoutMs` while waiting
for configuration to arrive. TODO left to remove this once
`@datadog/openfeature-node-server` unrefs it upstream.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(build): exclude unresolved peer entries from license check

scripts/check_licenses.js treated every non-dev entry in
vendor/package-lock.json as requiring license attribution, including
`peer: true` entries. Those record an unresolved peer dependency range
that npm never actually installs, so nothing is shipped for them. The
official dd-license-attribution tool used in CI already excludes these,
so the local script disagreed with CI and had two extraneous rows
(@openfeature/core, @openfeature/server-sdk) added to satisfy it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* test(licenses): stop expecting npm peer dependencies to need attribution

The prior fix's comment claimed npm never installs peer entries, which is
false under npm 7+ auto-install-peers - vendor/node_modules physically has
@openfeature/core and @openfeature/server-sdk installed. The real reason
they don't need attribution is that peer dependencies are supplied by the
consumer rather than shipped by this package, matching how the yarn.lock
walk already excludes peerDependencies. The pre-existing vendor-peer test
fixture assumed the opposite and was never validated against real CI
behavior for a peer case, so it broke once the npm-lock scan started
excluding peers too.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(openfeature): implement full ProviderEventEmitter contract in the deferred bridge emitter

@openfeature/core's transferListeners always calls oldProvider.events?.removeHandler(...)
on every subsequent OpenFeature.setProvider() call for a domain, regardless of whether the
app registered any handlers itself. The deferred emitter only implemented addHandler/emit,
so replacing the Datadog OpenFeature provider with another provider at runtime threw
"oldProvider.events.removeHandler is not a function", crashing the customer's app.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(openfeature): address review feedback on tests and SSI scope

- Fix test:openfeature script so the instrumentation spec actually runs:
  mocha treats a brace-only glob segment as a literal filename, so add
  a wildcard segment that both mocha and verify-exercised-tests resolve.
- Add real esbuild/webpack black-box tests that bundle
  @openfeature/server-sdk (not marked external) and assert dd-trace's
  bundler-instrumentation mechanism still bridges the real event
  emitter into the vendored provider.
- Correct comments in proxy.js and the dd-trace-api plugin: SSI itself
  is fixed by this PR's vendoring; the only remaining gap is that the
  dd-trace-api shim has no openfeature handoff.

Generated with Claude Code.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* feat(openfeature): drop the server-sdk event-emitter bridge

@datadog/openfeature-node-server 2.1.0 ships its own ProviderEventEmitter
and no longer depends on @openfeature/server-sdk at all (neither as a
dependency nor a peer), so the identity-preserving bridge this PR
introduced is no longer needed:

- Bump @datadog/openfeature-node-server to 2.1.0 everywhere it's pinned
  (vendor, root devDependencies, plugin versions matrix) and rebuild
  vendor/dist.
- Remove the @openfeature/server-sdk external redirect from
  vendor/rspack.config.js -- the vendored provider no longer references
  that package at all.
- Delete server-sdk-bridge.js, the openfeature-server-sdk require-hook
  instrumentation, and their specs.
- Remove the now-obsolete esbuild/webpack black-box tests that verified
  the bridge survived bundling -- there's no bridge left to verify.
- Update file-tracing.spec.js's expected traced files and loaded-modules
  list to match.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(openfeature): update stale yarn.lock entry for openfeature-node-server

root package.json's devDependency was bumped to 2.1.0 but yarn.lock still
pinned 2.0.2, which made bun's yarn.lock-migration fail to resolve the
package in plugin test CI.

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
pabloerhard pushed a commit that referenced this pull request Aug 17, 2026
…l peer dependency (#9570)

* feat(openfeature): return a ready provider from the openfeature entrypoint

dd-trace/openfeature previously only existed as a side-effect require for
file tracers (#9324). Turn it into the real public entrypoint: it now
returns a usable FlaggingProvider instance after tracer.init(), and
tracer.openfeature is deprecated in its favor since it doesn't work in
bundled applications.

Extract the base provider class into a factory so it can be constructed
from either the bundler-opaque require-provider wrapper (legacy
tracer.openfeature) or a plain require (the new entrypoint) without
duplicating the class body.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* test(openfeature): raise nft trace timeout for the entrypoint test

openfeature.js now pulls in the full tracer package to check
tracer.init() state, so nodeFileTrace has a much larger dependency
tree to walk. The default 5s mocha timeout was tight enough that CI
runners under load exceeded it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* feat(openfeature): vendor the flagging provider instead of an optional peer

`@datadog/openfeature-node-server` was an optional peer dependency resolved
through a bundler-opaque require, requiring dedicated esbuild/webpack plugin
code to keep the require invisible when the peer wasn't installed (#8635) and
to inline it so bundles survived relocation when it was (#8980). Vendoring the
provider into `vendor/dist/` removes the need for a peer at all, so all of
that machinery (`feature-registry.js`, `register.js`, `require-provider.js`,
the optional-peer esbuild/webpack loaders, and their integration tests) is
deleted in favor of a single lazy `tracer.openfeature` property.

The vendored provider's only remaining external dependency is
`@openfeature/server-sdk`, used solely for `OpenFeatureEventEmitter` and
`ProviderEvents`. Bundling our own copy would give those a different identity
than the customer's, so it's externalized to a small bridge module
(`server-sdk-bridge.js`) that a new `openfeature-server-sdk` instrumentation
fills in from the customer's own `require()`.

`FlaggingProvider#initialize` also unrefs the vendored provider's
initialization timer, which otherwise keeps an idle process (a short script, a
serverless handler) alive for up to `initializationTimeoutMs` while waiting
for configuration to arrive. TODO left to remove this once
`@datadog/openfeature-node-server` unrefs it upstream.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(build): exclude unresolved peer entries from license check

scripts/check_licenses.js treated every non-dev entry in
vendor/package-lock.json as requiring license attribution, including
`peer: true` entries. Those record an unresolved peer dependency range
that npm never actually installs, so nothing is shipped for them. The
official dd-license-attribution tool used in CI already excludes these,
so the local script disagreed with CI and had two extraneous rows
(@openfeature/core, @openfeature/server-sdk) added to satisfy it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* test(licenses): stop expecting npm peer dependencies to need attribution

The prior fix's comment claimed npm never installs peer entries, which is
false under npm 7+ auto-install-peers - vendor/node_modules physically has
@openfeature/core and @openfeature/server-sdk installed. The real reason
they don't need attribution is that peer dependencies are supplied by the
consumer rather than shipped by this package, matching how the yarn.lock
walk already excludes peerDependencies. The pre-existing vendor-peer test
fixture assumed the opposite and was never validated against real CI
behavior for a peer case, so it broke once the npm-lock scan started
excluding peers too.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(openfeature): implement full ProviderEventEmitter contract in the deferred bridge emitter

@openfeature/core's transferListeners always calls oldProvider.events?.removeHandler(...)
on every subsequent OpenFeature.setProvider() call for a domain, regardless of whether the
app registered any handlers itself. The deferred emitter only implemented addHandler/emit,
so replacing the Datadog OpenFeature provider with another provider at runtime threw
"oldProvider.events.removeHandler is not a function", crashing the customer's app.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(openfeature): address review feedback on tests and SSI scope

- Fix test:openfeature script so the instrumentation spec actually runs:
  mocha treats a brace-only glob segment as a literal filename, so add
  a wildcard segment that both mocha and verify-exercised-tests resolve.
- Add real esbuild/webpack black-box tests that bundle
  @openfeature/server-sdk (not marked external) and assert dd-trace's
  bundler-instrumentation mechanism still bridges the real event
  emitter into the vendored provider.
- Correct comments in proxy.js and the dd-trace-api plugin: SSI itself
  is fixed by this PR's vendoring; the only remaining gap is that the
  dd-trace-api shim has no openfeature handoff.

Generated with Claude Code.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* feat(openfeature): drop the server-sdk event-emitter bridge

@datadog/openfeature-node-server 2.1.0 ships its own ProviderEventEmitter
and no longer depends on @openfeature/server-sdk at all (neither as a
dependency nor a peer), so the identity-preserving bridge this PR
introduced is no longer needed:

- Bump @datadog/openfeature-node-server to 2.1.0 everywhere it's pinned
  (vendor, root devDependencies, plugin versions matrix) and rebuild
  vendor/dist.
- Remove the @openfeature/server-sdk external redirect from
  vendor/rspack.config.js -- the vendored provider no longer references
  that package at all.
- Delete server-sdk-bridge.js, the openfeature-server-sdk require-hook
  instrumentation, and their specs.
- Remove the now-obsolete esbuild/webpack black-box tests that verified
  the bridge survived bundling -- there's no bridge left to verify.
- Update file-tracing.spec.js's expected traced files and loaded-modules
  list to match.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(openfeature): update stale yarn.lock entry for openfeature-node-server

root package.json's devDependency was bumped to 2.1.0 but yarn.lock still
pinned 2.0.2, which made bun's yarn.lock-migration fail to resolve the
package in plugin test CI.

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants