Skip to content

feat: Implementing trace of graphql executions#3215

Open
ommeirelles wants to merge 19 commits intocanary-v2from
feat/otel-traces
Open

feat: Implementing trace of graphql executions#3215
ommeirelles wants to merge 19 commits intocanary-v2from
feat/otel-traces

Conversation

@ommeirelles
Copy link
Contributor

@ommeirelles ommeirelles commented Feb 19, 2026

image

Summary by CodeRabbit

  • Chores

    • Added a new diagnostics package and OpenTelemetry configs (dev/prod)
    • Added OpenTelemetry-related dependencies and adjusted build/config for sourcemaps and externals
  • New Features

    • Initialized telemetry across core and API
    • Enhanced GraphQL context with trace metadata and generated request IDs
    • Wrapped resolvers to emit tracing spans
  • Tests

    • Updated tests to await asynchronous context initialization

@ommeirelles ommeirelles requested a review from a team as a code owner February 19, 2026 15:33
@ommeirelles ommeirelles requested review from hellofanny and renatomaurovtex and removed request for a team February 19, 2026 15:33
@coderabbitai
Copy link

coderabbitai bot commented Feb 19, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds a new diagnostics package and integrates OpenTelemetry across the stack: adds telemetry client/trace factories, resolver-level tracing, async context factory initialization, global diagnostics state, build/config updates, and test adjustments to await the async factory.

Changes

Cohort / File(s) Summary
New Diagnostics Package
packages/diagnostics/package.json, packages/diagnostics/vite.config.mts, packages/diagnostics/tsconfig.json, packages/diagnostics/src/index.ts, packages/diagnostics/src/start.ts, packages/diagnostics/src/globals.ts, packages/diagnostics/@types/global.d.ts, packages/diagnostics/configs/dev.json, packages/diagnostics/configs/prod.json
Introduce @faststore/diagnostics package: telemetry/trace client factories, global singleton storage, dev/prod configs, and build configs.
Resolver Tracing & Typings
packages/api/src/observability/telemetry.ts, packages/api/src/typings/globals.ts
Add ResolverTrace HOF to wrap GraphQL resolvers with OpenTelemetry spans; add Resolver type and OTEL-related fields to global typings.
VTEX Platform & Context Factory
packages/api/src/platforms/vtex/index.ts, packages/api/src/platforms/vtex/clients/fetch.ts
Make GraphqlVtexContextFactory async, inject id and OTEL into GraphQL context, initialize telemetry when enabled, and wrap resolvers with ResolverTrace; change package.json import to JSON module form.
Core Instrumentation & Server
packages/core/src/instrumentation.ts, packages/core/src/server/options.ts, packages/core/src/server/index.ts, packages/core/package.json
Add async register() for Node telemetry init; add withTraceClient() to augment API options with OTEL and version; getEnvelop() now awaits trace-wrapped context factory; add diagnostics dependency.
API Build & Config
packages/api/package.json, packages/api/vite.config.mts
Add @faststore/diagnostics and @opentelemetry/api; vite config now includes devDependencies in externals, uses builtinModules, and sets mode-based sourcemap.
Local Runtime & Tests
packages/api/local/server.mjs, packages/api/test/integration/mutations.test.ts, packages/api/test/integration/queries.test.ts, packages/api/test/unit/platforms/vtex/clients/commerce.test.ts
Await the async GraphqlVtexContextFactory in runtime and tests; test runners refactored to await per-test initialization.
Misc / Minor
packages/ui/src/styles/base/utilities.scss
Whitespace/formatting tweak only.

Sequence Diagram

sequenceDiagram
    participant Client as GraphQL Client
    participant Server as GraphQL Server
    participant ResolverTrace as ResolverTrace HOF
    participant TraceClient as TraceClient
    participant OTEL as OpenTelemetry API
    participant Resolver as Original Resolver

    Client->>Server: Send GraphQL request
    Server->>ResolverTrace: Call wrapped resolver
    ResolverTrace->>TraceClient: extract context (context.OTEL) or use OTEL.context.active()
    alt OTEL enabled
        TraceClient->>OTEL: Start span (name, attrs)
    end
    ResolverTrace->>Resolver: Invoke original resolver(source, vars, context, info)
    alt returns Promise
        Resolver-->>ResolverTrace: Promise
        ResolverTrace->>OTEL: End span on resolution
        ResolverTrace-->>Server: Resolved value
    else sync
        Resolver-->>ResolverTrace: Return value
        ResolverTrace->>OTEL: End span
        ResolverTrace-->>Server: Return value
    end
    Server-->>Client: Respond
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Suggested reviewers

  • hellofanny
  • lemagnetic

Poem

Spans stitch through schema in the quiet night,
Async ids glimmer, telemetry alight,
Resolvers hum, traces chase the light,
A global map keeps clients in sight,
Observability sings code into sight. ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ 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%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ 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: Implementing trace of graphql executions' clearly summarizes the main change: adding OpenTelemetry tracing for GraphQL resolver execution across the codebase.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/otel-traces

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


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.

@codesandbox-ci
Copy link

codesandbox-ci bot commented Feb 19, 2026

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

@ommeirelles ommeirelles changed the title Implementing trace of graphql executions feat: Implementing trace of graphql executions Feb 19, 2026
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: 12

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/api/src/platforms/vtex/index.ts (1)

43-65: ⚠️ Potential issue | 🔴 Critical

Bug: id is generated once per factory, not per request — all requests share the same ID.

crypto.randomBytes(32) on line 49 executes in the outer factory scope. The inner closure (line 50) is what runs per-request, but it reuses the same id. This contradicts the "Request scope ID" comment on line 21. Move id generation inside the returned function:

Proposed fix
 export const GraphqlVtexContextFactory = (options: Options) => {
   getTelemetryClient({
     name,
     version,
     account: options.account,
   })
-  const id = crypto.randomBytes(32).toString('hex')
   return (ctx: any): GraphqlContext => {
-    ctx.id = id
+    ctx.id = crypto.randomBytes(32).toString('hex')
     ctx.storage = {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/api/src/platforms/vtex/index.ts` around lines 43 - 65, The factory
GraphqlVtexContextFactory currently generates id once in the outer scope (const
id = crypto.randomBytes(32).toString('hex')) so every request shares the same
id; move the id generation into the returned function so that inside the
returned (ctx: any): GraphqlContext => { ... } you create a fresh id (e.g.,
const id = crypto.randomBytes(32).toString('hex')) and then assign ctx.id = id,
removing the outer const id to ensure a unique request-scoped id for each ctx.
🧹 Nitpick comments (9)
packages/diagnostics/vite.config.mts (1)

21-23: dependencies ?? {} is a redundant null guard.

dependencies is already initialized with = {} on line 6, so it can never be undefined here.

♻️ Proposed fix
     external: [
       ...builtinModules.concat(builtinModules.map((e) => `node:${e}`)),
-      ...Object.keys({
-        ...(dependencies ?? {}),
-      }),
+      ...Object.keys(dependencies),
     ],
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/diagnostics/vite.config.mts` around lines 21 - 23, The spread uses a
redundant nullish fallback because the variable dependencies is initialized to
{} earlier; update the Object.keys spread to use dependencies directly (remove
the "?? {}" guard) so the line becomes Object.keys({ ...(dependencies) }) or
simply Object.keys(dependencies) as appropriate, ensuring you reference the
existing dependencies variable used on line 6 and the Object.keys(...)
expression to locate and modify the code.
packages/diagnostics/tsconfig.json (1)

4-4: outDir: "dist/esm" doesn't match Vite build output paths.

The Vite config outputs to dist/es/ and dist/cjs/, not dist/esm/. Since vite-plugin-dts drives the declaration output (not tsc), this is non-functional, but it's misleading for anyone running a bare tsc check on this package.

♻️ Suggested fix
-    "outDir": "dist/esm",
+    "outDir": "dist",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/diagnostics/tsconfig.json` at line 4, The tsconfig.json currently
sets "outDir": "dist/esm" which mismatches the Vite outputs and is misleading;
update the tsconfig.json's outDir key to "dist/es" to match Vite's ES output (or
remove the outDir entry entirely if you prefer to avoid any tsc emit assumptions
since vite-plugin-dts drives declaration output) so that the "outDir" setting
reflects the actual build paths used by the project.
packages/api/src/typings/index.ts (1)

32-37: info parameter should be GraphQLResolveInfo, not any.

info carries the full GraphQL resolution context (field name, parent type, path, schema, variables). Typing it as any opts out of type safety for every caller and forfeits valuable metadata for tracing. GraphQLResolveInfo is available through the existing graphql peer dependency.

♻️ Proposed fix
+import type { GraphQLResolveInfo } from 'graphql'

 type Resolver<
   TContext extends Record<string, any>,
   TSource = any,
   TVars = any,
   TReturn = any,
-> = (source: TSource, vars: TVars, context: TContext, info: any) => TReturn
+> = (source: TSource, vars: TVars, context: TContext, info: GraphQLResolveInfo) => TReturn

As per coding guidelines: "Type safety with generated types" and "Ensure type safety and avoid type assertions when possible."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/api/src/typings/index.ts` around lines 32 - 37, The Resolver type
currently types the fourth parameter as any; change it to GraphQLResolveInfo to
preserve GraphQL metadata and enable type safety: import { GraphQLResolveInfo }
from "graphql" at the top of the file and update the Resolver signature to
(source: TSource, vars: TVars, context: TContext, info: GraphQLResolveInfo) =>
TReturn (keeping the generics intact); ensure the graphql peer dependency is
used (no type assertions needed) so callers get proper info typing.
packages/diagnostics/package.json (1)

12-12: license: "ISC" is inconsistent with the rest of the monorepo.

Every other @faststore/* package uses "MIT". Consider aligning unless ISC is deliberate.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/diagnostics/package.json` at line 12, The package's license field in
its package.json is set to "ISC" while the monorepo uses "MIT"; update the
"license" property from "ISC" to "MIT" in this package's package.json so it
matches other `@faststore` packages, then run your repo's license/validation
script (or npm install/test) to confirm no further inconsistencies.
packages/api/src/platforms/vtex/index.ts (2)

67-71: Remove commented-out code.

Dead code adds noise. If VtexResolver is needed later, it can be retrieved from git history.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/api/src/platforms/vtex/index.ts` around lines 67 - 71, Remove the
commented-out VtexResolver block: delete the entire commented code that defines
VtexResolver (the lines referencing VtexResolver, GraphqlResolver,
ResolverTrace, and GraphqlContext) so the file no longer contains this dead
code; if needed later it can be restored from git history.

80-96: finalResolvers: any drops type safety; variable name shadows module-level import.

Two concerns in getResolvers:

  1. Line 83: finalResolvers: any discards the resolver type structure. Use a more descriptive intermediate type or infer from nativeGetResolvers().
  2. Line 86: The destructured name in .map(([name, resolverFunction]) shadows the name import from package.json (line 5). Rename to e.g. resolverName for clarity.
Proposed fix
 export function getResolvers() {
   const resolvers = nativeGetResolvers()
 
-  const finalResolvers: any = {}
+  const finalResolvers: Record<string, Record<string, unknown>> = {}
   for (const [key, resolver] of Object.entries(resolvers)) {
     finalResolvers[key] = Object.fromEntries(
-      Object.entries(resolver).map(([name, resolverFunction]) => {
-        if (typeof resolverFunction !== 'function')
-          return [name, resolverFunction]
+      Object.entries(resolver).map(([resolverName, resolverFunction]) => {
+        if (typeof resolverFunction !== 'function')
+          return [resolverName, resolverFunction]
 
-        return [name, ResolverTrace(resolverFunction, `${key}(${name})`)]
+        return [resolverName, ResolverTrace(resolverFunction, `${key}(${resolverName})`)]
       })
     )
   }

As per coding guidelines, "Ensure type safety and avoid type assertions when possible".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/api/src/platforms/vtex/index.ts` around lines 80 - 96, getResolvers
currently weakens types by declaring finalResolvers: any and also shadows the
module-level import name via the map destructuring; update getResolvers to infer
types from nativeGetResolvers() (e.g., let resolvers = nativeGetResolvers(); use
const finalResolvers: Partial<typeof resolvers> = {} or an appropriate mapped
type) and replace the destructured variable name in
Object.entries(...).map(([name, resolverFunction]) => ...) with a
non-conflicting identifier like resolverName, then assign
ResolverTrace(resolverFunction, `${key}(${resolverName})`) so type safety is
preserved and the imported name is not shadowed.
packages/diagnostics/src/index.ts (2)

1-11: Namespace re-exports defeat tree-shaking — significant bundle size impact.

import * of @opentelemetry/api and @opentelemetry/semantic-conventions, then re-exporting as OTELAPI and CONVENTIONS, forces bundlers to include the entire modules. Downstream code only uses a handful of symbols (context, SpanKind, ATTR_CODE_FUNCTION_NAME, ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION).

Consider re-exporting only the specific symbols needed, or let consumers import directly from @opentelemetry/* packages.

As per coding guidelines, "Consider bundle size impact of dependencies".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/diagnostics/src/index.ts` around lines 1 - 11, The current namespace
re-exports (OTELAPI and CONVENTIONS) pull in entire `@opentelemetry` packages and
defeat tree-shaking; replace the wildcard imports and re-exports with named
exports for only the symbols actually used (e.g., export { context, SpanKind }
from '@opentelemetry/api' and export { ATTR_CODE_FUNCTION_NAME,
ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from
'@opentelemetry/semantic-conventions'), or remove the OTELAPI/CONVENTIONS
exports and let consumers import directly; update any internal references to
OTELAPI or CONVENTIONS to use the named symbols or the original package imports
to restore tree-shaking and reduce bundle size.

13-18: Redundant default export alongside named exports.

The default export duplicates everything already available via named exports. This creates two ways to import the same thing with no benefit, and makes tree-shaking harder.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/diagnostics/src/index.ts` around lines 13 - 18, Remove the redundant
default export object that mirrors the named exports; delete the default export
block that returns OTELAPI, CONVENTIONS, getTelemetryClient, and getTraceClient
and rely solely on the existing named exports. Ensure the named exports OTELAPI,
CONVENTIONS, getTelemetryClient, and getTraceClient are exported where they are
defined (or add explicit export statements) so consumers import them by name and
tree-shaking works correctly.
packages/api/src/observability/telemetry.ts (1)

7-14: Return type TReturn is inaccurate when wrapping async resolvers.

When fn returns Promise<X> (so TReturn = Promise<X>), the .then() on line 49 also produces Promise<X>, so the as TReturn cast on line 52 happens to be correct at runtime. However, the outer function's declared return type is TReturn, yet OTELAPI.context.with(activeContext, callback) returns whatever the callback returns — the TypeScript compiler can't verify the span-wrapping logic preserves the type. This is acceptable for now but worth noting.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/api/src/observability/telemetry.ts` around lines 7 - 14, The
ResolverTrace wrapper's declared return type TReturn can diverge from the actual
return of the wrapped function (especially when the wrapped function returns a
Promise); update the ResolverTrace signature to take the resolver function as a
generic function type (e.g., F extends (source: TSource, vars: TVars, context:
TContext, info: any) => any) and use ReturnType<F> as the wrapper's return type
so the compiler preserves the exact return (Promise or non-Promise) of the
original fn; locate the ResolverTrace declaration and replace the TReturn
generic with an F generic and use ReturnType<F> for the outer function's return
type and for internal casts.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/api/package.json`:
- Around line 77-78: Remove the unnecessary explicit peerDependency entry for
"@vtex/diagnostics-nodejs" from the package.json for the `@faststore/api` package:
delete the "@vtex/diagnostics-nodejs": "^5.1.1" line from peerDependencies
(since `@faststore/diagnostics` already depends on it and the code in
`@faststore/api` does not import it), then update lockfiles/install
(npm/yarn/pnpm) as needed to ensure the dependency is resolved transitively and
run tests/build to confirm nothing breaks.

In `@packages/api/src/observability/telemetry.ts`:
- Around line 40-52: The span is not ended when fn throws synchronously or when
the returned promise rejects; wrap the invocation of fn (the call that assigns
returnedValue inside the context.with callback) in a try/catch so that
span.end() is called in the catch before rethrowing synchronous errors, and for
the async case replace the current then-only logic with a finally (or attach
both then and catch) on the Promise (e.g.,
Promise.resolve(returnedValue).finally(...)) to ensure span.end() runs on
resolve or rejection; reference the variables/functions returnedValue, fn, span
and the context.with callback when making the changes.

In `@packages/api/src/platforms/vtex/index.ts`:
- Around line 44-48: getTelemetryClient is called without awaiting so the trace
client (TRACE_CLIENT) isn't initialized before withTraceClient runs; make
GraphqlVtexContextFactory async and await getTelemetryClient(...) inside it so
the telemetry client is ready when GraphqlVtexContextFactory returns, and then
update the caller that invokes this factory (the place where withTraceClient is
composed with the factory) to await the async GraphqlVtexContextFactory so OTEL
context is populated for early requests.

In `@packages/core/package.json`:
- Line 66: Remove the direct dependency entry for "@vtex/diagnostics-nodejs"
from the dependencies list in package.json (the explicit line that reads
"@vtex/diagnostics-nodejs": "^5.1.1") because it is already provided
transitively by "@faststore/diagnostics"; update the dependencies section to
delete that line so only "@faststore/diagnostics" remains responsible for
bringing in that package.

In `@packages/core/src/instrumentation.ts`:
- Around line 1-11: The register function can throw from its dynamic imports
(package.json, discovery.config, or `@faststore/diagnostics`) and must not crash
Next.js on startup; update the async function register to check
process.env.NEXT_RUNTIME as before but wrap the entire import/initialization
block in a try/catch, gracefully short-circuit on error (do not rethrow), and
emit a safe error message (e.g., via console.error or a fallback logger) so
telemetry failures do not prevent the app from starting; reference the register
function and the three dynamic imports (import('../package.json'),
import('../discovery.config'), import('@faststore/diagnostics')) when making the
change.

In `@packages/core/src/server/options.ts`:
- Around line 26-36: Change the generic and return typing and fix the
import/variable typo: make withTraceClient generic constrained to objects (e.g.
function withTraceClient<T extends object>(apiOptions: T): Promise<T & { OTEL:
unknown }>) so spreading ...apiOptions is type-safe and the return reflects the
added OTEL property; remove the useless optional chain on the dynamic import
(use await import('@faststore/diagnostics') instead of (await
import(...))?.getTraceClient()) so import errors propagate, rename traceCLient
to traceClient, call const traceClient = (await import(...)).getTraceClient();
and keep a defensive traceClient?.inject(OTEL) call for when getTraceClient()
returns undefined.

In `@packages/diagnostics/package.json`:
- Around line 1-33: The package.json for `@faststore/diagnostics` is missing a
publishConfig entry which causes npm to publish the scoped package as
restricted; update packages/diagnostics/package.json (package name
"@faststore/diagnostics") to include "publishConfig": { "access": "public" } at
the top level of the JSON so it mirrors other `@faststore/`* packages and can be
published successfully.

In `@packages/diagnostics/src/start.ts`:
- Around line 22-25: The traces exporter is forcing TLS off by hardcoding
insecure: true in the Exporters.CreateTracesExporterConfig call (tracesConfig) —
change this to derive from an environment variable instead (for example check
process.env.NODE_ENV !== 'production' or a dedicated OTLP_INSECURE flag) and
pass that boolean into the insecure option when calling
Exporters.CreateTracesExporterConfig( { endpoint: OTLP_ENDPOINT, insecure:
<derivedBool> } ) so production defaults to secure/TLS while non-production can
disable it via env.
- Line 60: The console.log call that prints "TELEMETRY CLIENT STARTED" with the
opt object should be removed or converted to structured debug logging: replace
the console.log('TELEMETRY CLIENT STARTED', opt) usage with a debug-level logger
(e.g., processLogger.debug or the project's logger) and ensure you do not emit
sensitive fields from the opt object—either omit or redact opt.account before
logging (or only log a non-identifying subset). Locate the console.log in
start.ts where the opt variable is used and change it to a debug-level,
structured log that excludes or masks the account identifier.
- Around line 32-63: getTelemetryClient currently recreates and overwrites the
global TELEMETRY_CLIENT and TRACE_CLIENT on every call; add an idempotency guard
at the start of getTelemetryClient that returns the existing TELEMETRY_CLIENT
immediately if it's already initialized (so you don't call NewTelemetryClient,
setupTracesExporter, newLogsClient/newMetricsClient/newTracesClient or
registerInstrumentations again). Locate getTelemetryClient and reference the
TELEMETRY_CLIENT and TRACE_CLIENT globals; implement an early return using the
same function signature and ensure you do not re-run setupTracesExporter,
NewTelemetryClient, or Instrumentation.CommonInstrumentations.minimal() when
TELEMETRY_CLIENT is present.
- Around line 51-52: The code incorrectly reuses tracesExporter (from
Exporters.CreateTracesExporterConfig) for logs and metrics; replace those calls
to create and pass signal-specific exporters by creating a logsExporter via
Exporters.CreateLogsExporterConfig() and a metricsExporter via
Exporters.CreateMetricsExporterConfig(), then call
TELEMETRY_CLIENT.newLogsClient({ exporter: logsExporter }) and
TELEMETRY_CLIENT.newMetricsClient({ exporter: metricsExporter }) while leaving
the traces client using tracesExporter.

In `@packages/diagnostics/vite.config.mts`:
- Around line 15-17: The Vite library config currently uses fileName:
'[format]/index' which makes Vite emit index.cjs for the 'cjs' format while
package.json's "require" expects ./dist/cjs/index.js; update the Vite config's
fileName to a function that returns a path forcing .js for the 'cjs' format and
.mjs for the ES format (i.e., return 'cjs/index.js' for format === 'cjs' and
'es/index.mjs' otherwise) so the emitted files match package.json, or
alternatively update package.json's "require" to point to ./dist/cjs/index.cjs
if you prefer Vite's default naming.

---

Outside diff comments:
In `@packages/api/src/platforms/vtex/index.ts`:
- Around line 43-65: The factory GraphqlVtexContextFactory currently generates
id once in the outer scope (const id = crypto.randomBytes(32).toString('hex'))
so every request shares the same id; move the id generation into the returned
function so that inside the returned (ctx: any): GraphqlContext => { ... } you
create a fresh id (e.g., const id = crypto.randomBytes(32).toString('hex')) and
then assign ctx.id = id, removing the outer const id to ensure a unique
request-scoped id for each ctx.

---

Nitpick comments:
In `@packages/api/src/observability/telemetry.ts`:
- Around line 7-14: The ResolverTrace wrapper's declared return type TReturn can
diverge from the actual return of the wrapped function (especially when the
wrapped function returns a Promise); update the ResolverTrace signature to take
the resolver function as a generic function type (e.g., F extends (source:
TSource, vars: TVars, context: TContext, info: any) => any) and use
ReturnType<F> as the wrapper's return type so the compiler preserves the exact
return (Promise or non-Promise) of the original fn; locate the ResolverTrace
declaration and replace the TReturn generic with an F generic and use
ReturnType<F> for the outer function's return type and for internal casts.

In `@packages/api/src/platforms/vtex/index.ts`:
- Around line 67-71: Remove the commented-out VtexResolver block: delete the
entire commented code that defines VtexResolver (the lines referencing
VtexResolver, GraphqlResolver, ResolverTrace, and GraphqlContext) so the file no
longer contains this dead code; if needed later it can be restored from git
history.
- Around line 80-96: getResolvers currently weakens types by declaring
finalResolvers: any and also shadows the module-level import name via the map
destructuring; update getResolvers to infer types from nativeGetResolvers()
(e.g., let resolvers = nativeGetResolvers(); use const finalResolvers:
Partial<typeof resolvers> = {} or an appropriate mapped type) and replace the
destructured variable name in Object.entries(...).map(([name, resolverFunction])
=> ...) with a non-conflicting identifier like resolverName, then assign
ResolverTrace(resolverFunction, `${key}(${resolverName})`) so type safety is
preserved and the imported name is not shadowed.

In `@packages/api/src/typings/index.ts`:
- Around line 32-37: The Resolver type currently types the fourth parameter as
any; change it to GraphQLResolveInfo to preserve GraphQL metadata and enable
type safety: import { GraphQLResolveInfo } from "graphql" at the top of the file
and update the Resolver signature to (source: TSource, vars: TVars, context:
TContext, info: GraphQLResolveInfo) => TReturn (keeping the generics intact);
ensure the graphql peer dependency is used (no type assertions needed) so
callers get proper info typing.

In `@packages/diagnostics/package.json`:
- Line 12: The package's license field in its package.json is set to "ISC" while
the monorepo uses "MIT"; update the "license" property from "ISC" to "MIT" in
this package's package.json so it matches other `@faststore` packages, then run
your repo's license/validation script (or npm install/test) to confirm no
further inconsistencies.

In `@packages/diagnostics/src/index.ts`:
- Around line 1-11: The current namespace re-exports (OTELAPI and CONVENTIONS)
pull in entire `@opentelemetry` packages and defeat tree-shaking; replace the
wildcard imports and re-exports with named exports for only the symbols actually
used (e.g., export { context, SpanKind } from '@opentelemetry/api' and export {
ATTR_CODE_FUNCTION_NAME, ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from
'@opentelemetry/semantic-conventions'), or remove the OTELAPI/CONVENTIONS
exports and let consumers import directly; update any internal references to
OTELAPI or CONVENTIONS to use the named symbols or the original package imports
to restore tree-shaking and reduce bundle size.
- Around line 13-18: Remove the redundant default export object that mirrors the
named exports; delete the default export block that returns OTELAPI,
CONVENTIONS, getTelemetryClient, and getTraceClient and rely solely on the
existing named exports. Ensure the named exports OTELAPI, CONVENTIONS,
getTelemetryClient, and getTraceClient are exported where they are defined (or
add explicit export statements) so consumers import them by name and
tree-shaking works correctly.

In `@packages/diagnostics/tsconfig.json`:
- Line 4: The tsconfig.json currently sets "outDir": "dist/esm" which mismatches
the Vite outputs and is misleading; update the tsconfig.json's outDir key to
"dist/es" to match Vite's ES output (or remove the outDir entry entirely if you
prefer to avoid any tsc emit assumptions since vite-plugin-dts drives
declaration output) so that the "outDir" setting reflects the actual build paths
used by the project.

In `@packages/diagnostics/vite.config.mts`:
- Around line 21-23: The spread uses a redundant nullish fallback because the
variable dependencies is initialized to {} earlier; update the Object.keys
spread to use dependencies directly (remove the "?? {}" guard) so the line
becomes Object.keys({ ...(dependencies) }) or simply Object.keys(dependencies)
as appropriate, ensuring you reference the existing dependencies variable used
on line 6 and the Object.keys(...) expression to locate and modify the code.

Comment on lines 40 to 52
const returnedValue = fn(source, vars, graphqlContext, info)

if (!span) return returnedValue

if (!(returnedValue instanceof Promise)) {
span.end()
return returnedValue
}

return returnedValue.then((value) => {
span.end()
return value
}) as TReturn
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

Span is never ended on errors — both sync throws and promise rejections leak spans.

If fn() throws synchronously (line 40), execution exits the context.with callback without calling span.end(). Similarly, if the returned promise rejects, there's no .catch() to end the span. Leaked spans corrupt trace data and can cause memory pressure in the exporter.

Proposed fix
-      const returnedValue = fn(source, vars, graphqlContext, info)
+      let returnedValue: TReturn
+      try {
+        returnedValue = fn(source, vars, graphqlContext, info)
+      } catch (err) {
+        span?.setStatus({ code: OTELAPI.SpanStatusCode.ERROR })
+        span?.end()
+        throw err
+      }
 
       if (!span) return returnedValue
 
       if (!(returnedValue instanceof Promise)) {
         span.end()
         return returnedValue
       }
 
-      return returnedValue.then((value) => {
-        span.end()
-        return value
-      }) as TReturn
+      return returnedValue
+        .then((value) => {
+          span.end()
+          return value
+        })
+        .catch((err) => {
+          span.setStatus({ code: OTELAPI.SpanStatusCode.ERROR })
+          span.end()
+          throw err
+        }) as TReturn

As per coding guidelines, "Proper error handling and validation".

📝 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
const returnedValue = fn(source, vars, graphqlContext, info)
if (!span) return returnedValue
if (!(returnedValue instanceof Promise)) {
span.end()
return returnedValue
}
return returnedValue.then((value) => {
span.end()
return value
}) as TReturn
let returnedValue: TReturn
try {
returnedValue = fn(source, vars, graphqlContext, info)
} catch (err) {
span?.setStatus({ code: OTELAPI.SpanStatusCode.ERROR })
span?.end()
throw err
}
if (!span) return returnedValue
if (!(returnedValue instanceof Promise)) {
span.end()
return returnedValue
}
return returnedValue
.then((value) => {
span.end()
return value
})
.catch((err) => {
span.setStatus({ code: OTELAPI.SpanStatusCode.ERROR })
span.end()
throw err
}) as TReturn
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/api/src/observability/telemetry.ts` around lines 40 - 52, The span
is not ended when fn throws synchronously or when the returned promise rejects;
wrap the invocation of fn (the call that assigns returnedValue inside the
context.with callback) in a try/catch so that span.end() is called in the catch
before rethrowing synchronous errors, and for the async case replace the current
then-only logic with a finally (or attach both then and catch) on the Promise
(e.g., Promise.resolve(returnedValue).finally(...)) to ensure span.end() runs on
resolve or rejection; reference the variables/functions returnedValue, fn, span
and the context.with callback when making the changes.

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: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/api/src/platforms/vtex/index.ts (1)

43-65: ⚠️ Potential issue | 🔴 Critical

id is generated once at factory creation — all requests share the same ID.

crypto.randomBytes(32) on line 49 runs once when GraphqlVtexContextFactory is awaited during server init. The returned closure reuses this single id for every incoming request. If id is meant to be a per-request correlation identifier (as the JSDoc "Request scope ID" on line 21 suggests), it needs to move inside the returned function.

Proposed fix
 export const GraphqlVtexContextFactory = async (options: Options) => {
   await getTelemetryClient({
     name,
     version,
     account: options.account,
   })
-  const id = crypto.randomBytes(32).toString('hex')
   return (ctx: any): GraphqlContext => {
-    ctx.id = id
+    ctx.id = crypto.randomBytes(32).toString('hex')
     ctx.storage = {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/api/src/platforms/vtex/index.ts` around lines 43 - 65, The factory
currently generates a single `id` once in GraphqlVtexContextFactory and reuses
it for all requests; move the `crypto.randomBytes(32).toString('hex')` call into
the returned closure so a fresh request-scoped ID is assigned to `ctx.id` on
each invocation (update the returned function that sets `ctx.id`, leaving other
initializations like `ctx.storage`, `ctx.clients`, `ctx.loaders`, `ctx.account`,
and `ctx.OTEL` unchanged).
packages/api/local/server.mjs (1)

11-23: ⚠️ Potential issue | 🟡 Minor

Missing OTEL property in local server apiOptions.

GraphqlVtexContextFactory now assigns ctx.OTEL = options.OTEL. Without OTEL here, ctx.OTEL will be undefined, which violates the Record<string, unknown> contract on GraphqlContext. The core options (in packages/core/src/server/options.ts) already include OTEL: {}. This local server should match.

Proposed fix
 const apiOptions = {
   platform: 'vtex',
   account: 'storeframework',
   locale: 'en-US',
   environment: 'vtexcommercestable',
   channel:
     '{"salesChannel":"1","regionId":"","hasOnlyDefaultSalesChannel":"true"}',
   showSponsored: false,
+  OTEL: {},
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/api/local/server.mjs` around lines 11 - 23, The apiOptions object
used to build context for GraphqlVtexContextFactory is missing the OTEL
property, causing ctx.OTEL to be undefined and violating the GraphqlContext
contract; update the apiOptions constant to include an OTEL field (e.g., OTEL:
{}) so GraphqlVtexContextFactory receives OTEL and ctx.OTEL is defined when
creating the Yoga server with GraphqlVtexSchema() and
GraphqlVtexContextFactory(apiOptions).
🧹 Nitpick comments (5)
packages/api/vite.config.mts (1)

12-12: Consider external source maps (true) instead of 'inline' for non-production.

'inline' embeds the full base64-encoded source map directly into every output file, which can balloon dist artifacts significantly in dev/watch mode. Using true (or 'hidden') generates a separate .map file instead, keeping the bundled output lean while preserving debuggability.

♻️ Proposed adjustment
-    sourcemap: mode === 'production' ? 'hidden' : 'inline',
+    sourcemap: mode === 'production' ? 'hidden' : true,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/api/vite.config.mts` at line 12, The sourcemap config currently uses
'inline' for non-production which embeds base64 maps into files; update the
sourcemap expression in vite.config.mts (the sourcemap option) to generate
external maps for dev by returning true instead of 'inline' (e.g., keep
production as 'hidden' but change the non-production branch to true) so source
maps are emitted as separate .map files and dist artifacts stay lean.
packages/core/src/server/options.ts (1)

27-36: as T hides the OTEL augmentation and the generic is unconstrained — type safety concern.

The function always adds an OTEL property to the return value, but the return type Promise<T> doesn't reflect that. Meanwhile, T has no extends object constraint, so spreading ...apiOptions is only safe by accident. A constrained return type would be more honest:

-export async function withTraceClient<T = typeof apiOptions>(
-  apiOptions: T
-): Promise<T> {
+export async function withTraceClient<T extends Record<string, unknown> = typeof apiOptions>(
+  options: T
+): Promise<T & { OTEL: Record<string, unknown> }> {
   const OTEL = {}
   getTraceClient(name)?.inject(OTEL)
 
   return {
-    ...apiOptions,
+    ...options,
     OTEL,
-  } as T
+  }
 }

This also renames the parameter to avoid shadowing the module-level apiOptions. As per coding guidelines, "Ensure type safety and avoid type assertions when possible".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/server/options.ts` around lines 27 - 36, Change
withTraceClient to accurately reflect that it augments the input options with an
OTEL field and to constrain the generic: make the type parameter T extend object
(e.g., <T extends object>) and change the return type to Promise<T & { OTEL:
Record<string, unknown> }> (or a more specific OTEL type), rename the parameter
(e.g., inputOptions) to avoid shadowing the module-level apiOptions, remove the
unsafe "as T" cast, and return { ...inputOptions, OTEL } typed as T & { OTEL:
... } so the compiler knows the OTEL augmentation is present; keep the
getTraceClient(name)?.inject(OTEL) call unchanged.
packages/api/test/integration/queries.test.ts (1)

114-291: Consider extracting createRunner into a shared test utility.

The createRunner function is essentially identical in queries.test.ts and mutations.test.ts (same apiOptions shape, same schema/context wiring). A shared helper would reduce duplication and make future changes to the runner setup a single-point edit.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/api/test/integration/queries.test.ts` around lines 114 - 291, The
tests duplicate the createRunner setup across queries.test.ts and
mutations.test.ts; extract it into a shared test utility to DRY the code. Create
a new helper (e.g., export function createTestRunner or re-export createRunner)
that encapsulates the apiOptions shape and schema/context wiring used in both
files, move the existing createRunner implementation into that helper, update
queries.test.ts and mutations.test.ts to import and call the shared
createTestRunner (replacing their local createRunner), and remove the duplicated
local definitions so future changes are made in one place.
packages/api/src/platforms/vtex/index.ts (1)

67-71: Remove commented-out dead code.

The VtexResolver function is fully commented out. If it's no longer needed, remove it to keep the codebase clean. If it's planned for future use, track it in an issue instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/api/src/platforms/vtex/index.ts` around lines 67 - 71, Remove the
dead commented-out VtexResolver block: delete the commented function and its
lines referencing VtexResolver, GraphqlResolver, ResolverTrace, and
GraphqlContext; if this behavior is required later, open an issue referencing
VtexResolver and ResolverTrace instead of leaving the commented code in
packages/api/src/platforms/vtex/index.ts.
packages/api/src/observability/telemetry.ts (1)

7-14: Consider tightening the info parameter type.

info: any on lines 13 and 20 could be GraphQLResolveInfo from the graphql package. This would improve type safety and catch misuse at compile time without any runtime cost.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/api/src/observability/telemetry.ts` around lines 7 - 14, The
ResolverTrace function currently types its fourth parameter as info: any;
tighten this by importing GraphQLResolveInfo from the graphql package and
changing the parameter type in ResolverTrace (and any internal uses) to info:
GraphQLResolveInfo so callers and implementations get proper compile-time type
checking; update both the function signature (fn: (source, vars, context, info:
GraphQLResolveInfo) => TReturn) and the ResolverTrace declaration that
references info to use GraphQLResolveInfo.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/api/src/platforms/vtex/index.ts`:
- Around line 80-96: getResolvers() has two issues: the parameter name in the
map ([name, resolverFunction]) shadows the module-level name import and
finalResolvers is typed as any which defeats the final "satisfies typeof
resolvers" check. Fix by renaming the destructured variable to a non-conflicting
identifier (e.g., fieldName) in the Object.entries(...).map call so it doesn't
shadow the imported name, and give finalResolvers a proper type instead of any
(for example derive its shape from nativeGetResolvers() or use Partial<typeof
resolvers> / Record<string, typeof resolver> as appropriate) so the satisfies
typeof resolvers check is meaningful; keep wrapping resolver functions with
ResolverTrace(`${key}(${fieldName})`) and preserve use of nativeGetResolvers and
getResolvers.

In `@packages/api/vite.config.mts`:
- Line 6: The build is externalizing everything from devDependencies which
causes runtime packages to be omitted from the bundle; update the externals
logic in vite.config.mts to only use dependencies and peerDependencies (remove
devDependencies from the externals array/merge) and move runtime packages such
as `@envelop/`*, `@faststore/diagnostics`, `@opentelemetry/api`, graphql,
graphql-yoga, and tslib into package.json dependencies or peerDependencies as
appropriate, keeping only true build-time tools (vite, vitest,
`@graphql-codegen/`*) in devDependencies; reference the existing variables
dependencies, peerDependencies, devDependencies and the externals handling in
vite.config.mts when making the change.

In `@packages/diagnostics/src/config.json`:
- Around line 6-16: The "critical_endpoints" rule currently sets sampleRate: 1.0
which will trace every request to endpoints matching attribute "http.target"
value "^/api/graphql"; reduce the default sampling to a lower value (e.g., 0.1)
or make it environment-dependent by reading an env var and applying it via the
diagnostics config hot-reload mechanism so production uses a lower rate while
allowing full sampling in debug/staging; update the "critical_endpoints" entry
to use the new sampling value or env-backed value and ensure the config reload
logic respects the changed key.

In `@packages/diagnostics/src/start.ts`:
- Around line 54-55: The calls to newLogsClient() and newMetricsClient() are
being invoked without any logs/metrics exporter configuration in config.json, so
they will fall back to defaults; either add explicit logs and metrics exporter
sections to config.json (e.g., specify exporter type and options) or update the
code around newLogsClient() and newMetricsClient() to pass the intended exporter
config/options, and if the clients are intentionally left as no-ops add a
concise comment above the calls referencing newLogsClient() and
newMetricsClient() that this is deliberate and will be configured later so
reviewers understand the intent.
- Line 49: The use of require.resolve for configPath will break in ESM builds;
inside getTelemetryClient replace
require.resolve('@faststore/diagnostics/config') with an ESM-safe resolution
(either create a require via createRequire(import.meta.url).resolve(...) or use
await import.meta.resolve(...)) so the code runs in both CJS and ESM. Update the
top of the module to import createRequire from 'module' if you choose that
approach, or make the resolution async if using import.meta.resolve; ensure the
symbol configPath in getTelemetryClient uses the chosen resolver instead of
require.resolve.

In `@packages/diagnostics/vite.config.mts`:
- Line 10: Remove the unnecessary type assertion by updating the Vite config's
plugins array to use dts() directly; locate the plugins: [dts() as any] entry
and change it to plugins: [dts()] so it matches sibling packages (api,
components, sdk, ui) and removes the redundant cast.

---

Outside diff comments:
In `@packages/api/local/server.mjs`:
- Around line 11-23: The apiOptions object used to build context for
GraphqlVtexContextFactory is missing the OTEL property, causing ctx.OTEL to be
undefined and violating the GraphqlContext contract; update the apiOptions
constant to include an OTEL field (e.g., OTEL: {}) so GraphqlVtexContextFactory
receives OTEL and ctx.OTEL is defined when creating the Yoga server with
GraphqlVtexSchema() and GraphqlVtexContextFactory(apiOptions).

In `@packages/api/src/platforms/vtex/index.ts`:
- Around line 43-65: The factory currently generates a single `id` once in
GraphqlVtexContextFactory and reuses it for all requests; move the
`crypto.randomBytes(32).toString('hex')` call into the returned closure so a
fresh request-scoped ID is assigned to `ctx.id` on each invocation (update the
returned function that sets `ctx.id`, leaving other initializations like
`ctx.storage`, `ctx.clients`, `ctx.loaders`, `ctx.account`, and `ctx.OTEL`
unchanged).

---

Duplicate comments:
In `@packages/api/src/observability/telemetry.ts`:
- Around line 50-53: The returnedValue Promise handling in the wrapper doesn't
end the span on rejection, so update the Promise chain for returnedValue (the
variable returned from the resolver) to attach a .catch(...) that calls
span.end() before rethrowing the error; keep the existing .then(...) that ends
the span on success and returns the value, and ensure both success and error
paths always call span.end() (refer to returnedValue, span.end, and the
surrounding resolver wrapper function).

In `@packages/diagnostics/vite.config.mts`:
- Around line 15-17: The bundle is emitting mismatched extensions due to the
static fileName '[format]/index'; update the Vite config (the
entry/formats/fileName block in packages/diagnostics/vite.config.mts) to supply
a fileName function that returns explicit extensions per format (e.g., return
'cjs/index.cjs' for 'cjs' and 'es/index.js' for 'es') so output extensions are
pinned regardless of package.json "type"; after changing fileName, verify
packages/diagnostics/package.json "type" and the "require" export path point to
the correct extension (e.g., ./dist/cjs/index.cjs) and adjust the "require"
field if you prefer Vite's default naming instead.

---

Nitpick comments:
In `@packages/api/src/observability/telemetry.ts`:
- Around line 7-14: The ResolverTrace function currently types its fourth
parameter as info: any; tighten this by importing GraphQLResolveInfo from the
graphql package and changing the parameter type in ResolverTrace (and any
internal uses) to info: GraphQLResolveInfo so callers and implementations get
proper compile-time type checking; update both the function signature (fn:
(source, vars, context, info: GraphQLResolveInfo) => TReturn) and the
ResolverTrace declaration that references info to use GraphQLResolveInfo.

In `@packages/api/src/platforms/vtex/index.ts`:
- Around line 67-71: Remove the dead commented-out VtexResolver block: delete
the commented function and its lines referencing VtexResolver, GraphqlResolver,
ResolverTrace, and GraphqlContext; if this behavior is required later, open an
issue referencing VtexResolver and ResolverTrace instead of leaving the
commented code in packages/api/src/platforms/vtex/index.ts.

In `@packages/api/test/integration/queries.test.ts`:
- Around line 114-291: The tests duplicate the createRunner setup across
queries.test.ts and mutations.test.ts; extract it into a shared test utility to
DRY the code. Create a new helper (e.g., export function createTestRunner or
re-export createRunner) that encapsulates the apiOptions shape and
schema/context wiring used in both files, move the existing createRunner
implementation into that helper, update queries.test.ts and mutations.test.ts to
import and call the shared createTestRunner (replacing their local
createRunner), and remove the duplicated local definitions so future changes are
made in one place.

In `@packages/api/vite.config.mts`:
- Line 12: The sourcemap config currently uses 'inline' for non-production which
embeds base64 maps into files; update the sourcemap expression in
vite.config.mts (the sourcemap option) to generate external maps for dev by
returning true instead of 'inline' (e.g., keep production as 'hidden' but change
the non-production branch to true) so source maps are emitted as separate .map
files and dist artifacts stay lean.

In `@packages/core/src/server/options.ts`:
- Around line 27-36: Change withTraceClient to accurately reflect that it
augments the input options with an OTEL field and to constrain the generic: make
the type parameter T extend object (e.g., <T extends object>) and change the
return type to Promise<T & { OTEL: Record<string, unknown> }> (or a more
specific OTEL type), rename the parameter (e.g., inputOptions) to avoid
shadowing the module-level apiOptions, remove the unsafe "as T" cast, and return
{ ...inputOptions, OTEL } typed as T & { OTEL: ... } so the compiler knows the
OTEL augmentation is present; keep the getTraceClient(name)?.inject(OTEL) call
unchanged.

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: 7

♻️ Duplicate comments (7)
packages/diagnostics/vite.config.mts (2)

14-18: CJS output extension will not match the "require" field in package.json.

This was already raised. Vite emits .cjs for the cjs format when using a string template fileName, but package.json declares "require": "./dist/cjs/index.js". Use a function to control per-format extensions:

♻️ Proposed fix
-      fileName: '[format]/index',
+      fileName: (format) =>
+        format === 'cjs' ? 'cjs/index.js' : 'es/index.mjs',
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/diagnostics/vite.config.mts` around lines 14 - 18, Vite is emitting
.cjs for the "cjs" format because fileName is a string template; update the
lib.fileName to a function so you can return the correct extension per format
(e.g., for format === 'cjs' return 'cjs/index.js', otherwise return
`${format}/index`) so the produced CJS bundle matches the "require" field in
package.json (./dist/cjs/index.js); modify the lib config's fileName property
accordingly.

10-10: Remove the as any cast from dts().

The unnecessary as any was already flagged. Official vite-plugin-dts docs and examples consistently show plugins: [dts()] without any type assertion.

♻️ Proposed fix
-  plugins: [dts() as any],
+  plugins: [dts()],
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/diagnostics/vite.config.mts` at line 10, Remove the unnecessary type
assertion on the dts plugin: replace the plugins entry using "dts() as any" with
just "dts()". If TypeScript then complains about plugin array typing, import the
Vite Plugin type and type the plugins array (e.g., plugins: Plugin[] or annotate
the config via defineConfig) so dts() can be used without "as any"; this change
targets the plugins array and the dts() call.
packages/diagnostics/package.json (1)

1-42: publishConfig is still missing.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/diagnostics/package.json` around lines 1 - 42, Package is missing a
publishConfig entry in package.json; add a "publishConfig" object to the package
manifest (e.g., set "access": "public" or other org-appropriate settings) so the
package publishes with the correct registry and access. Update the package.json
top-level to include "publishConfig": { "access": "public" } (or your org's
registry URL/registry key) adjacent to existing fields like "exports" and
"packageManager" to ensure correct publishing behavior.
packages/diagnostics/src/start.ts (2)

61-62: newLogsClient() and newMetricsClient() called without exporter config — results are unused.

These calls create clients that fall back to library defaults (likely no-op). If they're intentional placeholders, assign the results to variables or add a comment clarifying intent. Otherwise, remove them to avoid unnecessary overhead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/diagnostics/src/start.ts` around lines 61 - 62, The calls to
client.newLogsClient() and client.newMetricsClient() currently produce unused
clients and fall back to library defaults; either remove these calls to avoid
unnecessary overhead or explicitly capture and use them (e.g., assign to
variables like logsClient and metricsClient) or add a clarifying comment that
they are intentional placeholders; update the code around the newLogsClient and
newMetricsClient invocations to either (1) remove the calls, (2) assign the
returned clients to variables and use or export them, or (3) add a clear inline
comment explaining they are intentional no-op/default-initialization
placeholders.

73-74: console.log leaks opt.account in dev mode.

The opt object contains account — a customer identifier. Even in dev mode, prefer structured logging that redacts or omits sensitive fields.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/diagnostics/src/start.ts` around lines 73 - 74, The dev-mode
console.log leaks sensitive customer identifiers via the opt object; update the
conditional that checks globalThis.fsDiagnostics.IS_DEV to use structured
logging and redact or omit opt.account before emitting (e.g., create a
sanitizedOpt = { ...opt, account: '[REDACTED]' } or delete sanitizedOpt.account)
and pass that sanitized object to your logger instead of console.log so the
original opt.account is never printed; locate the check around
globalThis.fsDiagnostics.IS_DEV in start.ts and replace the console.log call
with a call to your project logger (or console.info) using the sanitized object.
packages/api/src/platforms/vtex/index.ts (1)

86-98: name shadows module-level import; finalResolvers: any defeats satisfies.

These were flagged previously. The name on line 89 shadows the name imported from package.json (line 5), and finalResolvers: any makes as typeof resolvers a no-op for type checking.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/api/src/platforms/vtex/index.ts` around lines 86 - 98, The loop
currently shadows the module-level import name and disables type checking by
using any; rename the map callback parameter 'name' to something non-conflicting
(e.g., fieldName) in the Object.entries(resolver).map(([fieldName,
resolverFunction]) => ...) and remove finalResolvers: any by initializing
finalResolvers with a proper type (for example const finalResolvers = {} as
typeof resolvers or use Partial<typeof resolvers> and cast at the end) so the
ResolverTrace wrapping (ResolverTrace and resolvers) preserves static types.
packages/api/src/observability/telemetry.ts (1)

54-57: ⚠️ Potential issue | 🟠 Major

Promise rejection still leaks the span.

The try/catch handles synchronous throws (good), but if the resolver returns a rejected promise, .then() won't fire and there's no .catch() to end the span. This was flagged previously and only partially addressed.

Proposed fix
         return returnedValue.then((value) => {
           span.end()
           return value
-        }) as TReturn
+        }).catch((err) => {
+          span.setStatus?.({ code: OTELAPI.SpanStatusCode.ERROR })
+          span.end()
+          throw err
+        }) as TReturn

As per coding guidelines, "Proper error handling and validation".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/api/src/observability/telemetry.ts` around lines 54 - 57, The
current code ends the span only on promise fulfillment because it uses
returnedValue.then(...), which leaks the span on rejections; update the promise
handling around returnedValue and span (the variables in this block) to ensure
span.end() is always called (e.g., use returnedValue.finally(() => span.end())
and preserve the original return behavior/cast to TReturn) or attach both
.then(...) and .catch(...) where the catch rethrows the error after ending the
span so async rejections don't leak the span.
🧹 Nitpick comments (7)
packages/diagnostics/vite.config.mts (2)

24-27: dependencies ?? {} is a no-op — the default is already set on line 6.

const { dependencies = {}, ... } ensures dependencies is always {} at minimum; the ?? {} on line 25 is unreachable. devDependencies ?? {} on line 26 is correct and necessary since it has no destructuring default.

♻️ Proposed fix
       ...Object.keys({
-        ...(dependencies ?? {}),
+        ...dependencies,
         ...(devDependencies ?? {}),
       }),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/diagnostics/vite.config.mts` around lines 24 - 27, The expression
using "dependencies ?? {}" is redundant because dependencies already has a
default in the destructuring; remove the "?? {}" so the spread uses
"dependencies" directly and keep "devDependencies ?? {}" as-is; locate the
object spread around Object.keys(...) (the snippet that currently reads
"...Object.keys({ ...(dependencies ?? {}), ...(devDependencies ?? {}), })") and
change it to "...Object.keys({ ...(dependencies), ...(devDependencies ?? {}),
})" (or alternatively remove the destructuring default and keep both "?? {}" —
pick the simpler fix of removing "?? {}" after "dependencies").

9-9: Replace with process.cwd() directly.

process.env.PWD is Unix-specific and unreliable across CI environments. While this pattern is used consistently across multiple Vite configs in the workspace (packages/ui, packages/api, packages/sdk, packages/components), the correct approach—as shown in packages/core/next.config.js—is to use process.cwd() directly.

♻️ Proposed fix
-  root: process.env.PWD ?? process.cwd(),
+  root: process.cwd(),

Consider a coordinated update across all Vite configs if there's a separate refactoring pass.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/diagnostics/vite.config.mts` at line 9, The Vite config sets root
using the Unix-specific expression process.env.PWD ?? process.cwd(); change the
assignment to use process.cwd() directly (i.e., root: process.cwd()) in the
exported Vite config object to ensure consistent behavior across CI
environments; update the same pattern wherever the config assigns root using
process.env.PWD to keep configs consistent.
packages/diagnostics/package.json (1)

33-33: Drop the stream polyfill — it's a browser shim and a no-op in Node.js.

The npm stream package at 0.0.3 is a browser-compatibility wrapper that simply re-exports the Node.js built-in. This package is a server-side OpenTelemetry package; stream is already available natively in Node.js, so the dependency adds unnecessary installation overhead without any benefit. If a bundler-shim is genuinely required by some transitive tool, move it to devDependencies.

♻️ Proposed fix
-    "stream": "^0.0.3"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/diagnostics/package.json` at line 33, Remove the unnecessary browser
polyfill dependency "stream": "^0.0.3" from the package.json in the diagnostics
package; this is a no-op in Node.js, so delete the "stream" entry from the
"dependencies" section and, if a bundler shim is truly required only for
development/build, add it to "devDependencies" instead (or leave it out
entirely) and update package-lock/yarn.lock by reinstalling to ensure lockfile
consistency.
packages/diagnostics/src/index.ts (1)

2-3: Re-exporting entire OTel namespaces may hinder tree-shaking.

export * as OTELAPI and export * as CONVENTIONS re-export the full @opentelemetry/api and @opentelemetry/semantic-conventions namespaces. Consumers only use a handful of symbols. If this package is ever consumed client-side, this will bloat bundles. Consider re-exporting only what's needed:

export { context, SpanKind, SpanStatusCode } from '@opentelemetry/api'
export { ATTR_CODE_FUNCTION_NAME, ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from '@opentelemetry/semantic-conventions'

Since this is server-side today, this is a nice-to-have.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/diagnostics/src/index.ts` around lines 2 - 3, The current re-exports
export * as OTELAPI and export * as CONVENTIONS pull entire `@opentelemetry`
namespaces and hurt tree-shaking; replace these broad re-exports with narrow
named exports of only the symbols we actually use (e.g., export specific symbols
like context, SpanKind, SpanStatusCode from '@opentelemetry/api' and
ATTR_CODE_FUNCTION_NAME, ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION from
'@opentelemetry/semantic-conventions') so consumers only import required
identifiers; update the exports in the module where OTELAPI and CONVENTIONS are
declared to list those named symbols instead of exporting the full namespaces.
packages/api/src/typings/globals.ts (1)

34-39: Resolver type uses any for info — consider GraphQLResolveInfo.

Using any for the info parameter loses type safety on the GraphQL resolve info object. If you want to keep this generic, a default of GraphQLResolveInfo would be more precise while still allowing override.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/api/src/typings/globals.ts` around lines 34 - 39, The Resolver type
currently types the info parameter as any; update the signature of Resolver to
use GraphQLResolveInfo (from the 'graphql' package) as the default for the info
parameter while still allowing overrides, e.g. change the fourth parameter type
from any to GraphQLResolveInfo and add the necessary import for
GraphQLResolveInfo so Resolver<TContext, TSource, TVars, TReturn> = (source:
TSource, vars: TVars, context: TContext, info: GraphQLResolveInfo) => TReturn.
packages/api/src/observability/telemetry.ts (1)

26-28: Simplify the OTEL-enabled check.

(graphqlContext?.OTEL?.enabled ?? false) === false is a double-negative that's harder to parse. Consider:

-    if ((graphqlContext?.OTEL?.enabled ?? false) === false) {
+    if (!graphqlContext?.OTEL?.enabled) {

Both are functionally equivalent here since enabled is falsy when undefined, false, "", or 0.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/api/src/observability/telemetry.ts` around lines 26 - 28, The
current double-negative check ((graphqlContext?.OTEL?.enabled ?? false) ===
false) is confusing; replace it with a simple falsy check using the existing
symbols: use if (!graphqlContext?.OTEL?.enabled) to guard the early return in
the function that calls fn(source, vars, graphqlContext, info), keeping behavior
identical when enabled is undefined or falsy while improving readability.
packages/core/src/server/options.ts (1)

38-44: as T assertion hides the shape change.

The returned object overwrites OTEL on T, so the actual return type is Omit<T, 'OTEL'> & { OTEL: {...} }, not T. The as T masks this.

Proposed fix
-export async function withTraceClient<T extends APIOptions = typeof apiOptions>(
-  apiOptions: T
-): Promise<T> {
+export async function withTraceClient(
+  apiOptions: APIOptions
+): Promise<APIOptions> {
   const OTEL = {}
   getTraceClient(name)?.inject(OTEL)
 
   return {
     ...apiOptions,
     OTEL: {
       ...OTEL,
-      enabled: storeConfig.analytics.otelEnabled.toString() === 'true',
+      enabled: otelEnabled,
     },
-  } as T
+  }
 }

As per coding guidelines, "Ensure type safety and avoid type assertions when possible".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/server/options.ts` around lines 38 - 44, The code currently
forces the return to T with "as T" while overwriting OTEL; remove the unsafe
assertion and make the return type explicit as Omit<T, 'OTEL'> & { OTEL: typeof
OTEL & { enabled: boolean } } (or create a named type alias for that shape) and
return an object typed accordingly (e.g., const result: Omit<T,'OTEL'> & { OTEL:
typeof OTEL & { enabled: boolean } } = { ...apiOptions, OTEL: { ...OTEL,
enabled: storeConfig.analytics.otelEnabled.toString() === 'true' } }; return
result;). Ensure you reference apiOptions, OTEL and
storeConfig.analytics.otelEnabled when updating the function signature/return
type so the compiler reflects the actual shape instead of masking it with "as
T".
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/api/src/platforms/vtex/index.ts`:
- Line 44: The current implementation of GraphqlVtexContextFactory generates a
single id with crypto.randomBytes(32).toString('hex') at factory creation and
reuses it for every request; move the id generation into the returned closure so
a fresh id is created per invocation. Specifically, remove the top-level const
id and instead call crypto.randomBytes(32).toString('hex') inside the function
returned by GraphqlVtexContextFactory (the closure that builds the request
context) and use that new id when constructing the context values.

In `@packages/api/src/typings/globals.ts`:
- Around line 21-25: Change OTEL.enabled to a canonical boolean type and
normalize any string inputs where the configuration is read: update the OTEL
type definition (symbol OTEL.enabled) to boolean | undefined (or just boolean)
instead of boolean | string | undefined, and in the config loader (the code that
reads options.ts where .toString() is being called) coerce env/raw values to a
boolean (e.g., compare to 'true'/'1' or use a helper parseBoolean) so callers
can safely use boolean methods without guarding for undefined/string; update any
call sites that assumed a string (the use in options.ts that calls .toString())
to use the normalized boolean value instead.

In `@packages/core/src/server/options.ts`:
- Line 42: storeConfig.analytics.otelEnabled can be undefined which causes
.toString() to throw; update the enabled assignment to guard/normalize the value
before calling toString (or avoid toString entirely). Replace the current
expression that uses storeConfig.analytics.otelEnabled.toString() with a safe
conversion such as String(storeConfig.analytics?.otelEnabled) === 'true' or
(storeConfig.analytics?.otelEnabled ?? false) === true so the enabled field in
the options (where `enabled:` is set) never calls .toString() on undefined.
- Line 36: The call getTraceClient(name)?.inject(OTEL) is passing the core
package name so it returns undefined; change the argument to the package name
where the trace client is actually registered (use '@faststore/api') so
getTraceClient('@faststore/api')?.inject(OTEL) will find the client registered
by getTelemetryClient() in packages/api/src/platforms/vtex/index.ts; update the
call in options.ts to use the correct package name (or a shared constant
matching the registration) so inject(OTEL) runs.
- Around line 26-28: Normalize handling of storeConfig.analytics.otelEnabled so
it's converted once to a boolean and reused instead of mixing raw values and a
.toString() === 'true' check; update the OTEL config block (OTEL) to set enabled
to the normalized boolean (e.g., convert string 'true'/'false' or undefined to a
boolean) and replace any other occurrences that do .toString() === 'true' with
the normalized value to ensure consistent behavior across the module.

In `@packages/diagnostics/package.json`:
- Line 12: The package.json in the diagnostics package currently sets "license":
"ISC"; update that license field to "MIT" to match the rest of the monorepo so
downstream audits are consistent—locate the license property in
packages/diagnostics package.json and replace the value "ISC" with "MIT".

In `@packages/diagnostics/src/start.ts`:
- Around line 63-72: The TRACE_CLIENTS map is updated before TELEMETRY_CLIENTS,
creating a partial state if an error occurs; update both maps only after all
setup completes by either moving the
globalThis.fsDiagnostics.TRACE_CLIENTS.set(opt.name, ...) call to after
client.registerInstrumentations(...) and after setting
globalThis.fsDiagnostics.TELEMETRY_CLIENTS, or batch the assignments so both
TRACE_CLIENTS and TELEMETRY_CLIENTS are set atomically (e.g., prepare the new
trace client via client.newTracesClient and only call
globalThis.fsDiagnostics.TRACE_CLIENTS.set and
globalThis.fsDiagnostics.TELEMETRY_CLIENTS.set together once no errors
occurred), referencing client.newTracesClient, client.registerInstrumentations,
globalThis.fsDiagnostics.TRACE_CLIENTS,
globalThis.fsDiagnostics.TELEMETRY_CLIENTS, and opt.name.

---

Duplicate comments:
In `@packages/api/src/observability/telemetry.ts`:
- Around line 54-57: The current code ends the span only on promise fulfillment
because it uses returnedValue.then(...), which leaks the span on rejections;
update the promise handling around returnedValue and span (the variables in this
block) to ensure span.end() is always called (e.g., use returnedValue.finally(()
=> span.end()) and preserve the original return behavior/cast to TReturn) or
attach both .then(...) and .catch(...) where the catch rethrows the error after
ending the span so async rejections don't leak the span.

In `@packages/api/src/platforms/vtex/index.ts`:
- Around line 86-98: The loop currently shadows the module-level import name and
disables type checking by using any; rename the map callback parameter 'name' to
something non-conflicting (e.g., fieldName) in the
Object.entries(resolver).map(([fieldName, resolverFunction]) => ...) and remove
finalResolvers: any by initializing finalResolvers with a proper type (for
example const finalResolvers = {} as typeof resolvers or use Partial<typeof
resolvers> and cast at the end) so the ResolverTrace wrapping (ResolverTrace and
resolvers) preserves static types.

In `@packages/diagnostics/package.json`:
- Around line 1-42: Package is missing a publishConfig entry in package.json;
add a "publishConfig" object to the package manifest (e.g., set "access":
"public" or other org-appropriate settings) so the package publishes with the
correct registry and access. Update the package.json top-level to include
"publishConfig": { "access": "public" } (or your org's registry URL/registry
key) adjacent to existing fields like "exports" and "packageManager" to ensure
correct publishing behavior.

In `@packages/diagnostics/src/start.ts`:
- Around line 61-62: The calls to client.newLogsClient() and
client.newMetricsClient() currently produce unused clients and fall back to
library defaults; either remove these calls to avoid unnecessary overhead or
explicitly capture and use them (e.g., assign to variables like logsClient and
metricsClient) or add a clarifying comment that they are intentional
placeholders; update the code around the newLogsClient and newMetricsClient
invocations to either (1) remove the calls, (2) assign the returned clients to
variables and use or export them, or (3) add a clear inline comment explaining
they are intentional no-op/default-initialization placeholders.
- Around line 73-74: The dev-mode console.log leaks sensitive customer
identifiers via the opt object; update the conditional that checks
globalThis.fsDiagnostics.IS_DEV to use structured logging and redact or omit
opt.account before emitting (e.g., create a sanitizedOpt = { ...opt, account:
'[REDACTED]' } or delete sanitizedOpt.account) and pass that sanitized object to
your logger instead of console.log so the original opt.account is never printed;
locate the check around globalThis.fsDiagnostics.IS_DEV in start.ts and replace
the console.log call with a call to your project logger (or console.info) using
the sanitized object.

In `@packages/diagnostics/vite.config.mts`:
- Around line 14-18: Vite is emitting .cjs for the "cjs" format because fileName
is a string template; update the lib.fileName to a function so you can return
the correct extension per format (e.g., for format === 'cjs' return
'cjs/index.js', otherwise return `${format}/index`) so the produced CJS bundle
matches the "require" field in package.json (./dist/cjs/index.js); modify the
lib config's fileName property accordingly.
- Line 10: Remove the unnecessary type assertion on the dts plugin: replace the
plugins entry using "dts() as any" with just "dts()". If TypeScript then
complains about plugin array typing, import the Vite Plugin type and type the
plugins array (e.g., plugins: Plugin[] or annotate the config via defineConfig)
so dts() can be used without "as any"; this change targets the plugins array and
the dts() call.

---

Nitpick comments:
In `@packages/api/src/observability/telemetry.ts`:
- Around line 26-28: The current double-negative check
((graphqlContext?.OTEL?.enabled ?? false) === false) is confusing; replace it
with a simple falsy check using the existing symbols: use if
(!graphqlContext?.OTEL?.enabled) to guard the early return in the function that
calls fn(source, vars, graphqlContext, info), keeping behavior identical when
enabled is undefined or falsy while improving readability.

In `@packages/api/src/typings/globals.ts`:
- Around line 34-39: The Resolver type currently types the info parameter as
any; update the signature of Resolver to use GraphQLResolveInfo (from the
'graphql' package) as the default for the info parameter while still allowing
overrides, e.g. change the fourth parameter type from any to GraphQLResolveInfo
and add the necessary import for GraphQLResolveInfo so Resolver<TContext,
TSource, TVars, TReturn> = (source: TSource, vars: TVars, context: TContext,
info: GraphQLResolveInfo) => TReturn.

In `@packages/core/src/server/options.ts`:
- Around line 38-44: The code currently forces the return to T with "as T" while
overwriting OTEL; remove the unsafe assertion and make the return type explicit
as Omit<T, 'OTEL'> & { OTEL: typeof OTEL & { enabled: boolean } } (or create a
named type alias for that shape) and return an object typed accordingly (e.g.,
const result: Omit<T,'OTEL'> & { OTEL: typeof OTEL & { enabled: boolean } } = {
...apiOptions, OTEL: { ...OTEL, enabled:
storeConfig.analytics.otelEnabled.toString() === 'true' } }; return result;).
Ensure you reference apiOptions, OTEL and storeConfig.analytics.otelEnabled when
updating the function signature/return type so the compiler reflects the actual
shape instead of masking it with "as T".

In `@packages/diagnostics/package.json`:
- Line 33: Remove the unnecessary browser polyfill dependency "stream": "^0.0.3"
from the package.json in the diagnostics package; this is a no-op in Node.js, so
delete the "stream" entry from the "dependencies" section and, if a bundler shim
is truly required only for development/build, add it to "devDependencies"
instead (or leave it out entirely) and update package-lock/yarn.lock by
reinstalling to ensure lockfile consistency.

In `@packages/diagnostics/src/index.ts`:
- Around line 2-3: The current re-exports export * as OTELAPI and export * as
CONVENTIONS pull entire `@opentelemetry` namespaces and hurt tree-shaking; replace
these broad re-exports with narrow named exports of only the symbols we actually
use (e.g., export specific symbols like context, SpanKind, SpanStatusCode from
'@opentelemetry/api' and ATTR_CODE_FUNCTION_NAME, ATTR_SERVICE_NAME,
ATTR_SERVICE_VERSION from '@opentelemetry/semantic-conventions') so consumers
only import required identifiers; update the exports in the module where OTELAPI
and CONVENTIONS are declared to list those named symbols instead of exporting
the full namespaces.

In `@packages/diagnostics/vite.config.mts`:
- Around line 24-27: The expression using "dependencies ?? {}" is redundant
because dependencies already has a default in the destructuring; remove the "??
{}" so the spread uses "dependencies" directly and keep "devDependencies ?? {}"
as-is; locate the object spread around Object.keys(...) (the snippet that
currently reads "...Object.keys({ ...(dependencies ?? {}), ...(devDependencies
?? {}), })") and change it to "...Object.keys({ ...(dependencies),
...(devDependencies ?? {}), })" (or alternatively remove the destructuring
default and keep both "?? {}" — pick the simpler fix of removing "?? {}" after
"dependencies").
- Line 9: The Vite config sets root using the Unix-specific expression
process.env.PWD ?? process.cwd(); change the assignment to use process.cwd()
directly (i.e., root: process.cwd()) in the exported Vite config object to
ensure consistent behavior across CI environments; update the same pattern
wherever the config assigns root using process.env.PWD to keep configs
consistent.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a607a70 and 7dccb8e.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml and included by none
📒 Files selected for processing (11)
  • packages/api/src/observability/telemetry.ts
  • packages/api/src/platforms/vtex/index.ts
  • packages/api/src/typings/globals.ts
  • packages/core/src/instrumentation.ts
  • packages/core/src/server/options.ts
  • packages/diagnostics/configs/dev.json
  • packages/diagnostics/configs/prod.json
  • packages/diagnostics/package.json
  • packages/diagnostics/src/index.ts
  • packages/diagnostics/src/start.ts
  • packages/diagnostics/vite.config.mts
✅ Files skipped from review due to trivial changes (1)
  • packages/diagnostics/configs/prod.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/core/src/instrumentation.ts

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.

♻️ Duplicate comments (2)
packages/core/src/server/options.ts (2)

26-29: ⚠️ Potential issue | 🟠 Major

storeConfig.analytics.otelEnabled lacks null-safety and normalization in the static apiOptions.

Line 27 accesses storeConfig.analytics.otelEnabled without optional chaining — if analytics is absent in the config, this crashes at module-init time. The static apiOptions.OTEL.enabled also carries a raw boolean | string | undefined type while withTraceClient normalizes it to boolean via ?.toString() === 'true', creating a divergent type contract for the same field.

🐛 Proposed fix
-  OTEL: {
-    enabled: storeConfig.analytics.otelEnabled,
-  },
+  OTEL: {
+    enabled: String(storeConfig.analytics?.otelEnabled ?? false) === 'true',
+  },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/server/options.ts` around lines 26 - 29, The static
apiOptions sets OTEL.enabled directly from storeConfig.analytics.otelEnabled
which can throw if analytics is missing and leaves the field typed as
boolean|string|undefined; update apiOptions.OTEL.enabled to mirror
withTraceClient's normalization (use optional chaining and compare to 'true' or
otherwise coerce to a boolean) so it is null-safe (e.g., access
storeConfig.analytics?.otelEnabled) and always yields a boolean; change
references to OTEL.enabled and any callers expecting a boolean to rely on this
normalized boolean value.

35-36: ⚠️ Potential issue | 🟠 Major

getTraceClient(name) silently no-ops: name is @faststore/core but the client is registered under @faststore/api.

getTraceClient performs an exact key lookup; passing the core package name returns undefined, so inject(OTEL) never runs and the OTEL carrier stays empty. This means tracing is never actually wired up despite the rest of the setup.

🐛 Proposed fix
-  getTraceClient(name)?.inject(OTEL)
+  getTraceClient('@faststore/api')?.inject(OTEL)

Or, if the registration key is expected to match the consuming package, verify and align the key used in packages/api/src/platforms/vtex/index.ts.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/server/options.ts` around lines 35 - 36, The trace client
lookup is using the package name variable (name) which is '@faststore/core'
while the client was registered under '@faststore/api', so getTraceClient(name)
returns undefined and inject(OTEL) never runs; update the lookup in options.ts
to use the correct registration key (e.g., '@faststore/api') or add a fallback
lookup before calling inject on the returned client, ensuring
getTraceClient(...) returns a client before calling its inject method (refer to
getTraceClient, inject, and the OTEL carrier) so tracing is actually wired up;
alternatively, align the registration key in
packages/api/src/platforms/vtex/index.ts to match the consuming package name.
🧹 Nitpick comments (2)
packages/core/src/server/options.ts (2)

2-2: Static import eagerly loads @faststore/diagnostics unconditionally.

Previously this was a dynamic await import(...) inside withTraceClient, scoping the load to when tracing is actually used. As a static import it now loads (and executes any side effects in) @faststore/diagnostics at module-evaluation time — regardless of whether OTEL is enabled. If the package has SDK initialization side effects, they will run even for stores with otelEnabled: false.

♻️ Suggested approach
-import { getTraceClient } from '@faststore/diagnostics'
 export async function withTraceClient<T extends APIOptions = typeof apiOptions>(
   apiOptions: T
 ): Promise<T> {
   const OTEL = {}
-  getTraceClient(name)?.inject(OTEL)
+  const { getTraceClient } = await import('@faststore/diagnostics')
+  getTraceClient(name)?.inject(OTEL)

As per coding guidelines, "Consider bundle size impact of dependencies" for TypeScript files under packages/**/*.ts.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/server/options.ts` at line 2, Remove the top-level static
import of getTraceClient and restore a dynamic import inside the function that
uses it (withTraceClient) so `@faststore/diagnostics` is only loaded when tracing
is enabled; inside withTraceClient, do an awaited
import('@faststore/diagnostics') and extract getTraceClient from the imported
module before using it, and update any type annotations to reference the
imported symbol if needed to avoid eager evaluation.

38-44: as T type assertion bypasses structural verification of the return value.

The cast silently accepts any mismatch between the constructed object and T. A safer return type would be Promise<Omit<T, 'OTEL'> & { OTEL: Record<string, unknown> & { enabled: boolean } }>, which accurately reflects what is actually returned without forcing a cast.

As per coding guidelines, TypeScript files under packages/**/*.ts should "Ensure type safety and avoid type assertions when possible."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/server/options.ts` around lines 38 - 44, The current return
uses an unsafe "as T" cast which bypasses TypeScript structural checks; update
the function's return typing and remove the cast by declaring a precise return
type that matches the constructed object (e.g. Omit<T, 'OTEL'> & { OTEL:
Record<string, unknown> & { enabled: boolean } } or Promise<...> if the
surrounding function is async), then return { ...apiOptions, OTEL: { ...OTEL,
enabled: storeConfig.analytics?.otelEnabled?.toString() === 'true' } } without
"as T"; adjust the function signature (and any callers) to use that concrete
return type rather than forcing T.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@packages/core/src/server/options.ts`:
- Around line 26-29: The static apiOptions sets OTEL.enabled directly from
storeConfig.analytics.otelEnabled which can throw if analytics is missing and
leaves the field typed as boolean|string|undefined; update
apiOptions.OTEL.enabled to mirror withTraceClient's normalization (use optional
chaining and compare to 'true' or otherwise coerce to a boolean) so it is
null-safe (e.g., access storeConfig.analytics?.otelEnabled) and always yields a
boolean; change references to OTEL.enabled and any callers expecting a boolean
to rely on this normalized boolean value.
- Around line 35-36: The trace client lookup is using the package name variable
(name) which is '@faststore/core' while the client was registered under
'@faststore/api', so getTraceClient(name) returns undefined and inject(OTEL)
never runs; update the lookup in options.ts to use the correct registration key
(e.g., '@faststore/api') or add a fallback lookup before calling inject on the
returned client, ensuring getTraceClient(...) returns a client before calling
its inject method (refer to getTraceClient, inject, and the OTEL carrier) so
tracing is actually wired up; alternatively, align the registration key in
packages/api/src/platforms/vtex/index.ts to match the consuming package name.

---

Nitpick comments:
In `@packages/core/src/server/options.ts`:
- Line 2: Remove the top-level static import of getTraceClient and restore a
dynamic import inside the function that uses it (withTraceClient) so
`@faststore/diagnostics` is only loaded when tracing is enabled; inside
withTraceClient, do an awaited import('@faststore/diagnostics') and extract
getTraceClient from the imported module before using it, and update any type
annotations to reference the imported symbol if needed to avoid eager
evaluation.
- Around line 38-44: The current return uses an unsafe "as T" cast which
bypasses TypeScript structural checks; update the function's return typing and
remove the cast by declaring a precise return type that matches the constructed
object (e.g. Omit<T, 'OTEL'> & { OTEL: Record<string, unknown> & { enabled:
boolean } } or Promise<...> if the surrounding function is async), then return {
...apiOptions, OTEL: { ...OTEL, enabled:
storeConfig.analytics?.otelEnabled?.toString() === 'true' } } without "as T";
adjust the function signature (and any callers) to use that concrete return type
rather than forcing T.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7dccb8e and 3d05822.

📒 Files selected for processing (1)
  • packages/core/src/server/options.ts

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/api/src/platforms/vtex/index.ts (1)

55-69: ⚠️ Potential issue | 🔴 Critical

ctx.id is never assigned — GraphqlContext.id will always be undefined at runtime.

GraphqlContext declares id: string as non-optional (line 21), but the closure never sets ctx.id, and crypto is not imported. The per-request ID generation proposed in the past review (moving crypto.randomBytes(32).toString('hex') inside this closure) was never applied. Every consumer of ctx.id for trace correlation will silently receive undefined.

🐛 Proposed fix

Add crypto at the top of the file and assign inside the closure:

+import crypto from 'node:crypto'
 import { getTelemetryClient } from '@faststore/diagnostics'
   return (ctx: any): GraphqlContext => {
+    ctx.id = crypto.randomBytes(32).toString('hex')
     ctx.storage = {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/api/src/platforms/vtex/index.ts` around lines 55 - 69, The
GraphqlContext.id property is never set so ctx.id will be undefined at runtime;
import Node's crypto and assign a per-request id inside the returned middleware
closure (before returning ctx) using something like
crypto.randomBytes(32).toString('hex') to satisfy GraphqlContext.id; make the
change adjacent to where ctx.storage, ctx.clients (getClients) and ctx.loaders
(getLoaders) are assigned so the id is available to downstream code that reads
ctx.id.
♻️ Duplicate comments (2)
packages/diagnostics/package.json (1)

1-42: Missing publishConfig — scoped package will be published as restricted.

This was flagged in a prior review and remains unaddressed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/diagnostics/package.json` around lines 1 - 42, The package.json for
`@faststore/diagnostics` is missing a publishConfig, causing the scoped package to
be published with restricted access; add a top-level "publishConfig": {
"access": "public" } entry in package.json (near the existing "name"/"version"
fields) so the package is published as public; ensure the package name
"@faststore/diagnostics" remains unchanged and validate the JSON after adding
the publishConfig.
packages/api/src/platforms/vtex/index.ts (1)

85-89: name still shadows the module-level name import from package.json.

The destructured name in .map(([name, resolverFunction]) at line 85 shadows the top-level name imported on line 4. Despite the previous thread indicating a rename was applied, the current code still shows the conflicting identifier.

♻️ Proposed rename
-      Object.entries(resolver).map(([name, resolverFunction]) => {
+      Object.entries(resolver).map(([fieldName, resolverFunction]) => {
         if (typeof resolverFunction !== 'function')
-          return [name, resolverFunction]
+          return [fieldName, resolverFunction]

-        return [name, ResolverTrace(resolverFunction, `${key}(${name})`)]
+        return [fieldName, ResolverTrace(resolverFunction, `${key}(${fieldName})`)]
       })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/api/src/platforms/vtex/index.ts` around lines 85 - 89, The
destructured variable "name" in Object.entries(resolver).map(([name,
resolverFunction]) shadows the module-level import "name" from package.json;
rename that parameter (e.g., to resolverName or fieldName) wherever used in this
mapping and in the created trace label `${key}(${name})` so it no longer
collides with the imported "name", keeping the mapping logic (including the
ResolverTrace(resolverFunction, `${key}(<newName>)`)) unchanged.
🧹 Nitpick comments (2)
packages/diagnostics/package.json (2)

7-9: Add a prepublishOnly script to guard against publishing unbuilt artifacts.

Without it, publishing with a stale or missing dist/ directory silently ships broken output.

🛡️ Proposed fix
   "scripts": {
-    "build": "vite build"
+    "build": "vite build",
+    "prepublishOnly": "vite build"
   },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/diagnostics/package.json` around lines 7 - 9, The package's scripts
object only defines "build" and lacks a prepublish hook, so publishing can ship
unbuilt artifacts; add a "prepublishOnly" script to package.json that runs the
build (e.g., set "prepublishOnly": "npm run build") alongside the existing
"build" entry to ensure the dist/ output is produced before any publish step.

28-29: Declare @opentelemetry/api (and instrumentation-http) as peerDependencies in a library package.

Two concerns:

  1. Context breakage: If you're instrumenting a library, only install the OpenTelemetry API package. Your library will not emit telemetry on its own — it will only emit telemetry when it is part of an app that uses the OpenTelemetry SDK. Shipping @opentelemetry/api as a hard dependency risks bundling a second instance, which severs context propagation between this package and the host app's tracer.

  2. Double-instrumentation: @opentelemetry/instrumentation-http patches node:http at load time. If the host app already registers this instrumentation (e.g. via @opentelemetry/auto-instrumentations-node) and this package installs its own copy, HTTP spans will be doubled.

Move both to peerDependencies:

♻️ Proposed refactor
+  "peerDependencies": {
+    "@opentelemetry/api": "^1.9.0",
+    "@opentelemetry/instrumentation-http": "^0.212.0"
+  },
   "dependencies": {
-    "@opentelemetry/api": "^1.9.0",
-    "@opentelemetry/instrumentation-http": "^0.212.0",
     "@opentelemetry/semantic-conventions": "^1.39.0",
     "@vtex/diagnostics-nodejs": "^5.1.1",
     "resolve-pkg": "^3.0.1",
     "stream": "^0.0.3"
   },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/diagnostics/package.json` around lines 28 - 29, The package
currently lists "@opentelemetry/api" and "@opentelemetry/instrumentation-http"
as hard dependencies; remove them from dependencies and declare them in
peerDependencies (keeping the same semver ranges) so host apps provide the
single shared OpenTelemetry instances; also add them to devDependencies if you
need them for tests/builds inside the package; update any installation docs if
present.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/api/src/platforms/vtex/index.ts`:
- Line 39: GraphqlContext declares OTEL as non-optional but the factory assigns
options.OTEL which can be undefined; either make GraphqlContext.OTEL optional or
ensure the factory always sets a default object — e.g. change the factory
assignment of ctx.OTEL (the place where options.OTEL is copied into ctx) to use
a fallback (options.OTEL ?? {}) so downstream code never sees undefined; apply
the same fix to the other OTEL assignment elsewhere in this module to keep the
contract consistent.

In `@packages/diagnostics/package.json`:
- Line 33: Remove the unnecessary browser polyfill dependency "stream": "^0.0.3"
from the diagnostics package.json; locate the dependency entry `"stream":
"^0.0.3"` and delete it from the "dependencies" section, then run your package
manager to update lockfile (e.g., npm install / pnpm install) and ensure no code
imports the npm "stream" package (search for any require/import of "stream") so
Node's built-in stream module is used instead.

---

Outside diff comments:
In `@packages/api/src/platforms/vtex/index.ts`:
- Around line 55-69: The GraphqlContext.id property is never set so ctx.id will
be undefined at runtime; import Node's crypto and assign a per-request id inside
the returned middleware closure (before returning ctx) using something like
crypto.randomBytes(32).toString('hex') to satisfy GraphqlContext.id; make the
change adjacent to where ctx.storage, ctx.clients (getClients) and ctx.loaders
(getLoaders) are assigned so the id is available to downstream code that reads
ctx.id.

---

Duplicate comments:
In `@packages/api/src/platforms/vtex/index.ts`:
- Around line 85-89: The destructured variable "name" in
Object.entries(resolver).map(([name, resolverFunction]) shadows the module-level
import "name" from package.json; rename that parameter (e.g., to resolverName or
fieldName) wherever used in this mapping and in the created trace label
`${key}(${name})` so it no longer collides with the imported "name", keeping the
mapping logic (including the ResolverTrace(resolverFunction,
`${key}(<newName>)`)) unchanged.

In `@packages/diagnostics/package.json`:
- Around line 1-42: The package.json for `@faststore/diagnostics` is missing a
publishConfig, causing the scoped package to be published with restricted
access; add a top-level "publishConfig": { "access": "public" } entry in
package.json (near the existing "name"/"version" fields) so the package is
published as public; ensure the package name "@faststore/diagnostics" remains
unchanged and validate the JSON after adding the publishConfig.

---

Nitpick comments:
In `@packages/diagnostics/package.json`:
- Around line 7-9: The package's scripts object only defines "build" and lacks a
prepublish hook, so publishing can ship unbuilt artifacts; add a
"prepublishOnly" script to package.json that runs the build (e.g., set
"prepublishOnly": "npm run build") alongside the existing "build" entry to
ensure the dist/ output is produced before any publish step.
- Around line 28-29: The package currently lists "@opentelemetry/api" and
"@opentelemetry/instrumentation-http" as hard dependencies; remove them from
dependencies and declare them in peerDependencies (keeping the same semver
ranges) so host apps provide the single shared OpenTelemetry instances; also add
them to devDependencies if you need them for tests/builds inside the package;
update any installation docs if present.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3d05822 and 2f32504.

⛔ Files ignored due to path filters (2)
  • package.json is excluded by none and included by none
  • turbo.json is excluded by none and included by none
📒 Files selected for processing (3)
  • packages/api/src/platforms/vtex/index.ts
  • packages/api/src/typings/globals.ts
  • packages/diagnostics/package.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/api/src/typings/globals.ts

}
headers: Record<string, string>
account: string
OTEL: Record<string, unknown>
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

ctx.OTEL can be undefined — type contract with GraphqlContext is broken.

options.OTEL is demonstrably optional (optional-chaining on line 43), so ctx.OTEL = options.OTEL assigns undefined when OTEL is disabled. Since ctx is typed any, TypeScript silently accepts this, but GraphqlContext.OTEL: Record<string, unknown> is non-optional — any downstream code relying on the interface contract will get undefined.

Either make the field optional or default it to {}:

🛡️ Option A — make the field optional in the interface
-  OTEL: Record<string, unknown>
+  OTEL?: Record<string, unknown>
🛡️ Option B — default to `{}` in the factory closure
-    ctx.OTEL = options.OTEL
+    ctx.OTEL = options.OTEL ?? {}

Also applies to: 65-65

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/api/src/platforms/vtex/index.ts` at line 39, GraphqlContext declares
OTEL as non-optional but the factory assigns options.OTEL which can be
undefined; either make GraphqlContext.OTEL optional or ensure the factory always
sets a default object — e.g. change the factory assignment of ctx.OTEL (the
place where options.OTEL is copied into ctx) to use a fallback (options.OTEL ??
{}) so downstream code never sees undefined; apply the same fix to the other
OTEL assignment elsewhere in this module to keep the contract consistent.

"@opentelemetry/semantic-conventions": "^1.39.0",
"@vtex/diagnostics-nodejs": "^5.1.1",
"resolve-pkg": "^3.0.1",
"stream": "^0.0.3"
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

stream ^0.0.3 is a browser polyfill — unnecessary in a server-side Node.js package.

The stream npm package (published 2012) is a legacy browser-compat shim for Node's built-in stream module. A server-side diagnostics package running on Node.js already has stream available natively; shipping this adds dead weight and could shadow the built-in in bundled contexts.

🐛 Proposed fix
-    "stream": "^0.0.3"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/diagnostics/package.json` at line 33, Remove the unnecessary browser
polyfill dependency "stream": "^0.0.3" from the diagnostics package.json; locate
the dependency entry `"stream": "^0.0.3"` and delete it from the "dependencies"
section, then run your package manager to update lockfile (e.g., npm install /
pnpm install) and ensure no code imports the npm "stream" package (search for
any require/import of "stream") so Node's built-in stream module is used
instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant